Consolidate Rust port requirements and remove refactor notes docs
This commit is contained in:
159
Requirements.md
159
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.
|
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.
|
||||||
|
|||||||
@@ -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.
|
|
||||||
Reference in New Issue
Block a user