Add IEC62056 parsing, OLED timing, and batch LoRa send
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
#include "meter_driver.h"
|
||||
#include "config.h"
|
||||
#include <math.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
static constexpr uint32_t METER_READ_TIMEOUT_MS = 2000;
|
||||
@@ -76,7 +77,7 @@ void meter_init() {
|
||||
Serial2.begin(9600, SERIAL_7E1, PIN_METER_RX, -1);
|
||||
}
|
||||
|
||||
bool meter_read(MeterData &data) {
|
||||
static bool meter_read_sml(MeterData &data) {
|
||||
uint8_t buffer[SML_BUFFER_SIZE];
|
||||
size_t len = 0;
|
||||
bool started = false;
|
||||
@@ -164,3 +165,182 @@ parse_frame:
|
||||
data.valid = ok;
|
||||
return ok;
|
||||
}
|
||||
|
||||
static bool parse_obis_ascii_value(const char *line, const char *obis, float &out_value) {
|
||||
const char *p = strstr(line, obis);
|
||||
if (!p) {
|
||||
return false;
|
||||
}
|
||||
const char *lparen = strchr(p, '(');
|
||||
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;
|
||||
}
|
||||
}
|
||||
if (n == 0) {
|
||||
return false;
|
||||
}
|
||||
num_buf[n] = '\0';
|
||||
out_value = static_cast<float>(atof(num_buf));
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool parse_obis_ascii_unit_scale(const char *line, const char *obis, float &value) {
|
||||
const char *p = strstr(line, obis);
|
||||
if (!p) {
|
||||
return false;
|
||||
}
|
||||
const char *asterisk = strchr(p, '*');
|
||||
if (!asterisk) {
|
||||
return false;
|
||||
}
|
||||
const char *end = strchr(asterisk, ')');
|
||||
if (!end) {
|
||||
return false;
|
||||
}
|
||||
char unit_buf[8];
|
||||
size_t ulen = 0;
|
||||
for (const char *c = asterisk + 1; c < end && ulen + 1 < sizeof(unit_buf); ++c) {
|
||||
if (*c == ' ') {
|
||||
continue;
|
||||
}
|
||||
unit_buf[ulen++] = *c;
|
||||
}
|
||||
unit_buf[ulen] = '\0';
|
||||
if (ulen == 0) {
|
||||
return false;
|
||||
}
|
||||
if (strcmp(unit_buf, "Wh") == 0) {
|
||||
value *= 0.001f;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool meter_read_ascii(MeterData &data) {
|
||||
const uint32_t start_ms = millis();
|
||||
bool in_telegram = false;
|
||||
bool got_any = false;
|
||||
|
||||
bool energy_ok = false;
|
||||
bool total_p_ok = false;
|
||||
bool p1_ok = false;
|
||||
bool p2_ok = false;
|
||||
bool p3_ok = false;
|
||||
bool v1_ok = false;
|
||||
bool v2_ok = false;
|
||||
bool v3_ok = false;
|
||||
|
||||
char line[128];
|
||||
size_t line_len = 0;
|
||||
|
||||
while (millis() - start_ms < METER_READ_TIMEOUT_MS) {
|
||||
while (Serial2.available()) {
|
||||
char c = static_cast<char>(Serial2.read());
|
||||
if (!in_telegram) {
|
||||
if (c == '/') {
|
||||
in_telegram = true;
|
||||
line_len = 0;
|
||||
line[line_len++] = c;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (c == '\r') {
|
||||
continue;
|
||||
}
|
||||
if (c == '\n') {
|
||||
line[line_len] = '\0';
|
||||
if (line[0] == '!') {
|
||||
return got_any;
|
||||
}
|
||||
|
||||
float value = NAN;
|
||||
if (parse_obis_ascii_value(line, "1-0:1.8.0", value)) {
|
||||
parse_obis_ascii_unit_scale(line, "1-0:1.8.0", value);
|
||||
data.energy_total_kwh = value;
|
||||
energy_ok = true;
|
||||
got_any = true;
|
||||
}
|
||||
if (parse_obis_ascii_value(line, "1-0:16.7.0", value)) {
|
||||
data.total_power_w = value;
|
||||
total_p_ok = true;
|
||||
got_any = true;
|
||||
}
|
||||
if (parse_obis_ascii_value(line, "1-0:36.7.0", value)) {
|
||||
data.phase_power_w[0] = value;
|
||||
p1_ok = true;
|
||||
got_any = true;
|
||||
}
|
||||
if (parse_obis_ascii_value(line, "1-0:56.7.0", value)) {
|
||||
data.phase_power_w[1] = value;
|
||||
p2_ok = true;
|
||||
got_any = true;
|
||||
}
|
||||
if (parse_obis_ascii_value(line, "1-0:76.7.0", value)) {
|
||||
data.phase_power_w[2] = value;
|
||||
p3_ok = true;
|
||||
got_any = true;
|
||||
}
|
||||
if (parse_obis_ascii_value(line, "1-0:32.7.0", value)) {
|
||||
data.phase_voltage_v[0] = value;
|
||||
v1_ok = true;
|
||||
got_any = true;
|
||||
}
|
||||
if (parse_obis_ascii_value(line, "1-0:52.7.0", value)) {
|
||||
data.phase_voltage_v[1] = value;
|
||||
v2_ok = true;
|
||||
got_any = true;
|
||||
}
|
||||
if (parse_obis_ascii_value(line, "1-0:72.7.0", value)) {
|
||||
data.phase_voltage_v[2] = value;
|
||||
v3_ok = true;
|
||||
got_any = true;
|
||||
}
|
||||
|
||||
line_len = 0;
|
||||
continue;
|
||||
}
|
||||
if (line_len + 1 < sizeof(line)) {
|
||||
line[line_len++] = c;
|
||||
}
|
||||
}
|
||||
delay(5);
|
||||
}
|
||||
|
||||
data.valid = energy_ok || total_p_ok || p1_ok || p2_ok || p3_ok || v1_ok || v2_ok || v3_ok;
|
||||
return data.valid;
|
||||
}
|
||||
|
||||
bool meter_read(MeterData &data) {
|
||||
data.energy_total_kwh = NAN;
|
||||
data.total_power_w = NAN;
|
||||
data.phase_power_w[0] = NAN;
|
||||
data.phase_power_w[1] = NAN;
|
||||
data.phase_power_w[2] = NAN;
|
||||
data.phase_voltage_v[0] = NAN;
|
||||
data.phase_voltage_v[1] = NAN;
|
||||
data.phase_voltage_v[2] = NAN;
|
||||
data.valid = false;
|
||||
|
||||
if (meter_read_ascii(data)) {
|
||||
return true;
|
||||
}
|
||||
return meter_read_sml(data);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user