This article continues our look at how to build adapters and agents to integrate RFID readers into the IBM Websphere RFID solution. Part 1 described the IBM WebSphere RFID solution and the WebSphere RFID Device Infrastructure. In Part 2, you'll learn how to begin implementing an adapter, starting with the bottom layer of the Device Kit, the transport, and use the Sirit INfinity 510 reader as an example.
As described in Part 1, we use the Device Kit component of the WebSphere RFID Device Infrastructure to implement an adapter for a reader. The Device Kit consists of several layers, as shown in Figure 1. We'll begin implementing the adapter by constructing a transport.
Figure 1. Device Kit adapter
In Device Kit, the transport is responsible for converting bytes to more meaningful objects called messages. Bytes received from a device (reader) are parsed into messages and delivered to listeners. Messages passed by an application to the transport are converted into the appropriate bytes and sent to the device.
Transports are also responsible for initiating the connection to the hardware device using the appropriate connection class, and for controlling any start-up conversation required by the device. Start-up conversations may include setting parameters on the reader, logging into the reader, making connections to alternate message channels, and so on.
A transport is responsible for sending heartbeat messages to keep the connection to a hardware device alive.
A transport does not understand the messages it receives and sends; it is concerned with syntax, not semantics. It understands the hardware device protocol enough to parse messages from the input bytes and to ensure that output bytes have the necessary checksums, and so on. The only messages that a transport should really know about are those that are used in start-up conversations and heartbeats.
When integrating a new hardware device into WebSphere RFID Device Infrastructure, you'll create an initial definition of a transport using the Device Kit Transport Creation wizard, and modify certain attributes of the transport by editing a Command Markup Language (CML) file, but you'll also have to write Java™ code to implement a transport specific to that device. Implementing the transport is often the most work when implementing an adapter for a new reader.
Transports and their collaborators
Transports collaborate with several other objects to get their job done. Figure 2 shows the major collaborators.
- One transport (an instance of a subtype of
com.ibm.esc.transport.service.TransportService). - One connection (an instance of a subtype of
com.ibm.esc.connection.service.ConnectionService) that handles the communication to and from the RFID reader. - One controller (an instance of
com.ibm.esc.transport.Controller) that handles forwarding messages, errors, and state changes to transport's listeners, restarts the transport if there is an error, and notifies the transport when to send a heartbeat message. - Zero or more applications that are sources of bytes to write to the device. Applications can be any type.
- Zero
or more listeners that are the receivers of messages parsed by the
transport. Listeners are instances of subtypes of
com.ibm.esc.transport.service.TransportListener. Most often the application and the listener are the same object (an instance of a subclass ofcom.ibm.esc.device.TransportDevice).
Figure 2. Transport collaborators
To illustrate the collaboration between the transport and the other objects, take a look at what happens when an application asks a transport to send a message to a device:
- The
application invokes
send(Messageservice message)on the transport. - The
transport formats the message and invokes
write(byte[] bytes, int offset, int count)on a connection. - The connection writes the bytes to the device.
When a transport receives a message from a device, the flow is considerably more complex, as shown in Figure 3:
- The
transport runs a
ThreaddoingprocessInput()on itself. -
processInput()invokesread(byte[], int, int)on the connection (step 1 in the diagram). - The connection waits for bytes to come from the reader (step 2).
- When
bytes are returned, the transport invokes
processInput(byte[], int)on itself (step 3). -
processInput(byte[], int)parses the bytes into messages. When it finds one, it invokesfireMessageReceived(MessageService, Object)on the transport (step 4). -
fireMessageReceived(MessageService, Object)notifies the controller that a message is available by invokingmessageReceived(TransportService, Object, MessageService)(step 5). - The controller inspects the arriving message and determines whether the transport should be interested in it (using an interest mask that the transport defines).
- If the message is not of interest, it is discarded. Otherwise, the controller queues the received message.
- A
separate
Threadin the controller picks up the message to process it. This has the effect of decoupling message producers from message consumers and prevents thread blocking issues. - The
message is delivered to the listeners registered with the transport by
invoking
messageReceived(TransportService, Object, MessageService)on them (step 6).
Fortunately, in our case, the controller is already implemented for you and you don't have to worry about the details of interest masks, the message queue, and threads.
Figure 3. Transport receiving a message
Transport classes and interfaces
All transports are instances of subtypes of com.ibm.esc.transport.service.TransportService.
In practice, they are instances of subclasses of com.ibm.esc.transport.Transport to
inherit already implemented behavior. There are several subclasses of com.ibm.esc.transport.Transport;
the most common used for RFID readers is com.ibm.esc.transport.ResponseTransport
because most RFID readers implement a command/response model of interaction. A ResponseTransport
blocks writes until the response to the previous write is received, or
the response timeout expires. Messages received from a reader are
classified as responses to a previously sent message, or as non-responses. An
example of a non-response would be an asynchronous event message.
ResponseTransport defines some useful methods, some of
which you need to implement:
-
void write(MessageService message)is responsible for writing bytes to a connection. It should "preprocess" messages to compute checksums, lengths, and so on, required by the protocol. Messages are assumed to be the correct (final) number of bytes to be written. If the protocol does not require modifying messages before writing, you can just keep the default implementation of the method; otherwise you'll need to implement it. -
int processInput(byte[] buffer, int length)implements the parsing of bytes into instances of subtypes ofcom.ibm.esc.message.service.MessageService. You need to reimplement this method for each type of transport since it is where the parsing work is done. The method should process up tolengthbytes frombufferinto messages, and return the number of bytes processed (may be 0 if the buffer does not hold a complete message). As many messages as possible should be processed before returning. -
MessageService noActivityProcessingMessage()returns the message that will periodically be sent to the device as a "heartbeat" when no other activity is taking place. We'll discuss heartbeats below. Each transport must reimplement this method unless there is no heartbeat message. -
int startup(boolean arg)implements any special start-up actions needed (such as sending a login command or starting an asynchronous event handler). You may need to reimplement this method for a given transport. -
void startupMessageReceived(TransportService transport, Object timestamp, MessageService message)implements the processing of messages during the start-up conversation. If the transport implements a start-up conversation, you need to implement this method. We'll discuss start-up conversations below. -
void fireMessageReceived(Object timestamp, MessageService message)is invoked by the transport when it has parsed a response message from the input bytes. -
void fireMessageReceivedNoResponse(Object timestamp, MessageService message)is invoked by the transport to signal that a message it parsed is not a response to the previous output message. -
MessageService getSentMessage()returns the last message sent. It is useful when creating messages to connect a response with the message that generated it. -
void responseReceived()is invoked by the transport to indicate that a response to the previous output message has been received. Normally you don't need to invoke this method, but if you have an error in parsing you may need it. -
void handleError(Throwable error, int resourceId, Object[] objects)and its variants are used to notify the transport listeners that an error has occurred. Theintargument is a resource ID; it's easiest to use one of the IDs already defined in one of theTransportclasses. -
Object EscObject.getCurrentTimestamp()returns the current time as anObjectand is useful for invoking those methods that require a timestamp.
Certain methods are generated automatically from the CML file that defines the transport:
-
ConnectionService getDefaultConnection()returns an instance ofConnectionServiceto connect to the reader. -
long getDefaultResponseTimeout()returns the default response timeout. -
void setup()initializes the transport and connection properties. We'll discuss configuration properties below. -
getX()methods for any custom parameters you defined in the CML. We'll discuss custom parameters below.
As you can see from the list, when you implement a new
transport you will have to implement processInput(byte[], int).
You may have to implement write(MessageService),
noActivityProcessingMessage(),
startup(boolean),
and startupMessageReceived(TransportService, Object, MessageService).
The processInput(byte[], int) method is the
hardest of the group to implement. Device Kit does not provide any generic
parsing aids, so you'll have to write all the code yourself. We suggest that you
look at existing reader adapters with a protocol similar to your reader's for
examples. When we discuss implementing the transport for the Sirit INfinity 510
reader, we'll introduce some helper classes you can use to reduce the work of
parsing text-based message protocols.
The processInput(byte[], int) method should
parse bytes from the buffer, start-to-end, and "consume" as many bytes as
possible each time it is called. Consuming bytes means parsing them into
message objects and invoking the fireMessageReceived(Object, MessageService) or
fireMessageReceivedNoResponse(Object,
MessageService) methods. If your reader's protocol uses protocol
decorations such as checksums or message start/end markers, processInput(byte[], int)
should verify that they are correct and invoke handleError(Throwable, int,
Object[]) if they are not.
The write(MessageService) method accepts
messages that are the correct number of bytes long to write to the device, and
changes bytes associated with protocol decorations before writing. This ensures
that, for example, checksums are always correct for a message.
When a transport has not had any activity (received a
message or written one) for a period of time, its controller instructs it to
send a heartbeat message to the device. This allows the transport to discover
in a timely manner when a connection or a device has failed. It has the side
effect of acting as a "keep alive" message for those connections or devices
that require periodic interaction. The no activity timeout is a configuration
parameter (noactivitytimeout) that
defaults to 10 seconds. The message to send as a heartbeat is defined by the noActivityProcessingMessage() method.
When selecting a heartbeat message, it's important to select a message that
will "do no harm". For example, if your reader has a command to start reading
RFID tags, and another command to stop reading, it's possible that the
heartbeat message could be sent between them. The heartbeat message should not
cause the tag reading to terminate. If noActivityProcessingMessage() returns
null,
no heartbeat message will be sent.
One of the responsibilities of a transport is to engage in
any start-up conversation required by the reader. If the device has a start-up
conversation, you will need to implement the startupMessageReceived(TransportService,
Object, MessageService) method. It will be invoked whenever the
transport parses a message while the transport indicates that it is engaged in
a start-up conversation. The method typically compares the message received with
one or more template messages and, if it matches, sends another message, or ends
the start-up conversation and starts processing input normally. If the transport
needs to send a message to begin the start-up conversation, you'll need to
override the startup(boolean) method to send that message.
A transport goes through a set of states during its
lifetime. Most of the state transitions are handled automatically, but
sometimes you may have to control the state. All of the states are defined as static
constants on TransportService:
- The
CREATEDstate is when the transport has been created or stopped. The transport will not attempt to move to another state until a start request is received. - The
ALIVEstate is when the transport has been told to start. - The
CONNECTEDstate is when the transport is connected to the hardware device. - The
ACTIVEstate is when the transport is attempting to start but is not yet started. Usually this means a start-up conversation is being done with the device. - The
STARTEDstate is when the transport has started and should be processing messages. - The
DEADstate is when the transport has exited and cannot be restarted.
The default implementation of Transport.startup(boolean)
puts the transport into the STARTED state. If you need to
implement a start-up conversation in the transport, startup(boolean)
should put the transport into the ACTIVE state to indicate that a
start-up conversation is underway. When the start-up conversation is done, the transport
should be put into the STARTED state using Transport.setState(int),
probably in the startUpMessageReceived(TransportService, Object, MessageService)
method.
When you create a new transport with the Device Kit Transport Creation wizard, a transport project is created that contains a NameTransport.cml file, where Name is derived from the name of your transport. The CML file contains the definition of various transport parameters. The Device Kit tools allow you to regenerate the skeletal definition of your transport (without losing any code you may have added) based on the contents of the CML file.
Some of the typical subelements of the <transport>
element are:
-
<responsetimeout>defines the response timeout. If this element is present, the Device Kit tools make the transport a subclass ofResponseTransport. -
<noactivitytimeout>defines the time of no activity before a transport sends a heartbeat message. -
<tcpip>or<serial>define the configuration of the transport's default connection. -
<message>elements define messages used by the transport. Defining messages in CML is described in Part 1. -
<customparameter>defines a custom configuration property for the transport.
For a complete description of the transport CML, refer to the Websphere Studio Device Developer documentation (when the RFID Tracking Kit is installed).
Transports have various configuration properties. Some of
them are standard properties defined by the Transport
subclasses (for example, the response timeout). Some of them are custom
properties defined in the CML for a particular transport. In each case, the
property has a default value. Properties may be given new values by:
- Setting
the property in Java code via the
setX()method defined for the property. - Setting
the property in Java code via the
putConfigurationInformation(String, Object)method. - Setting the property in the esc.properties file.
Configuration properties are named name.propertyname,
where name is derived from the transport class
name, and propertyname is the name of
the property. For example, sirittransport.readtimeout is the
property name used in the esc.properties file and by the putConfigurationInformation(String,
Object) method for the example transport that we'll implement later
in this article. The same property can be set by the setResponseTimeout(long)
method and retrieved by the getResponseTimeout() method (note the
difference in the property name).
To set properties in the esc.properties file, edit the file and use the comments as a model (the comments include all the configuration properties that can be set, and their default values). Put the esc.properties file in the current directory of whatever Java program is executing the transport.
The primary function of a transport is to convert messages and bytes and vice versa. Transports parse bytes and create messages from them, and accept messages and create bytes from them.
Generally, transports should not have any knowledge of the semantics of messages. However, the transport does have to know about heartbeat messages and messages used during a start-up conversation. All messages that a transport has direct knowledge of should be defined in the transport CML file. The messages may be either concrete or template messages (see Part 1 for a discussion of messages). If a start-up conversation composes messages on the fly (such as a password echo that substitutes * for the characters in the password), those messages do not need to be defined in the CML.
When a transport parses incoming bytes into a message, it
may create an instance of any subtype of MessageService.
There are several subtypes available, and selecting the right one may simplify
things later. If the protocol is text-based, consider creating a subclass of com.ibm.esc.message.AsciiMessage
(for clarity). If the protocol uses \r\n as a message terminator,
use a subclass of com.ibm.esc.message.AsciiCrlfMessage.
That class excludes the \r\n from the data portion of the
message. If the transport is a subclass of ResponseTransport,
consider using a subtype of com.ibm.esc.message.service.ResponseMessageService
and set the sent message, if possible (use the transport getSentMessage()
method). That permits connecting a response message with the message that
generated it, making device modeling easier later.
The messages created by a transport are used later by the
Device Kit device component to interact with and interpret the results from a
reader (see Part
1 for an overview). To make device modeling easier, you may find it useful
to implement access to the message fields. For example, if a message defines
key/value pairs, it's useful to be able to access the values by the keys. To do
so, the get(Object)
method needs to find the value corresponding to the field. In some cases, using
"virtual" keys is useful. For example, if the reader protocol defines the
responses to commands to "get attribute X"
as "OK value
", it might be
useful to define key "0" to be the message up to (but not including) the space
character, and key "1" to be everything from the character after the space to
the end of the data. None of the Device Kit-provided message classes
implement field accessing.
Our recommendation is for transports to define a custom message class and always create
instances of it. The custom message class should subclass the appropriate
message class (AsciiMessage, AsciiResponseMessage, and so on),
and:
- Override
getDataLength()to return the number of bits that define the data portion of the message (usually excluding message terminators and trailers, such as checksums). Only bits at the end of the message may be excluded. - Implement
access to the message fields if possible. Override
get(Object)to parse the message fields to find the value corresponding to the key argument.
Transports use Device Kit connections as a communication channel to and from the hardware device. Device Kit provides several types of connections. The ones that are most relevant to RFID readers are:
-
com.ibm.esc.tcpip.connection.TcpipConnectionfor TCP/IP client connections -
com.ibm.esc.serial.connection.SerialConnectionfor serial (RS232) port connections -
com.ibm.esc.tcpip.server.connection.TcpipServerConnectionfor cases where a device initiates a connection to a transport
It's very rare that you'd have to implement a new type of connection.
In addition to the usual command/response connection to the
reader, some readers support an alternate message channel, over which
some messages (such as tag read events) may be delivered. The alternate channel
may be transport-initiated or device-initiated.
Transport-initiated alternate channels require the transport to create another
connection to the reader (often using a TcpipConnection).
Device-initiated alternate channels require that the device be configured with
a host and port to contact. In that case, the transport listens for a
connection from the device (probably using a TcpipServerConnection).
To use an alternate message channel, a transport requires a few changes:
- The transport must start the connection at the right time (usually during the start-up conversation).
- The transport must parse the messages coming from the alternate channel, and treat them appropriately (for example, they will almost always not be response messages).
- The transport must shut down the alternate channel at the right time.
- The transport must be prepared to handle the alternate channel being shut down prematurely.
Using an alternate message channel requires a transport to define custom configuration parameters.
When you create a new transport using the Device Kit Transport Creation wizard, you're given an option to create a test project. If you select that option, you can easily create test cases for your transport.
Unlike developing a transport, writing test cases does not require writing any Java code; test cases are specified using CML. After you edit the CML file in the transport test project, regenerate the test application using the Device Kit tools by right-clicking the test CML file and selecting Device Kit => Generate. Then run the test application by selecting the transport test project, the selecting Run => Run As => Java Application.
Transport tests consist of sending messages to a device. If you have tracing turned on, you should see enough information in the logs (messages sent and received) to determine whether the test was successful. There is no automated comparison of expected results with actual results.
To create a test case in CML, you need to edit the test
project's CML file, and insert one or more <send>
elements after the <description> element of <transporttest>.
Each <send>
element groups a set of <message> elements that define
the messages to send as the test. Listing 1 shows a simple example.
Listing 1. Sample transport test CML
<send id="LoginAndStatus">
<message id="ReaderWhoAmI">
<ascii>reader.who_am_i()\r\n</ascii>
</message>
<message idref="ReaderWhoAmI">
</message>
<message>
<ascii>reader.check_status()\r\n</ascii>
</message>
</send>
|
You can modify the esc.properties file in the test
project to set properties to control the test. The most common
properties to override are those to set the host (reader
to connect to) and the testcount (number of tests to run,
usually 1). The totaltesttime property controls the maximum running time
of the test; when the time is up, the test is terminated, whether it's done or
not. Set the esc.tracelevel property to 10 to get the
maximum amount of trace information for your test.
Building a transport for the Sirit INfinity 510 reader
This example guides you through the steps to create and test a transport for a Sirit INfinity 510 RFID reader, including instructions on how to develop all the necessary pieces.
You'll need the following to complete this exercise:
- WebSphere Studio Device Developer Version 5.1.7 installed with the WebSphere RFID Tracking Kit 1.1.1.
- Sirit INfinity 510 reader.
- INfinity 510 Protocol Reference Guide
You can download the completed example and some helper classes that we'll refer to in the Downloads section.
Understand the reader protocol
Before you implement your transport, you need to make sure you understand the protocol conventions for your reader. The INfinity 510 Protocol Reference Guide states:
- The reader's protocol is ASCII text based.
- You connect to port 50007 to send commands and receive responses from the reader.
- You connect to port 50008 to receive asynchronous event messages (including tag reads).
- All commands end in
\r\n. - All responses and events end in
\r\n\r\n.
We'll use the Transport Creation wizard to generate a skeleton transport for the Sirit INfinity 510 reader. To generate the transport, do the following:
- Select File => New => Other.
- Select Device Kit, then select Transport to open the wizard.
- Click Next.
- Fill in the following fields, as shown in Figure 4):
-
Transport Name:
Sirit -
Package Base:
devworks.example -
Response Timeout:
1000 - Connection: TCPIP
-
Transport Name:
- Check all of the generation options, then click Next.
Figure 4. Specify transport name and options
- In the next dialog, fill in the following fields (leave any other fields empty), as shown in Figure 5:
-
Host:
localhost -
Remote port:
50007
-
Host:
- Click Finish to generate the transport code.
Figure 5. Specify host name and port
- Take a look at what was generated in the SiritTransport
and SiritTransportTest projects:
Figure 6. Transport creation results
Test that your transport can connect to your reader by doing the following:
- In the SiritTransportTest project, find the esc.properties
file. Add the following properties to the end of the file:
esc.tracelevel=10 sirittransporttest.testcount=1 sirittransporttest.host=ip address of reader
These properties say that we want our tests to produce maximum trace output, to run one time, and that we want to connect to our reader.
- Run the SiritTransportTest application.
You should see output messages indicating that a connection was made to the
reader and that the transport started. The key message that indicates that the transport
started is:
[INFO] 2006-10-19 14:27:04.110 - TransportTest4005: Received transport state changed to STARTED from ACTIVE.
- Kill the test application or wait about one minute for it to stop.
Implement a custom message class
Define a new class, SiritMessage, in the same
package as your transport implementation. Since the Sirit reader uses an ASCII
text based protocol and a command/response style of interaction, you
should make the superclass com.ibm.esc.message.AsciiResponseMessage.
Redefine the getDataLength() method of your new
class to exclude message terminators from the data part of the message. The
Sirit INfinity 510 uses \r\n\r\n
as a message terminator, so getDataLength()
should return the number of bits in the message excluding the last 4
characters.
public int getDataLength() {
// ignore the CR+LF+CR+LF at the end of the message
return (getBytes().length - 4) << 3;
}
|
Implement the write(MessageService) method
The Sirit reader does not use any protocol decorations;
messages are output exactly as they are handed to the transport for writing, so you don't need to modify the write(MessageService) method.
Implement the processInput(byte[], int) method
The major work of a transport is to parse incoming
bytes from a device into instances of MessageService that other Device Kit
components can process. Although Device Kit doesn't provide any support for
parsing incoming bytes, we have some helper classes to make the job a little
easier.
Load the DeveloperWorksTransportHelpers project from the transport_helpers_project.zip file in the Downloads section
We'll use the parsing helper classes in the devworks.example.message.parsers package:
-
SimpleTerminatedMessageParserimplements a simple parser that recognizes the end of a message by a predefined terminator (on the Sirit, that will be\r\n\r\n). -
MessageParserListenerdefines the listener that aSimpleTerminatedMessageParsernotifies when it parses a message.
- First, define a private field in the transport to
hold the parser you'll create:
private SimpleTerminatedMessageParser parser;
- Create the parser in the
startup(boolean) method:public int startup(boolean output) throws Exception { parser = new SimpleTerminatedMessageParser (createParserListener(), "\r\n\r\n"); return TransportService.STARTED; }
- The
createParserListener()method creates an instance ofMessageParserListenerthat is notified when the parser finds a message:protected MessageParserListener createParserListener() { return new MessageParserListener() { public void parsedMessage(byte[] bytes, boolean maybeResponse, Object timestamp) { MessageService m = new SiritMessage(bytes, maybeResponse ? SiritTransport.this.sentMessage : null); if (maybeResponse) { SiritTransport.this.fireMessageReceived(timestamp, m); } else { SiritTransport.this.fireMessageReceivedNoResponsetimestamp, m); } } }; }
Use an anonymous inner class here, instead of defining the transport to implement the
MessageParserListenerinterface, because when you regenerate the transport from the CML file (when you add new message definitions, for example), the code generator overwrites your definition with its own. - Note that when the parser notifies the listener that it has
found a message, you create an instance of our custom message class, and connect
it to the message that generated it (if
maybeResponseistrue). - Finally, implement the
processInput(byte[], int)method:protected int processInput(byte[] bytes, int length) { return parser.parseMessages(bytes, length, true); }
Delegate the parsing to the parser helper. Thetrueargument tells the parser that the received message might be a response (for the Sirit reader, it is a response because it came from the command/response connection).
Test the write(MessageService) and processInput(byte[], int) methods
To test the transport, create a test application using CML. The test application will send various messages to the reader and you can inspect the output to determine if the responses were parsed correctly.
- Edit the SiritTransportTest.cml file in the SiritTransportTest
project. Insert some
<message>elements to specify what commands to send to the reader. You need to group<message>elements into logical tests using the<send>element. - Add the following after the
<description>element:<send id="LoginAndStatus"> <message id="ReaderWhoAmI"> <ascii>reader.who_am_i()\r\n</ascii> </message> <message> <ascii>reader.login(login = admin, pwd=readeradmin)\r\n</ascii> </message> <message idref="ReaderWhoAmI"> </message> <message> <ascii>reader.check_status()\r\n</ascii> </message> <message> <ascii>com.network.1.mac_address\r\n</ascii> </message> <message> <ascii>info.time\r\n</ascii> </message> <message> <ascii>setup.protocols\r\n</ascii> </send> <send id="ReaderVersion"> <message> <ascii>version.hw\r\n</ascii> </message> <message> <ascii>version.sw\r\n</ascii> </message> </send> <send id="ErrorMessages"> <message> <ascii>foo.bar\r\n</ascii> </message> </send>
This gives a variety of commands to check the output of. - Save the CML file and regenerate the test application by
right-clicking on the file, and selecting Device Kit => Generate.
If you get errors
after regenerating the transport, first check to make sure that there aren't
syntax errors in the Java code. If so, fix them. If you still get
errors, right-click
SiritTransportTest, and select SMF => Re-Cache All, then SMF => Validate All. - Run the test application. You should see many messages
on the console. Look through them to ensure that the commands sent and the
responses parsed look correct. Be sure that the responses include the
terminator characters (
\r\n\r\n) like this:[DEBUG] 2006-10-19 17:19:04.260 - Transport2023: Message received notification. transport: SiritTransport@34e234e2=STARTED message: "ok 0.4.4265\r\n\r\n" <- "version.sw\r\n"
You should see the command output and response messages "paired up" in log messages like this:
[DEBUG] 2006-10-19 15:55:06.476 - Transport2023: Message received notification. transport: RfidSiritCliTransport@34a034a0=STARTED message: "ok isoc\r\n\r\n" <- "setup.protocols\r\n"
All of the heartbeat ("no activity processing") machinery is
implemented in superclasses of your transport. All you have to do is define
what message to send. Think carefully when selecting a message; it should have
no side effects, be easy for the reader to execute, and have a small amount of
output. For this example, use the version.sw message.
Once you've selected the message you want to use, you need
to override the noActivityProcessingMessage()
method in your transport to return that message. All messages
used by a transport are defined in the transport CML file.
- Edit the SiritTransport.cml file in the SiritTransportDevelopment folder of the SiritTransport project.
- Add the following message definition between the
<description>and<responsetimeout>elements:<message id="VersionSwGetMessage"> <ascii>version.sw\r\n</ascii> </message>
- Regenerate the transport definition from the CML by right-clicking the CML file, then selecting Device Kit => Generate.
- Look in the
SiritTransportMessagesclass. You should see a definition for the message. Return that message from thenoActivityProcessingMessage()method:public MessageService noActivityProcessingMessage() { return SiritTransportMessages.getVersionSwGetMessage(); }
- If you want to define a different default time during
which no activity occurs before sending the no activity message, edit your
transport CML and add the following to the
<transport>element:<noactivitytimeout>value</noactivitytimeout>
- You can also configure the no activity timeout by editing
the esc.properties
file in the transport test project and adding a property (timeout in
milliseconds) to the end of the file:
sirittransporttest.noactivitytimeout=8000
Rerun the previous test. At the end of the test output, you
should see the version.sw message being sent, and a response received,
approximately every 10 seconds (or whatever timeout you configured).
Create an event channel connection
The Sirit INfinity 510 requires your transport to connect to a second port, 50008, to receive asynchronous notification of events. In addition, the reader uses a way of registering for events you're interested in receiving that requires the transport to submit a token that the reader handed out when the connection to port 50008 was first made.
Device Kit doesn't support creating alternate message channels, so we've implemented some helper classes to make the job a little easier. You can find these in the devworks.example.alternate.channel.handler package of the DeveloperWorksTransportHelpers project.
The alternate message channel helpers are:
-
AlternateChannelMessageHandlerhandles connecting to and receiving bytes from an alternate connection. -
AlternateChannelMessageListenerdefines the listener that aAlternateChannelMessageHandlernotifies when it has input to process.
- The event channel port number to connect to, 50008, is fixed
on the Sirit, but to follow conventions, you should make it a configurable
property of your transport. To do that, edit the transport CML file to add a
custom property after the
<description>element:<customparameter name="eventport" type="int" defaultvalue="50008" access="true"/>
- Also add other configuration properties needed by the
AlternateChannelMessageHandler:<customparameter name="eventChannelRetryTime" type="int" defaultvalue="2000" access="true"/> <customparameter name="eventChannelRetryAttempts" type="int" defaultvalue="2" access="true"/>
- Regenerate your transport as before.
- Next, define a private field in the transport to
hold the alternate channel message handler you'll create:
private AlternateChannelMessageHandler eventChannel = null;
- Define the start-up conversation to connect to the
event channel and register for events. The conversation begins by the transport
connecting to the event channel:
public int startup(boolean output) throws Exception { parser = new SimpleTerminatedMessageParser(createParserListener(), "\r\n\r\n"); eventChannel = new AlternateChannelMessageHandler( createAlternateChannelMessageListener(), "" + getEventport()); eventChannel.setNumberOfRetries(getEventChannelRetryAttempts()); eventChannel.setRetryWait(getEventChannelRetryTime()); eventChannel.start(); return TransportService.ACTIVE; }
Note that now
startup(boolean) returns the stateACTIVE(instead ofSTARTED) to indicate that the transport is engaged in a start-up conversation. - Once the transport makes a connection to the event port, it
will get a message containing a token, which must then be used to register to
receive events. Define three new messages in the transport CML file:
<message id="EventConnectionReportMessage"> <ascii>event.connection id = \r\n\r\n</ascii> <parameter type="ascii"> <index>22</index> </parameter> <filter> <bytes format="hex">ff, ff, ff, ff, ff, ff, ff, ff, ff, ff, ff, ff, ff, ff, ff, ff, ff, ff, ff, ff</bytes> </filter> </message> <message id="ReaderBindMessage"> <ascii>reader.bind(id=)\r\n</ascii> <parameter type="ascii"> <insert/> <index>15</index> </parameter> <filter> <bytes format="hex">ff, ff, ff, ff, ff, ff, ff, ff, ff, ff, ff, ff</bytes> </filter> </message> <message id="OkReaderBindReportMessage"> <ascii>ok\r\n\r\n</ascii> <sentmessage idref="ReaderBindMessage"/> </message>
The
EventConnectionReportMessageis what is received over the event channel when the transport connects. It contains a token that is used in theReaderBindMessagethat the transport send to set as the default token to use when registering to receive events. Finally, theOkReaderBindReportMessageis the response. When the transport gets that message, the start-up conversation is over. - To implement the start-up conversation, override the
startUpMessageReceived(TransportService, Object, MessageService)method:public void startupMessageReceived( TransportService source, Object timestamp, MessageService message) { if (SiritTransportMessages .getEventConnectionReportMessage() .matches(message) != null) { // got the event token, do the bind command final Object token = SiritTransportMessages .getEventConnectionReportMessage() .decodeMessage(message); putConfigurationInformation("event.connection.id", token); try { final MessageService bind = (MessageService) SiritTransportMessages .getReaderBindMessage() .clone(); write(bind.encodeMessage(bind, token)); } catch (CloneNotSupportedException cnse) { handleError(cnse, CLONE_EXCEPTION_RESOURCE); } catch (Exception e) { handleError(e, WRITE_EXCEPTION_RESOURCE); } return; } if (SiritTransportMessages.getOkReaderBindReportMessage() .matches(message) != null) { // got the OK for the reader.bind. // The transport is officially up. setState(TransportService.STARTED); } }
- Create the listener that the
AlternateChannelMessageHandlerwill notify when input is available:protected AlternateChannelMessageListener createAlternateChannelMessageListener() { return new AlternateChannelMessageListener() { public ConnectionService getDefaultAlternateConnection() { return SiritTransport.this.getDefaultAlternateConnection(); } public int processAlternateInput(byte[] bytes, int length) throws Exception { return SiritTransport.this.parser.parseMessages( bytes, length, false); } public boolean isProcessing() { return SiritTransport.this.isRunning() && SiritTransport.this.getState() >= TransportService.CONNECTED; } public void connectionFailed() { SiritTransport.this.restart(); } }; }
The
processAlternateInput(byte[], int)method just delegates to our parser to create the messages from the input. Thefalseargument indicates the message is not a response message. - Create the
ConnectionServiceto be used by theAlternateChannelMessageHandler:public ConnectionService getDefaultAlternateConnection() { return new TcpipConnection(getString("sirittransport.host", SiritTransportService.DEFAULT_HOST), getEventport(), getInt("sirittransport.localport", SiritTransportService.DEFAULT_LOCALPORT), getInt("sirittransport.readtimeout", SiritTransportService.DEFAULT_READTIMEOUT), getInt("sirittransport.readsize", SiritTransportService.DEFAULT_READSIZE), getInt("sirittransport.writesize", SiritTransportService.DEFAULT_WRITESIZE), getInt("sirittransport.linger", SiritTransportService.DEFAULT_LINGER)); }
The connection is modeled after the transport's TCP/IP connection, but uses the event channel port.
To finish testing your transport, add messages to the transport test CML to register for and generate events:
<send id="RegisterEvent">
<message id="ReaderRegisterEventMessage">
<ascii>reader.register_event(name=event.debug.trace.iop)\r\n</ascii>
</message>
<message id="ReaderRegisterEventDioMessage">
<ascii>reader.register_event(name=event.dio.all)\r\n</ascii>
</message>
</send>
<send id="Dio">
<message id="DioOutput1ReadMessage">
<ascii>dio.out.1\r\n</ascii>
</message>
<message id="DioOutput1OnMessage">
<ascii>dio.out.1 = 1\r\n</ascii>
</message>
<message idref="DioOutput1ReadMessage">
</message>
<message id="DioOutput1OffMessage">
<ascii>dio.out.1 = 0\r\n</ascii>
</message>
<message id="DioInput1ReadMessage">
<ascii>dio.in.1\r\n</ascii>
</message>
<message id="DioInput1OnMessage">
<ascii>dio.in.1 = 0\r\n</ascii>
</message>
<message id="DioInput1OffMessage">
<ascii>dio.in.1 = 1\r\n</ascii>
</message>
</send>
|
Regenerate the transport test and run it. You
should see event.dio.all event messages.
Implement field access in the custom message class
The device component we'll implement in the next article
in this series will require access to message fields by key. For this example,
note that responses for the reader commands and events in which we are
interested are generally of three types: ok value\r\n\r\n
or ok name = value\r\n\r\n
or event.name
name1 = value1, name2 = value2\r\n\r\n. The
device component will require two types of fields:
- The part of the response up to the first blank (key "0") and everything else (key "1") for the first kind of message.
- Keys based on the
namepart of the key/value pairs for the second and third kinds of messages.
Your custom message should reimplement the get(Object key)
method to provide such access. The method returns the field value associated
with the key. There are many ways to implement field parsing. We
recommend the following:
- Parse the "0" and "1" fields "on demand". In your device implementation, you'll probably never access either field more than once.
- Parse
all key/value pairs and save them as a
Map, but only if a named key is asked for. That way, for many messages, you'll never parse anything, and if you do, the parsing will only be done once to build all key/value pairs. - Although
get(Object)can return anyObject, for a text-oriented protocol like that of the Sirit INfinity 510 it makes the most sense to return fields as instances ofString. Keys should be instances ofString. - Be careful when parsing that you don't include the leading or trailing blanks in the value, or the trailing comma.
- Return
nullfor undefined keys.
A suitable implementation for the custom message class is included in the transport_projects.zip file in the Downloads section.
In this article we took a closer look at the transport component of a Device Kit adapter. We discussed the responsibilities of a transport and implementation considerations, and showed you how to create a transport for a Sirit INfinity 510 RFID reader. In the next article in this series, we'll model the device component.
| Description | Name | Size | Download method |
|---|---|---|---|
| Helper classes for transports | transport_helpers_project.zip | 8KB | HTTP |
| Completed sample transport | transport_projects.zip | 34KB | HTTP |
Information about download methods
Learn
-
Developing a device adapter and agent for the WebSphere RFID solution, Part 1: Introduction to the WebSphere RFID Device Kit (developerWorks, 2007): Describes the functions provided by the WebSphere RFID Device Infrastructure component and how to construct device adapters using the WebSphere RFID Device Infrastructure Device Development Kit.
-
Developing a device adapter and agent for the WebSphere RFID solution, Part 3: Building the device component (developerWorks, 2007): Learn how to create a new device and model the commands necessary to read RFID tags using a Sirit⢠INfinity 510 RFID reader.
-
Developing a device adapter and agent for the WebSphere RFID solution, Part 4: Building the reader agent (developerWorks, 2007): Learn how to create a new reader agent to read RFID tags using a Sirit INfinity 510 RFID reader.
-
Websphere RFID Premises Server product overview: A brief overview of the Websphere RFID solution.
-
WebSphere RFID Information Center: Product documentation for the Websphere RFID solution.
-
Websphere Studio Device Developer help has information for all the RFID TRacking Kit components
(after you install the RFID Tracking Kit feature)
-
The Sirit INfinity 510 User's Guide: Everything you need to know about how to install, configure, and use the reader.
-
Print simple and complex RFID labels with the WebSphere RFID solution (developerWorks, Nov 2006): A guide to printing RFID labels with the Websphere RFID solution.
-
Redbook: IBM WebSphere RFID Handbook: A Solution Guide
-
developerWorks Wireless with WebSphere zone
Get products and technologies
-
Download a trial version of WebSphere Studio Device Developer
-
Install the RFID Tracking Kit into Device Developer using the Update Manager and defining a site bookmark to
http://www-306.ibm.com/software/pervasive/wsdd/updates/571/rfid

Karl Freburger is an IT Architect with IBM Global Business Services. He works with IBM's Software Group to enable business partners to design solutions that use IBM's RFID components.

Allen Smith is a Senior Software Engineer with the Pervasive Computing Group in Research Triangle Park, North Carolina. He works with business partners to design solutions that use IBM's pervasive middleware. You can contact Allen at allens@us.ibm.com.
Comments (Undergoing maintenance)





