fixed clear command,

added buffer and bufferHandlingThread in Communication Protocol to separatly handle data (not slowing down protocol interface)
master
Jonas Arnold 3 years ago
parent df8fdd4a29
commit 8abb83345c
  1. 3
      MultiTerm.Core/ViewModel/CommunicationDataViewModel.cs
  2. 5
      MultiTerm.Core/ViewModel/ICommunicationDataViewModel.cs
  3. 95
      MultiTerm.Protocols/CommunicationProtocol.cs
  4. 18
      MultiTerm.Protocols/Serial/SerialProtocol.cs

@ -1,6 +1,7 @@
using Common; using Common;
using Common.Helpers; using Common.Helpers;
using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using MultiTerm.Core.Types; using MultiTerm.Core.Types;
using MultiTerm.Protocols.Model; using MultiTerm.Protocols.Model;
using System.Collections.ObjectModel; using System.Collections.ObjectModel;
@ -84,7 +85,7 @@ public partial class CommunicationDataViewModel : ObservableObject, ICommunicati
}); });
} }
[RelayCommand]
public void Clear() public void Clear()
{ {
// update collection and string, invoke ui thread if necessary // update collection and string, invoke ui thread if necessary

@ -1,4 +1,5 @@
using MultiTerm.Core.Types; using CommunityToolkit.Mvvm.Input;
using MultiTerm.Core.Types;
using System.Collections.ObjectModel; using System.Collections.ObjectModel;
namespace MultiTerm.Core.ViewModel; namespace MultiTerm.Core.ViewModel;
@ -36,5 +37,5 @@ public interface ICommunicationDataViewModel<T_Data, T_Raw> where T_Data : IData
/// <summary> /// <summary>
/// Allows to clear the data. /// Allows to clear the data.
/// </summary> /// </summary>
void Clear(); IRelayCommand ClearCommand { get; }
} }

@ -1,7 +1,9 @@
using Common.Logging; using Common.Logging;
using Common.Messaging; using Common.Messaging;
using CommunityToolkit.Mvvm.Messaging; using CommunityToolkit.Mvvm.Messaging;
using MultiTerm.Protocols.Model;
using MultiTerm.Protocols.Types; using MultiTerm.Protocols.Types;
using System.Collections.Concurrent;
namespace MultiTerm.Protocols; namespace MultiTerm.Protocols;
@ -11,6 +13,9 @@ public abstract class CommunicationProtocol : ICommunicationProtocol
protected readonly IMessenger messenger; protected readonly IMessenger messenger;
private CancellationTokenSource cancellationTokenSource; private CancellationTokenSource cancellationTokenSource;
private Thread? readingThread; private Thread? readingThread;
private Thread? bufferHandlingThread;
private ConcurrentQueue<ExtendedByte>? receivedDataQueue;
private ConcurrentQueue<ExtendedByte>? sentDataQueue;
public event EventHandler<ReceivedDataEventArgs>? ReceivedDataEvent; public event EventHandler<ReceivedDataEventArgs>? ReceivedDataEvent;
public event EventHandler<SentDataEventArgs>? SentDataEvent; public event EventHandler<SentDataEventArgs>? SentDataEvent;
@ -32,12 +37,12 @@ public abstract class CommunicationProtocol : ICommunicationProtocol
/// <summary> /// <summary>
/// To be called when data was received from the connected device. /// To be called when data was received from the connected device.
/// </summary> /// </summary>
protected void OnReceivedData(ReceivedDataEventArgs e) { this.ReceivedDataEvent?.Invoke(this, e); } protected void OnReceivedData(ExtendedByte receivedByte) { this.receivedDataQueue?.Enqueue(receivedByte); }
/// <summary> /// <summary>
/// To be called when data was sent to the connected device. /// To be called when data was sent to the connected device.
/// </summary> /// </summary>
protected void OnSentData(SentDataEventArgs e) { this.SentDataEvent?.Invoke(this, e); } protected void OnSentData(ExtendedByte sentByte) { this.sentDataQueue?.Enqueue(sentByte); }
/// <summary> /// <summary>
/// To be called whenever the protocol detected that it is disconnected, but this is not a known fact. /// To be called whenever the protocol detected that it is disconnected, but this is not a known fact.
@ -46,8 +51,10 @@ public abstract class CommunicationProtocol : ICommunicationProtocol
{ {
// update state // update state
this.IsConnected = false; this.IsConnected = false;
// log
this.CancelThreads();
this.logger.LogError($"'{nameof(OnUnintentionallyDisconnected)}()' called.", nameof(CommunicationProtocol)); this.logger.LogError($"'{nameof(OnUnintentionallyDisconnected)}()' called.", nameof(CommunicationProtocol));
// raise event indicating an unintentional disconnect // raise event indicating an unintentional disconnect
this.DisconnectedEvent?.Invoke(this, new DisconnectedEventArgs(true)); this.DisconnectedEvent?.Invoke(this, new DisconnectedEventArgs(true));
} }
@ -64,12 +71,8 @@ public abstract class CommunicationProtocol : ICommunicationProtocol
// guard is not connected => log warning. user of this function shall only use SendBytes if IsConnected is true // guard is not connected => log warning. user of this function shall only use SendBytes if IsConnected is true
if (this.IsConnected == false) { this.logger.LogWarn($"'{nameof(SendBytes)}()' was reached with {nameof(IsConnected)} being false", nameof(CommunicationProtocol)); } if (this.IsConnected == false) { this.logger.LogWarn($"'{nameof(SendBytes)}()' was reached with {nameof(IsConnected)} being false", nameof(CommunicationProtocol)); }
if (this.InternalSendBytes(bytes)) // send bytes and if the sending was cancelled report error
{ if (this.InternalSendBytes(bytes) == false)
// todo implement sent bytes
// ONsentBytes()
}
else
{ {
this.logger.LogError($"'{nameof(SendBytes)}()' failed to send during {nameof(InternalSendBytes)}.", nameof(CommunicationProtocol)); this.logger.LogError($"'{nameof(SendBytes)}()' failed to send during {nameof(InternalSendBytes)}.", nameof(CommunicationProtocol));
this.messenger.Send<IUserInterfaceMessage>(new GenericUserInterfaceMessage("Failed to send message", MessageImportance.High)); this.messenger.Send<IUserInterfaceMessage>(new GenericUserInterfaceMessage("Failed to send message", MessageImportance.High));
@ -109,8 +112,13 @@ public abstract class CommunicationProtocol : ICommunicationProtocol
// try connecting if settings are valid // try connecting if settings are valid
if (this.InternalConnect(settings)) if (this.InternalConnect(settings))
{ {
// renew token source // renew token source and data buffers
this.cancellationTokenSource = new CancellationTokenSource(); this.cancellationTokenSource = new CancellationTokenSource();
this.receivedDataQueue = new ConcurrentQueue<ExtendedByte>();
this.sentDataQueue = new ConcurrentQueue<ExtendedByte>();
// start interal buffer handling thread
this.bufferHandlingThread = new Thread(() => this.HandleDataQueues(this.cancellationTokenSource.Token));
this.bufferHandlingThread.Start();
// start internal reading thread // start internal reading thread
this.readingThread = new Thread(() => this.InternalRead(this.cancellationTokenSource.Token)); this.readingThread = new Thread(() => this.InternalRead(this.cancellationTokenSource.Token));
this.readingThread.Start(); this.readingThread.Start();
@ -131,16 +139,71 @@ public abstract class CommunicationProtocol : ICommunicationProtocol
protected abstract void InternalDisconnect(); protected abstract void InternalDisconnect();
public void Disconnect() public void Disconnect()
{ {
// if reading thread exists and is running => cancel it and wait this.CancelThreads();
if (this.readingThread != null && this.readingThread.IsAlive)
{
this.cancellationTokenSource.Cancel();
this.readingThread.Join();
}
this.InternalDisconnect(); this.InternalDisconnect();
this.IsConnected = false; this.IsConnected = false;
// raise event indicating an intentional disconnect // raise event indicating an intentional disconnect
this.DisconnectedEvent?.Invoke(this, new DisconnectedEventArgs(false)); this.DisconnectedEvent?.Invoke(this, new DisconnectedEventArgs(false));
} }
/// <summary>
/// Sets cancellation token and makes sure both threads do not run anymore.
/// </summary>
private void CancelThreads()
{
this.cancellationTokenSource.Cancel();
// join both threads if they exist
this.readingThread?.Join();
this.bufferHandlingThread?.Join();
}
/// <summary>
/// Internal handling of internal data queues.
/// </summary>
/// <param name="ct">cancellation token</param>
private void HandleDataQueues(CancellationToken ct)
{
while(ct.IsCancellationRequested == false)
{
bool receivedDataAvailable = this.receivedDataQueue != null && this.receivedDataQueue.TryPeek(out _);
bool sentDataAvailable = this.sentDataQueue != null && this.sentDataQueue.TryPeek(out _);
// collect received data
if (receivedDataAvailable)
{
// something is in the queue => block thread and try dequeue
ExtendedByte? newReceivedData;
while (this.receivedDataQueue!.TryDequeue(out newReceivedData) == false)
{
Thread.Sleep(1);
}
// raise event
this.ReceivedDataEvent?.Invoke(this, new ReceivedDataEventArgs(new ExtendedByte[] { newReceivedData }));
}
// collect sent data
if (sentDataAvailable)
{
// something is in the queue => block thread and try dequeue
ExtendedByte? newSentData;
while (this.sentDataQueue!.TryDequeue(out newSentData) == false)
{
Thread.Sleep(1);
}
// raise event
this.SentDataEvent?.Invoke(this, new SentDataEventArgs(new ExtendedByte[] { newSentData }));
}
// generally wait some time to slow down thread, if no data available
if (!receivedDataAvailable && !sentDataAvailable)
{
Thread.Sleep(5);
}
// always sleep 1ms to keep UI rolling
Thread.Sleep(1);
}
}
} }

@ -79,11 +79,8 @@ public class SerialProtocol : CommunicationProtocol
int readByte = serialPort.ReadByte(); int readByte = serialPort.ReadByte();
if (readByte != -1) // -1 = end of stream if (readByte != -1) // -1 = end of stream
{ {
// create extended char type
var character = new ExtendedByte((byte)readByte);
// report new data with event // report new data with event
this.OnReceivedData(new ReceivedDataEventArgs(new ExtendedByte[] { character })); this.OnReceivedData(new ExtendedByte((byte)readByte));
} }
} }
} }
@ -96,13 +93,22 @@ public class SerialProtocol : CommunicationProtocol
{ {
serialPort.WriteByte(b); serialPort.WriteByte(b);
} }
// When the Serial Port is closed and InvalidOperationException is thrown // When the Serial Port is closed and InvalidOperationException is thrown => report error
catch(InvalidOperationException) catch(InvalidOperationException)
{ {
this.OnUnintentionallyDisconnected(); this.OnUnintentionallyDisconnected();
return false;
}
// any other exception => report error
catch
{
return false;
} }
// report that data was be sent
this.OnSentData(new ExtendedByte(b));
} }
return true; return true; // success
} }
public static IEnumerable<string> GetPortNames() public static IEnumerable<string> GetPortNames()

Loading…
Cancel
Save