Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
1e93d12
docs: design spec for BIOBUZZ teleop robot code
Hazzer890 Sep 20, 2026
aa9c039
docs: implementation plan for BIOBUZZ teleop
Hazzer890 Sep 20, 2026
78e4384
chore: move to FTC SDK v12.0, the BIOBUZZ season release
Hazzer890 Sep 20, 2026
a737e79
chore: ignore .worktrees
Hazzer890 Sep 20, 2026
3b9cfa8
feature: mecanum maths as a unit-tested pure function
Hazzer890 Sep 20, 2026
2030e2d
feature: constants class, hub bulk reads and GamepadEx controls
Hazzer890 Sep 20, 2026
6a3b24d
feature: intake and indexer on the triggers
Hazzer890 Sep 20, 2026
9a8666d
feature: shooter RPM conversions with tests
Hazzer890 Sep 20, 2026
a479cb7
feature: closed-loop shooter with spin-up toggle and fire on X
Hazzer890 Sep 20, 2026
a9c2a4f
feature: shooter idle coasts instead of braking
Hazzer890 Sep 20, 2026
780d8af
feature: AprilTag aim assist on the alliance cell, alliance picked in…
Hazzer890 Sep 20, 2026
521d537
docs: driver telemetry, config table, controls and on-robot checklist
Hazzer890 Sep 20, 2026
62747eb
feature: shooter idle coasts, single velocity read per loop, robot dr…
Hazzer890 Sep 20, 2026
a9b7497
docs: design spec for shoot on the move
Hazzer890 Sep 20, 2026
8b5d2da
docs: spec wording
Hazzer890 Sep 20, 2026
9264a24
docs: implementation plan for shoot on the move, spec notes band-widt…
Hazzer890 Sep 20, 2026
3ff1356
feature: shot ballistics module, node exporter and inputs file
Hazzer890 Sep 20, 2026
8053945
feature: generated shot table from default inputs
Hazzer890 Sep 20, 2026
ce78abc
feature: shot table visualiser with trajectory fan, band chart and he…
Hazzer890 Sep 20, 2026
81a35a1
feature: shot solver with table lookup and heading lead, tested
Hazzer890 Sep 20, 2026
0ca1fee
feature: target tracker placing the cell from timestamped detections,…
Hazzer890 Sep 20, 2026
e7707e9
feature: visualiser panels sized to their grid tracks
Hazzer890 Sep 20, 2026
5d6c5fe
feature: shot solver rejects cells with no time of flight or no horiz…
Hazzer890 Sep 20, 2026
6d688ba
feature: adjustable shooter target with trim, odometry velocity, visi…
Hazzer890 Sep 20, 2026
b6c9d41
feature: CSV shot log on the hub
Hazzer890 Sep 20, 2026
391cc33
feature: solver-driven aim, fire gate, RPM trim and shot logging
Hazzer890 Sep 20, 2026
a3a94c2
docs: shot table tools guide, measurement record and on-robot ladder
Hazzer890 Sep 20, 2026
253821f
feature: heading reset clears the tracked target
Hazzer890 Sep 20, 2026
76e1f60
feature: shooter retargets only on a 10 RPM change and without re-ass…
Hazzer890 Sep 20, 2026
fdbe31e
feature: shot distance measured to the near lip, matching the table
Hazzer890 Sep 20, 2026
238de3b
feature: shooter follows the table whenever the cell is tracked, Y on…
Hazzer890 Sep 20, 2026
e2412c1
feature: shot table self-check tests the operator's inputs
Hazzer890 Sep 20, 2026
8a95655
feature: tighter aim gate, camera-constants warning, shot telemetry s…
Hazzer890 Sep 20, 2026
b2adf89
docs: spec and tools guide match what was built
Hazzer890 Sep 20, 2026
dcc548c
feature: shot tool defaults for an 80 degree launch and 60 degree cel…
Hazzer890 Sep 20, 2026
98f8be9
fix: shot graph drawn to scale so the launch angle and cell tilt are …
Hazzer890 Sep 20, 2026
e3ea755
Merge origin/main: SDK v12 and measured Pinpoint offset, offset moved…
Hazzer890 Sep 20, 2026
ab91810
feature: hood travel sim, sweeps hood angle and RPM to find the narro…
Hazzer890 Sep 25, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -52,3 +52,6 @@ jobs:

- name: Assemble the TeamCode debug APK
run: ./gradlew :TeamCode:assembleDebug --no-daemon --stacktrace

- name: Run the TeamCode JVM unit tests
run: ./gradlew :TeamCode:testDebugUnitTest --no-daemon --stacktrace
76 changes: 76 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -114,3 +114,79 @@ run the `TeamCode` configuration. Android Studio does the same `adb install` and
launch shown above. If the Hub does not appear in the device dropdown, run
`adb connect 192.168.43.1:5555` in a terminal first; Android Studio picks up
devices from the same adb server.

## Control Hub configuration

The Robot Controller configuration must use these names. The PR template asks
you to keep this table current when you add or move hardware.

| Config name | Type | Hub / port | Mechanism |
| --- | --- | --- | --- |
| `front_left_drive` | goBILDA 5202/3/4 series | | Drive |
| `front_right_drive` | goBILDA 5202/3/4 series | | Drive |
| `back_left_drive` | goBILDA 5202/3/4 series | | Drive |
| `back_right_drive` | goBILDA 5202/3/4 series | | Drive |
| `intake` | goBILDA 5202/3/4 series | | Intake |
| `indexer` | goBILDA 5202/3/4 series | | Indexer |
| `shooter_left` | goBILDA 5202/3/4 series | | Shooter |
| `shooter_right` | goBILDA 5202/3/4 series | | Shooter |
| `pinpoint` | goBILDA Pinpoint Odometry Computer (I2C) | | Odometry |
| `Webcam 1` | Webcam | USB | Vision |

Fill in the hub and port column when the robot is wired.

## Driver controls

One driver on gamepad 1. Pick the alliance during INIT with D-pad left (red)
or right (blue).

| Input | Action |
| --- | --- |
| Left stick | Translate, field-centric |
| Right stick X | Rotate |
| Left bumper (hold) | Robot-centric translate |
| A | Zero heading |
| Right trigger (hold) | Intake and indexer run |
| Left trigger (hold) | Intake and indexer reverse |
| Right bumper | Shooter on / off |
| X (hold) | Fire: feeds only when at speed, and when aiming also only with a valid shot and heading inside the Cell width |
| Y (hold) | Aim: rotation tracks the solved shot heading. The shooter target follows the table whenever the Cell position is known, aiming or not. |
| D-pad up / down | Trim every shooter target by ±50 RPM for the rest of the run |

## Testing on the robot

Run through these after any change to the matching subsystem and record the
result in the PR.

1. Drive: each motor spins forward on positive power, the robot drives
straight, strafes right on stick right, and field-centric holds direction
after a spin.
2. Odometry: Pinpoint LED green. X grows driving forward, Y grows driving left,
heading grows turning anticlockwise. Spinning in place moves X/Y under
100 mm. Returning to the start reads under 10 mm.
3. Intake and indexer: run, reverse, stop. The indexer holds a ball when
stopped.
4. Shooter: reaches the setpoint, telemetry RPM within tolerance, the AT SPEED
flag appears, and a Pollen lands in the Cell from the practice spot.
5. Vision: telemetry shows a bearing with a Cell tag in view, and holding Y
turns the robot toward it and settles.

Tuning values (shooter RPM and PIDF, aim gain, Pinpoint pod offsets) live in
`TeamCode/src/main/java/org/firstinspires/ftc/teamcode/Constants.java`.

### Shoot on the move

Tools and the measurement procedure are in `tools/shots/README.md`. Verify in
this order, recording hits in the PR:

0. Velocity frame: strafe left with the robot facing +X and confirm the Pose
telemetry shows `v` growing in the second component, then turn 90° and
repeat; the same component must still grow. If it swaps, the Pinpoint
reports robot-frame velocity and `OdometrySubsystem.getVelocity()` must
rotate it by the heading before the solver sees it.
1. Stationary at 1.0, 1.5, 2.0 and 2.5 m: at least 8 of 10 Pollen in. Fix the
table inputs before moving on.
2. Strafing across the Cell at a steady speed: 7 of 10.
3. Driving toward and away at a steady speed: 7 of 10, and "NO SHOT: CLOSING
TOO FAST" appears near the speed the band chart predicts.
4. Free driving: log, video, count, fix what the log shows.
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
package org.firstinspires.ftc.teamcode;

public enum Alliance { RED, BLUE }
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
package org.firstinspires.ftc.teamcode;

import com.qualcomm.robotcore.hardware.DcMotorSimple;

/** Hardware names, directions and tuning values. One home so drivers and Pedro Pathing can find them. */
public final class Constants {
private Constants() {}

// Robot Controller configuration names.
public static final String FRONT_LEFT_DRIVE = "front_left_drive";
public static final String FRONT_RIGHT_DRIVE = "front_right_drive";
public static final String BACK_LEFT_DRIVE = "back_left_drive";
public static final String BACK_RIGHT_DRIVE = "back_right_drive";
public static final String INTAKE = "intake";
public static final String INDEXER = "indexer";
public static final String SHOOTER_LEFT = "shooter_left";
public static final String SHOOTER_RIGHT = "shooter_right";
public static final String PINPOINT = "pinpoint";
public static final String WEBCAM = "Webcam 1";

// Motor directions. Left side reversed so positive power on every drive motor goes forward.
public static final DcMotorSimple.Direction FRONT_LEFT_DIRECTION = DcMotorSimple.Direction.REVERSE;
public static final DcMotorSimple.Direction FRONT_RIGHT_DIRECTION = DcMotorSimple.Direction.FORWARD;
public static final DcMotorSimple.Direction BACK_LEFT_DIRECTION = DcMotorSimple.Direction.REVERSE;
public static final DcMotorSimple.Direction BACK_RIGHT_DIRECTION = DcMotorSimple.Direction.FORWARD;
public static final DcMotorSimple.Direction INTAKE_DIRECTION = DcMotorSimple.Direction.FORWARD;
public static final DcMotorSimple.Direction INDEXER_DIRECTION = DcMotorSimple.Direction.FORWARD;
public static final DcMotorSimple.Direction SHOOTER_LEFT_DIRECTION = DcMotorSimple.Direction.FORWARD;
public static final DcMotorSimple.Direction SHOOTER_RIGHT_DIRECTION = DcMotorSimple.Direction.REVERSE;

// Pinpoint pod offsets from the tracking point, mm. X pod: left of centre positive.
// Y pod: forward of centre positive. Measure on the robot per the goBILDA setup guide.
public static final double PINPOINT_X_OFFSET_MM = 0.0;
public static final double PINPOINT_Y_OFFSET_MM = -105.0; // measured, from main 12cad4b

// Intake and indexer open-loop powers.
public static final double INTAKE_POWER = 1.0;
public static final double INDEXER_POWER = 1.0;

// Shooter. 1:1 Yellow Jacket, 28 ticks per rev, 6000 RPM free speed.
public static final double SHOOTER_TICKS_PER_REV = 28.0;
public static final double SHOOTER_SETPOINT_RPM = 3500.0; // tune on the robot
public static final double SHOOTER_TOLERANCE_RPM = 50.0; // the shot table needs the wheel within this; see tools/shots/README.md
// The scoring band is about 125 RPM wide, so a target change under this is noise: skip the hub writes.
public static final double SHOOTER_RETARGET_RPM = 10.0;
// Velocity PIDF starting point: F = 32767 / max ticks per second, P = 0.1 F, I = 0.1 P, D = 0.
public static final double SHOOTER_P = 1.17;
public static final double SHOOTER_I = 0.117;
public static final double SHOOTER_D = 0.0;
public static final double SHOOTER_F = 11.7;

// Driver controls.
public static final double TRIGGER_THRESHOLD = 0.2;

// Aim assist: rotation = -AIM_KP * bearingDeg, clamped. Bearing is positive to the left.
public static final double AIM_KP = 0.02;
public static final double AIM_MAX_ROTATE = 0.5;
public static final double AIM_DEADBAND_DEG = 1.0;

// Shoot on the move. Shooter geometry and the table live in the generated ShotTable.
public static final double FEED_DELAY_S = 0.15; // indexer feed command to ball exit, measure on the robot
public static final double SHOOTER_TRIM_STEP_RPM = 50.0; // D-pad up/down
public static final double TARGET_EXPIRY_S = 5.0; // how long the last tag fix is trusted
public static final int POSE_BUFFER_SIZE = 50; // about one second of loops

// Camera lens relative to the robot centre. Fill in from the CAD once the mount is designed.
public static final double CAMERA_FORWARD_M = 0.0;
public static final double CAMERA_LEFT_M = 0.0;
public static final double CAMERA_YAW_RAD = 0.0; // positive turned left
public static final double CAMERA_PITCH_RAD = 0.0; // positive tilted up

public static final String SHOT_LOG_DIR = "/sdcard/FIRST/shots";
}
10 changes: 10 additions & 0 deletions TeamCode/src/main/java/org/firstinspires/ftc/teamcode/Main.java
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,16 @@ public void init() {
robot.init();
}

@Override
public void init_loop() {
robot.initLoop();
}

@Override
public void start() {
robot.start();
}

@Override
public void loop() {
robot.periodic();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package org.firstinspires.ftc.teamcode;

/** Pure maths for a four-wheel mecanum base. No hardware, so it runs in JVM unit tests. */
public final class MecanumKinematics {
private MecanumKinematics() {}

/**
* Rotates a field-relative (forward, right) command into the robot frame.
*
* @param headingRadians robot heading, anticlockwise positive, 0 = facing field-forward
* @return {forward, right} in the robot frame
*/
public static double[] toRobotRelative(double forward, double right, double headingRadians) {
double cos = Math.cos(headingRadians);
double sin = Math.sin(headingRadians);
// Standard rotation by -heading with x = right, y = forward.
return new double[]{
forward * cos - right * sin,
right * cos + forward * sin,
};
}

/**
* Mixes forward, strafe-right and clockwise rotation into wheel powers.
*
* @return {frontLeft, frontRight, backLeft, backRight}, scaled so the largest magnitude is at most 1
*/
public static double[] mix(double forward, double right, double rotate) {
double[] p = {
forward + right + rotate,
forward - right - rotate,
forward - right + rotate,
forward + right - rotate,
};
double max = 1.0;
for (double v : p) max = Math.max(max, Math.abs(v));
for (int i = 0; i < p.length; i++) p[i] /= max;
return p;
}
}
Loading
Loading