From 20498e93ebe0810f659ca91ff760274062b8ec8f Mon Sep 17 00:00:00 2001 From: Sebastian Plattner Date: Sun, 24 Sep 2023 18:30:47 +0200 Subject: [PATCH] Deconz sensor handling --- pkg/client/deconzclient.go | 289 ++++++++++++++++++------------- pkg/deconz/types.go | 5 +- pkg/integration/advertisement.go | 2 +- pkg/integration/events.go | 2 +- pkg/integration/integration.go | 10 +- pkg/integration/register.go | 8 +- 6 files changed, 182 insertions(+), 134 deletions(-) diff --git a/pkg/client/deconzclient.go b/pkg/client/deconzclient.go index d2336ae..e0f583b 100644 --- a/pkg/client/deconzclient.go +++ b/pkg/client/deconzclient.go @@ -20,6 +20,8 @@ type DeconzClient struct { func NewDeconzClient(i *integration.Integration) *DeconzClient { client := DeconzClient{} + i.Config["ignoreEntitySubscription"] = true + client.IntegrationDriver = i // Start without a connection client.DeviceState = integration.DisconnectedDeviceState @@ -185,168 +187,211 @@ func (c *DeconzClient) configureDeconz() { } -func (c *DeconzClient) handleNewDeviceDiscovered(device *deconz.DeconzDevice) { - log.WithFields(log.Fields{ - "id": device.GetID(), - "type": device.Type, - "name": device.GetName(), - }).Debug("New Deconz Device discovered") +func (c *DeconzClient) handleNewSensorDeviceDiscovered(device *deconz.DeconzDevice) { - switch device.Type { - case deconz.SensorDeconzDeviceType: - //sensor := entities.NewSensorEntity(fmt.Sprintf("light%d", device.GetID()), entities.LanguageText{En: device.GetName()}, "") + var sensor *entities.SensorEntity + if device.Sensor.State.Temperature > 0 { + sensor = entities.NewSensorEntity(fmt.Sprintf("sensor%d", device.GetID()), entities.LanguageText{En: device.GetName()}, "", entities.TemperaturSensorDeviceClass) + } - //c.IntegrationDriver.AddEntity(sensor) + if device.Sensor.State.Humidity > 0 { + sensor = entities.NewSensorEntity(fmt.Sprintf("sensor%d", device.GetID()), entities.LanguageText{En: device.GetName()}, "", entities.HumiditySensorDeviceClass) + } - case deconz.LightDeconzDeviceType: - light := entities.NewLightEntity(fmt.Sprintf("light%d", device.GetID()), entities.LanguageText{En: device.GetName()}, "") + if sensor != nil { - // All correct Attributes - light.AddFeature(entities.OnOffLightEntityFeatures) - light.AddFeature(entities.ToggleLightEntityFeatures) - light.AddFeature(entities.DimLightEntityFeatures) - - if device.Light.HasColor { - switch device.Light.State.ColorMode { - case "ct": - light.AddFeature(entities.ColorTemperatureLightEntityFeatures) - case "hs": - light.AddFeature(entities.ColorLightEntityFeatures) - } - } + device.SetHandleChangeStateFunc(func(state *deconz.DeconzState) { + log.WithFields(log.Fields{ + "ID": device.GetID(), + "State": state, + }).Debug("Sensor changed") + + attributes := make(map[string]interface{}) + + switch sensor.DeviceClass { + case entities.TemperaturSensorDeviceClass: + attributes["value"] = state.Temperature + + case entities.HumiditySensorDeviceClass: + attributes["value"] = state.Humidity - // Set initial attribute - if light.HasAttribute(entities.StateLightEntityAttribute) { - if *device.Light.State.On { - light.Attributes[string(entities.StateLightEntityAttribute)] = entities.OnLightEntityState - } else { - light.Attributes[string(entities.StateLightEntityAttribute)] = entities.OffLightEntityState } - } - if light.HasAttribute(entities.BrightnessLightEntityAttribute) { - light.Attributes[string(entities.BrightnessLightEntityAttribute)] = device.Light.State.Bri + sensor.SetAttributes(attributes) + + }) + + c.IntegrationDriver.AddEntity(sensor) + } +} + +func (c *DeconzClient) handleNewLightDeviceDiscovered(device *deconz.DeconzDevice) { + light := entities.NewLightEntity(fmt.Sprintf("light%d", device.GetID()), entities.LanguageText{En: device.GetName()}, "") + + // All correct Attributes + light.AddFeature(entities.OnOffLightEntityFeatures) + light.AddFeature(entities.ToggleLightEntityFeatures) + light.AddFeature(entities.DimLightEntityFeatures) + if device.Light.HasColor { + switch device.Light.State.ColorMode { + case "ct": + light.AddFeature(entities.ColorTemperatureLightEntityFeatures) + case "hs": + light.AddFeature(entities.ColorLightEntityFeatures) } + } - // Add commands - light.AddCommand(entities.OnLightEntityCommand, func(entity entities.LightEntity, params map[string]interface{}) int { + // Set initial attribute + if light.HasAttribute(entities.StateLightEntityAttribute) { + if *device.Light.State.On { + light.Attributes[string(entities.StateLightEntityAttribute)] = entities.OnLightEntityState + } else { + light.Attributes[string(entities.StateLightEntityAttribute)] = entities.OffLightEntityState + } + } - if err := device.TurnOn(); err != nil { - return 404 - } - return 200 - }) + if light.HasAttribute(entities.BrightnessLightEntityAttribute) { + light.Attributes[string(entities.BrightnessLightEntityAttribute)] = device.Light.State.Bri - light.AddCommand(entities.OffLightEntityCommand, func(entity entities.LightEntity, params map[string]interface{}) int { + } - if err := device.TurnOff(); err != nil { - return 404 - } - return 200 - }) + // Add commands + light.AddCommand(entities.OnLightEntityCommand, func(entity entities.LightEntity, params map[string]interface{}) int { - light.AddCommand(entities.ToggleLightEntityCommand, func(entity entities.LightEntity, params map[string]interface{}) int { - if device.IsOn() { - device.TurnOff() - } else { - device.TurnOff() - } - return 200 - }) + if err := device.TurnOn(); err != nil { + return 404 + } + return 200 + }) - // Set the Handle State Change function - device.SetHandleChangeStateFunc(func(state *deconz.DeconzState) { - log.WithFields(log.Fields{ - "ID": device.GetID(), - "Type": device.Type, - }).Debug("Handle State Change") + light.AddCommand(entities.OffLightEntityCommand, func(entity entities.LightEntity, params map[string]interface{}) int { - attributes := make(map[string]interface{}) + if err := device.TurnOff(); err != nil { + return 404 + } + return 200 + }) + + light.AddCommand(entities.ToggleLightEntityCommand, func(entity entities.LightEntity, params map[string]interface{}) int { + if device.IsOn() { + device.TurnOff() + } else { + device.TurnOff() + } + return 200 + }) - if light.HasAttribute(entities.StateLightEntityAttribute) { - if *state.On { - attributes[string(entities.StateLightEntityAttribute)] = entities.OnLightEntityState - } else { - attributes[string(entities.StateLightEntityAttribute)] = entities.OffLightEntityState - } - } + // Set the Handle State Change function + device.SetHandleChangeStateFunc(func(state *deconz.DeconzState) { + log.WithFields(log.Fields{ + "ID": device.GetID(), + "Type": device.Type, + }).Debug("Handle State Change") - if light.HasAttribute(entities.BrightnessLightEntityAttribute) { - attributes[string(entities.BrightnessLightEntityAttribute)] = state.Bri + attributes := make(map[string]interface{}) + if light.HasAttribute(entities.StateLightEntityAttribute) { + if *state.On { + attributes[string(entities.StateLightEntityAttribute)] = entities.OnLightEntityState + } else { + attributes[string(entities.StateLightEntityAttribute)] = entities.OffLightEntityState } + } - light.SetAttributes(attributes) + if light.HasAttribute(entities.BrightnessLightEntityAttribute) { + attributes[string(entities.BrightnessLightEntityAttribute)] = state.Bri - }) + } - c.IntegrationDriver.AddEntity(light) + light.SetAttributes(attributes) - case deconz.GroupDeconzDeviceType: + }) - group := entities.NewLightEntity(fmt.Sprintf("group%d", device.GetID()), entities.LanguageText{En: device.GetName()}, "") - // Group only allows for on/off -> basic switch, no dimming - group.AddFeature(entities.OnOffLightEntityFeatures) - group.AddFeature(entities.ToggleLightEntityFeatures) + c.IntegrationDriver.AddEntity(light) +} - // Set initial attribute - // if group.HasAttribute(entities.StateLightEntityAttribute) { - // if *&device.Group.State.AnyOn { - // light.Attributes[string(entities.StateLightEntityAttribute)] = entities.OnLightEntityState - // } else { - // light.Attributes[string(entities.StateLightEntityAttribute)] = entities.OffLightEntityState - // } - // } +func (c *DeconzClient) handleNewGroupDeviceDiscovered(device *deconz.DeconzDevice) { + group := entities.NewLightEntity(fmt.Sprintf("group%d", device.GetID()), entities.LanguageText{En: device.GetName()}, "") + // Group only allows for on/off -> basic switch, no dimming + group.AddFeature(entities.OnOffLightEntityFeatures) + group.AddFeature(entities.ToggleLightEntityFeatures) + + // Set initial attribute + if group.HasAttribute(entities.StateLightEntityAttribute) { + if *device.Group.Action.On { + group.Attributes[string(entities.StateLightEntityAttribute)] = entities.OnLightEntityState + } else { + group.Attributes[string(entities.StateLightEntityAttribute)] = entities.OffLightEntityState + } + } - // Commands + // Commands - group.AddCommand(entities.OnLightEntityCommand, func(entity entities.LightEntity, params map[string]interface{}) int { + group.AddCommand(entities.OnLightEntityCommand, func(entity entities.LightEntity, params map[string]interface{}) int { - if err := device.TurnOn(); err != nil { - return 404 - } - return 200 - }) + if err := device.TurnOn(); err != nil { + return 404 + } + return 200 + }) - group.AddCommand(entities.OffLightEntityCommand, func(entity entities.LightEntity, params map[string]interface{}) int { + group.AddCommand(entities.OffLightEntityCommand, func(entity entities.LightEntity, params map[string]interface{}) int { - if err := device.TurnOff(); err != nil { - return 404 - } - return 200 - }) + if err := device.TurnOff(); err != nil { + return 404 + } + return 200 + }) + + group.AddCommand(entities.ToggleLightEntityCommand, func(entity entities.LightEntity, params map[string]interface{}) int { + if device.IsOn() { + device.TurnOff() + } else { + device.TurnOff() + } + return 200 + }) - group.AddCommand(entities.ToggleLightEntityCommand, func(entity entities.LightEntity, params map[string]interface{}) int { - if device.IsOn() { - device.TurnOff() + device.SetHandleChangeStateFunc(func(state *deconz.DeconzState) { + log.WithFields(log.Fields{ + "ID": device.GetID(), + "Type": device.Type, + }).Debug("Handle State Change") + + attributes := make(map[string]interface{}) + + if group.HasAttribute(entities.StateLightEntityAttribute) { + if *&state.AnyOn { + attributes[string(entities.StateLightEntityAttribute)] = entities.OnLightEntityState } else { - device.TurnOff() + attributes[string(entities.StateLightEntityAttribute)] = entities.OffLightEntityState } - return 200 - }) + } - device.SetHandleChangeStateFunc(func(state *deconz.DeconzState) { - log.WithFields(log.Fields{ - "ID": device.GetID(), - "Type": device.Type, - }).Debug("Handle State Change") + group.SetAttributes(attributes) - attributes := make(map[string]interface{}) + }) - if group.HasAttribute(entities.StateLightEntityAttribute) { - if *&state.AnyOn { - attributes[string(entities.StateLightEntityAttribute)] = entities.OnLightEntityState - } else { - attributes[string(entities.StateLightEntityAttribute)] = entities.OffLightEntityState - } - } + c.IntegrationDriver.AddEntity(group) +} + +func (c *DeconzClient) handleNewDeviceDiscovered(device *deconz.DeconzDevice) { + log.WithFields(log.Fields{ + "id": device.GetID(), + "type": device.Type, + "name": device.GetName(), + }).Debug("New Deconz Device discovered") - group.SetAttributes(attributes) + switch device.Type { + case deconz.SensorDeconzDeviceType: + c.handleNewSensorDeviceDiscovered(device) - }) + case deconz.LightDeconzDeviceType: + c.handleNewLightDeviceDiscovered(device) - c.IntegrationDriver.AddEntity(group) + case deconz.GroupDeconzDeviceType: + c.handleNewGroupDeviceDiscovered(device) } } diff --git a/pkg/deconz/types.go b/pkg/deconz/types.go index 79b1c67..b1d33e2 100644 --- a/pkg/deconz/types.go +++ b/pkg/deconz/types.go @@ -51,5 +51,8 @@ type DeconzState struct { AnyOn bool `json:"any_on,omitempty"` // Sensor - ButtonEvent int `json:"buttonevent,omitempty"` + ButtonEvent int `json:"buttonevent,omitempty"` + Humidity *uint16 `json:"humidity,omitempty"` + Temperature *int16 `json:"temperature,omitempty"` + Pressure *int16 `json:"pressure,omitempty"` } diff --git a/pkg/integration/advertisement.go b/pkg/integration/advertisement.go index 889c741..65a4865 100644 --- a/pkg/integration/advertisement.go +++ b/pkg/integration/advertisement.go @@ -17,7 +17,7 @@ func (i *Integration) startAdvertising() { "ws_path=/ws", } - server, err := zeroconf.Register(i.Metadata.DriverId, "_uc-integration._tcp", "local.", i.config["listenport"].(int), txt, nil) + server, err := zeroconf.Register(i.Metadata.DriverId, "_uc-integration._tcp", "local.", i.Config["listenport"].(int), txt, nil) if err != nil { panic(err) } diff --git a/pkg/integration/events.go b/pkg/integration/events.go index 39f24f4..de3cd4b 100644 --- a/pkg/integration/events.go +++ b/pkg/integration/events.go @@ -198,7 +198,7 @@ func (i *Integration) SendEntityChangeEvent(e interface{}) { log.WithField("subscribedEtities", i.SubscribedEntities).Debug("Currently subscribed entities") // Only send the event when remote is subscribed to - if slices.Contains(i.SubscribedEntities, entity_id) { + if (i.Config["ignoreEntitySubscription"] != nil && i.Config["ignoreEntitySubscription"].(bool)) || slices.Contains(i.SubscribedEntities, entity_id) { var res interface{} now := time.Now() diff --git a/pkg/integration/integration.go b/pkg/integration/integration.go index a6df2a6..daf7cb9 100644 --- a/pkg/integration/integration.go +++ b/pkg/integration/integration.go @@ -23,7 +23,7 @@ type Integration struct { deviceState DState - config Config + Config Config listenAddress string Remote remote @@ -46,7 +46,7 @@ type Integration struct { func NewIntegration(config Config) (*Integration, error) { i := Integration{ - config: config, + Config: config, listenAddress: fmt.Sprintf(":%d", config["listenport"].(int)), deviceState: DisconnectedDeviceState, DeviceId: "", // I think device_id is not yet implemented in Remote TV, used for multi-device integrati @@ -79,15 +79,15 @@ func (i *Integration) Run() error { return fmt.Errorf("Metadata not set") } - http.HandleFunc(i.config["websocketPath"].(string), i.wsEndpoint) + http.HandleFunc(i.Config["websocketPath"].(string), i.wsEndpoint) //MDNS - if i.config["enableMDNS"].(bool) { + if i.Config["enableMDNS"].(bool) { go i.startAdvertising() } // Register the integration - if i.config["enableRegistration"].(bool) && i.config["registrationPin"].(string) != "" { + if i.Config["enableRegistration"].(bool) && i.Config["registrationPin"].(string) != "" { go i.registerIntegration() } diff --git a/pkg/integration/register.go b/pkg/integration/register.go index 4759876..d5753c6 100644 --- a/pkg/integration/register.go +++ b/pkg/integration/register.go @@ -35,8 +35,8 @@ type DriverRegistration struct { func (i *Integration) registerIntegration() { // Use configured IP for registration instead of Remote Two discovery - if i.config["remoteTwoIP"].(string) != "" && i.config["remoteTwoPort"].(int) > 0 { - i.registerWithRemoteTwo(i.config["remoteTwoIP"].(string), i.config["remoteTwoPort"].(int)) + if i.Config["remoteTwoIP"].(string) != "" && i.Config["remoteTwoPort"].(int) > 0 { + i.registerWithRemoteTwo(i.Config["remoteTwoIP"].(string), i.Config["remoteTwoPort"].(int)) } else { entries := make(chan *zeroconf.ServiceEntry) @@ -78,7 +78,7 @@ func (i *Integration) registerIntegration() { func (i *Integration) registerWithRemoteTwo(remoteTwoIP string, remoteTwoPort int) { myip := GetLocalIP() - driverURL := "ws://" + myip + i.listenAddress + i.config["websocketPath"].(string) + driverURL := "ws://" + myip + i.listenAddress + i.Config["websocketPath"].(string) remoteTwoURL := "http://" + remoteTwoIP + ":" + fmt.Sprint(remoteTwoPort) driverRegistration := DriverRegistration{ @@ -107,7 +107,7 @@ func (i *Integration) registerWithRemoteTwo(remoteTwoIP string, remoteTwoPort in req.Header.Set("Content-Type", "application/json") // Authentication wit the Remote Two - credentials := b64.StdEncoding.EncodeToString([]byte(i.config["registrationUsername"].(string) + ":" + i.config["registrationPin"].(string))) + credentials := b64.StdEncoding.EncodeToString([]byte(i.Config["registrationUsername"].(string) + ":" + i.Config["registrationPin"].(string))) req.Header.Set("Authorization", "Basic "+credentials) // send the request