You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
229 lines
8.8 KiB
229 lines
8.8 KiB
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;
|
|
|
|
public abstract class CommunicationProtocol : ICommunicationProtocol
|
|
{
|
|
protected readonly ILogger logger;
|
|
protected readonly IMessenger messenger;
|
|
private CancellationTokenSource cancellationTokenSource;
|
|
private Thread? readingThread;
|
|
private Thread? bufferHandlingThread;
|
|
private ConcurrentQueue<ExtendedByte>? receivedDataQueue;
|
|
private ConcurrentQueue<ExtendedByte>? sentDataQueue;
|
|
|
|
public event EventHandler<ReceivedDataEventArgs>? ReceivedDataEvent;
|
|
public event EventHandler<SentDataEventArgs>? SentDataEvent;
|
|
public event EventHandler<ProtocolConnectionState>? ConnectionStateChangedEvent;
|
|
|
|
public abstract ProtocolType ProtocolType { get; }
|
|
public abstract string InstanceIdentifier { get; protected set; }
|
|
|
|
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)
|
|
{
|
|
// initialize other
|
|
this.cancellationTokenSource = new CancellationTokenSource();
|
|
this.logger = logger;
|
|
this.messenger = messenger;
|
|
}
|
|
|
|
/// <summary>
|
|
/// To be called when data was received from the connected device.
|
|
/// </summary>
|
|
protected void OnReceivedData(ExtendedByte receivedByte) { this.receivedDataQueue?.Enqueue(receivedByte); }
|
|
|
|
/// <summary>
|
|
/// To be called when data was sent to the connected device.
|
|
/// </summary>
|
|
protected void OnSentData(ExtendedByte sentByte) { this.sentDataQueue?.Enqueue(sentByte); }
|
|
|
|
/// <summary>
|
|
/// To be called whenever the protocol detected that it is disconnected, but this is not a known fact.
|
|
/// </summary>
|
|
protected void OnUnintentionallyDisconnected()
|
|
{
|
|
// perform all steps in a task, to prevent deadlock when this method is called from a thread that is joined.
|
|
Task.Run(() =>
|
|
{
|
|
this.CancelThreads();
|
|
this.logger.LogError($"'{nameof(OnUnintentionallyDisconnected)}()' called.", nameof(CommunicationProtocol));
|
|
|
|
// update state
|
|
this.State = ProtocolConnectionState.UnintentionallyDisconnected;
|
|
});
|
|
}
|
|
|
|
/// <summary>
|
|
/// Allows to send bytes using the implemented protocol.
|
|
/// </summary>
|
|
/// <param name="bytes">data as bytes</param>
|
|
/// <returns>true if the data was sent successfully</returns>
|
|
protected abstract bool InternalSendBytes(byte[] bytes);
|
|
|
|
public bool SendBytes(byte[] bytes)
|
|
{
|
|
bool success = true;
|
|
|
|
// guard is not connected => log warning. user of this function shall only use SendBytes if IsConnected is true
|
|
if (this.State != ProtocolConnectionState.Connected)
|
|
{
|
|
this.logger.LogWarn($"'{nameof(SendBytes)}()' was reached with wrong {nameof(ProtocolConnectionState)} of {this.State}", nameof(CommunicationProtocol));
|
|
return false; // return and do not send
|
|
}
|
|
|
|
// send bytes
|
|
success = this.InternalSendBytes(bytes);
|
|
|
|
// if the sending was cancelled report error
|
|
if (success == false)
|
|
{
|
|
this.logger.LogError($"'{nameof(SendBytes)}()' failed to send during {nameof(InternalSendBytes)}.", nameof(CommunicationProtocol));
|
|
this.messenger.Send<IUserInterfaceMessage>(new GenericUserInterfaceMessage("Failed to send data, for more information please check logfile.", MessageImportance.High));
|
|
}
|
|
|
|
return success;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Allows to connect to the selected device using the implemented protocol.
|
|
/// </summary>
|
|
/// <param name="settings">protocol settings required to connect</param>
|
|
/// <returns>true on success</returns>
|
|
protected abstract bool InternalConnect(IProtocolSettings settings);
|
|
|
|
/// <summary>
|
|
/// Reads from the connected device in an endless loop.
|
|
/// The endless loop must be ended when the <paramref name="ct"/> has <see cref="CancellationToken.IsCancellationRequested"/> set.
|
|
/// </summary>
|
|
/// <param name="ct">cancellation token</param>
|
|
protected abstract void InternalRead(CancellationToken ct);
|
|
|
|
public bool Connect(IProtocolSettings settings)
|
|
{
|
|
// check if not already connected
|
|
if (this.State == ProtocolConnectionState.Connected)
|
|
{
|
|
this.logger.LogWarn($"'{nameof(Connect)}()' was reached with wrong {nameof(ProtocolConnectionState)} of {this.State}", nameof(CommunicationProtocol));
|
|
return true;
|
|
}
|
|
|
|
// 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 false;
|
|
}
|
|
|
|
// try connecting if settings are valid
|
|
if (this.InternalConnect(settings))
|
|
{
|
|
// renew token source and data buffers
|
|
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
|
|
this.readingThread = new Thread(() => this.InternalRead(this.cancellationTokenSource.Token));
|
|
this.readingThread.Start();
|
|
// update state
|
|
this.State = ProtocolConnectionState.Connected;
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
this.logger.LogWarn($"'{nameof(Connect)}()' failed to connect to protocol, did not start reading thread.", nameof(CommunicationProtocol));
|
|
this.messenger.Send<IUserInterfaceMessage>(new GenericUserInterfaceMessage("Failed to connect to protocol, for more information please check logfile.", MessageImportance.High));
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Allows to disconnect from the connected device.
|
|
/// </summary>
|
|
protected abstract void InternalDisconnect();
|
|
public void Disconnect()
|
|
{
|
|
this.CancelThreads();
|
|
this.InternalDisconnect();
|
|
|
|
this.State = ProtocolConnectionState.NotConnected;
|
|
}
|
|
|
|
/// <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);
|
|
}
|
|
}
|
|
}
|
|
|