# AGENTS.md

PlatformIO / Arduino-framework firmware for a NUCLEO-F042K6 reading two MS5611
barometers (one I2C, one SPI) and emitting validated CSV over the ST-LINK VCP.
See `README.md` for wiring tables, CSV format and status codes.

## Commands

```sh
pio run                     # build (the only real verification gate)
pio run -t upload           # flash over ST-LINK
pio device monitor          # 115200 baud
pio run -t clean
```

There is one env, `nucleo_f042k6`. No lint, format, or typecheck step exists.

**Always read the size report at the end of `pio run`.** It is the acceptance
criterion for any change (see below). Current: flash 88.8% (29108 / 32768),
RAM 32.6% (2004 / 6144).

## Flash budget is the dominant constraint

3.6 KB of flash headroom. This shapes almost every design decision here, and it
is the single easiest thing to break without noticing.

`-flto` and `-fsingle-precision-constant` in `platformio.ini` are load-bearing.
Verified: building without them overflows `FLASH` by **7252 bytes** and fails to
link. Both MS5611 driver libraries write their compensation maths with unsuffixed
double literals, which otherwise links the double soft-float helpers.

Consequences for new code:

- Never introduce `double`, unsuffixed floating literals, or `<math.h>` calls on
  doubles. Use `float` and `f`-suffixed constants.
- Never call `Serial.print(someFloat, digits)` — it resolves to
  `Print::print(double, int)` and pulls in soft-float. `main.cpp::printFixed2()`
  exists solely to format floats via scaled integers; use it.
- Wrap string literals in `F()` so they stay in flash.
- No `String`, no `new`/`delete`. `~BaroChannel()` is deliberately non-virtual
  and `protected` to keep a destructor slot out of the vtable and avoid linking
  `operator delete`. Do not "fix" this into a virtual destructor.
- `lib_deps` pin exact driver tags (`MS5611#0.5.2`, `MS5611_SPI#0.4.3`). Bumping
  them can blow the budget; rebuild and check size if you do.

## Architecture

- `include/config.h` — every pin, address, rate, threshold and validation limit.
  Change hardware behaviour **here**, not in the `.cpp` files.
- `include/baro_channel.h` / `src/baro_channel.cpp` — `BaroChannel`, the shared
  state machine: init + PROM/CRC handshake, per-poll validation, error streaks,
  re-init, and the 1 s error LED pulse. Five pure-virtual `driver*()` hooks are
  the only per-bus surface.
- `src/baro_i2c.cpp` / `src/baro_spi.cpp` — one concrete subclass each, plus a
  file-static instance exposed through `baroI2cChannel()` / `baroSpiChannel()`.
- `src/ms5611_crc.cpp` — standalone PROM CRC-4 (AN520).
- `src/main.cpp` — `setup()`/`loop()`, CSV formatting, 10 Hz report scheduling.

**The two-file split is mandatory, not stylistic.** `MS5611.h` and
`MS5611_SPI.h` each define their own `enum osr_t` and `MS5611_READ_OK`, so they
cannot be included in the same translation unit. `baro_channel.h` therefore
includes neither driver header, and the channels are handed out via accessor
functions. Do not merge these TUs or hoist a driver include into the header.

## Conventions that differ from defaults

- **millis() rollover safety**: deadline comparisons use a signed difference —
  `if ((int32_t)(now - deadline) >= 0)`. Never write `now >= deadline`.
- **`poll()` blocks** for ~2 ADC conversions (~5 ms/sensor at OSR 1024).
  `main.cpp` re-reads `millis()` after polling before evaluating LED and report
  deadlines. Preserve that if you touch the loop.
- **Validation belongs in `BaroChannel`, not the drivers.** Neither library
  checks the PROM CRC, and `MS5611::read()` returns `MS5611_READ_OK` for an ADC
  read taken before the conversion completed (which yields 0). Do not simplify
  the range / stale / all-zero-PROM checks away by trusting driver return codes.
- Style is Allman braces, 2-space indent, `_camelCase` private members, `//  `
  (two spaces) block comments. There is **no `.clang-format`** in the repo —
  running `clang-format` would reformat everything into LLVM style. Don't.
- `SPI` and `I2C` appear in `lib_deps`, but the code uses `Wire` directly and
  nothing includes the third-party `I2C` library.

## Testing

There is no automated test suite. `test/` holds only the stock PlatformIO
README, and `pio test` collects 0 cases. Do not claim tests pass.

`ms5611_crc.{h,cpp}` is intentionally free of Arduino headers so it can be
exercised on the host:

```sh
clang++ -std=c++17 -Wall -Wextra -c src/ms5611_crc.cpp -Iinclude -o /tmp/crc.o
```

The differential check against the NuttX reference described in `README.md` was
a host-side exercise and is not committed.

Anything beyond the CRC needs real hardware: build, flash, and watch the CSV
stream. Unplugging a sensor is the quick way to exercise the error paths
(`ERR_INIT` / `ERR_CRC`, LED pulse, auto re-init after 10 consecutive failures).

## Notes

`.pio/`, `.omo/`, `.junie/` and `.idea/` are gitignored tooling state, not
project sources.
