Robot code for FTC team 11138 (Premier), 2024–2025 INTO THE DEEP season. The robot runs two scoring games: it picks samples off the floor and submersible and scores them in the high basket, and it clips specimens onto the high chamber. Both the intake and the autonomous routines are driven by an on-board OpenCV vision pipeline that finds the nearest game piece, measures its angle, and lines the claw up on it.
This document describes how the code is actually put together — the control
loop, the hardware abstraction, the subsystems, the command layers, the vision
pipeline, and the autonomous path system. Everything below refers to classes in
TeamCode/src/main/java/org/firstinspires/ftc/teamcode.
- FTCLib command-based framework — subsystems, commands, and a command scheduler. All robot behavior is expressed as commands.
- Pedro Pathing for autonomous path following, with a goBILDA Pinpoint
computer as the localizer (
PinpointLocalizer). Follower and localizer constants live inpedroPathing/constants/FConstants.javaandLConstants.java. - OpenCV through the FTC VisionPortal for sample detection and alignment.
- FTC Dashboard (
@Config) so tuning constants can be edited live over Wi-Fi while the robot runs. - fast-load for faster redeploys during practice.
Robot.java is the single owner of hardware and state. It is a singleton
(Robot.getInstance()) so any command can reach the follower, the subsystems,
or shared state without passing references around. On init it resets the
command scheduler, builds the Pedro Pathing Follower with the Pinpoint
localizer, and constructs each subsystem. Every subsystem adds itself to a
subsystems list in its constructor.
Each loop runs in three phases, kept separate on purpose:
updateData()— every subsystem reads its encoders, servos, and sensors and writes the values intoRobotData. This is the only place hardware is read.periodic()— every subsystem acts on the current state, thenfollower.update()runs the drivetrain. This is the only place hardware is written.write()—RobotDatadumps the whole robot state to telemetry (loop time, pose, every subsystem state).
Splitting read from write means a command can change a target in one place and trust that every subsystem sees a consistent snapshot when it acts.
RobotData is the blackboard. It holds every encoder position, servo position,
subsystem enum state, the current Pinpoint pose, and the camera outputs
(sampleAngle, sampleDx, sampleDy). It also holds three interlock flags —
intaking, scoring, and sampleLoaded — with small helpers to set them.
Composite commands check these before running so mechanisms don't collide: the
sample transfer, for example, only runs when both intaking and scoring are
false, so the intake arm never swings while the deposit bucket is up.
Tunables are kept out of the logic classes so they can be changed without touching behavior code, and edited live through FTC Dashboard:
Constants.java— encoder limits, PIDF-style ratios, servo setpoints, target positions, camera calibration, correction coefficients.Configuration.java— the hardware-map name strings.PoseConstants.java— absolute field coordinates for the autonomous paths.
Every position-controlled motor goes through RE_DcMotorEx instead of the raw
SDK motor, which adds three things the mechanisms need:
- Asymmetric power. Up and down travel scale by separate
upRatioanddownRatiofactors, and there is a thirdslowRatiothat kicks in past aslowencoder threshold. On the vertical lift, gravity does most of the work on the way down, so the down and slow ratios pull power back near the bottom of travel instead of letting the lift slam into the frame. - Soft limits. Targets are clipped to
[minPosition, maxPosition], and theperiodic()power logic refuses to drive past the limits (unless the globalGlobals.LIMITSoverride is off for testing). - State caching. It tracks
lastTargetand only callssetTargetPosition()/RUN_TO_POSITIONwhen the target actually changes, which keeps the control loop from issuing redundant hardware writes every tick.
RE_DcMotorExParams bundles the min/max/slow limits and the three ratios so
each mechanism configures its own profile. RE_SubsystemBase is the shared base
that gives every subsystem the updateData() / periodic() pair.
NewIntakeSubsystem— one extension motor (RE_DcMotorEx) plus a five-servo end effector: two arm servos driven together (arm2reversed and offset fromarm1), a claw, a wrist, and a rotate servo. Arm, wrist, claw, and rotation are each a small state machine (ArmState,WristState,ClawState,RotateState);periodic()maps the current state to a tuned servo setpoint. Rotation also has a continuous path —setRotate(angle)converts a measured angle into a servo position through a ticks-per-degree constant — which is what lets the claw snap to the angle the camera reports for a skewed sample.DepositSubsystem— the vertical scoring lift. Two motors (lift2reversed) run as one, both throughRE_DcMotorExwith the lift ratios and soft limits; aliftOffseton one side keeps the two strands aligned at height. A bucket servo has its ownBucketState(intake / drop / ascent).SpecimenSubsystem— an independent lift and claw for pulling specimens off the wall and hanging them on the chamber.CameraSubsystem— wraps the VisionPortal and the sample processor, reads out the block angle and pixel offsets, and turns the vertical offset into an extension correction (see the vision section). A second variant,NewTestCameraSubsystem, does the same job by projecting pixels to field meters for a bird's-eye estimate.SensorSubsystem— color and distance sensors for confirming a sample is captured.
Behavior is built in three layers on top of FTCLib commands.
Primitives (commands/subsystem/) are one-tick InstantCommands that set a
single subsystem state — WristStateCommand, LiftPositionCommand,
ClawStateCommand, and so on.
Drive commands (commands/drivecommand/) wrap the Pedro Pathing follower so
navigation fits inside the command framework. PathCommand and StrafeCommand
hand a path to the follower and finish when the follower reports it is done, so
the rest of a sequence doesn't have to poll the drivetrain.
Advanced macros (commands/advancedcommand/) sequence primitives into full
actions. NewSampleTransferCommand is a good example: it is a
ConditionalCommand gated on !intaking && !scoring, and inside it runs a
SequentialCommandGroup that moves the wrist to transfer, waits, rotates the
claw vertical, waits, brings the arm to the transfer position (only if it isn't
already there), marks sampleLoaded, opens the claw to drop the sample into the
bucket, then stows the arm, wrist, and rotation. The waits between steps give
the servos time to travel so the handoff doesn't jam.
The sample processor (NewTestingSampleAngleProcessor) is an OpenCV
VisionProcessor that runs on the VisionPortal stream. It is built for the
fixed geometry of the game's samples rather than a general object detector, so
it stays fast enough to run every frame:
- Undistort.
Calib3d.undistort()corrects lens distortion using a calibrated camera matrix (fX, fY, cX, cY) and five distortion coefficients (k1, k2, p1, p2, k3), all stored inConstants. - Perspective warp.
getPerspectiveTransformbuilds a homography from four tuned source corners to a flat 900×600 rectangle, andwarpPerspectiveprojects the frame into that top-down view so a sample's pixel geometry maps cleanly to its real orientation. - Alliance-aware masking. The frame is converted to HSV and thresholded for
yellow plus either red or blue, chosen from
Globals.ALLIANCE, so opposing alliance pieces are ignored. Red uses two hue ranges to cover the HSV wrap-around. Erode then dilate cleans up the mask. - Contour geometry. Contours over 500 px in area are fit with
minAreaRect, and the rect closest to frame center is chosen. The angle is normalized (+90when width < height, folded into[0, 180)) so it always describes the sample's long axis, and the center offset from frame center is reported asdx,dy.
Two consumers act on those outputs:
- Rotation —
RotateAlignCommandtakes the reported angle and snaps the rotate servo to it through the intake's continuous-angle path, so the claw meets the sample square regardless of how it landed. - Extension —
CameraSubsystemconverts the vertical pixel offsetdyinto an extension-motor tick correction using a fourth-degree polynomial indy(four tuned multipliers inConstants) plus piecewise offset corrections for near/far bands.ExtensionJumpCommandapplies that correction, so the claw reaches the right distance without the driver inching the robot in.
Autos are single SequentialCommandGroups that interleave driving and mechanism
work. Because drive commands and mechanism commands are both FTCLib commands,
.alongWith() runs them together — a routine drives toward a sample while the
intake is already deploying, so the arm is over the sample as the robot
settles instead of after.
Paths are generated by AutonomousMethods, a set of factory methods that build
Pedro Pathing Paths from BezierLines and two-, three-, or four-point
BezierCurves. Each factory applies setLinearHeadingInterpolation over the
path's t domain (0→1), so the robot sweeps its heading continuously as it
translates instead of stopping to turn; ...TurnLater reversed variants exist
for approaches that should hold heading and rotate late. Field coordinates for
every path come from PoseConstants.
The routines cover both games and several field starts:
- Sample side (
Auto_5Plus0,Auto_5Plus0_Reverse,Auto_0PickSub,Auto_5Plus0_SubPart) — score the preload, run vision-aligned pickups on the spike-mark samples, and pull a sample from the submersible. - Specimen side (
Auto_0Plus5,Auto_0Plus5NEW,Auto_0Plus4) — cycle specimens onto the chamber. Scoring usesSpecimenDepositCommand, which drops position control and instead drives the specimen lift down at a fixed raw power for a tuned time, seating the clip onto the bar by force so it latches reliably. The wall-pickup steps leave the claw open across a wide window so a human player has room to place the specimen. - Pose trackers (
Auto_PoseTracker_Sample,Auto_PoseTracker_Specimen) for logging and validating localization against the planned paths.
TeleOp_Solo (and TeleOp_Solo_New) run the robot from a single driver by
binding the advanced macros to buttons — one press runs the whole transfer or
deploys the intake, so the driver isn't sequencing servos by hand. The
drivetrain toggles between field-centric and robot-centric (Constants.robotCentric).
Two feedback channels keep the driver's eyes on the field: the gamepad rumbles
on key transitions (a short pulse on most actions, a long one on others), and
the gamepad LED shows drive mode — red for robot-centric, blue for
field-centric, and a green flash on reset actions. Vision assists are wired in
too: RotateAlignCommand snaps the claw to the camera's measured sample angle,
and ExtensionJumpCommand uses the camera offset to set extension distance. A
two-driver TeleOp_Duo is also included.
TeamCode/src/main/java/org/firstinspires/ftc/teamcode/
├── hardware/
│ ├── Robot.java # singleton registry + control loop
│ ├── RobotData.java # shared state, interlocks, telemetry
│ └── subsystems/ # intake, deposit, specimen, camera, sensor
├── commands/
│ ├── subsystem/ # one-tick setpoint primitives
│ ├── drivecommand/ # Pedro Pathing wrappers
│ └── advancedcommand/ # sequenced macros
├── opmode/
│ ├── TeleOp_Solo / _New / _Duo
│ └── auto/ # routines + AutonomousMethods
├── vision/ # OpenCV VisionProcessors
├── pedroPathing/ # follower/localizer constants, tuners
└── util/
├── Constants / Configuration / PoseConstants / Globals
└── wrappers/ # RE_DcMotorEx, RE_SubsystemBase
Open the project in Android Studio and deploy the TeamCode module to the REV
Control Hub. OpModes appear on the Driver Station under the names in their
@TeleOp / @Autonomous annotations. Pedro Pathing and the vision pipeline are
tuned through the constants files and FTC Dashboard; the pedroPathing/tuners_tests
OpModes are the standard Pedro tuning routines for localization and following.