HiveVision

HiveVision

Point a Limelight 3A at the field, get game-element positions in field coordinates, and route autonomous paths around obstacles. Built for FTC.

Install

Add the repository and the modules you need to your TeamCode/build.gradle:

repositories {
    maven { url = uri("https://sidhuharjas.github.io/hvtest") }
}

dependencies {
    implementation "org.hivevisionftc:core:1.0.0"
    // optional — auto-routing with Marrow Weaver
    implementation "org.hivevisionftc:weaver:1.0.0"
}

Sync, and you're done. core is the perception pipeline; weaver is an add-on that turns detections into obstacle-avoiding paths.

Quickstart

1. Set up the camera

Measure your camera mount (see Calibration), then create the config:

VisionConfig config = new VisionConfig()
        .camPitchDeg(25.0)      // tilt below horizontal, degrees
        .camHeight(9.2)         // floor to lens center, inches
        .ballHeight(3.0)        // floor to game element center, inches
        .camXOffset(4.0)        // lens forward of robot center, inches
        .camYOffset(0.0)        // lens left of robot center (+ left)
        .minConfidence(0.44)    // ignore weak detections
        .maxStalenessMs(120)    // discard stale data
        .pipeline(3)            // Limelight pipeline index
        .pollingRateHz(150);

Limelight3A limelight = hardwareMap.get(Limelight3A.class, "limelight");
HiveVision vision = new HiveVision(limelight, config);

// optional: restrict which classes to chase (default = all three)
vision.setAllowedClasses(
        BiobuzzClasses.POLLEN,          // neutral yellow pollen
        BiobuzzClasses.NECTAR_RED,      // alliance red nectar
        BiobuzzClasses.NECTAR_BLUE);    // alliance blue nectar

Class IDs match the model labels: POLLEN = 0, NECTAR_RED = 1, NECTAR_BLUE = 2. Skip setAllowedClasses entirely to keep all three enabled, or pass only the classes you want (e.g. NECTAR_RED, NECTAR_BLUE to ignore pollen).

2. Ask for the closest target

Pass your current pose so detections come back in field coordinates:

FieldPose robotPose = new FieldPose(
        follower.pose().x(),
        follower.pose().y(),
        follower.pose().heading());

Detection target = vision.detections()
        .fetch(robotPose)
        .sortByDistance()
        .getClosest();

if (target != null) {
    telemetry.addData("class", target.getBallClass());
    telemetry.addData("field x", target.getX());
    telemetry.addData("field y", target.getY());
}

3. Route a path through the detections

The optional weaver module turns the same list into a Marrow path that dodges obstacles. Feed it to Pedro Pathing and drive:

PathResult path = vision.detections()
        .fetch(robotPose)
        .sortByDistance()
        .with(WeaverRoute.fromFetchPose()
                .avoid(new CircleZone(new Point(72, 72), 18.0))
                .maxTargets(4));

if (path != null) {
    follower.follow(PedroPathingConverter.toPath(path));
}

avoid(...) zones are optional. with(...) returns null when nothing was detected — always null-check.

PedroPathingConverter is built into Marrow core itself, not Pedro:

import com.skeletonarmy.marrow.weaver.PedroPathingConverter;

Run the examples

Two finished OpModes ship in docs/examples/:

OpModeWhat it does
AutoBallHuntOpMode
"HiveVision Weaver Auto"
One-shot autonomous. Detects targets, builds a Weaver route through the closest 2, drives it.
ManualBallChaseOpMode
"HiveVision Loop with Obstacles"
Runs a detection loop the whole match. Re-plans around an obstacle whenever the robot is idle.
  1. Copy those two files plus SampleFollowerFactory.java into your TeamCode module (they already declare package org.firstinspires.ftc.teamcode).
  2. Fix the VisionConfig numbers for your camera mount. The config file expects the Limelight under the hardware name "limelight" — rename it in your hardwareMap / config if yours differs.
  3. Build and deploy TeamCode to the robot the way you normally ship code.
  4. On the Driver Station pick Autonomous → HiveVision Weaver Auto (or the loop one), turn on the Limelight, press start.

Telemetry shows what it sees: closest element class, field X/Y, and route status. If it says “No targets detected”, check the Limelight pipeline index and that the game elements are inside the camera view.

Calibration

Distance estimation comes from your camera geometry. Measure these once, write them down, and positions on the field will line up:

MethodWhat to measure
camHeightFloor to lens center (inches)
ballHeightFloor to game element center (inches)
camPitchDegTilt below horizontal (degrees)
camXOffsetLens forward of robot center (+ forward)
camYOffsetLens left of robot center (+ left, − right)
minConfidenceLowest confidence you trust (0.0–1.0)
maxStalenessMsHow old a detection may be (ms)
If your robot is in CAD, pull the four geometry values from the assembly — it beats a tape measure. Then double-check on the real robot for tile compression and mount flex.

Full measurement procedure and coordinate frames: docs/calibration.md. API reference: docs/MODULES.md. Example OpModes: docs/examples/.

Build from source

Clone sidhuharjas/hvtest, then:

./gradlew test           # unit tests, no camera needed
./gradlew spotlessApply  # format
./gradlew assembleRelease

Every push of a v* tag (or a manual run of the Publish to Pages Maven repo workflow) rebuilds the AARs and republishes them to this site. Bump version in build.gradle.kts before tagging.