User:Astronouth7303/Autonomous scripting whitepaper
The new scripting setup makes things a lot easier. But using it can be hard. Even if you can find the code to download and the file to look at, it's not what you expected when Dave Lavery said "Plain English" at kick-off.
This white paper shall tell all. How the code works; how to read it; how to make it more readable; how to write more commands; and a few pointers on how to add multiple scripts without recompiling.
While reading this, please follow along in the Navigation code (From Kevin Watson: http://kevin.org/frc/). I assume you are familiar with C.
The Script
[edit]The script, by default, is defined in commands.h. The script looks like this:
struct commands command_list[] = { /* Command parm 1 parm 2 parm 3 */ {CMD_GYRO_BIAS, 0, 0, 0}, {CMD_WAIT_FOR_BUMP, 100, 0, 0}, {CMD_WAIT, 1000, 0, 0}, {CMD_DRIVE, 1500, 0, 0}, {CMD_WAIT, 4000, 0, 0}, {CMD_TURN, (-1500), 50, 0}, {CMD_WAIT, 3000, 0, 0}, {CMD_DRIVE, 2400, 0, 0}, {CMD_WAIT, 4000, 0, 0}, {CMD_TURN, (PI_MRAD / 2), 50, 0}, {CMD_WAIT, 4000, 0, 0}, {CMD_DRIVE, 2400, 0, 0}, {CMD_WAIT, 4000, 0, 0}, {CMD_TURN, (-1500), 50, 0}, {CMD_WAIT, 1000, 0, 0}, {CMD_DRIVE, 0, 0, 0}, {CMD_KEEP_HEADING, 240000, 100, 0}, {CMD_JUMP, 1, 0, 0}, {NULL, 0, 0, 0} };
How it Works
[edit]The 'script' is actually an array of structures, each one containing the command and three arguments. (None of the default commands use all three, hence the zeros.) A NULL
command must be the last command. This array is read by robot_command()
in robot.c. It consists mostly of a big switch
statement, which hands it off to the appropriate function. The prototypes and definations of these commands and their functions are in robot.h.
These functions gather all the arguments and process data. They will return one of two values: DONE
or WORKING
(defined in robot.h).
- Note: Like everything else in the code,
robot_command()
is called frequently during autonomous.
What it Means
[edit]The script, as it stands, requires training to read. Here's the crash course.
Units
[edit]All commands use the same units.
- Length - millimeters
- Angle - milliradians (a thousandth of a radian)
- Time - milliseconds
- Velocity - millimeters per second
The Default Commands
[edit]Here is a list of the default commands, with the arguments they use. Whenever an angle is taken as an argument, there is also a tolerance, which is also in milliradians.
Command | Argument 1 | Argument 2 | Argument 3 | Description |
---|---|---|---|---|
NULL | None | None | None | Ends the script. |
CMD_SHOW_STATE | None | None | None | Prints debug information. |
CMD_VELOCITY | Velocity | None | None | Sets the velocity of the left and right sides. This is kept until another command changes them or a CMD_STOP. |
CMD_WAIT | Time | None | None | Waits for the given time, then continues. Motors still move during this time. |
CMD_WAIT_UNTIL | Time | None | None | Waits until the given time comes, relative to the begining of autonomous. |
CMD_DRIVE | Distance | None | None | Drives the given distance. |
CMD_TURN | Angle | Tolerance | None | Turns the robot the given angle, using the given tolerance. |
CMD_GOTO_WAYPOINT | Unknown | Unknown | Unknown | Not yet implemented (maybe when we get trig functions ;-) |
CMD_SET_POS | Unknown | Unknown | Unknown | Not yet implemented (maybe when we get trig functions ;-) |
CMD_SET_HEADING | Unknown | Unknown | Unknown | Not yet implemented (maybe when we get trig functions ;-) |
CMD_WAIT_FOR_BUMP | None | None | None | Waits until the bumper is pressed, then continues. Motors continue to move. |
CMD_STOP | None | None | None | Stops all moving. |
CMD_JUMP | Position | None | None | Jumps to the given index in the command array. The first line is 0. Don't go beyond the end of the list; lord help you if you do. |
CMD_GYRO_BIAS | None | None | None | Caligrates the gyro. Must be run when stopped and before you use the gyro. |
CMD_KEEP_HEADING | Time out | Tolerance | None | Will keep the current heading for the given time, using the given tolerance. |
Reading It
[edit]I am not going to insult or intelligence and walk you through reading it. I'll just give you a template that matches the script above. Each of the [fields] match the columns of the table above.
{[Command], [Argument 1], [Argument 2], [Argument 3]},
Expanding
[edit]So the 'script' isn't as daunting as it looks. But it still isn't English! Nor does it have camera support. And what about that arm you have?
A readable version
[edit]Making a readable version doesn't require massive changes to the code, just a little bit of preprocessor magic. I have already have posted this code at frCoder (http://frcoder.sourceforge.net/res/index.php/Scripting).
There are two macros you'll need: one to begin a script and one to end it. Those are very simple:
#define BEGIN_SCRIPT(name) struct commands name[] = { #define END_SCRIPT {NULL, 0, 0, 0} };
If you use these exactly, just be warned that they must be used this way:
BEGIN_SCRIPT(command_list) // Your script here END_SCRIPT
From there, you can just have a macro for each command. Here are some samples:
// This takes no arguments #define FOO_NO_ARGS {CMD_FOO_NO_ARGS, 0, 0, 0}, // This takes one argument #define FOO_ONE_ARG(arg1) {CMD_FOO_ONE_ARG, arg1, 0, 0}, // This takes two arguments #define FOO_TWO_ARGS(arg1,arg2) {CMD_FOO_TWO_ARGS, arg1, arg2, 0}, // This takes three arguments #define FOO_THREE_ARGS(arg1,arg2,arg3) {CMD_FOO_THREE_ARGS, arg1, arg2, arg3},
So if you use my header I linked to earlier, the default script can be rewritten as:
BEGIN_SCRIPT(command_list) GYRO_BIAS WAIT_FOR_BUMP WAIT(1000) DRIVE(1500) WAIT(4000) TURN(-1500, 50) WAIT(3000) DRIVE(2400) WAIT(4000) TURN(PI_MRAD/2, 50) WAIT(4000) DRIVE(2400) WAIT(4000) TURN(-1500, 50) WAIT(1000) DRIVE(0) KEEP_HEADING(240000, 100) JUMP(1) END_SCRIPT
New commands
[edit]There are several places that you have to add stuff to implement a new command:
- The declarations list in robot.h
- The big switch in
robot_command()
in robot.c - Your function, which can be where ever you want. (This is the hardest part.)
- Where you wrote out the macros for above.
This tutorial will take you through making CMD_FOO_BAR, a scripting command that takes three parameters and prints them.
Writing the function
[edit]The function that is called when your command is reached is where all the stuff happens. Here is a template layout for such a function:
int cmd_foo_bar(void) { static int state = START; static long int param1; static int param2, param3; int rc = WORKING; switch (state) { case START: { param1 = command_list[current_command].parm_1; param2 = command_list[current_command].parm_2; param3 = command_list[current_command].parm_3; state = IN_PROGRESS; rc = WORKING; break; } case IN_PROGRESS: case COMPLETE: case TURNING: case DRIVING: case STOPPED: { // Do your stuff here printf("CMD_FOO_BAR: param1=%ld, param2=%d, param3=%d\r", param1, param2, param3); //This is won't work if you're using older code, see below // Set this to something else if you aren't done yet state = COMPLETE; rc = WORKING; break; } case COMPLETE: { printf("Done CMD_FOO_BAR\r"); state = START; //Re-initialize this unless you only want to be called once rc = DONE; break; } } return rc; }
This is how most commands are laid out.
param1
, param2
, and param3
store the values of the parameters; feel free to rename them to something more meaningful or delete them altogether.
rc
is the value that is returned to robot_command()
. This should only be set to DONE
in the final case
of the switch
statement (case COMPLETE:
); instead set state
to COMPLETE
.
- Note: The
printf()
calls are based the default code from January 12, 2005 for version 2.4 of the MCC18 compiler. That version of the code used theprintf()
from the MCC18 libraries (declared in <stdio.h>), while older versions used theprintf()
from printf_lib.c (declared in "printf_lib.h").
If a command does not take an amount of time (eg, it doesn't go for X milliseconds or wait for something), the switch
statement can be removed, and the code laid out more like this:
int cmd_foo_bar(void) { long int param1; int param2, param3; param1 = command_list[current_command].parm_1; param2 = command_list[current_command].parm_2; param3 = command_list[current_command].parm_3; // Do your stuff here printf("CMD_FOO_BAR: param1=%ld, param2=%d, param3=%d\r", param1, param2, param3); return DONE; }
Adding the Prototypes
[edit]This is easy.
- Open robot.h
- Scroll down to the end of the commands (about line 169).
- Duplicate the command above it.
- Change the names (for our example,
CMD_FOO_BAR
andcmd_foo_bar
). - Change the number to the next one (or higher).
15
will work. - Document it! Don't leave the next guy wondering what it does.
- If you did #A readable version above, add the English version to that list.
Here's the example:
- robot.h:
//... // Line 168: int cmd_keep_heading(void); /* CMD_FOO_BAR takes three parameters and prints them to the debug stream. */ #define CMD_FOO_BAR 15 int cmd_foo_bar(void); /* Command States */ //...
- The plain-English command list:
//... #define FOO_BAR(param1,param2,param3) {CMD_FOO_BAR, (param1), (param2), (param3)}, //...
Getting it in the loop
[edit]This is also mostly copy-'n'-paste.
- Open robot.c and scroll down to
robot_command()
(Line 55). - Scroll down to the ende of the
switch
statement, just before thedefault:
. - Duplicate the
case
statement right above that. - Rename the appropriate functions and constants.
This is what mine looks like:
//... // Line 155: } case CMD_FOO_BAR: { rc = cmd_foo_bar(); break; } default: //...
And that's it! You've just made yourself and brand-new scripting command.
Multiple scripts
[edit]Now, if your team is like mine, you will probably have a dozen different strategies for autonomous, each one with it's own script. But the default setup makes you recompile the code whenever you switch scripts!
Here's a few general pointers as to how to change it so that you can change it on-the-fly:
- Make the parameter and command access macros, ie:
#define GET_COMMAND(index) command_list[(index)] #define CUR_COMMAND GET_COMMAND(current_command) #define COMMAND CUR_COMMAND.command #define PARAM1 CUR_COMMAND.parm_1 #define PARAM2 CUR_COMMAND.parm_2 #define PARAM3 CUR_COMMAND.parm_3
- Change the argument list of
robot_control()
androbot_command()
so that it accepts one argument of the typestruct commands *
, which will point to the first item in the script (ie,robot_control(default_script)
) - Add a global variable to robot.c:
struct commands *command_list