Merge branch 'develop'

pull/56/head
sleevezipper 4 years ago
commit b783ec0508

@ -90,6 +90,14 @@ This sensor shows if the microphone is currently being used. It uses the Windows
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.
@ -149,13 +157,24 @@ This sensor returns the current session state. It has the following possible sta
|InUse|A user is currently logged in.|
|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).
|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.|
### Dummy
This sensor spits out a random number every second. Useful for testing, maybe you'll find some other use for it.
## Commands
Commands can be used to trigger certain things on the client. For each command, a switch will be available in Home Assistant. Turning on the switch fires the command on the client and it will turn the switch off when it's done. Turning it off will cancel thje running command.
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
@ -178,3 +197,30 @@ This command allows you to run any Windows Commands. The command will be run in
|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 which are very self exlanatory.
- Play/Pause
- Next
- Previous
- Volume up
- Volume down
- Mute (toggle)
## Credits
This project depends on work done by others and they should at least get a mention. Please note that this list is not complete yet.
### [CoreAudio](https://github.com/morphx666/CoreAudio)
CoreAudio was used to check the current volume of playing audio.
### [LibreHardwareMonitor](https://github.com/LibreHardwareMonitor/LibreHardwareMonitor)
We use this for our GPU sensors.

@ -1,6 +1,5 @@
using Avalonia;
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Logging.Serilog;
using Avalonia.ReactiveUI;
using System;
@ -18,7 +17,7 @@ namespace UserInterface
public static AppBuilder BuildAvaloniaApp()
=> AppBuilder.Configure<App>()
.UsePlatformDetect()
.LogToDebug()
.LogToTrace()
.UseReactiveUI();
}

@ -11,11 +11,11 @@
<None Remove="Assets\hass-workstation-logo.ico" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Avalonia" Version="0.9.12" />
<PackageReference Include="Avalonia.Controls.DataGrid" Version="0.9.12" />
<PackageReference Include="Avalonia.Desktop" Version="0.9.12" />
<PackageReference Include="Avalonia.ReactiveUI" Version="0.9.12" />
<PackageReference Include="Avalonia.Win32" Version="0.9.12" />
<PackageReference Include="Avalonia" Version="0.10.0" />
<PackageReference Include="Avalonia.Controls.DataGrid" Version="0.10.0" />
<PackageReference Include="Avalonia.Desktop" Version="0.10.0" />
<PackageReference Include="Avalonia.ReactiveUI" Version="0.10.0" />
<PackageReference Include="Avalonia.Win32" Version="0.10.0" />
<PackageReference Include="JKang.IpcServiceFramework.Client.NamedPipe" Version="3.1.0" />
<PackageReference Include="Serilog.Sinks.Console" Version="3.1.1" />
</ItemGroup>

@ -13,9 +13,11 @@ namespace UserInterface.ViewModels
public string Description { get => description; set => this.RaiseAndSetIfChanged(ref description, value); }
public bool ShowCommandInput { get => showCommandInput; set => this.RaiseAndSetIfChanged(ref showCommandInput, value); }
public bool ShowKeyInput { get => showKeyInput; set => this.RaiseAndSetIfChanged(ref showKeyInput, value); }
private string moreInfoLink;
private bool showCommandInput;
private bool showKeyInput;
public string MoreInfoLink
{
@ -27,5 +29,6 @@ namespace UserInterface.ViewModels
public string Name { get; set; }
public string Command { get; set; }
public string Key { get; set; }
}
}

@ -20,6 +20,8 @@
<TextBlock Text="{Binding UpdateInterval, StringFormat= Update every {0} seconds}" HorizontalAlignment="Left" MinWidth="150"/>
<ContentControl IsVisible="{Binding ShowCommandInput}" Margin="0 20 0 10">Command</ContentControl>
<TextBox IsVisible="{Binding ShowCommandInput}" Text="{Binding Command}" Watermark="Rundll32.exe user32.dll,LockWorkStation" HorizontalAlignment="Left" MinWidth="300"/>
<ContentControl IsVisible="{Binding ShowKeyInput}" Margin="0 20 0 10">Key</ContentControl>
<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>
<Button Width="75" HorizontalAlignment="Right" Margin="0 40 0 10" Click="Save">Save</Button>
</StackPanel>

@ -23,9 +23,6 @@ namespace UserInterface.Views
public AddCommandDialog()
{
this.InitializeComponent();
#if DEBUG
this.AttachDevTools();
#endif
DataContext = new AddCommandViewModel();
this.comboBox = this.FindControl<ComboBox>("ComboBox");
this.comboBox.Items = Enum.GetValues(typeof(AvailableCommands)).Cast<AvailableCommands>().OrderBy(v => v.ToString());
@ -47,7 +44,7 @@ namespace UserInterface.Views
public async void Save(object sender, RoutedEventArgs args)
{
var item = ((AddCommandViewModel)this.DataContext);
dynamic model = new { item.Name, item.Command};
dynamic model = new { item.Name, item.Command, item.Key};
string json = JsonSerializer.Serialize(model);
await this.client.InvokeAsync(x => x.AddCommand(item.SelectedType, json));
Close();
@ -62,32 +59,79 @@ namespace UserInterface.Views
item.Description = "This command lets you execute any command you want. It will run in a Windows Command Prompt silently. ";
item.MoreInfoLink = "https://github.com/sleevezipper/hass-workstation-service#customcommand";
item.ShowCommandInput = true;
item.ShowKeyInput = false;
break;
case AvailableCommands.ShutdownCommand:
item.Description = "This command shuts down the PC immediately. ";
item.MoreInfoLink = "https://github.com/sleevezipper/hass-workstation-service#shutdowncommand";
item.ShowCommandInput = false;
item.ShowKeyInput = false;
break;
case AvailableCommands.RestartCommand:
item.Description = "This command restarts the PC immediately. ";
item.MoreInfoLink = "https://github.com/sleevezipper/hass-workstation-service#restartcommand";
item.ShowCommandInput = false;
item.ShowKeyInput = false;
break;
case AvailableCommands.LogOffCommand:
item.Description = "This command logs the current user off immediately. ";
item.MoreInfoLink = "https://github.com/sleevezipper/hass-workstation-service#logoffcommand";
item.ShowCommandInput = false;
item.ShowKeyInput = false;
break;
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.MoreInfoLink = "https://github.com/sleevezipper/hass-workstation-service#keycommand";
item.ShowCommandInput = false;
item.ShowKeyInput = true;
break;
case AvailableCommands.PlayPauseCommand:
item.Description = "This command plays or pauses currently playing media.";
item.MoreInfoLink = "https://github.com/sleevezipper/hass-workstation-service#media-commands";
item.ShowCommandInput = false;
item.ShowKeyInput = false;
break;
case AvailableCommands.NextCommand:
item.Description = "This command skips to the next media.";
item.MoreInfoLink = "https://github.com/sleevezipper/hass-workstation-service#media-commands";
item.ShowCommandInput = false;
item.ShowKeyInput = false;
break;
case AvailableCommands.PreviousCommand:
item.Description = "This command plays previous media.";
item.MoreInfoLink = "https://github.com/sleevezipper/hass-workstation-service#media-commands";
item.ShowCommandInput = false;
item.ShowKeyInput = false;
break;
case AvailableCommands.VolumeDownCommand:
item.Description = "Lowers the system volume.";
item.MoreInfoLink = "https://github.com/sleevezipper/hass-workstation-service#media-commands";
item.ShowCommandInput = false;
item.ShowKeyInput = false;
break;
case AvailableCommands.VolumeUpCommand:
item.Description = "Raises the system volume.";
item.MoreInfoLink = "https://github.com/sleevezipper/hass-workstation-service#media-commands";
item.ShowCommandInput = false;
item.ShowKeyInput = false;
break;
case AvailableCommands.MuteCommand:
item.Description = "Toggles muting the system volume.";
item.MoreInfoLink = "https://github.com/sleevezipper/hass-workstation-service#media-commands";
item.ShowCommandInput = false;
item.ShowKeyInput = false;
break;
default:
item.Description = null;
item.MoreInfoLink = null;
item.ShowCommandInput = false;
item.ShowKeyInput = false;
break;
}
}
public void OpenInfo(object sender, RoutedEventArgs args)
public void OpenInfo(object sender, RoutedEventArgs args)
{
var item = ((AddSensorViewModel)this.DataContext);
var item = ((AddCommandViewModel)this.DataContext);
BrowserUtil.OpenBrowser(item.MoreInfoLink);
}

@ -23,9 +23,6 @@ namespace UserInterface.Views
public AddSensorDialog()
{
this.InitializeComponent();
#if DEBUG
this.AttachDevTools();
#endif
DataContext = new AddSensorViewModel();
this.comboBox = this.FindControl<ComboBox>("ComboBox");
this.comboBox.Items = Enum.GetValues(typeof(AvailableSensors)).Cast<AvailableSensors>().OrderBy(v => v.ToString());
@ -156,6 +153,27 @@ namespace UserInterface.Views
item.ShowWindowNameInput = false;
item.UpdateInterval = 5;
break;
case AvailableSensors.CurrentVolumeSensor:
item.Description = "This sensor returns the volume of currently playing audio.";
item.MoreInfoLink = "https://github.com/sleevezipper/hass-workstation-service#currentvolume";
item.ShowQueryInput = false;
item.ShowWindowNameInput = false;
item.UpdateInterval = 5;
break;
case AvailableSensors.GPUTemperatureSensor:
item.Description = "This sensor returns the current temperature of the GPU in °C.";
item.MoreInfoLink = "https://github.com/sleevezipper/hass-workstation-service#gputemperature";
item.ShowQueryInput = false;
item.ShowWindowNameInput = false;
item.UpdateInterval = 5;
break;
case AvailableSensors.GPULoadSensor:
item.Description = "This sensor returns the current GPU load.";
item.MoreInfoLink = "https://github.com/sleevezipper/hass-workstation-service#gpuload";
item.ShowQueryInput = false;
item.ShowWindowNameInput = false;
item.UpdateInterval = 5;
break;
default:
item.Description = null;
item.MoreInfoLink = null;

@ -10,9 +10,7 @@ namespace UserInterface.Views
public MainWindow()
{
InitializeComponent();
#if DEBUG
this.AttachDevTools();
#endif
WindowsTrayIcon icon = new WindowsTrayIcon();
}

@ -50,12 +50,17 @@ namespace UserInterface.Views
sensorsNeedToRefresh = false;
List<ConfiguredSensorModel> status = await this.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)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();
while (!sensorsNeedToRefresh)
{
await Task.Delay(1000);
List<ConfiguredSensorModel> statusUpdated = await this.client.InvokeAsync(x => x.GetConfiguredSensors());
var configuredSensors = ((SensorSettingsViewModel)this.DataContext).ConfiguredSensors;
// this is a workaround for the list showing before it has been completely loaded in the service
if (statusUpdated.Count != configuredSensors.Count) {
sensorsNeedToRefresh = true;
GetConfiguredSensors();
}
statusUpdated.ForEach(s =>
{
var configuredSensor = configuredSensors.FirstOrDefault(cs => cs.Id == s.Id);

@ -73,9 +73,10 @@ namespace hass_workstation_service.Communication.InterProcesCommunication
return this._configurationService.IsAutoStartEnabled();
}
public List<ConfiguredSensorModel> GetConfiguredSensors()
public async Task<List<ConfiguredSensorModel>> GetConfiguredSensors()
{
return this._configurationService.ConfiguredSensors.Select(s => new ConfiguredSensorModel() { Name = s.Name, Type = s.GetType().Name, Value = s.PreviousPublishedState, Id = s.Id, UpdateInterval = s.UpdateInterval, UnitOfMeasurement = s.GetAutoDiscoveryConfig().Unit_of_measurement }).ToList();
var sensors = await this._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();
}
public List<ConfiguredCommandModel> GetConfiguredCommands()
@ -147,6 +148,15 @@ namespace hass_workstation_service.Communication.InterProcesCommunication
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;
@ -185,6 +195,27 @@ namespace hass_workstation_service.Communication.InterProcesCommunication
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;

@ -14,7 +14,7 @@ namespace hass_workstation_service.Communication.NamedPipe
MqqtClientStatus GetMqqtClientStatus();
void EnableAutostart(bool enable);
bool IsAutoStartEnabled();
List<ConfiguredSensorModel> GetConfiguredSensors();
Task<List<ConfiguredSensorModel>> GetConfiguredSensors();
void RemoveSensorById(Guid id);
void AddSensor(AvailableSensors sensorType, string json);
void RemoveCommandById(Guid id);

@ -50,7 +50,10 @@ namespace hass_workstation_service.Communication.InterProcesCommunication.Models
NamedWindowSensor,
LastActiveSensor,
LastBootSensor,
SessionStateSensor
SessionStateSensor,
CurrentVolumeSensor,
GPUTemperatureSensor,
GPULoadSensor
}
public enum AvailableCommands
@ -59,5 +62,12 @@ namespace hass_workstation_service.Communication.InterProcesCommunication.Models
ShutdownCommand,
LogOffCommand,
RestartCommand,
KeyCommand,
PlayPauseCommand,
NextCommand,
PreviousCommand,
VolumeUpCommand,
VolumeDownCommand,
MuteCommand
}
}

@ -7,6 +7,7 @@ using System.Threading.Tasks;
using hass_workstation_service.Communication.InterProcesCommunication.Models;
using hass_workstation_service.Communication.Util;
using hass_workstation_service.Data;
using hass_workstation_service.Domain;
using hass_workstation_service.Domain.Commands;
using Microsoft.Extensions.Logging;
using MQTTnet;
@ -79,7 +80,7 @@ namespace hass_workstation_service.Communication
// configure what happens on disconnect
this._mqttClient.UseDisconnectedHandler(e =>
{
this._mqttClientMessage = e.ReasonCode.ToString();
this._mqttClientMessage = e.Reason.ToString();
});
}
@ -95,8 +96,9 @@ namespace hass_workstation_service.Communication
this._logger.LogInformation($"Message dropped because mqtt not connected: {message}");
}
}
public async Task AnnounceAutoDiscoveryConfig(DiscoveryConfigModel config, string domain, bool clearConfig = false)
// 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)
{
if (this._mqttClient.IsConnected)
{
@ -109,8 +111,8 @@ namespace hass_workstation_service.Communication
};
var message = new MqttApplicationMessageBuilder()
.WithTopic($"homeassistant/{domain}/{this.DeviceConfigModel.Name}/{config.Name}/config")
.WithPayload(clearConfig ? "" : JsonSerializer.Serialize(config, config.GetType(), options))
.WithTopic($"homeassistant/{domain}/{this.DeviceConfigModel.Name}/{discoverable.ObjectId}/config")
.WithPayload(clearConfig ? "" : JsonSerializer.Serialize(discoverable.GetAutoDiscoveryConfig(), discoverable.GetAutoDiscoveryConfig().GetType(), options))
.WithRetainFlag()
.Build();
await this.Publish(message);
@ -177,7 +179,7 @@ namespace hass_workstation_service.Communication
{
if (this.IsConnected)
{
await this._mqttClient.SubscribeAsync(command.GetAutoDiscoveryConfig().Command_topic);
await this._mqttClient.SubscribeAsync(((CommandDiscoveryConfigModel) command.GetAutoDiscoveryConfig()).Command_topic);
}
else
{
@ -186,7 +188,7 @@ namespace hass_workstation_service.Communication
await Task.Delay(5500);
}
await this._mqttClient.SubscribeAsync(command.GetAutoDiscoveryConfig().Command_topic);
await this._mqttClient.SubscribeAsync(((CommandDiscoveryConfigModel) command.GetAutoDiscoveryConfig()).Command_topic);
}
@ -197,7 +199,7 @@ namespace hass_workstation_service.Communication
{
foreach (AbstractCommand command in this.Subscribers)
{
if (command.GetAutoDiscoveryConfig().Command_topic == applicationMessage.Topic)
if (((CommandDiscoveryConfigModel)command.GetAutoDiscoveryConfig()).Command_topic == applicationMessage.Topic)
{
if (Encoding.UTF8.GetString(applicationMessage?.Payload) == "ON")
{

@ -15,6 +15,11 @@ namespace hass_workstation_service.Communication
/// </summary>
/// <value></value>
public string Name { get; set; }
/// <summary>
/// The MQTT topic subscribed to receive sensor values.
/// </summary>
/// <value></value>
public string State_topic { get; set; }
}
public class SensorDiscoveryConfigModel : DiscoveryConfigModel
{
@ -69,12 +74,6 @@ namespace hass_workstation_service.Communication
/// </summary>
/// <value></value>
public int? Qos { get; set; }
/// <summary>
/// The MQTT topic subscribed to receive sensor values.
/// </summary>
/// <value></value>
public string State_topic { get; set; }
/// <summary>
/// (Optional) An ID that uniquely identifies this sensor. If two sensors have the same unique ID, Home Assistant will raise an exception.
/// </summary>
@ -147,11 +146,6 @@ namespace hass_workstation_service.Communication
/// <value></value>
public int? Qos { get; set; }
/// <summary>
/// The MQTT topic subscribed to receive sensor values.
/// </summary>
/// <value></value>
public string State_topic { get; set; }
/// <summary>
/// (Optional) An ID that uniquely identifies this sensor. If two sensors have the same unique ID, Home Assistant will raise an exception.
/// </summary>
/// <value></value>

@ -32,6 +32,7 @@ namespace hass_workstation_service.Data
private bool BrokerSettingsFileLocked { get; set; }
private bool SensorsSettingsFileLocked { get; set; }
private bool CommandSettingsFileLocked { get; set; }
private bool _sensorsLoading { get; set; }
private readonly string path = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "Hass Workstation Service");
@ -59,6 +60,7 @@ namespace hass_workstation_service.Data
public async void ReadSensorSettings(MqttPublisher publisher)
{
this._sensorsLoading = true;
while (this.SensorsSettingsFileLocked)
{
await Task.Delay(500);
@ -120,6 +122,15 @@ namespace hass_workstation_service.Data
case "SessionStateSensor":
sensor = new SessionStateSensor(publisher, configuredSensor.UpdateInterval, configuredSensor.Name, configuredSensor.Id);
break;
case "CurrentVolumeSensor":
sensor = new CurrentVolumeSensor(publisher, configuredSensor.UpdateInterval, configuredSensor.Name, configuredSensor.Id);
break;
case "GpuTemperatureSensor":
sensor = new GpuTemperatureSensor(publisher, configuredSensor.UpdateInterval, configuredSensor.Name, configuredSensor.Id);
break;
case "GpuLoadSensor":
sensor = new GpuLoadSensor(publisher, configuredSensor.UpdateInterval, configuredSensor.Name, configuredSensor.Id);
break;
// keep this one last!
case "WMIQuerySensor":
sensor = new WMIQuerySensor(publisher, configuredSensor.Query, configuredSensor.UpdateInterval, configuredSensor.Name, configuredSensor.Id);
@ -132,6 +143,7 @@ namespace hass_workstation_service.Data
{
this.ConfiguredSensors.Add(sensor);
}
this._sensorsLoading = false;
}
}
@ -171,6 +183,27 @@ namespace hass_workstation_service.Data
case "CustomCommand":
command = new CustomCommand(publisher, configuredCommand.Command, configuredCommand.Name, configuredCommand.Id);
break;
case "MediaPlayPauseCommand":
command = new MediaPlayPauseCommand(publisher, configuredCommand.Name, configuredCommand.Id);
break;
case "MediaNextCommand":
command = new MediaNextCommand(publisher, configuredCommand.Name, configuredCommand.Id);
break;
case "MediaPreviousCommand":
command = new MediaPreviousCommand(publisher, configuredCommand.Name, configuredCommand.Id);
break;
case "MediaVolumeUpCommand":
command = new MediaVolumeUpCommand(publisher, configuredCommand.Name, configuredCommand.Id);
break;
case "MediaVolumeDownCommand":
command = new MediaVolumeDownCommand(publisher, configuredCommand.Name, configuredCommand.Id);
break;
case "MediaMuteCommand":
command = new MediaMuteCommand(publisher, configuredCommand.Name, configuredCommand.Id);
break;
case "KeyCommand":
command = new KeyCommand(publisher, configuredCommand.KeyCode, configuredCommand.Name, configuredCommand.Id);
break;
default:
Log.Logger.Error("unsupported command type in config");
break;
@ -193,6 +226,7 @@ namespace hass_workstation_service.Data
.WithTls(new MqttClientOptionsBuilderTlsParameters()
{
UseTls = configuredBroker.UseTLS,
SslProtocol = System.Security.Authentication.SslProtocols.Tls12,
AllowUntrustedCertificates = true
})
.WithCredentials(configuredBroker.Username, configuredBroker.Password.ToString())
@ -289,9 +323,13 @@ namespace hass_workstation_service.Data
Log.Logger.Information($"writing configured commands to: {stream.Name}");
foreach (AbstractCommand command in this.ConfiguredCommands)
{
if (command is CustomCommand customcommand)
if (command is CustomCommand customCommand)
{
configuredCommandsToSave.Add(new ConfiguredCommand() { Id = customCommand.Id, Name = customCommand.Name, Type = customCommand.GetType().Name, Command = customCommand.Command });
}
if (command is KeyCommand customKeyCommand)
{
configuredCommandsToSave.Add(new ConfiguredCommand() { Id = customcommand.Id, Name = customcommand.Name, Type = customcommand.GetType().Name, Command = customcommand.Command });
configuredCommandsToSave.Add(new ConfiguredCommand() { Id = customKeyCommand.Id, Name = customKeyCommand.Name, Type = customKeyCommand.GetType().Name, KeyCode = customKeyCommand.KeyCode });
}
}
@ -446,5 +484,14 @@ namespace hass_workstation_service.Data
RegistryKey rkApp = Registry.CurrentUser.OpenSubKey(@"SOFTWARE\Microsoft\Windows\CurrentVersion\Run", true);
return rkApp.GetValue("hass-workstation-service") != null;
}
public async Task<ICollection<AbstractSensor>> GetSensorsAfterLoadingAsync()
{
while (this._sensorsLoading)
{
await Task.Delay(500);
}
return this.ConfiguredSensors;
}
}
}

@ -9,5 +9,6 @@ namespace hass_workstation_service.Data
public Guid Id { get; set; }
public string Name { get; set; }
public string Command { get; set; }
public byte KeyCode { get; set; }
}
}

@ -31,5 +31,6 @@ namespace hass_workstation_service.Data
void DeleteConfiguredCommand(Guid id);
void WriteCommandSettingsAsync();
void ReadCommandSettings(MqttPublisher publisher);
Task<ICollection<AbstractSensor>> GetSensorsAfterLoadingAsync();
}
}

@ -1,7 +1,9 @@
using System;
using hass_workstation_service.Communication;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
namespace hass_workstation_service.Domain
@ -9,5 +11,17 @@ namespace hass_workstation_service.Domain
public abstract class AbstractDiscoverable
{
public abstract string Domain { get; }
public string Name { get; protected set; }
public string ObjectId
{
get
{
return Regex.Replace(this.Name, "[^a-zA-Z0-9_-]", "_");
}
}
public Guid Id { get; protected set; }
public abstract DiscoveryConfigModel GetAutoDiscoveryConfig();
}
}

@ -8,8 +8,6 @@ namespace hass_workstation_service.Domain.Commands
public abstract class AbstractCommand : AbstractDiscoverable
{
public Guid Id { get; protected set; }
public string Name { get; protected set; }
/// <summary>
/// The update interval in seconds. It checks state only if the interval has passed.
/// </summary>
@ -40,7 +38,6 @@ namespace hass_workstation_service.Domain.Commands
return config;
}
public abstract CommandDiscoveryConfigModel GetAutoDiscoveryConfig();
public abstract string GetState();
public async Task PublishStateAsync()
@ -68,11 +65,11 @@ namespace hass_workstation_service.Domain.Commands
}
public async void PublishAutoDiscoveryConfigAsync()
{
await this.Publisher.AnnounceAutoDiscoveryConfig(this.GetAutoDiscoveryConfig(), this.Domain);
await this.Publisher.AnnounceAutoDiscoveryConfig(this, this.Domain);
}
public async Task UnPublishAutoDiscoveryConfigAsync()
{
await this.Publisher.AnnounceAutoDiscoveryConfig(this.GetAutoDiscoveryConfig(), this.Domain, true);
await this.Publisher.AnnounceAutoDiscoveryConfig(this, this.Domain, true);
}
public abstract void TurnOn();
public abstract void TurnOff();

@ -56,8 +56,8 @@ namespace hass_workstation_service.Domain.Commands
Name = this.Name,
Unique_id = this.Id.ToString(),
Availability_topic = $"homeassistant/sensor/{Publisher.DeviceConfigModel.Name}/availability",
Command_topic = $"homeassistant/{this.Domain}/{Publisher.DeviceConfigModel.Name}/{this.Name}/set",
State_topic = $"homeassistant/{this.Domain}/{Publisher.DeviceConfigModel.Name}/{this.Name}/state",
Command_topic = $"homeassistant/{this.Domain}/{Publisher.DeviceConfigModel.Name}/{this.ObjectId}/set",
State_topic = $"homeassistant/{this.Domain}/{Publisher.DeviceConfigModel.Name}/{this.ObjectId}/state",
Device = this.Publisher.DeviceConfigModel,
};
}

@ -0,0 +1,60 @@
using hass_workstation_service.Communication;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
namespace hass_workstation_service.Domain.Commands
{
public class KeyCommand : AbstractCommand
{
public const int KEYEVENTF_EXTENTEDKEY = 1;
public const int KEYEVENTF_KEYUP = 0;
public const int VK_MEDIA_NEXT_TRACK = 0xB0;
public const int VK_MEDIA_PLAY_PAUSE = 0xB3;
public const int VK_MEDIA_PREV_TRACK = 0xB1;
public const int VK_VOLUME_MUTE = 0xAD;
public const int VK_VOLUME_UP = 0xAF;
public const int VK_VOLUME_DOWN = 0xAE;
public byte KeyCode { get; protected set; }
public KeyCommand(MqttPublisher publisher, byte keyCode, string name = "Key", Guid id = default(Guid)) : base(publisher, name ?? "Key", id) {
this.KeyCode = keyCode;
}
public override CommandDiscoveryConfigModel GetAutoDiscoveryConfig()
{
return new CommandDiscoveryConfigModel()
{
Name = this.Name,
Unique_id = this.Id.ToString(),
Availability_topic = $"homeassistant/sensor/{Publisher.DeviceConfigModel.Name}/availability",
Command_topic = $"homeassistant/{this.Domain}/{Publisher.DeviceConfigModel.Name}/{this.ObjectId}/set",
State_topic = $"homeassistant/{this.Domain}/{Publisher.DeviceConfigModel.Name}/{this.ObjectId}/state",
Device = this.Publisher.DeviceConfigModel,
};
}
[DllImport("user32.dll")]
public static extern void keybd_event(byte virtualKey, byte scanCode, uint flags, IntPtr extraInfo);
public override string GetState()
{
return "OFF";
}
public override void TurnOff()
{
}
public override void TurnOn()
{
keybd_event(this.KeyCode, 0, KEYEVENTF_EXTENTEDKEY, IntPtr.Zero);
}
}
}

@ -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 MediaMuteCommand : KeyCommand
{
public MediaMuteCommand(MqttPublisher publisher, string name = "Mute", Guid id = default(Guid)) : base(publisher, KeyCommand.VK_VOLUME_MUTE, name ?? "Mute", 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 MediaNextCommand : KeyCommand
{
public MediaNextCommand(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 MediaPlayPauseCommand : KeyCommand
{
public MediaPlayPauseCommand(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 MediaPreviousCommand : KeyCommand
{
public MediaPreviousCommand(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 MediaVolumeDownCommand : KeyCommand
{
public MediaVolumeDownCommand(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 MediaVolumeUpCommand : KeyCommand
{
public MediaVolumeUpCommand(MqttPublisher publisher, string name = "VolumeUp", Guid id = default(Guid)) : base(publisher, KeyCommand.VK_VOLUME_UP, name ?? "VolumeUp", id) { }
}
}

@ -1,4 +1,5 @@
using System;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using hass_workstation_service.Communication;
using MQTTnet;
@ -8,8 +9,6 @@ namespace hass_workstation_service.Domain.Sensors
public abstract class AbstractSensor : AbstractDiscoverable
{
public Guid Id { get; protected set; }
public string Name { get; protected set; }
/// <summary>
/// The update interval in seconds. It checks state only if the interval has passed.
/// </summary>
@ -40,7 +39,6 @@ namespace hass_workstation_service.Domain.Sensors
return config;
}
public abstract SensorDiscoveryConfigModel GetAutoDiscoveryConfig();
public abstract string GetState();
public async Task PublishStateAsync()
@ -68,11 +66,11 @@ namespace hass_workstation_service.Domain.Sensors
}
public async void PublishAutoDiscoveryConfigAsync()
{
await this.Publisher.AnnounceAutoDiscoveryConfig(this.GetAutoDiscoveryConfig(), this.Domain);
await this.Publisher.AnnounceAutoDiscoveryConfig(this, this.Domain);
}
public async Task UnPublishAutoDiscoveryConfigAsync()
{
await this.Publisher.AnnounceAutoDiscoveryConfig(this.GetAutoDiscoveryConfig(), this.Domain, true);
await this.Publisher.AnnounceAutoDiscoveryConfig(this, this.Domain, true);
}
}

@ -16,7 +16,7 @@ namespace hass_workstation_service.Domain.Sensors
Name = this.Name,
Unique_id = this.Id.ToString(),
Device = this.Publisher.DeviceConfigModel,
State_topic = $"homeassistant/{this.Domain}/{Publisher.DeviceConfigModel.Name}/{this.Name}/state",
State_topic = $"homeassistant/{this.Domain}/{Publisher.DeviceConfigModel.Name}/{this.ObjectId}/state",
Icon = "mdi:window-maximize",
Availability_topic = $"homeassistant/{this.Domain}/{Publisher.DeviceConfigModel.Name}/availability"
});

@ -24,7 +24,7 @@ namespace hass_workstation_service.Domain.Sensors
Name = this.Name,
Unique_id = this.Id.ToString(),
Device = this.Publisher.DeviceConfigModel,
State_topic = $"homeassistant/{this.Domain}/{Publisher.DeviceConfigModel.Name}/{this.Name}/state",
State_topic = $"homeassistant/{this.Domain}/{Publisher.DeviceConfigModel.Name}/{this.ObjectId}/state",
Icon = "mdi:chart-areaspline",
Unit_of_measurement = "%",
Availability_topic = $"homeassistant/{this.Domain}/{Publisher.DeviceConfigModel.Name}/availability"
@ -34,17 +34,20 @@ namespace hass_workstation_service.Domain.Sensors
[SupportedOSPlatform("windows")]
public override string GetState()
{
ManagementObjectCollection collection = _searcher.Get();
List<int> processorLoadPercentages = new List<int>();
foreach (ManagementObject mo in collection)
using (ManagementObjectCollection collection = _searcher.Get())
{
foreach (PropertyData property in mo.Properties)
List<int> processorLoadPercentages = new List<int>();
foreach (ManagementObject mo in collection)
{
processorLoadPercentages.Add(int.Parse(property.Value.ToString()));
foreach (PropertyData property in mo.Properties)
{
processorLoadPercentages.Add(int.Parse(property.Value.ToString()));
}
}
double average = processorLoadPercentages.Count > 0 ? processorLoadPercentages.Average() : 0.0;
return average.ToString("#.##", CultureInfo.InvariantCulture);
}
double average = processorLoadPercentages.Count > 0 ? processorLoadPercentages.Average() : 0.0;
return average.ToString("#.##", CultureInfo.InvariantCulture);
}
}
}

@ -16,7 +16,7 @@ namespace hass_workstation_service.Domain.Sensors
Name = this.Name,
Unique_id = this.Id.ToString(),
Device = this.Publisher.DeviceConfigModel,
State_topic = $"homeassistant/{this.Domain}/{Publisher.DeviceConfigModel.Name}/{this.Name}/state",
State_topic = $"homeassistant/{this.Domain}/{Publisher.DeviceConfigModel.Name}/{this.ObjectId}/state",
Icon = "mdi:speedometer",
Unit_of_measurement = "MHz",
Availability_topic = $"homeassistant/{this.Domain}/{Publisher.DeviceConfigModel.Name}/availability"

@ -0,0 +1,49 @@
using CoreAudio;
using hass_workstation_service.Communication;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
namespace hass_workstation_service.Domain.Sensors
{
public class CurrentVolumeSensor : AbstractSensor
{
private MMDeviceEnumerator deviceEnumerator;
private MMDeviceCollection devices;
public CurrentVolumeSensor(MqttPublisher publisher, int? updateInterval = null, string name = "CurrentVolume", Guid id = default(Guid)) : base(publisher, name ?? "CurrentVolume", updateInterval ?? 10, id) {
this.deviceEnumerator = new MMDeviceEnumerator();
this.devices = deviceEnumerator.EnumerateAudioEndPoints(EDataFlow.eRender, DEVICE_STATE.DEVICE_STATE_ACTIVE);
}
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()
{
List<float> peaks = new List<float>();
foreach (MMDevice device in devices)
{
peaks.Add(device.AudioMeterInformation.PeakValues[0]);
}
return Math.Round(peaks.Max() * 100, 0).ToString(CultureInfo.InvariantCulture);
}
}
}

@ -20,7 +20,7 @@ namespace hass_workstation_service.Domain.Sensors
Name = this.Name,
Unique_id = this.Id.ToString(),
Device = this.Publisher.DeviceConfigModel,
State_topic = $"homeassistant/{this.Domain}/{Publisher.DeviceConfigModel.Name}/{this.Name}/state",
State_topic = $"homeassistant/{this.Domain}/{Publisher.DeviceConfigModel.Name}/{this.ObjectId}/state",
Availability_topic = $"homeassistant/{this.Domain}/{Publisher.DeviceConfigModel.Name}/availability"
});
}

@ -0,0 +1,61 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using hass_workstation_service.Communication;
using LibreHardwareMonitor.Hardware;
namespace hass_workstation_service.Domain.Sensors
{
public class GpuLoadSensor : AbstractSensor
{
private Computer _computer;
private IHardware _gpu;
public GpuLoadSensor(MqttPublisher publisher, int? updateInterval = null, string name = "GPULoad", Guid id = default(Guid)) : base(publisher, name ?? "GPULoad", updateInterval ?? 10, id)
{
_computer = new Computer
{
IsCpuEnabled = false,
IsGpuEnabled = true,
IsMemoryEnabled = false,
IsMotherboardEnabled = false,
IsControllerEnabled = false,
IsNetworkEnabled = false,
IsStorageEnabled = false,
};
_computer.Open();
this._gpu = _computer.Hardware.FirstOrDefault(h => h.HardwareType == HardwareType.GpuAmd || h.HardwareType == HardwareType.GpuNvidia);
}
public override DiscoveryConfigModel 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",
Unit_of_measurement = "%",
Availability_topic = $"homeassistant/{this.Domain}/{Publisher.DeviceConfigModel.Name}/availability"
});
}
public override string GetState()
{
if (_gpu == null)
{
return "NotSupported";
}
_gpu.Update();
var sensor = _gpu.Sensors.FirstOrDefault(s => s.SensorType == SensorType.Load);
if (sensor == null)
{
return "NotSupported";
}
return sensor.Value.HasValue ? sensor.Value.Value.ToString("#.##", CultureInfo.InvariantCulture) : "Unknown";
}
}
}

@ -0,0 +1,63 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using hass_workstation_service.Communication;
using LibreHardwareMonitor.Hardware;
namespace hass_workstation_service.Domain.Sensors
{
public class GpuTemperatureSensor : AbstractSensor
{
private Computer _computer;
private IHardware _gpu;
public GpuTemperatureSensor(MqttPublisher publisher, int? updateInterval = null, string name = "GPUTemperature", Guid id = default(Guid)) : base(publisher, name ?? "GPUTemperature", updateInterval ?? 10, id)
{
_computer = new Computer
{
IsCpuEnabled = false,
IsGpuEnabled = true,
IsMemoryEnabled = false,
IsMotherboardEnabled = false,
IsControllerEnabled = false,
IsNetworkEnabled = false,
IsStorageEnabled = false,
};
_computer.Open();
this._gpu = _computer.Hardware.FirstOrDefault(h => h.HardwareType == HardwareType.GpuAmd || h.HardwareType == HardwareType.GpuNvidia);
}
public override DiscoveryConfigModel 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",
Device_class = "temperature",
Unit_of_measurement = "°C",
Availability_topic = $"homeassistant/{this.Domain}/{Publisher.DeviceConfigModel.Name}/availability"
});
}
public override string GetState()
{
if (_gpu == null)
{
return "NotSupported";
}
_gpu.Update();
var sensor = _gpu.Sensors.FirstOrDefault(s => s.SensorType == SensorType.Temperature);
if (sensor == null)
{
return "NotSupported";
}
return sensor.Value.HasValue ? sensor.Value.Value.ToString("#.##", CultureInfo.InvariantCulture) : "Unknown";
}
}
}

@ -16,9 +16,10 @@ namespace hass_workstation_service.Domain.Sensors
Name = this.Name,
Unique_id = this.Id.ToString(),
Device = this.Publisher.DeviceConfigModel,
State_topic = $"homeassistant/{this.Domain}/{Publisher.DeviceConfigModel.Name}/{this.Name}/state",
State_topic = $"homeassistant/{this.Domain}/{Publisher.DeviceConfigModel.Name}/{this.ObjectId}/state",
Icon = "mdi:clock-time-three-outline",
Availability_topic = $"homeassistant/{this.Domain}/{Publisher.DeviceConfigModel.Name}/availability"
Availability_topic = $"homeassistant/{this.Domain}/{Publisher.DeviceConfigModel.Name}/availability",
Device_class = "timestamp"
});
}

@ -20,9 +20,10 @@ namespace hass_workstation_service.Domain.Sensors
Name = this.Name,
Unique_id = this.Id.ToString(),
Device = this.Publisher.DeviceConfigModel,
State_topic = $"homeassistant/{this.Domain}/{Publisher.DeviceConfigModel.Name}/{this.Name}/state",
State_topic = $"homeassistant/{this.Domain}/{Publisher.DeviceConfigModel.Name}/{this.ObjectId}/state",
Icon = "mdi:clock-time-three-outline",
Availability_topic = $"homeassistant/{this.Domain}/{Publisher.DeviceConfigModel.Name}/availability"
Availability_topic = $"homeassistant/{this.Domain}/{Publisher.DeviceConfigModel.Name}/availability",
Device_class = "timestamp"
});
}

@ -17,22 +17,25 @@ namespace hass_workstation_service.Domain.Sensors
}
public override string GetState()
{
ManagementObjectCollection collection = _searcher.Get();
UInt64? totalMemory = null;
UInt64? freeMemory = null;
foreach (ManagementObject mo in collection)
using (ManagementObjectCollection collection = _searcher.Get())
{
totalMemory = (UInt64)mo.Properties["TotalVisibleMemorySize"]?.Value;
freeMemory = (UInt64)mo.Properties["FreePhysicalMemory"]?.Value;
UInt64? totalMemory = null;
UInt64? freeMemory = null;
foreach (ManagementObject mo in collection)
{
totalMemory = (UInt64)mo.Properties["TotalVisibleMemorySize"]?.Value;
freeMemory = (UInt64)mo.Properties["FreePhysicalMemory"]?.Value;
}
if (totalMemory != null && freeMemory != null)
{
decimal totalMemoryDec = totalMemory.Value;
decimal freeMemoryDec = freeMemory.Value;
decimal precentageUsed = 100 - (freeMemoryDec / totalMemoryDec) * 100;
return precentageUsed.ToString("#.##", CultureInfo.InvariantCulture);
}
return "";
}
if (totalMemory != null && freeMemory != null)
{
decimal totalMemoryDec = totalMemory.Value;
decimal freeMemoryDec = freeMemory.Value;
decimal precentageUsed = 100 - (freeMemoryDec / totalMemoryDec) * 100;
return precentageUsed.ToString("#.##", CultureInfo.InvariantCulture);
}
return "";
}
public override SensorDiscoveryConfigModel GetAutoDiscoveryConfig()
{
@ -41,7 +44,7 @@ namespace hass_workstation_service.Domain.Sensors
Name = this.Name,
Unique_id = this.Id.ToString(),
Device = this.Publisher.DeviceConfigModel,
State_topic = $"homeassistant/{this.Domain}/{Publisher.DeviceConfigModel.Name}/{this.Name}/state",
State_topic = $"homeassistant/{this.Domain}/{Publisher.DeviceConfigModel.Name}/{this.ObjectId}/state",
Icon = "mdi:memory",
Unit_of_measurement = "%",
Availability_topic = $"homeassistant/{this.Domain}/{Publisher.DeviceConfigModel.Name}/availability"

@ -10,6 +10,7 @@ namespace hass_workstation_service.Domain.Sensors
public class MicrophoneActiveSensor : AbstractSensor
{
public override string Domain => "binary_sensor";
public MicrophoneActiveSensor(MqttPublisher publisher, int? updateInterval = null, string name = "MicrophoneActive", Guid id = default(Guid)) : base(publisher, name ?? "MicrophoneActive", updateInterval ?? 10, id)
{
}
@ -17,7 +18,7 @@ namespace hass_workstation_service.Domain.Sensors
{
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
return IsMicrophoneInUse() ? "True" : "False";
return IsMicrophoneInUse() ? "ON" : "OFF";
}
else return "unsupported";
}
@ -28,9 +29,8 @@ namespace hass_workstation_service.Domain.Sensors
Name = this.Name,
Unique_id = this.Id.ToString(),
Device = this.Publisher.DeviceConfigModel,
State_topic = $"homeassistant/{this.Domain}/{Publisher.DeviceConfigModel.Name}/{this.Name}/state",
Icon = "mdi:microphone",
Availability_topic = $"homeassistant/{this.Domain}/{Publisher.DeviceConfigModel.Name}/availability"
State_topic = $"homeassistant/{this.Domain}/{Publisher.DeviceConfigModel.Name}/{this.ObjectId}/state",
Availability_topic = $"homeassistant/sensor/{Publisher.DeviceConfigModel.Name}/availability"
});
}

@ -10,6 +10,7 @@ namespace hass_workstation_service.Domain.Sensors
{
public class NamedWindowSensor : AbstractSensor
{
public override string Domain => "binary_sensor";
public string WindowName { get; protected set; }
public NamedWindowSensor(MqttPublisher publisher, string windowName, string name = "NamedWindow", int? updateInterval = 10, Guid id = default) : base(publisher, name ?? "NamedWindow", updateInterval ?? 10, id)
{
@ -23,16 +24,15 @@ namespace hass_workstation_service.Domain.Sensors
Name = this.Name,
Unique_id = this.Id.ToString(),
Device = this.Publisher.DeviceConfigModel,
State_topic = $"homeassistant/{this.Domain}/{Publisher.DeviceConfigModel.Name}/{this.Name}/state",
Icon = "mdi:window-maximize",
Availability_topic = $"homeassistant/{this.Domain}/{Publisher.DeviceConfigModel.Name}/availability"
State_topic = $"homeassistant/{this.Domain}/{Publisher.DeviceConfigModel.Name}/{this.ObjectId}/state",
Availability_topic = $"homeassistant/sensor/{Publisher.DeviceConfigModel.Name}/availability"
});
}
public override string GetState()
{
var windowNames = GetOpenWindows().Values;
return windowNames.Any(v => v.Contains(this.WindowName, StringComparison.OrdinalIgnoreCase)) ? "True" : "False";
return windowNames.Any(v => v.Contains(this.WindowName, StringComparison.OrdinalIgnoreCase)) ? "ON" : "OFF";
}

@ -1,4 +1,6 @@
using hass_workstation_service.Communication;
using Serilog;
using Serilog.Core;
using System;
using System.Collections.Generic;
using System.Diagnostics;
@ -42,7 +44,7 @@ namespace hass_workstation_service.Domain.Sensors
Name = this.Name,
Unique_id = this.Id.ToString(),
Device = this.Publisher.DeviceConfigModel,
State_topic = $"homeassistant/{this.Domain}/{Publisher.DeviceConfigModel.Name}/{this.Name}/state",
State_topic = $"homeassistant/{this.Domain}/{Publisher.DeviceConfigModel.Name}/{this.ObjectId}/state",
Icon = "mdi:lock",
Availability_topic = $"homeassistant/{this.Domain}/{Publisher.DeviceConfigModel.Name}/availability"
});
@ -70,17 +72,35 @@ namespace hass_workstation_service.Domain.Sensors
var explorerProcesses = Process.GetProcessesByName("explorer")
.Select(p => p.Id.ToString())
.ToHashSet();
var REprocessid = new Regex(@"(?<=Handle="").*?(?="")", RegexOptions.Compiled);
int numberOfLogonSessionsWithExplorer = 1;
using (var managemntObjectSearcher = new ManagementObjectSearcher(scope, new SelectQuery("SELECT * FROM Win32_SessionProcess")))
{
numberOfLogonSessionsWithExplorer = managemntObjectSearcher.Get()
.Cast<ManagementObject>()
.Where(mo => explorerProcesses.Contains(REprocessid.Match(mo["Dependent"].ToString()).Value))
.Select(mo => mo["Antecedent"].ToString())
.Distinct()
.Count();
}
int numberOfUserDesktops = 1;
// this can fail sometimes, that's why we set numberOfUserDesktops to 1
try
{
using (var managementObjectSearcher = new ManagementObjectSearcher(scope, new SelectQuery("select * from win32_Perfrawdata_TermService_TerminalServicesSession")))
{
numberOfUserDesktops = managementObjectSearcher.Get().Count - 1; // don't count Service desktop
}
}
catch
{
}
var numberOfLogonSessionsWithExplorer = new ManagementObjectSearcher(scope, new SelectQuery("SELECT * FROM Win32_SessionProcess")).Get()
.Cast<ManagementObject>()
.Where(mo => explorerProcesses.Contains(REprocessid.Match(mo["Dependent"].ToString()).Value))
.Select(mo => mo["Antecedent"].ToString())
.Distinct()
.Count();
var numberOfUserDesktops = new ManagementObjectSearcher(scope, new SelectQuery("select * from win32_Perfrawdata_TermService_TerminalServicesSession")).Get().Count - 1; // don't count Service desktop
var numberOflogonUIProcesses = Process.GetProcessesByName("LogonUI").Length;
if (numberOflogonUIProcesses >= numberOfUserDesktops)
@ -91,10 +111,13 @@ namespace hass_workstation_service.Domain.Sensors
return PCUserStatuses.LoggedOff;
}
else
{
return PCUserStatuses.InUse;
}
}
catch
catch (Exception e)
{
Log.Logger.Error(e, "Exception in SessionStateSensor");
return PCUserStatuses.Unknown;
}
}

@ -34,16 +34,17 @@ namespace hass_workstation_service.Domain.Sensors
public override string GetState()
{
ManagementObjectCollection collection = _searcher.Get();
foreach (ManagementObject mo in collection)
using (ManagementObjectCollection collection = _searcher.Get())
{
foreach (PropertyData property in mo.Properties)
foreach (ManagementObject mo in collection)
{
return property.Value.ToString();
foreach (PropertyData property in mo.Properties)
{
return property.Value.ToString();
}
}
return "";
}
return "";
}
}

@ -9,14 +9,16 @@ namespace hass_workstation_service.Domain.Sensors
{
public class WebcamActiveSensor : AbstractSensor
{
public override string Domain => "binary_sensor";
public WebcamActiveSensor(MqttPublisher publisher, int? updateInterval = null, string name = "WebcamActive", Guid id = default) : base(publisher, name ?? "WebcamActive", updateInterval ?? 10, id)
{
}
public override string GetState()
{
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
return IsWebCamInUseRegistry() ? "True" : "False";
return IsWebCamInUseRegistry() ? "ON" : "OFF";
}
else
{
@ -31,8 +33,7 @@ namespace hass_workstation_service.Domain.Sensors
Unique_id = this.Id.ToString(),
Device = this.Publisher.DeviceConfigModel,
State_topic = $"homeassistant/{this.Domain}/{Publisher.DeviceConfigModel.Name}/{this.Name}/state",
Icon = "mdi:webcam",
Availability_topic = $"homeassistant/{this.Domain}/{Publisher.DeviceConfigModel.Name}/availability"
Availability_topic = $"homeassistant/sensor/{Publisher.DeviceConfigModel.Name}/availability"
});
}

@ -4,7 +4,7 @@ https://go.microsoft.com/fwlink/?LinkID=208121.
-->
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<ApplicationRevision>32</ApplicationRevision>
<ApplicationRevision>35</ApplicationRevision>
<ApplicationVersion>1.0.0.*</ApplicationVersion>
<BootstrapperEnabled>True</BootstrapperEnabled>
<Configuration>Release</Configuration>

@ -24,17 +24,14 @@
</ItemGroup>
<ItemGroup>
<None Remove="hass-workstation-service.pdb" />
<None Remove="libSkiaSharp.dll" />
<None Remove="UserInterface.exe" />
<None Remove="UserInterface.pdb" />
</ItemGroup>
<ItemGroup>
<Content Include="libSkiaSharp.dll">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="UserInterface.exe">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
</ItemGroup>
@ -46,13 +43,20 @@
<ItemGroup>
<PackageReference Include="JKang.IpcServiceFramework.Hosting.NamedPipe" Version="3.1.0" />
<PackageReference Include="LibreHardwareMonitorLib" Version="0.8.7" />
<PackageReference Include="Microsoft.Extensions.Hosting" Version="5.0.0" />
<PackageReference Include="Microsoft.Win32.Registry" Version="5.0.0" />
<PackageReference Include="MQTTnet" Version="3.0.13" />
<PackageReference Include="MQTTnet.Extensions.ManagedClient" Version="3.0.13" />
<PackageReference Include="MQTTnet" Version="3.0.15" />
<PackageReference Include="MQTTnet.Extensions.ManagedClient" Version="3.0.15" />
<PackageReference Include="Serilog.Extensions.Logging.File" Version="2.0.0" />
<PackageReference Include="Serilog.Sinks.Console" Version="3.1.1" />
<PackageReference Include="Serilog.Sinks.File" Version="4.1.0" />
<PackageReference Include="System.Management" Version="5.0.0" />
</ItemGroup>
<ItemGroup>
<Reference Include="CoreAudio">
<HintPath>..\lib\CoreAudio.dll</HintPath>
</Reference>
</ItemGroup>
</Project>

Binary file not shown.

@ -0,0 +1 @@
This folder should be used for referenced libraries that are not managed by Nuget.
Loading…
Cancel
Save