Consolidate Rust port requirements and remove refactor notes docs

This commit is contained in:
2026-02-20 20:13:25 +01:00
parent 9495e7e8de
commit 1169eab626
2 changed files with 114 additions and 122 deletions

View File

@@ -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. 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: - Role selection:
- `Sender` when `GPIO14` reads HIGH. - `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 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. - 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`: - `LoraMsgKind`:
- `BatchUp=0` - `BatchUp=0`
@@ -81,8 +105,13 @@ Function names below are C++ references. Rust naming/layout may differ, but the
- `device.name`: `<device_id>` - `device.name`: `<device_id>`
- `device.model`: `DD3-LoRa-Bridge` - `device.model`: `DD3-LoRa-Bridge`
- `device.manufacturer`: `AcidBurns` - `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` ## `src/config.cpp`
@@ -335,15 +364,25 @@ Internal route/state functions to preserve behavior:
- `test_receiver_loop` - `test_receiver_loop`
- decode test JSON, update display test markers, publish MQTT test topic. - 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: - Logging/utilities:
- `serial_debug_printf` - `serial_debug_printf`
- `bit_count32` - `bit_count32`
- `abs_diff_u32` - `abs_diff_u32`
- Meter-time anchoring and ingest: - Meter-time anchoring and ingest:
- `meter_time_update_snapshot` - `meter_time_update_snapshot`
- `set_last_meter_sample` - `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_task_entry`
- `meter_reader_start` - `meter_reader_start`
- `meter_reader_pump` - `meter_reader_pump`
- Sender state/data handling:
- Sender/receiver state setup and shared state:
- `init_sender_statuses`
- `update_battery_cache` - `update_battery_cache`
- `battery_sample_due` - `battery_sample_due`
- Queue and sample batching:
- `batch_queue_drop_oldest` - `batch_queue_drop_oldest`
- `sender_note_rx_reject` - `sender_note_rx_reject`
- `sender_log_diagnostics` - `sender_log_diagnostics`
@@ -367,8 +402,7 @@ These functions define end-to-end firmware behavior and must have equivalents:
- `reset_build_counters` - `reset_build_counters`
- `append_meter_sample` - `append_meter_sample`
- `last_sample_ts` - `last_sample_ts`
- Sender fault handling:
- Fault tracking/publish:
- `note_fault` - `note_fault`
- `clear_faults` - `clear_faults`
- `sender_reset_fault_stats` - `sender_reset_fault_stats`
@@ -377,11 +411,40 @@ These functions define end-to-end firmware behavior and must have equivalents:
- `age_seconds` - `age_seconds`
- `counters_changed` - `counters_changed`
- `publish_faults_if_needed` - `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: ## `src/receiver_pipeline.h/.cpp` (Receiver Runtime)
- `watchdog_init`
- `watchdog_kick`
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: - Binary helpers and ID conversion:
- `write_u16_le` - `write_u16_le`
- `read_u16_le` - `read_u16_le`
@@ -391,39 +454,27 @@ These functions define end-to-end firmware behavior and must have equivalents:
- `read_u32_be` - `read_u32_be`
- `sender_id_from_short_id` - `sender_id_from_short_id`
- `short_id_from_sender_id` - `short_id_from_sender_id`
- LoRa RX/TX pipeline:
- 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:
- `compute_batch_rx_timeout_ms` - `compute_batch_rx_timeout_ms`
- `compute_batch_ack_timeout_ms`
- LoRa TX pipeline:
- `send_batch_payload`
- `send_batch_ack` - `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` - `reset_batch_rx`
- `process_batch_packet` - `process_batch_packet`
- Role loop orchestration:
- `setup`
- `sender_loop`
- `receiver_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: - Preserve wire compatibility first:
- LoRa frame byte layout, CRC16, ACK format, payload schema v3. - 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`. - `FaultType`, `RxRejectReason`, `LoraMsgKind`.
Suggested Rust module split: 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: Suggested Rust primitives:
- async task for meter reader + bounded channel (drop-oldest behavior). - async task for meter reader + bounded channel (drop-oldest behavior).
- explicit state structs for sender/receiver loops. - explicit state structs for sender/receiver loops.
- serde-free/manual codec for wire compatibility where needed. - serde-free/manual codec for wire compatibility where needed.
## 6. Port Validation Checklist ## 7. Port Validation Checklist
- Sender unsynced boot sends only sync requests. - Sender unsynced boot sends only sync requests.
- ACK time bootstrap unlocks normal sender sampling. - 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 reads current and legacy CSV layouts successfully.
- History endpoint can read both local-date and legacy UTC-date day filenames. - History endpoint can read both local-date and legacy UTC-date day filenames.
- MQTT state/fault payload fields match existing names and semantics. - 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.

View File

@@ -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.