diff --git a/App.config b/App.config index 3d87d88..b18ad13 100644 --- a/App.config +++ b/App.config @@ -1,12 +1,15 @@ - + - -
+ +
+ + +
- + @@ -16,9 +19,25 @@ - - http://chip - - \ No newline at end of file + + + + 00:00:30 + + + 00:01:00 + + + http://{0}:{1}/api/hub/device-status + + + 172.23.10.3 + + + 80 + + + + diff --git a/HomeStatusWindow.csproj b/HomeStatusWindow.csproj index 7b6dfb4..e31ab29 100644 --- a/HomeStatusWindow.csproj +++ b/HomeStatusWindow.csproj @@ -9,11 +9,12 @@ Properties HomeStatusWindow HomeStatusWindow - v4.6 + v4.8 512 {60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} 4 true + AnyCPU @@ -53,6 +54,7 @@ Designer + App.xaml @@ -98,8 +100,11 @@ 1.0.0.9 - - 1.0.5 + + 6.0.8 + + + 13.0.1 diff --git a/Properties/Resources.Designer.cs b/Properties/Resources.Designer.cs index 8e2b58e..571bbac 100644 --- a/Properties/Resources.Designer.cs +++ b/Properties/Resources.Designer.cs @@ -19,7 +19,7 @@ namespace HomeStatusWindow.Properties { // class via a tool like ResGen or Visual Studio. // To add or remove a member, edit your .ResX file then rerun ResGen // with the /str option, or rebuild your VS project. - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")] [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] internal class Resources { diff --git a/Properties/Settings.Designer.cs b/Properties/Settings.Designer.cs index eaf1078..5dd43a4 100644 --- a/Properties/Settings.Designer.cs +++ b/Properties/Settings.Designer.cs @@ -12,7 +12,7 @@ namespace HomeStatusWindow.Properties { [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "14.0.0.0")] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "17.3.0.0")] internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase { private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); @@ -47,15 +47,48 @@ namespace HomeStatusWindow.Properties { } } - [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Configuration.ApplicationScopedSettingAttribute()] [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] - [global::System.Configuration.DefaultSettingValueAttribute("http://chip")] - public string ServerAddress { + [global::System.Configuration.DefaultSettingValueAttribute("00:00:30")] + public global::System.TimeSpan ReconnectTimerInterval { get { - return ((string)(this["ServerAddress"])); + return ((global::System.TimeSpan)(this["ReconnectTimerInterval"])); } - set { - this["ServerAddress"] = value; + } + + [global::System.Configuration.ApplicationScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("00:01:00")] + public global::System.TimeSpan ReconnectTimeout { + get { + return ((global::System.TimeSpan)(this["ReconnectTimeout"])); + } + } + + [global::System.Configuration.ApplicationScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("http://{0}:{1}/api/hub/device-status")] + public string ServerUri { + get { + return ((string)(this["ServerUri"])); + } + } + + [global::System.Configuration.ApplicationScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("172.23.10.3")] + public string ServerName { + get { + return ((string)(this["ServerName"])); + } + } + + [global::System.Configuration.ApplicationScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("80")] + public int ServerPort { + get { + return ((int)(this["ServerPort"])); } } } diff --git a/Properties/Settings.settings b/Properties/Settings.settings index c0c83cc..b0ee502 100644 --- a/Properties/Settings.settings +++ b/Properties/Settings.settings @@ -8,8 +8,20 @@ - - http://chip + + 00:00:30 + + + 00:01:00 + + + http://{0}:{1}/api/hub/device-status + + + 172.23.10.3 + + + 80 \ No newline at end of file diff --git a/Status.cs b/Status.cs index 6d4999a..35699dc 100644 --- a/Status.cs +++ b/Status.cs @@ -4,9 +4,9 @@ public class Status { // ReSharper disable once UnusedAutoPropertyAccessor.Global - public bool? Washer { get; set; } + public bool Washer { get; set; } // ReSharper disable once UnusedAutoPropertyAccessor.Global - public bool? Dryer { get; set; } + public bool Dryer { get; set; } } } diff --git a/StatusMessage.cs b/StatusMessage.cs new file mode 100644 index 0000000..a0e8f25 --- /dev/null +++ b/StatusMessage.cs @@ -0,0 +1,8 @@ +namespace HomeStatusWindow +{ + public class StatusMessage + { + public string Name { get; set; } + public bool Status { get; set; } + } +} diff --git a/WindowSource.cs b/WindowSource.cs index 16e4a41..5dac9fe 100644 --- a/WindowSource.cs +++ b/WindowSource.cs @@ -1,10 +1,11 @@ using FloatingStatusWindowLibrary; using HomeStatusWindow.Properties; +using Microsoft.AspNetCore.SignalR.Client; using Newtonsoft.Json; -using Quobject.SocketIoClientDotNet.Client; using System; using System.Text; using System.Threading.Tasks; +using System.Timers; using System.Windows.Threading; namespace HomeStatusWindow @@ -14,21 +15,30 @@ namespace HomeStatusWindow private readonly FloatingStatusWindow _floatingStatusWindow; private readonly Dispatcher _dispatcher; - private Socket _socket; + private HubConnection _hubConnection; + private DateTime _lastUpdate; + private Timer _reconnectTimer; + + private readonly Status _lastStatus = new Status(); internal WindowSource() { _dispatcher = Dispatcher.CurrentDispatcher; _floatingStatusWindow = new FloatingStatusWindow(this); - _floatingStatusWindow.SetText(Resources.Loading); + UpdateText(Resources.Loading); - Task.Factory.StartNew(Initialize); + // Initialize the connection + Task.Factory.StartNew(InitializeConnection); } - public void Dispose() + public async void Dispose() { - Terminate(); + // Stop the reconnection timer (if any) + StopReconnectionTimer(); + + // Terminate the connection + await TerminateConnection(); _floatingStatusWindow.Save(); _floatingStatusWindow.Dispose(); @@ -48,12 +58,14 @@ namespace HomeStatusWindow public string Name => Resources.Name; - public bool HasSettingsMenu => false; - public bool HasRefreshMenu => false; - public bool HasAboutMenu => false; - public System.Drawing.Icon Icon => Resources.ApplicationIcon; + public bool HasSettingsMenu => false; + + public bool HasRefreshMenu => false; + + public bool HasAboutMenu => false; + public string WindowSettings { get => Settings.Default.WindowSettings; @@ -64,64 +76,121 @@ namespace HomeStatusWindow } } - private readonly Status _fullStatus = new Status(); - - private void Initialize() + private void StartReconnectionTimer() { - // Create the socket - _socket = IO.Socket(Settings.Default.ServerAddress); + // Stop the current timer (if any) + StopReconnectionTimer(); - // Setup for status events - _socket.On("status", UpdateText); - - _socket.On(Socket.EVENT_CONNECT, () => _socket.Emit("getStatus")); - _socket.On(Socket.EVENT_DISCONNECT, () => SetText(Resources.Disconnected)); + // Create and start the reconnection timer + _reconnectTimer = new Timer(Settings.Default.ReconnectTimerInterval.TotalMilliseconds); + _reconnectTimer.Elapsed += HandleReconnectTimerElapsed; + _reconnectTimer.Start(); } - private void Terminate() + private void StopReconnectionTimer() { - _socket?.Disconnect(); + // Get rid of the reconnection timer + _reconnectTimer?.Dispose(); + _reconnectTimer = null; } - private void UpdateText(object data) + private void HandleReconnectTimerElapsed(object sender, ElapsedEventArgs e) { - var json = (string)data; - - var status = JsonConvert.DeserializeObject(json); - - if (status.Dryer.HasValue) - _fullStatus.Dryer = status.Dryer; - - if (status.Washer.HasValue) - _fullStatus.Washer = status.Washer; - - var text = GetText(_fullStatus); - - SetText(text); + // See if we haven't heard from the server within the timeout and reconnect if needed + if (DateTime.Now - _lastUpdate >= Settings.Default.ReconnectTimeout) + InitializeConnection(); } - private void SetText(string text) - { - // Update the window on the main thread - _dispatcher.Invoke(() => _floatingStatusWindow.SetText(text)); - } - - private static string GetText(Status status) + private void InitializeConnection() { try { - var output = new StringBuilder(); + // Stop the reconnection timer (if any) + StopReconnectionTimer(); - output.AppendFormat(Resources.DryerStatus, status.Dryer.GetValueOrDefault() ? Resources.On : Resources.Off); - output.AppendLine(); - output.AppendFormat(Resources.WasherStatus, status.Washer.GetValueOrDefault() ? Resources.On : Resources.Off); + // Create the URI for the server + var serverUri = string.Format(Settings.Default.ServerUri, Settings.Default.ServerName, Settings.Default.ServerPort); - return output.ToString(); + _hubConnection = new HubConnectionBuilder() + .WithUrl(serverUri) + .Build(); + + _hubConnection.Closed += error => + { + StartReconnectionTimer(); + + return Task.CompletedTask; + }; + + _hubConnection.On("LatestStatus", (message) => + { + try + { + var statusMessage = JsonConvert.DeserializeObject(message); + + switch (statusMessage?.Name) + { + case "washer": + _lastStatus.Washer = statusMessage.Status; + break; + case "dryer": + _lastStatus.Dryer = statusMessage.Status; + break; + } + + UpdateDisplay(_lastStatus); + } + catch (Exception e) + { + Console.WriteLine(e); + } + }); + + // Open the connection + _hubConnection.StartAsync().Wait(); + + _hubConnection.InvokeAsync("RequestLatestStatus").Wait(); } - catch (Exception ex) + catch (Exception exception) { - return ex.Message; + UpdateText($"Connection error: {exception.Message}"); + } + finally + { + // Start the reconnection check timer + StartReconnectionTimer(); } } + + private async Task TerminateConnection() + { + // If the client doesn't exist or isn't open then there's nothing to do + if (_hubConnection == null || _hubConnection.State != HubConnectionState.Connected) + return; + + // Close the connection + await _hubConnection.DisposeAsync(); + } + + private void UpdateDisplay(Status status) + { + // Last update was now + _lastUpdate = DateTime.Now; + + // Create a string builder + var text = new StringBuilder(); + + text.AppendFormat(Resources.DryerStatus, status.Dryer ? Resources.On : Resources.Off); + text.AppendLine(); + text.AppendFormat(Resources.WasherStatus, status.Washer ? Resources.On : Resources.Off); + + // Set the text + UpdateText(text.ToString()); + } + + private void UpdateText(string text) + { + _dispatcher.InvokeAsync(() => _floatingStatusWindow.SetText(text)); + } } -} +} \ No newline at end of file