implement custom command and availability topics wip

pull/34/head
sleevezipper 4 years ago
parent df8b564c37
commit 06b5373d6b

@ -124,3 +124,15 @@ 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.
### 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.|

@ -23,9 +23,18 @@
<ProjectReference Include="..\hass-workstation-service\hass-workstation-service.csproj" />
</ItemGroup>
<ItemGroup>
<Compile Update="Views\AddCommandDialog.axaml.cs">
<DependentUpon>AddCommandDialog.axaml</DependentUpon>
</Compile>
<Compile Update="Views\AppInfo.axaml.cs">
<DependentUpon>AppInfo.axaml</DependentUpon>
</Compile>
<Compile Update="Views\BackgroundServiceSettings.axaml.cs">
<DependentUpon>BackgroundServiceSettings.axaml</DependentUpon>
</Compile>
<Compile Update="Views\CommandSettings.axaml.cs">
<DependentUpon>CommandSettings.axaml</DependentUpon>
</Compile>
<Compile Update="Views\SensorSettings.axaml.cs">
<DependentUpon>SensorSettings.axaml</DependentUpon>
</Compile>

@ -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; }
}
}

@ -0,0 +1,25 @@
using ReactiveUI;
using System;
using System.Collections.Generic;
using System.Text;
namespace UserInterface.ViewModels
{
public class CommandSettingsViewModel : ViewModelBase
{
private ICollection<CommandViewModel> configuredCommands;
public ICollection<CommandViewModel> 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; }
}
}

@ -0,0 +1,26 @@
<Window xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="UserInterface.Views.AddCommandDialog"
SizeToContent="WidthAndHeight"
Title="Add sensor">
<StackPanel Margin="40" MinWidth="200">
<ContentControl Margin="0 20 0 10">Sensor type</ContentControl>
<ComboBox x:Name="ComboBox" SelectionChanged="ComboBoxClosed" SelectedItem="{Binding SelectedType}" MinHeight="27"></ComboBox>
<TextBlock Margin="0 10 0 10" MaxWidth="300" TextWrapping="Wrap" TextAlignment="Left" Text="{Binding Description}"></TextBlock>
<Button IsVisible="{Binding MoreInfoLink, Converter={x:Static StringConverters.IsNotNullOrEmpty}}" Click="OpenInfo" Margin="0 10 0 10">Click for more information.</Button>
<ContentControl Margin="0 20 0 10">Name</ContentControl>
<TextBox Text="{Binding Name}" HorizontalAlignment="Left" MinWidth="150"/>
<TextBlock Text="{Binding UpdateInterval, StringFormat= Update every {0} seconds}" HorizontalAlignment="Left" MinWidth="150"/>
<ContentControl IsVisible="{Binding ShowCommandInput}" Margin="0 20 0 10">Command</ContentControl>
<TextBox IsVisible="{Binding ShowCommandInput}" Text="{Binding Command}" Watermark="Rundll32.exe user32.dll,LockWorkStation" HorizontalAlignment="Left" MinWidth="300"/>
<Button Width="75" HorizontalAlignment="Right" Margin="0 40 0 10" Click="Test">Test</Button>
<Button Width="75" HorizontalAlignment="Right" Margin="0 40 0 10" Click="Save">Save</Button>
</StackPanel>
</Window>

@ -0,0 +1,97 @@
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<ServiceContractInterfaces> 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>("ComboBox");
this.comboBox.Items = Enum.GetValues(typeof(AvailableCommands)).Cast<AvailableCommands>().OrderBy(v => v.ToString());
this.comboBox.SelectedIndex = 0;
// register IPC clients
ServiceProvider serviceProvider = new ServiceCollection()
.AddNamedPipeIpcClient<ServiceContractInterfaces>("addCommand", pipeName: "pipeinternal")
.BuildServiceProvider();
// resolve IPC client factory
IIpcClientFactory<ServiceContractInterfaces> clientFactory = serviceProvider
.GetRequiredService<IIpcClientFactory<ServiceContractInterfaces>>();
// 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;
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);
}
}
}

@ -0,0 +1,17 @@
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" d:DesignWidth="500" d:DesignHeight="450"
x:Class="UserInterface.Views.AppInfo">
<StackPanel Margin="30">
<StackPanel Margin="0 0 0 20" HorizontalAlignment="Left">
<ContentControl FontSize="18" FontWeight="Bold" >Info</ContentControl>
<TextBlock Text="Need some help?" Margin="0 0 0 20"></TextBlock >
<StackPanel Margin="0 0 0 20" HorizontalAlignment="Left" Orientation="Horizontal">
<Button Width="75" HorizontalAlignment="Right" Margin="10 10" Click="Github">Github</Button>
<Button Width="75" HorizontalAlignment="Left" Margin="10 10" Click="Discord">Discord</Button>
</StackPanel>
</StackPanel>
</StackPanel>
</UserControl>

@ -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<ServiceContractInterfaces> client;
public AppInfo()
{
this.InitializeComponent();
// register IPC clients
ServiceProvider serviceProvider = new ServiceCollection()
.AddNamedPipeIpcClient<ServiceContractInterfaces>("broker", pipeName: "pipeinternal")
.BuildServiceProvider();
// resolve IPC client factory
IIpcClientFactory<ServiceContractInterfaces> clientFactory = serviceProvider
.GetRequiredService<IIpcClientFactory<ServiceContractInterfaces>>();
// 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);
}
}
}

@ -0,0 +1,26 @@
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" d:DesignWidth="500" d:DesignHeight="450"
MaxWidth="800"
x:Class="UserInterface.Views.CommandSettings" >
<StackPanel Margin="30" HorizontalAlignment="Left" >
<ContentControl FontSize="18" Margin="0 0 0 15" FontWeight="Bold">Commands</ContentControl>
<DataGrid x:Name="Grid" IsVisible="{Binding ConfiguredCommands.Count}" AutoGenerateColumns="False" IsReadOnly="True" SelectionMode="Single" Items="{Binding ConfiguredCommands}">
<DataGrid.Columns>
<DataGridTextColumn Header="Name"
Binding="{Binding Name}"
Width="1*" />
<DataGridTextColumn Header="Type"
Binding="{Binding Type}"
Width="1*" />
</DataGrid.Columns>
</DataGrid>
<TextBlock IsVisible="{Binding !ConfiguredSensors.Count}">Add some commands by clicking the "Add" button. </TextBlock>
<Button Width="75" HorizontalAlignment="Right" Margin="0 40 0 10" Click="Add">Add</Button>
<Button Width="75" HorizontalAlignment="Right" Margin="0 40 0 10" Click="Delete">Delete</Button>
</StackPanel>
</UserControl>

@ -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<ServiceContractInterfaces> client;
private DataGrid _dataGrid { get; set; }
private bool sensorsNeedToRefresh { get; set; }
public CommandSettings()
{
this.InitializeComponent();
// register IPC clients
ServiceProvider serviceProvider = new ServiceCollection()
.AddNamedPipeIpcClient<ServiceContractInterfaces>("commands", pipeName: "pipeinternal")
.BuildServiceProvider();
// resolve IPC client factory
IIpcClientFactory<ServiceContractInterfaces> clientFactory = serviceProvider
.GetRequiredService<IIpcClientFactory<ServiceContractInterfaces>>();
// create client
this.client = clientFactory.CreateClient("commands");
DataContext = new CommandSettingsViewModel();
GetConfiguredCommands();
this._dataGrid = this.FindControl<DataGrid>("Grid");
}
public async void GetConfiguredCommands()
{
sensorsNeedToRefresh = false;
List<ConfiguredCommandModel> 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);
}
}
}

@ -14,9 +14,11 @@
</Design.DataContext>
<Grid ColumnDefinitions="Auto,1*,Auto" RowDefinitions="Auto,Auto,Auto" Margin="30">
<views:BrokerSettings Grid.Column="0" Grid.Row="0" Margin="10 0" Background="#2D2D30"/>
<views:BrokerSettings Grid.Column="0" Grid.Row="0" Margin="10 0" Grid.RowSpan="2" Background="#2D2D30"/>
<views:SensorSettings Grid.Column="1" Grid.Row="0" Margin="10 0" Background="#2D2D30"/>
<views:CommandSettings Grid.Column="1" Grid.Row="1" Margin="0 10 0 0" Background="#2D2D30"/>
<views:BackgroundServiceSettings Grid.Column="2" Grid.Row="0" Margin="10 0" Background="#2D2D30"/>
<views:AppInfo Grid.Column="2" Grid.Row="1" Margin="10 10 10 0" Background="#2D2D30"/>
<!--<views:BrokerSettings Grid.Column="1" Grid.Row="0"/>
<views:BrokerSettings Grid.Column="2" Grid.Row="0"/>-->

@ -6,6 +6,7 @@
MaxWidth="800"
x:Class="UserInterface.Views.SensorSettings" >
<StackPanel Margin="30" HorizontalAlignment="Left" >
<ContentControl FontSize="18" Margin="0 0 0 15" FontWeight="Bold">Sensors</ContentControl>
<DataGrid x:Name="Grid" IsVisible="{Binding ConfiguredSensors.Count}" AutoGenerateColumns="False" IsReadOnly="True" SelectionMode="Single" Items="{Binding ConfiguredSensors}">
<DataGrid.Columns>

@ -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?";
}
/// <summary>
/// This writes the provided settings to the config file.
/// </summary>
/// <param name="settings"></param>
public void WriteMqttBrokerSettingsAsync(MqttSettings settings)
{
this._configurationService.WriteMqttBrokerSettingsAsync(settings);
}
/// <summary>
/// Enables or disables autostart.
/// </summary>
/// <param name="enable"></param>
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<ConfiguredCommandModel> 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);
}
/// <summary>
/// Adds a command to the configured commands. This properly initializes the class and writes it to the config file.
/// </summary>
/// <param name="sensorType"></param>
/// <param name="json"></param>
public void AddSensor(AvailableSensors sensorType, string json)
{
var serializerOptions = new JsonSerializerOptions
@ -133,5 +156,34 @@ namespace hass_workstation_service.Communication.InterProcesCommunication
this._configurationService.AddConfiguredSensor(sensorToCreate);
}
}
/// <summary>
/// Adds a command to the configured commands. This properly initializes the class, subscribes to the command topic and writes it to the config file.
/// </summary>
/// <param name="commandType"></param>
/// <param name="json"></param>
public void AddCommand(AvailableCommands commandType, string json)
{
var serializerOptions = new JsonSerializerOptions
{
Converters = { new DynamicJsonConverter() }
};
dynamic model = JsonSerializer.Deserialize<dynamic>(json, serializerOptions);
AbstractCommand commandToCreate = null;
switch (commandType)
{
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);
}
}
}
}

@ -17,5 +17,8 @@ namespace hass_workstation_service.Communication.NamedPipe
List<ConfiguredSensorModel> GetConfiguredSensors();
void RemoveSensorById(Guid id);
void AddSensor(AvailableSensors sensorType, string json);
void RemoveCommandById(Guid id);
List<ConfiguredCommandModel> GetConfiguredCommands();
void AddCommand(AvailableCommands commandType, string json);
}
}

@ -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,9 @@ namespace hass_workstation_service.Communication.InterProcesCommunication.Models
LastBootSensor,
SessionStateSensor
}
public enum AvailableCommands
{
CustomCommand
}
}

@ -1,10 +1,13 @@
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;
@ -24,6 +27,7 @@ namespace hass_workstation_service.Communication
private string _mqttClientMessage { get; set; }
public DateTime LastConfigAnnounce { get; private set; }
public DeviceConfigModel DeviceConfigModel { get; private set; }
public ICollection<AbstractCommand> Subscribers { get; private set; }
public bool IsConnected
{
get
@ -44,7 +48,7 @@ namespace hass_workstation_service.Communication
DeviceConfigModel deviceConfigModel,
IConfigurationService configurationService)
{
this.Subscribers = new List<AbstractCommand>();
this._logger = logger;
this.DeviceConfigModel = deviceConfigModel;
this._configurationService = configurationService;
@ -69,6 +73,8 @@ namespace hass_workstation_service.Communication
this._mqttClientMessage = "All good";
});
this._mqttClient.UseApplicationMessageReceivedHandler(e => this.HandleMessageReceived(e.ApplicationMessage));
// configure what happens on disconnect
this._mqttClient.UseDisconnectedHandler(async e =>
{
@ -103,7 +109,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 +117,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);
@ -147,5 +155,72 @@ 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()
);
}
else
{
this._logger.LogInformation($"Availability announce dropped because mqtt not connected");
}
}
public async Task DisconnectAsync()
{
if (this._mqttClient.IsConnected)
{
await this._mqttClient.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)
{
command.Execute();
}
}
Console.WriteLine("### RECEIVED APPLICATION MESSAGE ###");
Console.WriteLine($"+ Topic = {applicationMessage.Topic}");
Console.WriteLine($"+ Payload = {Encoding.UTF8.GetString(applicationMessage?.Payload)}");
Console.WriteLine($"+ QoS = {applicationMessage.QualityOfServiceLevel}");
Console.WriteLine($"+ Retain = {applicationMessage.Retain}");
Console.WriteLine();
}
}
}

@ -3,18 +3,26 @@ using System.Collections.Generic;
namespace hass_workstation_service.Communication
{
public class AutoDiscoveryConfigModel
public abstract class DiscoveryConfigModel
{
/// <summary>
/// (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.
/// </summary>
/// <value></value>
public string Availability_topic { get; set; }
public DeviceConfigModel Device { get; set; }
/// <summary>
/// (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.
/// </summary>
/// <value></value>
public DeviceConfigModel Device { get; set; }
public string Name { get; set; }
}
public class SensorDiscoveryConfigModel : DiscoveryConfigModel
{
/// <summary>
/// (Optional) The MQTT topic subscribed to receive availability (online/offline) updates.
/// </summary>
/// <value></value>
public string Availability_topic { get; set; }
/// <summary>
/// (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.
/// </summary>
@ -47,11 +55,6 @@ namespace hass_workstation_service.Communication
/// <value></value>
public string Json_attributes_topic { get; set; }
/// <summary>
/// (Optional) The name of the MQTT sensor. Defaults to MQTT Sensor in hass.
/// </summary>
/// <value></value>
public string Name { get; set; }
/// <summary>
/// (Optional) The payload that represents the available state.
/// </summary>
/// <value></value>
@ -89,6 +92,82 @@ namespace hass_workstation_service.Communication
public string Value_template { get; set; }
}
public class CommandDiscoveryConfigModel : DiscoveryConfigModel
{
/// <summary>
/// (Optional) The MQTT topic subscribed to receive availability (online/offline) updates.
/// </summary>
/// <value></value>
public string Availability_topic { get; set; }
/// <summary>
/// (Optional) The MQTT topic to set the command
/// </summary>
/// <value></value>
public string Command_topic { get; set; }
/// <summary>
/// (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.
/// </summary>
/// <value></value>
public string Device_class { get; set; }
/// <summary>
/// (Optional) Defines the number of seconds after the sensors state expires, if its not updated. After expiry, the sensors state becomes unavailable. Defaults to 0 in hass.
/// </summary>
/// <value></value>
public int? Expire_after { get; set; }
/// <summary>
/// Sends update events even if the value hasnt changed. Useful if you want to have meaningful value graphs in history.
/// </summary>
/// <value></value>
public bool? Force_update { get; set; }
/// <summary>
/// (Optional) The icon for the sensor.
/// </summary>
/// <value></value>
public string Icon { get; set; }
/// <summary>
/// (Optional) Defines a template to extract the JSON dictionary from messages received on the json_attributes_topic.
/// </summary>
/// <value></value>
public string Json_attributes_template { get; set; }
/// <summary>
/// (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.
/// </summary>
/// <value></value>
public string Json_attributes_topic { get; set; }
/// <summary>
/// (Optional) The payload that represents the available state.
/// </summary>
/// <value></value>
public string Payload_available { get; set; }
/// <summary>
/// (Optional) The payload that represents the unavailable state.
/// </summary>
/// <value></value>
public string Payload_not_available { get; set; }
/// <summary>
/// (Optional) The maximum QoS level of the state topic.
/// </summary>
/// <value></value>
public int? Qos { get; set; }
/// <summary>
/// The MQTT topic subscribed to receive sensor values.
/// </summary>
/// <value></value>
public string State_topic { get; set; }
/// <summary>
/// (Optional) An ID that uniquely identifies this sensor. If two sensors have the same unique ID, Home Assistant will raise an exception.
/// </summary>
/// <value></value>
public string Unique_id { get; set; }
/// <summary>
/// (Optional) Defines a template to extract the value.
/// </summary>
/// <value></value>
public string Value_template { get; set; }
}
/// <summary>
/// This information will be used when announcing this device on the mqtt topic
/// </summary>

@ -10,6 +10,7 @@ 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;
@ -23,10 +24,12 @@ namespace hass_workstation_service.Data
public class ConfigurationService : IConfigurationService
{
public ICollection<AbstractSensor> ConfiguredSensors { get; private set; }
public ICollection<AbstractCommand> ConfiguredCommands { get; private set; }
public Action<IMqttClientOptions> MqqtConfigChangedHandler { get; set; }
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");
@ -42,7 +45,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<AbstractSensor>();
ConfiguredCommands = new List<AbstractCommand>();
}
public async void ReadSensorSettings(MqttPublisher publisher)
@ -123,6 +132,44 @@ namespace hass_workstation_service.Data
}
}
public async void ReadCommandSettings(MqttPublisher publisher)
{
while (this.CommandSettingsFileLocked)
{
await Task.Delay(500);
}
this.CommandSettingsFileLocked = true;
List<ConfiguredCommand> commands = new List<ConfiguredCommand>();
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<List<ConfiguredCommand>>(stream);
}
stream.Close();
this.CommandSettingsFileLocked = false;
}
foreach (ConfiguredCommand configuredCommand in commands)
{
AbstractCommand command = null;
switch (configuredCommand.Type)
{
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<IMqttClientOptions> GetMqttClientOptionsAsync()
{
ConfiguredMqttBroker configuredBroker = await ReadMqttSettingsAsync();
@ -173,7 +220,7 @@ namespace hass_workstation_service.Data
return configuredBroker;
}
public async void WriteSettingsAsync()
public async void WriteSensorSettingsAsync()
{
while (this.SensorsSettingsFileLocked)
{
@ -210,11 +257,44 @@ namespace hass_workstation_service.Data
this.SensorsSettingsFileLocked = false;
}
public async void WriteCommandSettingsAsync()
{
while (this.CommandSettingsFileLocked)
{
await Task.Delay(500);
}
this.CommandSettingsFileLocked = true;
List<ConfiguredCommand> configuredCommandsToSave = new List<ConfiguredCommand>();
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 +304,7 @@ namespace hass_workstation_service.Data
{
await sensorToRemove.UnPublishAutoDiscoveryConfigAsync();
this.ConfiguredSensors.Remove(sensorToRemove);
WriteSettingsAsync();
WriteSensorSettingsAsync();
}
else
{
@ -233,10 +313,26 @@ namespace hass_workstation_service.Data
}
public async void DeleteConfiguredCommand(Guid id)
{
var sensorToRemove = this.ConfiguredCommands.FirstOrDefault(s => s.Id == id);
if (sensorToRemove != null)
{
await sensorToRemove.UnPublishAutoDiscoveryConfigAsync();
this.ConfiguredCommands.Remove(sensorToRemove);
WriteSensorSettingsAsync();
}
else
{
Log.Logger.Warning($"command with id {id} not found");
}
}
public void AddConfiguredSensors(List<AbstractSensor> sensors)
{
sensors.ForEach((sensor) => this.ConfiguredSensors.Add(sensor));
WriteSettingsAsync();
WriteSensorSettingsAsync();
}
/// <summary>

@ -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; }
}
}

@ -1,5 +1,6 @@
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 System;
@ -13,16 +14,21 @@ namespace hass_workstation_service.Data
{
ICollection<AbstractSensor> ConfiguredSensors { get; }
Action<IMqttClientOptions> MqqtConfigChangedHandler { get; set; }
ICollection<AbstractCommand> ConfiguredCommands { get; }
void AddConfiguredCommand(AbstractCommand command);
void AddConfiguredSensor(AbstractSensor sensor);
void AddConfiguredSensors(List<AbstractSensor> sensors);
Task<IMqttClientOptions> GetMqttClientOptionsAsync();
void ReadSensorSettings(MqttPublisher publisher);
void WriteMqttBrokerSettingsAsync(MqttSettings settings);
void WriteSettingsAsync();
void WriteSensorSettingsAsync();
Task<MqttSettings> GetMqttBrokerSettings();
void EnableAutoStart(bool enable);
bool IsAutoStartEnabled();
void DeleteConfiguredSensor(Guid id);
void DeleteConfiguredCommand(Guid id);
void WriteCommandSettingsAsync();
void ReadCommandSettings(MqttPublisher publisher);
}
}

@ -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; }
}
}

@ -0,0 +1,79 @@
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; }
/// <summary>
/// The update interval in seconds. It checks state only if the interval has passed.
/// </summary>
public int UpdateInterval { get; protected set; }
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 Execute();
}
}

@ -0,0 +1,48 @@
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 CustomCommand : AbstractCommand
{
public string Command { get; protected set; }
public CustomCommand(MqttPublisher publisher, string command, string name = "Custom", Guid id = default(Guid)) : base(publisher, name ?? "Custom", id)
{
this.Command = command;
}
public override void Execute()
{
System.Diagnostics.Process process = new System.Diagnostics.Process();
System.Diagnostics.ProcessStartInfo startInfo = new System.Diagnostics.ProcessStartInfo();
startInfo.WindowStyle = System.Diagnostics.ProcessWindowStyle.Hidden;
startInfo.CreateNoWindow = true;
startInfo.FileName = "cmd.exe";
startInfo.Arguments = $"/C {this.Command}";
process.StartInfo = startInfo;
process.Start();
}
public override CommandDiscoveryConfigModel GetAutoDiscoveryConfig()
{
return new CommandDiscoveryConfigModel()
{
Name = this.Name,
Unique_id = this.Id.ToString(),
Availability_topic = $"homeassistant/{this.Domain}/{Publisher.DeviceConfigModel.Name}/availability",
Command_topic = $"homeassistant/{this.Domain}/{Publisher.DeviceConfigModel.Name}/{this.Name}/set",
Device = this.Publisher.DeviceConfigModel,
Expire_after = 60
};
}
public override string GetState()
{
return "off";
}
}
}

@ -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);
}
}

@ -9,15 +9,17 @@ 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",
Expire_after = 60
});
}

@ -17,16 +17,18 @@ 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",
Expire_after = 60
});
}

@ -9,16 +9,18 @@ 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",
Expire_after = 60
});
}
}

@ -13,14 +13,16 @@ 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",
Expire_after = 60
});
}

@ -9,15 +9,17 @@ 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",
Expire_after = 60
});
}

@ -13,15 +13,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",
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",
Expire_after = 60
});
}

@ -34,16 +34,18 @@ 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",
Expire_after = 60
});
}
}

@ -21,15 +21,17 @@ 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",
Expire_after = 60
});
}

@ -16,15 +16,17 @@ 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",
Expire_after = 60
});
}

@ -35,15 +35,17 @@ 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",
Expire_after = 60
});
}

@ -9,15 +9,17 @@ 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",
Expire_after = 60
});
}

@ -20,14 +20,16 @@ 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",
Expire_after = 60
});
}

@ -23,15 +23,17 @@ 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",
Expire_after = 60
});
}

@ -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,11 +42,17 @@ namespace hass_workstation_service
_logger.LogInformation("Connected. Sending auto discovery messages.");
List<AbstractSensor> sensors = _configurationService.ConfiguredSensors.ToList();
List<AbstractCommand> commands = _configurationService.ConfiguredCommands.ToList();
_mqttPublisher.AnnounceAvailability("sensor");
_mqttPublisher.AnnounceAvailability("switch");
foreach (AbstractSensor sensor in sensors)
{
sensor.PublishAutoDiscoveryConfigAsync();
}
foreach (AbstractCommand command in commands)
{
command.PublishAutoDiscoveryConfigAsync();
}
while (!stoppingToken.IsCancellationRequested)
{
sensors = _configurationService.ConfiguredSensors.ToList();
@ -60,7 +68,7 @@ namespace hass_workstation_service
{
Log.Logger.Warning("Sensor failed: " + sensor.Name, ex);
}
}
// announce autodiscovery every 30 seconds
if (_mqttPublisher.LastConfigAnnounce < DateTime.UtcNow.AddSeconds(-30))
@ -69,9 +77,24 @@ namespace hass_workstation_service
{
sensor.PublishAutoDiscoveryConfigAsync();
}
foreach (AbstractCommand command in commands)
{
command.PublishAutoDiscoveryConfigAsync();
}
_mqttPublisher.AnnounceAvailability("sensor");
_mqttPublisher.AnnounceAvailability("switch");
}
await Task.Delay(1000, stoppingToken);
}
}
public override async Task StopAsync(CancellationToken stoppingToken)
{
_mqttPublisher.AnnounceAvailability("sensor", true);
_mqttPublisher.AnnounceAvailability("switch", true);
await _mqttPublisher.DisconnectAsync();
}
}
}

Loading…
Cancel
Save