Multiprocotol Terminalprogram (BAT)
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.
MultiTerm/MultiTerm.Protocols/CommunicationProtocol.cs

145 lines
5.5 KiB

using Common.Logging;
using Common.Messaging;
using CommunityToolkit.Mvvm.Messaging;
using MultiTerm.Protocols.Types;
namespace MultiTerm.Protocols;
public abstract class CommunicationProtocol : ICommunicationProtocol
{
protected readonly ILogger logger;
protected readonly IMessenger messenger;
private CancellationTokenSource cancellationTokenSource;
private Thread? readingThread;
public event EventHandler<ReceivedDataEventArgs>? ReceivedDataEvent;
public event EventHandler<SentDataEventArgs>? SentDataEvent;
public event EventHandler<DisconnectedEventArgs>? DisconnectedEvent;
public abstract ProtocolType ProtocolType { get; }
public bool IsConnected { get; private set; } = false;
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(ReceivedDataEventArgs e) { this.ReceivedDataEvent?.Invoke(this, e); }
/// <summary>
/// To be called when data was sent to the connected device.
/// </summary>
protected void OnSentData(SentDataEventArgs e) { this.SentDataEvent?.Invoke(this, e); }
/// <summary>
/// To be called whenever the protocol detected that it is disconnected, but this is not a known fact.
/// </summary>
protected void OnUnintentionallyDisconnected()
{
// update state
this.IsConnected = false;
// log
this.logger.LogError($"'{nameof(OnUnintentionallyDisconnected)}()' called.", nameof(CommunicationProtocol));
// raise event indicating an unintentional disconnect
this.DisconnectedEvent?.Invoke(this, new DisconnectedEventArgs(true));
}
/// <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 void SendBytes(byte[] bytes)
{
// 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
{
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));
}
}
/// <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.IsConnected == true)
{
this.logger.LogWarn($"'{nameof(Connect)}()' was reached even if {nameof(IsConnected)} is already true.", 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
this.cancellationTokenSource = new CancellationTokenSource();
// start internal reading thread
this.readingThread = new Thread(() => this.InternalRead(this.cancellationTokenSource.Token));
this.readingThread.Start();
// update state
this.IsConnected = true;
return true;
}
else
{
this.logger.LogWarn($"'{nameof(Connect)}()' failed to connect to protocol, did not start reading thread.", nameof(CommunicationProtocol));
return false;
}
}
/// <summary>
/// Allows to disconnect from the connected device.
/// </summary>
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.InternalDisconnect();
this.IsConnected = false;
// raise event indicating an intentional disconnect
this.DisconnectedEvent?.Invoke(this, new DisconnectedEventArgs(false));
}
}