public async Task Handle(string[] topicLevels, MqttApplicationMessage message, CancellationToken token = default) { Match mtch = _thermostatRegex.Match(topicLevels[0]); int controller = int.Parse(mtch.Groups["controller"].Value); int thermostat = int.Parse(mtch.Groups["thermostat"].Value); int obj = UponorObjects.Thermostat(UponorThermostats.RoomSetpoint, controller, thermostat); string convertPayloadToString = message.ConvertPayloadToString(); float newSetpoint = float.Parse(convertPayloadToString, _parsingCulture); _logger.LogInformation("Setting c{Controller} t{Thermostat} setpoint to {Value}", controller, thermostat, newSetpoint); await _client.SetValue(obj, UponorProperties.Value, newSetpoint, token); // Perform new read, wait until the value is applied using CancellationTokenSource cts = new CancellationTokenSource(TimeSpan.FromSeconds(10)); using CancellationTokenSource timeoutToken = CancellationTokenSource.CreateLinkedTokenSource(cts.Token, token); (_, UponorResponseContainer responseContainer) = await _client.WaitUntil(obj, UponorProperties.Value, testContainer => { if (!testContainer.TryGetValue(obj, UponorProperties.Value, out float appliedFloat)) { return(false); } return(Math.Abs(appliedFloat - newSetpoint) < 0.5f); }, timeoutToken.Token); _featureManager.Process(responseContainer); await _hassMqttManager.FlushAll(token); }
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); }
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); } }
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); } }
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"); } } } }
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); } }