711 lines
30 KiB
C#
711 lines
30 KiB
C#
using NLog;
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.IO.Ports;
|
|
using System.Threading;
|
|
using Xylem.Common.CommonCore.Consts;
|
|
using Xylem.Common.CommonCore.ThreadWatcher;
|
|
using Xylem.Common.Hardware.Interfaces.Ports.PortCore;
|
|
using Xylem.Common.Hardware.Interfaces.Ports.PortCore.EventArguments;
|
|
using Xylem.Common.Utils.Logging;
|
|
|
|
namespace Xylem.Common.Hardware.Interfaces.Ports.SerialPorts
|
|
{
|
|
/// <summary>
|
|
/// All Ports must use BasePort as an base class
|
|
/// its support some wrapping event/function handling
|
|
/// </summary>
|
|
public abstract class BaseSerialPort : IPort, IDisposable
|
|
{
|
|
|
|
private ILogger _byteDataLogger;
|
|
internal ILogger AsciiDataLogger;
|
|
|
|
/// <inheritdoc />
|
|
public virtual event EventHandler<BasePortDataEventArgs> OnRawRecordReceived;
|
|
|
|
/// <summary>
|
|
/// The port is not assigned.
|
|
/// </summary>
|
|
public const String PortNotAssigned = "NA";
|
|
|
|
/// <inheritdoc />
|
|
public virtual event EventHandler<BasePortDataEventArgs> OnRawRecordSendOut;
|
|
|
|
//initially do not signal event
|
|
private readonly AutoResetEvent _onSyncReceiveThread = new AutoResetEvent(false);
|
|
private readonly Thread _receiveThread;
|
|
private readonly CancellationTokenSource _receiveToken = new CancellationTokenSource();
|
|
|
|
/// <summary>
|
|
/// Delay between bytes if receiving has started in milliseconds
|
|
/// </summary>
|
|
private const Int32 InterByteReadDelayMs = 50;
|
|
|
|
/// <summary>
|
|
/// internal SerialPort class
|
|
/// </summary>
|
|
private readonly SerialPort _serialPort;
|
|
|
|
|
|
/// <summary>
|
|
/// Activate raw record recording
|
|
/// </summary>
|
|
public Boolean RecordStreamingRawData = false;
|
|
//public Boolean RecordStreamingRawData
|
|
//{
|
|
// get => _recordStreamingRawData;
|
|
// set => _recordStreamingRawData = value;
|
|
//}
|
|
|
|
/// <summary>
|
|
/// date and time of incoming first date interrupt on serial IO to set the
|
|
/// PC time-stamp to the record
|
|
/// </summary>
|
|
private DateTimeOffset _receiveTimeStampPc;
|
|
|
|
/// <summary>
|
|
/// Individual port settings depending on transmit protocol
|
|
/// </summary>
|
|
protected TransmitPortSettings PortSettingsForTransmitProtocol;
|
|
|
|
/// <inheritdoc />
|
|
public String GetPortName()
|
|
{
|
|
return _serialPort.PortName;
|
|
}
|
|
|
|
/// <summary>
|
|
/// store a identification of a port, this contains the Slot, Port, Protocol and Type
|
|
/// e.g. "Slot:1, Port:COM4, Protocol:Request, Type:IrDA -"
|
|
/// </summary>
|
|
public String Ident;
|
|
|
|
/// <summary>
|
|
/// ctor for SerialComPort with and full constructed <see cref="T:System.IO.Ports.SerialPort" /> properties
|
|
/// </summary>
|
|
/// <param name="ident">set port as unique</param>
|
|
/// <param name="serialPort">Class for communication, all parameters for serial communication needs to be set</param>
|
|
/// <param name="setting">Class for store all parameters for serial communication needs to be set came from transmit protocol</param>
|
|
protected BaseSerialPort(String ident, SerialPort serialPort, TransmitPortSettings setting)
|
|
{
|
|
Ident = ident;
|
|
|
|
//communication port settings
|
|
_serialPort = serialPort;
|
|
_serialPort.BaudRate = (Int32)setting.BaudRate;
|
|
|
|
PortSettingsForTransmitProtocol = setting;
|
|
|
|
//assign thread to loop
|
|
_receiveThread = new Thread(ReadingThreadLoop) { Name = $"{Ident} Reading thread" };
|
|
|
|
AsciiDataLogger = NLogHelper.CreateOrGetMultiLogger(Ident, "", "LedRawData", "TargetLedRawDataBase", "LedRawDataBase");
|
|
_byteDataLogger = NLogHelper.CreateOrGetMultiLogger(Ident, "", "UartSerialPort", "COMBase", "BaseSerialPort");
|
|
}
|
|
|
|
|
|
public void RefreshLogger()
|
|
{
|
|
|
|
AsciiDataLogger = NLogHelper.CreateOrGetMultiLogger(Ident, "", "LedRawData", "TargetLedRawDataBase", "LedRawDataBase");
|
|
_byteDataLogger = NLogHelper.CreateOrGetMultiLogger(Ident, "", "UartSerialPort", "COMBase", "BaseSerialPort");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
/// <summary>
|
|
/// received bytes in buffer of SerialComPort, calls base function
|
|
/// </summary>
|
|
/// <returns>number of bytes in Rx buffer</returns>
|
|
protected Int32 BytesToRead()
|
|
{
|
|
try
|
|
{
|
|
return _serialPort.BytesToRead;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_byteDataLogger.Error(ex);
|
|
return 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
/// <summary>
|
|
/// read single byte from Rx buffer of SerialComPort, calls base function
|
|
/// </summary>
|
|
/// <returns></returns>
|
|
protected Int32 ReadByte()
|
|
{
|
|
|
|
try
|
|
{
|
|
return _serialPort.ReadByte();
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_byteDataLogger.Error(ex);
|
|
return 0;
|
|
|
|
}
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public void PortWrite(Byte[] record)
|
|
{
|
|
try
|
|
{
|
|
SpecificPortWrite(record);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
throw new SystemException($"{Ident} Communication error while sending data to port", ex);
|
|
}
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
/// <summary>
|
|
/// flush Rx and Tx buffer of SerialComPort, calls base functions
|
|
/// </summary>
|
|
public void Clear()
|
|
{
|
|
try
|
|
{
|
|
if (!_serialPort.IsOpen)
|
|
{
|
|
return;
|
|
}
|
|
|
|
_serialPort.DiscardInBuffer();
|
|
_serialPort.DiscardOutBuffer();
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_byteDataLogger.Error(ex);
|
|
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Dispose serial port
|
|
/// </summary>
|
|
/// <remarks date="2025-Mai-12..14" author="T.Wiedebusch">
|
|
/// - Dispose procedure changed.
|
|
/// </remarks>
|
|
public void Dispose()
|
|
{
|
|
try
|
|
{
|
|
|
|
//remove registration for receive delegate
|
|
_serialPort.DataReceived -= PhysicalDataReceived;
|
|
|
|
//Cancel send and receive tokens
|
|
_receiveToken.Cancel();
|
|
|
|
//Run thread again to notice CancellationToken has changed
|
|
_onSyncReceiveThread.Set();
|
|
|
|
// dispose directly called from here to overcome glitches caused by USB to serial interface
|
|
_serialPort.Dispose();
|
|
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_byteDataLogger.Error(ex);
|
|
}
|
|
}
|
|
|
|
|
|
/// <inheritdoc />
|
|
public void Open()
|
|
{
|
|
try
|
|
{
|
|
//set receive interrupt threshold for immediate execution
|
|
_serialPort.ReceivedBytesThreshold = 1;
|
|
_serialPort.DataReceived += PhysicalDataReceived;
|
|
|
|
//start reading thread, will be immediately put to waitSleepJoin in ReadingThreadLoop
|
|
//to avoid side effects with RFID communication
|
|
|
|
ThreadWatcher.Instance.Start(_receiveThread);
|
|
|
|
if (!_serialPort.IsOpen)
|
|
{
|
|
_serialPort.Open();
|
|
if (!_serialPort.IsOpen)
|
|
{
|
|
_byteDataLogger.Error($"{Ident} Port cannot be opened");
|
|
throw new ApplicationException($"{Ident} Port cannot be opened");
|
|
}
|
|
_byteDataLogger.Info($"{Ident} Port is opened");
|
|
}
|
|
else
|
|
{
|
|
_byteDataLogger.Warn($"{Ident} Port was already opened");
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_byteDataLogger.Error($"{Ident} {ex.Message}");
|
|
throw new ApplicationException($"{Ident} {ex.Message}");
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Getting the base port
|
|
/// </summary>
|
|
protected SerialPort GetBaseComport()
|
|
{
|
|
return _serialPort;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Fill FIFO with received data
|
|
/// </summary>
|
|
/// <remarks date="2018-Feb-16" author="T.Wiedebusch">
|
|
/// - Initial
|
|
/// </remarks>
|
|
/// <remarks date="2018-Feb-21" author="T.Wiedebusch">
|
|
/// - Time stamp added
|
|
/// </remarks>
|
|
/// <remarks date="2018-Dec-14" author="T.Wiedebusch">
|
|
/// - Timeout moved from <see cref="ReadingThreadLoop"/> to PhysicalDataReceived to avoid
|
|
/// <see cref="TimeoutException"/> of serial port while waiting on first incoming
|
|
/// data.
|
|
/// </remarks>
|
|
/// <remarks date="2019-Dec-12" author="T.Wiedebusch">
|
|
/// - Inter byte read delay used instead of response delay!!!
|
|
/// </remarks>
|
|
|
|
/// <param name="sender"></param>
|
|
/// <param name="e"></param>
|
|
public virtual void PhysicalDataReceived(Object sender, SerialDataReceivedEventArgs e)
|
|
{
|
|
if (!_receiveToken.IsCancellationRequested)
|
|
{
|
|
//remove event delegate to avoid repeated execution during one record
|
|
_serialPort.DataReceived -= PhysicalDataReceived;
|
|
|
|
//put the actual time stamp to this record being able to assign it correctly
|
|
//even if the decoding is delayed. This time stamp will be used to do the first
|
|
//synchronization at start and stop of the measurement. Therefore, the serial buffer
|
|
//has to be flushed in advance to avoid wrong time stamp to "old" records
|
|
_receiveTimeStampPc = DateTimeOffset.UtcNow;
|
|
|
|
//start timeout for hanging read communication called inter byte delay
|
|
_serialPort.ReadTimeout = InterByteReadDelayMs;
|
|
// _serialPort.ReadTimeout = PortSettingsForTransmitProtocol.ResponseTimeoutMs;
|
|
|
|
//wake up the reading threat from JoinWaitSleep
|
|
_onSyncReceiveThread.Set();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// <see cref="SyncMarkRecord" />
|
|
/// </summary>
|
|
private SyncMarkRecord _syncMarkRecord;
|
|
|
|
/// <inheritdoc />
|
|
public void SynchronizeReceiveBuffer(SyncMarkRecord syncMarkRecord)
|
|
{
|
|
try
|
|
{
|
|
|
|
//not in test bench situation only for temp logging
|
|
if (syncMarkRecord == SyncMarkRecord.FlushBuffer)
|
|
{
|
|
FlushBuffer();
|
|
return;
|
|
}
|
|
//DecodeEveryPackage is ongoing and SkipDecoding is requested = do nothing
|
|
if (_syncMarkRecord == SyncMarkRecord.DecodeEveryPackage &&
|
|
syncMarkRecord == SyncMarkRecord.SkipDecoding)
|
|
{
|
|
return;
|
|
}
|
|
//deny intermediate record when a start or end record is requested
|
|
if (syncMarkRecord == SyncMarkRecord.DecodeIntermediate &&
|
|
(_syncMarkRecord == SyncMarkRecord.SyncEnd ||
|
|
_syncMarkRecord == SyncMarkRecord.SyncStart))
|
|
{
|
|
return;
|
|
}
|
|
//being able to detect the first incoming record after a sync is requested, the buffer is going to be flushed
|
|
//if the measurement will be started or stopped
|
|
if (syncMarkRecord == SyncMarkRecord.SyncEnd ||
|
|
syncMarkRecord == SyncMarkRecord.SyncStart ||
|
|
syncMarkRecord == SyncMarkRecord.DecodeIntermediate)
|
|
{
|
|
FlushBuffer();
|
|
}
|
|
|
|
_byteDataLogger.Info($"{Ident} Mark next incoming record as {syncMarkRecord} (was {_syncMarkRecord})");
|
|
_syncMarkRecord = syncMarkRecord;
|
|
|
|
|
|
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_byteDataLogger.Error($"{Ident} {ex.Message}");
|
|
}
|
|
}
|
|
|
|
private void FlushBuffer()
|
|
{
|
|
//check if flushing is allowed
|
|
if (null != PortSettingsForTransmitProtocol.ReceiveBufferFlushThreshold)
|
|
{
|
|
//flush buffer above threshold to get an accurate actual record with next record coming in
|
|
if (_serialPort.IsOpen && _serialPort.BytesToRead > PortSettingsForTransmitProtocol.ReceiveBufferFlushThreshold)
|
|
{
|
|
_byteDataLogger.Warn($"{Ident} Flushed receive buffer({_serialPort.BytesToRead}Byte)");
|
|
_serialPort.DiscardInBuffer();
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Reading thread loop getting data from serial port
|
|
/// </summary>
|
|
/// <remarks date="2018-Feb-16" author="T.Wiedebusch">
|
|
/// - Initial
|
|
/// </remarks>
|
|
/// <remarks date="2018-Mar-08" author="T.Wiedebusch">
|
|
/// - Reading will be executed and repeated until buffer is empty or timeout
|
|
/// </remarks>
|
|
/// <remarks date="2018-Mar-09" author="T.Wiedebusch">
|
|
/// - Receive byte protocol more dynamically on position of length information in record
|
|
/// and additional length,
|
|
/// - decoding of data controlled by dataSyncMarker!= SyncMarkRecord.SkipDecoding to speed up recording
|
|
/// </remarks>
|
|
/// <remarks date="2018-Oct-23" author="T.Wiedebusch">
|
|
/// - Directly invoked onRawRecord Received event,
|
|
/// - Doubling of sync byte implemented
|
|
/// </remarks>
|
|
/// <remarks date="2018-Dec-14" author="T.Wiedebusch">
|
|
/// - Timeout moved from ReadingThreadLoop to <see cref="PhysicalDataReceived"/> to avoid
|
|
/// <see cref="TimeoutException"/> of serial port while waiting on first incoming
|
|
/// data and flush receive buffer at timeout to force task to enter JoinWaitSleep state.
|
|
/// </remarks>
|
|
/// <remarks date="2019-Apr-11" author="T.Wiedebusch">
|
|
/// - Hide data in logging for e.g. passwords
|
|
/// </remarks>
|
|
/// <remarks date="2019-Dec-03" author="T.Wiedebusch">
|
|
/// - Raw record recording for missing sync-byte issues.
|
|
/// </remarks>
|
|
/// <remarks date="2019-Dec-04" author="T.Wiedebusch">
|
|
/// - Try to recover raw record on missing SYNC byte by adding the SYNC upfront and sending
|
|
/// the record to the decoding thread which will detect if just the SYNC byte had been
|
|
/// missed, then the CRC will match and the record can be decoded.
|
|
/// </remarks>
|
|
/// <remarks date="2019-Dec-05" author="T.Wiedebusch">
|
|
/// - Improved recovering of data sets at byte records with preceding SYNC byte:
|
|
/// - used payload length == 0 to skip decoding, it makes no sense to decode something where
|
|
/// the payload is empty,
|
|
/// - wait a certain time to complete incoming data.
|
|
/// </remarks>
|
|
/// <remarks date="2019-Dec-12" author="T.Wiedebusch">
|
|
/// - Inter byte read delay used instead of response delay.
|
|
/// </remarks>
|
|
/// <remarks date="2019-Dec-14" author="T.Wiedebusch">
|
|
/// - Flush buffer if received length is 0.
|
|
/// </remarks>
|
|
/// <remarks date="2022-Jul-15" author="T.Wiedebusch">
|
|
/// - Flush buffer (DiscardInBuffer) removed as some bytes are missing from time to time.
|
|
/// The IrDA sniffer showed these on the communication line, but they are incompletely received.
|
|
/// Assuming the DiscardInBuffer may be delayed, so the incoming data will be scrapped.
|
|
/// </remarks>
|
|
/// <remarks date="2025-Mai-12..14" author="T.Wiedebusch">
|
|
/// - Dispose procedure changed.
|
|
/// </remarks>
|
|
/// <remarks date="2026-Jan-06" author="T.Wiedebusch">
|
|
/// - Replaced 'ReadLine' with 'ReadTo' using a string delimiter to support others than LF "\n".
|
|
/// </remarks>
|
|
private void ReadingThreadLoop()
|
|
{
|
|
//put the receive thread to JoinWaitSleep to avoid reading and logger output for timeout on
|
|
//RFID communication, because RFID will handle the physical receive by itself
|
|
//here the assignment of the _synReadingThreadEvent to the reading thread is being done
|
|
_onSyncReceiveThread.WaitOne();
|
|
|
|
try
|
|
{
|
|
while (!_receiveToken.IsCancellationRequested)
|
|
{
|
|
try
|
|
{
|
|
//remind data marker for this Thread execution time slice
|
|
var dataSyncMarkThisRun = _syncMarkRecord;
|
|
|
|
//read line of ASCII indicated by syncByte == null
|
|
if (null == PortSettingsForTransmitProtocol.ProtSyncByte)
|
|
{
|
|
//exit is timeout from serialPort or received line
|
|
var rxStringRecord = _serialPort.ReadLine();
|
|
|
|
if (!string.IsNullOrEmpty(rxStringRecord))
|
|
{
|
|
//decode optional at synchronized data SyncStart, SyncEnd or DecodeIntermediate
|
|
//todo remove after air problem
|
|
if (SyncMarkRecord.SkipDecoding != dataSyncMarkThisRun)
|
|
{
|
|
OnRawRecordReceived?.Invoke(this, new StringPortDataEventArgs(rxStringRecord,
|
|
_receiveTimeStampPc, dataSyncMarkThisRun));
|
|
}
|
|
|
|
if (RecordStreamingRawData)
|
|
{
|
|
AsciiDataLogger.Trace($"{rxStringRecord}");
|
|
}
|
|
}
|
|
}
|
|
//byte record with preceding SYNC byte
|
|
else
|
|
{
|
|
//normally decoding is required
|
|
dataSyncMarkThisRun = SyncMarkRecord.DecodeEveryPackage;
|
|
|
|
//this is the record for decoding
|
|
var rxByteRecord = new List<Byte>();
|
|
|
|
//this is the informational record for logging on undetected SYNC byte
|
|
var rxRawRecord = new List<Byte>();
|
|
|
|
//each byte protocol has to start with a syncByte, this has to be detected first
|
|
Byte rxByte;
|
|
do
|
|
{
|
|
rxByte = (Byte)_serialPort.ReadByte();
|
|
//record raw data stream for output on missing sync-byte to investigate this issue
|
|
rxRawRecord.Add(rxByte);
|
|
} while (_serialPort.BytesToRead > 0 && PortSettingsForTransmitProtocol.ProtSyncByte != rxByte);
|
|
|
|
// if syncByte has not been detected skip read loop and wait for next incoming record
|
|
if (PortSettingsForTransmitProtocol.ProtSyncByte == rxByte)
|
|
{
|
|
//save received SYNC byte
|
|
rxByteRecord.Add(rxByte);
|
|
|
|
//to assemble the length it has to be extracted first from the record at given index,
|
|
//the payload length index cannot be 0 because at this position is always the SYNC byte,
|
|
//the overall length includes the SYNC byte!
|
|
var length = PortSettingsForTransmitProtocol.ProtLengthIndex + 1;
|
|
|
|
//if the protLengthIndex is null the protAddLength equals the entire record length
|
|
if (null == PortSettingsForTransmitProtocol.ProtLengthIndex)
|
|
{
|
|
length = PortSettingsForTransmitProtocol.ProtAddLength;
|
|
}
|
|
|
|
//read until length index to extract the length from the record
|
|
//the length index cannot be at position 0, because this is always the syncByte
|
|
do
|
|
{
|
|
rxByte = (Byte)_serialPort.ReadByte();
|
|
//read the next byte if doubled sync byte detected and required by transmit protocol settings
|
|
if (PortSettingsForTransmitProtocol.DoubleSyncByte)
|
|
{
|
|
if (rxByte.Equals(PortSettingsForTransmitProtocol.ProtSyncByte) &&
|
|
rxByteRecord[rxByteRecord.Count - 1]
|
|
.Equals(PortSettingsForTransmitProtocol.ProtSyncByte))
|
|
{
|
|
//skips this byte and read the next one
|
|
rxByte = (Byte)_serialPort.ReadByte();
|
|
}
|
|
}
|
|
|
|
//add byte to receive result buffer
|
|
rxByteRecord.Add(rxByte);
|
|
|
|
//capture the length at given index and readjust the record length
|
|
if (null != PortSettingsForTransmitProtocol.ProtLengthIndex
|
|
&& rxByteRecord.Count - 1 == PortSettingsForTransmitProtocol.ProtLengthIndex)
|
|
{
|
|
//if the received length indicates empty payload, then nothing is to decode
|
|
if (rxByte == 0)
|
|
{
|
|
//exit this loop
|
|
dataSyncMarkThisRun = SyncMarkRecord.SkipDecoding;
|
|
FlushBuffer();
|
|
}
|
|
else
|
|
{
|
|
//build the new length to continue this receive loop
|
|
length = (UInt16)(PortSettingsForTransmitProtocol.ProtAddLength + rxByte);
|
|
}
|
|
}
|
|
|
|
//wait a certain time to let the data stream coming in
|
|
if (length - rxByteRecord.Count - 1 > _serialPort.BytesToRead)
|
|
{
|
|
Thread.Sleep(2);
|
|
}
|
|
|
|
} while (rxByteRecord.Count < length && dataSyncMarkThisRun != SyncMarkRecord.SkipDecoding);
|
|
|
|
//all data received or skip decoding marked, skip decoding will clear the response timeout
|
|
//these protocols have always to be decoded because the base is a request protocol
|
|
OnRawRecordReceived?.Invoke(this, new ListBytePortDataEventArgs(rxByteRecord,
|
|
_receiveTimeStampPc, dataSyncMarkThisRun));
|
|
}
|
|
else
|
|
{
|
|
//output actual byte and recorded raw data stream to investigate missed sync-byte
|
|
_byteDataLogger.Warn($"{Ident} Response SYNC Byte missing. Raw Byte Received: " +
|
|
$"{BitConverter.ToString(rxRawRecord.ToArray())}");
|
|
}
|
|
}
|
|
}
|
|
catch (ThreadAbortException)
|
|
{
|
|
_byteDataLogger.Info($"{Ident} Thread abort exception fired!");
|
|
}
|
|
catch (TimeoutException)
|
|
{
|
|
//ATTENTION: This "_serialPort.DiscardInBuffer()" caused a lot of trouble as it flushes a few incoming
|
|
//bytes and therefore destroys the already started data stream. Here it had been left in to indicate
|
|
//this critical issue!
|
|
/*-------------------------------DO NOT ACTIVATE-----------------------------------------------------*/
|
|
//flush receive buffer at timeout to force task to enter JoinWaitSleep state in finally
|
|
//if (_serialPort.IsOpen)
|
|
//{
|
|
// _serialPort.DiscardInBuffer();
|
|
//}
|
|
/*---------------------------------------------------------------------------------------------------*/
|
|
|
|
_byteDataLogger.Trace($"{Ident} Read timeout({InterByteReadDelayMs}ms)");
|
|
//_byteDataLogger.Trace($"{Ident} Read timeout({PortSettingsForTransmitProtocol.ResponseTimeoutMs}ms)");
|
|
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
if (_receiveThread.ThreadState == ThreadState.Aborted
|
|
|| _receiveThread.ThreadState == ThreadState.AbortRequested)
|
|
{
|
|
_byteDataLogger.Warn(ex, $"{Ident} ThreadState is Aborted or AbortRequested but an " +
|
|
"error occurred while reading records from serial port");
|
|
}
|
|
else
|
|
{
|
|
_byteDataLogger.Error(ex, $"{Ident} Error while reading records from serial port");
|
|
}
|
|
}
|
|
finally
|
|
{
|
|
if (_serialPort.IsOpen && !_receiveToken.IsCancellationRequested)
|
|
{
|
|
//it makes no sense to put the thread to sleep if there is something to read
|
|
if (0 == _serialPort.BytesToRead)
|
|
{
|
|
//restore event delegate to activate handle for incoming records
|
|
_serialPort.DataReceived += PhysicalDataReceived;
|
|
|
|
//put this thread (receive thread) to JainWaitSleep
|
|
_onSyncReceiveThread.WaitOne();
|
|
}
|
|
}
|
|
}
|
|
} //while (!_tokenReadData.IsCancellationRequested)
|
|
} // over hole reading thread loop
|
|
catch (ThreadAbortException)
|
|
{
|
|
_byteDataLogger.Info($"{Ident} Thread abort exception fired!");
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_byteDataLogger.Error($"{Ident} {ex.Message}");
|
|
}
|
|
//finally
|
|
//{
|
|
// //var retries = 5;
|
|
// //while (_serialPort != null && _serialPort.IsOpen && retries-- > 0)
|
|
// //{
|
|
// // _serialPort.Close();
|
|
// // if (_serialPort.IsOpen)
|
|
// // {
|
|
// // _byteDataLogger.Info($"{Ident} Port closing delay!");
|
|
// // Thread.Sleep(500);
|
|
// // }
|
|
// //}
|
|
// //if (_serialPort != null && _serialPort.IsOpen)
|
|
// // _byteDataLogger.Info($"{Ident} Port unable to close");
|
|
// //else
|
|
// // _byteDataLogger.Info($"{Ident} Port is closed");
|
|
//}
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public Boolean IsOpen()
|
|
{
|
|
return _serialPort != null && _serialPort.IsOpen;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Serial port specific write routine
|
|
/// </summary>
|
|
/// <param name="txBuffer"></param>
|
|
/// <remarks date="2019-Apr-11" author="T.Wiedebusch">
|
|
/// - Hide data in logging for e.g. passwords
|
|
/// </remarks>
|
|
protected virtual void SpecificPortWrite(Byte[] txBuffer)
|
|
{
|
|
PhysicalWrite(txBuffer);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Physically sending to the serial port
|
|
/// </summary>
|
|
/// <remarks date="2017" author="R.Drabesch">
|
|
/// - Initial
|
|
/// </remarks>
|
|
/// <remarks date="2018-Oct-23" author="T.Wiedebusch">
|
|
/// - Doubling of sync byte in protocol behind sync byte itself implemented
|
|
/// </remarks>
|
|
/// <remarks date="2022-Feb-25" author="T.Wiedebusch, R.Drabesch">
|
|
/// - Flush buffer as MOXA sometimes takes two messages and combines these to one!
|
|
/// As the Genesis needs a separated wake-up message with a following delay before the
|
|
/// real payload message, this causes a lot of trouble.
|
|
/// </remarks>
|
|
protected void PhysicalWrite(Byte[] txBuffer)
|
|
{
|
|
try
|
|
{
|
|
//don't double the sync byte itself
|
|
var txByteList = new List<Byte> { txBuffer[0] };
|
|
for (var byteCtr = 1; byteCtr < txBuffer.Length; byteCtr++)
|
|
{
|
|
txByteList.Add(txBuffer[byteCtr]);
|
|
|
|
//write the doubled sync byte again if required by transmit protocol settings
|
|
if (PortSettingsForTransmitProtocol.DoubleSyncByte)
|
|
{
|
|
if (txBuffer[byteCtr].Equals(PortSettingsForTransmitProtocol.ProtSyncByte))
|
|
{
|
|
txByteList.Add(txBuffer[byteCtr]);
|
|
}
|
|
}
|
|
}
|
|
|
|
_serialPort.Write(txByteList.ToArray(), 0, txByteList.Count);
|
|
|
|
// flush the buffer for MOXA, to avoid two subsequent communications assembled to one communication!
|
|
_serialPort.BaseStream.Flush();
|
|
|
|
OnRawRecordSendOut?.Invoke(this, new ListBytePortDataEventArgs(txByteList, DateTimeOffset.UtcNow));
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_byteDataLogger.Error($"{Ident} {ex.Message}");
|
|
}
|
|
}
|
|
}
|
|
}
|