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.
177 lines
3.4 KiB
177 lines
3.4 KiB
package lifx
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"git.aiterp.net/lucifer/new-server/models"
|
|
"net"
|
|
"sync"
|
|
"time"
|
|
)
|
|
|
|
type Driver struct {
|
|
mu sync.Mutex
|
|
bridges []*Bridge
|
|
}
|
|
|
|
func (d *Driver) SearchBridge(ctx context.Context, address string, _ bool) ([]models.Bridge, error) {
|
|
if address == "" {
|
|
ifaces, err := net.Interfaces()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
bridges := make([]models.Bridge, 0, len(ifaces))
|
|
for _, iface := range ifaces {
|
|
if iface.Name == "lo" {
|
|
continue
|
|
}
|
|
|
|
addrs, err := iface.Addrs()
|
|
if err != nil || len(addrs) == 0 {
|
|
continue
|
|
}
|
|
|
|
for _, addr := range addrs {
|
|
bridges = append(bridges, models.Bridge{
|
|
ID: -1,
|
|
Name: fmt.Sprintf("%s (%s)", iface.Name, addr),
|
|
Driver: models.DTLIFX,
|
|
Address: addr.String(),
|
|
})
|
|
}
|
|
}
|
|
|
|
return bridges, nil
|
|
}
|
|
|
|
ctx2, cancel := context.WithCancel(ctx)
|
|
defer cancel()
|
|
_, err := createClient(ctx2, address, false)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return []models.Bridge{{
|
|
ID: 0,
|
|
Name: "Your network card",
|
|
Driver: models.DTLIFX,
|
|
Address: address,
|
|
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
|
|
}
|
|
|
|
before, err := d.ListDevices(ctx, bridge)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
err = b.StartSearch()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if timeout < time.Second/10 {
|
|
timeout = time.Second / 10
|
|
}
|
|
|
|
select {
|
|
case <-ctx.Done():
|
|
return nil, ctx.Err()
|
|
case <-time.After(timeout):
|
|
}
|
|
|
|
after, err := d.ListDevices(ctx, bridge)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
intersection := make([]models.Device, 0, 4)
|
|
for _, device := range after {
|
|
found := false
|
|
for _, device2 := range before {
|
|
if device2.InternalID == device.InternalID {
|
|
found = true
|
|
break
|
|
}
|
|
}
|
|
|
|
if !found {
|
|
intersection = append(intersection, device)
|
|
}
|
|
}
|
|
|
|
return intersection, 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) Publish(ctx context.Context, bridge models.Bridge, devices []models.Device) error {
|
|
b, err := d.ensureBridge(ctx, bridge)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
b.Publish(devices)
|
|
|
|
return 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.Token == "debug")
|
|
}
|
|
|
|
func (d *Driver) ensureBridge(ctx context.Context, info models.Bridge) (*Bridge, error) {
|
|
d.mu.Lock()
|
|
for _, bridge := range d.bridges {
|
|
if bridge.ip == info.Address {
|
|
d.mu.Unlock()
|
|
return bridge, nil
|
|
}
|
|
}
|
|
d.mu.Unlock()
|
|
|
|
bridge := &Bridge{
|
|
ip: info.Address,
|
|
externalID: info.ID,
|
|
}
|
|
|
|
// Try to create a short-lived client to make sure it works.
|
|
ctx2, cancel := context.WithCancel(ctx)
|
|
defer cancel()
|
|
_, err := createClient(ctx2, info.Address, info.Token == "debug")
|
|
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.ip == info.Address {
|
|
d.mu.Unlock()
|
|
return bridge, nil
|
|
}
|
|
}
|
|
d.bridges = append(d.bridges, bridge)
|
|
d.mu.Unlock()
|
|
|
|
return bridge, nil
|
|
}
|