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.

273 lines
6.8 KiB

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