From ad6adf65afd20c52456893362a11fe47cac6a1a5 Mon Sep 17 00:00:00 2001 From: FrankenApps Date: Tue, 21 Jul 2020 21:30:01 +0200 Subject: [PATCH] First version. --- .gitignore | 36 + App.axaml | 13 + App.axaml.cs | 26 + Assets/Images/logo.afdesign | Bin 0 -> 24974 bytes Assets/Images/logo.ico | Bin 0 -> 9683 bytes Assets/Images/logo.png | Bin 0 -> 19112 bytes Models/Client.cs | 17 + Models/MqttMessageOptions.cs | 15 + Models/MqttUser.cs | 18 + Models/Server.cs | 14 + MqttDebugger.csproj | 16 + Program.cs | 23 + .../PublishProfiles/FolderProfile.pubxml | 17 + ViewLocator.cs | 32 + ViewModels/MainWindowViewModel.cs | 663 ++++++++++++++++++ ViewModels/ViewModelBase.cs | 11 + Views/MainWindow.axaml | 230 ++++++ Views/MainWindow.axaml.cs | 106 +++ .../BooleanToKeyboardShortcutConverter.cs | 27 + logo.ico | Bin 0 -> 9683 bytes nuget.config | 11 + 21 files changed, 1275 insertions(+) create mode 100644 .gitignore create mode 100644 App.axaml create mode 100644 App.axaml.cs create mode 100644 Assets/Images/logo.afdesign create mode 100644 Assets/Images/logo.ico create mode 100644 Assets/Images/logo.png create mode 100644 Models/Client.cs create mode 100644 Models/MqttMessageOptions.cs create mode 100644 Models/MqttUser.cs create mode 100644 Models/Server.cs create mode 100644 MqttDebugger.csproj create mode 100644 Program.cs create mode 100644 Properties/PublishProfiles/FolderProfile.pubxml create mode 100644 ViewLocator.cs create mode 100644 ViewModels/MainWindowViewModel.cs create mode 100644 ViewModels/ViewModelBase.cs create mode 100644 Views/MainWindow.axaml create mode 100644 Views/MainWindow.axaml.cs create mode 100644 Views/ValueConverters/BooleanToKeyboardShortcutConverter.cs create mode 100644 logo.ico create mode 100644 nuget.config diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a468e7e --- /dev/null +++ b/.gitignore @@ -0,0 +1,36 @@ +# Exclude Visual Studio solution and project specific user options. +*.suo +*.user +*.userosscache +*.sln.docstates +.vs/ +*.opendb +*.VC.db +*.VC.VC.opendb +*.sln + +# Exclude StyleCop cache files. +[Ss]tyle[Cc]op.[Cc]ache + +# Exclude build result files. +[Bb]inary/ +[Bb]in/ +[Oo]bj/ +[Dd]ebug/ +[Rr]elease/ +[Tt]estResults/ + +# Exclude NuGet specific folders and files. +*.nupkg +**/[Pp]ackages/* +!**/[Pp]ackages/[Ww]itron* +!**/[Pp]ackages/[Bb]uild/ +!**/[Pp]ackages/[Rr]epositories.config + +# Exclude common temporary/log files. +*~ +*.~* +*.log + +# Exclude backup files created by some editors. +*.bak \ No newline at end of file diff --git a/App.axaml b/App.axaml new file mode 100644 index 0000000..d086068 --- /dev/null +++ b/App.axaml @@ -0,0 +1,13 @@ + + + + + + + + + + diff --git a/App.axaml.cs b/App.axaml.cs new file mode 100644 index 0000000..4b15de1 --- /dev/null +++ b/App.axaml.cs @@ -0,0 +1,26 @@ +using Avalonia; +using Avalonia.Controls.ApplicationLifetimes; +using Avalonia.Markup.Xaml; +using MqttDebugger.ViewModels; +using MqttDebugger.Views; + +namespace MqttDebugger +{ + public class App : Application + { + public override void Initialize() + { + AvaloniaXamlLoader.Load(this); + } + + public override void OnFrameworkInitializationCompleted() + { + if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) + { + desktop.MainWindow = new MainWindow(); + } + + base.OnFrameworkInitializationCompleted(); + } + } +} diff --git a/Assets/Images/logo.afdesign b/Assets/Images/logo.afdesign new file mode 100644 index 0000000000000000000000000000000000000000..05e9fe4efc787c693f9901360ee72f5c99084562 GIT binary patch literal 24974 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%0D9di7+@YtS;pBgs%oKU~~ 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 literal 0 HcmV?d00001 diff --git a/Models/Client.cs b/Models/Client.cs new file mode 100644 index 0000000..c731fcc --- /dev/null +++ b/Models/Client.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; +using System.Security; +using System.Text; + +namespace MqttDebugger.Models +{ + public class Client + { + public bool IsConnected { get; set; } = false; + public string Host { get; set; } = "localhost"; + public int Port { get; set; } = 1883; + public MqttUser User { get; set; } + public string Topic { get; set; } = "myTopic"; + public bool ConnectToInternalServer { get; set; } = false; + } +} diff --git a/Models/MqttMessageOptions.cs b/Models/MqttMessageOptions.cs new file mode 100644 index 0000000..541a964 --- /dev/null +++ b/Models/MqttMessageOptions.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace MqttDebugger.Models +{ + public class MqttMessageOptions + { + public bool DisplayTopic { get; set; } = false; + public bool DisplayPayloadAsString { get; set; } = true; + public string FilterByTopic { get; set; } = "#"; + public bool WritePayloadToFile { get; set; } = false; + public string FolderOutPath { get; set; } + } +} diff --git a/Models/MqttUser.cs b/Models/MqttUser.cs new file mode 100644 index 0000000..971181a --- /dev/null +++ b/Models/MqttUser.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace MqttDebugger.Models +{ + public class MqttUser + { + public string Username { get; set; } + public string Password { get; set; } + + public MqttUser(string _username, string _password) + { + Username = _username; + Password = _password; + } + } +} diff --git a/Models/Server.cs b/Models/Server.cs new file mode 100644 index 0000000..63f5d5b --- /dev/null +++ b/Models/Server.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Security; +using System.Text; + +namespace MqttDebugger.Models +{ + public class Server + { + public bool IsRunning { get; set; } + public List Users { get; set; } + public int Port { get; set; } = 1883; + } +} diff --git a/MqttDebugger.csproj b/MqttDebugger.csproj new file mode 100644 index 0000000..568ecaf --- /dev/null +++ b/MqttDebugger.csproj @@ -0,0 +1,16 @@ + + + WinExe + netcoreapp3.1 + logo.ico + + + + + + + + + + + diff --git a/Program.cs b/Program.cs new file mode 100644 index 0000000..0a1479a --- /dev/null +++ b/Program.cs @@ -0,0 +1,23 @@ +using System; +using Avalonia; +using Avalonia.Controls.ApplicationLifetimes; +using Avalonia.ReactiveUI; + +namespace MqttDebugger +{ + class Program + { + // Initialization code. Don't use any Avalonia, third-party APIs or any + // SynchronizationContext-reliant code before AppMain is called: things aren't initialized + // yet and stuff might break. + public static void Main(string[] args) => BuildAvaloniaApp() + .StartWithClassicDesktopLifetime(args); + + // Avalonia configuration, don't remove; also used by visual designer. + public static AppBuilder BuildAvaloniaApp() + => AppBuilder.Configure() + .UsePlatformDetect() + .LogToDebug() + .UseReactiveUI(); + } +} diff --git a/Properties/PublishProfiles/FolderProfile.pubxml b/Properties/PublishProfiles/FolderProfile.pubxml new file mode 100644 index 0000000..0f3adc4 --- /dev/null +++ b/Properties/PublishProfiles/FolderProfile.pubxml @@ -0,0 +1,17 @@ + + + + + Release + Any CPU + bin\Release\netcoreapp3.1\publish\ + FileSystem + netcoreapp3.1 + win-x64 + false + True + True + + \ No newline at end of file diff --git a/ViewLocator.cs b/ViewLocator.cs new file mode 100644 index 0000000..c99f7fc --- /dev/null +++ b/ViewLocator.cs @@ -0,0 +1,32 @@ +using System; +using Avalonia.Controls; +using Avalonia.Controls.Templates; +using MqttDebugger.ViewModels; + +namespace MqttDebugger +{ + public class ViewLocator : IDataTemplate + { + public bool SupportsRecycling => false; + + public IControl Build(object data) + { + var name = data.GetType().FullName.Replace("ViewModel", "View"); + var type = Type.GetType(name); + + if (type != null) + { + return (Control)Activator.CreateInstance(type); + } + else + { + return new TextBlock { Text = "Not Found: " + name }; + } + } + + public bool Match(object data) + { + return data is ViewModelBase; + } + } +} \ No newline at end of file diff --git a/ViewModels/MainWindowViewModel.cs b/ViewModels/MainWindowViewModel.cs new file mode 100644 index 0000000..ebcb329 --- /dev/null +++ b/ViewModels/MainWindowViewModel.cs @@ -0,0 +1,663 @@ +using Avalonia.Controls.Notifications; +using Avalonia.Media; +using Avalonia.Platform; +using MqttDebugger.Models; +using MqttDebugger.Views; +using MQTTnet; +using MQTTnet.Client; +using MQTTnet.Client.Options; +using MQTTnet.Extensions; +using MQTTnet.Protocol; +using MQTTnet.Server; +using ReactiveUI; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Net; +using System.Net.Sockets; +using System.Reactive; +using System.Reactive.Linq; +using System.Security; +using System.Text; +using System.Threading; + +namespace MqttDebugger.ViewModels +{ + public class MainWindowViewModel : ViewModelBase + { + /// + /// Create a NotificationManager in order to display messages. + /// + public IManagedNotificationManager NotificationManager + { + get { return _notificationManager; } + set { this.RaiseAndSetIfChanged(ref _notificationManager, value); } + } + + /// + /// Let the user choose which key should trigger the send action. + /// + private bool sendMessageShortcut = true; + public bool SendMessageShortcut + { + get => sendMessageShortcut; + set => this.RaiseAndSetIfChanged(ref sendMessageShortcut, value); + } + + /// + /// Let the user select choose dark or light appearance. + /// + private bool darkMode = false; + public bool DarkMode + { + get => darkMode; + set => this.RaiseAndSetIfChanged(ref darkMode, value); + } + + /// + /// Indicates if the included MQTT-Server is running. + /// + private bool isServerRunning = false; + public bool IsServerRunning + { + get => isServerRunning; + set => this.RaiseAndSetIfChanged(ref isServerRunning, value); + } + + /// + /// Indicates if the application is connected to a broker (e.g. MQTT-Server). + /// + private bool isConnectedToServer = false; + public bool IsConnectedToServer + { + get => isConnectedToServer; + set + { + this.RaiseAndSetIfChanged(ref isConnectedToServer, value); + mqttClient.IsConnected = IsConnectedToServer; + } + } + + /// + /// The username of the included MQTT-Server. + /// + private string serverUsernames = string.Empty; + public string ServerUsernames + { + get => serverUsernames; + set => this.RaiseAndSetIfChanged(ref serverUsernames, value); + } + + /// + /// The password of the included MQTT-Server. + /// + private string serverPasswords = string.Empty; + public string ServerPasswords + { + get => serverPasswords; + set => this.RaiseAndSetIfChanged(ref serverPasswords, value); + } + + /// + /// The host, to which the client should connect. + /// + private string clientHostname = string.Empty; + public string ClientHostname + { + get => clientHostname; + set + { + this.RaiseAndSetIfChanged(ref clientHostname, value); + mqttClient.Host = clientHostname; + } + } + + /// + /// The username used to connect to the specified host. + /// + private string clientUsername = string.Empty; + public string ClientUsername + { + get => clientUsername; + set => this.RaiseAndSetIfChanged(ref clientUsername, value); + } + + /// + /// The password used to connect to the specified host. + /// + private string clientPassword; + public string ClientPassword + { + get => clientPassword; + set => this.RaiseAndSetIfChanged(ref clientPassword, value); + } + + /// + /// The topic used to send messages to. + /// + private string clientTopic = "myTopic"; + public string ClientTopic + { + get => clientTopic; + set + { + this.RaiseAndSetIfChanged(ref clientTopic, value); + mqttClient.Topic = ClientTopic; + } + } + + /// + /// Shows if the user wants to connect to the included server. + /// In that case hostname, username and password are not necessary. + /// + private bool connectToInternalServer = true; + public bool ConnectToInternalServer + { + get => connectToInternalServer; + set => this.RaiseAndSetIfChanged(ref connectToInternalServer, value); + } + + /// + /// The text to display the client connection status. + /// + private string clientStatusText = "Status: Not connected."; + public string ClientStatusText + { + get => clientStatusText; + set => this.RaiseAndSetIfChanged(ref clientStatusText, value); + } + + /// + /// The text to indicate which option the button will trigger. + /// + private string clientConnectionButtonText = "Connect"; + public string ClientConnectionButtonText + { + get => clientConnectionButtonText; + set => this.RaiseAndSetIfChanged(ref clientConnectionButtonText, value); + } + + /// + /// The status display text for the server area. + /// + private string serverStatusText = "Stopped."; + public string ServerStatusText + { + get => serverStatusText; + set => this.RaiseAndSetIfChanged(ref serverStatusText, value); + } + + /// + /// Adjust text color according to status. + /// + private IBrush serverStatusTextColor = Brushes.Red; + public IBrush ServerStatusTextColor + { + get => serverStatusTextColor; + set => this.RaiseAndSetIfChanged(ref serverStatusTextColor, value); + } + + /// + /// The message currently staged to be sent. + /// + private string mqttMessageText = string.Empty; + public string MqttMessageText + { + get => mqttMessageText; + set => this.RaiseAndSetIfChanged(ref mqttMessageText, value); + } + + /// + /// The received messages of the current topic. + /// + private string receivedMessages = string.Empty; + public string ReceivedMessages + { + get => receivedMessages; + set => this.RaiseAndSetIfChanged(ref receivedMessages, value); + } + + /// + /// The IP-Adress of the MQTT-Server in the local network. + /// + private string localIp = "127.0.0.1:1883"; + public string LocalIp + { + get => localIp; + set => this.RaiseAndSetIfChanged(ref localIp, value); + } + + /// + /// Shows if the user wants to connect to the included server. + /// In that case hostname, username and password are not necessary. + /// + private bool showTopicSelector = false; + public bool ShowTopicSelector + { + get => showTopicSelector; + set => this.RaiseAndSetIfChanged(ref showTopicSelector, value); + } + + /// + /// Wether the Topic should be shown in the log window. + /// + private bool messageOptionShowTopic = false; + public bool MessageOptionShowTopic + { + get => messageOptionShowTopic; + set + { + this.RaiseAndSetIfChanged(ref messageOptionShowTopic, value); + mqttMessageOptions.DisplayTopic = MessageOptionShowTopic; + } + } + + /// + /// The topic used to listen for messages. + /// Listen to all topics except topics that start with '$'. + /// + private string filterByTopic = "#"; + public string FilterByTopic + { + get => filterByTopic; + set + { + this.RaiseAndSetIfChanged(ref filterByTopic, value); + mqttMessageOptions.FilterByTopic = filterByTopic; + } + } + + /// + /// Wether the Payload should be logged to the output window as a UTF-8 endcoded string. + /// + private bool messageOptionDisplayPayloadAsString = true; + public bool MessageOptionDisplayPayloadAsString + { + get => messageOptionDisplayPayloadAsString; + set + { + this.RaiseAndSetIfChanged(ref messageOptionDisplayPayloadAsString, value); + mqttMessageOptions.DisplayPayloadAsString = MessageOptionDisplayPayloadAsString; + } + } + + /// + /// Wether the Payload should be written to a file. + /// + private bool messageOptionWritePayloadToFile = false; + public bool MessageOptionWritePayloadToFile + { + get => messageOptionWritePayloadToFile; + set + { + this.RaiseAndSetIfChanged(ref messageOptionWritePayloadToFile, value); + mqttMessageOptions.WritePayloadToFile = MessageOptionWritePayloadToFile; + } + } + + /// + /// The path to the folder where the payload will be saved if WritePayloadToFile is true. + /// + private string fileOutputFolder; + public string FileOutputFolder + { + get => fileOutputFolder; + set + { + this.RaiseAndSetIfChanged(ref fileOutputFolder, value); + mqttMessageOptions.FolderOutPath = fileOutputFolder; + } + } + + // Create reactive commands. + public ReactiveCommand StartServerCommand { get; } + public ReactiveCommand StopServerCommand { get; } + public ReactiveCommand RestartServerCommand { get; } + public ReactiveCommand ResetSettingsCommand { get; } + public ReactiveCommand ConnectToServerCommand { get; } + public ReactiveCommand SendMessageCommand { get; } + public ReactiveCommand ClearMessageLogCommand { get; } + + + // For notification handling. + private MainWindow _window; + private IManagedNotificationManager _notificationManager; + + // Create default instances of client and server + private Server mqttServer = new Server(); + private Client mqttClient = new Client(); + private MqttMessageOptions mqttMessageOptions = new MqttMessageOptions(); + + private IMqttServer server; + private IMqttClient client; + private Thread listenForMessagesThread; + + public MainWindowViewModel(MainWindow window, IManagedNotificationManager notificationManager) + { + // Initialize reactive commands. + StartServerCommand = ReactiveCommand.Create(StartServer); + StopServerCommand = ReactiveCommand.Create(StopServer); + RestartServerCommand = ReactiveCommand.Create(RestartServer); + ResetSettingsCommand = ReactiveCommand.Create(ResetServerSettings); + ConnectToServerCommand = ReactiveCommand.Create(ConnectToServer); + SendMessageCommand = ReactiveCommand.Create(SendMessage); + ClearMessageLogCommand = ReactiveCommand.Create(ClearMessageLog); + + // Copy references for window and notificationManager + _notificationManager = notificationManager; + _window = window; + } + + /// + /// Creates a new MQTT-Server instance and starts it. + /// + private async void StartServer() + { + IMqttServerOptions serverOptions; + // Get Users and Passwords from user input. + string[] usernames = ServerUsernames.Replace(" ", "").Split(';'); + string[] passwords = ServerPasswords.Replace(" ", "").Split(';'); + + if (usernames.Length != passwords.Length) + { + NotificationManager.Show(new Avalonia.Controls.Notifications.Notification("Error", $"Could not start the server, because usernames and passwords do not match.", NotificationType.Error)); + return; + } + + mqttServer.Users = new List(); + for (int i = 0; i < usernames.Length; i++) + { + mqttServer.Users.Add(new MqttUser(usernames[i], passwords[i])); + } + + var factory = new MqttFactory(); + server = factory.CreateMqttServer(); + + if (ServerUsernames.Length > 0) + { + serverOptions = new MqttServerOptionsBuilder() + .WithDefaultEndpoint() + .WithDefaultEndpointPort(mqttServer.Port) + .WithConnectionValidator(c => + { + foreach (MqttUser user in mqttServer.Users) + { + if (user.Username == c.Username && user.Password == c.Password) + { + c.ReasonCode = MqttConnectReasonCode.Success; + return; + } + } + c.ReasonCode = MqttConnectReasonCode.BadUserNameOrPassword; + }) + .WithSubscriptionInterceptor(c => + { + c.AcceptSubscription = true; + }) + .WithApplicationMessageInterceptor(c => + { + c.AcceptPublish = true; + }) + .Build(); + } + else + { + serverOptions = new MqttServerOptionsBuilder() + .WithDefaultEndpoint() + .WithDefaultEndpointPort(mqttServer.Port) + .WithSubscriptionInterceptor(c => + { + c.AcceptSubscription = true; + }) + .WithApplicationMessageInterceptor(c => + { + c.AcceptPublish = true; + }) + .Build(); + } + + try + { + await server.StartAsync(serverOptions); + ServerStatusText = "Running"; + ServerStatusTextColor = Brushes.Green; + IsServerRunning = true; + } + catch (Exception e) + { + NotificationManager.Show(new Avalonia.Controls.Notifications.Notification("Error", $"Could not start the server: {e.Message}", NotificationType.Error)); + } + + try + { + try + { + using (Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, 0)) + { + socket.Connect("8.8.8.8", 65530); + IPEndPoint endPoint = socket.LocalEndPoint as IPEndPoint; + LocalIp = $"{endPoint.Address}:{mqttServer.Port}"; + } + } + catch (SocketException) + { + LocalIp = $"127.0.0.1:{mqttServer.Port}"; + } + } + catch (Exception e) + { + Debug.WriteLine($"Could not read local ip adress: {e}"); + } + + } + + /// + /// Stops a running MQTT-Server if available. + /// + private async void StopServer() + { + try + { + await server.StopAsync(); + ServerStatusText = "Stopped"; + ServerStatusTextColor = Brushes.Red; + IsServerRunning = false; + } + catch (Exception e) + { + NotificationManager.Show(new Avalonia.Controls.Notifications.Notification("Error", $"Could not stop the server: {e.Message}", NotificationType.Error)); + } + } + + /// + /// Restarts or Starts the MQTT-Server (depends on current state). + /// + private void RestartServer() + { + if (server.IsStarted) + { + NotificationManager.Show(new Avalonia.Controls.Notifications.Notification("Information", $"Restarting the server...", NotificationType.Information)); + StopServer(); + StartServer(); + } + else + { + NotificationManager.Show(new Avalonia.Controls.Notifications.Notification("Information", $"Starting the server...", NotificationType.Information)); + StartServer(); + } + } + + /// + /// Connect to a server via the provided credentials. + /// + private async void ConnectToServer() + { + if (mqttClient.IsConnected) + { + try + { + await client.DisconnectAsync(); + NotificationManager.Show(new Avalonia.Controls.Notifications.Notification("Information", $"Disconnected from server.", NotificationType.Information)); + ClientStatusText = "Status: Not connected."; + ClientConnectionButtonText = "Connect"; + IsConnectedToServer = false; + MqttMessageText = string.Empty; + // One might also keep those message in the queue... + ReceivedMessages = string.Empty; + } + catch (Exception e) + { + NotificationManager.Show(new Avalonia.Controls.Notifications.Notification("Error", $"Could not disconnect: {e.Message}", NotificationType.Error)); + } + + } + else + { + mqttClient.User = new MqttUser(ClientUsername, ClientPassword); + + if (ClientHostname.Length > 0) + { + if (ClientHostname.Contains(':')) + { + int port = 1883; + + mqttClient.Host = ClientHostname.Split(':')[0]; + if (int.TryParse(ClientHostname.Split(':')[1], out port)) + { + mqttClient.Port = port; + } + } + else + { + mqttClient.Host = ClientHostname; + } + } + + var factory = new MqttFactory(); + client = factory.CreateMqttClient(); + var clientOptions = new MqttClientOptionsBuilder() + .WithTcpServer(mqttClient.Host, mqttClient.Port) + .WithCredentials((ConnectToInternalServer ? mqttServer.Users?.FirstOrDefault().Username : mqttClient.User.Username) ?? "", + (ConnectToInternalServer ? mqttServer.Users?.FirstOrDefault().Password : mqttClient.User.Password) ?? "") + .Build(); + try + { + await client.ConnectAsync(clientOptions, new CancellationToken()); + listenForMessagesThread = new Thread(ListenForMessages); + listenForMessagesThread.Start(); + NotificationManager.Show(new Avalonia.Controls.Notifications.Notification("Success", $"Connected to Server.", NotificationType.Success)); + ClientStatusText = "Status: Connected to Server."; + ClientConnectionButtonText = "Disconnect"; + IsConnectedToServer = true; + } + catch (Exception e) + { + NotificationManager.Show(new Avalonia.Controls.Notifications.Notification("Error", $"Could not connect: {e.Message}", NotificationType.Error)); + } + } + } + + /// + /// Send the message from the current queue. + /// + private async void SendMessage() + { + if (client.IsConnected) + { + if (string.IsNullOrEmpty(mqttClient.Topic)) + { + NotificationManager.Show(new Avalonia.Controls.Notifications.Notification("Error", "The Topic can not be empty.", NotificationType.Error)); + } + else + { + if (string.IsNullOrEmpty(MqttMessageText)) + { + NotificationManager.Show(new Avalonia.Controls.Notifications.Notification("Error", "The Payload can not be empty.", NotificationType.Error)); + } + else + { + await client.PublishAsync(mqttClient.Topic, MqttMessageText); + MqttMessageText = string.Empty; + } + } + } + else + { + NotificationManager.Show(new Avalonia.Controls.Notifications.Notification("Error", "The server can not be reached anymore...", NotificationType.Error)); + } + } + + private async void ListenForMessages() + { + List topicFilters = new List(); + string [] topicsAsString = mqttMessageOptions.FilterByTopic.Replace(" ", string.Empty).Split(';'); + + if (topicsAsString.Length > 0) + { + foreach (string topicAsString in topicsAsString) + { + MqttTopicFilter mqttTopicFilter = new MqttTopicFilter(); + mqttTopicFilter.Topic = topicAsString; + topicFilters.Add(mqttTopicFilter); + } + + await client.SubscribeAsync(topicFilters.ToArray()); + } +/* else if (topicsAsString.Length == 1) + { + await client.SubscribeAsync(mqttMessageOptions.FilterByTopic); + }*/ + else + { + NotificationManager.Show(new Avalonia.Controls.Notifications.Notification("Error", "Can not subscribe to chosen topic. Please change the topic in the general tab.", NotificationType.Error)); + } + + + while (client.IsConnected == true) + { + client.UseApplicationMessageReceivedHandler(e => + { + if (e.ProcessingFailed == false) + { + string ReceivedMessage = string.Empty; + if (mqttMessageOptions.DisplayTopic) + { + ReceivedMessage += $"Topic: {e.ApplicationMessage.Topic} "; + } + if (mqttMessageOptions.DisplayPayloadAsString) + { + if (mqttMessageOptions.DisplayTopic || mqttMessageOptions.WritePayloadToFile) + { + ReceivedMessage += $"Payload: {Encoding.UTF8.GetString(e.ApplicationMessage.Payload)} "; + } + else + { + // If no other information is outputted, write out the message only. + ReceivedMessage += Encoding.UTF8.GetString(e.ApplicationMessage.Payload); + } + } + ReceivedMessage += "\n"; + ReceivedMessages += ReceivedMessage; + //_window.ScrollTextToEnd(); // Does not work. + } + }); + } + } + + /// + /// Replaces the user configuration of the development server with the default values. + /// + private void ResetServerSettings() + { + ServerPasswords = string.Empty; + ServerUsernames = string.Empty; + StopServer(); + } + + private void ClearMessageLog() + { + ReceivedMessages = string.Empty; + } + } +} diff --git a/ViewModels/ViewModelBase.cs b/ViewModels/ViewModelBase.cs new file mode 100644 index 0000000..60deb8c --- /dev/null +++ b/ViewModels/ViewModelBase.cs @@ -0,0 +1,11 @@ +using System; +using System.Collections.Generic; +using System.Text; +using ReactiveUI; + +namespace MqttDebugger.ViewModels +{ + public class ViewModelBase : ReactiveObject + { + } +} diff --git a/Views/MainWindow.axaml b/Views/MainWindow.axaml new file mode 100644 index 0000000..89bb48a --- /dev/null +++ b/Views/MainWindow.axaml @@ -0,0 +1,230 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Views/MainWindow.axaml.cs b/Views/MainWindow.axaml.cs new file mode 100644 index 0000000..5e0cea9 --- /dev/null +++ b/Views/MainWindow.axaml.cs @@ -0,0 +1,106 @@ +using Avalonia; +using Avalonia.Controls; +using Avalonia.Controls.Notifications; +using Avalonia.Data; +using Avalonia.Input; +using Avalonia.Interactivity; +using Avalonia.Markup.Xaml; +using MqttDebugger.ViewModels; +using System.Diagnostics; +using System.Windows.Input; + +namespace MqttDebugger.Views +{ + public class MainWindow : Window + { + private WindowNotificationManager _notificationArea; + private ScrollViewer messageLogScrollViewer; + + private TextBlock linkTextBlock; + private CheckBox saveToFileCheckBox; + private TextBox topicFilterTextBox; + + private string topicBefore = "#"; + + public MainWindow() + { + InitializeComponent(); + + _notificationArea = new WindowNotificationManager(this) + { + Position = NotificationPosition.TopRight, + MaxItems = 2 + }; + + DataContext = new MainWindowViewModel(this, _notificationArea); + + messageLogScrollViewer = this.FindControl("MessageLogScrollViewer"); + ScrollTextToEnd(); + + linkTextBlock = this.FindControl("LinkText"); + linkTextBlock.Tapped += OpenLink; + + saveToFileCheckBox = this.FindControl("SaveToFileCheckBox"); + saveToFileCheckBox.Checked += SaveToFileCheckBox_Checked; + + topicFilterTextBox = this.FindControl("TopicFilterTextBox"); + topicFilterTextBox.GotFocus += TopicFilterTextBox_GotFocus; + topicFilterTextBox.LostFocus += TopicFilterTextBox_LostFocus; + } + + private void TopicFilterTextBox_GotFocus(object sender, GotFocusEventArgs e) + { + topicBefore = topicFilterTextBox.Text; + } + + private void TopicFilterTextBox_LostFocus(object sender, RoutedEventArgs e) + { + if (((MainWindowViewModel)DataContext).IsConnectedToServer) + { + if (topicFilterTextBox.Text != topicBefore) + { + _notificationArea.Show(new Notification("Reload required.", "You will need to reconnect to the server, for that setting to become active.", NotificationType.Information)); + } + } + } + + private async void SaveToFileCheckBox_Checked(object sender, RoutedEventArgs e) + { + OpenFolderDialog dialog = new OpenFolderDialog(); + dialog.Title = "Select a folder to output the payload to."; + + string result = await dialog.ShowAsync(this); + + if (result.Length > 0) + { + ((MainWindowViewModel)DataContext).FileOutputFolder = result; + } + else + { + _notificationArea.Show(new Notification("Error", "An output folder is required to write the payload to files.", NotificationType.Error)); + saveToFileCheckBox.IsChecked = false; + } + } + + private void OpenLink(object sender, RoutedEventArgs e) + { + TextBlock urlTextBlock = (TextBlock)sender; + ProcessStartInfo psi = new ProcessStartInfo + { + FileName = $"http://{urlTextBlock.Text}", + UseShellExecute = true + }; + Process.Start(psi); + } + + public void ScrollTextToEnd() + { + messageLogScrollViewer.ScrollToEnd(); + } + + private void InitializeComponent() + { + AvaloniaXamlLoader.Load(this); + } + } +} diff --git a/Views/ValueConverters/BooleanToKeyboardShortcutConverter.cs b/Views/ValueConverters/BooleanToKeyboardShortcutConverter.cs new file mode 100644 index 0000000..e56ccc2 --- /dev/null +++ b/Views/ValueConverters/BooleanToKeyboardShortcutConverter.cs @@ -0,0 +1,27 @@ +using Avalonia.Data.Converters; +using Avalonia.Input; +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Text; + +namespace MqttDebugger.Views.ValueConverters +{ + public class BooleanToKeyboardShortcutConverter : IValueConverter + { + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + bool isShiftEnter = (bool)value; + if (isShiftEnter) + { + return new KeyGesture(Key.Enter, KeyModifiers.Shift); + } + return new KeyGesture(Key.Enter); + } + + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + throw new NotImplementedException(); + } + } +} diff --git a/logo.ico b/logo.ico new file mode 100644 index 0000000000000000000000000000000000000000..e2f04b478191419116d9270a85a5e4486ba332f8 GIT binary patch literal 9683 zcmd6NcTiK^*X~IO0wPi+0@9=?9Vss&9R;L`fOI4X7?3W#hhm|q^e#m}B2uFC9#nd- zqIBs+T4+fK$qn!C`|Hl!Kkm%E-+al*IVY37)>-S>&wke4djbF;^85S+0(gKH0RUhn z-^VIZ{5i74r88t2SGa0vxHTw91A=pL8x%{JsmAXS- zAnC;6*%)JgJ3kJNQJnOJB@?#|k}}tn*Fx45q}6$-BmW;B4jlNMCReqDYc08#xmZ}3 zli!7i4djYB{ZS{_mE{xs^n^6&7*gDo6Z0ovv=@W5 zhTk_}HH3V|QN8^R?`{=ldSx*32)3lXyT<5q{1;sqrl%*L_U&rgH%iuTLQ{=yfS8Tt z_3e2vlF{(HUCM!s2-th|SXdHU_j~wF3MKD^jeeF_)Cpazur$ke1kC=zx9bF7Gx4xX zBn73r&)VCIhT~Os3iPurM_*T63pC|%B8I4mm@cGe#IN|9^OSQcFw>b(nB8so7K z1Za3j6$b(vD|JgEdz1VHE~i-7pscMYkKDGc|=a_Za|)C zg%6TL4x(}qbD-N~9=*KdL(*6cUCoN=kXsd%ej!B1$kMimvPf25M8Z}ie!%FL)bpfE zu$|ZL+^hEArJ1S}NO1%@ur6x4=`@ygT$5B@AFv&nlB>7vyPx2swVxbIlI$<4K`8UF zm4}(_?JSF3wbq22ME??bVT7sOWx`MZ^pt7dgD+E!xNX&EKg)bV9|>tb)`G4TZxlEi6hru{$WocJ@WCuPYp$ zop`Trp_E3`D7@sZUpE@=%45MA&c|i`m@&OiW=HQEM`2rw{I(6^kO~s2a`sNyKR3u= z`TJRM^rGX9$WNaR2vcl2eQD_rnxJZPrXiS=Z4VB(F+r+da`nA>T;7$2?SK~>-&kH! zW8}Vi9WEO_LM*7RZZ~N(U|Z^KyXu`TXrz%;reEDEsdhAAahoHs4rFfWP1n+#m05hZ zE$xeZ`I0v7V74))G*NNoca82&Q{B^oj7K6j5vEKT1J(9g_OWr^`9I4k7SyC=5D7T8*=zbz5!Pp*P74p6KiZFkY z8H%;eaq#e`$8XFPw|OqdryZnpPKEpECqKAfMMrBdub4zeM)8I#_IDT-wx`B0cdR^L zik&AC4jOuz{7G)U9Yf2%Zpm}xQ{^GTww;zb2M}9VoU_5dOsYTKW@X{GQ&iH**`8C9 z-RYA^uD^eQHR6aR4{i1%O$@)>;LV(L$LGEbgzAR({+g7y5s;udm!k4Ek&hF*-=b^Z zxsKA$)@>{%*sJ7Zw~IWnlnf?C(a~PM zIrVi;hIh3l(Ya|_-NpoQxmLZ#IVkNQ65u(b4uqr`$At}rguR=}jS6Q-j zEDQ-2L_NDqn>QDkx{olr#I6|~5rnBZ#H=$OsR9+}w_zn%-4@V4DJt#0d|}yAFL%lc8J)bw&?pUe~Ij7us- z!az_}Q$`jMc=Y~(F5c~M9lQLN!x-agk)S*)S0oP0%@{|)LUdY!gy1YHB2-uD@3$?k zpxXXQmJl?r*DJbTo6S|pOY+<4Z+aT>erNahU4&k)UiO&_d-3nzN+no^@cl|#kDesF zJ*qX!^YjP((vel&>dq0Up}fxhvCe45t^FSTEPggH_>w{9j>=n)%*urD4#+}hnPe0B z0a&+D{>COd%8xtK>A_419V!6{c4%zm@v9fZTH@lJTOrg7&%jv6f7-79GOMuO-0<68 zw1+BxwwGsgAjrqKOpY4NHjY3)>W)Fi;^;w&h7diUiO)UI(-TC--qGYxSg0xy zQRg;>?9a=~7;gyj(v(WJ!SJXX9fF z6r|z9wNq*`v)_1;9j^gBfo;6%#webdmzM@Hn3-(5kT_HHTNS_EB&01Gzg(=(EcRtk zE>$J-xSq?_4+nFsShK`ciPK{D@rN5eh}k=1JA(c7p>pm$OtJ1Gk*k;mnL)yaG9m%ey)-NZ;9J9B(&TTuP7X5DlYw&2bA0g%a%{x zT9KRk=bH2@VunKcVx3!Gm_@?=P!EUP=*I9vOY2%u{c62^-tcR2Z4(ooZG9p*Oad=G z^JIlSy8QOiW#zzA_uc}H810sz4&=|0Wc3CGGVQzAVD}eFR#3fvhsH1*^ZK`+fX^D7 zMi9G#DRsL$Gur5{{6B*NM;zXECXwODrD+iL^p?JM`SOEi`DaEUo2vst_=(cwr0dtY z1m#>aS_g+t4OJAvY;9gZxJ1I&GCVNl1kJuk7vtiGe>W#mdaq-H2e08dUtz8Z7<=(!^`?2zuIq%=Brh| zK%OpnqsYVKau$@u?GjnmNSwB;R?rP(jb`aOR{G>kfaP$6iXG$SF#@HJ7njUG4 zYSA2#Xa+#$$$)7kfdBMpJYL{SOGx0ThzA=_L)W2bv{v+6aeR&a&dtw+Bu2hZ zYCDbC)yZK|EiLM{EI1Hq0em3n(oH&V04gyTS;Chuj5YH-W|0v_++9st>>YalY&z|o zX@#Da@S|{`%mjEs&`o8EuC7BSo0JNhfYFj_lxkqj71XzDX4bgCO+>AW*`4CC5yo8n zUBFa4T#x3BzgIz@jm^CpXCXuI$eY150ozAJXeM`xInn#W2cMbg(>%;w;2$1xQ#}dm z40jV$#UmP5cy65uje`(^2A;~)hiHYV-1)Oh`nTbAa>EeIva20;mpK zBC7Xmy|E$7qVJw9Ug$P9@!TmtpzQ$loAEHf@@zlkcpPsV;b!;n>F$|YvI z^~sNQhaa!vS-8Pp)Ii-9;WO6>#jX}QhO5W8)M2qX9=HMl#a4S4&?uc;|nEmMKQKf~{5h*H~n0gQIFc=6{MpVsKc&+-kYk2>*U zDiGVK^X3l~6z&)`R2tP$N!+3c?TsaOIwnbBgvF|rhWp9U#@#NLak8?rf$!%&afH%>%wvFmu80%-5#-0R zfE)%P@P@`w7@^?dx|i)o*=xLmM#O^t>jG({$q0%K2_8 zu83qk4x`Q|FG7sLQ8MgPs2RTY$;`=A9x^oOPHV&EkvYHZ(fH%6uRE`Si=O}~0m+%U z+^`BGjS{BYKgI_QXs)Kt$K!g~-yN$14BHY(NUm1J?BL!|+)yB>f1 z>ZOubH542U^e45+K%w}%4O-FYC%Jtt=ZqABzrB8fKRp>bc9I~+oFNurfE{W^l7oRs zYG8B#i{9PuA+By(!Mp%9kCv#2MbhGVGA+%w)!%<49QT_G8a|&6Wn?r^oT3G(0`ggu z-F)pFd5B!2l)(6KMfDjKxp)Q+&TTq-yDM@>=~}@s#97RLymc{M%{Ltr4^z8&NWJg2 zT0TP-bf1x3{Z)Sfu5G2U;sN9M^VHg;wQZwKbzh%9_WD>`(s^Q!SDVR+`E5Fkj@VEG z-~c$y8!ylNDh%~kDFM%v5_g(&aN=SNE%zmQwv_{`C;LRqdg;mW{noK9sH*xCTjyV? zUPt9Gc_mkaOj?O9SCd7*`5ynd4~6h++b_v$`?%q^8-F(05kDs7)ILZ@TzQjC33>R& z;&Xc<$(BTvLHW5&<>`|3nJ2t!G)Jh*$J09wy|$ZIHq6(~Ej75RLmrhm85=qZQzCr` zyFFWW7>bK-&6gDB?|wp7;d8Yw3B{*5 zT=|dNr#PhJ)OtJ{0Li}!x}RHDr8{R+v-#sq@L{T8#F(C~-#PC3tBwLYN2sk|iF3!t z#eUZKV+L}28PI*jYn;sQf&!HutsmR67z>n=-NQ0}7q*KKryKo|bIQu36mBvDS%d&> zPH1|4pR%$}YTJcky??+r2g;1?aEME^g&-o4B&0$n;Z0K@DNQ3zxp`?V{Sz6`JR~RY zWoibfW_;R;M1Q~W)W}?a3-OPdTKs>UiUFMrmX!|H@1^Rfk~3nKSDupkmU}S9!ClhY zIwGX~Z&A-?L{JV^VPvX2Yy`R=eAMAIt95nNQ4MDj*lT-O`ZHXYPxEl;`>dHI&VY_X zbBtZKw-!rI&Grg7fyJoQSlQb@exP33z>q0mxEXh*o0#}jXL?ITfedtN@EI4^dDB^N zLdvkS@lUe~g6gg5C=e)RxQOPAmFt1d%oc2>QPy|x*A|&CKq)l5ojlihj(V*{+`Z(9 zOJ@J4XzT10nNw_>1+?xAM>Lw19{=)1e+4v6O1w^NUta9FeF*|uE&+mQ7-5FdRT#Ux z5~fdo{u1`y)cIStYKQmb8KJml|IS)HUrFC0dNlbKJ}hh@-Wah!HZ|uPeWVXKL{pt} zPB?r{ea1!f9*j8B3WfJW*slmN`HxMWJPD&#g&{A`ZjMBy)K8M`I^8_#gQG~#eMUEk zJ0)W^N;V(VnZ-9k$?$a>gCtKH&i(?1Ir2~5L8h7-I%bbf40`)89FdXhLxVYEa|#B} zR^oRi<6r%(aPz_`Z2Ih56M2h_Xt>Vq@{lzI25EcI{GPZZYgdsW4Ad~-_oSZg+*lq_ z0$;H4nhD(!Ym~8jvDk}{QGDOISK0msv(-ST>fH2skPQnlycR(oa0C|BH3Xvla(`a2 zPMg|C9bA40t_iPrTRjB*RJTzvq<*oNbTIru1~*}*FWg#0F{Y2SlE04>nKM8p7oPKL z83lYNo-xYm-98w8M8h)TWRzxBb~W2YE1Ckl{-g@-lNwiC6c^@&B0*V4U5RsdGR(uY zp*cIJk~uq+n$17XqW~yrR67$hdy6uJBi8=@O{y~~VRGCLhSU>c-m;I>s70}u5f7SK za0MnJ$;bFA>wQv894v~vBcvF5&hUqTltyO9sx?d5=K57cdtaZ|bd)u58Gp2)Io}#P zz0)M=#UDD+s^ps-d?uKNPJZD3mUh0LE?qf^#?gTcqiPH|L8#C5mKIn|T%2AUhgBqN zpeN3F>*q%m^}DHcvt6!#r&5DVQSO5pXmsH0e2Hv&E4{R2A>n+$I)J(Zq^JfGRSya@ zmfTgArr&HuMQ!-ixyR0I&5x8qkM<9w8wi{}jR7_%^h=DeF=_j$JlX%U9Dg#H$qTwW zlqA5d*!^ev4Ttk&FSt^5$N40<(g4hf$gK;yDIfU@E=zvQ(WLI zIu1$PX+DN}^`Vr=fSZ2>9R6m0)hj4rx>+$Hh$^?n5sh~lsj2e=0;~P^Y&%j(KKCF4 z)HJtTPsyH4 z%`BOdl>)w)Lm4DtvVp)$>bpsrHn@4tH1ll0LFblB|8KUv=YH=LmbODlj5H2h+o5Zm z9ot8wJ6`-^F4Jj^0fCv#lS5T6nsR&^D+U@4XY;<0i#`wF!WyneuHo`bP7jN^&p2eA z+s_%c>JpQEvP9e+u-}K<1?(P$&dg1=t>WW`{Hh$HIhc5>!wwNxC z%y9?GOC>nofZOcDy7;Pup&8g)~?3L;63d#(@*|?d+ubV86aa z6wA11LRso3(T(<6n>*WXJBSNV;o&^{CWhY&m{s zkuk3^DB#{fq1IH;y!yYY^{WUFW)TdGBh95%CN|p%@vNYf1ED z^Z3KL`RBQZPmMFJBMuutw~t}^@!6!x{9kAVDff)N+7%>4Mg4+uiPyGRqX_%QoSiva zle?Q8At8nI7DT>3e`N4N+zW|;x4s}>YhgY)Pt+n-m&Oj=bM74P2Ae@=6&2fgBqZX9 z6=V@=V?}|YUnDCw(@5(7Pw8Rm^2H;L6t-Xyr&%#-qPAg*L~*`Uo&^Rqvq zG=l9Y&FH*eJ!{$L5Y_B@p?>@>^T1SmPa}zWX-a7oJa20-RBupf2CJEzSBai;My7QbR2XZ$N12FB!i>)c!2!6tpUH-` zTj6Dajf<$$>LtblOZyY)$BoU1CvD+d4bP<>hJ|;<9&eYFj!*?Sgw4*68LMWL3 z`gb+UM-kQZdwcu1wE|evrSAbkl4H}SizltEiTgFqscc&t0u}psA~QF)TA<`6nPjRF ztg7(KEyr2EjFA;sTzOsl1A6+dF;Z}8$IH1Ef17Lnh^|;j;N9f#0KEmFt^u3;*`%qd z=G8923KTK1a^m?FuCIn_zJ92thQla?pNA*{WX{{T@n5cS7O-QNWtzkvGHTg8Tkxwe zUTcM(9x3<=QB6`g;7*P=4u+@#st4-ZPAbyPFrDnOwXkxFDxnFK2SSro3Ss592bh4|frq*$S$H$IO)pUsEV%9oqxyY)k3Jg_>It@R(M>WpmI9<)C@Zw>f z%b4K0ozuEm`m0NUdqw|o*{Vk2+_%Er)&VnW!Iv8oiPC|ONk)Y_srMp9LuVTUTL;hO zl)A0|edrS1^UVpfj)r$R?LBRlgHQz@SMLUcJf(E<)_+x~aeOp&78g^JU&Jo6$1b)o zENm;8f&%vEs6~*x6M#Hbiwsycvn0DBFIDt*WN8>r8+{lZv;JY8q@rV5!Y~vz9nU)b`gf%q7<_leM8{C9wE!d`>iwsa zSnSss+QskLI_;Ci76401uc@oIqmH)XM7F2m;yPko#OvJ5c7x1+eHNK*EY~TAsU!%SnQV+{ zo?1RLKcByy+n5Cidde4-)X$V>4^NhC6(N@B*3cYsjLT~tvgb=8fO>(AX};QGD)`;3 z2YJfrHl|t4Z$$9oNHna=h6x3x9R7(9Gpz5cK=i3Dal*3lh_lOF2PRvS%+X+`x9R)`;A(Dxt z%lo-vt;Mbnfs#MMCcnrn(Uilf+Lc#8Z&vc+NaED08{mZ!M6YMf9iM2+w}PuEz{cLL z{4v0Ml?&IUzYKbsL^iLawyhSU&W~rH*>$ej=96WYxrLxy7@GMS1lrs3%`#PhUqt5? z|5|hrjsq@CcVAin(JmbQVNeaD&A;wFFh$wMr!r7kM^92Mt(m0&c#MijE0b143H0>{@phJ_AFz&*Mp9u;8_cSJD zpb-vACU18p%VsSdynnAV^xX5y_6?CZp!e2|auY!DY`)DP?clUFq~j+Z@-VL9nHxA^ zjI?37{i}Kdk$An9^x`SC>VRIICg?EHA8?jY!k0X<)Bm9El(_8$1W;$jxFNdTc9kEP zNJ%^gjjGM1_uW^?(|pYF{QbAZ#s)-rRt5PFfxXP(v20ye%hZ76iA{<;dHK}LK8l{m zNSW;Z>k&F>t*i;z;Ds7e+Q#v51#`7!#I{O@PTTCL|Ap08z;X_Pd+N6@d(Zr7mR-li zpUJ5jK$53mM-uqgds^^%q0ua7$%ssH7XsnyeRnBEETS__k(*%vj2)hvvjZ<+wp50l zktiQu-}k$JU8k)Og7@=pSB;Xb7Z%m6AL;I#A}^biA_u*Fs?*JgUyse20e&S>U+;%L zI86Icg9aqw`Pk5B+{=qslC8SAq05s=BSt*EMqqOfHPGP$^tS&Bi$as#E@m8Y$^EQ` zmFAZ;bB2R1wQcQ7NZS_irZ_Q9@vLsZn>6%=h1zaVNFN}q{bfT=G{z4*wW~It#@A@K z$=bU}@}kLn3GI`BS47?AAyxe{t1ZL!PFyiaON3ZTPo`c6h|&+t$q{D;=DBz9 zRlOfodEr+N9K&dABXyo;6pXmO-&+5~F)%-mU+m~GvT9u;vGMcNUC5SFSwR9p$la6O zRSZ?_+O7dBg7xRwPf?J5XA>1EYZ9FC$w(k1eW%`IwJ*1quLrLf!bxB zH!i6#m~isL;KOc1=hwFEI{vCGFZNjlt8{EHvO>Rk>zTo?c*{0fsF{LZz`dWxPhz#t z9(^wTBRrALCzEx#xm7|Og>h`HffhScnnJQJu|cGjGGqGH;bOG59O<|{nYXJz{N8k+ z8%YSEgy*ArJ*e1=ZZ8Z;q$WzQ%n{cVr}{WLzI3EFUH5eG(F~y4dM1dhX zaq*hKdme3k%^vlZ+k?hfU{cdV)IFTq4Y1<$IqY7ZS?=QTx9*M7l|!N_vV9b#mHf + + + + + + + +