166 lines
3.8 KiB
C++
166 lines
3.8 KiB
C++
#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;
|
|
|
|
void meter_init() {
|
|
Serial2.begin(9600, SERIAL_7E1, PIN_METER_RX, -1);
|
|
}
|
|
|
|
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;
|
|
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;
|
|
}
|
|
|
|
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;
|
|
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.valid = false;
|
|
|
|
return meter_read_ascii(data);
|
|
}
|