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 }