Merge branch 'develop'

pull/163/head 1.0.0.29957
Sleevezipper 2 years ago
commit 7fd20a4fc5

@ -17,7 +17,7 @@
<PackageReference Include="Avalonia.ReactiveUI" Version="0.10.8" /> <PackageReference Include="Avalonia.ReactiveUI" Version="0.10.8" />
<PackageReference Include="Avalonia.Win32" Version="0.10.8" /> <PackageReference Include="Avalonia.Win32" Version="0.10.8" />
<PackageReference Include="JKang.IpcServiceFramework.Client.NamedPipe" Version="3.1.0" /> <PackageReference Include="JKang.IpcServiceFramework.Client.NamedPipe" Version="3.1.0" />
<PackageReference Include="Serilog.Sinks.Console" Version="4.0.0" /> <PackageReference Include="Serilog.Sinks.Console" Version="4.0.1" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\hass-workstation-service\hass-workstation-service.csproj" /> <ProjectReference Include="..\hass-workstation-service\hass-workstation-service.csproj" />

@ -13,6 +13,7 @@ namespace UserInterface.ViewModels
private bool _showWindowNameInput; private bool _showWindowNameInput;
private string _moreInfoLink; private string _moreInfoLink;
private string _query; private string _query;
private string _scope;
private string _windowName; private string _windowName;
public AvailableSensors SelectedType { get => _selectedType; set => this.RaiseAndSetIfChanged(ref _selectedType, value); } public AvailableSensors SelectedType { get => _selectedType; set => this.RaiseAndSetIfChanged(ref _selectedType, value); }
@ -23,6 +24,7 @@ namespace UserInterface.ViewModels
public bool ShowWindowNameInput { get => _showWindowNameInput; set => this.RaiseAndSetIfChanged(ref _showWindowNameInput, value); } public bool ShowWindowNameInput { get => _showWindowNameInput; set => this.RaiseAndSetIfChanged(ref _showWindowNameInput, value); }
public string MoreInfoLink { get => _moreInfoLink; set => this.RaiseAndSetIfChanged(ref _moreInfoLink, value); } public string MoreInfoLink { get => _moreInfoLink; set => this.RaiseAndSetIfChanged(ref _moreInfoLink, value); }
public string Query { get => _query; set => this.RaiseAndSetIfChanged(ref _query, value); } public string Query { get => _query; set => this.RaiseAndSetIfChanged(ref _query, value); }
public string Scope { get => _scope; set => this.RaiseAndSetIfChanged(ref _scope, value); }
public string WindowName { get => _windowName; set => this.RaiseAndSetIfChanged(ref _windowName, value); } public string WindowName { get => _windowName; set => this.RaiseAndSetIfChanged(ref _windowName, value); }
} }
} }

@ -16,6 +16,9 @@ namespace UserInterface.ViewModels
private bool isConnected; private bool isConnected;
private int? port; private int? port;
private bool useTLS; private bool useTLS;
private bool retainLWT = true;
private string rootCaPath;
private string clientCertPath;
public bool IsConnected { get => isConnected; set => this.RaiseAndSetIfChanged(ref isConnected, value); } public bool IsConnected { get => isConnected; set => this.RaiseAndSetIfChanged(ref isConnected, value); }
public string Message { get => message; set => this.RaiseAndSetIfChanged(ref message, value); } public string Message { get => message; set => this.RaiseAndSetIfChanged(ref message, value); }
@ -29,6 +32,13 @@ namespace UserInterface.ViewModels
public bool UseTLS { get => useTLS; set => this.RaiseAndSetIfChanged(ref useTLS, value); } public bool UseTLS { get => useTLS; set => this.RaiseAndSetIfChanged(ref useTLS, value); }
public bool RetainLWT { get => retainLWT; set => this.RaiseAndSetIfChanged(ref retainLWT, value); }
public string RootCAPath { get => rootCaPath; set => this.RaiseAndSetIfChanged(ref rootCaPath, value); }
public string ClientCertPath { get => clientCertPath; set => this.RaiseAndSetIfChanged(ref clientCertPath, value); }
public void Update(MqttSettings settings) public void Update(MqttSettings settings)
{ {
this.Host = settings.Host; this.Host = settings.Host;
@ -36,6 +46,9 @@ namespace UserInterface.ViewModels
this.Password = settings.Password; this.Password = settings.Password;
this.Port = settings.Port; this.Port = settings.Port;
this.UseTLS = settings.UseTLS; this.UseTLS = settings.UseTLS;
this.RetainLWT = settings.RetainLWT;
this.RootCAPath = settings.RootCAPath;
this.ClientCertPath = settings.ClientCertPath;
} }
public void UpdateStatus(MqqtClientStatus status) public void UpdateStatus(MqqtClientStatus status)

@ -21,8 +21,10 @@
<TextBox Text="{Binding UpdateInterval}" HorizontalAlignment="Right" MaxWidth="30"/> <TextBox Text="{Binding UpdateInterval}" HorizontalAlignment="Right" MaxWidth="30"/>
</StackPanel> </StackPanel>
<TextBlock Text="{Binding UpdateInterval, StringFormat= Update every {0} seconds}" HorizontalAlignment="Left" MinWidth="150"/> <TextBlock Text="{Binding UpdateInterval, StringFormat= Update every {0} seconds}" HorizontalAlignment="Left" MinWidth="150"/>
<ContentControl IsVisible="{Binding ShowQueryInput}" Margin="0 20 0 10">Query</ContentControl> <ContentControl IsVisible="{Binding ShowQueryInput}" Margin="0 20 0 10">Scope (optional)</ContentControl>
<TextBox IsVisible="{Binding ShowQueryInput}" Text="{Binding Query}" Watermark="SELECT Name FROM Win32_Processor" HorizontalAlignment="Left" MinWidth="300"/> <TextBox IsVisible="{Binding ShowQueryInput}" Text="{Binding Scope}" Watermark="\\localhost\ROOT\StandardCimv2" HorizontalAlignment="Left" MinWidth="300"/>
<ContentControl IsVisible="{Binding ShowQueryInput}" Margin="0 20 0 10">Query</ContentControl>
<TextBox IsVisible="{Binding ShowQueryInput}" Text="{Binding Query}" Watermark="SELECT Name FROM Win32_Processor" HorizontalAlignment="Left" MinWidth="300"/>
<ContentControl IsVisible="{Binding ShowWindowNameInput}" Margin="0 20 0 5">Window name</ContentControl> <ContentControl IsVisible="{Binding ShowWindowNameInput}" Margin="0 20 0 5">Window name</ContentControl>
<TextBlock TextWrapping="Wrap" MaxWidth="300" FontStyle="Italic" IsVisible="{Binding ShowWindowNameInput}" Margin="0 0 0 10">This is case-insensitive and loosely matched. A window called "Spotify Premium" will match "spotify" or "premium".</TextBlock> <TextBlock TextWrapping="Wrap" MaxWidth="300" FontStyle="Italic" IsVisible="{Binding ShowWindowNameInput}" Margin="0 0 0 10">This is case-insensitive and loosely matched. A window called "Spotify Premium" will match "spotify" or "premium".</TextBlock>
<TextBox IsVisible="{Binding ShowWindowNameInput}" Text="{Binding WindowName}" Watermark="Visual Studio Code" HorizontalAlignment="Left" MinWidth="300"/> <TextBox IsVisible="{Binding ShowWindowNameInput}" Text="{Binding WindowName}" Watermark="Visual Studio Code" HorizontalAlignment="Left" MinWidth="300"/>

@ -67,6 +67,7 @@ namespace UserInterface.Views
item.Name = sensor.Name; item.Name = sensor.Name;
item.UpdateInterval = sensor.UpdateInterval; item.UpdateInterval = sensor.UpdateInterval;
item.Query = sensor.Query; item.Query = sensor.Query;
item.Scope = sensor.Scope;
item.WindowName = sensor.WindowName; item.WindowName = sensor.WindowName;
Title = $"Edit {sensor.Name}"; Title = $"Edit {sensor.Name}";
@ -75,7 +76,7 @@ namespace UserInterface.Views
public async void Save(object sender, RoutedEventArgs args) public async void Save(object sender, RoutedEventArgs args)
{ {
var item = (AddSensorViewModel)DataContext; var item = (AddSensorViewModel)DataContext;
dynamic model = new { item.Name, item.Query, item.UpdateInterval, item.WindowName }; dynamic model = new { item.Name, item.Query, item.UpdateInterval, item.WindowName, item.Scope };
string json = JsonSerializer.Serialize(model); string json = JsonSerializer.Serialize(model);
if (SensorId == Guid.Empty) if (SensorId == Guid.Empty)
await _client.InvokeAsync(x => x.AddSensor(item.SelectedType, json)); await _client.InvokeAsync(x => x.AddSensor(item.SelectedType, json));
@ -102,7 +103,7 @@ namespace UserInterface.Views
item.ShowWindowNameInput = false; item.ShowWindowNameInput = false;
item.UpdateInterval = 5; item.UpdateInterval = 5;
break; break;
case AvailableSensors.DummySensor: case AvailableSensors.DummySensor:
item.Description = "This sensor spits out a random number every second. Useful for testing, maybe you'll find some other use for it."; item.Description = "This sensor spits out a random number every second. Useful for testing, maybe you'll find some other use for it.";
item.MoreInfoLink = "https://github.com/sleevezipper/hass-workstation-service/blob/master/documentation/Sensors.md#dummysensor"; item.MoreInfoLink = "https://github.com/sleevezipper/hass-workstation-service/blob/master/documentation/Sensors.md#dummysensor";
@ -110,7 +111,7 @@ namespace UserInterface.Views
item.ShowWindowNameInput = false; item.ShowWindowNameInput = false;
item.UpdateInterval = 1; item.UpdateInterval = 1;
break; break;
case AvailableSensors.CPULoadSensor: case AvailableSensors.CPULoadSensor:
item.Description = "This sensor checks the current CPU load. It averages the load on all logical cores every second and rounds the output to two decimals."; item.Description = "This sensor checks the current CPU load. It averages the load on all logical cores every second and rounds the output to two decimals.";
item.MoreInfoLink = "https://github.com/sleevezipper/hass-workstation-service/blob/master/documentation/Sensors.md#cpuloadsensor"; item.MoreInfoLink = "https://github.com/sleevezipper/hass-workstation-service/blob/master/documentation/Sensors.md#cpuloadsensor";
@ -118,7 +119,7 @@ namespace UserInterface.Views
item.ShowWindowNameInput = false; item.ShowWindowNameInput = false;
item.UpdateInterval = 5; item.UpdateInterval = 5;
break; break;
case AvailableSensors.CurrentClockSpeedSensor: case AvailableSensors.CurrentClockSpeedSensor:
item.Description = "This sensor returns the BIOS configured baseclock for the processor."; item.Description = "This sensor returns the BIOS configured baseclock for the processor.";
item.MoreInfoLink = "https://github.com/sleevezipper/hass-workstation-service/blob/master/documentation/Sensors.md#currentclockspeedsensor"; item.MoreInfoLink = "https://github.com/sleevezipper/hass-workstation-service/blob/master/documentation/Sensors.md#currentclockspeedsensor";
@ -126,7 +127,7 @@ namespace UserInterface.Views
item.ShowWindowNameInput = false; item.ShowWindowNameInput = false;
item.UpdateInterval = 3600; item.UpdateInterval = 3600;
break; break;
case AvailableSensors.WMIQuerySensor: case AvailableSensors.WMIQuerySensor:
item.Description = "This advanced sensor executes a user defined WMI query and exposes the result. The query should return a single value."; item.Description = "This advanced sensor executes a user defined WMI query and exposes the result. The query should return a single value.";
item.MoreInfoLink = "https://github.com/sleevezipperhass-workstation-service/blob/master/documentation/WMIQuery.md#wmiquerysensor"; item.MoreInfoLink = "https://github.com/sleevezipperhass-workstation-service/blob/master/documentation/WMIQuery.md#wmiquerysensor";
@ -134,7 +135,7 @@ namespace UserInterface.Views
item.ShowWindowNameInput = false; item.ShowWindowNameInput = false;
item.UpdateInterval = 10; item.UpdateInterval = 10;
break; break;
case AvailableSensors.MemoryUsageSensor: case AvailableSensors.MemoryUsageSensor:
item.Description = "This sensor calculates the percentage of used memory."; item.Description = "This sensor calculates the percentage of used memory.";
item.MoreInfoLink = "https://github.com/sleevezipper/hass-workstation-service/blob/master/documentation/Sensors.md#memoryusagesensorsensor"; item.MoreInfoLink = "https://github.com/sleevezipper/hass-workstation-service/blob/master/documentation/Sensors.md#memoryusagesensorsensor";
@ -142,7 +143,7 @@ namespace UserInterface.Views
item.ShowWindowNameInput = false; item.ShowWindowNameInput = false;
item.UpdateInterval = 10; item.UpdateInterval = 10;
break; break;
case AvailableSensors.ActiveWindowSensor: case AvailableSensors.ActiveWindowSensor:
item.Description = "This sensor exposes the name of the currently active window."; item.Description = "This sensor exposes the name of the currently active window.";
item.MoreInfoLink = "https://github.com/sleevezipper/hass-workstation-service/blob/master/documentation/Sensors.md#activewindowsensor"; item.MoreInfoLink = "https://github.com/sleevezipper/hass-workstation-service/blob/master/documentation/Sensors.md#activewindowsensor";
@ -150,28 +151,35 @@ namespace UserInterface.Views
item.ShowWindowNameInput = false; item.ShowWindowNameInput = false;
item.UpdateInterval = 5; item.UpdateInterval = 5;
break; break;
case AvailableSensors.WebcamActiveSensor: case AvailableSensors.WebcamActiveSensor:
item.Description = "This sensor shows if the webcam is currently being used."; item.Description = "This sensor shows if the webcam is currently being used.";
item.MoreInfoLink = "https://github.com/sleevezipper/hass-workstation-service/blob/master/documentation/Sensors.md#webcamactivesensor"; item.MoreInfoLink = "https://github.com/sleevezipper/hass-workstation-service/blob/master/documentation/Sensors.md#webcamactivesensor";
item.ShowQueryInput = false; item.ShowQueryInput = false;
item.UpdateInterval = 10; item.UpdateInterval = 10;
break; break;
case AvailableSensors.WebcamProcessSensor: case AvailableSensors.WebcamProcessSensor:
item.Description = "This sensor shows which process is using the webcam."; item.Description = "This sensor shows which process is using the webcam.";
item.MoreInfoLink = "https://github.com/sleevezipper/hass-workstation-service/blob/master/documentation/Sensors.md#webcamprocesssensor"; item.MoreInfoLink = "https://github.com/sleevezipper/hass-workstation-service/blob/master/documentation/Sensors.md#webcamprocesssensor";
item.ShowQueryInput = false; item.ShowQueryInput = false;
item.UpdateInterval = 10; item.UpdateInterval = 10;
break; break;
case AvailableSensors.MicrophoneActiveSensor: case AvailableSensors.MicrophoneActiveSensor:
item.Description = "This sensor shows if the microphone is currently in use."; item.Description = "This sensor shows if the microphone is currently in use.";
item.MoreInfoLink = "https://github.com/sleevezipper/hass-workstation-service/blob/master/documentation/Sensors.md#microphoneactivesensor"; item.MoreInfoLink = "https://github.com/sleevezipper/hass-workstation-service/blob/master/documentation/Sensors.md#microphoneactivesensor";
item.ShowQueryInput = false; item.ShowQueryInput = false;
item.UpdateInterval = 10; item.UpdateInterval = 10;
break; break;
case AvailableSensors.MicrophoneProcessSensor:
item.Description = "This sensor shows which process is using the microphone.";
item.MoreInfoLink = "https://github.com/sleevezipper/hass-workstation-service/blob/master/documentation/Sensors.md#microphoneprocesssensor";
item.ShowQueryInput = false;
item.UpdateInterval = 10;
break;
case AvailableSensors.NamedWindowSensor: case AvailableSensors.NamedWindowSensor:
item.Description = "This sensor returns true if a window was found with the name you search for. "; item.Description = "This sensor returns true if a window was found with the name you search for. ";
item.MoreInfoLink = "https://github.com/sleevezipper/hass-workstation-service/blob/master/documentation/Sensors.md#namedwindowsensor"; item.MoreInfoLink = "https://github.com/sleevezipper/hass-workstation-service/blob/master/documentation/Sensors.md#namedwindowsensor";
@ -179,7 +187,7 @@ namespace UserInterface.Views
item.ShowWindowNameInput = true; item.ShowWindowNameInput = true;
item.UpdateInterval = 5; item.UpdateInterval = 5;
break; break;
case AvailableSensors.LastActiveSensor: case AvailableSensors.LastActiveSensor:
item.Description = "This sensor returns the date/time that the workstation was last active."; item.Description = "This sensor returns the date/time that the workstation was last active.";
item.MoreInfoLink = "https://github.com/sleevezipper/hass-workstation-service/blob/master/documentation/Sensors.md#lastactivesensor"; item.MoreInfoLink = "https://github.com/sleevezipper/hass-workstation-service/blob/master/documentation/Sensors.md#lastactivesensor";
@ -187,7 +195,7 @@ namespace UserInterface.Views
item.ShowWindowNameInput = false; item.ShowWindowNameInput = false;
item.UpdateInterval = 5; item.UpdateInterval = 5;
break; break;
case AvailableSensors.LastBootSensor: case AvailableSensors.LastBootSensor:
item.Description = "This sensor returns the date/time that Windows was last booted"; item.Description = "This sensor returns the date/time that Windows was last booted";
item.MoreInfoLink = "https://github.com/sleevezipper/hass-workstation-service/blob/master/documentation/Sensors.md#lastbootsensor"; item.MoreInfoLink = "https://github.com/sleevezipper/hass-workstation-service/blob/master/documentation/Sensors.md#lastbootsensor";
@ -195,7 +203,7 @@ namespace UserInterface.Views
item.ShowWindowNameInput = false; item.ShowWindowNameInput = false;
item.UpdateInterval = 5; item.UpdateInterval = 5;
break; break;
case AvailableSensors.SessionStateSensor: case AvailableSensors.SessionStateSensor:
item.Description = "This sensor returns the state of the Windows session."; item.Description = "This sensor returns the state of the Windows session.";
item.MoreInfoLink = "https://github.com/sleevezipper/hass-workstation-service/blob/master/documentation/Sensors.md#sessionstatesensor"; item.MoreInfoLink = "https://github.com/sleevezipper/hass-workstation-service/blob/master/documentation/Sensors.md#sessionstatesensor";
@ -203,7 +211,7 @@ namespace UserInterface.Views
item.ShowWindowNameInput = false; item.ShowWindowNameInput = false;
item.UpdateInterval = 5; item.UpdateInterval = 5;
break; break;
case AvailableSensors.CurrentVolumeSensor: case AvailableSensors.CurrentVolumeSensor:
item.Description = "This sensor returns the volume of currently playing audio."; item.Description = "This sensor returns the volume of currently playing audio.";
item.MoreInfoLink = "https://github.com/sleevezipper/hass-workstation-service/blob/master/documentation/Sensors.md#currentvolumesensor"; item.MoreInfoLink = "https://github.com/sleevezipper/hass-workstation-service/blob/master/documentation/Sensors.md#currentvolumesensor";
@ -211,7 +219,7 @@ namespace UserInterface.Views
item.ShowWindowNameInput = false; item.ShowWindowNameInput = false;
item.UpdateInterval = 5; item.UpdateInterval = 5;
break; break;
case AvailableSensors.MasterVolumeSensor: case AvailableSensors.MasterVolumeSensor:
item.Description = "This sensor returns the master volume of the currently selected default audio device as a percentage value."; item.Description = "This sensor returns the master volume of the currently selected default audio device as a percentage value.";
item.MoreInfoLink = "https://github.com/sleevezipper/hass-workstation-service/blob/master/documentation/Sensors.md#mastervolumesensor"; item.MoreInfoLink = "https://github.com/sleevezipper/hass-workstation-service/blob/master/documentation/Sensors.md#mastervolumesensor";
@ -219,7 +227,7 @@ namespace UserInterface.Views
item.ShowWindowNameInput = false; item.ShowWindowNameInput = false;
item.UpdateInterval = 5; item.UpdateInterval = 5;
break; break;
case AvailableSensors.GPUTemperatureSensor: case AvailableSensors.GPUTemperatureSensor:
item.Description = "This sensor returns the current temperature of the GPU in °C."; item.Description = "This sensor returns the current temperature of the GPU in °C.";
item.MoreInfoLink = "https://github.com/sleevezipper/hass-workstation-service/blob/master/documentation/Sensors.md#gputemperaturesensor"; item.MoreInfoLink = "https://github.com/sleevezipper/hass-workstation-service/blob/master/documentation/Sensors.md#gputemperaturesensor";
@ -227,7 +235,7 @@ namespace UserInterface.Views
item.ShowWindowNameInput = false; item.ShowWindowNameInput = false;
item.UpdateInterval = 5; item.UpdateInterval = 5;
break; break;
case AvailableSensors.GPULoadSensor: case AvailableSensors.GPULoadSensor:
item.Description = "This sensor returns the current GPU load."; item.Description = "This sensor returns the current GPU load.";
item.MoreInfoLink = "https://github.com/sleevezipper/hass-workstation-service/blob/master/documentation/Sensors.md#gpuloadsensor"; item.MoreInfoLink = "https://github.com/sleevezipper/hass-workstation-service/blob/master/documentation/Sensors.md#gpuloadsensor";
@ -235,7 +243,7 @@ namespace UserInterface.Views
item.ShowWindowNameInput = false; item.ShowWindowNameInput = false;
item.UpdateInterval = 5; item.UpdateInterval = 5;
break; break;
default: default:
item.Description = null; item.Description = null;
item.MoreInfoLink = null; item.MoreInfoLink = null;

@ -4,7 +4,7 @@
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" d:DesignWidth="500" d:DesignHeight="450" mc:Ignorable="d" d:DesignWidth="500" d:DesignHeight="450"
x:Class="UserInterface.Views.BrokerSettings"> x:Class="UserInterface.Views.BrokerSettings">
<StackPanel Margin="30" HorizontalAlignment="Left" ScrollViewer.VerticalScrollBarVisibility="Auto"> <StackPanel Margin="30" HorizontalAlignment="Left" MinWidth="250">
<ContentControl FontSize="18" FontWeight="Bold">MQTT Broker</ContentControl> <ContentControl FontSize="18" FontWeight="Bold">MQTT Broker</ContentControl>
<TextBlock IsVisible="{Binding IsConnected}" Foreground="Green" Text="{Binding Message}"></TextBlock > <TextBlock IsVisible="{Binding IsConnected}" Foreground="Green" Text="{Binding Message}"></TextBlock >
<TextBlock IsVisible="{Binding !IsConnected}" Foreground="Red" Text="{Binding Message}"></TextBlock > <TextBlock IsVisible="{Binding !IsConnected}" Foreground="Red" Text="{Binding Message}"></TextBlock >
@ -25,6 +25,65 @@
<TextBox Text="{Binding Username}" MinWidth="150"/> <TextBox Text="{Binding Username}" MinWidth="150"/>
<ContentControl Margin="0 20 0 10">Password</ContentControl> <ContentControl Margin="0 20 0 10">Password</ContentControl>
<TextBox Text="{Binding Password}" MinWidth="150" PasswordChar="•"/> <TextBox Text="{Binding Password}" MinWidth="150" PasswordChar="•"/>
<Expander Header="Advanced" Margin="0 20 0 0">
<StackPanel>
<StackPanel>
<StackPanel Margin="0 20 0 10" HorizontalAlignment="Left" Orientation="Horizontal">
<ContentControl>Retain LastWillAndTestament</ContentControl>
<TextBlock Cursor="Help" Margin="5 0 0 0" VerticalAlignment="Bottom" TextDecorations="Underline">
(What's this?)
<ToolTip.Tip>
<StackPanel>
<TextBlock>
[Experimental]
If set, sets Retain on the Last Will and Testament message.
Only turn this off if you use a broker that does not support this(e.g. AWS IoT Core)
Defaults to True
</TextBlock>
</StackPanel>
</ToolTip.Tip>
</TextBlock>
</StackPanel>
<CheckBox IsChecked="{Binding RetainLWT}" HorizontalAlignment="Left" Margin="0 0 0 0"/>
</StackPanel>
<StackPanel Margin="0 20 0 10" HorizontalAlignment="Left" Orientation="Horizontal">
<ContentControl>Root Cert Path (.pem/.crt)</ContentControl>
<TextBlock Cursor="Help" Margin="5 0 0 0" VerticalAlignment="Bottom" TextDecorations="Underline">
(What's this?)
<ToolTip.Tip>
<StackPanel>
<TextBlock>
[Experimental]
If set, use this certificate in the TLS configuration for the MQTT connection.
This will be a pem or crt file provided by your broker.
</TextBlock>
</StackPanel>
</ToolTip.Tip>
</TextBlock>
</StackPanel>
<TextBox Text="{Binding RootCAPath}" MinWidth="150"/>
<StackPanel Margin="0 20 0 10" HorizontalAlignment="Left" Orientation="Horizontal">
<ContentControl>Client Cert Path (.pfx)</ContentControl>
<TextBlock Cursor="Help" Margin="5 0 0 0" VerticalAlignment="Bottom" TextDecorations="Underline">
(What's this?)
<ToolTip.Tip>
<StackPanel>
<TextBlock>
[Experimental]
If set, use this certificate in the TLS configuration for the MQTT connection.
This should be the private key .pfx file for a device created in your broker corresponding to this Windows PC.
</TextBlock>
</StackPanel>
</ToolTip.Tip>
</TextBlock>
</StackPanel>
<TextBox Text="{Binding ClientCertPath}" MinWidth="150"/>
</StackPanel>
</Expander>
<Button Width="75" HorizontalAlignment="Right" Margin="0 40 0 10" Click="Configure">Save</Button> <Button Width="75" HorizontalAlignment="Right" Margin="0 40 0 10" Click="Configure">Save</Button>
</StackPanel> </StackPanel>
</UserControl> </UserControl>

@ -50,7 +50,7 @@ namespace UserInterface.Views
ICollection<ValidationResult> results; ICollection<ValidationResult> results;
if (model.IsValid(model, out results)) if (model.IsValid(model, out results))
{ {
var result = this.client.InvokeAsync(x => x.WriteMqttBrokerSettingsAsync(new MqttSettings() { Host = model.Host, Username = model.Username, Password = model.Password ?? "", Port = model.Port, UseTLS = model.UseTLS })); var result = this.client.InvokeAsync(x => x.WriteMqttBrokerSettingsAsync(new MqttSettings() { Host = model.Host, Username = model.Username, Password = model.Password ?? "", Port = model.Port, UseTLS = model.UseTLS, RootCAPath = model.RootCAPath, ClientCertPath = model.ClientCertPath, RetainLWT = model.RetainLWT }));
} }
} }

@ -4,22 +4,22 @@
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" d:DesignWidth="500" d:DesignHeight="450" mc:Ignorable="d" d:DesignWidth="500" d:DesignHeight="450"
x:Class="UserInterface.Views.GeneralSettingsView"> x:Class="UserInterface.Views.GeneralSettingsView">
<StackPanel Margin="30" HorizontalAlignment="Left"> <StackPanel Margin="30" HorizontalAlignment="Left" MinWidth="250">
<ContentControl FontSize="18" FontWeight="Bold">Settings</ContentControl> <ContentControl FontSize="18" FontWeight="Bold">Settings</ContentControl>
<StackPanel Margin="0 20 0 10" HorizontalAlignment="Left" Orientation="Horizontal"> <StackPanel Margin="0 20 0 10" HorizontalAlignment="Left" Orientation="Horizontal">
<ContentControl>Name prefix</ContentControl> <ContentControl>Name prefix</ContentControl>
<TextBlock Cursor="Help" Margin="5 0 0 0" VerticalAlignment="Bottom" TextDecorations="Underline">(What's this?) <TextBlock Cursor="Help" Margin="5 0 0 0" VerticalAlignment="Bottom" TextDecorations="Underline">(What's this?)
<ToolTip.Tip> <ToolTip.Tip>
<StackPanel> <StackPanel>
<TextBlock> <TextBlock>
[Experimental] [Experimental]
This allows you to set a name which will be used to prefix all sensor- and command names. For example: This allows you to set a name which will be used to prefix all sensor- and command names. For example:
If a sensor is called "ActiveWindow" and the name prefix is set to "laptop", the sensor will be named "laptop-ActiveWindow" and its entityId will be "laptop_activewindow". If a sensor is called "ActiveWindow" and the name prefix is set to "laptop", the sensor will be named "laptop-ActiveWindow" and its entityId will be "laptop_activewindow".
</TextBlock> </TextBlock>
</StackPanel> </StackPanel>
</ToolTip.Tip> </ToolTip.Tip>
</TextBlock> </TextBlock>
</StackPanel> </StackPanel>
<TextBox Text="{Binding NamePrefix}" HorizontalAlignment="Left" Width="100"/> <TextBox Text="{Binding NamePrefix}" HorizontalAlignment="Left" Width="100"/>
<Button Width="75" HorizontalAlignment="Right" Margin="0 40 0 10" Click="Configure">Save</Button> <Button Width="75" HorizontalAlignment="Right" Margin="0 40 0 10" Click="Configure">Save</Button>

@ -14,17 +14,18 @@
<Design.DataContext> <Design.DataContext>
<vm:MainWindowViewModel/> <vm:MainWindowViewModel/>
</Design.DataContext> </Design.DataContext>
<ScrollViewer VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Disabled">
<Grid ColumnDefinitions="Auto, *, Auto" Margin="10"> <Grid ColumnDefinitions="Auto, *, Auto" RowDefinitions="Auto, *" Margin="10">
<Grid.RowDefinitions> <ScrollViewer Grid.Column="0" Grid.Row="0" Margin="10" Grid.RowSpan="2" >
<RowDefinition MinHeight="500"></RowDefinition> <StackPanel>
<RowDefinition></RowDefinition> <views:BrokerSettings Background="#2D2D30"/>
</Grid.RowDefinitions> <views:GeneralSettingsView Margin="0 20 0 0" Background="#2D2D30"/>
<views:BrokerSettings Grid.Column="0" Grid.Row="0" Margin="10" Grid.RowSpan="2" MinHeight="500" Background="#2D2D30"/> </StackPanel>
<views:GeneralSettingsView Grid.Column="0" Grid.Row="1" Margin="10" Grid.RowSpan="2" Background="#2D2D30"/> </ScrollViewer>
<views:SensorSettings Grid.Column="1" Grid.Row="0" Margin="10" Background="#2D2D30"/> <views:SensorSettings Grid.Column="1" Grid.Row="0" Margin="10" Background="#2D2D30"/>
<views:CommandSettings Grid.Column="1" Grid.Row="1" Margin="10" Background="#2D2D30"/> <views:CommandSettings Grid.Column="1" Grid.Row="1" Margin="10" Background="#2D2D30"/>
<views:BackgroundServiceSettings Grid.Column="2" Grid.Row="0" Margin="10" Background="#2D2D30"/> <views:BackgroundServiceSettings Grid.Column="2" Grid.Row="0" Margin="10" Background="#2D2D30"/>
<views:AppInfo Grid.Column="2" Grid.Row="1" Margin="10" Background="#2D2D30"/> <views:AppInfo Grid.Column="2" Grid.Row="1" Margin="10" Background="#2D2D30"/>
</Grid> </Grid>
</ScrollViewer>
</Window> </Window>

@ -24,7 +24,7 @@ This sensor returns the current volume of playing audio. **It does not return th
This sensor returns the master volume for the currently selected default audio device. This sensor returns the master volume for the currently selected default audio device.
### DummySensor ### DummySensor
This sensor produces a random output every second, and is intended to test latency and connectivity. This sensor produces a random output every second, and is intended to test latency and connectivity.
@ -89,6 +89,10 @@ The webcam active sensor returns the status of the webcam.
The webcam process sensor returns the process which is using the webcam. The webcam process sensor returns the process which is using the webcam.
### MicrophoneProcessSensor
The microphone process sensor returns the process which is using the microphone.
### WMIQuerySensor ### WMIQuerySensor
Please see the specific documentaion page [here](https://github.com/sleevezipper/hass-workstation-service/blob/master/documentation/WMIQuery.md#wmiquerysensor). Please see the specific documentaion page [here](https://github.com/sleevezipper/hass-workstation-service/blob/master/documentation/WMIQuery.md#wmiquerysensor).

@ -13,7 +13,7 @@ The command ```sql SELECT * FROM Win32_Processor``` cannot be used because it re
You can use [WMI Explorer](https://github.com/vinaypamnani/wmie2/releases) to construct a query, or alternatively look at the user submitted sensors below You can use [WMI Explorer](https://github.com/vinaypamnani/wmie2/releases) to construct a query, or alternatively look at the user submitted sensors below
If a class or value cannot be found in the default scope, you can use the "Scope" setting when adding or editing the sensor.
--- ---
## User Submitted Sensor Examples ## User Submitted Sensor Examples

@ -30,7 +30,7 @@ namespace hass_workstation_service.Communication.InterProcesCommunication
/// <summary> /// <summary>
/// You can use this to check if the application responds. /// You can use this to check if the application responds.
/// </summary> /// </summary>
/// <param name="str"></param> /// <param name="str"></param>
/// <returns></returns> /// <returns></returns>
public string Ping(string str) => str == "ping" ? "pong" : "what?"; public string Ping(string str) => str == "ping" ? "pong" : "what?";
@ -44,7 +44,7 @@ namespace hass_workstation_service.Communication.InterProcesCommunication
public void WriteMqttBrokerSettingsAsync(MqttSettings settings) => _configurationService.WriteMqttBrokerSettingsAsync(settings); public void WriteMqttBrokerSettingsAsync(MqttSettings settings) => _configurationService.WriteMqttBrokerSettingsAsync(settings);
/// <summary> /// <summary>
/// Enables or disables autostart. /// Enables or disables autostart.
/// </summary> /// </summary>
/// <param name="enable"></param> /// <param name="enable"></param>
public void EnableAutostart(bool enable) => _configurationService.EnableAutoStart(enable); public void EnableAutostart(bool enable) => _configurationService.EnableAutoStart(enable);
@ -108,7 +108,7 @@ namespace hass_workstation_service.Communication.InterProcesCommunication
public void RemoveCommandById(Guid id) => _configurationService.DeleteConfiguredCommand(id); public void RemoveCommandById(Guid id) => _configurationService.DeleteConfiguredCommand(id);
/// <summary> /// <summary>
/// Adds a command to the configured commands. This properly initializes the class and writes it to the config file. /// Adds a command to the configured commands. This properly initializes the class and writes it to the config file.
/// </summary> /// </summary>
/// <param name="sensorType"></param> /// <param name="sensorType"></param>
/// <param name="json"></param> /// <param name="json"></param>
@ -123,7 +123,7 @@ namespace hass_workstation_service.Communication.InterProcesCommunication
} }
/// <summary> /// <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. /// 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> /// </summary>
/// <param name="commandType"></param> /// <param name="commandType"></param>
/// <param name="json"></param> /// <param name="json"></param>
@ -173,12 +173,13 @@ namespace hass_workstation_service.Communication.InterProcesCommunication
AvailableSensors.DummySensor => new DummySensor(_publisher, (int)model.UpdateInterval, model.Name), AvailableSensors.DummySensor => new DummySensor(_publisher, (int)model.UpdateInterval, model.Name),
AvailableSensors.CurrentClockSpeedSensor => new CurrentClockSpeedSensor(_publisher, (int)model.UpdateInterval, model.Name), AvailableSensors.CurrentClockSpeedSensor => new CurrentClockSpeedSensor(_publisher, (int)model.UpdateInterval, model.Name),
AvailableSensors.CPULoadSensor => new CPULoadSensor(_publisher, (int)model.UpdateInterval, model.Name), AvailableSensors.CPULoadSensor => new CPULoadSensor(_publisher, (int)model.UpdateInterval, model.Name),
AvailableSensors.WMIQuerySensor => new WMIQuerySensor(_publisher, model.Query, (int)model.UpdateInterval, model.Name), AvailableSensors.WMIQuerySensor => new WMIQuerySensor(_publisher, model.Query, (int)model.UpdateInterval, model.Name, scope: model.Scope),
AvailableSensors.MemoryUsageSensor => new MemoryUsageSensor(_publisher, (int)model.UpdateInterval, model.Name), AvailableSensors.MemoryUsageSensor => new MemoryUsageSensor(_publisher, (int)model.UpdateInterval, model.Name),
AvailableSensors.ActiveWindowSensor => new ActiveWindowSensor(_publisher, (int)model.UpdateInterval, model.Name), AvailableSensors.ActiveWindowSensor => new ActiveWindowSensor(_publisher, (int)model.UpdateInterval, model.Name),
AvailableSensors.WebcamActiveSensor => new WebcamActiveSensor(_publisher, (int)model.UpdateInterval, model.Name), AvailableSensors.WebcamActiveSensor => new WebcamActiveSensor(_publisher, (int)model.UpdateInterval, model.Name),
AvailableSensors.WebcamProcessSensor => new WebcamProcessSensor(_publisher, (int)model.UpdateInterval, model.Name), AvailableSensors.WebcamProcessSensor => new WebcamProcessSensor(_publisher, (int)model.UpdateInterval, model.Name),
AvailableSensors.MicrophoneActiveSensor => new MicrophoneActiveSensor(_publisher, (int)model.UpdateInterval, model.Name), AvailableSensors.MicrophoneActiveSensor => new MicrophoneActiveSensor(_publisher, (int)model.UpdateInterval, model.Name),
AvailableSensors.MicrophoneProcessSensor => new MicrophoneProcessSensor(_publisher, (int)model.UpdateInterval, model.Name),
AvailableSensors.NamedWindowSensor => new NamedWindowSensor(_publisher, model.WindowName, model.Name, (int)model.UpdateInterval), AvailableSensors.NamedWindowSensor => new NamedWindowSensor(_publisher, model.WindowName, model.Name, (int)model.UpdateInterval),
AvailableSensors.LastActiveSensor => new LastActiveSensor(_publisher, (int)model.UpdateInterval, model.Name), AvailableSensors.LastActiveSensor => new LastActiveSensor(_publisher, (int)model.UpdateInterval, model.Name),
AvailableSensors.LastBootSensor => new LastBootSensor(_publisher, (int)model.UpdateInterval, model.Name), AvailableSensors.LastBootSensor => new LastBootSensor(_publisher, (int)model.UpdateInterval, model.Name),

@ -11,6 +11,10 @@ namespace hass_workstation_service.Communication.InterProcesCommunication.Models
public string Password { get; set; } public string Password { get; set; }
public int? Port { get; set; } public int? Port { get; set; }
public bool UseTLS { get; set; } public bool UseTLS { get; set; }
public bool RetainLWT { get; set; }
public string RootCAPath { get; set; }
public string ClientCertPath { get; set; }
} }
public class MqqtClientStatus public class MqqtClientStatus
@ -26,6 +30,7 @@ namespace hass_workstation_service.Communication.InterProcesCommunication.Models
public string Name { get; set; } public string Name { get; set; }
public string Value { get; set; } public string Value { get; set; }
public string Query { get; set; } public string Query { get; set; }
public string Scope { get; set; }
public string WindowName { get; set; } public string WindowName { get; set; }
public int UpdateInterval { get; set; } public int UpdateInterval { get; set; }
public string UnitOfMeasurement { get; set; } public string UnitOfMeasurement { get; set; }
@ -40,12 +45,13 @@ namespace hass_workstation_service.Communication.InterProcesCommunication.Models
if (sensor is WMIQuerySensor wMIQuerySensor) if (sensor is WMIQuerySensor wMIQuerySensor)
{ {
this.Query = wMIQuerySensor.Query; this.Query = wMIQuerySensor.Query;
this.Scope = wMIQuerySensor.Scope;
} }
if (sensor is NamedWindowSensor namedWindowSensor) if (sensor is NamedWindowSensor namedWindowSensor)
{ {
this.WindowName = namedWindowSensor.WindowName; this.WindowName = namedWindowSensor.WindowName;
} }
this.UpdateInterval = sensor.UpdateInterval; this.UpdateInterval = sensor.UpdateInterval;
this.UnitOfMeasurement = ((SensorDiscoveryConfigModel)sensor.GetAutoDiscoveryConfig()).Unit_of_measurement; this.UnitOfMeasurement = ((SensorDiscoveryConfigModel)sensor.GetAutoDiscoveryConfig()).Unit_of_measurement;
} }
public ConfiguredSensorModel() public ConfiguredSensorModel()
@ -95,6 +101,7 @@ namespace hass_workstation_service.Communication.InterProcesCommunication.Models
WebcamActiveSensor, WebcamActiveSensor,
WebcamProcessSensor, WebcamProcessSensor,
MicrophoneActiveSensor, MicrophoneActiveSensor,
MicrophoneProcessSensor,
ActiveWindowSensor, ActiveWindowSensor,
NamedWindowSensor, NamedWindowSensor,
LastActiveSensor, LastActiveSensor,

@ -116,7 +116,7 @@ namespace hass_workstation_service.Communication
var message = new MqttApplicationMessageBuilder() var message = new MqttApplicationMessageBuilder()
.WithTopic($"homeassistant/{discoverable.Domain}/{this.DeviceConfigModel.Name}/{DiscoveryConfigModel.GetNameWithPrefix(discoverable.GetAutoDiscoveryConfig().NamePrefix, discoverable.ObjectId)}/config") .WithTopic($"homeassistant/{discoverable.Domain}/{this.DeviceConfigModel.Name}/{DiscoveryConfigModel.GetNameWithPrefix(discoverable.GetAutoDiscoveryConfig().NamePrefix, discoverable.ObjectId)}/config")
.WithPayload(clearConfig ? "" : JsonSerializer.Serialize(discoverable.GetAutoDiscoveryConfig(), discoverable.GetAutoDiscoveryConfig().GetType(), options)) .WithPayload(clearConfig ? "" : JsonSerializer.Serialize(discoverable.GetAutoDiscoveryConfig(), discoverable.GetAutoDiscoveryConfig().GetType(), options))
.WithRetainFlag() //.WithRetainFlag()
.Build(); .Build();
await this.Publish(message); await this.Publish(message);
// if clearconfig is true, also remove previous state messages // if clearconfig is true, also remove previous state messages
@ -125,7 +125,7 @@ namespace hass_workstation_service.Communication
var stateMessage = new MqttApplicationMessageBuilder() var stateMessage = new MqttApplicationMessageBuilder()
.WithTopic($"homeassistant/{discoverable.Domain}/{this.DeviceConfigModel.Name}/{DiscoveryConfigModel.GetNameWithPrefix(discoverable.GetAutoDiscoveryConfig().NamePrefix, discoverable.ObjectId)}/state") .WithTopic($"homeassistant/{discoverable.Domain}/{this.DeviceConfigModel.Name}/{DiscoveryConfigModel.GetNameWithPrefix(discoverable.GetAutoDiscoveryConfig().NamePrefix, discoverable.ObjectId)}/state")
.WithPayload("") .WithPayload("")
.WithRetainFlag() // .WithRetainFlag()
.Build(); .Build();
await this.Publish(stateMessage); await this.Publish(stateMessage);
} }

@ -5,6 +5,7 @@ using System.Linq;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Runtime.Versioning; using System.Runtime.Versioning;
using System.Security; using System.Security;
using System.Security.Cryptography.X509Certificates;
using System.Text.Json; using System.Text.Json;
using System.Threading.Tasks; using System.Threading.Tasks;
using hass_workstation_service.Communication; using hass_workstation_service.Communication;
@ -20,6 +21,7 @@ using MQTTnet.Client.Options;
using MQTTnet.Extensions.ManagedClient; using MQTTnet.Extensions.ManagedClient;
using Serilog; using Serilog;
namespace hass_workstation_service.Data namespace hass_workstation_service.Data
{ {
public class ConfigurationService : IConfigurationService public class ConfigurationService : IConfigurationService
@ -135,6 +137,9 @@ namespace hass_workstation_service.Data
case "MicrophoneActiveSensor": case "MicrophoneActiveSensor":
sensor = new MicrophoneActiveSensor(publisher, configuredSensor.UpdateInterval, configuredSensor.Name, configuredSensor.Id); sensor = new MicrophoneActiveSensor(publisher, configuredSensor.UpdateInterval, configuredSensor.Name, configuredSensor.Id);
break; break;
case "MicrophoneProcessSensor":
sensor = new MicrophoneProcessSensor(publisher, configuredSensor.UpdateInterval, configuredSensor.Name, configuredSensor.Id);
break;
case "SessionStateSensor": case "SessionStateSensor":
sensor = new SessionStateSensor(publisher, configuredSensor.UpdateInterval, configuredSensor.Name, configuredSensor.Id); sensor = new SessionStateSensor(publisher, configuredSensor.UpdateInterval, configuredSensor.Name, configuredSensor.Id);
break; break;
@ -152,7 +157,7 @@ namespace hass_workstation_service.Data
break; break;
// keep this one last! // keep this one last!
case "WMIQuerySensor": case "WMIQuerySensor":
sensor = new WMIQuerySensor(publisher, configuredSensor.Query, configuredSensor.UpdateInterval, configuredSensor.Name, configuredSensor.Id); sensor = new WMIQuerySensor(publisher, configuredSensor.Query, configuredSensor.UpdateInterval, configuredSensor.Name, configuredSensor.Id, configuredSensor.Scope);
break; break;
default: default:
Log.Logger.Error("unsupported sensor type in config"); Log.Logger.Error("unsupported sensor type in config");
@ -205,22 +210,22 @@ namespace hass_workstation_service.Data
case "CustomCommand": case "CustomCommand":
command = new CustomCommand(publisher, configuredCommand.Command, configuredCommand.Name, configuredCommand.Id); command = new CustomCommand(publisher, configuredCommand.Command, configuredCommand.Name, configuredCommand.Id);
break; break;
case "MediaPlayPauseCommand": case "PlayPauseCommand":
command = new PlayPauseCommand(publisher, configuredCommand.Name, configuredCommand.Id); command = new PlayPauseCommand(publisher, configuredCommand.Name, configuredCommand.Id);
break; break;
case "MediaNextCommand": case "NextCommand":
command = new NextCommand(publisher, configuredCommand.Name, configuredCommand.Id); command = new NextCommand(publisher, configuredCommand.Name, configuredCommand.Id);
break; break;
case "MediaPreviousCommand": case "PreviousCommand":
command = new PreviousCommand(publisher, configuredCommand.Name, configuredCommand.Id); command = new PreviousCommand(publisher, configuredCommand.Name, configuredCommand.Id);
break; break;
case "MediaVolumeUpCommand": case "VolumeUpCommand":
command = new VolumeUpCommand(publisher, configuredCommand.Name, configuredCommand.Id); command = new VolumeUpCommand(publisher, configuredCommand.Name, configuredCommand.Id);
break; break;
case "MediaVolumeDownCommand": case "VolumeDownCommand":
command = new VolumeDownCommand(publisher, configuredCommand.Name, configuredCommand.Id); command = new VolumeDownCommand(publisher, configuredCommand.Name, configuredCommand.Id);
break; break;
case "MediaMuteCommand": case "MuteCommand":
command = new MuteCommand(publisher, configuredCommand.Name, configuredCommand.Id); command = new MuteCommand(publisher, configuredCommand.Name, configuredCommand.Id);
break; break;
case "KeyCommand": case "KeyCommand":
@ -307,22 +312,54 @@ namespace hass_workstation_service.Data
if (configuredBroker != null && configuredBroker.Host != null) if (configuredBroker != null && configuredBroker.Host != null)
{ {
var mqttClientOptions = new MqttClientOptionsBuilder()
var mqttClientOptionsBuilder = new MqttClientOptionsBuilder()
.WithTcpServer(configuredBroker.Host, configuredBroker.Port) .WithTcpServer(configuredBroker.Host, configuredBroker.Port)
.WithTls(new MqttClientOptionsBuilderTlsParameters()
{
UseTls = configuredBroker.UseTLS,
AllowUntrustedCertificates = true,
SslProtocol = configuredBroker.UseTLS ? System.Security.Authentication.SslProtocols.Tls12 : System.Security.Authentication.SslProtocols.None
})
.WithCredentials(configuredBroker.Username, configuredBroker.Password.ToString()) .WithCredentials(configuredBroker.Username, configuredBroker.Password.ToString())
.WithKeepAlivePeriod(TimeSpan.FromSeconds(30)) .WithKeepAlivePeriod(TimeSpan.FromSeconds(30));
.WithWillMessage(new MqttApplicationMessageBuilder()
.WithRetainFlag()
/* Start LWT */
var lwtMessage = new MqttApplicationMessageBuilder()
.WithTopic($"homeassistant/sensor/{_deviceConfigModel.Name}/availability") .WithTopic($"homeassistant/sensor/{_deviceConfigModel.Name}/availability")
.WithPayload("offline") .WithPayload("offline");
.Build()) if (configuredBroker.RetainLWT) {
.Build(); lwtMessage.WithRetainFlag();
}
mqttClientOptionsBuilder.WithWillMessage(lwtMessage.Build());
/* End LWT */
/* Start TLS/Certificate configuration */
var tlsParameters = new MqttClientOptionsBuilderTlsParameters()
{
UseTls = configuredBroker.UseTLS,
AllowUntrustedCertificates = true,
SslProtocol = configuredBroker.UseTLS ? System.Security.Authentication.SslProtocols.Tls12 : System.Security.Authentication.SslProtocols.None
};
var certs = new List<X509Certificate>();
if (!string.IsNullOrEmpty(configuredBroker.RootCAPath)) {
certs.Add(new X509Certificate2(configuredBroker.RootCAPath));
}
if (!string.IsNullOrEmpty(configuredBroker.ClientCertPath))
{
certs.Add(new X509Certificate2(configuredBroker.ClientCertPath));
}
if (certs.Count > 0) {
// IF certs are configured, let's add them here
tlsParameters.Certificates = certs;
}
mqttClientOptionsBuilder.WithTls(tlsParameters);
/* End TLS/Certificate Configuration */
var mqttClientOptions = mqttClientOptionsBuilder.Build();
return new ManagedMqttClientOptionsBuilder().WithClientOptions(mqttClientOptions).Build(); return new ManagedMqttClientOptionsBuilder().WithClientOptions(mqttClientOptions).Build();
} }
else else
@ -375,7 +412,7 @@ namespace hass_workstation_service.Data
if (sensor is WMIQuerySensor wmiSensor) if (sensor is WMIQuerySensor wmiSensor)
{ {
#pragma warning disable CA1416 // Validate platform compatibility. We ignore it here because this would never happen. A cleaner solution may be implemented later. #pragma warning disable CA1416 // Validate platform compatibility. We ignore it here because this would never happen. A cleaner solution may be implemented later.
configuredSensorsToSave.Add(new ConfiguredSensor() { Id = wmiSensor.Id, Name = wmiSensor.Name, Type = wmiSensor.GetType().Name, UpdateInterval = wmiSensor.UpdateInterval, Query = wmiSensor.Query }); configuredSensorsToSave.Add(new ConfiguredSensor() { Id = wmiSensor.Id, Name = wmiSensor.Name, Type = wmiSensor.GetType().Name, UpdateInterval = wmiSensor.UpdateInterval, Query = wmiSensor.Query, Scope = wmiSensor.Scope });
#pragma warning restore CA1416 // Validate platform compatibility #pragma warning restore CA1416 // Validate platform compatibility
} }
else if (sensor is NamedWindowSensor namedWindowSensor) else if (sensor is NamedWindowSensor namedWindowSensor)
@ -462,7 +499,7 @@ namespace hass_workstation_service.Data
} }
/// <summary> /// <summary>
/// ///
/// </summary> /// </summary>
/// <param name="id">The Id of the sensor to replace</param> /// <param name="id">The Id of the sensor to replace</param>
/// <param name="sensor">The new sensor</param> /// <param name="sensor">The new sensor</param>
@ -541,7 +578,10 @@ namespace hass_workstation_service.Data
Username = settings.Username, Username = settings.Username,
Password = settings.Password ?? "", Password = settings.Password ?? "",
Port = settings.Port ?? 1883, Port = settings.Port ?? 1883,
UseTLS = settings.UseTLS UseTLS = settings.UseTLS,
RetainLWT = settings.RetainLWT,
RootCAPath = settings.RootCAPath,
ClientCertPath = settings.ClientCertPath
}; };
await JsonSerializer.SerializeAsync(stream, configuredBroker); await JsonSerializer.SerializeAsync(stream, configuredBroker);
@ -560,7 +600,10 @@ namespace hass_workstation_service.Data
Username = broker?.Username, Username = broker?.Username,
Password = broker?.Password, Password = broker?.Password,
Port = broker?.Port, Port = broker?.Port,
UseTLS = broker?.UseTLS ?? false UseTLS = broker?.UseTLS ?? false,
RetainLWT = broker?.RetainLWT ?? true,
RootCAPath = broker?.RootCAPath,
ClientCertPath = broker?.RootCAPath
}; };
} }

@ -8,11 +8,33 @@ namespace hass_workstation_service.Data
private string username; private string username;
private string password; private string password;
private int? port; private int? port;
private string rootCAPath;
private string clientCertPath;
public string Host { get; set; } public string Host { get; set; }
public int Port { get => port ?? 1883; set => port = value; } public int Port { get => port ?? 1883; set => port = value; }
public bool UseTLS { get; set; } public bool UseTLS { get; set; }
// Before this option, Retains was the default, so let's keep that here to not break backwards compatibility
public bool RetainLWT { get; set; } = true;
public string RootCAPath {
get
{
if (rootCAPath!= null) return rootCAPath;
return "";
}
set => rootCAPath = value;
}
public string ClientCertPath {
get
{
if (clientCertPath != null) return clientCertPath;
return "";
}
set => clientCertPath = value;
}
public string Username public string Username
{ {

@ -9,6 +9,7 @@ namespace hass_workstation_service.Data
public Guid Id { get; set; } public Guid Id { get; set; }
public string Name { get; set; } public string Name { get; set; }
public string Query { get; set; } public string Query { get; set; }
public string Scope { get; set; }
public int? UpdateInterval { get; set; } public int? UpdateInterval { get; set; }
public string WindowName { get; set; } public string WindowName { get; set; }
} }

@ -40,8 +40,8 @@ namespace hass_workstation_service.Domain.Commands
var message = new MqttApplicationMessageBuilder() var message = new MqttApplicationMessageBuilder()
.WithTopic(GetAutoDiscoveryConfig().State_topic) .WithTopic(GetAutoDiscoveryConfig().State_topic)
.WithPayload(state) .WithPayload(state)
.WithExactlyOnceQoS() //.WithExactlyOnceQoS()
.WithRetainFlag() //.WithRetainFlag()
.Build(); .Build();
await Publisher.Publish(message); await Publisher.Publish(message);
PreviousPublishedState = state; PreviousPublishedState = state;

@ -40,8 +40,8 @@ namespace hass_workstation_service.Domain.Sensors
var message = new MqttApplicationMessageBuilder() var message = new MqttApplicationMessageBuilder()
.WithTopic(GetAutoDiscoveryConfig().State_topic) .WithTopic(GetAutoDiscoveryConfig().State_topic)
.WithPayload(state) .WithPayload(state)
.WithExactlyOnceQoS() //.WithExactlyOnceQoS()
.WithRetainFlag() //.WithRetainFlag()
.Build(); .Build();
await Publisher.Publish(message); await Publisher.Publish(message);
PreviousPublishedState = state; PreviousPublishedState = state;

@ -6,7 +6,7 @@ namespace hass_workstation_service.Domain.Sensors
{ {
public class LastActiveSensor : AbstractSensor public class LastActiveSensor : AbstractSensor
{ {
private DateTime _lastActive = DateTime.MinValue;
public LastActiveSensor(MqttPublisher publisher, int? updateInterval = 10, string name = "LastActive", Guid id = default) : base(publisher, name ?? "LastActive", updateInterval ?? 10, id){} public LastActiveSensor(MqttPublisher publisher, int? updateInterval = 10, string name = "LastActive", Guid id = default) : base(publisher, name ?? "LastActive", updateInterval ?? 10, id){}
public override SensorDiscoveryConfigModel GetAutoDiscoveryConfig() public override SensorDiscoveryConfigModel GetAutoDiscoveryConfig()
@ -25,7 +25,12 @@ namespace hass_workstation_service.Domain.Sensors
public override string GetState() public override string GetState()
{ {
return GetLastInputTime().ToString("o", System.Globalization.CultureInfo.InvariantCulture); var lastInput = GetLastInputTime();
if ((_lastActive - lastInput).Duration().TotalSeconds > 1)
{
_lastActive = lastInput;
}
return _lastActive.ToString("o", System.Globalization.CultureInfo.InvariantCulture);
} }

@ -0,0 +1,96 @@
using hass_workstation_service.Communication;
using Microsoft.Win32;
using System;
using System.Linq;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using System.Collections.Generic;
namespace hass_workstation_service.Domain.Sensors
{
public class MicrophoneProcessSensor : AbstractSensor
{
private HashSet<string> processes = new HashSet<string>();
public MicrophoneProcessSensor(MqttPublisher publisher, int? updateInterval = null, string name = "MicrophoneProcess", Guid id = default) : base(publisher, name ?? "MicrophoneProcess", updateInterval ?? 10, id)
{
}
public override string GetState()
{
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
return IsMicrophoneInUseRegistry();
}
else
{
return "unsupported";
}
}
public override SensorDiscoveryConfigModel GetAutoDiscoveryConfig()
{
return this._autoDiscoveryConfigModel ?? SetAutoDiscoveryConfigModel(new SensorDiscoveryConfigModel()
{
Name = this.Name,
NamePrefix = Publisher.NamePrefix,
Unique_id = this.Id.ToString(),
Device = this.Publisher.DeviceConfigModel,
State_topic = $"homeassistant/{this.Domain}/{Publisher.DeviceConfigModel.Name}/{DiscoveryConfigModel.GetNameWithPrefix(Publisher.NamePrefix, this.ObjectId)}/state",
Availability_topic = $"homeassistant/sensor/{Publisher.DeviceConfigModel.Name}/availability"
});
}
[SupportedOSPlatform("windows")]
private void CheckLastUsed(RegistryKey key)
{
foreach (var subKeyName in key.GetSubKeyNames())
{
// NonPackaged has multiple subkeys
if (subKeyName == "NonPackaged")
{
using (var nonpackagedkey = key.OpenSubKey(subKeyName))
{
CheckLastUsed(nonpackagedkey);
}
}
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)
{
this.processes.Add(subKeyName);
}
}
}
}
}
}
[SupportedOSPlatform("windows")]
private string IsMicrophoneInUseRegistry()
{
// Clear old values
this.processes.Clear();
using (var key = Registry.LocalMachine.OpenSubKey(@"SOFTWARE\Microsoft\Windows\CurrentVersion\CapabilityAccessManager\ConsentStore\microphone"))
{
CheckLastUsed(key);
}
using (var key = Registry.CurrentUser.OpenSubKey(@"SOFTWARE\Microsoft\Windows\CurrentVersion\CapabilityAccessManager\ConsentStore\microphone"))
{
CheckLastUsed(key);
}
if (this.processes.Count() > 0)
{
return String.Join(",", this.processes.ToArray());
}
return "off";
}
}
}

@ -12,13 +12,26 @@ namespace hass_workstation_service.Domain.Sensors
public class WMIQuerySensor : AbstractSensor public class WMIQuerySensor : AbstractSensor
{ {
public string Query { get; private set; } public string Query { get; private set; }
public string Scope { get; private set; }
protected readonly ObjectQuery _objectQuery; protected readonly ObjectQuery _objectQuery;
protected readonly ManagementObjectSearcher _searcher; protected readonly ManagementObjectSearcher _searcher;
public WMIQuerySensor(MqttPublisher publisher, string query, int? updateInterval = null, string name = "WMIQuerySensor", Guid id = default) : base(publisher, name ?? "WMIQuerySensor", updateInterval ?? 10, id) public WMIQuerySensor(MqttPublisher publisher, string query, int? updateInterval = null, string name = "WMIQuerySensor", Guid id = default, string scope = "") : base(publisher, name ?? "WMIQuerySensor", updateInterval ?? 10, id)
{ {
this.Query = query; this.Query = query;
this.Scope = scope;
_objectQuery = new ObjectQuery(this.Query); _objectQuery = new ObjectQuery(this.Query);
_searcher = new ManagementObjectSearcher(query); ManagementScope managementscope;
// if we have a custom scope, use that
if (!string.IsNullOrWhiteSpace(scope))
{
managementscope = new ManagementScope(scope);
}
// otherwise, use the default
else
{
managementscope = new ManagementScope(@"\\localhost\");
}
_searcher = new ManagementObjectSearcher(managementscope, _objectQuery);
} }
public override SensorDiscoveryConfigModel GetAutoDiscoveryConfig() public override SensorDiscoveryConfigModel GetAutoDiscoveryConfig()
{ {

@ -4,15 +4,18 @@ using System;
using System.Linq; using System.Linq;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Runtime.Versioning; using System.Runtime.Versioning;
using System.Collections.Generic;
namespace hass_workstation_service.Domain.Sensors namespace hass_workstation_service.Domain.Sensors
{ {
public class WebcamProcessSensor : AbstractSensor public class WebcamProcessSensor : AbstractSensor
{ {
private HashSet<string> processes = new HashSet<string>();
public WebcamProcessSensor(MqttPublisher publisher, int? updateInterval = null, string name = "WebcamProcess", Guid id = default) : base(publisher, name ?? "WebcamProcess", updateInterval ?? 10, id) public WebcamProcessSensor(MqttPublisher publisher, int? updateInterval = null, string name = "WebcamProcess", Guid id = default) : base(publisher, name ?? "WebcamProcess", updateInterval ?? 10, id)
{ {
} }
public override string GetState() public override string GetState()
{ {
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
@ -38,92 +41,55 @@ namespace hass_workstation_service.Domain.Sensors
} }
[SupportedOSPlatform("windows")] [SupportedOSPlatform("windows")]
private string IsWebCamInUseRegistry() private void CheckLastUsed(RegistryKey key)
{ {
using (var key = Registry.LocalMachine.OpenSubKey(@"SOFTWARE\Microsoft\Windows\CurrentVersion\CapabilityAccessManager\ConsentStore\webcam")) foreach (var subKeyName in key.GetSubKeyNames())
{ {
foreach (var subKeyName in key.GetSubKeyNames()) // NonPackaged has multiple subkeys
if (subKeyName == "NonPackaged")
{ {
// NonPackaged has multiple subkeys using (var nonpackagedkey = key.OpenSubKey(subKeyName))
if (subKeyName == "NonPackaged")
{ {
using (var nonpackagedkey = key.OpenSubKey(subKeyName)) CheckLastUsed(nonpackagedkey);
{
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 nonpackagedSubKeyName;
}
}
}
}
}
} }
else }
else
{
using (var subKey = key.OpenSubKey(subKeyName))
{ {
using (var subKey = key.OpenSubKey(subKeyName)) if (subKey.GetValueNames().Contains("LastUsedTimeStop"))
{ {
if (subKey.GetValueNames().Contains("LastUsedTimeStop")) var endTime = subKey.GetValue("LastUsedTimeStop") is long ? (long)subKey.GetValue("LastUsedTimeStop") : -1;
if (endTime <= 0)
{ {
var endTime = subKey.GetValue("LastUsedTimeStop") is long ? (long)subKey.GetValue("LastUsedTimeStop") : -1; this.processes.Add(subKeyName);
if (endTime <= 0)
{
return subKeyName;
}
} }
} }
} }
} }
} }
}
[SupportedOSPlatform("windows")]
private string IsWebCamInUseRegistry()
{
// Clear old values
this.processes.Clear();
using (var key = Registry.LocalMachine.OpenSubKey(@"SOFTWARE\Microsoft\Windows\CurrentVersion\CapabilityAccessManager\ConsentStore\webcam"))
{
CheckLastUsed(key);
}
using (var key = Registry.CurrentUser.OpenSubKey(@"SOFTWARE\Microsoft\Windows\CurrentVersion\CapabilityAccessManager\ConsentStore\webcam")) using (var key = Registry.CurrentUser.OpenSubKey(@"SOFTWARE\Microsoft\Windows\CurrentVersion\CapabilityAccessManager\ConsentStore\webcam"))
{ {
foreach (var subKeyName in key.GetSubKeyNames()) CheckLastUsed(key);
{
// 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 nonpackagedSubKeyName;
}
}
}
}
}
}
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 subKeyName;
}
}
}
}
}
} }
if (this.processes.Count() > 0)
{
return String.Join(",", this.processes.ToArray());
}
return "off"; return "off";
} }
} }

@ -4,11 +4,12 @@ https://go.microsoft.com/fwlink/?LinkID=208121.
--> -->
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> <Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup> <PropertyGroup>
<ApplicationRevision>57</ApplicationRevision> <ApplicationRevision>58</ApplicationRevision>
<ApplicationVersion>1.0.0.*</ApplicationVersion> <ApplicationVersion>1.0.0.*</ApplicationVersion>
<BootstrapperEnabled>True</BootstrapperEnabled> <BootstrapperEnabled>True</BootstrapperEnabled>
<Configuration>Release</Configuration> <Configuration>Release</Configuration>
<CreateDesktopShortcut>True</CreateDesktopShortcut> <CreateDesktopShortcut>True</CreateDesktopShortcut>
<CreateWebPageOnPublish>True</CreateWebPageOnPublish>
<ErrorReportUrl>https://github.com/sleevezipper/hass-workstation-service</ErrorReportUrl> <ErrorReportUrl>https://github.com/sleevezipper/hass-workstation-service</ErrorReportUrl>
<GenerateManifests>True</GenerateManifests> <GenerateManifests>True</GenerateManifests>
<Install>true</Install> <Install>true</Install>
@ -16,8 +17,8 @@ https://go.microsoft.com/fwlink/?LinkID=208121.
<InstallUrl>https://hassworkstationstorage.z6.web.core.windows.net/publish/</InstallUrl> <InstallUrl>https://hassworkstationstorage.z6.web.core.windows.net/publish/</InstallUrl>
<IsRevisionIncremented>True</IsRevisionIncremented> <IsRevisionIncremented>True</IsRevisionIncremented>
<IsWebBootstrapper>True</IsWebBootstrapper> <IsWebBootstrapper>True</IsWebBootstrapper>
<ManifestCertificateThumbprint>820B7EDF3E26E24BB4C25B177A05B3D0C77BF73A</ManifestCertificateThumbprint> <ManifestCertificateThumbprint>66F1CEE175B90B89AFD3AA8F5F649268BF4DFA9E</ManifestCertificateThumbprint>
<ManifestKeyFile>hass-workstation-service_TemporaryKey.pfx</ManifestKeyFile> <ManifestKeyFile>hass-workstation-service_1_TemporaryKey.pfx</ManifestKeyFile>
<MapFileExtensions>true</MapFileExtensions> <MapFileExtensions>true</MapFileExtensions>
<OpenBrowserOnPublish>false</OpenBrowserOnPublish> <OpenBrowserOnPublish>false</OpenBrowserOnPublish>
<Platform>Any CPU</Platform> <Platform>Any CPU</Platform>
@ -37,12 +38,13 @@ https://go.microsoft.com/fwlink/?LinkID=208121.
<TrustUrlParameters>True</TrustUrlParameters> <TrustUrlParameters>True</TrustUrlParameters>
<UpdateEnabled>True</UpdateEnabled> <UpdateEnabled>True</UpdateEnabled>
<UpdateMode>Foreground</UpdateMode> <UpdateMode>Foreground</UpdateMode>
<History>False|2021-11-14T15:44:38.1032015Z;</History> <UpdateRequired>False</UpdateRequired>
<WebPageFileName>Publish.html</WebPageFileName>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<BootstrapperPackage Include="Microsoft.NetCore.CoreRuntime.5.0.x64"> <BootstrapperPackage Include="Microsoft.NetCore.CoreRuntime.5.0.x64">
<Install>true</Install> <Install>True</Install>
<ProductName>.NET Runtime 5.0.1 (x64)</ProductName> <ProductName>.NET Runtime 5.0.12 (x64)</ProductName>
</BootstrapperPackage> </BootstrapperPackage>
</ItemGroup> </ItemGroup>
</Project> </Project>

@ -43,7 +43,7 @@
<CopyToOutputDirectory>Always</CopyToOutputDirectory> <CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content> </Content>
<Content Include="UserInterface.pdb"> <Content Include="UserInterface.pdb">
<CopyToOutputDirectory>Always</CopyToOutputDirectory> <CopyToOutputDirectory>Never</CopyToOutputDirectory>
</Content> </Content>
</ItemGroup> </ItemGroup>
@ -55,15 +55,15 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="JKang.IpcServiceFramework.Hosting.NamedPipe" Version="3.1.0" /> <PackageReference Include="JKang.IpcServiceFramework.Hosting.NamedPipe" Version="3.1.0" />
<PackageReference Include="LibreHardwareMonitorLib" Version="0.8.8" /> <PackageReference Include="LibreHardwareMonitorLib" Version="0.8.9" />
<PackageReference Include="Microsoft.Extensions.Hosting" Version="5.0.0" /> <PackageReference Include="Microsoft.Extensions.Hosting" Version="6.0.0" />
<PackageReference Include="Microsoft.Win32.Registry" Version="5.0.0" /> <PackageReference Include="Microsoft.Win32.Registry" Version="5.0.0" />
<PackageReference Include="MQTTnet" Version="3.0.16" /> <PackageReference Include="MQTTnet" Version="3.1.1" />
<PackageReference Include="MQTTnet.Extensions.ManagedClient" Version="3.0.16" /> <PackageReference Include="MQTTnet.Extensions.ManagedClient" Version="3.1.1" />
<PackageReference Include="Serilog.Extensions.Logging.File" Version="2.0.0" /> <PackageReference Include="Serilog.Extensions.Logging.File" Version="2.0.0" />
<PackageReference Include="Serilog.Sinks.Console" Version="4.0.0" /> <PackageReference Include="Serilog.Sinks.Console" Version="4.0.1" />
<PackageReference Include="Serilog.Sinks.File" Version="5.0.0" /> <PackageReference Include="Serilog.Sinks.File" Version="5.0.0" />
<PackageReference Include="System.Management" Version="5.0.0" /> <PackageReference Include="System.Management" Version="6.0.0" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

Loading…
Cancel
Save