chore: unify HA manufacturer and add refactor guards
This commit is contained in:
@@ -144,7 +144,8 @@ Home Assistant discovery:
|
|||||||
- `identifiers: ["<device_id>"]`
|
- `identifiers: ["<device_id>"]`
|
||||||
- `name: "<device_id>"`
|
- `name: "<device_id>"`
|
||||||
- `model: "DD3-LoRa-Bridge"`
|
- `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
|
## Web UI, Wi-Fi, SD
|
||||||
|
|
||||||
|
|||||||
77
docs/REFRACTOR_NOTES.md
Normal file
77
docs/REFRACTOR_NOTES.md
Normal file
@@ -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.
|
||||||
@@ -86,6 +86,19 @@ constexpr bool WEB_AUTH_REQUIRE_STA = true;
|
|||||||
constexpr bool WEB_AUTH_REQUIRE_AP = true;
|
constexpr bool WEB_AUTH_REQUIRE_AP = true;
|
||||||
constexpr const char *WEB_AUTH_DEFAULT_USER = "admin";
|
constexpr const char *WEB_AUTH_DEFAULT_USER = "admin";
|
||||||
constexpr const char *WEB_AUTH_DEFAULT_PASS = "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 uint8_t NUM_SENDERS = 1;
|
||||||
constexpr uint32_t MIN_ACCEPTED_EPOCH_UTC = 1769904000UL; // 2026-02-01 00:00:00 UTC
|
constexpr uint32_t MIN_ACCEPTED_EPOCH_UTC = 1769904000UL; // 2026-02-01 00:00:00 UTC
|
||||||
|
|||||||
@@ -114,7 +114,7 @@ static bool publish_discovery_sensor(const char *device_id, const char *key, con
|
|||||||
identifiers.add(String(device_id));
|
identifiers.add(String(device_id));
|
||||||
device["name"] = String(device_id);
|
device["name"] = String(device_id);
|
||||||
device["model"] = "DD3-LoRa-Bridge";
|
device["model"] = "DD3-LoRa-Bridge";
|
||||||
device["manufacturer"] = "AcidBurns";
|
device["manufacturer"] = HA_MANUFACTURER;
|
||||||
|
|
||||||
String payload;
|
String payload;
|
||||||
size_t len = serializeJson(doc, payload);
|
size_t len = serializeJson(doc, payload);
|
||||||
|
|||||||
37
test/check_ha_manufacturer.ps1
Normal file
37
test/check_ha_manufacturer.ps1
Normal file
@@ -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."
|
||||||
41
test/test_refactor_smoke/test_refactor_smoke.cpp
Normal file
41
test/test_refactor_smoke/test_refactor_smoke.cpp
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
#include <Arduino.h>
|
||||||
|
#include <unity.h>
|
||||||
|
|
||||||
|
#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() {}
|
||||||
|
|
||||||
Reference in New Issue
Block a user