1
votes

I try to make a HTTP GET request with data read from an NDEF formatted MIFARE NFC tag. I fail to convert the byte array data from the tag into a format that works with the Ethernet client print() function.

Hardware setup is an Arduino Uno with a seeedstudio NFC Shield and an Arduino ethernet shield. I make use of the ethernet, the PN532, and the NfcAdapter library.

I tried several types of conversion using char * and char[] instead of the String object with no success.

To pinpoint the problem I picked a case where Serial.print() gives the expected result but client.print() does not.

Code is based on the PN532 NDEF library example 'ReadTagExtended'.

void loop(void)
{
    if (nfc.tagPresent()) // Do an NFC scan to see if an NFC tag is present
    {
        NfcTag tag = nfc.read(); // read the NFC tag
        if (tag.hasNdefMessage())
        {
            NdefMessage message = tag.getNdefMessage();
            for (int i = 0; i < message.getRecordCount(); i++)
            {
                NdefRecord record = message.getRecord(i);
                int payloadLength = record.getPayloadLength();
                byte payload[payloadLength];
                record.getPayload(payload);
                String tag_content = "";
                for(int i = 0; i<payloadLength; i++) {
                    tag_content += (char)payload[i];
                }
                Serial.println(tag_content); // prints the correct string
                request(tag_content);
            }
        }
    }
}

void request(String data) {
    EthernetClient client;

    // if you get a connection, report back via serial:
    if (client.connect(remote, 8080)) {
        client.print("GET /subaddress");
        client.print("?data=");
        client.print(data); // unfortunately empty
        client.println();
        client.println();

        while (client.connected()) {
        if (client.available()) {
            char c = client.read();
            Serial.print(c);
        }
    }
    client.stop();
    Serial.println(" OK");
    delay(100);
    } else {
        Serial.println("ERR");
        delay(100);
    }
}

With the above setup, I get the expected output using Serial.println(). Yet, in the (successfull) request, data is empty.


From the comments (summarized):

Printing the record type (record.getType()) gives the letter 'U'. payloadLength is 4 for a tag containing the string "def".

1
What record type do you use? I.e what's the value that you obtain from record.getType(...)? Does Serial.println(data); print anything when you call it from within the request() function? What's the value of payloadLength and what data string value is printed "correctly"? Is payloadLength a few bytes longer than the actual string? - Michael Roland
Hey @MichaelRoland! Printing record gives me the letter 'U' Interestingly when I call Serial.println(data) in the request function it prints something different from when i call it after the char assignment loop. Example: if the tag is written with the contents "abc" and "def", Serial.print, prints "abc" and "def" to the console when called in loop. When it's called in the request function it prints "f" twice. I just figgured this even differs whenever I add or remove other Serial prints in the request function. WIth that tag, payloadLength is 4 for each record. - lasse

1 Answers

1
votes

Based on the information that you provided in your comment, the tag contains a URI record (that, in turn, contains your data). The problem in your code is that you directly use the full payload of the URI record as a string. However, a URI record contains more that just a string. Particularly, the first byte of the payload is the prefix byte (typically a non-printable character). So it seems that Serial.println() (or rather your serial receiver) simply skips that character. client.print() will include that character into the HTTP request and will consequently create an invalid HTTP request (that looks as if the remaining bytes were omitted).

Therefore, you will have to follow the URI Record Type Definition to decode the payload into a proper URI before using it:

NdefMessage message = tag.getNdefMessage();
for (int i = 0; i < message.getRecordCount(); ++i) {
    NdefRecord record = message.getRecord(i);
    if (record.getType() == "U") {
        String uri = "";
        int payloadLength = record.getPayloadLength();
        if (payloadLength > 0) {
            byte payload[payloadLength];
            record.getPayload(payload);
            
            switch (payload[0]) {
                case 0x000: break;
                case 0x001: uri += "http://www."; break;
                case 0x002: uri += "https://www."; break;
                case 0x003: uri += "http://"; break;
                case 0x004: uri += "https://"; break;
                case 0x005: uri += "tel:"; break;
                case 0x006: uri += "mailto:"; break;
                case 0x007: uri += "ftp://anonymous:anonymous@"; break;
                case 0x008: uri += "ftp://ftp."; break;
                case 0x009: uri += "ftps://"; break;
                case 0x00A: uri += "sftp://"; break;
                case 0x00B: uri += "smb://"; break;
                case 0x00C: uri += "nfs://"; break;
                case 0x00D: uri += "ftp://"; break;
                case 0x00E: uri += "dav://"; break;
                case 0x00F: uri += "news:"; break;
                case 0x010: uri += "telnet://"; break;
                case 0x011: uri += "imap:"; break;
                case 0x012: uri += "rtsp://"; break;
                case 0x013: uri += "urn:"; break;
                case 0x014: uri += "pop:"; break;
                case 0x015: uri += "sip:"; break;
                case 0x016: uri += "sips:"; break;
                case 0x017: uri += "tftp:"; break;
                case 0x018: uri += "btspp://"; break;
                case 0x019: uri += "btl2cap://"; break;
                case 0x01A: uri += "btgoep://"; break;
                case 0x01B: uri += "tcpobex://"; break;
                case 0x01C: uri += "irdaobex://"; break;
                case 0x01D: uri += "file://"; break;
                case 0x01E: uri += "urn:epc:id:"; break;
                case 0x01F: uri += "urn:epc:tag:"; break;
                case 0x020: uri += "urn:epc:pat:"; break;
                case 0x021: uri += "urn:epc:raw:"; break;
                case 0x022: uri += "urn:epc:"; break;
                case 0x023: uri += "urn:nfc:"; break;
                default: break;
            }
            for (int j = 1; j < payloadLength; ++j) {
                uri += (char)payload[j]; // NOTE: this is wrong since the string is UTF-8 encoded (but we translate it byte-by-byte)
            }
        }
        Serial.println(uri);
        request(uri);
    }
}

Be aware that there a still a few issues with the solution above:

  • The string part of the URI (bytes starting at offset 1) is actually UTF-8 encoded. However, we treat it as if it was (more or less) ASCII encoded here. I haven't found any simple library to properly transform byte arrays into a UTF-8 encoded string representation on Arduino though.
  • If your resulting URI contains characters that have special meaning in URLs (such as "/", ":", "&", etc.), you would need to urlencode (see e.g. here) them before appending the string to the GET request. Otherwise, the resulting URI of the GET request may not be what you expected.
  • String is certainly not the best choice in terms of memory efficiency.