ziggs/vendor/github.com/amimof/huego/huego.go

303 lines
7.2 KiB
Go

// Package huego provides an extensive, easy to use interface to the Philips Hue bridge.
package huego
import (
"context"
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"strings"
)
const (
applicationJSON = "application/json"
contentType = "Content-Type"
)
// APIResponse holds the response data returned form the bridge after a request has been made.
type APIResponse struct {
Success map[string]interface{} `json:"success,omitempty"`
Error *APIError `json:"error,omitempty"`
}
// APIError defines the error response object returned from the bridge after an invalid API request.
type APIError struct {
Type int
Address string
Description string
}
// Response is a wrapper struct of the success response returned from the bridge after a successful API call.
type Response struct {
Success map[string]interface{}
}
// UnmarshalJSON makes sure that types are correct when unmarshalling. Implements package encoding/json
func (a *APIError) UnmarshalJSON(data []byte) error {
var aux map[string]interface{}
err := json.Unmarshal(data, &aux)
if err != nil {
return err
}
a.Type = int(aux["type"].(float64))
a.Address = aux["address"].(string)
a.Description = aux["description"].(string)
return nil
}
// Error returns an error string
func (a *APIError) Error() string {
return fmt.Sprintf("ERROR %d [%s]: \"%s\"", a.Type, a.Address, a.Description)
}
func handleResponse(a []*APIResponse) (*Response, error) {
success := map[string]interface{}{}
for _, r := range a {
if r.Success != nil {
for k, v := range r.Success {
success[k] = v
}
}
if r.Error != nil {
return nil, r.Error
}
}
resp := &Response{Success: success}
return resp, nil
}
// unmarshal will try to unmarshal data into APIResponse so that we can
// return the actual error returned by the bridge http API as an error struct.
func unmarshal(data []byte, v interface{}) error {
err := json.Unmarshal(data, &v)
if err != nil {
var a []*APIResponse
err = json.Unmarshal(data, &a)
if err != nil {
return err
}
_, err = handleResponse(a)
if err != nil {
return err
}
}
return nil
}
func get(ctx context.Context, url string, client *http.Client) ([]byte, error) {
req, err := http.NewRequest(http.MethodGet, url, nil)
if err != nil {
return nil, err
}
req = req.WithContext(ctx)
res, err := client.Do(req)
if err != nil {
return nil, err
}
defer res.Body.Close()
body, err := ioutil.ReadAll(res.Body)
if err != nil {
return nil, err
}
return body, nil
}
func put(ctx context.Context, url string, data []byte, client *http.Client) ([]byte, error) {
body := strings.NewReader(string(data))
req, err := http.NewRequest(http.MethodPut, url, body)
if err != nil {
return nil, err
}
req = req.WithContext(ctx)
req.Header.Set(contentType, applicationJSON)
res, err := client.Do(req)
if err != nil {
return nil, err
}
defer res.Body.Close()
result, err := ioutil.ReadAll(res.Body)
if err != nil {
return nil, err
}
return result, nil
}
func post(ctx context.Context, url string, data []byte, client *http.Client) ([]byte, error) {
body := strings.NewReader(string(data))
req, err := http.NewRequest(http.MethodPost, url, body)
if err != nil {
return nil, err
}
req = req.WithContext(ctx)
req.Header.Set(contentType, applicationJSON)
res, err := client.Do(req)
if err != nil {
return nil, err
}
defer res.Body.Close()
result, err := ioutil.ReadAll(res.Body)
if err != nil {
return nil, err
}
return result, nil
}
func del(ctx context.Context, url string, client *http.Client) ([]byte, error) {
req, err := http.NewRequest(http.MethodDelete, url, nil)
if err != nil {
return nil, err
}
req = req.WithContext(ctx)
req.Header.Set(contentType, applicationJSON)
res, err := client.Do(req)
if err != nil {
return nil, err
}
defer res.Body.Close()
result, err := ioutil.ReadAll(res.Body)
if err != nil {
return nil, err
}
return result, nil
}
// DiscoverAll performs a discovery on the network looking for bridges using https://www.meethue.com/api/nupnp service.
// DiscoverAll returns a list of Bridge objects.
func DiscoverAll() ([]Bridge, error) {
return DiscoverAllContext(context.Background())
}
// DiscoverAllContext performs a discovery on the network looking for bridges using https://www.meethue.com/api/nupnp service.
// DiscoverAllContext returns a list of Bridge objects.
func DiscoverAllContext(ctx context.Context) ([]Bridge, error) {
req, err := http.NewRequest(http.MethodGet, "https://discovery.meethue.com", nil)
if err != nil {
return nil, err
}
req = req.WithContext(ctx)
client := http.DefaultClient
res, err := client.Do(req)
if err != nil {
return nil, err
}
defer res.Body.Close()
d, err := ioutil.ReadAll(res.Body)
if err != nil {
return nil, err
}
var bridges []Bridge
err = json.Unmarshal(d, &bridges)
if err != nil {
return nil, err
}
return bridges, nil
}
// Discover performs a discovery on the network looking for bridges using https://www.meethue.com/api/nupnp service.
// Discover uses DiscoverAll() but only returns the first instance in the array of bridges if any.
func Discover() (*Bridge, error) {
return DiscoverContext(context.Background())
}
// DiscoverContext performs a discovery on the network looking for bridges using https://www.meethue.com/api/nupnp service.
// DiscoverContext uses DiscoverAllContext() but only returns the first instance in the array of bridges if any.
func DiscoverContext(ctx context.Context) (*Bridge, error) {
b := &Bridge{}
bridges, err := DiscoverAllContext(ctx)
if err != nil {
return nil, err
}
if len(bridges) > 0 {
b = &bridges[0]
}
return b, nil
}
// New instantiates and returns a new Bridge. New accepts hostname/ip address to the bridge (h) as well as an username (u).
// h may or may not be prefixed with http(s)://. For example http://192.168.1.20/ or 192.168.1.20.
// u is a username known to the bridge. Use Discover() and CreateUser() to create a user.
func New(h, u string) *Bridge {
return &Bridge{
Host: h,
User: u,
ID: "",
client: http.DefaultClient,
}
}
/*NewWithClient instantiates and returns a new Bridge with a custom HTTP client.
NewWithClient accepts the same parameters as New, but with an additional acceptance of an http.Client.
- h may or may not be prefixed with http(s)://. For example http://192.168.1.20/ or 192.168.1.20.
- u is a username known to the bridge. Use Discover() and CreateUser() to create a user.
- Difference between New and NewWithClient being the ability to implement your own http.RoundTripper for proxying.*/
func NewWithClient(h, u string, client *http.Client) *Bridge {
return &Bridge{
Host: h,
User: u,
ID: "",
client: client,
}
}
/*NewCustom instantiates and returns a new Bridge. NewCustom accepts:
- a raw JSON []byte slice as input for substantiating the Bridge type
- a custom HTTP client like NewWithClient that will be used to make API requests
Note that this is for advanced users, the other "New" functions may suit you better.*/
func NewCustom(raw []byte, host string, client *http.Client) (*Bridge, error) {
br := &Bridge{}
if err := json.Unmarshal(raw, br); err != nil {
return nil, err
}
br.Host = host
br.client = client
return br, nil
}