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.

286 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. return json.NewDecoder(res.Body).Decode(target)
  193. }
  194. func (b *Bridge) put(ctx context.Context, resource string, body interface{}, target interface{}) error {
  195. rb, err := reqBody(body)
  196. if err != nil {
  197. return err
  198. }
  199. if b.token != "" {
  200. resource = b.token + "/" + resource
  201. }
  202. req, err := http.NewRequest("PUT", fmt.Sprintf("http://%s/api/%s", b.host, resource), rb)
  203. if err != nil {
  204. return err
  205. }
  206. res, err := http.DefaultClient.Do(req.WithContext(ctx))
  207. if err != nil {
  208. return err
  209. }
  210. defer res.Body.Close()
  211. if target == nil {
  212. return nil
  213. }
  214. return json.NewDecoder(res.Body).Decode(target)
  215. }
  216. func reqBody(body interface{}) (io.Reader, error) {
  217. if body == nil {
  218. return nil, nil
  219. }
  220. switch v := body.(type) {
  221. case []byte:
  222. return bytes.NewReader(v), nil
  223. case string:
  224. return strings.NewReader(v), nil
  225. case io.Reader:
  226. return v, nil
  227. default:
  228. jsonData, err := json.Marshal(v)
  229. if err != nil {
  230. return nil, err
  231. }
  232. return bytes.NewReader(jsonData), nil
  233. }
  234. }