Skip to content
Work in progress. This library and its docs are new and still changing, and some parts are not yet tested on a real robot. Something wrong or unclear?Tell us with a short GitHub form.

Tunables.java: every number, part by part

For: anyone who writes or changes Tunables.java in a robot made in five files (lesson 1). Use it: find your part below, read its table, and copy only the lines you want to change. Every number has a default, except the ones marked REQUIRED. Where Robot.java names the part’s motor or servo, the default comes from the parts catalog (First run).

This page lists every number the library reads from Tunables.java. A script (tools/check_tunables.py) reads the library’s code and checks this page against it, so the list is complete: if a part reads a new number, the check fails until the number is here.

Start here when something on the robot is not right. Find what you see, then change the setting in the middle column, in the direction it says. Change one number at a time, test, and copy the good value back into Tunables.java. The tables further down list every setting, part by part.

What you see or want Setting Which way
Driving
The robot is too fast for the drivers Drive.maxPower less
New drivers need a slower robot in practice Drive.practiceMaxPower less
Fine moves with the slow trigger are too slow or too jumpy Drive.slowSpeed more / less
Double taps are missed, or single taps wait too long Buttons.tapGapSec more / less
Autonomous moves
Autonomous does not run (“AUTONOMOUS IS OFF”) Drive.calibrated true, after the Drive Tuner
Sideways moves are short or long (mecanum) Drive.strafeCorrection Drive Tuner
Turns stop short Drive.turnKp more
Turns wobble or overshoot Drive.turnKp / Drive.turnKd less / more
The robot stops just before the end of a move Drive.moveMinPower more
Path steps do not run on the real robot Odometry.checked true, after the Odometry Tuner
The odometry’s distances are off (OTOS) Odometry.linearScalar Odometry Tuner
Intake
Pieces are thrown or jam on the way in Intake.inPower less
Pieces do not come out Intake.outPower more
Lift, arm, servos
A lift or arm is slow to arrive, or shakes kP more / less
A lift sags kF more
A lift or arm slams into its ends slowZone more
A claw does not open or close far enough positions Servo Finder
Actions go on before a servo arrives travelSec more
Launcher
Shots land short or long speedRpm (or the shot table) Shot Tuner, or more / less
The flywheel is slow to reach speed, or its speed swings kP, kF Flywheel Finder
It waits too long before it fires readyBandRpm more
Shots land at different distances readyBandRpm less
The second of two quick shots lands short shotIntervalSec more
The first shot takes too long to spin up idleRpm more (uses more battery)
One D-pad press changes the speed too much or too little stepRpm less / more
Feeding
It feeds two pieces at once feedSec less
It feeds no piece feedSec more
A pusher comes back before the piece is gone pushSec more
Aiming
Aim is slow, or swings past the goal Aim.turnKp more / less
Shots miss although it says “AIMED” Aim.toleranceDeg less
The robot or turret aims a little to one side cameraOffset Aim Tuner
A turret swings too hard Turret.maxPower less
The wires allow a turret to turn more or less Turret.min, max Motor Finder
Camera and sensors
The camera pictures are too dark or noisy Camera.gain more / less
A color sensor sees pieces that are not there nearMm less
A color sensor mixes up kinds of piece hueTolerance less
  1. Add the part to Robot.java (one line, with its part number: .part("gobilda-5203-435")). The name in quotes (Lift.slide("lift")) is the device’s name in the robot configuration on the Driver Station, exactly. Its section in Tunables.java is that name in CamelCase: "lift" reads Lift.
  2. Press INIT. The part works with the library’s starting values. INIT shows one note per part with the values it used.
  3. If INIT stops and asks for a number (a lift’s min and max, a turret’s stops), add the part’s section to Tunables.java with that number. The message names the tool that finds it.
  4. Optional: write a line for each setting in the part’s table below, set to DEFAULT_FROM_CATALOG, with a short comment, and MEASURE for the numbers only your robot can give. Then the file shows every setting the part has, and you can change one when you need to. Leaving a line out does the same as DEFAULT_FROM_CATALOG. The Cookbook shows each part’s section this way.

Tunables.java is the file with your robot’s numbers: how far the lift goes, how fast the launcher spins, where the claw opens. Robot.java says WHICH parts the robot has; Tunables.java says their numbers. Why a separate file: you change numbers all season, and you can change them without touching the code that works.

public class Tunables {
public static class Lift { // the part's section
public static double min = 0, max = 2580; // REQUIRED for a lift (Motor Finder)
public static double kP = 0.004; // optional: the default is used if you leave it out
public static double down = 0, high = 2400; // a lift's other numbers are named heights
}
public static class Auto { // your own section: no part reads it
public static double parkInches = 24;
}
}
  • A section is a part. public static class Lift { ... } holds the numbers of the part whose device is called lift in the robot configuration. The section’s name is the device’s name in CamelCase: each word starts with a capital letter, with no spaces and no _. lift reads Lift, intake_deploy reads IntakeDeploy, Webcam 1 reads Webcam1. To use another name, write .tunables("Name") on the part in Robot.java.
  • Some sections have fixed names: Drive, Odometry, Aim and Buttons. An interlock rule uses the name you give it in keepApart("TurretLift", ...).
  • A field is one number. Write it as public static double name = value; (int works too), public static boolean name = true; for an on/off setting, and public static double[][] name = {{24, 2200}, {48, 2550}}; for a table. public static is needed: the library cannot read a field without it.
  • Names count, with their capitals. maxPower is a setting; MaxPower and maxpower are not.
  • Your own sections (no part reads them, like Auto): your code reads them as Tunables.Auto.parkInches. Every number in them is in the tuning menu too.

When you press INIT, the library reads Tunables.java and tells you what it found:

You wrote INIT does Why
no line for an optional number uses the default, and shows a note: Tunables.Lift: kD, kF not written: library defaults used (fine) a missing number is not a problem: the default works. Add the line to change it
no section at all for a part uses every default: Tunables.Intake not written: library defaults used (fine) the same
no line for a number that has a starting value from the parts catalog (a launcher’s gains and speed, a lift’s kP with .part(...), a servo’s travelSec with .part(...)) uses it, and shows ONE note per part with the values, where they came from and the tool that tunes them: Launcher: defaults from the catalog (gobilda-5203-6000): kF 11.7, kP 1.17, kI 0.117, speed 3600 rpm. Run the Flywheel Finder, then the Shot Tuner a number worked out for YOUR motor works much better than a fixed one made for another motor. It is still a start: tune it, then write it (First run)
no positions for a Claw, Wrist, Positioner or servo Indexer uses the library’s guesses and says so: Claw: no positions written: open 0.2, closed 0.75 are library guesses, not your servo's: find yours with the Servo Finder before the first TeleOp guessed positions can push a part into the frame
no line for a REQUIRED number stops: Tunables.Lift has no max: write max (the top of the safe range, in counts: Motor Finder, B). A safety limit never comes from a library default. a wrong limit breaks the robot. A default could be wrong for your robot
a name in Lift, Arm, Claw, Wrist or Positioner that looks like a setting (Max, k_P, maxPowr) stops: Tunables.Lift.Max looks like "max" (capitals and spelling count) in these parts every other name is a named position, so a typo would quietly become a position
a name no part reads, in any part’s section (inPowr in Intake, goalx in Aim, a leftover spinPower) stops: Tunables.Intake.inPowr is not a setting of Intake (did you mean inPower?): the robot would quietly ignore it. Fix the name (capitals count) or delete the line. Known: inPower, outPower a misspelled line is never read, so the part would quietly use its default (a safety number too). The “did you mean” appears when the name looks like a real one
a section no part reads that is named like a part’s kind (Tunables.Launcher for the launcher on shooter), or spelled almost like a part’s section (Tunables.Liftleft) warns (!!, every profile): Tunables.Launcher is not read by any part: your launcher's section is Tunables.Shooter (its device name "shooter" in CamelCase). Move the numbers there every number in it would be quietly ignored. A warning, not a stop: a section no part reads may be your own (Tunables.Auto), and a part’s required numbers (a lift’s min/max, a servo turret’s posA…) already stop INIT when its real section is missing

The name check covers Drive, Odometry, Aim, Buttons, every part on the robot and every interlock rule. These names are yours to pick and always pass: named positions (any number in Lift, Arm, Claw, Wrist, Positioner), a piece sensor’s kinds (POLLEN), a launcher’s speedByDistance... tables, and an interlock rule’s <part>From, <part>To, <part>Above, <part>Below, <part>Margin, <part>StopSec for its two parts. A field of the part that your robot does not use (a servo turret’s posA on a motor turret) passes too. Your own sections (Auto) are never checked, and neither is the section of an optional() part that is not on the robot. Every name in the tables below is the full list.

Check on the laptop: ./gradlew :sim:fromRobot (it writes the simulator’s robot file) runs the same name checks and prints what INIT would stop on or warn about. In a JUnit test, robot.checkTunables() gives the same lines (testing).

Measured servo numbers and the servo’s range. Servo positions (named positions, posA/posB, rest/push, a hood’s calibration) are measured for the pulse range the servo had then. Adding or removing .fullRange() on a part in Robot.java changes where each position points: measure them again (Full range).

Two words you can write in place of a number you do not have yet. Every setting is then in the file from day one, and the file shows what is still to do. Which one to write:

The number Write
You know it the number
A safety limit (a lift’s min/max, a stop) you do not know yet MEASURE
A tuning number (kP, a speed…) you do not know yet DEFAULT_FROM_CATALOG

Add this line at the top of Tunables.java, under the package line and above public class Tunables:

import static com.crossbotics.ftchelper.robot.Starting.*;
public static class Launcher {
public static double kP = DEFAULT_FROM_CATALOG, kI = DEFAULT_FROM_CATALOG, kD = DEFAULT_FROM_CATALOG, kF = DEFAULT_FROM_CATALOG;
public static double speedRpm = DEFAULT_FROM_CATALOG;
}
public static class Lift {
public static double min = MEASURE, max = MEASURE; // INIT stops until you write them
public static double kP = DEFAULT_FROM_CATALOG;
public static double down = 0, high = MEASURE;
}
You write INIT does
DEFAULT_FROM_CATALOG uses the library’s starting value, the same as leaving the line out: worked out from the part Robot.java names (the parts catalog, or your own part’s numbers) where it can, else the library’s default. INIT lists each value and where it came from: Launcher: defaults from the catalog (gobilda-5203-6000): kF 11.7, kP 1.17, kI 0.117, speed 3600 rpm. Run the Flywheel Finder, then the Shot Tuner, and for a number with no part value: Tunables.Launcher: DEFAULT_FROM_CATALOG, library defaults: stepRpm 100. The tuning menu shows the same source next to the value; its “copy into Tunables.java” lines are real numbers
DEFAULT_FROM_CATALOG and no part in Robot.java (or a part whose numbers are not known) the general default, and a note that says so and how to do better: Lift: no part in Robot.java, so general defaults (kP, kD, slowZone, tolerance made for a goBILDA 312 RPM motor). Add .part(...) (or Motors.custom(...)), or tune them in the tuning menu
DEFAULT_FROM_CATALOG on a REQUIRED (safety) number stops: Tunables.Lift.max = DEFAULT_FROM_CATALOG, but it is a safety number: a safety limit never comes from the parts catalog or a library default. Write MEASURE until you know it, then the number (...)
MEASURE stops and names the tool that finds it: Tunables.Lift.min = MEASURE: find it on the robot: the bottom of the safe range, in counts: Motor Finder, X. Write the number in place of MEASURE.

Without the import line, the build fails with cannot find symbol and symbol: variable DEFAULT_FROM_CATALOG (or MEASURE).

Write the word, or leave the line out? Both give the same starting value. Write the word: then the file shows what is still to tune.

Only on number lines. Use these words only on lines that start public static double. On int, boolean or { } (table) lines, leave the line out instead.

INIT stopped on MEASURE: what now? Only your TeleOp and your autos stop. The Motor Finder and the Servo Finder still run: they do not read Tunables.java. For a lift’s min and max:

  1. On the Driver Station, choose Motor Finder, press INIT, then START.
  2. Put the lift at the bottom. Choose lift (D-pad, then A): its count starts at 0 there.
  3. Move it with gamepad 1’s right stick. Press X just above the bottom block (min) and B just below the top block (max).
  4. It shows the numbers: Tunables.Lift: min = 0, max = 2580.
  5. Write each number in place of MEASURE, build, and INIT your TeleOp again.

A part that is optional() and not on the robot does not stop on MEASURE, and ./gradlew :sim:fromRobot writes the simulator’s robot file with MEASURE lines still in it (so you can run the finders in the simulator). :sim:newRobot writes every part’s settings this way. A part not in the catalog: describe it in Robot.java.

A new robot runs before any number is tuned. Every number has a safe starting value, and where Robot.java names the motor or servo (Launcher.flywheel("launcher", "gobilda-5203-6000"), .part("gobilda-5203-435")), the starting value is worked out from that part’s numbers in the parts catalog (the seller’s numbers, with the date they were checked). INIT shows one note per part with the values it used, where they came from and the tool that tunes them. A number you write always wins. Writing DEFAULT_FROM_CATALOG does the same as leaving the line out. No part named, or a part whose numbers are not known (Motors.unknown, a custom part with Double.NaN): the general default (the plain one in the tables below), and INIT says so.

Part Not written What it starts with Tune it with
Launcher kF, kP, kI, kD REV’s formula with the motor’s top speed: F = 32767 / (top RPM x counts per turn / 60), P = F / 10, I = P / 10, D = 0. A goBILDA 6000 RPM motor: kF 11.7, kP 1.17, kI 0.117. Not the hub’s own gains (the numbers the Control Hub uses when the code gives none: P 10, I 3, F 0): with no F the wheel only creeps up to speed Flywheel Finder
Launcher speedRpm 60% of the motor’s top speed, to 10 RPM (6000 RPM motor: 3600). Room above it for the speed control to recover after a shot and on a tired battery, and a gentle first shot Shot Tuner
Lift with .part(...) kP, kD, slowZone, tolerance the plain defaults (made for a goBILDA 312 RPM motor, 537.7 counts a turn) scaled to your motor’s counts per turn, so it acts the same per motor turn. A 435 RPM motor: kP 0.0056, kD 0.00028, slowZone 179, tolerance 14 the tuning menu (lesson 4, step 6)
Claw, Wrist, Positioner with .part(...) travelSec the servo’s catalog speed at the hub’s 5 V over its whole move (goBILDA 2000 torque: 270 degrees at 0.25 s per 60 = 1.13 s) time a full move
Hood, servo Turret with .part(...) travelSec the same, over the move from posA to posB time it
servo Indexer with .part(...) pushSec, returnSec the same, from rest to push, at least 0.25 s watch a feed
Claw, Wrist, Positioner, servo Indexer every position the library’s guesses, with a note: they are NOT your servo’s Servo Finder before the first TeleOp

Never a starting value: safety numbers stay REQUIRED (a lift’s or arm’s min/max, a hood’s or servo turret’s stops, interlock zones, a turret’s homeAngle with a switch). Battery compensation stays off until you write your own kF.

Each part uses one unit. The table header says which.

Part Positions and limits in Speeds and times in
Lift encoder counts (ticks), 0 = where it was at INIT (or the bottom switch) power 0 to 1
Arm, Turret degrees. Arm: 0 = where it was at INIT. Turret: 0 = straight forward, counterclockwise + power 0 to 1
Claw, Wrist, Positioner, Indexer (servo), Hood (servo) servo position, 0 to 1 seconds
Hood (motor) encoder counts power 0 to 1
Launcher RPM (turns per minute) of the flywheel seconds
Drive, Aim, Odometry inches and degrees on the field power 0 to 1
PieceSensor hue in degrees (0 to 360) or size in mm milliseconds (Ms)

A name that ends in Sec is seconds, Ms milliseconds, In inches, Mm millimetres, Deg degrees, Rpm RPM, Percent percent.

You can change some numbers while the robot runs: PRACTICE profile, after START hold GUIDE for 1 s, D-pad to choose and change, A to save on the Control Hub (tuning guide).

Live says What it means
yes, at once in the tuning menu; the robot uses the new value in the same run
yes, next INIT in the tuning menu; the robot uses the new value from the next INIT (it reads the number once, at INIT)
no not in the tuning menu: change it in Tunables.java and install again. Limits are never in the menu, so tuning can never drive a part past a hard stop

Copy every tuned value back into Tunables.java. The next INIT lists them under “Tuned on this robot (copy into Tunables.java)”, for example Tunables.Lift.kP = 0.0050 (code 0.004). Why: a value saved only on the hub is lost with a new Control Hub, and the file is the team’s record.

The finders measure numbers on the robot and show the lines to copy. Each line names the section first:

The finder shows Copy into Tunables.java
Tunables.Lift: min = 0, max = 2580, P1 = 1200 in public static class Lift: set min, max and a position. Rename P1 to a real name (high)
Tunables.Claw: P1 = 0.210, P2 = 0.770 in Claw: rename them open and closed
public static double[][] speedByDistance = {{24, 2200}, {48, 2550}}; // in Tunables.Launcher the whole line, into Launcher (replace the old one)
Tunables.Odometry: checked = true in Odometry: public static boolean checked = true;

The finders are: Servo Finder, Motor Finder, Flywheel Finder, Drive Tuner, Odometry Tuner, Aim Tuner, Shot Tuner, Hood Finder, Piece Tuner. “Test” in the tables below means: try it on the robot and change it by hand.


Section Drive (fixed name). Read once at INIT. Module: Drive.

Field What it does Units Default Live Found by When to change
maxPower the most power the wheels get (COMPETITION profile) 0 to 1 1.0 yes, next INIT test the robot is too fast for the drivers
practiceMaxPower the most power in the PRACTICE profile 0 to 1 0.6 yes, next INIT test new drivers need it slower
slowSpeed a power: the most power while the slow trigger is pulled all the way (a little squeeze is a little slower) 0 to 1 0.3 yes, next INIT test fine moves are too slow or too fast
strafeCorrection makes a sideways move in autonomous as long as asked (mecanum wheels slip sideways) times 1.15 yes, next INIT Drive Tuner sideways moves are short or long
turnKp autonomous turn: how hard it turns per degree off power per degree 0.012 yes, next INIT test turns stop short (more) or wobble (less)
turnKd autonomous turn: brakes the turn as it gets close power per degree/s 0.003 yes, next INIT test turns overshoot (more)
moveMinPower the least power that still moves the robot (autonomous) 0 to 1 0.30 yes, next INIT test the robot stops just before the end of a move
calibrated autonomous drives the robot only when this is true. Until then INIT says AUTONOMOUS IS OFF (the simulator runs it anyway, and says so) true/false false no Drive Tuner, then set it after the Drive Tuner, once moves are right

Section Odometry (fixed name). Read once at INIT. Module: Odometry.

Field What it does Units Default Live Found by When to change
checked like Drive.calibrated, for the odometry: driveTo and path steps run on a real robot only when this is true. Until then INIT warns Odometry not checked (the simulator runs them anyway) true/false false no Odometry Tuner after the Odometry Tuner’s loop check passes
linearScalar OTOS only: corrects its distances times 1.0 no Odometry Tuner the OTOS says 48 in when the robot drove 50
angularScalar OTOS only: corrects its turns times 1.0 no test (turn 10 times, compare) the heading drifts after many turns
resetX, resetY the “set pose here” spot for resetPose(): x, y seen from your wall, written for BLUE. Write both: one alone is not used (no reset spot) inches none: no reset spot no measure on the field you want a button that resets the pose at a known spot
resetHeading the heading at the reset spot degrees 0 no measure on the field the robot faces another way at that spot
mirrorForRed ONE setting for the whole robot (routes, start and reset spots, the aim’s goal point): RED uses BLUE’s numbers mirrored (true) or the same numbers from the red wall (false). Write it once, in Aim or here: Aim’s wins, then this one, then your autonomous’s mirrorForRed(). INIT shows the choice and where it came from true/false Tunables.Aim.mirrorForRed, else true no the game’s field BIOBUZZ’s field is turned half a turn: false

Section Aim (fixed name). Every line is optional. Module: Aim.

Field What it does Units Default Live Found by When to change
goalX, goalY the goal on the field, seen from YOUR drivers’ wall like every field number in your code (x right of the centre line, y from the centre toward the far wall), written for BLUE: the odometry backup aims at it when the camera does not see the goal. Write both: one alone is not used (no odometry backup). BIOBUZZ: 13, -13 inches none: camera only no the field map once per season (both, or neither)
mirrorForRed ONE setting for the whole robot: RED’s numbers (the goal point, the routes, the start and reset spots) are BLUE’s mirrored (true) or the same numbers from the red wall (false). The first one written wins: this one, Tunables.Odometry.mirrorForRed, your autonomous’s mirrorForRed() true/false true no the game’s field BIOBUZZ: false
cameraMaxAgeMs a camera picture older than this does not count as “sees the goal” ms 150 no test the camera is slow and the goal flickers to “odometry”
maxGoalDistance the odometry backup is off while it says the goal is farther than this (a wrong pose) inches 170 no the field size another field size
cameraOdometryAgree warn when the camera and the odometry differ by more than this inches 6 no test too many warnings with a good pose
turnKp whole-robot aim: turn power per degree off power per degree 0.02 yes, at once test aim is slow (more) or swings (less)
turnKd whole-robot aim: brakes the turn as it gets close power per degree/s 0.002 yes, at once test aim overshoots (more)
maxTurnPower whole-robot aim: the most turn power 0 to 1 0.5 yes, at once test aim is too fast or too slow
minTurnPower whole-robot aim: the least power that still turns the robot 0 to 1 0 yes, at once test aim stops a few degrees short
toleranceDeg whole-robot aim counts as aimed within this degrees 2 yes, at once test shots miss although “AIMED” (less)
cameraOffset whole-robot aim (no turret): a camera that is not on the robot’s centre line. A turret has its own, Tunables.Turret.cameraOffset; the Aim Tuner prints the line for the one your robot uses degrees 0 yes, at once test the robot aims a little to one side
rumbleDeg the aim buzz starts this close to the goal degrees 4 yes, at once test the drivers want it earlier or later
shootOnMove shooting while driving: on at START true/false false no test only after tuning it on the real robot
flightSec shooting on the move: the piece’s time in the air seconds 0 yes, at once test (film a shot) moving shots land behind (more) or ahead
flightSecPerIn shooting on the move: more flight time per inch of distance seconds per inch 0 yes, at once test far moving shots miss but near ones hit
maxLeadIn shooting on the move: the most it aims ahead inches 24 yes, at once test fast moves need more lead
cameraLatencyMs shooting on the move: the camera’s delay, instead of its own timestamp ms the camera’s own timestamp yes, at once test the camera’s timestamp is wrong
shooterForwardIn, shooterLeftIn where the shooter (turret pivot or launcher exit) is from the robot’s centre inches 0 no measure the robot the shooter is not at the centre
cameraForwardIn, cameraLeftIn where a camera on the robot’s body is from the centre inches 0 no measure the robot a body camera, not at the centre

Section Buttons (fixed name). For button gestures (double tap, hold). Module: ControlMap.

Field What it does Units Default Live Found by When to change
tapGapSec the most time between the two taps of a double tap seconds 0.35 yes, at once test double taps are missed (more) or single taps wait too long (less)
holdSec how long a press is held before it counts as a hold seconds 0.3 yes, at once test holds start by accident (more)

Section: the device’s name in CamelCase (intake reads Intake, front_roller reads FrontRoller). Module: Intake.

Field What it does Units Default Live Found by When to change
inPower power while it takes pieces in 0 to 1 1.0 yes, at once test pieces are thrown or jam (less)
outPower power while it pushes pieces out 0 to 1 0.7 yes, at once test pieces do not come out (more)

Section: the device’s name in CamelCase (lift reads Lift, lift_left reads LiftLeft, not Lift). With two motors (alsoMotor) it is the FIRST motor’s name. A Lift is in encoder counts, an Arm in degrees. Module: Lift, Arm.

Field What it does Units Default Live Found by When to change
min the bottom of the safe range: it never goes below counts / degrees REQUIRED no Motor Finder (X) the part touches its bottom stop
max the top of the safe range: it never goes above. Must be more than min counts / degrees REQUIRED no Motor Finder (B) the part touches its top stop
kP how hard it pushes toward its target, per unit off power per unit Lift 0.004 (with .part(...): scaled to the motor, First run), Arm 0.02 yes, at once test slow to arrive (more) or shakes (less)
kI a push that grows while it stays short power 0 yes, at once test it stops a little short and stays there
kD brakes it as it gets close power Lift 0.0002 (with .part(...): scaled to the motor), Arm 0.001 yes, at once test it overshoots (more)
kF a steady push that holds it up against gravity power Lift 0.05, Arm 0 yes, at once test the lift sags (more)
maxPower the most power it ever gets 0 to 1 Lift 1.0, Arm 0.6 no test it moves too violently
slowZone it slows down within this distance of min and max counts / degrees Lift 250 (with .part(...): about half a motor turn), Arm 15 no test it slams into its ends (more)
slowZonePower the power at the very end of the slow zone 0 to 1 0.2 no test it stalls near an end (more)
tolerance how close counts as “arrived” counts / degrees Lift 20 (with .part(...): 1/27 of a motor turn), Arm 3 no test actions wait too long (more)
<position> every other number is a named position (down, low, high); any name counts / degrees Lift: down 0, low 900, high 2400. Arm: stowed 0, score 90 (only when none are written) yes, at once Motor Finder (positions P1, P2…) a new scoring height

Section: the device’s name in CamelCase (claw reads Claw, intake_deploy reads IntakeDeploy). Module: Claw, Wrist, Positioner.

Field What it does Units Default Live Found by When to change
travelSec how long the servo takes for a full move, from position 0 to 1 (a shorter move: its share): a servo cannot say when it has arrived, so this is the guess seconds 0.3 (with .part(...): from the servo’s catalog speed, First run) no test (time a full move) actions go on before it arrives (more)
<position> every other number is a named position, 0 to 1. A Claw needs open and closed servo position 0 to 1 Claw: open 0.20, closed 0.75. Wrist: down 0.15, level 0.5, up 0.85. Positioner: stowed 0.0, deployed 0.5 (only when none are written; INIT says they are guesses) yes, at once Servo Finder (P1, P2…) it does not open, close or reach far enough

Section: the device’s name in CamelCase (turret reads Turret). Degrees, 0 = straight forward, counterclockwise +. Which lines your turret needs: Setting up a turret. Module: Turret.

Field What it does Units Default Live Found by When to change
min, max the range: it never turns past them (not used with continuous()) degrees min -90, max 90 no measure (turn by hand, read the angle) the wires or the robot allow more or less
startAngle where it points at INIT with no switch and no angle from the program before degrees 0 (the middle of min to max when 0 is outside it, with an INIT note) no put it there by hand your start mark is not straight forward
kP how hard it turns toward the goal, per degree off power per degree 0.02 yes, at once test slow to aim (more) or shakes (less)
kI a push that grows while it stays short power 0 yes, at once test it stops a degree short
kD brakes it as it gets close power 0.001 yes, at once test it overshoots (more)
maxPower the most power 0 to 1 0.8 no test it swings too hard
tolerance how close counts as aimed degrees 1.0 no test the launcher waits too long (more)
slowZone it slows down within this of min and max degrees 10 no test it hits its ends hard (more)
slowZonePower the power at the very end of the slow zone 0 to 1 0.25 no test it stalls near an end (more)
cameraOffset the camera is turned this much from the turret’s forward (the turret’s own; whole-robot aim uses Tunables.Aim.cameraOffset) degrees 0 no Aim Tuner shots go a little to one side
outOfRangeHold an odometry goal this far past a limit, behind the robot, makes it hold where it points degrees 10 no test it swings limit to limit when the goal is behind
keepAngle TeleOp starts from the angle the last program ended at (within 5 minutes) true/false true no none you turn the turret by hand between programs: false
homeAngle with a home switch: the turret’s angle where the switch closes degrees REQUIRED with homeSwitch(...) no measure (how) the switch moved
homeDirection with a home switch: which way it turns to find it (+1 counterclockwise, -1 clockwise) +1 / -1 from startAngle toward homeAngle no none it looks the long way round
homingPower with a home switch: the power while it looks for it 0 to 1 0.2 no test homing is too slow (more) or hits hard
homingTimeout with a home switch: stop with a fault if the switch is not found in time seconds 5 no test a slow turret needs longer
encoderOffset an absolute encoder: its reading with the turret straight forward degrees 0 no read it at INIT (how) the encoder was moved
posA, angleA a servo turret: one servo position and the turret’s angle there 0 to 1, degrees REQUIRED for Turret.servo no Servo Finder the servo or linkage changed
posB, angleB a servo turret: a second position and its angle 0 to 1, degrees REQUIRED for Turret.servo no Servo Finder the same
travelSec a servo turret: time from posA to posB (not 0 to 1) seconds 0.6 (with .part(...): from the servo’s catalog speed) no test (time it) it fires before it arrives (more)

Section: the rule’s name, keepApart("TurretLift", turret.zone(), lift.zoneAbove()) reads TurretLift. <part> is the part’s section name (its device’s name in CamelCase) with a small first letter: Turret -> turretFrom, LiftLeft (device lift_left) -> liftLeftAbove, not liftAbove (INIT stops on it and says “did you mean liftLeftAbove?”). Each part’s own units. Read once at INIT, never in the tuning menu: a zone that moved while the parts move could let them hit. Guide: interlocks. Module: Interlock.

Field What it does Units Default Live Found by When to change
<part>From, <part>To part.zone(): the zone between two numbers the part’s units REQUIRED with zone() no measure: move the part by hand to the edges the parts or their places changed
<part>Above part.zoneAbove(): the zone from there to the top the part’s units REQUIRED with zoneAbove() no measure the same
<part>Below part.zoneBelow(): the zone from there to the bottom the part’s units REQUIRED with zoneBelow() no measure the same
<part>Margin the extra safety distance around the zone (coasting is already covered by the stop distance) the part’s units turret: the larger of 3 deg and 2 x its tolerance; lift, arm: the larger of 2 x its tolerance and 1/50 of its range; hood: 1/50 of its range; claw, wrist, positioner: 0.02 no test the parts come too close (more)
<part>StopSec a turret, lift or arm: how long it takes to stop from full speed once its power is cut. Its stop distance is its speed x (2 x the slowest of the last 25 loops + half this), see interlocks seconds, 0 to 2 0.15 no test: full stick toward the zone, see where it stops it still ends up past its waiting point (more), or it waits too far out (less)

Section: the device’s name in CamelCase: a launcher on the motor launcher reads Launcher, one on shooter reads Shooter (not Launcher). Module: Launcher.

Field What it does Units Default Live Found by When to change
speedRpm the shot speed when not set by a shot table. At most the motor’s top speed RPM from the parts catalog: 60% of the motor’s top speed, to 10 RPM (6000 RPM motor: 3600) yes, at once Shot Tuner or test shots land short or long
stepRpm one D-pad press changes a fixed speed by this RPM 100 yes, at once none the drivers want bigger or smaller steps
readyBandRpm “at speed” means within this of the target RPM 60 no test it waits too long to fire (more) or shots vary (less)
idleRpm the speed between shots (0 = stop) RPM 0 no test the first shot takes too long to spin up
shotIntervalSec the least time between two shots seconds 0.25 no Flywheel Finder (the recovery) quick shots land short (more)
speedByDistance the shot table: rows of {inches, rpm}, or {inches, rpm, hood} with a hood; blended between rows inches, RPM none: speedRpm no Shot Tuner the field, the balls or the wheel changed
speedByDistance<Name> one more table per game piece, e.g. speedByDistancePollen (used as “pollen”) inches, RPM none no Shot Tuner pieces fly differently
kP the hub’s speed control: push per speed error hub units from the parts catalog: kF / 10 (6000 RPM motor: 1.17) no Flywheel Finder after the Flywheel Finder
kI the hub’s speed control: slow fix of a steady error hub units from the parts catalog: kP / 10 (6000 RPM motor: 0.117) no Flywheel Finder the same
kD the hub’s speed control: brakes changes hub units 0 no Flywheel Finder the same
kF the hub’s speed control: the steady power for a speed hub units from the parts catalog: 32767 / the motor’s top speed in counts per second (6000 RPM motor: 11.7) no Flywheel Finder the same
nominalVolts the battery voltage the gains were found at (battery compensation), 9 to 14 volts 12.8 (INIT notes that it assumes this) no Flywheel Finder after the Flywheel Finder
batteryCompensation with a written kF: scale F by the battery voltage (not yet proven on a real robot). Without your own kF it stays off, with a note true/false false no none to try it: true, then the 4-shot test (lesson 9)
trimStepPercent one speed trim step percent 1 no none the drivers want bigger steps
trimMaxPercent the most speed trim, at most 50 percent 15 no none you need more trim (better: fix the table)

Section: the device’s name in CamelCase: Indexer.servo("feeder") reads Feeder, windmill reads Windmill (not Indexer). Module: Indexer.

Field What it does Units Default Live Found by When to change
rest a servo indexer: where it waits 0 to 1 0.0 yes, at once Servo Finder it blocks the pieces
push a servo indexer: where it pushes a piece into the wheel 0 to 1 0.5 yes, at once Servo Finder pieces are not pushed far enough
pushSec a servo indexer: time to push seconds 0.25 (with .part(...): the servo’s catalog time from rest to push, at least 0.25) yes, at once test it comes back before the piece is gone (more)
returnSec a servo indexer: time to come back seconds 0.25 (with .part(...): the same as pushSec) yes, at once test the next piece jams on it (more)
power a motor or CR servo indexer: power while it feeds (- = the other way) -1 to 1 1.0 yes, at once test pieces are fed too hard or too softly
feedSec a motor or CR servo indexer: time for one piece seconds 0.4 yes, at once test it feeds two (less) or none (more)

Section: the device’s name in CamelCase (hood reads Hood, shooter_hood reads ShooterHood). A servo hood is in servo positions; a motor hood in encoder counts. “Angle” below is your angle, in degrees or any scale you chose, when angleA, angleB (or a calibration) are written; without them it is the position itself. Every default that says “of the range” fits degrees, positions and counts. Module: Hood.

Field What it does Units Default Live Found by When to change
posA the position a hair off one hard stop: a hard limit 0 to 1 / counts REQUIRED, unless calibration yes, at once Hood Finder the hood or its servo was moved
posB the position a hair off the other hard stop 0 to 1 / counts REQUIRED, unless calibration yes, at once Hood Finder the same
angleA, angleB the hood’s angle at posA and posB (both, or neither) your angle unit none: the angle is the position no Hood Finder you want the table in degrees
calibration instead of posA/posB: {position, angle} rows for a linkage; first and last rows are the stops position, angle none no Hood Finder the hood is not a straight gear
startAngle where it goes at START angle angle A no none another start angle
stepAngle one step of step(n) angle 1/25 of the range no none the drivers want bigger steps
byHandPerSec speed with the stick at full angle per second the range in 2.5 s no test the stick is too fast
trimMax the most hood trim angle 1/5 of the range no none you need more trim
tolerance a motor hood: how close counts as arrived angle 1/50 of the range no test the launcher waits too long (more)
travelSec a servo hood: time from stop to stop (posA to posB) seconds 0.5 (with .part(...): from the servo’s catalog speed) yes, at once test (time it) it fires before the hood arrives (more)
kP a motor hood: power per count off power per count 0.005 yes, at once test slow (more) or shakes (less)
maxPower a motor hood: the most power 0 to 1 0.5 yes, at once test it hits its stops hard

Section: the device’s name in CamelCase (limelight reads Limelight, Webcam 1 reads Webcam1). Read once at INIT; not in the tuning menu. Module: Camera.

Field What it does Units Default Live Found by When to change
pipeline a Limelight: the pipeline to use number 0 no the lighting test at INIT the test found a better one at this venue
autoLighting try a few settings at INIT and keep the best (autoPipelines / autoExposure) true/false true no none false to skip the test at a venue you know
exposureMs a webcam: a fixed exposure when the test is off (-1 = the camera’s own) ms -1 no the lighting test at INIT the test found a better one
gain a webcam: the gain for a fixed exposure (-1 = the camera’s highest) number -1 no test pictures are too dark or noisy
lightingSettleMs the test: wait after each switch ms 300 no none pictures are still changing
lightingPictures the test: pictures per setting count 10 no none results vary
lightingMinPictures the test: pictures with tags a setting needs count 3 no none a bad setting wins
lightingAfterStartMs the test: keep trying this long after START (no targets meanwhile) ms 0 no none rarely

Section: each sensor’s device name in CamelCase (intake_color reads IntakeColor). Module: Pieces and PieceSensor. (Pieces itself reads no numbers.)

Field What it does Units Default Live Found by When to change
<KIND> one per kind, the same name as in kinds(...), e.g. POLLEN: the kind’s hue (colour sensor, camera) or the mm read with it (distance sensor) degrees 0 to 360 / mm none: that kind reads UNKNOWN (INIT says so) yes, at once Piece Tuner new lights at a venue, or a moved sensor
nearMm colour and distance sensors: a piece is there below this distance mm 40 (colour), 100 (distance) yes, at once Piece Tuner it sees pieces that are not there
hueTolerance colour sensor, camera: how far from a kind’s hue still counts degrees 30 yes, at once Piece Tuner kinds are mixed up (less)
sizeToleranceMm distance sensor: how far from a kind’s size still counts mm 8 yes, at once Piece Tuner kinds are mixed up (less)
minSaturation colour sensor, camera: how colourful it must be (0 to 1) 0 to 1 0.35 (camera), 0 (colour) yes, at once Piece Tuner the background counts as a piece (more)
steadyMs a reading must stay this long before it counts ms 30 yes, at once test flickers count as pieces (more)
gapMm colour and distance sensors: a dip this big between two touching pieces counts as two (0 = off) mm 8 yes, at once test with two touching pieces two pieces count as one
gain colour sensor: its gain number the sensor’s own no test readings are too dark

Section: the device’s name in CamelCase (light reads Light, blinkin reads Blinkin). Module: Lights.

Field What it does Units Default Live Found by When to change
allianceAtInit shows the alliance (red or blue) during INIT and flashes it at START. A robot.lights.allianceAtInit(...) line in Controls.java wins over this one true/false true no none false: your rules show in INIT too

  • Pieces (the counter): its numbers are in Robot.java (max(4), startsWith(...)). Each sensor has its own section (above).
  • Pedro paths (pedro module): Pedro’s own constants file, not Tunables.java. Pedro uses Tunables.Drive.maxPower (or practiceMaxPower).
  • Shapes (looks(...)), part ids (part(...)) and gear ratios: in Robot.java, because they describe the robot, not a number you tune.

Something wrong on this page? Tell us with a short GitHub form.