From 1169eab6265cce2a63386ee0fccdf3478771df1e Mon Sep 17 00:00:00 2001 From: acidburns Date: Fri, 20 Feb 2026 20:13:25 +0100 Subject: [PATCH] Consolidate Rust port requirements and remove refactor notes docs --- Requirements.md | 159 ++++++++++++++++++++++++++++------------ docs/REFRACTOR_NOTES.md | 77 ------------------- 2 files changed, 114 insertions(+), 122 deletions(-) delete mode 100644 docs/REFRACTOR_NOTES.md diff --git a/Requirements.md b/Requirements.md index a47eeba..996dd51 100644 --- a/Requirements.md +++ b/Requirements.md @@ -11,7 +11,31 @@ It is based on the current `lora-refactor` code state and captures: Function names below are C++ references. Rust naming/layout may differ, but the behavior must remain equivalent. -## 2. System-Level Requirements +## 2. Refactored Architecture Baseline + +The `lora-refactor` branch split role-specific runtime from the previous large `main.cpp` into dedicated modules while keeping a single firmware image: + +- `src/main.cpp` is a thin coordinator that: + - detects role and initializes shared platform subsystems, + - prepares role module configuration, + - calls `begin()` once, + - delegates runtime in `loop()`. +- sender runtime ownership: + - `src/sender_state_machine.h` + - `src/sender_state_machine.cpp` +- receiver runtime ownership: + - `src/receiver_pipeline.h` + - `src/receiver_pipeline.cpp` +- receiver shared mutable state used by setup wiring and runtime: + - `src/app_context.h` (`ReceiverSharedState`) + +Sender state machine invariants must remain behavior-equivalent: +- single inflight batch at a time, +- ACK acceptance only for matching `batch_id`, +- retry bounded by `BATCH_MAX_RETRIES`, +- queue depth bounded by `BATCH_QUEUE_DEPTH`. + +## 3. System-Level Requirements - Role selection: - `Sender` when `GPIO14` reads HIGH. @@ -58,7 +82,7 @@ Function names below are C++ references. Rust naming/layout may differ, but the - history day-file resolution prefers local-date filenames and falls back to legacy UTC-date filenames. - history parser supports both current (`ts_utc,ts_hms_local,p_w,...`) and legacy (`ts_utc,p_w,...`) layouts. -## 3. Protocol and Data Contracts +## 4. Protocol and Data Contracts - `LoraMsgKind`: - `BatchUp=0` @@ -81,8 +105,13 @@ Function names below are C++ references. Rust naming/layout may differ, but the - `device.name`: `` - `device.model`: `DD3-LoRa-Bridge` - `device.manufacturer`: `AcidBurns` + - drift guards: + - canonical value is `HA_MANUFACTURER` in `include/config.h`, + - compile-time lock via `static_assert` in `include/config.h`, + - script guard `test/check_ha_manufacturer.ps1`, + - smoke test guard `test/test_refactor_smoke/test_refactor_smoke.cpp`. -## 4. Module and Function Requirements +## 5. Module and Function Requirements ## `src/config.cpp` @@ -335,15 +364,25 @@ Internal route/state functions to preserve behavior: - `test_receiver_loop` - decode test JSON, update display test markers, publish MQTT test topic. -## `src/main.cpp` (Core Orchestration) +## `src/app_context.h` -These functions define end-to-end firmware behavior and must have equivalents: +- `ReceiverSharedState` + - retains receiver-owned shared status/fault/discovery state used by setup wiring and runtime. +## `src/sender_state_machine.h/.cpp` (Sender Runtime) + +Public API: +- `SenderStateMachineConfig` +- `SenderStats` +- `SenderStateMachine::begin(...)` +- `SenderStateMachine::loop()` +- `SenderStateMachine::stats()` + +Behavior-critical internals (migrated from pre-refactor `main.cpp`) that must remain equivalent: - Logging/utilities: - `serial_debug_printf` - `bit_count32` - `abs_diff_u32` - - Meter-time anchoring and ingest: - `meter_time_update_snapshot` - `set_last_meter_sample` @@ -352,13 +391,9 @@ These functions define end-to-end firmware behavior and must have equivalents: - `meter_reader_task_entry` - `meter_reader_start` - `meter_reader_pump` - -- Sender/receiver state setup and shared state: - - `init_sender_statuses` +- Sender state/data handling: - `update_battery_cache` - `battery_sample_due` - -- Queue and sample batching: - `batch_queue_drop_oldest` - `sender_note_rx_reject` - `sender_log_diagnostics` @@ -367,8 +402,7 @@ These functions define end-to-end firmware behavior and must have equivalents: - `reset_build_counters` - `append_meter_sample` - `last_sample_ts` - -- Fault tracking/publish: +- Sender fault handling: - `note_fault` - `clear_faults` - `sender_reset_fault_stats` @@ -377,11 +411,40 @@ These functions define end-to-end firmware behavior and must have equivalents: - `age_seconds` - `counters_changed` - `publish_faults_if_needed` +- Sender-specific encoding/scheduling: + - `kwh_to_wh_from_float` + - `float_to_i16_w` + - `float_to_i16_w_clamped` + - `battery_mv_from_voltage` + - `compute_batch_ack_timeout_ms` + - `send_batch_payload` + - `invalidate_inflight_encode_cache` + - `prepare_inflight_from_queue` + - `send_inflight_batch` + - `send_meter_batch` + - `send_sync_request` + - `resend_inflight_batch` + - `finish_inflight_batch` + - `sender_loop` -- Watchdog: - - `watchdog_init` - - `watchdog_kick` +## `src/receiver_pipeline.h/.cpp` (Receiver Runtime) +Public API: +- `ReceiverPipelineConfig` +- `ReceiverStats` +- `ReceiverPipeline::begin(...)` +- `ReceiverPipeline::loop()` +- `ReceiverPipeline::stats()` + +Behavior-critical internals (migrated from pre-refactor `main.cpp`) that must remain equivalent: +- Receiver setup/state: + - `init_sender_statuses` +- Fault handling/publish: + - `note_fault` + - `clear_faults` + - `age_seconds` + - `counters_changed` + - `publish_faults_if_needed` - Binary helpers and ID conversion: - `write_u16_le` - `read_u16_le` @@ -391,39 +454,27 @@ These functions define end-to-end firmware behavior and must have equivalents: - `read_u32_be` - `sender_id_from_short_id` - `short_id_from_sender_id` - -- Numeric normalization/sanitization: - - `kwh_to_wh_from_float` - - `float_to_i16_w` - - `float_to_i16_w_clamped` - - `battery_mv_from_voltage` - -- Timeout and airtime-driven scheduling: +- LoRa RX/TX pipeline: - `compute_batch_rx_timeout_ms` - - `compute_batch_ack_timeout_ms` - -- LoRa TX pipeline: - - `send_batch_payload` - `send_batch_ack` - - `invalidate_inflight_encode_cache` - - `prepare_inflight_from_queue` - - `send_inflight_batch` - - `send_meter_batch` - - `send_sync_request` - - `resend_inflight_batch` - - `finish_inflight_batch` - -- LoRa RX reassembly/decode: - `reset_batch_rx` - `process_batch_packet` - -- Role loop orchestration: - - `setup` - - `sender_loop` - `receiver_loop` - - `loop` -## 5. Rust Porting Constraints and Recommendations +## `src/main.cpp` (Thin Coordinator) + +Current core orchestration requirements: +- `setup` + - initialize shared subsystems once, + - instantiate role config and call role `begin`, + - keep role-specific runtime out of this file. +- `loop` + - delegate to `SenderStateMachine::loop()` or `ReceiverPipeline::loop()` by role. +- Watchdog wrapper remains in coordinator: + - `watchdog_init` + - `watchdog_kick` + +## 6. Rust Porting Constraints and Recommendations - Preserve wire compatibility first: - LoRa frame byte layout, CRC16, ACK format, payload schema v3. @@ -439,14 +490,14 @@ These functions define end-to-end firmware behavior and must have equivalents: - `FaultType`, `RxRejectReason`, `LoraMsgKind`. Suggested Rust module split: -- `config`, `ids`, `meter`, `power`, `time`, `lora_transport`, `payload_codec`, `batch`, `mqtt`, `wifi_cfg`, `sd_log`, `web`, `display`, `runtime`. +- `config`, `ids`, `meter`, `power`, `time`, `lora_transport`, `payload_codec`, `sender_state_machine`, `receiver_pipeline`, `app_context`, `mqtt`, `wifi_cfg`, `sd_log`, `web`, `display`, `runtime`. Suggested Rust primitives: - async task for meter reader + bounded channel (drop-oldest behavior). - explicit state structs for sender/receiver loops. - serde-free/manual codec for wire compatibility where needed. -## 6. Port Validation Checklist +## 7. Port Validation Checklist - Sender unsynced boot sends only sync requests. - ACK time bootstrap unlocks normal sender sampling. @@ -459,3 +510,21 @@ Suggested Rust primitives: - History endpoint reads current and legacy CSV layouts successfully. - History endpoint can read both local-date and legacy UTC-date day filenames. - MQTT state/fault payload fields match existing names and semantics. + +## 8. Port Readiness Audit (2026-02-20) + +Evidence checked on `lora-refactor`: +- build verification: + - `pio run -e lilygo-t3-v1-6-1` + - `pio run -e lilygo-t3-v1-6-1-test` +- drift guard verification: + - `powershell -ExecutionPolicy Bypass -File test/check_ha_manufacturer.ps1` +- refactor ownership verification: + - sender state machine state/API present in `src/sender_state_machine.h/.cpp`, + - receiver pipeline API present in `src/receiver_pipeline.h/.cpp`, + - coordinator remains thin in `src/main.cpp`. + +Findings: +- Requirements are functionally met by current C++ baseline from static/code-build checks. +- The old requirement ownership under `src/main.cpp` was stale; this document now maps that behavior to `sender_state_machine` and `receiver_pipeline`. +- No wire/protocol or persistence contract drift found in this audit. diff --git a/docs/REFRACTOR_NOTES.md b/docs/REFRACTOR_NOTES.md deleted file mode 100644 index 8f7f514..0000000 --- a/docs/REFRACTOR_NOTES.md +++ /dev/null @@ -1,77 +0,0 @@ -# Refactor Notes: `main.cpp` Split - -## Architecture - -The firmware remains a unified image. Role selection is still done at boot in `src/main.cpp` using the role pin. `src/main.cpp` is now a thin coordinator that: - -- detects role and initializes shared platform subsystems, -- creates role module config, -- calls `begin()` once, -- delegates runtime via `loop()`. - -## Sender Logic - -Sender-only runtime now lives in: - -- `src/sender_state_machine.h` -- `src/sender_state_machine.cpp` - -Public API: - -- `SenderStateMachineConfig` -- `SenderStats` -- `SenderStateMachine::begin(...)` -- `SenderStateMachine::loop()` -- `SenderStateMachine::stats()` - -Sender state machine exposes explicit states (`Syncing`, `Normal`, `Catchup`, `WaitAck`) and keeps sender flow isolated from top-level orchestration. - -Sender helpers: - -- `handleMeterRead(...)` -- `maybeSendBatch(...)` -- `handleAckWindow(...)` -- `applyTimeFromAck(...)` -- `validateInvariants()` - -Sender invariants are checked and debug-traceable (`DD3_DEBUG`) for: - -- single inflight batch at a time, -- ACK acceptance only for matching `batch_id`, -- retry bounded by `BATCH_MAX_RETRIES`, -- queue depth bounded by `BATCH_QUEUE_DEPTH`. - -## Receiver Logic - -Receiver-only runtime now lives in: - -- `src/receiver_pipeline.h` -- `src/receiver_pipeline.cpp` - -Shared receiver arrays and fault/discovery state used by setup wiring and runtime are held in: - -- `src/app_context.h` (`ReceiverSharedState`) - -Public API: - -- `ReceiverPipelineConfig` -- `ReceiverStats` -- `ReceiverPipeline::begin(...)` -- `ReceiverPipeline::loop()` -- `ReceiverPipeline::stats()` - -Receiver pipeline owns LoRa RX processing, reassembly/decode, ACK send path, MQTT/SD/display updates, and receiver fault/discovery publication flow. - -## HA Manufacturer Single Source - -`include/config.h` defines the canonical manufacturer: - -- `HA_MANUFACTURER = "AcidBurns"` - -Home Assistant discovery payload generation in `src/mqtt_client.cpp` uses this constant directly (`device["manufacturer"] = HA_MANUFACTURER`). - -Drift guards: - -- compile-time lock in `include/config.h` via `static_assert`, -- `test/check_ha_manufacturer.ps1` validates discovery assignment and rejects stray hardcoded manufacturer literals in `src/` and `include/`, -- smoke test `test/test_refactor_smoke/test_refactor_smoke.cpp` asserts the constant value and module header compatibility.