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
162 lines
3.4 KiB
package nanoleaf
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"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, models.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, models.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
|
|
}
|