add HorribleBroadcast™ functionality to LifX driver.
This commit is contained in:
@@ -2,3 +2,5 @@
|
||||
.env
|
||||
.idea/
|
||||
*.iml
|
||||
/bridgetest
|
||||
/10.24.4.1
|
||||
@@ -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,
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user