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.

290 lines
5.6 KiB

  1. package hue
  2. import (
  3. "bytes"
  4. "context"
  5. "encoding/json"
  6. "fmt"
  7. "git.aiterp.net/lucifer/new-server/models"
  8. "golang.org/x/sync/errgroup"
  9. "io"
  10. "net/http"
  11. "strings"
  12. "sync"
  13. )
  14. type Bridge struct {
  15. mu sync.Mutex
  16. host string
  17. token string
  18. externalID int
  19. lightStates []*hueLightState
  20. sensorStates []*hueSensorState
  21. }
  22. func (b *Bridge) Refresh(ctx context.Context) error {
  23. lightMap, err := b.getLights(ctx)
  24. if err != nil {
  25. return err
  26. }
  27. b.mu.Lock()
  28. for index, light := range lightMap {
  29. var state *hueLightState
  30. for _, existingState := range b.lightStates {
  31. if existingState.index == index {
  32. state = existingState
  33. }
  34. }
  35. if state == nil {
  36. state = &hueLightState{
  37. index: index,
  38. uniqueID: light.Uniqueid,
  39. externalID: -1,
  40. info: light,
  41. }
  42. b.lightStates = append(b.lightStates, state)
  43. } else {
  44. if light.Uniqueid != state.uniqueID {
  45. state.uniqueID = light.Uniqueid
  46. state.externalID = -1
  47. }
  48. }
  49. state.CheckStaleness(light.State)
  50. }
  51. b.mu.Unlock()
  52. sensorMap, err := b.getSensors(ctx)
  53. if err != nil {
  54. return err
  55. }
  56. b.mu.Lock()
  57. for index, sensor := range sensorMap {
  58. var state *hueSensorState
  59. for _, existingState := range b.sensorStates {
  60. if existingState.index == index {
  61. state = existingState
  62. }
  63. }
  64. if state == nil {
  65. state = &hueSensorState{
  66. index: index,
  67. uniqueID: sensor.UniqueID,
  68. externalID: -1,
  69. }
  70. b.sensorStates = append(b.sensorStates, state)
  71. } else {
  72. if sensor.UniqueID != state.uniqueID {
  73. state.uniqueID = sensor.UniqueID
  74. state.externalID = -1
  75. }
  76. }
  77. }
  78. b.mu.Unlock()
  79. return nil
  80. }
  81. func (b *Bridge) SyncStale(ctx context.Context) error {
  82. indices := make([]int, 0, 4)
  83. inputs := make([]LightStateInput, 0, 4)
  84. eg, ctx := errgroup.WithContext(ctx)
  85. b.mu.Lock()
  86. for _, state := range b.lightStates {
  87. if !state.stale {
  88. continue
  89. }
  90. indices = append(indices, state.index)
  91. inputs = append(inputs, state.input)
  92. }
  93. b.mu.Unlock()
  94. if len(inputs) == 0 {
  95. return nil
  96. }
  97. for i, input := range inputs {
  98. index := indices[i]
  99. eg.Go(func() error { return b.putLightState(ctx, index, input) })
  100. }
  101. return eg.Wait()
  102. }
  103. func (b *Bridge) SyncSensors(ctx context.Context) ([]models.Event, error) {
  104. sensorMap, err := b.getSensors(ctx)
  105. if err != nil {
  106. return nil, err
  107. }
  108. var events []models.Event
  109. b.mu.Lock()
  110. for idx, sensorData := range sensorMap {
  111. for _, state := range b.sensorStates {
  112. if idx == state.index {
  113. event := state.Update(sensorData)
  114. if event != nil {
  115. events = append(events, *event)
  116. }
  117. break
  118. }
  119. }
  120. }
  121. b.mu.Unlock()
  122. return events, nil
  123. }
  124. func (b *Bridge) StartDiscovery(ctx context.Context, model string) error {
  125. return b.post(ctx, model, nil, nil)
  126. }
  127. func (b *Bridge) putLightState(ctx context.Context, index int, input LightStateInput) error {
  128. return b.put(ctx, fmt.Sprintf("lights/%d/state", index), input, nil)
  129. }
  130. func (b *Bridge) getToken(ctx context.Context) (string, error) {
  131. result := make([]CreateUserResponse, 0, 1)
  132. err := b.post(ctx, "", CreateUserInput{DeviceType: "git.aiterp.net/lucifer"}, &result)
  133. if err != nil {
  134. return "", err
  135. }
  136. if len(result) == 0 || result[0].Error != nil {
  137. return "", errLinkButtonNotPressed
  138. }
  139. if result[0].Success == nil {
  140. return "", models.ErrUnexpectedResponse
  141. }
  142. return result[0].Success.Username, nil
  143. }
  144. func (b *Bridge) getLights(ctx context.Context) (map[int]LightData, error) {
  145. result := make(map[int]LightData, 16)
  146. err := b.get(ctx, "lights", &result)
  147. if err != nil {
  148. return nil, err
  149. }
  150. return result, nil
  151. }
  152. func (b *Bridge) getSensors(ctx context.Context) (map[int]SensorData, error) {
  153. result := make(map[int]SensorData, 16)
  154. err := b.get(ctx, "sensors", &result)
  155. if err != nil {
  156. return nil, err
  157. }
  158. return result, nil
  159. }
  160. func (b *Bridge) get(ctx context.Context, resource string, target interface{}) error {
  161. if b.token != "" {
  162. resource = b.token + "/" + resource
  163. }
  164. req, err := http.NewRequest("GET", fmt.Sprintf("http://%s/api/%s", b.host, resource), nil)
  165. if err != nil {
  166. return err
  167. }
  168. res, err := http.DefaultClient.Do(req.WithContext(ctx))
  169. if err != nil {
  170. return err
  171. }
  172. defer res.Body.Close()
  173. return json.NewDecoder(res.Body).Decode(target)
  174. }
  175. func (b *Bridge) post(ctx context.Context, resource string, body interface{}, target interface{}) error {
  176. rb, err := reqBody(body)
  177. if err != nil {
  178. return err
  179. }
  180. if b.token != "" {
  181. resource = b.token + "/" + resource
  182. }
  183. req, err := http.NewRequest("POST", fmt.Sprintf("http://%s/api/%s", b.host, resource), rb)
  184. if err != nil {
  185. return err
  186. }
  187. res, err := http.DefaultClient.Do(req.WithContext(ctx))
  188. if err != nil {
  189. return err
  190. }
  191. defer res.Body.Close()
  192. if target == nil {
  193. return nil
  194. }
  195. return json.NewDecoder(res.Body).Decode(target)
  196. }
  197. func (b *Bridge) put(ctx context.Context, resource string, body interface{}, target interface{}) error {
  198. rb, err := reqBody(body)
  199. if err != nil {
  200. return err
  201. }
  202. if b.token != "" {
  203. resource = b.token + "/" + resource
  204. }
  205. req, err := http.NewRequest("PUT", fmt.Sprintf("http://%s/api/%s", b.host, resource), rb)
  206. if err != nil {
  207. return err
  208. }
  209. res, err := http.DefaultClient.Do(req.WithContext(ctx))
  210. if err != nil {
  211. return err
  212. }
  213. defer res.Body.Close()
  214. if target == nil {
  215. return nil
  216. }
  217. return json.NewDecoder(res.Body).Decode(target)
  218. }
  219. func reqBody(body interface{}) (io.Reader, error) {
  220. if body == nil {
  221. return nil, nil
  222. }
  223. switch v := body.(type) {
  224. case []byte:
  225. return bytes.NewReader(v), nil
  226. case string:
  227. return strings.NewReader(v), nil
  228. case io.Reader:
  229. return v, nil
  230. default:
  231. jsonData, err := json.Marshal(v)
  232. if err != nil {
  233. return nil, err
  234. }
  235. return bytes.NewReader(jsonData), nil
  236. }
  237. }