Skip to content
CrocoDocs
Starter GuideProgrammingEngineeringDesignPortfolio
Documentation

Introduction

IntroductionBIOBUZZ game overview

Getting started

Getting startedBlocksOnBot JavaAndroid StudioYour first OpModeJava basicsMotors and servosNonblocking codeDebugging robot code

Control systems

Control systemsJoystick mappingField-centric mecanum drivePID controlFeedforwardFlywheel velocity controlSystem identificationMotion profilingKalman filterLow-pass filter

Autonomous

AutonomousTimed and encoder movementOdometryLocalization troubleshootingMotion planningReliable autonomous sequencesPure PursuitSensor fusionAuto to TeleOp handoff

Season topics

Season topicsProjectile mathematicsTurret aimingShooting on the move

Code practices

Code practicesNaming conventionsCode organizationLoop timing and bulk readsFormattingComments and documentationTeam collaboration

Libraries

LibrariesNextFTCFTCLibSolversLibPedro PathingRoad RunnerFTC DashboardMercurial v2Mercurial v1PanelsSloth

Community software

Community softwareBlaze FTCOpenLoopHive Vision

Sensors and vision

Vision overviewVision basicsLimelight 3A
Starter Guide
Engineering
Design
Portfolio
Appearance

Auto to TeleOp handoff

Carry a pose between OpModes without accepting a stale position or resetting the field heading

Save the final pose, then deliberately choose whether should use it. A pose left over from yesterday's practice is worse than an explicit request to set the starting position.

What survives

SDK 12 exposes OpMode.blackboard, a map for values shared between in the same app process. It is not storage across an app restart or reboot. Static fields have the same persistence limitation.

Store a snapshot of numbers and their meaning: position, heading, coordinate frame, and when the estimate was captured. Do not carry , camera, follower, or OpMode objects into a new lifecycle. the new OpMode's devices normally.

The snapshot must be the measured final estimate, not the route's requested endpoint. If autonomous stopped early or localization failed, withhold the handoff.

Snapshot helper

Save this helper as PoseHandoff.java in TeamCode. It uses FTC SDK 12's blackboard and the same field frame as the Pedro 3 guide: at the bottom-left of the field diagram, +X right, +Y up, distances in inches, and counterclockwise heading in radians with zero along +X. Both OpModes must use that exact frame. Convert another localizer's values before saving; the helper does not perform a coordinate conversion.

package org.firstinspires.ftc.teamcode;

import com.qualcomm.robotcore.eventloop.opmode.OpMode;

public final class PoseHandoff {
    private static final String KEY = "crocodocs.pose.v1";
    private static final String FRAME = "pedro-bottom-left-in-ccw-rad";

    public static final class Snapshot {
        public final double xInches, yInches, headingRadians;
        public final String frame;
        public final long capturedNanos;

        private Snapshot(double x, double y, double heading) {
            xInches = x;
            yInches = y;
            headingRadians = heading;
            frame = FRAME;
            capturedNanos = System.nanoTime();
        }
    }

    public static void clear() {
        OpMode.blackboard.remove(KEY);
    }

    public static void save(double xInches, double yInches, double headingRadians) {
        if (!Double.isFinite(xInches) || !Double.isFinite(yInches)
                || !Double.isFinite(headingRadians)) {
            clear();
            return;
        }
        OpMode.blackboard.put(KEY, new Snapshot(xInches, yInches, headingRadians));
    }

    public static Snapshot take(double maxAgeSeconds) {
        Object stored = OpMode.blackboard.remove(KEY); // Consume at most once.
        if (!(stored instanceof Snapshot) || !Double.isFinite(maxAgeSeconds)
                || maxAgeSeconds <= 0) return null;
        Snapshot pose = (Snapshot) stored;
        double age = (System.nanoTime() - pose.capturedNanos) / 1e9;
        if (!FRAME.equals(pose.frame) || age < 0 || age > maxAgeSeconds) return null;
        return pose;
    }
}

Clear the old snapshot when beginning an autonomous run. Save only while its estimate is valid, updating the capture time when new localization data arrives. Do not refresh the timestamp by repeatedly saving an old cached pose after the localizer fails.

In TeleOp initialization, call PoseHandoff.take(maxAgeSeconds) once. Choose the age limit from the expected handoff time; a value such as 30 seconds is a practice setting, not proof that the robot stayed still. Display the result before enabling pose-dependent controls.

Use it with Pedro 3

These fragments use the follower created in the Pedro guide. Import com.pedropathing.math.Pose and com.pedropathing.api.PoseFactory in the calling files.

In autonomous, clear the old value during initialization. Save after a successful final move and a fresh, valid localization update, before stopping the follower:

// AUTO initialization:
PoseHandoff.clear();

// AUTO's successful finish branch, with valid localization:
Pose finished = follower.pose();
PoseHandoff.save(finished.x(), finished.y(), finished.heading());

Do not put the save unconditionally in finally: that branch also runs after STOP, a , or a failure. An intentional early handoff can still be useful, but it needs the same validity checks.

In TeleOp initialization, after creating its follower and before accepting pose-dependent commands:

PoseHandoff.Snapshot saved = PoseHandoff.take(30.0);
if (saved != null) {
    follower.setPose(PoseFactory.radians().of(
            saved.xInches, saved.yInches, saved.headingRadians));
    telemetry.addLine("AUTO pose restored; confirm robot has not moved.");
} else {
    telemetry.addLine("No recent AUTO pose. Set a known starting pose.");
}
telemetry.update();

The missing-pose branch reports the condition; it does not establish a pose. Keep automatic aiming and navigation disabled until the driver confirms a measured reference. Continue the follower's normal update lifecycle after initialization.

Restore heading consistently

Set the new localizer's initial X/Y and heading from the accepted snapshot using its installed API. If the new localizer reads raw yaw, align that yaw with the saved field heading:

θfield=wrap⁡(θsaved+θraw−θraw,start)\theta_{field}=\operatorname{wrap}(\theta_{saved}+\theta_{raw}-\theta_{raw,start})θfield​=wrap(θsaved​+θraw​−θraw,start​)

Capture the raw yaw and baselines together before movement. Do not also reset the field heading to zero. If your library establishes its own heading offset when you set the pose, use that once rather than applying the offset again.

The robot may move between OpModes. A saved estimate cannot observe a lift, push, or rotation while the new localizer is inactive. Confirm the physical pose or choose a measured restart reference when that happens.

TeleOp without autonomous

A missing or expired snapshot should lead to a normal practice path: place the robot at a known pose and explicitly initialize there. Keep ordinary robot-centric driving available if the team supports it, while leaving automatic aiming or field navigation disabled until localization is established.

Test a normal handoff, TeleOp started directly, a stale snapshot, an app restart, and a robot moved after AUTO. At headings other than zero, field-centric driving should retain the intended field direction. Repeated TeleOp initialization should not silently reuse a consumed snapshot.

Sources

  • FTC SDK 12 RobotCore source — OpMode.blackboard and lifecycle.
  • FIRST: universal IMU interface and yaw reset
  • Pedro Pathing: coordinates — field origin, axes, and heading convention.
  • Pedro Pathing: pose factory — explicit angle units when creating a pose.

Sensor fusion

Combine robot measurements while accounting for their limits

Season topics

Apply robot programming and physics to game tasks, starting with projectile motion and shooting while driving.

On this page

What survivesSnapshot helperUse it with Pedro 3Restore heading consistentlyTeleOp without autonomousSources