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? ReceivedDataEvent; public event EventHandler? SentDataEvent; public event EventHandler? DisconnectedEvent; public abstract ProtocolType ProtocolType { get; } public abstract string InstanceIdentifier { get; protected set; } 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; } /// /// To be called when data was received from the connected device. /// protected void OnReceivedData(ReceivedDataEventArgs e) { this.ReceivedDataEvent?.Invoke(this, e); } /// /// To be called when data was sent to the connected device. /// protected void OnSentData(SentDataEventArgs e) { this.SentDataEvent?.Invoke(this, e); } /// /// To be called whenever the protocol detected that it is disconnected, but this is not a known fact. /// 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)); } /// /// Allows to send bytes using the implemented protocol. /// /// data as bytes /// true if the data was sent successfully 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(new GenericUserInterfaceMessage("Failed to send message", MessageImportance.High)); } } /// /// Allows to connect to the selected device using the implemented protocol. /// /// protocol settings required to connect /// true on success protected abstract bool InternalConnect(IProtocolSettings settings); /// /// Reads from the connected device in an endless loop. /// The endless loop must be ended when the has set. /// /// cancellation token 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; } } /// /// Allows to disconnect from the connected device. /// 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)); } }