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}