Merge branch 'develop'

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

@ -17,7 +17,7 @@
<PackageReference Include="Avalonia.ReactiveUI" Version="0.10.8" />
<PackageReference Include="Avalonia.Win32" Version="0.10.8" />
<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>
<ProjectReference Include="..\hass-workstation-service\hass-workstation-service.csproj" />

@ -13,6 +13,7 @@ namespace UserInterface.ViewModels
private bool _showWindowNameInput;
private string _moreInfoLink;
private string _query;
private string _scope;
private string _windowName;
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 string MoreInfoLink { get => _moreInfoLink; set => this.RaiseAndSetIfChanged(ref _moreInfoLink, 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); }
}
}

@ -16,6 +16,9 @@ namespace UserInterface.ViewModels
private bool isConnected;
private int? port;
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 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 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)
{
this.Host = settings.Host;
@ -36,6 +46,9 @@ namespace UserInterface.ViewModels
this.Password = settings.Password;
this.Port = settings.Port;
this.UseTLS = settings.UseTLS;
this.RetainLWT = settings.RetainLWT;
this.RootCAPath = settings.RootCAPath;
this.ClientCertPath = settings.ClientCertPath;
}
public void UpdateStatus(MqqtClientStatus status)

@ -21,8 +21,10 @@
<TextBox Text="{Binding UpdateInterval}" HorizontalAlignment="Right" MaxWidth="30"/>
</StackPanel>
<TextBlock Text="{Binding UpdateInterval, StringFormat= Update every {0} seconds}" HorizontalAlignment="Left" MinWidth="150"/>
<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 ShowQueryInput}" Margin="0 20 0 10">Scope (optional)</ContentControl>
<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>
<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"/>

@ -67,6 +67,7 @@ namespace UserInterface.Views
item.Name = sensor.Name;
item.UpdateInterval = sensor.UpdateInterval;
item.Query = sensor.Query;
item.Scope = sensor.Scope;
item.WindowName = sensor.WindowName;
Title = $"Edit {sensor.Name}";
@ -75,7 +76,7 @@ namespace UserInterface.Views
public async void Save(object sender, RoutedEventArgs args)
{
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);
if (SensorId == Guid.Empty)
await _client.InvokeAsync(x => x.AddSensor(item.SelectedType, json));
@ -172,6 +173,13 @@ namespace UserInterface.Views
item.UpdateInterval = 10;
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:
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";

@ -4,7 +4,7 @@
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" d:DesignWidth="500" d:DesignHeight="450"
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>
<TextBlock IsVisible="{Binding IsConnected}" Foreground="Green" Text="{Binding Message}"></TextBlock >
<TextBlock IsVisible="{Binding !IsConnected}" Foreground="Red" Text="{Binding Message}"></TextBlock >
@ -25,6 +25,65 @@
<TextBox Text="{Binding Username}" MinWidth="150"/>
<ContentControl Margin="0 20 0 10">Password</ContentControl>
<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>
</StackPanel>
</UserControl>

@ -50,7 +50,7 @@ namespace UserInterface.Views
ICollection<ValidationResult> 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"
mc:Ignorable="d" d:DesignWidth="500" d:DesignHeight="450"
x:Class="UserInterface.Views.GeneralSettingsView">
<StackPanel Margin="30" HorizontalAlignment="Left">
<StackPanel Margin="30" HorizontalAlignment="Left" MinWidth="250">
<ContentControl FontSize="18" FontWeight="Bold">Settings</ContentControl>
<StackPanel Margin="0 20 0 10" HorizontalAlignment="Left" Orientation="Horizontal">
<ContentControl>Name prefix</ContentControl>
<TextBlock Cursor="Help" Margin="5 0 0 0" VerticalAlignment="Bottom" TextDecorations="Underline">(What's this?)
<ToolTip.Tip>
<StackPanel>
<TextBlock>
<TextBlock Cursor="Help" Margin="5 0 0 0" VerticalAlignment="Bottom" TextDecorations="Underline">(What's this?)
<ToolTip.Tip>
<StackPanel>
<TextBlock>
[Experimental]
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".
</TextBlock>
</TextBlock>
</StackPanel>
</ToolTip.Tip>
</TextBlock>
</StackPanel>
</ToolTip.Tip>
</TextBlock>
</StackPanel>
<TextBox Text="{Binding NamePrefix}" HorizontalAlignment="Left" Width="100"/>
<Button Width="75" HorizontalAlignment="Right" Margin="0 40 0 10" Click="Configure">Save</Button>

@ -14,17 +14,18 @@
<Design.DataContext>
<vm:MainWindowViewModel/>
</Design.DataContext>
<Grid ColumnDefinitions="Auto, *, Auto" Margin="10">
<Grid.RowDefinitions>
<RowDefinition MinHeight="500"></RowDefinition>
<RowDefinition></RowDefinition>
</Grid.RowDefinitions>
<views:BrokerSettings Grid.Column="0" Grid.Row="0" Margin="10" Grid.RowSpan="2" MinHeight="500" Background="#2D2D30"/>
<views:GeneralSettingsView Grid.Column="0" Grid.Row="1" Margin="10" Grid.RowSpan="2" 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:BackgroundServiceSettings Grid.Column="2" Grid.Row="0" Margin="10" Background="#2D2D30"/>
<views:AppInfo Grid.Column="2" Grid.Row="1" Margin="10" Background="#2D2D30"/>
<ScrollViewer VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Disabled">
<Grid ColumnDefinitions="Auto, *, Auto" RowDefinitions="Auto, *" Margin="10">
<ScrollViewer Grid.Column="0" Grid.Row="0" Margin="10" Grid.RowSpan="2" >
<StackPanel>
<views:BrokerSettings Background="#2D2D30"/>
<views:GeneralSettingsView Margin="0 20 0 0" Background="#2D2D30"/>
</StackPanel>
</ScrollViewer>
<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:BackgroundServiceSettings Grid.Column="2" Grid.Row="0" Margin="10" Background="#2D2D30"/>
<views:AppInfo Grid.Column="2" Grid.Row="1" Margin="10" Background="#2D2D30"/>
</Grid>
</ScrollViewer>
</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.
### DummySensor
### DummySensor
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.
### MicrophoneProcessSensor
The microphone process sensor returns the process which is using the microphone.
### 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
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

@ -173,12 +173,13 @@ namespace hass_workstation_service.Communication.InterProcesCommunication
AvailableSensors.DummySensor => new DummySensor(_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.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.ActiveWindowSensor => new ActiveWindowSensor(_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.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.LastActiveSensor => new LastActiveSensor(_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 int? Port { 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
@ -26,6 +30,7 @@ namespace hass_workstation_service.Communication.InterProcesCommunication.Models
public string Name { get; set; }
public string Value { get; set; }
public string Query { get; set; }
public string Scope { get; set; }
public string WindowName { get; set; }
public int UpdateInterval { get; set; }
public string UnitOfMeasurement { get; set; }
@ -40,6 +45,7 @@ namespace hass_workstation_service.Communication.InterProcesCommunication.Models
if (sensor is WMIQuerySensor wMIQuerySensor)
{
this.Query = wMIQuerySensor.Query;
this.Scope = wMIQuerySensor.Scope;
}
if (sensor is NamedWindowSensor namedWindowSensor)
{
@ -95,6 +101,7 @@ namespace hass_workstation_service.Communication.InterProcesCommunication.Models
WebcamActiveSensor,
WebcamProcessSensor,
MicrophoneActiveSensor,
MicrophoneProcessSensor,
ActiveWindowSensor,
NamedWindowSensor,
LastActiveSensor,

@ -116,7 +116,7 @@ namespace hass_workstation_service.Communication
var message = new MqttApplicationMessageBuilder()
.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))
.WithRetainFlag()
//.WithRetainFlag()
.Build();
await this.Publish(message);
// if clearconfig is true, also remove previous state messages
@ -125,7 +125,7 @@ namespace hass_workstation_service.Communication
var stateMessage = new MqttApplicationMessageBuilder()
.WithTopic($"homeassistant/{discoverable.Domain}/{this.DeviceConfigModel.Name}/{DiscoveryConfigModel.GetNameWithPrefix(discoverable.GetAutoDiscoveryConfig().NamePrefix, discoverable.ObjectId)}/state")
.WithPayload("")
.WithRetainFlag()
// .WithRetainFlag()
.Build();
await this.Publish(stateMessage);
}

@ -5,6 +5,7 @@ using System.Linq;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using System.Security;
using System.Security.Cryptography.X509Certificates;
using System.Text.Json;
using System.Threading.Tasks;
using hass_workstation_service.Communication;
@ -20,6 +21,7 @@ using MQTTnet.Client.Options;
using MQTTnet.Extensions.ManagedClient;
using Serilog;
namespace hass_workstation_service.Data
{
public class ConfigurationService : IConfigurationService
@ -135,6 +137,9 @@ namespace hass_workstation_service.Data
case "MicrophoneActiveSensor":
sensor = new MicrophoneActiveSensor(publisher, configuredSensor.UpdateInterval, configuredSensor.Name, configuredSensor.Id);
break;
case "MicrophoneProcessSensor":
sensor = new MicrophoneProcessSensor(publisher, configuredSensor.UpdateInterval, configuredSensor.Name, configuredSensor.Id);
break;
case "SessionStateSensor":
sensor = new SessionStateSensor(publisher, configuredSensor.UpdateInterval, configuredSensor.Name, configuredSensor.Id);
break;
@ -152,7 +157,7 @@ namespace hass_workstation_service.Data
break;
// keep this one last!
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;
default:
Log.Logger.Error("unsupported sensor type in config");
@ -205,22 +210,22 @@ namespace hass_workstation_service.Data
case "CustomCommand":
command = new CustomCommand(publisher, configuredCommand.Command, configuredCommand.Name, configuredCommand.Id);
break;
case "MediaPlayPauseCommand":
case "PlayPauseCommand":
command = new PlayPauseCommand(publisher, configuredCommand.Name, configuredCommand.Id);
break;
case "MediaNextCommand":
case "NextCommand":
command = new NextCommand(publisher, configuredCommand.Name, configuredCommand.Id);
break;
case "MediaPreviousCommand":
case "PreviousCommand":
command = new PreviousCommand(publisher, configuredCommand.Name, configuredCommand.Id);
break;
case "MediaVolumeUpCommand":
case "VolumeUpCommand":
command = new VolumeUpCommand(publisher, configuredCommand.Name, configuredCommand.Id);
break;
case "MediaVolumeDownCommand":
case "VolumeDownCommand":
command = new VolumeDownCommand(publisher, configuredCommand.Name, configuredCommand.Id);
break;
case "MediaMuteCommand":
case "MuteCommand":
command = new MuteCommand(publisher, configuredCommand.Name, configuredCommand.Id);
break;
case "KeyCommand":
@ -307,22 +312,54 @@ namespace hass_workstation_service.Data
if (configuredBroker != null && configuredBroker.Host != null)
{
var mqttClientOptions = new MqttClientOptionsBuilder()
var mqttClientOptionsBuilder = new MqttClientOptionsBuilder()
.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())
.WithKeepAlivePeriod(TimeSpan.FromSeconds(30))
.WithWillMessage(new MqttApplicationMessageBuilder()
.WithRetainFlag()
.WithKeepAlivePeriod(TimeSpan.FromSeconds(30));
/* Start LWT */
var lwtMessage = new MqttApplicationMessageBuilder()
.WithTopic($"homeassistant/sensor/{_deviceConfigModel.Name}/availability")
.WithPayload("offline")
.Build())
.Build();
.WithPayload("offline");
if (configuredBroker.RetainLWT) {
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();
}
else
@ -375,7 +412,7 @@ namespace hass_workstation_service.Data
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.
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
}
else if (sensor is NamedWindowSensor namedWindowSensor)
@ -541,7 +578,10 @@ namespace hass_workstation_service.Data
Username = settings.Username,
Password = settings.Password ?? "",
Port = settings.Port ?? 1883,
UseTLS = settings.UseTLS
UseTLS = settings.UseTLS,
RetainLWT = settings.RetainLWT,
RootCAPath = settings.RootCAPath,
ClientCertPath = settings.ClientCertPath
};
await JsonSerializer.SerializeAsync(stream, configuredBroker);
@ -560,7 +600,10 @@ namespace hass_workstation_service.Data
Username = broker?.Username,
Password = broker?.Password,
Port = broker?.Port,
UseTLS = broker?.UseTLS ?? false
UseTLS = broker?.UseTLS ?? false,
RetainLWT = broker?.RetainLWT ?? true,
RootCAPath = broker?.RootCAPath,
ClientCertPath = broker?.RootCAPath
};
}

@ -9,10 +9,32 @@ namespace hass_workstation_service.Data
private string password;
private int? port;
private string rootCAPath;
private string clientCertPath;
public string Host { get; set; }
public int Port { get => port ?? 1883; set => port = value; }
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
{

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

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

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

@ -6,7 +6,7 @@ namespace hass_workstation_service.Domain.Sensors
{
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 override SensorDiscoveryConfigModel GetAutoDiscoveryConfig()
@ -25,7 +25,12 @@ namespace hass_workstation_service.Domain.Sensors
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 string Query { get; private set; }
public string Scope { get; private set; }
protected readonly ObjectQuery _objectQuery;
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.Scope = scope;
_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()
{

@ -4,11 +4,14 @@ 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 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)
{
}
@ -38,92 +41,55 @@ namespace hass_workstation_service.Domain.Sensors
}
[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
if (subKeyName == "NonPackaged")
using (var nonpackagedkey = key.OpenSubKey(subKeyName))
{
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;
}
}
}
}
}
CheckLastUsed(nonpackagedkey);
}
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;
if (endTime <= 0)
{
return subKeyName;
}
this.processes.Add(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"))
{
foreach (var subKeyName in key.GetSubKeyNames())
{
// NonPackaged has multiple subkeys
if (subKeyName == "NonPackaged")
{
using (var nonpackagedkey = key.OpenSubKey(subKeyName))
{
foreach (var nonpackagedSubKeyName in nonpackagedkey.GetSubKeyNames())
{
using (var subKey = nonpackagedkey.OpenSubKey(nonpackagedSubKeyName))
{
if (subKey.GetValueNames().Contains("LastUsedTimeStop"))
{
var endTime = subKey.GetValue("LastUsedTimeStop") is long ? (long)subKey.GetValue("LastUsedTimeStop") : -1;
if (endTime <= 0)
{
return 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;
}
}
}
}
}
CheckLastUsed(key);
}
if (this.processes.Count() > 0)
{
return String.Join(",", this.processes.ToArray());
}
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">
<PropertyGroup>
<ApplicationRevision>57</ApplicationRevision>
<ApplicationRevision>58</ApplicationRevision>
<ApplicationVersion>1.0.0.*</ApplicationVersion>
<BootstrapperEnabled>True</BootstrapperEnabled>
<Configuration>Release</Configuration>
<CreateDesktopShortcut>True</CreateDesktopShortcut>
<CreateWebPageOnPublish>True</CreateWebPageOnPublish>
<ErrorReportUrl>https://github.com/sleevezipper/hass-workstation-service</ErrorReportUrl>
<GenerateManifests>True</GenerateManifests>
<Install>true</Install>
@ -16,8 +17,8 @@ https://go.microsoft.com/fwlink/?LinkID=208121.
<InstallUrl>https://hassworkstationstorage.z6.web.core.windows.net/publish/</InstallUrl>
<IsRevisionIncremented>True</IsRevisionIncremented>
<IsWebBootstrapper>True</IsWebBootstrapper>
<ManifestCertificateThumbprint>820B7EDF3E26E24BB4C25B177A05B3D0C77BF73A</ManifestCertificateThumbprint>
<ManifestKeyFile>hass-workstation-service_TemporaryKey.pfx</ManifestKeyFile>
<ManifestCertificateThumbprint>66F1CEE175B90B89AFD3AA8F5F649268BF4DFA9E</ManifestCertificateThumbprint>
<ManifestKeyFile>hass-workstation-service_1_TemporaryKey.pfx</ManifestKeyFile>
<MapFileExtensions>true</MapFileExtensions>
<OpenBrowserOnPublish>false</OpenBrowserOnPublish>
<Platform>Any CPU</Platform>
@ -37,12 +38,13 @@ https://go.microsoft.com/fwlink/?LinkID=208121.
<TrustUrlParameters>True</TrustUrlParameters>
<UpdateEnabled>True</UpdateEnabled>
<UpdateMode>Foreground</UpdateMode>
<History>False|2021-11-14T15:44:38.1032015Z;</History>
<UpdateRequired>False</UpdateRequired>
<WebPageFileName>Publish.html</WebPageFileName>
</PropertyGroup>
<ItemGroup>
<BootstrapperPackage Include="Microsoft.NetCore.CoreRuntime.5.0.x64">
<Install>true</Install>
<ProductName>.NET Runtime 5.0.1 (x64)</ProductName>
<Install>True</Install>
<ProductName>.NET Runtime 5.0.12 (x64)</ProductName>
</BootstrapperPackage>
</ItemGroup>
</Project>

@ -43,7 +43,7 @@
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Include="UserInterface.pdb">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
<CopyToOutputDirectory>Never</CopyToOutputDirectory>
</Content>
</ItemGroup>
@ -55,15 +55,15 @@
<ItemGroup>
<PackageReference Include="JKang.IpcServiceFramework.Hosting.NamedPipe" Version="3.1.0" />
<PackageReference Include="LibreHardwareMonitorLib" Version="0.8.8" />
<PackageReference Include="Microsoft.Extensions.Hosting" Version="5.0.0" />
<PackageReference Include="LibreHardwareMonitorLib" Version="0.8.9" />
<PackageReference Include="Microsoft.Extensions.Hosting" Version="6.0.0" />
<PackageReference Include="Microsoft.Win32.Registry" Version="5.0.0" />
<PackageReference Include="MQTTnet" Version="3.0.16" />
<PackageReference Include="MQTTnet.Extensions.ManagedClient" Version="3.0.16" />
<PackageReference Include="MQTTnet" Version="3.1.1" />
<PackageReference Include="MQTTnet.Extensions.ManagedClient" Version="3.1.1" />
<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="System.Management" Version="5.0.0" />
<PackageReference Include="System.Management" Version="6.0.0" />
</ItemGroup>
<ItemGroup>

Loading…
Cancel
Save