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.

219 lines
5.1 KiB

  1. package hue
  2. import (
  3. "git.aiterp.net/lucifer/new-server/models"
  4. "strconv"
  5. "time"
  6. )
  7. type hueLightState struct {
  8. index int
  9. uniqueID string
  10. externalID int
  11. info LightData
  12. input LightStateInput
  13. stale bool
  14. }
  15. func (s *hueLightState) Update(state models.DeviceState) {
  16. input := LightStateInput{}
  17. if state.Power {
  18. input.On = ptrBool(true)
  19. if state.Color.IsKelvin() {
  20. input.CT = ptrInt(1000000 / state.Color.Kelvin)
  21. if s.input.CT == nil || *s.input.CT != *input.CT {
  22. s.stale = true
  23. }
  24. } else {
  25. input.Hue = ptrInt(int(state.Color.Hue*(65536/360)) % 65536)
  26. if s.input.Hue == nil || *s.input.Hue != *input.Hue {
  27. s.stale = true
  28. }
  29. input.Sat = ptrInt(int(state.Color.Saturation * 255))
  30. if *input.Sat > 254 {
  31. *input.Sat = 254
  32. }
  33. if *input.Sat < 0 {
  34. *input.Sat = 0
  35. }
  36. if s.input.Sat == nil || *s.input.Sat != *input.Sat {
  37. s.stale = true
  38. }
  39. }
  40. input.Bri = ptrInt(int(state.Intensity * 255))
  41. if *input.Bri > 254 {
  42. *input.Bri = 254
  43. } else if *input.Bri < 0 {
  44. *input.Bri = 0
  45. }
  46. if s.input.Bri == nil || *s.input.Bri != *input.Bri {
  47. s.stale = true
  48. }
  49. } else {
  50. input.On = ptrBool(false)
  51. }
  52. if s.input.On == nil || *s.input.On != *input.On {
  53. s.stale = true
  54. }
  55. input.TransitionTime = ptrInt(1)
  56. s.input = input
  57. }
  58. func (s *hueLightState) CheckStaleness(state LightState) {
  59. if state.ColorMode == "xy" {
  60. s.stale = true
  61. if s.input.CT == nil && s.input.Hue == nil {
  62. s.input.Hue = ptrInt(state.Hue)
  63. s.input.Sat = ptrInt(state.Sat)
  64. s.input.Bri = ptrInt(state.Bri)
  65. }
  66. return
  67. } else if state.ColorMode == "ct" {
  68. if s.input.CT == nil || state.CT != *s.input.CT {
  69. s.stale = true
  70. }
  71. } else {
  72. if s.input.Hue == nil || state.Hue != *s.input.Hue || s.input.Sat == nil || state.Sat == *s.input.Sat {
  73. s.stale = true
  74. }
  75. }
  76. }
  77. type hueSensorState struct {
  78. index int
  79. externalID int
  80. uniqueID string
  81. prevData *SensorData
  82. prevTime time.Time
  83. presenceCooldown int
  84. }
  85. func (state *hueSensorState) Update(newData SensorData) *models.Event {
  86. stateTime, err := time.ParseInLocation("2006-01-02T15:04:05", newData.State.LastUpdated, time.UTC)
  87. if err != nil {
  88. // Invalid time is probably "none".
  89. return nil
  90. }
  91. defer func() {
  92. state.prevData = &newData
  93. state.prevTime = stateTime
  94. }()
  95. if state.uniqueID != newData.UniqueID {
  96. state.uniqueID = newData.UniqueID
  97. }
  98. if state.prevData != nil && newData.Type != state.prevData.Type {
  99. return nil
  100. }
  101. switch newData.Type {
  102. case "ZLLSwitch":
  103. {
  104. // Ignore old events.
  105. if time.Since(stateTime) > time.Second*3 {
  106. return nil
  107. }
  108. if state.prevData == nil {
  109. return nil
  110. }
  111. pe := state.prevData.State.ButtonEvent
  112. ce := newData.State.ButtonEvent
  113. td := stateTime.Sub(state.prevTime) >= time.Second
  114. pIdx := (pe / 1000) - 1
  115. cIdx := (ce / 1000) - 1
  116. pBtn := pe % 1000 / 2 // 0 = pressed, 1 = released
  117. cBtn := ce % 1000 / 2 // 0 = pressed, 1 = released
  118. if pIdx == cIdx && cBtn == 1 && pBtn == 0 {
  119. // Do not allow 4002 after 4000.
  120. // 4000 after 4002 is fine, though.
  121. break
  122. }
  123. if cBtn != pBtn || cIdx != pIdx || td {
  124. return &models.Event{
  125. Name: models.ENButtonPressed,
  126. Payload: map[string]string{
  127. "buttonIndex": strconv.Itoa(cIdx),
  128. "buttonName": buttonNames[cIdx],
  129. "deviceId": strconv.Itoa(state.externalID),
  130. "hueButtonEvent": strconv.Itoa(ce),
  131. },
  132. }
  133. }
  134. }
  135. case "ZLLPresence":
  136. {
  137. if state.prevData != nil && state.prevData.State.Presence != newData.State.Presence {
  138. if newData.State.Presence {
  139. state.presenceCooldown = -1
  140. return &models.Event{
  141. Name: models.ENSensorPresenceStarted,
  142. Payload: map[string]string{
  143. "deviceId": strconv.Itoa(state.externalID),
  144. "deviceInternalId": newData.UniqueID,
  145. },
  146. }
  147. } else {
  148. state.presenceCooldown = 0
  149. }
  150. }
  151. if state.presenceCooldown == -2 {
  152. state.presenceCooldown = int(time.Since(stateTime) / time.Minute)
  153. }
  154. nextEventWait := time.Minute * time.Duration(state.presenceCooldown)
  155. if state.presenceCooldown != -1 && !newData.State.Presence && time.Since(stateTime) > nextEventWait {
  156. state.presenceCooldown += 1
  157. return &models.Event{
  158. Name: models.ENSensorPresenceEnded,
  159. Payload: map[string]string{
  160. "deviceId": strconv.Itoa(state.externalID),
  161. "deviceInternalId": newData.UniqueID,
  162. "minutesElapsed": strconv.Itoa(state.presenceCooldown - 1),
  163. "secondsElapsed": strconv.Itoa((state.presenceCooldown - 1) * 60),
  164. },
  165. }
  166. }
  167. }
  168. case "ZLLTemperature":
  169. if time.Since(stateTime) > (time.Minute * 15) {
  170. return nil
  171. }
  172. if !state.prevTime.Equal(stateTime) {
  173. return &models.Event{
  174. Name: models.ENSensorTemperature,
  175. Payload: map[string]string{
  176. "temperature": strconv.FormatFloat(float64(newData.State.Temperature)/100, 'f', 2, 64),
  177. "deviceId": strconv.Itoa(state.externalID),
  178. "deviceInternalId": newData.UniqueID,
  179. },
  180. }
  181. }
  182. }
  183. return nil
  184. }
  185. func ptrBool(v bool) *bool {
  186. return &v
  187. }
  188. func ptrInt(v int) *int {
  189. return &v
  190. }