From 0faa25f9d271a2bb32a2fbf89981207ad120090c Mon Sep 17 00:00:00 2001 From: Andrew Elmendorf Date: Sat, 16 Mar 2024 13:24:36 -0400 Subject: [PATCH] Add readLine option,add ConnectionEventType to specify type of connection changed, add event ConnectionStatus fire on connection interruption(only fires once per interruption). --- SerialPortLib/Events.cs | 43 ++++- SerialPortLib/SerialPort.cs | 283 +++++++++++++++-------------- SerialPortLib/SerialPortLib.csproj | 1 + TestApp.Net/Program.cs | 104 ++++++----- 4 files changed, 245 insertions(+), 186 deletions(-) diff --git a/SerialPortLib/Events.cs b/SerialPortLib/Events.cs index 20d36b1..3517164 100644 --- a/SerialPortLib/Events.cs +++ b/SerialPortLib/Events.cs @@ -23,8 +23,13 @@ limitations under the License. using System; -namespace SerialPortLib -{ +namespace SerialPortLib { + + public enum ConnectionEventType { + Disconnected, + DisconnectWithRetry, + Connected + } /// /// Connected state changed event arguments. @@ -35,14 +40,24 @@ public class ConnectionStatusChangedEventArgs /// The connected state. /// public readonly bool Connected; + + /// + /// Message + /// + public readonly ConnectionEventType ConnectionEventType; /// /// Initializes a new instance of the class. /// /// State of the connection (true = connected, false = not connected). - public ConnectionStatusChangedEventArgs(bool state) - { + /// Details on the state of the connection: + /// Disconnected=The connection has been lost and the library will not attempt to reconnect, + /// DisconnectWithRetry=The connection has been lost and the library will attempt to reconnect, + /// Connected=The connection has been established + /// + public ConnectionStatusChangedEventArgs(bool state, ConnectionEventType connectionEventType) { Connected = state; + ConnectionEventType = connectionEventType; } } @@ -65,5 +80,25 @@ public MessageReceivedEventArgs(byte[] data) Data = data; } } + + /// + /// Message received event arguments from ReadLine. + /// + public class MessageReceivedLineEventArgs + { + /// + /// The line string. + /// + public readonly string Data; + + /// + /// Initializes a new instance of the class. + /// + /// + public MessageReceivedLineEventArgs(string Line) + { + Data =Line; + } + } } diff --git a/SerialPortLib/SerialPort.cs b/SerialPortLib/SerialPort.cs index 1533f27..abc3838 100644 --- a/SerialPortLib/SerialPort.cs +++ b/SerialPortLib/SerialPort.cs @@ -1,6 +1,6 @@ /* This file is part of SerialPortLib (https://github.com/genielabs/serialport-lib-dotnet) - + Copyright (2012-2023) G-Labs (https://github.com/genielabs) Licensed under the Apache License, Version 2.0 (the "License"); @@ -23,19 +23,19 @@ limitations under the License. using System; using System.Threading; - using System.IO.Ports; using System.Runtime.InteropServices; using NLog; +using System.Buffers; +using System.Diagnostics; +using System.Text; -namespace SerialPortLib -{ +namespace SerialPortLib { /// /// DataBits enum. /// - public enum DataBits - { + public enum DataBits { /// /// DataBits 5. /// @@ -57,8 +57,7 @@ public enum DataBits /// /// Serial port I/O /// - public class SerialPortInput - { + public class SerialPortInput { #region Private Fields @@ -80,9 +79,9 @@ public class SerialPortInput // Serial port connection watcher private Thread _connectionWatcher; private CancellationTokenSource _connectionWatcherCts; - private readonly object _accessLock = new object(); private bool _disconnectRequested; + private bool _disconnectRetryEventSent; #endregion @@ -108,27 +107,46 @@ public class SerialPortInput /// public event MessageReceivedEventHandler MessageReceived; + /// + /// Message received event. + /// + public delegate void MessageReceivedLineEventHandler(object sender, MessageReceivedLineEventArgs args); + + /// + /// Occurs when message received. + /// + public event MessageReceivedLineEventHandler MessageLineReceived; + + private Action ReadTask { get; set; } + #endregion #region Public Members - public SerialPortInput() - { + public SerialPortInput() { + _connectionWatcherCts = new CancellationTokenSource(); + _readerCts = new CancellationTokenSource(); + ReadTask = ReadBytesTask; + } + + public SerialPortInput(bool readLine=false) { _connectionWatcherCts = new CancellationTokenSource(); _readerCts = new CancellationTokenSource(); + if (readLine) { + this.ReadTask = ReadLineTask; + } else { + this.ReadTask= ReadBytesTask; + } } /// /// Connect to the serial port. /// - public bool Connect() - { - if (_disconnectRequested) - { + public bool Connect() { + if (_disconnectRequested) { return false; } - lock (_accessLock) - { + lock (_accessLock) { Disconnect(); Open(); _connectionWatcherCts = new CancellationTokenSource(); @@ -141,20 +159,15 @@ public bool Connect() /// /// Disconnect the serial port. /// - public void Disconnect() - { - if (_disconnectRequested) - { + public void Disconnect() { + if (_disconnectRequested) { return; } _disconnectRequested = true; Close(); - lock (_accessLock) - { - if (_connectionWatcher != null) - { - if (!_connectionWatcher.Join(5000)) - { + lock (_accessLock) { + if (_connectionWatcher != null) { + if (!_connectionWatcher.Join(5000)) { _connectionWatcherCts.Cancel(); } _connectionWatcher = null; @@ -167,8 +180,7 @@ public void Disconnect() /// Gets a value indicating whether the serial port is connected. /// /// true if connected; otherwise, false. - public bool IsConnected - { + public bool IsConnected { get { return _serialPort != null && !_gotReadWriteError && !_disconnectRequested; } } @@ -180,10 +192,8 @@ public bool IsConnected /// Stopbits. /// Parity. /// Databits. - public void SetPort(string portName, int baudRate = 115200, StopBits stopBits = StopBits.One, Parity parity = Parity.None, DataBits dataBits = DataBits.Eight) - { - if (_portName != portName || _baudRate != baudRate || stopBits != _stopBits || parity != _parity || dataBits != _dataBits) - { + public void SetPort(string portName, int baudRate = 115200, StopBits stopBits = StopBits.One, Parity parity = Parity.None, DataBits dataBits = DataBits.Eight) { + if (_portName != portName || _baudRate != baudRate || stopBits != _stopBits || parity != _parity || dataBits != _dataBits) { // Change Parameters request // Take into account immediately the new connection parameters // (do not use the ConnectionWatcher, otherwise strange things will occurs !) @@ -192,9 +202,8 @@ public void SetPort(string portName, int baudRate = 115200, StopBits stopBits = _stopBits = stopBits; _parity = parity; _dataBits = dataBits; - if (IsConnected) - { - Connect(); // Take into account immediately the new connection parameters + if (IsConnected) { + Connect();// Take into account immediately the new connection parameters } LogDebug(string.Format("Port parameters changed (port name {0} / baudrate {1} / stopbits {2} / parity {3} / databits {4})", portName, baudRate, stopBits, parity, dataBits)); } @@ -205,19 +214,14 @@ public void SetPort(string portName, int baudRate = 115200, StopBits stopBits = /// /// true, if message was sent, false otherwise. /// Message. - public bool SendMessage(byte[] message) - { + public bool SendMessage(byte[] message) { bool success = false; - if (IsConnected) - { - try - { + if (IsConnected) { + try { _serialPort.Write(message, 0, message.Length); success = true; LogDebug(BitConverter.ToString(message)); - } - catch (Exception e) - { + } catch(Exception e) { LogError(e); } } @@ -228,6 +232,7 @@ public bool SendMessage(byte[] message) /// Gets/Sets serial port reconnection delay in milliseconds. /// public int ReconnectDelay { get; set; } = 1000; + #endregion @@ -235,25 +240,21 @@ public bool SendMessage(byte[] message) #region Serial Port handling - private bool Open() - { + private bool Open() { bool success = false; - lock (_accessLock) - { + lock (_accessLock) { Close(); - try - { + try { bool tryOpen = true; var isWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows); - if (!isWindows) - { + if (!isWindows) { tryOpen = (tryOpen && System.IO.File.Exists(_portName)); } - if (tryOpen) - { + if (tryOpen) { _serialPort = new SerialPort(); _serialPort.ErrorReceived += HandleErrorReceived; + /*_serialPort.PinChanged += (sender, e) => LogDebug("SerialPort PinChanged: " + e.EventType);*/ _serialPort.PortName = _portName; _serialPort.BaudRate = _baudRate; _serialPort.StopBits = _stopBits; @@ -264,44 +265,37 @@ private bool Open() // We use the readerTask instead (see below). _serialPort.Open(); success = true; + this._disconnectRetryEventSent = false; } - } - catch (Exception e) - { + } catch(Exception e) { LogError(e); Close(); } - if (_serialPort != null && _serialPort.IsOpen) - { + if (_serialPort != null && _serialPort.IsOpen) { _gotReadWriteError = false; // Start the Reader task _readerCts = new CancellationTokenSource(); _reader = new Thread(ReaderTask) { IsBackground = true }; _reader.Start(_readerCts.Token); - OnConnectionStatusChanged(new ConnectionStatusChangedEventArgs(true)); + OnConnectionStatusChanged(new ConnectionStatusChangedEventArgs(true, ConnectionEventType.Connected)); } } return success; } - private void Close() - { - lock (_accessLock) - { + private void Close() { + lock (_accessLock) { // Stop the Reader task - if (_reader != null) - { + if (_reader != null) { if (!_reader.Join(5000)) _readerCts.Cancel(); _reader = null; } - if (_serialPort != null) - { + if (_serialPort != null) { _serialPort.ErrorReceived -= HandleErrorReceived; - if (_serialPort.IsOpen) - { + if (_serialPort.IsOpen) { _serialPort.Close(); - OnConnectionStatusChanged(new ConnectionStatusChangedEventArgs(false)); + OnConnectionStatusChanged(new ConnectionStatusChangedEventArgs(false, ConnectionEventType.Disconnected)); } _serialPort.Dispose(); _serialPort = null; @@ -310,8 +304,7 @@ private void Close() } } - private void HandleErrorReceived(object sender, SerialErrorReceivedEventArgs e) - { + private void HandleErrorReceived(object sender, SerialErrorReceivedEventArgs e) { LogError(e.EventType); } @@ -319,92 +312,95 @@ private void HandleErrorReceived(object sender, SerialErrorReceivedEventArgs e) #region Background Tasks - private void ReaderTask(object data) - { - var ct = (CancellationToken) data; - while (IsConnected && !ct.IsCancellationRequested) - { - int msglen = 0; - // - try - { - msglen = _serialPort.BytesToRead; - if (msglen > 0) - { - byte[] message = new byte[msglen]; - // - int readBytes = 0; - while (readBytes <= 0) - readBytes = _serialPort.Read(message, readBytes, msglen - readBytes); // noop - if (MessageReceived != null) - { - OnMessageReceived(new MessageReceivedEventArgs(message)); - } + private void ReaderTask(object data) { + var ct = (CancellationToken)data; + while (IsConnected && !ct.IsCancellationRequested) { + this.ReadTask(); + } + } + + private void ReadLineTask() { + try { + /*if (_serialPort.BytesToRead > 0) {*/ + var line = this._serialPort.ReadLine(); + if (MessageLineReceived != null) { + OnMessageLineReceived(new MessageReceivedLineEventArgs(line)); } - else - { - Thread.Sleep(100); + /*} else { + Thread.Sleep(100); + }*/ + } catch(Exception e) { + LogError(e); + _gotReadWriteError = true; + Thread.Sleep(1000); + } + } + + private void ReadBytesTask() { + int msglen = 0; + // + try { + msglen = _serialPort.BytesToRead; + if (msglen > 0) { + byte[] message = new byte[msglen]; + // + int readBytes = 0; + while (readBytes <= 0) + readBytes = _serialPort.Read(message, readBytes, msglen - readBytes);// noop + + if (MessageReceived != null) { + OnMessageReceived(new MessageReceivedEventArgs(message)); } + } else { + Thread.Sleep(100); } - catch (Exception e) - { - LogError(e); - _gotReadWriteError = true; - Thread.Sleep(1000); - } + } catch(Exception e) { + LogError(e); + _gotReadWriteError = true; + Thread.Sleep(1000); } } - private void ConnectionWatcherTask(object data) - { - var ct = (CancellationToken) data; + private void ConnectionWatcherTask(object data) { + var ct = (CancellationToken)data; // This task takes care of automatically reconnecting the interface // when the connection is drop or if an I/O error occurs - while (!_disconnectRequested && !ct.IsCancellationRequested) - { - if (_gotReadWriteError) - { - try - { + while (!_disconnectRequested && !ct.IsCancellationRequested) { + if (_gotReadWriteError) { + try { + if (!this._disconnectRetryEventSent) { + this._disconnectRetryEventSent = true; + OnConnectionStatusChanged(new ConnectionStatusChangedEventArgs(false, ConnectionEventType.DisconnectWithRetry)); + } Close(); // wait "ReconnectDelay" seconds before reconnecting Thread.Sleep(ReconnectDelay); - if (!_disconnectRequested) - { - try - { + if (!_disconnectRequested) { + try { Open(); - } - catch (Exception e) - { + } catch(Exception e) { LogError(e); } } - } - catch (Exception e) - { + } catch(Exception e) { LogError(e); } } - if (!_disconnectRequested) - { + if (!_disconnectRequested) { Thread.Sleep(1000); } } } - private void LogDebug(string message) - { + private void LogDebug(string message) { _logger.Debug(message); } - private void LogError(Exception ex) - { + private void LogError(Exception ex) { _logger.Error(ex, null); } - private void LogError(SerialError error) - { + private void LogError(SerialError error) { _logger.Error("SerialPort ErrorReceived: {0}", error); } @@ -416,11 +412,9 @@ private void LogError(SerialError error) /// Raises the connected state changed event. /// /// Arguments. - protected virtual void OnConnectionStatusChanged(ConnectionStatusChangedEventArgs args) - { + protected virtual void OnConnectionStatusChanged(ConnectionStatusChangedEventArgs args) { LogDebug(args.Connected.ToString()); - if (ConnectionStatusChanged != null) - { + if (ConnectionStatusChanged != null) { ConnectionStatusChanged(this, args); } } @@ -429,19 +423,28 @@ protected virtual void OnConnectionStatusChanged(ConnectionStatusChangedEventArg /// Raises the message received event. /// /// Arguments. - protected virtual void OnMessageReceived(MessageReceivedEventArgs args) - { + protected virtual void OnMessageReceived(MessageReceivedEventArgs args) { LogDebug(BitConverter.ToString(args.Data)); - if (MessageReceived != null) - { + if (MessageReceived != null) { MessageReceived(this, args); } } + /// + /// Raises the message line received event. + /// + /// Arguments. + protected virtual void OnMessageLineReceived(MessageReceivedLineEventArgs args) { + LogDebug(args.Data); + if (MessageLineReceived != null) { + MessageLineReceived(this, args); + } + } + #endregion #endregion } -} +} \ No newline at end of file diff --git a/SerialPortLib/SerialPortLib.csproj b/SerialPortLib/SerialPortLib.csproj index 04aa1b3..bf809d9 100644 --- a/SerialPortLib/SerialPortLib.csproj +++ b/SerialPortLib/SerialPortLib.csproj @@ -17,6 +17,7 @@ + diff --git a/TestApp.Net/Program.cs b/TestApp.Net/Program.cs index 42b5792..78c8a50 100644 --- a/TestApp.Net/Program.cs +++ b/TestApp.Net/Program.cs @@ -12,18 +12,76 @@ namespace TestApp.NetCore { class Program { - private static string _defaultPort = "/dev/ttyUSB0"; + private static string _defaultPort = "COM3"; private static SerialPortInput _serialPort; // NOTE: To disable debug output uncomment the following two lines // NLog.LogLevel.Info; private static readonly NLog.LogLevel MinLogLevel = NLog.LogLevel.Debug; - public static void Main(string[] args) + public static void Main(string[] args) { + var servicesProvider = BuildDi(); + using (servicesProvider as IDisposable) { + _serialPort = servicesProvider.GetRequiredService(); + _serialPort.ConnectionStatusChanged += SerialPort_ConnectionStatusChanged; + _serialPort.MessageLineReceived += SerialPortOnMessageLineReceived; + _serialPort.MessageReceived += SerialPort_MessageReceived; + + _serialPort.SetPort("COM3", 38400); + _serialPort.Connect(); + + Console.WriteLine("Press Enter to exit"); + Console.ReadLine(); + Console.WriteLine("Goodbye!"); + _serialPort.Disconnect(); + + } + } + private static void SerialPortOnMessageLineReceived(Object sender, MessageReceivedLineEventArgs args) { + Console.WriteLine(args.Data); + } + + static void SerialPort_MessageReceived(object sender, MessageReceivedEventArgs args) { + Console.WriteLine("Received message: {0}", BitConverter.ToString(args.Data)); + // On every message received we send an ACK message back to the device + _serialPort.SendMessage(new byte[] { 0x06 }); + } + + static void SerialPort_ConnectionStatusChanged(object sender, ConnectionStatusChangedEventArgs args) { + Console.WriteLine($"Serial port connection status = {args.Connected}, ConnectionEventType = {args.ConnectionEventType}"); + + } + + private static IServiceProvider BuildDi() + { + return new ServiceCollection() + .AddTransient(provider =>new SerialPortInput(false)) + .AddLogging(loggingBuilder => + { + // configure Logging with NLog + loggingBuilder.ClearProviders(); + loggingBuilder.SetMinimumLevel(LogLevel.Trace); + loggingBuilder.AddNLog(new LoggingConfiguration + { + LoggingRules = + { + new LoggingRule( + "*", + MinLogLevel, + new ConsoleTarget + { + Layout = new SimpleLayout("${longdate} ${callsite} ${level} ${message} ${exception}") + }) + } + }); + }) + .BuildServiceProvider(); + } + + private static void LibMain() { var servicesProvider = BuildDi(); - using (servicesProvider as IDisposable) - { + using (servicesProvider as IDisposable) { _serialPort = servicesProvider.GetRequiredService(); _serialPort.ConnectionStatusChanged += SerialPort_ConnectionStatusChanged; _serialPort.MessageReceived += SerialPort_MessageReceived; @@ -71,43 +129,5 @@ public static void Main(string[] args) } } } - - static void SerialPort_MessageReceived(object sender, MessageReceivedEventArgs args) - { - Console.WriteLine("Received message: {0}", BitConverter.ToString(args.Data)); - // On every message received we send an ACK message back to the device - _serialPort.SendMessage(new byte[] { 0x06 }); - } - - static void SerialPort_ConnectionStatusChanged(object sender, ConnectionStatusChangedEventArgs args) - { - Console.WriteLine("Serial port connection status = {0}", args.Connected); - } - - private static IServiceProvider BuildDi() - { - return new ServiceCollection() - .AddTransient() - .AddLogging(loggingBuilder => - { - // configure Logging with NLog - loggingBuilder.ClearProviders(); - loggingBuilder.SetMinimumLevel(LogLevel.Trace); - loggingBuilder.AddNLog(new LoggingConfiguration - { - LoggingRules = - { - new LoggingRule( - "*", - MinLogLevel, - new ConsoleTarget - { - Layout = new SimpleLayout("${longdate} ${callsite} ${level} ${message} ${exception}") - }) - } - }); - }) - .BuildServiceProvider(); - } } }