Use configured local timezone in web UI and drop legacy history CSV parsing

This commit is contained in:
2026-02-16 11:14:30 +01:00
parent 4de1dda82b
commit 6ea8d9d5fc

View File

@@ -57,26 +57,23 @@ static HistoryJob g_history = {};
static constexpr size_t SD_LIST_MAX_FILES = 200; static constexpr size_t SD_LIST_MAX_FILES = 200;
static constexpr size_t SD_DOWNLOAD_MAX_PATH = 160; static constexpr size_t SD_DOWNLOAD_MAX_PATH = 160;
static String format_utc_hms(uint32_t ts_utc) { static String format_local_hms(uint32_t ts_utc) {
if (ts_utc == 0) { if (ts_utc == 0) {
return "n/a"; return "n/a";
} }
time_t t = static_cast<time_t>(ts_utc); time_t t = static_cast<time_t>(ts_utc);
struct tm tm_utc; struct tm tm_local;
gmtime_r(&t, &tm_utc); localtime_r(&t, &tm_local);
char buf[16]; char buf[24];
snprintf(buf, sizeof(buf), "%02d:%02d:%02d UTC", strftime(buf, sizeof(buf), "%H:%M:%S %Z", &tm_local);
tm_utc.tm_hour,
tm_utc.tm_min,
tm_utc.tm_sec);
return String(buf); return String(buf);
} }
static String format_epoch_hms(uint32_t ts_utc) { static String format_epoch_local_hms(uint32_t ts_utc) {
if (ts_utc == 0) { if (ts_utc == 0) {
return "n/a"; return "n/a";
} }
return String(ts_utc) + " (" + format_utc_hms(ts_utc) + ")"; return String(ts_utc) + " (" + format_local_hms(ts_utc) + ")";
} }
static uint32_t timestamp_age_seconds(uint32_t ts_utc) { static uint32_t timestamp_age_seconds(uint32_t ts_utc) {
@@ -277,12 +274,12 @@ static bool history_parse_line(const char *line, uint32_t &ts_out, float &p_out)
if (!line || line[0] < '0' || line[0] > '9') { if (!line || line[0] < '0' || line[0] > '9') {
return false; return false;
} }
const char *comma = strchr(line, ','); const char *comma1 = strchr(line, ',');
if (!comma) { if (!comma1) {
return false; return false;
} }
char ts_buf[16]; char ts_buf[16];
size_t ts_len = static_cast<size_t>(comma - line); size_t ts_len = static_cast<size_t>(comma1 - line);
if (ts_len >= sizeof(ts_buf)) { if (ts_len >= sizeof(ts_buf)) {
return false; return false;
} }
@@ -293,32 +290,22 @@ static bool history_parse_line(const char *line, uint32_t &ts_out, float &p_out)
if (end == ts_buf) { if (end == ts_buf) {
return false; return false;
} }
auto parse_float_field = [](const char *start, const char *end, float &out) -> bool { const char *comma2 = strchr(comma1 + 1, ',');
if (!start) { if (!comma2) {
return false; return false;
}
char p_buf[16];
size_t p_len = end ? static_cast<size_t>(end - start) : strlen(start);
if (p_len == 0 || p_len >= sizeof(p_buf)) {
return false;
}
memcpy(p_buf, start, p_len);
p_buf[p_len] = '\0';
char *endp = nullptr;
out = strtof(p_buf, &endp);
return endp != p_buf;
};
const char *field2_start = comma + 1;
const char *field2_end = strchr(field2_start, ',');
float p = 0.0f;
bool parsed_power = parse_float_field(field2_start, field2_end, p);
if (!parsed_power && field2_end) {
const char *field3_start = field2_end + 1;
const char *field3_end = strchr(field3_start, ',');
parsed_power = parse_float_field(field3_start, field3_end, p);
} }
if (!parsed_power) { const char *p_start = comma2 + 1;
const char *p_end = strchr(p_start, ',');
char p_buf[16];
size_t p_len = p_end ? static_cast<size_t>(p_end - p_start) : strlen(p_start);
if (p_len == 0 || p_len >= sizeof(p_buf)) {
return false;
}
memcpy(p_buf, p_start, p_len);
p_buf[p_len] = '\0';
char *endp = nullptr;
float p = strtof(p_buf, &endp);
if (endp == p_buf) {
return false; return false;
} }
ts_out = ts; ts_out = ts;
@@ -418,7 +405,7 @@ static String render_sender_block(const SenderStatus &status) {
if (!status.has_data) { if (!status.has_data) {
s += "No data"; s += "No data";
} else { } else {
s += "Last update: " + format_epoch_hms(status.last_update_ts_utc); s += "Last update: " + format_epoch_local_hms(status.last_update_ts_utc);
if (time_is_synced()) { if (time_is_synced()) {
s += " (" + String(timestamp_age_seconds(status.last_update_ts_utc)) + "s ago)"; s += " (" + String(timestamp_age_seconds(status.last_update_ts_utc)) + "s ago)";
} }
@@ -437,7 +424,7 @@ static String render_sender_block(const SenderStatus &status) {
duplicate_pct = (static_cast<float>(duplicate_batches) * 100.0f) / static_cast<float>(total_batches); 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 += "<br>Dup batches: " + String(duplicate_batches) + "/" + String(total_batches) + " (" + String(duplicate_pct, 1) + "%)";
s += " last: " + format_epoch_hms(status.rx_last_duplicate_ts_utc); s += " last: " + format_epoch_local_hms(status.rx_last_duplicate_ts_utc);
if (time_is_synced() && status.rx_last_duplicate_ts_utc > 0) { 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 += " (" + String(timestamp_age_seconds(status.rx_last_duplicate_ts_utc)) + "s ago)";
} }
@@ -665,14 +652,14 @@ static void handle_sender() {
if (g_last_batch_count[i] > 0) { if (g_last_batch_count[i] > 0) {
html += "<h3>Last batch (" + String(g_last_batch_count[i]) + " samples)</h3>"; html += "<h3>Last batch (" + String(g_last_batch_count[i]) + " samples)</h3>";
html += "<table border='1' cellspacing='0' cellpadding='3'>"; html += "<table border='1' cellspacing='0' cellpadding='3'>";
html += "<tr><th>#</th><th>ts_utc</th><th>ts_hms_utc</th><th>e_kwh</th><th>p_w</th><th>p1_w</th><th>p2_w</th><th>p3_w</th>"; html += "<tr><th>#</th><th>ts_utc</th><th>ts_hms_local</th><th>e_kwh</th><th>p_w</th><th>p1_w</th><th>p2_w</th><th>p3_w</th>";
html += "<th>bat_v</th><th>bat_pct</th><th>rssi</th><th>snr</th><th>err_tx</th><th>err_last</th><th>rx_reject</th></tr>"; html += "<th>bat_v</th><th>bat_pct</th><th>rssi</th><th>snr</th><th>err_tx</th><th>err_last</th><th>rx_reject</th></tr>";
for (uint8_t r = 0; r < g_last_batch_count[i]; ++r) { for (uint8_t r = 0; r < g_last_batch_count[i]; ++r) {
const MeterData &d = g_last_batch[i][r]; const MeterData &d = g_last_batch[i][r];
html += "<tr>"; html += "<tr>";
html += "<td>" + String(r) + "</td>"; html += "<td>" + String(r) + "</td>";
html += "<td>" + String(d.ts_utc) + "</td>"; html += "<td>" + String(d.ts_utc) + "</td>";
html += "<td>" + format_utc_hms(d.ts_utc) + "</td>"; html += "<td>" + format_local_hms(d.ts_utc) + "</td>";
html += "<td>" + String(d.energy_total_kwh, 2) + "</td>"; html += "<td>" + String(d.energy_total_kwh, 2) + "</td>";
html += "<td>" + String(round_power_w(d.total_power_w)) + "</td>"; html += "<td>" + String(round_power_w(d.total_power_w)) + "</td>";
html += "<td>" + String(round_power_w(d.phase_power_w[0])) + "</td>"; html += "<td>" + String(round_power_w(d.phase_power_w[0])) + "</td>";