added errormessages (in method MCI_TxRequest_RxResponse, for better NFC error analysis) CRC check included. If the meter CRC doesn't match the calculated CRC an error is returned. removed method MCI_Raw_Read/write (same as MCI_Read/Write, which autoselects the correct password) Refactoring more detailed errorcodes(no more unidentified, separated host (>=20) and Meter messages (0..8))
562 lines
21 KiB
C#
562 lines
21 KiB
C#
|
|
using System;
|
|
using System.IO.Ports;
|
|
using System.Diagnostics;
|
|
|
|
//*****************************************************************************
|
|
// Copyright 2020 Sensus GmbH Ludwigshafen. All rights reserved.
|
|
// Author: Venkat, Rajeshwar
|
|
//*****************************************************************************
|
|
|
|
namespace Sensus.iPerl.NfcHandler
|
|
{
|
|
public sealed class NfcDataHandler
|
|
{
|
|
|
|
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;
|
|
|
|
private string _ComPort = "";
|
|
|
|
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();
|
|
ST25DV_MailboxCommStop();
|
|
return false;
|
|
}
|
|
return ST25DV_MailboxCommStop();
|
|
}
|
|
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();
|
|
ST25DV_MailboxCommStop();
|
|
return false;
|
|
}
|
|
return ST25DV_MailboxCommStop();
|
|
}
|
|
return false;
|
|
}
|
|
|
|
public bool MCI_Read(byte msgId, ushort offset, byte payloadlength, int timeoutMs)
|
|
{
|
|
LastErrorMessage = string.Empty;
|
|
byte[] response;
|
|
byte errorCode = 0;
|
|
if (ST25DV_MailboxCommStart())
|
|
{
|
|
bool ans = _MCI_Protocol.MCI_Read((MCI_Protocol.StructName) msgId, offset, payloadlength, timeoutMs,
|
|
out response, out errorCode);
|
|
LastErrorCode = errorCode;
|
|
LastData = response;
|
|
if (!ans)
|
|
{
|
|
SetLastErrorMesage();
|
|
ST25DV_MailboxCommStop();
|
|
return false;
|
|
}
|
|
return ST25DV_MailboxCommStop();
|
|
}
|
|
return false;
|
|
}
|
|
|
|
public bool MCI_Write(byte msgId, 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((MCI_Protocol.StructName) msgId, offset, payloadlength, payload,
|
|
timeoutMs, out errorCode);
|
|
LastErrorCode = errorCode;
|
|
if (!ans)
|
|
{
|
|
SetLastErrorMesage();
|
|
ST25DV_MailboxCommStop();
|
|
return false;
|
|
}
|
|
return ST25DV_MailboxCommStop();
|
|
}
|
|
return false;
|
|
}
|
|
|
|
private bool ST25DV_MailboxCommStart()
|
|
{
|
|
bool success = false;
|
|
byte[] EepromBlockRead = { 0, 0, 0, 0 };
|
|
byte[] EepromBlockWrite = { 0, 0, 0, 0 };
|
|
|
|
//Byte-2 in block-2 represent the EEPROM I2C interface status ->
|
|
//0xA5-EEPROM I2C Interface Idle, 0x5A-EEPROM I2C Interface Active
|
|
success = _ST25DV_Device.ST25DV_ReadSingleBlock(2, out EepromBlockRead, false);
|
|
if (success)
|
|
{
|
|
if (EepromBlockRead[1] == 0xA5) //0xA5-EEPROM I2C Interface Idle
|
|
{
|
|
//Byte-1 in block-64 represent the Mailbox RF interface status ->
|
|
//0xA5-Mailbox RF Interface Idle, 0x5A-Mailbox RF Interface Active
|
|
EepromBlockWrite[0] = 0x5A; //Raising Mailbox RF interface Active flag to avoid collisions with host interface
|
|
success = _ST25DV_Device.ST25DV_WriteSingleBlock(64, EepromBlockWrite, false);
|
|
if (success)
|
|
{
|
|
//Sets 0x01 (MB_EN) in the register (MB_CTRL_Dyn) with address 0x0D
|
|
if (_ST25DV_Device.ST25DV_WriteDynamicConfiguration(0x0D, 0x01, false))
|
|
{
|
|
return true; //With this we proceed for mailbox communication with ST25
|
|
}
|
|
else
|
|
{
|
|
LastErrorCode = 0x08; //MCI Error: Unidentified
|
|
LastErrorMessage = "MB_CTRL_Dyn writing failed(ST25DV_MailboxCommStart), try again";
|
|
return false;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
LastErrorCode = 0x08; //MCI Error: Unidentified
|
|
LastErrorMessage = "Mailbox RF interface status writing failed(ST25DV_MailboxCommStart), try again";
|
|
return false;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
LastErrorCode = 0x08; //MCI Error: Unidentified
|
|
LastErrorMessage = "EEPROM interface busy(ST25DV_MailboxCommStart), try again";
|
|
return false;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
LastErrorCode = 0x08; //MCI Error: Unidentified
|
|
LastErrorMessage = "EEPROM interface status reading failed(ST25DV_MailboxCommStart), try again";
|
|
return false;
|
|
}
|
|
}
|
|
|
|
private bool ST25DV_MailboxCommStop()
|
|
{
|
|
bool success = false;
|
|
byte[] EepromBlockWrite = { 0, 0, 0, 0 };
|
|
|
|
//Sets 0x00 (MB_EN) in the register (MB_CTRL_Dyn) with address 0x0D
|
|
if (_ST25DV_Device.ST25DV_WriteDynamicConfiguration(0x0D, 0x00, false))
|
|
{
|
|
//Byte-1 in block-64 represent the Mailbox RF interface status ->
|
|
//0xA5-Mailbox RF Interface Idle, 0x5A-Mailbox RF Interface Active
|
|
EepromBlockWrite[0] = 0xA5; //Raising Mailbox RF interface Idle flag to avoid collisions with host interface
|
|
success = _ST25DV_Device.ST25DV_WriteSingleBlock(64, EepromBlockWrite, false);
|
|
if (success)
|
|
{
|
|
return true; //With this we proceed for interpreting the communication happened with ST25
|
|
}
|
|
else
|
|
{
|
|
LastErrorCode = 0x08; //MCI Error: Unidentified
|
|
LastErrorMessage = "Mailbox RF interface status writing failed(ST25DV_MailboxCommStop), try again";
|
|
return false;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
LastErrorCode = 0x08; //MCI Error: Unidentified
|
|
LastErrorMessage = "MB_CTRL_Dyn writing failed(ST25DV_MailboxCommStop), 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 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()
|
|
{
|
|
System.Reflection.Assembly assembly = System.Reflection.Assembly.GetExecutingAssembly();
|
|
FileVersionInfo fvi = FileVersionInfo.GetVersionInfo(assembly.Location);
|
|
string str = "NfcS5 DLL v" + fvi.FileVersion;
|
|
return str.Remove(str.Length - 2); // only show x.y.z version, hide 4th number
|
|
}
|
|
|
|
#region Message Event
|
|
private void OnMessageEvent(string message)
|
|
{
|
|
NfcMessageEventArgs e = new NfcMessageEventArgs();
|
|
e.Message = message;
|
|
|
|
if (MessageEvent != null)
|
|
{
|
|
MessageEvent(this, e);
|
|
}
|
|
e = null;
|
|
}
|
|
#endregion
|
|
}
|
|
}
|