Qt Webkit with Proximity Smart Card (RFID) on Freescale i.MX51
Published on March 4, 2011
In the previous two blog posts, we described how you can integrate a mag stripe reader and barcode scanner into the
Qt Webkit framework by using the Qt Webkit Bridge.In this post, we'll build upon those two examples by integrating a proximity smart card (RFID) reader, which is
somewhat more complicated because it allows sending commands as well as receiving responses.We'll use the very nice SL015B reader from Stronglink for this example, but the approach should apply to many applications which require bi-directional communications from web applications.
In order to follow the details, you'll want to grab a copy of the User's manual.Before delving into the code, we should look closely at some of the key characteristics of the RFID reader and cards. I know that the cards supported by the Stronglink device aren't actually RFID cards, but proximity smart cards, but RFID is much easier to type (and say). I also understand that the Stronglink device is a reader/writer, but I'll refer to it as a reader for brevity.Looking at the Stronglink manual, it's clear that all communication is of the form request->response
. All commands are initiated by the host (in this case, our Nitrogen board. Each response from the reader contains a reference to the command along with the status and optional data.Since our target application environment is the killer web-app, one of the primary design criteria will be how to handle the asynchronous nature of request from the app. When we send a command to the reader, we can't just block waiting for a response or that fancy animation our app is doing will visibly choke. Furthermore, many operations we'd like to perform on a card will require multiple message exchanges. To top things off, these are proximity smart cards and can be removed or presented in the middle of an operation, so we'd like to be able to handle a multi-message operation gracefully.Let's examine a couple of mechanisms we might use. As a test case, we'll use a simple example of a two message exchange:
- Login to a sector (command 0x02)
- Read block 0
In each of the examples, I'll separate out the command code and data on the transmit side and the response code from optional data on the receive side. I'll also assume that the rfid
object will handle checksum calculation and inserting the message length.
The simplest implementation: it's all up to the app
The simplest implementation is to simply leave everything up to the application and expose a send()
routine and a receive()
handler.[sourcecode language="plain"]rfid.receive = function(responseCode, data){<br />
console.log("rfid rx:"+responseCode.toString(16)+":"+escape(data)+"n");<br />
// switch based on state of communication?<br />
}<br />
// send sector 0, key A, key<br />
rfid.send(0x02, "x00xAAx01x02x03x04x05x06");<br />
[/sourcecode]In this case, the send
method could be directly implemented in C++ and the single receive
routine could be connected to a signal (i.e. it could be a slot).I'm not going to provide pseudo-code for the response handler because this interface doesn't seem to scale very well. If you try to use the handler for application code, you'll likely end up with a mess. This approach could be used as the basis for a higher-level interface, but that would simply be moving the issue from C++ to Javascript.If you consider the need to poll for card presentation and removal, it doesn't seem that a garbage-collected Javascript is the right place to do this.
Keeping it together
A simpler interface for the application is probably something like this:
rfid.send( 0x02, "x00xAAx01x02x03x04x05x06", function(responseCode, responseData){ console.log( "received response: "+responseCode.toString(16)+":"+responseData); } );
That is, for each transmission to the card, we define a anonymous handler function. This will allow apps to keep the handler close to its' origin and improve overall readability.It still might be a problem though.If you extend this to the sector login + read block example, you'd naturally end up with something like this:
rfid.send( 0x02, "x00xAAx01x02x03x04x05x06", function(responseCode, responseData){ console.log( "received login response: "+responseCode.toString(16)+":"+responseData); if (0 === responseCode) { rfid.send (0x03, "x00", function(responseCode, responseData){ console.log( "received block read response: "+responseCode.toString(16)+":"+responseData); }); } );
This might not be too bad for a single block read, but if you scatter many of these around your application, or if you need to read and write multiple blocks, you'll probably want something different.
Starting from the top
Let's back up a bit and see where we really want to be. Ignoring the details of the actual card reader, we probably want to do something like this in our application:
rfid.read_block( blockNum, function(blockData){ console.log("read block"+blockNum.toString(16)+":"+escape(blockData)); }, function(errorMsg){ console.log("error "+errorMsg+" reading block"+blockNum.toString(16)); } );
That is, provide a single high-level operation that occurs atomically or not at all. Because of the nature of the RFID connection, we can't guarantee atomicity, but we can support a single success handler and a single failure handler through some mixture of C++ and Javascript. For our simple example, the response code from the reader could simply invoke either the success
or error
function based on the returned status. Because the Stronglink reader does a nice job of always using 0
to indicate success, this works pretty well.We could define the read_block
routine in Javascript and keep track of the outstanding requests in some form of list.
We could even do all of this in that single simple handler we described at the outset.Our simple example is probably too simple to be generalized, though. If you think through what would be necessary in a more complex example like a multi-block read or a multi-block write, some things become clear:
- A request list is needed,
- Some of the responses may contain optional data,
- We'll need to coordinate these requests with polls to see if the card is still present.
The third item in the list is the clincher for me. I'd really like to keep device polling in the C++ layer to allow an application which only needs the serial number of a card to something like this:
rfid.cardPresent.connect(function(serialNum,cardType){
console.log("card present: "+escape(serialNum)+":"+cardType);
});
rfid.cardRemoved.connect(function(){
console.log("card removed");
});
If the C++ layer is doing the polling for card presentation and removal, it really needs to know whether the application is in the middle of a multi-message transaction.And by that reasoning, the C++ layer should have a list of requests.
A design takes shape
If you've followed me this far, there's not too much further to go before digging into the code.
- Native (C++)
send
routine will accept an array of requests, - Native (C++) will provide four signals:
cardPresent
. This signal will be fired each time a card is presented and will supply the serial number and card type.cardRemoved
. This signal will be fired each time a card is removed.success
. This signal will be generated once after the last message in a transaction succeeds. Data returned from each of the messages in a transaction will be accumulated in an array of the same length as the request array for processing on the Javascript side.failure
. This signal will be generated when any message in a transaction fails. If data was gathered during preceding messages in the transaction, it will be returned to the Javascript code for processing.
Either the
success
orfailure
signal will be generated once for each request made to thesend
method. - If a transaction is in progress when a card removal event occurs, the
failure
signal will be emitted and thecardRemoved
signal will be emitted in that order. - If
send
is called when no card is present, thefailure
signal will be generated immediately. - A convenience method
isPresent
will be made available.
An implementation takes shape
Alright, this is now done, and I only diverged a bit from the design.
- Instead of having separate
success
andfailure
methods, I implemented a singleresponse()
method that is handed a response code and set of zero or more message results. - I implemented two forms of
send()
.- The first accepts two parameters: a message type (integer command code) and optional data.
- The second accepts one parameter: an array of objects of the form
{code:0x01, data:"x01x02x03"}
. This form will send each of the pieces until something fails or the response to the last message is parsed.
This handler code now works:
rfid.cardPresent.connect(function(serialNum,cardType){ console.log("card present: "+escape(serialNum)+":"+cardType); }); rfid.cardRemoved.connect(function(){ console.log("card removed"); }); rfid.response.connect(function(result,msgParts){ console.log("response code: 0x"+result.toString(16)); try { for (var i = 0 ; iActually, it doesn't completely work. The
$.toJSON()
always returns an empty string and I haven't yet pursued why.Note that the
response
handler receives a single result code and an array of message objects. Each message object will contain the data returned from the associated piece of the request array.As a convenience, I introduced a Javascript console to the test browser in this iteration. It turns out that reading from
stdin
and evaluating a string in the Javascript are incredibly easy in Qt. By using this, I was able to test
the interfaces from the console instead of writing a bunch of HTML scaffolding. The following are some test cases and their output.Select a card once
The first line is what I typed in to
stdin
and the remaining lines are the output. The call tosend
uses two parameters to differentiate it from thearray
form. The command code is aselect card
for simplicity:rfid.send(1,"") https://10.0.0.1/testrfid.html:23:response code: 0x0 https://10.0.0.1/testrfid.html:27:0 https://10.0.0.1/testrfid.html:30:[0]: 0x0:l%CFa%7E%01 https://10.0.0.1/testrfid.html:35:response received: {}Things to note in the output include:
- Line 27 shows the global result code (0). This is repeated in the single fragment on line 30. It turns out that the global response code will always be the value of the last fragment, but it seems more intuitive to separate it.
- The JSON output on line 35 is bogus. Why?
- The data returned in the output is escaped. This is done because I haven't figured out how to push binary data through the Qt Webkit bridge.
Select a card four times
Again, this example uses the select card
command for simplicity:
rfid.send([{code:1},{code:1},{code:1},{code:1},{code:1}]) https://10.0.0.1/testrfid.html:23:response code: 0x0 https://10.0.0.1/testrfid.html:27:0 https://10.0.0.1/testrfid.html:30:[0]: 0x0:l%CFa%7E%01 https://10.0.0.1/testrfid.html:27:1 https://10.0.0.1/testrfid.html:30:[1]: 0x0:l%CFa%7E%01 https://10.0.0.1/testrfid.html:27:2 https://10.0.0.1/testrfid.html:30:[2]: 0x0:l%CFa%7E%01 https://10.0.0.1/testrfid.html:27:3 https://10.0.0.1/testrfid.html:30:[3]: 0x0:l%CFa%7E%01 https://10.0.0.1/testrfid.html:27:4 https://10.0.0.1/testrfid.html:30:[4]: 0x0:l%CFa%7E%01 https://10.0.0.1/testrfid.html:35:response received: {}
Note that the optional data isn't necessary in this form. It's also possible that I'll want to deprecate the two parameter version because this:
rfid.send(1,'')
Isn't that much clearer than this:
rfid.send([{code:1}]);
Error example
As a final example, here's the output from a three-fragment transaction where the second transaction (a block read request) fails because we haven't logged in to that sector:
rfid.send([{code:1},{code:3,data:"x00x01x02x03"},{code:1}]) https://10.0.0.1/testrfid.html:23:response code: 0xd https://10.0.0.1/testrfid.html:27:0 https://10.0.0.1/testrfid.html:30:[0]: 0x0:l%CFa%7E%01 https://10.0.0.1/testrfid.html:27:1 https://10.0.0.1/testrfid.html:30:[1]: 0xd: https://10.0.0.1/testrfid.html:35:response received: {}
Note that the global response code on line 23 shows a value of 0x0d
or Not authenticate and that two elements are available in the returned result since only two messages were sent.This is probably enough for one blog post. We didn't quite get to the ideal block read
and block write
implementation, but we're not far.If you made it through this entire blog post, shoot us a note and give some feedback.The sources are available here.