From 9489e977b32d503a27e8fdd172f286e7f9239448 Mon Sep 17 00:00:00 2001 From: sleevezipper Date: Thu, 31 Dec 2020 18:28:21 +0100 Subject: [PATCH] improve ipc structure and wip implement ui --- .../DesignTimeBuild/.dtbcache.v2 | Bin 152374 -> 161353 bytes .vs/hass-workstation-service/v16/.suo | Bin 230912 -> 231936 bytes UserInterface/UserInterface.csproj | 8 +- .../BackgroundServiceSettingsViewModel.cs | 26 ++++ .../ViewModels/BrokerSettingsViewModel.cs | 30 ++++- .../Views/BackgroundServiceSettings.axaml | 14 +++ .../Views/BackgroundServiceSettings.axaml.cs | 76 ++++++++++++ UserInterface/Views/BrokerSettings.axaml | 19 +++ UserInterface/Views/BrokerSettings.axaml.cs | 82 +++++++++++++ UserInterface/Views/MainWindow.axaml | 20 ++- ...kerSettings.axaml => SensorSettings.axaml} | 9 +- ...tings.axaml.cs => SensorSettings.axaml.cs} | 23 ++-- .../InterProcessApi.cs | 51 ++++++++ .../ServiceContractInterfaces.cs | 8 +- .../ServiceContractModels.cs | 19 +++ .../Communication/MQTT/MqttPublisher.cs | 20 ++- .../Data/ConfigurationService.cs | 114 ++++++++++-------- .../Data/IConfigurationService.cs | 9 +- hass-workstation-service/Program.cs | 6 +- 19 files changed, 448 insertions(+), 86 deletions(-) create mode 100644 UserInterface/ViewModels/BackgroundServiceSettingsViewModel.cs create mode 100644 UserInterface/Views/BackgroundServiceSettings.axaml create mode 100644 UserInterface/Views/BackgroundServiceSettings.axaml.cs create mode 100644 UserInterface/Views/BrokerSettings.axaml create mode 100644 UserInterface/Views/BrokerSettings.axaml.cs rename UserInterface/Views/{BrokerSettings/BrokerSettings.axaml => SensorSettings.axaml} (69%) rename UserInterface/Views/{BrokerSettings/BrokerSettings.axaml.cs => SensorSettings.axaml.cs} (77%) create mode 100644 hass-workstation-service/Communication/InterProcesCommunication/InterProcessApi.cs create mode 100644 hass-workstation-service/Communication/InterProcesCommunication/ServiceContractModels.cs diff --git a/.vs/hass-workstation-service/DesignTimeBuild/.dtbcache.v2 b/.vs/hass-workstation-service/DesignTimeBuild/.dtbcache.v2 index 4e4a2cb27fa30395650ac36f5aea8606f52a57a3..27b853f1334a8a885617f2d2ba5a50be54840b9f 100644 GIT binary patch literal 161353 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⁡7|gG0vtx*Fo44e91d^XOyFXGO9)&7kS34@ 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 literal 152374 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 delta 14648 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

s+$_8uC8B$S0$+sfFcqzN}Bcj`DMZyQp#J~ zg>1Ylzl84I#%lurk5NN&BDvSsE<@vaexBjPo}{g%`1yySDL z@Jj+c&?wQvZkH%G83p`=q!aIq2jc7ff-b_b0M7R2r3RAkcNW+@j8AZe0+wpJfQ)K)() zVNl?@yJ^#RR`J(b;!^&8`k@u{LpkB6ABCHRQ@u1u zxXh|I>Y@9iBK3U~shl4fw39*4<(hIq&)dJC1x#z4@x!y`kSnofC&%6SHeD*}rq&HH z@JT6&=j+pj>#xLN4fN1wbgSDpbE_A*B0zXpPGK3S{aDj@)qP2Ir&@krE zB4IY=J;`7}ao&G!zCyWjDLKo#eT4h?xifSIFP|$s3HM0Kn}m(rRxU(Z{R@Nx!~;Gy z6aOpTji@)}vvUXK{BK4$b@P`AU-B0WLJ8lsN%$LI(IQyLYWtA{_WuyRpjn8rn-Q{k zQG=l4@2?cjkvV9T%HTJfKr=YEPT0nG%ohf^ut~U||Fl*3f-J^XviT - + + BackgroundServiceSettings.axaml + + + SensorSettings.axaml + + %(Filename) diff --git a/UserInterface/ViewModels/BackgroundServiceSettingsViewModel.cs b/UserInterface/ViewModels/BackgroundServiceSettingsViewModel.cs new file mode 100644 index 0000000..267723a --- /dev/null +++ b/UserInterface/ViewModels/BackgroundServiceSettingsViewModel.cs @@ -0,0 +1,26 @@ +using hass_workstation_service.Communication.InterProcesCommunication.Models; +using ReactiveUI; +using System; +using System.Collections.Generic; +using System.Text; + +namespace UserInterface.ViewModels +{ + public class BackgroundServiceSettingsViewModel : ViewModelBase + { + private string host; + private string username; + private string password; + private string message; + private bool isRunning; + + public bool IsRunning { get => isRunning; set => this.RaiseAndSetIfChanged(ref isRunning, value); } + public string Message { get => message; set => this.RaiseAndSetIfChanged(ref message, value); } + + public void UpdateStatus(bool isRunning, string message) + { + this.IsRunning = isRunning; + this.Message = message; + } + } +} diff --git a/UserInterface/ViewModels/BrokerSettingsViewModel.cs b/UserInterface/ViewModels/BrokerSettingsViewModel.cs index f57b81a..42e8ea6 100644 --- a/UserInterface/ViewModels/BrokerSettingsViewModel.cs +++ b/UserInterface/ViewModels/BrokerSettingsViewModel.cs @@ -1,4 +1,6 @@ -using System; +using hass_workstation_service.Communication.InterProcesCommunication.Models; +using ReactiveUI; +using System; using System.Collections.Generic; using System.Text; @@ -6,8 +8,28 @@ namespace UserInterface.ViewModels { public class BrokerSettingsViewModel : ViewModelBase { - public string Host { get; set; } - public string Username { get; set; } - public string Password { get; set; } + private string host; + private string username; + private string password; + private string message; + private bool isConnected; + + public bool IsConnected { get => isConnected; set => this.RaiseAndSetIfChanged(ref isConnected, value); } + public string Message { get => message; set => this.RaiseAndSetIfChanged(ref message, value); } + public string Host { get => host; set => this.RaiseAndSetIfChanged(ref host, value); } + public string Username { get => username; set => this.RaiseAndSetIfChanged(ref username, value); } + public string Password { get => password; set => this.RaiseAndSetIfChanged(ref password, value); } + public void Update(MqttSettings settings) + { + this.Host = settings.Host; + this.Username = settings.Username; + this.Password = settings.Password; + } + + public void UpdateStatus(MqqtClientStatus status) + { + this.IsConnected = status.IsConnected; + this.Message = status.Message; + } } } diff --git a/UserInterface/Views/BackgroundServiceSettings.axaml b/UserInterface/Views/BackgroundServiceSettings.axaml new file mode 100644 index 0000000..d69e803 --- /dev/null +++ b/UserInterface/Views/BackgroundServiceSettings.axaml @@ -0,0 +1,14 @@ + + + Background service + + + + + + diff --git a/UserInterface/Views/BackgroundServiceSettings.axaml.cs b/UserInterface/Views/BackgroundServiceSettings.axaml.cs new file mode 100644 index 0000000..cf0721f --- /dev/null +++ b/UserInterface/Views/BackgroundServiceSettings.axaml.cs @@ -0,0 +1,76 @@ +using Avalonia; +using Avalonia.Controls; +using Avalonia.Markup.Xaml; +using Microsoft.Extensions.DependencyInjection; +using hass_workstation_service.Communication.NamedPipe; +using JKang.IpcServiceFramework.Client; +using System.Threading.Tasks; +using Avalonia.Interactivity; +using System.Reactive.Linq; +using UserInterface.ViewModels; +using System.Security; +using hass_workstation_service.Communication.InterProcesCommunication.Models; + +namespace UserInterface.Views +{ + public class BackgroundServiceSettings : UserControl + { + private readonly IIpcClient client; + + public BackgroundServiceSettings() + { + this.InitializeComponent(); + // register IPC clients + ServiceProvider serviceProvider = new ServiceCollection() + .AddNamedPipeIpcClient("broker", pipeName: "pipeinternal") + .BuildServiceProvider(); + + // resolve IPC client factory + IIpcClientFactory clientFactory = serviceProvider + .GetRequiredService>(); + + // create client + this.client = clientFactory.CreateClient("broker"); + + + DataContext = new BackgroundServiceSettingsViewModel(); + Ping(); + } + public async void Ping() { + while (true) + { + try + { + var result = await this.client.InvokeAsync(x => x.Ping("ping")); + if (result == "pong") + { + ((BackgroundServiceSettingsViewModel)this.DataContext).UpdateStatus(true, "All good"); + } + else + { + ((BackgroundServiceSettingsViewModel)this.DataContext).UpdateStatus(false, "Not running"); + } + } + catch (System.Exception) + { + ((BackgroundServiceSettingsViewModel)this.DataContext).UpdateStatus(false, "Not running"); + } + + + + await Task.Delay(1000); + } + } + + public void Start(object sender, RoutedEventArgs args) + { + //TODO: fix the path. This will depend on the deployment structure. + System.Diagnostics.Process.Start("hass-worstation-service.exe"); + } + + private void InitializeComponent() + { + AvaloniaXamlLoader.Load(this); + } + } +} diff --git a/UserInterface/Views/BrokerSettings.axaml b/UserInterface/Views/BrokerSettings.axaml new file mode 100644 index 0000000..dfea2d9 --- /dev/null +++ b/UserInterface/Views/BrokerSettings.axaml @@ -0,0 +1,19 @@ + + + Mqtt broker + + + IP or hostname + + Username + + Password + + + + diff --git a/UserInterface/Views/BrokerSettings.axaml.cs b/UserInterface/Views/BrokerSettings.axaml.cs new file mode 100644 index 0000000..1d80121 --- /dev/null +++ b/UserInterface/Views/BrokerSettings.axaml.cs @@ -0,0 +1,82 @@ +using Avalonia; +using Avalonia.Controls; +using Avalonia.Markup.Xaml; +using Microsoft.Extensions.DependencyInjection; +using hass_workstation_service.Communication.NamedPipe; +using JKang.IpcServiceFramework.Client; +using System.Threading.Tasks; +using Avalonia.Interactivity; +using System.Reactive.Linq; +using UserInterface.ViewModels; +using System.Security; +using hass_workstation_service.Communication.InterProcesCommunication.Models; + +namespace UserInterface.Views +{ + public class BrokerSettings : UserControl + { + private readonly IIpcClient client; + + public BrokerSettings() + { + this.InitializeComponent(); + // register IPC clients + ServiceProvider serviceProvider = new ServiceCollection() + .AddNamedPipeIpcClient("broker", pipeName: "pipeinternal") + .BuildServiceProvider(); + + // resolve IPC client factory + IIpcClientFactory clientFactory = serviceProvider + .GetRequiredService>(); + + // create client + this.client = clientFactory.CreateClient("broker"); + + + DataContext = new BrokerSettingsViewModel(); + GetSettings(); + GetStatus(); + + } + public void Ping(object sender, RoutedEventArgs args) { + var result = this.client.InvokeAsync(x => x.Ping("ping")).Result; + } + + public void Configure(object sender, RoutedEventArgs args) + { + var model = (BrokerSettingsViewModel)this.DataContext; + var result = this.client.InvokeAsync(x => x.WriteMqttBrokerSettingsAsync(new MqttSettings() { Host = model.Host, Username = model.Username, Password = model.Password })); + } + + public async void GetSettings() + { + MqttSettings settings = await this.client.InvokeAsync(x => x.GetMqttBrokerSettings()); + ((BrokerSettingsViewModel)this.DataContext).Update(settings); + } + + public async void GetStatus() + { + while (true) + { + try + { + MqqtClientStatus status = await this.client.InvokeAsync(x => x.GetMqqtClientStatus()); + + ((BrokerSettingsViewModel)this.DataContext).UpdateStatus(status); + await Task.Delay(1000); + } + catch (System.Exception) + { + ((BrokerSettingsViewModel)this.DataContext).UpdateStatus(new MqqtClientStatus() {IsConnected = false, Message = "Unable to get connectionstatus" }); + } + + } + } + + + private void InitializeComponent() + { + AvaloniaXamlLoader.Load(this); + } + } +} diff --git a/UserInterface/Views/MainWindow.axaml b/UserInterface/Views/MainWindow.axaml index f154da4..c2123af 100644 --- a/UserInterface/Views/MainWindow.axaml +++ b/UserInterface/Views/MainWindow.axaml @@ -4,16 +4,30 @@ xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:views="clr-namespace:UserInterface.Views" - mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" + mc:Ignorable="d" d:DesignWidth="400" d:DesignHeight="450" x:Class="UserInterface.Views.MainWindow" Icon="/Assets/hass-workstation-logo.ico" - Title="UserInterface"> + SizeToContent="WidthAndHeight" + Title="Settings" + Background="LightGray"> + - + + + + + + + diff --git a/UserInterface/Views/BrokerSettings/BrokerSettings.axaml b/UserInterface/Views/SensorSettings.axaml similarity index 69% rename from UserInterface/Views/BrokerSettings/BrokerSettings.axaml rename to UserInterface/Views/SensorSettings.axaml index 110b651..328e5f9 100644 --- a/UserInterface/Views/BrokerSettings/BrokerSettings.axaml +++ b/UserInterface/Views/SensorSettings.axaml @@ -2,10 +2,13 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" - mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" - x:Class="UserInterface.Views.BrokerSettings"> - + mc:Ignorable="d" d:DesignWidth="500" d:DesignHeight="450" + MaxWidth="500" + x:Class="UserInterface.Views.SensorSettings"> + Mqtt broker + + IP or hostname Username diff --git a/UserInterface/Views/BrokerSettings/BrokerSettings.axaml.cs b/UserInterface/Views/SensorSettings.axaml.cs similarity index 77% rename from UserInterface/Views/BrokerSettings/BrokerSettings.axaml.cs rename to UserInterface/Views/SensorSettings.axaml.cs index 75ff0f3..2a2e62c 100644 --- a/UserInterface/Views/BrokerSettings/BrokerSettings.axaml.cs +++ b/UserInterface/Views/SensorSettings.axaml.cs @@ -9,22 +9,20 @@ using Avalonia.Interactivity; using System.Reactive.Linq; using UserInterface.ViewModels; using System.Security; +using hass_workstation_service.Communication.InterProcesCommunication.Models; namespace UserInterface.Views { - public class BrokerSettings : UserControl + public class SensorSettings : UserControl { private readonly IIpcClient client; - private string _host { get; set; } - private string _username { get; set; } - private string _password { get; set; } - public BrokerSettings() + + public SensorSettings() { - DataContext = new BrokerSettingsViewModel(); this.InitializeComponent(); - // register IPC clients + // register IPC clients ServiceProvider serviceProvider = new ServiceCollection() - .AddNamedPipeIpcClient("client1", pipeName: "pipeinternal") + .AddNamedPipeIpcClient("sensors", pipeName: "pipeinternal") .BuildServiceProvider(); // resolve IPC client factory @@ -32,7 +30,10 @@ namespace UserInterface.Views .GetRequiredService>(); // create client - this.client = clientFactory.CreateClient("client1"); + this.client = clientFactory.CreateClient("sensors"); + + + DataContext = new BrokerSettingsViewModel(); } public void Ping(object sender, RoutedEventArgs args) { @@ -42,12 +43,10 @@ namespace UserInterface.Views public void Configure(object sender, RoutedEventArgs args) { var model = (BrokerSettingsViewModel)this.DataContext; - var result = this.client.InvokeAsync(x => x.WriteMqttBrokerSettings(model.Host, model.Username, model.Password)); + var result = this.client.InvokeAsync(x => x.WriteMqttBrokerSettingsAsync(new MqttSettings() { Host = model.Host, Username = model.Username, Password = model.Password })); } - - private void InitializeComponent() { AvaloniaXamlLoader.Load(this); diff --git a/hass-workstation-service/Communication/InterProcesCommunication/InterProcessApi.cs b/hass-workstation-service/Communication/InterProcesCommunication/InterProcessApi.cs new file mode 100644 index 0000000..343a0d3 --- /dev/null +++ b/hass-workstation-service/Communication/InterProcesCommunication/InterProcessApi.cs @@ -0,0 +1,51 @@ +using hass_workstation_service.Communication.InterProcesCommunication.Models; +using hass_workstation_service.Communication.NamedPipe; +using hass_workstation_service.Data; +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading.Tasks; + +namespace hass_workstation_service.Communication.InterProcesCommunication +{ + public class InterProcessApi : ServiceContractInterfaces + { + private readonly MqttPublisher _publisher; + private readonly IConfigurationService _configurationService; + + public InterProcessApi(MqttPublisher publisher, IConfigurationService configurationService) + { + _publisher = publisher; + _configurationService = configurationService; + } + + public MqqtClientStatus GetMqqtClientStatus() + { + return this._publisher.GetStatus(); + } + + public Task GetMqttBrokerSettings() + { + return this._configurationService.GetMqttBrokerSettings(); + } + + ///

+ /// You can use this to check if the application responds. + /// + /// + /// + public string Ping(string str) + { + if (str == "ping") + { + return "pong"; + } + return "what?"; + } + + public void WriteMqttBrokerSettingsAsync(MqttSettings settings) + { + this._configurationService.WriteMqttBrokerSettingsAsync(settings); + } + } +} diff --git a/hass-workstation-service/Communication/InterProcesCommunication/ServiceContractInterfaces.cs b/hass-workstation-service/Communication/InterProcesCommunication/ServiceContractInterfaces.cs index 8750f31..de5500e 100644 --- a/hass-workstation-service/Communication/InterProcesCommunication/ServiceContractInterfaces.cs +++ b/hass-workstation-service/Communication/InterProcesCommunication/ServiceContractInterfaces.cs @@ -1,12 +1,16 @@ -using System; +using hass_workstation_service.Communication.InterProcesCommunication.Models; +using System; using System.Collections.Generic; using System.Text; +using System.Threading.Tasks; namespace hass_workstation_service.Communication.NamedPipe { public interface ServiceContractInterfaces { + Task GetMqttBrokerSettings(); public string Ping(string str); - void WriteMqttBrokerSettings(string host, string username, string password); + void WriteMqttBrokerSettingsAsync(MqttSettings settings); + MqqtClientStatus GetMqqtClientStatus(); } } diff --git a/hass-workstation-service/Communication/InterProcesCommunication/ServiceContractModels.cs b/hass-workstation-service/Communication/InterProcesCommunication/ServiceContractModels.cs new file mode 100644 index 0000000..a39663d --- /dev/null +++ b/hass-workstation-service/Communication/InterProcesCommunication/ServiceContractModels.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace hass_workstation_service.Communication.InterProcesCommunication.Models +{ + public class MqttSettings + { + public string Host { get; set; } + public string Username { get; set; } + public string Password { get; set; } + } + + public class MqqtClientStatus + { + public bool IsConnected { get; set; } + public string Message { get; set; } + } +} diff --git a/hass-workstation-service/Communication/MQTT/MqttPublisher.cs b/hass-workstation-service/Communication/MQTT/MqttPublisher.cs index 2b1dd92..4ae21fb 100644 --- a/hass-workstation-service/Communication/MQTT/MqttPublisher.cs +++ b/hass-workstation-service/Communication/MQTT/MqttPublisher.cs @@ -2,6 +2,7 @@ using System; using System.Text.Json; using System.Threading; using System.Threading.Tasks; +using hass_workstation_service.Communication.InterProcesCommunication.Models; using hass_workstation_service.Communication.Util; using hass_workstation_service.Data; using Microsoft.Extensions.Logging; @@ -18,6 +19,7 @@ namespace hass_workstation_service.Communication private readonly IMqttClient _mqttClient; private readonly ILogger _logger; private readonly IConfigurationService _configurationService; + private string _mqttClientMessage { get; set; } public DateTime LastConfigAnnounce { get; private set; } public DeviceConfigModel DeviceConfigModel { get; private set; } public bool IsConnected @@ -45,16 +47,22 @@ namespace hass_workstation_service.Communication this.DeviceConfigModel = deviceConfigModel; this._configurationService = configurationService; - var options = _configurationService.ReadMqttSettings().Result; + var options = _configurationService.GetMqttClientOptionsAsync().Result; _configurationService.MqqtConfigChangedHandler = this.ReplaceMqttClient; var factory = new MqttFactory(); this._mqttClient = factory.CreateMqttClient(); this._mqttClient.ConnectAsync(options); + + this._mqttClient.UseConnectedHandler(e => { + this._mqttClientMessage = "All good"; + }); + // configure what happens on disconnect this._mqttClient.UseDisconnectedHandler(async e => { + this._mqttClientMessage = e.ReasonCode.ToString(); if (e.ReasonCode != MQTTnet.Client.Disconnecting.MqttClientDisconnectReason.NormalDisconnection) { _logger.LogWarning("Disconnected from server"); @@ -115,13 +123,15 @@ namespace hass_workstation_service.Communication } catch (Exception ex) { + this._mqttClientMessage = ex.Message; Log.Logger.Error("Could not connect to broker: " + ex.Message); } - finally - { - Log.Logger.Information("Connected to new broker"); - } } + + public MqqtClientStatus GetStatus() + { + return new MqqtClientStatus() { IsConnected = _mqttClient.IsConnected, Message = _mqttClientMessage }; + } } } diff --git a/hass-workstation-service/Data/ConfigurationService.cs b/hass-workstation-service/Data/ConfigurationService.cs index dcb4f0b..6dbf2bf 100644 --- a/hass-workstation-service/Data/ConfigurationService.cs +++ b/hass-workstation-service/Data/ConfigurationService.cs @@ -6,6 +6,7 @@ using System.Security; using System.Text.Json; using System.Threading.Tasks; using hass_workstation_service.Communication; +using hass_workstation_service.Communication.InterProcesCommunication.Models; using hass_workstation_service.Communication.NamedPipe; using hass_workstation_service.Domain.Sensors; using Microsoft.Extensions.Configuration; @@ -16,7 +17,7 @@ using Serilog; namespace hass_workstation_service.Data { - public class ConfigurationService : ServiceContractInterfaces, IConfigurationService + public class ConfigurationService : IConfigurationService { public ICollection ConfiguredSensors { get; private set; } public Action MqqtConfigChangedHandler { get; set; } @@ -32,14 +33,16 @@ namespace hass_workstation_service.Data public async void ReadSensorSettings(MqttPublisher publisher) { - IsolatedStorageFileStream stream = this._fileStorage.OpenFile("configured-sensors.json", FileMode.OpenOrCreate); - Log.Logger.Information($"reading configured sensors from: {stream.Name}"); List sensors = new List(); - if (stream.Length > 0) + using (var stream = this._fileStorage.OpenFile("configured-sensors.json", FileMode.OpenOrCreate)) { - sensors = await JsonSerializer.DeserializeAsync>(stream); + Log.Logger.Information($"reading configured sensors from: {stream.Name}"); + if (stream.Length > 0) + { + sensors = await JsonSerializer.DeserializeAsync>(stream); + } } - stream.Close(); + foreach (ConfiguredSensor configuredSensor in sensors) { AbstractSensor sensor; @@ -58,16 +61,9 @@ namespace hass_workstation_service.Data } } - public async Task ReadMqttSettings() + public async Task GetMqttClientOptionsAsync() { - IsolatedStorageFileStream stream = this._fileStorage.OpenFile("mqttbroker.json", FileMode.OpenOrCreate); - Log.Logger.Information($"reading configured mqttbroker from: {stream.Name}"); - ConfiguredMqttBroker configuredBroker = null; - if (stream.Length > 0) - { - configuredBroker = await JsonSerializer.DeserializeAsync(stream); - } - stream.Close(); + ConfiguredMqttBroker configuredBroker = await ReadMqttSettingsAsync(); if (configuredBroker != null && configuredBroker.Host != null) { @@ -89,64 +85,84 @@ namespace hass_workstation_service.Data } } - public async void WriteSettings() + /// + /// Gets the saved broker settings from configfile. Null if not found. + /// + /// + public async Task ReadMqttSettingsAsync() { - IsolatedStorageFileStream stream = this._fileStorage.OpenFile("configured-sensors.json", FileMode.OpenOrCreate); - Log.Logger.Information($"writing configured sensors to: {stream.Name}"); - List configuredSensorsToSave = new List(); - - foreach (AbstractSensor sensor in this.ConfiguredSensors) + ConfiguredMqttBroker configuredBroker = null; + using (IsolatedStorageFileStream stream = this._fileStorage.OpenFile("mqttbroker.json", FileMode.OpenOrCreate)) { - configuredSensorsToSave.Add(new ConfiguredSensor() { Id = sensor.Id, Name = sensor.Name, Type = sensor.GetType().Name }); + Log.Logger.Information($"reading configured mqttbroker from: {stream.Name}"); + if (stream.Length > 0) + { + configuredBroker = await JsonSerializer.DeserializeAsync(stream); + } } - await JsonSerializer.SerializeAsync(stream, configuredSensorsToSave); - stream.Close(); + return configuredBroker; + } + + public async void WriteSettingsAsync() + { + List configuredSensorsToSave = new List(); + using (IsolatedStorageFileStream stream = this._fileStorage.OpenFile("configured-sensors.json", FileMode.OpenOrCreate)) + { + Log.Logger.Information($"writing configured sensors to: {stream.Name}"); + foreach (AbstractSensor sensor in this.ConfiguredSensors) + { + configuredSensorsToSave.Add(new ConfiguredSensor() { Id = sensor.Id, Name = sensor.Name, Type = sensor.GetType().Name }); + } + + await JsonSerializer.SerializeAsync(stream, configuredSensorsToSave); + } } public void AddConfiguredSensor(AbstractSensor sensor) { this.ConfiguredSensors.Add(sensor); - WriteSettings(); + WriteSettingsAsync(); } public void AddConfiguredSensors(List sensors) { sensors.ForEach((sensor) => this.ConfiguredSensors.Add(sensor)); - WriteSettings(); + WriteSettingsAsync(); } - public async void WriteMqttBrokerSettings(string host, string username, string password) + /// + /// Writes provided settings to the config file and invokes a reconfigure to the current mqqtClient + /// + /// + public async void WriteMqttBrokerSettingsAsync(MqttSettings settings) { - IsolatedStorageFileStream stream = this._fileStorage.OpenFile("mqttbroker.json", FileMode.OpenOrCreate); - Log.Logger.Information($"writing configured mqttbroker to: {stream.Name}"); - ConfiguredMqttBroker configuredBroker = new ConfiguredMqttBroker() + using (IsolatedStorageFileStream stream = this._fileStorage.OpenFile("mqttbroker.json", FileMode.OpenOrCreate)) { - Host = host, - Username = username, - Password = password - }; + Log.Logger.Information($"writing configured mqttbroker to: {stream.Name}"); - await JsonSerializer.SerializeAsync(stream, configuredBroker); - stream.Close(); + ConfiguredMqttBroker configuredBroker = new ConfiguredMqttBroker() + { + Host = settings.Host, + Username = settings.Username, + Password = settings.Password + }; - this.MqqtConfigChangedHandler.Invoke(await this.ReadMqttSettings()); - } + await JsonSerializer.SerializeAsync(stream, configuredBroker); + } - + this.MqqtConfigChangedHandler.Invoke(await this.GetMqttClientOptionsAsync()); + } - /// - /// You can use this to check if the application responds. - /// - /// - /// - public string Ping(string str) + public async Task GetMqttBrokerSettings() { - if (str == "ping") + ConfiguredMqttBroker broker = await ReadMqttSettingsAsync(); + return new MqttSettings { - return "pong"; - } - return "what?"; + Host = broker?.Host, + Username = broker?.Username, + Password = broker?.Password + }; } } } \ No newline at end of file diff --git a/hass-workstation-service/Data/IConfigurationService.cs b/hass-workstation-service/Data/IConfigurationService.cs index 7dac53c..7bc98a1 100644 --- a/hass-workstation-service/Data/IConfigurationService.cs +++ b/hass-workstation-service/Data/IConfigurationService.cs @@ -1,4 +1,5 @@ using hass_workstation_service.Communication; +using hass_workstation_service.Communication.InterProcesCommunication.Models; using hass_workstation_service.Domain.Sensors; using MQTTnet.Client.Options; using System; @@ -15,10 +16,10 @@ namespace hass_workstation_service.Data void AddConfiguredSensor(AbstractSensor sensor); void AddConfiguredSensors(List sensors); - string Ping(string str); - Task ReadMqttSettings(); + Task GetMqttClientOptionsAsync(); void ReadSensorSettings(MqttPublisher publisher); - void WriteMqttBrokerSettings(string host, string username, string password); - void WriteSettings(); + void WriteMqttBrokerSettingsAsync(MqttSettings settings); + void WriteSettingsAsync(); + Task GetMqttBrokerSettings(); } } \ No newline at end of file diff --git a/hass-workstation-service/Program.cs b/hass-workstation-service/Program.cs index b5223bc..0d11ccd 100644 --- a/hass-workstation-service/Program.cs +++ b/hass-workstation-service/Program.cs @@ -18,6 +18,7 @@ using System.IO; using Microsoft.Win32; using JKang.IpcServiceFramework.Hosting; using hass_workstation_service.Communication.NamedPipe; +using hass_workstation_service.Communication.InterProcesCommunication; namespace hass_workstation_service { @@ -88,9 +89,8 @@ namespace hass_workstation_service Sw_version = GetVersion() }; services.AddSingleton(deviceConfig); - ConfigurationService configurationService = new ConfigurationService(); - services.AddSingleton(configurationService); - services.AddSingleton(configurationService); + services.AddSingleton(); + services.AddSingleton(); services.AddSingleton(); services.AddHostedService(); }).ConfigureIpcHost(builder =>