Gisle Aune
3 years ago
7 changed files with 447 additions and 2 deletions
-
7app/api/util.go
-
36app/services/scene/manager.go
-
87app/services/scene/scene.go
-
49models/device.go
-
7models/errors.go
-
204models/scene.go
-
55models/shared.go
@ -0,0 +1,36 @@ |
|||||
|
package scene |
||||
|
|
||||
|
import ( |
||||
|
"git.aiterp.net/lucifer/new-server/models" |
||||
|
"sync" |
||||
|
) |
||||
|
|
||||
|
type Manager struct { |
||||
|
mu sync.Mutex |
||||
|
assignments map[int]*Scene |
||||
|
scenes []*Scene |
||||
|
} |
||||
|
|
||||
|
func (mgr *Manager) UpdateDevice(device *models.Device) { |
||||
|
|
||||
|
} |
||||
|
|
||||
|
func (mgr *Manager) GetUnassigned(devices []models.Device) []models.Device { |
||||
|
mgr.mu.Lock() |
||||
|
defer mgr.mu.Unlock() |
||||
|
|
||||
|
res := make([]models.Device, 0, len(devices)) |
||||
|
for _, device := range devices { |
||||
|
if _, ok := mgr.assignments[device.ID]; !ok { |
||||
|
res = append(res, device) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return res |
||||
|
} |
||||
|
|
||||
|
var globalManager = &Manager{} |
||||
|
|
||||
|
func Get() *Manager { |
||||
|
return globalManager |
||||
|
} |
@ -0,0 +1,87 @@ |
|||||
|
package scene |
||||
|
|
||||
|
import ( |
||||
|
"git.aiterp.net/lucifer/new-server/models" |
||||
|
"time" |
||||
|
) |
||||
|
|
||||
|
type Scene struct { |
||||
|
startTime time.Time |
||||
|
duration time.Duration |
||||
|
tag string |
||||
|
data models.Scene |
||||
|
roleList [][]models.Device |
||||
|
deviceMap map[int]*models.Device |
||||
|
roleMap map[int]int |
||||
|
changes map[int]models.NewDeviceState |
||||
|
} |
||||
|
|
||||
|
func (s *Scene) UpdateScene(scene models.Scene) { |
||||
|
s.data = scene |
||||
|
s.roleMap = make(map[int]int) |
||||
|
|
||||
|
for _, device := range s.deviceMap { |
||||
|
s.moveDevice(*device) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
func (s *Scene) UpsertDevice(device models.Device) { |
||||
|
s.deviceMap[device.ID] = &device |
||||
|
s.moveDevice(device) |
||||
|
} |
||||
|
|
||||
|
func (s *Scene) RemoveDevice(device models.Device) { |
||||
|
delete(s.deviceMap, device.ID) |
||||
|
delete(s.roleMap, device.ID) |
||||
|
} |
||||
|
|
||||
|
func (s *Scene) runRole(index int) { |
||||
|
|
||||
|
} |
||||
|
|
||||
|
func (s *Scene) moveDevice(device models.Device, run bool) { |
||||
|
oldRole, hasOldRole := s.roleMap[device.ID] |
||||
|
newRole := s.data.RoleIndex(&device) |
||||
|
|
||||
|
if hasOldRole && oldRole == newRole { |
||||
|
return |
||||
|
} |
||||
|
|
||||
|
if hasOldRole { |
||||
|
for i, device2 := range s.roleList[oldRole] { |
||||
|
if device2.ID == device.ID { |
||||
|
s.roleList[oldRole] = append(s.roleList[oldRole][:i], s.roleList[oldRole][i+1:]...) |
||||
|
break |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
s.runRole(oldRole) |
||||
|
} |
||||
|
|
||||
|
s.roleMap[device.ID] = newRole |
||||
|
s.runRole(newRole) |
||||
|
} |
||||
|
|
||||
|
func (s *Scene) intervalNumber() int64 { |
||||
|
intervalDuration := time.Duration(s.data.IntervalMS) * time.Millisecond |
||||
|
|
||||
|
return int64(time.Since(s.startTime) / intervalDuration) |
||||
|
} |
||||
|
|
||||
|
func (s *Scene) intervalFac() float64 { |
||||
|
return clamp(float64(time.Since(s.startTime)) / float64(s.duration)) |
||||
|
} |
||||
|
|
||||
|
func (s *Scene) positionFac(i, len int) float64 { |
||||
|
return clamp(float64(i) / float64(len)) |
||||
|
} |
||||
|
|
||||
|
func clamp(fac float64) float64 { |
||||
|
if fac > 1.00 { |
||||
|
return 1.00 |
||||
|
} else if fac < 0.00 { |
||||
|
return 0.00 |
||||
|
} else { |
||||
|
return fac |
||||
|
} |
||||
|
} |
@ -0,0 +1,204 @@ |
|||||
|
package models |
||||
|
|
||||
|
import ( |
||||
|
"math" |
||||
|
"math/rand" |
||||
|
"sort" |
||||
|
"strings" |
||||
|
) |
||||
|
|
||||
|
type Scene struct { |
||||
|
ID int `json:"id"` |
||||
|
Name string `json:"name"` |
||||
|
IntervalMS int64 `json:"interval"` |
||||
|
Roles []SceneRole `json:"roles"` |
||||
|
} |
||||
|
|
||||
|
func (s *Scene) Validate() error { |
||||
|
if s.IntervalMS < 0 { |
||||
|
return ErrSceneInvalidInterval |
||||
|
} |
||||
|
if len(s.Roles) == 0 { |
||||
|
return ErrSceneNoRoles |
||||
|
} |
||||
|
|
||||
|
for _, role := range s.Roles { |
||||
|
err := role.Validate() |
||||
|
if err != nil { |
||||
|
return err |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return nil |
||||
|
} |
||||
|
|
||||
|
func (s *Scene) Role(device *Device) *SceneRole { |
||||
|
index := s.RoleIndex(device) |
||||
|
if index == -1 { |
||||
|
return nil |
||||
|
} |
||||
|
|
||||
|
return &s.Roles[index] |
||||
|
} |
||||
|
|
||||
|
func (s *Scene) RoleIndex(device *Device) int { |
||||
|
for i, role := range s.Roles { |
||||
|
if role.TargetKind.Matches(device, role.TargetValue) { |
||||
|
return i |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return -1 |
||||
|
} |
||||
|
|
||||
|
type SceneEffect string |
||||
|
|
||||
|
const ( |
||||
|
SEStatic SceneEffect = "Plain" |
||||
|
SERandom SceneEffect = "Random" |
||||
|
SEGradient SceneEffect = "Gradient" |
||||
|
SETransition SceneEffect = "Transition" |
||||
|
) |
||||
|
|
||||
|
type SceneRole struct { |
||||
|
Effect SceneEffect `json:"effect"` |
||||
|
PowerMode ScenePowerMode `json:"overridePower"` |
||||
|
TargetKind ReferenceKind `json:"targetKind"` |
||||
|
TargetValue string `json:"targetValue"` |
||||
|
Interpolate bool `json:"interpolate"` |
||||
|
Order string `json:"order"` |
||||
|
States []NewDeviceState `json:"states"` |
||||
|
} |
||||
|
|
||||
|
func (r *SceneRole) Validate() error { |
||||
|
if len(r.States) == 0 { |
||||
|
return ErrSceneRoleNoStates |
||||
|
} |
||||
|
|
||||
|
switch r.TargetKind { |
||||
|
case RKTag, RKBridgeID, RKDeviceID, RKName, RKAll: |
||||
|
default: |
||||
|
return ErrBadInput |
||||
|
} |
||||
|
|
||||
|
switch r.PowerMode { |
||||
|
case SPScene, SPDevice, SPOverride: |
||||
|
default: |
||||
|
return ErrSceneRoleUnknownPowerMode |
||||
|
} |
||||
|
|
||||
|
switch r.Effect { |
||||
|
case SEStatic, SERandom, SEGradient, SETransition: |
||||
|
default: |
||||
|
return ErrSceneRoleUnknownEffect |
||||
|
} |
||||
|
|
||||
|
switch r.Order { |
||||
|
case "", "-name", "name", "+name", "-id", "id", "+id": |
||||
|
default: |
||||
|
return ErrSceneRoleUnsupportedOrdering |
||||
|
} |
||||
|
|
||||
|
return nil |
||||
|
} |
||||
|
|
||||
|
func (r *SceneRole) ApplyOrder(devices []Device) { |
||||
|
if r.Order == "" { |
||||
|
return |
||||
|
} |
||||
|
|
||||
|
desc := strings.HasPrefix(r.Order, "-") |
||||
|
|
||||
|
orderKey := r.Order |
||||
|
if desc || strings.HasPrefix(r.Order, "+") { |
||||
|
orderKey = orderKey[1:] |
||||
|
} |
||||
|
|
||||
|
switch orderKey { |
||||
|
case "id": |
||||
|
sort.Slice(devices, func(i, j int) bool { |
||||
|
if desc { |
||||
|
return devices[i].ID > devices[j].ID |
||||
|
} |
||||
|
|
||||
|
return devices[i].ID < devices[j].ID |
||||
|
}) |
||||
|
case "name": |
||||
|
sort.Slice(devices, func(i, j int) bool { |
||||
|
if desc { |
||||
|
return strings.Compare(devices[i].Name, devices[j].Name) < 0 |
||||
|
} |
||||
|
|
||||
|
return strings.Compare(devices[i].Name, devices[j].Name) > 0 |
||||
|
}) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
func (r *SceneRole) ApplyEffect(device *Device, intervalFac float64, positionFac float64) (newState NewDeviceState, deviceChanged bool) { |
||||
|
switch r.Effect { |
||||
|
case SEStatic: |
||||
|
newState = r.States[0] |
||||
|
case SERandom: |
||||
|
newState = r.State(rand.Float64()) |
||||
|
case SEGradient: |
||||
|
newState = r.State(positionFac) |
||||
|
case SETransition: |
||||
|
newState = r.State(intervalFac) |
||||
|
} |
||||
|
|
||||
|
switch r.PowerMode { |
||||
|
case SPDevice: |
||||
|
newState.Power = nil |
||||
|
case SPScene: |
||||
|
// Do nothing
|
||||
|
case SPOverride: |
||||
|
if newState.Power != nil && device.State.Power != *newState.Power { |
||||
|
device.State.Power = *newState.Power |
||||
|
deviceChanged = true |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return |
||||
|
} |
||||
|
|
||||
|
func (r *SceneRole) State(fac float64) NewDeviceState { |
||||
|
if len(r.States) == 0 { |
||||
|
return NewDeviceState{} |
||||
|
} else if len(r.States) == 1 { |
||||
|
return r.States[0] |
||||
|
} |
||||
|
|
||||
|
if r.Interpolate { |
||||
|
if len(r.States) == 2 { |
||||
|
return r.States[0].Interpolate(r.States[1], fac) |
||||
|
} else { |
||||
|
pos := fac * float64(len(r.States)-1) |
||||
|
start := math.Floor(pos) |
||||
|
localFac := pos - start |
||||
|
|
||||
|
return r.States[int(start)].Interpolate(r.States[int(start)+1], localFac) |
||||
|
} |
||||
|
} else { |
||||
|
index := int(fac * float64(len(r.States))) |
||||
|
if index == len(r.States) { |
||||
|
index = len(r.States) - 1 |
||||
|
} |
||||
|
|
||||
|
return r.States[index] |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
type ScenePowerMode string |
||||
|
|
||||
|
const ( |
||||
|
SPDevice ScenePowerMode = "Device" // Device state decides power. Scene state may only power off.
|
||||
|
SPScene ScenePowerMode = "Scene" // Scene state decide power.
|
||||
|
SPOverride ScenePowerMode = "Override" // Same as above, but Scene state set Device state' power. This is good for "wake up" scenes.
|
||||
|
) |
||||
|
|
||||
|
type SceneAssignment struct { |
||||
|
Scene *Scene |
||||
|
Device *Device |
||||
|
Tag string |
||||
|
} |
||||
|
|
Write
Preview
Loading…
Cancel
Save
Reference in new issue