many things - device discovery still not working as intended

pull/9/head
sleevezipper 4 years ago
parent de6ba209d3
commit 7af05b88b9

@ -0,0 +1,4 @@
{
"todo-tree.tree.showBadges": true,
"todo-tree.tree.showCountsInTree": true
}

@ -0,0 +1,130 @@
using System;
using System.Collections.Generic;
namespace hass_desktop_service.Communication
{
public class AutoDiscoveryConfigModel
{
/// <summary>
/// (Optional) The MQTT topic subscribed to receive availability (online/offline) updates.
/// </summary>
/// <value></value>
public string Availability_topic { get; set; }
/// <summary>
/// (Optional) Information about the device this sensor is a part of to tie it into the device registry. Only works through MQTT discovery and when unique_id is set.
/// </summary>
/// <value></value>
public DeviceConfigModel Device { get; set; }
/// <summary>
/// (Optional) The type/class of the sensor to set the icon in the frontend. See https://www.home-assistant.io/integrations/sensor/#device-class for options.
/// </summary>
/// <value></value>
public string Device_class { get; set; }
/// <summary>
/// (Optional) Defines the number of seconds after the sensors state expires, if its not updated. After expiry, the sensors state becomes unavailable. Defaults to 0 in hass.
/// </summary>
/// <value></value>
public int? Expire_after { get; set; }
/// <summary>
/// Sends update events even if the value hasnt changed. Useful if you want to have meaningful value graphs in history.
/// </summary>
/// <value></value>
public bool? Force_update { get; set; }
/// <summary>
/// (Optional) The icon for the sensor.
/// </summary>
/// <value></value>
public string Icon { get; set; }
/// <summary>
/// (Optional) Defines a template to extract the JSON dictionary from messages received on the json_attributes_topic.
/// </summary>
/// <value></value>
public string Json_attributes_template { get; set; }
/// <summary>
/// (Optional) The MQTT topic subscribed to receive a JSON dictionary payload and then set as sensor attributes. Implies force_update of the current sensor state when a message is received on this topic.
/// </summary>
/// <value></value>
public string Json_attributes_topic { get; set; }
/// <summary>
/// (Optional) The name of the MQTT sensor. Defaults to MQTT Sensor in hass.
/// </summary>
/// <value></value>
public string Name { get; set; }
/// <summary>
/// (Optional) The payload that represents the available state.
/// </summary>
/// <value></value>
public string Payload_available { get; set; }
/// <summary>
/// (Optional) The payload that represents the unavailable state.
/// </summary>
/// <value></value>
public string Payload_not_available { get; set; }
/// <summary>
/// (Optional) The maximum QoS level of the state topic.
/// </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>
/// <value></value>
public string Unique_id { get; set; }
/// <summary>
/// (Optional) Defines the units of measurement of the sensor, if any.
/// </summary>
/// <value></value>
public string Unit_of_measurement { get; set; }
/// <summary>
/// (Optional) Defines a template to extract the value.
/// </summary>
/// <value></value>
public string Value_template { get; set; }
}
public class DeviceConfigModel
{
/// <summary>
/// (Optional) A list of connections of the device to the outside world as a list of tuples [connection_type, connection_identifier]. For example the MAC address of a network interface: "connections": [["mac", "02:5b:26:a8:dc:12"]].
/// </summary>
/// <value></value>
public ICollection<Tuple<string, string>> Connections { get; set; }
/// <summary>
/// (Optional) An Id to identify the device. For example a serial number.
/// </summary>
/// <value></value>
public string Identifiers { get; set; }
/// <summary>
/// (Optional) The manufacturer of the device.
/// </summary>
/// <value></value>
public string Manufacturer { get; set; }
/// <summary>
/// (Optional) The model of the device.
/// </summary>
/// <value></value>
public string Model { get; set; }
/// <summary>
/// (Optional) The name of the device.
/// </summary>
/// <value></value>
public string Name { get; set; }
/// <summary>
/// (Optional) The firmware version of the device.
/// </summary>
/// <value></value>
public string Sw_version { get; set; }
/// <summary>
/// (Optional) Identifier of a device that routes messages between this device and Home Assistant. Examples of such devices are hubs, or parent devices of a sub-device. This is used to show device topology in Home Assistant.
/// </summary>
/// <value></value>
public string Via_device { get; set; }
}
}

@ -0,0 +1,93 @@
using System;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
using hass_desktop_service.Communication.Util;
using Microsoft.Extensions.Logging;
using MQTTnet;
using MQTTnet.Client;
using MQTTnet.Client.Options;
namespace hass_desktop_service.Communication
{
public class MqttPublisher
{
private readonly IMqttClient _mqttClient;
private readonly ILogger<MqttPublisher> _logger;
public DateTime LastConfigAnnounce { get; private set; }
public DeviceConfigModel DeviceConfigModel { get; private set; }
public bool IsConnected
{
get
{
return this._mqttClient.IsConnected;
}
}
public MqttPublisher(
ILogger<MqttPublisher> logger,
IMqttClientOptions options,
DeviceConfigModel deviceConfigModel)
{
this._logger = logger;
this.DeviceConfigModel = deviceConfigModel;
var factory = new MqttFactory();
this._mqttClient = factory.CreateMqttClient();
// connect to the broker
this._mqttClient.ConnectAsync(options);
// configure what happens on disconnect
this._mqttClient.UseDisconnectedHandler(async e =>
{
_logger.LogWarning("Disconnected from server");
await Task.Delay(TimeSpan.FromSeconds(5));
try
{
await this._mqttClient.ConnectAsync(options, CancellationToken.None);
}
catch
{
_logger.LogError("Reconnecting failed");
}
});
}
public async Task Publish(MqttApplicationMessage message)
{
if (this._mqttClient.IsConnected)
{
await this._mqttClient.PublishAsync(message);
}
else
{
this._logger.LogInformation($"message dropped because mqtt not connected: {message}");
}
}
public async Task PublishAutoDiscoveryConfig(AutoDiscoveryConfigModel config, bool clearPreviousConfig = false)
{
if (this._mqttClient.IsConnected)
{
var options = new JsonSerializerOptions
{
PropertyNamingPolicy = new CamelCaseJsonNamingpolicy(),
IgnoreNullValues = true,
PropertyNameCaseInsensitive = true
};
var message = new MqttApplicationMessageBuilder()
.WithTopic($"homeassistant/sensor/{config.Device.Identifiers}/config")
.WithPayload(clearPreviousConfig ? "" : JsonSerializer.Serialize(config, options))
.WithRetainFlag()
.Build();
await this.Publish(message);
LastConfigAnnounce = DateTime.UtcNow;
}
}
}
}

@ -0,0 +1,9 @@
using System.Text.Json;
namespace hass_desktop_service.Communication.Util
{
public class CamelCaseJsonNamingpolicy : JsonNamingPolicy
{
public override string ConvertName(string name) => name.ToLowerInvariant();
}
}

@ -0,0 +1,11 @@
using System;
namespace hass_desktop_service.Data
{
public class ConfiguredSensor
{
public string Type { get; set; }
public Guid Id { get; set; }
public string Name { get; set; }
}
}

@ -0,0 +1,54 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.IO.IsolatedStorage;
using System.Text.Json;
using hass_desktop_service.Communication;
using hass_desktop_service.Domain.Sensors;
using Microsoft.Extensions.Configuration;
namespace hass_desktop_service.Data
{
public class ConfiguredSensorsService
{
public ICollection<AbstractSensor> ConfiguredSensors { get; private set; }
public IConfiguration Configuration { get; private set; }
private readonly MqttPublisher _publisher;
private readonly IsolatedStorageFile _fileStorage;
public ConfiguredSensorsService(MqttPublisher publisher)
{
this._fileStorage = IsolatedStorageFile.GetUserStoreForApplication();
ConfiguredSensors = new List<AbstractSensor>();
_publisher = publisher;
ReadSettings();
}
public async void ReadSettings()
{
IsolatedStorageFileStream stream = this._fileStorage.OpenFile("configured-sensors.json", FileMode.OpenOrCreate);
List<ConfiguredSensor> sensors = await JsonSerializer.DeserializeAsync<List<ConfiguredSensor>>(stream);
foreach (ConfiguredSensor configuredSensor in sensors)
{
AbstractSensor sensor;
#pragma warning disable IDE0066
switch (configuredSensor.Type)
{
case "UserNotificationStateSensor":
sensor = new UserNotificationStateSensor(_publisher, configuredSensor.Name, configuredSensor.Id);
break;
default:
throw new InvalidOperationException("unsupported sensor type in config");
}
this.ConfiguredSensors.Add(sensor);
}
}
public void AddConfiguredSensor(AbstractSensor sensor)
{
}
}
}

@ -0,0 +1,18 @@
using System;
using System.Collections.Generic;
using hass_desktop_service.Domain.Sensors;
namespace hass_desktop_service.Domain
{
public abstract class Device
{
public Guid Id { get; private set; }
public string Name { get; private set; }
public string Manufacturer { get; private set; }
public string Model { get; private set; }
public string Version { get; private set; }
public ICollection<AbstractSensor> Sensors { get; set; }
}
}

@ -0,0 +1,39 @@
using System;
using System.Threading.Tasks;
using hass_desktop_service.Communication;
using MQTTnet;
namespace hass_desktop_service.Domain.Sensors
{
public abstract class AbstractSensor
{
public Guid Id { get; protected set; }
public string Name { get; protected set; }
public MqttPublisher Publisher { get; protected set; }
protected AutoDiscoveryConfigModel _autoDiscoveryConfigModel;
protected AutoDiscoveryConfigModel SetAutoDiscoveryConfigModel(AutoDiscoveryConfigModel config)
{
this._autoDiscoveryConfigModel = config;
return config;
}
public abstract AutoDiscoveryConfigModel GetAutoDiscoveryConfig();
public abstract string GetState();
public async Task PublishStateAsync()
{
var message = new MqttApplicationMessageBuilder()
.WithTopic(this.GetAutoDiscoveryConfig().State_topic)
.WithPayload(this.GetState())
.WithExactlyOnceQoS()
.WithRetainFlag()
.Build();
await Publisher.Publish(message);
}
public async Task PublishAutoDiscoveryConfigAsync()
{
await this.Publisher.PublishAutoDiscoveryConfig(this.GetAutoDiscoveryConfig());
}
}
}

@ -1,9 +1,53 @@
using System;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using PInvoke; using System.Threading.Tasks;
using static PInvoke.Shell32; using hass_desktop_service.Communication;
namespace hass_desktop_service.StateDetectors.Windows.Fullscreen namespace hass_desktop_service.Domain.Sensors
{ {
public class UserNotificationStateSensor : AbstractSensor
{
public UserNotificationStateSensor(MqttPublisher publisher, string name = "NotificationState")
{
this.Id = new Guid();
this.Name = name;
this.Publisher = publisher;
}
public UserNotificationStateSensor(MqttPublisher publisher, string name, Guid id)
{
this.Id = id;
this.Name = name;
this.Publisher = publisher;
}
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/{this.Name}/state",
Icon = "mdi:laptop",
});
}
public override string GetState()
{
return GetStateEnum().ToString();
}
[DllImport("shell32.dll")]
static extern int SHQueryUserNotificationState(out UserNotificationState state);
public UserNotificationState GetStateEnum()
{
SHQueryUserNotificationState(out UserNotificationState state);
return state;
}
}
public enum UserNotificationState public enum UserNotificationState
{ {
/// <summary> /// <summary>
@ -43,17 +87,4 @@ public enum UserNotificationState
/// </summary> /// </summary>
QuietTime = 6 QuietTime = 6
}; };
public class UserNotificationStateDetector
{
[DllImport("shell32.dll")]
static extern int SHQueryUserNotificationState(out UserNotificationState state);
public UserNotificationState GetState(){
UserNotificationState state;
SHQueryUserNotificationState(out state);
return state;
}
}
} }

@ -1,11 +1,11 @@
using System; using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Hosting;
using hass_desktop_service.StateDetectors.Windows.Fullscreen; using hass_desktop_service.Communication;
using Microsoft.Extensions.Configuration;
using MQTTnet.Client.Options;
using hass_desktop_service.Data;
namespace hass_desktop_service namespace hass_desktop_service
{ {
@ -13,7 +13,6 @@ namespace hass_desktop_service
{ {
public static void Main(string[] args) public static void Main(string[] args)
{ {
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{ {
CreateHostBuilder(args).Build().Run(); CreateHostBuilder(args).Build().Run();
@ -29,7 +28,25 @@ namespace hass_desktop_service
Host.CreateDefaultBuilder(args) Host.CreateDefaultBuilder(args)
.ConfigureServices((hostContext, services) => .ConfigureServices((hostContext, services) =>
{ {
services.AddSingleton<UserNotificationStateDetector>(); IConfiguration configuration = hostContext.Configuration;
IConfigurationSection mqttSection = configuration.GetSection("MqttBroker");
var mqttClientOptions = new MqttClientOptionsBuilder()
.WithTcpServer(mqttSection.GetValue<string>("Host"))
// .WithTls()
.WithCredentials(mqttSection.GetValue<string>("Username"), mqttSection.GetValue<string>("Password"))
.Build();
var deviceConfig = new DeviceConfigModel
{
Name = "hass-workstation-service3",
//TODO: make this more dynamic
Identifiers = "hass-workstation-service_unique4",
Sw_version = "0.0.4"
};
services.AddSingleton(configuration);
services.AddSingleton(deviceConfig);
services.AddSingleton(mqttClientOptions);
services.AddSingleton<ConfiguredSensorsService>();
services.AddSingleton<MqttPublisher>();
services.AddHostedService<Worker>(); services.AddHostedService<Worker>();
}); });
} }

@ -3,30 +3,55 @@ using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using hass_desktop_service.StateDetectors.Windows.Fullscreen; using hass_desktop_service.Communication;
using hass_desktop_service.Data;
using hass_desktop_service.Domain.Sensors;
using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using MQTTnet.Client;
namespace hass_desktop_service namespace hass_desktop_service
{ {
public class Worker : BackgroundService public class Worker : BackgroundService
{ {
private readonly ILogger<Worker> _logger; private readonly ILogger<Worker> _logger;
private readonly UserNotificationStateDetector _userNotificationStateDetector; private readonly ConfiguredSensorsService _configuredSensorsService;
private readonly MqttPublisher _mqttPublisher;
public Worker(ILogger<Worker> logger, UserNotificationStateDetector userNotificationStateDetector) public Worker(ILogger<Worker> logger,
ConfiguredSensorsService configuredSensorsService,
MqttPublisher mqttPublisher)
{ {
_logger = logger; _logger = logger;
this._userNotificationStateDetector = userNotificationStateDetector; this._configuredSensorsService = configuredSensorsService;
this._mqttPublisher = mqttPublisher;
} }
protected override async Task ExecuteAsync(CancellationToken stoppingToken) protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{ {
while (!_mqttPublisher.IsConnected)
{
_logger.LogInformation("Connecting to MQTT broker...");
await Task.Delay(2000);
}
_logger.LogInformation("Connected. Sending auto discovery messages.");
foreach (AbstractSensor sensor in _configuredSensorsService.ConfiguredSensors)
{
await sensor.PublishAutoDiscoveryConfigAsync();
}
while (!stoppingToken.IsCancellationRequested) while (!stoppingToken.IsCancellationRequested)
{ {
_logger.LogInformation("Worker running at: {time}", DateTimeOffset.Now); _logger.LogDebug("Worker running at: {time}", DateTimeOffset.Now);
_logger.LogInformation($"Notificationstate: {this._userNotificationStateDetector.GetState()}");
foreach (AbstractSensor sensor in _configuredSensorsService.ConfiguredSensors)
{
await sensor.PublishStateAsync();
}
// announce autodiscovery every 30 seconds
if (_mqttPublisher.LastConfigAnnounce < DateTime.UtcNow.AddSeconds(-30))
{
// TODO: make every sensor publish its auto discovery config
}
await Task.Delay(1000, stoppingToken); await Task.Delay(1000, stoppingToken);
} }
} }

@ -5,5 +5,15 @@
"Microsoft": "Warning", "Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information" "Microsoft.Hosting.Lifetime": "Information"
} }
} },
"MqttBroker": {
"Host": "192.168.2.6",
"Username": "tester",
"Password": "tester"
},
"ConfiguredSensors": [
{"Type": "UserNotificationStateSensor", "Ïd": "17fec74e-5d82-4334-8d40-45cfa8449228", "Name": "Sensor1"},
{"Type": "UserNotificationStateSensor", "Ïd": "27fec74e-5d82-4334-8d40-45cfa8449228", "Name": "Sensor2"},
{"Type": "UserNotificationStateSensor", "Ïd": "37fec74e-5d82-4334-8d40-45cfa8449228", "Name": "Sensor3"}
]
} }

@ -8,5 +8,6 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.Extensions.Hosting" Version="3.1.10" /> <PackageReference Include="Microsoft.Extensions.Hosting" Version="3.1.10" />
<PackageReference Include="MQTTnet" Version="3.0.13" />
</ItemGroup> </ItemGroup>
</Project> </Project>

Loading…
Cancel
Save