Skip to main content

AprilTag Pose + Aim Assist

Disclaimer: WIP and untested on 2026 hardware; tune gains and stale timeout on your robot. Custom code built on WPILib APIs (not official WPILib sample).

Purpose: Use AprilTags to align to hub/tower; fall back to odometry if vision is stale.

Code (Java/WPILib + PhotonVision style)

public class VisionAim {
private final PhotonCamera cam = new PhotonCamera("photonvision");
private final PIDController turnPid = new PIDController(0.02, 0.0, 0.001);
private final double maxTurn = 0.5; // clamp
private double lastYaw = 0.0;
private double lastTime = 0.0;

public OptionalDouble getYawToTarget() {
var res = cam.getLatestResult();
if (!res.hasTargets()) return OptionalDouble.empty();
PhotonTrackedTarget target = res.getBestTarget();
lastYaw = target.getYaw();
lastTime = Timer.getFPGATimestamp();
return OptionalDouble.of(lastYaw);
}

public double turnCommand() {
double yaw = getYawToTarget().orElse(lastYaw);
double out = turnPid.calculate(yaw, 0.0);
return MathUtil.clamp(out, -maxTurn, maxTurn);
}

public boolean stale(double maxAgeSec) {
return (Timer.getFPGATimestamp() - lastTime) > maxAgeSec;
}
}

Usage

VisionAim vision = new VisionAim();

// In teleop, button to aim:
double turn = vision.turnCommand();
if (vision.stale(0.5)) {
// fallback: use gyro/odometry instead of vision
turn = 0.0;
}
drive.arcadeDrive(fwd, turn);

Parameters to tune

  • PID gains and maxTurn.
  • Max age before considering vision stale.
  • Camera name and pipeline settings for AprilTags.

How to test

  • Bench: point robot at a tag; verify yaw reading and turn output sign.
  • Field: rotate 360°, ensure stale detection kicks in when tag lost.
  • With drivetrain on blocks, command turn and observe smoothness/clamp.

Pitfalls

  • Ensure camera is configured for AprilTags with correct layout.
  • Clamp turn to avoid oscillation; add slew if needed.