more tinkering.
This commit is contained in:
@@ -1,14 +1,18 @@
|
|||||||
package lucifer3
|
package lucifer3
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"log"
|
"fmt"
|
||||||
"sync"
|
"sync"
|
||||||
)
|
)
|
||||||
|
|
||||||
type ServiceKey struct{}
|
type ServiceKey struct{}
|
||||||
|
|
||||||
type Event interface {
|
type Event interface {
|
||||||
EventName() string
|
EventDescription() string
|
||||||
|
}
|
||||||
|
|
||||||
|
type Command interface {
|
||||||
|
CommandDescription() string
|
||||||
}
|
}
|
||||||
|
|
||||||
type EventBus struct {
|
type EventBus struct {
|
||||||
@@ -18,7 +22,7 @@ type EventBus struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// JoinCallback joins the event bus for a moment.
|
// JoinCallback joins the event bus for a moment.
|
||||||
func (b *EventBus) JoinCallback(cb func(event Event, sender ServiceID) bool) {
|
func (b *EventBus) JoinCallback(cb func(event Event) bool) {
|
||||||
b.Join(newCallbackService(cb))
|
b.Join(newCallbackService(cb))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -30,7 +34,7 @@ func (b *EventBus) Join(service Service) {
|
|||||||
b.mu.Lock()
|
b.mu.Lock()
|
||||||
listener := &serviceListener{
|
listener := &serviceListener{
|
||||||
bus: b,
|
bus: b,
|
||||||
queue: make([]queuedEvent, 0, 16),
|
queue: make([]serviceMessage, 0, 16),
|
||||||
service: service,
|
service: service,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -40,7 +44,17 @@ func (b *EventBus) Join(service Service) {
|
|||||||
b.mu.Unlock()
|
b.mu.Unlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *EventBus) Send(event Event, sender ServiceID, recipient *ServiceID) {
|
func (b *EventBus) RunCommand(command Command) {
|
||||||
|
fmt.Println("[COMMAND]", command.CommandDescription())
|
||||||
|
b.send(serviceMessage{command: command})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *EventBus) RunEvent(event Event) {
|
||||||
|
fmt.Println("[EVENT]", event.EventDescription())
|
||||||
|
b.send(serviceMessage{event: event})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *EventBus) send(message serviceMessage) {
|
||||||
b.mu.Lock()
|
b.mu.Lock()
|
||||||
defer b.mu.Unlock()
|
defer b.mu.Unlock()
|
||||||
|
|
||||||
@@ -50,15 +64,9 @@ func (b *EventBus) Send(event Event, sender ServiceID, recipient *ServiceID) {
|
|||||||
deleteList = append(deleteList, i-len(deleteList))
|
deleteList = append(deleteList, i-len(deleteList))
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if recipient != nil && *recipient != listener.service.ServiceID() {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
listener.mu.Lock()
|
listener.mu.Lock()
|
||||||
listener.queue = append(listener.queue, queuedEvent{
|
listener.queue = append(listener.queue, message)
|
||||||
event: event,
|
|
||||||
sender: sender,
|
|
||||||
})
|
|
||||||
listener.mu.Unlock()
|
listener.mu.Unlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -66,12 +74,6 @@ func (b *EventBus) Send(event Event, sender ServiceID, recipient *ServiceID) {
|
|||||||
b.listeners = append(b.listeners[:i], b.listeners[i+1:]...)
|
b.listeners = append(b.listeners[:i], b.listeners[i+1:]...)
|
||||||
}
|
}
|
||||||
|
|
||||||
if recipient != nil {
|
|
||||||
log.Printf("%s (-> %s): %s", &sender, recipient, event.EventName())
|
|
||||||
} else {
|
|
||||||
log.Printf("%s: %s", &sender, event.EventName())
|
|
||||||
}
|
|
||||||
|
|
||||||
if b.signal != nil {
|
if b.signal != nil {
|
||||||
close(b.signal)
|
close(b.signal)
|
||||||
b.signal = nil
|
b.signal = nil
|
||||||
@@ -92,12 +94,18 @@ func (b *EventBus) signalCh() <-chan struct{} {
|
|||||||
type serviceListener struct {
|
type serviceListener struct {
|
||||||
mu sync.Mutex
|
mu sync.Mutex
|
||||||
bus *EventBus
|
bus *EventBus
|
||||||
queue []queuedEvent
|
queue []serviceMessage
|
||||||
service Service
|
service Service
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *serviceListener) run(signal <-chan struct{}) {
|
func (l *serviceListener) run(signal <-chan struct{}) {
|
||||||
queue := make([]queuedEvent, 0, 16)
|
// Detect command support
|
||||||
|
var activeService ActiveService
|
||||||
|
if as, ok := l.service.(ActiveService); ok {
|
||||||
|
activeService = as
|
||||||
|
}
|
||||||
|
|
||||||
|
queue := make([]serviceMessage, 0, 16)
|
||||||
|
|
||||||
for {
|
for {
|
||||||
// Listen for the signal, but stop here if the service has marked
|
// Listen for the signal, but stop here if the service has marked
|
||||||
@@ -126,13 +134,19 @@ func (l *serviceListener) run(signal <-chan struct{}) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
l.service.Handle(l.bus, message.event, message.sender)
|
if message.event != nil {
|
||||||
|
l.service.HandleEvent(l.bus, message.event)
|
||||||
|
}
|
||||||
|
|
||||||
|
if message.command != nil && activeService != nil {
|
||||||
|
activeService.HandleCommand(l.bus, message.command)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
queue = queue[:0]
|
queue = queue[:0]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type queuedEvent struct {
|
type serviceMessage struct {
|
||||||
event Event
|
event Event
|
||||||
sender ServiceID
|
command Command
|
||||||
}
|
}
|
||||||
|
|||||||
+45
-44
@@ -2,6 +2,7 @@ package main
|
|||||||
|
|
||||||
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/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/color"
|
||||||
@@ -13,65 +14,65 @@ import (
|
|||||||
func main() {
|
func main() {
|
||||||
bus := lucifer3.EventBus{}
|
bus := lucifer3.EventBus{}
|
||||||
|
|
||||||
bus.JoinCallback(func(event lucifer3.Event, sender lucifer3.ServiceID) bool {
|
bus.JoinCallback(func(event lucifer3.Event) bool {
|
||||||
switch event := event.(type) {
|
switch event := event.(type) {
|
||||||
case events.HubConnected:
|
case events.Connected:
|
||||||
log.Println("Callback got connect event")
|
log.Println("Callback got connect event for", event.Prefix)
|
||||||
case events.DeviceAssignment:
|
|
||||||
log.Println("Callback can see", len(event.IDs), "devices are being assigned")
|
|
||||||
case events.DeviceRegister:
|
|
||||||
log.Println("Callback should not see that", event.InternalID, "has got a new ID")
|
|
||||||
case events.DeviceDesiredStateChange:
|
|
||||||
log.Println("Callback saw", event.ID, "got a new state.")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return true
|
return true
|
||||||
})
|
})
|
||||||
|
|
||||||
c := color.RGB{Red: 1, Green: 0.7, Blue: 0.25}.ToXY()
|
bus.RunEvent(events.Connected{Prefix: "nanoleaf:10.80.1.11"})
|
||||||
|
|
||||||
bus.Send(events.HubConnected{}, lucifer3.ServiceID{Kind: lucifer3.SKDeviceHub, ID: 1}, nil)
|
|
||||||
bus.Send(events.DeviceAssignment{
|
|
||||||
IDs: []int64{1, 2, 3, 4, 5, 6, 7, 8, 9},
|
|
||||||
Version: 1,
|
|
||||||
State: &device.State{
|
|
||||||
Power: gentools.Ptr(true),
|
|
||||||
Temperature: nil,
|
|
||||||
Intensity: nil,
|
|
||||||
Color: &color.Color{XY: &c},
|
|
||||||
},
|
|
||||||
}, lucifer3.ServiceID{Kind: lucifer3.SKSingleton, Name: "DeviceManager"}, nil)
|
|
||||||
bus.Send(events.DeviceAssignment{
|
|
||||||
IDs: []int64{44, 45, 46, 47, 48, 49, 50, 51, 52, 53},
|
|
||||||
Version: 2,
|
|
||||||
Effect: gentools.Ptr[int64](1044),
|
|
||||||
}, lucifer3.ServiceID{Kind: lucifer3.SKSingleton, Name: "DeviceManager"}, nil)
|
|
||||||
|
|
||||||
time.Sleep(time.Second / 2)
|
time.Sleep(time.Second / 2)
|
||||||
|
|
||||||
bus.Send(events.DeviceHardwareStateChange{
|
for _, id := range []string{"e28c", "67db", "f744", "d057", "73c1"} {
|
||||||
InternalID: "c68b310c-7fd6-47a4-ae11-d60d701769da",
|
bus.RunEvent(events.HardwareState{
|
||||||
DeviceFlags: device.SFlagPower | device.SFlagIntensity,
|
DeviceID: "nanoleaf:10.80.1.11:" + id,
|
||||||
ColorFlags: 0,
|
SupportFlags: device.SFlagPower | device.SFlagColor | device.SFlagIntensity,
|
||||||
|
ColorFlags: device.CFlagRGB,
|
||||||
|
State: device.State{},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
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"},
|
||||||
|
SceneID: 7,
|
||||||
|
})
|
||||||
|
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{
|
State: device.State{
|
||||||
Power: gentools.Ptr(true),
|
Power: gentools.Ptr(true),
|
||||||
Intensity: gentools.Ptr(0.75),
|
Intensity: gentools.Ptr(0.75),
|
||||||
|
Color: gentools.Ptr(color.MustParse("xy:0.3209,0.1542")),
|
||||||
},
|
},
|
||||||
}, lucifer3.ServiceID{Kind: lucifer3.SKDeviceHub, ID: 1}, nil)
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
time.Sleep(time.Second / 2)
|
||||||
|
|
||||||
|
bus.RunEvent(events.HardwareState{
|
||||||
|
DeviceID: "nanoleaf:10.80.1.11:40e5",
|
||||||
|
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"},
|
||||||
|
SceneID: 7,
|
||||||
|
})
|
||||||
|
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")),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
time.Sleep(time.Second / 4)
|
time.Sleep(time.Second / 4)
|
||||||
|
|
||||||
bus.Send(events.DeviceRegister{
|
|
||||||
InternalID: "c68b310c-7fd6-47a4-ae11-d60d701769da",
|
|
||||||
ID: 10,
|
|
||||||
}, lucifer3.ServiceID{Kind: lucifer3.SKSingleton, Name: "DeviceManager"}, &lucifer3.ServiceID{Kind: lucifer3.SKDeviceHub, ID: 1})
|
|
||||||
bus.Send(events.DeviceDesiredStateChange{
|
|
||||||
ID: 10,
|
|
||||||
NewState: device.State{
|
|
||||||
Power: gentools.Ptr(true),
|
|
||||||
Intensity: gentools.Ptr(0.75),
|
|
||||||
},
|
|
||||||
}, lucifer3.ServiceID{Kind: lucifer3.SKSingleton, Name: "DeviceManager"}, nil)
|
|
||||||
|
|
||||||
time.Sleep(time.Second * 4)
|
time.Sleep(time.Second * 4)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,16 @@
|
|||||||
|
package commands
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
lucifer3 "git.aiterp.net/lucifer3/server"
|
||||||
|
"git.aiterp.net/lucifer3/server/internal/formattools"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Assign struct {
|
||||||
|
IDs []string `json:"ids"`
|
||||||
|
Effect lucifer3.Effect `json:"effect"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c Assign) CommandDescription() string {
|
||||||
|
return fmt.Sprintf("Assign(%v, %s)", formattools.CompactIDList(c.IDs), c.Effect.EffectDescription())
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
package commands
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"git.aiterp.net/lucifer3/server/internal/formattools"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ReplaceScene struct {
|
||||||
|
IDs []string `json:"ids"`
|
||||||
|
SceneID int64 `json:"sceneId"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c ReplaceScene) CommandDescription() string {
|
||||||
|
return fmt.Sprintf("ReplaceScene(%v, %d)", formattools.CompactIDList(c.IDs), c.SceneID)
|
||||||
|
}
|
||||||
|
|
||||||
|
type ClearScene struct {
|
||||||
|
IDs []string `json:"ids"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c ClearScene) CommandDescription() string {
|
||||||
|
return fmt.Sprintf("ClearScene(%v)", formattools.CompactIDList(c.IDs))
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
package commands
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"git.aiterp.net/lucifer3/server/device"
|
||||||
|
)
|
||||||
|
|
||||||
|
type SetState struct {
|
||||||
|
ID string
|
||||||
|
State device.State
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c SetState) CommandDescription() string {
|
||||||
|
return fmt.Sprintf("SetState(%s, %s)", c.ID, c.State)
|
||||||
|
}
|
||||||
+24
-9
@@ -1,17 +1,12 @@
|
|||||||
package device
|
package device
|
||||||
|
|
||||||
import "math/bits"
|
import (
|
||||||
|
"git.aiterp.net/lucifer3/server/internal/gentools"
|
||||||
|
"math/bits"
|
||||||
|
)
|
||||||
|
|
||||||
type SupportFlags uint32
|
type SupportFlags uint32
|
||||||
|
|
||||||
func (f SupportFlags) HasAny(d SupportFlags) bool {
|
|
||||||
return (f & d) != 0
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f SupportFlags) HasAll(d SupportFlags) bool {
|
|
||||||
return bits.OnesCount32(uint32(f&d)) == bits.OnesCount32(uint32(d))
|
|
||||||
}
|
|
||||||
|
|
||||||
const (
|
const (
|
||||||
SFlagPower SupportFlags = 1 << iota
|
SFlagPower SupportFlags = 1 << iota
|
||||||
SFlagIntensity
|
SFlagIntensity
|
||||||
@@ -23,6 +18,20 @@ const (
|
|||||||
SFlagSensorPresence
|
SFlagSensorPresence
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var supportFlags = []SupportFlags{SFlagPower, SFlagIntensity, SFlagColor, SFlagTemperature, SFlagSensorButtons, SFlagSensorTemperature, SFlagSensorLightLevel, SFlagSensorPresence}
|
||||||
|
|
||||||
|
func (f SupportFlags) String() string {
|
||||||
|
return gentools.FlagString(f, supportFlags, []string{"P", "I", "C", "T", "SB", "ST", "SLL", "SP"})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f SupportFlags) HasAny(d SupportFlags) bool {
|
||||||
|
return (f & d) != 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f SupportFlags) HasAll(d SupportFlags) bool {
|
||||||
|
return bits.OnesCount32(uint32(f&d)) == bits.OnesCount32(uint32(d))
|
||||||
|
}
|
||||||
|
|
||||||
// ColorFlag is primarily to detect warm-white lights, as XY/RGB/HS/HSK can convert without trouble.
|
// ColorFlag is primarily to detect warm-white lights, as XY/RGB/HS/HSK can convert without trouble.
|
||||||
type ColorFlag uint32
|
type ColorFlag uint32
|
||||||
|
|
||||||
@@ -34,6 +43,12 @@ const (
|
|||||||
CFlagKelvin
|
CFlagKelvin
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var colorFlags = []ColorFlag{CFlagXY, CFlagRGB, CFlagHS, CFlagHSK, CFlagKelvin}
|
||||||
|
|
||||||
|
func (f ColorFlag) String() string {
|
||||||
|
return gentools.FlagString(f, colorFlags, []string{"XY", "RGB", "HS", "HSK", "K"})
|
||||||
|
}
|
||||||
|
|
||||||
func (f ColorFlag) IsColor() bool {
|
func (f ColorFlag) IsColor() bool {
|
||||||
return f.HasAny(CFlagXY | CFlagRGB | CFlagHS | CFlagHSK)
|
return f.HasAny(CFlagXY | CFlagRGB | CFlagHS | CFlagHSK)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,32 @@
|
|||||||
|
package effects
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"git.aiterp.net/lucifer3/server/device"
|
||||||
|
"git.aiterp.net/lucifer3/server/internal/color"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Manual struct {
|
||||||
|
Power *bool `json:"power,omitempty"`
|
||||||
|
Color *color.Color `json:"color,omitempty"`
|
||||||
|
Intensity *float64 `json:"intensity,omitempty"`
|
||||||
|
Temperature *float64 `json:"temperature,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *Manual) EffectDescription() string {
|
||||||
|
return fmt.Sprintf("Manual%s", e.State(0, 0, 0).String())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *Manual) Frequency() time.Duration {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *Manual) State(int, int, int) device.State {
|
||||||
|
return device.State{
|
||||||
|
Power: e.Power,
|
||||||
|
Temperature: e.Temperature,
|
||||||
|
Intensity: e.Intensity,
|
||||||
|
Color: e.Color,
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
package events
|
||||||
|
|
||||||
|
import "fmt"
|
||||||
|
|
||||||
|
type Connected struct {
|
||||||
|
Prefix string `json:"prefix"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e Connected) EventDescription() string {
|
||||||
|
return fmt.Sprintf("Connect(prefix:%s)", e.Prefix)
|
||||||
|
}
|
||||||
|
|
||||||
|
type Disconnected struct {
|
||||||
|
Prefix string `json:"prefix"`
|
||||||
|
Reason string `json:"reason"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e Disconnected) EventDescription() string {
|
||||||
|
return fmt.Sprintf("Disconnected(prefix:%s, reason:%s)", e.Prefix, e.Reason)
|
||||||
|
}
|
||||||
+10
-71
@@ -3,86 +3,25 @@ package events
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"git.aiterp.net/lucifer3/server/device"
|
"git.aiterp.net/lucifer3/server/device"
|
||||||
"git.aiterp.net/lucifer3/server/internal/gentools"
|
|
||||||
"strings"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type DeviceHardwareStateChange struct {
|
type HardwareState struct {
|
||||||
ID int64 `json:"id,omitempty"`
|
DeviceID string `json:"internalId"`
|
||||||
InternalID string `json:"internalId"`
|
SupportFlags device.SupportFlags `json:"deviceFlags"`
|
||||||
DeviceFlags device.SupportFlags `json:"deviceFlags"`
|
|
||||||
ColorFlags device.ColorFlag `json:"colorFlags"`
|
ColorFlags device.ColorFlag `json:"colorFlags"`
|
||||||
State device.State `json:"state"`
|
State device.State `json:"state"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d DeviceHardwareStateChange) EventName() string {
|
func (d HardwareState) EventDescription() string {
|
||||||
idStr := ""
|
return fmt.Sprintf("HardwareState(id:%s, sflags:%s, cflags:%s, state:%s)",
|
||||||
if d.ID != 0 {
|
d.DeviceID, d.SupportFlags, d.ColorFlags, d.State,
|
||||||
idStr = fmt.Sprintf("id:%d, ", d.ID)
|
|
||||||
}
|
|
||||||
|
|
||||||
return fmt.Sprintf("DeviceHardwareStateChange(%siid:%s, dflags:%d, cflags:%d, state:%s)",
|
|
||||||
idStr, d.InternalID, d.DeviceFlags, d.ColorFlags, d.State,
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeviceRegister must be sent directly to the device hub that took it.
|
type Unreachable struct {
|
||||||
type DeviceRegister struct {
|
DeviceID string `json:"deviceId"`
|
||||||
ID int64 `json:"id"`
|
|
||||||
InternalID string `json:"internalId"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d DeviceRegister) EventName() string {
|
func (d Unreachable) EventDescription() string {
|
||||||
return fmt.Sprintf("DeviceRegister(id:%d, iid:%s)", d.ID, d.InternalID)
|
return fmt.Sprintf("Unreachable(id:%s)", d.DeviceID)
|
||||||
}
|
|
||||||
|
|
||||||
type DeviceDesiredStateChange struct {
|
|
||||||
ID int64 `json:"id"`
|
|
||||||
NewState device.State `json:"newState"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d DeviceDesiredStateChange) EventName() string {
|
|
||||||
return fmt.Sprintf("DeviceDesiredStateChange(id:%d, state:%s)",
|
|
||||||
d.ID, d.NewState,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
type DeviceAssignment struct {
|
|
||||||
IDs []int64 `json:"ids"`
|
|
||||||
Version int64 `json:"version"` // DeviceManager sets the version.
|
|
||||||
State *device.State `json:"state"` // DeviceManager sets the state in a follow-up event. Others still need to see it to kick the device.
|
|
||||||
Effect *int64 `json:"effect"` // An effect will pick this up. A scene may defer to own effects
|
|
||||||
Scene *int64 `json:"scene"` // A scene will take it and handle it. Might move it out a level so scenes are just filters that create assignments
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d DeviceAssignment) EventName() string {
|
|
||||||
s := "(!!no change!!)"
|
|
||||||
switch {
|
|
||||||
case d.State != nil:
|
|
||||||
s = fmt.Sprintf("state:%s", *d.State)
|
|
||||||
case d.Effect != nil:
|
|
||||||
s = fmt.Sprintf("effect:%d", *d.Effect)
|
|
||||||
case d.Scene != nil:
|
|
||||||
s = fmt.Sprintf("scene:%d", *d.Scene)
|
|
||||||
}
|
|
||||||
|
|
||||||
return fmt.Sprintf("DeviceAssignment(ids:%s, version:%d, %s)",
|
|
||||||
strings.Join(gentools.FmtSprintArray(d.IDs), ","),
|
|
||||||
d.Version,
|
|
||||||
s,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// DeviceRestore attempts to restore devices to a previous version. It may not be
|
|
||||||
// successful.
|
|
||||||
type DeviceRestore struct {
|
|
||||||
IDs []int64 `json:"ids"`
|
|
||||||
Version int64 `json:"version"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d DeviceRestore) EventName() string {
|
|
||||||
return fmt.Sprintf("DeviceRestore(ids:%s, version:%d)",
|
|
||||||
strings.Join(gentools.FmtSprintArray(d.IDs), ","),
|
|
||||||
d.Version,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,17 +0,0 @@
|
|||||||
package events
|
|
||||||
|
|
||||||
import "fmt"
|
|
||||||
|
|
||||||
type HubConnected struct{}
|
|
||||||
|
|
||||||
func (c HubConnected) EventName() string {
|
|
||||||
return "HubConnected"
|
|
||||||
}
|
|
||||||
|
|
||||||
type HubDisconnected struct {
|
|
||||||
Reason string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d HubDisconnected) EventName() string {
|
|
||||||
return fmt.Sprintf("HubDisconnected(reason:%s)", d.Reason)
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
package lucifer3
|
||||||
|
|
||||||
|
import (
|
||||||
|
"git.aiterp.net/lucifer3/server/device"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Effect interface {
|
||||||
|
State(index, round, random int) device.State
|
||||||
|
Frequency() time.Duration
|
||||||
|
EffectDescription() string
|
||||||
|
}
|
||||||
@@ -206,6 +206,15 @@ func (col *Color) colorful() colorful.Color {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func MustParse(raw string) Color {
|
||||||
|
col, err := Parse(raw)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return col
|
||||||
|
}
|
||||||
|
|
||||||
func Parse(raw string) (col Color, err error) {
|
func Parse(raw string) (col Color, err error) {
|
||||||
if raw == "" {
|
if raw == "" {
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -0,0 +1,51 @@
|
|||||||
|
package formattools
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
func CompactIDList(ids []string) []string {
|
||||||
|
sort.Strings(ids)
|
||||||
|
|
||||||
|
currentGroup := ""
|
||||||
|
currentIDs := make([]string, 0, len(ids))
|
||||||
|
groups := make([]string, 0, 2)
|
||||||
|
for _, id := range ids {
|
||||||
|
if id == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
split := strings.SplitN(id, ":", 3)
|
||||||
|
if len(split) < 3 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
group := id[:len(split[0])+len(split[1])+1]
|
||||||
|
|
||||||
|
if group != currentGroup {
|
||||||
|
if len(currentIDs) > 0 {
|
||||||
|
if len(currentIDs) == 1 {
|
||||||
|
groups = append(groups, fmt.Sprintf("%s:%s", currentGroup, currentIDs[0]))
|
||||||
|
} else {
|
||||||
|
groups = append(groups, fmt.Sprintf("%s:{%s}", currentGroup, strings.Join(currentIDs, ",")))
|
||||||
|
}
|
||||||
|
currentIDs = currentIDs[:0]
|
||||||
|
}
|
||||||
|
currentGroup = group
|
||||||
|
}
|
||||||
|
|
||||||
|
currentIDs = append(currentIDs, split[2])
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(currentIDs) > 0 {
|
||||||
|
if len(currentIDs) == 1 {
|
||||||
|
groups = append(groups, fmt.Sprintf("%s:%s", currentGroup, currentIDs[0]))
|
||||||
|
} else {
|
||||||
|
groups = append(groups, fmt.Sprintf("%s:{%s}", currentGroup, strings.Join(currentIDs, ",")))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return groups
|
||||||
|
}
|
||||||
@@ -1,6 +1,9 @@
|
|||||||
package gentools
|
package gentools
|
||||||
|
|
||||||
import "fmt"
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
func FmtSprintArray[T any](arr []T) []string {
|
func FmtSprintArray[T any](arr []T) []string {
|
||||||
res := make([]string, len(arr))
|
res := make([]string, len(arr))
|
||||||
@@ -10,3 +13,7 @@ func FmtSprintArray[T any](arr []T) []string {
|
|||||||
|
|
||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func FmtSprintArrayJoin[T any](arr []T, sep string) string {
|
||||||
|
return strings.Join(FmtSprintArray(arr), sep)
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,18 @@
|
|||||||
|
package gentools
|
||||||
|
|
||||||
|
import "strings"
|
||||||
|
|
||||||
|
func FlagString[T ~uint32](value T, flags []T, names []string) string {
|
||||||
|
if value == 0 {
|
||||||
|
return "(none)"
|
||||||
|
}
|
||||||
|
|
||||||
|
parts := make([]string, 0, len(flags))
|
||||||
|
for i, flag := range flags {
|
||||||
|
if value&flag == flag {
|
||||||
|
parts = append(parts, names[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return strings.Join(parts, "|")
|
||||||
|
}
|
||||||
+7
-68
@@ -1,40 +1,19 @@
|
|||||||
package lucifer3
|
package lucifer3
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Service interface {
|
type Service interface {
|
||||||
Active() bool
|
Active() bool
|
||||||
ServiceID() ServiceID
|
HandleEvent(bus *EventBus, event Event)
|
||||||
Handle(bus *EventBus, event Event, sender ServiceID)
|
|
||||||
}
|
|
||||||
|
|
||||||
type ServiceID struct {
|
|
||||||
Kind ServiceKind
|
|
||||||
ID int64
|
|
||||||
Name string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *ServiceID) String() string {
|
|
||||||
if s == nil {
|
|
||||||
return "BROADCAST"
|
|
||||||
}
|
|
||||||
if s.Kind == SKNotService {
|
|
||||||
return "EXTERNAL"
|
|
||||||
}
|
|
||||||
|
|
||||||
if s.Name != "" {
|
|
||||||
return s.Name
|
|
||||||
} else {
|
|
||||||
return fmt.Sprintf("%s[%d]", s.Kind.String(), s.ID)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ActiveService interface {
|
||||||
|
HandleCommand(bus *EventBus, command Command)
|
||||||
}
|
}
|
||||||
|
|
||||||
type ServiceCommon struct {
|
type ServiceCommon struct {
|
||||||
serviceID ServiceID
|
|
||||||
inactive uint32
|
inactive uint32
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -46,60 +25,20 @@ func (s *ServiceCommon) markInactive() {
|
|||||||
atomic.StoreUint32(&s.inactive, 1)
|
atomic.StoreUint32(&s.inactive, 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *ServiceCommon) ServiceID() ServiceID {
|
|
||||||
return s.serviceID
|
|
||||||
}
|
|
||||||
|
|
||||||
type ServiceKind int
|
|
||||||
|
|
||||||
func (s ServiceKind) String() string {
|
|
||||||
switch s {
|
|
||||||
case SKSingleton:
|
|
||||||
return "Singleton"
|
|
||||||
case SKStorage:
|
|
||||||
return "Storage"
|
|
||||||
case SKDeviceHub:
|
|
||||||
return "DeviceHub"
|
|
||||||
case SKEffect:
|
|
||||||
return "Effect"
|
|
||||||
case SKCallback:
|
|
||||||
return "Callback"
|
|
||||||
case SKNotService:
|
|
||||||
return "NotService"
|
|
||||||
default:
|
|
||||||
return "???"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const (
|
|
||||||
SKNotService ServiceKind = iota
|
|
||||||
SKSingleton
|
|
||||||
SKStorage
|
|
||||||
SKDeviceHub
|
|
||||||
SKEffect
|
|
||||||
SKCallback
|
|
||||||
)
|
|
||||||
|
|
||||||
type callbackService struct {
|
type callbackService struct {
|
||||||
ServiceCommon
|
ServiceCommon
|
||||||
cb func(event Event, sender ServiceID) bool
|
cb func(event Event) bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *callbackService) Handle(_ *EventBus, event Event, sender ServiceID) {
|
func (c *callbackService) HandleEvent(_ *EventBus, event Event) {
|
||||||
if !c.cb(event, sender) {
|
if !c.cb(event) {
|
||||||
c.markInactive()
|
c.markInactive()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var nextCallback int64 = 0
|
func newCallbackService(cb func(event Event) bool) Service {
|
||||||
|
|
||||||
func newCallbackService(cb func(event Event, sender ServiceID) bool) Service {
|
|
||||||
return &callbackService{
|
return &callbackService{
|
||||||
ServiceCommon: ServiceCommon{
|
ServiceCommon: ServiceCommon{
|
||||||
serviceID: ServiceID{
|
|
||||||
Kind: SKCallback,
|
|
||||||
ID: atomic.AddInt64(&nextCallback, 1),
|
|
||||||
},
|
|
||||||
inactive: 0,
|
inactive: 0,
|
||||||
},
|
},
|
||||||
cb: cb,
|
cb: cb,
|
||||||
|
|||||||
Reference in New Issue
Block a user