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.

312 lines
6.7 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. SEMotion SceneEffect = "Motion"
  54. SETemperature SceneEffect = "Temperature"
  55. )
  56. type SceneRole struct {
  57. Effect SceneEffect `json:"effect"`
  58. MotionSeconds float64 `json:"motionSeconds"`
  59. MinTemperature int `json:"minTemperature"`
  60. MaxTemperature int `json:"maxTemperature"`
  61. PowerMode ScenePowerMode `json:"powerMode"`
  62. TargetKind ReferenceKind `json:"targetKind"`
  63. TargetValue string `json:"targetValue"`
  64. Interpolate bool `json:"interpolate"`
  65. Relative bool `json:"relative"`
  66. Order string `json:"order"`
  67. States []NewDeviceState `json:"states"`
  68. }
  69. type SceneRunContext struct {
  70. CurrentTime time.Time
  71. Index int
  72. Length int
  73. IntervalNumber int64
  74. IntervalMax int64
  75. Sensors []SceneSensor
  76. }
  77. type SceneSensor struct {
  78. ID int
  79. UpdateTime time.Time
  80. Temperature *float64
  81. Presence *bool
  82. }
  83. func (d *SceneRunContext) PositionFacShifted() float64 {
  84. shiftFac := float64(d.IntervalNumber%int64(d.Length)) / float64(d.Length)
  85. return math.Mod(d.PositionFac()+shiftFac, 1)
  86. }
  87. func (d *SceneRunContext) PositionFac() float64 {
  88. return float64(d.Index) / float64(d.Length)
  89. }
  90. func (d *SceneRunContext) IntervalFac() float64 {
  91. fac := float64(d.IntervalNumber) / float64(d.IntervalMax)
  92. if fac > 1 {
  93. return 1
  94. } else if fac < 0 {
  95. return 0
  96. } else {
  97. return fac
  98. }
  99. }
  100. func (r *SceneRole) Validate() error {
  101. if len(r.States) == 0 {
  102. return ErrSceneRoleNoStates
  103. }
  104. switch r.TargetKind {
  105. case RKTag, RKBridgeID, RKDeviceID, RKName, RKAll:
  106. default:
  107. return ErrBadInput
  108. }
  109. switch r.PowerMode {
  110. case SPScene, SPDevice, SPBoth:
  111. default:
  112. return ErrSceneRoleUnknownPowerMode
  113. }
  114. switch r.Effect {
  115. case SEStatic, SERandom, SEGradient, SEWalkingGradient, SETransition, SEMotion, SETemperature:
  116. default:
  117. return ErrSceneRoleUnknownEffect
  118. }
  119. switch r.Order {
  120. case "", "-name", "name", "+name", "-id", "id", "+id":
  121. default:
  122. return ErrSceneRoleUnsupportedOrdering
  123. }
  124. return nil
  125. }
  126. func (r *SceneRole) ApplyOrder(devices []Device) {
  127. if r.Order == "" {
  128. return
  129. }
  130. desc := strings.HasPrefix(r.Order, "-")
  131. orderKey := r.Order
  132. if desc || strings.HasPrefix(r.Order, "+") {
  133. orderKey = orderKey[1:]
  134. }
  135. switch orderKey {
  136. case "id":
  137. sort.Slice(devices, func(i, j int) bool {
  138. if desc {
  139. return devices[i].ID > devices[j].ID
  140. }
  141. return devices[i].ID < devices[j].ID
  142. })
  143. case "name":
  144. sort.Slice(devices, func(i, j int) bool {
  145. if desc {
  146. return strings.Compare(devices[i].Name, devices[j].Name) < 0
  147. }
  148. return strings.Compare(devices[i].Name, devices[j].Name) > 0
  149. })
  150. }
  151. }
  152. func (r *SceneRole) ApplyEffect(device *Device, c SceneRunContext) (newState NewDeviceState) {
  153. switch r.Effect {
  154. case SEStatic:
  155. newState = r.States[0]
  156. case SERandom:
  157. newState = r.State(rand.Float64())
  158. case SEGradient:
  159. newState = r.State(c.PositionFac())
  160. case SEWalkingGradient:
  161. newState = r.State(c.PositionFacShifted())
  162. case SETransition:
  163. newState = r.State(c.IntervalFac())
  164. case SEMotion:
  165. presence := false
  166. absenceTime := time.Time{}
  167. for _, sensors := range c.Sensors {
  168. if sensors.Presence == nil {
  169. continue
  170. }
  171. if *sensors.Presence {
  172. presence = true
  173. } else {
  174. if sensors.UpdateTime.After(absenceTime) {
  175. absenceTime = sensors.UpdateTime
  176. }
  177. }
  178. }
  179. if presence {
  180. newState = r.State(0.0)
  181. } else {
  182. fac := c.CurrentTime.Sub(absenceTime).Seconds() / r.MotionSeconds
  183. if fac < 0 {
  184. fac = 0
  185. } else if fac > 1 {
  186. fac = 1
  187. }
  188. newState = r.State(fac)
  189. }
  190. case SETemperature:
  191. avg := 0.0
  192. count := 0
  193. for _, sensor := range c.Sensors {
  194. if sensor.Temperature == nil {
  195. continue
  196. }
  197. if count == 0 {
  198. avg = *sensor.Temperature
  199. count = 1
  200. } else {
  201. avg = ((avg * float64(count)) + *sensor.Temperature) / float64(count+1)
  202. count += 1
  203. }
  204. }
  205. }
  206. if r.Relative {
  207. newState = newState.RelativeTo(*device)
  208. }
  209. switch r.PowerMode {
  210. case SPDevice:
  211. newState.Power = nil
  212. case SPScene:
  213. // Do nothing
  214. case SPBoth:
  215. if newState.Power != nil {
  216. powerIntersection := *newState.Power && device.State.Power
  217. newState.Power = &powerIntersection
  218. }
  219. }
  220. return
  221. }
  222. func (r *SceneRole) State(fac float64) NewDeviceState {
  223. if len(r.States) == 0 {
  224. return NewDeviceState{}
  225. } else if len(r.States) == 1 {
  226. return r.States[0]
  227. }
  228. if r.Interpolate {
  229. if len(r.States) == 2 {
  230. return r.States[0].Interpolate(r.States[1], fac)
  231. } else {
  232. pos := fac * float64(len(r.States)-1)
  233. start := math.Floor(pos)
  234. localFac := pos - start
  235. return r.States[int(start)].Interpolate(r.States[int(start)+1], localFac)
  236. }
  237. } else {
  238. index := int(fac * float64(len(r.States)))
  239. if index == len(r.States) {
  240. index = len(r.States) - 1
  241. }
  242. return r.States[index]
  243. }
  244. }
  245. type ScenePowerMode string
  246. const (
  247. SPDevice ScenePowerMode = "Device" // Device state decides power. Scene state may only power off.
  248. SPScene ScenePowerMode = "Scene" // Scene state decide power.
  249. SPBoth ScenePowerMode = "Both" // Power is only on if both Device and Scene says it is.
  250. )
  251. // DeviceSceneAssignment is an entry on the device stack. StartTime and DurationMS are only respected when the scene
  252. // instance is created for the group.
  253. type DeviceSceneAssignment struct {
  254. SceneID int `json:"sceneId"`
  255. Group string `json:"group"`
  256. StartTime time.Time `json:"start"`
  257. DurationMS int64 `json:"durationMs"`
  258. }
  259. type SceneRepository interface {
  260. Find(ctx context.Context, id int) (*Scene, error)
  261. FetchAll(ctx context.Context) ([]Scene, error)
  262. Save(ctx context.Context, bridge *Scene) error
  263. Delete(ctx context.Context, bridge *Scene) error
  264. }