From b3930db45b0331fba8bfac2ec4b8ac9b20b236e6 Mon Sep 17 00:00:00 2001 From: Scott Leckie Date: Thu, 3 Feb 2022 15:15:17 +0000 Subject: [PATCH 1/2] 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. --- .../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..8bd253724 --- /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.AddScoped(); + services.AddScoped(); + + 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 ed9d30acce44324b0fb216de6c62c129fc77443f Mon Sep 17 00:00:00 2001 From: Scott Leckie Date: Thu, 3 Feb 2022 16:16:35 +0000 Subject: [PATCH 2/2] DI scope update from PR comment --- NetDaemon.Extensions.MqttEntities/DependencyInjectionSetup.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/NetDaemon.Extensions.MqttEntities/DependencyInjectionSetup.cs b/NetDaemon.Extensions.MqttEntities/DependencyInjectionSetup.cs index 8bd253724..b07329d1b 100644 --- a/NetDaemon.Extensions.MqttEntities/DependencyInjectionSetup.cs +++ b/NetDaemon.Extensions.MqttEntities/DependencyInjectionSetup.cs @@ -12,8 +12,8 @@ public static class DependencyInjectionSetup public static IServiceCollection AddMqttExtensions(this IServiceCollection services) { services.AddSingleton(); - services.AddScoped(); - services.AddScoped(); + services.AddSingleton(); + services.AddSingleton(); return services; }