diff --git a/README.md b/README.md index deab2bf..79f5139 100644 --- a/README.md +++ b/README.md @@ -144,7 +144,8 @@ Home Assistant discovery: - `identifiers: [""]` - `name: ""` - `model: "DD3-LoRa-Bridge"` - - `manufacturer: "AcidBurns"` + - `manufacturer: "AcidBurns"` (from `HA_MANUFACTURER` in `include/config.h`) + - single source of truth: change manufacturer only in `include/config.h` ## Web UI, Wi-Fi, SD diff --git a/docs/REFRACTOR_NOTES.md b/docs/REFRACTOR_NOTES.md new file mode 100644 index 0000000..8f7f514 --- /dev/null +++ b/docs/REFRACTOR_NOTES.md @@ -0,0 +1,77 @@ +# 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. diff --git a/include/config.h b/include/config.h index 1fc2aac..c4b1533 100644 --- a/include/config.h +++ b/include/config.h @@ -86,6 +86,19 @@ constexpr bool WEB_AUTH_REQUIRE_STA = true; constexpr bool WEB_AUTH_REQUIRE_AP = true; constexpr const char *WEB_AUTH_DEFAULT_USER = "admin"; constexpr const char *WEB_AUTH_DEFAULT_PASS = "admin"; +inline constexpr char HA_MANUFACTURER[] = "AcidBurns"; +static_assert( + HA_MANUFACTURER[0] == 'A' && + HA_MANUFACTURER[1] == 'c' && + HA_MANUFACTURER[2] == 'i' && + HA_MANUFACTURER[3] == 'd' && + HA_MANUFACTURER[4] == 'B' && + HA_MANUFACTURER[5] == 'u' && + HA_MANUFACTURER[6] == 'r' && + HA_MANUFACTURER[7] == 'n' && + HA_MANUFACTURER[8] == 's' && + HA_MANUFACTURER[9] == '\0', + "HA_MANUFACTURER must remain exactly \"AcidBurns\""); constexpr uint8_t NUM_SENDERS = 1; constexpr uint32_t MIN_ACCEPTED_EPOCH_UTC = 1769904000UL; // 2026-02-01 00:00:00 UTC diff --git a/src/mqtt_client.cpp b/src/mqtt_client.cpp index 224bea6..630a690 100644 --- a/src/mqtt_client.cpp +++ b/src/mqtt_client.cpp @@ -114,7 +114,7 @@ static bool publish_discovery_sensor(const char *device_id, const char *key, con identifiers.add(String(device_id)); device["name"] = String(device_id); device["model"] = "DD3-LoRa-Bridge"; - device["manufacturer"] = "AcidBurns"; + device["manufacturer"] = HA_MANUFACTURER; String payload; size_t len = serializeJson(doc, payload); diff --git a/test/check_ha_manufacturer.ps1 b/test/check_ha_manufacturer.ps1 new file mode 100644 index 0000000..18d632d --- /dev/null +++ b/test/check_ha_manufacturer.ps1 @@ -0,0 +1,37 @@ +Set-StrictMode -Version Latest +$ErrorActionPreference = "Stop" + +$repoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..")).ProviderPath +$configPath = (Resolve-Path (Join-Path $repoRoot "include/config.h")).ProviderPath +$mqttPath = (Resolve-Path (Join-Path $repoRoot "src/mqtt_client.cpp")).ProviderPath + +$configText = Get-Content -Raw -Path $configPath +if ($configText -notmatch 'HA_MANUFACTURER\[\]\s*=\s*"AcidBurns"\s*;') { + throw "include/config.h must define HA_MANUFACTURER as exactly ""AcidBurns""." +} + +$mqttText = Get-Content -Raw -Path $mqttPath +if ($mqttText -notmatch 'device\["manufacturer"\]\s*=\s*HA_MANUFACTURER\s*;') { + throw "src/mqtt_client.cpp must assign device[""manufacturer""] from HA_MANUFACTURER." +} +if ($mqttText -match 'device\["manufacturer"\]\s*=\s*"[^"]+"\s*;') { + throw "src/mqtt_client.cpp must not hardcode manufacturer string literals." +} + +$roots = @( + Join-Path $repoRoot "src" + Join-Path $repoRoot "include" +) + +$literalHits = Get-ChildItem -Path $roots -Recurse -File -Include *.c,*.cc,*.cpp,*.h,*.hpp | + Select-String -Pattern '"AcidBurns"' | + Where-Object { (Resolve-Path $_.Path).ProviderPath -ne $configPath } + +if ($literalHits) { + $details = $literalHits | ForEach-Object { + "$($_.Path):$($_.LineNumber)" + } + throw "Unexpected hardcoded ""AcidBurns"" literal(s) outside include/config.h:`n$($details -join "`n")" +} + +Write-Host "HA manufacturer drift check passed." diff --git a/test/test_refactor_smoke/test_refactor_smoke.cpp b/test/test_refactor_smoke/test_refactor_smoke.cpp new file mode 100644 index 0000000..29e7a75 --- /dev/null +++ b/test/test_refactor_smoke/test_refactor_smoke.cpp @@ -0,0 +1,41 @@ +#include +#include + +#include "../../src/app_context.h" +#include "../../src/receiver_pipeline.h" +#include "../../src/sender_state_machine.h" +#include "config.h" + +static void test_refactor_headers_and_types() { + SenderStateMachineConfig sender_cfg = {}; + sender_cfg.short_id = 0xF19C; + sender_cfg.device_id = "dd3-F19C"; + + ReceiverSharedState shared = {}; + ReceiverPipelineConfig receiver_cfg = {}; + receiver_cfg.short_id = 0xF19C; + receiver_cfg.device_id = "dd3-F19C"; + receiver_cfg.shared = &shared; + + SenderStateMachine sender_sm; + ReceiverPipeline receiver_pipe; + + TEST_ASSERT_EQUAL_UINT16(0xF19C, sender_cfg.short_id); + TEST_ASSERT_NOT_NULL(receiver_cfg.shared); + (void)sender_sm; + (void)receiver_pipe; +} + +static void test_ha_manufacturer_constant() { + TEST_ASSERT_EQUAL_STRING("AcidBurns", HA_MANUFACTURER); +} + +void setup() { + UNITY_BEGIN(); + RUN_TEST(test_refactor_headers_and_types); + RUN_TEST(test_ha_manufacturer_constant); + UNITY_END(); +} + +void loop() {} +