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  }