diff --git a/.vs/hass-workstation-service/DesignTimeBuild/.dtbcache.v2 b/.vs/hass-workstation-service/DesignTimeBuild/.dtbcache.v2 index 27b853f..a1b0305 100644 Binary files a/.vs/hass-workstation-service/DesignTimeBuild/.dtbcache.v2 and b/.vs/hass-workstation-service/DesignTimeBuild/.dtbcache.v2 differ diff --git a/.vs/hass-workstation-service/v16/.suo b/.vs/hass-workstation-service/v16/.suo index e8ad257..1ef96ae 100644 Binary files a/.vs/hass-workstation-service/v16/.suo and b/.vs/hass-workstation-service/v16/.suo differ diff --git a/README.md b/README.md index a57a8bf..efa3bf1 100644 --- a/README.md +++ b/README.md @@ -40,4 +40,4 @@ This sensor watches the UserNotificationState. This is normally used in applicat ### Dummy -This sensor spits out a random number every second. Useful for testing, maybe you'll find some other use for it. \ No newline at end of file +This sensor spits out a random number every second. Useful for testing, maybe you'll find some other use for it. diff --git a/UserInterface/App.axaml b/UserInterface/App.axaml index 172252f..1e1a085 100644 --- a/UserInterface/App.axaml +++ b/UserInterface/App.axaml @@ -8,6 +8,7 @@ - + + diff --git a/UserInterface/UserInterface.csproj b/UserInterface/UserInterface.csproj index 100580f..3938707 100644 --- a/UserInterface/UserInterface.csproj +++ b/UserInterface/UserInterface.csproj @@ -1,4 +1,4 @@ - + WinExe netcoreapp3.1 @@ -12,6 +12,7 @@ + diff --git a/UserInterface/Util/OpenBrowser.cs b/UserInterface/Util/OpenBrowser.cs new file mode 100644 index 0000000..f1c2fff --- /dev/null +++ b/UserInterface/Util/OpenBrowser.cs @@ -0,0 +1,40 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Runtime.InteropServices; +using System.Text; + +namespace UserInterface.Util +{ + public class BrowserUtil + { + public static void OpenBrowser(string url) + { + try + { + Process.Start(url); + } + catch + { + // hack because of this: https://github.com/dotnet/corefx/issues/10361 + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + url = url.Replace("&", "^&"); + Process.Start(new ProcessStartInfo("cmd", $"/c start {url}") { CreateNoWindow = true }); + } + else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) + { + Process.Start("xdg-open", url); + } + else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) + { + Process.Start("open", url); + } + else + { + throw; + } + } + } + } +} diff --git a/UserInterface/ViewModels/AddSensorViewModel.cs b/UserInterface/ViewModels/AddSensorViewModel.cs new file mode 100644 index 0000000..1fd95bf --- /dev/null +++ b/UserInterface/ViewModels/AddSensorViewModel.cs @@ -0,0 +1,28 @@ +using hass_workstation_service.Communication.InterProcesCommunication.Models; +using ReactiveUI; +using System; +using System.Collections.Generic; +using System.Text; + +namespace UserInterface.ViewModels +{ + public class AddSensorViewModel : ViewModelBase + { + private AvailableSensors selectedType; + private string description; + + public string Description { get => description; set => this.RaiseAndSetIfChanged(ref description, value); } + + private string moreInfoLink; + + public string MoreInfoLink + { + get { return moreInfoLink; } + set { this.RaiseAndSetIfChanged(ref moreInfoLink, value); } + } + + + public AvailableSensors SelectedType { get => selectedType; set => this.RaiseAndSetIfChanged(ref selectedType, value); } + public string Name { get; set; } + } +} diff --git a/UserInterface/ViewModels/SensorSettingsViewModel.cs b/UserInterface/ViewModels/SensorSettingsViewModel.cs new file mode 100644 index 0000000..dc2eca7 --- /dev/null +++ b/UserInterface/ViewModels/SensorSettingsViewModel.cs @@ -0,0 +1,24 @@ +using ReactiveUI; +using System; +using System.Collections.Generic; +using System.Text; + +namespace UserInterface.ViewModels +{ + public class SensorSettingsViewModel : ViewModelBase + { + private ICollection configuredSensors; + + public ICollection ConfiguredSensors { get => configuredSensors; set => this.RaiseAndSetIfChanged(ref configuredSensors, value); } + } + + public class SensorViewModel : ViewModelBase + { + private string _value; + + public Guid Id { get; set; } + public string Type { get; set; } + public string Name { get; set; } + public string Value { get => _value; set => this.RaiseAndSetIfChanged(ref _value, value); } + } +} diff --git a/UserInterface/Views/AddSensorDialog.axaml b/UserInterface/Views/AddSensorDialog.axaml new file mode 100644 index 0000000..bb5fdd8 --- /dev/null +++ b/UserInterface/Views/AddSensorDialog.axaml @@ -0,0 +1,19 @@ + + + Sensor type + + + + + Name + + + + diff --git a/UserInterface/Views/AddSensorDialog.axaml.cs b/UserInterface/Views/AddSensorDialog.axaml.cs new file mode 100644 index 0000000..52b567e --- /dev/null +++ b/UserInterface/Views/AddSensorDialog.axaml.cs @@ -0,0 +1,86 @@ +using Avalonia; +using Avalonia.Controls; +using Avalonia.Interactivity; +using Avalonia.Markup.Xaml; +using hass_workstation_service.Communication.InterProcesCommunication.Models; +using hass_workstation_service.Communication.NamedPipe; +using JKang.IpcServiceFramework.Client; +using Microsoft.Extensions.DependencyInjection; +using System; +using System.Dynamic; +using System.Linq; +using System.Text.Json; +using UserInterface.Util; +using UserInterface.ViewModels; + +namespace UserInterface.Views +{ + public class AddSensorDialog : Window + { + private readonly IIpcClient client; + public ComboBox comboBox { get; set; } + public AddSensorDialog() + { + this.InitializeComponent(); +#if DEBUG + this.AttachDevTools(); +#endif + this.comboBox = this.FindControl("ComboBox"); + this.comboBox.Items = Enum.GetValues(typeof(AvailableSensors)).Cast(); + + // register IPC clients + ServiceProvider serviceProvider = new ServiceCollection() + .AddNamedPipeIpcClient("addsensor", pipeName: "pipeinternal") + .BuildServiceProvider(); + + // resolve IPC client factory + IIpcClientFactory clientFactory = serviceProvider + .GetRequiredService>(); + + // create client + this.client = clientFactory.CreateClient("addsensor"); + + + DataContext = new AddSensorViewModel(); + } + + public async void Save(object sender, RoutedEventArgs args) + { + var item = ((AddSensorViewModel)this.DataContext); + dynamic model = new { Name = item.Name }; + string json = JsonSerializer.Serialize(model); + await this.client.InvokeAsync(x => x.AddSensor(item.SelectedType, json)); + Close(); + } + + public void ComboBoxClosed(object sender, SelectionChangedEventArgs args) + { + var item = ((AddSensorViewModel)this.DataContext); + switch (item.SelectedType) + { + case AvailableSensors.UserNotificationStateSensor: + item.Description = "This sensor watches the UserNotificationState. This is normally used in applications to determine if it is appropriate to send a notification but we can use it to expose this state. \n "; + item.MoreInfoLink = "https://github.com/sleevezipper/hass-workstation-service#usernotificationstate"; + break; + case AvailableSensors.DummySensor: + item.Description = "This sensor spits out a random number every second. Useful for testing, maybe you'll find some other use for it."; + item.MoreInfoLink = "https://github.com/sleevezipper/hass-workstation-service#dummy"; + break; + default: + item.Description = null; + item.MoreInfoLink = null; + break; + } + } + public void OpenInfo(object sender, RoutedEventArgs args) + { + var item = ((AddSensorViewModel)this.DataContext); + BrowserUtil.OpenBrowser(item.MoreInfoLink); + } + + private void InitializeComponent() + { + AvaloniaXamlLoader.Load(this); + } + } +} diff --git a/UserInterface/Views/MainWindow.axaml b/UserInterface/Views/MainWindow.axaml index c2123af..50d8e41 100644 --- a/UserInterface/Views/MainWindow.axaml +++ b/UserInterface/Views/MainWindow.axaml @@ -8,17 +8,15 @@ x:Class="UserInterface.Views.MainWindow" Icon="/Assets/hass-workstation-logo.ico" SizeToContent="WidthAndHeight" - Title="Settings" - Background="LightGray"> - - - - + Title="Settings"> + + + - - - + + + @@ -28,7 +26,7 @@ + + + + + + + + + Add some sensors by clicking the "Add" button. + + + diff --git a/UserInterface/Views/SensorSettings.axaml.cs b/UserInterface/Views/SensorSettings.axaml.cs index 2a2e62c..fa5041d 100644 --- a/UserInterface/Views/SensorSettings.axaml.cs +++ b/UserInterface/Views/SensorSettings.axaml.cs @@ -10,12 +10,17 @@ using System.Reactive.Linq; using UserInterface.ViewModels; using System.Security; using hass_workstation_service.Communication.InterProcesCommunication.Models; +using System.Collections.Generic; +using System.Linq; +using Avalonia.Controls.ApplicationLifetimes; namespace UserInterface.Views { public class SensorSettings : UserControl { private readonly IIpcClient client; + private DataGrid _dataGrid { get; set; } + private bool sensorsNeedToRefresh { get; set; } public SensorSettings() { @@ -33,23 +38,61 @@ namespace UserInterface.Views this.client = clientFactory.CreateClient("sensors"); - DataContext = new BrokerSettingsViewModel(); + DataContext = new SensorSettingsViewModel(); + GetConfiguredSensors(); + this._dataGrid = this.FindControl("Grid"); } - public void Ping(object sender, RoutedEventArgs args) { - var result = this.client.InvokeAsync(x => x.Ping("ping")).Result; - } - public void Configure(object sender, RoutedEventArgs args) + + public async void GetConfiguredSensors() { - var model = (BrokerSettingsViewModel)this.DataContext; - var result = this.client.InvokeAsync(x => x.WriteMqttBrokerSettingsAsync(new MqttSettings() { Host = model.Host, Username = model.Username, Password = model.Password })); + sensorsNeedToRefresh = false; + List status = await this.client.InvokeAsync(x => x.GetConfiguredSensors()); + + ((SensorSettingsViewModel)this.DataContext).ConfiguredSensors = status.Select(s => new SensorViewModel() { Name = s.Name, Type = s.Type, Value = s.Value, Id = s.Id }).ToList(); + while (!sensorsNeedToRefresh) + { + await Task.Delay(2000); + List statusUpdated = await this.client.InvokeAsync(x => x.GetConfiguredSensors()); + var configuredSensors = ((SensorSettingsViewModel)this.DataContext).ConfiguredSensors; + statusUpdated.ForEach(s => + { + var configuredSensor = configuredSensors.FirstOrDefault(cs => cs.Id == s.Id); + if (configuredSensor != null) + { + configuredSensor.Value = s.Value; + + configuredSensors.FirstOrDefault(cs => cs.Id == s.Id).Value = s.Value; + } + }); + } + + } + public void Delete(object sender, RoutedEventArgs args) + { + var item = ((SensorViewModel)this._dataGrid.SelectedItem); + this.client.InvokeAsync(x => x.RemoveSensorById(item.Id)); + // TODO: improve this. it is not working well. + sensorsNeedToRefresh = true; + GetConfiguredSensors(); } + public async void AddSensor(object sender, RoutedEventArgs args) + { + AddSensorDialog dialog = new AddSensorDialog(); + if (Application.Current.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) + { + await dialog.ShowDialog(desktop.MainWindow); + GetConfiguredSensors(); + } + } private void InitializeComponent() { AvaloniaXamlLoader.Load(this); } + + } } diff --git a/hass-workstation-service/Communication/InterProcesCommunication/InterProcessApi.cs b/hass-workstation-service/Communication/InterProcesCommunication/InterProcessApi.cs index 97fff4a..4a7ecbc 100644 --- a/hass-workstation-service/Communication/InterProcesCommunication/InterProcessApi.cs +++ b/hass-workstation-service/Communication/InterProcesCommunication/InterProcessApi.cs @@ -1,9 +1,15 @@ using hass_workstation_service.Communication.InterProcesCommunication.Models; using hass_workstation_service.Communication.NamedPipe; +using hass_workstation_service.Communication.Util; using hass_workstation_service.Data; +using hass_workstation_service.Domain.Sensors; +using Serilog; using System; using System.Collections.Generic; +using System.Dynamic; +using System.Linq; using System.Text; +using System.Text.Json; using System.Threading.Tasks; namespace hass_workstation_service.Communication.InterProcesCommunication @@ -57,5 +63,42 @@ namespace hass_workstation_service.Communication.InterProcesCommunication { return this._configurationService.IsAutoStartEnabled(); } + + public List GetConfiguredSensors() + { + return this._configurationService.ConfiguredSensors.Select(s => new ConfiguredSensorModel() { Name = s.Name, Type = s.GetType().Name, Value = s.PreviousPublishedState, Id = s.Id }).ToList(); + } + + public void RemoveSensorById(Guid id) + { + this._configurationService.DeleteConfiguredSensor(id); + } + + public void AddSensor(AvailableSensors sensorType, string json) + { + var serializerOptions = new JsonSerializerOptions + { + Converters = { new DynamicJsonConverter() } + }; + dynamic model = JsonSerializer.Deserialize(json, serializerOptions); + + AbstractSensor sensorToCreate = null; + switch (sensorType) + { + case AvailableSensors.UserNotificationStateSensor: + sensorToCreate = new UserNotificationStateSensor(this._publisher, model.Name); + break; + case AvailableSensors.DummySensor: + sensorToCreate = new DummySensor(this._publisher, model.Name); + break; + default: + Log.Logger.Error("Unknown sensortype"); + break; + } + if (sensorToCreate != null) + { + this._configurationService.AddConfiguredSensor(sensorToCreate); + } + } } } diff --git a/hass-workstation-service/Communication/InterProcesCommunication/ServiceContractInterfaces.cs b/hass-workstation-service/Communication/InterProcesCommunication/ServiceContractInterfaces.cs index b686baa..a85f5fe 100644 --- a/hass-workstation-service/Communication/InterProcesCommunication/ServiceContractInterfaces.cs +++ b/hass-workstation-service/Communication/InterProcesCommunication/ServiceContractInterfaces.cs @@ -14,5 +14,8 @@ namespace hass_workstation_service.Communication.NamedPipe MqqtClientStatus GetMqqtClientStatus(); void EnableAutostart(bool enable); bool IsAutoStartEnabled(); + List GetConfiguredSensors(); + void RemoveSensorById(Guid id); + void AddSensor(AvailableSensors sensorType, string json); } } diff --git a/hass-workstation-service/Communication/InterProcesCommunication/ServiceContractModels.cs b/hass-workstation-service/Communication/InterProcesCommunication/ServiceContractModels.cs index a39663d..ff95e36 100644 --- a/hass-workstation-service/Communication/InterProcesCommunication/ServiceContractModels.cs +++ b/hass-workstation-service/Communication/InterProcesCommunication/ServiceContractModels.cs @@ -1,4 +1,5 @@ -using System; +using hass_workstation_service.Domain.Sensors; +using System; using System.Collections.Generic; using System.Text; @@ -16,4 +17,18 @@ namespace hass_workstation_service.Communication.InterProcesCommunication.Models public bool IsConnected { get; set; } public string Message { get; set; } } + + public class ConfiguredSensorModel + { + public Guid Id { get; set; } + public string Type { get; set; } + public string Name { get; set; } + public string Value { get; set; } + } + + public enum AvailableSensors + { + UserNotificationStateSensor, + DummySensor + } } diff --git a/hass-workstation-service/Communication/Util/DynamicJsonConverter.cs b/hass-workstation-service/Communication/Util/DynamicJsonConverter.cs new file mode 100644 index 0000000..6856b40 --- /dev/null +++ b/hass-workstation-service/Communication/Util/DynamicJsonConverter.cs @@ -0,0 +1,127 @@ + +using System; +using System.Collections.Generic; +using System.Dynamic; +using System.Text.Json; +using System.Text.Json.Serialization; +namespace hass_workstation_service.Communication.Util +{ + /// + /// Temp Dynamic Converter + /// by:tchivs@live.cn + /// + public class DynamicJsonConverter : JsonConverter + { + public override dynamic Read(ref Utf8JsonReader reader, + Type typeToConvert, + JsonSerializerOptions options) + { + + if (reader.TokenType == JsonTokenType.True) + { + return true; + } + + if (reader.TokenType == JsonTokenType.False) + { + return false; + } + + if (reader.TokenType == JsonTokenType.Number) + { + if (reader.TryGetInt64(out long l)) + { + return l; + } + + return reader.GetDouble(); + } + + if (reader.TokenType == JsonTokenType.String) + { + if (reader.TryGetDateTime(out DateTime datetime)) + { + return datetime; + } + + return reader.GetString(); + } + + if (reader.TokenType == JsonTokenType.StartObject) + { + using JsonDocument documentV = JsonDocument.ParseValue(ref reader); + return ReadObject(documentV.RootElement); + } + // Use JsonElement as fallback. + // Newtonsoft uses JArray or JObject. + JsonDocument document = JsonDocument.ParseValue(ref reader); + return document.RootElement.Clone(); + } + + private object ReadObject(JsonElement jsonElement) + { + IDictionary expandoObject = new ExpandoObject(); + foreach (var obj in jsonElement.EnumerateObject()) + { + var k = obj.Name; + var value = ReadValue(obj.Value); + expandoObject[k] = value; + } + return expandoObject; + } + private object? ReadValue(JsonElement jsonElement) + { + object? result = null; + switch (jsonElement.ValueKind) + { + case JsonValueKind.Object: + result = ReadObject(jsonElement); + break; + case JsonValueKind.Array: + result = ReadList(jsonElement); + break; + case JsonValueKind.String: + //TODO: Missing Datetime&Bytes Convert + result = jsonElement.GetString(); + break; + case JsonValueKind.Number: + //TODO: more num type + result = 0; + if (jsonElement.TryGetInt64(out long l)) + { + result = l; + } + break; + case JsonValueKind.True: + result = true; + break; + case JsonValueKind.False: + result = false; + break; + case JsonValueKind.Undefined: + case JsonValueKind.Null: + result = null; + break; + default: + throw new ArgumentOutOfRangeException(); + } + return result; + } + + private object? ReadList(JsonElement jsonElement) + { + IList list = new List(); + foreach (var item in jsonElement.EnumerateArray()) + { + list.Add(ReadValue(item)); + } + return list.Count == 0 ? null : list; + } + public override void Write(Utf8JsonWriter writer, + object value, + JsonSerializerOptions options) + { + // writer.WriteStringValue(value.ToString()); + } + } +} \ No newline at end of file diff --git a/hass-workstation-service/Data/ConfigurationService.cs b/hass-workstation-service/Data/ConfigurationService.cs index 07dab17..5e3de6c 100644 --- a/hass-workstation-service/Data/ConfigurationService.cs +++ b/hass-workstation-service/Data/ConfigurationService.cs @@ -123,6 +123,13 @@ namespace hass_workstation_service.Data WriteSettingsAsync(); } + public void DeleteConfiguredSensor(Guid id) + { + var sensorToRemove = this.ConfiguredSensors.FirstOrDefault(s => s.Id == id); + this.ConfiguredSensors.Remove(sensorToRemove); + WriteSettingsAsync(); + } + public void AddConfiguredSensors(List sensors) { sensors.ForEach((sensor) => this.ConfiguredSensors.Add(sensor)); diff --git a/hass-workstation-service/Data/IConfigurationService.cs b/hass-workstation-service/Data/IConfigurationService.cs index 9a8a69d..3c63154 100644 --- a/hass-workstation-service/Data/IConfigurationService.cs +++ b/hass-workstation-service/Data/IConfigurationService.cs @@ -23,5 +23,6 @@ namespace hass_workstation_service.Data Task GetMqttBrokerSettings(); void EnableAutoStart(bool enable); bool IsAutoStartEnabled(); + void DeleteConfiguredSensor(Guid id); } } \ No newline at end of file diff --git a/hass-workstation-service/Domain/Device.cs b/hass-workstation-service/Domain/Device.cs deleted file mode 100644 index e2f82c3..0000000 --- a/hass-workstation-service/Domain/Device.cs +++ /dev/null @@ -1,18 +0,0 @@ -using System; -using System.Collections.Generic; -using hass_workstation_service.Domain.Sensors; - -namespace hass_workstation_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 Sensors { get; set; } - - } -} \ No newline at end of file diff --git a/hass-workstation-service/Domain/Sensors/AbstractSensor.cs b/hass-workstation-service/Domain/Sensors/AbstractSensor.cs index efb59e0..03bf74a 100644 --- a/hass-workstation-service/Domain/Sensors/AbstractSensor.cs +++ b/hass-workstation-service/Domain/Sensors/AbstractSensor.cs @@ -5,9 +5,7 @@ using MQTTnet; namespace hass_workstation_service.Domain.Sensors { - /// - /// This - /// + public abstract class AbstractSensor { public Guid Id { get; protected set; } diff --git a/hass-workstation-service/Domain/Sensors/UserNotificationStateSensor.cs b/hass-workstation-service/Domain/Sensors/UserNotificationStateSensor.cs index 68600e5..588cc9f 100644 --- a/hass-workstation-service/Domain/Sensors/UserNotificationStateSensor.cs +++ b/hass-workstation-service/Domain/Sensors/UserNotificationStateSensor.cs @@ -88,11 +88,7 @@ namespace hass_workstation_service.Domain.Sensors QuietTime = 6, /// - /// Introduced in Windows 7. The current user is in "quiet time", which is the first hour after - /// a new user logs into his or her account for the first time. During this time, most notifications - /// should not be sent or shown. This lets a user become accustomed to a new computer system - /// without those distractions. - /// Quiet time also occurs for each user after an operating system upgrade or clean installation. + /// A Windows Store app is running. /// RunningWindowsStoreApp = 7 }; diff --git a/hass-workstation-service/Program.cs b/hass-workstation-service/Program.cs index 4d40bb3..8ee7285 100644 --- a/hass-workstation-service/Program.cs +++ b/hass-workstation-service/Program.cs @@ -34,7 +34,9 @@ namespace hass_workstation_service // We do it this way because there is currently no way to pass an argument to a dotnet core app when using clickonce if (Process.GetProcessesByName("hass-workstation-service").Count() > 1) //bg service running { +#if !DEBUG StartUI(); +#endif } else { @@ -61,7 +63,7 @@ namespace hass_workstation_service } } - + } public static IHostBuilder CreateHostBuilder(string[] args) => Host.CreateDefaultBuilder(args) diff --git a/hass-workstation-service/Properties/PublishProfiles/AzureHosted.pubxml b/hass-workstation-service/Properties/PublishProfiles/AzureHosted.pubxml index 93ac6ec..9a7f24f 100644 --- a/hass-workstation-service/Properties/PublishProfiles/AzureHosted.pubxml +++ b/hass-workstation-service/Properties/PublishProfiles/AzureHosted.pubxml @@ -4,12 +4,12 @@ https://go.microsoft.com/fwlink/?LinkID=208121. --> - 15 + 20 1.0.0.* True Release True - https://github.com/sleevezipper/hass-workstation-service/issues + https://github.com/sleevezipper/hass-workstation-service True true Web @@ -32,7 +32,7 @@ https://go.microsoft.com/fwlink/?LinkID=208121. False sha256RSA True - https://github.com/sleevezipper/hass-workstation-service/issues + https://github.com/sleevezipper/hass-workstation-service netcoreapp3.1 True True diff --git a/hass-workstation-service/UserInterface.exe b/hass-workstation-service/UserInterface.exe index 5352119..8af97ca 100644 Binary files a/hass-workstation-service/UserInterface.exe and b/hass-workstation-service/UserInterface.exe differ diff --git a/hass-workstation-service/Worker.cs b/hass-workstation-service/Worker.cs index f054334..96a5e93 100644 --- a/hass-workstation-service/Worker.cs +++ b/hass-workstation-service/Worker.cs @@ -31,18 +31,13 @@ namespace hass_workstation_service { _configurationService.ReadSensorSettings(_mqttPublisher); - while (!_mqttPublisher.IsConnected) { _logger.LogInformation($"Connecting to MQTT broker..."); await Task.Delay(2000); } _logger.LogInformation("Connected. Sending auto discovery messages."); - // if there are no configured sensors we add a dummy sensor - if (_configurationService.ConfiguredSensors.Count == 0) - { - _configurationService.AddConfiguredSensors(new List() { new DummySensor(_mqttPublisher), new UserNotificationStateSensor(_mqttPublisher) }); - } + foreach (AbstractSensor sensor in _configurationService.ConfiguredSensors) { await sensor.PublishAutoDiscoveryConfigAsync();