TinySoar on the Lego® Mindstorms RCX


It's probably best to start with the build instructions, which explain how to build and install the “default” test agent for the Lego Mindstorms RCX, bumper.soar. Once you've verified that this agent works as expected, you're ready to write a new agent for the RCX. The steps to create a new agent are as follows:

  1. Load the agent's productions into the Tcl host environment,
  2. Use the export command to write a new version of the agent.inc file,
  3. Re-compile the legosoar target for the RCX.
  4. Download the new version of the legosoar firmware to the RCX using the LegOS firmdl3 command.

For example, say we wanted to install the brain-dead agent implemented in drive.soar on the RCX. First, we start the tclsh host environment and load the productions by source-ing the drive.soar file:

$ TCLLIBPATH=`pwd` tclsh
% source ../tinysoar/tests/drive.soar
compiled: propose*drive
compiled: implement*drive
compiled: drive*reconsider

Next, we export this file, replacing tinysoar/agent.inc:

% export ../tinysoar/agent.inc

Now we exit the host environment, and rebuild the legosoar target:

% exit
$ cd ../h8300-hitachi-hms
$ make legosoar
...Build output...

And finally, we download the new firmware to the RCX:

$ /usr/src/legOS/util/firmdl3 --slow legosoar

Note that you'll need to pull the batteries out of the RCX each time before downloading a new TinySoar agent. This is because a TinySoar agent is firmware, and isn't smart enough to notice that new firmware is trying to be downloaded on top of it. (I really ought to fix this.)

If all goes well, the motors should start to unconditionally run when you press the Run button on the RCX.

Input and Output

The TinySoar runtime for the Lego Mindstorms RCX allows a TinySoar agent to interact with the RCX's sensors via the ^input-link, and control the RCX's motors with the ^output-link.


Specifically, each of the three sensors are sampled once per elaboration. Each sensor's value is converted to an integer value between 0 and 1023, and added to the top state's ^io.input-link as the value of the ^sensor-1, ^sensor-2, or ^sensor-3 preference, as appropriate.

The touch sensors typically register values above 1,000 when not depressed, and values in the low 100's when depressed (the signals tend to drift, due to the fact that they are being converted from an analog voltage). A rule like the one that follows is probably sufficient to detect a touch sensor's depression:

sp {detect*sensor-a*depressed
    (state <s> ^superstate nil ^io.input-link.sensor-1 > 512)
    (<s> ^touch-sensor-a depressed)}

The light sensor tends to wobble between values of about 300 for bright light to 800 for darkness.

The sensors are currently run in “active mode” all the time; e.g., so that the light sensor's LED will turn on.


Similarly, the RCX runtime checks the ^io.output-link for ^motor-a, ^motor-b, and ^motor-c attributes once per elaboration. Each attribute's value controls the corresponding motor: the symbolic constant forward will make the motor run “forwards”; the symbolic constant reverse will make the motor run “backwards”; the symbolic constant brake will lock the motor in position; and a value of off (or the absence of an attribute) will turn off the motor, allowing it to “free-wheel”.

The following rule (from drive.soar) runs motors A and C “forwards” when the drive operator is selected:

sp {implement*drive
    (state <s> ^operator <o> ^io <io>)
    (<o> ^name drive)
    (<io> ^output-link <out>)
    (<out> ^motor-a forward ^motor-c forward)}