Colour sensors#
The board doesn’t just hand you raw red, green, and blue numbers and leave you to work out what they mean. You teach it the colours you care about, and then ask it a direct question: am I looking at colour 1?
Teaching a colour#
Hold the target in front of the sensor and teach it:
exp.teachColor(0, 1); // sensor port 0, this is now "colour 1"
expander.teachColor(0, 1); // sensor port 0, this is now "colour 1"
expander.teach_color(0, 1) # sensor port 0, this is now "colour 1"
That takes a moment, and it saves to the board’s flash automatically. You do not need to call saveConfigToFlash() afterwards — the teach helpers do it for you.
Each port remembers up to 7 colours, numbered 1 to 7.
exp.teachColor(0, 1); // hold red → colour 1
exp.teachColor(0, 2); // hold blue → colour 2
exp.teachColor(0, 3); // hold yellow → colour 3
expander.teachColor(0, 1); // hold red → colour 1
expander.teachColor(0, 2); // hold blue → colour 2
expander.teachColor(0, 3); // hold yellow → colour 3
expander.teach_color(0, 1) # hold red -> colour 1
expander.teach_color(0, 2) # hold blue -> colour 2
expander.teach_color(0, 3) # hold yellow -> colour 3
Because it is stored in flash, teaching survives power cycles and applies to everything that reads the board afterwards — a different program, a different language, a different host entirely. Teach at the start of the day, use all day.
When teaching refuses#
Teaching fails rather than storing a colour the board doesn’t believe in — if the target is too dark, if the sensor is saturated, or if there is no sensor on that port.
This is on purpose. A badly taught colour is worse than no colour at all: it either never fires or always fires, and both look like a code bug rather than a calibration problem. Better to find out while you’re standing at the robot holding the target.
If it refuses, the usual fixes are more light, less light, or holding the target closer.
Reading colours#
int which = exp.getColorClass(0); // 0 = no match, 1-7 = which colour
boolean isRed = exp.seesColor(0, 1); // just asking about colour 1
seesColor() is usually the one you want — it reads as the question you are actually asking:
if (exp.seesColor(0, 1)) {
telemetry.addLine("over red");
}
uint8_t which = expander.colorClass(0); // 0 = no match, 1-7 = which colour
bool isRed = expander.seesColor(0, 1); // just asking about colour 1
seesColor() is usually the one you want — it reads as the question you are actually asking:
if (expander.seesColor(0, 1)) {
Serial.println(F("over red"));
}
which = expander.color_class(0) # 0 = no match, 1-7 = which colour
is_red = expander.sees_color(0, 1) # just asking about colour 1
sees_color() is usually the one you want — it reads as the question you are actually asking:
if expander.sees_color(0, 1):
print("over red")
Both fail safe. A dark, saturated, stale or unplugged sensor reads 0 — “none of the colours I know” — rather than confidently naming the wrong one.
Teaching at the field, not in code#
Rather than hard-coding a teach call, let a person do it at the robot:
if (gamepad1.a && !prevA) {
try {
exp.teachColor(0, 1);
status = "learned colour 1";
} catch (BBRDigitalExpander.BBRException e) {
status = "teach failed: " + e.getMessage();
}
}
prevA = gamepad1.a;
Catching the exception and showing it in telemetry turns a failed teach into something the driver can immediately react to, instead of a stack trace.
int c = Serial.read();
if (c >= '1' && c <= '7') {
if (expander.teachColor(0, (uint8_t)(c - '0'))) {
Serial.println(F("learned"));
} else {
Serial.print(F("teach failed: "));
Serial.println(expander.lastErrorText());
}
}
Printing lastErrorText() turns a failed teach into something you can immediately react to — it names the reason, and usually the fix.
answer = input("colour 1-7: ").strip()
if answer in "1234567" and answer:
try:
expander.teach_color(0, int(answer))
print("learned")
except BBRError as exc:
print(f"teach failed: {exc}")
The message on the exception turns a failed teach into something you can immediately react to — it names the reason, and usually the fix.
The edge detection matters as much as the error handling. Teaching writes to flash, and the board accepts at most one save per second — so a held button teaching on every loop would stall you to a crawl. One press, one teach. See Set up once.
Letting the board watch for you#
Once a colour is taught, the board can drive a digital output whenever it sees it — with no I2C traffic and no code in your loop at all:
exp.triggerOnColor(0, 0, 1); // output 0 high while port 0 sees colour 1
This also saves automatically. Your match code then reads a stock DigitalChannel.
expander.triggerOnColor(0, 0, 1); // output 0 high while port 0 sees colour 1
This also saves automatically. Your runtime sketch then reads the pin with digitalRead().
expander.trigger_on_color(0, 0, 1) # output 0 high while port 0 sees colour 1
This also saves automatically. Your runtime script then reads the pin as a plain GPIO input.
See Digital outputs and triggers.
Raw values#
If you need the underlying numbers — for tuning, or for a classifier of your own — they are in the telemetry block:
BBRDigitalExpander.TelemetryBlock t = exp.readTelemetry();
int[] rgbip = t.rawColor[0]; // r, g, b, ir, proximity
int confidence = t.classConfidence[0]; // 0-255
BBRTelemetry t;
if (expander.readTelemetry(t)) {
uint16_t red = t.sensor[0].color.red; // also .green .blue .ir .proximity
uint8_t confidence = t.sensor[0].confidence; // 0-255
}
Read .color only on a port whose type is BBR_STYPE_COLOR; on a distance port the same bytes carry .distance instead.
t = expander.read_telemetry()
red = t.sensor[0].color.red # also .green .blue .ir .proximity
confidence = t.sensor[0].confidence # 0-255
.color is populated only on a port whose type is SensorType.COLOR; on a distance port .distance is populated instead and .color is None.
Classification uses normalized chromaticity rather than raw brightness, which is why a taught colour keeps working as the lighting changes between your workshop and the competition venue.
Worked example#
BBRTeachColorExample and BBRThreeColorExample both teach from a gamepad button properly. BBRColorClassExample covers reading. See Example OpModes.
ColorTeach teaches from the serial monitor, and ColorRead shows classes, confidence and raw channels for every port. See Example sketches.
color_teach.py teaches from the terminal, and color_read.py shows classes, confidence and raw channels for every port. See Example scripts.