Browse Source

hue driver can set stuff now, I hope

beelzebub
Gisle Aune 2 years ago
parent
commit
edbc576eed
  1. 2
      bus.go
  2. 2
      commands/state.go
  3. 7
      device/state.go
  4. 3
      go.mod
  5. 2
      go.sum
  6. 149
      internal/color/color.go
  7. 232
      services/hue/bridge.go
  8. 11
      services/hue/data.go
  9. 13
      services/hue/service.go

2
bus.go

@ -65,7 +65,7 @@ func (b *EventBus) JoinPrivileged(service ActiveService) {
}
func (b *EventBus) RunCommand(command Command) {
if cd := command.CommandDescription(); !strings.HasPrefix(cd, "SetState") {
if cd := command.CommandDescription(); !strings.HasPrefix(cd, "SetStates(") {
if setStates := atomic.LoadInt32(&b.setStates); setStates > 0 {
fmt.Println("[INFO]", setStates, "SetStates commands hidden.")
atomic.AddInt32(&b.setStates, -setStates)

2
commands/state.go

@ -20,7 +20,7 @@ func (c SetState) Matches(driver string) (sub string, ok bool) {
}
func (c SetState) CommandDescription() string {
return fmt.Sprintf("SetStates(%s, %s)", c.ID, c.State)
return fmt.Sprintf("SetState(%s, %s)", c.ID, c.State)
}
type SetStateBatch map[string]device.State

7
device/state.go

@ -14,6 +14,13 @@ type State struct {
Color *color.Color `json:"color"`
}
func (s State) Empty() bool {
return s.Power == nil &&
s.Temperature == nil &&
s.Intensity == nil &&
s.Color == nil
}
func (s State) String() string {
parts := make([]string, 0, 4)
if s.Power != nil {

3
go.mod

@ -3,4 +3,7 @@ module git.aiterp.net/lucifer3/server
go 1.19
require github.com/lucasb-eyer/go-colorful v1.2.0
require github.com/gobwas/glob v0.2.3
require golang.org/x/sync v0.1.0 // indirect

2
go.sum

@ -2,3 +2,5 @@ github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=
github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8=
github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=

149
internal/color/color.go

@ -104,6 +104,10 @@ func (col *Color) ToRGB() (col2 Color, ok bool) {
rgb := col.XY.ToRGB()
col2 = Color{RGB: &rgb}
ok = true
} else if col.K != nil {
rgb := kToRGB(*col.K)
col2 = Color{RGB: &rgb}
ok = true
}
return
@ -122,6 +126,10 @@ func (col *Color) ToHS() (col2 Color, ok bool) {
hs := col.XY.ToHS()
col2 = Color{HS: &hs}
ok = true
} else if col.K != nil {
hs := kToRGB(*col.K).ToHS()
col2 = Color{HS: &hs}
ok = true
}
return
@ -174,6 +182,10 @@ func (col *Color) ToXY() (col2 Color, ok bool) {
xy := col.RGB.ToXY()
col2 = Color{XY: &xy}
ok = true
} else if col.K != nil {
xy := kToRGB(*col.K).ToXY()
col2 = Color{XY: &xy}
ok = true
}
return
@ -436,3 +448,140 @@ func hex2digit(h byte) int {
return int(h - '0')
}
}
func kToRGB(kelvin int) RGB {
if kelvin < 1000 {
kelvin = 1000
} else if kelvin > 12000 {
kelvin = 12000
}
if kelvin%100 == 0 {
return kelvinRGBTable[kelvin]
}
fac := float64(kelvin%100) / 100
floor := (kelvin / 100) * 100
ceil := floor + 100
floorRGB := kelvinRGBTable[floor]
ceilRGB := kelvinRGBTable[ceil]
return RGB{
Red: (floorRGB.Red * fac) + (ceilRGB.Red * (1 - fac)),
Green: (floorRGB.Green * fac) + (ceilRGB.Green * (1 - fac)),
Blue: (floorRGB.Blue * fac) + (ceilRGB.Blue * (1 - fac)),
}
}
var kelvinRGBTable = map[int]RGB{
1000: {Red: 1.000, Green: 0.220, Blue: 0.000},
1100: {Red: 1.000, Green: 0.278, Blue: 0.000},
1200: {Red: 1.000, Green: 0.325, Blue: 0.000},
1300: {Red: 1.000, Green: 0.365, Blue: 0.000},
1400: {Red: 1.000, Green: 0.396, Blue: 0.000},
1500: {Red: 1.000, Green: 0.427, Blue: 0.000},
1600: {Red: 1.000, Green: 0.451, Blue: 0.000},
1700: {Red: 1.000, Green: 0.475, Blue: 0.000},
1800: {Red: 1.000, Green: 0.494, Blue: 0.000},
1900: {Red: 1.000, Green: 0.514, Blue: 0.000},
2000: {Red: 1.000, Green: 0.541, Blue: 0.071},
2100: {Red: 1.000, Green: 0.557, Blue: 0.129},
2200: {Red: 1.000, Green: 0.576, Blue: 0.173},
2300: {Red: 1.000, Green: 0.596, Blue: 0.212},
2400: {Red: 1.000, Green: 0.616, Blue: 0.247},
2500: {Red: 1.000, Green: 0.631, Blue: 0.282},
2600: {Red: 1.000, Green: 0.647, Blue: 0.310},
2700: {Red: 1.000, Green: 0.663, Blue: 0.341},
2800: {Red: 1.000, Green: 0.678, Blue: 0.369},
2900: {Red: 1.000, Green: 0.694, Blue: 0.396},
3000: {Red: 1.000, Green: 0.706, Blue: 0.420},
3100: {Red: 1.000, Green: 0.722, Blue: 0.447},
3200: {Red: 1.000, Green: 0.733, Blue: 0.471},
3300: {Red: 1.000, Green: 0.745, Blue: 0.494},
3400: {Red: 1.000, Green: 0.757, Blue: 0.518},
3500: {Red: 1.000, Green: 0.769, Blue: 0.537},
3600: {Red: 1.000, Green: 0.780, Blue: 0.561},
3700: {Red: 1.000, Green: 0.788, Blue: 0.580},
3800: {Red: 1.000, Green: 0.800, Blue: 0.600},
3900: {Red: 1.000, Green: 0.808, Blue: 0.624},
4000: {Red: 1.000, Green: 0.820, Blue: 0.639},
4100: {Red: 1.000, Green: 0.827, Blue: 0.659},
4200: {Red: 1.000, Green: 0.835, Blue: 0.678},
4300: {Red: 1.000, Green: 0.843, Blue: 0.694},
4400: {Red: 1.000, Green: 0.851, Blue: 0.714},
4500: {Red: 1.000, Green: 0.859, Blue: 0.729},
4600: {Red: 1.000, Green: 0.867, Blue: 0.745},
4700: {Red: 1.000, Green: 0.875, Blue: 0.761},
4800: {Red: 1.000, Green: 0.882, Blue: 0.776},
4900: {Red: 1.000, Green: 0.890, Blue: 0.792},
5000: {Red: 1.000, Green: 0.894, Blue: 0.808},
5100: {Red: 1.000, Green: 0.902, Blue: 0.824},
5200: {Red: 1.000, Green: 0.910, Blue: 0.835},
5300: {Red: 1.000, Green: 0.914, Blue: 0.851},
5400: {Red: 1.000, Green: 0.922, Blue: 0.863},
5500: {Red: 1.000, Green: 0.925, Blue: 0.878},
5600: {Red: 1.000, Green: 0.933, Blue: 0.890},
5700: {Red: 1.000, Green: 0.937, Blue: 0.902},
5800: {Red: 1.000, Green: 0.941, Blue: 0.914},
5900: {Red: 1.000, Green: 0.949, Blue: 0.925},
6000: {Red: 1.000, Green: 0.953, Blue: 0.937},
6100: {Red: 1.000, Green: 0.957, Blue: 0.949},
6200: {Red: 1.000, Green: 0.961, Blue: 0.961},
6300: {Red: 1.000, Green: 0.965, Blue: 0.969},
6400: {Red: 1.000, Green: 0.973, Blue: 0.984},
6500: {Red: 1.000, Green: 0.976, Blue: 0.992},
6600: {Red: 0.996, Green: 0.976, Blue: 1.000},
6700: {Red: 0.988, Green: 0.969, Blue: 1.000},
6800: {Red: 0.976, Green: 0.965, Blue: 1.000},
6900: {Red: 0.969, Green: 0.961, Blue: 1.000},
7000: {Red: 0.961, Green: 0.953, Blue: 1.000},
7100: {Red: 0.953, Green: 0.949, Blue: 1.000},
7200: {Red: 0.941, Green: 0.945, Blue: 1.000},
7300: {Red: 0.937, Green: 0.941, Blue: 1.000},
7400: {Red: 0.929, Green: 0.937, Blue: 1.000},
7500: {Red: 0.922, Green: 0.933, Blue: 1.000},
7600: {Red: 0.914, Green: 0.929, Blue: 1.000},
7700: {Red: 0.906, Green: 0.925, Blue: 1.000},
7800: {Red: 0.902, Green: 0.922, Blue: 1.000},
7900: {Red: 0.894, Green: 0.918, Blue: 1.000},
8000: {Red: 0.890, Green: 0.914, Blue: 1.000},
8100: {Red: 0.882, Green: 0.910, Blue: 1.000},
8200: {Red: 0.878, Green: 0.906, Blue: 1.000},
8300: {Red: 0.871, Green: 0.902, Blue: 1.000},
8400: {Red: 0.867, Green: 0.902, Blue: 1.000},
8500: {Red: 0.863, Green: 0.898, Blue: 1.000},
8600: {Red: 0.855, Green: 0.898, Blue: 1.000},
8700: {Red: 0.851, Green: 0.890, Blue: 1.000},
8800: {Red: 0.847, Green: 0.890, Blue: 1.000},
8900: {Red: 0.843, Green: 0.886, Blue: 1.000},
9000: {Red: 0.839, Green: 0.882, Blue: 1.000},
9100: {Red: 0.831, Green: 0.882, Blue: 1.000},
9200: {Red: 0.827, Green: 0.878, Blue: 1.000},
9300: {Red: 0.824, Green: 0.875, Blue: 1.000},
9400: {Red: 0.820, Green: 0.875, Blue: 1.000},
9500: {Red: 0.816, Green: 0.871, Blue: 1.000},
9600: {Red: 0.812, Green: 0.867, Blue: 1.000},
9700: {Red: 0.812, Green: 0.867, Blue: 1.000},
9800: {Red: 0.808, Green: 0.863, Blue: 1.000},
9900: {Red: 0.804, Green: 0.863, Blue: 1.000},
10000: {Red: 0.812, Green: 0.855, Blue: 1.000},
10100: {Red: 0.812, Green: 0.855, Blue: 1.000},
10200: {Red: 0.808, Green: 0.851, Blue: 1.000},
10300: {Red: 0.804, Green: 0.851, Blue: 1.000},
10400: {Red: 0.800, Green: 0.847, Blue: 1.000},
10500: {Red: 0.800, Green: 0.847, Blue: 1.000},
10600: {Red: 0.796, Green: 0.843, Blue: 1.000},
10700: {Red: 0.792, Green: 0.843, Blue: 1.000},
10800: {Red: 0.792, Green: 0.839, Blue: 1.000},
10900: {Red: 0.788, Green: 0.839, Blue: 1.000},
11000: {Red: 0.784, Green: 0.835, Blue: 1.000},
11100: {Red: 0.784, Green: 0.835, Blue: 1.000},
11200: {Red: 0.780, Green: 0.831, Blue: 1.000},
11300: {Red: 0.776, Green: 0.831, Blue: 1.000},
11400: {Red: 0.776, Green: 0.831, Blue: 1.000},
11500: {Red: 0.773, Green: 0.827, Blue: 1.000},
11600: {Red: 0.773, Green: 0.827, Blue: 1.000},
11700: {Red: 0.773, Green: 0.824, Blue: 1.000},
11800: {Red: 0.769, Green: 0.824, Blue: 1.000},
11900: {Red: 0.765, Green: 0.824, Blue: 1.000},
12000: {Red: 0.765, Green: 0.820, Blue: 1.000},
}

232
services/hue/bridge.go

@ -6,8 +6,11 @@ import (
lucifer3 "git.aiterp.net/lucifer3/server"
"git.aiterp.net/lucifer3/server/device"
"git.aiterp.net/lucifer3/server/events"
"git.aiterp.net/lucifer3/server/internal/color"
"git.aiterp.net/lucifer3/server/internal/gentools"
"golang.org/x/sync/errgroup"
"log"
"math"
"strings"
"sync"
"time"
@ -22,7 +25,9 @@ func NewBridge(host string, client *Client) *Bridge {
resources: map[string]*ResourceData{},
activeStates: map[string]device.State{},
desiredStates: map[string]device.State{},
colorFlags: map[string]device.ColorFlags{},
hasSeen: map[string]bool{},
triggerCongruenceCheckCh: make(chan struct{}, 2),
}
}
@ -37,8 +42,12 @@ type Bridge struct {
resources map[string]*ResourceData
activeStates map[string]device.State
desiredStates map[string]device.State
colorFlags map[string]device.ColorFlags
reachable map[string]bool
hasSeen map[string]bool
triggerCongruenceCheckCh chan struct{}
lastDiscoverCancel context.CancelFunc
}
@ -100,20 +109,39 @@ func (b *Bridge) RefreshAll() ([]lucifer3.Event, error) {
resources[allResources[i].ID] = &allResources[i]
}
newEvents := make([]lucifer3.Event, 0, 0)
b.mu.Lock()
hasSeen := b.hasSeen
reachable := b.reachable
b.mu.Unlock()
hasSeen = gentools.CopyMap(hasSeen)
reachable = gentools.CopyMap(reachable)
colorFlags := make(map[string]device.ColorFlags)
activeStates := make(map[string]device.State)
newEvents := make([]lucifer3.Event, 0, 0)
for id, res := range resources {
if res.Type == "device" {
if res.Type == "device" && res.Metadata.Archetype != "bridge_v2" {
hwState, hwEvent := res.GenerateEvent(b.host, resources)
if !b.hasSeen[id] {
if !hasSeen[id] {
newEvents = append(newEvents, hwState, hwEvent, events.DeviceReady{ID: hwState.ID})
b.hasSeen[id] = true
hasSeen[id] = true
} else {
newEvents = append(newEvents, hwState)
}
activeStates[id] = hwState.State
colorFlags[id] = hwState.ColorFlags
reachable[id] = !hwState.Unreachable
}
}
b.mu.Lock()
b.resources = resources
b.hasSeen = hasSeen
b.colorFlags = colorFlags
b.activeStates = activeStates
b.reachable = reachable
b.mu.Unlock()
return newEvents, nil
@ -121,9 +149,17 @@ func (b *Bridge) RefreshAll() ([]lucifer3.Event, error) {
func (b *Bridge) ApplyPatches(resources []ResourceData) (events []lucifer3.Event, shouldRefresh bool) {
b.mu.Lock()
mapCopy := gentools.CopyMap(b.resources)
resourceMap := b.resources
activeStates := b.activeStates
reachable := b.reachable
colorFlags := b.colorFlags
b.mu.Unlock()
mapCopy := gentools.CopyMap(resourceMap)
activeStatesCopy := gentools.CopyMap(activeStates)
reachableCopy := gentools.CopyMap(reachable)
colorFlagsCopy := gentools.CopyMap(colorFlags)
for _, resource := range resources {
if mapCopy[resource.ID] != nil {
mapCopy[resource.ID] = mapCopy[resource.ID].WithPatch(resource)
@ -138,12 +174,21 @@ func (b *Bridge) ApplyPatches(resources []ResourceData) (events []lucifer3.Event
if parent, ok := mapCopy[resource.Owner.ID]; ok {
hwState, _ := parent.GenerateEvent(b.host, mapCopy)
events = append(events, hwState)
activeStatesCopy[resource.Owner.ID] = hwState.State
reachableCopy[resource.Owner.ID] = !hwState.Unreachable
if hwState.ColorFlags != 0 {
colorFlagsCopy[resource.Owner.ID] = hwState.ColorFlags
}
}
}
}
b.mu.Lock()
b.resources = mapCopy
b.activeStates = activeStatesCopy
b.reachable = reachableCopy
b.colorFlags = colorFlagsCopy
b.mu.Unlock()
return
@ -167,20 +212,28 @@ func (b *Bridge) SetStates(patch map[string]device.State) {
continue
}
if !state.Empty() {
newStates[id] = resource.FixState(state, resources)
} else {
delete(newStates, id)
}
}
b.mu.Lock()
b.desiredStates = newStates
b.mu.Unlock()
b.triggerCongruenceCheck()
}
func (b *Bridge) Run(ctx context.Context, bus *lucifer3.EventBus) interface{} {
hwEvents, err := b.RefreshAll()
if err != nil {
return nil
return errors.New("failed to connect to bridge")
}
go b.makeCongruentLoop(ctx)
bus.RunEvents(hwEvents)
sse := b.client.SSE(ctx)
@ -206,17 +259,182 @@ func (b *Bridge) Run(ctx context.Context, bus *lucifer3.EventBus) interface{} {
if shouldUpdate {
hwEvents, err := b.RefreshAll()
if err != nil {
return nil
return errors.New("failed to refresh states")
}
bus.RunEvents(hwEvents)
}
b.triggerCongruenceCheck()
}
case <-step.C:
hwEvents, err := b.RefreshAll()
if err != nil {
return nil
}
bus.RunEvents(hwEvents)
b.triggerCongruenceCheck()
case <-ctx.Done():
{
return nil
}
}
}
}
func (b *Bridge) makeCongruentLoop(ctx context.Context) {
for range b.triggerCongruenceCheckCh {
if ctx.Err() != nil {
break
}
// Make sure this loop takes half a second
rateLimit := time.After(time.Second / 2)
// Take states
b.mu.Lock()
resources := b.resources
desiredStates := b.desiredStates
activeStates := b.activeStates
reachable := b.reachable
colorFlags := b.colorFlags
b.mu.Unlock()
newActiveStates := make(map[string]device.State, 0)
updates := make(map[string]ResourceUpdate)
for id, desired := range desiredStates {
active, activeOK := activeStates[id]
lightID := resources[id].ServiceID("light")
if !reachable[id] || !activeOK || lightID == nil {
log.Println("No light", !reachable[id], !activeOK, lightID == nil)
continue
}
light := resources[*lightID]
if light == nil {
log.Println("No light", *lightID)
continue
}
// Handle power first
if desired.Power != nil && active.Power != nil && *desired.Power != *active.Power {
updates["light/"+*lightID] = ResourceUpdate{Power: gentools.Ptr(*active.Power)}
newActiveState := activeStates[id]
newActiveState.Power = gentools.Ptr(*desired.Power)
newActiveStates[id] = newActiveState
continue
}
if active.Power != nil && *active.Power == false {
// Don't do more with shut-off-light.
continue
}
updated := false
update := ResourceUpdate{}
newActiveState := activeStates[id]
if active.Color != nil && desired.Color != nil {
ac := *active.Color
dc := *desired.Color
if !dc.IsKelvin() || !colorFlags[id].IsWarmWhite() {
dc, _ = dc.ToXY()
dc.XY = gentools.Ptr(light.Color.Gamut.Conform(*dc.XY))
}
if dc.XY != nil {
if ac.XY != nil {
dist := math.Abs(ac.XY.X-dc.XY.X) + math.Abs(ac.XY.Y-dc.XY.Y)
if dist > 0.0002 {
update.ColorXY = gentools.Ptr(*dc.XY)
updated = true
}
} else {
newActiveState.Color = &color.Color{XY: gentools.Ptr(*dc.XY)}
update.ColorXY = gentools.Ptr(*dc.XY)
updated = true
}
} else {
dcMirek := 1000000 / *dc.K
if dcMirek < light.ColorTemperature.MirekSchema.MirekMinimum {
dcMirek = light.ColorTemperature.MirekSchema.MirekMinimum
} else if dcMirek > light.ColorTemperature.MirekSchema.MirekMaximum {
dcMirek = light.ColorTemperature.MirekSchema.MirekMaximum
}
acMirek := 0
if ac.K != nil {
acMirek = 1000000 / *ac.K
}
if acMirek != dcMirek {
newActiveState.Color = &color.Color{K: gentools.Ptr(*dc.K)}
update.Mirek = &dcMirek
updated = true
}
}
}
if active.Intensity != nil && desired.Intensity != nil {
if math.Abs(*active.Intensity-*desired.Intensity) >= 0.01 {
update.Brightness = gentools.Ptr(*desired.Intensity * 100)
newActiveState.Intensity = gentools.Ptr(*desired.Intensity)
updated = true
}
}
if updated {
update.TransitionDuration = gentools.Ptr(time.Millisecond * 101)
updates["light/"+*lightID] = update
newActiveStates[id] = newActiveState
}
}
if len(updates) > 0 {
timeout, cancel := context.WithTimeout(ctx, time.Second)
eg, ctx := errgroup.WithContext(timeout)
for key := range updates {
update := updates[key]
split := strings.SplitN(key, "/", 2)
link := ResourceLink{Kind: split[0], ID: split[1]}
eg.Go(func() error {
return b.client.UpdateResource(ctx, link, update)
})
}
err := eg.Wait()
if err != nil {
log.Println("Failed to run update", err)
}
b.mu.Lock()
activeStates = b.activeStates
b.mu.Unlock()
activeStates = gentools.CopyMap(activeStates)
for id, state := range newActiveStates {
activeStates[id] = state
}
b.mu.Lock()
b.activeStates = activeStates
b.mu.Unlock()
cancel()
}
// Wait the remaining time for the rate limit
<-rateLimit
}
}
func (b *Bridge) triggerCongruenceCheck() {
select {
case b.triggerCongruenceCheckCh <- struct{}{}:
default:
}
}

11
services/hue/data.go

@ -182,8 +182,6 @@ func (res *ResourceData) WithPatch(patch ResourceData) *ResourceData {
gentools.ShallowCopyTo(&res2.ProductData, patch.ProductData)
gentools.ShallowCopyTo(&res2.Button, patch.Button)
gentools.ShallowCopyTo(&res2.Power, patch.Power)
gentools.ShallowCopyTo(&res2.Color, patch.Color)
gentools.ShallowCopyTo(&res2.ColorTemperature, patch.ColorTemperature)
gentools.ShallowCopyTo(&res2.Dimming, patch.Dimming)
gentools.ShallowCopyTo(&res2.Dynamics, patch.Dynamics)
gentools.ShallowCopyTo(&res2.Alert, patch.Alert)
@ -218,6 +216,11 @@ func (res *ResourceData) GenerateEvent(hostname string, resources map[string]*Re
}
switch ptr.Kind {
case "zigbee_connectivity":
switch *svc.Status {
case "connectivity_issue":
hwState.Unreachable = true
}
case "device_power":
hwState.BatteryPercentage = gentools.Ptr(int(svc.PowerState.BatteryLevel))
case "button":
@ -237,11 +240,13 @@ func (res *ResourceData) GenerateEvent(hostname string, resources map[string]*Re
if svc.ColorTemperature != nil {
hwState.SupportFlags |= device.SFlagColor
hwState.ColorFlags |= device.CFlagKelvin
hwState.TemperatureRange = &[2]int{
if svc.ColorTemperature.MirekSchema.MirekMinimum != 0 {
hwState.ColorKelvinRange = &[2]int{
1000000 / svc.ColorTemperature.MirekSchema.MirekMinimum,
1000000 / svc.ColorTemperature.MirekSchema.MirekMaximum,
}
}
}
if svc.Color != nil {
hwState.SupportFlags |= device.SFlagColor
hwState.ColorFlags |= device.CFlagXY

13
services/hue/service.go

@ -7,6 +7,7 @@ import (
"git.aiterp.net/lucifer3/server/commands"
"git.aiterp.net/lucifer3/server/events"
"git.aiterp.net/lucifer3/server/internal/gentools"
"log"
"sync"
"time"
)
@ -63,6 +64,18 @@ func (s *service) HandleCommand(bus *lucifer3.EventBus, command lucifer3.Command
}
}
case commands.SearchDevices:
if sub, ok := command.Matches("hue"); ok {
if s.bridges[sub] != nil {
go func() {
err := s.bridges[sub].SearchDevices(time.Second * 30)
if err != nil {
log.Println("Search failed:", err)
}
}()
}
}
case commands.ConnectDevice:
if sub, ok := command.Matches("hue"); ok {
if s.bridges[sub] != nil {

Loading…
Cancel
Save