diff --git a/src/devices/Card/Card.sln b/src/devices/Card/Card.sln index b165ac7fb3..2af530d17b 100644 --- a/src/devices/Card/Card.sln +++ b/src/devices/Card/Card.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 16 -VisualStudioVersion = 16.0.31105.61 +# Visual Studio Version 17 +VisualStudioVersion = 17.12.35527.113 d17.12 MinimumVisualStudioVersion = 15.0.26124.0 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CardRfid", "CardRfid.csproj", "{2B97523A-956B-4C88-8931-28C1AF88012C}" EndProject @@ -11,6 +11,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Mifare", "Mifare\Mifare.csp EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Common", "..\Common\Common.csproj", "{03F9B2A3-4B73-4D39-872B-BB0D81F3593E}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Icode", "Icode\Icode.csproj", "{1A0C6CC9-D66D-4935-9880-644230F1C5B9}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -69,6 +71,18 @@ Global {03F9B2A3-4B73-4D39-872B-BB0D81F3593E}.Release|x64.Build.0 = Release|Any CPU {03F9B2A3-4B73-4D39-872B-BB0D81F3593E}.Release|x86.ActiveCfg = Release|Any CPU {03F9B2A3-4B73-4D39-872B-BB0D81F3593E}.Release|x86.Build.0 = Release|Any CPU + {1A0C6CC9-D66D-4935-9880-644230F1C5B9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1A0C6CC9-D66D-4935-9880-644230F1C5B9}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1A0C6CC9-D66D-4935-9880-644230F1C5B9}.Debug|x64.ActiveCfg = Debug|Any CPU + {1A0C6CC9-D66D-4935-9880-644230F1C5B9}.Debug|x64.Build.0 = Debug|Any CPU + {1A0C6CC9-D66D-4935-9880-644230F1C5B9}.Debug|x86.ActiveCfg = Debug|Any CPU + {1A0C6CC9-D66D-4935-9880-644230F1C5B9}.Debug|x86.Build.0 = Debug|Any CPU + {1A0C6CC9-D66D-4935-9880-644230F1C5B9}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1A0C6CC9-D66D-4935-9880-644230F1C5B9}.Release|Any CPU.Build.0 = Release|Any CPU + {1A0C6CC9-D66D-4935-9880-644230F1C5B9}.Release|x64.ActiveCfg = Release|Any CPU + {1A0C6CC9-D66D-4935-9880-644230F1C5B9}.Release|x64.Build.0 = Release|Any CPU + {1A0C6CC9-D66D-4935-9880-644230F1C5B9}.Release|x86.ActiveCfg = Release|Any CPU + {1A0C6CC9-D66D-4935-9880-644230F1C5B9}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/src/devices/Card/Icode/Icode.csproj b/src/devices/Card/Icode/Icode.csproj new file mode 100644 index 0000000000..6444e2d5e7 --- /dev/null +++ b/src/devices/Card/Icode/Icode.csproj @@ -0,0 +1,11 @@ + + + + $(DefaultBindingTfms) + + + + + + + diff --git a/src/devices/Card/Icode/IcodeCard.cs b/src/devices/Card/Icode/IcodeCard.cs new file mode 100644 index 0000000000..7ac88de207 --- /dev/null +++ b/src/devices/Card/Icode/IcodeCard.cs @@ -0,0 +1,423 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using Iot.Device.Common; +using Microsoft.Extensions.Logging; + +namespace Iot.Device.Card.Icode +{ + /// + /// A Icode card class Supports ICODE SLIX,ICODE SLIX2,ICODE DNA,ICODE 3 + /// + public class IcodeCard + { + private const byte BytesPerBlock = 4; + + // This is the actual RFID reader + private readonly CardTransceiver _rfid; + + private readonly ILogger _logger; + + // the size of response + private ushort _responseSize; + + // The command to execute on the card + private IcodeCardCommand _command; + + /// + /// The tag number detected by the reader + /// + public byte Target { get; set; } + + /// + /// unique identifier of the card + /// + public byte[]? Uid { get; set; } + + /// + /// The storage capacity + /// + public IcodeCardCapacity Capacity { get; set; } + + /// + /// The block number to read or write + /// + public byte BlockNumber { get; set; } + + /// + /// The block count when read multiple blocks + /// + public byte BlockCount { get; set; } + + /// + /// The Data which has been read or to write for the specific block + /// + public byte[] Data { get; set; } = new byte[0]; + + /// + /// AFI (Application family identifier) represents the type of application targeted by the VCD + /// + public byte Afi { get; set; } + + /// + /// The Data storage format identifier indicates how the data is structured in the VICC memory + /// + public byte Dsfid { get; set; } + + /// + /// Constructor for IcodeCard + /// + /// A card transceiver class + /// The target number as some card readers attribute one + public IcodeCard(CardTransceiver rfid, byte target) + { + _rfid = rfid; + Target = target; + _logger = this.GetCurrentClassLogger(); + } + + /// + /// Provide a calculation of CRC for ISO15693 + /// The PN5180 module seems to have implemented crc and does not need to calculate when coding + /// + /// The buffer to process + /// The CRC, Must be a 2 bytes buffer + public void CalculateCrcIso15693(ReadOnlySpan buffer, Span crc) + { + if (crc.Length != 2) + { + throw new ArgumentException($"The length of crc must be 2 bytes.", nameof(crc)); + } + + ushort polynomial = 0x8408; + ushort currentCrc = 0xFFFF; + // ISO15693-3.pdf + for (int i = 0; i < buffer.Length; i++) + { + currentCrc = (ushort)(currentCrc ^ buffer[i]); + for (int j = 0; j < 8; j++) + { + if ((currentCrc & 0x0001) != 0) + { + currentCrc = (ushort)((currentCrc >> 1) ^ polynomial); + } + else + { + currentCrc = (ushort)(currentCrc >> 1); + } + } + } + + currentCrc = (ushort)~currentCrc; + crc[0] = (byte)(currentCrc & 0xFF); + crc[1] = (byte)((currentCrc >> 8) & 0xFF); + } + + /// + /// Run the last setup command. In case of reading bytes, they are automatically pushed into the Data property + /// + /// -1 if the process fails otherwise the number of bytes read + private int RunIcodeCardCommand() + { + byte[] requestData = Serialize(); + byte[] dataOut = new byte[_responseSize]; + + var ret = _rfid.Transceive(Target, requestData, dataOut.AsSpan(), NfcProtocol.Iso15693); + if (_logger.IsEnabled(LogLevel.Debug)) + { + _logger.LogDebug($"{nameof(RunIcodeCardCommand)}: {_command}, Target: {Target}, Data: {BitConverter.ToString(requestData)}, Success: {ret}, Dataout: {BitConverter.ToString(dataOut)}"); + } + + if (ret > 0) + { + Data = dataOut; + } + + return ret; + } + + /// + /// Serialize request data according to the protocol + /// Request format: SOF, Flags, Command code, Parameters (opt.), Data (opt.), CRC16, EOF + /// + /// The serialized bits + private byte[] Serialize() + { + byte[]? ser = null; + switch (_command) + { + case IcodeCardCommand.ReadSingleBlock: + // Flags(1 byte), Command code(1 byte), UID(8 byte), BlockNumber(1 byte) + ser = new byte[2 + 8 + 1]; + ser[0] = 0x22; + ser[1] = (byte)_command; + ser[10] = BlockNumber; + Uid?.CopyTo(ser, 2); + _responseSize = 5; + return ser; + case IcodeCardCommand.WriteSingleBlock: + // Flags(1 byte), Command code(1 byte), UID(8 byte), BlockNumber(1 byte),Data to write(4 byte) + ser = new byte[2 + 8 + 1 + 4]; + ser[0] = 0x22; + ser[1] = (byte)_command; + ser[10] = BlockNumber; + Uid?.CopyTo(ser, 2); + Data.CopyTo(ser, 11); + _responseSize = 2; + return ser; + case IcodeCardCommand.LockBlock: + // Flags(1 byte), Command code(1 byte), UID(8 byte), BlockNumber(1 byte) + ser = new byte[2 + 8 + 1]; + ser[0] = 0x22; + ser[1] = (byte)_command; + ser[10] = BlockNumber; + Uid?.CopyTo(ser, 2); + _responseSize = 2; + return ser; + case IcodeCardCommand.ReadMultipleBlocks: + // Flags(1 byte), Command code(1 byte), UID(8 byte), FirstBlockNumber(1 byte), NumBlocks + ser = new byte[2 + 8 + 2]; + ser[0] = 0x22; + ser[1] = (byte)_command; + ser[10] = BlockNumber; + ser[11] = BlockCount; + Uid?.CopyTo(ser, 2); + _responseSize = (ushort)(1 + (BlockCount + 1) * 4); + return ser; + case IcodeCardCommand.WriteMultipleBlocks: + // Flags(1 byte), Command code(1 byte), UID(8 byte), FirstBlockNumber(1 byte), numBlocks, Data to write + ser = new byte[2 + 8 + 2 + Data.Length]; + ser[0] = 0x22; + ser[1] = (byte)_command; + ser[10] = BlockNumber; + ser[11] = (byte)(Data.Length / 4); + Uid?.CopyTo(ser, 2); + Data.CopyTo(ser, 12); + _responseSize = 2; + return ser; + case IcodeCardCommand.StayQuiet: + case IcodeCardCommand.Select: + case IcodeCardCommand.ResettoRead: + case IcodeCardCommand.LockAfi: + case IcodeCardCommand.LockDsfid: + // Flags(1 byte), Command code(1 byte), UID(8 byte) + ser = new byte[2 + 8]; + ser[0] = 0x22; + ser[1] = (byte)_command; + Uid?.CopyTo(ser, 2); + _responseSize = 2; + return ser; + case IcodeCardCommand.GetSystemInformation: + // Flags(1 byte), Command code(1 byte), UID(8 byte) + ser = new byte[2 + 8]; + ser[0] = 0x22; + ser[1] = (byte)_command; + Uid?.CopyTo(ser, 2); + _responseSize = 15; + return ser; + case IcodeCardCommand.WriteAfi: + // Flags(1 byte), Command code(1 byte), UID(8 byte), AFI(1 byte) + ser = new byte[2 + 8 + 1]; + ser[0] = 0x22; + ser[1] = (byte)_command; + ser[10] = Afi; + Uid?.CopyTo(ser, 2); + _responseSize = 2; + return ser; + case IcodeCardCommand.WriteDsfid: + // Flags(1 byte), Command code(1 byte), UID(8 byte), DSFID(1 byte) + ser = new byte[2 + 8 + 1]; + ser[0] = 0x22; + ser[1] = (byte)_command; + ser[10] = Dsfid; + Uid?.CopyTo(ser, 2); + _responseSize = 2; + return ser; + default: + return new byte[0]; + } + } + + /// + /// Perform a read and place the result into the 4 bytes Data property on a specific block + /// + /// The block number to read + /// True if success. This only means whether the communication between VCD and VICC is successful or not + public bool ReadSingleBlock(byte block) + { + BlockNumber = block; + _command = IcodeCardCommand.ReadSingleBlock; + var ret = RunIcodeCardCommand(); + return ret >= 0; + } + + /// + /// Perform a write using the 4 bytes present in Data on a specific block + /// + /// The block number to write + /// True if success. This only means whether the communication between VCD and VICC is successful or not + public bool WriteSingleBlock(byte block) + { + if (Data.Length < 1 || Data.Length > 4) + { + _logger.LogDebug("Length of data must be larger than zero and less than or equal four."); + return false; + } + + BlockNumber = block; + _command = IcodeCardCommand.WriteSingleBlock; + var ret = RunIcodeCardCommand(); + return ret >= 0; + } + + /// + /// Lock a specific block + /// + /// The block number to Lock + /// True if success. This only means whether the communication between VCD and VICC is successful or not + public bool LockBlock(byte block) + { + BlockNumber = block; + _command = IcodeCardCommand.LockBlock; + var ret = RunIcodeCardCommand(); + return ret >= 0; + } + + /// + /// Perform a read and place the result into the Data property on continuous block + /// + /// The start block number to read + /// Total bolck count to read + /// True if success. This only means whether the communication between VCD and VICC is successful or not + public bool ReadMultipleBlocks(byte block, byte count) + { + BlockNumber = block; + BlockCount = count; + _command = IcodeCardCommand.ReadMultipleBlocks; + var ret = RunIcodeCardCommand(); + return ret >= 0; + } + + /// + /// Perform a write using the Data property on a specific blocks + /// + /// The start block number to write + /// True if success. This only means whether the communication between VCD and VICC is successful or not + public bool WriteMultipleBlocks(byte block) + { + if (Data.Length < 1) + { + _logger.LogDebug("Length of data must be larger than zero."); + return false; + } + + BlockNumber = block; + _command = IcodeCardCommand.WriteMultipleBlocks; + var ret = RunIcodeCardCommand(); + return ret >= 0; + } + + /// + /// Let the VICC stay quiet stauts + /// + /// True if success. This only means whether the communication between VCD and VICC is successful or not + public bool StayQuiet() + { + if (Uid == null || Uid.Length != 8) + { + _logger.LogDebug("Uid is null or is invalid!"); + return false; + } + + _command = IcodeCardCommand.StayQuiet; + var ret = RunIcodeCardCommand(); + return ret >= 0; + } + + /// + /// Select a card + /// + /// The uid of card + /// True if success. This only means whether the communication between VCD and VICC is successful or not + public bool Select(byte[] uid) + { + if (uid == null || uid.Length != 8) + { + _logger.LogDebug("Uid is null or is invalid!"); + return false; + } + + Uid = uid; + _command = IcodeCardCommand.Select; + var ret = RunIcodeCardCommand(); + return ret >= 0; + } + + /// + /// Let the VICC stay ready status + /// + /// True if success. This only means whether the communication between VCD and VICC is successful or not + public bool ResettoRead() + { + _command = IcodeCardCommand.ResettoRead; + var ret = RunIcodeCardCommand(); + return ret >= 0; + } + + /// + /// Write AFI + /// + /// True if success. This only means whether the communication between VCD and VICC is successful or not + public bool WriteAfi() + { + _command = IcodeCardCommand.WriteAfi; + var ret = RunIcodeCardCommand(); + return ret >= 0; + } + + /// + /// Lock the AFI + /// + /// True if success. This only means whether the communication between VCD and VICC is successful or not + public bool LockAfi() + { + _command = IcodeCardCommand.LockAfi; + var ret = RunIcodeCardCommand(); + return ret >= 0; + } + + /// + /// Write DSFID + /// + /// True if success. This only means whether the communication between VCD and VICC is successful or not + public bool WriteDsfid() + { + _command = IcodeCardCommand.WriteDsfid; + var ret = RunIcodeCardCommand(); + return ret >= 0; + } + + /// + /// Lock the DSFID + /// + /// True if success. This only means whether the communication between VCD and VICC is successful or not + public bool LockDsfid() + { + _command = IcodeCardCommand.LockDsfid; + var ret = RunIcodeCardCommand(); + return ret >= 0; + } + + /// + /// Get the SystemInformation such as DSFID,AFI,Capacity of VICC + /// + /// True if success. This only means whether the communication between VCD and VICC is successful or not + public bool GetSystemInformation() + { + _command = IcodeCardCommand.GetSystemInformation; + var ret = RunIcodeCardCommand(); + return ret >= 0; + } + } +} diff --git a/src/devices/Card/Icode/IcodeCardCapacity.cs b/src/devices/Card/Icode/IcodeCardCapacity.cs new file mode 100644 index 0000000000..47c5efa2fc --- /dev/null +++ b/src/devices/Card/Icode/IcodeCardCapacity.cs @@ -0,0 +1,42 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Iot.Device.Card.Icode +{ + /// + /// Different storage capacity for ICODE cards + /// https://www.nxp.com.cn/docs/en/application-note/AN11809.pdf + /// + public enum IcodeCardCapacity + { + /// + /// Unknown + /// + Unknown = 0, + + /// + /// ICODE SLIX + /// + IcodeSlix = 896, + + /// + /// ICODE SLIX2 + /// + IcodeSlix2 = 2528, + + /// + /// ICODE DNA + /// + IcodeDna = 2016, + + /// + /// ICODE 3 + /// + Icode3 = 2400, + + /// + /// ICODE 3 TagTamper + /// + Icode3TagTamper = Icode3 + } +} diff --git a/src/devices/Card/Icode/IcodeCardCommand.cs b/src/devices/Card/Icode/IcodeCardCommand.cs new file mode 100644 index 0000000000..0e53cf4362 --- /dev/null +++ b/src/devices/Card/Icode/IcodeCardCommand.cs @@ -0,0 +1,86 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Iot.Device.Card.Icode +{ + /// + /// List of commands available for the Icode cards + /// + internal enum IcodeCardCommand + { + /// + /// Inventory cards + /// + Inventory = 0x01, + + /// + /// Set VICC quiet state + /// + StayQuiet = 0x02, + + /// + /// Read single block + /// + ReadSingleBlock = 0x20, + + /// + /// Write single block + /// + WriteSingleBlock = 0x21, + + /// + /// Lock block + /// + LockBlock = 0x22, + + /// + /// Read multiple blocks + /// + ReadMultipleBlocks = 0x23, + + /// + /// Write multiple block + /// + WriteMultipleBlocks = 0x24, + + /// + /// Select + /// + Select = 0x25, + + /// + /// Reset to read + /// + ResettoRead = 0x26, + + /// + /// Write AFI + /// + WriteAfi = 0x27, + + /// + /// Lock AFI + /// + LockAfi = 0x28, + + /// + /// Write DSFID + /// + WriteDsfid = 0x29, + + /// + /// Lock DSFID + /// + LockDsfid = 0x2A, + + /// + /// Get system information + /// + GetSystemInformation = 0x2B, + + /// + /// Get multiple block security status + /// + GetMultipleBlockSecurityStatus = 0x2C + } +} diff --git a/src/devices/Card/Rfid/Data26_53kbps.cs b/src/devices/Card/Rfid/Data26_53kbps.cs new file mode 100644 index 0000000000..3c19d5227f --- /dev/null +++ b/src/devices/Card/Rfid/Data26_53kbps.cs @@ -0,0 +1,53 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Iot.Device.Rfid +{ + /// + /// 26 and 53 kbps card elements like ICODE + /// + public class Data26_53kbps + { + /// + /// create 26 and 53 kbps card elements like ICODE + /// + /// The target number + /// Application Family Idenfifie + /// Electronic Article Surveillance + /// Data Storage Format Identify + /// The unique NFC ID + public Data26_53kbps(byte targetNumber, byte afi, byte eas, byte dsfid, byte[] nfcId) + { + TargetNumber = targetNumber; + Afi = afi; + Eas = eas; + Dsfid = dsfid; + NfcId = nfcId; + } + + /// + /// The target number + /// + public byte TargetNumber { get; set; } + + /// + /// Application Family Idenfifie + /// + public byte Afi { get; set; } + + /// + /// Electronic Article Surveillance + /// + public byte Eas { get; set; } + + /// + /// Data Storage Format Identify + /// + public byte Dsfid { get; set; } + + /// + /// The unique NFC ID + /// + public byte[] NfcId { get; set; } + } +} diff --git a/src/devices/Pn5180/Pn5180.cs b/src/devices/Pn5180/Pn5180.cs index b8795432ae..d8d1e34688 100644 --- a/src/devices/Pn5180/Pn5180.cs +++ b/src/devices/Pn5180/Pn5180.cs @@ -13,6 +13,7 @@ using System.Diagnostics.CodeAnalysis; using Iot.Device.Card; using Iot.Device.Card.Mifare; +using Iot.Device.Card.Icode; using Iot.Device.Common; using Iot.Device.Rfid; using Microsoft.Extensions.Logging; @@ -441,6 +442,18 @@ public override int Transceive(byte targetNumber, ReadOnlySpan dataToSend, return TransceiveBuffer(dataToSend, dataFromCard); } } + else if (protocol == NfcProtocol.Iso15693) + { + var ret = SendDataToCard(dataToSend.ToArray()); + if (!ret) + { + return -1; + } + + // ISO/IEC 15693-3:2001 page 25 + // waiting time:(302μs) * number of bytes + eof(320.9μs) + 20ms + return ReadWithTimeout(dataFromCard, 1 + dataToSend.Length * 3 / 10 + 20); + } return TransceiveClassic(targetNumber, dataToSend, dataFromCard); } @@ -1465,6 +1478,106 @@ private bool GetIrqStatus(Span irqStatus) return true; } + /// + /// Listen to 15693 cards with 16 slots + /// + /// The transmitter configuration, should be compatible with 15693 card + /// The receiver configuration, should be compatible with 15693 card + /// The 15693 cards once detected + /// The time to poll the card in milliseconds. Card detection will stop once the detection time will be over + /// True if a 15693 card has been detected + public bool ListenToCardIso15693(TransmitterRadioFrequencyConfiguration transmitter, ReceiverRadioFrequencyConfiguration receiver, +#if NET5_0_OR_GREATER + [NotNullWhen(true)] +#endif + out IList? cards, int timeoutPollingMilliseconds) + { + cards = new List(); + var ret = LoadRadioFrequencyConfiguration(transmitter, receiver); + // Switch on the radio frequence field and check it + ret &= SetRadioFrequency(true); + + Span inventoryResponse = stackalloc byte[10]; + Span dsfid = stackalloc byte[1]; + Span uid = stackalloc byte[8]; + + int numBytes = 0; + + DateTime dtTimeout = DateTime.Now.AddMilliseconds(timeoutPollingMilliseconds); + + try + { + // Clears all interrupt + SpiWriteRegister(Command.WRITE_REGISTER, Register.IRQ_CLEAR, new byte[] { 0xFF, 0xFF, 0x0F, 0x00 }); + // Sets the PN5180 into IDLE state + SpiWriteRegister(Command.WRITE_REGISTER_AND_MASK, Register.SYSTEM_CONFIG, new byte[] { 0xF8, 0xFF, 0xFF, 0xFF }); + // Activates TRANSCEIVE routine + SpiWriteRegister(Command.WRITE_REGISTER_OR_MASK, Register.SYSTEM_CONFIG, new byte[] { 0x03, 0x00, 0x00, 0x00 }); + // Sends an inventory command with 16 slots + ret = SendDataToCard(new byte[] { 0x06, 0x01, 0x00 }); + if (dtTimeout < DateTime.Now) + { + return false; + } + + for (byte slotCounter = 0; slotCounter < 16; slotCounter++) + { + (numBytes, _) = GetNumberOfBytesReceivedAndValidBits(); + if (numBytes > 0) + { + ret &= ReadDataFromCard(inventoryResponse, inventoryResponse.Length); + if (ret) + { + cards.Add(new Data26_53kbps(slotCounter, 0, 0, inventoryResponse[1], inventoryResponse.Slice(2, 8).ToArray())); + } + } + + // Send only EOF (End of Frame) without data at the next RF communication + SpiWriteRegister(Command.WRITE_REGISTER_AND_MASK, Register.TX_CONFIG, new byte[] { 0x3F, 0xFB, 0xFF, 0xFF }); + // Sets the PN5180 into IDLE state + SpiWriteRegister(Command.WRITE_REGISTER_AND_MASK, Register.SYSTEM_CONFIG, new byte[] { 0xF8, 0xFF, 0xFF, 0xFF }); + // Activates TRANSCEIVE routine + SpiWriteRegister(Command.WRITE_REGISTER_OR_MASK, Register.SYSTEM_CONFIG, new byte[] { 0x03, 0x00, 0x00, 0x00 }); + // Clears the interrupt register IRQ_STATUS + SpiWriteRegister(Command.WRITE_REGISTER, Register.IRQ_CLEAR, new byte[] { 0xFF, 0xFF, 0x0F, 0x00 }); + // Send EOF + SendDataToCard(new Span { }); + } + + if (cards.Count > 0) + { + return true; + } + else + { + return false; + } + } + catch (TimeoutException) + { + return false; + } + } + + /// + /// reset PN5180 RF Configuration and some Register + /// + /// The transmitter configuration + /// The receiver configuration + /// + public bool ResetPN5180Configuration(TransmitterRadioFrequencyConfiguration transmitter, ReceiverRadioFrequencyConfiguration receiver) + { + var ret = LoadRadioFrequencyConfiguration(transmitter, receiver); + // Switch on the radio frequence field and check it + ret &= SetRadioFrequency(true); + // Clears all interrupt + SpiWriteRegister(Command.WRITE_REGISTER, Register.IRQ_CLEAR, new byte[] { 0xFF, 0xFF, 0x0F, 0x00 }); + // Sets the PN5180 into IDLE state + SpiWriteRegister(Command.WRITE_REGISTER_AND_MASK, Register.SYSTEM_CONFIG, new byte[] { 0xF8, 0xFF, 0xFF, 0xFF }); + // Activates TRANSCEIVE routine + SpiWriteRegister(Command.WRITE_REGISTER_OR_MASK, Register.SYSTEM_CONFIG, new byte[] { 0x03, 0x00, 0x00, 0x00 }); + return ret; + } #endregion #region SPI primitives diff --git a/src/devices/Pn5180/Pn5180.csproj b/src/devices/Pn5180/Pn5180.csproj index 426fa4701f..64e289352c 100644 --- a/src/devices/Pn5180/Pn5180.csproj +++ b/src/devices/Pn5180/Pn5180.csproj @@ -9,5 +9,6 @@ + \ No newline at end of file diff --git a/src/devices/Pn5180/README.md b/src/devices/Pn5180/README.md index 5f90739a10..c3a552c348 100644 --- a/src/devices/Pn5180/README.md +++ b/src/devices/Pn5180/README.md @@ -108,7 +108,22 @@ do while (!Console.KeyAvailable); ``` -Please note that the ```ListenToCardIso14443TypeA``` and ```ListenToCardIso14443TypeB``` can be configured with different transceiver and receiver configurations. Usually the configuration need to match but you can adjust and change them. See the section with Radio Frequency configuration for more information. +And for an ISO 15693 card: + +```csharp +if (pn5180.ListenToCardIso15693(TransmitterRadioFrequencyConfiguration.Iso15693_ASK100_26, ReceiverRadioFrequencyConfiguration.Iso15693_26, out IList? cards, 20000)) +{ + pn5180.ResetPN5180Configuration(TransmitterRadioFrequencyConfiguration.Iso15693_ASK100_26, ReceiverRadioFrequencyConfiguration.Iso15693_26); + foreach (Data26_53kbps card in cards) + { + Console.WriteLine($"Target number: {card.TargetNumber}"); + Console.WriteLine($"UID: {BitConverter.ToString(card.NfcId)}"); + Console.WriteLine($"DSFID: {card.Dsfid}"); + } +} +``` + +Please note that the ```ListenToCardIso14443TypeA```, ```ListenToCardIso14443TypeB``` and ```ListenToCardIso15693``` can be configured with different transceiver and receiver configurations. Usually the configuration need to match but you can adjust and change them. See the section with Radio Frequency configuration for more information. A card will be continuously tried to be detected during the duration on your polling. If nothing is detected or if any issue, the function will return false. @@ -116,6 +131,8 @@ Specific for type B cards, they have a target number. This target number is need You should deselect the Type B card at the end to release the target number. If not done, during the next poll, this implementation will test if the card is still present, keep it in this case. +For 15693 cards, the PN5180 can support up to 16 cards at the same time. ```ListenToCardIso15693``` can inventory multiple card at a time. + ## EEPROM You can fully access the PN5180 EEPROM. Here is an example on how to do it: @@ -265,6 +282,53 @@ else } ``` +This shows how to read and write ICODE SLIX (ISO 15693) card fully: + +```csharp +if (pn5180.ListenToCardIso15693(TransmitterRadioFrequencyConfiguration.Iso15693_ASK100_26, ReceiverRadioFrequencyConfiguration.Iso15693_26, out IList? cards, 20000)) +{ + pn5180.ResetPN5180Configuration(TransmitterRadioFrequencyConfiguration.Iso15693_ASK100_26, ReceiverRadioFrequencyConfiguration.Iso15693_26); + foreach (Data26_53kbps card in cards) + { + Console.WriteLine($"Target number: {card.TargetNumber}"); + Console.WriteLine($"UID: {BitConverter.ToString(card.NfcId)}"); + Console.WriteLine($"DSFID: {card.Dsfid}"); + if (card.NfcId[6] == 0x04) + { + IcodeCard icodeCard = new IcodeCard(pn5180, card.TargetNumber) + { + Uid = card.NfcId, + Capacity = IcodeCardCapacity.IcodeSlix, + }; + + icodeCard.GetSystemInformation(); + Console.WriteLine($"SystemInfo data is :{BitConverter.ToString(icodeCard.Data)}"); + icodeCard.Data = new byte[] { 0x1c, 0x1b, 0x1b, 0x1b }; + // icodeCard.LockBlock(27); + icodeCard.WriteSingleBlock(2); + Console.WriteLine($"write data response is :{BitConverter.ToString(icodeCard.Data)}"); + icodeCard.ReadMultipleBlocks(0, 3); + Console.WriteLine($"block 0~3 data is :{BitConverter.ToString(icodeCard.Data)}"); + for (byte i = 0; i < 28; i++) + { + if (icodeCard.ReadSingleBlock(i)) + { + Console.WriteLine($"Block {i} data is :{BitConverter.ToString(icodeCard.Data)}"); + } + else + { + icodeCard.Data = new byte[] { }; + } + } + } + else + { + Console.WriteLine("Only Icode cards are supported"); + } + } +} +``` + The [example](./samples/Program.cs) contains as well an implementation to fully dump the content of a credit card. ## Current implementation @@ -295,6 +359,7 @@ PN5180 as an initiator (reader) commands: - [X] Auto poll ISO 14443 type A cards - [X] Auto poll ISO 14443 type B cards +- [X] Auto poll ISO 15693 cards - [X] Deselect ISO 14443 type B cards - [X] Multi card support at the same time: partial, depending on the card, CID mandatory in all 14443 type B communications - [X] ISO 14443-4 communication protocol diff --git a/src/devices/Pn5180/samples/Pn5180sample.csproj b/src/devices/Pn5180/samples/Pn5180sample.csproj index a527c61a88..39fb769c44 100644 --- a/src/devices/Pn5180/samples/Pn5180sample.csproj +++ b/src/devices/Pn5180/samples/Pn5180sample.csproj @@ -6,6 +6,7 @@ + diff --git a/src/devices/Pn5180/samples/Program.cs b/src/devices/Pn5180/samples/Program.cs index ee2a2aa07d..1cd871e430 100644 --- a/src/devices/Pn5180/samples/Program.cs +++ b/src/devices/Pn5180/samples/Program.cs @@ -5,10 +5,12 @@ using System.Collections.Generic; using System.Device.Gpio; using System.Device.Spi; +using System.Drawing.Imaging; using System.Linq; using System.Text; using System.Threading; using Iot.Device.Card.CreditCardProcessing; +using Iot.Device.Card.Icode; using Iot.Device.Card.Mifare; using Iot.Device.Card.Ultralight; using Iot.Device.Ft4222; @@ -51,6 +53,7 @@ Console.WriteLine($"5 Pull ISO 14443 Type A and B cards, display information"); Console.WriteLine($"6 Pull ISO 14443 B cards, display information"); Console.WriteLine($"7 dump Ultralight card and various tests"); +Console.WriteLine($"8 Pull ISO 15693 cards, display information"); choice = Console.ReadKey().KeyChar; Console.WriteLine(); Console.WriteLine(); @@ -83,6 +86,10 @@ { ProcessUltralight(); } +else if (choice == '8') +{ + ICode(); +} else { Console.WriteLine($"Not a valid choice, please choose the test you want to run"); @@ -590,3 +597,51 @@ void ProcessUltralight() Console.WriteLine("Error writing NDEF data on card"); } } + +void ICode() +{ + Console.WriteLine(); + // Poll the data for 20 seconds + if (pn5180.ListenToCardIso15693(TransmitterRadioFrequencyConfiguration.Iso15693_ASK100_26, ReceiverRadioFrequencyConfiguration.Iso15693_26, out IList? cards, 20000)) + { + pn5180.ResetPN5180Configuration(TransmitterRadioFrequencyConfiguration.Iso15693_ASK100_26, ReceiverRadioFrequencyConfiguration.Iso15693_26); + foreach (Data26_53kbps card in cards) + { + Console.WriteLine($"Target number: {card.TargetNumber}"); + Console.WriteLine($"UID: {BitConverter.ToString(card.NfcId)}"); + Console.WriteLine($"DSFID: {card.Dsfid}"); + if (card.NfcId[6] == 0x04) + { + IcodeCard icodeCard = new IcodeCard(pn5180, card.TargetNumber) + { + Uid = card.NfcId, + Capacity = IcodeCardCapacity.IcodeSlix, + }; + + icodeCard.GetSystemInformation(); + Console.WriteLine($"SystemInfo data is :{BitConverter.ToString(icodeCard.Data)}"); + icodeCard.Data = new byte[] { 0x1c, 0x1b, 0x1b, 0x1b }; + // icodeCard.LockBlock(27); + icodeCard.WriteSingleBlock(2); + Console.WriteLine($"write data response is :{BitConverter.ToString(icodeCard.Data)}"); + icodeCard.ReadMultipleBlocks(0, 3); + Console.WriteLine($"block 0~3 data is :{BitConverter.ToString(icodeCard.Data)}"); + for (byte i = 0; i < 28; i++) + { + if (icodeCard.ReadSingleBlock(i)) + { + Console.WriteLine($"Block {i} data is :{BitConverter.ToString(icodeCard.Data)}"); + } + else + { + icodeCard.Data = new byte[] { }; + } + } + } + else + { + Console.WriteLine("Only Icode cards are supported"); + } + } + } +}