diff --git a/Examples/Configuration/config.spec.yaml b/Examples/Configuration/config.spec.yaml
index 9827112..cf93291 100644
--- a/Examples/Configuration/config.spec.yaml
+++ b/Examples/Configuration/config.spec.yaml
@@ -144,9 +144,26 @@ Overrides:
Enabled: false
HomeAssistant:
+ # Is home assistant mQTT-discovery enabled?
+ # If you want to manually create entities, or, just want your PDU data pushed to MQTT without home-assistant, change to false.
+ # (Optional) Default: false
DiscoveryEnabled: true
+
+ # Should discovery messages be retained? Strongly recommend leaving this to true!
+ # (Optional) Default: true
+ DiscoveryRetain: true
+
+ # This is the home-assistant discovery topic.
+ # Should prob leave this set as-is.
DiscoveryTopic: "homeassistant/discovery"
+
+ # How often should discovery messages be sent?
+ # 0 = Send Once at startup.
+ # Note, if you have DiscoveryRetain=true, the only benefit to running more discovery jobs- is to update the names of entityes
+ # based on labels set in the PDU.
+ # (Optional) Default: 0
DiscoveryInterval: 300
+
# Default expireAfter interval applied to all sensors. After this time- the sensor will be marked as unavailable.
SensorExpireAfterSeconds: 300
diff --git a/rPDU2MQTT/Classes/MqttEventHandler.cs b/rPDU2MQTT/Classes/MqttEventHandler.cs
new file mode 100644
index 0000000..5617b52
--- /dev/null
+++ b/rPDU2MQTT/Classes/MqttEventHandler.cs
@@ -0,0 +1,53 @@
+using HiveMQtt.Client;
+using System.Diagnostics;
+using System.Timers;
+using Timer = System.Timers.Timer;
+
+namespace rPDU2MQTT.Classes;
+
+public class MqttEventHandler
+{
+ private readonly HiveMQClient client;
+ private readonly Timer _timer;
+ public MqttEventHandler(HiveMQClient client)
+ {
+ this.client = client;
+ client.AfterConnect += Client_AfterConnect;
+ client.OnDisconnectSent += Client_OnDisconnectSent;
+ client.OnDisconnectReceived += Client_OnDisconnectReceived;
+
+ _timer = new Timer(TimeSpan.FromSeconds(10));
+ _timer.Elapsed += HealthTimer;
+ _timer.AutoReset = true; // Ensures the event fires repeatedly at the interval
+ _timer.Enabled = true; // Starts the timer
+ }
+
+ private void Client_OnDisconnectReceived(object? sender, HiveMQtt.Client.Events.OnDisconnectReceivedEventArgs e)
+ {
+ Log.Error("Received disconnect from broker.");
+ }
+
+ private void Client_OnDisconnectSent(object? sender, HiveMQtt.Client.Events.OnDisconnectSentEventArgs e)
+ {
+ Log.Information("Sending disconnect to broker");
+ }
+
+ private void HealthTimer(object? sender, ElapsedEventArgs e)
+ {
+ Log.Verbose("Timer.Tick()");
+ sendStatusOnline();
+
+ }
+
+ private void Client_AfterConnect(object? sender, HiveMQtt.Client.Events.AfterConnectEventArgs e)
+ {
+ Log.Information("MQTT Client Connected");
+ sendStatusOnline();
+ }
+
+
+ private void sendStatusOnline()
+ {
+ TaskManager.AddTask(() => client.PublishAsync(client.Options!.LastWillAndTestament!.Topic, "online", HiveMQtt.MQTT5.Types.QualityOfService.AtLeastOnceDelivery));
+ }
+}
diff --git a/rPDU2MQTT/Classes/TaskManager.cs b/rPDU2MQTT/Classes/TaskManager.cs
new file mode 100644
index 0000000..a1730e4
--- /dev/null
+++ b/rPDU2MQTT/Classes/TaskManager.cs
@@ -0,0 +1,32 @@
+using System.Collections.Concurrent;
+
+namespace rPDU2MQTT.Classes;
+
+///
+/// This class, exists to watch tasks until completion. Non-async code, which needs to execute a task, just passes the task off to this class.
+///
+public static class TaskManager
+{
+ private static readonly ConcurrentBag taskBag = new ConcurrentBag();
+
+ public static void AddTask(Action task)
+ {
+ Task newTask = Task.Run(task);
+ taskBag.Add(newTask);
+ }
+
+ public static void AddTask(Task task)
+ {
+ taskBag.Add(task);
+ }
+
+ public static void WaitForAllTasks()
+ {
+ foreach (var task in taskBag)
+ {
+ task.Wait();
+ }
+
+ taskBag.Clear(); // ConcurrentBag doesn't have a clear method, but assigning a new one is equivalent to clearing.
+ }
+}
diff --git a/rPDU2MQTT/Models/Config/HomeAssistantConfig.cs b/rPDU2MQTT/Models/Config/HomeAssistantConfig.cs
index 639b475..b2c2c02 100644
--- a/rPDU2MQTT/Models/Config/HomeAssistantConfig.cs
+++ b/rPDU2MQTT/Models/Config/HomeAssistantConfig.cs
@@ -20,7 +20,15 @@ public class HomeAssistantConfig
///
/// How often should discovery data be published?
///
- public int DiscoveryInterval { get; set; } = 300;
+ ///
+ /// A value of 0, will run a single discovery, and not run a re-discovery until the application is restarted.
+ ///
+ public int DiscoveryInterval { get; set; } = 0;
+
+ ///
+ /// Should discovery messages be retained?
+ ///
+ public bool DiscoveryRetain { get; set; } = true;
///
/// Default expireAfter interval applied to all sensors. After this time- the sensor will be marked as unavailable.
diff --git a/rPDU2MQTT/Program.cs b/rPDU2MQTT/Program.cs
index 58d9eb4..e479295 100644
--- a/rPDU2MQTT/Program.cs
+++ b/rPDU2MQTT/Program.cs
@@ -23,7 +23,6 @@
//Ensure we can actually connect to MQTT.
var client = host.Services.GetRequiredService();
-var logger = host.Services.GetRequiredService>();
Log.Information($"Connecting to MQTT Broker at {client.Options.Host}:{client.Options.Port}");
diff --git a/rPDU2MQTT/Services/baseTypes/baseDiscoveryService.cs b/rPDU2MQTT/Services/baseTypes/baseDiscoveryService.cs
index 4e75881..1182f83 100644
--- a/rPDU2MQTT/Services/baseTypes/baseDiscoveryService.cs
+++ b/rPDU2MQTT/Services/baseTypes/baseDiscoveryService.cs
@@ -110,7 +110,8 @@ protected Task PushDiscoveryMessage(T sensor, CancellationToken cancellationT
var msg = new MQTT5PublishMessage(topic, QualityOfService.AtLeastOnceDelivery)
{
ContentType = "json",
- PayloadAsString = System.Text.Json.JsonSerializer.Serialize(sensor, this.jsonOptions)
+ PayloadAsString = System.Text.Json.JsonSerializer.Serialize(sensor, this.jsonOptions),
+ Retain = cfg.HASS.DiscoveryRetain,
};
if (cfg.Debug.PrintDiscovery)
diff --git a/rPDU2MQTT/Services/baseTypes/baseMQTTTService.cs b/rPDU2MQTT/Services/baseTypes/baseMQTTTService.cs
index da420ab..c8e39d5 100644
--- a/rPDU2MQTT/Services/baseTypes/baseMQTTTService.cs
+++ b/rPDU2MQTT/Services/baseTypes/baseMQTTTService.cs
@@ -12,9 +12,8 @@ namespace rPDU2MQTT.Services.baseTypes;
///
public abstract class baseMQTTTService : IHostedService, IDisposable
{
- private readonly int interval;
private IHiveMQClient mqtt { get; init; }
- private PeriodicTimer timer;
+ private PeriodicTimer? timer;
private Task timerTask = Task.CompletedTask;
protected Config cfg { get; }
protected PDU pdu { get; }
@@ -24,11 +23,16 @@ public abstract class baseMQTTTService : IHostedService, IDisposable
protected baseMQTTTService(MQTTServiceDependancies dependancies) : this(dependancies, dependancies.Cfg.PDU.PollInterval) { }
protected baseMQTTTService(MQTTServiceDependancies dependancies, int Interval)
{
- interval = Interval;
mqtt = dependancies.Mqtt;
cfg = dependancies.Cfg;
pdu = dependancies.PDU;
- timer = new PeriodicTimer(TimeSpan.FromSeconds(Interval));
+
+ // If the interval is 0, don't create a timer.
+ if (Interval <= 0)
+ timer = null;
+ else
+ timer = new PeriodicTimer(TimeSpan.FromSeconds(Interval));
+
jsonOptions = new System.Text.Json.JsonSerializerOptions
{
WriteIndented = true,
@@ -46,6 +50,14 @@ protected baseMQTTTService(MQTTServiceDependancies dependancies, int Interval)
public async Task StartAsync(CancellationToken cancellationToken)
{
+ if (timer is null)
+ {
+ Log.Information($"{GetType().Name} - performing single discovery.");
+ await Execute(cancellationToken);
+ return;
+
+ }
+
Log.Information($"{GetType().Name} is starting.");
timerTask = Task.Run(() => timerTaskExecution(cancellationToken).Wait());
@@ -53,6 +65,7 @@ public async Task StartAsync(CancellationToken cancellationToken)
//Kick off the first one manually.
await Execute(cancellationToken);
+ // Log message to indicate the service has been started.
Log.Information($"{GetType().Name} is running.");
}
@@ -75,9 +88,14 @@ public Task StopAsync(CancellationToken stoppingToken)
return Task.CompletedTask;
}
+ ~baseMQTTTService()
+ {
+ Log.Debug($"{GetType().Name} has been finalized");
+ }
+
public void Dispose()
{
- timer.Dispose();
+ timer?.Dispose();
}
///
diff --git a/rPDU2MQTT/Startup/ServiceConfiguration.cs b/rPDU2MQTT/Startup/ServiceConfiguration.cs
index 0646053..592c979 100644
--- a/rPDU2MQTT/Startup/ServiceConfiguration.cs
+++ b/rPDU2MQTT/Startup/ServiceConfiguration.cs
@@ -30,11 +30,16 @@ public static void Configure(HostBuilderContext context, IServiceCollection serv
ThrowError.TestRequiredConfigurationSection(cfg.MQTT.Connection.Host, "MQTT.Connection.Host");
ThrowError.TestRequiredConfigurationSection(cfg.MQTT.Connection.Host, "MQTT.Connection.Port");
+ var lwt = new LastWillAndTestament(
+ MQTTHelper.JoinPaths(cfg.MQTT.ParentTopic, "Status"), payload: "offline", HiveMQtt.MQTT5.Types.QualityOfService.AtLeastOnceDelivery, true);
+
var mqttBuilder = new HiveMQClientOptionsBuilder()
.WithBroker(cfg.MQTT.Connection.Host)
.WithPort(cfg.MQTT.Connection.Port!.Value)
- .WithClientId(cfg.MQTT.ClientID ?? "rpdu2mqtt")
- .WithAutomaticReconnect(true);
+ .WithClientId((cfg.MQTT.ClientID ?? "rpdu2mqtt") + Guid.NewGuid().ToString())
+ .WithAutomaticReconnect(true)
+ .WithKeepAlive(cfg.MQTT.KeepAlive)
+ .WithLastWillAndTestament(lwt);
if (cfg.MQTT.Credentials?.Username is not null)
mqttBuilder.WithUserName(cfg.MQTT.Credentials.Username);
@@ -43,7 +48,13 @@ public static void Configure(HostBuilderContext context, IServiceCollection serv
mqttBuilder.WithPassword(cfg.MQTT.Credentials.Password);
// Return new client, with options applied.
- return new HiveMQClient(mqttBuilder.Build());
+ var x = new HiveMQClient(mqttBuilder.Build());
+
+ // While we are here- lets go ahead and create / bind the event handler.
+ services.AddSingleton(new MqttEventHandler(x));
+ return x;
+
+
});
//Configure Services