From 4142645115fe6048b38d54498f5972bbb5e5757f Mon Sep 17 00:00:00 2001 From: Gisle Aune Date: Mon, 15 Aug 2022 19:45:26 +0200 Subject: [PATCH] add resolve service prototype. --- cmd/bustest/main.go | 32 ++++---- commands/edit.go | 33 ++++++++ device/flags.go | 4 +- device/state.go | 18 ----- events/device.go | 8 +- internal/gentools/slices.go | 24 ++++++ service.go | 2 + services/package.go | 3 + services/resolver.go | 149 ++++++++++++++++++++++++++++++++++++ 9 files changed, 234 insertions(+), 39 deletions(-) create mode 100644 commands/edit.go create mode 100644 internal/gentools/slices.go create mode 100644 services/package.go create mode 100644 services/resolver.go diff --git a/cmd/bustest/main.go b/cmd/bustest/main.go index 2770b9c..bd4ebdd 100644 --- a/cmd/bustest/main.go +++ b/cmd/bustest/main.go @@ -1,35 +1,30 @@ package main import ( + "fmt" lucifer3 "git.aiterp.net/lucifer3/server" "git.aiterp.net/lucifer3/server/commands" "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" - "log" + "git.aiterp.net/lucifer3/server/services" "time" ) func main() { bus := lucifer3.EventBus{} - bus.JoinCallback(func(event lucifer3.Event) bool { - switch event := event.(type) { - case events.Connected: - log.Println("Callback got connect event for", event.Prefix) - } - - return true - }) + bus.Join(services.Resolver()) bus.RunEvent(events.Connected{Prefix: "nanoleaf:10.80.1.11"}) time.Sleep(time.Second / 2) - for _, id := range []string{"e28c", "67db", "f744", "d057", "73c1"} { + for i, id := range []string{"e28c", "67db", "f744", "d057", "73c1"} { bus.RunEvent(events.HardwareState{ - DeviceID: "nanoleaf:10.80.1.11:" + id, + ID: "nanoleaf:10.80.1.11:" + id, + InternalName: fmt.Sprintf("Hexagon %d", i+1), SupportFlags: device.SFlagPower | device.SFlagColor | device.SFlagIntensity, ColorFlags: device.CFlagRGB, State: device.State{}, @@ -37,9 +32,12 @@ func main() { } bus.RunCommand(commands.ReplaceScene{ - IDs: []string{"nanoleaf:10.80.1.11:e28c", "nanoleaf:10.80.1.11:67db", "nanoleaf:10.80.1.11:f744", "nanoleaf:10.80.1.11:d057", "nanoleaf:10.80.1.11:73c1"}, + IDs: []string{"lucifer:name:Hexagon*"}, SceneID: 7, }) + + time.Sleep(time.Second / 4) + for _, id := range []string{"nanoleaf:10.80.1.11:e28c", "nanoleaf:10.80.1.11:67db", "nanoleaf:10.80.1.11:f744", "nanoleaf:10.80.1.11:d057", "nanoleaf:10.80.1.11:73c1"} { bus.RunCommand(commands.SetState{ ID: id, @@ -54,15 +52,19 @@ func main() { time.Sleep(time.Second / 2) bus.RunEvent(events.HardwareState{ - DeviceID: "nanoleaf:10.80.1.11:40e5", + ID: "nanoleaf:10.80.1.11:40e5", + InternalName: "Hexagon 6", SupportFlags: device.SFlagPower | device.SFlagColor | device.SFlagIntensity, ColorFlags: device.CFlagRGB, State: device.State{}, }) bus.RunCommand(commands.ReplaceScene{ - IDs: []string{"nanoleaf:10.80.1.11:40e5", "nanoleaf:10.80.1.11:e28c", "nanoleaf:10.80.1.11:67db", "nanoleaf:10.80.1.11:f744", "nanoleaf:10.80.1.11:d057", "nanoleaf:10.80.1.11:73c1"}, + IDs: []string{"lucifer:name:Hexagon*"}, SceneID: 7, }) + + time.Sleep(time.Second / 4) + bus.RunCommand(commands.SetState{ ID: "nanoleaf:10.80.1.11:40e5", State: device.State{ @@ -73,6 +75,4 @@ func main() { }) time.Sleep(time.Second / 4) - - time.Sleep(time.Second * 4) } diff --git a/commands/edit.go b/commands/edit.go new file mode 100644 index 0000000..a4ecf01 --- /dev/null +++ b/commands/edit.go @@ -0,0 +1,33 @@ +package commands + +import ( + "fmt" + "git.aiterp.net/lucifer3/server/internal/formattools" +) + +type SetName struct { + ID string + Name string +} + +func (c SetName) CommandDescription() string { + return fmt.Sprintf("SetName(%v, %s)", c.ID, c.Name) +} + +type AddTag struct { + IDs []string + Tag string +} + +func (c AddTag) CommandDescription() string { + return fmt.Sprintf("AddTag(%v, %s)", formattools.CompactIDList(c.IDs), c.Tag) +} + +type RemoveTag struct { + IDs []string + Tag string +} + +func (c RemoveTag) CommandDescription() string { + return fmt.Sprintf("RemoveTag(%v, %s)", formattools.CompactIDList(c.IDs), c.Tag) +} diff --git a/device/flags.go b/device/flags.go index f726d42..a87b1cc 100644 --- a/device/flags.go +++ b/device/flags.go @@ -58,11 +58,11 @@ func (f ColorFlag) IsWarmWhite() bool { } func (f ColorFlag) IsColorOnly() bool { - return f != 0 && f&CFlagKelvin == 0 + return f.IsColor() && !f.IsWarmWhite() } func (f ColorFlag) IsWarmWhiteOnly() bool { - return f == CFlagKelvin + return f.IsWarmWhite() && !f.IsColor() } func (f ColorFlag) HasAny(d ColorFlag) bool { diff --git a/device/state.go b/device/state.go index e5ba277..833871a 100644 --- a/device/state.go +++ b/device/state.go @@ -11,11 +11,6 @@ type State struct { Temperature *float64 `json:"temperature"` Intensity *float64 `json:"intensity"` Color *color.Color `json:"color"` - - SensedTemperature *float64 `json:"sensedTemperature"` - SensedLightLevel *float64 `json:"sensedLightLevel"` - SensedPresence *PresenceState `json:"sensedPresence"` - SensedButton *int `json:"sensedButton"` } func (d State) String() string { @@ -33,19 +28,6 @@ func (d State) String() string { parts = append(parts, fmt.Sprintf("color:%s", d.Color.String())) } - if d.SensedTemperature != nil { - parts = append(parts, fmt.Sprintf("sensedTemperature:%.2f", *d.SensedTemperature)) - } - if d.SensedLightLevel != nil { - parts = append(parts, fmt.Sprintf("sensedLightLevel:%.1f", *d.SensedLightLevel)) - } - if d.SensedPresence != nil { - parts = append(parts, fmt.Sprintf("sensedPresense:(%t,%.1f)", d.SensedPresence.Present, d.SensedPresence.AbsenceSeconds)) - } - if d.SensedButton != nil { - parts = append(parts, fmt.Sprintf("sensedButton:%d", *d.SensedButton)) - } - return fmt.Sprint("(", strings.Join(parts, ", "), ")") } diff --git a/events/device.go b/events/device.go index 0ec305c..5d07a73 100644 --- a/events/device.go +++ b/events/device.go @@ -6,15 +6,17 @@ import ( ) type HardwareState struct { - DeviceID string `json:"internalId"` + ID string `json:"internalId"` + InternalName string `json:"internalName"` SupportFlags device.SupportFlags `json:"deviceFlags"` ColorFlags device.ColorFlag `json:"colorFlags"` + Buttons []string `json:"buttons"` State device.State `json:"state"` } func (d HardwareState) EventDescription() string { - return fmt.Sprintf("HardwareState(id:%s, sflags:%s, cflags:%s, state:%s)", - d.DeviceID, d.SupportFlags, d.ColorFlags, d.State, + return fmt.Sprintf("HardwareState(id:%s, iname:%#+v, sflags:%s, cflags:%s, buttons:%v, state:%s)", + d.ID, d.InternalName, d.SupportFlags, d.ColorFlags, d.Buttons, d.State, ) } diff --git a/internal/gentools/slices.go b/internal/gentools/slices.go new file mode 100644 index 0000000..5578a3f --- /dev/null +++ b/internal/gentools/slices.go @@ -0,0 +1,24 @@ +package gentools + +func IndexOf[T comparable](arr []T, value T) int { + for i, v := range arr { + if v == value { + return i + } + } + + return -1 +} + +func AddUniques[T comparable](arr *[]T, values ...T) { +Outer: + for _, v := range values { + for _, v2 := range *arr { + if v2 == v { + continue Outer + } + } + + *arr = append(*arr, v) + } +} diff --git a/service.go b/service.go index 84cf17b..a475d5e 100644 --- a/service.go +++ b/service.go @@ -10,6 +10,8 @@ type Service interface { } type ActiveService interface { + Service + HandleCommand(bus *EventBus, command Command) } diff --git a/services/package.go b/services/package.go new file mode 100644 index 0000000..c6ef4c0 --- /dev/null +++ b/services/package.go @@ -0,0 +1,3 @@ +// Package services is for self-contained general services. Things like bridges, databases and such should +// not be in here. +package services diff --git a/services/resolver.go b/services/resolver.go new file mode 100644 index 0000000..6ac83f0 --- /dev/null +++ b/services/resolver.go @@ -0,0 +1,149 @@ +package services + +import ( + lucifer3 "git.aiterp.net/lucifer3/server" + "git.aiterp.net/lucifer3/server/commands" + "git.aiterp.net/lucifer3/server/events" + "git.aiterp.net/lucifer3/server/internal/gentools" + "strings" +) + +func Resolver() lucifer3.Service { + return &resolver{ + tags: make(map[string][]string), + names: make(map[string][]string), + } +} + +type resolver struct { + tags map[string][]string + names map[string][]string +} + +func (r *resolver) Active() bool { + return true +} + +func (r *resolver) HandleEvent(_ *lucifer3.EventBus, event lucifer3.Event) { + switch event := event.(type) { + case events.HardwareState: + // On HardwareState, use the internal name if (and only if) it is not already named. + + if event.InternalName != "" { + found := false + for _, ids := range r.names { + i := gentools.IndexOf(ids, event.ID) + if i != -1 { + found = true + break + } + } + + if !found { + r.names[event.InternalName] = append(r.names[event.InternalName], event.ID) + } + } + } +} + +func (r *resolver) HandleCommand(bus *lucifer3.EventBus, command lucifer3.Command) { + var resolveList []string + var resolveCB func(newIDs []string) lucifer3.Command + + switch command := command.(type) { + case commands.Assign: + resolveList = command.IDs + resolveCB = func(newIDs []string) lucifer3.Command { command.IDs = newIDs; return command } + case commands.ReplaceScene: + resolveList = command.IDs + resolveCB = func(newIDs []string) lucifer3.Command { command.IDs = newIDs; return command } + case commands.ClearScene: + resolveList = command.IDs + resolveCB = func(newIDs []string) lucifer3.Command { command.IDs = newIDs; return command } + case commands.SetName: + if !strings.HasPrefix(command.ID, "lucifer:") { + for name := range r.names { + i := gentools.IndexOf(r.names[name], command.ID) + if i != -1 { + r.names[name] = append(r.names[name][:i], r.names[name][i+1:]...) + } + } + r.names[command.Name] = append(r.names[command.Name], command.ID) + } + case commands.AddTag: + for _, id := range command.IDs { + if strings.HasPrefix(id, "lucifer:") { + continue + } + + found := false + for _, id2 := range r.tags[command.Tag] { + if id == id2 { + found = true + break + } + } + if !found { + r.tags[command.Tag] = append(r.tags[command.Tag], id) + } + } + + resolveList = command.IDs + resolveCB = func(newIDs []string) lucifer3.Command { command.IDs = newIDs; return command } + case commands.RemoveTag: + for _, id := range command.IDs { + if strings.HasPrefix(id, "lucifer:") { + continue + } + + for i, id2 := range r.tags[command.Tag] { + if id == id2 { + r.tags[command.Tag] = append(r.tags[command.Tag][:i], r.tags[command.Tag][i+1:]...) + break + } + } + } + + resolveList = command.IDs + resolveCB = func(newIDs []string) lucifer3.Command { command.IDs = newIDs; return command } + } + + if resolveList != nil && resolveCB != nil { + newList := make([]string, 0, 16) + + for _, id := range resolveList { + switch { + case strings.HasPrefix(id, "lucifer:tag:"): + tag := id[12:] + if len(newList) == 0 { + newList = append(newList, r.tags[tag]...) + } else { + gentools.AddUniques(&newList, r.tags[tag]...) + } + case strings.HasPrefix(id, "lucifer:name:"): + name := id[13:] + if strings.HasSuffix(name, "*") { + prefix := name[:len(name)-1] + for name, ids := range r.names { + if strings.HasPrefix(name, prefix) { + gentools.AddUniques(&newList, ids...) + } + } + } else if strings.HasPrefix(name, "*") { + suffix := name[1:] + for name, ids := range r.names { + if strings.HasSuffix(name, suffix) { + gentools.AddUniques(&newList, ids...) + } + } + } else { + gentools.AddUniques(&newList, r.names[name]...) + } + } + } + + if len(newList) > 0 { + bus.RunCommand(resolveCB(newList)) + } + } +}