package mqtt import ( "encoding/json" "fmt" "log" "git.k7n.net/mats/go-telldus/pkg/datastore" "git.k7n.net/mats/go-telldus/pkg/telldus" ) // HADevice represents a device in Home Assistant MQTT discovery type HADevice struct { Identifiers []string `json:"identifiers"` Name string `json:"name"` Manufacturer string `json:"manufacturer"` Model string `json:"model,omitempty"` } // SwitchDiscovery represents Home Assistant MQTT switch discovery config // Reference: https://www.home-assistant.io/integrations/switch.mqtt/ type SwitchDiscovery struct { Name string `json:"name"` CommandTopic string `json:"command_topic"` StateTopic string `json:"state_topic"` UniqueID string `json:"unique_id"` Device HADevice `json:"device"` PayloadOn string `json:"payload_on,omitempty"` PayloadOff string `json:"payload_off,omitempty"` StateOn string `json:"state_on,omitempty"` StateOff string `json:"state_off,omitempty"` OptimisticMode bool `json:"optimistic,omitempty"` Qos int `json:"qos,omitempty"` Retain bool `json:"retain,omitempty"` AvailabilityTopic string `json:"availability_topic,omitempty"` } // SensorDiscovery represents Home Assistant MQTT sensor discovery config // Reference: https://www.home-assistant.io/integrations/sensor.mqtt/ type SensorDiscovery struct { Name string `json:"name"` StateTopic string `json:"state_topic"` UniqueID string `json:"unique_id"` Device HADevice `json:"device"` UnitOfMeasurement string `json:"unit_of_measurement,omitempty"` DeviceClass string `json:"device_class,omitempty"` StateClass string `json:"state_class,omitempty"` ValueTemplate string `json:"value_template,omitempty"` Qos int `json:"qos,omitempty"` AvailabilityTopic string `json:"availability_topic,omitempty"` } // PublishAllDiscovery publishes Home Assistant discovery messages for all devices and sensors func (c *Client) PublishAllDiscovery() error { if err := c.publishDeviceDiscovery(); err != nil { return err } if err := c.publishSensorDiscovery(); err != nil { return err } return nil } // publishDeviceDiscovery publishes discovery for all devices func (c *Client) publishDeviceDiscovery() error { for device := range c.store.ListDevices() { if err := c.PublishDeviceDiscovery(device.ID); err != nil { log.Printf("Error publishing discovery for device %d: %v", device.ID, err) } } return nil } // PublishDeviceDiscovery publishes Home Assistant discovery for a single device func (c *Client) PublishDeviceDiscovery(deviceID int) error { device, err := c.store.GetDevice(deviceID) if err != nil { return fmt.Errorf("device %d not found: %w", deviceID, err) } discovery := SwitchDiscovery{ Name: device.Name, CommandTopic: fmt.Sprintf("telldus/device/%d/set", deviceID), StateTopic: fmt.Sprintf("telldus/device/%d/state", deviceID), UniqueID: device.UniqueID, PayloadOn: "ON", PayloadOff: "OFF", StateOn: "ON", StateOff: "OFF", Device: HADevice{ Identifiers: []string{fmt.Sprintf("telldus_%d", deviceID)}, Name: device.Name, Manufacturer: "Telldus", Model: fmt.Sprintf("%s %s", device.Protocol, device.Model), }, } payload, err := json.Marshal(discovery) if err != nil { return fmt.Errorf("failed to marshal discovery: %w", err) } topic := fmt.Sprintf("homeassistant/switch/%s/config", device.UniqueID) c.client.Publish(topic, 0, true, payload) return nil } // publishSensorDiscovery publishes discovery for all sensors func (c *Client) publishSensorDiscovery() error { var protocol, model string var id, dataTypes int ret := telldus.Sensor(&protocol, &model, &id, &dataTypes) for ret == 0 { sensor, err := c.store.GetSensorByIdentity(protocol, model, id) if err != nil || sensor.Hidden { log.Printf("Sensor %s %s %d not in DB or hidden, skipping", protocol, model, id) ret = telldus.Sensor(&protocol, &model, &id, &dataTypes) continue } if err := c.publishSensorDiscoveryForSensor(sensor, dataTypes); err != nil { log.Printf("Error publishing discovery for sensor %s %s %d: %v", protocol, model, id, err) } ret = telldus.Sensor(&protocol, &model, &id, &dataTypes) } return nil } // PublishSensorDiscovery publishes Home Assistant discovery for a single sensor func (c *Client) PublishSensorDiscovery(protocol, model string, id int) error { sensor, err := c.store.GetSensorByIdentity(protocol, model, id) if err != nil { return fmt.Errorf("sensor %s %s %d not found: %w", protocol, model, id, err) } // Get current data types from telldus var p, m string var sensorID, dataTypes int ret := telldus.Sensor(&p, &m, &sensorID, &dataTypes) // Find matching sensor for ret == 0 { if p == protocol && m == model && sensorID == id { return c.publishSensorDiscoveryForSensor(sensor, dataTypes) } ret = telldus.Sensor(&p, &m, &sensorID, &dataTypes) } return fmt.Errorf("sensor %s %s %d not found in telldus", protocol, model, id) } // publishSensorDiscoveryForSensor publishes discovery messages for a sensor's data types func (c *Client) publishSensorDiscoveryForSensor(sensor *datastore.Sensor, dataTypes int) error { sensorDevice := HADevice{ Identifiers: []string{fmt.Sprintf("telldus_sensor_%s_%s_%d", sensor.Protocol, sensor.Model, sensor.ID)}, Name: sensor.Name, Manufacturer: "Telldus", Model: fmt.Sprintf("%s %s", sensor.Protocol, sensor.Model), } if dataTypes&telldus.DataTypeTemperature != 0 && sensor.TemperatureUniqueID != "" { discovery := SensorDiscovery{ Name: fmt.Sprintf("%s Temperature", sensor.Name), StateTopic: fmt.Sprintf("telldus/sensor/%s/%s/%d/temperature", sensor.Protocol, sensor.Model, sensor.ID), UniqueID: sensor.TemperatureUniqueID, UnitOfMeasurement: "°C", DeviceClass: "temperature", StateClass: "measurement", Device: sensorDevice, } payload, err := json.Marshal(discovery) if err != nil { return fmt.Errorf("failed to marshal temperature discovery: %w", err) } topic := fmt.Sprintf("homeassistant/sensor/%s/config", sensor.TemperatureUniqueID) c.client.Publish(topic, 0, true, payload) } if dataTypes&telldus.DataTypeHumidity != 0 && sensor.HumidityUniqueID != "" { discovery := SensorDiscovery{ Name: fmt.Sprintf("%s Humidity", sensor.Name), StateTopic: fmt.Sprintf("telldus/sensor/%s/%s/%d/humidity", sensor.Protocol, sensor.Model, sensor.ID), UniqueID: sensor.HumidityUniqueID, UnitOfMeasurement: "%", DeviceClass: "humidity", StateClass: "measurement", Device: sensorDevice, } payload, err := json.Marshal(discovery) if err != nil { return fmt.Errorf("failed to marshal humidity discovery: %w", err) } topic := fmt.Sprintf("homeassistant/sensor/%s/config", sensor.HumidityUniqueID) c.client.Publish(topic, 0, true, payload) } return nil }