Browse Source

new and improved Resolver.

beelzebub
Gisle Aune 2 years ago
parent
commit
6eb03b2e44
  1. 52
      cmd/bustest/main.go
  2. 30
      commands/edit.go
  3. 11
      commands/scene.go
  4. 5
      device/Resolver.go
  5. 65
      device/pointer.go
  6. 2
      go.mod
  7. 2
      go.sum
  8. 188
      services/resolver.go

52
cmd/bustest/main.go

@ -6,16 +6,16 @@ import (
"git.aiterp.net/lucifer3/server/commands" "git.aiterp.net/lucifer3/server/commands"
"git.aiterp.net/lucifer3/server/device" "git.aiterp.net/lucifer3/server/device"
"git.aiterp.net/lucifer3/server/events" "git.aiterp.net/lucifer3/server/events"
"git.aiterp.net/lucifer3/server/internal/color"
"git.aiterp.net/lucifer3/server/internal/gentools"
"git.aiterp.net/lucifer3/server/services" "git.aiterp.net/lucifer3/server/services"
"log"
"time" "time"
) )
func main() { func main() {
bus := lucifer3.EventBus{} bus := lucifer3.EventBus{}
bus.Join(services.Resolver())
resolver := services.NewResolver()
bus.Join(resolver)
bus.RunEvent(events.Connected{Prefix: "nanoleaf:10.80.1.11"}) bus.RunEvent(events.Connected{Prefix: "nanoleaf:10.80.1.11"})
@ -32,24 +32,25 @@ func main() {
} }
bus.RunCommand(commands.ReplaceScene{ bus.RunCommand(commands.ReplaceScene{
IDs: []string{"lucifer:name:Hexagon*"},
Match: "lucifer:name:Hex*",
SceneID: 7, SceneID: 7,
}) })
time.Sleep(time.Second / 4)
time.Sleep(time.Second / 8)
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,
State: device.State{
Power: gentools.Ptr(true),
Intensity: gentools.Ptr(0.75),
Color: gentools.Ptr(color.MustParse("xy:0.3209,0.1542")),
},
})
}
bus.RunCommand(commands.AddAlias{
Match: "nanoleaf:10.80.1.{11,7,16,5}:*",
Alias: "lucifer:tag:Magic Lamps",
})
time.Sleep(time.Second / 2)
time.Sleep(time.Second / 8)
bus.RunCommand(commands.ReplaceScene{
Match: "lucifer:name:Hex*",
SceneID: 7,
})
time.Sleep(time.Second / 8)
bus.RunEvent(events.HardwareState{ bus.RunEvent(events.HardwareState{
ID: "nanoleaf:10.80.1.11:40e5", ID: "nanoleaf:10.80.1.11:40e5",
@ -58,21 +59,14 @@ func main() {
ColorFlags: device.CFlagRGB, ColorFlags: device.CFlagRGB,
State: device.State{}, State: device.State{},
}) })
bus.RunCommand(commands.ReplaceScene{
IDs: []string{"lucifer:name:Hexagon*"},
SceneID: 7,
})
time.Sleep(time.Second / 4)
time.Sleep(time.Second / 8)
bus.RunCommand(commands.SetState{
ID: "nanoleaf:10.80.1.11:40e5",
State: device.State{
Power: gentools.Ptr(true),
Intensity: gentools.Ptr(0.75),
Color: gentools.Ptr(color.MustParse("xy:0.3209,0.1542")),
},
})
log.Println("Search \"**:Hexagon {1,5,6}\"")
for _, dev := range resolver.Resolve("lucifer:name:Hexagon {1,5,6}") {
log.Println("- ID:", dev.ID)
log.Println(" Aliases:", dev.Aliases)
}
time.Sleep(time.Second / 4) time.Sleep(time.Second / 4)
} }

30
commands/edit.go

@ -2,32 +2,22 @@ package commands
import ( import (
"fmt" "fmt"
"git.aiterp.net/lucifer3/server/internal/formattools"
) )
type SetName struct {
ID string
Name string
type AddAlias struct {
Match string
Alias string
} }
func (c SetName) CommandDescription() string {
return fmt.Sprintf("SetName(%v, %s)", c.ID, c.Name)
func (c AddAlias) CommandDescription() string {
return fmt.Sprintf("AddAlias(%v, %v)", c.Match, c.Alias)
} }
type AddTag struct {
IDs []string
Tag string
type RemoveAlias struct {
Match string
Alias 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)
func (c RemoveAlias) CommandDescription() string {
return fmt.Sprintf("RemoveAlias(%v, %v)", c.Match, c.Alias)
} }

11
commands/scene.go

@ -2,22 +2,21 @@ package commands
import ( import (
"fmt" "fmt"
"git.aiterp.net/lucifer3/server/internal/formattools"
) )
type ReplaceScene struct { type ReplaceScene struct {
IDs []string `json:"ids"`
SceneID int64 `json:"sceneId"`
Match string `json:"match"`
SceneID int64 `json:"sceneId"`
} }
func (c ReplaceScene) CommandDescription() string { func (c ReplaceScene) CommandDescription() string {
return fmt.Sprintf("ReplaceScene(%v, %d)", formattools.CompactIDList(c.IDs), c.SceneID)
return fmt.Sprintf("ReplaceScene(%v, %d)", c.Match, c.SceneID)
} }
type ClearScene struct { type ClearScene struct {
IDs []string `json:"ids"`
Match string `json:"match"`
} }
func (c ClearScene) CommandDescription() string { func (c ClearScene) CommandDescription() string {
return fmt.Sprintf("ClearScene(%v)", formattools.CompactIDList(c.IDs))
return fmt.Sprintf("ClearScene(%v)", c.Match)
} }

5
device/Resolver.go

@ -0,0 +1,5 @@
package device
type Resolver interface {
Resolve(pattern string) []Pointer
}

65
device/pointer.go

@ -0,0 +1,65 @@
package device
import (
"strings"
)
type PointerMatcher interface {
Match(str string) bool
}
type Pointer struct {
ID string `json:"id"`
Aliases []string `json:"aliases"`
}
func (p *Pointer) AddAlias(alias string) {
if strings.HasPrefix(alias, "lucifer:name:") {
for i, alias2 := range p.Aliases {
if strings.HasPrefix(alias2, "lucifer:name:") {
p.Aliases = append(p.Aliases[:i], p.Aliases[i+1:]...)
break
}
}
}
for _, alias2 := range p.Aliases {
if alias2 == alias {
return
}
}
p.Aliases = append(p.Aliases, alias)
}
func (p *Pointer) RemoveAlias(alias string) {
for i, alias2 := range p.Aliases {
if alias2 == alias {
p.Aliases = append(p.Aliases[:i], p.Aliases[i+1:]...)
break
}
}
}
func (p *Pointer) Name() string {
for _, alias := range p.Aliases {
if strings.HasPrefix(alias, "lucifer:name:") {
return alias
}
}
return ""
}
func (p *Pointer) Matches(matcher PointerMatcher) bool {
if matcher.Match(p.ID) {
return true
}
for _, alias := range p.Aliases {
if matcher.Match(alias) {
return true
}
}
return false
}

2
go.mod

@ -3,3 +3,5 @@ module git.aiterp.net/lucifer3/server
go 1.19 go 1.19
require github.com/lucasb-eyer/go-colorful v1.2.0 require github.com/lucasb-eyer/go-colorful v1.2.0
require github.com/gobwas/glob v0.2.3 // indirect

2
go.sum

@ -1,2 +1,4 @@
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 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=

188
services/resolver.go

@ -3,143 +3,105 @@ package services
import ( import (
lucifer3 "git.aiterp.net/lucifer3/server" lucifer3 "git.aiterp.net/lucifer3/server"
"git.aiterp.net/lucifer3/server/commands" "git.aiterp.net/lucifer3/server/commands"
"git.aiterp.net/lucifer3/server/device"
"git.aiterp.net/lucifer3/server/events" "git.aiterp.net/lucifer3/server/events"
"git.aiterp.net/lucifer3/server/internal/gentools"
"reflect"
"strings"
"github.com/gobwas/glob"
"sync"
) )
func Resolver() lucifer3.Service {
return &resolver{
tags: make(map[string][]string),
names: make(map[string][]string),
func NewResolver() *Resolver {
return &Resolver{
pointers: make([]*device.Pointer, 0, 128),
} }
} }
type resolver struct {
tags map[string][]string
names map[string][]string
// Resolver implements both device.Resolver and lucifer3.Service. There should be only one of them as it keeps its
// state from event-sourcery.
type Resolver struct {
mu sync.Mutex
pointers []*device.Pointer
} }
func (r *resolver) Active() bool {
return true
func (r *Resolver) ensure(id string) *device.Pointer {
for _, ptr := range r.pointers {
if ptr.ID == id {
return ptr
}
}
r.pointers = append(r.pointers, &device.Pointer{
ID: id,
})
return r.pointers[len(r.pointers)-1]
} }
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) resolve(pattern string) []*device.Pointer {
g, err := glob.Compile(pattern, ':')
if err != nil {
return []*device.Pointer{}
}
res := make([]*device.Pointer, 0, 32)
for _, ptr := range r.pointers {
if ptr.Matches(g) {
res = append(res, ptr)
} }
} }
return res
} }
func (r *resolver) rerun(bus *lucifer3.EventBus, resolveList *[]string, command interface{}) {
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]...)
}
}
func (r *Resolver) Resolve(pattern string) []device.Pointer {
g, err := glob.Compile(pattern, ':')
if err != nil {
return []device.Pointer{}
} }
if len(newList) > 0 {
*resolveList = newList
r.mu.Lock()
defer r.mu.Unlock()
bus.RunCommand(reflect.Indirect(reflect.ValueOf(command)).Interface().(lucifer3.Command))
res := make([]device.Pointer, 0, 32)
for _, ptr := range r.pointers {
if ptr.Matches(g) {
res = append(res, *ptr)
}
} }
return res
} }
func (r *resolver) HandleCommand(bus *lucifer3.EventBus, command lucifer3.Command) {
func (r *Resolver) Active() bool {
return true
}
switch command := command.(type) {
case commands.Assign:
r.rerun(bus, &command.IDs, &command)
case commands.ReplaceScene:
r.rerun(bus, &command.IDs, &command)
case commands.ClearScene:
r.rerun(bus, &command.IDs, &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)
}
func (r *Resolver) HandleEvent(_ *lucifer3.EventBus, event lucifer3.Event) {
switch event := event.(type) {
// On HardwareState, use the internal name if (and only if) it is not already named.
case events.HardwareState:
r.mu.Lock()
ptr := r.ensure(event.ID)
if event.InternalName != "" && ptr.Name() == "" {
ptr.AddAlias("lucifer:name:" + event.InternalName)
} }
r.mu.Unlock()
}
}
r.rerun(bus, &command.IDs, &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
}
}
func (r *Resolver) HandleCommand(_ *lucifer3.EventBus, command lucifer3.Command) {
switch command := command.(type) {
case commands.AddAlias:
r.mu.Lock()
for _, ptr := range r.resolve(command.Match) {
ptr.AddAlias(command.Alias)
} }
r.mu.Unlock()
r.rerun(bus, &command.IDs, &command)
case commands.RemoveAlias:
r.mu.Lock()
for _, ptr := range r.resolve(command.Match) {
ptr.AddAlias(command.Alias)
}
r.mu.Unlock()
} }
} }
Loading…
Cancel
Save