From a65105892224e5d9d398dd76d590a891642b6d74 Mon Sep 17 00:00:00 2001 From: Scott Leckie Date: Thu, 3 Feb 2022 23:41:36 +0000 Subject: [PATCH 01/15] Initial commit of MQTT entity management framework. (#656) * Initial commit of MQTT entity management framework. Addable extension method `AddMqttExtensions()`, rough entity creation through `EntityUpdater` (which is probably a poor name) that needs more strongly-typed. * DI scope update from PR comment --- .../DependencyInjectionSetup.cs | 20 ++++++ .../EntityUpdater.cs | 31 +++++++++ .../IEntityUpdater.cs | 6 ++ .../IMessageSender.cs | 6 ++ .../MessageSender.cs | 64 +++++++++++++++++++ .../MqttConfiguration.cs | 9 +++ .../NetDaemon.Extensions.MqttEntities.csproj | 16 +++++ NetDaemon.sln | 15 +++++ 8 files changed, 167 insertions(+) create mode 100644 NetDaemon.Extensions.MqttEntities/DependencyInjectionSetup.cs create mode 100644 NetDaemon.Extensions.MqttEntities/EntityUpdater.cs create mode 100644 NetDaemon.Extensions.MqttEntities/IEntityUpdater.cs create mode 100644 NetDaemon.Extensions.MqttEntities/IMessageSender.cs create mode 100644 NetDaemon.Extensions.MqttEntities/MessageSender.cs create mode 100644 NetDaemon.Extensions.MqttEntities/MqttConfiguration.cs create mode 100644 NetDaemon.Extensions.MqttEntities/NetDaemon.Extensions.MqttEntities.csproj diff --git a/NetDaemon.Extensions.MqttEntities/DependencyInjectionSetup.cs b/NetDaemon.Extensions.MqttEntities/DependencyInjectionSetup.cs new file mode 100644 index 000000000..b07329d1b --- /dev/null +++ b/NetDaemon.Extensions.MqttEntities/DependencyInjectionSetup.cs @@ -0,0 +1,20 @@ +using Microsoft.Extensions.DependencyInjection; +using MQTTnet; + +namespace NetDaemon.Extensions.MqttEntities; + +public static class DependencyInjectionSetup +{ + /// + /// Adds scheduling capabilities through dependency injection + /// + /// Provided service collection + public static IServiceCollection AddMqttExtensions(this IServiceCollection services) + { + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + + return services; + } +} \ No newline at end of file diff --git a/NetDaemon.Extensions.MqttEntities/EntityUpdater.cs b/NetDaemon.Extensions.MqttEntities/EntityUpdater.cs new file mode 100644 index 000000000..a9c046405 --- /dev/null +++ b/NetDaemon.Extensions.MqttEntities/EntityUpdater.cs @@ -0,0 +1,31 @@ +using System.Text.Json; +using Microsoft.Extensions.Logging; + +namespace NetDaemon.Extensions.MqttEntities; + +public class EntityUpdater : IEntityUpdater +{ + private readonly ILogger _logger; + private readonly IMessageSender _messageSender; + + public EntityUpdater(ILogger logger, IMessageSender messageSender) + { + _logger = logger; + _messageSender = messageSender; + } + + public async Task CreateAsync(string deviceType, string deviceClass, string entityId, string name) + { + var rootPath = $"homeassistant/{deviceType}/{entityId}"; + var topicPath = $"{rootPath}/config"; + var statePath = $"{rootPath}/state"; + var attrsPath = $"{rootPath}/attributes"; + + var payload = JsonSerializer.Serialize( new + { + name = name, device_class = deviceClass, state_topic = statePath, json_attributes_topic = attrsPath + }); + + await _messageSender.SendMessageAsync( topicPath, payload); + } +} \ No newline at end of file diff --git a/NetDaemon.Extensions.MqttEntities/IEntityUpdater.cs b/NetDaemon.Extensions.MqttEntities/IEntityUpdater.cs new file mode 100644 index 000000000..0a22f0b65 --- /dev/null +++ b/NetDaemon.Extensions.MqttEntities/IEntityUpdater.cs @@ -0,0 +1,6 @@ +namespace NetDaemon.Extensions.MqttEntities; + +public interface IEntityUpdater +{ + Task CreateAsync(string deviceType, string deviceClass, string entityId, string name); +} \ No newline at end of file diff --git a/NetDaemon.Extensions.MqttEntities/IMessageSender.cs b/NetDaemon.Extensions.MqttEntities/IMessageSender.cs new file mode 100644 index 000000000..b2663117f --- /dev/null +++ b/NetDaemon.Extensions.MqttEntities/IMessageSender.cs @@ -0,0 +1,6 @@ +namespace NetDaemon.Extensions.MqttEntities; + +public interface IMessageSender +{ + Task SendMessageAsync(string topic, string payload); +} \ No newline at end of file diff --git a/NetDaemon.Extensions.MqttEntities/MessageSender.cs b/NetDaemon.Extensions.MqttEntities/MessageSender.cs new file mode 100644 index 000000000..5642e7583 --- /dev/null +++ b/NetDaemon.Extensions.MqttEntities/MessageSender.cs @@ -0,0 +1,64 @@ +using System.Security.Authentication; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Logging; +using MQTTnet; +using MQTTnet.Client; +using MQTTnet.Client.Connecting; +using MQTTnet.Client.Options; +using MQTTnet.Client.Publishing; + +namespace NetDaemon.Extensions.MqttEntities; + +internal class MessageSender : IMessageSender +{ + private readonly ILogger _logger; + private readonly IMqttFactory _mqttFactory; + private readonly MqttConfiguration? _mqttConfig; + + public MessageSender(ILogger logger, IConfiguration configuration, IMqttFactory mqttFactory) + { + _logger = logger; + _mqttFactory = mqttFactory; + + _mqttConfig = configuration.GetSection("Mqtt") + .Get(); + + _logger.LogDebug($"MQTT connection is {_mqttConfig?.Host}:{_mqttConfig?.Port}/{_mqttConfig?.UserId}"); + } + + public async Task SendMessageAsync(string topic, string payload) + { + using (var mqttClient = _mqttFactory.CreateMqttClient()) + { + await ConnectAsync(mqttClient); + await PublishMessage(mqttClient, topic, payload); + } + } + + private async Task ConnectAsync(IMqttClient mqttClient) + { + var options = new MqttClientOptionsBuilder() + .WithTcpServer(_mqttConfig?.Host, _mqttConfig?.Port) + .WithCredentials(_mqttConfig?.UserId, _mqttConfig?.Password) + .Build(); + + var connectResult = await mqttClient.ConnectAsync(options, CancellationToken.None); + if (connectResult.ResultCode != MqttClientConnectResultCode.Success) + throw new AuthenticationException(connectResult.ReasonString); + } + + private async Task PublishMessage(IApplicationMessagePublisher client, string topic, string payload) + { + var message = new MqttApplicationMessageBuilder() + .WithTopic(topic) + .WithPayload(payload) + .WithRetainFlag() + .Build(); + + _logger.LogDebug($"Sending to {message.Topic}:\r\n {message.ConvertPayloadToString()}"); + + var publishResult = await client.PublishAsync(message, CancellationToken.None); + if (publishResult.ReasonCode != MqttClientPublishReasonCode.Success) + throw new InvalidOperationException(publishResult.ReasonString); + } +} \ No newline at end of file diff --git a/NetDaemon.Extensions.MqttEntities/MqttConfiguration.cs b/NetDaemon.Extensions.MqttEntities/MqttConfiguration.cs new file mode 100644 index 000000000..6963b1f9f --- /dev/null +++ b/NetDaemon.Extensions.MqttEntities/MqttConfiguration.cs @@ -0,0 +1,9 @@ +namespace NetDaemon.Extensions.MqttEntities; + +internal class MqttConfiguration +{ + public string? Host { get; set; } + public int? Port { get; set; } + public string? UserId { get; set; } + public string? Password { get; set; } +} \ No newline at end of file diff --git a/NetDaemon.Extensions.MqttEntities/NetDaemon.Extensions.MqttEntities.csproj b/NetDaemon.Extensions.MqttEntities/NetDaemon.Extensions.MqttEntities.csproj new file mode 100644 index 000000000..7bd20349a --- /dev/null +++ b/NetDaemon.Extensions.MqttEntities/NetDaemon.Extensions.MqttEntities.csproj @@ -0,0 +1,16 @@ + + + + net6.0 + enable + enable + + + + + + + + + + diff --git a/NetDaemon.sln b/NetDaemon.sln index e6d6d3504..28c2bfb64 100644 --- a/NetDaemon.sln +++ b/NetDaemon.sln @@ -55,6 +55,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NetDaemon.Extensions.Loggin EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NetDaemon.Extensions.Tts", "src\Extensions\NetDaemon.Extensions.Tts\NetDaemon.Extensions.Tts.csproj", "{F4B29B77-9B92-4037-A884-288CA5EF0B78}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NetDaemon.Extensions.MqttEntities", "NetDaemon.Extensions.MqttEntities\NetDaemon.Extensions.MqttEntities.csproj", "{DE352CE2-977E-4B29-9055-A71F213A55C5}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -269,6 +271,18 @@ Global {F4B29B77-9B92-4037-A884-288CA5EF0B78}.Release|x64.Build.0 = Release|Any CPU {F4B29B77-9B92-4037-A884-288CA5EF0B78}.Release|x86.ActiveCfg = Release|Any CPU {F4B29B77-9B92-4037-A884-288CA5EF0B78}.Release|x86.Build.0 = Release|Any CPU + {DE352CE2-977E-4B29-9055-A71F213A55C5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {DE352CE2-977E-4B29-9055-A71F213A55C5}.Debug|Any CPU.Build.0 = Debug|Any CPU + {DE352CE2-977E-4B29-9055-A71F213A55C5}.Debug|x64.ActiveCfg = Debug|Any CPU + {DE352CE2-977E-4B29-9055-A71F213A55C5}.Debug|x64.Build.0 = Debug|Any CPU + {DE352CE2-977E-4B29-9055-A71F213A55C5}.Debug|x86.ActiveCfg = Debug|Any CPU + {DE352CE2-977E-4B29-9055-A71F213A55C5}.Debug|x86.Build.0 = Debug|Any CPU + {DE352CE2-977E-4B29-9055-A71F213A55C5}.Release|Any CPU.ActiveCfg = Release|Any CPU + {DE352CE2-977E-4B29-9055-A71F213A55C5}.Release|Any CPU.Build.0 = Release|Any CPU + {DE352CE2-977E-4B29-9055-A71F213A55C5}.Release|x64.ActiveCfg = Release|Any CPU + {DE352CE2-977E-4B29-9055-A71F213A55C5}.Release|x64.Build.0 = Release|Any CPU + {DE352CE2-977E-4B29-9055-A71F213A55C5}.Release|x86.ActiveCfg = Release|Any CPU + {DE352CE2-977E-4B29-9055-A71F213A55C5}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -292,6 +306,7 @@ Global {898966EA-F814-4B7B-9A3D-5E78C38174B2} = {E15D4280-7FFC-4F8B-9B8C-CF9AF2BF838C} {00333EBA-DB52-4D56-ADF7-940FB533E530} = {DFF3E7AA-7A50-4A1E-B3F8-EC01531FB83D} {F4B29B77-9B92-4037-A884-288CA5EF0B78} = {DFF3E7AA-7A50-4A1E-B3F8-EC01531FB83D} + {DE352CE2-977E-4B29-9055-A71F213A55C5} = {DFF3E7AA-7A50-4A1E-B3F8-EC01531FB83D} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {7C5FBB7F-654C-4CAC-964F-6D71AF3D62F8} From 228e3fd1c233637252a36f9d8ce5246bf5f4b1b4 Mon Sep 17 00:00:00 2001 From: Eugene Niemand Date: Fri, 4 Feb 2022 00:27:09 +0000 Subject: [PATCH 02/15] Added Update and Remove methods and resolved some of the comments for PR #656 --- .../DependencyInjectionSetup.cs | 6 +-- .../EntityUpdater.cs | 54 ++++++++++++++----- .../IMessageSender.cs | 2 +- .../MessageSender.cs | 49 ++++++++--------- .../MqttConfiguration.cs | 4 +- 5 files changed, 73 insertions(+), 42 deletions(-) diff --git a/NetDaemon.Extensions.MqttEntities/DependencyInjectionSetup.cs b/NetDaemon.Extensions.MqttEntities/DependencyInjectionSetup.cs index b07329d1b..6dc39949c 100644 --- a/NetDaemon.Extensions.MqttEntities/DependencyInjectionSetup.cs +++ b/NetDaemon.Extensions.MqttEntities/DependencyInjectionSetup.cs @@ -9,12 +9,12 @@ public static class DependencyInjectionSetup /// Adds scheduling capabilities through dependency injection /// /// Provided service collection - public static IServiceCollection AddMqttExtensions(this IServiceCollection services) + public static IServiceCollection AddNetDaemonMqttEntityManagement(this IServiceCollection services) { services.AddSingleton(); services.AddSingleton(); - services.AddSingleton(); - + services.AddTransient(); + return services; } } \ No newline at end of file diff --git a/NetDaemon.Extensions.MqttEntities/EntityUpdater.cs b/NetDaemon.Extensions.MqttEntities/EntityUpdater.cs index a9c046405..5b4f2cea1 100644 --- a/NetDaemon.Extensions.MqttEntities/EntityUpdater.cs +++ b/NetDaemon.Extensions.MqttEntities/EntityUpdater.cs @@ -6,26 +6,56 @@ namespace NetDaemon.Extensions.MqttEntities; public class EntityUpdater : IEntityUpdater { private readonly ILogger _logger; - private readonly IMessageSender _messageSender; + private readonly IMessageSender _messageSender; - public EntityUpdater(ILogger logger, IMessageSender messageSender) + internal EntityUpdater(ILogger logger, IMessageSender messageSender) { - _logger = logger; + _logger = logger; _messageSender = messageSender; } - public async Task CreateAsync(string deviceType, string deviceClass, string entityId, string name) + public async Task CreateAsync(string domain, string deviceClass, string entityId, string name) { - var rootPath = $"homeassistant/{deviceType}/{entityId}"; - var topicPath = $"{rootPath}/config"; - var statePath = $"{rootPath}/state"; - var attrsPath = $"{rootPath}/attributes"; - - var payload = JsonSerializer.Serialize( new + var payload = JsonSerializer.Serialize(new { - name = name, device_class = deviceClass, state_topic = statePath, json_attributes_topic = attrsPath + name = name, + device_class = deviceClass, + state_topic = StatePath(domain, entityId), + json_attributes_topic = AttrsPath(domain, entityId) }); - await _messageSender.SendMessageAsync( topicPath, payload); + await _messageSender.SendMessageAsync(ConfigPath(domain, entityId), payload).ConfigureAwait(false); + } + + public async Task UpdateAsync(string domain, string entityId, string state, string? attributes) + { + await _messageSender.SendMessageAsync(StatePath(domain, entityId), state).ConfigureAwait(false); + if (attributes != null) + await _messageSender.SendMessageAsync(AttrsPath(domain, entityId), state).ConfigureAwait(false); + } + + public async Task RemoveAsync(string domain, string entityId) + { + await _messageSender.SendMessageAsync(ConfigPath(domain, entityId), "").ConfigureAwait(false); + } + + private static string AttrsPath(string domain, string entityId) + { + return $"{RootPath(domain, entityId)}/attributes"; + } + + private static string StatePath(string domain, string entityId) + { + return $"{RootPath(domain, entityId)}/state"; + } + + private static string ConfigPath(string domain, string entityId) + { + return $"{RootPath(domain, entityId)}/config"; + } + + private static string RootPath(string domain, string entityId) + { + return $"homeassistant/{domain}/{entityId}"; } } \ No newline at end of file diff --git a/NetDaemon.Extensions.MqttEntities/IMessageSender.cs b/NetDaemon.Extensions.MqttEntities/IMessageSender.cs index b2663117f..c30c85306 100644 --- a/NetDaemon.Extensions.MqttEntities/IMessageSender.cs +++ b/NetDaemon.Extensions.MqttEntities/IMessageSender.cs @@ -1,6 +1,6 @@ namespace NetDaemon.Extensions.MqttEntities; -public interface IMessageSender +internal interface IMessageSender { Task SendMessageAsync(string topic, string payload); } \ No newline at end of file diff --git a/NetDaemon.Extensions.MqttEntities/MessageSender.cs b/NetDaemon.Extensions.MqttEntities/MessageSender.cs index 5642e7583..005c62eaf 100644 --- a/NetDaemon.Extensions.MqttEntities/MessageSender.cs +++ b/NetDaemon.Extensions.MqttEntities/MessageSender.cs @@ -1,48 +1,49 @@ using System.Security.Authentication; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; using MQTTnet; using MQTTnet.Client; using MQTTnet.Client.Connecting; using MQTTnet.Client.Options; using MQTTnet.Client.Publishing; +using MQTTnet.Exceptions; namespace NetDaemon.Extensions.MqttEntities; internal class MessageSender : IMessageSender { private readonly ILogger _logger; - private readonly IMqttFactory _mqttFactory; - private readonly MqttConfiguration? _mqttConfig; - - public MessageSender(ILogger logger, IConfiguration configuration, IMqttFactory mqttFactory) + private readonly IMqttFactory _mqttFactory; + private readonly MqttConfiguration _mqttConfig; + + public MessageSender(ILogger logger, IConfiguration configuration, IMqttFactory mqttFactory, IOptions mqttConfig) { - _logger = logger; + _logger = logger; _mqttFactory = mqttFactory; - - _mqttConfig = configuration.GetSection("Mqtt") - .Get(); - - _logger.LogDebug($"MQTT connection is {_mqttConfig?.Host}:{_mqttConfig?.Port}/{_mqttConfig?.UserId}"); + _mqttConfig = mqttConfig.Value; + + if (_mqttConfig == null) + throw new MqttConfigurationException("The Mqtt config was not found or there was an error loading it"); + + _logger.LogDebug($"MQTT connection is {_mqttConfig.Host}:{_mqttConfig.Port}/{_mqttConfig.UserId}"); } public async Task SendMessageAsync(string topic, string payload) { - using (var mqttClient = _mqttFactory.CreateMqttClient()) - { - await ConnectAsync(mqttClient); - await PublishMessage(mqttClient, topic, payload); - } + using var mqttClient = _mqttFactory.CreateMqttClient(); + await ConnectAsync(mqttClient); + await PublishMessage(mqttClient, topic, payload); } private async Task ConnectAsync(IMqttClient mqttClient) { var options = new MqttClientOptionsBuilder() - .WithTcpServer(_mqttConfig?.Host, _mqttConfig?.Port) - .WithCredentials(_mqttConfig?.UserId, _mqttConfig?.Password) - .Build(); + .WithTcpServer(_mqttConfig.Host, _mqttConfig.Port) + .WithCredentials(_mqttConfig.UserId, _mqttConfig.Password) + .Build(); - var connectResult = await mqttClient.ConnectAsync(options, CancellationToken.None); + var connectResult = await mqttClient.ConnectAsync(options, CancellationToken.None).ConfigureAwait(false); if (connectResult.ResultCode != MqttClientConnectResultCode.Success) throw new AuthenticationException(connectResult.ReasonString); } @@ -50,14 +51,14 @@ private async Task ConnectAsync(IMqttClient mqttClient) private async Task PublishMessage(IApplicationMessagePublisher client, string topic, string payload) { var message = new MqttApplicationMessageBuilder() - .WithTopic(topic) - .WithPayload(payload) - .WithRetainFlag() - .Build(); + .WithTopic(topic) + .WithPayload(payload) + .WithRetainFlag() + .Build(); _logger.LogDebug($"Sending to {message.Topic}:\r\n {message.ConvertPayloadToString()}"); - var publishResult = await client.PublishAsync(message, CancellationToken.None); + var publishResult = await client.PublishAsync(message, CancellationToken.None).ConfigureAwait(false); if (publishResult.ReasonCode != MqttClientPublishReasonCode.Success) throw new InvalidOperationException(publishResult.ReasonString); } diff --git a/NetDaemon.Extensions.MqttEntities/MqttConfiguration.cs b/NetDaemon.Extensions.MqttEntities/MqttConfiguration.cs index 6963b1f9f..27e8a3a2d 100644 --- a/NetDaemon.Extensions.MqttEntities/MqttConfiguration.cs +++ b/NetDaemon.Extensions.MqttEntities/MqttConfiguration.cs @@ -2,8 +2,8 @@ internal class MqttConfiguration { - public string? Host { get; set; } - public int? Port { get; set; } + public string Host { get; set; } + public int Port { get; set; } = 1883; public string? UserId { get; set; } public string? Password { get; set; } } \ No newline at end of file From 30f3184fb378c6ba8cf6cfa0cccc42d31725fa3f Mon Sep 17 00:00:00 2001 From: Eugene Niemand Date: Fri, 4 Feb 2022 08:29:53 +0000 Subject: [PATCH 03/15] Changing names to be less generic --- .../DependencyInjectionSetup.cs | 4 ++-- NetDaemon.Extensions.MqttEntities/IEntityUpdater.cs | 6 ------ NetDaemon.Extensions.MqttEntities/IMessageSender.cs | 2 +- .../IMqttEntityManager.cs | 8 ++++++++ NetDaemon.Extensions.MqttEntities/MessageSender.cs | 2 +- .../MqttConfiguration.cs | 2 +- .../{EntityUpdater.cs => MqttEntityManager.cs} | 12 ++++-------- 7 files changed, 17 insertions(+), 19 deletions(-) delete mode 100644 NetDaemon.Extensions.MqttEntities/IEntityUpdater.cs create mode 100644 NetDaemon.Extensions.MqttEntities/IMqttEntityManager.cs rename NetDaemon.Extensions.MqttEntities/{EntityUpdater.cs => MqttEntityManager.cs} (82%) diff --git a/NetDaemon.Extensions.MqttEntities/DependencyInjectionSetup.cs b/NetDaemon.Extensions.MqttEntities/DependencyInjectionSetup.cs index 6dc39949c..4c06f2301 100644 --- a/NetDaemon.Extensions.MqttEntities/DependencyInjectionSetup.cs +++ b/NetDaemon.Extensions.MqttEntities/DependencyInjectionSetup.cs @@ -1,7 +1,7 @@ using Microsoft.Extensions.DependencyInjection; using MQTTnet; -namespace NetDaemon.Extensions.MqttEntities; +namespace NetDaemon.Extensions.MqttEntityManager; public static class DependencyInjectionSetup { @@ -12,7 +12,7 @@ public static class DependencyInjectionSetup public static IServiceCollection AddNetDaemonMqttEntityManagement(this IServiceCollection services) { services.AddSingleton(); - services.AddSingleton(); + services.AddSingleton(); services.AddTransient(); return services; diff --git a/NetDaemon.Extensions.MqttEntities/IEntityUpdater.cs b/NetDaemon.Extensions.MqttEntities/IEntityUpdater.cs deleted file mode 100644 index 0a22f0b65..000000000 --- a/NetDaemon.Extensions.MqttEntities/IEntityUpdater.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace NetDaemon.Extensions.MqttEntities; - -public interface IEntityUpdater -{ - Task CreateAsync(string deviceType, string deviceClass, string entityId, string name); -} \ No newline at end of file diff --git a/NetDaemon.Extensions.MqttEntities/IMessageSender.cs b/NetDaemon.Extensions.MqttEntities/IMessageSender.cs index c30c85306..861340db1 100644 --- a/NetDaemon.Extensions.MqttEntities/IMessageSender.cs +++ b/NetDaemon.Extensions.MqttEntities/IMessageSender.cs @@ -1,4 +1,4 @@ -namespace NetDaemon.Extensions.MqttEntities; +namespace NetDaemon.Extensions.MqttEntityManager; internal interface IMessageSender { diff --git a/NetDaemon.Extensions.MqttEntities/IMqttEntityManager.cs b/NetDaemon.Extensions.MqttEntities/IMqttEntityManager.cs new file mode 100644 index 000000000..fadbd5c8e --- /dev/null +++ b/NetDaemon.Extensions.MqttEntities/IMqttEntityManager.cs @@ -0,0 +1,8 @@ +namespace NetDaemon.Extensions.MqttEntityManager; + +public interface IMqttEntityManager +{ + Task CreateAsync(string domain, string deviceClass, string entityId, string name); + Task UpdateAsync(string domain, string entityId, string state, string? attributes); + Task RemoveAsync(string domain, string entityId); +} \ No newline at end of file diff --git a/NetDaemon.Extensions.MqttEntities/MessageSender.cs b/NetDaemon.Extensions.MqttEntities/MessageSender.cs index 005c62eaf..3af41978c 100644 --- a/NetDaemon.Extensions.MqttEntities/MessageSender.cs +++ b/NetDaemon.Extensions.MqttEntities/MessageSender.cs @@ -9,7 +9,7 @@ using MQTTnet.Client.Publishing; using MQTTnet.Exceptions; -namespace NetDaemon.Extensions.MqttEntities; +namespace NetDaemon.Extensions.MqttEntityManager; internal class MessageSender : IMessageSender { diff --git a/NetDaemon.Extensions.MqttEntities/MqttConfiguration.cs b/NetDaemon.Extensions.MqttEntities/MqttConfiguration.cs index 27e8a3a2d..6e278d299 100644 --- a/NetDaemon.Extensions.MqttEntities/MqttConfiguration.cs +++ b/NetDaemon.Extensions.MqttEntities/MqttConfiguration.cs @@ -1,4 +1,4 @@ -namespace NetDaemon.Extensions.MqttEntities; +namespace NetDaemon.Extensions.MqttEntityManager; internal class MqttConfiguration { diff --git a/NetDaemon.Extensions.MqttEntities/EntityUpdater.cs b/NetDaemon.Extensions.MqttEntities/MqttEntityManager.cs similarity index 82% rename from NetDaemon.Extensions.MqttEntities/EntityUpdater.cs rename to NetDaemon.Extensions.MqttEntities/MqttEntityManager.cs index 5b4f2cea1..e4995d99c 100644 --- a/NetDaemon.Extensions.MqttEntities/EntityUpdater.cs +++ b/NetDaemon.Extensions.MqttEntities/MqttEntityManager.cs @@ -1,16 +1,13 @@ using System.Text.Json; -using Microsoft.Extensions.Logging; -namespace NetDaemon.Extensions.MqttEntities; +namespace NetDaemon.Extensions.MqttEntityManager; -public class EntityUpdater : IEntityUpdater +public class MqttEntityManager : IMqttEntityManager { - private readonly ILogger _logger; - private readonly IMessageSender _messageSender; + private readonly IMessageSender _messageSender; - internal EntityUpdater(ILogger logger, IMessageSender messageSender) + internal MqttEntityManager(IMessageSender messageSender) { - _logger = logger; _messageSender = messageSender; } @@ -23,7 +20,6 @@ public async Task CreateAsync(string domain, string deviceClass, string entityId state_topic = StatePath(domain, entityId), json_attributes_topic = AttrsPath(domain, entityId) }); - await _messageSender.SendMessageAsync(ConfigPath(domain, entityId), payload).ConfigureAwait(false); } From 4fc6954434e4986cb11b15b14475ce07be164bc1 Mon Sep 17 00:00:00 2001 From: Eugene Niemand Date: Fri, 4 Feb 2022 08:37:31 +0000 Subject: [PATCH 04/15] Moved to src/extensions, renamed folder and fixed warning --- NetDaemon.sln | 36 +++++++++---------- .../DependencyInjectionSetup.cs | 0 .../IMessageSender.cs | 0 .../IMqttEntityManager.cs | 0 .../MessageSender.cs | 0 .../MqttConfiguration.cs | 2 +- .../MqttEntityManager.cs | 0 ...Daemon.Extensions.MqttEntityManager.csproj | 0 8 files changed, 19 insertions(+), 19 deletions(-) rename {NetDaemon.Extensions.MqttEntities => src/Extensions/NetDaemon.Extensions.MqttEntityManager}/DependencyInjectionSetup.cs (100%) rename {NetDaemon.Extensions.MqttEntities => src/Extensions/NetDaemon.Extensions.MqttEntityManager}/IMessageSender.cs (100%) rename {NetDaemon.Extensions.MqttEntities => src/Extensions/NetDaemon.Extensions.MqttEntityManager}/IMqttEntityManager.cs (100%) rename {NetDaemon.Extensions.MqttEntities => src/Extensions/NetDaemon.Extensions.MqttEntityManager}/MessageSender.cs (100%) rename {NetDaemon.Extensions.MqttEntities => src/Extensions/NetDaemon.Extensions.MqttEntityManager}/MqttConfiguration.cs (80%) rename {NetDaemon.Extensions.MqttEntities => src/Extensions/NetDaemon.Extensions.MqttEntityManager}/MqttEntityManager.cs (100%) rename NetDaemon.Extensions.MqttEntities/NetDaemon.Extensions.MqttEntities.csproj => src/Extensions/NetDaemon.Extensions.MqttEntityManager/NetDaemon.Extensions.MqttEntityManager.csproj (100%) diff --git a/NetDaemon.sln b/NetDaemon.sln index 28c2bfb64..5475ad59e 100644 --- a/NetDaemon.sln +++ b/NetDaemon.sln @@ -45,17 +45,17 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NetDaemon.Host.Default", "s EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Host", "Host", "{A6D03AB9-6C8E-42BE-ACF6-7FA6A1C539C2}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NetDaemon.Runtime.Tests", "src\Runtime\NetDaemon.Runtime.Tests\NetDaemon.Runtime.Tests.csproj", "{966C5143-7667-4E85-B7E3-336F7C28549F}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NetDaemon.Runtime.Tests", "src\Runtime\NetDaemon.Runtime.Tests\NetDaemon.Runtime.Tests.csproj", "{966C5143-7667-4E85-B7E3-336F7C28549F}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Debug", "Debug", "{E15D4280-7FFC-4F8B-9B8C-CF9AF2BF838C}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DebugHost", "dev\DebugHost\DebugHost.csproj", "{898966EA-F814-4B7B-9A3D-5E78C38174B2}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DebugHost", "dev\DebugHost\DebugHost.csproj", "{898966EA-F814-4B7B-9A3D-5E78C38174B2}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NetDaemon.Extensions.Logging", "src\Extensions\NetDaemon.Extensions.Logging\NetDaemon.Extensions.Logging.csproj", "{00333EBA-DB52-4D56-ADF7-940FB533E530}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NetDaemon.Extensions.Logging", "src\Extensions\NetDaemon.Extensions.Logging\NetDaemon.Extensions.Logging.csproj", "{00333EBA-DB52-4D56-ADF7-940FB533E530}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NetDaemon.Extensions.Tts", "src\Extensions\NetDaemon.Extensions.Tts\NetDaemon.Extensions.Tts.csproj", "{F4B29B77-9B92-4037-A884-288CA5EF0B78}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NetDaemon.Extensions.Tts", "src\Extensions\NetDaemon.Extensions.Tts\NetDaemon.Extensions.Tts.csproj", "{F4B29B77-9B92-4037-A884-288CA5EF0B78}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NetDaemon.Extensions.MqttEntities", "NetDaemon.Extensions.MqttEntities\NetDaemon.Extensions.MqttEntities.csproj", "{DE352CE2-977E-4B29-9055-A71F213A55C5}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NetDaemon.Extensions.MqttEntityManager", "src\Extensions\NetDaemon.Extensions.MqttEntityManager\NetDaemon.Extensions.MqttEntityManager.csproj", "{3EB8C461-C91E-4900-BFBD-0986CBBE87A6}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -271,18 +271,18 @@ Global {F4B29B77-9B92-4037-A884-288CA5EF0B78}.Release|x64.Build.0 = Release|Any CPU {F4B29B77-9B92-4037-A884-288CA5EF0B78}.Release|x86.ActiveCfg = Release|Any CPU {F4B29B77-9B92-4037-A884-288CA5EF0B78}.Release|x86.Build.0 = Release|Any CPU - {DE352CE2-977E-4B29-9055-A71F213A55C5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {DE352CE2-977E-4B29-9055-A71F213A55C5}.Debug|Any CPU.Build.0 = Debug|Any CPU - {DE352CE2-977E-4B29-9055-A71F213A55C5}.Debug|x64.ActiveCfg = Debug|Any CPU - {DE352CE2-977E-4B29-9055-A71F213A55C5}.Debug|x64.Build.0 = Debug|Any CPU - {DE352CE2-977E-4B29-9055-A71F213A55C5}.Debug|x86.ActiveCfg = Debug|Any CPU - {DE352CE2-977E-4B29-9055-A71F213A55C5}.Debug|x86.Build.0 = Debug|Any CPU - {DE352CE2-977E-4B29-9055-A71F213A55C5}.Release|Any CPU.ActiveCfg = Release|Any CPU - {DE352CE2-977E-4B29-9055-A71F213A55C5}.Release|Any CPU.Build.0 = Release|Any CPU - {DE352CE2-977E-4B29-9055-A71F213A55C5}.Release|x64.ActiveCfg = Release|Any CPU - {DE352CE2-977E-4B29-9055-A71F213A55C5}.Release|x64.Build.0 = Release|Any CPU - {DE352CE2-977E-4B29-9055-A71F213A55C5}.Release|x86.ActiveCfg = Release|Any CPU - {DE352CE2-977E-4B29-9055-A71F213A55C5}.Release|x86.Build.0 = Release|Any CPU + {3EB8C461-C91E-4900-BFBD-0986CBBE87A6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {3EB8C461-C91E-4900-BFBD-0986CBBE87A6}.Debug|Any CPU.Build.0 = Debug|Any CPU + {3EB8C461-C91E-4900-BFBD-0986CBBE87A6}.Debug|x64.ActiveCfg = Debug|Any CPU + {3EB8C461-C91E-4900-BFBD-0986CBBE87A6}.Debug|x64.Build.0 = Debug|Any CPU + {3EB8C461-C91E-4900-BFBD-0986CBBE87A6}.Debug|x86.ActiveCfg = Debug|Any CPU + {3EB8C461-C91E-4900-BFBD-0986CBBE87A6}.Debug|x86.Build.0 = Debug|Any CPU + {3EB8C461-C91E-4900-BFBD-0986CBBE87A6}.Release|Any CPU.ActiveCfg = Release|Any CPU + {3EB8C461-C91E-4900-BFBD-0986CBBE87A6}.Release|Any CPU.Build.0 = Release|Any CPU + {3EB8C461-C91E-4900-BFBD-0986CBBE87A6}.Release|x64.ActiveCfg = Release|Any CPU + {3EB8C461-C91E-4900-BFBD-0986CBBE87A6}.Release|x64.Build.0 = Release|Any CPU + {3EB8C461-C91E-4900-BFBD-0986CBBE87A6}.Release|x86.ActiveCfg = Release|Any CPU + {3EB8C461-C91E-4900-BFBD-0986CBBE87A6}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -306,7 +306,7 @@ Global {898966EA-F814-4B7B-9A3D-5E78C38174B2} = {E15D4280-7FFC-4F8B-9B8C-CF9AF2BF838C} {00333EBA-DB52-4D56-ADF7-940FB533E530} = {DFF3E7AA-7A50-4A1E-B3F8-EC01531FB83D} {F4B29B77-9B92-4037-A884-288CA5EF0B78} = {DFF3E7AA-7A50-4A1E-B3F8-EC01531FB83D} - {DE352CE2-977E-4B29-9055-A71F213A55C5} = {DFF3E7AA-7A50-4A1E-B3F8-EC01531FB83D} + {3EB8C461-C91E-4900-BFBD-0986CBBE87A6} = {DFF3E7AA-7A50-4A1E-B3F8-EC01531FB83D} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {7C5FBB7F-654C-4CAC-964F-6D71AF3D62F8} diff --git a/NetDaemon.Extensions.MqttEntities/DependencyInjectionSetup.cs b/src/Extensions/NetDaemon.Extensions.MqttEntityManager/DependencyInjectionSetup.cs similarity index 100% rename from NetDaemon.Extensions.MqttEntities/DependencyInjectionSetup.cs rename to src/Extensions/NetDaemon.Extensions.MqttEntityManager/DependencyInjectionSetup.cs diff --git a/NetDaemon.Extensions.MqttEntities/IMessageSender.cs b/src/Extensions/NetDaemon.Extensions.MqttEntityManager/IMessageSender.cs similarity index 100% rename from NetDaemon.Extensions.MqttEntities/IMessageSender.cs rename to src/Extensions/NetDaemon.Extensions.MqttEntityManager/IMessageSender.cs diff --git a/NetDaemon.Extensions.MqttEntities/IMqttEntityManager.cs b/src/Extensions/NetDaemon.Extensions.MqttEntityManager/IMqttEntityManager.cs similarity index 100% rename from NetDaemon.Extensions.MqttEntities/IMqttEntityManager.cs rename to src/Extensions/NetDaemon.Extensions.MqttEntityManager/IMqttEntityManager.cs diff --git a/NetDaemon.Extensions.MqttEntities/MessageSender.cs b/src/Extensions/NetDaemon.Extensions.MqttEntityManager/MessageSender.cs similarity index 100% rename from NetDaemon.Extensions.MqttEntities/MessageSender.cs rename to src/Extensions/NetDaemon.Extensions.MqttEntityManager/MessageSender.cs diff --git a/NetDaemon.Extensions.MqttEntities/MqttConfiguration.cs b/src/Extensions/NetDaemon.Extensions.MqttEntityManager/MqttConfiguration.cs similarity index 80% rename from NetDaemon.Extensions.MqttEntities/MqttConfiguration.cs rename to src/Extensions/NetDaemon.Extensions.MqttEntityManager/MqttConfiguration.cs index 6e278d299..ca5bc8064 100644 --- a/NetDaemon.Extensions.MqttEntities/MqttConfiguration.cs +++ b/src/Extensions/NetDaemon.Extensions.MqttEntityManager/MqttConfiguration.cs @@ -2,7 +2,7 @@ internal class MqttConfiguration { - public string Host { get; set; } + public string Host { get; set; } = string.Empty; public int Port { get; set; } = 1883; public string? UserId { get; set; } public string? Password { get; set; } diff --git a/NetDaemon.Extensions.MqttEntities/MqttEntityManager.cs b/src/Extensions/NetDaemon.Extensions.MqttEntityManager/MqttEntityManager.cs similarity index 100% rename from NetDaemon.Extensions.MqttEntities/MqttEntityManager.cs rename to src/Extensions/NetDaemon.Extensions.MqttEntityManager/MqttEntityManager.cs diff --git a/NetDaemon.Extensions.MqttEntities/NetDaemon.Extensions.MqttEntities.csproj b/src/Extensions/NetDaemon.Extensions.MqttEntityManager/NetDaemon.Extensions.MqttEntityManager.csproj similarity index 100% rename from NetDaemon.Extensions.MqttEntities/NetDaemon.Extensions.MqttEntities.csproj rename to src/Extensions/NetDaemon.Extensions.MqttEntityManager/NetDaemon.Extensions.MqttEntityManager.csproj From d6e15d0c4e34c5faa6594e3760a9906a0567f7ec Mon Sep 17 00:00:00 2001 From: Eugene Niemand Date: Fri, 4 Feb 2022 12:03:28 +0000 Subject: [PATCH 05/15] Added example app and fixed IOptions for mqtt config --- dev/DebugHost/DebugHost.csproj | 1 + dev/DebugHost/Program.cs | 35 ++++++++------- dev/DebugHost/Properties/launchSettings.json | 10 +++++ .../apps/Extensions/MqttEntityManagerApp.cs | 45 +++++++++++++++++++ dev/DebugHost/appsettings.json | 3 ++ .../DependencyInjectionSetup.cs | 19 ++++---- .../IMessageSender.cs | 2 +- .../IMqttEntityManager.cs | 2 +- .../MessageSender.cs | 15 +++---- .../MqttConfiguration.cs | 2 +- .../MqttEntityManager.cs | 8 ++-- ...Daemon.Extensions.MqttEntityManager.csproj | 1 + 12 files changed, 102 insertions(+), 41 deletions(-) create mode 100644 dev/DebugHost/Properties/launchSettings.json create mode 100644 dev/DebugHost/apps/Extensions/MqttEntityManagerApp.cs diff --git a/dev/DebugHost/DebugHost.csproj b/dev/DebugHost/DebugHost.csproj index c60fb4bcb..5e89617e8 100644 --- a/dev/DebugHost/DebugHost.csproj +++ b/dev/DebugHost/DebugHost.csproj @@ -28,6 +28,7 @@ + diff --git a/dev/DebugHost/Program.cs b/dev/DebugHost/Program.cs index a581449f6..e850a10e1 100644 --- a/dev/DebugHost/Program.cs +++ b/dev/DebugHost/Program.cs @@ -3,32 +3,35 @@ using NetDaemon.Runtime; using NetDaemon.AppModel; using System.Reflection; +using Microsoft.Extensions.DependencyInjection; using NetDaemon.Extensions.Logging; using NetDaemon.Extensions.Tts; +using NetDaemon.Extensions.MqttEntityManager; #pragma warning disable CA1812 try { await Host.CreateDefaultBuilder(args) - .UseNetDaemonAppSettings() - .UseNetDaemonDefaultLogging() - .UseNetDaemonRuntime() - .UseNetDaemonTextToSpeech() - .ConfigureServices((_, services) => - services - // change type of compilation here - // .AddAppsFromSource(true) - .AddAppsFromAssembly(Assembly.GetEntryAssembly()!) - // Remove this is you are not running the integration! - .AddNetDaemonStateManager() - ) - .Build() - .RunAsync() - .ConfigureAwait(false); + .UseNetDaemonAppSettings() + .UseNetDaemonDefaultLogging() + .UseNetDaemonRuntime() + .UseNetDaemonTextToSpeech() + .UseNetDaemonMqttEntityManagement() + .ConfigureServices((_, services) => + services + // change type of compilation here + // .AddAppsFromSource(true) + .AddAppsFromAssembly(Assembly.GetEntryAssembly()!) + // Remove this is you are not running the integration! + .AddNetDaemonStateManager() + ) + .Build() + .RunAsync() + .ConfigureAwait(false); } catch (Exception e) { Console.WriteLine($"Failed to start host... {e}"); throw; -} +} \ No newline at end of file diff --git a/dev/DebugHost/Properties/launchSettings.json b/dev/DebugHost/Properties/launchSettings.json new file mode 100644 index 000000000..6d8977e7d --- /dev/null +++ b/dev/DebugHost/Properties/launchSettings.json @@ -0,0 +1,10 @@ +{ + "profiles": { + "DebugHost": { + "commandName": "Project", + "environmentVariables": { + "DOTNET_ENVIRONMENT": "Development" + } + } + } +} \ No newline at end of file diff --git a/dev/DebugHost/apps/Extensions/MqttEntityManagerApp.cs b/dev/DebugHost/apps/Extensions/MqttEntityManagerApp.cs new file mode 100644 index 000000000..1c03112ac --- /dev/null +++ b/dev/DebugHost/apps/Extensions/MqttEntityManagerApp.cs @@ -0,0 +1,45 @@ +using System.Text.Json; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Extensions.Logging; +using NetDaemon.AppModel; +using NetDaemon.Extensions.MqttEntityManager; +using NetDaemon.HassModel; + +namespace DebugHost.apps.Extensions; + +[Focus] +[NetDaemonApp] +internal class MqttEntityManagerApp : IAsyncInitializable +{ + private readonly IHaContext _ha; + private readonly ILogger _logger; + private readonly IMqttEntityManager _manager; + + public MqttEntityManagerApp(IHaContext ha, ILogger logger, IMqttEntityManager manager) + { + _ha = ha; + _logger = logger; + _manager = manager; + } + + [System.Diagnostics.CodeAnalysis.SuppressMessage("Naming", "CA1727:Use PascalCase for named placeholders", Justification = "")] + public async Task InitializeAsync(CancellationToken cancellationToken) + { + _logger.LogInformation("Creating Entity {domain}.{entityId}", "binary_sensor", "manager_test"); + await _manager.CreateAsync("binary_sensor", "motion", "manager_test", "Manager Test").ConfigureAwait(false); + await Task.Delay(250, cancellationToken).ConfigureAwait(false); + + var entity = _ha.Entity("binary_sensor.manager_test"); + _logger.LogInformation("Entity {domain}.{entityId} State: {state}", "binary_sensor", "manager_test", entity.State); + + await _manager.UpdateAsync("binary_sensor", "manager_test", "ON", JsonSerializer.Serialize(new { attribute1 = "attr1" })).ConfigureAwait(false); + await Task.Delay(250, cancellationToken).ConfigureAwait(false); + _logger.LogInformation("Entity {domain}.{entityId} State: {state} Attributes: {attributes}", "binary_sensor", "manager_test", entity.State, entity.Attributes); + + await _manager.RemoveAsync("binary_sensor", "manager_test").ConfigureAwait(false); + await Task.Delay(250, cancellationToken).ConfigureAwait(false); + var removed = _ha.Entity("binary_sensor.manager_test").State == null; + _logger.LogInformation("Removed Entity: {removed}", removed); + } +} \ No newline at end of file diff --git a/dev/DebugHost/appsettings.json b/dev/DebugHost/appsettings.json index f97f74f95..8c44f4a7f 100644 --- a/dev/DebugHost/appsettings.json +++ b/dev/DebugHost/appsettings.json @@ -16,5 +16,8 @@ "NetDaemon": { "Admin": true, "ApplicationConfigurationFolder": "./apps" + }, + "MqttConfiguration": { + "Host": "localhost" } } \ No newline at end of file diff --git a/src/Extensions/NetDaemon.Extensions.MqttEntityManager/DependencyInjectionSetup.cs b/src/Extensions/NetDaemon.Extensions.MqttEntityManager/DependencyInjectionSetup.cs index 4c06f2301..e15be7240 100644 --- a/src/Extensions/NetDaemon.Extensions.MqttEntityManager/DependencyInjectionSetup.cs +++ b/src/Extensions/NetDaemon.Extensions.MqttEntityManager/DependencyInjectionSetup.cs @@ -1,20 +1,19 @@ using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; using MQTTnet; namespace NetDaemon.Extensions.MqttEntityManager; public static class DependencyInjectionSetup { - /// - /// Adds scheduling capabilities through dependency injection - /// - /// Provided service collection - public static IServiceCollection AddNetDaemonMqttEntityManagement(this IServiceCollection services) + public static IHostBuilder UseNetDaemonMqttEntityManagement(this IHostBuilder hostBuilder) { - services.AddSingleton(); - services.AddSingleton(); - services.AddTransient(); - - return services; + return hostBuilder.ConfigureServices((context, services) => + { + services.AddSingleton(); + services.AddSingleton(); + services.AddTransient(); + services.Configure(context.Configuration.GetSection(nameof(MqttConfiguration))); + }); } } \ No newline at end of file diff --git a/src/Extensions/NetDaemon.Extensions.MqttEntityManager/IMessageSender.cs b/src/Extensions/NetDaemon.Extensions.MqttEntityManager/IMessageSender.cs index 861340db1..8393f499a 100644 --- a/src/Extensions/NetDaemon.Extensions.MqttEntityManager/IMessageSender.cs +++ b/src/Extensions/NetDaemon.Extensions.MqttEntityManager/IMessageSender.cs @@ -1,6 +1,6 @@ namespace NetDaemon.Extensions.MqttEntityManager; -internal interface IMessageSender +public interface IMessageSender { Task SendMessageAsync(string topic, string payload); } \ No newline at end of file diff --git a/src/Extensions/NetDaemon.Extensions.MqttEntityManager/IMqttEntityManager.cs b/src/Extensions/NetDaemon.Extensions.MqttEntityManager/IMqttEntityManager.cs index fadbd5c8e..00128f3a2 100644 --- a/src/Extensions/NetDaemon.Extensions.MqttEntityManager/IMqttEntityManager.cs +++ b/src/Extensions/NetDaemon.Extensions.MqttEntityManager/IMqttEntityManager.cs @@ -3,6 +3,6 @@ public interface IMqttEntityManager { Task CreateAsync(string domain, string deviceClass, string entityId, string name); - Task UpdateAsync(string domain, string entityId, string state, string? attributes); + Task UpdateAsync(string domain, string entityId, string state, string? attributes = null); Task RemoveAsync(string domain, string entityId); } \ No newline at end of file diff --git a/src/Extensions/NetDaemon.Extensions.MqttEntityManager/MessageSender.cs b/src/Extensions/NetDaemon.Extensions.MqttEntityManager/MessageSender.cs index 3af41978c..c073cf0c1 100644 --- a/src/Extensions/NetDaemon.Extensions.MqttEntityManager/MessageSender.cs +++ b/src/Extensions/NetDaemon.Extensions.MqttEntityManager/MessageSender.cs @@ -23,8 +23,8 @@ public MessageSender(ILogger logger, IConfiguration configuration _mqttFactory = mqttFactory; _mqttConfig = mqttConfig.Value; - if (_mqttConfig == null) - throw new MqttConfigurationException("The Mqtt config was not found or there was an error loading it"); + if (string.IsNullOrEmpty(_mqttConfig.Host)) + throw new MqttConfigurationException("The Mqtt config was not found or there was an error loading it. Please add MqttConfiguration section to appsettings.json"); _logger.LogDebug($"MQTT connection is {_mqttConfig.Host}:{_mqttConfig.Port}/{_mqttConfig.UserId}"); } @@ -50,13 +50,12 @@ private async Task ConnectAsync(IMqttClient mqttClient) private async Task PublishMessage(IApplicationMessagePublisher client, string topic, string payload) { - var message = new MqttApplicationMessageBuilder() - .WithTopic(topic) - .WithPayload(payload) - .WithRetainFlag() - .Build(); + var message = new MqttApplicationMessageBuilder().WithTopic(topic) + .WithPayload(payload) + .WithRetainFlag() + .Build(); - _logger.LogDebug($"Sending to {message.Topic}:\r\n {message.ConvertPayloadToString()}"); + _logger.LogDebug($"Sending to {message.Topic}: {message.ConvertPayloadToString()}"); var publishResult = await client.PublishAsync(message, CancellationToken.None).ConfigureAwait(false); if (publishResult.ReasonCode != MqttClientPublishReasonCode.Success) diff --git a/src/Extensions/NetDaemon.Extensions.MqttEntityManager/MqttConfiguration.cs b/src/Extensions/NetDaemon.Extensions.MqttEntityManager/MqttConfiguration.cs index ca5bc8064..03447d348 100644 --- a/src/Extensions/NetDaemon.Extensions.MqttEntityManager/MqttConfiguration.cs +++ b/src/Extensions/NetDaemon.Extensions.MqttEntityManager/MqttConfiguration.cs @@ -1,6 +1,6 @@ namespace NetDaemon.Extensions.MqttEntityManager; -internal class MqttConfiguration +public class MqttConfiguration { public string Host { get; set; } = string.Empty; public int Port { get; set; } = 1883; diff --git a/src/Extensions/NetDaemon.Extensions.MqttEntityManager/MqttEntityManager.cs b/src/Extensions/NetDaemon.Extensions.MqttEntityManager/MqttEntityManager.cs index e4995d99c..006175ca2 100644 --- a/src/Extensions/NetDaemon.Extensions.MqttEntityManager/MqttEntityManager.cs +++ b/src/Extensions/NetDaemon.Extensions.MqttEntityManager/MqttEntityManager.cs @@ -6,7 +6,7 @@ public class MqttEntityManager : IMqttEntityManager { private readonly IMessageSender _messageSender; - internal MqttEntityManager(IMessageSender messageSender) + public MqttEntityManager(IMessageSender messageSender) { _messageSender = messageSender; } @@ -23,16 +23,16 @@ public async Task CreateAsync(string domain, string deviceClass, string entityId await _messageSender.SendMessageAsync(ConfigPath(domain, entityId), payload).ConfigureAwait(false); } - public async Task UpdateAsync(string domain, string entityId, string state, string? attributes) + public async Task UpdateAsync(string domain, string entityId, string state, string? attributes = null) { await _messageSender.SendMessageAsync(StatePath(domain, entityId), state).ConfigureAwait(false); if (attributes != null) - await _messageSender.SendMessageAsync(AttrsPath(domain, entityId), state).ConfigureAwait(false); + await _messageSender.SendMessageAsync(AttrsPath(domain, entityId), attributes).ConfigureAwait(false); } public async Task RemoveAsync(string domain, string entityId) { - await _messageSender.SendMessageAsync(ConfigPath(domain, entityId), "").ConfigureAwait(false); + await _messageSender.SendMessageAsync(ConfigPath(domain, entityId), string.Empty).ConfigureAwait(false); } private static string AttrsPath(string domain, string entityId) diff --git a/src/Extensions/NetDaemon.Extensions.MqttEntityManager/NetDaemon.Extensions.MqttEntityManager.csproj b/src/Extensions/NetDaemon.Extensions.MqttEntityManager/NetDaemon.Extensions.MqttEntityManager.csproj index 7bd20349a..04246d7e1 100644 --- a/src/Extensions/NetDaemon.Extensions.MqttEntityManager/NetDaemon.Extensions.MqttEntityManager.csproj +++ b/src/Extensions/NetDaemon.Extensions.MqttEntityManager/NetDaemon.Extensions.MqttEntityManager.csproj @@ -9,6 +9,7 @@ + From 577d93cc85518c98d9cf5907948d9cf4582aed5c Mon Sep 17 00:00:00 2001 From: Eugene Niemand Date: Fri, 4 Feb 2022 12:11:36 +0000 Subject: [PATCH 06/15] Fix CI Build error --- dev/DebugHost/apps/Extensions/MqttEntityManagerApp.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dev/DebugHost/apps/Extensions/MqttEntityManagerApp.cs b/dev/DebugHost/apps/Extensions/MqttEntityManagerApp.cs index 1c03112ac..efa3f2707 100644 --- a/dev/DebugHost/apps/Extensions/MqttEntityManagerApp.cs +++ b/dev/DebugHost/apps/Extensions/MqttEntityManagerApp.cs @@ -8,9 +8,9 @@ namespace DebugHost.apps.Extensions; -[Focus] [NetDaemonApp] -internal class MqttEntityManagerApp : IAsyncInitializable +// [Focus] +public class MqttEntityManagerApp : IAsyncInitializable { private readonly IHaContext _ha; private readonly ILogger _logger; From a1fd37f1cd17576a50e517f02ecd9737df46441a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomas=20Hellstr=C3=B6m?= Date: Fri, 4 Feb 2022 15:31:46 +0100 Subject: [PATCH 07/15] Add support for nuget and default host (#660) --- .github/workflows/push_nuget_manual.yml | 2 ++ .github/workflows/tags_nuget.yml | 2 ++ ...NetDaemon.Extensions.MqttEntityManager.csproj | 16 ++++++++++++++++ .../NetDaemon.Host.Default.csproj | 13 +++++++------ src/Host/NetDaemon.Host.Default/Program.cs | 2 ++ 5 files changed, 29 insertions(+), 6 deletions(-) diff --git a/.github/workflows/push_nuget_manual.yml b/.github/workflows/push_nuget_manual.yml index 22f760165..74c10a2ee 100644 --- a/.github/workflows/push_nuget_manual.yml +++ b/.github/workflows/push_nuget_manual.yml @@ -48,5 +48,7 @@ jobs: run: dotnet pack src/Extensions/NetDaemon.Extensions.Logging/NetDaemon.Extensions.Logging.csproj --configuration Release -p:PackageVersion=${{ github.event.inputs.tag }} - name: 🎁 Pack TTS extensions run: dotnet pack src/Extensions/NetDaemon.Extensions.Tts/NetDaemon.Extensions.Tts.csproj --configuration Release -p:PackageVersion=${{ github.event.inputs.tag }} + - name: 🎁 Pack Mqtt extensions + run: dotnet pack src/Extensions/NetDaemon.Extensions.MqttEntityManager/NetDaemon.Extensions.MqttEntityManager.csproj --configuration Release -p:PackageVersion=${{ github.event.inputs.tag }} - name: 📨 Push to nuget run: dotnet nuget push **/*.nupkg --api-key ${{secrets.NUGET_API_KEY}} --source https://api.nuget.org/v3/index.json --no-symbols true diff --git a/.github/workflows/tags_nuget.yml b/.github/workflows/tags_nuget.yml index 228867c8b..e830a6c48 100644 --- a/.github/workflows/tags_nuget.yml +++ b/.github/workflows/tags_nuget.yml @@ -49,5 +49,7 @@ jobs: run: dotnet pack src/Extensions/NetDaemon.Extensions.Logging/NetDaemon.Extensions.Logging.csproj --configuration Release -p:PackageVersion=${{ steps.version.outputs.version }} - name: 🎁 Pack TTS extensions run: dotnet pack src/Extensions/NetDaemon.Extensions.Tts/NetDaemon.Extensions.Tts.csproj --configuration Release -p:PackageVersion=${{ steps.version.outputs.version }} + - name: 🎁 Pack Mqtt extensions + run: dotnet pack src/Extensions/NetDaemon.Extensions.MqttEntityManager/NetDaemon.Extensions.MqttEntityManager.csproj --configuration Release -p:PackageVersion=${{ steps.version.outputs.version }} - name: 📨 Push to nuget run: dotnet nuget push **/*.nupkg --api-key ${{secrets.NUGET_API_KEY}} --source https://api.nuget.org/v3/index.json --no-symbols true diff --git a/src/Extensions/NetDaemon.Extensions.MqttEntityManager/NetDaemon.Extensions.MqttEntityManager.csproj b/src/Extensions/NetDaemon.Extensions.MqttEntityManager/NetDaemon.Extensions.MqttEntityManager.csproj index 04246d7e1..31203f28f 100644 --- a/src/Extensions/NetDaemon.Extensions.MqttEntityManager/NetDaemon.Extensions.MqttEntityManager.csproj +++ b/src/Extensions/NetDaemon.Extensions.MqttEntityManager/NetDaemon.Extensions.MqttEntityManager.csproj @@ -4,6 +4,22 @@ net6.0 enable enable + 10.0 + + NetDaemon + JoySoftware.NetDaemon.Extensions.Mqtt + 22.5.1 + eugeneniemand, skotl + JoySoftware + Mqtt extensions for NetDaemon + https://github.com/net-daemon/netdaemon + MIT + + This package contains the features to manage entities using MQTT for NetDaemon. + + Please advice this is still in beta + Home Assistant, NetDaemon, MQTT + true diff --git a/src/Host/NetDaemon.Host.Default/NetDaemon.Host.Default.csproj b/src/Host/NetDaemon.Host.Default/NetDaemon.Host.Default.csproj index cc6db3b6f..70ea97319 100644 --- a/src/Host/NetDaemon.Host.Default/NetDaemon.Host.Default.csproj +++ b/src/Host/NetDaemon.Host.Default/NetDaemon.Host.Default.csproj @@ -9,7 +9,7 @@ - + runtime; build; native; contentfiles; analyzers; buildtransitive all @@ -27,11 +27,12 @@ - - - - - + + + + + + ..\..\..\.linting\roslynator.ruleset diff --git a/src/Host/NetDaemon.Host.Default/Program.cs b/src/Host/NetDaemon.Host.Default/Program.cs index 74ee1954f..b7a732ced 100644 --- a/src/Host/NetDaemon.Host.Default/Program.cs +++ b/src/Host/NetDaemon.Host.Default/Program.cs @@ -7,6 +7,7 @@ using NetDaemon.Extensions.Tts; using NetDaemon.Runtime; using NetDaemon.HassModel.Integration; +using NetDaemon.Extensions.MqttEntityManager; #pragma warning disable CA1812 // This assembly is not used by the host so we have to force load it @@ -20,6 +21,7 @@ await Host.CreateDefaultBuilder(args) .UseNetDaemonDefaultLogging() .UseNetDaemonRuntime() .UseNetDaemonTextToSpeech() + .UseNetDaemonMqttEntityManagement() .ConfigureServices((_, services) => services .AddAppsFromSource() From c95a4bb455ce7a88e8bb0375ed60ae6a22054781 Mon Sep 17 00:00:00 2001 From: Eugene Niemand Date: Fri, 4 Feb 2022 17:19:28 +0000 Subject: [PATCH 08/15] Added Discovery prefix --- .../apps/Extensions/MqttEntityManagerApp.cs | 11 ++++-- .../MqttConfiguration.cs | 5 +-- .../MqttEntityManager.cs | 35 ++++++++++--------- 3 files changed, 30 insertions(+), 21 deletions(-) diff --git a/dev/DebugHost/apps/Extensions/MqttEntityManagerApp.cs b/dev/DebugHost/apps/Extensions/MqttEntityManagerApp.cs index efa3f2707..977a04b7a 100644 --- a/dev/DebugHost/apps/Extensions/MqttEntityManagerApp.cs +++ b/dev/DebugHost/apps/Extensions/MqttEntityManagerApp.cs @@ -1,4 +1,7 @@ -using System.Text.Json; +#region + +using System.Diagnostics.CodeAnalysis; +using System.Text.Json; using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.Logging; @@ -6,10 +9,12 @@ using NetDaemon.Extensions.MqttEntityManager; using NetDaemon.HassModel; +#endregion + namespace DebugHost.apps.Extensions; [NetDaemonApp] -// [Focus] +[Focus] public class MqttEntityManagerApp : IAsyncInitializable { private readonly IHaContext _ha; @@ -23,7 +28,7 @@ public MqttEntityManagerApp(IHaContext ha, ILogger logger, _manager = manager; } - [System.Diagnostics.CodeAnalysis.SuppressMessage("Naming", "CA1727:Use PascalCase for named placeholders", Justification = "")] + [SuppressMessage("Naming", "CA1727:Use PascalCase for named placeholders", Justification = "")] public async Task InitializeAsync(CancellationToken cancellationToken) { _logger.LogInformation("Creating Entity {domain}.{entityId}", "binary_sensor", "manager_test"); diff --git a/src/Extensions/NetDaemon.Extensions.MqttEntityManager/MqttConfiguration.cs b/src/Extensions/NetDaemon.Extensions.MqttEntityManager/MqttConfiguration.cs index 03447d348..b61069c1c 100644 --- a/src/Extensions/NetDaemon.Extensions.MqttEntityManager/MqttConfiguration.cs +++ b/src/Extensions/NetDaemon.Extensions.MqttEntityManager/MqttConfiguration.cs @@ -2,8 +2,9 @@ public class MqttConfiguration { - public string Host { get; set; } = string.Empty; public int Port { get; set; } = 1883; - public string? UserId { get; set; } + public string DiscoveryPrefix { get; set; } = "homeassistant"; + public string Host { get; set; } = string.Empty; public string? Password { get; set; } + public string? UserId { get; set; } } \ No newline at end of file diff --git a/src/Extensions/NetDaemon.Extensions.MqttEntityManager/MqttEntityManager.cs b/src/Extensions/NetDaemon.Extensions.MqttEntityManager/MqttEntityManager.cs index 006175ca2..b81d37319 100644 --- a/src/Extensions/NetDaemon.Extensions.MqttEntityManager/MqttEntityManager.cs +++ b/src/Extensions/NetDaemon.Extensions.MqttEntityManager/MqttEntityManager.cs @@ -1,14 +1,17 @@ using System.Text.Json; +using Microsoft.Extensions.Options; namespace NetDaemon.Extensions.MqttEntityManager; public class MqttEntityManager : IMqttEntityManager { - private readonly IMessageSender _messageSender; + private readonly IMessageSender _messageSender; + private readonly MqttConfiguration _config; - public MqttEntityManager(IMessageSender messageSender) + public MqttEntityManager(IMessageSender messageSender, IOptions config) { _messageSender = messageSender; + _config = config.Value; } public async Task CreateAsync(string domain, string deviceClass, string entityId, string name) @@ -17,41 +20,41 @@ public async Task CreateAsync(string domain, string deviceClass, string entityId { name = name, device_class = deviceClass, - state_topic = StatePath(domain, entityId), - json_attributes_topic = AttrsPath(domain, entityId) + state_topic = StatePath(_config.DiscoveryPrefix, domain, entityId), + json_attributes_topic = AttrsPath(_config.DiscoveryPrefix, domain, entityId) }); - await _messageSender.SendMessageAsync(ConfigPath(domain, entityId), payload).ConfigureAwait(false); + await _messageSender.SendMessageAsync(ConfigPath(_config.DiscoveryPrefix, domain, entityId), payload).ConfigureAwait(false); } public async Task UpdateAsync(string domain, string entityId, string state, string? attributes = null) { - await _messageSender.SendMessageAsync(StatePath(domain, entityId), state).ConfigureAwait(false); + await _messageSender.SendMessageAsync(StatePath(_config.DiscoveryPrefix, domain, entityId), state).ConfigureAwait(false); if (attributes != null) - await _messageSender.SendMessageAsync(AttrsPath(domain, entityId), attributes).ConfigureAwait(false); + await _messageSender.SendMessageAsync(AttrsPath(_config.DiscoveryPrefix, domain, entityId), attributes).ConfigureAwait(false); } public async Task RemoveAsync(string domain, string entityId) { - await _messageSender.SendMessageAsync(ConfigPath(domain, entityId), string.Empty).ConfigureAwait(false); + await _messageSender.SendMessageAsync(ConfigPath(_config.DiscoveryPrefix, domain, entityId), string.Empty).ConfigureAwait(false); } - private static string AttrsPath(string domain, string entityId) + private static string AttrsPath(string discoveryPrefix, string domain, string entityId) { - return $"{RootPath(domain, entityId)}/attributes"; + return $"{RootPath(discoveryPrefix, domain, entityId)}/attributes"; } - private static string StatePath(string domain, string entityId) + private static string StatePath(string discoveryPrefix, string domain, string entityId) { - return $"{RootPath(domain, entityId)}/state"; + return $"{RootPath(discoveryPrefix, domain, entityId)}/state"; } - private static string ConfigPath(string domain, string entityId) + private static string ConfigPath(string discoveryPrefix, string domain, string entityId) { - return $"{RootPath(domain, entityId)}/config"; + return $"{RootPath(discoveryPrefix, domain, entityId)}/config"; } - private static string RootPath(string domain, string entityId) + private static string RootPath(string discoveryPrefix, string domain, string entityId) { - return $"homeassistant/{domain}/{entityId}"; + return $"{discoveryPrefix}/{domain}/{entityId}"; } } \ No newline at end of file From 15a917db16be57c23555cf5b2e4b1c56906bc039 Mon Sep 17 00:00:00 2001 From: Scott Leckie Date: Fri, 4 Feb 2022 20:05:56 +0000 Subject: [PATCH 09/15] MQTT connect and publish can throw exceptions as well as return error codes, so handle each case in a consistent manner by throwing a new MqttConnection or MqttPublish exception. Add try...catch to the test app so that we can see when exceptions are thrown in called methods. --- .../apps/Extensions/MqttEntityManagerApp.cs | 42 ++++++++++++------- .../Exceptions/MqttConnectionException.cs | 10 +++++ .../Exceptions/MqttPublishException.cs | 10 +++++ .../MessageSender.cs | 33 +++++++++++---- 4 files changed, 72 insertions(+), 23 deletions(-) create mode 100644 src/Extensions/NetDaemon.Extensions.MqttEntityManager/Exceptions/MqttConnectionException.cs create mode 100644 src/Extensions/NetDaemon.Extensions.MqttEntityManager/Exceptions/MqttPublishException.cs diff --git a/dev/DebugHost/apps/Extensions/MqttEntityManagerApp.cs b/dev/DebugHost/apps/Extensions/MqttEntityManagerApp.cs index 977a04b7a..9472ac4dd 100644 --- a/dev/DebugHost/apps/Extensions/MqttEntityManagerApp.cs +++ b/dev/DebugHost/apps/Extensions/MqttEntityManagerApp.cs @@ -1,5 +1,6 @@ #region +using System; using System.Diagnostics.CodeAnalysis; using System.Text.Json; using System.Threading; @@ -31,20 +32,31 @@ public MqttEntityManagerApp(IHaContext ha, ILogger logger, [SuppressMessage("Naming", "CA1727:Use PascalCase for named placeholders", Justification = "")] public async Task InitializeAsync(CancellationToken cancellationToken) { - _logger.LogInformation("Creating Entity {domain}.{entityId}", "binary_sensor", "manager_test"); - await _manager.CreateAsync("binary_sensor", "motion", "manager_test", "Manager Test").ConfigureAwait(false); - await Task.Delay(250, cancellationToken).ConfigureAwait(false); - - var entity = _ha.Entity("binary_sensor.manager_test"); - _logger.LogInformation("Entity {domain}.{entityId} State: {state}", "binary_sensor", "manager_test", entity.State); - - await _manager.UpdateAsync("binary_sensor", "manager_test", "ON", JsonSerializer.Serialize(new { attribute1 = "attr1" })).ConfigureAwait(false); - await Task.Delay(250, cancellationToken).ConfigureAwait(false); - _logger.LogInformation("Entity {domain}.{entityId} State: {state} Attributes: {attributes}", "binary_sensor", "manager_test", entity.State, entity.Attributes); - - await _manager.RemoveAsync("binary_sensor", "manager_test").ConfigureAwait(false); - await Task.Delay(250, cancellationToken).ConfigureAwait(false); - var removed = _ha.Entity("binary_sensor.manager_test").State == null; - _logger.LogInformation("Removed Entity: {removed}", removed); + try + { + + _logger.LogInformation("Creating Entity {domain}.{entityId}", "binary_sensor", "manager_test"); + await _manager.CreateAsync("binary_sensor", "motion", "manager_test", "Manager Test"); + await Task.Delay(250, cancellationToken).ConfigureAwait(false); + + var entity = _ha.Entity("binary_sensor.manager_test"); + _logger.LogInformation("Entity {domain}.{entityId} State: {state}", "binary_sensor", "manager_test", + entity.State); + + await _manager.UpdateAsync("binary_sensor", "manager_test", "ON", + JsonSerializer.Serialize(new { attribute1 = "attr1" })).ConfigureAwait(false); + await Task.Delay(250, cancellationToken).ConfigureAwait(false); + _logger.LogInformation("Entity {domain}.{entityId} State: {state} Attributes: {attributes}", + "binary_sensor", "manager_test", entity.State, entity.Attributes); + + await _manager.RemoveAsync("binary_sensor", "manager_test").ConfigureAwait(false); + await Task.Delay(250, cancellationToken).ConfigureAwait(false); + var removed = _ha.Entity("binary_sensor.manager_test").State == null; + _logger.LogInformation("Removed Entity: {removed}", removed); + } + catch (Exception e) + { + _logger.LogError(e, e.Message); + } } } \ No newline at end of file diff --git a/src/Extensions/NetDaemon.Extensions.MqttEntityManager/Exceptions/MqttConnectionException.cs b/src/Extensions/NetDaemon.Extensions.MqttEntityManager/Exceptions/MqttConnectionException.cs new file mode 100644 index 000000000..65f6f9281 --- /dev/null +++ b/src/Extensions/NetDaemon.Extensions.MqttEntityManager/Exceptions/MqttConnectionException.cs @@ -0,0 +1,10 @@ +namespace NetDaemon.Extensions.MqttEntityManager.Exceptions; + +public class MqttConnectionException : Exception +{ + public MqttConnectionException(string msg) : base(msg) + {} + + public MqttConnectionException(string msg, Exception innerException) : base(msg, innerException) + {} +} \ No newline at end of file diff --git a/src/Extensions/NetDaemon.Extensions.MqttEntityManager/Exceptions/MqttPublishException.cs b/src/Extensions/NetDaemon.Extensions.MqttEntityManager/Exceptions/MqttPublishException.cs new file mode 100644 index 000000000..c7594a849 --- /dev/null +++ b/src/Extensions/NetDaemon.Extensions.MqttEntityManager/Exceptions/MqttPublishException.cs @@ -0,0 +1,10 @@ +namespace NetDaemon.Extensions.MqttEntityManager.Exceptions; + +public class MqttPublishException : Exception +{ + public MqttPublishException(string msg) : base(msg) + {} + + public MqttPublishException(string msg, Exception innerException) : base(msg, innerException) + {} +} \ No newline at end of file diff --git a/src/Extensions/NetDaemon.Extensions.MqttEntityManager/MessageSender.cs b/src/Extensions/NetDaemon.Extensions.MqttEntityManager/MessageSender.cs index c073cf0c1..36b2388f5 100644 --- a/src/Extensions/NetDaemon.Extensions.MqttEntityManager/MessageSender.cs +++ b/src/Extensions/NetDaemon.Extensions.MqttEntityManager/MessageSender.cs @@ -1,5 +1,4 @@ -using System.Security.Authentication; -using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using MQTTnet; @@ -8,6 +7,7 @@ using MQTTnet.Client.Options; using MQTTnet.Client.Publishing; using MQTTnet.Exceptions; +using NetDaemon.Extensions.MqttEntityManager.Exceptions; namespace NetDaemon.Extensions.MqttEntityManager; @@ -43,9 +43,18 @@ private async Task ConnectAsync(IMqttClient mqttClient) .WithCredentials(_mqttConfig.UserId, _mqttConfig.Password) .Build(); - var connectResult = await mqttClient.ConnectAsync(options, CancellationToken.None).ConfigureAwait(false); - if (connectResult.ResultCode != MqttClientConnectResultCode.Success) - throw new AuthenticationException(connectResult.ReasonString); + try + { + var connectResult = + await mqttClient.ConnectAsync(options, CancellationToken.None).ConfigureAwait(false); + if (connectResult.ResultCode != MqttClientConnectResultCode.Success) + throw new MqttConnectionException(connectResult.ReasonString); + } + catch (Exception e) + { + _logger.LogError(e, e.Message); + throw new MqttConnectionException(e.Message, e); + } } private async Task PublishMessage(IApplicationMessagePublisher client, string topic, string payload) @@ -57,8 +66,16 @@ private async Task PublishMessage(IApplicationMessagePublisher client, string to _logger.LogDebug($"Sending to {message.Topic}: {message.ConvertPayloadToString()}"); - var publishResult = await client.PublishAsync(message, CancellationToken.None).ConfigureAwait(false); - if (publishResult.ReasonCode != MqttClientPublishReasonCode.Success) - throw new InvalidOperationException(publishResult.ReasonString); + try + { + var publishResult = await client.PublishAsync(message, CancellationToken.None).ConfigureAwait(false); + if (publishResult.ReasonCode != MqttClientPublishReasonCode.Success) + throw new MqttPublishException(publishResult.ReasonString); + } + catch (Exception e) + { + _logger.LogError(e.Message, e); + throw new MqttPublishException(e.Message, e); + } } } \ No newline at end of file From 72b109b934eeac6ce9a55a74f6f37054f9db75c3 Mon Sep 17 00:00:00 2001 From: Scott Leckie Date: Fri, 4 Feb 2022 20:23:18 +0000 Subject: [PATCH 10/15] 1. Add XML comments 2. Remove unused IConfiguration arg from MessageSender constructor --- .../DependencyInjectionSetup.cs | 8 ++++++ .../Exceptions/MqttConnectionException.cs | 12 +++++++++ .../Exceptions/MqttPublishException.cs | 12 +++++++++ .../IMessageSender.cs | 9 +++++++ .../IMqttEntityManager.cs | 14 ++++++++++ .../MessageSender.cs | 17 +++++++++++- .../MqttConfiguration.cs | 22 +++++++++++++++ .../MqttEntityManager.cs | 27 +++++++++++++++++++ 8 files changed, 120 insertions(+), 1 deletion(-) diff --git a/src/Extensions/NetDaemon.Extensions.MqttEntityManager/DependencyInjectionSetup.cs b/src/Extensions/NetDaemon.Extensions.MqttEntityManager/DependencyInjectionSetup.cs index e15be7240..50792864a 100644 --- a/src/Extensions/NetDaemon.Extensions.MqttEntityManager/DependencyInjectionSetup.cs +++ b/src/Extensions/NetDaemon.Extensions.MqttEntityManager/DependencyInjectionSetup.cs @@ -4,8 +4,16 @@ namespace NetDaemon.Extensions.MqttEntityManager; +/// +/// DI setup for Mqtt Entity Manager +/// public static class DependencyInjectionSetup { + /// + /// Add support for managing entities via MQTT + /// + /// + /// public static IHostBuilder UseNetDaemonMqttEntityManagement(this IHostBuilder hostBuilder) { return hostBuilder.ConfigureServices((context, services) => diff --git a/src/Extensions/NetDaemon.Extensions.MqttEntityManager/Exceptions/MqttConnectionException.cs b/src/Extensions/NetDaemon.Extensions.MqttEntityManager/Exceptions/MqttConnectionException.cs index 65f6f9281..5464e5a0b 100644 --- a/src/Extensions/NetDaemon.Extensions.MqttEntityManager/Exceptions/MqttConnectionException.cs +++ b/src/Extensions/NetDaemon.Extensions.MqttEntityManager/Exceptions/MqttConnectionException.cs @@ -1,10 +1,22 @@ namespace NetDaemon.Extensions.MqttEntityManager.Exceptions; +/// +/// MQTT connection failed +/// public class MqttConnectionException : Exception { + /// + /// MQTT connection failed + /// + /// public MqttConnectionException(string msg) : base(msg) {} + /// + /// MQTT connection failed + /// + /// + /// public MqttConnectionException(string msg, Exception innerException) : base(msg, innerException) {} } \ No newline at end of file diff --git a/src/Extensions/NetDaemon.Extensions.MqttEntityManager/Exceptions/MqttPublishException.cs b/src/Extensions/NetDaemon.Extensions.MqttEntityManager/Exceptions/MqttPublishException.cs index c7594a849..855e48ae1 100644 --- a/src/Extensions/NetDaemon.Extensions.MqttEntityManager/Exceptions/MqttPublishException.cs +++ b/src/Extensions/NetDaemon.Extensions.MqttEntityManager/Exceptions/MqttPublishException.cs @@ -1,10 +1,22 @@ namespace NetDaemon.Extensions.MqttEntityManager.Exceptions; +/// +/// Failed to publish a message to MQTT +/// public class MqttPublishException : Exception { + /// + /// Failed to publish a message to MQTT + /// + /// public MqttPublishException(string msg) : base(msg) {} + /// + /// Failed to publish a message to MQTT + /// + /// + /// public MqttPublishException(string msg, Exception innerException) : base(msg, innerException) {} } \ No newline at end of file diff --git a/src/Extensions/NetDaemon.Extensions.MqttEntityManager/IMessageSender.cs b/src/Extensions/NetDaemon.Extensions.MqttEntityManager/IMessageSender.cs index 8393f499a..350db96ab 100644 --- a/src/Extensions/NetDaemon.Extensions.MqttEntityManager/IMessageSender.cs +++ b/src/Extensions/NetDaemon.Extensions.MqttEntityManager/IMessageSender.cs @@ -1,6 +1,15 @@ namespace NetDaemon.Extensions.MqttEntityManager; +/// +/// Interface to send messages to MQTT +/// public interface IMessageSender { + /// + /// Send a message for the given payload to the MQTT topic + /// + /// + /// + /// Task SendMessageAsync(string topic, string payload); } \ No newline at end of file diff --git a/src/Extensions/NetDaemon.Extensions.MqttEntityManager/IMqttEntityManager.cs b/src/Extensions/NetDaemon.Extensions.MqttEntityManager/IMqttEntityManager.cs index 00128f3a2..011c905a0 100644 --- a/src/Extensions/NetDaemon.Extensions.MqttEntityManager/IMqttEntityManager.cs +++ b/src/Extensions/NetDaemon.Extensions.MqttEntityManager/IMqttEntityManager.cs @@ -1,8 +1,22 @@ namespace NetDaemon.Extensions.MqttEntityManager; +/// +/// Interface for managing entities via MQTT +/// public interface IMqttEntityManager { + /// + /// Create an entity in Home Assistant via MQTT + /// Task CreateAsync(string domain, string deviceClass, string entityId, string name); + + /// + /// Update state and, optionally, attributes of an HA entity via MQTT + /// Task UpdateAsync(string domain, string entityId, string state, string? attributes = null); + + /// + /// Remove an entity from Home Assistant + /// Task RemoveAsync(string domain, string entityId); } \ No newline at end of file diff --git a/src/Extensions/NetDaemon.Extensions.MqttEntityManager/MessageSender.cs b/src/Extensions/NetDaemon.Extensions.MqttEntityManager/MessageSender.cs index 36b2388f5..3a746e7e1 100644 --- a/src/Extensions/NetDaemon.Extensions.MqttEntityManager/MessageSender.cs +++ b/src/Extensions/NetDaemon.Extensions.MqttEntityManager/MessageSender.cs @@ -11,13 +11,23 @@ namespace NetDaemon.Extensions.MqttEntityManager; +/// +/// Manage connections and message publishing to MQTT +/// internal class MessageSender : IMessageSender { private readonly ILogger _logger; private readonly IMqttFactory _mqttFactory; private readonly MqttConfiguration _mqttConfig; - public MessageSender(ILogger logger, IConfiguration configuration, IMqttFactory mqttFactory, IOptions mqttConfig) + /// + /// Manage connections and message publishing to MQTT + /// + /// + /// + /// + /// + public MessageSender(ILogger logger, IMqttFactory mqttFactory, IOptions mqttConfig) { _logger = logger; _mqttFactory = mqttFactory; @@ -29,6 +39,11 @@ public MessageSender(ILogger logger, IConfiguration configuration _logger.LogDebug($"MQTT connection is {_mqttConfig.Host}:{_mqttConfig.Port}/{_mqttConfig.UserId}"); } + /// + /// Connect to MQTT and publish a message to the given topic + /// + /// + /// Json structure of payload public async Task SendMessageAsync(string topic, string payload) { using var mqttClient = _mqttFactory.CreateMqttClient(); diff --git a/src/Extensions/NetDaemon.Extensions.MqttEntityManager/MqttConfiguration.cs b/src/Extensions/NetDaemon.Extensions.MqttEntityManager/MqttConfiguration.cs index b61069c1c..55fa79c8a 100644 --- a/src/Extensions/NetDaemon.Extensions.MqttEntityManager/MqttConfiguration.cs +++ b/src/Extensions/NetDaemon.Extensions.MqttEntityManager/MqttConfiguration.cs @@ -1,10 +1,32 @@ namespace NetDaemon.Extensions.MqttEntityManager; +/// +/// Configuration model for MQTT +/// public class MqttConfiguration { + /// + /// Port to connect on, defaults to 1883 + /// public int Port { get; set; } = 1883; + + /// + /// Discovery Prefix, defaults to "homeassistant" + /// public string DiscoveryPrefix { get; set; } = "homeassistant"; + + /// + /// Host address of MQTT broker + /// public string Host { get; set; } = string.Empty; + + /// + /// User ID to connect to MQTT broker + /// public string? Password { get; set; } + + /// + /// Password to connect to MQTT broker + /// public string? UserId { get; set; } } \ No newline at end of file diff --git a/src/Extensions/NetDaemon.Extensions.MqttEntityManager/MqttEntityManager.cs b/src/Extensions/NetDaemon.Extensions.MqttEntityManager/MqttEntityManager.cs index b81d37319..c1efa28ce 100644 --- a/src/Extensions/NetDaemon.Extensions.MqttEntityManager/MqttEntityManager.cs +++ b/src/Extensions/NetDaemon.Extensions.MqttEntityManager/MqttEntityManager.cs @@ -3,17 +3,32 @@ namespace NetDaemon.Extensions.MqttEntityManager; +/// +/// Manage entities via MQTT +/// public class MqttEntityManager : IMqttEntityManager { private readonly IMessageSender _messageSender; private readonly MqttConfiguration _config; + /// + /// Manage entities via MQTT + /// + /// + /// public MqttEntityManager(IMessageSender messageSender, IOptions config) { _messageSender = messageSender; _config = config.Value; } + /// + /// Create an entity in Home Assistant via MQTT + /// + /// + /// + /// + /// public async Task CreateAsync(string domain, string deviceClass, string entityId, string name) { var payload = JsonSerializer.Serialize(new @@ -26,6 +41,13 @@ public async Task CreateAsync(string domain, string deviceClass, string entityId await _messageSender.SendMessageAsync(ConfigPath(_config.DiscoveryPrefix, domain, entityId), payload).ConfigureAwait(false); } + /// + /// Update state and, optionally, attributes of an HA entity via MQTT + /// + /// + /// + /// + /// Json string of attributes public async Task UpdateAsync(string domain, string entityId, string state, string? attributes = null) { await _messageSender.SendMessageAsync(StatePath(_config.DiscoveryPrefix, domain, entityId), state).ConfigureAwait(false); @@ -33,6 +55,11 @@ public async Task UpdateAsync(string domain, string entityId, string state, stri await _messageSender.SendMessageAsync(AttrsPath(_config.DiscoveryPrefix, domain, entityId), attributes).ConfigureAwait(false); } + /// + /// Remove an entity from Home Assistant + /// + /// + /// public async Task RemoveAsync(string domain, string entityId) { await _messageSender.SendMessageAsync(ConfigPath(_config.DiscoveryPrefix, domain, entityId), string.Empty).ConfigureAwait(false); From 455efc78420631c2a94e609cd4249a10d83911cb Mon Sep 17 00:00:00 2001 From: Eugene Niemand Date: Fri, 4 Feb 2022 23:30:43 +0000 Subject: [PATCH 11/15] Resolving PR comments --- .../apps/Extensions/MqttEntityManagerApp.cs | 2 +- .../DependencyInjectionSetup.cs | 8 ++- .../IMqttEntityManager.cs | 7 +-- .../MessageSender.cs | 12 +++-- .../MqttEntityManager.cs | 52 ++++++++----------- 5 files changed, 41 insertions(+), 40 deletions(-) diff --git a/dev/DebugHost/apps/Extensions/MqttEntityManagerApp.cs b/dev/DebugHost/apps/Extensions/MqttEntityManagerApp.cs index 9472ac4dd..d27a21045 100644 --- a/dev/DebugHost/apps/Extensions/MqttEntityManagerApp.cs +++ b/dev/DebugHost/apps/Extensions/MqttEntityManagerApp.cs @@ -36,7 +36,7 @@ public async Task InitializeAsync(CancellationToken cancellationToken) { _logger.LogInformation("Creating Entity {domain}.{entityId}", "binary_sensor", "manager_test"); - await _manager.CreateAsync("binary_sensor", "motion", "manager_test", "Manager Test"); + await _manager.CreateAsync("binary_sensor", "manager_test", "motion", "Manager Test"); await Task.Delay(250, cancellationToken).ConfigureAwait(false); var entity = _ha.Entity("binary_sensor.manager_test"); diff --git a/src/Extensions/NetDaemon.Extensions.MqttEntityManager/DependencyInjectionSetup.cs b/src/Extensions/NetDaemon.Extensions.MqttEntityManager/DependencyInjectionSetup.cs index 50792864a..48d63f647 100644 --- a/src/Extensions/NetDaemon.Extensions.MqttEntityManager/DependencyInjectionSetup.cs +++ b/src/Extensions/NetDaemon.Extensions.MqttEntityManager/DependencyInjectionSetup.cs @@ -1,7 +1,11 @@ -using Microsoft.Extensions.DependencyInjection; +#region + +using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using MQTTnet; +#endregion + namespace NetDaemon.Extensions.MqttEntityManager; /// @@ -21,7 +25,7 @@ public static IHostBuilder UseNetDaemonMqttEntityManagement(this IHostBuilder ho services.AddSingleton(); services.AddSingleton(); services.AddTransient(); - services.Configure(context.Configuration.GetSection(nameof(MqttConfiguration))); + services.Configure(context.Configuration.GetSection("Mqtt")); }); } } \ No newline at end of file diff --git a/src/Extensions/NetDaemon.Extensions.MqttEntityManager/IMqttEntityManager.cs b/src/Extensions/NetDaemon.Extensions.MqttEntityManager/IMqttEntityManager.cs index 011c905a0..a572ba2ae 100644 --- a/src/Extensions/NetDaemon.Extensions.MqttEntityManager/IMqttEntityManager.cs +++ b/src/Extensions/NetDaemon.Extensions.MqttEntityManager/IMqttEntityManager.cs @@ -5,18 +5,19 @@ /// public interface IMqttEntityManager { - /// + /// /// Create an entity in Home Assistant via MQTT /// - Task CreateAsync(string domain, string deviceClass, string entityId, string name); + Task CreateAsync(string domain, string entityId, string deviceClass, string name); + /// /// Update state and, optionally, attributes of an HA entity via MQTT /// - Task UpdateAsync(string domain, string entityId, string state, string? attributes = null); /// /// Remove an entity from Home Assistant /// Task RemoveAsync(string domain, string entityId); + Task UpdateAsync(string domain, string entityId, string state, string? attributes = null); } \ No newline at end of file diff --git a/src/Extensions/NetDaemon.Extensions.MqttEntityManager/MessageSender.cs b/src/Extensions/NetDaemon.Extensions.MqttEntityManager/MessageSender.cs index 3a746e7e1..738977206 100644 --- a/src/Extensions/NetDaemon.Extensions.MqttEntityManager/MessageSender.cs +++ b/src/Extensions/NetDaemon.Extensions.MqttEntityManager/MessageSender.cs @@ -1,4 +1,6 @@ -using Microsoft.Extensions.Configuration; +#region + +using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using MQTTnet; @@ -9,6 +11,8 @@ using MQTTnet.Exceptions; using NetDaemon.Extensions.MqttEntityManager.Exceptions; +#endregion + namespace NetDaemon.Extensions.MqttEntityManager; /// @@ -17,8 +21,8 @@ namespace NetDaemon.Extensions.MqttEntityManager; internal class MessageSender : IMessageSender { private readonly ILogger _logger; - private readonly IMqttFactory _mqttFactory; private readonly MqttConfiguration _mqttConfig; + private readonly IMqttFactory _mqttFactory; /// /// Manage connections and message publishing to MQTT @@ -36,7 +40,7 @@ public MessageSender(ILogger logger, IMqttFactory mqttFactory, IO if (string.IsNullOrEmpty(_mqttConfig.Host)) throw new MqttConfigurationException("The Mqtt config was not found or there was an error loading it. Please add MqttConfiguration section to appsettings.json"); - _logger.LogDebug($"MQTT connection is {_mqttConfig.Host}:{_mqttConfig.Port}/{_mqttConfig.UserId}"); + _logger.LogDebug("MQTT connection is {host}:{port}/{userId}", _mqttConfig.Host, _mqttConfig.Port, _mqttConfig.UserId); } /// @@ -79,7 +83,7 @@ private async Task PublishMessage(IApplicationMessagePublisher client, string to .WithRetainFlag() .Build(); - _logger.LogDebug($"Sending to {message.Topic}: {message.ConvertPayloadToString()}"); + _logger.LogDebug("Sending to {topic}: {message}", message.Topic, message.ConvertPayloadToString()); try { diff --git a/src/Extensions/NetDaemon.Extensions.MqttEntityManager/MqttEntityManager.cs b/src/Extensions/NetDaemon.Extensions.MqttEntityManager/MqttEntityManager.cs index c1efa28ce..24e77212f 100644 --- a/src/Extensions/NetDaemon.Extensions.MqttEntityManager/MqttEntityManager.cs +++ b/src/Extensions/NetDaemon.Extensions.MqttEntityManager/MqttEntityManager.cs @@ -1,6 +1,10 @@ -using System.Text.Json; +#region + +using System.Text.Json; using Microsoft.Extensions.Options; +#endregion + namespace NetDaemon.Extensions.MqttEntityManager; /// @@ -8,8 +12,8 @@ namespace NetDaemon.Extensions.MqttEntityManager; /// public class MqttEntityManager : IMqttEntityManager { - private readonly IMessageSender _messageSender; private readonly MqttConfiguration _config; + private readonly IMessageSender _messageSender; /// /// Manage entities via MQTT @@ -22,23 +26,23 @@ public MqttEntityManager(IMessageSender messageSender, IOptions + /// /// Create an entity in Home Assistant via MQTT /// /// /// /// /// - public async Task CreateAsync(string domain, string deviceClass, string entityId, string name) + public async Task CreateAsync(string domain, string entityId, string deviceClass, string name) { var payload = JsonSerializer.Serialize(new { - name = name, + name, device_class = deviceClass, - state_topic = StatePath(_config.DiscoveryPrefix, domain, entityId), - json_attributes_topic = AttrsPath(_config.DiscoveryPrefix, domain, entityId) + state_topic = StatePath(domain, entityId), + json_attributes_topic = AttrsPath(domain, entityId) }); - await _messageSender.SendMessageAsync(ConfigPath(_config.DiscoveryPrefix, domain, entityId), payload).ConfigureAwait(false); + await _messageSender.SendMessageAsync(ConfigPath(domain, entityId), payload).ConfigureAwait(false); } /// @@ -48,13 +52,6 @@ public async Task CreateAsync(string domain, string deviceClass, string entityId /// /// /// Json string of attributes - public async Task UpdateAsync(string domain, string entityId, string state, string? attributes = null) - { - await _messageSender.SendMessageAsync(StatePath(_config.DiscoveryPrefix, domain, entityId), state).ConfigureAwait(false); - if (attributes != null) - await _messageSender.SendMessageAsync(AttrsPath(_config.DiscoveryPrefix, domain, entityId), attributes).ConfigureAwait(false); - } - /// /// Remove an entity from Home Assistant /// @@ -62,26 +59,21 @@ public async Task UpdateAsync(string domain, string entityId, string state, stri /// public async Task RemoveAsync(string domain, string entityId) { - await _messageSender.SendMessageAsync(ConfigPath(_config.DiscoveryPrefix, domain, entityId), string.Empty).ConfigureAwait(false); + await _messageSender.SendMessageAsync(ConfigPath(domain, entityId), string.Empty).ConfigureAwait(false); } - private static string AttrsPath(string discoveryPrefix, string domain, string entityId) + public async Task UpdateAsync(string domain, string entityId, string state, string? attributes = null) { - return $"{RootPath(discoveryPrefix, domain, entityId)}/attributes"; + await _messageSender.SendMessageAsync(StatePath(domain, entityId), state).ConfigureAwait(false); + if (attributes != null) + await _messageSender.SendMessageAsync(AttrsPath(domain, entityId), attributes).ConfigureAwait(false); } - private static string StatePath(string discoveryPrefix, string domain, string entityId) - { - return $"{RootPath(discoveryPrefix, domain, entityId)}/state"; - } + private string AttrsPath(string domain, string entityId) => $"{RootPath(domain, entityId)}/attributes"; - private static string ConfigPath(string discoveryPrefix, string domain, string entityId) - { - return $"{RootPath(discoveryPrefix, domain, entityId)}/config"; - } + private string ConfigPath(string domain, string entityId) => $"{RootPath(domain, entityId)}/config"; - private static string RootPath(string discoveryPrefix, string domain, string entityId) - { - return $"{discoveryPrefix}/{domain}/{entityId}"; - } + private string RootPath(string domain, string entityId) => $"{_config.DiscoveryPrefix}/{domain}/{entityId}"; + + private string StatePath(string domain, string entityId) => $"{RootPath(domain, entityId)}/state"; } \ No newline at end of file From 7889701b767f0e093720b99f24e3974b36b69bd6 Mon Sep 17 00:00:00 2001 From: Eugene Niemand Date: Fri, 4 Feb 2022 23:52:38 +0000 Subject: [PATCH 12/15] Merged Skotl's code and fixed hard coded retain flag --- .../apps/Extensions/MqttEntityManagerApp.cs | 13 ++++----- dev/DebugHost/appsettings.json | 2 +- .../IMessageSender.cs | 7 +++-- .../IMqttEntityManager.cs | 21 +++++++------- .../MessageSender.cs | 16 +++++----- .../MqttEntityManager.cs | 29 ++++++++++--------- 6 files changed, 45 insertions(+), 43 deletions(-) diff --git a/dev/DebugHost/apps/Extensions/MqttEntityManagerApp.cs b/dev/DebugHost/apps/Extensions/MqttEntityManagerApp.cs index d27a21045..f855c4810 100644 --- a/dev/DebugHost/apps/Extensions/MqttEntityManagerApp.cs +++ b/dev/DebugHost/apps/Extensions/MqttEntityManagerApp.cs @@ -30,26 +30,24 @@ public MqttEntityManagerApp(IHaContext ha, ILogger logger, } [SuppressMessage("Naming", "CA1727:Use PascalCase for named placeholders", Justification = "")] + [SuppressMessage("Reliability", "CA2007:Consider calling ConfigureAwait on the awaited task", Justification = "")] public async Task InitializeAsync(CancellationToken cancellationToken) { try { - _logger.LogInformation("Creating Entity {domain}.{entityId}", "binary_sensor", "manager_test"); - await _manager.CreateAsync("binary_sensor", "manager_test", "motion", "Manager Test"); + await _manager.CreateAsync("binary_sensor", "manager_test", "motion", "Manager Test"); await Task.Delay(250, cancellationToken).ConfigureAwait(false); var entity = _ha.Entity("binary_sensor.manager_test"); - _logger.LogInformation("Entity {domain}.{entityId} State: {state}", "binary_sensor", "manager_test", - entity.State); + _logger.LogInformation("Entity {domain}.{entityId} State: {state}", "binary_sensor", "manager_test", entity.State); - await _manager.UpdateAsync("binary_sensor", "manager_test", "ON", - JsonSerializer.Serialize(new { attribute1 = "attr1" })).ConfigureAwait(false); + await _manager.UpdateAsync("binary_sensor", "manager_test", "ON", JsonSerializer.Serialize(new { attribute1 = "attr1" })); await Task.Delay(250, cancellationToken).ConfigureAwait(false); _logger.LogInformation("Entity {domain}.{entityId} State: {state} Attributes: {attributes}", "binary_sensor", "manager_test", entity.State, entity.Attributes); - await _manager.RemoveAsync("binary_sensor", "manager_test").ConfigureAwait(false); + await _manager.RemoveAsync("binary_sensor", "manager_test"); await Task.Delay(250, cancellationToken).ConfigureAwait(false); var removed = _ha.Entity("binary_sensor.manager_test").State == null; _logger.LogInformation("Removed Entity: {removed}", removed); @@ -57,6 +55,7 @@ await _manager.UpdateAsync("binary_sensor", "manager_test", "ON", catch (Exception e) { _logger.LogError(e, e.Message); + throw; } } } \ No newline at end of file diff --git a/dev/DebugHost/appsettings.json b/dev/DebugHost/appsettings.json index 8c44f4a7f..d5a62c8ba 100644 --- a/dev/DebugHost/appsettings.json +++ b/dev/DebugHost/appsettings.json @@ -17,7 +17,7 @@ "Admin": true, "ApplicationConfigurationFolder": "./apps" }, - "MqttConfiguration": { + "Mqtt": { "Host": "localhost" } } \ No newline at end of file diff --git a/src/Extensions/NetDaemon.Extensions.MqttEntityManager/IMessageSender.cs b/src/Extensions/NetDaemon.Extensions.MqttEntityManager/IMessageSender.cs index 350db96ab..a177a893f 100644 --- a/src/Extensions/NetDaemon.Extensions.MqttEntityManager/IMessageSender.cs +++ b/src/Extensions/NetDaemon.Extensions.MqttEntityManager/IMessageSender.cs @@ -1,15 +1,16 @@ namespace NetDaemon.Extensions.MqttEntityManager; /// -/// Interface to send messages to MQTT +/// Interface to send messages to MQTT /// public interface IMessageSender { /// - /// Send a message for the given payload to the MQTT topic + /// Send a message for the given payload to the MQTT topic /// /// /// + /// /// - Task SendMessageAsync(string topic, string payload); + Task SendMessageAsync(string topic, string payload, bool retain = false); } \ No newline at end of file diff --git a/src/Extensions/NetDaemon.Extensions.MqttEntityManager/IMqttEntityManager.cs b/src/Extensions/NetDaemon.Extensions.MqttEntityManager/IMqttEntityManager.cs index a572ba2ae..faefa0ea9 100644 --- a/src/Extensions/NetDaemon.Extensions.MqttEntityManager/IMqttEntityManager.cs +++ b/src/Extensions/NetDaemon.Extensions.MqttEntityManager/IMqttEntityManager.cs @@ -1,23 +1,24 @@ namespace NetDaemon.Extensions.MqttEntityManager; /// -/// Interface for managing entities via MQTT +/// Interface for managing entities via MQTT /// public interface IMqttEntityManager { - /// - /// Create an entity in Home Assistant via MQTT - /// - Task CreateAsync(string domain, string entityId, string deviceClass, string name); - - /// - /// Update state and, optionally, attributes of an HA entity via MQTT + /// Create an entity in Home Assistant via MQTT /// - + Task CreateAsync(string domain, string entityId, string deviceClass, string name, bool persist = true); + + /// - /// Remove an entity from Home Assistant + /// Remove an entity from Home Assistant /// Task RemoveAsync(string domain, string entityId); + + + /// + /// Update state and, optionally, attributes of an HA entity via MQTT + /// Task UpdateAsync(string domain, string entityId, string state, string? attributes = null); } \ No newline at end of file diff --git a/src/Extensions/NetDaemon.Extensions.MqttEntityManager/MessageSender.cs b/src/Extensions/NetDaemon.Extensions.MqttEntityManager/MessageSender.cs index 738977206..c866d938e 100644 --- a/src/Extensions/NetDaemon.Extensions.MqttEntityManager/MessageSender.cs +++ b/src/Extensions/NetDaemon.Extensions.MqttEntityManager/MessageSender.cs @@ -1,6 +1,5 @@ #region -using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using MQTTnet; @@ -16,7 +15,7 @@ namespace NetDaemon.Extensions.MqttEntityManager; /// -/// Manage connections and message publishing to MQTT +/// Manage connections and message publishing to MQTT /// internal class MessageSender : IMessageSender { @@ -25,7 +24,7 @@ internal class MessageSender : IMessageSender private readonly IMqttFactory _mqttFactory; /// - /// Manage connections and message publishing to MQTT + /// Manage connections and message publishing to MQTT /// /// /// @@ -44,15 +43,16 @@ public MessageSender(ILogger logger, IMqttFactory mqttFactory, IO } /// - /// Connect to MQTT and publish a message to the given topic + /// Connect to MQTT and publish a message to the given topic /// /// /// Json structure of payload - public async Task SendMessageAsync(string topic, string payload) + /// + public async Task SendMessageAsync(string topic, string payload, bool retain = false) { using var mqttClient = _mqttFactory.CreateMqttClient(); await ConnectAsync(mqttClient); - await PublishMessage(mqttClient, topic, payload); + await PublishMessage(mqttClient, topic, payload, retain); } private async Task ConnectAsync(IMqttClient mqttClient) @@ -76,11 +76,11 @@ private async Task ConnectAsync(IMqttClient mqttClient) } } - private async Task PublishMessage(IApplicationMessagePublisher client, string topic, string payload) + private async Task PublishMessage(IApplicationMessagePublisher client, string topic, string payload, bool retain) { var message = new MqttApplicationMessageBuilder().WithTopic(topic) .WithPayload(payload) - .WithRetainFlag() + .WithRetainFlag(retain) .Build(); _logger.LogDebug("Sending to {topic}: {message}", message.Topic, message.ConvertPayloadToString()); diff --git a/src/Extensions/NetDaemon.Extensions.MqttEntityManager/MqttEntityManager.cs b/src/Extensions/NetDaemon.Extensions.MqttEntityManager/MqttEntityManager.cs index 24e77212f..4e58c6093 100644 --- a/src/Extensions/NetDaemon.Extensions.MqttEntityManager/MqttEntityManager.cs +++ b/src/Extensions/NetDaemon.Extensions.MqttEntityManager/MqttEntityManager.cs @@ -8,7 +8,7 @@ namespace NetDaemon.Extensions.MqttEntityManager; /// -/// Manage entities via MQTT +/// Manage entities via MQTT /// public class MqttEntityManager : IMqttEntityManager { @@ -16,7 +16,7 @@ public class MqttEntityManager : IMqttEntityManager private readonly IMessageSender _messageSender; /// - /// Manage entities via MQTT + /// Manage entities via MQTT /// /// /// @@ -26,14 +26,15 @@ public MqttEntityManager(IMessageSender messageSender, IOptions - /// Create an entity in Home Assistant via MQTT + /// + /// Create an entity in Home Assistant via MQTT /// /// /// /// /// - public async Task CreateAsync(string domain, string entityId, string deviceClass, string name) + /// This will persist the entity if Home Assistant restarts + public async Task CreateAsync(string domain, string entityId, string deviceClass, string name, bool persist = true) { var payload = JsonSerializer.Serialize(new { @@ -42,18 +43,11 @@ public async Task CreateAsync(string domain, string entityId, string deviceClass state_topic = StatePath(domain, entityId), json_attributes_topic = AttrsPath(domain, entityId) }); - await _messageSender.SendMessageAsync(ConfigPath(domain, entityId), payload).ConfigureAwait(false); + await _messageSender.SendMessageAsync(ConfigPath(domain, entityId), payload, persist).ConfigureAwait(false); } /// - /// Update state and, optionally, attributes of an HA entity via MQTT - /// - /// - /// - /// - /// Json string of attributes - /// - /// Remove an entity from Home Assistant + /// Remove an entity from Home Assistant /// /// /// @@ -62,6 +56,13 @@ public async Task RemoveAsync(string domain, string entityId) await _messageSender.SendMessageAsync(ConfigPath(domain, entityId), string.Empty).ConfigureAwait(false); } + /// + /// Update state and, optionally, attributes of an HA entity via MQTT + /// + /// + /// + /// + /// Json string of attributes public async Task UpdateAsync(string domain, string entityId, string state, string? attributes = null) { await _messageSender.SendMessageAsync(StatePath(domain, entityId), state).ConfigureAwait(false); From 753a43b79d9e5bfcfc9d38f6961032dc013194b1 Mon Sep 17 00:00:00 2001 From: Eugene Niemand Date: Sat, 5 Feb 2022 00:43:40 +0000 Subject: [PATCH 13/15] Removing throw and fixed warning. Added comment for Delay --- dev/DebugHost/apps/Extensions/MqttEntityManagerApp.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/dev/DebugHost/apps/Extensions/MqttEntityManagerApp.cs b/dev/DebugHost/apps/Extensions/MqttEntityManagerApp.cs index f855c4810..e8ed3ab46 100644 --- a/dev/DebugHost/apps/Extensions/MqttEntityManagerApp.cs +++ b/dev/DebugHost/apps/Extensions/MqttEntityManagerApp.cs @@ -31,12 +31,15 @@ public MqttEntityManagerApp(IHaContext ha, ILogger logger, [SuppressMessage("Naming", "CA1727:Use PascalCase for named placeholders", Justification = "")] [SuppressMessage("Reliability", "CA2007:Consider calling ConfigureAwait on the awaited task", Justification = "")] + [SuppressMessage("Design", "CA1031:Do not catch general exception types", Justification = "We need to log unexpected errors")] public async Task InitializeAsync(CancellationToken cancellationToken) { try { _logger.LogInformation("Creating Entity {domain}.{entityId}", "binary_sensor", "manager_test"); await _manager.CreateAsync("binary_sensor", "manager_test", "motion", "Manager Test"); + // 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"); @@ -55,7 +58,6 @@ public async Task InitializeAsync(CancellationToken cancellationToken) catch (Exception e) { _logger.LogError(e, e.Message); - throw; } } } \ No newline at end of file From f5da8c1721f3e4a19c3b64f33ddf2b8bfd47b170 Mon Sep 17 00:00:00 2001 From: Scott Leckie Date: Sat, 5 Feb 2022 10:53:47 +0000 Subject: [PATCH 14/15] address PR comments --- .../apps/Extensions/MqttEntityManagerApp.cs | 8 ++++---- .../MessageSender.cs | 15 ++++++++------- .../MqttConfiguration.cs | 7 ++++--- 3 files changed, 16 insertions(+), 14 deletions(-) diff --git a/dev/DebugHost/apps/Extensions/MqttEntityManagerApp.cs b/dev/DebugHost/apps/Extensions/MqttEntityManagerApp.cs index e8ed3ab46..e2452d3d6 100644 --- a/dev/DebugHost/apps/Extensions/MqttEntityManagerApp.cs +++ b/dev/DebugHost/apps/Extensions/MqttEntityManagerApp.cs @@ -30,14 +30,13 @@ public MqttEntityManagerApp(IHaContext ha, ILogger logger, } [SuppressMessage("Naming", "CA1727:Use PascalCase for named placeholders", Justification = "")] - [SuppressMessage("Reliability", "CA2007:Consider calling ConfigureAwait on the awaited task", Justification = "")] [SuppressMessage("Design", "CA1031:Do not catch general exception types", Justification = "We need to log unexpected errors")] public async Task InitializeAsync(CancellationToken cancellationToken) { try { _logger.LogInformation("Creating Entity {domain}.{entityId}", "binary_sensor", "manager_test"); - await _manager.CreateAsync("binary_sensor", "manager_test", "motion", "Manager Test"); + await _manager.CreateAsync("binary_sensor", "manager_test", "motion", "Manager Test").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); @@ -45,12 +44,13 @@ public async Task InitializeAsync(CancellationToken cancellationToken) var entity = _ha.Entity("binary_sensor.manager_test"); _logger.LogInformation("Entity {domain}.{entityId} State: {state}", "binary_sensor", "manager_test", entity.State); - await _manager.UpdateAsync("binary_sensor", "manager_test", "ON", JsonSerializer.Serialize(new { attribute1 = "attr1" })); + await _manager.UpdateAsync("binary_sensor", "manager_test", "ON", JsonSerializer.Serialize(new { attribute1 = "attr1" })) + .ConfigureAwait(false); await Task.Delay(250, cancellationToken).ConfigureAwait(false); _logger.LogInformation("Entity {domain}.{entityId} State: {state} Attributes: {attributes}", "binary_sensor", "manager_test", entity.State, entity.Attributes); - await _manager.RemoveAsync("binary_sensor", "manager_test"); + await _manager.RemoveAsync("binary_sensor", "manager_test").ConfigureAwait(false); await Task.Delay(250, cancellationToken).ConfigureAwait(false); var removed = _ha.Entity("binary_sensor.manager_test").State == null; _logger.LogInformation("Removed Entity: {removed}", removed); diff --git a/src/Extensions/NetDaemon.Extensions.MqttEntityManager/MessageSender.cs b/src/Extensions/NetDaemon.Extensions.MqttEntityManager/MessageSender.cs index c866d938e..3a3cd2184 100644 --- a/src/Extensions/NetDaemon.Extensions.MqttEntityManager/MessageSender.cs +++ b/src/Extensions/NetDaemon.Extensions.MqttEntityManager/MessageSender.cs @@ -22,6 +22,7 @@ internal class MessageSender : IMessageSender private readonly ILogger _logger; private readonly MqttConfiguration _mqttConfig; private readonly IMqttFactory _mqttFactory; + private readonly IMqttClientOptions _mqttClientOptions; /// /// Manage connections and message publishing to MQTT @@ -39,7 +40,12 @@ public MessageSender(ILogger logger, IMqttFactory mqttFactory, IO if (string.IsNullOrEmpty(_mqttConfig.Host)) throw new MqttConfigurationException("The Mqtt config was not found or there was an error loading it. Please add MqttConfiguration section to appsettings.json"); - _logger.LogDebug("MQTT connection is {host}:{port}/{userId}", _mqttConfig.Host, _mqttConfig.Port, _mqttConfig.UserId); + _logger.LogDebug("MQTT connection is {host}:{port}/{userId}", _mqttConfig.Host, _mqttConfig.Port, _mqttConfig.UserName); + + _mqttClientOptions = new MqttClientOptionsBuilder() + .WithTcpServer(_mqttConfig.Host, _mqttConfig.Port) + .WithCredentials(_mqttConfig.UserName, _mqttConfig.Password) + .Build(); } /// @@ -57,15 +63,10 @@ public async Task SendMessageAsync(string topic, string payload, bool retain = f private async Task ConnectAsync(IMqttClient mqttClient) { - var options = new MqttClientOptionsBuilder() - .WithTcpServer(_mqttConfig.Host, _mqttConfig.Port) - .WithCredentials(_mqttConfig.UserId, _mqttConfig.Password) - .Build(); - try { var connectResult = - await mqttClient.ConnectAsync(options, CancellationToken.None).ConfigureAwait(false); + await mqttClient.ConnectAsync(_mqttClientOptions, CancellationToken.None).ConfigureAwait(false); if (connectResult.ResultCode != MqttClientConnectResultCode.Success) throw new MqttConnectionException(connectResult.ReasonString); } diff --git a/src/Extensions/NetDaemon.Extensions.MqttEntityManager/MqttConfiguration.cs b/src/Extensions/NetDaemon.Extensions.MqttEntityManager/MqttConfiguration.cs index 55fa79c8a..dff6a7e61 100644 --- a/src/Extensions/NetDaemon.Extensions.MqttEntityManager/MqttConfiguration.cs +++ b/src/Extensions/NetDaemon.Extensions.MqttEntityManager/MqttConfiguration.cs @@ -21,12 +21,13 @@ public class MqttConfiguration public string Host { get; set; } = string.Empty; /// - /// User ID to connect to MQTT broker + /// User name to connect to MQTT broker /// - public string? Password { get; set; } + public string? UserName { get; set; } /// /// Password to connect to MQTT broker /// - public string? UserId { get; set; } + public string? Password { get; set; } + } \ No newline at end of file From 812ac17309a9055dfe19b9cc431b5455fe48c377 Mon Sep 17 00:00:00 2001 From: Scott Leckie Date: Sat, 5 Feb 2022 11:10:34 +0000 Subject: [PATCH 15/15] Internalising... --- .../NetDaemon.Extensions.MqttEntityManager/IMessageSender.cs | 2 +- .../NetDaemon.Extensions.MqttEntityManager/MqttEntityManager.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Extensions/NetDaemon.Extensions.MqttEntityManager/IMessageSender.cs b/src/Extensions/NetDaemon.Extensions.MqttEntityManager/IMessageSender.cs index a177a893f..33951af4d 100644 --- a/src/Extensions/NetDaemon.Extensions.MqttEntityManager/IMessageSender.cs +++ b/src/Extensions/NetDaemon.Extensions.MqttEntityManager/IMessageSender.cs @@ -3,7 +3,7 @@ /// /// Interface to send messages to MQTT /// -public interface IMessageSender +internal interface IMessageSender { /// /// Send a message for the given payload to the MQTT topic diff --git a/src/Extensions/NetDaemon.Extensions.MqttEntityManager/MqttEntityManager.cs b/src/Extensions/NetDaemon.Extensions.MqttEntityManager/MqttEntityManager.cs index 4e58c6093..573f65a05 100644 --- a/src/Extensions/NetDaemon.Extensions.MqttEntityManager/MqttEntityManager.cs +++ b/src/Extensions/NetDaemon.Extensions.MqttEntityManager/MqttEntityManager.cs @@ -10,7 +10,7 @@ namespace NetDaemon.Extensions.MqttEntityManager; /// /// Manage entities via MQTT /// -public class MqttEntityManager : IMqttEntityManager +internal class MqttEntityManager : IMqttEntityManager { private readonly MqttConfiguration _config; private readonly IMessageSender _messageSender;