Skip to content

feat(firmware): PDU monitoring node and TELEM_n telemetry frame (#216) - #343

Merged
mottihoresh merged 6 commits into
mainfrom
feat/pdu-telemetry-216
Oct 15, 2026
Merged

mottihoresh merged 6 commits into
mainfrom
feat/pdu-telemetry-216

Conversation

@mottihoresh

@mottihoresh mottihoresh commented Oct 15, 2026 •

Copy link
Copy Markdown
Collaborator

Closes #216.

What

Firmware for the PDU monitoring node (#202 hardware) and the new TELEM_n telemetry frame, as designed in #216 (design signed off 2026-10-15; builds on #340's serviceNodeStatus()).

Piece Where
TELEM_n = canIdTelem(n) = 0x180 + n, 8-byte TelemPayload (byte 0 = page) CANProtocol.h
_NODE_TELEM host contract (NODE_TELEM_PROTO_VERSION 1), TelemPage, PduRailFlag, PduStatusFlag, NodeFaultCode FUSE_OPEN / OVER_TEMPERATURE / SENSOR_FAULT NodeStatus.h
Forward every TELEM_n frame to the host unread as _NODE_TELEM <nodeId><8 bytes> (page 0 / short dropped, never liveness) PanelBridge.cpp
The node: sampling, conversions, fault flags, telemetry schedule, calibration store, DiagSerial CAL commands new Firmware/Libraries/PowerMonitor/
Three identical thin sketches, NODE_IDs 3 / 4 / 5 (registered) `Firmware/Panels/PDU_Left

Pages — 0x01 PDU_RAIL_12V / 0x02 PDU_RAIL_5V: flags · vAvgMv · vMinMv · iAvgMa (int16); 0x03 PDU_STATUS: flags · NTC 0.1 °C · peak 12 V / 5 V mA. One frame per second rotating the three pages (each page every 3 s), plus an immediate frame when a page's flags change (≤ 1 / 100 ms / page); SYNC_REQ sends all three. Rail / thermal faults also reach HEALTH_n.faultId through the FaultSource registry. Full layouts in FirmwarePlan 02; decision D18.

Calibration — per board in the last 1 KB flash page (0x0800FC00), append-only 32-byte CRC records; board_upload.maximum_size = 64512 keeps code out of it. DiagSerial: CAL SHOW, CAL VDDA <mV> (DMM on 3V3 — VDDA is assumed 3300 mV until anchored), CAL ZERO (no load; 75 / 120 mA guards — the 5 V zero subtracts the PDU's own draw), CAL CLEAR, RAW.

Design decisions carried from #216: status LED unchanged (CAN/link + master-loss only); 500 ms fault blanking at power-up; no PanelGroup dependency (the analog pins are read directly — PinRef lives in PanelGroup); all thresholds are PDU_* build flags.

One small change from the #216 text: on SYNC_REQ the node sends all three pages (not just the next one), so a reconnecting host gets the full picture at once.

Tests — 2026-10-15, PanelGroup Rev 0.1.0 board + CANable2 (not a PDU, so analog readings are meaningless)

  • New Tests/PowerMonitor, on hardware, 106 checks, 0 failures: pdu_math 26 · rail_monitor 27 · board_thermal 16 · telem_schedule 12 · cal_store 25 (real flash page: save / load, newest wins, corrupt record skipped, full-page wrap, left erased).
  • PDU firmware on a real bus (PDU_Left, CANable2 ACKing + logging): READY_3 at boot; HB_3 every 500 ms (first 500 ms after boot); HEALTH_3 every 1000 ms; TELEM_3 once per second rotating 01 → 02 → 03; boot edge frame (UNCAL flags) and per-page edges when the 500 ms blanking ended; SYNC_REQ → pages 01, 02, 03 within 300 ms; 1000 rail scans/s; CAL VDDA / CAL ZERO / CAL CLEAR / bad-command all behave; board left uncalibrated.
  • PanelBridge startup_sequence / output_submission re-run on this branch: HB_0 500 ms, HEALTH_0 1000 ms, exports → CTRL_BCAST, no errors.
  • Tests/CANProtocol protocol_layout: canIdTelem range + TelemPayload size asserts.
  • All 37 firmware projects compile (CI's PlatformIO build is weekly/manual, so this local sweep is the build evidence); no new warnings · check_doc_refs.py · check_node_ids.py (4 production sketches) · MkDocs build clean.
  • Calibration survives a reflash: CAL VDDA + CAL ZERO stored, re-uploaded with pio … -t upload (OpenOCD), CAL SHOW unchanged.
  • Tests/PanelBridge test_node_telem (_NODE_TELEM forwarding) — compiles; running it needs the USB-UART adapter on PA2/PA3 (250 kbaud) → moved to feat(pcb): per-console power distribution + monitoring CAN node (PDU) — INA180 telemetry #202's bench list.
  • On the PDU itself (real readings, per-board CAL VDDA / CAL ZERO, threshold tuning, fault checks) → moved to feat(pcb): per-console power distribution + monitoring CAN node (PDU) — INA180 telemetry #202's bring-up checklist.
  • Client decode + display → feat: node data layer — PDU telemetry, node mapping, new-node detection SkyHawkClient#61 (in that repo's mirrored "Firmware v0.2.0" milestone).

Spec / docs (same PR)

  • FirmwarePlan: TX-queue policy corrected to the as-built bounded retry for HB / HEALTH / TELEM (02, 00, 10, TechSpec CANProtocol — only CTRL_BCAST coalesces); 00-decisions (D18; D13's stale INA226 wording), 02-can-protocol (ID fn, frame table, Node Telemetry Payload, bus load, bridge accept ranges — the list also gains the REL / DIR / ACTION ranges it had drifted from), 04-dcs-bios-integration (_NODE_TELEM), 06-panelbridge-api, 08-hardware-firmware-contracts (PDU analog front end + calibration page), 10-implementation-plan (Phase 5b)
  • TechSpec: CANProtocol, NodeStatus, PanelBridge (RX table gains HEALTH + TELEM), new PowerMonitor.md, README dependency tree
  • docs: new hardware/pdu.md (Power Distribution Unit — what it measures, fuses, power-in pinout checked against the 0.2.0 netlist, bring-up + calibration; in the Hardware nav after Base Boards, linked from Base Boards and Power), architecture/can-bus.md, architecture/power.md (PDU monitoring + wiring assumptions), firmware/index.md, firmware/build-flags.md (PDU_*), firmware/node-id.md
  • Also: Firmware/NODE_IDS.md (3 / 4 / 5, next = 6), docs/_source/base-boards.md (INA226 → INA180A2, no eFuse), PDU DESIGN-NOTES.md §B1b (VDDA correction), firmware skill TechSpec list

Docs-drift reminder — checked: no new hardware (firmware for the existing PDU, #202; this PR adds its published page hardware/pdu.md); the TELEM_n decision is reflected on architecture/can-bus.md + architecture/power.md (design-decisions.md keeps its own D1–D10 architecture list, not the FirmwarePlan series); no published page still says INA226 / TMP117 / eFuse for the PDU (_source/base-boards.md and the PDU DESIGN-NOTES corrected here); NODE_IDs stay transcluded from Firmware/NODE_IDS.md.

New PowerMonitor library and PDU_Left/Right/Center sketches (NODE_IDs 3/4/5):
samples the 12 V / 5 V rails (voltage + current) and the board NTC at 1 kHz,
converts with per-board calibration stored in the last flash page, raises
UV / OV / over-current / fuse-open / temperature / sensor flags with
persistence and hysteresis, and feeds faults into HEALTH_n via FaultSource.

New paged frame TELEM_n = 0x180 + n (TelemPayload, byte 0 = TelemPage): one
frame per second rotating PDU_RAIL_12V / PDU_RAIL_5V / PDU_STATUS, plus
rate-limited edge sends on any flag change; SYNC_REQ sends all three.
PanelBridge forwards every TELEM_n frame to the host unread as _NODE_TELEM.
DiagSerial calibration: CAL SHOW / CAL VDDA <mV> / CAL ZERO / CAL CLEAR / RAW.

Tests/PowerMonitor (5 envs) passes on hardware; real-bus smoke of the PDU
firmware with a CANable2. Spec and docs synced (D18).
@github-actions

Copy link
Copy Markdown
Contributor

📝 Docs site may need an update

This PR touches files the docs site is built from. Before merging,
confirm the published pages still match:

  • New hardware (panel / PCB / CAD) has a matching page under docs/
  • Changed design decisions are reflected on the relevant page
    (e.g. architecture/design-decisions.md, the hardware reference)
  • No page now contradicts docs/_source/ or hardware-standards.md
  • Structured tables (NODE_IDs, pinouts) are transcluded, not retyped

Backstop: the weekly docs-drift review. Ignore docs/api/ (auto-generated).

if (!strcmp(line, "CAL SHOW")) printCal();
else if (!strncmp(line, "CAL VDDA ", 9)) cmdCalVdda(line + 9);
else if (!strcmp(line, "CAL ZERO")) cmdCalZero();
else if (!strcmp(line, "CAL CLEAR")) { gStore.erase(); loadCal(); out().print(F("[PDU] cleared. ")); printCal(); }

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P2] Handle a failed calibration-page erase before reporting success

CalStore::erase() returns false when the HAL flash erase fails, but this command ignores that result, reloads whatever record remains, and still prints [PDU] cleared.. That can tell an operator calibration was removed when the old calibration is still active. Please check the return value, emit an error on failure, and only reload/report cleared after a successful erase.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in bcd8b23. CAL CLEAR now checks CalStore::erase(): on failure it prints [PDU] ERROR: flash erase failed — calibration unchanged and leaves the loaded calibration as it is; only a successful erase reloads and reports cleared. Verified the success path on hardware (CAL VDDA → 1 slot → CAL CLEAR → cleared, 0 slots, confirmed by CAL SHOW); the failure branch is review-only, since a HAL erase failure can't be forced on the bench without a fault-injection hook.

Comment thread docs/hardware/pdu.md Outdated
!!! note "Status LED"
The red/green status LED works exactly as on every other board — CAN link and bus health (see
[Debugging](../firmware/debugging.md)). Rail faults are reported to the host app and shown on
the rail LEDs, not on the status LED.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P2] Do not describe the passive rail-presence LEDs as fault indicators

D3/D4 are wired directly from the delivered +12 V/+5 V rails through resistors, so they only indicate that a rail is present. Firmware does not drive them: an over-voltage, over-current, or temperature fault can leave both LEDs lit normally. This wording can mislead bring-up/debugging. Please say that faults are reported to the host, while D3/D4 independently show rail presence (a blown/dead rail may extinguish its LED). The same inaccurate wording should also be corrected in Firmware/ScratchPad/FirmwarePlan/08-hardware-firmware-contracts.md and PduNode.h.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in db26ab0, in all three places (docs/hardware/pdu.md, 08-hardware-firmware-contracts.md, PduNode.h). Checked against the 0.2.0 netlist: D3 (+12 V), D4 (+5 V) and D5 (+3V3) are passive, resistor-fed power LEDs; D6/D7 are the MCU status pair on PB14/PB15. The text now says rail faults are reported to the host only, the status LEDs behave as on every node and don't change for rail faults, and the power LEDs show rail presence only — dark on a dead rail or blown fuse, lit through OV / OC / overheat. The page's feature bullet also now lists all three power LEDs (it had omitted the 3.3 V one).

@mottihoresh
mottihoresh marked this pull request as ready for review October 15, 2026 22:21
@mottihoresh
mottihoresh merged commit 22b6a87 into main Oct 15, 2026
13 checks passed
@mottihoresh
mottihoresh deleted the feat/pdu-telemetry-216 branch October 15, 2026 22:28
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat(firmware): PDU monitoring node — telemetry firmware + new CAN telemetry packet class

1 participant