diff --git a/examples/pn532_emulatetag/pn532_emulatetag.ino b/examples/pn532_emulatetag/pn532_emulatetag.ino new file mode 100644 index 0000000..8c82a61 --- /dev/null +++ b/examples/pn532_emulatetag/pn532_emulatetag.ino @@ -0,0 +1,117 @@ +/**************************************************************************/ +/*! + This example will attempt to connect to an ISO14443A + card or tag and retrieve some basic information about it + that can be used to determine what type of card it is. + + Note that you need the baud rate to be 115200 because we need to print + out the data and read from the card at the same time! + + To enable debug message, define DEBUG in PN532/PN532_debug.h + +*/ +/**************************************************************************/ + + +#define NFC_INTERFACE_I2C +#define BOARD_PN532_RF_REST 45 +#define BOARD_PN532_IRQ 17 +#define BOARD_I2C_SDA 8 +#define BOARD_I2C_SCL 18 + +#include +#include +#include +#include + +#include "NdefMessage.h" +#include "emulatetag.h" +uint8_t ndefBuf[120]; +NdefMessage message; +int messageSize; + +uint8_t uid[3] = {0x12, 0x34, 0x56}; + +PN532_I2C pn532i2c(Wire); +EmulateTag nfc(pn532i2c); + + +void setup(void) +{ + Serial.begin(115200); + + int start_delay = 3; + while (start_delay) + { + Serial.print(start_delay); + delay(1000); + start_delay--; + } + Serial.println("Hello!"); + + // pinMode(_irq, INPUT); + pinMode(BOARD_PN532_RF_REST, OUTPUT); + + digitalWrite(BOARD_PN532_RF_REST, LOW); + delay(1); // min 20ns + digitalWrite(BOARD_PN532_RF_REST, HIGH); + delay(2); // max 2ms + + // iic scan + Wire.setPins(BOARD_I2C_SDA, BOARD_I2C_SCL); + Wire.begin(); + + message = NdefMessage(); + message.addUriRecord("http://www.seeedstudio.com"); + messageSize = message.getEncodedSize(); + if (messageSize > sizeof(ndefBuf)) + { + Serial.println("ndefBuf is too small"); + while (1) + { + } + } + + Serial.print("Ndef encoded message size: "); + Serial.println(messageSize); + + message.encode(ndefBuf); + + // comment out this command for no ndef message + nfc.setNdefFile(ndefBuf, messageSize); + + // uid must be 3 bytes! + nfc.setUid(uid); + + nfc.init(); +} + +void loop(void) +{ + // uncomment for overriding ndef in case a write to this tag occured + // nfc.setNdefFile(ndefBuf, messageSize); + + // start emulation (blocks) + // nfc.emulate(); + + // or start emulation with timeout + if(!nfc.emulate(1000)){ // timeout 1 second + Serial.println("timed out"); + } + + // deny writing to the tag + // nfc.setTagWriteable(false); + + if (nfc.writeOccured()) + { + Serial.println("\nWrite occured !"); + uint8_t *tag_buf; + uint16_t length; + + nfc.getContent(&tag_buf, &length); + NdefMessage msg = NdefMessage(tag_buf, length); + // msg.print(); + } + + delay(20); +} diff --git a/lib/NDEF/Due.h b/lib/NDEF/Due.h new file mode 100644 index 0000000..c7f9f0b --- /dev/null +++ b/lib/NDEF/Due.h @@ -0,0 +1,25 @@ +// redefine some stuff so code works on Due +// http://arduino.cc/forum/index.php?&topic=153761.0 + +#ifndef Due_h +#define Due_h + +#if defined(__SAM3X8E__) + #define PROGMEM + #define pgm_read_byte(x) (*((char *)x)) +// #define pgm_read_word(x) (*((short *)(x & 0xfffffffe))) + #define pgm_read_word(x) ( ((*((unsigned char *)x + 1)) << 8) + (*((unsigned char *)x))) + #define pgm_read_byte_near(x) (*((char *)x)) + #define pgm_read_byte_far(x) (*((char *)x)) +// #define pgm_read_word_near(x) (*((short *)(x & 0xfffffffe)) +// #define pgm_read_word_far(x) (*((short *)(x & 0xfffffffe))) + #define pgm_read_word_near(x) ( ((*((unsigned char *)x + 1)) << 8) + (*((unsigned char *)x))) + #define pgm_read_word_far(x) ( ((*((unsigned char *)x + 1)) << 8) + (*((unsigned char *)x)))) + #define PSTR(x) x + #if defined F + #undef F + #endif + #define F(X) (X) +#endif + +#endif \ No newline at end of file diff --git a/lib/NDEF/LICENSE.txt b/lib/NDEF/LICENSE.txt new file mode 100644 index 0000000..4070d05 --- /dev/null +++ b/lib/NDEF/LICENSE.txt @@ -0,0 +1,29 @@ +Software License Agreement (BSD License) + +Copyright (c) 2013-2014, Don Coleman +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. + +3. Neither the name of the copyright holders nor the +names of its contributors may be used to endorse or promote products +derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS "AS IS" AND ANY +EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file diff --git a/lib/NDEF/MifareClassic.cpp b/lib/NDEF/MifareClassic.cpp new file mode 100644 index 0000000..acebdb8 --- /dev/null +++ b/lib/NDEF/MifareClassic.cpp @@ -0,0 +1,451 @@ +#include "MifareClassic.h" +#ifdef NDEF_SUPPORT_MIFARE_CLASSIC + +#define BLOCK_SIZE 16 +#define LONG_TLV_SIZE 4 +#define SHORT_TLV_SIZE 2 + +#define MIFARE_CLASSIC ("Mifare Classic") + +MifareClassic::MifareClassic(PN532& nfcShield) +{ + _nfcShield = &nfcShield; +} + +MifareClassic::~MifareClassic() +{ +} + +NfcTag MifareClassic::read(byte *uid, unsigned int uidLength) +{ + uint8_t key[6] = { 0xD3, 0xF7, 0xD3, 0xF7, 0xD3, 0xF7 }; + int currentBlock = 4; + int messageStartIndex = 0; + int messageLength = 0; + byte data[BLOCK_SIZE]; + + // read first block to get message length + int success = _nfcShield->mifareclassic_AuthenticateBlock(uid, uidLength, currentBlock, 0, key); + if (success) + { + success = _nfcShield->mifareclassic_ReadDataBlock(currentBlock, data); + if (success) + { + if (!decodeTlv(data, messageLength, messageStartIndex)) { + return NfcTag(uid, uidLength, "ERROR"); // TODO should the error message go in NfcTag? + } + } + else + { +#ifdef NDEF_USE_SERIAL + Serial.print(F("Error. Failed read block "));Serial.println(currentBlock); +#endif + return NfcTag(uid, uidLength, MIFARE_CLASSIC); + } + } + else + { +#ifdef NDEF_USE_SERIAL + Serial.println(F("Tag is not NDEF formatted.")); +#endif + // TODO set tag.isFormatted = false + return NfcTag(uid, uidLength, MIFARE_CLASSIC); + } + + // this should be nested in the message length loop + int index = 0; + int bufferSize = getBufferSize(messageLength); + uint8_t buffer[bufferSize]; + + #ifdef MIFARE_CLASSIC_DEBUG + Serial.print(F("Message Length "));Serial.println(messageLength); + Serial.print(F("Buffer Size "));Serial.println(bufferSize); + #endif + + while (index < bufferSize) + { + + // authenticate on every sector + if (_nfcShield->mifareclassic_IsFirstBlock(currentBlock)) + { + success = _nfcShield->mifareclassic_AuthenticateBlock(uid, uidLength, currentBlock, 0, key); + if (!success) + { +#ifdef NDEF_USE_SERIAL + Serial.print(F("Error. Block Authentication failed for "));Serial.println(currentBlock); +#endif + // TODO error handling + } + } + + // read the data + success = _nfcShield->mifareclassic_ReadDataBlock(currentBlock, &buffer[index]); + if (success) + { + #ifdef MIFARE_CLASSIC_DEBUG + Serial.print(F("Block "));Serial.print(currentBlock);Serial.print(" "); + _nfcShield->PrintHexChar(&buffer[index], BLOCK_SIZE); + #endif + } + else + { +#ifdef NDEF_USE_SERIAL + Serial.print(F("Read failed "));Serial.println(currentBlock); +#endif + // TODO handle errors here + } + + index += BLOCK_SIZE; + currentBlock++; + + // skip the trailer block + if (_nfcShield->mifareclassic_IsTrailerBlock(currentBlock)) + { + #ifdef MIFARE_CLASSIC_DEBUG + Serial.print(F("Skipping block "));Serial.println(currentBlock); + #endif + currentBlock++; + } + } + + return NfcTag(uid, uidLength, MIFARE_CLASSIC, &buffer[messageStartIndex], messageLength); +} + +int MifareClassic::getBufferSize(int messageLength) +{ + + int bufferSize = messageLength; + + // TLV header is 2 or 4 bytes, TLV terminator is 1 byte. + if (messageLength < 0xFF) + { + bufferSize += SHORT_TLV_SIZE + 1; + } + else + { + bufferSize += LONG_TLV_SIZE + 1; + } + + // bufferSize needs to be a multiple of BLOCK_SIZE + if (bufferSize % BLOCK_SIZE != 0) + { + bufferSize = ((bufferSize / BLOCK_SIZE) + 1) * BLOCK_SIZE; + } + + return bufferSize; +} + +// skip null tlvs (0x0) before the real message +// technically unlimited null tlvs, but we assume +// T & L of TLV in the first block we read +int MifareClassic::getNdefStartIndex(byte *data) +{ + + for (int i = 0; i < BLOCK_SIZE; i++) + { + if (data[i] == 0x0) + { + // do nothing, skip + } + else if (data[i] == 0x3) + { + return i; + } + else + { +#ifdef NDEF_USE_SERIAL + Serial.print("Unknown TLV ");Serial.println(data[i], HEX); +#endif + return -2; + } + } + + return -1; +} + +// Decode the NDEF data length from the Mifare TLV +// Leading null TLVs (0x0) are skipped +// Assuming T & L of TLV will be in the first block +// messageLength and messageStartIndex written to the parameters +// success or failure status is returned +// +// { 0x3, LENGTH } +// { 0x3, 0xFF, LENGTH, LENGTH } +bool MifareClassic::decodeTlv(byte *data, int &messageLength, int &messageStartIndex) +{ + int i = getNdefStartIndex(data); + + if (i < 0 || data[i] != 0x3) + { +#ifdef NDEF_USE_SERIAL + Serial.println(F("Error. Can't decode message length.")); +#endif + return false; + } + else + { + if (data[i+1] == 0xFF) + { + messageLength = ((0xFF & data[i+2]) << 8) | (0xFF & data[i+3]); + messageStartIndex = i + LONG_TLV_SIZE; + } + else + { + messageLength = data[i+1]; + messageStartIndex = i + SHORT_TLV_SIZE; + } + } + + return true; +} + +// Intialized NDEF tag contains one empty NDEF TLV 03 00 FE - AN1304 6.3.1 +// We are formatting in read/write mode with a NDEF TLV 03 03 and an empty NDEF record D0 00 00 FE - AN1304 6.3.2 +boolean MifareClassic::formatNDEF(byte * uid, unsigned int uidLength) +{ + uint8_t keya[6] = { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }; + uint8_t emptyNdefMesg[16] = {0x03, 0x03, 0xD0, 0x00, 0x00, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; + uint8_t sectorbuffer0[16] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; + uint8_t sectorbuffer4[16] = {0xD3, 0xF7, 0xD3, 0xF7, 0xD3, 0xF7, 0x7F, 0x07, 0x88, 0x40, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; + + boolean success = _nfcShield->mifareclassic_AuthenticateBlock (uid, uidLength, 0, 0, keya); + if (!success) + { +#ifdef NDEF_USE_SERIAL + Serial.println(F("Unable to authenticate block 0 to enable card formatting!")); +#endif + return false; + } + success = _nfcShield->mifareclassic_FormatNDEF(); + if (!success) + { +#ifdef NDEF_USE_SERIAL + Serial.println(F("Unable to format the card for NDEF")); +#endif + } + else + { + for (int i=4; i<64; i+=4) { + success = _nfcShield->mifareclassic_AuthenticateBlock (uid, uidLength, i, 0, keya); + + if (success) { + if (i == 4) // special handling for block 4 + { + if (!(_nfcShield->mifareclassic_WriteDataBlock (i, emptyNdefMesg))) + { +#ifdef NDEF_USE_SERIAL + Serial.print(F("Unable to write block "));Serial.println(i); +#endif + } + } + else + { + if (!(_nfcShield->mifareclassic_WriteDataBlock (i, sectorbuffer0))) + { +#ifdef NDEF_USE_SERIAL + Serial.print(F("Unable to write block "));Serial.println(i); +#endif + } + } + if (!(_nfcShield->mifareclassic_WriteDataBlock (i+1, sectorbuffer0))) + { +#ifdef NDEF_USE_SERIAL + Serial.print(F("Unable to write block "));Serial.println(i+1); +#endif + } + if (!(_nfcShield->mifareclassic_WriteDataBlock (i+2, sectorbuffer0))) + { +#ifdef NDEF_USE_SERIAL + Serial.print(F("Unable to write block "));Serial.println(i+2); +#endif + } + if (!(_nfcShield->mifareclassic_WriteDataBlock (i+3, sectorbuffer4))) + { +#ifdef NDEF_USE_SERIAL + Serial.print(F("Unable to write block "));Serial.println(i+3); +#endif + } + } else { + unsigned int iii=uidLength; +#ifdef NDEF_USE_SERIAL + Serial.print(F("Unable to authenticate block "));Serial.println(i); +#endif + _nfcShield->readPassiveTargetID(PN532_MIFARE_ISO14443A, uid, (uint8_t*)&iii); + } + } + } + return success; +} + +#define NR_SHORTSECTOR (32) // Number of short sectors on Mifare 1K/4K +#define NR_LONGSECTOR (8) // Number of long sectors on Mifare 4K +#define NR_BLOCK_OF_SHORTSECTOR (4) // Number of blocks in a short sector +#define NR_BLOCK_OF_LONGSECTOR (16) // Number of blocks in a long sector + +// Determine the sector trailer block based on sector number +#define BLOCK_NUMBER_OF_SECTOR_TRAILER(sector) (((sector)mifareclassic_AuthenticateBlock (uid, uidLength, BLOCK_NUMBER_OF_SECTOR_TRAILER(idx), 1, (uint8_t *)KEY_DEFAULT_KEYAB); + if (!success) + { +#ifdef NDEF_USE_SERIAL + Serial.print(F("Authentication failed for sector ")); Serial.println(idx); +#endif + return false; + } + + // Step 2: Write to the other blocks + if (idx == 0) + { + memset(blockBuffer, 0, sizeof(blockBuffer)); + if (!(_nfcShield->mifareclassic_WriteDataBlock((BLOCK_NUMBER_OF_SECTOR_TRAILER(idx)) - 2, blockBuffer))) + { +#ifdef NDEF_USE_SERIAL + Serial.print(F("Unable to write to sector ")); Serial.println(idx); +#endif + } + } + else + { + memset(blockBuffer, 0, sizeof(blockBuffer)); + // this block has not to be overwritten for block 0. It contains Tag id and other unique data. + if (!(_nfcShield->mifareclassic_WriteDataBlock((BLOCK_NUMBER_OF_SECTOR_TRAILER(idx)) - 3, blockBuffer))) + { +#ifdef NDEF_USE_SERIAL + Serial.print(F("Unable to write to sector ")); Serial.println(idx); +#endif + } + if (!(_nfcShield->mifareclassic_WriteDataBlock((BLOCK_NUMBER_OF_SECTOR_TRAILER(idx)) - 2, blockBuffer))) + { +#ifdef NDEF_USE_SERIAL + Serial.print(F("Unable to write to sector ")); Serial.println(idx); +#endif + } + } + + memset(blockBuffer, 0, sizeof(blockBuffer)); + + if (!(_nfcShield->mifareclassic_WriteDataBlock((BLOCK_NUMBER_OF_SECTOR_TRAILER(idx)) - 1, blockBuffer))) + { +#ifdef NDEF_USE_SERIAL + Serial.print(F("Unable to write to sector ")); Serial.println(idx); +#endif + } + + // Step 3: Reset both keys to 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF + memcpy(blockBuffer, KEY_DEFAULT_KEYAB, sizeof(KEY_DEFAULT_KEYAB)); + memcpy(blockBuffer + 6, blankAccessBits, sizeof(blankAccessBits)); + blockBuffer[9] = 0x69; + memcpy(blockBuffer + 10, KEY_DEFAULT_KEYAB, sizeof(KEY_DEFAULT_KEYAB)); + + // Step 4: Write the trailer block + if (!(_nfcShield->mifareclassic_WriteDataBlock((BLOCK_NUMBER_OF_SECTOR_TRAILER(idx)), blockBuffer))) + { +#ifdef NDEF_USE_SERIAL + Serial.print(F("Unable to write trailer block of sector ")); Serial.println(idx); +#endif + } + } + return true; +} + +boolean MifareClassic::write(NdefMessage& m, byte * uid, unsigned int uidLength) +{ + + uint8_t encoded[m.getEncodedSize()]; + m.encode(encoded); + + uint8_t buffer[getBufferSize(sizeof(encoded))]; + memset(buffer, 0, sizeof(buffer)); + + #ifdef MIFARE_CLASSIC_DEBUG + Serial.print(F("sizeof(encoded) "));Serial.println(sizeof(encoded)); + Serial.print(F("sizeof(buffer) "));Serial.println(sizeof(buffer)); + #endif + + if (sizeof(encoded) < 0xFF) + { + buffer[0] = 0x3; + buffer[1] = sizeof(encoded); + memcpy(&buffer[2], encoded, sizeof(encoded)); + buffer[2+sizeof(encoded)] = 0xFE; // terminator + } + else + { + buffer[0] = 0x3; + buffer[1] = 0xFF; + buffer[2] = ((sizeof(encoded) >> 8) & 0xFF); + buffer[3] = (sizeof(encoded) & 0xFF); + memcpy(&buffer[4], encoded, sizeof(encoded)); + buffer[4+sizeof(encoded)] = 0xFE; // terminator + } + + // Write to tag + unsigned int index = 0; + int currentBlock = 4; + uint8_t key[6] = { 0xD3, 0xF7, 0xD3, 0xF7, 0xD3, 0xF7 }; // this is Sector 1 - 15 key + + while (index < sizeof(buffer)) + { + + if (_nfcShield->mifareclassic_IsFirstBlock(currentBlock)) + { + int success = _nfcShield->mifareclassic_AuthenticateBlock(uid, uidLength, currentBlock, 0, key); + if (!success) + { +#ifdef NDEF_USE_SERIAL + Serial.print(F("Error. Block Authentication failed for "));Serial.println(currentBlock); +#endif + return false; + } + } + + int write_success = _nfcShield->mifareclassic_WriteDataBlock (currentBlock, &buffer[index]); + if (write_success) + { + #ifdef MIFARE_CLASSIC_DEBUG + Serial.print(F("Wrote block "));Serial.print(currentBlock);Serial.print(" - "); + _nfcShield->PrintHexChar(&buffer[index], BLOCK_SIZE); + #endif + } + else + { +#ifdef NDEF_USE_SERIAL + Serial.print(F("Write failed "));Serial.println(currentBlock); +#endif + return false; + } + index += BLOCK_SIZE; + currentBlock++; + + if (_nfcShield->mifareclassic_IsTrailerBlock(currentBlock)) + { + // can't write to trailer block + #ifdef MIFARE_CLASSIC_DEBUG + Serial.print(F("Skipping block "));Serial.println(currentBlock); + #endif + currentBlock++; + } + + } + + return true; +} +#endif diff --git a/lib/NDEF/MifareClassic.h b/lib/NDEF/MifareClassic.h new file mode 100644 index 0000000..95cb5c7 --- /dev/null +++ b/lib/NDEF/MifareClassic.h @@ -0,0 +1,31 @@ +#ifndef MifareClassic_h +#define MifareClassic_h + +// Comment out next line to remove Mifare Classic and save memory +#define NDEF_SUPPORT_MIFARE_CLASSIC + +#ifdef NDEF_SUPPORT_MIFARE_CLASSIC + +#include +#include +#include +#include + +class MifareClassic +{ + public: + MifareClassic(PN532& nfcShield); + ~MifareClassic(); + NfcTag read(byte *uid, unsigned int uidLength); + boolean write(NdefMessage& ndefMessage, byte *uid, unsigned int uidLength); + boolean formatNDEF(byte * uid, unsigned int uidLength); + boolean formatMifare(byte * uid, unsigned int uidLength); + private: + PN532* _nfcShield; + int getBufferSize(int messageLength); + int getNdefStartIndex(byte *data); + bool decodeTlv(byte *data, int &messageLength, int &messageStartIndex); +}; + +#endif +#endif diff --git a/lib/NDEF/MifareUltralight.cpp b/lib/NDEF/MifareUltralight.cpp new file mode 100644 index 0000000..c139b7d --- /dev/null +++ b/lib/NDEF/MifareUltralight.cpp @@ -0,0 +1,259 @@ +#include + +#define ULTRALIGHT_PAGE_SIZE 4 +#define ULTRALIGHT_READ_SIZE 4 // we should be able to read 16 bytes at a time + +#define ULTRALIGHT_DATA_START_PAGE 4 +#define ULTRALIGHT_MESSAGE_LENGTH_INDEX 1 +#define ULTRALIGHT_DATA_START_INDEX 2 +#define ULTRALIGHT_MAX_PAGE 63 + +#define NFC_FORUM_TAG_TYPE_2 ("NFC Forum Type 2") + +MifareUltralight::MifareUltralight(PN532& nfcShield) +{ + nfc = &nfcShield; + ndefStartIndex = 0; + messageLength = 0; +} + +MifareUltralight::~MifareUltralight() +{ +} + +NfcTag MifareUltralight::read(byte * uid, unsigned int uidLength) +{ + if (isUnformatted()) + { +#ifdef NDEF_USE_SERIAL + Serial.println(F("WARNING: Tag is not formatted.")); +#endif + return NfcTag(uid, uidLength, NFC_FORUM_TAG_TYPE_2); + } + + readCapabilityContainer(); // meta info for tag + findNdefMessage(); + calculateBufferSize(); + + if (messageLength == 0) { // data is 0x44 0x03 0x00 0xFE + NdefMessage message = NdefMessage(); + message.addEmptyRecord(); + return NfcTag(uid, uidLength, NFC_FORUM_TAG_TYPE_2, message); + } + + boolean success; + uint8_t page; + uint8_t index = 0; + byte buffer[bufferSize]; + for (page = ULTRALIGHT_DATA_START_PAGE; page < ULTRALIGHT_MAX_PAGE; page++) + { + // read the data + success = nfc->mifareultralight_ReadPage(page, &buffer[index]); + if (success) + { + #ifdef MIFARE_ULTRALIGHT_DEBUG + Serial.print(F("Page "));Serial.print(page);Serial.print(" "); + nfc->PrintHexChar(&buffer[index], ULTRALIGHT_PAGE_SIZE); + #endif + } + else + { +#ifdef NDEF_USE_SERIAL + Serial.print(F("Read failed "));Serial.println(page); +#endif + // TODO error handling + messageLength = 0; + break; + } + + if (index >= (messageLength + ndefStartIndex)) + { + break; + } + + index += ULTRALIGHT_PAGE_SIZE; + } + + NdefMessage ndefMessage = NdefMessage(&buffer[ndefStartIndex], messageLength); + return NfcTag(uid, uidLength, NFC_FORUM_TAG_TYPE_2, ndefMessage); + +} + +boolean MifareUltralight::isUnformatted() +{ + uint8_t page = 4; + byte data[ULTRALIGHT_READ_SIZE]; + boolean success = nfc->mifareultralight_ReadPage (page, data); + if (success) + { + return (data[0] == 0xFF && data[1] == 0xFF && data[2] == 0xFF && data[3] == 0xFF); + } + else + { +#ifdef NDEF_USE_SERIAL + Serial.print(F("Error. Failed read page "));Serial.println(page); +#endif + return false; + } +} + +// page 3 has tag capabilities +void MifareUltralight::readCapabilityContainer() +{ + byte data[ULTRALIGHT_PAGE_SIZE]; + int success = nfc->mifareultralight_ReadPage (3, data); + if (success) + { + // See AN1303 - different rules for Mifare Family byte2 = (additional data + 48)/8 + tagCapacity = data[2] * 8; + #ifdef MIFARE_ULTRALIGHT_DEBUG + Serial.print(F("Tag capacity "));Serial.print(tagCapacity);Serial.println(F(" bytes")); + #endif + + // TODO future versions should get lock information + } +} + +// read enough of the message to find the ndef message length +void MifareUltralight::findNdefMessage() +{ + int page; + byte data[12]; // 3 pages + byte* data_ptr = &data[0]; + + // the nxp read command reads 4 pages, unfortunately adafruit give me one page at a time + boolean success = true; + for (page = 4; page < 6; page++) + { + success = success && nfc->mifareultralight_ReadPage(page, data_ptr); + #ifdef MIFARE_ULTRALIGHT_DEBUG + Serial.print(F("Page "));Serial.print(page);Serial.print(F(" - ")); + nfc->PrintHexChar(data_ptr, 4); + #endif + data_ptr += ULTRALIGHT_PAGE_SIZE; + } + + if (success) + { + if (data[0] == 0x03) + { + messageLength = data[1]; + ndefStartIndex = 2; + } + else if (data[5] == 0x3) // page 5 byte 1 + { + // TODO should really read the lock control TLV to ensure byte[5] is correct + messageLength = data[6]; + ndefStartIndex = 7; + } + } + + #ifdef MIFARE_ULTRALIGHT_DEBUG + Serial.print(F("messageLength "));Serial.println(messageLength); + Serial.print(F("ndefStartIndex "));Serial.println(ndefStartIndex); + #endif +} + +// buffer is larger than the message, need to handle some data before and after +// message and need to ensure we read full pages +void MifareUltralight::calculateBufferSize() +{ + // TLV terminator 0xFE is 1 byte + bufferSize = messageLength + ndefStartIndex + 1; + + if (bufferSize % ULTRALIGHT_READ_SIZE != 0) + { + // buffer must be an increment of page size + bufferSize = ((bufferSize / ULTRALIGHT_READ_SIZE) + 1) * ULTRALIGHT_READ_SIZE; + } +} + +boolean MifareUltralight::write(NdefMessage& m, byte * uid, unsigned int uidLength) +{ + if (isUnformatted()) + { +#ifdef NDEF_USE_SERIAL + Serial.println(F("WARNING: Tag is not formatted.")); +#endif + return false; + } + readCapabilityContainer(); // meta info for tag + + messageLength = m.getEncodedSize(); + ndefStartIndex = messageLength < 0xFF ? 2 : 4; + calculateBufferSize(); + + if(bufferSize>tagCapacity) { + #ifdef MIFARE_ULTRALIGHT_DEBUG + Serial.print(F("Encoded Message length exceeded tag Capacity "));Serial.println(tagCapacity); + #endif + return false; + } + + uint8_t encoded[bufferSize]; + uint8_t * src = encoded; + unsigned int position = 0; + uint8_t page = ULTRALIGHT_DATA_START_PAGE; + + // Set message size. With ultralight should always be less than 0xFF but who knows? + + encoded[0] = 0x3; + if (messageLength < 0xFF) + { + encoded[1] = messageLength; + } + else + { + encoded[1] = 0xFF; + encoded[2] = ((messageLength >> 8) & 0xFF); + encoded[3] = (messageLength & 0xFF); + } + + m.encode(encoded+ndefStartIndex); + // this is always at least 1 byte copy because of terminator. + memset(encoded+ndefStartIndex+messageLength,0,bufferSize-ndefStartIndex-messageLength); + encoded[ndefStartIndex+messageLength] = 0xFE; // terminator + + #ifdef MIFARE_ULTRALIGHT_DEBUG + Serial.print(F("messageLength "));Serial.println(messageLength); + Serial.print(F("Tag Capacity "));Serial.println(tagCapacity); + nfc->PrintHex(encoded,bufferSize); + #endif + + while (position < bufferSize){ //bufferSize is always times pagesize so no "last chunk" check + // write page + if (!nfc->mifareultralight_WritePage(page, src)) + return false; + #ifdef MIFARE_ULTRALIGHT_DEBUG + Serial.print(F("Wrote page "));Serial.print(page);Serial.print(F(" - ")); + nfc->PrintHex(src,ULTRALIGHT_PAGE_SIZE); + #endif + page++; + src+=ULTRALIGHT_PAGE_SIZE; + position+=ULTRALIGHT_PAGE_SIZE; + } + return true; +} + +// Mifare Ultralight can't be reset to factory state +// zero out tag data like the NXP Tag Write Android application +boolean MifareUltralight::clean() +{ + readCapabilityContainer(); // meta info for tag + + uint8_t pages = (tagCapacity / ULTRALIGHT_PAGE_SIZE) + ULTRALIGHT_DATA_START_PAGE; + + // factory tags have 0xFF, but OTP-CC blocks have already been set so we use 0x00 + uint8_t data[4] = { 0x00, 0x00, 0x00, 0x00 }; + + for (int i = ULTRALIGHT_DATA_START_PAGE; i < pages; i++) { + #ifdef MIFARE_ULTRALIGHT_DEBUG + Serial.print(F("Wrote page "));Serial.print(i);Serial.print(F(" - ")); + nfc->PrintHex(data, ULTRALIGHT_PAGE_SIZE); + #endif + if (!nfc->mifareultralight_WritePage(i, data)) { + return false; + } + } + return true; +} diff --git a/lib/NDEF/MifareUltralight.h b/lib/NDEF/MifareUltralight.h new file mode 100644 index 0000000..2b98849 --- /dev/null +++ b/lib/NDEF/MifareUltralight.h @@ -0,0 +1,28 @@ +#ifndef MifareUltralight_h +#define MifareUltralight_h + +#include +#include +#include + +class MifareUltralight +{ + public: + MifareUltralight(PN532& nfcShield); + ~MifareUltralight(); + NfcTag read(byte *uid, unsigned int uidLength); + boolean write(NdefMessage& ndefMessage, byte *uid, unsigned int uidLength); + boolean clean(); + private: + PN532* nfc; + unsigned int tagCapacity; + unsigned int messageLength; + unsigned int bufferSize; + unsigned int ndefStartIndex; + boolean isUnformatted(); + void readCapabilityContainer(); + void findNdefMessage(); + void calculateBufferSize(); +}; + +#endif diff --git a/lib/NDEF/Ndef.cpp b/lib/NDEF/Ndef.cpp new file mode 100644 index 0000000..846bc89 --- /dev/null +++ b/lib/NDEF/Ndef.cpp @@ -0,0 +1,59 @@ +#include "Ndef.h" + +#ifdef NDEF_USE_SERIAL +// Borrowed from Adafruit_NFCShield_I2C +void PrintHex(const byte * data, const long numBytes) +{ + int32_t szPos; + for (szPos=0; szPos < numBytes; szPos++) + { + Serial.print("0x"); + // Append leading 0 for small values + if (data[szPos] <= 0xF) + Serial.print("0"); + Serial.print(data[szPos]&0xff, HEX); + if ((numBytes > 1) && (szPos != numBytes - 1)) + { + Serial.print(" "); + } + } + Serial.println(""); +} + +// Borrowed from Adafruit_NFCShield_I2C +void PrintHexChar(const byte * data, const long numBytes) +{ + int32_t szPos; + for (szPos=0; szPos < numBytes; szPos++) + { + // Append leading 0 for small values + if (data[szPos] <= 0xF) + Serial.print("0"); + Serial.print(data[szPos], HEX); + if ((numBytes > 1) && (szPos != numBytes - 1)) + { + Serial.print(" "); + } + } + Serial.print(" "); + for (szPos=0; szPos < numBytes; szPos++) + { + if (data[szPos] <= 0x1F) + Serial.print("."); + else + Serial.print((char)data[szPos]); + } + Serial.println(""); +} + +// Note if buffer % blockSize != 0, last block will not be written +void DumpHex(const byte * data, const long numBytes, const unsigned int blockSize) +{ + int i; + for (i = 0; i < (numBytes / blockSize); i++) + { + PrintHexChar(data, blockSize); + data += blockSize; + } +} +#endif diff --git a/lib/NDEF/Ndef.h b/lib/NDEF/Ndef.h new file mode 100644 index 0000000..aafc048 --- /dev/null +++ b/lib/NDEF/Ndef.h @@ -0,0 +1,19 @@ +#ifndef Ndef_h +#define Ndef_h + +// To save memory and stop serial output comment out the next line +#define NDEF_USE_SERIAL + +/* NOTE: To use the Ndef library in your code, don't include Ndef.h + See README.md for details on which files to include in your sketch. +*/ + +#include + +#ifdef NDEF_USE_SERIAL +void PrintHex(const byte *data, const long numBytes); +void PrintHexChar(const byte *data, const long numBytes); +void DumpHex(const byte *data, const long numBytes, const int blockSize); +#endif + +#endif diff --git a/lib/NDEF/NdefMessage.cpp b/lib/NDEF/NdefMessage.cpp new file mode 100644 index 0000000..7b79e66 --- /dev/null +++ b/lib/NDEF/NdefMessage.cpp @@ -0,0 +1,278 @@ +#include + +NdefMessage::NdefMessage(void) +{ + _recordCount = 0; +} + +NdefMessage::NdefMessage(const byte * data, const int numBytes) +{ + #ifdef NDEF_DEBUG + Serial.print(F("Decoding "));Serial.print(numBytes);Serial.println(F(" bytes")); + PrintHexChar(data, numBytes); + //DumpHex(data, numBytes, 16); + #endif + + _recordCount = 0; + + int index = 0; + + while (index <= numBytes) + { + + // decode tnf - first byte is tnf with bit flags + // see the NFDEF spec for more info + byte tnf_byte = data[index]; + // bool mb = tnf_byte & 0x80; + bool me = tnf_byte & 0x40; + // bool cf = tnf_byte & 0x20; + bool sr = tnf_byte & 0x10; + bool il = tnf_byte & 0x8; + byte tnf = (tnf_byte & 0x7); + + NdefRecord record = NdefRecord(); + record.setTnf(tnf); + + index++; + int typeLength = data[index]; + + uint32_t payloadLength = 0; + if (sr) + { + index++; + payloadLength = data[index]; + } + else + { + payloadLength = + (static_cast(data[index]) << 24) + | (static_cast(data[index+1]) << 16) + | (static_cast(data[index+2]) << 8) + | static_cast(data[index+3]); + index += 4; + } + + int idLength = 0; + if (il) + { + index++; + idLength = data[index]; + } + + index++; + record.setType(&data[index], typeLength); + index += typeLength; + + if (il) + { + record.setId(&data[index], idLength); + index += idLength; + } + + record.setPayload(&data[index], payloadLength); + index += payloadLength; + + addRecord(record); + + if (me) break; // last message + } + +} + +NdefMessage::NdefMessage(const NdefMessage& rhs) +{ + + _recordCount = rhs._recordCount; + for (unsigned int i = 0; i < _recordCount; i++) + { + _records[i] = rhs._records[i]; + } + +} + +NdefMessage::~NdefMessage() +{ +} + +NdefMessage& NdefMessage::operator=(const NdefMessage& rhs) +{ + + if (this != &rhs) + { + + // delete existing records + for (unsigned int i = 0; i < _recordCount; i++) + { + // TODO Dave: is this the right way to delete existing records? + _records[i] = NdefRecord(); + } + + _recordCount = rhs._recordCount; + for (unsigned int i = 0; i < _recordCount; i++) + { + _records[i] = rhs._records[i]; + } + } + return *this; +} + +unsigned int NdefMessage::getRecordCount() +{ + return _recordCount; +} + +int NdefMessage::getEncodedSize() +{ + int size = 0; + for (unsigned int i = 0; i < _recordCount; i++) + { + size += _records[i].getEncodedSize(); + } + return size; +} + +// TODO change this to return uint8_t* +void NdefMessage::encode(uint8_t* data) +{ + // assert sizeof(data) >= getEncodedSize() + uint8_t* data_ptr = &data[0]; + + for (unsigned int i = 0; i < _recordCount; i++) + { + _records[i].encode(data_ptr, i == 0, (i + 1) == _recordCount); + // TODO can NdefRecord.encode return the record size? + data_ptr += _records[i].getEncodedSize(); + } + +} + +boolean NdefMessage::addRecord(NdefRecord& record) +{ + + if (_recordCount < MAX_NDEF_RECORDS) + { + _records[_recordCount] = record; + _recordCount++; + return true; + } + else + { +#ifdef NDEF_USE_SERIAL + Serial.println(F("WARNING: Too many records. Increase MAX_NDEF_RECORDS.")); +#endif + return false; + } +} + +void NdefMessage::addMimeMediaRecord(String mimeType, String payload) +{ + + byte payloadBytes[payload.length() + 1]; + payload.getBytes(payloadBytes, sizeof(payloadBytes)); + + addMimeMediaRecord(mimeType, payloadBytes, payload.length()); +} + +void NdefMessage::addMimeMediaRecord(String mimeType, uint8_t* payload, int payloadLength) +{ + NdefRecord r = NdefRecord(); + r.setTnf(TNF_MIME_MEDIA); + + byte type[mimeType.length() + 1]; + mimeType.getBytes(type, sizeof(type)); + r.setType(type, mimeType.length()); + + r.setPayload(payload, payloadLength); + + addRecord(r); +} + +void NdefMessage::addTextRecord(String text) +{ + addTextRecord(text, "en"); +} + +void NdefMessage::addTextRecord(String text, String encoding) +{ + NdefRecord r = NdefRecord(); + r.setTnf(TNF_WELL_KNOWN); + + uint8_t RTD_TEXT[1] = { 0x54 }; // TODO this should be a constant or preprocessor + r.setType(RTD_TEXT, sizeof(RTD_TEXT)); + + // X is a placeholder for encoding length + // TODO is it more efficient to build w/o string concatenation? + String payloadString = "X" + encoding + text; + + byte payload[payloadString.length() + 1]; + payloadString.getBytes(payload, sizeof(payload)); + + // replace X with the real encoding length + payload[0] = encoding.length(); + + r.setPayload(payload, payloadString.length()); + + addRecord(r); +} + +void NdefMessage::addUriRecord(String uri) +{ + NdefRecord* r = new NdefRecord(); + r->setTnf(TNF_WELL_KNOWN); + + uint8_t RTD_URI[1] = { 0x55 }; // TODO this should be a constant or preprocessor + r->setType(RTD_URI, sizeof(RTD_URI)); + + // X is a placeholder for identifier code + String payloadString = "X" + uri; + + byte payload[payloadString.length() + 1]; + payloadString.getBytes(payload, sizeof(payload)); + + // add identifier code 0x0, meaning no prefix substitution + payload[0] = 0x0; + + r->setPayload(payload, payloadString.length()); + + addRecord(*r); + delete(r); +} + +void NdefMessage::addEmptyRecord() +{ + NdefRecord* r = new NdefRecord(); + r->setTnf(TNF_EMPTY); + addRecord(*r); + delete(r); +} + +NdefRecord NdefMessage::getRecord(int index) +{ + if (index > -1 && index < static_cast(_recordCount)) + { + return _records[index]; + } + else + { + return NdefRecord(); // would rather return NULL + } +} + +NdefRecord NdefMessage::operator[](int index) +{ + return getRecord(index); +} + +#ifdef NDEF_USE_SERIAL +void NdefMessage::print() +{ + Serial.print(F("\nNDEF Message "));Serial.print(_recordCount);Serial.print(F(" record")); + _recordCount == 1 ? Serial.print(", ") : Serial.print("s, "); + Serial.print(getEncodedSize());Serial.println(F(" bytes")); + + for (unsigned int i = 0; i < _recordCount; i++) + { + _records[i].print(); + } +} +#endif diff --git a/lib/NDEF/NdefMessage.h b/lib/NDEF/NdefMessage.h new file mode 100644 index 0000000..bc42c9f --- /dev/null +++ b/lib/NDEF/NdefMessage.h @@ -0,0 +1,41 @@ +#ifndef NdefMessage_h +#define NdefMessage_h + +#include +#include + +#define MAX_NDEF_RECORDS 4 + +class NdefMessage +{ + public: + NdefMessage(void); + NdefMessage(const byte *data, const int numBytes); + NdefMessage(const NdefMessage& rhs); + ~NdefMessage(); + NdefMessage& operator=(const NdefMessage& rhs); + + int getEncodedSize(); // need so we can pass array to encode + void encode(byte *data); + + boolean addRecord(NdefRecord& record); + void addMimeMediaRecord(String mimeType, String payload); + void addMimeMediaRecord(String mimeType, byte *payload, int payloadLength); + void addTextRecord(String text); + void addTextRecord(String text, String encoding); + void addUriRecord(String uri); + void addEmptyRecord(); + + unsigned int getRecordCount(); + NdefRecord getRecord(int index); + NdefRecord operator[](int index); + +#ifdef NDEF_USE_SERIAL + void print(); +#endif + private: + NdefRecord _records[MAX_NDEF_RECORDS]; + unsigned int _recordCount; +}; + +#endif \ No newline at end of file diff --git a/lib/NDEF/NdefRecord.cpp b/lib/NDEF/NdefRecord.cpp new file mode 100644 index 0000000..006c5b1 --- /dev/null +++ b/lib/NDEF/NdefRecord.cpp @@ -0,0 +1,354 @@ +#include "NdefRecord.h" + +NdefRecord::NdefRecord() +{ + //Serial.println("NdefRecord Constructor 1"); + _tnf = 0; + _typeLength = 0; + _payloadLength = 0; + _idLength = 0; + _type = (byte *)NULL; + _payload = (byte *)NULL; + _id = (byte *)NULL; +} + +NdefRecord::NdefRecord(const NdefRecord& rhs) +{ + //Serial.println("NdefRecord Constructor 2 (copy)"); + + _tnf = rhs._tnf; + _typeLength = rhs._typeLength; + _payloadLength = rhs._payloadLength; + _idLength = rhs._idLength; + _type = (byte *)NULL; + _payload = (byte *)NULL; + _id = (byte *)NULL; + + if (_typeLength) + { + _type = (byte*)malloc(_typeLength); + memcpy(_type, rhs._type, _typeLength); + } + + if (_payloadLength) + { + _payload = (byte*)malloc(_payloadLength); + memcpy(_payload, rhs._payload, _payloadLength); + } + + if (_idLength) + { + _id = (byte*)malloc(_idLength); + memcpy(_id, rhs._id, _idLength); + } + +} + +// TODO NdefRecord::NdefRecord(tnf, type, payload, id) + +NdefRecord::~NdefRecord() +{ + //Serial.println("NdefRecord Destructor"); + if (_typeLength) + { + free(_type); + } + + if (_payloadLength) + { + free(_payload); + } + + if (_idLength) + { + free(_id); + } +} + +NdefRecord& NdefRecord::operator=(const NdefRecord& rhs) +{ + //Serial.println("NdefRecord ASSIGN"); + + if (this != &rhs) + { + // free existing + if (_typeLength) + { + free(_type); + } + + if (_payloadLength) + { + free(_payload); + } + + if (_idLength) + { + free(_id); + } + + _tnf = rhs._tnf; + _typeLength = rhs._typeLength; + _payloadLength = rhs._payloadLength; + _idLength = rhs._idLength; + + if (_typeLength) + { + _type = (byte*)malloc(_typeLength); + memcpy(_type, rhs._type, _typeLength); + } + + if (_payloadLength) + { + _payload = (byte*)malloc(_payloadLength); + memcpy(_payload, rhs._payload, _payloadLength); + } + + if (_idLength) + { + _id = (byte*)malloc(_idLength); + memcpy(_id, rhs._id, _idLength); + } + } + return *this; +} + +// size of records in bytes +int NdefRecord::getEncodedSize() +{ + int size = 2; // tnf + typeLength + if (_payloadLength > 0xFF) + { + size += 4; + } + else + { + size += 1; + } + + if (_idLength) + { + size += 1; + } + + size += (_typeLength + _payloadLength + _idLength); + + return size; +} + +void NdefRecord::encode(byte *data, bool firstRecord, bool lastRecord) +{ + // assert data > getEncodedSize() + + uint8_t* data_ptr = &data[0]; + + *data_ptr = getTnfByte(firstRecord, lastRecord); + data_ptr += 1; + + *data_ptr = _typeLength; + data_ptr += 1; + + if (_payloadLength <= 0xFF) { // short record + *data_ptr = _payloadLength; + data_ptr += 1; + } else { // long format + // 4 bytes but we store length as an int + data_ptr[0] = 0x0; // (_payloadLength >> 24) & 0xFF; + data_ptr[1] = 0x0; // (_payloadLength >> 16) & 0xFF; + data_ptr[2] = (_payloadLength >> 8) & 0xFF; + data_ptr[3] = _payloadLength & 0xFF; + data_ptr += 4; + } + + if (_idLength) + { + *data_ptr = _idLength; + data_ptr += 1; + } + + //Serial.println(2); + memcpy(data_ptr, _type, _typeLength); + data_ptr += _typeLength; + + if (_idLength) + { + memcpy(data_ptr, _id, _idLength); + data_ptr += _idLength; + } + + memcpy(data_ptr, _payload, _payloadLength); + data_ptr += _payloadLength; +} + +byte NdefRecord::getTnfByte(bool firstRecord, bool lastRecord) +{ + int value = _tnf; + + if (firstRecord) { // mb + value = value | 0x80; + } + + if (lastRecord) { // + value = value | 0x40; + } + + // chunked flag is always false for now + // if (cf) { + // value = value | 0x20; + // } + + if (_payloadLength <= 0xFF) { + value = value | 0x10; + } + + if (_idLength) { + value = value | 0x8; + } + + return value; +} + +byte NdefRecord::getTnf() +{ + return _tnf; +} + +void NdefRecord::setTnf(byte tnf) +{ + _tnf = tnf; +} + +unsigned int NdefRecord::getTypeLength() +{ + return _typeLength; +} + +int NdefRecord::getPayloadLength() +{ + return _payloadLength; +} + +unsigned int NdefRecord::getIdLength() +{ + return _idLength; +} + +String NdefRecord::getType() +{ + char type[_typeLength + 1]; + memcpy(type, _type, _typeLength); + type[_typeLength] = '\0'; // null terminate + return String(type); +} + +// this assumes the caller created type correctly +void NdefRecord::getType(uint8_t* type) +{ + memcpy(type, _type, _typeLength); +} + +void NdefRecord::setType(const byte * type, const unsigned int numBytes) +{ + if(_typeLength) + { + free(_type); + } + + _type = (uint8_t*)malloc(numBytes); + memcpy(_type, type, numBytes); + _typeLength = numBytes; +} + +// assumes the caller sized payload properly +void NdefRecord::getPayload(byte *payload) +{ + memcpy(payload, _payload, _payloadLength); +} + +void NdefRecord::setPayload(const byte * payload, const int numBytes) +{ + if (_payloadLength) + { + free(_payload); + } + + _payload = (byte*)malloc(numBytes); + memcpy(_payload, payload, numBytes); + _payloadLength = numBytes; +} + +String NdefRecord::getId() +{ + char id[_idLength + 1]; + memcpy(id, _id, _idLength); + id[_idLength] = '\0'; // null terminate + return String(id); +} + +void NdefRecord::getId(byte *id) +{ + memcpy(id, _id, _idLength); +} + +void NdefRecord::setId(const byte * id, const unsigned int numBytes) +{ + if (_idLength) + { + free(_id); + } + + _id = (byte*)malloc(numBytes); + memcpy(_id, id, numBytes); + _idLength = numBytes; +} +#ifdef NDEF_USE_SERIAL + +void NdefRecord::print() +{ + Serial.println(F(" NDEF Record")); + Serial.print(F(" TNF 0x"));Serial.print(_tnf, HEX);Serial.print(" "); + switch (_tnf) { + case TNF_EMPTY: + Serial.println(F("Empty")); + break; + case TNF_WELL_KNOWN: + Serial.println(F("Well Known")); + break; + case TNF_MIME_MEDIA: + Serial.println(F("Mime Media")); + break; + case TNF_ABSOLUTE_URI: + Serial.println(F("Absolute URI")); + break; + case TNF_EXTERNAL_TYPE: + Serial.println(F("External")); + break; + case TNF_UNKNOWN: + Serial.println(F("Unknown")); + break; + case TNF_UNCHANGED: + Serial.println(F("Unchanged")); + break; + case TNF_RESERVED: + Serial.println(F("Reserved")); + break; + default: + Serial.println(); + } + Serial.print(F(" Type Length 0x"));Serial.print(_typeLength, HEX);Serial.print(" ");Serial.println(_typeLength); + Serial.print(F(" Payload Length 0x"));Serial.print(_payloadLength, HEX);;Serial.print(" ");Serial.println(_payloadLength); + if (_idLength) + { + Serial.print(F(" Id Length 0x"));Serial.println(_idLength, HEX); + } + Serial.print(F(" Type "));PrintHexChar(_type, _typeLength); + // TODO chunk large payloads so this is readable + Serial.print(F(" Payload "));PrintHexChar(_payload, _payloadLength); + if (_idLength) + { + Serial.print(F(" Id "));PrintHexChar(_id, _idLength); + } + Serial.print(F(" Record is "));Serial.print(getEncodedSize());Serial.println(" bytes"); + +} +#endif diff --git a/lib/NDEF/NdefRecord.h b/lib/NDEF/NdefRecord.h new file mode 100644 index 0000000..bbdd8a2 --- /dev/null +++ b/lib/NDEF/NdefRecord.h @@ -0,0 +1,60 @@ +#ifndef NdefRecord_h +#define NdefRecord_h + +#include +#include +#include + +#define TNF_EMPTY 0x0 +#define TNF_WELL_KNOWN 0x01 +#define TNF_MIME_MEDIA 0x02 +#define TNF_ABSOLUTE_URI 0x03 +#define TNF_EXTERNAL_TYPE 0x04 +#define TNF_UNKNOWN 0x05 +#define TNF_UNCHANGED 0x06 +#define TNF_RESERVED 0x07 + +class NdefRecord +{ + public: + NdefRecord(); + NdefRecord(const NdefRecord& rhs); + ~NdefRecord(); + NdefRecord& operator=(const NdefRecord& rhs); + + int getEncodedSize(); + void encode(byte *data, bool firstRecord, bool lastRecord); + + unsigned int getTypeLength(); + int getPayloadLength(); + unsigned int getIdLength(); + + byte getTnf(); + void getType(byte *type); + void getPayload(byte *payload); + void getId(byte *id); + + // convenience methods + String getType(); + String getId(); + + void setTnf(byte tnf); + void setType(const byte *type, const unsigned int numBytes); + void setPayload(const byte *payload, const int numBytes); + void setId(const byte *id, const unsigned int numBytes); + +#ifdef NDEF_USE_SERIAL + void print(); +#endif + private: + byte getTnfByte(bool firstRecord, bool lastRecord); + byte _tnf; // 3 bit + unsigned int _typeLength; + int _payloadLength; + unsigned int _idLength; + byte *_type; + byte *_payload; + byte *_id; +}; + +#endif \ No newline at end of file diff --git a/lib/NDEF/NfcAdapter.cpp b/lib/NDEF/NfcAdapter.cpp new file mode 100644 index 0000000..9099bde --- /dev/null +++ b/lib/NDEF/NfcAdapter.cpp @@ -0,0 +1,218 @@ +#include + +NfcAdapter::NfcAdapter(PN532Interface &interface) +{ + shield = new PN532(interface); +} + +NfcAdapter::~NfcAdapter(void) +{ + delete shield; +} + +void NfcAdapter::begin(boolean verbose) +{ + shield->begin(); + + uint32_t versiondata = shield->getFirmwareVersion(); + + if (! versiondata) + { +#ifdef NDEF_USE_SERIAL + Serial.print(F("Didn't find PN53x board")); +#endif + while (1); // halt + } + + if (verbose) + { +#ifdef NDEF_USE_SERIAL + Serial.print(F("Found chip PN5")); Serial.println((versiondata>>24) & 0xFF, HEX); + Serial.print(F("Firmware ver. ")); Serial.print((versiondata>>16) & 0xFF, DEC); + Serial.print('.'); Serial.println((versiondata>>8) & 0xFF, DEC); +#endif + } + // configure board to read RFID tags + shield->SAMConfig(); +} + +boolean NfcAdapter::tagPresent(unsigned long timeout) +{ + uint8_t success; + uidLength = 0; + + if (timeout == 0) + { + success = shield->readPassiveTargetID(PN532_MIFARE_ISO14443A, uid, (uint8_t*)&uidLength); + } + else + { + success = shield->readPassiveTargetID(PN532_MIFARE_ISO14443A, uid, (uint8_t*)&uidLength, timeout); + } + return success; +} + +boolean NfcAdapter::erase() +{ + NdefMessage message = NdefMessage(); + message.addEmptyRecord(); + return write(message); +} + +boolean NfcAdapter::format() +{ + boolean success; +#ifdef NDEF_SUPPORT_MIFARE_CLASSIC + if (uidLength == 4) + { + MifareClassic mifareClassic = MifareClassic(*shield); + success = mifareClassic.formatNDEF(uid, uidLength); + } + else +#endif + { +#ifdef NDEF_USE_SERIAL + Serial.print(F("Unsupported Tag.")); +#endif + success = false; + } + return success; +} + +boolean NfcAdapter::clean() +{ + uint8_t type = guessTagType(); + +#ifdef NDEF_SUPPORT_MIFARE_CLASSIC + if (type == TAG_TYPE_MIFARE_CLASSIC) + { + #ifdef NDEF_DEBUG + Serial.println(F("Cleaning Mifare Classic")); + #endif + MifareClassic mifareClassic = MifareClassic(*shield); + return mifareClassic.formatMifare(uid, uidLength); + } + else +#endif + if (type == TAG_TYPE_2) + { + #ifdef NDEF_DEBUG + Serial.println(F("Cleaning Mifare Ultralight")); + #endif + MifareUltralight ultralight = MifareUltralight(*shield); + return ultralight.clean(); + } + else + { +#ifdef NDEF_USE_SERIAL + Serial.print(F("No driver for card type "));Serial.println(type); +#endif + return false; + } + +} + + +NfcTag NfcAdapter::read() +{ + uint8_t type = guessTagType(); + +#ifdef NDEF_SUPPORT_MIFARE_CLASSIC + if (type == TAG_TYPE_MIFARE_CLASSIC) + { + #ifdef NDEF_DEBUG + Serial.println(F("Reading Mifare Classic")); + #endif + MifareClassic mifareClassic = MifareClassic(*shield); + return mifareClassic.read(uid, uidLength); + } + else +#endif + if (type == TAG_TYPE_2) + { + #ifdef NDEF_DEBUG + Serial.println(F("Reading Mifare Ultralight")); + #endif + MifareUltralight ultralight = MifareUltralight(*shield); + return ultralight.read(uid, uidLength); + } + else if (type == TAG_TYPE_UNKNOWN) + { +#ifdef NDEF_USE_SERIAL + Serial.print(F("Can not determine tag type")); +#endif + return NfcTag(uid, uidLength); + } + else + { + // Serial.print(F("No driver for card type "));Serial.println(type); + // TODO should set type here + return NfcTag(uid, uidLength); + } + +} + +boolean NfcAdapter::write(NdefMessage& ndefMessage) +{ + boolean success; + uint8_t type = guessTagType(); + +#ifdef NDEF_SUPPORT_MIFARE_CLASSIC + if (type == TAG_TYPE_MIFARE_CLASSIC) + { + #ifdef NDEF_DEBUG + Serial.println(F("Writing Mifare Classic")); + #endif + MifareClassic mifareClassic = MifareClassic(*shield); + success = mifareClassic.write(ndefMessage, uid, uidLength); + } + else +#endif + if (type == TAG_TYPE_2) + { + #ifdef NDEF_DEBUG + Serial.println(F("Writing Mifare Ultralight")); + #endif + MifareUltralight mifareUltralight = MifareUltralight(*shield); + success = mifareUltralight.write(ndefMessage, uid, uidLength); + } + else if (type == TAG_TYPE_UNKNOWN) + { +#ifdef NDEF_USE_SERIAL + Serial.print(F("Can not determine tag type")); +#endif + success = false; + } + else + { +#ifdef NDEF_USE_SERIAL + Serial.print(F("No driver for card type "));Serial.println(type); +#endif + success = false; + } + + return success; +} + +// TODO this should return a Driver MifareClassic, MifareUltralight, Type 4, Unknown +// Guess Tag Type by looking at the ATQA and SAK values +// Need to follow spec for Card Identification. Maybe AN1303, AN1305 and ??? +unsigned int NfcAdapter::guessTagType() +{ + + // 4 byte id - Mifare Classic + // - ATQA 0x4 && SAK 0x8 + // 7 byte id + // - ATQA 0x44 && SAK 0x8 - Mifare Classic + // - ATQA 0x44 && SAK 0x0 - Mifare Ultralight NFC Forum Type 2 + // - ATQA 0x344 && SAK 0x20 - NFC Forum Type 4 + + if (uidLength == 4) + { + return TAG_TYPE_MIFARE_CLASSIC; + } + else + { + return TAG_TYPE_2; + } +} diff --git a/lib/NDEF/NfcAdapter.h b/lib/NDEF/NfcAdapter.h new file mode 100644 index 0000000..1ef9e59 --- /dev/null +++ b/lib/NDEF/NfcAdapter.h @@ -0,0 +1,45 @@ +#ifndef NfcAdapter_h +#define NfcAdapter_h + +#include +#include +#include +#include + +// Drivers +#include +#include + +#define TAG_TYPE_MIFARE_CLASSIC (0) +#define TAG_TYPE_1 (1) +#define TAG_TYPE_2 (2) +#define TAG_TYPE_3 (3) +#define TAG_TYPE_4 (4) +#define TAG_TYPE_UNKNOWN (99) + +#define IRQ (2) +#define RESET (3) // Not connected by default on the NFC Shield + +class NfcAdapter { + public: + NfcAdapter(PN532Interface &interface); + + ~NfcAdapter(void); + void begin(boolean verbose=true); + boolean tagPresent(unsigned long timeout=0); // tagAvailable + NfcTag read(); + boolean write(NdefMessage& ndefMessage); + // erase tag by writing an empty NDEF record + boolean erase(); + // format a tag as NDEF + boolean format(); + // reset tag back to factory state + boolean clean(); + private: + PN532* shield; + byte uid[7]; // Buffer to store the returned UID + unsigned int uidLength; // Length of the UID (4 or 7 bytes depending on ISO14443A card type) + unsigned int guessTagType(); +}; + +#endif diff --git a/lib/NDEF/NfcDriver.h b/lib/NDEF/NfcDriver.h new file mode 100644 index 0000000..07d0ef8 --- /dev/null +++ b/lib/NDEF/NfcDriver.h @@ -0,0 +1,9 @@ +// eventually the NFC drivers should extend this class +class NfcDriver +{ + public: + virtual NfcTag read(uint8_t * uid, int uidLength) = 0; + virtual boolean write(NdefMessage& message, uint8_t * uid, int uidLength) = 0; + // erase() + // format() +} \ No newline at end of file diff --git a/lib/NDEF/NfcTag.cpp b/lib/NDEF/NfcTag.cpp new file mode 100644 index 0000000..c0f4d3e --- /dev/null +++ b/lib/NDEF/NfcTag.cpp @@ -0,0 +1,123 @@ +#include + +NfcTag::NfcTag() +{ + _uid = 0; + _uidLength = 0; + _tagType = "Unknown"; + _ndefMessage = (NdefMessage*)NULL; +} + +NfcTag::NfcTag(byte *uid, unsigned int uidLength) +{ + _uid = uid; + _uidLength = uidLength; + _tagType = "Unknown"; + _ndefMessage = (NdefMessage*)NULL; +} + +NfcTag::NfcTag(byte *uid, unsigned int uidLength, String tagType) +{ + _uid = uid; + _uidLength = uidLength; + _tagType = tagType; + _ndefMessage = (NdefMessage*)NULL; +} + +NfcTag::NfcTag(byte *uid, unsigned int uidLength, String tagType, NdefMessage& ndefMessage) +{ + _uid = uid; + _uidLength = uidLength; + _tagType = tagType; + _ndefMessage = new NdefMessage(ndefMessage); +} + +// I don't like this version, but it will use less memory +NfcTag::NfcTag(byte *uid, unsigned int uidLength, String tagType, const byte *ndefData, const int ndefDataLength) +{ + _uid = uid; + _uidLength = uidLength; + _tagType = tagType; + _ndefMessage = new NdefMessage(ndefData, ndefDataLength); +} + +NfcTag::~NfcTag() +{ + delete _ndefMessage; +} + +NfcTag& NfcTag::operator=(const NfcTag& rhs) +{ + if (this != &rhs) + { + delete _ndefMessage; + _uid = rhs._uid; + _uidLength = rhs._uidLength; + _tagType = rhs._tagType; + // TODO do I need a copy here? + _ndefMessage = rhs._ndefMessage; + } + return *this; +} + +uint8_t NfcTag::getUidLength() +{ + return _uidLength; +} + +void NfcTag::getUid(byte *uid, unsigned int uidLength) +{ + memcpy(uid, _uid, _uidLength < uidLength ? _uidLength : uidLength); +} + +String NfcTag::getUidString() +{ + String uidString = ""; + for (unsigned int i = 0; i < _uidLength; i++) + { + if (i > 0) + { + uidString += " "; + } + + if (_uid[i] < 0xF) + { + uidString += "0"; + } + + uidString += String((unsigned int)_uid[i], (unsigned char)HEX); + } + uidString.toUpperCase(); + return uidString; +} + +String NfcTag::getTagType() +{ + return _tagType; +} + +boolean NfcTag::hasNdefMessage() +{ + return (_ndefMessage != NULL); +} + +NdefMessage NfcTag::getNdefMessage() +{ + return *_ndefMessage; +} +#ifdef NDEF_USE_SERIAL + +void NfcTag::print() +{ + Serial.print(F("NFC Tag - "));Serial.println(_tagType); + Serial.print(F("UID "));Serial.println(getUidString()); + if (_ndefMessage == NULL) + { + Serial.println(F("\nNo NDEF Message")); + } + else + { + _ndefMessage->print(); + } +} +#endif diff --git a/lib/NDEF/NfcTag.h b/lib/NDEF/NfcTag.h new file mode 100644 index 0000000..e3a9a9c --- /dev/null +++ b/lib/NDEF/NfcTag.h @@ -0,0 +1,36 @@ +#ifndef NfcTag_h +#define NfcTag_h + +#include +#include +#include + +class NfcTag +{ + public: + NfcTag(); + NfcTag(byte *uid, unsigned int uidLength); + NfcTag(byte *uid, unsigned int uidLength, String tagType); + NfcTag(byte *uid, unsigned int uidLength, String tagType, NdefMessage& ndefMessage); + NfcTag(byte *uid, unsigned int uidLength, String tagType, const byte *ndefData, const int ndefDataLength); + ~NfcTag(void); + NfcTag& operator=(const NfcTag& rhs); + uint8_t getUidLength(); + void getUid(byte *uid, unsigned int uidLength); + String getUidString(); + String getTagType(); + boolean hasNdefMessage(); + NdefMessage getNdefMessage(); +#ifdef NDEF_USE_SERIAL + void print(); +#endif + private: + byte *_uid; + unsigned int _uidLength; + String _tagType; // Mifare Classic, NFC Forum Type {1,2,3,4}, Unknown + NdefMessage* _ndefMessage; + // TODO capacity + // TODO isFormatted +}; + +#endif \ No newline at end of file diff --git a/lib/NDEF/README.md b/lib/NDEF/README.md new file mode 100644 index 0000000..14dba0d --- /dev/null +++ b/lib/NDEF/README.md @@ -0,0 +1,134 @@ +# NDEF Library for Arduino + +Read and Write NDEF messages on NFC Tags with Arduino. + +NFC Data Exchange Format (NDEF) is a common data format that operates across all NFC devices, regardless of the underlying tag or device technology. + +This code works with the [Adafruit NFC Shield](https://www.adafruit.com/products/789), [Seeed Studio NFC Shield v2.0](http://www.seeedstudio.com/depot/nfc-shield-v20-p-1370.html) and the [Seeed Studio NFC Shield](http://www.seeedstudio.com/depot/nfc-shield-p-916.html?cPath=73). The library supports I2C for the Adafruit shield and SPI with the Seeed shields. The Adafruit Shield can also be modified to use SPI. It should also work with the [Adafruit NFC Breakout Board](https://www.adafruit.com/products/364). + +### Supports + - Reading from Mifare Classic Tags with 4 byte UIDs. + - Writing to Mifare Classic Tags with 4 byte UIDs. + - Reading from Mifare Ultralight tags. + - Writing to Mifare Ultralight tags. + - Peer to Peer with the Seeed Studio shield + +### Requires + +[Yihui Xiong's PN532 Library](https://github.com/Seeed-Studio/PN532) + +## Getting Started + +To use the Ndef library in your code, include the following in your sketch + +For the Adafruit Shield using I2C + + #include + #include + #include + #include + + PN532_I2C pn532_i2c(Wire); + NfcAdapter nfc = NfcAdapter(pn532_i2c); + +For the Seeed Shield using SPI + + #include + #include + #include + #include + + PN532_SPI pn532spi(SPI, 10); + NfcAdapter nfc = NfcAdapter(pn532spi); + +### NfcAdapter + +The user interacts with the NfcAdapter to read and write NFC tags using the NFC shield. + +Read a message from a tag + + if (nfc.tagPresent()) { + NfcTag tag = nfc.read(); + tag.print(); + } + +Write a message to a tag + + if (nfc.tagPresent()) { + NdefMessage message = NdefMessage(); + message.addTextRecord("Hello, Arduino!"); + success = nfc.write(message); + } + +Erase a tag. Tags are erased by writing an empty NDEF message. Tags are not zeroed out the old data may still be read off a tag using an application like [NXP's TagInfo](https://play.google.com/store/apps/details?id=com.nxp.taginfolite&hl=en). + + if (nfc.tagPresent()) { + success = nfc.erase(); + } + + +Format a Mifare Classic tag as NDEF. + + if (nfc.tagPresent()) { + success = nfc.format(); + } + + +Clean a tag. Cleaning resets a tag back to a factory-like state. For Mifare Classic, tag is zeroed and reformatted as Mifare Classic (non-NDEF). For Mifare Ultralight, the tag is zeroed and left empty. + + if (nfc.tagPresent()) { + success = nfc.clean(); + } + + +### NfcTag + +Reading a tag with the shield, returns a NfcTag object. The NfcTag object contains meta data about the tag UID, technology, size. When an NDEF tag is read, the NfcTag object contains a NdefMessage. + +### NdefMessage + +A NdefMessage consist of one or more NdefRecords. + +The NdefMessage object has helper methods for adding records. + + ndefMessage.addTextRecord("hello, world"); + ndefMessage.addUriRecord("http://arduino.cc"); + +The NdefMessage object is responsible for encoding NdefMessage into bytes so it can be written to a tag. The NdefMessage also decodes bytes read from a tag back into a NdefMessage object. + +### NdefRecord + +A NdefRecord carries a payload and info about the payload within a NdefMessage. + +### Peer to Peer + +Peer to Peer is provided by the LLCP and SNEP support in the [Seeed Studio library](https://github.com/Seeed-Studio/PN532). P2P requires SPI and has only been tested with the Seeed Studio shield. Peer to Peer was tested between Arduino and Android or BlackBerry 10. (Unfortunately Windows Phone 8 did not work.) See [P2P_Send](examples/P2P_Send/P2P_Send.ino) and [P2P_Receive](examples/P2P_Receive/P2P_Receive.ino) for more info. + +### Specifications + +This code is based on the "NFC Data Exchange Format (NDEF) Technical Specification" and the "Record Type Definition Technical Specifications" that can be downloaded from the [NFC Forum](http://www.nfc-forum.org/specs/spec_license). + +### Tests + +To run the tests, you'll need [ArduinoUnit](https://github.com/mmurdoch/arduinounit). To "install", I clone the repo to my home directory and symlink the source into ~/Documents/Arduino/libraries/ArduinoUnit. + + $ cd ~ + $ git clone git@github.com:mmurdoch/arduinounit.git + $ cd ~/Documents/Arduino/libraries/ + $ ln -s ~/arduinounit/src ArduinoUnit + +Tests can be run on an Uno without a NFC shield, since the NDEF logic is what is being tested. + +## Warning + +This software is in development. It works for the happy path. Error handling could use improvement. It runs out of memory, especially on the Uno board. Use small messages with the Uno. The Due board can write larger messages. Please submit patches. + +## Book +Need more info? Check out my book +Beginning NFC: Near Field Communication with Arduino, Android, and PhoneGap. + +Beginning NFC + +## License + +[BSD License](https://github.com/don/Ndef/blob/master/LICENSE.txt) (c) 2013-2014, Don Coleman diff --git a/lib/NDEF/examples/CleanTag/CleanTag.ino b/lib/NDEF/examples/CleanTag/CleanTag.ino new file mode 100644 index 0000000..01445f8 --- /dev/null +++ b/lib/NDEF/examples/CleanTag/CleanTag.ino @@ -0,0 +1,45 @@ +// Clean resets a tag back to factory-like state +// For Mifare Classic, tag is zero'd and reformatted as Mifare Classic +// For Mifare Ultralight, tags is zero'd and left empty + +#if 0 +#include +#include +#include +#include + +PN532_SPI pn532spi(SPI, 10); +NfcAdapter nfc = NfcAdapter(pn532spi); +#else + +#include +#include +#include +#include + +PN532_I2C pn532_i2c(Wire); +NfcAdapter nfc = NfcAdapter(pn532_i2c); +#endif + +void setup(void) { + Serial.begin(9600); + Serial.println("NFC Tag Cleaner"); + nfc.begin(); +} + +void loop(void) { + + Serial.println("\nPlace a tag on the NFC reader to clean."); + + if (nfc.tagPresent()) { + + bool success = nfc.clean(); + if (success) { + Serial.println("\nSuccess, tag restored to factory state."); + } else { + Serial.println("\nError, unable to clean tag."); + } + + } + delay(5000); +} diff --git a/lib/NDEF/examples/EraseTag/EraseTag.ino b/lib/NDEF/examples/EraseTag/EraseTag.ino new file mode 100644 index 0000000..eade2d1 --- /dev/null +++ b/lib/NDEF/examples/EraseTag/EraseTag.ino @@ -0,0 +1,42 @@ +// Erases a NFC tag by writing an empty NDEF message + +#if 0 +#include +#include +#include +#include + +PN532_SPI pn532spi(SPI, 10); +NfcAdapter nfc = NfcAdapter(pn532spi); +#else + +#include +#include +#include +#include + +PN532_I2C pn532_i2c(Wire); +NfcAdapter nfc = NfcAdapter(pn532_i2c); +#endif + +void setup(void) { + Serial.begin(9600); + Serial.println("NFC Tag Eraser"); + nfc.begin(); +} + +void loop(void) { + Serial.println("\nPlace a tag on the NFC reader to erase."); + + if (nfc.tagPresent()) { + + bool success = nfc.erase(); + if (success) { + Serial.println("\nSuccess, tag contains an empty record."); + } else { + Serial.println("\nUnable to erase tag."); + } + + } + delay(5000); +} diff --git a/lib/NDEF/examples/FormatTag/FormatTag.ino b/lib/NDEF/examples/FormatTag/FormatTag.ino new file mode 100644 index 0000000..ce5ec8e --- /dev/null +++ b/lib/NDEF/examples/FormatTag/FormatTag.ino @@ -0,0 +1,44 @@ +// Formats a Mifare Classic tags as an NDEF tag +// This will fail if the tag is already formatted NDEF +// nfc.clean will turn a NDEF formatted Mifare Classic tag back to the Mifare Classic format + +#if 0 +#include +#include +#include +#include + +PN532_SPI pn532spi(SPI, 10); +NfcAdapter nfc = NfcAdapter(pn532spi); +#else + +#include +#include +#include +#include + +PN532_I2C pn532_i2c(Wire); +NfcAdapter nfc = NfcAdapter(pn532_i2c); +#endif + +void setup(void) { + Serial.begin(9600); + Serial.println("NDEF Formatter"); + nfc.begin(); +} + +void loop(void) { + + Serial.println("\nPlace an unformatted Mifare Classic tag on the reader."); + if (nfc.tagPresent()) { + + bool success = nfc.format(); + if (success) { + Serial.println("\nSuccess, tag formatted as NDEF."); + } else { + Serial.println("\nFormat failed."); + } + + } + delay(5000); +} diff --git a/lib/NDEF/examples/P2P_Receive/P2P_Receive.ino b/lib/NDEF/examples/P2P_Receive/P2P_Receive.ino new file mode 100644 index 0000000..fb914ee --- /dev/null +++ b/lib/NDEF/examples/P2P_Receive/P2P_Receive.ino @@ -0,0 +1,30 @@ +// Receive a NDEF message from a Peer +// Requires SPI. Tested with Seeed Studio NFC Shield v2 + +#include "SPI.h" +#include "PN532_SPI.h" +#include "snep.h" +#include "NdefMessage.h" + +PN532_SPI pn532spi(SPI, 10); +SNEP nfc(pn532spi); +uint8_t ndefBuf[128]; + +void setup() { + Serial.begin(9600); + Serial.println("NFC Peer to Peer Example - Receive Message"); +} + +void loop() { + Serial.println("Waiting for message from Peer"); + int msgSize = nfc.read(ndefBuf, sizeof(ndefBuf)); + if (msgSize > 0) { + NdefMessage msg = NdefMessage(ndefBuf, msgSize); + msg.print(); + Serial.println("\nSuccess"); + } else { + Serial.println("Failed"); + } + delay(3000); +} + diff --git a/lib/NDEF/examples/P2P_Receive_LCD/P2P_Receive_LCD.ino b/lib/NDEF/examples/P2P_Receive_LCD/P2P_Receive_LCD.ino new file mode 100644 index 0000000..45adb8a --- /dev/null +++ b/lib/NDEF/examples/P2P_Receive_LCD/P2P_Receive_LCD.ino @@ -0,0 +1,71 @@ +// Receive a NDEF message from a Peer and +// display the payload of the first record on a LCD +// +// SeeedStudio NFC shield http://www.seeedstudio.com/depot/NFC-Shield-V20-p-1370.html +// LCD using the Adafruit backpack http://adafru.it/292 +// Adafruit Liquid Crystal library https://github.com/adafruit/LiquidCrystal +// Use a Android of BlackBerry phone to send a message to the NFC shield + +#include "SPI.h" +#include "PN532_SPI.h" +#include "snep.h" +#include "NdefMessage.h" + +#include "Wire.h" +#include "LiquidCrystal.h" + +PN532_SPI pn532spi(SPI, 10); +SNEP nfc(pn532spi); +uint8_t ndefBuf[128]; + +// Connect via i2c, default address #0 (A0-A2 not jumpered) +LiquidCrystal lcd(0); + +void setup() { + Serial.begin(9600); + // set up the LCD's number of rows and columns: + lcd.begin(16, 2); + Serial.println("NFC Peer to Peer Example - Receive Message"); +} + +void loop() { + Serial.println("Waiting for message from a peer"); + int msgSize = nfc.read(ndefBuf, sizeof(ndefBuf)); + if (msgSize > 0) { + NdefMessage msg = NdefMessage(ndefBuf, msgSize); + msg.print(); + + NdefRecord record = msg.getRecord(0); + + int payloadLength = record.getPayloadLength(); + byte payload[payloadLength]; + record.getPayload(payload); + + // The TNF and Type are used to determine how your application processes the payload + // There's no generic processing for the payload, it's returned as a byte[] + int startChar = 0; + if (record.getTnf() == TNF_WELL_KNOWN && record.getType() == "T") { // text message + // skip the language code + startChar = payload[0] + 1; + } else if (record.getTnf() == TNF_WELL_KNOWN && record.getType() == "U") { // URI + // skip the url prefix (future versions should decode) + startChar = 1; + } + + // Force the data into a String (might fail for some content) + // Real code should use smarter processing + String payloadAsString = ""; + for (int c = startChar; c < payloadLength; c++) { + payloadAsString += (char)payload[c]; + } + + // print on the LCD display + lcd.setCursor(0, 0); + lcd.print(payloadAsString); + + Serial.println("\nSuccess"); + } else { + Serial.println("Failed"); + } + delay(3000); +} diff --git a/lib/NDEF/examples/P2P_Send/P2P_Send.ino b/lib/NDEF/examples/P2P_Send/P2P_Send.ino new file mode 100644 index 0000000..5be595e --- /dev/null +++ b/lib/NDEF/examples/P2P_Send/P2P_Send.ino @@ -0,0 +1,42 @@ +// Sends a NDEF Message to a Peer +// Requires SPI. Tested with Seeed Studio NFC Shield v2 + +#include "SPI.h" +#include "PN532_SPI.h" +#include "snep.h" +#include "NdefMessage.h" + +PN532_SPI pn532spi(SPI, 10); +SNEP nfc(pn532spi); +uint8_t ndefBuf[128]; + +void setup() { + Serial.begin(9600); + Serial.println("NFC Peer to Peer Example - Send Message"); +} + +void loop() { + Serial.println("Send a message to Peer"); + + NdefMessage message = NdefMessage(); + message.addUriRecord("http://shop.oreilly.com/product/mobile/0636920021193.do"); + //message.addUriRecord("http://arduino.cc"); + //message.addUriRecord("https://github.com/don/NDEF"); + + + int messageSize = message.getEncodedSize(); + if (messageSize > sizeof(ndefBuf)) { + Serial.println("ndefBuf is too small"); + while (1) { + } + } + + message.encode(ndefBuf); + if (0 >= nfc.write(ndefBuf, messageSize)) { + Serial.println("Failed"); + } else { + Serial.println("Success"); + } + + delay(3000); +} diff --git a/lib/NDEF/examples/ReadTag/ReadTag.ino b/lib/NDEF/examples/ReadTag/ReadTag.ino new file mode 100644 index 0000000..5fd761d --- /dev/null +++ b/lib/NDEF/examples/ReadTag/ReadTag.ino @@ -0,0 +1,35 @@ + +#if 0 +#include +#include +#include +#include + +PN532_SPI pn532spi(SPI, 10); +NfcAdapter nfc = NfcAdapter(pn532spi); +#else + +#include +#include +#include +#include + +PN532_I2C pn532_i2c(Wire); +NfcAdapter nfc = NfcAdapter(pn532_i2c); +#endif + +void setup(void) { + Serial.begin(9600); + Serial.println("NDEF Reader"); + nfc.begin(); +} + +void loop(void) { + Serial.println("\nScan a NFC tag\n"); + if (nfc.tagPresent()) + { + NfcTag tag = nfc.read(); + tag.print(); + } + delay(5000); +} \ No newline at end of file diff --git a/lib/NDEF/examples/ReadTagExtended/ReadTagExtended.ino b/lib/NDEF/examples/ReadTagExtended/ReadTagExtended.ino new file mode 100644 index 0000000..57d2acb --- /dev/null +++ b/lib/NDEF/examples/ReadTagExtended/ReadTagExtended.ino @@ -0,0 +1,86 @@ +#if 0 +#include +#include +#include +#include + +PN532_SPI pn532spi(SPI, 10); +NfcAdapter nfc = NfcAdapter(pn532spi); +#else + +#include +#include +#include +#include + +PN532_I2C pn532_i2c(Wire); +NfcAdapter nfc = NfcAdapter(pn532_i2c); +#endif + +void setup(void) { + Serial.begin(9600); + Serial.println("NDEF Reader"); + nfc.begin(); +} + +void loop(void) { + Serial.println("\nScan a NFC tag\n"); + + if (nfc.tagPresent()) + { + NfcTag tag = nfc.read(); + Serial.println(tag.getTagType()); + Serial.print("UID: ");Serial.println(tag.getUidString()); + + if (tag.hasNdefMessage()) // every tag won't have a message + { + + NdefMessage message = tag.getNdefMessage(); + Serial.print("\nThis NFC Tag contains an NDEF Message with "); + Serial.print(message.getRecordCount()); + Serial.print(" NDEF Record"); + if (message.getRecordCount() != 1) { + Serial.print("s"); + } + Serial.println("."); + + // cycle through the records, printing some info from each + int recordCount = message.getRecordCount(); + for (int i = 0; i < recordCount; i++) + { + Serial.print("\nNDEF Record ");Serial.println(i+1); + NdefRecord record = message.getRecord(i); + // NdefRecord record = message[i]; // alternate syntax + + Serial.print(" TNF: ");Serial.println(record.getTnf()); + Serial.print(" Type: ");Serial.println(record.getType()); // will be "" for TNF_EMPTY + + // The TNF and Type should be used to determine how your application processes the payload + // There's no generic processing for the payload, it's returned as a byte[] + int payloadLength = record.getPayloadLength(); + byte payload[payloadLength]; + record.getPayload(payload); + + // Print the Hex and Printable Characters + Serial.print(" Payload (HEX): "); + PrintHexChar(payload, payloadLength); + + // Force the data into a String (might work depending on the content) + // Real code should use smarter processing + String payloadAsString = ""; + for (int c = 0; c < payloadLength; c++) { + payloadAsString += (char)payload[c]; + } + Serial.print(" Payload (as String): "); + Serial.println(payloadAsString); + + // id is probably blank and will return "" + String uid = record.getId(); + if (uid != "") { + Serial.print(" ID: ");Serial.println(uid); + } + } + } + } + delay(3000); +} diff --git a/lib/NDEF/examples/WriteTag/WriteTag.ino b/lib/NDEF/examples/WriteTag/WriteTag.ino new file mode 100644 index 0000000..721c630 --- /dev/null +++ b/lib/NDEF/examples/WriteTag/WriteTag.ino @@ -0,0 +1,40 @@ +#if 0 +#include +#include +#include +#include + +PN532_SPI pn532spi(SPI, 10); +NfcAdapter nfc = NfcAdapter(pn532spi); +#else + +#include +#include +#include +#include + +PN532_I2C pn532_i2c(Wire); +NfcAdapter nfc = NfcAdapter(pn532_i2c); +#endif + +void setup() { + Serial.begin(9600); + Serial.println("NDEF Writer"); + nfc.begin(); +} + +void loop() { + Serial.println("\nPlace a formatted Mifare Classic or Ultralight NFC tag on the reader."); + if (nfc.tagPresent()) { + NdefMessage message = NdefMessage(); + message.addUriRecord("http://arduino.cc"); + + bool success = nfc.write(message); + if (success) { + Serial.println("Success. Try reading this tag with your phone."); + } else { + Serial.println("Write failed."); + } + } + delay(5000); +} \ No newline at end of file diff --git a/lib/NDEF/examples/WriteTagMultipleRecords/WriteTagMultipleRecords.ino b/lib/NDEF/examples/WriteTagMultipleRecords/WriteTagMultipleRecords.ino new file mode 100644 index 0000000..87d2d62 --- /dev/null +++ b/lib/NDEF/examples/WriteTagMultipleRecords/WriteTagMultipleRecords.ino @@ -0,0 +1,41 @@ +#if 0 +#include +#include +#include +#include + +PN532_SPI pn532spi(SPI, 10); +NfcAdapter nfc = NfcAdapter(pn532spi); +#else + +#include +#include +#include +#include + +PN532_I2C pn532_i2c(Wire); +NfcAdapter nfc = NfcAdapter(pn532_i2c); +#endif + +void setup() { + Serial.begin(9600); + Serial.println("NDEF Writer"); + nfc.begin(); +} + +void loop() { + Serial.println("\nPlace a formatted Mifare Classic NFC tag on the reader."); + if (nfc.tagPresent()) { + NdefMessage message = NdefMessage(); + message.addTextRecord("Hello, Arduino!"); + message.addUriRecord("http://arduino.cc"); + message.addTextRecord("Goodbye, Arduino!"); + boolean success = nfc.write(message); + if (success) { + Serial.println("Success. Try reading this tag with your phone."); + } else { + Serial.println("Write failed"); + } + } + delay(3000); +} diff --git a/lib/NDEF/keywords.txt b/lib/NDEF/keywords.txt new file mode 100644 index 0000000..7fc40c7 --- /dev/null +++ b/lib/NDEF/keywords.txt @@ -0,0 +1,55 @@ +####################################### +# Syntax Coloring Map For Ndef +####################################### + +####################################### +# Datatypes (KEYWORD1) +####################################### + +MifareClassic KEYWORD1 +MifareUltralight KEYWORD1 +NdefMessage KEYWORD1 +NdefRecord KEYWORD1 +NfcAdapter KEYWORD1 +NfcDriver KEYWORD1 +NfcTag KEYWORD1 + +####################################### +# Methods and Functions (KEYWORD2) +####################################### + +addEmptyRecord KEYWORD2 +addMimeMediaRecord KEYWORD2 +addRecord KEYWORD2 +addTextRecord KEYWORD2 +addUriRecord KEYWORD2 +begin KEYWORD2 +encode KEYWORD2 +erase KEYWORD2 +format KEYWORD2 +getEncodedSize KEYWORD2 +getId KEYWORD2 +getIdLength KEYWORD2 +getNdefMessage KEYWORD2 +getPayload KEYWORD2 +getPayloadLength KEYWORD2 +getRecord KEYWORD2 +getRecordCount KEYWORD2 +getTagType KEYWORD2 +getTnf KEYWORD2 +getType KEYWORD2 +getTypeLength KEYWORD2 +getUid KEYWORD2 +getUidLength KEYWORD2 +getUidString KEYWORD2 +hasNdefMessage KEYWORD2 +print KEYWORD2 +read KEYWORD2 +setId KEYWORD2 +setPayload KEYWORD2 +setTnf KEYWORD2 +setType KEYWORD2 +share KEYWORD2 +tagPresent KEYWORD2 +unshare KEYWORD2 +write KEYWORD2 diff --git a/lib/NDEF/library.properties b/lib/NDEF/library.properties new file mode 100644 index 0000000..34e9785 --- /dev/null +++ b/lib/NDEF/library.properties @@ -0,0 +1,10 @@ +name=NDEF +version=1.1.0 +author=Don Coleman +maintainer=Don Coleman +sentence=An Arduino library for NFC Data Exchange Format (NDEF). +paragraph=Read and write NDEF messages to NFC tags and peers. Supports I2C and SPI readers based on the PN532 NFC chip. Works with Adafruit and SeeedStudio NFC readers. This library depends on the Seeed Studio PN532 Library https://github.com/Seeed-Studio/PN532. +category=Communication +url=https://github.com/don/NDEF +architectures=* +includes=NfcAdapter.h \ No newline at end of file diff --git a/lib/NDEF/tests/NdefMemoryTest/NdefMemoryTest.ino b/lib/NDEF/tests/NdefMemoryTest/NdefMemoryTest.ino new file mode 100644 index 0000000..4d004ad --- /dev/null +++ b/lib/NDEF/tests/NdefMemoryTest/NdefMemoryTest.ino @@ -0,0 +1,222 @@ +#include +#include +#include +#include +#include + +void leakCheck(void (*callback)()) +{ + int start = freeMemory(); + (*callback)(); + int end = freeMemory(); + Serial.println((end - start), DEC); +} + +// Custom Assertion +void assertNoLeak(void (*callback)()) +{ + int start = freeMemory(); + (*callback)(); + int end = freeMemory(); + assertEqual(0, (start - end)); +} + +void record() +{ + NdefRecord* r = new NdefRecord(); + delete r; +} + +void emptyRecord() +{ + NdefRecord* r = new NdefRecord(); +#ifdef NDEF_USE_SERIAL + r->print(); +#endif + delete r; +} + +void textRecord() +{ + NdefRecord* r = new NdefRecord(); + r->setTnf(0x1); + uint8_t type[] = { 0x54 }; + r->setType(type, sizeof(type)); + uint8_t payload[] = { 0x1A, 0x1B, 0x1C }; + r->setPayload(payload, sizeof(payload)); +#ifdef NDEF_USE_SERIAL + r->print(); +#endif + delete r; +} + +void recordMallocZero() +{ + NdefRecord r = NdefRecord(); + String type = r.getType(); + String id = r.getId(); + byte payload[r.getPayloadLength()]; + r.getPayload(payload); +} + +// this is OK +void emptyMessage() +{ + NdefMessage* m = new NdefMessage(); + delete m; +} + +// this is OK +void printEmptyMessage() +{ + NdefMessage* m = new NdefMessage(); +#ifdef NDEF_USE_SERIAL + m->print(); +#endif + delete m; +} + +// this is OK +void printEmptyMessageNoNew() +{ + NdefMessage m = NdefMessage(); +#ifdef NDEF_USE_SERIAL + m.print(); +#endif +} + +void messageWithTextRecord() +{ + NdefMessage m = NdefMessage(); + m.addTextRecord("foo"); +#ifdef NDEF_USE_SERIAL + m.print(); +#endif +} + +void messageWithEmptyRecord() +{ + NdefMessage m = NdefMessage(); + NdefRecord r = NdefRecord(); + m.addRecord(r); +#ifdef NDEF_USE_SERIAL + m.print(); +#endif +} + +void messageWithoutHelper() +{ + NdefMessage m = NdefMessage(); + NdefRecord r = NdefRecord(); + r.setTnf(1); + uint8_t type[] = { 0x54 }; + r.setType(type, sizeof(type)); + uint8_t payload[] = { 0x02, 0x65, 0x6E, 0x66, 0x6F, 0x6F }; + r.setPayload(payload, sizeof(payload)); + m.addRecord(r); +#ifdef NDEF_USE_SERIAL + m.print(); +#endif +} + +void messageWithId() +{ + NdefMessage m = NdefMessage(); + NdefRecord r = NdefRecord(); + r.setTnf(1); + uint8_t type[] = { 0x54 }; + r.setType(type, sizeof(type)); + uint8_t payload[] = { 0x02, 0x65, 0x6E, 0x66, 0x6F, 0x6F }; + r.setPayload(payload, sizeof(payload)); + uint8_t id[] = { 0x0, 0x0, 0x0 }; + r.setId(id, sizeof(id)); + m.addRecord(r); +#ifdef NDEF_USE_SERIAL + m.print(); +#endif +} + +void message80() +{ + NdefMessage message = NdefMessage(); + message.addTextRecord("This record is 80 characters.X01234567890123456789012345678901234567890123456789"); +#ifdef NDEF_USE_SERIAL + //message.print(); +#endif +} + +void message100() +{ + NdefMessage message = NdefMessage(); + message.addTextRecord("This record is 100 characters.0123456789012345678901234567890123456789012345678901234567890123456789"); +#ifdef NDEF_USE_SERIAL + //message.print(); +#endif +} + +void message120() +{ + NdefMessage message = NdefMessage(); + message.addTextRecord("This record is 120 characters.012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789"); +#ifdef NDEF_USE_SERIAL + //message.print(); +#endif +} + +void setup() { + Serial.begin(9600); + Serial.println("\n"); + Serial.println(F("=========")); + Serial.println(freeMemory()); + Serial.println(F("=========")); +} + +test(memoryKludgeEnd) +{ + // TODO ensure the output matches start + Serial.println(F("=========")); + Serial.print("End ");Serial.println(freeMemory()); + Serial.println(F("=========")); +} + +test(recordLeaks) +{ + assertNoLeak(&record); + assertNoLeak(&emptyRecord); + assertNoLeak(&textRecord); +} + +test(recordAccessorLeaks) +{ + assertNoLeak(&recordMallocZero); +} + +test(messageLeaks) +{ + assertNoLeak(&emptyMessage); + assertNoLeak(&printEmptyMessage); + assertNoLeak(&printEmptyMessageNoNew); + assertNoLeak(&messageWithTextRecord); + assertNoLeak(&messageWithEmptyRecord); + assertNoLeak(&messageWithoutHelper); + assertNoLeak(&messageWithId); +} + +test(messageOneBigRecord) +{ + assertNoLeak(&message80); + // The next 2 fail. Maybe out of memory? Look into helper methods + //assertNoLeak(&message100); + //assertNoLeak(&message120); +} + +test(memoryKludgeStart) +{ + Serial.println(F("---------")); + Serial.print("Start ");Serial.println(freeMemory()); + Serial.println(F("---------")); +} + +void loop() { + Test::run(); +} diff --git a/lib/NDEF/tests/NdefMessageTest/NdefMessageTest.ino b/lib/NDEF/tests/NdefMessageTest/NdefMessageTest.ino new file mode 100644 index 0000000..6ca1fcf --- /dev/null +++ b/lib/NDEF/tests/NdefMessageTest/NdefMessageTest.ino @@ -0,0 +1,238 @@ +#include +#include +#include +#include +#include + +// Custom Assertion +void assertNoLeak(void (*callback)()) +{ + int start = freeMemory(); + (*callback)(); + int end = freeMemory(); + assertEqual(0, (start - end)); +} + +void assertBytesEqual(const uint8_t* expected, const uint8_t* actual, int size) { + for (int i = 0; i < size; i++) { + // Serial.print("> ");Serial.print(expected[i]);Serial.print(" ");Serial.println(actual[i]); + assertEqual(expected[i], actual[i]); + } +} + +void setup() { + Serial.begin(9600); +} + +test(messageDelete) +{ + int start = freeMemory(); + + NdefMessage* m1 = new NdefMessage(); + m1->addTextRecord("Foo"); + delete m1; + + int end = freeMemory(); +// Serial.print("Start ");Serial.println(start); +// Serial.print("End ");Serial.println(end); + assertEqual(0, (start-end)); +} + + +test(assign) +{ + int start = freeMemory(); + + if (true) // bogus block so automatic storage duration objects are deleted + { + NdefMessage* m1 = new NdefMessage(); + m1->addTextRecord("We the People of the United States, in Order to form a more perfect Union..."); + + NdefMessage* m2 = new NdefMessage(); + + *m2 = *m1; + + NdefRecord r1 = m1->getRecord(0); + NdefRecord r2 = m2->getRecord(0); + + assertEqual(r1.getTnf(), r2.getTnf()); + assertEqual(r1.getTypeLength(), r2.getTypeLength()); + assertEqual(r1.getPayloadLength(), r2.getPayloadLength()); + assertEqual(r1.getIdLength(), r2.getIdLength()); + + byte p1[r1.getPayloadLength()]; + byte p2[r2.getPayloadLength()]; + r1.getPayload(p1); + r2.getPayload(p2); + + int size = r1.getPayloadLength(); + assertBytesEqual(p1, p2, size); + + delete m2; + delete m1; + } + + int end = freeMemory(); + assertEqual(0, (start-end)); +} + +test(assign2) +{ + int start = freeMemory(); + + if (true) // bogus block so automatic storage duration objects are deleted + { + NdefMessage m1 = NdefMessage(); + m1.addTextRecord("We the People of the United States, in Order to form a more perfect Union..."); + + NdefMessage m2 = NdefMessage(); + + m2 = m1; + + NdefRecord r1 = m1.getRecord(0); + NdefRecord r2 = m2.getRecord(0); + + assertEqual(r1.getTnf(), r2.getTnf()); + assertEqual(r1.getTypeLength(), r2.getTypeLength()); + assertEqual(r1.getPayloadLength(), r2.getPayloadLength()); + assertEqual(r1.getIdLength(), r2.getIdLength()); + + // TODO check type + + byte p1[r1.getPayloadLength()]; + byte p2[r2.getPayloadLength()]; + r1.getPayload(p1); + r2.getPayload(p2); + + int size = r1.getPayloadLength(); + assertBytesEqual(p1, p2, size); + } + + int end = freeMemory(); + assertEqual(0, (start-end)); +} + +test(assign3) +{ + int start = freeMemory(); + + if (true) // bogus block so automatic storage duration objects are deleted + { + + NdefMessage* m1 = new NdefMessage(); + m1->addTextRecord("We the People of the United States, in Order to form a more perfect Union..."); + + NdefMessage* m2 = new NdefMessage(); + + *m2 = *m1; + + delete m1; + + NdefRecord r = m2->getRecord(0); + + assertEqual(TNF_WELL_KNOWN, r.getTnf()); + assertEqual(1, r.getTypeLength()); + assertEqual(79, r.getPayloadLength()); + assertEqual(0, r.getIdLength()); + + ::String s = "We the People of the United States, in Order to form a more perfect Union..."; + byte payload[s.length() + 1]; + s.getBytes(payload, sizeof(payload)); + + byte p[r.getPayloadLength()]; + r.getPayload(p); + assertBytesEqual(payload, p+3, s.length()); + + delete m2; + } + + int end = freeMemory(); + assertEqual(0, (start-end)); +} + +test(assign4) +{ + int start = freeMemory(); + + if (true) // bogus block so automatic storage duration objects are deleted + { + + NdefMessage* m1 = new NdefMessage(); + m1->addTextRecord("We the People of the United States, in Order to form a more perfect Union..."); + + NdefMessage* m2 = new NdefMessage(); + m2->addTextRecord("Record 1"); + m2->addTextRecord("RECORD 2"); + m2->addTextRecord("Record 3"); + + assertEqual(3, m2->getRecordCount()); + *m2 = *m1; + assertEqual(1, m2->getRecordCount()); + +// NdefRecord ghost = m2->getRecord(1); +// ghost.print(); +// +// NdefRecord ghost2 = m2->getRecord(3); +// ghost2.print(); + +// +// delete m1; +// +// NdefRecord r = m2->getRecord(0); +// +// assertEqual(TNF_WELL_KNOWN, r.getTnf()); +// assertEqual(1, r.getTypeLength()); +// assertEqual(79, r.getPayloadLength()); +// assertEqual(0, r.getIdLength()); +// +// String s = "We the People of the United States, in Order to form a more perfect Union..."; +// byte payload[s.length() + 1]; +// s.getBytes(payload, sizeof(payload)); +// +// uint8_t* p = r.getPayload(); +// int size = r.getPayloadLength(); +// assertBytesEqual(payload, p+3, s.length()); +// free(p); + + delete m1; + delete m2; + } + + int end = freeMemory(); + assertEqual(0, (start-end)); +} + +// really a record test +test(doublePayload) +{ + int start = freeMemory(); + + NdefRecord* r = new NdefRecord(); + uint8_t p1[] = { 0x1, 0x2, 0x3, 0x4, 0x5, 0x6 }; + r->setPayload(p1, sizeof(p1)); + r->setPayload(p1, sizeof(p1)); + + delete r; + + int end = freeMemory(); + assertEqual(0, (start-end)); +} + +test(aaa_printFreeMemoryAtStart) // warning: relies on fact tests are run in alphabetical order +{ + Serial.println(F("---------------------")); + Serial.print("Free Memory Start ");Serial.println(freeMemory()); + Serial.println(F("---------------------")); +} + +test(zzz_printFreeMemoryAtEnd) // warning: relies on fact tests are run in alphabetical order +{ + // unfortunately the user needs to manually check this matches the start value + Serial.println(F("=====================")); + Serial.print("Free Memory End ");Serial.println(freeMemory()); + Serial.println(F("=====================")); +} + +void loop() { + Test::run(); +} \ No newline at end of file diff --git a/lib/NDEF/tests/NdefUnitTest/NdefUnitTest.ino b/lib/NDEF/tests/NdefUnitTest/NdefUnitTest.ino new file mode 100644 index 0000000..d1f8c93 --- /dev/null +++ b/lib/NDEF/tests/NdefUnitTest/NdefUnitTest.ino @@ -0,0 +1,173 @@ +#include +#include +#include +#include + +void assertBytesEqual(const uint8_t* expected, const uint8_t* actual, uint8_t size) { + for (int i = 0; i < size; i++) { + assertEqual(expected[i], actual[i]); + } +} + +void setup() { + Serial.begin(9600); +} + +test(accessors) { + NdefRecord record = NdefRecord(); + record.setTnf(TNF_WELL_KNOWN); + uint8_t recordType[] = { 0x54 }; // "T" Text Record + assertEqual(0x54, recordType[0]); + record.setType(recordType, sizeof(recordType)); + // 2 + "en" + "Unit Test" + uint8_t payload[] = { 0x02, 0x65, 0x6e, 0x55, 0x6e, 0x69, 0x74, 0x20, 0x54, 0x65, 0x73, 0x74 }; + record.setPayload(payload, sizeof(payload)); + uint8_t id[] = { 0x74, 0x65, 0x73, 0x74, 0x69, 0x64}; // testid + record.setId(id, sizeof(id)); + + assertEqual(TNF_WELL_KNOWN, record.getTnf()); + assertEqual(sizeof(recordType), record.getTypeLength()); + assertEqual(1, record.getTypeLength()); + assertEqual(sizeof(payload), record.getPayloadLength()); + assertEqual(12, record.getPayloadLength()); + assertEqual(sizeof(id), record.getIdLength()); + assertEqual(6, record.getIdLength()); + + uint8_t typeCheck[record.getTypeLength()]; + record.getType(typeCheck); + + assertEqual(0x54, typeCheck[0]); + assertBytesEqual(recordType, typeCheck, sizeof(recordType)); + + uint8_t payloadCheck[record.getPayloadLength()]; + record.getPayload(&payloadCheck[0]); + assertBytesEqual(payload, payloadCheck, sizeof(payload)); + + uint8_t idCheck[record.getIdLength()]; + record.getId(&idCheck[0]); + assertBytesEqual(id, idCheck, sizeof(id)); +} + +test(newaccessors) { + NdefRecord record = NdefRecord(); + record.setTnf(TNF_WELL_KNOWN); + uint8_t recordType[] = { 0x54 }; // "T" Text Record + assertEqual(0x54, recordType[0]); + record.setType(recordType, sizeof(recordType)); + // 2 + "en" + "Unit Test" + uint8_t payload[] = { 0x02, 0x65, 0x6e, 0x55, 0x6e, 0x69, 0x74, 0x20, 0x54, 0x65, 0x73, 0x74 }; + record.setPayload(payload, sizeof(payload)); + uint8_t id[] = { 0x74, 0x65, 0x73, 0x74, 0x69, 0x64}; // testid + record.setId(id, sizeof(id)); + + assertEqual(TNF_WELL_KNOWN, record.getTnf()); + assertEqual(sizeof(recordType), record.getTypeLength()); + assertEqual(1, record.getTypeLength()); + assertEqual(sizeof(payload), record.getPayloadLength()); + assertEqual(12, record.getPayloadLength()); + assertEqual(sizeof(id), record.getIdLength()); + assertEqual(6, record.getIdLength()); + + ::String typeCheck = record.getType(); + assertTrue(typeCheck.equals("T")); + + byte payloadCheck[record.getPayloadLength()]; + record.getPayload(payloadCheck); + assertBytesEqual(payload, payloadCheck, sizeof(payload)); + + byte idCheck[record.getIdLength()]; + record.getId(idCheck); + assertBytesEqual(id, idCheck, sizeof(id)); +} + +test(assignment) +{ + NdefRecord record = NdefRecord(); + record.setTnf(TNF_WELL_KNOWN); + uint8_t recordType[] = { 0x54 }; // "T" Text Record + assertEqual(0x54, recordType[0]); + record.setType(recordType, sizeof(recordType)); + // 2 + "en" + "Unit Test" + uint8_t payload[] = { 0x02, 0x65, 0x6e, 0x55, 0x6e, 0x69, 0x74, 0x20, 0x54, 0x65, 0x73, 0x74 }; + record.setPayload(payload, sizeof(payload)); + uint8_t id[] = { 0x74, 0x65, 0x73, 0x74, 0x69, 0x64}; // testid + record.setId(id, sizeof(id)); + + NdefRecord record2 = NdefRecord(); + record2 = record; + + assertEqual(TNF_WELL_KNOWN, record.getTnf()); + assertEqual(sizeof(recordType), record2.getTypeLength()); + assertEqual(sizeof(payload), record2.getPayloadLength()); + assertEqual(sizeof(id), record2.getIdLength()); + + ::String typeCheck = record.getType(); + assertTrue(typeCheck.equals("T")); + + byte payload2[record2.getPayloadLength()]; + record2.getPayload(payload2); + assertBytesEqual(payload, payload2, sizeof(payload)); + + byte id2[record.getIdLength()]; + record2.getId(id2); + assertBytesEqual(id, id2, sizeof(id)); +} + +test(getEmptyPayload) +{ + NdefRecord r = NdefRecord(); + assertEqual(TNF_EMPTY, r.getTnf()); + assertEqual(0, r.getPayloadLength()); + + byte payload[r.getPayloadLength()]; + r.getPayload(payload); + + byte id[r.getIdLength()]; + r.getId(id); + + byte empty[0]; + assertBytesEqual(empty, payload, sizeof(payload)); + assertBytesEqual(empty, id, sizeof(id)); +} + +test(encoding_without_record_id) { + NdefRecord record = NdefRecord(); + record.setTnf(TNF_WELL_KNOWN); + uint8_t recordType[] = { 0x54 }; // "T" Text Record + assertEqual(0x54, recordType[0]); + record.setType(recordType, sizeof(recordType)); + // 2 + "en" + "Unit Test" + uint8_t payload[] = { 0x02, 0x65, 0x6e, 0x55, 0x6e, 0x69, 0x74, 0x20, 0x54, 0x65, 0x73, 0x74 }; + record.setPayload(payload, sizeof(payload)); + + uint8_t encodedBytes[record.getEncodedSize()]; + record.encode(encodedBytes, true, true); + + uint8_t expectedBytes[] = { 209, 1, 12, 84, 2, 101, 110, 85, 110, 105, 116, 32, 84, 101, 115, 116 }; + assertBytesEqual(encodedBytes, expectedBytes, sizeof(encodedBytes)); +} + +// https://github.com/don/NDEF/issues/30 +test(encoding_with_record_id) { + NdefRecord record = NdefRecord(); + record.setTnf(TNF_WELL_KNOWN); + uint8_t recordType[] = { 0x54 }; // "T" Text Record + assertEqual(0x54, recordType[0]); + record.setType(recordType, sizeof(recordType)); + // 2 + "en" + "Unit Test" + uint8_t payload[] = { 0x02, 0x65, 0x6e, 0x55, 0x6e, 0x69, 0x74, 0x20, 0x54, 0x65, 0x73, 0x74 }; + record.setPayload(payload, sizeof(payload)); + // testid + uint8_t id[] = { 0x74, 0x65, 0x73, 0x74, 0x69, 0x64}; + record.setId(id, sizeof(id)); + + uint8_t encodedBytes[record.getEncodedSize()]; + record.encode(encodedBytes, true, true); + uint8_t expectedBytes[] = { 217, 1, 12, 6, 84, 116, 101, 115, 116, 105, 100, 2, 101, 110, 85, 110, 105, 116, 32, 84, 101, 115, 116 }; + + assertBytesEqual(encodedBytes, expectedBytes, sizeof(encodedBytes)); +} + +void loop() { + Test::run(); +} diff --git a/lib/NDEF/tests/NfcTagTest/NfcTagTest.ino b/lib/NDEF/tests/NfcTagTest/NfcTagTest.ino new file mode 100644 index 0000000..0820bfd --- /dev/null +++ b/lib/NDEF/tests/NfcTagTest/NfcTagTest.ino @@ -0,0 +1,37 @@ +#include +#include +#include +#include + +void setup() { + Serial.begin(9600); +} + +// Test for pull requests #14 and #16 +test(getUid) +{ + byte uid[4] = { 0x00, 0xFF, 0xAA, 0x17 }; + byte uidFromTag[sizeof(uid)]; + + NfcTag tag = NfcTag(uid, sizeof(uid)); + + assertEqual(sizeof(uid), tag.getUidLength()); + + tag.getUid(uidFromTag, sizeof(uidFromTag)); + + // make sure the 2 uids are the same + for (int i = 0; i < sizeof(uid); i++) { + assertEqual(uid[i], uidFromTag[i]); + } + + // check contents, to ensure the original uid wasn't overwritten + assertEqual(0x00, uid[0]); + assertEqual(0xFF, uid[1]); + assertEqual(0xAA, uid[2]); + assertEqual(0x17, uid[3]); +} + +void loop() { + Test::run(); +} + diff --git a/lib/PN532/.gitattributes b/lib/PN532/.gitattributes new file mode 100644 index 0000000..9a42e40 --- /dev/null +++ b/lib/PN532/.gitattributes @@ -0,0 +1,8 @@ +# From https://docs.github.com/en/get-started/getting-started-with-git/configuring-git-to-handle-line-endings +# Set the default behavior, in case people don't have core.autocrlf set. +* text=auto + +# Explicitly declare text files you want to always be normalized and converted +# to native line endings on checkout. +*.cpp text +*.h text \ No newline at end of file diff --git a/lib/PN532/.github/workflows/run-cl-arduino.yml b/lib/PN532/.github/workflows/run-cl-arduino.yml new file mode 100644 index 0000000..d56b3d5 --- /dev/null +++ b/lib/PN532/.github/workflows/run-cl-arduino.yml @@ -0,0 +1,54 @@ +name: Run Ci Arduino + +on: + push: + pull_request: + repository_dispatch: + types: [trigger-workflow] + +jobs: + ci-arduino: + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Checkout script repository + uses: actions/checkout@v4 + with: + repository: Seeed-Studio/ci-arduino + path: ci + + + - name: Setup arduino cli + uses: arduino/setup-arduino-cli@v2.0.0 + + - name: Create a depend.list file + run: | + # eg: echo "" >> depend.list + echo "OSSLibraries/Arduino_MFRC522v2" >> depend.list + echo "don/NDEF" >> depend.list + + + - name: Create a ignore.list file + run: | + # eg: echo "," >> ignore.list + + + + - name: Build sketch + run: ./ci/tools/compile.sh + + - name: Build result + run: | + cat build.log + if [ ${{ github.event_name }} == 'pull_request' ] && [ -f compile.failed ]; then + exit 1 + fi + + - name: Generate issue + if: ${{ github.event_name != 'pull_request' }} + run: ./ci/tools/issue.sh + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} \ No newline at end of file diff --git a/lib/PN532/.github/workflows/stale.yml b/lib/PN532/.github/workflows/stale.yml new file mode 100644 index 0000000..570033e --- /dev/null +++ b/lib/PN532/.github/workflows/stale.yml @@ -0,0 +1,25 @@ +name: 'Close stale issues and PRs' + +on: + workflow_dispatch: + schedule: + - cron: '0 4 * * *' + +jobs: + stale: + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Checkout script repository + uses: actions/checkout@v4 + with: + repository: Seeed-Studio/sync-github-all-issues + path: ci + + - name: Run script + run: ./ci/tools/stale.sh + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/lib/PN532/.github/workflows/sync_issues.yml b/lib/PN532/.github/workflows/sync_issues.yml new file mode 100644 index 0000000..dd58e00 --- /dev/null +++ b/lib/PN532/.github/workflows/sync_issues.yml @@ -0,0 +1,24 @@ +name: Automate Issue Management + +on: + issues: + types: + - opened + - edited + - assigned + - unassigned + - labeled + - unlabeled + - reopened + +jobs: + add_issue_to_project: + runs-on: ubuntu-latest + steps: + - name: Add issue to GitHub Project + uses: actions/add-to-project@v1.0.2 + with: + project-url: https://github.com/orgs/Seeed-Studio/projects/17 + github-token: ${{ secrets.ISSUE_ASSEMBLE }} + labeled: bug + label-operator: NOT diff --git a/lib/PN532/.gitignore b/lib/PN532/.gitignore new file mode 100644 index 0000000..b46bd5d --- /dev/null +++ b/lib/PN532/.gitignore @@ -0,0 +1,45 @@ +# Created by https://www.toptal.com/developers/gitignore/api/c++,platformio +# Edit at https://www.toptal.com/developers/gitignore?templates=c++,platformio + +### C++ ### +# Prerequisites +*.d + +# Compiled Object files +*.slo +*.lo +*.o +*.obj + +# Precompiled Headers +*.gch +*.pch + +# Compiled Dynamic libraries +*.so +*.dylib +*.dll + +# Fortran module files +*.mod +*.smod + +# Compiled Static libraries +*.lai +*.la +*.a +*.lib + +# Executables +*.exe +*.out +*.app + +### PlatformIO ### +.pioenvs +.piolibdeps +.clang_complete +.gcc-flags.json +.pio + +# End of https://www.toptal.com/developers/gitignore/api/c++,platformio \ No newline at end of file diff --git a/lib/PN532/LICENSE.md b/lib/PN532/LICENSE.md new file mode 100644 index 0000000..252aaff --- /dev/null +++ b/lib/PN532/LICENSE.md @@ -0,0 +1,29 @@ +BSD 3-Clause License + +Copyright (c) 2012, Adafruit Industries +Copyright (c) 2013, Seeed Technology Inc. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/lib/PN532/README.md b/lib/PN532/README.md new file mode 100644 index 0000000..d0ca09f --- /dev/null +++ b/lib/PN532/README.md @@ -0,0 +1,145 @@ +# NFC library + +This is an library for PN532 to use NFC technology. +It is for [NFC Shield](https://www.seeedstudio.com/NFC-Shield-V2-0.html) and [Grove - NFC](https://www.seeedstudio.com/Grove-NFC.html). + +[![NFC Shield](https://statics3.seeedstudio.com/images/113030001%201.jpg)](https://www.seeedstudio.com/NFC-Shield-V2-0.html) +[![Grove - NFC](https://statics3.seeedstudio.com/images/product/grove%20nfc.jpg)](https://www.seeedstudio.com/Grove-NFC.html) + +## Features + +- Support all interfaces of PN532 (I2C, SPI, HSU) +- Read/write Mifare Classic Card +- Works with [Don's NDEF Library](https://github.com/don/NDEF) +- Communicate with android 4.0+([Lists of devices supported](https://github.com/Seeed-Studio/PN532/wiki/List-of-devices-supported)) +- Support [mbed platform](https://os.mbed.com/teams/Seeed/code/PN532/) +- Card emulation (NFC Type 4 tag) + +## To Do + +- To support more than one INFO PDU of P2P communication +- To read/write NFC Type 4 tag + +## Getting Started + +### Using Arduino IDE + +1. Download [zip file](https://github.com/Seeed-Studio/PN532/archive/refs/heads/arduino.zip), extract it into Arduino's libraries and rename it to PN532-Arduino. +2. Download [Don's NDEF library](https://github.com/don/NDEF/archive/refs/heads/master.zip), extract it into Arduino's libraries and rename it to NDEF. +3. Add the `NFC_INTERFACE_` build flag to your build system or define it in your code using `#define NFC_INTERFACE_` like + + ```cpp + #define NFC_INTERFACE_I2C + ``` + +4. Follow the examples of the two libraries. + +### PlatformIO library + +Add `https://github.com/Seeed-Studio/PN532.git` to your `lib_deps` variable in `platformio.ini` like so. This library will automatically include Don's NDEF library as well. + +``` +lib_deps = + https://github.com/Seeed-Studio/PN532.git +``` + +> ⚠️ Besides using the correct `PN532_.h` include file, you have to add `-DNFC_INTERFACE_` to `build_flags` to select what interface you want to use. This is done to prevent requiring unnecessary dependencies on e.g. `SoftwareSerial` or `SPI` when you are not using those interfaces. + +``` +build_flags = + -DNFC_INTERFACE_HSU +``` + +### Git way for Linux/Mac (recommended) + +1. Get PN532 library and NDEF library + + cd {Arduino}\libraries + git clone --recursive https://github.com/Seeed-Studio/PN532.git NFC + git clone --recursive https://github.com/don/NDEF.git NDEF + ln -s NFC/PN532 ./ + ln -s NDEF/NDEF ./ + +1. Add the `NFC_INTERFACE_` build flag to your build system or define it in your code using `#define NFC_INTERFACE_` like + + ```cpp + #define NFC_INTERFACE_I2C + ``` + +1. Follow the examples of the two libraries + +## Interfaces + +This library offers four ways to interface with the PN532 board: + +- `HSU` (High Speed Uart) +- `I2C` +- `SPI` +- `SWHSU` (Software-based High Speed Uart) + +Read the section for the interface you want to use. + +> Make sure to add the `PN532_.h` include file and the `NFC_INTERFACE_` define to your code like the example below: + +```cpp +#define NFC_INTERFACE_HSU + +#include +#include +``` + +## HSU Interface + +HSU is short for High Speed Uart. HSU interface needs only 4 wires to connect PN532 with Arduino, [Sensor Shield](http://goo.gl/i0EQgd) can make it more easier. For some Arduino boards like [Leonardo][leonardo], [DUE][due], [Mega][mega] ect, there are more than one `Serial` on these boards, so we can use this additional Serial to control PN532, HSU uses 115200 baud rate. + +To use the `Serial1` control PN532, refer to the code below. + +```c++ +#define NFC_INTERFACE_HSU + +#include +#include + +PN532_HSU pn532hsu(Serial1); +PN532 nfc(pn532hsu); + +void setup(void) +{ + nfc.begin(); + //... +} +``` + +If your Arduino has only one serial interface and you want to keep it for control or debugging with the Serial Monitor, you can use the [`SoftwareSerial`][softwareserial] library to control the PN532 by emulating a serial interface. Include `PN532_SWHSU.h` instead of `PN532_HSU.h`: + +```c++ +#define NFC_INTERFACE_SWHSU + +#include +#include +#include + +SoftwareSerial SWSerial( 10, 11 ); // RX, TX + +PN532_SWHSU pn532swhsu( SWSerial ); +PN532 nfc( pn532swhsu ); + +void setup(void) +{ + nfc.begin(); + //... +} +``` + +## Attribution + +This library is based on [Adafruit_NFCShield_I2C](https://github.com/adafruit/Adafruit_NFCShield_I2C). +[Seeed Studio](hhttps://www.seeedstudio.com/) rewrite the library to make it easy to support different interfaces and platforms. +[@Don](https://github.com/don) writes the [NDEF library](https://github.com/don/NDEF) to make it more easy to use. +[@JiapengLi](https://github.com/JiapengLi) adds HSU interface. +[@awieser](https://github.com/awieser) adds card emulation function. + +[mega]: http://arduino.cc/en/Main/arduinoBoardMega +[due]: http://arduino.cc/en/Main/arduinoBoardDue +[leonardo]: http://arduino.cc/en/Main/arduinoBoardLeonardo +[softwareserial]: https://www.arduino.cc/en/Reference/softwareSerial diff --git a/lib/PN532/examples/FeliCa_card_detection/FeliCa_card_detection.pde b/lib/PN532/examples/FeliCa_card_detection/FeliCa_card_detection.pde new file mode 100644 index 0000000..72e0185 --- /dev/null +++ b/lib/PN532/examples/FeliCa_card_detection/FeliCa_card_detection.pde @@ -0,0 +1,121 @@ +/**************************************************************************/ +/*! + This example will attempt to connect to an FeliCa + card or tag and retrieve some basic information about it + that can be used to determine what type of card it is. + + Note that you need the baud rate to be 115200 because we need to print + out the data and read from the card at the same time! + + To enable debug message, define DEBUG in PN532/PN532_debug.h + + */ +/**************************************************************************/ +#include + +#if 1 + #define NFC_INTERFACE_SPI + #include + #include + #include + #include + +PN532_SPI pn532spi(SPI, 10); +PN532 nfc(pn532spi); +#elif 0 + #define NFC_INTERFACE_HSU + #include + #include + #include + +PN532_HSU pn532hsu(Serial1); +PN532 nfc(pn532hsu); +#else + #define NFC_INTERFACE_I2C + #include + #include + #include + #include + +PN532_I2C pn532i2c(Wire); +PN532 nfc(pn532i2c); +#endif + +#include + +uint8_t _prevIDm[8]; +unsigned long _prevTime; + +void setup(void) +{ + Serial.begin(115200); + Serial.println("Hello!"); + + nfc.begin(); + + uint32_t versiondata = nfc.getFirmwareVersion(); + if (!versiondata) + { + Serial.print("Didn't find PN53x board"); + while (1) {delay(10);}; // halt + } + + // Got ok data, print it out! + Serial.print("Found chip PN5"); Serial.println((versiondata >> 24) & 0xFF, HEX); + Serial.print("Firmware ver. "); Serial.print((versiondata >> 16) & 0xFF, DEC); + Serial.print('.'); Serial.println((versiondata >> 8) & 0xFF, DEC); + + // Set the max number of retry attempts to read from a card + // This prevents us from waiting forever for a card, which is + // the default behaviour of the PN532. + nfc.setPassiveActivationRetries(0xFF); + nfc.SAMConfig(); + + memset(_prevIDm, 0, 8); +} + +void loop(void) +{ + uint8_t ret; + uint16_t systemCode = 0xFFFF; + uint8_t requestCode = 0x01; // System Code request + uint8_t idm[8]; + uint8_t pmm[8]; + uint16_t systemCodeResponse; + + // Wait for an FeliCa type cards. + // When one is found, some basic information such as IDm, PMm, and System Code are retrieved. + Serial.print("Waiting for an FeliCa card... "); + ret = nfc.felica_Polling(systemCode, requestCode, idm, pmm, &systemCodeResponse, 5000); + + if (ret != 1) + { + Serial.println("Could not find a card"); + delay(500); + return; + } + + if ( memcmp(idm, _prevIDm, 8) == 0 ) { + if ( (millis() - _prevTime) < 3000 ) { + Serial.println("Same card"); + delay(500); + return; + } + } + + Serial.println("Found a card!"); + Serial.print(" IDm: "); + nfc.PrintHex(idm, 8); + Serial.print(" PMm: "); + nfc.PrintHex(pmm, 8); + Serial.print(" System Code: "); + Serial.print(systemCodeResponse, HEX); + Serial.print("\n"); + + memcpy(_prevIDm, idm, 8); + _prevTime = millis(); + + // Wait 1 second before continuing + Serial.println("Card access completed!\n"); + delay(1000); +} diff --git a/lib/PN532/examples/FeliCa_card_read/FeliCa_card_read.pde b/lib/PN532/examples/FeliCa_card_read/FeliCa_card_read.pde new file mode 100644 index 0000000..d878f9c --- /dev/null +++ b/lib/PN532/examples/FeliCa_card_read/FeliCa_card_read.pde @@ -0,0 +1,171 @@ +/**************************************************************************/ +/*! + This example will attempt to connect to an FeliCa + card or tag and retrieve some basic information about it + that can be used to determine what type of card it is. + + Note that you need the baud rate to be 115200 because we need to print + out the data and read from the card at the same time! + + To enable debug message, define DEBUG in PN532/PN532_debug.h + + */ +/**************************************************************************/ +#include + +#if 1 + #define NFC_INTERFACE_SPI + #include + #include + #include + #include + +PN532_SPI pn532spi(SPI, 10); +PN532 nfc(pn532spi); +#elif 0 + #define NFC_INTERFACE_HSU + #include + #include + #include + +PN532_HSU pn532hsu(Serial1); +PN532 nfc(pn532hsu); +#else + #define NFC_INTERFACE_I2C + #include + #include + #include + #include + +PN532_I2C pn532i2c(Wire); +PN532 nfc(pn532i2c); +#endif + +#include + +uint8_t _prevIDm[8]; +unsigned long _prevTime; + +void PrintHex8(const uint8_t d) { + Serial.print(" "); + Serial.print( (d >> 4) & 0x0F, HEX); + Serial.print( d & 0x0F, HEX); +} + +void setup(void) +{ + Serial.begin(115200); + Serial.println("Hello!"); + + nfc.begin(); + + uint32_t versiondata = nfc.getFirmwareVersion(); + if (!versiondata) + { + Serial.print("Didn't find PN53x board"); + while (1) {delay(10);}; // halt + } + + // Got ok data, print it out! + Serial.print("Found chip PN5"); Serial.println((versiondata >> 24) & 0xFF, HEX); + Serial.print("Firmware ver. "); Serial.print((versiondata >> 16) & 0xFF, DEC); + Serial.print('.'); Serial.println((versiondata >> 8) & 0xFF, DEC); + + // Set the max number of retry attempts to read from a card + // This prevents us from waiting forever for a card, which is + // the default behaviour of the PN532. + nfc.setPassiveActivationRetries(0xFF); + nfc.SAMConfig(); + + memset(_prevIDm, 0, 8); +} + +void loop(void) +{ + uint8_t ret; + uint16_t systemCode = 0xFFFF; + uint8_t requestCode = 0x01; // System Code request + uint8_t idm[8]; + uint8_t pmm[8]; + uint16_t systemCodeResponse; + + // Wait for an FeliCa type cards. + // When one is found, some basic information such as IDm, PMm, and System Code are retrieved. + Serial.print("Waiting for an FeliCa card... "); + ret = nfc.felica_Polling(systemCode, requestCode, idm, pmm, &systemCodeResponse, 5000); + + if (ret != 1) + { + Serial.println("Could not find a card"); + delay(500); + return; + } + + if ( memcmp(idm, _prevIDm, 8) == 0 ) { + if ( (millis() - _prevTime) < 3000 ) { + Serial.println("Same card"); + delay(500); + return; + } + } + + Serial.println("Found a card!"); + Serial.print(" IDm: "); + nfc.PrintHex(idm, 8); + Serial.print(" PMm: "); + nfc.PrintHex(pmm, 8); + Serial.print(" System Code: "); + Serial.print(systemCodeResponse, HEX); + Serial.print("\n"); + + memcpy(_prevIDm, idm, 8); + _prevTime = millis(); + + uint8_t blockData[3][16]; + uint16_t serviceCodeList[1]; + uint16_t blockList[3]; + + Serial.println("Write Without Encryption command "); + serviceCodeList[0] = 0x0009; + blockList[0] = 0x8000; + unsigned long now = millis(); + blockData[0][3] = now & 0xFF; + blockData[0][2] = (now >>= 8) & 0xFF; + blockData[0][1] = (now >>= 8) & 0xFF; + blockData[0][0] = (now >>= 8) & 0xFF; + Serial.print(" Writing current millis ("); + PrintHex8(blockData[0][0]); + PrintHex8(blockData[0][1]); + PrintHex8(blockData[0][2]); + PrintHex8(blockData[0][3]); + Serial.print(" ) to Block 0 -> "); + ret = nfc.felica_WriteWithoutEncryption(1, serviceCodeList, 1, blockList, blockData); + if (ret != 1) + { + Serial.println("error"); + } else { + Serial.println("OK!"); + } + memset(blockData[0], 0, 16); + + Serial.print("Read Without Encryption command -> "); + serviceCodeList[0] = 0x000B; + blockList[0] = 0x8000; + blockList[1] = 0x8001; + blockList[2] = 0x8002; + ret = nfc.felica_ReadWithoutEncryption(1, serviceCodeList, 3, blockList, blockData); + if (ret != 1) + { + Serial.println("error"); + } else { + Serial.println("OK!"); + for(int i=0; i<3; i++ ) { + Serial.print(" Block no. "); Serial.print(i, DEC); Serial.print(": "); + nfc.PrintHex(blockData[i], 16); + } + } + + // Wait 1 second before continuing + Serial.println("Card access completed!\n"); + delay(1000); +} diff --git a/lib/PN532/examples/android_hce/android_hce.ino b/lib/PN532/examples/android_hce/android_hce.ino new file mode 100644 index 0000000..958bbb7 --- /dev/null +++ b/lib/PN532/examples/android_hce/android_hce.ino @@ -0,0 +1,178 @@ +#if 0 +#define NFC_INTERFACE_SPI +#include +#include +#include +#include "PN532.h" + + PN532_SPI pn532spi(SPI, 10); + PN532 nfc(pn532spi); +#elif 1 +#define NFC_INTERFACE_HSU +#include +#include +#include + +PN532_HSU pn532hsu(Serial1); +PN532 nfc(pn532hsu); +#else +#define NFC_INTERFACE_I2C +#include +#include +#include +#include +#endif + +#ifdef USE_TINYUSB +#include +#endif + + +void setup() +{ + Serial.begin(115200); + Serial.println("-------Peer to Peer HCE--------"); + + nfc.begin(); + + uint32_t versiondata = nfc.getFirmwareVersion(); + if (!versiondata) + { + Serial.print("Didn't find PN53x board"); + while (1) + ; // halt + } + + // Got ok data, print it out! + Serial.print("Found chip PN5"); + Serial.println((versiondata >> 24) & 0xFF, HEX); + Serial.print("Firmware ver. "); + Serial.print((versiondata >> 16) & 0xFF, DEC); + Serial.print('.'); + Serial.println((versiondata >> 8) & 0xFF, DEC); + + // Set the max number of retry attempts to read from a card + // This prevents us from waiting forever for a card, which is + // the default behaviour of the PN532. + //nfc.setPassiveActivationRetries(0xFF); + + // configure board to read RFID tags + nfc.SAMConfig(); +} + +void loop() +{ + bool success; + + uint8_t responseLength = 32; + + Serial.println("Waiting for an ISO14443A card"); + + // set shield to inListPassiveTarget + success = nfc.inListPassiveTarget(); + + if (success) + { + + Serial.println("Found something!"); + + uint8_t selectApdu[] = {0x00, /* CLA */ + 0xA4, /* INS */ + 0x04, /* P1 */ + 0x00, /* P2 */ + 0x07, /* Length of AID */ + 0xF0, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, /* AID defined on Android App */ + 0x00 /* Le */}; + + uint8_t response[32]; + + success = nfc.inDataExchange(selectApdu, sizeof(selectApdu), response, &responseLength); + + if (success) + { + + Serial.print("responseLength: "); + Serial.println(responseLength); + + nfc.PrintHexChar(response, responseLength); + + do + { + uint8_t apdu[] = "Hello from Arduino"; + uint8_t back[32]; + uint8_t length = 32; + + success = nfc.inDataExchange(apdu, sizeof(apdu), back, &length); + + if (success) + { + + Serial.print("responseLength: "); + Serial.println(length); + + nfc.PrintHexChar(back, length); + } + else + { + + Serial.println("Broken connection?"); + } + } while (success); + } + else + { + + Serial.println("Failed sending SELECT AID"); + } + } + else + { + + Serial.println("Didn't find anything!"); + } + + delay(1000); +} + +void printResponse(uint8_t *response, uint8_t responseLength) +{ + + String respBuffer; + + for (int i = 0; i < responseLength; i++) + { + + if (response[i] < 0x10) + respBuffer = respBuffer + "0"; //Adds leading zeros if hex value is smaller than 0x10 + + respBuffer = respBuffer + String(response[i], HEX) + " "; + } + + Serial.print("response: "); + Serial.println(respBuffer); +} + +void setupNFC() +{ + + nfc.begin(); + + uint32_t versiondata = nfc.getFirmwareVersion(); + if (!versiondata) + { + Serial.print("Didn't find PN53x board"); + while (1) + ; // halt + } + + // Got ok data, print it out! + Serial.print("Found chip PN5"); + Serial.println((versiondata >> 24) & 0xFF, HEX); + Serial.print("Firmware ver. "); + Serial.print((versiondata >> 16) & 0xFF, DEC); + Serial.print('.'); + Serial.println((versiondata >> 8) & 0xFF, DEC); + + // configure board to read RFID tags + nfc.SAMConfig(); +} diff --git a/lib/PN532/examples/emulate_tag_ndef/emulate_tag_ndef.ino b/lib/PN532/examples/emulate_tag_ndef/emulate_tag_ndef.ino new file mode 100644 index 0000000..e53f1c4 --- /dev/null +++ b/lib/PN532/examples/emulate_tag_ndef/emulate_tag_ndef.ino @@ -0,0 +1,93 @@ +#include "emulatetag.h" +#include "NdefMessage.h" + +#ifdef NRF52840_XXAA +#ifdef USE_TINYUSB +#include +#endif +#endif + +#if 0 +#define NFC_INTERFACE_SPI +#include +#include +#include +#include "PN532.h" + + PN532_SPI pn532spi(SPI, 10); + EmulateTag nfc(pn532spi); +#elif 1 +#define NFC_INTERFACE_HSU +#include +#include +#include + +PN532_HSU pn532hsu(Serial1); +EmulateTag nfc(pn532hsu); +#endif + +uint8_t ndefBuf[120]; +NdefMessage message; +int messageSize; + +uint8_t uid[3] = {0x12, 0x34, 0x56}; + +void setup() +{ + Serial.begin(115200); + Serial.println("------- Emulate Tag --------"); + + message = NdefMessage(); + message.addUriRecord("http://www.seeedstudio.com"); + messageSize = message.getEncodedSize(); + if (messageSize > sizeof(ndefBuf)) + { + Serial.println("ndefBuf is too small"); + while (1) + { + } + } + + Serial.print("Ndef encoded message size: "); + Serial.println(messageSize); + + message.encode(ndefBuf); + + // comment out this command for no ndef message + nfc.setNdefFile(ndefBuf, messageSize); + + // uid must be 3 bytes! + nfc.setUid(uid); + + nfc.init(); +} + +void loop() +{ + // uncomment for overriding ndef in case a write to this tag occured + //nfc.setNdefFile(ndefBuf, messageSize); + + // start emulation (blocks) + nfc.emulate(); + + // or start emulation with timeout + /*if(!nfc.emulate(1000)){ // timeout 1 second + Serial.println("timed out"); + }*/ + + // deny writing to the tag + // nfc.setTagWriteable(false); + + if (nfc.writeOccured()) + { + Serial.println("\nWrite occured !"); + uint8_t *tag_buf; + uint16_t length; + + nfc.getContent(&tag_buf, &length); + NdefMessage msg = NdefMessage(tag_buf, length); + // msg.print(); + } + + delay(1000); +} diff --git a/lib/PN532/examples/iso14443a_uid/iso14443a_uid.pde b/lib/PN532/examples/iso14443a_uid/iso14443a_uid.pde new file mode 100644 index 0000000..0cfaafd --- /dev/null +++ b/lib/PN532/examples/iso14443a_uid/iso14443a_uid.pde @@ -0,0 +1,104 @@ +/**************************************************************************/ +/*! + This example will attempt to connect to an ISO14443A + card or tag and retrieve some basic information about it + that can be used to determine what type of card it is. + + Note that you need the baud rate to be 115200 because we need to print + out the data and read from the card at the same time! + + To enable debug message, define DEBUG in PN532/PN532_debug.h + +*/ +/**************************************************************************/ + + +/* When the number after #if set as 1, it will be switch to SPI Mode*/ +#if 0 + #define NFC_INTERFACE_SPI + #include + #include + #include + #include "PN532.h" + + PN532_SPI pn532spi(SPI, 10); + PN532 nfc(pn532spi); + +/* When the number after #elif set as 1, it will be switch to HSU Mode*/ +#elif 0 + #define NFC_INTERFACE_HSU + #include + #include + #include + + PN532_HSU pn532hsu(Serial1); + PN532 nfc(pn532hsu); + +/* When the number after #if & #elif set as 0, it will be switch to I2C Mode*/ +#else + #define NFC_INTERFACE_I2C + #include + #include + #include + #include + + PN532_I2C pn532i2c(Wire); + PN532 nfc(pn532i2c); +#endif + +void setup(void) { + Serial.begin(115200); + Serial.println("Hello!"); + + nfc.begin(); + + uint32_t versiondata = nfc.getFirmwareVersion(); + if (! versiondata) { + Serial.print("Didn't find PN53x board"); + while (1); // halt + } + + // Got ok data, print it out! + Serial.print("Found chip PN5"); Serial.println((versiondata>>24) & 0xFF, HEX); + Serial.print("Firmware ver. "); Serial.print((versiondata>>16) & 0xFF, DEC); + Serial.print('.'); Serial.println((versiondata>>8) & 0xFF, DEC); + + // Set the max number of retry attempts to read from a card + // This prevents us from waiting forever for a card, which is + // the default behaviour of the PN532. + nfc.setPassiveActivationRetries(0xFF); + + // configure board to read RFID tags + nfc.SAMConfig(); + + Serial.println("Waiting for an ISO14443A card"); +} + +void loop(void) { + boolean success; + uint8_t uid[] = { 0, 0, 0, 0, 0, 0, 0 }; // Buffer to store the returned UID + uint8_t uidLength; // Length of the UID (4 or 7 bytes depending on ISO14443A card type) + + // Wait for an ISO14443A type cards (Mifare, etc.). When one is found + // 'uid' will be populated with the UID, and uidLength will indicate + // if the uid is 4 bytes (Mifare Classic) or 7 bytes (Mifare Ultralight) + success = nfc.readPassiveTargetID(PN532_MIFARE_ISO14443A, &uid[0], &uidLength); + + if (success) { + Serial.println("Found a card!"); + Serial.print("UID Length: ");Serial.print(uidLength, DEC);Serial.println(" bytes"); + Serial.print("UID Value: "); + for (uint8_t i=0; i < uidLength; i++) + { + Serial.print(" 0x");Serial.print(uid[i], HEX); + } + Serial.println(""); + // Wait 1 second before continuing + delay(1000); + } + else + { + // PN532 probably timed out waiting for a card + Serial.println("Timed out waiting for a card"); + } +} diff --git a/lib/PN532/examples/mifareclassic_formatndef/mifareclassic_formatndef.pde b/lib/PN532/examples/mifareclassic_formatndef/mifareclassic_formatndef.pde new file mode 100644 index 0000000..5c8b956 --- /dev/null +++ b/lib/PN532/examples/mifareclassic_formatndef/mifareclassic_formatndef.pde @@ -0,0 +1,192 @@ +/**************************************************************************/ +/*! + This example attempts to format a clean Mifare Classic 1K card as + an NFC Forum tag (to store NDEF messages that can be read by any + NFC enabled Android phone, etc.) + + Note that you need the baud rate to be 115200 because we need to print + out the data and read from the card at the same time! + + To enable debug message, define DEBUG in PN532/PN532_debug.h +*/ +/**************************************************************************/ + +#if 0 + #define NFC_INTERFACE_SPI + #include + #include + #include + #include "PN532.h" + + PN532_SPI pn532spi(SPI, 10); + PN532 nfc(pn532spi); +#elif 1 + #define NFC_INTERFACE_HSU + #include + #include + #include + + PN532_HSU pn532hsu(Serial1); + PN532 nfc(pn532hsu); +#else + #define NFC_INTERFACE_I2C + #include + #include + #include + #include +#endif + +#ifdef USE_TINYUSB +#include +#endif + +/* + We can encode many different kinds of pointers to the card, + from a URL, to an Email address, to a phone number, and many more + check the library header .h file to see the large # of supported + prefixes! +*/ +// For a http://www. url: +const char * url = "elechouse.com"; +uint8_t ndefprefix = NDEF_URIPREFIX_HTTP_WWWDOT; + +// for an email address +//const char * url = "mail@example.com"; +//uint8_t ndefprefix = NDEF_URIPREFIX_MAILTO; + +// for a phone number +//const char * url = "+1 212 555 1212"; +//uint8_t ndefprefix = NDEF_URIPREFIX_TEL; + + +void setup(void) { + Serial.begin(115200); + Serial.println("Looking for PN532..."); + + nfc.begin(); + + uint32_t versiondata = nfc.getFirmwareVersion(); + if (! versiondata) { + Serial.print("Didn't find PN53x board"); + while (1); // halt + } + + // Got ok data, print it out! + Serial.print("Found chip PN5"); Serial.println((versiondata>>24) & 0xFF, HEX); + Serial.print("Firmware ver. "); Serial.print((versiondata>>16) & 0xFF, DEC); + Serial.print('.'); Serial.println((versiondata>>8) & 0xFF, DEC); + + // configure board to read RFID tags + nfc.SAMConfig(); +} + +void loop(void) { + uint8_t success; // Flag to check if there was an error with the PN532 + uint8_t uid[] = { 0, 0, 0, 0, 0, 0, 0 }; // Buffer to store the returned UID + uint8_t uidLength; // Length of the UID (4 or 7 bytes depending on ISO14443A card type) + bool authenticated = false; // Flag to indicate if the sector is authenticated + + // Use the default key + uint8_t keya[6] = { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }; + + Serial.println(""); + Serial.println("PLEASE NOTE: Formatting your card for NDEF records will change the"); + Serial.println("authentication keys. To reformat your NDEF tag as a clean Mifare"); + Serial.println("Classic tag, use the mifareclassic_ndeftoclassic example!"); + Serial.println(""); + Serial.println("Place your Mifare Classic card on the reader to format with NDEF"); + Serial.println("and press any key to continue ..."); + // Wait for user input before proceeding + while (!Serial.available()); + // a key was pressed1 + while (Serial.available()) Serial.read(); + + // Wait for an ISO14443A type card (Mifare, etc.). When one is found + // 'uid' will be populated with the UID, and uidLength will indicate + // if the uid is 4 bytes (Mifare Classic) or 7 bytes (Mifare Ultralight) + success = nfc.readPassiveTargetID(PN532_MIFARE_ISO14443A, uid, &uidLength); + + if (success) + { + // Display some basic information about the card + Serial.println("Found an ISO14443A card"); + Serial.print(" UID Length: ");Serial.print(uidLength, DEC);Serial.println(" bytes"); + Serial.print(" UID Value: "); + nfc.PrintHex(uid, uidLength); + for (uint8_t i = 0; i < uidLength; i++) { + Serial.print(uid[i], HEX); + Serial.print(' '); + } + Serial.println(""); + + // Make sure this is a Mifare Classic card + if (uidLength != 4) + { + Serial.println("Ooops ... this doesn't seem to be a Mifare Classic card!"); + return; + } + + // We probably have a Mifare Classic card ... + Serial.println("Seems to be a Mifare Classic card (4 byte UID)"); + + // Try to format the card for NDEF data + success = nfc.mifareclassic_AuthenticateBlock (uid, uidLength, 0, 0, keya); + if (!success) + { + Serial.println("Unable to authenticate block 0 to enable card formatting!"); + return; + } + success = nfc.mifareclassic_FormatNDEF(); + if (!success) + { + Serial.println("Unable to format the card for NDEF"); + return; + } + + Serial.println("Card has been formatted for NDEF data using MAD1"); + + // Try to authenticate block 4 (first block of sector 1) using our key + success = nfc.mifareclassic_AuthenticateBlock (uid, uidLength, 4, 0, keya); + + // Make sure the authentification process didn't fail + if (!success) + { + Serial.println("Authentication failed."); + return; + } + + // Try to write a URL + Serial.println("Writing URI to sector 1 as an NDEF Message"); + + // Authenticated seems to have worked + // Try to write an NDEF record to sector 1 + // Use 0x01 for the URI Identifier Code to prepend "http://www." + // to the url (and save some space). For information on URI ID Codes + // see http://www.ladyada.net/wiki/private/articlestaging/nfc/ndef + if (strlen(url) > 38) + { + // The length is also checked in the WriteNDEFURI function, but lets + // warn users here just in case they change the value and it's bigger + // than it should be + Serial.println("URI is too long ... must be less than 38 characters long"); + return; + } + + // URI is within size limits ... write it to the card and report success/failure + success = nfc.mifareclassic_WriteNDEFURI(1, ndefprefix, url); + if (success) + { + Serial.println("NDEF URI Record written to sector 1"); + } + else + { + Serial.println("NDEF Record creation failed! :("); + } + } + + // Wait a bit before trying again + Serial.println("\n\nDone!"); + delay(1000); + Serial.flush(); + while(Serial.available()) Serial.read(); +} \ No newline at end of file diff --git a/lib/PN532/examples/mifareclassic_memdump/mifareclassic_memdump.pde b/lib/PN532/examples/mifareclassic_memdump/mifareclassic_memdump.pde new file mode 100644 index 0000000..5a7da51 --- /dev/null +++ b/lib/PN532/examples/mifareclassic_memdump/mifareclassic_memdump.pde @@ -0,0 +1,183 @@ +/**************************************************************************/ +/*! + This example attempts to dump the contents of a Mifare Classic 1K card + + Note that you need the baud rate to be 115200 because we need to print + out the data and read from the card at the same time! + + To enable debug message, define DEBUG in PN532/PN532_debug.h +*/ +/**************************************************************************/ + +#if 0 + #define NFC_INTERFACE_SPI + #include + #include + #include + #include "PN532.h" + + PN532_SPI pn532spi(SPI, 10); + PN532 nfc(pn532spi); +#elif 1 + #define NFC_INTERFACE_HSU + #include + #include + #include + + PN532_HSU pn532hsu(Serial1); + PN532 nfc(pn532hsu); +#else + #define NFC_INTERFACE_I2C + #include + #include + #include + #include + + PN532_I2C pn532i2c(Wire); + PN532 nfc(pn532i2c); +#endif + +#ifdef USE_TINYUSB +#include +#endif + + +void setup(void) { + // has to be fast to dump the entire memory contents! + Serial.begin(115200); + Serial.println("Looking for PN532..."); + + nfc.begin(); + + uint32_t versiondata = nfc.getFirmwareVersion(); + if (! versiondata) { + Serial.print("Didn't find PN53x board"); + while (1); // halt + } + // Got ok data, print it out! + Serial.print("Found chip PN5"); Serial.println((versiondata>>24) & 0xFF, HEX); + Serial.print("Firmware ver. "); Serial.print((versiondata>>16) & 0xFF, DEC); + Serial.print('.'); Serial.println((versiondata>>8) & 0xFF, DEC); + + // configure board to read RFID tags + nfc.SAMConfig(); + + Serial.println("Waiting for an ISO14443A Card ..."); +} + + +void loop(void) { + uint8_t success; // Flag to check if there was an error with the PN532 + uint8_t uid[] = { 0, 0, 0, 0, 0, 0, 0 }; // Buffer to store the returned UID + uint8_t uidLength; // Length of the UID (4 or 7 bytes depending on ISO14443A card type) + uint8_t currentblock; // Counter to keep track of which block we're on + bool authenticated = false; // Flag to indicate if the sector is authenticated + uint8_t data[16]; // Array to store block data during reads + + // Keyb on NDEF and Mifare Classic should be the same + uint8_t keyuniversal[6] = { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }; + + // Wait for an ISO14443A type cards (Mifare, etc.). When one is found + // 'uid' will be populated with the UID, and uidLength will indicate + // if the uid is 4 bytes (Mifare Classic) or 7 bytes (Mifare Ultralight) + success = nfc.readPassiveTargetID(PN532_MIFARE_ISO14443A, uid, &uidLength); + + if (success) { + // Display some basic information about the card + Serial.println("Found an ISO14443A card"); + Serial.print(" UID Length: ");Serial.print(uidLength, DEC);Serial.println(" bytes"); + Serial.print(" UID Value: "); + for (uint8_t i = 0; i < uidLength; i++) { + Serial.print(uid[i], HEX); + Serial.print(' '); + } + Serial.println(""); + + if (uidLength == 4) + { + // We probably have a Mifare Classic card ... + Serial.println("Seems to be a Mifare Classic card (4 byte UID)"); + + // Now we try to go through all 16 sectors (each having 4 blocks) + // authenticating each sector, and then dumping the blocks + for (currentblock = 0; currentblock < 64; currentblock++) + { + // Check if this is a new block so that we can reauthenticate + if (nfc.mifareclassic_IsFirstBlock(currentblock)) authenticated = false; + + // If the sector hasn't been authenticated, do so first + if (!authenticated) + { + // Starting of a new sector ... try to to authenticate + Serial.print("------------------------Sector ");Serial.print(currentblock/4, DEC);Serial.println("-------------------------"); + if (currentblock == 0) + { + // This will be 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF for Mifare Classic (non-NDEF!) + // or 0xA0 0xA1 0xA2 0xA3 0xA4 0xA5 for NDEF formatted cards using key a, + // but keyb should be the same for both (0xFF 0xFF 0xFF 0xFF 0xFF 0xFF) + success = nfc.mifareclassic_AuthenticateBlock (uid, uidLength, currentblock, 1, keyuniversal); + } + else + { + // This will be 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF for Mifare Classic (non-NDEF!) + // or 0xD3 0xF7 0xD3 0xF7 0xD3 0xF7 for NDEF formatted cards using key a, + // but keyb should be the same for both (0xFF 0xFF 0xFF 0xFF 0xFF 0xFF) + success = nfc.mifareclassic_AuthenticateBlock (uid, uidLength, currentblock, 1, keyuniversal); + } + if (success) + { + authenticated = true; + } + else + { + Serial.println("Authentication error"); + } + } + // If we're still not authenticated just skip the block + if (!authenticated) + { + Serial.print("Block ");Serial.print(currentblock, DEC);Serial.println(" unable to authenticate"); + } + else + { + // Authenticated ... we should be able to read the block now + // Dump the data into the 'data' array + success = nfc.mifareclassic_ReadDataBlock(currentblock, data); + if (success) + { + // Read successful + Serial.print("Block ");Serial.print(currentblock, DEC); + if (currentblock < 10) + { + Serial.print(" "); + } + else + { + Serial.print(" "); + } + // Dump the raw data + nfc.PrintHexChar(data, 16); + } + else + { + // Oops ... something happened + Serial.print("Block ");Serial.print(currentblock, DEC); + Serial.println(" unable to read this block"); + } + } + } + } + else + { + Serial.println("Ooops ... this doesn't seem to be a Mifare Classic card!"); + } + } + // Wait a bit before trying again + Serial.println("\n\nSend a character to run the mem dumper again!"); + Serial.flush(); + while (!Serial.available()); + while (Serial.available()) { + Serial.read(); + } + Serial.flush(); +} \ No newline at end of file diff --git a/lib/PN532/examples/mifareclassic_ndeftoclassic/mifareclassic_ndeftoclassic.pde b/lib/PN532/examples/mifareclassic_ndeftoclassic/mifareclassic_ndeftoclassic.pde new file mode 100644 index 0000000..c9ff621 --- /dev/null +++ b/lib/PN532/examples/mifareclassic_ndeftoclassic/mifareclassic_ndeftoclassic.pde @@ -0,0 +1,193 @@ +/**************************************************************************/ +/*! + This examples attempts to take a Mifare Classic 1K card that has been + formatted for NDEF messages using mifareclassic_formatndef, and resets + the authentication keys back to the Mifare Classic defaults + + To enable debug message, define DEBUG in PN532/PN532_debug.h +*/ +/**************************************************************************/ + +#if 0 + #define NFC_INTERFACE_SPI + #include + #include + #include + #include "PN532.h" + + PN532_SPI pn532spi(SPI, 10); + PN532 nfc(pn532spi); +#elif 1 + #define NFC_INTERFACE_HSU + #include + #include + #include + + PN532_HSU pn532hsu(Serial1); + PN532 nfc(pn532hsu); +#else + #define NFC_INTERFACE_I2C + #include + #include + #include + #include +#endif + +#ifdef USE_TINYUSB +#include +#endif + +#define NR_SHORTSECTOR (32) // Number of short sectors on Mifare 1K/4K +#define NR_LONGSECTOR (8) // Number of long sectors on Mifare 4K +#define NR_BLOCK_OF_SHORTSECTOR (4) // Number of blocks in a short sector +#define NR_BLOCK_OF_LONGSECTOR (16) // Number of blocks in a long sector + +// Determine the sector trailer block based on sector number +#define BLOCK_NUMBER_OF_SECTOR_TRAILER(sector) (((sector)>24) & 0xFF, HEX); + Serial.print("Firmware ver. "); Serial.print((versiondata>>16) & 0xFF, DEC); + Serial.print('.'); Serial.println((versiondata>>8) & 0xFF, DEC); + + // configure board to read RFID tags + nfc.SAMConfig(); +} + +void loop(void) { + uint8_t success; // Flag to check if there was an error with the PN532 + uint8_t uid[] = { 0, 0, 0, 0, 0, 0, 0 }; // Buffer to store the returned UID + uint8_t uidLength; // Length of the UID (4 or 7 bytes depending on ISO14443A card type) + bool authenticated = false; // Flag to indicate if the sector is authenticated + uint8_t blockBuffer[16]; // Buffer to store block contents + uint8_t blankAccessBits[3] = { 0xff, 0x07, 0x80 }; + uint8_t idx = 0; + uint8_t numOfSector = 16; // Assume Mifare Classic 1K for now (16 4-block sectors) + + Serial.println("Place your NDEF formatted Mifare Classic 1K card on the reader"); + Serial.println("and press any key to continue ..."); + + // Wait for user input before proceeding + while (!Serial.available()); + while (Serial.available()) Serial.read(); + + // Wait for an ISO14443A type card (Mifare, etc.). When one is found + // 'uid' will be populated with the UID, and uidLength will indicate + // if the uid is 4 bytes (Mifare Classic) or 7 bytes (Mifare Ultralight) + success = nfc.readPassiveTargetID(PN532_MIFARE_ISO14443A, uid, &uidLength); + + if (success) + { + // We seem to have a tag ... + // Display some basic information about it + Serial.println("Found an ISO14443A card/tag"); + Serial.print(" UID Length: ");Serial.print(uidLength, DEC);Serial.println(" bytes"); + Serial.print(" UID Value: "); + nfc.PrintHex(uid, uidLength); + Serial.println(""); + + // Make sure this is a Mifare Classic card + if (uidLength != 4) + { + Serial.println("Ooops ... this doesn't seem to be a Mifare Classic card!"); + return; + } + + Serial.println("Seems to be a Mifare Classic card (4 byte UID)"); + Serial.println(""); + Serial.println("Reformatting card for Mifare Classic (please don't touch it!) ... "); + + // Now run through the card sector by sector + for (idx = 0; idx < numOfSector; idx++) + { + // Step 1: Authenticate the current sector using key B 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF + success = nfc.mifareclassic_AuthenticateBlock (uid, uidLength, BLOCK_NUMBER_OF_SECTOR_TRAILER(idx), 1, (uint8_t *)KEY_DEFAULT_KEYAB); + if (!success) + { + Serial.print("Authentication failed for sector "); Serial.println(numOfSector); + return; + } + + // Step 2: Write to the other blocks + if (idx == 16) + { + memset(blockBuffer, 0, sizeof(blockBuffer)); + if (!(nfc.mifareclassic_WriteDataBlock((BLOCK_NUMBER_OF_SECTOR_TRAILER(idx)) - 3, blockBuffer))) + { + Serial.print("Unable to write to sector "); Serial.println(numOfSector); + return; + } + } + if ((idx == 0) || (idx == 16)) + { + memset(blockBuffer, 0, sizeof(blockBuffer)); + if (!(nfc.mifareclassic_WriteDataBlock((BLOCK_NUMBER_OF_SECTOR_TRAILER(idx)) - 2, blockBuffer))) + { + Serial.print("Unable to write to sector "); Serial.println(numOfSector); + return; + } + } + else + { + memset(blockBuffer, 0, sizeof(blockBuffer)); + if (!(nfc.mifareclassic_WriteDataBlock((BLOCK_NUMBER_OF_SECTOR_TRAILER(idx)) - 3, blockBuffer))) + { + Serial.print("Unable to write to sector "); Serial.println(numOfSector); + return; + } + if (!(nfc.mifareclassic_WriteDataBlock((BLOCK_NUMBER_OF_SECTOR_TRAILER(idx)) - 2, blockBuffer))) + { + Serial.print("Unable to write to sector "); Serial.println(numOfSector); + return; + } + } + memset(blockBuffer, 0, sizeof(blockBuffer)); + if (!(nfc.mifareclassic_WriteDataBlock((BLOCK_NUMBER_OF_SECTOR_TRAILER(idx)) - 1, blockBuffer))) + { + Serial.print("Unable to write to sector "); Serial.println(numOfSector); + return; + } + + // Step 3: Reset both keys to 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF + memcpy(blockBuffer, KEY_DEFAULT_KEYAB, sizeof(KEY_DEFAULT_KEYAB)); + memcpy(blockBuffer + 6, blankAccessBits, sizeof(blankAccessBits)); + blockBuffer[9] = 0x69; + memcpy(blockBuffer + 10, KEY_DEFAULT_KEYAB, sizeof(KEY_DEFAULT_KEYAB)); + + // Step 4: Write the trailer block + if (!(nfc.mifareclassic_WriteDataBlock((BLOCK_NUMBER_OF_SECTOR_TRAILER(idx)), blockBuffer))) + { + Serial.print("Unable to write trailer block of sector "); Serial.println(numOfSector); + return; + } + } + } + + // Wait a bit before trying again + Serial.println("\n\nDone!"); + delay(1000); + Serial.flush(); + while(Serial.available()) Serial.read(); +} \ No newline at end of file diff --git a/lib/PN532/examples/mifareclassic_updatendef/mifareclassic_updatendef.pde b/lib/PN532/examples/mifareclassic_updatendef/mifareclassic_updatendef.pde new file mode 100644 index 0000000..3a77cfe --- /dev/null +++ b/lib/PN532/examples/mifareclassic_updatendef/mifareclassic_updatendef.pde @@ -0,0 +1,166 @@ +/**************************************************************************/ +/*! + Updates a sector that is already formatted for NDEF (using + mifareclassic_formatndef.pde for example), inserting a new url + + To enable debug message, define DEBUG in PN532/PN532_debug.h +*/ +/**************************************************************************/ + +#if 0 + #define NFC_INTERFACE_SPI + #include + #include + #include + #include "PN532.h" + + PN532_SPI pn532spi(SPI, 10); + PN532 nfc(pn532spi); +#elif 1 + #define NFC_INTERFACE_HSU + #include + #include + #include + + PN532_HSU pn532hsu(Serial1); + PN532 nfc(pn532hsu); +#else + #define NFC_INTERFACE_I2C + #include + #include + #include + #include +#endif + +#ifdef USE_TINYUSB +#include +#endif + + +/* + We can encode many different kinds of pointers to the card, + from a URL, to an Email address, to a phone number, and many more + check the library header .h file to see the large # of supported + prefixes! +*/ +// For a http://www. url: +const char * url = "elechouse.com"; +uint8_t ndefprefix = NDEF_URIPREFIX_HTTP_WWWDOT; + +// for an email address +//const char * url = "mail@example.com"; +//uint8_t ndefprefix = NDEF_URIPREFIX_MAILTO; + +// for a phone number +//const char * url = "+1 212 555 1212"; +//uint8_t ndefprefix = NDEF_URIPREFIX_TEL; + + +void setup(void) { + Serial.begin(115200); + Serial.println("Looking for PN532..."); + + nfc.begin(); + + uint32_t versiondata = nfc.getFirmwareVersion(); + if (! versiondata) { + Serial.print("Didn't find PN53x board"); + while (1); // halt + } + + // Got ok data, print it out! + Serial.print("Found chip PN5"); Serial.println((versiondata>>24) & 0xFF, HEX); + Serial.print("Firmware ver. "); Serial.print((versiondata>>16) & 0xFF, DEC); + Serial.print('.'); Serial.println((versiondata>>8) & 0xFF, DEC); + + // configure board to read RFID tags + nfc.SAMConfig(); +} + +void loop(void) { + uint8_t success; // Flag to check if there was an error with the PN532 + uint8_t uid[] = { 0, 0, 0, 0, 0, 0, 0 }; // Buffer to store the returned UID + uint8_t uidLength; // Length of the UID (4 or 7 bytes depending on ISO14443A card type) + bool authenticated = false; // Flag to indicate if the sector is authenticated + + // Use the default NDEF keys (these would have have set by mifareclassic_formatndef.pde!) + uint8_t keya[6] = { 0xA0, 0xA1, 0xA2, 0xA3, 0xA4, 0xA5 }; + uint8_t keyb[6] = { 0xD3, 0xF7, 0xD3, 0xF7, 0xD3, 0xF7 }; + + Serial.println("Place your NDEF formatted Mifare Classic card on the reader to update the"); + Serial.println("NDEF record and press any key to continue ..."); + // Wait for user input before proceeding + while (!Serial.available()); + // a key was pressed1 + while (Serial.available()) Serial.read(); + + // Wait for an ISO14443A type card (Mifare, etc.). When one is found + // 'uid' will be populated with the UID, and uidLength will indicate + // if the uid is 4 bytes (Mifare Classic) or 7 bytes (Mifare Ultralight) + success = nfc.readPassiveTargetID(PN532_MIFARE_ISO14443A, uid, &uidLength); + + if (success) + { + // Display some basic information about the card + Serial.println("Found an ISO14443A card"); + Serial.print(" UID Length: ");Serial.print(uidLength, DEC);Serial.println(" bytes"); + Serial.print(" UID Value: "); + nfc.PrintHex(uid, uidLength); + Serial.println(""); + + // Make sure this is a Mifare Classic card + if (uidLength != 4) + { + Serial.println("Ooops ... this doesn't seem to be a Mifare Classic card!"); + return; + } + + // We probably have a Mifare Classic card ... + Serial.println("Seems to be a Mifare Classic card (4 byte UID)"); + + // Check if this is an NDEF card (using first block of sector 1 from mifareclassic_formatndef.pde) + // Must authenticate on the first key using 0xD3 0xF7 0xD3 0xF7 0xD3 0xF7 + success = nfc.mifareclassic_AuthenticateBlock (uid, uidLength, 4, 0, keyb); + if (!success) + { + Serial.println("Unable to authenticate block 4 ... is this card NDEF formatted?"); + return; + } + + Serial.println("Authentication succeeded (seems to be an NDEF/NFC Forum tag) ..."); + + // Authenticated seems to have worked + // Try to write an NDEF record to sector 1 + // Use 0x01 for the URI Identifier Code to prepend "http://www." + // to the url (and save some space). For information on URI ID Codes + // see http://www.ladyada.net/wiki/private/articlestaging/nfc/ndef + if (strlen(url) > 38) + { + // The length is also checked in the WriteNDEFURI function, but lets + // warn users here just in case they change the value and it's bigger + // than it should be + Serial.println("URI is too long ... must be less than 38 characters!"); + return; + } + + Serial.println("Updating sector 1 with URI as NDEF Message"); + + // URI is within size limits ... write it to the card and report success/failure + success = nfc.mifareclassic_WriteNDEFURI(1, ndefprefix, url); + if (success) + { + Serial.println("NDEF URI Record written to sector 1"); + Serial.println(""); + } + else + { + Serial.println("NDEF Record creation failed! :("); + } + } + + // Wait a bit before trying again + Serial.println("\n\nDone!"); + delay(1000); + Serial.flush(); + while(Serial.available()) Serial.read(); +} diff --git a/lib/PN532/examples/ntag21x_protect/ntag21x_protect.ino b/lib/PN532/examples/ntag21x_protect/ntag21x_protect.ino new file mode 100644 index 0000000..ed0fc5d --- /dev/null +++ b/lib/PN532/examples/ntag21x_protect/ntag21x_protect.ino @@ -0,0 +1,96 @@ +// NTAG21x supports 4 bytes password to protect pages started from AUTH0 +// AUTH0 defines the page address from which the password verification is required. +// Valid address range for byte AUTH0 is from 00h to FFh. +// If AUTH0 is set to a page address which is higher than the last page from the user configuration, +// the password protection is effectively disabled +#include +#include +#if 0 // Using PN532's SPI (Seeed NFC shield) +#define NFC_INTERFACE_SPI +#include +#include +#include + + +PN532_SPI intf(SPI, 10); +PN532 nfc = PN532(intf); +#else // Using PN532's I2C +#define NFC_INTERFACE_I2C +#include +#include +#include + +PN532_I2C intf(Wire); +PN532 nfc = PN532(intf); +#endif + +// Using PN532's UART (Grove NFC) + +// #include +// #include +// #include +// PN532_HSU intf(Serial1); +// PN532 nfc = PN532(intf); + +uint8_t password[4] = {0x12, 0x34, 0x56, 0x78}; +uint8_t buf[4]; +uint8_t uid[7]; +uint8_t uidLength; + +void setup(void) +{ + Serial.begin(9600); + Serial.println("NTAG21x R/W"); + + nfc.begin(); + nfc.SAMConfig(); +} + +void loop(void) +{ + Serial.println("wait for a tag"); + // wait until a tag is present + while (!nfc.readPassiveTargetID(PN532_MIFARE_ISO14443A, uid, &uidLength)) + { + } + + // if NTAG21x enables r/w protection, uncomment the following line + // nfc.ntag21x_auth(password); + + nfc.mifareultralight_ReadPage(3, buf); + int capacity = buf[2] * 8; + Serial.print(F("Tag capacity ")); + Serial.print(capacity); + Serial.println(F(" bytes")); + + uint8_t cfg_page_base = 0x29; // NTAG213 + if (capacity == 0x3E) + { + cfg_page_base = 0x83; // NTAG215 + } + else if (capacity == 0x6D) + { + cfg_page_base = 0xE3; // NTAG216 + } + + // PWD page, set new password + nfc.mifareultralight_WritePage(cfg_page_base + 2, password); + + // disable r/w + // | PROT | CFG_LCK | RFUI | NFC_CNT_EN | NFC_CNT_PWD_PROT | AUTHLIM (2:0) | + buf[0] = (1 << 7) | 0x0; + nfc.mifareultralight_WritePage(cfg_page_base + 1, buf); + + // protect pages started from AUTH0 + uint8_t auth0 = 0x10; + buf[0] = 0; + buf[1] = 0; + buf[2] = 0; + buf[3] = auth0; + nfc.mifareultralight_WritePage(cfg_page_base, buf); + + // wait until the tag is removed + while (nfc.readPassiveTargetID(PN532_MIFARE_ISO14443A, uid, &uidLength)) + { + } +} diff --git a/lib/PN532/examples/ntag21x_rw/ntag21x_rw.ino b/lib/PN532/examples/ntag21x_rw/ntag21x_rw.ino new file mode 100644 index 0000000..5b613e0 --- /dev/null +++ b/lib/PN532/examples/ntag21x_rw/ntag21x_rw.ino @@ -0,0 +1,74 @@ + +// Clean resets a tag back to factory-like state +// For Mifare Classic, tag is zero'd and reformatted as Mifare Classic +// For Mifare Ultralight, tags is zero'd and left empty +#include +#include + +#if 0 // Using PN532's SPI (Seeed NFC shield) +#define NFC_INTERFACE_SPI +#include +#include +#include + +PN532_SPI intf(SPI, 10); +PN532 nfc = PN532(intf); +#else // Using PN532's I2C +#define NFC_INTERFACE_I2C +#include +#include +#include +PN532_I2C intf(Wire); +PN532 nfc = PN532(intf); +#endif + +// Using PN532's UART (Grove NFC) + +// #include +// #include +// #include +// PN532_HSU intf(Serial1); +// PN532 nfc = PN532(intf); + +uint8_t password[4] = {0x12, 0x34, 0x56, 0x78}; +uint8_t buf[4]; +uint8_t uid[7]; +uint8_t uidLength; + +void setup(void) +{ + Serial.begin(9600); + Serial.println("NTAG21x R/W"); + + nfc.begin(); + nfc.SAMConfig(); +} + +void loop(void) +{ + Serial.println("wait for a tag"); + // wait until a tag is present + while (!nfc.readPassiveTargetID(PN532_MIFARE_ISO14443A, uid, &uidLength)) + { + } + + // if NTAG21x enables r/w protection, uncomment the following line + // nfc.ntag21x_auth(password); + + nfc.mifareultralight_ReadPage(3, buf); + int capacity = buf[2] * 8; + Serial.print(F("Tag capacity ")); + Serial.print(capacity); + Serial.println(F(" bytes")); + + for (int i = 4; i < capacity / 4; i++) + { + nfc.mifareultralight_ReadPage(i, buf); + nfc.PrintHexChar(buf, 4); + } + + // wait until the tag is removed + while (nfc.readPassiveTargetID(PN532_MIFARE_ISO14443A, uid, &uidLength)) + { + } +} diff --git a/lib/PN532/examples/p2p_raw/p2p_raw.ino b/lib/PN532/examples/p2p_raw/p2p_raw.ino new file mode 100644 index 0000000..87c53aa --- /dev/null +++ b/lib/PN532/examples/p2p_raw/p2p_raw.ino @@ -0,0 +1,57 @@ +// snep_test.ino +// send a SNEP message to adnroid and get a message from android +#define NFC_INTERFACE_SPI +#include "SPI.h" +#include "PN532_SPI.h" +#include "PN532_SPI.cpp" +#include "llcp.h" +#include "snep.h" + +PN532_SPI pn532spi(SPI, 10); +SNEP nfc(pn532spi); + +void setup() +{ + Serial.begin(115200); + Serial.println("-------Peer to Peer--------"); +} + +uint8_t message[] = { + 0xD2, 0xA, 0xB, 0x74, 0x65, 0x78, 0x74, 0x2F, 0x70, 0x6C, + 0x61, 0x69, 0x6E, 0x68, 0x65, 0x6C, 0x6C, 0x6F, 0x20, 0x77, + 0x6F, 0x72, 0x6C, 0x64}; + +uint8_t buf[128]; + +void loop() +{ + + nfc.write(message, sizeof(message)); + delay(3000); + + int16_t len = nfc.read(buf, sizeof(buf)); + if (len > 0) + { + Serial.println("get a SNEP message:"); + for (uint8_t i = 0; i < len; i++) + { + Serial.print(buf[i], HEX); + Serial.print(' '); + } + Serial.print('\n'); + for (uint8_t i = 0; i < len; i++) + { + char c = buf[i]; + if (c <= 0x1f || c > 0x7f) + { + Serial.print('.'); + } + else + { + Serial.print(c); + } + } + Serial.print('\n'); + } + delay(3000); +} \ No newline at end of file diff --git a/lib/PN532/examples/p2p_with_ndef_library/p2p_with_ndef_library.ino b/lib/PN532/examples/p2p_with_ndef_library/p2p_with_ndef_library.ino new file mode 100644 index 0000000..5af6880 --- /dev/null +++ b/lib/PN532/examples/p2p_with_ndef_library/p2p_with_ndef_library.ino @@ -0,0 +1,62 @@ +// send a NDEF message to adnroid or get a NDEF message +// +// note: [NDEF library](https://github.com/Don/NDEF) is needed. +#define NFC_INTERFACE_SPI +#include "SPI.h" +#include "PN532_SPI.h" +#include "PN532_SPI.cpp" +#include "snep.h" +#include "NdefMessage.h" + +PN532_SPI pn532spi(SPI, 10); +SNEP nfc(pn532spi); +uint8_t ndefBuf[128]; + +void setup() +{ + Serial.begin(115200); + Serial.println("-------Peer to Peer--------"); +} + +void loop() +{ +#if 1 + Serial.println("Send a message to Android"); + NdefMessage message = NdefMessage(); + message.addUriRecord("http://www.seeedstudio.com"); + int messageSize = message.getEncodedSize(); + if (messageSize > sizeof(ndefBuf)) + { + Serial.println("ndefBuf is too small"); + while (1) + { + } + } + + message.encode(ndefBuf); + if (0 >= nfc.write(ndefBuf, messageSize)) + { + Serial.println("Failed"); + } + else + { + Serial.println("Success"); + } + + delay(3000); +#else + Serial.println("Get a message from Android"); + int msgSize = nfc.read(ndefBuf, sizeof(ndefBuf)); + if (msgSize > 0) + { + NdefMessage msg = NdefMessage(ndefBuf, msgSize); + // msg.print(); + Serial.println("\nSuccess"); + } + else + { + Serial.println("failed"); + } + delay(3000); +#endif +} diff --git a/lib/PN532/examples/readMifare/readMifare.pde b/lib/PN532/examples/readMifare/readMifare.pde new file mode 100644 index 0000000..32710c1 --- /dev/null +++ b/lib/PN532/examples/readMifare/readMifare.pde @@ -0,0 +1,169 @@ +/**************************************************************************/ +/*! + This example will wait for any ISO14443A card or tag, and + depending on the size of the UID will attempt to read from it. + + If the card has a 4-byte UID it is probably a Mifare + Classic card, and the following steps are taken: + + - Authenticate block 4 (the first block of Sector 1) using + the default KEYA of 0XFF 0XFF 0XFF 0XFF 0XFF 0XFF + - If authentication succeeds, we can then read any of the + 4 blocks in that sector (though only block 4 is read here) + + If the card has a 7-byte UID it is probably a Mifare + Ultralight card, and the 4 byte pages can be read directly. + Page 4 is read by default since this is the first 'general- + purpose' page on the tags. + + To enable debug message, define DEBUG in PN532/PN532_debug.h +*/ +/**************************************************************************/ + +#if 0 + #define NFC_INTERFACE_SPI + #include + #include + #include + #include "PN532.h" + + PN532_SPI pn532spi(SPI, 10); + PN532 nfc(pn532spi); +#elif 1 + #define NFC_INTERFACE_HSU + #include + #include + #include + + PN532_HSU pn532hsu(Serial1); + PN532 nfc(pn532hsu); +#else + #define NFC_INTERFACE_I2C + #include + #include + #include + #include + PN532_I2C pn532i2c(Wire); + PN532 nfc(pn532i2c); +#endif + +#ifdef USE_TINYUSB +#include +#endif + +void setup(void) { + Serial.begin(115200); + Serial.println("Hello!"); + + nfc.begin(); + + uint32_t versiondata = nfc.getFirmwareVersion(); + if (! versiondata) { + Serial.print("Didn't find PN53x board"); + while (1); // halt + } + // Got ok data, print it out! + Serial.print("Found chip PN5"); Serial.println((versiondata>>24) & 0xFF, HEX); + Serial.print("Firmware ver. "); Serial.print((versiondata>>16) & 0xFF, DEC); + Serial.print('.'); Serial.println((versiondata>>8) & 0xFF, DEC); + + // configure board to read RFID tags + nfc.SAMConfig(); + + Serial.println("Waiting for an ISO14443A Card ..."); +} + + +void loop(void) { + uint8_t success; + uint8_t uid[] = { 0, 0, 0, 0, 0, 0, 0 }; // Buffer to store the returned UID + uint8_t uidLength; // Length of the UID (4 or 7 bytes depending on ISO14443A card type) + + // Wait for an ISO14443A type cards (Mifare, etc.). When one is found + // 'uid' will be populated with the UID, and uidLength will indicate + // if the uid is 4 bytes (Mifare Classic) or 7 bytes (Mifare Ultralight) + success = nfc.readPassiveTargetID(PN532_MIFARE_ISO14443A, uid, &uidLength); + + if (success) { + // Display some basic information about the card + Serial.println("Found an ISO14443A card"); + Serial.print(" UID Length: ");Serial.print(uidLength, DEC);Serial.println(" bytes"); + Serial.print(" UID Value: "); + nfc.PrintHex(uid, uidLength); + Serial.println(""); + + if (uidLength == 4) + { + // We probably have a Mifare Classic card ... + Serial.println("Seems to be a Mifare Classic card (4 byte UID)"); + + // Now we need to try to authenticate it for read/write access + // Try with the factory default KeyA: 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF + Serial.println("Trying to authenticate block 4 with default KEYA value"); + uint8_t keya[6] = { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }; + + // Start with block 4 (the first block of sector 1) since sector 0 + // contains the manufacturer data and it's probably better just + // to leave it alone unless you know what you're doing + success = nfc.mifareclassic_AuthenticateBlock(uid, uidLength, 4, 0, keya); + + if (success) + { + Serial.println("Sector 1 (Blocks 4..7) has been authenticated"); + uint8_t data[16]; + + // If you want to write something to block 4 to test with, uncomment + // the following line and this text should be read back in a minute + // data = { 'a', 'd', 'a', 'f', 'r', 'u', 'i', 't', '.', 'c', 'o', 'm', 0, 0, 0, 0}; + // success = nfc.mifareclassic_WriteDataBlock (4, data); + + // Try to read the contents of block 4 + success = nfc.mifareclassic_ReadDataBlock(4, data); + + if (success) + { + // Data seems to have been read ... spit it out + Serial.println("Reading Block 4:"); + nfc.PrintHexChar(data, 16); + Serial.println(""); + + // Wait a bit before reading the card again + delay(1000); + } + else + { + Serial.println("Ooops ... unable to read the requested block. Try another key?"); + } + } + else + { + Serial.println("Ooops ... authentication failed: Try another key?"); + } + } + + if (uidLength == 7) + { + // We probably have a Mifare Ultralight card ... + Serial.println("Seems to be a Mifare Ultralight tag (7 byte UID)"); + + // Try to read the first general-purpose user page (#4) + Serial.println("Reading page 4"); + uint8_t data[32]; + success = nfc.mifareultralight_ReadPage (4, data); + if (success) + { + // Data seems to have been read ... spit it out + nfc.PrintHexChar(data, 4); + Serial.println(""); + + // Wait a bit before reading the card again + delay(1000); + } + else + { + Serial.println("Ooops ... unable to read the requested page!?"); + } + } + } +} + diff --git a/lib/PN532/library.json b/lib/PN532/library.json new file mode 100644 index 0000000..29c54f7 --- /dev/null +++ b/lib/PN532/library.json @@ -0,0 +1,124 @@ +{ + "$schema": "https://raw.githubusercontent.com/platformio/platformio-core/develop/platformio/assets/schema/library.json", + "name": "PN532-Arduino", + "description": "This is Seeed-Studio's library for PN532 to use NFC technology.", + "authors": [ + { + "maintainer": true, + "name": "Baozhu Zuo", + "email": "zuobaozhu@gmail.com", + "url": "https://github.com/Pillar1989" + }, + { + "name": "Yihui Xiong", + "url": "https://github.com/xiongyihui" + }, + { + "url": "https://github.com/awieser" + }, + { + "name": "Jiapeng Li", + "email": "mail@jiapeng.me", + "url": "https://github.com/JiapengLi" + }, + { + "url": "https://github.com/wilson-elechouse" + } + ], + "frameworks": ["arduino"], + "platforms": ["*"], + "keywords": ["PN532", "NFC"], + "headers": [ + "PN532.h", + "PN532_HSU.h", + "PN532_I2C.h", + "PN532_SPI.h", + "PN532_SWHSU.h" + ], + "repository": { + "url": "https://github.com/Seeed-Studio/PN532.git", + "branch": "arduino", + "type": "git" + }, + "dependencies": [ + { + "name": "external-repo", + "version": "https://github.com/don/NDEF" + } + ], + "build": { + "includeDir": "src" + }, + "examples": [ + { + "name": "FeliCa card detection", + "base": "examples/FeliCa_card_detection", + "files": ["FeliCa_card_detection.pde"] + }, + { + "name": "FeliCa card read", + "base": "examples/FeliCa_card_read", + "files": ["FeliCa_card_read.pde"] + }, + { + "name": "Android Host-based card emulation", + "base": "examples/android_hce", + "files": ["android_hce.ino"] + }, + { + "name": "Emulate tag ndef", + "base": "examples/emulate_tag_ndef", + "files": ["emulate_tag_ndef.ino"] + }, + { + "name": "Read ISO14443A UID", + "base": "examples/iso14443a_uid", + "files": ["iso14443a_uid.pde"] + }, + { + "name": "Format a clean Mifare Classic 1K card as an NFC Forum tag", + "base": "examples/mifareclassic_formatndef", + "files": ["mifareclassic_formatndef.pde"] + }, + { + "name": "Dump the contents of a Mifare Classic 1K card", + "base": "examples/mifareclassic_memdump", + "files": ["mifareclassic_memdump.pde"] + }, + { + "name": "Resets the authentication keys back to the Mifare Classic defaults", + "base": "examples/mifareclassic_ndeftoclassic", + "files": ["mifareclassic_ndeftoclassic.pde"] + }, + { + "name": "Updates a Mifare Classic that is already formatted for NDEF", + "base": "examples/mifareclassic_updatendef", + "files": ["mifareclassic_updatendef.pde"] + }, + { + "name": "NTAG21x password protection", + "base": "examples/ntag21x_protect", + "files": ["ntag21x_protect.ino"] + }, + { + "name": "Factory reset a tag", + "base": "examples/ntag21x_rw", + "files": ["ntag21x_rw.ino"] + }, + { + "name": "Send a SNEP message to Android and get a message from Android", + "base": "examples/p2p_raw", + "files": ["p2p_raw.ino"] + }, + { + "name": "Send a NDEF message to Android or get a NDEF message", + "base": "examples/p2p_with_ndef_library", + "files": ["p2p_with_ndef_library.ino"] + }, + { + "name": "Read Mifare card", + "base": "examples/readMifare", + "files": ["readMifare.pde"] + } + ] +} diff --git a/lib/PN532/library.properties b/lib/PN532/library.properties new file mode 100644 index 0000000..a318096 --- /dev/null +++ b/lib/PN532/library.properties @@ -0,0 +1,11 @@ +name=PN532-Arduino +version=1.0.0 +author=Baozhu Zuo , Yihui Xiong, awieser, Jiapeng Li , wilson-elechouse +maintainer=Baozhu Zuo +sentence=This is the Seeed-Studio library for PN532 to use NFC technology. +paragraph= +category=Sensors +url=https://github.com/Seeed-Studio/PN532 +architectures=* +includes=PN532.h +depends=NDEF \ No newline at end of file diff --git a/lib/PN532/src/PN532.cpp b/lib/PN532/src/PN532.cpp new file mode 100644 index 0000000..9b533b3 --- /dev/null +++ b/lib/PN532/src/PN532.cpp @@ -0,0 +1,1644 @@ +/**************************************************************************/ +/*! + @file PN532.cpp + @author Adafruit Industries & Seeed Studio + @license BSD +*/ +/**************************************************************************/ + +#include "Arduino.h" +#include "PN532.h" +#include "PN532_debug.h" +#include + +#define HAL(func) (_interface->func) + +PN532::PN532(PN532Interface &interface) +{ + _interface = &interface; +} + +/**************************************************************************/ +/*! + @brief Setups the HW +*/ +/**************************************************************************/ +void PN532::begin() +{ + HAL(begin) + (); + HAL(wakeup) + (); +} + +/**************************************************************************/ +/*! + @brief Prints a hexadecimal value in plain characters + + @param data Pointer to the uint8_t data + @param numBytes Data length in bytes +*/ +/**************************************************************************/ +void PN532::PrintHex(const uint8_t *data, const uint32_t numBytes) +{ +#ifdef ARDUINO + for (uint8_t i = 0; i < numBytes; i++) + { + if (data[i] < 0x10) + { + PN532_DEBUG_SERIAL.print(" 0"); + } + else + { + PN532_DEBUG_SERIAL.print(' '); + } + PN532_DEBUG_SERIAL.print(data[i], HEX); + } + PN532_DEBUG_SERIAL.println(""); +#else + for (uint8_t i = 0; i < numBytes; i++) + { + printf(" %2X", data[i]); + } + printf("\n"); +#endif +} + +/**************************************************************************/ +/*! + @brief Prints a hexadecimal value in plain characters, along with + the char equivalents in the following format + + 00 00 00 00 00 00 ...... + + @param data Pointer to the data + @param numBytes Data length in bytes +*/ +/**************************************************************************/ +void PN532::PrintHexChar(const uint8_t *data, const uint32_t numBytes) +{ +#ifdef ARDUINO + for (uint8_t i = 0; i < numBytes; i++) + { + if (data[i] < 0x10) + { + PN532_DEBUG_SERIAL.print(" 0"); + } + else + { + PN532_DEBUG_SERIAL.print(' '); + } + PN532_DEBUG_SERIAL.print(data[i], HEX); + } + PN532_DEBUG_SERIAL.print(" "); + for (uint8_t i = 0; i < numBytes; i++) + { + char c = data[i]; + if (c <= 0x1f || c > 0x7f) + { + PN532_DEBUG_SERIAL.print('.'); + } + else + { + PN532_DEBUG_SERIAL.print(c); + } + } + PN532_DEBUG_SERIAL.println(""); +#else + for (uint8_t i = 0; i < numBytes; i++) + { + printf(" %2X", data[i]); + } + printf(" "); + for (uint8_t i = 0; i < numBytes; i++) + { + char c = data[i]; + if (c <= 0x1f || c > 0x7f) + { + printf("."); + } + else + { + printf("%c", c); + } + printf("\n"); + } +#endif +} + +/**************************************************************************/ +/*! + @brief Checks the firmware version of the PN5xx chip + + @returns The chip's firmware version and ID +*/ +/**************************************************************************/ +uint32_t PN532::getFirmwareVersion(void) +{ + uint32_t response; + + pn532_packetbuffer[0] = PN532_COMMAND_GETFIRMWAREVERSION; + + if (HAL(writeCommand)(pn532_packetbuffer, 1)) + { + return 0; + } + + // read data packet + int16_t status = HAL(readResponse)(pn532_packetbuffer, sizeof(pn532_packetbuffer)); + if (0 > status) + { + return 0; + } + + response = pn532_packetbuffer[0]; + response <<= 8; + response |= pn532_packetbuffer[1]; + response <<= 8; + response |= pn532_packetbuffer[2]; + response <<= 8; + response |= pn532_packetbuffer[3]; + + return response; +} + +/**************************************************************************/ +/*! + @brief Read a PN532 register. + + @param reg the 16-bit register address. + + @returns The register value. +*/ +/**************************************************************************/ +uint32_t PN532::readRegister(uint16_t reg) +{ + uint32_t response; + + pn532_packetbuffer[0] = PN532_COMMAND_READREGISTER; + pn532_packetbuffer[1] = (reg >> 8) & 0xFF; + pn532_packetbuffer[2] = reg & 0xFF; + + if (HAL(writeCommand)(pn532_packetbuffer, 3)) + { + return 0; + } + + // read data packet + int16_t status = HAL(readResponse)(pn532_packetbuffer, sizeof(pn532_packetbuffer)); + if (0 > status) + { + return 0; + } + + response = pn532_packetbuffer[0]; + + return response; +} + +/**************************************************************************/ +/*! + @brief Write to a PN532 register. + + @param reg the 16-bit register address. + @param val the 8-bit value to write. + + @returns 0 for failure, 1 for success. +*/ +/**************************************************************************/ +uint32_t PN532::writeRegister(uint16_t reg, uint8_t val) +{ + uint32_t response; + + pn532_packetbuffer[0] = PN532_COMMAND_WRITEREGISTER; + pn532_packetbuffer[1] = (reg >> 8) & 0xFF; + pn532_packetbuffer[2] = reg & 0xFF; + pn532_packetbuffer[3] = val; + + if (HAL(writeCommand)(pn532_packetbuffer, 4)) + { + return 0; + } + + // read data packet + int16_t status = HAL(readResponse)(pn532_packetbuffer, sizeof(pn532_packetbuffer)); + if (0 > status) + { + return 0; + } + + return 1; +} + +/**************************************************************************/ +/*! + Writes an 8-bit value that sets the state of the PN532's GPIO pins + + @warning This function is provided exclusively for board testing and + is dangerous since it will throw an error if any pin other + than the ones marked "Can be used as GPIO" are modified! All + pins that can not be used as GPIO should ALWAYS be left high + (value = 1) or the system will become unstable and a HW reset + will be required to recover the PN532. + + pinState[0] = P30 Can be used as GPIO + pinState[1] = P31 Can be used as GPIO + pinState[2] = P32 *** RESERVED (Must be 1!) *** + pinState[3] = P33 Can be used as GPIO + pinState[4] = P34 *** RESERVED (Must be 1!) *** + pinState[5] = P35 Can be used as GPIO + + @returns 1 if everything executed properly, 0 for an error +*/ +/**************************************************************************/ +bool PN532::writeGPIO(uint8_t pinstate) +{ + // Make sure pinstate does not try to toggle P32 or P34 + pinstate |= (1 << PN532_GPIO_P32) | (1 << PN532_GPIO_P34); + + // Fill command buffer + pn532_packetbuffer[0] = PN532_COMMAND_WRITEGPIO; + pn532_packetbuffer[1] = PN532_GPIO_VALIDATIONBIT | pinstate; // P3 Pins + pn532_packetbuffer[2] = 0x00; // P7 GPIO Pins (not used ... taken by I2C) + + DMSG("Writing P3 GPIO: "); + DMSG_HEX(pn532_packetbuffer[1]); + DMSG("\n"); + + // Send the WRITEGPIO command (0x0E) + if (HAL(writeCommand)(pn532_packetbuffer, 3)) + return 0; + + return (0 <= HAL(readResponse)(pn532_packetbuffer, sizeof(pn532_packetbuffer))); +} + +/**************************************************************************/ +/*! + Reads the state of the PN532's GPIO pins + + @returns An 8-bit value containing the pin state where: + + pinState[0] = P30 + pinState[1] = P31 + pinState[2] = P32 + pinState[3] = P33 + pinState[4] = P34 + pinState[5] = P35 +*/ +/**************************************************************************/ +uint8_t PN532::readGPIO(void) +{ + pn532_packetbuffer[0] = PN532_COMMAND_READGPIO; + + // Send the READGPIO command (0x0C) + if (HAL(writeCommand)(pn532_packetbuffer, 1)) + return 0x0; + + HAL(readResponse) + (pn532_packetbuffer, sizeof(pn532_packetbuffer)); + + /* READGPIO response without prefix and suffix should be in the following format: + + byte Description + ------------- ------------------------------------------ + b0 P3 GPIO Pins + b1 P7 GPIO Pins (not used ... taken by I2C) + b2 Interface Mode Pins (not used ... bus select pins) + */ + + DMSG("P3 GPIO: "); + DMSG_HEX(pn532_packetbuffer[7]); + DMSG("P7 GPIO: "); + DMSG_HEX(pn532_packetbuffer[8]); + DMSG("I0I1 GPIO: "); + DMSG_HEX(pn532_packetbuffer[9]); + DMSG("\n"); + + return pn532_packetbuffer[0]; +} + +/**************************************************************************/ +/*! + @brief Configures the SAM (Secure Access Module) +*/ +/**************************************************************************/ +bool PN532::SAMConfig(void) +{ + pn532_packetbuffer[0] = PN532_COMMAND_SAMCONFIGURATION; + pn532_packetbuffer[1] = 0x01; // normal mode; + pn532_packetbuffer[2] = 0x14; // timeout 50ms * 20 = 1 second + pn532_packetbuffer[3] = 0x01; // use IRQ pin! + + DMSG("SAMConfig\n"); + + if (HAL(writeCommand)(pn532_packetbuffer, 4)) + return false; + + return (0 < HAL(readResponse)(pn532_packetbuffer, sizeof(pn532_packetbuffer))); +} + +/**************************************************************************/ +/*! + @brief Turn the module into power mode will wake up on I2C or SPI request +*/ +/**************************************************************************/ +bool PN532::powerDownMode() +{ + pn532_packetbuffer[0] = PN532_COMMAND_POWERDOWN; + pn532_packetbuffer[1] = 0xC0; // I2C or SPI Wakeup + pn532_packetbuffer[2] = 0x00; // no IRQ + + DMSG("POWERDOWN\n"); + + if (HAL(writeCommand)(pn532_packetbuffer, 4)) + return false; + + return (0 < HAL(readResponse)(pn532_packetbuffer, sizeof(pn532_packetbuffer))); +} + +/**************************************************************************/ +/*! + Sets the MxRtyPassiveActivation uint8_t of the RFConfiguration register + + @param maxRetries 0xFF to wait forever, 0x00..0xFE to timeout + after mxRetries + + @returns 1 if everything executed properly, 0 for an error +*/ +/**************************************************************************/ +bool PN532::setPassiveActivationRetries(uint8_t maxRetries) +{ + pn532_packetbuffer[0] = PN532_COMMAND_RFCONFIGURATION; + pn532_packetbuffer[1] = 5; // Config item 5 (MaxRetries) + pn532_packetbuffer[2] = 0xFF; // MxRtyATR (default = 0xFF) + pn532_packetbuffer[3] = 0x01; // MxRtyPSL (default = 0x01) + pn532_packetbuffer[4] = maxRetries; + + if (HAL(writeCommand)(pn532_packetbuffer, 5)) + return 0x0; // no ACK + + return (0 < HAL(readResponse)(pn532_packetbuffer, sizeof(pn532_packetbuffer))); +} + +/**************************************************************************/ +/*! + Sets the RFon/off uint8_t of the RFConfiguration register + + @param autoRFCA 0x00 No check of the external field before + activation + + 0x02 Check the external field before + activation + + @param rFOnOff 0x00 Switch the RF field off, 0x01 switch the RF + field on + + @returns 1 if everything executed properly, 0 for an error +*/ +/**************************************************************************/ + +bool PN532::setRFField(uint8_t autoRFCA, uint8_t rFOnOff) +{ + pn532_packetbuffer[0] = PN532_COMMAND_RFCONFIGURATION; + pn532_packetbuffer[1] = 1; + pn532_packetbuffer[2] = 0x00 | autoRFCA | rFOnOff; + + if (HAL(writeCommand)(pn532_packetbuffer, 3)) + { + return 0x0; // command failed + } + + return (0 < HAL(readResponse)(pn532_packetbuffer, sizeof(pn532_packetbuffer))); +} + +/***** ISO14443A Commands ******/ + +/**************************************************************************/ +/*! + Puts PN532 into passive detection state with IRQ while waiting for an ISO14443A target + + @param cardBaudRate Baud rate of the card + + @returns 1 if everything executed properly, 0 for an error +*/ +bool PN532::startPassiveTargetIDDetection(uint8_t cardbaudrate) +{ + pn532_packetbuffer[0] = PN532_COMMAND_INLISTPASSIVETARGET; + pn532_packetbuffer[1] = 1; // max 1 cards at once (we can set this to 2 later) + pn532_packetbuffer[2] = cardbaudrate; + + if (HAL(writeCommand)(pn532_packetbuffer, 3)) + { + return 0x0; // command failed + } + return 1; +} + +/**************************************************************************/ +/*! + Waits for an ISO14443A target to enter the field + + @param cardBaudRate Baud rate of the card + @param uid Pointer to the array that will be populated + with the card's UID (up to 7 bytes) + @param uidLength Pointer to the variable that will hold the + length of the card's UID. + @param timeout The number of tries before timing out + @param inlist If set to true, the card will be inlisted + + @returns 1 if everything executed properly, 0 for an error +*/ +/**************************************************************************/ +bool PN532::readPassiveTargetID(uint8_t cardbaudrate, uint8_t *uid, uint8_t *uidLength, uint16_t timeout, bool inlist) +{ + pn532_packetbuffer[0] = PN532_COMMAND_INLISTPASSIVETARGET; + pn532_packetbuffer[1] = 1; // max 1 cards at once (we can set this to 2 later) + pn532_packetbuffer[2] = cardbaudrate; + + if (HAL(writeCommand)(pn532_packetbuffer, 3)) + { + return 0x0; // command failed + } + + // read data packet + if (HAL(readResponse)(pn532_packetbuffer, sizeof(pn532_packetbuffer), timeout) < 0) + { + return 0x0; + } + + // check some basic stuff + /* ISO14443A card response should be in the following format: + + byte Description + ------------- ------------------------------------------ + b0 Tags Found + b1 Tag Number (only one used in this example) + b2..3 SENS_RES + b4 SEL_RES + b5 NFCID Length + b6..NFCIDLen NFCID + */ + + if (pn532_packetbuffer[0] != 1) + return 0; + + uint16_t sens_res = pn532_packetbuffer[2]; + sens_res <<= 8; + sens_res |= pn532_packetbuffer[3]; + + DMSG("ATQA: 0x"); + DMSG_HEX(sens_res); + DMSG("SAK: 0x"); + DMSG_HEX(pn532_packetbuffer[4]); + DMSG("\n"); + + /* Card appears to be Mifare Classic */ + *uidLength = pn532_packetbuffer[5]; + + for (uint8_t i = 0; i < pn532_packetbuffer[5]; i++) + { + uid[i] = pn532_packetbuffer[6 + i]; + } + + if (inlist) + { + inListedTag = pn532_packetbuffer[1]; + } + + return 1; +} + +/***** Mifare Classic Functions ******/ + +/**************************************************************************/ +/*! + Indicates whether the specified block number is the first block + in the sector (block 0 relative to the current sector) +*/ +/**************************************************************************/ +bool PN532::mifareclassic_IsFirstBlock(uint32_t uiBlock) +{ + // Test if we are in the small or big sectors + if (uiBlock < 128) + return ((uiBlock) % 4 == 0); + else + return ((uiBlock) % 16 == 0); +} + +/**************************************************************************/ +/*! + Indicates whether the specified block number is the sector trailer +*/ +/**************************************************************************/ +bool PN532::mifareclassic_IsTrailerBlock(uint32_t uiBlock) +{ + // Test if we are in the small or big sectors + if (uiBlock < 128) + return ((uiBlock + 1) % 4 == 0); + else + return ((uiBlock + 1) % 16 == 0); +} + +/**************************************************************************/ +/*! + Tries to authenticate a block of memory on a MIFARE card using the + INDATAEXCHANGE command. See section 7.3.8 of the PN532 User Manual + for more information on sending MIFARE and other commands. + + @param uid Pointer to a byte array containing the card UID + @param uidLen The length (in bytes) of the card's UID (Should + be 4 for MIFARE Classic) + @param blockNumber The block number to authenticate. (0..63 for + 1KB cards, and 0..255 for 4KB cards). + @param keyNumber Which key type to use during authentication + (0 = MIFARE_CMD_AUTH_A, 1 = MIFARE_CMD_AUTH_B) + @param keyData Pointer to a byte array containing the 6 bytes + key value + + @returns 1 if everything executed properly, 0 for an error +*/ +/**************************************************************************/ +uint8_t PN532::mifareclassic_AuthenticateBlock(uint8_t *uid, uint8_t uidLen, uint32_t blockNumber, uint8_t keyNumber, uint8_t *keyData) +{ + uint8_t i; + + // Hang on to the key and uid data + memcpy(_key, keyData, 6); + memcpy(_uid, uid, uidLen); + _uidLen = uidLen; + + // Prepare the authentication command // + pn532_packetbuffer[0] = PN532_COMMAND_INDATAEXCHANGE; /* Data Exchange Header */ + pn532_packetbuffer[1] = 1; /* Max card numbers */ + pn532_packetbuffer[2] = (keyNumber) ? MIFARE_CMD_AUTH_B : MIFARE_CMD_AUTH_A; + pn532_packetbuffer[3] = blockNumber; /* Block Number (1K = 0..63, 4K = 0..255 */ + memcpy(pn532_packetbuffer + 4, _key, 6); + for (i = 0; i < _uidLen; i++) + { + pn532_packetbuffer[10 + i] = _uid[i]; /* 4 bytes card ID */ + } + + if (HAL(writeCommand)(pn532_packetbuffer, 10 + _uidLen)) + return 0; + + // Read the response packet + HAL(readResponse) + (pn532_packetbuffer, sizeof(pn532_packetbuffer)); + + // Check if the response is valid and we are authenticated??? + // for an auth success it should be bytes 5-7: 0xD5 0x41 0x00 + // Mifare auth error is technically byte 7: 0x14 but anything other and 0x00 is not good + if (pn532_packetbuffer[0] != 0x00) + { + DMSG("Authentification failed\n"); + return 0; + } + + return 1; +} + +/**************************************************************************/ +/*! + Tries to read an entire 16-bytes data block at the specified block + address. + + @param blockNumber The block number to authenticate. (0..63 for + 1KB cards, and 0..255 for 4KB cards). + @param data Pointer to the byte array that will hold the + retrieved data (if any) + + @returns 1 if everything executed properly, 0 for an error +*/ +/**************************************************************************/ +uint8_t PN532::mifareclassic_ReadDataBlock(uint8_t blockNumber, uint8_t *data) +{ + DMSG("Trying to read 16 bytes from block "); + DMSG_INT(blockNumber); + + /* Prepare the command */ + pn532_packetbuffer[0] = PN532_COMMAND_INDATAEXCHANGE; + pn532_packetbuffer[1] = 1; /* Card number */ + pn532_packetbuffer[2] = MIFARE_CMD_READ; /* Mifare Read command = 0x30 */ + pn532_packetbuffer[3] = blockNumber; /* Block Number (0..63 for 1K, 0..255 for 4K) */ + + /* Send the command */ + if (HAL(writeCommand)(pn532_packetbuffer, 4)) + { + return 0; + } + + /* Read the response packet */ + HAL(readResponse) + (pn532_packetbuffer, sizeof(pn532_packetbuffer)); + + /* If byte 8 isn't 0x00 we probably have an error */ + if (pn532_packetbuffer[0] != 0x00) + { + return 0; + } + + /* Copy the 16 data bytes to the output buffer */ + /* Block content starts at byte 9 of a valid response */ + memcpy(data, pn532_packetbuffer + 1, 16); + + return 1; +} + +/**************************************************************************/ +/*! + Tries to write an entire 16-bytes data block at the specified block + address. + + @param blockNumber The block number to authenticate. (0..63 for + 1KB cards, and 0..255 for 4KB cards). + @param data The byte array that contains the data to write. + + @returns 1 if everything executed properly, 0 for an error +*/ +/**************************************************************************/ +uint8_t PN532::mifareclassic_WriteDataBlock(uint8_t blockNumber, uint8_t *data) +{ + /* Prepare the first command */ + pn532_packetbuffer[0] = PN532_COMMAND_INDATAEXCHANGE; + pn532_packetbuffer[1] = 1; /* Card number */ + pn532_packetbuffer[2] = MIFARE_CMD_WRITE; /* Mifare Write command = 0xA0 */ + pn532_packetbuffer[3] = blockNumber; /* Block Number (0..63 for 1K, 0..255 for 4K) */ + memcpy(pn532_packetbuffer + 4, data, 16); /* Data Payload */ + + /* Send the command */ + if (HAL(writeCommand)(pn532_packetbuffer, 20)) + { + return 0; + } + + /* Read the response packet */ + if (0 > HAL(readResponse)(pn532_packetbuffer, sizeof(pn532_packetbuffer))) + { + return 0; + } + + /* Check status */ + if (pn532_packetbuffer[0] != 0x00) + { + DMSG("Status code indicates an error: "); + DMSG_HEX(pn532_packetbuffer[0]); + DMSG("\n"); + return 0; + } + + return 1; +} + +/**************************************************************************/ +/*! + Formats a Mifare Classic card to store NDEF Records + + @returns 1 if everything executed properly, 0 for an error +*/ +/**************************************************************************/ +uint8_t PN532::mifareclassic_FormatNDEF(void) +{ + uint8_t sectorbuffer1[16] = {0x14, 0x01, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1}; + uint8_t sectorbuffer2[16] = {0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1}; + uint8_t sectorbuffer3[16] = {0xA0, 0xA1, 0xA2, 0xA3, 0xA4, 0xA5, 0x78, 0x77, 0x88, 0xC1, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; + + // Note 0xA0 0xA1 0xA2 0xA3 0xA4 0xA5 must be used for key A + // for the MAD sector in NDEF records (sector 0) + + // Write block 1 and 2 to the card + if (!(mifareclassic_WriteDataBlock(1, sectorbuffer1))) + return 0; + if (!(mifareclassic_WriteDataBlock(2, sectorbuffer2))) + return 0; + // Write key A and access rights card + if (!(mifareclassic_WriteDataBlock(3, sectorbuffer3))) + return 0; + + // Seems that everything was OK (?!) + return 1; +} + +/**************************************************************************/ +/*! + Writes an NDEF URI Record to the specified sector (1..15) + + Note that this function assumes that the Mifare Classic card is + already formatted to work as an "NFC Forum Tag" and uses a MAD1 + file system. You can use the NXP TagWriter app on Android to + properly format cards for this. + + @param sectorNumber The sector that the URI record should be written + to (can be 1..15 for a 1K card) + @param uriIdentifier The uri identifier code (0 = none, 0x01 = + "http://www.", etc.) + @param url The uri text to write (max 38 characters). + + @returns 1 if everything executed properly, 0 for an error +*/ +/**************************************************************************/ +uint8_t PN532::mifareclassic_WriteNDEFURI(uint8_t sectorNumber, uint8_t uriIdentifier, const char *url) +{ + // Figure out how long the string is + uint8_t len = strlen(url); + + // Make sure we're within a 1K limit for the sector number + if ((sectorNumber < 1) || (sectorNumber > 15)) + return 0; + + // Make sure the URI payload is between 1 and 38 chars + if ((len < 1) || (len > 38)) + return 0; + + // Note 0xD3 0xF7 0xD3 0xF7 0xD3 0xF7 must be used for key A + // in NDEF records + + // Setup the sector buffer (w/pre-formatted TLV wrapper and NDEF message) + uint8_t sectorbuffer1[16] = {0x00, 0x00, 0x03, (uint8_t)(len + 5), 0xD1, 0x01, (uint8_t)(len + 1), 0x55, uriIdentifier, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; + uint8_t sectorbuffer2[16] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; + uint8_t sectorbuffer3[16] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; + uint8_t sectorbuffer4[16] = {0xD3, 0xF7, 0xD3, 0xF7, 0xD3, 0xF7, 0x7F, 0x07, 0x88, 0x40, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; + if (len <= 6) + { + // Unlikely we'll get a url this short, but why not ... + memcpy(sectorbuffer1 + 9, url, len); + sectorbuffer1[len + 9] = 0xFE; + } + else if (len == 7) + { + // 0xFE needs to be wrapped around to next block + memcpy(sectorbuffer1 + 9, url, len); + sectorbuffer2[0] = 0xFE; + } + else if ((len > 7) && (len <= 22)) + { + // Url fits in two blocks + memcpy(sectorbuffer1 + 9, url, 7); + memcpy(sectorbuffer2, url + 7, len - 7); + sectorbuffer2[len - 7] = 0xFE; + } + else if (len == 23) + { + // 0xFE needs to be wrapped around to final block + memcpy(sectorbuffer1 + 9, url, 7); + memcpy(sectorbuffer2, url + 7, len - 7); + sectorbuffer3[0] = 0xFE; + } + else + { + // Url fits in three blocks + memcpy(sectorbuffer1 + 9, url, 7); + memcpy(sectorbuffer2, url + 7, 16); + memcpy(sectorbuffer3, url + 23, len - 23); + sectorbuffer3[len - 23] = 0xFE; + } + + // Now write all three blocks back to the card + if (!(mifareclassic_WriteDataBlock(sectorNumber * 4, sectorbuffer1))) + return 0; + if (!(mifareclassic_WriteDataBlock((sectorNumber * 4) + 1, sectorbuffer2))) + return 0; + if (!(mifareclassic_WriteDataBlock((sectorNumber * 4) + 2, sectorbuffer3))) + return 0; + if (!(mifareclassic_WriteDataBlock((sectorNumber * 4) + 3, sectorbuffer4))) + return 0; + + // Seems that everything was OK (?!) + return 1; +} + +/***** Mifare Ultralight Functions ******/ + +/**************************************************************************/ +/*! + Tries to read an entire 4-bytes page at the specified address. + + @param page The page number (0..63 in most cases) + @param buffer Pointer to the byte array that will hold the + retrieved data (if any) +*/ +/**************************************************************************/ +uint8_t PN532::mifareultralight_ReadPage(uint8_t page, uint8_t *buffer) +{ + /* Prepare the command */ + pn532_packetbuffer[0] = PN532_COMMAND_INDATAEXCHANGE; + pn532_packetbuffer[1] = 1; /* Card number */ + pn532_packetbuffer[2] = MIFARE_CMD_READ; /* Mifare Read command = 0x30 */ + pn532_packetbuffer[3] = page; /* Page Number (0..63 in most cases) */ + + /* Send the command */ + if (HAL(writeCommand)(pn532_packetbuffer, 4)) + { + return 0; + } + + /* Read the response packet */ + HAL(readResponse) + (pn532_packetbuffer, sizeof(pn532_packetbuffer)); + + /* If byte 8 isn't 0x00 we probably have an error */ + if (pn532_packetbuffer[0] == 0x00) + { + /* Copy the 4 data bytes to the output buffer */ + /* Block content starts at byte 9 of a valid response */ + /* Note that the command actually reads 16 bytes or 4 */ + /* pages at a time ... we simply discard the last 12 */ + /* bytes */ + memcpy(buffer, pn532_packetbuffer + 1, 4); + } + else + { + return 0; + } + + // Return OK signal + return 1; +} + +/**************************************************************************/ +/*! + Tries to write an entire 4-bytes data buffer at the specified page + address. + + @param page The page number to write into. (0..63). + @param buffer The byte array that contains the data to write. + + @returns 1 if everything executed properly, 0 for an error +*/ +/**************************************************************************/ +uint8_t PN532::mifareultralight_WritePage(uint8_t page, uint8_t *buffer) +{ + /* Prepare the first command */ + pn532_packetbuffer[0] = PN532_COMMAND_INDATAEXCHANGE; + pn532_packetbuffer[1] = 1; /* Card number */ + pn532_packetbuffer[2] = MIFARE_CMD_WRITE_ULTRALIGHT; /* Mifare UL Write cmd = 0xA2 */ + pn532_packetbuffer[3] = page; /* page Number (0..63) */ + memcpy(pn532_packetbuffer + 4, buffer, 4); /* Data Payload */ + + /* Send the command */ + if (HAL(writeCommand)(pn532_packetbuffer, 8)) + { + return 0; + } + + /* Read the response packet */ + return (0 < HAL(readResponse)(pn532_packetbuffer, sizeof(pn532_packetbuffer))); +} + +/**************************************************************************/ +/*! + @brief Exchanges an APDU with the currently inlisted peer + + @param send Pointer to data to send + @param sendLength Length of the data to send + @param response Pointer to response data + @param responseLength Pointer to the response data length +*/ +/**************************************************************************/ +bool PN532::inDataExchange(uint8_t *send, uint8_t sendLength, uint8_t *response, uint8_t *responseLength) +{ + uint8_t i; + + pn532_packetbuffer[0] = 0x40; // PN532_COMMAND_INDATAEXCHANGE; + pn532_packetbuffer[1] = inListedTag; + + if (HAL(writeCommand)(pn532_packetbuffer, 2, send, sendLength)) + { + return false; + } + + int16_t status = HAL(readResponse)(response, *responseLength, 1000); + if (status < 0) + { + return false; + } + + if ((response[0] & 0x3f) != 0) + { + DMSG("Status code indicates an error\n"); + return false; + } + + uint8_t length = status; + length -= 1; + + if (length > *responseLength) + { + length = *responseLength; // silent truncation... + } + + for (uint8_t i = 0; i < length; i++) + { + response[i] = response[i + 1]; + } + *responseLength = length; + + return true; +} + +/**************************************************************************/ +/*! + This command is used to support basic data exchanges + between the PN532 and a target. + + @param send Pointer to the command buffer + @param sendLength Command length in bytes + @param response Pointer to response data + @param responseLength Pointer to the response data length +*/ +/**************************************************************************/ +bool PN532::inCommunicateThru(uint8_t *send, uint8_t sendLength, uint8_t *response, uint8_t *responseLength) +{ + pn532_packetbuffer[0] = PN532_COMMAND_INCOMMUNICATETHRU; + + if (HAL(writeCommand)(pn532_packetbuffer, 1, send, sendLength)) + { + return false; + } + + int16_t status = HAL(readResponse)(response, *responseLength, 1000); + if (status < 0) + { + return false; + } + + // check status code + if (response[0] != 0x0) + { + DMSG("Status code indicates an error : 0x"); + DMSG_HEX(pn532_packetbuffer[0]); + DMSG("\n"); + return false; + } + + uint8_t length = status; + length -= 1; + + if (length > *responseLength) + { + length = *responseLength; // silent truncation... + } + + for (uint8_t i = 0; i < length; i++) + { + response[i] = response[i + 1]; + } + *responseLength = length; + + return true; +} + +/**************************************************************************/ +/*! + @brief 'InLists' a passive target. PN532 acting as reader/initiator, + peer acting as card/responder. +*/ +/**************************************************************************/ +bool PN532::inListPassiveTarget() +{ + pn532_packetbuffer[0] = PN532_COMMAND_INLISTPASSIVETARGET; + pn532_packetbuffer[1] = 1; + pn532_packetbuffer[2] = 0; + + DMSG("inList passive target\n"); + + if (HAL(writeCommand)(pn532_packetbuffer, 3)) + { + return false; + } + + int16_t status = HAL(readResponse)(pn532_packetbuffer, sizeof(pn532_packetbuffer), 30000); + if (status < 0) + { + return false; + } + + if (pn532_packetbuffer[0] != 1) + { + return false; + } + + inListedTag = pn532_packetbuffer[1]; + + return true; +} + +int8_t PN532::tgInitAsTarget(const uint8_t *command, const uint8_t len, const uint16_t timeout) +{ + + int8_t status = HAL(writeCommand)(command, len); + if (status < 0) + { + return -1; + } + + status = HAL(readResponse)(pn532_packetbuffer, sizeof(pn532_packetbuffer), timeout); + if (status > 0) + { + return 1; + } + else if (PN532_TIMEOUT == status) + { + return 0; + } + else + { + return -2; + } +} + +/** + * Peer to Peer + */ +int8_t PN532::tgInitAsTarget(uint16_t timeout) +{ + const uint8_t command[] = { + PN532_COMMAND_TGINITASTARGET, + 0, + 0x00, 0x00, // SENS_RES + 0x00, 0x00, 0x00, // NFCID1 + 0x40, // SEL_RES + + 0x01, 0xFE, 0x0F, 0xBB, 0xBA, 0xA6, 0xC9, 0x89, // POL_RES + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xFF, 0xFF, + + 0x01, 0xFE, 0x0F, 0xBB, 0xBA, 0xA6, 0xC9, 0x89, 0x00, 0x00, // NFCID3t: Change this to desired value + + 0x0a, 0x46, 0x66, 0x6D, 0x01, 0x01, 0x10, 0x02, 0x02, 0x00, 0x80, // LLCP magic number, version parameter and MIUX + 0x00}; + return tgInitAsTarget(command, sizeof(command), timeout); +} + +int16_t PN532::tgGetData(uint8_t *buf, uint8_t len) +{ + buf[0] = PN532_COMMAND_TGGETDATA; + + if (HAL(writeCommand)(buf, 1)) + { + return -1; + } + + int16_t status = HAL(readResponse)(buf, len, 3000); + if (0 >= status) + { + return status; + } + + uint16_t length = status - 1; + + if (buf[0] != 0) + { + DMSG("status is not ok\n"); + return -5; + } + + for (uint8_t i = 0; i < length; i++) + { + buf[i] = buf[i + 1]; + } + + return length; +} + +bool PN532::tgSetData(const uint8_t *header, uint8_t hlen, const uint8_t *body, uint8_t blen) +{ + if (hlen > (sizeof(pn532_packetbuffer) - 1)) + { + if ((body != 0) || (header == pn532_packetbuffer)) + { + DMSG("tgSetData:buffer too small\n"); + return false; + } + + pn532_packetbuffer[0] = PN532_COMMAND_TGSETDATA; + if (HAL(writeCommand)(pn532_packetbuffer, 1, header, hlen)) + { + return false; + } + } + else + { + for (int8_t i = hlen - 1; i >= 0; i--) + { + pn532_packetbuffer[i + 1] = header[i]; + } + pn532_packetbuffer[0] = PN532_COMMAND_TGSETDATA; + + if (HAL(writeCommand)(pn532_packetbuffer, hlen + 1, body, blen)) + { + return false; + } + } + + if (0 > HAL(readResponse)(pn532_packetbuffer, sizeof(pn532_packetbuffer), 3000)) + { + return false; + } + + if (0 != pn532_packetbuffer[0]) + { + return false; + } + + return true; +} + +int16_t PN532::inRelease(const uint8_t relevantTarget) +{ + + pn532_packetbuffer[0] = PN532_COMMAND_INRELEASE; + pn532_packetbuffer[1] = relevantTarget; + + if (HAL(writeCommand)(pn532_packetbuffer, 2)) + { + return 0; + } + + // read data packet + return HAL(readResponse)(pn532_packetbuffer, sizeof(pn532_packetbuffer)); +} + +/***** FeliCa Functions ******/ +/**************************************************************************/ +/*! + @brief Poll FeliCa card. PN532 acting as reader/initiator, + peer acting as card/responder. + @param[in] systemCode Designation of System Code. When sending FFFFh as System Code, + all FeliCa cards can return response. + @param[in] requestCode Designation of Request Data as follows: + 00h: No Request + 01h: System Code request (to acquire System Code of the card) + 02h: Communication perfomance request + @param[out] idm IDm of the card (8 bytes) + @param[out] pmm PMm of the card (8 bytes) + @param[out] systemCodeResponse System Code of the card (Optional, 2bytes) + @return = 1: A FeliCa card has detected + = 0: No card has detected + < 0: error +*/ +/**************************************************************************/ +int8_t PN532::felica_Polling(uint16_t systemCode, uint8_t requestCode, uint8_t *idm, uint8_t *pmm, uint16_t *systemCodeResponse, uint16_t timeout) +{ + pn532_packetbuffer[0] = PN532_COMMAND_INLISTPASSIVETARGET; + pn532_packetbuffer[1] = 1; + pn532_packetbuffer[2] = 1; + pn532_packetbuffer[3] = FELICA_CMD_POLLING; + pn532_packetbuffer[4] = (systemCode >> 8) & 0xFF; + pn532_packetbuffer[5] = systemCode & 0xFF; + pn532_packetbuffer[6] = requestCode; + pn532_packetbuffer[7] = 0; + + if (HAL(writeCommand)(pn532_packetbuffer, 8)) + { + DMSG("Could not send Polling command\n"); + return -1; + } + + int16_t status = HAL(readResponse)(pn532_packetbuffer, 22, timeout); + if (status < 0) + { + DMSG("Could not receive response\n"); + return -2; + } + + // Check NbTg (pn532_packetbuffer[7]) + if (pn532_packetbuffer[0] == 0) + { + DMSG("No card had detected\n"); + return 0; + } + else if (pn532_packetbuffer[0] != 1) + { + DMSG("Unhandled number of targets inlisted. NbTg: "); + DMSG_HEX(pn532_packetbuffer[7]); + DMSG("\n"); + return -3; + } + + inListedTag = pn532_packetbuffer[1]; + DMSG("Tag number: "); + DMSG_HEX(pn532_packetbuffer[1]); + DMSG("\n"); + + // length check + uint8_t responseLength = pn532_packetbuffer[2]; + if (responseLength != 18 && responseLength != 20) + { + DMSG("Wrong response length\n"); + return -4; + } + + uint8_t i; + for (i = 0; i < 8; ++i) + { + idm[i] = pn532_packetbuffer[4 + i]; + _felicaIDm[i] = pn532_packetbuffer[4 + i]; + pmm[i] = pn532_packetbuffer[12 + i]; + _felicaPMm[i] = pn532_packetbuffer[12 + i]; + } + + if (responseLength == 20) + { + *systemCodeResponse = (uint16_t)((pn532_packetbuffer[20] << 8) + pn532_packetbuffer[21]); + } + + return 1; +} + +/**************************************************************************/ +/*! + @brief Sends FeliCa command to the currently inlisted peer + + @param[in] command FeliCa command packet. (e.g. 00 FF FF 00 00 for Polling command) + @param[in] commandlength Length of the FeliCa command packet. (e.g. 0x05 for above Polling command ) + @param[out] response FeliCa response packet. (e.g. 01 NFCID2(8 bytes) PAD(8 bytes) for Polling response) + @param[out] responselength Length of the FeliCa response packet. (e.g. 0x11 for above Polling command ) + @return = 1: Success + < 0: error +*/ +/**************************************************************************/ +int8_t PN532::felica_SendCommand(const uint8_t *command, uint8_t commandlength, uint8_t *response, uint8_t *responseLength) +{ + if (commandlength > 0xFE) + { + DMSG("Command length too long\n"); + return -1; + } + + pn532_packetbuffer[0] = 0x40; // PN532_COMMAND_INDATAEXCHANGE; + pn532_packetbuffer[1] = inListedTag; + pn532_packetbuffer[2] = commandlength + 1; + + if (HAL(writeCommand)(pn532_packetbuffer, 3, command, commandlength)) + { + DMSG("Could not send FeliCa command\n"); + return -2; + } + + // Wait card response + int16_t status = HAL(readResponse)(pn532_packetbuffer, sizeof(pn532_packetbuffer), 200); + if (status < 0) + { + DMSG("Could not receive response\n"); + return -3; + } + + // Check status (pn532_packetbuffer[0]) + if ((pn532_packetbuffer[0] & 0x3F) != 0) + { + DMSG("Status code indicates an error: "); + DMSG_HEX(pn532_packetbuffer[0]); + DMSG("\n"); + return -4; + } + + // length check + *responseLength = pn532_packetbuffer[1] - 1; + if ((status - 2) != *responseLength) + { + DMSG("Wrong response length\n"); + return -5; + } + + memcpy(response, &pn532_packetbuffer[2], *responseLength); + + return 1; +} + +/**************************************************************************/ +/*! + @brief Sends FeliCa Request Service command + + @param[in] numNode length of the nodeCodeList + @param[in] nodeCodeList Node codes(Big Endian) + @param[out] keyVersions Key Version of each Node (Big Endian) + @return = 1: Success + < 0: error +*/ +/**************************************************************************/ +int8_t PN532::felica_RequestService(uint8_t numNode, uint16_t *nodeCodeList, uint16_t *keyVersions) +{ + if (numNode > FELICA_REQ_SERVICE_MAX_NODE_NUM) + { + DMSG("numNode is too large\n"); + return -1; + } + + uint8_t i, j = 0; + uint8_t cmdLen = 1 + 8 + 1 + 2 * numNode; + uint8_t cmd[cmdLen]; + cmd[j++] = FELICA_CMD_REQUEST_SERVICE; + for (i = 0; i < 8; ++i) + { + cmd[j++] = _felicaIDm[i]; + } + cmd[j++] = numNode; + for (i = 0; i < numNode; ++i) + { + cmd[j++] = nodeCodeList[i] & 0xFF; + cmd[j++] = (nodeCodeList[i] >> 8) & 0xff; + } + + uint8_t response[10 + 2 * numNode]; + uint8_t responseLength; + + if (felica_SendCommand(cmd, cmdLen, response, &responseLength) != 1) + { + DMSG("Request Service command failed\n"); + return -2; + } + + // length check + if (responseLength != 10 + 2 * numNode) + { + DMSG("Request Service command failed (wrong response length)\n"); + return -3; + } + + for (i = 0; i < numNode; i++) + { + keyVersions[i] = (uint16_t)(response[10 + i * 2] + (response[10 + i * 2 + 1] << 8)); + } + return 1; +} + +/**************************************************************************/ +/*! + @brief Sends FeliCa Request Service command + + @param[out] mode Current Mode of the card + @return = 1: Success + < 0: error +*/ +/**************************************************************************/ +int8_t PN532::felica_RequestResponse(uint8_t *mode) +{ + uint8_t cmd[9]; + cmd[0] = FELICA_CMD_REQUEST_RESPONSE; + memcpy(&cmd[1], _felicaIDm, 8); + + uint8_t response[10]; + uint8_t responseLength; + if (felica_SendCommand(cmd, 9, response, &responseLength) != 1) + { + DMSG("Request Response command failed\n"); + return -1; + } + + // length check + if (responseLength != 10) + { + DMSG("Request Response command failed (wrong response length)\n"); + return -2; + } + + *mode = response[9]; + return 1; +} + +/**************************************************************************/ +/*! + @brief Sends FeliCa Read Without Encryption command + + @param[in] numService Length of the serviceCodeList + @param[in] serviceCodeList Service Code List (Big Endian) + @param[in] numBlock Length of the blockList + @param[in] blockList Block List (Big Endian, This API only accepts 2-byte block list element) + @param[out] blockData Block Data + @return = 1: Success + < 0: error +*/ +/**************************************************************************/ +int8_t PN532::felica_ReadWithoutEncryption(uint8_t numService, const uint16_t *serviceCodeList, uint8_t numBlock, const uint16_t *blockList, uint8_t blockData[][16]) +{ + if (numService > FELICA_READ_MAX_SERVICE_NUM) + { + DMSG("numService is too large\n"); + return -1; + } + if (numBlock > FELICA_READ_MAX_BLOCK_NUM) + { + DMSG("numBlock is too large\n"); + return -2; + } + + uint8_t i, j = 0, k; + uint8_t cmdLen = 1 + 8 + 1 + 2 * numService + 1 + 2 * numBlock; + uint8_t cmd[cmdLen]; + cmd[j++] = FELICA_CMD_READ_WITHOUT_ENCRYPTION; + for (i = 0; i < 8; ++i) + { + cmd[j++] = _felicaIDm[i]; + } + cmd[j++] = numService; + for (i = 0; i < numService; ++i) + { + cmd[j++] = serviceCodeList[i] & 0xFF; + cmd[j++] = (serviceCodeList[i] >> 8) & 0xff; + } + cmd[j++] = numBlock; + for (i = 0; i < numBlock; ++i) + { + cmd[j++] = (blockList[i] >> 8) & 0xFF; + cmd[j++] = blockList[i] & 0xff; + } + + uint8_t response[12 + 16 * numBlock]; + uint8_t responseLength; + if (felica_SendCommand(cmd, cmdLen, response, &responseLength) != 1) + { + DMSG("Read Without Encryption command failed\n"); + return -3; + } + + // length check + if (responseLength != 12 + 16 * numBlock) + { + DMSG("Read Without Encryption command failed (wrong response length)\n"); + return -4; + } + + // status flag check + if (response[9] != 0 || response[10] != 0) + { + DMSG("Read Without Encryption command failed (Status Flag: "); + DMSG_HEX(pn532_packetbuffer[9]); + DMSG_HEX(pn532_packetbuffer[10]); + DMSG(")\n"); + return -5; + } + + k = 12; + for (i = 0; i < numBlock; i++) + { + for (j = 0; j < 16; j++) + { + blockData[i][j] = response[k++]; + } + } + + return 1; +} + +/**************************************************************************/ +/*! + @brief Sends FeliCa Write Without Encryption command + + @param[in] numService Length of the serviceCodeList + @param[in] serviceCodeList Service Code List (Big Endian) + @param[in] numBlock Length of the blockList + @param[in] blockList Block List (Big Endian, This API only accepts 2-byte block list element) + @param[in] blockData Block Data (each Block has 16 bytes) + @return = 1: Success + < 0: error +*/ +/**************************************************************************/ +int8_t PN532::felica_WriteWithoutEncryption(uint8_t numService, const uint16_t *serviceCodeList, uint8_t numBlock, const uint16_t *blockList, uint8_t blockData[][16]) +{ + if (numService > FELICA_WRITE_MAX_SERVICE_NUM) + { + DMSG("numService is too large\n"); + return -1; + } + if (numBlock > FELICA_WRITE_MAX_BLOCK_NUM) + { + DMSG("numBlock is too large\n"); + return -2; + } + + uint8_t i, j = 0, k; + uint8_t cmdLen = 1 + 8 + 1 + 2 * numService + 1 + 2 * numBlock + 16 * numBlock; + uint8_t cmd[cmdLen]; + cmd[j++] = FELICA_CMD_WRITE_WITHOUT_ENCRYPTION; + for (i = 0; i < 8; ++i) + { + cmd[j++] = _felicaIDm[i]; + } + cmd[j++] = numService; + for (i = 0; i < numService; ++i) + { + cmd[j++] = serviceCodeList[i] & 0xFF; + cmd[j++] = (serviceCodeList[i] >> 8) & 0xff; + } + cmd[j++] = numBlock; + for (i = 0; i < numBlock; ++i) + { + cmd[j++] = (blockList[i] >> 8) & 0xFF; + cmd[j++] = blockList[i] & 0xff; + } + for (i = 0; i < numBlock; ++i) + { + for (k = 0; k < 16; k++) + { + cmd[j++] = blockData[i][k]; + } + } + + uint8_t response[11]; + uint8_t responseLength; + if (felica_SendCommand(cmd, cmdLen, response, &responseLength) != 1) + { + DMSG("Write Without Encryption command failed\n"); + return -3; + } + + // length check + if (responseLength != 11) + { + DMSG("Write Without Encryption command failed (wrong response length)\n"); + return -4; + } + + // status flag check + if (response[9] != 0 || response[10] != 0) + { + DMSG("Write Without Encryption command failed (Status Flag: "); + DMSG_HEX(pn532_packetbuffer[9]); + DMSG_HEX(pn532_packetbuffer[10]); + DMSG(")\n"); + return -5; + } + + return 1; +} + +/**************************************************************************/ +/*! + @brief Sends FeliCa Request System Code command + + @param[out] numSystemCode Length of the systemCodeList + @param[out] systemCodeList System Code list (Array length should longer than 16) + @return = 1: Success + < 0: error +*/ +/**************************************************************************/ +int8_t PN532::felica_RequestSystemCode(uint8_t *numSystemCode, uint16_t *systemCodeList) +{ + uint8_t cmd[9]; + cmd[0] = FELICA_CMD_REQUEST_SYSTEM_CODE; + memcpy(&cmd[1], _felicaIDm, 8); + + uint8_t response[10 + 2 * 16]; + uint8_t responseLength; + if (felica_SendCommand(cmd, 9, response, &responseLength) != 1) + { + DMSG("Request System Code command failed\n"); + return -1; + } + *numSystemCode = response[9]; + + // length check + if (responseLength < 10 + 2 * *numSystemCode) + { + DMSG("Request System Code command failed (wrong response length)\n"); + return -2; + } + + uint8_t i; + for (i = 0; i < *numSystemCode; i++) + { + systemCodeList[i] = (uint16_t)((response[10 + i * 2] << 8) + response[10 + i * 2 + 1]); + } + + return 1; +} + +/**************************************************************************/ +/*! + @brief Release FeliCa card + @return = 1: Success + < 0: error +*/ +/**************************************************************************/ +int8_t PN532::felica_Release() +{ + // InRelease + pn532_packetbuffer[0] = PN532_COMMAND_INRELEASE; + pn532_packetbuffer[1] = 0x00; // All target + DMSG("Release all FeliCa target\n"); + + if (HAL(writeCommand)(pn532_packetbuffer, 2)) + { + DMSG("No ACK\n"); + return -1; // no ACK + } + + // Wait card response + int16_t frameLength = HAL(readResponse)(pn532_packetbuffer, sizeof(pn532_packetbuffer), 1000); + if (frameLength < 0) + { + DMSG("Could not receive response\n"); + return -2; + } + + // Check status (pn532_packetbuffer[0]) + if ((pn532_packetbuffer[0] & 0x3F) != 0) + { + DMSG("Status code indicates an error: "); + DMSG_HEX(pn532_packetbuffer[7]); + DMSG("\n"); + return -3; + } + + return 1; +} diff --git a/lib/PN532/src/PN532.h b/lib/PN532/src/PN532.h new file mode 100644 index 0000000..ef7b583 --- /dev/null +++ b/lib/PN532/src/PN532.h @@ -0,0 +1,212 @@ +/**************************************************************************/ +/*! + @file PN532.h + @author Adafruit Industries & Seeed Studio + @license BSD +*/ +/**************************************************************************/ + +#ifndef __PN532_H__ +#define __PN532_H__ + +#include +#include "PN532Interface.h" + +// PN532 Commands +#define PN532_COMMAND_DIAGNOSE (0x00) +#define PN532_COMMAND_GETFIRMWAREVERSION (0x02) +#define PN532_COMMAND_GETGENERALSTATUS (0x04) +#define PN532_COMMAND_READREGISTER (0x06) +#define PN532_COMMAND_WRITEREGISTER (0x08) +#define PN532_COMMAND_READGPIO (0x0C) +#define PN532_COMMAND_WRITEGPIO (0x0E) +#define PN532_COMMAND_SETSERIALBAUDRATE (0x10) +#define PN532_COMMAND_SETPARAMETERS (0x12) +#define PN532_COMMAND_SAMCONFIGURATION (0x14) +#define PN532_COMMAND_POWERDOWN (0x16) +#define PN532_COMMAND_RFCONFIGURATION (0x32) +#define PN532_COMMAND_RFREGULATIONTEST (0x58) +#define PN532_COMMAND_INJUMPFORDEP (0x56) +#define PN532_COMMAND_INJUMPFORPSL (0x46) +#define PN532_COMMAND_INLISTPASSIVETARGET (0x4A) +#define PN532_COMMAND_INATR (0x50) +#define PN532_COMMAND_INPSL (0x4E) +#define PN532_COMMAND_INDATAEXCHANGE (0x40) +#define PN532_COMMAND_INCOMMUNICATETHRU (0x42) +#define PN532_COMMAND_INDESELECT (0x44) +#define PN532_COMMAND_INRELEASE (0x52) +#define PN532_COMMAND_INSELECT (0x54) +#define PN532_COMMAND_INAUTOPOLL (0x60) +#define PN532_COMMAND_TGINITASTARGET (0x8C) +#define PN532_COMMAND_TGSETGENERALBYTES (0x92) +#define PN532_COMMAND_TGGETDATA (0x86) +#define PN532_COMMAND_TGSETDATA (0x8E) +#define PN532_COMMAND_TGSETMETADATA (0x94) +#define PN532_COMMAND_TGGETINITIATORCOMMAND (0x88) +#define PN532_COMMAND_TGRESPONSETOINITIATOR (0x90) +#define PN532_COMMAND_TGGETTARGETSTATUS (0x8A) + +#define PN532_RESPONSE_INDATAEXCHANGE (0x41) +#define PN532_RESPONSE_INLISTPASSIVETARGET (0x4B) + +#define PN532_MIFARE_ISO14443A (0x00) + +// Mifare Commands +#define MIFARE_CMD_AUTH_A (0x60) +#define MIFARE_CMD_AUTH_B (0x61) +#define MIFARE_CMD_READ (0x30) +#define MIFARE_CMD_WRITE (0xA0) +#define MIFARE_CMD_WRITE_ULTRALIGHT (0xA2) +#define MIFARE_CMD_TRANSFER (0xB0) +#define MIFARE_CMD_DECREMENT (0xC0) +#define MIFARE_CMD_INCREMENT (0xC1) +#define MIFARE_CMD_STORE (0xC2) + +// FeliCa Commands +#define FELICA_CMD_POLLING (0x00) +#define FELICA_CMD_REQUEST_SERVICE (0x02) +#define FELICA_CMD_REQUEST_RESPONSE (0x04) +#define FELICA_CMD_READ_WITHOUT_ENCRYPTION (0x06) +#define FELICA_CMD_WRITE_WITHOUT_ENCRYPTION (0x08) +#define FELICA_CMD_REQUEST_SYSTEM_CODE (0x0C) + +// Prefixes for NDEF Records (to identify record type) +#define NDEF_URIPREFIX_NONE (0x00) +#define NDEF_URIPREFIX_HTTP_WWWDOT (0x01) +#define NDEF_URIPREFIX_HTTPS_WWWDOT (0x02) +#define NDEF_URIPREFIX_HTTP (0x03) +#define NDEF_URIPREFIX_HTTPS (0x04) +#define NDEF_URIPREFIX_TEL (0x05) +#define NDEF_URIPREFIX_MAILTO (0x06) +#define NDEF_URIPREFIX_FTP_ANONAT (0x07) +#define NDEF_URIPREFIX_FTP_FTPDOT (0x08) +#define NDEF_URIPREFIX_FTPS (0x09) +#define NDEF_URIPREFIX_SFTP (0x0A) +#define NDEF_URIPREFIX_SMB (0x0B) +#define NDEF_URIPREFIX_NFS (0x0C) +#define NDEF_URIPREFIX_FTP (0x0D) +#define NDEF_URIPREFIX_DAV (0x0E) +#define NDEF_URIPREFIX_NEWS (0x0F) +#define NDEF_URIPREFIX_TELNET (0x10) +#define NDEF_URIPREFIX_IMAP (0x11) +#define NDEF_URIPREFIX_RTSP (0x12) +#define NDEF_URIPREFIX_URN (0x13) +#define NDEF_URIPREFIX_POP (0x14) +#define NDEF_URIPREFIX_SIP (0x15) +#define NDEF_URIPREFIX_SIPS (0x16) +#define NDEF_URIPREFIX_TFTP (0x17) +#define NDEF_URIPREFIX_BTSPP (0x18) +#define NDEF_URIPREFIX_BTL2CAP (0x19) +#define NDEF_URIPREFIX_BTGOEP (0x1A) +#define NDEF_URIPREFIX_TCPOBEX (0x1B) +#define NDEF_URIPREFIX_IRDAOBEX (0x1C) +#define NDEF_URIPREFIX_FILE (0x1D) +#define NDEF_URIPREFIX_URN_EPC_ID (0x1E) +#define NDEF_URIPREFIX_URN_EPC_TAG (0x1F) +#define NDEF_URIPREFIX_URN_EPC_PAT (0x20) +#define NDEF_URIPREFIX_URN_EPC_RAW (0x21) +#define NDEF_URIPREFIX_URN_EPC (0x22) +#define NDEF_URIPREFIX_URN_NFC (0x23) + +#define PN532_GPIO_VALIDATIONBIT (0x80) +#define PN532_GPIO_P30 (0) +#define PN532_GPIO_P31 (1) +#define PN532_GPIO_P32 (2) +#define PN532_GPIO_P33 (3) +#define PN532_GPIO_P34 (4) +#define PN532_GPIO_P35 (5) + +// FeliCa consts +#define FELICA_READ_MAX_SERVICE_NUM 16 +#define FELICA_READ_MAX_BLOCK_NUM 12 // for typical FeliCa card +#define FELICA_WRITE_MAX_SERVICE_NUM 16 +#define FELICA_WRITE_MAX_BLOCK_NUM 10 // for typical FeliCa card +#define FELICA_REQ_SERVICE_MAX_NODE_NUM 32 + +class PN532 +{ +public: + PN532(PN532Interface &interface); + + void begin(void); + + // Generic PN532 functions + bool SAMConfig(void); + uint32_t getFirmwareVersion(void); + uint32_t readRegister(uint16_t reg); + uint32_t writeRegister(uint16_t reg, uint8_t val); + bool writeGPIO(uint8_t pinstate); + uint8_t readGPIO(void); + bool setPassiveActivationRetries(uint8_t maxRetries); + bool setRFField(uint8_t autoRFCA, uint8_t rFOnOff); + bool powerDownMode(); + + /** + * @brief Init PN532 as a target + * @param timeout max time to wait, 0 means no timeout + * @return > 0 success + * = 0 timeout + * < 0 failed + */ + int8_t tgInitAsTarget(uint16_t timeout = 0); + int8_t tgInitAsTarget(const uint8_t *command, const uint8_t len, const uint16_t timeout = 0); + + int16_t tgGetData(uint8_t *buf, uint8_t len); + bool tgSetData(const uint8_t *header, uint8_t hlen, const uint8_t *body = 0, uint8_t blen = 0); + + int16_t inRelease(const uint8_t relevantTarget = 0); + + // ISO14443A functions + bool inListPassiveTarget(); + bool startPassiveTargetIDDetection(uint8_t cardbaudrate); + bool readPassiveTargetID(uint8_t cardbaudrate, uint8_t *uid, uint8_t *uidLength, uint16_t timeout = 1000, bool inlist = false); + bool inDataExchange(uint8_t *send, uint8_t sendLength, uint8_t *response, uint8_t *responseLength); + bool inCommunicateThru(uint8_t *send, uint8_t sendLength, uint8_t *response, uint8_t *responseLength); + + // Mifare Classic functions + bool mifareclassic_IsFirstBlock(uint32_t uiBlock); + bool mifareclassic_IsTrailerBlock(uint32_t uiBlock); + uint8_t mifareclassic_AuthenticateBlock(uint8_t *uid, uint8_t uidLen, uint32_t blockNumber, uint8_t keyNumber, uint8_t *keyData); + uint8_t mifareclassic_ReadDataBlock(uint8_t blockNumber, uint8_t *data); + uint8_t mifareclassic_WriteDataBlock(uint8_t blockNumber, uint8_t *data); + uint8_t mifareclassic_FormatNDEF(void); + uint8_t mifareclassic_WriteNDEFURI(uint8_t sectorNumber, uint8_t uriIdentifier, const char *url); + + // Mifare Ultralight functions + uint8_t mifareultralight_ReadPage(uint8_t page, uint8_t *buffer); + uint8_t mifareultralight_WritePage(uint8_t page, uint8_t *buffer); + + // FeliCa Functions + int8_t felica_Polling(uint16_t systemCode, uint8_t requestCode, uint8_t *idm, uint8_t *pmm, uint16_t *systemCodeResponse, uint16_t timeout = 1000); + int8_t felica_SendCommand(const uint8_t *command, uint8_t commandlength, uint8_t *response, uint8_t *responseLength); + int8_t felica_RequestService(uint8_t numNode, uint16_t *nodeCodeList, uint16_t *keyVersions); + int8_t felica_RequestResponse(uint8_t *mode); + int8_t felica_ReadWithoutEncryption(uint8_t numService, const uint16_t *serviceCodeList, uint8_t numBlock, const uint16_t *blockList, uint8_t blockData[][16]); + int8_t felica_WriteWithoutEncryption(uint8_t numService, const uint16_t *serviceCodeList, uint8_t numBlock, const uint16_t *blockList, uint8_t blockData[][16]); + int8_t felica_RequestSystemCode(uint8_t *numSystemCode, uint16_t *systemCodeList); + int8_t felica_Release(); + + // Help functions to display formatted text + static void PrintHex(const uint8_t *data, const uint32_t numBytes); + static void PrintHexChar(const uint8_t *pbtData, const uint32_t numBytes); + + uint8_t *getBuffer(uint8_t *len) + { + *len = sizeof(pn532_packetbuffer) - 4; + return pn532_packetbuffer; + }; + +private: + uint8_t _uid[7]; // ISO14443A uid + uint8_t _uidLen; // uid len + uint8_t _key[6]; // Mifare Classic key + uint8_t inListedTag; // Tg number of inlisted tag. + uint8_t _felicaIDm[8]; // FeliCa IDm (NFCID2) + uint8_t _felicaPMm[8]; // FeliCa PMm (PAD) + + uint8_t pn532_packetbuffer[64]; + + PN532Interface *_interface; +}; + +#endif diff --git a/lib/PN532/src/PN532Interface.h b/lib/PN532/src/PN532Interface.h new file mode 100644 index 0000000..14f590a --- /dev/null +++ b/lib/PN532/src/PN532Interface.h @@ -0,0 +1,56 @@ + + +#ifndef __PN532_INTERFACE_H__ +#define __PN532_INTERFACE_H__ + +#include + +#define PN532_PREAMBLE (0x00) +#define PN532_STARTCODE1 (0x00) +#define PN532_STARTCODE2 (0xFF) +#define PN532_POSTAMBLE (0x00) + +#define PN532_HOSTTOPN532 (0xD4) +#define PN532_PN532TOHOST (0xD5) + +#define PN532_ACK_WAIT_TIME (10) // ms, timeout of waiting for ACK + +#define PN532_INVALID_ACK (-1) +#define PN532_TIMEOUT (-2) +#define PN532_INVALID_FRAME (-3) +#define PN532_NO_SPACE (-4) + +#define REVERSE_BITS_ORDER(b) b = (b & 0xF0) >> 4 | (b & 0x0F) << 4; \ + b = (b & 0xCC) >> 2 | (b & 0x33) << 2; \ + b = (b & 0xAA) >> 1 | (b & 0x55) << 1 + +class PN532Interface +{ +public: + virtual void begin() = 0; + virtual void wakeup() = 0; + + /** + * @brief write a command and check ack + * @param header packet header + * @param hlen length of header + * @param body packet body + * @param blen length of body + * @return 0 success + * not 0 failed + */ + virtual int8_t writeCommand(const uint8_t *header, uint8_t hlen, const uint8_t *body = 0, uint8_t blen = 0) = 0; + + /** + * @brief read the response of a command, strip prefix and suffix + * @param buf to contain the response data + * @param len lenght to read + * @param timeout max time to wait, 0 means no timeout + * @return >=0 length of response without prefix and suffix + * <0 failed to read response + */ + virtual int16_t readResponse(uint8_t buf[], uint8_t len, uint16_t timeout = 1000) = 0; +}; + +#endif + diff --git a/lib/PN532/src/PN532_HSU.cpp b/lib/PN532/src/PN532_HSU.cpp new file mode 100644 index 0000000..f12b6d8 --- /dev/null +++ b/lib/PN532/src/PN532_HSU.cpp @@ -0,0 +1,226 @@ +#ifdef NFC_INTERFACE_HSU + +#include "PN532_HSU.h" +#include "PN532_debug.h" + +PN532_HSU::PN532_HSU(HardwareSerial &serial) +{ + _serial = &serial; + command = 0; +} + +void PN532_HSU::begin() +{ + _serial->begin(115200); +} + +void PN532_HSU::wakeup() +{ + _serial->write(0x55); + _serial->write(0x55); + _serial->write(uint8_t(0x00)); + _serial->write(uint8_t(0x00)); + _serial->write(uint8_t(0x00)); + + /** dump serial buffer */ + if (_serial->available()) + { + DMSG("Dump serial buffer: "); + } + while (_serial->available()) + { + uint8_t ret = _serial->read(); + DMSG_HEX(ret); + } +} + +int8_t PN532_HSU::writeCommand(const uint8_t *header, uint8_t hlen, const uint8_t *body, uint8_t blen) +{ + + /** dump serial buffer */ + if (_serial->available()) + { + DMSG("Dump serial buffer: "); + } + while (_serial->available()) + { + uint8_t ret = _serial->read(); + DMSG_HEX(ret); + } + + command = header[0]; + + _serial->write(uint8_t(PN532_PREAMBLE)); + _serial->write(uint8_t(PN532_STARTCODE1)); + _serial->write(uint8_t(PN532_STARTCODE2)); + + uint8_t length = hlen + blen + 1; // length of data field: TFI + DATA + _serial->write(length); + _serial->write(~length + 1); // checksum of length + + _serial->write(PN532_HOSTTOPN532); + uint8_t sum = PN532_HOSTTOPN532; // sum of TFI + DATA + + DMSG("\nWrite: "); + + _serial->write(header, hlen); + for (uint8_t i = 0; i < hlen; i++) + { + sum += header[i]; + + DMSG_HEX(header[i]); + } + + _serial->write(body, blen); + for (uint8_t i = 0; i < blen; i++) + { + sum += body[i]; + + DMSG_HEX(body[i]); + } + + uint8_t checksum = ~sum + 1; // checksum of TFI + DATA + _serial->write(checksum); + _serial->write(uint8_t(PN532_POSTAMBLE)); + + return readAckFrame(); +} + +int16_t PN532_HSU::readResponse(uint8_t buf[], uint8_t len, uint16_t timeout) +{ + uint8_t tmp[3]; + + DMSG("\nRead: "); + + /** Frame Preamble and Start Code */ + if (receive(tmp, 3, timeout) <= 0) + { + return PN532_TIMEOUT; + } + if (0 != tmp[0] || 0 != tmp[1] || 0xFF != tmp[2]) + { + DMSG("Preamble error"); + return PN532_INVALID_FRAME; + } + + /** receive length and check */ + uint8_t length[2]; + if (receive(length, 2, timeout) <= 0) + { + return PN532_TIMEOUT; + } + if (0 != (uint8_t)(length[0] + length[1])) + { + DMSG("Length error"); + return PN532_INVALID_FRAME; + } + length[0] -= 2; + if (length[0] > len) + { + return PN532_NO_SPACE; + } + + /** receive command byte */ + uint8_t cmd = command + 1; // response command + if (receive(tmp, 2, timeout) <= 0) + { + return PN532_TIMEOUT; + } + if (PN532_PN532TOHOST != tmp[0] || cmd != tmp[1]) + { + DMSG("Command error"); + return PN532_INVALID_FRAME; + } + + if (receive(buf, length[0], timeout) != length[0]) + { + return PN532_TIMEOUT; + } + uint8_t sum = PN532_PN532TOHOST + cmd; + for (uint8_t i = 0; i < length[0]; i++) + { + sum += buf[i]; + } + + /** checksum and postamble */ + if (receive(tmp, 2, timeout) <= 0) + { + return PN532_TIMEOUT; + } + if (0 != (uint8_t)(sum + tmp[0]) || 0 != tmp[1]) + { + DMSG("Checksum error"); + return PN532_INVALID_FRAME; + } + + return length[0]; +} + +int8_t PN532_HSU::readAckFrame() +{ + const uint8_t PN532_ACK[] = {0, 0, 0xFF, 0, 0xFF, 0}; + uint8_t ackBuf[sizeof(PN532_ACK)]; + + DMSG("\nAck: "); + + if (receive(ackBuf, sizeof(PN532_ACK), PN532_ACK_WAIT_TIME) <= 0) + { + DMSG("Timeout\n"); + return PN532_TIMEOUT; + } + + if (memcmp(ackBuf, PN532_ACK, sizeof(PN532_ACK))) + { + DMSG("Invalid\n"); + return PN532_INVALID_ACK; + } + return 0; +} + +/** + @brief receive data . + @param buf --> return value buffer. + len --> length expect to receive. + timeout --> time of reveiving + @retval number of received bytes, 0 means no data received. +*/ +int8_t PN532_HSU::receive(uint8_t *buf, int len, uint16_t timeout) +{ + int read_bytes = 0; + int ret; + unsigned long start_millis; + + while (read_bytes < len) + { + start_millis = millis(); + do + { + ret = _serial->read(); + if (ret >= 0) + { + break; + } + } while ((timeout == 0) || ((millis() - start_millis) < timeout)); + + if (ret < 0) + { + if (read_bytes) + { + return read_bytes; + } + else + { + return PN532_TIMEOUT; + } + buf[read_bytes] = (uint8_t)ret; + DMSG_HEX(ret); + read_bytes++; + } + buf[read_bytes] = (uint8_t)ret; + DMSG_HEX(ret); + read_bytes++; + } + return read_bytes; +} + +#endif // NFC_INTERFACE_HSU \ No newline at end of file diff --git a/lib/PN532/src/PN532_HSU.h b/lib/PN532/src/PN532_HSU.h new file mode 100644 index 0000000..e230709 --- /dev/null +++ b/lib/PN532/src/PN532_HSU.h @@ -0,0 +1,31 @@ + +#ifndef __PN532_HSU_H__ +#define __PN532_HSU_H__ + +#include "PN532Interface.h" +#include "Arduino.h" + +#define PN532_HSU_DEBUG + +#define PN532_HSU_READ_TIMEOUT (1000) + +class PN532_HSU : public PN532Interface +{ +public: + PN532_HSU(HardwareSerial &serial); + + void begin(); + void wakeup(); + virtual int8_t writeCommand(const uint8_t *header, uint8_t hlen, const uint8_t *body = 0, uint8_t blen = 0); + int16_t readResponse(uint8_t buf[], uint8_t len, uint16_t timeout); + +private: + HardwareSerial *_serial; + uint8_t command; + + int8_t readAckFrame(); + + int8_t receive(uint8_t *buf, int len, uint16_t timeout = PN532_HSU_READ_TIMEOUT); +}; + +#endif diff --git a/lib/PN532/src/PN532_I2C.cpp b/lib/PN532/src/PN532_I2C.cpp new file mode 100644 index 0000000..24a62c8 --- /dev/null +++ b/lib/PN532/src/PN532_I2C.cpp @@ -0,0 +1,258 @@ +#ifdef NFC_INTERFACE_I2C + +/** + * @modified picospuch + */ + +#include "PN532_I2C.h" +#include "PN532_debug.h" +#include "Arduino.h" + +#define PN532_I2C_ADDRESS (0x48 >> 1) + +PN532_I2C::PN532_I2C(TwoWire &wire) +{ + _wire = &wire; + command = 0; +} + +void PN532_I2C::begin() +{ + _wire->begin(); +} + +void PN532_I2C::wakeup() +{ + delay(500); // wait for all ready to manipulate pn532 +} + +int8_t PN532_I2C::writeCommand(const uint8_t *header, uint8_t hlen, const uint8_t *body, uint8_t blen) +{ + command = header[0]; + _wire->beginTransmission(PN532_I2C_ADDRESS); + + write(PN532_PREAMBLE); + write(PN532_STARTCODE1); + write(PN532_STARTCODE2); + + uint8_t length = hlen + blen + 1; // length of data field: TFI + DATA + write(length); + write(~length + 1); // checksum of length + + write(PN532_HOSTTOPN532); + uint8_t sum = PN532_HOSTTOPN532; // sum of TFI + DATA + + DMSG("write: "); + + for (uint8_t i = 0; i < hlen; i++) + { + if (write(header[i])) + { + sum += header[i]; + + DMSG_HEX(header[i]); + } + else + { + DMSG("\nToo many data to send, I2C doesn't support such a big packet\n"); // I2C max packet: 32 bytes + return PN532_INVALID_FRAME; + } + } + + for (uint8_t i = 0; i < blen; i++) + { + if (write(body[i])) + { + sum += body[i]; + + DMSG_HEX(body[i]); + } + else + { + DMSG("\nToo many data to send, I2C doesn't support such a big packet\n"); // I2C max packet: 32 bytes + return PN532_INVALID_FRAME; + } + } + + uint8_t checksum = ~sum + 1; // checksum of TFI + DATA + write(checksum); + write(PN532_POSTAMBLE); + + _wire->endTransmission(); + + DMSG('\n'); + + return readAckFrame(); +} + +int16_t PN532_I2C::getResponseLength(uint8_t buf[], uint8_t len, uint16_t timeout) +{ + const uint8_t PN532_NACK[] = {0, 0, 0xFF, 0xFF, 0, 0}; + uint16_t time = 0; + + do + { + if (_wire->requestFrom(PN532_I2C_ADDRESS, 6)) + { + if (read() & 1) + { // check first byte --- status + break; // PN532 is ready + } + } + + delay(1); + time++; + if ((0 != timeout) && (time > timeout)) + { + return -1; + } + } while (1); + + if (0x00 != read() || // PREAMBLE + 0x00 != read() || // STARTCODE1 + 0xFF != read() // STARTCODE2 + ) + { + + return PN532_INVALID_FRAME; + } + + uint8_t length = read(); + + // request for last respond msg again + _wire->beginTransmission(PN532_I2C_ADDRESS); + for (uint16_t i = 0; i < sizeof(PN532_NACK); ++i) + { + write(PN532_NACK[i]); + } + _wire->endTransmission(); + + return length; +} + +int16_t PN532_I2C::readResponse(uint8_t buf[], uint8_t len, uint16_t timeout) +{ + uint16_t time = 0; + uint8_t length; + + length = getResponseLength(buf, len, timeout); + + // [RDY] 00 00 FF LEN LCS (TFI PD0 ... PDn) DCS 00 + do + { + if (_wire->requestFrom(PN532_I2C_ADDRESS, 6 + length + 2)) + { + if (read() & 1) + { // check first byte --- status + break; // PN532 is ready + } + } + + delay(1); + time++; + if ((0 != timeout) && (time > timeout)) + { + return -1; + } + } while (1); + + if (0x00 != read() || // PREAMBLE + 0x00 != read() || // STARTCODE1 + 0xFF != read() // STARTCODE2 + ) + { + + return PN532_INVALID_FRAME; + } + + length = read(); + + if (0 != (uint8_t)(length + read())) + { // checksum of length + return PN532_INVALID_FRAME; + } + + uint8_t cmd = command + 1; // response command + if (PN532_PN532TOHOST != read() || (cmd) != read()) + { + return PN532_INVALID_FRAME; + } + + length -= 2; + if (length > len) + { + return PN532_NO_SPACE; // not enough space + } + + DMSG("read: "); + DMSG_HEX(cmd); + + uint8_t sum = PN532_PN532TOHOST + cmd; + for (uint8_t i = 0; i < length; i++) + { + buf[i] = read(); + sum += buf[i]; + + DMSG_HEX(buf[i]); + } + DMSG('\n'); + + uint8_t checksum = read(); + if (0 != (uint8_t)(sum + checksum)) + { + DMSG("checksum is not ok\n"); + return PN532_INVALID_FRAME; + } + read(); // POSTAMBLE + + return length; +} + +int8_t PN532_I2C::readAckFrame() +{ + const uint8_t PN532_ACK[] = {0, 0, 0xFF, 0, 0xFF, 0}; + uint8_t ackBuf[sizeof(PN532_ACK)]; + + DMSG("wait for ack at : "); + DMSG(millis()); + DMSG('\n'); + + uint16_t time = 0; + do + { + if (_wire->requestFrom(PN532_I2C_ADDRESS, (int)sizeof(PN532_ACK) + 1)) + { + if (read() & 1) + { // check first byte --- status + break; // PN532 is ready + } + } + + delay(1); + time++; + if (time > PN532_ACK_WAIT_TIME) + { + DMSG("Time out when waiting for ACK\n"); + return PN532_TIMEOUT; + } + } while (1); + + DMSG("ready at : "); + DMSG(millis()); + DMSG('\n'); + + for (uint8_t i = 0; i < sizeof(PN532_ACK); i++) + { + ackBuf[i] = read(); + } + + if (memcmp(ackBuf, PN532_ACK, sizeof(PN532_ACK))) + { + DMSG("Invalid ACK\n"); + return PN532_INVALID_ACK; + } + + return 0; +} + +#endif // NFC_INTERFACE_I2C \ No newline at end of file diff --git a/lib/PN532/src/PN532_I2C.h b/lib/PN532/src/PN532_I2C.h new file mode 100644 index 0000000..3502180 --- /dev/null +++ b/lib/PN532/src/PN532_I2C.h @@ -0,0 +1,47 @@ +/** + * @modified picospuch + */ + +#ifndef __PN532_I2C_H__ +#define __PN532_I2C_H__ + +#include +#include "PN532Interface.h" + +class PN532_I2C : public PN532Interface +{ +public: + PN532_I2C(TwoWire &wire); + + void begin(); + void wakeup(); + virtual int8_t writeCommand(const uint8_t *header, uint8_t hlen, const uint8_t *body = 0, uint8_t blen = 0); + int16_t readResponse(uint8_t buf[], uint8_t len, uint16_t timeout); + +private: + TwoWire *_wire; + uint8_t command; + + int8_t readAckFrame(); + int16_t getResponseLength(uint8_t buf[], uint8_t len, uint16_t timeout); + + inline uint8_t write(uint8_t data) + { +#if ARDUINO >= 100 + return _wire->write(data); +#else + return _wire->send(data); +#endif + } + + inline uint8_t read() + { +#if ARDUINO >= 100 + return _wire->read(); +#else + return _wire->receive(); +#endif + } +}; + +#endif diff --git a/lib/PN532/src/PN532_SPI.cpp b/lib/PN532/src/PN532_SPI.cpp new file mode 100644 index 0000000..929b4ed --- /dev/null +++ b/lib/PN532/src/PN532_SPI.cpp @@ -0,0 +1,233 @@ +#ifdef NFC_INTERFACE_SPI + +#include "PN532_SPI.h" +#include "PN532_debug.h" +#include "Arduino.h" + +#define STATUS_READ 2 +#define DATA_WRITE 1 +#define DATA_READ 3 + +PN532_SPI::PN532_SPI(SPIClass &spi, uint8_t ss) +{ + command = 0; + _spi = &spi; + _ss = ss; +} + +void PN532_SPI::begin() +{ + pinMode(_ss, OUTPUT); + + _spi->begin(); + _spi->beginTransaction(SPISettings(1000000, LSBFIRST, SPI_MODE0)); +#if defined(ARDUINO_XIAO_RA4M1) || defined(ARDUINO_ARCH_RP2040) || defined(ARDUINO_ARCH_RP2350) +#else + // _spi->setDataMode(SPI_MODE0); // PN532 only supports mode0 + // _spi->setBitOrder(LSBFIRST); +#if defined __SAM3X8E__ + /** DUE spi library does not support SPI_CLOCK_DIV8 macro */ + _spi->setClockDivider(42); // set clock 2MHz(max: 5MHz) +#elif defined __SAMD21G18A__ + /** M0 spi library does not support SPI_CLOCK_DIV8 macro */ + _spi->setClockDivider(24); // set clock 2MHz(max: 5MHz) +#else + _spi->setClockDivider(SPI_CLOCK_DIV8); // set clock 2MHz(max: 5MHz) +#endif +#endif +} + +void PN532_SPI::wakeup() +{ + digitalWrite(_ss, LOW); + delay(2); + digitalWrite(_ss, HIGH); +} + +int8_t PN532_SPI::writeCommand(const uint8_t *header, uint8_t hlen, const uint8_t *body, uint8_t blen) +{ + command = header[0]; + writeFrame(header, hlen, body, blen); + + uint8_t timeout = PN532_ACK_WAIT_TIME; + while (!isReady()) + { + delay(1); + timeout--; + if (0 == timeout) + { + DMSG("Time out when waiting for ACK\n"); + return -2; + } + } + if (readAckFrame()) + { + DMSG("Invalid ACK\n"); + return PN532_INVALID_ACK; + } + return 0; +} + +int16_t PN532_SPI::readResponse(uint8_t buf[], uint8_t len, uint16_t timeout) +{ + uint16_t time = 0; + while (!isReady()) + { + delay(1); + time++; + if (time > timeout) + { + return PN532_TIMEOUT; + } + } + + digitalWrite(_ss, LOW); + delay(1); + + int16_t result; + do + { + write(DATA_READ); + + if (0x00 != read() || // PREAMBLE + 0x00 != read() || // STARTCODE1 + 0xFF != read() // STARTCODE2 + ) + { + + result = PN532_INVALID_FRAME; + break; + } + + uint8_t length = read(); + if (0 != (uint8_t)(length + read())) + { // checksum of length + result = PN532_INVALID_FRAME; + break; + } + + uint8_t cmd = command + 1; // response command + if (PN532_PN532TOHOST != read() || (cmd) != read()) + { + result = PN532_INVALID_FRAME; + break; + } + + DMSG("read: "); + DMSG_HEX(cmd); + + length -= 2; + if (length > len) + { + for (uint8_t i = 0; i < length; i++) + { + DMSG_HEX(read()); // dump message + } + DMSG("\nNot enough space\n"); + read(); + read(); + result = PN532_NO_SPACE; // not enough space + break; + } + + uint8_t sum = PN532_PN532TOHOST + cmd; + for (uint8_t i = 0; i < length; i++) + { + buf[i] = read(); + sum += buf[i]; + + DMSG_HEX(buf[i]); + } + DMSG('\n'); + + uint8_t checksum = read(); + if (0 != (uint8_t)(sum + checksum)) + { + DMSG("checksum is not ok\n"); + result = PN532_INVALID_FRAME; + break; + } + read(); // POSTAMBLE + + result = length; + } while (0); + + digitalWrite(_ss, HIGH); + + return result; +} + +bool PN532_SPI::isReady() +{ + digitalWrite(_ss, LOW); + + write(STATUS_READ); + uint8_t status = read() & 1; + digitalWrite(_ss, HIGH); + return status; +} + +void PN532_SPI::writeFrame(const uint8_t *header, uint8_t hlen, const uint8_t *body, uint8_t blen) +{ + digitalWrite(_ss, LOW); + delay(2); // wake up PN532 + + write(DATA_WRITE); + write(PN532_PREAMBLE); + write(PN532_STARTCODE1); + write(PN532_STARTCODE2); + + uint8_t length = hlen + blen + 1; // length of data field: TFI + DATA + write(length); + write(~length + 1); // checksum of length + + write(PN532_HOSTTOPN532); + uint8_t sum = PN532_HOSTTOPN532; // sum of TFI + DATA + + DMSG("write: "); + + for (uint8_t i = 0; i < hlen; i++) + { + write(header[i]); + sum += header[i]; + + DMSG_HEX(header[i]); + } + for (uint8_t i = 0; i < blen; i++) + { + write(body[i]); + sum += body[i]; + + DMSG_HEX(body[i]); + } + + uint8_t checksum = ~sum + 1; // checksum of TFI + DATA + write(checksum); + write(PN532_POSTAMBLE); + + digitalWrite(_ss, HIGH); + + DMSG('\n'); +} + +int8_t PN532_SPI::readAckFrame() +{ + const uint8_t PN532_ACK[] = {0, 0, 0xFF, 0, 0xFF, 0}; + + uint8_t ackBuf[sizeof(PN532_ACK)]; + + digitalWrite(_ss, LOW); + delay(1); + write(DATA_READ); + + for (uint8_t i = 0; i < sizeof(PN532_ACK); i++) + { + ackBuf[i] = read(); + } + + digitalWrite(_ss, HIGH); + + return memcmp(ackBuf, PN532_ACK, sizeof(PN532_ACK)); +} + +#endif // NFC_INTERFACE_SPI \ No newline at end of file diff --git a/lib/PN532/src/PN532_SPI.h b/lib/PN532/src/PN532_SPI.h new file mode 100644 index 0000000..691739d --- /dev/null +++ b/lib/PN532/src/PN532_SPI.h @@ -0,0 +1,39 @@ + +#ifndef __PN532_SPI_H__ +#define __PN532_SPI_H__ + +#include +#include "PN532Interface.h" + +class PN532_SPI : public PN532Interface +{ +public: + PN532_SPI(SPIClass &spi, uint8_t ss); + + void begin(); + void wakeup(); + int8_t writeCommand(const uint8_t *header, uint8_t hlen, const uint8_t *body = 0, uint8_t blen = 0); + + int16_t readResponse(uint8_t buf[], uint8_t len, uint16_t timeout); + +private: + SPIClass *_spi; + uint8_t _ss; + uint8_t command; + + bool isReady(); + void writeFrame(const uint8_t *header, uint8_t hlen, const uint8_t *body = 0, uint8_t blen = 0); + int8_t readAckFrame(); + + inline void write(uint8_t data) + { + _spi->transfer(data); + }; + + inline uint8_t read() + { + return _spi->transfer(0); + }; +}; + +#endif diff --git a/lib/PN532/src/PN532_SWHSU.cpp b/lib/PN532/src/PN532_SWHSU.cpp new file mode 100644 index 0000000..2079b02 --- /dev/null +++ b/lib/PN532/src/PN532_SWHSU.cpp @@ -0,0 +1,223 @@ +#ifdef NFC_INTERFACE_SWHSU + +#include "PN532_SWHSU.h" +#include "PN532_debug.h" + +PN532_SWHSU::PN532_SWHSU(SoftwareSerial &serial) +{ + _serial = &serial; + command = 0; +} + +void PN532_SWHSU::begin() +{ + _serial->begin(115200); +} + +void PN532_SWHSU::wakeup() +{ + _serial->write(0x55); + _serial->write(0x55); + _serial->write((uint8_t)0); + _serial->write((uint8_t)0); + _serial->write((uint8_t)0); + + /** dump serial buffer */ + if (_serial->available()) + { + DMSG("Dump serial buffer: "); + } + while (_serial->available()) + { + uint8_t ret = _serial->read(); + DMSG_HEX(ret); + } +} + +int8_t PN532_SWHSU::writeCommand(const uint8_t *header, uint8_t hlen, const uint8_t *body, uint8_t blen) +{ + + /** dump serial buffer */ + if (_serial->available()) + { + DMSG("Dump serial buffer: "); + } + while (_serial->available()) + { + uint8_t ret = _serial->read(); + DMSG_HEX(ret); + } + + command = header[0]; + + _serial->write((uint8_t)PN532_PREAMBLE); + _serial->write((uint8_t)PN532_STARTCODE1); + _serial->write((uint8_t)PN532_STARTCODE2); + + uint8_t length = hlen + blen + 1; // length of data field: TFI + DATA + _serial->write(length); + _serial->write(~length + 1); // checksum of length + + _serial->write((uint8_t)PN532_HOSTTOPN532); + uint8_t sum = PN532_HOSTTOPN532; // sum of TFI + DATA + + DMSG("\nWrite: "); + + _serial->write(header, hlen); + for (uint8_t i = 0; i < hlen; i++) + { + sum += header[i]; + + DMSG_HEX(header[i]); + } + + _serial->write(body, blen); + for (uint8_t i = 0; i < blen; i++) + { + sum += body[i]; + + DMSG_HEX(body[i]); + } + + uint8_t checksum = ~sum + 1; // checksum of TFI + DATA + _serial->write(checksum); + _serial->write((uint8_t)PN532_POSTAMBLE); + + return readAckFrame(); +} + +int16_t PN532_SWHSU::readResponse(uint8_t buf[], uint8_t len, uint16_t timeout) +{ + uint8_t tmp[3]; + + DMSG("\nRead: "); + + /** Frame Preamble and Start Code */ + if (receive(tmp, 3, timeout) <= 0) + { + return PN532_TIMEOUT; + } + if (0 != tmp[0] || 0 != tmp[1] || 0xFF != tmp[2]) + { + DMSG("Preamble error"); + return PN532_INVALID_FRAME; + } + + /** receive length and check */ + uint8_t length[2]; + if (receive(length, 2, timeout) <= 0) + { + return PN532_TIMEOUT; + } + if (0 != (uint8_t)(length[0] + length[1])) + { + DMSG("Length error"); + return PN532_INVALID_FRAME; + } + length[0] -= 2; + if (length[0] > len) + { + return PN532_NO_SPACE; + } + + /** receive command byte */ + uint8_t cmd = command + 1; // response command + if (receive(tmp, 2, timeout) <= 0) + { + return PN532_TIMEOUT; + } + if (PN532_PN532TOHOST != tmp[0] || cmd != tmp[1]) + { + DMSG("Command error"); + return PN532_INVALID_FRAME; + } + + if (receive(buf, length[0], timeout) != length[0]) + { + return PN532_TIMEOUT; + } + uint8_t sum = PN532_PN532TOHOST + cmd; + for (uint8_t i = 0; i < length[0]; i++) + { + sum += buf[i]; + } + + /** checksum and postamble */ + if (receive(tmp, 2, timeout) <= 0) + { + return PN532_TIMEOUT; + } + if (0 != (uint8_t)(sum + tmp[0]) || 0 != tmp[1]) + { + DMSG("Checksum error"); + return PN532_INVALID_FRAME; + } + + return length[0]; +} + +int8_t PN532_SWHSU::readAckFrame() +{ + const uint8_t PN532_ACK[] = {0, 0, 0xFF, 0, 0xFF, 0}; + uint8_t ackBuf[sizeof(PN532_ACK)]; + + DMSG("\nAck: "); + + if (receive(ackBuf, sizeof(PN532_ACK), PN532_ACK_WAIT_TIME) <= 0) + { + DMSG("Timeout\n"); + return PN532_TIMEOUT; + } + + if (memcmp(ackBuf, PN532_ACK, sizeof(PN532_ACK))) + { + DMSG("Invalid\n"); + return PN532_INVALID_ACK; + } + return 0; +} + +/** + @brief receive data . + @param buf --> return value buffer. + len --> length expect to receive. + timeout --> time of reveiving + @retval number of received bytes, 0 means no data received. +*/ +int8_t PN532_SWHSU::receive(uint8_t *buf, int len, uint16_t timeout) +{ + int read_bytes = 0; + int ret; + unsigned long start_millis; + + while (read_bytes < len) + { + start_millis = millis(); + do + { + ret = _serial->read(); + if (ret >= 0) + { + break; + } + } while ((timeout == 0) || ((millis() - start_millis) < timeout)); + + if (ret < 0) + { + if (read_bytes) + { + return read_bytes; + } + else + { + return PN532_TIMEOUT; + } + } + buf[read_bytes] = (uint8_t)ret; + DMSG_HEX(ret); + read_bytes++; + } + return read_bytes; +} + +#endif // NFC_INTERFACE_SWHSU \ No newline at end of file diff --git a/lib/PN532/src/PN532_SWHSU.h b/lib/PN532/src/PN532_SWHSU.h new file mode 100644 index 0000000..8f7289d --- /dev/null +++ b/lib/PN532/src/PN532_SWHSU.h @@ -0,0 +1,33 @@ + +#ifndef __PN532_SWHSU_H__ +#define __PN532_SWHSU_H__ + +#include + +#include "PN532Interface.h" +#include "Arduino.h" + +#define PN532_SWHSU_DEBUG + +#define PN532_SWHSU_READ_TIMEOUT (1000) + +class PN532_SWHSU : public PN532Interface +{ +public: + PN532_SWHSU(SoftwareSerial &serial); + + void begin(); + void wakeup(); + virtual int8_t writeCommand(const uint8_t *header, uint8_t hlen, const uint8_t *body = 0, uint8_t blen = 0); + int16_t readResponse(uint8_t buf[], uint8_t len, uint16_t timeout); + +private: + SoftwareSerial *_serial; + uint8_t command; + + int8_t readAckFrame(); + + int8_t receive(uint8_t *buf, int len, uint16_t timeout = PN532_SWHSU_READ_TIMEOUT); +}; + +#endif diff --git a/lib/PN532/src/PN532_debug.h b/lib/PN532/src/PN532_debug.h new file mode 100644 index 0000000..24e018c --- /dev/null +++ b/lib/PN532/src/PN532_debug.h @@ -0,0 +1,34 @@ +#ifndef __DEBUG_H__ +#define __DEBUG_H__ + +// #define DEBUG + +#include "Arduino.h" + +#ifdef SEEED_XIAO_M0 + #define PN532_DEBUG_SERIAL Serial +#elif defined(ARDUINO_SAMD_VARIANT_COMPLIANCE) + #define PN532_DEBUG_SERIAL SerialUSB +#else + #define PN532_DEBUG_SERIAL Serial +#endif + + +#ifdef DEBUG +#define DMSG(args...) PN532_DEBUG_SERIAL.print(args) +#define DMSG_STR(str) PN532_DEBUG_SERIAL.println(str) +#define DMSG_HEX(num) \ + PN532_DEBUG_SERIAL.print(' '); \ + PN532_DEBUG_SERIAL.print((num >> 4) & 0x0F, HEX); \ + PN532_DEBUG_SERIAL.print(num & 0x0F, HEX) +#define DMSG_INT(num) \ + PN532_DEBUG_SERIAL.print(' '); \ + PN532_DEBUG_SERIAL.print(num) +#else +#define DMSG(args...) +#define DMSG_STR(str) +#define DMSG_HEX(num) +#define DMSG_INT(num) +#endif + +#endif diff --git a/lib/PN532/src/emulatetag.cpp b/lib/PN532/src/emulatetag.cpp new file mode 100644 index 0000000..a862285 --- /dev/null +++ b/lib/PN532/src/emulatetag.cpp @@ -0,0 +1,298 @@ +/**************************************************************************/ +/*! + @file emulatetag.cpp + @author Armin Wieser + @license BSD +*/ +/**************************************************************************/ + +#include "emulatetag.h" +#include "PN532_debug.h" + +#include + +#define MAX_TGREAD + +// Command APDU +#define C_APDU_CLA 0 +#define C_APDU_INS 1 // instruction +#define C_APDU_P1 2 // parameter 1 +#define C_APDU_P2 3 // parameter 2 +#define C_APDU_LC 4 // length command +#define C_APDU_DATA 5 // data + +#define C_APDU_P1_SELECT_BY_ID 0x00 +#define C_APDU_P1_SELECT_BY_NAME 0x04 + +// Response APDU +#define R_APDU_SW1_COMMAND_COMPLETE 0x90 +#define R_APDU_SW2_COMMAND_COMPLETE 0x00 + +#define R_APDU_SW1_NDEF_TAG_NOT_FOUND 0x6a +#define R_APDU_SW2_NDEF_TAG_NOT_FOUND 0x82 + +#define R_APDU_SW1_FUNCTION_NOT_SUPPORTED 0x6A +#define R_APDU_SW2_FUNCTION_NOT_SUPPORTED 0x81 + +#define R_APDU_SW1_MEMORY_FAILURE 0x65 +#define R_APDU_SW2_MEMORY_FAILURE 0x81 + +#define R_APDU_SW1_END_OF_FILE_BEFORE_REACHED_LE_BYTES 0x62 +#define R_APDU_SW2_END_OF_FILE_BEFORE_REACHED_LE_BYTES 0x82 + +// ISO7816-4 commands +#define ISO7816_SELECT_FILE 0xA4 +#define ISO7816_READ_BINARY 0xB0 +#define ISO7816_UPDATE_BINARY 0xD6 + +typedef enum +{ + NONE, + CC, + NDEF +} tag_file; // CC ... Compatibility Container + +bool EmulateTag::init() +{ + pn532.begin(); + return pn532.SAMConfig(); +} + +void EmulateTag::setNdefFile(const uint8_t *ndef, const int16_t ndefLength) +{ + if (ndefLength > (NDEF_MAX_LENGTH - 2)) + { + DMSG("ndef file too large (> NDEF_MAX_LENGHT -2) - aborting"); + return; + } + + ndef_file[0] = ndefLength >> 8; + ndef_file[1] = ndefLength & 0xFF; + memcpy(ndef_file + 2, ndef, ndefLength); +} + +void EmulateTag::setUid(uint8_t *uid) +{ + uidPtr = uid; +} + +bool EmulateTag::emulate(const uint16_t tgInitAsTargetTimeout) +{ + + uint8_t command[] = { + PN532_COMMAND_TGINITASTARGET, + 5, // MODE: PICC only, Passive only + + 0x04, 0x00, // SENS_RES + 0x00, 0x00, 0x00, // NFCID1 + 0x20, // SEL_RES + + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, // FeliCaParams + 0, 0, + + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // NFCID3t + + 0, // length of general bytes + 0 // length of historical bytes + }; + + if (uidPtr != 0) + { // if uid is set copy 3 bytes to nfcid1 + memcpy(command + 4, uidPtr, 3); + } + + if (1 != pn532.tgInitAsTarget(command, sizeof(command), tgInitAsTargetTimeout)) + { + DMSG("tgInitAsTarget failed or timed out!"); + return false; + } + + uint8_t compatibility_container[] = { + 0, 0x0F, + 0x20, + 0, 0x54, + 0, 0xFF, + 0x04, // T + 0x06, // L + 0xE1, 0x04, // File identifier + ((NDEF_MAX_LENGTH & 0xFF00) >> 8), (NDEF_MAX_LENGTH & 0xFF), // maximum NDEF file size + 0x00, // read access 0x0 = granted + 0x00 // write access 0x0 = granted | 0xFF = deny + }; + + if (tagWriteable == false) + { + compatibility_container[14] = 0xFF; + } + + tagWrittenByInitiator = false; + + uint8_t rwbuf[128]; + uint8_t sendlen; + int16_t status; + tag_file currentFile = NONE; + uint16_t cc_size = sizeof(compatibility_container); + bool runLoop = true; + + while (runLoop) + { + status = pn532.tgGetData(rwbuf, sizeof(rwbuf)); + if (status < 0) + { + DMSG("tgGetData failed!\n"); + pn532.inRelease(); + return true; + } + + uint8_t p1 = rwbuf[C_APDU_P1]; + uint8_t p2 = rwbuf[C_APDU_P2]; + uint8_t lc = rwbuf[C_APDU_LC]; + uint16_t p1p2_length = ((int16_t)p1 << 8) + p2; + + switch (rwbuf[C_APDU_INS]) + { + case ISO7816_SELECT_FILE: + switch (p1) + { + case C_APDU_P1_SELECT_BY_ID: + if (p2 != 0x0c) + { + DMSG("C_APDU_P2 != 0x0c\n"); + setResponse(COMMAND_COMPLETE, rwbuf, &sendlen); + } + else if (lc == 2 && rwbuf[C_APDU_DATA] == 0xE1 && (rwbuf[C_APDU_DATA + 1] == 0x03 || rwbuf[C_APDU_DATA + 1] == 0x04)) + { + setResponse(COMMAND_COMPLETE, rwbuf, &sendlen); + if (rwbuf[C_APDU_DATA + 1] == 0x03) + { + currentFile = CC; + } + else if (rwbuf[C_APDU_DATA + 1] == 0x04) + { + currentFile = NDEF; + } + } + else + { + setResponse(TAG_NOT_FOUND, rwbuf, &sendlen); + } + break; + case C_APDU_P1_SELECT_BY_NAME: + const uint8_t ndef_tag_application_name_v2[] = {0, 0x7, 0xD2, 0x76, 0x00, 0x00, 0x85, 0x01, 0x01}; + if (0 == memcmp(ndef_tag_application_name_v2, rwbuf + C_APDU_P2, sizeof(ndef_tag_application_name_v2))) + { + setResponse(COMMAND_COMPLETE, rwbuf, &sendlen); + } + else + { + DMSG("function not supported\n"); + setResponse(FUNCTION_NOT_SUPPORTED, rwbuf, &sendlen); + } + break; + } + break; + case ISO7816_READ_BINARY: + switch (currentFile) + { + case NONE: + setResponse(TAG_NOT_FOUND, rwbuf, &sendlen); + break; + case CC: + if (p1p2_length > NDEF_MAX_LENGTH) + { + setResponse(END_OF_FILE_BEFORE_REACHED_LE_BYTES, rwbuf, &sendlen); + } + else + { + memcpy(rwbuf, compatibility_container + p1p2_length, lc); + setResponse(COMMAND_COMPLETE, rwbuf + lc, &sendlen, lc); + } + break; + case NDEF: + if (p1p2_length > NDEF_MAX_LENGTH) + { + setResponse(END_OF_FILE_BEFORE_REACHED_LE_BYTES, rwbuf, &sendlen); + } + else + { + memcpy(rwbuf, ndef_file + p1p2_length, lc); + setResponse(COMMAND_COMPLETE, rwbuf + lc, &sendlen, lc); + } + break; + } + break; + case ISO7816_UPDATE_BINARY: + if (!tagWriteable) + { + setResponse(FUNCTION_NOT_SUPPORTED, rwbuf, &sendlen); + } + else + { + if (p1p2_length > NDEF_MAX_LENGTH) + { + setResponse(MEMORY_FAILURE, rwbuf, &sendlen); + } + else + { + memcpy(ndef_file + p1p2_length, rwbuf + C_APDU_DATA, lc); + setResponse(COMMAND_COMPLETE, rwbuf, &sendlen); + tagWrittenByInitiator = true; + + uint16_t ndef_length = (ndef_file[0] << 8) + ndef_file[1]; + if ((ndef_length > 0) && (updateNdefCallback != 0)) + { + updateNdefCallback(ndef_file + 2, ndef_length); + } + } + } + break; + default: + DMSG("Command not supported!"); + DMSG_HEX(rwbuf[C_APDU_INS]); + DMSG("\n"); + setResponse(FUNCTION_NOT_SUPPORTED, rwbuf, &sendlen); + } + status = pn532.tgSetData(rwbuf, sendlen); + if (status < 0) + { + DMSG("tgSetData failed\n!"); + pn532.inRelease(); + return true; + } + } + pn532.inRelease(); + return true; +} + +void EmulateTag::setResponse(responseCommand cmd, uint8_t *buf, uint8_t *sendlen, uint8_t sendlenOffset) +{ + switch (cmd) + { + case COMMAND_COMPLETE: + buf[0] = R_APDU_SW1_COMMAND_COMPLETE; + buf[1] = R_APDU_SW2_COMMAND_COMPLETE; + *sendlen = 2 + sendlenOffset; + break; + case TAG_NOT_FOUND: + buf[0] = R_APDU_SW1_NDEF_TAG_NOT_FOUND; + buf[1] = R_APDU_SW2_NDEF_TAG_NOT_FOUND; + *sendlen = 2; + break; + case FUNCTION_NOT_SUPPORTED: + buf[0] = R_APDU_SW1_FUNCTION_NOT_SUPPORTED; + buf[1] = R_APDU_SW2_FUNCTION_NOT_SUPPORTED; + *sendlen = 2; + break; + case MEMORY_FAILURE: + buf[0] = R_APDU_SW1_MEMORY_FAILURE; + buf[1] = R_APDU_SW2_MEMORY_FAILURE; + *sendlen = 2; + break; + case END_OF_FILE_BEFORE_REACHED_LE_BYTES: + buf[0] = R_APDU_SW1_END_OF_FILE_BEFORE_REACHED_LE_BYTES; + buf[1] = R_APDU_SW2_END_OF_FILE_BEFORE_REACHED_LE_BYTES; + *sendlen = 2; + break; + } +} diff --git a/lib/PN532/src/emulatetag.h b/lib/PN532/src/emulatetag.h new file mode 100644 index 0000000..f1aedd5 --- /dev/null +++ b/lib/PN532/src/emulatetag.h @@ -0,0 +1,85 @@ +/**************************************************************************/ +/*! + @file emulatetag.h + @author Armin Wieser + @license BSD + + Implemented using NFC forum documents & library of libnfc +*/ +/**************************************************************************/ + +#ifndef __EMULATETAG_H__ +#define __EMULATETAG_H__ + +#include "PN532.h" + +#define NDEF_MAX_LENGTH 128 // altough ndef can handle up to 0xfffe in size, arduino cannot. +typedef enum +{ + COMMAND_COMPLETE, + TAG_NOT_FOUND, + FUNCTION_NOT_SUPPORTED, + MEMORY_FAILURE, + END_OF_FILE_BEFORE_REACHED_LE_BYTES +} responseCommand; + +class EmulateTag +{ + +public: + EmulateTag(PN532Interface &interface) : pn532(interface), uidPtr(0), tagWrittenByInitiator(false), tagWriteable(true), updateNdefCallback(0) {} + + bool init(); + + bool emulate(const uint16_t tgInitAsTargetTimeout = 0); + + /* + * @param uid pointer to byte array of length 3 (uid is 4 bytes - first byte is fixed) or zero for uid + */ + void setUid(uint8_t *uid = 0); + + void setNdefFile(const uint8_t *ndef, const int16_t ndefLength); + + void getContent(uint8_t **buf, uint16_t *length) + { + *buf = ndef_file + 2; // first 2 bytes = length + *length = (ndef_file[0] << 8) + ndef_file[1]; + } + + bool writeOccured() + { + return tagWrittenByInitiator; + } + + void setTagWriteable(bool setWriteable) + { + tagWriteable = setWriteable; + } + + uint8_t *getNdefFilePtr() + { + return ndef_file; + } + + uint8_t getNdefMaxLength() + { + return NDEF_MAX_LENGTH; + } + + void attach(void (*func)(uint8_t *buf, uint16_t length)) + { + updateNdefCallback = func; + }; + +private: + PN532 pn532; + uint8_t ndef_file[NDEF_MAX_LENGTH]; + uint8_t *uidPtr; + bool tagWrittenByInitiator; + bool tagWriteable; + void (*updateNdefCallback)(uint8_t *ndef, uint16_t length); + + void setResponse(responseCommand cmd, uint8_t *buf, uint8_t *sendlen, uint8_t sendlenOffset = 0); +}; + +#endif diff --git a/lib/PN532/src/llcp.cpp b/lib/PN532/src/llcp.cpp new file mode 100644 index 0000000..559cd7a --- /dev/null +++ b/lib/PN532/src/llcp.cpp @@ -0,0 +1,373 @@ + +#include "llcp.h" +#include "PN532_debug.h" + +// LLCP PDU Type Values +#define PDU_SYMM 0x00 +#define PDU_PAX 0x01 +#define PDU_CONNECT 0x04 +#define PDU_DISC 0x05 +#define PDU_CC 0x06 +#define PDU_DM 0x07 +#define PDU_I 0x0c +#define PDU_RR 0x0d + +uint8_t LLCP::SYMM_PDU[2] = {0, 0}; + +inline uint8_t getPType(const uint8_t *buf) +{ + return ((buf[0] & 0x3) << 2) + (buf[1] >> 6); +} + +inline uint8_t getSSAP(const uint8_t *buf) +{ + return buf[1] & 0x3f; +} + +inline uint8_t getDSAP(const uint8_t *buf) +{ + return buf[0] >> 2; +} + +int8_t LLCP::activate(uint16_t timeout) +{ + return link.activateAsTarget(timeout); +} + +int8_t LLCP::waitForConnection(uint16_t timeout) +{ + uint8_t type; + + mode = 1; + ns = 0; + nr = 0; + + // Get CONNECT PDU + DMSG("wait for a CONNECT PDU\n"); + do + { + if (2 > link.read(headerBuf, headerBufLen)) + { + return -1; + } + + type = getPType(headerBuf); + if (PDU_CONNECT == type) + { + break; + } + else if (PDU_SYMM == type) + { + if (!link.write(SYMM_PDU, sizeof(SYMM_PDU))) + { + return -2; + } + } + else + { + return -3; + } + + } while (1); + + // Put CC PDU + DMSG("put a CC(Connection Complete) PDU to response the CONNECT PDU\n"); + ssap = getDSAP(headerBuf); + dsap = getSSAP(headerBuf); + headerBuf[0] = (dsap << 2) + ((PDU_CC >> 2) & 0x3); + headerBuf[1] = ((PDU_CC & 0x3) << 6) + ssap; + if (!link.write(headerBuf, 2)) + { + return -2; + } + + return 1; +} + +int8_t LLCP::waitForDisconnection(uint16_t timeout) +{ + uint8_t type; + + // Get DISC PDU + DMSG("wait for a DISC PDU\n"); + do + { + if (2 > link.read(headerBuf, headerBufLen)) + { + return -1; + } + + type = getPType(headerBuf); + if (PDU_DISC == type) + { + break; + } + else if (PDU_SYMM == type) + { + if (!link.write(SYMM_PDU, sizeof(SYMM_PDU))) + { + return -2; + } + } + else + { + return -3; + } + + } while (1); + + // Put DM PDU + DMSG("put a DM(Disconnect Mode) PDU to response the DISC PDU\n"); + // ssap = getDSAP(headerBuf); + // dsap = getSSAP(headerBuf); + headerBuf[0] = (dsap << 2) + (PDU_DM >> 2); + headerBuf[1] = ((PDU_DM & 0x3) << 6) + ssap; + if (!link.write(headerBuf, 2)) + { + return -2; + } + + return 1; +} + +int8_t LLCP::connect(uint16_t timeout) +{ + uint8_t type; + + mode = 0; + dsap = LLCP_DEFAULT_DSAP; + ssap = LLCP_DEFAULT_SSAP; + ns = 0; + nr = 0; + + // try to get a SYMM PDU + if (2 > link.read(headerBuf, headerBufLen)) + { + return -1; + } + type = getPType(headerBuf); + if (PDU_SYMM != type) + { + return -1; + } + + // put a CONNECT PDU + headerBuf[0] = (LLCP_DEFAULT_DSAP << 2) + (PDU_CONNECT >> 2); + headerBuf[1] = ((PDU_CONNECT & 0x03) << 6) + LLCP_DEFAULT_SSAP; + uint8_t body[] = " urn:nfc:sn:snep"; + body[0] = 0x06; + body[1] = sizeof(body) - 2 - 1; + if (!link.write(headerBuf, 2, body, sizeof(body) - 1)) + { + return -2; + } + + // wait for a CC PDU + DMSG("wait for a CC PDU\n"); + do + { + if (2 > link.read(headerBuf, headerBufLen)) + { + return -1; + } + + type = getPType(headerBuf); + if (PDU_CC == type) + { + break; + } + else if (PDU_SYMM == type) + { + if (!link.write(SYMM_PDU, sizeof(SYMM_PDU))) + { + return -2; + } + } + else + { + return -3; + } + + } while (1); + + return 1; +} + +int8_t LLCP::disconnect(uint16_t timeout) +{ + uint8_t type; + + // try to get a SYMM PDU + if (2 > link.read(headerBuf, headerBufLen)) + { + return -1; + } + type = getPType(headerBuf); + if (PDU_SYMM != type) + { + return -1; + } + + // put a DISC PDU + headerBuf[0] = (LLCP_DEFAULT_DSAP << 2) + (PDU_DISC >> 2); + headerBuf[1] = ((PDU_DISC & 0x03) << 6) + LLCP_DEFAULT_SSAP; + if (!link.write(headerBuf, 2)) + { + return -2; + } + + // wait for a DM PDU + DMSG("wait for a DM PDU\n"); + do + { + if (2 > link.read(headerBuf, headerBufLen)) + { + return -1; + } + + type = getPType(headerBuf); + if (PDU_CC == type) + { + break; + } + else if (PDU_DM == type) + { + if (!link.write(SYMM_PDU, sizeof(SYMM_PDU))) + { + return -2; + } + } + else + { + return -3; + } + + } while (1); + + return 1; +} + +bool LLCP::write(const uint8_t *header, uint8_t hlen, const uint8_t *body, uint8_t blen) +{ + uint8_t type; + uint8_t buf[3]; + + if (mode) + { + // Get a SYMM PDU + if (2 != link.read(buf, sizeof(buf))) + { + return false; + } + } + + if (headerBufLen < (hlen + 3)) + { + return false; + } + + for (int8_t i = hlen - 1; i >= 0; i--) + { + headerBuf[i + 3] = header[i]; + } + + headerBuf[0] = (dsap << 2) + (PDU_I >> 2); + headerBuf[1] = ((PDU_I & 0x3) << 6) + ssap; + headerBuf[2] = (ns << 4) + nr; + if (!link.write(headerBuf, 3 + hlen, body, blen)) + { + return false; + } + + ns++; + + // Get a RR PDU + int16_t status; + do + { + status = link.read(headerBuf, headerBufLen); + if (2 > status) + { + return false; + } + + type = getPType(headerBuf); + if (PDU_RR == type) + { + break; + } + else if (PDU_SYMM == type) + { + if (!link.write(SYMM_PDU, sizeof(SYMM_PDU))) + { + return false; + } + } + else + { + return false; + } + } while (1); + + if (!link.write(SYMM_PDU, sizeof(SYMM_PDU))) + { + return false; + } + + return true; +} + +int16_t LLCP::read(uint8_t *buf, uint8_t length) +{ + uint8_t type; + uint16_t status; + + // Get INFO PDU + do + { + status = link.read(buf, length); + if (2 > status) + { + return -1; + } + + type = getPType(buf); + if (PDU_I == type) + { + break; + } + else if (PDU_SYMM == type) + { + if (!link.write(SYMM_PDU, sizeof(SYMM_PDU))) + { + return -2; + } + } + else + { + return -3; + } + + } while (1); + + uint8_t len = status - 3; + ssap = getDSAP(buf); + dsap = getSSAP(buf); + + headerBuf[0] = (dsap << 2) + (PDU_RR >> 2); + headerBuf[1] = ((PDU_RR & 0x3) << 6) + ssap; + headerBuf[2] = (buf[2] >> 4) + 1; + if (!link.write(headerBuf, 3)) + { + return -2; + } + + for (uint8_t i = 0; i < len; i++) + { + buf[i] = buf[i + 3]; + } + + nr++; + + return len; +} diff --git a/lib/PN532/src/llcp.h b/lib/PN532/src/llcp.h new file mode 100644 index 0000000..701c5d7 --- /dev/null +++ b/lib/PN532/src/llcp.h @@ -0,0 +1,75 @@ + +#ifndef __LLCP_H__ +#define __LLCP_H__ + +#include "mac_link.h" + +#define LLCP_DEFAULT_TIMEOUT 20000 +#define LLCP_DEFAULT_DSAP 0x04 +#define LLCP_DEFAULT_SSAP 0x20 + +class LLCP { +public: + LLCP(PN532Interface &interface) : link(interface) { + headerBuf = link.getHeaderBuffer(&headerBufLen); + ns = 0; + nr = 0; + }; + + /** + * @brief Actiave PN532 as a target + * @param timeout max time to wait, 0 means no timeout + * @return > 0 success + * = 0 timeout + * < 0 failed + */ + int8_t activate(uint16_t timeout = 0); + + int8_t waitForConnection(uint16_t timeout = LLCP_DEFAULT_TIMEOUT); + + int8_t waitForDisconnection(uint16_t timeout = LLCP_DEFAULT_TIMEOUT); + + int8_t connect(uint16_t timeout = LLCP_DEFAULT_TIMEOUT); + + int8_t disconnect(uint16_t timeout = LLCP_DEFAULT_TIMEOUT); + + /** + * @brief write a packet, the packet should be less than (255 - 2) bytes + * @param header packet header + * @param hlen length of header + * @param body packet body + * @param blen length of body + * @return true success + * false failed + */ + bool write(const uint8_t *header, uint8_t hlen, const uint8_t *body = 0, uint8_t blen = 0); + + /** + * @brief read a packet, the packet will be less than (255 - 2) bytes + * @param buf the buffer to contain the packet + * @param len lenght of the buffer + * @return >=0 length of the packet + * <0 failed + */ + int16_t read(uint8_t *buf, uint8_t len); + + uint8_t *getHeaderBuffer(uint8_t *len) { + uint8_t *buf = link.getHeaderBuffer(len); + len -= 3; // I PDU header has 3 bytes + return buf; + }; + +private: + MACLink link; + uint8_t mode; + uint8_t ssap; + uint8_t dsap; + uint8_t *headerBuf; + uint8_t headerBufLen; + uint8_t ns; // Number of I PDU Sent + uint8_t nr; // Number of I PDU Received + + static uint8_t SYMM_PDU[2]; +}; + +#endif // __LLCP_H__ diff --git a/lib/PN532/src/mac_link.cpp b/lib/PN532/src/mac_link.cpp new file mode 100644 index 0000000..75aca8e --- /dev/null +++ b/lib/PN532/src/mac_link.cpp @@ -0,0 +1,20 @@ + +#include "mac_link.h" +#include "PN532_debug.h" + +int8_t MACLink::activateAsTarget(uint16_t timeout) +{ + pn532.begin(); + pn532.SAMConfig(); + return pn532.tgInitAsTarget(timeout); +} + +bool MACLink::write(const uint8_t *header, uint8_t hlen, const uint8_t *body, uint8_t blen) +{ + return pn532.tgSetData(header, hlen, body, blen); +} + +int16_t MACLink::read(uint8_t *buf, uint8_t len) +{ + return pn532.tgGetData(buf, len); +} diff --git a/lib/PN532/src/mac_link.h b/lib/PN532/src/mac_link.h new file mode 100644 index 0000000..1575c04 --- /dev/null +++ b/lib/PN532/src/mac_link.h @@ -0,0 +1,51 @@ + + +#ifndef __MAC_LINK_H__ +#define __MAC_LINK_H__ + +#include "PN532.h" + +class MACLink { +public: + MACLink(PN532Interface &interface) : pn532(interface) { + + }; + + /** + * @brief Activate PN532 as a target + * @param timeout max time to wait, 0 means no timeout + * @return > 0 success + * = 0 timeout + * < 0 failed + */ + int8_t activateAsTarget(uint16_t timeout = 0); + + /** + * @brief write a PDU packet, the packet should be less than (255 - 2) bytes + * @param header packet header + * @param hlen length of header + * @param body packet body + * @param blen length of body + * @return true success + * false failed + */ + bool write(const uint8_t *header, uint8_t hlen, const uint8_t *body = 0, uint8_t blen = 0); + + /** + * @brief read a PDU packet, the packet will be less than (255 - 2) bytes + * @param buf the buffer to contain the PDU packet + * @param len lenght of the buffer + * @return >=0 length of the PDU packet + * <0 failed + */ + int16_t read(uint8_t *buf, uint8_t len); + + uint8_t *getHeaderBuffer(uint8_t *len) { + return pn532.getBuffer(len); + }; + +private: + PN532 pn532; +}; + +#endif // __MAC_LINK_H__ diff --git a/lib/PN532/src/snep.cpp b/lib/PN532/src/snep.cpp new file mode 100644 index 0000000..73c71c4 --- /dev/null +++ b/lib/PN532/src/snep.cpp @@ -0,0 +1,133 @@ + +#include "snep.h" +#include "PN532_debug.h" + +int8_t SNEP::write(const uint8_t *buf, uint8_t len, uint16_t timeout) +{ + if (0 >= llcp.activate(timeout)) + { + DMSG("failed to activate PN532 as a target\n"); + return -1; + } + + if (0 >= llcp.connect(timeout)) + { + DMSG("failed to set up a connection\n"); + return -2; + } + + // response a success SNEP message + headerBuf[0] = SNEP_DEFAULT_VERSION; + headerBuf[1] = SNEP_REQUEST_PUT; + headerBuf[2] = 0; + headerBuf[3] = 0; + headerBuf[4] = 0; + headerBuf[5] = len; + if (0 >= llcp.write(headerBuf, 6, buf, len)) + { + return -3; + } + + uint8_t rbuf[16]; + if (6 > llcp.read(rbuf, sizeof(rbuf))) + { + return -4; + } + + // check SNEP version + if (SNEP_DEFAULT_VERSION != rbuf[0]) + { + DMSG("The received SNEP message's major version is different\n"); + // To-do: send Unsupported Version response + return -4; + } + + // expect a put request + if (SNEP_RESPONSE_SUCCESS != rbuf[1]) + { + DMSG("Expect a success response\n"); + return -4; + } + + llcp.disconnect(timeout); + + return 1; +} + +int16_t SNEP::read(uint8_t *buf, uint8_t len, uint16_t timeout) +{ + if (0 >= llcp.activate(timeout)) + { + DMSG("failed to activate PN532 as a target\n"); + return -1; + } + + if (0 >= llcp.waitForConnection(timeout)) + { + DMSG("failed to set up a connection\n"); + return -2; + } + + uint16_t status = llcp.read(buf, len); + if (6 > status) + { + return -3; + } + + // check SNEP version + + // in case of platform specific bug, shift SNEP message for 4 bytes. + // tested on Nexus 5, Android 5.1 + if (SNEP_DEFAULT_VERSION != buf[0] && SNEP_DEFAULT_VERSION == buf[4]) + { + for (uint8_t i = 0; i < len - 4; i++) + { + buf[i] = buf[i + 4]; + } + } + + if (SNEP_DEFAULT_VERSION != buf[0]) + { + DMSG(F("SNEP->read: The received SNEP message's major version is different, me: ")); + DMSG(SNEP_DEFAULT_VERSION); + DMSG(", their: "); + DMSG(buf[0]); + DMSG("\n"); + // To-do: send Unsupported Version response + return -4; + } + + // expect a put request + if (SNEP_REQUEST_PUT != buf[1]) + { + DMSG("Expect a put request\n"); + return -4; + } + + // check message's length + uint32_t length = (buf[2] << 24) + (buf[3] << 16) + (buf[4] << 8) + buf[5]; + // length should not be more than 244 (header + body < 255, header = 6 + 3 + 2) + if (length > (status - 6)) + { + DMSG("The SNEP message is too large: "); + DMSG_INT(length); + DMSG_INT(status - 6); + DMSG("\n"); + return -4; + } + for (uint8_t i = 0; i < length; i++) + { + buf[i] = buf[i + 6]; + } + + // response a success SNEP message + headerBuf[0] = SNEP_DEFAULT_VERSION; + headerBuf[1] = SNEP_RESPONSE_SUCCESS; + headerBuf[2] = 0; + headerBuf[3] = 0; + headerBuf[4] = 0; + headerBuf[5] = 0; + llcp.write(headerBuf, 6); + + return length; +} diff --git a/lib/PN532/src/snep.h b/lib/PN532/src/snep.h new file mode 100644 index 0000000..1a51696 --- /dev/null +++ b/lib/PN532/src/snep.h @@ -0,0 +1,49 @@ + + +#ifndef __SNEP_H__ +#define __SNEP_H__ + +#include "llcp.h" + +#define SNEP_DEFAULT_VERSION 0x10 // Major: 1, Minor: 0 + +#define SNEP_REQUEST_PUT 0x02 +#define SNEP_REQUEST_GET 0x01 + +#define SNEP_RESPONSE_SUCCESS 0x81 +#define SNEP_RESPONSE_REJECT 0xFF + +class SNEP { +public: + SNEP(PN532Interface &interface) : llcp(interface) { + headerBuf = llcp.getHeaderBuffer(&headerBufLen); + }; + + /** + * @brief write a SNEP packet, the packet should be less than (255 - 2 - 3) bytes + * @param buf the buffer to contain the packet + * @param len lenght of the buffer + * @param timeout max time to wait, 0 means no timeout + * @return >0 success + * =0 timeout + * <0 failed + */ + int8_t write(const uint8_t *buf, uint8_t len, uint16_t timeout = 0); + + /** + * @brief read a SNEP packet, the packet will be less than (255 - 2 - 3) bytes + * @param buf the buffer to contain the packet + * @param len lenght of the buffer + * @param timeout max time to wait, 0 means no timeout + * @return >=0 length of the packet + * <0 failed + */ + int16_t read(uint8_t *buf, uint8_t len, uint16_t timeout = 0); + +private: + LLCP llcp; + uint8_t *headerBuf; + uint8_t headerBufLen; +}; + +#endif // __SNEP_H__ diff --git a/platformio.ini b/platformio.ini index 027c106..b5ea341 100644 --- a/platformio.ini +++ b/platformio.ini @@ -9,34 +9,34 @@ ; https://docs.platformio.org/page/projectconf.html [platformio] -;-------------- examples ----------------; -; Only one of the following example can be selected -;----------------------------------------; -; default_envs = cc1101_recv -; default_envs = cc1101_send -; default_envs = cc1101_recv_irq -; default_envs = cc1101_send_irq -; default_envs = infrared_recv_test -; default_envs = infrared_send_test - -# default_envs = display_test -; default_envs = lvgl_test -; default_envs = encode_test -; default_envs = ws2812_test -; default_envs = pn532_test -; default_envs = bq25896_test -; default_envs = record_test -; default_envs = voice_test -; default_envs = tf_card_test -; default_envs = bq27xxx_test +;--------------- select you board --------------; + +;--------------- select a example --------------; +; src_dir = examples/cc1101_recv +; src_dir = examples/cc1101_send ; +; src_dir = examples/cc1101_recv_irq ; +; src_dir = examples/cc1101_send_irq ; +; src_dir = examples/infrared_recv_test ; +; src_dir = examples/infrared_send_test ; +; src_dir = examples/display_test ; +; src_dir = examples/lvgl_test ; +; src_dir = examples/encode_test ; +; src_dir = examples/ws2812_test ; +; src_dir = examples/pn532_test ; +; src_dir = examples/pn532_emulatetag ; +; src_dir = examples/bq25896_test ; +; src_dir = examples/record_test ; +; src_dir = examples/voice_test ; +; src_dir = examples/tf_card_test ; +; src_dir = examples/bq27xxx_test ; +src_dir = examples/factory_test ; -default_envs = factory_test ;----------------------------------------; boards_dir = boards -src_dir = examples/${platformio.default_envs} +; src_dir = examples/${platformio.default_envs} -[env] +[env:T_Embed_CC1101] platform = espressif32@6.5.0 board = T_Embed_PN532 framework = arduino @@ -55,6 +55,8 @@ build_flags = -DRADIOLIB_LOW_LEVEL=1 + -DNFC_INTERFACE_I2C + ; -DBOARD_HAS_PSRAM=1 ; -mfix-esp32-psram-cache-issue @@ -62,48 +64,7 @@ build_flags = -include lib/lv_conf.h -; lib_deps = - ; lewisxhe/XPowersLib@^0.2.3 -; esphome/ESP32-audioI2S@2.1.0 - - -[env:display_test] - -[env:encode_test] - -[env:lvgl_test] - -[env:ws2812_test] - -[env:cc1101_recv] - -[env:cc1101_send] - -[env:cc1101_recv_irq] - -[env:cc1101_send_irq] - -[env:pn532_test] - -[env:bq25896_test] - -[env:bq27220_test] - -[env:record_test] - -[env:voice_test] - -[env:infrared_test] - -[env:tf_card_test] - -[env:usb_msc_flash] - -[env:bq27xxx_test] - -[env:infrared_recv_test] - -[env:infrared_send_test] - -[env:factory_test] - +lib_deps = + ; https://github.com/Seeed-Studio/PN532.git + ; lewisxhe/XPowersLib@^0.2.3 + ; esphome/ESP32-audioI2S@2.1.0 \ No newline at end of file