/// <summary> /// Heartbeat ping. Failure will result in the heartbeat being stopped, which will /// make any future calls throw an exception as the heartbeat indicator will be disabled. /// </summary> /// <returns>>A <see cref="Task"/> representing the asynchronous operation.</returns> private async Task RefreshAsync() { // Get current revision status var summaryRequest = new ThermostatSummaryRequest { Selection = new Selection { SelectionType = "registered" } }; var status = await _client.GetAsync <ThermostatSummaryRequest, ThermostatSummaryResponse>(summaryRequest); foreach (var thermostatRevision in status.RevisionList) { _log.LogInformation($"Got revision: {thermostatRevision}"); var revisionStatus = new RevisionStatus(thermostatRevision); if (_revisionStatusCache[revisionStatus.ThermostatIdentifier].ThermostatRevision != revisionStatus.ThermostatRevision || _revisionStatusCache[revisionStatus.ThermostatIdentifier].RuntimeRevision != revisionStatus.RuntimeRevision) { await RefreshThermostatAsync(revisionStatus); } // Cache last run values _revisionStatusCache[revisionStatus.ThermostatIdentifier] = revisionStatus; } }
/// <summary> /// Get, cache, and publish initial states. /// </summary> /// <returns>An awaitable <see cref="Task"/>.</returns> private async Task GetInitialStatusAsync() { var summaryRequest = new ThermostatSummaryRequest { Selection = new Selection { SelectionType = "registered" } }; var summary = await _client.GetAsync <ThermostatSummaryRequest, ThermostatSummaryResponse>(summaryRequest) .ConfigureAwait(false); // Set initial revision cache _revisionStatusCache.Clear(); _thermostatStatus.Clear(); foreach (var revision in summary.RevisionList) { _log.LogInformation($"Got revision: {revision}"); var revisionStatus = new RevisionStatus(revision); RefreshThermostatAsync(revisionStatus); _revisionStatusCache.Add(revisionStatus.ThermostatIdentifier, revisionStatus); } }
/// <summary> /// Handles updating status for a thermostat and publishing changes. /// </summary> /// <param name="revisionStatus">Revision status that triggered the update.</param> /// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns> private async Task RefreshThermostatAsync(RevisionStatus revisionStatus) { // Build single thermostat request var thermostatRequest = new ThermostatRequest { Selection = new Selection { SelectionType = "thermostats", SelectionMatch = revisionStatus.ThermostatIdentifier, IncludeEquipmentStatus = true, IncludeEvents = true, IncludeRuntime = true, IncludeSensors = true, IncludeSettings = true, IncludeWeather = true } }; var thermostatUpdate = await _client.GetAsync <ThermostatRequest, ThermostatResponse>(thermostatRequest) .ConfigureAwait(false); // Publish updates and cache new values await UpdateState(thermostatUpdate?.ThermostatList?.FirstOrDefault()); }
/// <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; } }