Troubleshooting#
The OpMode throws at init#
“Not a BBR Digital Expander: DEVICE_ID …” — something answered at the address, but it wasn’t an Expander. Check you picked the right I2C bus in the robot configuration, and that nothing else on the bus is at address 0x38.
Device not found in hardwareMap — the name in your code doesn’t match the name in the robot configuration. The examples use expander.
Configuration doesn’t list “BBR Digital Expander” — the driver folder isn’t in your project, or the project didn’t rebuild. Confirm com/botbuilders/bbr/ is under TeamCode/src/main/java/ and rebuild.
If you used this board before it was renamed, your old robot configuration entry will no longer resolve. Delete it and add “BBR Digital Expander” fresh.
A sensor reads EMPTY#
getSensorType(port) returning EMPTY means the board genuinely cannot see a sensor on that port.
- Check the cable at both ends.
- Try the sensor on a different port — that separates a bad sensor from a bad port.
- Run
BBRSensorDumpTest, which shows all four ports at once.
teachColor() throws#
Working as intended. It refuses to store a colour that would never fire or always fire — too dark, saturated, or no sensor on the port.
- Too dark — more light, or hold the target closer.
- Saturated — less light, or move the target back.
- Then teach again.
A stored bad colour is much harder to debug than a refused teach, which is why it fails at the point where you’re standing there holding the target.
A digital output never goes high#
- Was the colour taught?
triggerOnColor()refers to a slot thatteachColor()has to have filled first. - Is the sensor seeing it? Check
getColorClass(port)returns the slot number you expect. - Is it saved? The trigger helpers auto-save, but advanced-tier
configureOutput()does not. CheckisConfigDirty(). - Is the Hub input configured? The digital input needs its own entry in the robot configuration.
- Is it wired to the pin you think? Output 0 is not necessarily the first pin on the connector.
A digital output flickers#
The condition is sitting right on the threshold. Use hysteresis rather than trying to be more precise:
- Distance: raise
hysteresisMmin aDistanceClass. - Any output: raise
debounceAssert/debounceReleasein anOutputConfig.
Both are set to sensible defaults by the everyday helpers, so if you are seeing chatter you are probably on the advanced tier.
Heading or pose throws#
The board refuses to hand you a heading it cannot stand behind, rather than returning a plausible-looking 0.00. Read the message — it names which case you are in:
- The gyro is not responding. A hardware fault, or a board that lost power mid-transaction. Power-cycle the robot; if it persists on every boot, the board needs looking at.
- The board stopped answering entirely. Check the I2C cable, then see The OpMode throws at init.
- The localizer was never started. Pose methods need
resetLocalizerAndCalibrateImu()first — heading alone does not.
An exception here is the driver working correctly. A heading of 0.00 that never changes is indistinguishable from working software right up until the match.
Heading drifts#
- Run
calibrateGyro()with the robot completely still. - Check
isBiasValid()— false means it hasn’t had a good calibration. - Check
isGyroSaturated()— if the robot spun faster than the gyro can measure, the estimate is no longer trustworthy and needs a re-zero.
The pose is wrong#
getPose()throws unless the localizer isRUNNING— check the status first.- Verify pod geometry and ticks-per-mm. The board cannot detect a wrong measurement; it just accumulates error.
- Check the pods stay in contact with the floor. Skipping loses counts permanently.
- Calibrate with the robot still.
The OpMode crawls, roughly one loop per second#
A setup helper is being called inside your match loop with values that keep changing.
teachColor() and the triggerOn… / triggerWhen… helpers save to flash, and the board accepts only one save per second. The driver waits that limit out rather than throwing, so each call costs your loop up to a full second.
- Move the call into a setup OpMode, or behind a button press with edge detection (
if (gamepad1.a && !prevA)), so holding the button doesn’t re-fire it every loop. - If the value genuinely has to change during a match, use advanced-tier
configureOutput(), which changes the trigger in RAM without touching flash, and callsaveConfigToFlash()only when you want it to persist.
Repeating a call with unchanged values is not the cause — the board recognises there is nothing new to store and skips the write. It is changing values every loop that costs you.
See Set up once for what this also does to your triggers while the board is writing.
Readings freeze mid-match#
Check isDataFresh(). When a read can’t reach the device the driver serves the last known good value rather than throwing, so a stale reading looks like a working one that stopped changing.
A genuine wiring fault — a sustained streak of failures — throws a BBRException naming it. A brief blip does not.
Everything breaks when the OpMode stops#
Expected, and already handled. The SDK tears the I2C device down at stop, so a read already in flight comes back empty. The driver raises BBRTransportException for that case.
If you have a polling loop, catch it and break — the IMU and localizer examples show the pattern. It means “the read never reached the device”, not “something is broken”.
Configuration lost after power-off#
Advanced-tier calls don’t auto-save. Call saveConfigToFlash(). isConfigDirty() tells you whether you have unsaved changes.
The everyday teach and trigger helpers save for you.