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= 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
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  command must be the last command. This array is read by  in robot.c. It consists mostly of a big  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:  or   (defined in robot.h).


 * Note: Like everything else in the code,  is called frequently during autonomous.

What it Means
The script, as it stands, requires training to read. Here's the crash course.

Units
All commands use the same units.
 * Length - millimeters
 * Angle - milliradians (a thousandth of a radian)
 * Time - milliseconds
 * Velocity - millimeters per second

The Default Commands
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.

Reading It
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= 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
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: If you use these exactly, just be warned that they must be used this way: BEGIN_SCRIPT(command_list) // Your script here END_SCRIPT
 * 1) define BEGIN_SCRIPT(name) struct commands name[] = {
 * 2) define END_SCRIPT                  {NULL,                    0,      0,     0} };

From there, you can just have a macro for each command. Here are some samples: // This takes no arguments // This takes one argument // This takes two arguments // This takes three arguments
 * 1) define FOO_NO_ARGS {CMD_FOO_NO_ARGS, 0, 0, 0},
 * 1) define FOO_ONE_ARG(arg1) {CMD_FOO_ONE_ARG, arg1, 0, 0},
 * 1) define FOO_TWO_ARGS(arg1,arg2) {CMD_FOO_TWO_ARGS, arg1, arg2, 0},
 * 1) 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
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  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
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.

,, and   store the values of the parameters; feel free to rename them to something more meaningful or delete them altogether.

is the value that is returned to. This should only be set to  in the final   of the   statement ; instead set   to.


 * Note: The  calls are based the default code from January 12, 2005 for version 2.4 of the MCC18 compiler. That version of the code used the   from the MCC18 libraries (declared in  ), while older versions used the   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  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
This is easy. Here's the example:
 * 1) Open robot.h
 * 2) Scroll down to the end of the commands (about line 169).
 * 3) Duplicate the command above it.
 * 4) Change the names (for our example,  and  ).
 * 5) Change the number to the next one (or higher).  will work.
 * 6) Document it! Don't leave the next guy wondering what it does.
 * 7) If you did above, add the English version to that list.
 * robot.h:

//... // Line 168: int cmd_keep_heading(void); /* CMD_FOO_BAR takes three parameters and prints them to the debug stream. */ int cmd_foo_bar(void); /* Command States */ //...
 * 1) define CMD_FOO_BAR          15
 * The plain-English command list:

//... //...
 * 1) define FOO_BAR(param1,param2,param3) {CMD_FOO_BAR, (param1), (param2), (param3)},

Getting it in the loop
This is also mostly copy-'n'-paste.
 * 1) Open robot.c and scroll down to  (Line 55).
 * 2) Scroll down to the ende of the  statement, just before the.
 * 3) Duplicate the  statement right above that.
 * 4) 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
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:
 * 1) define GET_COMMAND(index) command_list[(index)]
 * 2) define CUR_COMMAND GET_COMMAND(current_command)
 * 3) define COMMAND CUR_COMMAND.command
 * 4) define PARAM1 CUR_COMMAND.parm_1
 * 5) define PARAM2 CUR_COMMAND.parm_2
 * 6) define PARAM3 CUR_COMMAND.parm_3
 * Change the argument list of  and   so that it accepts one argument of the type , which will point to the first item in the script (ie,  )
 * Add a global variable to robot.c: