Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/saquer916/odyssey/llms.txt

Use this file to discover all available pages before exploring further.

Follower is Odyssey’s main runtime class. Each call to update() queries the localizer for the current robot pose, projects it onto the path to find where the robot is, reads the target velocity from the profile, applies PID corrections, and returns a DriveSignal ready to be passed to MecanumDrive.drive(). Package: org.firstinspires.ftc.teamcode.odyssey.follower

Constructor

public Follower(
    Path path,
    Localizer localizer,
    VelocityProfile velocityProfile,
    PIDController pidTranslational,
    PIDController pidHeading,
    double minDriveSpeed,
    double floorCutoff
)
path
Path
required
The cubic Bézier Path to follow. Follower queries it for point positions, tangent vectors, curvature, and centripetal vectors on every update() call.
localizer
Localizer
required
A Localizer implementation that provides the current robot Pose2d. Follower calls localizer.getPose() on every update() — you must call localizer.update() yourself each loop before calling follower.update().
velocityProfile
VelocityProfile
required
Pre-computed trapezoidal velocity profile for the path. Follower calls getTargetVelocity() and getTargetTangentialAcceleration() to obtain the commanded speed and tangential feedforward at the current path distance.
pidTranslational
PIDController
required
Corrects lateral offset from the path. The setpoint should be 0; the input passed at runtime is the negative magnitude of the vector from the robot’s position to the nearest path point (-offset.getMagnitude()). The output is added to the drive vector in world-frame before the robot-frame rotation is applied.
pidHeading
PIDController
required
Corrects heading error. The setpoint should be 0; the input passed at runtime is the negative normalized angle difference between the reference heading and the robot heading (-normalizeAngle(referenceHeading - poseHeading)). The output is forwarded as the turn component of the returned DriveSignal.
minDriveSpeed
double
required
Minimum drive speed in mm/s. When the velocity profile returns a speed lower than this value, minDriveSpeed is used instead — keeping the robot moving when the profile speed is very low near the start of the path. This floor is disabled once distanceRemaining falls below floorCutoff.
floorCutoff
double
required
Distance remaining in mm below which minDriveSpeed ceases to apply. Setting this to a small positive value (e.g. 50.0) lets the robot decelerate naturally to a full stop near the end of the path rather than being held at minimum speed.

Methods

update(double currentTime)DriveSignal

public DriveSignal update(double currentTime)
The main control loop method. Must be called every loop iteration after localizer.update(). Pass the current elapsed time in seconds — both PID controllers use this value to derive dt for derivative and integral calculations.
return
DriveSignal
A DriveSignal containing the robot-frame forward velocity, strafe velocity, forward acceleration feedforward, strafe acceleration feedforward, and heading correction. Pass it directly to MecanumDrive.drive().
What update() does — step by step:
  1. Get pose — calls localizer.getPose() to read the current Pose2d.
  2. Project onto path — calls path.getDistanceOnPath(pose.getPosition()) to find the arc-length distance of the closest point on the path.
  3. Get reference pose — calls path.getPointOnPath(distance) to obtain the target position and heading at that arc-length.
  4. Compute commanded speed — calls velocityProfile.getTargetVelocity(distance); if distanceRemaining > floorCutoff, clamps the result to at least minDriveSpeed via Math.max(profileSpeed, minDriveSpeed).
  5. Compute translational PID correction — subtracts the robot position from the reference position to get an offset vector, then feeds its negative magnitude into pidTranslational.getOutput(). The normalized offset direction is scaled by this output to produce a correction vector.
  6. Compute drive vector — normalizes the path tangent at the current distance and scales it by the commanded speed.
  7. Compute heading PID correction — normalizes the angle difference between the reference heading and the robot heading, feeds its negative into pidHeading.getOutput(), and stores the result as the turn component.
  8. Compose robot-frame velocity vector — sums the translational correction and the drive vector, then rotates the result by -pose.getHeading() to convert from world frame to robot frame.
  9. Compute acceleration feedforward — combines a centripetal vector (v² × κ in the centripetal direction) and a tangential vector (getTargetTangentialAcceleration() along the tangent), then rotates the sum into robot frame.
  10. Return DriveSignal — packages (velocityVector.x, velocityVector.y, accelVector.x, accelVector.y, heading) into a new DriveSignal.

getDistanceRemaining()double

public double getDistanceRemaining()
Returns the arc-length distance remaining to the end of the path in mm. This value is cached from the most recent update() call — it reuses the getDistanceOnPath() result already computed that iteration so no duplicate projection is performed.
return
double
Remaining path distance in mm. Valid only after at least one call to update().
getDistanceRemaining() returns 0.0 before the first update() call because lastDistanceRemaining is initialized to 0. Always call update() at least once before using this value as a loop condition.

Usage example

ElapsedTime timer = new ElapsedTime();

while (opModeIsActive() && follower.getDistanceRemaining() > 20.0) {
    localizer.update();
    DriveSignal signal = follower.update(timer.seconds());
    drive.drive(signal);
}

// Explicitly stop the robot after the path completes
drive.drive(new DriveSignal(0, 0, 0, 0, 0));
Always call localizer.update() before follower.update(). Follower reads the robot pose directly from the Localizer interface by calling getPose() — if update() has not been called first, the pose will be stale by one loop iteration, which degrades tracking accuracy at high speeds.

Build docs developers (and LLMs) love