Track receiver duplicate batches in web and OLED

This commit is contained in:
2026-02-13 20:03:15 +01:00
parent 195cce2bcf
commit 32358c3351
4 changed files with 54 additions and 14 deletions

View File

@@ -47,6 +47,9 @@ struct MeterData {
struct SenderStatus { struct SenderStatus {
MeterData last_data; MeterData last_data;
uint32_t last_update_ts_utc; 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; bool has_data;
}; };

View File

@@ -356,22 +356,29 @@ static void render_receiver_sender(uint8_t index) {
display.setCursor(0, 32); display.setCursor(0, 32);
display.printf("L2 %dW", static_cast<int>(round_power_w(status.last_data.phase_power_w[1]))); display.printf("L2 %dW", static_cast<int>(round_power_w(status.last_data.phase_power_w[1])));
display.setCursor(0, 42); display.setCursor(0, 42);
display.printf("L3 %dW", static_cast<int>(round_power_w(status.last_data.phase_power_w[2]))); display.printf("L3 %dW P%dW",
static_cast<int>(round_power_w(status.last_data.phase_power_w[2])),
static_cast<int>(round_power_w(status.last_data.total_power_w)));
display.setCursor(0, 52); display.setCursor(0, 52);
display.print("P"); uint32_t total_batches = status.rx_batches_total;
char p_buf[16]; uint32_t duplicate_batches = status.rx_batches_duplicate;
snprintf(p_buf, sizeof(p_buf), "%dW", static_cast<int>(round_power_w(status.last_data.total_power_w))); float duplicate_pct = 0.0f;
int16_t x1 = 0; if (total_batches > 0) {
int16_t y1 = 0; duplicate_pct = (static_cast<float>(duplicate_batches) * 100.0f) / static_cast<float>(total_batches);
uint16_t w = 0;
uint16_t h = 0;
display.getTextBounds(p_buf, 0, 0, &x1, &y1, &w, &h);
int16_t x = static_cast<int16_t>(display.width() - w);
if (x < 0) {
x = 0;
} }
display.setCursor(x, 52); char dup_time[6];
display.print(p_buf); 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<time_t>(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<double>(duplicate_pct),
static_cast<unsigned long>(duplicate_batches),
dup_time);
display.display(); display.display();
} }

View File

@@ -274,6 +274,9 @@ static void init_sender_statuses() {
g_sender_statuses[i] = {}; g_sender_statuses[i] = {};
g_sender_statuses[i].has_data = false; g_sender_statuses[i].has_data = false;
g_sender_statuses[i].last_update_ts_utc = 0; 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]; 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]); 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] = {}; 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; 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); send_batch_ack(batch_id, batch.n);
if (duplicate) { if (duplicate) {
goto receiver_loop_done; goto receiver_loop_done;

View File

@@ -412,6 +412,17 @@ static String render_sender_block(const SenderStatus &status) {
String(round_power_w(status.last_data.phase_power_w[2])) + " W<br>"; String(round_power_w(status.last_data.phase_power_w[2])) + " W<br>";
s += "Battery: " + String(status.last_data.battery_percent) + "% (" + String(status.last_data.battery_voltage_v, 2) + " V)"; 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<float>(duplicate_batches) * 100.0f) / static_cast<float>(total_batches);
}
s += "<br>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 += "</div>"; s += "</div>";
return s; return s;
} }