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