Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@ public double getLeftY() {
return 0;
}

public double getRightX() {
return 0;
}

public Trigger a() {
return new Trigger(() -> false);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,8 @@ public XboxController(int id) {}
public boolean getAButton() {
return false;
}

public boolean getLeftBumper() {
return false;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
/*
* Copyright 2026 FRCSoftware
*
* SPDX-License-Identifier: BSD-3-Clause
*/
package sources;

import org.wpilib.command3.Command;
import org.wpilib.command3.Mechanism;
import org.wpilib.command3.Trigger;
import org.wpilib.driverstation.XboxController;

class SpotTheError {
private final XboxController xbox = new XboxController(0);
private final Intake intake = new Intake();

void triggerBindings() {
// [triggerCapturedValueBug]
boolean aButton = xbox.getAButton();
Trigger aButtonTrigger = new Trigger(() -> aButton);
aButtonTrigger.whileTrue(intake.runAtThrottle(0.5));
// [/triggerCapturedValueBug]

// [intakeOnTrueBug]
new Trigger(() -> xbox.getLeftBumper()).onTrue(intake.runAtThrottle(0.5));
// [/intakeOnTrueBug]

// [intakeWhileTrue]
new Trigger(() -> xbox.getLeftBumper()).whileTrue(intake.runAtThrottle(0.5));
// [/intakeWhileTrue]
}

void triggerBindingsFixed() {
// [triggerCapturedValueFix]
Trigger aButtonTrigger = new Trigger(() -> xbox.getAButton());
aButtonTrigger.whileTrue(intake.runAtThrottle(0.5));
// [/triggerCapturedValueFix]
}

// [intakeClass]
class Intake implements Mechanism {
private final ExampleMotor motor = new ExampleMotor();

Command runAtThrottle(double throttle) {
return run(coroutine -> {
while (true) {
motor.setThrottle(throttle);
coroutine.yield();
}
})
.named("Intake");
}
}
// [/intakeClass]

void intakeClassWithDefaultCmd() {
// [intakeClassWithDefaultCmd]
class Intake implements Mechanism {
private final ExampleMotor motor = new ExampleMotor();

public Intake() {
setDefaultCommand(runAtThrottle(0));
}

Command runAtThrottle(double throttle) {
return run(coroutine -> {
while (true) {
motor.setThrottle(throttle);
coroutine.yield();
}
})
.named("Intake");
}
}
// [/intakeClassWithDefaultCmd]
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
/*
* Copyright 2026 FRCSoftware
*
* SPDX-License-Identifier: BSD-3-Clause
*/
package sources;

import java.util.function.DoubleSupplier;
import org.wpilib.command3.Command;
import org.wpilib.command3.Mechanism;
import org.wpilib.command3.Scheduler;
import org.wpilib.command3.button.CommandXboxController;
import org.wpilib.drive.DifferentialDrive;
import org.wpilib.framework.OpModeRobot;
import org.wpilib.hardware.imu.OnboardIMU;

public class SpotTheErrorPt2 {
private final DifferentialDrive differentialDrive = null;
private final OnboardIMU imu = null;

class ArcadeDriveBug implements Mechanism {
// [arcadeDriveBug]
Command arcadeDrive(DoubleSupplier forwardThrottle, DoubleSupplier rotationThrottle) {
double forward = forwardThrottle.getAsDouble();
double rotation = rotationThrottle.getAsDouble();
return run(coroutine -> {
while (true) {
differentialDrive.arcadeDrive(forward, rotation);
coroutine.yield();
}
})
.named("Drive");
}
// [/arcadeDriveBug]
}

class ArcadeDriveFix implements Mechanism {
// [arcadeDriveFix]
Command arcadeDrive(DoubleSupplier forwardThrottle, DoubleSupplier rotationThrottle) {
return run(coroutine -> {
while (true) {
differentialDrive.arcadeDrive(
forwardThrottle.getAsDouble(), rotationThrottle.getAsDouble());
coroutine.yield();
}
})
.named("Drive");
}
// [/arcadeDriveFix]
}

// [robotPeriodicBug]
public class Robot extends OpModeRobot {
private final CommandXboxController xbox = new CommandXboxController(0);

public Robot() {
xbox.a().whileTrue(printHelloWorld());
}

@Override
public void robotPeriodic() {}

private Command printHelloWorld() {
return Command.noRequirements(coroutine -> {
while (true) {
System.out.println("Hello World!");
coroutine.yield();
}
})
.named("Hello World!");
}
}
// [/robotPeriodicBug]

class RobotFixed extends OpModeRobot {
private final Drivetrain drivetrain = new Drivetrain();

// [robotPeriodicFix]
@Override
public void robotPeriodic() {
Scheduler.getDefault().run();
}
// [/robotPeriodicFix]
}

class RotateInPlaceBug implements Mechanism {
// [rotateInPlaceBug]
Command rotateInPlace(double angleDegrees) {
double targetAngle = imu.getRotation2d().getDegrees() + angleDegrees;
return run(coroutine -> {
while (imu.getRotation2d().getDegrees() < targetAngle) {
differentialDrive.arcadeDrive(0.0, 0.2);
coroutine.yield();
}
})
.named("RotateInPlace");
}
// [/rotateInPlaceBug]
}

class RotateInPlaceFix implements Mechanism {
// [rotateInPlaceFix]
Command rotateInPlace(double angleDegrees) {
return run(coroutine -> {
double targetAngle = imu.getRotation2d().getDegrees() + angleDegrees;
while (imu.getRotation2d().getDegrees() < targetAngle) {
differentialDrive.arcadeDrive(0.0, 0.2);
coroutine.yield();
}
})
.named("RotateInPlace");
}
// [/rotateInPlaceFix]
}

class Drivetrain implements Mechanism {
void periodic() {}

Command arcadeDrive(DoubleSupplier forwardThrottle, DoubleSupplier rotationThrottle) {
return null;
}
}
}
8 changes: 8 additions & 0 deletions src/config/sidebarConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,10 @@ export const sidebarSections: Record<string, SidebarSection[]> = {
label: 'Commands & Mechanisms, Pt. 2',
slug: 'learning-course/stage1/stage1b/commands-and-mechanisms-pt2',
},
{
label: 'Bonus: Spot the Error',
slug: 'learning-course/stage1/stage1b/spot-the-error',
},
{
label: 'Exercise - Kitbot Rewrite, Pt. 1',
slug: 'learning-course/stage1/stage1b/command-based-kitbot',
Expand All @@ -149,6 +153,10 @@ export const sidebarSections: Record<string, SidebarSection[]> = {
label: 'Exercise - Kitbot Rewrite, Pt. 2',
slug: 'learning-course/stage1/stage1b/command-based-kitbot-pt2',
},
{
label: 'Bonus: Spot the Error, Pt 2',
slug: 'learning-course/stage1/stage1b/spot-the-error-pt2',
},
],
},
],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
title: Exercise - Kitbot Rewrite, Pt. 2
description: Rewriting the kitbot code in stage 1A to command-based
prev: learning-course/stage1/stage1b/suppliers-in-command-based
next: false
next: learning-course/stage1/stage1b/spot-the-error-pt2
codeRegionSources:
default: stage1/stage1b/snippets/src/main/java/sources/CommandBasedKitbotPt2.java
---
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
title: Commands and Mechanisms, Part 2
description: Intermediate-level uses of commands and mechanisms
prev: learning-course/stage1/stage1b/triggers
next: learning-course/stage1/stage1b/command-based-kitbot
next: learning-course/stage1/stage1b/spot-the-error
codeRegionSources:
default: stage1/stage1b/snippets/src/main/java/sources/CommandsAndMechsPt2.java
---
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
---
title: Spot the Error, Part 2
description: A review exercise for stage 1B
prev: learning-course/stage1/stage1b/command-based-kitbot-pt2
next: false
codeRegionSources:
default: stage1/stage1b/snippets/src/main/java/sources/SpotTheErrorPt2.java
---

### Exercise 1

"Hello World!" doesn't show up in the console while the a button is pressed.
Can you spot the error?

```java #robotPeriodicBug

```

<details class="accordion">
<summary>Reveal</summary>
The scheduler never runs, so scheduled commands never execute.
Call `Scheduler.getDefault().run()` inside `robotPeriodic()`:

```java #robotPeriodicFix

```

</details>

### Exercise 2

This command is supposed to drive the robot using the joystick's forward and rotation axes.

```java #arcadeDriveBug

```

Can you spot the error?

<details class="accordion">
<summary>Reveal</summary>
The supplier values are read once, before the command starts running.
The robot drives at the same speed and rotation forever, even as the joystick moves.
Read the values from the suppliers inside the loop instead:

```java #arcadeDriveFix

```

</details>

### Exercise 3

This command is supposed to rotate the robot 90 degrees clockwise from its starting position.

```java #rotateInPlaceBug

```

Can you spot the error?

<details class="accordion">
<summary>Reveal</summary>
The target angle is read once, before the command starts running.
If the robot's heading changes before the command runs, it will rotate to the wrong angle.
Read the target angle from the IMU when the command starts running instead:

```java #rotateInPlaceFix

```

</details>
Loading