Typed-error catalog
Every driver crate in this workspace exposes a generic Error<E> enum.
The E parameter is the underlying transport error (I²C, SPI, or UART),
which the firmware wraps with Debug2Format for defmt logging.
This page summarises every variant across the workspace so a new contributor (or an AI agent debugging a panic trace) can map an error log line to its root cause without grepping the source.
Common pattern: transport vs protocol
Most drivers split errors into two categories:
- Transport — the bus rejected the operation. Bus glitch, missing
pull-up, chip-not-present, wrong address. Carries the inner bus error
(e.g.
embedded_hal_async::i2c::ErrorKind::ArbitrationLoss). - Protocol — the chip answered but the response was wrong. Bad chip-ID, malformed packet, checksum mismatch, value out of range. Carries the offending byte(s) so logs are self-describing.
Recovery hint: transport errors usually warrant a retry; protocol errors usually mean a wiring or firmware bug and should not be retried blindly.
Per-crate catalog
axp2101::Error<E>
PMU init / battery gauge.
I2c(E)— bus error.
aw9523::Error<E>
I/O expander init.
I2c(E)— bus error.
aw88298::Error<E>
Mono Class-D amplifier (TX path).
I2c(E)— bus error.BadChipId(u16)—CHIPIDregister didn’t return expected ID. AW88298 uses 16-bit big-endian registers; raw value is in the variant.
bm8563::Error<E>
RTC.
I2c(E)— bus error.VoltageLow—VLflag set; backup battery dropped, current time unreliable. Treat as “unset.”
bmi270::Error<E>
6-axis IMU.
I2c(E)— bus error.BadChipId(u8)— wrong device, bus glitch, or held in reset.NotDetected— neither primary nor secondary I²C address answered.InitTimeout— config blob upload didn’t finish; usually truncation or defective part.
bmm150::Error<E>
Magnetometer.
I2c(E)— bus error.ChipId { expected, actual }— caller targeted the wrong address or part is damaged.NotDetected— both candidate addresses failed to respond.Overflow— reading exceeded ADC dynamic range; retry or ignore the sample.
es7210::Error<E>
4-channel ADC (RX path).
I2c(E)— bus error.BadChipId(u8, u8)—(CHIP_ID1, CHIP_ID2)mismatch. ES7210 needs MCLK to answer I²C — see memory note on audio codec quirks.
ft6336u::Error<E>
Capacitive touch.
I2c(E)— bus error.
gc0308::Error<E>
Camera SCCB.
I2c(E)— SCCB (I²C-compatible) bus error.BadChipId(u8)— wrong device or wiring issue.
ltr553::Error<E>
Ambient + proximity.
I2c(E)— bus error.BadPartId(u8)—PART_IDupper nibble mismatch; common cause is a related Lite-On part with a different lux formula.
py32::Error<E>
Co-processor (servo power gate, LED ring).
I2c(E)— bus error.InvalidPin(u8)— caller passed pin outside0..=13.TooManyLeds(usize)— caller passed more thanMAX_LEDSpixels.
scservo::Error<E>
Half-duplex serial servo bus.
Uart(E)— UART transport error.PayloadTooLarge— write exceededMAX_DATA_BYTES; v1 write surface never reaches this.NoResponse— UART closed before full response (rare on open-ended serial; usually a hung slave).MalformedResponse— wrong header bytes or response ID mismatch.ChecksumMismatch— packet arrived but checksum failed.PositionOutOfRange(u16)— position abovePOSITION_MAX(1023); the servo would mis-interpret high bits.BroadcastNotAllowed— caller usedBROADCAST_IDon an operation requiring a response.
si12t::Error<E>
I2c(E)— bus error during init orOUTPUT1read.
ir-nec
No Error enum — decoder returns Option<NecCommand> and treats noise as “no decode” rather than an error.
stackchan_net::ConfigError
RON config parse + validation. Host crate; firmware wraps with Debug2Format for defmt logging. Validators only fire through parse_ron — the firmware’s Config::default() fallback path never trips them.
Parse(SpannedError)— RON syntax error / missing field / type mismatch. Inner value carries(line, col).Serialize(ron::Error)— RON round-trip failure onrender_ron. Should not happen with well-formedConfig; treat as a bug.EmptySsid—wifi.ssidempty / whitespace-only on disk. DefaultWifiConfighas an empty SSID by design (the offline-first sentinel), but explicit blanks in a written file are almost always mistakes.InvalidCountry(String)—wifi.countrynot exactly two uppercase ASCII letters. esp-wifi expects ISO-3166 alpha-2 in canonical case ("US","JP"); lowercase silently mis-applies the regulatory mask at the driver layer, so the validator pins the case here.InvalidHostname(String)—mdns.hostnamefailed RFC-952 subset (alphanumerics + hyphen, must start with letter, no trailing hyphen, length 1-63).NoSntpServers—time.sntp_serversempty. Firmware needs at least one candidate.EmptySntpServer(usize)— atime.sntp_serversentry was empty / whitespace-only. Theusizecarries the offending index. Caught at parse time so the firmware’s “try in order” loop doesn’t burn its full per-server timeout on an unresolvable hostname before falling back.InvalidVolumePct(u8)—audio.volume_pctwas outside0..=100. The wire format is a percentile; the firmware maps it linearly across the AW88298 dB range. Theu8carries the offending value.
stackchan_tts::RenderError
Speech-backend rendering. Returned from SpeechBackend::render when a
backend that answered can_handle == true can’t produce audio for the
specific utterance, or is currently degraded. #[non_exhaustive].
UnsupportedContent— content kind is supported but the specificPhraseId/ dynamic handle isn’t baked into this backend yet. Caller should fall through to the next backend in registration order.AssetMissing— a required asset (PCM file, cached response) is unavailable. Distinct fromUnsupportedContentbecause the content kind itself is supported in principle.BackendUnavailable— the backend is in a degraded state (Wi-Fi down for a cloud backend, decoder failed to initialize). Caller may retry later.
Firmware-side error wrapping
stackchan-firmware does not introduce its own typed-error enum. Init
failures inside #[esp_rtos::main] panic via defmt::panic! because
there’s no caller to bubble to. Per-task failures are logged with
defmt::warn! or defmt::error! and the task continues, parks
forever, or restarts depending on severity (see each src/<chip>.rs).
Adding a new error variant
When adding a variant to a driver crate’s Error:
- Document what triggered it and what to do about it in the doc-comment — the catalog above relies on this to stay accurate.
- Carry the offending value (
u8,u16, etc.) when it aids debugging. Logs that just say “bad chip ID” without the byte are hard to triage. - Update this catalog in the same PR.