diff --git a/ADIS_Csharp/RobotClientWpf/Assets/cog.png b/ADIS_Csharp/RobotClientWpf/Assets/cog.png new file mode 100644 index 0000000..a39a424 Binary files /dev/null and b/ADIS_Csharp/RobotClientWpf/Assets/cog.png differ diff --git a/ADIS_Csharp/RobotClientWpf/Assets/robot-industrial.png b/ADIS_Csharp/RobotClientWpf/Assets/robot-industrial.png new file mode 100644 index 0000000..1babb14 Binary files /dev/null and b/ADIS_Csharp/RobotClientWpf/Assets/robot-industrial.png differ diff --git a/ADIS_Csharp/RobotClientWpf/ChallengeFactory.cs b/ADIS_Csharp/RobotClientWpf/ChallengeFactory.cs new file mode 100644 index 0000000..59f3366 --- /dev/null +++ b/ADIS_Csharp/RobotClientWpf/ChallengeFactory.cs @@ -0,0 +1,19 @@ +using RobotLib; +using RobotLib.Communication; + +namespace RobotClientWpf +{ + internal class ChallengeFactory + { + public MqttPublisherSubscriber PublisherSubscriber { get; } + public Robot RobotStationary { get; } + public Robot RobotMobile { get; } + + public ChallengeFactory() + { + this.PublisherSubscriber = MqttPublisherSubscriber.Instance; + //this.RobotStationary = new Robot(this.PublisherSubscriber); + //this.RobotMobile = new Robot(this.PublisherSubscriber); + } + } +} diff --git a/ADIS_Csharp/RobotClientWpf/IUiService.cs b/ADIS_Csharp/RobotClientWpf/IUiService.cs new file mode 100644 index 0000000..fb46398 --- /dev/null +++ b/ADIS_Csharp/RobotClientWpf/IUiService.cs @@ -0,0 +1,15 @@ +namespace RobotClientWpf +{ + public interface IUiService + { + void DisplayBottomMessage(MessageSeverity severity, string message); + } + public enum MessageSeverity + { + Unknown = 0, + Success, + Warning, + Error, + Information + } +} diff --git a/ADIS_Csharp/RobotClientWpf/MainWindow.xaml b/ADIS_Csharp/RobotClientWpf/MainWindow.xaml index 069ff63..e2a3025 100644 --- a/ADIS_Csharp/RobotClientWpf/MainWindow.xaml +++ b/ADIS_Csharp/RobotClientWpf/MainWindow.xaml @@ -4,11 +4,50 @@ xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:local="clr-namespace:RobotClientWpf" - mc:Ignorable="d" - Title="RobotClientWpf" Height="450" Width="800"> + xmlns:views="clr-namespace:RobotClientWpf.Views" + mc:Ignorable="d" FontSize="15" + Title="Challenge UI" Height="700" Width="1200" WindowStartupLocation="CenterScreen" ResizeMode="NoResize"> - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ADIS_Csharp/RobotClientWpf/MainWindow.xaml.cs b/ADIS_Csharp/RobotClientWpf/MainWindow.xaml.cs index 4bdc636..231c149 100644 --- a/ADIS_Csharp/RobotClientWpf/MainWindow.xaml.cs +++ b/ADIS_Csharp/RobotClientWpf/MainWindow.xaml.cs @@ -1,17 +1,9 @@ using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; +using System.Net; using System.Threading.Tasks; using System.Windows; -using System.Windows.Controls; -using System.Windows.Data; -using System.Windows.Documents; -using System.Windows.Input; using System.Windows.Media; -using System.Windows.Media.Imaging; -using System.Windows.Navigation; -using System.Windows.Shapes; +using RobotClientWpf.Utilities; using RobotLib; namespace RobotClientWpf @@ -21,11 +13,24 @@ namespace RobotClientWpf /// public partial class MainWindow : Window { + private ChallengeFactory challenge; + private bool manualDisconnect; + public MainWindow() { InitializeComponent(); - Robot.Instance.Connect("host", 1818); - Robot.Instance.Battery.BatteryChanged += BatteryChanged; + this.challenge = new ChallengeFactory(); + this.challenge.PublisherSubscriber.ConnectionStateChanged += PublisherSubscriber_ConnectionStateChanged; + } + + private void PublisherSubscriber_ConnectionStateChanged(object? sender, bool e) + { + if(e == false) + { + if (manualDisconnect == true) manualDisconnect = false; return; + this.DisplayBottomMessage(MessageSeverity.Error, "Connection to MQTT broker lost."); + this.SetIpFieldsState(true, true, "Connect"); + } } private void BatteryChanged(object? sender, BatteryEventArgs e) @@ -37,13 +42,90 @@ namespace RobotClientWpf } else { - labelBattery.Content = $"Battery: {e.Voltage} V"; + //labelBattery.Content = $"Battery: {e.Voltage} V"; } } private void buttonBuzz_Click(object sender, RoutedEventArgs e) { - Robot.Instance.Buzzer.Beep(300, 500); + //Robot.Instance.Buzzer.Beep(300, 500); + } + + private void btnConnectDisconnect_Click(object sender, RoutedEventArgs e) + { + Task.Run(delegate () + { + this.ClearBottomMessage(); + if (this.challenge.PublisherSubscriber.IsConnected) // is connected => disconnect + { + manualDisconnect = true; // prevent wrong message + this.challenge.PublisherSubscriber.Disconnect(); + this.DisplayBottomMessage(MessageSeverity.Success, "Disconnected from MQTT broker."); + this.SetIpFieldsState(true, true, "Connect"); + } + else // is not yet connected => connect + { + this.SetIpFieldsState(false, false, "Connecting..."); + string ipAddress = UIAccessHelpers.GetTextboxText(tbIp); + if (IPAddress.TryParse(ipAddress, out _)) + { + var connectionSuccess = this.challenge.PublisherSubscriber.Connect(ipAddress); + if (connectionSuccess) + { + this.DisplayBottomMessage(MessageSeverity.Success, "Successfully connected to MQTT broker."); + this.SetIpFieldsState(false, true, "Disconnect"); + } + else + { + this.DisplayBottomMessage(MessageSeverity.Error, $"Failed to connect to MQTT broker on address {ipAddress}."); + this.SetIpFieldsState(true, true, "Connect"); + } + } + else + { + this.DisplayBottomMessage(MessageSeverity.Error, $"Unable to parse MQTT Broker IP address {ipAddress}."); + this.SetIpFieldsState(true, true, "Connect"); + } + } + }); + } + + private void SetIpFieldsState(bool ipTextbox, bool connectDisconnectButton, string buttonText) + { + UIAccessHelpers.SetButtonState(btnConnectDisconnect, connectDisconnectButton); + UIAccessHelpers.SetTextboxState(tbIp, ipTextbox); + UIAccessHelpers.SetButtonContent(btnConnectDisconnect, buttonText); + } + + public void DisplayBottomMessage(MessageSeverity severity, string message) + { + Brush col; + switch (severity) + { + case MessageSeverity.Success: + col = Brushes.Green; + break; + case MessageSeverity.Warning: + col = Brushes.Orange; + break; + case MessageSeverity.Error: + col = Brushes.Red; + break; + case MessageSeverity.Information: + col = Brushes.Blue; + break; + case MessageSeverity.Unknown: + default: + col = Brushes.DarkGray; + break; + } + + UIAccessHelpers.SetTextblockTextAndForegroundColor(tbBottomMessage, message, col); + } + + private void ClearBottomMessage() + { + this.DisplayBottomMessage(MessageSeverity.Information, ""); } } } diff --git a/ADIS_Csharp/RobotClientWpf/NLog.config b/ADIS_Csharp/RobotClientWpf/NLog.config new file mode 100644 index 0000000..7da0939 --- /dev/null +++ b/ADIS_Csharp/RobotClientWpf/NLog.config @@ -0,0 +1,27 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/ADIS_Csharp/RobotClientWpf/RobotClientWpf.csproj b/ADIS_Csharp/RobotClientWpf/RobotClientWpf.csproj index 08b8f5f..fed4496 100644 --- a/ADIS_Csharp/RobotClientWpf/RobotClientWpf.csproj +++ b/ADIS_Csharp/RobotClientWpf/RobotClientWpf.csproj @@ -7,8 +7,22 @@ true + + + + + + + + Always + + + Always + + + diff --git a/ADIS_Csharp/RobotClientWpf/Utilities/UIAccessHelpers.cs b/ADIS_Csharp/RobotClientWpf/Utilities/UIAccessHelpers.cs new file mode 100644 index 0000000..b43aa37 --- /dev/null +++ b/ADIS_Csharp/RobotClientWpf/Utilities/UIAccessHelpers.cs @@ -0,0 +1,103 @@ +using System; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Controls.Primitives; +using System.Windows.Media; + +namespace RobotClientWpf.Utilities +{ + public static class UIAccessHelpers + { + public static void SetButtonState(ButtonBase button, bool enabled) + { + if (!Application.Current.Dispatcher.CheckAccess()) + { + Application.Current.Dispatcher.BeginInvoke(new Action(() => + { + SetButtonState(button, enabled); + })); + } + else + { + button.IsEnabled = enabled; + } + } + public static void SetButtonContent(ButtonBase button, string content) + { + if (!Application.Current.Dispatcher.CheckAccess()) + { + Application.Current.Dispatcher.BeginInvoke(new Action(() => + { + SetButtonContent(button, content); + })); + } + else + { + button.Content = content; + } + } + + public static string GetTextboxText(TextBox textBox) + { + string text = String.Empty; + if (!Application.Current.Dispatcher.CheckAccess()) + { + Application.Current.Dispatcher.BeginInvoke(new Action(() => + { + text = GetTextboxText(textBox); + })).Wait(); + } + else + { + text = textBox.Text; + } + return text; + } + + public static void SetTextboxState(TextBoxBase textBox, bool enabled) + { + if (!Application.Current.Dispatcher.CheckAccess()) + { + Application.Current.Dispatcher.BeginInvoke(new Action(() => + { + SetTextboxState(textBox, enabled); + })); + } + else + { + textBox.IsEnabled = enabled; + } + } + + public static void SetTextboxText(TextBox textBox, string text) + { + if (!Application.Current.Dispatcher.CheckAccess()) + { + Application.Current.Dispatcher.BeginInvoke(new Action(() => + { + SetTextboxText(textBox, text); + })); + } + else + { + textBox.Text = text; + } + } + + public static void SetTextblockTextAndForegroundColor(TextBlock textBlock, string text, Brush foregroundColor) + { + if (!Application.Current.Dispatcher.CheckAccess()) + { + Application.Current.Dispatcher.BeginInvoke(new Action(() => + { + SetTextblockTextAndForegroundColor(textBlock, text, foregroundColor); + })); + } + else + { + textBlock.Text = text; + textBlock.Foreground = foregroundColor; + } + } + } +} diff --git a/ADIS_Csharp/RobotClientWpf/Views/ConfigView.xaml b/ADIS_Csharp/RobotClientWpf/Views/ConfigView.xaml new file mode 100644 index 0000000..97a0623 --- /dev/null +++ b/ADIS_Csharp/RobotClientWpf/Views/ConfigView.xaml @@ -0,0 +1,13 @@ + + + + diff --git a/ADIS_Csharp/RobotClientWpf/Views/ConfigView.xaml.cs b/ADIS_Csharp/RobotClientWpf/Views/ConfigView.xaml.cs new file mode 100644 index 0000000..f7dfbd6 --- /dev/null +++ b/ADIS_Csharp/RobotClientWpf/Views/ConfigView.xaml.cs @@ -0,0 +1,28 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Data; +using System.Windows.Documents; +using System.Windows.Input; +using System.Windows.Media; +using System.Windows.Media.Imaging; +using System.Windows.Navigation; +using System.Windows.Shapes; + +namespace RobotClientWpf.Views +{ + /// + /// Interaction logic for ConfigView.xaml + /// + public partial class ConfigView : UserControl + { + public ConfigView() + { + InitializeComponent(); + } + } +} diff --git a/ADIS_Csharp/RobotClientWpf/Views/MainView.xaml b/ADIS_Csharp/RobotClientWpf/Views/MainView.xaml new file mode 100644 index 0000000..37b735b --- /dev/null +++ b/ADIS_Csharp/RobotClientWpf/Views/MainView.xaml @@ -0,0 +1,13 @@ + + + + diff --git a/ADIS_Csharp/RobotClientWpf/Views/MainView.xaml.cs b/ADIS_Csharp/RobotClientWpf/Views/MainView.xaml.cs new file mode 100644 index 0000000..8e7df43 --- /dev/null +++ b/ADIS_Csharp/RobotClientWpf/Views/MainView.xaml.cs @@ -0,0 +1,28 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Data; +using System.Windows.Documents; +using System.Windows.Input; +using System.Windows.Media; +using System.Windows.Media.Imaging; +using System.Windows.Navigation; +using System.Windows.Shapes; + +namespace RobotClientWpf.Views +{ + /// + /// Interaction logic for MainView.xaml + /// + public partial class MainView : UserControl + { + public MainView() + { + InitializeComponent(); + } + } +} diff --git a/ADIS_Csharp/RobotLib/Communication/MqttPublisherSubscriber.cs b/ADIS_Csharp/RobotLib/Communication/MqttPublisherSubscriber.cs index 9c66fde..be31f5c 100644 --- a/ADIS_Csharp/RobotLib/Communication/MqttPublisherSubscriber.cs +++ b/ADIS_Csharp/RobotLib/Communication/MqttPublisherSubscriber.cs @@ -21,10 +21,28 @@ namespace RobotLib.Communication /// public event EventHandler NewMessageArrived; + /// + /// Event to catch for the connection state to the broker. + /// + public event EventHandler ConnectionStateChanged; + /// /// Connection state to the broker. /// - public bool IsConnected { get ; private set; } + public bool IsConnected + { + get { return _isConnected; } + private set + { + // generate event if the value has changed + if(_isConnected != value) + { + this.ConnectionStateChanged?.Invoke(this, value); + } + _isConnected = value; + } + } + private bool _isConnected; private MqttPublisherSubscriber() { } @@ -63,14 +81,22 @@ namespace RobotLib.Communication client.ConnectionClosed += Client_ConnectionClosed; // generate a clientID and connect to Broker string clientId = Guid.NewGuid().ToString(); - if (String.IsNullOrEmpty(username) || String.IsNullOrEmpty(password)) + try { - client.Connect(clientId); + if (String.IsNullOrEmpty(username) || String.IsNullOrEmpty(password)) + { + client.Connect(clientId); + } + else + { + client.Connect(clientId, username, password); + } } - else + catch (Exception ex) { - client.Connect(clientId, username, password); + log.Error(ex.Message); } + success = client.IsConnected; log.Info($"Connecting done. Success = {success}"); } @@ -83,7 +109,12 @@ namespace RobotLib.Communication { if (IsConnected == false) return; - client.Disconnect(); + try + { + client.Disconnect(); + } + catch { } + log.Info($"Disconnect was called."); IsConnected = false; }