Automated release from CI pipeline
Changes:
feat(firmware): persistent on-node log and coredump-to-flash (#1878)
- feat(firmware): add the 16MB flash profile the C6 fleet runs on
The 4MB and display layouts have 1.875MB and 2MB OTA slots, sized for 4MB
and 8MB parts. A 16MB C6 (DevKitC-1 N16) has no layout in the tree at all,
so building for one meant hand-editing sdkconfig -- and a smaller table
written over a deployed 16MB node relocates NVS and destroys its
provisioning, which is why this belongs in the repo rather than in a
workstation's untracked sdkconfig.
Two 4MB OTA slots, plus a 64K coredump partition and an 8000K FAT volume
for on-node storage. Rollback is enabled so an OTA'd image that cannot
confirm itself is reverted by the bootloader.
Layered, not inherited: sdkconfig.defaults.esp32c6 keeps its 4MB layout so
the common 4MB devkit is never silently repartitioned.
Co-Authored-By: claude-flow ruv@ruv.net
Claude-Session: https://claude.ai/code/session_01PVWMiHQifoYXL7uL3bphrZ
- feat(firmware): persistent on-node log and coredump-to-flash
A remote fault currently leaves no post-mortem. main.c decodes the reset
reason at boot and logs it to the CONSOLE -- which is exactly what a power
cycle destroys, and a power cycle is how a wedged node gets recovered.
The flash was already provisioned for this and the features were simply off:
partitions_16mb.csv has reserved a 64K coredump partition and an 8000K
storage FAT volume since the 16MB layout was written, neither had ever been
mounted or written, and the build defaulted to CONFIG_ESP_COREDUMP_ENABLE_TO_NONE.
- node_log.c/h: a 128 B-record ring on the storage volume. Boot records
(reset reason + the previous session's final uptime + a health snapshot),
periodic health samples, and event records. Ordering is boot_id + uptime_s
because these boards have no RTC and nothing can be timestamped absolutely.
Counters are stored RAW and differenced at read time, so a cadence change
never invalidates old records. Retention, not wear, binds: 128 B every
300 s is ~208 days in 8 MB, and the write rate is bounded explicitly. - ap_rssi_dbm, the association link from esp_wifi_sta_get_ap_info. The
adaptive controller's rssi_median_dbm is a CSI statistic, not the uplink,
so a node with a bad uplink currently shows no signal for it anywhere. - CONFIG_ESP_COREDUMP_ENABLE_TO_FLASH, complementary rather than a
substitute: a coredump catches a CRASH, the boot record catches a HANG
followed by a watchdog reset.
node_log_init failing is non-fatal by design -- the module disables itself
and every entry point becomes a no-op. A logging volume that will not mount
must never take the fleet down with it.
csi_collector gains two gate accessors. This build selects its frame gate at
compile time, so both return CSI_GATE_NOT_CONFIGURABLE rather than a
plausible number: the log keeps a field of fixed width and meaning either
way, and an explicit "not applicable" is stored instead of something a
reader would later reason from as a real setting.
Built with ESP-IDF v5.4 for esp32c6 using
defaults + .esp32c6 + .16mb. Verified from the resulting artifacts: 16MB
table with coredump and storage present, COREDUMP_ENABLE_TO_FLASH set,
rollback enabled, image 0x10abd0 bytes in a 4M slot. NOT flashed to hardware
from this branch -- the equivalent code was exercised on a node-3 pilot, but
that is not evidence for this build.
Co-Authored-By: claude-flow ruv@ruv.net
Claude-Session: https://claude.ai/code/session_01PVWMiHQifoYXL7uL3bphrZ
- docs(adr-362): reading the on-node log is on-demand, never streamed
The log engine lands with no consumer, so the access pattern was open. Records
the decision to pull a node's log only when a human asks -- after a hang or a
crash, or while troubleshooting that node -- and to keep nothing server-side.
The reasoning that decided it: the ring already survives a power cycle on the
node (~208 days of retention), so a server copy duplicates a durability
property that is already met; and airtime is this fleet's binding constraint,
measured at 91% consumed by two slow nodes, so a periodic drain across nine
boards would spend exactly the resource whose exhaustion you would open the
log to investigate. An instrument must not perturb what it measures.
Leaves the decode-where question open on purpose, with the argument for a raw
proxy stated: decoding server-side defines the record layout in both C and
Rust, and the pilot already paid once for a host/firmware disagreement about a
field's meaning.
Co-Authored-By: claude-flow ruv@ruv.net
Claude-Session: https://claude.ai/code/session_01PVWMiHQifoYXL7uL3bphrZ
Co-authored-by: Joe joe@GitDev.hedgehog
Co-authored-by: claude-flow ruv@ruv.net
Docker Image:
ghcr.io/ruvnet/RuView:500cfe47875705b47e08e6d79cd4b4066a70e445