The JavaBean Component Architecture provides a means to reuse
software and, when combined with tool support, can dramatically
increase developer productivity. This model has been realized
primarily in graphical display applications with AWT and Java Swing
components. Recent advances in J2ME and the release of the Real-Time
Specification for Java (RTSJ) allow similar productivity gains to be
achieved with real-time and embedded-hardware driver components.
In this article we examine the advantages and disadvantages of using
the JavaBean Component Architecture for embedded hardware components.
We describe a slight variation of the JavaBean model that's used in
the aJile Java processor runtime libraries that make Java components
more suited to time-critical and embedded applications. We follow
with an example application that uses this component model to program
a 100% Java-based controller based on the aJile aJ-80 Java
microprocessor for Lego Mindstorms sensors. We conclude with
suggestions on how to apply this component model to different
applications.
The JavaBean Component Architecture
The JavaBean Component Architecture has been used
successfully for graphical applications using AWT and Swing
components and by third-party software vendors to distribute reusable
software components using standard packaging. The JavaBean model
specifies properties that can configure the characteristics of the
JavaBean from inside a development environment at design time. It
also specifies events that are sent from the JavaBean to the
application when an event of interest occurs.
The JavaBean component specification also includes support
for "bean info" files, and many development tools provide property
editors to configure beans appropriately for the application under
development. The JavaBean component model is well suited for
graphical and nongraphical components for general-purpose computing
systems, but, as we shall see, the JavaBean event model is less
appropriate for time-critical or embedded systems.
Properties
JavaBean properties are get/set methods used to configure the
behavior of the component at design time by a development tool, and
at runtime by an application. Some extra overhead is associated with
calling these get/set methods; however, it's minimal compared to the
benefits gained. This method call overhead is also predictable,
making JavaBean properties acceptable for embedded and real-time
applications without modification.
Events
JavaBean events include an event listener interface that has
one or more methods that will be called when an event is sent. An
application implements the interface and registers it with the
JavaBean, which calls the event methods at the appropriate time. Each
of these event methods must also include a parameter of type
EventObject. When a component sends an event, it creates a descendent
of EventObject, initializes it, and passes it as a parameter to each
registered event listener.
Normally, a JavaBean will create a new EventObject each time
an event is sent since an event handler could keep a reference to the
EventObject for the future. If the JavaBean reused the same
EventObject, the internal state could be changed without the event
listener expecting it. This event-transmission technique is less
desirable for embedded or time-critical applications, because
creating and discarding EventObjects is expensive. The new()
operation for the EventObject is generally time-consuming and
unpredictable as it could also initiate a garbage-collection cycle at
an inappropriate time.
One alternative to creating a new EventObject each time an
event is sent is to create one EventObject and reuse it. This would
prevent the overhead associated with the new() operation and possible
garbage collection at the expense of not strictly adhering to the
JavaBean specification. This would also allow development tools to
recognize the events and provide full tool support. This alternative
still causes some overhead because the fields in the EventObject must
be initialized with custom state information and the event handler
has to access these fields to get the state information.
Cracked Events
A second alternative is to pass data directly to event
handlers as primitive data types, bypassing the EventObject. This has
the maximum speed advantage, at the expense of some development tools
not recognizing these events as JavaBean events. This type of event
is sometimes called a cracked event or primitive event. Development
tool support for cracked events that still conform to the JavaBeans
naming convention varies from no support to almost full support;
Borland JBuilder, for instance, recognizes cracked events (see Figure
1). The Real-Time Specification for Java calls for a special
real-time event called AsyncEvent that has no parameters. Perhaps, as
the RTSJ gains in popularity, more tools support will be provided for
this type of event.
Cracked events and their respective event listeners can be
unique for each component and each type of event that's sent, or
generic and reused for more than one component and for more than one
event in the same component. Unique events have the advantage of
being more appropriately named: each event is descriptive and
specific to one type of event. Generic events, on the other hand,
require fewer class definitions and are independent of any one
component. This reduces the number of inner classes required to
interface the components and also reduces call overhead. Components
utilizing generic events can be wired together many times in a
pipeline fashion, with little or no "glue code" required.
The aJile Real-Time Embedded Component Model
The aJile Real-Time Embedded Component Model (aRTEC), a
variation on the JavaBean Component Architecture, uses standard
JavaBean properties but eschews EventObjects in favor of cracked
events. These choices enable development tool support, while avoiding
many of the negative runtime consequences of the JavaBean event model.
Three cracked events are used for the majority of the aRTEC
components: a TriggerEvent with no parameters, an AssertEvent with
one Boolean parameter, and a DataEvent with one int parameter. The
trigger event is sent as an indicator that some physical condition
has occurred. It could be a timeout or a sensor threshold being
crossed. It can also be received to control physical conditions, such
as resetting a timer.
An AssertEvent is sent when a state change occurs. A true
value in the parameter indicates some state is active and a false
value indicates the state is inactive. For example, a lamp component
can receive an AssertEvent that will trigger the lamp to turn on when
a true value is passed, and turn off when a false value is passed. A
DataEvent is used when a value is associated with the event. For
example, a rotation sensor can send a DataEvent when the angle of
rotation changes; the new angle data is passed as the parameter.
There are additional types of cracked events used in the aJile
embedded component libraries, but these three have been found to be
adequate for most of our real-time needs.
Many JavaBeans can send the same event to more than one
registered listener, called multicast events. To implement multicast
events, some JavaBeans keep a vector of the registered listeners.
When an event occurs, the vector is traversed and the event is sent
to each registered listener. Other JavaBeans implement a multicaster,
which is an event listener that relays the event to two additional
listeners. If one of the additional listeners is also a multicaster,
the event is relayed to two more listeners. In this manner any number
of listeners can be registered without the use of a vector. The use
of the multicasters is hidden behind the JavaBean event registration
methods.
The advantages of using multicasters in a real-time
environment are clear. When only one listener is registered and an
event is sent, the event is sent by a single interface call with no
looping and no overhead associated with the vector. However, as more
listeners are registered for the same event, events will be routed
through multiple multicasters, causing more overhead. At some point,
if enough listeners are registered, multicasters will have more
overhead than traversing a vector; however, it's rare to have more
than one or two listeners for the same event.
TriggerEvents, AssertEvents, and DataEvents all have their
own multicaster classes that are used internally to implement
multicast events.
Real-Time Components
Often a timer is required in a real-time or an embedded
application. The aRTEC OneShotTimer component implements a timer that
can be started, stopped, or restarted. When its time expires, it
sends a TriggerEvent and waits to be started again. If the timer has
been started but has not yet expired, it's considered to be running.
If the timer is restarted while it's running, the timer starts over,
effectively extending the time delay. When the OneShotTimer is
started, it sends an AssertEvent with true as the value to indicate
that it's running. When the time expires, it sends an AssertEvent
with false as the parameter to indicate the timer is no longer
running. At that time a TriggerEvent is also sent.
The OneShotTimer can receive one TriggerEvent to start the
timer and one to stop it. A trigger event sent directly to this
object will start the timer. Because this class can implement the
TriggerEventListener interface only once, an inner class is used to
provide a triggerEventListener() method that will stop the timer.
The aRTEC libraries also include a PeriodicTimer and a
Counter component with similar interfaces. Generally, these
components provide a friendly front end to real-time execution
primitives provided by the Real-Time Specification for Java.
Lego Mindstorms Overview
To demonstrate the ability of the aRTEC components to monitor
and control embedded hardware, components were written for the Lego
Mindstorms robot sensors. This platform was chosen because it's well
known and exhibits many of the same characteristics as other embedded
and real-time applications. Lego Mindstorms hardware components
include motors, touch sensors, light sensors, rotation sensors,
temperature sensors, and lamps.
The Lego Mindstorms controller unit is called the RCX. For
our example program we won't be using the RCX, but rather an advance
prototype of the Systronix JCX controller that uses the aJile aJ-80
direct execution Java processor. The JCX provides extremely efficient
and time-deterministic execution of 100% Java-control applications.
The JCX is a direct replacement for the RCX unit and in its base
configuration supports eight input sensors and four output ports
compatible with the Lego RCX sensor and motor ports.
Lego Components
Each of the Lego Mindstorms sensors and motors has a
corresponding Java component. To use the component the physical Lego
hardware needs to be attached to a JCX sensor or motor port, and a
corresponding software component needs to be created and initialized
to the correct port number. The port number is a JavaBeans property.
Motor
The motor component has a forward, reverse, off, and brake
state. This state can be set at any time through the State property.
The motor component can also receive four unique TriggerEvents to set
the motor state corresponding to forward, reverse, off, and brake.
Because a TriggerEventListener is an interface, the motor component
can directly receive only one TriggerEvent. This is because a Java
object can implement an interface only once. This default trigger
event will set the motor state to forward. Three inner classes that
implement the TriggerEventListener interface are used to set the
state to reverse, off, or brake. Applications can obtain a reference
to one of these inner classes to register it as an event listener to
another component by calling the getReverseListener(), getOff-
Listener(), or getBrakeListener() methods.
The power level of the motor can be set from zero to full
power. Two properties set the motor power level: MaximumPowerLevel,
which determines the power range, and PowerLevel, which determines
the duty cycle for the application of power to the motor when it's in
the forward or reverse state. Because of the real-time responsiveness
of the aJ-80 direct execution Java processor used in the JCX, the
duty cycle for the motor can be set in the high kHz range.
Touch Sensor
The touch sensor component monitors the state of one Lego
touch sensor switch. The state of the switch can be checked by
calling the isPushed() method. An AssertEvent is sent with true for
the parameter when the button is pushed and false when the button is
released. A TriggerEvent is also sent when the button is pushed and a
second trigger event is sent when the button is released. This
component also can receive an AssertEvent that will disable the touch
sensor when false is passed and enable it when true is passed.
Light Sensor
The light sensor component monitors the light intensity from
one Lego Mindstorms light sensor. The light intensity can be checked
by calling the getLightIntensity() method, which returns a value
between 0 and 4095, with 0 being the darkest. The light sensor also
has a specific light range. The lower and upper limits of the range
can be set using the UpperLimit and LowerLimit properties. When the
light intensity transitions from inside the specified range to
outside the range, a TriggerEvent is sent, as is an AssertEvent with
false for the parameter. When the light intensity transitions to
inside the range, a second TriggerEvent is sent as well as an
AssertEvent with true for the parameter. If a situation were to occur
where more than one light intensity range were required, it's
possible to create more than one light sensor component and assign
them to the same sensor port, each component configured for a unique
intensity range. The light sensor also receives an AssertEvent that
will disable the sensor when false is passed and enable it when true
is passed.
Example: Autonomous Mobile Robot Line Follower
To show the ease of use of the components, we implemented an
Autonomus mobile robot line follower (see Figure 2). The LineFollower
application (see Listing 1) uses a light sensor, two motors, and a
one-shot timer to drive a wheeled robot down a black line drawn on
white paper. Because most of the functionality is in the components,
the line follower code is extremely small and easy to maintain. The
LineFollower application creates the components, configures them, and
defines any event handlers that require special processing. It's also
possible to use a JavaBean-friendly developer tool such as Borland
JBuilder to autogenerate the code that creates and initializes the
components (see Figure 1).
Figure 1:
The LineFollower application uses a light sensor to follow
the line. When the robot is over a line, the motors will cause the
robot to move forward. When the robot leaves a line, the motors will
cause the robot to turn back to the line. This is done by remembering
which direction the robot had to turn last time to find the line, and
turn the other direction this time. This algorithm assumes that the
robot crosses the line and exits on the opposite side. There's a
possibility that the robot will leave the line on the same side that
it entered it. This can happen if the line curves or if the robot
finds the line before it completely turns toward it. In this case,
the robot must again change directions to refind the line (see Figure
2). This condition is detected by monitoring the time since the robot
left the line. If a time limit expires, the robot again changes
directions, this time toward the line (see Figure 3).
Figure 2:
Figure 3:
The LineFollower application is arranged into three distinct
sections: creating the components, configuring their properties, and
defining any special event handlers. This same pattern is used for
many applications that use JavaBeans.
The left and right motor components control the left- and
right-drive wheels of the robot, respectively. When both motors are
in the forward state, the robot will move forward. When one motor is
in the forward state and the other is in the reverse state, the robot
will turn. Initially both motors are set to the forward state so the
robot will move forward until it first finds the line.
A light sensor component is created and initialized to a
lower threshold of 0 and an upper threshold of 1,000. Test cases
showed a reading of about 800 when the light sensor was over a black
line and about 1,200 when it was not. These limits will configure the
sensor to be in range when it is over a black line and out of range
when it is not.
When the light sensor detects a line, it sends a
TriggerEvent. An inner class is registered to receive this event and
in turn calls the enteringLine() method. When a line is entered, the
robot should stop turning and move forward so both motors are forced
into the forward state. When the light sensor detects white space, it
will send another trigger event indicating that it is now out of
range. A second inner class is registered to receive this event and
in turn calls the leavingLine() method.
When the robot is no longer tracking a line, it needs to
start turning in the opposite direction. This is done in the
triggerEvent() method that checks the direction variable and sets the
motors to turn the other way. The enteringLine() method also has to
start the timer so that the direction will again be switched if the
line is not found.
A OneShotTimer component is used to reverse the direction of
the robot if a line is not found. It's initialized to a period of
500ms and will send a TriggerEvent when a timeout occurs. Instead of
using an inner class to receive the trigger event, our main class
implements the triggerEvent() method directly and registers itself as
the listener for OneShotTimer timeouts. When a timeout occurs, the
triggerEvent() method will be called and will reverse the robot's
turn direction.
The design and implementation of the Mindstorms components
required a few weeks of effort, but after that the LineFollower
application required only about an hour to write, and most of that
time was spent writing detailed comments. This is a very small
example, but a large example that uses similar components could
achieve similar productivity.
The LineFollower implementation on the aJ-80 processor is
also very memory-efficient. Passing the code through the aJile
ROMizing tool, JEMBuilder, produced a standalone executable that
required less than 80KB of ROM, and only 35KB of RAM when using the
standard J2ME Connected Limited Device Configuration (CLDC) runtime.
Finally, because of the Java bytecode execution efficiency of the JCX
platform, the robot line follower was amply powered by 6AA batteries
(the same power supply used by the Lego RCX); much of the power is
consumed by the motors.
Conclusion
The LineFollower example program described above demonstrates
100% Java-based components that are used to control a Lego Mindstorms
robot. The components were built specifically for the Lego hardware,
but the concepts are applicable to many other types of embedded and
real-time applications. Both a serial port or a debounced button
component would be appropriate for an embedded controller, a joystick
component for a gaming box, and an error logger component for an
industrial-control application. In all these cases, design and
implementation time can be drastically reduced and maintainability
enhanced by using a standard, embedded real-time component model.
Java and the JavaBean Component Architecture are an excellent
platform for building reusable applications. However, until recently
the Java platform was not practical for embedded and real-time
computing. The release of J2ME, the Real-Time Specification for Java
(RTSJ), and the availability of high-speed low-power Java hardware
have overcome these barriers, allowing embedded developers to take
advantage of the productivity and portability of the Java platform.
The addition of a real-time embedded component model makes real-time
embedded Java environments all the more compelling for developers.
Author Bio
David Hardin is chief technical officer and cofounder of aJile
Systems, Inc. He is a coauthor of The Real-Time Specification for
Java, and a member of several Java expert groups
convened under the Java Community Process. Dr. Hardin holds a PhD in
electrical and computer engineering from Kansas State University.
david.hardin@ajile.com
Mike Frerking is software lead at aJile Systems, Inc., where he
develops tools and components for embedded and real-time Java development. Mike received
his BSEE from Iowa State University.
mike.frerking@ajile.com
Listing 1
/**
* Controls a JCX with a light sensor and two motors to follow a line.
* The line follower has two guiding rules. First if it crosses the line,
* it will change the direction to find the line again. Second,
* if it doesn't cross the line after a specified amount of time,
* it is probably in error and will again flip to find the line.
*/
package linefollower;
import com.ajile.jcx.*;
import com.ajile.components.OneShotTimer;
import com.ajile.events.*;
public class LineFollower implements TriggerEventListener {
// Create left hand side motor
Motor leftMotor = new Motor(Motor.MOTOR_PORT_0);
// Create right hand side motor
Motor rightMotor = new Motor(Motor.MOTOR_PORT_1);
// Create the light sensor
LightSensor lightSensor = new LightSensor(LightSensor.SENSOR_PORT_2);
// Create one shot timer
OneShotTimer timer = new OneShotTimer();
// Indicates last turn direction.
public static final int LEFT = 0;
public static final int RIGHT = 1;
int direction = RIGHT;
/**
* Main entry point. Creates one instance of this class
*/
public static void main(String[] args) {
LineFollower instance = new LineFollower();
}
/**
* Initializes all JCX components and registers event handlers
*/
public LineFollower() {
// turn motors on
leftMotor.setState(Motor.MOTOR_FORWARD);
rightMotor.setState(Motor.MOTOR_FORWARD);
// initialize light sensor so on the line is in range (0-2000)
lightSensor.setLowerLimit(0);
lightSensor.setUpperLimit(1000);
// initialize the timer for a .5 second delay
timer.setIntervalMillis(500);
// register the flip triggerEvent method when a timeout occurs
timer.addTimerListener(this);
// use inner class to register the leavingLine method when the
// light sensor moves out of range
lightSensor.addOutOfRangeListener( new TriggerEventListener() {
public void triggerEvent() {
leavingLine();
}
});
// use inner class to register the enteringLine method when the
// light sensor moves in range
lightSensor.addInRangeListener( new TriggerEventListener() {
public void triggerEvent() {
enteringLine();
}
});
}
/**
* Called by the light sensor when light intensity
* increases past the upper threshold indicating that we
* are moving from dark to light. We lost a line.
*/
void leavingLine() {
// the line has been lost, so reverse turn
triggerEvent();
// reset the timer in case line is not refound
timer.start();
}
/**
* Called by the light sensor when light intensity
* decreases past the upper threshold indicating that we
* are moving from light to dark. We found a line
*/
void enteringLine() {
// stop the timer
timer.stop();
// Line found, move forward
leftMotor.setState(Motor.MOTOR_FORWARD);
rightMotor.setState(Motor.MOTOR_FORWARD);
}
/**
* Causes the JCX to turn in the opposite direction.
* If the JCX is presently going straght, this routine
* will still cause it to turn in the opposite direction
* as last time this routine was called.
*/
synchronized public void triggerEvent() {
if (direction == LEFT) {
direction = RIGHT;
leftMotor.setState(Motor.MOTOR_REVERSE);
rightMotor.setState(Motor.MOTOR_FORWARD);
} else {
direction = LEFT;
leftMotor.setState(Motor.MOTOR_FORWARD);
rightMotor.setState(Motor.MOTOR_REVERSE);
}
}
}