diff --git a/README.md b/README.md index 093c726..d964599 100644 --- a/README.md +++ b/README.md @@ -13,13 +13,25 @@ It will try to futher accomplish this goal in the future by: - Being platform independent -## Screenshots +## Hass Workstation Service in the press + +
+View some screenshots ![The settings screen](https://i.imgur.com/RBQx807.png) ![The resulting sensors and commands in Home Assistant](https://i.imgur.com/jXRU2cu.png) -Not convinced yet? Check out [this excellent video](https://youtu.be/D5A7le79R5M) by GeekToolkit on YouTube. +
+ +If you would prefer a video, look at : + - [How to Control a PC from Home Assistant](https://youtu.be/D5A7le79R5M) by GeekToolkit on YouTube. + - [Mit Home-Assistant den Computer AN und AUS schalten!](https://www.youtube.com/watch?v=oDJHGEcV84A) by Fabsenet on YouTube. + - [The Butter, What?! show's review](https://youtu.be/wBTKfwkV-vs?t=376) by Pat and Brian on youtube. + +Or a written article : + - [How to Setup HASS Workstation Service in Home Assistant](https://smarthomepursuits.com/how-to-setup-hass-workstation-service-in-home-assistant/) by Danny @ smarthomepursuits.com + - [Control your Windows PC with HASS Workstation Service](https://home-assistant-guide.com/2021/01/18/control-your-windows-pc-with-hass-workstation-service/) by home-assistant-guide.com ## Installation @@ -35,7 +47,7 @@ If you don't want to use the installer, standalone is what you need. Make sure t As a prerequisite, make sure you have an MQTT username and password available. Using Home Assistant in combination with the Mosquitto broker add-on and integration? You can both use a Home Assistant account and a local account. From a security perspective, we recommend a local account as this only provides access to the MQTT Broker and not to your Home Assistant instance. Now that you are all set, make sure to run the `hass-workstation-service.exe` executable first. This executable is responsible for setting up the sensors and talking with your MQTT Broker. To configure the service, start the `UserInterface.exe` executable. -Add your `hostname` or `IP address`, `port`, `username` and `password` and click on Save. In case you use the Mosquitto add-in, provide port `8883` and check `Use TLS`. The application will mention "All good" when configured correctly. +Add your `hostname` or `IP address`, `port`, `username` and `password` and click on Save. In case you use the Mosquitto add-in, provide port `8883` and check `Use TLS`. If you don't want to use TLS the default port is `1883`. The application will mention "All good" when configured correctly. ### Updating @@ -43,7 +55,7 @@ If you used the installer, the app checks for updates on startup. If an update i ## Need help? -Find us on [Discord](https://discord.gg/VraYT2N3wd). +Find us on [Discord](https://discord.gg/VraYT2N3wd), or check out the [frequently asked questions](https://github.com/sleevezipper/hass-workstation-service/blob/master/documentation/FAQ.md#frequently-asked-questions). ## Development @@ -67,8 +79,8 @@ Here is a list of the most commonly used sensors with the full documentation [he |sensor|use| |---|---| |ActiveWindow|Exposes the currently selected window| -|WebcamActive|Exposes the microphone state| -|MicrophoneActive|Exposes the webcam state| +|WebcamActive|Exposes the webcam state| +|MicrophoneActive|Exposes the microphone state| ## Commands diff --git a/UserInterface/ViewModels/BrokerSettingsViewModel.cs b/UserInterface/ViewModels/BrokerSettingsViewModel.cs index 2811cb3..5c999d6 100644 --- a/UserInterface/ViewModels/BrokerSettingsViewModel.cs +++ b/UserInterface/ViewModels/BrokerSettingsViewModel.cs @@ -16,6 +16,9 @@ namespace UserInterface.ViewModels private bool isConnected; private int? port; private bool useTLS; + private bool retainLWT = true; + private string rootCaPath; + private string clientCertPath; public bool IsConnected { get => isConnected; set => this.RaiseAndSetIfChanged(ref isConnected, value); } public string Message { get => message; set => this.RaiseAndSetIfChanged(ref message, value); } @@ -29,6 +32,13 @@ namespace UserInterface.ViewModels public bool UseTLS { get => useTLS; set => this.RaiseAndSetIfChanged(ref useTLS, value); } + public bool RetainLWT { get => retainLWT; set => this.RaiseAndSetIfChanged(ref retainLWT, value); } + + public string RootCAPath { get => rootCaPath; set => this.RaiseAndSetIfChanged(ref rootCaPath, value); } + + public string ClientCertPath { get => clientCertPath; set => this.RaiseAndSetIfChanged(ref clientCertPath, value); } + + public void Update(MqttSettings settings) { this.Host = settings.Host; @@ -36,6 +46,9 @@ namespace UserInterface.ViewModels this.Password = settings.Password; this.Port = settings.Port; this.UseTLS = settings.UseTLS; + this.RetainLWT = settings.RetainLWT; + this.RootCAPath = settings.RootCAPath; + this.ClientCertPath = settings.ClientCertPath; } public void UpdateStatus(MqqtClientStatus status) diff --git a/UserInterface/Views/BrokerSettings.axaml b/UserInterface/Views/BrokerSettings.axaml index b23b798..92673df 100644 --- a/UserInterface/Views/BrokerSettings.axaml +++ b/UserInterface/Views/BrokerSettings.axaml @@ -19,12 +19,69 @@ Use TLS + + + + Retain LastWillAndTestament + + (What's this?) + + + +[Experimental] +If set, sets Retain on the Last Will and Testament message. +Only turn this off if you use a broker that does not support this(e.g. AWS IoT Core) +Defaults to True + + + + + + + Username + Password - + + + + Root Cert Path (.pem/.crt) + + (What's this?) + + + + [Experimental] + If set, use this certificate in the TLS configuration for the MQTT connection. + This will be a pem or crt file provided by your broker. + + + + + + + + + Client Cert Path (.pfx) + + (What's this?) + + + + [Experimental] + If set, use this certificate in the TLS configuration for the MQTT connection. + This should be the private key .pfx file for a device created in your broker corresponding to this Windows PC. + + + + + + + + diff --git a/UserInterface/Views/BrokerSettings.axaml.cs b/UserInterface/Views/BrokerSettings.axaml.cs index e870a80..746b44d 100644 --- a/UserInterface/Views/BrokerSettings.axaml.cs +++ b/UserInterface/Views/BrokerSettings.axaml.cs @@ -50,7 +50,7 @@ namespace UserInterface.Views ICollection results; if (model.IsValid(model, out results)) { - var result = this.client.InvokeAsync(x => x.WriteMqttBrokerSettingsAsync(new MqttSettings() { Host = model.Host, Username = model.Username, Password = model.Password ?? "", Port = model.Port, UseTLS = model.UseTLS })); + var result = this.client.InvokeAsync(x => x.WriteMqttBrokerSettingsAsync(new MqttSettings() { Host = model.Host, Username = model.Username, Password = model.Password ?? "", Port = model.Port, UseTLS = model.UseTLS, RootCAPath = model.RootCAPath, ClientCertPath = model.ClientCertPath, RetainLWT = model.RetainLWT })); } } diff --git a/UserInterface/Views/GeneralSettings.axaml b/UserInterface/Views/GeneralSettings.axaml index 4ca197d..a2c723e 100644 --- a/UserInterface/Views/GeneralSettings.axaml +++ b/UserInterface/Views/GeneralSettings.axaml @@ -8,18 +8,18 @@ Settings Name prefix - (What's this?) - - - -[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". - + (What's this?) + + + + [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". + - - - + + + diff --git a/documentation/FAQ.md b/documentation/FAQ.md new file mode 100644 index 0000000..d7984e3 --- /dev/null +++ b/documentation/FAQ.md @@ -0,0 +1,25 @@ +# Frequently asked questions + +There are some common problems people encounter with hass workstations service, so if you run into a problem you should search this list. Most browsers have a search function you can access by pressing `CTRL` and `F` simultaniously. + +If you cannot solve your problem still, join the [discord server](https://discord.gg/VraYT2N3wd). + +### Where are config files located? + +You can find the configuration files inside of `%appdata%\Hass Workstation Service` on windows. + +|file|usage| +| --- | --- | +|configured-commands.json|stores all data about commands, including their properties| +|configured-sensors.json|stores information about sensors, including their properties| +|mqttbroker.json|stores data about your MQTT broker, dont share online| + + +### Are there any client logs? + +Check the logs folder, its stored in the same place as config files. + + +### I cannot find documentation on a new feature? + +You can submit a pull request with new documentation, ask on the [discord server](https://discord.gg/VraYT2N3wd), or check the [develop branch](https://github.com/sleevezipper/hass-workstation-service/tree/develop) for updated documentation. diff --git a/hass-workstation-service/Communication/InterProcesCommunication/ServiceContractModels.cs b/hass-workstation-service/Communication/InterProcesCommunication/ServiceContractModels.cs index 86f7065..5423707 100644 --- a/hass-workstation-service/Communication/InterProcesCommunication/ServiceContractModels.cs +++ b/hass-workstation-service/Communication/InterProcesCommunication/ServiceContractModels.cs @@ -11,6 +11,10 @@ namespace hass_workstation_service.Communication.InterProcesCommunication.Models public string Password { get; set; } public int? Port { get; set; } public bool UseTLS { get; set; } + + public bool RetainLWT { get; set; } + public string RootCAPath { get; set; } + public string ClientCertPath { get; set; } } public class MqqtClientStatus diff --git a/hass-workstation-service/Communication/MQTT/MqttPublisher.cs b/hass-workstation-service/Communication/MQTT/MqttPublisher.cs index 7449d67..0cacde5 100644 --- a/hass-workstation-service/Communication/MQTT/MqttPublisher.cs +++ b/hass-workstation-service/Communication/MQTT/MqttPublisher.cs @@ -116,7 +116,7 @@ namespace hass_workstation_service.Communication var message = new MqttApplicationMessageBuilder() .WithTopic($"homeassistant/{discoverable.Domain}/{this.DeviceConfigModel.Name}/{DiscoveryConfigModel.GetNameWithPrefix(discoverable.GetAutoDiscoveryConfig().NamePrefix, discoverable.ObjectId)}/config") .WithPayload(clearConfig ? "" : JsonSerializer.Serialize(discoverable.GetAutoDiscoveryConfig(), discoverable.GetAutoDiscoveryConfig().GetType(), options)) - .WithRetainFlag() + //.WithRetainFlag() .Build(); await this.Publish(message); // if clearconfig is true, also remove previous state messages @@ -125,7 +125,7 @@ namespace hass_workstation_service.Communication var stateMessage = new MqttApplicationMessageBuilder() .WithTopic($"homeassistant/{discoverable.Domain}/{this.DeviceConfigModel.Name}/{DiscoveryConfigModel.GetNameWithPrefix(discoverable.GetAutoDiscoveryConfig().NamePrefix, discoverable.ObjectId)}/state") .WithPayload("") - .WithRetainFlag() + // .WithRetainFlag() .Build(); await this.Publish(stateMessage); } diff --git a/hass-workstation-service/Data/ConfigurationService.cs b/hass-workstation-service/Data/ConfigurationService.cs index f00bb3c..c3090f0 100644 --- a/hass-workstation-service/Data/ConfigurationService.cs +++ b/hass-workstation-service/Data/ConfigurationService.cs @@ -5,6 +5,7 @@ using System.Linq; using System.Runtime.InteropServices; using System.Runtime.Versioning; using System.Security; +using System.Security.Cryptography.X509Certificates; using System.Text.Json; using System.Threading.Tasks; using hass_workstation_service.Communication; @@ -20,6 +21,7 @@ using MQTTnet.Client.Options; using MQTTnet.Extensions.ManagedClient; using Serilog; + namespace hass_workstation_service.Data { public class ConfigurationService : IConfigurationService @@ -310,22 +312,54 @@ namespace hass_workstation_service.Data if (configuredBroker != null && configuredBroker.Host != null) { - var mqttClientOptions = new MqttClientOptionsBuilder() + + var mqttClientOptionsBuilder = new MqttClientOptionsBuilder() .WithTcpServer(configuredBroker.Host, configuredBroker.Port) - .WithTls(new MqttClientOptionsBuilderTlsParameters() - { - UseTls = configuredBroker.UseTLS, - AllowUntrustedCertificates = true, - SslProtocol = configuredBroker.UseTLS ? System.Security.Authentication.SslProtocols.Tls12 : System.Security.Authentication.SslProtocols.None - }) .WithCredentials(configuredBroker.Username, configuredBroker.Password.ToString()) - .WithKeepAlivePeriod(TimeSpan.FromSeconds(30)) - .WithWillMessage(new MqttApplicationMessageBuilder() - .WithRetainFlag() + .WithKeepAlivePeriod(TimeSpan.FromSeconds(30)); + + + /* Start LWT */ + var lwtMessage = new MqttApplicationMessageBuilder() .WithTopic($"homeassistant/sensor/{_deviceConfigModel.Name}/availability") - .WithPayload("offline") - .Build()) - .Build(); + .WithPayload("offline"); + if (configuredBroker.RetainLWT) { + lwtMessage.WithRetainFlag(); + } + + mqttClientOptionsBuilder.WithWillMessage(lwtMessage.Build()); + /* End LWT */ + + + /* Start TLS/Certificate configuration */ + + var tlsParameters = new MqttClientOptionsBuilderTlsParameters() + { + UseTls = configuredBroker.UseTLS, + AllowUntrustedCertificates = true, + SslProtocol = configuredBroker.UseTLS ? System.Security.Authentication.SslProtocols.Tls12 : System.Security.Authentication.SslProtocols.None + }; + + var certs = new List(); + + if (configuredBroker.RootCAPath != null) { + certs.Add(new X509Certificate2(configuredBroker.RootCAPath)); + } + + if (configuredBroker.ClientCertPath != null) + { + certs.Add(new X509Certificate2(configuredBroker.ClientCertPath)); + } + if (certs.Count > 0) { + // IF certs are configured, let's add them here + tlsParameters.Certificates = certs; + } + mqttClientOptionsBuilder.WithTls(tlsParameters); + + /* End TLS/Certificate Configuration */ + + + var mqttClientOptions = mqttClientOptionsBuilder.Build(); return new ManagedMqttClientOptionsBuilder().WithClientOptions(mqttClientOptions).Build(); } else @@ -544,7 +578,10 @@ namespace hass_workstation_service.Data Username = settings.Username, Password = settings.Password ?? "", Port = settings.Port ?? 1883, - UseTLS = settings.UseTLS + UseTLS = settings.UseTLS, + RetainLWT = settings.RetainLWT, + RootCAPath = settings.RootCAPath, + ClientCertPath = settings.ClientCertPath }; await JsonSerializer.SerializeAsync(stream, configuredBroker); @@ -563,7 +600,10 @@ namespace hass_workstation_service.Data Username = broker?.Username, Password = broker?.Password, Port = broker?.Port, - UseTLS = broker?.UseTLS ?? false + UseTLS = broker?.UseTLS ?? false, + RetainLWT = broker?.RetainLWT ?? true, + RootCAPath = broker?.RootCAPath, + ClientCertPath = broker?.RootCAPath }; } diff --git a/hass-workstation-service/Data/ConfiguredMqttBroker.cs b/hass-workstation-service/Data/ConfiguredMqttBroker.cs index f188322..d4a73d1 100644 --- a/hass-workstation-service/Data/ConfiguredMqttBroker.cs +++ b/hass-workstation-service/Data/ConfiguredMqttBroker.cs @@ -8,11 +8,33 @@ namespace hass_workstation_service.Data private string username; private string password; private int? port; + + private string rootCAPath; + private string clientCertPath; public string Host { get; set; } public int Port { get => port ?? 1883; set => port = value; } public bool UseTLS { get; set; } + // Before this option, Retains was the default, so let's keep that here to not break backwards compatibility + public bool RetainLWT { get; set; } = true; + + public string RootCAPath { + get + { + if (rootCAPath!= null) return rootCAPath; + return ""; + } + set => rootCAPath = value; + } + public string ClientCertPath { + get + { + if (clientCertPath != null) return clientCertPath; + return ""; + } + set => clientCertPath = value; + } public string Username { diff --git a/hass-workstation-service/Domain/Commands/AbstractCommand.cs b/hass-workstation-service/Domain/Commands/AbstractCommand.cs index 94df49d..4e42b86 100644 --- a/hass-workstation-service/Domain/Commands/AbstractCommand.cs +++ b/hass-workstation-service/Domain/Commands/AbstractCommand.cs @@ -40,8 +40,8 @@ namespace hass_workstation_service.Domain.Commands var message = new MqttApplicationMessageBuilder() .WithTopic(GetAutoDiscoveryConfig().State_topic) .WithPayload(state) - .WithExactlyOnceQoS() - .WithRetainFlag() + //.WithExactlyOnceQoS() + //.WithRetainFlag() .Build(); await Publisher.Publish(message); PreviousPublishedState = state; diff --git a/hass-workstation-service/Domain/Sensors/AbstractSensor.cs b/hass-workstation-service/Domain/Sensors/AbstractSensor.cs index 73904bf..4aa8f7c 100644 --- a/hass-workstation-service/Domain/Sensors/AbstractSensor.cs +++ b/hass-workstation-service/Domain/Sensors/AbstractSensor.cs @@ -40,8 +40,8 @@ namespace hass_workstation_service.Domain.Sensors var message = new MqttApplicationMessageBuilder() .WithTopic(GetAutoDiscoveryConfig().State_topic) .WithPayload(state) - .WithExactlyOnceQoS() - .WithRetainFlag() + //.WithExactlyOnceQoS() + //.WithRetainFlag() .Build(); await Publisher.Publish(message); PreviousPublishedState = state; diff --git a/hass-workstation-service/hass-workstation-service.csproj b/hass-workstation-service/hass-workstation-service.csproj index 30c7c44..77d0cd7 100644 --- a/hass-workstation-service/hass-workstation-service.csproj +++ b/hass-workstation-service/hass-workstation-service.csproj @@ -43,7 +43,7 @@ Always - Always + Never