Use fixed-point meter parsing and early-exit on complete frame

This commit is contained in:
2026-02-17 01:10:43 +01:00
parent 557420c200
commit ea3e99f350

View File

@@ -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<uint64_t>(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<uint32_t>(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<double>(int_part);
if (frac_div > 1U) {
value += static_cast<double>(frac_part) / static_cast<double>(frac_div);
}
if (negative) {
value = -value;
}
out_value = static_cast<float>(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 = '.';
const char *end = lparen + 1;
while (*end && *end != ')' && *end != '*') {
++end;
}
if (n + 1 < sizeof(num_buf)) {
num_buf[n++] = c;
}
} else if (n == 0) {
continue;
} else {
break;
}
}
if (n == 0) {
if (end <= lparen + 1) {
return false;
}
num_buf[n] = '\0';
out_value = static_cast<float>(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;
}