From ae5b4a940a14363eec68e279efbed87d50354ebf Mon Sep 17 00:00:00 2001 From: acidburns Date: Fri, 20 Feb 2026 20:42:06 +0100 Subject: [PATCH] Rework test mode to use normal LoRa batching and ACK flow --- src/main.cpp | 19 -------------- src/receiver_pipeline.cpp | 15 ++++++++++- src/sender_state_machine.cpp | 51 ++++++++++++++++++++++++++++++++++++ 3 files changed, 65 insertions(+), 20 deletions(-) diff --git a/src/main.cpp b/src/main.cpp index 9306bd7..11705dd 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -11,7 +11,6 @@ #include "receiver_pipeline.h" #include "sd_logger.h" #include "sender_state_machine.h" -#include "test_mode.h" #include "time_manager.h" #include "web_server.h" #include "wifi_manager.h" @@ -136,24 +135,6 @@ void setup() { } void loop() { -#ifdef ENABLE_TEST_MODE - if (g_role == DeviceRole::Sender) { - test_sender_loop(g_short_id, g_device_id); - display_tick(); - watchdog_kick(); - delay(50); - } else { - test_receiver_loop(g_receiver_shared.sender_statuses, NUM_SENDERS, g_short_id); - mqtt_loop(); - web_server_loop(); - display_set_receiver_status(g_receiver_shared.ap_mode, wifi_is_connected() ? wifi_get_ssid().c_str() : "AP", mqtt_is_connected()); - display_tick(); - watchdog_kick(); - delay(50); - } - return; -#endif - if (g_role == DeviceRole::Sender) { g_sender_state_machine.loop(); } else { diff --git a/src/receiver_pipeline.cpp b/src/receiver_pipeline.cpp index be44c11..4b3162e 100644 --- a/src/receiver_pipeline.cpp +++ b/src/receiver_pipeline.cpp @@ -6,6 +6,7 @@ #include "config.h" #include "display_ui.h" +#include "json_codec.h" #include "lora_transport.h" #include "mqtt_client.h" #include "payload_codec.h" @@ -77,6 +78,18 @@ static uint8_t bit_count32(uint32_t value) { return count; } +static bool mqtt_publish_sample(const MeterData &data) { +#ifdef ENABLE_TEST_MODE + String payload; + if (!meterDataToJson(data, payload)) { + return false; + } + return mqtt_publish_test(data.device_id, payload); +#else + return mqtt_publish_state(data); +#endif +} + struct BatchRxState { bool active; uint16_t batch_id; @@ -470,7 +483,7 @@ static void receiver_loop() { web_server_set_last_batch(static_cast(sender_idx), samples, count); for (size_t s = 0; s < count; ++s) { - mqtt_publish_state(samples[s]); + mqtt_publish_sample(samples[s]); } g_sender_statuses[sender_idx].last_data = samples[count - 1]; g_sender_statuses[sender_idx].last_update_ts_utc = samples[count - 1].ts_utc; diff --git a/src/sender_state_machine.cpp b/src/sender_state_machine.cpp index 232edc2..4e23ac6 100644 --- a/src/sender_state_machine.cpp +++ b/src/sender_state_machine.cpp @@ -145,6 +145,10 @@ static uint32_t g_build_invalid = 0; static constexpr uint32_t METER_SAMPLE_MAX_AGE_MS = 15000; static constexpr uint32_t METER_TIME_DELTA_TOLERANCE_S = 2; static constexpr int64_t METER_TIME_ANCHOR_DRIFT_TOLERANCE_S = 2; +#ifdef ENABLE_TEST_MODE +static uint32_t g_test_meter_last_emit_ms = 0; +static uint32_t g_test_meter_tick = 0; +#endif struct MeterSampleEvent { MeterData data; @@ -350,6 +354,27 @@ static bool parse_meter_frame_sample(const char *frame, size_t frame_len, MeterD return meter_parse_frame(frame, frame_len, parsed); } +#ifdef ENABLE_TEST_MODE +static bool generate_test_meter_sample(uint32_t now_ms, MeterData &parsed) { + if (g_test_meter_last_emit_ms != 0 && now_ms - g_test_meter_last_emit_ms < METER_SAMPLE_INTERVAL_MS) { + return false; + } + g_test_meter_last_emit_ms = now_ms; + g_test_meter_tick++; + + parsed = {}; + parsed.valid = true; + parsed.meter_seconds_valid = true; + parsed.meter_seconds = MIN_ACCEPTED_EPOCH_UTC + g_test_meter_tick; + parsed.energy_total_kwh = static_cast(g_test_meter_tick) / 1000.0f; // 1 Wh step per sample. + parsed.phase_power_w[0] = static_cast(g_test_meter_tick); + parsed.phase_power_w[1] = static_cast(g_test_meter_tick); + parsed.phase_power_w[2] = static_cast(g_test_meter_tick); + parsed.total_power_w = parsed.phase_power_w[0] + parsed.phase_power_w[1] + parsed.phase_power_w[2]; + return true; +} +#endif + #ifdef ARDUINO_ARCH_ESP32 static void meter_queue_push_latest(const MeterSampleEvent &event) { if (!g_meter_sample_queue) { @@ -374,6 +399,20 @@ static void meter_queue_push_latest(const MeterSampleEvent &event) { static void meter_reader_task_entry(void *arg) { (void)arg; for (;;) { +#ifdef ENABLE_TEST_MODE + MeterData test_sample = {}; + uint32_t now_ms = millis(); + if (!generate_test_meter_sample(now_ms, test_sample)) { + vTaskDelay(pdMS_TO_TICKS(5)); + continue; + } + MeterSampleEvent event = {}; + event.data = test_sample; + event.rx_ms = now_ms; + meter_queue_push_latest(event); + continue; +#endif + const char *frame = nullptr; size_t frame_len = 0; if (!meter_poll_frame(frame, frame_len)) { @@ -438,6 +477,14 @@ static void meter_reader_pump(uint32_t now_ms) { } #endif +#ifdef ENABLE_TEST_MODE + MeterData test_sample = {}; + if (generate_test_meter_sample(now_ms, test_sample)) { + set_last_meter_sample(test_sample, now_ms); + } + return; +#endif + const char *frame = nullptr; size_t frame_len = 0; if (!meter_poll_frame(frame, frame_len)) { @@ -1498,6 +1545,10 @@ bool SenderStateMachine::begin(const SenderStateMachineConfig &config) { g_time_acquired = false; g_sender_faults_reset_after_first_sync = false; g_sender_faults_reset_hour_utc = UINT32_MAX; +#ifdef ENABLE_TEST_MODE + g_test_meter_last_emit_ms = 0; + g_test_meter_tick = 0; +#endif update_battery_cache(); sender_transition(SenderPhase::Syncing, "begin"); return true;