laatzen/Common/Hardware/Interfaces/Protocols/TransmitProtocol/IrdaTransmitProtocol.cs
2021-10-01 11:09:20 +02:00

166 lines
7.1 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;
// 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 IrdaMessage = 0x03;
// IrdaSyncByte|IrdaReceiveHeader|IrdaPayLoadLength|IrdaMessage|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 = 50;
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 IrdaMessage identifier check as it was observed receiving a 0x03 or a 0x04 in this field.
/// </remarks>
public override List<Byte> DecodeDataForLogicLayer(List<Byte> irdaRecord)
{
Logger.Info($"DecodeDataForPhysicalLayer 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[IrdaMessageIndex] != IrdaMessage )
{
var error = new ApplicationException("Reply format from IrDA is invalid. Decoding of transmit layer failed.");
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("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>
public override List<Byte> DecodeDataForPhysicalLayer(Byte requestProtocolCommand, Byte[] requestProtocolPayload)
{
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));
Logger.Info($"DecodeDataForPhysicalLayer PC->Cordonel({BitConverter.ToString(irdaRecord.ToArray())})");
return irdaRecord;
}
}
}