From cdd3adabe5a5e36b348420142a2500a626bba272 Mon Sep 17 00:00:00 2001 From: Azalea Date: Fri, 24 Nov 2023 20:14:55 -0500 Subject: [PATCH 01/16] first commit --- README.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 0000000..9b072ed --- /dev/null +++ b/README.md @@ -0,0 +1 @@ +# CardReader From 5b28a2a39065209ddf55c0d7a70a68066db36239 Mon Sep 17 00:00:00 2001 From: Azalea Date: Fri, 24 Nov 2023 20:15:25 -0500 Subject: [PATCH 02/16] [+] Initial version --- Audio/mixkit-gaming-lock-2848.wav | Bin 0 -> 201240 bytes firmware.ino | 104 ++++++++++++++++++++++++++++++ receiver.py | 65 +++++++++++++++++++ requirements.txt | 3 + 4 files changed, 172 insertions(+) create mode 100644 Audio/mixkit-gaming-lock-2848.wav create mode 100644 firmware.ino create mode 100644 receiver.py create mode 100644 requirements.txt diff --git a/Audio/mixkit-gaming-lock-2848.wav b/Audio/mixkit-gaming-lock-2848.wav new file mode 100644 index 0000000000000000000000000000000000000000..d0794795a1b4f3bc4ceb06ff729f40c01572dda0 GIT binary patch literal 201240 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

CkVT;>yW9&+J#*nbnsj*_K*ud-3+o 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 JOMkEX{{Tc@ + +#include +#include +#include + +PN532_I2C pn532i2c(Wire); +PN532 nfc(pn532i2c); + +#define Serial USBSerial + +#include + +uint8_t _prevIDm[8]; +unsigned long _prevTime; + +void setup(void) +{ + delay(1000); + Serial.begin(115200); + Serial.println("Hello!"); + Wire.setPins(4, 5); + + nfc.begin(); + + uint32_t versiondata = nfc.getFirmwareVersion(); + if (!versiondata) + { + Serial.print("Didn't find PN53x board"); + while (1) {delay(10);}; // halt + } + + // Got ok data, print it out! + Serial.print("Found chip PN5"); Serial.println((versiondata >> 24) & 0xFF, HEX); + Serial.print("Firmware ver. "); Serial.print((versiondata >> 16) & 0xFF, DEC); + Serial.print('.'); Serial.println((versiondata >> 8) & 0xFF, DEC); + + // Set the max number of retry attempts to read from a card + // This prevents us from waiting forever for a card, which is + // the default behaviour of the PN532. + nfc.setPassiveActivationRetries(0xFF); + nfc.SAMConfig(); + + memset(_prevIDm, 0, 8); +} + +void loop(void) +{ + uint8_t ret; + uint16_t systemCode = 0xFFFF; + uint8_t requestCode = 0x00; // System Code request + uint8_t idm[8]; + uint8_t pmm[8]; + uint16_t systemCodeResponse; + + // Wait for an FeliCa type cards. + // When one is found, some basic information such as IDm, PMm, and System Code are retrieved. + Serial.print("F"); + ret = nfc.felica_Polling(systemCode, requestCode, idm, pmm, &systemCodeResponse, 5); + + if (ret == 1) { + if ( memcmp(idm, _prevIDm, 8) == 0 ) { + if ( (millis() - _prevTime) < 3000 ) { + delay(5); + return; + } + } + + Serial.println("\nFound a Felica card!"); + printUid(idm, 8); + + memcpy(_prevIDm, idm, 8); + _prevTime = millis(); + return; + } + + Serial.print("M"); + uint8_t uid[] = { 0, 0, 0, 0, 0, 0, 0 }; // Buffer to store the returned UID + uint8_t uidLength; // Length of the UID (4 or 7 bytes depending on ISO14443A card type) + + if (nfc.readPassiveTargetID(PN532_MIFARE_ISO14443A, uid, &uidLength, 5)) { + // Check if the same card is present + if (memcmp(uid, _prevIDm, uidLength) == 0 && (millis() - _prevTime) < 3000) { + delay(5); + return; + } + + Serial.println("\nFound a MIFARE card!"); + printUid(uid, uidLength); + + memcpy(_prevIDm, uid, uidLength); + _prevTime = millis(); + return; + } +} + +void printUid(uint8_t* uid, uint8_t uidLength) { + Serial.print("UID Length: ");Serial.print(uidLength, DEC);Serial.println(" bytes"); + Serial.print("UID Value: "); + for (uint8_t i = 0; i < uidLength; i++) { + Serial.print(uid[i], HEX); + } + Serial.println(""); +} diff --git a/receiver.py b/receiver.py new file mode 100644 index 0000000..094415c --- /dev/null +++ b/receiver.py @@ -0,0 +1,65 @@ +from pathlib import Path +import time +import serial +import re +from playsound import playsound +from pynput.keyboard import Key, Controller + +keyboard = Controller() + +# Configure your serial port and baud rate +SERIAL_PORT = 'COMx' # Replace 'COMx' with your serial port (e.g., 'COM3' on Windows or '/dev/ttyUSB0' on Linux) +# SERIAL_PORT = '/dev/ttyACM0' +BAUD_RATE = 115200 +PATH = Path('C:/MUGS/felica.txt') +# PATH = Path('/tmp/felica.txt') + +AUDIO_EFFECT = Path(__file__).parent / 'Audio/tofu.wav' + +def parse_uid(data): + """ + Parse the UID from the serial data. + """ + uid_value_match = re.search(r'UID Value: ([0-9A-F]+)', data) + + if uid_value_match: + uid = uid_value_match.group(1) + + # If UID is not 8 bytes, pad it with zeros + if len(uid) < 16: + uid = uid.zfill(16) + + # If the UID Doesn't start with 01 2E, set it to 01 2E + if uid[:4] != '012E': + uid = '012E' + uid[4:] + + # Write the UID to the file + print(f"UID: {uid}") + PATH.write_text(uid) + + # Play audio effect + playsound(str(AUDIO_EFFECT)) + + # Press ENTER button + keyboard.press(Key.enter) + time.sleep(0.5) + keyboard.release(Key.enter) + + return uid + + +if __name__ == "__main__": + ser = serial.Serial(SERIAL_PORT, BAUD_RATE, timeout=1) + print(f"Listening on {SERIAL_PORT}...") + + try: + while True: + if ser.in_waiting > 0: + line = ser.readline().decode('utf-8', errors='replace').strip() + print(line) + parse_uid(line) + + except KeyboardInterrupt: + print("Exiting...") + finally: + ser.close() \ No newline at end of file diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..66e805c --- /dev/null +++ b/requirements.txt @@ -0,0 +1,3 @@ +pyserial +playsound +pynput \ No newline at end of file From 72678a08f946f277c5211b7d22c13dc7313337ed Mon Sep 17 00:00:00 2001 From: Azalea Date: Fri, 24 Nov 2023 20:27:47 -0500 Subject: [PATCH 03/16] [U] Windows winsound support --- receiver.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/receiver.py b/receiver.py index 094415c..7a82113 100644 --- a/receiver.py +++ b/receiver.py @@ -1,8 +1,8 @@ +import os from pathlib import Path import time import serial import re -from playsound import playsound from pynput.keyboard import Key, Controller keyboard = Controller() @@ -38,7 +38,13 @@ def parse_uid(data): PATH.write_text(uid) # Play audio effect - playsound(str(AUDIO_EFFECT)) + # If is Windows, use winsound + if os.name == 'nt': + import winsound + winsound.PlaySound(str(AUDIO_EFFECT), winsound.SND_FILENAME) + else: + from playsound import playsound + playsound(str(AUDIO_EFFECT)) # Press ENTER button keyboard.press(Key.enter) From e122894ff0cef5c2abe2d8f7d208b90aa64d9470 Mon Sep 17 00:00:00 2001 From: Azalea <22280294+hykilpikonna@users.noreply.github.com> Date: Sat, 25 Nov 2023 00:53:28 -0500 Subject: [PATCH 04/16] Create .gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c18dd8d --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +__pycache__/ From 0b4b1ca212a92869fb686d11fc84f210fae6f5cf Mon Sep 17 00:00:00 2001 From: Azalea <22280294+hykilpikonna@users.noreply.github.com> Date: Sat, 25 Nov 2023 00:53:45 -0500 Subject: [PATCH 05/16] [+] Use win32 api for key press --- vk.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 vk.py diff --git a/vk.py b/vk.py new file mode 100644 index 0000000..1519e03 --- /dev/null +++ b/vk.py @@ -0,0 +1,15 @@ +import ctypes +from ctypes import wintypes + +# Virtual-Key codes +# RESERVED: 0xEA +VK = 0x6B +KEYEVENTF_KEYUP = 0x0002 + +def press_key(vk_code = VK): + scan_code = ctypes.windll.user32.MapVirtualKeyA(vk_code, 0) + ctypes.windll.user32.keybd_event(vk_code, scan_code, 0, 0) + +def release_key(vk_code = VK): + scan_code = ctypes.windll.user32.MapVirtualKeyA(vk_code, 0) + ctypes.windll.user32.keybd_event(vk_code, scan_code, KEYEVENTF_KEYUP, 0) \ No newline at end of file From 10870f9ac8023df32843742cf44aec8415527ae0 Mon Sep 17 00:00:00 2001 From: Azalea <22280294+hykilpikonna@users.noreply.github.com> Date: Sat, 25 Nov 2023 00:53:56 -0500 Subject: [PATCH 06/16] [U] Update receiver --- receiver.py | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/receiver.py b/receiver.py index 7a82113..37f86cd 100644 --- a/receiver.py +++ b/receiver.py @@ -3,12 +3,10 @@ from pathlib import Path import time import serial import re -from pynput.keyboard import Key, Controller - -keyboard = Controller() +from vk import * # Configure your serial port and baud rate -SERIAL_PORT = 'COMx' # Replace 'COMx' with your serial port (e.g., 'COM3' on Windows or '/dev/ttyUSB0' on Linux) +SERIAL_PORT = 'COM3' # Replace 'COMx' with your serial port (e.g., 'COM3' on Windows or '/dev/ttyUSB0' on Linux) # SERIAL_PORT = '/dev/ttyACM0' BAUD_RATE = 115200 PATH = Path('C:/MUGS/felica.txt') @@ -46,10 +44,10 @@ def parse_uid(data): from playsound import playsound playsound(str(AUDIO_EFFECT)) - # Press ENTER button - keyboard.press(Key.enter) - time.sleep(0.5) - keyboard.release(Key.enter) + # Press scan button + press_key() + time.sleep(2) + release_key() return uid From 874c8a5b98f4bb9ad8098a923099613c7f2ada51 Mon Sep 17 00:00:00 2001 From: Azalea <22280294+hykilpikonna@users.noreply.github.com> Date: Sat, 25 Nov 2023 00:54:05 -0500 Subject: [PATCH 07/16] [+] Start script --- start.bat | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 start.bat diff --git a/start.bat b/start.bat new file mode 100644 index 0000000..2fa58d5 --- /dev/null +++ b/start.bat @@ -0,0 +1,9 @@ +%1 mshta vbscript:CreateObject("Shell.Application").ShellExecute("cmd.exe","/c %~s0 ::","","runas",1)(window.close)&&exit +cd /d "%~dp0" +cd Code\CardReader +:Start +python receiver.py +echo Press Ctrl-C if you don't want to restart automatically +ping -n 1 localhost + +goto Start \ No newline at end of file From 80e142a50d33f4ca9720b3b572a7baba2befc19e Mon Sep 17 00:00:00 2001 From: Azalea <22280294+hykilpikonna@users.noreply.github.com> Date: Sat, 25 Nov 2023 01:47:07 -0500 Subject: [PATCH 08/16] [F] Fix spicetools not picking up presses --- receiver.py | 17 +++++++---------- requirements.txt | 1 - 2 files changed, 7 insertions(+), 11 deletions(-) diff --git a/receiver.py b/receiver.py index 37f86cd..c2766f8 100644 --- a/receiver.py +++ b/receiver.py @@ -4,6 +4,7 @@ import time import serial import re from vk import * +import winsound # Configure your serial port and baud rate SERIAL_PORT = 'COM3' # Replace 'COMx' with your serial port (e.g., 'COM3' on Windows or '/dev/ttyUSB0' on Linux) @@ -36,18 +37,14 @@ def parse_uid(data): PATH.write_text(uid) # Play audio effect - # If is Windows, use winsound - if os.name == 'nt': - import winsound - winsound.PlaySound(str(AUDIO_EFFECT), winsound.SND_FILENAME) - else: - from playsound import playsound - playsound(str(AUDIO_EFFECT)) + winsound.PlaySound(str(AUDIO_EFFECT), winsound.SND_FILENAME) # Press scan button - press_key() - time.sleep(2) - release_key() + for i in range(3): + press_key() + time.sleep(0.1) + release_key() + time.sleep(0.1) return uid diff --git a/requirements.txt b/requirements.txt index 66e805c..40c5ccd 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,2 @@ pyserial -playsound pynput \ No newline at end of file From 50d64742100737ef4d0bc034be9b4142b439fb47 Mon Sep 17 00:00:00 2001 From: Azalea <22280294+hykilpikonna@users.noreply.github.com> Date: Sat, 25 Nov 2023 01:56:50 -0500 Subject: [PATCH 09/16] [M] Move firmware --- .gitignore | 1 + firmware.ino => src/firmware.cpp | 0 2 files changed, 1 insertion(+) rename firmware.ino => src/firmware.cpp (100%) diff --git a/.gitignore b/.gitignore index c18dd8d..a031648 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ __pycache__/ +.idea \ No newline at end of file diff --git a/firmware.ino b/src/firmware.cpp similarity index 100% rename from firmware.ino rename to src/firmware.cpp From 0033203c032e81a9b61f96ea4673e7bc1138621c Mon Sep 17 00:00:00 2001 From: Azalea <22280294+hykilpikonna@users.noreply.github.com> Date: Sat, 25 Nov 2023 01:57:00 -0500 Subject: [PATCH 10/16] [+] PlatformIO --- platformio.ini | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 platformio.ini diff --git a/platformio.ini b/platformio.ini new file mode 100644 index 0000000..0ca15e6 --- /dev/null +++ b/platformio.ini @@ -0,0 +1,14 @@ +; PlatformIO Project Configuration File +; +; Build options: build flags, source filter +; Upload options: custom upload port, speed and extra flags +; Library options: dependencies, extra library storages +; Advanced options: extra scripting +; +; Please visit documentation for the other options and examples +; https://docs.platformio.org/page/projectconf.html + +[env:airm2m_core_esp32c3] +platform = espressif32 +board = airm2m_core_esp32c3 +framework = arduino From 1da9c22d2fa8f2d2b66a501d0a8af191f20a9c22 Mon Sep 17 00:00:00 2001 From: Azalea <22280294+hykilpikonna@users.noreply.github.com> Date: Sat, 25 Nov 2023 02:12:56 -0500 Subject: [PATCH 11/16] [+] Libraries --- .gitmodules | 3 +++ include/README | 39 ++++++++++++++++++++++++++++ lib/PN532 | 1 + lib/PN532-repo | 1 + lib/PN532_I2C | 1 + lib/README | 46 ++++++++++++++++++++++++++++++++++ src/{firmware.cpp => main.cpp} | 24 +++++++++--------- test/README | 11 ++++++++ 8 files changed, 114 insertions(+), 12 deletions(-) create mode 100644 .gitmodules create mode 100644 include/README create mode 120000 lib/PN532 create mode 160000 lib/PN532-repo create mode 120000 lib/PN532_I2C create mode 100644 lib/README rename src/{firmware.cpp => main.cpp} (98%) create mode 100644 test/README diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..8c0f673 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "lib/PN532"] + path = lib/PN532-repo + url = https://github.com/Seeed-Studio/PN532/ diff --git a/include/README b/include/README new file mode 100644 index 0000000..194dcd4 --- /dev/null +++ b/include/README @@ -0,0 +1,39 @@ + +This directory is intended for project header files. + +A header file is a file containing C declarations and macro definitions +to be shared between several project source files. You request the use of a +header file in your project source file (C, C++, etc) located in `src` folder +by including it, with the C preprocessing directive `#include'. + +```src/main.c + +#include "header.h" + +int main (void) +{ + ... +} +``` + +Including a header file produces the same results as copying the header file +into each source file that needs it. Such copying would be time-consuming +and error-prone. With a header file, the related declarations appear +in only one place. If they need to be changed, they can be changed in one +place, and programs that include the header file will automatically use the +new version when next recompiled. The header file eliminates the labor of +finding and changing all the copies as well as the risk that a failure to +find one copy will result in inconsistencies within a program. + +In C, the usual convention is to give header files names that end with `.h'. +It is most portable to use only letters, digits, dashes, and underscores in +header file names, and at most one dot. + +Read more about using header files in official GCC documentation: + +* Include Syntax +* Include Operation +* Once-Only Headers +* Computed Includes + +https://gcc.gnu.org/onlinedocs/cpp/Header-Files.html diff --git a/lib/PN532 b/lib/PN532 new file mode 120000 index 0000000..e225928 --- /dev/null +++ b/lib/PN532 @@ -0,0 +1 @@ +PN532-repo/PN532 \ No newline at end of file diff --git a/lib/PN532-repo b/lib/PN532-repo new file mode 160000 index 0000000..40fa741 --- /dev/null +++ b/lib/PN532-repo @@ -0,0 +1 @@ +Subproject commit 40fa7418636da5e03b0126e38c221b143b24b5a4 diff --git a/lib/PN532_I2C b/lib/PN532_I2C new file mode 120000 index 0000000..62e3a10 --- /dev/null +++ b/lib/PN532_I2C @@ -0,0 +1 @@ +PN532-repo/PN532_I2C \ No newline at end of file diff --git a/lib/README b/lib/README new file mode 100644 index 0000000..6debab1 --- /dev/null +++ b/lib/README @@ -0,0 +1,46 @@ + +This directory is intended for project specific (private) libraries. +PlatformIO will compile them to static libraries and link into executable file. + +The source code of each library should be placed in a an own separate directory +("lib/your_library_name/[here are source files]"). + +For example, see a structure of the following two libraries `Foo` and `Bar`: + +|--lib +| | +| |--Bar +| | |--docs +| | |--examples +| | |--src +| | |- Bar.c +| | |- Bar.h +| | |- library.json (optional, custom build options, etc) https://docs.platformio.org/page/librarymanager/config.html +| | +| |--Foo +| | |- Foo.c +| | |- Foo.h +| | +| |- README --> THIS FILE +| +|- platformio.ini +|--src + |- main.c + +and a contents of `src/main.c`: +``` +#include +#include + +int main (void) +{ + ... +} + +``` + +PlatformIO Library Dependency Finder will find automatically dependent +libraries scanning project source files. + +More information about PlatformIO Library Dependency Finder +- https://docs.platformio.org/page/librarymanager/ldf.html diff --git a/src/firmware.cpp b/src/main.cpp similarity index 98% rename from src/firmware.cpp rename to src/main.cpp index b6f863c..f58dbd8 100644 --- a/src/firmware.cpp +++ b/src/main.cpp @@ -14,7 +14,7 @@ PN532 nfc(pn532i2c); uint8_t _prevIDm[8]; unsigned long _prevTime; -void setup(void) +void setup() { delay(1000); Serial.begin(115200); @@ -44,7 +44,16 @@ void setup(void) memset(_prevIDm, 0, 8); } -void loop(void) +void printUid(uint8_t* uid, uint8_t uidLength) { + Serial.print("UID Length: ");Serial.print(uidLength, DEC);Serial.println(" bytes"); + Serial.print("UID Value: "); + for (uint8_t i = 0; i < uidLength; i++) { + Serial.print(uid[i], HEX); + } + Serial.println(""); +} + +void loop() { uint8_t ret; uint16_t systemCode = 0xFFFF; @@ -73,7 +82,7 @@ void loop(void) _prevTime = millis(); return; } - + Serial.print("M"); uint8_t uid[] = { 0, 0, 0, 0, 0, 0, 0 }; // Buffer to store the returned UID uint8_t uidLength; // Length of the UID (4 or 7 bytes depending on ISO14443A card type) @@ -93,12 +102,3 @@ void loop(void) return; } } - -void printUid(uint8_t* uid, uint8_t uidLength) { - Serial.print("UID Length: ");Serial.print(uidLength, DEC);Serial.println(" bytes"); - Serial.print("UID Value: "); - for (uint8_t i = 0; i < uidLength; i++) { - Serial.print(uid[i], HEX); - } - Serial.println(""); -} diff --git a/test/README b/test/README new file mode 100644 index 0000000..9b1e87b --- /dev/null +++ b/test/README @@ -0,0 +1,11 @@ + +This directory is intended for PlatformIO Test Runner and project tests. + +Unit Testing is a software testing method by which individual units of +source code, sets of one or more MCU program modules together with associated +control data, usage procedures, and operating procedures, are tested to +determine whether they are fit for use. Unit testing finds problems early +in the development cycle. + +More information about PlatformIO Unit Testing: +- https://docs.platformio.org/en/latest/advanced/unit-testing/index.html From 121c3cca2c794bf27e19ea89483f05a70ff8e28d Mon Sep 17 00:00:00 2001 From: Azalea <22280294+hykilpikonna@users.noreply.github.com> Date: Sat, 25 Nov 2023 02:54:25 -0500 Subject: [PATCH 12/16] [+] PN532 Library --- .gitignore | 3 +- README.md | 6 + lib/PN532 | 1 - lib/PN532-repo | 1 - lib/PN532/PN532.cpp | 1509 +++++++++++++++++ lib/PN532/PN532.h | 212 +++ lib/PN532/PN532Interface.h | 56 + lib/PN532/PN532_debug.h | 26 + lib/PN532/README.md | 30 + lib/PN532/emulatetag.cpp | 298 ++++ lib/PN532/emulatetag.h | 85 + .../FeliCa_card_detection.pde | 115 ++ .../FeliCa_card_read/FeliCa_card_read.pde | 165 ++ .../examples/android_hce/android_hce.ino | 167 ++ .../emulate_tag_ndef/emulate_tag_ndef.ino | 83 + .../examples/iso14443a_uid/iso14443a_uid.pde | 98 ++ .../mifareclassic_formatndef.pde | 181 ++ .../mifareclassic_memdump.pde | 172 ++ .../mifareclassic_ndeftoclassic.pde | 183 ++ .../mifareclassic_updatendef.pde | 156 ++ .../ntag21x_protect/ntag21x_protect.ino | 89 + lib/PN532/examples/ntag21x_rw/ntag21x_rw.ino | 71 + lib/PN532/examples/p2p_raw/p2p_raw.ino | 50 + .../p2p_with_ndef_library.ino | 54 + lib/PN532/examples/readMifare/readMifare.pde | 158 ++ lib/PN532/license.txt | 27 + lib/PN532/llcp.cpp | 373 ++++ lib/PN532/llcp.h | 75 + lib/PN532/mac_link.cpp | 20 + lib/PN532/mac_link.h | 51 + lib/PN532/snep.cpp | 133 ++ lib/PN532/snep.h | 49 + lib/PN532_I2C | 1 - lib/PN532_I2C/PN532_I2C.cpp | 254 +++ lib/PN532_I2C/PN532_I2C.h | 47 + 35 files changed, 4995 insertions(+), 4 deletions(-) delete mode 120000 lib/PN532 delete mode 160000 lib/PN532-repo create mode 100644 lib/PN532/PN532.cpp create mode 100644 lib/PN532/PN532.h create mode 100644 lib/PN532/PN532Interface.h create mode 100644 lib/PN532/PN532_debug.h create mode 100644 lib/PN532/README.md create mode 100644 lib/PN532/emulatetag.cpp create mode 100644 lib/PN532/emulatetag.h create mode 100644 lib/PN532/examples/FeliCa_card_detection/FeliCa_card_detection.pde create mode 100644 lib/PN532/examples/FeliCa_card_read/FeliCa_card_read.pde create mode 100644 lib/PN532/examples/android_hce/android_hce.ino create mode 100644 lib/PN532/examples/emulate_tag_ndef/emulate_tag_ndef.ino create mode 100644 lib/PN532/examples/iso14443a_uid/iso14443a_uid.pde create mode 100644 lib/PN532/examples/mifareclassic_formatndef/mifareclassic_formatndef.pde create mode 100644 lib/PN532/examples/mifareclassic_memdump/mifareclassic_memdump.pde create mode 100644 lib/PN532/examples/mifareclassic_ndeftoclassic/mifareclassic_ndeftoclassic.pde create mode 100644 lib/PN532/examples/mifareclassic_updatendef/mifareclassic_updatendef.pde create mode 100644 lib/PN532/examples/ntag21x_protect/ntag21x_protect.ino create mode 100644 lib/PN532/examples/ntag21x_rw/ntag21x_rw.ino create mode 100644 lib/PN532/examples/p2p_raw/p2p_raw.ino create mode 100644 lib/PN532/examples/p2p_with_ndef_library/p2p_with_ndef_library.ino create mode 100644 lib/PN532/examples/readMifare/readMifare.pde create mode 100644 lib/PN532/license.txt create mode 100644 lib/PN532/llcp.cpp create mode 100644 lib/PN532/llcp.h create mode 100644 lib/PN532/mac_link.cpp create mode 100644 lib/PN532/mac_link.h create mode 100644 lib/PN532/snep.cpp create mode 100644 lib/PN532/snep.h delete mode 120000 lib/PN532_I2C create mode 100644 lib/PN532_I2C/PN532_I2C.cpp create mode 100644 lib/PN532_I2C/PN532_I2C.h diff --git a/.gitignore b/.gitignore index a031648..12a8bf3 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ __pycache__/ -.idea \ No newline at end of file +.idea +.pio \ No newline at end of file diff --git a/README.md b/README.md index 9b072ed..d722d1f 100644 --- a/README.md +++ b/README.md @@ -1 +1,7 @@ # CardReader + +A rhythm game arcade card reader that support all NFC cards. + +## Dependencies: + +* PN532 Library: https://github.com/Seeed-Studio/PN532/ \ No newline at end of file diff --git a/lib/PN532 b/lib/PN532 deleted file mode 120000 index e225928..0000000 --- a/lib/PN532 +++ /dev/null @@ -1 +0,0 @@ -PN532-repo/PN532 \ No newline at end of file diff --git a/lib/PN532-repo b/lib/PN532-repo deleted file mode 160000 index 40fa741..0000000 --- a/lib/PN532-repo +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 40fa7418636da5e03b0126e38c221b143b24b5a4 diff --git a/lib/PN532/PN532.cpp b/lib/PN532/PN532.cpp new file mode 100644 index 0000000..46152ea --- /dev/null +++ b/lib/PN532/PN532.cpp @@ -0,0 +1,1509 @@ +/**************************************************************************/ +/*! + @file PN532.cpp + @author Adafruit Industries & Seeed Studio + @license BSD +*/ +/**************************************************************************/ + +#include "Arduino.h" +#include "PN532.h" +#include "PN532_debug.h" +#include + +#define HAL(func) (_interface->func) + +PN532::PN532(PN532Interface &interface) +{ + _interface = &interface; +} + +/**************************************************************************/ +/*! + @brief Setups the HW +*/ +/**************************************************************************/ +void PN532::begin() +{ + HAL(begin)(); + HAL(wakeup)(); +} + +/**************************************************************************/ +/*! + @brief Prints a hexadecimal value in plain characters + + @param data Pointer to the uint8_t data + @param numBytes Data length in bytes +*/ +/**************************************************************************/ +void PN532::PrintHex(const uint8_t *data, const uint32_t numBytes) +{ +#ifdef ARDUINO + for (uint8_t i = 0; i < numBytes; i++) { + if (data[i] < 0x10) { + SERIAL.print(" 0"); + } else { + SERIAL.print(' '); + } + SERIAL.print(data[i], HEX); + } + SERIAL.println(""); +#else + for (uint8_t i = 0; i < numBytes; i++) { + printf(" %2X", data[i]); + } + printf("\n"); +#endif +} + +/**************************************************************************/ +/*! + @brief Prints a hexadecimal value in plain characters, along with + the char equivalents in the following format + + 00 00 00 00 00 00 ...... + + @param data Pointer to the data + @param numBytes Data length in bytes +*/ +/**************************************************************************/ +void PN532::PrintHexChar(const uint8_t *data, const uint32_t numBytes) +{ +#ifdef ARDUINO + for (uint8_t i = 0; i < numBytes; i++) { + if (data[i] < 0x10) { + SERIAL.print(" 0"); + } else { + SERIAL.print(' '); + } + SERIAL.print(data[i], HEX); + } + SERIAL.print(" "); + for (uint8_t i = 0; i < numBytes; i++) { + char c = data[i]; + if (c <= 0x1f || c > 0x7f) { + SERIAL.print('.'); + } else { + SERIAL.print(c); + } + } + SERIAL.println(""); +#else + for (uint8_t i = 0; i < numBytes; i++) { + printf(" %2X", data[i]); + } + printf(" "); + for (uint8_t i = 0; i < numBytes; i++) { + char c = data[i]; + if (c <= 0x1f || c > 0x7f) { + printf("."); + } else { + printf("%c", c); + } + printf("\n"); + } +#endif +} + +/**************************************************************************/ +/*! + @brief Checks the firmware version of the PN5xx chip + + @returns The chip's firmware version and ID +*/ +/**************************************************************************/ +uint32_t PN532::getFirmwareVersion(void) +{ + uint32_t response; + + pn532_packetbuffer[0] = PN532_COMMAND_GETFIRMWAREVERSION; + + if (HAL(writeCommand)(pn532_packetbuffer, 1)) { + return 0; + } + + // read data packet + int16_t status = HAL(readResponse)(pn532_packetbuffer, sizeof(pn532_packetbuffer)); + if (0 > status) { + return 0; + } + + response = pn532_packetbuffer[0]; + response <<= 8; + response |= pn532_packetbuffer[1]; + response <<= 8; + response |= pn532_packetbuffer[2]; + response <<= 8; + response |= pn532_packetbuffer[3]; + + return response; +} + + +/**************************************************************************/ +/*! + @brief Read a PN532 register. + + @param reg the 16-bit register address. + + @returns The register value. +*/ +/**************************************************************************/ +uint32_t PN532::readRegister(uint16_t reg) +{ + uint32_t response; + + pn532_packetbuffer[0] = PN532_COMMAND_READREGISTER; + pn532_packetbuffer[1] = (reg >> 8) & 0xFF; + pn532_packetbuffer[2] = reg & 0xFF; + + if (HAL(writeCommand)(pn532_packetbuffer, 3)) { + return 0; + } + + // read data packet + int16_t status = HAL(readResponse)(pn532_packetbuffer, sizeof(pn532_packetbuffer)); + if (0 > status) { + return 0; + } + + response = pn532_packetbuffer[0]; + + return response; +} + +/**************************************************************************/ +/*! + @brief Write to a PN532 register. + + @param reg the 16-bit register address. + @param val the 8-bit value to write. + + @returns 0 for failure, 1 for success. +*/ +/**************************************************************************/ +uint32_t PN532::writeRegister(uint16_t reg, uint8_t val) +{ + uint32_t response; + + pn532_packetbuffer[0] = PN532_COMMAND_WRITEREGISTER; + pn532_packetbuffer[1] = (reg >> 8) & 0xFF; + pn532_packetbuffer[2] = reg & 0xFF; + pn532_packetbuffer[3] = val; + + + if (HAL(writeCommand)(pn532_packetbuffer, 4)) { + return 0; + } + + // read data packet + int16_t status = HAL(readResponse)(pn532_packetbuffer, sizeof(pn532_packetbuffer)); + if (0 > status) { + return 0; + } + + return 1; +} + +/**************************************************************************/ +/*! + Writes an 8-bit value that sets the state of the PN532's GPIO pins + + @warning This function is provided exclusively for board testing and + is dangerous since it will throw an error if any pin other + than the ones marked "Can be used as GPIO" are modified! All + pins that can not be used as GPIO should ALWAYS be left high + (value = 1) or the system will become unstable and a HW reset + will be required to recover the PN532. + + pinState[0] = P30 Can be used as GPIO + pinState[1] = P31 Can be used as GPIO + pinState[2] = P32 *** RESERVED (Must be 1!) *** + pinState[3] = P33 Can be used as GPIO + pinState[4] = P34 *** RESERVED (Must be 1!) *** + pinState[5] = P35 Can be used as GPIO + + @returns 1 if everything executed properly, 0 for an error +*/ +/**************************************************************************/ +bool PN532::writeGPIO(uint8_t pinstate) +{ + // Make sure pinstate does not try to toggle P32 or P34 + pinstate |= (1 << PN532_GPIO_P32) | (1 << PN532_GPIO_P34); + + // Fill command buffer + pn532_packetbuffer[0] = PN532_COMMAND_WRITEGPIO; + pn532_packetbuffer[1] = PN532_GPIO_VALIDATIONBIT | pinstate; // P3 Pins + pn532_packetbuffer[2] = 0x00; // P7 GPIO Pins (not used ... taken by I2C) + + DMSG("Writing P3 GPIO: "); + DMSG_HEX(pn532_packetbuffer[1]); + DMSG("\n"); + + // Send the WRITEGPIO command (0x0E) + if (HAL(writeCommand)(pn532_packetbuffer, 3)) + return 0; + + return (0 < HAL(readResponse)(pn532_packetbuffer, sizeof(pn532_packetbuffer))); +} + +/**************************************************************************/ +/*! + Reads the state of the PN532's GPIO pins + + @returns An 8-bit value containing the pin state where: + + pinState[0] = P30 + pinState[1] = P31 + pinState[2] = P32 + pinState[3] = P33 + pinState[4] = P34 + pinState[5] = P35 +*/ +/**************************************************************************/ +uint8_t PN532::readGPIO(void) +{ + pn532_packetbuffer[0] = PN532_COMMAND_READGPIO; + + // Send the READGPIO command (0x0C) + if (HAL(writeCommand)(pn532_packetbuffer, 1)) + return 0x0; + + HAL(readResponse)(pn532_packetbuffer, sizeof(pn532_packetbuffer)); + + /* READGPIO response without prefix and suffix should be in the following format: + + byte Description + ------------- ------------------------------------------ + b0 P3 GPIO Pins + b1 P7 GPIO Pins (not used ... taken by I2C) + b2 Interface Mode Pins (not used ... bus select pins) + */ + + + DMSG("P3 GPIO: "); DMSG_HEX(pn532_packetbuffer[7]); + DMSG("P7 GPIO: "); DMSG_HEX(pn532_packetbuffer[8]); + DMSG("I0I1 GPIO: "); DMSG_HEX(pn532_packetbuffer[9]); + DMSG("\n"); + + return pn532_packetbuffer[0]; +} + +/**************************************************************************/ +/*! + @brief Configures the SAM (Secure Access Module) +*/ +/**************************************************************************/ +bool PN532::SAMConfig(void) +{ + pn532_packetbuffer[0] = PN532_COMMAND_SAMCONFIGURATION; + pn532_packetbuffer[1] = 0x01; // normal mode; + pn532_packetbuffer[2] = 0x14; // timeout 50ms * 20 = 1 second + pn532_packetbuffer[3] = 0x01; // use IRQ pin! + + DMSG("SAMConfig\n"); + + if (HAL(writeCommand)(pn532_packetbuffer, 4)) + return false; + + return (0 < HAL(readResponse)(pn532_packetbuffer, sizeof(pn532_packetbuffer))); +} + +/**************************************************************************/ +/*! + @brief Turn the module into power mode will wake up on I2C or SPI request +*/ +/**************************************************************************/ +bool PN532::powerDownMode() +{ + pn532_packetbuffer[0] = PN532_COMMAND_POWERDOWN; + pn532_packetbuffer[1] = 0xC0; // I2C or SPI Wakeup + pn532_packetbuffer[2] = 0x00; // no IRQ + + DMSG("POWERDOWN\n"); + + if (HAL(writeCommand)(pn532_packetbuffer, 4)) + return false; + + return (0 < HAL(readResponse)(pn532_packetbuffer, sizeof(pn532_packetbuffer))); +} + +/**************************************************************************/ +/*! + Sets the MxRtyPassiveActivation uint8_t of the RFConfiguration register + + @param maxRetries 0xFF to wait forever, 0x00..0xFE to timeout + after mxRetries + + @returns 1 if everything executed properly, 0 for an error +*/ +/**************************************************************************/ +bool PN532::setPassiveActivationRetries(uint8_t maxRetries) +{ + pn532_packetbuffer[0] = PN532_COMMAND_RFCONFIGURATION; + pn532_packetbuffer[1] = 5; // Config item 5 (MaxRetries) + pn532_packetbuffer[2] = 0xFF; // MxRtyATR (default = 0xFF) + pn532_packetbuffer[3] = 0x01; // MxRtyPSL (default = 0x01) + pn532_packetbuffer[4] = maxRetries; + + if (HAL(writeCommand)(pn532_packetbuffer, 5)) + return 0x0; // no ACK + + return (0 < HAL(readResponse)(pn532_packetbuffer, sizeof(pn532_packetbuffer))); +} + +/**************************************************************************/ +/*! + Sets the RFon/off uint8_t of the RFConfiguration register + + @param autoRFCA 0x00 No check of the external field before + activation + + 0x02 Check the external field before + activation + + @param rFOnOff 0x00 Switch the RF field off, 0x01 switch the RF + field on + + @returns 1 if everything executed properly, 0 for an error +*/ +/**************************************************************************/ + +bool PN532::setRFField(uint8_t autoRFCA, uint8_t rFOnOff) +{ + pn532_packetbuffer[0] = PN532_COMMAND_RFCONFIGURATION; + pn532_packetbuffer[1] = 1; + pn532_packetbuffer[2] = 0x00 | autoRFCA | rFOnOff; + + if (HAL(writeCommand)(pn532_packetbuffer, 3)) { + return 0x0; // command failed + } + + return (0 < HAL(readResponse)(pn532_packetbuffer, sizeof(pn532_packetbuffer))); +} + +/***** ISO14443A Commands ******/ + +/**************************************************************************/ +/*! + Puts PN532 into passive detection state with IRQ while waiting for an ISO14443A target + + @param cardBaudRate Baud rate of the card + + @returns 1 if everything executed properly, 0 for an error +*/ +bool PN532::startPassiveTargetIDDetection(uint8_t cardbaudrate) { + pn532_packetbuffer[0] = PN532_COMMAND_INLISTPASSIVETARGET; + pn532_packetbuffer[1] = 1; // max 1 cards at once (we can set this to 2 later) + pn532_packetbuffer[2] = cardbaudrate; + + if (HAL(writeCommand)(pn532_packetbuffer, 3)) { + return 0x0; // command failed + } +} + +/**************************************************************************/ +/*! + Waits for an ISO14443A target to enter the field + + @param cardBaudRate Baud rate of the card + @param uid Pointer to the array that will be populated + with the card's UID (up to 7 bytes) + @param uidLength Pointer to the variable that will hold the + length of the card's UID. + @param timeout The number of tries before timing out + @param inlist If set to true, the card will be inlisted + + @returns 1 if everything executed properly, 0 for an error +*/ +/**************************************************************************/ +bool PN532::readPassiveTargetID(uint8_t cardbaudrate, uint8_t *uid, uint8_t *uidLength, uint16_t timeout, bool inlist) +{ + pn532_packetbuffer[0] = PN532_COMMAND_INLISTPASSIVETARGET; + pn532_packetbuffer[1] = 1; // max 1 cards at once (we can set this to 2 later) + pn532_packetbuffer[2] = cardbaudrate; + + if (HAL(writeCommand)(pn532_packetbuffer, 3)) { + return 0x0; // command failed + } + + // read data packet + if (HAL(readResponse)(pn532_packetbuffer, sizeof(pn532_packetbuffer), timeout) < 0) { + return 0x0; + } + + // check some basic stuff + /* ISO14443A card response should be in the following format: + + byte Description + ------------- ------------------------------------------ + b0 Tags Found + b1 Tag Number (only one used in this example) + b2..3 SENS_RES + b4 SEL_RES + b5 NFCID Length + b6..NFCIDLen NFCID + */ + + if (pn532_packetbuffer[0] != 1) + return 0; + + uint16_t sens_res = pn532_packetbuffer[2]; + sens_res <<= 8; + sens_res |= pn532_packetbuffer[3]; + + DMSG("ATQA: 0x"); DMSG_HEX(sens_res); + DMSG("SAK: 0x"); DMSG_HEX(pn532_packetbuffer[4]); + DMSG("\n"); + + /* Card appears to be Mifare Classic */ + *uidLength = pn532_packetbuffer[5]; + + for (uint8_t i = 0; i < pn532_packetbuffer[5]; i++) { + uid[i] = pn532_packetbuffer[6 + i]; + } + + if (inlist) { + inListedTag = pn532_packetbuffer[1]; + } + + return 1; +} + + +/***** Mifare Classic Functions ******/ + +/**************************************************************************/ +/*! + Indicates whether the specified block number is the first block + in the sector (block 0 relative to the current sector) +*/ +/**************************************************************************/ +bool PN532::mifareclassic_IsFirstBlock (uint32_t uiBlock) +{ + // Test if we are in the small or big sectors + if (uiBlock < 128) + return ((uiBlock) % 4 == 0); + else + return ((uiBlock) % 16 == 0); +} + +/**************************************************************************/ +/*! + Indicates whether the specified block number is the sector trailer +*/ +/**************************************************************************/ +bool PN532::mifareclassic_IsTrailerBlock (uint32_t uiBlock) +{ + // Test if we are in the small or big sectors + if (uiBlock < 128) + return ((uiBlock + 1) % 4 == 0); + else + return ((uiBlock + 1) % 16 == 0); +} + +/**************************************************************************/ +/*! + Tries to authenticate a block of memory on a MIFARE card using the + INDATAEXCHANGE command. See section 7.3.8 of the PN532 User Manual + for more information on sending MIFARE and other commands. + + @param uid Pointer to a byte array containing the card UID + @param uidLen The length (in bytes) of the card's UID (Should + be 4 for MIFARE Classic) + @param blockNumber The block number to authenticate. (0..63 for + 1KB cards, and 0..255 for 4KB cards). + @param keyNumber Which key type to use during authentication + (0 = MIFARE_CMD_AUTH_A, 1 = MIFARE_CMD_AUTH_B) + @param keyData Pointer to a byte array containing the 6 bytes + key value + + @returns 1 if everything executed properly, 0 for an error +*/ +/**************************************************************************/ +uint8_t PN532::mifareclassic_AuthenticateBlock (uint8_t *uid, uint8_t uidLen, uint32_t blockNumber, uint8_t keyNumber, uint8_t *keyData) +{ + uint8_t i; + + // Hang on to the key and uid data + memcpy (_key, keyData, 6); + memcpy (_uid, uid, uidLen); + _uidLen = uidLen; + + // Prepare the authentication command // + pn532_packetbuffer[0] = PN532_COMMAND_INDATAEXCHANGE; /* Data Exchange Header */ + pn532_packetbuffer[1] = 1; /* Max card numbers */ + pn532_packetbuffer[2] = (keyNumber) ? MIFARE_CMD_AUTH_B : MIFARE_CMD_AUTH_A; + pn532_packetbuffer[3] = blockNumber; /* Block Number (1K = 0..63, 4K = 0..255 */ + memcpy (pn532_packetbuffer + 4, _key, 6); + for (i = 0; i < _uidLen; i++) { + pn532_packetbuffer[10 + i] = _uid[i]; /* 4 bytes card ID */ + } + + if (HAL(writeCommand)(pn532_packetbuffer, 10 + _uidLen)) + return 0; + + // Read the response packet + HAL(readResponse)(pn532_packetbuffer, sizeof(pn532_packetbuffer)); + + // Check if the response is valid and we are authenticated??? + // for an auth success it should be bytes 5-7: 0xD5 0x41 0x00 + // Mifare auth error is technically byte 7: 0x14 but anything other and 0x00 is not good + if (pn532_packetbuffer[0] != 0x00) { + DMSG("Authentification failed\n"); + return 0; + } + + return 1; +} + +/**************************************************************************/ +/*! + Tries to read an entire 16-bytes data block at the specified block + address. + + @param blockNumber The block number to authenticate. (0..63 for + 1KB cards, and 0..255 for 4KB cards). + @param data Pointer to the byte array that will hold the + retrieved data (if any) + + @returns 1 if everything executed properly, 0 for an error +*/ +/**************************************************************************/ +uint8_t PN532::mifareclassic_ReadDataBlock (uint8_t blockNumber, uint8_t *data) +{ + DMSG("Trying to read 16 bytes from block "); + DMSG_INT(blockNumber); + + /* Prepare the command */ + pn532_packetbuffer[0] = PN532_COMMAND_INDATAEXCHANGE; + pn532_packetbuffer[1] = 1; /* Card number */ + pn532_packetbuffer[2] = MIFARE_CMD_READ; /* Mifare Read command = 0x30 */ + pn532_packetbuffer[3] = blockNumber; /* Block Number (0..63 for 1K, 0..255 for 4K) */ + + /* Send the command */ + if (HAL(writeCommand)(pn532_packetbuffer, 4)) { + return 0; + } + + /* Read the response packet */ + HAL(readResponse)(pn532_packetbuffer, sizeof(pn532_packetbuffer)); + + /* If byte 8 isn't 0x00 we probably have an error */ + if (pn532_packetbuffer[0] != 0x00) { + return 0; + } + + /* Copy the 16 data bytes to the output buffer */ + /* Block content starts at byte 9 of a valid response */ + memcpy (data, pn532_packetbuffer + 1, 16); + + return 1; +} + +/**************************************************************************/ +/*! + Tries to write an entire 16-bytes data block at the specified block + address. + + @param blockNumber The block number to authenticate. (0..63 for + 1KB cards, and 0..255 for 4KB cards). + @param data The byte array that contains the data to write. + + @returns 1 if everything executed properly, 0 for an error +*/ +/**************************************************************************/ +uint8_t PN532::mifareclassic_WriteDataBlock (uint8_t blockNumber, uint8_t *data) +{ + /* Prepare the first command */ + pn532_packetbuffer[0] = PN532_COMMAND_INDATAEXCHANGE; + pn532_packetbuffer[1] = 1; /* Card number */ + pn532_packetbuffer[2] = MIFARE_CMD_WRITE; /* Mifare Write command = 0xA0 */ + pn532_packetbuffer[3] = blockNumber; /* Block Number (0..63 for 1K, 0..255 for 4K) */ + memcpy (pn532_packetbuffer + 4, data, 16); /* Data Payload */ + + /* Send the command */ + if (HAL(writeCommand)(pn532_packetbuffer, 20)) { + return 0; + } + + /* Read the response packet */ + if (0 > HAL(readResponse)(pn532_packetbuffer, sizeof(pn532_packetbuffer))) { + return 0; + } + + /* Check status */ + if (pn532_packetbuffer[0] != 0x00) { + DMSG("Status code indicates an error: "); + DMSG_HEX(pn532_packetbuffer[0]); + DMSG("\n"); + return 0; + } + + return 1; +} + +/**************************************************************************/ +/*! + Formats a Mifare Classic card to store NDEF Records + + @returns 1 if everything executed properly, 0 for an error +*/ +/**************************************************************************/ +uint8_t PN532::mifareclassic_FormatNDEF (void) +{ + uint8_t sectorbuffer1[16] = {0x14, 0x01, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1}; + uint8_t sectorbuffer2[16] = {0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1}; + uint8_t sectorbuffer3[16] = {0xA0, 0xA1, 0xA2, 0xA3, 0xA4, 0xA5, 0x78, 0x77, 0x88, 0xC1, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; + + // Note 0xA0 0xA1 0xA2 0xA3 0xA4 0xA5 must be used for key A + // for the MAD sector in NDEF records (sector 0) + + // Write block 1 and 2 to the card + if (!(mifareclassic_WriteDataBlock (1, sectorbuffer1))) + return 0; + if (!(mifareclassic_WriteDataBlock (2, sectorbuffer2))) + return 0; + // Write key A and access rights card + if (!(mifareclassic_WriteDataBlock (3, sectorbuffer3))) + return 0; + + // Seems that everything was OK (?!) + return 1; +} + +/**************************************************************************/ +/*! + Writes an NDEF URI Record to the specified sector (1..15) + + Note that this function assumes that the Mifare Classic card is + already formatted to work as an "NFC Forum Tag" and uses a MAD1 + file system. You can use the NXP TagWriter app on Android to + properly format cards for this. + + @param sectorNumber The sector that the URI record should be written + to (can be 1..15 for a 1K card) + @param uriIdentifier The uri identifier code (0 = none, 0x01 = + "http://www.", etc.) + @param url The uri text to write (max 38 characters). + + @returns 1 if everything executed properly, 0 for an error +*/ +/**************************************************************************/ +uint8_t PN532::mifareclassic_WriteNDEFURI (uint8_t sectorNumber, uint8_t uriIdentifier, const char *url) +{ + // Figure out how long the string is + uint8_t len = strlen(url); + + // Make sure we're within a 1K limit for the sector number + if ((sectorNumber < 1) || (sectorNumber > 15)) + return 0; + + // Make sure the URI payload is between 1 and 38 chars + if ((len < 1) || (len > 38)) + return 0; + + // Note 0xD3 0xF7 0xD3 0xF7 0xD3 0xF7 must be used for key A + // in NDEF records + + // Setup the sector buffer (w/pre-formatted TLV wrapper and NDEF message) + uint8_t sectorbuffer1[16] = {0x00, 0x00, 0x03, len + 5, 0xD1, 0x01, len + 1, 0x55, uriIdentifier, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; + uint8_t sectorbuffer2[16] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; + uint8_t sectorbuffer3[16] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; + uint8_t sectorbuffer4[16] = {0xD3, 0xF7, 0xD3, 0xF7, 0xD3, 0xF7, 0x7F, 0x07, 0x88, 0x40, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; + if (len <= 6) { + // Unlikely we'll get a url this short, but why not ... + memcpy (sectorbuffer1 + 9, url, len); + sectorbuffer1[len + 9] = 0xFE; + } else if (len == 7) { + // 0xFE needs to be wrapped around to next block + memcpy (sectorbuffer1 + 9, url, len); + sectorbuffer2[0] = 0xFE; + } else if ((len > 7) && (len <= 22)) { + // Url fits in two blocks + memcpy (sectorbuffer1 + 9, url, 7); + memcpy (sectorbuffer2, url + 7, len - 7); + sectorbuffer2[len - 7] = 0xFE; + } else if (len == 23) { + // 0xFE needs to be wrapped around to final block + memcpy (sectorbuffer1 + 9, url, 7); + memcpy (sectorbuffer2, url + 7, len - 7); + sectorbuffer3[0] = 0xFE; + } else { + // Url fits in three blocks + memcpy (sectorbuffer1 + 9, url, 7); + memcpy (sectorbuffer2, url + 7, 16); + memcpy (sectorbuffer3, url + 23, len - 23); + sectorbuffer3[len - 23] = 0xFE; + } + + // Now write all three blocks back to the card + if (!(mifareclassic_WriteDataBlock (sectorNumber * 4, sectorbuffer1))) + return 0; + if (!(mifareclassic_WriteDataBlock ((sectorNumber * 4) + 1, sectorbuffer2))) + return 0; + if (!(mifareclassic_WriteDataBlock ((sectorNumber * 4) + 2, sectorbuffer3))) + return 0; + if (!(mifareclassic_WriteDataBlock ((sectorNumber * 4) + 3, sectorbuffer4))) + return 0; + + // Seems that everything was OK (?!) + return 1; +} + +/***** Mifare Ultralight Functions ******/ + +/**************************************************************************/ +/*! + Tries to read an entire 4-bytes page at the specified address. + + @param page The page number (0..63 in most cases) + @param buffer Pointer to the byte array that will hold the + retrieved data (if any) +*/ +/**************************************************************************/ +uint8_t PN532::mifareultralight_ReadPage (uint8_t page, uint8_t *buffer) +{ + /* Prepare the command */ + pn532_packetbuffer[0] = PN532_COMMAND_INDATAEXCHANGE; + pn532_packetbuffer[1] = 1; /* Card number */ + pn532_packetbuffer[2] = MIFARE_CMD_READ; /* Mifare Read command = 0x30 */ + pn532_packetbuffer[3] = page; /* Page Number (0..63 in most cases) */ + + /* Send the command */ + if (HAL(writeCommand)(pn532_packetbuffer, 4)) { + return 0; + } + + /* Read the response packet */ + HAL(readResponse)(pn532_packetbuffer, sizeof(pn532_packetbuffer)); + + /* If byte 8 isn't 0x00 we probably have an error */ + if (pn532_packetbuffer[0] == 0x00) { + /* Copy the 4 data bytes to the output buffer */ + /* Block content starts at byte 9 of a valid response */ + /* Note that the command actually reads 16 bytes or 4 */ + /* pages at a time ... we simply discard the last 12 */ + /* bytes */ + memcpy (buffer, pn532_packetbuffer + 1, 4); + } else { + return 0; + } + + // Return OK signal + return 1; +} + +/**************************************************************************/ +/*! + Tries to write an entire 4-bytes data buffer at the specified page + address. + + @param page The page number to write into. (0..63). + @param buffer The byte array that contains the data to write. + + @returns 1 if everything executed properly, 0 for an error +*/ +/**************************************************************************/ +uint8_t PN532::mifareultralight_WritePage (uint8_t page, uint8_t *buffer) +{ + /* Prepare the first command */ + pn532_packetbuffer[0] = PN532_COMMAND_INDATAEXCHANGE; + pn532_packetbuffer[1] = 1; /* Card number */ + pn532_packetbuffer[2] = MIFARE_CMD_WRITE_ULTRALIGHT; /* Mifare UL Write cmd = 0xA2 */ + pn532_packetbuffer[3] = page; /* page Number (0..63) */ + memcpy (pn532_packetbuffer + 4, buffer, 4); /* Data Payload */ + + /* Send the command */ + if (HAL(writeCommand)(pn532_packetbuffer, 8)) { + return 0; + } + + /* Read the response packet */ + return (0 < HAL(readResponse)(pn532_packetbuffer, sizeof(pn532_packetbuffer))); +} + +/**************************************************************************/ +/*! + @brief Exchanges an APDU with the currently inlisted peer + + @param send Pointer to data to send + @param sendLength Length of the data to send + @param response Pointer to response data + @param responseLength Pointer to the response data length +*/ +/**************************************************************************/ +bool PN532::inDataExchange(uint8_t *send, uint8_t sendLength, uint8_t *response, uint8_t *responseLength) +{ + uint8_t i; + + pn532_packetbuffer[0] = 0x40; // PN532_COMMAND_INDATAEXCHANGE; + pn532_packetbuffer[1] = inListedTag; + + if (HAL(writeCommand)(pn532_packetbuffer, 2, send, sendLength)) { + return false; + } + + int16_t status = HAL(readResponse)(response, *responseLength, 1000); + if (status < 0) { + return false; + } + + if ((response[0] & 0x3f) != 0) { + DMSG("Status code indicates an error\n"); + return false; + } + + uint8_t length = status; + length -= 1; + + if (length > *responseLength) { + length = *responseLength; // silent truncation... + } + + for (uint8_t i = 0; i < length; i++) { + response[i] = response[i + 1]; + } + *responseLength = length; + + return true; +} + +/**************************************************************************/ +/*! + This command is used to support basic data exchanges + between the PN532 and a target. + + @param send Pointer to the command buffer + @param sendLength Command length in bytes + @param response Pointer to response data + @param responseLength Pointer to the response data length +*/ +/**************************************************************************/ +bool PN532::inCommunicateThru(uint8_t *send, uint8_t sendLength, uint8_t *response, uint8_t *responseLength) +{ + pn532_packetbuffer[0] = PN532_COMMAND_INCOMMUNICATETHRU; + + if (HAL(writeCommand)(pn532_packetbuffer, 1, send, sendLength)) { + return false; + } + + int16_t status = HAL(readResponse)(response, *responseLength, 1000); + if (status < 0) { + return false; + } + + // check status code + if (response[0] != 0x0) { + DMSG("Status code indicates an error : 0x"); + DMSG_HEX(pn532_packetbuffer[0]); + DMSG("\n"); + return false; + } + + uint8_t length = status; + length -= 1; + + if (length > *responseLength) { + length = *responseLength; // silent truncation... + } + + for (uint8_t i = 0; i < length; i++) { + response[i] = response[i + 1]; + } + *responseLength = length; + + return true; +} + +/**************************************************************************/ +/*! + @brief 'InLists' a passive target. PN532 acting as reader/initiator, + peer acting as card/responder. +*/ +/**************************************************************************/ +bool PN532::inListPassiveTarget() +{ + pn532_packetbuffer[0] = PN532_COMMAND_INLISTPASSIVETARGET; + pn532_packetbuffer[1] = 1; + pn532_packetbuffer[2] = 0; + + DMSG("inList passive target\n"); + + if (HAL(writeCommand)(pn532_packetbuffer, 3)) { + return false; + } + + int16_t status = HAL(readResponse)(pn532_packetbuffer, sizeof(pn532_packetbuffer), 30000); + if (status < 0) { + return false; + } + + if (pn532_packetbuffer[0] != 1) { + return false; + } + + inListedTag = pn532_packetbuffer[1]; + + return true; +} + +int8_t PN532::tgInitAsTarget(const uint8_t* command, const uint8_t len, const uint16_t timeout){ + + int8_t status = HAL(writeCommand)(command, len); + if (status < 0) { + return -1; + } + + status = HAL(readResponse)(pn532_packetbuffer, sizeof(pn532_packetbuffer), timeout); + if (status > 0) { + return 1; + } else if (PN532_TIMEOUT == status) { + return 0; + } else { + return -2; + } +} + +/** + * Peer to Peer + */ +int8_t PN532::tgInitAsTarget(uint16_t timeout) +{ + const uint8_t command[] = { + PN532_COMMAND_TGINITASTARGET, + 0, + 0x00, 0x00, //SENS_RES + 0x00, 0x00, 0x00, //NFCID1 + 0x40, //SEL_RES + + 0x01, 0xFE, 0x0F, 0xBB, 0xBA, 0xA6, 0xC9, 0x89, // POL_RES + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xFF, 0xFF, + + 0x01, 0xFE, 0x0F, 0xBB, 0xBA, 0xA6, 0xC9, 0x89, 0x00, 0x00, //NFCID3t: Change this to desired value + + 0x0a, 0x46, 0x66, 0x6D, 0x01, 0x01, 0x10, 0x02, 0x02, 0x00, 0x80, // LLCP magic number, version parameter and MIUX + 0x00 + }; + return tgInitAsTarget(command, sizeof(command), timeout); +} + +int16_t PN532::tgGetData(uint8_t *buf, uint8_t len) +{ + buf[0] = PN532_COMMAND_TGGETDATA; + + if (HAL(writeCommand)(buf, 1)) { + return -1; + } + + int16_t status = HAL(readResponse)(buf, len, 3000); + if (0 >= status) { + return status; + } + + uint16_t length = status - 1; + + + if (buf[0] != 0) { + DMSG("status is not ok\n"); + return -5; + } + + for (uint8_t i = 0; i < length; i++) { + buf[i] = buf[i + 1]; + } + + return length; +} + +bool PN532::tgSetData(const uint8_t *header, uint8_t hlen, const uint8_t *body, uint8_t blen) +{ + if (hlen > (sizeof(pn532_packetbuffer) - 1)) { + if ((body != 0) || (header == pn532_packetbuffer)) { + DMSG("tgSetData:buffer too small\n"); + return false; + } + + pn532_packetbuffer[0] = PN532_COMMAND_TGSETDATA; + if (HAL(writeCommand)(pn532_packetbuffer, 1, header, hlen)) { + return false; + } + } else { + for (int8_t i = hlen - 1; i >= 0; i--){ + pn532_packetbuffer[i + 1] = header[i]; + } + pn532_packetbuffer[0] = PN532_COMMAND_TGSETDATA; + + if (HAL(writeCommand)(pn532_packetbuffer, hlen + 1, body, blen)) { + return false; + } + } + + if (0 > HAL(readResponse)(pn532_packetbuffer, sizeof(pn532_packetbuffer), 3000)) { + return false; + } + + if (0 != pn532_packetbuffer[0]) { + return false; + } + + return true; +} + +int16_t PN532::inRelease(const uint8_t relevantTarget){ + + pn532_packetbuffer[0] = PN532_COMMAND_INRELEASE; + pn532_packetbuffer[1] = relevantTarget; + + if (HAL(writeCommand)(pn532_packetbuffer, 2)) { + return 0; + } + + // read data packet + return HAL(readResponse)(pn532_packetbuffer, sizeof(pn532_packetbuffer)); +} + + +/***** FeliCa Functions ******/ +/**************************************************************************/ +/*! + @brief Poll FeliCa card. PN532 acting as reader/initiator, + peer acting as card/responder. + @param[in] systemCode Designation of System Code. When sending FFFFh as System Code, + all FeliCa cards can return response. + @param[in] requestCode Designation of Request Data as follows: + 00h: No Request + 01h: System Code request (to acquire System Code of the card) + 02h: Communication perfomance request + @param[out] idm IDm of the card (8 bytes) + @param[out] pmm PMm of the card (8 bytes) + @param[out] systemCodeResponse System Code of the card (Optional, 2bytes) + @return = 1: A FeliCa card has detected + = 0: No card has detected + < 0: error +*/ +/**************************************************************************/ +int8_t PN532::felica_Polling(uint16_t systemCode, uint8_t requestCode, uint8_t * idm, uint8_t * pmm, uint16_t *systemCodeResponse, uint16_t timeout) +{ + pn532_packetbuffer[0] = PN532_COMMAND_INLISTPASSIVETARGET; + pn532_packetbuffer[1] = 1; + pn532_packetbuffer[2] = 1; + pn532_packetbuffer[3] = FELICA_CMD_POLLING; + pn532_packetbuffer[4] = (systemCode >> 8) & 0xFF; + pn532_packetbuffer[5] = systemCode & 0xFF; + pn532_packetbuffer[6] = requestCode; + pn532_packetbuffer[7] = 0; + + if (HAL(writeCommand)(pn532_packetbuffer, 8)) { + DMSG("Could not send Polling command\n"); + return -1; + } + + int16_t status = HAL(readResponse)(pn532_packetbuffer, 22, timeout); + if (status < 0) { + DMSG("Could not receive response\n"); + return -2; + } + + // Check NbTg (pn532_packetbuffer[7]) + if (pn532_packetbuffer[0] == 0) { + DMSG("No card had detected\n"); + return 0; + } else if (pn532_packetbuffer[0] != 1) { + DMSG("Unhandled number of targets inlisted. NbTg: "); + DMSG_HEX(pn532_packetbuffer[7]); + DMSG("\n"); + return -3; + } + + inListedTag = pn532_packetbuffer[1]; + DMSG("Tag number: "); + DMSG_HEX(pn532_packetbuffer[1]); + DMSG("\n"); + + // length check + uint8_t responseLength = pn532_packetbuffer[2]; + if (responseLength != 18 && responseLength != 20) { + DMSG("Wrong response length\n"); + return -4; + } + + uint8_t i; + for (i=0; i<8; ++i) { + idm[i] = pn532_packetbuffer[4+i]; + _felicaIDm[i] = pn532_packetbuffer[4+i]; + pmm[i] = pn532_packetbuffer[12+i]; + _felicaPMm[i] = pn532_packetbuffer[12+i]; + } + + if ( responseLength == 20 ) { + *systemCodeResponse = (uint16_t)((pn532_packetbuffer[20] << 8) + pn532_packetbuffer[21]); + } + + return 1; +} + +/**************************************************************************/ +/*! + @brief Sends FeliCa command to the currently inlisted peer + + @param[in] command FeliCa command packet. (e.g. 00 FF FF 00 00 for Polling command) + @param[in] commandlength Length of the FeliCa command packet. (e.g. 0x05 for above Polling command ) + @param[out] response FeliCa response packet. (e.g. 01 NFCID2(8 bytes) PAD(8 bytes) for Polling response) + @param[out] responselength Length of the FeliCa response packet. (e.g. 0x11 for above Polling command ) + @return = 1: Success + < 0: error +*/ +/**************************************************************************/ +int8_t PN532::felica_SendCommand (const uint8_t *command, uint8_t commandlength, uint8_t *response, uint8_t *responseLength) +{ + if (commandlength > 0xFE) { + DMSG("Command length too long\n"); + return -1; + } + + pn532_packetbuffer[0] = 0x40; // PN532_COMMAND_INDATAEXCHANGE; + pn532_packetbuffer[1] = inListedTag; + pn532_packetbuffer[2] = commandlength + 1; + + if (HAL(writeCommand)(pn532_packetbuffer, 3, command, commandlength)) { + DMSG("Could not send FeliCa command\n"); + return -2; + } + + // Wait card response + int16_t status = HAL(readResponse)(pn532_packetbuffer, sizeof(pn532_packetbuffer), 200); + if (status < 0) { + DMSG("Could not receive response\n"); + return -3; + } + + // Check status (pn532_packetbuffer[0]) + if ((pn532_packetbuffer[0] & 0x3F)!=0) { + DMSG("Status code indicates an error: "); + DMSG_HEX(pn532_packetbuffer[0]); + DMSG("\n"); + return -4; + } + + // length check + *responseLength = pn532_packetbuffer[1] - 1; + if ( (status - 2) != *responseLength) { + DMSG("Wrong response length\n"); + return -5; + } + + memcpy(response, &pn532_packetbuffer[2], *responseLength); + + return 1; +} + + +/**************************************************************************/ +/*! + @brief Sends FeliCa Request Service command + + @param[in] numNode length of the nodeCodeList + @param[in] nodeCodeList Node codes(Big Endian) + @param[out] keyVersions Key Version of each Node (Big Endian) + @return = 1: Success + < 0: error +*/ +/**************************************************************************/ +int8_t PN532::felica_RequestService(uint8_t numNode, uint16_t *nodeCodeList, uint16_t *keyVersions) +{ + if (numNode > FELICA_REQ_SERVICE_MAX_NODE_NUM) { + DMSG("numNode is too large\n"); + return -1; + } + + uint8_t i, j=0; + uint8_t cmdLen = 1 + 8 + 1 + 2*numNode; + uint8_t cmd[cmdLen]; + cmd[j++] = FELICA_CMD_REQUEST_SERVICE; + for (i=0; i<8; ++i) { + cmd[j++] = _felicaIDm[i]; + } + cmd[j++] = numNode; + for (i=0; i> 8) & 0xff; + } + + uint8_t response[10+2*numNode]; + uint8_t responseLength; + + if (felica_SendCommand(cmd, cmdLen, response, &responseLength) != 1) { + DMSG("Request Service command failed\n"); + return -2; + } + + // length check + if ( responseLength != 10+2*numNode ) { + DMSG("Request Service command failed (wrong response length)\n"); + return -3; + } + + for(i=0; i FELICA_READ_MAX_SERVICE_NUM) { + DMSG("numService is too large\n"); + return -1; + } + if (numBlock > FELICA_READ_MAX_BLOCK_NUM) { + DMSG("numBlock is too large\n"); + return -2; + } + + uint8_t i, j=0, k; + uint8_t cmdLen = 1 + 8 + 1 + 2*numService + 1 + 2*numBlock; + uint8_t cmd[cmdLen]; + cmd[j++] = FELICA_CMD_READ_WITHOUT_ENCRYPTION; + for (i=0; i<8; ++i) { + cmd[j++] = _felicaIDm[i]; + } + cmd[j++] = numService; + for (i=0; i> 8) & 0xff; + } + cmd[j++] = numBlock; + for (i=0; i> 8) & 0xFF; + cmd[j++] = blockList[i] & 0xff; + } + + uint8_t response[12+16*numBlock]; + uint8_t responseLength; + if (felica_SendCommand(cmd, cmdLen, response, &responseLength) != 1) { + DMSG("Read Without Encryption command failed\n"); + return -3; + } + + // length check + if ( responseLength != 12+16*numBlock ) { + DMSG("Read Without Encryption command failed (wrong response length)\n"); + return -4; + } + + // status flag check + if ( response[9] != 0 || response[10] != 0 ) { + DMSG("Read Without Encryption command failed (Status Flag: "); + DMSG_HEX(pn532_packetbuffer[9]); + DMSG_HEX(pn532_packetbuffer[10]); + DMSG(")\n"); + return -5; + } + + k = 12; + for(i=0; i FELICA_WRITE_MAX_SERVICE_NUM) { + DMSG("numService is too large\n"); + return -1; + } + if (numBlock > FELICA_WRITE_MAX_BLOCK_NUM) { + DMSG("numBlock is too large\n"); + return -2; + } + + uint8_t i, j=0, k; + uint8_t cmdLen = 1 + 8 + 1 + 2*numService + 1 + 2*numBlock + 16 * numBlock; + uint8_t cmd[cmdLen]; + cmd[j++] = FELICA_CMD_WRITE_WITHOUT_ENCRYPTION; + for (i=0; i<8; ++i) { + cmd[j++] = _felicaIDm[i]; + } + cmd[j++] = numService; + for (i=0; i> 8) & 0xff; + } + cmd[j++] = numBlock; + for (i=0; i> 8) & 0xFF; + cmd[j++] = blockList[i] & 0xff; + } + for (i=0; i +#include "PN532Interface.h" + +// PN532 Commands +#define PN532_COMMAND_DIAGNOSE (0x00) +#define PN532_COMMAND_GETFIRMWAREVERSION (0x02) +#define PN532_COMMAND_GETGENERALSTATUS (0x04) +#define PN532_COMMAND_READREGISTER (0x06) +#define PN532_COMMAND_WRITEREGISTER (0x08) +#define PN532_COMMAND_READGPIO (0x0C) +#define PN532_COMMAND_WRITEGPIO (0x0E) +#define PN532_COMMAND_SETSERIALBAUDRATE (0x10) +#define PN532_COMMAND_SETPARAMETERS (0x12) +#define PN532_COMMAND_SAMCONFIGURATION (0x14) +#define PN532_COMMAND_POWERDOWN (0x16) +#define PN532_COMMAND_RFCONFIGURATION (0x32) +#define PN532_COMMAND_RFREGULATIONTEST (0x58) +#define PN532_COMMAND_INJUMPFORDEP (0x56) +#define PN532_COMMAND_INJUMPFORPSL (0x46) +#define PN532_COMMAND_INLISTPASSIVETARGET (0x4A) +#define PN532_COMMAND_INATR (0x50) +#define PN532_COMMAND_INPSL (0x4E) +#define PN532_COMMAND_INDATAEXCHANGE (0x40) +#define PN532_COMMAND_INCOMMUNICATETHRU (0x42) +#define PN532_COMMAND_INDESELECT (0x44) +#define PN532_COMMAND_INRELEASE (0x52) +#define PN532_COMMAND_INSELECT (0x54) +#define PN532_COMMAND_INAUTOPOLL (0x60) +#define PN532_COMMAND_TGINITASTARGET (0x8C) +#define PN532_COMMAND_TGSETGENERALBYTES (0x92) +#define PN532_COMMAND_TGGETDATA (0x86) +#define PN532_COMMAND_TGSETDATA (0x8E) +#define PN532_COMMAND_TGSETMETADATA (0x94) +#define PN532_COMMAND_TGGETINITIATORCOMMAND (0x88) +#define PN532_COMMAND_TGRESPONSETOINITIATOR (0x90) +#define PN532_COMMAND_TGGETTARGETSTATUS (0x8A) + +#define PN532_RESPONSE_INDATAEXCHANGE (0x41) +#define PN532_RESPONSE_INLISTPASSIVETARGET (0x4B) + + +#define PN532_MIFARE_ISO14443A (0x00) + +// Mifare Commands +#define MIFARE_CMD_AUTH_A (0x60) +#define MIFARE_CMD_AUTH_B (0x61) +#define MIFARE_CMD_READ (0x30) +#define MIFARE_CMD_WRITE (0xA0) +#define MIFARE_CMD_WRITE_ULTRALIGHT (0xA2) +#define MIFARE_CMD_TRANSFER (0xB0) +#define MIFARE_CMD_DECREMENT (0xC0) +#define MIFARE_CMD_INCREMENT (0xC1) +#define MIFARE_CMD_STORE (0xC2) + +// FeliCa Commands +#define FELICA_CMD_POLLING (0x00) +#define FELICA_CMD_REQUEST_SERVICE (0x02) +#define FELICA_CMD_REQUEST_RESPONSE (0x04) +#define FELICA_CMD_READ_WITHOUT_ENCRYPTION (0x06) +#define FELICA_CMD_WRITE_WITHOUT_ENCRYPTION (0x08) +#define FELICA_CMD_REQUEST_SYSTEM_CODE (0x0C) + +// Prefixes for NDEF Records (to identify record type) +#define NDEF_URIPREFIX_NONE (0x00) +#define NDEF_URIPREFIX_HTTP_WWWDOT (0x01) +#define NDEF_URIPREFIX_HTTPS_WWWDOT (0x02) +#define NDEF_URIPREFIX_HTTP (0x03) +#define NDEF_URIPREFIX_HTTPS (0x04) +#define NDEF_URIPREFIX_TEL (0x05) +#define NDEF_URIPREFIX_MAILTO (0x06) +#define NDEF_URIPREFIX_FTP_ANONAT (0x07) +#define NDEF_URIPREFIX_FTP_FTPDOT (0x08) +#define NDEF_URIPREFIX_FTPS (0x09) +#define NDEF_URIPREFIX_SFTP (0x0A) +#define NDEF_URIPREFIX_SMB (0x0B) +#define NDEF_URIPREFIX_NFS (0x0C) +#define NDEF_URIPREFIX_FTP (0x0D) +#define NDEF_URIPREFIX_DAV (0x0E) +#define NDEF_URIPREFIX_NEWS (0x0F) +#define NDEF_URIPREFIX_TELNET (0x10) +#define NDEF_URIPREFIX_IMAP (0x11) +#define NDEF_URIPREFIX_RTSP (0x12) +#define NDEF_URIPREFIX_URN (0x13) +#define NDEF_URIPREFIX_POP (0x14) +#define NDEF_URIPREFIX_SIP (0x15) +#define NDEF_URIPREFIX_SIPS (0x16) +#define NDEF_URIPREFIX_TFTP (0x17) +#define NDEF_URIPREFIX_BTSPP (0x18) +#define NDEF_URIPREFIX_BTL2CAP (0x19) +#define NDEF_URIPREFIX_BTGOEP (0x1A) +#define NDEF_URIPREFIX_TCPOBEX (0x1B) +#define NDEF_URIPREFIX_IRDAOBEX (0x1C) +#define NDEF_URIPREFIX_FILE (0x1D) +#define NDEF_URIPREFIX_URN_EPC_ID (0x1E) +#define NDEF_URIPREFIX_URN_EPC_TAG (0x1F) +#define NDEF_URIPREFIX_URN_EPC_PAT (0x20) +#define NDEF_URIPREFIX_URN_EPC_RAW (0x21) +#define NDEF_URIPREFIX_URN_EPC (0x22) +#define NDEF_URIPREFIX_URN_NFC (0x23) + +#define PN532_GPIO_VALIDATIONBIT (0x80) +#define PN532_GPIO_P30 (0) +#define PN532_GPIO_P31 (1) +#define PN532_GPIO_P32 (2) +#define PN532_GPIO_P33 (3) +#define PN532_GPIO_P34 (4) +#define PN532_GPIO_P35 (5) + +// FeliCa consts +#define FELICA_READ_MAX_SERVICE_NUM 16 +#define FELICA_READ_MAX_BLOCK_NUM 12 // for typical FeliCa card +#define FELICA_WRITE_MAX_SERVICE_NUM 16 +#define FELICA_WRITE_MAX_BLOCK_NUM 10 // for typical FeliCa card +#define FELICA_REQ_SERVICE_MAX_NODE_NUM 32 + +class PN532 +{ +public: + PN532(PN532Interface &interface); + + void begin(void); + + // Generic PN532 functions + bool SAMConfig(void); + uint32_t getFirmwareVersion(void); + uint32_t readRegister(uint16_t reg); + uint32_t writeRegister(uint16_t reg, uint8_t val); + bool writeGPIO(uint8_t pinstate); + uint8_t readGPIO(void); + bool setPassiveActivationRetries(uint8_t maxRetries); + bool setRFField(uint8_t autoRFCA, uint8_t rFOnOff); + bool powerDownMode(); + + /** + * @brief Init PN532 as a target + * @param timeout max time to wait, 0 means no timeout + * @return > 0 success + * = 0 timeout + * < 0 failed + */ + int8_t tgInitAsTarget(uint16_t timeout = 0); + int8_t tgInitAsTarget(const uint8_t* command, const uint8_t len, const uint16_t timeout = 0); + + int16_t tgGetData(uint8_t *buf, uint8_t len); + bool tgSetData(const uint8_t *header, uint8_t hlen, const uint8_t *body = 0, uint8_t blen = 0); + + int16_t inRelease(const uint8_t relevantTarget = 0); + + // ISO14443A functions + bool inListPassiveTarget(); + bool startPassiveTargetIDDetection(uint8_t cardbaudrate); + bool readPassiveTargetID(uint8_t cardbaudrate, uint8_t *uid, uint8_t *uidLength, uint16_t timeout = 1000, bool inlist = false); + bool inDataExchange(uint8_t *send, uint8_t sendLength, uint8_t *response, uint8_t *responseLength); + bool inCommunicateThru(uint8_t *send, uint8_t sendLength, uint8_t *response, uint8_t *responseLength); + + // Mifare Classic functions + bool mifareclassic_IsFirstBlock (uint32_t uiBlock); + bool mifareclassic_IsTrailerBlock (uint32_t uiBlock); + uint8_t mifareclassic_AuthenticateBlock (uint8_t *uid, uint8_t uidLen, uint32_t blockNumber, uint8_t keyNumber, uint8_t *keyData); + uint8_t mifareclassic_ReadDataBlock (uint8_t blockNumber, uint8_t *data); + uint8_t mifareclassic_WriteDataBlock (uint8_t blockNumber, uint8_t *data); + uint8_t mifareclassic_FormatNDEF (void); + uint8_t mifareclassic_WriteNDEFURI (uint8_t sectorNumber, uint8_t uriIdentifier, const char *url); + + // Mifare Ultralight functions + uint8_t mifareultralight_ReadPage (uint8_t page, uint8_t *buffer); + uint8_t mifareultralight_WritePage (uint8_t page, uint8_t *buffer); + + // FeliCa Functions + int8_t felica_Polling(uint16_t systemCode, uint8_t requestCode, uint8_t *idm, uint8_t *pmm, uint16_t *systemCodeResponse, uint16_t timeout=1000); + int8_t felica_SendCommand (const uint8_t * command, uint8_t commandlength, uint8_t * response, uint8_t * responseLength); + int8_t felica_RequestService(uint8_t numNode, uint16_t *nodeCodeList, uint16_t *keyVersions) ; + int8_t felica_RequestResponse(uint8_t *mode); + int8_t felica_ReadWithoutEncryption (uint8_t numService, const uint16_t *serviceCodeList, uint8_t numBlock, const uint16_t *blockList, uint8_t blockData[][16]); + int8_t felica_WriteWithoutEncryption (uint8_t numService, const uint16_t *serviceCodeList, uint8_t numBlock, const uint16_t *blockList, uint8_t blockData[][16]); + int8_t felica_RequestSystemCode(uint8_t *numSystemCode, uint16_t *systemCodeList); + int8_t felica_Release(); + + // Help functions to display formatted text + static void PrintHex(const uint8_t *data, const uint32_t numBytes); + static void PrintHexChar(const uint8_t *pbtData, const uint32_t numBytes); + + uint8_t *getBuffer(uint8_t *len) { + *len = sizeof(pn532_packetbuffer) - 4; + return pn532_packetbuffer; + }; + +private: + uint8_t _uid[7]; // ISO14443A uid + uint8_t _uidLen; // uid len + uint8_t _key[6]; // Mifare Classic key + uint8_t inListedTag; // Tg number of inlisted tag. + uint8_t _felicaIDm[8]; // FeliCa IDm (NFCID2) + uint8_t _felicaPMm[8]; // FeliCa PMm (PAD) + + uint8_t pn532_packetbuffer[64]; + + PN532Interface *_interface; +}; + +#endif diff --git a/lib/PN532/PN532Interface.h b/lib/PN532/PN532Interface.h new file mode 100644 index 0000000..14f590a --- /dev/null +++ b/lib/PN532/PN532Interface.h @@ -0,0 +1,56 @@ + + +#ifndef __PN532_INTERFACE_H__ +#define __PN532_INTERFACE_H__ + +#include + +#define PN532_PREAMBLE (0x00) +#define PN532_STARTCODE1 (0x00) +#define PN532_STARTCODE2 (0xFF) +#define PN532_POSTAMBLE (0x00) + +#define PN532_HOSTTOPN532 (0xD4) +#define PN532_PN532TOHOST (0xD5) + +#define PN532_ACK_WAIT_TIME (10) // ms, timeout of waiting for ACK + +#define PN532_INVALID_ACK (-1) +#define PN532_TIMEOUT (-2) +#define PN532_INVALID_FRAME (-3) +#define PN532_NO_SPACE (-4) + +#define REVERSE_BITS_ORDER(b) b = (b & 0xF0) >> 4 | (b & 0x0F) << 4; \ + b = (b & 0xCC) >> 2 | (b & 0x33) << 2; \ + b = (b & 0xAA) >> 1 | (b & 0x55) << 1 + +class PN532Interface +{ +public: + virtual void begin() = 0; + virtual void wakeup() = 0; + + /** + * @brief write a command and check ack + * @param header packet header + * @param hlen length of header + * @param body packet body + * @param blen length of body + * @return 0 success + * not 0 failed + */ + virtual int8_t writeCommand(const uint8_t *header, uint8_t hlen, const uint8_t *body = 0, uint8_t blen = 0) = 0; + + /** + * @brief read the response of a command, strip prefix and suffix + * @param buf to contain the response data + * @param len lenght to read + * @param timeout max time to wait, 0 means no timeout + * @return >=0 length of response without prefix and suffix + * <0 failed to read response + */ + virtual int16_t readResponse(uint8_t buf[], uint8_t len, uint16_t timeout = 1000) = 0; +}; + +#endif + diff --git a/lib/PN532/PN532_debug.h b/lib/PN532/PN532_debug.h new file mode 100644 index 0000000..a123a03 --- /dev/null +++ b/lib/PN532/PN532_debug.h @@ -0,0 +1,26 @@ +#ifndef __DEBUG_H__ +#define __DEBUG_H__ + +//#define DEBUG + +#include "Arduino.h" + +#ifdef ARDUINO_SAMD_VARIANT_COMPLIANCE + #define SERIAL SerialUSB +#else + #define SERIAL Serial +#endif + +#ifdef DEBUG +#define DMSG(args...) SERIAL.print(args) +#define DMSG_STR(str) SERIAL.println(str) +#define DMSG_HEX(num) SERIAL.print(' '); SERIAL.print((num>>4)&0x0F, HEX); SERIAL.print(num&0x0F, HEX) +#define DMSG_INT(num) SERIAL.print(' '); SERIAL.print(num) +#else +#define DMSG(args...) +#define DMSG_STR(str) +#define DMSG_HEX(num) +#define DMSG_INT(num) +#endif + +#endif diff --git a/lib/PN532/README.md b/lib/PN532/README.md new file mode 100644 index 0000000..8d9d4f6 --- /dev/null +++ b/lib/PN532/README.md @@ -0,0 +1,30 @@ +## NFC library for Arduino + +This is an Arduino library for PN532 to use NFC technology. +It works with + ++ [NFC Shield](http://goo.gl/Cac2OH) ++ [Xadow NFC](http://goo.gl/qBZMt0) ++ [PN532 NFC/RFID controller breakout board](http://goo.gl/tby9Sw) + +### Features ++ Support I2C, SPI and HSU of PN532 ++ Read/write Mifare Classic Card ++ Works with [Don's NDEF Library](http://goo.gl/jDjsXl) ++ Support Peer to Peer communication(exchange data with android 4.0+) ++ Support [mbed platform](http://goo.gl/kGPovZ) + +### Getting Started +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. +2. Downlaod [Don's NDEF library](http://goo.gl/ewxeAe) and extract it intro Arduino's libraries. +3. Follow the examples of the two libraries. + +### To do ++ Card emulation + +### Contribution +It's based on [Adafruit_NFCShield_I2C](http://goo.gl/pk3FdB). +[Seeed Studio](http://goo.gl/zh1iQh) adds SPI interface and peer to peer communication support. +@Don writes the [NDEF library](http://goo.gl/jDjsXl) to make it more easy to use. +@JiapengLi adds HSU interface. diff --git a/lib/PN532/emulatetag.cpp b/lib/PN532/emulatetag.cpp new file mode 100644 index 0000000..a862285 --- /dev/null +++ b/lib/PN532/emulatetag.cpp @@ -0,0 +1,298 @@ +/**************************************************************************/ +/*! + @file emulatetag.cpp + @author Armin Wieser + @license BSD +*/ +/**************************************************************************/ + +#include "emulatetag.h" +#include "PN532_debug.h" + +#include + +#define MAX_TGREAD + +// Command APDU +#define C_APDU_CLA 0 +#define C_APDU_INS 1 // instruction +#define C_APDU_P1 2 // parameter 1 +#define C_APDU_P2 3 // parameter 2 +#define C_APDU_LC 4 // length command +#define C_APDU_DATA 5 // data + +#define C_APDU_P1_SELECT_BY_ID 0x00 +#define C_APDU_P1_SELECT_BY_NAME 0x04 + +// Response APDU +#define R_APDU_SW1_COMMAND_COMPLETE 0x90 +#define R_APDU_SW2_COMMAND_COMPLETE 0x00 + +#define R_APDU_SW1_NDEF_TAG_NOT_FOUND 0x6a +#define R_APDU_SW2_NDEF_TAG_NOT_FOUND 0x82 + +#define R_APDU_SW1_FUNCTION_NOT_SUPPORTED 0x6A +#define R_APDU_SW2_FUNCTION_NOT_SUPPORTED 0x81 + +#define R_APDU_SW1_MEMORY_FAILURE 0x65 +#define R_APDU_SW2_MEMORY_FAILURE 0x81 + +#define R_APDU_SW1_END_OF_FILE_BEFORE_REACHED_LE_BYTES 0x62 +#define R_APDU_SW2_END_OF_FILE_BEFORE_REACHED_LE_BYTES 0x82 + +// ISO7816-4 commands +#define ISO7816_SELECT_FILE 0xA4 +#define ISO7816_READ_BINARY 0xB0 +#define ISO7816_UPDATE_BINARY 0xD6 + +typedef enum +{ + NONE, + CC, + NDEF +} tag_file; // CC ... Compatibility Container + +bool EmulateTag::init() +{ + pn532.begin(); + return pn532.SAMConfig(); +} + +void EmulateTag::setNdefFile(const uint8_t *ndef, const int16_t ndefLength) +{ + if (ndefLength > (NDEF_MAX_LENGTH - 2)) + { + DMSG("ndef file too large (> NDEF_MAX_LENGHT -2) - aborting"); + return; + } + + ndef_file[0] = ndefLength >> 8; + ndef_file[1] = ndefLength & 0xFF; + memcpy(ndef_file + 2, ndef, ndefLength); +} + +void EmulateTag::setUid(uint8_t *uid) +{ + uidPtr = uid; +} + +bool EmulateTag::emulate(const uint16_t tgInitAsTargetTimeout) +{ + + uint8_t command[] = { + PN532_COMMAND_TGINITASTARGET, + 5, // MODE: PICC only, Passive only + + 0x04, 0x00, // SENS_RES + 0x00, 0x00, 0x00, // NFCID1 + 0x20, // SEL_RES + + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, // FeliCaParams + 0, 0, + + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // NFCID3t + + 0, // length of general bytes + 0 // length of historical bytes + }; + + if (uidPtr != 0) + { // if uid is set copy 3 bytes to nfcid1 + memcpy(command + 4, uidPtr, 3); + } + + if (1 != pn532.tgInitAsTarget(command, sizeof(command), tgInitAsTargetTimeout)) + { + DMSG("tgInitAsTarget failed or timed out!"); + return false; + } + + uint8_t compatibility_container[] = { + 0, 0x0F, + 0x20, + 0, 0x54, + 0, 0xFF, + 0x04, // T + 0x06, // L + 0xE1, 0x04, // File identifier + ((NDEF_MAX_LENGTH & 0xFF00) >> 8), (NDEF_MAX_LENGTH & 0xFF), // maximum NDEF file size + 0x00, // read access 0x0 = granted + 0x00 // write access 0x0 = granted | 0xFF = deny + }; + + if (tagWriteable == false) + { + compatibility_container[14] = 0xFF; + } + + tagWrittenByInitiator = false; + + uint8_t rwbuf[128]; + uint8_t sendlen; + int16_t status; + tag_file currentFile = NONE; + uint16_t cc_size = sizeof(compatibility_container); + bool runLoop = true; + + while (runLoop) + { + status = pn532.tgGetData(rwbuf, sizeof(rwbuf)); + if (status < 0) + { + DMSG("tgGetData failed!\n"); + pn532.inRelease(); + return true; + } + + uint8_t p1 = rwbuf[C_APDU_P1]; + uint8_t p2 = rwbuf[C_APDU_P2]; + uint8_t lc = rwbuf[C_APDU_LC]; + uint16_t p1p2_length = ((int16_t)p1 << 8) + p2; + + switch (rwbuf[C_APDU_INS]) + { + case ISO7816_SELECT_FILE: + switch (p1) + { + case C_APDU_P1_SELECT_BY_ID: + if (p2 != 0x0c) + { + DMSG("C_APDU_P2 != 0x0c\n"); + setResponse(COMMAND_COMPLETE, rwbuf, &sendlen); + } + else if (lc == 2 && rwbuf[C_APDU_DATA] == 0xE1 && (rwbuf[C_APDU_DATA + 1] == 0x03 || rwbuf[C_APDU_DATA + 1] == 0x04)) + { + setResponse(COMMAND_COMPLETE, rwbuf, &sendlen); + if (rwbuf[C_APDU_DATA + 1] == 0x03) + { + currentFile = CC; + } + else if (rwbuf[C_APDU_DATA + 1] == 0x04) + { + currentFile = NDEF; + } + } + else + { + setResponse(TAG_NOT_FOUND, rwbuf, &sendlen); + } + break; + case C_APDU_P1_SELECT_BY_NAME: + const uint8_t ndef_tag_application_name_v2[] = {0, 0x7, 0xD2, 0x76, 0x00, 0x00, 0x85, 0x01, 0x01}; + if (0 == memcmp(ndef_tag_application_name_v2, rwbuf + C_APDU_P2, sizeof(ndef_tag_application_name_v2))) + { + setResponse(COMMAND_COMPLETE, rwbuf, &sendlen); + } + else + { + DMSG("function not supported\n"); + setResponse(FUNCTION_NOT_SUPPORTED, rwbuf, &sendlen); + } + break; + } + break; + case ISO7816_READ_BINARY: + switch (currentFile) + { + case NONE: + setResponse(TAG_NOT_FOUND, rwbuf, &sendlen); + break; + case CC: + if (p1p2_length > NDEF_MAX_LENGTH) + { + setResponse(END_OF_FILE_BEFORE_REACHED_LE_BYTES, rwbuf, &sendlen); + } + else + { + memcpy(rwbuf, compatibility_container + p1p2_length, lc); + setResponse(COMMAND_COMPLETE, rwbuf + lc, &sendlen, lc); + } + break; + case NDEF: + if (p1p2_length > NDEF_MAX_LENGTH) + { + setResponse(END_OF_FILE_BEFORE_REACHED_LE_BYTES, rwbuf, &sendlen); + } + else + { + memcpy(rwbuf, ndef_file + p1p2_length, lc); + setResponse(COMMAND_COMPLETE, rwbuf + lc, &sendlen, lc); + } + break; + } + break; + case ISO7816_UPDATE_BINARY: + if (!tagWriteable) + { + setResponse(FUNCTION_NOT_SUPPORTED, rwbuf, &sendlen); + } + else + { + if (p1p2_length > NDEF_MAX_LENGTH) + { + setResponse(MEMORY_FAILURE, rwbuf, &sendlen); + } + else + { + memcpy(ndef_file + p1p2_length, rwbuf + C_APDU_DATA, lc); + setResponse(COMMAND_COMPLETE, rwbuf, &sendlen); + tagWrittenByInitiator = true; + + uint16_t ndef_length = (ndef_file[0] << 8) + ndef_file[1]; + if ((ndef_length > 0) && (updateNdefCallback != 0)) + { + updateNdefCallback(ndef_file + 2, ndef_length); + } + } + } + break; + default: + DMSG("Command not supported!"); + DMSG_HEX(rwbuf[C_APDU_INS]); + DMSG("\n"); + setResponse(FUNCTION_NOT_SUPPORTED, rwbuf, &sendlen); + } + status = pn532.tgSetData(rwbuf, sendlen); + if (status < 0) + { + DMSG("tgSetData failed\n!"); + pn532.inRelease(); + return true; + } + } + pn532.inRelease(); + return true; +} + +void EmulateTag::setResponse(responseCommand cmd, uint8_t *buf, uint8_t *sendlen, uint8_t sendlenOffset) +{ + switch (cmd) + { + case COMMAND_COMPLETE: + buf[0] = R_APDU_SW1_COMMAND_COMPLETE; + buf[1] = R_APDU_SW2_COMMAND_COMPLETE; + *sendlen = 2 + sendlenOffset; + break; + case TAG_NOT_FOUND: + buf[0] = R_APDU_SW1_NDEF_TAG_NOT_FOUND; + buf[1] = R_APDU_SW2_NDEF_TAG_NOT_FOUND; + *sendlen = 2; + break; + case FUNCTION_NOT_SUPPORTED: + buf[0] = R_APDU_SW1_FUNCTION_NOT_SUPPORTED; + buf[1] = R_APDU_SW2_FUNCTION_NOT_SUPPORTED; + *sendlen = 2; + break; + case MEMORY_FAILURE: + buf[0] = R_APDU_SW1_MEMORY_FAILURE; + buf[1] = R_APDU_SW2_MEMORY_FAILURE; + *sendlen = 2; + break; + case END_OF_FILE_BEFORE_REACHED_LE_BYTES: + buf[0] = R_APDU_SW1_END_OF_FILE_BEFORE_REACHED_LE_BYTES; + buf[1] = R_APDU_SW2_END_OF_FILE_BEFORE_REACHED_LE_BYTES; + *sendlen = 2; + break; + } +} diff --git a/lib/PN532/emulatetag.h b/lib/PN532/emulatetag.h new file mode 100644 index 0000000..2cc7f7c --- /dev/null +++ b/lib/PN532/emulatetag.h @@ -0,0 +1,85 @@ +/**************************************************************************/ +/*! + @file PN532/PN532/emulatetag.h + @author Armin Wieser + @license BSD + + Implemented using NFC forum documents & library of libnfc +*/ +/**************************************************************************/ + +#ifndef __EMULATETAG_H__ +#define __EMULATETAG_H__ + +#include "PN532.h" + +#define NDEF_MAX_LENGTH 128 // altough ndef can handle up to 0xfffe in size, arduino cannot. +typedef enum +{ + COMMAND_COMPLETE, + TAG_NOT_FOUND, + FUNCTION_NOT_SUPPORTED, + MEMORY_FAILURE, + END_OF_FILE_BEFORE_REACHED_LE_BYTES +} responseCommand; + +class EmulateTag +{ + +public: + EmulateTag(PN532Interface &interface) : pn532(interface), uidPtr(0), tagWrittenByInitiator(false), tagWriteable(true), updateNdefCallback(0) {} + + bool init(); + + bool emulate(const uint16_t tgInitAsTargetTimeout = 0); + + /* + * @param uid pointer to byte array of length 3 (uid is 4 bytes - first byte is fixed) or zero for uid + */ + void setUid(uint8_t *uid = 0); + + void setNdefFile(const uint8_t *ndef, const int16_t ndefLength); + + void getContent(uint8_t **buf, uint16_t *length) + { + *buf = ndef_file + 2; // first 2 bytes = length + *length = (ndef_file[0] << 8) + ndef_file[1]; + } + + bool writeOccured() + { + return tagWrittenByInitiator; + } + + void setTagWriteable(bool setWriteable) + { + tagWriteable = setWriteable; + } + + uint8_t *getNdefFilePtr() + { + return ndef_file; + } + + uint8_t getNdefMaxLength() + { + return NDEF_MAX_LENGTH; + } + + void attach(void (*func)(uint8_t *buf, uint16_t length)) + { + updateNdefCallback = func; + }; + +private: + PN532 pn532; + uint8_t ndef_file[NDEF_MAX_LENGTH]; + uint8_t *uidPtr; + bool tagWrittenByInitiator; + bool tagWriteable; + void (*updateNdefCallback)(uint8_t *ndef, uint16_t length); + + void setResponse(responseCommand cmd, uint8_t *buf, uint8_t *sendlen, uint8_t sendlenOffset = 0); +}; + +#endif diff --git a/lib/PN532/examples/FeliCa_card_detection/FeliCa_card_detection.pde b/lib/PN532/examples/FeliCa_card_detection/FeliCa_card_detection.pde new file mode 100644 index 0000000..2f60bc3 --- /dev/null +++ b/lib/PN532/examples/FeliCa_card_detection/FeliCa_card_detection.pde @@ -0,0 +1,115 @@ +/**************************************************************************/ +/*! + This example will attempt to connect to an FeliCa + card or tag and retrieve some basic information about it + that can be used to determine what type of card it is. + + Note that you need the baud rate to be 115200 because we need to print + out the data and read from the card at the same time! + + To enable debug message, define DEBUG in PN532/PN532_debug.h + + */ +/**************************************************************************/ +#include + +#if 1 + #include + #include + #include + +PN532_SPI pn532spi(SPI, 10); +PN532 nfc(pn532spi); +#elif 0 + #include + #include + +PN532_HSU pn532hsu(Serial1); +PN532 nfc(pn532hsu); +#else + #include + #include + #include + +PN532_I2C pn532i2c(Wire); +PN532 nfc(pn532i2c); +#endif + +#include + +uint8_t _prevIDm[8]; +unsigned long _prevTime; + +void setup(void) +{ + Serial.begin(115200); + Serial.println("Hello!"); + + nfc.begin(); + + uint32_t versiondata = nfc.getFirmwareVersion(); + if (!versiondata) + { + Serial.print("Didn't find PN53x board"); + while (1) {delay(10);}; // halt + } + + // Got ok data, print it out! + Serial.print("Found chip PN5"); Serial.println((versiondata >> 24) & 0xFF, HEX); + Serial.print("Firmware ver. "); Serial.print((versiondata >> 16) & 0xFF, DEC); + Serial.print('.'); Serial.println((versiondata >> 8) & 0xFF, DEC); + + // Set the max number of retry attempts to read from a card + // This prevents us from waiting forever for a card, which is + // the default behaviour of the PN532. + nfc.setPassiveActivationRetries(0xFF); + nfc.SAMConfig(); + + memset(_prevIDm, 0, 8); +} + +void loop(void) +{ + uint8_t ret; + uint16_t systemCode = 0xFFFF; + uint8_t requestCode = 0x01; // System Code request + uint8_t idm[8]; + uint8_t pmm[8]; + uint16_t systemCodeResponse; + + // Wait for an FeliCa type cards. + // When one is found, some basic information such as IDm, PMm, and System Code are retrieved. + Serial.print("Waiting for an FeliCa card... "); + ret = nfc.felica_Polling(systemCode, requestCode, idm, pmm, &systemCodeResponse, 5000); + + if (ret != 1) + { + Serial.println("Could not find a card"); + delay(500); + return; + } + + if ( memcmp(idm, _prevIDm, 8) == 0 ) { + if ( (millis() - _prevTime) < 3000 ) { + Serial.println("Same card"); + delay(500); + return; + } + } + + Serial.println("Found a card!"); + Serial.print(" IDm: "); + nfc.PrintHex(idm, 8); + Serial.print(" PMm: "); + nfc.PrintHex(pmm, 8); + Serial.print(" System Code: "); + Serial.print(systemCodeResponse, HEX); + Serial.print("\n"); + + memcpy(_prevIDm, idm, 8); + _prevTime = millis(); + + // Wait 1 second before continuing + Serial.println("Card access completed!\n"); + delay(1000); +} diff --git a/lib/PN532/examples/FeliCa_card_read/FeliCa_card_read.pde b/lib/PN532/examples/FeliCa_card_read/FeliCa_card_read.pde new file mode 100644 index 0000000..6842de7 --- /dev/null +++ b/lib/PN532/examples/FeliCa_card_read/FeliCa_card_read.pde @@ -0,0 +1,165 @@ +/**************************************************************************/ +/*! + This example will attempt to connect to an FeliCa + card or tag and retrieve some basic information about it + that can be used to determine what type of card it is. + + Note that you need the baud rate to be 115200 because we need to print + out the data and read from the card at the same time! + + To enable debug message, define DEBUG in PN532/PN532_debug.h + + */ +/**************************************************************************/ +#include + +#if 1 + #include + #include + #include + +PN532_SPI pn532spi(SPI, 10); +PN532 nfc(pn532spi); +#elif 0 + #include + #include + +PN532_HSU pn532hsu(Serial1); +PN532 nfc(pn532hsu); +#else + #include + #include + #include + +PN532_I2C pn532i2c(Wire); +PN532 nfc(pn532i2c); +#endif + +#include + +uint8_t _prevIDm[8]; +unsigned long _prevTime; + +void PrintHex8(const uint8_t d) { + Serial.print(" "); + Serial.print( (d >> 4) & 0x0F, HEX); + Serial.print( d & 0x0F, HEX); +} + +void setup(void) +{ + Serial.begin(115200); + Serial.println("Hello!"); + + nfc.begin(); + + uint32_t versiondata = nfc.getFirmwareVersion(); + if (!versiondata) + { + Serial.print("Didn't find PN53x board"); + while (1) {delay(10);}; // halt + } + + // Got ok data, print it out! + Serial.print("Found chip PN5"); Serial.println((versiondata >> 24) & 0xFF, HEX); + Serial.print("Firmware ver. "); Serial.print((versiondata >> 16) & 0xFF, DEC); + Serial.print('.'); Serial.println((versiondata >> 8) & 0xFF, DEC); + + // Set the max number of retry attempts to read from a card + // This prevents us from waiting forever for a card, which is + // the default behaviour of the PN532. + nfc.setPassiveActivationRetries(0xFF); + nfc.SAMConfig(); + + memset(_prevIDm, 0, 8); +} + +void loop(void) +{ + uint8_t ret; + uint16_t systemCode = 0xFFFF; + uint8_t requestCode = 0x01; // System Code request + uint8_t idm[8]; + uint8_t pmm[8]; + uint16_t systemCodeResponse; + + // Wait for an FeliCa type cards. + // When one is found, some basic information such as IDm, PMm, and System Code are retrieved. + Serial.print("Waiting for an FeliCa card... "); + ret = nfc.felica_Polling(systemCode, requestCode, idm, pmm, &systemCodeResponse, 5000); + + if (ret != 1) + { + Serial.println("Could not find a card"); + delay(500); + return; + } + + if ( memcmp(idm, _prevIDm, 8) == 0 ) { + if ( (millis() - _prevTime) < 3000 ) { + Serial.println("Same card"); + delay(500); + return; + } + } + + Serial.println("Found a card!"); + Serial.print(" IDm: "); + nfc.PrintHex(idm, 8); + Serial.print(" PMm: "); + nfc.PrintHex(pmm, 8); + Serial.print(" System Code: "); + Serial.print(systemCodeResponse, HEX); + Serial.print("\n"); + + memcpy(_prevIDm, idm, 8); + _prevTime = millis(); + + uint8_t blockData[3][16]; + uint16_t serviceCodeList[1]; + uint16_t blockList[3]; + + Serial.println("Write Without Encryption command "); + serviceCodeList[0] = 0x0009; + blockList[0] = 0x8000; + unsigned long now = millis(); + blockData[0][3] = now & 0xFF; + blockData[0][2] = (now >>= 8) & 0xFF; + blockData[0][1] = (now >>= 8) & 0xFF; + blockData[0][0] = (now >>= 8) & 0xFF; + Serial.print(" Writing current millis ("); + PrintHex8(blockData[0][0]); + PrintHex8(blockData[0][1]); + PrintHex8(blockData[0][2]); + PrintHex8(blockData[0][3]); + Serial.print(" ) to Block 0 -> "); + ret = nfc.felica_WriteWithoutEncryption(1, serviceCodeList, 1, blockList, blockData); + if (ret != 1) + { + Serial.println("error"); + } else { + Serial.println("OK!"); + } + memset(blockData[0], 0, 16); + + Serial.print("Read Without Encryption command -> "); + serviceCodeList[0] = 0x000B; + blockList[0] = 0x8000; + blockList[1] = 0x8001; + blockList[2] = 0x8002; + ret = nfc.felica_ReadWithoutEncryption(1, serviceCodeList, 3, blockList, blockData); + if (ret != 1) + { + Serial.println("error"); + } else { + Serial.println("OK!"); + for(int i=0; i<3; i++ ) { + Serial.print(" Block no. "); Serial.print(i, DEC); Serial.print(": "); + nfc.PrintHex(blockData[i], 16); + } + } + + // Wait 1 second before continuing + Serial.println("Card access completed!\n"); + delay(1000); +} diff --git a/lib/PN532/examples/android_hce/android_hce.ino b/lib/PN532/examples/android_hce/android_hce.ino new file mode 100644 index 0000000..fd7cc24 --- /dev/null +++ b/lib/PN532/examples/android_hce/android_hce.ino @@ -0,0 +1,167 @@ +#if 0 +#include +#include +#include "PN532/PN532/PN532.h" + + PN532_SPI pn532spi(SPI, 10); + PN532 nfc(pn532spi); +#elif 1 +#include +#include + +PN532_HSU pn532hsu(Serial1); +PN532 nfc(pn532hsu); +#else +#include +#include +#include +#endif + +void setup() +{ + Serial.begin(115200); + Serial.println("-------Peer to Peer HCE--------"); + + nfc.begin(); + + uint32_t versiondata = nfc.getFirmwareVersion(); + if (!versiondata) + { + Serial.print("Didn't find PN53x board"); + while (1) + ; // halt + } + + // Got ok data, print it out! + Serial.print("Found chip PN5"); + Serial.println((versiondata >> 24) & 0xFF, HEX); + Serial.print("Firmware ver. "); + Serial.print((versiondata >> 16) & 0xFF, DEC); + Serial.print('.'); + Serial.println((versiondata >> 8) & 0xFF, DEC); + + // Set the max number of retry attempts to read from a card + // This prevents us from waiting forever for a card, which is + // the default behaviour of the PN532. + //nfc.setPassiveActivationRetries(0xFF); + + // configure board to read RFID tags + nfc.SAMConfig(); +} + +void loop() +{ + bool success; + + uint8_t responseLength = 32; + + Serial.println("Waiting for an ISO14443A card"); + + // set shield to inListPassiveTarget + success = nfc.inListPassiveTarget(); + + if (success) + { + + Serial.println("Found something!"); + + uint8_t selectApdu[] = {0x00, /* CLA */ + 0xA4, /* INS */ + 0x04, /* P1 */ + 0x00, /* P2 */ + 0x07, /* Length of AID */ + 0xF0, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, /* AID defined on Android App */ + 0x00 /* Le */}; + + uint8_t response[32]; + + success = nfc.inDataExchange(selectApdu, sizeof(selectApdu), response, &responseLength); + + if (success) + { + + Serial.print("responseLength: "); + Serial.println(responseLength); + + nfc.PrintHexChar(response, responseLength); + + do + { + uint8_t apdu[] = "Hello from Arduino"; + uint8_t back[32]; + uint8_t length = 32; + + success = nfc.inDataExchange(apdu, sizeof(apdu), back, &length); + + if (success) + { + + Serial.print("responseLength: "); + Serial.println(length); + + nfc.PrintHexChar(back, length); + } + else + { + + Serial.println("Broken connection?"); + } + } while (success); + } + else + { + + Serial.println("Failed sending SELECT AID"); + } + } + else + { + + Serial.println("Didn't find anything!"); + } + + delay(1000); +} + +void printResponse(uint8_t *response, uint8_t responseLength) +{ + + String respBuffer; + + for (int i = 0; i < responseLength; i++) + { + + if (response[i] < 0x10) + respBuffer = respBuffer + "0"; //Adds leading zeros if hex value is smaller than 0x10 + + respBuffer = respBuffer + String(response[i], HEX) + " "; + } + + Serial.print("response: "); + Serial.println(respBuffer); +} + +void setupNFC() +{ + + nfc.begin(); + + uint32_t versiondata = nfc.getFirmwareVersion(); + if (!versiondata) + { + Serial.print("Didn't find PN53x board"); + while (1) + ; // halt + } + + // Got ok data, print it out! + Serial.print("Found chip PN5"); + Serial.println((versiondata >> 24) & 0xFF, HEX); + Serial.print("Firmware ver. "); + Serial.print((versiondata >> 16) & 0xFF, DEC); + Serial.print('.'); + Serial.println((versiondata >> 8) & 0xFF, DEC); + + // configure board to read RFID tags + nfc.SAMConfig(); +} diff --git a/lib/PN532/examples/emulate_tag_ndef/emulate_tag_ndef.ino b/lib/PN532/examples/emulate_tag_ndef/emulate_tag_ndef.ino new file mode 100644 index 0000000..46a79f3 --- /dev/null +++ b/lib/PN532/examples/emulate_tag_ndef/emulate_tag_ndef.ino @@ -0,0 +1,83 @@ +#include "PN532/PN532/emulatetag.h" +#include "NdefMessage.h" + +#if 0 +#include +#include +#include "PN532/PN532/PN532.h" + + PN532_SPI pn532spi(SPI, 10); + EmulateTag nfc(pn532spi); +#elif 1 +#include +#include + +PN532_HSU pn532hsu(Serial1); +EmulateTag nfc(pn532hsu); +#endif + +uint8_t ndefBuf[120]; +NdefMessage message; +int messageSize; + +uint8_t uid[3] = {0x12, 0x34, 0x56}; + +void setup() +{ + Serial.begin(115200); + Serial.println("------- Emulate Tag --------"); + + message = NdefMessage(); + message.addUriRecord("http://www.seeedstudio.com"); + messageSize = message.getEncodedSize(); + if (messageSize > sizeof(ndefBuf)) + { + Serial.println("ndefBuf is too small"); + while (1) + { + } + } + + Serial.print("Ndef encoded message size: "); + Serial.println(messageSize); + + message.encode(ndefBuf); + + // comment out this command for no ndef message + nfc.setNdefFile(ndefBuf, messageSize); + + // uid must be 3 bytes! + nfc.setUid(uid); + + nfc.init(); +} + +void loop() +{ + // uncomment for overriding ndef in case a write to this tag occured + //nfc.setNdefFile(ndefBuf, messageSize); + + // start emulation (blocks) + nfc.emulate(); + + // or start emulation with timeout + /*if(!nfc.emulate(1000)){ // timeout 1 second + Serial.println("timed out"); + }*/ + + // deny writing to the tag + // nfc.setTagWriteable(false); + + if (nfc.writeOccured()) + { + Serial.println("\nWrite occured !"); + uint8_t *tag_buf; + uint16_t length; + + nfc.getContent(&tag_buf, &length); + NdefMessage msg = NdefMessage(tag_buf, length); + msg.print(); + } + + delay(1000); +} diff --git a/lib/PN532/examples/iso14443a_uid/iso14443a_uid.pde b/lib/PN532/examples/iso14443a_uid/iso14443a_uid.pde new file mode 100644 index 0000000..af5d71d --- /dev/null +++ b/lib/PN532/examples/iso14443a_uid/iso14443a_uid.pde @@ -0,0 +1,98 @@ +/**************************************************************************/ +/*! + This example will attempt to connect to an ISO14443A + card or tag and retrieve some basic information about it + that can be used to determine what type of card it is. + + Note that you need the baud rate to be 115200 because we need to print + out the data and read from the card at the same time! + + To enable debug message, define DEBUG in PN532/PN532_debug.h + +*/ +/**************************************************************************/ + + +/* When the number after #if set as 1, it will be switch to SPI Mode*/ +#if 0 + #include + #include + #include "PN532.h" + + PN532_SPI pn532spi(SPI, 10); + PN532 nfc(pn532spi); + +/* When the number after #elif set as 1, it will be switch to HSU Mode*/ +#elif 0 + #include + #include + + PN532_HSU pn532hsu(Serial1); + PN532 nfc(pn532hsu); + +/* When the number after #if & #elif set as 0, it will be switch to I2C Mode*/ +#else + #include + #include + #include + + PN532_I2C pn532i2c(Wire); + PN532 nfc(pn532i2c); +#endif + +void setup(void) { + Serial.begin(115200); + Serial.println("Hello!"); + + nfc.begin(); + + uint32_t versiondata = nfc.getFirmwareVersion(); + if (! versiondata) { + Serial.print("Didn't find PN53x board"); + while (1); // halt + } + + // Got ok data, print it out! + Serial.print("Found chip PN5"); Serial.println((versiondata>>24) & 0xFF, HEX); + Serial.print("Firmware ver. "); Serial.print((versiondata>>16) & 0xFF, DEC); + Serial.print('.'); Serial.println((versiondata>>8) & 0xFF, DEC); + + // Set the max number of retry attempts to read from a card + // This prevents us from waiting forever for a card, which is + // the default behaviour of the PN532. + nfc.setPassiveActivationRetries(0xFF); + + // configure board to read RFID tags + nfc.SAMConfig(); + + Serial.println("Waiting for an ISO14443A card"); +} + +void loop(void) { + boolean success; + uint8_t uid[] = { 0, 0, 0, 0, 0, 0, 0 }; // Buffer to store the returned UID + uint8_t uidLength; // Length of the UID (4 or 7 bytes depending on ISO14443A card type) + + // Wait for an ISO14443A type cards (Mifare, etc.). When one is found + // 'uid' will be populated with the UID, and uidLength will indicate + // if the uid is 4 bytes (Mifare Classic) or 7 bytes (Mifare Ultralight) + success = nfc.readPassiveTargetID(PN532_MIFARE_ISO14443A, &uid[0], &uidLength); + + if (success) { + Serial.println("Found a card!"); + Serial.print("UID Length: ");Serial.print(uidLength, DEC);Serial.println(" bytes"); + Serial.print("UID Value: "); + for (uint8_t i=0; i < uidLength; i++) + { + Serial.print(" 0x");Serial.print(uid[i], HEX); + } + Serial.println(""); + // Wait 1 second before continuing + delay(1000); + } + else + { + // PN532 probably timed out waiting for a card + Serial.println("Timed out waiting for a card"); + } +} diff --git a/lib/PN532/examples/mifareclassic_formatndef/mifareclassic_formatndef.pde b/lib/PN532/examples/mifareclassic_formatndef/mifareclassic_formatndef.pde new file mode 100644 index 0000000..af36a36 --- /dev/null +++ b/lib/PN532/examples/mifareclassic_formatndef/mifareclassic_formatndef.pde @@ -0,0 +1,181 @@ +/**************************************************************************/ +/*! + This example attempts to format a clean Mifare Classic 1K card as + an NFC Forum tag (to store NDEF messages that can be read by any + NFC enabled Android phone, etc.) + + Note that you need the baud rate to be 115200 because we need to print + out the data and read from the card at the same time! + + To enable debug message, define DEBUG in PN532/PN532_debug.h +*/ +/**************************************************************************/ + +#if 0 + #include + #include + #include "PN532.h" + + PN532_SPI pn532spi(SPI, 10); + PN532 nfc(pn532spi); +#elif 1 + #include + #include + + PN532_HSU pn532hsu(Serial1); + PN532 nfc(pn532hsu); +#else + #include + #include + #include +#endif +/* + We can encode many different kinds of pointers to the card, + from a URL, to an Email address, to a phone number, and many more + check the library header .h file to see the large # of supported + prefixes! +*/ +// For a http://www. url: +const char * url = "elechouse.com"; +uint8_t ndefprefix = NDEF_URIPREFIX_HTTP_WWWDOT; + +// for an email address +//const char * url = "mail@example.com"; +//uint8_t ndefprefix = NDEF_URIPREFIX_MAILTO; + +// for a phone number +//const char * url = "+1 212 555 1212"; +//uint8_t ndefprefix = NDEF_URIPREFIX_TEL; + + +void setup(void) { + Serial.begin(115200); + Serial.println("Looking for PN532..."); + + nfc.begin(); + + uint32_t versiondata = nfc.getFirmwareVersion(); + if (! versiondata) { + Serial.print("Didn't find PN53x board"); + while (1); // halt + } + + // Got ok data, print it out! + Serial.print("Found chip PN5"); Serial.println((versiondata>>24) & 0xFF, HEX); + Serial.print("Firmware ver. "); Serial.print((versiondata>>16) & 0xFF, DEC); + Serial.print('.'); Serial.println((versiondata>>8) & 0xFF, DEC); + + // configure board to read RFID tags + nfc.SAMConfig(); +} + +void loop(void) { + uint8_t success; // Flag to check if there was an error with the PN532 + uint8_t uid[] = { 0, 0, 0, 0, 0, 0, 0 }; // Buffer to store the returned UID + uint8_t uidLength; // Length of the UID (4 or 7 bytes depending on ISO14443A card type) + bool authenticated = false; // Flag to indicate if the sector is authenticated + + // Use the default key + uint8_t keya[6] = { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }; + + Serial.println(""); + Serial.println("PLEASE NOTE: Formatting your card for NDEF records will change the"); + Serial.println("authentication keys. To reformat your NDEF tag as a clean Mifare"); + Serial.println("Classic tag, use the mifareclassic_ndeftoclassic example!"); + Serial.println(""); + Serial.println("Place your Mifare Classic card on the reader to format with NDEF"); + Serial.println("and press any key to continue ..."); + // Wait for user input before proceeding + while (!Serial.available()); + // a key was pressed1 + while (Serial.available()) Serial.read(); + + // Wait for an ISO14443A type card (Mifare, etc.). When one is found + // 'uid' will be populated with the UID, and uidLength will indicate + // if the uid is 4 bytes (Mifare Classic) or 7 bytes (Mifare Ultralight) + success = nfc.readPassiveTargetID(PN532_MIFARE_ISO14443A, uid, &uidLength); + + if (success) + { + // Display some basic information about the card + Serial.println("Found an ISO14443A card"); + Serial.print(" UID Length: ");Serial.print(uidLength, DEC);Serial.println(" bytes"); + Serial.print(" UID Value: "); + nfc.PrintHex(uid, uidLength); + for (uint8_t i = 0; i < uidLength; i++) { + Serial.print(uid[i], HEX); + Serial.print(' '); + } + Serial.println(""); + + // Make sure this is a Mifare Classic card + if (uidLength != 4) + { + Serial.println("Ooops ... this doesn't seem to be a Mifare Classic card!"); + return; + } + + // We probably have a Mifare Classic card ... + Serial.println("Seems to be a Mifare Classic card (4 byte UID)"); + + // Try to format the card for NDEF data + success = nfc.mifareclassic_AuthenticateBlock (uid, uidLength, 0, 0, keya); + if (!success) + { + Serial.println("Unable to authenticate block 0 to enable card formatting!"); + return; + } + success = nfc.mifareclassic_FormatNDEF(); + if (!success) + { + Serial.println("Unable to format the card for NDEF"); + return; + } + + Serial.println("Card has been formatted for NDEF data using MAD1"); + + // Try to authenticate block 4 (first block of sector 1) using our key + success = nfc.mifareclassic_AuthenticateBlock (uid, uidLength, 4, 0, keya); + + // Make sure the authentification process didn't fail + if (!success) + { + Serial.println("Authentication failed."); + return; + } + + // Try to write a URL + Serial.println("Writing URI to sector 1 as an NDEF Message"); + + // Authenticated seems to have worked + // Try to write an NDEF record to sector 1 + // Use 0x01 for the URI Identifier Code to prepend "http://www." + // to the url (and save some space). For information on URI ID Codes + // see http://www.ladyada.net/wiki/private/articlestaging/nfc/ndef + if (strlen(url) > 38) + { + // The length is also checked in the WriteNDEFURI function, but lets + // warn users here just in case they change the value and it's bigger + // than it should be + Serial.println("URI is too long ... must be less than 38 characters long"); + return; + } + + // URI is within size limits ... write it to the card and report success/failure + success = nfc.mifareclassic_WriteNDEFURI(1, ndefprefix, url); + if (success) + { + Serial.println("NDEF URI Record written to sector 1"); + } + else + { + Serial.println("NDEF Record creation failed! :("); + } + } + + // Wait a bit before trying again + Serial.println("\n\nDone!"); + delay(1000); + Serial.flush(); + while(Serial.available()) Serial.read(); +} \ No newline at end of file diff --git a/lib/PN532/examples/mifareclassic_memdump/mifareclassic_memdump.pde b/lib/PN532/examples/mifareclassic_memdump/mifareclassic_memdump.pde new file mode 100644 index 0000000..d242582 --- /dev/null +++ b/lib/PN532/examples/mifareclassic_memdump/mifareclassic_memdump.pde @@ -0,0 +1,172 @@ +/**************************************************************************/ +/*! + This example attempts to dump the contents of a Mifare Classic 1K card + + Note that you need the baud rate to be 115200 because we need to print + out the data and read from the card at the same time! + + To enable debug message, define DEBUG in PN532/PN532_debug.h +*/ +/**************************************************************************/ + +#if 0 + #include + #include + #include "PN532.h" + + PN532_SPI pn532spi(SPI, 10); + PN532 nfc(pn532spi); +#elif 1 + #include + #include + + PN532_HSU pn532hsu(Serial1); + PN532 nfc(pn532hsu); +#else + #include + #include + #include + + PN532_I2C pn532i2c(Wire); + PN532 nfc(pn532i2c); +#endif + +void setup(void) { + // has to be fast to dump the entire memory contents! + Serial.begin(115200); + Serial.println("Looking for PN532..."); + + nfc.begin(); + + uint32_t versiondata = nfc.getFirmwareVersion(); + if (! versiondata) { + Serial.print("Didn't find PN53x board"); + while (1); // halt + } + // Got ok data, print it out! + Serial.print("Found chip PN5"); Serial.println((versiondata>>24) & 0xFF, HEX); + Serial.print("Firmware ver. "); Serial.print((versiondata>>16) & 0xFF, DEC); + Serial.print('.'); Serial.println((versiondata>>8) & 0xFF, DEC); + + // configure board to read RFID tags + nfc.SAMConfig(); + + Serial.println("Waiting for an ISO14443A Card ..."); +} + + +void loop(void) { + uint8_t success; // Flag to check if there was an error with the PN532 + uint8_t uid[] = { 0, 0, 0, 0, 0, 0, 0 }; // Buffer to store the returned UID + uint8_t uidLength; // Length of the UID (4 or 7 bytes depending on ISO14443A card type) + uint8_t currentblock; // Counter to keep track of which block we're on + bool authenticated = false; // Flag to indicate if the sector is authenticated + uint8_t data[16]; // Array to store block data during reads + + // Keyb on NDEF and Mifare Classic should be the same + uint8_t keyuniversal[6] = { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }; + + // Wait for an ISO14443A type cards (Mifare, etc.). When one is found + // 'uid' will be populated with the UID, and uidLength will indicate + // if the uid is 4 bytes (Mifare Classic) or 7 bytes (Mifare Ultralight) + success = nfc.readPassiveTargetID(PN532_MIFARE_ISO14443A, uid, &uidLength); + + if (success) { + // Display some basic information about the card + Serial.println("Found an ISO14443A card"); + Serial.print(" UID Length: ");Serial.print(uidLength, DEC);Serial.println(" bytes"); + Serial.print(" UID Value: "); + for (uint8_t i = 0; i < uidLength; i++) { + Serial.print(uid[i], HEX); + Serial.print(' '); + } + Serial.println(""); + + if (uidLength == 4) + { + // We probably have a Mifare Classic card ... + Serial.println("Seems to be a Mifare Classic card (4 byte UID)"); + + // Now we try to go through all 16 sectors (each having 4 blocks) + // authenticating each sector, and then dumping the blocks + for (currentblock = 0; currentblock < 64; currentblock++) + { + // Check if this is a new block so that we can reauthenticate + if (nfc.mifareclassic_IsFirstBlock(currentblock)) authenticated = false; + + // If the sector hasn't been authenticated, do so first + if (!authenticated) + { + // Starting of a new sector ... try to to authenticate + Serial.print("------------------------Sector ");Serial.print(currentblock/4, DEC);Serial.println("-------------------------"); + if (currentblock == 0) + { + // This will be 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF for Mifare Classic (non-NDEF!) + // or 0xA0 0xA1 0xA2 0xA3 0xA4 0xA5 for NDEF formatted cards using key a, + // but keyb should be the same for both (0xFF 0xFF 0xFF 0xFF 0xFF 0xFF) + success = nfc.mifareclassic_AuthenticateBlock (uid, uidLength, currentblock, 1, keyuniversal); + } + else + { + // This will be 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF for Mifare Classic (non-NDEF!) + // or 0xD3 0xF7 0xD3 0xF7 0xD3 0xF7 for NDEF formatted cards using key a, + // but keyb should be the same for both (0xFF 0xFF 0xFF 0xFF 0xFF 0xFF) + success = nfc.mifareclassic_AuthenticateBlock (uid, uidLength, currentblock, 1, keyuniversal); + } + if (success) + { + authenticated = true; + } + else + { + Serial.println("Authentication error"); + } + } + // If we're still not authenticated just skip the block + if (!authenticated) + { + Serial.print("Block ");Serial.print(currentblock, DEC);Serial.println(" unable to authenticate"); + } + else + { + // Authenticated ... we should be able to read the block now + // Dump the data into the 'data' array + success = nfc.mifareclassic_ReadDataBlock(currentblock, data); + if (success) + { + // Read successful + Serial.print("Block ");Serial.print(currentblock, DEC); + if (currentblock < 10) + { + Serial.print(" "); + } + else + { + Serial.print(" "); + } + // Dump the raw data + nfc.PrintHexChar(data, 16); + } + else + { + // Oops ... something happened + Serial.print("Block ");Serial.print(currentblock, DEC); + Serial.println(" unable to read this block"); + } + } + } + } + else + { + Serial.println("Ooops ... this doesn't seem to be a Mifare Classic card!"); + } + } + // Wait a bit before trying again + Serial.println("\n\nSend a character to run the mem dumper again!"); + Serial.flush(); + while (!Serial.available()); + while (Serial.available()) { + Serial.read(); + } + Serial.flush(); +} \ No newline at end of file diff --git a/lib/PN532/examples/mifareclassic_ndeftoclassic/mifareclassic_ndeftoclassic.pde b/lib/PN532/examples/mifareclassic_ndeftoclassic/mifareclassic_ndeftoclassic.pde new file mode 100644 index 0000000..e67740e --- /dev/null +++ b/lib/PN532/examples/mifareclassic_ndeftoclassic/mifareclassic_ndeftoclassic.pde @@ -0,0 +1,183 @@ +/**************************************************************************/ +/*! + This examples attempts to take a Mifare Classic 1K card that has been + formatted for NDEF messages using mifareclassic_formatndef, and resets + the authentication keys back to the Mifare Classic defaults + + To enable debug message, define DEBUG in PN532/PN532_debug.h +*/ +/**************************************************************************/ + +#if 0 + #include + #include + #include "PN532.h" + + PN532_SPI pn532spi(SPI, 10); + PN532 nfc(pn532spi); +#elif 1 + #include + #include + + PN532_HSU pn532hsu(Serial1); + PN532 nfc(pn532hsu); +#else + #include + #include + #include +#endif + +#define NR_SHORTSECTOR (32) // Number of short sectors on Mifare 1K/4K +#define NR_LONGSECTOR (8) // Number of long sectors on Mifare 4K +#define NR_BLOCK_OF_SHORTSECTOR (4) // Number of blocks in a short sector +#define NR_BLOCK_OF_LONGSECTOR (16) // Number of blocks in a long sector + +// Determine the sector trailer block based on sector number +#define BLOCK_NUMBER_OF_SECTOR_TRAILER(sector) (((sector)>24) & 0xFF, HEX); + Serial.print("Firmware ver. "); Serial.print((versiondata>>16) & 0xFF, DEC); + Serial.print('.'); Serial.println((versiondata>>8) & 0xFF, DEC); + + // configure board to read RFID tags + nfc.SAMConfig(); +} + +void loop(void) { + uint8_t success; // Flag to check if there was an error with the PN532 + uint8_t uid[] = { 0, 0, 0, 0, 0, 0, 0 }; // Buffer to store the returned UID + uint8_t uidLength; // Length of the UID (4 or 7 bytes depending on ISO14443A card type) + bool authenticated = false; // Flag to indicate if the sector is authenticated + uint8_t blockBuffer[16]; // Buffer to store block contents + uint8_t blankAccessBits[3] = { 0xff, 0x07, 0x80 }; + uint8_t idx = 0; + uint8_t numOfSector = 16; // Assume Mifare Classic 1K for now (16 4-block sectors) + + Serial.println("Place your NDEF formatted Mifare Classic 1K card on the reader"); + Serial.println("and press any key to continue ..."); + + // Wait for user input before proceeding + while (!Serial.available()); + while (Serial.available()) Serial.read(); + + // Wait for an ISO14443A type card (Mifare, etc.). When one is found + // 'uid' will be populated with the UID, and uidLength will indicate + // if the uid is 4 bytes (Mifare Classic) or 7 bytes (Mifare Ultralight) + success = nfc.readPassiveTargetID(PN532_MIFARE_ISO14443A, uid, &uidLength); + + if (success) + { + // We seem to have a tag ... + // Display some basic information about it + Serial.println("Found an ISO14443A card/tag"); + Serial.print(" UID Length: ");Serial.print(uidLength, DEC);Serial.println(" bytes"); + Serial.print(" UID Value: "); + nfc.PrintHex(uid, uidLength); + Serial.println(""); + + // Make sure this is a Mifare Classic card + if (uidLength != 4) + { + Serial.println("Ooops ... this doesn't seem to be a Mifare Classic card!"); + return; + } + + Serial.println("Seems to be a Mifare Classic card (4 byte UID)"); + Serial.println(""); + Serial.println("Reformatting card for Mifare Classic (please don't touch it!) ... "); + + // Now run through the card sector by sector + for (idx = 0; idx < numOfSector; idx++) + { + // Step 1: Authenticate the current sector using key B 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF + success = nfc.mifareclassic_AuthenticateBlock (uid, uidLength, BLOCK_NUMBER_OF_SECTOR_TRAILER(idx), 1, (uint8_t *)KEY_DEFAULT_KEYAB); + if (!success) + { + Serial.print("Authentication failed for sector "); Serial.println(numOfSector); + return; + } + + // Step 2: Write to the other blocks + if (idx == 16) + { + memset(blockBuffer, 0, sizeof(blockBuffer)); + if (!(nfc.mifareclassic_WriteDataBlock((BLOCK_NUMBER_OF_SECTOR_TRAILER(idx)) - 3, blockBuffer))) + { + Serial.print("Unable to write to sector "); Serial.println(numOfSector); + return; + } + } + if ((idx == 0) || (idx == 16)) + { + memset(blockBuffer, 0, sizeof(blockBuffer)); + if (!(nfc.mifareclassic_WriteDataBlock((BLOCK_NUMBER_OF_SECTOR_TRAILER(idx)) - 2, blockBuffer))) + { + Serial.print("Unable to write to sector "); Serial.println(numOfSector); + return; + } + } + else + { + memset(blockBuffer, 0, sizeof(blockBuffer)); + if (!(nfc.mifareclassic_WriteDataBlock((BLOCK_NUMBER_OF_SECTOR_TRAILER(idx)) - 3, blockBuffer))) + { + Serial.print("Unable to write to sector "); Serial.println(numOfSector); + return; + } + if (!(nfc.mifareclassic_WriteDataBlock((BLOCK_NUMBER_OF_SECTOR_TRAILER(idx)) - 2, blockBuffer))) + { + Serial.print("Unable to write to sector "); Serial.println(numOfSector); + return; + } + } + memset(blockBuffer, 0, sizeof(blockBuffer)); + if (!(nfc.mifareclassic_WriteDataBlock((BLOCK_NUMBER_OF_SECTOR_TRAILER(idx)) - 1, blockBuffer))) + { + Serial.print("Unable to write to sector "); Serial.println(numOfSector); + return; + } + + // Step 3: Reset both keys to 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF + memcpy(blockBuffer, KEY_DEFAULT_KEYAB, sizeof(KEY_DEFAULT_KEYAB)); + memcpy(blockBuffer + 6, blankAccessBits, sizeof(blankAccessBits)); + blockBuffer[9] = 0x69; + memcpy(blockBuffer + 10, KEY_DEFAULT_KEYAB, sizeof(KEY_DEFAULT_KEYAB)); + + // Step 4: Write the trailer block + if (!(nfc.mifareclassic_WriteDataBlock((BLOCK_NUMBER_OF_SECTOR_TRAILER(idx)), blockBuffer))) + { + Serial.print("Unable to write trailer block of sector "); Serial.println(numOfSector); + return; + } + } + } + + // Wait a bit before trying again + Serial.println("\n\nDone!"); + delay(1000); + Serial.flush(); + while(Serial.available()) Serial.read(); +} \ No newline at end of file diff --git a/lib/PN532/examples/mifareclassic_updatendef/mifareclassic_updatendef.pde b/lib/PN532/examples/mifareclassic_updatendef/mifareclassic_updatendef.pde new file mode 100644 index 0000000..3720be0 --- /dev/null +++ b/lib/PN532/examples/mifareclassic_updatendef/mifareclassic_updatendef.pde @@ -0,0 +1,156 @@ +/**************************************************************************/ +/*! + Updates a sector that is already formatted for NDEF (using + mifareclassic_formatndef.pde for example), inserting a new url + + To enable debug message, define DEBUG in PN532/PN532_debug.h +*/ +/**************************************************************************/ + +#if 0 + #include + #include + #include "PN532.h" + + PN532_SPI pn532spi(SPI, 10); + PN532 nfc(pn532spi); +#elif 1 + #include + #include + + PN532_HSU pn532hsu(Serial1); + PN532 nfc(pn532hsu); +#else + #include + #include + #include +#endif + + +/* + We can encode many different kinds of pointers to the card, + from a URL, to an Email address, to a phone number, and many more + check the library header .h file to see the large # of supported + prefixes! +*/ +// For a http://www. url: +const char * url = "elechouse.com"; +uint8_t ndefprefix = NDEF_URIPREFIX_HTTP_WWWDOT; + +// for an email address +//const char * url = "mail@example.com"; +//uint8_t ndefprefix = NDEF_URIPREFIX_MAILTO; + +// for a phone number +//const char * url = "+1 212 555 1212"; +//uint8_t ndefprefix = NDEF_URIPREFIX_TEL; + + +void setup(void) { + Serial.begin(115200); + Serial.println("Looking for PN532..."); + + nfc.begin(); + + uint32_t versiondata = nfc.getFirmwareVersion(); + if (! versiondata) { + Serial.print("Didn't find PN53x board"); + while (1); // halt + } + + // Got ok data, print it out! + Serial.print("Found chip PN5"); Serial.println((versiondata>>24) & 0xFF, HEX); + Serial.print("Firmware ver. "); Serial.print((versiondata>>16) & 0xFF, DEC); + Serial.print('.'); Serial.println((versiondata>>8) & 0xFF, DEC); + + // configure board to read RFID tags + nfc.SAMConfig(); +} + +void loop(void) { + uint8_t success; // Flag to check if there was an error with the PN532 + uint8_t uid[] = { 0, 0, 0, 0, 0, 0, 0 }; // Buffer to store the returned UID + uint8_t uidLength; // Length of the UID (4 or 7 bytes depending on ISO14443A card type) + bool authenticated = false; // Flag to indicate if the sector is authenticated + + // Use the default NDEF keys (these would have have set by mifareclassic_formatndef.pde!) + uint8_t keya[6] = { 0xA0, 0xA1, 0xA2, 0xA3, 0xA4, 0xA5 }; + uint8_t keyb[6] = { 0xD3, 0xF7, 0xD3, 0xF7, 0xD3, 0xF7 }; + + Serial.println("Place your NDEF formatted Mifare Classic card on the reader to update the"); + Serial.println("NDEF record and press any key to continue ..."); + // Wait for user input before proceeding + while (!Serial.available()); + // a key was pressed1 + while (Serial.available()) Serial.read(); + + // Wait for an ISO14443A type card (Mifare, etc.). When one is found + // 'uid' will be populated with the UID, and uidLength will indicate + // if the uid is 4 bytes (Mifare Classic) or 7 bytes (Mifare Ultralight) + success = nfc.readPassiveTargetID(PN532_MIFARE_ISO14443A, uid, &uidLength); + + if (success) + { + // Display some basic information about the card + Serial.println("Found an ISO14443A card"); + Serial.print(" UID Length: ");Serial.print(uidLength, DEC);Serial.println(" bytes"); + Serial.print(" UID Value: "); + nfc.PrintHex(uid, uidLength); + Serial.println(""); + + // Make sure this is a Mifare Classic card + if (uidLength != 4) + { + Serial.println("Ooops ... this doesn't seem to be a Mifare Classic card!"); + return; + } + + // We probably have a Mifare Classic card ... + Serial.println("Seems to be a Mifare Classic card (4 byte UID)"); + + // Check if this is an NDEF card (using first block of sector 1 from mifareclassic_formatndef.pde) + // Must authenticate on the first key using 0xD3 0xF7 0xD3 0xF7 0xD3 0xF7 + success = nfc.mifareclassic_AuthenticateBlock (uid, uidLength, 4, 0, keyb); + if (!success) + { + Serial.println("Unable to authenticate block 4 ... is this card NDEF formatted?"); + return; + } + + Serial.println("Authentication succeeded (seems to be an NDEF/NFC Forum tag) ..."); + + // Authenticated seems to have worked + // Try to write an NDEF record to sector 1 + // Use 0x01 for the URI Identifier Code to prepend "http://www." + // to the url (and save some space). For information on URI ID Codes + // see http://www.ladyada.net/wiki/private/articlestaging/nfc/ndef + if (strlen(url) > 38) + { + // The length is also checked in the WriteNDEFURI function, but lets + // warn users here just in case they change the value and it's bigger + // than it should be + Serial.println("URI is too long ... must be less than 38 characters!"); + return; + } + + Serial.println("Updating sector 1 with URI as NDEF Message"); + + // URI is within size limits ... write it to the card and report success/failure + success = nfc.mifareclassic_WriteNDEFURI(1, ndefprefix, url); + if (success) + { + Serial.println("NDEF URI Record written to sector 1"); + Serial.println(""); + } + else + { + Serial.println("NDEF Record creation failed! :("); + } + } + + // Wait a bit before trying again + Serial.println("\n\nDone!"); + delay(1000); + Serial.flush(); + while(Serial.available()) Serial.read(); +} diff --git a/lib/PN532/examples/ntag21x_protect/ntag21x_protect.ino b/lib/PN532/examples/ntag21x_protect/ntag21x_protect.ino new file mode 100644 index 0000000..b93f5a0 --- /dev/null +++ b/lib/PN532/examples/ntag21x_protect/ntag21x_protect.ino @@ -0,0 +1,89 @@ +// NTAG21x supports 4 bytes password to protect pages started from AUTH0 +// AUTH0 defines the page address from which the password verification is required. +// Valid address range for byte AUTH0 is from 00h to FFh. +// If AUTH0 is set to a page address which is higher than the last page from the user configuration, +// the password protection is effectively disabled +#include +#include +#if 0 // Using PN532's SPI (Seeed NFC shield) +#include +#include + + +PN532_SPI intf(SPI, 10); +PN532 nfc = PN532(intf); +#else // Using PN532's I2C +#include +#include + +PN532_I2C intf(Wire); +PN532 nfc = PN532(intf); +#endif + +// Using PN532's UART (Grove NFC) + +// #include +// #include +// #include +// PN532_HSU intf(Serial1); +// PN532 nfc = PN532(intf); + + +uint8_t password[4] = {0x12, 0x34, 0x56, 0x78}; +uint8_t buf[4]; +uint8_t uid[7]; +uint8_t uidLength; + +void setup(void) { + Serial.begin(9600); + Serial.println("NTAG21x R/W"); + + nfc.begin(); + nfc.SAMConfig(); +} + +void loop(void) { + Serial.println("wait for a tag"); + // wait until a tag is present + while (!nfc.readPassiveTargetID(PN532_MIFARE_ISO14443A, uid, &uidLength)) { + + } + + // if NTAG21x enables r/w protection, uncomment the following line + // nfc.ntag21x_auth(password); + + nfc.mifareultralight_ReadPage(3, buf); + int capacity = buf[2] * 8; + Serial.print(F("Tag capacity ")); + Serial.print(capacity); + Serial.println(F(" bytes")); + + uint8_t cfg_page_base = 0x29; // NTAG213 + if (capacity == 0x3E) { + cfg_page_base = 0x83; // NTAG215 + } else if (capacity == 0x6D) { + cfg_page_base = 0xE3; // NTAG216 + } + + // PWD page, set new password + nfc.mifareultralight_WritePage(cfg_page_base + 2, password); + + // disable r/w + // | PROT | CFG_LCK | RFUI | NFC_CNT_EN | NFC_CNT_PWD_PROT | AUTHLIM (2:0) | + buf[0] = (1 << 7) | 0x0; + nfc.mifareultralight_WritePage(cfg_page_base + 1, buf); + + // protect pages started from AUTH0 + uint8_t auth0 = 0x10; + buf[0] = 0; + buf[1] = 0; + buf[2] = 0; + buf[3] = auth0; + nfc.mifareultralight_WritePage(cfg_page_base, buf); + + + // wait until the tag is removed + while (nfc.readPassiveTargetID(PN532_MIFARE_ISO14443A, uid, &uidLength)) { + + } +} diff --git a/lib/PN532/examples/ntag21x_rw/ntag21x_rw.ino b/lib/PN532/examples/ntag21x_rw/ntag21x_rw.ino new file mode 100644 index 0000000..76c76d6 --- /dev/null +++ b/lib/PN532/examples/ntag21x_rw/ntag21x_rw.ino @@ -0,0 +1,71 @@ + +// Clean resets a tag back to factory-like state +// For Mifare Classic, tag is zero'd and reformatted as Mifare Classic +// For Mifare Ultralight, tags is zero'd and left empty +#include +#include + +#if 0 // Using PN532's SPI (Seeed NFC shield) +#include +#include + + +PN532_SPI intf(SPI, 10); +PN532 nfc = PN532(intf); +#else // Using PN532's I2C +#include +#include + + +PN532_I2C intf(Wire); +PN532 nfc = PN532(intf); +#endif + +// Using PN532's UART (Grove NFC) + +// #include +// #include +// #include +// PN532_HSU intf(Serial1); +// PN532 nfc = PN532(intf); + + +uint8_t password[4] = {0x12, 0x34, 0x56, 0x78}; +uint8_t buf[4]; +uint8_t uid[7]; +uint8_t uidLength; + +void setup(void) { + Serial.begin(9600); + Serial.println("NTAG21x R/W"); + + nfc.begin(); + nfc.SAMConfig(); +} + +void loop(void) { + Serial.println("wait for a tag"); + // wait until a tag is present + while (!nfc.readPassiveTargetID(PN532_MIFARE_ISO14443A, uid, &uidLength)) { + + } + + // if NTAG21x enables r/w protection, uncomment the following line + // nfc.ntag21x_auth(password); + + nfc.mifareultralight_ReadPage(3, buf); + int capacity = buf[2] * 8; + Serial.print(F("Tag capacity ")); + Serial.print(capacity); + Serial.println(F(" bytes")); + + for (int i=4; i 0) { + Serial.println("get a SNEP message:"); + for (uint8_t i = 0; i < len; i++) { + Serial.print(buf[i], HEX); + Serial.print(' '); + } + Serial.print('\n'); + for (uint8_t i = 0; i < len; i++) { + char c = buf[i]; + if (c <= 0x1f || c > 0x7f) { + Serial.print('.'); + } else { + Serial.print(c); + } + } + Serial.print('\n'); + } + delay(3000); +} \ No newline at end of file diff --git a/lib/PN532/examples/p2p_with_ndef_library/p2p_with_ndef_library.ino b/lib/PN532/examples/p2p_with_ndef_library/p2p_with_ndef_library.ino new file mode 100644 index 0000000..ee89d73 --- /dev/null +++ b/lib/PN532/examples/p2p_with_ndef_library/p2p_with_ndef_library.ino @@ -0,0 +1,54 @@ +// send a NDEF message to adnroid or get a NDEF message +// +// note: [NDEF library](https://github.com/Don/NDEF) is needed. + +#include "SPI.h" +#include "PN532/PN532_SPI/PN532_SPI.h" +#include "PN532/PN532/snep.h" +#include "NdefMessage.h" + +PN532_SPI pn532spi(SPI, 10); +SNEP nfc(pn532spi); +uint8_t ndefBuf[128]; + +void setup() +{ + Serial.begin(115200); + Serial.println("-------Peer to Peer--------"); +} + +void loop() +{ +#if 1 + Serial.println("Send a message to Android"); + NdefMessage message = NdefMessage(); + message.addUriRecord("http://www.seeedstudio.com"); + int messageSize = message.getEncodedSize(); + if (messageSize > sizeof(ndefBuf)) { + Serial.println("ndefBuf is too small"); + while (1) { + } + + } + + message.encode(ndefBuf); + if (0 >= nfc.write(ndefBuf, messageSize)) { + Serial.println("Failed"); + } else { + Serial.println("Success"); + } + + delay(3000); +#else + Serial.println("Get a message from Android"); + int msgSize = nfc.read(ndefBuf, sizeof(ndefBuf)); + if (msgSize > 0) { + NdefMessage msg = NdefMessage(ndefBuf, msgSize); + msg.print(); + Serial.println("\nSuccess"); + } else { + Serial.println("failed"); + } + delay(3000); +#endif +} diff --git a/lib/PN532/examples/readMifare/readMifare.pde b/lib/PN532/examples/readMifare/readMifare.pde new file mode 100644 index 0000000..1e73236 --- /dev/null +++ b/lib/PN532/examples/readMifare/readMifare.pde @@ -0,0 +1,158 @@ +/**************************************************************************/ +/*! + This example will wait for any ISO14443A card or tag, and + depending on the size of the UID will attempt to read from it. + + If the card has a 4-byte UID it is probably a Mifare + Classic card, and the following steps are taken: + + - Authenticate block 4 (the first block of Sector 1) using + the default KEYA of 0XFF 0XFF 0XFF 0XFF 0XFF 0XFF + - If authentication succeeds, we can then read any of the + 4 blocks in that sector (though only block 4 is read here) + + If the card has a 7-byte UID it is probably a Mifare + Ultralight card, and the 4 byte pages can be read directly. + Page 4 is read by default since this is the first 'general- + purpose' page on the tags. + + To enable debug message, define DEBUG in PN532/PN532_debug.h +*/ +/**************************************************************************/ + +#if 0 + #include + #include + #include "PN532.h" + + PN532_SPI pn532spi(SPI, 10); + PN532 nfc(pn532spi); +#elif 1 + #include + #include + + PN532_HSU pn532hsu(Serial1); + PN532 nfc(pn532hsu); +#else + #include + #include + #include + PN532_I2C pn532i2c(Wire); + PN532 nfc(pn532i2c); +#endif +void setup(void) { + Serial.begin(115200); + Serial.println("Hello!"); + + nfc.begin(); + + uint32_t versiondata = nfc.getFirmwareVersion(); + if (! versiondata) { + Serial.print("Didn't find PN53x board"); + while (1); // halt + } + // Got ok data, print it out! + Serial.print("Found chip PN5"); Serial.println((versiondata>>24) & 0xFF, HEX); + Serial.print("Firmware ver. "); Serial.print((versiondata>>16) & 0xFF, DEC); + Serial.print('.'); Serial.println((versiondata>>8) & 0xFF, DEC); + + // configure board to read RFID tags + nfc.SAMConfig(); + + Serial.println("Waiting for an ISO14443A Card ..."); +} + + +void loop(void) { + uint8_t success; + uint8_t uid[] = { 0, 0, 0, 0, 0, 0, 0 }; // Buffer to store the returned UID + uint8_t uidLength; // Length of the UID (4 or 7 bytes depending on ISO14443A card type) + + // Wait for an ISO14443A type cards (Mifare, etc.). When one is found + // 'uid' will be populated with the UID, and uidLength will indicate + // if the uid is 4 bytes (Mifare Classic) or 7 bytes (Mifare Ultralight) + success = nfc.readPassiveTargetID(PN532_MIFARE_ISO14443A, uid, &uidLength); + + if (success) { + // Display some basic information about the card + Serial.println("Found an ISO14443A card"); + Serial.print(" UID Length: ");Serial.print(uidLength, DEC);Serial.println(" bytes"); + Serial.print(" UID Value: "); + nfc.PrintHex(uid, uidLength); + Serial.println(""); + + if (uidLength == 4) + { + // We probably have a Mifare Classic card ... + Serial.println("Seems to be a Mifare Classic card (4 byte UID)"); + + // Now we need to try to authenticate it for read/write access + // Try with the factory default KeyA: 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF + Serial.println("Trying to authenticate block 4 with default KEYA value"); + uint8_t keya[6] = { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }; + + // Start with block 4 (the first block of sector 1) since sector 0 + // contains the manufacturer data and it's probably better just + // to leave it alone unless you know what you're doing + success = nfc.mifareclassic_AuthenticateBlock(uid, uidLength, 4, 0, keya); + + if (success) + { + Serial.println("Sector 1 (Blocks 4..7) has been authenticated"); + uint8_t data[16]; + + // If you want to write something to block 4 to test with, uncomment + // the following line and this text should be read back in a minute + // data = { 'a', 'd', 'a', 'f', 'r', 'u', 'i', 't', '.', 'c', 'o', 'm', 0, 0, 0, 0}; + // success = nfc.mifareclassic_WriteDataBlock (4, data); + + // Try to read the contents of block 4 + success = nfc.mifareclassic_ReadDataBlock(4, data); + + if (success) + { + // Data seems to have been read ... spit it out + Serial.println("Reading Block 4:"); + nfc.PrintHexChar(data, 16); + Serial.println(""); + + // Wait a bit before reading the card again + delay(1000); + } + else + { + Serial.println("Ooops ... unable to read the requested block. Try another key?"); + } + } + else + { + Serial.println("Ooops ... authentication failed: Try another key?"); + } + } + + if (uidLength == 7) + { + // We probably have a Mifare Ultralight card ... + Serial.println("Seems to be a Mifare Ultralight tag (7 byte UID)"); + + // Try to read the first general-purpose user page (#4) + Serial.println("Reading page 4"); + uint8_t data[32]; + success = nfc.mifareultralight_ReadPage (4, data); + if (success) + { + // Data seems to have been read ... spit it out + nfc.PrintHexChar(data, 4); + Serial.println(""); + + // Wait a bit before reading the card again + delay(1000); + } + else + { + Serial.println("Ooops ... unable to read the requested page!?"); + } + } + } +} + diff --git a/lib/PN532/license.txt b/lib/PN532/license.txt new file mode 100644 index 0000000..09c45e1 --- /dev/null +++ b/lib/PN532/license.txt @@ -0,0 +1,27 @@ +Software License Agreement (BSD License) + +Copyright (c) 2012, Adafruit Industries +Copyright (c) 2013, Seeed Technology Inc. +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: +1. Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. +3. Neither the name of the copyright holders nor the +names of its contributors may be used to endorse or promote products +derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ''AS IS'' AND ANY +EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/lib/PN532/llcp.cpp b/lib/PN532/llcp.cpp new file mode 100644 index 0000000..559cd7a --- /dev/null +++ b/lib/PN532/llcp.cpp @@ -0,0 +1,373 @@ + +#include "llcp.h" +#include "PN532_debug.h" + +// LLCP PDU Type Values +#define PDU_SYMM 0x00 +#define PDU_PAX 0x01 +#define PDU_CONNECT 0x04 +#define PDU_DISC 0x05 +#define PDU_CC 0x06 +#define PDU_DM 0x07 +#define PDU_I 0x0c +#define PDU_RR 0x0d + +uint8_t LLCP::SYMM_PDU[2] = {0, 0}; + +inline uint8_t getPType(const uint8_t *buf) +{ + return ((buf[0] & 0x3) << 2) + (buf[1] >> 6); +} + +inline uint8_t getSSAP(const uint8_t *buf) +{ + return buf[1] & 0x3f; +} + +inline uint8_t getDSAP(const uint8_t *buf) +{ + return buf[0] >> 2; +} + +int8_t LLCP::activate(uint16_t timeout) +{ + return link.activateAsTarget(timeout); +} + +int8_t LLCP::waitForConnection(uint16_t timeout) +{ + uint8_t type; + + mode = 1; + ns = 0; + nr = 0; + + // Get CONNECT PDU + DMSG("wait for a CONNECT PDU\n"); + do + { + if (2 > link.read(headerBuf, headerBufLen)) + { + return -1; + } + + type = getPType(headerBuf); + if (PDU_CONNECT == type) + { + break; + } + else if (PDU_SYMM == type) + { + if (!link.write(SYMM_PDU, sizeof(SYMM_PDU))) + { + return -2; + } + } + else + { + return -3; + } + + } while (1); + + // Put CC PDU + DMSG("put a CC(Connection Complete) PDU to response the CONNECT PDU\n"); + ssap = getDSAP(headerBuf); + dsap = getSSAP(headerBuf); + headerBuf[0] = (dsap << 2) + ((PDU_CC >> 2) & 0x3); + headerBuf[1] = ((PDU_CC & 0x3) << 6) + ssap; + if (!link.write(headerBuf, 2)) + { + return -2; + } + + return 1; +} + +int8_t LLCP::waitForDisconnection(uint16_t timeout) +{ + uint8_t type; + + // Get DISC PDU + DMSG("wait for a DISC PDU\n"); + do + { + if (2 > link.read(headerBuf, headerBufLen)) + { + return -1; + } + + type = getPType(headerBuf); + if (PDU_DISC == type) + { + break; + } + else if (PDU_SYMM == type) + { + if (!link.write(SYMM_PDU, sizeof(SYMM_PDU))) + { + return -2; + } + } + else + { + return -3; + } + + } while (1); + + // Put DM PDU + DMSG("put a DM(Disconnect Mode) PDU to response the DISC PDU\n"); + // ssap = getDSAP(headerBuf); + // dsap = getSSAP(headerBuf); + headerBuf[0] = (dsap << 2) + (PDU_DM >> 2); + headerBuf[1] = ((PDU_DM & 0x3) << 6) + ssap; + if (!link.write(headerBuf, 2)) + { + return -2; + } + + return 1; +} + +int8_t LLCP::connect(uint16_t timeout) +{ + uint8_t type; + + mode = 0; + dsap = LLCP_DEFAULT_DSAP; + ssap = LLCP_DEFAULT_SSAP; + ns = 0; + nr = 0; + + // try to get a SYMM PDU + if (2 > link.read(headerBuf, headerBufLen)) + { + return -1; + } + type = getPType(headerBuf); + if (PDU_SYMM != type) + { + return -1; + } + + // put a CONNECT PDU + headerBuf[0] = (LLCP_DEFAULT_DSAP << 2) + (PDU_CONNECT >> 2); + headerBuf[1] = ((PDU_CONNECT & 0x03) << 6) + LLCP_DEFAULT_SSAP; + uint8_t body[] = " urn:nfc:sn:snep"; + body[0] = 0x06; + body[1] = sizeof(body) - 2 - 1; + if (!link.write(headerBuf, 2, body, sizeof(body) - 1)) + { + return -2; + } + + // wait for a CC PDU + DMSG("wait for a CC PDU\n"); + do + { + if (2 > link.read(headerBuf, headerBufLen)) + { + return -1; + } + + type = getPType(headerBuf); + if (PDU_CC == type) + { + break; + } + else if (PDU_SYMM == type) + { + if (!link.write(SYMM_PDU, sizeof(SYMM_PDU))) + { + return -2; + } + } + else + { + return -3; + } + + } while (1); + + return 1; +} + +int8_t LLCP::disconnect(uint16_t timeout) +{ + uint8_t type; + + // try to get a SYMM PDU + if (2 > link.read(headerBuf, headerBufLen)) + { + return -1; + } + type = getPType(headerBuf); + if (PDU_SYMM != type) + { + return -1; + } + + // put a DISC PDU + headerBuf[0] = (LLCP_DEFAULT_DSAP << 2) + (PDU_DISC >> 2); + headerBuf[1] = ((PDU_DISC & 0x03) << 6) + LLCP_DEFAULT_SSAP; + if (!link.write(headerBuf, 2)) + { + return -2; + } + + // wait for a DM PDU + DMSG("wait for a DM PDU\n"); + do + { + if (2 > link.read(headerBuf, headerBufLen)) + { + return -1; + } + + type = getPType(headerBuf); + if (PDU_CC == type) + { + break; + } + else if (PDU_DM == type) + { + if (!link.write(SYMM_PDU, sizeof(SYMM_PDU))) + { + return -2; + } + } + else + { + return -3; + } + + } while (1); + + return 1; +} + +bool LLCP::write(const uint8_t *header, uint8_t hlen, const uint8_t *body, uint8_t blen) +{ + uint8_t type; + uint8_t buf[3]; + + if (mode) + { + // Get a SYMM PDU + if (2 != link.read(buf, sizeof(buf))) + { + return false; + } + } + + if (headerBufLen < (hlen + 3)) + { + return false; + } + + for (int8_t i = hlen - 1; i >= 0; i--) + { + headerBuf[i + 3] = header[i]; + } + + headerBuf[0] = (dsap << 2) + (PDU_I >> 2); + headerBuf[1] = ((PDU_I & 0x3) << 6) + ssap; + headerBuf[2] = (ns << 4) + nr; + if (!link.write(headerBuf, 3 + hlen, body, blen)) + { + return false; + } + + ns++; + + // Get a RR PDU + int16_t status; + do + { + status = link.read(headerBuf, headerBufLen); + if (2 > status) + { + return false; + } + + type = getPType(headerBuf); + if (PDU_RR == type) + { + break; + } + else if (PDU_SYMM == type) + { + if (!link.write(SYMM_PDU, sizeof(SYMM_PDU))) + { + return false; + } + } + else + { + return false; + } + } while (1); + + if (!link.write(SYMM_PDU, sizeof(SYMM_PDU))) + { + return false; + } + + return true; +} + +int16_t LLCP::read(uint8_t *buf, uint8_t length) +{ + uint8_t type; + uint16_t status; + + // Get INFO PDU + do + { + status = link.read(buf, length); + if (2 > status) + { + return -1; + } + + type = getPType(buf); + if (PDU_I == type) + { + break; + } + else if (PDU_SYMM == type) + { + if (!link.write(SYMM_PDU, sizeof(SYMM_PDU))) + { + return -2; + } + } + else + { + return -3; + } + + } while (1); + + uint8_t len = status - 3; + ssap = getDSAP(buf); + dsap = getSSAP(buf); + + headerBuf[0] = (dsap << 2) + (PDU_RR >> 2); + headerBuf[1] = ((PDU_RR & 0x3) << 6) + ssap; + headerBuf[2] = (buf[2] >> 4) + 1; + if (!link.write(headerBuf, 3)) + { + return -2; + } + + for (uint8_t i = 0; i < len; i++) + { + buf[i] = buf[i + 3]; + } + + nr++; + + return len; +} diff --git a/lib/PN532/llcp.h b/lib/PN532/llcp.h new file mode 100644 index 0000000..701c5d7 --- /dev/null +++ b/lib/PN532/llcp.h @@ -0,0 +1,75 @@ + +#ifndef __LLCP_H__ +#define __LLCP_H__ + +#include "mac_link.h" + +#define LLCP_DEFAULT_TIMEOUT 20000 +#define LLCP_DEFAULT_DSAP 0x04 +#define LLCP_DEFAULT_SSAP 0x20 + +class LLCP { +public: + LLCP(PN532Interface &interface) : link(interface) { + headerBuf = link.getHeaderBuffer(&headerBufLen); + ns = 0; + nr = 0; + }; + + /** + * @brief Actiave PN532 as a target + * @param timeout max time to wait, 0 means no timeout + * @return > 0 success + * = 0 timeout + * < 0 failed + */ + int8_t activate(uint16_t timeout = 0); + + int8_t waitForConnection(uint16_t timeout = LLCP_DEFAULT_TIMEOUT); + + int8_t waitForDisconnection(uint16_t timeout = LLCP_DEFAULT_TIMEOUT); + + int8_t connect(uint16_t timeout = LLCP_DEFAULT_TIMEOUT); + + int8_t disconnect(uint16_t timeout = LLCP_DEFAULT_TIMEOUT); + + /** + * @brief write a packet, the packet should be less than (255 - 2) bytes + * @param header packet header + * @param hlen length of header + * @param body packet body + * @param blen length of body + * @return true success + * false failed + */ + bool write(const uint8_t *header, uint8_t hlen, const uint8_t *body = 0, uint8_t blen = 0); + + /** + * @brief read a packet, the packet will be less than (255 - 2) bytes + * @param buf the buffer to contain the packet + * @param len lenght of the buffer + * @return >=0 length of the packet + * <0 failed + */ + int16_t read(uint8_t *buf, uint8_t len); + + uint8_t *getHeaderBuffer(uint8_t *len) { + uint8_t *buf = link.getHeaderBuffer(len); + len -= 3; // I PDU header has 3 bytes + return buf; + }; + +private: + MACLink link; + uint8_t mode; + uint8_t ssap; + uint8_t dsap; + uint8_t *headerBuf; + uint8_t headerBufLen; + uint8_t ns; // Number of I PDU Sent + uint8_t nr; // Number of I PDU Received + + static uint8_t SYMM_PDU[2]; +}; + +#endif // __LLCP_H__ diff --git a/lib/PN532/mac_link.cpp b/lib/PN532/mac_link.cpp new file mode 100644 index 0000000..75aca8e --- /dev/null +++ b/lib/PN532/mac_link.cpp @@ -0,0 +1,20 @@ + +#include "mac_link.h" +#include "PN532_debug.h" + +int8_t MACLink::activateAsTarget(uint16_t timeout) +{ + pn532.begin(); + pn532.SAMConfig(); + return pn532.tgInitAsTarget(timeout); +} + +bool MACLink::write(const uint8_t *header, uint8_t hlen, const uint8_t *body, uint8_t blen) +{ + return pn532.tgSetData(header, hlen, body, blen); +} + +int16_t MACLink::read(uint8_t *buf, uint8_t len) +{ + return pn532.tgGetData(buf, len); +} diff --git a/lib/PN532/mac_link.h b/lib/PN532/mac_link.h new file mode 100644 index 0000000..1575c04 --- /dev/null +++ b/lib/PN532/mac_link.h @@ -0,0 +1,51 @@ + + +#ifndef __MAC_LINK_H__ +#define __MAC_LINK_H__ + +#include "PN532.h" + +class MACLink { +public: + MACLink(PN532Interface &interface) : pn532(interface) { + + }; + + /** + * @brief Activate PN532 as a target + * @param timeout max time to wait, 0 means no timeout + * @return > 0 success + * = 0 timeout + * < 0 failed + */ + int8_t activateAsTarget(uint16_t timeout = 0); + + /** + * @brief write a PDU packet, the packet should be less than (255 - 2) bytes + * @param header packet header + * @param hlen length of header + * @param body packet body + * @param blen length of body + * @return true success + * false failed + */ + bool write(const uint8_t *header, uint8_t hlen, const uint8_t *body = 0, uint8_t blen = 0); + + /** + * @brief read a PDU packet, the packet will be less than (255 - 2) bytes + * @param buf the buffer to contain the PDU packet + * @param len lenght of the buffer + * @return >=0 length of the PDU packet + * <0 failed + */ + int16_t read(uint8_t *buf, uint8_t len); + + uint8_t *getHeaderBuffer(uint8_t *len) { + return pn532.getBuffer(len); + }; + +private: + PN532 pn532; +}; + +#endif // __MAC_LINK_H__ diff --git a/lib/PN532/snep.cpp b/lib/PN532/snep.cpp new file mode 100644 index 0000000..73c71c4 --- /dev/null +++ b/lib/PN532/snep.cpp @@ -0,0 +1,133 @@ + +#include "snep.h" +#include "PN532_debug.h" + +int8_t SNEP::write(const uint8_t *buf, uint8_t len, uint16_t timeout) +{ + if (0 >= llcp.activate(timeout)) + { + DMSG("failed to activate PN532 as a target\n"); + return -1; + } + + if (0 >= llcp.connect(timeout)) + { + DMSG("failed to set up a connection\n"); + return -2; + } + + // response a success SNEP message + headerBuf[0] = SNEP_DEFAULT_VERSION; + headerBuf[1] = SNEP_REQUEST_PUT; + headerBuf[2] = 0; + headerBuf[3] = 0; + headerBuf[4] = 0; + headerBuf[5] = len; + if (0 >= llcp.write(headerBuf, 6, buf, len)) + { + return -3; + } + + uint8_t rbuf[16]; + if (6 > llcp.read(rbuf, sizeof(rbuf))) + { + return -4; + } + + // check SNEP version + if (SNEP_DEFAULT_VERSION != rbuf[0]) + { + DMSG("The received SNEP message's major version is different\n"); + // To-do: send Unsupported Version response + return -4; + } + + // expect a put request + if (SNEP_RESPONSE_SUCCESS != rbuf[1]) + { + DMSG("Expect a success response\n"); + return -4; + } + + llcp.disconnect(timeout); + + return 1; +} + +int16_t SNEP::read(uint8_t *buf, uint8_t len, uint16_t timeout) +{ + if (0 >= llcp.activate(timeout)) + { + DMSG("failed to activate PN532 as a target\n"); + return -1; + } + + if (0 >= llcp.waitForConnection(timeout)) + { + DMSG("failed to set up a connection\n"); + return -2; + } + + uint16_t status = llcp.read(buf, len); + if (6 > status) + { + return -3; + } + + // check SNEP version + + // in case of platform specific bug, shift SNEP message for 4 bytes. + // tested on Nexus 5, Android 5.1 + if (SNEP_DEFAULT_VERSION != buf[0] && SNEP_DEFAULT_VERSION == buf[4]) + { + for (uint8_t i = 0; i < len - 4; i++) + { + buf[i] = buf[i + 4]; + } + } + + if (SNEP_DEFAULT_VERSION != buf[0]) + { + DMSG(F("SNEP->read: The received SNEP message's major version is different, me: ")); + DMSG(SNEP_DEFAULT_VERSION); + DMSG(", their: "); + DMSG(buf[0]); + DMSG("\n"); + // To-do: send Unsupported Version response + return -4; + } + + // expect a put request + if (SNEP_REQUEST_PUT != buf[1]) + { + DMSG("Expect a put request\n"); + return -4; + } + + // check message's length + uint32_t length = (buf[2] << 24) + (buf[3] << 16) + (buf[4] << 8) + buf[5]; + // length should not be more than 244 (header + body < 255, header = 6 + 3 + 2) + if (length > (status - 6)) + { + DMSG("The SNEP message is too large: "); + DMSG_INT(length); + DMSG_INT(status - 6); + DMSG("\n"); + return -4; + } + for (uint8_t i = 0; i < length; i++) + { + buf[i] = buf[i + 6]; + } + + // response a success SNEP message + headerBuf[0] = SNEP_DEFAULT_VERSION; + headerBuf[1] = SNEP_RESPONSE_SUCCESS; + headerBuf[2] = 0; + headerBuf[3] = 0; + headerBuf[4] = 0; + headerBuf[5] = 0; + llcp.write(headerBuf, 6); + + return length; +} diff --git a/lib/PN532/snep.h b/lib/PN532/snep.h new file mode 100644 index 0000000..1a51696 --- /dev/null +++ b/lib/PN532/snep.h @@ -0,0 +1,49 @@ + + +#ifndef __SNEP_H__ +#define __SNEP_H__ + +#include "llcp.h" + +#define SNEP_DEFAULT_VERSION 0x10 // Major: 1, Minor: 0 + +#define SNEP_REQUEST_PUT 0x02 +#define SNEP_REQUEST_GET 0x01 + +#define SNEP_RESPONSE_SUCCESS 0x81 +#define SNEP_RESPONSE_REJECT 0xFF + +class SNEP { +public: + SNEP(PN532Interface &interface) : llcp(interface) { + headerBuf = llcp.getHeaderBuffer(&headerBufLen); + }; + + /** + * @brief write a SNEP packet, the packet should be less than (255 - 2 - 3) bytes + * @param buf the buffer to contain the packet + * @param len lenght of the buffer + * @param timeout max time to wait, 0 means no timeout + * @return >0 success + * =0 timeout + * <0 failed + */ + int8_t write(const uint8_t *buf, uint8_t len, uint16_t timeout = 0); + + /** + * @brief read a SNEP packet, the packet will be less than (255 - 2 - 3) bytes + * @param buf the buffer to contain the packet + * @param len lenght of the buffer + * @param timeout max time to wait, 0 means no timeout + * @return >=0 length of the packet + * <0 failed + */ + int16_t read(uint8_t *buf, uint8_t len, uint16_t timeout = 0); + +private: + LLCP llcp; + uint8_t *headerBuf; + uint8_t headerBufLen; +}; + +#endif // __SNEP_H__ diff --git a/lib/PN532_I2C b/lib/PN532_I2C deleted file mode 120000 index 62e3a10..0000000 --- a/lib/PN532_I2C +++ /dev/null @@ -1 +0,0 @@ -PN532-repo/PN532_I2C \ No newline at end of file diff --git a/lib/PN532_I2C/PN532_I2C.cpp b/lib/PN532_I2C/PN532_I2C.cpp new file mode 100644 index 0000000..b8d5863 --- /dev/null +++ b/lib/PN532_I2C/PN532_I2C.cpp @@ -0,0 +1,254 @@ +/** + * @modified picospuch + */ + +#include "PN532_I2C.h" +#include "PN532_debug.h" +#include "Arduino.h" + +#define PN532_I2C_ADDRESS (0x48 >> 1) + +PN532_I2C::PN532_I2C(TwoWire &wire) +{ + _wire = &wire; + command = 0; +} + +void PN532_I2C::begin() +{ + _wire->begin(); +} + +void PN532_I2C::wakeup() +{ + delay(500); // wait for all ready to manipulate pn532 +} + +int8_t PN532_I2C::writeCommand(const uint8_t *header, uint8_t hlen, const uint8_t *body, uint8_t blen) +{ + command = header[0]; + _wire->beginTransmission(PN532_I2C_ADDRESS); + + write(PN532_PREAMBLE); + write(PN532_STARTCODE1); + write(PN532_STARTCODE2); + + uint8_t length = hlen + blen + 1; // length of data field: TFI + DATA + write(length); + write(~length + 1); // checksum of length + + write(PN532_HOSTTOPN532); + uint8_t sum = PN532_HOSTTOPN532; // sum of TFI + DATA + + DMSG("write: "); + + for (uint8_t i = 0; i < hlen; i++) + { + if (write(header[i])) + { + sum += header[i]; + + DMSG_HEX(header[i]); + } + else + { + DMSG("\nToo many data to send, I2C doesn't support such a big packet\n"); // I2C max packet: 32 bytes + return PN532_INVALID_FRAME; + } + } + + for (uint8_t i = 0; i < blen; i++) + { + if (write(body[i])) + { + sum += body[i]; + + DMSG_HEX(body[i]); + } + else + { + DMSG("\nToo many data to send, I2C doesn't support such a big packet\n"); // I2C max packet: 32 bytes + return PN532_INVALID_FRAME; + } + } + + uint8_t checksum = ~sum + 1; // checksum of TFI + DATA + write(checksum); + write(PN532_POSTAMBLE); + + _wire->endTransmission(); + + DMSG('\n'); + + return readAckFrame(); +} + +int16_t PN532_I2C::getResponseLength(uint8_t buf[], uint8_t len, uint16_t timeout) +{ + const uint8_t PN532_NACK[] = {0, 0, 0xFF, 0xFF, 0, 0}; + uint16_t time = 0; + + do + { + if (_wire->requestFrom(PN532_I2C_ADDRESS, 6)) + { + if (read() & 1) + { // check first byte --- status + break; // PN532 is ready + } + } + + delay(1); + time++; + if ((0 != timeout) && (time > timeout)) + { + return -1; + } + } while (1); + + if (0x00 != read() || // PREAMBLE + 0x00 != read() || // STARTCODE1 + 0xFF != read() // STARTCODE2 + ) + { + + return PN532_INVALID_FRAME; + } + + uint8_t length = read(); + + // request for last respond msg again + _wire->beginTransmission(PN532_I2C_ADDRESS); + for (uint16_t i = 0; i < sizeof(PN532_NACK); ++i) + { + write(PN532_NACK[i]); + } + _wire->endTransmission(); + + return length; +} + +int16_t PN532_I2C::readResponse(uint8_t buf[], uint8_t len, uint16_t timeout) +{ + uint16_t time = 0; + uint8_t length; + + length = getResponseLength(buf, len, timeout); + + // [RDY] 00 00 FF LEN LCS (TFI PD0 ... PDn) DCS 00 + do + { + if (_wire->requestFrom(PN532_I2C_ADDRESS, 6 + length + 2)) + { + if (read() & 1) + { // check first byte --- status + break; // PN532 is ready + } + } + + delay(1); + time++; + if ((0 != timeout) && (time > timeout)) + { + return -1; + } + } while (1); + + if (0x00 != read() || // PREAMBLE + 0x00 != read() || // STARTCODE1 + 0xFF != read() // STARTCODE2 + ) + { + + return PN532_INVALID_FRAME; + } + + length = read(); + + if (0 != (uint8_t)(length + read())) + { // checksum of length + return PN532_INVALID_FRAME; + } + + uint8_t cmd = command + 1; // response command + if (PN532_PN532TOHOST != read() || (cmd) != read()) + { + return PN532_INVALID_FRAME; + } + + length -= 2; + if (length > len) + { + return PN532_NO_SPACE; // not enough space + } + + DMSG("read: "); + DMSG_HEX(cmd); + + uint8_t sum = PN532_PN532TOHOST + cmd; + for (uint8_t i = 0; i < length; i++) + { + buf[i] = read(); + sum += buf[i]; + + DMSG_HEX(buf[i]); + } + DMSG('\n'); + + uint8_t checksum = read(); + if (0 != (uint8_t)(sum + checksum)) + { + DMSG("checksum is not ok\n"); + return PN532_INVALID_FRAME; + } + read(); // POSTAMBLE + + return length; +} + +int8_t PN532_I2C::readAckFrame() +{ + const uint8_t PN532_ACK[] = {0, 0, 0xFF, 0, 0xFF, 0}; + uint8_t ackBuf[sizeof(PN532_ACK)]; + + DMSG("wait for ack at : "); + DMSG(millis()); + DMSG('\n'); + + uint16_t time = 0; + do + { + if (_wire->requestFrom(PN532_I2C_ADDRESS, sizeof(PN532_ACK) + 1)) + { + if (read() & 1) + { // check first byte --- status + break; // PN532 is ready + } + } + + delay(1); + time++; + if (time > PN532_ACK_WAIT_TIME) + { + DMSG("Time out when waiting for ACK\n"); + return PN532_TIMEOUT; + } + } while (1); + + DMSG("ready at : "); + DMSG(millis()); + DMSG('\n'); + + for (uint8_t i = 0; i < sizeof(PN532_ACK); i++) + { + ackBuf[i] = read(); + } + + if (memcmp(ackBuf, PN532_ACK, sizeof(PN532_ACK))) + { + DMSG("Invalid ACK\n"); + return PN532_INVALID_ACK; + } + + return 0; +} diff --git a/lib/PN532_I2C/PN532_I2C.h b/lib/PN532_I2C/PN532_I2C.h new file mode 100644 index 0000000..3502180 --- /dev/null +++ b/lib/PN532_I2C/PN532_I2C.h @@ -0,0 +1,47 @@ +/** + * @modified picospuch + */ + +#ifndef __PN532_I2C_H__ +#define __PN532_I2C_H__ + +#include +#include "PN532Interface.h" + +class PN532_I2C : public PN532Interface +{ +public: + PN532_I2C(TwoWire &wire); + + void begin(); + void wakeup(); + virtual int8_t writeCommand(const uint8_t *header, uint8_t hlen, const uint8_t *body = 0, uint8_t blen = 0); + int16_t readResponse(uint8_t buf[], uint8_t len, uint16_t timeout); + +private: + TwoWire *_wire; + uint8_t command; + + int8_t readAckFrame(); + int16_t getResponseLength(uint8_t buf[], uint8_t len, uint16_t timeout); + + inline uint8_t write(uint8_t data) + { +#if ARDUINO >= 100 + return _wire->write(data); +#else + return _wire->send(data); +#endif + } + + inline uint8_t read() + { +#if ARDUINO >= 100 + return _wire->read(); +#else + return _wire->receive(); +#endif + } +}; + +#endif From e7d4e6d9668d2712e5cc61514c65d792d7cc6f5d Mon Sep 17 00:00:00 2001 From: Azalea <22280294+hykilpikonna@users.noreply.github.com> Date: Sat, 25 Nov 2023 02:54:35 -0500 Subject: [PATCH 13/16] [U] Refactor --- src/main.cpp | 146 ++++++++++++++++++++++++++------------------------- 1 file changed, 75 insertions(+), 71 deletions(-) diff --git a/src/main.cpp b/src/main.cpp index f58dbd8..e2d661f 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -4,101 +4,105 @@ #include #include +#define u8 uint8_t +#define u16 uint16_t +#define u32 uint32_t + PN532_I2C pn532i2c(Wire); PN532 nfc(pn532i2c); -#define Serial USBSerial - -#include - -uint8_t _prevIDm[8]; -unsigned long _prevTime; +uint8_t prevIDm[8]; +unsigned long prevTime; void setup() { - delay(1000); - Serial.begin(115200); - Serial.println("Hello!"); - Wire.setPins(4, 5); + // Add initial delay to allow the serial monitor to catch up + delay(1000); - nfc.begin(); + // Initialize serial port + USBSerial.begin(115200); + USBSerial.println("Hello!"); - uint32_t versiondata = nfc.getFirmwareVersion(); - if (!versiondata) - { - Serial.print("Didn't find PN53x board"); - while (1) {delay(10);}; // halt - } + // Initialize I2C communication + // Wire.setPins(GPIO_NUM_4, GPIO_NUM_5); - // Got ok data, print it out! - Serial.print("Found chip PN5"); Serial.println((versiondata >> 24) & 0xFF, HEX); - Serial.print("Firmware ver. "); Serial.print((versiondata >> 16) & 0xFF, DEC); - Serial.print('.'); Serial.println((versiondata >> 8) & 0xFF, DEC); + // Find the PN532 NFC module + nfc.begin(); + u32 versiondata; + while ((versiondata = nfc.getFirmwareVersion()) == 0) + { + USBSerial.println("Didn't find PN53x board"); + delay(100); + } - // Set the max number of retry attempts to read from a card - // This prevents us from waiting forever for a card, which is - // the default behaviour of the PN532. - nfc.setPassiveActivationRetries(0xFF); - nfc.SAMConfig(); + // Got ok data, print it out! + USBSerial.print("Found chip PN5"); + USBSerial.println(versiondata >> 24 & 0xFF, HEX); + USBSerial.print("Firmware ver. "); + USBSerial.print(versiondata >> 16 & 0xFF, DEC); + USBSerial.print('.'); + USBSerial.println(versiondata >> 8 & 0xFF, DEC); - memset(_prevIDm, 0, 8); + // Set the max number of retry attempts to read from a card + // This prevents us from waiting forever for a card, which is the default behaviour of the PN532. + nfc.setPassiveActivationRetries(0xFF); + nfc.SAMConfig(); + + // Clear the IDm buffer + memset(prevIDm, 0, 8); } -void printUid(uint8_t* uid, uint8_t uidLength) { - Serial.print("UID Length: ");Serial.print(uidLength, DEC);Serial.println(" bytes"); - Serial.print("UID Value: "); - for (uint8_t i = 0; i < uidLength; i++) { - Serial.print(uid[i], HEX); - } - Serial.println(""); +void printUid(const uint8_t* uid, const uint8_t uidLength) +{ + USBSerial.print("UID Length: "); + USBSerial.print(uidLength, DEC); + USBSerial.println(" bytes"); + USBSerial.print("UID Value: "); + for (u8 i = 0; i < uidLength; i++) + USBSerial.print(uid[i], HEX); + USBSerial.println(""); } void loop() { - uint8_t ret; - uint16_t systemCode = 0xFFFF; - uint8_t requestCode = 0x00; // System Code request - uint8_t idm[8]; - uint8_t pmm[8]; - uint16_t systemCodeResponse; + u8 idm[8]; + u8 pmm[8]; - // Wait for an FeliCa type cards. - // When one is found, some basic information such as IDm, PMm, and System Code are retrieved. - Serial.print("F"); - ret = nfc.felica_Polling(systemCode, requestCode, idm, pmm, &systemCodeResponse, 5); + // Wait for an FeliCa type cards. + // When one is found, some basic information such as IDm, PMm, and System Code are retrieved. + USBSerial.print("F"); + if (nfc.felica_Polling(0xFFFF, 0x01, idm, pmm, nullptr, 5) == 1) + { + if (memcmp(idm, prevIDm, 8) == 0 && millis() - prevTime < 3000) + { + delay(5); + return; + } - if (ret == 1) { - if ( memcmp(idm, _prevIDm, 8) == 0 ) { - if ( (millis() - _prevTime) < 3000 ) { - delay(5); + USBSerial.println("\nFound a Felica card!"); + printUid(idm, 8); + + memcpy(prevIDm, idm, 8); + prevTime = millis(); return; - } } - Serial.println("\nFound a Felica card!"); - printUid(idm, 8); + u8 uidLength; // Length of the UID (4 or 7 bytes depending on ISO14443A card type) - memcpy(_prevIDm, idm, 8); - _prevTime = millis(); - return; - } + USBSerial.print("M"); + if (nfc.readPassiveTargetID(PN532_MIFARE_ISO14443A, idm, &uidLength, 5)) + { + // Check if the same card is present + if (memcmp(idm, prevIDm, uidLength) == 0 && millis() - prevTime < 3000) + { + delay(5); + return; + } - Serial.print("M"); - uint8_t uid[] = { 0, 0, 0, 0, 0, 0, 0 }; // Buffer to store the returned UID - uint8_t uidLength; // Length of the UID (4 or 7 bytes depending on ISO14443A card type) + USBSerial.println("\nFound a MIFARE card!"); + printUid(idm, uidLength); - if (nfc.readPassiveTargetID(PN532_MIFARE_ISO14443A, uid, &uidLength, 5)) { - // Check if the same card is present - if (memcmp(uid, _prevIDm, uidLength) == 0 && (millis() - _prevTime) < 3000) { - delay(5); - return; + memcpy(prevIDm, idm, uidLength); + prevTime = millis(); } - - Serial.println("\nFound a MIFARE card!"); - printUid(uid, uidLength); - - memcpy(_prevIDm, uid, uidLength); - _prevTime = millis(); - return; - } } From 92a2cc9bb572c1cf50bc44322bf7bd9bc08587c0 Mon Sep 17 00:00:00 2001 From: Azalea <22280294+hykilpikonna@users.noreply.github.com> Date: Sat, 25 Nov 2023 03:19:27 -0500 Subject: [PATCH 14/16] [F] Fix segfault --- src/main.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/main.cpp b/src/main.cpp index e2d661f..4ac81d7 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -24,7 +24,7 @@ void setup() USBSerial.println("Hello!"); // Initialize I2C communication - // Wire.setPins(GPIO_NUM_4, GPIO_NUM_5); + Wire.setPins(GPIO_NUM_4, GPIO_NUM_5); // Find the PN532 NFC module nfc.begin(); @@ -67,11 +67,12 @@ void loop() { u8 idm[8]; u8 pmm[8]; + u16 systemCode; // Wait for an FeliCa type cards. // When one is found, some basic information such as IDm, PMm, and System Code are retrieved. USBSerial.print("F"); - if (nfc.felica_Polling(0xFFFF, 0x01, idm, pmm, nullptr, 5) == 1) + if (nfc.felica_Polling(0xFFFF, 0x00, idm, pmm, &systemCode, 5) == 1) { if (memcmp(idm, prevIDm, 8) == 0 && millis() - prevTime < 3000) { From cd6986191a38616d933f65deadc96e351ce1f5e8 Mon Sep 17 00:00:00 2001 From: Azalea <22280294+hykilpikonna@users.noreply.github.com> Date: Sat, 25 Nov 2023 03:39:31 -0500 Subject: [PATCH 15/16] [O] Refactor --- src/main.cpp | 61 +++++++++++++++++++--------------------------------- 1 file changed, 22 insertions(+), 39 deletions(-) diff --git a/src/main.cpp b/src/main.cpp index 4ac81d7..2d0ccf8 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -11,8 +11,9 @@ PN532_I2C pn532i2c(Wire); PN532 nfc(pn532i2c); -uint8_t prevIDm[8]; -unsigned long prevTime; +constexpr u8 UID_LENGTH = 8; +u8 prevIDm[UID_LENGTH]; +u32 prevTime; void setup() { @@ -52,58 +53,40 @@ void setup() memset(prevIDm, 0, 8); } -void printUid(const uint8_t* uid, const uint8_t uidLength) +void foundCard(const u8* uid, const u8 len, const char* cardType) { - USBSerial.print("UID Length: "); - USBSerial.print(uidLength, DEC); - USBSerial.println(" bytes"); + // Check if the same card is present + if (memcmp(uid, prevIDm, UID_LENGTH) == 0 && millis() - prevTime < 3000) + { + delay(5); + return; + } + + USBSerial.printf("\nFound a %s card!\n", cardType); USBSerial.print("UID Value: "); - for (u8 i = 0; i < uidLength; i++) + for (u8 i = 0; i < len; i++) USBSerial.print(uid[i], HEX); USBSerial.println(""); + + memcpy(prevIDm, uid, UID_LENGTH); + prevTime = millis(); } void loop() { - u8 idm[8]; + u8 idm[UID_LENGTH] = {0}; u8 pmm[8]; u16 systemCode; // Wait for an FeliCa type cards. // When one is found, some basic information such as IDm, PMm, and System Code are retrieved. USBSerial.print("F"); - if (nfc.felica_Polling(0xFFFF, 0x00, idm, pmm, &systemCode, 5) == 1) - { - if (memcmp(idm, prevIDm, 8) == 0 && millis() - prevTime < 3000) - { - delay(5); - return; - } - - USBSerial.println("\nFound a Felica card!"); - printUid(idm, 8); - - memcpy(prevIDm, idm, 8); - prevTime = millis(); - return; - } - - u8 uidLength; // Length of the UID (4 or 7 bytes depending on ISO14443A card type) + if (nfc.felica_Polling(0xFFFF, 0x00, idm, pmm, &systemCode, 5)) + foundCard(idm, UID_LENGTH, "FeliCa"); + // Wait for an ISO14443A type cards (MIFARE, etc.). When one is found + u8 uidLength; USBSerial.print("M"); if (nfc.readPassiveTargetID(PN532_MIFARE_ISO14443A, idm, &uidLength, 5)) - { - // Check if the same card is present - if (memcmp(idm, prevIDm, uidLength) == 0 && millis() - prevTime < 3000) - { - delay(5); - return; - } - - USBSerial.println("\nFound a MIFARE card!"); - printUid(idm, uidLength); - - memcpy(prevIDm, idm, uidLength); - prevTime = millis(); - } + foundCard(idm, uidLength, "ISO14443A"); } From c96195c7993a3bf9f62f19734aaebdb6d512c5ce Mon Sep 17 00:00:00 2001 From: Azalea <22280294+hykilpikonna@users.noreply.github.com> Date: Sat, 25 Nov 2023 04:36:03 -0500 Subject: [PATCH 16/16] [+] RGB!! --- .gitmodules | 6 +++--- include/types.h | 15 +++++++++++++++ lib/FastLED | 1 + src/main.cpp | 47 ++++++++++++++++++++++++++++++++++++++--------- 4 files changed, 57 insertions(+), 12 deletions(-) create mode 100644 include/types.h create mode 160000 lib/FastLED diff --git a/.gitmodules b/.gitmodules index 8c0f673..8328524 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,3 @@ -[submodule "lib/PN532"] - path = lib/PN532-repo - url = https://github.com/Seeed-Studio/PN532/ +[submodule "lib/FastLED"] + path = lib/FastLED + url = https://github.com/FastLED/FastLED diff --git a/include/types.h b/include/types.h new file mode 100644 index 0000000..a74aa1c --- /dev/null +++ b/include/types.h @@ -0,0 +1,15 @@ +#ifndef TYPES_H +#define TYPES_H + +#define u8 uint8_t +#define u16 uint16_t +#define u32 uint32_t + +#define s8 int8_t +#define s16 int16_t +#define s32 int32_t + +#define f32 float +#define f64 double + +#endif //TYPES_H diff --git a/lib/FastLED b/lib/FastLED new file mode 160000 index 0000000..3a03742 --- /dev/null +++ b/lib/FastLED @@ -0,0 +1 @@ +Subproject commit 3a03742a09aeb219a065954d7f85be9cdd2582f0 diff --git a/src/main.cpp b/src/main.cpp index 2d0ccf8..ef11adf 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -3,10 +3,13 @@ #include #include #include +#include +#include -#define u8 uint8_t -#define u16 uint16_t -#define u32 uint32_t +constexpr u8 NUM_LEDS = 7; +constexpr u8 BR_DIM = 8; +constexpr u8 BR_BRIGHT = 14; +CRGB leds[NUM_LEDS]; PN532_I2C pn532i2c(Wire); PN532 nfc(pn532i2c); @@ -18,7 +21,7 @@ u32 prevTime; void setup() { // Add initial delay to allow the serial monitor to catch up - delay(1000); + delay(500); // Initialize serial port USBSerial.begin(115200); @@ -27,6 +30,10 @@ void setup() // Initialize I2C communication Wire.setPins(GPIO_NUM_4, GPIO_NUM_5); + // Initialize the LED + CFastLED::addLeds(leds, NUM_LEDS); + FastLED.setBrightness(BR_DIM); + // Find the PN532 NFC module nfc.begin(); u32 versiondata; @@ -50,7 +57,25 @@ void setup() nfc.SAMConfig(); // Clear the IDm buffer - memset(prevIDm, 0, 8); + memset(prevIDm, 0, UID_LENGTH); +} + +void led_animation() +{ + FastLED.setBrightness(BR_BRIGHT); + CRGB colors[] = {CRGB::LimeGreen, CRGB::Black, CRGB::Gold, CRGB::Black}; + + for (const auto color : colors) + { + for (u8 i = 1; i < NUM_LEDS; i++) + { + leds[i] = color; + FastLED.show(); + delay(35); + } + } + + FastLED.setBrightness(BR_DIM); } void foundCard(const u8* uid, const u8 len, const char* cardType) @@ -68,6 +93,8 @@ void foundCard(const u8* uid, const u8 len, const char* cardType) USBSerial.print(uid[i], HEX); USBSerial.println(""); + led_animation(); + memcpy(prevIDm, uid, UID_LENGTH); prevTime = millis(); } @@ -80,13 +107,15 @@ void loop() // Wait for an FeliCa type cards. // When one is found, some basic information such as IDm, PMm, and System Code are retrieved. - USBSerial.print("F"); - if (nfc.felica_Polling(0xFFFF, 0x00, idm, pmm, &systemCode, 5)) + leds[0] = CRGB::BlueViolet; + FastLED.show(); + if (nfc.felica_Polling(0xFFFF, 0x00, idm, pmm, &systemCode, 5) == 1) foundCard(idm, UID_LENGTH, "FeliCa"); // Wait for an ISO14443A type cards (MIFARE, etc.). When one is found u8 uidLength; - USBSerial.print("M"); - if (nfc.readPassiveTargetID(PN532_MIFARE_ISO14443A, idm, &uidLength, 5)) + leds[0] = CRGB::OrangeRed; + FastLED.show(); + if (nfc.readPassiveTargetID(PN532_MIFARE_ISO14443A, idm, &uidLength, 5) == 1) foundCard(idm, uidLength, "ISO14443A"); }