JavaFX and Near Field Communication on the Raspberry Pi

Learn how to use Java SE 8 to communicate with a near field communication (NFC) card reader, show a status message on a screen, or send information to a back-end system.
Use your Java skills to create end-to-end applications that span card readers on embedded devices to back-end systems.
JavaFX has already proven to be an excellent platform running on the Raspberry Pi. If you are working on client-side Java development on embedded devices, you probably know that JavaFX and the Raspberry Pi make for a great combination. In a number of cases, embedded devices are used in kiosk environments. A card reader is connected to an embedded device, and users have to scan a card to initiate an action—for example, to be allowed to enter a room, pay for something, or gain some credits.
There are different types of card readers, different types of cards, and different protocols. In this article, we look at how Java SE 8, which is available for the Raspberry Pi, can be used to communicate with a near field communication (NFC) card reader, show a status message on a screen, or send information to a back-end system.
Note: The source code for the examples shown in this article is available at https://bitbucket .org/johanvos/javafx-pi-nfc.
Components of Our System
The NFC specification defines a number of standards for the near field communication between devices, including radio communication between devices that are close to each other. Various cards are equipped with a chipset. In this article, we will use a MIFARE DESFire card.
You can use your existing Java skills to create end-to-end applications that span different environments.
A card reader needs to be chosen that works out of the box on the Raspberry Pi. The USB-based ACR122U card reader from Advanced Card Systems Ltd. is a widely used device that can easily be connected to the Raspberry Pi and allows it to read MIFARE cards.
Note: It is recommended that a powered USB hub be used for connecting the card reader to the Raspberry Pi, because card readers consume a lot of power.
In order to have visual feedback, we need to attach a screen to the Raspberry Pi. A variety of screens are available, including a regular HDMI monitor and smaller screens suited for cars, kiosks, and copiers. Although this is the setup we will use in this article, different setups are possible.
Set Up the Raspberry Pi
In order to run the code provided for this project, you need a working setup. We will use an image for the Raspberry Pi that is available for download here.
In order to have the ACR122U card reader work with this image, you have to install two additional packages. Run the following commands to do this:
sudo apt-get update sudo apt-get install libpcsclite1 pcscd
Install Java
Download Java SE 8 and install on the Raspberry Pi.
Java SE comes with a
package, javax.smartcardio
, that allows
developers to communicate with smart card readers. By default, this
package will look for a personal computer/smart card (PC/SC)
implementation on the system.
Now that you have installed the required Linux packages on your
Raspberry Pi, the only remaining task is that we have to point the
Java Virtual Machine to the location of
the pcsclite
library.
This is done by setting the system
property sun.security.smartcardio.library
to
the location of the pcsclite
library as
follows:
java -Dsun.security.smartcardio .library=/path/to/libpcsclite.so
The JavaFX Application
Making a connection with a card reader and reading cards are operations that can take some time. Hence, it is not a good idea to execute those tasks at the JavaFX application thread level. Instead, we will create a dedicated thread that will deal with the card reader and uses the standard JavaFX way to update the user interface.
In a kiosk, a card reader is connected to an embedded device, and users scan a card to initiate an action.
Creating the user interface. We will
create a very simple user interface that shows the identifier of
the latest successfully scanned card. This identifier is stored
in StringProperty latestId
, as follows:
private StringProperty latestId = new SimpleStringProperty(" --- ")
Our simple user interface contains only
an HBox
with a label containing some static
information followed by a label containing the identifier of the
latest scanned card. It is constructed as shown in Listing 1.
HBox hbox = new HBox(3); Label info = new Label("Last checkin by: "); Label latestCheckinLabel = new Label(); latestCheckinLabel.textProperty().bind(latestId); hbox.getChildren().addAll(info, latestCheckinLabel); Scene scene = new Scene(hbox, 400, 250); primaryStage.setScene(scene); primaryStage.show();
Listing 1
Communicating with the card reader. The
thread responsible for the communication with the card reader is
created in a function calleddoCardReaderCommunication
,
which is called in the start()
method of the
JavaFX application. A JavaFX Task
is
created, assigned to a new thread, and started, as follows:
Task task = new Task() { … }; Thread thread = new Thread(task); thread.start();
This Task
performs the following
work:
- It detects a card reader.
- It detects a card being recognized by the card reader.
- It reads the identifier from the card.
- It updates the value of
latestId
. - It waits for the card to be removed.
- It goes back to Step 2.
Detecting a card reader. Before we can
access a card reader we need a TerminalFactory
.
The javax.microcard
package provides a
static method for getting a
default TerminalFactory
, and it allows us to
create
more-specific TerminalFactory
instances for
different types of card readers. The
default TerminalFactory
will use the PC/SC
stack that we installed on the Raspberry Pi, and it is obtained by
calling the code shown in Listing 2. Using
the code shown in Listing 3, we can
query terminalFactory
to detect the card
readers that are installed.
TerminalFactory terminalFactory = TerminalFactory.getDefault();
Listing 2
List<CardTerminal> cardTerminalList = terminalFactory.terminals().list();
Listing 3
In this case, our setup was successful,
and cardTerminal List
contains one element.
This CardTerminal
is obtained by running the
code shown in Listing 4.
CardTerminal cardTerminal = cardTerminalList.get(0);
Listing 4
Detecting cards. Once we have a reference to the card reader, we can start reading cards. We will do this using the infinite loop shown in Listing 5. In real-world cases, a dummy card could be used to stop reading.
while (true) { cardTerminal.waitForCardPresent(0); handleCard(cardTerminal); cardTerminal.waitForCardAbsent(0); }
Listing 5
The thread will block until the reader detects a card. Once a card is detected, we will handle it as discussed in the next section. Then, the thread will block again until the card is removed.
Handling
cards. The handleCard
method
is called when we know that a card is presented to the card reader.
Note that a card can be removed during the process of reading, and
proper exception handling is required.
A connection to the card is obtained by calling the code shown in Listing 6.
Card card = cardTerminal.connect("*");
Listing 6
Before we can read the identifier on the card, we need to know what type of card we are reading. Different card types use different addresses to store the identifier. In our simple demo, we will limit ourselves to MIFARE DESFire cards, but the example can easily be extended to other card types.
Information on the type of card we are reading can be obtained by inspecting the ATR (answer-to-reset) bytes of the card as follows:
ATR atr = card.getATR();
We compare the bytes in this atr
object
with the bytes we expect on a DESFire card, as follows:
Arrays.equals( desfire, atr.getBytes());
As shown in Listing
7, desfire
is a static byte array
containing the information we expect.
static byte[] desfire = new byte[]{0x3b, (byte) 0x81, (byte) 0x80, 0x01, (byte) 0x80, (byte) 0x80};
Listing 7
If we have a supported card type (that is, a DESFire card), we
can query the identifier. In order to do so, we need to transmit an
application protocol data unit (APDU) command over the basic logic
channel of the card, and then read the response. Using
the javax .microcard
package, this is done
as shown in Listing 8.
CardChannel channel = card.getBasicChannel(); CommandAPDU command = new CommandAPDU(getAddress); ResponseAPDU response = channel.transmit(command); byte[] uidBytes = response.getData(); final String uid = readable(uidBytes);
Listing 8
The getAddress
parameter we are passing
is a static byte array that defines how to read the identifier for
a DESFire card, and it is declared as shown in Listing
9. Different card types might require a different byte
array.
static byte[] getAddress = new byte[]{(byte) 0xff, (byte) 0xca, 0, 0, 0};
Listing 9
The function readable(byte[])
translates
the byte array into a more human-readable format by converting each
byte to its
hexadecimal String
representation
(see Listing 10).
private static String readable(byte[] src) { String answer = ""; for (byte b : src) { answer = answer + String.format("%02X", b); } return answer; }
Listing 10
Now that we have a readable identifier for the card, we need to
show the identifier in the user interface. Because the identifier
is obtained on a thread different from the JavaFX application
thread (that is, from the communication thread), we need to make
sure that we don’t change the latestId
StringProperty
directly from the communication thread.
Rather, we need to put the change on the JavaFX application thread
as shown here.
Platform.runLater(new Runnable() { public void run() { latestId.setValue(uid); } });
This separation of concerns also guarantees that the communication with the card reader is not disturbed by activity in the user interface and vice versa.
Sending the data to a back end. In many cases, visual feedback is required when a reader scans a card. In other cases, the information needs to be sent to a back-end system. In the next section, we will send data to the back end using a very simple client-server module by which identifiers are sent from the Raspberry Pi to a Java EE 7 back end and then visualized on a web page. Sending data from a JavaFX application to a REST-based back-end system is easily done using DataFX.
The code shown in Listing 11 will
send the identifier uid
to a REST endpoint
at http:// 192.168.1.6:8080/webmonitor/ rest/card/checkin.
private void sendToBackend(String uid) { RestSource restSource = RestSourceBuilder.create() .host("http://192.168.1.6:8080") .path("webmonitor") .path("rest/card/checkin") .formParam("id", uid) .build(); ObjectDataProvider odp = ObjectDataProviderBuilder.create() .dataReader(restSource).build(); odp.retrieve(); }
Listing 11
Note that we will use the POST
method,
because a form parameter is specified in the REST request. This
method should be called whenever a new identifier is read. Because
DataFX deals with the threading, we have to call this method from
the JavaFX application thread. Hence, we can do this in the same
code block in which we set the value of
the latestId
property
(see Listing 12).
Platform.runLater(new Runnable() { public void run() { latestId.setValue(uid); sendToBackend(uid); } });
Listing 12
In real-world applications, the server might send additional information, such as the real name of the user of the card, back to the client. The client application can use this information to make the user interface more personal.
The Back End
We will now create a very simple back end that accepts the REST requests and shows them on a web page. Because it is not assumed that the user of the web page knows when cards are being scanned, it makes sense to dynamically update the web page whenever a card has been scanned. This can easily be done leveraging the WebSocket API in Java EE 7. It is surprising how little code is required for doing this.
It’s easy to create an embedded Java application, enrich it with a simple JavaFX user interface, connect it to a card reader, connect it to a Java EE system, and visualize the information using HTML5.
We will start with the web page, which is a simple HTML page
with a division named checkins
that will
contain the list of identifiers and a time stamp indicating when
each card scan happened.
When the HTML page is loaded, a WebSocket is created, pointing
to our simple back end. The content of
the checkins
division is populated whenever
a message arrives over the WebSocket. This is easily achieved using
the code shown in Listing 13.
function openConnection() { connection = new WebSocket('ws://localhost:8080/webmonitor/endpoint'); connection.onmessage = function(evt) { var date = new Date(); var chld = document.createElement("p"); chld.innerHTML = date + " : " + evt.data; var messages = document.getElementById("checkins"); messages.appendChild(chld); }; }
Listing 13
Note that the HTML page will open a WebSocket toward
localhost:8080/webmonitor/endpoint, while the JavaFX application
will push identifiers to
localhost:8080/webmonitor/rest/card/checkin. The glue for this is
our single-file back-end system, implemented in a class
named CardHandler
.
Because CardHandler
provides a REST
endpoint, it extends javax
.ws.rs.core.Application
, and it contains
an@ApplicationPath
annotation specifying that the
REST interface is located at the rest
value.
The handler itself is registered at a pathcard
, and
the particular method for receiving identifiers is associated with
the path checkin
. This method also takes a form
parameter named id
, which contains the identifier
(see Listing 14).
@ApplicationPath("rest") @Path("card") public class CardHandler extends Application { @Path("checkin") @POST public Response checkin( @FormParam("id") String id) throws IOException { System.out.println("Received card with id " + id); return Response.ok().build(); }
Listing 14
If we want to send this information to WebSocket clients, for
example, to the simple web page we created in the previous section,
we need to register a WebSocket endpoint as well. We can
leverage CardHandler
for this, because a
class can have multiple annotations. Doing so, we put all the
back-end code into a single file. This is probably not the best
idea for a more complex production system, but by doing it here, we
can see how easy it is to create an enterprise application in Java,
combining REST and WebSockets.
When the WebSocket endpoint receives a connection request, the
created session is stored in a Set
. When the
connection is closed, the session is removed from
the Set
, as shown in Listing
15.
@ServerEndpoint(value="/endpoint") public class CardHandler extends Application { private static Set<Session> sessions = new HashSet<>(); @OnOpen public void onOpen (Session s) { sessions.add(s); } @OnClose public void onClose (Session s) { sessions.remove(s); } }
Listing 15
We can now modify the checkin
method
created in Listing 14, and send the
identifier to the connected WebSocket clients, as shown
inListing 16.
@Path("checkin") @POST public Response checkin(@FormParam("id") String id) throws IOException { System.out.println("Received card with id " + id); System.out.println("sessions = "+sessions); for (Session session: sessions) { session.getBasicRemote().sendText(id); } return Response.ok().build(); }
Listing 16
Conclusion
In this article, we saw how easy it is to create an embedded Java application, enrich it with a simple JavaFX user interface, connect it to external hardware (a card reader), connect it to a Java EE system, and visualize the information using HTML5.
The underlying systems all use the same Java SE code. As a consequence, developers can use their existing Java skills to create end-to-end applications that span different environments.
Originally published in the March/April 2014 issue of Java Magazine. Subscribe today.
About the author
|
Johan Vos started working with Java in 1995. He is a cofounder of LodgON, where he is working on Java-based solutions for social networking software. An enthusiast of both embedded and enterprise development, Vos focuses on end-to-end Java using JavaFX and Java EE.
(1) Originally published in the March/April
2014 Edition of Java Magazine
(2) Copyright © [2014] Oracle.