Skip to content

refactor: sensor seam — metadata table, drawing, panel dispatch (P2, inc. 1-3, hw-validated) - #170

Merged
angeloINTJ merged 3 commits into
mainfrom
feat/features-p2-sensors
Sep 26, 2026
Merged

angeloINTJ merged 3 commits into
mainfrom
feat/features-p2-sensors

Conversation

@angeloINTJ

@angeloINTJ angeloINTJ commented Sep 26, 2026 •

Copy link
Copy Markdown
Owner

P2 sensor seam — the tractable de-entanglement, hardware-validated (increments 1–3)

The sensor pipeline carried 85 #if SIMUT_SENSOR_* sites across 13 files,
the largest entanglement concentration in the tree, and all three families are
on in every profile — the switchability is scaffolding no build exercises. This
PR is the part of the seam that de-entangles cleanly and carries strong safety
proofs. Sensor-macro sites: 85 → 50. Tree watermark: 283 → 246. All three
increments validated on the bench rig across all three sensor families.

Increment 1 — per-type metadata becomes a table

The four switch(SensorType) helpers in SensorHelpers.h become lookups over
SENSOR_TYPE_TABLE (name, enabled, interval, channel mask, pins). Rows are
unconditional; enabled is the feature macro as a value (0/1), not an
#if. Adds the first native test the sensor pipeline ever had — test_sensors
(native_sensors, the 8th suite). Watermark 281 → 269.

Increment 2 — SensorDrawing.h leaves the sensor macros

Seven #if SIMUT_SENSOR_* guards on inline icon helpers removed — an inline
helper no enabled driver calls is dropped by the linker (--gc-sections)
regardless. pico_w_release firmware.bin byte-identical before/after
(sha256 181a5fa3…). Watermark 269 → 254.

Increment 3 — table-driven panel dispatch

sensorRenderPanel/sensorRenderMinMax switch(type) with a #if per case
becomes a lookup over SENSOR_PANEL_DISPATCH, each type paired with a thin
adapter forwarding the uniform argument list to the driver's own (differing)
renderer. Watermark 254 → 246. Cost: +1184 B on release (999 676 B, 1 176 B
under budget) — the function pointers stop the compiler inlining the renderers,
the honest price of data-driven dispatch. Deleting the guards instead would be
0 B but would game the watermark and fail to compile a family-off build.

Hardware validation (rig 192.168.3.24, flashed by USB)

  • /api/sensors type catalogue byte-identical to the pre-seam firmware:
    the SENSOR_TYPE_TABLE produces the same names, channel masks, pin maps and
    serial-number flags on real silicon.
  • five sensors reading across all three families via /api/status: 2× DS18B20,
    2× DHT22 (temp + humidity), 1× BMP280 (temp + pressure).
  • dashboard renders identically: DHT22 panel (thermometer + temp + drop +
    humidity), DS18B20 panel (thermometer + temp), and the alarm blink phase
    crosses the increment-3 adapters correctly (screenshots caught the yellow
    band alternating).

What is left, and why it is a separate decision

The remaining sensor sites live in SensorManager (the scan and read paths and
the driver members) and the DS18B20 cross-file leaks (ROM identity,
ds18Resolution). De-entangling them cleanly needs a uniform driver registry —
but the three drivers are heterogeneous with no common interface: DS18B20 is
one PIO machine batch-converting across pins, DHT22 one machine reading
sequentially, BME280 a std::vector of instances per (bus, address) with a
factory and chip-ID retyping. The doc's SensorTypeDef { begin, update, renderPanel } vision assumes a uniform update, which does not fit a read path
where DS18B20 fires every pin at once. That is an architecture decision on the
measurement pipeline — historically the most bug-prone code in the tree — and
belongs in its own PR, designed with the maintainer, not bundled here.

Stacking

Stacks on #169 (routes seam); base feat/features-p2-routes, watermark
cumulative. Merge #169 first; GitHub retargets this to main.

🤖 Generated with Claude Code

https://claude.ai/code/session_01Jq4auDQppGzmAx1gLKn1z3

…increment 1)

The sensor pipeline carries 85 #if SIMUT_SENSOR_* sites across 13 files, and the
three families are on in every profile today — the switchability is scaffolding
no build exercises. The P2 sensor seam replaces that scaffolding with data, one
increment at a time. This is the first: the four switch(SensorType) helpers in
SensorHelpers.h (sensorTypeName, sensorTypeEnabled, sensorDefaultIntervalMs,
SensorFormat::forType), each of which carried its own #if SIMUT_SENSOR_* per
case, become lookups over a single data table:

    SENSOR_TYPE_TABLE — one row per driver type: name, enabled, default
    interval, channel mask, pin map.

Rows are unconditional; `enabled` is the feature macro used as a VALUE (0/1),
not an #if, so this header no longer spreads SIMUT_SENSOR_* across its helpers.
The metadata is pure data and stays available even for a family compiled out,
which a slot configured for an absent driver needs to render "not available".

Behavior: every profile ships all three families, so the table returns exactly
what the switches did for every build that exists (cross-checked on host). The
only divergence is a not-yet-existing build that compiles a family out, where
sensorTypeName returns the real name instead of "Unknown" — the intended model
behavior.

The sensor pipeline had NO native test coverage. The table is pure data, so this
adds the first: test_sensors (native_sensors env) pins the table's contract —
names, enabled, intervals, channel masks (including the BMP280 humidity hole),
pins, value/limit counts, serial-number capability. A minimal hardware/gpio.h
host stub lets SensorHelpers.h parse on the host (its inline gpioInitForRole
names the Pico SDK gpio_* calls).

Entanglement watermark: 281 -> 269 (SIMUT_SENSOR_DS18B20 35->31,
SIMUT_SENSOR_BME280 27->23, SIMUT_SENSOR_DHT22 23->19 — all twelve sites in
SensorHelpers.h).

Verified:
  - test_sensors: 9 cases pass; all 7 pre-existing native suites still pass
  - host cross-check: the table returns the switches' exact values, every type
  - pico_w_release: Flash 998492 B (+88), 2360 B under budget
  - pico_w_air: Flash 1003344 B (-8), 2944 B under budget
  - all gates green (authz, angulo, air, gen_features, check_features)
  - not flashed on hardware — the sensor drivers are unchanged; only the
    metadata switches became a table. Hardware validation still owed.

Docs kept honest: CLAUDE.md / CONTRIBUTING.md / docker-compose.yml native-suite
counts corrected to eight (two were already stale at "six"); CI runs
native_sensors as its own step.

Stacks on #169 (the routes seam): the watermark is cumulative, so merge #169
first.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Jq4auDQppGzmAx1gLKn1z3
@github-actions github-actions Bot added documentation Improvements or additions to documentation tests Testing infrastructure and test coverage ci Continuous integration and automation tools Build tools, scripts, and developer tooling code Firmware source changes (src/, WebUI.h) labels Sep 26, 2026
…ncrement 2)

SensorDrawing.h wraps its procedural panel icons (thermometer, drop, barometer,
the min/max helpers) in per-family #if SIMUT_SENSOR_* so a disabled sensor's
icon left the flash — seven guards, fifteen macro sites. The guards are
redundant: the icons are `inline`, and an inline helper no enabled driver calls
is dropped by the linker (--gc-sections) whether or not a #if wraps it, which is
exactly how the ungated °C helpers in the same file have always worked. Removing
them takes SensorDrawing.h out of the sensor feature macros entirely.

Every profile compiles all three families, so all seven guards are true today
and removing them changes nothing in any current build: the pico_w_release
firmware.bin is BYTE-IDENTICAL before and after (sha256 181a5fa3…). The
flash-strip guarantee for a hypothetical family-off build moves from the
preprocessor to the linker, where the °C helpers already relied on it.

Entanglement watermark: 269 -> 254 (SIMUT_SENSOR_DS18B20 31->28,
SIMUT_SENSOR_BME280 23->16, SIMUT_SENSOR_DHT22 19->14 — all fifteen sites in
SensorDrawing.h).

Verified: byte-identical release firmware; all 8 native suites pass; gates green.
Not flashed on hardware — the binary is unchanged.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Jq4auDQppGzmAx1gLKn1z3
@angeloINTJ angeloINTJ changed the title refactor: SensorTypeDef table for per-type metadata (P2 sensor seam, increment 1) refactor: sensor metadata table + drawing de-entanglement (P2 sensor seam, inc. 1-2) Sep 26, 2026
sensorRenderPanel/sensorRenderMinMax dispatched by switch(SensorType), each
case guarded by #if SIMUT_SENSOR_* so a case never referenced a driver's render
function that was compiled out — eight sites. They are now table lookups:
SENSOR_PANEL_DISPATCH pairs each type with a thin adapter that forwards the
uniform argument list to the driver's own renderPanel/renderMinMax. The drivers
keep their own (differing) signatures and bodies untouched — a DS18B20 takes one
value, a DHT22 two, a BMP280 two of a different pair — the adapter absorbs the
difference.

This file compiles only in TFT builds (DisplayManager_Dashboard leaves the
filter otherwise) and every TFT profile ships all three families, so the table's
function pointers always resolve. Per-family conditionality returns with the
driver-to-.cpp step, which excludes a driver's unit from the link.

Entanglement watermark: 254 -> 246 (SIMUT_SENSOR_DS18B20 28->24,
SIMUT_SENSOR_BME280 16->14, SIMUT_SENSOR_DHT22 14->12 — all eight in
SensorPanelDispatch.h).

Cost: +1184 B on pico_w_release (999676 B, 1176 B under budget). The function
pointers stop the compiler inlining the driver renderers into the dispatch, the
way the switch did. This is the honest price of data-driven dispatch; deleting
the switch guards instead would be 0 B but would game the watermark — the file
would still hard-code every type's renderer and would fail to compile the moment
a family is turned off, which is exactly the coupling the seam is removing.

VALIDATED ON HARDWARE (rig 192.168.3.24, pico_w_release flashed by USB):
  - dashboard renders identically to before: DHT22 (AMBIENTE) thermometer +
    temp + drop + humidity; DS18B20 (ESCONDERIJO) thermometer + temp; the alarm
    blink phase (isRedPhase) crosses the adapters correctly (two frames caught
    the yellow alarm band alternating).
  - all five sensors read across the three families via /api/status: DS18B20
    24.4/24.8, DHT22 24.8/74% and 25.0/75%, BMP280 24.8/1007.5 hPa.
  - flash budget passes; sprawl gate at 246; native_sensors + gates green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Jq4auDQppGzmAx1gLKn1z3
@angeloINTJ angeloINTJ changed the title refactor: sensor metadata table + drawing de-entanglement (P2 sensor seam, inc. 1-2) refactor: sensor seam — metadata table, drawing, panel dispatch (P2, inc. 1-3, hw-validated) Sep 26, 2026
@angeloINTJ
angeloINTJ deleted the branch main September 26, 2026 12:25
@angeloINTJ angeloINTJ closed this Sep 26, 2026
@angeloINTJ angeloINTJ reopened this Sep 26, 2026
@angeloINTJ
angeloINTJ changed the base branch from feat/features-p2-routes to main September 26, 2026 12:26
@angeloINTJ angeloINTJ closed this Sep 26, 2026
@angeloINTJ angeloINTJ reopened this Sep 26, 2026
@angeloINTJ
angeloINTJ merged commit bfb819a into main Sep 26, 2026
12 checks passed
@angeloINTJ
angeloINTJ deleted the feat/features-p2-sensors branch September 26, 2026 12:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ci Continuous integration and automation code Firmware source changes (src/, WebUI.h) documentation Improvements or additions to documentation tests Testing infrastructure and test coverage tools Build tools, scripts, and developer tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant