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.

163 lines
3.5 KiB

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