User:Astronouth7303/Autonomous scripting whitepaper

From Wikipedia, the free encyclopedia

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 the printf() from the MCC18 libraries (declared in <stdio.h>), while older versions used the printf() 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.

  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, CMD_FOO_BAR and cmd_foo_bar).
  5. Change the number to the next one (or higher). 15 will work.
  6. Document it! Don't leave the next guy wondering what it does.
  7. 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.

  1. Open robot.c and scroll down to robot_command() (Line 55).
  2. Scroll down to the ende of the switch statement, just before the default:.
  3. Duplicate the case 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[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() and robot_command() so that it accepts one argument of the type struct 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