Merge branch 'beelzebub' of git.aiterp.net:Lucifer3/Server into beelzebub
This commit is contained in:
+2
-7
@@ -2,10 +2,10 @@ package main
|
||||
|
||||
import (
|
||||
lucifer3 "git.aiterp.net/lucifer3/server"
|
||||
"git.aiterp.net/lucifer3/server/commands"
|
||||
"git.aiterp.net/lucifer3/server/services"
|
||||
"git.aiterp.net/lucifer3/server/services/hue"
|
||||
"git.aiterp.net/lucifer3/server/services/nanoleaf"
|
||||
"git.aiterp.net/lucifer3/server/services/tradfri"
|
||||
"time"
|
||||
)
|
||||
|
||||
@@ -20,12 +20,7 @@ func main() {
|
||||
bus.Join(services.NewEffectEnforcer(resolver, sceneMap))
|
||||
bus.Join(nanoleaf.NewService())
|
||||
bus.Join(hue.NewService())
|
||||
|
||||
bus.RunCommand(commands.ConnectDevice{ID: "hue:10.80.1.5", APIKey: "0-Ch5MKQtYnXrA3b8jvE4408mS3tHo9Vn57Zv8pt"})
|
||||
bus.Join(tradfri.NewService())
|
||||
|
||||
time.Sleep(time.Hour)
|
||||
}
|
||||
|
||||
func p[T any](v T) *T {
|
||||
return &v
|
||||
}
|
||||
|
||||
+2
-1
@@ -8,10 +8,11 @@ import (
|
||||
|
||||
type PairDevice struct {
|
||||
ID string `json:"id"`
|
||||
APIKey string `json:"apiKey"`
|
||||
}
|
||||
|
||||
func (c PairDevice) CommandDescription() string {
|
||||
return fmt.Sprintf("PairDevice(%s)", c.ID)
|
||||
return fmt.Sprintf("PairDevice(%s, %s)", c.ID, formattools.Asterisks(c.APIKey))
|
||||
}
|
||||
|
||||
func (c PairDevice) Matches(driver string) (sub string, ok bool) {
|
||||
|
||||
@@ -2,11 +2,24 @@ module git.aiterp.net/lucifer3/server
|
||||
|
||||
go 1.19
|
||||
|
||||
require github.com/lucasb-eyer/go-colorful v1.2.0
|
||||
|
||||
require (
|
||||
github.com/eriklupander/tradfri-go v1.0.1
|
||||
github.com/go-sql-driver/mysql v1.7.0
|
||||
github.com/gobwas/glob v0.2.3
|
||||
github.com/google/uuid v1.3.0
|
||||
github.com/lucasb-eyer/go-colorful v1.2.0
|
||||
github.com/sirupsen/logrus v1.9.0
|
||||
golang.org/x/sync v0.1.0
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/bocajim/dtls v0.0.0-20190226153416-329cdc841d4f // indirect
|
||||
github.com/dustin/go-coap v0.0.0-20170214053734-ddcc80675fa4 // indirect
|
||||
github.com/eriklupander/dtls v0.0.0-20190304211642-b36018226359 // indirect
|
||||
github.com/golang/protobuf v1.3.1 // indirect
|
||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a // indirect
|
||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 // indirect
|
||||
golang.org/x/text v0.3.0 // indirect
|
||||
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8 // indirect
|
||||
google.golang.org/grpc v1.21.1 // indirect
|
||||
)
|
||||
|
||||
@@ -1,10 +1,84 @@
|
||||
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/bocajim/dtls v0.0.0-20190226153416-329cdc841d4f h1:5c9skqLuQJ2iyEhmR5xiFVBvAKmFfpYfoE72UqBRZds=
|
||||
github.com/bocajim/dtls v0.0.0-20190226153416-329cdc841d4f/go.mod h1:htuSw7xe15DPFg5oJtcepjot00bvkhmXsx/b0yvFaKU=
|
||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/dustin/go-coap v0.0.0-20170214053734-ddcc80675fa4 h1:+3t0PiNl/W3Cl4/+XmQ8gD1HQuw3ISaGHHmSBylNVJ8=
|
||||
github.com/dustin/go-coap v0.0.0-20170214053734-ddcc80675fa4/go.mod h1:as2rZ2aojRzZF8bGx1bPAn1yi9ICG6LwkiPOj6PBtjc=
|
||||
github.com/eriklupander/dtls v0.0.0-20190304211642-b36018226359 h1:GrRdzY4NkR4IGoip3PvJH1VYkzMQW6HGV9Bl48yq9js=
|
||||
github.com/eriklupander/dtls v0.0.0-20190304211642-b36018226359/go.mod h1:9cQp/YAWpoevkrztrrOhFYyeHX8cOvhwHMiwg91o4Eo=
|
||||
github.com/eriklupander/tradfri-go v1.0.1 h1:KXgaV5dB+F/c5dAi/Zh2A//mE8G7hYTWaDow166MrDg=
|
||||
github.com/eriklupander/tradfri-go v1.0.1/go.mod h1:YfJVeg82Z9AHi/ZmkQgPc0LRAOqHojCxWlJBOxrMOLo=
|
||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
github.com/go-chi/chi v4.0.2+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ=
|
||||
github.com/go-sql-driver/mysql v1.7.0 h1:ueSltNNllEqE3qcWBTD0iQd3IpL/6U+mJxLkazJ7YPc=
|
||||
github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
|
||||
github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=
|
||||
github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8=
|
||||
github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.1 h1:YF8+flBXS5eO826T4nzqPrxfhQThhXl0YzfuUPu4SBg=
|
||||
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
|
||||
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
|
||||
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
|
||||
github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00=
|
||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
|
||||
github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
|
||||
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
|
||||
github.com/mitchellh/mapstructure v1.0.0/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
||||
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
|
||||
github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0=
|
||||
github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
||||
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
|
||||
github.com/spf13/cast v1.2.0/go.mod h1:r2rcYCSwa1IExKTDiTfzaxqT2FNHs8hODu4LnUfgKEg=
|
||||
github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
|
||||
github.com/spf13/pflag v1.0.2/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
|
||||
github.com/spf13/viper v1.2.1/go.mod h1:P4AexN0a+C9tGAnUFNwDMYYZv3pjFuvmeiMyKRaNVlI=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a h1:oWX7TPOiFAMXLq8o0ikBYfCJVlRHBcsciT5bXOrH628=
|
||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
|
||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20180906133057-8cf3aee42992/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 h1:0A+M6Uqn+Eje4kHMK80dtF3JCXC4ykBgQG4Fe06QRhQ=
|
||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8 h1:Nw54tB0rB7hY/N0NQvRW8DG4Yk3Q6T9cu9RcFQDu1tc=
|
||||
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||
google.golang.org/grpc v1.21.1 h1:j6XxA85m/6txkUCHvzlV5f+HBNl/1r5cZ2A/3IEFOO8=
|
||||
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
|
||||
@@ -0,0 +1,246 @@
|
||||
package tradfri
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
lucifer3 "git.aiterp.net/lucifer3/server"
|
||||
"git.aiterp.net/lucifer3/server/device"
|
||||
"git.aiterp.net/lucifer3/server/events"
|
||||
"git.aiterp.net/lucifer3/server/internal/color"
|
||||
"git.aiterp.net/lucifer3/server/internal/gentools"
|
||||
"github.com/eriklupander/tradfri-go/tradfri"
|
||||
"math"
|
||||
"math/rand"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Bridge struct {
|
||||
mx sync.Mutex
|
||||
client *tradfri.Client
|
||||
|
||||
id string
|
||||
newIP string
|
||||
newCredentials string
|
||||
|
||||
internalMap map[string]int
|
||||
nameMap map[string]string
|
||||
stateMap map[string]device.State
|
||||
}
|
||||
|
||||
func connect(ip, credentials string) (bridge *Bridge, retErr error) {
|
||||
bridge = &Bridge{}
|
||||
defer func() {
|
||||
retErr = catchPanic()
|
||||
}()
|
||||
|
||||
if !strings.Contains(ip, ":") {
|
||||
ip = ip + ":5684"
|
||||
}
|
||||
|
||||
parts := strings.Split(credentials, ":")
|
||||
if len(parts) == 1 {
|
||||
bridge.client = tradfri.NewTradfriClient(ip, "Client_identity", parts[0])
|
||||
|
||||
clientID := fmt.Sprintf("Lucifer4_%d", rand.Intn(10000))
|
||||
|
||||
token, err := bridge.client.AuthExchange(clientID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
bridge.newCredentials = clientID + ":" + token.Token
|
||||
} else {
|
||||
bridge.client = tradfri.NewTradfriClient(ip, parts[0], parts[1])
|
||||
|
||||
bridge.newCredentials = parts[0] + ":" + parts[1]
|
||||
}
|
||||
|
||||
bridge.newIP = ip
|
||||
bridge.id = fmt.Sprintf("tradfri:%s", ip)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (b *Bridge) listen(bus *lucifer3.EventBus) {
|
||||
b.internalMap = make(map[string]int, 16)
|
||||
b.nameMap = make(map[string]string, 16)
|
||||
b.stateMap = make(map[string]device.State, 16)
|
||||
|
||||
go func() {
|
||||
defer b.mx.Unlock()
|
||||
|
||||
for {
|
||||
b.mx.Lock()
|
||||
|
||||
devices, err := b.client.ListDevices()
|
||||
if err != nil {
|
||||
bus.RunEvent(events.DeviceFailed{
|
||||
ID: b.id,
|
||||
Error: "Unable to fetch IKEA devices",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
for _, ikeaDevice := range devices {
|
||||
id := fmt.Sprintf("%s:%d", b.id, ikeaDevice.DeviceId)
|
||||
|
||||
lc := ikeaDevice.LightControl
|
||||
if len(lc) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
currState := device.State{
|
||||
Power: gentools.Ptr(lc[0].Power > 0),
|
||||
Intensity: gentools.Ptr(float64(lc[0].Dimmer) / 254),
|
||||
Color: &color.Color{
|
||||
XY: &color.XY{
|
||||
X: float64(lc[0].CIE_1931_X) / 65535,
|
||||
Y: float64(lc[0].CIE_1931_Y) / 65535,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
if len(b.nameMap[id]) == 0 {
|
||||
b.internalMap[id] = ikeaDevice.DeviceId
|
||||
b.nameMap[id] = ikeaDevice.Name
|
||||
b.stateMap[id] = currState
|
||||
|
||||
bus.RunEvent(events.DeviceReady{ID: id})
|
||||
bus.RunEvent(events.HardwareMetadata{ID: id})
|
||||
bus.RunEvent(events.HardwareState{
|
||||
ID: id,
|
||||
InternalName: ikeaDevice.Name,
|
||||
SupportFlags: defaultSF,
|
||||
ColorFlags: defaultCF,
|
||||
State: b.stateMap[id],
|
||||
})
|
||||
}
|
||||
|
||||
b.refreshDevice(id, bus)
|
||||
}
|
||||
|
||||
b.mx.Unlock()
|
||||
time.Sleep(10 * time.Second)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
func (b *Bridge) writeState(id string, state device.State, bus *lucifer3.EventBus) {
|
||||
b.stateMap[id] = state
|
||||
b.refreshDevice(id, bus)
|
||||
}
|
||||
|
||||
func (b *Bridge) refreshDevice(id string, bus *lucifer3.EventBus) {
|
||||
ikeaDevice, err := b.client.GetDevice(b.internalMap[id])
|
||||
if err != nil {
|
||||
bus.RunEvent(events.DeviceFailed{
|
||||
ID: id,
|
||||
Error: "Unable to fetch IKEA device",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
changed := false
|
||||
|
||||
if len(ikeaDevice.LightControl) > 0 {
|
||||
ikeaState := ikeaDevice.LightControl[0]
|
||||
luciferState := b.stateMap[id]
|
||||
|
||||
currPower := ikeaState.Power > 0
|
||||
currIntensity := float64(ikeaState.Dimmer) / 254
|
||||
currX := float64(ikeaState.CIE_1931_X) / 65535
|
||||
currY := float64(ikeaState.CIE_1931_Y) / 65535
|
||||
|
||||
if luciferState.Intensity != nil {
|
||||
newIntensity := *luciferState.Intensity
|
||||
diffIntensity := math.Abs(newIntensity - currIntensity)
|
||||
|
||||
if diffIntensity >= 0.01 {
|
||||
changed = true
|
||||
|
||||
intensityInt := int(math.Round(newIntensity * 254))
|
||||
|
||||
_, err := b.client.PutDeviceDimming(ikeaDevice.DeviceId, intensityInt)
|
||||
if err != nil {
|
||||
bus.RunEvent(events.DeviceFailed{
|
||||
ID: id,
|
||||
Error: "Failed to update intensity state",
|
||||
})
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if luciferState.Color != nil && luciferState.Color.XY != nil {
|
||||
newX := luciferState.Color.XY.X
|
||||
newY := luciferState.Color.XY.Y
|
||||
diffX := math.Abs(newX - currX)
|
||||
diffY := math.Abs(newY - currY)
|
||||
|
||||
if diffX >= 0.0001 || diffY >= 0.0001 {
|
||||
changed = true
|
||||
|
||||
xInt := int(math.Round(newX * 65535))
|
||||
yInt := int(math.Round(newY * 65535))
|
||||
|
||||
_, err := b.client.PutDeviceColor(ikeaDevice.DeviceId, xInt, yInt)
|
||||
if err != nil {
|
||||
bus.RunEvent(events.DeviceFailed{
|
||||
ID: id,
|
||||
Error: "Failed to update color state",
|
||||
})
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if luciferState.Power != nil {
|
||||
newPower := *luciferState.Power
|
||||
diffPower := newPower != currPower
|
||||
|
||||
if diffPower {
|
||||
changed = true
|
||||
|
||||
powerInt := 0
|
||||
if newPower {
|
||||
powerInt = 1
|
||||
}
|
||||
|
||||
_, err := b.client.PutDevicePower(ikeaDevice.DeviceId, powerInt)
|
||||
if err != nil {
|
||||
bus.RunEvent(events.DeviceFailed{
|
||||
ID: id,
|
||||
Error: "Failed to update power state",
|
||||
})
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if changed {
|
||||
bus.RunEvent(events.HardwareState{
|
||||
ID: id,
|
||||
InternalName: ikeaDevice.Name,
|
||||
SupportFlags: defaultSF,
|
||||
ColorFlags: defaultCF,
|
||||
State: luciferState,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const defaultSF = device.SFlagPower | device.SFlagIntensity | device.SFlagColor
|
||||
const defaultCF = device.CFlagXY
|
||||
|
||||
func catchPanic(mutexes ...sync.Locker) (retErr error) {
|
||||
if err, ok := recover().(error); ok {
|
||||
retErr = err
|
||||
}
|
||||
|
||||
for _, mx := range mutexes {
|
||||
mx.Unlock()
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
@@ -0,0 +1,118 @@
|
||||
package tradfri
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
lucifer3 "git.aiterp.net/lucifer3/server"
|
||||
"git.aiterp.net/lucifer3/server/commands"
|
||||
"git.aiterp.net/lucifer3/server/events"
|
||||
"github.com/sirupsen/logrus"
|
||||
"io"
|
||||
"strings"
|
||||
"sync"
|
||||
)
|
||||
|
||||
func NewService() lucifer3.ActiveService {
|
||||
logrus.StandardLogger().Out = io.Discard
|
||||
logrus.StandardLogger().ExitFunc = func(i int) {
|
||||
panic(fmt.Sprintf("tradfri: exit code %d", i))
|
||||
}
|
||||
|
||||
return &service{
|
||||
bridges: make(map[string]*Bridge, 4),
|
||||
}
|
||||
}
|
||||
|
||||
type service struct {
|
||||
mu sync.Mutex
|
||||
|
||||
bridges map[string]*Bridge
|
||||
}
|
||||
|
||||
func (s *service) Active() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (s *service) HandleEvent(*lucifer3.EventBus, lucifer3.Event) {
|
||||
// NOP
|
||||
}
|
||||
|
||||
func (s *service) HandleCommand(bus *lucifer3.EventBus, command lucifer3.Command) {
|
||||
defer s.mu.Unlock()
|
||||
s.mu.Lock()
|
||||
|
||||
switch command := command.(type) {
|
||||
case commands.PairDevice:
|
||||
if sub, ok := command.Matches("tradfri"); ok {
|
||||
bridge, err := connect(sub, command.APIKey)
|
||||
if err != nil {
|
||||
bus.RunEvent(events.DeviceFailed{
|
||||
ID: command.ID,
|
||||
Error: err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
s.bridges[bridge.id] = bridge
|
||||
|
||||
bus.RunEvent(events.DeviceAccepted{
|
||||
ID: bridge.id,
|
||||
APIKey: bridge.newCredentials,
|
||||
})
|
||||
}
|
||||
case commands.SearchDevices:
|
||||
bus.RunEvent(events.DeviceFailed{
|
||||
ID: command.ID,
|
||||
Error: "Please follow instructions in IKEA app; they will appear here after pairing.",
|
||||
})
|
||||
case commands.SetState:
|
||||
if _, ok := command.Matches("tradfri"); ok {
|
||||
bridge, ok := s.findBridge(command.ID)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
bridge.writeState(command.ID, command.State, bus)
|
||||
}
|
||||
case commands.SetStateBatch:
|
||||
for id, state := range command {
|
||||
if strings.HasPrefix(id, "tradfri") {
|
||||
bridge, ok := s.findBridge(id)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
bridge.writeState(id, state, bus)
|
||||
}
|
||||
}
|
||||
case commands.ConnectDevice:
|
||||
if sub, ok := command.Matches("tradfri"); ok {
|
||||
if s.bridges[command.ID] == nil {
|
||||
bridge, err := connect(sub, command.APIKey)
|
||||
if err != nil {
|
||||
bus.RunEvent(events.DeviceFailed{
|
||||
ID: command.ID,
|
||||
Error: err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
s.bridges[command.ID] = bridge
|
||||
}
|
||||
|
||||
bus.RunEvent(events.DeviceConnected{Prefix: s.bridges[command.ID].id})
|
||||
s.bridges[command.ID].listen(bus)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *service) findBridge(id string) (*Bridge, bool) {
|
||||
bits := strings.Split(id, ":")
|
||||
for i := 2; i < len(bits); i++ {
|
||||
prefix := strings.Join(bits[0:i], ":")
|
||||
if s.bridges[prefix] != nil {
|
||||
return s.bridges[prefix], true
|
||||
}
|
||||
}
|
||||
|
||||
return nil, false
|
||||
}
|
||||
Reference in New Issue
Block a user