diff options
-rw-r--r-- | README.md | 11 | ||||
-rw-r--r-- | bridge.go | 102 | ||||
-rw-r--r-- | group.go | 4 | ||||
-rw-r--r-- | light.go | 58 | ||||
-rw-r--r-- | scene.go | 12 | ||||
-rw-r--r-- | schedule.go | 6 |
6 files changed, 97 insertions, 96 deletions
@@ -2,6 +2,13 @@ Package hue interfaces Philips Hue devices to control lights, scenes, schedules, and groups. [![GoDoc](https://camo.githubusercontent.com/b3b2a2b7fad4e76052830945cd839a3bba5be723/687474703a2f2f696d672e736869656c64732e696f2f62616467652f676f646f632d7265666572656e63652d3532373242342e706e67)](https://godoc.org/github.com/Collinux/GoHue) +[![Go Report Card](https://goreportcard.com/badge/github.com/Collinux/GoHue)](https://goreportcard.com/report/github.com/Collinux/GoHue) + +## See GoHue in action! +##### Have a cool project you made using GoHue? Add yours here in a pull request! +[HueBeat](https://github.com/Mobilpadde/HueBeat) by [Mobilpadde](https://github.com/mobilpadde) - Light up a room in sync with your heartbeat. + +[BitHue](https://github.com/realytcracker/go-bithue) by [ytcracker](https://github.com/realytcracker) - Light color according to profit gain/loss in bitcoin price ## Installation ``` @@ -110,10 +117,6 @@ func main() { - [ ] Update rule - [ ] Delete rule -## See GoHue in action! -##### Have a cool project you made using GoHue? Add yours here in a pull request! -[HueBeat](https://github.com/Mobilpadde/HueBeat) by Mobilpadde - Light up a room in sync with your heartbeat. - ## API Documentation For official Philips Hue documentation check out the [Philips Hue website](http://www.developers.meethue.com/philips-hue-api) @@ -24,6 +24,7 @@ import ( "runtime" "strconv" "strings" + "time" ) // Bridge struct defines hardware that is used to communicate with the lights. @@ -51,78 +52,76 @@ type BridgeInfo struct { } `xml:"device"` } -// bridge.Get sends an http GET to the bridge +// Get sends an http GET to the bridge func (bridge *Bridge) Get(path string) ([]byte, io.Reader, error) { uri := fmt.Sprintf("http://" + bridge.IPAddress + path) - resp, err := http.Get(uri) + client := &http.Client{Timeout: time.Second * 5} + resp, err := client.Get(uri) + if err != nil { - err = errors.New("Error: Unable to access bridge. ") - log.Println(err) + err = errors.New("unable to access bridge") return []byte{}, nil, err } return HandleResponse(resp) } -// Bridge.Put sends an http PUT to the bridge with +// Put sends an http PUT to the bridge with // a body formatted with parameters (in a generic interface) func (bridge *Bridge) Put(path string, params interface{}) ([]byte, io.Reader, error) { uri := fmt.Sprintf("http://" + bridge.IPAddress + path) - client := &http.Client{} + client := &http.Client{Timeout: time.Second * 5} data, err := json.Marshal(params) if err != nil { - err = errors.New("Error: Unable marshal PUT request interface.") - log.Println(err) + err = errors.New("unable to marshal PUT request interface") return []byte{}, nil, err } //fmt.Println("\n\nPARAMS: ", params) - //log.Println("\nSending PUT body: ", string(data)) - request, err := http.NewRequest("PUT", uri, bytes.NewReader(data)) + request, _ := http.NewRequest("PUT", uri, bytes.NewReader(data)) resp, err := client.Do(request) if err != nil { - err = errors.New("Error: Unable to access bridge.") - log.Println(err) + err = errors.New("unable to access bridge") return []byte{}, nil, err } return HandleResponse(resp) } -// bridge.Post sends an http POST to the bridge with +// Post sends an http POST to the bridge with // a body formatted with parameters (in a generic interface). // If `params` is nil then it will send an empty body with the post request. func (bridge *Bridge) Post(path string, params interface{}) ([]byte, io.Reader, error) { - // Add the params to the request or allow an empty body - request := []byte{} - if params != nil { - reqBody, err := json.Marshal(params) - if err != nil { - err = errors.New("Error: Unable to add POST body parameters due to json marshal error.") - log.Println(err) - return []byte{}, nil, err - } - request = reqBody - } + // Add the params to the request or allow an empty body + request := []byte{} + if params != nil { + reqBody, err := json.Marshal(params) + if err != nil { + err = errors.New("unable to add POST body parameters due to json marshalling error") + return []byte{}, nil, err + } + request = reqBody + } // Send the request and handle the response uri := fmt.Sprintf("http://" + bridge.IPAddress + path) - resp, err := http.Post(uri, "text/json", bytes.NewReader(request)) + client := &http.Client{Timeout: time.Second * 5} + resp, err := client.Post(uri, "text/json", bytes.NewReader(request)) + if err != nil { - err = errors.New("Error: Unable to access bridge.") - log.Println(err) + err = errors.New("unable to access bridge") return []byte{}, nil, err } - return HandleResponse(resp) + return HandleResponse(resp) } -// Bridge.Delete sends an http DELETE to the bridge +// Delete sends an http DELETE to the bridge func (bridge *Bridge) Delete(path string) error { uri := fmt.Sprintf("http://" + bridge.IPAddress + path) - client := &http.Client{} - req, err := http.NewRequest("DELETE", uri, nil) + client := &http.Client{Timeout: time.Second * 5} + req, _ := http.NewRequest("DELETE", uri, nil) resp, err := client.Do(req) + if err != nil { - err = errors.New("Error: Unable to access bridge.") - log.Println(err) + err = errors.New("unable to access bridge") return err } _, _, err = HandleResponse(resp) @@ -134,7 +133,7 @@ func (bridge *Bridge) Delete(path string) error { // and invalid return types. func HandleResponse(resp *http.Response) ([]byte, io.Reader, error) { body, err := ioutil.ReadAll(resp.Body) - defer resp.Body.Close() + defer resp.Body.Close() if err != nil { trace("Error parsing bridge description xml.", nil) return []byte{}, nil, err @@ -146,7 +145,6 @@ func HandleResponse(resp *http.Response) ([]byte, io.Reader, error) { errDesc := errString[strings.Index(errString, "description\":\"")+14 : strings.Index(errString, "\"}}")] errOut := fmt.Sprintf("Error type %s: %s.", errNum, errDesc) err = errors.New(errOut) - log.Println(err) return []byte{}, nil, err } return body, reader, nil @@ -158,15 +156,14 @@ func FindBridges() ([]Bridge, error) { bridge := Bridge{IPAddress: "www.meethue.com"} body, _, err := bridge.Get("/api/nupnp") if err != nil { - err = errors.New("Error: Unable to locate bridge.") - log.Fatal(err) + err = errors.New("unable to locate bridge") return []Bridge{}, err } - bridges := []Bridge{} - err = json.Unmarshal(body, &bridges) - if err != nil { - return []Bridge{}, errors.New("Unable to parse FindBridges response. ") - } + bridges := []Bridge{} + err = json.Unmarshal(body, &bridges) + if err != nil { + return []Bridge{}, errors.New("unable to parse FindBridges response") + } return bridges, nil } @@ -181,13 +178,12 @@ func NewBridge(ip string) (*Bridge, error) { // Test the connection by attempting to get the bridge info. err := bridge.GetInfo() if err != nil { - log.Fatal("Error: Unable to access bridge. ", err) return &Bridge{}, err } return &bridge, nil } -// GetBridgeInfo retreives the description.xml file from the bridge. +// GetInfo retreives the description.xml file from the bridge. // This is used as a check to see if the bridge is accessible // and any error will be fatal as the bridge is required for nearly // all functions. @@ -200,27 +196,25 @@ func (bridge *Bridge) GetInfo() error { err = xml.NewDecoder(reader).Decode(&data) if err != nil { err = errors.New("Error: Unable to decode XML response from bridge. ") - log.Fatal(err) return err } bridge.Info = data return nil } -// Bridge.Login verifies that the username token has bridge access +// Login verifies that the username token has bridge access // and only assigns the bridge its Username value if verification is successful. func (bridge *Bridge) Login(username string) error { uri := fmt.Sprintf("/api/%s", username) _, _, err := bridge.Get(uri) if err != nil { - log.Fatal(err) return err } bridge.Username = username return nil } -// Bridge.CreateUser adds a new user token on the whitelist. +// CreateUser adds a new user token on the whitelist. // The token is the first return value in this function which must // be used with `Bridge.Login`. You cannot use a plaintext username // like the argument provided in this function. @@ -229,20 +223,15 @@ func (bridge *Bridge) CreateUser(deviceType string) (string, error) { params := map[string]string{"devicetype": deviceType} body, _, err := bridge.Post("/api", params) if err != nil { - log.Fatal("Error: Failed to create user. ", err) return "", err } content := string(body) username := content[strings.LastIndex(content, ":\"")+2 : strings.LastIndex(content, "\"")] - userOut := fmt.Sprintf( - "Created user token '%s'. Use Bridge.Login with this token from now on.", - username) - log.Println(userOut) bridge.Username = username return username, nil } -// Bridge.DeleteUser deletes a user given its USER KEY, not the string name. +// DeleteUser deletes a user given its USER KEY, not the string name. // See http://www.developers.meethue.com/documentation/configuration-api // for description on `username` deprecation in place of the devicetype key. func (bridge *Bridge) DeleteUser(username string) error { @@ -307,7 +296,7 @@ func (bridge *Bridge) GetLightByIndex(index int) (Light, error) { return light, nil } -// Bridge.FindNewLights makes the bridge search the zigbee spectrum for +// FindNewLights makes the bridge search the zigbee spectrum for // lights in the area and will add them to the list of lights available. // If successful these new lights can be used by `Bridge.GetAllLights` // @@ -321,13 +310,12 @@ func (bridge *Bridge) FindNewLights() error { uri := fmt.Sprintf("/api/%s/lights", bridge.Username) _, _, err := bridge.Post(uri, nil) if err != nil { - log.Println(err) return err } return nil } -// GetLight returns a light struct containing data on a given name. +// GetLightByName returns a light struct containing data on a given name. func (bridge *Bridge) GetLightByName(name string) (Light, error) { lights, _ := bridge.GetAllLights() for _, light := range lights { @@ -35,7 +35,7 @@ type Group struct { Type string `json:"type"` } -// Bridge.GetGroups gets the attributes for each group of lights. +// GetGroups gets the attributes for each group of lights. // TODO: NOT TESTED, NOT FULLY IMPLEMENTED func (bridge *Bridge) GetGroups() ([]Group, error) { uri := fmt.Sprintf("/api/%s/groups", bridge.Username) @@ -56,7 +56,7 @@ func (bridge *Bridge) GetGroups() ([]Group, error) { return []Group{}, nil } -// Bridge.SetGroupState sends an action to group +// SetGroupState sends an action to group func (bridge *Bridge) SetGroupState(group int, action *Action) error { uri := fmt.Sprintf("/api/%s/groups/%d/action", bridge.Username, group) _, _, err := bridge.Put(uri, action) @@ -11,7 +11,6 @@ package hue import ( "errors" "fmt" - "log" "time" ) @@ -58,7 +57,7 @@ type LightState struct { Name string `json:"name,omitempty"` } -// Light.SetName assigns a new name in the light's +// SetName assigns a new name in the light's // attributes as recognized by the bridge. func (light *Light) SetName(name string) error { uri := fmt.Sprintf("/api/%s/lights/%d", light.Bridge.Username, light.Index) @@ -71,27 +70,25 @@ func (light *Light) SetName(name string) error { return nil } -// Light.Off turns the light source off +// Off turns the light source off func (light *Light) Off() error { return light.SetState(LightState{On: false}) } -// Light.Off turns the light source on +// On turns the light source on func (light *Light) On() error { return light.SetState(LightState{On: true}) } -// Light.Toggle switches the light source on and off +// Toggle switches the light source on and off func (light *Light) Toggle() error { if light.State.On { return light.Off() - } else { - return light.On() } - return nil + return light.On() } -// Light.Delete removes the light from the +// Delete removes the light from the // list of lights available on the bridge. func (light *Light) Delete() error { uri := fmt.Sprintf("/api/%s/lights/%d", light.Bridge.Username, light.Index) @@ -102,7 +99,7 @@ func (light *Light) Delete() error { return nil } -// Light.Blink increases and decrease the brightness +// Blink increases and decrease the brightness // repeatedly for a given seconds interval and return the // light back to its off or on state afterwards. // Note: time will vary based on connection speed and algorithm speed. @@ -141,7 +138,7 @@ func (light *Light) Blink(seconds int) error { return nil } -// Light.ColorLoop sets the light state to 'colorloop' if `active` +// ColorLoop sets the light state to 'colorloop' if `active` // is true or it sets the light state to "none" if `activate` is false. func (light *Light) ColorLoop(activate bool) error { var state = "none" @@ -165,7 +162,7 @@ var ( WHITE = &[2]float32{0.3227, 0.3290} ) -// Light.SetColor requires a selection from the above light +// SetColor requires a selection from the above light // color variable section and sets the light to that XY HSL color func (light *Light) SetColor(color *[2]float32) error { lightState := LightState{On: true, XY: color} @@ -176,7 +173,25 @@ func (light *Light) SetColor(color *[2]float32) error { return nil } -// Light.Dim lowers the brightness by a percent. +// SetColorXY requires a selection from the above light +// color variable section and sets the light to that XY HSL color +// aliased for clarity +func (light *Light) SetColorXY(color *[2]float32) { + light.SetColor(color) +} + +// SetColorHS requires a selection from the above light +// color variable section and sets the light to the Hue value +func (light *Light) SetColorHS(color uint16) error { + lightState := LightState{On: true, Hue: color} + err := light.SetState(lightState) + if err != nil { + return err + } + return nil +} + +// Dim lowers the brightness by a percent. // Note the required value is an integer, for example "20" is converted to 20%. func (light *Light) Dim(percent int) error { if percent > 0 && percent <= 100 { @@ -185,7 +200,6 @@ func (light *Light) Dim(percent int) error { newBri := uint8(originalBri - uint8(decreaseBri)) if newBri < 0 { newBri = 0 - log.Println("Light.Dim state set under 0%, setting brightness to 0. ") } lightState := LightState{On: true, Bri: newBri} err := light.SetState(lightState) @@ -193,12 +207,11 @@ func (light *Light) Dim(percent int) error { return err } return nil - } else { - return errors.New("Light.Dim percentage given is not between 1 and 100. ") } + return errors.New("Light.Dim percentage given is not between 1 and 100. ") } -// Light.SetBrightness sets the brightness to a percentage of the maximum +// SetBrightness sets the brightness to a percentage of the maximum // maximum brightness as an integer (`LightStruct.Bri between 1 and 254 inclusive`) func (light *Light) SetBrightness(percent int) error { if percent > 0 && percent <= 100 { @@ -209,12 +222,11 @@ func (light *Light) SetBrightness(percent int) error { return err } return nil - } else { - return errors.New("Light.SetBrightness percentage is not between 1 and 100. ") } + return errors.New("Light.SetBrightness percentage is not between 1 and 100. ") } -// Light.Brighten will increase LightStruct.Bri by a given percent (integer) +// Brighten will increase LightStruct.Bri by a given percent (integer) func (light *Light) Brighten(percent int) error { if percent > 0 && percent <= 100 { originalBri := light.State.Bri @@ -222,7 +234,6 @@ func (light *Light) Brighten(percent int) error { newBri := uint8(originalBri + uint8(increaseBri)) if newBri > 254 { // LightState.Bri must be between 1 and 254 inclusive newBri = 254 - log.Println("Light.Brighten state set over 100%, setting brightness to 100%. ") } lightState := LightState{On: true, Bri: newBri} err := light.SetState(lightState) @@ -230,12 +241,11 @@ func (light *Light) Brighten(percent int) error { return err } return nil - } else { - return errors.New("Light.Brighten percentage is not between 1 and 100. ") } + return errors.New("Light.Brighten percentage is not between 1 and 100. ") } -// Light.SetState modifyies light attributes. See `LightState` struct for attributes. +// SetState modifyies light attributes. See `LightState` struct for attributes. // Brightness must be between 1 and 254 (inclusive) // Hue must be between 0 and 65535 (inclusive) // Sat must be between 0 and 254 (inclusive) @@ -31,7 +31,7 @@ type Scene struct { ID string `json:",omitempty"` } -// Bridge.GetScenes gets the attributes for all scenes. +// GetAllScenes gets the attributes for all scenes. func (bridge *Bridge) GetAllScenes() ([]Scene, error) { uri := fmt.Sprintf("/api/%s/scenes", bridge.Username) body, _, err := bridge.Get(uri) @@ -54,7 +54,7 @@ func (bridge *Bridge) GetAllScenes() ([]Scene, error) { return scenesList, nil } -// Bridge.GetScene gets the attributes for an individual scene. +// GetScene gets the attributes for an individual scene. // This is used to optimize time when updating the state of the scene. // Note: The ID is not an index, it's a unique key generated for each scene. func (bridge *Bridge) GetScene(id string) (Scene, error) { @@ -72,7 +72,7 @@ func (bridge *Bridge) GetScene(id string) (Scene, error) { return scene, nil } -// Bridge.GetSceneByName gets the attributes for the scene identified by a name +// GetSceneByName gets the attributes for the scene identified by a name func (bridge *Bridge) GetSceneByName(name string) (Scene, error) { scenes, _ := bridge.GetAllScenes() @@ -88,13 +88,13 @@ func (bridge *Bridge) GetSceneByName(name string) (Scene, error) { return Scene{}, errors.New(errOut) } -// Bridge.RecallScene recalls a scene +// RecallScene recalls a scene func (bridge *Bridge) RecallScene(id string) error { action := &Action{Scene: id} return bridge.SetGroupState(0, action) } -// Bridge.RecallSceneByName recalls a scene +// RecallSceneByName recalls a scene func (bridge *Bridge) RecallSceneByName(name string) error { scene, err := bridge.GetSceneByName(name) if err != nil { @@ -103,7 +103,7 @@ func (bridge *Bridge) RecallSceneByName(name string) error { return bridge.RecallScene(scene.ID) } -// Bridge.CreateScene posts a new scene configuration to the bridge. +// CreateScene posts a new scene configuration to the bridge. func (bridge *Bridge) CreateScene(scene Scene) error { uri := fmt.Sprintf("/api/%s/scenes/", bridge.Username) _, _, err := bridge.Post(uri, scene) diff --git a/schedule.go b/schedule.go index 50755ef..52315f9 100644 --- a/schedule.go +++ b/schedule.go @@ -32,7 +32,7 @@ type Schedule struct { ID string } -// Bridge.GetAllSchedules gets Alarms and Timers in a Schedule struct. +// GetAllSchedules gets Alarms and Timers in a Schedule struct. func (bridge *Bridge) GetAllSchedules() ([]Schedule, error) { uri := fmt.Sprintf("/api/%s/schedules", bridge.Username) body, _, err := bridge.Get(uri) @@ -57,7 +57,7 @@ func (bridge *Bridge) GetAllSchedules() ([]Schedule, error) { return scheduleList, nil } -// Bridge.GetSchedule gets the attributes for an individual schedule. +// GetSchedule gets the attributes for an individual schedule. // This is used to optimize time when updating the state of a schedule item. // Note: The ID is not an index, it's a unique key generated for each schedule. func (bridge *Bridge) GetSchedule(id string) (Schedule, error) { @@ -75,7 +75,7 @@ func (bridge *Bridge) GetSchedule(id string) (Schedule, error) { return schedule, nil } -// TODO: NOT TESTED, NOT FULLY IMPLEMENTED +// CreateSchedule TODO: NOT TESTED, NOT FULLY IMPLEMENTED func (bridge *Bridge) CreateSchedule(schedule Schedule) error { uri := fmt.Sprintf("/api/%s/schedules", bridge.Username) body, _, err := bridge.Post(uri, schedule) |