laatzen/Common/Hardware/Interfaces/Protocols/TransmitProtocol/IrdaTransmitProtocol.cs

215 lines
9.4 KiB
C#

using System;
using System.Collections.Generic;
using Xylem.Common.Hardware.Interfaces.Ports.PortCore;
using Xylem.Common.Utils.Crc16Ccitt;
namespace Xylem.Common.Hardware.Interfaces.Protocols.TransmitProtocol
{
/// <summary>
/// IrDA transmission protocol
/// </summary>
public class IrdaTransmitProtocol : BaseTransmitProtocol
{
// Format of IrDA transmit protocol:
// IrdaSyncByte|IrdaSend/ReceiveHeader|IrdaPayLoadLength|IrdaCommand/Message|
// IrdaPayLoad|IrdaCrc LSB|IrdaCrc MSB
private const Byte IrdaSyncByte = 0x9B;
// Encoding of IrdaSendHeader (LAT: listen after talk, LAT 10b: 500ms)
// Bits: 0| 0| 10| 0| 000
// standard frame|adapter to register|LAT|reserved|optical command
private const Byte IrdaSendHeader = 0x20;
// Encoding of IrdaReceiveHeader (LAT: listen after talk, LAT 10b: 500ms)
// Bits: 0| 1| 10| 0| 001
// standard frame|register to adapter|LAT|reserved|optical message
private const Byte IrdaReceiveHeader = 0x61;
private const Byte IrdaWakeupHeader = 0x41;
// Mask out the LAT and the reserved bit for the IrdaReceiveHeader
private const Byte IrdaReceiveHeaderMask = 0xC7;
// data from adapter (software) to register (water meter) referred as optical command
private const Byte IrdaCommand = 0x02;
// data from register (water meter) to adapter (software) referred as optical message
private const Byte IrdaMessageId = 0x03;
private const Byte IrdaWakeupId = 0x04;
// IrdaSyncByte|IrdaReceiveHeader|IrdaPayLoadLength|IrdaMessageId|CRC LSB|CRC MSB
private const Int32 IrdaProtocolFrameLength = 6;
// Indexes of IrDA optical protocol
private const Int32 IrdaSyncByteIndex = 0;
private const Int32 IrdaHeaderIndex = 1;
private const Int32 IrdaPayLoadLengthIndex = 2;
private const Int32 IrdaMessageIndex = 3;
private const Int32 IrdaPayLoadIndex = 4;
//the IrDA length covers the payload length only
private const UInt16 ProtAddLength = IrdaProtocolFrameLength;
private const Int32 DefaultResponseTimeoutMs = 250;
//private const Int32 DefaultResponseTimeoutMs = 150;
private Int32 _responseTimeoutMs = DefaultResponseTimeoutMs;
private const UInt32 BaudRate = 115200;
private readonly UInt32? _receiveBufferFlushThreshold = 1;
/// <inheritdoc />
public override void SetResponseTimeout(Int32 responseTimeoutMs)
{
_responseTimeoutMs = responseTimeoutMs;
}
/// <inheritdoc />
public override void SetDefaultResponseTimeout()
{
_responseTimeoutMs = DefaultResponseTimeoutMs;
}
/// <inheritdoc />
public IrdaTransmitProtocol(String portName) : base(portName)
{
}
/// <inheritdoc />
public override TransmitPortSettings GetTransmitPortSettings()
{
return new TransmitPortSettings(IrdaSyncByte, IrdaPayLoadLengthIndex, ProtAddLength,
_responseTimeoutMs, BaudRate, _receiveBufferFlushThreshold);
}
/// <inheritdoc />
/// <remarks date="2018-Mar-15" author="T.Wiedebusch">
/// - Initial
/// </remarks>
/// <remarks date="2020-Dec-19" author="T.Wiedebusch">
/// - Removed IrdaMessageId identifier check as it was observed receiving a 0x03 or a 0x04 in this field.
/// </remarks>
/// <remarks date="2022-Jul-19" author="T.Wiedebusch">
/// - Wakeup message detection reported to log-file,
/// - Message error text changed.
/// </remarks>
/// <remarks date="2023-Sep-01" author="T.Wiedebusch">
/// - Hide data in log introduced.
/// </remarks>
public override List<Byte> DecodeDataForLogicLayer(String ident, List<Byte> irdaRecord, Boolean hideDataInLog = false)
{
// avoid logging of passwords or other sensitive data
Logger.Info(hideDataInLog
? $"{ident} DecodeDataForLogicalLayer Cordonel->PC(*****)"
: $"{ident} DecodeDataForLogicalLayer Cordonel->PC({BitConverter.ToString(irdaRecord.ToArray())})");
// check the record length and start of frame information
if (irdaRecord.Count < IrdaProtocolFrameLength + irdaRecord[IrdaPayLoadLengthIndex]
|| irdaRecord[IrdaSyncByteIndex] != IrdaSyncByte
|| (((irdaRecord[IrdaHeaderIndex] & IrdaReceiveHeaderMask) != (IrdaReceiveHeader & IrdaReceiveHeaderMask)
&& (irdaRecord[IrdaHeaderIndex] != IrdaWakeupHeader))
|| ((irdaRecord[IrdaMessageIndex] != IrdaMessageId)
&& irdaRecord[IrdaMessageIndex] != IrdaWakeupId)))
{
var error = new ApplicationException($"{ident} Reply from IrDA is invalid.");
Logger.Error(error.Message, error);
return new List<Byte>();
}
// extract the received CRC first the MSB at last position
UInt16 receivedCrc = irdaRecord[irdaRecord.Count - 1];
receivedCrc <<= 8;
// add the LSB from before last position, the LSB of the CRC will be sent first
receivedCrc += irdaRecord[irdaRecord.Count - 2];
// the CRC is being built from IrdaReceiveHeader to end of IrdaPayload,
// subtract irdaSyncByteLength and irdaCrcLength
var irdaCrcInputBuffer = new List<Byte>();
irdaCrcInputBuffer.AddRange(irdaRecord.GetRange(IrdaHeaderIndex, irdaRecord.Count - 3));
var calculatedCrc = Crc16Ccitt.CalculateReversedLsb8408(irdaCrcInputBuffer.ToArray());
if (receivedCrc != calculatedCrc)
{
var error = new ApplicationException($"{ident} Reply CRC failure. Decoding of transmit layer failed.");
Logger.Error(error.Message, error);
return new List<Byte>();
}
// build the return value witch equals the IrDA payload
var irdaPayLoad = new List<Byte>();
irdaPayLoad.AddRange(irdaRecord.GetRange(IrdaPayLoadIndex, irdaRecord.Count - IrdaProtocolFrameLength));
return irdaPayLoad;
}
/// <inheritdoc />
/// <remarks date="2018-Mar-13" author="T.Wiedebusch">
/// - Initial
/// </remarks>
/// <remarks date="2023-Mai-16" author="T.Wiedebusch">
/// - Hide data in log forwarded to DecodeDateForPhysicalLayer to hide passwords in log files.
/// </remarks>
public override List<Byte> DecodeDataForPhysicalLayer(String ident, Byte requestProtocolCommand, Byte[] requestProtocolPayload,
Boolean hideDataInLog = false)
{
var irdaCrcInputBuffer = new List<Byte>
{
// build CRC calculation buffer without sync byte
// IrdaSendHeader|IrdaPayLoadLength|IrdaCommand|requestProtocolCommand|requestProtocolPayload
IrdaSendHeader,
// IrdaPayloadLength is the requestProtocolCommandLength + requestProtocolPayloadLength
(Byte)(requestProtocolPayload.Length + 1),
IrdaCommand,
// add the IrdaPayload witch is the requestProtocolCommand + requestProtocolPayload
requestProtocolCommand
};
irdaCrcInputBuffer.AddRange(requestProtocolPayload);
var crcResult = Crc16Ccitt.CalculateReversedLsb8408(irdaCrcInputBuffer.ToArray());
// assemble result buffer
// IrdaSyncByte|crcInputBuffer|IrdaCrc LSB|IrdaCrc MSB
var irdaRecord = new List<Byte> { IrdaSyncByte };
irdaRecord.AddRange(irdaCrcInputBuffer);
irdaRecord.Add((Byte)(crcResult & 0xFF));
irdaRecord.Add((Byte)(crcResult >> 8));
// avoid logging of passwords or other sensitive data
Logger.Info(hideDataInLog
? $"{ident} DecodeDataForPhysicalLayer PC->Cordonel(*****)"
: $"{ident} DecodeDataForPhysicalLayer PC->Cordonel({BitConverter.ToString(irdaRecord.ToArray())})");
return irdaRecord;
}
/// <inheritdoc />
/// <remarks date="2022-Aug-24" author="R.Drabesch">
/// - Initial
/// </remarks>
public override List<Byte> DecodeDataForPhysicalLayerUI1236(String ident, Byte[] requestProtocolPayload,
Boolean hideDataInLog = false)
{
var collection = new List<Byte>
{
35,
(Byte) (requestProtocolPayload.Length / 2)
};
collection.AddRange(requestProtocolPayload);
var crcResult = Crc16Ccitt.CalculateReversedLsb8408(collection.ToArray());
var byteList = new List<Byte>
{
155
};
byteList.AddRange(collection);
byteList.Add((Byte)(crcResult & 0xFF));
byteList.Add((Byte)(crcResult >> 8));
// avoid logging of passwords or other sensitive data
Logger.Info(hideDataInLog
? $"{ident} DecodeDataForPhysicalLayerUI1236 PC->Cordonel(*****)"
: $"{ident} DecodeDataForPhysicalLayerUI1236 PC->Cordonel({BitConverter.ToString(byteList.ToArray())})");
return byteList;
}
}
}