The default command is initially an idle command that only owns the mechanism without doing
+ * anything. This command has the lowest possible priority to allow any other command to run.
+ *
+ * @param defaultCommand the new default command
+ */
+ default void setDefaultCommand(Command defaultCommand) {
+ getRegisteredScheduler().setDefaultCommand(this, defaultCommand);
+ }
+
+ /**
+ * Gets the default command that was set by the latest call to {@link
+ * #setDefaultCommand(Command)}.
+ *
+ * @return The currently configured default command
+ */
+ default Command getDefaultCommand() {
+ return getRegisteredScheduler().getDefaultCommandFor(this);
+ }
+
+ /**
+ * Starts building a command that requires this mechanism.
+ *
+ * @param commandBody The main function body of the command.
+ * @return The command builder, for further configuration.
+ */
+ default NeedsNameBuilderStage run(Consumer commandBody) {
+ return new StagedCommandBuilder().requiring(this).executing(commandBody);
+ }
+
+ /**
+ * Starts building a command that requires this mechanism. The given function will be called
+ * repeatedly in an infinite loop. Useful for building commands that don't need state or multiple
+ * stages of logic.
+ *
+ * @param loopBody The body of the infinite loop.
+ * @return The command builder, for further configuration.
+ */
+ default NeedsNameBuilderStage runRepeatedly(Runnable loopBody) {
+ return run(coroutine -> {
+ while (true) {
+ loopBody.run();
+ coroutine.yield();
+ }
+ });
+ }
+
+ /**
+ * Returns a command that idles this mechanism until another command claims it. The idle command
+ * has {@link Command#LOWEST_PRIORITY the lowest priority} and can be interrupted by any other
+ * command.
+ *
+ *
The {@link #getDefaultCommand() default command} for every mechanism is an idle command
+ * unless a different default command has been configured.
+ *
+ * @return A new idle command.
+ */
+ default Command idle() {
+ return run(Coroutine::park).withPriority(Command.LOWEST_PRIORITY).named(getName() + "[IDLE]");
+ }
+
+ /**
+ * Returns a command that idles this mechanism for the given duration of time.
+ *
+ * @param duration How long the mechanism should idle for.
+ * @return A new idle command.
+ */
+ default Command idleFor(Time duration) {
+ return idle().withTimeout(duration);
+ }
+
+ /**
+ * Gets all running commands that require this mechanism. Commands are returned in the order in
+ * which they were scheduled. The returned list is read-only. Every command in the list will have
+ * been scheduled by the previous entry in the list or by intermediate commands that do not
+ * require the mechanism.
+ *
+ * @return The currently running commands that require the mechanism.
+ */
+ @NoDiscard
+ default List getRunningCommands() {
+ return getRegisteredScheduler().getRunningCommandsFor(this);
+ }
+}
diff --git a/examples/stage1/snippets/src/main/java/org/wpilib/command3/button/CommandXboxController.java b/examples/stage1/snippets/src/main/java/org/wpilib/command3/button/CommandXboxController.java
new file mode 100644
index 0000000..b07e7ca
--- /dev/null
+++ b/examples/stage1/snippets/src/main/java/org/wpilib/command3/button/CommandXboxController.java
@@ -0,0 +1,23 @@
+package org.wpilib.command3.button;
+
+import org.wpilib.command3.Trigger;
+
+/**
+ * A shim for the `CommandXboxController` class, which isn't yet present in wpilib beta 6 (but is in
+ * beta 7).
+ */
+public class CommandXboxController {
+ public CommandXboxController(int port) {}
+
+ public double getLeftY() {
+ return 0;
+ }
+
+ public Trigger a() {
+ return new Trigger(() -> false);
+ }
+
+ public Trigger leftBumper() {
+ return new Trigger(() -> false);
+ }
+}
diff --git a/examples/stage1/snippets/src/main/java/org/wpilib/driverstation/XboxController.java b/examples/stage1/snippets/src/main/java/org/wpilib/driverstation/XboxController.java
new file mode 100644
index 0000000..41ef170
--- /dev/null
+++ b/examples/stage1/snippets/src/main/java/org/wpilib/driverstation/XboxController.java
@@ -0,0 +1,13 @@
+package org.wpilib.driverstation;
+
+/**
+ * A shim for the `CommandXboxController` class, which isn't yet present in wpilib beta 6 (but is in
+ * beta 7).
+ */
+public class XboxController {
+ public XboxController(int id) {}
+
+ public boolean getAButton() {
+ return false;
+ }
+}
diff --git a/examples/stage1/snippets/src/main/java/sources/CommandBasedKitbot.java b/examples/stage1/snippets/src/main/java/sources/CommandBasedKitbot.java
new file mode 100644
index 0000000..67abc56
--- /dev/null
+++ b/examples/stage1/snippets/src/main/java/sources/CommandBasedKitbot.java
@@ -0,0 +1,43 @@
+package sources;
+
+import org.wpilib.command3.Command;
+import org.wpilib.command3.Scheduler;
+import org.wpilib.command3.button.CommandXboxController;
+import org.wpilib.framework.OpModeRobot;
+import org.wpilib.opmode.PeriodicOpMode;
+
+class CommandBasedKitbot {
+ void schedulerExample() {
+ // [robotDef]
+ class Robot extends OpModeRobot {
+ @Override
+ public void robotPeriodic() {
+ Scheduler.getDefault().run();
+ }
+ }
+ // [/robotDef]
+ }
+
+ class MyTeleop extends PeriodicOpMode {
+ private final CommandXboxController xbox = new CommandXboxController(0);
+
+ // [triggerBindingDef]
+ public MyTeleop(Robot robot) {
+ xbox.leftBumper().whileTrue(robot.intake.intake()).whileTrue(robot.feeder.intake());
+
+ // now fill in the rest...
+ }
+ // [/triggerBindingDef]
+ }
+
+ class ExampleMechanism {
+ Command intake() {
+ return null;
+ }
+ }
+
+ class Robot {
+ ExampleMechanism intake = new ExampleMechanism();
+ ExampleMechanism feeder = new ExampleMechanism();
+ }
+}
diff --git a/examples/stage1/snippets/src/main/java/sources/CommandBasedKitbotPt2.java b/examples/stage1/snippets/src/main/java/sources/CommandBasedKitbotPt2.java
new file mode 100644
index 0000000..2726831
--- /dev/null
+++ b/examples/stage1/snippets/src/main/java/sources/CommandBasedKitbotPt2.java
@@ -0,0 +1,75 @@
+package sources;
+
+import static org.wpilib.units.Units.Seconds;
+
+import java.util.function.DoubleSupplier;
+import org.wpilib.command3.Command;
+import org.wpilib.command3.Mechanism;
+import org.wpilib.command3.Scheduler;
+import org.wpilib.drive.DifferentialDrive;
+import org.wpilib.hardware.imu.OnboardIMU;
+import org.wpilib.opmode.PeriodicOpMode;
+
+class CommandBasedKitbotPt2 {
+ class ExampleOpMode extends PeriodicOpMode {
+ private final Command myAutoCommand = null;
+
+ // [startMethod]
+ @Override
+ public void start() {
+ Scheduler.getDefault().schedule(myAutoCommand);
+ }
+ // [/startMethod]
+
+ public ExampleOpMode(Robot robot) {
+ DoubleSupplier forwardThrottle = null, rotationThrottle = null;
+
+ var driveCmd =
+ // [driveCommand]
+ robot.drivetrain.arcadeDrive(forwardThrottle, rotationThrottle);
+ // [/driveCommand]
+
+ var driveCmdWithTimeout =
+ // [4SecDriveCommand]
+ robot.drivetrain
+ .arcadeDrive(forwardThrottle, rotationThrottle)
+ .withTimeout(Seconds.of(4));
+ // [/4SecDriveCommand]
+ }
+ }
+
+ class Robot {
+ ExampleDriveMechanism drivetrain = new ExampleDriveMechanism();
+ }
+
+ class ExampleDriveMechanism implements Mechanism {
+ private final OnboardIMU imu = new OnboardIMU(OnboardIMU.MountOrientation.FLAT);
+ private final DifferentialDrive differentialDrive = new DifferentialDrive(throttle -> {}, throttle -> {});
+
+ Command arcadeDrive(DoubleSupplier forwardThrottle, DoubleSupplier rotationThrottle) {
+ return run(coroutine -> {
+ // [arcadeDriveBody]
+ while (true) {
+ differentialDrive.arcadeDrive(
+ forwardThrottle.getAsDouble(), rotationThrottle.getAsDouble());
+ coroutine.yield();
+ }
+ // [/arcadeDriveBody]
+ })
+ .named("Drive");
+ }
+
+ Command rotateInPlaceHint(double angleDegrees) {
+ return run(coroutine -> {
+ // [rotateInPlaceBody]
+ double targetAngle = imu.getRotation2d().getDegrees() + angleDegrees;
+ while (true) {
+ // What to add here?
+ coroutine.yield();
+ }
+ // [/rotateInPlaceBody]
+ })
+ .named("RotateInPlace");
+ }
+ }
+}
diff --git a/examples/stage1/snippets/src/main/java/sources/CommandBody.java b/examples/stage1/snippets/src/main/java/sources/CommandBody.java
new file mode 100644
index 0000000..ae507c8
--- /dev/null
+++ b/examples/stage1/snippets/src/main/java/sources/CommandBody.java
@@ -0,0 +1,47 @@
+package sources;
+
+import org.wpilib.command3.Command;
+import org.wpilib.drive.DifferentialDrive;
+
+class CommandBody {
+ void main() {
+ // [triangleLoop]
+ int number = 1;
+ while (number < 4) {
+ System.out.println("-".repeat(number));
+ number = number + 1;
+ }
+ while (number > 0) {
+ System.out.println("-".repeat(number));
+ number = number - 1;
+ }
+ // [/triangleLoop]
+
+ var exampleCommand = Command.noRequirements(coroutine -> {
+ var motor = new ExampleMotor();
+ var differentialDrive = new DifferentialDrive(throttle -> {}, throttle -> {});
+
+ // [rotate90CommandBody]
+ double targetDirection = getCurrentYaw() + 90;
+ while (getCurrentYaw() < targetDirection) {
+ differentialDrive.arcadeDrive(0, 0.5);
+ coroutine.yield();
+ }
+ differentialDrive.arcadeDrive(0, 0);
+ // [/rotate90CommandBody]
+
+ // [fullThrottleCmdBody]
+ System.out.println("Full Speed Baby!");
+ while (true) {
+ motor.setThrottle(1.0);
+ coroutine.yield(); // Confused? We'll explain this in the section below
+ }
+ // [/fullThrottleCmdBody]
+ })
+ .named("Example!");
+ }
+
+ private double getCurrentYaw() {
+ return 0;
+ }
+}
diff --git a/examples/stage1/snippets/src/main/java/sources/CommandsAndMechsPt1.java b/examples/stage1/snippets/src/main/java/sources/CommandsAndMechsPt1.java
new file mode 100644
index 0000000..5cd7fa8
--- /dev/null
+++ b/examples/stage1/snippets/src/main/java/sources/CommandsAndMechsPt1.java
@@ -0,0 +1,111 @@
+package sources;
+
+import org.wpilib.command3.Command;
+import org.wpilib.command3.Mechanism;
+
+class CommandsAndMechsPt1 implements Mechanism {
+ private final ExampleMotor motor = new ExampleMotor();
+
+ void partialDefinitions() {
+ // [mechanismDef]
+ class Intake implements Mechanism {
+ // Store any motors specific to the mechanism as private members.
+ // This can include TalonFX, SparkMax and/or SparkFlex instances.
+ private final ExampleMotor motor = new ExampleMotor();
+ }
+ // [/mechanismDef]
+
+ // [mechanismInRobotDef]
+ class Robot {
+ private final Intake intake = new Intake();
+ }
+ // [/mechanismInRobotDef]
+ }
+
+ void commandDefinitions() {
+ var exampleCommand =
+ // [commandDef]
+ run(coroutine -> {
+ System.out.println("Full Speed Baby!");
+ while (true) {
+ motor.setThrottle(1.0);
+ coroutine.yield();
+ }
+ })
+ .named("Set to Full Throttle");
+ // [/commandDef]
+
+ var exampleCommandWithPriority =
+ // [commandWithPriorityDef]
+ run(coroutine -> {
+ System.out.println("Full Speed Baby!");
+ while (true) {
+ motor.setThrottle(1.0);
+ coroutine.yield();
+ }
+ })
+ .withPriority(1) // recall that the default priority is 0.
+ .named("Set to Full Throttle");
+ // [/commandWithPriorityDef]
+ }
+
+ // [fullThrottleIntake]
+ class Intake implements Mechanism {
+ private final ExampleMotor motor = new ExampleMotor();
+
+ public Command fullThrottle() {
+ return run(coroutine -> {
+ System.out.println("Full Speed Baby!");
+ while (true) {
+ motor.setThrottle(1.0);
+ coroutine.yield();
+ }
+ })
+ .named("Set to Full Throttle");
+ }
+ }
+ // [/fullThrottleIntake]
+
+ // [runAtThrottleCommand]
+ // This command can now set the intake at any throttle level:
+ public Command runAtThrottle(double throttle) {
+ return run(coroutine -> {
+ while (true) {
+ motor.setThrottle(throttle);
+ coroutine.yield();
+ }
+ })
+ .named("Set Throttle to " + throttle);
+ }
+ // [/runAtThrottleCommand]
+
+ // [commandAwait]
+ public Command printHiThenFullThrottle() {
+ return run(coroutine -> {
+ // "Hi" is printed before the runAtThrottle Command is run
+ System.out.println("Hi!");
+ coroutine.await(runAtThrottle(1.0));
+ })
+ .named("Set to Full Throttle & Print Hi");
+ }
+ // [/commandAwait]
+
+ // [commandSequence]
+ public Command deployPivot() {
+ return null; // placeholder for actual command
+ }
+
+ public Command runIntakeRollers() {
+ return null; // placeholder for actual command
+ }
+
+ public Command commandSequence() {
+ return run(coroutine -> {
+ System.out.println("Hi!"); // step 1
+ coroutine.await(deployPivot()); // step 2
+ coroutine.await(runIntakeRollers()); // step 3
+ })
+ .named("Command Sequence");
+ }
+ // [/commandSequence]
+}
diff --git a/examples/stage1/snippets/src/main/java/sources/CommandsAndMechsPt2.java b/examples/stage1/snippets/src/main/java/sources/CommandsAndMechsPt2.java
new file mode 100644
index 0000000..0f1d0b2
--- /dev/null
+++ b/examples/stage1/snippets/src/main/java/sources/CommandsAndMechsPt2.java
@@ -0,0 +1,80 @@
+package sources;
+
+import static org.wpilib.units.Units.Seconds;
+
+import org.wpilib.command3.Command;
+import org.wpilib.command3.Mechanism;
+
+class CommandsAndMechsPt2 {
+ // [defaultCommand]
+ class Intake implements Mechanism {
+ // Placeholder for TalonFX, SparkMax or SparkFlex
+ private final ExampleMotor motor = new ExampleMotor();
+
+ public Intake() {
+ setDefaultCommand(idle());
+ }
+
+ public Command idle() {
+ return run(coroutine -> {
+ while (true) {
+ motor.setThrottle(0);
+ coroutine.yield();
+ }
+ })
+ .named("Idle");
+ }
+ }
+ // [/defaultCommand]
+
+ // [noRequirementsCommand]
+ class Robot {
+ public Command justPrintHi() {
+ return Command.noRequirements(coroutine -> {
+ System.out.println("Hello World!");
+ })
+ .named("Hello World!");
+ }
+ }
+ // [/noRequirementsCommand]
+
+ class CommandUsages implements Mechanism {
+ // [delayCommand]
+ public Command wait5SecsThenPrintHi() {
+ return run(coroutine -> {
+ coroutine.wait(Seconds.of(5));
+ System.out.println("This will show up 5 seconds later.");
+ })
+ .named("Wait 5 Secs, Then Print Hi");
+ }
+ // [/delayCommand]
+
+ // [timeoutCommand]
+ public Command fullThrottleForFiveSeconds() {
+ return fullThrottle().withTimeout(Seconds.of(5));
+ }
+
+ public Command fullThrottle() {
+ return null; // placeholder for actual command
+ }
+ // [/timeoutCommand]
+
+ // [parallelCommands]
+ public Command parallelCommands() {
+ return run(coroutine -> {
+ System.out.println("Hi!");
+ coroutine.awaitAll(deployIntake(), runIntakeRollers());
+ })
+ .named("Parallel Commands");
+ }
+
+ private Command deployIntake() {
+ return null; // placeholder for actual command
+ }
+
+ private Command runIntakeRollers() {
+ return null; // placeholder for actual command
+ }
+ // [/parallelCommands]
+ }
+}
diff --git a/examples/stage1/snippets/src/main/java/sources/ExampleMotor.java b/examples/stage1/snippets/src/main/java/sources/ExampleMotor.java
new file mode 100644
index 0000000..72e6e6f
--- /dev/null
+++ b/examples/stage1/snippets/src/main/java/sources/ExampleMotor.java
@@ -0,0 +1,10 @@
+package sources;
+
+/** This class acts as a stand-in for a TalonFX, SparkMax, etc. in snippets. */
+public class ExampleMotor {
+ void setThrottle(double throttle) {}
+
+ double speed() {
+ return 0;
+ }
+}
diff --git a/examples/stage1/snippets/src/main/java/sources/SuppliersInCommandBased.java b/examples/stage1/snippets/src/main/java/sources/SuppliersInCommandBased.java
new file mode 100644
index 0000000..dc44a6c
--- /dev/null
+++ b/examples/stage1/snippets/src/main/java/sources/SuppliersInCommandBased.java
@@ -0,0 +1,96 @@
+package sources;
+
+import java.util.function.DoubleSupplier;
+import org.wpilib.command3.Command;
+import org.wpilib.command3.Mechanism;
+import org.wpilib.command3.Trigger;
+import org.wpilib.command3.button.CommandXboxController;
+import org.wpilib.driverstation.RobotState;
+
+class SuppliersInCommandBased {
+ void main() {
+ var motor = new ExampleMotor();
+ var xbox = new CommandXboxController(0);
+
+ // [doubleSupplierExample]
+ DoubleSupplier supplier = () -> xbox.getLeftY();
+ double controllerOutput = supplier.getAsDouble();
+
+ // [/doubleSupplierExample]
+
+ // [triggerBooleanSupplier]
+ new Trigger(() -> motor.speed() > 60);
+
+ // [/triggerBooleanSupplier]
+ }
+
+ void correctUsages() {
+ class Intake implements Mechanism {
+ private final ExampleMotor motor = new ExampleMotor();
+
+ // [runAtThrottleSupplier]
+ public Command runAtThrottle(DoubleSupplier throttleSupplier) {
+ return run(coroutine -> {
+ while (true) {
+ double throttle = throttleSupplier.getAsDouble();
+ motor.setThrottle(throttle);
+ coroutine.yield();
+ }
+ })
+ .named("Run Intake");
+ }
+ // [/runAtThrottleSupplier]
+
+ // [untilModifier]
+ // From the Intake class mentioned earlier
+ public Command fullThrottleUntilRobotDisabled() {
+ return fullThrottle().until(() -> RobotState.isDisabled()).named("Full Throttle Until Disable");
+ }
+
+ private Command fullThrottle() {
+ return null; // placeholder for actual command
+ }
+ // [/untilModifier]
+ }
+
+ // [correctCommand]
+ class Robot {
+ public Robot() {
+ var xbox = new CommandXboxController(0);
+ var intake = new Intake();
+ intake.setDefaultCommand(intake.runAtThrottle(() -> xbox.getLeftY()));
+ }
+ }
+ // [/correctCommand]
+ }
+
+ void incorrectUsages() {
+ // [runAtThrottleDouble]
+ class Intake implements Mechanism {
+ // Placeholder for TalonFX, SparkMax or SparkFlex
+ private final ExampleMotor motor = new ExampleMotor();
+
+ public Command runAtThrottle(double throttle) {
+ return run(coroutine -> {
+ while (true) {
+ motor.setThrottle(throttle);
+ coroutine.yield();
+ }
+ })
+ .named("Run Intake");
+ }
+ }
+ // [/runAtThrottleDouble]
+
+ // [incorrectCommand]
+ class Robot {
+ public Robot() {
+ var xbox = new CommandXboxController(0);
+ var intake = new Intake();
+ double controllerOutput = xbox.getLeftY();
+ intake.setDefaultCommand(intake.runAtThrottle(controllerOutput));
+ }
+ }
+ // [/incorrectCommand]
+ }
+}
diff --git a/examples/stage1/snippets/src/main/java/sources/Triggers.java b/examples/stage1/snippets/src/main/java/sources/Triggers.java
new file mode 100644
index 0000000..e8f5e71
--- /dev/null
+++ b/examples/stage1/snippets/src/main/java/sources/Triggers.java
@@ -0,0 +1,113 @@
+package sources;
+
+import org.wpilib.command3.Command;
+import org.wpilib.command3.Scheduler;
+import org.wpilib.command3.Trigger;
+import org.wpilib.command3.button.CommandXboxController;
+import org.wpilib.driverstation.RobotState;
+import org.wpilib.driverstation.XboxController;
+import org.wpilib.framework.TimedRobot;
+import org.wpilib.opmode.Autonomous;
+import org.wpilib.opmode.PeriodicOpMode;
+
+class Triggers {
+ void main() {
+ var motor = new ExampleMotor();
+ var intake = new ExampleMechanism();
+ var shooter = new ExampleMechanism();
+
+ // [motorTooFastTrigger]
+ Trigger motorTooFastTrigger = new Trigger(() -> motor.speed() > 60);
+ boolean isMotorTooFast = motorTooFastTrigger.getAsBoolean();
+ // [/motorTooFastTrigger]
+
+ // [multiBindingsVerbose]
+ Trigger teleopEnabledTrigger = new Trigger(() -> RobotState.isTeleopEnabled());
+ teleopEnabledTrigger.whileTrue(intake.runAtThrottle(0.5));
+ teleopEnabledTrigger.whileTrue(shooter.runAtThrottle(0.5));
+ // [/multiBindingsVerbose]
+
+ // [multiBindings]
+ new Trigger(() -> RobotState.isTeleopEnabled())
+ .whileTrue(intake.runAtThrottle(0.5))
+ .whileTrue(shooter.runAtThrottle(0.5));
+ // [/multiBindings]
+
+ // [xboxGetAButton]
+ XboxController xbox = new XboxController(0);
+ boolean aButtonHeld = xbox.getAButton();
+ // [/xboxGetAButton]
+
+ // [triggerAButton]
+ XboxController xbox2 = new XboxController(0);
+ Trigger aButton = new Trigger(() -> xbox2.getAButton());
+ aButton.whileTrue(someCommand());
+ // [/triggerAButton]
+
+ // [commandXboxController]
+ CommandXboxController xbox3 = new CommandXboxController(0);
+ Trigger aButton2 = xbox3.a();
+ aButton2.whileTrue(someCommand());
+ // shorthand:
+ xbox3.a().whileTrue(someCommand());
+ // [/commandXboxController]
+
+ // [scheduleAndPrint]
+ Scheduler.getDefault().schedule(myAutonomousCommand());
+ System.out.println("Hello!");
+ // [/scheduleAndPrint]
+ }
+
+ void scheduleMethodExamples() {
+ // [timedRobotExample]
+ class Robot extends TimedRobot {
+ @Override
+ public void autonomousInit() {
+ Scheduler.getDefault().schedule(myAutonomousCommand());
+ }
+ }
+ // [/timedRobotExample]
+
+ // [opModeExample]
+ @Autonomous
+ class MyAutoOpMode extends PeriodicOpMode {
+ @Override
+ public void start() {
+ Scheduler.getDefault().schedule(myAutonomousCommand());
+ }
+ }
+ // [/opModeExample]
+ }
+
+ // [teleopEnabledRobot]
+ public class Robot {
+ private final ExampleMechanism intake = new ExampleMechanism();
+
+ private static boolean teleopEnabled() {
+ return RobotState.isTeleopEnabled(); // in case you didn't know!
+ }
+
+ public Robot() {
+ Trigger teleopEnabledTrigger = new Trigger(() -> teleopEnabled());
+ teleopEnabledTrigger.onTrue(intake.runAtThrottle(0.5));
+
+ // A shorthand for the above:
+ new Trigger(() -> teleopEnabled()).onTrue(intake.runAtThrottle(0.5));
+ }
+ }
+ // [/teleopEnabledRobot]
+
+ Command myAutonomousCommand() {
+ return null;
+ }
+
+ Command someCommand() {
+ return null;
+ }
+
+ class ExampleMechanism {
+ Command runAtThrottle(double throttle) {
+ return null;
+ }
+ }
+}
diff --git a/examples/stage1/snippets/vendordeps/CommandsV3.json b/examples/stage1/snippets/vendordeps/CommandsV3.json
new file mode 100644
index 0000000..f406bde
--- /dev/null
+++ b/examples/stage1/snippets/vendordeps/CommandsV3.json
@@ -0,0 +1,25 @@
+{
+ "fileName": "CommandsV3.json",
+ "name": "Commands v3",
+ "version": "1.0.0",
+ "uuid": "4decdc05-a056-46cf-9561-39449bbb0130",
+ "wpilibYear": "2027_alpha5",
+ "mavenUrls": [],
+ "jsonUrl": "",
+ "conflictsWith": [
+ {
+ "uuid": "111e20f7-815e-48f8-9dd6-e675ce75b266",
+ "errorMessage": "Users can not have both Commands v2 and Commands v3 vendordeps in their robot program.",
+ "offlineFileName": "CommandsV2.json"
+ }
+ ],
+ "javaDependencies": [
+ {
+ "groupId": "org.wpilib",
+ "artifactId": "commands3-java",
+ "version": "wpilib"
+ }
+ ],
+ "jniDependencies": [],
+ "cppDependencies": []
+}
diff --git a/public/stage1/commands/2910-robot.png b/public/stage1/commands/2910-robot.png
new file mode 100644
index 0000000..921907c
Binary files /dev/null and b/public/stage1/commands/2910-robot.png differ
diff --git a/public/stage1/commands/human-flow-diagram.png b/public/stage1/commands/human-flow-diagram.png
new file mode 100644
index 0000000..9990a9e
Binary files /dev/null and b/public/stage1/commands/human-flow-diagram.png differ
diff --git a/public/stage1/commands/robot-flow-diagram.png b/public/stage1/commands/robot-flow-diagram.png
new file mode 100644
index 0000000..07e1456
Binary files /dev/null and b/public/stage1/commands/robot-flow-diagram.png differ
diff --git a/src/config/sidebarConfig.ts b/src/config/sidebarConfig.ts
index d59606a..783e37b 100644
--- a/src/config/sidebarConfig.ts
+++ b/src/config/sidebarConfig.ts
@@ -117,6 +117,45 @@ export const sidebarSections: Record = {
},
],
+ '/stage-1b-commands': [
+ {
+ label: 'Stage 1B: Commands',
+ items: [
+ {
+ label: 'The Concepts',
+ slug: 'stage-1b-commands/command-based-overview',
+ },
+ {
+ label: 'The Body of a Command',
+ slug: 'stage-1b-commands/the-command-body',
+ },
+ {
+ label: 'Commands & Mechanisms, Pt. 1',
+ slug: 'stage-1b-commands/commands-and-mechanisms',
+ },
+ {
+ label: 'Triggers and Scheduling',
+ slug: 'stage-1b-commands/triggers',
+ },
+ {
+ label: 'Commands & Mechanisms, Pt. 2',
+ slug: 'stage-1b-commands/commands-and-mechanisms-pt2',
+ },
+ {
+ label: 'Exercise - Kitbot Rewrite, Pt. 1',
+ slug: 'stage-1b-commands/command-based-kitbot',
+ },
+ {
+ label: 'Suppliers in Command-Based',
+ slug: 'stage-1b-commands/suppliers-in-command-based',
+ },
+ {
+ label: 'Exercise - Kitbot Rewrite, Pt. 2',
+ slug: 'stage-1b-commands/command-based-kitbot-pt2',
+ },
+ ],
+ },
+ ],
// Intro To Java section
'/intro-to-java': [
{
diff --git a/src/content/docs/getting-started/required-tools.mdx b/src/content/docs/getting-started/required-tools.mdx
index 76f71ad..93ba4d2 100644
--- a/src/content/docs/getting-started/required-tools.mdx
+++ b/src/content/docs/getting-started/required-tools.mdx
@@ -36,4 +36,4 @@ It’s commonly used in FRC because it's free and easily accessible, in addition
For many of the lessons on this website, it will be necessary to have a GitHub account.
One can be can be created [here](https://github.com/signup).
-It's also recommended to sign up for the [GitHub student developer pack](https://education.github.com/pack), this will give you access to a variety of resources.
+It's also recommended to sign up for the [GitHub student developer pack](https://education.github.com/pack), this will give you access to a variety of resources/
diff --git a/src/content/docs/stage-1b-commands/command-based-kitbot-pt2.mdx b/src/content/docs/stage-1b-commands/command-based-kitbot-pt2.mdx
new file mode 100644
index 0000000..d4f2ba2
--- /dev/null
+++ b/src/content/docs/stage-1b-commands/command-based-kitbot-pt2.mdx
@@ -0,0 +1,127 @@
+---
+title: Exercise - Kitbot Rewrite, Pt. 2
+description: Rewriting the kitbot code in stage 1A to command-based
+prev: stage-1b-commands/suppliers-in-command-based
+next: false
+---
+
+import Aside from '../../../components/Aside.astro';
+
+### What You'll Accomplish
+
+In this second part of the kitbot rewrite, you will:
+
+1. Create a `Drivetrain` mechanism class.
+2. Define an `arcadeDrive` command that dynamically reads controller inputs using `DoubleSupplier`s.
+3. Bind the default drivetrain command in your teleop OpMode to enable driving.
+4. Define some basic autonomous modes.
+
+### Task 1: The Drivetrain Mechanism
+
+In `Drivetrain.java`, implement the following:
+
+1. An `idle()` command that commands `0.0` speed and `0.0` rotation, and is set as the default command in the constructor.
+2. An `arcadeDrive(DoubleSupplier forwardThrottle, DoubleSupplier rotationThrottle)` command that continuously updates the drive motor speeds using the values from the suppliers.
+
+{/* prettier-ignore */}
+
+ Hint
+ Since the joystick values change constantly, you must fetch the speed and
+ rotation from the suppliers inside the command's loop. You should have the
+ following inside of your command's body:
+ ```java stage1/snippets/src/main/java/sources/CommandBasedKitbotPt2.java#arcadeDriveBody
+
+ ```
+
+
+
+### Task 2: Drivetrain Setup
+
+1. Create a `Drivetrain` instance in `Robot.java` named `drivetrain`.
+ It should be a public instead of private.
+2. Then, open `MyTeleop.java` (your teleop OpMode).
+ In the constructor, set the drivetrain's default command to `arcadeDrive`.
+ Pass lambda expressions that read from your controller's joystick axes.
+
+
+ Hint 1
+ You can create an arcade drive command like so:
+ ```java stage1/snippets/src/main/java/sources/CommandBasedKitbotPt2.java#driveCommand
+
+ ```
+ Can you figure out the expressions to replace `forwardThrottle` and `rotationThrottle` with?
+
+
+ Hint 2
+ The expression for the `forwardThrottle` argument is `() -> -xbox.getLeftY()`, since we
+ want to recompute the value of `-xbox.getLeftY()` while the `arcadeDrive` command is running.
+ Can you figure out the expression for the `rotationThrottle` argument, considering
+ that the goal is to recompute the value of `xbox.getRightX()`?
+
+
+
+
+### Task 3: Drive Straight Autonomous
+
+Go to the `DriveStraightAutoMode.java` file.
+In the `start()` method, schedule
+an `arcadeDrive` command with a timeout of 4 seconds.
+
+This `arcadeDrive` command should have a constant forward throttle of `0.5`
+and a constant rotational throttle of `0`.
+
+
+ Hint 1
+ To schedule a command without a trigger, use `Scheduler.getDefault().schedule(Command)`:
+ ```java stage1/snippets/src/main/java/sources/CommandBasedKitbotPt2.java#startMethod
+
+ ```
+ Can you figure out what `myAutoCommand` should be?
+
+
+ Hint 2
+ A `DoubleSupplier` with syntax `() -> 0.5` will always return
+ 0.5 when its `getAsDouble()` method is called.
+ Use this information to complete the following `Command` by
+ replacing `forwardThrottle` and `rotationalThrottle` with `DoubleSupplier`s:
+ ```java stage1/snippets/src/main/java/sources/CommandBasedKitbotPt2.java#4SecDriveCommand
+
+ ```
+
+
+
+### Task 4: Rotate in Place Command
+
+Create a new `Command` in `Drivetrain.java`, called `rotateInPlace`,
+with the parameters `double angleDegrees` and `DoubleSupplier rotationThrottle`.
+It should execute these actions, in order:
+
+1. Define a local variable, named `targetAngle`, whose value is the sum of `angleDegrees` and the robot's current yaw.
+ To get the robot's current yaw, call `imu.getRotation2d().getDegrees()`.
+2. Call `differentialDrive.arcadeDrive()` in a while loop,
+ which should continue while the robot's current yaw is less than the `targetAngle`.
+
+
+ Hint
+ ```java stage1/snippets/src/main/java/sources/CommandBasedKitbotPt2.java#rotateInPlaceBody
+
+ ```
+
+
+
+### Task 5: Rotate in Place Autonomous
+
+Finally, use the `rotateInPlace` command to implement a turn-in-place autonomous within `RotationAutoMode.java`.
+It should rotate the robot 90 degrees from its starting position, at a throttle of 0.2.
+
+
+ Hint
+ Use the `Scheduler` to schedule a new `rotateInPlace` command, just like how
+ task 3 accomplishes this.
+
diff --git a/src/content/docs/stage-1b-commands/command-based-kitbot.mdx b/src/content/docs/stage-1b-commands/command-based-kitbot.mdx
new file mode 100644
index 0000000..163c483
--- /dev/null
+++ b/src/content/docs/stage-1b-commands/command-based-kitbot.mdx
@@ -0,0 +1,121 @@
+---
+title: Exercise - Kitbot Rewrite, Pt. 1
+description: Rewriting the kitbot code in stage 1A to command-based
+prev: stage-1b-commands/commands-and-mechanisms-pt2
+next: stage-1b-commands/suppliers-in-command-based
+---
+
+import Aside from '../../../components/Aside.astro';
+
+### What You'll Accomplish
+
+You will rewrite the code for a kitbot's intake launcher and feeder
+mechanisms.
+In part 2 of this exercise, you'll rewrite the drivetrain code
+in command-based, and add some basic auto modes.
+
+
+
+### Task 1: Setup
+
+Add the `Scheduler.getDefault().run()` statement in the `Robot` class.
+This allows for commands to function.
+
+```java stage1/snippets/src/main/java/sources/CommandBasedKitbot.java#robotDef
+
+```
+
+### Task 2: The Feeder Mechanism
+
+Add the following to the feeder mechanism in the `Feeder.java` file:
+
+1. A private `TalonFX` motor controller object, named `motor`,
+ with ID 5 and a systemcore CANBus of ID 0.
+2. A `feed()` command that sets the throttle of the motor to 0.75 forever.
+3. An `intake()` command that sets the throttle of the motor to -1 forever.
+4. An `outtake()` command that sets the throttle of the motor to 1 forever.
+5. An `idle()` command that sets the throttle of the motor to 0 forever.
+ Make this the default command.
+
+The `feed()`, `intake()`, `outtake()` and `idle()` commands should be returned from methods.
+
+After that, you should see a property named `sim` that is currently marked `null`.
+Replace `null` with `new SingleFlywheelSim(motor, "Feeder")`.
+
+
+ Item 1, Hint 1
+ You can create a `CANBus` object via `CANBus.systemcore(id)`.
+
+{/* prettier-ignore */}
+
+ Item 1, Hint 2
+ Remember that `TalonFX` is a class, and that creating objects within another
+ class is done like so:
+ {/* rli:ignore */}
+ ```java
+ private TypeOfClass myMotor = new TypeOfClass();
+ ```
+ First, figure out what to replace `TypeOfClass` with. Next,
+ add the appropriate constructor parameters by hovering over
+ the red squiggly line.
+
+
+ Items 2-4, Hint 1
+ Re-read the "Defining Commands" and "Combining Commands and Mechanisms"
+ subsections of the "Commands and Mechanisms, Pt. 1" supersection (it should
+ be visible from the left sidebar).
+
+
+ Item 5, Hint 1
+ To set a mechanism's default command, call `setDefaultCommand(Command)`
+ inside of the constructor.
+
+
+### Task 3: The IntakeLauncher Mechanism
+
+Add the following to the intake launcher mechanism in the `IntakeLauncher.java` file:
+
+1. A private `TalonFX` motor controller object, with ID 4 and a systemcore CANBus of ID 0
+2. A `shoot()` command that waits 2 seconds, then sets the throttle of the motor to 0.9 forever.
+3. A `intake()` command that sets throttle to 0.8 forever.
+4. A `outtake()` command that sets throttle to -0.8 forever.
+5. A `idle()` command that sets throttle to 0 forever.
+ Make this the default command.
+
+
+ Hint
+ Call `coroutine.wait(Seconds.of(2))` to wait 2 seconds within a `Command`.
+
+
+### Task 4: Finish the `Robot` class
+
+Create new instances of the `IntakeLauncher` and `Feeder` classes.
+They should be
+public properties of the `Robot` class, so that they are accessible from opmodes.
+Name them `intakeLauncher` and `feeder`, respectively.
+
+### Task 5: Finish the teleop OpMode
+
+In the constructor of the teleop opmode (it should be in `MyTeleop.java`),
+create a private `CommandXboxController` instance named `xbox`.
+
+In your opmode constructor, define the following behavior:
+
+1. While the `xbox.leftBumper()` trigger is active,
+ run the `robot.intake.intake()` and `robot.feeder.intake()` commands.
+2. While the `xbox.rightBumper()` trigger is active,
+ run the `robot.intake.shoot()` and `robot.feeder.feed()` commands.
+3. While the `xbox.a()` trigger is active,
+ run the `robot.intake.outtake()` and `robot.feeder.outtake()` commands.
+
+
+ Hint
+ ```java stage1/snippets/src/main/java/sources/CommandBasedKitbot.java#triggerBindingDef
+
+ ```
+ Can you think of why `whileTrue()` is used instead of `onTrue()`?
+
+
diff --git a/src/content/docs/stage-1b-commands/command-based-overview.mdx b/src/content/docs/stage-1b-commands/command-based-overview.mdx
new file mode 100644
index 0000000..316e88e
--- /dev/null
+++ b/src/content/docs/stage-1b-commands/command-based-overview.mdx
@@ -0,0 +1,85 @@
+---
+title: The Concepts of Command-Based Programming
+description: The what, how, and why of commands
+prev: false
+next: stage-1b-commands/the-command-body
+---
+
+import Aside from '../../../components/Aside.astro';
+import ContentImage from '../../../components/ContentImage.astro';
+
+### What is Command-Based Programming?
+
+Up until now, most of the code you've written has been akin to a
+set of instructions (execute this task, then this, and finally this).
+However, robots (and humans!) don't just execute a set of tasks and shut down.
+
+Imagine your daily routine.
+When an alarm clock rings, you turn it off and tumble out of bed.
+When your belly rumbles, you walk to the fridge and get a snack.
+When the clock strikes 8 AM,
+you open the front door to leave for school.
+
+
+
+Notice how none of these actions had a predefined order; rather, they were a reaction to
+to certain kinds of stimuli.
+
+Command-based programming models robot behavior in a similar way.
+
+1. It defines behaviors/actions(tumbling out of bed, getting a snack, etc.) as `Command`s.
+2. It defines the stimuli that trigger these behaviors (a ringing alarm clock, a rumbling belly, etc.) as `Trigger`s.
+
+For instance, a command-based robot might have the following structure:
+
+
+
+Here, everything on the left is a `Trigger`, while everything on the right is a `Command`.
+
+
+
+### Mechanisms and Priority
+
+However, the structure of using `Command`s to represent behaviors misses an important component.
+
+Think back to the human example: you’re walking to the fridge, but the clock hits 8 AM before you get there.
+You can't do both at once, since both actions require your arms and legs.
+So, you stop and leave for school.
+
+Next, the robot.
+If the X and Y buttons are held, the shooter and intake motors should both run.
+But if the X and A buttons are held, the robot code must choose between running the shooter
+at a fast speed or a slow speed.
+
+Command-Based models this relationship with 2 concepts: Mechansisms and Priority.
+
+##### Mechanisms
+
+A Mechanism is a robot component whose state can be updated through commands.
+`Command`s declare
+the mechanisms they require - thus, mechanisms are also called "requirements".
+
+An intake is a mechanism because your code controls its speed.
+On the other hand,
+an apriltag camera wouldn't be because your code only reads data from it, but doesn't update its state.
+
+
+
+##### Priority
+
+When 2 commands share one or more requirements, priority determines which command
+should be cancelled and which command should continue.
+
+In the human example, the "leave for school" `Command` would have a higher priority
+than the "get a snack" `Command`.
+
+Priority is represented as an `int`.
+Commands default to a priority of 0, and a
+larger number represents a higher priority.
diff --git a/src/content/docs/stage-1b-commands/commands-and-mechanisms-pt2.mdx b/src/content/docs/stage-1b-commands/commands-and-mechanisms-pt2.mdx
new file mode 100644
index 0000000..d9c95b3
--- /dev/null
+++ b/src/content/docs/stage-1b-commands/commands-and-mechanisms-pt2.mdx
@@ -0,0 +1,71 @@
+---
+title: Commands and Mechanisms, Part 2
+description: Intermediate-level uses of commands and mechanisms
+prev: stage-1b-commands/triggers
+next: stage-1b-commands/command-based-kitbot
+---
+
+import ContentImage from '../../../components/ContentImage.astro';
+
+### Default Commands
+
+Most mechanisms define a "default command" that runs when
+no other commands requiring it are active.
+The default command
+of an intake or a shooter, for instance, would be setting the throttle
+of all motors to 0, forever.
+
+
+
+To set a mechanism's default command, simply call `setDefaultCommand(Command)`, which is
+an instance method of all mechanisms:
+
+```java stage1/snippets/src/main/java/sources/CommandsAndMechsPt2.java#defaultCommand
+
+```
+
+### Adding Delays to Commands
+
+Use `coroutine.wait()` to wait for a predefined amount of time within
+the body of a command:
+
+```java stage1/snippets/src/main/java/sources/CommandsAndMechsPt2.java#delayCommand
+
+```
+
+You can also give an existing command a timeout with the `withTimeout()` modifier:
+
+```java stage1/snippets/src/main/java/sources/CommandsAndMechsPt2.java#timeoutCommand
+
+```
+
+### More commands info
+
+The following information isn't strictly needed for
+the exercises in stage 1b; however, they will be very
+useful for future exercises and coding a real robot.
+
+#### Commands without requirements
+
+Sometimes, you need to create a command that doesn't require any mechanisms.
+To do so, you can replace `run(...)` with `Command.noRequirements(...)`.
+
+```java stage1/snippets/src/main/java/sources/CommandsAndMechsPt2.java#noRequirementsCommand
+
+```
+
+#### Running Commands in parallel
+
+`coroutine.awaitAll(a, b, c)` will run the commands a, b, and c in parallel,
+waiting for all 3 to finish before proceeding.
+
+```java stage1/snippets/src/main/java/sources/CommandsAndMechsPt2.java#parallelCommands
+
+```
+
+On the other hand, `coroutine.awaitAny(a, b, c)` runs a, b, and c in parallel
+until any of a, b, or c finishes.
+Then, it will cancel the remaining commands.
diff --git a/src/content/docs/stage-1b-commands/commands-and-mechanisms.mdx b/src/content/docs/stage-1b-commands/commands-and-mechanisms.mdx
new file mode 100644
index 0000000..de8f005
--- /dev/null
+++ b/src/content/docs/stage-1b-commands/commands-and-mechanisms.mdx
@@ -0,0 +1,135 @@
+---
+title: Commands & Mechanisms, Part 1
+description: The basics of defining mechanisms and writing commands
+prev: stage-1b-commands/the-command-body
+next: stage-1b-commands/why-mechanisms
+---
+
+import Aside from '../../../components/Aside.astro';
+
+### Defining Mechanisms
+
+Mechanisms are simply classes that have the `implements Mechanism` keyword:
+
+```java stage1/snippets/src/main/java/sources/CommandsAndMechsPt1.java#mechanismDef
+
+```
+
+In general, you should create one instance of each mechanism, and that instance should be stored
+in your robot class:
+
+```java stage1/snippets/src/main/java/sources/CommandsAndMechsPt1.java#mechanismInRobotDef
+
+```
+
+
+
+### Defining Commands
+
+Most commands are defined inside of the file containing the class of a mechanism
+(like Intake.java or Shooter.java).
+The code below defines a new `Command`:
+
+```java stage1/snippets/src/main/java/sources/CommandsAndMechsPt1.java#commandDef
+
+```
+
+Notice that:
+
+- Every line of code between `coroutine -> {` and `}` will be run
+ when the command runs, like a method body.
+- This command will print "Full Speed Baby!" once before commanding
+ the motor to spin at max speed.
+- The `named("Set to Full Throttle")` call is required to create a `Command`.
+ This allows coders to distinguish this command from others when debugging their code.
+
+To change the priority of the command, simply add a `withPriority(int)` call before
+you call `named()`:
+
+```java stage1/snippets/src/main/java/sources/CommandsAndMechsPt1.java#commandWithPriorityDef
+
+```
+
+
+
+### Combining Commands and Mechanisms
+
+Here's an example of a command defined inside of a mechanism class:
+
+```java stage1/snippets/src/main/java/sources/CommandsAndMechsPt1.java#fullThrottleIntake
+
+```
+
+Commands created with `run()` will automatically require the mechanism that they are defined inside.
+Here, the full throttle command requires the `Intake`.
+
+Also, notice that the fullThrottle method creates and returns a command.
+All mechanism-specific commands should be defined this way, for 2 reasons:
+
+1. It allows for commands to define parameters, just like a method.
+2. It gives the `Robot` class easy access to these commands.
+
+```java stage1/snippets/src/main/java/sources/CommandsAndMechsPt1.java#runAtThrottleCommand
+
+```
+
+However, this approach requires caution.
+Observe the snippet below:
+
+{/* rli:ignore */}
+
+```java
+public Command printHiThenFullThrottle() {
+ return run(coroutine -> {
+ System.out.println("Hi!");
+ runAtThrottle(1.0); // Error: Commands must be used! Did you mean to fork it or bind it to a trigger?
+ })
+ .named("Set to Full Throttle");
+}
+```
+
+Calling the `fullThrottle()`
+method, as shown, doesn't spin the motor, but instead creates a `Command` object that must be run separately.
+The java
+compiler will catch this and throw a compile-time error.
+
+
+
+
+
+### Command Sequences
+
+If you want to run the `fullThrottle` command inside of another command, you must
+call `coroutine.await(Command)`, like so:
+
+```java stage1/snippets/src/main/java/sources/CommandsAndMechsPt1.java#commandAwait
+
+```
+
+That means you can run multiple commands in sequence:
+
+```java stage1/snippets/src/main/java/sources/CommandsAndMechsPt1.java#commandSequence
+
+```
diff --git a/src/content/docs/stage-1b-commands/suppliers-in-command-based.mdx b/src/content/docs/stage-1b-commands/suppliers-in-command-based.mdx
new file mode 100644
index 0000000..a4a88b1
--- /dev/null
+++ b/src/content/docs/stage-1b-commands/suppliers-in-command-based.mdx
@@ -0,0 +1,107 @@
+---
+title: Suppliers in Command-Based
+description: The basics of suppliers and their use in command-based programming
+prev: stage-1b-commands/command-based-kitbot
+next: stage-1b-commands/command-based-kitbot-pt2
+---
+
+import Aside from '../../../components/Aside.astro';
+
+### A Small Riddle
+
+Let's say that we define an `Intake` mechanism:
+
+```java stage1/snippets/src/main/java/sources/SuppliersInCommandBased.java#runAtThrottleDouble
+
+```
+
+And the following code in Robot.java:
+
+```java stage1/snippets/src/main/java/sources/SuppliersInCommandBased.java#incorrectCommand
+
+```
+
+
+
+When you run the code above, what happens?
+
+1. The intake does a little dance before rejoicing your coding skills.
+2. The intake runs at a speed dependent on how far up or down the joystick
+ on the left is pushed.
+3. The intake does nothing.
+
+
+ Solution
+ #3: The intake does nothing.
+
+
+### But Why?
+
+Recall that the constructor of the `Robot` class is called when the
+robot code is first loaded, not when the robot is enabled.
+At this point,
+your hands aren't even on the controller - so, the value of `controllerOutput` would be 0.
+
+When the robot is enabled, the value of `getLeftY()` will change; however, the value of the `throttle`
+parameter passed into the `runAtThrottle` command cannot, because it's value has already been determined
+before the command has even began.
+
+### Fixing the problem
+
+In java, we use a `DoubleSupplier` to represent a value of type `double` that is constantly changing.
+We call `getAsDouble()` to fetch its current value.
+
+```java stage1/snippets/src/main/java/sources/SuppliersInCommandBased.java#runAtThrottleSupplier
+
+```
+
+`DoubleSupplier` instances are created with the syntax of `() -> expression`, with the caveat that
+the expression must return a `double`.
+Calling `getAsDouble()` will evaluate that expression.
+
+```java stage1/snippets/src/main/java/sources/SuppliersInCommandBased.java#doubleSupplierExample
+
+```
+
+
+
+Here is how we would define and use a `DoubleSupplier` in our previous example:
+
+```java stage1/snippets/src/main/java/sources/SuppliersInCommandBased.java#correctCommand
+
+```
+
+### `BooleanSupplier`s and the `until()` Command Modifier
+
+You might recognize the `() ->` syntax used to define `DoubleSupplier`s.
+As it turns out,
+we define `Trigger`s with that syntax:
+
+```java stage1/snippets/src/main/java/sources/SuppliersInCommandBased.java#triggerBooleanSupplier
+
+```
+
+Notice, however, that the expression to the left of the `() ->` statement is
+a `boolean` instead of a `double`.
+As it turns out, the constructor for a `Trigger`
+takes a `BooleanSupplier`.
+
+
+
+`BooleanSupplier`s are used in one other place: adding stop conditions to commands.
+You can use the `until(BooleanSupplier)` method to add an arbitrary stop condition
+to a command:
+
+```java stage1/snippets/src/main/java/sources/SuppliersInCommandBased.java#untilModifier
+
+```
diff --git a/src/content/docs/stage-1b-commands/the-command-body.mdx b/src/content/docs/stage-1b-commands/the-command-body.mdx
new file mode 100644
index 0000000..b716a79
--- /dev/null
+++ b/src/content/docs/stage-1b-commands/the-command-body.mdx
@@ -0,0 +1,62 @@
+---
+title: The Body of a Command
+description: How the behavior of commands are written
+prev: stage-1b-commands/command-based-overview
+next: stage-1b-commands/commands-and-mechanisms
+---
+
+import Aside from '../../../components/Aside.astro';
+
+### Something Looks Familiar Here...
+
+If you've ever coded in Scratch, with blocks, or taken an intro to programming class,
+you've probably written software as a sequence of statements.
+Like so:
+
+```java stage1/snippets/src/main/java/sources/CommandBody.java#triangleLoop
+
+```
+
+(In case you were curious - this prints a triangle to the console.)
+
+From the previous section, you might remember that command-based programming,
+as a whole, cannot be represented with just a sequence of statements.
+However, individual commands (or robot actions) can.
+
+For instance, this command body runs a motor at full speed forever:
+
+```java stage1/snippets/src/main/java/sources/CommandBody.java#fullThrottleCmdBody
+
+```
+
+And this command body would rotate your robot by roughly 90 degrees, then stop:
+
+```java stage1/snippets/src/main/java/sources/CommandBody.java#rotate90CommandBody
+
+```
+
+Fundamentally, the commands (or robot actions) themselves can be represented with familiar
+programming structures: while loops, variables, method calls, and more.
+
+
+
+### The Meaning of `coroutine.yield();`
+
+`coroutine.yield()` is a special statement that must be called inside of while loops.
+It allows for commands to run in parallel, while ensuring that background tasks
+(like motor safety checks) are run.
+An anology would be the
+sips of water you take while you finish your homework.
+
+The `coroutine` object contains many other useful methods that can only be called
+inside of commands.
+Some of these will be mentioned in the following sections.
+
+
diff --git a/src/content/docs/stage-1b-commands/triggers.mdx b/src/content/docs/stage-1b-commands/triggers.mdx
new file mode 100644
index 0000000..f622c40
--- /dev/null
+++ b/src/content/docs/stage-1b-commands/triggers.mdx
@@ -0,0 +1,107 @@
+---
+title: Triggers and Scheduling
+description: How commands are scheduled, with and without triggers
+prev: stage-1b-commands/commands-and-mechanisms
+next: stage-1b-commands/commands-and-mechanisms-pt-2
+---
+
+import Aside from '../../../components/Aside.astro';
+
+### A quick recap
+
+Remember that `Trigger`s are the stimuli that trigger robot behavior, which we call `Command`s.
+We say that `Trigger`s "schedule" `Command`s.
+
+### How triggers are defined
+
+sources/Triggers, at their core, are methods that return a boolean - true for active, and false for inactive.
+You can fetch
+a trigger's active status with the `getAsBoolean()` method:
+
+```java stage1/snippets/src/main/java/sources/Triggers.java#motorTooFastTrigger
+
+```
+
+There are 2 ways triggers can schedule commands:
+
+1. `trigger.onTrue(Command)`: Schedules a command when a trigger switches from inactive to active.
+ In the following example, the `runAtThrottle` command will run once teleop mode is selected and enabled.
+
+```java stage1/snippets/src/main/java/sources/Triggers.java#teleopEnabledRobot
+
+```
+
+2. `trigger.whileTrue(Command)`: Identical to onTrue, but cancels the running command when the trigger becomes inactive again.
+ If `teleopEnabledTrigger.onTrue(...)` was changed to `teleopEnabledTrigger.whileTrue(...)`, disabling teleop mode
+ on the driver station will cancel the `runAtThrottle` command mid-run.
+
+
+
+### Controller-based sources/Triggers
+
+A common stimulus for robot behavior is the buttons on a controller.
+For instance, shooting balls
+while the x button on the operator controller is held.
+From section 1A, you might recall that fetching
+the state of a button can be done like so:
+
+```java stage1/snippets/src/main/java/sources/Triggers.java#xboxGetAButton
+
+```
+
+Where the `getAButton()` method returns `true` if the button is being held, and `false` otherwise.
+So, it's
+possible to create a trigger mapped to the a button like so:
+
+```java stage1/snippets/src/main/java/sources/Triggers.java#triggerAButton
+
+```
+
+However, this is a common enough usecase that the commands framework provides a special class,
+called `CommandXboxController`, that makes interfacing with controller buttons easier for command-based
+programming.
+
+```java stage1/snippets/src/main/java/sources/Triggers.java#commandXboxController
+
+```
+
+### Scheduling commands manually
+
+You can also decide to run a command immediately, bypassing triggers entirely, like so:
+
+```java stage1/snippets/src/main/java/sources/Triggers.java#scheduleAndPrint
+
+```
+
+Think of calling `schedule()` as ordering a robot to start washing the dishes
+without waiting for it to finish.
+In this case, the `println("Hello!")` statement
+will run before the autonomous command completes.
+
+
+
+Example Usage with `TimedRobot`:
+
+```java stage1/snippets/src/main/java/sources/Triggers.java#timedRobotExample
+
+```
+
+Example `OpMode` usage:
+
+```java stage1/snippets/src/main/java/sources/Triggers.java#opModeExample
+
+```