diff --git a/include/data_model.h b/include/data_model.h index 670b799..4f0568d 100644 --- a/include/data_model.h +++ b/include/data_model.h @@ -47,6 +47,9 @@ struct MeterData { struct SenderStatus { MeterData last_data; uint32_t last_update_ts_utc; + uint32_t rx_batches_total; + uint32_t rx_batches_duplicate; + uint32_t rx_last_duplicate_ts_utc; bool has_data; }; diff --git a/src/display_ui.cpp b/src/display_ui.cpp index 1f0196a..31cea5b 100644 --- a/src/display_ui.cpp +++ b/src/display_ui.cpp @@ -356,22 +356,29 @@ static void render_receiver_sender(uint8_t index) { display.setCursor(0, 32); display.printf("L2 %dW", static_cast(round_power_w(status.last_data.phase_power_w[1]))); display.setCursor(0, 42); - display.printf("L3 %dW", static_cast(round_power_w(status.last_data.phase_power_w[2]))); + display.printf("L3 %dW P%dW", + static_cast(round_power_w(status.last_data.phase_power_w[2])), + static_cast(round_power_w(status.last_data.total_power_w))); display.setCursor(0, 52); - display.print("P"); - char p_buf[16]; - snprintf(p_buf, sizeof(p_buf), "%dW", static_cast(round_power_w(status.last_data.total_power_w))); - int16_t x1 = 0; - int16_t y1 = 0; - uint16_t w = 0; - uint16_t h = 0; - display.getTextBounds(p_buf, 0, 0, &x1, &y1, &w, &h); - int16_t x = static_cast(display.width() - w); - if (x < 0) { - x = 0; + uint32_t total_batches = status.rx_batches_total; + uint32_t duplicate_batches = status.rx_batches_duplicate; + float duplicate_pct = 0.0f; + if (total_batches > 0) { + duplicate_pct = (static_cast(duplicate_batches) * 100.0f) / static_cast(total_batches); } - display.setCursor(x, 52); - display.print(p_buf); + char dup_time[6]; + strncpy(dup_time, "--:--", sizeof(dup_time)); + dup_time[sizeof(dup_time) - 1] = '\0'; + if (status.rx_last_duplicate_ts_utc > 0 && time_is_synced()) { + time_t t = static_cast(status.rx_last_duplicate_ts_utc); + struct tm timeinfo; + localtime_r(&t, &timeinfo); + snprintf(dup_time, sizeof(dup_time), "%02d:%02d", timeinfo.tm_hour, timeinfo.tm_min); + } + display.printf("Dup %.1f%%(%lu) %s", + static_cast(duplicate_pct), + static_cast(duplicate_batches), + dup_time); display.display(); } diff --git a/src/main.cpp b/src/main.cpp index 160bc18..8f8ba27 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -274,6 +274,9 @@ static void init_sender_statuses() { g_sender_statuses[i] = {}; g_sender_statuses[i].has_data = false; g_sender_statuses[i].last_update_ts_utc = 0; + g_sender_statuses[i].rx_batches_total = 0; + g_sender_statuses[i].rx_batches_duplicate = 0; + g_sender_statuses[i].rx_last_duplicate_ts_utc = 0; g_sender_statuses[i].last_data.short_id = EXPECTED_SENDER_IDS[i]; snprintf(g_sender_statuses[i].last_data.device_id, sizeof(g_sender_statuses[i].last_data.device_id), "dd3-%04X", EXPECTED_SENDER_IDS[i]); g_sender_faults_remote[i] = {}; @@ -1261,6 +1264,22 @@ static void receiver_loop() { } bool duplicate = sender_idx >= 0 && g_last_batch_id_rx[sender_idx] == batch_id; + if (sender_idx >= 0) { + SenderStatus &status = g_sender_statuses[sender_idx]; + if (status.rx_batches_total < UINT32_MAX) { + status.rx_batches_total++; + } + if (duplicate) { + if (status.rx_batches_duplicate < UINT32_MAX) { + status.rx_batches_duplicate++; + } + uint32_t duplicate_ts = time_get_utc(); + if (duplicate_ts == 0) { + duplicate_ts = batch.t_last; + } + status.rx_last_duplicate_ts_utc = duplicate_ts; + } + } send_batch_ack(batch_id, batch.n); if (duplicate) { goto receiver_loop_done; diff --git a/src/web_server.cpp b/src/web_server.cpp index d702a47..65edb7c 100644 --- a/src/web_server.cpp +++ b/src/web_server.cpp @@ -412,6 +412,17 @@ static String render_sender_block(const SenderStatus &status) { String(round_power_w(status.last_data.phase_power_w[2])) + " W
"; s += "Battery: " + String(status.last_data.battery_percent) + "% (" + String(status.last_data.battery_voltage_v, 2) + " V)"; } + uint32_t total_batches = status.rx_batches_total; + uint32_t duplicate_batches = status.rx_batches_duplicate; + float duplicate_pct = 0.0f; + if (total_batches > 0) { + duplicate_pct = (static_cast(duplicate_batches) * 100.0f) / static_cast(total_batches); + } + s += "
Dup batches: " + String(duplicate_batches) + "/" + String(total_batches) + " (" + String(duplicate_pct, 1) + "%)"; + s += " last: " + format_utc_timestamp(status.rx_last_duplicate_ts_utc); + if (time_is_synced() && status.rx_last_duplicate_ts_utc > 0) { + s += " (" + String(timestamp_age_seconds(status.rx_last_duplicate_ts_utc)) + "s ago)"; + } s += ""; return s; }