-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathclient_functions.go
150 lines (129 loc) · 3.97 KB
/
client_functions.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
package egobee
import (
"encoding/json"
"errors"
"fmt"
"io"
"net/http"
"net/url"
)
const (
requestContentType = "application/json; charset=utf-8"
)
// These are simple stubs which allow for easy overrides in testing without a
// heavy mocks library.
var (
httpNewRequest = http.NewRequest
jsonMarshal = json.Marshal
errPagingUnimplemented = errors.New("multi-page responses unimplemented")
// jsonDecode wraps the usual JSON decode workflow to make testing easier.
jsonDecode = func(from io.Reader, to interface{}) error {
if err := json.NewDecoder(from).Decode(to); err != nil {
return fmt.Errorf("failed to decode JSON: %v", err)
}
return nil
}
)
// page is used for paging in some APIs.
type page struct {
Page int `json:"page"`
TotalPages int `json:"totalPages"`
PageSize int `json:"pageSize"`
Total int `json:"total"`
}
// summarySelection wraps a Selection, and serializes to the format expected by
// the thermostatSummary API.
type summarySelection struct {
Selection Selection `json:"selection,omitempty"`
}
func assembleSelectionURL(apiURL string, selection *Selection) (string, error) {
ss := &summarySelection{
Selection: *selection,
}
qb, err := jsonMarshal(ss)
if err != nil {
return "", err
}
return fmt.Sprintf(`%v?json=%v`, apiURL, url.QueryEscape(string(qb))), nil
}
func assembleSelectionRequest(url string, s *Selection) (*http.Request, error) {
u, err := assembleSelectionURL(url, s)
if err != nil {
return nil, err
}
r, err := httpNewRequest(http.MethodGet, u, nil)
if err != nil {
return nil, fmt.Errorf("failed to create request: %v", err)
}
r.Header.Add("Content-Type", requestContentType)
return r, nil
}
// validateSelectionResponse validates that a http.Response resulting from a
// selection request is actually usable.
func validateSelectionResponse(res *http.Response) error {
if (res.StatusCode / 100) != 2 {
return fmt.Errorf("non-ok status response from API: %v %v", res.StatusCode, res.Status)
}
return nil
}
// ThermostatSummary retrieves a list of thermostat configuration and state
// revisions. This API request is a light-weight polling method which will only
// return the revision numbers for the significant portions of the thermostat
// data.
// See https://www.ecobee.com/home/developer/api/documentation/v1/operations/get-thermostat-summary.shtml
func (c *Client) ThermostatSummary() (*ThermostatSummary, error) {
req, err := assembleSelectionRequest(c.api.URL(thermostatSummaryURL), &Selection{
SelectionType: SelectionTypeRegistered,
IncludeEquipmentStatus: true,
IncludeAlerts: true,
})
if err != nil {
return nil, err
}
res, err := c.Do(req)
if err != nil {
return nil, fmt.Errorf("failed to Do(): %v", err)
}
defer res.Body.Close()
if err := validateSelectionResponse(res); err != nil {
return nil, err
}
ts := &ThermostatSummary{}
if err := jsonDecode(res.Body, ts); err != nil {
return nil, err
}
return ts, nil
}
// See https://www.ecobee.com/home/developer/api/documentation/v1/operations/get-thermostats.shtml
type pagedThermostatResponse struct {
Page page `json:"page,omitempty"`
Thermostats []*Thermostat `json:"thermostatList,omitempty"`
Status struct {
Code int `json:"code,omitempty"`
Message string `json:"message,omitempty"`
} `json:"status,omitempty"`
}
// Thermostats returns all Thermostat objects which match selection.
func (c *Client) Thermostats(selection *Selection) ([]*Thermostat, error) {
req, err := assembleSelectionRequest(c.api.URL(thermostatURL), selection)
if err != nil {
return nil, err
}
res, err := c.Do(req)
if err != nil {
return nil, err
}
defer res.Body.Close()
if err := validateSelectionResponse(res); err != nil {
return nil, err
}
ptr := &pagedThermostatResponse{}
if err := jsonDecode(res.Body, ptr); err != nil {
return nil, err
}
if ptr.Page.Page != ptr.Page.TotalPages {
// TODO(cfunkhouser): Handle paged responses.
return nil, errPagingUnimplemented
}
return ptr.Thermostats, nil
}