Skip to main content

Developing a device adapter and agent for the WebSphere RFID solution, Part 2: Building the transport component

Karl Freburger (karlf@us.ibm.com), IT Architect, IBM
Karl Freburg photo
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 (allens@us.ibm.com ), Senior Software Engineer, IBM
Allen Smith photo
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.

Summary:  In Part 2 of this series, you'll learn how to begin implementing an adapter with the IBM® WebSphere® RFID Device Kit component of the WebSphere RFID Device Infrastructure, starting with the transport layer and using the Sirit™ INfinity 510 reader as an example.

View more content in this series

Date:  31 Jan 2007
Level:  Intermediate
Activity:  260 views

Introduction

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.


Transports in Device Kit

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
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 of com.ibm.esc.device.TransportDevice).

Figure 2. Transport collaborators
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:

  1. The application invokes send(Messageservice message) on the transport.
  2. The transport formats the message and invokes write(byte[] bytes, int offset, int count) on a connection.
  3. 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:

  1. The transport runs a Thread doing processInput() on itself.
  2. processInput() invokes read(byte[], int, int) on the connection (step 1 in the diagram).
  3. The connection waits for bytes to come from the reader (step 2).
  4. When bytes are returned, the transport invokes processInput(byte[], int) on itself (step 3).
  5. processInput(byte[], int) parses the bytes into messages. When it finds one, it invokes fireMessageReceived(MessageService, Object) on the transport (step 4).
  6. fireMessageReceived(MessageService, Object) notifies the controller that a message is available by invoking messageReceived(TransportService, Object, MessageService) (step 5).
  7. The controller inspects the arriving message and determines whether the transport should be interested in it (using an interest mask that the transport defines).
  8. If the message is not of interest, it is discarded. Otherwise, the controller queues the received message.
  9. A separate Thread in 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.
  10. 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
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 of com.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 to length bytes from buffer into 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. The int argument is a resource ID; it's easiest to use one of the IDs already defined in one of the Transport classes.
  • Object EscObject.getCurrentTimestamp() returns the current time as an Object and 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 of ConnectionService to 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.

Transport lifecycle

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 CREATED state 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 ALIVE state is when the transport has been told to start.
  • The CONNECTED state is when the transport is connected to the hardware device.
  • The ACTIVE state 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 STARTED state is when the transport has started and should be processing messages.
  • The DEAD state 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.

Transport CML

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 of ResponseTransport.
  • <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).

Configuring transports

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.


Transports and messages

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 and connections

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.TcpipConnection for TCP/IP client connections
  • com.ibm.esc.serial.connection.SerialConnection for serial (RS232) port connections
  • com.ibm.esc.tcpip.server.connection.TcpipServerConnection for 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.


Testing transports

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.

Prerequisites

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.

Create the transport

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:

  1. Select File => New => Other.
  2. Select Device Kit, then select Transport to open the wizard.
  3. Click Next.
  4. Fill in the following fields, as shown in Figure 4):
    • Transport Name: Sirit
    • Package Base: devworks.example
    • Response Timeout: 1000
    • Connection: TCPIP
  5. Check all of the generation options, then click Next.

    Figure 4. Specify transport name and options
    Figure 4. Specify transport name and options

  6. In the next dialog, fill in the following fields (leave any other fields empty), as shown in Figure 5:
    • Host: localhost
    • Remote port: 50007
  7. Click Finish to generate the transport code.

    Figure 5. Specify host name and port
    Figure 5. Specify host name and port

  8. Take a look at what was generated in the SiritTransport and SiritTransportTest projects:

    Figure 6. Transport creation results
    Figure 6. Transport creation results

Test the transport connection

Test that your transport can connect to your reader by doing the following:

  1. 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.

  2. 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.
    

  3. 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:

  • SimpleTerminatedMessageParser implements a simple parser that recognizes the end of a message by a predefined terminator (on the Sirit, that will be \r\n\r\n).
  • MessageParserListener defines the listener that a SimpleTerminatedMessageParser notifies when it parses a message.
  1. First, define a private field in the transport to hold the parser you'll create:
     private SimpleTerminatedMessageParser parser;
     

  2. 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;
    }
    

  3. The createParserListener() method creates an instance of MessageParserListener that 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 MessageParserListener interface, 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.

  4. 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 maybeResponse is true).
  5. 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. The true argument 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.

  1. 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.
  2. 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.
  3. 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.
  4. 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"
    

Define the heartbeat message

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.

  1. Edit the SiritTransport.cml file in the SiritTransportDevelopment folder of the SiritTransport project.
  2. Add the following message definition between the <description> and <responsetimeout> elements:
    <message id="VersionSwGetMessage">
       <ascii>version.sw\r\n</ascii>
    </message>
    

  3. Regenerate the transport definition from the CML by right-clicking the CML file, then selecting Device Kit => Generate.
  4. Look in the SiritTransportMessages class. You should see a definition for the message. Return that message from the noActivityProcessingMessage() method:
    public MessageService noActivityProcessingMessage() {
       return SiritTransportMessages.getVersionSwGetMessage();
    }
    

  5. 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>
    

  6. 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
    

Test the heartbeat message

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:

  • AlternateChannelMessageHandler handles connecting to and receiving bytes from an alternate connection.
  • AlternateChannelMessageListener defines the listener that a AlternateChannelMessageHandler notifies when it has input to process.
  1. 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"/>
    

  2. 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"/>
    

  3. Regenerate your transport as before.
  4. Next, define a private field in the transport to hold the alternate channel message handler you'll create:
    private AlternateChannelMessageHandler eventChannel = null;
    

  5. 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 state ACTIVE (instead of STARTED) to indicate that the transport is engaged in a start-up conversation.

  6. 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 EventConnectionReportMessage is what is received over the event channel when the transport connects. It contains a token that is used in the ReaderBindMessage that the transport send to set as the default token to use when registering to receive events. Finally, the OkReaderBindReportMessage is the response. When the transport gets that message, the start-up conversation is over.

  7. 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);
        }
    }
    

  8. Create the listener that the AlternateChannelMessageHandler will 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. The false argument indicates the message is not a response message.

  9. Create the ConnectionService to be used by the AlternateChannelMessageHandler:
    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.

Test event handling

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:

  1. The part of the response up to the first blank (key "0") and everything else (key "1") for the first kind of message.
  2. Keys based on the name part 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:

  1. Parse the "0" and "1" fields "on demand". In your device implementation, you'll probably never access either field more than once.
  2. 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.
  3. Although get(Object) can return any Object, for a text-oriented protocol like that of the Sirit INfinity 510 it makes the most sense to return fields as instances of String. Keys should be instances of String.
  4. Be careful when parsing that you don't include the leading or trailing blanks in the value, or the trailing comma.
  5. Return null for undefined keys.

A suitable implementation for the custom message class is included in the transport_projects.zip file in the Downloads section.


Summary

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.



Downloads

DescriptionNameSizeDownload method
Helper classes for transportstransport_helpers_project.zip8KBHTTP
Completed sample transporttransport_projects.zip34KBHTTP

Information about download methods


Resources

Learn

Get products and technologies

About the authors

Karl Freburg photo

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 photo

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)



Trademarks  |  My developerWorks terms and conditions

Help: Update or add to My dW interests

What's this?

This little timesaver lets you update your My developerWorks profile with just one click! The general subject of this content (AIX and UNIX, Information Management, Lotus, Rational, Tivoli, WebSphere, Java, Linux, Open source, SOA and Web services, Web development, or XML) will be added to the interests section of your profile, if it's not there already. You only need to be logged in to My developerWorks.

And what's the point of adding your interests to your profile? That's how you find other users with the same interests as yours, and see what they're reading and contributing to the community. Your interests also help us recommend relevant developerWorks content to you.

View your My developerWorks profile

Return from help

Help: Remove from My dW interests

What's this?

Removing this interest does not alter your profile, but rather removes this piece of content from a list of all content for which you've indicated interest. In a future enhancement to My developerWorks, you'll be able to see a record of that content.

View your My developerWorks profile

Return from help

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=1
Zone=WebSphere
ArticleID=192508
ArticleTitle=Developing a device adapter and agent for the WebSphere RFID solution, Part 2: Building the transport component
publish-date=01312007
author1-email=karlf@us.ibm.com
author1-email-cc=crothemi@us.ibm.com
author2-email=allens@us.ibm.com
author2-email-cc=crothemi@us.ibm.com

My developerWorks community

Tags

Help
Use the search field to find all types of content in My developerWorks with that tag.

Use the slider bar to see more or fewer tags.

Popular tags shows the top tags for this particular content zone (for example, Java technology, Linux, WebSphere).

My tags shows your tags for this particular content zone (for example, Java technology, Linux, WebSphere).

Use the search field to find all types of content in My developerWorks with that tag. Popular tags shows the top tags for this particular content zone (for example, Java technology, Linux, WebSphere). My tags shows your tags for this particular content zone (for example, Java technology, Linux, WebSphere).

Rate a product. Write a review.

Special offers