|
|
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 }
|