diff --git a/README.md b/README.md index 3e4c6a2..8b5a81c 100644 --- a/README.md +++ b/README.md @@ -26,9 +26,14 @@ 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, standalone is what you need. Make sure to install [.NET 5 Runtime](https://dotnet.microsoft.com/download/dotnet/current/runtime) first. 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. -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. +### Getting Started + +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. ### Updating @@ -38,6 +43,17 @@ If you used the installer, the app checks for updates on startup. If an update i Find us on us on [Discord](https://discord.gg/VraYT2N3wd). +## Development + +Want to develop or build the application yourself? Make sure to install the .NET Runtime [.NET 5 Runtime](https://dotnet.microsoft.com/download/dotnet/current/runtime) and [.NET 5 SDK](https://dotnet.microsoft.com/download/dotnet/current). Run the following commands from the `hass-workstation-service\hass-workstation-service` directory to get you started: + +```` powershell +dotnet build +dotnet publish +```` + +In case you are using Visual Studio Code, open the `hass-workstation-service\hass-workstation-service` folder to take advantage of the predefined build and publish tasks. + ## Sensors The application provides several sensors. Sensors can be configured with a name and this name will be used in the MQTT topic like this: `homeassistant/sensor/{DeviceName}/{Name}/state`. Sensors will expose themselves through [MQTT discovery](https://www.home-assistant.io/docs/mqtt/discovery/) and will automatically appear in Home assistant or any other platform that supports this type of configuration. diff --git a/UserInterface/Views/AddCommandDialog.axaml b/UserInterface/Views/AddCommandDialog.axaml index 0ea8d9f..c4f6639 100644 --- a/UserInterface/Views/AddCommandDialog.axaml +++ b/UserInterface/Views/AddCommandDialog.axaml @@ -5,9 +5,9 @@ mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" x:Class="UserInterface.Views.AddCommandDialog" SizeToContent="WidthAndHeight" - Title="Add sensor"> + Title="Add command"> - Sensor type + Command type diff --git a/UserInterface/Views/AppInfo.axaml b/UserInterface/Views/AppInfo.axaml index 5bbdfbc..33e766f 100644 --- a/UserInterface/Views/AppInfo.axaml +++ b/UserInterface/Views/AppInfo.axaml @@ -9,7 +9,7 @@ Info - + diff --git a/UserInterface/Views/AppInfo.axaml.cs b/UserInterface/Views/AppInfo.axaml.cs index 7a4c576..0fda95c 100644 --- a/UserInterface/Views/AppInfo.axaml.cs +++ b/UserInterface/Views/AppInfo.axaml.cs @@ -64,7 +64,7 @@ namespace UserInterface.Views } } - public void Github(object sender, RoutedEventArgs args) + public void GitHub(object sender, RoutedEventArgs args) { BrowserUtil.OpenBrowser("https://github.com/sleevezipper/hass-workstation-service"); } diff --git a/UserInterface/Views/BrokerSettings.axaml b/UserInterface/Views/BrokerSettings.axaml index ee3df95..d583039 100644 --- a/UserInterface/Views/BrokerSettings.axaml +++ b/UserInterface/Views/BrokerSettings.axaml @@ -5,10 +5,10 @@ mc:Ignorable="d" d:DesignWidth="500" d:DesignHeight="450" x:Class="UserInterface.Views.BrokerSettings"> - Mqtt broker + MQTT Broker - IP or hostname + IP address or hostname diff --git a/hass-workstation-service/Communication/InterProcesCommunication/InterProcessApi.cs b/hass-workstation-service/Communication/InterProcesCommunication/InterProcessApi.cs index 95c4a6b..7dc486f 100644 --- a/hass-workstation-service/Communication/InterProcesCommunication/InterProcessApi.cs +++ b/hass-workstation-service/Communication/InterProcesCommunication/InterProcessApi.cs @@ -75,7 +75,7 @@ namespace hass_workstation_service.Communication.InterProcesCommunication public List 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(); + 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 = ((SensorDiscoveryConfigModel)s.GetAutoDiscoveryConfig()).Unit_of_measurement }).ToList(); } public List GetConfiguredCommands() diff --git a/hass-workstation-service/Communication/MQTT/MqttPublisher.cs b/hass-workstation-service/Communication/MQTT/MqttPublisher.cs index b431d90..bcdd12f 100644 --- a/hass-workstation-service/Communication/MQTT/MqttPublisher.cs +++ b/hass-workstation-service/Communication/MQTT/MqttPublisher.cs @@ -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; @@ -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") { diff --git a/hass-workstation-service/Communication/MQTT/SensorDiscoveryConfigModel.cs b/hass-workstation-service/Communication/MQTT/SensorDiscoveryConfigModel.cs index 1c5218d..3d942e1 100644 --- a/hass-workstation-service/Communication/MQTT/SensorDiscoveryConfigModel.cs +++ b/hass-workstation-service/Communication/MQTT/SensorDiscoveryConfigModel.cs @@ -15,6 +15,11 @@ namespace hass_workstation_service.Communication /// /// public string Name { get; set; } + /// + /// The MQTT topic subscribed to receive sensor values. + /// + /// + public string State_topic { get; set; } } public class SensorDiscoveryConfigModel : DiscoveryConfigModel { @@ -69,12 +74,6 @@ namespace hass_workstation_service.Communication /// /// public int? Qos { get; set; } - - /// - /// The MQTT topic subscribed to receive sensor values. - /// - /// - public string State_topic { get; set; } /// /// (Optional) An ID that uniquely identifies this sensor. If two sensors have the same unique ID, Home Assistant will raise an exception. /// @@ -147,11 +146,6 @@ namespace hass_workstation_service.Communication /// public int? Qos { get; set; } /// - /// The MQTT topic subscribed to receive sensor values. - /// - /// - public string State_topic { get; set; } - /// /// (Optional) An ID that uniquely identifies this sensor. If two sensors have the same unique ID, Home Assistant will raise an exception. /// /// diff --git a/hass-workstation-service/Domain/AbstractDiscoverable.cs b/hass-workstation-service/Domain/AbstractDiscoverable.cs index 7c388f7..aba479e 100644 --- a/hass-workstation-service/Domain/AbstractDiscoverable.cs +++ b/hass-workstation-service/Domain/AbstractDiscoverable.cs @@ -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(); } } diff --git a/hass-workstation-service/Domain/Commands/AbstractCommand.cs b/hass-workstation-service/Domain/Commands/AbstractCommand.cs index b1b30e6..849077e 100644 --- a/hass-workstation-service/Domain/Commands/AbstractCommand.cs +++ b/hass-workstation-service/Domain/Commands/AbstractCommand.cs @@ -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; } /// /// The update interval in seconds. It checks state only if the interval has passed. /// @@ -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(); diff --git a/hass-workstation-service/Domain/Sensors/AbstractSensor.cs b/hass-workstation-service/Domain/Sensors/AbstractSensor.cs index 8c5cd50..22d9a37 100644 --- a/hass-workstation-service/Domain/Sensors/AbstractSensor.cs +++ b/hass-workstation-service/Domain/Sensors/AbstractSensor.cs @@ -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; } /// /// The update interval in seconds. It checks state only if the interval has passed. /// @@ -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); } } diff --git a/hass-workstation-service/Domain/Sensors/DummySensor.cs b/hass-workstation-service/Domain/Sensors/DummySensor.cs index 3431fb5..66eaaad 100644 --- a/hass-workstation-service/Domain/Sensors/DummySensor.cs +++ b/hass-workstation-service/Domain/Sensors/DummySensor.cs @@ -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" }); }