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.

162 lines
3.4 KiB

  1. package nanoleaf
  2. import (
  3. "context"
  4. "encoding/json"
  5. "fmt"
  6. "git.aiterp.net/lucifer/new-server/models"
  7. "net/http"
  8. "sync"
  9. "time"
  10. )
  11. type Driver struct {
  12. mu sync.Mutex
  13. bridges []*bridge
  14. }
  15. // SearchBridge checks the bridge at the address. If it's not a dry-run, you must hold down the power button
  16. // before calling this function and wait for the pattern.
  17. func (d *Driver) SearchBridge(ctx context.Context, address string, dryRun bool) ([]models.Bridge, error) {
  18. res, err := http.Get(fmt.Sprintf("http://%s/device_info", address))
  19. if err != nil {
  20. return nil, err
  21. }
  22. defer res.Body.Close()
  23. deviceInfo := DeviceInfo{}
  24. err = json.NewDecoder(res.Body).Decode(&deviceInfo)
  25. if err != nil {
  26. return nil, err
  27. }
  28. if deviceInfo.ModelNumber == "" {
  29. return nil, models.ErrUnexpectedResponse
  30. }
  31. token := ""
  32. if !dryRun {
  33. req, err := http.NewRequest("POST", fmt.Sprintf("http://%s:16021/api/v1/new/", address), nil)
  34. if err != nil {
  35. return nil, err
  36. }
  37. res, err := http.DefaultClient.Do(req.WithContext(ctx))
  38. if err != nil {
  39. return nil, err
  40. }
  41. defer res.Body.Close()
  42. if res.StatusCode != 200 {
  43. return nil, models.ErrBridgeSearchFailed
  44. }
  45. tokenResponse := TokenResponse{}
  46. err = json.NewDecoder(res.Body).Decode(&tokenResponse)
  47. if err != nil {
  48. return nil, err
  49. }
  50. token = tokenResponse.Token
  51. }
  52. return []models.Bridge{{
  53. ID: -1,
  54. Name: fmt.Sprintf("Nanoleaf Controller (MN: %s, SN: %s, HV: %s, FV: %s, BV: %s)",
  55. deviceInfo.ModelNumber,
  56. deviceInfo.SerialNumber,
  57. deviceInfo.HardwareVersion,
  58. deviceInfo.FirmwareVersion,
  59. deviceInfo.BootloaderVersion,
  60. ),
  61. Driver: models.DTNanoLeaf,
  62. Address: address,
  63. Token: token,
  64. }}, nil
  65. }
  66. func (d *Driver) SearchDevices(ctx context.Context, bridge models.Bridge, timeout time.Duration) ([]models.Device, error) {
  67. b, err := d.ensureBridge(ctx, bridge)
  68. if err != nil {
  69. return nil, err
  70. }
  71. if timeout > time.Millisecond {
  72. timeoutCtx, cancel := context.WithTimeout(ctx, timeout)
  73. defer cancel()
  74. ctx = timeoutCtx
  75. }
  76. err = b.Refresh(ctx)
  77. if err != nil {
  78. return nil, err
  79. }
  80. return b.Devices(), nil
  81. }
  82. func (d *Driver) ListDevices(ctx context.Context, bridge models.Bridge) ([]models.Device, error) {
  83. b, err := d.ensureBridge(ctx, bridge)
  84. if err != nil {
  85. return nil, err
  86. }
  87. return b.Devices(), nil
  88. }
  89. func (d *Driver) Run(ctx context.Context, bridge models.Bridge, ch chan<- models.Event) error {
  90. b, err := d.ensureBridge(ctx, bridge)
  91. if err != nil {
  92. return err
  93. }
  94. return b.Run(ctx, bridge, ch)
  95. }
  96. func (d *Driver) Publish(ctx context.Context, bridge models.Bridge, devices []models.Device) error {
  97. b, err := d.ensureBridge(ctx, bridge)
  98. if err != nil {
  99. return err
  100. }
  101. b.Update(devices)
  102. return nil
  103. }
  104. func (d *Driver) ensureBridge(ctx context.Context, info models.Bridge) (*bridge, error) {
  105. d.mu.Lock()
  106. for _, bridge := range d.bridges {
  107. if bridge.host == info.Address {
  108. d.mu.Unlock()
  109. return bridge, nil
  110. }
  111. }
  112. d.mu.Unlock()
  113. bridge := &bridge{
  114. host: info.Address,
  115. apiKey: info.Token,
  116. externalID: info.ID,
  117. panelIDMap: make(map[uint16]int, 9),
  118. }
  119. // If this fails, then the authorization failed.
  120. err := bridge.Refresh(ctx)
  121. if err != nil {
  122. return nil, err
  123. }
  124. // To avoid a potential duplicate, try looking for it again before inserting
  125. d.mu.Lock()
  126. for _, bridge := range d.bridges {
  127. if bridge.host == info.Address {
  128. d.mu.Unlock()
  129. return bridge, nil
  130. }
  131. }
  132. d.bridges = append(d.bridges, bridge)
  133. d.mu.Unlock()
  134. return bridge, nil
  135. }