github.com/amimof/huego@v1.2.1/huego.go (about) 1 // Package huego provides an extensive, easy to use interface to the Philips Hue bridge. 2 package huego 3 4 import ( 5 "context" 6 "encoding/json" 7 "fmt" 8 "io/ioutil" 9 "net/http" 10 "strings" 11 ) 12 13 const ( 14 applicationJSON = "application/json" 15 contentType = "Content-Type" 16 ) 17 18 // APIResponse holds the response data returned form the bridge after a request has been made. 19 type APIResponse struct { 20 Success map[string]interface{} `json:"success,omitempty"` 21 Error *APIError `json:"error,omitempty"` 22 } 23 24 // APIError defines the error response object returned from the bridge after an invalid API request. 25 type APIError struct { 26 Type int 27 Address string 28 Description string 29 } 30 31 // Response is a wrapper struct of the success response returned from the bridge after a successful API call. 32 type Response struct { 33 Success map[string]interface{} 34 } 35 36 // UnmarshalJSON makes sure that types are correct when unmarshalling. Implements package encoding/json 37 func (a *APIError) UnmarshalJSON(data []byte) error { 38 var aux map[string]interface{} 39 err := json.Unmarshal(data, &aux) 40 if err != nil { 41 return err 42 } 43 a.Type = int(aux["type"].(float64)) 44 a.Address = aux["address"].(string) 45 a.Description = aux["description"].(string) 46 return nil 47 } 48 49 // Error returns an error string 50 func (a *APIError) Error() string { 51 return fmt.Sprintf("ERROR %d [%s]: \"%s\"", a.Type, a.Address, a.Description) 52 } 53 54 func handleResponse(a []*APIResponse) (*Response, error) { 55 success := map[string]interface{}{} 56 for _, r := range a { 57 if r.Success != nil { 58 for k, v := range r.Success { 59 success[k] = v 60 } 61 } 62 if r.Error != nil { 63 return nil, r.Error 64 } 65 } 66 resp := &Response{Success: success} 67 return resp, nil 68 } 69 70 // unmarshal will try to unmarshal data into APIResponse so that we can 71 // return the actual error returned by the bridge http API as an error struct. 72 func unmarshal(data []byte, v interface{}) error { 73 err := json.Unmarshal(data, &v) 74 if err != nil { 75 var a []*APIResponse 76 err = json.Unmarshal(data, &a) 77 if err != nil { 78 return err 79 } 80 _, err = handleResponse(a) 81 if err != nil { 82 return err 83 } 84 } 85 return nil 86 } 87 88 func get(ctx context.Context, url string) ([]byte, error) { 89 90 req, err := http.NewRequest(http.MethodGet, url, nil) 91 if err != nil { 92 return nil, err 93 } 94 95 req = req.WithContext(ctx) 96 97 client := http.DefaultClient 98 res, err := client.Do(req) 99 if err != nil { 100 return nil, err 101 } 102 103 defer res.Body.Close() 104 105 body, err := ioutil.ReadAll(res.Body) 106 107 if err != nil { 108 return nil, err 109 } 110 111 return body, nil 112 } 113 114 func put(ctx context.Context, url string, data []byte) ([]byte, error) { 115 116 body := strings.NewReader(string(data)) 117 118 req, err := http.NewRequest(http.MethodPut, url, body) 119 if err != nil { 120 return nil, err 121 } 122 123 req = req.WithContext(ctx) 124 125 req.Header.Set(contentType, applicationJSON) 126 127 client := http.DefaultClient 128 res, err := client.Do(req) 129 if err != nil { 130 return nil, err 131 } 132 133 defer res.Body.Close() 134 135 result, err := ioutil.ReadAll(res.Body) 136 if err != nil { 137 return nil, err 138 } 139 140 return result, nil 141 142 } 143 144 func post(ctx context.Context, url string, data []byte) ([]byte, error) { 145 146 body := strings.NewReader(string(data)) 147 148 req, err := http.NewRequest(http.MethodPost, url, body) 149 if err != nil { 150 return nil, err 151 } 152 153 req = req.WithContext(ctx) 154 155 req.Header.Set(contentType, applicationJSON) 156 157 client := http.DefaultClient 158 res, err := client.Do(req) 159 if err != nil { 160 return nil, err 161 } 162 163 defer res.Body.Close() 164 165 result, err := ioutil.ReadAll(res.Body) 166 if err != nil { 167 return nil, err 168 } 169 170 return result, nil 171 172 } 173 174 func delete(ctx context.Context, url string) ([]byte, error) { 175 176 req, err := http.NewRequest(http.MethodDelete, url, nil) 177 if err != nil { 178 return nil, err 179 } 180 181 req = req.WithContext(ctx) 182 183 req.Header.Set(contentType, applicationJSON) 184 185 client := http.DefaultClient 186 res, err := client.Do(req) 187 if err != nil { 188 return nil, err 189 } 190 191 defer res.Body.Close() 192 193 result, err := ioutil.ReadAll(res.Body) 194 if err != nil { 195 return nil, err 196 } 197 198 return result, nil 199 200 } 201 202 // DiscoverAll performs a discovery on the network looking for bridges using https://www.meethue.com/api/nupnp service. 203 // DiscoverAll returns a list of Bridge objects. 204 func DiscoverAll() ([]Bridge, error) { 205 return DiscoverAllContext(context.Background()) 206 } 207 208 // DiscoverAllContext performs a discovery on the network looking for bridges using https://www.meethue.com/api/nupnp service. 209 // DiscoverAllContext returns a list of Bridge objects. 210 func DiscoverAllContext(ctx context.Context) ([]Bridge, error) { 211 212 req, err := http.NewRequest(http.MethodGet, "https://discovery.meethue.com", nil) 213 if err != nil { 214 return nil, err 215 } 216 217 req = req.WithContext(ctx) 218 219 client := http.DefaultClient 220 res, err := client.Do(req) 221 if err != nil { 222 return nil, err 223 } 224 225 defer res.Body.Close() 226 227 d, err := ioutil.ReadAll(res.Body) 228 if err != nil { 229 return nil, err 230 } 231 232 var bridges []Bridge 233 234 err = json.Unmarshal(d, &bridges) 235 if err != nil { 236 return nil, err 237 } 238 239 return bridges, nil 240 241 } 242 243 // Discover performs a discovery on the network looking for bridges using https://www.meethue.com/api/nupnp service. 244 // Discover uses DiscoverAll() but only returns the first instance in the array of bridges if any. 245 func Discover() (*Bridge, error) { 246 return DiscoverContext(context.Background()) 247 } 248 249 // DiscoverContext performs a discovery on the network looking for bridges using https://www.meethue.com/api/nupnp service. 250 // DiscoverContext uses DiscoverAllContext() but only returns the first instance in the array of bridges if any. 251 func DiscoverContext(ctx context.Context) (*Bridge, error) { 252 253 b := &Bridge{} 254 255 bridges, err := DiscoverAllContext(ctx) 256 if err != nil { 257 return nil, err 258 } 259 260 if len(bridges) > 0 { 261 b = &bridges[0] 262 } 263 264 return b, nil 265 266 } 267 268 // New instantiates and returns a new Bridge. New accepts hostname/ip address to the bridge (h) as well as an username (u). 269 // h may or may not be prefixed with http(s)://. For example http://192.168.1.20/ or 192.168.1.20. 270 // u is a username known to the bridge. Use Discover() and CreateUser() to create a user. 271 func New(h, u string) *Bridge { 272 return &Bridge{h, u, ""} 273 }