|
The following sections provide an overview of the BEA WebLogic RFID Edge Mobile SDK and describe how client applications use the SDK APIs to write mobile applications:
The Edge Server device framework supports device drivers that communicate with a wide variety of devices, operating in different modes. A given device may implement one or more modes, with different latency, throughput, and network usage trade-offs between them. Within the Edge Server, the goal is for a driver to support all modes implemented by the device, although not generally simultaneously. Drivers implemented in the field for a specific application may only support the specific modes required by that application.
The use cases include all of the following driver modes:
Some polling devices may support reading from all device units at once, with sequencing implemented by the device. Other polling devices may only be able to read from a subset of device units at once, or only one. Some polling devices will be able to include in their results the device unit which observed a tag. Some may not be able to include this information.
Some autonomous and asynchronous devices will only support autonomous/asynchronous OpSpecs. Some will only support synchronous OpSpecs. Some will support a combination of these. It should be possible with autonomous devices to support autonomous tag operations across network failures, driver reconfigurations, and driver restarts without missing tags or clearing the buffer, if such behavior is enabled in configuration.
An Edge Server may connect to a device using one or more channel types:
For stream channel types, a given device may permit synchronous requests and/or asynchronous requests.
The framework handles connection failure and retry logic directly, so device drivers do not have to. In order for this logic to work, a number of framework abstract methods are declared to throw IOException. If an implementation of one of these methods sees a communications problem, it must throw an IOException so that the retry logic is invoked.
To control resource usage, the Edge Server uses a system of thread pools and event queues. The system is configured to create a pool of threads which may be fixed in size, or grow as needed. Each device driver has an event queue, which also may be fixed in size or grow as needed. Each event on the queue is represented by an implementation of the event interface, which consists of a single handle() method.
The events on each queue are processed on the threads in the pool. Although there is no guarantee that all events will be processed on a single thread, it is guaranteed that no more than one thread will process a queue at a given time. This means only one event on a queue is processed at a time; as a result, synchronization is unnecessary for data touched only by event handlers. Finally, there is an alarm clock which allows for an event to be added to a queue at a time in the future.
In order not to block the use of the threads in the pool, events should not block on long-lived operations. As an exception to this rule, it is acceptable to perform network I/O operations in event handlers if the read is expected to complete. While such requests can time out and block a thread for an extended period, writing a driver in a completely I/O event driven fashion is burdensome. If a read may block for an unbounded period of time (for example, while waiting for a tag to enter an antenna's field), then a listener should be registered with the channel to handle incoming asynchronous data.
If the driver blocks for a long time in an event handler, then the rule that only one event from a queue may be processed at a time will mean that all event processing on the queue will block until the long-lived operation finally completes. Because all operations, such as changing the list of DeviceUnitRequests, are performed via events, this will have a significant effect on the operation of the device.
If a device defers work until a later time, it should use the alarm clock, not sleep. This event may be queued to occur as soon as all queued events complete, or it may be scheduled to occur at some time in the future (for example, a keepalive or timeout handler).
If an event scheduled for the future should be cancelled (such as a timeout which is no longer needed), then the driver should arrange for the event to be ignored. It may do this by using a serial number pattern. In this pattern, the driver stores a serial number which represents some iteration of an internal process. When that process is reset (for example, when a response to a request arrives, or times out), the serial number is incremented. Events which should not occur once the process completes should store the serial number when they are created, and check the current value when they are executed. If the stored serial number is not equal to the current one, then the event handler should return without doing anything.
Each driver must implement a static method called getPluginMeta which describes the configuration options required by the driver. This information is used by the Administration Console or other DynamicConfig clients to display configuration screens to the user, and to parse and validate configuration data received.
A module's PluginMeta, returned by the static method getPluginMeta(), contains the configuration information about the module.
When creating PluginMeta, all the output ports must be defined under the sub-configuration Module Outputs and all the input ports must be defined under sub-configuration Module Inputs. When driver instance is created, the AbstractPhysicalDevice.initialize() method is called. This method is passed a PluginConfig instance which contains the name and configuration information for the device. This method should perform any additional configuration verification necessary, and store the configuration values as needed. It must also create and register with the framework DeviceUnit objects corresponding to each device unit on the device, and DeviceChannel objects corresponding to each communication channel.

The communications between the Edge Server and a device vary greatly. Sometimes the connection is a simple TCP socket which carries byte or character data. Sometimes the connection is not a single connection at all, but HTTP or SOAP which does not maintain any underlying network connection at all times (each HTTP request is often a separate TCP connection). Some connections are initiated by the device, not by the Edge Server. Some devices are implemented on top of an API that performs its own connection management.
In order to provide consistency, the framework provides the notion of a device channel. A driver may have more than one channel, each of which represents one potential means of communications between the Edge Server and the device. A channel may include one or more underlying network connections, and it may be initiated by the Edge Server or the device. A channel at any given time is either active or inactive. An active connection is one which is capable of carrying messages without any further setup on the part of the Edge Server.
For example, a TCP socket connection initiated by the Edge Server is a type of channel. This channel is active when the connection is alive, and inactive when the connection is down.
A TCP socket connection initiated by the device is also a type of channel. This channel is active whenever the TCP port is bound and listening, even if there is no socket open, because the device could open the a connection to the port and send a message at any time. When such a connection closes, the channel is still active.
If the channels are inactive, the framework will periodically attempt to reactivate the channels in order to resume operations on the device.
A set of commonly used channels is provided by the framework for all the modes supported. Drivers should use these channels when possible. When this is not possible (for example, when the channel is a wrapper around a library provided by the device vendor), then a new derived class of DeviceChannel should be created.
The initialize() method of the driver should create all the channels and add them to the framework. Note that this does not cause the channels to be activated, and the initialize() method must not attempt to activate the channels at this time. The framework handles channel activation and deactivation.
During operations, the various methods which can talk to the device are all declared to throw IOException. If the driver throws IOException, then the framework will execute a reset process which includes deactivating and reactivating each channel. (See the sequence diagrams for more details.)
If the channel is inactive due to a failure, it will only be reactivated again when the set of DeviceUnitRequests changes, or when the status poll interval expires for that device.
If a channel delivers data asynchronously to the Edge Server, the driver should make use of a channel with a listener. A listener is an interface which is registered with the channel, and contains callback methods when data is readable on a channel, or when a channel closes or gets an error. When a listener is registered, the channel will wait for data to be available. It will not read it, but instead queue an event on the driver's event queue which invokes the listener. This listener must then read a message from the device. A listener callback must only read as many messages from the device as are guaranteed to be present, or else the device's event queue will be blocked.
Such devices must also take care when performing request/response style operations, where a message is sent to the device, and a response is then read back. If a device is set up so that it might send an asynchronous message (containing tag data), then the response message might not be the expected response message at all, but instead an asynchronous message. The device can handle this in two ways. The device can implement a state machine where all responses, even to request/response style operations, are handled via the listener in an event-driven way. Or, the device can implement a request/response input algorithm which takes care to look at the message type information to determine if the message is a response to a request, or an asynchronous message. In the latter case, the driver must read and process the entire asynchronous message, then wait for the next message and try again (since the next message could also be either the response, or an asynchronous message).
After the device is initialized, the state machine for a given device driver is in one of four states:
The events that drive the transitions between states are:
This method is called periodically (default is every fifteen seconds) to perform reconnect or status check operations on an otherwise idle session.
If the driver is a polling mode driver, its processDeviceUnitRequests method is called periodically (configured as the default rate) whenever it has device unit requests.
There are methods associated with each of the events above. A driver can override these methods, but it generally does not. Instead, the framework state machine converts the above events into calls to the following methods as appropriate (see the Javadocs and attached sequence diagram for details):
Each driver must provide implementations which include business logic to configure and enable the device, operate on tags, and return tag data to the framework. The framework provides a number of support methods to simplify these tasks. There is an attached sequence diagram for each driver mode which describes best current practices behavior. Each of these methods is called from an event handler on the driver's event queue, so calls are guaranteed not to overlap.
If the device is a polling or autonomous device, AbstractPhysicalDevice.processDeviceUnitRequests() is called periodically to activate device units or read from the device's buffer. In either case, multiple DeviceUnitRequests for multiple device units may be simultaneously active if the device supports it.
For polling and autonomous modes, the framework provides support for sequencing DeviceUnitRequests. Specifically, given hints from the driver, the framework guarantee that all DeviceUnitRequests passed to a single call to processDeviceUnitRequests can be executed in a single command to the device (such device unit requests are called compatible). The DeviceCapabilities object contributes to this computation.
If the combined flag is set in the device capabilities, then the framework permits multiple device units to be created with the same logical reader name. Some devices will support this configuration directly, by internally treating both device units as one; in this case, it may not even be possible to determine what device unit observes a tag. Because they have the same logical reader name, they cannot be used separately. A DeviceUnitRequest on such a device will include all DeviceUnits with the same logical reader name when they are to be used.
The distinguished flag should be set in the device capabilities if the device can be instructed to read from multiple device units in a single command and the results identify for each tag which device unit or device units observed the tag.
The sequencer will invoke processDeviceUnitRequests with more than one DeviceUnitRequest if the requests are compatible. The default compatibility function checks if the following conditions are true:
This definition is conservative. The method AbstractPollingPhysicalDevice.areDeviceUnitRequestsCompatible can be overridden to provide a driver-specific compatibility function if the default is not suitable.
For an autonomous mode driver to operate properly, the device must be capable of sequencing between all active device units by itself. If the device cannot do this, then a polling mode driver is more appropriate. If the buffer can be read only as a complete unit, then the compatibility method must always return true. Otherwise, processDeviceUnitRequests will be called with disjoint subsets of active DeviceUnitRequests, and the buffer device will have no way to distribute the tag data to each DeviceUnitRequest correctly.
The work which a device should perform is described by a set of DeviceUnitRequests. A DeviceUnitRequest includes an unordered set of DeviceUnits, and an ordered list of OpSpecs. When a tag is observed by a DeviceUnit, the OpSpecs described in each DeviceUnitRequest which include the DeviceUnit must be executed on the tag, in order. In the simplest case, this DeviceUnitRequest may involve no more than reading the EPC of a tag.
This processing may be driven synchronously by the Edge Server on a device, or occur autonomously. Combinations may also be permitted. For example, the Impinj Mach1 protocol supports reading the EPC and one user memory field automatically, but must be explicitly commanded to perform other operations on the tag. (Nearly every device reports the EPC automatically for free, since the inventory includes this data.)
It should be noted that many devices support multiple modes of operation. It is not necessary for a device driver to support all the modes of operation of a device, or all the OpSpecs which the device can potentially implement. Various trade-offs in implementation time and complexity, network utilization, read latency, device performance, and user needs may affect what subset of functionality is implemented.
Because OpSpec processing is complex, the framework provides some help. The AbstractPhysicalDevice.processOpSpecs() method takes a TagProcessor and a list of OpSpecs, and invokes methods on the TagProcessor based on the OpSpecs in the list. Each method returns an OpReport specific to that method. These results are returned by processOpSpecs in a list whose elements correspond one-to-one with those in the list of OpSpecs. The TagProcessor class is abstract; the concrete class is defined by the device driver, and will often also implement one or more of a family of interfaces whose methods represent operations on tags which can be performed by that driver. The processOpSpecs() method will map the OpSpecs onto TagProcessor operations as efficiently as possible. If no interface is available to implement an operation, then an error OpReport is automatically generated by the class. In this way, the application can describe desired operations, the driver can describe the operations it can perform, and the framework can map between the two.
A TagProcessor may be used by updateRequests() to map a DeviceUnitRequest into the commands necessary to program a device to operate on tags automatically. Other possible uses for a TagProcessor are to operate on the tag directly when a poll event is received, or to help parse the response in an asynchronous driver. If the TagProcessor is used, then the DeviceOpSpec class and its derived classes should not need to be referenced directly.
In the future, the list of valid OpSpecs and TagProcessor interfaces will be extensible.
In the description above, each OpSpec results in a single call to an abstract method, which then performs the necessary work. This scheme works well when the relationship between OpSpecs and device operations is roughly one to one. However, it does not work if device operation is more coarse grained. Consider two use cases:
The first use case is handled by the AbstractPrinterPhysicalReader class. This class is specific to RFID printer devices. It has a single abstract print() method which takes all the input from the DeviceUnitRequest and should be implemented by the device driver to print an program an RFID label.
The second use case is not supported by the framework.
Once the device has done its work on the tags, it needs to report back what it has seen. When a tag is observed and has been operated upon due to a DeviceUnitRequest, a TagData object is passed to the framework using AbstractPhysicalDevice.addTagData(). The TagData object includes the identity of the tag (for EPCglobal tags, this is the EPC value and AFI, if any), the DeviceUnit which observed the tag, and a list of OpReports corresponding to the list of OpSpecs. An OpReport includes the data resulting from the OpSpec (for instance, the user memory data read, or the value of an EPC written from a cache), an error status code, and a string describing the error status in more detail.
Finally, after a set of tags is added, the framework method AbstractPhysicalDevice.report() must be called. This method actually sends the tags to the event cycle for a given DeviceUnit. This method has a boolean argument cycleComplete which must be set to true when a read cycle ends, even if no tags have been reported. This does the necessary bookkeeping for operations whose duration is based on the number of read cycles completed.
If the device has no notion of a read cycle, then the driver author may choose to pass in false for cycleComplete. Such a driver will never have a full read cycle, and so an event cycle on this reader with a duration in read cycles will never reach that termination condition. Or, it may choose to synthesize the cycle completion. This must be done with care, or else event cycles with a duration in read cycles and a report set spec of ADDITIONS or DELETIONS may return unpredictable results.
When a device is undefined or redefined, the running instance is stopped by the Edge Server. Stopping the instance is done in two phases: shutdown and termination. When the shutdown() method is called to begin the first phase, the driver should begin the process of gracefully stopping device operations. For example, for an asynchronous mode driver, this might include removing the configuration of the host and port on the Edge Server where notifications are sent. This method should generally not block, using similar rules of thumb as event handlers. A second method, waitForShutdown() is then invoked by the Edge Server with a timeout. This method should perform any necessary wait() and join() methods to complete the shutdown.
The second phase, termination, should forcibly free any resources not already cleaned up by the shutdown process. This phase is implemented by the terminate() and waitForTermination() methods.
In both cases, the super class methods should be invoked in order to do framework level cleanup. The framework termination process will deactivate all the channels and terminate any listener threads which may have been started by the system.
You have a new device. How do you work from there to a fully functioning driver? Here's a roadmap to make sure this process goes smoothly.
Read whatever developer documentation is available. Look at samples, maybe write some prototype code. Get a feel for how the device behaves.
Knowing this will help you make decisions later on in the development process. What are your tag throughput requirements? What are your latency requirements? What are you bandwidth requirements? Which is more important? (There may be more than one correct answer to these questions.) What OpSpecs do you need to support?
This decision depends on the use cases and requirements above. If you have more than one set of requirements, you may need to support multiple driver modes. For example, a polling driver may reduce latency, but an asynchronous driver may reduce bandwidth. Some devices may support a choice of channel types which may or may not be tied to the choice of driver mode. If no single mode meets all the requirements, then the driver may support multiple modes, where the choice of what mode to use may be manual based on configuration options, or automatic based on the DeviceUnitRequests in place. Based on your choice, there are three base classes you should choose from:
At a minimum, you will need to configure DeviceChannels and logical reader names for each DeviceUnit. Beyond that, you may choose to expose parameters which allow the deployer or developer to choose between different behaviors in the driver, or to set configuration options such as antenna gains on the device itself.
Provide implementations for all necessary abstract methods and other base class methods. Create concrete TagProcessor and TagData objects as necessary to implement the driver's behavior.
Not all of the above tests will be meaningful for every device, and some devices should be tested in additional ways.
|