You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
204 lines
4.1 KiB
204 lines
4.1 KiB
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
|
|
}
|
|
|