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.

267 lines
6.5 KiB

  1. package lifx
  2. import (
  3. "context"
  4. "git.aiterp.net/lucifer/new-server/models"
  5. "log"
  6. "sync"
  7. "time"
  8. )
  9. type Bridge struct {
  10. mu sync.Mutex
  11. externalID int
  12. ip string
  13. states []*State
  14. client *Client
  15. }
  16. func (b *Bridge) StartSearch() error {
  17. c := b.getClient()
  18. if c == nil {
  19. return models.ErrBridgeRunningRequired
  20. }
  21. _, err := c.Send("", &GetService{})
  22. return err
  23. }
  24. func (b *Bridge) Publish(devices []models.Device) {
  25. b.mu.Lock()
  26. for _, device := range devices {
  27. state, _ := b.ensureState(device.InternalID)
  28. state.deviceState = new(models.DeviceState)
  29. *state.deviceState = device.State
  30. b.checkAndUpdateState(state)
  31. state.externalId = device.ID
  32. }
  33. b.mu.Unlock()
  34. }
  35. func (b *Bridge) Devices() []models.Device {
  36. devices := make([]models.Device, 0, 8)
  37. b.mu.Lock()
  38. for _, state := range b.states {
  39. if state.lightState == nil || state.firmware == nil || state.version == nil {
  40. continue
  41. }
  42. deviceState := models.DeviceState{}
  43. if state.deviceState != nil {
  44. deviceState = *state.deviceState
  45. }
  46. device := models.Device{
  47. ID: state.externalId,
  48. BridgeID: b.externalID,
  49. InternalID: state.target,
  50. Icon: "lightbulb",
  51. Name: state.lightState.Label,
  52. Capabilities: []models.DeviceCapability{models.DCPower, models.DCIntensity},
  53. DriverProperties: make(map[string]interface{}),
  54. State: deviceState,
  55. }
  56. device.DriverProperties["lifxVendorId"] = state.version.Vendor
  57. device.DriverProperties["lifxProductId"] = state.version.Product
  58. device.DriverProperties["lifxFirmwareMajor"] = state.firmware.Major
  59. device.DriverProperties["lifxFirmwareMinor"] = state.firmware.Minor
  60. device.DriverProperties["lifxFirmwareBuild"] = state.firmware.BuildTime
  61. product := findProduct(state.version.Vendor, state.version.Product)
  62. if product != nil {
  63. device.DriverProperties["productName"] = product.Name
  64. if product.Features.Color {
  65. device.Capabilities = append(device.Capabilities, models.DCColorHS)
  66. }
  67. if len(product.Features.TemperatureRange) >= 2 {
  68. device.Capabilities = append(device.Capabilities, models.DCColorHSK, models.DCColorKelvin)
  69. device.DriverProperties["minKelvin"] = product.Features.TemperatureRange[0]
  70. device.DriverProperties["maxKelvin"] = product.Features.TemperatureRange[1]
  71. }
  72. for _, upgrade := range product.Upgrades {
  73. if state.firmware.Major > upgrade.Major || (state.firmware.Major >= upgrade.Major && state.firmware.Minor >= upgrade.Minor) {
  74. if upgrade.Features.TemperatureRange != nil {
  75. device.DriverProperties["minKelvin"] = upgrade.Features.TemperatureRange[0]
  76. device.DriverProperties["maxKelvin"] = upgrade.Features.TemperatureRange[1]
  77. }
  78. }
  79. }
  80. }
  81. devices = append(devices, device)
  82. }
  83. b.mu.Unlock()
  84. return devices
  85. }
  86. func (b *Bridge) Run(ctx context.Context, debug bool) error {
  87. client, err := createClient(ctx, b.ip, debug)
  88. if err != nil {
  89. return err
  90. }
  91. b.mu.Lock()
  92. b.client = client
  93. b.mu.Unlock()
  94. defer func() {
  95. b.mu.Lock()
  96. if b.client == client {
  97. b.client = nil
  98. }
  99. b.mu.Unlock()
  100. }()
  101. lastSearchTime := time.Now()
  102. lastServiceTime := time.Time{}
  103. _, err = client.Send("", &GetService{})
  104. if err != nil {
  105. return err
  106. }
  107. for {
  108. target, seq, payload, err := client.Recv(time.Millisecond * 200)
  109. if err == models.ErrInvalidPacketSize || err == models.ErrPayloadTooShort || err == models.ErrUnrecognizedPacketType {
  110. log.Println("LIFX udp socket received something weird:", err)
  111. } else if err != nil && err != models.ErrReadTimeout {
  112. if ctx.Err() != nil {
  113. return ctx.Err()
  114. }
  115. return err
  116. }
  117. if payload != nil {
  118. b.mu.Lock()
  119. state, _ := b.ensureState(target)
  120. b.mu.Unlock()
  121. switch p := payload.(type) {
  122. case *StateService:
  123. if p.Service == 1 {
  124. // Throw these messages to the wind. UDP errors will eventually be caught.
  125. // Get the version only if it's missing. It should never change anyway.
  126. b.mu.Lock()
  127. if state.version == nil {
  128. _, _ = client.Send(target, &GetVersion{})
  129. }
  130. b.mu.Unlock()
  131. _, _ = client.Send(target, &GetHostFirmware{})
  132. lastServiceTime = time.Now()
  133. }
  134. case *LightState:
  135. b.mu.Lock()
  136. state.lightState = p
  137. state.lightStateTime = time.Now()
  138. if state.deviceState == nil {
  139. state.deviceState = &models.DeviceState{
  140. Power: p.On,
  141. Color: models.ColorValue{
  142. Hue: p.Hue,
  143. Saturation: p.Sat,
  144. Kelvin: p.Kelvin,
  145. },
  146. Intensity: p.Bri,
  147. }
  148. }
  149. b.checkAndUpdateState(state)
  150. b.mu.Unlock()
  151. case *StateHostFirmware:
  152. b.mu.Lock()
  153. state.firmware = p
  154. b.mu.Unlock()
  155. case *StateVersion:
  156. b.mu.Lock()
  157. state.version = p
  158. b.mu.Unlock()
  159. case *Acknowledgement:
  160. b.mu.Lock()
  161. state.handleAck(seq)
  162. b.mu.Unlock()
  163. }
  164. }
  165. b.mu.Lock()
  166. for _, state := range b.states {
  167. if time.Since(state.lightStateTime) > time.Second*10 && time.Since(state.requestTime) > time.Second*3 {
  168. state.requestTime = time.Now()
  169. _, _ = client.Send(state.target, &GetColor{})
  170. } else if len(state.acksPending) > 0 && time.Since(state.updateTime) > time.Second {
  171. state.requestTime = time.Now()
  172. b.checkAndUpdateState(state)
  173. }
  174. if time.Since(state.discoveredTime) > time.Second*10 {
  175. if state.firmware == nil {
  176. _, _ = client.Send(state.target, &GetHostFirmware{})
  177. }
  178. if state.version == nil {
  179. _, _ = client.Send(state.target, &GetVersion{})
  180. }
  181. }
  182. }
  183. b.mu.Unlock()
  184. //
  185. if time.Since(lastServiceTime) > time.Second*30 && time.Since(lastSearchTime) > time.Second*3 {
  186. lastSearchTime = time.Now()
  187. _, err = client.Send("", &GetService{})
  188. if err != nil {
  189. return err
  190. }
  191. }
  192. }
  193. }
  194. func (b *Bridge) checkAndUpdateState(state *State) {
  195. state.acksPending = state.acksPending[:0]
  196. updatePayloads := state.generateUpdate()
  197. for _, updatePayload := range updatePayloads {
  198. seq, err := b.client.Send(state.target, updatePayload)
  199. if err != nil {
  200. log.Println("Error sending updates to", state.externalId, state.target, "err:", err)
  201. continue
  202. }
  203. state.updateTime = time.Now()
  204. state.acksPending = append(state.acksPending, seq)
  205. }
  206. }
  207. func (b *Bridge) ensureState(target string) (*State, bool) {
  208. for _, state := range b.states {
  209. if state.target == target {
  210. return state, false
  211. }
  212. }
  213. state := &State{
  214. target: target,
  215. discoveredTime: time.Now(),
  216. }
  217. b.states = append(b.states, state)
  218. return state, true
  219. }
  220. func (b *Bridge) getClient() *Client {
  221. b.mu.Lock()
  222. client := b.client
  223. b.mu.Unlock()
  224. return client
  225. }