using System; using System.IO.Ports; using System.Text; using System.Threading; //***************************************************************************** // Copyright 2020 Sensus GmbH Ludwigshafen. All rights reserved. // Author: Venkat, Rajeshwar //***************************************************************************** namespace Sensus.iPerl.NfcHandler { public sealed class NfcDataHandler { private string _ComPort = ""; private readonly byte[] readCommand = { 0x80, 0x08, 0x00, 0x03, 0x00, 0x00 }; public SERIAL_Driver _SERIAL_Driver; public SERIAL_Driver _SERIAL_Driver_Head_Config; public CR95HF_Reader _CR95HF_Reader; public ST25DV_Device _ST25DV_Device; public MCI_Protocol _MCI_Protocol; public NFCHeadConfig _NFCHead_Config; public byte LastErrorCode { get; set; } public string LastErrorMessage { get; set; } public byte[] LastData { get; set; } public event DelNfcMessageHandler MessageEvent; public NfcDataHandler() { _SERIAL_Driver = new SERIAL_Driver(); _SERIAL_Driver_Head_Config = new SERIAL_Driver(); _CR95HF_Reader = new CR95HF_Reader(_SERIAL_Driver); _ST25DV_Device = new ST25DV_Device(_CR95HF_Reader); _MCI_Protocol = new MCI_Protocol(_ST25DV_Device); _NFCHead_Config = new NFCHeadConfig(_SERIAL_Driver_Head_Config); LastErrorCode = 0; } public bool OpenConnection(string comport, int baudrate, int databits, Parity parity, StopBits stopbits) { _ComPort = comport; bool isopen = _SERIAL_Driver.OpenConnection(comport, baudrate, databits, parity, stopbits); if (!isopen) OnMessageEvent(_SERIAL_Driver.ErrorMessage); return isopen; } public bool isOpen() { return _SERIAL_Driver.isOpen(); } public void Close() { _SERIAL_Driver.Close(); _SERIAL_Driver.Dispose(); } public bool OpenConnectionSTM(string comport) { // helper for lazy people who don't know the comport settings or don't have // parameters of type 'Parity' or 'StopBits' in their language (Python) at hand // used here: https://slm.sms-esaap.com/iPERL/tetoo/-/tree/dev/tetoo/nfcs5_dll_wrapper return OpenConnection(comport, 57600, 8, Parity.None, StopBits.Two); } public bool NFCHead_OpenConnection(string comport) { if (isOpen() && _ComPort == comport) { OnMessageEvent(comport + "......... Already in use "); return false; } bool isopen = _SERIAL_Driver_Head_Config.OpenConnection(comport, 9600, 8, Parity.None, StopBits.Two); OnMessageEvent(_SERIAL_Driver_Head_Config.ErrorMessage); return isopen; } public bool isNFCHeadOpen() { return _SERIAL_Driver_Head_Config.isOpen(); } public void NFCHeadClose() { if (_SERIAL_Driver_Head_Config.isOpen()) { _SERIAL_Driver_Head_Config.Close(); _SERIAL_Driver_Head_Config.Dispose(); } } public bool ConnectReader() { if (_CR95HF_Reader.CR95HF_Connect()) { OnMessageEvent("CombiHead.......Connected"); return true; } else { OnMessageEvent("CombiHead.......Not connected"); return false; } } public bool RFProtocolON() { if (_CR95HF_Reader.CR95HF_RFProtocolOn()) { OnMessageEvent("NFC CR95HF RF Protocol ON........Success"); return true; } else { OnMessageEvent("NFC CR95HF RF Protocol ON........Failed"); return false; } } public bool RFProtocolOFF() { if (_CR95HF_Reader.CR95HF_RFProtocolOff()) { OnMessageEvent("NFC CR95HF RF Protocol OFF........Success"); return true; } else { OnMessageEvent("NFC CR95HF RF Protocol OFF........Failed"); return false; } } public bool ConnectDevice() { byte[] responseData; if (_CR95HF_Reader.CR95HF_RFProtocolOn()) { if (GetSystemInfo()) { responseData = LastData; if (responseData[0] == 0x0F) OnMessageEvent("Device -> ST25DV04K"); else OnMessageEvent("Device -> ST25DV16K or 64K"); OnMessageEvent("UID -> " + Tools.ByteToHexString(responseData[1]) + " " + Tools.ByteToHexString(responseData[2]) + " " + Tools.ByteToHexString(responseData[3]) + " " + Tools.ByteToHexString(responseData[4]) + " " + Tools.ByteToHexString(responseData[5]) + " " + Tools.ByteToHexString(responseData[6]) + " " + Tools.ByteToHexString(responseData[7]) + " " + Tools.ByteToHexString(responseData[8])); //OnMessageEvent("DSFID -> " + Tools.ByteToHexString(responseData[9])); //OnMessageEvent("AFI -> " + Tools.ByteToHexString(responseData[10])); //OnMessageEvent("Block size -> " + Tools.ByteToHexString(responseData[11])); //OnMessageEvent("Memory size -> " + Tools.ByteToHexString(responseData[12])); //if (responseData[13] == 0x24) // OnMessageEvent("IC Ref -> ST25DV04K"); //else // OnMessageEvent("IC Ref -> ST25DV16K or 64K"); OnMessageEvent("iPERL.......Connected"); return true; } else { OnMessageEvent("iPERL.......Not connected"); } } return false; } public bool GetSystemInfo() { byte[] responseData = null; try { if (_ST25DV_Device.ST25DV_GetSystemInfo(out responseData)) { LastData = responseData; return true; } } catch (Exception ex) { OnMessageEvent(ex.Message); } return false; } public bool ST25DV_ReadSingleBlock(byte blockNumber, out byte[] outBuffer) { return _ST25DV_Device.ST25DV_ReadSingleBlock(blockNumber, out outBuffer); } public bool ST25DV_WriteSingleBlock(byte blockNumber, byte[] inBuffer) { return _ST25DV_Device.ST25DV_WriteSingleBlock(blockNumber, inBuffer); } public bool ST25DV_ReadMultipleBlocks(byte startingBlockNumber, byte numberOfBlocks, out byte[] OutBuffer) { return _ST25DV_Device.ST25DV_ReadMultipleBlocks(startingBlockNumber, numberOfBlocks, out OutBuffer); } public bool Echo() { if (!_CR95HF_Reader.CR95HF_Echo()) { OnMessageEvent("Echo..Failed. CombiHead connection...Lost"); return false; } return true; } public bool ST25DV_ReadConfiguration(byte pointer, out byte value) { return _ST25DV_Device.ST25DV_ReadConfiguration(pointer, out value, true); } public bool ST25DV_ReadDynamicConfiguration(byte regAddress, out byte OutBuffer) { return _ST25DV_Device.ST25DV_ReadDynamicConfiguration(regAddress, out OutBuffer, true); } public bool ST25DV_WriteDynamicConfiguration(byte regAddress, byte regValue) { return _ST25DV_Device.ST25DV_WriteDynamicConfiguration(regAddress, regValue, true); } public bool ST25DV_ReadMailboxMessageLength(out byte MessageLength) { return _ST25DV_Device.ST25DV_ReadMailboxMessageLength(out MessageLength, true); } // This function-header is needed for calls to the DLL without need for using enums. // In python 3.11 the module pythonnet (v3.x) does not support casting 'int' to 'enum' aynmore, // so this workaround is needed. public bool MCI_Read_pythonwrapper_enum(int STIndex, ushort offset, byte payloadlength, int timeoutMs) { return MCI_Read((MCI_Protocol.StructName)STIndex, offset, payloadlength, timeoutMs); } public bool MCI_Read(MCI_Protocol.StructName StName, ushort offset, byte payloadlength, int timeoutMs) { LastErrorMessage = string.Empty; byte[] response; byte errorCode = 0; if (ST25DV_MailboxCommStart()) { bool worked = _MCI_Protocol.MCI_Read(StName, offset, payloadlength, timeoutMs, out response, out errorCode); LastErrorCode = errorCode; LastData = response; if (!worked) { SetLastErrorMesage(); return false; } return true; } return false; } // This function-header is needed for calls to the DLL without need for using enums. // In python 3.11 the module pythonnet (v3.x) does not support casting 'int' to 'enum' aynmore, // so this workaround is needed. public bool MCI_Write_pythonwrapper_enum(int STIndex, ushort offset, byte payloadlength, byte[] payload, int timeoutMs) { return MCI_Write((MCI_Protocol.StructName)STIndex, offset, payloadlength, payload, timeoutMs); } public bool MCI_Write(MCI_Protocol.StructName StName, ushort offset, byte payloadlength, byte[] payload, int timeoutMs) { LastErrorMessage = string.Empty; LastData = null; byte errorCode = 0; if (ST25DV_MailboxCommStart()) { bool ans = _MCI_Protocol.MCI_Write(StName, offset, payloadlength, payload, timeoutMs, out errorCode); LastErrorCode = errorCode; if (!ans) { SetLastErrorMesage(); return false; } return true; } return false; } public bool MCI_Write_Read(MCI_Protocol.StructName StName, ushort offset, byte payloadlength, byte[] payload, int timeoutMs) { LastErrorMessage = string.Empty; LastData = null; byte[] response; byte errorCode = 0; if (ST25DV_MailboxCommStart()) { bool ans = _MCI_Protocol.MCI_Write_Read(StName, offset, payloadlength, payload, out response, timeoutMs, out errorCode); LastErrorCode = errorCode; LastData = response; if (!ans) { SetLastErrorMesage(); return false; } return true; } return false; } private bool ST25DV_MailboxCommStart() { // wake up the ST25DV by setting the GPO pin to output a pulse -> INT on Metro (set highest bit to one -> causes a pulse on GPO) if (_ST25DV_Device.ST25DV_WriteGPOManageCommand(_ST25DV_Device.GPO_MANAGE_SET_PULSE, false)) { Thread.Sleep(10); // wait 10ms to give metro some time byte MB_Ctrl_Reg_Value = 0x00; for (int i = 0; i < 20; i++) // do 20 retries max (20 times 100ms = 2sec, which should be more than enough) { if (_ST25DV_Device.ST25DV_ReadMailboxControlReg(out MB_Ctrl_Reg_Value)) { if ((MB_Ctrl_Reg_Value & 0x01) == 0x01) { return true; //With this we proceed for mailbox communication with ST25 } else { Thread.Sleep(100); // wait 100ms, then try again LastErrorMessage = "Mailbox was not activated by Firmware (ST25DV_MailboxCommStart), try again"; } } else { Thread.Sleep(100); // wait 100ms, then try again LastErrorMessage = "Mailbox state could not be read (ST25DV_MailboxCommStart), try again"; } } LastErrorCode = 0x08; //MCI Error: Unidentified OnMessageEvent(">> REMARK: Check if Metorlogy firmware-version is at least: 5.1.24 <<"); return false; } else { LastErrorCode = 0x08; //MCI Error: Unidentified LastErrorMessage = "GPO config writing failed(ST25DV_MailboxCommStart), try again"; return false; } } private void SetLastErrorMesage() { try { LastErrorMessage = MCI_Protocol.GetMCIErrorMessage(LastErrorCode); } catch { LastErrorMessage = "MCI Error: The Errorcode '" + LastErrorCode as string + "' couldn't be converted to string with the enum MCI_ErrorCodes!"; } } public bool NFCHeadConfig_PulseLedOutputEnable() { return _NFCHead_Config.NFCHeadConfig_PulseLedOutputEnable(); // true - success, false - failed } public bool NFCHeadConfig_DataLedOutputEnable() { return _NFCHead_Config.NFCHeadConfig_DataLedOutputEnable(); // true - success, false - failed } public ushort NFCHeadConfig_ReadOpampOutput() { return _NFCHead_Config.NFCHeadConfig_ReadOpampOutput(); // 0 - failed, any other value is opamp output } public bool NFCHeadConfig_SetGain(byte gain) { return _NFCHead_Config.NFCHeadConfig_SetGain(gain); // true - success, false - failed } public byte NFCHeadConfig_ReadGain() { return _NFCHead_Config.NFCHeadConfig_ReadGain(); // 0 - failed, any other value is gain } public bool NFCHeadConfig_SetInterface(bool value) { return _NFCHead_Config.NFCHeadConfig_SetInterface(value); // true - success, false - failed } public byte NFCHeadConfig_ReadInterface() { return _NFCHead_Config.NFCHeadConfig_ReadInterface(); // 0 - failed, 1 - NFC, 2 - RFID } public byte[] NFCHeadConfig_ReadFirmwareVersion() { return _NFCHead_Config.NFCHeadConfig_ReadFirmwareVersion(); // 0 - failed, any other value is firmware version } public bool NFCSecurity_SendSignedCommand(byte[] payload, int timeoutMs) { byte payloadLength = Convert.ToByte(payload.Length); ushort offset = 0; try { if ((!MCI_Write(MCI_Protocol.StructName.NfcSecurity, offset, payloadLength, payload, timeoutMs)) || (LastErrorCode != 0x00)) { return false; } } catch (Exception ex) { OnMessageEvent(ex.Message); } return true; // true - success, false - failed } public string NFCSecurity_Read_EndpointId(int timeoutMs) { string result = string.Empty; byte[] payload = readCommand; byte payloadLength = Convert.ToByte(payload.Length); ushort offset = 0; try { if ((!MCI_Write_Read(MCI_Protocol.StructName.NfcSecurity, offset, payloadLength, payload, timeoutMs)) || (LastErrorCode != 0x00)) { return string.Empty; } } catch (Exception ex) { OnMessageEvent(ex.Message); return string.Empty; } //Check response header if (LastData[0] != 0x05 || LastData[1] != 0x00) { LastErrorMessage = "Wrong response header"; return string.Empty; } //Check length byte if (LastData[2] == 0x00) { LastErrorMessage = "Response length is 0"; return string.Empty; } //Check length of message int lengthMessage = LastData[2] + 3; // two header bytes and one length byte if (LastData.Length != lengthMessage) { LastErrorMessage = "Response length is wrong"; return string.Empty; } //Check identifier for information if (LastData[3] != 0x00 || LastData[4] != 0x00) { LastErrorMessage = "Wrong identifier"; return string.Empty; } int lengthEndpointId = LastData[2] - 2; //Identifier bytes byte[] id = new byte[lengthEndpointId]; Array.Copy(LastData, 5, id, 0, lengthEndpointId); result = Encoding.ASCII.GetString(id); return result; } public byte NFCSecurity_ReadPrivilegeLevel(int timeoutMs) { ushort offset = 0; byte payloadLength = 1; byte payload = 4; try { if ((!MCI_Read(MCI_Protocol.StructName.NfcSecurity, offset, payloadLength, timeoutMs)) || (LastErrorCode != 0x00)) { payload = 4; } else { payload = LastData[0]; } } catch (Exception ex) { OnMessageEvent(ex.Message); } return payload; // 0 - No Security, 1 - Customer Maintenance, 2 - Customer Admin, 3 - Sensus Admin, 4 and above - failed } public bool NFCSecurity_ClearPrivilegeLevel(int timeoutMs) { ushort offset = 0; byte payloadLength = 1; byte[] payload = { 0x23 }; // 0x23 - Command code for 'NFC Security reset access level' try { if ((!MCI_Write(MCI_Protocol.StructName.Command, offset, payloadLength, payload, timeoutMs)) || (LastErrorCode != 0x00)) { return false; } } catch (Exception ex) { OnMessageEvent(ex.Message); } return true; // true - success, false - failed } public string GetDllVersion() { Version dll_version = System.Reflection.Assembly.GetExecutingAssembly().GetName().Version; return "NfcS5 DLL v" + String.Format("{0}.{1}.{2}", dll_version.Major, dll_version.Minor, dll_version.Build); //, dll_version.MajorRevision); } #region Message Event private void OnMessageEvent(string message) { NfcMessageEventArgs e = new NfcMessageEventArgs(); e.Message = message; if (MessageEvent != null) { MessageEvent(this, e); } e = null; } #endregion } }