This tutorial introduces a Processing interface sketch provides a GUI for the command-line interface written into the Arduino RFID example. You should read that tutorial first. The sketch shown here also allows you to upload tags it reads to O’Reilly’s Emerging Technology Conference attendee portal, and retrieves the resulting profile. The API for this was written by Edd Dumbill. The Processing sketch retrieves RFID tags from the Arduino reader serially, then passes the tag via a HTTP request to a PHP script on a remote server, shown below, that adds an authorized login to the O’Reilly site.
Caveat: this tutorial was written specifically for the RFID workshop at Etech 2009. If you’re doing this on your own, the uploader won’t work because your tags won’t be associated with records in the O’Reilly database, and the PHP script that it calls probably won’t be active on my site anymore. But you could build your own version on your own server. The PHP code that follows below gives you a start on that process, and the Processing code below can make a HTTP call to any web address you give it.
The entire sketch can be downloaded here:
The Processing Interface
Attach the Arduino-based RFID reader to your computer before you start, because this sketch interfaces with it via a USB-to-serial link. When you run the Processing sketch that follows, you’ll get a graphic interface that allows you to read tags, delete and print the reader’s database, and upload the tags to the O’Reilly Emerging Tech database and retrieve the user associated with the tag.
Initially, the screen looks like Figure 1:
After you scan a few tags, you’ll see a list in the Tags to upload field:
Finally, if you click upload, the GUI will retrieve a record from the O’Reilly site, like so:
The Processing Code
Now that you’ve got a picture of the basic functionality, here’s the code. It’s divided into three tabs in the Processing sketch. The main tab provides functionality to communicate with the Arduino module. The buttons tab provides methods for making and drawing user buttons on the screen. It’s basically the same as the buttons tab in the RFID writer tutorial. The profiler tab provides the functions necessary to make the HTTP request to the PHP scrips below, and to parse the XML record that comes back from the request.
You’ll need to import the serial library to access the serial port. In addition, there are a few global variables at the top of the main tab to do some housekeeping, like manage the text in teh screen, keep track of the tags you’ve read from the reader, and save your own tag to a file on your computer so the sketch knows who you are.
import processing.serial.*; Serial myPort; // the serial port int fontHeight = 14; // font for drawing on the screen String messageString; // the main display string int lineCount = 0; // count of lines in messageString int maxLineCount = 5; // largest that lineCount can be String tagsToUpload = ""; // CSV string of hex-encoded RFID tags boolean identifyingSelf = false; // whether you're scanning your own tag String myTagId = ""; // your tag ID
The setup() method initializes the serial port and the font for drawing text on the screen, makes the user interface buttons, and checks to see if there’s a file with your RFID tag saved in the sketch folder.
void setup() { // set the window size: size(600,400); // list all the serial ports: println(Serial.list()); // based on the list of serial ports printed from the //previous command, change the 0 to your port's number: String portnum = Serial.list()[0]; // initialize the serial port: myPort = new Serial(this, portnum, 9600); // clear the serial buffer: myPort.clear(); // only generate a serialEvent() when you get a newline: myPort.bufferUntil('n'); // create a font with the second font available to the system: PFont myFont = createFont(PFont.list()[2], fontHeight); textFont(myFont); // initalize the message string: messageString = "waiting for reader to resetn"; // make the UI buttons: makeButtons(); // get the user's ID if it's saved: String[] savedData = loadStrings("yourID.txt"); if (savedData != null) { if (savedData.length > 0) { myTagId = savedData[0]; } } }
The draw() method is very simple. It calls two routines to draw the user interface buttons and the user profile if one has been retrieved from the web, and draws the text on the screen to let you know what’s been communicated to and from the reader.
void draw() { // clear the screen: background(0); // draw the UI buttons: drawButtons(); // show the last obtained user profile: showProfile(); // draw the message string and tags to upload: textAlign(LEFT); text("Your tag ID:" + myTagId, 10, 30); text(messageString, 10, 50,300, 130); text("Tags to upload:", 10, 220); text(tagsToUpload, 10, 240,500, 130); }
The serialEvent() method is called automatically every time there is new serial data available. The serial library buffers the serial data until a newline character (n) is received, then it generates the serialEvent(). The method itself calls another method called parseForTag() to look for an RFID tag string in the incoming data. If it finds one, it checks to see if you requested that this be saved as your own tag. If so, it saves the tag to a file in the sketch folder. If not, it adds it to a string of received tags:
void serialEvent(Serial myPort) { // read the serial buffer: String inputString = myPort.readStringUntil('n'); // if there's something there, act: if (inputString != null) { // see if the newest line contains an RFID tag: String newTag = parseForTag(inputString); // if you got a tag, add it to the list to upload: if (newTag != null) { if (identifyingSelf) { myTagId = newTag; identifyingSelf = false; String[] dataToSave = new String[1]; dataToSave[0] = myTagId; saveStrings("yourID.txt", dataToSave); } else { // add a comma if there's already text in the string: if (tagsToUpload != "") { tagsToUpload += ","; } tagsToUpload += newTag; } } // display the incoming lines, 5 lines at a time: if (lineCount < maxLineCount) { messageString += inputString; lineCount++; } else { messageString = inputString; lineCount = 0; } } }
The aforementioned parseForTag() method scans each line of text received and looks for a colon. It assumes anything after the colon is an RFID tag string. When it finds a tag, it returns it:
String parseForTag(String thisString) { String thisTag = null; // separate the string on the colon: String[] tagElements = split(thisString, ":"); // if you have at least 2 elements, extract the parts: if (tagElements.length > 1) { // get the record number: int recordNumber = int(tagElements[0]); // if the tag ID is not "0000", get it: if (!tagElements[1].substring(0,4).equals("0000")) { thisTag = tagElements[1]; thisTag = trim(thisTag); } } return thisTag; }
A method called buttonPressed() is called when the user releases the mouse button over one of the user interface buttons. It determines which button was pressed and takes the appropriate action:
void buttonPressed(RectButton thisButton) { // get the button number from the button passed to you: int buttonNumber = buttons.indexOf(thisButton); // do different things depending on the button number: switch (buttonNumber) { case 0: // get tags from reader: tagsToUpload = ""; myPort.write("p"); break; case 1: // upload tags to net: if (tagsToUpload.equals("")) { messageString = "No tags to upload."; } else { String[] theseTags = split(tagsToUpload, ","); for (int thisTag = 0; thisTag< theseTags.length; thisTag++) { makeRequest(theseTags[thisTag]); } // after you upload, clear tagsToUpload: tagsToUpload = ""; } break; case 2: // delete tags from reader: messageString = "deleting reader databasen"; myPort.write("c"); break; case 3: // scan your own tag identifyingSelf = true; messageString = "Waiting for your personal tag"; } }
That’s the end of the main tab.
The profiler tab contains the functions necessary to upload a tag to the web and retrieve the results. It starts with some global variables to store the URL of the PHP script and the results of the retrieved user record:
// the URL of the PHP script that passes the tag to the database: String myUrl = "http://tigoe.net/workshops/etech09/lookupTag.php?tag="; PImage photo; // String containing the photo URL String[] profile; // String array containing the HTML of the profile String username; // String of the username String affiliation; // String of the affiliation String twitter; // String of the twitter username String tagNumber; // String of the user tag number String country; // String of the country int profileX = 350; // horizontal position of the profile int profileY = 150; // vertical position of the profile
The makeRequest() method adds the RFID tag string to the end of the URL string, makes the HTTP call, and waits for the results. Then it saves the results in a file. Finally, it parses the results for a valid XML record using the parseRecord() method:
// This method makes the HTTP request to the PHP script // that calls the O'Reilly database and stores it // in a file: void makeRequest(String whichTag) { // make HTTP call: String thisUrl = myUrl + whichTag; // save the resulting file in an array: String[] httpRequest = loadStrings(thisUrl); saveStrings("person.xml", httpRequest); // parse the results: parseRecord("person.xml"); }
The parseRecord() method does just that: it opens the file created by the previous method and parses it for the XML data. Processing’s XMLElement object always reads from a file, so that’s why the two methods use a file to exchange the record. Before parsing the XML, this method checks to see that the file begins with an XML header. If it doesn’t, the method stops, clears the record variables and returns. It does this because the O’Reilly API sometimes returns HTTP error messages instead of XML, for example, if you give it a tag that’s not in the database.
void parseRecord(String filename) { // get the first line, make sure it's an XML file. // if not, skip the rest of the substring and return: String firstLine = loadStrings(filename)[0]; if (!(firstLine.substring(0, 5).equals("<?xml"))) { // clear the profile variables: photo = null; profile = null; username = null; affiliation= null; twitter = null; tagNumber = null; country = null; return; } // open the XML record: XMLElement xml = new XMLElement(this, filename); int lines = xml.getChildCount(); // parse the record line by line: for (int i = 0; i < lines; i++) { XMLElement thisRecord = xml.getChild(i); String fieldName = thisRecord.getName(); String content = thisRecord.getContent(); if (fieldName.equals("photo")) { photo = loadImage(content); } if (fieldName.equals("profile")) { profile = loadStrings(content); } if (fieldName.equals("name")) { username = content; } if (fieldName.equals("affiliation")) { affiliation = content; } if (fieldName.equals("twitter")) { twitter = content; } if (fieldName.equals("rfid")) { tagNumber = content; } if (fieldName.equals("country")) { country = content; } } }
The final method in the profiler tab, showProfile(), draws the profile information to the screen. It’s called by the draw() method:
void showProfile() { // text color: fill(0); int lineNumber = profileY; textAlign(LEFT); // show profile results if the profile variables are populated: if (photo != null) { image(photo, profileX, lineNumber); lineNumber = lineNumber + 130; } // not displaying profile because it's all HTML and I'm too lazy to // strip out the bio div. It's a good goal for someone else if (username != null) { text(username, profileX, lineNumber); // increment the line vertical position: lineNumber = lineNumber + fontHeight+4; } if (affiliation != null) { text(affiliation, profileX, lineNumber, width - profileX, 40); // increment the line vertical position: lineNumber = lineNumber + 3*(fontHeight+4); } if (twitter != null) { text(twitter, profileX, lineNumber); // increment the line vertical position: lineNumber = lineNumber + fontHeight+4; } if (tagNumber != null) { text(tagNumber, profileX, lineNumber); // increment the line vertical position: lineNumber = lineNumber + fontHeight+4; } if (country != null) { text(country, profileX, lineNumber); // increment the line vertical position: lineNumber = lineNumber + fontHeight+4; } }
That’s the main code for the sketch. The final tab in the sketch that generates the buttons. It’s based on Processing‘s buttons example (in the File menu –> Examples –> Topics –> GUI –> Buttons). For more details, see that example.
Here’s the sketch in its entirety:
The PHP script
The PHP script that the Processing sketch is pretty simple. It uses PHP’s cURL library to access the O’Reilly database and return the results. It doesn’t do any parsing, just passes the results straight back to the Processing sketch. It’s divided up into two files, to keep the password and username more secure. The access file looks like this:
<?php $username=urlencode('you@yourserver.com'; // the email address you registered with $password=urlencode('l33+-h4x0r'); // your password ?>
The main PHP script is as follows:
<?php include_once('access.php'); // Where's the database: $url="https://en.oreilly.com/event/20/admin/api/rfid/show/"; // get the tag from the call to this script: $url .= $_GET["tag"]; // initialize curl: $ch = curl_init(); // set up the URL: curl_setopt($ch, CURLOPT_URL, $url); curl_setopt ($ch, CURLOPT_SSL_VERIFYHOST, 0); curl_setopt ($ch, CURLOPT_SSL_VERIFYPEER, 0); // prepare to make a POST call: curl_setopt($ch, CURLOPT_POST, 1); // set up authentication: curl_setopt($ch, CURLOPT_HTTPAUTH, CURLAUTH_BASIC); curl_setopt($ch, CURLOPT_USERPWD, "$username:$password"); /* censored line goes here */ // print the result: if (curl_errno($ch)) { echo "CURL Error: " . curl_error($ch); } else { // Show the result var_dump($response); curl_close($ch); } ?>
You may notice a comment there that says /* censored line goes here */. For reasons beyond my control, I cannot enter the curl command that goes there. It could be that WordPress censors this line, or it could be that dreamhost does it. Either way, you’ll have to work out the line for yourself, but here’s a hint:
$response equals curl underscore exec ($ch) semicolon
If you want to duplicate this on your own server, you’ll need to change the URLs in the Processing sketch and the PHP script, of course, and you’ll have to make a database to replace the O’Reilly one that generates its own XML records. The XML records look like this:
<?xml version="1.0" encoding="UTF-8"?> <attendee> <photo>http://assets.en.oreilly.com/1/eventprovider/1/_@user_2071.jpg</photo> <profile>http://en.oreilly.com/et2009/profile/2071</profile> <name>Tom Igoe</name> <affiliation>Interactive Telecommunications Program, NYU</affiliation> <twitter>tigoe</twitter> <rfid>66bd00c0</rfid> <country>US</country> </attendee>
That’s it! Happy tagging.