| Programming Robots
By Paul J. Perrone
By simply laying down a few configuration files and a minimal amount of custom code, robot components, their interconnections, and their execution can be rapidly configured with MAX and Java. In this article based on chapter 3 of Programming Robots, author Paul Perrone explains how most of the brilliance and specific behaviors of your bot will be buried inside of the conduct, sensor, and actuator components themselves.
You may also be interested in...
|
Sensors and actuators are devices that connect to your robot and
interface with the physical world. In the case of sensors, we’re generally
sensing information from the physical world. In the case of actuators, we’re
generally affecting the physical world based on informational commands from
your robot. There is a commonality across sensors and actuators to realize this
physical interface with the world and we leverage that fact using the peripheral
abstractions illustrated in figure 1.
Here we see that a peripheral ultimately derives from the component
and system abstractions. Additionally, a peripheral is considered a type of
hardware component and hence why it has a hardware abstraction as its immediate
parent abstraction. In the MAX library, there are interfaces for peripheral and
hardware and associated generic class realizations called PeripheralGeneric
and HardwareGeneric.
Figure 1 Robot peripheral structure
Peripheral Interfaces
Since a peripheral is an external interface with your robot,
there must be some means to communicate and interact with this external device.
That is, if your peripheral is a GPS sensor, you may, for example, interact
with this GPS device through a serial port interface. Alternately, you may also
have an Ethernet interface to such a device. The range of physical interfaces
to and from your robot and different peripheral devices may vary widely. Be it
serial ports, Ethernet, digital I/O, analog I/O, or some other physical
connection, your peripheral will have some physical interface to your robot.
Thus, as illustrated in figure 1, a peripheral abstraction has a relationship
with an interface object. The peripheral may serve as a software representation
of an external device for your robot, but the interface represents the physical
interface this peripheral has with the external device. This relationship is
managed underneath the hood by your peripheral via an InterfaceManager. The
InterfaceManager can manage one or more physical interfaces for your peripheral.
However, it is most commonly used to simply manage a single primary interface.
It manages when the interface is opened, when it is closed, how information is
written to the interface, and how information is propagated from the interface.
This degree of abstraction aids in building interface-independent
robot applications. Therefore, for example, if you build an application that
talks to a GPS sensor over a serial port and then later change to a different
GPS device that leverages an Ethernet interface or perhaps leverages a serial-to-Ethernet
converter, you can change the interface type without changing the GPS class and
code. This is just one form of abstraction built into MAX that helps isolate
change and provides for a tremendous degree of flexibility in coding portable
and extensible robot applications. As illustrated in figure 2, an InterfaceGeneric
class is extended by a wide variety of concrete interface types in MAX and also
provides a hook to more rapidly build and integrate your own physical
interfaces if you so choose.
Figure 2 Robot interface examples
Configuring Actuator Peripherals
With this background on peripherals in mind, let’s go to our
Hello World sample. Listing 1 shows the configuration for our sample actuator.
We pulled this configuration and its interface configuration directly from the
[templates-config]/actuators/standardoutput directory over to our
[src-config]HelloWorld/actuators directory. Here, we’re configuring a generic
actuator specifying the class ActuatorGeneric. Common to all MAX objects, we
can indicate how this object will be referenced by other objects via a
ReferencePolicy configuration specification. Our Actuator instance is shared by any object
referencing this object. These are the common reference policy types:
- Dedicated—
Indicates that each instance of this object referenced by other objects in the
process is uniquely instantiated. That is, when referenced, a new instance of
the object is returned. This is the default configuration.
- Shared—Indicates
that this object configuration instance is shared by all objects referencing
this object. Thus, a single instance of the object is created and that same
instance is returned to all referencing objects.
- Pooled—Indicates
that this object configuration creates a pool of objects. Thus, referencing
objects get unique handles as managed by a pool of objects. Objects returned to
the pool are released for other objects to reference.
Listing 1 Configuring an actuator peripheral
="1.0"
<hashtable id="Actuator">
<string id="ClassName">
com.perronerobotics.actuator.ActuatorGeneric
</string>
<id id="ReferencePolicy">Shared</id>
<hashtable id="Configuration">
<boolean id="AutoStart">true</boolean>
<id id="Interface">./StandardOutputInterface</id>
</hashtable>
</hashtable>
We’re also configuring our Actuator to AutoStart. This is a
configuration parameter part and parcel of this base HardwareGeneric class with behavior
further refined in the PeripheralGeneric
class. Setting this value to true induces the peripheral to call openInterfaces()
on its InterfaceManager at the tail end of the peripheral’s configuration. This
induces the InterfaceManager
to call open()
on each of the peripheral’s configured interfaces. Thus, for example, if the
interface is a serial port, the port may be opened at this point automatically.
Otherwise, a client object to the Peripheral would have to explicitly induce an
open of its underlying interfaces.
In this sample, as indicated in listing 1, the actuator
peripheral indicates that its configured interface has an ID labeled
"./StandardOutputInterface". We saw earlier that the Robot’s configuration
specified that one of its components had an ID of actuators.Actuator. In the directory
[src-config]/HelloWorld/actuators, there is the Actuator.xml file listed in listing
1. In the case of this actuator, it is indicating that its interface is in the
same context as itself using the "./" notation. Thus, the fully qualified name
of the interface is actuators.StandardOutputInterface
and you’ll find a StandardOutputInterface.xml file in
[src-config]/HelloWorld/actuators.
This StandardOutputInterface.xml configuration is shown in listing
2. As we see here, this interface is a StandardIOInterface
class (in the MAX
library). This class extends the InterfaceGeneric
class and provides a simple interface to
standard input and output. In this case, the class is configured with its
default configuration. When induced by the peripheral to write information to
its output, it simply writes to standard output. This provides us with a simple
way to see how an actuator plugs into an application. This class simply dumps
its information to standard output but other actuators may have interfaces
which write commands out to computer ports or directly control an output via a
discrete digital, pulse width modulated, or analog output interface.
Listing 2 Configuring An Actuator Interface
="1.0"
<hashtable id="StandardOutputInterface">
<string id="ClassName">
com.perronetech.comm.stdio.StandardIOInterface
</string>
</hashtable>
As you can see, configuring a Peripheral, and, in the above case,
an actuator peripheral, is rather straightforward. If we’re able to leverage
generic classes such as ActuatorGeneric
or more concrete classes in the MAX library such as StandardIOInterface
, writing to an
actuator may be as simple as dropping a few configuration files into a project
directory. Naturally there are more complicated scenarios to consider such as
mapping application information to/from low level protocol information going to
or coming from a specific peripheral device and interface. It may also be the
case that you desire to leverage some of the generic features of peripherals
and interfaces and extend their services with concrete classes of your own
making. Thus, you might define your own concrete extension of ActuatorGeneric
or
InterfaceGeneric
.
Configuring Sensor Peripherals
The other peripheral leveraged by our sample Robot is a sensor.
Listing 3 provides configuration information for a Sensor object located within the sensors.Sensor
context and [src-config]/HelloWorld/sensors/Sensor.xml file, which we pulled
from the [templates-config]/sensors/standardinput directory. You should begin
to detect a pattern here by now. You’ll see that we have a SensorGeneric
class defined, with a Shared reference policy, configured to AutoStart its
interfaces, and defining its interface. Here, the interface is a StandardInputInterface
object stored in the sensor’s same context (in sensors.StandardInputInterface).
For a sensor and, for that matter, for any Peripheral receiving
input, we need to define some way to read information from the sensor. In this
case, we’ve configured an AsynchronousPolicy, which instructs the peripheral to
automatically listen for events on its interface. Thus, when information is
available at the interface driver level, it will be received and pushed to the
Peripheral’s InterfaceManager
. That gets information from the Interface to the
InterfaceManager
automatically as it is received on the interface. To further
automatically push information from the InterfaceManager
to the Peripheral
object itself, we configured a ListenForInterfaceUpdates
variable. This gets information
automatically routed from the InterfaceManager
to the peripheral no matter what
type of information is received. Since one Interface object may actually feed
one or more peripheral objects, this filtering will come in handy.
="1.0"
<hashtable id="Actuator">
<string id="ClassName">
com.perronerobotics.actuator.ActuatorGeneric
</string>
<id id="ReferencePolicy">Shared</id>
<hashtable id="Configuration">
<boolean id="AutoStart">true</boolean>
<id id="Interface">./StandardOutputInterface</id>
</hashtable>
</hashtable>
We have information propagated from this sensor peripheral’s
interface all the way to the sensor peripheral object itself. It is possible to
subclass and further configure the SensorGeneric
class and do some
preprocessing on the information it receives from the external world. But in
our HelloWorld sensor configuration, we simply pass the information own
downstream to a conduct.Plan
object. We do achieve this by adding an entry to the TriggerAlwaysObjectID
s configuration
section in the template. For our prebuilt HelloWorld example in listing 3, this
value is already added. The TriggerAlwaysObjectIDs
configuration is built into the SystemGeneric class (a parent class for SensorGeneric) and
automatically propagates events received to objects listed in its
configuration. Here, we configure our application to push events to our custom
Plan object that has been configured as a sub-component of our sample Robot.
Our Sensor
object is configured with the interface whose configuration is listed in listing
4. Here we see that our HelloWorld sensor’s interface is simply an
instantiation of the same StandardIOInterface
class used for the actuator. The difference here is that we’ve defined this
interface object to ListenOnConsole
,
which means that it will listen on standard input for input information. This
is our simple way to illustrate how a sensor interface may be configured. You
act as the external sensor and enter integer values on the command line when
running this sample. The numbers you input are read by the StandardIOInterface
,
propagate to the Sensor’s InterfaceManager
, to the sensor itself, and then to
the Plan
object.
Listing 4 Configuring a Sensor Interface
="1.0"
<hashtable id="StandardInputInterface">
<string id="ClassName">
com.perronetech.comm.stdio.StandardIOInterface
</string>
<hashtable id="Configuration">
<boolean id="ListenOnConsole">true</boolean>
</hashtable>
</hashtable>
Summary
Peripherals, be they sensors or actuators, are key components of
any robot system. As we’ve just seen in our basic sample, it is very easy to
configure a basic sensor and actuator for use in our robots using the technique
shown here. Peripheral interfaces are also easily configured and in such a way
that a decoupling between peripheral objects and their physical interfaces may
be achieved.
Here are
some other Manning titles you might be interested in: