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.

254 lines
6.0 KiB

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