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.

Odyssey uses a Localizer interface as its only connection to the robot’s position estimate. Any odometry source — two-wheel dead reckoning, three-wheel pods, VSLAM, or a vision pipeline — can feed the Follower as long as it implements this interface. The included PinpointLocalizer wraps the GoBILDA Pinpoint odometry computer and is the recommended starting point for most FTC teams.

The Localizer interface

public interface Localizer {
    Pose2d getPose();
    void update();
}
getPose() must return the robot’s current position in mm and heading in radians as a Pose2d. update() is called once per loop by the user to poll the hardware — Odyssey never calls update() internally, so the timing is entirely under your control.

Setting up PinpointLocalizer

The Pinpoint is declared in the hardware map under the name "localizer". Call resetPosAndIMU() during init() to zero the odometry at the robot’s starting position before the match begins.
GoBildaPinpointDriver pinpoint = hardwareMap.get(GoBildaPinpointDriver.class, "localizer");
pinpoint.resetPosAndIMU();
PinpointLocalizer localizer = new PinpointLocalizer(pinpoint);
PinpointLocalizer.getPose() reads three values from the Pinpoint driver and packages them into a Pose2d:
double x       = pinpointDriver.getPosX(DistanceUnit.MM);
double y       = pinpointDriver.getPosY(DistanceUnit.MM);
double heading = pinpointDriver.getHeading(AngleUnit.RADIANS);
return new Pose2d(x, y, heading);
Pose2d normalises the heading to the range (−π, π] via MathUtils.normalizeAngle() on construction, so you never need to clamp the heading yourself.

Verifying with the Push Test OpMode

Before running any autonomous path, verify that the localizer tracks correctly using the Push Test Odometry TeleOp (PushTest.java):
1

Deploy and start

Build the TeamCode module and start the Push Test Odometry TeleOp. The Driver Station telemetry will show X, Y, and HEADING.
2

Push forward

Push the robot straight forward (toward the +Y wall). Confirm that Y increases and X stays near zero. If X changes instead of Y, the Pinpoint pods are swapped.
3

Push sideways

Push the robot to the right. Confirm that X increases. If the sign is wrong, the X pod direction is reversed in the Pinpoint configuration.
4

Rotate in place

Rotate the robot counter-clockwise by hand. HEADING should increase toward . If it decreases, the IMU orientation needs to be flipped in the GoBILDA Pinpoint setup.
Once all three axes respond correctly, the localizer is ready for autonomous use.

Implementing a custom Localizer

If you are using a different odometry source — for example, three dead-wheel pods through Road Runner’s kinematics, or AprilTag vision — implement Localizer directly:
public class MyLocalizer implements Localizer {

    @Override
    public Pose2d getPose() {
        // Return the current robot pose from your odometry source.
        // x and y must be in mm; heading must be in radians.
        return new Pose2d(x, y, heading);
    }

    @Override
    public void update() {
        // Poll odometry hardware and update internal state.
        // This is called by your OpMode, not by Odyssey.
    }
}
Pass an instance of your custom localizer to the Follower constructor in place of PinpointLocalizer. No other changes are required.

Coordinate units

Pose2d stores position in whatever units you provide, but the Follower compares pose positions directly against path control points. Use millimetres consistently throughout — all Vector2d control points in your BezierCurve definitions and all positions returned by getPose() must be in the same unit.
Odyssey expects the same coordinate units as your path control points. If your path uses mm (the FTC field is 3657.5 mm × 3657.5 mm), your Localizer must return mm. Mixing units will cause the follower to report wildly wrong distances and the PID controllers will saturate.
Call pinpoint.resetPosAndIMU() in the init() block of your autonomous OpMode, not in start() or the loop. This zeros the position at the robot’s physical starting tile, giving you a clean reference frame that matches your path’s coordinate origin.

Build docs developers (and LLMs) love