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.

269 lines
6.6 KiB

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