-
Notifications
You must be signed in to change notification settings - Fork 0
Using CommandGroups
A CommandGroup is a type of Command designed to encapsulate a number of other Commands. CommandGroups are the main way within VexOS to implement the sequence of Commands that are executed during the Autonomous period or the programming skills challenge. There are other uses for CommandGroups as well, such as sequencing complex operations involving sensors (such as ejecting a single ball from a hopper or reacting to use button pushes). CommandGroups offer a lot of flexibility and can often be an efficient design solution to avoid having to manually write finite state machine code.
There are two methods in VexOS for defining CommandGroups: either a generic CommandGroup used as a Collection or a CommandGroup subclass. In either case, the same two public methods are used to add elements to a CommandGroup.
The built-in CommandGroup Command class can be used directly as a simple collection. To do this you create a named CommandGroup instance and add commands to it. This is usually done by the robot initializer or by a method called from the robot initializer. This is a good approach for autonomous programs where there is little hope of re-use in other robots.
An example of defining an autonomous program is:
// method to construct the program //
static Command* getAutoProgram1() {
Command* group = Command_new(&CommandGroup, "Auto Program 1");
CommandGroup_addSequential(group, Command_new(&AutoDriveInches, 20.0));
CommandGroup_addSequential(group, Command_new(&AutoTurnDegrees, -90.0));
CommandGroup_addParallel(group, Command_new(&AutoDriveInches, 12.0));
CommandGroup_addParallel(group, Command_new(&SetLift, LiftPosition_Score));
CommandGroup_addSequential(group, Command_new(&WaitForChildren));
CommandGroup_addSequential(group, Command_new(&SetIntake, IntakeDirection_Blow));
CommandGroup_addSequential(group, Command_new(&WaitCommand, 10));
CommandGroup_addSequential(group, Command_new(&SetIntake, IntakeDirection_Stop));
CommandGroup_addParallel(group, Command_new(&AutoDriveInches, 12.0));
return group;
}
// robot initializer, called by VexOS during start-up //
static void initialize() {
...
Autonomous_addProgram(getAutoProgram1());
}This is an example of a program that drives forward, turns, drives forward and raises the lift simultaneously, waits for the move and lift to complete, outputs game pieces, waits for them to exit, then stops the intake and backs up. It is based on some Built-in Commands and some hypothetical Commands that the user would write. This example indicates the great flexibility inherent to the command-based architecture. For more information on adding to CommandGroups, see the Adding Commands to CommandGroups section below.
The other way to create CommandGroups are as a distinct CommandClass that is a sub-class of CommandGroup. The primary use of this approach is to provide more generic or re-usable CommandGroups. New Subclasses must be declared and defined, just like a normal custom Command class.
As in the Defining Commands tutorial, the first step is to declare the new Command class in Robot.h with a CommandClass macro:
DeclareCommandClass(SuperAutonomous);Then, you must make a new file to contain the Command class definition. This file should follow the same naming conventions used for Commands (cmd__NAME_.c in easyC and in a commands/ sub-directory with no prefix outside of easyC).
Sample codes is:
//
// SuperAutonomous.c
//
#include "CommandClass.h"
#include "Robot.h"
/********************************************************************
* Class Definition *
********************************************************************/
DefineCommandGroup(SuperAutonomous);
static void constructor(va_list argp) {
CommandGroup_addSequential(self, Command_new(&AutoDriveInches, 20.0));
CommandGroup_addSequential(self, Command_new(&AutoTurnDegrees, -90.0));
CommandGroup_addParallel(self, Command_new(&AutoDriveInches, 12.0));
CommandGroup_addParallel(self, Command_new(&SetLift, LiftPosition_Score));
CommandGroup_addSequential(self, Command_new(&WaitForChildren));
CommandGroup_addSequential(self, Command_new(&SetIntake, IntakeDirection_Blow));
CommandGroup_addSequential(self, Command_new(&WaitCommand, 10));
CommandGroup_addSequential(self, Command_new(&SetIntake, IntakeDirection_Stop));
CommandGroup_addParallel(self, Command_new(&AutoDriveInches, 12.0));
}The sequence of Commands that are added is the same, but this time everything is added in the CommandGroup constructor. Instead of adding to a separate pointer named group, as in the collection example, the Commands are added to the current self pointer.
To create this new CommandGroup in your code, you would write:
Command* prog = Command_new(&SuperAutonomous);The Command class SuperAutonomous can now be used like any of the built-in Commands or your custom Commands.
Because CommandGroups can take arguments, this is a convenient way to re-use code for different field start positions. For example, if the robot can be at either position 1 or 2 on the field, you could rework the constructor above as:
static void constructor(va_list argp) {
int position = va_arg(argp, int);
CommandGroup_addSequential(self, Command_new(&AutoDriveInches, 20.0));
if(position == 1) {
CommandGroup_addSequential(self, Command_new(&AutoTurnDegrees, -90.0));
CommandGroup_addParallel(self, Command_new(&AutoDriveInches, 12.0));
} else {
CommandGroup_addSequential(self, Command_new(&AutoTurnDegrees, 90.0));
CommandGroup_addParallel(self, Command_new(&AutoDriveInches, 24.0));
}
CommandGroup_addParallel(self, Command_new(&SetLift, LiftPosition_Score));
CommandGroup_addSequential(self, Command_new(&WaitForChildren));
CommandGroup_addSequential(self, Command_new(&SetIntake, IntakeDirection_Blow));
CommandGroup_addSequential(self, Command_new(&WaitCommand, 10));
CommandGroup_addSequential(self, Command_new(&SetIntake, IntakeDirection_Stop));
CommandGroup_addParallel(self, Command_new(&AutoDriveInches, 12.0));
}Then you could make two instances of SuperAutonomous that are configured in different ways:
Command* autoPos1 = Command_new(&SuperAutonomous, 1);
Command* autoPos2 = Command_new(&SuperAutonomous, 2);Of course, one could do the same thing with arguments passed to the getAutoProgram1() method in the collection example, so this type of genericness is not limited to just the subclass method of creating CommandGroups.
There are two ways to add Commands to a CommandGroup: sequential or parallel. These are implemented as public methods on the CommandGroup base class. The method signatures are:
void CommandGroup_addSequential(Command* group, Command* cmd);
void CommandGroup_addParallel(Command* group, Command* cmd);The addSequential method indicates the next command should not start until the added command is finished. The addParellel method indicates that the next command should start immediately, regardless of if the added command is finished. In both cases, the added command will itself start when the previous sequential command has finished.
A consequence of this logic is that if the last added Command was a parallel Command, the sequential command will started in parallel too! For example:
CommandGroup_addParallel(group, Command_new(&GenericCommand, 1));
CommandGroup_addParallel(group, Command_new(&GenericCommand, 2));
CommandGroup_addSequential(group, Command_new(&GenericCommand, 3));
CommandGroup_addSequential(group, Command_new(&GenericCommand, 4));This will actually start 1, 2 and 3, but 4 will not start until 3 finishes. It is possible that both 1 and 2 will still be running at that time. To prevent this behavior and wait for a group of parallel commands to finish before continuing, you use the WaitForChildren built-in Command.
CommandGroup_addParallel(group, Command_new(&GenericCommand, 1));
CommandGroup_addParallel(group, Command_new(&GenericCommand, 2));
CommandGroup_addSequential(group, Command_new(&WaitForChildren));
CommandGroup_addSequential(group, Command_new(&GenericCommand, 3));
CommandGroup_addSequential(group, Command_new(&GenericCommand, 4));This sequence will wait for 1 and 2 to finish before starting 3. 4 will not start until 3 is finished.