| name | limelight3a |
| description | Expert guidance for using Limelight 3A vision camera in FTC robotics. Use when working with AprilTag detection, robot localization, MegaTag, pipeline configuration, coordinate transformations, or vision-based targeting. |
Limelight 3A FTC Vision Skill
This skill provides comprehensive guidance for using the Limelight 3A vision camera in FTC robotics for AprilTag detection, robot localization, and vision-based targeting.
Overview
The Limelight 3A is a compact USB vision camera optimized for FTC with:
- OV5647 sensor: 640×480 @ 90 FPS
- Field of View: 54.5° horizontal, 42° vertical
- AprilTag tracking with MegaTag/MegaTag2 robot localization
- 10 hot-swappable pipelines for different vision tasks
- Web-based configuration UI at
http://limelight.local:5801
- USB-C connection to Control Hub USB 3.0 port
- No LED illumination (rely on ambient light or external LEDs)
Power: 4.1V-5.75V via USB, max 4W consumption
Weight: 0.20 lb
Dimensions: 2.839" × 1.894" × 0.661"
FTC API Overview
Initialization
import com.qualcomm.hardware.limelightvision.Limelight3A
import com.qualcomm.hardware.limelightvision.LLResult
import com.qualcomm.hardware.limelightvision.LLResultTypes
val limelight = hardwareMap.get(Limelight3A::class.java, "limelight")
limelight.pipelineSwitch(0)
limelight.setPollRateHz(100)
limelight.start()
Key Limelight3A Methods
| Method | Description |
|---|
pipelineSwitch(int) | Switch between pipelines 0-9 |
getLatestResult() | Get current LLResult with all detection data |
updateRobotOrientation(double yaw) | Provide IMU heading for MegaTag2 fusion |
updatePythonInputs(double[]) | Send data to Python SnapScript pipelines |
setPollRateHz(int) | Set polling frequency (default 100 Hz) |
start() | Begin vision processing |
captureSnapshot(String) | Save debug images |
deleteSnapshots() | Clear stored snapshots |
LLResult Object
Always check validity first:
val result = limelight.latestResult
if (result != null && result.isValid) {
}
IMPORTANT: getLatestResult() always returns an LLResult object, but isValid() will be false if no valid targets are detected. Always check isValid() before accessing data.
Basic Targeting Data:
result.tx
result.ty
result.ta
result.isValid
result.getStaleness()
result.pipelineIndex
Robot Localization (MegaTag):
result.botpose
result.botpose_MT2
result.botposeTagCount
val pose = result.botpose
pose.position.x
pose.position.y
pose.position.z
pose.orientation.yaw
pose.orientation.pitch
pose.orientation.roll
AprilTag Detection:
result.fiducialResults
for (tag in result.fiducialResults) {
tag.fiducialId
tag.targetXDegrees
tag.targetYDegrees
tag.targetArea
tag.robotPose
tag.targetPose
}
Other Result Types:
result.colorResults
result.barcodeResults
result.classifierResults
result.detectorResults
result.pythonOutput
Pipeline Configuration
Accessing Web UI
- Connect Limelight via USB to Control Hub
- Connect Driver Station to Control Hub WiFi
- Navigate to
http://limelight.local:5801 in browser
- Or use Limelight Hardware Manager application
Pipeline Types
Limelight 3A supports 10 unique pipelines (0-9):
- AprilTag Pipelines - Fiducial marker detection
- Color Pipelines - Color blob tracking (90 FPS)
- Neural Network Pipelines - CPU inference (~10 FPS)
- Python Pipelines - Custom OpenCV processing
- Barcode Pipelines - QR code/barcode reading
AprilTag Pipeline Configuration
Input Tab Settings:
| Setting | Recommended Value | Notes |
|---|
| Resolution | 640×480 | Higher = better 3D accuracy, lower FPS |
| Exposure | Low (start 1-2 ms) | Minimize motion blur |
| Sensor Gain | 15 | Increase brightness without longer exposure |
| Black Level | 0 | Reduce arena lights/bright spots |
Fiducial Tab Settings:
| Setting | Value | Notes |
|---|
| Family | AprilTag Classic 36h11 | FTC standard |
| Tag Size | Varies by game | DECODE: 50mm for samples, etc. |
| Detector Downscale | 1.0-2.0 | Higher = more FPS, less range |
| Quality Threshold | Default | Filters spurious detections |
| ID Filters | Comma-separated IDs | e.g., "1,2,3,21,22,23" |
3D Settings:
- Enable "Full 3D" mode for robot localization
- Configure camera pose relative to robot center
- Upload field map with AprilTag locations
Pipeline Switching
Switch pipelines dynamically for different tasks:
limelight.pipelineSwitch(0)
limelight.pipelineSwitch(1)
limelight.pipelineSwitch(2)
Robot Localization with MegaTag
MegaTag vs MegaTag2
MegaTag 1 (botpose):
- Uses only AprilTag detections
- No IMU fusion
- Works with 1+ tags visible
- More stable with multiple tags
MegaTag2 (botpose_MT2):
- Fuses AprilTag data with IMU
- Requires
updateRobotOrientation(yaw) calls
- More accurate with IMU fusion
- Resilient to temporary tag loss
Recommendation: Use MegaTag2 for best results in FTC.
Setting Up Localization
1. Configure Camera Pose in Web UI:
- Navigate to "3D" tab in AprilTag pipeline
- Enter camera position relative to robot center
- Example: X=0, Y=0, Z=0.2m (20cm above center)
- Enter camera orientation (pitch, yaw, roll)
2. Upload Field Map:
- Use pre-made FTC field maps from Limelight
- Or create custom map with AprilTag positions
- Upload via "Field Map" button in web UI
3. Enable in Code:
limelight.updateRobotOrientation(imu.robotYawPitchRollAngles.getYaw(AngleUnit.DEGREES))
val result = limelight.latestResult
if (result != null && result.isValid) {
if (result.botposeTagCount >= 2) {
val pose = result.botpose_MT2
val x = pose.position.x
val y = pose.position.y
val heading = Math.toRadians(pose.orientation.yaw)
}
}
Coordinate System Transformations
Limelight Coordinate System (from camera perspective):
- X-axis: Right is positive
- Y-axis: Down is positive
- Z-axis: Forward (away from camera) is positive
FTC Field Coordinate System (standard):
- X-axis: Varies by alliance
- Y-axis: Varies by alliance
- Heading: Varies by starting orientation
Common Transformation Pattern (from this codebase):
val botX = -botpose.position.y
val botY = botpose.position.x
val botRot = ((botpose.orientation.yaw * Math.PI) / 180.0) - Math.PI
Important: Always test coordinate transformations empirically. Place robot at known position, read Limelight pose, and adjust transformation until it matches.
Integration Patterns
Pattern 1: Motif Detection (Game Element Patterns)
Detect specific AprilTag IDs to determine game element configuration:
enum class Motif {
GPP,
PGP,
PPG
}
fun detectMotif(fiducialResults: List<LLResultTypes.FiducialResult>): Motif {
var currentMotif = Motif.GPP
for (tag in fiducialResults) {
currentMotif = when (tag.fiducialId) {
21 -> Motif.GPP
22 -> Motif.PGP
23 -> Motif.PPG
else -> currentMotif
}
}
return currentMotif
}
Pattern 2: Continuous Localization Update
Update robot odometry with vision data:
class LimelightIO(val limelight: Limelight3A) {
var localization = Pose2D(DistanceUnit.METER, 0.0, 0.0, AngleUnit.RADIANS, 0.0)
private set
var localizationTags = 0
private set
fun run(imuYaw: Double) {
limelight.updateRobotOrientation(imuYaw)
val result = limelight.latestResult
if (result != null && result.isValid) {
localizationTags = result.botposeTagCount
if (result.botposeTagCount >= 2) {
updateBotPosition(result.botpose_MT2)
}
}
}
private fun updateBotPosition(botpose: Pose3D) {
val botX = -botpose.position.y
val botY = botpose.position.x
val botRot = ((botpose.orientation.yaw * Math.PI) / 180.0) - Math.PI
localization = Pose2D(DistanceUnit.METER, botX, botY, AngleUnit.RADIANS, botRot)
}
}
Pattern 3: Vision-Assisted Targeting
Use tx/ty for alignment assistance:
fun isAlignedWithTarget(result: LLResult, toleranceDegrees: Double = 2.0): Boolean {
if (!result.isValid) return false
return abs(result.tx) < toleranceDegrees
}
fun getSteeringCorrection(result: LLResult, kP: Double = 0.05): Double {
if (!result.isValid) return 0.0
return -result.tx * kP
}
Pattern 4: Odometry Fusion with Vision
Combine vision with odometry using weighted averaging or Kalman filter:
fun fuseVisionWithOdometry(
odomPose: Pose2D,
visionPose: Pose2D,
visionWeight: Double = 0.15
): Pose2D {
val fusedX = odomPose.x * (1 - visionWeight) + visionPose.x * visionWeight
val fusedY = odomPose.y * (1 - visionWeight) + visionPose.y * visionWeight
val odomHeading = odomPose.heading
val visionHeading = visionPose.heading
val headingDiff = normalizeAngle(visionHeading - odomHeading)
val fusedHeading = normalizeAngle(odomHeading + headingDiff * visionWeight)
return Pose2D(DistanceUnit.METER, fusedX, fusedY, AngleUnit.RADIANS, fusedHeading)
}
Best Practices
Performance Optimization
- Use appropriate resolution: 320×240 for 2D detection (90 FPS), 640×480 for 3D localization
- Minimize exposure: Reduce motion blur during robot movement (start at 1-2 ms)
- Increase detector downscale: Trades range for framerate (1.5-2.0 works well)
- Filter by tag count: Only use pose estimates with 2+ tags for reliability
- Set poll rate: 100 Hz is good balance for FTC (don't exceed loop rate)
Reliability
- Always check
isValid() before accessing result data
- Check staleness: Reject old data (
getStaleness() > 50ms)
- Validate tag count: More tags = more reliable pose (
botposeTagCount >= 2)
- Handle missing targets: Have fallback behavior when no tags detected
- Test transformations: Empirically verify coordinate transformations
Camera Mounting
- Height: Mount above or below tag height for better detection
- Angle: Slight tilt (10-20°) prevents head-on ambiguity
- Stable mount: Minimize vibration and movement
- USB routing: Ensure USB-C cable won't disconnect during movement
- Field of view: Position to see multiple tags simultaneously
Debugging
- Use web UI: Live view shows detections in real-time
- Capture snapshots: Save problem frames for offline analysis
- Check telemetry: Monitor tx, ty, tag count, staleness
- Verify field map: Ensure AprilTag positions match physical field
- Test camera pose: Verify robot-to-camera transform is correct
Common Issues and Solutions
Issue: No AprilTags Detected
Solutions:
- Check lighting (too bright or too dark)
- Reduce exposure to minimize motion blur
- Increase sensor gain for low light
- Verify tag size configuration matches physical tags
- Check ID filters aren't excluding your tags
- Ensure tags are flat and not damaged
Issue: Unreliable Pose Estimates
Solutions:
- Only use poses with 2+ tags (
botposeTagCount >= 2)
- Check camera pose configuration in web UI
- Verify field map matches physical tag locations
- Use MegaTag2 with IMU fusion (
updateRobotOrientation())
- Reject poses with low tag count or high staleness
- Implement outlier rejection (check if pose jumped >1m)
Issue: Wrong Coordinate Transformation
Solutions:
- Test transformation empirically (place robot at known position)
- Check coordinate system documentation
- Verify camera orientation in robot pose config
- Use telemetry to debug X, Y, heading separately
- Consider alliance-specific transformations
Issue: Low Frame Rate
Solutions:
- Use lower resolution (320×240 instead of 640×480)
- Increase detector downscale (1.5-2.0)
- Reduce number of tags in ID filter
- Use simpler pipeline type (AprilTag is faster than neural nets)
- Check USB connection quality
Issue: Tags Detected But Wrong IDs
Solutions:
- Increase quality threshold to filter spurious detections
- Use ID filters to restrict to expected tags
- Check tag family setting (should be 36h11 for FTC)
- Ensure tags are correctly printed/sized
- Improve lighting conditions
Integration with This Codebase
Current Architecture
LimelightIO.kt - Abstraction layer:
class LimelightIO(limelight3A: Limelight3A, physicalHardware: HardwareIO) {
}
Limelight.kt - Subsystem:
class Limelight(robot: Robot): SubsystemBase() {
}
HardwareIO.kt - Hardware interface:
override fun limelightDetectedMotif(): Limelight.Motif
override fun limelightLocalizationAprilTagCount(): Int
override fun limelightLocalization(): Pose2D
Usage in Autonomous
val motif = robot.hardware.limelightDetectedMotif()
ShootMotif(robot.spiceRack, motif)
Usage in TeleOp
Display targeting info:
telemetry.addData("Limelight Tags", hardware.limelightLocalizationAprilTagCount())
telemetry.addData("Motif", hardware.limelightDetectedMotif())
Resources
Quick Reference
| Task | Code |
|---|
| Initialize | hardwareMap.get(Limelight3A::class.java, "limelight") |
| Switch pipeline | limelight.pipelineSwitch(0) |
| Get result | limelight.latestResult |
| Check validity | result.isValid |
| Target angle | result.tx, result.ty |
| Robot pose | result.botpose_MT2 |
| Tag count | result.botposeTagCount |
| Update IMU | updateRobotOrientation(yaw) |
| AprilTag IDs | result.fiducialResults |
| Start processing | limelight.start() |
Remember:
- Always check
result.isValid before accessing data
- Use 2+ tags for reliable localization
- Update IMU heading for MegaTag2
- Test coordinate transformations empirically
- Access web UI at
http://limelight.local:5801