From 8abb83345cbc14808fa6e4ef8ea0aba310e0cf14 Mon Sep 17 00:00:00 2001 From: Jonas Arnold Date: Wed, 3 May 2023 21:33:19 +0200 Subject: [PATCH] fixed clear command, added buffer and bufferHandlingThread in Communication Protocol to separatly handle data (not slowing down protocol interface) --- .../ViewModel/CommunicationDataViewModel.cs | 5 +- .../ViewModel/ICommunicationDataViewModel.cs | 5 +- MultiTerm.Protocols/CommunicationProtocol.cs | 95 +++++++++++++++---- MultiTerm.Protocols/Serial/SerialProtocol.cs | 18 ++-- 4 files changed, 97 insertions(+), 26 deletions(-) diff --git a/MultiTerm.Core/ViewModel/CommunicationDataViewModel.cs b/MultiTerm.Core/ViewModel/CommunicationDataViewModel.cs index ca97217..c34c225 100644 --- a/MultiTerm.Core/ViewModel/CommunicationDataViewModel.cs +++ b/MultiTerm.Core/ViewModel/CommunicationDataViewModel.cs @@ -1,6 +1,7 @@ using Common; using Common.Helpers; using CommunityToolkit.Mvvm.ComponentModel; +using CommunityToolkit.Mvvm.Input; using MultiTerm.Core.Types; using MultiTerm.Protocols.Model; using System.Collections.ObjectModel; @@ -65,7 +66,7 @@ public partial class CommunicationDataViewModel : ObservableObject, ICommunicati ContextHelpers.InvokeIfNecessary(this.uiContext, (Action)delegate { this.DataAsString = InsertNewNewCharactersIntoCollection(this.Data, this.DataAsString, - ref this.dataCharacterCount, ref this.listOfPreviousCharacters, + ref this.dataCharacterCount, ref this.listOfPreviousCharacters, this.NewlineSeparator, newRawData); }); } @@ -84,7 +85,7 @@ public partial class CommunicationDataViewModel : ObservableObject, ICommunicati }); } - + [RelayCommand] public void Clear() { // update collection and string, invoke ui thread if necessary diff --git a/MultiTerm.Core/ViewModel/ICommunicationDataViewModel.cs b/MultiTerm.Core/ViewModel/ICommunicationDataViewModel.cs index a5a6622..4afc9c7 100644 --- a/MultiTerm.Core/ViewModel/ICommunicationDataViewModel.cs +++ b/MultiTerm.Core/ViewModel/ICommunicationDataViewModel.cs @@ -1,4 +1,5 @@ -using MultiTerm.Core.Types; +using CommunityToolkit.Mvvm.Input; +using MultiTerm.Core.Types; using System.Collections.ObjectModel; namespace MultiTerm.Core.ViewModel; @@ -36,5 +37,5 @@ public interface ICommunicationDataViewModel where T_Data : IData /// /// Allows to clear the data. /// - void Clear(); + IRelayCommand ClearCommand { get; } } \ No newline at end of file diff --git a/MultiTerm.Protocols/CommunicationProtocol.cs b/MultiTerm.Protocols/CommunicationProtocol.cs index 950b47b..2d383af 100644 --- a/MultiTerm.Protocols/CommunicationProtocol.cs +++ b/MultiTerm.Protocols/CommunicationProtocol.cs @@ -1,7 +1,9 @@ using Common.Logging; using Common.Messaging; using CommunityToolkit.Mvvm.Messaging; +using MultiTerm.Protocols.Model; using MultiTerm.Protocols.Types; +using System.Collections.Concurrent; namespace MultiTerm.Protocols; @@ -11,6 +13,9 @@ public abstract class CommunicationProtocol : ICommunicationProtocol protected readonly IMessenger messenger; private CancellationTokenSource cancellationTokenSource; private Thread? readingThread; + private Thread? bufferHandlingThread; + private ConcurrentQueue? receivedDataQueue; + private ConcurrentQueue? sentDataQueue; public event EventHandler? ReceivedDataEvent; public event EventHandler? SentDataEvent; @@ -32,12 +37,12 @@ public abstract class CommunicationProtocol : ICommunicationProtocol /// /// To be called when data was received from the connected device. /// - protected void OnReceivedData(ReceivedDataEventArgs e) { this.ReceivedDataEvent?.Invoke(this, e); } + protected void OnReceivedData(ExtendedByte receivedByte) { this.receivedDataQueue?.Enqueue(receivedByte); } /// /// To be called when data was sent to the connected device. /// - protected void OnSentData(SentDataEventArgs e) { this.SentDataEvent?.Invoke(this, e); } + protected void OnSentData(ExtendedByte sentByte) { this.sentDataQueue?.Enqueue(sentByte); } /// /// 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 this.IsConnected = false; - // log + + this.CancelThreads(); this.logger.LogError($"'{nameof(OnUnintentionallyDisconnected)}()' called.", nameof(CommunicationProtocol)); + // raise event indicating an unintentional disconnect 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 if (this.IsConnected == false) { this.logger.LogWarn($"'{nameof(SendBytes)}()' was reached with {nameof(IsConnected)} being false", nameof(CommunicationProtocol)); } - if (this.InternalSendBytes(bytes)) - { - // todo implement sent bytes - // ONsentBytes() - } - else + // send bytes and if the sending was cancelled report error + if (this.InternalSendBytes(bytes) == false) { this.logger.LogError($"'{nameof(SendBytes)}()' failed to send during {nameof(InternalSendBytes)}.", nameof(CommunicationProtocol)); this.messenger.Send(new GenericUserInterfaceMessage("Failed to send message", MessageImportance.High)); @@ -109,8 +112,13 @@ public abstract class CommunicationProtocol : ICommunicationProtocol // try connecting if settings are valid if (this.InternalConnect(settings)) { - // renew token source + // renew token source and data buffers this.cancellationTokenSource = new CancellationTokenSource(); + this.receivedDataQueue = new ConcurrentQueue(); + this.sentDataQueue = new ConcurrentQueue(); + // start interal buffer handling thread + this.bufferHandlingThread = new Thread(() => this.HandleDataQueues(this.cancellationTokenSource.Token)); + this.bufferHandlingThread.Start(); // start internal reading thread this.readingThread = new Thread(() => this.InternalRead(this.cancellationTokenSource.Token)); this.readingThread.Start(); @@ -131,16 +139,71 @@ public abstract class CommunicationProtocol : ICommunicationProtocol protected abstract void InternalDisconnect(); public void Disconnect() { - // if reading thread exists and is running => cancel it and wait - if (this.readingThread != null && this.readingThread.IsAlive) - { - this.cancellationTokenSource.Cancel(); - this.readingThread.Join(); - } + this.CancelThreads(); this.InternalDisconnect(); this.IsConnected = false; // raise event indicating an intentional disconnect this.DisconnectedEvent?.Invoke(this, new DisconnectedEventArgs(false)); } + + /// + /// Sets cancellation token and makes sure both threads do not run anymore. + /// + private void CancelThreads() + { + this.cancellationTokenSource.Cancel(); + + // join both threads if they exist + this.readingThread?.Join(); + this.bufferHandlingThread?.Join(); + } + + /// + /// Internal handling of internal data queues. + /// + /// cancellation token + 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); + } + } } diff --git a/MultiTerm.Protocols/Serial/SerialProtocol.cs b/MultiTerm.Protocols/Serial/SerialProtocol.cs index 2350239..09b78d0 100644 --- a/MultiTerm.Protocols/Serial/SerialProtocol.cs +++ b/MultiTerm.Protocols/Serial/SerialProtocol.cs @@ -79,11 +79,8 @@ public class SerialProtocol : CommunicationProtocol int readByte = serialPort.ReadByte(); if (readByte != -1) // -1 = end of stream { - // create extended char type - var character = new ExtendedByte((byte)readByte); - // 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); } - // When the Serial Port is closed and InvalidOperationException is thrown + // When the Serial Port is closed and InvalidOperationException is thrown => report error catch(InvalidOperationException) { 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 GetPortNames()