Skip to content

Commit 1b33c85

Browse files
committed
✨ Add support for RGB bulbs
1 parent c94d43d commit 1b33c85

File tree

7 files changed

+198
-74
lines changed

7 files changed

+198
-74
lines changed

Gopkg.lock

+7-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Gopkg.toml

+4
Original file line numberDiff line numberDiff line change
@@ -24,3 +24,7 @@
2424
[[constraint]]
2525
name = "github.com/eclipse/paho.mqtt.golang"
2626
branch = "master"
27+
28+
[[constraint]]
29+
branch = "master"
30+
name = "github.com/lucasb-eyer/go-colorful"

device.go

+145-62
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,37 @@
11
package sladdlos
22

33
import (
4+
"fmt"
45
"github.com/hemtjanst/hemtjanst/device"
56
"github.com/hemtjanst/hemtjanst/messaging"
67
"github.com/hemtjanst/sladdlos/tradfri"
8+
"github.com/lucasb-eyer/go-colorful"
79
"log"
810
"strconv"
911
"strings"
1012
"sync"
1113
)
1214

15+
const (
16+
lTypeNone = iota
17+
lTypeTemp
18+
lTypeRgb
19+
)
20+
1321
type HemtjanstDevice struct {
1422
sync.RWMutex
15-
client *HemtjanstClient
16-
mqClient messaging.PublishSubscriber
17-
Topic string
18-
isRunning bool
19-
isGroup bool
20-
accessory *tradfri.Accessory
21-
members []*HemtjanstDevice
22-
group *tradfri.Group
23-
device *device.Device
24-
features map[string]*device.Feature
23+
client *HemtjanstClient
24+
mqClient messaging.PublishSubscriber
25+
Topic string
26+
isRunning bool
27+
isGroup bool
28+
accessory *tradfri.Accessory
29+
members []*HemtjanstDevice
30+
group *tradfri.Group
31+
device *device.Device
32+
features map[string]*device.Feature
33+
lastHue *int
34+
lastSaturation *int
2535
}
2636

2737
func NewHemtjanstAccessory(client *HemtjanstClient, topic string, accessory *tradfri.Accessory, group *HemtjanstDevice) *HemtjanstDevice {
@@ -87,7 +97,8 @@ func (h *HemtjanstDevice) init() {
8797
return
8898
}
8999
var dev *device.Device
90-
hasColorTemp := false
100+
101+
lType := lTypeNone
91102
if h.isGroup {
92103
if h.group == nil {
93104
return
@@ -109,10 +120,13 @@ func (h *HemtjanstDevice) init() {
109120
if d.accessory != nil {
110121
if l := d.accessory.Light(); l != nil {
111122
if l.HasColorTemperature() {
112-
hasColorTemp = true
113-
break
123+
lType = lTypeTemp
114124
}
115125
}
126+
if d.accessory.DeviceInfo.IsRGBModel() {
127+
lType = lTypeRgb
128+
break
129+
}
116130
}
117131
}
118132
} else {
@@ -137,13 +151,24 @@ func (h *HemtjanstDevice) init() {
137151
dev.LastWillID = h.client.Id
138152

139153
dev.AddFeature("reachable", &device.Feature{})
140-
hasColorTemp = h.accessory.Light().HasColorTemperature()
154+
155+
if h.accessory.Light().HasColorTemperature() {
156+
lType = lTypeTemp
157+
}
158+
if h.accessory.DeviceInfo.IsRGBModel() {
159+
lType = lTypeRgb
160+
}
141161
}
142162

143163
dev.AddFeature("on", &device.Feature{})
144164
dev.AddFeature("brightness", &device.Feature{})
145-
if hasColorTemp {
165+
166+
switch lType {
167+
case lTypeTemp:
146168
dev.AddFeature("colorTemperature", &device.Feature{})
169+
case lTypeRgb:
170+
dev.AddFeature("hue", &device.Feature{})
171+
dev.AddFeature("saturation", &device.Feature{})
147172
}
148173
if dev != nil {
149174
h.isRunning = true
@@ -177,6 +202,7 @@ func (h *HemtjanstDevice) handleFeature(name string, ft *device.Feature) {
177202
ft.OnSet(func(msg messaging.Message) {
178203
h.onDeviceSet(ftName, string(msg.Payload()))
179204
})
205+
h.publish(ftName)
180206
}
181207
}
182208

@@ -210,17 +236,50 @@ func (h *HemtjanstDevice) onDeviceSet(feature string, newValue string) {
210236
for _, m := range h.members {
211237
if m.accessory != nil && m.accessory.Light() != nil {
212238
if m.accessory.Light().HasColorTemperature() {
213-
m.accessory.SetColor(newTemp)
239+
m.accessory.SetColorTemp(newTemp)
214240
}
215241
}
216242
}
217243
} else if h.accessory != nil {
218-
h.accessory.SetColor(newTemp)
244+
h.accessory.SetColorTemp(newTemp)
219245
}
220246
}
247+
case "hue":
248+
if hue, err := strconv.Atoi(newValue); err == nil {
249+
h.lastHue = &hue
250+
h.updateColor()
251+
}
252+
case "saturation":
253+
if saturation, err := strconv.Atoi(newValue); err == nil {
254+
h.lastSaturation = &saturation
255+
h.updateColor()
256+
}
221257
}
222258
}
223259

260+
func (h *HemtjanstDevice) updateColor() {
261+
if h.lastHue == nil || h.lastSaturation == nil {
262+
return
263+
}
264+
hue := *h.lastHue
265+
sat := *h.lastSaturation
266+
267+
newColor := colorful.Hsv(float64(hue), float64(sat)/100, float64(1))
268+
269+
if h.isGroup && h.group != nil {
270+
for _, m := range h.members {
271+
if m.accessory != nil && m.accessory.Light() != nil {
272+
if m.accessory.DeviceInfo.IsRGBModel() {
273+
m.accessory.SetColor(newColor)
274+
}
275+
}
276+
}
277+
} else if h.accessory != nil {
278+
h.accessory.SetColor(newColor)
279+
}
280+
281+
}
282+
224283
func (h *HemtjanstDevice) dimmable() *tradfri.Dimmable {
225284
if h.isGroup {
226285
if h.group != nil {
@@ -252,59 +311,83 @@ func (h *HemtjanstDevice) lightSetting() *tradfri.LightSetting {
252311
return &l.LightSetting
253312
}
254313

314+
func (h *HemtjanstDevice) publish(feature string) error {
315+
switch feature {
316+
case "on":
317+
dim := h.dimmable()
318+
if dim == nil {
319+
return fmt.Errorf("Device doesn't support %s", feature)
320+
}
321+
val := "0"
322+
if dim.IsOn() {
323+
val = "1"
324+
}
325+
if ft, err := h.device.GetFeature("on"); err == nil && ft != nil {
326+
return ft.Update(val)
327+
}
328+
return fmt.Errorf("Feature %s not found", feature)
329+
case "brightness":
330+
dim := h.dimmable()
331+
if dim == nil {
332+
return fmt.Errorf("Device doesn't support %s", feature)
333+
}
334+
newDim := dim.DimInt()
335+
if ft, err := h.device.GetFeature("brightness"); err == nil && ft != nil {
336+
return ft.Update(strconv.Itoa(newDim))
337+
}
338+
return fmt.Errorf("Feature %s not found", feature)
339+
case "colorTemperature":
340+
ls := h.lightSetting()
341+
if ls == nil || !ls.HasColorTemperature() {
342+
return fmt.Errorf("Device doesn't support %s", feature)
343+
}
344+
newVal := ""
345+
switch ls.GetColorName() {
346+
case "cold":
347+
newVal = "111"
348+
case "normal":
349+
newVal = "222"
350+
case "warm":
351+
newVal = "400"
352+
}
353+
if ft, err := h.device.GetFeature("colorTemperature"); err == nil && ft != nil {
354+
return ft.Update(newVal)
355+
}
356+
return fmt.Errorf("Feature %s not found", feature)
357+
case "reachable":
358+
if h.isGroup || h.accessory == nil {
359+
return fmt.Errorf("Device doesn't support %s", feature)
360+
}
361+
val := "0"
362+
if h.accessory.IsAlive() {
363+
val = "1"
364+
}
365+
if ft, err := h.device.GetFeature("reachable"); err == nil && ft != nil {
366+
return ft.Update(val)
367+
}
368+
return fmt.Errorf("Feature %s not found", feature)
369+
}
370+
return fmt.Errorf("Device doesn't support %s", feature)
371+
}
372+
255373
func (h *HemtjanstDevice) onTradfriChange(change []*tradfri.ObservedChange) {
374+
colorUpdated := false
256375
for _, ch := range change {
257376
log.Printf("[%s] %s changed from %v to %v", h.Topic, ch.Field, ch.OldValue, ch.NewValue)
258377
switch ch.Field {
259378
case "Dim":
260-
dim := h.dimmable()
261-
if dim == nil {
262-
continue
263-
}
264-
newDim := dim.DimInt()
265-
if ft, err := h.device.GetFeature("brightness"); err == nil && ft != nil {
266-
ft.Update(strconv.Itoa(newDim))
267-
}
379+
h.publish("brightness")
268380
case "On":
269-
dim := h.dimmable()
270-
if dim == nil {
271-
continue
272-
}
273-
val := "0"
274-
if dim.IsOn() {
275-
val = "1"
276-
}
277-
if ft, err := h.device.GetFeature("on"); err == nil && ft != nil {
278-
ft.Update(val)
279-
}
280-
case "Color":
281-
ls := h.lightSetting()
282-
if ls == nil {
283-
continue
284-
}
285-
newVal := ""
286-
switch ls.GetColorName() {
287-
case "cold":
288-
newVal = "111"
289-
case "normal":
290-
newVal = "222"
291-
case "warm":
292-
newVal = "400"
293-
}
294-
if ft, err := h.device.GetFeature("colorTemperature"); err == nil && ft != nil {
295-
ft.Update(newVal)
381+
h.publish("on")
382+
case "Color", "ColorX", "ColorY":
383+
if !colorUpdated {
384+
colorUpdated = true
385+
h.publish("colorTemperature")
386+
h.publish("hue")
387+
h.publish("saturation")
296388
}
297389
case "Alive":
298-
if h.isGroup || h.accessory == nil {
299-
continue
300-
}
301-
val := "0"
302-
if h.accessory.IsAlive() {
303-
val = "1"
304-
}
305-
if ft, err := h.device.GetFeature("reachable"); err == nil && ft != nil {
306-
ft.Update(val)
307-
}
390+
h.publish("reachable")
308391
}
309392

310393
}

tradfri/accessory.go

+14-4
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"log"
66
"strconv"
77
"time"
8+
"image/color"
89
)
910

1011
type Accessory struct {
@@ -112,7 +113,7 @@ func (a *Accessory) SetName(name string) {
112113
})
113114
}
114115

115-
func (a *Accessory) SetColor(c string) {
116+
func (a *Accessory) SetColor(c color.Color) {
116117
if !a.IsLight() {
117118
return
118119
}
@@ -121,14 +122,23 @@ func (a *Accessory) SetColor(c string) {
121122
})
122123
}
123124

125+
func (a *Accessory) SetColorTemp(c string) {
126+
if !a.IsLight() {
127+
return
128+
}
129+
a.updateLight(func(ch *Light) {
130+
ch.SetColorTemp(c)
131+
})
132+
}
133+
124134
func (a *Accessory) SetColorCold() {
125-
a.SetColor(Cold)
135+
a.SetColorTemp(Cold)
126136
}
127137

128138
func (a *Accessory) SetColorNormal() {
129-
a.SetColor(Normal)
139+
a.SetColorTemp(Normal)
130140
}
131141

132142
func (a *Accessory) SetColorWarm() {
133-
a.SetColor(Warm)
143+
a.SetColorTemp(Warm)
134144
}

tradfri/device_info.go

+6
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
package tradfri
22

3+
import "strings"
4+
35
type DeviceInfo struct {
46
Manufacturer string `json:"0"`
57
Model string `json:"1"`
@@ -8,3 +10,7 @@ type DeviceInfo struct {
810
Power int `json:"6"`
911
Battery int `json:"9"`
1012
}
13+
14+
func (d *DeviceInfo) IsRGBModel() bool {
15+
return strings.Contains(d.Model, " CWS ")
16+
}

tradfri/dimmable.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -44,8 +44,8 @@ func calcDim(dim int) uint8 {
4444
newDim = dim*2 - 11
4545
} else {
4646
dimf := float64(dim-40)*3.1 + 69
47-
if dimf > 255 {
48-
newDim = 255
47+
if dimf > 254 {
48+
newDim = 254
4949
} else {
5050
newDim = int(dimf)
5151
}

0 commit comments

Comments
 (0)