Modular Autonomous?

As nice as fully autonomous driving would be, realistically autonomous this year is a series of drive->kick->drive->kick instructions coded in a linear fashion. However, this game does create some depth in autonomous mode with the introduction of the three zones: you have to program the robot to successfully score whether there be one, two, or three balls near, mid-range, or far.

Therefore, this is the perfect year for a sort of modular approach to autonomous: i.e., the ability to code general functions such as “drive to initial ball” or “turn towards target” or “kick ball” and have an external source such as a text file or an array of variables determine which actions get done in what order.

As for our team, we had three autonomous modes (one for each zone) that each worked successfully. However, especially during elims, modifications needed to be made which caused praying that the code didn’t break just because we asked it to drive backwards after kicking the last ball. Therefore, during the next week, we’d love to apply the modular approach I described above. This will lead to faster changes and the ability to easily custom fit our autonomous with the teams we are with in both quals and elims.

Has any other team done this? How did you tackle the problem? It is better just to hardcode three modes?

dallen, when i first saw this thread i thought to myself wow david would love to read this because it sounds like it was written by someone who knows what they are talking about. Well, seeing as you made it, that is not necessary.

This type of modular development is the heart of object oriented programming. You should try to develop all code in this fashion.

Most of our code is modular. as far as autonomous is concerned we have a VI that I wrote which takes input (speed, distance to go to, distance the robot is at, dead-bands) and based on the distance we want to go to, we set the speed to the motor. If we just tell that VI we want to go to a distance less than where it is at, it will drive backwards. We use this VI for autonomous and for the Swerve Drive that never ended up on the final robot.

Agreed. Our entire robot is done in this fashion.

Our autonomous was developed incredibly quickly, as it was almost entirely functions that we already had developed for teleop.

Our autonomous was originally pretty complex, Andrew was programming it the hard way. Then I started moving commonly used bits to ralff.whatever (after qbranche’s original Robotics Autonomous Language For FIRST). I designed the interface that ralff takes care of just about everything. It generally works like `ralff.do(parameter, time)’. This left turning still fairly complex (our drive train is fairly complex), so I took some code directly from the teleop, and spoof joystick inputs.

switch (autoMode) {
	case 0://Testing (nothing) mode//////////////////////////////////////////////
		break;
	case 1://Zone 1/////////////////////////////////////////////////
		switch (stage) {
			case 0: if (ralff.driveForward(-1, 1   )) stage++; break;
			case 1: if (ralff.turnBack(0.5,-1, 1.65 )) stage++; break;
			case 2: if (ralff.driveForward(-1, 0.25)) stage++; break;
			case 3: break;
		}
		break;
	case 2://Zone 2/////////////////////////////////////////////////
		switch (stage) {
...

Since 2006, Team 1519 has broken our autonomous programs down into a series of more general specific operations, exactly like the ones you describe. This actions are the “instructions” that we want an autonomous program to perform. Our autonomous programs are then collections of these “instructions” in a specific sequence. Once each of the individual instructions is written and debugged, these can be quickly composed into a multitude of variations of autonomous programs.

Below are the “instructions” we have implemented this year:

  • DELAY(delay)
  • DRIVE_STRAIGHT(distance, power)
  • DRIVE_STRAIGHT_UNTIL_TILT(power, tilt)
  • DRIVE_STRAIGHT_FOR_TIME(power, time)
  • DRIVE_STRAIGHT_UNTIL_BALL(power, time)
  • KICK_STRENGTH(strength)
  • KICK()
  • KICK_AND_WAIT()
  • KICK_WITH_AIM()
  • KICK_WITH_AIM_AND_WAIT()
  • SPIN_TO(heading, power)
  • SPIN_BY(heading, power)
  • STOP()

We are coding in C++. Each of the above “instructions” is interpreted by a “virtual machine” that we have that runs during autonomous, and executes a series of the above instructions in sequence.

As of right now, we have 15 different autonomous programs which are built up of lesser “instructions” such as the ones you describe. I think we have only actually used about 6 of these in real matches, but we have tested the others on the practice field so that they are ready to go when needed. The entire set of autonomous programs is always programmed into the robot – the drive team selects the autonomous program to be run immediately before each match.

Using the above instructions, our 5-ball autonomous routine is as follows:

// Kick 3 from the far zone, cross the bump and kick 2 from the center zone
AutonomousInstruction *code] = {
		KICK_STRENGTH(KICK_STRENGTH_FAR1),
		DELAY(1.0),
		DRIVE_STRAIGHT(18, DEF_SPEED),
		KICK_AND_WAIT(),
		DELAY(1.0),
		KICK_STRENGTH(KICK_STRENGTH_FAR2),
		DRIVE_STRAIGHT(36, DEF_SPEED),
		KICK_AND_WAIT(),
		DELAY(1.0),
		KICK_STRENGTH(KICK_STRENGTH_FAR3),
		DRIVE_STRAIGHT(36, DEF_SPEED),
		KICK_AND_WAIT(),
		KICK_STRENGTH(KICK_STRENGTH_MID1),
		DRIVE_STRAIGHT(155, 0.55),
		KICK_AND_WAIT(),
		DELAY(1.0),
		KICK_STRENGTH(KICK_STRENGTH_MID2),
		DRIVE_STRAIGHT(36, DEF_SPEED),
		KICK_AND_WAIT(),
		STOP()
};

With the above instruction set, we can quickly build up a new autonomous program at the tournament depending up on alliance needs and have moderate confidence that it could work even the first time we try it. However, rather than wait until at the tournament to implement a new autonomous program, we’ve tried to anticipate what programs could be needed and test them in advance. We haven’t had to do it yet this year, but have added new programs in past years in order to implement a specific alliance strategy.

To get started with this, I would first suggest coding up a few of the general functions you listed, such as “drive to initial ball” or “turn towards target” or “kick ball”. (Actually, all three of those are instructions in our instruction set, albeit with slightly different names.) Once you have the individual instructions implemented, you could hard-code the sequence of general functions as an “autonomous program.” Then, after you have that working, you would add a “virtual machine” to step through your instructions to create the larger “autonomous program.”

We do something very similar to the above (but in Java).

We defined a custom “scripting format” that lets you describe high level robot behaviors in a plain text format. The “autonomous mode text file” can be created on a laptop (either with Notepad or, hopefully next year, in a full-featured GUI) and FTP’d down to the cRIO. Upon autonomous mode starting, our system searches the file system for the autonomous.txt file and executes the commands sequentially.

The actual bit of code that executes each command (or Action, as we call them) is highly object oriented. An “AutonomousThread” instance receives an array of Action objects. Actions each have “execute()” and “cancel()” methods that are implemented to provide the desired robot behavior. Because of the beauty of polymorphism, you just need to loop through each Action and call “execute()” for each step of the recipe.

If you have the time (and interest), a system like this is fun to create and can lead to more maintainable code. Of course, it takes a lot of boilerplate to get it up and running, and you need to be absolutely sure it is bug free or you will be doing yourself no favors.

This.

First of all, I agree on object orientation. Do you realize that Fully autonomous is achieved by lots of events put together? Just putting your “Modular Autonomous” in a bigger package: PlayGame().

also what jared said: like a config file

I agree with the posters above: modular code is what makes object-oriented code as powerful and elegant as it is. My philosophy is that if you’re copy and pasting code everywhere, you haven’t created enough methods/function/subroutines/whatever-they’re-called. Our autonomous code looks like:

DriveSequence();
drive(-0.25,0);
Timer.delay(0.2);
drive(0,0);
turn(a,b);
fire();

If I had more time, I would’ve put this in a method of its own:

drive(-0.25,0);
Timer.delay(0.2);
drive(0,0);

Boom! You are thinking like a professional programmer now.

Nobody wants to do more work than they have to. Copy-and-paste sucks and is generally considered bad practice. Also, code like this:

sc1.set(joy.getAxis(4));

is useless to anyone but the original programmer, and even in the case that you are the only code monkey for your team, it puts a lot of pressure on you to remember all of the fine details (which axis is axis 4, which degree of freedom sc1 is, and so on). That’s not something you want to trip you up at competition.

leftDrive.set(joy1.getAxis(JOY_LEFT_Y));

is significantly more readable, and when you need to change which axis is being used, you change the value of the constant, not in 20 places. Trust me, this saves you so much time and sanity.

This absolutely helps for autonomous, too. Breaking bigger functions down into smaller, commonly-used components makes your code more robust and readable. Any place where you are doing something repeatedly, there is an opportunity to clean up your code.

As far as making your code have several modes, you might look into a sequential thumb switch (something similar to this, except that one seems a bit pricey). You wire it up to a few digital inputs, and read the inputs from least significant to most significant and convert that number to decimal…then use a case structure to select which mode you’d like. I’ve gone this route before and it is so nice. If you’d like more info on this, shoot me a PM and I’ll help you out.

tl;dr version:

  • Pull out anything that is in common–anything you’d be tempted to copy and paste. If you’ve copied and pasted something, you are doing it wrong.
  • Name your variables logically. You shouldn’t need to scroll around to figure out which variable you need.
  • Create smartly named constants to replace literal numbers. It is easier to read for everyone. Convention suggests you use a prefix for a group of constants, e.g. if they are all joystick axes, make them JOY_Y, JOY_X, JOY_SHOULDER, etc.
  • Using a switch on the robot, you can switch programs without needing to re-deploy code every match. It is very nice.

That was a bit more long-winded than I meant it to be…I guess I soapbox too easily :o

My 2 cents…
Jacob

Very interesting and useful information, especially from Ken Streeter and 1519 (I was extremely jealous of your autonomous at North Carolina). I’ve sketched out a more streamlined and OO version of our autonomous.

Our individual components already included their own functions, but there was plenty of overlap, especially controlling our relatively complicated winch/ratchet kicker.

We are trying a similar approach. Do you mind elaborating on the syntax of this file and how you parse it within the code?

We split our Autonomous mode control into 2 discrete sections.

There is an ‘AutonomousModeController’, which is the thing that executes the selected autonomous mode. The controller itself is pretty dumb. It has a pretty simple interface



void addCmd(autoCmd_t newCmd); 
void reset();
void handle();

You push in commands, which are housed in a struct (this holds a command ID, and a couple arguments… this could be an object if you want) and they get queued up and held onto. (You can use a linked list or a deque or whatever… any kind of data structure that can act as a FIFO)

When you call handle, it pops the the newest command off the top and calls a specified function over and over again until that function returns a true, indicating that it is done. When the individual command is done, it pops the next one off and does that. Similar to 1519, we have functions for driving distances, driving to ball acquired, spinning, delaying, etc. The functions handle the logic of when they are complete. These can usually be reused from year to year. (These live in our AutoModeController, but they could really live anywhere, and they should probably be in a layer between the robot and the automodecontroller)

We also have an AutoModeSelector which keeps track of the Auto Mode ‘scripts’, and pushes them into the AutoModeController when asked to. The idea here is that you can use any kind of mechanism to create the auto modes (selection switches, script file, driver station input), and the thing that actually executes the auto mode should remain unchanged. We were a bit lazy and just hard coded the instructions into the selector and kept track of which one we wanted to use with an incremented variable, but there’s no reason you couldn’t do something fancy here.

More importantly than this though, we constructed our robot code in a manner such that everything mechanical could be controlled through a high level interface. We have functions like Robot::driveSpeedTurn(float speed, float turn) and Kicker::kick() that do exactly what you think they would do. This way, sharing robot functions between auto and teleop is super simple.

Our autonomous consists of 8 “primitives” (basic driving maneuvers) that can be strung together in any order. The primitives are scripted by creating .csv files in Excel. The .csv files are stored on the cRIO’s file system and the drivers select the desired autonomous script by pressing joystick buttons at the driver station.

We were going to do this, but never got around to it. I’ve done systems like this (but not for FRC). I think that the biggest reason we didn’t push at all to get this done, is that it does add another point of failure. Our current method just requires surrounding the commands with this:

case *n*: if (ralff.*COMMAND()*) stage++; break;

Which is near as easy to edit, but won’t be dead if there’s a syntax error (would be caught before deploy), and is easier to edit the system. I should note that the ralff system is designed so that all the commands interact in a certain way that makes it extra lovely.

This position bothers me. Everything you said is true, modularity is one of OO’s strengths. If you’re copy/pasting code everywhere, you are doing something wrong.

But, code doesn’t need to be object-oriented to be modular, powerful, elegant, and avoid copy/pasting. Yes, modularity is one of OO’s strengths. I feel that they are teaching the benefits of OO, playing it as a superior paradigm. Yes, it has strengths, but so do others. And I’ve written more C code that is more modular than the better part of the Object-Oriented C++ and Java code I have seen.

I agree with this. I just find it’s easier to write modular code with object-oriented programming, but that’s just a matter of preference.

I made my code too modular, i think i broke 30 classes of java code

I made a rather simple autonomous that works for all 3 zones, using one of the old banner sensors as a ball detector.

Autonomous is a simple loop of :

  • Drive Forward
  • Check if the banner sensor has tripped
  • Check if the kicker has reloaded
  • Fire the ball if it is, and reload if it’s not
  • Rinse, repeat

However, it times out after about 2 seconds to prevent hitting the bump or the wall in case there’s no more balls in the zone.

Another benefit we (team 1519) get from our autonomous mode virtual machine is that adding a new autonomous mode or changing an existing one does not really involve changing code, so we can do it with much less risk of breaking something important. This made it very easy to take our existing 3 ball autonomous and turn it into a 5 ball autonomous program at GSR, our local week 1 regional.

I’d be interested to hear how other teams select which autonomous mode to use. We select the program number using joystick buttons on the OI, displaying the current program number using the DriverStationLCD. We have +/- 1 and +/- 10 buttons. Also, we allow the operator to select a delay before the program starts running. This has been useful when we’ve been playing with an alliance partner who would otherwise block our shots at the start of autonomous.

Noel

That doesn’t necessarily sound “too modular” to me. Team 1519’s C++ code this year has 27 classes which between them cover autonomous, teleop and self test.

Noel