From ea3e99f350093ca310749d9d27a0f7e8106bd8ab Mon Sep 17 00:00:00 2001 From: acidburns Date: Tue, 17 Feb 2026 01:10:43 +0100 Subject: [PATCH] Use fixed-point meter parsing and early-exit on complete frame --- src/meter_driver.cpp | 105 ++++++++++++++++++++++++++++++++++--------- 1 file changed, 84 insertions(+), 21 deletions(-) diff --git a/src/meter_driver.cpp b/src/meter_driver.cpp index 5645206..7874a21 100644 --- a/src/meter_driver.cpp +++ b/src/meter_driver.cpp @@ -24,6 +24,7 @@ static uint32_t g_frames_parse_fail = 0; static uint32_t g_rx_overflow = 0; static uint32_t g_rx_timeout = 0; static uint32_t g_last_log_ms = 0; +static constexpr uint32_t METER_FIXED_FRAC_MAX_DIV = 10000; void meter_init() { #ifdef ARDUINO_ARCH_ESP32 @@ -72,35 +73,91 @@ static ObisField detect_obis_field(const char *line) { return ObisField::None; } +static bool parse_decimal_fixed(const char *start, const char *end, float &out_value) { + if (!start || !end || end <= start) { + return false; + } + + const char *cur = start; + bool started = false; + bool negative = false; + bool in_fraction = false; + bool saw_digit = false; + uint64_t int_part = 0; + uint32_t frac_part = 0; + uint32_t frac_div = 1; + + while (cur < end) { + char c = *cur++; + if (!started) { + if (c == '+' || c == '-') { + started = true; + negative = (c == '-'); + continue; + } + if (c >= '0' && c <= '9') { + started = true; + saw_digit = true; + int_part = static_cast(c - '0'); + continue; + } + if (c == '.' || c == ',') { + started = true; + in_fraction = true; + continue; + } + continue; + } + + if (c >= '0' && c <= '9') { + saw_digit = true; + uint32_t digit = static_cast(c - '0'); + if (!in_fraction) { + if (int_part <= (UINT64_MAX - digit) / 10ULL) { + int_part = int_part * 10ULL + digit; + } + } else if (frac_div < METER_FIXED_FRAC_MAX_DIV) { + frac_part = frac_part * 10U + digit; + frac_div *= 10U; + } + continue; + } + + if ((c == '.' || c == ',') && !in_fraction) { + in_fraction = true; + continue; + } + + break; + } + + if (!saw_digit) { + return false; + } + double value = static_cast(int_part); + if (frac_div > 1U) { + value += static_cast(frac_part) / static_cast(frac_div); + } + if (negative) { + value = -value; + } + out_value = static_cast(value); + return true; +} + static bool parse_obis_ascii_payload_value(const char *line, float &out_value) { const char *lparen = strchr(line, '('); if (!lparen) { return false; } - const char *cur = lparen + 1; - char num_buf[24]; - size_t n = 0; - while (*cur && *cur != ')' && *cur != '*') { - char c = *cur++; - if ((c >= '0' && c <= '9') || c == '-' || c == '+' || c == '.' || c == ',') { - if (c == ',') { - c = '.'; - } - if (n + 1 < sizeof(num_buf)) { - num_buf[n++] = c; - } - } else if (n == 0) { - continue; - } else { - break; - } + const char *end = lparen + 1; + while (*end && *end != ')' && *end != '*') { + ++end; } - if (n == 0) { + if (end <= lparen + 1) { return false; } - num_buf[n] = '\0'; - out_value = static_cast(atof(num_buf)); - return true; + return parse_decimal_fixed(lparen + 1, end, out_value); } static bool parse_obis_ascii_unit_scale(const char *line, float &value) { @@ -342,6 +399,12 @@ bool meter_parse_frame(const char *frame, size_t len, MeterData &data) { break; } + if (energy_ok && total_p_ok && p1_ok && p2_ok && p3_ok && data.meter_seconds_valid) { + data.valid = true; + g_frames_ok++; + return true; + } + line_len = 0; continue; }