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.

315 lines
6.9 KiB

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