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 (a GitHub issue).

Util module

Package: com.crossbotics.ftchelper.util

Learn it hands-on: 06 A fast, clear loop, 10 Autonomous with mechanisms, 13 Testing.

Small helpers used everywhere: number math, angle math, a loop timer, and a way to pass the robot’s position from autonomous to TeleOp. The rest of the library uses them, and you can use them in any code you write. Reach for them when you need one; there is nothing to set up.

Class Use it to
MathUtil keep a number in a range, ignore tiny values, blend two numbers, compare, scale down together
Angles do math with headings (the -180/180 seam)
LoopTimer see how fast your loop runs
FieldPose hold a pose: x, y and heading
PoseStore pass the robot’s pose from Autonomous to TeleOp
BackgroundWorker run slow jobs (network, camera settings) without stopping the loop

Method What it does Example
clamp(value, min, max) keeps a number inside a range. NaN (“not a number”, e.g. 0 / 0) gives 0, a safe value for a motor. Also works with int. clamp(1.3, -1, 1) = 1.0
deadband(value, band) values smaller than band become 0 deadband(0.03, 0.05) = 0
deadbandScaled(value, band) same, but starts again from 0 at the band edge, so there is no jump deadbandScaled(0.55, 0.1) = 0.5
lerp(a, b, t) the number t of the way from a to b (0 = a, 1 = b, 0.25 = a quarter of the way) lerp(0, 10, 0.25) = 2.5
isNear(a, b, tolerance) true if a and b are within tolerance isNear(100, 103, 5) = true
normalize(values, limit) scales an array down together so none is over limit [2, 1, -1, 0], 1 gives [1, 0.5, -0.5, 0]

Headings go around a circle. Plain subtraction is wrong near the -180/180 seam: from 170 to -170 is a 20 degree turn, but -170 - 170 = -340.

Method What it does Example
wrapDegrees(deg) writes any angle between -180 and 180 wrapDegrees(190) = -170
shortestDiff(target, current) the shortest turn from current to target. Positive = turn left (counterclockwise). shortestDiff(-170, 170) = 20
LoopTimer loopTimer = new LoopTimer();
while (opModeIsActive()) {
loopTimer.update(); // top of every loop
telemetry.addData("Loop", "%.0f Hz", loopTimer.getHz()); // loops per second, no decimals
...
}
Method What it does
update() call once at the top of each loop. Returns seconds since the last call.
getHz() loops per second (smoothed). 50-200 is usual; below 30 feels laggy.
getAverageMs() average loop time in milliseconds
getLastSeconds() how long the last loop took
reset() start over

If the loop is slow: turn on bulk reads, send fewer telemetry lines, and make sure nothing in the loop calls sleep().

An x, y (inches) and heading (degrees, counterclockwise positive). The library does not decide where (0, 0) is on the field: your team decides that each season and writes it down.

Member Meaning
new FieldPose(x, y, headingDeg) make one (new FieldPose() = 0, 0, 0)
x, y, headingDeg the values; you can read and change them
copy() a separate copy
toString() text like (12.0 in, -3.5 in, 90.0 deg)

Passes the robot’s last pose from Autonomous to TeleOp. Both run in the same app, so a saved value stays between them (until the app restarts or the robot loses power).

// End of Autonomous:
PoseStore.save(new FieldPose(x, y, auto.headingDeg())); // inches, inches, degrees (left = +)
// TeleOp INIT:
FieldPose start = PoseStore.consume(); // null if autonomous did not save one
if (start != null) { ... use it ... } // always check for null first
// For the heading alone, use DriverHeading (docs/modules/drive.md): it does this for you.
Method What it does
save(pose) saves a copy. null is ignored.
consume() gives the saved pose and forgets it, so it is used once. null if none.
peek() gives a copy and keeps it saved
has() true if one is saved
clear() forgets it
ageSec() seconds since it was saved (very large if none). Over a few minutes = an earlier match.

Runs slow jobs on its own thread (a second line of work that runs at the same time as your loop), so the robot’s loop never waits. The library uses it for the Limelight’s network requests and webcam settings. Guide: Non-blocking code.

// "() -> ..." is a lambda: the slow job, handed over to run on the worker's own thread
BackgroundWorker worker = new BackgroundWorker("my-sensor");
worker.submitLatest("setting", () -> sensor.slowSetting(value)); // returns at once
worker.shutdown(); // in stop()
Method What it does
new BackgroundWorker(name) starts the thread (a daemon: it never keeps the app from closing)
submit(job) queue a job
submitLatest(key, job) queue a job, replacing a waiting one with the same key (only the newest runs)
queued(), getLastError(), isRunning() read; a job that throws never crashes the robot
shutdown() stop; waiting jobs are dropped

Never move motors or servos from a background job: only the loop does that.