aboutsummaryrefslogtreecommitdiff
path: root/light.go
blob: 0dd38db4e79194d72a11b117a1ff1a7c3f184c0d (plain)
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
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
/*
* light.go
* GoHue library for Philips Hue
* Copyright (C) 2016 Collin Guarino (Collinux) collin.guarino@gmail.com
* License: GPL version 2 or higher http://www.gnu.org/licenses/gpl.html
 */
// http://www.developers.meethue.com/documentation/lights-api

package hue

import (
	"errors"
	"fmt"
	"log"
	"time"
)

// Light struct defines attributes of a light.
type Light struct {
	State struct {
		On         bool       `json:"on"`        // On or Off state of the light ("true" or "false")
		Bri        uint8      `json:"bri"`       // Brightness value 1-254
		Hue        int        `json:"hue"`       // Hue value 1-65535
		Saturation int        `json:"sat"`       // Saturation value 0-254
		Effect     string     `json:"effect"`    // "None" or "Colorloop"
		XY         [2]float32 `json:"xy"`        // Coordinates of color in CIE color space
		CT         int        `json:"ct"`        // Mired Color Temperature (google it)
		Alert      string     `json:"alert"`     // "selected" or "none"
		ColorMode  string     `json:"colormode"` // HS or XY mode for choosing color
		Reachable  bool       `json:"reachable"`
	} `json:"state"`
	Type             string  `json:"type"`
	Name             string  `json:"name"`
	ModelID          string  `json:"modelid"`
	ManufacturerName string  `json:"manufacturername"`
	UniqueID         string  `json:"uniqueid"`
	SWVersion        string  `json:"swversion"`
	Index            int     // Set by index of light array response
	Bridge           *Bridge // Set by the bridge when the light is found
}

// LightState used in Light.SetState to amend light attributes.
type LightState struct {
	On                  bool        `json:"on"`
	Bri                 uint8       `json:"bri,omitempty"`
	Hue                 uint16      `json:"hue,omitempty"`
	Sat                 uint8       `json:"sat,omitempty"`
	XY                  *[2]float32 `json:"xy,omitempty"`
	CT                  uint16      `json:"ct,omitempty"`
	Effect              string      `json:"effect,omitempty"`
	Alert               string      `json:"alert,omitempty"`
	TransitionTime      string      `json:"transitiontime,omitempty"`
	SaturationIncrement int16       `json:"sat_inc,omitempty"`
	HueIncrement        int32       `json:"hue_inc,omitempty"`
	BrightnessIncrement int16       `json:"bri_inc,omitempty"`
	CTIncrement         int32       `json:"ct_inc,omitempty"`
	XYIncrement         *[2]float32 `json:"xy_inc,omitempty"`
	Name                string      `json:"name,omitempty"`
}

// Light.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)
	body := make(map[string]string)
	body["name"] = name
	_, _, err := light.Bridge.Put(uri, body)
	if err != nil {
		return err
	}
	return nil
}

// Light.Off turns the light source off
func (light *Light) Off() error {
	return light.SetState(LightState{On: false})
}

// Light.Off 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
func (light *Light) Toggle() error {
	if light.State.On {
		return light.Off()
	} else {
		return light.On()
	}
	return nil
}

// Light.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)
	err := light.Bridge.Delete(uri)
	if err != nil {
		return err
	}
	return nil
}

// Light.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.
func (light *Light) Blink(seconds int) error {
	originalPosition := light.State.On
	originalBrightness := light.State.Bri
	blinkMax := 75 // Percent brightness
	blinkMin := 25 // Percent brightness

	// Start with near maximum brightness and toggle between that and
	// a lesser brightness to create a blinking effect.
	for i := 0; i <= seconds*2; i++ {
		if i == 0 {
			err := light.SetBrightness(blinkMax)
			if err != nil {
				return err
			}
		} else if i%2 == 0 {
			err := light.SetBrightness(blinkMax)
			if err != nil {
				return err
			}
		} else {
			err := light.SetBrightness(blinkMin)
			if err != nil {
				return err
			}
		}
		time.Sleep(time.Second / 2)
	}

	// Return the light to its original on or off state and brightness
	if light.State.Bri != originalBrightness || light.State.On != originalPosition {
		light.SetState(LightState{On: originalPosition, Bri: originalBrightness})
	}
	return nil
}

// Light.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"
	if activate {
		state = "colorloop"
	}
	return light.SetState(LightState{On: true, Effect: state})
}

// XY positions on the HSL color spectrum used in `Light.SetColor`
// https://en.wikipedia.org/wiki/HSL_and_HSV
var (
	RED    = &[2]float32{0.6915, 0.3083}
	YELLOW = &[2]float32{0.4023, 0.4725}
	ORANGE = &[2]float32{0.4693, 0.4007}
	GREEN  = &[2]float32{0.1700, 0.7000}
	CYAN   = &[2]float32{0.1610, 0.3549}
	BLUE   = &[2]float32{0.1530, 0.0480}
	PURPLE = &[2]float32{0.2363, 0.1154}
	PINK   = &[2]float32{0.3645, 0.1500}
	WHITE  = &[2]float32{0.3227, 0.3290}
)

// 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}
	err := light.SetState(lightState)
	if err != nil {
		return err
	}
	return nil
}

// Light.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 {
		originalBri := light.State.Bri
		decreaseBri := float32(originalBri) * float32((float32(percent) / 100.0))
		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)
		if err != nil {
			return err
		}
		return nil
	} else {
		return errors.New("Light.Dim percentage given is not between 1 and 100. ")
	}
}

// Light.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 {
		brightness := uint8(float32(percent) * 2.54) // 100=254x --> 2.54
		lightState := LightState{On: true, Bri: brightness}
		err := light.SetState(lightState)
		if err != nil {
			return err
		}
		return nil
	} else {
		return errors.New("Light.SetBrightness percentage is not between 1 and 100. ")
	}
}

// Light.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
		increaseBri := float32(originalBri) * float32((float32(percent) / 100.0))
		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)
		if err != nil {
			return err
		}
		return nil
	} else {
		return errors.New("Light.Brighten percentage is not between 1 and 100. ")
	}
}

// Light.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)
// See http://www.developers.meethue.com/documentation/lights-api for more info
func (light *Light) SetState(newState LightState) error {
	uri := fmt.Sprintf("/api/%s/lights/%d/state", light.Bridge.Username, light.Index)
	_, _, err := light.Bridge.Put(uri, newState)
	if err != nil {
		return err
	}

	// Get the new light state and update the current Light struct
	*light, err = light.Bridge.GetLightByIndex(light.Index)
	if err != nil {
		return err
	}
	return nil
}