├── .github └── workflows │ ├── run-cl-arduino.yml │ ├── stale.yml │ └── sync_issues.yml ├── .gitlab-ci.yml ├── .travis.yml ├── LICENSE.txt ├── README.md ├── examples ├── CleanTag │ └── CleanTag.ino ├── EraseTag │ └── EraseTag.ino ├── FormatTag │ └── FormatTag.ino ├── P2P_Receive │ └── P2P_Receive.ino ├── P2P_Receive_LCD │ └── P2P_Receive_LCD.ino ├── P2P_Send │ └── P2P_Send.ino ├── ReadTag │ └── ReadTag.ino ├── ReadTagExtended │ └── ReadTagExtended.ino ├── WriteTag │ └── WriteTag.ino └── WriteTagMultipleRecords │ └── WriteTagMultipleRecords.ino ├── keywords.txt ├── library.properties ├── src ├── Due.h ├── MifareClassic.cpp ├── MifareClassic.h ├── MifareUltralight.cpp ├── MifareUltralight.h ├── Ndef.cpp ├── Ndef.h ├── NdefMessage.cpp ├── NdefMessage.h ├── NdefRecord.cpp ├── NdefRecord.h ├── NfcAdapter.cpp ├── NfcAdapter.h ├── NfcDriver.h ├── NfcTag.cpp ├── NfcTag.h └── PN532 │ ├── .gitignore │ ├── PN532 │ ├── PN532.cpp │ ├── PN532.h │ ├── PN532Interface.h │ ├── PN532_debug.h │ ├── README.md │ ├── emulatetag.cpp │ ├── emulatetag.h │ ├── license.txt │ ├── llcp.cpp │ ├── llcp.h │ ├── mac_link.cpp │ ├── mac_link.h │ ├── snep.cpp │ └── snep.h │ ├── PN532_HSU │ ├── PN532_HSU.cpp │ └── PN532_HSU.h │ ├── PN532_I2C │ ├── PN532_I2C.cpp │ └── PN532_I2C.h │ ├── PN532_SPI │ ├── PN532_SPI.cpp │ └── PN532_SPI.h │ ├── PN532_SWHSU │ ├── PN532_SWHSU.cpp │ └── PN532_SWHSU.h │ └── README.md └── tests ├── NdefMemoryTest └── NdefMemoryTest.ino ├── NdefMessageTest └── NdefMessageTest.ino ├── NdefUnitTest └── NdefUnitTest.ino └── NfcTagTest └── NfcTagTest.ino /.github/workflows/run-cl-arduino.yml: -------------------------------------------------------------------------------- 1 | name: Run Ci Arduino 2 | 3 | on: 4 | push: 5 | pull_request: 6 | repository_dispatch: 7 | types: [trigger-workflow] 8 | 9 | jobs: 10 | ci-arduino: 11 | runs-on: ubuntu-latest 12 | 13 | steps: 14 | - name: Checkout repository 15 | uses: actions/checkout@v4 16 | 17 | - name: Checkout script repository 18 | uses: actions/checkout@v4 19 | with: 20 | repository: Seeed-Studio/ci-arduino 21 | path: ci 22 | 23 | 24 | - name: Setup arduino cli 25 | uses: arduino/setup-arduino-cli@v2.0.0 26 | 27 | - name: Create a depend.list file 28 | run: | 29 | # eg: echo "" >> depend.list 30 | echo "arduino-libraries/LiquidCrystal" >> depend.list 31 | 32 | 33 | - name: Create a ignore.list file 34 | run: | 35 | # eg: echo "," >> ignore.list 36 | echo "P2P_Receive,Seeeduino:renesas_uno:XIAO_RA4M1" >> ignore.list 37 | echo "P2P_Send,Seeeduino:renesas_uno:XIAO_RA4M1" >> ignore.list 38 | echo "P2P_Receive_LCD,Seeeduino:renesas_uno:XIAO_RA4M1" >> ignore.list 39 | 40 | 41 | - name: Build sketch 42 | run: ./ci/tools/compile.sh 43 | 44 | - name: Build result 45 | run: | 46 | cat build.log 47 | if [ ${{ github.event_name }} == 'pull_request' ] && [ -f compile.failed ]; then 48 | exit 1 49 | fi 50 | 51 | - name: Generate issue 52 | if: ${{ github.event_name != 'pull_request' }} 53 | run: ./ci/tools/issue.sh 54 | env: 55 | GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} -------------------------------------------------------------------------------- /.github/workflows/stale.yml: -------------------------------------------------------------------------------- 1 | name: 'Close stale issues and PRs' 2 | 3 | on: 4 | workflow_dispatch: 5 | schedule: 6 | - cron: '0 4 * * *' 7 | 8 | jobs: 9 | stale: 10 | runs-on: ubuntu-latest 11 | 12 | steps: 13 | - name: Checkout repository 14 | uses: actions/checkout@v4 15 | 16 | - name: Checkout script repository 17 | uses: actions/checkout@v4 18 | with: 19 | repository: Seeed-Studio/sync-github-all-issues 20 | path: ci 21 | 22 | - name: Run script 23 | run: ./ci/tools/stale.sh 24 | env: 25 | GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} 26 | -------------------------------------------------------------------------------- /.github/workflows/sync_issues.yml: -------------------------------------------------------------------------------- 1 | name: Automate Issue Management 2 | 3 | on: 4 | issues: 5 | types: 6 | - opened 7 | - edited 8 | - assigned 9 | - unassigned 10 | - labeled 11 | - unlabeled 12 | - reopened 13 | 14 | jobs: 15 | add_issue_to_project: 16 | runs-on: ubuntu-latest 17 | steps: 18 | - name: Add issue to GitHub Project 19 | uses: actions/add-to-project@v1.0.2 20 | with: 21 | project-url: https://github.com/orgs/Seeed-Studio/projects/17 22 | github-token: ${{ secrets.ISSUE_ASSEMBLE }} 23 | labeled: bug 24 | label-operator: NOT -------------------------------------------------------------------------------- /.gitlab-ci.yml: -------------------------------------------------------------------------------- 1 | build: 2 | tags: 3 | - nas 4 | script: 5 | - wget -c https://files.seeedstudio.com/arduino/seeed-arduino-ci.sh 6 | - chmod +x seeed-arduino-ci.sh 7 | - bash $PWD/seeed-arduino-ci.sh test 8 | 9 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | 2 | language: generic 3 | dist: bionic 4 | sudo: false 5 | cache: 6 | directories: 7 | - ~/arduino_ide 8 | - ~/.arduino15/packages/ 9 | 10 | before_install: 11 | - wget -c https://files.seeedstudio.com/arduino/seeed-arduino-ci.sh 12 | 13 | script: 14 | - chmod +x seeed-arduino-ci.sh 15 | - cat $PWD/seeed-arduino-ci.sh 16 | - bash $PWD/seeed-arduino-ci.sh test 17 | 18 | notifications: 19 | email: 20 | on_success: change 21 | on_failure: change 22 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Software License Agreement (BSD License) 2 | 3 | Copyright (c) 2013-2014, Don Coleman 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright 10 | notice, this list of conditions and the following disclaimer. 11 | 12 | 2. Redistributions in binary form must reproduce the above copyright 13 | notice, this list of conditions and the following disclaimer in the 14 | documentation and/or other materials provided with the distribution. 15 | 16 | 3. Neither the name of the copyright holders nor the 17 | names of its contributors may be used to endorse or promote products 18 | derived from this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS "AS IS" AND ANY 21 | EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 22 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY 24 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 25 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 26 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 27 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 28 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 29 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Seeed Arduino NFC Library [![Build Status](https://travis-ci.com/Seeed-Studio/Seeed_Arduino_NFC.svg?branch=master)](https://travis-ci.com/Seeed-Studio/Seeed_Arduino_NFC) 2 | 3 | Read and Write NDEF messages on NFC Tags with Arduino. 4 | 5 | NFC Data Exchange Format (NDEF) is a common data format that operates across all NFC devices, regardless of the underlying tag or device technology. 6 | 7 | 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). 8 | 9 | ### Supports 10 | - Reading from Mifare Classic Tags with 4 byte UIDs. 11 | - Writing to Mifare Classic Tags with 4 byte UIDs. 12 | - Reading from Mifare Ultralight tags. 13 | - Writing to Mifare Ultralight tags. 14 | - Peer to Peer with the Seeed Studio shield 15 | 16 | 17 | ## Getting Started 18 | 19 | To use the Ndef library in your code, include the following in your sketch 20 | 21 | For the Adafruit Shield using I2C 22 | ```c++ 23 | #include 24 | #include 25 | 26 | #include 27 | #include 28 | #include 29 | #include 30 | 31 | PN532_I2C pn532_i2c(Wire); 32 | NfcAdapter nfc = NfcAdapter(pn532_i2c); 33 | ``` 34 | For the Seeed Shield using SPI 35 | ```c++ 36 | #include 37 | #include 38 | 39 | #include 40 | #include 41 | #include 42 | #include 43 | 44 | PN532_SPI pn532spi(SPI, 10); 45 | NfcAdapter nfc = NfcAdapter(pn532spi);; 46 | ``` 47 | ### NfcAdapter 48 | 49 | The user interacts with the NfcAdapter to read and write NFC tags using the NFC shield. 50 | 51 | Read a message from a tag 52 | ```c 53 | if (nfc.tagPresent()) { 54 | NfcTag tag = nfc.read(); 55 | tag.print(); 56 | } 57 | ``` 58 | Write a message to a tag 59 | ```c++ 60 | if (nfc.tagPresent()) { 61 | NdefMessage message = NdefMessage(); 62 | message.addTextRecord("Hello, Arduino!"); 63 | success = nfc.write(message); 64 | } 65 | ``` 66 | 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). 67 | ```c++ 68 | if (nfc.tagPresent()) { 69 | success = nfc.erase(); 70 | } 71 | ``` 72 | 73 | Format a Mifare Classic tag as NDEF. 74 | ```c 75 | if (nfc.tagPresent()) { 76 | success = nfc.format(); 77 | } 78 | ``` 79 | 80 | 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. 81 | ```c++ 82 | if (nfc.tagPresent()) { 83 | success = nfc.clean(); 84 | } 85 | ``` 86 | 87 | ### NfcTag 88 | 89 | 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. 90 | 91 | ### NdefMessage 92 | 93 | A NdefMessage consist of one or more NdefRecords. 94 | 95 | The NdefMessage object has helper methods for adding records. 96 | ```c++ 97 | ndefMessage.addTextRecord("hello, world"); 98 | ndefMessage.addUriRecord("http://arduino.cc"); 99 | ``` 100 | 101 | 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. 102 | 103 | ### NdefRecord 104 | 105 | A NdefRecord carries a payload and info about the payload within a NdefMessage. 106 | 107 | ### Peer to Peer 108 | 109 | 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. 110 | 111 | ### Specifications 112 | 113 | 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). 114 | 115 | ### Tests 116 | 117 | 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. 118 | ```shell 119 | $ cd ~ 120 | $ git clone git@github.com:mmurdoch/arduinounit.git 121 | $ cd ~/Documents/Arduino/libraries/ 122 | $ ln -s ~/arduinounit/src ArduinoUnit 123 | ``` 124 | Tests can be run on an Uno without a NFC shield, since the NDEF logic is what is being tested. 125 | 126 | ## Warning 127 | 128 | 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. 129 | 130 | ## Book 131 | Need more info? Check out my book 132 | Beginning NFC: Near Field Communication with Arduino, Android, and PhoneGap. 133 | 134 | Beginning NFC 135 | 136 | ## License 137 | 138 | [BSD License](https://github.com/don/Ndef/blob/master/LICENSE.txt) (c) 2013-2014, Don Coleman 139 | -------------------------------------------------------------------------------- /examples/CleanTag/CleanTag.ino: -------------------------------------------------------------------------------- 1 | // Clean resets a tag back to factory-like state 2 | // For Mifare Classic, tag is zero'd and reformatted as Mifare Classic 3 | // For Mifare Ultralight, tags is zero'd and left empty 4 | #include 5 | #include 6 | #if 0 7 | #include 8 | #include 9 | 10 | PN532_SPI pn532spi(SPI, 10); 11 | NfcAdapter nfc = NfcAdapter(pn532spi); 12 | #else 13 | 14 | #include 15 | #include 16 | 17 | 18 | PN532_I2C pn532_i2c(Wire); 19 | NfcAdapter nfc = NfcAdapter(pn532_i2c); 20 | #endif 21 | 22 | void setup(void) { 23 | SERIAL.begin(9600); 24 | SERIAL.println("NFC Tag Cleaner"); 25 | nfc.begin(); 26 | } 27 | 28 | void loop(void) { 29 | 30 | SERIAL.println("\nPlace a tag on the NFC reader to clean."); 31 | 32 | if (nfc.tagPresent()) { 33 | 34 | bool success = nfc.clean(); 35 | if (success) { 36 | SERIAL.println("\nSuccess, tag restored to factory state."); 37 | } else { 38 | SERIAL.println("\nError, unable to clean tag."); 39 | } 40 | 41 | } 42 | delay(5000); 43 | } 44 | -------------------------------------------------------------------------------- /examples/EraseTag/EraseTag.ino: -------------------------------------------------------------------------------- 1 | // Erases a NFC tag by writing an empty NDEF message 2 | #include 3 | #include 4 | #if 0 5 | #include 6 | #include 7 | 8 | 9 | PN532_SPI pn532spi(SPI, 10); 10 | NfcAdapter nfc = NfcAdapter(pn532spi); 11 | #else 12 | 13 | #include 14 | #include 15 | 16 | 17 | PN532_I2C pn532_i2c(Wire); 18 | NfcAdapter nfc = NfcAdapter(pn532_i2c); 19 | #endif 20 | 21 | void setup(void) { 22 | SERIAL.begin(9600); 23 | SERIAL.println("NFC Tag Eraser"); 24 | nfc.begin(); 25 | } 26 | 27 | void loop(void) { 28 | SERIAL.println("\nPlace a tag on the NFC reader to erase."); 29 | 30 | if (nfc.tagPresent()) { 31 | 32 | bool success = nfc.erase(); 33 | if (success) { 34 | SERIAL.println("\nSuccess, tag contains an empty record."); 35 | } else { 36 | SERIAL.println("\nUnable to erase tag."); 37 | } 38 | 39 | } 40 | delay(5000); 41 | } 42 | -------------------------------------------------------------------------------- /examples/FormatTag/FormatTag.ino: -------------------------------------------------------------------------------- 1 | // Formats a Mifare Classic tags as an NDEF tag 2 | // This will fail if the tag is already formatted NDEF 3 | // nfc.clean will turn a NDEF formatted Mifare Classic tag back to the Mifare Classic format 4 | #include 5 | #include 6 | #if 0 7 | #include 8 | #include 9 | 10 | 11 | PN532_SPI pn532spi(SPI, 10); 12 | NfcAdapter nfc = NfcAdapter(pn532spi); 13 | #else 14 | 15 | #include 16 | #include 17 | 18 | 19 | PN532_I2C pn532_i2c(Wire); 20 | NfcAdapter nfc = NfcAdapter(pn532_i2c); 21 | #endif 22 | 23 | void setup(void) { 24 | SERIAL.begin(9600); 25 | SERIAL.println("NDEF Formatter"); 26 | nfc.begin(); 27 | } 28 | 29 | void loop(void) { 30 | 31 | SERIAL.println("\nPlace an unformatted Mifare Classic tag on the reader."); 32 | if (nfc.tagPresent()) { 33 | 34 | bool success = nfc.format(); 35 | if (success) { 36 | SERIAL.println("\nSuccess, tag formatted as NDEF."); 37 | } else { 38 | SERIAL.println("\nFormat failed."); 39 | } 40 | 41 | } 42 | delay(5000); 43 | } 44 | -------------------------------------------------------------------------------- /examples/P2P_Receive/P2P_Receive.ino: -------------------------------------------------------------------------------- 1 | // Receive a NDEF message from a Peer 2 | // Requires SPI. Tested with Seeed Studio NFC Shield v2 3 | #include 4 | #include 5 | #include "SPI.h" 6 | #include "PN532/PN532_SPI/PN532_SPI.h" 7 | #include "PN532/PN532/snep.h" 8 | #include "NdefMessage.h" 9 | 10 | PN532_SPI pn532spi(SPI, 10); 11 | SNEP nfc(pn532spi); 12 | uint8_t ndefBuf[128]; 13 | 14 | void setup() { 15 | SERIAL.begin(9600); 16 | SERIAL.println("NFC Peer to Peer Example - Receive Message"); 17 | } 18 | 19 | void loop() { 20 | SERIAL.println("Waiting for message from Peer"); 21 | int msgSize = nfc.read(ndefBuf, sizeof(ndefBuf)); 22 | if (msgSize > 0) { 23 | NdefMessage msg = NdefMessage(ndefBuf, msgSize); 24 | msg.print(); 25 | SERIAL.println("\nSuccess"); 26 | } else { 27 | SERIAL.println("Failed"); 28 | } 29 | delay(3000); 30 | } 31 | 32 | -------------------------------------------------------------------------------- /examples/P2P_Receive_LCD/P2P_Receive_LCD.ino: -------------------------------------------------------------------------------- 1 | // Receive a NDEF message from a Peer and 2 | // display the payload of the first record on a LCD 3 | // 4 | // SeeedStudio NFC shield http://www.seeedstudio.com/depot/NFC-Shield-V20-p-1370.html 5 | // LCD using the Adafruit backpack http://adafru.it/292 6 | // Adafruit Liquid Crystal library https://github.com/adafruit/LiquidCrystal 7 | // Use a Android of BlackBerry phone to send a message to the NFC shield 8 | 9 | #include 10 | #include 11 | 12 | #include "SPI.h" 13 | #include "PN532/PN532_SPI/PN532_SPI.h" 14 | #include "PN532/PN532/snep.h" 15 | #include "NdefMessage.h" 16 | 17 | #include "Wire.h" 18 | #include "LiquidCrystal.h" 19 | 20 | PN532_SPI pn532spi(SPI, 10); 21 | SNEP nfc(pn532spi); 22 | uint8_t ndefBuf[128]; 23 | 24 | // // Connect via i2c, default address #0 (A0-A2 not jumpered) 25 | // LiquidCrystal lcd(0); 26 | 27 | const int rs = D0, en = D1, d4 = D2, d5 = D3, d6 = D4, d7 = D5; 28 | LiquidCrystal lcd(rs, en, d4, d5, d6, d7); 29 | 30 | void setup() { 31 | SERIAL.begin(9600); 32 | // set up the LCD's number of rows and columns: 33 | lcd.begin(16, 2); 34 | SERIAL.println("NFC Peer to Peer Example - Receive Message"); 35 | } 36 | 37 | void loop() { 38 | SERIAL.println("Waiting for message from a peer"); 39 | int msgSize = nfc.read(ndefBuf, sizeof(ndefBuf)); 40 | if (msgSize > 0) { 41 | NdefMessage msg = NdefMessage(ndefBuf, msgSize); 42 | msg.print(); 43 | 44 | NdefRecord record = msg.getRecord(0); 45 | 46 | int payloadLength = record.getPayloadLength(); 47 | byte payload[payloadLength]; 48 | record.getPayload(payload); 49 | 50 | // The TNF and Type are used to determine how your application processes the payload 51 | // There's no generic processing for the payload, it's returned as a byte[] 52 | int startChar = 0; 53 | if (record.getTnf() == TNF_WELL_KNOWN && record.getType() == "T") { // text message 54 | // skip the language code 55 | startChar = payload[0] + 1; 56 | } else if (record.getTnf() == TNF_WELL_KNOWN && record.getType() == "U") { // URI 57 | // skip the url prefix (future versions should decode) 58 | startChar = 1; 59 | } 60 | 61 | // Force the data into a String (might fail for some content) 62 | // Real code should use smarter processing 63 | String payloadAsString = ""; 64 | for (int c = startChar; c < payloadLength; c++) { 65 | payloadAsString += (char)payload[c]; 66 | } 67 | 68 | // print on the LCD display 69 | lcd.setCursor(0, 0); 70 | lcd.print(payloadAsString); 71 | 72 | SERIAL.println("\nSuccess"); 73 | } else { 74 | SERIAL.println("Failed"); 75 | } 76 | delay(3000); 77 | } 78 | -------------------------------------------------------------------------------- /examples/P2P_Send/P2P_Send.ino: -------------------------------------------------------------------------------- 1 | // Sends a NDEF Message to a Peer 2 | // Requires SPI. Tested with Seeed Studio NFC Shield v2 3 | #include 4 | #include 5 | #include "SPI.h" 6 | #include "PN532/PN532_SPI/PN532_SPI.h" 7 | #include "PN532/PN532/snep.h" 8 | #include "NdefMessage.h" 9 | 10 | PN532_SPI pn532spi(SPI, 10); 11 | SNEP nfc(pn532spi); 12 | uint8_t ndefBuf[128]; 13 | 14 | void setup() { 15 | SERIAL.begin(9600); 16 | SERIAL.println("NFC Peer to Peer Example - Send Message"); 17 | } 18 | 19 | void loop() { 20 | SERIAL.println("Send a message to Peer"); 21 | 22 | NdefMessage message = NdefMessage(); 23 | message.addUriRecord("http://shop.oreilly.com/product/mobile/0636920021193.do"); 24 | //message.addUriRecord("http://arduino.cc"); 25 | //message.addUriRecord("https://github.com/don/NDEF"); 26 | 27 | 28 | int messageSize = message.getEncodedSize(); 29 | if (messageSize > sizeof(ndefBuf)) { 30 | SERIAL.println("ndefBuf is too small"); 31 | while (1) { 32 | } 33 | } 34 | 35 | message.encode(ndefBuf); 36 | if (0 >= nfc.write(ndefBuf, messageSize)) { 37 | SERIAL.println("Failed"); 38 | } else { 39 | SERIAL.println("Success"); 40 | } 41 | 42 | delay(3000); 43 | } 44 | -------------------------------------------------------------------------------- /examples/ReadTag/ReadTag.ino: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #if 0 // use SPI 5 | #include 6 | #include 7 | PN532_SPI pn532spi(SPI, 9); 8 | NfcAdapter nfc = NfcAdapter(pn532spi); 9 | #elif 0 // use hardware serial 10 | 11 | #include 12 | PN532_HSU pn532hsu(Serial1); 13 | NfcAdapter nfc(pn532hsu); 14 | #elif 0 // use software serial 15 | 16 | #include 17 | #include "SoftwareSerial.h" 18 | SoftwareSerial SWSerial(2, 3); 19 | PN532_SWHSU pn532swhsu(SWSerial); 20 | NfcAdapter nfc(pn532swhsu); 21 | #else //use I2C 22 | 23 | #include 24 | #include 25 | 26 | PN532_I2C pn532_i2c(Wire); 27 | NfcAdapter nfc = NfcAdapter(pn532_i2c); 28 | #endif 29 | 30 | void setup(void) { 31 | SERIAL.begin(9600); 32 | SERIAL.println("NDEF Reader"); 33 | nfc.begin(); 34 | } 35 | 36 | void loop(void) { 37 | SERIAL.println("\nScan a NFC tag\n"); 38 | if (nfc.tagPresent()) { 39 | NfcTag tag = nfc.read(); 40 | tag.print(); 41 | } 42 | delay(5000); 43 | } 44 | -------------------------------------------------------------------------------- /examples/ReadTagExtended/ReadTagExtended.ino: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #if 0 4 | #include 5 | #include 6 | 7 | 8 | PN532_SPI pn532spi(SPI, 10); 9 | NfcAdapter nfc = NfcAdapter(pn532spi); 10 | #else 11 | 12 | #include 13 | #include 14 | 15 | 16 | PN532_I2C pn532_i2c(Wire); 17 | NfcAdapter nfc = NfcAdapter(pn532_i2c); 18 | #endif 19 | 20 | void setup(void) { 21 | SERIAL.begin(9600); 22 | SERIAL.println("NDEF Reader"); 23 | nfc.begin(); 24 | } 25 | 26 | void loop(void) { 27 | SERIAL.println("\nScan a NFC tag\n"); 28 | 29 | if (nfc.tagPresent()) { 30 | NfcTag tag = nfc.read(); 31 | SERIAL.println(tag.getTagType()); 32 | SERIAL.print("UID: "); SERIAL.println(tag.getUidString()); 33 | 34 | if (tag.hasNdefMessage()) { // every tag won't have a message 35 | 36 | NdefMessage message = tag.getNdefMessage(); 37 | SERIAL.print("\nThis NFC Tag contains an NDEF Message with "); 38 | SERIAL.print(message.getRecordCount()); 39 | SERIAL.print(" NDEF Record"); 40 | if (message.getRecordCount() != 1) { 41 | SERIAL.print("s"); 42 | } 43 | SERIAL.println("."); 44 | 45 | // cycle through the records, printing some info from each 46 | int recordCount = message.getRecordCount(); 47 | for (int i = 0; i < recordCount; i++) { 48 | SERIAL.print("\nNDEF Record "); SERIAL.println(i + 1); 49 | NdefRecord record = message.getRecord(i); 50 | // NdefRecord record = message[i]; // alternate syntax 51 | 52 | SERIAL.print(" TNF: "); SERIAL.println(record.getTnf()); 53 | SERIAL.print(" Type: "); SERIAL.println(record.getType()); // will be "" for TNF_EMPTY 54 | 55 | // The TNF and Type should be used to determine how your application processes the payload 56 | // There's no generic processing for the payload, it's returned as a byte[] 57 | int payloadLength = record.getPayloadLength(); 58 | byte payload[payloadLength]; 59 | record.getPayload(payload); 60 | 61 | // Print the Hex and Printable Characters 62 | SERIAL.print(" Payload (HEX): "); 63 | PrintHexChar(payload, payloadLength); 64 | 65 | // Force the data into a String (might work depending on the content) 66 | // Real code should use smarter processing 67 | String payloadAsString = ""; 68 | for (int c = 0; c < payloadLength; c++) { 69 | payloadAsString += (char)payload[c]; 70 | } 71 | SERIAL.print(" Payload (as String): "); 72 | SERIAL.println(payloadAsString); 73 | 74 | // id is probably blank and will return "" 75 | String uid = record.getId(); 76 | if (uid != "") { 77 | SERIAL.print(" ID: "); SERIAL.println(uid); 78 | } 79 | } 80 | } 81 | } 82 | delay(3000); 83 | } 84 | -------------------------------------------------------------------------------- /examples/WriteTag/WriteTag.ino: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #if 0 4 | #include 5 | #include 6 | 7 | 8 | PN532_SPI pn532spi(SPI, 10); 9 | NfcAdapter nfc = NfcAdapter(pn532spi); 10 | #else 11 | 12 | #include 13 | #include 14 | 15 | 16 | PN532_I2C pn532_i2c(Wire); 17 | NfcAdapter nfc = NfcAdapter(pn532_i2c); 18 | #endif 19 | 20 | void setup() { 21 | SERIAL.begin(9600); 22 | SERIAL.println("NDEF Writer"); 23 | nfc.begin(); 24 | } 25 | 26 | void loop() { 27 | SERIAL.println("\nPlace a formatted Mifare Classic or Ultralight NFC tag on the reader."); 28 | if (nfc.tagPresent()) { 29 | NdefMessage message = NdefMessage(); 30 | message.addUriRecord("http://arduino.cc"); 31 | 32 | bool success = nfc.write(message); 33 | if (success) { 34 | SERIAL.println("Success. Try reading this tag with your phone."); 35 | } else { 36 | SERIAL.println("Write failed."); 37 | } 38 | } 39 | delay(5000); 40 | } -------------------------------------------------------------------------------- /examples/WriteTagMultipleRecords/WriteTagMultipleRecords.ino: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #if 0 4 | #include 5 | #include 6 | 7 | 8 | PN532_SPI pn532spi(SPI, 10); 9 | NfcAdapter nfc = NfcAdapter(pn532spi); 10 | #else 11 | 12 | #include 13 | #include 14 | 15 | 16 | PN532_I2C pn532_i2c(Wire); 17 | NfcAdapter nfc = NfcAdapter(pn532_i2c); 18 | #endif 19 | 20 | void setup() { 21 | SERIAL.begin(9600); 22 | SERIAL.println("NDEF Writer"); 23 | nfc.begin(); 24 | } 25 | 26 | void loop() { 27 | SERIAL.println("\nPlace a formatted Mifare Classic NFC tag on the reader."); 28 | if (nfc.tagPresent()) { 29 | NdefMessage message = NdefMessage(); 30 | message.addTextRecord("Hello, Arduino!"); 31 | message.addUriRecord("http://arduino.cc"); 32 | message.addTextRecord("Goodbye, Arduino!"); 33 | boolean success = nfc.write(message); 34 | if (success) { 35 | SERIAL.println("Success. Try reading this tag with your phone."); 36 | } else { 37 | SERIAL.println("Write failed"); 38 | } 39 | } 40 | delay(3000); 41 | } 42 | -------------------------------------------------------------------------------- /keywords.txt: -------------------------------------------------------------------------------- 1 | ####################################### 2 | # Syntax Coloring Map For Ndef 3 | ####################################### 4 | 5 | ####################################### 6 | # Datatypes (KEYWORD1) 7 | ####################################### 8 | 9 | MifareClassic KEYWORD1 10 | MifareUltralight KEYWORD1 11 | NdefMessage KEYWORD1 12 | NdefRecord KEYWORD1 13 | NfcAdapter KEYWORD1 14 | NfcDriver KEYWORD1 15 | NfcTag KEYWORD1 16 | 17 | ####################################### 18 | # Methods and Functions (KEYWORD2) 19 | ####################################### 20 | 21 | addEmptyRecord KEYWORD2 22 | addMimeMediaRecord KEYWORD2 23 | addRecord KEYWORD2 24 | addTextRecord KEYWORD2 25 | addUriRecord KEYWORD2 26 | begin KEYWORD2 27 | encode KEYWORD2 28 | erase KEYWORD2 29 | format KEYWORD2 30 | getEncodedSize KEYWORD2 31 | getId KEYWORD2 32 | getIdLength KEYWORD2 33 | getNdefMessage KEYWORD2 34 | getPayload KEYWORD2 35 | getPayloadLength KEYWORD2 36 | getRecord KEYWORD2 37 | getRecordCount KEYWORD2 38 | getTagType KEYWORD2 39 | getTnf KEYWORD2 40 | getType KEYWORD2 41 | getTypeLength KEYWORD2 42 | getUid KEYWORD2 43 | getUidLength KEYWORD2 44 | getUidString KEYWORD2 45 | hasNdefMessage KEYWORD2 46 | print KEYWORD2 47 | read KEYWORD2 48 | setId KEYWORD2 49 | setPayload KEYWORD2 50 | setTnf KEYWORD2 51 | setType KEYWORD2 52 | share KEYWORD2 53 | tagPresent KEYWORD2 54 | unshare KEYWORD2 55 | write KEYWORD2 56 | -------------------------------------------------------------------------------- /library.properties: -------------------------------------------------------------------------------- 1 | name=Seeed_Arduino_NFC 2 | version=1.1.0 3 | author=Don Coleman 4 | maintainer=Don Coleman 5 | sentence=An Arduino library for NFC Data Exchange Format (NDEF). 6 | 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. 7 | category=Communication 8 | url=https://github.com/Seeed-Studio/Seeed_Arduino_NFC 9 | architectures=* 10 | -------------------------------------------------------------------------------- /src/Due.h: -------------------------------------------------------------------------------- 1 | // redefine some stuff so code works on Due 2 | // http://arduino.cc/forum/index.php?&topic=153761.0 3 | 4 | #ifndef Due_h 5 | #define Due_h 6 | 7 | #if defined(__SAM3X8E__) 8 | #define PROGMEM 9 | #define pgm_read_byte(x) (*((char *)x)) 10 | // #define pgm_read_word(x) (*((short *)(x & 0xfffffffe))) 11 | #define pgm_read_word(x) ( ((*((unsigned char *)x + 1)) << 8) + (*((unsigned char *)x))) 12 | #define pgm_read_byte_near(x) (*((char *)x)) 13 | #define pgm_read_byte_far(x) (*((char *)x)) 14 | // #define pgm_read_word_near(x) (*((short *)(x & 0xfffffffe)) 15 | // #define pgm_read_word_far(x) (*((short *)(x & 0xfffffffe))) 16 | #define pgm_read_word_near(x) ( ((*((unsigned char *)x + 1)) << 8) + (*((unsigned char *)x))) 17 | #define pgm_read_word_far(x) ( ((*((unsigned char *)x + 1)) << 8) + (*((unsigned char *)x)))) 18 | #define PSTR(x) x 19 | #if defined F 20 | #undef F 21 | #endif 22 | #define F(X) (X) 23 | #endif 24 | 25 | #endif -------------------------------------------------------------------------------- /src/MifareClassic.cpp: -------------------------------------------------------------------------------- 1 | #include "MifareClassic.h" 2 | #ifdef NDEF_SUPPORT_MIFARE_CLASSIC 3 | 4 | #define BLOCK_SIZE 16 5 | #define LONG_TLV_SIZE 4 6 | #define SHORT_TLV_SIZE 2 7 | 8 | #define MIFARE_CLASSIC ("Mifare Classic") 9 | 10 | MifareClassic::MifareClassic(PN532& nfcShield) { 11 | _nfcShield = &nfcShield; 12 | } 13 | 14 | MifareClassic::~MifareClassic() { 15 | } 16 | 17 | NfcTag MifareClassic::read(byte* uid, unsigned int uidLength) { 18 | uint8_t key[6] = { 0xD3, 0xF7, 0xD3, 0xF7, 0xD3, 0xF7 }; 19 | int currentBlock = 4; 20 | int messageStartIndex = 0; 21 | int messageLength = 0; 22 | byte data[BLOCK_SIZE]; 23 | 24 | // read first block to get message length 25 | int success = _nfcShield->mifareclassic_AuthenticateBlock(uid, uidLength, currentBlock, 0, key); 26 | if (success) { 27 | success = _nfcShield->mifareclassic_ReadDataBlock(currentBlock, data); 28 | if (success) { 29 | if (!decodeTlv(data, messageLength, messageStartIndex)) { 30 | return NfcTag(uid, uidLength, "ERROR"); // TODO should the error message go in NfcTag? 31 | } 32 | } else { 33 | #ifdef NDEF_USE_SERIAL 34 | SERIAL.print(F("Error. Failed read block ")); SERIAL.println(currentBlock); 35 | #endif 36 | return NfcTag(uid, uidLength, MIFARE_CLASSIC); 37 | } 38 | } else { 39 | #ifdef NDEF_USE_SERIAL 40 | SERIAL.println(F("Tag is not NDEF formatted.")); 41 | #endif 42 | // TODO set tag.isFormatted = false 43 | return NfcTag(uid, uidLength, MIFARE_CLASSIC); 44 | } 45 | 46 | // this should be nested in the message length loop 47 | int index = 0; 48 | int bufferSize = getBufferSize(messageLength); 49 | uint8_t buffer[bufferSize]; 50 | 51 | #ifdef MIFARE_CLASSIC_DEBUG 52 | SERIAL.print(F("Message Length ")); SERIAL.println(messageLength); 53 | SERIAL.print(F("Buffer Size ")); SERIAL.println(bufferSize); 54 | #endif 55 | 56 | while (index < bufferSize) { 57 | 58 | // authenticate on every sector 59 | if (_nfcShield->mifareclassic_IsFirstBlock(currentBlock)) { 60 | success = _nfcShield->mifareclassic_AuthenticateBlock(uid, uidLength, currentBlock, 0, key); 61 | if (!success) { 62 | #ifdef NDEF_USE_SERIAL 63 | SERIAL.print(F("Error. Block Authentication failed for ")); SERIAL.println(currentBlock); 64 | #endif 65 | // TODO error handling 66 | } 67 | } 68 | 69 | // read the data 70 | success = _nfcShield->mifareclassic_ReadDataBlock(currentBlock, &buffer[index]); 71 | if (success) { 72 | #ifdef MIFARE_CLASSIC_DEBUG 73 | SERIAL.print(F("Block ")); SERIAL.print(currentBlock); SERIAL.print(" "); 74 | _nfcShield->PrintHexChar(&buffer[index], BLOCK_SIZE); 75 | #endif 76 | } else { 77 | #ifdef NDEF_USE_SERIAL 78 | SERIAL.print(F("Read failed ")); SERIAL.println(currentBlock); 79 | #endif 80 | // TODO handle errors here 81 | } 82 | 83 | index += BLOCK_SIZE; 84 | currentBlock++; 85 | 86 | // skip the trailer block 87 | if (_nfcShield->mifareclassic_IsTrailerBlock(currentBlock)) { 88 | #ifdef MIFARE_CLASSIC_DEBUG 89 | SERIAL.print(F("Skipping block ")); SERIAL.println(currentBlock); 90 | #endif 91 | currentBlock++; 92 | } 93 | } 94 | 95 | return NfcTag(uid, uidLength, MIFARE_CLASSIC, &buffer[messageStartIndex], messageLength); 96 | } 97 | 98 | int MifareClassic::getBufferSize(int messageLength) { 99 | 100 | int bufferSize = messageLength; 101 | 102 | // TLV header is 2 or 4 bytes, TLV terminator is 1 byte. 103 | if (messageLength < 0xFF) { 104 | bufferSize += SHORT_TLV_SIZE + 1; 105 | } else { 106 | bufferSize += LONG_TLV_SIZE + 1; 107 | } 108 | 109 | // bufferSize needs to be a multiple of BLOCK_SIZE 110 | if (bufferSize % BLOCK_SIZE != 0) { 111 | bufferSize = ((bufferSize / BLOCK_SIZE) + 1) * BLOCK_SIZE; 112 | } 113 | 114 | return bufferSize; 115 | } 116 | 117 | // skip null tlvs (0x0) before the real message 118 | // technically unlimited null tlvs, but we assume 119 | // T & L of TLV in the first block we read 120 | int MifareClassic::getNdefStartIndex(byte* data) { 121 | 122 | for (int i = 0; i < BLOCK_SIZE; i++) { 123 | if (data[i] == 0x0) { 124 | // do nothing, skip 125 | } else if (data[i] == 0x3) { 126 | return i; 127 | } else { 128 | #ifdef NDEF_USE_SERIAL 129 | SERIAL.print("Unknown TLV "); SERIAL.println(data[i], HEX); 130 | #endif 131 | return -2; 132 | } 133 | } 134 | 135 | return -1; 136 | } 137 | 138 | // Decode the NDEF data length from the Mifare TLV 139 | // Leading null TLVs (0x0) are skipped 140 | // Assuming T & L of TLV will be in the first block 141 | // messageLength and messageStartIndex written to the parameters 142 | // success or failure status is returned 143 | // 144 | // { 0x3, LENGTH } 145 | // { 0x3, 0xFF, LENGTH, LENGTH } 146 | bool MifareClassic::decodeTlv(byte* data, int& messageLength, int& messageStartIndex) { 147 | int i = getNdefStartIndex(data); 148 | 149 | if (i < 0 || data[i] != 0x3) { 150 | #ifdef NDEF_USE_SERIAL 151 | SERIAL.println(F("Error. Can't decode message length.")); 152 | #endif 153 | return false; 154 | } else { 155 | if (data[i + 1] == 0xFF) { 156 | messageLength = ((0xFF & data[i + 2]) << 8) | (0xFF & data[i + 3]); 157 | messageStartIndex = i + LONG_TLV_SIZE; 158 | } else { 159 | messageLength = data[i + 1]; 160 | messageStartIndex = i + SHORT_TLV_SIZE; 161 | } 162 | } 163 | 164 | return true; 165 | } 166 | 167 | // Intialized NDEF tag contains one empty NDEF TLV 03 00 FE - AN1304 6.3.1 168 | // 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 169 | boolean MifareClassic::formatNDEF(byte* uid, unsigned int uidLength) { 170 | uint8_t keya[6] = { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }; 171 | uint8_t emptyNdefMesg[16] = {0x03, 0x03, 0xD0, 0x00, 0x00, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; 172 | uint8_t sectorbuffer0[16] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; 173 | uint8_t sectorbuffer4[16] = {0xD3, 0xF7, 0xD3, 0xF7, 0xD3, 0xF7, 0x7F, 0x07, 0x88, 0x40, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; 174 | 175 | boolean success = _nfcShield->mifareclassic_AuthenticateBlock(uid, uidLength, 0, 0, keya); 176 | if (!success) { 177 | #ifdef NDEF_USE_SERIAL 178 | SERIAL.println(F("Unable to authenticate block 0 to enable card formatting!")); 179 | #endif 180 | return false; 181 | } 182 | success = _nfcShield->mifareclassic_FormatNDEF(); 183 | if (!success) { 184 | #ifdef NDEF_USE_SERIAL 185 | SERIAL.println(F("Unable to format the card for NDEF")); 186 | #endif 187 | } else { 188 | for (int i = 4; i < 64; i += 4) { 189 | success = _nfcShield->mifareclassic_AuthenticateBlock(uid, uidLength, i, 0, keya); 190 | 191 | if (success) { 192 | if (i == 4) { // special handling for block 4 193 | if (!(_nfcShield->mifareclassic_WriteDataBlock(i, emptyNdefMesg))) { 194 | #ifdef NDEF_USE_SERIAL 195 | SERIAL.print(F("Unable to write block ")); SERIAL.println(i); 196 | #endif 197 | } 198 | } else { 199 | if (!(_nfcShield->mifareclassic_WriteDataBlock(i, sectorbuffer0))) { 200 | #ifdef NDEF_USE_SERIAL 201 | SERIAL.print(F("Unable to write block ")); SERIAL.println(i); 202 | #endif 203 | } 204 | } 205 | if (!(_nfcShield->mifareclassic_WriteDataBlock(i + 1, sectorbuffer0))) { 206 | #ifdef NDEF_USE_SERIAL 207 | SERIAL.print(F("Unable to write block ")); SERIAL.println(i + 1); 208 | #endif 209 | } 210 | if (!(_nfcShield->mifareclassic_WriteDataBlock(i + 2, sectorbuffer0))) { 211 | #ifdef NDEF_USE_SERIAL 212 | SERIAL.print(F("Unable to write block ")); SERIAL.println(i + 2); 213 | #endif 214 | } 215 | if (!(_nfcShield->mifareclassic_WriteDataBlock(i + 3, sectorbuffer4))) { 216 | #ifdef NDEF_USE_SERIAL 217 | SERIAL.print(F("Unable to write block ")); SERIAL.println(i + 3); 218 | #endif 219 | } 220 | } else { 221 | unsigned int iii = uidLength; 222 | #ifdef NDEF_USE_SERIAL 223 | SERIAL.print(F("Unable to authenticate block ")); SERIAL.println(i); 224 | #endif 225 | _nfcShield->readPassiveTargetID(PN532_MIFARE_ISO14443A, uid, (uint8_t*)&iii); 226 | } 227 | } 228 | } 229 | return success; 230 | } 231 | 232 | #define NR_SHORTSECTOR (32) // Number of short sectors on Mifare 1K/4K 233 | #define NR_LONGSECTOR (8) // Number of long sectors on Mifare 4K 234 | #define NR_BLOCK_OF_SHORTSECTOR (4) // Number of blocks in a short sector 235 | #define NR_BLOCK_OF_LONGSECTOR (16) // Number of blocks in a long sector 236 | 237 | // Determine the sector trailer block based on sector number 238 | #define BLOCK_NUMBER_OF_SECTOR_TRAILER(sector) (((sector)mifareclassic_AuthenticateBlock(uid, uidLength, BLOCK_NUMBER_OF_SECTOR_TRAILER(idx), 1, 256 | (uint8_t*)KEY_DEFAULT_KEYAB); 257 | if (!success) { 258 | #ifdef NDEF_USE_SERIAL 259 | SERIAL.print(F("Authentication failed for sector ")); SERIAL.println(idx); 260 | #endif 261 | return false; 262 | } 263 | 264 | // Step 2: Write to the other blocks 265 | if (idx == 0) { 266 | memset(blockBuffer, 0, sizeof(blockBuffer)); 267 | if (!(_nfcShield->mifareclassic_WriteDataBlock((BLOCK_NUMBER_OF_SECTOR_TRAILER(idx)) - 2, blockBuffer))) { 268 | #ifdef NDEF_USE_SERIAL 269 | SERIAL.print(F("Unable to write to sector ")); SERIAL.println(idx); 270 | #endif 271 | } 272 | } else { 273 | memset(blockBuffer, 0, sizeof(blockBuffer)); 274 | // this block has not to be overwritten for block 0. It contains Tag id and other unique data. 275 | if (!(_nfcShield->mifareclassic_WriteDataBlock((BLOCK_NUMBER_OF_SECTOR_TRAILER(idx)) - 3, blockBuffer))) { 276 | #ifdef NDEF_USE_SERIAL 277 | SERIAL.print(F("Unable to write to sector ")); SERIAL.println(idx); 278 | #endif 279 | } 280 | if (!(_nfcShield->mifareclassic_WriteDataBlock((BLOCK_NUMBER_OF_SECTOR_TRAILER(idx)) - 2, blockBuffer))) { 281 | #ifdef NDEF_USE_SERIAL 282 | SERIAL.print(F("Unable to write to sector ")); SERIAL.println(idx); 283 | #endif 284 | } 285 | } 286 | 287 | memset(blockBuffer, 0, sizeof(blockBuffer)); 288 | 289 | if (!(_nfcShield->mifareclassic_WriteDataBlock((BLOCK_NUMBER_OF_SECTOR_TRAILER(idx)) - 1, blockBuffer))) { 290 | #ifdef NDEF_USE_SERIAL 291 | SERIAL.print(F("Unable to write to sector ")); SERIAL.println(idx); 292 | #endif 293 | } 294 | 295 | // Step 3: Reset both keys to 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 296 | memcpy(blockBuffer, KEY_DEFAULT_KEYAB, sizeof(KEY_DEFAULT_KEYAB)); 297 | memcpy(blockBuffer + 6, blankAccessBits, sizeof(blankAccessBits)); 298 | blockBuffer[9] = 0x69; 299 | memcpy(blockBuffer + 10, KEY_DEFAULT_KEYAB, sizeof(KEY_DEFAULT_KEYAB)); 300 | 301 | // Step 4: Write the trailer block 302 | if (!(_nfcShield->mifareclassic_WriteDataBlock((BLOCK_NUMBER_OF_SECTOR_TRAILER(idx)), blockBuffer))) { 303 | #ifdef NDEF_USE_SERIAL 304 | SERIAL.print(F("Unable to write trailer block of sector ")); SERIAL.println(idx); 305 | #endif 306 | } 307 | } 308 | return true; 309 | } 310 | 311 | boolean MifareClassic::write(NdefMessage& m, byte* uid, unsigned int uidLength) { 312 | 313 | uint8_t encoded[m.getEncodedSize()]; 314 | m.encode(encoded); 315 | 316 | uint8_t buffer[getBufferSize(sizeof(encoded))]; 317 | memset(buffer, 0, sizeof(buffer)); 318 | 319 | #ifdef MIFARE_CLASSIC_DEBUG 320 | SERIAL.print(F("sizeof(encoded) ")); SERIAL.println(sizeof(encoded)); 321 | SERIAL.print(F("sizeof(buffer) ")); SERIAL.println(sizeof(buffer)); 322 | #endif 323 | 324 | if (sizeof(encoded) < 0xFF) { 325 | buffer[0] = 0x3; 326 | buffer[1] = sizeof(encoded); 327 | memcpy(&buffer[2], encoded, sizeof(encoded)); 328 | buffer[2 + sizeof(encoded)] = 0xFE; // terminator 329 | } else { 330 | buffer[0] = 0x3; 331 | buffer[1] = 0xFF; 332 | buffer[2] = ((sizeof(encoded) >> 8) & 0xFF); 333 | buffer[3] = (sizeof(encoded) & 0xFF); 334 | memcpy(&buffer[4], encoded, sizeof(encoded)); 335 | buffer[4 + sizeof(encoded)] = 0xFE; // terminator 336 | } 337 | 338 | // Write to tag 339 | unsigned int index = 0; 340 | int currentBlock = 4; 341 | uint8_t key[6] = { 0xD3, 0xF7, 0xD3, 0xF7, 0xD3, 0xF7 }; // this is Sector 1 - 15 key 342 | 343 | while (index < sizeof(buffer)) { 344 | 345 | if (_nfcShield->mifareclassic_IsFirstBlock(currentBlock)) { 346 | int success = _nfcShield->mifareclassic_AuthenticateBlock(uid, uidLength, currentBlock, 0, key); 347 | if (!success) { 348 | #ifdef NDEF_USE_SERIAL 349 | SERIAL.print(F("Error. Block Authentication failed for ")); SERIAL.println(currentBlock); 350 | #endif 351 | return false; 352 | } 353 | } 354 | 355 | int write_success = _nfcShield->mifareclassic_WriteDataBlock(currentBlock, &buffer[index]); 356 | if (write_success) { 357 | #ifdef MIFARE_CLASSIC_DEBUG 358 | SERIAL.print(F("Wrote block ")); SERIAL.print(currentBlock); SERIAL.print(" - "); 359 | _nfcShield->PrintHexChar(&buffer[index], BLOCK_SIZE); 360 | #endif 361 | } else { 362 | #ifdef NDEF_USE_SERIAL 363 | SERIAL.print(F("Write failed ")); SERIAL.println(currentBlock); 364 | #endif 365 | return false; 366 | } 367 | index += BLOCK_SIZE; 368 | currentBlock++; 369 | 370 | if (_nfcShield->mifareclassic_IsTrailerBlock(currentBlock)) { 371 | // can't write to trailer block 372 | #ifdef MIFARE_CLASSIC_DEBUG 373 | SERIAL.print(F("Skipping block ")); SERIAL.println(currentBlock); 374 | #endif 375 | currentBlock++; 376 | } 377 | 378 | } 379 | 380 | return true; 381 | } 382 | #endif 383 | -------------------------------------------------------------------------------- /src/MifareClassic.h: -------------------------------------------------------------------------------- 1 | #ifndef MifareClassic_h 2 | #define MifareClassic_h 3 | 4 | // Comment out next line to remove Mifare Classic and save memory 5 | #define NDEF_SUPPORT_MIFARE_CLASSIC 6 | 7 | #ifdef NDEF_SUPPORT_MIFARE_CLASSIC 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | class MifareClassic { 15 | public: 16 | MifareClassic(PN532& nfcShield); 17 | ~MifareClassic(); 18 | NfcTag read(byte* uid, unsigned int uidLength); 19 | boolean write(NdefMessage& ndefMessage, byte* uid, unsigned int uidLength); 20 | boolean formatNDEF(byte* uid, unsigned int uidLength); 21 | boolean formatMifare(byte* uid, unsigned int uidLength); 22 | private: 23 | PN532* _nfcShield; 24 | int getBufferSize(int messageLength); 25 | int getNdefStartIndex(byte* data); 26 | bool decodeTlv(byte* data, int& messageLength, int& messageStartIndex); 27 | }; 28 | 29 | #endif 30 | #endif 31 | -------------------------------------------------------------------------------- /src/MifareUltralight.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #define ULTRALIGHT_PAGE_SIZE 4 4 | #define ULTRALIGHT_READ_SIZE 4 // we should be able to read 16 bytes at a time 5 | 6 | #define ULTRALIGHT_DATA_START_PAGE 4 7 | #define ULTRALIGHT_MESSAGE_LENGTH_INDEX 1 8 | #define ULTRALIGHT_DATA_START_INDEX 2 9 | #define ULTRALIGHT_MAX_PAGE 63 10 | 11 | #define NFC_FORUM_TAG_TYPE_2 ("NFC Forum Type 2") 12 | 13 | MifareUltralight::MifareUltralight(PN532& nfcShield) { 14 | nfc = &nfcShield; 15 | ndefStartIndex = 0; 16 | messageLength = 0; 17 | } 18 | 19 | MifareUltralight::~MifareUltralight() { 20 | } 21 | 22 | NfcTag MifareUltralight::read(byte* uid, unsigned int uidLength) { 23 | if (isUnformatted()) { 24 | #ifdef NDEF_USE_SERIAL 25 | SERIAL.println(F("WARNING: Tag is not formatted.")); 26 | #endif 27 | return NfcTag(uid, uidLength, NFC_FORUM_TAG_TYPE_2); 28 | } 29 | 30 | readCapabilityContainer(); // meta info for tag 31 | findNdefMessage(); 32 | calculateBufferSize(); 33 | 34 | if (messageLength == 0) { // data is 0x44 0x03 0x00 0xFE 35 | NdefMessage message = NdefMessage(); 36 | message.addEmptyRecord(); 37 | return NfcTag(uid, uidLength, NFC_FORUM_TAG_TYPE_2, message); 38 | } 39 | 40 | boolean success; 41 | uint8_t page; 42 | uint8_t index = 0; 43 | byte buffer[bufferSize]; 44 | for (page = ULTRALIGHT_DATA_START_PAGE; page < ULTRALIGHT_MAX_PAGE; page++) { 45 | // read the data 46 | success = nfc->mifareultralight_ReadPage(page, &buffer[index]); 47 | if (success) { 48 | #ifdef MIFARE_ULTRALIGHT_DEBUG 49 | SERIAL.print(F("Page ")); SERIAL.print(page); SERIAL.print(" "); 50 | nfc->PrintHexChar(&buffer[index], ULTRALIGHT_PAGE_SIZE); 51 | #endif 52 | } else { 53 | #ifdef NDEF_USE_SERIAL 54 | SERIAL.print(F("Read failed ")); SERIAL.println(page); 55 | #endif 56 | // TODO error handling 57 | messageLength = 0; 58 | break; 59 | } 60 | 61 | if (index >= (messageLength + ndefStartIndex)) { 62 | break; 63 | } 64 | 65 | index += ULTRALIGHT_PAGE_SIZE; 66 | } 67 | 68 | NdefMessage ndefMessage = NdefMessage(&buffer[ndefStartIndex], messageLength); 69 | return NfcTag(uid, uidLength, NFC_FORUM_TAG_TYPE_2, ndefMessage); 70 | 71 | } 72 | 73 | boolean MifareUltralight::isUnformatted() { 74 | uint8_t page = 4; 75 | byte data[ULTRALIGHT_READ_SIZE]; 76 | boolean success = nfc->mifareultralight_ReadPage(page, data); 77 | if (success) { 78 | return (data[0] == 0xFF && data[1] == 0xFF && data[2] == 0xFF && data[3] == 0xFF); 79 | } else { 80 | #ifdef NDEF_USE_SERIAL 81 | SERIAL.print(F("Error. Failed read page ")); SERIAL.println(page); 82 | #endif 83 | return false; 84 | } 85 | } 86 | 87 | // page 3 has tag capabilities 88 | void MifareUltralight::readCapabilityContainer() { 89 | byte data[ULTRALIGHT_PAGE_SIZE]; 90 | int success = nfc->mifareultralight_ReadPage(3, data); 91 | if (success) { 92 | // See AN1303 - different rules for Mifare Family byte2 = (additional data + 48)/8 93 | tagCapacity = data[2] * 8; 94 | #ifdef MIFARE_ULTRALIGHT_DEBUG 95 | SERIAL.print(F("Tag capacity ")); SERIAL.print(tagCapacity); SERIAL.println(F(" bytes")); 96 | #endif 97 | 98 | // TODO future versions should get lock information 99 | } 100 | } 101 | 102 | // read enough of the message to find the ndef message length 103 | void MifareUltralight::findNdefMessage() { 104 | int page; 105 | byte data[12]; // 3 pages 106 | byte* data_ptr = &data[0]; 107 | 108 | // the nxp read command reads 4 pages, unfortunately adafruit give me one page at a time 109 | boolean success = true; 110 | for (page = 4; page < 6; page++) { 111 | success = success && nfc->mifareultralight_ReadPage(page, data_ptr); 112 | #ifdef MIFARE_ULTRALIGHT_DEBUG 113 | SERIAL.print(F("Page ")); SERIAL.print(page); SERIAL.print(F(" - ")); 114 | nfc->PrintHexChar(data_ptr, 4); 115 | #endif 116 | data_ptr += ULTRALIGHT_PAGE_SIZE; 117 | } 118 | 119 | if (success) { 120 | if (data[0] == 0x03) { 121 | messageLength = data[1]; 122 | ndefStartIndex = 2; 123 | } else if (data[5] == 0x3) { // page 5 byte 1 124 | // TODO should really read the lock control TLV to ensure byte[5] is correct 125 | messageLength = data[6]; 126 | ndefStartIndex = 7; 127 | } 128 | } 129 | 130 | #ifdef MIFARE_ULTRALIGHT_DEBUG 131 | SERIAL.print(F("messageLength ")); SERIAL.println(messageLength); 132 | SERIAL.print(F("ndefStartIndex ")); SERIAL.println(ndefStartIndex); 133 | #endif 134 | } 135 | 136 | // buffer is larger than the message, need to handle some data before and after 137 | // message and need to ensure we read full pages 138 | void MifareUltralight::calculateBufferSize() { 139 | // TLV terminator 0xFE is 1 byte 140 | bufferSize = messageLength + ndefStartIndex + 1; 141 | 142 | if (bufferSize % ULTRALIGHT_READ_SIZE != 0) { 143 | // buffer must be an increment of page size 144 | bufferSize = ((bufferSize / ULTRALIGHT_READ_SIZE) + 1) * ULTRALIGHT_READ_SIZE; 145 | } 146 | } 147 | 148 | boolean MifareUltralight::write(NdefMessage& m, byte* uid, unsigned int uidLength) { 149 | if (isUnformatted()) { 150 | #ifdef NDEF_USE_SERIAL 151 | SERIAL.println(F("WARNING: Tag is not formatted.")); 152 | #endif 153 | return false; 154 | } 155 | readCapabilityContainer(); // meta info for tag 156 | 157 | messageLength = m.getEncodedSize(); 158 | ndefStartIndex = messageLength < 0xFF ? 2 : 4; 159 | calculateBufferSize(); 160 | 161 | if (bufferSize > tagCapacity) { 162 | #ifdef MIFARE_ULTRALIGHT_DEBUG 163 | SERIAL.print(F("Encoded Message length exceeded tag Capacity ")); SERIAL.println(tagCapacity); 164 | #endif 165 | return false; 166 | } 167 | 168 | uint8_t encoded[bufferSize]; 169 | uint8_t* src = encoded; 170 | unsigned int position = 0; 171 | uint8_t page = ULTRALIGHT_DATA_START_PAGE; 172 | 173 | // Set message size. With ultralight should always be less than 0xFF but who knows? 174 | 175 | encoded[0] = 0x3; 176 | if (messageLength < 0xFF) { 177 | encoded[1] = messageLength; 178 | } else { 179 | encoded[1] = 0xFF; 180 | encoded[2] = ((messageLength >> 8) & 0xFF); 181 | encoded[3] = (messageLength & 0xFF); 182 | } 183 | 184 | m.encode(encoded + ndefStartIndex); 185 | // this is always at least 1 byte copy because of terminator. 186 | memset(encoded + ndefStartIndex + messageLength, 0, bufferSize - ndefStartIndex - messageLength); 187 | encoded[ndefStartIndex + messageLength] = 0xFE; // terminator 188 | 189 | #ifdef MIFARE_ULTRALIGHT_DEBUG 190 | SERIAL.print(F("messageLength ")); SERIAL.println(messageLength); 191 | SERIAL.print(F("Tag Capacity ")); SERIAL.println(tagCapacity); 192 | nfc->PrintHex(encoded, bufferSize); 193 | #endif 194 | 195 | while (position < bufferSize) { //bufferSize is always times pagesize so no "last chunk" check 196 | // write page 197 | if (!nfc->mifareultralight_WritePage(page, src)) { 198 | return false; 199 | } 200 | #ifdef MIFARE_ULTRALIGHT_DEBUG 201 | SERIAL.print(F("Wrote page ")); SERIAL.print(page); SERIAL.print(F(" - ")); 202 | nfc->PrintHex(src, ULTRALIGHT_PAGE_SIZE); 203 | #endif 204 | page++; 205 | src += ULTRALIGHT_PAGE_SIZE; 206 | position += ULTRALIGHT_PAGE_SIZE; 207 | } 208 | return true; 209 | } 210 | 211 | // Mifare Ultralight can't be reset to factory state 212 | // zero out tag data like the NXP Tag Write Android application 213 | boolean MifareUltralight::clean() { 214 | readCapabilityContainer(); // meta info for tag 215 | 216 | uint8_t pages = (tagCapacity / ULTRALIGHT_PAGE_SIZE) + ULTRALIGHT_DATA_START_PAGE; 217 | 218 | // factory tags have 0xFF, but OTP-CC blocks have already been set so we use 0x00 219 | uint8_t data[4] = { 0x00, 0x00, 0x00, 0x00 }; 220 | 221 | for (int i = ULTRALIGHT_DATA_START_PAGE; i < pages; i++) { 222 | #ifdef MIFARE_ULTRALIGHT_DEBUG 223 | SERIAL.print(F("Wrote page ")); SERIAL.print(i); SERIAL.print(F(" - ")); 224 | nfc->PrintHex(data, ULTRALIGHT_PAGE_SIZE); 225 | #endif 226 | if (!nfc->mifareultralight_WritePage(i, data)) { 227 | return false; 228 | } 229 | } 230 | return true; 231 | } 232 | -------------------------------------------------------------------------------- /src/MifareUltralight.h: -------------------------------------------------------------------------------- 1 | #ifndef MifareUltralight_h 2 | #define MifareUltralight_h 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | class MifareUltralight { 9 | public: 10 | MifareUltralight(PN532& nfcShield); 11 | ~MifareUltralight(); 12 | NfcTag read(byte* uid, unsigned int uidLength); 13 | boolean write(NdefMessage& ndefMessage, byte* uid, unsigned int uidLength); 14 | boolean clean(); 15 | private: 16 | PN532* nfc; 17 | unsigned int tagCapacity; 18 | unsigned int messageLength; 19 | unsigned int bufferSize; 20 | unsigned int ndefStartIndex; 21 | boolean isUnformatted(); 22 | void readCapabilityContainer(); 23 | void findNdefMessage(); 24 | void calculateBufferSize(); 25 | }; 26 | 27 | #endif 28 | -------------------------------------------------------------------------------- /src/Ndef.cpp: -------------------------------------------------------------------------------- 1 | #include "Ndef.h" 2 | 3 | #ifdef NDEF_USE_SERIAL 4 | // Borrowed from Adafruit_NFCShield_I2C 5 | void PrintHex(const byte* data, const long numBytes) { 6 | int32_t szPos; 7 | for (szPos = 0; szPos < numBytes; szPos++) { 8 | SERIAL.print("0x"); 9 | // Append leading 0 for small values 10 | if (data[szPos] <= 0xF) { 11 | SERIAL.print("0"); 12 | } 13 | SERIAL.print(data[szPos] & 0xff, HEX); 14 | if ((numBytes > 1) && (szPos != numBytes - 1)) { 15 | SERIAL.print(" "); 16 | } 17 | } 18 | SERIAL.println(""); 19 | } 20 | 21 | // Borrowed from Adafruit_NFCShield_I2C 22 | void PrintHexChar(const byte* data, const long numBytes) { 23 | int32_t szPos; 24 | for (szPos = 0; szPos < numBytes; szPos++) { 25 | // Append leading 0 for small values 26 | if (data[szPos] <= 0xF) { 27 | SERIAL.print("0"); 28 | } 29 | SERIAL.print(data[szPos], HEX); 30 | if ((numBytes > 1) && (szPos != numBytes - 1)) { 31 | SERIAL.print(" "); 32 | } 33 | } 34 | SERIAL.print(" "); 35 | for (szPos = 0; szPos < numBytes; szPos++) { 36 | if (data[szPos] <= 0x1F) { 37 | SERIAL.print("."); 38 | } else { 39 | SERIAL.print((char)data[szPos]); 40 | } 41 | } 42 | SERIAL.println(""); 43 | } 44 | 45 | // Note if buffer % blockSize != 0, last block will not be written 46 | void DumpHex(const byte* data, const long numBytes, const unsigned int blockSize) { 47 | int i; 48 | for (i = 0; i < (numBytes / blockSize); i++) { 49 | PrintHexChar(data, blockSize); 50 | data += blockSize; 51 | } 52 | } 53 | #endif 54 | -------------------------------------------------------------------------------- /src/Ndef.h: -------------------------------------------------------------------------------- 1 | #ifndef Ndef_h 2 | #define Ndef_h 3 | 4 | // To save memory and stop serial output comment out the next line 5 | #define NDEF_USE_SERIAL 6 | 7 | /* NOTE: To use the Ndef library in your code, don't include Ndef.h 8 | See README.md for details on which files to include in your sketch. 9 | */ 10 | 11 | #include 12 | #include "PN532/PN532/PN532_debug.h" 13 | #ifdef NDEF_USE_SERIAL 14 | void PrintHex(const byte* data, const long numBytes); 15 | void PrintHexChar(const byte* data, const long numBytes); 16 | void DumpHex(const byte* data, const long numBytes, const int blockSize); 17 | #endif 18 | 19 | #endif 20 | -------------------------------------------------------------------------------- /src/NdefMessage.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | NdefMessage::NdefMessage(void) { 4 | _recordCount = 0; 5 | } 6 | 7 | NdefMessage::NdefMessage(const byte* data, const int numBytes) { 8 | #ifdef NDEF_DEBUG 9 | SERIAL.print(F("Decoding ")); SERIAL.print(numBytes); SERIAL.println(F(" bytes")); 10 | PrintHexChar(data, numBytes); 11 | //DumpHex(data, numBytes, 16); 12 | #endif 13 | 14 | _recordCount = 0; 15 | 16 | int index = 0; 17 | 18 | while (index <= numBytes) { 19 | 20 | // decode tnf - first byte is tnf with bit flags 21 | // see the NFDEF spec for more info 22 | byte tnf_byte = data[index]; 23 | // bool mb = tnf_byte & 0x80; 24 | bool me = tnf_byte & 0x40; 25 | // bool cf = tnf_byte & 0x20; 26 | bool sr = tnf_byte & 0x10; 27 | bool il = tnf_byte & 0x8; 28 | byte tnf = (tnf_byte & 0x7); 29 | 30 | NdefRecord record = NdefRecord(); 31 | record.setTnf(tnf); 32 | 33 | index++; 34 | int typeLength = data[index]; 35 | 36 | uint32_t payloadLength = 0; 37 | if (sr) { 38 | index++; 39 | payloadLength = data[index]; 40 | } else { 41 | payloadLength = 42 | (static_cast(data[index]) << 24) 43 | | (static_cast(data[index + 1]) << 16) 44 | | (static_cast(data[index + 2]) << 8) 45 | | static_cast(data[index + 3]); 46 | index += 4; 47 | } 48 | 49 | int idLength = 0; 50 | if (il) { 51 | index++; 52 | idLength = data[index]; 53 | } 54 | 55 | index++; 56 | record.setType(&data[index], typeLength); 57 | index += typeLength; 58 | 59 | if (il) { 60 | record.setId(&data[index], idLength); 61 | index += idLength; 62 | } 63 | 64 | record.setPayload(&data[index], payloadLength); 65 | index += payloadLength; 66 | 67 | addRecord(record); 68 | 69 | if (me) { 70 | break; // last message 71 | } 72 | } 73 | 74 | } 75 | 76 | NdefMessage::NdefMessage(const NdefMessage& rhs) { 77 | 78 | _recordCount = rhs._recordCount; 79 | for (unsigned int i = 0; i < _recordCount; i++) { 80 | _records[i] = rhs._records[i]; 81 | } 82 | 83 | } 84 | 85 | NdefMessage::~NdefMessage() { 86 | } 87 | 88 | NdefMessage& NdefMessage::operator=(const NdefMessage& rhs) { 89 | 90 | if (this != &rhs) { 91 | 92 | // delete existing records 93 | for (unsigned int i = 0; i < _recordCount; i++) { 94 | // TODO Dave: is this the right way to delete existing records? 95 | _records[i] = NdefRecord(); 96 | } 97 | 98 | _recordCount = rhs._recordCount; 99 | for (unsigned int i = 0; i < _recordCount; i++) { 100 | _records[i] = rhs._records[i]; 101 | } 102 | } 103 | return *this; 104 | } 105 | 106 | unsigned int NdefMessage::getRecordCount() { 107 | return _recordCount; 108 | } 109 | 110 | int NdefMessage::getEncodedSize() { 111 | int size = 0; 112 | for (unsigned int i = 0; i < _recordCount; i++) { 113 | size += _records[i].getEncodedSize(); 114 | } 115 | return size; 116 | } 117 | 118 | // TODO change this to return uint8_t* 119 | void NdefMessage::encode(uint8_t* data) { 120 | // assert sizeof(data) >= getEncodedSize() 121 | uint8_t* data_ptr = &data[0]; 122 | 123 | for (unsigned int i = 0; i < _recordCount; i++) { 124 | _records[i].encode(data_ptr, i == 0, (i + 1) == _recordCount); 125 | // TODO can NdefRecord.encode return the record size? 126 | data_ptr += _records[i].getEncodedSize(); 127 | } 128 | 129 | } 130 | 131 | boolean NdefMessage::addRecord(NdefRecord& record) { 132 | 133 | if (_recordCount < MAX_NDEF_RECORDS) { 134 | _records[_recordCount] = record; 135 | _recordCount++; 136 | return true; 137 | } else { 138 | #ifdef NDEF_USE_SERIAL 139 | SERIAL.println(F("WARNING: Too many records. Increase MAX_NDEF_RECORDS.")); 140 | #endif 141 | return false; 142 | } 143 | } 144 | 145 | void NdefMessage::addMimeMediaRecord(String mimeType, String payload) { 146 | 147 | byte payloadBytes[payload.length() + 1]; 148 | payload.getBytes(payloadBytes, sizeof(payloadBytes)); 149 | 150 | addMimeMediaRecord(mimeType, payloadBytes, payload.length()); 151 | } 152 | 153 | void NdefMessage::addMimeMediaRecord(String mimeType, uint8_t* payload, int payloadLength) { 154 | NdefRecord r = NdefRecord(); 155 | r.setTnf(TNF_MIME_MEDIA); 156 | 157 | byte type[mimeType.length() + 1]; 158 | mimeType.getBytes(type, sizeof(type)); 159 | r.setType(type, mimeType.length()); 160 | 161 | r.setPayload(payload, payloadLength); 162 | 163 | addRecord(r); 164 | } 165 | 166 | void NdefMessage::addTextRecord(String text) { 167 | addTextRecord(text, "en"); 168 | } 169 | 170 | void NdefMessage::addTextRecord(String text, String encoding) { 171 | NdefRecord r = NdefRecord(); 172 | r.setTnf(TNF_WELL_KNOWN); 173 | 174 | uint8_t RTD_TEXT[1] = { 0x54 }; // TODO this should be a constant or preprocessor 175 | r.setType(RTD_TEXT, sizeof(RTD_TEXT)); 176 | 177 | // X is a placeholder for encoding length 178 | // TODO is it more efficient to build w/o string concatenation? 179 | String payloadString = "X" + encoding + text; 180 | 181 | byte payload[payloadString.length() + 1]; 182 | payloadString.getBytes(payload, sizeof(payload)); 183 | 184 | // replace X with the real encoding length 185 | payload[0] = encoding.length(); 186 | 187 | r.setPayload(payload, payloadString.length()); 188 | 189 | addRecord(r); 190 | } 191 | 192 | void NdefMessage::addUriRecord(String uri) { 193 | NdefRecord* r = new NdefRecord(); 194 | r->setTnf(TNF_WELL_KNOWN); 195 | 196 | uint8_t RTD_URI[1] = { 0x55 }; // TODO this should be a constant or preprocessor 197 | r->setType(RTD_URI, sizeof(RTD_URI)); 198 | 199 | // X is a placeholder for identifier code 200 | String payloadString = "X" + uri; 201 | 202 | byte payload[payloadString.length() + 1]; 203 | payloadString.getBytes(payload, sizeof(payload)); 204 | 205 | // add identifier code 0x0, meaning no prefix substitution 206 | payload[0] = 0x0; 207 | 208 | r->setPayload(payload, payloadString.length()); 209 | 210 | addRecord(*r); 211 | delete (r); 212 | } 213 | 214 | void NdefMessage::addEmptyRecord() { 215 | NdefRecord* r = new NdefRecord(); 216 | r->setTnf(TNF_EMPTY); 217 | addRecord(*r); 218 | delete (r); 219 | } 220 | 221 | NdefRecord NdefMessage::getRecord(int index) { 222 | if (index > -1 && index < static_cast(_recordCount)) { 223 | return _records[index]; 224 | } else { 225 | return NdefRecord(); // would rather return NULL 226 | } 227 | } 228 | 229 | NdefRecord NdefMessage::operator[](int index) { 230 | return getRecord(index); 231 | } 232 | 233 | #ifdef NDEF_USE_SERIAL 234 | void NdefMessage::print() { 235 | SERIAL.print(F("\nNDEF Message ")); SERIAL.print(_recordCount); SERIAL.print(F(" record")); 236 | _recordCount == 1 ? SERIAL.print(", ") : SERIAL.print("s, "); 237 | SERIAL.print(getEncodedSize()); SERIAL.println(F(" bytes")); 238 | 239 | for (unsigned int i = 0; i < _recordCount; i++) { 240 | _records[i].print(); 241 | } 242 | } 243 | #endif 244 | -------------------------------------------------------------------------------- /src/NdefMessage.h: -------------------------------------------------------------------------------- 1 | #ifndef NdefMessage_h 2 | #define NdefMessage_h 3 | 4 | #include 5 | #include 6 | 7 | #define MAX_NDEF_RECORDS 4 8 | 9 | class NdefMessage { 10 | public: 11 | NdefMessage(void); 12 | NdefMessage(const byte* data, const int numBytes); 13 | NdefMessage(const NdefMessage& rhs); 14 | ~NdefMessage(); 15 | NdefMessage& operator=(const NdefMessage& rhs); 16 | 17 | int getEncodedSize(); // need so we can pass array to encode 18 | void encode(byte* data); 19 | 20 | boolean addRecord(NdefRecord& record); 21 | void addMimeMediaRecord(String mimeType, String payload); 22 | void addMimeMediaRecord(String mimeType, byte* payload, int payloadLength); 23 | void addTextRecord(String text); 24 | void addTextRecord(String text, String encoding); 25 | void addUriRecord(String uri); 26 | void addEmptyRecord(); 27 | 28 | unsigned int getRecordCount(); 29 | NdefRecord getRecord(int index); 30 | NdefRecord operator[](int index); 31 | 32 | #ifdef NDEF_USE_SERIAL 33 | void print(); 34 | #endif 35 | private: 36 | NdefRecord _records[MAX_NDEF_RECORDS]; 37 | unsigned int _recordCount; 38 | }; 39 | 40 | #endif -------------------------------------------------------------------------------- /src/NdefRecord.cpp: -------------------------------------------------------------------------------- 1 | #include "NdefRecord.h" 2 | 3 | NdefRecord::NdefRecord() { 4 | //SERIAL.println("NdefRecord Constructor 1"); 5 | _tnf = 0; 6 | _typeLength = 0; 7 | _payloadLength = 0; 8 | _idLength = 0; 9 | _type = (byte*)NULL; 10 | _payload = (byte*)NULL; 11 | _id = (byte*)NULL; 12 | } 13 | 14 | NdefRecord::NdefRecord(const NdefRecord& rhs) { 15 | //SERIAL.println("NdefRecord Constructor 2 (copy)"); 16 | 17 | _tnf = rhs._tnf; 18 | _typeLength = rhs._typeLength; 19 | _payloadLength = rhs._payloadLength; 20 | _idLength = rhs._idLength; 21 | _type = (byte*)NULL; 22 | _payload = (byte*)NULL; 23 | _id = (byte*)NULL; 24 | 25 | if (_typeLength) { 26 | _type = (byte*)malloc(_typeLength); 27 | memcpy(_type, rhs._type, _typeLength); 28 | } 29 | 30 | if (_payloadLength) { 31 | _payload = (byte*)malloc(_payloadLength); 32 | memcpy(_payload, rhs._payload, _payloadLength); 33 | } 34 | 35 | if (_idLength) { 36 | _id = (byte*)malloc(_idLength); 37 | memcpy(_id, rhs._id, _idLength); 38 | } 39 | 40 | } 41 | 42 | // TODO NdefRecord::NdefRecord(tnf, type, payload, id) 43 | 44 | NdefRecord::~NdefRecord() { 45 | //SERIAL.println("NdefRecord Destructor"); 46 | if (_typeLength) { 47 | free(_type); 48 | } 49 | 50 | if (_payloadLength) { 51 | free(_payload); 52 | } 53 | 54 | if (_idLength) { 55 | free(_id); 56 | } 57 | } 58 | 59 | NdefRecord& NdefRecord::operator=(const NdefRecord& rhs) { 60 | //SERIAL.println("NdefRecord ASSIGN"); 61 | 62 | if (this != &rhs) { 63 | // free existing 64 | if (_typeLength) { 65 | free(_type); 66 | } 67 | 68 | if (_payloadLength) { 69 | free(_payload); 70 | } 71 | 72 | if (_idLength) { 73 | free(_id); 74 | } 75 | 76 | _tnf = rhs._tnf; 77 | _typeLength = rhs._typeLength; 78 | _payloadLength = rhs._payloadLength; 79 | _idLength = rhs._idLength; 80 | 81 | if (_typeLength) { 82 | _type = (byte*)malloc(_typeLength); 83 | memcpy(_type, rhs._type, _typeLength); 84 | } 85 | 86 | if (_payloadLength) { 87 | _payload = (byte*)malloc(_payloadLength); 88 | memcpy(_payload, rhs._payload, _payloadLength); 89 | } 90 | 91 | if (_idLength) { 92 | _id = (byte*)malloc(_idLength); 93 | memcpy(_id, rhs._id, _idLength); 94 | } 95 | } 96 | return *this; 97 | } 98 | 99 | // size of records in bytes 100 | int NdefRecord::getEncodedSize() { 101 | int size = 2; // tnf + typeLength 102 | if (_payloadLength > 0xFF) { 103 | size += 4; 104 | } else { 105 | size += 1; 106 | } 107 | 108 | if (_idLength) { 109 | size += 1; 110 | } 111 | 112 | size += (_typeLength + _payloadLength + _idLength); 113 | 114 | return size; 115 | } 116 | 117 | void NdefRecord::encode(byte* data, bool firstRecord, bool lastRecord) { 118 | // assert data > getEncodedSize() 119 | 120 | uint8_t* data_ptr = &data[0]; 121 | 122 | *data_ptr = getTnfByte(firstRecord, lastRecord); 123 | data_ptr += 1; 124 | 125 | *data_ptr = _typeLength; 126 | data_ptr += 1; 127 | 128 | if (_payloadLength <= 0xFF) { // short record 129 | *data_ptr = _payloadLength; 130 | data_ptr += 1; 131 | } else { // long format 132 | // 4 bytes but we store length as an int 133 | data_ptr[0] = 0x0; // (_payloadLength >> 24) & 0xFF; 134 | data_ptr[1] = 0x0; // (_payloadLength >> 16) & 0xFF; 135 | data_ptr[2] = (_payloadLength >> 8) & 0xFF; 136 | data_ptr[3] = _payloadLength & 0xFF; 137 | data_ptr += 4; 138 | } 139 | 140 | if (_idLength) { 141 | *data_ptr = _idLength; 142 | data_ptr += 1; 143 | } 144 | 145 | //SERIAL.println(2); 146 | memcpy(data_ptr, _type, _typeLength); 147 | data_ptr += _typeLength; 148 | 149 | if (_idLength) { 150 | memcpy(data_ptr, _id, _idLength); 151 | data_ptr += _idLength; 152 | } 153 | 154 | memcpy(data_ptr, _payload, _payloadLength); 155 | data_ptr += _payloadLength; 156 | } 157 | 158 | byte NdefRecord::getTnfByte(bool firstRecord, bool lastRecord) { 159 | int value = _tnf; 160 | 161 | if (firstRecord) { // mb 162 | value = value | 0x80; 163 | } 164 | 165 | if (lastRecord) { // 166 | value = value | 0x40; 167 | } 168 | 169 | // chunked flag is always false for now 170 | // if (cf) { 171 | // value = value | 0x20; 172 | // } 173 | 174 | if (_payloadLength <= 0xFF) { 175 | value = value | 0x10; 176 | } 177 | 178 | if (_idLength) { 179 | value = value | 0x8; 180 | } 181 | 182 | return value; 183 | } 184 | 185 | byte NdefRecord::getTnf() { 186 | return _tnf; 187 | } 188 | 189 | void NdefRecord::setTnf(byte tnf) { 190 | _tnf = tnf; 191 | } 192 | 193 | unsigned int NdefRecord::getTypeLength() { 194 | return _typeLength; 195 | } 196 | 197 | int NdefRecord::getPayloadLength() { 198 | return _payloadLength; 199 | } 200 | 201 | unsigned int NdefRecord::getIdLength() { 202 | return _idLength; 203 | } 204 | 205 | String NdefRecord::getType() { 206 | char type[_typeLength + 1]; 207 | memcpy(type, _type, _typeLength); 208 | type[_typeLength] = '\0'; // null terminate 209 | return String(type); 210 | } 211 | 212 | // this assumes the caller created type correctly 213 | void NdefRecord::getType(uint8_t* type) { 214 | memcpy(type, _type, _typeLength); 215 | } 216 | 217 | void NdefRecord::setType(const byte* type, const unsigned int numBytes) { 218 | if (_typeLength) { 219 | free(_type); 220 | } 221 | 222 | _type = (uint8_t*)malloc(numBytes); 223 | memcpy(_type, type, numBytes); 224 | _typeLength = numBytes; 225 | } 226 | 227 | // assumes the caller sized payload properly 228 | void NdefRecord::getPayload(byte* payload) { 229 | memcpy(payload, _payload, _payloadLength); 230 | } 231 | 232 | void NdefRecord::setPayload(const byte* payload, const int numBytes) { 233 | if (_payloadLength) { 234 | free(_payload); 235 | } 236 | 237 | _payload = (byte*)malloc(numBytes); 238 | memcpy(_payload, payload, numBytes); 239 | _payloadLength = numBytes; 240 | } 241 | 242 | String NdefRecord::getId() { 243 | char id[_idLength + 1]; 244 | memcpy(id, _id, _idLength); 245 | id[_idLength] = '\0'; // null terminate 246 | return String(id); 247 | } 248 | 249 | void NdefRecord::getId(byte* id) { 250 | memcpy(id, _id, _idLength); 251 | } 252 | 253 | void NdefRecord::setId(const byte* id, const unsigned int numBytes) { 254 | if (_idLength) { 255 | free(_id); 256 | } 257 | 258 | _id = (byte*)malloc(numBytes); 259 | memcpy(_id, id, numBytes); 260 | _idLength = numBytes; 261 | } 262 | #ifdef NDEF_USE_SERIAL 263 | 264 | void NdefRecord::print() { 265 | SERIAL.println(F(" NDEF Record")); 266 | SERIAL.print(F(" TNF 0x")); SERIAL.print(_tnf, HEX); SERIAL.print(" "); 267 | switch (_tnf) { 268 | case TNF_EMPTY: 269 | SERIAL.println(F("Empty")); 270 | break; 271 | case TNF_WELL_KNOWN: 272 | SERIAL.println(F("Well Known")); 273 | break; 274 | case TNF_MIME_MEDIA: 275 | SERIAL.println(F("Mime Media")); 276 | break; 277 | case TNF_ABSOLUTE_URI: 278 | SERIAL.println(F("Absolute URI")); 279 | break; 280 | case TNF_EXTERNAL_TYPE: 281 | SERIAL.println(F("External")); 282 | break; 283 | case TNF_UNKNOWN: 284 | SERIAL.println(F("Unknown")); 285 | break; 286 | case TNF_UNCHANGED: 287 | SERIAL.println(F("Unchanged")); 288 | break; 289 | case TNF_RESERVED: 290 | SERIAL.println(F("Reserved")); 291 | break; 292 | default: 293 | SERIAL.println(); 294 | } 295 | SERIAL.print(F(" Type Length 0x")); SERIAL.print(_typeLength, HEX); SERIAL.print(" "); SERIAL.println(_typeLength); 296 | SERIAL.print(F(" Payload Length 0x")); SERIAL.print(_payloadLength, HEX);; SERIAL.print(" "); 297 | SERIAL.println(_payloadLength); 298 | if (_idLength) { 299 | SERIAL.print(F(" Id Length 0x")); SERIAL.println(_idLength, HEX); 300 | } 301 | SERIAL.print(F(" Type ")); PrintHexChar(_type, _typeLength); 302 | // TODO chunk large payloads so this is readable 303 | SERIAL.print(F(" Payload ")); PrintHexChar(_payload, _payloadLength); 304 | if (_idLength) { 305 | SERIAL.print(F(" Id ")); PrintHexChar(_id, _idLength); 306 | } 307 | SERIAL.print(F(" Record is ")); SERIAL.print(getEncodedSize()); SERIAL.println(" bytes"); 308 | 309 | } 310 | #endif 311 | -------------------------------------------------------------------------------- /src/NdefRecord.h: -------------------------------------------------------------------------------- 1 | #ifndef NdefRecord_h 2 | #define NdefRecord_h 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | #define TNF_EMPTY 0x0 9 | #define TNF_WELL_KNOWN 0x01 10 | #define TNF_MIME_MEDIA 0x02 11 | #define TNF_ABSOLUTE_URI 0x03 12 | #define TNF_EXTERNAL_TYPE 0x04 13 | #define TNF_UNKNOWN 0x05 14 | #define TNF_UNCHANGED 0x06 15 | #define TNF_RESERVED 0x07 16 | 17 | class NdefRecord { 18 | public: 19 | NdefRecord(); 20 | NdefRecord(const NdefRecord& rhs); 21 | ~NdefRecord(); 22 | NdefRecord& operator=(const NdefRecord& rhs); 23 | 24 | int getEncodedSize(); 25 | void encode(byte* data, bool firstRecord, bool lastRecord); 26 | 27 | unsigned int getTypeLength(); 28 | int getPayloadLength(); 29 | unsigned int getIdLength(); 30 | 31 | byte getTnf(); 32 | void getType(byte* type); 33 | void getPayload(byte* payload); 34 | void getId(byte* id); 35 | 36 | // convenience methods 37 | String getType(); 38 | String getId(); 39 | 40 | void setTnf(byte tnf); 41 | void setType(const byte* type, const unsigned int numBytes); 42 | void setPayload(const byte* payload, const int numBytes); 43 | void setId(const byte* id, const unsigned int numBytes); 44 | 45 | #ifdef NDEF_USE_SERIAL 46 | void print(); 47 | #endif 48 | private: 49 | byte getTnfByte(bool firstRecord, bool lastRecord); 50 | byte _tnf; // 3 bit 51 | unsigned int _typeLength; 52 | int _payloadLength; 53 | unsigned int _idLength; 54 | byte* _type; 55 | byte* _payload; 56 | byte* _id; 57 | }; 58 | 59 | #endif -------------------------------------------------------------------------------- /src/NfcAdapter.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | NfcAdapter::NfcAdapter(PN532Interface& interface) { 4 | shield = new PN532(interface); 5 | } 6 | 7 | NfcAdapter::~NfcAdapter(void) { 8 | delete shield; 9 | } 10 | 11 | void NfcAdapter::begin(boolean verbose) { 12 | shield->begin(); 13 | 14 | uint32_t versiondata = shield->getFirmwareVersion(); 15 | 16 | if (! versiondata) { 17 | #ifdef NDEF_USE_SERIAL 18 | SERIAL.print(F("Didn't find PN53x board")); 19 | #endif 20 | while (1); // halt 21 | } 22 | 23 | if (verbose) { 24 | #ifdef NDEF_USE_SERIAL 25 | SERIAL.print(F("Found chip PN5")); SERIAL.println((versiondata >> 24) & 0xFF, HEX); 26 | SERIAL.print(F("Firmware ver. ")); SERIAL.print((versiondata >> 16) & 0xFF, DEC); 27 | SERIAL.print('.'); SERIAL.println((versiondata >> 8) & 0xFF, DEC); 28 | #endif 29 | } 30 | // configure board to read RFID tags 31 | shield->SAMConfig(); 32 | } 33 | 34 | boolean NfcAdapter::tagPresent(unsigned long timeout) { 35 | uint8_t success; 36 | uidLength = 0; 37 | 38 | if (timeout == 0) { 39 | success = shield->readPassiveTargetID(PN532_MIFARE_ISO14443A, uid, (uint8_t*)&uidLength); 40 | } else { 41 | success = shield->readPassiveTargetID(PN532_MIFARE_ISO14443A, uid, (uint8_t*)&uidLength, timeout); 42 | } 43 | return success; 44 | } 45 | 46 | boolean NfcAdapter::erase() { 47 | NdefMessage message = NdefMessage(); 48 | message.addEmptyRecord(); 49 | return write(message); 50 | } 51 | 52 | boolean NfcAdapter::format() { 53 | boolean success; 54 | #ifdef NDEF_SUPPORT_MIFARE_CLASSIC 55 | if (uidLength == 4) { 56 | MifareClassic mifareClassic = MifareClassic(*shield); 57 | success = mifareClassic.formatNDEF(uid, uidLength); 58 | } else 59 | #endif 60 | { 61 | #ifdef NDEF_USE_SERIAL 62 | SERIAL.print(F("Unsupported Tag.")); 63 | #endif 64 | success = false; 65 | } 66 | return success; 67 | } 68 | 69 | boolean NfcAdapter::clean() { 70 | uint8_t type = guessTagType(); 71 | 72 | #ifdef NDEF_SUPPORT_MIFARE_CLASSIC 73 | if (type == TAG_TYPE_MIFARE_CLASSIC) { 74 | #ifdef NDEF_DEBUG 75 | SERIAL.println(F("Cleaning Mifare Classic")); 76 | #endif 77 | MifareClassic mifareClassic = MifareClassic(*shield); 78 | return mifareClassic.formatMifare(uid, uidLength); 79 | } else 80 | #endif 81 | if (type == TAG_TYPE_2) { 82 | #ifdef NDEF_DEBUG 83 | SERIAL.println(F("Cleaning Mifare Ultralight")); 84 | #endif 85 | MifareUltralight ultralight = MifareUltralight(*shield); 86 | return ultralight.clean(); 87 | } else { 88 | #ifdef NDEF_USE_SERIAL 89 | SERIAL.print(F("No driver for card type ")); SERIAL.println(type); 90 | #endif 91 | return false; 92 | } 93 | 94 | } 95 | 96 | 97 | NfcTag NfcAdapter::read() { 98 | uint8_t type = guessTagType(); 99 | 100 | #ifdef NDEF_SUPPORT_MIFARE_CLASSIC 101 | if (type == TAG_TYPE_MIFARE_CLASSIC) { 102 | #ifdef NDEF_DEBUG 103 | SERIAL.println(F("Reading Mifare Classic")); 104 | #endif 105 | MifareClassic mifareClassic = MifareClassic(*shield); 106 | return mifareClassic.read(uid, uidLength); 107 | } else 108 | #endif 109 | if (type == TAG_TYPE_2) { 110 | #ifdef NDEF_DEBUG 111 | SERIAL.println(F("Reading Mifare Ultralight")); 112 | #endif 113 | MifareUltralight ultralight = MifareUltralight(*shield); 114 | return ultralight.read(uid, uidLength); 115 | } else if (type == TAG_TYPE_UNKNOWN) { 116 | #ifdef NDEF_USE_SERIAL 117 | SERIAL.print(F("Can not determine tag type")); 118 | #endif 119 | return NfcTag(uid, uidLength); 120 | } else { 121 | // SERIAL.print(F("No driver for card type "));SERIAL.println(type); 122 | // TODO should set type here 123 | return NfcTag(uid, uidLength); 124 | } 125 | 126 | } 127 | 128 | boolean NfcAdapter::write(NdefMessage& ndefMessage) { 129 | boolean success; 130 | uint8_t type = guessTagType(); 131 | 132 | #ifdef NDEF_SUPPORT_MIFARE_CLASSIC 133 | if (type == TAG_TYPE_MIFARE_CLASSIC) { 134 | #ifdef NDEF_DEBUG 135 | SERIAL.println(F("Writing Mifare Classic")); 136 | #endif 137 | MifareClassic mifareClassic = MifareClassic(*shield); 138 | success = mifareClassic.write(ndefMessage, uid, uidLength); 139 | } else 140 | #endif 141 | if (type == TAG_TYPE_2) { 142 | #ifdef NDEF_DEBUG 143 | SERIAL.println(F("Writing Mifare Ultralight")); 144 | #endif 145 | MifareUltralight mifareUltralight = MifareUltralight(*shield); 146 | success = mifareUltralight.write(ndefMessage, uid, uidLength); 147 | } else if (type == TAG_TYPE_UNKNOWN) { 148 | #ifdef NDEF_USE_SERIAL 149 | SERIAL.print(F("Can not determine tag type")); 150 | #endif 151 | success = false; 152 | } else { 153 | #ifdef NDEF_USE_SERIAL 154 | SERIAL.print(F("No driver for card type ")); SERIAL.println(type); 155 | #endif 156 | success = false; 157 | } 158 | 159 | return success; 160 | } 161 | 162 | // TODO this should return a Driver MifareClassic, MifareUltralight, Type 4, Unknown 163 | // Guess Tag Type by looking at the ATQA and SAK values 164 | // Need to follow spec for Card Identification. Maybe AN1303, AN1305 and ??? 165 | unsigned int NfcAdapter::guessTagType() { 166 | 167 | // 4 byte id - Mifare Classic 168 | // - ATQA 0x4 && SAK 0x8 169 | // 7 byte id 170 | // - ATQA 0x44 && SAK 0x8 - Mifare Classic 171 | // - ATQA 0x44 && SAK 0x0 - Mifare Ultralight NFC Forum Type 2 172 | // - ATQA 0x344 && SAK 0x20 - NFC Forum Type 4 173 | 174 | if (uidLength == 4) { 175 | return TAG_TYPE_MIFARE_CLASSIC; 176 | } else { 177 | return TAG_TYPE_2; 178 | } 179 | } 180 | -------------------------------------------------------------------------------- /src/NfcAdapter.h: -------------------------------------------------------------------------------- 1 | #ifndef NfcAdapter_h 2 | #define NfcAdapter_h 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | // Drivers 10 | #include 11 | #include 12 | 13 | #define TAG_TYPE_MIFARE_CLASSIC (0) 14 | #define TAG_TYPE_1 (1) 15 | #define TAG_TYPE_2 (2) 16 | #define TAG_TYPE_3 (3) 17 | #define TAG_TYPE_4 (4) 18 | #define TAG_TYPE_UNKNOWN (99) 19 | 20 | #define IRQ (2) 21 | #define RESET (3) // Not connected by default on the NFC Shield 22 | 23 | class NfcAdapter { 24 | public: 25 | NfcAdapter(PN532Interface& interface); 26 | 27 | ~NfcAdapter(void); 28 | void begin(boolean verbose = true); 29 | boolean tagPresent(unsigned long timeout = 0); // tagAvailable 30 | NfcTag read(); 31 | boolean write(NdefMessage& ndefMessage); 32 | // erase tag by writing an empty NDEF record 33 | boolean erase(); 34 | // format a tag as NDEF 35 | boolean format(); 36 | // reset tag back to factory state 37 | boolean clean(); 38 | private: 39 | PN532* shield; 40 | byte uid[7]; // Buffer to store the returned UID 41 | unsigned int uidLength; // Length of the UID (4 or 7 bytes depending on ISO14443A card type) 42 | unsigned int guessTagType(); 43 | }; 44 | 45 | #endif 46 | -------------------------------------------------------------------------------- /src/NfcDriver.h: -------------------------------------------------------------------------------- 1 | // eventually the NFC drivers should extend this class 2 | class NfcDriver { 3 | public: 4 | virtual NfcTag read(uint8_t* uid, int uidLength) = 0; 5 | virtual boolean write(NdefMessage& message, uint8_t* uid, int uidLength) = 0; 6 | // erase() 7 | // format() 8 | } -------------------------------------------------------------------------------- /src/NfcTag.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | NfcTag::NfcTag() { 4 | _uid = 0; 5 | _uidLength = 0; 6 | _tagType = "Unknown"; 7 | _ndefMessage = (NdefMessage*)NULL; 8 | } 9 | 10 | NfcTag::NfcTag(byte* uid, unsigned int uidLength) { 11 | _uid = uid; 12 | _uidLength = uidLength; 13 | _tagType = "Unknown"; 14 | _ndefMessage = (NdefMessage*)NULL; 15 | } 16 | 17 | NfcTag::NfcTag(byte* uid, unsigned int uidLength, String tagType) { 18 | _uid = uid; 19 | _uidLength = uidLength; 20 | _tagType = tagType; 21 | _ndefMessage = (NdefMessage*)NULL; 22 | } 23 | 24 | NfcTag::NfcTag(byte* uid, unsigned int uidLength, String tagType, NdefMessage& ndefMessage) { 25 | _uid = uid; 26 | _uidLength = uidLength; 27 | _tagType = tagType; 28 | _ndefMessage = new NdefMessage(ndefMessage); 29 | } 30 | 31 | // I don't like this version, but it will use less memory 32 | NfcTag::NfcTag(byte* uid, unsigned int uidLength, String tagType, const byte* ndefData, const int ndefDataLength) { 33 | _uid = uid; 34 | _uidLength = uidLength; 35 | _tagType = tagType; 36 | _ndefMessage = new NdefMessage(ndefData, ndefDataLength); 37 | } 38 | 39 | NfcTag::~NfcTag() { 40 | delete _ndefMessage; 41 | } 42 | 43 | NfcTag& NfcTag::operator=(const NfcTag& rhs) { 44 | if (this != &rhs) { 45 | delete _ndefMessage; 46 | _uid = rhs._uid; 47 | _uidLength = rhs._uidLength; 48 | _tagType = rhs._tagType; 49 | // TODO do I need a copy here? 50 | _ndefMessage = rhs._ndefMessage; 51 | } 52 | return *this; 53 | } 54 | 55 | uint8_t NfcTag::getUidLength() { 56 | return _uidLength; 57 | } 58 | 59 | void NfcTag::getUid(byte* uid, unsigned int uidLength) { 60 | memcpy(uid, _uid, _uidLength < uidLength ? _uidLength : uidLength); 61 | } 62 | 63 | String NfcTag::getUidString() { 64 | String uidString = ""; 65 | for (unsigned int i = 0; i < _uidLength; i++) { 66 | if (i > 0) { 67 | uidString += " "; 68 | } 69 | 70 | if (_uid[i] < 0xF) { 71 | uidString += "0"; 72 | } 73 | 74 | uidString += String((unsigned int)_uid[i], (unsigned char)HEX); 75 | } 76 | uidString.toUpperCase(); 77 | return uidString; 78 | } 79 | 80 | String NfcTag::getTagType() { 81 | return _tagType; 82 | } 83 | 84 | boolean NfcTag::hasNdefMessage() { 85 | return (_ndefMessage != NULL); 86 | } 87 | 88 | NdefMessage NfcTag::getNdefMessage() { 89 | return *_ndefMessage; 90 | } 91 | #ifdef NDEF_USE_SERIAL 92 | 93 | void NfcTag::print() { 94 | SERIAL.print(F("NFC Tag - ")); SERIAL.println(_tagType); 95 | SERIAL.print(F("UID ")); SERIAL.println(getUidString()); 96 | if (_ndefMessage == NULL) { 97 | SERIAL.println(F("\nNo NDEF Message")); 98 | } else { 99 | _ndefMessage->print(); 100 | } 101 | } 102 | #endif 103 | -------------------------------------------------------------------------------- /src/NfcTag.h: -------------------------------------------------------------------------------- 1 | #ifndef NfcTag_h 2 | #define NfcTag_h 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | class NfcTag { 9 | public: 10 | NfcTag(); 11 | NfcTag(byte* uid, unsigned int uidLength); 12 | NfcTag(byte* uid, unsigned int uidLength, String tagType); 13 | NfcTag(byte* uid, unsigned int uidLength, String tagType, NdefMessage& ndefMessage); 14 | NfcTag(byte* uid, unsigned int uidLength, String tagType, const byte* ndefData, const int ndefDataLength); 15 | ~NfcTag(void); 16 | NfcTag& operator=(const NfcTag& rhs); 17 | uint8_t getUidLength(); 18 | void getUid(byte* uid, unsigned int uidLength); 19 | String getUidString(); 20 | String getTagType(); 21 | boolean hasNdefMessage(); 22 | NdefMessage getNdefMessage(); 23 | #ifdef NDEF_USE_SERIAL 24 | void print(); 25 | #endif 26 | private: 27 | byte* _uid; 28 | unsigned int _uidLength; 29 | String _tagType; // Mifare Classic, NFC Forum Type {1,2,3,4}, Unknown 30 | NdefMessage* _ndefMessage; 31 | // TODO capacity 32 | // TODO isFormatted 33 | }; 34 | 35 | #endif -------------------------------------------------------------------------------- /src/PN532/.gitignore: -------------------------------------------------------------------------------- 1 | */ 2 | !PN532* 3 | !NDEF* 4 | 5 | .DS_Store 6 | -------------------------------------------------------------------------------- /src/PN532/PN532/PN532.h: -------------------------------------------------------------------------------- 1 | /**************************************************************************/ 2 | /*! 3 | @file PN532/PN532/PN532.h 4 | @author Adafruit Industries & Seeed Studio 5 | @license BSD 6 | */ 7 | /**************************************************************************/ 8 | 9 | #ifndef __PN532_H__ 10 | #define __PN532_H__ 11 | 12 | #include 13 | #include "PN532Interface.h" 14 | 15 | // PN532 Commands 16 | #define PN532_COMMAND_DIAGNOSE (0x00) 17 | #define PN532_COMMAND_GETFIRMWAREVERSION (0x02) 18 | #define PN532_COMMAND_GETGENERALSTATUS (0x04) 19 | #define PN532_COMMAND_READREGISTER (0x06) 20 | #define PN532_COMMAND_WRITEREGISTER (0x08) 21 | #define PN532_COMMAND_READGPIO (0x0C) 22 | #define PN532_COMMAND_WRITEGPIO (0x0E) 23 | #define PN532_COMMAND_SETSERIALBAUDRATE (0x10) 24 | #define PN532_COMMAND_SETPARAMETERS (0x12) 25 | #define PN532_COMMAND_SAMCONFIGURATION (0x14) 26 | #define PN532_COMMAND_POWERDOWN (0x16) 27 | #define PN532_COMMAND_RFCONFIGURATION (0x32) 28 | #define PN532_COMMAND_RFREGULATIONTEST (0x58) 29 | #define PN532_COMMAND_INJUMPFORDEP (0x56) 30 | #define PN532_COMMAND_INJUMPFORPSL (0x46) 31 | #define PN532_COMMAND_INLISTPASSIVETARGET (0x4A) 32 | #define PN532_COMMAND_INATR (0x50) 33 | #define PN532_COMMAND_INPSL (0x4E) 34 | #define PN532_COMMAND_INDATAEXCHANGE (0x40) 35 | #define PN532_COMMAND_INCOMMUNICATETHRU (0x42) 36 | #define PN532_COMMAND_INDESELECT (0x44) 37 | #define PN532_COMMAND_INRELEASE (0x52) 38 | #define PN532_COMMAND_INSELECT (0x54) 39 | #define PN532_COMMAND_INAUTOPOLL (0x60) 40 | #define PN532_COMMAND_TGINITASTARGET (0x8C) 41 | #define PN532_COMMAND_TGSETGENERALBYTES (0x92) 42 | #define PN532_COMMAND_TGGETDATA (0x86) 43 | #define PN532_COMMAND_TGSETDATA (0x8E) 44 | #define PN532_COMMAND_TGSETMETADATA (0x94) 45 | #define PN532_COMMAND_TGGETINITIATORCOMMAND (0x88) 46 | #define PN532_COMMAND_TGRESPONSETOINITIATOR (0x90) 47 | #define PN532_COMMAND_TGGETTARGETSTATUS (0x8A) 48 | 49 | #define PN532_RESPONSE_INDATAEXCHANGE (0x41) 50 | #define PN532_RESPONSE_INLISTPASSIVETARGET (0x4B) 51 | 52 | 53 | #define PN532_MIFARE_ISO14443A (0x00) 54 | 55 | // Mifare Commands 56 | #define MIFARE_CMD_AUTH_A (0x60) 57 | #define MIFARE_CMD_AUTH_B (0x61) 58 | #define MIFARE_CMD_READ (0x30) 59 | #define MIFARE_CMD_WRITE (0xA0) 60 | #define MIFARE_CMD_WRITE_ULTRALIGHT (0xA2) 61 | #define MIFARE_CMD_TRANSFER (0xB0) 62 | #define MIFARE_CMD_DECREMENT (0xC0) 63 | #define MIFARE_CMD_INCREMENT (0xC1) 64 | #define MIFARE_CMD_STORE (0xC2) 65 | 66 | // FeliCa Commands 67 | #define FELICA_CMD_POLLING (0x00) 68 | #define FELICA_CMD_REQUEST_SERVICE (0x02) 69 | #define FELICA_CMD_REQUEST_RESPONSE (0x04) 70 | #define FELICA_CMD_READ_WITHOUT_ENCRYPTION (0x06) 71 | #define FELICA_CMD_WRITE_WITHOUT_ENCRYPTION (0x08) 72 | #define FELICA_CMD_REQUEST_SYSTEM_CODE (0x0C) 73 | 74 | // Prefixes for NDEF Records (to identify record type) 75 | #define NDEF_URIPREFIX_NONE (0x00) 76 | #define NDEF_URIPREFIX_HTTP_WWWDOT (0x01) 77 | #define NDEF_URIPREFIX_HTTPS_WWWDOT (0x02) 78 | #define NDEF_URIPREFIX_HTTP (0x03) 79 | #define NDEF_URIPREFIX_HTTPS (0x04) 80 | #define NDEF_URIPREFIX_TEL (0x05) 81 | #define NDEF_URIPREFIX_MAILTO (0x06) 82 | #define NDEF_URIPREFIX_FTP_ANONAT (0x07) 83 | #define NDEF_URIPREFIX_FTP_FTPDOT (0x08) 84 | #define NDEF_URIPREFIX_FTPS (0x09) 85 | #define NDEF_URIPREFIX_SFTP (0x0A) 86 | #define NDEF_URIPREFIX_SMB (0x0B) 87 | #define NDEF_URIPREFIX_NFS (0x0C) 88 | #define NDEF_URIPREFIX_FTP (0x0D) 89 | #define NDEF_URIPREFIX_DAV (0x0E) 90 | #define NDEF_URIPREFIX_NEWS (0x0F) 91 | #define NDEF_URIPREFIX_TELNET (0x10) 92 | #define NDEF_URIPREFIX_IMAP (0x11) 93 | #define NDEF_URIPREFIX_RTSP (0x12) 94 | #define NDEF_URIPREFIX_URN (0x13) 95 | #define NDEF_URIPREFIX_POP (0x14) 96 | #define NDEF_URIPREFIX_SIP (0x15) 97 | #define NDEF_URIPREFIX_SIPS (0x16) 98 | #define NDEF_URIPREFIX_TFTP (0x17) 99 | #define NDEF_URIPREFIX_BTSPP (0x18) 100 | #define NDEF_URIPREFIX_BTL2CAP (0x19) 101 | #define NDEF_URIPREFIX_BTGOEP (0x1A) 102 | #define NDEF_URIPREFIX_TCPOBEX (0x1B) 103 | #define NDEF_URIPREFIX_IRDAOBEX (0x1C) 104 | #define NDEF_URIPREFIX_FILE (0x1D) 105 | #define NDEF_URIPREFIX_URN_EPC_ID (0x1E) 106 | #define NDEF_URIPREFIX_URN_EPC_TAG (0x1F) 107 | #define NDEF_URIPREFIX_URN_EPC_PAT (0x20) 108 | #define NDEF_URIPREFIX_URN_EPC_RAW (0x21) 109 | #define NDEF_URIPREFIX_URN_EPC (0x22) 110 | #define NDEF_URIPREFIX_URN_NFC (0x23) 111 | 112 | #define PN532_GPIO_VALIDATIONBIT (0x80) 113 | #define PN532_GPIO_P30 (0) 114 | #define PN532_GPIO_P31 (1) 115 | #define PN532_GPIO_P32 (2) 116 | #define PN532_GPIO_P33 (3) 117 | #define PN532_GPIO_P34 (4) 118 | #define PN532_GPIO_P35 (5) 119 | 120 | // FeliCa consts 121 | #define FELICA_READ_MAX_SERVICE_NUM 16 122 | #define FELICA_READ_MAX_BLOCK_NUM 12 // for typical FeliCa card 123 | #define FELICA_WRITE_MAX_SERVICE_NUM 16 124 | #define FELICA_WRITE_MAX_BLOCK_NUM 10 // for typical FeliCa card 125 | #define FELICA_REQ_SERVICE_MAX_NODE_NUM 32 126 | 127 | class PN532 { 128 | public: 129 | PN532(PN532Interface& interface); 130 | 131 | void begin(void); 132 | 133 | // Generic PN532 functions 134 | bool SAMConfig(void); 135 | uint32_t getFirmwareVersion(void); 136 | uint32_t readRegister(uint16_t reg); 137 | uint32_t writeRegister(uint16_t reg, uint8_t val); 138 | bool writeGPIO(uint8_t pinstate); 139 | uint8_t readGPIO(void); 140 | bool setPassiveActivationRetries(uint8_t maxRetries); 141 | bool setRFField(uint8_t autoRFCA, uint8_t rFOnOff); 142 | 143 | /** 144 | @brief Init PN532 as a target 145 | @param timeout max time to wait, 0 means no timeout 146 | @return > 0 success 147 | = 0 timeout 148 | < 0 failed 149 | */ 150 | int8_t tgInitAsTarget(uint16_t timeout = 0); 151 | int8_t tgInitAsTarget(const uint8_t* command, const uint8_t len, const uint16_t timeout = 0); 152 | 153 | int16_t tgGetData(uint8_t* buf, uint8_t len); 154 | bool tgSetData(const uint8_t* header, uint8_t hlen, const uint8_t* body = 0, uint8_t blen = 0); 155 | 156 | int16_t inRelease(const uint8_t relevantTarget = 0); 157 | 158 | // ISO14443A functions 159 | bool inListPassiveTarget(); 160 | bool readPassiveTargetID(uint8_t cardbaudrate, uint8_t* uid, uint8_t* uidLength, uint16_t timeout = 1000, 161 | bool inlist = false); 162 | bool inDataExchange(uint8_t* send, uint8_t sendLength, uint8_t* response, uint8_t* responseLength); 163 | 164 | // Mifare Classic functions 165 | bool mifareclassic_IsFirstBlock(uint32_t uiBlock); 166 | bool mifareclassic_IsTrailerBlock(uint32_t uiBlock); 167 | uint8_t mifareclassic_AuthenticateBlock(uint8_t* uid, uint8_t uidLen, uint32_t blockNumber, uint8_t keyNumber, 168 | uint8_t* keyData); 169 | uint8_t mifareclassic_ReadDataBlock(uint8_t blockNumber, uint8_t* data); 170 | uint8_t mifareclassic_WriteDataBlock(uint8_t blockNumber, uint8_t* data); 171 | uint8_t mifareclassic_FormatNDEF(void); 172 | uint8_t mifareclassic_WriteNDEFURI(uint8_t sectorNumber, uint8_t uriIdentifier, const char* url); 173 | 174 | // Mifare Ultralight functions 175 | uint8_t mifareultralight_ReadPage(uint8_t page, uint8_t* buffer); 176 | uint8_t mifareultralight_WritePage(uint8_t page, uint8_t* buffer); 177 | 178 | // FeliCa Functions 179 | int8_t felica_Polling(uint16_t systemCode, uint8_t requestCode, uint8_t* idm, uint8_t* pmm, 180 | uint16_t* systemCodeResponse, uint16_t timeout = 1000); 181 | int8_t felica_SendCommand(const uint8_t* command, uint8_t commandlength, uint8_t* response, uint8_t* responseLength); 182 | int8_t felica_RequestService(uint8_t numNode, uint16_t* nodeCodeList, uint16_t* keyVersions) ; 183 | int8_t felica_RequestResponse(uint8_t* mode); 184 | int8_t felica_ReadWithoutEncryption(uint8_t numService, const uint16_t* serviceCodeList, uint8_t numBlock, 185 | const uint16_t* blockList, uint8_t blockData[][16]); 186 | int8_t felica_WriteWithoutEncryption(uint8_t numService, const uint16_t* serviceCodeList, uint8_t numBlock, 187 | const uint16_t* blockList, uint8_t blockData[][16]); 188 | int8_t felica_RequestSystemCode(uint8_t* numSystemCode, uint16_t* systemCodeList); 189 | int8_t felica_Release(); 190 | 191 | // Help functions to display formatted text 192 | static void PrintHex(const uint8_t* data, const uint32_t numBytes); 193 | static void PrintHexChar(const uint8_t* pbtData, const uint32_t numBytes); 194 | 195 | uint8_t* getBuffer(uint8_t* len) { 196 | *len = sizeof(pn532_packetbuffer) - 4; 197 | return pn532_packetbuffer; 198 | }; 199 | 200 | private: 201 | uint8_t _uid[7]; // ISO14443A uid 202 | uint8_t _uidLen; // uid len 203 | uint8_t _key[6]; // Mifare Classic key 204 | uint8_t inListedTag; // Tg number of inlisted tag. 205 | uint8_t _felicaIDm[8]; // FeliCa IDm (NFCID2) 206 | uint8_t _felicaPMm[8]; // FeliCa PMm (PAD) 207 | 208 | uint8_t pn532_packetbuffer[64]; 209 | 210 | PN532Interface* _interface; 211 | }; 212 | 213 | #endif 214 | -------------------------------------------------------------------------------- /src/PN532/PN532/PN532Interface.h: -------------------------------------------------------------------------------- 1 | 2 | 3 | #ifndef __PN532_INTERFACE_H__ 4 | #define __PN532_INTERFACE_H__ 5 | 6 | #include 7 | 8 | #define PN532_PREAMBLE (0x00) 9 | #define PN532_STARTCODE1 (0x00) 10 | #define PN532_STARTCODE2 (0xFF) 11 | #define PN532_POSTAMBLE (0x00) 12 | 13 | #define PN532_HOSTTOPN532 (0xD4) 14 | #define PN532_PN532TOHOST (0xD5) 15 | 16 | #define PN532_ACK_WAIT_TIME (10) // ms, timeout of waiting for ACK 17 | 18 | #define PN532_INVALID_ACK (-1) 19 | #define PN532_TIMEOUT (-2) 20 | #define PN532_INVALID_FRAME (-3) 21 | #define PN532_NO_SPACE (-4) 22 | 23 | #define REVERSE_BITS_ORDER(b) b = (b & 0xF0) >> 4 | (b & 0x0F) << 4; \ 24 | b = (b & 0xCC) >> 2 | (b & 0x33) << 2; \ 25 | b = (b & 0xAA) >> 1 | (b & 0x55) << 1 26 | 27 | class PN532Interface { 28 | public: 29 | virtual void begin() = 0; 30 | virtual void wakeup() = 0; 31 | 32 | /** 33 | @brief write a command and check ack 34 | @param header packet header 35 | @param hlen length of header 36 | @param body packet body 37 | @param blen length of body 38 | @return 0 success 39 | not 0 failed 40 | */ 41 | virtual int8_t writeCommand(const uint8_t* header, uint8_t hlen, const uint8_t* body = 0, uint8_t blen = 0) = 0; 42 | 43 | /** 44 | @brief read the response of a command, strip prefix and suffix 45 | @param buf to contain the response data 46 | @param len lenght to read 47 | @param timeout max time to wait, 0 means no timeout 48 | @return >=0 length of response without prefix and suffix 49 | <0 failed to read response 50 | */ 51 | virtual int16_t readResponse(uint8_t buf[], uint8_t len, uint16_t timeout = 1000) = 0; 52 | }; 53 | 54 | #endif 55 | 56 | -------------------------------------------------------------------------------- /src/PN532/PN532/PN532_debug.h: -------------------------------------------------------------------------------- 1 | #ifndef __DEBUG_H__ 2 | #define __DEBUG_H__ 3 | 4 | //#define DEBUG 5 | 6 | #include "Arduino.h" 7 | 8 | #if defined(ARDUINO_SAMD_VARIANT_COMPLIANCE) && defined(SerialUSB) 9 | #define SERIAL SerialUSB 10 | #else 11 | #define SERIAL Serial 12 | #endif 13 | 14 | #ifdef DEBUG 15 | #define DMSG(args...) SERIAL.print(args) 16 | #define DMSG_STR(str) SERIAL.println(str) 17 | #define DMSG_HEX(num) SERIAL.print(' '); SERIAL.print((num>>4)&0x0F, HEX); SERIAL.print(num&0x0F, HEX) 18 | #define DMSG_INT(num) SERIAL.print(' '); SERIAL.print(num) 19 | #else 20 | #define DMSG(args...) 21 | #define DMSG_STR(str) 22 | #define DMSG_HEX(num) 23 | #define DMSG_INT(num) 24 | #endif 25 | 26 | #endif 27 | -------------------------------------------------------------------------------- /src/PN532/PN532/README.md: -------------------------------------------------------------------------------- 1 | ## NFC library for Arduino 2 | 3 | This is an Arduino library for PN532 to use NFC technology. 4 | It works with 5 | 6 | + [NFC Shield](http://goo.gl/Cac2OH) 7 | + [Xadow NFC](http://goo.gl/qBZMt0) 8 | + [PN532 NFC/RFID controller breakout board](http://goo.gl/tby9Sw) 9 | 10 | ### Features 11 | + Support I2C, SPI and HSU of PN532 12 | + Read/write Mifare Classic Card 13 | + Works with [Don's NDEF Library](http://goo.gl/jDjsXl) 14 | + Support Peer to Peer communication(exchange data with android 4.0+) 15 | + Support [mbed platform](http://goo.gl/kGPovZ) 16 | 17 | ### Getting Started 18 | 1. Download [zip file](http://goo.gl/F6beRM) and 19 | extract the 4 folders(PN532, PN532_SPI, PN532_I2C and PN532_HSU) into Arduino's libraries. 20 | 2. Downlaod [Don's NDEF library](http://goo.gl/ewxeAe) and extract it intro Arduino's libraries. 21 | 3. Follow the examples of the two libraries. 22 | 23 | ### To do 24 | + Card emulation 25 | 26 | ### Contribution 27 | It's based on [Adafruit_NFCShield_I2C](http://goo.gl/pk3FdB). 28 | [Seeed Studio](http://goo.gl/zh1iQh) adds SPI interface and peer to peer communication support. 29 | @Don writes the [NDEF library](http://goo.gl/jDjsXl) to make it more easy to use. 30 | @JiapengLi adds HSU interface. 31 | -------------------------------------------------------------------------------- /src/PN532/PN532/emulatetag.cpp: -------------------------------------------------------------------------------- 1 | /**************************************************************************/ 2 | /*! 3 | @file emulatetag.cpp 4 | @author Armin Wieser 5 | @license BSD 6 | */ 7 | /**************************************************************************/ 8 | 9 | #include "PN532/PN532/emulatetag.h" 10 | #include "PN532/PN532/PN532_debug.h" 11 | 12 | #include 13 | 14 | #define MAX_TGREAD 15 | 16 | // Command APDU 17 | #define C_APDU_CLA 0 18 | #define C_APDU_INS 1 // instruction 19 | #define C_APDU_P1 2 // parameter 1 20 | #define C_APDU_P2 3 // parameter 2 21 | #define C_APDU_LC 4 // length command 22 | #define C_APDU_DATA 5 // data 23 | 24 | #define C_APDU_P1_SELECT_BY_ID 0x00 25 | #define C_APDU_P1_SELECT_BY_NAME 0x04 26 | 27 | // Response APDU 28 | #define R_APDU_SW1_COMMAND_COMPLETE 0x90 29 | #define R_APDU_SW2_COMMAND_COMPLETE 0x00 30 | 31 | #define R_APDU_SW1_NDEF_TAG_NOT_FOUND 0x6a 32 | #define R_APDU_SW2_NDEF_TAG_NOT_FOUND 0x82 33 | 34 | #define R_APDU_SW1_FUNCTION_NOT_SUPPORTED 0x6A 35 | #define R_APDU_SW2_FUNCTION_NOT_SUPPORTED 0x81 36 | 37 | #define R_APDU_SW1_MEMORY_FAILURE 0x65 38 | #define R_APDU_SW2_MEMORY_FAILURE 0x81 39 | 40 | #define R_APDU_SW1_END_OF_FILE_BEFORE_REACHED_LE_BYTES 0x62 41 | #define R_APDU_SW2_END_OF_FILE_BEFORE_REACHED_LE_BYTES 0x82 42 | 43 | // ISO7816-4 commands 44 | #define ISO7816_SELECT_FILE 0xA4 45 | #define ISO7816_READ_BINARY 0xB0 46 | #define ISO7816_UPDATE_BINARY 0xD6 47 | 48 | typedef enum { 49 | NONE, 50 | CC, 51 | NDEF 52 | } tag_file; // CC ... Compatibility Container 53 | 54 | bool EmulateTag::init() { 55 | pn532.begin(); 56 | return pn532.SAMConfig(); 57 | } 58 | 59 | void EmulateTag::setNdefFile(const uint8_t* ndef, const int16_t ndefLength) { 60 | if (ndefLength > (NDEF_MAX_LENGTH - 2)) { 61 | DMSG("ndef file too large (> NDEF_MAX_LENGHT -2) - aborting"); 62 | return; 63 | } 64 | 65 | ndef_file[0] = ndefLength >> 8; 66 | ndef_file[1] = ndefLength & 0xFF; 67 | memcpy(ndef_file + 2, ndef, ndefLength); 68 | } 69 | 70 | void EmulateTag::setUid(uint8_t* uid) { 71 | uidPtr = uid; 72 | } 73 | 74 | bool EmulateTag::emulate(const uint16_t tgInitAsTargetTimeout) { 75 | 76 | uint8_t command[] = { 77 | PN532_COMMAND_TGINITASTARGET, 78 | 5, // MODE: PICC only, Passive only 79 | 80 | 0x04, 0x00, // SENS_RES 81 | 0x00, 0x00, 0x00, // NFCID1 82 | 0x20, // SEL_RES 83 | 84 | 0, 0, 0, 0, 0, 0, 0, 0, 85 | 0, 0, 0, 0, 0, 0, 0, 0, // FeliCaParams 86 | 0, 0, 87 | 88 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // NFCID3t 89 | 90 | 0, // length of general bytes 91 | 0 // length of historical bytes 92 | }; 93 | 94 | if (uidPtr != 0) { 95 | // if uid is set copy 3 bytes to nfcid1 96 | memcpy(command + 4, uidPtr, 3); 97 | } 98 | 99 | if (1 != pn532.tgInitAsTarget(command, sizeof(command), tgInitAsTargetTimeout)) { 100 | DMSG("tgInitAsTarget failed or timed out!"); 101 | return false; 102 | } 103 | 104 | uint8_t compatibility_container[] = { 105 | 0, 0x0F, 106 | 0x20, 107 | 0, 0x54, 108 | 0, 0xFF, 109 | 0x04, // T 110 | 0x06, // L 111 | 0xE1, 0x04, // File identifier 112 | ((NDEF_MAX_LENGTH & 0xFF00) >> 8), (NDEF_MAX_LENGTH & 0xFF), // maximum NDEF file size 113 | 0x00, // read access 0x0 = granted 114 | 0x00 // write access 0x0 = granted | 0xFF = deny 115 | }; 116 | 117 | if (tagWriteable == false) { 118 | compatibility_container[14] = 0xFF; 119 | } 120 | 121 | tagWrittenByInitiator = false; 122 | 123 | uint8_t rwbuf[128]; 124 | uint8_t sendlen; 125 | int16_t status; 126 | tag_file currentFile = NONE; 127 | uint16_t cc_size = sizeof(compatibility_container); 128 | bool runLoop = true; 129 | 130 | while (runLoop) { 131 | status = pn532.tgGetData(rwbuf, sizeof(rwbuf)); 132 | if (status < 0) { 133 | DMSG("tgGetData failed!\n"); 134 | pn532.inRelease(); 135 | return true; 136 | } 137 | 138 | uint8_t p1 = rwbuf[C_APDU_P1]; 139 | uint8_t p2 = rwbuf[C_APDU_P2]; 140 | uint8_t lc = rwbuf[C_APDU_LC]; 141 | uint16_t p1p2_length = ((int16_t)p1 << 8) + p2; 142 | 143 | switch (rwbuf[C_APDU_INS]) { 144 | case ISO7816_SELECT_FILE: 145 | switch (p1) { 146 | case C_APDU_P1_SELECT_BY_ID: 147 | if (p2 != 0x0c) { 148 | DMSG("C_APDU_P2 != 0x0c\n"); 149 | setResponse(COMMAND_COMPLETE, rwbuf, &sendlen); 150 | } else if (lc == 2 && rwbuf[C_APDU_DATA] == 0xE1 && (rwbuf[C_APDU_DATA + 1] == 0x03 151 | || rwbuf[C_APDU_DATA + 1] == 0x04)) { 152 | setResponse(COMMAND_COMPLETE, rwbuf, &sendlen); 153 | if (rwbuf[C_APDU_DATA + 1] == 0x03) { 154 | currentFile = CC; 155 | } else if (rwbuf[C_APDU_DATA + 1] == 0x04) { 156 | currentFile = NDEF; 157 | } 158 | } else { 159 | setResponse(TAG_NOT_FOUND, rwbuf, &sendlen); 160 | } 161 | break; 162 | case C_APDU_P1_SELECT_BY_NAME: 163 | const uint8_t ndef_tag_application_name_v2[] = {0, 0x7, 0xD2, 0x76, 0x00, 0x00, 0x85, 0x01, 0x01}; 164 | if (0 == memcmp(ndef_tag_application_name_v2, rwbuf + C_APDU_P2, sizeof(ndef_tag_application_name_v2))) { 165 | setResponse(COMMAND_COMPLETE, rwbuf, &sendlen); 166 | } else { 167 | DMSG("function not supported\n"); 168 | setResponse(FUNCTION_NOT_SUPPORTED, rwbuf, &sendlen); 169 | } 170 | break; 171 | } 172 | break; 173 | case ISO7816_READ_BINARY: 174 | switch (currentFile) { 175 | case NONE: 176 | setResponse(TAG_NOT_FOUND, rwbuf, &sendlen); 177 | break; 178 | case CC: 179 | if (p1p2_length > NDEF_MAX_LENGTH) { 180 | setResponse(END_OF_FILE_BEFORE_REACHED_LE_BYTES, rwbuf, &sendlen); 181 | } else { 182 | memcpy(rwbuf, compatibility_container + p1p2_length, lc); 183 | setResponse(COMMAND_COMPLETE, rwbuf + lc, &sendlen, lc); 184 | } 185 | break; 186 | case NDEF: 187 | if (p1p2_length > NDEF_MAX_LENGTH) { 188 | setResponse(END_OF_FILE_BEFORE_REACHED_LE_BYTES, rwbuf, &sendlen); 189 | } else { 190 | memcpy(rwbuf, ndef_file + p1p2_length, lc); 191 | setResponse(COMMAND_COMPLETE, rwbuf + lc, &sendlen, lc); 192 | } 193 | break; 194 | } 195 | break; 196 | case ISO7816_UPDATE_BINARY: 197 | if (!tagWriteable) { 198 | setResponse(FUNCTION_NOT_SUPPORTED, rwbuf, &sendlen); 199 | } else { 200 | if (p1p2_length > NDEF_MAX_LENGTH) { 201 | setResponse(MEMORY_FAILURE, rwbuf, &sendlen); 202 | } else { 203 | memcpy(ndef_file + p1p2_length, rwbuf + C_APDU_DATA, lc); 204 | setResponse(COMMAND_COMPLETE, rwbuf, &sendlen); 205 | tagWrittenByInitiator = true; 206 | 207 | uint16_t ndef_length = (ndef_file[0] << 8) + ndef_file[1]; 208 | if ((ndef_length > 0) && (updateNdefCallback != 0)) { 209 | updateNdefCallback(ndef_file + 2, ndef_length); 210 | } 211 | } 212 | } 213 | break; 214 | default: 215 | DMSG("Command not supported!"); 216 | DMSG_HEX(rwbuf[C_APDU_INS]); 217 | DMSG("\n"); 218 | setResponse(FUNCTION_NOT_SUPPORTED, rwbuf, &sendlen); 219 | } 220 | status = pn532.tgSetData(rwbuf, sendlen); 221 | if (status < 0) { 222 | DMSG("tgSetData failed\n!"); 223 | pn532.inRelease(); 224 | return true; 225 | } 226 | } 227 | pn532.inRelease(); 228 | return true; 229 | } 230 | 231 | void EmulateTag::setResponse(responseCommand cmd, uint8_t* buf, uint8_t* sendlen, uint8_t sendlenOffset) { 232 | switch (cmd) { 233 | case COMMAND_COMPLETE: 234 | buf[0] = R_APDU_SW1_COMMAND_COMPLETE; 235 | buf[1] = R_APDU_SW2_COMMAND_COMPLETE; 236 | *sendlen = 2 + sendlenOffset; 237 | break; 238 | case TAG_NOT_FOUND: 239 | buf[0] = R_APDU_SW1_NDEF_TAG_NOT_FOUND; 240 | buf[1] = R_APDU_SW2_NDEF_TAG_NOT_FOUND; 241 | *sendlen = 2; 242 | break; 243 | case FUNCTION_NOT_SUPPORTED: 244 | buf[0] = R_APDU_SW1_FUNCTION_NOT_SUPPORTED; 245 | buf[1] = R_APDU_SW2_FUNCTION_NOT_SUPPORTED; 246 | *sendlen = 2; 247 | break; 248 | case MEMORY_FAILURE: 249 | buf[0] = R_APDU_SW1_MEMORY_FAILURE; 250 | buf[1] = R_APDU_SW2_MEMORY_FAILURE; 251 | *sendlen = 2; 252 | break; 253 | case END_OF_FILE_BEFORE_REACHED_LE_BYTES: 254 | buf[0] = R_APDU_SW1_END_OF_FILE_BEFORE_REACHED_LE_BYTES; 255 | buf[1] = R_APDU_SW2_END_OF_FILE_BEFORE_REACHED_LE_BYTES; 256 | *sendlen = 2; 257 | break; 258 | } 259 | } 260 | -------------------------------------------------------------------------------- /src/PN532/PN532/emulatetag.h: -------------------------------------------------------------------------------- 1 | /**************************************************************************/ 2 | /*! 3 | @file PN532/PN532/emulatetag.h 4 | @author Armin Wieser 5 | @license BSD 6 | 7 | Implemented using NFC forum documents & library of libnfc 8 | */ 9 | /**************************************************************************/ 10 | 11 | #ifndef __EMULATETAG_H__ 12 | #define __EMULATETAG_H__ 13 | 14 | #include "PN532/PN532/PN532.h" 15 | 16 | #define NDEF_MAX_LENGTH 128 // altough ndef can handle up to 0xfffe in size, arduino cannot. 17 | typedef enum { 18 | COMMAND_COMPLETE, 19 | TAG_NOT_FOUND, 20 | FUNCTION_NOT_SUPPORTED, 21 | MEMORY_FAILURE, 22 | END_OF_FILE_BEFORE_REACHED_LE_BYTES 23 | } responseCommand; 24 | 25 | class EmulateTag { 26 | 27 | public: 28 | EmulateTag(PN532Interface& interface) : pn532(interface), uidPtr(0), tagWrittenByInitiator(false), tagWriteable(true), 29 | updateNdefCallback(0) {} 30 | 31 | bool init(); 32 | 33 | bool emulate(const uint16_t tgInitAsTargetTimeout = 0); 34 | 35 | /* 36 | @param uid pointer to byte array of length 3 (uid is 4 bytes - first byte is fixed) or zero for uid 37 | */ 38 | void setUid(uint8_t* uid = 0); 39 | 40 | void setNdefFile(const uint8_t* ndef, const int16_t ndefLength); 41 | 42 | void getContent(uint8_t** buf, uint16_t* length) { 43 | *buf = ndef_file + 2; // first 2 bytes = length 44 | *length = (ndef_file[0] << 8) + ndef_file[1]; 45 | } 46 | 47 | bool writeOccured() { 48 | return tagWrittenByInitiator; 49 | } 50 | 51 | void setTagWriteable(bool setWriteable) { 52 | tagWriteable = setWriteable; 53 | } 54 | 55 | uint8_t* getNdefFilePtr() { 56 | return ndef_file; 57 | } 58 | 59 | uint8_t getNdefMaxLength() { 60 | return NDEF_MAX_LENGTH; 61 | } 62 | 63 | void attach(void (*func)(uint8_t* buf, uint16_t length)) { 64 | updateNdefCallback = func; 65 | }; 66 | 67 | private: 68 | PN532 pn532; 69 | uint8_t ndef_file[NDEF_MAX_LENGTH]; 70 | uint8_t* uidPtr; 71 | bool tagWrittenByInitiator; 72 | bool tagWriteable; 73 | void (*updateNdefCallback)(uint8_t* ndef, uint16_t length); 74 | 75 | void setResponse(responseCommand cmd, uint8_t* buf, uint8_t* sendlen, uint8_t sendlenOffset = 0); 76 | }; 77 | 78 | #endif 79 | -------------------------------------------------------------------------------- /src/PN532/PN532/license.txt: -------------------------------------------------------------------------------- 1 | Software License Agreement (BSD License) 2 | 3 | Copyright (c) 2012, Adafruit Industries 4 | Copyright (c) 2013, Seeed Technology Inc. 5 | All rights reserved. 6 | 7 | Redistribution and use in source and binary forms, with or without 8 | modification, are permitted provided that the following conditions are met: 9 | 1. Redistributions of source code must retain the above copyright 10 | notice, this list of conditions and the following disclaimer. 11 | 2. Redistributions in binary form must reproduce the above copyright 12 | notice, this list of conditions and the following disclaimer in the 13 | documentation and/or other materials provided with the distribution. 14 | 3. Neither the name of the copyright holders nor the 15 | names of its contributors may be used to endorse or promote products 16 | derived from this software without specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ''AS IS'' AND ANY 19 | EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 20 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY 22 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 23 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 24 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 25 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 27 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /src/PN532/PN532/llcp.cpp: -------------------------------------------------------------------------------- 1 | 2 | #include "PN532/PN532/llcp.h" 3 | #include "PN532/PN532/PN532_debug.h" 4 | 5 | // LLCP PDU Type Values 6 | #define PDU_SYMM 0x00 7 | #define PDU_PAX 0x01 8 | #define PDU_CONNECT 0x04 9 | #define PDU_DISC 0x05 10 | #define PDU_CC 0x06 11 | #define PDU_DM 0x07 12 | #define PDU_I 0x0c 13 | #define PDU_RR 0x0d 14 | 15 | uint8_t LLCP::SYMM_PDU[2] = {0, 0}; 16 | 17 | inline uint8_t getPType(const uint8_t* buf) { 18 | return ((buf[0] & 0x3) << 2) + (buf[1] >> 6); 19 | } 20 | 21 | inline uint8_t getSSAP(const uint8_t* buf) { 22 | return buf[1] & 0x3f; 23 | } 24 | 25 | inline uint8_t getDSAP(const uint8_t* buf) { 26 | return buf[0] >> 2; 27 | } 28 | 29 | int8_t LLCP::activate(uint16_t timeout) { 30 | return link.activateAsTarget(timeout); 31 | } 32 | 33 | int8_t LLCP::waitForConnection(uint16_t timeout) { 34 | uint8_t type; 35 | 36 | mode = 1; 37 | ns = 0; 38 | nr = 0; 39 | 40 | // Get CONNECT PDU 41 | DMSG("wait for a CONNECT PDU\n"); 42 | do { 43 | if (2 > link.read(headerBuf, headerBufLen)) { 44 | return -1; 45 | } 46 | 47 | type = getPType(headerBuf); 48 | if (PDU_CONNECT == type) { 49 | break; 50 | } else if (PDU_SYMM == type) { 51 | if (!link.write(SYMM_PDU, sizeof(SYMM_PDU))) { 52 | return -2; 53 | } 54 | } else { 55 | return -3; 56 | } 57 | 58 | } while (1); 59 | 60 | // Put CC PDU 61 | DMSG("put a CC(Connection Complete) PDU to response the CONNECT PDU\n"); 62 | ssap = getDSAP(headerBuf); 63 | dsap = getSSAP(headerBuf); 64 | headerBuf[0] = (dsap << 2) + ((PDU_CC >> 2) & 0x3); 65 | headerBuf[1] = ((PDU_CC & 0x3) << 6) + ssap; 66 | if (!link.write(headerBuf, 2)) { 67 | return -2; 68 | } 69 | 70 | return 1; 71 | } 72 | 73 | int8_t LLCP::waitForDisconnection(uint16_t timeout) { 74 | uint8_t type; 75 | 76 | // Get DISC PDU 77 | DMSG("wait for a DISC PDU\n"); 78 | do { 79 | if (2 > link.read(headerBuf, headerBufLen)) { 80 | return -1; 81 | } 82 | 83 | type = getPType(headerBuf); 84 | if (PDU_DISC == type) { 85 | break; 86 | } else if (PDU_SYMM == type) { 87 | if (!link.write(SYMM_PDU, sizeof(SYMM_PDU))) { 88 | return -2; 89 | } 90 | } else { 91 | return -3; 92 | } 93 | 94 | } while (1); 95 | 96 | // Put DM PDU 97 | DMSG("put a DM(Disconnect Mode) PDU to response the DISC PDU\n"); 98 | // ssap = getDSAP(headerBuf); 99 | // dsap = getSSAP(headerBuf); 100 | headerBuf[0] = (dsap << 2) + (PDU_DM >> 2); 101 | headerBuf[1] = ((PDU_DM & 0x3) << 6) + ssap; 102 | if (!link.write(headerBuf, 2)) { 103 | return -2; 104 | } 105 | 106 | return 1; 107 | } 108 | 109 | int8_t LLCP::connect(uint16_t timeout) { 110 | uint8_t type; 111 | 112 | mode = 0; 113 | dsap = LLCP_DEFAULT_DSAP; 114 | ssap = LLCP_DEFAULT_SSAP; 115 | ns = 0; 116 | nr = 0; 117 | 118 | // try to get a SYMM PDU 119 | if (2 > link.read(headerBuf, headerBufLen)) { 120 | return -1; 121 | } 122 | type = getPType(headerBuf); 123 | if (PDU_SYMM != type) { 124 | return -1; 125 | } 126 | 127 | // put a CONNECT PDU 128 | headerBuf[0] = (LLCP_DEFAULT_DSAP << 2) + (PDU_CONNECT >> 2); 129 | headerBuf[1] = ((PDU_CONNECT & 0x03) << 6) + LLCP_DEFAULT_SSAP; 130 | uint8_t body[] = " urn:nfc:sn:snep"; 131 | body[0] = 0x06; 132 | body[1] = sizeof(body) - 2 - 1; 133 | if (!link.write(headerBuf, 2, body, sizeof(body) - 1)) { 134 | return -2; 135 | } 136 | 137 | // wait for a CC PDU 138 | DMSG("wait for a CC PDU\n"); 139 | do { 140 | if (2 > link.read(headerBuf, headerBufLen)) { 141 | return -1; 142 | } 143 | 144 | type = getPType(headerBuf); 145 | if (PDU_CC == type) { 146 | break; 147 | } else if (PDU_SYMM == type) { 148 | if (!link.write(SYMM_PDU, sizeof(SYMM_PDU))) { 149 | return -2; 150 | } 151 | } else { 152 | return -3; 153 | } 154 | 155 | } while (1); 156 | 157 | return 1; 158 | } 159 | 160 | int8_t LLCP::disconnect(uint16_t timeout) { 161 | uint8_t type; 162 | 163 | // try to get a SYMM PDU 164 | if (2 > link.read(headerBuf, headerBufLen)) { 165 | return -1; 166 | } 167 | type = getPType(headerBuf); 168 | if (PDU_SYMM != type) { 169 | return -1; 170 | } 171 | 172 | // put a DISC PDU 173 | headerBuf[0] = (LLCP_DEFAULT_DSAP << 2) + (PDU_DISC >> 2); 174 | headerBuf[1] = ((PDU_DISC & 0x03) << 6) + LLCP_DEFAULT_SSAP; 175 | if (!link.write(headerBuf, 2)) { 176 | return -2; 177 | } 178 | 179 | // wait for a DM PDU 180 | DMSG("wait for a DM PDU\n"); 181 | do { 182 | if (2 > link.read(headerBuf, headerBufLen)) { 183 | return -1; 184 | } 185 | 186 | type = getPType(headerBuf); 187 | if (PDU_CC == type) { 188 | break; 189 | } else if (PDU_DM == type) { 190 | if (!link.write(SYMM_PDU, sizeof(SYMM_PDU))) { 191 | return -2; 192 | } 193 | } else { 194 | return -3; 195 | } 196 | 197 | } while (1); 198 | 199 | return 1; 200 | } 201 | 202 | bool LLCP::write(const uint8_t* header, uint8_t hlen, const uint8_t* body, uint8_t blen) { 203 | uint8_t type; 204 | uint8_t buf[3]; 205 | 206 | if (mode) { 207 | // Get a SYMM PDU 208 | if (2 != link.read(buf, sizeof(buf))) { 209 | return false; 210 | } 211 | } 212 | 213 | if (headerBufLen < (hlen + 3)) { 214 | return false; 215 | } 216 | 217 | for (int8_t i = hlen - 1; i >= 0; i--) { 218 | headerBuf[i + 3] = header[i]; 219 | } 220 | 221 | headerBuf[0] = (dsap << 2) + (PDU_I >> 2); 222 | headerBuf[1] = ((PDU_I & 0x3) << 6) + ssap; 223 | headerBuf[2] = (ns << 4) + nr; 224 | if (!link.write(headerBuf, 3 + hlen, body, blen)) { 225 | return false; 226 | } 227 | 228 | ns++; 229 | 230 | // Get a RR PDU 231 | int16_t status; 232 | do { 233 | status = link.read(headerBuf, headerBufLen); 234 | if (2 > status) { 235 | return false; 236 | } 237 | 238 | type = getPType(headerBuf); 239 | if (PDU_RR == type) { 240 | break; 241 | } else if (PDU_SYMM == type) { 242 | if (!link.write(SYMM_PDU, sizeof(SYMM_PDU))) { 243 | return false; 244 | } 245 | } else { 246 | return false; 247 | } 248 | } while (1); 249 | 250 | if (!link.write(SYMM_PDU, sizeof(SYMM_PDU))) { 251 | return false; 252 | } 253 | 254 | return true; 255 | } 256 | 257 | int16_t LLCP::read(uint8_t* buf, uint8_t length) { 258 | uint8_t type; 259 | uint16_t status; 260 | 261 | // Get INFO PDU 262 | do { 263 | status = link.read(buf, length); 264 | if (2 > status) { 265 | return -1; 266 | } 267 | 268 | type = getPType(buf); 269 | if (PDU_I == type) { 270 | break; 271 | } else if (PDU_SYMM == type) { 272 | if (!link.write(SYMM_PDU, sizeof(SYMM_PDU))) { 273 | return -2; 274 | } 275 | } else { 276 | return -3; 277 | } 278 | 279 | } while (1); 280 | 281 | uint8_t len = status - 3; 282 | ssap = getDSAP(buf); 283 | dsap = getSSAP(buf); 284 | 285 | headerBuf[0] = (dsap << 2) + (PDU_RR >> 2); 286 | headerBuf[1] = ((PDU_RR & 0x3) << 6) + ssap; 287 | headerBuf[2] = (buf[2] >> 4) + 1; 288 | if (!link.write(headerBuf, 3)) { 289 | return -2; 290 | } 291 | 292 | for (uint8_t i = 0; i < len; i++) { 293 | buf[i] = buf[i + 3]; 294 | } 295 | 296 | nr++; 297 | 298 | return len; 299 | } 300 | -------------------------------------------------------------------------------- /src/PN532/PN532/llcp.h: -------------------------------------------------------------------------------- 1 | 2 | #ifndef __LLCP_H__ 3 | #define __LLCP_H__ 4 | 5 | #include "PN532/PN532/mac_link.h" 6 | 7 | #define LLCP_DEFAULT_TIMEOUT 20000 8 | #define LLCP_DEFAULT_DSAP 0x04 9 | #define LLCP_DEFAULT_SSAP 0x20 10 | 11 | class LLCP { 12 | public: 13 | LLCP(PN532Interface& interface) : link(interface) { 14 | headerBuf = link.getHeaderBuffer(&headerBufLen); 15 | ns = 0; 16 | nr = 0; 17 | }; 18 | 19 | /** 20 | @brief Actiave PN532 as a target 21 | @param timeout max time to wait, 0 means no timeout 22 | @return > 0 success 23 | = 0 timeout 24 | < 0 failed 25 | */ 26 | int8_t activate(uint16_t timeout = 0); 27 | 28 | int8_t waitForConnection(uint16_t timeout = LLCP_DEFAULT_TIMEOUT); 29 | 30 | int8_t waitForDisconnection(uint16_t timeout = LLCP_DEFAULT_TIMEOUT); 31 | 32 | int8_t connect(uint16_t timeout = LLCP_DEFAULT_TIMEOUT); 33 | 34 | int8_t disconnect(uint16_t timeout = LLCP_DEFAULT_TIMEOUT); 35 | 36 | /** 37 | @brief write a packet, the packet should be less than (255 - 2) bytes 38 | @param header packet header 39 | @param hlen length of header 40 | @param body packet body 41 | @param blen length of body 42 | @return true success 43 | false failed 44 | */ 45 | bool write(const uint8_t* header, uint8_t hlen, const uint8_t* body = 0, uint8_t blen = 0); 46 | 47 | /** 48 | @brief read a packet, the packet will be less than (255 - 2) bytes 49 | @param buf the buffer to contain the packet 50 | @param len lenght of the buffer 51 | @return >=0 length of the packet 52 | <0 failed 53 | */ 54 | int16_t read(uint8_t* buf, uint8_t len); 55 | 56 | uint8_t* getHeaderBuffer(uint8_t* len) { 57 | uint8_t* buf = link.getHeaderBuffer(len); 58 | len -= 3; // I PDU header has 3 bytes 59 | return buf; 60 | }; 61 | 62 | private: 63 | MACLink link; 64 | uint8_t mode; 65 | uint8_t ssap; 66 | uint8_t dsap; 67 | uint8_t* headerBuf; 68 | uint8_t headerBufLen; 69 | uint8_t ns; // Number of I PDU Sent 70 | uint8_t nr; // Number of I PDU Received 71 | 72 | static uint8_t SYMM_PDU[2]; 73 | }; 74 | 75 | #endif // __LLCP_H__ 76 | -------------------------------------------------------------------------------- /src/PN532/PN532/mac_link.cpp: -------------------------------------------------------------------------------- 1 | 2 | #include "PN532/PN532/mac_link.h" 3 | #include "PN532/PN532/PN532_debug.h" 4 | 5 | int8_t MACLink::activateAsTarget(uint16_t timeout) { 6 | pn532.begin(); 7 | pn532.SAMConfig(); 8 | return pn532.tgInitAsTarget(timeout); 9 | } 10 | 11 | bool MACLink::write(const uint8_t* header, uint8_t hlen, const uint8_t* body, uint8_t blen) { 12 | return pn532.tgSetData(header, hlen, body, blen); 13 | } 14 | 15 | int16_t MACLink::read(uint8_t* buf, uint8_t len) { 16 | return pn532.tgGetData(buf, len); 17 | } 18 | -------------------------------------------------------------------------------- /src/PN532/PN532/mac_link.h: -------------------------------------------------------------------------------- 1 | 2 | 3 | #ifndef __MAC_LINK_H__ 4 | #define __MAC_LINK_H__ 5 | 6 | #include "PN532/PN532/PN532.h" 7 | 8 | class MACLink { 9 | public: 10 | MACLink(PN532Interface& interface) : pn532(interface) { 11 | 12 | }; 13 | 14 | /** 15 | @brief Activate PN532 as a target 16 | @param timeout max time to wait, 0 means no timeout 17 | @return > 0 success 18 | = 0 timeout 19 | < 0 failed 20 | */ 21 | int8_t activateAsTarget(uint16_t timeout = 0); 22 | 23 | /** 24 | @brief write a PDU packet, the packet should be less than (255 - 2) bytes 25 | @param header packet header 26 | @param hlen length of header 27 | @param body packet body 28 | @param blen length of body 29 | @return true success 30 | false failed 31 | */ 32 | bool write(const uint8_t* header, uint8_t hlen, const uint8_t* body = 0, uint8_t blen = 0); 33 | 34 | /** 35 | @brief read a PDU packet, the packet will be less than (255 - 2) bytes 36 | @param buf the buffer to contain the PDU packet 37 | @param len lenght of the buffer 38 | @return >=0 length of the PDU packet 39 | <0 failed 40 | */ 41 | int16_t read(uint8_t* buf, uint8_t len); 42 | 43 | uint8_t* getHeaderBuffer(uint8_t* len) { 44 | return pn532.getBuffer(len); 45 | }; 46 | 47 | private: 48 | PN532 pn532; 49 | }; 50 | 51 | #endif // __MAC_LINK_H__ 52 | -------------------------------------------------------------------------------- /src/PN532/PN532/snep.cpp: -------------------------------------------------------------------------------- 1 | 2 | #include "PN532/PN532/snep.h" 3 | #include "PN532/PN532/PN532_debug.h" 4 | 5 | int8_t SNEP::write(const uint8_t* buf, uint8_t len, uint16_t timeout) { 6 | if (0 >= llcp.activate(timeout)) { 7 | DMSG("failed to activate PN532 as a target\n"); 8 | return -1; 9 | } 10 | 11 | if (0 >= llcp.connect(timeout)) { 12 | DMSG("failed to set up a connection\n"); 13 | return -2; 14 | } 15 | 16 | // response a success SNEP message 17 | headerBuf[0] = SNEP_DEFAULT_VERSION; 18 | headerBuf[1] = SNEP_REQUEST_PUT; 19 | headerBuf[2] = 0; 20 | headerBuf[3] = 0; 21 | headerBuf[4] = 0; 22 | headerBuf[5] = len; 23 | if (0 >= llcp.write(headerBuf, 6, buf, len)) { 24 | return -3; 25 | } 26 | 27 | uint8_t rbuf[16]; 28 | if (6 > llcp.read(rbuf, sizeof(rbuf))) { 29 | return -4; 30 | } 31 | 32 | // check SNEP version 33 | if (SNEP_DEFAULT_VERSION != rbuf[0]) { 34 | DMSG("The received SNEP message's major version is different\n"); 35 | // To-do: send Unsupported Version response 36 | return -4; 37 | } 38 | 39 | // expect a put request 40 | if (SNEP_RESPONSE_SUCCESS != rbuf[1]) { 41 | DMSG("Expect a success response\n"); 42 | return -4; 43 | } 44 | 45 | llcp.disconnect(timeout); 46 | 47 | return 1; 48 | } 49 | 50 | int16_t SNEP::read(uint8_t* buf, uint8_t len, uint16_t timeout) { 51 | if (0 >= llcp.activate(timeout)) { 52 | DMSG("failed to activate PN532 as a target\n"); 53 | return -1; 54 | } 55 | 56 | if (0 >= llcp.waitForConnection(timeout)) { 57 | DMSG("failed to set up a connection\n"); 58 | return -2; 59 | } 60 | 61 | uint16_t status = llcp.read(buf, len); 62 | if (6 > status) { 63 | return -3; 64 | } 65 | 66 | // check SNEP version 67 | 68 | // in case of platform specific bug, shift SNEP message for 4 bytes. 69 | // tested on Nexus 5, Android 5.1 70 | if (SNEP_DEFAULT_VERSION != buf[0] && SNEP_DEFAULT_VERSION == buf[4]) { 71 | for (uint8_t i = 0; i < len - 4; i++) { 72 | buf[i] = buf[i + 4]; 73 | } 74 | } 75 | 76 | if (SNEP_DEFAULT_VERSION != buf[0]) { 77 | DMSG(F("SNEP->read: The received SNEP message's major version is different, me: ")); 78 | DMSG(SNEP_DEFAULT_VERSION); 79 | DMSG(", their: "); 80 | DMSG(buf[0]); 81 | DMSG("\n"); 82 | // To-do: send Unsupported Version response 83 | return -4; 84 | } 85 | 86 | // expect a put request 87 | if (SNEP_REQUEST_PUT != buf[1]) { 88 | DMSG("Expect a put request\n"); 89 | return -4; 90 | } 91 | 92 | // check message's length 93 | uint32_t length = (buf[2] << 24) + (buf[3] << 16) + (buf[4] << 8) + buf[5]; 94 | // length should not be more than 244 (header + body < 255, header = 6 + 3 + 2) 95 | if (length > (status - 6)) { 96 | DMSG("The SNEP message is too large: "); 97 | DMSG_INT(length); 98 | DMSG_INT(status - 6); 99 | DMSG("\n"); 100 | return -4; 101 | } 102 | for (uint8_t i = 0; i < length; i++) { 103 | buf[i] = buf[i + 6]; 104 | } 105 | 106 | // response a success SNEP message 107 | headerBuf[0] = SNEP_DEFAULT_VERSION; 108 | headerBuf[1] = SNEP_RESPONSE_SUCCESS; 109 | headerBuf[2] = 0; 110 | headerBuf[3] = 0; 111 | headerBuf[4] = 0; 112 | headerBuf[5] = 0; 113 | llcp.write(headerBuf, 6); 114 | 115 | return length; 116 | } 117 | -------------------------------------------------------------------------------- /src/PN532/PN532/snep.h: -------------------------------------------------------------------------------- 1 | 2 | 3 | #ifndef __SNEP_H__ 4 | #define __SNEP_H__ 5 | 6 | #include "PN532/PN532/llcp.h" 7 | 8 | #define SNEP_DEFAULT_VERSION 0x10 // Major: 1, Minor: 0 9 | 10 | #define SNEP_REQUEST_PUT 0x02 11 | #define SNEP_REQUEST_GET 0x01 12 | 13 | #define SNEP_RESPONSE_SUCCESS 0x81 14 | #define SNEP_RESPONSE_REJECT 0xFF 15 | 16 | class SNEP { 17 | public: 18 | SNEP(PN532Interface& interface) : llcp(interface) { 19 | headerBuf = llcp.getHeaderBuffer(&headerBufLen); 20 | }; 21 | 22 | /** 23 | @brief write a SNEP packet, the packet should be less than (255 - 2 - 3) bytes 24 | @param buf the buffer to contain the packet 25 | @param len lenght of the buffer 26 | @param timeout max time to wait, 0 means no timeout 27 | @return >0 success 28 | =0 timeout 29 | <0 failed 30 | */ 31 | int8_t write(const uint8_t* buf, uint8_t len, uint16_t timeout = 0); 32 | 33 | /** 34 | @brief read a SNEP packet, the packet will be less than (255 - 2 - 3) bytes 35 | @param buf the buffer to contain the packet 36 | @param len lenght of the buffer 37 | @param timeout max time to wait, 0 means no timeout 38 | @return >=0 length of the packet 39 | <0 failed 40 | */ 41 | int16_t read(uint8_t* buf, uint8_t len, uint16_t timeout = 0); 42 | 43 | private: 44 | LLCP llcp; 45 | uint8_t* headerBuf; 46 | uint8_t headerBufLen; 47 | }; 48 | 49 | #endif // __SNEP_H__ 50 | -------------------------------------------------------------------------------- /src/PN532/PN532_HSU/PN532_HSU.cpp: -------------------------------------------------------------------------------- 1 | 2 | #include "PN532/PN532_HSU/PN532_HSU.h" 3 | #include "PN532/PN532/PN532_debug.h" 4 | 5 | PN532_HSU::PN532_HSU(HardwareSerial& serial) { 6 | _serial = &serial; 7 | command = 0; 8 | } 9 | 10 | void PN532_HSU::begin() { 11 | _serial->begin(115200); 12 | } 13 | 14 | void PN532_HSU::wakeup() { 15 | _serial->write(0x55); 16 | _serial->write(0x55); 17 | _serial->write(uint8_t(0x00)); 18 | _serial->write(uint8_t(0x00)); 19 | _serial->write(uint8_t(0x00)); 20 | 21 | /** dump serial buffer */ 22 | if (_serial->available()) { 23 | DMSG("Dump serial buffer: "); 24 | } 25 | while (_serial->available()) { 26 | uint8_t ret = _serial->read(); 27 | DMSG_HEX(ret); 28 | } 29 | } 30 | 31 | int8_t PN532_HSU::writeCommand(const uint8_t* header, uint8_t hlen, const uint8_t* body, uint8_t blen) { 32 | 33 | /** dump serial buffer */ 34 | if (_serial->available()) { 35 | DMSG("Dump serial buffer: "); 36 | } 37 | while (_serial->available()) { 38 | uint8_t ret = _serial->read(); 39 | DMSG_HEX(ret); 40 | } 41 | 42 | command = header[0]; 43 | 44 | _serial->write(uint8_t(PN532_PREAMBLE)); 45 | _serial->write(uint8_t(PN532_STARTCODE1)); 46 | _serial->write(uint8_t(PN532_STARTCODE2)); 47 | 48 | uint8_t length = hlen + blen + 1; // length of data field: TFI + DATA 49 | _serial->write(length); 50 | _serial->write(~length + 1); // checksum of length 51 | 52 | _serial->write(PN532_HOSTTOPN532); 53 | uint8_t sum = PN532_HOSTTOPN532; // sum of TFI + DATA 54 | 55 | DMSG("\nWrite: "); 56 | 57 | _serial->write(header, hlen); 58 | for (uint8_t i = 0; i < hlen; i++) { 59 | sum += header[i]; 60 | 61 | DMSG_HEX(header[i]); 62 | } 63 | 64 | _serial->write(body, blen); 65 | for (uint8_t i = 0; i < blen; i++) { 66 | sum += body[i]; 67 | 68 | DMSG_HEX(body[i]); 69 | } 70 | 71 | uint8_t checksum = ~sum + 1; // checksum of TFI + DATA 72 | _serial->write(checksum); 73 | _serial->write(uint8_t(PN532_POSTAMBLE)); 74 | 75 | return readAckFrame(); 76 | } 77 | 78 | int16_t PN532_HSU::readResponse(uint8_t buf[], uint8_t len, uint16_t timeout) { 79 | uint8_t tmp[3]; 80 | 81 | DMSG("\nRead: "); 82 | 83 | /** Frame Preamble and Start Code */ 84 | if (receive(tmp, 3, timeout) <= 0) { 85 | return PN532_TIMEOUT; 86 | } 87 | if (0 != tmp[0] || 0 != tmp[1] || 0xFF != tmp[2]) { 88 | DMSG("Preamble error"); 89 | return PN532_INVALID_FRAME; 90 | } 91 | 92 | /** receive length and check */ 93 | uint8_t length[2]; 94 | if (receive(length, 2, timeout) <= 0) { 95 | return PN532_TIMEOUT; 96 | } 97 | if (0 != (uint8_t)(length[0] + length[1])) { 98 | DMSG("Length error"); 99 | return PN532_INVALID_FRAME; 100 | } 101 | length[0] -= 2; 102 | if (length[0] > len) { 103 | return PN532_NO_SPACE; 104 | } 105 | 106 | /** receive command byte */ 107 | uint8_t cmd = command + 1; // response command 108 | if (receive(tmp, 2, timeout) <= 0) { 109 | return PN532_TIMEOUT; 110 | } 111 | if (PN532_PN532TOHOST != tmp[0] || cmd != tmp[1]) { 112 | DMSG("Command error"); 113 | return PN532_INVALID_FRAME; 114 | } 115 | 116 | if (receive(buf, length[0], timeout) != length[0]) { 117 | return PN532_TIMEOUT; 118 | } 119 | uint8_t sum = PN532_PN532TOHOST + cmd; 120 | for (uint8_t i = 0; i < length[0]; i++) { 121 | sum += buf[i]; 122 | } 123 | 124 | /** checksum and postamble */ 125 | if (receive(tmp, 2, timeout) <= 0) { 126 | return PN532_TIMEOUT; 127 | } 128 | if (0 != (uint8_t)(sum + tmp[0]) || 0 != tmp[1]) { 129 | DMSG("Checksum error"); 130 | return PN532_INVALID_FRAME; 131 | } 132 | 133 | return length[0]; 134 | } 135 | 136 | int8_t PN532_HSU::readAckFrame() { 137 | const uint8_t PN532_ACK[] = {0, 0, 0xFF, 0, 0xFF, 0}; 138 | uint8_t ackBuf[sizeof(PN532_ACK)]; 139 | 140 | DMSG("\nAck: "); 141 | 142 | if (receive(ackBuf, sizeof(PN532_ACK), PN532_ACK_WAIT_TIME) <= 0) { 143 | DMSG("Timeout\n"); 144 | return PN532_TIMEOUT; 145 | } 146 | 147 | if (memcmp(ackBuf, PN532_ACK, sizeof(PN532_ACK))) { 148 | DMSG("Invalid\n"); 149 | return PN532_INVALID_ACK; 150 | } 151 | return 0; 152 | } 153 | 154 | /** 155 | @brief receive data . 156 | @param buf --> return value buffer. 157 | len --> length expect to receive. 158 | timeout --> time of reveiving 159 | @retval number of received bytes, 0 means no data received. 160 | */ 161 | int8_t PN532_HSU::receive(uint8_t* buf, int len, uint16_t timeout) { 162 | int read_bytes = 0; 163 | int ret; 164 | unsigned long start_millis; 165 | 166 | while (read_bytes < len) { 167 | start_millis = millis(); 168 | do { 169 | ret = _serial->read(); 170 | if (ret >= 0) { 171 | break; 172 | } 173 | } while ((timeout == 0) || ((millis() - start_millis) < timeout)); 174 | 175 | if (ret < 0) { 176 | if (read_bytes) { 177 | return read_bytes; 178 | } else { 179 | return PN532_TIMEOUT; 180 | } 181 | buf[read_bytes] = (uint8_t)ret; 182 | DMSG_HEX(ret); 183 | read_bytes++; 184 | } 185 | buf[read_bytes] = (uint8_t)ret; 186 | DMSG_HEX(ret); 187 | read_bytes++; 188 | } 189 | return read_bytes; 190 | } 191 | -------------------------------------------------------------------------------- /src/PN532/PN532_HSU/PN532_HSU.h: -------------------------------------------------------------------------------- 1 | 2 | #ifndef __PN532_HSU_H__ 3 | #define __PN532_HSU_H__ 4 | 5 | #include "PN532/PN532/PN532Interface.h" 6 | #include "Arduino.h" 7 | 8 | #define PN532_HSU_DEBUG 9 | 10 | #define PN532_HSU_READ_TIMEOUT (1000) 11 | 12 | class PN532_HSU : public PN532Interface { 13 | public: 14 | PN532_HSU(HardwareSerial& serial); 15 | 16 | void begin(); 17 | void wakeup(); 18 | virtual int8_t writeCommand(const uint8_t* header, uint8_t hlen, const uint8_t* body = 0, uint8_t blen = 0); 19 | int16_t readResponse(uint8_t buf[], uint8_t len, uint16_t timeout); 20 | 21 | private: 22 | HardwareSerial* _serial; 23 | uint8_t command; 24 | 25 | int8_t readAckFrame(); 26 | 27 | int8_t receive(uint8_t* buf, int len, uint16_t timeout = PN532_HSU_READ_TIMEOUT); 28 | }; 29 | 30 | #endif 31 | -------------------------------------------------------------------------------- /src/PN532/PN532_I2C/PN532_I2C.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | @modified picospuch 3 | */ 4 | 5 | #include "PN532/PN532_I2C/PN532_I2C.h" 6 | #include "PN532/PN532/PN532_debug.h" 7 | #include "Arduino.h" 8 | 9 | #define PN532_I2C_ADDRESS (0x48 >> 1) 10 | 11 | PN532_I2C::PN532_I2C(TwoWire& wire) { 12 | _wire = &wire; 13 | command = 0; 14 | } 15 | 16 | void PN532_I2C::begin() { 17 | _wire->begin(); 18 | } 19 | 20 | void PN532_I2C::wakeup() { 21 | delay(500); // wait for all ready to manipulate pn532 22 | } 23 | 24 | int8_t PN532_I2C::writeCommand(const uint8_t* header, uint8_t hlen, const uint8_t* body, uint8_t blen) { 25 | command = header[0]; 26 | _wire->beginTransmission(PN532_I2C_ADDRESS); 27 | 28 | write(PN532_PREAMBLE); 29 | write(PN532_STARTCODE1); 30 | write(PN532_STARTCODE2); 31 | 32 | uint8_t length = hlen + blen + 1; // length of data field: TFI + DATA 33 | write(length); 34 | write(~length + 1); // checksum of length 35 | 36 | write(PN532_HOSTTOPN532); 37 | uint8_t sum = PN532_HOSTTOPN532; // sum of TFI + DATA 38 | 39 | DMSG("write: "); 40 | 41 | for (uint8_t i = 0; i < hlen; i++) { 42 | if (write(header[i])) { 43 | sum += header[i]; 44 | 45 | DMSG_HEX(header[i]); 46 | } else { 47 | DMSG("\nToo many data to send, I2C doesn't support such a big packet\n"); // I2C max packet: 32 bytes 48 | return PN532_INVALID_FRAME; 49 | } 50 | } 51 | 52 | for (uint8_t i = 0; i < blen; i++) { 53 | if (write(body[i])) { 54 | sum += body[i]; 55 | 56 | DMSG_HEX(body[i]); 57 | } else { 58 | DMSG("\nToo many data to send, I2C doesn't support such a big packet\n"); // I2C max packet: 32 bytes 59 | return PN532_INVALID_FRAME; 60 | } 61 | } 62 | 63 | uint8_t checksum = ~sum + 1; // checksum of TFI + DATA 64 | write(checksum); 65 | write(PN532_POSTAMBLE); 66 | 67 | _wire->endTransmission(); 68 | 69 | DMSG('\n'); 70 | 71 | return readAckFrame(); 72 | } 73 | 74 | int16_t PN532_I2C::getResponseLength(uint8_t buf[], uint8_t len, uint16_t timeout) { 75 | const uint8_t PN532_NACK[] = {0, 0, 0xFF, 0xFF, 0, 0}; 76 | uint16_t time = 0; 77 | 78 | do { 79 | if (_wire->requestFrom(PN532_I2C_ADDRESS, 6)) { 80 | if (read() & 1) { 81 | // check first byte --- status 82 | break; // PN532 is ready 83 | } 84 | } 85 | 86 | delay(1); 87 | time++; 88 | if ((0 != timeout) && (time > timeout)) { 89 | return -1; 90 | } 91 | } while (1); 92 | 93 | if (0x00 != read() || // PREAMBLE 94 | 0x00 != read() || // STARTCODE1 95 | 0xFF != read() // STARTCODE2 96 | ) { 97 | 98 | return PN532_INVALID_FRAME; 99 | } 100 | 101 | uint8_t length = read(); 102 | 103 | // request for last respond msg again 104 | _wire->beginTransmission(PN532_I2C_ADDRESS); 105 | for (uint16_t i = 0; i < sizeof(PN532_NACK); ++i) { 106 | write(PN532_NACK[i]); 107 | } 108 | _wire->endTransmission(); 109 | 110 | return length; 111 | } 112 | 113 | int16_t PN532_I2C::readResponse(uint8_t buf[], uint8_t len, uint16_t timeout) { 114 | uint16_t time = 0; 115 | uint8_t length; 116 | 117 | length = getResponseLength(buf, len, timeout); 118 | 119 | // [RDY] 00 00 FF LEN LCS (TFI PD0 ... PDn) DCS 00 120 | do { 121 | if (_wire->requestFrom(PN532_I2C_ADDRESS, 6 + length + 2)) { 122 | if (read() & 1) { 123 | // check first byte --- status 124 | break; // PN532 is ready 125 | } 126 | } 127 | 128 | delay(1); 129 | time++; 130 | if ((0 != timeout) && (time > timeout)) { 131 | return -1; 132 | } 133 | } while (1); 134 | 135 | if (0x00 != read() || // PREAMBLE 136 | 0x00 != read() || // STARTCODE1 137 | 0xFF != read() // STARTCODE2 138 | ) { 139 | 140 | return PN532_INVALID_FRAME; 141 | } 142 | 143 | length = read(); 144 | 145 | if (0 != (uint8_t)(length + read())) { 146 | // checksum of length 147 | return PN532_INVALID_FRAME; 148 | } 149 | 150 | uint8_t cmd = command + 1; // response command 151 | if (PN532_PN532TOHOST != read() || (cmd) != read()) { 152 | return PN532_INVALID_FRAME; 153 | } 154 | 155 | length -= 2; 156 | if (length > len) { 157 | return PN532_NO_SPACE; // not enough space 158 | } 159 | 160 | DMSG("read: "); 161 | DMSG_HEX(cmd); 162 | 163 | uint8_t sum = PN532_PN532TOHOST + cmd; 164 | for (uint8_t i = 0; i < length; i++) { 165 | buf[i] = read(); 166 | sum += buf[i]; 167 | 168 | DMSG_HEX(buf[i]); 169 | } 170 | DMSG('\n'); 171 | 172 | uint8_t checksum = read(); 173 | if (0 != (uint8_t)(sum + checksum)) { 174 | DMSG("checksum is not ok\n"); 175 | return PN532_INVALID_FRAME; 176 | } 177 | read(); // POSTAMBLE 178 | 179 | return length; 180 | } 181 | 182 | int8_t PN532_I2C::readAckFrame() { 183 | const uint8_t PN532_ACK[] = {0, 0, 0xFF, 0, 0xFF, 0}; 184 | uint8_t ackBuf[sizeof(PN532_ACK)]; 185 | 186 | DMSG("wait for ack at : "); 187 | DMSG(millis()); 188 | DMSG('\n'); 189 | 190 | uint16_t time = 0; 191 | do { 192 | if (_wire->requestFrom(PN532_I2C_ADDRESS, sizeof(PN532_ACK) + 1)) { 193 | if (read() & 1) { 194 | // check first byte --- status 195 | break; // PN532 is ready 196 | } 197 | } 198 | 199 | delay(1); 200 | time++; 201 | if (time > PN532_ACK_WAIT_TIME) { 202 | DMSG("Time out when waiting for ACK\n"); 203 | return PN532_TIMEOUT; 204 | } 205 | } while (1); 206 | 207 | DMSG("ready at : "); 208 | DMSG(millis()); 209 | DMSG('\n'); 210 | 211 | for (uint8_t i = 0; i < sizeof(PN532_ACK); i++) { 212 | ackBuf[i] = read(); 213 | } 214 | 215 | if (memcmp(ackBuf, PN532_ACK, sizeof(PN532_ACK))) { 216 | DMSG("Invalid ACK\n"); 217 | return PN532_INVALID_ACK; 218 | } 219 | 220 | return 0; 221 | } 222 | -------------------------------------------------------------------------------- /src/PN532/PN532_I2C/PN532_I2C.h: -------------------------------------------------------------------------------- 1 | /** 2 | @modified picospuch 3 | */ 4 | 5 | #ifndef __PN532_I2C_H__ 6 | #define __PN532_I2C_H__ 7 | 8 | #include 9 | #include "PN532/PN532/PN532Interface.h" 10 | 11 | class PN532_I2C : public PN532Interface { 12 | public: 13 | PN532_I2C(TwoWire& wire); 14 | 15 | void begin(); 16 | void wakeup(); 17 | virtual int8_t writeCommand(const uint8_t* header, uint8_t hlen, const uint8_t* body = 0, uint8_t blen = 0); 18 | int16_t readResponse(uint8_t buf[], uint8_t len, uint16_t timeout); 19 | 20 | private: 21 | TwoWire* _wire; 22 | uint8_t command; 23 | 24 | int8_t readAckFrame(); 25 | int16_t getResponseLength(uint8_t buf[], uint8_t len, uint16_t timeout); 26 | 27 | inline uint8_t write(uint8_t data) { 28 | #if ARDUINO >= 100 29 | return _wire->write(data); 30 | #else 31 | return _wire->send(data); 32 | #endif 33 | } 34 | 35 | inline uint8_t read() { 36 | #if ARDUINO >= 100 37 | return _wire->read(); 38 | #else 39 | return _wire->receive(); 40 | #endif 41 | } 42 | }; 43 | 44 | #endif 45 | -------------------------------------------------------------------------------- /src/PN532/PN532_SPI/PN532_SPI.cpp: -------------------------------------------------------------------------------- 1 | 2 | #ifndef ARDUINO_XIAO_RA4M1 3 | 4 | #include "PN532/PN532_SPI/PN532_SPI.h" 5 | #include "PN532/PN532/PN532_debug.h" 6 | #include "Arduino.h" 7 | 8 | #define STATUS_READ 2 9 | #define DATA_WRITE 1 10 | #define DATA_READ 3 11 | 12 | #if defined(ARDUINO_ARCH_RP2040) 13 | PN532_SPI::PN532_SPI(SPIClassRP2040& spi, uint8_t ss) { 14 | command = 0; 15 | _spi = &spi; 16 | _ss = ss; 17 | } 18 | #else 19 | PN532_SPI::PN532_SPI(SPIClass& spi, uint8_t ss) { 20 | command = 0; 21 | _spi = &spi; 22 | _ss = ss; 23 | } 24 | #endif 25 | 26 | void PN532_SPI::begin() { 27 | pinMode(_ss, OUTPUT); 28 | 29 | _spi->begin(); 30 | #if defined(ARDUINO_ARCH_RP2040) 31 | _spi->setDataMode(SPI_MODE0); // PN532 only supports mode0 32 | _spi->setBitOrder(LSBFIRST); 33 | #else 34 | _spi->setDataMode(SPI_MODE0); // PN532 only supports mode0 35 | _spi->setBitOrder(LSBFIRST); 36 | #if defined __SAM3X8E__ 37 | /** DUE spi library does not support SPI_CLOCK_DIV8 macro */ 38 | _spi->setClockDivider(42); // set clock 2MHz(max: 5MHz) 39 | #elif defined __SAMD21G18A__ 40 | /** M0 spi library does not support SPI_CLOCK_DIV8 macro */ 41 | _spi->setClockDivider(24); // set clock 2MHz(max: 5MHz) 42 | #else 43 | _spi->setClockDivider(SPI_CLOCK_DIV8); // set clock 2MHz(max: 5MHz) 44 | #endif 45 | #endif 46 | } 47 | 48 | void PN532_SPI::wakeup() { 49 | digitalWrite(_ss, LOW); 50 | delay(2); 51 | digitalWrite(_ss, HIGH); 52 | } 53 | 54 | int8_t PN532_SPI::writeCommand(const uint8_t* header, uint8_t hlen, const uint8_t* body, uint8_t blen) { 55 | command = header[0]; 56 | writeFrame(header, hlen, body, blen); 57 | 58 | uint8_t timeout = PN532_ACK_WAIT_TIME; 59 | while (!isReady()) { 60 | delay(1); 61 | timeout--; 62 | if (0 == timeout) { 63 | DMSG("Time out when waiting for ACK\n"); 64 | return -2; 65 | } 66 | } 67 | if (readAckFrame()) { 68 | DMSG("Invalid ACK\n"); 69 | return PN532_INVALID_ACK; 70 | } 71 | return 0; 72 | } 73 | 74 | int16_t PN532_SPI::readResponse(uint8_t buf[], uint8_t len, uint16_t timeout) { 75 | uint16_t time = 0; 76 | while (!isReady()) { 77 | delay(1); 78 | time++; 79 | if (time > timeout) { 80 | return PN532_TIMEOUT; 81 | } 82 | } 83 | 84 | digitalWrite(_ss, LOW); 85 | delay(1); 86 | 87 | int16_t result; 88 | do { 89 | write(DATA_READ); 90 | 91 | if (0x00 != read() || // PREAMBLE 92 | 0x00 != read() || // STARTCODE1 93 | 0xFF != read() // STARTCODE2 94 | ) { 95 | 96 | result = PN532_INVALID_FRAME; 97 | break; 98 | } 99 | 100 | uint8_t length = read(); 101 | if (0 != (uint8_t)(length + read())) { 102 | // checksum of length 103 | result = PN532_INVALID_FRAME; 104 | break; 105 | } 106 | 107 | uint8_t cmd = command + 1; // response command 108 | if (PN532_PN532TOHOST != read() || (cmd) != read()) { 109 | result = PN532_INVALID_FRAME; 110 | break; 111 | } 112 | 113 | DMSG("read: "); 114 | DMSG_HEX(cmd); 115 | 116 | length -= 2; 117 | if (length > len) { 118 | for (uint8_t i = 0; i < length; i++) { 119 | DMSG_HEX(read()); // dump message 120 | } 121 | DMSG("\nNot enough space\n"); 122 | read(); 123 | read(); 124 | result = PN532_NO_SPACE; // not enough space 125 | break; 126 | } 127 | 128 | uint8_t sum = PN532_PN532TOHOST + cmd; 129 | for (uint8_t i = 0; i < length; i++) { 130 | buf[i] = read(); 131 | sum += buf[i]; 132 | 133 | DMSG_HEX(buf[i]); 134 | } 135 | DMSG('\n'); 136 | 137 | uint8_t checksum = read(); 138 | if (0 != (uint8_t)(sum + checksum)) { 139 | DMSG("checksum is not ok\n"); 140 | result = PN532_INVALID_FRAME; 141 | break; 142 | } 143 | read(); // POSTAMBLE 144 | 145 | result = length; 146 | } while (0); 147 | 148 | digitalWrite(_ss, HIGH); 149 | 150 | return result; 151 | } 152 | 153 | bool PN532_SPI::isReady() { 154 | digitalWrite(_ss, LOW); 155 | 156 | write(STATUS_READ); 157 | uint8_t status = read() & 1; 158 | digitalWrite(_ss, HIGH); 159 | return status; 160 | } 161 | 162 | void PN532_SPI::writeFrame(const uint8_t* header, uint8_t hlen, const uint8_t* body, uint8_t blen) { 163 | digitalWrite(_ss, LOW); 164 | delay(2); // wake up PN532 165 | 166 | write(DATA_WRITE); 167 | write(PN532_PREAMBLE); 168 | write(PN532_STARTCODE1); 169 | write(PN532_STARTCODE2); 170 | 171 | uint8_t length = hlen + blen + 1; // length of data field: TFI + DATA 172 | write(length); 173 | write(~length + 1); // checksum of length 174 | 175 | write(PN532_HOSTTOPN532); 176 | uint8_t sum = PN532_HOSTTOPN532; // sum of TFI + DATA 177 | 178 | DMSG("write: "); 179 | 180 | for (uint8_t i = 0; i < hlen; i++) { 181 | write(header[i]); 182 | sum += header[i]; 183 | 184 | DMSG_HEX(header[i]); 185 | } 186 | for (uint8_t i = 0; i < blen; i++) { 187 | write(body[i]); 188 | sum += body[i]; 189 | 190 | DMSG_HEX(body[i]); 191 | } 192 | 193 | uint8_t checksum = ~sum + 1; // checksum of TFI + DATA 194 | write(checksum); 195 | write(PN532_POSTAMBLE); 196 | 197 | digitalWrite(_ss, HIGH); 198 | 199 | DMSG('\n'); 200 | } 201 | 202 | int8_t PN532_SPI::readAckFrame() { 203 | const uint8_t PN532_ACK[] = {0, 0, 0xFF, 0, 0xFF, 0}; 204 | 205 | uint8_t ackBuf[sizeof(PN532_ACK)]; 206 | 207 | digitalWrite(_ss, LOW); 208 | delay(1); 209 | write(DATA_READ); 210 | 211 | for (uint8_t i = 0; i < sizeof(PN532_ACK); i++) { 212 | ackBuf[i] = read(); 213 | } 214 | 215 | digitalWrite(_ss, HIGH); 216 | 217 | return memcmp(ackBuf, PN532_ACK, sizeof(PN532_ACK)); 218 | } 219 | 220 | #endif -------------------------------------------------------------------------------- /src/PN532/PN532_SPI/PN532_SPI.h: -------------------------------------------------------------------------------- 1 | 2 | #ifndef __PN532_SPI_H__ 3 | #define __PN532_SPI_H__ 4 | 5 | #ifndef ARDUINO_XIAO_RA4M1 6 | 7 | #include 8 | #include "PN532/PN532/PN532Interface.h" 9 | 10 | class PN532_SPI : public PN532Interface { 11 | public: 12 | #if defined(ARDUINO_ARCH_RP2040) 13 | PN532_SPI(SPIClassRP2040& spi, uint8_t ss); 14 | #else 15 | PN532_SPI(SPIClass& spi, uint8_t ss); 16 | #endif 17 | 18 | void begin(); 19 | void wakeup(); 20 | int8_t writeCommand(const uint8_t* header, uint8_t hlen, const uint8_t* body = 0, uint8_t blen = 0); 21 | 22 | int16_t readResponse(uint8_t buf[], uint8_t len, uint16_t timeout); 23 | 24 | private: 25 | #if defined(ARDUINO_ARCH_RP2040) 26 | SPIClassRP2040* _spi; 27 | #else 28 | SPIClass* _spi; 29 | #endif 30 | uint8_t _ss; 31 | uint8_t command; 32 | 33 | bool isReady(); 34 | void writeFrame(const uint8_t* header, uint8_t hlen, const uint8_t* body = 0, uint8_t blen = 0); 35 | int8_t readAckFrame(); 36 | 37 | inline void write(uint8_t data) { 38 | _spi->transfer(data); 39 | }; 40 | 41 | inline uint8_t read() { 42 | return _spi->transfer(0); 43 | }; 44 | }; 45 | #endif 46 | 47 | #endif 48 | -------------------------------------------------------------------------------- /src/PN532/PN532_SWHSU/PN532_SWHSU.cpp: -------------------------------------------------------------------------------- 1 | #ifndef ESP32 2 | 3 | 4 | #include "PN532/PN532_SWHSU/PN532_SWHSU.h" 5 | #include "PN532/PN532/PN532_debug.h" 6 | 7 | 8 | PN532_SWHSU::PN532_SWHSU(SoftwareSerial& serial) { 9 | _serial = &serial; 10 | command = 0; 11 | } 12 | 13 | void PN532_SWHSU::begin() { 14 | _serial->begin(115200); 15 | } 16 | 17 | void PN532_SWHSU::wakeup() { 18 | _serial->write(0x55); 19 | _serial->write(0x55); 20 | _serial->write((uint8_t) 0); 21 | _serial->write((uint8_t) 0); 22 | _serial->write((uint8_t) 0); 23 | 24 | /** dump serial buffer */ 25 | if (_serial->available()) { 26 | DMSG("Dump serial buffer: "); 27 | } 28 | while (_serial->available()) { 29 | uint8_t ret = _serial->read(); 30 | DMSG_HEX(ret); 31 | } 32 | 33 | } 34 | 35 | int8_t PN532_SWHSU::writeCommand(const uint8_t* header, uint8_t hlen, const uint8_t* body, uint8_t blen) { 36 | 37 | /** dump serial buffer */ 38 | if (_serial->available()) { 39 | DMSG("Dump serial buffer: "); 40 | } 41 | while (_serial->available()) { 42 | uint8_t ret = _serial->read(); 43 | DMSG_HEX(ret); 44 | } 45 | 46 | command = header[0]; 47 | 48 | _serial->write((uint8_t) PN532_PREAMBLE); 49 | _serial->write((uint8_t) PN532_STARTCODE1); 50 | _serial->write((uint8_t) PN532_STARTCODE2); 51 | 52 | uint8_t length = hlen + blen + 1; // length of data field: TFI + DATA 53 | _serial->write(length); 54 | _serial->write(~length + 1); // checksum of length 55 | 56 | _serial->write((uint8_t) PN532_HOSTTOPN532); 57 | uint8_t sum = PN532_HOSTTOPN532; // sum of TFI + DATA 58 | 59 | DMSG("\nWrite: "); 60 | 61 | _serial->write(header, hlen); 62 | for (uint8_t i = 0; i < hlen; i++) { 63 | sum += header[i]; 64 | 65 | DMSG_HEX(header[i]); 66 | } 67 | 68 | _serial->write(body, blen); 69 | for (uint8_t i = 0; i < blen; i++) { 70 | sum += body[i]; 71 | 72 | DMSG_HEX(body[i]); 73 | } 74 | 75 | uint8_t checksum = ~sum + 1; // checksum of TFI + DATA 76 | _serial->write(checksum); 77 | _serial->write((uint8_t) PN532_POSTAMBLE); 78 | 79 | return readAckFrame(); 80 | } 81 | 82 | int16_t PN532_SWHSU::readResponse(uint8_t buf[], uint8_t len, uint16_t timeout) { 83 | uint8_t tmp[3]; 84 | 85 | DMSG("\nRead: "); 86 | 87 | /** Frame Preamble and Start Code */ 88 | if (receive(tmp, 3, timeout) <= 0) { 89 | return PN532_TIMEOUT; 90 | } 91 | if (0 != tmp[0] || 0 != tmp[1] || 0xFF != tmp[2]) { 92 | DMSG("Preamble error"); 93 | return PN532_INVALID_FRAME; 94 | } 95 | 96 | /** receive length and check */ 97 | uint8_t length[2]; 98 | if (receive(length, 2, timeout) <= 0) { 99 | return PN532_TIMEOUT; 100 | } 101 | if (0 != (uint8_t)(length[0] + length[1])) { 102 | DMSG("Length error"); 103 | return PN532_INVALID_FRAME; 104 | } 105 | length[0] -= 2; 106 | if (length[0] > len) { 107 | return PN532_NO_SPACE; 108 | } 109 | 110 | /** receive command byte */ 111 | uint8_t cmd = command + 1; // response command 112 | if (receive(tmp, 2, timeout) <= 0) { 113 | return PN532_TIMEOUT; 114 | } 115 | if (PN532_PN532TOHOST != tmp[0] || cmd != tmp[1]) { 116 | DMSG("Command error"); 117 | return PN532_INVALID_FRAME; 118 | } 119 | 120 | if (receive(buf, length[0], timeout) != length[0]) { 121 | return PN532_TIMEOUT; 122 | } 123 | uint8_t sum = PN532_PN532TOHOST + cmd; 124 | for (uint8_t i = 0; i < length[0]; i++) { 125 | sum += buf[i]; 126 | } 127 | 128 | /** checksum and postamble */ 129 | if (receive(tmp, 2, timeout) <= 0) { 130 | return PN532_TIMEOUT; 131 | } 132 | if (0 != (uint8_t)(sum + tmp[0]) || 0 != tmp[1]) { 133 | DMSG("Checksum error"); 134 | return PN532_INVALID_FRAME; 135 | } 136 | 137 | return length[0]; 138 | } 139 | 140 | int8_t PN532_SWHSU::readAckFrame() { 141 | const uint8_t PN532_ACK[] = {0, 0, 0xFF, 0, 0xFF, 0}; 142 | uint8_t ackBuf[sizeof(PN532_ACK)]; 143 | 144 | DMSG("\nAck: "); 145 | 146 | if (receive(ackBuf, sizeof(PN532_ACK), PN532_ACK_WAIT_TIME) <= 0) { 147 | DMSG("Timeout\n"); 148 | return PN532_TIMEOUT; 149 | } 150 | 151 | if (memcmp(ackBuf, PN532_ACK, sizeof(PN532_ACK))) { 152 | DMSG("Invalid\n"); 153 | return PN532_INVALID_ACK; 154 | } 155 | return 0; 156 | } 157 | 158 | /** 159 | @brief receive data . 160 | @param buf --> return value buffer. 161 | len --> length expect to receive. 162 | timeout --> time of reveiving 163 | @retval number of received bytes, 0 means no data received. 164 | */ 165 | int8_t PN532_SWHSU::receive(uint8_t* buf, int len, uint16_t timeout) { 166 | int read_bytes = 0; 167 | int ret; 168 | unsigned long start_millis; 169 | 170 | while (read_bytes < len) { 171 | start_millis = millis(); 172 | do { 173 | ret = _serial->read(); 174 | if (ret >= 0) { 175 | break; 176 | } 177 | } while ((timeout == 0) || ((millis() - start_millis) < timeout)); 178 | 179 | if (ret < 0) { 180 | if (read_bytes) { 181 | return read_bytes; 182 | } else { 183 | return PN532_TIMEOUT; 184 | } 185 | } 186 | buf[read_bytes] = (uint8_t)ret; 187 | DMSG_HEX(ret); 188 | read_bytes++; 189 | } 190 | return read_bytes; 191 | } 192 | 193 | 194 | #endif -------------------------------------------------------------------------------- /src/PN532/PN532_SWHSU/PN532_SWHSU.h: -------------------------------------------------------------------------------- 1 | 2 | #ifndef __PN532_SWHSU_H__ 3 | #define __PN532_SWHSU_H__ 4 | #ifndef ESP32 5 | 6 | #include 7 | 8 | #include "PN532/PN532/PN532Interface.h" 9 | #include "Arduino.h" 10 | 11 | #define PN532_SWHSU_DEBUG 12 | 13 | #define PN532_SWHSU_READ_TIMEOUT (1000) 14 | 15 | class PN532_SWHSU : public PN532Interface { 16 | public: 17 | PN532_SWHSU(SoftwareSerial& serial); 18 | 19 | void begin(); 20 | void wakeup(); 21 | virtual int8_t writeCommand(const uint8_t* header, uint8_t hlen, const uint8_t* body = 0, uint8_t blen = 0); 22 | int16_t readResponse(uint8_t buf[], uint8_t len, uint16_t timeout); 23 | 24 | private: 25 | SoftwareSerial* _serial; 26 | uint8_t command; 27 | 28 | int8_t readAckFrame(); 29 | 30 | int8_t receive(uint8_t* buf, int len, uint16_t timeout = PN532_SWHSU_READ_TIMEOUT); 31 | }; 32 | 33 | #endif 34 | #endif 35 | -------------------------------------------------------------------------------- /src/PN532/README.md: -------------------------------------------------------------------------------- 1 | ## NFC library for Arduino 2 | 3 | This is an Arduino library for PN532 to use NFC technology. 4 | It is for [NFC Shield](http://goo.gl/Cac2OH) and [Grove - NFC](http://goo.gl/L3Uw5G). 5 | 6 | [![NFC Shield](https://statics3.seeedstudio.com/images/113030001%201.jpg)](http://goo.gl/Cac2OH) 7 | [![Grove - NFC](https://statics3.seeedstudio.com/images/product/grove%20nfc.jpg)](http://goo.gl/L3Uw5G) 8 | 9 | ### Features 10 | + Support all interfaces of PN532 (I2C, SPI, HSU ) 11 | + Read/write Mifare Classic Card 12 | + Works with [Don's NDEF Library](http://goo.gl/jDjsXl) 13 | + Communicate with android 4.0+([Lists of devices supported](https://github.com/Seeed-Studio/PN532/wiki/List-of-devices-supported)) 14 | + Support [mbed platform](http://goo.gl/kGPovZ) 15 | + Card emulation (NFC Type 4 tag) 16 | 17 | ### To Do 18 | + To support more than one INFO PDU of P2P communication 19 | + To read/write NFC Type 4 tag 20 | 21 | ### Getting Started 22 | + Easy way 23 | 24 | 1. Download [zip file](http://goo.gl/F6beRM) and extract the 4 folders(PN532, PN532_SPI, PN532_I2C and PN532_HSU) into Arduino's libraries. 25 | 2. Download [Don's NDEF library](http://goo.gl/ewxeAe), extract it into Arduino's libraries and rename it to NDEF. 26 | 3. Follow the examples of the two libraries. 27 | 28 | + Git way for Linux/Mac (recommended) 29 | 30 | 1. Get PN532 library and NDEF library 31 | 32 | cd {Arduino}\libraries 33 | git clone --recursive https://github.com/Seeed-Studio/PN532.git NFC 34 | ln -s NFC/PN532 ./ 35 | ln -s NFC/PN532_SPI ./ 36 | ln -s NFC/PN532_I2C ./ 37 | ln -s NFC/PN532_HSU ./ 38 | ln -s NFC/NDEF ./ 39 | 40 | 2. Follow the examples of the two libraries 41 | 42 | ### Contribution 43 | It's based on [Adafruit_NFCShield_I2C](http://goo.gl/pk3FdB). 44 | [Seeed Studio](http://goo.gl/zh1iQh) rewrite the library to make it easy to support different interfaces and platforms. 45 | @Don writes the [NDEF library](http://goo.gl/jDjsXl) to make it more easy to use. 46 | @JiapengLi adds HSU interface. 47 | @awieser adds card emulation function. 48 | 49 | ## HSU Interface 50 | 51 | HSU is short for High Speed Uart. HSU interface needs only 4 wires to connect PN532 with Arduino, [Sensor Shield](http://goo.gl/i0EQgd) can make it more easier. For some Arduino boards like [Leonardo][Leonardo], [DUE][DUE], [Mega][Mega] ect, there are more than one `Serial` on these boards, so we can use this additional Serial to control PN532, HSU uses 115200 baud rate . 52 | 53 | To use the `Serial1` control PN532, refer to the code below. 54 | ```c++ 55 | #include 56 | #include 57 | 58 | PN532_HSU pn532hsu(Serial1); 59 | PN532 nfc(pn532hsu); 60 | 61 | void setup(void) 62 | { 63 | nfc.begin(); 64 | //... 65 | } 66 | ``` 67 | If your Arduino has only one serial interface and you want to keep it for control or debugging with the Serial Monitor, you can use the [`SoftwareSerial`][SoftwareSerial] library to control the PN532 by emulating a serial interface. Include `PN532_SWHSU.h` instead of `PN532_HSU.h`: 68 | ```c++ 69 | #include 70 | #include 71 | #include 72 | 73 | SoftwareSerial SWSerial( 10, 11 ); // RX, TX 74 | 75 | PN532_SWHSU pn532swhsu( SWSerial ); 76 | PN532 nfc( pn532swhsu ); 77 | 78 | void setup(void) 79 | { 80 | nfc.begin(); 81 | //... 82 | } 83 | ``` 84 | [Mega]: http://arduino.cc/en/Main/arduinoBoardMega 85 | [DUE]: http://arduino.cc/en/Main/arduinoBoardDue 86 | [Leonardo]: http://arduino.cc/en/Main/arduinoBoardLeonardo 87 | [SoftwareSerial]: https://www.arduino.cc/en/Reference/softwareSerial 88 | 89 | -------------------------------------------------------------------------------- /tests/NdefMemoryTest/NdefMemoryTest.ino: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | void leakCheck(void (*callback)()) { 8 | int start = freeMemory(); 9 | (*callback)(); 10 | int end = freeMemory(); 11 | Serial.println((end - start), DEC); 12 | } 13 | 14 | // Custom Assertion 15 | void assertNoLeak(void (*callback)()) { 16 | int start = freeMemory(); 17 | (*callback)(); 18 | int end = freeMemory(); 19 | assertEqual(0, (start - end)); 20 | } 21 | 22 | void record() { 23 | NdefRecord* r = new NdefRecord(); 24 | delete r; 25 | } 26 | 27 | void emptyRecord() { 28 | NdefRecord* r = new NdefRecord(); 29 | #ifdef NDEF_USE_SERIAL 30 | r->print(); 31 | #endif 32 | delete r; 33 | } 34 | 35 | void textRecord() { 36 | NdefRecord* r = new NdefRecord(); 37 | r->setTnf(0x1); 38 | uint8_t type[] = { 0x54 }; 39 | r->setType(type, sizeof(type)); 40 | uint8_t payload[] = { 0x1A, 0x1B, 0x1C }; 41 | r->setPayload(payload, sizeof(payload)); 42 | #ifdef NDEF_USE_SERIAL 43 | r->print(); 44 | #endif 45 | delete r; 46 | } 47 | 48 | void recordMallocZero() { 49 | NdefRecord r = NdefRecord(); 50 | String type = r.getType(); 51 | String id = r.getId(); 52 | byte payload[r.getPayloadLength()]; 53 | r.getPayload(payload); 54 | } 55 | 56 | // this is OK 57 | void emptyMessage() { 58 | NdefMessage* m = new NdefMessage(); 59 | delete m; 60 | } 61 | 62 | // this is OK 63 | void printEmptyMessage() { 64 | NdefMessage* m = new NdefMessage(); 65 | #ifdef NDEF_USE_SERIAL 66 | m->print(); 67 | #endif 68 | delete m; 69 | } 70 | 71 | // this is OK 72 | void printEmptyMessageNoNew() { 73 | NdefMessage m = NdefMessage(); 74 | #ifdef NDEF_USE_SERIAL 75 | m.print(); 76 | #endif 77 | } 78 | 79 | void messageWithTextRecord() { 80 | NdefMessage m = NdefMessage(); 81 | m.addTextRecord("foo"); 82 | #ifdef NDEF_USE_SERIAL 83 | m.print(); 84 | #endif 85 | } 86 | 87 | void messageWithEmptyRecord() { 88 | NdefMessage m = NdefMessage(); 89 | NdefRecord r = NdefRecord(); 90 | m.addRecord(r); 91 | #ifdef NDEF_USE_SERIAL 92 | m.print(); 93 | #endif 94 | } 95 | 96 | void messageWithoutHelper() { 97 | NdefMessage m = NdefMessage(); 98 | NdefRecord r = NdefRecord(); 99 | r.setTnf(1); 100 | uint8_t type[] = { 0x54 }; 101 | r.setType(type, sizeof(type)); 102 | uint8_t payload[] = { 0x02, 0x65, 0x6E, 0x66, 0x6F, 0x6F }; 103 | r.setPayload(payload, sizeof(payload)); 104 | m.addRecord(r); 105 | #ifdef NDEF_USE_SERIAL 106 | m.print(); 107 | #endif 108 | } 109 | 110 | void messageWithId() { 111 | NdefMessage m = NdefMessage(); 112 | NdefRecord r = NdefRecord(); 113 | r.setTnf(1); 114 | uint8_t type[] = { 0x54 }; 115 | r.setType(type, sizeof(type)); 116 | uint8_t payload[] = { 0x02, 0x65, 0x6E, 0x66, 0x6F, 0x6F }; 117 | r.setPayload(payload, sizeof(payload)); 118 | uint8_t id[] = { 0x0, 0x0, 0x0 }; 119 | r.setId(id, sizeof(id)); 120 | m.addRecord(r); 121 | #ifdef NDEF_USE_SERIAL 122 | m.print(); 123 | #endif 124 | } 125 | 126 | void message80() { 127 | NdefMessage message = NdefMessage(); 128 | message.addTextRecord("This record is 80 characters.X01234567890123456789012345678901234567890123456789"); 129 | #ifdef NDEF_USE_SERIAL 130 | //message.print(); 131 | #endif 132 | } 133 | 134 | void message100() { 135 | NdefMessage message = NdefMessage(); 136 | message.addTextRecord("This record is 100 characters.0123456789012345678901234567890123456789012345678901234567890123456789"); 137 | #ifdef NDEF_USE_SERIAL 138 | //message.print(); 139 | #endif 140 | } 141 | 142 | void message120() { 143 | NdefMessage message = NdefMessage(); 144 | message.addTextRecord("This record is 120 characters.012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789"); 145 | #ifdef NDEF_USE_SERIAL 146 | //message.print(); 147 | #endif 148 | } 149 | 150 | void setup() { 151 | Serial.begin(9600); 152 | Serial.println("\n"); 153 | Serial.println(F("=========")); 154 | Serial.println(freeMemory()); 155 | Serial.println(F("=========")); 156 | } 157 | 158 | test(memoryKludgeEnd) { 159 | // TODO ensure the output matches start 160 | Serial.println(F("=========")); 161 | Serial.print("End "); Serial.println(freeMemory()); 162 | Serial.println(F("=========")); 163 | } 164 | 165 | test(recordLeaks) { 166 | assertNoLeak(&record); 167 | assertNoLeak(&emptyRecord); 168 | assertNoLeak(&textRecord); 169 | } 170 | 171 | test(recordAccessorLeaks) { 172 | assertNoLeak(&recordMallocZero); 173 | } 174 | 175 | test(messageLeaks) { 176 | assertNoLeak(&emptyMessage); 177 | assertNoLeak(&printEmptyMessage); 178 | assertNoLeak(&printEmptyMessageNoNew); 179 | assertNoLeak(&messageWithTextRecord); 180 | assertNoLeak(&messageWithEmptyRecord); 181 | assertNoLeak(&messageWithoutHelper); 182 | assertNoLeak(&messageWithId); 183 | } 184 | 185 | test(messageOneBigRecord) { 186 | assertNoLeak(&message80); 187 | // The next 2 fail. Maybe out of memory? Look into helper methods 188 | //assertNoLeak(&message100); 189 | //assertNoLeak(&message120); 190 | } 191 | 192 | test(memoryKludgeStart) { 193 | Serial.println(F("---------")); 194 | Serial.print("Start "); Serial.println(freeMemory()); 195 | Serial.println(F("---------")); 196 | } 197 | 198 | void loop() { 199 | Test::run(); 200 | } 201 | -------------------------------------------------------------------------------- /tests/NdefMessageTest/NdefMessageTest.ino: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | // Custom Assertion 8 | void assertNoLeak(void (*callback)()) { 9 | int start = freeMemory(); 10 | (*callback)(); 11 | int end = freeMemory(); 12 | assertEqual(0, (start - end)); 13 | } 14 | 15 | void assertBytesEqual(const uint8_t* expected, const uint8_t* actual, int size) { 16 | for (int i = 0; i < size; i++) { 17 | // Serial.print("> ");Serial.print(expected[i]);Serial.print(" ");Serial.println(actual[i]); 18 | assertEqual(expected[i], actual[i]); 19 | } 20 | } 21 | 22 | void setup() { 23 | Serial.begin(9600); 24 | } 25 | 26 | test(messageDelete) { 27 | int start = freeMemory(); 28 | 29 | NdefMessage* m1 = new NdefMessage(); 30 | m1->addTextRecord("Foo"); 31 | delete m1; 32 | 33 | int end = freeMemory(); 34 | // Serial.print("Start ");Serial.println(start); 35 | // Serial.print("End ");Serial.println(end); 36 | assertEqual(0, (start - end)); 37 | } 38 | 39 | 40 | test(assign) { 41 | int start = freeMemory(); 42 | 43 | if (true) { // bogus block so automatic storage duration objects are deleted 44 | NdefMessage* m1 = new NdefMessage(); 45 | m1->addTextRecord("We the People of the United States, in Order to form a more perfect Union..."); 46 | 47 | NdefMessage* m2 = new NdefMessage(); 48 | 49 | *m2 = *m1; 50 | 51 | NdefRecord r1 = m1->getRecord(0); 52 | NdefRecord r2 = m2->getRecord(0); 53 | 54 | assertEqual(r1.getTnf(), r2.getTnf()); 55 | assertEqual(r1.getTypeLength(), r2.getTypeLength()); 56 | assertEqual(r1.getPayloadLength(), r2.getPayloadLength()); 57 | assertEqual(r1.getIdLength(), r2.getIdLength()); 58 | 59 | byte p1[r1.getPayloadLength()]; 60 | byte p2[r2.getPayloadLength()]; 61 | r1.getPayload(p1); 62 | r2.getPayload(p2); 63 | 64 | int size = r1.getPayloadLength(); 65 | assertBytesEqual(p1, p2, size); 66 | 67 | delete m2; 68 | delete m1; 69 | } 70 | 71 | int end = freeMemory(); 72 | assertEqual(0, (start - end)); 73 | } 74 | 75 | test(assign2) { 76 | int start = freeMemory(); 77 | 78 | if (true) { // bogus block so automatic storage duration objects are deleted 79 | NdefMessage m1 = NdefMessage(); 80 | m1.addTextRecord("We the People of the United States, in Order to form a more perfect Union..."); 81 | 82 | NdefMessage m2 = NdefMessage(); 83 | 84 | m2 = m1; 85 | 86 | NdefRecord r1 = m1.getRecord(0); 87 | NdefRecord r2 = m2.getRecord(0); 88 | 89 | assertEqual(r1.getTnf(), r2.getTnf()); 90 | assertEqual(r1.getTypeLength(), r2.getTypeLength()); 91 | assertEqual(r1.getPayloadLength(), r2.getPayloadLength()); 92 | assertEqual(r1.getIdLength(), r2.getIdLength()); 93 | 94 | // TODO check type 95 | 96 | byte p1[r1.getPayloadLength()]; 97 | byte p2[r2.getPayloadLength()]; 98 | r1.getPayload(p1); 99 | r2.getPayload(p2); 100 | 101 | int size = r1.getPayloadLength(); 102 | assertBytesEqual(p1, p2, size); 103 | } 104 | 105 | int end = freeMemory(); 106 | assertEqual(0, (start - end)); 107 | } 108 | 109 | test(assign3) { 110 | int start = freeMemory(); 111 | 112 | if (true) { // bogus block so automatic storage duration objects are deleted 113 | 114 | NdefMessage* m1 = new NdefMessage(); 115 | m1->addTextRecord("We the People of the United States, in Order to form a more perfect Union..."); 116 | 117 | NdefMessage* m2 = new NdefMessage(); 118 | 119 | *m2 = *m1; 120 | 121 | delete m1; 122 | 123 | NdefRecord r = m2->getRecord(0); 124 | 125 | assertEqual(TNF_WELL_KNOWN, r.getTnf()); 126 | assertEqual(1, r.getTypeLength()); 127 | assertEqual(79, r.getPayloadLength()); 128 | assertEqual(0, r.getIdLength()); 129 | 130 | ::String s = "We the People of the United States, in Order to form a more perfect Union..."; 131 | byte payload[s.length() + 1]; 132 | s.getBytes(payload, sizeof(payload)); 133 | 134 | byte p[r.getPayloadLength()]; 135 | r.getPayload(p); 136 | assertBytesEqual(payload, p + 3, s.length()); 137 | 138 | delete m2; 139 | } 140 | 141 | int end = freeMemory(); 142 | assertEqual(0, (start - end)); 143 | } 144 | 145 | test(assign4) { 146 | int start = freeMemory(); 147 | 148 | if (true) { // bogus block so automatic storage duration objects are deleted 149 | 150 | NdefMessage* m1 = new NdefMessage(); 151 | m1->addTextRecord("We the People of the United States, in Order to form a more perfect Union..."); 152 | 153 | NdefMessage* m2 = new NdefMessage(); 154 | m2->addTextRecord("Record 1"); 155 | m2->addTextRecord("RECORD 2"); 156 | m2->addTextRecord("Record 3"); 157 | 158 | assertEqual(3, m2->getRecordCount()); 159 | *m2 = *m1; 160 | assertEqual(1, m2->getRecordCount()); 161 | 162 | // NdefRecord ghost = m2->getRecord(1); 163 | // ghost.print(); 164 | // 165 | // NdefRecord ghost2 = m2->getRecord(3); 166 | // ghost2.print(); 167 | 168 | // 169 | // delete m1; 170 | // 171 | // NdefRecord r = m2->getRecord(0); 172 | // 173 | // assertEqual(TNF_WELL_KNOWN, r.getTnf()); 174 | // assertEqual(1, r.getTypeLength()); 175 | // assertEqual(79, r.getPayloadLength()); 176 | // assertEqual(0, r.getIdLength()); 177 | // 178 | // String s = "We the People of the United States, in Order to form a more perfect Union..."; 179 | // byte payload[s.length() + 1]; 180 | // s.getBytes(payload, sizeof(payload)); 181 | // 182 | // uint8_t* p = r.getPayload(); 183 | // int size = r.getPayloadLength(); 184 | // assertBytesEqual(payload, p+3, s.length()); 185 | // free(p); 186 | 187 | delete m1; 188 | delete m2; 189 | } 190 | 191 | int end = freeMemory(); 192 | assertEqual(0, (start - end)); 193 | } 194 | 195 | // really a record test 196 | test(doublePayload) { 197 | int start = freeMemory(); 198 | 199 | NdefRecord* r = new NdefRecord(); 200 | uint8_t p1[] = { 0x1, 0x2, 0x3, 0x4, 0x5, 0x6 }; 201 | r->setPayload(p1, sizeof(p1)); 202 | r->setPayload(p1, sizeof(p1)); 203 | 204 | delete r; 205 | 206 | int end = freeMemory(); 207 | assertEqual(0, (start - end)); 208 | } 209 | 210 | test(aaa_printFreeMemoryAtStart) { // warning: relies on fact tests are run in alphabetical order 211 | Serial.println(F("---------------------")); 212 | Serial.print("Free Memory Start "); Serial.println(freeMemory()); 213 | Serial.println(F("---------------------")); 214 | } 215 | 216 | test(zzz_printFreeMemoryAtEnd) { // warning: relies on fact tests are run in alphabetical order 217 | // unfortunately the user needs to manually check this matches the start value 218 | Serial.println(F("=====================")); 219 | Serial.print("Free Memory End "); Serial.println(freeMemory()); 220 | Serial.println(F("=====================")); 221 | } 222 | 223 | void loop() { 224 | Test::run(); 225 | } -------------------------------------------------------------------------------- /tests/NdefUnitTest/NdefUnitTest.ino: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | void assertBytesEqual(const uint8_t* expected, const uint8_t* actual, uint8_t size) { 7 | for (int i = 0; i < size; i++) { 8 | assertEqual(expected[i], actual[i]); 9 | } 10 | } 11 | 12 | void setup() { 13 | Serial.begin(9600); 14 | } 15 | 16 | test(accessors) { 17 | NdefRecord record = NdefRecord(); 18 | record.setTnf(TNF_WELL_KNOWN); 19 | uint8_t recordType[] = { 0x54 }; // "T" Text Record 20 | assertEqual(0x54, recordType[0]); 21 | record.setType(recordType, sizeof(recordType)); 22 | // 2 + "en" + "Unit Test" 23 | uint8_t payload[] = { 0x02, 0x65, 0x6e, 0x55, 0x6e, 0x69, 0x74, 0x20, 0x54, 0x65, 0x73, 0x74 }; 24 | record.setPayload(payload, sizeof(payload)); 25 | uint8_t id[] = { 0x74, 0x65, 0x73, 0x74, 0x69, 0x64}; // testid 26 | record.setId(id, sizeof(id)); 27 | 28 | assertEqual(TNF_WELL_KNOWN, record.getTnf()); 29 | assertEqual(sizeof(recordType), record.getTypeLength()); 30 | assertEqual(1, record.getTypeLength()); 31 | assertEqual(sizeof(payload), record.getPayloadLength()); 32 | assertEqual(12, record.getPayloadLength()); 33 | assertEqual(sizeof(id), record.getIdLength()); 34 | assertEqual(6, record.getIdLength()); 35 | 36 | uint8_t typeCheck[record.getTypeLength()]; 37 | record.getType(typeCheck); 38 | 39 | assertEqual(0x54, typeCheck[0]); 40 | assertBytesEqual(recordType, typeCheck, sizeof(recordType)); 41 | 42 | uint8_t payloadCheck[record.getPayloadLength()]; 43 | record.getPayload(&payloadCheck[0]); 44 | assertBytesEqual(payload, payloadCheck, sizeof(payload)); 45 | 46 | uint8_t idCheck[record.getIdLength()]; 47 | record.getId(&idCheck[0]); 48 | assertBytesEqual(id, idCheck, sizeof(id)); 49 | } 50 | 51 | test(newaccessors) { 52 | NdefRecord record = NdefRecord(); 53 | record.setTnf(TNF_WELL_KNOWN); 54 | uint8_t recordType[] = { 0x54 }; // "T" Text Record 55 | assertEqual(0x54, recordType[0]); 56 | record.setType(recordType, sizeof(recordType)); 57 | // 2 + "en" + "Unit Test" 58 | uint8_t payload[] = { 0x02, 0x65, 0x6e, 0x55, 0x6e, 0x69, 0x74, 0x20, 0x54, 0x65, 0x73, 0x74 }; 59 | record.setPayload(payload, sizeof(payload)); 60 | uint8_t id[] = { 0x74, 0x65, 0x73, 0x74, 0x69, 0x64}; // testid 61 | record.setId(id, sizeof(id)); 62 | 63 | assertEqual(TNF_WELL_KNOWN, record.getTnf()); 64 | assertEqual(sizeof(recordType), record.getTypeLength()); 65 | assertEqual(1, record.getTypeLength()); 66 | assertEqual(sizeof(payload), record.getPayloadLength()); 67 | assertEqual(12, record.getPayloadLength()); 68 | assertEqual(sizeof(id), record.getIdLength()); 69 | assertEqual(6, record.getIdLength()); 70 | 71 | ::String typeCheck = record.getType(); 72 | assertTrue(typeCheck.equals("T")); 73 | 74 | byte payloadCheck[record.getPayloadLength()]; 75 | record.getPayload(payloadCheck); 76 | assertBytesEqual(payload, payloadCheck, sizeof(payload)); 77 | 78 | byte idCheck[record.getIdLength()]; 79 | record.getId(idCheck); 80 | assertBytesEqual(id, idCheck, sizeof(id)); 81 | } 82 | 83 | test(assignment) { 84 | NdefRecord record = NdefRecord(); 85 | record.setTnf(TNF_WELL_KNOWN); 86 | uint8_t recordType[] = { 0x54 }; // "T" Text Record 87 | assertEqual(0x54, recordType[0]); 88 | record.setType(recordType, sizeof(recordType)); 89 | // 2 + "en" + "Unit Test" 90 | uint8_t payload[] = { 0x02, 0x65, 0x6e, 0x55, 0x6e, 0x69, 0x74, 0x20, 0x54, 0x65, 0x73, 0x74 }; 91 | record.setPayload(payload, sizeof(payload)); 92 | uint8_t id[] = { 0x74, 0x65, 0x73, 0x74, 0x69, 0x64}; // testid 93 | record.setId(id, sizeof(id)); 94 | 95 | NdefRecord record2 = NdefRecord(); 96 | record2 = record; 97 | 98 | assertEqual(TNF_WELL_KNOWN, record.getTnf()); 99 | assertEqual(sizeof(recordType), record2.getTypeLength()); 100 | assertEqual(sizeof(payload), record2.getPayloadLength()); 101 | assertEqual(sizeof(id), record2.getIdLength()); 102 | 103 | ::String typeCheck = record.getType(); 104 | assertTrue(typeCheck.equals("T")); 105 | 106 | byte payload2[record2.getPayloadLength()]; 107 | record2.getPayload(payload2); 108 | assertBytesEqual(payload, payload2, sizeof(payload)); 109 | 110 | byte id2[record.getIdLength()]; 111 | record2.getId(id2); 112 | assertBytesEqual(id, id2, sizeof(id)); 113 | } 114 | 115 | test(getEmptyPayload) { 116 | NdefRecord r = NdefRecord(); 117 | assertEqual(TNF_EMPTY, r.getTnf()); 118 | assertEqual(0, r.getPayloadLength()); 119 | 120 | byte payload[r.getPayloadLength()]; 121 | r.getPayload(payload); 122 | 123 | byte id[r.getIdLength()]; 124 | r.getId(id); 125 | 126 | byte empty[0]; 127 | assertBytesEqual(empty, payload, sizeof(payload)); 128 | assertBytesEqual(empty, id, sizeof(id)); 129 | } 130 | 131 | test(encoding_without_record_id) { 132 | NdefRecord record = NdefRecord(); 133 | record.setTnf(TNF_WELL_KNOWN); 134 | uint8_t recordType[] = { 0x54 }; // "T" Text Record 135 | assertEqual(0x54, recordType[0]); 136 | record.setType(recordType, sizeof(recordType)); 137 | // 2 + "en" + "Unit Test" 138 | uint8_t payload[] = { 0x02, 0x65, 0x6e, 0x55, 0x6e, 0x69, 0x74, 0x20, 0x54, 0x65, 0x73, 0x74 }; 139 | record.setPayload(payload, sizeof(payload)); 140 | 141 | uint8_t encodedBytes[record.getEncodedSize()]; 142 | record.encode(encodedBytes, true, true); 143 | 144 | uint8_t expectedBytes[] = { 209, 1, 12, 84, 2, 101, 110, 85, 110, 105, 116, 32, 84, 101, 115, 116 }; 145 | assertBytesEqual(encodedBytes, expectedBytes, sizeof(encodedBytes)); 146 | } 147 | 148 | // https://github.com/don/NDEF/issues/30 149 | test(encoding_with_record_id) { 150 | NdefRecord record = NdefRecord(); 151 | record.setTnf(TNF_WELL_KNOWN); 152 | uint8_t recordType[] = { 0x54 }; // "T" Text Record 153 | assertEqual(0x54, recordType[0]); 154 | record.setType(recordType, sizeof(recordType)); 155 | // 2 + "en" + "Unit Test" 156 | uint8_t payload[] = { 0x02, 0x65, 0x6e, 0x55, 0x6e, 0x69, 0x74, 0x20, 0x54, 0x65, 0x73, 0x74 }; 157 | record.setPayload(payload, sizeof(payload)); 158 | // testid 159 | uint8_t id[] = { 0x74, 0x65, 0x73, 0x74, 0x69, 0x64}; 160 | record.setId(id, sizeof(id)); 161 | 162 | uint8_t encodedBytes[record.getEncodedSize()]; 163 | record.encode(encodedBytes, true, true); 164 | 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 }; 165 | 166 | assertBytesEqual(encodedBytes, expectedBytes, sizeof(encodedBytes)); 167 | } 168 | 169 | void loop() { 170 | Test::run(); 171 | } 172 | -------------------------------------------------------------------------------- /tests/NfcTagTest/NfcTagTest.ino: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | void setup() { 7 | Serial.begin(9600); 8 | } 9 | 10 | // Test for pull requests #14 and #16 11 | test(getUid) { 12 | byte uid[4] = { 0x00, 0xFF, 0xAA, 0x17 }; 13 | byte uidFromTag[sizeof(uid)]; 14 | 15 | NfcTag tag = NfcTag(uid, sizeof(uid)); 16 | 17 | assertEqual(sizeof(uid), tag.getUidLength()); 18 | 19 | tag.getUid(uidFromTag, sizeof(uidFromTag)); 20 | 21 | // make sure the 2 uids are the same 22 | for (int i = 0; i < sizeof(uid); i++) { 23 | assertEqual(uid[i], uidFromTag[i]); 24 | } 25 | 26 | // check contents, to ensure the original uid wasn't overwritten 27 | assertEqual(0x00, uid[0]); 28 | assertEqual(0xFF, uid[1]); 29 | assertEqual(0xAA, uid[2]); 30 | assertEqual(0x17, uid[3]); 31 | } 32 | 33 | void loop() { 34 | Test::run(); 35 | } 36 | 37 | --------------------------------------------------------------------------------