common/Hardware/Interfaces/Ports/SerialPorts/RfidSerialPort.cs
2026-04-23 17:50:07 +02:00

603 lines
25 KiB
C#

using System;
using System.IO.Ports;
using System.Linq;
using System.Threading;
using Xylem.Common.Utils.Crc16Ccitt;
using Xylem.Common.Utils.Logging;
using Xylem.Common.Hardware.Interfaces.Ports.PortCore;
using Xylem.Common.Hardware.Interfaces.Ports.PortCore.EventArguments;
using NLog;
namespace Xylem.Common.Hardware.Interfaces.Ports.SerialPorts
{
/// <inheritdoc />
public class RfidSerialPort : BaseSerialPort
{
/// <inheritdoc />
public override event EventHandler<BasePortDataEventArgs> OnRawRecordReceived;
private const Byte RfidFrameStartId = 0x01;
private const Byte RfidFrameTxLength = 0x12;
private const Byte RfidFrameRxLength = 0x0C;
private const Byte RfidFrameExpectedRxLength = 0x0A;
private const Byte RfidFramePayloadDataMarker = 0x7D;
private const Byte RfidRxModeMarker = 0x7E;
private const Byte RfidFramePollingByte = 0x03;
private const Int32 RfidRxStartSyncPosition = 0;
private const Int32 RfidRxLengthPosition = 1;
private const Int32 RfidRxModePosition = 4;
private const Int32 RfidRxDataPosition = 5;
private const Int32 RfidRxDataMarkerPosition = 11;
private const Int32 RfidRxCrcLowPosition = 12;
private const Int32 RfidRxCrcHighPosition = 13;
private const Int32 RfidRxBccPosition = 14;
private const Int32 RfidCommRetries = 4;
private const Int32 RfidStartPatternLength = 10;
//additional frame length for start, length and BCC added to frame length
//being in the length itself
private const Int32 RfidProtAddLength = 3;
private const Int32 RfidRxLength = RfidFrameRxLength + RfidProtAddLength;
private const Int32 RfidTxLength = RfidFrameTxLength + RfidProtAddLength;
//first byte is fixed to 0x7D (data mode) at the Tx or the last byte behind
//the payload at Rx, the others are from the device protocol (the payload),
//this byte needs to be included into the CRC calculation
private const Int32 RfidPayLoadLength = 7;
private const Int32 RfidRawPayLoadLength = 6;
//the raw payload to add to RFID transmit buffer, it has a start Id, a length position
//and some additional length needed to add to the raw Rx length
private Byte[] _rawTxPayLoad;
private Int32 _rawRxLength;
private Byte[] _rawRxPayLoad;
//create RFID payload
private Byte[] _rfidTxPayLoad;
private Int32 _payLoadTxCounterPosition;
private Int32 _payLoadRxCounterPosition;
//remind send state
private Boolean _txIsActive;
//marker for first protocol containing the start id and the length
private Boolean _waitRawStartSyncPattern;
//this is the RFID raw buffer being sent to the serial port
private Byte[] _rfidTxBuffer = new Byte[RfidTxLength];
private Byte[] _rfidRxBuffer = new Byte[RfidRxLength];
private Int32 _rfidCommRetryCounter = RfidCommRetries;
private RfidRxState _rfidRxState;
private readonly ILogger _logger;
//communication counter for debug for application layer
private Int32 _commCounter;
/// <inheritdoc />
/// <remarks date="2017-Dec-14" author="T.Wiedebusch">
/// - Initial
/// </remarks>
public RfidSerialPort(String ident, SerialPort serialPort, TransmitPortSettings setting)
: base(ident, serialPort, setting)
{
_logger = NLogHelper.CreateOrGetMultiLogger(ident, "", "RfidSerialPort", "COMBase", "BaseSerialPort");
}
/// <inheritdoc />
/// <remarks date="2017-Dec-14" author="T.Wiedebusch">
/// - Not needed and therefor deactivated by overwriting
/// </remarks>
public override void PhysicalDataReceived(Object sender, SerialDataReceivedEventArgs e)
{
//remove registration of delegate
GetBaseComport().DataReceived -= PhysicalDataReceived;
}
/// <inheritdoc />
/// <summary>
/// Overwritten routine, kicks off the first communication,
/// handles the communication state machine
/// </summary>
/// <param name="tx"></param>
/// <remarks date="2017-Dec-15" author="R.Drabesch">
/// - Not needed and therefor deactivated by overwriting
/// </remarks>
/// <remarks date="2017-Dec-17" author="T.Wiedebusch">
/// - communication scheduler removed, call directly the stats machine
/// </remarks>
/// <remarks date="2019-Apr-11" author="T.Wiedebusch">
/// - Hide data in logging for e.g. passwords
/// </remarks>
protected override void SpecificPortWrite(Byte[] tx)
{
//raw protocol before wrapped into RFID
SendDataViaRfid(tx);
RfidRxState rfidState;
//call state machine until ready
do
{
rfidState = RfidCommStateMachine();
} while (RfidRxState.CommunicationFinished != rfidState &&
RfidRxState.CommunicationFailed != rfidState);
}
/// <summary>
/// Send data via RFID
/// </summary>
/// <param name="data">data to be transferred via RFID</param>
/// <remarks date="2017-Dec-14" author="T.Wiedebusch">
/// - Initial
/// </remarks>
/// <remarks date="2017-Dec-15" author="T.Wiedebusch">
/// - Receive size removed, will be automatically extracted from underlay-protocol (raw)
/// </remarks>
/// <remarks date="2017-Dec-17" author="T.Wiedebusch">
/// - Raw buffer increased to communication retries * raw packet size.
/// </remarks>
public void SendDataViaRfid(Byte[] data)
{
if (data.Length < RfidRawPayLoadLength) return;
//length is unknown before receiving the first raw data, setup to x packets size for
//receive routine
_rawRxLength = RfidRawPayLoadLength * _rfidCommRetryCounter;
_rawRxPayLoad = new Byte[_rawRxLength];
_waitRawStartSyncPattern = true;
//start with sending of data
_txIsActive = true;
//allow retries during communication
_rfidCommRetryCounter = RfidCommRetries;
//reset running counters
_payLoadTxCounterPosition = 0;
_payLoadRxCounterPosition = 0;
_commCounter = 0;
//copy part of the huge payload buffer (containing the entire data) to small
//6 byte chunks being able to transmit within one RFID communication
_rfidTxPayLoad = null;
_rfidTxPayLoad = new Byte[RfidRawPayLoadLength];
_rawTxPayLoad = new Byte[data.Length];
_rawTxPayLoad = data;
for (var i = 0; i < RfidRawPayLoadLength; i++)
{
_rfidTxPayLoad[i] = _rawTxPayLoad[_payLoadTxCounterPosition];
_payLoadTxCounterPosition++;
}
//assemble the RFID transmit buffer
PrepareRfidTxBuffer(ref _rfidTxBuffer, _rfidTxPayLoad);
//kick off first communication
_rfidRxState = RfidComm(ref _rfidRxBuffer, _rfidTxBuffer);
}
/// <summary>
/// RFID Tx buffer content
/// </summary>
/// <returns>array of RFID Tx buffer content</returns>
/// <remarks date="2017-Dec-14" author="T.Wiedebusch">
/// - Initial
/// </remarks>
public Byte[] GetRfidTxBuffer()
{
return _rfidTxBuffer;
}
/// <summary>
/// RFID Rx buffer content
/// </summary>
/// <returns>array of RFID Rx buffer content</returns>
/// <remarks date="2017-Dec-14" author="T.Wiedebusch">
/// - Initial
/// </remarks>
public Byte[] GetRfidRxBuffer()
{
return _rfidRxBuffer;
}
/// <summary>
/// read out the received data from RFID
/// </summary>
/// <returns></returns>
/// <remarks date="2017-Dec-14" author="T.Wiedebusch">
/// - Initial
/// </remarks>
public Byte[] GetDecodedDataFromRfid()
{
return _rawRxPayLoad;
}
/// <summary>
/// feedback of communication counter
/// </summary>
/// <returns>number of RFID communications</returns>
/// <remarks date="2017-Dec-14" author="T.Wiedebusch">
/// - Initial
/// </remarks>
public Int32 GetRfidComCounter()
{
return _commCounter;
}
/// <summary>
/// communication state machine
/// </summary>
/// <returns></returns>
/// <remarks date="2017-Dec-14" author="T.Wiedebusch">
/// - Initial
/// </remarks>
/// <remarks date="2017-Dec-15" author="T.Wiedebusch">
/// - Extract length information from raw protocol
/// </remarks>
/// <remarks date="2017-Dec-17" author="T.Wiedebusch">
/// - Return of communication failed on missing start sync pattern of raw protocol
/// </remarks>
/// <remarks date="2017-Dec-19" author="T.Wiedebusch">
/// - New modes implemented to differ between sending / waiting for Rx start
/// and receiving
/// </remarks>
/// <remarks date="2018-Feb-21" author="T.Wiedebusch">
/// - Time stamp added
/// </remarks>
/// <remarks date="2019-Apr-12" author="T.Wiedebusch">
/// - Hide data in logging for e.g. passwords
/// </remarks>
public RfidRxState RfidCommStateMachine()
{
//check the receive status
switch (_rfidRxState)
{
case RfidRxState.EchoRxProtocolOk:
//clear payload buffer to force filling with polling pattern
_rfidTxPayLoad = null;
//send loop
if (_txIsActive)
{
if (_payLoadTxCounterPosition < _rawTxPayLoad.Length)
{
//assign new Tx buffer
Int32 arraySize;
if (_payLoadTxCounterPosition + RfidRawPayLoadLength <
_rawTxPayLoad.Length)
arraySize = RfidRawPayLoadLength;
else
arraySize = _rawTxPayLoad.Length - _payLoadTxCounterPosition;
_rfidTxPayLoad = new Byte[arraySize];
//fill payload buffer with remaining payload bytes
for (var i = 0; i < arraySize; i++)
{
if (_payLoadTxCounterPosition >= _rawTxPayLoad.Length) continue;
_rfidTxPayLoad[i] = _rawTxPayLoad[_payLoadTxCounterPosition];
_payLoadTxCounterPosition++;
}
}
else
{
//switch receive loop active
_txIsActive = false;
}
}
//reset retry counter
_rfidCommRetryCounter = RfidCommRetries;
//assemble new RFID transmit buffer
PrepareRfidTxBuffer(ref _rfidTxBuffer, _rfidTxPayLoad);
//start communication
_rfidRxState = RfidComm(ref _rfidRxBuffer, _rfidTxBuffer);
break;
case RfidRxState.DataRxProtocolOk:
//receive loop with polling pattern sending
for (var i = 0; i < RfidRawPayLoadLength; i++)
{
//avoid out of bounce access
if (_payLoadRxCounterPosition >= _rawRxLength) continue;
_rawRxPayLoad[_payLoadRxCounterPosition] =
_rfidRxBuffer[i + RfidRxDataPosition];
_payLoadRxCounterPosition++;
}
//check for entire message received
if (_payLoadRxCounterPosition >= _rawRxLength)
{
//assign time stamp of received record
var readDateTimePc = DateTimeOffset.UtcNow;
//communication finished, return data to caller
//RawRecordReceived(this, _rawRxPayLoad, readDateTimePc);
var byteList = _rawRxPayLoad.ToList();
OnRawRecordReceived?.Invoke(this, new ListBytePortDataEventArgs(byteList, readDateTimePc));
//log the RFID payload, RFID protocol is removed
//_logger.Trace($"{Ident} Read({BitConverter.ToString(_rawRxPayLoad)})");
return _waitRawStartSyncPattern ? RfidRxState.CommunicationFailed :
RfidRxState.CommunicationFinished;
}
//reset retry counter
_rfidCommRetryCounter = RfidCommRetries;
//start communication with polling pattern, has been assembled in last send run
_rfidRxState = RfidComm(ref _rfidRxBuffer, _rfidTxBuffer);
break;
case RfidRxState.Idle:
break;
default:
//RFID or device is not ready or protocol error, start retry
if (_rfidCommRetryCounter > 0)
{
_logger.Trace($"{Ident} Internal RFID retry");
_rfidCommRetryCounter--;
//send the same RFID message again
_rfidRxState = RfidComm(ref _rfidRxBuffer, _rfidTxBuffer);
}
else
{
//communication failed because of exceeded internal RFID retry counter
_rfidRxState = RfidRxState.CommunicationFailed;
_logger.Trace($"{Ident} Internal RFID communication failed, RFID retry counter exceeded");
}
break;
}
return _rfidRxState;
}
/// <summary>
/// send and receive
/// </summary>
/// <param name="rxBuffer"></param>
/// <param name="txBuffer"></param>
/// <returns>RfidRxState</returns>
/// <remarks date="2017-Dec-14" author="T.Wiedebusch">
/// - Initial
/// </remarks>
/// <remarks date="2017-Dec-18" author="T.Wiedebusch">
/// - Communication timeout activated.
/// </remarks>
/// <remarks date="2019-Apr-12" author="T.Wiedebusch">
/// - Hide data in logging for e.g. passwords
/// </remarks>
private RfidRxState RfidComm(ref Byte[] rxBuffer, Byte[] txBuffer)
{
var rfidRxState = RfidRxState.CommPortError;
if (!IsOpen()) return rfidRxState;
Clear();
//_logger.Trace($"{Ident} RFID raw sent({BitConverter.ToString(txBuffer)})");
PhysicalWrite(txBuffer);
_commCounter++;
//set timeout for receive exit, take 5 records of 6 byte chunks as base
var commTimeoutMs = PortSettingsForTransmitProtocol.ResponseTimeoutMs / 5;
while (commTimeoutMs > 0 && BytesToRead() < RfidRxLength)
{
Thread.Sleep(1);
commTimeoutMs--;
}
var i = 0;
//test if something is in input buffer
if (RfidRxLength > BytesToRead())
{
_logger.Trace($"{Ident} Internal RFID read timeout({PortSettingsForTransmitProtocol.ResponseTimeoutMs}ms)");
return RfidRxState.RxTimeout;
}
while (BytesToRead() > 0 && i < RfidRxLength)
{
rxBuffer[i] = (Byte)ReadByte();
i++;
}
if (i != RfidRxLength) return rfidRxState;
//all expected bytes received
//_logger.Trace($"{Ident} RFID raw read({BitConverter.ToString(rxBuffer)})");
rfidRxState = CheckRfidRxBuffer(rxBuffer);
return rfidRxState;
}
/// <summary>
/// check the received buffer CRC, BCC and
/// </summary>
/// <param name="rfidRxBuffer"></param>
/// <remarks date="2017-Dec-14" author="T.Wiedebusch">
/// - Initial
/// </remarks>
/// <remarks date="2017-Dec-19" author="T.Wiedebusch">
/// - New modes implemented to differ between sending / waiting for Rx start
/// and receiving
/// </remarks>
/// <returns>RfidRxState</returns>
private RfidRxState CheckRfidRxBuffer(Byte[] rfidRxBuffer)
{
//retry required by start sync error
if (RfidFrameStartId != rfidRxBuffer[RfidRxStartSyncPosition])
return RfidRxState.StartSyncError;
//retry required by length error
if (RfidFrameRxLength != rfidRxBuffer[RfidRxLengthPosition])
return RfidRxState.LengthError;
//data marked as useful data
if (RfidRxModeMarker != rfidRxBuffer[RfidRxModePosition] ||
RfidFramePayloadDataMarker != rfidRxBuffer[RfidRxDataMarkerPosition])
return RfidRxState.RetryRequired;
//BCC check transfer entire receive buffer, this will automatically handled
if (rfidRxBuffer[RfidRxBccPosition] != BuildRfidBcc(rfidRxBuffer))
return RfidRxState.BccError;
//extract the 6 data (payload) bytes and the data frame marker
var testBuffer = new Byte[RfidPayLoadLength];
for (var i = 0; i < RfidPayLoadLength; i++)
testBuffer[i] = rfidRxBuffer[i + RfidRxDataPosition];
//CRC with LSB first
var buildCrc = Crc16Ccitt.CalculateLsb0408(testBuffer);
UInt16 receivedCrc = rfidRxBuffer[RfidRxCrcHighPosition];
receivedCrc <<= 8;
receivedCrc &= 0xFF00;
receivedCrc += rfidRxBuffer[RfidRxCrcLowPosition];
if (receivedCrc != buildCrc) return RfidRxState.DataCrcError;
//send loop is active
if (_txIsActive) return RfidRxState.EchoRxProtocolOk;
//if the start sync byte has been detected the normal receive mode is active
if (!_waitRawStartSyncPattern) return RfidRxState.DataRxProtocolOk;
//search for raw protocol start sync Id to extract length information,
//if start Id hasn't been found the wait for Rx start mode is active
//forcing the retry loop being active (default switch)
if (PortSettingsForTransmitProtocol.ProtSyncByte != _rfidRxBuffer[RfidRxDataPosition])
return RfidRxState.WaitRxStartProtocolOk;
//here the Rx mode is going to be activated, first data received including
//start sync Id and length information
_waitRawStartSyncPattern = false;
_rawRxLength = _rfidRxBuffer[RfidRxDataPosition +
(PortSettingsForTransmitProtocol.ProtLengthIndex.HasValue ?
PortSettingsForTransmitProtocol.ProtLengthIndex.Value : 0)] +
PortSettingsForTransmitProtocol.ProtAddLength;
_rawRxPayLoad = new Byte[_rawRxLength];
return RfidRxState.DataRxProtocolOk;
}
/// <summary>
/// assemble the RFID transmit buffer as bytes
/// </summary>
/// <param name="rfidTxBuffer"></param>
/// <param name="payLoad"></param>
/// <remarks date="2017-Dec-14" author="T.Wiedebusch">
/// - Initial
/// </remarks>
private static void PrepareRfidTxBuffer(ref Byte[] rfidTxBuffer, Byte[] payLoad)
{
RfidTxProtocol rfidTxStruct;
rfidTxStruct.StartPattern = new Byte[]
{
RfidFrameStartId, //start identifier 0x01
RfidFrameTxLength, //length, start behind length without BCC
0xE8, 0x90, 0x00, //CMD 1, 2, 3
0x00, 0x32, 0x00, 0x11, //Power 1 and 2
0x48 //TX bits
};
//the default data is the polling pattern 0x03
rfidTxStruct.PayLoad = new[] { RfidFramePayloadDataMarker,
RfidFramePollingByte, RfidFramePollingByte, RfidFramePollingByte,
RfidFramePollingByte, RfidFramePollingByte, RfidFramePollingByte};
//fill the payload with real data
var i = 0;
if (payLoad != null)
{
//put payload behind RFID frame payload data marker
for (; i < payLoad.Length; i++)
rfidTxStruct.PayLoad[i + 1] = payLoad[i];
}
rfidTxStruct.ExpectedRxBytes = RfidFrameExpectedRxLength;
//fill transmit buffer with constant start pattern for RFID communication
i = 0;
for (; i < RfidStartPatternLength; i++)
{
rfidTxBuffer[i] = rfidTxStruct.StartPattern[i];
}
//add payload to buffer, first byte is constant 0x7D (data mode)
var c = 0;
for (; i < RfidStartPatternLength + RfidPayLoadLength; i++)
{
rfidTxBuffer[i] = rfidTxStruct.PayLoad[c];
c++;
}
//add data CRC, LSB first
rfidTxStruct.DataCrc = Crc16Ccitt.CalculateLsb0408(rfidTxStruct.PayLoad);
rfidTxBuffer[i] = (Byte)(rfidTxStruct.DataCrc & 0xFF);
i++;
rfidTxBuffer[i] = (Byte)((rfidTxStruct.DataCrc & 0xFF00) >> 8);
//add expected receive bytes
i++;
rfidTxBuffer[i] = rfidTxStruct.ExpectedRxBytes;
//add BCC
rfidTxStruct.Bcc = BuildRfidBcc(rfidTxBuffer);
i++;
rfidTxBuffer[i] = rfidTxStruct.Bcc;
}
/// <summary>
/// build simple byte by byte XOR'ed checksum called BCC for RFID
/// </summary>
/// <param name="rfidRawBuffer"></param>
/// <returns>BCC code</returns>
/// <remarks date="2017-Dec-14" author="T.Wiedebusch">
/// - Initial
/// </remarks>
private static Byte BuildRfidBcc(Byte[] rfidRawBuffer)
{
//remove the "start byte" and the BCC itself
var maxCounts = rfidRawBuffer.Length - 1;
//start behind the "start byte"
var i = 1;
Byte bcc = rfidRawBuffer[i];
//take the second value
i++;
for (; i < maxCounts; i++)
{
bcc ^= rfidRawBuffer[i];
}
return bcc;
}
private struct RfidTxProtocol
{
public Byte[] StartPattern;
public Byte[] PayLoad;
public UInt16 DataCrc;
public Byte ExpectedRxBytes;
public Byte Bcc;
}
}
public enum RfidRxState
{
/// <summary>
/// No communication is ongoing
/// </summary>
Idle,
/// <summary>
/// Echo of sent protocol is received successfully back
/// </summary>
EchoRxProtocolOk,
/// <summary>
/// Waiting for switch from transmitting to receiving
/// </summary>
WaitRxStartProtocolOk,
/// <summary>
/// Received data message is valid
/// </summary>
DataRxProtocolOk,
/// <summary>
/// Communication port assignment error
/// </summary>
CommPortError,
/// <summary>
/// Start of synchronization error
/// </summary>
StartSyncError,
/// <summary>
/// Length error
/// </summary>
LengthError,
/// <summary>
/// Retry required
/// </summary>
RetryRequired,
/// <summary>
/// CRC error of data in payload field
/// </summary>
DataCrcError,
/// <summary>
/// BCC (special checksum) error of RFID
/// </summary>
BccError,
/// <summary>
/// Receive timeout
/// </summary>
RxTimeout,
/// <summary>
/// Communication to RFID failed, record cannot be assembled
/// </summary>
CommunicationFailed,
/// <summary>
/// Communication to RFID successfully executed
/// </summary>
CommunicationFinished
}
}