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

Limelight 3A

Read a configured pipeline, reject stale results, and separate target aiming from field localization

Start by displaying a Limelight result while the stays still. A target visible in the camera's web interface has not yet established that TeamCode receives the right pipeline or a current result.

Setup

Use a Limelight 3A with its setup procedure and an SDK that includes its driver; the example below uses FTC SDK 12. Connect and configure the device as limelight. For initial computer setup, use the manufacturer's Hardware Manager or USB browser connection. The robot's Wi-Fi connection and the camera's direct USB connection are different access paths.

For the preview below, create a colour or AprilTag pipeline in the camera's web interface, note its index, and provide a clearly visible target. These pipelines supply the target offsets used by the example. A neural detector or Python pipeline may need its own result fields; installing a model does not change which pipeline TeamCode selects.

For the BIOBUZZ neural-model workflow, follow Hive Vision. For webcam processing on the , use VisionPortal instead of the Limelight driver.

Stationary preview

Save this complete file as LimelightPreview.java. Set PIPELINE to the configured slot. It reads and reports results without commanding or .

package org.firstinspires.ftc.teamcode;

import com.qualcomm.hardware.limelightvision.LLResult;
import com.qualcomm.hardware.limelightvision.Limelight3A;
import com.qualcomm.robotcore.eventloop.opmode.LinearOpMode;
import com.qualcomm.robotcore.eventloop.opmode.TeleOp;

@TeleOp(name = "Limelight Preview", group = "Tests")
public class LimelightPreview extends LinearOpMode {
    private static final int PIPELINE = 0;
    private static final long MAX_AGE_MS = 250; // Initial diagnostic limit.

    @Override
    public void runOpMode() {
        Limelight3A limelight = hardwareMap.get(Limelight3A.class, "limelight");
        limelight.setPollRateHz(50);
        limelight.pipelineSwitch(PIPELINE);
        limelight.start();
        double lastFrameTimestamp = Double.NaN;
        long frameAdvancedAt = 0;
        try {
            waitForStart();
            while (opModeIsActive()) {
                long now = System.nanoTime();
                LLResult result = limelight.getLatestResult();
                double timestamp = result == null ? Double.NaN : result.getTimestamp();
                if (Double.isFinite(timestamp) && timestamp != lastFrameTimestamp) {
                    lastFrameTimestamp = timestamp;
                    frameAdvancedAt = now;
                }
                long receiptAgeMs = result == null ? Long.MAX_VALUE : result.getStaleness();
                boolean usable = result != null && result.isValid()
                        && result.getPipelineIndex() == PIPELINE
                        && receiptAgeMs >= 0 && receiptAgeMs <= MAX_AGE_MS
                        && frameAdvancedAt != 0
                        && now - frameAdvancedAt <= MAX_AGE_MS * 1_000_000L
                        && Double.isFinite(result.getTx()) && Double.isFinite(result.getTy());
                telemetry.addData("Target", usable ? "Usable" : "Unavailable / stale / wrong pipeline");
                if (result != null) {
                    telemetry.addData("Pipeline", result.getPipelineIndex());
                    telemetry.addData("Receipt age (ms)", receiptAgeMs);
                    telemetry.addData("Camera timestamp", "%.3f", timestamp);
                }
                if (usable) {
                    telemetry.addData("tx (deg)", "%.2f", result.getTx());
                    telemetry.addData("ty (deg)", "%.2f", result.getTy());
                }
                telemetry.update();
                idle();
            }
        } finally {
            limelight.stop();
        }
    }
}

start() begins polling. Call setPollRateHz() before it, and stop polling during cleanup. Polling at 50 Hz does not make a slow detector produce 50 distinct frames each second.

SDK 12's getLatestResult() can retain a previous result when communication fails. Check validity and age rather than relying on a non-null object. getStaleness() measures time since receipt on the Control Hub, not the entire image-to-action delay. The camera timestamp check also notices a frame that stops advancing while results are still retrieved. The 250 ms limit is for diagnosis; choose a tighter or looser limit from measured motion, processing rate, and allowable aiming error.

Cover the target, change pipeline, stop the pipeline, and disconnect the camera during this stationary test. The usable state should disappear. In a motion controller, clear the automatic correction and inhibit feeding when data becomes unavailable; do not keep the last steering output indefinitely.

Aiming and localization

Aiming can use a target's image error without knowing the robot's field X/Y. tx and ty are angular image offsets in degrees. Verify the sign with a small manual motion before closing the loop. If several targets are visible, select the intended ID or class rather than blindly aiming at whichever result appears first.

Field localization additionally needs a valid target map and the camera's pose on the robot. MegaTag1 estimates pose from the configured tags. MegaTag2 also takes a robot yaw aligned with that map through updateRobotOrientation(); convert radians to degrees at that API boundary.

ValueMeaningBoundary to check
tx, tyAngular target offsets in the camera image, in degreesTarget selection, sign, and camera orientation
Limelight botposeEstimated robot pose in the configured field mapMap , axes, camera mounting, and returned units
Follower poseRobot reference point in the path library's field frameConvert the entire pose before applying a correction

An image offset is not an X/Y field position. A valid aiming result also does not guarantee a usable mapped pose.

Inspect the returned Pose3D position unit instead of treating it as inches. The SDK 12 Limelight driver constructs pose positions in metres. Convert units, origin, axis directions, and heading together before passing a pose to a follower. Test several known field points and headings, with correction disabled, to expose a bad transform.

Camera and target movement

A fixed camera mount has a constant robot-to-camera transform. A camera on a turret has a changing one. Its field-pose calculation needs the camera orientation and offset for the image capture time; configuring one fixed offset does not account for turret motion.

Likewise, a tag on a moving structure does not have one permanent field pose. Only use it for global relocalization when its position and orientation are known for that observation. Target-relative aiming may still be useful without a valid global map. For tilted cameras, compose the full 3D transform rather than subtracting one pitch angle from every detection.

Before applying corrections, reject invalid or stale frames, implausible jumps, and observations that violate the map assumptions. Apply each accepted frame once. Reusing one frame as several independent measurements makes a fusion estimate overconfident. Use capture-time alignment when moving; see Sensor fusion.

Fault isolation

SymptomCheck
Web preview works; TeamCode has no targetDevice name, polling, pipeline index, and result type
Model file cannot be selected or loadedThe camera's required model/export format and the model's own installation guide
Camera works until a heavy model runsStatus, power, cabling, processing load, and cooling; capture a log before changing several settings
Two cameras work alone but not togetherDevice discovery, supported topology, and available USB power; verify the manufacturer's multi-device support
Target correction jumps when the robot turnsCamera frame, turret angle, units, timestamp, and mounting transform

For a first automatic turret, establish odometry-based aiming, then compare a camera correction against the same stationary targets. Keep the accepted source of aim and the reason for rejecting a measurement visible in telemetry.

Sources

  • Limelight: 3A quick start
  • Limelight: FTC programming API
  • FTC SDK 12 Hardware source — Limelight3A polling and LLResult timestamps, units, and validity.
  • FIRST: AprilTag reference frames and pose

Vision basics

Learn what a camera pipeline measures before writing robot decisions

EasyOpenCV basics

Open a camera and understand how a custom OpenCV pipeline receives frames

On this page

SetupStationary previewAiming and localizationCamera and target movementFault isolationSources