Browse Source

add HorribleBroadcast™ functionality to LifX driver.

pull/1/head
Gisle Aune 4 years ago
parent
commit
766c534fcb
  1. 2
      .gitignore
  2. 22
      internal/drivers/hue/bridge.go
  3. 8
      internal/drivers/lifx/bridge.go
  4. 107
      internal/drivers/lifx/client.go
  5. 2
      internal/drivers/lifx/driver.go

2
.gitignore

@ -2,3 +2,5 @@
.env
.idea/
*.iml
/bridgetest
/10.24.4.1

22
internal/drivers/hue/bridge.go

@ -8,9 +8,11 @@ import (
"git.aiterp.net/lucifer/new-server/models"
"golang.org/x/sync/errgroup"
"io"
"net"
"net/http"
"strings"
"sync"
"time"
)
type Bridge struct {
@ -203,7 +205,7 @@ func (b *Bridge) get(ctx context.Context, resource string, target interface{}) e
return err
}
res, err := http.DefaultClient.Do(req.WithContext(ctx))
res, err := httpClient.Do(req.WithContext(ctx))
if err != nil {
return err
}
@ -227,7 +229,7 @@ func (b *Bridge) post(ctx context.Context, resource string, body interface{}, ta
return err
}
res, err := http.DefaultClient.Do(req.WithContext(ctx))
res, err := httpClient.Do(req.WithContext(ctx))
if err != nil {
return err
}
@ -255,7 +257,7 @@ func (b *Bridge) put(ctx context.Context, resource string, body interface{}, tar
return err
}
res, err := http.DefaultClient.Do(req.WithContext(ctx))
res, err := httpClient.Do(req.WithContext(ctx))
if err != nil {
return err
}
@ -289,3 +291,17 @@ func reqBody(body interface{}) (io.Reader, error) {
return bytes.NewReader(jsonData), nil
}
}
var httpClient = &http.Client{
Transport: &http.Transport{
Proxy: http.ProxyFromEnvironment,
DialContext: (&net.Dialer{
Timeout: 30 * time.Second,
KeepAlive: 30 * time.Second,
}).DialContext,
MaxIdleConns: 256,
MaxIdleConnsPerHost: 16,
IdleConnTimeout: 10 * time.Minute,
},
Timeout: time.Minute,
}

8
internal/drivers/lifx/bridge.go

@ -5,6 +5,7 @@ import (
"git.aiterp.net/lucifer/new-server/models"
"log"
"sync"
"sync/atomic"
"time"
)
@ -16,13 +17,13 @@ type Bridge struct {
client *Client
}
func (b *Bridge) StartSearch() error {
func (b *Bridge) StartSearch(ctx context.Context) error {
c := b.getClient()
if c == nil {
return models.ErrBridgeRunningRequired
}
_, err := c.Send("", &GetService{})
_, err := c.HorribleBroadcast(ctx, &GetService{})
return err
}
@ -214,8 +215,7 @@ func (b *Bridge) Run(ctx context.Context, debug bool) error {
}
b.mu.Unlock()
//
if time.Since(lastServiceTime) > time.Second*30 && time.Since(lastSearchTime) > time.Second*3 {
if atomic.LoadUint32(&client.isHorrible) == 0 && time.Since(lastServiceTime) > time.Second*30 && time.Since(lastSearchTime) > time.Second*3 {
lastSearchTime = time.Now()
_, err = client.Send("", &GetService{})
if err != nil {

107
internal/drivers/lifx/client.go

@ -2,12 +2,14 @@ package lifx
import (
"context"
"encoding/binary"
"errors"
"git.aiterp.net/lucifer/new-server/models"
"log"
"math/rand"
"net"
"sync"
"sync/atomic"
"time"
)
@ -21,6 +23,8 @@ type Client struct {
source uint32
debug bool
isHorrible uint32
addrMap map[string]*net.UDPAddr
}
@ -74,6 +78,107 @@ func (c *Client) Send(addr string, payload Payload) (seq uint8, err error) {
return
}
// HorribleBroadcast "broadcasts" by blasting every IP address in the space with a tagged packet, because LIFX is LIFX.
func (c *Client) HorribleBroadcast(ctx context.Context, payload Payload) (seq uint8, err error) {
c.mu.Lock()
seq = c.seq
c.seq += 1
c.mu.Unlock()
defer atomic.StoreUint32(&c.isHorrible, 0)
packet := createPacket()
packet.SetSource(c.source)
packet.SetSequence(seq)
packet.SetPayload(payload)
packet.SetTagged(true)
ifaces, err := net.Interfaces()
if err != nil {
return 0, err
}
connIP := c.conn.LocalAddr().(*net.UDPAddr).IP
var minIPNum uint32
var maxIPNum uint32
for _, iface := range ifaces {
addrs, err := iface.Addrs()
if err != nil {
continue
}
for _, addr := range addrs {
naddr, ok := addr.(*net.IPNet)
if !ok || !naddr.Contains(connIP) {
continue
}
networkAddress := naddr.IP.Mask(naddr.Mask)
cidr, bits := naddr.Mask.Size()
minIPNum = binary.BigEndian.Uint32(networkAddress) + 1
maxIPNum = minIPNum + (1 << (bits - cidr)) - 2
break
}
}
if minIPNum == maxIPNum {
return
}
myIPNum := binary.BigEndian.Uint32(connIP)
if c.debug {
ipBuf := make(net.IP, 8)
binary.BigEndian.PutUint32(ipBuf, minIPNum)
binary.BigEndian.PutUint32(ipBuf[4:], maxIPNum-1)
log.Println("Sending tagged", payload, "to range", ipBuf[:4], "-", ipBuf[4:])
defer func() {
log.Println("Completed sending tagged", payload, "to range", ipBuf[:4], "-", ipBuf[4:])
}()
}
for ipNum := minIPNum; ipNum < maxIPNum; ipNum++ {
if ipNum == myIPNum {
continue
}
atomic.StoreUint32(&c.isHorrible, 1)
ipBuf := make(net.IP, 4)
binary.BigEndian.PutUint32(ipBuf, ipNum)
addr := &net.UDPAddr{IP: ipBuf, Port: 56700}
_, err = c.conn.WriteToUDP(packet, addr)
if err != nil {
if c.debug {
log.Println("HorribleBroadcast™ aborted by error.")
}
return
}
if ipNum%256 == 0 {
if c.debug {
log.Println("Progress:", (ipNum-minIPNum)+1, "of", maxIPNum-minIPNum)
}
time.Sleep(time.Millisecond * 100)
}
if ctx.Err() != nil {
if c.debug {
log.Println("HorribleBroadcast™ aborted at call site.")
}
return
}
}
return
}
// LastPacket gets the last read packet. This data is valid until the next call to Recv. The Packet may
// not be valid, however!
func (c *Client) LastPacket() Packet {
@ -169,6 +274,8 @@ func createClient(ctx context.Context, bindAddr string, debug bool) (*Client, er
return nil, err
}
_ = conn.SetWriteBuffer(2048 * 1024)
go func() {
<-ctx.Done()
_ = conn.Close()

2
internal/drivers/lifx/driver.go

@ -72,7 +72,7 @@ func (d *Driver) SearchDevices(ctx context.Context, bridge models.Bridge, timeou
return nil, err
}
err = b.StartSearch()
err = b.StartSearch(ctx)
if err != nil {
return nil, err
}

Loading…
Cancel
Save