Introduction: Control Your IRobot Create With a Palm Pilot

In this instructable I will describe how to interface a palm pilot with the iRobot create. I've used a Palm IIIxe in my project, but any PDA that runs the Palm OS and has a serial port should work. I've also designed this project to work with the iRobot Create, but as long as the interface commands are the same this should also work with the Roomba.

What do I mean by interface?

At the end of this instructable you should be able to monitor your iRobot Create with your Palm Pilot; this includes checking all of it's external sensors and viewing the battery voltage. You will be able to run the demo programs from the Palm Pilot where there will also be descriptions of the demo's. And last but not least you will be able to create scripts for the iRobot Create to follow without the need for a computer.

I've included all of my source code, and trough this instructable I will describe it piece-by-piece. My goal is that you will be able to take my program and modify it to suite your own needs.

Good luck!

Step 1: Gather the Materials

To complete this project you will need:

  • An iRobot Create (or possibly a Roomba)
  • The Create Robot Serial Cable (it comes with the iRobot Create).
  • A Palm Pilot (you can get these off of eBay or in yard sales for cheep). I'm using a Palm IIIxe.
  • A dock for the Palm Pilot.
  • A Null Modem and a 9-pin gender changer (male - male). To make your own (I'll show you how) you will need:
  • 2 9-pin D-SUB connectors (male).
  • 2 9-pin enclosures for the connectors.
  • Some cable to connect the 2 9-pin connectors together (the cable must have at least 3 wires).
  • The tools and skills required to solder.

I have included the program so you can install it onto your Palm Pilot without any hassle. If you want to compile it yourself you will need:
If you plan on developing for the palm or the iRobot, there is a lot of really good documentation you should look at. The website where you can download the Palm OS SDK also has some really good documentation. If you want to work with the create iRobot puts out really good documentation for that too.

Disclaimer: I'm using Linux to do everything, so these steps (specifically the ones dealing with compiling the source code and installing the program on the Palm Pilot) might change slightly depending on your OS. This website contains tools for all 3 major operating systems.

Step 2: Build the Null Modem

If you already have a Null Modem and a male-male 9-pin gender changer you can skip this step.

We need to use a Null Modem (as opposed to just a 9-pin gender changer) to swap the transmit-receive wires between the iRobot Create and the Palm Pilot. If we don't the transmit wire on the iRobot Create would go to the transmit wire on the Palm Pilot (that doesn't work). With the Null Modem the transmit wire on one device goes to the receive wire on the other device (and vice-versa) (this is how we want it). There are different designs for Null Modem's out there, the simplest one is the Null Modem without Handshaking. In this design the wiring is as follows:

Connecter1 | Connector2
!-------------------
Pin 2 --> Pin 3
Pin 3 --> Pin 2
Pin 5 --> Pin 5

See the pictures for a wiring diagram.

This is the most basic wiring for the Null Modem, and for our purposes it should be more then sufficient; however any Null Modem should work. Regular Null modems usually have one male end and one female end, and thus the need for a gender changer. Were using 2 male ends in this case because one end needs to plug into the Create Robot Serial Cable and the other needs to plug into the Palm Pilot's dock.

Note: I have a few extra wires in my picture because I made a Null Modem with loop back handshaking, this was a personal choice.

Step 3: Put Everything Together

The physical assembly of this robot is pretty simple, as we aren't adding any robotics or sensors. The assembly basically consists of stuffing all of the cables into the convenient space left in the back of the iRobot Create, and making a simple cover to add professionalism (the next step). I happen to have 2 Palm Pilot dock's, so I can dedicate one to the robot. If you only have one you might want to first install the iRobot program onto the Palm Pilot (step 5).

Right now you should have the Palm dock, the Create Robot Serial Cable, and your null modem. It's a good idea to bundle up the wires with twist-ties (or string/elastics/etc.) so we don't have a big mess of wire. The Serial Cable should be plugged into the create and the Null Modem. The other end of the Null modem should be plugged into the Palm dock (if you need to use a 9-pin gender-changer to make things fit then do so).

When your finished you should have something that resembles the picture below. With just this you can communicate with your robot using your Palm Pilot; you might want to test everything now in case something isn't working. If everything works congrats, you can move on to the next step.

Step 4: Make a Snazzy Cover for the Cargo Bay

This step is largely optional, but it really helps the look of the finished robot.

First you should take a piece of paper (tracing paper might be best) and set it over the iRobot Create's cargo bay. Using a pencil (or pen, or whatever) poke out holes in the paper where there are screw holes in the Create. I found it helpful to use the screws that come with the create to physically screw down the page. Then trace A box around the cargo bay (the 4 screw holes should be within the box). The idea here is to make a stencil we can use to cut out a cargo bay cover. When you are finished you should cut off the excess paper from the stencil, it should basically look like a box with 4 holes around the sides.

What you want your cover to be made of is up to you, personally I just used cardboard but if you have something better handy feel free to use that instead. Take the material of choice and use the stencil you just made to trace out the outline of the cargo bay cover; you'll also want to make a mark for each screw hole. With scissors (or other cutting-thing) cut out the cargo bay cover and punch out the screw holes.

Now that you have the cover, you just need to affix the palm pilot/dock to it. I found velcro worked well, but you can use anything. You may want to avoid permanent adhesion (glue), in case you want to re-use the dock. It's useful to cut out a hole in the cover to allow the chord to enter the cargo bay, and exit where it plugs into the serial port (see picture). A hole punch (like you would use at school to punch holes in paper) can be really useful here.

Step 5: Install the IRobot Program

Now we'll install the iRobot program (inventive name, eh?). If you want, you can play with the source code and modify it however you please. But for now we'll use the pre-compiled binary that I've included with this instructable.

If your using Windows you can use the Palm Desktop software to install programs onto the palm pilot. I'm not using Windows so I can't provide details, but it's pretty strait forward. Just install the Palm Desktop program, and use the software installer to install the iRobot program onto your Palm device.

If your using Linux and KDE chances are that KPilot is already installed on your system. At least on my computer it was located under Utility's -> Sync -> KPilot in the applications menu. The first time you start it up it will open a wizard that will walk you trough the configuration. Once it's started you can go to the "File Installer" to add files that will be installed to the Palm Pilot next time you Sync. If your using Gnome, or if KPilot isn't already installed, you should be able to use your package manager to install it. Gnome has it's own software for Syncing Organizers called gnome-pilot but I'm not familiar with it myself.

At this point you should have the robot fully assembled with the iRobot program installed on your Palm Pilot. The next few steps will describe how to use the program and how the source code works.

Step 6: How to Use the Program - Part 1

The program is fairly strait forward to use. It doesn't do anything really advanced, but it will allow you to get a feel for what the iRobot Create "sees" and make simple scripts for it to follow.

When you start the iRobot program you'll be greeted with a image of a Roomba Discovery and a few text fields. This is just an informational page where you can get a few statistics from the robot. Right now there isn't much going on, that's because the program isn't querying the robot. If you press the menu button on your Palm Pilot you'll see a few menus. The "Main " menu allows you to select between the different parts of the program (more on that in a second) and if you select the "Robot" menu you'll see some options for communicating with your robot. Click on the "Connect" button under the "Robot" menu to start communications with the iRobot Create. The create will need to be turned on, and the palm pilot connected for this to work. If it's not hooked up when you click "Connect" that's ok, it'll start querying the robot when you plug it in. If everything is on and working the Palm Pilot should be displaying information from the robot. If you press the bumper, or lift it up you should see various parts of the Roomba image light up in response. The text fields should also be displaying information like battery voltage and run time. Because of the way the display updates it'll be difficult to use the menu while the program is updating the sensor info. To stop this you can press the "Stop" button, or use the shortcuts instead of the menu.

The short-cuts are pretty strait forward. You draw a diagonal line in the "Graffiti" area on you palm pilot (going from bottom-left to top-right) using the stylus. Then quickly draw the Graffiti symbol for the letter that represents the menu item you want to select.

S - Status page
I - Scripts page
M - Edit Macros page
!---------------------------
C - Connect
D - Disconnect
R - Run
O - Stop
!---------------------------
H - Help
A - About

Step 7: How to Use the Program - Part 2

The most useful part of this program (besides possibly being able to monitor the battery voltage) is it's ability to make and run scripts on the iRobot Create.

If you select the "Scripts" option under the "Main" menu you will be displayed a list of the demo programs available on the iRobot Create along with a short description of each one. If you select a script and then select the "Run" command from the "Robot" menu on the Palm Pilot it will tell the iRobot Create to run that script. If you go to the "Status" page the program will remember your selection. When you switch between the different pages in the iRobot program the Palm Pilot will stay connected to the robot (it will only send the sensor query's while your viewing the "Status" page). The program only disconnects when you tell it to do so or you exit. Also, the program will automatically connect to the robot (if it's not connected already) when you select the "Run" and "Stop" menu items.

The Edit Macro's page allows you to make a script of commands and send them to the iRobot Create. The scripting options are a little limited, just basic movement, but complexity wasn't my intent. You are also limited to 8 commands; this is mostly a limitation of the iRobot Create because I've opted to use it's built-in scripting ability. For reasons that I've yet to determine you'll probably need to press the "Send to Robot" button twice before the robot reacts to your commands.

With this aside, the scripts still provides amusement value and more power then just the demo's offer. You can select a series of actions to send to the iRobot Create, and select the length of time (in seconds) to complete each action (such as drive forward, or turn). When your finished you can send the script to your robot and watch it run.

Step 8: Prepare the Build Environment

If you want to work with the source, you'll need to set up the build environment.

There are 3 main tools that you'll need to make Palm Pilot programs, they are The SDK, prc-tools, and PilRC. To download the SDK you will need to make an account on the website (it's completely free). I also recommend looking around at the documentation you can find there, it has proved to be invaluable for me.

The steps to install the various tools can differ between OS's. For Linux, I would first check your package manager to see if any of the programs I've mentioned above can be installed that way. If the packages aren't available trough your package manager, you can download the programs from the sites I've linked above. Instructions for installation will come with the files. As with the vast majority of programs for linux, you should be able to compile and install the programs from there source code by downloading the program (in source code form), extracting the files you just downloaded, opening up the command line, navigating to the folder containing the program, and typing "./configure", "make" and then (as root) "make install" (without the quotes).

Step 9: The Program, Part 1

In the next few steps I'm going to assume the reader is already familiar with the C language. If your not you should try out a few tutorials and practice programs first. I found this document helpful, but it assumes a fair amount of programming experience.

Disclaimer: Before this project I had zero experience with the C language, my programming experience had been limited to Visual Basic and (mostly) Java. Because of this I'm not sure where the similarity between C programming for the Palm OS and for the Computer begin and end. And since this is quite literally my first C program ('cept for the obligatory "Hello World" app) my adherence to C standards and good codeing practice is probably pretty bad as well.

The first part of this program is the .rcp file. This file contains information PilRC uses to build the GUI; we'll call it "robot.rcp". We'll also need another file called "robot.h" where we will define some constants that will be used when referencing the GUI elements. Each element is assigned an ID number, and we will assign each number a name. When the compiler goes trough the source code it will replace the names with the numbers that they represent. For example "FormMain" is defined as 9000, that means every time we use "FormMain" in the program we really are using the number 9000.

Here is my robot.h file:
//--------------------- Fourms -----------------------
#define FormMain	9000
#define FormSelect	9001
#define FormMacro	9002
#define MainMenu	9100
#define AboutAlert	9101

//--------------------- Form Main --------------------
#define PicRobot	1000
#define PicWall_	1001
#define PicCliff	1010
#define PicWhealR	1011
#define PicWhealL	1012
#define PicBumpR	1013
#define PicBumpL	1014
#define PicWall		1015
#define FldVoltage	1100
#define FldProg		1101
#define BtnDisconnect	1200

//--------------------- Form Select ------------------
#define SelScript	2000
#define FldDescription	2001

//--------------------- Form Macro -------------------
#define BtnSend		3000
#define BtnClear	3001
#define BtnDel		3002
#define BtnUp		3100
#define BtnDown		3101
#define BtnLeft		3102
#define BtnRight	3103
#define BtnCClock	3104
#define BtnClock	3105
#define BtnPause	3106
#define BtnP1		3201
#define BtnP2		3202
#define BtnP4		3204
#define BtnP8		3208
#define BtnP16		3216
#define PicBar		3300
#define PicUp		3400
#define PicDown		3401
#define PicLeft		3402
#define PicRight	3403
#define PicClock	3404
#define PicCClock	3405
#define PicPause	3406
#define FldScript	3500
#define SclScript	3501

//--------------------- Main Menu --------------------
#define MnuStatus	8000
#define MnuScript	8001
#define MnuMacro	8002
#define MnuConnect	8100
#define MnuDisconnect	8101
#define MnuRun		8102
#define MnuStop		8103
#define MnuAbout	8200

Step 10: The Program, Part 2

Now we can define the GUI elements. At the top of the "robot.rcp" file there needs to be a line that contains:
#include "robot.h"
Without this line the compiler wouldn't substitute the element names for ID numbers.

The first form (page) in our program will contain 2 pictures, some labels, and some text areas. The text areas shouldn't be editable by the user. I don't want there to be a border, and there should be a button that allows the user to easily stop the program from updating the sensor display.
FORM ID FormMain AT (0 0 160 160)
NOFRAMEUSABLEMENUID MainMenu
BEGIN
	TITLE "iRobot Status"
	FORMBITMAP AT (20 25) BITMAP PicRobot
	FORMBITMAP AT (110 21) BITMAP PicWall_
	LABEL "Program:" AUTOID AT (10 120) FONT 0
	FIELD ID FldProg AT (80 PREVTOP 70 AUTO) NONEDITABLE UNDERLINED MAXCHARS 15
	LABEL "Voltage:" AUTOID AT (10 PREVBOTTOM+1) FONT 0
	FIELD ID FldVoltage AT (80 PREVTOP 70 AUTO) NONEDITABLE UNDERLINED MAXCHARS 15
	BUTTON "Stop" ID BtnDisconnect AT (110 102 AUTO AUTO)
END

The second form will be used to select the Demo programs on the iRobot Create. It should have an explanatory text label, a list of possible options, and an (uneditable) text field that will show a short description of the selected demo. It should also contain a title, but no border.
FORM ID FormSelect AT (0 0 160 160)
NOFRAMEUSABLEMENUID MainMenu
BEGIN
	TITLE "Select Script"
	LABEL "Select a demo program or Macro:" AUTOID AT (5 20) FONT 0
	LIST "Cover" "Cover and Dock" "Spot Cover" "Mouse" "Figure Eight" "Wimp" "Home" "Tag"
		"Pachelbel" "Banjo" ID SelScript AT (30 35 90 1) FONT 0 VISIBLEITEMS 5
	FIELD ID FldDescription AT (5 PREVBOTTOM+8 145 60) FONT 0 NONEDITABLE UNDERLINED MULTIPLELINES MAXCHARS 255
END

The final form will be used for creating scripts. It should contain some buttons that control the script as a whole (such as "Run Script" or "Delete Script") as well as buttons used to add commands to the script. To separate these 2 sets of buttons there should be a dividing line (in this case a bitmap). There should also be an (again, uneditable) text field that will show the commands in the script, this text field should be accompanied by a scroll bar. Like the last form there should be a title.
FORM ID FormMacro AT (0 0 160 160)
NOFRAMEUSABLEMENUID MainMenu
BEGIN
	TITLE "Create/Edit Macro"
	BUTTON "Send to robot" ID BtnSend AT (6 20 AUTO AUTO)
	BUTTON "Clear" ID BtnClear AT (PREVRIGHT+3 PREVTOP AUTO AUTO)
	BUTTON "Delete" ID BtnDel AT (PREVRIGHT+3 PREVTOP AUTO AUTO)
	FORMBITMAP AT (0 35) BITMAP PicBar
	BUTTON "Up" ID BtnUp AT (23 45 13 13) NOFRAME GRAPHICAL BITMAPID PicUp
	BUTTON "Left" ID BtnLeft AT (PREVLEFT-15 PREVTOP 13 13) NOFRAME GRAPHICAL BITMAPID PicLeft
	BUTTON "Right" ID BtnRight AT (PREVRIGHT+17 PREVTOP 13 13) NOFRAME GRAPHICAL BITMAPID PicRight
	BUTTON "Down" ID BtnDown AT (PREVLEFT-15 PREVBOTTOM+4 13 13) NOFRAME GRAPHICAL BITMAPID PicDown
	BUTTON "Clock" ID BtnClock AT (68 45 18 18) NOFRAME GRAPHICAL BITMAPID PicClock
	BUTTON "CClock" ID BtnCClock AT (PREVRIGHT+7 PREVTOP 18 18) NOFRAME GRAPHICAL BITMAPID PicCClock
	BUTTON "Pause" ID BtnPause AT (130 PREVTOP 18 18) NOFRAME GRAPHICAL BITMAPID PicPause
	LABEL "Plus" AUTOID AT (46 70) FONT 0	BUTTON "1" ID BtnP1 AT (PREVRIGHT PREVTOP 10 11) RECTFRAME
	BUTTON "2" ID BtnP2 AT (PREVRIGHT+1 PREVTOP 10 11) RECTFRAME
	BUTTON "4" ID BtnP4 AT (PREVRIGHT+1 PREVTOP 10 11) RECTFRAME
	BUTTON "8" ID BtnP8 AT (PREVRIGHT+1 PREVTOP 10 11) RECTFRAME
	BUTTON "16" ID BtnP16 AT (PREVRIGHT+1 PREVTOP 15 11) RECTFRAME
	LABEL "seconds" AUTOID AT (PREVRIGHT+2 PREVTOP) FONT 0
	FORMBITMAP AT (0 PREVBOTTOM+5) BITMAP PicBar
	FIELD ID FldScript AT (5 90 135 70) FONT 0 NONEDITABLE UNDERLINED MULTIPLELINES MAXCHARS 500 HASSCROLLBAR
	SCROLLBAR ID SclScript AT (PREVRIGHT+5 PREVTOP 7 PREVHEIGHT) VALUE 0 MIN 0 MAX 8 PAGESIZE 1
END

Each form should have a menu. The menu will allow the user to select between the forms and control communication with the robot.
MENU ID MainMenu
BEGIN
	PULLDOWN "Main"
	BEGIN
		MENUITEM "Status" ID MnuStatus "S"
		MENUITEM "Scripts" ID MnuScript "I"
		MENUITEM "Edit Macros" ID MnuMacro "M"
	END	PULLDOWN "Robot"
	BEGIN
		MENUITEM "Connect" ID MnuConnect "C"
		MENUITEM "Disconnect" ID MnuDisconnect "D"
		MENUITEM SEPARATOR
		MENUITEM "Run" ID MnuRun "R"
		MENUITEM "Stop" ID MnuStop "O"
	END	PULLDOWN "Help"
	BEGIN
		MENUITEM "Help" ID MnuHelp "H"
		MENUITEM "About" ID MnuAbout "A"
	END
END

We need to define the "about" screen.
ALERT ID AboutAlert
INFORMATIONBEGIN
	TITLE "About iRobot"
	MESSAGE "A program designed to communicate with an iRobot Create\n" \
		"------------------\n"\
		"By: Tristan Losier\n"\
		"Released under the GPL\n"\
		"------------------\n"\
		"iRobot is a registered trademark of the iRobot Corporation"
	BUTTONS "Ok"
END

And last, but not least, we need to define all of the bitmaps and icons that will be used in the program. You may notice that many of the bitmaps defined below aren't used above, that's because they are used elsewhere in the program.
BITMAP ID PicBar	"images/bar.bmp"
BITMAP ID PicRobot	"images/irobot.bmp"
BITMAP ID PicWall_	"images/wall_.bmp"
BITMAP ID PicCliff	"images/sensors/cliff.bmp"
BITMAP ID PicWhealL	"images/sensors/lwheal.bmp"
BITMAP ID PicWhealR	"images/sensors/rwheal.bmp"
BITMAP ID PicBumpL	"images/sensors/bumpl.bmp"
BITMAP ID PicBumpR	"images/sensors/bumpr.bmp"
BITMAP ID PicWall	"images/sensors/wall.bmp"
BITMAP ID PicUp		"images/buttons/up.bmp"
BITMAP ID PicDown	"images/buttons/down.bmp"
BITMAP ID PicLeft	"images/buttons/left.bmp"
BITMAP ID PicRight	"images/buttons/right.bmp"
BITMAP ID PicClock	"images/buttons/clock.bmp"
BITMAP ID PicCClock	"images/buttons/cclock.bmp"
BITMAP ID PicPause	"images/buttons/pause.bmp"
ICON			"robot.bmp"
SMALLICON		"robotsmall.bmp"

Step 11: The Program, Part 3

Now it's time for the "core" of the program, the "robot.c" file.

At the very top of the "robot.c" file we need:
#include "robot.h"
#include "string_arrays.c"
#include <PalmTypes.h>
#include <PalmCompatibility.h>
#include <System/SystemPublic.h>
#include <UI/UIPublic.h>

void SetField (UInt16 formID, UInt16 fieldID, MemPtr str);
Int16 Connect();
Boolean Disconnect();
void DisplaySensors();
Boolean MenuHandler (EventPtr event);
Boolean SelectFormHandler (EventPtr event);
Int16 SendScript (char length);
void Display (char length);
Boolean ScriptFormHandler (EventPtr event);
Boolean MainFormHandler (EventPtr event);
Boolean AppHandleEvent (EventPtr event);
void AppEventLoop();
void AppStart();
void AppStop();
UInt32 PilotMain (UInt16 cmd, MemPtr cmdPBP, UInt16 launchFlags);
// GlobalsUInt16 port = 0;
char prog = 0;
unsigned char script[30][2];
Part of this is the generic Palm OS library's. I'm also declaring all of the functions I've used at the top, so you don't need to worry about any special order when editing the robot.c file. At the bottom I also have a few global variables that I use at various points in the program.

The "PilotMain" function is the entry point to the program. There are various reasons why a program might be started on a Palm Pilot, but we only want to worry about one; If the program was started by the user PilotMain() will start the rest of the program.
UInt32 PilotMain (UInt16 cmd, MemPtr cmdPBP, UInt16 launchFlags) {
	if (cmd == sysAppLaunchCmdNormalLaunch) {
		AppStart();
		AppEventLoop();
		AppStop();
	}
	return 0;
}

The AppStart() and AppStop() functions are pretty basic, AppStart() make sure the application starts properly, and AppStop() makes sure the application closes cleanly.
void AppStart() {
	FrmGotoForm(FormMain);
}
void AppStop() {
	Disconnect();
	FrmCloseAllForms();
}

AppEventLoop() is the core of the program. Whenever something happens AppEventLoop() will retrieve the "event" and try to handle it. First it sends the event to a few built-in functions to try and let the system handle the event. If the system doesn't fully handle the event AppEventLoop() then passes the event to our own functions to try and handle it. FrmDispatchEvent() is the event handler function that we assigned to handle events for currently active form.
void AppEventLoop() {
	EventType event;
	short error;
	do {
		EvtGetEvent (&event, 50); // Wait 100 ticks before sending a nilEvent
		  // Allow the system to attemt to handle events before trying ourself
		if (SysHandleEvent(&event)) continue;
		if (MenuHandleEvent((void*)0, &event, &error)) continue;
		if (AppHandleEvent(&event)) continue;
		if (MenuHandler(&event)) continue;
		  // Send the event to the form event handler
		FrmDispatchEvent(&event);
	} while (event.eType != appStopEvent);
}

AppHandleEvent() is the first non-system function that AppEventLoop() sends events to. It checks to see if the event signifies the loading of one of the forms. If so it sets up the form and assigns a default event handler (for FrmDispatchEvent()).
Boolean AppHandleEvent (EventPtr event) {
	FormPtr frm;
	Int frmID;
	Boolean handled = false;
	char str[255];
	str[0] = '\0';
	if (event->eType == frmLoadEvent) {
		frmID = event->data.frmLoad.formID;
		frm = FrmInitForm(frmID);
		FrmSetActiveForm(frm);
		switch (frmID) {
			case FormMain:
				FrmSetEventHandler(frm, MainFormHandler);
				FrmDrawForm(frm);
				StrCat(str, PROGRAM[prog]);
				SetField(frmID, FldProg, str);
				handled = true;
				break;
			case FormSelect:
				FrmSetEventHandler(frm, SelectFormHandler);
				FrmDrawForm(frm);
				StrCat (str, DISCRIPIONS[prog]);
				SetField (frmID, FldDescription, str);
				handled = true;
				break;
			case FormMacro:
				FrmSetEventHandler(frm, ScriptFormHandler);
				FrmDrawForm(frm);
				handled = true;
				break;
		}
	}
	return handled;
}

MenuHandler() is the next function on AppEventLoop()'s list. If the user makes a menu selection MenuHandler() will handle it.
Boolean MenuHandler (EventPtr event) {
	Boolean handled = false;
	Err err;
	  // The command that runs the demo program that corosponds to 'prog'
	char data[] = {128, 136, prog};
	  // Handle the menu button's
	if (event->eType == menuEvent) {
		MenuEraseStatus(NULL);
		switch (event->data.menu.itemID) {
			  // Go to the verious forms
			case MnuStatus:
				FrmGotoForm(FormMain);
				handled = true;
				break;
			case MnuScript:
				FrmGotoForm(FormSelect);
				handled = true;
				break;
			case MnuMacro:
				FrmGotoForm(FormMacro);
				handled = true;
				break;
			  // Open the serial connection
			case MnuConnect:
				Connect();
				handled = true;
				break;
			  // Close the serial connection
			case MnuDisconnect:
				Disconnect();
				handled = true;
				break;
			  // Start/Stop the currently selected program or macro
			case MnuRun:
				if (!Connect()) break;
				  // Send the command to the iRobot
				SrmSend(port, data, 3, &err);
				SrmSendFlush(port);
				handled = true;
				break;
			case MnuStop:
				  // Replace the 'prog' byte with -1 (255) to stop the currently running program
				data[2] = 255;
				  // Send the command to the iRobot
				if (!Connect()) break;
				SrmSend(port, data, 3, &err);
				SrmSendFlush(port);
				handled = true;
				break;
			case MnuAbout:
				FrmAlert(AboutAlert);
				handled = true;
				break;
		}
	}
	return handled;
}

Connect() and Disconnect() are used to open and close the Palm Pilot's serial port. Weather or not the robot is hooked up to the Palm Pilot and turned on when these functions are used is not too important. Tough, if the robot doesn't receive the "Start" command at least once (it's sent when the Connect() function is called) you might have some problems running your own scripts.
Int16 Connect() {
	Err err;
	char data = 128;
	  // Open the serial port
	if (!port) SrmOpen(serPortCradlePort, 57600, &port);
	  // Send the "Start" command to make sure the Create comes out of "Off" mode
	SrmSend(port, &data, 1, &err);
	SrmSendFlush(port);
	return port;
}
Boolean Disconnect() {
	  // Close the serial port
	if (port && !SrmClose(port)) {
		port = 0;
		return true;
	} else return false;
}

SetFiled() is used by various functions to display data in the text fields defined by the "robot.rcp" file.
void SetField (UInt16 formID, UInt16 fieldID, MemPtr str){
	FormPtr 	frm;
	FieldPtr	fld;
	UInt16		obj;
	CharPtr		p;
	VoidHand	h;
		frm = FrmGetFormPtr(formID);
	obj = FrmGetObjectIndex(frm, fieldID);
	fld = (FieldPtr)FrmGetObjectPtr(frm, obj);
	h = (VoidHand)FldGetTextHandle(fld);
	if (h == NULL)	{
		h = MemHandleNew (FldGetMaxChars(fld)+1);
		ErrFatalDisplayIf(!h, "No Memory");
	}
	p = (CharPtr)MemHandleLock(h);
	StrCopy(p, str);
	MemHandleUnlock(h);
	FldSetTextHandle(fld, (Handle)h);
	FldDrawField(fld);
}

DisplaySensors() is used by the main form (the "Status" form) to retrieve sensor info from the robot and then display it to the user. It first sends the "Query List" command to the robot with a list of sensors that we are interested in. DisplaySensors() will then analyze the robot's reply and update the display accordingly.
void DisplaySensors() {
	  // The data packet sent to the create that requests the sensor data
	char data[] = {128, 149, 8, 8, 7, 9, 10, 11, 12, 22, 21};
	  // The byte array that holds the sensor data retrived by the create
	char inData[9];	char str[20];
	UInt16 tmp, *volt;
	Err err;
	VoidHand bitmapHandle;
	BitmapPtr bitmap;
	Boolean wheall = false, whealr = false;	str[0] = '\0';
	if (port) { // Only perform this loop if the serial connection is open
		FrmDrawForm(FrmGetActiveForm()); // Re-draw the form, to clear the old data
		  // Send the request to the create for the sensor data, and put the reply in 'inData'
		SrmReceiveFlush(port, 0);
		SrmSend(port, data, 11, &err);
		SrmSendFlush(port);
		SrmReceive(port, inData, 9, 20, &err);
		  // The wall sensor
		if (inData[0]) {
			bitmapHandle = DmGetResource('Tbmp', PicWall);
			bitmap = MemHandleLock(bitmapHandle);
			WinDrawBitmap(bitmap, 110, 25);
			MemHandleUnlock(bitmapHandle);
		}
		  // The bumper's and wheal drop sensors
		tmp = (int)inData[1];
		if (tmp >= 16) tmp=tmp-16; // Caster wheal
		if (tmp >= 8) { // Left wheal
			wheall = true;
			tmp = tmp - 8;
		}
		if (tmp >= 4) { // Right wheal
			whealr = true;
			tmp = tmp - 4;
		}
		if (tmp >= 2) { // Left wheal
			bitmapHandle = DmGetResource('Tbmp', PicBumpL);
			bitmap = MemHandleLock(bitmapHandle);
			WinDrawBitmap(bitmap, 25, 30);
			MemHandleUnlock(bitmapHandle);
			tmp = tmp - 2;
		}
		if (tmp == 1) { // Right wheal
			bitmapHandle = DmGetResource('Tbmp', PicBumpR);
			bitmap = MemHandleLock(bitmapHandle);
			WinDrawBitmap(bitmap, 60, 30);
			MemHandleUnlock(bitmapHandle);
		}
		  // We draw the wheal's after drawing the bumpers, so the
		  // pictures overlap properly
		if (wheall) {
			bitmapHandle = DmGetResource('Tbmp', PicWhealL);
			bitmap = MemHandleLock(bitmapHandle);
			WinDrawBitmap(bitmap, 37, 52);
			MemHandleUnlock(bitmapHandle);
		}
		if (whealr) {
			bitmapHandle = DmGetResource('Tbmp', PicWhealR);
			bitmap = MemHandleLock(bitmapHandle);
			WinDrawBitmap(bitmap, 71, 52);
			MemHandleUnlock(bitmapHandle);
		}
		  // The 4 clif sensors
		if (inData[2]+inData[3]+inData[4]+inData[5] > 0) {
			bitmapHandle = DmGetResource('Tbmp', PicCliff);
			bitmap = MemHandleLock(bitmapHandle);
			if (inData[2] > 0)
				WinDrawBitmap(bitmap, 21, 35); // Left
			if (inData[3] > 0)
				WinDrawBitmap(bitmap, 31, 25); // Top left
			if (inData[4] > 0)
				WinDrawBitmap(bitmap, 80, 25); // Top Right
			if (inData[5] > 0)
				WinDrawBitmap(bitmap, 90, 35); // Right
			MemHandleUnlock(bitmapHandle);
		}
		  // The raw battery voltage
		volt = (UInt16*)(inData + 6);
		StrIToA(str, *volt);
		StrCat(str, " mV");
		if (inData[8] > 0 && inData[8] <= 3) StrCat(str, " (crg)");
		SetField(FormMain, FldVoltage, str);
	}
}

MainFormHandler() is the function assigned to the main (status) form. It is designed to handle events specific to the status form. The individual form handler's mostly just handle events caused by the user, but the main form handler will also respond to nillEvent's that are generated every 50 "ticks" (a tick is a measurement of time that the Palm OS uses) by AppEventLoop().
Boolean MainFormHandler (EventPtr event) {
	Boolean handled = false;
	switch (event->eType) {
		  // Every 50 ticks we check the create's sensors (pending a serial connection)
		case nilEvent:
			DisplaySensors();
			handled = true;
			break;
		case ctlSelectEvent:
			if (event->data.ctlSelect.controlID == BtnDisconnect) {
				Disconnect();
				handled = true;
			}
			break;
	}
	return handled;
}

SelectFormHandler() handles events for the select form.
Boolean SelectFormHandler (EventPtr event) {
	Boolean handled = false;
	char str[255];
	str[0] = '\0';
	switch (event->eType) {
		case lstSelectEvent:
			prog = event->data.lstSelect.selection;
			StrCat (str, DISCRIPIONS[prog]);
			SetField (FormSelect, FldDescription, str);
			handled = true;
			break;
	}
	return handled;
}

ScriptFormHandler() handles events for the scripts form. A big portion of ScriptFormHandler() is distinguishing between the various programming buttons. Because the programming button ID's are consecutive numbers, instead of looking for specific button events we can look for button ranges.
Boolean ScriptFormHandler (EventPtr event) {
	const char MAX_SCRIPT = 8;
	static char length = 0;
	Boolean handled = false;
	UInt16 id;
	Int16 x;
	FieldPtr fld;
	FormPtr frm;
	switch (event->eType) {
		case frmOpenEvent:
			Display(length);
			handled=true;
			break;
		  // Handle the various buttons
		case ctlSelectEvent:
			id = event->data.ctlSelect.controlID;
			switch (id) {
				case BtnSend:
					SendScript(length);
					handled = true;
					break;
				case BtnClear:
					length = 0;
					Display(length);
					handled = true;
					break;
				case BtnDel:
					length--;
					if (length < 0) length = 0;
					Display(length);
					handled = true;
					break;
			}
			  // The script action buttons
			if (id >= BtnUp && id <= BtnPause) {
				if (length < MAX_SCRIPT) {
					script[length][0] = id-3100;
					script[length][1] = 1;
					length++;
				}
				Display(length);
				handled = true;
			}
			  // The script time buttons
			if (id >= BtnP1 && id <= BtnP16) {
				x = script[length-1][1];
				script[length-1][1] = (x+(id-3200) <= 25) ? x+(id-3200) : x;
				Display(length);
				handled = true;
			}
			break;
		  // The scroll bar
		case sclRepeatEvent:
			x = event->data.sclRepeat.newValue - event->data.sclRepeat.value;
			frm = FrmGetActiveForm();
			fld = (FieldPtr)FrmGetObjectPtr (frm, FrmGetObjectIndex(frm, FldScript));
			if (x >= 0) FldScrollField (fld, x, winDown);
			else FldScrollField (fld, x*-1, winUp);
			break;
	}
	return handled;
}

The Display() function translates the data in the script array into a human-friendly string and displays it in a text field that the user can see.
void Display (char length) {
	char str[1000];
	char x;
	char tmp[10];
	str[0] = '\0';
	  // Turn the script array into a string and display it
	for (x = 0; x < length; x++) {
		StrCat (str, COMMANDS[script[x][0]]);
		StrCat (str, " for \0");
		StrIToA (tmp, script[x][1]);
		StrCat (str, tmp);
		StrCat (str, " Seconds\n\0");
	}
	SetField (FormMacro, FldScript, str);
}

Finally the SendScript() function will take the script variable and translate it into commands that the iRobot Create can understand, and then send the result to your robot. It is called from the ScriptFormHandler() function.
Int16 SendScript (char length) {
	char safe[] = {128, 131};
	char data[150];
	char i;
	char x = 0;
	char y;
	char cmds = 0;
	Err err;
	data[x] = 131;
	data[++x] = 152;
	y = ++x;
	for (i = 0; i < length; i++) {
		switch (script[i][0]) {
			case 0:
				data[++x] = 137;
				data[++x] = 0;
				data[++x] = 150;
				data[++x] = 128;
				data[++x] = 0;
				data[++x] = 155;
				data[++x] = script[i][1]*10;
				data[++x] = 137;
				data[++x] = 0;
				data[++x] = 0;
				data[++x] = 128;
				data[++x] = 0;
				cmds += 3;
				break;
			case 1:
				data[++x] = 137;
				data[++x] = 255;
				data[++x] = 106;
				data[++x] = 128;
				data[++x] = 0;
				data[++x] = 155;
				data[++x] = script[i][1]*10;
				data[++x] = 137;
				data[++x] = 0;
				data[++x] = 0;
				data[++x] = 128;
				data[++x] = 0;
				cmds += 3;
				break;
			case 2:
				data[++x] = 137;
				data[++x] = 0;
				data[++x] = 150;
				data[++x] = 1;
				data[++x] = 44;
				data[++x] = 155;
				data[++x] = script[i][1]*10;
				data[++x] = 137;
				data[++x] = 0;
				data[++x] = 0;
				data[++x] = 128;
				data[++x] = 0;
				cmds += 3;
				break;
			case 3:	
			data[++x] = 137;
				data[++x] = 0;
				data[++x] = 150;
				data[++x] = 254;
				data[++x] = 212;
				data[++x] = 155;
				data[++x] = script[i][1]*10;
				data[++x] = 137;
				data[++x] = 0;
				data[++x] = 0;
				data[++x] = 128;
				data[++x] = 0;
				cmds += 3;
				break;
			case 4:	
			data[++x] = 137;
				data[++x] = 0;
				data[++x] = 125;
				data[++x] = 0;
				data[++x] = 1;
				data[++x] = 155;
				data[++x] = script[i][1]*10;
				data[++x] = 137;
				data[++x] = 0;
				data[++x] = 0;
				data[++x] = 128;
				data[++x] = 0;
				cmds += 3;
				break;
			case 5:
				data[++x] = 137;
				data[++x] = 0;
				data[++x] = 125;
				data[++x] = 255;
				data[++x] = 255;
				data[++x] = 155;
				data[++x] = script[i][1]*10;
				data[++x] = 137;
				data[++x] = 0;
				data[++x] = 0;
				data[++x] = 128;
				data[++x] = 0;
				cmds += 3;
				break;
			case 6:
				data[++x] = 155;
				data[++x] = script[i][1]*10;
				cmds += 1;
				break;
		}
	}
	data[y] = x+1;
	Connect();
	  // Send the script
	SrmSend(port, data, x+1, &err);
	SrmSendWait(port);
	SrmSendFlush(port);
	return err;
}

Step 12: The Program, Part 4

There are only 2 small files (not including images) left in the program. The first is called "string_arrays.c", and it defines some text strings that are used in the program. I have opted to put these in there own file instead of the top of robot.c because of the space that they take up.

string_arrays.c:

const char* COMMANDS[] = {"Up", "Down", "Left", "Right", "CClock", "Clock", "Stop", "Pause"};

const char* PROGRAM[] = {"Cover", "Cover and Dock", "Spot Cover", "Mouse", "Figure Eight", "Wimp", "Home", "Tag", "Pachelbel"};

const char* DISCRIPIONS[] = {
"Create attempts to cover an entire room using a combination of behaviors, such as random bounce, wall following, and spiraling.",
"Identical to the Cover demo. But if the Create sees an infrared signal from an iRobot Home Base, it uses that signal to dock with the Home Base and recharge itself.",
"Create covers an area around its starting position by spiraling outward, then inward.",
"Create drives in search of a wall. Once a wall is found, Create drives along the wall, traveling around circumference of the room.",
"Create continuously drives in a figure 8 pattern.",
"Create drives forward when pushed from behind. If Create hits an obstacle while driving, it drives away from the obstacle.",
"Create drives toward an iRobot Virtual Wall as long as the back and sides of the virtual wall receiver are blinded by black electrical tape.",
"Identical to the Home demo, except Create drives into multiple virtual walls by bumping into one, turning around, driving to the next virtual wall, etc.",
"Create plays the notes of Pachelbel's Canon in sequence when cliff sensors are activated.",
"Create plays a note of a chord for each of its four cliff sensors." };


The last file is the Makefile (It's called "Makefile"). This file tells the compiler how to make our program.

CC = m68k-palmos-gccCFLAGS = -O2all: robot.prcrobot.prc: robot bin.stamp	build-prc robot.prc "iRobot" iRob robot *.binrobot: robot.o	$(CC) $(CFLAGS) -o robot robot.orobot.o: robot.c robot.h	$(CC) $(CFLAGS) -c robot.cbin.stamp: robot.rcp robot.h robot.bmp	pilrc robot.rcpclean:	-rm -f *.[oa] robot *.bin *.stamp


There are also images, I've included them in the source archive. I've opted not to display them individually here because of there number.

To compile this program you need to navigate to the source code directory using the command line. In the directory type "make" (no quotes) and hit enter. The make program will create a bunch of extra files in the source code directory that can be safely removed by entering the command "make clean". Assuming there were no errors, you should have a new robot.prc file that you can install onto your Palm Pilot.