Command Composition
You have written commands that do one thing while a button is held. An autonomous routine is a list of those commands, run in order. This lesson builds the list.
- Arm and flywheel commands from Writing Commands:
runSlow(),runFast(),stop(). - A
TeleopOpModewith working button bindings, from OpModes. - The simulator running, from Hardware Simulation.
Every mechanism command you have written so far is a hold. runRepeatedly(...) re-sends the same request every loop and never ends.
So the work comes in two parts: give a command an ending, then glue the endings together in order.
Holds and steps
A hold never reports that it is finished, so a list containing one stops there for good. Step two never runs. Nothing errors and nothing logs, so the routine looks frozen.
.withTimeout(...) wraps a command and ends it after a fixed time, finished or not. That turns a hold into a step, something with a beginning and an end. Steps are what you can put in a list.
import static org.wpilib.units.Units.Seconds; // A hold. Pushes forever, never finishes.arm.runFast() // A step. Pushes for one second, then ends.arm.runFast().withTimeout(Seconds.of(1.0))The import static line is what lets you write Seconds rather than Units.Seconds every time. .withTimeout(...) refuses a bare 1.0. It takes a Time, so nobody can pass milliseconds where seconds were meant.
Steps in order
Command.sequence(a, b, c) runs a until it finishes, then b, then c. You write autonomous routines in exactly this shape.
Command liftThenSpin = Command.sequence( // A step: it ends, so the sequence moves on. arm.runFast().withTimeout(Seconds.of(1.0)), // A hold: the last member, so the group is a hold too. flywheel.runFast()) .named("Lift Then Spin (hold)");Command.sequence(...) hands back a builder rather than a Command. .named("...") is what finishes it, and leaving it off will not compile. Name the group after what it does. If the group is a hold, end the name with (hold), the way the mechanism commands do.
Do not re-name a command that already has one. arm.runFast() arrives finished, so .named(...) on it is a compile error.
A bare hold in the middle
Swap the first member for a plain arm.runFast() and the sequence sticks there for the rest of the match. When a routine looks frozen, a member with no ending is the first thing to check.
Two things at once
A sequence is this, then that. A race is this while that. Command.race(...) starts every member at the same time and cancels the rest as soon as one finishes.
Command spinWhileHolding = Command.race( flywheel.runFast().withTimeout(Seconds.of(2.0)), arm.runSlow()) .named("Spin While Holding Arm");The flywheel member has an ending and the arm hold does not, so the flywheel decides when the group ends. A hold can never win a race. Other teams call this pattern a deadline. Commands v3 spells it Command.race(...), and there is no Command.deadline(...).
Bind the group
A group is a command, so it binds like one. On Writing Commands you started a hold with onTrue and undid it with onFalse, because neither verb cancels anything.
whileTrue does cancel. It runs the group while the button is held and drops it on release. From here on it is how this team binds a hold.
driver.y().whileTrue(liftThenSpin).whileFalse(flywheel.stop());Canceling never stops the motor
A canceled command hands the mechanism back to idle(), and idle() sends nothing at all. It does not zero the last request, so Phoenix keeps applying the last voltage it was given. The flywheel keeps spinning. Every group needs a stop somewhere: a whileFalse binding, or a stop step of its own.
Whether a group ends at all comes down to its last member. End on a hold and the group is a hold. End on a step and the group finishes by itself, with nothing left commanding the mechanism. Then the stop belongs inside the group.
Check your work
- Add the
Secondsimport and theLift Then Spin (hold)binding to yourTeleopOpModeconstructor. - Start the simulator and click Enable.
- Hold Y for three seconds, then release.
- Now break it on purpose. Drop
.withTimeout(...)off the arm member, hold Y again, then put the timeout back. - Bind Y to
arm.runFast().withTimeout(Seconds.of(1.0))on its own instead and hold it for two seconds. The step ends after one second and the arm keeps pushing 6 V, because nothing claimed it afterwards. A closingarm.stop().withTimeout(Seconds.of(0.5))step is the fix.
You should see
- The arm runs for one second and stops, then the flywheel starts.
- The flywheel holds while Y is down and stops when you release it.
- With the timeout gone, the arm pushes and the flywheel never starts.
These three failures look nothing alike.
| What you see | Cause |
|---|---|
| Y does nothing | A member with no ending. |
| The group will not compile | No .named("..."), or .named(...) on a command that already had one. |
| Both mechanisms move together | A Command.race where you meant Command.sequence. |
Get this binding working in the simulator. Autonomous is the same move against a drivetrain, with a stop on the end.
The template's chained OpModeCheck yourself
You put arm.runFast(), a hold with no timeout, as the first member of Command.sequence(...). What happens?
Why does .withTimeout(Seconds.of(2.0)) refuse a plain 2.0?
In Command.race(flywheel.runFast().withTimeout(Seconds.of(2.0)), arm.runSlow()), what ends the group?
Your group's last member is a hold. You bind it with whileTrue and nothing else, then release the button. What does the flywheel do?