BBR Digital Expander Documentation

Java API summary#

The everyday tier, which is what most teams need. Every method validates its arguments and throws a message that names the fix rather than an array index exception.

Getting the device#

BBRDigitalExpander exp = hardwareMap.get(BBRDigitalExpander.class, "expander");

Encoders#

Method Returns
getEncoderCount(channel) Signed accumulated counts
getEncoderVelocity(channel) Signed counts per second, computed on the board
getPulseWidthUs(channel) Pulse width in µs (PULSE_WIDTH mode only)
resetEncoder(channel) —
resetAllEncoders() —

Sensors#

Method Returns
isSensorConnected(port) boolean
getSensorType(port) COLOR, DISTANCE, or EMPTY
getColorClass(port) 0 for no match, else the colour slot 1–7
seesColor(port, classSlot) boolean
getDistanceMm(port) Distance in mm

Teaching and triggers#

All of these save to flash automatically, which is why they are setup calls: run them once, or on a button press, never in a match loop. See Set up once.

Method Effect
teachColor(port, classSlot) Learn the colour currently in view
triggerOnColor(output, port, classSlot) Output high while that colour is seen
triggerWhenNear(output, port, maxMm) Output high while something is within range
triggerWhenEncoderPast(output, channel, counts) Output high past a threshold
triggerWhenFacing(output, headingDeg, toleranceDeg) Output high while facing that heading
setEncoderDirection(channel, direction) FORWARD or REVERSE for one channel
setEncoderVelocityWindowMs(ms) Window for all encoder velocities, 1–255 ms (default 25): longer is smoother, shorter reacts faster
clearOutputLatch(output) Un-latch one output
clearAllOutputLatches() Un-latch all four

Heading and pose#

These throw rather than return a heading or pose the board cannot stand behind.

Method Returns
getHeading() Degrees, CCW positive
resetHeading() —
calibrateGyro() boolean — false if the robot moved; the previous bias is kept (~1 s, keep it still)
getPose() Pose2D — mm, radians, +X forward, +Y left
setPose(pose) —
resetLocalizerAndCalibrateImu() boolean — false if the robot moved; the pose still resets and the localizer still starts
waitForLocalizerReady(timeoutMs) boolean
readImu() ImuState

Mounting and direction#

Set once when the board is bolted on. Unlike setEncoderDirection() above, nothing here auto-saves — follow with saveConfigToFlash().

Method Effect
getEncoderDirection(channel) Which way that channel counts
getEncoderVelocityWindowMs() Current encoder velocity window in ms
setEncoderInvertMask(mask) All four at once; bit per channel, set = inverted
getEncoderInvertMask() Current mask
setImuAxisUp(axis) Which board axis points up (BBRRegMap.AXIS_*, flat is AXIS_POS_Z)
getImuAxisUp() Current axis-up setting

Board identity and health#

Method Returns
getFirmwareVersion() Firmware on the board, e.g. "1.1.1"
getCapabilities() Capability bits
isConfigDirty() Are there unsaved config changes
isDataFresh() Did the last read actually reach the device
getRebootCount() Reboots seen since the Robot Controller app started
readDiagnostics() Why the board last reset, and I2C bus-trouble counters (firmware 1.1.0+)
setReadFailurePolicy(policy) KEEP_RUNNING (default) or STOP_OPMODE when reads keep failing

Advanced tier#

The full register map is available underneath: configureOutput(), writeColorClass(), writeDistanceClass(), setChannelMode(), setPwmChannelParams(), setLocalizerParams(), readTelemetry(), runCommand().

Nothing in the advanced tier saves automatically. Call saveConfigToFlash(), or the configuration is gone at power-off. isConfigDirty() tells you whether you have unsaved changes.

Behaviour worth knowing#

Init refuses loudly. A wrong device ID or an unsupported protocol version throws at init rather than producing corrupt data later.

One transaction per snapshot. The everyday getters share a snapshot refreshed roughly every 10 ms, so four questions in one loop iteration cost one transaction and the answers are mutually consistent.

Commands are idempotent. Every command carries a token, so an I2C retry cannot execute it twice.

Config writes are verified by readback. The bus layer never NAKs, so an out-of-range value would otherwise be silently ignored.

IMU access throws when the gyro cannot be trusted rather than returning zeros, because a heading of 0.00 that never changes is indistinguishable from working software.

Failed reads serve the last known good value. When a read cannot reach the device — or, for the localizer, when three reads in a row fail their checksum — the driver returns the previous snapshot and isDataFresh() goes false, rather than throwing. This is what stops a normal OpMode shutdown, or a burst of bus noise mid-match, from detonating your loop. A long streak of failures (over half a second) logs one warning to the Robot Controller log and keeps going, so anything that acts on a reading, like a flywheel controller, should check isDataFresh() first. For bench testing, setReadFailurePolicy(ReadFailurePolicy.STOP_OPMODE) makes that streak throw instead.

Telemetry is checksummed on firmware 1.1.0 and later. Encoder, sensor and localizer readings that arrive damaged are re-read, so a corrupted value never reaches your code looking real. Older firmware has no checksum on the encoder and sensor block.

Reboots are logged. If the board restarts, the driver notices its uptime clock jump backwards, logs expander REBOOTED with the reason (firmware 1.1.0+), and counts it in getRebootCount().

BBRTransportException means the read never reached the device, not that anything is broken. The SDK tears the I2C device down when an OpMode stops, so a read already in flight comes back empty. A polling loop should catch it and break.