Skip to content

feat: node data layer — PDU telemetry, node mapping, new-node detection #61

Closed

Description

@mottihoresh

Summary

Firmware is adding the PDU monitoring node (OpenSkyHawk/OpenSkyhawk#216, Firmware v0.2.0): one per console, measuring the 12 V and 5 V rails (voltage, current) plus board temperature. Its data travels on a new CAN frame family, TELEM_n, which PanelBridge forwards to this client as a new DCS-BIOS message, _NODE_TELEM. The client needs to tap, decode and display it.

_NODE_STATUS is unchanged (stays proto v2). PDU nodes already appear in the Connected Nodes roster through it — up/down, die temp, and the degraded badge when a rail faults — with no client change.

Wire format (as designed in #216 — final layout lands with the firmware PR)

_NODE_TELEM <hex>      18 hex chars: nodeId(2) + the 8 TELEM frame bytes (16), in wire order
  • Pushed by PanelBridge as each frame arrives — no request, no cache, no end marker. Bridge mode only (same path as _NODE_STATUS).
  • Byte 0 of the frame is a page id (TelemPage, global and append-only; 0x00 invalid). The bridge never decodes pages, so the client owns the decode. Multi-byte fields are little-endian (note: unlike _NODE_STATUS, these are raw frame bytes, not MSB-first fields).
  • Rate: one message per second per PDU (same as HEALTH_n), rotating through the three pages — each page refreshes every 3 s — plus an immediate send when any flag changes (≤ 1 per 100 ms per page). ~3 PDUs → ~3 messages/s steady.
Page Byte 1 Bytes 2–3 Bytes 4–5 Bytes 6–7
0x01 PDU_RAIL_12V / 0x02 PDU_RAIL_5V railFlags vAvgMv u16 vMinMv u16 (window min) iAvgMa int16
0x03 PDU_STATUS statusFlags ntcTempDC int16, 0.1 °C (0x8000 = unavailable) i12PeakMa u16 i5PeakMa u16
  • railFlags: bit0 UNDER_V · bit1 OVER_V · bit2 OVER_CURRENT · bit3 FUSE_OPEN · bit4 SENSOR_FAULT
  • statusFlags: bit0 UNCAL_VDDA · bit1 UNCAL_ZERO · bit2 TEMP_WARN · bit3 TEMP_CRIT · bit4 NTC_FAULT
  • All flags fault-sense: all-zero = healthy.
  • iAvgMa is signed — a small negative value is residual sensor offset; clamp to 0 for display.
  • Averages / min / peak cover the 3 s since that page's previous periodic send. Peak current is after the board's ~100 µs RC filter.
  • ntcTempDC is a real board temperature (±0.5 °C class), unlike the uncalibrated die temp in _NODE_STATUS.

Contract source: NodeStatus.h gains NODE_TELEM_MSG_NAME, NODE_TELEM_PROTO_VERSION (1), and the TelemPage / PduRailFlag / PduStatusFlag enums, so sync-a4ec can generate them alongside the node-status contract. It also gains new NodeFaultCode values 0x06 FUSE_OPEN, 0x07 OVER_TEMPERATURE, 0x08 SENSOR_FAULT — they render as Fault N until the sync is re-pointed (the decoder already passes unknown ids through).

Today's behaviour without this change

relayFromDevice (src/main/session.ts:369) taps only _NODE_STATUS / _NODE_STATUS_END. _NODE_TELEM lines fall through to stats.command + the command log, so the log fills with ~3 lines/s of telemetry noise. They are also relayed to DCS:7778 like every device line; DCS ignores the unknown identifier (same as _NODE_STATUS). Nothing breaks.

Changes

PDU telemetry (existing scope, minus UI)

  • scripts/sync-a4ec.ts — re-point PIN to 22b6a87e (feat(firmware): PDU monitoring node and TELEM_n telemetry frame (#216) OpenSkyhawk#343, which closes #216); emit NODE_TELEM constants + TelemPage / PduRailFlag / PduStatusFlag; regen — also brings NodeFaultCode 0x06 FUSE_OPEN / 0x07 OVER_TEMPERATURE / 0x08 SENSOR_FAULT (today they render Fault N)
  • reference.test.ts — assert NODE_TELEM.protoVersion == new SUPPORTED_TELEM_PROTO
  • src/shared/ — pure parseNodeTelem(hex) + latest-value store keyed (nodeId, page) with last-seen; fixtures for all 3 pages, signed current, 0x8000 temp, malformed, unknown page
  • src/main/session.ts — tap _NODE_TELEM (not logged as a command); throttled nodes:telemetry event
  • A node that sends PDU pages is a PDU; stale telemetry (> 10 s) reported as unknown

Node mapping (new)

  • <userData>/node-mapping.json — per NODE_ID: display name, console, power zone (PDU); tolerant read / best-effort write like settings.ts
  • Defaults from the bundled NODE_IDS (name, console; PDU = the PDU on that console); only differences are stored
  • Reset (all) to registry defaults; count of nodes that differ
  • Ignored NODE_IDs list (permanent, with un-ignore)

New-node detection (new)

  • A NODE_ID that is in the roster but not in NODE_IDS and not mapped/ignored → "new"; kind = PDU if it sends PDU pages, else panel group; first-seen time
  • Registry nodes are placed automatically (never "new")

Status rules (new, pure + unit-tested)

  • A registry node absent from the first complete roster after the link comes up → offline (no separate "waiting" state)
  • "Hidden until it reports again" flag in node-mapping.json (replaced / removed boards): such nodes are skipped by the rule above and the flag clears when the node reports
  • PDU-offline grouping: PDU offline + more than half of its mapped nodes going offline within ±2 s (or found late by the next roster poll, ≤ 7 s after the PDU) → one group event (feeds the Cockpit's combined Attention item and the fuse hint)
  • PDU with status but no telemetry (never or > 10 s) → "no readings" (separate from status colour); everHadReadings flag for the "PanelBridge firmware may be too old" hint

Decisions (settled during implementation, PR #71)

  • PDUs in the registry are recognised by the PDU_ name prefix (NODE_IDS.md has no type column). A panel's default power zone is the PDU on its console when there is exactly one.
  • Grouping window: 2 s. Nodes that lose power together stop heartbeating together, so the bridge's 3 s timeout fires within one 500 ms heartbeat period and each drop arrives as a live delta. A drop whose delta was lost is caught by the 5 s roster poll, so it can count up to 7 s after the PDU's. A PDU and its nodes all missing from the first roster also group.
  • First-seen for new nodes is in memory only, so a node nobody acts on stays new/pending. It is saved to node-mapping.json once adopted.
  • Reset all clears registry overrides only, including those of an ignored registry node (the "differ" count matches). Adopted nodes, the ignore list and hidden flags stay.
  • Adopt requires a name and a console. A power zone must be a registry or adopted PDU that is not ignored; a PDU has none. Checked on set; loading stays tolerant.
  • Hide until it reports again applies only to registry and adopted nodes (Ignore is the action for a new id). It clears on an offline→online transition, not while the bridge still lists the board.
  • node-mapping.json is written atomically. A failed save returns { ok: false } and is not applied. A damaged file is moved aside before starting empty. An unreadable file, or one from a newer client, is loaded read-only.
  • Readings need a fresh 12 V, 5 V or status page; an unknown page type never counts. A status counts as a roster reply only within 1 s of the request, so a bridge that never sends _NODE_STATUS_END cannot skew grouping; registry nodes then stay unknown (rosterComplete: false).
  • Stale pages (> 10 s) keep their last values with stale: true, so a reading can be shown as last known or unknown.
  • Fault badge tags are unique. The new OVER_TEMPERATURE (0x07) would have tagged OVER, the same as OVER_VOLTAGE. Tags that share a first token now take the next token's initial (OVER-V / OVER-T), and the sync fails if two still collide.

IPC boundary (for #63)

#61 owns the main↔renderer IPC and preload. #63 is UI only, including the renderer store. Pushes are not gated on relaying (device-link independence comes from #69).

Out of scope

UI — the NodesCard PDU rows / alerts originally listed here move to the Cockpit tab (#63). No stop-gap readout.

Related

Depends on: OpenSkyHawk/OpenSkyhawk#216 (merged via #343, 22b6a87e).

Activity

  1. added this to the Firmware v0.2.0 milestone on Oct 15, 2026
  2. changed the title [-]feat: PDU rail telemetry (_NODE_TELEM) — decode and per-rail display[/-] [+]feat: node data layer — PDU telemetry, node mapping, new-node detection[/+] on Oct 16, 2026
  3. added 4 commits that reference this issue on Oct 16, 2026
    75c27b4
    395f295
    ea8fa2a
    68e59e2
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

      Milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions