The main server, and probably only repository in this org.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

169 lines
3.3 KiB

package hue
import (
"context"
"errors"
"log"
"sync"
"time"
"git.aiterp.net/lucifer/lucifer/light"
"git.aiterp.net/lucifer/lucifer/models"
gohue "github.com/collinux/gohue"
"golang.org/x/sync/errgroup"
)
// A driver is a driver for Phillips Hue lights.
type driver struct {
mutex sync.Mutex
bridges map[int]*gohue.Bridge
}
func (d *driver) Apply(ctx context.Context, bridge models.Bridge, lights ...models.Light) error {
hueBridge, err := d.getBridge(bridge)
if err != nil {
return err
}
hueLights, err := hueBridge.GetAllLights()
if err != nil {
return err
}
eg, egCtx := errgroup.WithContext(ctx)
for _, hueLight := range hueLights {
if !hueLight.State.Reachable {
continue
}
for _, light := range lights {
if hueLight.UniqueID != light.InternalID {
continue
}
// Prevent race condition since `hueLight` changes per iteration.
hl := hueLight
eg.Go(func() error {
select {
case <-egCtx.Done():
return egCtx.Err()
default:
}
return hl.SetState(gohue.LightState{
On: light.On,
Hue: light.Color.Hue,
Sat: light.Color.Sat,
Bri: light.Color.Bri,
})
})
break
}
}
return eg.Wait()
}
func (d *driver) DiscoverLights(ctx context.Context, bridge models.Bridge) ([]models.Light, error) {
hueBridge, err := d.getBridge(bridge)
if err != nil {
return nil, err
}
hueLights, err := hueBridge.GetAllLights()
if err != nil {
return nil, err
}
lights := make([]models.Light, 0, len(hueLights))
for _, hueLight := range hueLights {
lights = append(lights, models.Light{
ID: -1,
Name: hueLight.Name,
BridgeID: bridge.ID,
InternalID: hueLight.UniqueID,
On: hueLight.State.On,
Color: models.LightColor{
Hue: hueLight.State.Hue,
Sat: hueLight.State.Saturation,
Bri: hueLight.State.Bri,
},
})
}
return lights, nil
}
func (d *driver) DiscoverBridges(ctx context.Context) ([]models.Bridge, error) {
panic("not implemented")
}
func (d *driver) Connect(ctx context.Context, bridge models.Bridge) (models.Bridge, error) {
hueBridge, err := gohue.NewBridge(bridge.Addr)
if err != nil {
log.Fatalln(err)
}
// Make 30 attempts (30 seconds)
attempts := 30
for attempts > 0 {
key, err := hueBridge.CreateUser("Lucifer (git.aiterp.net/lucifer/lucifer)")
if len(key) > 0 && err == nil {
bridge.Key = []byte(key)
bridge.InternalID = hueBridge.Info.Device.SerialNumber
return bridge, nil
}
select {
case <-time.After(time.Second):
attempts--
case <-ctx.Done():
return models.Bridge{}, ctx.Err()
}
}
return models.Bridge{}, errors.New("Failed to create bridge")
}
func (d *driver) getBridge(bridge models.Bridge) (*gohue.Bridge, error) {
d.mutex.Lock()
defer d.mutex.Unlock()
if hueBridge, ok := d.bridges[bridge.ID]; ok {
return hueBridge, nil
}
hueBridge, err := gohue.NewBridge(bridge.Addr)
if err != nil {
return nil, err
}
if err := hueBridge.GetInfo(); err != nil {
return nil, err
}
if hueBridge.Info.Device.SerialNumber != bridge.InternalID {
return nil, errors.New("Serial number does not match hardware")
}
err = hueBridge.Login(string(bridge.Key))
if err != nil {
return nil, err
}
d.bridges[bridge.ID] = hueBridge
return hueBridge, nil
}
func init() {
driver := &driver{
bridges: make(map[int]*gohue.Bridge, 16),
}
light.RegisterDriver("hue", driver)
}