changed IsConnected boolean to State of Enum ProtocolConnectionState,

changed DisconnectedEvent to ConnectionStateChanged event,
fixed exception while unintentional disconnect in SerialProtocol,
implemented connection state indicator in tab header,
resolved warnings
master
Jonas Arnold 3 years ago
parent eac4757427
commit 92beb2ec45
  1. 27
      MultiTerm.Core/ViewModel/TerminalViewModel.cs
  2. 34
      MultiTerm.Protocols/CommunicationProtocol.cs
  3. 11
      MultiTerm.Protocols/ICommunicationProtocol.cs
  4. 17
      MultiTerm.Protocols/Serial/SerialProtocol.cs
  5. 24
      MultiTerm.Protocols/Types/ProtocolConnectionState.cs
  6. 4
      MultiTerm.Protocols/UsbHid/UsbHidProtocol.cs
  7. 1
      MultiTerm.Wpf.CustomControl/MultiFormatDataView/MultiFormatDataView.cs
  8. 34
      MultiTerm.Wpf/ValueConverters/ProtocolConnectionStateToBrushConverter.cs
  9. 10
      MultiTerm.Wpf/View/ShellView.xaml

@ -57,6 +57,7 @@ public abstract partial class TerminalViewModel : ObservableObject, ITerminalVie
}
#endregion
#region Observable Properties
/// <summary>
/// Holds communication data that was received via the communication protocol.
/// </summary>
@ -87,6 +88,15 @@ public abstract partial class TerminalViewModel : ObservableObject, ITerminalVie
[ObservableProperty]
private IProtocolSettingsViewModel? protocolSettings;
/// <summary>
/// <see cref="ProtocolConnectionState"/> of <see cref="CommunicationProtocol"/> as ObservableProperty.
/// Required since <see cref="CommunicationProtocol"/> does not implement <see cref="ObservableObject"/>.
/// </summary>
[ObservableProperty]
private ProtocolConnectionState communicationProtocolConnectionState;
#endregion
public TerminalViewModel(IAppSettingsProvider appSettings, IMessenger messenger, IContext context)
{
@ -108,7 +118,7 @@ public abstract partial class TerminalViewModel : ObservableObject, ITerminalVie
// unsubscribe events
this.CommunicationProtocol.ReceivedDataEvent -= CommunicationProtocol_ReceivedDataEvent;
this.CommunicationProtocol.SentDataEvent -= CommunicationProtocol_SentDataEvent;
this.CommunicationProtocol.DisconnectedEvent -= CommunicationProtocol_DisconnectedEvent;
this.CommunicationProtocol.ConnectionStateChangedEvent -= CommunicationProtocol_ConnectionStateChangedEvent;
// remove reference to object
this.CommunicationProtocol = null;
}
@ -126,7 +136,7 @@ public abstract partial class TerminalViewModel : ObservableObject, ITerminalVie
{
this.CommunicationProtocol.ReceivedDataEvent += CommunicationProtocol_ReceivedDataEvent;
this.CommunicationProtocol.SentDataEvent += CommunicationProtocol_SentDataEvent;
this.CommunicationProtocol.DisconnectedEvent += CommunicationProtocol_DisconnectedEvent;
this.CommunicationProtocol.ConnectionStateChangedEvent += CommunicationProtocol_ConnectionStateChangedEvent;
}
// initializes default newline separators, updates them directly inside ReceivedData and SentData objects
@ -153,11 +163,14 @@ public abstract partial class TerminalViewModel : ObservableObject, ITerminalVie
this.SentData.HandleNewData(e.SentData);
}
private void CommunicationProtocol_DisconnectedEvent(object? sender, DisconnectedEventArgs e)
private void CommunicationProtocol_ConnectionStateChangedEvent(object? sender, ProtocolConnectionState e)
{
// on unintentional disconnect the view model may still display connected => force to disconnected state
// update internal state
this.CommunicationProtocolConnectionState = e;
// If ViewModel still displays connected after unintentional disconnect => force to disconnected state
// allows user to change settings
if(e.Unintentional == true && this.ProtocolSettings!.DisplaysConnected == true)
if (e == ProtocolConnectionState.UnintentionallyDisconnected && this.ProtocolSettings!.DisplaysConnected == true)
{
this.ProtocolSettings.ForceConnectedState(false);
}
@ -177,9 +190,9 @@ public abstract partial class TerminalViewModel : ObservableObject, ITerminalVie
if(this.CommunicationProtocol == null) { throw new NullReferenceException($"'{nameof(SendToCommunicationProtocol)}()' was called but {nameof(CommunicationProtocol)} is null."); }
// inform user and quit if communication protocol is not connected
if (this.CommunicationProtocol.IsConnected == false)
if (this.CommunicationProtocol.State != ProtocolConnectionState.Connected)
{
this.messenger.Send<IUserInterfaceMessage>(new ProtocolNotConnectedMessage("Cannot send message"));
this.messenger.Send<IUserInterfaceMessage>(new ProtocolNotConnectedMessage("Cannot send message, Protocol is not connected."));
return false;
}

@ -19,12 +19,21 @@ public abstract class CommunicationProtocol : ICommunicationProtocol
public event EventHandler<ReceivedDataEventArgs>? ReceivedDataEvent;
public event EventHandler<SentDataEventArgs>? SentDataEvent;
public event EventHandler<DisconnectedEventArgs>? DisconnectedEvent;
public event EventHandler<ProtocolConnectionState>? ConnectionStateChangedEvent;
public abstract ProtocolType ProtocolType { get; }
public abstract string InstanceIdentifier { get; protected set; }
public bool IsConnected { get; private set; } = false;
private ProtocolConnectionState state = ProtocolConnectionState.NotConnected;
public ProtocolConnectionState State
{
get { return state; }
set
{
state = value;
this.ConnectionStateChangedEvent?.Invoke(this, state);
}
}
public CommunicationProtocol(ILogger logger, IMessenger messenger)
{
@ -52,14 +61,11 @@ public abstract class CommunicationProtocol : ICommunicationProtocol
// perform all steps in a task, to prevent deadlock when this method is called from a thread that is joined.
Task.Run(() =>
{
// update state
this.IsConnected = false;
this.CancelThreads();
this.logger.LogError($"'{nameof(OnUnintentionallyDisconnected)}()' called.", nameof(CommunicationProtocol));
// raise event indicating an unintentional disconnect
this.DisconnectedEvent?.Invoke(this, new DisconnectedEventArgs(true));
// update state
this.State = ProtocolConnectionState.UnintentionallyDisconnected;
});
}
@ -75,9 +81,9 @@ public abstract class CommunicationProtocol : ICommunicationProtocol
bool success = true;
// guard is not connected => log warning. user of this function shall only use SendBytes if IsConnected is true
if (this.IsConnected == false)
if (this.State != ProtocolConnectionState.Connected)
{
this.logger.LogWarn($"'{nameof(SendBytes)}()' was reached with {nameof(IsConnected)} being false", nameof(CommunicationProtocol));
this.logger.LogWarn($"'{nameof(SendBytes)}()' was reached with wrong {nameof(ProtocolConnectionState)} of {this.State}", nameof(CommunicationProtocol));
return false; // return and do not send
}
@ -111,9 +117,9 @@ public abstract class CommunicationProtocol : ICommunicationProtocol
public bool Connect(IProtocolSettings settings)
{
// check if not already connected
if (this.IsConnected == true)
if (this.State == ProtocolConnectionState.Connected)
{
this.logger.LogWarn($"'{nameof(Connect)}()' was reached even if {nameof(IsConnected)} is already true.", nameof(CommunicationProtocol));
this.logger.LogWarn($"'{nameof(Connect)}()' was reached with wrong {nameof(ProtocolConnectionState)} of {this.State}", nameof(CommunicationProtocol));
return true;
}
@ -138,7 +144,7 @@ public abstract class CommunicationProtocol : ICommunicationProtocol
this.readingThread = new Thread(() => this.InternalRead(this.cancellationTokenSource.Token));
this.readingThread.Start();
// update state
this.IsConnected = true;
this.State = ProtocolConnectionState.Connected;
return true;
}
else
@ -156,10 +162,8 @@ public abstract class CommunicationProtocol : ICommunicationProtocol
{
this.CancelThreads();
this.InternalDisconnect();
this.IsConnected = false;
// raise event indicating an intentional disconnect
this.DisconnectedEvent?.Invoke(this, new DisconnectedEventArgs(false));
this.State = ProtocolConnectionState.NotConnected;
}
/// <summary>

@ -28,15 +28,16 @@ public interface ICommunicationProtocol
event EventHandler<SentDataEventArgs>? SentDataEvent;
/// <summary>
/// Thrown when the device disconnected.
/// Reports the state of connection to the communication protocol.
/// This property shall be used to check if the protocol is ready to receive data via <see cref="SendBytes(byte[])"/>.
/// </summary>
event EventHandler<DisconnectedEventArgs>? DisconnectedEvent;
ProtocolConnectionState State { get; }
/// <summary>
/// Indicates wether the communication protocol is connected.
/// True if connected and ready to receive data via <see cref="SendBytes(byte[])"/>, false if not.
/// Raised when the <see cref="State"/> changed.
/// The handed <see cref="ProtocolConnectionState"/> is the new state.
/// </summary>
bool IsConnected { get; }
event EventHandler<ProtocolConnectionState>? ConnectionStateChangedEvent;
/// <summary>
/// Connect to the device.

@ -75,8 +75,21 @@ public class SerialProtocol : CommunicationProtocol
{
while(ct.IsCancellationRequested == false)
{
// reads character based on configured encoding (here ASCII)
int readByte = this.serialPort.ReadByte();
int readByte = -1;
// try reading
try
{
// reads character based on configured encoding (here ASCII)
readByte = this.serialPort.ReadByte();
}
catch (IOException ex) // thrown when a device disconnected
{
this.logger.LogException(ex, $"Exception while reading data in {nameof(InternalRead)}", nameof(SerialProtocol));
this.OnUnintentionallyDisconnected(); // report disconnect
break; // break loop
}
if (readByte != -1) // -1 = end of stream
{
// report new data with event

@ -0,0 +1,24 @@
using System.ComponentModel;
namespace MultiTerm.Protocols.Types;
public enum ProtocolConnectionState
{
/// <summary>
/// Indicates that the protocol is currently not connected or has not yet been connected.
/// </summary>
[Description("Not Connected")]
NotConnected,
/// <summary>
/// Indicates that the protocol is currently connected and ready to transmit/receive data.
/// </summary>
[Description("Connected")]
Connected,
/// <summary>
/// Indicates that the protocol disconnected unintentionally (e.g. device connection was interrupted).
/// </summary>
[Description("Unintentionally Disconnected")]
UnintentionallyDisconnected
}

@ -125,10 +125,10 @@ public class UsbHidProtocol : CommunicationProtocol
// remove from start of original list either; taken amount of bytes or remaining count of bytes in list
bytesToSend.RemoveRange(0, Math.Min(numBytesToTake, bytesToSend.Count));
Debug.WriteLine($"{nameof(InternalSendBytes)}() of {nameof(UsbHidProtocol)} took {nextBytes.Count()} bytes and has {bytesToSend.Count} left on queue.");
Debug.WriteLine($"{nameof(InternalSendBytes)}() of {nameof(UsbHidProtocol)} took {nextBytes.Count} bytes and has {bytesToSend.Count} left on queue.");
// check number of bytes to send
int numBytesToSend = nextBytes.Count();
int numBytesToSend = nextBytes.Count;
if (numBytesToSend > MaxBytesPerReport || numBytesToSend > byte.MaxValue || numBytesToSend <= 0)
{
throw new Exception($"'{nameof(InternalSendBytes)}()': Invalid of bytes to send ({nameof(numBytesToSend)})");

@ -241,7 +241,6 @@ public class MultiFormatDataView : Control
{
var newSelection = new ObservableCollection<ByteDataViewModel>();
int selectionStartIndex = this.tbCharOnlyView!.SelectionStart;
// TEMP OLD
// extract text from the beginning to the start of the selected text
var textFromBeginningToStartOfSelection = this.tbCharOnlyView!.Text.Substring(0, selectionStartIndex);
// count amount of manually introduced newline sequences in this text section (these to not exist in the data source!)

@ -0,0 +1,34 @@
using MultiTerm.Protocols.Types;
using System;
using System.Globalization;
using System.Windows.Data;
using System.Windows.Media;
namespace MultiTerm.Wpf.ValueConverters;
/// <summary>
/// Converts a <see cref="ProtocolConnectionState"/> to a acoordingly colored <see cref="Brush"/>.
/// </summary>
[ValueConversion(typeof(ProtocolConnectionState), typeof(Brush))]
public class ProtocolConnectionStateToBrushConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value is not ProtocolConnectionState protocolConnectionState)
{ throw new ArgumentException($"Wrong object provided, can only convert from type {nameof(ProtocolConnectionState)}"); }
return protocolConnectionState switch
{
ProtocolConnectionState.NotConnected => Brushes.LightGray,
ProtocolConnectionState.Connected => Brushes.LightGreen,
ProtocolConnectionState.UnintentionallyDisconnected => Brushes.OrangeRed,
_ => throw new NotImplementedException(),
};
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}

@ -20,6 +20,7 @@
<conv:MessageImportanceToBrushConverter x:Key="MsgImportanceBrushConverter"/>
<conv:MessageImportanceToFontWeightConverter x:Key="MsgImportanceFontWeightConverter"/>
<conv:ProtocolTypeToIconConverter x:Key="ProtocolTypeIconConverter"/>
<conv:ProtocolConnectionStateToBrushConverter x:Key="PrtclConnectionStateBrushConverter"/>
<!-- Data Sources -->
<ObjectDataProvider x:Key="NewlineSeparatorTypeValues"
@ -144,10 +145,15 @@
<!-- Tab Template -->
<DataTemplate>
<StackPanel Orientation="Horizontal">
<Image Height="20" Stretch="Uniform" Margin="0 0 8 0"
<Ellipse Width="15" Height="15" Margin="0 0 8 0"
Stroke="Black" StrokeThickness="0.7"
Fill="{Binding Path=CommunicationProtocolConnectionState, Converter={StaticResource PrtclConnectionStateBrushConverter}}"
ToolTip="{Binding Path=CommunicationProtocolConnectionState, Converter={StaticResource EnumDescriptionConverter}}"
ToolTipService.InitialShowDelay="200"/>
<Image Height="20" Stretch="Uniform" Margin="0 0 8 0"
Source="{Binding Path=ProtocolType, Converter={StaticResource ProtocolTypeIconConverter}}"
ToolTip="{Binding Path=ProtocolType,Converter={StaticResource EnumDescriptionConverter}}"
ToolTipService.InitialShowDelay="0"/>
ToolTipService.InitialShowDelay="200"/>
<TextBlock Text="{Binding Title, Mode=OneWay}" />
<Button Command="{Binding CloseRequestCommand}" Width="20" Padding="0" Margin="8 0 0 0" Content="X">
<Button.Style>

Loading…
Cancel
Save