|
|
package hue
import ( "context" "errors" lucifer3 "git.aiterp.net/lucifer3/server" "git.aiterp.net/lucifer3/server/device" "git.aiterp.net/lucifer3/server/events" "git.aiterp.net/lucifer3/server/internal/gentools" "log" "strings" "sync" "time" )
func NewBridge(host string, client *Client) *Bridge { return &Bridge{ client: client, host: host, ctx: context.Background(), cancel: func() {}, resources: map[string]*ResourceData{}, activeStates: map[string]device.State{}, desiredStates: map[string]device.State{}, hasSeen: map[string]bool{}, } }
type Bridge struct { mu sync.Mutex
client *Client host string ctx context.Context cancel context.CancelFunc
resources map[string]*ResourceData activeStates map[string]device.State desiredStates map[string]device.State hasSeen map[string]bool
lastDiscoverCancel context.CancelFunc }
func (b *Bridge) SearchDevices(timeout time.Duration) error { discoverCtx, cancel := context.WithCancel(b.ctx)
b.mu.Lock() if b.lastDiscoverCancel != nil { b.lastDiscoverCancel() } b.lastDiscoverCancel = cancel b.mu.Unlock()
if timeout <= time.Second*10 { timeout = time.Second * 10 }
// Spend half the time waiting for devices
// TODO: Wait for v2 endpoint
ctx, cancel := context.WithTimeout(discoverCtx, timeout/2) defer cancel() err := b.client.LegacyDiscover(ctx, "sensors") if err != nil { return err } <-ctx.Done() if discoverCtx.Err() != nil { return discoverCtx.Err() }
// Spend half the time waiting for lights
// TODO: Wait for v2 endpoint
ctx, cancel = context.WithTimeout(discoverCtx, timeout/2) defer cancel() err = b.client.LegacyDiscover(ctx, "sensors") if err != nil { return err } <-ctx.Done() if discoverCtx.Err() != nil { return discoverCtx.Err() }
// Let the main loop get the new light.
return nil }
func (b *Bridge) RefreshAll() ([]lucifer3.Event, error) { ctx, cancel := context.WithTimeout(b.ctx, time.Second*15) defer cancel()
allResources, err := b.client.AllResources(ctx) if err != nil { return nil, err }
resources := make(map[string]*ResourceData, len(allResources)) for i := range allResources { resources[allResources[i].ID] = &allResources[i] }
newEvents := make([]lucifer3.Event, 0, 0) b.mu.Lock() for id, res := range resources { if res.Type == "device" { hwState, hwEvent := res.GenerateEvent(b.host, resources) if !b.hasSeen[id] { newEvents = append(newEvents, hwState, hwEvent, events.DeviceReady{ID: hwState.ID}) b.hasSeen[id] = true } else { newEvents = append(newEvents, hwState) } } } b.resources = resources b.mu.Unlock()
return newEvents, nil }
func (b *Bridge) ApplyPatches(resources []ResourceData) (events []lucifer3.Event, shouldRefresh bool) { b.mu.Lock() mapCopy := gentools.CopyMap(b.resources) b.mu.Unlock()
for _, resource := range resources { if mapCopy[resource.ID] != nil { mapCopy[resource.ID] = mapCopy[resource.ID].WithPatch(resource) } else { log.Println(resource.ID, resource.Type, "not seen!") shouldRefresh = true } }
for _, resource := range resources { if resource.Owner != nil && resource.Owner.Kind == "device" { if parent, ok := mapCopy[resource.Owner.ID]; ok { hwState, _ := parent.GenerateEvent(b.host, mapCopy) events = append(events, hwState) } } }
b.mu.Lock() b.resources = mapCopy b.mu.Unlock()
return }
func (b *Bridge) SetStates(patch map[string]device.State) { b.mu.Lock() newStates := gentools.CopyMap(b.desiredStates) resources := b.resources b.mu.Unlock()
prefix := "hue:" + b.host + ":" for id, state := range patch { if !strings.HasPrefix(id, prefix) { continue } id = id[len(prefix):]
resource := resources[id] if resource == nil { continue }
newStates[id] = resource.FixState(state, resources) }
b.mu.Lock() b.desiredStates = newStates b.mu.Unlock() }
func (b *Bridge) Run(ctx context.Context, bus *lucifer3.EventBus) interface{} { hwEvents, err := b.RefreshAll() if err != nil { return nil }
bus.RunEvents(hwEvents)
sse := b.client.SSE(ctx) step := time.NewTicker(time.Second * 30) defer step.Stop()
for { select { case updates, ok := <-sse: { if !ok { return errors.New("SSE lost connection") }
newEvents, shouldUpdate := b.ApplyPatches( gentools.Flatten(gentools.Map(updates, func(update SSEUpdate) []ResourceData { return update.Data })), )
bus.RunEvents(newEvents)
if shouldUpdate { hwEvents, err := b.RefreshAll() if err != nil { return nil }
bus.RunEvents(hwEvents) } } case <-ctx.Done(): { return nil } } }
}
|