diff --git a/README.md b/README.md
index d4164f5..3e4c6a2 100644
--- a/README.md
+++ b/README.md
@@ -15,9 +15,9 @@ It will try to futher accomplish this goal in the future by:
## Screenshots
-![The settings screen](https://i.imgur.com/WpCZaDR.png)
+![The settings screen](https://i.imgur.com/RBQx807.png)
-![The resulting sensors in Home Assistant](https://i.imgur.com/Kka8VOi.png)
+![The resulting sensors and commands in Home Assistant](https://i.imgur.com/jXRU2cu.png)
## Installation
@@ -34,6 +34,10 @@ If you don't want to use the installer, you can find the standalone version rele
If you used the installer, the app checks for updates on startup. If an update is available you will be prompted to install. If you use the standalone, just delete all files from the previous install and unpack the zip to the same location as before.
+## Need help?
+
+Find us on us on [Discord](https://discord.gg/VraYT2N3wd).
+
## Sensors
The application provides several sensors. Sensors can be configured with a name and this name will be used in the MQTT topic like this: `homeassistant/sensor/{DeviceName}/{Name}/state`. Sensors will expose themselves through [MQTT discovery](https://www.home-assistant.io/docs/mqtt/discovery/) and will automatically appear in Home assistant or any other platform that supports this type of configuration.
@@ -132,3 +136,29 @@ This sensor returns the current session state. It has the following possible sta
### Dummy
This sensor spits out a random number every second. Useful for testing, maybe you'll find some other use for it.
+
+## Commands
+
+Commands can be used to trigger certain things on the client. For each command, a switch will be available in Home Assistant. Turning on the switch fires the command on the client and it will turn the switch off when it's done. Turning it off will cancel thje running command.
+
+### ShutdownCommand
+
+This command shuts down the computer immediately. It runs `shutdown /s`.
+
+### RestartCommand
+
+This command restarts the computer immediately. It runs `shutdown /r`.
+
+### LogOffCommand
+
+This command logs off the current user. It runs `shutdown /l`.
+
+### CustomCommand
+
+This command allows you to run any Windows Commands. The command will be run in a hidden Command Prompt. Some examples:
+
+|Command|Explanation|
+|---|---|
+|Rundll32.exe user32.dll,LockWorkStation|This locks the current session.|
+|shutdown /s /t 300|Shuts the PC down after 5 minutes (300 seconds).|
+|C:\path\to\your\batchfile.bat|Run the specified batch file.|
diff --git a/UserInterface/UserInterface.csproj b/UserInterface/UserInterface.csproj
index c2877e3..7805ed5 100644
--- a/UserInterface/UserInterface.csproj
+++ b/UserInterface/UserInterface.csproj
@@ -23,9 +23,18 @@
+
+ AddCommandDialog.axaml
+
+
+ AppInfo.axaml
+
BackgroundServiceSettings.axaml
+
+ CommandSettings.axaml
+
SensorSettings.axaml
diff --git a/UserInterface/ViewModels/AddCommandViewModel.cs b/UserInterface/ViewModels/AddCommandViewModel.cs
new file mode 100644
index 0000000..7fc458a
--- /dev/null
+++ b/UserInterface/ViewModels/AddCommandViewModel.cs
@@ -0,0 +1,31 @@
+using hass_workstation_service.Communication.InterProcesCommunication.Models;
+using ReactiveUI;
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace UserInterface.ViewModels
+{
+ public class AddCommandViewModel : ViewModelBase
+ {
+ private AvailableCommands selectedType;
+ private string description;
+
+ public string Description { get => description; set => this.RaiseAndSetIfChanged(ref description, value); }
+ public bool ShowCommandInput { get => showCommandInput; set => this.RaiseAndSetIfChanged(ref showCommandInput, value); }
+
+ private string moreInfoLink;
+ private bool showCommandInput;
+
+ public string MoreInfoLink
+ {
+ get { return moreInfoLink; }
+ set { this.RaiseAndSetIfChanged(ref moreInfoLink, value); }
+ }
+
+ public AvailableCommands SelectedType { get => selectedType; set => this.RaiseAndSetIfChanged(ref selectedType, value); }
+
+ public string Name { get; set; }
+ public string Command { get; set; }
+ }
+}
diff --git a/UserInterface/ViewModels/CommandSettingsViewModel.cs b/UserInterface/ViewModels/CommandSettingsViewModel.cs
new file mode 100644
index 0000000..4477508
--- /dev/null
+++ b/UserInterface/ViewModels/CommandSettingsViewModel.cs
@@ -0,0 +1,25 @@
+using ReactiveUI;
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace UserInterface.ViewModels
+{
+ public class CommandSettingsViewModel : ViewModelBase
+ {
+ private ICollection configuredCommands;
+
+ public ICollection ConfiguredCommands { get => configuredCommands; set => this.RaiseAndSetIfChanged(ref configuredCommands, value); }
+ public void TriggerUpdate()
+ {
+ this.RaisePropertyChanged();
+ }
+ }
+
+ public class CommandViewModel : ViewModelBase
+ {
+ public Guid Id { get; set; }
+ public string Type { get; set; }
+ public string Name { get; set; }
+ }
+}
diff --git a/UserInterface/Views/AddCommandDialog.axaml b/UserInterface/Views/AddCommandDialog.axaml
new file mode 100644
index 0000000..0ea8d9f
--- /dev/null
+++ b/UserInterface/Views/AddCommandDialog.axaml
@@ -0,0 +1,26 @@
+
+
+ Sensor type
+
+
+
+
+ Name
+
+
+
+
+
+ Command
+
+
+
+
+
diff --git a/UserInterface/Views/AddCommandDialog.axaml.cs b/UserInterface/Views/AddCommandDialog.axaml.cs
new file mode 100644
index 0000000..f0499b5
--- /dev/null
+++ b/UserInterface/Views/AddCommandDialog.axaml.cs
@@ -0,0 +1,112 @@
+using Avalonia;
+using Avalonia.Controls;
+using Avalonia.Interactivity;
+using Avalonia.Markup.Xaml;
+using hass_workstation_service.Communication.InterProcesCommunication.Models;
+using hass_workstation_service.Communication.NamedPipe;
+using JKang.IpcServiceFramework.Client;
+using Microsoft.Extensions.DependencyInjection;
+using System;
+using System.Dynamic;
+using System.Linq;
+using System.Text.Json;
+using UserInterface.Util;
+using UserInterface.ViewModels;
+
+namespace UserInterface.Views
+{
+ public class AddCommandDialog : Window
+ {
+ private readonly IIpcClient client;
+ public ComboBox comboBox { get; set; }
+ public ComboBox detectionModecomboBox { get; set; }
+ public AddCommandDialog()
+ {
+ this.InitializeComponent();
+#if DEBUG
+ this.AttachDevTools();
+#endif
+ DataContext = new AddCommandViewModel();
+ this.comboBox = this.FindControl("ComboBox");
+ this.comboBox.Items = Enum.GetValues(typeof(AvailableCommands)).Cast().OrderBy(v => v.ToString());
+ this.comboBox.SelectedIndex = 0;
+
+ // register IPC clients
+ ServiceProvider serviceProvider = new ServiceCollection()
+ .AddNamedPipeIpcClient("addCommand", pipeName: "pipeinternal")
+ .BuildServiceProvider();
+
+ // resolve IPC client factory
+ IIpcClientFactory clientFactory = serviceProvider
+ .GetRequiredService>();
+
+ // create client
+ this.client = clientFactory.CreateClient("addCommand");
+ }
+
+ public async void Save(object sender, RoutedEventArgs args)
+ {
+ var item = ((AddCommandViewModel)this.DataContext);
+ dynamic model = new { item.Name, item.Command};
+ string json = JsonSerializer.Serialize(model);
+ await this.client.InvokeAsync(x => x.AddCommand(item.SelectedType, json));
+ Close();
+ }
+
+ public void ComboBoxClosed(object sender, SelectionChangedEventArgs args)
+ {
+ var item = ((AddCommandViewModel)this.DataContext);
+ switch (item.SelectedType)
+ {
+ case AvailableCommands.CustomCommand:
+ item.Description = "This command lets you execute any command you want. It will run in a Windows Command Prompt silently. ";
+ item.MoreInfoLink = "https://github.com/sleevezipper/hass-workstation-service#customcommand";
+ item.ShowCommandInput = true;
+ break;
+ case AvailableCommands.ShutdownCommand:
+ item.Description = "This command shuts down the PC immediately. ";
+ item.MoreInfoLink = "https://github.com/sleevezipper/hass-workstation-service#shutdowncommand";
+ item.ShowCommandInput = false;
+ break;
+ case AvailableCommands.RestartCommand:
+ item.Description = "This command restarts the PC immediately. ";
+ item.MoreInfoLink = "https://github.com/sleevezipper/hass-workstation-service#restartcommand";
+ item.ShowCommandInput = false;
+ break;
+ case AvailableCommands.LogOffCommand:
+ item.Description = "This command logs the current user off immediately. ";
+ item.MoreInfoLink = "https://github.com/sleevezipper/hass-workstation-service#logoffcommand";
+ item.ShowCommandInput = false;
+ break;
+ default:
+ item.Description = null;
+ item.MoreInfoLink = null;
+ item.ShowCommandInput = false;
+ break;
+ }
+ }
+ public void OpenInfo(object sender, RoutedEventArgs args)
+ {
+ var item = ((AddSensorViewModel)this.DataContext);
+ BrowserUtil.OpenBrowser(item.MoreInfoLink);
+ }
+
+ public void Test(object sender, RoutedEventArgs args)
+ {
+ var item = ((AddCommandViewModel)this.DataContext);
+
+ System.Diagnostics.Process process = new System.Diagnostics.Process();
+ System.Diagnostics.ProcessStartInfo startInfo = new System.Diagnostics.ProcessStartInfo();
+ startInfo.WindowStyle = System.Diagnostics.ProcessWindowStyle.Normal;
+ startInfo.FileName = "cmd.exe";
+ startInfo.Arguments = $"/k {"echo You won't see this window normally. &&" + item.Command}";
+ process.StartInfo = startInfo;
+ process.Start();
+ }
+
+ private void InitializeComponent()
+ {
+ AvaloniaXamlLoader.Load(this);
+ }
+ }
+}
diff --git a/UserInterface/Views/AppInfo.axaml b/UserInterface/Views/AppInfo.axaml
new file mode 100644
index 0000000..5bbdfbc
--- /dev/null
+++ b/UserInterface/Views/AppInfo.axaml
@@ -0,0 +1,17 @@
+
+
+
+ Info
+
+
+
+
+
+
+
+
diff --git a/UserInterface/Views/AppInfo.axaml.cs b/UserInterface/Views/AppInfo.axaml.cs
new file mode 100644
index 0000000..7a4c576
--- /dev/null
+++ b/UserInterface/Views/AppInfo.axaml.cs
@@ -0,0 +1,91 @@
+using Avalonia;
+using Avalonia.Controls;
+using Avalonia.Markup.Xaml;
+using Microsoft.Extensions.DependencyInjection;
+using hass_workstation_service.Communication.NamedPipe;
+using JKang.IpcServiceFramework.Client;
+using System.Threading.Tasks;
+using Avalonia.Interactivity;
+using System.Reactive.Linq;
+using UserInterface.ViewModels;
+using System.Security;
+using hass_workstation_service.Communication.InterProcesCommunication.Models;
+using UserInterface.Util;
+
+namespace UserInterface.Views
+{
+ public class AppInfo : UserControl
+ {
+ private readonly IIpcClient client;
+
+ public AppInfo()
+ {
+ this.InitializeComponent();
+ // register IPC clients
+ ServiceProvider serviceProvider = new ServiceCollection()
+ .AddNamedPipeIpcClient("broker", pipeName: "pipeinternal")
+ .BuildServiceProvider();
+
+ // resolve IPC client factory
+ IIpcClientFactory clientFactory = serviceProvider
+ .GetRequiredService>();
+
+ // create client
+ this.client = clientFactory.CreateClient("broker");
+
+
+ DataContext = new BackgroundServiceSettingsViewModel();
+ Ping();
+ }
+ public async void Ping() {
+ while (true)
+ {
+ try
+ {
+ var result = await this.client.InvokeAsync(x => x.Ping("ping"));
+ if (result == "pong")
+ {
+ ((BackgroundServiceSettingsViewModel)this.DataContext).UpdateStatus(true, "All good");
+ }
+ else
+ {
+ ((BackgroundServiceSettingsViewModel)this.DataContext).UpdateStatus(false, "Not running");
+ }
+ }
+ catch (System.Exception)
+ {
+ ((BackgroundServiceSettingsViewModel)this.DataContext).UpdateStatus(false, "Not running");
+ }
+
+ var autostartresult = await this.client.InvokeAsync(x => x.IsAutoStartEnabled());
+ ((BackgroundServiceSettingsViewModel)this.DataContext).UpdateAutostartStatus(autostartresult);
+
+ await Task.Delay(1000);
+ }
+ }
+
+ public void Github(object sender, RoutedEventArgs args)
+ {
+ BrowserUtil.OpenBrowser("https://github.com/sleevezipper/hass-workstation-service");
+ }
+
+ public void Discord(object sender, RoutedEventArgs args)
+ {
+ BrowserUtil.OpenBrowser("https://discord.gg/VraYT2N3wd");
+ }
+
+ public void EnableAutostart(object sender, RoutedEventArgs args)
+ {
+ this.client.InvokeAsync(x => x.EnableAutostart(true));
+ }
+ public void DisableAutostart(object sender, RoutedEventArgs args)
+ {
+ this.client.InvokeAsync(x => x.EnableAutostart(false));
+ }
+
+ private void InitializeComponent()
+ {
+ AvaloniaXamlLoader.Load(this);
+ }
+ }
+}
diff --git a/UserInterface/Views/CommandSettings.axaml b/UserInterface/Views/CommandSettings.axaml
new file mode 100644
index 0000000..efc93f0
--- /dev/null
+++ b/UserInterface/Views/CommandSettings.axaml
@@ -0,0 +1,26 @@
+
+
+ Commands
+
+
+
+
+
+
+
+ Add some commands by clicking the "Add" button.
+
+
+
+
+
diff --git a/UserInterface/Views/CommandSettings.axaml.cs b/UserInterface/Views/CommandSettings.axaml.cs
new file mode 100644
index 0000000..fb963f4
--- /dev/null
+++ b/UserInterface/Views/CommandSettings.axaml.cs
@@ -0,0 +1,82 @@
+using Avalonia;
+using Avalonia.Controls;
+using Avalonia.Markup.Xaml;
+using Microsoft.Extensions.DependencyInjection;
+using hass_workstation_service.Communication.NamedPipe;
+using JKang.IpcServiceFramework.Client;
+using System.Threading.Tasks;
+using Avalonia.Interactivity;
+using System.Reactive.Linq;
+using UserInterface.ViewModels;
+using System.Security;
+using hass_workstation_service.Communication.InterProcesCommunication.Models;
+using System.Collections.Generic;
+using System.Linq;
+using Avalonia.Controls.ApplicationLifetimes;
+
+namespace UserInterface.Views
+{
+ public class CommandSettings : UserControl
+ {
+ private readonly IIpcClient client;
+ private DataGrid _dataGrid { get; set; }
+ private bool sensorsNeedToRefresh { get; set; }
+
+ public CommandSettings()
+ {
+ this.InitializeComponent();
+ // register IPC clients
+ ServiceProvider serviceProvider = new ServiceCollection()
+ .AddNamedPipeIpcClient("commands", pipeName: "pipeinternal")
+ .BuildServiceProvider();
+
+ // resolve IPC client factory
+ IIpcClientFactory clientFactory = serviceProvider
+ .GetRequiredService>();
+
+ // create client
+ this.client = clientFactory.CreateClient("commands");
+
+
+ DataContext = new CommandSettingsViewModel();
+ GetConfiguredCommands();
+
+ this._dataGrid = this.FindControl("Grid");
+ }
+
+
+ public async void GetConfiguredCommands()
+ {
+ sensorsNeedToRefresh = false;
+ List status = await this.client.InvokeAsync(x => x.GetConfiguredCommands());
+
+ ((CommandSettingsViewModel)this.DataContext).ConfiguredCommands = status.Select(s => new CommandViewModel() { Name = s.Name, Type = s.Type, Id = s.Id}).ToList();
+
+ }
+ public void Delete(object sender, RoutedEventArgs args)
+ {
+ var item = ((CommandViewModel)this._dataGrid.SelectedItem);
+ this.client.InvokeAsync(x => x.RemoveCommandById(item.Id));
+ ((CommandSettingsViewModel)this.DataContext).ConfiguredCommands.Remove(item);
+ this._dataGrid.SelectedIndex = -1;
+ ((CommandSettingsViewModel)this.DataContext).TriggerUpdate();
+ }
+
+ public async void Add(object sender, RoutedEventArgs args)
+ {
+ AddCommandDialog dialog = new AddCommandDialog();
+ if (Application.Current.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
+ {
+ await dialog.ShowDialog(desktop.MainWindow);
+ sensorsNeedToRefresh = true;
+ GetConfiguredCommands();
+ }
+ }
+
+ private void InitializeComponent()
+ {
+ AvaloniaXamlLoader.Load(this);
+ }
+
+ }
+}
diff --git a/UserInterface/Views/MainWindow.axaml b/UserInterface/Views/MainWindow.axaml
index 50d8e41..c8f2cc3 100644
--- a/UserInterface/Views/MainWindow.axaml
+++ b/UserInterface/Views/MainWindow.axaml
@@ -14,9 +14,11 @@
-
+
+
+
diff --git a/UserInterface/Views/SensorSettings.axaml b/UserInterface/Views/SensorSettings.axaml
index 48a2227..4d8d085 100644
--- a/UserInterface/Views/SensorSettings.axaml
+++ b/UserInterface/Views/SensorSettings.axaml
@@ -6,6 +6,7 @@
MaxWidth="800"
x:Class="UserInterface.Views.SensorSettings" >
+ Sensors
diff --git a/hass-workstation-service/Communication/InterProcesCommunication/InterProcessApi.cs b/hass-workstation-service/Communication/InterProcesCommunication/InterProcessApi.cs
index 4fe7dc5..95c4a6b 100644
--- a/hass-workstation-service/Communication/InterProcesCommunication/InterProcessApi.cs
+++ b/hass-workstation-service/Communication/InterProcesCommunication/InterProcessApi.cs
@@ -2,6 +2,7 @@ using hass_workstation_service.Communication.InterProcesCommunication.Models;
using hass_workstation_service.Communication.NamedPipe;
using hass_workstation_service.Communication.Util;
using hass_workstation_service.Data;
+using hass_workstation_service.Domain.Commands;
using hass_workstation_service.Domain.Sensors;
using Serilog;
using System;
@@ -49,11 +50,19 @@ namespace hass_workstation_service.Communication.InterProcesCommunication
return "what?";
}
+ ///
+ /// This writes the provided settings to the config file.
+ ///
+ ///
public void WriteMqttBrokerSettingsAsync(MqttSettings settings)
{
this._configurationService.WriteMqttBrokerSettingsAsync(settings);
}
+ ///
+ /// Enables or disables autostart.
+ ///
+ ///
public void EnableAutostart(bool enable)
{
this._configurationService.EnableAutoStart(enable);
@@ -69,11 +78,25 @@ namespace hass_workstation_service.Communication.InterProcesCommunication
return this._configurationService.ConfiguredSensors.Select(s => new ConfiguredSensorModel() { Name = s.Name, Type = s.GetType().Name, Value = s.PreviousPublishedState, Id = s.Id, UpdateInterval = s.UpdateInterval, UnitOfMeasurement = s.GetAutoDiscoveryConfig().Unit_of_measurement }).ToList();
}
+ public List GetConfiguredCommands()
+ {
+ return this._configurationService.ConfiguredCommands.Select(s => new ConfiguredCommandModel() { Name = s.Name, Type = s.GetType().Name, Id = s.Id }).ToList();
+ }
+ public void RemoveCommandById(Guid id)
+ {
+ this._configurationService.DeleteConfiguredCommand(id);
+ }
+
public void RemoveSensorById(Guid id)
{
this._configurationService.DeleteConfiguredSensor(id);
}
+ ///
+ /// Adds a command to the configured commands. This properly initializes the class and writes it to the config file.
+ ///
+ ///
+ ///
public void AddSensor(AvailableSensors sensorType, string json)
{
var serializerOptions = new JsonSerializerOptions
@@ -133,5 +156,43 @@ namespace hass_workstation_service.Communication.InterProcesCommunication
this._configurationService.AddConfiguredSensor(sensorToCreate);
}
}
+
+ ///
+ /// Adds a command to the configured commands. This properly initializes the class, subscribes to the command topic and writes it to the config file.
+ ///
+ ///
+ ///
+ public void AddCommand(AvailableCommands commandType, string json)
+ {
+ var serializerOptions = new JsonSerializerOptions
+ {
+ Converters = { new DynamicJsonConverter() }
+ };
+ dynamic model = JsonSerializer.Deserialize(json, serializerOptions);
+
+ AbstractCommand commandToCreate = null;
+ switch (commandType)
+ {
+ case AvailableCommands.ShutdownCommand:
+ commandToCreate = new ShutdownCommand(this._publisher, model.Name);
+ break;
+ case AvailableCommands.RestartCommand:
+ commandToCreate = new RestartCommand(this._publisher, model.Name);
+ break;
+ case AvailableCommands.LogOffCommand:
+ commandToCreate = new LogOffCommand(this._publisher, model.Name);
+ break;
+ case AvailableCommands.CustomCommand:
+ commandToCreate = new CustomCommand(this._publisher, model.Command, model.Name);
+ break;
+ default:
+ Log.Logger.Error("Unknown sensortype");
+ break;
+ }
+ if (commandToCreate != null)
+ {
+ this._configurationService.AddConfiguredCommand(commandToCreate);
+ }
+ }
}
}
diff --git a/hass-workstation-service/Communication/InterProcesCommunication/ServiceContractInterfaces.cs b/hass-workstation-service/Communication/InterProcesCommunication/ServiceContractInterfaces.cs
index a85f5fe..189bd5c 100644
--- a/hass-workstation-service/Communication/InterProcesCommunication/ServiceContractInterfaces.cs
+++ b/hass-workstation-service/Communication/InterProcesCommunication/ServiceContractInterfaces.cs
@@ -17,5 +17,8 @@ namespace hass_workstation_service.Communication.NamedPipe
List GetConfiguredSensors();
void RemoveSensorById(Guid id);
void AddSensor(AvailableSensors sensorType, string json);
+ void RemoveCommandById(Guid id);
+ List GetConfiguredCommands();
+ void AddCommand(AvailableCommands commandType, string json);
}
}
diff --git a/hass-workstation-service/Communication/InterProcesCommunication/ServiceContractModels.cs b/hass-workstation-service/Communication/InterProcesCommunication/ServiceContractModels.cs
index aee1b16..9f7481a 100644
--- a/hass-workstation-service/Communication/InterProcesCommunication/ServiceContractModels.cs
+++ b/hass-workstation-service/Communication/InterProcesCommunication/ServiceContractModels.cs
@@ -29,7 +29,13 @@ namespace hass_workstation_service.Communication.InterProcesCommunication.Models
public int UpdateInterval { get; set; }
public string UnitOfMeasurement { get; set; }
}
-
+ public class ConfiguredCommandModel
+ {
+ public Guid Id { get; set; }
+ public string Type { get; set; }
+ public string Name { get; set; }
+ public string Command { get; set; }
+ }
public enum AvailableSensors
{
UserNotificationStateSensor,
@@ -46,4 +52,12 @@ namespace hass_workstation_service.Communication.InterProcesCommunication.Models
LastBootSensor,
SessionStateSensor
}
+
+ public enum AvailableCommands
+ {
+ CustomCommand,
+ ShutdownCommand,
+ LogOffCommand,
+ RestartCommand,
+ }
}
diff --git a/hass-workstation-service/Communication/MQTT/MqttPublisher.cs b/hass-workstation-service/Communication/MQTT/MqttPublisher.cs
index 090cb2e..b431d90 100644
--- a/hass-workstation-service/Communication/MQTT/MqttPublisher.cs
+++ b/hass-workstation-service/Communication/MQTT/MqttPublisher.cs
@@ -1,16 +1,20 @@
using System;
+using System.Collections.Generic;
+using System.Text;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
using hass_workstation_service.Communication.InterProcesCommunication.Models;
using hass_workstation_service.Communication.Util;
using hass_workstation_service.Data;
+using hass_workstation_service.Domain.Commands;
using Microsoft.Extensions.Logging;
using MQTTnet;
using MQTTnet.Adapter;
using MQTTnet.Client;
using MQTTnet.Client.Options;
using MQTTnet.Exceptions;
+using MQTTnet.Extensions.ManagedClient;
using Serilog;
namespace hass_workstation_service.Communication
@@ -18,12 +22,14 @@ namespace hass_workstation_service.Communication
public class MqttPublisher
{
- private readonly IMqttClient _mqttClient;
+ private readonly IManagedMqttClient _mqttClient;
private readonly ILogger _logger;
private readonly IConfigurationService _configurationService;
private string _mqttClientMessage { get; set; }
public DateTime LastConfigAnnounce { get; private set; }
+ public DateTime LastAvailabilityAnnounce { get; private set; }
public DeviceConfigModel DeviceConfigModel { get; private set; }
+ public ICollection Subscribers { get; private set; }
public bool IsConnected
{
get
@@ -44,7 +50,7 @@ namespace hass_workstation_service.Communication
DeviceConfigModel deviceConfigModel,
IConfigurationService configurationService)
{
-
+ this.Subscribers = new List();
this._logger = logger;
this.DeviceConfigModel = deviceConfigModel;
this._configurationService = configurationService;
@@ -53,11 +59,11 @@ namespace hass_workstation_service.Communication
_configurationService.MqqtConfigChangedHandler = this.ReplaceMqttClient;
var factory = new MqttFactory();
- this._mqttClient = factory.CreateMqttClient();
+ this._mqttClient = factory.CreateManagedMqttClient();
if (options != null)
{
- this._mqttClient.ConnectAsync(options);
+ this._mqttClient.StartAsync(options);
this._mqttClientMessage = "Connecting...";
}
else
@@ -68,25 +74,12 @@ namespace hass_workstation_service.Communication
this._mqttClient.UseConnectedHandler(e => {
this._mqttClientMessage = "All good";
});
+ this._mqttClient.UseApplicationMessageReceivedHandler(e => this.HandleMessageReceived(e.ApplicationMessage));
// configure what happens on disconnect
- this._mqttClient.UseDisconnectedHandler(async e =>
+ this._mqttClient.UseDisconnectedHandler(e =>
{
this._mqttClientMessage = e.ReasonCode.ToString();
- if (e.ReasonCode != MQTTnet.Client.Disconnecting.MqttClientDisconnectReason.NormalDisconnection)
- {
- _logger.LogWarning("Disconnected from server");
- await Task.Delay(TimeSpan.FromSeconds(5));
-
- try
- {
- await this._mqttClient.ConnectAsync(options, CancellationToken.None);
- }
- catch (Exception ex)
- {
- _logger.LogError(ex, "Reconnecting failed");
- }
- }
});
}
@@ -103,7 +96,7 @@ namespace hass_workstation_service.Communication
}
}
- public async Task AnnounceAutoDiscoveryConfig(AutoDiscoveryConfigModel config, bool clearConfig = false)
+ public async Task AnnounceAutoDiscoveryConfig(DiscoveryConfigModel config, string domain, bool clearConfig = false)
{
if (this._mqttClient.IsConnected)
{
@@ -111,11 +104,13 @@ namespace hass_workstation_service.Communication
{
PropertyNamingPolicy = new CamelCaseJsonNamingpolicy(),
IgnoreNullValues = true,
- PropertyNameCaseInsensitive = true
+ PropertyNameCaseInsensitive = true,
+
};
+
var message = new MqttApplicationMessageBuilder()
- .WithTopic($"homeassistant/sensor/{this.DeviceConfigModel.Name}/{config.Name}/config")
- .WithPayload(clearConfig ? "" : JsonSerializer.Serialize(config, options))
+ .WithTopic($"homeassistant/{domain}/{this.DeviceConfigModel.Name}/{config.Name}/config")
+ .WithPayload(clearConfig ? "" : JsonSerializer.Serialize(config, config.GetType(), options))
.WithRetainFlag()
.Build();
await this.Publish(message);
@@ -123,13 +118,13 @@ namespace hass_workstation_service.Communication
}
}
- public async void ReplaceMqttClient(IMqttClientOptions options)
+ public async void ReplaceMqttClient(IManagedMqttClientOptions options)
{
this._logger.LogInformation($"Replacing Mqtt client with new config");
- await _mqttClient.DisconnectAsync();
+ await _mqttClient.StopAsync();
try
{
- await _mqttClient.ConnectAsync(options);
+ await _mqttClient.StartAsync(options);
}
catch (MqttConnectingFailedException ex)
{
@@ -147,5 +142,74 @@ namespace hass_workstation_service.Communication
{
return new MqqtClientStatus() { IsConnected = _mqttClient.IsConnected, Message = _mqttClientMessage };
}
+
+ public async void AnnounceAvailability(string domain, bool offline = false)
+ {
+ if (this._mqttClient.IsConnected)
+ {
+ await this._mqttClient.PublishAsync(
+ new MqttApplicationMessageBuilder()
+ .WithTopic($"homeassistant/{domain}/{DeviceConfigModel.Name}/availability")
+ .WithPayload(offline ? "offline" : "online")
+ .Build()
+ );
+ this.LastAvailabilityAnnounce = DateTime.UtcNow;
+ }
+ else
+ {
+ this._logger.LogInformation($"Availability announce dropped because mqtt not connected");
+ }
+ }
+
+ public async Task DisconnectAsync()
+ {
+ if (this._mqttClient.IsConnected)
+ {
+ await this._mqttClient.InternalClient.DisconnectAsync();
+ }
+ else
+ {
+ this._logger.LogInformation($"Disconnected");
+ }
+ }
+
+ public async void Subscribe(AbstractCommand command)
+ {
+ if (this.IsConnected)
+ {
+ await this._mqttClient.SubscribeAsync(command.GetAutoDiscoveryConfig().Command_topic);
+ }
+ else
+ {
+ while (this.IsConnected == false)
+ {
+ await Task.Delay(5500);
+ }
+
+ await this._mqttClient.SubscribeAsync(command.GetAutoDiscoveryConfig().Command_topic);
+
+ }
+
+ Subscribers.Add(command);
+ }
+
+ private void HandleMessageReceived(MqttApplicationMessage applicationMessage)
+ {
+ foreach (AbstractCommand command in this.Subscribers)
+ {
+ if (command.GetAutoDiscoveryConfig().Command_topic == applicationMessage.Topic)
+ {
+ if (Encoding.UTF8.GetString(applicationMessage?.Payload) == "ON")
+ {
+ command.TurnOn();
+ }
+ else if (Encoding.UTF8.GetString(applicationMessage?.Payload) == "OFF")
+ {
+ command.TurnOff();
+ }
+
+ }
+ }
+ }
}
}
diff --git a/hass-workstation-service/Communication/MQTT/AutoDiscoveryConfigModel.cs b/hass-workstation-service/Communication/MQTT/SensorDiscoveryConfigModel.cs
similarity index 64%
rename from hass-workstation-service/Communication/MQTT/AutoDiscoveryConfigModel.cs
rename to hass-workstation-service/Communication/MQTT/SensorDiscoveryConfigModel.cs
index 4a9ae66..1c5218d 100644
--- a/hass-workstation-service/Communication/MQTT/AutoDiscoveryConfigModel.cs
+++ b/hass-workstation-service/Communication/MQTT/SensorDiscoveryConfigModel.cs
@@ -3,18 +3,26 @@ using System.Collections.Generic;
namespace hass_workstation_service.Communication
{
- public class AutoDiscoveryConfigModel
+ public abstract class DiscoveryConfigModel
{
///
- /// (Optional) The MQTT topic subscribed to receive availability (online/offline) updates.
+ /// (Optional) Information about the device this sensor is a part of to tie it into the device registry. Only works through MQTT discovery and when unique_id is set.
///
///
- public string Availability_topic { get; set; }
+ public DeviceConfigModel Device { get; set; }
///
- /// (Optional) Information about the device this sensor is a part of to tie it into the device registry. Only works through MQTT discovery and when unique_id is set.
+ /// (Optional) The name of the MQTT sensor. Defaults to MQTT Sensor in hass.
///
///
- public DeviceConfigModel Device { get; set; }
+ public string Name { get; set; }
+ }
+ public class SensorDiscoveryConfigModel : DiscoveryConfigModel
+ {
+ ///
+ /// (Optional) The MQTT topic subscribed to receive availability (online/offline) updates.
+ ///
+ ///
+ public string Availability_topic { get; set; }
///
/// (Optional) The type/class of the sensor to set the icon in the frontend. See https://www.home-assistant.io/integrations/sensor/#device-class for options.
///
@@ -47,11 +55,6 @@ namespace hass_workstation_service.Communication
///
public string Json_attributes_topic { get; set; }
///
- /// (Optional) The name of the MQTT sensor. Defaults to MQTT Sensor in hass.
- ///
- ///
- public string Name { get; set; }
- ///
/// (Optional) The payload that represents the available state.
///
///
@@ -89,6 +92,78 @@ namespace hass_workstation_service.Communication
public string Value_template { get; set; }
}
+ public class CommandDiscoveryConfigModel : DiscoveryConfigModel
+ {
+ ///
+ /// (Optional) The MQTT topic subscribed to receive availability (online/offline) updates.
+ ///
+ ///
+ public string Availability_topic { get; set; }
+ ///
+ /// (Optional) The MQTT topic to set the command
+ ///
+ ///
+ public string Command_topic { get; set; }
+ ///
+ /// (Optional) The type/class of the sensor to set the icon in the frontend. See https://www.home-assistant.io/integrations/sensor/#device-class for options.
+ ///
+ ///
+ public string Device_class { get; set; }
+
+ ///
+ /// Sends update events even if the value hasn’t changed. Useful if you want to have meaningful value graphs in history.
+ ///
+ ///
+ public bool? Force_update { get; set; }
+
+ ///
+ /// (Optional) The icon for the sensor.
+ ///
+ ///
+ public string Icon { get; set; }
+ ///
+ /// (Optional) Defines a template to extract the JSON dictionary from messages received on the json_attributes_topic.
+ ///
+ ///
+ public string Json_attributes_template { get; set; }
+ ///
+ /// (Optional) The MQTT topic subscribed to receive a JSON dictionary payload and then set as sensor attributes. Implies force_update of the current sensor state when a message is received on this topic.
+ ///
+ ///
+ public string Json_attributes_topic { get; set; }
+ ///
+ /// (Optional) The payload that represents the available state.
+ ///
+ ///
+ public string Payload_available { get; set; }
+ ///
+ /// (Optional) The payload that represents the unavailable state.
+ ///
+ ///
+ public string Payload_not_available { get; set; }
+ ///
+ /// (Optional) The maximum QoS level of the state topic.
+ ///
+ ///
+ public int? Qos { get; set; }
+ ///
+ /// The MQTT topic subscribed to receive sensor values.
+ ///
+ ///
+ public string State_topic { get; set; }
+ ///
+ /// (Optional) An ID that uniquely identifies this sensor. If two sensors have the same unique ID, Home Assistant will raise an exception.
+ ///
+ ///
+ public string Unique_id { get; set; }
+ ///
+ /// (Optional) Defines a template to extract the value.
+ ///
+ ///
+ public string Value_template { get; set; }
+ }
+
+
///
/// This information will be used when announcing this device on the mqtt topic
///
diff --git a/hass-workstation-service/Data/ConfigurationService.cs b/hass-workstation-service/Data/ConfigurationService.cs
index 4527374..7c0f61d 100644
--- a/hass-workstation-service/Data/ConfigurationService.cs
+++ b/hass-workstation-service/Data/ConfigurationService.cs
@@ -10,12 +10,14 @@ using System.Threading.Tasks;
using hass_workstation_service.Communication;
using hass_workstation_service.Communication.InterProcesCommunication.Models;
using hass_workstation_service.Communication.NamedPipe;
+using hass_workstation_service.Domain.Commands;
using hass_workstation_service.Domain.Sensors;
using Microsoft.Extensions.Configuration;
using Microsoft.Win32;
using MQTTnet;
using MQTTnet.Client;
using MQTTnet.Client.Options;
+using MQTTnet.Extensions.ManagedClient;
using Serilog;
namespace hass_workstation_service.Data
@@ -23,15 +25,19 @@ namespace hass_workstation_service.Data
public class ConfigurationService : IConfigurationService
{
public ICollection ConfiguredSensors { get; private set; }
- public Action MqqtConfigChangedHandler { get; set; }
+ public ICollection ConfiguredCommands { get; private set; }
+ public Action MqqtConfigChangedHandler { get; set; }
+ private readonly DeviceConfigModel _deviceConfigModel;
private bool BrokerSettingsFileLocked { get; set; }
private bool SensorsSettingsFileLocked { get; set; }
+ private bool CommandSettingsFileLocked { get; set; }
private readonly string path = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "Hass Workstation Service");
- public ConfigurationService()
+ public ConfigurationService(DeviceConfigModel deviceConfigModel)
{
+ this._deviceConfigModel = deviceConfigModel;
if (!File.Exists(Path.Combine(path, "mqttbroker.json")))
{
File.Create(Path.Combine(path, "mqttbroker.json")).Close();
@@ -42,7 +48,13 @@ namespace hass_workstation_service.Data
File.Create(Path.Combine(path, "configured-sensors.json")).Close();
}
+ if (!File.Exists(Path.Combine(path, "configured-commands.json")))
+ {
+ File.Create(Path.Combine(path, "configured-commands.json")).Close();
+ }
+
ConfiguredSensors = new List();
+ ConfiguredCommands = new List();
}
public async void ReadSensorSettings(MqttPublisher publisher)
@@ -123,7 +135,54 @@ namespace hass_workstation_service.Data
}
}
- public async Task GetMqttClientOptionsAsync()
+ public async void ReadCommandSettings(MqttPublisher publisher)
+ {
+ while (this.CommandSettingsFileLocked)
+ {
+ await Task.Delay(500);
+ }
+ this.CommandSettingsFileLocked = true;
+ List commands = new List();
+ using (var stream = new FileStream(Path.Combine(path, "configured-commands.json"), FileMode.Open))
+ {
+ Log.Logger.Information($"reading configured commands from: {stream.Name}");
+ if (stream.Length > 0)
+ {
+ commands = await JsonSerializer.DeserializeAsync>(stream);
+ }
+ stream.Close();
+ this.CommandSettingsFileLocked = false;
+ }
+
+ foreach (ConfiguredCommand configuredCommand in commands)
+ {
+ AbstractCommand command = null;
+ switch (configuredCommand.Type)
+ {
+ case "ShutdownCommand":
+ command = new ShutdownCommand(publisher, configuredCommand.Name, configuredCommand.Id);
+ break;
+ case "RestartCommand":
+ command = new RestartCommand(publisher, configuredCommand.Name, configuredCommand.Id);
+ break;
+ case "LogOffCommand":
+ command = new LogOffCommand(publisher, configuredCommand.Name, configuredCommand.Id);
+ break;
+ case "CustomCommand":
+ command = new CustomCommand(publisher, configuredCommand.Command, configuredCommand.Name, configuredCommand.Id);
+ break;
+ default:
+ Log.Logger.Error("unsupported command type in config");
+ break;
+ }
+ if (command != null)
+ {
+ this.ConfiguredCommands.Add(command);
+ }
+ }
+ }
+
+ public async Task GetMqttClientOptionsAsync()
{
ConfiguredMqttBroker configuredBroker = await ReadMqttSettingsAsync();
if (configuredBroker != null && configuredBroker.Host != null)
@@ -137,8 +196,14 @@ namespace hass_workstation_service.Data
AllowUntrustedCertificates = true
})
.WithCredentials(configuredBroker.Username, configuredBroker.Password.ToString())
+ .WithKeepAlivePeriod(TimeSpan.FromSeconds(30))
+ .WithWillMessage(new MqttApplicationMessageBuilder()
+ .WithRetainFlag()
+ .WithTopic($"homeassistant/sensor/{_deviceConfigModel.Name}/availability")
+ .WithPayload("offline")
+ .Build())
.Build();
- return mqttClientOptions;
+ return new ManagedMqttClientOptionsBuilder().WithClientOptions(mqttClientOptions).Build();
}
else
{
@@ -173,7 +238,7 @@ namespace hass_workstation_service.Data
return configuredBroker;
}
- public async void WriteSettingsAsync()
+ public async void WriteSensorSettingsAsync()
{
while (this.SensorsSettingsFileLocked)
{
@@ -210,11 +275,44 @@ namespace hass_workstation_service.Data
this.SensorsSettingsFileLocked = false;
}
+ public async void WriteCommandSettingsAsync()
+ {
+ while (this.CommandSettingsFileLocked)
+ {
+ await Task.Delay(500);
+ }
+ this.CommandSettingsFileLocked = true;
+ List configuredCommandsToSave = new List();
+ using (FileStream stream = new FileStream(Path.Combine(path, "configured-commands.json"), FileMode.Open))
+ {
+ stream.SetLength(0);
+ Log.Logger.Information($"writing configured commands to: {stream.Name}");
+ foreach (AbstractCommand command in this.ConfiguredCommands)
+ {
+ if (command is CustomCommand customcommand)
+ {
+ configuredCommandsToSave.Add(new ConfiguredCommand() { Id = customcommand.Id, Name = customcommand.Name, Type = customcommand.GetType().Name, Command = customcommand.Command });
+ }
+ }
+
+ await JsonSerializer.SerializeAsync(stream, configuredCommandsToSave);
+ stream.Close();
+ }
+ this.CommandSettingsFileLocked = false;
+ }
+
public void AddConfiguredSensor(AbstractSensor sensor)
{
this.ConfiguredSensors.Add(sensor);
sensor.PublishAutoDiscoveryConfigAsync();
- WriteSettingsAsync();
+ WriteSensorSettingsAsync();
+ }
+
+ public void AddConfiguredCommand(AbstractCommand command)
+ {
+ this.ConfiguredCommands.Add(command);
+ command.PublishAutoDiscoveryConfigAsync();
+ WriteCommandSettingsAsync();
}
public async void DeleteConfiguredSensor(Guid id)
@@ -224,7 +322,7 @@ namespace hass_workstation_service.Data
{
await sensorToRemove.UnPublishAutoDiscoveryConfigAsync();
this.ConfiguredSensors.Remove(sensorToRemove);
- WriteSettingsAsync();
+ WriteSensorSettingsAsync();
}
else
{
@@ -233,10 +331,26 @@ namespace hass_workstation_service.Data
}
+ public async void DeleteConfiguredCommand(Guid id)
+ {
+ var commandToRemove = this.ConfiguredCommands.FirstOrDefault(s => s.Id == id);
+ if (commandToRemove != null)
+ {
+ await commandToRemove.UnPublishAutoDiscoveryConfigAsync();
+ this.ConfiguredCommands.Remove(commandToRemove);
+ WriteCommandSettingsAsync();
+ }
+ else
+ {
+ Log.Logger.Warning($"command with id {id} not found");
+ }
+
+ }
+
public void AddConfiguredSensors(List sensors)
{
sensors.ForEach((sensor) => this.ConfiguredSensors.Add(sensor));
- WriteSettingsAsync();
+ WriteSensorSettingsAsync();
}
///
diff --git a/hass-workstation-service/Data/ConfiguredCommand.cs b/hass-workstation-service/Data/ConfiguredCommand.cs
new file mode 100644
index 0000000..d5d6b48
--- /dev/null
+++ b/hass-workstation-service/Data/ConfiguredCommand.cs
@@ -0,0 +1,13 @@
+using hass_workstation_service.Domain.Sensors;
+using System;
+
+namespace hass_workstation_service.Data
+{
+ public class ConfiguredCommand
+ {
+ public string Type { get; set; }
+ public Guid Id { get; set; }
+ public string Name { get; set; }
+ public string Command { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/hass-workstation-service/Data/IConfigurationService.cs b/hass-workstation-service/Data/IConfigurationService.cs
index 3c63154..ba0d4d7 100644
--- a/hass-workstation-service/Data/IConfigurationService.cs
+++ b/hass-workstation-service/Data/IConfigurationService.cs
@@ -1,7 +1,9 @@
using hass_workstation_service.Communication;
using hass_workstation_service.Communication.InterProcesCommunication.Models;
+using hass_workstation_service.Domain.Commands;
using hass_workstation_service.Domain.Sensors;
using MQTTnet.Client.Options;
+using MQTTnet.Extensions.ManagedClient;
using System;
using System.Collections.Generic;
using System.Security;
@@ -12,17 +14,22 @@ namespace hass_workstation_service.Data
public interface IConfigurationService
{
ICollection ConfiguredSensors { get; }
- Action MqqtConfigChangedHandler { get; set; }
+ Action MqqtConfigChangedHandler { get; set; }
+ ICollection ConfiguredCommands { get; }
+ void AddConfiguredCommand(AbstractCommand command);
void AddConfiguredSensor(AbstractSensor sensor);
void AddConfiguredSensors(List sensors);
- Task GetMqttClientOptionsAsync();
+ Task GetMqttClientOptionsAsync();
void ReadSensorSettings(MqttPublisher publisher);
void WriteMqttBrokerSettingsAsync(MqttSettings settings);
- void WriteSettingsAsync();
+ void WriteSensorSettingsAsync();
Task GetMqttBrokerSettings();
void EnableAutoStart(bool enable);
bool IsAutoStartEnabled();
void DeleteConfiguredSensor(Guid id);
+ void DeleteConfiguredCommand(Guid id);
+ void WriteCommandSettingsAsync();
+ void ReadCommandSettings(MqttPublisher publisher);
}
}
\ No newline at end of file
diff --git a/hass-workstation-service/Domain/AbstractDiscoverable.cs b/hass-workstation-service/Domain/AbstractDiscoverable.cs
new file mode 100644
index 0000000..7c388f7
--- /dev/null
+++ b/hass-workstation-service/Domain/AbstractDiscoverable.cs
@@ -0,0 +1,13 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace hass_workstation_service.Domain
+{
+ public abstract class AbstractDiscoverable
+ {
+ public abstract string Domain { get; }
+ }
+}
diff --git a/hass-workstation-service/Domain/Commands/AbstractCommand.cs b/hass-workstation-service/Domain/Commands/AbstractCommand.cs
new file mode 100644
index 0000000..b1b30e6
--- /dev/null
+++ b/hass-workstation-service/Domain/Commands/AbstractCommand.cs
@@ -0,0 +1,80 @@
+using System;
+using System.Threading.Tasks;
+using hass_workstation_service.Communication;
+using MQTTnet;
+
+namespace hass_workstation_service.Domain.Commands
+{
+
+ public abstract class AbstractCommand : AbstractDiscoverable
+ {
+ public Guid Id { get; protected set; }
+ public string Name { get; protected set; }
+ ///
+ /// The update interval in seconds. It checks state only if the interval has passed.
+ ///
+ public int UpdateInterval { get => 1; }
+ public DateTime? LastUpdated { get; protected set; }
+ public string PreviousPublishedState { get; protected set; }
+ public MqttPublisher Publisher { get; protected set; }
+ public override string Domain { get => "switch"; }
+ public AbstractCommand(MqttPublisher publisher, string name, Guid id = default(Guid))
+ {
+ if (id == Guid.Empty)
+ {
+ this.Id = Guid.NewGuid();
+ }
+ else
+ {
+ this.Id = id;
+ }
+ this.Name = name;
+ this.Publisher = publisher;
+ publisher.Subscribe(this);
+
+ }
+ protected CommandDiscoveryConfigModel _autoDiscoveryConfigModel;
+ protected CommandDiscoveryConfigModel SetAutoDiscoveryConfigModel(CommandDiscoveryConfigModel config)
+ {
+ this._autoDiscoveryConfigModel = config;
+ return config;
+ }
+
+ public abstract CommandDiscoveryConfigModel GetAutoDiscoveryConfig();
+ public abstract string GetState();
+
+ public async Task PublishStateAsync()
+ {
+ if (LastUpdated.HasValue && LastUpdated.Value.AddSeconds(this.UpdateInterval) > DateTime.UtcNow)
+ {
+ // dont't even check the state if the update interval hasn't passed
+ return;
+ }
+ string state = this.GetState();
+ if (this.PreviousPublishedState == state)
+ {
+ // don't publish the state if it hasn't changed
+ return;
+ }
+ var message = new MqttApplicationMessageBuilder()
+ .WithTopic(this.GetAutoDiscoveryConfig().State_topic)
+ .WithPayload(state)
+ .WithExactlyOnceQoS()
+ .WithRetainFlag()
+ .Build();
+ await Publisher.Publish(message);
+ this.PreviousPublishedState = state;
+ this.LastUpdated = DateTime.UtcNow;
+ }
+ public async void PublishAutoDiscoveryConfigAsync()
+ {
+ await this.Publisher.AnnounceAutoDiscoveryConfig(this.GetAutoDiscoveryConfig(), this.Domain);
+ }
+ public async Task UnPublishAutoDiscoveryConfigAsync()
+ {
+ await this.Publisher.AnnounceAutoDiscoveryConfig(this.GetAutoDiscoveryConfig(), this.Domain, true);
+ }
+ public abstract void TurnOn();
+ public abstract void TurnOff();
+ }
+}
\ No newline at end of file
diff --git a/hass-workstation-service/Domain/Commands/CustomCommand.cs b/hass-workstation-service/Domain/Commands/CustomCommand.cs
new file mode 100644
index 0000000..6a62b53
--- /dev/null
+++ b/hass-workstation-service/Domain/Commands/CustomCommand.cs
@@ -0,0 +1,75 @@
+using hass_workstation_service.Communication;
+using Serilog;
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace hass_workstation_service.Domain.Commands
+{
+ public class CustomCommand : AbstractCommand
+ {
+ public string Command { get; protected set; }
+ public string State { get; protected set; }
+ public Process Process { get; private set; }
+ public CustomCommand(MqttPublisher publisher, string command, string name = "Custom", Guid id = default(Guid)) : base(publisher, name ?? "Custom", id)
+ {
+ this.Command = command;
+ this.State = "OFF";
+ }
+
+ public override async void TurnOn()
+ {
+ this.State = "ON";
+ this.Process = new Process();
+ ProcessStartInfo startInfo = new ProcessStartInfo();
+ startInfo.WindowStyle = ProcessWindowStyle.Hidden;
+ startInfo.CreateNoWindow = true;
+ startInfo.FileName = "cmd.exe";
+ startInfo.Arguments = $"/C {this.Command}";
+ this.Process.StartInfo = startInfo;
+ try
+ {
+ this.Process.Start();
+ }
+ catch (Exception e)
+ {
+ Log.Logger.Error($"Sensor {this.Name} failed", e);
+ this.State = "FAILED";
+ }
+
+ while (!this.Process.HasExited)
+ {
+ await Task.Delay(1000);
+ }
+ this.State = "OFF";
+ }
+
+
+
+ public override CommandDiscoveryConfigModel GetAutoDiscoveryConfig()
+ {
+ return new CommandDiscoveryConfigModel()
+ {
+ Name = this.Name,
+ Unique_id = this.Id.ToString(),
+ Availability_topic = $"homeassistant/sensor/{Publisher.DeviceConfigModel.Name}/availability",
+ Command_topic = $"homeassistant/{this.Domain}/{Publisher.DeviceConfigModel.Name}/{this.Name}/set",
+ State_topic = $"homeassistant/{this.Domain}/{Publisher.DeviceConfigModel.Name}/{this.Name}/state",
+ Device = this.Publisher.DeviceConfigModel,
+ };
+ }
+
+ public override string GetState()
+ {
+ return this.State;
+ }
+
+ public override void TurnOff()
+ {
+ this.Process.Kill();
+ }
+ }
+}
diff --git a/hass-workstation-service/Domain/Commands/LogOffCommand.cs b/hass-workstation-service/Domain/Commands/LogOffCommand.cs
new file mode 100644
index 0000000..921393e
--- /dev/null
+++ b/hass-workstation-service/Domain/Commands/LogOffCommand.cs
@@ -0,0 +1,17 @@
+using hass_workstation_service.Communication;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace hass_workstation_service.Domain.Commands
+{
+ public class LogOffCommand : CustomCommand
+ {
+ public LogOffCommand(MqttPublisher publisher, string name = "Shutdown", Guid id = default(Guid)) : base(publisher, "shutdown /l", name ?? "LogOff", id)
+ {
+ this.State = "OFF";
+ }
+ }
+}
diff --git a/hass-workstation-service/Domain/Commands/RestartCommand.cs b/hass-workstation-service/Domain/Commands/RestartCommand.cs
new file mode 100644
index 0000000..323b806
--- /dev/null
+++ b/hass-workstation-service/Domain/Commands/RestartCommand.cs
@@ -0,0 +1,17 @@
+using hass_workstation_service.Communication;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace hass_workstation_service.Domain.Commands
+{
+ public class RestartCommand : CustomCommand
+ {
+ public RestartCommand(MqttPublisher publisher, string name = "Shutdown", Guid id = default(Guid)) : base(publisher, "shutdown /r", name ?? "Restart", id)
+ {
+ this.State = "OFF";
+ }
+ }
+}
diff --git a/hass-workstation-service/Domain/Commands/ShutdownCommand.cs b/hass-workstation-service/Domain/Commands/ShutdownCommand.cs
new file mode 100644
index 0000000..7ef5211
--- /dev/null
+++ b/hass-workstation-service/Domain/Commands/ShutdownCommand.cs
@@ -0,0 +1,17 @@
+using hass_workstation_service.Communication;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace hass_workstation_service.Domain.Commands
+{
+ public class ShutdownCommand : CustomCommand
+ {
+ public ShutdownCommand(MqttPublisher publisher, string name = "Shutdown", Guid id = default(Guid)) : base(publisher, "shutdown /s", name ?? "Shutdown", id)
+ {
+ this.State = "OFF";
+ }
+ }
+}
diff --git a/hass-workstation-service/Domain/Sensors/AbstractSensor.cs b/hass-workstation-service/Domain/Sensors/AbstractSensor.cs
index 1b095bc..8c5cd50 100644
--- a/hass-workstation-service/Domain/Sensors/AbstractSensor.cs
+++ b/hass-workstation-service/Domain/Sensors/AbstractSensor.cs
@@ -6,7 +6,7 @@ using MQTTnet;
namespace hass_workstation_service.Domain.Sensors
{
- public abstract class AbstractSensor
+ public abstract class AbstractSensor : AbstractDiscoverable
{
public Guid Id { get; protected set; }
public string Name { get; protected set; }
@@ -17,6 +17,7 @@ namespace hass_workstation_service.Domain.Sensors
public DateTime? LastUpdated { get; protected set; }
public string PreviousPublishedState { get; protected set; }
public MqttPublisher Publisher { get; protected set; }
+ public override string Domain { get => "sensor"; }
public AbstractSensor(MqttPublisher publisher, string name, int updateInterval = 10, Guid id = default(Guid))
{
if (id == Guid.Empty)
@@ -32,14 +33,14 @@ namespace hass_workstation_service.Domain.Sensors
this.UpdateInterval = updateInterval;
}
- protected AutoDiscoveryConfigModel _autoDiscoveryConfigModel;
- protected AutoDiscoveryConfigModel SetAutoDiscoveryConfigModel(AutoDiscoveryConfigModel config)
+ protected SensorDiscoveryConfigModel _autoDiscoveryConfigModel;
+ protected SensorDiscoveryConfigModel SetAutoDiscoveryConfigModel(SensorDiscoveryConfigModel config)
{
this._autoDiscoveryConfigModel = config;
return config;
}
- public abstract AutoDiscoveryConfigModel GetAutoDiscoveryConfig();
+ public abstract SensorDiscoveryConfigModel GetAutoDiscoveryConfig();
public abstract string GetState();
public async Task PublishStateAsync()
@@ -67,11 +68,11 @@ namespace hass_workstation_service.Domain.Sensors
}
public async void PublishAutoDiscoveryConfigAsync()
{
- await this.Publisher.AnnounceAutoDiscoveryConfig(this.GetAutoDiscoveryConfig());
+ await this.Publisher.AnnounceAutoDiscoveryConfig(this.GetAutoDiscoveryConfig(), this.Domain);
}
public async Task UnPublishAutoDiscoveryConfigAsync()
{
- await this.Publisher.AnnounceAutoDiscoveryConfig(this.GetAutoDiscoveryConfig(), true);
+ await this.Publisher.AnnounceAutoDiscoveryConfig(this.GetAutoDiscoveryConfig(), this.Domain, true);
}
}
diff --git a/hass-workstation-service/Domain/Sensors/ActiveWindowSensor.cs b/hass-workstation-service/Domain/Sensors/ActiveWindowSensor.cs
index d431f14..b9fb58b 100644
--- a/hass-workstation-service/Domain/Sensors/ActiveWindowSensor.cs
+++ b/hass-workstation-service/Domain/Sensors/ActiveWindowSensor.cs
@@ -9,15 +9,16 @@ namespace hass_workstation_service.Domain.Sensors
public class ActiveWindowSensor : AbstractSensor
{
public ActiveWindowSensor(MqttPublisher publisher, int? updateInterval = null, string name = "ActiveWindow", Guid id = default(Guid)) : base(publisher, name ?? "ActiveWindow", updateInterval ?? 10, id) { }
- public override AutoDiscoveryConfigModel GetAutoDiscoveryConfig()
+ public override SensorDiscoveryConfigModel GetAutoDiscoveryConfig()
{
- return this._autoDiscoveryConfigModel ?? SetAutoDiscoveryConfigModel(new AutoDiscoveryConfigModel()
+ return this._autoDiscoveryConfigModel ?? SetAutoDiscoveryConfigModel(new SensorDiscoveryConfigModel()
{
Name = this.Name,
Unique_id = this.Id.ToString(),
Device = this.Publisher.DeviceConfigModel,
- State_topic = $"homeassistant/sensor/{Publisher.DeviceConfigModel.Name}/{this.Name}/state",
+ State_topic = $"homeassistant/{this.Domain}/{Publisher.DeviceConfigModel.Name}/{this.Name}/state",
Icon = "mdi:window-maximize",
+ Availability_topic = $"homeassistant/{this.Domain}/{Publisher.DeviceConfigModel.Name}/availability"
});
}
diff --git a/hass-workstation-service/Domain/Sensors/CPULoadSensor.cs b/hass-workstation-service/Domain/Sensors/CPULoadSensor.cs
index 7000943..d84fe1f 100644
--- a/hass-workstation-service/Domain/Sensors/CPULoadSensor.cs
+++ b/hass-workstation-service/Domain/Sensors/CPULoadSensor.cs
@@ -17,16 +17,17 @@ namespace hass_workstation_service.Domain.Sensors
{
}
- public override AutoDiscoveryConfigModel GetAutoDiscoveryConfig()
+ public override SensorDiscoveryConfigModel GetAutoDiscoveryConfig()
{
- return this._autoDiscoveryConfigModel ?? SetAutoDiscoveryConfigModel(new AutoDiscoveryConfigModel()
+ return this._autoDiscoveryConfigModel ?? SetAutoDiscoveryConfigModel(new SensorDiscoveryConfigModel()
{
Name = this.Name,
Unique_id = this.Id.ToString(),
Device = this.Publisher.DeviceConfigModel,
- State_topic = $"homeassistant/sensor/{Publisher.DeviceConfigModel.Name}/{this.Name}/state",
+ State_topic = $"homeassistant/{this.Domain}/{Publisher.DeviceConfigModel.Name}/{this.Name}/state",
Icon = "mdi:chart-areaspline",
- Unit_of_measurement = "%"
+ Unit_of_measurement = "%",
+ Availability_topic = $"homeassistant/{this.Domain}/{Publisher.DeviceConfigModel.Name}/availability"
});
}
diff --git a/hass-workstation-service/Domain/Sensors/CurrentClockSpeedSensor.cs b/hass-workstation-service/Domain/Sensors/CurrentClockSpeedSensor.cs
index 921a96d..523fe30 100644
--- a/hass-workstation-service/Domain/Sensors/CurrentClockSpeedSensor.cs
+++ b/hass-workstation-service/Domain/Sensors/CurrentClockSpeedSensor.cs
@@ -9,16 +9,17 @@ namespace hass_workstation_service.Domain.Sensors
{
public CurrentClockSpeedSensor(MqttPublisher publisher, int? updateInterval = null, string name = "CurrentClockSpeed", Guid id = default(Guid)) : base(publisher, "SELECT CurrentClockSpeed FROM Win32_Processor", updateInterval ?? 10, name ?? "CurrentClockSpeed", id) { }
- public override AutoDiscoveryConfigModel GetAutoDiscoveryConfig()
+ public override SensorDiscoveryConfigModel GetAutoDiscoveryConfig()
{
- return this._autoDiscoveryConfigModel ?? SetAutoDiscoveryConfigModel(new AutoDiscoveryConfigModel()
+ return this._autoDiscoveryConfigModel ?? SetAutoDiscoveryConfigModel(new SensorDiscoveryConfigModel()
{
Name = this.Name,
Unique_id = this.Id.ToString(),
Device = this.Publisher.DeviceConfigModel,
- State_topic = $"homeassistant/sensor/{Publisher.DeviceConfigModel.Name}/{this.Name}/state",
+ State_topic = $"homeassistant/{this.Domain}/{Publisher.DeviceConfigModel.Name}/{this.Name}/state",
Icon = "mdi:speedometer",
- Unit_of_measurement = "MHz"
+ Unit_of_measurement = "MHz",
+ Availability_topic = $"homeassistant/{this.Domain}/{Publisher.DeviceConfigModel.Name}/availability"
});
}
}
diff --git a/hass-workstation-service/Domain/Sensors/DummySensor.cs b/hass-workstation-service/Domain/Sensors/DummySensor.cs
index 5960104..3431fb5 100644
--- a/hass-workstation-service/Domain/Sensors/DummySensor.cs
+++ b/hass-workstation-service/Domain/Sensors/DummySensor.cs
@@ -13,14 +13,15 @@ namespace hass_workstation_service.Domain.Sensors
this._random = new Random();
}
- public override AutoDiscoveryConfigModel GetAutoDiscoveryConfig()
+ public override SensorDiscoveryConfigModel GetAutoDiscoveryConfig()
{
- return this._autoDiscoveryConfigModel ?? SetAutoDiscoveryConfigModel(new AutoDiscoveryConfigModel()
+ return this._autoDiscoveryConfigModel ?? SetAutoDiscoveryConfigModel(new SensorDiscoveryConfigModel()
{
Name = this.Name,
Unique_id = this.Id.ToString(),
Device = this.Publisher.DeviceConfigModel,
- State_topic = $"homeassistant/sensor/{Publisher.DeviceConfigModel.Name}/{this.Name}/state"
+ State_topic = $"homeassistant/{this.Domain}/{Publisher.DeviceConfigModel.Name}/{this.Name}/state",
+ Availability_topic = $"homeassistant/{this.Domain}/{Publisher.DeviceConfigModel.Name}/availability"
});
}
diff --git a/hass-workstation-service/Domain/Sensors/LastActiveSensor.cs b/hass-workstation-service/Domain/Sensors/LastActiveSensor.cs
index 4a81342..8bcfdc4 100644
--- a/hass-workstation-service/Domain/Sensors/LastActiveSensor.cs
+++ b/hass-workstation-service/Domain/Sensors/LastActiveSensor.cs
@@ -9,15 +9,16 @@ namespace hass_workstation_service.Domain.Sensors
public LastActiveSensor(MqttPublisher publisher, int? updateInterval = 10, string name = "LastActive", Guid id = default) : base(publisher, name ?? "LastActive", updateInterval ?? 10, id){}
- public override AutoDiscoveryConfigModel GetAutoDiscoveryConfig()
+ public override SensorDiscoveryConfigModel GetAutoDiscoveryConfig()
{
- return this._autoDiscoveryConfigModel ?? SetAutoDiscoveryConfigModel(new AutoDiscoveryConfigModel()
+ return this._autoDiscoveryConfigModel ?? SetAutoDiscoveryConfigModel(new SensorDiscoveryConfigModel()
{
Name = this.Name,
Unique_id = this.Id.ToString(),
Device = this.Publisher.DeviceConfigModel,
- State_topic = $"homeassistant/sensor/{Publisher.DeviceConfigModel.Name}/{this.Name}/state",
- Icon = "mdi:clock-time-three-outline"
+ State_topic = $"homeassistant/{this.Domain}/{Publisher.DeviceConfigModel.Name}/{this.Name}/state",
+ Icon = "mdi:clock-time-three-outline",
+ Availability_topic = $"homeassistant/{this.Domain}/{Publisher.DeviceConfigModel.Name}/availability"
});
}
diff --git a/hass-workstation-service/Domain/Sensors/LastBootSensor.cs b/hass-workstation-service/Domain/Sensors/LastBootSensor.cs
index b9bcbd6..963e858 100644
--- a/hass-workstation-service/Domain/Sensors/LastBootSensor.cs
+++ b/hass-workstation-service/Domain/Sensors/LastBootSensor.cs
@@ -13,15 +13,16 @@ namespace hass_workstation_service.Domain.Sensors
}
- public override AutoDiscoveryConfigModel GetAutoDiscoveryConfig()
+ public override SensorDiscoveryConfigModel GetAutoDiscoveryConfig()
{
- return this._autoDiscoveryConfigModel ?? SetAutoDiscoveryConfigModel(new AutoDiscoveryConfigModel()
+ return this._autoDiscoveryConfigModel ?? SetAutoDiscoveryConfigModel(new SensorDiscoveryConfigModel()
{
Name = this.Name,
Unique_id = this.Id.ToString(),
Device = this.Publisher.DeviceConfigModel,
- State_topic = $"homeassistant/sensor/{Publisher.DeviceConfigModel.Name}/{this.Name}/state",
- Icon = "mdi:clock-time-three-outline"
+ State_topic = $"homeassistant/{this.Domain}/{Publisher.DeviceConfigModel.Name}/{this.Name}/state",
+ Icon = "mdi:clock-time-three-outline",
+ Availability_topic = $"homeassistant/{this.Domain}/{Publisher.DeviceConfigModel.Name}/availability"
});
}
diff --git a/hass-workstation-service/Domain/Sensors/MemoryUsageSensor.cs b/hass-workstation-service/Domain/Sensors/MemoryUsageSensor.cs
index d905475..6893d0a 100644
--- a/hass-workstation-service/Domain/Sensors/MemoryUsageSensor.cs
+++ b/hass-workstation-service/Domain/Sensors/MemoryUsageSensor.cs
@@ -34,16 +34,17 @@ namespace hass_workstation_service.Domain.Sensors
}
return "";
}
- public override AutoDiscoveryConfigModel GetAutoDiscoveryConfig()
+ public override SensorDiscoveryConfigModel GetAutoDiscoveryConfig()
{
- return this._autoDiscoveryConfigModel ?? SetAutoDiscoveryConfigModel(new AutoDiscoveryConfigModel()
+ return this._autoDiscoveryConfigModel ?? SetAutoDiscoveryConfigModel(new SensorDiscoveryConfigModel()
{
Name = this.Name,
Unique_id = this.Id.ToString(),
Device = this.Publisher.DeviceConfigModel,
- State_topic = $"homeassistant/sensor/{Publisher.DeviceConfigModel.Name}/{this.Name}/state",
+ State_topic = $"homeassistant/{this.Domain}/{Publisher.DeviceConfigModel.Name}/{this.Name}/state",
Icon = "mdi:memory",
- Unit_of_measurement = "%"
+ Unit_of_measurement = "%",
+ Availability_topic = $"homeassistant/{this.Domain}/{Publisher.DeviceConfigModel.Name}/availability"
});
}
}
diff --git a/hass-workstation-service/Domain/Sensors/MicrophoneActiveSensor.cs b/hass-workstation-service/Domain/Sensors/MicrophoneActiveSensor.cs
index 0acf53d..8a6a246 100644
--- a/hass-workstation-service/Domain/Sensors/MicrophoneActiveSensor.cs
+++ b/hass-workstation-service/Domain/Sensors/MicrophoneActiveSensor.cs
@@ -21,33 +21,100 @@ namespace hass_workstation_service.Domain.Sensors
}
else return "unsupported";
}
- public override AutoDiscoveryConfigModel GetAutoDiscoveryConfig()
+ public override SensorDiscoveryConfigModel GetAutoDiscoveryConfig()
{
- return this._autoDiscoveryConfigModel ?? SetAutoDiscoveryConfigModel(new AutoDiscoveryConfigModel()
+ return this._autoDiscoveryConfigModel ?? SetAutoDiscoveryConfigModel(new SensorDiscoveryConfigModel()
{
Name = this.Name,
Unique_id = this.Id.ToString(),
Device = this.Publisher.DeviceConfigModel,
- State_topic = $"homeassistant/sensor/{Publisher.DeviceConfigModel.Name}/{this.Name}/state",
+ State_topic = $"homeassistant/{this.Domain}/{Publisher.DeviceConfigModel.Name}/{this.Name}/state",
Icon = "mdi:microphone",
+ Availability_topic = $"homeassistant/{this.Domain}/{Publisher.DeviceConfigModel.Name}/availability"
});
}
[SupportedOSPlatform("windows")]
private bool IsMicrophoneInUse()
{
- using (var key = Registry.LocalMachine.OpenSubKey(@"SOFTWARE\Microsoft\Windows\CurrentVersion\CapabilityAccessManager\ConsentStore\microphone\NonPackaged"))
+ using (var key = Registry.LocalMachine.OpenSubKey(@"SOFTWARE\Microsoft\Windows\CurrentVersion\CapabilityAccessManager\ConsentStore\microphone"))
{
foreach (var subKeyName in key.GetSubKeyNames())
{
- using (var subKey = key.OpenSubKey(subKeyName))
+ // NonPackaged has multiple subkeys
+ if (subKeyName == "NonPackaged")
{
- if (subKey.GetValueNames().Contains("LastUsedTimeStop"))
+ using (var nonpackagedkey = key.OpenSubKey(subKeyName))
{
- var endTime = subKey.GetValue("LastUsedTimeStop") is long ? (long)subKey.GetValue("LastUsedTimeStop") : -1;
- if (endTime <= 0)
+ foreach (var nonpackagedSubKeyName in nonpackagedkey.GetSubKeyNames())
{
- return true;
+ using (var subKey = nonpackagedkey.OpenSubKey(nonpackagedSubKeyName))
+ {
+ if (subKey.GetValueNames().Contains("LastUsedTimeStop"))
+ {
+ var endTime = subKey.GetValue("LastUsedTimeStop") is long ? (long)subKey.GetValue("LastUsedTimeStop") : -1;
+ if (endTime <= 0)
+ {
+ return true;
+ }
+ }
+ }
+ }
+ }
+ }
+ else
+ {
+ using (var subKey = key.OpenSubKey(subKeyName))
+ {
+ if (subKey.GetValueNames().Contains("LastUsedTimeStop"))
+ {
+ var endTime = subKey.GetValue("LastUsedTimeStop") is long ? (long)subKey.GetValue("LastUsedTimeStop") : -1;
+ if (endTime <= 0)
+ {
+ return true;
+ }
+ }
+ }
+ }
+ }
+ }
+
+ using (var key = Registry.CurrentUser.OpenSubKey(@"SOFTWARE\Microsoft\Windows\CurrentVersion\CapabilityAccessManager\ConsentStore\microphone"))
+ {
+ foreach (var subKeyName in key.GetSubKeyNames())
+ {
+ // NonPackaged has multiple subkeys
+ if (subKeyName == "NonPackaged")
+ {
+ using (var nonpackagedkey = key.OpenSubKey(subKeyName))
+ {
+ foreach (var nonpackagedSubKeyName in nonpackagedkey.GetSubKeyNames())
+ {
+ using (var subKey = nonpackagedkey.OpenSubKey(nonpackagedSubKeyName))
+ {
+ if (subKey.GetValueNames().Contains("LastUsedTimeStop"))
+ {
+ var endTime = subKey.GetValue("LastUsedTimeStop") is long ? (long)subKey.GetValue("LastUsedTimeStop") : -1;
+ if (endTime <= 0)
+ {
+ return true;
+ }
+ }
+ }
+ }
+ }
+ }
+ else
+ {
+ using (var subKey = key.OpenSubKey(subKeyName))
+ {
+ if (subKey.GetValueNames().Contains("LastUsedTimeStop"))
+ {
+ var endTime = subKey.GetValue("LastUsedTimeStop") is long ? (long)subKey.GetValue("LastUsedTimeStop") : -1;
+ if (endTime <= 0)
+ {
+ return true;
+ }
}
}
}
diff --git a/hass-workstation-service/Domain/Sensors/NamedWindowSensor.cs b/hass-workstation-service/Domain/Sensors/NamedWindowSensor.cs
index 7cf7989..1a5dc64 100644
--- a/hass-workstation-service/Domain/Sensors/NamedWindowSensor.cs
+++ b/hass-workstation-service/Domain/Sensors/NamedWindowSensor.cs
@@ -16,15 +16,16 @@ namespace hass_workstation_service.Domain.Sensors
this.WindowName = windowName;
}
- public override AutoDiscoveryConfigModel GetAutoDiscoveryConfig()
+ public override SensorDiscoveryConfigModel GetAutoDiscoveryConfig()
{
- return this._autoDiscoveryConfigModel ?? SetAutoDiscoveryConfigModel(new AutoDiscoveryConfigModel()
+ return this._autoDiscoveryConfigModel ?? SetAutoDiscoveryConfigModel(new SensorDiscoveryConfigModel()
{
Name = this.Name,
Unique_id = this.Id.ToString(),
Device = this.Publisher.DeviceConfigModel,
- State_topic = $"homeassistant/sensor/{Publisher.DeviceConfigModel.Name}/{this.Name}/state",
+ State_topic = $"homeassistant/{this.Domain}/{Publisher.DeviceConfigModel.Name}/{this.Name}/state",
Icon = "mdi:window-maximize",
+ Availability_topic = $"homeassistant/{this.Domain}/{Publisher.DeviceConfigModel.Name}/availability"
});
}
diff --git a/hass-workstation-service/Domain/Sensors/SessionStateSensor.cs b/hass-workstation-service/Domain/Sensors/SessionStateSensor.cs
index dc20f38..d043a76 100644
--- a/hass-workstation-service/Domain/Sensors/SessionStateSensor.cs
+++ b/hass-workstation-service/Domain/Sensors/SessionStateSensor.cs
@@ -35,15 +35,16 @@ namespace hass_workstation_service.Domain.Sensors
public class SessionStateSensor : AbstractSensor
{
public SessionStateSensor(MqttPublisher publisher, int? updateInterval = null, string name = "SessionState", Guid id = default(Guid)) : base(publisher, name ?? "SessionState", updateInterval ?? 10, id) { }
- public override AutoDiscoveryConfigModel GetAutoDiscoveryConfig()
+ public override SensorDiscoveryConfigModel GetAutoDiscoveryConfig()
{
- return this._autoDiscoveryConfigModel ?? SetAutoDiscoveryConfigModel(new AutoDiscoveryConfigModel()
+ return this._autoDiscoveryConfigModel ?? SetAutoDiscoveryConfigModel(new SensorDiscoveryConfigModel()
{
Name = this.Name,
Unique_id = this.Id.ToString(),
Device = this.Publisher.DeviceConfigModel,
- State_topic = $"homeassistant/sensor/{Publisher.DeviceConfigModel.Name}/{this.Name}/state",
+ State_topic = $"homeassistant/{this.Domain}/{Publisher.DeviceConfigModel.Name}/{this.Name}/state",
Icon = "mdi:lock",
+ Availability_topic = $"homeassistant/{this.Domain}/{Publisher.DeviceConfigModel.Name}/availability"
});
}
diff --git a/hass-workstation-service/Domain/Sensors/UserNotificationStateSensor.cs b/hass-workstation-service/Domain/Sensors/UserNotificationStateSensor.cs
index e98e1d9..793df7e 100644
--- a/hass-workstation-service/Domain/Sensors/UserNotificationStateSensor.cs
+++ b/hass-workstation-service/Domain/Sensors/UserNotificationStateSensor.cs
@@ -9,15 +9,16 @@ namespace hass_workstation_service.Domain.Sensors
{
public UserNotificationStateSensor(MqttPublisher publisher, int? updateInterval = null, string name = "NotificationState", Guid id = default(Guid)) : base(publisher, name ?? "NotificationState", updateInterval ?? 10, id) { }
- public override AutoDiscoveryConfigModel GetAutoDiscoveryConfig()
+ public override SensorDiscoveryConfigModel GetAutoDiscoveryConfig()
{
- return this._autoDiscoveryConfigModel ?? SetAutoDiscoveryConfigModel(new AutoDiscoveryConfigModel()
+ return this._autoDiscoveryConfigModel ?? SetAutoDiscoveryConfigModel(new SensorDiscoveryConfigModel()
{
Name = this.Name,
Unique_id = this.Id.ToString(),
Device = this.Publisher.DeviceConfigModel,
- State_topic = $"homeassistant/sensor/{Publisher.DeviceConfigModel.Name}/{this.Name}/state",
+ State_topic = $"homeassistant/{this.Domain}/{Publisher.DeviceConfigModel.Name}/{this.Name}/state",
Icon = "mdi:laptop",
+ Availability_topic = $"homeassistant/{this.Domain}/{Publisher.DeviceConfigModel.Name}/availability"
});
}
diff --git a/hass-workstation-service/Domain/Sensors/WMIQuerySensor.cs b/hass-workstation-service/Domain/Sensors/WMIQuerySensor.cs
index ed44784..6304389 100644
--- a/hass-workstation-service/Domain/Sensors/WMIQuerySensor.cs
+++ b/hass-workstation-service/Domain/Sensors/WMIQuerySensor.cs
@@ -20,14 +20,15 @@ namespace hass_workstation_service.Domain.Sensors
_objectQuery = new ObjectQuery(this.Query);
_searcher = new ManagementObjectSearcher(query);
}
- public override AutoDiscoveryConfigModel GetAutoDiscoveryConfig()
+ public override SensorDiscoveryConfigModel GetAutoDiscoveryConfig()
{
- return this._autoDiscoveryConfigModel ?? SetAutoDiscoveryConfigModel(new AutoDiscoveryConfigModel()
+ return this._autoDiscoveryConfigModel ?? SetAutoDiscoveryConfigModel(new SensorDiscoveryConfigModel()
{
Name = this.Name,
Unique_id = this.Id.ToString(),
Device = this.Publisher.DeviceConfigModel,
- State_topic = $"homeassistant/sensor/{Publisher.DeviceConfigModel.Name}/{this.Name}/state",
+ State_topic = $"homeassistant/{this.Domain}/{Publisher.DeviceConfigModel.Name}/{this.Name}/state",
+ Availability_topic = $"homeassistant/{this.Domain}/{Publisher.DeviceConfigModel.Name}/availability"
});
}
diff --git a/hass-workstation-service/Domain/Sensors/WebcamActiveSensor.cs b/hass-workstation-service/Domain/Sensors/WebcamActiveSensor.cs
index 6806e78..205e467 100644
--- a/hass-workstation-service/Domain/Sensors/WebcamActiveSensor.cs
+++ b/hass-workstation-service/Domain/Sensors/WebcamActiveSensor.cs
@@ -23,33 +23,100 @@ namespace hass_workstation_service.Domain.Sensors
return "unsupported";
}
}
- public override AutoDiscoveryConfigModel GetAutoDiscoveryConfig()
+ public override SensorDiscoveryConfigModel GetAutoDiscoveryConfig()
{
- return this._autoDiscoveryConfigModel ?? SetAutoDiscoveryConfigModel(new AutoDiscoveryConfigModel()
+ return this._autoDiscoveryConfigModel ?? SetAutoDiscoveryConfigModel(new SensorDiscoveryConfigModel()
{
Name = this.Name,
Unique_id = this.Id.ToString(),
Device = this.Publisher.DeviceConfigModel,
- State_topic = $"homeassistant/sensor/{Publisher.DeviceConfigModel.Name}/{this.Name}/state",
+ State_topic = $"homeassistant/{this.Domain}/{Publisher.DeviceConfigModel.Name}/{this.Name}/state",
Icon = "mdi:webcam",
+ Availability_topic = $"homeassistant/{this.Domain}/{Publisher.DeviceConfigModel.Name}/availability"
});
}
[SupportedOSPlatform("windows")]
private bool IsWebCamInUseRegistry()
{
- using (var key = Registry.LocalMachine.OpenSubKey(@"SOFTWARE\Microsoft\Windows\CurrentVersion\CapabilityAccessManager\ConsentStore\webcam\NonPackaged"))
+ using (var key = Registry.LocalMachine.OpenSubKey(@"SOFTWARE\Microsoft\Windows\CurrentVersion\CapabilityAccessManager\ConsentStore\webcam"))
{
foreach (var subKeyName in key.GetSubKeyNames())
{
- using (var subKey = key.OpenSubKey(subKeyName))
+ // NonPackaged has multiple subkeys
+ if (subKeyName == "NonPackaged")
{
- if (subKey.GetValueNames().Contains("LastUsedTimeStop"))
+ using (var nonpackagedkey = key.OpenSubKey(subKeyName))
{
- var endTime = subKey.GetValue("LastUsedTimeStop") is long ? (long)subKey.GetValue("LastUsedTimeStop") : -1;
- if (endTime <= 0)
+ foreach (var nonpackagedSubKeyName in nonpackagedkey.GetSubKeyNames())
{
- return true;
+ using (var subKey = nonpackagedkey.OpenSubKey(nonpackagedSubKeyName))
+ {
+ if (subKey.GetValueNames().Contains("LastUsedTimeStop"))
+ {
+ var endTime = subKey.GetValue("LastUsedTimeStop") is long ? (long)subKey.GetValue("LastUsedTimeStop") : -1;
+ if (endTime <= 0)
+ {
+ return true;
+ }
+ }
+ }
+ }
+ }
+ }
+ else
+ {
+ using (var subKey = key.OpenSubKey(subKeyName))
+ {
+ if (subKey.GetValueNames().Contains("LastUsedTimeStop"))
+ {
+ var endTime = subKey.GetValue("LastUsedTimeStop") is long ? (long)subKey.GetValue("LastUsedTimeStop") : -1;
+ if (endTime <= 0)
+ {
+ return true;
+ }
+ }
+ }
+ }
+ }
+ }
+
+ using (var key = Registry.CurrentUser.OpenSubKey(@"SOFTWARE\Microsoft\Windows\CurrentVersion\CapabilityAccessManager\ConsentStore\webcam"))
+ {
+ foreach (var subKeyName in key.GetSubKeyNames())
+ {
+ // NonPackaged has multiple subkeys
+ if (subKeyName == "NonPackaged")
+ {
+ using (var nonpackagedkey = key.OpenSubKey(subKeyName))
+ {
+ foreach (var nonpackagedSubKeyName in nonpackagedkey.GetSubKeyNames())
+ {
+ using (var subKey = nonpackagedkey.OpenSubKey(nonpackagedSubKeyName))
+ {
+ if (subKey.GetValueNames().Contains("LastUsedTimeStop"))
+ {
+ var endTime = subKey.GetValue("LastUsedTimeStop") is long ? (long)subKey.GetValue("LastUsedTimeStop") : -1;
+ if (endTime <= 0)
+ {
+ return true;
+ }
+ }
+ }
+ }
+ }
+ }
+ else
+ {
+ using (var subKey = key.OpenSubKey(subKeyName))
+ {
+ if (subKey.GetValueNames().Contains("LastUsedTimeStop"))
+ {
+ var endTime = subKey.GetValue("LastUsedTimeStop") is long ? (long)subKey.GetValue("LastUsedTimeStop") : -1;
+ if (endTime <= 0)
+ {
+ return true;
+ }
}
}
}
diff --git a/hass-workstation-service/Program.cs b/hass-workstation-service/Program.cs
index 61f597d..d720614 100644
--- a/hass-workstation-service/Program.cs
+++ b/hass-workstation-service/Program.cs
@@ -67,11 +67,10 @@ namespace hass_workstation_service
Log.CloseAndFlush();
}
}
-
-
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
+
.ConfigureLogging((hostContext, loggingBuilder) =>
loggingBuilder.AddSerilog(dispose: true))
.ConfigureServices((hostContext, services) =>
diff --git a/hass-workstation-service/Properties/PublishProfiles/AzureHosted.pubxml b/hass-workstation-service/Properties/PublishProfiles/AzureHosted.pubxml
index 558f6d5..73bfde2 100644
--- a/hass-workstation-service/Properties/PublishProfiles/AzureHosted.pubxml
+++ b/hass-workstation-service/Properties/PublishProfiles/AzureHosted.pubxml
@@ -4,7 +4,7 @@ https://go.microsoft.com/fwlink/?LinkID=208121.
-->
- 27
+ 32
1.0.0.*
True
Release
diff --git a/hass-workstation-service/UserInterface.exe b/hass-workstation-service/UserInterface.exe
index be6aeec..9aaa9fa 100644
Binary files a/hass-workstation-service/UserInterface.exe and b/hass-workstation-service/UserInterface.exe differ
diff --git a/hass-workstation-service/Worker.cs b/hass-workstation-service/Worker.cs
index 540225b..18ddfa3 100644
--- a/hass-workstation-service/Worker.cs
+++ b/hass-workstation-service/Worker.cs
@@ -5,6 +5,7 @@ using System.Threading;
using System.Threading.Tasks;
using hass_workstation_service.Communication;
using hass_workstation_service.Data;
+using hass_workstation_service.Domain.Commands;
using hass_workstation_service.Domain.Sensors;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
@@ -30,6 +31,7 @@ namespace hass_workstation_service
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
+ _configurationService.ReadCommandSettings(_mqttPublisher);
_configurationService.ReadSensorSettings(_mqttPublisher);
while (!_mqttPublisher.IsConnected)
@@ -40,16 +42,26 @@ namespace hass_workstation_service
_logger.LogInformation("Connected. Sending auto discovery messages.");
List sensors = _configurationService.ConfiguredSensors.ToList();
-
+ List commands = _configurationService.ConfiguredCommands.ToList();
+ _mqttPublisher.AnnounceAvailability("sensor");
foreach (AbstractSensor sensor in sensors)
{
sensor.PublishAutoDiscoveryConfigAsync();
}
+ foreach (AbstractCommand command in commands)
+ {
+ command.PublishAutoDiscoveryConfigAsync();
+ }
while (!stoppingToken.IsCancellationRequested)
{
- sensors = _configurationService.ConfiguredSensors.ToList();
_logger.LogDebug("Worker running at: {time}", DateTimeOffset.Now);
+ // announce autodiscovery every 30 seconds
+ if (_mqttPublisher.LastAvailabilityAnnounce < DateTime.UtcNow.AddSeconds(-10))
+ {
+ _mqttPublisher.AnnounceAvailability("sensor");
+ }
+
foreach (AbstractSensor sensor in sensors)
{
try
@@ -60,7 +72,19 @@ namespace hass_workstation_service
{
Log.Logger.Warning("Sensor failed: " + sensor.Name, ex);
}
-
+
+ }
+ foreach (AbstractCommand command in commands)
+ {
+ try
+ {
+ await command.PublishStateAsync();
+ }
+ catch (Exception ex)
+ {
+ Log.Logger.Warning("Command state failed: " + command.Name, ex);
+ }
+
}
// announce autodiscovery every 30 seconds
if (_mqttPublisher.LastConfigAnnounce < DateTime.UtcNow.AddSeconds(-30))
@@ -69,9 +93,21 @@ namespace hass_workstation_service
{
sensor.PublishAutoDiscoveryConfigAsync();
}
+ foreach (AbstractCommand command in commands)
+ {
+ command.PublishAutoDiscoveryConfigAsync();
+ }
}
await Task.Delay(1000, stoppingToken);
}
+
+ }
+
+ public override async Task StopAsync(CancellationToken stoppingToken)
+ {
+ _mqttPublisher.AnnounceAvailability("sensor", true);
+ await _mqttPublisher.DisconnectAsync();
}
+
}
}
diff --git a/hass-workstation-service/hass-workstation-service.csproj b/hass-workstation-service/hass-workstation-service.csproj
index d75fb12..84ff10d 100644
--- a/hass-workstation-service/hass-workstation-service.csproj
+++ b/hass-workstation-service/hass-workstation-service.csproj
@@ -49,6 +49,7 @@
+
diff --git a/hass-workstation-service/hass-workstation-service.exe b/hass-workstation-service/hass-workstation-service.exe
deleted file mode 100644
index c309e92..0000000
Binary files a/hass-workstation-service/hass-workstation-service.exe and /dev/null differ