Your first script#
This reads a colour sensor on port 0 and prints what it sees. It is about as small as a useful Expander script gets.
import time
from bbr_digital_expander import BBRDigitalExpander, BBRError
try:
with BBRDigitalExpander(bus=1) as expander:
print(f"sensor on port 0: {expander.sensor_type(0).name}")
while True:
print(f"colour class {expander.color_class(0)} "
f"encoder 0 {expander.encoder_count(0)}")
time.sleep(0.1)
except KeyboardInterrupt:
pass
except BBRError as exc:
print(f"expander: {exc}")
You should see what kind of sensor is on port 0 before anything else happens. If it reports EMPTY, the sensor isn’t detected — check the connection.
Teaching it a colour#
color_class() returns 0 until you have taught the board what to look for. Teaching takes one call, and the result is saved to the board’s flash automatically:
expander.teach_color(0, 1) # what the sensor sees right now is "colour 1"
Run that once, with the target held in front of the sensor, and from then on color_class(0) returns 1 whenever it sees that colour again — including after a power cycle, and including from a completely different script or a different host entirely.
There is a full walkthrough in Colour sensors.
Errors are exceptions#
Everything that can fail raises a subclass of BBRError, carrying a sentence that names the failure and usually the fix:
from bbr_digital_expander import BBRError, NoImuError, WrongSensorTypeError
try:
mm = expander.distance_mm(0)
except WrongSensorTypeError:
print("that port has a colour sensor on it")
except BBRError as exc:
print(exc) # the sentence
print(exc.status) # a Status value you can branch on
Catch BBRError when you just want a message, and a specific subclass when you want to do something different about it. BadArgumentError is also a ValueError, because a port number out of range is a bug in your code rather than a fault in the hardware.
There are exactly two sentinels rather than exceptions, chosen so the obvious code is still correct when there is no answer:
| Call | When it cannot answer |
|---|---|
distance_mm(port) |
math.inf, so distance_mm(0) < 300 is simply false |
color_class(port) |
0, the same as “none of the colours I know” |
A heading is not one of them. heading() raises on a board with no IMU, or one whose IMU has stopped answering, because a heading of 0.00 that never changes is indistinguishable from working software.
How reads work#
Every everyday getter — encoders, sensors, colours, distances — is served from a snapshot the driver refreshes when it is older than 10 ms.
This matters because it means asking four questions in one loop iteration costs one I2C transaction, not four:
a = expander.encoder_count(0)
b = expander.encoder_count(1)
red = expander.sees_color(2, 1)
mm = expander.distance_mm(3)
# all four answers came from the same snapshot, one transaction
It also means those values are mutually consistent — they were all captured at the same instant by the board, not read one at a time while the robot moved.
You don’t have to manage this. Just call the getters. telemetry_max_age_ms changes the window if you want a faster or slower refresh, and invalidate_telemetry() forces the next getter to go to the device.
Which loop speed?#
The board samples continuously on its own, so your loop rate does not affect what it measures — only how often you look.
This is worth more on a Pi than on a microcontroller. Linux is not a real-time system: your process can be descheduled for tens of milliseconds at a time, and time.sleep(0.01) is a request rather than a promise. Because the board does the sampling, a late loop misses nothing — the next snapshot is still current, and encoder counts accumulated while you were away.
Where timing genuinely matters, use the triggers: the board watches the condition itself and drives a pin, and your loop cost drops to one GPIO read that the scheduler cannot make wrong. See Digital outputs and triggers.
What to do next#
- Four extra encoders → Encoders
- Detect a colour → Colour sensors
- Detect an object → Distance sensors
- Get the board to do the watching for you → Digital outputs and triggers