diff --git a/Common/Helpers/EnumHelpers.cs b/Common/Helpers/EnumHelpers.cs index 0961b0d..9403b8c 100644 --- a/Common/Helpers/EnumHelpers.cs +++ b/Common/Helpers/EnumHelpers.cs @@ -1,4 +1,7 @@ -namespace Common.Helpers; +using System.ComponentModel; +using System.Reflection; + +namespace Common.Helpers; public static class EnumHelpers { @@ -12,4 +15,34 @@ public static class EnumHelpers { return (T)Enum.Parse(typeof(T), value, true); } + + /// + /// Gets the description of an Enum value. + /// If there is no Description set, the Enum Value will be converted to string. + /// + /// Enum Object to get Description of. + /// String with Content of DescriptionAttribute of Enum object. + public static string GetEnumDescription(Enum enumObject) + { + // guard argument null + if (enumObject == null) { throw new ArgumentNullException(nameof(enumObject)); } + + // get field info from enum type + FieldInfo? fieldInfo = enumObject.GetType().GetField(enumObject.ToString()); + // return string of enum value if there is no field info + if (fieldInfo == null) + { + return enumObject.ToString(); + } + + // get description attribute and return if it is present + DescriptionAttribute? descAttrib = (DescriptionAttribute?)fieldInfo.GetCustomAttribute(typeof(DescriptionAttribute), true); + if (descAttrib != null) + { + return descAttrib.Description; + } + + // if no description attribute was found => return string of enum value + return enumObject.ToString(); + } } diff --git a/MultiTerm.Core/Types/ProtocolNotConnectedMessage.cs b/MultiTerm.Core/Types/ProtocolNotConnectedMessage.cs deleted file mode 100644 index d1e3da3..0000000 --- a/MultiTerm.Core/Types/ProtocolNotConnectedMessage.cs +++ /dev/null @@ -1,27 +0,0 @@ -using Common.Messaging; - -namespace MultiTerm.Core.Types; - -public class ProtocolNotConnectedMessage : IUserInterfaceMessage -{ - private readonly string reason; - - public MessageImportance Importance => MessageImportance.Medium; - - public ProtocolNotConnectedMessage(string reason) - { - this.reason = reason; - } - - public override string ToString() - { - if (string.IsNullOrEmpty(reason)) - { - return $"Protocol is not connected."; - } - else - { - return $"{reason}: Protocol is not connected."; - } - } -} diff --git a/MultiTerm.Core/Types/ProtocolNotConnectedUIMessage.cs b/MultiTerm.Core/Types/ProtocolNotConnectedUIMessage.cs new file mode 100644 index 0000000..ec0feeb --- /dev/null +++ b/MultiTerm.Core/Types/ProtocolNotConnectedUIMessage.cs @@ -0,0 +1,27 @@ +using Common.Messaging; + +namespace MultiTerm.Core.Types; + +public sealed class ProtocolNotConnectedUIMessage : IUserInterfaceMessage +{ + private readonly string message; + + public MessageImportance Importance => MessageImportance.Medium; + + public ProtocolNotConnectedUIMessage(string message) + { + this.message = message; + } + + public override string ToString() + { + if (string.IsNullOrEmpty(this.message)) + { + return $"Protocol is not connected."; + } + else + { + return $"Protocol is not connected: {this.message}"; + } + } +} diff --git a/MultiTerm.Core/ViewModel/TerminalViewModel.cs b/MultiTerm.Core/ViewModel/TerminalViewModel.cs index 5b266a8..74cfb1e 100644 --- a/MultiTerm.Core/ViewModel/TerminalViewModel.cs +++ b/MultiTerm.Core/ViewModel/TerminalViewModel.cs @@ -192,7 +192,7 @@ public abstract partial class TerminalViewModel : ObservableObject, ITerminalVie // inform user and quit if communication protocol is not connected if (this.CommunicationProtocol.State != ProtocolConnectionState.Connected) { - this.messenger.Send(new ProtocolNotConnectedMessage("Cannot send message, Protocol is not connected.")); + this.messenger.Send(new ProtocolNotConnectedUIMessage("Cannot send message.")); return false; } diff --git a/MultiTerm.Protocols/CommunicationProtocol.cs b/MultiTerm.Protocols/CommunicationProtocol.cs index 836fda0..cb68dd2 100644 --- a/MultiTerm.Protocols/CommunicationProtocol.cs +++ b/MultiTerm.Protocols/CommunicationProtocol.cs @@ -1,4 +1,5 @@ -using Common.Logging; +using Common.Helpers; +using Common.Logging; using Common.Messaging; using CommunityToolkit.Mvvm.Messaging; using MultiTerm.Protocols.Model; @@ -226,4 +227,11 @@ public abstract class CommunicationProtocol : ICommunicationProtocol Thread.Sleep(1); } } + + #region Helpers + public string GetProtocolAndInstanceIdentifier() + { + return $"{EnumHelpers.GetEnumDescription(this.ProtocolType)} {this.InstanceIdentifier}"; + } + #endregion } diff --git a/MultiTerm.Protocols/Helpers/ServiceExtensions.cs b/MultiTerm.Protocols/Helpers/ServiceExtensions.cs index 822156e..f70a6de 100644 --- a/MultiTerm.Protocols/Helpers/ServiceExtensions.cs +++ b/MultiTerm.Protocols/Helpers/ServiceExtensions.cs @@ -1,6 +1,7 @@ using Microsoft.Extensions.DependencyInjection; using MultiTerm.Protocols.Factories; using MultiTerm.Protocols.Serial; +using MultiTerm.Protocols.Tcp; using MultiTerm.Protocols.Udp; using MultiTerm.Protocols.UsbHid; @@ -20,12 +21,14 @@ public static class ServiceExtensions services.AddTransient(); services.AddTransient(); services.AddTransient(); + services.AddTransient(); // TODO extend // add all settings view model implementations to the services collection services.AddTransient(); services.AddTransient(); services.AddTransient(); + services.AddTransient(); // TODO extend // add a function to the services collection, which is used by the CommunicationProtocolFactory diff --git a/MultiTerm.Protocols/Network/NetworkProtocolSettingsViewModel.cs b/MultiTerm.Protocols/Network/NetworkProtocolSettingsViewModel.cs index 7d3eea7..f441748 100644 --- a/MultiTerm.Protocols/Network/NetworkProtocolSettingsViewModel.cs +++ b/MultiTerm.Protocols/Network/NetworkProtocolSettingsViewModel.cs @@ -39,12 +39,12 @@ public abstract partial class NetworkProtocolSettingsViewModel : ProtocolSetting { if (String.IsNullOrEmpty(this.Hostname)) { - this.messenger.Send(new ProtocolSettingsInvalidMessage(nameof(this.Hostname), "Null or empty")); + this.messenger.Send(new ProtocolSettingsInvalidUIMessage(nameof(this.Hostname), "Null or empty")); return false; } if (this.Port < IPEndPoint.MinPort && this.Port > IPEndPoint.MaxPort) { - this.messenger.Send(new ProtocolSettingsInvalidMessage(nameof(this.Port), "Out of range")); + this.messenger.Send(new ProtocolSettingsInvalidUIMessage(nameof(this.Port), "Out of range")); return false; } // try parse to IP address @@ -62,17 +62,17 @@ public abstract partial class NetworkProtocolSettingsViewModel : ProtocolSetting } catch (ArgumentOutOfRangeException) { - this.messenger.Send(new ProtocolSettingsInvalidMessage(nameof(this.Hostname), "Length is greater than 255 characters")); + this.messenger.Send(new ProtocolSettingsInvalidUIMessage(nameof(this.Hostname), "Length is greater than 255 characters")); return false; } catch (SocketException) { - this.messenger.Send(new ProtocolSettingsInvalidMessage(nameof(this.Hostname), "Error encountered when resolving hostname")); + this.messenger.Send(new ProtocolSettingsInvalidUIMessage(nameof(this.Hostname), "Error encountered when resolving hostname")); return false; } catch (ArgumentException) { - this.messenger.Send(new ProtocolSettingsInvalidMessage(nameof(this.Hostname), "Invalid IP address")); + this.messenger.Send(new ProtocolSettingsInvalidUIMessage(nameof(this.Hostname), "Invalid IP address")); return false; } diff --git a/MultiTerm.Protocols/Serial/SerialProtocolSettingsViewModel.cs b/MultiTerm.Protocols/Serial/SerialProtocolSettingsViewModel.cs index 28bb498..9bd321f 100644 --- a/MultiTerm.Protocols/Serial/SerialProtocolSettingsViewModel.cs +++ b/MultiTerm.Protocols/Serial/SerialProtocolSettingsViewModel.cs @@ -53,22 +53,22 @@ public partial class SerialProtocolSettingsViewModel : ProtocolSettingsViewModel { if (String.IsNullOrEmpty(this.PortName)) { - this.messenger.Send(new ProtocolSettingsInvalidMessage(nameof(this.PortName), "Null or empty")); + this.messenger.Send(new ProtocolSettingsInvalidUIMessage(nameof(this.PortName), "Null or empty")); return false; } if (this.PortName.ToLower().StartsWith("com") == false) { - this.messenger.Send(new ProtocolSettingsInvalidMessage(nameof(this.PortName), "Must start with COM")); + this.messenger.Send(new ProtocolSettingsInvalidUIMessage(nameof(this.PortName), "Must start with COM")); return false; } if ((this.DataBits < 5 || this.DataBits > 8) && this.DataBits != 16) { - this.messenger.Send(new ProtocolSettingsInvalidMessage(nameof(this.DataBits), "Must be 5...8 or 16")); + this.messenger.Send(new ProtocolSettingsInvalidUIMessage(nameof(this.DataBits), "Must be 5...8 or 16")); return false; } if(this.BaudRate < 0) { - this.messenger.Send(new ProtocolSettingsInvalidMessage(nameof(this.BaudRate), "Must be larger than 0")); + this.messenger.Send(new ProtocolSettingsInvalidUIMessage(nameof(this.BaudRate), "Must be larger than 0")); return false; } diff --git a/MultiTerm.Protocols/Tcp/TcpClientProtocol.cs b/MultiTerm.Protocols/Tcp/TcpClientProtocol.cs new file mode 100644 index 0000000..64cd407 --- /dev/null +++ b/MultiTerm.Protocols/Tcp/TcpClientProtocol.cs @@ -0,0 +1,188 @@ +using Common.Logging; +using Common.Messaging; +using CommunityToolkit.Mvvm.Messaging; +using MultiTerm.Protocols.Helpers; +using MultiTerm.Protocols.Model; +using MultiTerm.Protocols.Network; +using MultiTerm.Protocols.Types; +using System.Net; +using System.Net.Sockets; + +namespace MultiTerm.Protocols.Tcp; + +public class TcpClientProtocol : CommunicationProtocol +{ + public override Types.ProtocolType ProtocolType => Types.ProtocolType.Tcp_Client; + + public override string InstanceIdentifier { get; protected set; } = string.Empty; + + private INetworkProtocolSettings? settings; + private TcpClient? client; + private NetworkStream? stream; + + private const int BufferSizeBytes = 1024; // number of bytes when reading data from TCP server + private const int ReadTimeoutMs = 100; // milliseconds until the read operation timeouts + private const int WriteTimeoutMs = 100; // milliseconds until the write operation timeouts + + public TcpClientProtocol(ILogger logger, IMessenger messenger) : base(logger, messenger) { } + + protected override bool InternalConnect(IProtocolSettings settings) + { + // check if settings are of correct type + if (settings is not INetworkProtocolSettings networkSettings) + { + this.settings = null; + throw new ArgumentException($"Cannot connect due to wrong type of Protocol Settings. " + + $"Check parameter {nameof(settings)}' of '{nameof(InternalConnect)}()' in {nameof(TcpClientProtocol)}."); + } + + // store locally + this.settings = networkSettings; + + // update identifier + this.InstanceIdentifier = NetworkProtocolHelpers.GetLimitedLengthHostname(this.settings); + + // check if client is null + if (this.client != null) + { + throw new Exception($"The TCP client was not null when {nameof(InternalConnect)} was called."); + } + + ///* resolve hostname */ + //IPAddress ipAddress; + //try + //{ + // IPHostEntry ipHostInfo = Dns.GetHostEntry(this.settings.Hostname); + // ipAddress = ipHostInfo.AddressList[0]; + //} + //catch (Exception ex) + //{ + // this.logger.LogException(ex, $"'{nameof(InternalConnect)}()' Failed to resolve hostname '{this.settings.Hostname}'.", nameof(TcpClientProtocol)); + // return false; + //} + + /* create client */ + //var ipEndPoint = new IPEndPoint(ipAddress, this.settings.Port); + this.client = new(AddressFamily.Unknown); // creates dual stack tcp client (ipv4 and ipv6) + try + { + this.client.Connect(this.settings.Hostname, this.settings.Port); + this.stream = client.GetStream(); + } + catch (Exception ex) + { + this.logger.LogException(ex, $"'{nameof(InternalConnect)}()' Creating TCP client failed:", nameof(TcpClientProtocol)); + // rollback + this.InternalDisconnect(); + return false; + } + + // set static settings + this.stream.ReadTimeout = ReadTimeoutMs; + this.stream.WriteTimeout = WriteTimeoutMs; + + // send message to inform about resolved IP address + if (this.client.Client.RemoteEndPoint is IPEndPoint remoteEndpoint) + { + this.messenger.Send(new TcpConnectedMessage(remoteEndpoint.Address)); + } + + return true; + } + + protected override void InternalDisconnect() + { + // close client + if (this.client != null) + { + this.client?.Close(); + this.client?.Dispose(); + this.client = null; + } + // close stream + if (this.stream != null) + { + this.stream?.Close(); + this.stream?.Dispose(); + this.stream = null; + } + // reset settings + this.settings = null; + // send message that indicates that no protocol is connected anymore + this.messenger.Send(new TcpConnectedMessage(null)); + } + + protected override void InternalRead(CancellationToken ct) + { + while (ct.IsCancellationRequested == false) + { + // try receive message with a buffer + int readByte = -1; + try + { + // will throw ObjectDisposedException if null + // timeout is defined after creation of stream in ctor + readByte = this.stream!.ReadByte(); + } + catch (OperationCanceledException) // intentionally cancelled => just break + { + break; + } + catch (ObjectDisposedException objex) + { + this.logger.LogException(objex, $"ObjectDisposedException while sending data in {nameof(InternalRead)}", nameof(TcpClientProtocol)); + this.OnUnintentionallyDisconnected(); + break; // break loop + } + catch (Exception ex) + { + this.logger.LogException(ex, $"Exception while reading data in {nameof(InternalRead)}", nameof(TcpClientProtocol)); + this.messenger.Send(new StoppedReadingUIMessage(this, ex.Message)); + break; // break loop + } + + // any data received? + if(readByte != -1) // -1 = end of stream or default value + { + // report received byte + this.OnReceivedData(new ExtendedByte((byte)readByte)); + } + } + } + + protected override bool InternalSendBytes(byte[] bytes) + { + // check for empty bytes array + if (bytes == null || bytes.Length == 0) + { + this.logger.LogWarn($"'{nameof(InternalSendBytes)}()' got null or empty bytes array.", nameof(TcpClientProtocol)); + return false; + } + + // try sending each byte individually + foreach (byte b in bytes) + { + try + { + // will throw ObjectDisposedException if null + this.stream!.WriteByte(b); + } + catch (ObjectDisposedException objex) + { + this.logger.LogException(objex, $"ObjectDisposedException while sending data in {nameof(InternalSendBytes)}", nameof(TcpClientProtocol)); + this.OnUnintentionallyDisconnected(); + return false; + } + catch (Exception ex) + { + this.logger.LogException(ex, $"Exception while sending data in {nameof(InternalSendBytes)}", nameof(TcpClientProtocol)); + return false; + } + + // report sent data + this.OnSentData(new ExtendedByte(b)); + } + + return true; // success + } +} diff --git a/MultiTerm.Protocols/Tcp/TcpClientProtocolSettingsViewModel.cs b/MultiTerm.Protocols/Tcp/TcpClientProtocolSettingsViewModel.cs new file mode 100644 index 0000000..068bde6 --- /dev/null +++ b/MultiTerm.Protocols/Tcp/TcpClientProtocolSettingsViewModel.cs @@ -0,0 +1,32 @@ +using CommunityToolkit.Mvvm.Messaging; +using MultiTerm.Protocols.Network; +using MultiTerm.Protocols.Types; + +namespace MultiTerm.Protocols.Tcp; + +public class TcpClientProtocolSettingsViewModel : NetworkProtocolSettingsViewModel, IRecipient +{ + public override ProtocolType ProtocolType => ProtocolType.Tcp_Client; + + public TcpClientProtocolSettingsViewModel(IMessenger messenger) : base(messenger) + { + // register for messages + messenger.Register(this); + } + + /// + /// Sets the proprty to the resolved address from the message. + /// + /// + void IRecipient.Receive(TcpConnectedMessage message) + { + if(message.ResolvedAddress == null) + { + this.ResolvedAddress = ""; + } + else + { + this.ResolvedAddress = message.ResolvedAddress.ToString(); + } + } +} diff --git a/MultiTerm.Protocols/Tcp/TcpConnectedMessage.cs b/MultiTerm.Protocols/Tcp/TcpConnectedMessage.cs new file mode 100644 index 0000000..8122595 --- /dev/null +++ b/MultiTerm.Protocols/Tcp/TcpConnectedMessage.cs @@ -0,0 +1,10 @@ +using System.Net; + +namespace MultiTerm.Protocols.Tcp; + +/// +/// A message that is sent by the to report that it has connected to an endpoint. +/// Main reason to use this message is to find out the of the endpoint. +/// May be null, e.g. if no IP is connected anymore. +/// +internal record TcpConnectedMessage(IPAddress? ResolvedAddress); \ No newline at end of file diff --git a/MultiTerm.Protocols/Types/ProtocolSettingsInvalidMessage.cs b/MultiTerm.Protocols/Types/ProtocolSettingsInvalidUIMessage.cs similarity index 73% rename from MultiTerm.Protocols/Types/ProtocolSettingsInvalidMessage.cs rename to MultiTerm.Protocols/Types/ProtocolSettingsInvalidUIMessage.cs index 82553c8..09af8c2 100644 --- a/MultiTerm.Protocols/Types/ProtocolSettingsInvalidMessage.cs +++ b/MultiTerm.Protocols/Types/ProtocolSettingsInvalidUIMessage.cs @@ -2,14 +2,14 @@ namespace MultiTerm.Protocols.Types; -public sealed class ProtocolSettingsInvalidMessage : IUserInterfaceMessage +public sealed class ProtocolSettingsInvalidUIMessage : IUserInterfaceMessage { public MessageImportance Importance => MessageImportance.Medium; public string SettingName { get; } public string Message { get; } - public ProtocolSettingsInvalidMessage(string settingName, string message) + public ProtocolSettingsInvalidUIMessage(string settingName, string message) { this.SettingName = settingName; this.Message = message; diff --git a/MultiTerm.Protocols/Types/ProtocolType.cs b/MultiTerm.Protocols/Types/ProtocolType.cs index 18f3e56..60e33af 100644 --- a/MultiTerm.Protocols/Types/ProtocolType.cs +++ b/MultiTerm.Protocols/Types/ProtocolType.cs @@ -17,10 +17,10 @@ public enum ProtocolType UsbHid, /// - /// TCP Protocol + /// TCP Client Protocol /// - [Description("TCP")] - Tcp, + [Description("TCP Client")] + Tcp_Client, /// /// UDP Protocol diff --git a/MultiTerm.Protocols/Types/StoppedReadingUIMessage.cs b/MultiTerm.Protocols/Types/StoppedReadingUIMessage.cs new file mode 100644 index 0000000..ff178c8 --- /dev/null +++ b/MultiTerm.Protocols/Types/StoppedReadingUIMessage.cs @@ -0,0 +1,46 @@ +using Common.Messaging; + +namespace MultiTerm.Protocols.Types; + +public sealed class StoppedReadingUIMessage : IUserInterfaceMessage +{ + private readonly string affectedTerminalIdentifier; + private readonly string reason; + + public MessageImportance Importance => MessageImportance.Medium; + + /// + /// Create with given and . + /// + /// let the user know which terminal is affected (e.g. 'UDP 192.168.1.1' or 'Serial COM5') + /// why the reading was stopped + public StoppedReadingUIMessage(string affectedTerminalIdentifier, string reason = "") + { + this.affectedTerminalIdentifier = affectedTerminalIdentifier; + this.reason = reason; + } + + /// + /// Create with given . + /// Extracts from given . + /// + /// root communication protocol instance, to extract affectedTerminalIdentifier from + /// why the reading was stopped + public StoppedReadingUIMessage(CommunicationProtocol communicationProtocol, string reason = "") + { + this.affectedTerminalIdentifier = communicationProtocol.GetProtocolAndInstanceIdentifier(); + this.reason = reason; + } + + public override string ToString() + { + if (string.IsNullOrEmpty(this.reason)) + { + return $"Stopped Reading from '{this.affectedTerminalIdentifier}' because of unknown issue. Please check logfile."; + } + else + { + return $"Stopped Reading from '{this.affectedTerminalIdentifier}': {this.reason}"; + } + } +} diff --git a/MultiTerm.Protocols/Udp/UdpConnectedMessage.cs b/MultiTerm.Protocols/Udp/UdpConnectedMessage.cs index 3862c61..ff92c9a 100644 --- a/MultiTerm.Protocols/Udp/UdpConnectedMessage.cs +++ b/MultiTerm.Protocols/Udp/UdpConnectedMessage.cs @@ -6,4 +6,4 @@ namespace MultiTerm.Protocols.Udp; /// A message that is sent by the to report that it has connected to an endpoint. /// Main reason to use this message is to find out the of the endpoint. /// -internal record UdpConnectedMessage(IPAddress ResolvedAddress); \ No newline at end of file +internal record UdpConnectedMessage(IPAddress? ResolvedAddress); \ No newline at end of file diff --git a/MultiTerm.Protocols/Udp/UdpProtocol.cs b/MultiTerm.Protocols/Udp/UdpProtocol.cs index 0de0bf7..c948668 100644 --- a/MultiTerm.Protocols/Udp/UdpProtocol.cs +++ b/MultiTerm.Protocols/Udp/UdpProtocol.cs @@ -4,6 +4,7 @@ using CommunityToolkit.Mvvm.Messaging; using MultiTerm.Protocols.Helpers; using MultiTerm.Protocols.Model; using MultiTerm.Protocols.Network; +using MultiTerm.Protocols.Types; using System.Net.Sockets; namespace MultiTerm.Protocols.Udp; @@ -52,7 +53,7 @@ public class UdpProtocol : CommunicationProtocol } catch (Exception ex) { - this.logger.LogException(ex, $"'{nameof(InternalConnect)}()'Opening Receiving UDP socket failed:", nameof(UdpProtocol)); + this.logger.LogException(ex, $"'{nameof(InternalConnect)}()' Opening Receiving UDP socket failed:", nameof(UdpProtocol)); // rollback this.InternalDisconnect(); return false; @@ -67,7 +68,7 @@ public class UdpProtocol : CommunicationProtocol } catch (Exception ex) { - this.logger.LogException(ex, $"'{nameof(InternalConnect)}()'Opening Sending UDP Socket failed:", nameof(UdpProtocol)); + this.logger.LogException(ex, $"'{nameof(InternalConnect)}()' Opening Sending UDP Socket failed:", nameof(UdpProtocol)); // rollback this.InternalDisconnect(); return false; @@ -94,24 +95,20 @@ public class UdpProtocol : CommunicationProtocol } // reset settings this.settings = null; + // send message that indicates that no protocol is connected anymore + this.messenger.Send(new UdpConnectedMessage(null)); } protected override void InternalRead(CancellationToken ct) { while(ct.IsCancellationRequested == false) { - // if receiving Udp Client is null => break - if (this.receivingUdpClient == null) - { - this.OnUnintentionallyDisconnected(); - break; // break loop - } - // try receive message UdpReceiveResult receivedResult; try { - receivedResult = this.receivingUdpClient.ReceiveAsync(ct).GetAwaiter().GetResult(); + // will throw ObjectDisposedException if null + receivedResult = this.receivingUdpClient!.ReceiveAsync(ct).GetAwaiter().GetResult(); } catch (OperationCanceledException) // intentionally cancelled => just break { @@ -126,8 +123,7 @@ public class UdpProtocol : CommunicationProtocol catch (Exception ex) { this.logger.LogException(ex, $"Exception while reading data in {nameof(InternalRead)}", nameof(UdpProtocol)); - this.messenger.Send(new GenericUserInterfaceMessage($"UDP client ended reading on port " + - $"{this.settings!.Port} because of exception.", MessageImportance.Medium)); + this.messenger.Send(new StoppedReadingUIMessage(this, ex.Message)); break; // break loop } @@ -146,13 +142,6 @@ public class UdpProtocol : CommunicationProtocol protected override bool InternalSendBytes(byte[] bytes) { - // if sending Udp Client is null => return error - if (this.sendingUdpClient == null) - { - this.OnUnintentionallyDisconnected(); - return false; - } - // check for empty bytes array if (bytes == null || bytes.Length == 0) { @@ -163,7 +152,8 @@ public class UdpProtocol : CommunicationProtocol // try sending the data try { - this.sendingUdpClient.Send(bytes, bytes.Length); + // will throw ObjectDisposedException if null + this.sendingUdpClient!.Send(bytes, bytes.Length); } catch (ObjectDisposedException objex) { diff --git a/MultiTerm.Protocols/Udp/UdpProtocolSettingsViewModel.cs b/MultiTerm.Protocols/Udp/UdpProtocolSettingsViewModel.cs index f5580aa..16330c5 100644 --- a/MultiTerm.Protocols/Udp/UdpProtocolSettingsViewModel.cs +++ b/MultiTerm.Protocols/Udp/UdpProtocolSettingsViewModel.cs @@ -19,6 +19,13 @@ public partial class UdpProtocolSettingsViewModel : NetworkProtocolSettingsViewM /// void IRecipient.Receive(UdpConnectedMessage message) { - this.ResolvedAddress = message.ResolvedAddress.ToString(); + if (message.ResolvedAddress == null) + { + this.ResolvedAddress = ""; + } + else + { + this.ResolvedAddress = message.ResolvedAddress.ToString(); + } } } diff --git a/MultiTerm.Protocols/UsbHid/UsbHidProtocolSettingsViewModel.cs b/MultiTerm.Protocols/UsbHid/UsbHidProtocolSettingsViewModel.cs index 76f6cea..d5b28fc 100644 --- a/MultiTerm.Protocols/UsbHid/UsbHidProtocolSettingsViewModel.cs +++ b/MultiTerm.Protocols/UsbHid/UsbHidProtocolSettingsViewModel.cs @@ -69,12 +69,12 @@ public partial class UsbHidProtocolSettingsViewModel : ProtocolSettingsViewModel this.VendorIdHex = this.VendorIdHex.PadLeft(4, '0'); if (this.VendorIdHex.Length > 4) { - this.messenger.Send(new ProtocolSettingsInvalidMessage(nameof(this.VendorId), "Must consist of a maximum of 4 hexadecimal characters")); + this.messenger.Send(new ProtocolSettingsInvalidUIMessage(nameof(this.VendorId), "Must consist of a maximum of 4 hexadecimal characters")); return false; } if (ushort.TryParse(this.VendorIdHex, System.Globalization.NumberStyles.HexNumber, null, out ushort vendorId) == false) { - this.messenger.Send(new ProtocolSettingsInvalidMessage(nameof(this.VendorId), "Invalid. Not a 16bit number.")); + this.messenger.Send(new ProtocolSettingsInvalidUIMessage(nameof(this.VendorId), "Invalid. Not a 16bit number.")); return false; } // set internal @@ -84,12 +84,12 @@ public partial class UsbHidProtocolSettingsViewModel : ProtocolSettingsViewModel this.ProductIdHex = this.ProductIdHex.PadLeft(4, '0'); if (this.ProductIdHex.Length > 4) { - this.messenger.Send(new ProtocolSettingsInvalidMessage(nameof(this.ProductId), "Must consist of a maximum of 4 hexadecimal characters")); + this.messenger.Send(new ProtocolSettingsInvalidUIMessage(nameof(this.ProductId), "Must consist of a maximum of 4 hexadecimal characters")); return false; } if (ushort.TryParse(this.ProductIdHex, System.Globalization.NumberStyles.HexNumber, null, out ushort productId) == false) { - this.messenger.Send(new ProtocolSettingsInvalidMessage(nameof(this.ProductId), "Invalid. Not a 16bit number.")); + this.messenger.Send(new ProtocolSettingsInvalidUIMessage(nameof(this.ProductId), "Invalid. Not a 16bit number.")); return false; } // set internal diff --git a/MultiTerm.Wpf/MultiTerm.Wpf.csproj b/MultiTerm.Wpf/MultiTerm.Wpf.csproj index 2090a4c..becc936 100644 --- a/MultiTerm.Wpf/MultiTerm.Wpf.csproj +++ b/MultiTerm.Wpf/MultiTerm.Wpf.csproj @@ -33,4 +33,10 @@ + + + Code + + + diff --git a/MultiTerm.Wpf/ValueConverters/ProtocolTypeToIconConverter.cs b/MultiTerm.Wpf/ValueConverters/ProtocolTypeToIconConverter.cs index 9450e09..a9447a6 100644 --- a/MultiTerm.Wpf/ValueConverters/ProtocolTypeToIconConverter.cs +++ b/MultiTerm.Wpf/ValueConverters/ProtocolTypeToIconConverter.cs @@ -24,7 +24,7 @@ internal class ProtocolTypeToIconConverter : IValueConverter { ProtocolType.Serial => "mdi-serial-port.png", ProtocolType.UsbHid => "mdi-keyboard.png", - ProtocolType.Tcp => "mdi-network.png", + ProtocolType.Tcp_Client => "mdi-network.png", ProtocolType.Udp => "mdi-network.png", _ => throw new NotImplementedException(), }; diff --git a/MultiTerm.Wpf/View/SendReceiveView.xaml b/MultiTerm.Wpf/View/SendReceiveView.xaml index d82b647..599e62b 100644 --- a/MultiTerm.Wpf/View/SendReceiveView.xaml +++ b/MultiTerm.Wpf/View/SendReceiveView.xaml @@ -9,6 +9,7 @@ xmlns:protocol_serial="clr-namespace:MultiTerm.Protocols.Serial;assembly=MultiTerm.Protocols" xmlns:protocol_usbhid="clr-namespace:MultiTerm.Protocols.UsbHid;assembly=MultiTerm.Protocols" xmlns:protocol_udp="clr-namespace:MultiTerm.Protocols.Udp;assembly=MultiTerm.Protocols" + xmlns:protocol_tcp="clr-namespace:MultiTerm.Protocols.Tcp;assembly=MultiTerm.Protocols" xmlns:types="clr-namespace:MultiTerm.Core.Types;assembly=MultiTerm.Core" xmlns:settings_view="clr-namespace:MultiTerm.Wpf.View.SettingsView" xmlns:custom_controls="clr-namespace:MultiTerm.Wpf.CustomControl;assembly=MultiTerm.Wpf.CustomControl" @@ -54,6 +55,9 @@ + + + diff --git a/MultiTerm.Wpf/View/SettingsView/NetworkSettingsView.xaml b/MultiTerm.Wpf/View/SettingsView/NetworkSettingsView.xaml index 03b3393..72eed13 100644 --- a/MultiTerm.Wpf/View/SettingsView/NetworkSettingsView.xaml +++ b/MultiTerm.Wpf/View/SettingsView/NetworkSettingsView.xaml @@ -47,26 +47,27 @@ - - - - - - - - + + + + + + + + + diff --git a/MultiTerm.Wpf/View/SettingsView/TcpClientSettingsView.xaml b/MultiTerm.Wpf/View/SettingsView/TcpClientSettingsView.xaml new file mode 100644 index 0000000..9473e8d --- /dev/null +++ b/MultiTerm.Wpf/View/SettingsView/TcpClientSettingsView.xaml @@ -0,0 +1,11 @@ + + + diff --git a/MultiTerm.Wpf/View/SettingsView/TcpClientSettingsView.xaml.cs b/MultiTerm.Wpf/View/SettingsView/TcpClientSettingsView.xaml.cs new file mode 100644 index 0000000..3f307e5 --- /dev/null +++ b/MultiTerm.Wpf/View/SettingsView/TcpClientSettingsView.xaml.cs @@ -0,0 +1,11 @@ +using System.Windows.Controls; + +namespace MultiTerm.Wpf.View.SettingsView; + +public partial class TcpClientSettingsView : UserControl +{ + public TcpClientSettingsView() + { + InitializeComponent(); + } +}