1.0 Introduction
This project started with a simple objective.
I wanted to transfer my WLAN connection details from my phone to my microcontroller via one of these I2C connected NFC Tags, such as the NXP NTAG I2C Plus (NT3H2111).
However, the process to achieve this objective was not that straightforward as there was no open-source / off-the-GitHub-shelf library available that would do it for me. This meant that I needed to bolster my knowledge and understanding of the NFC Data Exchange Format (NDEF) as reading data sheets and web pages only gets you so far. Hence, this called for an iterative learning-by-doing (or trial-and-error) exploration and development process.
Well, I’m pleased to announce that I’ve cracked it. I have finally gone beyond just extracting out text message or URL message formats and I can now successfully extract out the WiFi formatted details as well as extract out Bluetooth pairing information, although in this I do not find this useful as most widely used Android NFC Writer Apps only give you devices that your phone has paired with in the past and not what is currently available for connection (you then have to manually enter the data in the apps). Anyhow, as connectivity via Bluetooth was not my objective, I am not really bothered by this.
So let me take you through this journey and explain it all as best I can. Then there will be a couple of video demos throughout but no tea and cakes afterwards...
I will start with the hardware and then move onto firmware.
2.0 Hardware
For this project I am using an NXP NTAG I2C plus NFC tag (NT3H2111), which is connected to an Arduino MKR1000 via the I2C bus. The module shown in the picture below is a MIKROE NFC Tag2 Click board.
{gallery} My Gallery Title |
---|
The NXP NTAG I2C plus is a NFC tag which combines a passive contactless ISO/IEC 14443 NFC interface with a connected I2C interface (see Figure 1 - source: NT3H2111_2211 Product Data Sheet).
When a NFC enabled phone or NFC reader is within close proximity to the NTAG antenna it will generate a RF wireless link at 13.56MHz to connect with the tag and enable an electric charge for energy harvesting and powering the passive NFC chip which then allows two-way data transfer between the devices. For the purposes of this blog tutorial we will be focusing merely on the data transfer aspect and will be ignoring the energy harvesting feature. In fact, we will take for granted that the NFC part works as intended and will focus instead on how a microcontroller (MCU) communicates with the NTAG using the I2C bus. The data transfer, in this case, could be either via the NTAG SRAM, acting as a data bridge for fast data exchange, or it could be via the NTAG EEPROM, which is used for non volatile data retention.
So, in essence, an NTAG is basically a shared SRAM / EEPROM device with some digital control or "smarts" involved to handle this memory sharing process as well as provide some very nice read and write security features. This block diagram (Figure 2 from the NXP Product Data Sheet) illustrates this concept:
The 1k NTAG Memory Map from I2C perspective
Section 8.3 of the Product Data Sheet provides a good description of the NTAG memory management from both NFC perspective and from I2C perspective. It also provides memory block information for both 1 kByte and 2 kByte tags. As I have a 1kByte tag, I'm focusing on this configuration. As shown in Figure 1, there is both 64 bytes of SRAM volatile memory, which has no write endurance limitations but is only available and accessible when powered via VCC, and then there is EEPROM non volatile memory, which has a write endurance limit of a 500,000 cycles.
I am focusing on EEPROM as this allows an NFC reader or phone to write to this memory sector even if the NTAG is without power.
Here is the memory map for EEPROM, as I see it (note that I have reconfigured it slightly compared to the data sheet as sometimes laying it out in a different manner makes it a little clearer - well it did for me anyway). The 64bytes of SRAM is accessible from block 0xF8h to block 0xFBh (occupies 4 blocks of memory).
As per the NTAG product data sheet, we can see from the diagram that memory, which is accessed from the I2C interface, is organised in blocks of 16 bytes each. Block 0 contained the read only 7-byte UID, which is unique for each NTAG and is set and write protected at time of production. The I²C slave address is stored in most significant 7 bits of byte 0 in block 0. A rather interesting feature is that when reading block 0, the NTAG I2C plus will always return 0x04h for byte 0.
The product data sheet, also comes with a WARNING (in bold). It warns that when configuring Static lock bytes and Capability container, the Address byte will always get updated too. So if you try this (as I did without realising) your default slave address changes from 55h to 02h (well mine did anyway).
Another interesting feature is Static Lock Bytes. Static Lock Bytes, or the field programmable read-only locking mechanism, can be configured via NFC or I2C. The I2C option gives greater control in that you can undo or change the read only NFC-based configuration. But word of warning if you set the I2C option as Read only then there is no way to undo this. There is some nice degree of control available from NFC perspective, as highlighted in Figure 8 (source: page 24 of NTAG product data sheet) below. Each NFC page, or 4 bytes of data, from page 03h (i.e. the Capability Containter) to page 0Fh can be individually locked by setting the corresponding locking bit to logic 1 to prevent further write access. After locking, the corresponding page becomes read-only memory.
In addition to this, NTAG uses the 3 LSB (least significant bits) of lock byte 0 as the block-locking bits. That is, bit 2 controls pages 0Ah to 0Fh (via NFC), bit 1 controls pages 04h to 09h (via NFC) and bit 0 controls page 03h (CC).
In Block 38h there is another option called Dynamic Lock Bytes. I am still a little perplexed about why it is termed "dynamic" as setting these bytes to 1 from 0 via NFC is also irreversible in exact same way as with Static Lock Bytes. It is just that here Dynamic Lock Bytes deals with a different sector (or group of pages) of memory. Apparently, this naming convention (i.e. static versus dynamic, is based on the NFC Forum Type 2 Tag Technical Specification).
There is one other configurable One Time Programmable (OTP) section, from NFC perspective, called the Capability Container (CC). The default value of the CC is initialised with 00000000h during the chip production. NDEF messages, for example, can only be written with NFC Forum devices, after setting these CC bytes according to application-specific needs. The CC also stores memory length information that represents the part of the memory that is allocated for storing the NDEF Message. According to the NTAG product data sheet, NXP recommends setting the size parameter of the CC only to values that the T2T_Area ends at lock bit granularity boundaries when using only part of the memory for storing NDEF messages. In order make sense of this, the product data sheet provides an example. Namely, the T2T_Area size should be 112 + 64*N or 888 bytes with N less or equal to 13 for the 1k version. Now I am not sure how the product data sheet came to the value of 13 as this does not meet the criteria (i.e. max 888 bytes). If you apply 12 as the maximum then it does meet the criteria (112+12*64 = 880).
According to the NTAG product data sheet, Table 8 would be the typical settings you would use for NFC Forum-compliant content using the whole memory of sector 0 for NDEF messages.
Now, for most users, the blocks of interest for application run-time use, from an I2C perspective, would be Block 01h to Block 37h, plus the first 8 bytes of block 38h. This gives you 54.5 blocks or 872 bytes of standard user EEPROM to play with.
The NTAG I2C plus can also be configured to have password protected memory areas. I'm not going to cover this area. For further information please refer to section 8.3.11 Password and Access Configuration (page 28) of the product data sheet.
Similarly with the Configuration and Session register settings. Please refer to section 8.3.12 NTAG I2C configuration and session registers (page 30) of the product data sheet.
It took me a good while to get my head around all the terminology and to understand all the memory config settings. I found a very useful app which helped me see what is going on under the hood, which is NXP's taginfo app. I've attached 4 screenshots of my NTAG.
The 1st screenshot shows the IC information. So as can be seen, my NTAG is manufactured by NXP, it is a NT3H2111 tag and it is classified as a Type 2 NFC Forum NDEF-compliant tag.
The 2nd screenshot shows the NDEF information. Here it shows that there is 1 NDEF record which is 18 bytes long.The maximum NDEF message size allowed is 868 bytes and there are no restrictions on Read and Write access. The NDEF record stored is type "U" or URL and the URI is "https://element14.com". The NDEF Capability Container shows a max size of 872 bytes, has Read&Write Access and the settings are set as "0xE1 0x11 0x6D 0x00", which if you notice is not quite the same as that given in Table 8 (i.e. 0xE1 0x10 0x6D 0x00). So it simply confirms that when I broke my NTAG, I did not put it back together exactly as it should... ah well what's new... it is working for me anyways.
The 3rd screenshot provides the detail through a full scan. It confirms what the product data sheet documentation says, which is that byte 0 of Page 0 is always shown as "04h" and that this Byte 0 is also the first byte value of the 7-byte UID, which is give as "04:40:A5:72:08:4F:80". It shows the Static Lock bytes (page 002) and the Capability Container (CC) in page 003. The detailed scan then shows the data starting on page 004.
Then the 4th screenshot provides more details from the full scan. It shows all the blocks for password protection, blocks allocated for RFU (reserved for future use) as per documentation. It also gives info on the Configuration and Session registers from NFC perspective.
3.0 Firmware
As noted in the introduction, the aim of my project was to have firmware that allowed my MKR1000 to connect to a WLAN once it has received the connection details from my phone via the NXP NTAG I2C Plus (NT3H2111) tag. And as there was no open-source / off-the-GitHub-shelf library available that would do this all for me, I decided to embark on a mini Arduino protect to make this happen.
Now, as you may know, the Arduino open-source community is HUGE, and it is rather unlikely these days that you will find that there is nothing available to help you along the way. So, it did not take long, after a little online searching, to find this library on github: https://github.com/LieBtrau/arduino-ntag
It is a library by Christoph Tack (LieBtrau), which provides some very useful functions to interface with the NXP NTAG (NT3H1101 and NT3H1201) tags through I²C. Thanks to the NXP NTAG common architecture, this library works with the NT3H2111 tag as well.
This library also uses another library which many will be familiar with, namely don's NDEF library: https://github.com/don/NDEF
This NDEF library allows you to Read and Write NDEF messages with Arduino. It is commonly associated with Arduino NFC libraries written for the PN532 controller.
So what now.
Well, as I am merely interested in reading information from the NTAG, I made that my focus. I also wanted to have it that the information could be updated during runtime. Thankfully the NTAG has a handy pin called the Field Detect or FD pin. This pin goes low when a NFC compatible device initiates a RF connection. Now all we need to do is wait until the NFC RF connection completes when the device is removed from the RF field and the FD pin will go high again. Then we can check to see if anything was updated in the EEPROM. To facilitate matters the arduino-ntag library makes use of a handy debounce library called bounce2 (also found on GitHub). However, I found that I needed to use the functionality directly in my project code I decided to comment out the bounce2 initialisation in the library code.
This is how I started my project code:
#include <nfc_ntag.h> #include <NdefMessage.h> #include <Bounce2.h> #define FD_PIN 6 // Field detection pin (an input for the MKR1000) const uint16_t EEPROMWRITETIMEDELAY = 100; // We allow for a short delay to allow the EEPROM write to complete uint32_t FDPinDetected = 0L; // A timer for monitoring Field Detect pin and state uint8_t FDstate = 0; // NFC Tag Objects NFC_ntag ntag(NTAG_I2C_1K, FD_PIN); // instantiate the NTAG object NdefMessage* _ndefMessage; // instantiate the ndefMessage object // Instantiate a Bounce object Bounce debouncer = Bounce();
Let's look at the ntag object quickly, which basically starts the I2C bus and then initialises the FD pin as INPUT_PULLUP:
void NFC_ntag::begin(unsigned int i2c_clockspeed) { pinMode(_fd_pin, INPUT_PULLUP); bool bResult=true; WIRE.setClock(i2c_clockspeed); WIRE.begin(); delay(1); // Short start up delay to allow slave device to start }
With NDEF all we are interested in how the NDEF library handles the message object and as such I am only using the NdefMessage.h part of the NDEF library. This differs somewhat from the original NTAG library which includes the full ndef library (or a modified version thereof).
There is not much else to set up other than define the debounce delay for the FD pin.
The key functionality we require is to read EEPROM. We assume that any device writing NDEF messages to our NTAG will start at the first available block of memory.
The first thing we do is check what sort of data we have in this first block of NV (non volatile) memory (i.e. the first 16 bytes of data) using the function: ntag.readEeprom(0,EEPROMdata,16) which returns data stored in a 16 byte array - EEPROMdata.
What we are looking for is 0x03 as the first byte (EEPROMdata[0]) as this defines the data as in NDEF format. The next byte is the size of the data stored. For purposes of simplicity, my project only looks at the case where this data size is 255 bytes or less. If NDEF data stored is larger than 255, such as with Vcards then things get more complicated. So for now I've excluded all cases where NDEF data stored is greater than 255.
Then once I extract the size from the second byte (EEPROMdata[1]), I copy that data across and let NdefMessage do its magic using this function: _ndefMessage = new NdefMessage(readNDEFfromEeprom, NDEFcntr).
We can now determine how many NDEF messages we have using: _ndefMessage->getRecordCount();
We can now loop through each message and extract out the relevant information from the individual NDEF record: _ndefMessage->getRecord(i)
The first thing we need from the NDEF record is header information as this tells us everything we need to know about the record. This first byte is split into 5 different flags or identifiers and then 3 bits defining the Type Name Format (TNF).
The getTnf function, record.getTnf(), extracts out TNF identifier which then tells you the type of content contained in the record or how to interpret the payload type. The spec defines 7 possibilities:
switch (tnf) { case 0x00: Serial.println(F("EMPTY RECORD (0x00)")); break; case 0x01: Serial.println(F("NFC Forum well-known type [NFC RTD] (0x01)")); break; case 0x02: Serial.println(F("Media-type as defined in RFC 2046 [RFC 2046] (0x02)")); break; case 0x03: Serial.println(F("Absolute URI as defined in RFC 3986 [RFC 3986] (0x03)")); break; case 0x04: Serial.println(F("NFC Forum external type [NFC RTD] (0x04)")); break; case 0x05: Serial.println(F("RECORD UNKNOWN (0x05)")); break; case 0x06: Serial.println(F("RECORD UNCHANGED - this is Part Chunked Payload (0x06)")); break; }
So for text messages and URL messages, TNF is defined as type 0x01 (NFC Forum well-known type). For Bluetooth and WiFi data TNF will be defined as type 0x02 (Media-type).
I modified the NDEF message library to show me all this info via the Serial monitor.
Then within each TNF you will find different payload types. Yep more terminology. The Payload type, or Record Type Definition (RTD), provides you with an identifier that describes the content of the payload more specifically. The NFC Forum describes RTD as follows:
Specifies the format and rules for building standard record types used by NFC Forum application definitions and third parties that are based on the NDEF data format. The RTD specification provides a way to efficiently define record formats for new applications and gives users the opportunity to create their own applications based on NFC Forum specifications.
The NFC Forum provides a list of the various RTD's. This is how the NFC Forum describes them:
- Text RTD: can store text strings in multiple languages by using the RTD mechanism and NDEF format.
- URI RTD: can store Uniform Resource Identifiers (URI) by using the RTD mechanism and NDEF format
- Smart Poster RTD: defines a process to put URLs, SMSs or phone numbers on an NFC tag by using the URI RTD and Text RTD as building blocks.
- Verb RTD: Used to encode generic and carrier-specific supported services. The Verb Record can, for example, encode the service to trigger the printing of a document or picture that will be transferred via the Bluetooth or WLAN connection.
- Signature RTD: Specifies the format used when signing single or multiple NDEF records.
Let's look at how we can decode some of these RTD's
Decoding Text Messages
So let's demonstrate this decoding process with a simple text message "NXP Semiconductors do great NFC!"
This produces a 39 byte NDEF record: D1 01 23 54 02 65 6E 4E 58 50 20 53 65 6D 69 63 6F 6E 64 75 63 74 6F 72 73 20 64 6F 20 67 72 65 61 74 20 4E 46 43 21
This is split into header information and payload data.
The first two bytes D1 (Record header) and 01 (type length) are pretty standard, so no comment to make here. A fellow Element14 member has already provided a very useful table and explanation of the NDEF format: NFC-Badge - Update your badge with your smartphone - NDEF and app
So for our example the first bit of information is found in the 3rd byte (value = 0x23) which gives us the record type. Here the NDEF message library extracts out the header flags as: MB-ME-CF-SR-IL:01-01-00-01-00. This tells us that this is both the beginning and end record (MB and ME have value 01) and that it is a short record (SR = 01) and data length is 35 bytes. The TNF value extracted is 0x01.
Looking at the 4th byte we see its value is 0x54 or "T". This tells us this NDEF message is formatted as a text message and our payload is: 02 65 6E 4E 58 50 20 53 65 6D 69 63 6F 6E 64 75 63 74 6F 72 73 20 64 6F 20 67 72 65 61 74 20 4E 46 43 21
However we notice that the first byte is 0x02 which is a non-printable character. That is because it gives us further information. It tells us the encoding type (UTF-8 or UTF-16) by looking at bit 7 of this byte. Then extracting out the other bits we get the length of the language code. In our case it is "en" or 2 bytes long with values 0x65 and 0x6E. Then finally the message.
The code used to parse a text message is thus as follows:
// Parse the text message here ------------------------------ else if (TNFval == 1 && RTDval == 0x54) { // Check bit 7 of payload[0] bool UTF16 = bitRead(payload[0], 7); String UTFcoding = ""; String textLang = ""; if (UTF16) UTFcoding = "[utf16]"; else UTFcoding = "[utf8]"; //Now check the length of "language code" byte langlen = payload[0] & 0x0F; if (langlen) { textLang = "["; for (byte c = 0; c < langlen; c++) { textLang += (char)payload[c+1]; } textLang += "]"; for (int c = langlen+1; c < payloadLength; c++) { payloadAsString += (char)payload[c]; } } else { for (int c = 1; c < payloadLength; c++) { payloadAsString += (char)payload[c]; } } Serial.print(UTFcoding); Serial.println(textLang); }
Decoding URI Messages
Let's move on and demonstrate the URI decoding process using this domain address "https://www.element14.com" as our URL.
This produces a 18 byte NDEF record: D1 01 0E 55 02 65 6C 65 6D 65 6E 74 31 34 2E 63 6F 6D
Jumping to the 3rd byte (value = 0x16) the NDEF message library extracts out the header flags as: MB-ME-CF-SR-IL:01-01-00-01-00. This tells us that this is both the beginning and end record (MB and ME have value 01) and that it is a short record (SR = 01), with data length 14 bytes and the TNF value extracted is 0x01.
Looking at the 4th byte we see its value is 0x55 or "U". This tells us this NDEF message is now formatted as a URL message and our payload is: 02 65 6C 65 6D 65 6E 74 31 34 2E 63 6F 6D
Even though the first byte value is the same as the 1st byte in text message payload, it is telling us something completely different. This is a code that relates to the prefix, e.g. "https://" or "http://" or "ftp://" etc. The next byte is then the start of the url, which in our case is "e" or 0x65.
The code is as follows (note that I have not included all possible url prefix options available):
// Parse the URL here ------------------------------------- else if (TNFval == 1 && RTDval == 0x55) { // We check the first value switch(payload[0]) { case 0x01: // HTTPWWW payloadAsString = "http://www."; break; case 0x02: // HTTPSWWW payloadAsString = "https://www."; break; case 0x03: // NDEF_HTTP payloadAsString = "http://"; break; case 0x04: // NDEF_HTTPS payloadAsString = "https://"; break; case 0x05: // TEL: payloadAsString = "tel:"; break; case 0x06: // MAILTO: payloadAsString = "mailto:"; break; case 0x07: // ftp anon@ payloadAsString = "ftp://anonymous:anonymous@"; break; case 0x08: // ftp: payloadAsString = "ftp://ftp."; break; case 0x09: // ftps:/ payloadAsString = "ftps:/"; break; case 0x0A: // sftp: payloadAsString = "sftp://"; break; case 0x0D: // ftp: payloadAsString = "ftp://"; break; case 0x1D: // file: payloadAsString = "file://"; break; } for (int c = 1; c < payloadLength; c++) { payloadAsString += (char)payload[c]; } }
Decoding "Smart Poster" Messages
Smart Poster NDEF messages are somewhat unique in that they will always contain a URL record and then an optional image/MIME record and/or one or more Text Messages. So these are a little more complicated to deal with than plain text or url messages. So, as I have no need for them I have not gone to any great lengths parsing out the different messages etc.
The little code I have to capture these is as follows:
// Parse the "special poster" here --------------------------- if (TNFval == 1 && RTDval == 0x53) { for (int c = 0; c < payloadLength; c++) { if (payload[c] > 31 && payload[c] < 127) payloadAsString += (char)payload[c]; else { payloadAsString += ("[" + String(payload[c], HEX) + "]"); } } }
Decoding MIME RTD Vcards
Decoding Vcards is relatively straightforward, as each data field within a Vcard is delimited with a 0xd and a 0xa. The only limitation in my code for handling Vcards is size (I have a max limit of 255) as they can be quite large if all the Vcard data fields are included.
Let's take a quick look at what we get. Vcards are defined as TNF type 2. The flags are similar: MB-ME-CF-SR-IL:01-01-00-01-00. Note that if a Vcard was greater than 255 then SR would be 0 as no longer regarded as a short record.
My bare minimum code is as follows:
// This is the Vcard RTD details ------------------------------------------ else if (TNFval == 2 && RTDval == 3) { payloadAsString = "\r\n\r\n"; for (int c = 0; c < payloadLength; c++) { if (payload[c] > 31 && payload[c] < 127) payloadAsString += (char)payload[c]; else { if (payload[c] == 0xd) payloadAsString += "\r"; else if (payload[c] == 0xa) payloadAsString += "\n"; else payloadAsString += ("[" + String(payload[c], HEX) + "]"); } } }
Decoding MIME RTD Verb (application/vnd) Bluetooth details
Let's now look at these "verbs". The TNF is a MIME-type and the RTD is categorised as "application/vnd.bluetooth.ep.oob".
To test this out, I used my Fitbit as these devices pair or bond with your phone. When I write this Bluetooth information to the tag it records 53 bytes as the total message length.
DA 20 10 01 61 70 70 6C 69 63 61 74 69 6F 6E 2F 76 6E 64 2E 62 6C 75 65 74 6F 6F 74 68 2E 65 70 2E 6F 6F 62 30 10 00 64 92 01 B9 6D FB 07 09 46 6C 65 78 20 32
When we spit out the flags from the NDEF header (MB-ME-CF-SR-IL:01-01-00-01-01) we see that the "IL" flag is set to 1. This tells us that the ID Length Field is present in this case. As to what this does exactly, who knows. Within the NDEF library this is what happens when IL = 1:
if (il) { record.setId(&data[index], idLength); index += idLength; }
Then in the library NdefRecord file we have:
String NdefRecord::getId() { char id[_idLength + 1]; memcpy(id, _id, _idLength); id[_idLength] = '\0'; // null terminate return String(id); }
And this returns in my ID: 0 (len:1).
Looking at the payload, we see that the payload length (16 bytes) is much less than the message length (53 bytes). That is because there is a bunch of additional information relating to pair and encryption included too, but for sake is simplicity this information is not extracted out in my example. Instead I simply parse out the following:
The first two bytes give us the OOB payload length. Then we get the MAC address, which I discovered is listed in reverse, then we get a code to tell us that the GATT service is a complete list 128-bit UUID's. Finally it tells us that the Bluetooth Name listed is the complete name.
The code to parse out this information is as follows:
// This is a bluetooth data payload ----------------------------------------------- else if (TNFval == 2 && RTDval == 2) { if (payload[0] > 0) { // Here the il flag is 1 so the ID will be set. Serial.print(" ID: "); String TNFid = record.getId(); Serial.print(TNFid); Serial.print(" (len:"); Serial.print(record.getIdLength(),DEC); Serial.println(")"); // First 2 bytes provide the data length payloadAsString = "[OOB data length: " + String((payload[0] | payload[1] << 8), DEC) + "]"; payloadAsString += "[MAC: "; payloadAsString += MACHexChar(payload+2, 6); payloadAsString += "]"; // Let's check payload[8]. Tells us a bit about the UUID's. If 0x07 then it tells us a service UUID is 128bit switch (payload[8]) { case 0x02: payloadAsString += "[incomplete list 16-bit UUID's]"; break; case 0x03: payloadAsString += "[complete list 16-bit UUID's]"; break; case 0x04: payloadAsString += "[incomplete list 32-bit UUID's]"; break; case 0x05: payloadAsString += "[complete list 32-bit UUID's]"; break; case 0x06: payloadAsString += "[incomplete list 128-bit UUID's]"; break; case 0x07: payloadAsString += "[complete list 128-bit UUID's]"; break; default: payloadAsString += ("[" + String(payload[8], HEX) + "]"); } // Let's check payload[9]. If 0x08 then SHORT_NAME or if 0x09 then COMPLETE_NAME if (payload[9] == 0x08 ) { payloadAsString += "[SHORT NAME]"; } else if (payload[9] == 0x09 ) { payloadAsString += "[COMPLETE NAME]"; } else { payloadAsString += ("[" + String(payload[9], HEX) + "]"); } for (int c = 10; c < payloadLength; c++) { if (payload[c] > 31 && payload[c] < 127) payloadAsString += (char)payload[c]; else payloadAsString += ("[" + String(payload[c],HEX) + "]"); } } }
Decoding MIME RTD Verb (application/vnd) WiFi details
Finally, we are now onto the part that extracts out the WiFi verb. Here the TNF is a MIME-type and the RTD is categorised as "application/vnd.wfa.wsc".
The NDEF header flags are set as follows: MB-ME-CF-SR-IL:01-01-00-01-01. Here too IL = 1 and this returns ID: 0 (len:1).
Within the payload we can extract the following:
- SSID: (7) 0x1045 [size 9] ----- SSID name --------
- WLAN MAC Address: (20) 0x1020 [size 6] F4:42:8F:0E:07:BA
- Network Key (PWD): (30) 0x1027 [size 16] ******PWD**********
- Auth Type: (50) 0x1003 [size 2] WPA_PSK
- Encryption Type: (56) 0x100F [size 2] AES Encryption Type
Here is the code that does the work for us:
// Parse the wifi data payload --------------------------- else if (TNFval == 2 && RTDval == 1) { // Here the il flag is 1 so the ID will be set. Serial.print(" ID: "); String TNFid = record.getId(); Serial.print(TNFid); Serial.print(" (len:"); Serial.print(record.getIdLength(),DEC); Serial.println(")"); // Let's parse and conver to uint16 as most data fields are in uint16_t mode uint16_t wifiFieldID = 0; uint16_t wifiFieldLen = 0; for (int c = 3; c < payloadLength; c++) { wifiFieldID = (payload[c-3]<< 8) | payload[c-2]; wifiFieldLen = (payload[c-1]<< 8) | payload[c]; if (wifiFieldID == SSID_FIELD_ID) { Serial.print(F(" - SSID: (")); Serial.print(c, DEC); Serial.print(") 0x"); Serial.print(wifiFieldID, HEX); Serial.print(" [size "); Serial.print(wifiFieldLen, DEC); Serial.print("] "); if (wifiFieldLen) { byte wifi_c[wifiFieldLen]; memcpy(wifi_c, payload+c+1, wifiFieldLen); for (uint8_t i = 0; i < wifiFieldLen; i++) NewSSID += (char)wifi_c[i]; Serial.println(NewSSID); } } else if (wifiFieldID == NETWORK_KEY_FIELD_ID) { Serial.print(F(" - Network Key (PWD): (")); Serial.print(c, DEC); Serial.print(") 0x"); Serial.print(wifiFieldID, HEX); Serial.print(" [size "); Serial.print(wifiFieldLen, DEC); Serial.print("] "); if (wifiFieldLen) { byte wifi_c[wifiFieldLen]; memcpy(wifi_c, payload+c+1, wifiFieldLen); // Hide the password String Secret; for (uint8_t i = 0; i < wifiFieldLen; i++) { NewPWD += (char)wifi_c[i]; Secret += "*"; } Serial.println(Secret); } } else if (wifiFieldID == MAC_ADDRESS_ID) { Serial.print(F(" - WLAN MAC Address: (")); Serial.print(c, DEC); Serial.print(") 0x"); Serial.print(wifiFieldID, HEX); Serial.print(" [size "); Serial.print(wifiFieldLen, DEC); Serial.print("] "); if (wifiFieldLen) { Serial.println(MACHexChar(payload+c+1, wifiFieldLen)); } } else if (wifiFieldID == AUTH_TYPE_FIELD_ID) { Serial.print(F(" - Auth Type: (")); Serial.print(c, DEC); Serial.print(") 0x"); Serial.print(wifiFieldID, HEX); Serial.print(" [size "); Serial.print(wifiFieldLen, DEC); Serial.print("] "); // Use wifiFieldID to capture the Auth Type 16bit value if (wifiFieldLen == 2) { wifiFieldID = (payload[c+1]<< 8) | payload[c+2]; if (wifiFieldID & AUTH_TYPE_WPA_PSK || wifiFieldID & AUTH_TYPE_WPA2_PSK) { Serial.println("WPA_PSK"); } else if (wifiFieldID & AUTH_TYPE_WPA_EAP || wifiFieldID & AUTH_TYPE_WPA2_EAP) { Serial.println("WPA_EAP"); } else if (wifiFieldID & AUTH_TYPE_OPEN) { Serial.println("NONE (Open)"); } else { Serial.print("[0x");Serial.print(wifiFieldID, HEX);Serial.println("]"); } } else { Serial.println(); } } else if (wifiFieldID == ENCRYPTION_TYPE_ID) { Serial.print(F(" - Encryption Type: (")); Serial.print(c, DEC); Serial.print(") 0x"); Serial.print(wifiFieldID, HEX); Serial.print(" [size "); Serial.print(wifiFieldLen, DEC); Serial.print("] "); if (wifiFieldLen == 2) { wifiFieldID = (payload[c+1]<< 8) | payload[c+2]; if (wifiFieldID == ENCRYPT_TYPE_NONE) Serial.println(F("No Encryption Type")); else if (wifiFieldID == ENCRYPT_TYPE_WEP) Serial.println(F("WEP Encryption Type - deprecated")); else if (wifiFieldID == ENCRYPT_TYPE_TKIP) Serial.println(F("TKIP Encryption Type")); else if (wifiFieldID == ENCRYPT_TYPE_AES) Serial.println(F("AES Encryption Type")); else if (wifiFieldID == ENCRYPT_TYPE_MMODE) Serial.println(F("AES/TKIP Mixed Mode Type")); } else { Serial.println(); } } //if (payload[c] > 31 && payload[c] < 127) payloadAsString += (char)payload[c]; //else payloadAsString += ("[" + String(payload[c],HEX) + "]"); } }
The Arduino code used in the above can be found in my GitHub repository: https://github.com/Gerriko/NTAG_MKR1000
4.0 Demos
Here are a couple of video demos.
The 1st shows the different NDEF parsing options and the 2nd shows an example where text and vcard NDEF messages are displayed on an e-paper display (EPD).
Then finally, here is a video showing how the WiFi details are extracted allowing for the MKR1000 to connect to WiFi.
Top Comments