basic HTTP api and uistate.

This commit is contained in:
2023-01-01 16:30:40 +01:00
parent 865dd1994a
commit fe5bfc1efc
14 changed files with 583 additions and 21 deletions
+118
View File
@@ -0,0 +1,118 @@
package uistate
import (
"git.aiterp.net/lucifer3/server/device"
"git.aiterp.net/lucifer3/server/effects"
"git.aiterp.net/lucifer3/server/events"
"git.aiterp.net/lucifer3/server/internal/gentools"
"github.com/google/uuid"
)
type Data struct {
Devices map[string]Device `json:"devices"`
Assignments map[uuid.UUID]Assignment `json:"assignments"`
}
func (d *Data) WithPatch(patches ...Patch) Data {
newData := d.Copy()
for _, patch := range patches {
if patch.Device != nil {
pd := d.ensureDevice(patch.Device.ID)
gentools.ApplyUpdateSlice(&pd.Aliases, patch.Device.SetAliases)
gentools.ApplyUpdate(&pd.Name, patch.Device.Name)
gentools.ApplyUpdatePtr(&pd.HWState, patch.Device.HWState)
gentools.ApplyUpdatePtr(&pd.HWMetadata, patch.Device.HWMetadata)
gentools.ApplyUpdatePtr(&pd.DesiredState, patch.Device.DesiredState)
gentools.ApplyUpdatePtr(&pd.Assignment, patch.Device.Assignment)
if patch.Device.AddAlias != nil {
pd.Aliases = append(pd.Aliases[:0:0], pd.Aliases...)
pd.Aliases = append(pd.Aliases, *patch.Device.AddAlias)
}
if patch.Device.RemoveAlias != nil {
for i, alias := range pd.Aliases {
if alias == *patch.Device.RemoveAlias {
pd.Aliases = append(pd.Aliases[:0:0], pd.Aliases...)
pd.Aliases = append(pd.Aliases[:i], pd.Aliases[i+1:]...)
break
}
}
}
if patch.Device.ClearAssignment {
pd.Assignment = nil
}
if patch.Device.Delete {
delete(newData.Devices, pd.ID)
} else {
newData.Devices[pd.ID] = pd
}
}
if patch.Assignment != nil {
pa := d.ensureAssignment(patch.Assignment.ID)
gentools.ApplyUpdatePtr(&pa.Effect, patch.Assignment.Effect)
if patch.Assignment.AddDeviceID != nil {
pa.DeviceIDs = append(pa.DeviceIDs[:0:0], pa.DeviceIDs...)
pa.DeviceIDs = append(pa.DeviceIDs, *patch.Assignment.AddDeviceID)
}
if patch.Assignment.RemoveDeviceID != nil {
for i, id := range pa.DeviceIDs {
if id == *patch.Assignment.RemoveDeviceID {
pa.DeviceIDs = append(pa.DeviceIDs[:0:0], pa.DeviceIDs...)
pa.DeviceIDs[i] = ""
break
}
}
}
if patch.Assignment.Delete {
delete(newData.Assignments, pa.ID)
} else {
newData.Assignments[pa.ID] = pa
}
}
}
return newData
}
func (d *Data) Copy() Data {
return Data{
Devices: gentools.CopyMap(d.Devices),
Assignments: gentools.CopyMap(d.Assignments),
}
}
func (d *Data) ensureDevice(id string) Device {
if device, ok := d.Devices[id]; ok {
return device
} else {
return Device{ID: id}
}
}
func (d *Data) ensureAssignment(id uuid.UUID) Assignment {
if assignment, ok := d.Assignments[id]; ok {
return assignment
} else {
return Assignment{ID: id}
}
}
type Device struct {
ID string `json:"id"`
Name string `json:"name"`
HWMetadata *events.HardwareMetadata `json:"hwMetadata"`
HWState *events.HardwareState `json:"hwState"`
DesiredState *device.State `json:"desiredState"`
Aliases []string `json:"aliases"`
Assignment *uuid.UUID `json:"assignment"`
}
type Assignment struct {
ID uuid.UUID `json:"id"`
DeviceIDs []string `json:"deviceIds"`
Effect *effects.Serializable `json:"effect"`
}
+51
View File
@@ -0,0 +1,51 @@
package uistate
import (
"fmt"
"git.aiterp.net/lucifer3/server/device"
"git.aiterp.net/lucifer3/server/effects"
"git.aiterp.net/lucifer3/server/events"
"github.com/google/uuid"
)
type Patch struct {
Assignment *AssignmentPatch `json:"assignment,omitempty"`
Device *DevicePatch `json:"device,omitempty"`
}
func (e Patch) EventDescription() string {
if e.Device != nil {
switch {
case e.Device.DesiredState != nil:
return fmt.Sprintf("uistate.Patch(device=%s, desired state)", e.Device.ID)
default:
return fmt.Sprintf("uistate.Patch(device=%s)", e.Device.ID)
}
} else if e.Assignment != nil {
return fmt.Sprintf("uistate.Patch(assignment=%s)", e.Assignment.ID)
} else {
return "uistate.Patch"
}
}
type DevicePatch struct {
ID string `json:"id,omitempty"`
Name *string `json:"name,omitempty"`
HWMetadata *events.HardwareMetadata `json:"hwMetadata,omitempty"`
HWState *events.HardwareState `json:"hwState,omitempty"`
DesiredState *device.State `json:"desiredState,omitempty"`
SetAliases []string `json:"setAliases,omitempty"`
AddAlias *string `json:"addAlias,omitempty"`
RemoveAlias *string `json:"removeAlias,omitempty"`
Assignment *uuid.UUID `json:"assignment,omitempty"`
ClearAssignment bool `json:"clearAssignment,omitempty"`
Delete bool `json:"delete,omitempty"`
}
type AssignmentPatch struct {
ID uuid.UUID `json:"id"`
AddDeviceID *string `json:"addDeviceId"`
RemoveDeviceID *string `json:"removeDeviceId"`
Effect *effects.Serializable `json:"effect"`
Delete bool `json:"delete"`
}
+105
View File
@@ -0,0 +1,105 @@
package uistate
import (
lucifer3 "git.aiterp.net/lucifer3/server"
"git.aiterp.net/lucifer3/server/commands"
"git.aiterp.net/lucifer3/server/effects"
"git.aiterp.net/lucifer3/server/events"
"git.aiterp.net/lucifer3/server/internal/gentools"
"sync"
)
func NewService() lucifer3.ActiveService {
return &service{}
}
type service struct {
mu sync.Mutex
data Data
listener []chan Patch
}
func (s *service) Active() bool {
return true
}
func (s *service) HandleCommand(bus *lucifer3.EventBus, command lucifer3.Command) {
var patches []Patch
switch command := command.(type) {
case commands.SetState:
patches = []Patch{{Device: &DevicePatch{ID: command.ID, DesiredState: &command.State}}}
case commands.SetStateBatch:
for id, state := range command {
patches = []Patch{{Device: &DevicePatch{ID: id, DesiredState: gentools.ShallowCopy(&state)}}}
}
}
if len(patches) > 0 {
s.mu.Lock()
s.data = s.data.WithPatch(patches...)
s.mu.Unlock()
for _, patch := range patches {
bus.RunEvent(patch)
}
}
}
func (s *service) HandleEvent(bus *lucifer3.EventBus, event lucifer3.Event) {
var patches []Patch
switch event := event.(type) {
case events.AliasAdded:
patches = []Patch{{Device: &DevicePatch{ID: event.ID, AddAlias: &event.Alias}}}
case events.AliasRemoved:
patches = []Patch{{Device: &DevicePatch{ID: event.ID, RemoveAlias: &event.Alias}}}
case events.HardwareState:
patches = []Patch{{Device: &DevicePatch{ID: event.ID, HWState: &event}}}
case events.HardwareMetadata:
patches = []Patch{{Device: &DevicePatch{ID: event.ID, HWMetadata: &event}}}
case events.AssignmentCreated:
patches = []Patch{{Assignment: &AssignmentPatch{
ID: event.ID,
Effect: &effects.Serializable{Effect: event.Effect},
}}}
case events.AssignmentRemoved:
patches = []Patch{{Assignment: &AssignmentPatch{
ID: event.ID,
Delete: true,
}}}
case events.DeviceAssigned:
// Un-assign from current assignment (if any)
if d, ok := s.data.Devices[event.DeviceID]; ok && d.Assignment != nil {
patches = append(patches, Patch{Assignment: &AssignmentPatch{
ID: *d.Assignment,
RemoveDeviceID: &d.ID,
}})
}
// Assign to current assignment (if it's not cleared)
if event.AssignmentID != nil {
patches = append(patches, Patch{Assignment: &AssignmentPatch{
ID: *event.AssignmentID,
AddDeviceID: &event.DeviceID,
}})
}
// Always set the assignment
patches = append(patches, Patch{Device: &DevicePatch{
ID: event.DeviceID,
Assignment: gentools.ShallowCopy(event.AssignmentID),
ClearAssignment: event.AssignmentID == nil,
}})
}
if len(patches) > 0 {
s.mu.Lock()
s.data = s.data.WithPatch(patches...)
s.mu.Unlock()
for _, patch := range patches {
bus.RunEvent(patch)
}
}
}