Distance sensors#
A VL53L0X on any sensor port gives you range in millimetres.
Reading distance#
double mm = exp.getDistanceMm(1);
Returns Double.POSITIVE_INFINITY when nothing is in range.
float mm = expander.distanceMm(1);
Returns INFINITY when nothing is in range.
mm = expander.distance_mm(1)
Returns math.inf when nothing is in range.
The board detects the sensor type by itself, so there is nothing to configure. Asking for a distance from a port holding a colour sensor fails rather than returning nonsense — that is a wiring mix-up, not a reading.
“Nothing in range” being infinity rather than a special case is deliberate: distance < 300 is simply false when there is nothing there, which is what you meant. You never have to remember to test for the sentinel before comparing.
Invalid readings#
A distance sensor cannot always produce an answer. Nothing in range, a surface too dark to bounce enough light back, bright sunlight washing out the return — all of these give you a reading that means “I don’t know” rather than a distance.
If you are reading the telemetry block directly rather than using the everyday getter, check validity before trusting a number:
BBRDigitalExpander.TelemetryBlock t = exp.readTelemetry();
if (t.isDistanceValid(1)) {
double mm = t.distanceMm[1];
}
BBRTelemetry t;
if (expander.readTelemetry(t) && t.distanceValid(1)) {
uint16_t mm = t.sensor[1].distance.distanceMm;
}
t = expander.read_telemetry()
if t.distance_valid(1):
mm = t.sensor[1].distance.distance_mm
Treating an invalid reading as a real distance is the single most common way distance-sensor code goes wrong. The failure looks like the robot confidently doing the wrong thing, because as far as your code is concerned it got a number.
The telemetry block also carries signalRate and ambientRate, which tell you why a reading is weak — low signal means not enough light coming back, high ambient means too much light overall.
Triggering on proximity#
The board can watch the distance for you and drive a digital output when something comes within range:
exp.triggerWhenNear(0, 1, 300); // output 0 high when port 1 sees something within 300 mm
Saved automatically. Your match code reads a DigitalChannel and does no I2C at all.
expander.triggerWhenNear(0, 1, 300); // output 0 high when port 1 sees something within 300 mm
Saved automatically. Your runtime sketch reads the pin with digitalRead() and does no I2C at all.
expander.trigger_when_near(0, 1, 300) # output 0 high when port 1 sees something within 300 mm
Saved automatically. Your runtime script reads the pin as a plain GPIO input and does no I2C at all.
This helper sets a signal-rate floor for you, so weak edge-of-range returns don’t cause the output to chatter. That is exactly the kind of tuning value the everyday tier exists to hide.
triggerWhenNear() stores its range window in class slot 7 of that port. Keep any taught colours on that port in slots 1–6 so they can’t collide.
Full control#
If you need a two-sided window — “between 100 mm and 300 mm” rather than “closer than 300 mm” — use the advanced tier:
BBRDigitalExpander.DistanceClass cls = new BBRDigitalExpander.DistanceClass();
cls.distMin = 100;
cls.distMax = 300;
cls.hysteresisMm = 10; // stops chatter at the boundary
cls.minSignalRate = 100; // reject weak returns
exp.writeDistanceClass(1, 5, cls); // port 1, class slot 5
exp.saveConfigToFlash(); // advanced tier does NOT auto-save
BBRDistanceClass cls;
cls.distMin = 100;
cls.distMax = 300;
cls.hysteresisMm = 10; // stops chatter at the boundary
cls.minSignalRate = 100; // reject weak returns
expander.writeDistanceClass(1, 5, cls); // port 1, class slot 5
expander.saveConfigToFlash(); // advanced tier does NOT auto-save
expander.write_distance_class(1, 5, DistanceClass( # port 1, class slot 5
dist_min=100,
dist_max=300,
hysteresis_mm=10, # stops chatter at the boundary
min_signal_rate=100, # reject weak returns
))
expander.save_config_to_flash() # advanced tier does NOT auto-save
hysteresisMm is what stops the output flickering when the robot sits exactly on the threshold. Leave it at the default unless you have a reason.
Advanced-tier calls do not save to flash. Without saveConfigToFlash(), your configuration is lost at power-off. isConfigDirty() tells you whether there are unsaved changes.
Worked example#
BBRDistanceTriggerSetup is the canonical setup-once OpMode, and BBRDistanceTriggerRuntime is the match half that reads a plain digital input. See Example OpModes.
DistanceRead covers reading, and the TriggerSetup / TriggerRuntime pair is the setup-once configuration plus the runtime sketch that reads a plain pin. See Example sketches.
distance_read.py covers reading, and the trigger_setup.py / trigger_runtime.py pair is the setup-once configuration plus the runtime script that reads a plain GPIO pin. See Example scripts.