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.

251 lines
5.4 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 = "Static"
  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. Relative bool `json:"relative"`
  61. Order string `json:"order"`
  62. States []NewDeviceState `json:"states"`
  63. }
  64. type SceneRunContext struct {
  65. Index int
  66. Length int
  67. IntervalNumber int64
  68. IntervalMax int64
  69. }
  70. func (d *SceneRunContext) PositionFacShifted() float64 {
  71. shiftFac := float64(d.IntervalNumber%int64(d.Length)) / float64(d.Length)
  72. return math.Mod(d.PositionFac()+shiftFac, 1)
  73. }
  74. func (d *SceneRunContext) PositionFac() float64 {
  75. return float64(d.Index) / float64(d.Length)
  76. }
  77. func (d *SceneRunContext) IntervalFac() float64 {
  78. fac := float64(d.IntervalNumber) / float64(d.IntervalMax)
  79. if fac > 1 {
  80. return 1
  81. } else if fac < 0 {
  82. return 0
  83. } else {
  84. return fac
  85. }
  86. }
  87. func (r *SceneRole) Validate() error {
  88. if len(r.States) == 0 {
  89. return ErrSceneRoleNoStates
  90. }
  91. switch r.TargetKind {
  92. case RKTag, RKBridgeID, RKDeviceID, RKName, RKAll:
  93. default:
  94. return ErrBadInput
  95. }
  96. switch r.PowerMode {
  97. case SPScene, SPDevice, SPBoth:
  98. default:
  99. return ErrSceneRoleUnknownPowerMode
  100. }
  101. switch r.Effect {
  102. case SEStatic, SERandom, SEGradient, SEWalkingGradient, SETransition:
  103. default:
  104. return ErrSceneRoleUnknownEffect
  105. }
  106. switch r.Order {
  107. case "", "-name", "name", "+name", "-id", "id", "+id":
  108. default:
  109. return ErrSceneRoleUnsupportedOrdering
  110. }
  111. return nil
  112. }
  113. func (r *SceneRole) ApplyOrder(devices []Device) {
  114. if r.Order == "" {
  115. return
  116. }
  117. desc := strings.HasPrefix(r.Order, "-")
  118. orderKey := r.Order
  119. if desc || strings.HasPrefix(r.Order, "+") {
  120. orderKey = orderKey[1:]
  121. }
  122. switch orderKey {
  123. case "id":
  124. sort.Slice(devices, func(i, j int) bool {
  125. if desc {
  126. return devices[i].ID > devices[j].ID
  127. }
  128. return devices[i].ID < devices[j].ID
  129. })
  130. case "name":
  131. sort.Slice(devices, func(i, j int) bool {
  132. if desc {
  133. return strings.Compare(devices[i].Name, devices[j].Name) < 0
  134. }
  135. return strings.Compare(devices[i].Name, devices[j].Name) > 0
  136. })
  137. }
  138. }
  139. func (r *SceneRole) ApplyEffect(device *Device, c SceneRunContext) (newState NewDeviceState) {
  140. switch r.Effect {
  141. case SEStatic:
  142. newState = r.States[0]
  143. case SERandom:
  144. newState = r.State(rand.Float64())
  145. case SEGradient:
  146. newState = r.State(c.PositionFac())
  147. case SEWalkingGradient:
  148. newState = r.State(c.PositionFacShifted())
  149. case SETransition:
  150. newState = r.State(c.IntervalFac())
  151. }
  152. if r.Relative {
  153. newState = newState.RelativeTo(*device)
  154. }
  155. switch r.PowerMode {
  156. case SPDevice:
  157. newState.Power = nil
  158. case SPScene:
  159. // Do nothing
  160. case SPBoth:
  161. if newState.Power != nil {
  162. powerIntersection := *newState.Power && device.State.Power
  163. newState.Power = &powerIntersection
  164. }
  165. }
  166. return
  167. }
  168. func (r *SceneRole) State(fac float64) NewDeviceState {
  169. if len(r.States) == 0 {
  170. return NewDeviceState{}
  171. } else if len(r.States) == 1 {
  172. return r.States[0]
  173. }
  174. if r.Interpolate {
  175. if len(r.States) == 2 {
  176. return r.States[0].Interpolate(r.States[1], fac)
  177. } else {
  178. pos := fac * float64(len(r.States)-1)
  179. start := math.Floor(pos)
  180. localFac := pos - start
  181. return r.States[int(start)].Interpolate(r.States[int(start)+1], localFac)
  182. }
  183. } else {
  184. index := int(fac * float64(len(r.States)))
  185. if index == len(r.States) {
  186. index = len(r.States) - 1
  187. }
  188. return r.States[index]
  189. }
  190. }
  191. type ScenePowerMode string
  192. const (
  193. SPDevice ScenePowerMode = "Device" // Device state decides power. Scene state may only power off.
  194. SPScene ScenePowerMode = "Scene" // Scene state decide power.
  195. SPBoth ScenePowerMode = "Both" // Power is only on if both Device and Scene says it is.
  196. )
  197. // DeviceSceneAssignment is an entry on the device stack. StartTime and DurationMS are only respected when the scene
  198. // instance is created for the group.
  199. type DeviceSceneAssignment struct {
  200. SceneID int `json:"sceneId"`
  201. Group string `json:"group"`
  202. StartTime time.Time `json:"start"`
  203. DurationMS int64 `json:"durationMs"`
  204. }
  205. type SceneRepository interface {
  206. Find(ctx context.Context, id int) (*Scene, error)
  207. FetchAll(ctx context.Context) ([]Scene, error)
  208. Save(ctx context.Context, bridge *Scene) error
  209. Delete(ctx context.Context, bridge *Scene) error
  210. }