initial commit
This commit is contained in:
25
Due.h
Normal file
25
Due.h
Normal file
@@ -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
|
||||
29
LICENSE.txt
Normal file
29
LICENSE.txt
Normal file
@@ -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.
|
||||
451
MifareClassic.cpp
Normal file
451
MifareClassic.cpp
Normal file
@@ -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)<NR_SHORTSECTOR)? \
|
||||
((sector)*NR_BLOCK_OF_SHORTSECTOR + NR_BLOCK_OF_SHORTSECTOR-1):\
|
||||
(NR_SHORTSECTOR*NR_BLOCK_OF_SHORTSECTOR + (sector-NR_SHORTSECTOR)*NR_BLOCK_OF_LONGSECTOR + NR_BLOCK_OF_LONGSECTOR-1))
|
||||
|
||||
boolean MifareClassic::formatMifare(byte * uid, unsigned int uidLength)
|
||||
{
|
||||
|
||||
// The default Mifare Classic key
|
||||
uint8_t KEY_DEFAULT_KEYAB[6] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};
|
||||
|
||||
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)
|
||||
boolean success = false;
|
||||
|
||||
for (idx = 0; idx < numOfSector; idx++)
|
||||
{
|
||||
// Step 1: Authenticate the current sector using key B 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF
|
||||
success = _nfcShield->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
|
||||
31
MifareClassic.h
Normal file
31
MifareClassic.h
Normal file
@@ -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 <Due.h>
|
||||
#include <PN532.h>
|
||||
#include <Ndef.h>
|
||||
#include <NfcTag.h>
|
||||
|
||||
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
|
||||
259
MifareUltralight.cpp
Normal file
259
MifareUltralight.cpp
Normal file
@@ -0,0 +1,259 @@
|
||||
#include <MifareUltralight.h>
|
||||
|
||||
#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;
|
||||
}
|
||||
28
MifareUltralight.h
Normal file
28
MifareUltralight.h
Normal file
@@ -0,0 +1,28 @@
|
||||
#ifndef MifareUltralight_h
|
||||
#define MifareUltralight_h
|
||||
|
||||
#include <PN532.h>
|
||||
#include <NfcTag.h>
|
||||
#include <Ndef.h>
|
||||
|
||||
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
|
||||
59
Ndef.cpp
Normal file
59
Ndef.cpp
Normal file
@@ -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
|
||||
19
Ndef.h
Normal file
19
Ndef.h
Normal file
@@ -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 <Arduino.h>
|
||||
|
||||
#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
|
||||
278
NdefMessage.cpp
Normal file
278
NdefMessage.cpp
Normal file
@@ -0,0 +1,278 @@
|
||||
#include <NdefMessage.h>
|
||||
|
||||
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<uint32_t>(data[index]) << 24)
|
||||
| (static_cast<uint32_t>(data[index+1]) << 16)
|
||||
| (static_cast<uint32_t>(data[index+2]) << 8)
|
||||
| static_cast<uint32_t>(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<int>(_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
|
||||
41
NdefMessage.h
Normal file
41
NdefMessage.h
Normal file
@@ -0,0 +1,41 @@
|
||||
#ifndef NdefMessage_h
|
||||
#define NdefMessage_h
|
||||
|
||||
#include <Ndef.h>
|
||||
#include <NdefRecord.h>
|
||||
|
||||
#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
|
||||
354
NdefRecord.cpp
Normal file
354
NdefRecord.cpp
Normal file
@@ -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
|
||||
60
NdefRecord.h
Normal file
60
NdefRecord.h
Normal file
@@ -0,0 +1,60 @@
|
||||
#ifndef NdefRecord_h
|
||||
#define NdefRecord_h
|
||||
|
||||
#include <Due.h>
|
||||
#include <Arduino.h>
|
||||
#include <Ndef.h>
|
||||
|
||||
#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
|
||||
218
NfcAdapter.cpp
Normal file
218
NfcAdapter.cpp
Normal file
@@ -0,0 +1,218 @@
|
||||
#include <NfcAdapter.h>
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
45
NfcAdapter.h
Normal file
45
NfcAdapter.h
Normal file
@@ -0,0 +1,45 @@
|
||||
#ifndef NfcAdapter_h
|
||||
#define NfcAdapter_h
|
||||
|
||||
#include <PN532Interface.h>
|
||||
#include <PN532.h>
|
||||
#include <NfcTag.h>
|
||||
#include <Ndef.h>
|
||||
|
||||
// Drivers
|
||||
#include <MifareClassic.h>
|
||||
#include <MifareUltralight.h>
|
||||
|
||||
#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
|
||||
9
NfcDriver.h
Normal file
9
NfcDriver.h
Normal file
@@ -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()
|
||||
}
|
||||
123
NfcTag.cpp
Normal file
123
NfcTag.cpp
Normal file
@@ -0,0 +1,123 @@
|
||||
#include <NfcTag.h>
|
||||
|
||||
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
|
||||
36
NfcTag.h
Normal file
36
NfcTag.h
Normal file
@@ -0,0 +1,36 @@
|
||||
#ifndef NfcTag_h
|
||||
#define NfcTag_h
|
||||
|
||||
#include <inttypes.h>
|
||||
#include <Arduino.h>
|
||||
#include <NdefMessage.h>
|
||||
|
||||
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
|
||||
134
README.md
Normal file
134
README.md
Normal file
@@ -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 <Wire.h>
|
||||
#include <PN532_I2C.h>
|
||||
#include <PN532.h>
|
||||
#include <NfcAdapter.h>
|
||||
|
||||
PN532_I2C pn532_i2c(Wire);
|
||||
NfcAdapter nfc = NfcAdapter(pn532_i2c);
|
||||
|
||||
For the Seeed Shield using SPI
|
||||
|
||||
#include <SPI.h>
|
||||
#include <PN532_SPI.h>
|
||||
#include <PN532.h>
|
||||
#include <NfcAdapter.h>
|
||||
|
||||
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 <a href="http://www.anrdoezrs.net/click-7521423-11260198-1430755877000?url=http%3A%2F%2Fshop.oreilly.com%2Fproduct%2F0636920021193.do%3Fcmp%3Daf-prog-books-videos-product_cj_9781449372064_%2525zp&cjsku=0636920021193" target="_top">
|
||||
Beginning NFC: Near Field Communication with Arduino, Android, and PhoneGap</a><img src="http://www.lduhtrp.net/image-7521423-11260198-1430755877000" width="1" height="1" border="0"/>.
|
||||
|
||||
<a href="http://www.tkqlhce.com/click-7521423-11260198-1430755877000?url=http%3A%2F%2Fshop.oreilly.com%2Fproduct%2F0636920021193.do%3Fcmp%3Daf-prog-books-videos-product_cj_9781449372064_%2525zp&cjsku=0636920021193" target="_top"><img src="http://akamaicovers.oreilly.com/images/0636920021193/cat.gif" border="0" alt="Beginning NFC"/></a><img src="http://www.awltovhc.com/image-7521423-11260198-1430755877000" width="1" height="1" border="0"/>
|
||||
|
||||
## License
|
||||
|
||||
[BSD License](https://github.com/don/Ndef/blob/master/LICENSE.txt) (c) 2013-2014, Don Coleman
|
||||
45
examples/CleanTag/CleanTag.ino
Normal file
45
examples/CleanTag/CleanTag.ino
Normal file
@@ -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 <SPI.h>
|
||||
#include <PN532_SPI.h>
|
||||
#include <PN532.h>
|
||||
#include <NfcAdapter.h>
|
||||
|
||||
PN532_SPI pn532spi(SPI, 10);
|
||||
NfcAdapter nfc = NfcAdapter(pn532spi);
|
||||
#else
|
||||
|
||||
#include <Wire.h>
|
||||
#include <PN532_I2C.h>
|
||||
#include <PN532.h>
|
||||
#include <NfcAdapter.h>
|
||||
|
||||
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);
|
||||
}
|
||||
42
examples/EraseTag/EraseTag.ino
Normal file
42
examples/EraseTag/EraseTag.ino
Normal file
@@ -0,0 +1,42 @@
|
||||
// Erases a NFC tag by writing an empty NDEF message
|
||||
|
||||
#if 0
|
||||
#include <SPI.h>
|
||||
#include <PN532_SPI.h>
|
||||
#include <PN532.h>
|
||||
#include <NfcAdapter.h>
|
||||
|
||||
PN532_SPI pn532spi(SPI, 10);
|
||||
NfcAdapter nfc = NfcAdapter(pn532spi);
|
||||
#else
|
||||
|
||||
#include <Wire.h>
|
||||
#include <PN532_I2C.h>
|
||||
#include <PN532.h>
|
||||
#include <NfcAdapter.h>
|
||||
|
||||
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);
|
||||
}
|
||||
44
examples/FormatTag/FormatTag.ino
Normal file
44
examples/FormatTag/FormatTag.ino
Normal file
@@ -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 <SPI.h>
|
||||
#include <PN532_SPI.h>
|
||||
#include <PN532.h>
|
||||
#include <NfcAdapter.h>
|
||||
|
||||
PN532_SPI pn532spi(SPI, 10);
|
||||
NfcAdapter nfc = NfcAdapter(pn532spi);
|
||||
#else
|
||||
|
||||
#include <Wire.h>
|
||||
#include <PN532_I2C.h>
|
||||
#include <PN532.h>
|
||||
#include <NfcAdapter.h>
|
||||
|
||||
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);
|
||||
}
|
||||
30
examples/P2P_Receive/P2P_Receive.ino
Normal file
30
examples/P2P_Receive/P2P_Receive.ino
Normal file
@@ -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);
|
||||
}
|
||||
|
||||
71
examples/P2P_Receive_LCD/P2P_Receive_LCD.ino
Normal file
71
examples/P2P_Receive_LCD/P2P_Receive_LCD.ino
Normal file
@@ -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);
|
||||
}
|
||||
42
examples/P2P_Send/P2P_Send.ino
Normal file
42
examples/P2P_Send/P2P_Send.ino
Normal file
@@ -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);
|
||||
}
|
||||
35
examples/ReadTag/ReadTag.ino
Normal file
35
examples/ReadTag/ReadTag.ino
Normal file
@@ -0,0 +1,35 @@
|
||||
|
||||
#if 0
|
||||
#include <SPI.h>
|
||||
#include <PN532_SPI.h>
|
||||
#include <PN532.h>
|
||||
#include <NfcAdapter.h>
|
||||
|
||||
PN532_SPI pn532spi(SPI, 10);
|
||||
NfcAdapter nfc = NfcAdapter(pn532spi);
|
||||
#else
|
||||
|
||||
#include <Wire.h>
|
||||
#include <PN532_I2C.h>
|
||||
#include <PN532.h>
|
||||
#include <NfcAdapter.h>
|
||||
|
||||
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);
|
||||
}
|
||||
86
examples/ReadTagExtended/ReadTagExtended.ino
Normal file
86
examples/ReadTagExtended/ReadTagExtended.ino
Normal file
@@ -0,0 +1,86 @@
|
||||
#if 0
|
||||
#include <SPI.h>
|
||||
#include <PN532_SPI.h>
|
||||
#include <PN532.h>
|
||||
#include <NfcAdapter.h>
|
||||
|
||||
PN532_SPI pn532spi(SPI, 10);
|
||||
NfcAdapter nfc = NfcAdapter(pn532spi);
|
||||
#else
|
||||
|
||||
#include <Wire.h>
|
||||
#include <PN532_I2C.h>
|
||||
#include <PN532.h>
|
||||
#include <NfcAdapter.h>
|
||||
|
||||
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);
|
||||
}
|
||||
40
examples/WriteTag/WriteTag.ino
Normal file
40
examples/WriteTag/WriteTag.ino
Normal file
@@ -0,0 +1,40 @@
|
||||
#if 0
|
||||
#include <SPI.h>
|
||||
#include <PN532_SPI.h>
|
||||
#include <PN532.h>
|
||||
#include <NfcAdapter.h>
|
||||
|
||||
PN532_SPI pn532spi(SPI, 10);
|
||||
NfcAdapter nfc = NfcAdapter(pn532spi);
|
||||
#else
|
||||
|
||||
#include <Wire.h>
|
||||
#include <PN532_I2C.h>
|
||||
#include <PN532.h>
|
||||
#include <NfcAdapter.h>
|
||||
|
||||
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);
|
||||
}
|
||||
41
examples/WriteTagMultipleRecords/WriteTagMultipleRecords.ino
Normal file
41
examples/WriteTagMultipleRecords/WriteTagMultipleRecords.ino
Normal file
@@ -0,0 +1,41 @@
|
||||
#if 0
|
||||
#include <SPI.h>
|
||||
#include <PN532_SPI.h>
|
||||
#include <PN532.h>
|
||||
#include <NfcAdapter.h>
|
||||
|
||||
PN532_SPI pn532spi(SPI, 10);
|
||||
NfcAdapter nfc = NfcAdapter(pn532spi);
|
||||
#else
|
||||
|
||||
#include <Wire.h>
|
||||
#include <PN532_I2C.h>
|
||||
#include <PN532.h>
|
||||
#include <NfcAdapter.h>
|
||||
|
||||
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);
|
||||
}
|
||||
55
keywords.txt
Normal file
55
keywords.txt
Normal file
@@ -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
|
||||
10
library.properties
Normal file
10
library.properties
Normal file
@@ -0,0 +1,10 @@
|
||||
name=NDEF
|
||||
version=1.1.0
|
||||
author=Don Coleman <don.coleman@gmail.com>
|
||||
maintainer=Don Coleman <don.coleman@gmail.com>
|
||||
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
|
||||
222
tests/NdefMemoryTest/NdefMemoryTest.ino
Normal file
222
tests/NdefMemoryTest/NdefMemoryTest.ino
Normal file
@@ -0,0 +1,222 @@
|
||||
#include <Wire.h>
|
||||
#include <PN532.h>
|
||||
#include <NdefMessage.h>
|
||||
#include <NdefRecord.h>
|
||||
#include <ArduinoUnit.h>
|
||||
|
||||
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();
|
||||
}
|
||||
238
tests/NdefMessageTest/NdefMessageTest.ino
Normal file
238
tests/NdefMessageTest/NdefMessageTest.ino
Normal file
@@ -0,0 +1,238 @@
|
||||
#include <Wire.h>
|
||||
#include <PN532.h>
|
||||
#include <NdefMessage.h>
|
||||
#include <NdefRecord.h>
|
||||
#include <ArduinoUnit.h>
|
||||
|
||||
// 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();
|
||||
}
|
||||
173
tests/NdefUnitTest/NdefUnitTest.ino
Normal file
173
tests/NdefUnitTest/NdefUnitTest.ino
Normal file
@@ -0,0 +1,173 @@
|
||||
#include <Wire.h>
|
||||
#include <PN532.h>
|
||||
#include <NdefRecord.h>
|
||||
#include <ArduinoUnit.h>
|
||||
|
||||
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();
|
||||
}
|
||||
37
tests/NfcTagTest/NfcTagTest.ino
Normal file
37
tests/NfcTagTest/NfcTagTest.ino
Normal file
@@ -0,0 +1,37 @@
|
||||
#include <Wire.h>
|
||||
#include <PN532.h>
|
||||
#include <NfcTag.h>
|
||||
#include <ArduinoUnit.h>
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user