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.
- An
Armwithvertical()andhorizontal(), and aFlywheelwithrunSlow(),runFast()andstop(), from Motion Magic in Code. - A
MyTeleopwith 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. Drives to vertical and keeps holding it, never finishes.robot.arm.vertical() // A step. Holds vertical for one second, then ends.robot.arm.vertical().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 spinUpWhenReady = Command.sequence( // A step: it ends, so the sequence moves on. robot.arm.vertical().withTimeout(Seconds.of(1.0)), // A hold: the last member, so the group is a hold too. robot.flywheel.runFast()) .named("Spin Up When Ready (hold)");A sequence is itself a small coroutine. Its body is one loop that calls coroutine.await(member) on each member in turn, and Coroutines writes that call by hand.
Command.sequence(...) returns 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. robot.arm.vertical() is already finished, so .named(...) on it is a compile error.
A bare hold in the middle
Swap the first member for a plain robot.arm.vertical() 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( robot.flywheel.runFast().withTimeout(Seconds.of(2.0)), robot.arm.horizontal()) .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 the same way a single command does. whileTrue runs the group while the button is held and cancels it on release. Canceling sends nothing to the motors, as Hardware Simulation showed, so the release sends a stop of its own.
driver.y().whileTrue(spinUpWhenReady).whileFalse(robot.flywheel.stop());The last member decides whether a group ends at all. End on a hold and the group is a hold. End on a step and the group finishes by itself, and the last request stays latched, so the stop belongs inside the group.
Check your work
- Add the
Secondsimport and theSpin Up When Ready (hold)binding to yourMyTeleopconstructor. - 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
robot.flywheel.runFast().withTimeout(Seconds.of(1.0))on its own instead and hold it for two seconds. The step ends after one second and the flywheel stays at 75 rotations per second, because nothing claimed it afterwards. A closingrobot.flywheel.stop().withTimeout(Seconds.of(0.5))step is the fix.
You should see
- The arm drives to vertical and holds there for one second, then the flywheel spins up.
- The flywheel holds while Y is down and stops when you release it.
- With the timeout gone, the arm reaches vertical and the flywheel never starts.
These three failures look nothing alike.
| What you see | Cause |
|---|---|
| The arm moves and the flywheel never starts | A member with no ending ahead of it. |
| 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. Finish Conditions is next and replaces the stopwatch on the arm member with the arm's own report that it arrived.
Check yourself
You put robot.arm.vertical(), a hold with no timeout, as the first member of Command.sequence(...). What happens?
You bind Y to Command.sequence(stepA, stepB, robot.flywheel.runFast()), where stepA and stepB each end on a timeout. You hold Y for ten seconds. When does the group end?
In Command.race(robot.flywheel.runFast().withTimeout(Seconds.of(2.0)), robot.arm.horizontal()), 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?