/// <summary> /// Update state and publish new statuses. /// </summary> /// <param name="thermostat">Thermostat status.</param> /// <returns>>A <see cref="Task"/> representing the asynchronous operation.</returns> private async Task UpdateState(Thermostat thermostat) { if (thermostat == null) { return; } var thermostatStatus = new ThermostatStatus(); // Equipment status foreach (var device in thermostat.EquipmentStatus.Split(',').Where(x => !string.IsNullOrEmpty(x))) { thermostatStatus.EquipmentStatus[device] = "on"; } // Status thermostatStatus.Status["hvacMode"] = thermostat.Settings.HvacMode; thermostatStatus.Status["humidifierMode"] = thermostat.Settings.HumidifierMode; thermostatStatus.Status["dehumidifierMode"] = thermostat.Settings.DehumidifierMode; thermostatStatus.Status["autoAway"] = thermostat.Settings.AutoAway.HasValue && thermostat.Settings.AutoAway.Value ? "true" : "false"; thermostatStatus.Status["vent"] = thermostat.Settings.Vent; thermostatStatus.Status["actualTemperature"] = thermostat.Runtime.ActualTemperature.HasValue ? (thermostat.Runtime.ActualTemperature.Value / 10m).ToString() : null; thermostatStatus.Status["actualHumidity"] = thermostat.Runtime.ActualHumidity.HasValue ? thermostat.Runtime.ActualHumidity.Value.ToString() : null; thermostatStatus.Status["desiredHeat"] = thermostat.Runtime.DesiredHeat.HasValue ? (thermostat.Runtime.DesiredHeat.Value / 10m).ToString() : null; thermostatStatus.Status["desiredCool"] = thermostat.Runtime.DesiredCool.HasValue ? (thermostat.Runtime.DesiredCool.Value / 10m).ToString() : null; thermostatStatus.Status["desiredHumidity"] = thermostat.Runtime.DesiredHumidity.HasValue ? thermostat.Runtime.DesiredHumidity.Value.ToString() : null; thermostatStatus.Status["desiredDehumidity"] = thermostat.Runtime.DesiredDehumidity.HasValue ? thermostat.Runtime.DesiredDehumidity.Value.ToString() : null; thermostatStatus.Status["desiredFanMode"] = thermostat.Runtime.DesiredFanMode; // Weather forcasts var forecast = thermostat.Weather.Forecasts?.FirstOrDefault(); if (forecast != null) { thermostatStatus.Status["weatherDewPoint"] = forecast.Dewpoint.HasValue ? (forecast.Dewpoint.Value / 10m).ToString() : null; thermostatStatus.Status["weatherPrecipitationChance"] = forecast.Pop.HasValue ? forecast.Pop.Value.ToString() : null; thermostatStatus.Status["weatherPressure"] = forecast.Pressure.HasValue ? (forecast.Pressure.Value / 100m).ToString() : null; thermostatStatus.Status["weatherRelativeHumidity"] = forecast.RelativeHumidity.HasValue ? forecast.RelativeHumidity.Value.ToString() : null; thermostatStatus.Status["weatherTemperature"] = forecast.Temperature.HasValue ? (forecast.Temperature.Value / 10m).ToString() : null; thermostatStatus.Status["weatherTempLow"] = forecast.TempLow.HasValue ? (forecast.TempLow.Value / 10m).ToString() : null; thermostatStatus.Status["weatherTempHigh"] = forecast.TempHigh.HasValue ? (forecast.TempHigh.Value / 10m).ToString() : null; thermostatStatus.Status["weatherVisibility"] = forecast.Visibility.HasValue ? forecast.Visibility.Value.ToString() : null; thermostatStatus.Status["weatherWindBearing"] = forecast.WindBearing.HasValue ? forecast.WindBearing.Value.ToString() : null; thermostatStatus.Status["weatherWindDirection"] = forecast.WindDirection; thermostatStatus.Status["weatherWindGust"] = forecast.WindGust.HasValue ? forecast.WindGust.Value.ToString() : null; thermostatStatus.Status["weatherWindSpeed"] = forecast.WindSpeed.HasValue ? forecast.WindSpeed.Value.ToString() : null; } // Sensors if (thermostat.RemoteSensors != null && thermostat.RemoteSensors.Count > 0) { foreach (var sensor in thermostat.RemoteSensors) { thermostatStatus.Sensors[sensor.Name] = sensor.Capability.ToDictionary( s => s.Type, s => { // Convert temperature values to human readable if (string.Equals(s.Type, "temperature", System.StringComparison.OrdinalIgnoreCase)) { if (decimal.TryParse(s.Value, out var decimalValue)) { return((decimalValue / 10m).ToString()); } } return(s.Value); }); } } // Hold var holdEvent = thermostat.Events.FirstOrDefault(x => x.Type == "hold"); if (holdEvent != null && holdEvent.Running.HasValue && holdEvent.Running.Value) { thermostatStatus.ActiveHold["running"] = holdEvent.Running.HasValue && holdEvent.Running.Value ? "true" : "false"; thermostatStatus.ActiveHold["startTime"] = DateTime.TryParse($"{holdEvent.StartDate} {holdEvent.StartTime}", out var startTimeResult) ? startTimeResult.ToString() : null; thermostatStatus.ActiveHold["endTime"] = DateTime.TryParse($"{holdEvent.EndDate} {holdEvent.EndTime}", out var endTimeResult) ? endTimeResult.ToString() : null; thermostatStatus.ActiveHold["coldHoldTemp"] = holdEvent.CoolHoldTemp.HasValue ? (holdEvent.CoolHoldTemp.Value / 10m).ToString() : null; thermostatStatus.ActiveHold["heatHoldTemp"] = holdEvent.HeatHoldTemp.HasValue ? (holdEvent.HeatHoldTemp.Value / 10m).ToString() : null; thermostatStatus.ActiveHold["fan"] = holdEvent.Fan; thermostatStatus.ActiveHold["fanMinOnTime"] = holdEvent.FanMinOnTime.HasValue ? holdEvent.FanMinOnTime.Value.ToString() : null; thermostatStatus.ActiveHold["vent"] = holdEvent.Vent; thermostatStatus.ActiveHold["ventilatorMinOnTime"] = holdEvent.VentilatorMinOnTime.HasValue ? holdEvent.VentilatorMinOnTime.Value.ToString() : null; } if (_thermostatStatus.ContainsKey(thermostat.Identifier)) { // Publish updates foreach (var device in thermostatStatus.EquipmentStatus) { if (device.Value != _thermostatStatus[thermostat.Identifier].EquipmentStatus[device.Key] && device.Value != null) { await MqttClient.PublishAsync(new MqttApplicationMessageBuilder() .WithTopic($"{TopicRoot}/{thermostat.Identifier}/{device.Key}") .WithPayload(device.Value) .WithAtLeastOnceQoS() .WithRetainFlag() .Build()) .ConfigureAwait(false); } } foreach (var status in thermostatStatus.Status) { if (status.Value != _thermostatStatus[thermostat.Identifier].Status[status.Key] && status.Value != null) { await MqttClient.PublishAsync(new MqttApplicationMessageBuilder() .WithTopic($"{TopicRoot}/{thermostat.Identifier}/{status.Key}") .WithPayload(status.Value) .WithAtLeastOnceQoS() .WithRetainFlag() .Build()) .ConfigureAwait(false); } } // Hold status foreach (var holdStatus in thermostatStatus.ActiveHold) { if (holdStatus.Value != _thermostatStatus[thermostat.Identifier].ActiveHold[holdStatus.Key] && holdStatus.Value != null) { await MqttClient.PublishAsync(new MqttApplicationMessageBuilder() .WithTopic($"{TopicRoot}/{thermostat.Identifier}/hold/{holdStatus.Key}") .WithPayload(holdStatus.Value) .WithAtLeastOnceQoS() .WithRetainFlag() .Build()) .ConfigureAwait(false); } } // Publish everything for new sensors, new capabilities, and changes in existing ability values foreach (var sensor in thermostatStatus.Sensors) { foreach (var sensorCapability in sensor.Value) { if (!_thermostatStatus[thermostat.Identifier].Sensors.ContainsKey(sensor.Key) && sensorCapability.Value != null) { await MqttClient.PublishAsync(new MqttApplicationMessageBuilder() .WithTopic($"{TopicRoot}/{thermostat.Identifier}/sensor/{sensor.Key.Sluggify()}/{sensorCapability.Key}") .WithPayload(sensorCapability.Value) .WithAtLeastOnceQoS() .WithRetainFlag() .Build()) .ConfigureAwait(false); } else if (!_thermostatStatus[thermostat.Identifier].Sensors[sensor.Key].ContainsKey(sensorCapability.Key) && sensorCapability.Value != null) { await MqttClient.PublishAsync(new MqttApplicationMessageBuilder() .WithTopic($"{TopicRoot}/{thermostat.Identifier}/sensor/{sensor.Key.Sluggify()}/{sensorCapability.Key}") .WithPayload(sensorCapability.Value) .WithAtLeastOnceQoS() .WithRetainFlag() .Build()) .ConfigureAwait(false); } else if (sensorCapability.Value != _thermostatStatus[thermostat.Identifier].Sensors[sensor.Key][sensorCapability.Key] && sensorCapability.Value != null) { await MqttClient.PublishAsync(new MqttApplicationMessageBuilder() .WithTopic($"{TopicRoot}/{thermostat.Identifier}/sensor/{sensor.Key.Sluggify()}/{sensorCapability.Key}") .WithPayload(sensorCapability.Value) .WithAtLeastOnceQoS() .WithRetainFlag() .Build()) .ConfigureAwait(false); } } } } else { // Publish initial state foreach (var device in thermostatStatus.EquipmentStatus.Where(x => x.Value != null)) { await MqttClient.PublishAsync(new MqttApplicationMessageBuilder() .WithTopic($"{TopicRoot}/{thermostat.Identifier}/{device.Key}") .WithPayload(device.Value) .WithAtLeastOnceQoS() .WithRetainFlag() .Build()) .ConfigureAwait(false); } foreach (var status in thermostatStatus.Status.Where(x => x.Value != null)) { await MqttClient.PublishAsync(new MqttApplicationMessageBuilder() .WithTopic($"{TopicRoot}/{thermostat.Identifier}/{status.Key}") .WithPayload(status.Value) .WithAtLeastOnceQoS() .WithRetainFlag() .Build()) .ConfigureAwait(false); } // Hold status foreach (var holdStatus in thermostatStatus.ActiveHold.Where(x => x.Value != null)) { await MqttClient.PublishAsync(new MqttApplicationMessageBuilder() .WithTopic($"{TopicRoot}/{thermostat.Identifier}/hold/{holdStatus.Key}") .WithPayload(holdStatus.Value) .WithAtLeastOnceQoS() .WithRetainFlag() .Build()) .ConfigureAwait(false); } foreach (var sensor in thermostatStatus.Sensors) { foreach (var sensorCapability in sensor.Value.Where(x => x.Value != null)) { await MqttClient.PublishAsync(new MqttApplicationMessageBuilder() .WithTopic($"{TopicRoot}/{thermostat.Identifier}/sensor/{sensor.Key.Sluggify()}/{sensorCapability.Key}") .WithPayload(sensorCapability.Value) .WithAtLeastOnceQoS() .WithRetainFlag() .Build()) .ConfigureAwait(false); } } } _thermostatStatus[thermostat.Identifier] = thermostatStatus; }
/// <summary> /// Handles updating status for a thermostat and publishing changes. /// </summary> /// <param name="revisionStatus">Revision status that triggered the update.</param> private async void RefreshThermostatAsync(RevisionStatus revisionStatus) { // Build single thermostat request var thermostatRequest = new ThermostatRequest { Selection = new Selection { SelectionType = "thermostats", SelectionMatch = revisionStatus.ThermostatIdentifier, IncludeEquipmentStatus = true, IncludeDevice = true, IncludeLocation = true, IncludeSettings = true, IncludeRuntime = true, IncludeSensors = true, IncludeWeather = true } }; var thermostatUpdate = await _client.GetAsync <ThermostatRequest, ThermostatResponse>(thermostatRequest) .ConfigureAwait(false); // Publish updates and cache new values var thermostat = thermostatUpdate.ThermostatList.FirstOrDefault(); if (thermostat != null) { var thermostatStatus = new ThermostatStatus(); // Equipment status foreach (var device in thermostat.EquipmentStatus.Split(',').Where(x => !string.IsNullOrEmpty(x))) { thermostatStatus.EquipmentStatus[device] = "on"; } // ID thermostatStatus.Status["name"] = thermostat.Name; thermostatStatus.Status["city"] = thermostat.Location.City; // Status thermostatStatus.Status["hvacMode"] = thermostat.Settings.HvacMode; thermostatStatus.Status["humidifierMode"] = thermostat.Settings.HumidifierMode; thermostatStatus.Status["dehumidifierMode"] = thermostat.Settings.DehumidifierMode; thermostatStatus.Status["autoAway"] = thermostat.Settings.AutoAway ? "true" : "false"; thermostatStatus.Status["vent"] = thermostat.Settings.Vent; thermostatStatus.Status["actualTemperature"] = (thermostat.Runtime.ActualTemperature / 10m).ToString(); thermostatStatus.Status["actualHumidity"] = thermostat.Runtime.ActualHumidity.ToString(); thermostatStatus.Status["desiredHeat"] = (thermostat.Runtime.DesiredHeat / 10m).ToString(); thermostatStatus.Status["desiredCool"] = (thermostat.Runtime.DesiredCool / 10m).ToString(); thermostatStatus.Status["desiredHumidity"] = thermostat.Runtime.DesiredHumidity.ToString(); thermostatStatus.Status["desiredDehumidity"] = thermostat.Runtime.DesiredDehumidity.ToString(); thermostatStatus.Status["desiredFanMode"] = thermostat.Runtime.DesiredFanMode; // Weather forcasts var forecast = thermostat.Weather.Forecasts?.FirstOrDefault(); if (forecast != null) { thermostatStatus.Status["weatherDewPoint"] = (forecast.Dewpoint / 10m).ToString(); thermostatStatus.Status["weatherPrecipitationChance"] = forecast.Pop.ToString(); thermostatStatus.Status["weatherPressure"] = (forecast.Pressure / 100m).ToString(); thermostatStatus.Status["weatherRelativeHumidity"] = forecast.RelativeHumidity.ToString(); thermostatStatus.Status["weatherTemperature"] = (forecast.Temperature / 10m).ToString(); thermostatStatus.Status["weatherTempLow"] = (forecast.TempLow / 10m).ToString(); thermostatStatus.Status["weatherTempHigh"] = (forecast.TempHigh / 10m).ToString(); thermostatStatus.Status["weatherVisibility"] = forecast.Visibility.ToString(); thermostatStatus.Status["weatherWindBearing"] = forecast.WindBearing.ToString(); thermostatStatus.Status["weatherWindDirection"] = forecast.WindDirection.ToString(); thermostatStatus.Status["weatherWindGust"] = forecast.WindGust.ToString(); thermostatStatus.Status["weatherWindSpeed"] = forecast.WindSpeed.ToString(); } // Sensors if (thermostat.RemoteSensors != null && thermostat.RemoteSensors.Count > 0) { foreach (var sensor in thermostat.RemoteSensors) { thermostatStatus.Sensors[sensor.Name] = sensor.Capability.ToDictionary( s => s.Type, s => { // Convert temperature values to human readable if (string.Equals(s.Type, "temperature", System.StringComparison.OrdinalIgnoreCase)) { if (decimal.TryParse(s.Value, out var decimalValue)) { return((decimalValue / 10m).ToString()); } } return(s.Value); }); } } if (_thermostatStatus.ContainsKey(revisionStatus.ThermostatIdentifier)) { // Publish updates foreach (var device in thermostatStatus.EquipmentStatus) { if (device.Value != _thermostatStatus[revisionStatus.ThermostatIdentifier].EquipmentStatus[device.Key]) { await MqttClient.PublishAsync(new MqttApplicationMessageBuilder() .WithTopic($"{TopicRoot}/{revisionStatus.ThermostatIdentifier}/{device.Key}") .WithPayload(device.Value.ToString()) .WithAtLeastOnceQoS() .WithRetainFlag() .Build()) .ConfigureAwait(false); } } foreach (var status in thermostatStatus.Status) { if (status.Value != _thermostatStatus[revisionStatus.ThermostatIdentifier].Status[status.Key]) { await MqttClient.PublishAsync(new MqttApplicationMessageBuilder() .WithTopic($"{TopicRoot}/{revisionStatus.ThermostatIdentifier}/{status.Key}") .WithPayload(status.Value) .WithAtLeastOnceQoS() .WithRetainFlag() .Build()) .ConfigureAwait(false); } } // Publish everything for new sensors, new capabilities, and changes in existing ability values foreach (var sensor in thermostatStatus.Sensors) { foreach (var sensorCapability in sensor.Value) { if (!_thermostatStatus[revisionStatus.ThermostatIdentifier].Sensors.ContainsKey(sensor.Key)) { await MqttClient.PublishAsync(new MqttApplicationMessageBuilder() .WithTopic($"{TopicRoot}/{revisionStatus.ThermostatIdentifier}/sensor/{sensor.Key.Sluggify()}/{sensorCapability.Key}") .WithPayload(sensorCapability.Value) .WithAtLeastOnceQoS() .WithRetainFlag() .Build()) .ConfigureAwait(false); } else if (!_thermostatStatus[revisionStatus.ThermostatIdentifier].Sensors[sensor.Key].ContainsKey(sensorCapability.Key)) { await MqttClient.PublishAsync(new MqttApplicationMessageBuilder() .WithTopic($"{TopicRoot}/{revisionStatus.ThermostatIdentifier}/sensor/{sensor.Key.Sluggify()}/{sensorCapability.Key}") .WithPayload(sensorCapability.Value) .WithAtLeastOnceQoS() .WithRetainFlag() .Build()) .ConfigureAwait(false); } else if (sensorCapability.Value != _thermostatStatus[revisionStatus.ThermostatIdentifier].Sensors[sensor.Key][sensorCapability.Key]) { await MqttClient.PublishAsync(new MqttApplicationMessageBuilder() .WithTopic($"{TopicRoot}/{revisionStatus.ThermostatIdentifier}/sensor/{sensor.Key.Sluggify()}/{sensorCapability.Key}") .WithPayload(sensorCapability.Value) .WithAtLeastOnceQoS() .WithRetainFlag() .Build()) .ConfigureAwait(false); } } } } else { // Publish initial state foreach (var device in thermostatStatus.EquipmentStatus) { await MqttClient.PublishAsync(new MqttApplicationMessageBuilder() .WithTopic($"{TopicRoot}/{revisionStatus.ThermostatIdentifier}/{device.Key}") .WithPayload(device.Value.ToString()) .WithAtLeastOnceQoS() .WithRetainFlag() .Build()) .ConfigureAwait(false); } foreach (var status in thermostatStatus.Status) { await MqttClient.PublishAsync(new MqttApplicationMessageBuilder() .WithTopic($"{TopicRoot}/{revisionStatus.ThermostatIdentifier}/{status.Key}") .WithPayload(status.Value) .WithAtLeastOnceQoS() .WithRetainFlag() .Build()) .ConfigureAwait(false); } foreach (var sensor in thermostatStatus.Sensors) { foreach (var sensorCapability in sensor.Value) { await MqttClient.PublishAsync(new MqttApplicationMessageBuilder() .WithTopic($"{TopicRoot}/{revisionStatus.ThermostatIdentifier}/sensor/{sensor.Key.Sluggify()}/{sensorCapability.Key}") .WithPayload(sensorCapability.Value) .WithAtLeastOnceQoS() .WithRetainFlag() .Build()) .ConfigureAwait(false); } } } _thermostatStatus[revisionStatus.ThermostatIdentifier] = thermostatStatus; } }