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 { /// /// IrDA transmission protocol /// 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; /// public override void SetResponseTimeout(Int32 responseTimeoutMs) { _responseTimeoutMs = responseTimeoutMs; } /// public override void SetDefaultResponseTimeout() { _responseTimeoutMs = DefaultResponseTimeoutMs; } /// public IrdaTransmitProtocol(String portName) : base(portName) { } /// public override TransmitPortSettings GetTransmitPortSettings() { return new TransmitPortSettings(IrdaSyncByte, IrdaPayLoadLengthIndex, ProtAddLength, _responseTimeoutMs, BaudRate, _receiveBufferFlushThreshold); } /// /// /// - Initial /// /// /// - Removed IrdaMessageId identifier check as it was observed receiving a 0x03 or a 0x04 in this field. /// /// /// - Wakeup message detection reported to log-file, /// - Message error text changed. /// /// /// - Hide data in log introduced. /// public override List DecodeDataForLogicLayer(String ident, List 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(); } // 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(); 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(); } // build the return value witch equals the IrDA payload var irdaPayLoad = new List(); irdaPayLoad.AddRange(irdaRecord.GetRange(IrdaPayLoadIndex, irdaRecord.Count - IrdaProtocolFrameLength)); return irdaPayLoad; } /// /// /// - Initial /// /// /// - Hide data in log forwarded to DecodeDateForPhysicalLayer to hide passwords in log files. /// public override List DecodeDataForPhysicalLayer(String ident, Byte requestProtocolCommand, Byte[] requestProtocolPayload, Boolean hideDataInLog = false) { var irdaCrcInputBuffer = new List { // 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 { 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; } /// /// /// - Initial /// public override List DecodeDataForPhysicalLayerUI1236(String ident, Byte[] requestProtocolPayload, Boolean hideDataInLog = false) { var collection = new List { 35, (Byte) (requestProtocolPayload.Length / 2) }; collection.AddRange(requestProtocolPayload); var crcResult = Crc16Ccitt.CalculateReversedLsb8408(collection.ToArray()); var byteList = new List { 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; } } }