Beispiel #1
0
        public static async Task <(bool, UponorResponseContainer)> WaitUntil(this UhomeUponorClient client, int @object, UponorProperties property, Func <UponorResponseContainer, bool> condition, CancellationToken token = default)
        {
            UponorResponseContainer res = new UponorResponseContainer();

            try
            {
                while (!token.IsCancellationRequested)
                {
                    // Attempt to read the value
                    res = await client.ReadValue(@object, property, token);

                    bool conditionMet = condition(res);
                    if (conditionMet)
                    {
                        return(true, res);
                    }

                    // Condition was not met, carry on
                    await Task.Delay(TimeSpan.FromMilliseconds(500), token);
                }
            }
            catch (TaskCanceledException)
            {
                return(false, res);
            }

            return(false, res);
        }
Beispiel #2
0
 public void Process(UponorResponseContainer values)
 {
     foreach (FeatureBase feature in _features)
     {
         feature.Process(values);
     }
 }
        private async Task PerformUpdate(CancellationToken stoppingToken)
        {
            // Update system & controller details
            IEnumerable <int> objects = _detailsContainer.GetAvailableThermostats().SelectMany(c => new[]
            {
                // Generic
                UponorObjects.Thermostat(UponorThermostats.RoomName, c.controller, c.thermostat),
                UponorObjects.Thermostat(UponorThermostats.RoomInDemand, c.controller, c.thermostat),
                // Humidity
                UponorObjects.Thermostat(UponorThermostats.RhValue, c.controller, c.thermostat),
                // Temperature
                UponorObjects.Thermostat(UponorThermostats.RoomSetpoint, c.controller, c.thermostat),
                UponorObjects.Thermostat(UponorThermostats.RoomTemperature, c.controller, c.thermostat),
                // Alarms
                UponorObjects.Thermostat(UponorThermostats.TamperIndication, c.controller, c.thermostat),
                UponorObjects.Thermostat(UponorThermostats.BatteryAlarm, c.controller, c.thermostat),
                UponorObjects.Thermostat(UponorThermostats.RfAlarm, c.controller, c.thermostat),
                UponorObjects.Thermostat(UponorThermostats.TechnicalAlarm, c.controller, c.thermostat)
            })
                                        .Concat(_detailsContainer.GetAvailableOutdoorSensors().SelectMany(c => new[]
            {
                // Outdoor sensor
                UponorObjects.Controller(UponorController.MeasuredOutdoorTemperature, c),
            }))
                                        .Concat(new[]
            {
                // Average temperature
                UponorObjects.System(UponorSystem.AverageRoomTemperature),
            });

            UponorResponseContainer values = await _uponorClient.ReadValues(objects, new[] { UponorProperties.Value }, stoppingToken);

            _featureManager.Process(values);
        }
Beispiel #4
0
        protected override async Task ExecuteAsync(CancellationToken stoppingToken)
        {
            // Update loop
            DateTime lastRun = DateTime.MinValue;

            while (!stoppingToken.IsCancellationRequested)
            {
                TimeSpan toDelay = _config.DiscoveryInterval - (DateTime.UtcNow - lastRun);
                if (toDelay > TimeSpan.Zero)
                {
                    try
                    {
                        await Task.Delay(toDelay, stoppingToken);
                    }
                    catch (OperationCanceledException)
                    {
                        // Do nothing
                        continue;
                    }
                }

                _logger.LogDebug("Beginning discovery");

                try
                {
                    UponorResponseContainer values = await AcquireDiscoveryDetails(stoppingToken);

                    CreateEntities(values);

                    _featureManager.Process(values);
                }
                catch (OperationCanceledException) when(stoppingToken.IsCancellationRequested)
                {
                    // Do nothing
                }
                catch (Exception e)
                {
                    _logger.LogError(e, "An error occurred while performing the update");
                }

                try
                {
                    await _hassMqttManager.FlushAll(stoppingToken);
                }
                catch (OperationCanceledException) when(stoppingToken.IsCancellationRequested)
                {
                    // Do nothing
                }
                catch (Exception e)
                {
                    _logger.LogError(e, "An error occurred while pushing updated values");
                }

                lastRun = DateTime.UtcNow;
            }
        }
Beispiel #5
0
        private async Task <UponorResponseContainer> AcquireDiscoveryDetails(CancellationToken stoppingToken)
        {
            SystemProperties systemInfo = await _uponorClient.GetSystemInfo(stoppingToken);

            // Update details container
            _detailsContainer.Update(systemInfo.AvailableControllers, systemInfo.AvailableThermostats, systemInfo.AvailableOutdoorSensors, systemInfo.HcMode);

            // Batch all Value properties
            IEnumerable <int> objects = new[] {
                UponorObjects.System(UponorSystem.DeviceLostAlarm),
                UponorObjects.System(UponorSystem.NoCommController1),
                UponorObjects.System(UponorSystem.NoCommController2),
                UponorObjects.System(UponorSystem.NoCommController3),
                UponorObjects.System(UponorSystem.NoCommController4),
                UponorObjects.System(UponorSystem.UhomeModuleId),
                UponorObjects.System(UponorSystem.DeviceLostAlarm),
            }
            .Concat(systemInfo.AvailableControllers.SelectMany(c => new[]
            {
                UponorObjects.Controller(UponorController.ControllerSwVersion, c)
            }))
            .Concat(systemInfo.AvailableOutdoorSensors.SelectMany(s => new[]
            {
                UponorObjects.Controller(UponorController.OutdoorSensorPresence, s)
            }))
            .Concat(_detailsContainer.GetAvailableThermostats().SelectMany(c => new[]
            {
                UponorObjects.Thermostat(UponorThermostats.RoomName, c.controller, c.thermostat),
                UponorObjects.Thermostat(UponorThermostats.MinSetpoint, c.controller, c.thermostat),
                UponorObjects.Thermostat(UponorThermostats.MaxSetpoint, c.controller, c.thermostat)
            }));

            UponorResponseContainer values = await _uponorClient.ReadValues(objects, new[] { UponorProperties.Value }, stoppingToken);

            // Batch other properties
            // Update some system properties
            objects = systemInfo.AvailableControllers.SelectMany(c => new[]
            {
                UponorObjects.System(UponorSystem.DeviceObject)
            });

            UponorResponseContainer systemValues = await _uponorClient.ReadValues(objects, new[]
            {
                UponorProperties.ApplicationVersion,
                UponorProperties.DeviceName,
                UponorProperties.DeviceId,
                UponorProperties.SerialNumber,
                UponorProperties.ProductName,
                UponorProperties.Supplier,
                UponorProperties.MacAddress
            }, stoppingToken);

            values.Merge(systemValues);

            return(values);
        }
        public override void Process(UponorResponseContainer values)
        {
            // Average temperature
            if (!values.TryGetValue(UponorObjects.System(UponorSystem.AverageRoomTemperature),
                                    UponorProperties.Value, out object val))
            {
                return;
            }

            string           deviceId = HassUniqueIdBuilder.GetUhomeDeviceId();
            ISensorContainer sensor   = HassMqttManager.GetSensor(deviceId, "average_temperature");

            MqttStateValueTopic sender = sensor.GetValueSender(HassTopicKind.State);

            sender.Value = val;
        }
        public override void Process(UponorResponseContainer values)
        {
            string deviceId = HassUniqueIdBuilder.GetUhomeDeviceId();

            if (!HassMqttManager.TryGetSensor(deviceId, "uhome", out ISensorContainer sensor))
            {
                return;
            }

            sensor.SetValue(HassTopicKind.State, "discovered");

            if (values.TryGetValue(UponorObjects.System(UponorSystem.DeviceObject), UponorProperties.ApplicationVersion, out object val))
            {
                sensor.SetAttribute("application_version", val);
            }

            if (values.TryGetValue(UponorObjects.System(UponorSystem.DeviceObject), UponorProperties.DeviceName, out val))
            {
                sensor.SetAttribute("device_name", val);
            }

            if (values.TryGetValue(UponorObjects.System(UponorSystem.DeviceObject), UponorProperties.DeviceId, out val))
            {
                sensor.SetAttribute("device_id", val);
            }

            if (values.TryGetValue(UponorObjects.System(UponorSystem.DeviceObject), UponorProperties.SerialNumber, out val))
            {
                sensor.SetAttribute("serial_number", val);
            }

            if (values.TryGetValue(UponorObjects.System(UponorSystem.DeviceObject), UponorProperties.ProductName, out val))
            {
                sensor.SetAttribute("product_name", val);
            }

            if (values.TryGetValue(UponorObjects.System(UponorSystem.DeviceObject), UponorProperties.Supplier, out val))
            {
                sensor.SetAttribute("supplier", val);
            }

            if (values.TryGetValue(UponorObjects.System(UponorSystem.DeviceObject), UponorProperties.MacAddress, out val))
            {
                sensor.SetAttribute("macaddress", val);
            }
        }
Beispiel #8
0
        public override void Process(UponorResponseContainer values)
        {
            // Outdoor sensors
            foreach (int controller in _systemDetails.GetAvailableOutdoorSensors())
            {
                if (!values.TryGetValue(UponorObjects.Controller(UponorController.MeasuredOutdoorTemperature, controller),
                                        UponorProperties.Value, out object val))
                {
                    continue;
                }

                string           deviceId = HassUniqueIdBuilder.GetControllerDeviceId(controller);
                ISensorContainer sensor   = HassMqttManager.GetSensor(deviceId, "outdoor_sensor");

                MqttStateValueTopic sender = sensor.GetValueSender(HassTopicKind.State);

                sender.Value = val;
            }
        }
        public override void Process(UponorResponseContainer values)
        {
            // Software versions
            foreach (int controller in _systemDetails.GetAvailableControllers())
            {
                if (!values.TryGetValue(UponorObjects.Controller(UponorController.ControllerSwVersion, controller),
                                        UponorProperties.Value, out object val))
                {
                    continue;
                }

                string           deviceId = HassUniqueIdBuilder.GetControllerDeviceId(controller);
                ISensorContainer sensor   = HassMqttManager.GetSensor(deviceId, "controller");

                MqttStateValueTopic sender     = sensor.GetValueSender(HassTopicKind.State);
                MqttAttributesTopic attributes = sensor.GetAttributesSender();

                sender.Value = "discovered";
                attributes.SetAttribute("sw_version", val);
            }
        }
Beispiel #10
0
        public override void Process(UponorResponseContainer values)
        {
            foreach ((int controller, int thermostat) in _systemDetails.GetAvailableThermostats())
            {
                string deviceId = HassUniqueIdBuilder.GetThermostatDeviceId(controller, thermostat);

                // Temperature
                ISensorContainer    sensor = HassMqttManager.GetSensor(deviceId, "temperature");
                MqttStateValueTopic sender = sensor.GetValueSender(HassTopicKind.State);

                if (values.TryGetValue(UponorObjects.Thermostat(UponorThermostats.RoomTemperature, controller, thermostat),
                                       UponorProperties.Value, out float floatVal))
                {
                    if (IsValid.Temperature(floatVal))
                    {
                        sender.Value = floatVal;
                    }
                    else
                    {
                        _logger.LogWarning("Received an invalid temperature of {Value} for {Device}", floatVal, deviceId);
                    }
                }
            }
        }
        public override void Process(UponorResponseContainer values)
        {
            List <string> problems = new List <string>();

            foreach ((int controller, int thermostat) in _systemDetails.GetAvailableThermostats())
            {
                string deviceId = HassUniqueIdBuilder.GetThermostatDeviceId(controller, thermostat);

                // Battery sensor
                // We don't know what the battery level is with Uponor. So we can only say it's "good" or "bad"
                ISensorContainer sensor = HassMqttManager.GetSensor(deviceId, "battery");

                if (values.TryGetValue(
                        UponorObjects.Thermostat(UponorThermostats.BatteryAlarm, controller, thermostat),
                        UponorProperties.Value, out object objVal) && objVal != null)
                {
                    sensor.SetValue(HassTopicKind.State, BatteryLow);
                }
                else
                {
                    sensor.SetValue(HassTopicKind.State, BatteryOk);
                }

                // Alarm sensor
                sensor = HassMqttManager.GetSensor(deviceId, "alarms");

                problems.Clear();

                // Check one of: RfAlarm, BatteryAlarm, TechnicalAlarm, TamperIndication
                if (values.TryGetValue(
                        UponorObjects.Thermostat(UponorThermostats.RfAlarm, controller, thermostat),
                        UponorProperties.Value, out objVal) && objVal != null)
                {
                    problems.Add("No signal");
                    sensor.SetAttribute("signal", "alarm");
                }
                else
                {
                    sensor.SetAttribute("signal", "ok");
                }

                if (values.TryGetValue(
                        UponorObjects.Thermostat(UponorThermostats.TechnicalAlarm, controller, thermostat),
                        UponorProperties.Value, out objVal) && objVal != null)
                {
                    problems.Add("Technical (?)");
                    sensor.SetAttribute("technical", "alarm");
                }
                else
                {
                    sensor.SetAttribute("technical", "ok");
                }

                if (values.TryGetValue(
                        UponorObjects.Thermostat(UponorThermostats.TamperIndication, controller, thermostat),
                        UponorProperties.Value, out objVal) && objVal != null)
                {
                    problems.Add("Tampering");
                    sensor.SetAttribute("tampering", "alarm");
                }
                else
                {
                    sensor.SetAttribute("tampering", "ok");
                }

                if (problems.Any())
                {
                    sensor.SetValue(HassTopicKind.State, "on");
                    sensor.SetAttribute("problem", string.Join(", ", problems));
                }
                else
                {
                    sensor.SetValue(HassTopicKind.State, "off");
                    sensor.SetAttribute("problem", string.Empty);
                }
            }
        }
        public override void Process(UponorResponseContainer values)
        {
            foreach ((int controller, int thermostat) in _systemDetails.GetAvailableThermostats())
            {
                string deviceId = HassUniqueIdBuilder.GetThermostatDeviceId(controller, thermostat);

                // Temperature
                ISensorContainer sensor = HassMqttManager.GetSensor(deviceId, "temp");

                if (values.TryGetValue(
                        UponorObjects.Thermostat(UponorThermostats.RoomTemperature, controller, thermostat),
                        UponorProperties.Value, out float floatVal))
                {
                    if (IsValid.Temperature(floatVal))
                    {
                        sensor.SetValue(HassTopicKind.CurrentTemperature, floatVal);
                    }
                    else
                    {
                        _logger.LogWarning("Received an invalid temperature of {Value} for {Device}", floatVal, deviceId);
                    }
                }

                // Setpoint
                if (values.TryGetValue(
                        UponorObjects.Thermostat(UponorThermostats.RoomSetpoint, controller, thermostat),
                        UponorProperties.Value, out floatVal))
                {
                    sensor.SetValue(HassTopicKind.TemperatureState, floatVal);
                }

                // Action & Mode
                if (values.TryGetValue(
                        UponorObjects.Thermostat(UponorThermostats.RoomInDemand, controller, thermostat),
                        UponorProperties.Value, out int intVal))
                {
                    // If this value is >0, the room is heating/cooling (see H/C mode)
                    string action, mode;

                    // Valid values: off, heating, cooling, drying, idle, fan.
                    if (intVal <= 0)
                    {
                        action = "idle";
                        mode   = "off";
                    }
                    else if (_systemDetails.HcMode == HcMode.Heating)
                    {
                        action = "heating";
                        mode   = "heat";
                    }
                    else
                    {
                        action = "cooling";
                        mode   = "cool";
                    }

                    // Override Mode as auto
                    if (_operationConfig.OperationMode == OperationMode.Normal)
                    {
                        mode = "auto";
                    }

                    sensor.SetValue(HassTopicKind.Action, action);
                    sensor.SetValue(HassTopicKind.ModeState, mode);
                }

                // Home/away
                if (values.TryGetValue(
                        UponorObjects.Thermostat(UponorThermostats.HomeAwayModeStatus, controller, thermostat),
                        UponorProperties.Value, out intVal))
                {
                    if (intVal > 0)
                    {
                        // Away
                        sensor.SetValue(HassTopicKind.AwayModeState, "on");
                    }
                    else
                    {
                        // Home
                        sensor.SetValue(HassTopicKind.AwayModeState, "off");
                    }
                }
            }
        }
Beispiel #13
0
 public abstract void Process(UponorResponseContainer values);
Beispiel #14
0
        private void CreateEntities(UponorResponseContainer values)
        {
            // System
            string uHomeDeviceId = HassUniqueIdBuilder.GetUhomeDeviceId();

            {
                const string entityId = "uhome";
                _hassMqttManager.ConfigureSensor <MqttSensor>(uHomeDeviceId, entityId)
                .ConfigureTopics(HassTopicKind.State, HassTopicKind.JsonAttributes)
                .ConfigureDevice(device =>
                {
                    if (!device.Identifiers.Contains(uHomeDeviceId))
                    {
                        device.Identifiers.Add(uHomeDeviceId);
                    }

                    device.Name         = "Uponor U@Home";
                    device.Manufacturer = "Uponor";
                })
                .ConfigureAliveService();

                _hassMqttManager.ConfigureSensor <MqttSensor>(uHomeDeviceId, "average_temperature")
                .ConfigureTopics(HassTopicKind.State)
                .ConfigureDevice(device =>
                {
                    if (!device.Identifiers.Contains(uHomeDeviceId))
                    {
                        device.Identifiers.Add(uHomeDeviceId);
                    }

                    device.Name         = "Uponor U@Home";
                    device.Manufacturer = "Uponor";
                })
                .ConfigureDiscovery(discovery =>
                {
                    discovery.Name = "Uponor Average Temperature";
                    discovery.UnitOfMeasurement = "°C";
                    discovery.DeviceClass       = HassSensorDeviceClass.Temperature;
                })
                .ConfigureAliveService();
            }

            // Controllers
            foreach (int controller in _detailsContainer.GetAvailableControllers())
            {
                string       deviceId = HassUniqueIdBuilder.GetControllerDeviceId(controller);
                const string entityId = "controller";

                _hassMqttManager.ConfigureSensor <MqttSensor>(deviceId, entityId)
                .ConfigureTopics(HassTopicKind.State, HassTopicKind.JsonAttributes)
                .ConfigureDevice(device =>
                {
                    if (!device.Identifiers.Contains(deviceId))
                    {
                        device.Identifiers.Add(deviceId);
                    }

                    device.Name         = $"Uponor Controller {controller}";
                    device.Manufacturer = "Uponor";
                    device.ViaDevice    = uHomeDeviceId;
                })
                .ConfigureAliveService();
            }

            // Outdoor sensors
            foreach (int controller in _detailsContainer.GetAvailableOutdoorSensors())
            {
                string       controllerId = HassUniqueIdBuilder.GetControllerDeviceId(controller);
                const string entityId     = "outdoor_sensor";

                _hassMqttManager.ConfigureSensor <MqttSensor>(controllerId, entityId)
                .ConfigureTopics(HassTopicKind.State)
                .ConfigureDevice(device =>
                {
                    if (!device.Identifiers.Contains(controllerId))
                    {
                        device.Identifiers.Add(controllerId);
                    }

                    device.Name         = $"Uponor Controller {controller}";
                    device.Manufacturer = "Uponor";
                    device.ViaDevice    = controllerId;
                })
                .ConfigureDiscovery(discovery =>
                {
                    discovery.Name              = $"Controller {controller} Outdoor Sensor";
                    discovery.DeviceClass       = HassSensorDeviceClass.Temperature;
                    discovery.UnitOfMeasurement = "°C";
                    discovery.StateClass        = HassStateClass.Measurement;
                })
                .ConfigureAliveService();
            }

            // Thermostats
            void SetThermostatDeviceInfo <TEntity>(IDiscoveryDocumentBuilder <TEntity> builder, string name, string deviceId, string controllerId) where TEntity : IHassDiscoveryDocument
            {
                builder.ConfigureDevice(device =>
                {
                    if (!device.Identifiers.Contains(deviceId))
                    {
                        device.Identifiers.Add(deviceId);
                    }

                    device.Name         = name;
                    device.Manufacturer = "Uponor";
                    device.ViaDevice    = controllerId;
                });
            }

            foreach ((int controller, int thermostat) in _detailsContainer.GetAvailableThermostats())
            {
                string controllerId = HassUniqueIdBuilder.GetControllerDeviceId(controller);
                string deviceId     = HassUniqueIdBuilder.GetThermostatDeviceId(controller, thermostat);

                // Name
                string deviceName = $"Thermostat {controller}.{thermostat}";
                if (values.TryGetValue(UponorObjects.Thermostat(UponorThermostats.RoomName, controller, thermostat),
                                       UponorProperties.Value, out string stringVal) && !string.IsNullOrWhiteSpace(stringVal))
                {
                    deviceName = stringVal;
                }

                // Climate
                IDiscoveryDocumentBuilder <MqttClimate> climateBuilder = _hassMqttManager.ConfigureSensor <MqttClimate>(deviceId, "temp")
                                                                         .ConfigureTopics(HassTopicKind.JsonAttributes)
                                                                         .ConfigureTopics(HassTopicKind.CurrentTemperature, HassTopicKind.AwayModeState, HassTopicKind.Action, HassTopicKind.ModeState)
                                                                         .ConfigureTopics(HassTopicKind.TemperatureCommand, HassTopicKind.TemperatureState)
                                                                         .ConfigureDiscovery(discovery =>
                {
                    discovery.Name      = $"{deviceName} Thermostat";
                    discovery.Precision = 0.1f;
                    discovery.TempStep  = 0.5f;
                })
                                                                         .ConfigureAliveService();

                SetThermostatDeviceInfo(climateBuilder, deviceName, deviceId, controllerId);

                // Hacks: HASS has an odd way of determining what Climate devices do.
                // With HASS, the mode of the device is what the device is set to do. Ie, in a heating-only climate system, they will _always_ be heating
                // While I prefer that the device is shown as what it's currently doing, given my "auto" settings.
                switch (_operationConfig.OperationMode)
                {
                case OperationMode.Normal:
                    climateBuilder.Discovery.Modes = new[] { "auto" };
                    break;

                case OperationMode.ModeWorkaround:
                    climateBuilder.Discovery.Modes = new[] { "off", _detailsContainer.HcMode == HcMode.Heating ? "heat" : "cool" };
                    break;

                default:
                    throw new ArgumentOutOfRangeException();
                }

                if (values.TryGetValue(UponorObjects.Thermostat(UponorThermostats.MinSetpoint, controller, thermostat),
                                       UponorProperties.Value, out float floatVal))
                {
                    climateBuilder.Discovery.MinTemp = floatVal;
                }

                if (values.TryGetValue(UponorObjects.Thermostat(UponorThermostats.MaxSetpoint, controller, thermostat),
                                       UponorProperties.Value, out floatVal))
                {
                    climateBuilder.Discovery.MaxTemp = floatVal;
                }

                // Temperature
                IDiscoveryDocumentBuilder <MqttSensor> sensorBuilder = _hassMqttManager.ConfigureSensor <MqttSensor>(deviceId, "temperature")
                                                                       .ConfigureTopics(HassTopicKind.State)
                                                                       .ConfigureDiscovery(discovery =>
                {
                    discovery.Name              = $"{deviceName} Temperature";
                    discovery.DeviceClass       = HassSensorDeviceClass.Temperature;
                    discovery.UnitOfMeasurement = "°C";
                    discovery.StateClass        = HassStateClass.Measurement;
                })
                                                                       .ConfigureAliveService();

                SetThermostatDeviceInfo(sensorBuilder, deviceName, deviceId, controllerId);

                // Humidity
                sensorBuilder = _hassMqttManager.ConfigureSensor <MqttSensor>(deviceId, "humidity")
                                .ConfigureTopics(HassTopicKind.State)
                                .ConfigureDiscovery(discovery =>
                {
                    discovery.Name              = $"{deviceName} Humidity";
                    discovery.DeviceClass       = HassSensorDeviceClass.Humidity;
                    discovery.UnitOfMeasurement = "%";
                    discovery.StateClass        = HassStateClass.Measurement;
                })
                                .ConfigureAliveService();

                SetThermostatDeviceInfo(sensorBuilder, deviceName, deviceId, controllerId);

                // Battery sensor
                sensorBuilder = _hassMqttManager.ConfigureSensor <MqttSensor>(deviceId, "battery")
                                .ConfigureTopics(HassTopicKind.State)
                                .ConfigureDiscovery(discovery =>
                {
                    discovery.Name              = $"{deviceName} Battery";
                    discovery.DeviceClass       = HassSensorDeviceClass.Battery;
                    discovery.UnitOfMeasurement = "%";
                })
                                .ConfigureAliveService();

                SetThermostatDeviceInfo(sensorBuilder, deviceName, deviceId, controllerId);

                // Alarm sensor
                IDiscoveryDocumentBuilder <MqttBinarySensor> binarySensorBuilder = _hassMqttManager.ConfigureSensor <MqttBinarySensor>(deviceId, "alarms")
                                                                                   .ConfigureTopics(HassTopicKind.State, HassTopicKind.JsonAttributes)
                                                                                   .ConfigureDiscovery(discovery =>
                {
                    discovery.Name        = $"{deviceName} Alarms";
                    discovery.DeviceClass = HassBinarySensorDeviceClass.Problem;
                })
                                                                                   .ConfigureAliveService();

                SetThermostatDeviceInfo(binarySensorBuilder, deviceName, deviceId, controllerId);
            }
        }