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 }