From e83bc11deaf0c64b4fc8a75b88fbaf5204ce9488 Mon Sep 17 00:00:00 2001 From: acidburns Date: Fri, 13 Feb 2026 22:48:48 +0100 Subject: [PATCH] Improve meter ingestion resilience under UART gaps --- src/main.cpp | 29 ++++++++++++++++++++--------- src/meter_driver.cpp | 13 ++++++++++--- 2 files changed, 30 insertions(+), 12 deletions(-) diff --git a/src/main.cpp b/src/main.cpp index 8f8ba27..0e65f1b 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -369,14 +369,18 @@ static void reset_build_counters() { g_build_invalid = 0; } -static bool append_meter_sample(const MeterData &data, bool meter_ok) { - if (!meter_ok) { +static bool append_meter_sample(const MeterData &data, bool meter_ok, bool has_snapshot) { + if (!has_snapshot) { g_build_invalid++; return false; } g_last_sample_ts_utc = data.ts_utc; g_build_samples[g_build_count++] = data; - g_build_valid++; + if (meter_ok) { + g_build_valid++; + } else { + g_build_invalid++; + } if (g_build_count >= METER_BATCH_MAX_SAMPLES) { batch_queue_enqueue(g_build_samples, g_build_count); g_build_count = 0; @@ -1035,8 +1039,9 @@ static void sender_loop() { g_build_attempts++; uint32_t meter_age_ms = g_last_meter_valid ? (now_ms - g_last_meter_rx_ms) : UINT32_MAX; // Reuse recent good samples to bridge short parser gaps without accepting stale data forever. - bool meter_ok = g_last_meter_valid && meter_age_ms <= METER_SAMPLE_MAX_AGE_MS; - if (meter_ok) { + bool has_snapshot = g_last_meter_valid; + bool meter_ok = has_snapshot && meter_age_ms <= METER_SAMPLE_MAX_AGE_MS; + if (has_snapshot) { data.energy_total_kwh = g_last_meter_data.energy_total_kwh; data.total_power_w = g_last_meter_data.total_power_w; data.phase_power_w[0] = g_last_meter_data.phase_power_w[0]; @@ -1064,9 +1069,9 @@ static void sender_loop() { } } data.ts_utc = sample_ts_utc; - data.valid = meter_ok; + data.valid = has_snapshot; - bool appended = append_meter_sample(data, meter_ok); + bool appended = append_meter_sample(data, meter_ok, has_snapshot); if (SERIAL_DEBUG_MODE) { serial_debug_printf("sample: i=%lu ok=%u appended=%u e_kwh=%.3f p1=%.1f p2=%.1f p3=%.1f ms=%lu", static_cast(g_build_attempts), @@ -1233,8 +1238,9 @@ static void sender_loop() { } if (!g_batch_ack_pending && next_due > now_ms) { watchdog_kick(); + uint32_t idle_ms = next_due - now_ms; if (SERIAL_DEBUG_MODE) { - g_sender_sleep_ms += (next_due - now_ms); + g_sender_sleep_ms += idle_ms; if (now_ms - g_sender_power_log_ms >= 10000) { g_sender_power_log_ms = now_ms; serial_debug_printf("power: rx_ms=%lu sleep_ms=%lu", static_cast(g_sender_rx_window_ms), @@ -1242,7 +1248,12 @@ static void sender_loop() { } } lora_sleep(); - light_sleep_ms(next_due - now_ms); + if (g_time_acquired) { + // Keep the meter reader task running while metering is active. + delay(idle_ms); + } else { + light_sleep_ms(idle_ms); + } } } diff --git a/src/meter_driver.cpp b/src/meter_driver.cpp index 50c9f00..0626e89 100644 --- a/src/meter_driver.cpp +++ b/src/meter_driver.cpp @@ -4,9 +4,9 @@ #include #include -// LoRa TX/RX windows can block the main loop for several seconds at SF12. -// Keep partial frame state long enough so valid telegrams are not dropped. -static constexpr uint32_t METER_FRAME_TIMEOUT_MS = 20000; +// Dedicated reader task pumps UART continuously; keep timeout short so parser can +// recover quickly from broken frames. +static constexpr uint32_t METER_FRAME_TIMEOUT_MS = 3000; static constexpr size_t METER_FRAME_MAX = 512; enum class MeterRxState : uint8_t { @@ -142,6 +142,13 @@ bool meter_poll_frame(const char *&frame, size_t &len) { continue; } + // Fast resync if a new telegram starts before current frame completed. + if (c == '/') { + g_frame_len = 0; + g_frame_buf[g_frame_len++] = c; + continue; + } + if (g_frame_len + 1 >= sizeof(g_frame_buf)) { g_rx_overflow++; g_rx_state = MeterRxState::WaitStart;