From 0442380f495992e95512ec8384fefbd4d43c41b8 Mon Sep 17 00:00:00 2001 From: Scott Leckie Date: Tue, 8 Aug 2023 20:25:32 +0100 Subject: [PATCH 1/3] Provide more useful information on disconnection. --- .../AssuredMqttConnection.cs | 20 +++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/src/Extensions/NetDaemon.Extensions.MqttEntityManager/AssuredMqttConnection.cs b/src/Extensions/NetDaemon.Extensions.MqttEntityManager/AssuredMqttConnection.cs index 66be73ea6..9e1e3aa0e 100644 --- a/src/Extensions/NetDaemon.Extensions.MqttEntityManager/AssuredMqttConnection.cs +++ b/src/Extensions/NetDaemon.Extensions.MqttEntityManager/AssuredMqttConnection.cs @@ -1,4 +1,5 @@ -using Microsoft.Extensions.Logging; +using System.Text; +using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using MQTTnet.Client; using MQTTnet.Extensions.ManagedClient; @@ -71,7 +72,7 @@ private async Task ConnectAsync(MqttConfiguration mqttConfig, IMqttFactoryWrappe private Task MqttClientOnDisconnectedAsync(MqttClientDisconnectedEventArgs arg) { - _logger.LogDebug("MQTT disconnected: {Reason}", arg.ConnectResult?.ReasonString); + _logger.LogDebug("MQTT disconnected: {Reason}", BuildErrorResponse(arg)); return Task.CompletedTask; } @@ -81,6 +82,21 @@ private Task MqttClientOnConnectedAsync(MqttClientConnectedEventArgs arg) return Task.CompletedTask; } + private static string BuildErrorResponse(MqttClientDisconnectedEventArgs arg) + { + var sb = new StringBuilder(); + + sb.AppendLine($"{arg.Exception?.Message} ({arg.Reason})"); // Note: arg.ReasonString is always null + var ex = arg.Exception?.InnerException; + while (ex != null) + { + sb.AppendLine(ex.Message); + ex = ex.InnerException; + } + + return sb.ToString(); + } + public void Dispose() { if (_disposed) From 6a074d0a130e4da45ceb1fbf72995cc42155945b Mon Sep 17 00:00:00 2001 From: Scott Leckie Date: Tue, 8 Aug 2023 21:19:14 +0100 Subject: [PATCH 2/3] Add some comments on how to test the MQTT entity subscriptions --- .../Extensions/MttEntitySubscriptionApp.cs | 27 ++++++++++++------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/src/debug/DebugHost/apps/Extensions/MttEntitySubscriptionApp.cs b/src/debug/DebugHost/apps/Extensions/MttEntitySubscriptionApp.cs index b09ceb99e..8307ffd02 100644 --- a/src/debug/DebugHost/apps/Extensions/MttEntitySubscriptionApp.cs +++ b/src/debug/DebugHost/apps/Extensions/MttEntitySubscriptionApp.cs @@ -28,39 +28,48 @@ public async Task InitializeAsync(CancellationToken cancellationToken) await Task.Run(() => ExercisorAsync(cancellationToken), cancellationToken).ConfigureAwait(false); } + /// + /// Important: All this method does is set up the subscriptions that will log when the test switches are toggled + /// To verify this is running: + /// 1. Run the code + /// 2. Check your HomeAssistant instance and you should see two switches named "One" and "Two" + /// 3. Toggle them and ensure that the a message is logged (see code within PrepareCommandSubscriptionAsync...) + /// 4. (optionally) uncomment the code at the end to remove the test switches, rebuild and re-run + /// + /// private async Task ExercisorAsync(CancellationToken cancellationToken) { - var switch1Id = "switch.switch_one"; - var switch2Id = "switch.switch_two"; - var onCommand = "ON"; - var offCommand = "OFF"; + const string switch1Id = "switch.switch_one"; + const string switch2Id = "switch.switch_two"; + const string onCommand = "ON"; + const string offCommand = "OFF"; await _entityManager.CreateAsync(switch1Id, new EntityCreationOptions(Name: "Switch One", PayloadOn: onCommand, PayloadOff: offCommand)) .ConfigureAwait(false); - + await _entityManager.CreateAsync(switch2Id, new EntityCreationOptions(Name: "Switch Two", PayloadOn: onCommand, PayloadOff: offCommand)) .ConfigureAwait(false); - + (await _entityManager.PrepareCommandSubscriptionAsync(switch1Id).ConfigureAwait(false)).Subscribe(new Action(async s => { _logger.LogInformation("Subscription #1a got command for {Switch} {Cmd}", switch1Id, s); await Task.Yield(); // Achieves nothing, just masks the CS1998 warning })); - + (await _entityManager.PrepareCommandSubscriptionAsync(switch1Id).ConfigureAwait(false)).Subscribe(new Action(async s => { _logger.LogInformation("Subscription #1b got command for {Switch} {Cmd}", switch1Id, s); await Task.Yield(); // Achieves nothing, just masks the CS1998 warning })); - + (await _entityManager.PrepareCommandSubscriptionAsync(switch2Id).ConfigureAwait(false)).Subscribe(new Action(async s => { _logger.LogInformation("Subscription #2 got command for {Switch} {Cmd}", switch2Id, s); await Task.Yield(); // Achieves nothing, just masks the CS1998 warning })); - + // Thread.Sleep(2000); // await _entityManager.RemoveAsync(switch1Id).ConfigureAwait(false); // await _entityManager.RemoveAsync(switch2Id).ConfigureAwait(false); From 743c2236bc927ea195230298922413760d4e64c9 Mon Sep 17 00:00:00 2001 From: Scott Leckie Date: Tue, 8 Aug 2023 21:36:08 +0100 Subject: [PATCH 3/3] Refactor MQTT entity creation and exercise app - use consistent IDs and ensure they are deleted when done --- .../apps/Extensions/MqttEntityManagerApp.cs | 66 ++++++++++--------- 1 file changed, 36 insertions(+), 30 deletions(-) diff --git a/src/debug/DebugHost/apps/Extensions/MqttEntityManagerApp.cs b/src/debug/DebugHost/apps/Extensions/MqttEntityManagerApp.cs index 33f4b3661..15722de8d 100644 --- a/src/debug/DebugHost/apps/Extensions/MqttEntityManagerApp.cs +++ b/src/debug/DebugHost/apps/Extensions/MqttEntityManagerApp.cs @@ -45,21 +45,26 @@ private async Task ExercisorAsync(CancellationToken cancellationToken) // Quick entity creation tests // **NOTE THAT THESE ENTITIES ARE REMOVED AT THE END OF THIS method //************************** + const string hotDogSensorId = "binary_sensor.s2"; + const string rainNextHour4Id = "sensor.rain_next_hour4"; + const string basicSensorId = "sensor.basic_sensor"; + const string overrideSensorId = "sensor.my_id"; + const string helToSwitchId = "switch.hel_to_switch"; + const string stateChangeId = "sensor.my_id"; + const string binarySensorId = "binary_sensor.manager_test"; // Create a binary sensor and set its state // Note the use of custom payloads... - var basicSensorId = "binary_sensor.s2"; - await _entityManager.CreateAsync(basicSensorId, new EntityCreationOptions( + await _entityManager.CreateAsync(hotDogSensorId, new EntityCreationOptions( Name: "HotDog sensor", PayloadAvailable: "hot", PayloadNotAvailable: "cold" )).ConfigureAwait(false); - await _entityManager.SetStateAsync(basicSensorId, "cold").ConfigureAwait(false); + await _entityManager.SetStateAsync(hotDogSensorId, "cold").ConfigureAwait(false); // Create a humidity sensor with custom measurement and apply a sequence of values - var rainNexthour4Id = "sensor.rain_nexthour4"; - await _entityManager.CreateAsync(rainNexthour4Id, new EntityCreationOptions( + await _entityManager.CreateAsync(rainNextHour4Id, new EntityCreationOptions( Name: "Rain Next Hour4", DeviceClass: "humidity", PayloadAvailable: "up", @@ -68,61 +73,62 @@ private async Task ExercisorAsync(CancellationToken cancellationToken) new { unit_of_measurement = "mm/h" } ).ConfigureAwait(false); - await _entityManager.SetAvailabilityAsync(rainNexthour4Id, "up").ConfigureAwait(false); - await _entityManager.SetStateAsync(rainNexthour4Id, "3").ConfigureAwait(false); - await _entityManager.SetStateAsync(rainNexthour4Id, "2").ConfigureAwait(false); - await _entityManager.SetStateAsync(rainNexthour4Id, "1").ConfigureAwait(false); - - //************************** - // More in-depth creation and testing of results - //************************** + await _entityManager.SetAvailabilityAsync(rainNextHour4Id, "up").ConfigureAwait(false); + await _entityManager.SetStateAsync(rainNextHour4Id, "3").ConfigureAwait(false); + await _entityManager.SetStateAsync(rainNextHour4Id, "2").ConfigureAwait(false); + await _entityManager.SetStateAsync(rainNextHour4Id, "1").ConfigureAwait(false); // Basic entity creation - await _entityManager.CreateAsync("sensor.basic_sensor").ConfigureAwait(false); + await _entityManager.CreateAsync(basicSensorId).ConfigureAwait(false); // Overriding the default unique ID and name - await _entityManager.CreateAsync("sensor.my_id", + await _entityManager.CreateAsync(overrideSensorId, new EntityCreationOptions(UniqueId: "my_id", Name: "A special kind of sensor")) .ConfigureAwait(false); // Switches require a device class - await _entityManager.CreateAsync("switch.helto_switch", - new EntityCreationOptions(Name: "Helto switch", DeviceClass: "switch")) + await _entityManager.CreateAsync(helToSwitchId, + new EntityCreationOptions(DeviceClass: "switch", Name: "HelTo switch")) .ConfigureAwait(false); // Change state of the new sensor, set an attribute to right now - await _entityManager.SetStateAsync("sensor.my_id", "shiny") + await _entityManager.SetStateAsync(stateChangeId, "shiny") .ConfigureAwait(false); - await _entityManager.SetAttributesAsync("sensor.my_id", new { updated = DateTime.UtcNow }) + await _entityManager.SetAttributesAsync(stateChangeId, new { updated = DateTime.UtcNow }) .ConfigureAwait(false); // Walk through a more complete set of examples, checking HA to verify that each operation completed - _logger.LogInformation("Creating Entity binary_sensor.manager_test"); + _logger.LogInformation("Creating Entity {EntityId}", binarySensorId); var createOptions = new EntityCreationOptions(DeviceClass: "motion", Name: "Manager Test"); - await _entityManager.CreateAsync("binary_sensor.manager_test", createOptions).ConfigureAwait(false); + await _entityManager.CreateAsync(binarySensorId, createOptions).ConfigureAwait(false); // Using Delay to give Mqtt and HA enough time to process events. // Only needed for the example as we immediately read the entity and it may not yet exist await Task.Delay(250, cancellationToken).ConfigureAwait(false); - var entity = _ha.Entity("binary_sensor.manager_test"); - _logger.LogInformation("Entity binary_sensor.manager_test State: {State}", entity.State); + var entity = _ha.Entity(binarySensorId); + _logger.LogInformation("Entity {EntityId} State: {State}",binarySensorId, entity.State); - await _entityManager.SetStateAsync("binary_sensor.manager_test", "ON") + await _entityManager.SetStateAsync(binarySensorId, "ON") .ConfigureAwait(false); - await _entityManager.SetAttributesAsync("binary_sensor.manager_test", new { attribute1 = "attr1" }) + await _entityManager.SetAttributesAsync(binarySensorId, new { attribute1 = "attr1" }) .ConfigureAwait(false); await Task.Delay(250, cancellationToken).ConfigureAwait(false); - _logger.LogInformation("Entity binary_sensor.manager_test State: {State} Attributes: {Attributes}", - entity.State, entity.Attributes); + _logger.LogInformation("Entity {EntityId} State: {State} Attributes: {Attributes}", + binarySensorId, entity.State, entity.Attributes); - await _entityManager.RemoveAsync("binary_sensor.manager_test").ConfigureAwait(false); + await _entityManager.RemoveAsync(binarySensorId).ConfigureAwait(false); await Task.Delay(250, cancellationToken).ConfigureAwait(false); - var removed = _ha.Entity("binary_sensor.manager_test").State == null; + var removed = _ha.Entity(binarySensorId).State == null; _logger.LogInformation("Removed Entity: {Removed}", removed); // Remove other entities + // SET BREAKPOINT HERE if you want to check the entities in Home Assistant + await _entityManager.RemoveAsync(hotDogSensorId).ConfigureAwait(false); + await _entityManager.RemoveAsync(rainNextHour4Id).ConfigureAwait(false); await _entityManager.RemoveAsync(basicSensorId).ConfigureAwait(false); - await _entityManager.RemoveAsync(rainNexthour4Id).ConfigureAwait(false); + await _entityManager.RemoveAsync(overrideSensorId).ConfigureAwait(false); + await _entityManager.RemoveAsync(helToSwitchId).ConfigureAwait(false); + await _entityManager.RemoveAsync(stateChangeId).ConfigureAwait(false); } catch (Exception e) {