implemented serial protocol,

made changes to CommunicationProtocol,
added ILibraryEquivalentConverter to convert library types to local types,
added Messenger to App,
implemented UserInterfaceMessages
master
Jonas Arnold 3 years ago
parent 8393a41cea
commit 585ce8bebd
  1. 18
      Common/Messaging/IUserInterfaceMessage.cs
  2. 28
      Common/Messaging/MessageImportance.cs
  3. 27
      MultiTerm.Core/ViewModel/ShellViewModel.cs
  4. 6
      MultiTerm.Core/ViewModel/TerminalViewModel.cs
  5. 17
      MultiTerm.Protocols/CommunicationProtocol.cs
  6. 8
      MultiTerm.Protocols/ICommunicationProtocol.cs
  7. 23
      MultiTerm.Protocols/ILibraryEquivalentConverter.cs
  8. 3
      MultiTerm.Protocols/IProtocolSettings.cs
  9. 3
      MultiTerm.Protocols/IProtocolSettingsViewModel.cs
  10. 1
      MultiTerm.Protocols/MultiTerm.Protocols.csproj
  11. 12
      MultiTerm.Protocols/ProtocolSettingsViewModel.cs
  12. 74
      MultiTerm.Protocols/Serial/Handshake.cs
  13. 30
      MultiTerm.Protocols/Serial/ISerialProtocolSettings.cs
  14. 56
      MultiTerm.Protocols/Serial/Parity.cs
  15. 76
      MultiTerm.Protocols/Serial/SerialProtocol.cs
  16. 46
      MultiTerm.Protocols/Serial/SerialProtocolSettingsViewModel.cs
  17. 44
      MultiTerm.Protocols/Serial/StopBits.cs
  18. 22
      MultiTerm.Protocols/Types/ProtocolSettingsInvalidMessage.cs
  19. 2
      MultiTerm.Wpf/App.xaml.cs
  20. 33
      MultiTerm.Wpf/ValueConverters/MessageImportanceToBrushConverter.cs
  21. 9
      MultiTerm.Wpf/View/ShellView.xaml

@ -0,0 +1,18 @@
namespace Common.Messaging;
/// <summary>
/// Interface represents a user interface message.
/// </summary>
public interface IUserInterfaceMessage
{
/// <summary>
/// Converst message to string that is displayed to user.
/// </summary>
/// <returns>string to be displayed to user</returns>
string ToString();
/// <summary>
/// Defines how prominent the message shall be displayed to the user.
/// </summary>
MessageImportance Importance { get; }
}

@ -0,0 +1,28 @@
namespace Common.Messaging;
public enum MessageImportance
{
/// <summary>
/// Normal importance.
/// Message is displayed but user might not recognize it immediately.
/// </summary>
Normal,
/// <summary>
/// Medium importance.
/// Message is displayed so user should recognizes it in a matter of seconds.
/// </summary>
Medium,
/// <summary>
/// High importance.
/// Message is prominently displayed so user should recognize it immediately.
/// </summary>
High,
/// <summary>
/// High importance.
/// User is interrupted with this very prominently displayed message and needs to confirm the message.
/// </summary>
HighAndRequiresConfirmation
}

@ -7,10 +7,12 @@ using MultiTerm.Core.Factories;
using System.Collections.ObjectModel; using System.Collections.ObjectModel;
using MultiTerm.Core.Types; using MultiTerm.Core.Types;
using MultiTerm.Protocols.Types; using MultiTerm.Protocols.Types;
using CommunityToolkit.Mvvm.Messaging;
using Common.Messaging;
namespace MultiTerm.Core.ViewModel; namespace MultiTerm.Core.ViewModel;
public partial class ShellViewModel : ObservableObject public partial class ShellViewModel : ObservableObject, IRecipient<IUserInterfaceMessage>
{ {
private const string defaultReceiveNewlineSeparatorAppSettingsKey = "DefaultReceiveNewlineSeparator"; private const string defaultReceiveNewlineSeparatorAppSettingsKey = "DefaultReceiveNewlineSeparator";
private const string defaultSendNewlineSeparatorAppSettingsKey = "DefaultSendNewlineSeparator"; private const string defaultSendNewlineSeparatorAppSettingsKey = "DefaultSendNewlineSeparator";
@ -36,19 +38,32 @@ public partial class ShellViewModel : ObservableObject
private TerminalViewType selectedTerminalViewType = TerminalViewType.SendReceive; private TerminalViewType selectedTerminalViewType = TerminalViewType.SendReceive;
#endregion #endregion
#region Status Bar
[ObservableProperty]
private string statusBarMessage = "";
[ObservableProperty]
private MessageImportance statusBarMessageImportance = MessageImportance.Normal;
#endregion
private readonly ITerminalViewModelFactory terminalViewModelFactory; private readonly ITerminalViewModelFactory terminalViewModelFactory;
private readonly ILogger logger; private readonly ILogger logger;
private readonly IAppSettingsProvider appSettings; private readonly IAppSettingsProvider appSettings;
private readonly IMessenger messenger;
public ShellViewModel(ITerminalViewModelFactory terminalViewModelFactory, ILogger logger, IAppSettingsProvider appSettings) public ShellViewModel(ITerminalViewModelFactory terminalViewModelFactory, ILogger logger, IAppSettingsProvider appSettings, IMessenger messenger)
{ {
this.terminalViewModelFactory = terminalViewModelFactory; this.terminalViewModelFactory = terminalViewModelFactory;
this.logger = logger; this.logger = logger;
this.appSettings = appSettings; this.appSettings = appSettings;
this.messenger = messenger;
// intialize values from app settings // intialize values from app settings
this.LoadFromAppSettings(); this.LoadFromAppSettings();
// register to messages
this.messenger.Register(this);
// TEMP Init // TEMP Init
this.AppendTerminalWithSelectedViewType(ProtocolType.Serial); this.AppendTerminalWithSelectedViewType(ProtocolType.Serial);
} }
@ -110,4 +125,12 @@ public partial class ShellViewModel : ObservableObject
this.appSettings.WriteSetting(defaultSendNewlineSeparatorAppSettingsKey, value.ToString()); this.appSettings.WriteSetting(defaultSendNewlineSeparatorAppSettingsKey, value.ToString());
} }
#endregion #endregion
#region Messaging handling
public void Receive(IUserInterfaceMessage message)
{
this.StatusBarMessage = message.ToString();
this.StatusBarMessageImportance = message.Importance;
}
#endregion
} }

@ -26,7 +26,7 @@ public abstract partial class TerminalViewModel : ObservableObject, ITerminalVie
// register event handler for connection request from viewmodel // register event handler for connection request from viewmodel
if(value != null) if(value != null)
{ {
protocolSettings!.ConnectRequested += OnViewModelRequestedConnect; protocolSettings!.ConnectRequested += OnViewModelRequestedConnect; ;
protocolSettings!.DisconnectRequested += OnViewModelRequestedDisconnect; protocolSettings!.DisconnectRequested += OnViewModelRequestedDisconnect;
} }
} }
@ -49,9 +49,9 @@ public abstract partial class TerminalViewModel : ObservableObject, ITerminalVie
} }
} }
private void OnViewModelRequestedConnect(object? sender, EventArgs e) private void OnViewModelRequestedConnect(object? sender, IProtocolSettings e)
{ {
this.CommunicationProtocol?.Connect(); this.CommunicationProtocol?.Connect(e);
} }
private void OnViewModelRequestedDisconnect(object? sender, EventArgs e) private void OnViewModelRequestedDisconnect(object? sender, EventArgs e)

@ -1,4 +1,5 @@
using Common.Logging; using Common.Logging;
using CommunityToolkit.Mvvm.Messaging;
using MultiTerm.Protocols.Types; using MultiTerm.Protocols.Types;
namespace MultiTerm.Protocols; namespace MultiTerm.Protocols;
@ -9,7 +10,6 @@ public abstract class CommunicationProtocol : ICommunicationProtocol
private CancellationTokenSource cancellationTokenSource; private CancellationTokenSource cancellationTokenSource;
private Thread? readingThread; private Thread? readingThread;
public IProtocolSettings? Settings { get; set; }
public event EventHandler<ReceivedDataEventArgs>? ReceivedDataEvent; public event EventHandler<ReceivedDataEventArgs>? ReceivedDataEvent;
public event EventHandler<SentDataEventArgs>? SentDataEvent; public event EventHandler<SentDataEventArgs>? SentDataEvent;
@ -48,8 +48,9 @@ public abstract class CommunicationProtocol : ICommunicationProtocol
/// <summary> /// <summary>
/// Allows to connect to the selected device using the implemented protocol. /// Allows to connect to the selected device using the implemented protocol.
/// </summary> /// </summary>
/// <param name="settings">protocol settings required to connect</param>
/// <returns>true on success</returns> /// <returns>true on success</returns>
protected abstract bool InternalConnect(); protected abstract bool InternalConnect(IProtocolSettings settings);
/// <summary> /// <summary>
/// Reads from the connected device in an endless loop. /// Reads from the connected device in an endless loop.
@ -58,9 +59,17 @@ public abstract class CommunicationProtocol : ICommunicationProtocol
/// <param name="ct">cancellation token</param> /// <param name="ct">cancellation token</param>
protected abstract void InternalRead(CancellationToken ct); protected abstract void InternalRead(CancellationToken ct);
public void Connect() public void Connect(IProtocolSettings settings)
{ {
if (this.InternalConnect()) // check if settings are valid, cancel if not
if (settings.AreValid() == false)
{
this.logger.LogError($"'{nameof(Connect)}()' failed since the provided protocol settings are invalid", nameof(CommunicationProtocol));
return;
}
// try connecting if serttings are valid
if (this.InternalConnect(settings))
{ {
// renew token source // renew token source
this.cancellationTokenSource = new CancellationTokenSource(); this.cancellationTokenSource = new CancellationTokenSource();

@ -12,11 +12,6 @@ public interface ICommunicationProtocol
/// </summary> /// </summary>
ProtocolType ProtocolType { get; } ProtocolType ProtocolType { get; }
/// <summary>
/// Contains settings that are required to connect and use this communication protocol.
/// </summary>
IProtocolSettings? Settings { get; }
/// <summary> /// <summary>
/// New data received from connected device. /// New data received from connected device.
/// </summary> /// </summary>
@ -30,7 +25,8 @@ public interface ICommunicationProtocol
/// <summary> /// <summary>
/// Connect to the device. /// Connect to the device.
/// </summary> /// </summary>
void Connect(); /// <param name="settings">settings required to connect and use the protocol</param>
void Connect(IProtocolSettings settings);
/// <summary> /// <summary>
/// Disconnect from the device. Ends all internal activities. /// Disconnect from the device. Ends all internal activities.

@ -0,0 +1,23 @@
namespace MultiTerm.Protocols;
/// <summary>
/// Interface for a converter that converts a facade type into the equivalent of the related software library.
/// </summary>
/// <typeparam name="T_local">type of the local class</typeparam>
/// <typeparam name="T_library">type of the library class</typeparam>
internal interface ILibraryEquivalentConverter<T_local, T_library>
{
/// <summary>
/// Convert the object to the library type.
/// </summary>
/// <param name="obj">object of local class</param>
/// <returns>object of class within library</returns>
T_library ConvertToLibraryType(T_local obj);
/// <summary>
/// Convert the object to the local type.
/// </summary>
/// <param name="obj">object of class within library</param>
/// <returns>object of local class</returns>
T_local ConvertToLocalType(T_library obj);
}

@ -15,5 +15,6 @@ public interface IProtocolSettings
/// <summary> /// <summary>
/// Checks if the entered settings are valid and a connection can be made. /// Checks if the entered settings are valid and a connection can be made.
/// </summary> /// </summary>
void AreValid(); /// <returns>true if the settings are valid</returns>
bool AreValid();
} }

@ -11,8 +11,9 @@ public interface IProtocolSettingsViewModel
/// <summary> /// <summary>
/// Event that is thrown when the user requested to connect to the device with the entered settings. /// Event that is thrown when the user requested to connect to the device with the entered settings.
/// Provides protocol settings to use for connection.
/// </summary> /// </summary>
event EventHandler? ConnectRequested; event EventHandler<IProtocolSettings> ConnectRequested;
/// <summary> /// <summary>
/// Event that is thrown when the user requested to disconnect from the device. /// Event that is thrown when the user requested to disconnect from the device.

@ -8,6 +8,7 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.1.0" /> <PackageReference Include="CommunityToolkit.Mvvm" Version="8.1.0" />
<PackageReference Include="SerialPortStream" Version="2.4.1" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

@ -1,5 +1,6 @@
using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input; using CommunityToolkit.Mvvm.Input;
using CommunityToolkit.Mvvm.Messaging;
using MultiTerm.Protocols.Types; using MultiTerm.Protocols.Types;
namespace MultiTerm.Protocols; namespace MultiTerm.Protocols;
@ -7,13 +8,15 @@ namespace MultiTerm.Protocols;
/// <summary> /// <summary>
/// Class that represents all connection and usage settings for a specific <see cref="CommunicationProtocol"/>. /// Class that represents all connection and usage settings for a specific <see cref="CommunicationProtocol"/>.
/// Can be bound to UI using MVVM pattern. /// Can be bound to UI using MVVM pattern.
/// Implementing class must also implement <see cref="IProtocolSettings"/>! Otherwise <see cref="ConnectDisconnectCommand"/> does not work.
/// </summary> /// </summary>
public abstract partial class ProtocolSettingsViewModel : ObservableObject, IProtocolSettingsViewModel public abstract partial class ProtocolSettingsViewModel : ObservableObject, IProtocolSettingsViewModel
{ {
private const string connectedStateButtonText = "Disconnect"; private const string connectedStateButtonText = "Disconnect";
private const string disconnectedStateButtonText = "Connect"; private const string disconnectedStateButtonText = "Connect";
protected readonly IMessenger messenger;
public event EventHandler? ConnectRequested; public event EventHandler<IProtocolSettings>? ConnectRequested;
public event EventHandler? DisconnectRequested; public event EventHandler? DisconnectRequested;
public abstract ProtocolType ProtocolType { get; } public abstract ProtocolType ProtocolType { get; }
@ -27,10 +30,15 @@ public abstract partial class ProtocolSettingsViewModel : ObservableObject, IPro
[ObservableProperty] [ObservableProperty]
private string connectDisconnectButtonText = disconnectedStateButtonText; private string connectDisconnectButtonText = disconnectedStateButtonText;
public ProtocolSettingsViewModel(IMessenger messenger)
{
this.messenger = messenger;
}
/// <summary> /// <summary>
/// Binding to Connect/Disconnect button. /// Binding to Connect/Disconnect button.
/// Button text is provided in <see cref="ConnectDisconnectButtonText"/> /// Button text is provided in <see cref="ConnectDisconnectButtonText"/>
/// Implementing class must also implement <see cref="IProtocolSettings"/>! Otherwise <see cref="ConnectDisconnectCommand"/> does not work.
/// </summary> /// </summary>
[RelayCommand(AllowConcurrentExecutions = false)] [RelayCommand(AllowConcurrentExecutions = false)]
private async Task ConnectDisconnectAsync() private async Task ConnectDisconnectAsync()
@ -42,7 +50,7 @@ public abstract partial class ProtocolSettingsViewModel : ObservableObject, IPro
{ {
// CONNECT // CONNECT
this.AreEditable = false; this.AreEditable = false;
this.ConnectRequested?.Invoke(this, EventArgs.Empty); this.ConnectRequested?.Invoke(this, (IProtocolSettings)this); // implementing class must also implement IProtocolSettings!
this.ConnectDisconnectButtonText = connectedStateButtonText; this.ConnectDisconnectButtonText = connectedStateButtonText;
} }
// if currently connected // if currently connected

@ -0,0 +1,74 @@
using System.ComponentModel;
using Library = RJCP.IO.Ports;
namespace MultiTerm.Protocols.Serial;
public enum Handshake
{
//
// Summary:
// No handshaking.
[Description("None")]
None = 0x0,
//
// Summary:
// Software handshaking.
[Description("XON")]
XOn = 0x1,
//
// Summary:
// Hardware handshaking (RTS/CTS).
[Description("RTS")]
Rts = 0x2,
//
// Summary:
// Hardware handshaking (DTR/DSR) (uncommon).
[Description("DTR")]
Dtr = 0x4,
//
// Summary:
// RTS and Software handshaking.
[Description("RTS XON")]
RtsXOn = 0x3,
//
// Summary:
// DTR and Software handshaking (uncommon).
[Description("DTR XON")]
DtrXOn = 0x5,
//
// Summary:
// Hardware handshaking with RTS/CTS and DTR/DSR (uncommon).
[Description("DTR RTS")]
DtrRts = 0x6,
//
// Summary:
// Hardware handshaking with RTS/CTS and DTR/DSR and Software handshaking (uncommon).
[Description("DTR RTS XON")]
DtrRtsXOn = 0x7
}
internal class HandshakeLibraryEquivalentConverter : ILibraryEquivalentConverter<Handshake, Library.Handshake>
{
public Library.Handshake ConvertToLibraryType(Handshake obj)
{
return obj switch
{
Handshake.None => Library.Handshake.None,
Handshake.XOn => Library.Handshake.XOn,
Handshake.Rts => Library.Handshake.Rts,
Handshake.Dtr => Library.Handshake.Dtr,
Handshake.RtsXOn => Library.Handshake.RtsXOn,
Handshake.DtrXOn => Library.Handshake.DtrXOn,
Handshake.DtrRts => Library.Handshake.DtrRts,
Handshake.DtrRtsXOn => Library.Handshake.DtrRtsXOn,
_ => throw new NotImplementedException(),
};
}
public Handshake ConvertToLocalType(Library.Handshake obj)
{
throw new NotImplementedException();
}
}

@ -2,6 +2,34 @@
public interface ISerialProtocolSettings : IProtocolSettings public interface ISerialProtocolSettings : IProtocolSettings
{ {
//TODO /// <summary>
/// Port for communication, e.g. COM3 or com50.
/// </summary>
string PortName { get; set; }
/// <summary>
/// Serial Baud rate.
/// </summary>
int BaudRate { get; set; } int BaudRate { get; set; }
/// <summary>
/// The type of parity to use.
/// </summary>
Parity Parity { get; set; }
/// <summary>
/// The standard length of data bits per byte.
/// </summary>
int DataBits { get; set; }
/// <summary>
/// Number of stop bits to use.
/// </summary>
StopBits StopBits { get; set; }
/// <summary>
/// Handshaking protocol for serial port transmission of data.
/// </summary>
Handshake Handshake { get; set; }
} }

@ -0,0 +1,56 @@
using System.ComponentModel;
using Library = RJCP.IO.Ports;
namespace MultiTerm.Protocols.Serial;
public enum Parity
{
//
// Summary:
// No parity.
[Description("None")]
None,
//
// Summary:
// Odd parity.
[Description("Odd")]
Odd,
//
// Summary:
// Even parity.
[Description("Even")]
Even,
//
// Summary:
// Mark parity.
[Description("Mark")]
Mark,
//
// Summary:
// Space parity.
[Description("Space")]
Space
}
internal class ParityLibraryEquivalentConverter : ILibraryEquivalentConverter<Parity, Library.Parity>
{
public Library.Parity ConvertToLibraryType(Parity obj)
{
return obj switch
{
Parity.None => Library.Parity.None,
Parity.Odd => Library.Parity.Odd,
Parity.Even => Library.Parity.Even,
Parity.Mark => Library.Parity.Mark,
Parity.Space => Library.Parity.Space,
_ => throw new NotImplementedException(),
};
}
public Parity ConvertToLocalType(Library.Parity obj)
{
throw new NotImplementedException();
}
}

@ -1,34 +1,96 @@
using Common.Logging; using Common.Logging;
using MultiTerm.Protocols.Model;
using MultiTerm.Protocols.Types; using MultiTerm.Protocols.Types;
using RJCP.IO.Ports;
using System.Text;
namespace MultiTerm.Protocols.Serial; namespace MultiTerm.Protocols.Serial;
public class SerialProtocol : CommunicationProtocol public class SerialProtocol : CommunicationProtocol
{ {
public override ProtocolType ProtocolType => ProtocolType.Serial; public override ProtocolType ProtocolType => ProtocolType.Serial;
private ISerialProtocolSettings? serialSettings;
private SerialPortStream serialPort = new();
public SerialProtocol(ILogger logger) : base(logger)
{
public SerialProtocol(ILogger logger) : base(logger) { }
protected override bool InternalConnect(IProtocolSettings protocolSettings)
{
// check if settings are of correct type
if (protocolSettings is not ISerialProtocolSettings serialProtocolSettings)
{
this.serialSettings = null;
throw new ArgumentException($"Cannot connect due to wrong type of Protocol Settings. " +
$"Check parameter {nameof(protocolSettings)}' of '{nameof(InternalConnect)}()' in {nameof(SerialProtocol)}.");
} }
protected override bool InternalConnect() // store locally
this.serialSettings = serialProtocolSettings;
// create new serial port
this.serialPort = new()
{ {
throw new NotImplementedException(); // apply user settings, where needed converters are used
PortName = this.serialSettings.PortName,
BaudRate = this.serialSettings.BaudRate,
DataBits = this.serialSettings.DataBits,
Parity = new ParityLibraryEquivalentConverter().ConvertToLibraryType(this.serialSettings.Parity),
StopBits = new StopBitsLibraryEquivalentConverter().ConvertToLibraryType(this.serialSettings.StopBits),
Handshake = new HandshakeLibraryEquivalentConverter().ConvertToLibraryType(this.serialSettings.Handshake),
// define static settings
Encoding = Encoding.Unicode,
ReadTimeout = 500,
WriteTimeout = 500
};
// try opening serial port
try
{
serialPort.Open();
}
catch (Exception ex)
{
this.logger.LogException(ex, $"'{nameof(InternalConnect)}()'Opening serial port failed:", nameof(SerialProtocol));
return false;
}
return true;
} }
protected override void InternalDisconnect() protected override void InternalDisconnect()
{ {
throw new NotImplementedException(); serialPort.Close();
this.serialSettings = null;
} }
protected override void InternalRead(CancellationToken ct) protected override void InternalRead(CancellationToken ct)
{ {
throw new NotImplementedException(); while(ct.IsCancellationRequested == false)
{
// reads character based on configured encoding (here Unicode)
int readCharacter = serialPort.ReadChar();
if (readCharacter != -1) // -1 = timeout
{
// create extended char type
var character = new ExtendedChar((char)readCharacter);
// report new data with event
this.OnReceivedData(new ReceivedDataEventArgs(new ExtendedChar[] { character }));
}
}
} }
protected override void InternalSendBytes(byte[] bytes) protected override void InternalSendBytes(byte[] bytes)
{ {
throw new NotImplementedException(); foreach (byte b in bytes)
{
serialPort.WriteByte(b);
}
}
public static IEnumerable<string> GetPortNames()
{
return SerialPortStream.GetPortNames();
} }
} }

@ -1,4 +1,6 @@
using CommunityToolkit.Mvvm.ComponentModel; using Common.Messaging;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Messaging;
using MultiTerm.Protocols.Types; using MultiTerm.Protocols.Types;
namespace MultiTerm.Protocols.Serial; namespace MultiTerm.Protocols.Serial;
@ -8,16 +10,50 @@ public partial class SerialProtocolSettingsViewModel : ProtocolSettingsViewModel
public override ProtocolType ProtocolType => ProtocolType.Serial; public override ProtocolType ProtocolType => ProtocolType.Serial;
[ObservableProperty] [ObservableProperty]
private int baudRate; private int baudRate = 115200;
//public int BaudRate { get => throw new NotImplementedException(); set => throw new NotImplementedException(); }
[ObservableProperty]
private string portName = "";
[ObservableProperty]
private Parity parity = Parity.None;
[ObservableProperty]
private int dataBits = 8;
[ObservableProperty]
private StopBits stopBits = StopBits.One;
[ObservableProperty]
private Handshake handshake = Handshake.None;
// TODO
public string NewlineSequenceOnSend => throw new NotImplementedException(); public string NewlineSequenceOnSend => throw new NotImplementedException();
public string NewlineOnReceivedSequence => throw new NotImplementedException(); public string NewlineOnReceivedSequence => throw new NotImplementedException();
public void AreValid() public SerialProtocolSettingsViewModel(IMessenger messenger) : base(messenger) { }
public bool AreValid()
{
if (String.IsNullOrEmpty(this.PortName))
{
this.messenger.Send<IUserInterfaceMessage>(new ProtocolSettingsInvalidMessage(nameof(this.PortName), "Null or empty"));
return false;
}
if (this.PortName.ToLower().StartsWith("com") == false)
{ {
throw new NotImplementedException(); this.messenger.Send<IUserInterfaceMessage>(new ProtocolSettingsInvalidMessage(nameof(this.PortName), "Must start with COM"));
return false;
}
if (this.DataBits < 5 && this.DataBits > 8 && this.DataBits != 16)
{
this.messenger.Send<IUserInterfaceMessage>(new ProtocolSettingsInvalidMessage(nameof(this.DataBits), "Must be 5...8 or 16"));
return false;
}
return true;
} }
} }

@ -0,0 +1,44 @@
using System.ComponentModel;
using Library = RJCP.IO.Ports;
namespace MultiTerm.Protocols.Serial;
public enum StopBits
{
//
// Summary:
// One stop bit.
[Description("1")]
One,
//
// Summary:
// 1.5 stop bits.
[Description("1.5")]
One5,
//
// Summary:
// Two stop bits.
[Description("2")]
Two
}
internal class StopBitsLibraryEquivalentConverter : ILibraryEquivalentConverter<StopBits, Library.StopBits>
{
public Library.StopBits ConvertToLibraryType(StopBits obj)
{
return obj switch
{
StopBits.One => Library.StopBits.One,
StopBits.One5 => Library.StopBits.One5,
StopBits.Two => Library.StopBits.Two,
_ => throw new NotImplementedException(),
};
}
public StopBits ConvertToLocalType(Library.StopBits obj)
{
throw new NotImplementedException();
}
}

@ -0,0 +1,22 @@
using Common.Messaging;
namespace MultiTerm.Protocols.Types;
public sealed class ProtocolSettingsInvalidMessage : IUserInterfaceMessage
{
public MessageImportance Importance => MessageImportance.Medium;
public string SettingName { get; }
public string Message { get; }
public ProtocolSettingsInvalidMessage(string settingName, string message)
{
this.SettingName = settingName;
this.Message = message;
}
public override string ToString()
{
return $"Protocol setting '{this.SettingName}' is invalid: {this.Message}";
}
}

@ -8,6 +8,7 @@ using System;
using System.Threading.Tasks; using System.Threading.Tasks;
using Common.AppSettings; using Common.AppSettings;
using MultiTerm.Protocols.Helpers; using MultiTerm.Protocols.Helpers;
using CommunityToolkit.Mvvm.Messaging;
namespace MultiTerm.Wpf; namespace MultiTerm.Wpf;
@ -26,6 +27,7 @@ public partial class App : Application
services.AddSingleton<MainWindow>(); services.AddSingleton<MainWindow>();
services.AddSingleton<ILogger>(new SerilogLogger("C:/log/multiterm-log-.txt", true)); services.AddSingleton<ILogger>(new SerilogLogger("C:/log/multiterm-log-.txt", true));
services.AddSingleton<IAppSettingsProvider>(new XmlAppSettingsProvider("C:/log/multiterm-config.xml")); services.AddSingleton<IAppSettingsProvider>(new XmlAppSettingsProvider("C:/log/multiterm-config.xml"));
services.AddSingleton<IMessenger, WeakReferenceMessenger>();
// viewmodels // viewmodels
services.AddSingleton<ShellViewModel>(); services.AddSingleton<ShellViewModel>();

@ -0,0 +1,33 @@
using Common.Messaging;
using System;
using System.Globalization;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Media;
namespace MultiTerm.Wpf.ValueConverters;
[ValueConversion(typeof(MessageImportance), typeof(Brush))]
internal class MessageImportanceToBrushConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if(value is not MessageImportance msgImportance)
{ throw new ArgumentException($"Wrong object provided, can only convert from type {nameof(MessageImportance)}"); }
return msgImportance switch
{
MessageImportance.Normal => Brushes.Black,
MessageImportance.Medium => Brushes.DarkSalmon,
MessageImportance.High => Brushes.Red,
MessageImportance.HighAndRequiresConfirmation => throw new NotImplementedException(),
_ => throw new NotImplementedException(),
};
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}

@ -17,6 +17,7 @@
<UserControl.Resources> <UserControl.Resources>
<!-- Value Converters --> <!-- Value Converters -->
<conv:EnumDescriptionToMenuItemConverter x:Key="EnumDescriptionConverter"/> <conv:EnumDescriptionToMenuItemConverter x:Key="EnumDescriptionConverter"/>
<conv:MessageImportanceToBrushConverter x:Key="MsgImportanceBrushConverter"/>
<!-- Data Sources --> <!-- Data Sources -->
<ObjectDataProvider x:Key="NewlineSeparatorTypeValues" <ObjectDataProvider x:Key="NewlineSeparatorTypeValues"
@ -71,6 +72,14 @@
<MenuItem Header="_About"/> <MenuItem Header="_About"/>
</Menu> </Menu>
<!-- Bottom status bar with separator -->
<StatusBar DockPanel.Dock="Bottom">
<StatusBarItem>
<TextBlock Text="{Binding StatusBarMessage}" Foreground="{Binding Path=StatusBarMessageImportance, Converter={StaticResource MsgImportanceBrushConverter}}"/>
</StatusBarItem>
</StatusBar>
<Separator DockPanel.Dock="Bottom" Margin="0 5 0 5"/>
<!-- Left Panel that holds History and Command Sets --> <!-- Left Panel that holds History and Command Sets -->
<StackPanel Orientation="Vertical" DockPanel.Dock="Left" Width="150"> <StackPanel Orientation="Vertical" DockPanel.Dock="Left" Width="150">

Loading…
Cancel
Save