Merge branch 'develop'

pull/130/head 1.0.0.31612
Sleevezipper 3 years ago
commit 242e72601f

@ -24,7 +24,7 @@ Not convinced yet? Check out [this excellent video](https://youtu.be/D5A7le79R5M
## Installation ## Installation
You can get the installer from [here](https://hassworkstationstorage.z6.web.core.windows.net/publish/setup.exe). When using the installer, the application checks for updates on startup. This is the recommended way to install for most users. You can get the installer from [here](https://hassworkstationstorage.z6.web.core.windows.net/publish/setup.exe). When using the installer, the application checks for updates on startup. This is the recommended way to install for most users.
Note: You'll get a Windows Smartscreen warning because the code was self signed. You can click "More info" and then "Run anyway" to proceed with installing. Note: You'll get a Windows Smartscreen warning because the code was self signed. You can click "More info" and then "Run anyway" to proceed with installing. If you get an error stating your system's settings not allowing installation, please refer to [this StackOverflow answer](https://superuser.com/a/1252757).
### Standalone ### Standalone
@ -47,14 +47,14 @@ Find us on [Discord](https://discord.gg/VraYT2N3wd), or check out the [frequentl
## Development ## Development
Want to develop or build the application yourself? Make sure to install the .NET Runtime [.NET 5 Runtime](https://dotnet.microsoft.com/download/dotnet/current/runtime) and [.NET 5 SDK](https://dotnet.microsoft.com/download/dotnet/current). Run the following commands from the `hass-workstation-service\hass-workstation-service` directory to get you started: If you want to help develop Hass Workstation service, make sure to install the .NET Runtime [.NET 5 Runtime](https://dotnet.microsoft.com/download/dotnet/current/runtime) and [.NET 5 SDK](https://dotnet.microsoft.com/download/dotnet/current). Run the following commands from the `hass-workstation-service\hass-workstation-service` directory to get you started:
```` powershell ```` powershell
dotnet build dotnet build
dotnet publish dotnet publish
```` ````
In case you are using Visual Studio Code, open the `hass-workstation-service\hass-workstation-service` folder to take advantage of the predefined build and publish tasks. If you are using [Visual Studio](https://visualstudio.microsoft.com/), open the `hass-workstation-service\hass-workstation-service` folder to take advantage of the predefined build and publish tasks, alternatively you can open the project directly from github using the green download button to use the integrated git tools.
## Sensors ## Sensors
@ -62,151 +62,25 @@ The application provides several sensors. Sensors can be configured with a name
Sensors publish their state on their own interval which you can configure and only publish when the state changes. Sensors publish their state on their own interval which you can configure and only publish when the state changes.
### UserNotificationState Here is a list of the most commonly used sensors with the full documentation [here](https://github.com/sleevezipper/hass-workstation-service/blob/master/documentation/Sensors.md):
This sensor watches the UserNotificationState. This is normally used in applications to determine if it is appropriate to send a notification but we can use it to expose this state. Notice that this status does not watch Focus Assist. It has the following possible states: |sensor|use|
|State|Explanation|
|---|---|
|NotPresent|A screen saver is displayed, the machine is locked, or a nonactive Fast User Switching session is in progress. |
|Busy|A full-screen application is running or Presentation Settings are applied. Presentation Settings allow a user to put their machine into a state fit for an uninterrupted presentation, such as a set of PowerPoint slides, with a single click.|
|RunningDirect3dFullScreen|A full-screen (exclusive mode) Direct3D application is running.|
|PresentationMode|The user has activated Windows presentation settings to block notifications and pop-up messages.|
|AcceptsNotifications|None of the other states are found, notifications can be freely sent.|
|QuietTime|Introduced in Windows 7. The current user is in "quiet time", which is the first hour after a new user logs into his or her account for the first time. During this time, most notifications should not be sent or shown. This lets a user become accustomed to a new computer system without those distractions. Quiet time also occurs for each user after an operating system upgrade or clean installation.|
|RunningWindowsStoreApp|A Windows Store app is running.|
### ActiveWindow
This sensor exposes the name of the currently focused window.
### WebcamActive
This sensor shows if the webcam is currently being used. It uses the Windows registry to check will work from Windows 10 version 1903 and higher.
### MicrophoneActive
This sensor shows if the microphone is currently being used. It uses the Windows registry to check and will work from Windows 10 version 1903 and higher.
### CPULoad
This sensor checks the current CPU load. It averages the load on all logical cores every second and rounds the output to two decimals.
### GPULoad
This sensor returns the current GPU load. This should work for both NVidia and AMD GPU's.
### GPUTemperature
This sensor returns the current temperature of the GPU in °C. This should work for both NVidia and AMD GPU's.
### UsedMemory
This sensor calculates the percentage of used memory.
### CurrentClockSpeed
This sensor returns the BIOS configured baseclock for the processor.
### WMIQuery
This advanced sensor executes a user defined [WMI query](https://docs.microsoft.com/en-us/windows/win32/wmisdk/wmi-and-sql) and exposes the result. The query should return a single value.
For example:
```sql
SELECT * FROM Win32_Processor
```
returns
`|64|9|To Be Filled By O.E.M.|3|Intel64 Family 6 Model 94 Stepping 3|252|1|Win32_Processor|4008|12|64|Intel64 Family 6 Model 94 Stepping 3|CPU0|100|198|1024|8192|0|6|4|GenuineIntel|4008|Intel(R) Core(TM) i7-6700K CPU @ 4.00GHz|4|4|8|To Be Filled By O.E.M.|False|BFEBFBFF000506E3|3|24067|CPU|False|To Be Filled By O.E.M.|U3E1|OK|3|Win32_ComputerSystem|GAME-PC-2016|8|1|False|False|`
This cannot not be used for this sensor. Instead try
```sql
SELECT CurrentClockSpeed FROM Win32_Processor
```
which results in `4008` for my PC.
You can use [WMI Explorer](https://github.com/vinaypamnani/wmie2/tree/v2.0.0.2) to find see what data is available.
Here's some queries from other users:
|Query|Explanation|Thanks|
|---|---|---|
|`SELECT username FROM Win32_ComputerSystem`|Shows the current user|@grizzlyjere|
Want to add you query here? Please create a pull request or open an issue.
### LastActive
This sensor returns the date/time that the workstation was last active. Typing or moving your mouse will reset the date/time.
### LastBoot
This sensor returns the date/time that Windows was last booted.
### SessionState
This sensor returns the current session state. It has the following possible states:
|State|Explanation|
|---|---| |---|---|
|Locked|All user sessions are locked.| |ActiveWindow|Exposes the currently selected window|
|LoggedOff|No users are logged in.| |WebcamActive|Exposes the microphone state|
|InUse|A user is currently logged in.| |MicrophoneActive|Exposes the webcam state|
|Unknown|Something went wrong while getting the status.|
### CurrentVolume
This sensor returns the volume of the currently playing audio. So if you're listening to music and you pause, this sensor will return 0 (or at least a very low value).
### Dummy
This sensor spits out a random number every second. Useful for testing, maybe you'll find some other use for it.
## Commands ## Commands
Commands can be used to trigger certain things on the client. For each command, a switch will be available in Home Assistant. Turning on the switch fires the command on the client and it will turn the switch off when it's done. Turning it off will cancel the running command. This application allows you to send commands over MQTT to control the host system, and will be exposed using [MQTT discovery](https://www.home-assistant.io/docs/mqtt/discovery/). Alternatively you can directly send a command from Home Assistant using this topic : `homeassistant/switch/{DeviceName}/{Name}/set`, with the payload `ON`.
### ShutdownCommand
This command shuts down the computer immediately. It runs `shutdown /s`.
### RestartCommand
This command restarts the computer immediately. It runs `shutdown /r`.
### LogOffCommand
This command logs off the current user. It runs `shutdown /l`.
### CustomCommand Here is a list of the most commonly used sensors with the full documentation [here](https://github.com/sleevezipper/hass-workstation-service/blob/master/documentation/Commands.md)
This command allows you to run any Windows Commands. The command will be run in a hidden Command Prompt. Some examples: |command|use|
|Command|Explanation|
|---|---| |---|---|
|Rundll32.exe user32.dll,LockWorkStation|This locks the current session.| |ShutdownCommand|Shutdown the PC|
|shutdown /s /t 300|Shuts the PC down after 5 minutes (300 seconds).| |RestartCommand|Restart the PC|
|C:\path\to\your\batchfile.bat|Run the specified batch file.| |MuteCommand|Mute the speakers|
### KeyCommand
Sends a keystroke with the specified key. You can pick [any of these](https://docs.microsoft.com/en-us/windows/win32/inputdev/virtual-key-codes) key codes.
### Media Commands
There's several media commands available which are very self exlanatory.
- Play/Pause
- Next
- Previous
- Volume up
- Volume down
- Mute (toggle)
## Credits ## Credits

@ -5,20 +5,19 @@
<StartupObject>UserInterface.Program</StartupObject> <StartupObject>UserInterface.Program</StartupObject>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<Folder Include="Models\" />
<AvaloniaResource Include="Assets\**" /> <AvaloniaResource Include="Assets\**" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<None Remove="Assets\hass-workstation-logo.ico" /> <None Remove="Assets\hass-workstation-logo.ico" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Avalonia" Version="0.10.0" /> <PackageReference Include="Avalonia" Version="0.10.8" />
<PackageReference Include="Avalonia.Controls.DataGrid" Version="0.10.0" /> <PackageReference Include="Avalonia.Controls.DataGrid" Version="0.10.8" />
<PackageReference Include="Avalonia.Desktop" Version="0.10.0" /> <PackageReference Include="Avalonia.Desktop" Version="0.10.8" />
<PackageReference Include="Avalonia.ReactiveUI" Version="0.10.0" /> <PackageReference Include="Avalonia.ReactiveUI" Version="0.10.8" />
<PackageReference Include="Avalonia.Win32" Version="0.10.0" /> <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="3.1.1" /> <PackageReference Include="Serilog.Sinks.Console" Version="4.0.0" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\hass-workstation-service\hass-workstation-service.csproj" /> <ProjectReference Include="..\hass-workstation-service\hass-workstation-service.csproj" />
@ -33,6 +32,9 @@
<Compile Update="Views\BackgroundServiceSettings.axaml.cs"> <Compile Update="Views\BackgroundServiceSettings.axaml.cs">
<DependentUpon>BackgroundServiceSettings.axaml</DependentUpon> <DependentUpon>BackgroundServiceSettings.axaml</DependentUpon>
</Compile> </Compile>
<Compile Update="Views\GeneralSettings.axaml.cs">
<DependentUpon>GeneralSettings.axaml</DependentUpon>
</Compile>
<Compile Update="Views\CommandSettings.axaml.cs"> <Compile Update="Views\CommandSettings.axaml.cs">
<DependentUpon>CommandSettings.axaml</DependentUpon> <DependentUpon>CommandSettings.axaml</DependentUpon>
</Compile> </Compile>

@ -1,34 +1,26 @@
using hass_workstation_service.Communication.InterProcesCommunication.Models; using hass_workstation_service.Communication.InterProcesCommunication.Models;
using ReactiveUI; using ReactiveUI;
using System;
using System.Collections.Generic;
using System.Text;
namespace UserInterface.ViewModels namespace UserInterface.ViewModels
{ {
public class AddCommandViewModel : ViewModelBase public class AddCommandViewModel : ViewModelBase
{ {
private AvailableCommands selectedType; private AvailableCommands _selectedType;
private string description; private string _name;
private string _description;
private bool _showCommandInput;
private bool _showKeyInput;
private string _moreInfoLink;
private string _command;
private string _key;
public string Description { get => description; set => this.RaiseAndSetIfChanged(ref description, value); } public AvailableCommands SelectedType { get => _selectedType; set => this.RaiseAndSetIfChanged(ref _selectedType, value); }
public bool ShowCommandInput { get => showCommandInput; set => this.RaiseAndSetIfChanged(ref showCommandInput, value); } public string Name { get => _name; set => this.RaiseAndSetIfChanged(ref _name, value); }
public bool ShowKeyInput { get => showKeyInput; set => this.RaiseAndSetIfChanged(ref showKeyInput, value); } public string Description { get => _description; set => this.RaiseAndSetIfChanged(ref _description, value); }
public bool ShowCommandInput { get => _showCommandInput; set => this.RaiseAndSetIfChanged(ref _showCommandInput, value); }
private string moreInfoLink; public bool ShowKeyInput { get => _showKeyInput; set => this.RaiseAndSetIfChanged(ref _showKeyInput, value); }
private bool showCommandInput; public string MoreInfoLink { get => _moreInfoLink; set => this.RaiseAndSetIfChanged(ref _moreInfoLink, value); }
private bool showKeyInput; public string Command { get => _command; set => this.RaiseAndSetIfChanged(ref _command, value); }
public string Key { get => _key; set => this.RaiseAndSetIfChanged(ref _key, value); }
public string MoreInfoLink
{
get { return moreInfoLink; }
set { this.RaiseAndSetIfChanged(ref moreInfoLink, value); }
}
public AvailableCommands SelectedType { get => selectedType; set => this.RaiseAndSetIfChanged(ref selectedType, value); }
public string Name { get; set; }
public string Command { get; set; }
public string Key { get; set; }
} }
} }

@ -1,40 +1,28 @@
using hass_workstation_service.Communication.InterProcesCommunication.Models; using hass_workstation_service.Communication.InterProcesCommunication.Models;
using ReactiveUI; using ReactiveUI;
using System;
using System.Collections.Generic;
using System.Text;
namespace UserInterface.ViewModels namespace UserInterface.ViewModels
{ {
public class AddSensorViewModel : ViewModelBase public class AddSensorViewModel : ViewModelBase
{ {
private AvailableSensors selectedType; private AvailableSensors _selectedType;
private string description; private string _name;
private bool showQueryInput; private int _updateInterval;
private string _description;
public string Description { get => description; set => this.RaiseAndSetIfChanged(ref description, value); } private bool _showQueryInput;
public bool ShowQueryInput { get => showQueryInput; set => this.RaiseAndSetIfChanged(ref showQueryInput, value); } private bool _showWindowNameInput;
public bool ShowWindowNameInput { get => showWindowNameInput; set => this.RaiseAndSetIfChanged(ref showWindowNameInput, value); } private string _moreInfoLink;
private string _query;
public bool ShowDetectionModeOptions { get => showDetectionModeOptions; set => this.RaiseAndSetIfChanged(ref showDetectionModeOptions, value); } private string _windowName;
private string moreInfoLink; public AvailableSensors SelectedType { get => _selectedType; set => this.RaiseAndSetIfChanged(ref _selectedType, value); }
private int updateInterval; public string Name { get => _name; set => this.RaiseAndSetIfChanged(ref _name, value); }
private bool showWindowNameInput; public int UpdateInterval { get => _updateInterval; set => this.RaiseAndSetIfChanged(ref _updateInterval, value); }
private bool showDetectionModeOptions; public string Description { get => _description; set => this.RaiseAndSetIfChanged(ref _description, value); }
public bool ShowQueryInput { get => _showQueryInput; set => this.RaiseAndSetIfChanged(ref _showQueryInput, value); }
public string MoreInfoLink public bool ShowWindowNameInput { get => _showWindowNameInput; set => this.RaiseAndSetIfChanged(ref _showWindowNameInput, value); }
{ public string MoreInfoLink { get => _moreInfoLink; set => this.RaiseAndSetIfChanged(ref _moreInfoLink, value); }
get { return moreInfoLink; } public string Query { get => _query; set => this.RaiseAndSetIfChanged(ref _query, value); }
set { this.RaiseAndSetIfChanged(ref moreInfoLink, value); } public string WindowName { get => _windowName; set => this.RaiseAndSetIfChanged(ref _windowName, value); }
}
public AvailableSensors SelectedType { get => selectedType; set => this.RaiseAndSetIfChanged(ref selectedType, value); }
public string Name { get; set; }
public string Query { get; set; }
public string WindowName { get; set; }
public int UpdateInterval { get => updateInterval; set => this.RaiseAndSetIfChanged(ref updateInterval, value); }
} }
} }

@ -1,15 +1,20 @@
using ReactiveUI; using hass_workstation_service.Communication.InterProcesCommunication.Models;
using ReactiveUI;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Text;
namespace UserInterface.ViewModels namespace UserInterface.ViewModels
{ {
public class CommandSettingsViewModel : ViewModelBase public class CommandSettingsViewModel : ViewModelBase
{ {
private ICollection<CommandViewModel> configuredCommands; private ICollection<CommandViewModel> _configuredCommands;
public ICollection<CommandViewModel> ConfiguredCommands
{
get => _configuredCommands;
set => this.RaiseAndSetIfChanged(ref _configuredCommands, value);
}
public ICollection<CommandViewModel> ConfiguredCommands { get => configuredCommands; set => this.RaiseAndSetIfChanged(ref configuredCommands, value); }
public void TriggerUpdate() public void TriggerUpdate()
{ {
this.RaisePropertyChanged(); this.RaisePropertyChanged();
@ -19,7 +24,7 @@ namespace UserInterface.ViewModels
public class CommandViewModel : ViewModelBase public class CommandViewModel : ViewModelBase
{ {
public Guid Id { get; set; } public Guid Id { get; set; }
public string Type { get; set; } public AvailableCommands Type { get; set; }
public string Name { get; set; } public string Name { get; set; }
} }
} }

@ -0,0 +1,22 @@
using hass_workstation_service.Communication.InterProcesCommunication.Models;
using hass_workstation_service.Data;
using ReactiveUI;
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Text;
namespace UserInterface.ViewModels
{
public class GeneralSettingsViewModel : ViewModelBase
{
private string namePrefix;
public string NamePrefix { get => namePrefix; set => this.RaiseAndSetIfChanged(ref namePrefix, value); }
public void Update(GeneralSettings settings)
{
this.NamePrefix = settings.NamePrefix;
}
}
}

@ -1,15 +1,20 @@
using ReactiveUI; using hass_workstation_service.Communication.InterProcesCommunication.Models;
using ReactiveUI;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Text;
namespace UserInterface.ViewModels namespace UserInterface.ViewModels
{ {
public class SensorSettingsViewModel : ViewModelBase public class SensorSettingsViewModel : ViewModelBase
{ {
private ICollection<SensorViewModel> configuredSensors; private ICollection<SensorViewModel> _configuredSensors;
public ICollection<SensorViewModel> ConfiguredSensors
{
get => _configuredSensors;
set => this.RaiseAndSetIfChanged(ref _configuredSensors, value);
}
public ICollection<SensorViewModel> ConfiguredSensors { get => configuredSensors; set => this.RaiseAndSetIfChanged(ref configuredSensors, value); }
public void TriggerUpdate() public void TriggerUpdate()
{ {
this.RaisePropertyChanged(); this.RaisePropertyChanged();
@ -21,30 +26,21 @@ namespace UserInterface.ViewModels
private string _value; private string _value;
public Guid Id { get; set; } public Guid Id { get; set; }
public string Type { get; set; } public AvailableSensors Type { get; set; }
public string Name { get; set; } public string Name { get; set; }
public int UpdateInterval { get; set; } public int UpdateInterval { get; set; }
public string Value public string Value
{ {
get => _value; set get => _value;
set
{ {
this.RaiseAndSetIfChanged(ref _value, value); this.RaiseAndSetIfChanged(ref _value, value);
this.RaisePropertyChanged("ValueString"); this.RaisePropertyChanged(nameof(ValueString));
} }
} }
public string UnitOfMeasurement { get; set; }
public string ValueString public string UnitOfMeasurement { get; set; }
{
get
{
if (!string.IsNullOrWhiteSpace(_value))
{
return _value + " " + UnitOfMeasurement;
}
else return "";
} public string ValueString => string.IsNullOrWhiteSpace(_value) ? string.Empty : $"{_value} {UnitOfMeasurement}";
}
} }
} }

@ -5,24 +5,26 @@
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="UserInterface.Views.AddCommandDialog" x:Class="UserInterface.Views.AddCommandDialog"
SizeToContent="WidthAndHeight" SizeToContent="WidthAndHeight"
Title="Add command"> Title="Add / edit command">
<StackPanel Margin="40" MinWidth="200"> <StackPanel Margin="40" MinWidth="200">
<ContentControl Margin="0 20 0 10">Command type</ContentControl> <ContentControl Margin="0 20 0 10">Command type</ContentControl>
<ComboBox x:Name="ComboBox" SelectionChanged="ComboBoxClosed" SelectedItem="{Binding SelectedType}" MinHeight="27"></ComboBox> <ComboBox x:Name="ComboBox" SelectionChanged="ComboBoxClosed" SelectedItem="{Binding SelectedType}" MinHeight="27"></ComboBox>
<TextBlock Margin="0 10 0 10" MaxWidth="300" TextWrapping="Wrap" TextAlignment="Left" Text="{Binding Description}"></TextBlock> <TextBlock Margin="0 10 0 10" MaxWidth="300" TextWrapping="Wrap" TextAlignment="Left" Text="{Binding Description}"></TextBlock>
<Button IsVisible="{Binding MoreInfoLink, Converter={x:Static StringConverters.IsNotNullOrEmpty}}" Click="OpenInfo" Margin="0 10 0 10">Click for more information.</Button> <Button IsVisible="{Binding MoreInfoLink, Converter={x:Static StringConverters.IsNotNullOrEmpty}}" Click="OpenInfo" Margin="0 10 0 10">Click for more information.</Button>
<ContentControl Margin="0 20 0 10">Name</ContentControl> <ContentControl Margin="0 20 0 10">Name</ContentControl>
<TextBox Text="{Binding Name}" HorizontalAlignment="Left" MinWidth="150"/> <TextBox Text="{Binding Name}" HorizontalAlignment="Left" MinWidth="150"/>
<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 ShowCommandInput}" Margin="0 20 0 10">Command</ContentControl> <ContentControl IsVisible="{Binding ShowCommandInput}" Margin="0 20 0 10">Command</ContentControl>
<TextBox IsVisible="{Binding ShowCommandInput}" Text="{Binding Command}" Watermark="Rundll32.exe user32.dll,LockWorkStation" HorizontalAlignment="Left" MinWidth="300"/> <TextBox IsVisible="{Binding ShowCommandInput}" Text="{Binding Command}" Watermark="Rundll32.exe user32.dll,LockWorkStation" HorizontalAlignment="Left" MinWidth="300"/>
<ContentControl IsVisible="{Binding ShowKeyInput}" Margin="0 20 0 10">Key</ContentControl> <ContentControl IsVisible="{Binding ShowKeyInput}" Margin="0 20 0 10">Key</ContentControl>
<TextBox IsVisible="{Binding ShowKeyInput}" Text="{Binding Key}" Watermark="0xAD" HorizontalAlignment="Left" MinWidth="300"/> <TextBox IsVisible="{Binding ShowKeyInput}" Text="{Binding Key}" Watermark="0xAD" HorizontalAlignment="Left" MinWidth="300"/>
<Button IsVisible="{Binding ShowCommandInput}" Width="75" HorizontalAlignment="Right" Margin="0 40 0 10" Click="Test">Test</Button> <Grid>
<Button Width="75" HorizontalAlignment="Right" Margin="0 40 0 10" Click="Save">Save</Button> <Button IsVisible="{Binding ShowCommandInput}" Width="75" HorizontalAlignment="Left" Margin="0 40 0 10" Click="Test">Test</Button>
</StackPanel> <Button Width="75" HorizontalAlignment="Right" Margin="0 40 0 10" Click="Save">Save</Button>
</Grid>
</StackPanel>
</Window> </Window>

@ -7,7 +7,6 @@ using hass_workstation_service.Communication.NamedPipe;
using JKang.IpcServiceFramework.Client; using JKang.IpcServiceFramework.Client;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using System; using System;
using System.Dynamic;
using System.Linq; using System.Linq;
using System.Text.Json; using System.Text.Json;
using UserInterface.Util; using UserInterface.Util;
@ -17,107 +16,153 @@ namespace UserInterface.Views
{ {
public class AddCommandDialog : Window public class AddCommandDialog : Window
{ {
private readonly IIpcClient<ServiceContractInterfaces> client; private readonly IIpcClient<IServiceContractInterfaces> _client;
public ComboBox comboBox { get; set; } public ComboBox ComboBox { get; set; }
public ComboBox detectionModecomboBox { get; set; } public ComboBox DetectionModecomboBox { get; set; }
public Guid CommandId { get; }
public AddCommandDialog(Guid commandId) : this()
{
CommandId = commandId;
GetCommandInfo(CommandId);
Title = "Edit command";
}
public AddCommandDialog() public AddCommandDialog()
{ {
this.InitializeComponent(); InitializeComponent();
DataContext = new AddCommandViewModel(); DataContext = new AddCommandViewModel();
this.comboBox = this.FindControl<ComboBox>("ComboBox"); ComboBox = this.FindControl<ComboBox>("ComboBox");
this.comboBox.Items = Enum.GetValues(typeof(AvailableCommands)).Cast<AvailableCommands>().OrderBy(v => v.ToString()); ComboBox.Items = Enum.GetValues(typeof(AvailableCommands)).Cast<AvailableCommands>().OrderBy(v => v.ToString());
this.comboBox.SelectedIndex = 0; ComboBox.SelectedIndex = 0;
// register IPC clients // register IPC clients
ServiceProvider serviceProvider = new ServiceCollection() ServiceProvider serviceProvider = new ServiceCollection()
.AddNamedPipeIpcClient<ServiceContractInterfaces>("addCommand", pipeName: "pipeinternal") .AddNamedPipeIpcClient<IServiceContractInterfaces>("addCommand", pipeName: "pipeinternal")
.BuildServiceProvider(); .BuildServiceProvider();
// resolve IPC client factory // resolve IPC client factory
IIpcClientFactory<ServiceContractInterfaces> clientFactory = serviceProvider IIpcClientFactory<IServiceContractInterfaces> clientFactory = serviceProvider
.GetRequiredService<IIpcClientFactory<ServiceContractInterfaces>>(); .GetRequiredService<IIpcClientFactory<IServiceContractInterfaces>>();
// create client // create client
this.client = clientFactory.CreateClient("addCommand"); _client = clientFactory.CreateClient("addCommand");
Title = "Add command";
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
private async void GetCommandInfo(Guid commandId)
{
var command = await _client.InvokeAsync(x => x.GetConfiguredCommand(commandId));
ComboBox.SelectedItem = command.Type;
FillDefaultValues();
ComboBox.IsEnabled = false;
var item = (AddCommandViewModel)DataContext;
item.SelectedType = command.Type;
item.Name = command.Name;
item.Command = command.Command;
item.Key = command.Key;
} }
public async void Save(object sender, RoutedEventArgs args) public async void Save(object sender, RoutedEventArgs args)
{ {
var item = ((AddCommandViewModel)this.DataContext); var item = (AddCommandViewModel)DataContext;
dynamic model = new { item.Name, item.Command, item.Key}; dynamic model = new { item.Name, item.Command, item.Key };
string json = JsonSerializer.Serialize(model); string json = JsonSerializer.Serialize(model);
await this.client.InvokeAsync(x => x.AddCommand(item.SelectedType, json)); if (CommandId == Guid.Empty)
await _client.InvokeAsync(x => x.AddCommand(item.SelectedType, json));
else
await _client.InvokeAsync(x => x.UpdateCommandById(CommandId, json));
Close(); Close();
} }
public void ComboBoxClosed(object sender, SelectionChangedEventArgs args) public void ComboBoxClosed(object sender, SelectionChangedEventArgs args)
{ {
var item = ((AddCommandViewModel)this.DataContext); FillDefaultValues();
switch (this.comboBox.SelectedItem) }
private void FillDefaultValues()
{
var item = (AddCommandViewModel)DataContext;
switch (ComboBox.SelectedItem)
{ {
case AvailableCommands.CustomCommand: case AvailableCommands.CustomCommand:
item.Description = "This command lets you execute any command you want. It will run in a Windows Command Prompt silently. "; item.Description = "This command lets you execute any command you want. It will run in a Windows Command Prompt silently. ";
item.MoreInfoLink = "https://github.com/sleevezipper/hass-workstation-service#customcommand"; item.MoreInfoLink = "https://github.com/sleevezipper/hass-workstation-service/blob/master/documentation/Commands.md#customcommand";
item.ShowCommandInput = true; item.ShowCommandInput = true;
item.ShowKeyInput = false; item.ShowKeyInput = false;
break; break;
case AvailableCommands.ShutdownCommand: case AvailableCommands.ShutdownCommand:
item.Description = "This command shuts down the PC immediately. "; item.Description = "This command shuts down the PC immediately. ";
item.MoreInfoLink = "https://github.com/sleevezipper/hass-workstation-service#shutdowncommand"; item.MoreInfoLink = "https://github.com/sleevezipper/hass-workstation-service/blob/master/documentation/Commands.md#shutdowncommand";
item.ShowCommandInput = false; item.ShowCommandInput = false;
item.ShowKeyInput = false; item.ShowKeyInput = false;
break; break;
case AvailableCommands.RestartCommand: case AvailableCommands.RestartCommand:
item.Description = "This command restarts the PC immediately. "; item.Description = "This command restarts the PC immediately. ";
item.MoreInfoLink = "https://github.com/sleevezipper/hass-workstation-service#restartcommand"; item.MoreInfoLink = "https://github.com/sleevezipper/hass-workstation-service/blob/master/documentation/Commands.md#restartcommand";
item.ShowCommandInput = false;
item.ShowKeyInput = false;
break;
case AvailableCommands.HibernateCommand:
item.Description = "This command hibernates the PC immediately. ";
item.MoreInfoLink = "https://github.com/sleevezipper/hass-workstation-service/blob/master/documentation/Commands.md#hibernatecommand";
item.ShowCommandInput = false; item.ShowCommandInput = false;
item.ShowKeyInput = false; item.ShowKeyInput = false;
break; break;
case AvailableCommands.LogOffCommand: case AvailableCommands.LogOffCommand:
item.Description = "This command logs the current user off immediately. "; item.Description = "This command logs the current user off immediately. ";
item.MoreInfoLink = "https://github.com/sleevezipper/hass-workstation-service#logoffcommand"; item.MoreInfoLink = "https://github.com/sleevezipper/hass-workstation-service/blob/master/documentation/Commands.md#logoffcommand";
item.ShowCommandInput = false; item.ShowCommandInput = false;
item.ShowKeyInput = false; item.ShowKeyInput = false;
break; break;
case AvailableCommands.KeyCommand: case AvailableCommands.KeyCommand:
item.Description = "This command can be used to emulate a keystroke. It requires a key code which you can find by clicking the info button below."; item.Description = "This command can be used to emulate a keystroke. It requires a key code which you can find by clicking the info button below.";
item.MoreInfoLink = "https://github.com/sleevezipper/hass-workstation-service#keycommand"; item.MoreInfoLink = "https://github.com/sleevezipper/hass-workstation-service/blob/master/documentation/Commands.md#keycommand";
item.ShowCommandInput = false; item.ShowCommandInput = false;
item.ShowKeyInput = true; item.ShowKeyInput = true;
break; break;
case AvailableCommands.PlayPauseCommand: case AvailableCommands.PlayPauseCommand:
item.Description = "This command plays or pauses currently playing media."; item.Description = "This command plays or pauses currently playing media.";
item.MoreInfoLink = "https://github.com/sleevezipper/hass-workstation-service#media-commands"; item.MoreInfoLink = "https://github.com/sleevezipper/hass-workstation-service/blob/master/documentation/Commands.md#media-commands";
item.ShowCommandInput = false; item.ShowCommandInput = false;
item.ShowKeyInput = false; item.ShowKeyInput = false;
break; break;
case AvailableCommands.NextCommand: case AvailableCommands.NextCommand:
item.Description = "This command skips to the next media."; item.Description = "This command skips to the next media.";
item.MoreInfoLink = "https://github.com/sleevezipper/hass-workstation-service#media-commands"; item.MoreInfoLink = "https://github.com/sleevezipper/hass-workstation-service/blob/master/documentation/Commands.md#media-commands";
item.ShowCommandInput = false; item.ShowCommandInput = false;
item.ShowKeyInput = false; item.ShowKeyInput = false;
break; break;
case AvailableCommands.PreviousCommand: case AvailableCommands.PreviousCommand:
item.Description = "This command plays previous media."; item.Description = "This command plays previous media.";
item.MoreInfoLink = "https://github.com/sleevezipper/hass-workstation-service#media-commands"; item.MoreInfoLink = "https://github.com/sleevezipper/hass-workstation-service/blob/master/documentation/Commands.md#media-commands";
item.ShowCommandInput = false; item.ShowCommandInput = false;
item.ShowKeyInput = false; item.ShowKeyInput = false;
break; break;
case AvailableCommands.VolumeDownCommand: case AvailableCommands.VolumeDownCommand:
item.Description = "Lowers the system volume."; item.Description = "Lowers the system volume.";
item.MoreInfoLink = "https://github.com/sleevezipper/hass-workstation-service#media-commands"; item.MoreInfoLink = "https://github.com/sleevezipper/hass-workstation-service/blob/master/documentation/Commands.md#media-commands";
item.ShowCommandInput = false; item.ShowCommandInput = false;
item.ShowKeyInput = false; item.ShowKeyInput = false;
break; break;
case AvailableCommands.VolumeUpCommand: case AvailableCommands.VolumeUpCommand:
item.Description = "Raises the system volume."; item.Description = "Raises the system volume.";
item.MoreInfoLink = "https://github.com/sleevezipper/hass-workstation-service#media-commands"; item.MoreInfoLink = "https://github.com/sleevezipper/hass-workstation-service/blob/master/documentation/Commands.md#media-commands";
item.ShowCommandInput = false; item.ShowCommandInput = false;
item.ShowKeyInput = false; item.ShowKeyInput = false;
break; break;
case AvailableCommands.MuteCommand: case AvailableCommands.MuteCommand:
item.Description = "Toggles muting the system volume."; item.Description = "Toggles muting the system volume.";
item.MoreInfoLink = "https://github.com/sleevezipper/hass-workstation-service#media-commands"; item.MoreInfoLink = "https://github.com/sleevezipper/hass-workstation-service/blob/master/documentation/Commands.md#media-commands";
item.ShowCommandInput = false; item.ShowCommandInput = false;
item.ShowKeyInput = false; item.ShowKeyInput = false;
break; break;
@ -129,28 +174,26 @@ namespace UserInterface.Views
break; break;
} }
} }
public void OpenInfo(object sender, RoutedEventArgs args) public void OpenInfo(object sender, RoutedEventArgs args)
{ {
var item = ((AddCommandViewModel)this.DataContext); var item = (AddCommandViewModel)DataContext;
BrowserUtil.OpenBrowser(item.MoreInfoLink); BrowserUtil.OpenBrowser(item.MoreInfoLink);
} }
public void Test(object sender, RoutedEventArgs args) public void Test(object sender, RoutedEventArgs args)
{ {
var item = ((AddCommandViewModel)this.DataContext); var item = (AddCommandViewModel)DataContext;
System.Diagnostics.Process process = new System.Diagnostics.Process(); var process = new System.Diagnostics.Process();
System.Diagnostics.ProcessStartInfo startInfo = new System.Diagnostics.ProcessStartInfo(); var startInfo = new System.Diagnostics.ProcessStartInfo
startInfo.WindowStyle = System.Diagnostics.ProcessWindowStyle.Normal; {
startInfo.FileName = "cmd.exe"; WindowStyle = System.Diagnostics.ProcessWindowStyle.Normal,
startInfo.Arguments = $"/k {"echo You won't see this window normally. &&" + item.Command}"; FileName = "cmd.exe",
Arguments = $"/k {"echo You won't see this window normally. &&" + item.Command}"
};
process.StartInfo = startInfo; process.StartInfo = startInfo;
process.Start(); process.Start();
} }
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
} }
} }

@ -5,27 +5,27 @@
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="UserInterface.Views.AddSensorDialog" x:Class="UserInterface.Views.AddSensorDialog"
SizeToContent="WidthAndHeight" SizeToContent="WidthAndHeight"
Title="Add sensor"> Title="Add / edit sensor">
<StackPanel Margin="40" MinWidth="200"> <StackPanel Margin="40" MinWidth="200">
<ContentControl Margin="0 20 0 10">Sensor type</ContentControl> <ContentControl Margin="0 20 0 10">Sensor type</ContentControl>
<ComboBox x:Name="ComboBox" SelectionChanged="ComboBoxClosed" SelectedItem="{Binding SelectedType}" MinHeight="27"></ComboBox> <ComboBox x:Name="ComboBox" SelectionChanged="ComboBoxClosed" SelectedItem="{Binding SelectedType}" MinHeight="27"></ComboBox>
<TextBlock Margin="0 10 0 10" MaxWidth="300" TextWrapping="Wrap" TextAlignment="Left" Text="{Binding Description}"></TextBlock> <TextBlock Margin="0 10 0 10" MaxWidth="300" TextWrapping="Wrap" TextAlignment="Left" Text="{Binding Description}"></TextBlock>
<Button IsVisible="{Binding MoreInfoLink, Converter={x:Static StringConverters.IsNotNullOrEmpty}}" Click="OpenInfo" Margin="0 10 0 10">Click for more information.</Button> <Button IsVisible="{Binding MoreInfoLink, Converter={x:Static StringConverters.IsNotNullOrEmpty}}" Click="OpenInfo" Margin="0 10 0 10">Click for more information.</Button>
<ContentControl Margin="0 20 0 10">Name</ContentControl> <ContentControl Margin="0 20 0 10">Name</ContentControl>
<TextBox Text="{Binding Name}" HorizontalAlignment="Left" MinWidth="150"/> <TextBox Text="{Binding Name}" HorizontalAlignment="Left" MinWidth="150"/>
<ContentControl Margin="0 20 0 10">Update interval</ContentControl> <ContentControl Margin="0 20 0 10">Update interval</ContentControl>
<StackPanel Orientation="Horizontal"> <StackPanel Orientation="Horizontal">
<Slider Value="{Binding UpdateInterval}" Minimum="1" Maximum="300" HorizontalAlignment="Left" Width="150"/> <Slider Value="{Binding UpdateInterval}" Minimum="1" Maximum="300" HorizontalAlignment="Left" Width="150"/>
<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">Query</ContentControl>
<TextBox IsVisible="{Binding ShowQueryInput}" Text="{Binding Query}" Watermark="SELECT Name FROM Win32_Processor" HorizontalAlignment="Left" MinWidth="300"/> <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"/>
<Button Width="75" HorizontalAlignment="Right" Margin="0 40 0 10" Click="Save">Save</Button> <Button Width="75" HorizontalAlignment="Right" Margin="0 40 0 10" Click="Save">Save</Button>
</StackPanel> </StackPanel>
</Window> </Window>

@ -7,7 +7,6 @@ using hass_workstation_service.Communication.NamedPipe;
using JKang.IpcServiceFramework.Client; using JKang.IpcServiceFramework.Client;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using System; using System;
using System.Dynamic;
using System.Linq; using System.Linq;
using System.Text.Json; using System.Text.Json;
using UserInterface.Util; using UserInterface.Util;
@ -17,163 +16,219 @@ namespace UserInterface.Views
{ {
public class AddSensorDialog : Window public class AddSensorDialog : Window
{ {
private readonly IIpcClient<ServiceContractInterfaces> client; private readonly IIpcClient<IServiceContractInterfaces> _client;
public ComboBox comboBox { get; set; } public ComboBox ComboBox { get; set; }
public ComboBox detectionModecomboBox { get; set; } public ComboBox DetectionModecomboBox { get; set; }
public Guid SensorId { get; }
public AddSensorDialog(Guid sensorId) : this()
{
SensorId = sensorId;
GetSensorInfo(SensorId);
Title = "Edit sensor";
}
public AddSensorDialog() public AddSensorDialog()
{ {
this.InitializeComponent(); InitializeComponent();
DataContext = new AddSensorViewModel(); DataContext = new AddSensorViewModel();
this.comboBox = this.FindControl<ComboBox>("ComboBox"); ComboBox = this.FindControl<ComboBox>("ComboBox");
this.comboBox.Items = Enum.GetValues(typeof(AvailableSensors)).Cast<AvailableSensors>().OrderBy(v => v.ToString()); ComboBox.Items = Enum.GetValues(typeof(AvailableSensors)).Cast<AvailableSensors>().OrderBy(v => v.ToString());
this.comboBox.SelectedIndex = 0; ComboBox.SelectedIndex = 0;
// register IPC clients // register IPC clients
ServiceProvider serviceProvider = new ServiceCollection() ServiceProvider serviceProvider = new ServiceCollection()
.AddNamedPipeIpcClient<ServiceContractInterfaces>("addsensor", pipeName: "pipeinternal") .AddNamedPipeIpcClient<IServiceContractInterfaces>("addsensor", pipeName: "pipeinternal")
.BuildServiceProvider(); .BuildServiceProvider();
// resolve IPC client factory // resolve IPC client factory
IIpcClientFactory<ServiceContractInterfaces> clientFactory = serviceProvider IIpcClientFactory<IServiceContractInterfaces> clientFactory = serviceProvider
.GetRequiredService<IIpcClientFactory<ServiceContractInterfaces>>(); .GetRequiredService<IIpcClientFactory<IServiceContractInterfaces>>();
// create client // create client
this.client = clientFactory.CreateClient("addsensor"); _client = clientFactory.CreateClient("addsensor");
Title = "Add sensor";
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
private async void GetSensorInfo(Guid sensorId)
{
ConfiguredSensorModel sensor = await _client.InvokeAsync(x => x.GetConfiguredSensor(sensorId));
ComboBox.SelectedItem = sensor.Type;
FillDefaultValues();
ComboBox.IsEnabled = false;
var item = (AddSensorViewModel)DataContext;
item.SelectedType = sensor.Type;
item.Name = sensor.Name;
item.UpdateInterval = sensor.UpdateInterval;
item.Query = sensor.Query;
item.WindowName = sensor.WindowName;
Title = $"Edit {sensor.Name}";
} }
public async void Save(object sender, RoutedEventArgs args) public async void Save(object sender, RoutedEventArgs args)
{ {
var item = ((AddSensorViewModel)this.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 };
string json = JsonSerializer.Serialize(model); string json = JsonSerializer.Serialize(model);
await this.client.InvokeAsync(x => x.AddSensor(item.SelectedType, json)); if (SensorId == Guid.Empty)
await _client.InvokeAsync(x => x.AddSensor(item.SelectedType, json));
else
await _client.InvokeAsync(x => x.UpdateSensorById(SensorId, json));
Close(); Close();
} }
public void ComboBoxClosed(object sender, SelectionChangedEventArgs args) public void ComboBoxClosed(object sender, SelectionChangedEventArgs args)
{ {
var item = ((AddSensorViewModel)this.DataContext); FillDefaultValues();
switch (this.comboBox.SelectedItem) }
private void FillDefaultValues()
{
var item = (AddSensorViewModel)DataContext;
switch (ComboBox.SelectedItem)
{ {
case AvailableSensors.UserNotificationStateSensor: case AvailableSensors.UserNotificationStateSensor:
item.Description = "This sensor watches the UserNotificationState. This is normally used in applications to determine if it is appropriate to send a notification but we can use it to expose this state. \n "; item.Description = "This sensor watches the UserNotificationState. This is normally used in applications to determine if it is appropriate to send a notification but we can use it to expose this state. \n ";
item.MoreInfoLink = "https://github.com/sleevezipper/hass-workstation-service#usernotificationstate"; item.MoreInfoLink = "https://github.com/sleevezipper/hass-workstation-service/blob/master/documentation/Sensors.md#usernotificationstate";
item.ShowDetectionModeOptions = false;
item.ShowQueryInput = false; item.ShowQueryInput = false;
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#dummy"; item.MoreInfoLink = "https://github.com/sleevezipper/hass-workstation-service/blob/master/documentation/Sensors.md#dummysensor";
item.ShowDetectionModeOptions = false;
item.ShowQueryInput = false; item.ShowQueryInput = false;
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#cpuload"; item.MoreInfoLink = "https://github.com/sleevezipper/hass-workstation-service/blob/master/documentation/Sensors.md#cpuloadsensor";
item.ShowDetectionModeOptions = false;
item.ShowQueryInput = false; item.ShowQueryInput = false;
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#currentclockspeed"; item.MoreInfoLink = "https://github.com/sleevezipper/hass-workstation-service/blob/master/documentation/Sensors.md#currentclockspeedsensor";
item.ShowDetectionModeOptions = false;
item.ShowQueryInput = false; item.ShowQueryInput = false;
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/sleevezipper/hass-workstation-service#wmiquerysensor"; item.MoreInfoLink = "https://github.com/sleevezipperhass-workstation-service/blob/master/documentation/WMIQuery.md#wmiquerysensor";
item.ShowDetectionModeOptions = false;
item.ShowQueryInput = true; item.ShowQueryInput = true;
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#usedmemory"; item.MoreInfoLink = "https://github.com/sleevezipper/hass-workstation-service/blob/master/documentation/Sensors.md#memoryusagesensorsensor";
item.ShowDetectionModeOptions = false;
item.ShowQueryInput = false; item.ShowQueryInput = false;
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#activewindow"; item.MoreInfoLink = "https://github.com/sleevezipper/hass-workstation-service/blob/master/documentation/Sensors.md#activewindowsensor";
item.ShowDetectionModeOptions = false;
item.ShowQueryInput = false; item.ShowQueryInput = false;
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#webcamactive"; item.MoreInfoLink = "https://github.com/sleevezipper/hass-workstation-service/blob/master/documentation/Sensors.md#webcamactivesensor";
item.ShowDetectionModeOptions = true;
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#microphoneactive"; item.MoreInfoLink = "https://github.com/sleevezipper/hass-workstation-service/blob/master/documentation/Sensors.md#microphoneactivesensor";
item.ShowDetectionModeOptions = false;
item.ShowQueryInput = false; item.ShowQueryInput = false;
item.UpdateInterval = 10; item.UpdateInterval = 10;
break; 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#namedwindow"; item.MoreInfoLink = "https://github.com/sleevezipper/hass-workstation-service/blob/master/documentation/Sensors.md#namedwindowsensor";
item.ShowQueryInput = false; item.ShowQueryInput = false;
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#lastactive"; item.MoreInfoLink = "https://github.com/sleevezipper/hass-workstation-service/blob/master/documentation/Sensors.md#lastactivesensor";
item.ShowQueryInput = false; item.ShowQueryInput = false;
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#lastboot"; item.MoreInfoLink = "https://github.com/sleevezipper/hass-workstation-service/blob/master/documentation/Sensors.md#lastbootsensor";
item.ShowQueryInput = false; item.ShowQueryInput = false;
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#sessionstate"; item.MoreInfoLink = "https://github.com/sleevezipper/hass-workstation-service/blob/master/documentation/Sensors.md#sessionstatesensor";
item.ShowQueryInput = false; item.ShowQueryInput = false;
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#currentvolume"; item.MoreInfoLink = "https://github.com/sleevezipper/hass-workstation-service/blob/master/documentation/Sensors.md#currentvolumesensor";
item.ShowQueryInput = false;
item.ShowWindowNameInput = false;
item.UpdateInterval = 5;
break;
case AvailableSensors.MasterVolumeSensor:
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.ShowQueryInput = false; item.ShowQueryInput = false;
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#gputemperature"; item.MoreInfoLink = "https://github.com/sleevezipper/hass-workstation-service/blob/master/documentation/Sensors.md#gputemperaturesensor";
item.ShowQueryInput = false; item.ShowQueryInput = false;
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#gpuload"; item.MoreInfoLink = "https://github.com/sleevezipper/hass-workstation-service/blob/master/documentation/Sensors.md#gpuloadsensor";
item.ShowQueryInput = false; item.ShowQueryInput = false;
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;
@ -181,15 +236,11 @@ namespace UserInterface.Views
break; break;
} }
} }
public void OpenInfo(object sender, RoutedEventArgs args) public void OpenInfo(object sender, RoutedEventArgs args)
{ {
var item = ((AddSensorViewModel)this.DataContext); var item = (AddSensorViewModel)DataContext;
BrowserUtil.OpenBrowser(item.MoreInfoLink); BrowserUtil.OpenBrowser(item.MoreInfoLink);
} }
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
} }
} }

@ -13,6 +13,14 @@
<Button Width="75" HorizontalAlignment="Right" Margin="10 10" Click="GitHub">GitHub</Button> <Button Width="75" HorizontalAlignment="Right" Margin="10 10" Click="GitHub">GitHub</Button>
<Button Width="75" HorizontalAlignment="Left" Margin="10 10" Click="Discord">Discord</Button> <Button Width="75" HorizontalAlignment="Left" Margin="10 10" Click="Discord">Discord</Button>
</StackPanel> </StackPanel>
<TextBlock Margin="0 0 0 20">
Having issues? Check out the log files.
Using the configuration files is recommended
for advanced users only.</TextBlock >
<StackPanel Margin="0 0 0 20" HorizontalAlignment="Left" Orientation="Horizontal">
<Button Width="75" HorizontalAlignment="Right" Margin="10 10" Click="OpenLogDirectory">Log files</Button>
<Button Width="75" HorizontalAlignment="Left" Margin="10 10" Click="OpenConfigDirectory">Config files</Button>
</StackPanel>
</StackPanel> </StackPanel>
</StackPanel> </StackPanel>
</UserControl> </UserControl>

@ -11,28 +11,31 @@ using UserInterface.ViewModels;
using System.Security; using System.Security;
using hass_workstation_service.Communication.InterProcesCommunication.Models; using hass_workstation_service.Communication.InterProcesCommunication.Models;
using UserInterface.Util; using UserInterface.Util;
using System;
using System.IO;
using System.Diagnostics;
namespace UserInterface.Views namespace UserInterface.Views
{ {
public class AppInfo : UserControl public class AppInfo : UserControl
{ {
private readonly IIpcClient<ServiceContractInterfaces> client; private readonly IIpcClient<IServiceContractInterfaces> client;
private readonly string _basePath;
public AppInfo() public AppInfo()
{ {
this.InitializeComponent(); this.InitializeComponent();
// register IPC clients // register IPC clients
ServiceProvider serviceProvider = new ServiceCollection() ServiceProvider serviceProvider = new ServiceCollection()
.AddNamedPipeIpcClient<ServiceContractInterfaces>("info", pipeName: "pipeinternal") .AddNamedPipeIpcClient<IServiceContractInterfaces>("info", pipeName: "pipeinternal")
.BuildServiceProvider(); .BuildServiceProvider();
// resolve IPC client factory // resolve IPC client factory
IIpcClientFactory<ServiceContractInterfaces> clientFactory = serviceProvider IIpcClientFactory<IServiceContractInterfaces> clientFactory = serviceProvider
.GetRequiredService<IIpcClientFactory<ServiceContractInterfaces>>(); .GetRequiredService<IIpcClientFactory<IServiceContractInterfaces>>();
// create client // create client
this.client = clientFactory.CreateClient("info"); this.client = clientFactory.CreateClient("info");
this._basePath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "Hass Workstation Service");
@ -63,6 +66,17 @@ namespace UserInterface.Views
BrowserUtil.OpenBrowser("https://discord.gg/VraYT2N3wd"); BrowserUtil.OpenBrowser("https://discord.gg/VraYT2N3wd");
} }
public void OpenLogDirectory(object sender, RoutedEventArgs args)
{
string path = Path.Combine(this._basePath, "logs");
Process.Start("explorer.exe", path);
}
public void OpenConfigDirectory(object sender, RoutedEventArgs args)
{
Process.Start("explorer.exe", this._basePath);
}
private void InitializeComponent() private void InitializeComponent()
{ {
AvaloniaXamlLoader.Load(this); AvaloniaXamlLoader.Load(this);

@ -15,49 +15,50 @@ namespace UserInterface.Views
{ {
public class BackgroundServiceSettings : UserControl public class BackgroundServiceSettings : UserControl
{ {
private readonly IIpcClient<ServiceContractInterfaces> client; private readonly IIpcClient<IServiceContractInterfaces> _client;
public BackgroundServiceSettings() public BackgroundServiceSettings()
{ {
this.InitializeComponent(); this.InitializeComponent();
// register IPC clients // register IPC clients
ServiceProvider serviceProvider = new ServiceCollection() ServiceProvider serviceProvider = new ServiceCollection()
.AddNamedPipeIpcClient<ServiceContractInterfaces>("broker", pipeName: "pipeinternal") .AddNamedPipeIpcClient<IServiceContractInterfaces>("broker", pipeName: "pipeinternal")
.BuildServiceProvider(); .BuildServiceProvider();
// resolve IPC client factory // resolve IPC client factory
IIpcClientFactory<ServiceContractInterfaces> clientFactory = serviceProvider IIpcClientFactory<IServiceContractInterfaces> clientFactory = serviceProvider
.GetRequiredService<IIpcClientFactory<ServiceContractInterfaces>>(); .GetRequiredService<IIpcClientFactory<IServiceContractInterfaces>>();
// create client // create client
this.client = clientFactory.CreateClient("broker"); this._client = clientFactory.CreateClient("broker");
DataContext = new BackgroundServiceSettingsViewModel(); DataContext = new BackgroundServiceSettingsViewModel();
Ping(); Ping();
} }
public async void Ping() {
public async void Ping()
{
while (true) while (true)
{ {
if (DataContext is not BackgroundServiceSettingsViewModel viewModel)
throw new System.Exception("Wrong viewmodel class!");
try try
{ {
var result = await this.client.InvokeAsync(x => x.Ping("ping")); var result = await this._client.InvokeAsync(x => x.Ping("ping"));
if (result == "pong") if (result == "pong")
{ viewModel.UpdateStatus(true, "All good");
((BackgroundServiceSettingsViewModel)this.DataContext).UpdateStatus(true, "All good");
}
else else
{ viewModel.UpdateStatus(false, "Not running");
((BackgroundServiceSettingsViewModel)this.DataContext).UpdateStatus(false, "Not running");
}
} }
catch (System.Exception) catch (System.Exception)
{ {
((BackgroundServiceSettingsViewModel)this.DataContext).UpdateStatus(false, "Not running"); viewModel.UpdateStatus(false, "Not running");
} }
var autostartresult = await this.client.InvokeAsync(x => x.IsAutoStartEnabled()); var autostartresult = await this._client.InvokeAsync(x => x.IsAutoStartEnabled());
((BackgroundServiceSettingsViewModel)this.DataContext).UpdateAutostartStatus(autostartresult); viewModel.UpdateAutostartStatus(autostartresult);
await Task.Delay(1000); await Task.Delay(1000);
} }
@ -71,11 +72,12 @@ namespace UserInterface.Views
public void EnableAutostart(object sender, RoutedEventArgs args) public void EnableAutostart(object sender, RoutedEventArgs args)
{ {
this.client.InvokeAsync(x => x.EnableAutostart(true)); this._client.InvokeAsync(x => x.EnableAutostart(true));
} }
public void DisableAutostart(object sender, RoutedEventArgs args) public void DisableAutostart(object sender, RoutedEventArgs args)
{ {
this.client.InvokeAsync(x => x.EnableAutostart(false)); this._client.InvokeAsync(x => x.EnableAutostart(false));
} }
private void InitializeComponent() private void InitializeComponent()

@ -17,19 +17,19 @@ namespace UserInterface.Views
{ {
public class BrokerSettings : UserControl public class BrokerSettings : UserControl
{ {
private readonly IIpcClient<ServiceContractInterfaces> client; private readonly IIpcClient<IServiceContractInterfaces> client;
public BrokerSettings() public BrokerSettings()
{ {
this.InitializeComponent(); this.InitializeComponent();
// register IPC clients // register IPC clients
ServiceProvider serviceProvider = new ServiceCollection() ServiceProvider serviceProvider = new ServiceCollection()
.AddNamedPipeIpcClient<ServiceContractInterfaces>("broker", pipeName: "pipeinternal") .AddNamedPipeIpcClient<IServiceContractInterfaces>("broker", pipeName: "pipeinternal")
.BuildServiceProvider(); .BuildServiceProvider();
// resolve IPC client factory // resolve IPC client factory
IIpcClientFactory<ServiceContractInterfaces> clientFactory = serviceProvider IIpcClientFactory<IServiceContractInterfaces> clientFactory = serviceProvider
.GetRequiredService<IIpcClientFactory<ServiceContractInterfaces>>(); .GetRequiredService<IIpcClientFactory<IServiceContractInterfaces>>();
// create client // create client
this.client = clientFactory.CreateClient("broker"); this.client = clientFactory.CreateClient("broker");

@ -3,24 +3,27 @@
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
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"
MaxWidth="800" x:Class="UserInterface.Views.CommandSettings">
x:Class="UserInterface.Views.CommandSettings" >
<StackPanel Margin="30" HorizontalAlignment="Left" >
<ContentControl FontSize="18" Margin="0 0 0 15" FontWeight="Bold">Commands</ContentControl>
<DataGrid x:Name="Grid" IsVisible="{Binding ConfiguredCommands.Count}" AutoGenerateColumns="False" IsReadOnly="True" SelectionMode="Single" Items="{Binding ConfiguredCommands}">
<DataGrid.Columns> <Grid RowDefinitions="Auto, *, Auto, Auto" Margin="30 30 30 10" >
<DataGridTextColumn Header="Name" <TextBlock Grid.Row="0" Margin="0 0 0 15" FontSize="18" FontWeight="Bold" Text="Commands"/>
Binding="{Binding Name}" <DataGrid Grid.Row="1" x:Name="Grid" IsVisible="{Binding ConfiguredCommands.Count}"
Width="1*" /> AutoGenerateColumns="False" IsReadOnly="True" SelectionMode="Single"
<DataGridTextColumn Header="Type" Items="{Binding ConfiguredCommands}">
Binding="{Binding Type}" <DataGrid.Columns>
Width="1*" /> <DataGridTextColumn Header="Name"
</DataGrid.Columns> Binding="{Binding Name}"
</DataGrid> Width="1*" />
<TextBlock IsVisible="{Binding !ConfiguredCommands.Count}">Add some commands by clicking the "Add" button. </TextBlock> <DataGridTextColumn Header="Type"
Binding="{Binding Type}"
<Button Width="75" HorizontalAlignment="Right" Margin="0 40 0 10" Click="Add">Add</Button> Width="1*" />
<Button Width="75" HorizontalAlignment="Right" Margin="0 40 0 10" Click="Delete">Delete</Button> </DataGrid.Columns>
</StackPanel> </DataGrid>
<TextBlock Grid.Row="2" IsVisible="{Binding !ConfiguredCommands.Count}" Text="Add some commands by clicking the 'Add' button."/>
<StackPanel Grid.Row="3" Orientation="Horizontal" HorizontalAlignment="Right">
<Button Width="75" Margin="10 10 0 0" Click="AddCommand" Content="Add"/>
<Button Width="75" Margin="10 10 0 0" Click="EditCommand" Content="Edit"/>
<Button Width="75" Margin="10 10 0 0" Click="DeleteCommand" Content="Delete"/>
</StackPanel>
</Grid>
</UserControl> </UserControl>

@ -4,79 +4,105 @@ using Avalonia.Markup.Xaml;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using hass_workstation_service.Communication.NamedPipe; using hass_workstation_service.Communication.NamedPipe;
using JKang.IpcServiceFramework.Client; using JKang.IpcServiceFramework.Client;
using System.Threading.Tasks;
using Avalonia.Interactivity; using Avalonia.Interactivity;
using System.Reactive.Linq; using System.Reactive.Linq;
using UserInterface.ViewModels; using UserInterface.ViewModels;
using System.Security;
using hass_workstation_service.Communication.InterProcesCommunication.Models; using hass_workstation_service.Communication.InterProcesCommunication.Models;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using Avalonia.Controls.ApplicationLifetimes; using Avalonia.Controls.ApplicationLifetimes;
using System.Threading.Tasks;
namespace UserInterface.Views namespace UserInterface.Views
{ {
public class CommandSettings : UserControl public class CommandSettings : UserControl
{ {
private readonly IIpcClient<ServiceContractInterfaces> client; private readonly IIpcClient<IServiceContractInterfaces> _client;
private DataGrid _dataGrid { get; set; } private readonly DataGrid _dataGrid;
private bool sensorsNeedToRefresh { get; set; } private bool _commandsNeedToRefresh;
public CommandSettings() public CommandSettings()
{ {
this.InitializeComponent(); InitializeComponent();
// register IPC clients // register IPC clients
ServiceProvider serviceProvider = new ServiceCollection() ServiceProvider serviceProvider = new ServiceCollection()
.AddNamedPipeIpcClient<ServiceContractInterfaces>("commands", pipeName: "pipeinternal") .AddNamedPipeIpcClient<IServiceContractInterfaces>("commands", pipeName: "pipeinternal")
.BuildServiceProvider(); .BuildServiceProvider();
// resolve IPC client factory // resolve IPC client factory
IIpcClientFactory<ServiceContractInterfaces> clientFactory = serviceProvider IIpcClientFactory<IServiceContractInterfaces> clientFactory = serviceProvider
.GetRequiredService<IIpcClientFactory<ServiceContractInterfaces>>(); .GetRequiredService<IIpcClientFactory<IServiceContractInterfaces>>();
// create client // create client
this.client = clientFactory.CreateClient("commands"); _client = clientFactory.CreateClient("commands");
_dataGrid = this.FindControl<DataGrid>("Grid");
DataContext = new CommandSettingsViewModel(); DataContext = new CommandSettingsViewModel();
GetConfiguredCommands(); GetConfiguredCommands();
this._dataGrid = this.FindControl<DataGrid>("Grid");
} }
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
public async void GetConfiguredCommands() public async void GetConfiguredCommands()
{ {
sensorsNeedToRefresh = false; List<ConfiguredCommandModel> status = await _client.InvokeAsync(x => x.GetConfiguredCommands());
List<ConfiguredCommandModel> status = await this.client.InvokeAsync(x => x.GetConfiguredCommands());
((CommandSettingsViewModel)this.DataContext).ConfiguredCommands = status.Select(s => new CommandViewModel() { Name = s.Name, Type = s.Type, Id = s.Id}).ToList(); ((CommandSettingsViewModel)DataContext).ConfiguredCommands = status.Select(s =>
new CommandViewModel()
{
Name = s.Name,
Type = s.Type,
Id = s.Id
}).ToList();
if (_commandsNeedToRefresh)
{
await Task.Delay(1000);
GetConfiguredCommands();
_commandsNeedToRefresh = false;
}
} }
public void Delete(object sender, RoutedEventArgs args)
public async void AddCommand(object sender, RoutedEventArgs args)
{ {
var item = ((CommandViewModel)this._dataGrid.SelectedItem); var dialog = new AddCommandDialog();
this.client.InvokeAsync(x => x.RemoveCommandById(item.Id)); if (Application.Current.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
((CommandSettingsViewModel)this.DataContext).ConfiguredCommands.Remove(item); {
this._dataGrid.SelectedIndex = -1; await dialog.ShowDialog(desktop.MainWindow);
((CommandSettingsViewModel)this.DataContext).TriggerUpdate(); GetConfiguredCommands();
}
} }
public async void Add(object sender, RoutedEventArgs args) public async void EditCommand(object sender, RoutedEventArgs args)
{ {
AddCommandDialog dialog = new AddCommandDialog(); if (_dataGrid.SelectedItem is not CommandViewModel item)
return;
var dialog = new AddCommandDialog(item.Id);
if (Application.Current.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) if (Application.Current.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
{ {
await dialog.ShowDialog(desktop.MainWindow); await dialog.ShowDialog(desktop.MainWindow);
sensorsNeedToRefresh = true; _commandsNeedToRefresh = true;
GetConfiguredCommands(); GetConfiguredCommands();
} }
} }
private void InitializeComponent() public void DeleteCommand(object sender, RoutedEventArgs args)
{ {
AvaloniaXamlLoader.Load(this); if (_dataGrid.SelectedItem is not CommandViewModel item)
} return;
_client.InvokeAsync(x => x.RemoveCommandById(item.Id));
if (DataContext is not CommandSettingsViewModel viewModel)
return;
viewModel.ConfiguredCommands.Remove(item);
_dataGrid.SelectedIndex = -1;
viewModel.TriggerUpdate();
}
} }
} }

@ -0,0 +1,27 @@
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" d:DesignWidth="500" d:DesignHeight="450"
x:Class="UserInterface.Views.GeneralSettingsView">
<StackPanel Margin="30" HorizontalAlignment="Left">
<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>
[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>
</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>
</StackPanel>
</UserControl>

@ -0,0 +1,66 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
using Microsoft.Extensions.DependencyInjection;
using hass_workstation_service.Communication.NamedPipe;
using JKang.IpcServiceFramework.Client;
using System.Threading.Tasks;
using Avalonia.Interactivity;
using System.Reactive.Linq;
using UserInterface.ViewModels;
using System.Security;
using hass_workstation_service.Communication.InterProcesCommunication.Models;
using System.ComponentModel.DataAnnotations;
using System.Collections.Generic;
using hass_workstation_service.Data;
namespace UserInterface.Views
{
public class GeneralSettingsView : UserControl
{
private readonly IIpcClient<IServiceContractInterfaces> client;
public GeneralSettingsView()
{
this.InitializeComponent();
// register IPC clients
ServiceProvider serviceProvider = new ServiceCollection()
.AddNamedPipeIpcClient<IServiceContractInterfaces>("general", pipeName: "pipeinternal")
.BuildServiceProvider();
// resolve IPC client factory
IIpcClientFactory<IServiceContractInterfaces> clientFactory = serviceProvider
.GetRequiredService<IIpcClientFactory<IServiceContractInterfaces>>();
// create client
this.client = clientFactory.CreateClient("general");
DataContext = new GeneralSettingsViewModel();
GetSettings();
}
public void Configure(object sender, RoutedEventArgs args)
{
var model = (GeneralSettingsViewModel)this.DataContext;
ICollection<ValidationResult> results;
if (model.IsValid(model, out results))
{
var result = this.client.InvokeAsync(x => x.WriteGeneralSettings(new GeneralSettings() { NamePrefix = model.NamePrefix }));
}
}
public async void GetSettings()
{
GeneralSettings settings = await this.client.InvokeAsync(x => x.GetGeneralSettings());
((GeneralSettingsViewModel)this.DataContext).Update(settings);
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
}
}

@ -4,23 +4,24 @@
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:views="clr-namespace:UserInterface.Views" xmlns:views="clr-namespace:UserInterface.Views"
mc:Ignorable="d" d:DesignWidth="400" d:DesignHeight="450" mc:Ignorable="d" d:DesignWidth="700" d:DesignHeight="500"
x:Class="UserInterface.Views.MainWindow" x:Class="UserInterface.Views.MainWindow"
Icon="/Assets/hass-workstation-logo.ico" Icon="/Assets/hass-workstation-logo.ico"
SizeToContent="WidthAndHeight" MinWidth="700"
MinHeight="500"
Title="Settings"> Title="Settings">
<Design.DataContext>
<vm:MainWindowViewModel/>
</Design.DataContext>
<ScrollViewer Height="700" VerticalScrollBarVisibility="Visible">
<Grid ColumnDefinitions="Auto,1*,Auto" RowDefinitions="Auto,Auto,Auto" Margin="30" VerticalAlignment="Stretch">
<views:BrokerSettings Grid.Column="0" Grid.Row="0" Margin="10 0" Grid.RowSpan="2" Background="#2D2D30"/>
<views:SensorSettings Grid.Column="1" Grid.Row="0" Margin="10 0" Background="#2D2D30"/>
<views:CommandSettings Grid.Column="1" Grid.Row="1" Margin="0 10 0 0" Background="#2D2D30"/>
<views:BackgroundServiceSettings Grid.Column="2" Grid.Row="0" Margin="10 0" Background="#2D2D30"/>
<views:AppInfo Grid.Column="2" Grid.Row="1" Margin="10 10 10 0" Background="#2D2D30"/>
</Grid>
</ScrollViewer>
<Design.DataContext>
<vm:MainWindowViewModel/>
</Design.DataContext>
<Grid ColumnDefinitions="Auto, *, Auto" RowDefinitions="*, *" Margin="10">
<views:BrokerSettings Grid.Column="0" Grid.Row="0" Margin="10" Grid.RowSpan="2" 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"/>
</Grid>
</Window> </Window>

@ -11,7 +11,7 @@ namespace UserInterface.Views
{ {
InitializeComponent(); InitializeComponent();
WindowsTrayIcon icon = new WindowsTrayIcon(); _ = new WindowsTrayIcon();
} }
private void InitializeComponent() private void InitializeComponent()

@ -3,30 +3,33 @@
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
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"
MaxWidth="800" x:Class="UserInterface.Views.SensorSettings">
x:Class="UserInterface.Views.SensorSettings" >
<StackPanel Margin="30" HorizontalAlignment="Left" >
<ContentControl FontSize="18" Margin="0 0 0 15" FontWeight="Bold">Sensors</ContentControl>
<DataGrid x:Name="Grid" IsVisible="{Binding ConfiguredSensors.Count}" AutoGenerateColumns="False" IsReadOnly="True" SelectionMode="Single" Items="{Binding ConfiguredSensors}">
<DataGrid.Columns> <Grid RowDefinitions="Auto, *, Auto, Auto" Margin="30 30 30 10" >
<DataGridTextColumn Header="Name" <TextBlock Grid.Row="0" Margin="0 0 0 15" FontSize="18" FontWeight="Bold" Text="Sensors"/>
Binding="{Binding Name}" <DataGrid Grid.Row="1" x:Name="Grid" IsVisible="{Binding ConfiguredSensors.Count}"
Width="1*" /> AutoGenerateColumns="False" IsReadOnly="True" SelectionMode="Single"
<DataGridTextColumn Header="Type" Items="{Binding ConfiguredSensors}">
Binding="{Binding Type}" <DataGrid.Columns>
Width="1*" /> <DataGridTextColumn Header="Name"
<DataGridTextColumn Header="Update Interval" Binding="{Binding Name}"
Binding="{Binding UpdateInterval}" Width="1*" />
Width="1*" /> <DataGridTextColumn Header="Type"
<DataGridTextColumn Header="Value" Binding="{Binding Type}"
Binding="{Binding ValueString}" Width="1*" />
Width="2*" /> <DataGridTextColumn Header="Update Interval"
</DataGrid.Columns> Binding="{Binding UpdateInterval}"
</DataGrid> Width="1*" />
<TextBlock IsVisible="{Binding !ConfiguredSensors.Count}">Add some sensors by clicking the "Add" button. </TextBlock> <DataGridTextColumn Header="Value"
Binding="{Binding ValueString}"
<Button Width="75" HorizontalAlignment="Right" Margin="0 40 0 10" Click="AddSensor">Add</Button> Width="2*" />
<Button Width="75" HorizontalAlignment="Right" Margin="0 40 0 10" Click="Delete">Delete</Button> </DataGrid.Columns>
</StackPanel> </DataGrid>
<TextBlock Grid.Row="2" IsVisible="{Binding !ConfiguredSensors.Count}" Text="Add some sensors by clicking the 'Add' button."/>
<StackPanel Grid.Row="3" Orientation="Horizontal" HorizontalAlignment="Right">
<Button Width="75" Margin="10 10 0 0" Click="AddSensor" Content="Add"/>
<Button Width="75" Margin="10 10 0 0" Click="EditSensor" Content="Edit"/>
<Button Width="75" Margin="10 10 0 0" Click="DeleteSensor" Content="Delete"/>
</StackPanel>
</Grid>
</UserControl> </UserControl>

@ -8,7 +8,6 @@ using System.Threading.Tasks;
using Avalonia.Interactivity; using Avalonia.Interactivity;
using System.Reactive.Linq; using System.Reactive.Linq;
using UserInterface.ViewModels; using UserInterface.ViewModels;
using System.Security;
using hass_workstation_service.Communication.InterProcesCommunication.Models; using hass_workstation_service.Communication.InterProcesCommunication.Models;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
@ -18,47 +17,60 @@ namespace UserInterface.Views
{ {
public class SensorSettings : UserControl public class SensorSettings : UserControl
{ {
private readonly IIpcClient<ServiceContractInterfaces> client; private readonly IIpcClient<IServiceContractInterfaces> _client;
private DataGrid _dataGrid { get; set; } private readonly DataGrid _dataGrid;
private bool sensorsNeedToRefresh { get; set; } private bool _sensorsNeedToRefresh;
public SensorSettings() public SensorSettings()
{ {
this.InitializeComponent(); InitializeComponent();
// register IPC clients // register IPC clients
ServiceProvider serviceProvider = new ServiceCollection() ServiceProvider serviceProvider = new ServiceCollection()
.AddNamedPipeIpcClient<ServiceContractInterfaces>("sensors", pipeName: "pipeinternal") .AddNamedPipeIpcClient<IServiceContractInterfaces>("sensors", pipeName: "pipeinternal")
.BuildServiceProvider(); .BuildServiceProvider();
// resolve IPC client factory // resolve IPC client factory
IIpcClientFactory<ServiceContractInterfaces> clientFactory = serviceProvider IIpcClientFactory<IServiceContractInterfaces> clientFactory = serviceProvider
.GetRequiredService<IIpcClientFactory<ServiceContractInterfaces>>(); .GetRequiredService<IIpcClientFactory<IServiceContractInterfaces>>();
// create client // create client
this.client = clientFactory.CreateClient("sensors"); _client = clientFactory.CreateClient("sensors");
_dataGrid = this.FindControl<DataGrid>("Grid");
DataContext = new SensorSettingsViewModel(); DataContext = new SensorSettingsViewModel();
GetConfiguredSensors(); GetConfiguredSensors();
this._dataGrid = this.FindControl<DataGrid>("Grid");
} }
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
public async void GetConfiguredSensors() public async void GetConfiguredSensors()
{ {
sensorsNeedToRefresh = false; _sensorsNeedToRefresh = false;
List<ConfiguredSensorModel> status = await this.client.InvokeAsync(x => x.GetConfiguredSensors()); List<ConfiguredSensorModel> status = await _client.InvokeAsync(x => x.GetConfiguredSensors());
((SensorSettingsViewModel)this.DataContext).ConfiguredSensors = status.Select(s => new SensorViewModel() { Name = s.Name, Type = s.Type, Value = s.Value, Id = s.Id, UpdateInterval = s.UpdateInterval, UnitOfMeasurement = s.UnitOfMeasurement }).ToList(); ((SensorSettingsViewModel)DataContext).ConfiguredSensors = status.Select(s =>
while (!sensorsNeedToRefresh) new SensorViewModel()
{
Name = s.Name,
Type = s.Type,
Value = s.Value,
Id = s.Id,
UpdateInterval = s.UpdateInterval,
UnitOfMeasurement = s.UnitOfMeasurement
}).ToList();
while (!_sensorsNeedToRefresh)
{ {
await Task.Delay(1000); await Task.Delay(1000);
List<ConfiguredSensorModel> statusUpdated = await this.client.InvokeAsync(x => x.GetConfiguredSensors()); List<ConfiguredSensorModel> statusUpdated = await _client.InvokeAsync(x => x.GetConfiguredSensors());
var configuredSensors = ((SensorSettingsViewModel)this.DataContext).ConfiguredSensors; var configuredSensors = ((SensorSettingsViewModel)DataContext).ConfiguredSensors;
// this is a workaround for the list showing before it has been completely loaded in the service // this is a workaround for the list showing before it has been completely loaded in the service
if (statusUpdated.Count != configuredSensors.Count) { if (statusUpdated.Count != configuredSensors.Count)
sensorsNeedToRefresh = true; {
_sensorsNeedToRefresh = true;
GetConfiguredSensors(); GetConfiguredSensors();
} }
statusUpdated.ForEach(s => statusUpdated.ForEach(s =>
@ -72,33 +84,46 @@ namespace UserInterface.Views
} }
}); });
} }
}
public void Delete(object sender, RoutedEventArgs args)
{
var item = ((SensorViewModel)this._dataGrid.SelectedItem);
this.client.InvokeAsync(x => x.RemoveSensorById(item.Id));
((SensorSettingsViewModel)this.DataContext).ConfiguredSensors.Remove(item);
this._dataGrid.SelectedIndex = -1;
((SensorSettingsViewModel)this.DataContext).TriggerUpdate();
} }
public async void AddSensor(object sender, RoutedEventArgs args) public async void AddSensor(object sender, RoutedEventArgs args)
{ {
AddSensorDialog dialog = new AddSensorDialog(); var dialog = new AddSensorDialog();
if (Application.Current.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) if (Application.Current.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
{ {
await dialog.ShowDialog(desktop.MainWindow); await dialog.ShowDialog(desktop.MainWindow);
sensorsNeedToRefresh = true; _sensorsNeedToRefresh = true;
GetConfiguredSensors(); GetConfiguredSensors();
} }
} }
private void InitializeComponent() public async void EditSensor(object sender, RoutedEventArgs args)
{ {
AvaloniaXamlLoader.Load(this); if (_dataGrid.SelectedItem is not SensorViewModel item)
return;
var dialog = new AddSensorDialog(item.Id);
if (Application.Current.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
{
await dialog.ShowDialog(desktop.MainWindow);
_sensorsNeedToRefresh = true;
GetConfiguredSensors();
}
} }
public void DeleteSensor(object sender, RoutedEventArgs args)
{
if (_dataGrid.SelectedItem is not SensorViewModel item)
return;
_client.InvokeAsync(x => x.RemoveSensorById(item.Id));
if (DataContext is not SensorSettingsViewModel viewModel)
return;
viewModel.ConfiguredSensors.Remove(item);
_dataGrid.SelectedIndex = -1;
viewModel.TriggerUpdate();
}
} }
} }

@ -0,0 +1,47 @@
# Commands
Commands can be used to trigger certain things on the client. For each command, a switch will be available in Home Assistant. Turning on the switch fires the command on the client and it will turn the switch off when it's done. Turning it off will cancel the running command.
### ShutdownCommand
This command shuts down the computer immediately. It runs `shutdown /s`.
### RestartCommand
This command restarts the computer immediately. It runs `shutdown /r`.
### HibernateCommand
This command hibernates the computer immediately. It runs `shutdown /h`.
### LogOffCommand
This command logs off the current user. It runs `shutdown /l`.
### CustomCommand
This command allows you to run any Windows Commands. The command will be run in a hidden Command Prompt. Some examples:
|Command|Explanation|
|---|---|
|shutdown /s /f /t 000|Forcefully shutdown the PC immediately.|
|Rundll32.exe user32.dll,LockWorkStation|This locks the current session.|
|shutdown /s /t 300|Shuts the PC down after 5 minutes (300 seconds).|
|C:\path\to\your\batchfile.bat|Run the specified batch file.|
### KeyCommand
Sends a keystroke with the specified key. You can pick [any of these](https://docs.microsoft.com/en-us/windows/win32/inputdev/virtual-key-codes) key codes.
### Media Commands
There's several media commands available that allow you to control media playback. You can combine them into a media player entity as shown [here](https://pastebin.com/1VdL5iQm).
|Command|use|
|---|---|
|Play/Pause|The same as pressing the play/pause media key|
|Next|skip to next track|
|Previous|skip to previous track|
|Volume up|Increase system master volume|
|Volume down|Decrease system master volume|
|Mute (toggle)|Mute the system|

@ -0,0 +1,90 @@
# Sensors
Sensors are used to transfer data about the host system to an automation hub, where it can be processed. `hass-workstation-service` provides many sensors, and they are lisited below in the same order as listed in the configuration GUI.
### ActiveWindowSensor
The active window sensor returns the title of the currently selected window, and is the same value shown when hovering over the icon in the windows task bar.
This sensor is commonly used to trigger automations when a specific program is in use, such as pausing audio during a skype call. This sensor can be unreliable when used with applications such as web browsers that update their title based on the current context. You can partially resolve this using regular expressions.
### CPULoadSensor
The CPU load sensor is used to determine the current utilization of the CPU, and returns a percentage value.
### CurrentClockSpeedSensor
The current clock speed sensor returns the base system clock as configured in the bios. **It does not return the current operating frequency of the CPU**
### CurrentVolumeSensor
This sensor returns the current volume of playing audio. **It does not return the master volume.** If you have no sound playing the value will be 0.
### MasterVolumeSensor
This sensor returns the master volume for the currently selected default audio device.
### DummySensor
This sensor produces a random output every second, and is intended to test latency and connectivity.
### GPULoadSensor
This is the same as the [CPULoadSensor](https://github.com/sleevezipper/hass-workstation-service/new/master/documentation#cpuloadsensor), but for the GPU.
### GPUTemperatureSensor
The GPU temperature returns the current operating temperature of the GPU. This sensor is useful for controling external cooling systems such as air conditioning.
### LastActiveSensor
The last active sensor returns the time when the workstation was last active (last keyboard and mouse input). It is useful as a form of presence detection when combined with motion sensors or software such as room assistant, although may not be reliable if used with auto clickers or other macro software commonly used for video game automation.
### LastBootSensor
The last boot sensor returns the time the windows computer booted. It can be used to calculate uptime, if combined with another sensor to detect system shutdowns.
### MemoryUsageSensor
This returns the amount of system memory used as a percentage value, as indicated by the task manager.
### MicrophoneActiveSensor
This is a binary sensor that can be used to detect if the microphone is in use. **It does not return what process is using it**
### NamedWindowSensor
The named window sensor is similar to the [ActiveWindowSensor](https://github.com/sleevezipper/hass-workstation-service/new/master/documentation#activewindowsensor), however it is a binary sensor that returns true if a window with a title matching a pre determined value is detected.
### SessionStateSensor
The session state sensor can be used to detect if someone is logged in. It has the following values :
|State|Explanation|
|---|---|
|Locked|All user sessions are locked.|
|LoggedOff|No users are logged in.|
|InUse|A user is currently logged in.|
|Unknown|Something went wrong while getting the status.|
### UserNotificationState
This sensor watches the UserNotificationState. This is normally used in applications to determine if it is appropriate to send a notification but we can use it to expose this state. Notice that this status does not watch Focus Assist. It has the following possible states:
|State|Explanation|
|---|---|
|NotPresent|A screen saver is displayed, the machine is locked, or a nonactive Fast User Switching session is in progress. |
|Busy|A full-screen application is running or Presentation Settings are applied. Presentation Settings allow a user to put their machine into a state fit for an uninterrupted presentation, such as a set of PowerPoint slides, with a single click.|
|RunningDirect3dFullScreen|A full-screen (exclusive mode) Direct3D application is running.|
|PresentationMode|The user has activated Windows presentation settings to block notifications and pop-up messages.|
|AcceptsNotifications|None of the other states are found, notifications can be freely sent.|
|QuietTime|Introduced in Windows 7. The current user is in "quiet time", which is the first hour after a new user logs into his or her account for the first time. During this time, most notifications should not be sent or shown. This lets a user become accustomed to a new computer system without those distractions. Quiet time also occurs for each user after an operating system upgrade or clean installation.|
|RunningWindowsStoreApp|A Windows Store app is running.|
### WebcamActiveSensor
The webcam active sensor returns the status of the webcam.
### WMIQuerySensor
Please see the specific documentaion page [here](https://github.com/sleevezipper/hass-workstation-service/blob/master/documentation/WMIQuery.md#wmiquerysensor).

@ -0,0 +1,21 @@
# WMIQuerySensor
The WMI query sensor is an advanced sensor that executes a user defined [WMI query](https://docs.microsoft.com/en-us/windows/win32/wmisdk/wmi-and-sql) and exposes the result.
To use the WMI query sensor, you should create a WMI query and paste it in the box. For example, If you wanted to find the current CPU frequency you can use this command:
```sql
SELECT CurrentClockSpeed FROM Win32_Processor
```
which results in `4008` for my PC. Because this query retuens a single value (CPU frequency in MHz), it can be used with the current WMI query sensor implementation.
The command ```sql SELECT * FROM Win32_Processor``` cannot be used because it returns `|64|9|To Be Filled By O.E.M.|3| ... |GAME-PC-2016|8|1|False|False|`, and the current WMI query implementation only supports commands that return a single value.
You can use [WMI Explorer](https://github.com/vinaypamnani/wmie2/tree/v2.0.0.2) to construct a query, or alternatively look at the user submited sensors below:
|Query|Explanation|Submitted by|
|---|---|---|
|`SELECT username FROM Win32_ComputerSystem`|Shows the current user|@grizzlyjere|
|`Select * from Win32_Process Where Name = 'notepad.exe'`|Shows if the defined process is running|@lafferlaffer|

@ -23,7 +23,7 @@ We can execute the shutdown command, and turn off the media center PC.
## Reading sensor data ## Reading sensor data
Reading sensor data can be done by subscribing to an MQTT topic. For example, if I wanted to know the CPU load, i can subscribe to this topic : Reading sensor data can be done by subscribing to an MQTT topic. For example, if I wanted to know the CPU load, I can subscribe to this topic :
``` ```
homeassistant/sensor/DESKTOP-1/MediaCenterPC/State homeassistant/sensor/DESKTOP-1/MediaCenterPC/State
``` ```

@ -1,25 +1,31 @@
using hass_workstation_service.Communication.InterProcesCommunication.Models; using hass_workstation_service.Communication.InterProcesCommunication.Models;
using hass_workstation_service.Data;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace hass_workstation_service.Communication.NamedPipe namespace hass_workstation_service.Communication.NamedPipe
{ {
public interface ServiceContractInterfaces public interface IServiceContractInterfaces
{ {
Task<MqttSettings> GetMqttBrokerSettings(); Task<MqttSettings> GetMqttBrokerSettings();
Task<GeneralSettings> GetGeneralSettings();
void WriteGeneralSettings(GeneralSettings settings);
public string Ping(string str); public string Ping(string str);
void WriteMqttBrokerSettingsAsync(MqttSettings settings); void WriteMqttBrokerSettingsAsync(MqttSettings settings);
MqqtClientStatus GetMqqtClientStatus(); MqqtClientStatus GetMqqtClientStatus();
void EnableAutostart(bool enable); void EnableAutostart(bool enable);
bool IsAutoStartEnabled(); bool IsAutoStartEnabled();
Task<ConfiguredSensorModel> GetConfiguredSensor(Guid id);
Task<List<ConfiguredSensorModel>> GetConfiguredSensors(); Task<List<ConfiguredSensorModel>> GetConfiguredSensors();
void RemoveSensorById(Guid id);
void AddSensor(AvailableSensors sensorType, string json); void AddSensor(AvailableSensors sensorType, string json);
void RemoveCommandById(Guid id); void RemoveSensorById(Guid id);
void UpdateSensorById(Guid id, string json);
ConfiguredCommandModel GetConfiguredCommand(Guid id);
List<ConfiguredCommandModel> GetConfiguredCommands(); List<ConfiguredCommandModel> GetConfiguredCommands();
void AddCommand(AvailableCommands commandType, string json); void AddCommand(AvailableCommands commandType, string json);
void RemoveCommandById(Guid id);
void UpdateCommandById(Guid id, string json);
string GetCurrentVersion(); string GetCurrentVersion();
} }
} }

@ -7,15 +7,13 @@ using hass_workstation_service.Domain.Sensors;
using Serilog; using Serilog;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Dynamic;
using System.Linq; using System.Linq;
using System.Text;
using System.Text.Json; using System.Text.Json;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace hass_workstation_service.Communication.InterProcesCommunication namespace hass_workstation_service.Communication.InterProcesCommunication
{ {
public class InterProcessApi : ServiceContractInterfaces public class InterProcessApi : IServiceContractInterfaces
{ {
private readonly MqttPublisher _publisher; private readonly MqttPublisher _publisher;
private readonly IConfigurationService _configurationService; private readonly IConfigurationService _configurationService;
@ -26,73 +24,89 @@ namespace hass_workstation_service.Communication.InterProcesCommunication
_configurationService = configurationService; _configurationService = configurationService;
} }
public MqqtClientStatus GetMqqtClientStatus() public MqqtClientStatus GetMqqtClientStatus() => _publisher.GetStatus();
{
return this._publisher.GetStatus();
}
public Task<MqttSettings> GetMqttBrokerSettings() public Task<MqttSettings> GetMqttBrokerSettings() => _configurationService.GetMqttBrokerSettings();
{
return this._configurationService.GetMqttBrokerSettings();
}
/// <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) public string Ping(string str) => str == "ping" ? "pong" : "what?";
{
if (str == "ping") public string GetCurrentVersion() => Program.GetVersion();
{
return "pong";
}
return "what?";
}
/// <summary> /// <summary>
/// This writes the provided settings to the config file. /// This writes the provided settings to the config file.
/// </summary> /// </summary>
/// <param name="settings"></param> /// <param name="settings"></param>
public void WriteMqttBrokerSettingsAsync(MqttSettings settings) public void WriteMqttBrokerSettingsAsync(MqttSettings settings) => _configurationService.WriteMqttBrokerSettingsAsync(settings);
{
this._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) public void EnableAutostart(bool enable) => _configurationService.EnableAutoStart(enable);
{
this._configurationService.EnableAutoStart(enable);
}
public bool IsAutoStartEnabled() public bool IsAutoStartEnabled() => _configurationService.IsAutoStartEnabled();
{
return this._configurationService.IsAutoStartEnabled();
}
public async Task<List<ConfiguredSensorModel>> GetConfiguredSensors() public async Task<List<ConfiguredSensorModel>> GetConfiguredSensors()
{ {
var sensors = await this._configurationService.GetSensorsAfterLoadingAsync(); var sensors = await _configurationService.GetSensorsAfterLoadingAsync();
return sensors.Select(s => new ConfiguredSensorModel() { Name = s.Name, Type = s.GetType().Name, Value = s.PreviousPublishedState, Id = s.Id, UpdateInterval = s.UpdateInterval, UnitOfMeasurement = ((SensorDiscoveryConfigModel)s.GetAutoDiscoveryConfig()).Unit_of_measurement }).ToList(); return sensors.Select(s =>
{
if (!Enum.TryParse(s.GetType().Name, out AvailableSensors type))
Log.Logger.Error("Unknown sensor");
return new ConfiguredSensorModel(s);
}).ToList();
} }
public List<ConfiguredCommandModel> GetConfiguredCommands() public async Task<ConfiguredSensorModel> GetConfiguredSensor(Guid id)
{ {
return this._configurationService.ConfiguredCommands.Select(s => new ConfiguredCommandModel() { Name = s.Name, Type = s.GetType().Name, Id = s.Id }).ToList(); var sensors = await _configurationService.GetSensorsAfterLoadingAsync();
var s = sensors.FirstOrDefault(x => id == x.Id);
if (s == null)
return null;
else
{
if (!Enum.TryParse(s.GetType().Name, out AvailableSensors type))
Log.Logger.Error("Unknown sensor");
return new ConfiguredSensorModel(s);
}
} }
public void RemoveCommandById(Guid id)
public List<ConfiguredCommandModel> GetConfiguredCommands()
{ {
this._configurationService.DeleteConfiguredCommand(id); return _configurationService.ConfiguredCommands.Select(c =>
{
if (!Enum.TryParse(c.GetType().Name, out AvailableCommands type))
Log.Logger.Error("Unknown command");
return new ConfiguredCommandModel(c);
}).ToList();
} }
public void RemoveSensorById(Guid id) public ConfiguredCommandModel GetConfiguredCommand(Guid id)
{ {
this._configurationService.DeleteConfiguredSensor(id); var c = _configurationService.ConfiguredCommands.FirstOrDefault(x => id == x.Id);
if (c == null)
return null;
else
{
if (!Enum.TryParse(c.GetType().Name, out AvailableCommands type))
Log.Logger.Error("Unknown command");
return new ConfiguredCommandModel(c);
}
} }
public void RemoveSensorById(Guid id) => _configurationService.DeleteConfiguredSensor(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>
@ -100,71 +114,12 @@ namespace hass_workstation_service.Communication.InterProcesCommunication
/// <param name="json"></param> /// <param name="json"></param>
public void AddSensor(AvailableSensors sensorType, string json) public void AddSensor(AvailableSensors sensorType, string json)
{ {
var serializerOptions = new JsonSerializerOptions var sensorToCreate = GetSensorToCreate(sensorType, json);
{
Converters = { new DynamicJsonConverter() }
};
dynamic model = JsonSerializer.Deserialize<dynamic>(json, serializerOptions);
AbstractSensor sensorToCreate = null; if (sensorToCreate == null)
switch (sensorType) Log.Logger.Error("Unknown sensortype");
{ else
case AvailableSensors.UserNotificationStateSensor: _configurationService.AddConfiguredSensor(sensorToCreate);
sensorToCreate = new UserNotificationStateSensor(this._publisher, (int)model.UpdateInterval, model.Name);
break;
case AvailableSensors.DummySensor:
sensorToCreate = new DummySensor(this._publisher, (int)model.UpdateInterval, model.Name);
break;
case AvailableSensors.CurrentClockSpeedSensor:
sensorToCreate = new CurrentClockSpeedSensor(this._publisher, (int)model.UpdateInterval, model.Name);
break;
case AvailableSensors.CPULoadSensor:
sensorToCreate = new CPULoadSensor(this._publisher, (int)model.UpdateInterval, model.Name);
break;
case AvailableSensors.WMIQuerySensor:
sensorToCreate = new WMIQuerySensor(this._publisher, model.Query, (int)model.UpdateInterval, model.Name);
break;
case AvailableSensors.MemoryUsageSensor:
sensorToCreate = new MemoryUsageSensor(this._publisher, (int)model.UpdateInterval, model.Name);
break;
case AvailableSensors.ActiveWindowSensor:
sensorToCreate = new ActiveWindowSensor(this._publisher, (int)model.UpdateInterval, model.Name);
break;
case AvailableSensors.WebcamActiveSensor:
sensorToCreate = new WebcamActiveSensor(this._publisher, (int)model.UpdateInterval, model.Name);
break;
case AvailableSensors.MicrophoneActiveSensor:
sensorToCreate = new MicrophoneActiveSensor(this._publisher, (int)model.UpdateInterval, model.Name);
break;
case AvailableSensors.NamedWindowSensor:
sensorToCreate = new NamedWindowSensor(this._publisher, model.WindowName, model.Name, (int)model.UpdateInterval);
break;
case AvailableSensors.LastActiveSensor:
sensorToCreate = new LastActiveSensor(this._publisher,(int)model.UpdateInterval, model.Name);
break;
case AvailableSensors.LastBootSensor:
sensorToCreate = new LastBootSensor(this._publisher, (int)model.UpdateInterval, model.Name);
break;
case AvailableSensors.SessionStateSensor:
sensorToCreate = new SessionStateSensor(this._publisher, (int)model.UpdateInterval, model.Name);
break;
case AvailableSensors.CurrentVolumeSensor:
sensorToCreate = new CurrentVolumeSensor(this._publisher, (int)model.UpdateInterval, model.Name);
break;
case AvailableSensors.GPUTemperatureSensor:
sensorToCreate = new GpuTemperatureSensor(this._publisher, (int)model.UpdateInterval, model.Name);
break;
case AvailableSensors.GPULoadSensor:
sensorToCreate = new GpuLoadSensor(this._publisher, (int)model.UpdateInterval, model.Name);
break;
default:
Log.Logger.Error("Unknown sensortype");
break;
}
if (sensorToCreate != null)
{
this._configurationService.AddConfiguredSensor(sensorToCreate);
}
} }
/// <summary> /// <summary>
@ -173,6 +128,38 @@ namespace hass_workstation_service.Communication.InterProcesCommunication
/// <param name="commandType"></param> /// <param name="commandType"></param>
/// <param name="json"></param> /// <param name="json"></param>
public void AddCommand(AvailableCommands commandType, string json) public void AddCommand(AvailableCommands commandType, string json)
{
var commandToCreate = GetCommandToCreate(commandType, json);
if (commandToCreate == null)
Log.Logger.Error("Unknown command type");
else
_configurationService.AddConfiguredCommand(commandToCreate);
}
public async void UpdateSensorById(Guid id, string json)
{
var existingSensor = await GetConfiguredSensor(id);
var sensorToUpdate = GetSensorToCreate(existingSensor.Type, json);
if (sensorToUpdate == null)
Log.Logger.Error("Unknown sensortype");
else
_configurationService.UpdateConfiguredSensor(id, sensorToUpdate);
}
public void UpdateCommandById(Guid id, string json)
{
var existingCommand = GetConfiguredCommand(id);
var commandToUpdate = GetCommandToCreate(existingCommand.Type, json);
if (commandToUpdate == null)
Log.Logger.Error("Unknown commandtype");
else
_configurationService.UpdateConfiguredCommand(id, commandToUpdate);
}
private AbstractSensor GetSensorToCreate(AvailableSensors sensorType, string json)
{ {
var serializerOptions = new JsonSerializerOptions var serializerOptions = new JsonSerializerOptions
{ {
@ -180,55 +167,58 @@ namespace hass_workstation_service.Communication.InterProcesCommunication
}; };
dynamic model = JsonSerializer.Deserialize<dynamic>(json, serializerOptions); dynamic model = JsonSerializer.Deserialize<dynamic>(json, serializerOptions);
AbstractCommand commandToCreate = null; return sensorType switch
switch (commandType)
{
case AvailableCommands.ShutdownCommand:
commandToCreate = new ShutdownCommand(this._publisher, model.Name);
break;
case AvailableCommands.RestartCommand:
commandToCreate = new RestartCommand(this._publisher, model.Name);
break;
case AvailableCommands.LogOffCommand:
commandToCreate = new LogOffCommand(this._publisher, model.Name);
break;
case AvailableCommands.CustomCommand:
commandToCreate = new CustomCommand(this._publisher, model.Command, model.Name);
break;
case AvailableCommands.PlayPauseCommand:
commandToCreate = new MediaPlayPauseCommand(this._publisher, model.Name);
break;
case AvailableCommands.NextCommand:
commandToCreate = new MediaNextCommand(this._publisher, model.Name);
break;
case AvailableCommands.PreviousCommand:
commandToCreate = new MediaPreviousCommand(this._publisher, model.Name);
break;
case AvailableCommands.VolumeUpCommand:
commandToCreate = new MediaVolumeUpCommand(this._publisher, model.Name);
break;
case AvailableCommands.VolumeDownCommand:
commandToCreate = new MediaVolumeDownCommand(this._publisher, model.Name);
break;
case AvailableCommands.MuteCommand:
commandToCreate = new MediaMuteCommand(this._publisher, model.Name);
break;
case AvailableCommands.KeyCommand:
commandToCreate = new KeyCommand(this._publisher, Convert.ToByte(model.Key, 16), model.Name);
break;
default:
Log.Logger.Error("Unknown sensortype");
break;
}
if (commandToCreate != null)
{ {
this._configurationService.AddConfiguredCommand(commandToCreate); AvailableSensors.UserNotificationStateSensor => new UserNotificationStateSensor(_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.CPULoadSensor => new CPULoadSensor(_publisher, (int)model.UpdateInterval, model.Name),
AvailableSensors.WMIQuerySensor => new WMIQuerySensor(_publisher, model.Query, (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.WebcamActiveSensor => new WebcamActiveSensor(_publisher, (int)model.UpdateInterval, model.Name),
AvailableSensors.MicrophoneActiveSensor => new MicrophoneActiveSensor(_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),
AvailableSensors.SessionStateSensor => new SessionStateSensor(_publisher, (int)model.UpdateInterval, model.Name),
AvailableSensors.CurrentVolumeSensor => new CurrentVolumeSensor(_publisher, (int)model.UpdateInterval, model.Name),
AvailableSensors.GPUTemperatureSensor => new GpuTemperatureSensor(_publisher, (int)model.UpdateInterval, model.Name),
AvailableSensors.GPULoadSensor => new GpuLoadSensor(_publisher, (int)model.UpdateInterval, model.Name),
AvailableSensors.MasterVolumeSensor => new MasterVolumeSensor(_publisher, (int)model.UpdateInterval, model.Name),
_ => null
};
} }
public string GetCurrentVersion() private AbstractCommand GetCommandToCreate(AvailableCommands commandType, string json)
{ {
return Program.GetVersion(); var serializerOptions = new JsonSerializerOptions
{
Converters = { new DynamicJsonConverter() }
};
dynamic model = JsonSerializer.Deserialize<dynamic>(json, serializerOptions);
return commandType switch
{
AvailableCommands.ShutdownCommand => new ShutdownCommand(_publisher, model.Name),
AvailableCommands.RestartCommand => new RestartCommand(_publisher, model.Name),
AvailableCommands.HibernateCommand => new HibernateCommand(_publisher, model.Name),
AvailableCommands.LogOffCommand => new LogOffCommand(_publisher, model.Name),
AvailableCommands.CustomCommand => new CustomCommand(_publisher, model.Command, model.Name),
AvailableCommands.PlayPauseCommand => new PlayPauseCommand(_publisher, model.Name),
AvailableCommands.NextCommand => new NextCommand(_publisher, model.Name),
AvailableCommands.PreviousCommand => new PreviousCommand(_publisher, model.Name),
AvailableCommands.VolumeUpCommand => new VolumeUpCommand(_publisher, model.Name),
AvailableCommands.VolumeDownCommand => new VolumeDownCommand(_publisher, model.Name),
AvailableCommands.MuteCommand => new MuteCommand(_publisher, model.Name),
AvailableCommands.KeyCommand => new KeyCommand(_publisher, Convert.ToByte(model.Key, 16), model.Name),
_ => null
};
} }
public Task<GeneralSettings> GetGeneralSettings() => _configurationService.ReadGeneralSettings();
public void WriteGeneralSettings(GeneralSettings settings) => _configurationService.WriteGeneralSettingsAsync(settings);
} }
} }

@ -1,7 +1,6 @@
using hass_workstation_service.Domain.Commands;
using hass_workstation_service.Domain.Sensors; using hass_workstation_service.Domain.Sensors;
using System; using System;
using System.Collections.Generic;
using System.Text;
namespace hass_workstation_service.Communication.InterProcesCommunication.Models namespace hass_workstation_service.Communication.InterProcesCommunication.Models
{ {
@ -23,19 +22,68 @@ namespace hass_workstation_service.Communication.InterProcesCommunication.Models
public class ConfiguredSensorModel public class ConfiguredSensorModel
{ {
public Guid Id { get; set; } public Guid Id { get; set; }
public string Type { get; set; } public AvailableSensors Type { get; set; }
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 WindowName { get; set; }
public int UpdateInterval { get; set; } public int UpdateInterval { get; set; }
public string UnitOfMeasurement { get; set; } public string UnitOfMeasurement { get; set; }
public ConfiguredSensorModel(AbstractSensor sensor)
{
this.Id = sensor.Id;
this.Name = sensor.Name;
Enum.TryParse(sensor.GetType().Name, out AvailableSensors type);
this.Type = type;
this.Value = sensor.PreviousPublishedState;
if (sensor is WMIQuerySensor wMIQuerySensor)
{
this.Query = wMIQuerySensor.Query;
}
if (sensor is NamedWindowSensor namedWindowSensor)
{
this.WindowName = namedWindowSensor.WindowName;
}
this.UpdateInterval = sensor.UpdateInterval;
this.UnitOfMeasurement = ((SensorDiscoveryConfigModel)sensor.GetAutoDiscoveryConfig()).Unit_of_measurement;
}
public ConfiguredSensorModel()
{
}
} }
public class ConfiguredCommandModel public class ConfiguredCommandModel
{ {
public Guid Id { get; set; } public Guid Id { get; set; }
public string Type { get; set; } public AvailableCommands Type { get; set; }
public string Name { get; set; } public string Name { get; set; }
public string Command { get; set; } public string Command { get; set; }
public string Key { get; set; }
public ConfiguredCommandModel(AbstractCommand command)
{
this.Id = command.Id;
Enum.TryParse(command.GetType().Name, out AvailableCommands type);
this.Type = type;
this.Name = command.Name;
if (command is CustomCommand customCommand)
{
this.Command = customCommand.Command;
}
if (command is KeyCommand keyCommand)
{
this.Key = "0x" + Convert.ToString(keyCommand.KeyCode, 16);
}
}
public ConfiguredCommandModel()
{
}
} }
public enum AvailableSensors public enum AvailableSensors
{ {
UserNotificationStateSensor, UserNotificationStateSensor,
@ -52,6 +100,7 @@ namespace hass_workstation_service.Communication.InterProcesCommunication.Models
LastBootSensor, LastBootSensor,
SessionStateSensor, SessionStateSensor,
CurrentVolumeSensor, CurrentVolumeSensor,
MasterVolumeSensor,
GPUTemperatureSensor, GPUTemperatureSensor,
GPULoadSensor GPULoadSensor
} }
@ -62,6 +111,7 @@ namespace hass_workstation_service.Communication.InterProcesCommunication.Models
ShutdownCommand, ShutdownCommand,
LogOffCommand, LogOffCommand,
RestartCommand, RestartCommand,
HibernateCommand,
KeyCommand, KeyCommand,
PlayPauseCommand, PlayPauseCommand,
NextCommand, NextCommand,

@ -31,6 +31,7 @@ namespace hass_workstation_service.Communication
public DateTime LastAvailabilityAnnounce { get; private set; } public DateTime LastAvailabilityAnnounce { get; private set; }
public DeviceConfigModel DeviceConfigModel { get; private set; } public DeviceConfigModel DeviceConfigModel { get; private set; }
public ICollection<AbstractCommand> Subscribers { get; private set; } public ICollection<AbstractCommand> Subscribers { get; private set; }
public string NamePrefix { get; private set; }
public bool IsConnected public bool IsConnected
{ {
get get
@ -55,9 +56,11 @@ namespace hass_workstation_service.Communication
this._logger = logger; this._logger = logger;
this.DeviceConfigModel = deviceConfigModel; this.DeviceConfigModel = deviceConfigModel;
this._configurationService = configurationService; this._configurationService = configurationService;
this.NamePrefix = configurationService.GeneralSettings?.NamePrefix;
var options = _configurationService.GetMqttClientOptionsAsync().Result; var options = _configurationService.GetMqttClientOptionsAsync().Result;
_configurationService.MqqtConfigChangedHandler = this.ReplaceMqttClient; _configurationService.MqqtConfigChangedHandler = this.ReplaceMqttClient;
_configurationService.NamePrefixChangedHandler = this.UpdateNamePrefix;
var factory = new MqttFactory(); var factory = new MqttFactory();
this._mqttClient = factory.CreateManagedMqttClient(); this._mqttClient = factory.CreateManagedMqttClient();
@ -96,9 +99,9 @@ namespace hass_workstation_service.Communication
this._logger.LogInformation($"Message dropped because mqtt not connected: {message}"); this._logger.LogInformation($"Message dropped because mqtt not connected: {message}");
} }
} }
// TODO: This should take a sensor/command instead of a config.
// Then we can ask the sensor about the topic based on ObjectId instead of referencing Name directly
public async Task AnnounceAutoDiscoveryConfig(AbstractDiscoverable discoverable, string domain, bool clearConfig = false) public async Task AnnounceAutoDiscoveryConfig(AbstractDiscoverable discoverable, bool clearConfig = false)
{ {
if (this._mqttClient.IsConnected) if (this._mqttClient.IsConnected)
{ {
@ -111,11 +114,21 @@ namespace hass_workstation_service.Communication
}; };
var message = new MqttApplicationMessageBuilder() var message = new MqttApplicationMessageBuilder()
.WithTopic($"homeassistant/{domain}/{this.DeviceConfigModel.Name}/{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)
{
var stateMessage = new MqttApplicationMessageBuilder()
.WithTopic($"homeassistant/{discoverable.Domain}/{this.DeviceConfigModel.Name}/{DiscoveryConfigModel.GetNameWithPrefix(discoverable.GetAutoDiscoveryConfig().NamePrefix, discoverable.ObjectId)}/state")
.WithPayload("")
.WithRetainFlag()
.Build();
await this.Publish(stateMessage);
}
LastConfigAnnounce = DateTime.UtcNow; LastConfigAnnounce = DateTime.UtcNow;
} }
} }
@ -195,6 +208,11 @@ namespace hass_workstation_service.Communication
Subscribers.Add(command); Subscribers.Add(command);
} }
public void UpdateNamePrefix(string prefix)
{
this.NamePrefix = prefix;
}
private void HandleMessageReceived(MqttApplicationMessage applicationMessage) private void HandleMessageReceived(MqttApplicationMessage applicationMessage)
{ {
foreach (AbstractCommand command in this.Subscribers) foreach (AbstractCommand command in this.Subscribers)

@ -1,5 +1,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Text.Json.Serialization;
namespace hass_workstation_service.Communication namespace hass_workstation_service.Communication
{ {
@ -14,12 +15,49 @@ namespace hass_workstation_service.Communication
/// (Optional) The name of the MQTT sensor. Defaults to MQTT Sensor in hass. /// (Optional) The name of the MQTT sensor. Defaults to MQTT Sensor in hass.
/// </summary> /// </summary>
/// <value></value> /// <value></value>
[JsonIgnore]
public string Name { get; set; } public string Name { get; set; }
/// <summary>
/// (Optional) The first part of the name.
/// </summary>
/// <value></value>
[JsonIgnore]
public string NamePrefix { get; set; }
[JsonPropertyName("name")]
public string CompiledName { get => GetName(); }
/// <summary> /// <summary>
/// The MQTT topic subscribed to receive sensor values. /// The MQTT topic subscribed to receive sensor values.
/// </summary> /// </summary>
/// <value></value> /// <value></value>
public string State_topic { get; set; } public string State_topic { get; set; }
/// <summary>
/// Gets the name including the prefix
/// </summary>
/// <returns></returns>
public string GetName()
{
if (string.IsNullOrWhiteSpace(NamePrefix))
{
return Name;
}
return $"{NamePrefix}-{Name}";
}
/// <summary>
/// Gets the name including the prefix if the class has not been instantiated yet.
/// </summary>
/// <returns></returns>
public static string GetNameWithPrefix(string namePrefix, string name)
{
if (string.IsNullOrWhiteSpace(namePrefix))
{
return name;
}
return $"{namePrefix}-{name}";
}
} }
public class SensorDiscoveryConfigModel : DiscoveryConfigModel public class SensorDiscoveryConfigModel : DiscoveryConfigModel
{ {

@ -26,36 +26,49 @@ namespace hass_workstation_service.Data
{ {
public ICollection<AbstractSensor> ConfiguredSensors { get; private set; } public ICollection<AbstractSensor> ConfiguredSensors { get; private set; }
public ICollection<AbstractCommand> ConfiguredCommands { get; private set; } public ICollection<AbstractCommand> ConfiguredCommands { get; private set; }
public GeneralSettings GeneralSettings { get; private set; }
public Action<IManagedMqttClientOptions> MqqtConfigChangedHandler { get; set; } public Action<IManagedMqttClientOptions> MqqtConfigChangedHandler { get; set; }
public Action<string> NamePrefixChangedHandler { get; set; }
private readonly DeviceConfigModel _deviceConfigModel; private readonly DeviceConfigModel _deviceConfigModel;
private bool BrokerSettingsFileLocked { get; set; } private bool BrokerSettingsFileLocked { get; set; }
private bool SensorsSettingsFileLocked { get; set; } private bool SensorsSettingsFileLocked { get; set; }
private bool CommandSettingsFileLocked { get; set; } private bool CommandSettingsFileLocked { get; set; }
private bool GeneralSettingsFileLocked { get; set; }
private bool _sensorsLoading { get; set; } private bool _sensorsLoading { get; set; }
private readonly string path = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "Hass Workstation Service"); private readonly string path = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "Hass Workstation Service");
private const string MQTT_SETTINGS_FILENAME = "mqttbroker.json";
private const string SENSORS_SETTINGS_FILENAME = "configured-sensors.json";
private const string COMMANDS_SETTINGS_FILENAME = "configured-commands.json";
private const string GENERAL_SETTINGS_FILENAME = "general-settings.json";
public ConfigurationService(DeviceConfigModel deviceConfigModel) public ConfigurationService(DeviceConfigModel deviceConfigModel)
{ {
this._deviceConfigModel = deviceConfigModel; this._deviceConfigModel = deviceConfigModel;
if (!File.Exists(Path.Combine(path, "mqttbroker.json"))) if (!File.Exists(Path.Combine(path, MQTT_SETTINGS_FILENAME)))
{ {
File.Create(Path.Combine(path, "mqttbroker.json")).Close(); File.Create(Path.Combine(path, MQTT_SETTINGS_FILENAME)).Close();
} }
if (!File.Exists(Path.Combine(path, "configured-sensors.json"))) if (!File.Exists(Path.Combine(path, SENSORS_SETTINGS_FILENAME)))
{ {
File.Create(Path.Combine(path, "configured-sensors.json")).Close(); File.Create(Path.Combine(path, SENSORS_SETTINGS_FILENAME)).Close();
} }
if (!File.Exists(Path.Combine(path, "configured-commands.json"))) if (!File.Exists(Path.Combine(path, COMMANDS_SETTINGS_FILENAME)))
{
File.Create(Path.Combine(path, COMMANDS_SETTINGS_FILENAME)).Close();
}
if (!File.Exists(Path.Combine(path, GENERAL_SETTINGS_FILENAME)))
{ {
File.Create(Path.Combine(path, "configured-commands.json")).Close(); File.Create(Path.Combine(path, GENERAL_SETTINGS_FILENAME)).Close();
} }
ConfiguredSensors = new List<AbstractSensor>(); ConfiguredSensors = new List<AbstractSensor>();
ConfiguredCommands = new List<AbstractCommand>(); ConfiguredCommands = new List<AbstractCommand>();
this.ReadGeneralSettings();
} }
public async void ReadSensorSettings(MqttPublisher publisher) public async void ReadSensorSettings(MqttPublisher publisher)
@ -67,7 +80,7 @@ namespace hass_workstation_service.Data
} }
this.SensorsSettingsFileLocked = true; this.SensorsSettingsFileLocked = true;
List<ConfiguredSensor> sensors = new List<ConfiguredSensor>(); List<ConfiguredSensor> sensors = new List<ConfiguredSensor>();
using (var stream = new FileStream(Path.Combine(path, "configured-sensors.json"), FileMode.Open)) using (var stream = new FileStream(Path.Combine(path, SENSORS_SETTINGS_FILENAME), FileMode.Open))
{ {
Log.Logger.Information($"reading configured sensors from: {stream.Name}"); Log.Logger.Information($"reading configured sensors from: {stream.Name}");
if (stream.Length > 0) if (stream.Length > 0)
@ -125,6 +138,9 @@ namespace hass_workstation_service.Data
case "CurrentVolumeSensor": case "CurrentVolumeSensor":
sensor = new CurrentVolumeSensor(publisher, configuredSensor.UpdateInterval, configuredSensor.Name, configuredSensor.Id); sensor = new CurrentVolumeSensor(publisher, configuredSensor.UpdateInterval, configuredSensor.Name, configuredSensor.Id);
break; break;
case "MasterVolumeSensor":
sensor = new MasterVolumeSensor(publisher, configuredSensor.UpdateInterval, configuredSensor.Name, configuredSensor.Id);
break;
case "GpuTemperatureSensor": case "GpuTemperatureSensor":
sensor = new GpuTemperatureSensor(publisher, configuredSensor.UpdateInterval, configuredSensor.Name, configuredSensor.Id); sensor = new GpuTemperatureSensor(publisher, configuredSensor.UpdateInterval, configuredSensor.Name, configuredSensor.Id);
break; break;
@ -155,7 +171,7 @@ namespace hass_workstation_service.Data
} }
this.CommandSettingsFileLocked = true; this.CommandSettingsFileLocked = true;
List<ConfiguredCommand> commands = new List<ConfiguredCommand>(); List<ConfiguredCommand> commands = new List<ConfiguredCommand>();
using (var stream = new FileStream(Path.Combine(path, "configured-commands.json"), FileMode.Open)) using (var stream = new FileStream(Path.Combine(path, COMMANDS_SETTINGS_FILENAME), FileMode.Open))
{ {
Log.Logger.Information($"reading configured commands from: {stream.Name}"); Log.Logger.Information($"reading configured commands from: {stream.Name}");
if (stream.Length > 0) if (stream.Length > 0)
@ -177,29 +193,32 @@ namespace hass_workstation_service.Data
case "RestartCommand": case "RestartCommand":
command = new RestartCommand(publisher, configuredCommand.Name, configuredCommand.Id); command = new RestartCommand(publisher, configuredCommand.Name, configuredCommand.Id);
break; break;
case "HibernateCommand":
command = new HibernateCommand(publisher, configuredCommand.Name, configuredCommand.Id);
break;
case "LogOffCommand": case "LogOffCommand":
command = new LogOffCommand(publisher, configuredCommand.Name, configuredCommand.Id); command = new LogOffCommand(publisher, configuredCommand.Name, configuredCommand.Id);
break; break;
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 "MediaPlayPauseCommand":
command = new MediaPlayPauseCommand(publisher, configuredCommand.Name, configuredCommand.Id); command = new PlayPauseCommand(publisher, configuredCommand.Name, configuredCommand.Id);
break; break;
case "MediaNextCommand": case "MediaNextCommand":
command = new MediaNextCommand(publisher, configuredCommand.Name, configuredCommand.Id); command = new NextCommand(publisher, configuredCommand.Name, configuredCommand.Id);
break; break;
case "MediaPreviousCommand": case "MediaPreviousCommand":
command = new MediaPreviousCommand(publisher, configuredCommand.Name, configuredCommand.Id); command = new PreviousCommand(publisher, configuredCommand.Name, configuredCommand.Id);
break; break;
case "MediaVolumeUpCommand": case "MediaVolumeUpCommand":
command = new MediaVolumeUpCommand(publisher, configuredCommand.Name, configuredCommand.Id); command = new VolumeUpCommand(publisher, configuredCommand.Name, configuredCommand.Id);
break; break;
case "MediaVolumeDownCommand": case "MediaVolumeDownCommand":
command = new MediaVolumeDownCommand(publisher, configuredCommand.Name, configuredCommand.Id); command = new VolumeDownCommand(publisher, configuredCommand.Name, configuredCommand.Id);
break; break;
case "MediaMuteCommand": case "MediaMuteCommand":
command = new MediaMuteCommand(publisher, configuredCommand.Name, configuredCommand.Id); command = new MuteCommand(publisher, configuredCommand.Name, configuredCommand.Id);
break; break;
case "KeyCommand": case "KeyCommand":
command = new KeyCommand(publisher, configuredCommand.KeyCode, configuredCommand.Name, configuredCommand.Id); command = new KeyCommand(publisher, configuredCommand.KeyCode, configuredCommand.Name, configuredCommand.Id);
@ -215,6 +234,70 @@ namespace hass_workstation_service.Data
} }
} }
public async Task<GeneralSettings> ReadGeneralSettings()
{
while (this.GeneralSettingsFileLocked)
{
await Task.Delay(500);
}
this.GeneralSettingsFileLocked = true;
GeneralSettings settings = new GeneralSettings();
using (var stream = new FileStream(Path.Combine(path, GENERAL_SETTINGS_FILENAME), FileMode.Open))
{
Log.Logger.Information($"reading general settings from: {stream.Name}");
if (stream.Length > 0)
{
settings = await JsonSerializer.DeserializeAsync<GeneralSettings>(stream);
}
stream.Close();
this.GeneralSettings = settings;
this.GeneralSettingsFileLocked = false;
return settings;
}
}
/// <summary>
/// Writes provided settings to the config file and reconfigures all sensors and commands if the nameprefix changed
/// </summary>
/// <param name="settings"></param>
public async void WriteGeneralSettingsAsync(GeneralSettings settings)
{
while (this.GeneralSettingsFileLocked)
{
await Task.Delay(500);
}
this.GeneralSettingsFileLocked = true;
using (FileStream stream = new FileStream(Path.Combine(path, GENERAL_SETTINGS_FILENAME), FileMode.Open))
{
stream.SetLength(0);
Log.Logger.Information($"writing general settings to: {stream.Name}");
await JsonSerializer.SerializeAsync(stream, settings);
stream.Close();
}
this.GeneralSettingsFileLocked = false;
// if the nameprefix changed, we need to update all sensors and commands to reflect the new name
if (settings.NamePrefix != this.GeneralSettings.NamePrefix)
{
// notify the mqtt publisher of the new prefix
this.NamePrefixChangedHandler.Invoke(settings.NamePrefix);
foreach (AbstractSensor sensor in this.ConfiguredSensors)
{
await sensor.UnPublishAutoDiscoveryConfigAsync();
sensor.PublishAutoDiscoveryConfigAsync();
}
foreach (AbstractCommand command in this.ConfiguredCommands)
{
await command.UnPublishAutoDiscoveryConfigAsync();
command.PublishAutoDiscoveryConfigAsync();
}
}
}
public async Task<IManagedMqttClientOptions> GetMqttClientOptionsAsync() public async Task<IManagedMqttClientOptions> GetMqttClientOptionsAsync()
{ {
ConfiguredMqttBroker configuredBroker = await ReadMqttSettingsAsync(); ConfiguredMqttBroker configuredBroker = await ReadMqttSettingsAsync();
@ -226,7 +309,8 @@ namespace hass_workstation_service.Data
.WithTls(new MqttClientOptionsBuilderTlsParameters() .WithTls(new MqttClientOptionsBuilderTlsParameters()
{ {
UseTls = configuredBroker.UseTLS, UseTls = configuredBroker.UseTLS,
AllowUntrustedCertificates = true 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))
@ -257,7 +341,7 @@ namespace hass_workstation_service.Data
} }
this.BrokerSettingsFileLocked = true; this.BrokerSettingsFileLocked = true;
ConfiguredMqttBroker configuredBroker = null; ConfiguredMqttBroker configuredBroker = null;
using (FileStream stream = new FileStream(Path.Combine(path, "mqttbroker.json"), FileMode.Open)) using (FileStream stream = new FileStream(Path.Combine(path, MQTT_SETTINGS_FILENAME), FileMode.Open))
{ {
Log.Logger.Information($"reading configured mqttbroker from: {stream.Name}"); Log.Logger.Information($"reading configured mqttbroker from: {stream.Name}");
if (stream.Length > 0) if (stream.Length > 0)
@ -279,7 +363,7 @@ namespace hass_workstation_service.Data
} }
this.SensorsSettingsFileLocked = true; this.SensorsSettingsFileLocked = true;
List<ConfiguredSensor> configuredSensorsToSave = new List<ConfiguredSensor>(); List<ConfiguredSensor> configuredSensorsToSave = new List<ConfiguredSensor>();
using (FileStream stream = new FileStream(Path.Combine(path, "configured-sensors.json"), FileMode.Open)) using (FileStream stream = new FileStream(Path.Combine(path, SENSORS_SETTINGS_FILENAME), FileMode.Open))
{ {
stream.SetLength(0); stream.SetLength(0);
Log.Logger.Information($"writing configured sensors to: {stream.Name}"); Log.Logger.Information($"writing configured sensors to: {stream.Name}");
@ -316,7 +400,7 @@ namespace hass_workstation_service.Data
} }
this.CommandSettingsFileLocked = true; this.CommandSettingsFileLocked = true;
List<ConfiguredCommand> configuredCommandsToSave = new List<ConfiguredCommand>(); List<ConfiguredCommand> configuredCommandsToSave = new List<ConfiguredCommand>();
using (FileStream stream = new FileStream(Path.Combine(path, "configured-commands.json"), FileMode.Open)) using (FileStream stream = new FileStream(Path.Combine(path, COMMANDS_SETTINGS_FILENAME), FileMode.Open))
{ {
stream.SetLength(0); stream.SetLength(0);
Log.Logger.Information($"writing configured commands to: {stream.Name}"); Log.Logger.Information($"writing configured commands to: {stream.Name}");
@ -340,54 +424,96 @@ namespace hass_workstation_service.Data
public void AddConfiguredSensor(AbstractSensor sensor) public void AddConfiguredSensor(AbstractSensor sensor)
{ {
this.ConfiguredSensors.Add(sensor); AddSensor(sensor);
sensor.PublishAutoDiscoveryConfigAsync();
WriteSensorSettingsAsync(); WriteSensorSettingsAsync();
} }
public void AddConfiguredCommand(AbstractCommand command) public void AddConfiguredCommand(AbstractCommand command)
{ {
this.ConfiguredCommands.Add(command); AddCommand(command);
command.PublishAutoDiscoveryConfigAsync();
WriteCommandSettingsAsync(); WriteCommandSettingsAsync();
} }
public async void DeleteConfiguredSensor(Guid id) public void AddConfiguredSensors(List<AbstractSensor> sensors)
{ {
var sensorToRemove = this.ConfiguredSensors.FirstOrDefault(s => s.Id == id); sensors.ForEach(sensor => AddSensor(sensor));
if (sensorToRemove != null) WriteSensorSettingsAsync();
{ }
await sensorToRemove.UnPublishAutoDiscoveryConfigAsync();
this.ConfiguredSensors.Remove(sensorToRemove); public void AddConfiguredCommands(List<AbstractCommand> commands)
WriteSensorSettingsAsync(); {
} commands.ForEach(command => AddCommand(command));
else WriteCommandSettingsAsync();
{ }
Log.Logger.Warning($"sensor with id {id} not found");
}
public async void DeleteConfiguredSensor(Guid id)
{
await DeleteSensor(id);
WriteSensorSettingsAsync();
} }
public async void DeleteConfiguredCommand(Guid id) public async void DeleteConfiguredCommand(Guid id)
{ {
var commandToRemove = this.ConfiguredCommands.FirstOrDefault(s => s.Id == id); await DeleteCommand(id);
if (commandToRemove != null) WriteCommandSettingsAsync();
{ }
await commandToRemove.UnPublishAutoDiscoveryConfigAsync();
this.ConfiguredCommands.Remove(commandToRemove); /// <summary>
WriteCommandSettingsAsync(); ///
} /// </summary>
else /// <param name="id">The Id of the sensor to replace</param>
/// <param name="sensor">The new sensor</param>
public async void UpdateConfiguredSensor(Guid id, AbstractSensor sensor)
{
await DeleteSensor(id);
await Task.Delay(500);
AddSensor(sensor);
WriteSensorSettingsAsync();
}
public async void UpdateConfiguredCommand(Guid id, AbstractCommand command)
{
await DeleteCommand(id);
await Task.Delay(500);
AddCommand(command);
WriteCommandSettingsAsync();
}
private void AddSensor(AbstractSensor sensor)
{
ConfiguredSensors.Add(sensor);
sensor.PublishAutoDiscoveryConfigAsync();
}
private void AddCommand(AbstractCommand command)
{
ConfiguredCommands.Add(command);
command.PublishAutoDiscoveryConfigAsync();
}
private async Task DeleteSensor(Guid id)
{
var sensorToRemove = ConfiguredSensors.FirstOrDefault(s => s.Id == id);
if (sensorToRemove == null)
{ {
Log.Logger.Warning($"command with id {id} not found"); Log.Logger.Warning($"sensor with id {id} not found");
return;
} }
await sensorToRemove.UnPublishAutoDiscoveryConfigAsync();
ConfiguredSensors.Remove(sensorToRemove);
} }
public void AddConfiguredSensors(List<AbstractSensor> sensors) private async Task DeleteCommand(Guid id)
{ {
sensors.ForEach((sensor) => this.ConfiguredSensors.Add(sensor)); var commandToRemove = ConfiguredCommands.FirstOrDefault(c => c.Id == id);
WriteSensorSettingsAsync(); if (commandToRemove == null)
{
Log.Logger.Warning($"command with id {id} not found");
return;
}
await commandToRemove.UnPublishAutoDiscoveryConfigAsync();
ConfiguredCommands.Remove(commandToRemove);
} }
/// <summary> /// <summary>
@ -401,7 +527,7 @@ namespace hass_workstation_service.Data
await Task.Delay(500); await Task.Delay(500);
} }
this.BrokerSettingsFileLocked = true; this.BrokerSettingsFileLocked = true;
using (FileStream stream = new FileStream(Path.Combine(path, "mqttbroker.json"), FileMode.Open)) using (FileStream stream = new FileStream(Path.Combine(path, MQTT_SETTINGS_FILENAME), FileMode.Open))
{ {
stream.SetLength(0); stream.SetLength(0);
Log.Logger.Information($"writing configured mqttbroker to: {stream.Name}"); Log.Logger.Information($"writing configured mqttbroker to: {stream.Name}");

@ -0,0 +1,13 @@
using hass_workstation_service.Domain.Sensors;
using System;
namespace hass_workstation_service.Data
{
public class GeneralSettings
{
/// <summary>
/// If set, all sensor and command names will be be prefixed with this
/// </summary>
public string NamePrefix { get; set; }
}
}

@ -13,13 +13,21 @@ namespace hass_workstation_service.Data
{ {
public interface IConfigurationService public interface IConfigurationService
{ {
ICollection<AbstractSensor> ConfiguredSensors { get; }
Action<IManagedMqttClientOptions> MqqtConfigChangedHandler { get; set; } Action<IManagedMqttClientOptions> MqqtConfigChangedHandler { get; set; }
Action<string> NamePrefixChangedHandler { get; set; }
ICollection<AbstractSensor> ConfiguredSensors { get; }
ICollection<AbstractCommand> ConfiguredCommands { get; } ICollection<AbstractCommand> ConfiguredCommands { get; }
GeneralSettings GeneralSettings { get; }
void AddConfiguredCommand(AbstractCommand command);
void AddConfiguredSensor(AbstractSensor sensor); void AddConfiguredSensor(AbstractSensor sensor);
void AddConfiguredCommand(AbstractCommand command);
void AddConfiguredSensors(List<AbstractSensor> sensors); void AddConfiguredSensors(List<AbstractSensor> sensors);
void AddConfiguredCommands(List<AbstractCommand> commands);
void DeleteConfiguredSensor(Guid id);
void DeleteConfiguredCommand(Guid id);
Task<GeneralSettings> ReadGeneralSettings();
void UpdateConfiguredSensor(Guid id, AbstractSensor sensor);
void UpdateConfiguredCommand(Guid id, AbstractCommand command);
Task<IManagedMqttClientOptions> GetMqttClientOptionsAsync(); Task<IManagedMqttClientOptions> GetMqttClientOptionsAsync();
void ReadSensorSettings(MqttPublisher publisher); void ReadSensorSettings(MqttPublisher publisher);
void WriteMqttBrokerSettingsAsync(MqttSettings settings); void WriteMqttBrokerSettingsAsync(MqttSettings settings);
@ -27,10 +35,9 @@ namespace hass_workstation_service.Data
Task<MqttSettings> GetMqttBrokerSettings(); Task<MqttSettings> GetMqttBrokerSettings();
void EnableAutoStart(bool enable); void EnableAutoStart(bool enable);
bool IsAutoStartEnabled(); bool IsAutoStartEnabled();
void DeleteConfiguredSensor(Guid id);
void DeleteConfiguredCommand(Guid id);
void WriteCommandSettingsAsync(); void WriteCommandSettingsAsync();
void ReadCommandSettings(MqttPublisher publisher); void ReadCommandSettings(MqttPublisher publisher);
Task<ICollection<AbstractSensor>> GetSensorsAfterLoadingAsync(); Task<ICollection<AbstractSensor>> GetSensorsAfterLoadingAsync();
void WriteGeneralSettingsAsync(GeneralSettings settings);
} }
} }

@ -12,16 +12,8 @@ namespace hass_workstation_service.Domain
{ {
public abstract string Domain { get; } public abstract string Domain { get; }
public string Name { get; protected set; } public string Name { get; protected set; }
public string ObjectId => Regex.Replace(Name, "[^a-zA-Z0-9_-]", "_");
public string ObjectId
{
get
{
return Regex.Replace(this.Name, "[^a-zA-Z0-9_-]", "_");
}
}
public Guid Id { get; protected set; } public Guid Id { get; protected set; }
public abstract DiscoveryConfigModel GetAutoDiscoveryConfig(); public abstract DiscoveryConfigModel GetAutoDiscoveryConfig();
} }
} }

@ -5,72 +5,60 @@ using MQTTnet;
namespace hass_workstation_service.Domain.Commands namespace hass_workstation_service.Domain.Commands
{ {
public abstract class AbstractCommand : AbstractDiscoverable public abstract class AbstractCommand : AbstractDiscoverable
{ {
/// <summary> /// <summary>
/// The update interval in seconds. It checks state only if the interval has passed. /// The update interval in seconds. It checks state only if the interval has passed.
/// </summary> /// </summary>
public int UpdateInterval { get => 1; } public static int UpdateInterval { get => 1; }
public DateTime? LastUpdated { get; protected set; } public DateTime? LastUpdated { get; protected set; }
public string PreviousPublishedState { get; protected set; } public string PreviousPublishedState { get; protected set; }
public MqttPublisher Publisher { get; protected set; } public MqttPublisher Publisher { get; protected set; }
public override string Domain { get => "switch"; } public override string Domain { get => "switch"; }
public AbstractCommand(MqttPublisher publisher, string name, Guid id = default(Guid))
{
if (id == Guid.Empty)
{
this.Id = Guid.NewGuid();
}
else
{
this.Id = id;
}
this.Name = name;
this.Publisher = publisher;
publisher.Subscribe(this);
} public AbstractCommand(MqttPublisher publisher, string name, Guid id = default)
protected CommandDiscoveryConfigModel _autoDiscoveryConfigModel;
protected CommandDiscoveryConfigModel SetAutoDiscoveryConfigModel(CommandDiscoveryConfigModel config)
{ {
this._autoDiscoveryConfigModel = config; Id = id == Guid.Empty ? Guid.NewGuid() : id;
return config; Name = name;
Publisher = publisher;
publisher.Subscribe(this);
} }
public abstract string GetState(); public abstract string GetState();
public async Task PublishStateAsync() public async Task PublishStateAsync()
{ {
if (LastUpdated.HasValue && LastUpdated.Value.AddSeconds(this.UpdateInterval) > DateTime.UtcNow) // dont't even check the state if the update interval hasn't passed
{ if (LastUpdated.HasValue && LastUpdated.Value.AddSeconds(UpdateInterval) > DateTime.UtcNow)
// dont't even check the state if the update interval hasn't passed
return; return;
}
string state = this.GetState(); string state = GetState();
if (this.PreviousPublishedState == state) // don't publish the state if it hasn't changed
{ if (PreviousPublishedState == state)
// don't publish the state if it hasn't changed
return; return;
}
var message = new MqttApplicationMessageBuilder() var message = new MqttApplicationMessageBuilder()
.WithTopic(this.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);
this.PreviousPublishedState = state; PreviousPublishedState = state;
this.LastUpdated = DateTime.UtcNow; LastUpdated = DateTime.UtcNow;
} }
public async void PublishAutoDiscoveryConfigAsync()
{ public async void PublishAutoDiscoveryConfigAsync() => await Publisher.AnnounceAutoDiscoveryConfig(this);
await this.Publisher.AnnounceAutoDiscoveryConfig(this, this.Domain);
} public async Task UnPublishAutoDiscoveryConfigAsync() => await Publisher.AnnounceAutoDiscoveryConfig(this, true);
public async Task UnPublishAutoDiscoveryConfigAsync()
protected CommandDiscoveryConfigModel _autoDiscoveryConfigModel;
protected CommandDiscoveryConfigModel SetAutoDiscoveryConfigModel(CommandDiscoveryConfigModel config)
{ {
await this.Publisher.AnnounceAutoDiscoveryConfig(this, this.Domain, true); _autoDiscoveryConfigModel = config;
return config;
} }
public abstract void TurnOn(); public abstract void TurnOn();
public abstract void TurnOff(); public abstract void TurnOff();
} }

@ -30,6 +30,11 @@ namespace hass_workstation_service.Domain.Commands
startInfo.FileName = "cmd.exe"; startInfo.FileName = "cmd.exe";
startInfo.Arguments = $"/C {this.Command}"; startInfo.Arguments = $"/C {this.Command}";
this.Process.StartInfo = startInfo; this.Process.StartInfo = startInfo;
// turn off the sensor to guarantee disable the switch
// useful if command changes power state of device
this.State = "OFF";
try try
{ {
this.Process.Start(); this.Process.Start();
@ -39,12 +44,6 @@ namespace hass_workstation_service.Domain.Commands
Log.Logger.Error($"Sensor {this.Name} failed", e); Log.Logger.Error($"Sensor {this.Name} failed", e);
this.State = "FAILED"; this.State = "FAILED";
} }
while (!this.Process.HasExited)
{
await Task.Delay(1000);
}
this.State = "OFF";
} }
@ -54,10 +53,11 @@ namespace hass_workstation_service.Domain.Commands
return new CommandDiscoveryConfigModel() return new CommandDiscoveryConfigModel()
{ {
Name = this.Name, Name = this.Name,
NamePrefix = Publisher.NamePrefix,
Unique_id = this.Id.ToString(), Unique_id = this.Id.ToString(),
Availability_topic = $"homeassistant/sensor/{Publisher.DeviceConfigModel.Name}/availability", Availability_topic = $"homeassistant/sensor/{Publisher.DeviceConfigModel.Name}/availability",
Command_topic = $"homeassistant/{this.Domain}/{Publisher.DeviceConfigModel.Name}/{this.ObjectId}/set", Command_topic = $"homeassistant/{this.Domain}/{Publisher.DeviceConfigModel.Name}/{Publisher.NamePrefix}{this.ObjectId}/set",
State_topic = $"homeassistant/{this.Domain}/{Publisher.DeviceConfigModel.Name}/{this.ObjectId}/state", State_topic = $"homeassistant/{this.Domain}/{Publisher.DeviceConfigModel.Name}/{DiscoveryConfigModel.GetNameWithPrefix(Publisher.NamePrefix, this.ObjectId)}/state",
Device = this.Publisher.DeviceConfigModel, Device = this.Publisher.DeviceConfigModel,
}; };
} }

@ -0,0 +1,17 @@
using hass_workstation_service.Communication;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace hass_workstation_service.Domain.Commands
{
public class HibernateCommand : CustomCommand
{
public HibernateCommand(MqttPublisher publisher, string name = "Hibernate", Guid id = default(Guid)) : base(publisher, "shutdown /h", name ?? "Hibernate", id)
{
this.State = "OFF";
}
}
}

@ -11,7 +11,7 @@ namespace hass_workstation_service.Domain.Commands
public class KeyCommand : AbstractCommand public class KeyCommand : AbstractCommand
{ {
public const int KEYEVENTF_EXTENTEDKEY = 1; public const int KEYEVENTF_EXTENTEDKEY = 1;
public const int KEYEVENTF_KEYUP = 0; public const int KEYEVENTF_KEYUP = 0x0002;
public const int VK_MEDIA_NEXT_TRACK = 0xB0; public const int VK_MEDIA_NEXT_TRACK = 0xB0;
public const int VK_MEDIA_PLAY_PAUSE = 0xB3; public const int VK_MEDIA_PLAY_PAUSE = 0xB3;
public const int VK_MEDIA_PREV_TRACK = 0xB1; public const int VK_MEDIA_PREV_TRACK = 0xB1;
@ -30,10 +30,11 @@ namespace hass_workstation_service.Domain.Commands
return new CommandDiscoveryConfigModel() return new CommandDiscoveryConfigModel()
{ {
Name = this.Name, Name = this.Name,
NamePrefix = Publisher.NamePrefix,
Unique_id = this.Id.ToString(), Unique_id = this.Id.ToString(),
Availability_topic = $"homeassistant/sensor/{Publisher.DeviceConfigModel.Name}/availability", Availability_topic = $"homeassistant/sensor/{Publisher.DeviceConfigModel.Name}/availability",
Command_topic = $"homeassistant/{this.Domain}/{Publisher.DeviceConfigModel.Name}/{this.ObjectId}/set", Command_topic = $"homeassistant/{this.Domain}/{Publisher.DeviceConfigModel.Name}/{Publisher.NamePrefix}{this.ObjectId}/set",
State_topic = $"homeassistant/{this.Domain}/{Publisher.DeviceConfigModel.Name}/{this.ObjectId}/state", State_topic = $"homeassistant/{this.Domain}/{Publisher.DeviceConfigModel.Name}/{DiscoveryConfigModel.GetNameWithPrefix(Publisher.NamePrefix, this.ObjectId)}/state",
Device = this.Publisher.DeviceConfigModel, Device = this.Publisher.DeviceConfigModel,
}; };
} }
@ -54,7 +55,8 @@ namespace hass_workstation_service.Domain.Commands
public override void TurnOn() public override void TurnOn()
{ {
keybd_event(this.KeyCode, 0, KEYEVENTF_EXTENTEDKEY, IntPtr.Zero); keybd_event(this.KeyCode, 0, 0, IntPtr.Zero);
keybd_event(this.KeyCode, 0, KEYEVENTF_KEYUP, IntPtr.Zero);
} }
} }
} }

@ -1,14 +0,0 @@
using hass_workstation_service.Communication;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace hass_workstation_service.Domain.Commands
{
public class MediaPlayPauseCommand : KeyCommand
{
public MediaPlayPauseCommand(MqttPublisher publisher, string name = "PlayPause", Guid id = default(Guid)) : base(publisher, KeyCommand.VK_MEDIA_PLAY_PAUSE, name ?? "PlayPause", id) { }
}
}

@ -1,14 +0,0 @@
using hass_workstation_service.Communication;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace hass_workstation_service.Domain.Commands
{
public class MediaPreviousCommand : KeyCommand
{
public MediaPreviousCommand(MqttPublisher publisher, string name = "Previous", Guid id = default(Guid)) : base(publisher, KeyCommand.VK_MEDIA_PREV_TRACK, name ?? "Previous", id) { }
}
}

@ -1,14 +0,0 @@
using hass_workstation_service.Communication;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace hass_workstation_service.Domain.Commands
{
public class MediaVolumeDownCommand : KeyCommand
{
public MediaVolumeDownCommand(MqttPublisher publisher, string name = "VolumeDown", Guid id = default(Guid)) : base(publisher, KeyCommand.VK_VOLUME_DOWN, name ?? "VolumeDown", id) { }
}
}

@ -1,14 +0,0 @@
using hass_workstation_service.Communication;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace hass_workstation_service.Domain.Commands
{
public class MediaVolumeUpCommand : KeyCommand
{
public MediaVolumeUpCommand(MqttPublisher publisher, string name = "VolumeUp", Guid id = default(Guid)) : base(publisher, KeyCommand.VK_VOLUME_UP, name ?? "VolumeUp", id) { }
}
}

@ -7,8 +7,8 @@ using System.Threading.Tasks;
namespace hass_workstation_service.Domain.Commands namespace hass_workstation_service.Domain.Commands
{ {
public class MediaMuteCommand : KeyCommand public class MuteCommand : KeyCommand
{ {
public MediaMuteCommand(MqttPublisher publisher, string name = "Mute", Guid id = default(Guid)) : base(publisher, KeyCommand.VK_VOLUME_MUTE, name ?? "Mute", id) { } public MuteCommand(MqttPublisher publisher, string name = "Mute", Guid id = default(Guid)) : base(publisher, KeyCommand.VK_VOLUME_MUTE, name ?? "Mute", id) { }
} }
} }

@ -7,8 +7,8 @@ using System.Threading.Tasks;
namespace hass_workstation_service.Domain.Commands namespace hass_workstation_service.Domain.Commands
{ {
public class MediaNextCommand : KeyCommand public class NextCommand : KeyCommand
{ {
public MediaNextCommand(MqttPublisher publisher, string name = "Next", Guid id = default(Guid)) : base(publisher, KeyCommand.VK_MEDIA_NEXT_TRACK, name ?? "Next", id) { } public NextCommand(MqttPublisher publisher, string name = "Next", Guid id = default(Guid)) : base(publisher, KeyCommand.VK_MEDIA_NEXT_TRACK, name ?? "Next", id) { }
} }
} }

@ -0,0 +1,14 @@
using hass_workstation_service.Communication;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace hass_workstation_service.Domain.Commands
{
public class PlayPauseCommand : KeyCommand
{
public PlayPauseCommand(MqttPublisher publisher, string name = "PlayPause", Guid id = default(Guid)) : base(publisher, KeyCommand.VK_MEDIA_PLAY_PAUSE, name ?? "PlayPause", id) { }
}
}

@ -0,0 +1,14 @@
using hass_workstation_service.Communication;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace hass_workstation_service.Domain.Commands
{
public class PreviousCommand : KeyCommand
{
public PreviousCommand(MqttPublisher publisher, string name = "Previous", Guid id = default(Guid)) : base(publisher, KeyCommand.VK_MEDIA_PREV_TRACK, name ?? "Previous", id) { }
}
}

@ -0,0 +1,14 @@
using hass_workstation_service.Communication;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace hass_workstation_service.Domain.Commands
{
public class VolumeDownCommand : KeyCommand
{
public VolumeDownCommand(MqttPublisher publisher, string name = "VolumeDown", Guid id = default(Guid)) : base(publisher, KeyCommand.VK_VOLUME_DOWN, name ?? "VolumeDown", id) { }
}
}

@ -0,0 +1,14 @@
using hass_workstation_service.Communication;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace hass_workstation_service.Domain.Commands
{
public class VolumeUpCommand : KeyCommand
{
public VolumeUpCommand(MqttPublisher publisher, string name = "VolumeUp", Guid id = default(Guid)) : base(publisher, KeyCommand.VK_VOLUME_UP, name ?? "VolumeUp", id) { }
}
}

@ -1,12 +1,10 @@
using System; using System;
using System.Text.RegularExpressions;
using System.Threading.Tasks; using System.Threading.Tasks;
using hass_workstation_service.Communication; using hass_workstation_service.Communication;
using MQTTnet; using MQTTnet;
namespace hass_workstation_service.Domain.Sensors namespace hass_workstation_service.Domain.Sensors
{ {
public abstract class AbstractSensor : AbstractDiscoverable public abstract class AbstractSensor : AbstractDiscoverable
{ {
/// <summary> /// <summary>
@ -17,61 +15,52 @@ namespace hass_workstation_service.Domain.Sensors
public string PreviousPublishedState { get; protected set; } public string PreviousPublishedState { get; protected set; }
public MqttPublisher Publisher { get; protected set; } public MqttPublisher Publisher { get; protected set; }
public override string Domain { get => "sensor"; } public override string Domain { get => "sensor"; }
public AbstractSensor(MqttPublisher publisher, string name, int updateInterval = 10, Guid id = default(Guid))
{
if (id == Guid.Empty)
{
this.Id = Guid.NewGuid();
}
else
{
this.Id = id;
}
this.Name = name;
this.Publisher = publisher;
this.UpdateInterval = updateInterval;
} public AbstractSensor(MqttPublisher publisher, string name, int updateInterval = 10, Guid id = default)
protected SensorDiscoveryConfigModel _autoDiscoveryConfigModel;
protected SensorDiscoveryConfigModel SetAutoDiscoveryConfigModel(SensorDiscoveryConfigModel config)
{ {
this._autoDiscoveryConfigModel = config; Id = id == Guid.Empty ? Guid.NewGuid() : id;
return config; Name = name;
Publisher = publisher;
UpdateInterval = updateInterval;
} }
public abstract string GetState(); public abstract string GetState();
public async Task PublishStateAsync() public async Task PublishStateAsync()
{ {
if (LastUpdated.HasValue && LastUpdated.Value.AddSeconds(this.UpdateInterval) > DateTime.UtcNow) // dont't even check the state if the update interval hasn't passed
{ if (LastUpdated.HasValue && LastUpdated.Value.AddSeconds(UpdateInterval) > DateTime.UtcNow)
// dont't even check the state if the update interval hasn't passed
return; return;
}
string state = this.GetState(); string state = GetState();
if (this.PreviousPublishedState == state) // don't publish the state if it hasn't changed
{ if (PreviousPublishedState == state)
// don't publish the state if it hasn't changed
return; return;
}
var message = new MqttApplicationMessageBuilder() var message = new MqttApplicationMessageBuilder()
.WithTopic(this.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);
this.PreviousPublishedState = state; PreviousPublishedState = state;
this.LastUpdated = DateTime.UtcNow; LastUpdated = DateTime.UtcNow;
}
public async void PublishAutoDiscoveryConfigAsync()
{
await this.Publisher.AnnounceAutoDiscoveryConfig(this, this.Domain);
} }
public async void PublishAutoDiscoveryConfigAsync() => await Publisher.AnnounceAutoDiscoveryConfig(this);
public async Task UnPublishAutoDiscoveryConfigAsync() public async Task UnPublishAutoDiscoveryConfigAsync()
{ {
await this.Publisher.AnnounceAutoDiscoveryConfig(this, this.Domain, true); await Publisher.AnnounceAutoDiscoveryConfig(this, true);
this._autoDiscoveryConfigModel = null;
} }
protected SensorDiscoveryConfigModel _autoDiscoveryConfigModel;
protected SensorDiscoveryConfigModel SetAutoDiscoveryConfigModel(SensorDiscoveryConfigModel config)
{
_autoDiscoveryConfigModel = config;
return config;
}
} }
} }

@ -14,9 +14,10 @@ namespace hass_workstation_service.Domain.Sensors
return this._autoDiscoveryConfigModel ?? SetAutoDiscoveryConfigModel(new SensorDiscoveryConfigModel() return this._autoDiscoveryConfigModel ?? SetAutoDiscoveryConfigModel(new SensorDiscoveryConfigModel()
{ {
Name = this.Name, Name = this.Name,
NamePrefix = Publisher.NamePrefix,
Unique_id = this.Id.ToString(), Unique_id = this.Id.ToString(),
Device = this.Publisher.DeviceConfigModel, Device = this.Publisher.DeviceConfigModel,
State_topic = $"homeassistant/{this.Domain}/{Publisher.DeviceConfigModel.Name}/{this.ObjectId}/state", State_topic = $"homeassistant/{this.Domain}/{Publisher.DeviceConfigModel.Name}/{DiscoveryConfigModel.GetNameWithPrefix(Publisher.NamePrefix, this.ObjectId)}/state",
Icon = "mdi:window-maximize", Icon = "mdi:window-maximize",
Availability_topic = $"homeassistant/{this.Domain}/{Publisher.DeviceConfigModel.Name}/availability" Availability_topic = $"homeassistant/{this.Domain}/{Publisher.DeviceConfigModel.Name}/availability"
}); });

@ -22,9 +22,10 @@ namespace hass_workstation_service.Domain.Sensors
return this._autoDiscoveryConfigModel ?? SetAutoDiscoveryConfigModel(new SensorDiscoveryConfigModel() return this._autoDiscoveryConfigModel ?? SetAutoDiscoveryConfigModel(new SensorDiscoveryConfigModel()
{ {
Name = this.Name, Name = this.Name,
NamePrefix = Publisher.NamePrefix,
Unique_id = this.Id.ToString(), Unique_id = this.Id.ToString(),
Device = this.Publisher.DeviceConfigModel, Device = this.Publisher.DeviceConfigModel,
State_topic = $"homeassistant/{this.Domain}/{Publisher.DeviceConfigModel.Name}/{this.ObjectId}/state", State_topic = $"homeassistant/{this.Domain}/{Publisher.DeviceConfigModel.Name}/{DiscoveryConfigModel.GetNameWithPrefix(Publisher.NamePrefix, this.ObjectId)}/state",
Icon = "mdi:chart-areaspline", Icon = "mdi:chart-areaspline",
Unit_of_measurement = "%", Unit_of_measurement = "%",
Availability_topic = $"homeassistant/{this.Domain}/{Publisher.DeviceConfigModel.Name}/availability" Availability_topic = $"homeassistant/{this.Domain}/{Publisher.DeviceConfigModel.Name}/availability"

@ -14,9 +14,10 @@ namespace hass_workstation_service.Domain.Sensors
return this._autoDiscoveryConfigModel ?? SetAutoDiscoveryConfigModel(new SensorDiscoveryConfigModel() return this._autoDiscoveryConfigModel ?? SetAutoDiscoveryConfigModel(new SensorDiscoveryConfigModel()
{ {
Name = this.Name, Name = this.Name,
NamePrefix = Publisher.NamePrefix,
Unique_id = this.Id.ToString(), Unique_id = this.Id.ToString(),
Device = this.Publisher.DeviceConfigModel, Device = this.Publisher.DeviceConfigModel,
State_topic = $"homeassistant/{this.Domain}/{Publisher.DeviceConfigModel.Name}/{this.ObjectId}/state", State_topic = $"homeassistant/{this.Domain}/{Publisher.DeviceConfigModel.Name}/{DiscoveryConfigModel.GetNameWithPrefix(Publisher.NamePrefix, this.ObjectId)}/state",
Icon = "mdi:speedometer", Icon = "mdi:speedometer",
Unit_of_measurement = "MHz", Unit_of_measurement = "MHz",
Availability_topic = $"homeassistant/{this.Domain}/{Publisher.DeviceConfigModel.Name}/availability" Availability_topic = $"homeassistant/{this.Domain}/{Publisher.DeviceConfigModel.Name}/availability"

@ -23,9 +23,10 @@ namespace hass_workstation_service.Domain.Sensors
return this._autoDiscoveryConfigModel ?? SetAutoDiscoveryConfigModel(new SensorDiscoveryConfigModel() return this._autoDiscoveryConfigModel ?? SetAutoDiscoveryConfigModel(new SensorDiscoveryConfigModel()
{ {
Name = this.Name, Name = this.Name,
NamePrefix = Publisher.NamePrefix,
Unique_id = this.Id.ToString(), Unique_id = this.Id.ToString(),
Device = this.Publisher.DeviceConfigModel, Device = this.Publisher.DeviceConfigModel,
State_topic = $"homeassistant/{this.Domain}/{Publisher.DeviceConfigModel.Name}/{this.ObjectId}/state", State_topic = $"homeassistant/{this.Domain}/{Publisher.DeviceConfigModel.Name}/{DiscoveryConfigModel.GetNameWithPrefix(Publisher.NamePrefix, this.ObjectId)}/state",
Icon = "mdi:volume-medium", Icon = "mdi:volume-medium",
Unit_of_measurement = "%", Unit_of_measurement = "%",
Availability_topic = $"homeassistant/{this.Domain}/{Publisher.DeviceConfigModel.Name}/availability" Availability_topic = $"homeassistant/{this.Domain}/{Publisher.DeviceConfigModel.Name}/availability"

@ -18,9 +18,10 @@ namespace hass_workstation_service.Domain.Sensors
return this._autoDiscoveryConfigModel ?? SetAutoDiscoveryConfigModel(new SensorDiscoveryConfigModel() return this._autoDiscoveryConfigModel ?? SetAutoDiscoveryConfigModel(new SensorDiscoveryConfigModel()
{ {
Name = this.Name, Name = this.Name,
NamePrefix = Publisher.NamePrefix,
Unique_id = this.Id.ToString(), Unique_id = this.Id.ToString(),
Device = this.Publisher.DeviceConfigModel, Device = this.Publisher.DeviceConfigModel,
State_topic = $"homeassistant/{this.Domain}/{Publisher.DeviceConfigModel.Name}/{this.ObjectId}/state", State_topic = $"homeassistant/{this.Domain}/{Publisher.DeviceConfigModel.Name}/{DiscoveryConfigModel.GetNameWithPrefix(Publisher.NamePrefix, this.ObjectId)}/state",
Availability_topic = $"homeassistant/{this.Domain}/{Publisher.DeviceConfigModel.Name}/availability" Availability_topic = $"homeassistant/{this.Domain}/{Publisher.DeviceConfigModel.Name}/availability"
}); });
} }

@ -35,9 +35,10 @@ namespace hass_workstation_service.Domain.Sensors
return this._autoDiscoveryConfigModel ?? SetAutoDiscoveryConfigModel(new SensorDiscoveryConfigModel() return this._autoDiscoveryConfigModel ?? SetAutoDiscoveryConfigModel(new SensorDiscoveryConfigModel()
{ {
Name = this.Name, Name = this.Name,
NamePrefix = Publisher.NamePrefix,
Unique_id = this.Id.ToString(), Unique_id = this.Id.ToString(),
Device = this.Publisher.DeviceConfigModel, Device = this.Publisher.DeviceConfigModel,
State_topic = $"homeassistant/{this.Domain}/{Publisher.DeviceConfigModel.Name}/{this.ObjectId}/state", State_topic = $"homeassistant/{this.Domain}/{Publisher.DeviceConfigModel.Name}/{DiscoveryConfigModel.GetNameWithPrefix(Publisher.NamePrefix, this.ObjectId)}/state",
Unit_of_measurement = "%", Unit_of_measurement = "%",
Availability_topic = $"homeassistant/{this.Domain}/{Publisher.DeviceConfigModel.Name}/availability" Availability_topic = $"homeassistant/{this.Domain}/{Publisher.DeviceConfigModel.Name}/availability"
}); });

@ -35,9 +35,10 @@ namespace hass_workstation_service.Domain.Sensors
return this._autoDiscoveryConfigModel ?? SetAutoDiscoveryConfigModel(new SensorDiscoveryConfigModel() return this._autoDiscoveryConfigModel ?? SetAutoDiscoveryConfigModel(new SensorDiscoveryConfigModel()
{ {
Name = this.Name, Name = this.Name,
NamePrefix = Publisher.NamePrefix,
Unique_id = this.Id.ToString(), Unique_id = this.Id.ToString(),
Device = this.Publisher.DeviceConfigModel, Device = this.Publisher.DeviceConfigModel,
State_topic = $"homeassistant/{this.Domain}/{Publisher.DeviceConfigModel.Name}/{this.ObjectId}/state", State_topic = $"homeassistant/{this.Domain}/{Publisher.DeviceConfigModel.Name}/{DiscoveryConfigModel.GetNameWithPrefix(Publisher.NamePrefix, this.ObjectId)}/state",
Device_class = "temperature", Device_class = "temperature",
Unit_of_measurement = "°C", Unit_of_measurement = "°C",
Availability_topic = $"homeassistant/{this.Domain}/{Publisher.DeviceConfigModel.Name}/availability" Availability_topic = $"homeassistant/{this.Domain}/{Publisher.DeviceConfigModel.Name}/availability"

@ -14,11 +14,11 @@ namespace hass_workstation_service.Domain.Sensors
return this._autoDiscoveryConfigModel ?? SetAutoDiscoveryConfigModel(new SensorDiscoveryConfigModel() return this._autoDiscoveryConfigModel ?? SetAutoDiscoveryConfigModel(new SensorDiscoveryConfigModel()
{ {
Name = this.Name, Name = this.Name,
NamePrefix = Publisher.NamePrefix,
Unique_id = this.Id.ToString(), Unique_id = this.Id.ToString(),
Device = this.Publisher.DeviceConfigModel, Device = this.Publisher.DeviceConfigModel,
State_topic = $"homeassistant/{this.Domain}/{Publisher.DeviceConfigModel.Name}/{this.ObjectId}/state", State_topic = $"homeassistant/{this.Domain}/{Publisher.DeviceConfigModel.Name}/{DiscoveryConfigModel.GetNameWithPrefix(Publisher.NamePrefix, this.ObjectId)}/state",
Icon = "mdi:clock-time-three-outline", Icon = "mdi:clock-time-three-outline",
Availability_topic = $"homeassistant/{this.Domain}/{Publisher.DeviceConfigModel.Name}/availability",
Device_class = "timestamp" Device_class = "timestamp"
}); });
} }

@ -18,11 +18,11 @@ namespace hass_workstation_service.Domain.Sensors
return this._autoDiscoveryConfigModel ?? SetAutoDiscoveryConfigModel(new SensorDiscoveryConfigModel() return this._autoDiscoveryConfigModel ?? SetAutoDiscoveryConfigModel(new SensorDiscoveryConfigModel()
{ {
Name = this.Name, Name = this.Name,
NamePrefix = Publisher.NamePrefix,
Unique_id = this.Id.ToString(), Unique_id = this.Id.ToString(),
Device = this.Publisher.DeviceConfigModel, Device = this.Publisher.DeviceConfigModel,
State_topic = $"homeassistant/{this.Domain}/{Publisher.DeviceConfigModel.Name}/{this.ObjectId}/state", State_topic = $"homeassistant/{this.Domain}/{Publisher.DeviceConfigModel.Name}/{DiscoveryConfigModel.GetNameWithPrefix(Publisher.NamePrefix, this.ObjectId)}/state",
Icon = "mdi:clock-time-three-outline", Icon = "mdi:clock-time-three-outline",
Availability_topic = $"homeassistant/{this.Domain}/{Publisher.DeviceConfigModel.Name}/availability",
Device_class = "timestamp" Device_class = "timestamp"
}); });
} }

@ -0,0 +1,41 @@
using CoreAudio;
using hass_workstation_service.Communication;
using System;
using System.Globalization;
namespace hass_workstation_service.Domain.Sensors
{
public class MasterVolumeSensor : AbstractSensor
{
private MMDeviceEnumerator deviceEnumerator;
public MasterVolumeSensor(MqttPublisher publisher, int? updateInterval = null, string name = "MasterVolume", Guid id = default(Guid)) : base(publisher, name ?? "CurrentVolume", updateInterval ?? 10, id)
{
this.deviceEnumerator = new MMDeviceEnumerator();
}
public override SensorDiscoveryConfigModel GetAutoDiscoveryConfig()
{
return this._autoDiscoveryConfigModel ?? SetAutoDiscoveryConfigModel(new SensorDiscoveryConfigModel()
{
Name = this.Name,
Unique_id = this.Id.ToString(),
Device = this.Publisher.DeviceConfigModel,
State_topic = $"homeassistant/{this.Domain}/{Publisher.DeviceConfigModel.Name}/{this.ObjectId}/state",
Icon = "mdi:volume-medium",
Unit_of_measurement = "%",
Availability_topic = $"homeassistant/{this.Domain}/{Publisher.DeviceConfigModel.Name}/availability"
});
}
public override string GetState()
{
var defaultAudioDevice = deviceEnumerator.GetDefaultAudioEndpoint(EDataFlow.eRender, ERole.eMultimedia);
// check if the volume is muted
if (defaultAudioDevice.AudioEndpointVolume.Mute) return "0";
return Math.Round(defaultAudioDevice.AudioEndpointVolume.MasterVolumeLevelScalar * 100, 0) // round volume and convert to percent
.ToString(CultureInfo.InvariantCulture); // convert to string
}
}
}

@ -42,9 +42,10 @@ namespace hass_workstation_service.Domain.Sensors
return this._autoDiscoveryConfigModel ?? SetAutoDiscoveryConfigModel(new SensorDiscoveryConfigModel() return this._autoDiscoveryConfigModel ?? SetAutoDiscoveryConfigModel(new SensorDiscoveryConfigModel()
{ {
Name = this.Name, Name = this.Name,
NamePrefix = Publisher.NamePrefix,
Unique_id = this.Id.ToString(), Unique_id = this.Id.ToString(),
Device = this.Publisher.DeviceConfigModel, Device = this.Publisher.DeviceConfigModel,
State_topic = $"homeassistant/{this.Domain}/{Publisher.DeviceConfigModel.Name}/{this.ObjectId}/state", State_topic = $"homeassistant/{this.Domain}/{Publisher.DeviceConfigModel.Name}/{DiscoveryConfigModel.GetNameWithPrefix(Publisher.NamePrefix, this.ObjectId)}/state",
Icon = "mdi:memory", Icon = "mdi:memory",
Unit_of_measurement = "%", Unit_of_measurement = "%",
Availability_topic = $"homeassistant/{this.Domain}/{Publisher.DeviceConfigModel.Name}/availability" Availability_topic = $"homeassistant/{this.Domain}/{Publisher.DeviceConfigModel.Name}/availability"

@ -27,9 +27,10 @@ namespace hass_workstation_service.Domain.Sensors
return this._autoDiscoveryConfigModel ?? SetAutoDiscoveryConfigModel(new SensorDiscoveryConfigModel() return this._autoDiscoveryConfigModel ?? SetAutoDiscoveryConfigModel(new SensorDiscoveryConfigModel()
{ {
Name = this.Name, Name = this.Name,
NamePrefix = Publisher.NamePrefix,
Unique_id = this.Id.ToString(), Unique_id = this.Id.ToString(),
Device = this.Publisher.DeviceConfigModel, Device = this.Publisher.DeviceConfigModel,
State_topic = $"homeassistant/{this.Domain}/{Publisher.DeviceConfigModel.Name}/{this.ObjectId}/state", State_topic = $"homeassistant/{this.Domain}/{Publisher.DeviceConfigModel.Name}/{DiscoveryConfigModel.GetNameWithPrefix(Publisher.NamePrefix, this.ObjectId)}/state",
Availability_topic = $"homeassistant/sensor/{Publisher.DeviceConfigModel.Name}/availability" Availability_topic = $"homeassistant/sensor/{Publisher.DeviceConfigModel.Name}/availability"
}); });
} }

@ -22,11 +22,12 @@ namespace hass_workstation_service.Domain.Sensors
return this._autoDiscoveryConfigModel ?? SetAutoDiscoveryConfigModel(new SensorDiscoveryConfigModel() return this._autoDiscoveryConfigModel ?? SetAutoDiscoveryConfigModel(new SensorDiscoveryConfigModel()
{ {
Name = this.Name, Name = this.Name,
NamePrefix = Publisher.NamePrefix,
Unique_id = this.Id.ToString(), Unique_id = this.Id.ToString(),
Device = this.Publisher.DeviceConfigModel, Device = this.Publisher.DeviceConfigModel,
State_topic = $"homeassistant/{this.Domain}/{Publisher.DeviceConfigModel.Name}/{this.ObjectId}/state", State_topic = $"homeassistant/{this.Domain}/{Publisher.DeviceConfigModel.Name}/{DiscoveryConfigModel.GetNameWithPrefix(Publisher.NamePrefix, this.ObjectId)}/state",
Availability_topic = $"homeassistant/sensor/{Publisher.DeviceConfigModel.Name}/availability" Availability_topic = $"homeassistant/sensor/{Publisher.DeviceConfigModel.Name}/availability"
}); });;
} }
public override string GetState() public override string GetState()

@ -42,9 +42,10 @@ namespace hass_workstation_service.Domain.Sensors
return this._autoDiscoveryConfigModel ?? SetAutoDiscoveryConfigModel(new SensorDiscoveryConfigModel() return this._autoDiscoveryConfigModel ?? SetAutoDiscoveryConfigModel(new SensorDiscoveryConfigModel()
{ {
Name = this.Name, Name = this.Name,
NamePrefix = Publisher.NamePrefix,
Unique_id = this.Id.ToString(), Unique_id = this.Id.ToString(),
Device = this.Publisher.DeviceConfigModel, Device = this.Publisher.DeviceConfigModel,
State_topic = $"homeassistant/{this.Domain}/{Publisher.DeviceConfigModel.Name}/{this.ObjectId}/state", State_topic = $"homeassistant/{this.Domain}/{Publisher.DeviceConfigModel.Name}/{DiscoveryConfigModel.GetNameWithPrefix(Publisher.NamePrefix, this.ObjectId)}/state",
Icon = "mdi:lock", Icon = "mdi:lock",
Availability_topic = $"homeassistant/{this.Domain}/{Publisher.DeviceConfigModel.Name}/availability" Availability_topic = $"homeassistant/{this.Domain}/{Publisher.DeviceConfigModel.Name}/availability"
}); });

@ -14,9 +14,10 @@ namespace hass_workstation_service.Domain.Sensors
return this._autoDiscoveryConfigModel ?? SetAutoDiscoveryConfigModel(new SensorDiscoveryConfigModel() return this._autoDiscoveryConfigModel ?? SetAutoDiscoveryConfigModel(new SensorDiscoveryConfigModel()
{ {
Name = this.Name, Name = this.Name,
NamePrefix = Publisher.NamePrefix,
Unique_id = this.Id.ToString(), Unique_id = this.Id.ToString(),
Device = this.Publisher.DeviceConfigModel, Device = this.Publisher.DeviceConfigModel,
State_topic = $"homeassistant/{this.Domain}/{Publisher.DeviceConfigModel.Name}/{this.Name}/state", State_topic = $"homeassistant/{this.Domain}/{Publisher.DeviceConfigModel.Name}/{DiscoveryConfigModel.GetNameWithPrefix(Publisher.NamePrefix, this.ObjectId)}/state",
Icon = "mdi:laptop", Icon = "mdi:laptop",
Availability_topic = $"homeassistant/{this.Domain}/{Publisher.DeviceConfigModel.Name}/availability" Availability_topic = $"homeassistant/{this.Domain}/{Publisher.DeviceConfigModel.Name}/availability"
}); });

@ -25,9 +25,10 @@ namespace hass_workstation_service.Domain.Sensors
return this._autoDiscoveryConfigModel ?? SetAutoDiscoveryConfigModel(new SensorDiscoveryConfigModel() return this._autoDiscoveryConfigModel ?? SetAutoDiscoveryConfigModel(new SensorDiscoveryConfigModel()
{ {
Name = this.Name, Name = this.Name,
NamePrefix = Publisher.NamePrefix,
Unique_id = this.Id.ToString(), Unique_id = this.Id.ToString(),
Device = this.Publisher.DeviceConfigModel, Device = this.Publisher.DeviceConfigModel,
State_topic = $"homeassistant/{this.Domain}/{Publisher.DeviceConfigModel.Name}/{this.Name}/state", State_topic = $"homeassistant/{this.Domain}/{Publisher.DeviceConfigModel.Name}/{DiscoveryConfigModel.GetNameWithPrefix(Publisher.NamePrefix, this.ObjectId)}/state",
Availability_topic = $"homeassistant/{this.Domain}/{Publisher.DeviceConfigModel.Name}/availability" Availability_topic = $"homeassistant/{this.Domain}/{Publisher.DeviceConfigModel.Name}/availability"
}); });
} }

@ -30,9 +30,10 @@ namespace hass_workstation_service.Domain.Sensors
return this._autoDiscoveryConfigModel ?? SetAutoDiscoveryConfigModel(new SensorDiscoveryConfigModel() return this._autoDiscoveryConfigModel ?? SetAutoDiscoveryConfigModel(new SensorDiscoveryConfigModel()
{ {
Name = this.Name, Name = this.Name,
NamePrefix = Publisher.NamePrefix,
Unique_id = this.Id.ToString(), Unique_id = this.Id.ToString(),
Device = this.Publisher.DeviceConfigModel, Device = this.Publisher.DeviceConfigModel,
State_topic = $"homeassistant/{this.Domain}/{Publisher.DeviceConfigModel.Name}/{this.Name}/state", State_topic = $"homeassistant/{this.Domain}/{Publisher.DeviceConfigModel.Name}/{DiscoveryConfigModel.GetNameWithPrefix(Publisher.NamePrefix, this.ObjectId)}/state",
Availability_topic = $"homeassistant/sensor/{Publisher.DeviceConfigModel.Name}/availability" Availability_topic = $"homeassistant/sensor/{Publisher.DeviceConfigModel.Name}/availability"
}); });
} }

@ -70,7 +70,6 @@ namespace hass_workstation_service
} }
public static IHostBuilder CreateHostBuilder(string[] args) => public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args) Host.CreateDefaultBuilder(args)
.ConfigureLogging((hostContext, loggingBuilder) => .ConfigureLogging((hostContext, loggingBuilder) =>
loggingBuilder.AddSerilog(dispose: true)) loggingBuilder.AddSerilog(dispose: true))
.ConfigureServices((hostContext, services) => .ConfigureServices((hostContext, services) =>
@ -84,14 +83,14 @@ namespace hass_workstation_service
Sw_version = GetVersion() Sw_version = GetVersion()
}; };
services.AddSingleton(deviceConfig); services.AddSingleton(deviceConfig);
services.AddSingleton<ServiceContractInterfaces, InterProcessApi>(); services.AddSingleton<IServiceContractInterfaces, InterProcessApi>();
services.AddSingleton<IConfigurationService, ConfigurationService>(); services.AddSingleton<IConfigurationService, ConfigurationService>();
services.AddSingleton<MqttPublisher>(); services.AddSingleton<MqttPublisher>();
services.AddHostedService<Worker>(); services.AddHostedService<Worker>();
}).ConfigureIpcHost(builder => }).ConfigureIpcHost(builder =>
{ {
// configure IPC endpoints // configure IPC endpoints
builder.AddNamedPipeEndpoint<ServiceContractInterfaces>(pipeName: "pipeinternal"); builder.AddNamedPipeEndpoint<IServiceContractInterfaces>(pipeName: "pipeinternal");
}); });
static internal string GetVersion() static internal string GetVersion()
{ {

@ -4,7 +4,7 @@ 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>47</ApplicationRevision> <ApplicationRevision>55</ApplicationRevision>
<ApplicationVersion>1.0.0.*</ApplicationVersion> <ApplicationVersion>1.0.0.*</ApplicationVersion>
<BootstrapperEnabled>True</BootstrapperEnabled> <BootstrapperEnabled>True</BootstrapperEnabled>
<Configuration>Release</Configuration> <Configuration>Release</Configuration>
@ -37,6 +37,7 @@ 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>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<BootstrapperPackage Include="Microsoft.NetCore.CoreRuntime.5.0.x64"> <BootstrapperPackage Include="Microsoft.NetCore.CoreRuntime.5.0.x64">

@ -1,7 +0,0 @@
{
"MqttBroker": {
"Host": "192.168.2.6",
"Username": "tester",
"Password": "tester"
}
}

@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk.Worker"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>net5.0</TargetFramework> <TargetFramework>net5.0</TargetFramework>
@ -42,6 +42,9 @@
<Content Include="UserInterface.exe"> <Content Include="UserInterface.exe">
<CopyToOutputDirectory>Always</CopyToOutputDirectory> <CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content> </Content>
<Content Include="UserInterface.pdb">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
@ -52,14 +55,14 @@
<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.7" /> <PackageReference Include="LibreHardwareMonitorLib" Version="0.8.8" />
<PackageReference Include="Microsoft.Extensions.Hosting" Version="5.0.0" /> <PackageReference Include="Microsoft.Extensions.Hosting" Version="5.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.13" /> <PackageReference Include="MQTTnet" Version="3.0.16" />
<PackageReference Include="MQTTnet.Extensions.ManagedClient" Version="3.0.13" /> <PackageReference Include="MQTTnet.Extensions.ManagedClient" Version="3.0.16" />
<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="3.1.1" /> <PackageReference Include="Serilog.Sinks.Console" Version="4.0.0" />
<PackageReference Include="Serilog.Sinks.File" Version="4.1.0" /> <PackageReference Include="Serilog.Sinks.File" Version="5.0.0" />
<PackageReference Include="System.Management" Version="5.0.0" /> <PackageReference Include="System.Management" Version="5.0.0" />
</ItemGroup> </ItemGroup>
@ -67,5 +70,11 @@
<Reference Include="CoreAudio"> <Reference Include="CoreAudio">
<HintPath>..\lib\CoreAudio.dll</HintPath> <HintPath>..\lib\CoreAudio.dll</HintPath>
</Reference> </Reference>
<!--<Reference Include="libHarfBuzzSharp.dll">
<HintPath>..\lib\libHarfBuzzSharp.dll</HintPath>
</Reference>
<Reference Include="libSkiaSharp.dll">
<HintPath>..\lib\libSkiaSharp.dll</HintPath>
</Reference>-->
</ItemGroup> </ItemGroup>
</Project> </Project>

File diff suppressed because it is too large Load Diff

@ -0,0 +1,9 @@
{
"runtimeOptions": {
"tfm": "net5.0",
"framework": {
"name": "Microsoft.NETCore.App",
"version": "5.0.0"
}
}
}
Loading…
Cancel
Save