From 2e30961de9c309dfbdb8890911a64bad8903b15c Mon Sep 17 00:00:00 2001 From: Sebastian Plattner Date: Wed, 22 Dec 2021 12:58:07 +0100 Subject: [PATCH 1/2] deconz sensor / buttons, not working draft --- pkg/discovery/deconz.go | 203 +++++++++++++++++++++++++++++++++++----- pkg/vdcdapi/client.go | 18 ++++ pkg/vdcdapi/device.go | 32 ++++++- pkg/vdcdapi/types.go | 12 +-- 4 files changed, 230 insertions(+), 35 deletions(-) diff --git a/pkg/discovery/deconz.go b/pkg/discovery/deconz.go index 2b050a4..77c1afa 100644 --- a/pkg/discovery/deconz.go +++ b/pkg/discovery/deconz.go @@ -16,6 +16,7 @@ import ( deconzgroup "github.com/jurgen-kluft/go-conbee/groups" deconzlight "github.com/jurgen-kluft/go-conbee/lights" + deconzsensor "github.com/jurgen-kluft/go-conbee/sensors" ) type DeconzDevice struct { @@ -29,11 +30,15 @@ type DeconzDevice struct { IsLight bool light deconzlight.Light - allDeconzDevices []DeconzDevice - IsGroup bool group deconzgroup.Group + IsSensor bool + sensor deconzsensor.Sensor + sensorButtonID int + + allDeconzDevices []DeconzDevice + done chan interface{} interrupt chan os.Signal } @@ -87,8 +92,21 @@ type DeconzState struct { // Group AllOn bool `json:"all_on,omitempty"` AnyOn bool `json:"any_on,omitempty"` + + // Sensor + ButtonEvent int `json:"buttonevent,omitempty"` } +type ButtonEvent int + +const ( + Hold ButtonEvent = iota + 1 + ShortRelease + DoublePress + TreeplePress + LongRelease +) + func (e *DeconzDevice) NewDeconzDevice(vdcdClient *vdcdapi.Client, deconzHost string, deconzPort int, deconzWebSocketPort int, deconzAPI string) *vdcdapi.Device { e.vdcdClient = vdcdClient @@ -122,20 +140,43 @@ func (e *DeconzDevice) NewDeconzDevice(vdcdClient *vdcdapi.Client, deconzHost st } if e.IsGroup { - device.ModelName = "Light Group" + // Group only allows for on/off -> basic switch, no dimming device.NewLightDevice(e.vdcdClient, fmt.Sprintf("%d", e.group.ID), false) + + device.ModelName = "Light Group" device.SetName(fmt.Sprintf("Group: %s", e.group.Name)) } + if e.IsSensor { + + log.Debugf("Adding sensor %s %s", e.sensor.Name, e.sensorButtonID) + + device.NewButtonDevice(e.vdcdClient, fmt.Sprintf("%s-%s", e.sensor.UniqueID, e.sensorButtonID)) + + device.SetName(fmt.Sprintf("%s / Button %d", e.sensor.Name, e.sensorButtonID+1)) + + device.ModelName = e.sensor.ModelID + device.ModelVersion = e.sensor.SWVersion + device.VendorName = e.sensor.ManufacturerName + + button := new(vdcdapi.Button) + button.Id = "button" //fmt.Sprint(e.sensorButtonID) + button.ButtonId = e.sensorButtonID + button.ButtonType = vdcdapi.SingleButton + button.Group = vdcdapi.YellowLightGroup + device.AddButton(*button) + + } + device.ConfigUrl = fmt.Sprintf("http://%s:%d", e.deconzHost, e.deconzPort) + device.SourceDevice = e e.originDevice = device e.vdcdClient.AddDevice(device) return device - } func (e *DeconzDevice) StartDiscovery(vdcdClient *vdcdapi.Client, deconzHost string, deconzPort int, deconcWebSockerPort int, deconzAPI string, enableGroups bool) { @@ -150,30 +191,32 @@ func (e *DeconzDevice) StartDiscovery(vdcdClient *vdcdapi.Client, deconzHost str log.Infof("Starting Deconz Device discovery on host %s\n", host) - // Lights - dl := deconzlight.New(host, e.deconzAPI) + // // Lights + // dl := deconzlight.New(host, e.deconzAPI) - allLights, _ := dl.GetAllLights() - for _, l := range allLights { + // allLights, _ := dl.GetAllLights() + // for _, l := range allLights { - if l.Type != "Configuration tool" { // filter this out - if *l.State.Reachable { // only available/reachable devices - deconzDevice := new(DeconzDevice) + // if l.Type != "Configuration tool" { // filter this out + // if *l.State.Reachable { // only available/reachable devices + // deconzDevice := new(DeconzDevice) - deconzDevice.IsLight = true - deconzDevice.light = l + // deconzDevice.IsLight = true + // deconzDevice.light = l - _, notfounderr := e.vdcdClient.GetDeviceByUniqueId(l.UniqueID) - if notfounderr != nil { - log.Debugf("Deconz Device not found in vcdc -> Adding \n") - deconzDevice.NewDeconzDevice(e.vdcdClient, e.deconzHost, e.deconzPort, e.deconzWebSocketPort, e.deconzAPI) - } + // log.Infof("Deconz Lights discovered: Name: %s, \n", l.Name) - e.allDeconzDevices = append(e.allDeconzDevices, *deconzDevice) - } - } + // _, notfounderr := e.vdcdClient.GetDeviceByUniqueId(l.UniqueID) + // if notfounderr != nil { + // log.Debugf("Deconz Device not found in vcdc -> Adding \n") + // deconzDevice.NewDeconzDevice(e.vdcdClient, e.deconzHost, e.deconzPort, e.deconzWebSocketPort, e.deconzAPI) + // } - } + // e.allDeconzDevices = append(e.allDeconzDevices, *deconzDevice) + // } + // } + + // } // Groups if enableGroups { @@ -187,6 +230,8 @@ func (e *DeconzDevice) StartDiscovery(vdcdClient *vdcdapi.Client, deconzHost str deconzDeviceGroup.IsGroup = true deconzDeviceGroup.group = g + log.Infof("Deconz Group discovered: Name: %s, \n", g.Name) + _, notfounderr := e.vdcdClient.GetDeviceByUniqueId(fmt.Sprint(g.ID)) if notfounderr != nil { log.Debugf("Deconz Device not found in vcdc -> Adding \n") @@ -199,6 +244,43 @@ func (e *DeconzDevice) StartDiscovery(vdcdClient *vdcdapi.Client, deconzHost str } } + // Sensors + ds := deconzsensor.New(host, e.deconzAPI) + + allSensors, _ := ds.GetAllSensors() + for _, sensor := range allSensors { + + if sensor.Type == "ZHASwitch" { + + log.Infof("Deconz Sensor discovered: Name: %s, Type: %s, \n", sensor.Name, sensor.Type) + + switch sensor.ModelID { + case "lumi.remote.b286opcn01": + sensor.Name = strings.Replace(sensor.Name, "OPPLE ", "", -1) + e.CreateButtonDevice(&sensor, 0) + e.CreateButtonDevice(&sensor, 1) + + case "lumi.remote.b486opcn01": + sensor.Name = strings.Replace(sensor.Name, "OPPLE ", "", -1) + // e.CreateButtonDevice(&sensor, 0) + // e.CreateButtonDevice(&sensor, 1) + // e.CreateButtonDevice(&sensor, 2) + // e.CreateButtonDevice(&sensor, 3) + + case "lumi.remote.b686opcn01": + sensor.Name = strings.Replace(sensor.Name, "OPPLE ", "", -1) + // e.CreateButtonDevice(&sensor, 0) + // e.CreateButtonDevice(&sensor, 1) + // e.CreateButtonDevice(&sensor, 2) + // e.CreateButtonDevice(&sensor, 3) + // e.CreateButtonDevice(&sensor, 4) + // e.CreateButtonDevice(&sensor, 5) + + } + + } + } + // WebSocket Handling for all Devices // no need for every device to open its own websocket connection log.Debugf("Call DeconZ Websocket Loop\n") @@ -207,6 +289,24 @@ func (e *DeconzDevice) StartDiscovery(vdcdClient *vdcdapi.Client, deconzHost str log.Debugf("Deconz Device Discovery finished\n") } +func (e *DeconzDevice) CreateButtonDevice(sensor *deconzsensor.Sensor, buttonID int) { + log.Infof("Deconz, Create ButtonDevice for %s, Button: %s, \n", sensor.Name, buttonID) + + deconzDeviceSensor := new(DeconzDevice) + deconzDeviceSensor.IsSensor = true + deconzDeviceSensor.sensor = *sensor + deconzDeviceSensor.sensorButtonID = buttonID + + _, notfounderr := e.vdcdClient.GetDeviceByUniqueId(fmt.Sprintf("%s-%s", sensor.UniqueID, buttonID)) + if notfounderr != nil { + log.Debugf("Deconz Device not found in vcdc -> Adding \n") + deconzDeviceSensor.NewDeconzDevice(e.vdcdClient, e.deconzHost, e.deconzPort, e.deconzWebSocketPort, e.deconzAPI) + } + + e.allDeconzDevices = append(e.allDeconzDevices, *deconzDeviceSensor) + +} + func (e *DeconzDevice) websocketLoop() { log.Debugln("Starting Deconz Websocket Loop") @@ -302,7 +402,7 @@ func (e *DeconzDevice) websocketReceiveHandler(connection *websocket.Conn) { for _, l := range e.allDeconzDevices { if l.IsLight { if fmt.Sprint(l.light.ID) == message.ID { - log.Infof("Deconz Websocket Light changed event for light %s\n", l.light.Name) + log.Infof("Deconz Websocket changed event for light %s\n", l.light.Name) l.lightStateChangedCallback(&message.State) break } @@ -315,12 +415,12 @@ func (e *DeconzDevice) websocketReceiveHandler(connection *websocket.Conn) { // Handling group Resources if message.Type == "event" && message.Resource == "groups" && message.Event == "changed" { - log.Debugln("Groups changed Event received") + log.Debugln("Group changed Event received") for _, l := range e.allDeconzDevices { if l.IsGroup { if fmt.Sprint(l.group.ID) == message.ID { - log.Debugln("Matching Deconz Group found for light changed event") + log.Infof("Deconz Websocker changed event for group %s\n", l.group.Name) l.groupStateChangedCallback(&message.State) break } @@ -330,6 +430,24 @@ func (e *DeconzDevice) websocketReceiveHandler(connection *websocket.Conn) { } } + + // Handling sensor Resources + if message.Type == "event" && message.Resource == "sensors" && message.Event == "changed" { + log.Debugln("Sensor changed Event received") + + for _, l := range e.allDeconzDevices { + if l.IsSensor { + if fmt.Sprint(l.sensor.ID) == message.ID { + log.Infof("Deconz Websocker changed event for sensor %s\n", l.sensor.Name) + l.sensorStateChangedCallback(&message.State) + break + } + + } + + } + + } } } @@ -421,6 +539,41 @@ func (e *DeconzDevice) groupStateChangedCallback(state *DeconzState) { } +func (e *DeconzDevice) sensorStateChangedCallback(state *DeconzState) { + + log.Debugf("sensorStateChangedCallback called for Device '%s'. State: '%+v'\n", e.sensor.Name, state) + + if state.ButtonEvent > 0 { + + // Get Button for which the event is for + button := int(math.Round(float64(state.ButtonEvent) / 1000)) + log.Debugf("Event for Device '%s' on Button %d\n", e.sensor.Name, button) + + if e.sensor.ModelID == "lumi.remote.b286opcn01" || + e.sensor.ModelID == "lumi.remote.b486opcn01" || + e.sensor.ModelID == "lumi.remote.b686opcn01" { + + var event ButtonEvent + event = ButtonEvent(state.ButtonEvent - (button * 1000)) + + switch event { + case Hold: + + case ShortRelease: + e.vdcdClient.SendButtonMessage() + + case DoublePress: + + case TreeplePress: + + case LongRelease: + + } + } + } + +} + func (e *DeconzDevice) TurnOn() { if e.IsLight { diff --git a/pkg/vdcdapi/client.go b/pkg/vdcdapi/client.go index 5047696..ccab9ee 100644 --- a/pkg/vdcdapi/client.go +++ b/pkg/vdcdapi/client.go @@ -296,6 +296,8 @@ func (e *Client) sendMessage(message interface{}) { payload, err := json.Marshal(message) + log.Debugf("Send Message. Raw: %s", string(payload)) + if err != nil { log.Errorln("Failed to Marshall object") return @@ -360,6 +362,22 @@ func (e *Client) SendSensorMessage(value float32, tag string, channelName string e.sendMessage(channelMessage) } +func (e *Client) SendButtonMessage(value float32, tag string, index int) { + channelMessageHeader := GenericMessageHeader{MessageType: "button"} + channelMessageFields := GenericDeviceMessageFields{Index: index, Tag: tag, Value: value} + channelMessage := GenericDeviceMessage{channelMessageHeader, channelMessageFields} + + payload, err := json.Marshal(channelMessage) + if err != nil { + log.Errorln("Failed to Marshall object", err.Error()) + return + } + + log.Debugf("Send Button Message: %s\n", string(payload)) + e.sendMessage(channelMessage) + +} + func (e *Client) GetDeviceByUniqueId(uniqueid string) (*Device, error) { for i := 0; i < len(e.devices); i++ { if e.devices[i].UniqueID == uniqueid { diff --git a/pkg/vdcdapi/device.go b/pkg/vdcdapi/device.go index 123bbf6..3ee972f 100644 --- a/pkg/vdcdapi/device.go +++ b/pkg/vdcdapi/device.go @@ -9,11 +9,16 @@ import ( func (e *Device) NewDevice(client *Client, uniqueID string) { e.UniqueID = uniqueID e.Tag = uniqueID - e.Output = BasicOutput e.client = client e.ModelName = e.client.modelName e.VendorName = e.client.vendorName +} + +func (e *Device) NewBasicSwitchDevice(client *Client, uniqueID string) { + e.NewDevice(client, uniqueID) + + e.Output = BasicOutput basicChannel := new(Channel) basicChannel.ChannelName = "basic_switch" @@ -22,9 +27,18 @@ func (e *Device) NewDevice(client *Client, uniqueID string) { e.AddChannel(*basicChannel) } -func (e *Device) NewLightDevice(client *Client, uniqueID string, dimmable bool) { +func (e *Device) NewButtonDevice(client *Client, uniqueID string) { e.NewDevice(client, uniqueID) + // Preconfigure to Light Group + e.Group = YellowLightGroup + e.ColorClass = YellowColorClassT + +} + +func (e *Device) NewLightDevice(client *Client, uniqueID string, dimmable bool) { + e.NewBasicSwitchDevice(client, uniqueID) + if dimmable { e.Output = LightOutput brightnessChannel := new(Channel) @@ -39,7 +53,7 @@ func (e *Device) NewLightDevice(client *Client, uniqueID string, dimmable bool) } func (e *Device) NewColorLightDevice(client *Client, uniqueID string) { - e.NewDevice(client, uniqueID) + e.NewBasicSwitchDevice(client, uniqueID) e.Output = ColorLightOutput @@ -69,7 +83,7 @@ func (e *Device) NewColorLightDevice(client *Client, uniqueID string) { } func (e *Device) NewCTLightDevice(client *Client, uniqueID string) { - e.NewDevice(client, uniqueID) + e.NewBasicSwitchDevice(client, uniqueID) e.Output = CtLightOutput @@ -125,6 +139,16 @@ func (e *Device) UpdateValue(newValue float32, channelName string, channelType C } +func (e *Device) ButtonEvent(value float32, id string) { + + for i := 0; i < len(e.Buttons); i++ { + if e.Buttons[i].Id == id { + e.client.SendButtonMessage(value, e.Tag, i) + } + } + +} + func (e *Device) UpdateSensorValue(newValue float32, sensorId string) { for i := 0; i < len(e.Sensors); i++ { diff --git a/pkg/vdcdapi/types.go b/pkg/vdcdapi/types.go index a95770d..8011995 100644 --- a/pkg/vdcdapi/types.go +++ b/pkg/vdcdapi/types.go @@ -272,12 +272,12 @@ type Device struct { Events map[string]Event `json:"events,omitempty"` Properties map[string]Property `json:"properties,omitempty"` - value float32 - client *Client - channel_cb func(message *GenericVDCDMessage, device *Device) - InitDone bool - SourceDevice interface{} - Channels []Channel + value float32 `json:"-"` + client *Client `json:"-"` + channel_cb func(message *GenericVDCDMessage, device *Device) `json:"-"` + InitDone bool `json:"-"` + SourceDevice interface{} `json:"-"` + Channels []Channel `json:"-"` } type Channel struct { From 4dc48b78212784ca0cadafbc3fb94a89b42e094e Mon Sep 17 00:00:00 2001 From: Sebastain Plattner Date: Wed, 22 Dec 2021 18:38:25 +0100 Subject: [PATCH 2/2] Adding deconz Sensor to dss --- .gitignore | 1 + go.mod | 7 +- go.sum | 15 +- pkg/discovery/deconz.go | 375 +++++---------------------------- pkg/discovery/deconz_group.go | 86 ++++++++ pkg/discovery/deconz_light.go | 112 ++++++++++ pkg/discovery/deconz_sensor.go | 188 +++++++++++++++++ pkg/vdcdapi/client.go | 12 +- pkg/vdcdapi/types.go | 6 +- 9 files changed, 463 insertions(+), 339 deletions(-) create mode 100644 pkg/discovery/deconz_group.go create mode 100644 pkg/discovery/deconz_light.go create mode 100644 pkg/discovery/deconz_sensor.go diff --git a/.gitignore b/.gitignore index becff44..dda8102 100644 --- a/.gitignore +++ b/.gitignore @@ -16,5 +16,6 @@ .vscode/ +vendor __debug_bin vdcd-bridge \ No newline at end of file diff --git a/go.mod b/go.mod index bb4e7f3..53926d6 100644 --- a/go.mod +++ b/go.mod @@ -4,11 +4,8 @@ go 1.16 require ( github.com/akamensky/argparse v1.3.1 - github.com/clagraff/argparse v1.0.1 github.com/eclipse/paho.mqtt.golang v1.3.5 - github.com/gorilla/websocket v1.4.2 // indirect - github.com/jurgen-kluft/go-conbee v0.0.0-20211124004556-1d2ff903ea59 // indirect - github.com/mattn/go-runewidth v0.0.13 // indirect - github.com/nsf/termbox-go v1.1.1 // indirect + github.com/gorilla/websocket v1.4.2 + github.com/jurgen-kluft/go-conbee v0.0.0-20211124004556-1d2ff903ea59 github.com/sirupsen/logrus v1.8.1 ) diff --git a/go.sum b/go.sum index 7d0f234..0f35331 100644 --- a/go.sum +++ b/go.sum @@ -1,9 +1,6 @@ -github.com/akamensky/argparse v1.3.0 h1:/y3NFl6Nhw6alHDWoI9tx3vSyrB5zRgs4y9jzHxtjjg= -github.com/akamensky/argparse v1.3.0/go.mod h1:S5kwC7IuDcEr5VeXtGPRVZ5o/FdhcMlQz4IZQuw64xA= github.com/akamensky/argparse v1.3.1 h1:kP6+OyvR0fuBH6UhbE6yh/nskrDEIQgEA1SUXDPjx4g= github.com/akamensky/argparse v1.3.1/go.mod h1:S5kwC7IuDcEr5VeXtGPRVZ5o/FdhcMlQz4IZQuw64xA= -github.com/clagraff/argparse v1.0.1 h1:gi6vlttsGJak58CuoKr+6TzNw+QwBZ1XG/jerh0FuZA= -github.com/clagraff/argparse v1.0.1/go.mod h1:esCjXvK1Cd4w0srd5+2YHpDp+XKvCCUBnZ8SQXjan0k= +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/eclipse/paho.mqtt.golang v1.3.5 h1:sWtmgNxYM9P2sP+xEItMozsR3w0cqZFlqnNN1bdl41Y= github.com/eclipse/paho.mqtt.golang v1.3.5/go.mod h1:eTzb4gxwwyWpqBUHGQZ4ABAV7+Jgm1PklsYT/eo8Hcc= @@ -11,17 +8,11 @@ github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0U github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/jurgen-kluft/go-conbee v0.0.0-20211124004556-1d2ff903ea59 h1:f9Gn7xzl2wfNdlJ4slukOmX8LhOs9XflRDbaQxA/Onk= github.com/jurgen-kluft/go-conbee v0.0.0-20211124004556-1d2ff903ea59/go.mod h1:nxv2+SfQy+RF9UzE0rnRpYJGVmBA1MQ45UxUgiOxdqg= -github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0= -github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= -github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4OSgU= -github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= -github.com/nsf/termbox-go v1.1.1 h1:nksUPLCb73Q++DwbYUBEglYBRPZyoXJdrj5L+TkjyZY= -github.com/nsf/termbox-go v1.1.1/go.mod h1:T0cTdVuOwf7pHQNtfhnEbzHbcNyCEcVU4YPpouCbVxo= +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/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= -github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE= github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= +github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/net v0.0.0-20200425230154-ff2c4b7c35a0 h1:Jcxah/M+oLZ/R4/z5RzfPzGbPXnVDPkEDtf2JnuxN+U= diff --git a/pkg/discovery/deconz.go b/pkg/discovery/deconz.go index 77c1afa..e47afd3 100644 --- a/pkg/discovery/deconz.go +++ b/pkg/discovery/deconz.go @@ -6,7 +6,6 @@ import ( "math" "os" "os/signal" - "strings" "time" "github.com/gorilla/websocket" @@ -33,10 +32,13 @@ type DeconzDevice struct { IsGroup bool group deconzgroup.Group - IsSensor bool - sensor deconzsensor.Sensor - sensorButtonID int + IsSensor bool + sensor deconzsensor.Sensor + // For when there are multiple buttons on one sensor + // sensorButtonId is the identifier to get the correct device + sensorButtonId int + // Array with all lights, groups, sensors allDeconzDevices []DeconzDevice done chan interface{} @@ -97,16 +99,6 @@ type DeconzState struct { ButtonEvent int `json:"buttonevent,omitempty"` } -type ButtonEvent int - -const ( - Hold ButtonEvent = iota + 1 - ShortRelease - DoublePress - TreeplePress - LongRelease -) - func (e *DeconzDevice) NewDeconzDevice(vdcdClient *vdcdapi.Client, deconzHost string, deconzPort int, deconzWebSocketPort int, deconzAPI string) *vdcdapi.Device { e.vdcdClient = vdcdClient @@ -115,67 +107,20 @@ func (e *DeconzDevice) NewDeconzDevice(vdcdClient *vdcdapi.Client, deconzHost st e.deconzWebSocketPort = deconzWebSocketPort e.deconzAPI = deconzAPI - device := new(vdcdapi.Device) - - device.SetChannelMessageCB(e.vcdcChannelCallback()) + var device *vdcdapi.Device if e.IsLight { - - if e.light.HasColor { - if e.light.State.ColorMode == "ct" { - device.NewCTLightDevice(e.vdcdClient, e.light.UniqueID) - } - if e.light.State.ColorMode == "hs" { - device.NewColorLightDevice(e.vdcdClient, e.light.UniqueID) - } - - } else { - device.NewLightDevice(e.vdcdClient, e.light.UniqueID, true) - } - - device.ModelName = e.light.ModelID - device.ModelVersion = e.light.SWVersion - device.SetName(e.light.Name) - + device = e.NewDeconzLightDevice() } if e.IsGroup { - - // Group only allows for on/off -> basic switch, no dimming - device.NewLightDevice(e.vdcdClient, fmt.Sprintf("%d", e.group.ID), false) - - device.ModelName = "Light Group" - device.SetName(fmt.Sprintf("Group: %s", e.group.Name)) - + device = e.NewDeconzGroupDevice() } if e.IsSensor { - - log.Debugf("Adding sensor %s %s", e.sensor.Name, e.sensorButtonID) - - device.NewButtonDevice(e.vdcdClient, fmt.Sprintf("%s-%s", e.sensor.UniqueID, e.sensorButtonID)) - - device.SetName(fmt.Sprintf("%s / Button %d", e.sensor.Name, e.sensorButtonID+1)) - - device.ModelName = e.sensor.ModelID - device.ModelVersion = e.sensor.SWVersion - device.VendorName = e.sensor.ManufacturerName - - button := new(vdcdapi.Button) - button.Id = "button" //fmt.Sprint(e.sensorButtonID) - button.ButtonId = e.sensorButtonID - button.ButtonType = vdcdapi.SingleButton - button.Group = vdcdapi.YellowLightGroup - device.AddButton(*button) - + device = e.NewDeconzSensorDevice() } - device.ConfigUrl = fmt.Sprintf("http://%s:%d", e.deconzHost, e.deconzPort) - device.SourceDevice = e - - e.originDevice = device - e.vdcdClient.AddDevice(device) - return device } @@ -189,176 +134,88 @@ func (e *DeconzDevice) StartDiscovery(vdcdClient *vdcdapi.Client, deconzHost str host := fmt.Sprintf("%s:%d", deconzHost, deconzPort) - log.Infof("Starting Deconz Device discovery on host %s\n", host) - - // // Lights - // dl := deconzlight.New(host, e.deconzAPI) - - // allLights, _ := dl.GetAllLights() - // for _, l := range allLights { - - // if l.Type != "Configuration tool" { // filter this out - // if *l.State.Reachable { // only available/reachable devices - // deconzDevice := new(DeconzDevice) + log.Infof("Starting Deconz device discovery on host %s\n", host) - // deconzDevice.IsLight = true - // deconzDevice.light = l - - // log.Infof("Deconz Lights discovered: Name: %s, \n", l.Name) - - // _, notfounderr := e.vdcdClient.GetDeviceByUniqueId(l.UniqueID) - // if notfounderr != nil { - // log.Debugf("Deconz Device not found in vcdc -> Adding \n") - // deconzDevice.NewDeconzDevice(e.vdcdClient, e.deconzHost, e.deconzPort, e.deconzWebSocketPort, e.deconzAPI) - // } - - // e.allDeconzDevices = append(e.allDeconzDevices, *deconzDevice) - // } - // } - - // } + // Lights + dl := deconzlight.New(host, e.deconzAPI) + allLights, _ := dl.GetAllLights() + for _, light := range allLights { + e.lightsDiscovery(light) + } // Groups if enableGroups { dg := deconzgroup.New(host, e.deconzAPI) - allGroups, _ := dg.GetAllGroups() - for _, g := range allGroups { - if len(g.Lights) > 0 { - - deconzDeviceGroup := new(DeconzDevice) - deconzDeviceGroup.IsGroup = true - deconzDeviceGroup.group = g - - log.Infof("Deconz Group discovered: Name: %s, \n", g.Name) - - _, notfounderr := e.vdcdClient.GetDeviceByUniqueId(fmt.Sprint(g.ID)) - if notfounderr != nil { - log.Debugf("Deconz Device not found in vcdc -> Adding \n") - deconzDeviceGroup.NewDeconzDevice(e.vdcdClient, e.deconzHost, e.deconzPort, e.deconzWebSocketPort, e.deconzAPI) - } - - e.allDeconzDevices = append(e.allDeconzDevices, *deconzDeviceGroup) - - } + for _, group := range allGroups { + e.groupsDiscovery(group) } } // Sensors ds := deconzsensor.New(host, e.deconzAPI) - allSensors, _ := ds.GetAllSensors() for _, sensor := range allSensors { - - if sensor.Type == "ZHASwitch" { - - log.Infof("Deconz Sensor discovered: Name: %s, Type: %s, \n", sensor.Name, sensor.Type) - - switch sensor.ModelID { - case "lumi.remote.b286opcn01": - sensor.Name = strings.Replace(sensor.Name, "OPPLE ", "", -1) - e.CreateButtonDevice(&sensor, 0) - e.CreateButtonDevice(&sensor, 1) - - case "lumi.remote.b486opcn01": - sensor.Name = strings.Replace(sensor.Name, "OPPLE ", "", -1) - // e.CreateButtonDevice(&sensor, 0) - // e.CreateButtonDevice(&sensor, 1) - // e.CreateButtonDevice(&sensor, 2) - // e.CreateButtonDevice(&sensor, 3) - - case "lumi.remote.b686opcn01": - sensor.Name = strings.Replace(sensor.Name, "OPPLE ", "", -1) - // e.CreateButtonDevice(&sensor, 0) - // e.CreateButtonDevice(&sensor, 1) - // e.CreateButtonDevice(&sensor, 2) - // e.CreateButtonDevice(&sensor, 3) - // e.CreateButtonDevice(&sensor, 4) - // e.CreateButtonDevice(&sensor, 5) - - } - - } + e.sensorDiscovery(sensor) } // WebSocket Handling for all Devices // no need for every device to open its own websocket connection - log.Debugf("Call DeconZ Websocket Loop\n") go e.websocketLoop() - log.Debugf("Deconz Device Discovery finished\n") -} - -func (e *DeconzDevice) CreateButtonDevice(sensor *deconzsensor.Sensor, buttonID int) { - log.Infof("Deconz, Create ButtonDevice for %s, Button: %s, \n", sensor.Name, buttonID) - - deconzDeviceSensor := new(DeconzDevice) - deconzDeviceSensor.IsSensor = true - deconzDeviceSensor.sensor = *sensor - deconzDeviceSensor.sensorButtonID = buttonID - - _, notfounderr := e.vdcdClient.GetDeviceByUniqueId(fmt.Sprintf("%s-%s", sensor.UniqueID, buttonID)) - if notfounderr != nil { - log.Debugf("Deconz Device not found in vcdc -> Adding \n") - deconzDeviceSensor.NewDeconzDevice(e.vdcdClient, e.deconzHost, e.deconzPort, e.deconzWebSocketPort, e.deconzAPI) - } - - e.allDeconzDevices = append(e.allDeconzDevices, *deconzDeviceSensor) - + log.Debugf("Deconz, Device Discovery finished\n") } func (e *DeconzDevice) websocketLoop() { - log.Debugln("Starting Deconz Websocket Loop") + log.Debugln("Deconz, Starting Deconz Websocket Loop") e.done = make(chan interface{}) // Channel to indicate that the receiverHandler is done e.interrupt = make(chan os.Signal) // Channel to listen for interrupt signal to terminate gracefully signal.Notify(e.interrupt, os.Interrupt) // Notify the interrupt channel for SIGINT socketUrl := fmt.Sprintf("ws://%s:%d", e.deconzHost, e.deconzWebSocketPort) - log.Debugf("Trying to connect to Deconz Websocket %s\n", socketUrl) + log.Debugf("Deconz, Trying to connect to Deconz Websocket %s\n", socketUrl) conn, _, err := websocket.DefaultDialer.Dial(socketUrl, nil) if err != nil { - log.Fatal("Error connecting to Websocket Server:", err) + log.Fatal("Deconz, Error connecting to Websocket Server:", err) } - log.Debugln("Connected to Deconz websocket") + log.Debugln("Deconz, Connected to Deconz websocket") defer conn.Close() - log.Debugln("Calling Deconz Websocket receive handler") go e.websocketReceiveHandler(conn) // Our main loop for the client // We send our relevant packets here - log.Debugln("Starting Deconz Websocket client main loop") + log.Debugln("Deconz, Starting Deconz Websocket client main loop") for { select { case <-time.After(time.Duration(1) * time.Millisecond * 1000): // Send an echo packet every second err := conn.WriteMessage(websocket.TextMessage, []byte("Hello from vdcd-brige!")) if err != nil { - log.Println("Error during writing to websocket:", err) + log.Println("Deconz, Error during writing to websocket:", err) return } case <-e.interrupt: // We received a SIGINT (Ctrl + C). Terminate gracefully... - log.Println("Received SIGINT interrupt signal. Closing all pending connections") + log.Println("Deconz, Received SIGINT interrupt signal. Closing all pending connections") // Close our websocket connection err := conn.WriteMessage(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseNormalClosure, "")) if err != nil { - log.Println("Error during closing websocket:", err) + log.Println("Deconz, Error during closing websocket:", err) return } select { case <-e.done: - log.Println("Receiver Channel Closed!") + log.Println("Deconz, Receiver Channel Closed!") case <-time.After(time.Duration(1) * time.Second): - log.Println("Timeout in closing receiving channel.") + log.Println("Deconz, Timeout in closing receiving channel.") } - log.Debugln("Returning from Deconz Websocket Main loop") return } } @@ -366,17 +223,17 @@ func (e *DeconzDevice) websocketLoop() { func (e *DeconzDevice) websocketReceiveHandler(connection *websocket.Conn) { - log.Debugln("Starting Deconz Websocket receive handler") + log.Debugln("Deconz, Starting Deconz Websocket receive handler") defer close(e.done) for { _, msg, err := connection.ReadMessage() if err != nil { - log.Println("Error in Deconz Websocket Message receive:", err) + log.Println("Deconz, Error in Deconz Websocket Message receive:", err) return } - log.Debugf("Received Deconz Websocket Message. Raw Message: %s\n", msg) + log.Debugf("Deconz, Received Deconz Websocket Message. Raw Message: %s\n", msg) var message DeconzWebSocketMessage err = json.Unmarshal(msg, &message) @@ -397,7 +254,6 @@ func (e *DeconzDevice) websocketReceiveHandler(connection *websocket.Conn) { message.State.Reachable != nil || message.State.ColorMode != "" || message.State.ColorLoopSpeed != nil { - log.Debugln("Deconz Websocket Lights changed Event received") for _, l := range e.allDeconzDevices { if l.IsLight { @@ -415,12 +271,11 @@ func (e *DeconzDevice) websocketReceiveHandler(connection *websocket.Conn) { // Handling group Resources if message.Type == "event" && message.Resource == "groups" && message.Event == "changed" { - log.Debugln("Group changed Event received") for _, l := range e.allDeconzDevices { if l.IsGroup { if fmt.Sprint(l.group.ID) == message.ID { - log.Infof("Deconz Websocker changed event for group %s\n", l.group.Name) + log.Infof("Deconz Websocket changed event for group %s\n", l.group.Name) l.groupStateChangedCallback(&message.State) break } @@ -433,20 +288,16 @@ func (e *DeconzDevice) websocketReceiveHandler(connection *websocket.Conn) { // Handling sensor Resources if message.Type == "event" && message.Resource == "sensors" && message.Event == "changed" { - log.Debugln("Sensor changed Event received") for _, l := range e.allDeconzDevices { if l.IsSensor { if fmt.Sprint(l.sensor.ID) == message.ID { - log.Infof("Deconz Websocker changed event for sensor %s\n", l.sensor.Name) - l.sensorStateChangedCallback(&message.State) - break + // Send to all devices which handles this sensor + log.Infof("Deconz, Websocket changed event for sensor %s\n", l.sensor.Name) + l.sensorWebsocketCallback(&message.State) } - } - } - } } } @@ -454,17 +305,14 @@ func (e *DeconzDevice) websocketReceiveHandler(connection *websocket.Conn) { // Apply update from dss to deconz device func (e *DeconzDevice) SetValue(value float32, channelName string, channelType vdcdapi.ChannelTypeType) { - log.Infof("Set Value for Deconz Device %s to %f on Channel '%s' \n", e.light.Name, value, channelName) + log.Infof("Deconz, Set Value for Deconz Device %s to %f on Channel '%s' \n", e.light.Name, value, channelName) // Also sync the state with originDevice e.originDevice.SetValue(value, channelName) switch channelName { - case "basic_switch": - brightness := float32(math.Round(float64(value))) - e.SetBrightness(brightness) - case "brightness": + case "basic_switch", "brightness": brightness := float32(math.Round(float64(value))) e.SetBrightness(brightness) case "hue": @@ -473,131 +321,43 @@ func (e *DeconzDevice) SetValue(value float32, channelName string, channelType v e.SetSaturation(value) case "colortemp": e.SetColorTemp(value) - } - } func (e *DeconzDevice) vcdcChannelCallback() func(message *vdcdapi.GenericVDCDMessage, device *vdcdapi.Device) { var f func(message *vdcdapi.GenericVDCDMessage, device *vdcdapi.Device) = func(message *vdcdapi.GenericVDCDMessage, device *vdcdapi.Device) { - log.Debugf("vcdcCallBack called for Device %s\n", device.UniqueID) + log.Debugf("Deconz, vcdcCallBack called for Device %s\n", device.UniqueID) e.SetValue(message.Value, message.ChannelName, message.ChannelType) - } return f } -func (e *DeconzDevice) lightStateChangedCallback(state *DeconzState) { - - log.Debugf("lightStateChangedCallback called for Device '%s'. State: '%+v'\n", e.light.Name, state) - - if state.Bri != nil { - log.Debugf("lightStateChangedCallback: set Brightness to %d\n", *state.Bri) - bri_converted := float32(math.Round(float64(*state.Bri) / 255 * 100)) - e.originDevice.UpdateValue(float32(bri_converted), "brightness", vdcdapi.BrightnessType) - } - - if state.CT != nil { - log.Debugf("lightStateChangedCallback: set CT to %d\n", *state.CT) - e.originDevice.UpdateValue(float32(*state.CT), "colortemp", vdcdapi.ColorTemperatureType) - } - - // if state.Sat != nil { - // log.Debugf("lightStateChangedCallback: set Saturation to %d\n", *state.Sat) - // e.originDevice.UpdateValue(float32(*state.Sat), "saturation", vdcdapi.SaturationType) - // } - - // if state.Hue != nil { - // log.Debugf("lightStateChangedCallback: set Hue to %d\n", *state.Hue) - // e.originDevice.UpdateValue(float32(*state.Hue), "hue", vdcdapi.HueType) - // } - - // if !*state.On { - // log.Debugf("lightStateChangedCallback: state off, set Brightness to 0\n") - // e.originDevice.UpdateValue(0, "basic_switch", vdcdapi.UndefinedType) - // } - -} - -func (e *DeconzDevice) groupStateChangedCallback(state *DeconzState) { - - log.Debugf("groupStateChangedCallback called for Device '%s'. State: '%+v'\n", e.light.Name, state) - - if state.AllOn { - e.originDevice.UpdateValue(100, "basic_switch", vdcdapi.UndefinedType) - } - - if state.AnyOn { - e.originDevice.UpdateValue(100, "basic_switch", vdcdapi.UndefinedType) - } - - if state.AnyOn == false { - e.originDevice.UpdateValue(0, "basic_switch", vdcdapi.UndefinedType) - } - -} - -func (e *DeconzDevice) sensorStateChangedCallback(state *DeconzState) { - - log.Debugf("sensorStateChangedCallback called for Device '%s'. State: '%+v'\n", e.sensor.Name, state) - - if state.ButtonEvent > 0 { - - // Get Button for which the event is for - button := int(math.Round(float64(state.ButtonEvent) / 1000)) - log.Debugf("Event for Device '%s' on Button %d\n", e.sensor.Name, button) - - if e.sensor.ModelID == "lumi.remote.b286opcn01" || - e.sensor.ModelID == "lumi.remote.b486opcn01" || - e.sensor.ModelID == "lumi.remote.b686opcn01" { - - var event ButtonEvent - event = ButtonEvent(state.ButtonEvent - (button * 1000)) - - switch event { - case Hold: - - case ShortRelease: - e.vdcdClient.SendButtonMessage() - - case DoublePress: - - case TreeplePress: - - case LongRelease: - - } - } - } - -} - func (e *DeconzDevice) TurnOn() { if e.IsLight { e.light.State.SetOn(true) - e.setLightState() } if e.IsGroup { e.group.Action.SetOn(true) - e.setGroupState() } + + e.setState() } func (e *DeconzDevice) TurnOff() { if e.IsLight { e.light.State.SetOn(false) - e.setLightState() } if e.IsGroup { e.group.Action.SetOn(false) - e.setGroupState() } + + e.setState() } func (e *DeconzDevice) SetBrightness(brightness float32) { @@ -612,7 +372,6 @@ func (e *DeconzDevice) SetBrightness(brightness float32) { bri_converted := uint8(math.Round(float64(brightness) / 100 * 255)) e.light.State.Bri = &bri_converted - e.setLightState() } if e.IsGroup { @@ -624,9 +383,9 @@ func (e *DeconzDevice) SetBrightness(brightness float32) { bri_converted := uint8(math.Round(float64(brightness) / 100 * 255)) e.group.Action.Bri = &bri_converted - - e.setGroupState() } + + e.setState() } func (e *DeconzDevice) SetColorTemp(ct float32) { @@ -635,13 +394,13 @@ func (e *DeconzDevice) SetColorTemp(ct float32) { if e.IsLight { e.light.State.CT = &converted - e.setLightState() } if e.IsGroup { e.group.Action.CT = &converted - e.setGroupState() } + + e.setState() } func (e *DeconzDevice) SetHue(hue float32) { @@ -649,13 +408,13 @@ func (e *DeconzDevice) SetHue(hue float32) { converted := uint16(hue) if e.IsLight { e.light.State.Hue = &converted - e.setLightState() } if e.IsGroup { e.group.Action.Hue = &converted - e.setGroupState() } + + e.setState() } func (e *DeconzDevice) SetSaturation(saturation float32) { @@ -664,42 +423,22 @@ func (e *DeconzDevice) SetSaturation(saturation float32) { if e.IsLight { e.light.State.Sat = &converted - e.setLightState() } if e.IsGroup { e.group.Action.Sat = &converted - e.setGroupState() } + e.setState() } -func (e *DeconzDevice) setLightState() { +func (e *DeconzDevice) setState() { - state := strings.Replace(e.light.State.String(), "\n", ",", -1) - state = strings.Replace(state, " ", "", -1) - - log.Infof("Deconz call SetLightState with state (%s) for Light with id %d\n", state, e.light.ID) - - conbeehost := fmt.Sprintf("%s:%d", e.deconzHost, e.deconzPort) - ll := deconzlight.New(conbeehost, e.deconzAPI) - _, err := ll.SetLightState(e.light.ID, &e.light.State) - if err != nil { - log.Debugln("SetLightState Error", err) + if e.IsLight { + e.setLightState() } -} - -func (e *DeconzDevice) setGroupState() { - - state := strings.Replace(e.group.Action.String(), "\n", ",", -1) - state = strings.Replace(state, " ", "", -1) - log.Infof("Deconz call SetGroupState with state (%s) for Light with id %d\n", state, e.group.ID) - - conbeehost := fmt.Sprintf("%s:%d", e.deconzHost, e.deconzPort) - ll := deconzgroup.New(conbeehost, e.deconzAPI) - _, err := ll.SetGroupState(e.light.ID, e.group.Action) - if err != nil { - log.Debugln("SetGroupState Error", err) + if e.IsGroup { + e.setGroupState() } } diff --git a/pkg/discovery/deconz_group.go b/pkg/discovery/deconz_group.go new file mode 100644 index 0000000..1a9c708 --- /dev/null +++ b/pkg/discovery/deconz_group.go @@ -0,0 +1,86 @@ +package discovery + +import ( + "fmt" + "strings" + + deconzgroup "github.com/jurgen-kluft/go-conbee/groups" + log "github.com/sirupsen/logrus" + "github.com/splattner/vdcd-bridge/pkg/vdcdapi" +) + +func (e *DeconzDevice) NewDeconzGroupDevice() *vdcdapi.Device { + + device := new(vdcdapi.Device) + + device.SetChannelMessageCB(e.vcdcChannelCallback()) + + // Group only allows for on/off -> basic switch, no dimming + device.NewLightDevice(e.vdcdClient, fmt.Sprintf("%d", e.group.ID), false) + + device.ModelName = "Light Group" + device.SetName(fmt.Sprintf("Group: %s", e.group.Name)) + + device.ConfigUrl = fmt.Sprintf("http://%s:%d", e.deconzHost, e.deconzPort) + device.SourceDevice = e + + e.originDevice = device + e.vdcdClient.AddDevice(device) + + return device +} + +func (e *DeconzDevice) groupsDiscovery(group deconzgroup.Group) { + + if len(group.Lights) > 0 { + + deconzDeviceGroup := new(DeconzDevice) + deconzDeviceGroup.IsGroup = true + deconzDeviceGroup.group = group + + log.Infof("Deconz, Group discovered: Name: %s, \n", group.Name) + + _, notfounderr := e.vdcdClient.GetDeviceByUniqueId(fmt.Sprint(group.ID)) + if notfounderr != nil { + log.Debugf("Deconz, Device not found in vcdc -> Adding \n") + deconzDeviceGroup.NewDeconzDevice(e.vdcdClient, e.deconzHost, e.deconzPort, e.deconzWebSocketPort, e.deconzAPI) + } + + e.allDeconzDevices = append(e.allDeconzDevices, *deconzDeviceGroup) + + } + +} + +func (e *DeconzDevice) groupStateChangedCallback(state *DeconzState) { + + log.Debugf("Deconz, groupStateChangedCallback called for Device '%s'. State: '%+v'\n", e.light.Name, state) + + if state.AllOn { + e.originDevice.UpdateValue(100, "basic_switch", vdcdapi.UndefinedType) + } + + if state.AnyOn { + e.originDevice.UpdateValue(100, "basic_switch", vdcdapi.UndefinedType) + } + + if !state.AnyOn { + e.originDevice.UpdateValue(0, "basic_switch", vdcdapi.UndefinedType) + } + +} + +func (e *DeconzDevice) setGroupState() { + + state := strings.Replace(e.group.Action.String(), "\n", ",", -1) + state = strings.Replace(state, " ", "", -1) + + log.Infof("Deconz, call SetGroupState with state (%s) for Light with id %d\n", state, e.group.ID) + + conbeehost := fmt.Sprintf("%s:%d", e.deconzHost, e.deconzPort) + ll := deconzgroup.New(conbeehost, e.deconzAPI) + _, err := ll.SetGroupState(e.light.ID, e.group.Action) + if err != nil { + log.Debugln("Deconz, SetGroupState Error", err) + } +} diff --git a/pkg/discovery/deconz_light.go b/pkg/discovery/deconz_light.go new file mode 100644 index 0000000..ac378bf --- /dev/null +++ b/pkg/discovery/deconz_light.go @@ -0,0 +1,112 @@ +package discovery + +import ( + "fmt" + "math" + "strings" + + deconzlight "github.com/jurgen-kluft/go-conbee/lights" + log "github.com/sirupsen/logrus" + "github.com/splattner/vdcd-bridge/pkg/vdcdapi" +) + +func (e *DeconzDevice) NewDeconzLightDevice() *vdcdapi.Device { + + device := new(vdcdapi.Device) + + device.SetChannelMessageCB(e.vcdcChannelCallback()) + + if e.light.HasColor { + if e.light.State.ColorMode == "ct" { + device.NewCTLightDevice(e.vdcdClient, e.light.UniqueID) + } + if e.light.State.ColorMode == "hs" { + device.NewColorLightDevice(e.vdcdClient, e.light.UniqueID) + } + + } else { + device.NewLightDevice(e.vdcdClient, e.light.UniqueID, true) + } + + device.ModelName = e.light.ModelID + device.ModelVersion = e.light.SWVersion + device.SetName(e.light.Name) + + device.ConfigUrl = fmt.Sprintf("http://%s:%d", e.deconzHost, e.deconzPort) + device.SourceDevice = e + + e.originDevice = device + e.vdcdClient.AddDevice(device) + + return device +} + +func (e *DeconzDevice) lightsDiscovery(light deconzlight.Light) { + + if light.Type != "Configuration tool" { // filter this out + if *light.State.Reachable { // only available/reachable devices + deconzDevice := new(DeconzDevice) + + deconzDevice.IsLight = true + deconzDevice.light = light + + log.Infof("Deconz, Lights discovered: Name: %s, \n", light.Name) + + _, notfounderr := e.vdcdClient.GetDeviceByUniqueId(light.UniqueID) + if notfounderr != nil { + log.Debugf("Deconz, Device not found in vcdc -> Adding \n") + deconzDevice.NewDeconzDevice(e.vdcdClient, e.deconzHost, e.deconzPort, e.deconzWebSocketPort, e.deconzAPI) + } + + e.allDeconzDevices = append(e.allDeconzDevices, *deconzDevice) + } + } + +} + +func (e *DeconzDevice) lightStateChangedCallback(state *DeconzState) { + + log.Debugf("Deconz, lightStateChangedCallback called for Device '%s'. State: '%+v'\n", e.light.Name, state) + + if state.Bri != nil { + log.Debugf("Deconz, lightStateChangedCallback: set Brightness to %d\n", *state.Bri) + bri_converted := float32(math.Round(float64(*state.Bri) / 255 * 100)) + e.originDevice.UpdateValue(float32(bri_converted), "brightness", vdcdapi.BrightnessType) + } + + if state.CT != nil { + log.Debugf("Deconz, lightStateChangedCallback: set CT to %d\n", *state.CT) + e.originDevice.UpdateValue(float32(*state.CT), "colortemp", vdcdapi.ColorTemperatureType) + } + + // if state.Sat != nil { + // log.Debugf("lightStateChangedCallback: set Saturation to %d\n", *state.Sat) + // e.originDevice.UpdateValue(float32(*state.Sat), "saturation", vdcdapi.SaturationType) + // } + + // if state.Hue != nil { + // log.Debugf("lightStateChangedCallback: set Hue to %d\n", *state.Hue) + // e.originDevice.UpdateValue(float32(*state.Hue), "hue", vdcdapi.HueType) + // } + + // if !*state.On { + // log.Debugf("lightStateChangedCallback: state off, set Brightness to 0\n") + // e.originDevice.UpdateValue(0, "basic_switch", vdcdapi.UndefinedType) + // } + +} + +func (e *DeconzDevice) setLightState() { + + state := strings.Replace(e.light.State.String(), "\n", ",", -1) + state = strings.Replace(state, " ", "", -1) + + log.Infof("Deconz, call SetLightState with state (%s) for Light with id %d\n", state, e.light.ID) + + conbeehost := fmt.Sprintf("%s:%d", e.deconzHost, e.deconzPort) + ll := deconzlight.New(conbeehost, e.deconzAPI) + _, err := ll.SetLightState(e.light.ID, &e.light.State) + if err != nil { + log.Debugln("Deconz, SetLightState Error", err) + } +} diff --git a/pkg/discovery/deconz_sensor.go b/pkg/discovery/deconz_sensor.go new file mode 100644 index 0000000..e9ebee3 --- /dev/null +++ b/pkg/discovery/deconz_sensor.go @@ -0,0 +1,188 @@ +package discovery + +import ( + "fmt" + "math" + "strings" + + deconzsensor "github.com/jurgen-kluft/go-conbee/sensors" + log "github.com/sirupsen/logrus" + "github.com/splattner/vdcd-bridge/pkg/vdcdapi" +) + +type ButtonEvent int + +// http://developer.digitalstrom.org/Architecture/ds-basics.pdf +const ( + Hold ButtonEvent = iota + 1 + ShortRelease + LongRelease + DoublePress + TreeplePress +) + +const ( + // milisecs + SingleTip int = 150 + SingleClick int = 50 +) + +func (e *DeconzDevice) NewDeconzSensorDevice() *vdcdapi.Device { + log.Debugf("Deconz, Adding sensor %s for Button %d", e.sensor.Name, e.sensorButtonId) + + device := new(vdcdapi.Device) + device.NewButtonDevice(e.vdcdClient, e.getUniqueId()) + + device.SetChannelMessageCB(e.vcdcChannelCallback()) + + device.SetName(e.getName()) + device.ModelName = e.sensor.ModelID + device.ModelVersion = e.sensor.SWVersion + device.VendorName = e.sensor.ManufacturerName + + // Add Buttons depending on the ModelId + switch e.sensor.ModelID { + case "lumi.remote.b286opcn01", "lumi.remote.b486opcn01", "lumi.remote.b686opcn01": + // this is called for all buttons on these sensors + // so it will create a individual device for each button on the sensor + + button := new(vdcdapi.Button) + button.Id = fmt.Sprintf("button%d", e.sensorButtonId+1) + button.ButtonId = e.sensorButtonId + button.ButtonType = vdcdapi.SingleButton + button.Group = vdcdapi.YellowLightGroup + button.LocalButton = false + + device.AddButton(*button) + + } + + device.ConfigUrl = fmt.Sprintf("http://%s:%d", e.deconzHost, e.deconzPort) + device.SourceDevice = e + + e.originDevice = device + e.vdcdClient.AddDevice(device) + + return device +} + +func (e *DeconzDevice) getUniqueId() string { + uniqueID := fmt.Sprintf("%s-%d", e.sensor.UniqueID, e.sensorButtonId) + return uniqueID +} + +func (e *DeconzDevice) getName() string { + name := fmt.Sprintf("%s Button %d", e.sensor.Name, e.sensorButtonId+1) + return name +} + +func (e *DeconzDevice) sensorDiscovery(sensor deconzsensor.Sensor) { + + // Call Type specifiv Discovery function + // See https://dresden-elektronik.github.io/deconz-rest-doc/endpoints/sensors/#supported-sensor-types-and-states + // for available types + switch sensor.Type { + case "ZHASwitch": + e.ZHASwitchSensorDiscovery(sensor) + } + +} + +func (e *DeconzDevice) ZHASwitchSensorDiscovery(sensor deconzsensor.Sensor) { + log.Infof("Deconz, ZHASwitch discovered: Name: %s Model: %s\n", sensor.Name, sensor.ModelID) + + switch sensor.ModelID { + case "lumi.remote.b286opcn01": + sensor.Name = strings.Replace(sensor.Name, "OPPLE ", "", -1) + e.CreateButtonDevice(sensor, 0) + e.CreateButtonDevice(sensor, 1) + + case "lumi.remote.b486opcn01": + sensor.Name = strings.Replace(sensor.Name, "OPPLE ", "", -1) + e.CreateButtonDevice(sensor, 0) + e.CreateButtonDevice(sensor, 1) + e.CreateButtonDevice(sensor, 2) + e.CreateButtonDevice(sensor, 3) + + case "lumi.remote.b686opcn01": + sensor.Name = strings.Replace(sensor.Name, "OPPLE ", "", -1) + e.CreateButtonDevice(sensor, 0) + e.CreateButtonDevice(sensor, 1) + e.CreateButtonDevice(sensor, 2) + e.CreateButtonDevice(sensor, 3) + e.CreateButtonDevice(sensor, 4) + e.CreateButtonDevice(sensor, 5) + + } + +} + +func (e *DeconzDevice) CreateButtonDevice(sensor deconzsensor.Sensor, buttonId int) { + log.Infof("Deconz, Create ButtonDevice for %s, Button: %d \n", sensor.Name, buttonId) + + deconzDeviceSensor := new(DeconzDevice) + deconzDeviceSensor.IsSensor = true + deconzDeviceSensor.sensor = sensor + deconzDeviceSensor.sensorButtonId = buttonId // This is used for the uniqueId + + _, notfounderr := e.vdcdClient.GetDeviceByUniqueId(e.getUniqueId()) + if notfounderr != nil { + log.Debugf("Deconz, Device not found in vcdc -> Adding \n") + deconzDeviceSensor.NewDeconzDevice(e.vdcdClient, e.deconzHost, e.deconzPort, e.deconzWebSocketPort, e.deconzAPI) + } + + e.allDeconzDevices = append(e.allDeconzDevices, *deconzDeviceSensor) + +} + +func (e *DeconzDevice) sensorWebsocketCallback(state *DeconzState) { + + log.Debugf("Deconz, sensorStateChangedCallback called for Device '%s'. State: '%+v'\n", e.getName(), state) + + // Only when there is a ButtonEvent + if state.ButtonEvent > 0 { + // Get Button for which the event is for + // the 4 Digit is the Button Number + + button := int(math.Round(float64(state.ButtonEvent) / 1000)) + + if e.sensorButtonId != button-1 { + // Its not for this device + // The same sensor is used by multiple devices + // as each button of a sensor has its one device + log.Debugf("Deconz, Event %d is not for this Device '%s' on Button %d\n", state.ButtonEvent, e.getName(), button) + return + } + + log.Debugf("Deconz, Event %d is for this Device '%s' on Button %d\n", state.ButtonEvent, e.getName(), button) + + switch e.sensor.ModelID { + case "lumi.remote.b286opcn01", "lumi.remote.b486opcn01", "lumi.remote.b686opcn01": + // the first digit is the event + var event ButtonEvent = ButtonEvent(state.ButtonEvent - (button * 1000)) + log.Debugf("Deconz, Event %d -> %d for Device '%s' on Button %d\n", state.ButtonEvent, event, e.sensor.Name, button) + + switch event { + case Hold: + log.Debugf("Deconz, Event Hold for Device '%s' on Button %d\n", e.sensor.Name, button) + e.vdcdClient.SendButtonMessage(1, e.originDevice.Tag, 0) + + case ShortRelease: + log.Debugf("Deconz, Event ShortRelease for Device '%s' on Button %d\n", e.sensor.Name, button) + e.vdcdClient.SendButtonMessage(float32(SingleTip), e.originDevice.Tag, 0) + + case DoublePress: + log.Debugf("Deconz, Event DoublePress for Device '%s' on Button %d\n", e.sensor.Name, button) + + case TreeplePress: + log.Debugf("Deconz, Event TreeplePress for Device '%s' on Button %d\n", e.sensor.Name, button) + + case LongRelease: + log.Debugf("Deconz, Event LongRelease for Device '%s' on Button %d\n", e.sensor.Name, button) + e.vdcdClient.SendButtonMessage(0, e.originDevice.Tag, 0) + + } + } + } + +} diff --git a/pkg/vdcdapi/client.go b/pkg/vdcdapi/client.go index ccab9ee..8797a29 100644 --- a/pkg/vdcdapi/client.go +++ b/pkg/vdcdapi/client.go @@ -296,7 +296,7 @@ func (e *Client) sendMessage(message interface{}) { payload, err := json.Marshal(message) - log.Debugf("Send Message. Raw: %s", string(payload)) + //log.Debugf("Send Message. Raw: %s", string(payload)) if err != nil { log.Errorln("Failed to Marshall object") @@ -388,6 +388,16 @@ func (e *Client) GetDeviceByUniqueId(uniqueid string) (*Device, error) { return nil, errors.New(("Device not found")) } +func (e *Client) GetDeviceByUniqueIdAndSubDeviceIndex(uniqueid string, subDeviceIndex int) (*Device, error) { + for i := 0; i < len(e.devices); i++ { + if e.devices[i].UniqueID == uniqueid && e.devices[i].SubDeviceIndex == fmt.Sprintf("%d", subDeviceIndex) { + return e.devices[i], nil + } + } + + return nil, errors.New(("Device not found")) +} + func (e *Client) GetDeviceByTag(tag string) (*Device, error) { for i := 0; i < len(e.devices); i++ { if e.devices[i].Tag == tag { diff --git a/pkg/vdcdapi/types.go b/pkg/vdcdapi/types.go index 8011995..35f525b 100644 --- a/pkg/vdcdapi/types.go +++ b/pkg/vdcdapi/types.go @@ -218,9 +218,9 @@ type GenericDeviceMessageFields struct { Tag string `json:"tag,omitempty"` Text string `json:"text,omitempty"` Index int `json:"index"` - ChannelName string `json:"id"` + ChannelName string `json:"id,omitempty"` Value float32 `json:"value"` - ChannelType ChannelTypeType `json:"type"` + ChannelType ChannelTypeType `json:"type,omitempty"` } type GenericDeviceMessage struct { @@ -236,7 +236,7 @@ type DeviceInitMessage struct { type Device struct { Tag string `json:"tag,omitempty"` UniqueID string `json:"uniqueid,omitempty"` - SubDeviceIndex int `json:"subdeviceindex,omitempty"` + SubDeviceIndex string `json:"subdeviceindex,omitempty"` ColorClass ColorClassType `json:"colorclass,omitempty"` Group GroupType `json:"group,omitempty"` Output OutputType `json:"output,omitempty"`