Merge branch '23-sensor-with-space-in-its-name-doesnt-' into develop

#21-media-commands
sleevezipper 4 years ago
commit 9b6ff675ad

@ -26,9 +26,14 @@ Note: You'll get a Windows Smartscreen warning because the code was self signed.
### Standalone ### 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 ### 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). 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 ## 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. 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.

@ -5,9 +5,9 @@
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="UserInterface.Views.AddCommandDialog" x:Class="UserInterface.Views.AddCommandDialog"
SizeToContent="WidthAndHeight" SizeToContent="WidthAndHeight"
Title="Add sensor"> Title="Add command">
<StackPanel Margin="40" MinWidth="200"> <StackPanel Margin="40" MinWidth="200">
<ContentControl Margin="0 20 0 10">Sensor type</ContentControl> <ContentControl Margin="0 20 0 10">Command type</ContentControl>
<ComboBox x:Name="ComboBox" SelectionChanged="ComboBoxClosed" SelectedItem="{Binding SelectedType}" MinHeight="27"></ComboBox> <ComboBox x:Name="ComboBox" SelectionChanged="ComboBoxClosed" SelectedItem="{Binding SelectedType}" MinHeight="27"></ComboBox>
<TextBlock Margin="0 10 0 10" MaxWidth="300" TextWrapping="Wrap" TextAlignment="Left" Text="{Binding Description}"></TextBlock> <TextBlock Margin="0 10 0 10" MaxWidth="300" TextWrapping="Wrap" TextAlignment="Left" Text="{Binding Description}"></TextBlock>

@ -9,7 +9,7 @@
<ContentControl FontSize="18" FontWeight="Bold" >Info</ContentControl> <ContentControl FontSize="18" FontWeight="Bold" >Info</ContentControl>
<TextBlock Text="Need some help?" Margin="0 0 0 20"></TextBlock > <TextBlock Text="Need some help?" Margin="0 0 0 20"></TextBlock >
<StackPanel Margin="0 0 0 20" HorizontalAlignment="Left" Orientation="Horizontal"> <StackPanel Margin="0 0 0 20" HorizontalAlignment="Left" Orientation="Horizontal">
<Button Width="75" HorizontalAlignment="Right" Margin="10 10" Click="Github">Github</Button> <Button Width="75" HorizontalAlignment="Right" Margin="10 10" Click="GitHub">GitHub</Button>
<Button Width="75" HorizontalAlignment="Left" Margin="10 10" Click="Discord">Discord</Button> <Button Width="75" HorizontalAlignment="Left" Margin="10 10" Click="Discord">Discord</Button>
</StackPanel> </StackPanel>
</StackPanel> </StackPanel>

@ -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"); BrowserUtil.OpenBrowser("https://github.com/sleevezipper/hass-workstation-service");
} }

@ -5,10 +5,10 @@
mc:Ignorable="d" d:DesignWidth="500" d:DesignHeight="450" mc:Ignorable="d" d:DesignWidth="500" d:DesignHeight="450"
x:Class="UserInterface.Views.BrokerSettings"> x:Class="UserInterface.Views.BrokerSettings">
<StackPanel Margin="30" HorizontalAlignment="Left"> <StackPanel Margin="30" HorizontalAlignment="Left">
<ContentControl FontSize="18" FontWeight="Bold">Mqtt broker</ContentControl> <ContentControl FontSize="18" FontWeight="Bold">MQTT Broker</ContentControl>
<TextBlock IsVisible="{Binding IsConnected}" Foreground="Green" Text="{Binding Message}"></TextBlock > <TextBlock IsVisible="{Binding IsConnected}" Foreground="Green" Text="{Binding Message}"></TextBlock >
<TextBlock IsVisible="{Binding !IsConnected}" Foreground="Red" Text="{Binding Message}"></TextBlock > <TextBlock IsVisible="{Binding !IsConnected}" Foreground="Red" Text="{Binding Message}"></TextBlock >
<ContentControl Margin="0 20 0 10">IP or hostname</ContentControl> <ContentControl Margin="0 20 0 10">IP address or hostname</ContentControl>
<TextBox Text="{Binding Host}" HorizontalAlignment="Left" Width="100" Watermark="192.168.1.200"/> <TextBox Text="{Binding Host}" HorizontalAlignment="Left" Width="100" Watermark="192.168.1.200"/>
<StackPanel Orientation="Horizontal"> <StackPanel Orientation="Horizontal">
<StackPanel Orientation="Vertical"> <StackPanel Orientation="Vertical">

@ -75,7 +75,7 @@ namespace hass_workstation_service.Communication.InterProcesCommunication
public List<ConfiguredSensorModel> GetConfiguredSensors() public 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(); 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<ConfiguredCommandModel> GetConfiguredCommands() public List<ConfiguredCommandModel> GetConfiguredCommands()

@ -7,6 +7,7 @@ using System.Threading.Tasks;
using hass_workstation_service.Communication.InterProcesCommunication.Models; using hass_workstation_service.Communication.InterProcesCommunication.Models;
using hass_workstation_service.Communication.Util; using hass_workstation_service.Communication.Util;
using hass_workstation_service.Data; using hass_workstation_service.Data;
using hass_workstation_service.Domain;
using hass_workstation_service.Domain.Commands; using hass_workstation_service.Domain.Commands;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using MQTTnet; using MQTTnet;
@ -95,8 +96,9 @@ namespace hass_workstation_service.Communication
this._logger.LogInformation($"Message dropped because mqtt not connected: {message}"); this._logger.LogInformation($"Message dropped because mqtt not connected: {message}");
} }
} }
// TODO: This should take a sensor/command instead of a config.
public async Task AnnounceAutoDiscoveryConfig(DiscoveryConfigModel config, string domain, bool clearConfig = false) // 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) if (this._mqttClient.IsConnected)
{ {
@ -109,8 +111,8 @@ namespace hass_workstation_service.Communication
}; };
var message = new MqttApplicationMessageBuilder() var message = new MqttApplicationMessageBuilder()
.WithTopic($"homeassistant/{domain}/{this.DeviceConfigModel.Name}/{config.Name}/config") .WithTopic($"homeassistant/{domain}/{this.DeviceConfigModel.Name}/{discoverable.ObjectId}/config")
.WithPayload(clearConfig ? "" : JsonSerializer.Serialize(config, config.GetType(), options)) .WithPayload(clearConfig ? "" : JsonSerializer.Serialize(discoverable.GetAutoDiscoveryConfig(), discoverable.GetAutoDiscoveryConfig().GetType(), options))
.WithRetainFlag() .WithRetainFlag()
.Build(); .Build();
await this.Publish(message); await this.Publish(message);
@ -177,7 +179,7 @@ namespace hass_workstation_service.Communication
{ {
if (this.IsConnected) if (this.IsConnected)
{ {
await this._mqttClient.SubscribeAsync(command.GetAutoDiscoveryConfig().Command_topic); await this._mqttClient.SubscribeAsync(((CommandDiscoveryConfigModel) command.GetAutoDiscoveryConfig()).Command_topic);
} }
else else
{ {
@ -186,7 +188,7 @@ namespace hass_workstation_service.Communication
await Task.Delay(5500); 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) 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") if (Encoding.UTF8.GetString(applicationMessage?.Payload) == "ON")
{ {

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

@ -1,7 +1,9 @@
using System; using hass_workstation_service.Communication;
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Text; using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace hass_workstation_service.Domain namespace hass_workstation_service.Domain
@ -9,5 +11,17 @@ namespace hass_workstation_service.Domain
public abstract class AbstractDiscoverable public abstract class AbstractDiscoverable
{ {
public abstract string Domain { get; } 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 abstract class AbstractCommand : AbstractDiscoverable
{ {
public Guid Id { get; protected set; }
public string Name { get; protected set; }
/// <summary> /// <summary>
/// The update interval in seconds. It checks state only if the interval has passed. /// The update interval in seconds. It checks state only if the interval has passed.
/// </summary> /// </summary>
@ -40,7 +38,6 @@ namespace hass_workstation_service.Domain.Commands
return config; return config;
} }
public abstract CommandDiscoveryConfigModel GetAutoDiscoveryConfig();
public abstract string GetState(); public abstract string GetState();
public async Task PublishStateAsync() public async Task PublishStateAsync()
@ -68,11 +65,11 @@ namespace hass_workstation_service.Domain.Commands
} }
public async void PublishAutoDiscoveryConfigAsync() public async void PublishAutoDiscoveryConfigAsync()
{ {
await this.Publisher.AnnounceAutoDiscoveryConfig(this.GetAutoDiscoveryConfig(), this.Domain); await this.Publisher.AnnounceAutoDiscoveryConfig(this, this.Domain);
} }
public async Task UnPublishAutoDiscoveryConfigAsync() 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 TurnOn();
public abstract void TurnOff(); public abstract void TurnOff();

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

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

Loading…
Cancel
Save