Merge branch 'develop' into notify

wip - not working
notify
sleevezipper 3 years ago
commit 191ddfd954

@ -26,6 +26,8 @@ Note: You'll get a Windows Smartscreen warning because the code was self signed.
### Standalone
You'll need [.NET 5 Runtime](https://dotnet.microsoft.com/download/dotnet/current/runtime) installed.
If you don't want to use the installer, you can find the standalone version releases on GitHub [here](https://github.com/sleevezipper/hass-workstation-service/releases). Unpack all files to a folder and run `hass-workstation-service.exe`. This is the background service and you can use `UserInterface.exe` to configure the service. There is no automatic (or prompted) updating in the standalone version.
### Updating
@ -58,14 +60,11 @@ This sensor exposes the name of the currently focused window.
### WebcamActive
This sensor shows if the webcam is currently being used. It has two detection modes:
- Registry - this is the preferred method. This will work from Windows 10 version 1903 and higher.
- OpenCV - this method tries to access the webcam and if that fails, it assumes it is currently in use. This will flash the webcam activity light at every update interval. It also uses more CPU cycles and memory.
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 wil work from Windows 10 version 1903 and higher.
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
@ -103,6 +102,10 @@ 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.
### IdleTime
This sensor returns the amount of seconds the workstation has been idle for. It starts counting the moment you stop typing or moving your mouse.
### Dummy
This sensor spits out a random number every second. Useful for testing, maybe you'll find some other use for it.

@ -8,7 +8,7 @@ https://go.microsoft.com/fwlink/?LinkID=208121.
<Platform>Any CPU</Platform>
<PublishDir>..\hass-workstation-service\</PublishDir>
<PublishProtocol>FileSystem</PublishProtocol>
<TargetFramework>netcoreapp3.1</TargetFramework>
<TargetFramework>net5.0</TargetFramework>
<RuntimeIdentifier>win-x64</RuntimeIdentifier>
<SelfContained>false</SelfContained>
<PublishSingleFile>True</PublishSingleFile>

@ -8,7 +8,7 @@ https://go.microsoft.com/fwlink/?LinkID=208121.
<Platform>Any CPU</Platform>
<PublishDir>bin\Userinterface-standalone</PublishDir>
<PublishProtocol>FileSystem</PublishProtocol>
<TargetFramework>netcoreapp3.1</TargetFramework>
<TargetFramework>net5.0</TargetFramework>
<RuntimeIdentifier>win-x64</RuntimeIdentifier>
<SelfContained>true</SelfContained>
<PublishSingleFile>False</PublishSingleFile>

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>netcoreapp3.1</TargetFramework>
<TargetFramework>net5.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<Folder Include="Models\" />
@ -17,6 +17,8 @@
<PackageReference Include="Avalonia.ReactiveUI" Version="0.9.12" />
<PackageReference Include="Avalonia.Win32" Version="0.9.12" />
<PackageReference Include="JKang.IpcServiceFramework.Client.NamedPipe" Version="3.1.0" />
<PackageReference Include="Microsoft.Windows.CsWinRT" Version="1.1.0" />
<PackageReference Include="Serilog.Sinks.Console" Version="3.1.1" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\hass-workstation-service\hass-workstation-service.csproj" />

@ -9,7 +9,6 @@ namespace UserInterface.ViewModels
public class AddSensorViewModel : ViewModelBase
{
private AvailableSensors selectedType;
private WebcamDetectionMode selectedDetectionMode;
private string description;
private bool showQueryInput;
@ -32,7 +31,6 @@ namespace UserInterface.ViewModels
public AvailableSensors SelectedType { get => selectedType; set => this.RaiseAndSetIfChanged(ref selectedType, value); }
public WebcamDetectionMode SelectedDetectionMode { get => selectedDetectionMode; set => this.RaiseAndSetIfChanged(ref selectedDetectionMode, value); }
public string Name { get; set; }
public string Query { get; set; }

@ -2,6 +2,7 @@
using ReactiveUI;
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Text;
namespace UserInterface.ViewModels
@ -13,17 +14,28 @@ namespace UserInterface.ViewModels
private string password;
private string message;
private bool isConnected;
private int? port;
private bool useTLS;
public bool IsConnected { get => isConnected; set => this.RaiseAndSetIfChanged(ref isConnected, value); }
public string Message { get => message; set => this.RaiseAndSetIfChanged(ref message, value); }
[Required(AllowEmptyStrings = false)]
public string Host { get => host; set => this.RaiseAndSetIfChanged(ref host, value); }
public string Username { get => username; set => this.RaiseAndSetIfChanged(ref username, value); }
public string Password { get => password; set => this.RaiseAndSetIfChanged(ref password, value); }
[Required]
[Range(1, 65535)]
public int? Port { get => port; set => this.RaiseAndSetIfChanged(ref port, value); }
public bool UseTLS { get => useTLS; set => this.RaiseAndSetIfChanged(ref useTLS, value); }
public void Update(MqttSettings settings)
{
this.Host = settings.Host;
this.Username = settings.Username;
this.Password = settings.Password;
this.Port = settings.Port;
this.UseTLS = settings.UseTLS;
}
public void UpdateStatus(MqqtClientStatus status)

@ -1,11 +1,18 @@
using ReactiveUI;
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Text;
namespace UserInterface.ViewModels
{
public class ViewModelBase : ReactiveObject
{
public bool IsValid<T>(T obj, out ICollection<ValidationResult> results)
{
results = new List<ValidationResult>();
return Validator.TryValidateObject(obj, new ValidationContext(obj), results, true);
}
}
}

@ -26,8 +26,6 @@
<ContentControl IsVisible="{Binding ShowWindowNameInput}" Margin="0 20 0 5">Window name</ContentControl>
<TextBlock TextWrapping="Wrap" MaxWidth="300" FontStyle="Italic" IsVisible="{Binding ShowWindowNameInput}" Margin="0 0 0 10">This is case-insensitive and loosely matched. A window called "Spotify Premium" will match "spotify" or "premium".</TextBlock>
<TextBox IsVisible="{Binding ShowWindowNameInput}" Text="{Binding WindowName}" Watermark="Visual Studio Code" HorizontalAlignment="Left" MinWidth="300"/>
<ContentControl IsVisible="{Binding ShowDetectionModeOptions}" Margin="0 20 0 10">Detection mode</ContentControl>
<ComboBox IsVisible="{Binding ShowDetectionModeOptions}" x:Name="DetectionModeComboBox" SelectedItem="{Binding SelectedDetectionMode}" MinHeight="27"></ComboBox>
<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>
</Window>

@ -29,9 +29,6 @@ namespace UserInterface.Views
this.comboBox = this.FindControl<ComboBox>("ComboBox");
this.comboBox.Items = Enum.GetValues(typeof(AvailableSensors)).Cast<AvailableSensors>();
this.comboBox = this.FindControl<ComboBox>("DetectionModeComboBox");
this.comboBox.Items = Enum.GetValues(typeof(WebcamDetectionMode)).Cast<WebcamDetectionMode>();
// register IPC clients
ServiceProvider serviceProvider = new ServiceCollection()
.AddNamedPipeIpcClient<ServiceContractInterfaces>("addsensor", pipeName: "pipeinternal")
@ -51,7 +48,7 @@ namespace UserInterface.Views
public async void Save(object sender, RoutedEventArgs args)
{
var item = ((AddSensorViewModel)this.DataContext);
dynamic model = new { item.Name, item.Query, item.UpdateInterval, item.WindowName, DetectionMode = item.SelectedDetectionMode };
dynamic model = new { item.Name, item.Query, item.UpdateInterval, item.WindowName};
string json = JsonSerializer.Serialize(model);
await this.client.InvokeAsync(x => x.AddSensor(item.SelectedType, json));
Close();
@ -139,6 +136,13 @@ namespace UserInterface.Views
item.ShowWindowNameInput = true;
item.UpdateInterval = 5;
break;
case AvailableSensors.IdleTimeSensor:
item.Description = "This sensor returns the amount of seconds the workstation has been idle for. ";
item.MoreInfoLink = "https://github.com/sleevezipper/hass-workstation-service#idletime";
item.ShowQueryInput = false;
item.ShowWindowNameInput = false;
item.UpdateInterval = 5;
break;
default:
item.Description = null;
item.MoreInfoLink = null;

@ -10,6 +10,17 @@
<TextBlock IsVisible="{Binding !IsConnected}" Foreground="Red" Text="{Binding Message}"></TextBlock >
<ContentControl Margin="0 20 0 10">IP or hostname</ContentControl>
<TextBox Text="{Binding Host}" HorizontalAlignment="Left" Width="100" Watermark="192.168.1.200"/>
<StackPanel Orientation="Horizontal">
<StackPanel Orientation="Vertical">
<ContentControl Margin="0 20 0 10">Port</ContentControl>
<TextBox Text="{Binding Port}" HorizontalAlignment="Left" Width="50" Watermark="1883"/>
</StackPanel>
<StackPanel Orientation="Vertical" Margin="30 0 0 0">
<ContentControl Margin="0 20 0 10">Use TLS</ContentControl>
<CheckBox IsChecked="{Binding UseTLS}" HorizontalAlignment="Left" Margin="0 3 0 0"/>
</StackPanel>
</StackPanel>
<ContentControl Margin="0 20 0 10">Username</ContentControl>
<TextBox Text="{Binding Username}" MinWidth="150"/>
<ContentControl Margin="0 20 0 10">Password</ContentControl>

@ -10,6 +10,8 @@ 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;
namespace UserInterface.Views
{
@ -45,7 +47,11 @@ namespace UserInterface.Views
public void Configure(object sender, RoutedEventArgs args)
{
var model = (BrokerSettingsViewModel)this.DataContext;
var result = this.client.InvokeAsync(x => x.WriteMqttBrokerSettingsAsync(new MqttSettings() { Host = model.Host, Username = model.Username, Password = model.Password ?? "" }));
ICollection<ValidationResult> results;
if (model.IsValid(model, out results))
{
var result = this.client.InvokeAsync(x => x.WriteMqttBrokerSettingsAsync(new MqttSettings() { Host = model.Host, Username = model.Username, Password = model.Password ?? "", Port = model.Port, UseTLS = model.UseTLS }));
}
}
public async void GetSettings()

@ -1,4 +1,4 @@
using hass_workstation_service.Communication.InterProcesCommunication.Models;
using hass_workstation_service.Communication.InterProcesCommunication.Models;
using hass_workstation_service.Communication.NamedPipe;
using hass_workstation_service.Communication.Util;
using hass_workstation_service.Data;
@ -107,20 +107,7 @@ namespace hass_workstation_service.Communication.InterProcesCommunication
sensorToCreate = new ActiveWindowSensor(this._publisher, (int)model.UpdateInterval, model.Name);
break;
case AvailableSensors.WebcamActiveSensor:
DetectionMode detectionMode;
switch ((WebcamDetectionMode)model.DetectionMode)
{
case WebcamDetectionMode.Registry:
detectionMode = DetectionMode.Registry;
break;
case WebcamDetectionMode.OpenCV:
detectionMode = DetectionMode.OpenCV;
break;
default:
detectionMode = DetectionMode.Registry;
break;
}
sensorToCreate = new WebcamActiveSensor(this._publisher, (int)model.UpdateInterval, model.Name, detectionMode);
sensorToCreate = new WebcamActiveSensor(this._publisher, (int)model.UpdateInterval, model.Name);
break;
case AvailableSensors.MicrophoneActiveSensor:
sensorToCreate = new MicrophoneActiveSensor(this._publisher, (int)model.UpdateInterval, model.Name);
@ -128,6 +115,9 @@ namespace hass_workstation_service.Communication.InterProcesCommunication
case AvailableSensors.NamedWindowSensor:
sensorToCreate = new NamedWindowSensor(this._publisher, model.WindowName, model.Name, (int)model.UpdateInterval);
break;
case AvailableSensors.IdleTimeSensor:
sensorToCreate = new IdleTimeSensor(this._publisher,(int)model.UpdateInterval, model.Name);
break;
default:
Log.Logger.Error("Unknown sensortype");
break;

@ -1,4 +1,4 @@
using hass_workstation_service.Domain.Sensors;
using hass_workstation_service.Domain.Sensors;
using System;
using System.Collections.Generic;
using System.Text;
@ -10,6 +10,8 @@ namespace hass_workstation_service.Communication.InterProcesCommunication.Models
public string Host { get; set; }
public string Username { get; set; }
public string Password { get; set; }
public int? Port { get; set; }
public bool UseTLS { get; set; }
}
public class MqqtClientStatus
@ -39,12 +41,7 @@ namespace hass_workstation_service.Communication.InterProcesCommunication.Models
WebcamActiveSensor,
MicrophoneActiveSensor,
ActiveWindowSensor,
NamedWindowSensor
}
public enum WebcamDetectionMode
{
Registry,
OpenCV
NamedWindowSensor,
IdleTimeSensor
}
}

@ -12,6 +12,7 @@ using MQTTnet;
using MQTTnet.Adapter;
using MQTTnet.Client;
using MQTTnet.Client.Options;
using MQTTnet.Exceptions;
using Serilog;
using static hass_workstation_service.Domain.Notify.Notifier;
@ -150,7 +151,11 @@ namespace hass_workstation_service.Communication
this._mqttClientMessage = ex.ResultCode.ToString();
Log.Logger.Error("Could not connect to broker: " + ex.ResultCode.ToString());
}
catch (MqttCommunicationException ex)
{
this._mqttClientMessage = ex.ToString();
Log.Logger.Error("Could not connect to broker: " + ex.Message);
}
}
public MqqtClientStatus GetStatus()

@ -2,6 +2,8 @@ using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using System.Security;
using System.Text.Json;
using System.Threading.Tasks;
@ -23,8 +25,8 @@ namespace hass_workstation_service.Data
public ICollection<AbstractSensor> ConfiguredSensors { get; private set; }
public Action<IMqttClientOptions> MqqtConfigChangedHandler { get; set; }
public bool _brokerSettingsFileLocked { get; set; }
public bool _sensorsSettingsFileLocked { get; set; }
private bool BrokerSettingsFileLocked { get; set; }
private bool SensorsSettingsFileLocked { get; set; }
private readonly string path = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "Hass Workstation Service");
@ -32,12 +34,12 @@ namespace hass_workstation_service.Data
{
if (!File.Exists(Path.Combine(path, "mqttbroker.json")))
{
File.Create(Path.Combine(path, "mqttbroker.json"));
File.Create(Path.Combine(path, "mqttbroker.json")).Close();
}
if (!File.Exists(Path.Combine(path, "configured-sensors.json")))
{
File.Create(Path.Combine(path, "configured-sensors.json"));
File.Create(Path.Combine(path, "configured-sensors.json")).Close();
}
ConfiguredSensors = new List<AbstractSensor>();
@ -45,11 +47,11 @@ namespace hass_workstation_service.Data
public async void ReadSensorSettings(MqttPublisher publisher)
{
while (this._sensorsSettingsFileLocked)
while (this.SensorsSettingsFileLocked)
{
await Task.Delay(500);
}
this._sensorsSettingsFileLocked = true;
this.SensorsSettingsFileLocked = true;
List<ConfiguredSensor> sensors = new List<ConfiguredSensor>();
using (var stream = new FileStream(Path.Combine(path, "configured-sensors.json"), FileMode.Open))
{
@ -59,7 +61,7 @@ namespace hass_workstation_service.Data
sensors = await JsonSerializer.DeserializeAsync<List<ConfiguredSensor>>(stream);
}
stream.Close();
this._sensorsSettingsFileLocked = false;
this.SensorsSettingsFileLocked = false;
}
foreach (ConfiguredSensor configuredSensor in sensors)
@ -83,7 +85,10 @@ namespace hass_workstation_service.Data
sensor = new CPULoadSensor(publisher, configuredSensor.UpdateInterval, configuredSensor.Name, configuredSensor.Id);
break;
case "MemoryUsageSensor":
sensor = new MemoryUsageSensor(publisher, configuredSensor.UpdateInterval, configuredSensor.Name, configuredSensor.Id);
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
sensor = new MemoryUsageSensor(publisher, configuredSensor.UpdateInterval, configuredSensor.Name, configuredSensor.Id);
}
break;
case "ActiveWindowSensor":
sensor = new ActiveWindowSensor(publisher, configuredSensor.UpdateInterval, configuredSensor.Name, configuredSensor.Id);
@ -91,8 +96,11 @@ namespace hass_workstation_service.Data
case "NamedWindowSensor":
sensor = new NamedWindowSensor(publisher, configuredSensor.WindowName, configuredSensor.Name, configuredSensor.UpdateInterval, configuredSensor.Id);
break;
case "IdleTimeSensor":
sensor = new IdleTimeSensor(publisher, configuredSensor.UpdateInterval, configuredSensor.Name, configuredSensor.Id);
break;
case "WebcamActiveSensor":
sensor = new WebcamActiveSensor(publisher, configuredSensor.UpdateInterval, configuredSensor.Name, configuredSensor.DetectionMode, configuredSensor.Id);
sensor = new WebcamActiveSensor(publisher, configuredSensor.UpdateInterval, configuredSensor.Name, configuredSensor.Id);
break;
case "MicrophoneActiveSensor":
sensor = new MicrophoneActiveSensor(publisher, configuredSensor.UpdateInterval, configuredSensor.Name, configuredSensor.Id);
@ -115,8 +123,12 @@ namespace hass_workstation_service.Data
{
var mqttClientOptions = new MqttClientOptionsBuilder()
.WithTcpServer(configuredBroker.Host)
// .WithTls()
.WithTcpServer(configuredBroker.Host, configuredBroker.Port)
.WithTls(new MqttClientOptionsBuilderTlsParameters()
{
UseTls = configuredBroker.UseTLS,
AllowUntrustedCertificates = true
})
.WithCredentials(configuredBroker.Username, configuredBroker.Password.ToString())
.Build();
return mqttClientOptions;
@ -134,11 +146,11 @@ namespace hass_workstation_service.Data
/// <returns></returns>
public async Task<ConfiguredMqttBroker> ReadMqttSettingsAsync()
{
while (this._brokerSettingsFileLocked)
while (this.BrokerSettingsFileLocked)
{
await Task.Delay(500);
}
this._brokerSettingsFileLocked = true;
this.BrokerSettingsFileLocked = true;
ConfiguredMqttBroker configuredBroker = null;
using (FileStream stream = new FileStream(Path.Combine(path, "mqttbroker.json"), FileMode.Open))
{
@ -150,17 +162,17 @@ namespace hass_workstation_service.Data
stream.Close();
}
this._brokerSettingsFileLocked = false;
this.BrokerSettingsFileLocked = false;
return configuredBroker;
}
public async void WriteSettingsAsync()
{
while (this._sensorsSettingsFileLocked)
while (this.SensorsSettingsFileLocked)
{
await Task.Delay(500);
}
this._sensorsSettingsFileLocked = true;
this.SensorsSettingsFileLocked = true;
List<ConfiguredSensor> configuredSensorsToSave = new List<ConfiguredSensor>();
using (FileStream stream = new FileStream(Path.Combine(path, "configured-sensors.json"), FileMode.Open))
{
@ -168,14 +180,14 @@ namespace hass_workstation_service.Data
Log.Logger.Information($"writing configured sensors to: {stream.Name}");
foreach (AbstractSensor sensor in this.ConfiguredSensors)
{
if (sensor is WMIQuerySensor)
if (sensor is WMIQuerySensor wmiSensor)
{
var wmiSensor = (WMIQuerySensor)sensor;
#pragma warning disable CA1416 // Validate platform compatibility. We ignore it here because this would never happen. A cleaner solution may be implemented later.
configuredSensorsToSave.Add(new ConfiguredSensor() { Id = wmiSensor.Id, Name = wmiSensor.Name, Type = wmiSensor.GetType().Name, UpdateInterval = wmiSensor.UpdateInterval, Query = wmiSensor.Query });
#pragma warning restore CA1416 // Validate platform compatibility
}
if (sensor is NamedWindowSensor)
if (sensor is NamedWindowSensor namedWindowSensor)
{
var namedWindowSensor = (NamedWindowSensor)sensor;
configuredSensorsToSave.Add(new ConfiguredSensor() { Id = namedWindowSensor.Id, Name = namedWindowSensor.Name, Type = namedWindowSensor.GetType().Name, UpdateInterval = namedWindowSensor.UpdateInterval, WindowName = namedWindowSensor.WindowName });
}
else
@ -188,7 +200,7 @@ namespace hass_workstation_service.Data
await JsonSerializer.SerializeAsync(stream, configuredSensorsToSave);
stream.Close();
}
this._sensorsSettingsFileLocked = false;
this.SensorsSettingsFileLocked = false;
}
public void AddConfiguredSensor(AbstractSensor sensor)
@ -226,11 +238,11 @@ namespace hass_workstation_service.Data
/// <param name="settings"></param>
public async void WriteMqttBrokerSettingsAsync(MqttSettings settings)
{
while (this._brokerSettingsFileLocked)
while (this.BrokerSettingsFileLocked)
{
await Task.Delay(500);
}
this._brokerSettingsFileLocked = true;
this.BrokerSettingsFileLocked = true;
using (FileStream stream = new FileStream(Path.Combine(path, "mqttbroker.json"), FileMode.Open))
{
stream.SetLength(0);
@ -240,13 +252,15 @@ namespace hass_workstation_service.Data
{
Host = settings.Host,
Username = settings.Username,
Password = settings.Password ?? ""
Password = settings.Password ?? "",
Port = settings.Port ?? 1883,
UseTLS = settings.UseTLS
};
await JsonSerializer.SerializeAsync(stream, configuredBroker);
stream.Close();
}
this._brokerSettingsFileLocked = false;
this.BrokerSettingsFileLocked = false;
this.MqqtConfigChangedHandler.Invoke(await this.GetMqttClientOptionsAsync());
}
@ -257,7 +271,9 @@ namespace hass_workstation_service.Data
{
Host = broker?.Host,
Username = broker?.Username,
Password = broker?.Password
Password = broker?.Password,
Port = broker?.Port,
UseTLS = broker?.UseTLS ?? false
};
}
@ -265,6 +281,7 @@ namespace hass_workstation_service.Data
/// Enable or disable autostarting the background service. It does this by adding the application shortcut (appref-ms) to the registry run key for the current user
/// </summary>
/// <param name="enable"></param>
[SupportedOSPlatform("windows")]
public void EnableAutoStart(bool enable)
{
if (enable)
@ -299,6 +316,7 @@ namespace hass_workstation_service.Data
}
}
[SupportedOSPlatform("windows")]
public bool IsAutoStartEnabled()
{
RegistryKey rkApp = Registry.CurrentUser.OpenSubKey(@"SOFTWARE\Microsoft\Windows\CurrentVersion\Run", true);

@ -5,8 +5,35 @@ namespace hass_workstation_service.Data
{
public class ConfiguredMqttBroker
{
private string username;
private string password;
private int? port;
public string Host { get; set; }
public string Username { get; set; }
public string Password { get; set; }
public int Port { get => port ?? 1883; set => port = value; }
public bool UseTLS { get; set; }
public string Username
{
get
{
if (username != null) return username;
return "";
}
set => username = value;
}
public string Password
{
get
{
if (password != null) return password;
return "";
}
set => password = value;
}
}
}

@ -11,6 +11,5 @@ namespace hass_workstation_service.Data
public string Query { get; set; }
public int? UpdateInterval { get; set; }
public string WindowName { get; set; }
public DetectionMode DetectionMode { get; set; }
}
}

@ -19,7 +19,7 @@ namespace hass_workstation_service.Domain.Sensors
public MqttPublisher Publisher { get; protected set; }
public AbstractSensor(MqttPublisher publisher, string name, int updateInterval = 10, Guid id = default(Guid))
{
if (id == Guid.Empty || id == null)
if (id == Guid.Empty)
{
this.Id = Guid.NewGuid();
}

@ -4,10 +4,13 @@ using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Management;
using System.Runtime.Versioning;
using System.Text;
namespace hass_workstation_service.Domain.Sensors
{
[SupportedOSPlatform("windows")]
public class CPULoadSensor : WMIQuerySensor
{
public CPULoadSensor(MqttPublisher publisher, int? updateInterval = null, string name = "CPULoadSensor", Guid id = default) : base(publisher, "SELECT PercentProcessorTime FROM Win32_PerfFormattedData_PerfOS_Processor", updateInterval ?? 10, name ?? "CPULoadSensor", id)
@ -26,6 +29,8 @@ namespace hass_workstation_service.Domain.Sensors
Unit_of_measurement = "%"
});
}
[SupportedOSPlatform("windows")]
public override string GetState()
{
ManagementObjectCollection collection = _searcher.Get();

@ -0,0 +1,67 @@
using hass_workstation_service.Communication;
using System;
using System.Runtime.InteropServices;
namespace hass_workstation_service.Domain.Sensors
{
public class IdleTimeSensor : AbstractSensor
{
public IdleTimeSensor(MqttPublisher publisher, int? updateInterval = 10, string name = "IdleTime", Guid id = default) : base(publisher, name ?? "IdleTime", updateInterval ?? 10, id){}
public override AutoDiscoveryConfigModel GetAutoDiscoveryConfig()
{
return this._autoDiscoveryConfigModel ?? SetAutoDiscoveryConfigModel(new AutoDiscoveryConfigModel()
{
Name = this.Name,
Unique_id = this.Id.ToString(),
Device = this.Publisher.DeviceConfigModel,
State_topic = $"homeassistant/sensor/{Publisher.DeviceConfigModel.Name}/{this.Name}/state",
Icon = "mdi:window-maximize",
});
}
public override string GetState()
{
return GetLastInputTime().ToString();
}
static int GetLastInputTime()
{
int idleTime = 0;
LASTINPUTINFO lastInputInfo = new LASTINPUTINFO();
lastInputInfo.cbSize = Marshal.SizeOf(lastInputInfo);
lastInputInfo.dwTime = 0;
int envTicks = Environment.TickCount;
if (GetLastInputInfo(ref lastInputInfo))
{
int lastInputTick = Convert.ToInt32(lastInputInfo.dwTime);
idleTime = envTicks - lastInputTick;
}
return ((idleTime > 0) ? (idleTime / 1000) : idleTime);
}
[DllImport("User32.dll")]
private static extern bool GetLastInputInfo(ref LASTINPUTINFO plii);
[StructLayout(LayoutKind.Sequential)]
struct LASTINPUTINFO
{
public static readonly int SizeOf = Marshal.SizeOf(typeof(LASTINPUTINFO));
[MarshalAs(UnmanagedType.U4)]
public int cbSize;
[MarshalAs(UnmanagedType.U4)]
public UInt32 dwTime;
}
}
}

@ -3,10 +3,13 @@ using System;
using System.Collections.Generic;
using System.Globalization;
using System.Management;
using System.Runtime.Versioning;
using System.Text;
namespace hass_workstation_service.Domain.Sensors
{
[SupportedOSPlatform("windows")]
public class MemoryUsageSensor : WMIQuerySensor
{
public MemoryUsageSensor(MqttPublisher publisher, int? updateInterval = null, string name = "WMIQuerySensor", Guid id = default) : base(publisher, "SELECT FreePhysicalMemory,TotalVisibleMemorySize FROM Win32_OperatingSystem", updateInterval ?? 10, name, id)

@ -2,6 +2,8 @@
using Microsoft.Win32;
using System;
using System.Linq;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
namespace hass_workstation_service.Domain.Sensors
{
@ -13,7 +15,11 @@ namespace hass_workstation_service.Domain.Sensors
}
public override string GetState()
{
return IsMicrophoneInUse() ? "True" : "False";
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
return IsMicrophoneInUse() ? "True" : "False";
}
else return "unsupported";
}
public override AutoDiscoveryConfigModel GetAutoDiscoveryConfig()
{
@ -27,6 +33,7 @@ namespace hass_workstation_service.Domain.Sensors
});
}
[SupportedOSPlatform("windows")]
private bool IsMicrophoneInUse()
{
using (var key = Registry.LocalMachine.OpenSubKey(@"SOFTWARE\Microsoft\Windows\CurrentVersion\CapabilityAccessManager\ConsentStore\microphone\NonPackaged"))

@ -2,16 +2,19 @@
using System;
using System.Collections.Generic;
using System.Management;
using System.Runtime.Versioning;
using System.Text;
namespace hass_workstation_service.Domain.Sensors
{
[SupportedOSPlatform("windows")]
public class WMIQuerySensor : AbstractSensor
{
public string Query { get; private set; }
protected readonly ObjectQuery _objectQuery;
protected readonly ManagementObjectSearcher _searcher;
public WMIQuerySensor(MqttPublisher publisher, string query, int? updateInterval = null, string name = "WMIQuerySensor", Guid id = default(Guid)) : base(publisher, name ?? "WMIQuerySensor", updateInterval ?? 10, id)
public WMIQuerySensor(MqttPublisher publisher, string query, int? updateInterval = null, string name = "WMIQuerySensor", Guid id = default) : base(publisher, name ?? "WMIQuerySensor", updateInterval ?? 10, id)
{
this.Query = query;
_objectQuery = new ObjectQuery(this.Query);

@ -1,35 +1,27 @@
using hass_workstation_service.Communication;
using Microsoft.Win32;
using OpenCvSharp;
using System;
using System.Linq;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
namespace hass_workstation_service.Domain.Sensors
{
public enum DetectionMode
{
Registry,
OpenCV
}
public class WebcamActiveSensor : AbstractSensor
{
public DetectionMode DetectionMode { get; private set; }
public WebcamActiveSensor(MqttPublisher publisher, int? updateInterval = null, string name = "WebcamActive", DetectionMode detectionMode = DetectionMode.Registry, Guid id = default(Guid)) : base(publisher, name, updateInterval ?? 10, id)
public WebcamActiveSensor(MqttPublisher publisher, int? updateInterval = null, string name = "WebcamActive", Guid id = default) : base(publisher, name, updateInterval ?? 10, id)
{
this.DetectionMode = detectionMode;
}
public override string GetState()
{
switch (this.DetectionMode)
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
case DetectionMode.Registry:
return IsWebCamInUseRegistry() ? "True" : "False";
case DetectionMode.OpenCV:
return IsWebCamInUseOpenCV() ? "True" : "False";
default:
return "Error";
return IsWebCamInUseRegistry() ? "True" : "False";
}
else
{
return "unsupported";
}
}
public override AutoDiscoveryConfigModel GetAutoDiscoveryConfig()
{
@ -43,35 +35,7 @@ namespace hass_workstation_service.Domain.Sensors
});
}
private bool IsWebCamInUseOpenCV()
{
try
{
VideoCapture capture = new VideoCapture(0);
OutputArray image = OutputArray.Create(new Mat());
// capture.Read() return false if it doesn't succeed in capturing
if (capture.Read(image))
{
capture.Release();
capture.Dispose();
return false;
}
else
{
capture.Release();
capture.Dispose();
return true;
}
}
catch (Exception)
{
return false;
}
}
[SupportedOSPlatform("windows")]
private bool IsWebCamInUseRegistry()
{
using (var key = Registry.LocalMachine.OpenSubKey(@"SOFTWARE\Microsoft\Windows\CurrentVersion\CapabilityAccessManager\ConsentStore\webcam\NonPackaged"))

@ -73,13 +73,14 @@ namespace hass_workstation_service
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.UseSerilog()
.ConfigureLogging((hostContext, loggingBuilder) =>
loggingBuilder.AddSerilog(dispose: true))
.ConfigureServices((hostContext, services) =>
{
var deviceConfig = new DeviceConfigModel
{
Name = Environment.MachineName,
Identifiers = "hass-workstation-service",
Identifiers = "hass-workstation-service" + Environment.MachineName,
Manufacturer = Environment.UserName,
Model = Environment.OSVersion.ToString(),
Sw_version = GetVersion()

@ -33,15 +33,15 @@ https://go.microsoft.com/fwlink/?LinkID=208121.
<SignatureAlgorithm>sha256RSA</SignatureAlgorithm>
<SignManifests>True</SignManifests>
<SupportUrl>https://github.com/sleevezipper/hass-workstation-service</SupportUrl>
<TargetFramework>netcoreapp3.1</TargetFramework>
<TargetFramework>net5.0</TargetFramework>
<TrustUrlParameters>True</TrustUrlParameters>
<UpdateEnabled>True</UpdateEnabled>
<UpdateMode>Foreground</UpdateMode>
</PropertyGroup>
<ItemGroup>
<BootstrapperPackage Include="Microsoft.NetCore.CoreRuntime.3.1.x64">
<BootstrapperPackage Include="Microsoft.NetCore.CoreRuntime.5.0.x64">
<Install>true</Install>
<ProductName>.NET Core Runtime 3.1.10 (x64)</ProductName>
<ProductName>.NET Runtime 5.0.1 (x64)</ProductName>
</BootstrapperPackage>
</ItemGroup>
</Project>

@ -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>1</ApplicationRevision>
<ApplicationRevision>6</ApplicationRevision>
<ApplicationVersion>1.0.0.*</ApplicationVersion>
<BootstrapperEnabled>True</BootstrapperEnabled>
<Configuration>Release</Configuration>
@ -19,23 +19,23 @@ https://go.microsoft.com/fwlink/?LinkID=208121.
<MapFileExtensions>true</MapFileExtensions>
<OpenBrowserOnPublish>false</OpenBrowserOnPublish>
<Platform>Any CPU</Platform>
<PublishDir>bin\publish\</PublishDir>
<PublishUrl>bin\publish\</PublishUrl>
<PublishDir>bin\clickonce-framework-dependent\</PublishDir>
<PublishUrl>bin\clickonce-framework-dependent\</PublishUrl>
<PublishProtocol>ClickOnce</PublishProtocol>
<PublishReadyToRun>True</PublishReadyToRun>
<PublishSingleFile>True</PublishSingleFile>
<PublishReadyToRun>False</PublishReadyToRun>
<PublishSingleFile>False</PublishSingleFile>
<RuntimeIdentifier>win-x64</RuntimeIdentifier>
<SelfContained>False</SelfContained>
<SignatureAlgorithm>sha256RSA</SignatureAlgorithm>
<SignManifests>True</SignManifests>
<TargetFramework>netcoreapp3.1</TargetFramework>
<TargetFramework>net5.0</TargetFramework>
<UpdateEnabled>False</UpdateEnabled>
<UpdateMode>Foreground</UpdateMode>
</PropertyGroup>
<ItemGroup>
<BootstrapperPackage Include="Microsoft.NetCore.CoreRuntime.3.1.x64">
<BootstrapperPackage Include="Microsoft.NetCore.CoreRuntime.5.0.x64">
<Install>true</Install>
<ProductName>.NET Core Runtime 3.1.10 (x64)</ProductName>
<ProductName>.NET Runtime 5.0.1 (x64)</ProductName>
</BootstrapperPackage>
</ItemGroup>
</Project>

@ -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>36</ApplicationRevision>
<ApplicationRevision>37</ApplicationRevision>
<ApplicationVersion>1.0.0.*</ApplicationVersion>
<BootstrapperEnabled>True</BootstrapperEnabled>
<Configuration>Release</Configuration>
@ -28,14 +28,14 @@ https://go.microsoft.com/fwlink/?LinkID=208121.
<SelfContained>False</SelfContained>
<SignatureAlgorithm>sha256RSA</SignatureAlgorithm>
<SignManifests>True</SignManifests>
<TargetFramework>netcoreapp3.1</TargetFramework>
<TargetFramework>net5.0</TargetFramework>
<UpdateEnabled>False</UpdateEnabled>
<UpdateMode>Foreground</UpdateMode>
</PropertyGroup>
<ItemGroup>
<BootstrapperPackage Include="Microsoft.NetCore.CoreRuntime.3.1.x64">
<BootstrapperPackage Include="Microsoft.NetCore.CoreRuntime.5.0.x64">
<Install>true</Install>
<ProductName>.NET Core Runtime 3.1.10 (x64)</ProductName>
<ProductName>.NET Runtime 5.0.1 (x64)</ProductName>
</BootstrapperPackage>
</ItemGroup>
</Project>

@ -8,7 +8,7 @@ https://go.microsoft.com/fwlink/?LinkID=208121.
<Platform>Any CPU</Platform>
<PublishDir>bin\Manual\</PublishDir>
<PublishProtocol>FileSystem</PublishProtocol>
<TargetFramework>netcoreapp3.1</TargetFramework>
<TargetFramework>net5.0</TargetFramework>
<SelfContained>true</SelfContained>
<RuntimeIdentifier>win-x64</RuntimeIdentifier>
<PublishSingleFile>False</PublishSingleFile>

@ -0,0 +1,35 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
https://go.microsoft.com/fwlink/?LinkID=208121.
-->
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<ApplicationRevision>1</ApplicationRevision>
<ApplicationVersion>1.0.0.*</ApplicationVersion>
<BootstrapperEnabled>True</BootstrapperEnabled>
<Configuration>Release</Configuration>
<CreateDesktopShortcut>True</CreateDesktopShortcut>
<GenerateManifests>True</GenerateManifests>
<Install>true</Install>
<InstallFrom>Disk</InstallFrom>
<IsRevisionIncremented>True</IsRevisionIncremented>
<IsWebBootstrapper>False</IsWebBootstrapper>
<ManifestCertificateThumbprint>820B7EDF3E26E24BB4C25B177A05B3D0C77BF73A</ManifestCertificateThumbprint>
<ManifestKeyFile>hass-workstation-service_TemporaryKey.pfx</ManifestKeyFile>
<MapFileExtensions>true</MapFileExtensions>
<OpenBrowserOnPublish>false</OpenBrowserOnPublish>
<Platform>Any CPU</Platform>
<PublishDir>bin\clickonce-selfcontained\</PublishDir>
<PublishUrl>bin\clickonce-selfcontained\</PublishUrl>
<PublishProtocol>ClickOnce</PublishProtocol>
<PublishReadyToRun>False</PublishReadyToRun>
<PublishSingleFile>False</PublishSingleFile>
<RuntimeIdentifier>win-x64</RuntimeIdentifier>
<SelfContained>True</SelfContained>
<SignatureAlgorithm>sha256RSA</SignatureAlgorithm>
<SignManifests>True</SignManifests>
<TargetFramework>net5.0</TargetFramework>
<UpdateEnabled>False</UpdateEnabled>
<UpdateMode>Foreground</UpdateMode>
</PropertyGroup>
</Project>

@ -5,7 +5,6 @@ using System.Threading;
using System.Threading.Tasks;
using hass_workstation_service.Communication;
using hass_workstation_service.Data;
using hass_workstation_service.Domain.Notify;
using hass_workstation_service.Domain.Sensors;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk.Worker">
<Project Sdk="Microsoft.NET.Sdk.Worker">
<PropertyGroup>
<TargetFramework>netcoreapp3.1</TargetFramework>
<TargetFramework>net5.0</TargetFramework>
<UserSecretsId>dotnet-hass_workstation_service-C65C2EBE-1977-4C24-AC6B-6921877E1390</UserSecretsId>
<RootNamespace>hass_workstation_service</RootNamespace>
<OutputType>WinExe</OutputType>
@ -24,11 +24,15 @@
</ItemGroup>
<ItemGroup>
<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>
</Content>
@ -46,8 +50,9 @@
<PackageReference Include="Microsoft.Win32.Registry" Version="5.0.0" />
<PackageReference Include="Microsoft.Windows.SDK.Contracts" Version="10.0.19041.1" />
<PackageReference Include="MQTTnet" Version="3.0.13" />
<PackageReference Include="OpenCvSharp4.Windows" Version="4.5.1.20201229" />
<PackageReference Include="Serilog.AspNetCore" Version="3.4.0" />
<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>
</Project>

Loading…
Cancel
Save