Add meter Sekundenindex anchoring for epoch timestamps
Parse 0-0:96.8.0*255 meter seconds, derive sample epoch from anchored offset, and detect meter-time jumps via monotonic/delta checks.
This commit is contained in:
104
src/main.cpp
104
src/main.cpp
@@ -100,12 +100,20 @@ static MeterData g_last_meter_data = {};
|
||||
static bool g_last_meter_valid = false;
|
||||
static uint32_t g_last_meter_rx_ms = 0;
|
||||
static uint32_t g_meter_stale_seconds = 0;
|
||||
static bool g_meter_time_anchor_valid = false;
|
||||
static int64_t g_meter_epoch_offset = 0;
|
||||
static bool g_meter_time_prev_valid = false;
|
||||
static uint32_t g_meter_time_prev_seconds = 0;
|
||||
static uint32_t g_meter_time_prev_rx_ms = 0;
|
||||
static bool g_meter_time_jump_pending = false;
|
||||
static bool g_time_acquired = false;
|
||||
static uint32_t g_last_sync_request_ms = 0;
|
||||
static uint32_t g_build_attempts = 0;
|
||||
static uint32_t g_build_valid = 0;
|
||||
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;
|
||||
struct MeterSampleEvent {
|
||||
MeterData data;
|
||||
uint32_t rx_ms;
|
||||
@@ -151,7 +159,79 @@ static uint8_t bit_count32(uint32_t value) {
|
||||
return count;
|
||||
}
|
||||
|
||||
static void set_last_meter_sample(const MeterData &parsed, uint32_t rx_ms) {
|
||||
static uint32_t abs_diff_u32(uint32_t a, uint32_t b) {
|
||||
return a >= b ? (a - b) : (b - a);
|
||||
}
|
||||
|
||||
static void meter_time_update_snapshot(MeterData &parsed, uint32_t rx_ms) {
|
||||
if (!parsed.meter_seconds_valid) {
|
||||
return;
|
||||
}
|
||||
|
||||
bool jump = false;
|
||||
const char *jump_reason = nullptr;
|
||||
uint32_t delta_meter_s = 0;
|
||||
uint32_t delta_wall_s = 0;
|
||||
if (g_meter_time_prev_valid) {
|
||||
if (parsed.meter_seconds < g_meter_time_prev_seconds) {
|
||||
jump = true;
|
||||
jump_reason = "rollback";
|
||||
} else {
|
||||
delta_meter_s = parsed.meter_seconds - g_meter_time_prev_seconds;
|
||||
uint32_t delta_wall_ms = rx_ms - g_meter_time_prev_rx_ms;
|
||||
delta_wall_s = (delta_wall_ms + 500) / 1000;
|
||||
if (abs_diff_u32(delta_meter_s, delta_wall_s) > METER_TIME_DELTA_TOLERANCE_S) {
|
||||
jump = true;
|
||||
jump_reason = "delta";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (time_is_synced()) {
|
||||
uint32_t epoch_now = time_get_utc();
|
||||
if (epoch_now >= MIN_ACCEPTED_EPOCH_UTC) {
|
||||
int64_t new_offset = static_cast<int64_t>(epoch_now) - static_cast<int64_t>(parsed.meter_seconds);
|
||||
if (!g_meter_time_anchor_valid || jump) {
|
||||
g_meter_epoch_offset = new_offset;
|
||||
g_meter_time_anchor_valid = true;
|
||||
} else {
|
||||
int64_t drift_s = new_offset - g_meter_epoch_offset;
|
||||
if (drift_s > METER_TIME_ANCHOR_DRIFT_TOLERANCE_S || drift_s < -METER_TIME_ANCHOR_DRIFT_TOLERANCE_S) {
|
||||
jump = true;
|
||||
jump_reason = jump_reason ? jump_reason : "anchor";
|
||||
g_meter_epoch_offset = new_offset;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (g_meter_time_anchor_valid) {
|
||||
int64_t epoch64 = static_cast<int64_t>(parsed.meter_seconds) + g_meter_epoch_offset;
|
||||
if (epoch64 > 0 && epoch64 <= static_cast<int64_t>(UINT32_MAX)) {
|
||||
parsed.ts_utc = static_cast<uint32_t>(epoch64);
|
||||
}
|
||||
}
|
||||
|
||||
if (jump) {
|
||||
g_meter_time_jump_pending = true;
|
||||
if (SERIAL_DEBUG_MODE) {
|
||||
serial_debug_printf("meter_time: jump reason=%s sec=%lu prev=%lu d_meter=%lu d_wall=%lu",
|
||||
jump_reason ? jump_reason : "unknown",
|
||||
static_cast<unsigned long>(parsed.meter_seconds),
|
||||
static_cast<unsigned long>(g_meter_time_prev_seconds),
|
||||
static_cast<unsigned long>(delta_meter_s),
|
||||
static_cast<unsigned long>(delta_wall_s));
|
||||
}
|
||||
}
|
||||
|
||||
g_meter_time_prev_seconds = parsed.meter_seconds;
|
||||
g_meter_time_prev_rx_ms = rx_ms;
|
||||
g_meter_time_prev_valid = true;
|
||||
}
|
||||
|
||||
static void set_last_meter_sample(const MeterData &parsed_in, uint32_t rx_ms) {
|
||||
MeterData parsed = parsed_in;
|
||||
meter_time_update_snapshot(parsed, rx_ms);
|
||||
g_last_meter_data = parsed;
|
||||
g_last_meter_valid = true;
|
||||
g_last_meter_rx_ms = rx_ms;
|
||||
@@ -1095,6 +1175,8 @@ static void sender_loop() {
|
||||
bool has_snapshot = g_last_meter_valid;
|
||||
bool meter_ok = has_snapshot && meter_age_ms <= METER_SAMPLE_MAX_AGE_MS;
|
||||
if (has_snapshot) {
|
||||
data.meter_seconds = g_last_meter_data.meter_seconds;
|
||||
data.meter_seconds_valid = g_last_meter_data.meter_seconds_valid;
|
||||
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];
|
||||
@@ -1108,17 +1190,27 @@ static void sender_loop() {
|
||||
note_fault(g_sender_faults, g_sender_last_error, g_sender_last_error_utc, g_sender_last_error_ms, FaultType::MeterRead);
|
||||
display_set_last_error(g_sender_last_error, g_sender_last_error_utc, g_sender_last_error_ms);
|
||||
}
|
||||
if (g_meter_time_jump_pending) {
|
||||
g_meter_time_jump_pending = false;
|
||||
note_fault(g_sender_faults, g_sender_last_error, g_sender_last_error_utc, g_sender_last_error_ms, FaultType::MeterRead);
|
||||
display_set_last_error(g_sender_last_error, g_sender_last_error_utc, g_sender_last_error_ms);
|
||||
}
|
||||
if (g_build_count == 0 && battery_sample_due(now_ms)) {
|
||||
update_battery_cache();
|
||||
}
|
||||
data.battery_voltage_v = g_last_battery_voltage_v;
|
||||
data.battery_percent = g_last_battery_percent;
|
||||
data.rx_reject_reason = static_cast<uint8_t>(g_sender_rx_reject_reason);
|
||||
uint32_t sample_ts_utc = time_get_utc();
|
||||
if (sample_ts_utc > 0 && now_ms > g_last_sample_ms) {
|
||||
uint32_t lag_s = (now_ms - g_last_sample_ms) / 1000;
|
||||
if (sample_ts_utc > lag_s) {
|
||||
sample_ts_utc -= lag_s;
|
||||
uint32_t sample_ts_utc = 0;
|
||||
if (has_snapshot && g_last_meter_data.meter_seconds_valid && g_last_meter_data.ts_utc >= MIN_ACCEPTED_EPOCH_UTC) {
|
||||
sample_ts_utc = g_last_meter_data.ts_utc;
|
||||
} else {
|
||||
sample_ts_utc = time_get_utc();
|
||||
if (sample_ts_utc > 0 && now_ms > g_last_sample_ms) {
|
||||
uint32_t lag_s = (now_ms - g_last_sample_ms) / 1000;
|
||||
if (sample_ts_utc > lag_s) {
|
||||
sample_ts_utc -= lag_s;
|
||||
}
|
||||
}
|
||||
}
|
||||
data.ts_utc = sample_ts_utc;
|
||||
|
||||
Reference in New Issue
Block a user