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

package nanoleaf
import (
"context"
"encoding/json"
"fmt"
"git.aiterp.net/lucifer/new-server/internal/lerrors"
"git.aiterp.net/lucifer/new-server/models"
"net/http"
"sync"
"time"
)
type Driver struct {
mu sync.Mutex
bridges []*bridge
}
// SearchBridge checks the bridge at the address. If it's not a dry-run, you must hold down the power button
// before calling this function and wait for the pattern.
func (d *Driver) SearchBridge(ctx context.Context, address, _ string, dryRun bool) ([]models.Bridge, error) {
res, err := http.Get(fmt.Sprintf("http://%s/device_info", address))
if err != nil {
return nil, err
}
defer res.Body.Close()
deviceInfo := DeviceInfo{}
err = json.NewDecoder(res.Body).Decode(&deviceInfo)
if err != nil {
return nil, err
}
if deviceInfo.ModelNumber == "" {
return nil, lerrors.ErrUnexpectedResponse
}
token := ""
if !dryRun {
req, err := http.NewRequest("POST", fmt.Sprintf("http://%s:16021/api/v1/new/", address), nil)
if err != nil {
return nil, err
}
res, err := http.DefaultClient.Do(req.WithContext(ctx))
if err != nil {
return nil, err
}
defer res.Body.Close()
if res.StatusCode != 200 {
return nil, lerrors.ErrBridgeSearchFailed
}
tokenResponse := TokenResponse{}
err = json.NewDecoder(res.Body).Decode(&tokenResponse)
if err != nil {
return nil, err
}
token = tokenResponse.Token
}
return []models.Bridge{{
ID: -1,
Name: fmt.Sprintf("Nanoleaf Controller (MN: %s, SN: %s, HV: %s, FV: %s, BV: %s)",
deviceInfo.ModelNumber,
deviceInfo.SerialNumber,
deviceInfo.HardwareVersion,
deviceInfo.FirmwareVersion,
deviceInfo.BootloaderVersion,
),
Driver: models.DTNanoLeaf,
Address: address,
Token: token,
}}, nil
}
func (d *Driver) SearchDevices(ctx context.Context, bridge models.Bridge, timeout time.Duration) ([]models.Device, error) {
b, err := d.ensureBridge(ctx, bridge)
if err != nil {
return nil, err
}
if timeout > time.Millisecond {
timeoutCtx, cancel := context.WithTimeout(ctx, timeout)
defer cancel()
ctx = timeoutCtx
}
err = b.Refresh(ctx)
if err != nil {
return nil, err
}
return b.Devices(), nil
}
func (d *Driver) ListDevices(ctx context.Context, bridge models.Bridge) ([]models.Device, error) {
b, err := d.ensureBridge(ctx, bridge)
if err != nil {
return nil, err
}
return b.Devices(), nil
}
func (d *Driver) Run(ctx context.Context, bridge models.Bridge, ch chan<- models.Event) error {
b, err := d.ensureBridge(ctx, bridge)
if err != nil {
return err
}
return b.Run(ctx, bridge, ch)
}
func (d *Driver) Publish(ctx context.Context, bridge models.Bridge, devices []models.Device) error {
b, err := d.ensureBridge(ctx, bridge)
if err != nil {
return err
}
b.Update(devices)
return nil
}
func (d *Driver) ensureBridge(ctx context.Context, info models.Bridge) (*bridge, error) {
d.mu.Lock()
for _, bridge := range d.bridges {
if bridge.host == info.Address {
d.mu.Unlock()
return bridge, nil
}
}
d.mu.Unlock()
bridge := &bridge{
host: info.Address,
apiKey: info.Token,
externalID: info.ID,
panelIDMap: make(map[uint16]int, 9),
}
// If this fails, then the authorization failed.
err := bridge.Refresh(ctx)
if err != nil {
return nil, err
}
// To avoid a potential duplicate, try looking for it again before inserting
d.mu.Lock()
for _, bridge := range d.bridges {
if bridge.host == info.Address {
d.mu.Unlock()
return bridge, nil
}
}
d.bridges = append(d.bridges, bridge)
d.mu.Unlock()
return bridge, nil
}