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.

245 lines
5.3 KiB

  1. package models
  2. import (
  3. "context"
  4. "math"
  5. "math/rand"
  6. "sort"
  7. "strings"
  8. "time"
  9. )
  10. type Scene struct {
  11. ID int `json:"id"`
  12. Name string `json:"name"`
  13. IntervalMS int64 `json:"interval"`
  14. Roles []SceneRole `json:"roles"`
  15. }
  16. func (s *Scene) Validate() error {
  17. if s.IntervalMS < 0 {
  18. return ErrSceneInvalidInterval
  19. }
  20. if len(s.Roles) == 0 {
  21. return ErrSceneNoRoles
  22. }
  23. for _, role := range s.Roles {
  24. err := role.Validate()
  25. if err != nil {
  26. return err
  27. }
  28. }
  29. return nil
  30. }
  31. func (s *Scene) Role(device *Device) *SceneRole {
  32. index := s.RoleIndex(device)
  33. if index == -1 {
  34. return nil
  35. }
  36. return &s.Roles[index]
  37. }
  38. func (s *Scene) RoleIndex(device *Device) int {
  39. for i, role := range s.Roles {
  40. if role.TargetKind.Matches(device, role.TargetValue) {
  41. return i
  42. }
  43. }
  44. return -1
  45. }
  46. type SceneEffect string
  47. const (
  48. SEStatic SceneEffect = "Plain"
  49. SERandom SceneEffect = "Random"
  50. SEGradient SceneEffect = "Gradient"
  51. SEWalkingGradient SceneEffect = "WalkingGradient"
  52. SETransition SceneEffect = "Transition"
  53. )
  54. type SceneRole struct {
  55. Effect SceneEffect `json:"effect"`
  56. PowerMode ScenePowerMode `json:"powerMode"`
  57. TargetKind ReferenceKind `json:"targetKind"`
  58. TargetValue string `json:"targetValue"`
  59. Interpolate bool `json:"interpolate"`
  60. Order string `json:"order"`
  61. States []NewDeviceState `json:"states"`
  62. }
  63. type SceneRunContext struct {
  64. Index int
  65. Length int
  66. IntervalNumber int64
  67. IntervalMax int64
  68. }
  69. func (d *SceneRunContext) PositionFacShifted() float64 {
  70. shiftFac := float64((int64(d.Index)+d.IntervalNumber)%int64(d.Length)) * (1 / float64(d.Length))
  71. return math.Mod(d.PositionFac()+shiftFac, 1)
  72. }
  73. func (d *SceneRunContext) PositionFac() float64 {
  74. return float64(d.Index) / float64(d.Length)
  75. }
  76. func (d *SceneRunContext) IntervalFac() float64 {
  77. fac := float64(d.IntervalNumber) / float64(d.IntervalMax)
  78. if fac > 1 {
  79. return 1
  80. } else if fac < 0 {
  81. return 0
  82. } else {
  83. return fac
  84. }
  85. }
  86. func (r *SceneRole) Validate() error {
  87. if len(r.States) == 0 {
  88. return ErrSceneRoleNoStates
  89. }
  90. switch r.TargetKind {
  91. case RKTag, RKBridgeID, RKDeviceID, RKName, RKAll:
  92. default:
  93. return ErrBadInput
  94. }
  95. switch r.PowerMode {
  96. case SPScene, SPDevice, SPOverride:
  97. default:
  98. return ErrSceneRoleUnknownPowerMode
  99. }
  100. switch r.Effect {
  101. case SEStatic, SERandom, SEGradient, SETransition:
  102. default:
  103. return ErrSceneRoleUnknownEffect
  104. }
  105. switch r.Order {
  106. case "", "-name", "name", "+name", "-id", "id", "+id":
  107. default:
  108. return ErrSceneRoleUnsupportedOrdering
  109. }
  110. return nil
  111. }
  112. func (r *SceneRole) ApplyOrder(devices []Device) {
  113. if r.Order == "" {
  114. return
  115. }
  116. desc := strings.HasPrefix(r.Order, "-")
  117. orderKey := r.Order
  118. if desc || strings.HasPrefix(r.Order, "+") {
  119. orderKey = orderKey[1:]
  120. }
  121. switch orderKey {
  122. case "id":
  123. sort.Slice(devices, func(i, j int) bool {
  124. if desc {
  125. return devices[i].ID > devices[j].ID
  126. }
  127. return devices[i].ID < devices[j].ID
  128. })
  129. case "name":
  130. sort.Slice(devices, func(i, j int) bool {
  131. if desc {
  132. return strings.Compare(devices[i].Name, devices[j].Name) < 0
  133. }
  134. return strings.Compare(devices[i].Name, devices[j].Name) > 0
  135. })
  136. }
  137. }
  138. func (r *SceneRole) ApplyEffect(device *Device, c SceneRunContext) (newState NewDeviceState, deviceChanged bool) {
  139. switch r.Effect {
  140. case SEStatic:
  141. newState = r.States[0]
  142. case SERandom:
  143. newState = r.State(rand.Float64())
  144. case SEGradient:
  145. newState = r.State(c.PositionFac())
  146. case SEWalkingGradient:
  147. newState = r.State(c.PositionFacShifted())
  148. case SETransition:
  149. newState = r.State(c.IntervalFac())
  150. }
  151. switch r.PowerMode {
  152. case SPDevice:
  153. newState.Power = nil
  154. case SPScene:
  155. // Do nothing
  156. case SPOverride:
  157. if newState.Power != nil && device.State.Power != *newState.Power {
  158. device.State.Power = *newState.Power
  159. deviceChanged = true
  160. }
  161. }
  162. return
  163. }
  164. func (r *SceneRole) State(fac float64) NewDeviceState {
  165. if len(r.States) == 0 {
  166. return NewDeviceState{}
  167. } else if len(r.States) == 1 {
  168. return r.States[0]
  169. }
  170. if r.Interpolate {
  171. if len(r.States) == 2 {
  172. return r.States[0].Interpolate(r.States[1], fac)
  173. } else {
  174. pos := fac * float64(len(r.States)-1)
  175. start := math.Floor(pos)
  176. localFac := pos - start
  177. return r.States[int(start)].Interpolate(r.States[int(start)+1], localFac)
  178. }
  179. } else {
  180. index := int(fac * float64(len(r.States)))
  181. if index == len(r.States) {
  182. index = len(r.States) - 1
  183. }
  184. return r.States[index]
  185. }
  186. }
  187. type ScenePowerMode string
  188. const (
  189. SPDevice ScenePowerMode = "Device" // Device state decides power. Scene state may only power off.
  190. SPScene ScenePowerMode = "Scene" // Scene state decide power.
  191. SPOverride ScenePowerMode = "Override" // Same as above, but Scene state set Device state' power. This is good for "wake up" scenes.
  192. )
  193. // DeviceSceneAssignment is an entry on the device stack.
  194. type DeviceSceneAssignment struct {
  195. SceneID int `json:"sceneId"`
  196. Group string `json:"group"`
  197. StartTime time.Time `json:"start"`
  198. DurationMS int64 `json:"durationMs"`
  199. }
  200. type SceneRepository interface {
  201. Find(ctx context.Context, id int) (*Scene, error)
  202. FetchAll(ctx context.Context) ([]Scene, error)
  203. Save(ctx context.Context, bridge *Scene) error
  204. Delete(ctx context.Context, bridge *Scene) error
  205. }