The FollowPathCommand
Writing a wrapper class to bridge FTCLib's Command Scheduler and PedroPathing.
20 minintermediate
By the end you can
- Understand why a wrapper class is necessary.
- Review the FollowPathCommand.java implementation from the team's repository.
- Understand how isFinished() works with PedroPathing tolerances and stall detection.
PedroPathing is incredible at driving the robot, but it doesn't know anything about FTCLib Commands. If you want to use a SequentialCommandGroup to drive to a position and then run your LaunchBallsCommand, FTCLib needs a way to know when PedroPathing is finished driving!
To solve this, our team uses a Wrapper Class. A wrapper class is a standard FTCLib CommandBase that simply "wraps" around PedroPathing's functions.
Writing the Wrapper
Let's look at FollowPathCommand.java directly from the Unearthed-Alberta repository.
- Open your project in Android Studio.
- Navigate to
TeamCode > src > main > java > org > firstinspires > ftc > teamcode > commands. - Create a new Java Class named
FollowPathCommand. - Copy the code below.
package org.firstinspires.ftc.teamcode.commands;
import android.util.Log;
import com.arcrobotics.ftclib.command.CommandBase;
import com.pedropathing.follower.Follower;
import com.pedropathing.paths.PathChain;
import com.qualcomm.robotcore.util.ElapsedTime;
public class FollowPathCommand extends CommandBase {
private final Follower follower;
private final PathChain path;
private final boolean holdEnd;
// Stall detection variables
private ElapsedTime stallTimer = new ElapsedTime();
private final ElapsedTime initializationTimer = new ElapsedTime();
private static final double STALL_VELOCITY_THRESHOLD = 0.5; // inches per second
private static final double STALL_TIMEOUT = 500; // milliseconds before giving up
private static final double END_TOLERANCE = 3.0; // Finish when 3 inches away
private static final double MINIMUM_RUN_TIME = 500.0; // Ensure at least 500ms of run time
private boolean initialized = false;
public FollowPathCommand(Follower follower, PathChain path, boolean holdEnd) {
this.follower = follower;
this.path = path;
this.holdEnd = holdEnd;
this.initialized = false;
}
@Override
public void initialize() {
// 1. Tell Pedro to start driving!
follower.followPath(path, holdEnd);
// 2. Force an update immediately so the state locks in
follower.update();
}
@Override
public void execute() {
if (!initialized) {
initializationTimer.reset();
stallTimer.reset();
initialized = true;
}
double currentVelocity = follower.getVelocity().getMagnitude();
// 3. Stall Detection: If we are moving faster than the threshold, reset the timer
if (currentVelocity > STALL_VELOCITY_THRESHOLD) {
stallTimer.reset();
}
}
@Override
public boolean isFinished() {
// SAFETY: Do not allow the command to finish if it hasn't been running for 500ms.
// This prevents race conditions where isBusy() is checked before the path latches.
if (initializationTimer.milliseconds() < MINIMUM_RUN_TIME) {
return false;
}
// 4. Finish Logic
double distanceRemaining = follower.getCurrentPath().getDistanceRemaining();
boolean closeEnough = distanceRemaining < END_TOLERANCE;
// Finish if Pedro says done, OR we are close enough, OR we stalled (hit a wall)
return !follower.isBusy() || closeEnough || stallTimer.milliseconds() > STALL_TIMEOUT;
}
@Override
public void end(boolean interrupted) {
if (stallTimer.milliseconds() > STALL_TIMEOUT) {
Log.w("Follow Path Command", "Path Stalled! Moving to next command.");
follower.breakFollowing(); // Stop the motors immediately
}
initialized = false;
}
}Breaking Down the Magic
Why is this file so complicated? Because the real world is messy!
follower.followPath(): This is called insideinitialize()to start the movement.- The "Latch" Bug: Sometimes, if you ask PedroPathing "Are you busy?" a millisecond after telling it to follow a path, it says "No" because it hasn't loaded the path yet! We fix this by calling
follower.update()immediately, and enforcing aMINIMUM_RUN_TIMEof 500ms before it's allowed to finish. - Stall Detection: If your robot drives into a wall or gets stuck on a game piece, PedroPathing will sit there forever trying to reach the end of the path, ruining your Autonomous. The
stallTimerconstantly checks your velocity. If your robot stops moving (< 0.5inches/sec) for more than500ms, it cancels the path and moves on to the next command! END_TOLERANCE: Just like we learned in the Advanced Control lesson, waiting for exact perfection takes too long. If the robot is within 3 inches of the target, we declare the path "finished" so the shooter can start spinning up early, saving precious time!
Check Your Understanding
Check yourself