mirror of
https://github.com/hatlabs/SH-ESP32-vedirect.git
synced 2025-05-15 22:50:11 -07:00
First functional implementation
This commit is contained in:
parent
c073a5e1d7
commit
8a1d60f2ea
17
src/main.cpp
17
src/main.cpp
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
#include "sensesp_app.h"
|
#include "sensesp_app.h"
|
||||||
#include "sensesp_app_builder.h"
|
#include "sensesp_app_builder.h"
|
||||||
|
#include "sensors/vedirect.h"
|
||||||
#include "signalk/signalk_output.h"
|
#include "signalk/signalk_output.h"
|
||||||
|
|
||||||
// SDA and SCL pins on SH-ESP32
|
// SDA and SCL pins on SH-ESP32
|
||||||
@ -11,7 +12,6 @@
|
|||||||
#define TX_PIN SCL_PIN
|
#define TX_PIN SCL_PIN
|
||||||
#define RX_PIN SDA_PIN
|
#define RX_PIN SDA_PIN
|
||||||
|
|
||||||
|
|
||||||
ReactESP app([]() {
|
ReactESP app([]() {
|
||||||
// Some initialization boilerplate when in debug mode...
|
// Some initialization boilerplate when in debug mode...
|
||||||
#ifndef SERIAL_DEBUG_DISABLED
|
#ifndef SERIAL_DEBUG_DISABLED
|
||||||
@ -28,9 +28,18 @@ ReactESP app([]() {
|
|||||||
|
|
||||||
Serial1.begin(19200, SERIAL_8N1, RX_PIN, TX_PIN, false);
|
Serial1.begin(19200, SERIAL_8N1, RX_PIN, TX_PIN, false);
|
||||||
|
|
||||||
app.onAvailable(Serial1, []() {
|
// app.onAvailable(Serial1, []() {
|
||||||
Serial.write(Serial1.read());
|
// Serial.write(Serial1.read());
|
||||||
});
|
// });
|
||||||
|
|
||||||
|
VEDirectInput* vedi = new VEDirectInput(&Serial1);
|
||||||
|
|
||||||
|
vedi->parser.data.channel_1_battery_voltage.connect_to(new SKOutputNumber(
|
||||||
|
"electrical.batteries.house.voltage", "/battery/1/voltage"));
|
||||||
|
vedi->parser.data.channel_1_battery_current.connect_to(new SKOutputNumber(
|
||||||
|
"electrical.batteries.house.current", "/battery/1/current"));
|
||||||
|
vedi->parser.data.state_of_charge.connect_to(new SKOutputNumber(
|
||||||
|
"electrical.batteries.house.capacity.stateOfCharge", "/battery/1/stateOfCharge"));
|
||||||
|
|
||||||
sensesp_app->enable();
|
sensesp_app->enable();
|
||||||
});
|
});
|
||||||
|
17
src/sensors/vedirect.cpp
Normal file
17
src/sensors/vedirect.cpp
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
|
||||||
|
#include "vedirect.h"
|
||||||
|
|
||||||
|
#include "sensesp.h"
|
||||||
|
|
||||||
|
VEDirectInput::VEDirectInput(Stream* rx_stream)
|
||||||
|
: Sensor(), rx_stream_{rx_stream} {}
|
||||||
|
|
||||||
|
|
||||||
|
void VEDirectInput::enable() {
|
||||||
|
// enable reading the serial port
|
||||||
|
app.onAvailable(*rx_stream_, [this]() {
|
||||||
|
while (rx_stream_->available()) {
|
||||||
|
parser.handle(rx_stream_->read());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
18
src/sensors/vedirect.h
Normal file
18
src/sensors/vedirect.h
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
#ifndef _VEDIRECT_H_
|
||||||
|
#define _VEDIRECT_H_
|
||||||
|
|
||||||
|
#include "system/vedirect_parser.h"
|
||||||
|
|
||||||
|
#include "sensors/sensor.h"
|
||||||
|
|
||||||
|
|
||||||
|
class VEDirectInput : public Sensor {
|
||||||
|
public:
|
||||||
|
VEDirectInput(Stream* rx_stream);
|
||||||
|
virtual void enable() override final;
|
||||||
|
VEDirect::Parser parser;
|
||||||
|
private:
|
||||||
|
Stream* rx_stream_;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
441
src/system/vedirect_parser.cpp
Normal file
441
src/system/vedirect_parser.cpp
Normal file
@ -0,0 +1,441 @@
|
|||||||
|
#include "vedirect_parser.h"
|
||||||
|
|
||||||
|
#include <cstring>
|
||||||
|
|
||||||
|
namespace VEDirect {
|
||||||
|
|
||||||
|
bool FieldParser_mV(const String value, ObservableValue<float>* observable) {
|
||||||
|
float val_ = value.toFloat() / 1000; // mV to V
|
||||||
|
observable->set(val_);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FieldParser_permille(const String value,
|
||||||
|
ObservableValue<float>* observable) {
|
||||||
|
float val_ = value.toFloat() / 1000; // permille to ratio
|
||||||
|
observable->set(val_);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FieldParser_W(const String value, ObservableValue<float>* observable) {
|
||||||
|
float val_ = value.toFloat();
|
||||||
|
observable->set(val_);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FieldParser_mA(const String value, ObservableValue<float>* observable) {
|
||||||
|
float val_ = value.toFloat() / 1000; // mA to A
|
||||||
|
observable->set(val_);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FieldParser_mAh(const String value, ObservableValue<float>* observable) {
|
||||||
|
float val_ = value.toFloat() / 1000 * 3600; // mAh to Coulomb
|
||||||
|
observable->set(val_);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FieldParser_degC(const String value, ObservableValue<float>* observable) {
|
||||||
|
float val_ = value.toFloat() + 273.15; // degC to K
|
||||||
|
observable->set(val_);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FieldParser_minutes(const String value,
|
||||||
|
ObservableValue<float>* observable) {
|
||||||
|
float val_ = value.toFloat() * 60; // minutes to seconds
|
||||||
|
observable->set(val_);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FieldParser_seconds(const String value,
|
||||||
|
ObservableValue<float>* observable) {
|
||||||
|
float val_ = value.toFloat(); // minutes to seconds
|
||||||
|
observable->set(val_);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FieldParser_kWh(const String value, ObservableValue<float>* observable) {
|
||||||
|
float val_ = value.toFloat() * 0.01 * 3.6e6; // 0.01 kWh to J
|
||||||
|
observable->set(val_);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FieldParser_Vac(const String value, ObservableValue<float>* observable) {
|
||||||
|
float val_ = value.toFloat() * 0.01; // 0.01 V to V
|
||||||
|
observable->set(val_);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FieldParser_Iac(const String value, ObservableValue<float>* observable) {
|
||||||
|
float val_ = value.toFloat() * 0.1; // 0.1 A to A
|
||||||
|
observable->set(val_);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FieldParser_VA(const String value, ObservableValue<float>* observable) {
|
||||||
|
float val_ = value.toFloat();
|
||||||
|
observable->set(val_);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FieldParser_int(const String value, ObservableValue<float>* observable) {
|
||||||
|
float val_ = value.toInt();
|
||||||
|
observable->set(val_);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FieldParser_int(const String value, ObservableValue<int>* observable) {
|
||||||
|
float val_ = value.toInt();
|
||||||
|
observable->set(val_);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FieldParser_ONOFF(const String value, ObservableValue<bool>* observable) {
|
||||||
|
bool ok = false;
|
||||||
|
bool val_;
|
||||||
|
|
||||||
|
String val_upper = value;
|
||||||
|
val_upper.toUpperCase();
|
||||||
|
|
||||||
|
if (val_upper == "ON") {
|
||||||
|
val_ = true;
|
||||||
|
ok = true;
|
||||||
|
} else if (val_upper == "OFF") {
|
||||||
|
val_ = false;
|
||||||
|
ok = true;
|
||||||
|
}
|
||||||
|
if (ok) {
|
||||||
|
observable->set(val_);
|
||||||
|
}
|
||||||
|
return ok;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FieldParser_uint16(const String value, ObservableValue<int>* observable) {
|
||||||
|
float val_ = value.toInt();
|
||||||
|
observable->set(val_);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FieldParser_uint24(const String value, ObservableValue<int>* observable) {
|
||||||
|
float val_ = value.toInt();
|
||||||
|
observable->set(val_);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FieldParser_str(const String value, ObservableValue<String>* observable) {
|
||||||
|
observable->set(value);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
Parser::Parser() {
|
||||||
|
add_all_field_parsers();
|
||||||
|
current_state = &Parser::state_start;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Parser::add_all_field_parsers() {
|
||||||
|
field_parsers["V"] = [this](const String value) -> bool {
|
||||||
|
return FieldParser_mV(value, &(this->data.channel_1_battery_voltage));
|
||||||
|
};
|
||||||
|
field_parsers["V2"] = [this](const String value) -> bool {
|
||||||
|
return FieldParser_mV(value, &(this->data.channel_2_battery_voltage));
|
||||||
|
};
|
||||||
|
field_parsers["V3"] = [this](const String value) -> bool {
|
||||||
|
return FieldParser_mV(value, &(this->data.channel_3_battery_voltage));
|
||||||
|
};
|
||||||
|
field_parsers["VS"] = [this](const String value) -> bool {
|
||||||
|
return FieldParser_mV(value, &(this->data.auxiliary_voltage));
|
||||||
|
};
|
||||||
|
field_parsers["VM"] = [this](const String value) -> bool {
|
||||||
|
return FieldParser_mV(value, &(this->data.mid_point_voltage));
|
||||||
|
};
|
||||||
|
field_parsers["DM"] = [this](const String value) -> bool {
|
||||||
|
return FieldParser_permille(value, &(this->data.mid_point_deviation));
|
||||||
|
};
|
||||||
|
field_parsers["VPV"] = [this](const String value) -> bool {
|
||||||
|
return FieldParser_mV(value, &(this->data.panel_voltage));
|
||||||
|
};
|
||||||
|
field_parsers["PPV"] = [this](const String value) -> bool {
|
||||||
|
return FieldParser_W(value, &(this->data.panel_power));
|
||||||
|
};
|
||||||
|
field_parsers["I"] = [this](const String value) -> bool {
|
||||||
|
return FieldParser_mA(value, &(this->data.channel_1_battery_current));
|
||||||
|
};
|
||||||
|
field_parsers["I2"] = [this](const String value) -> bool {
|
||||||
|
return FieldParser_mA(value, &(this->data.channel_2_battery_current));
|
||||||
|
};
|
||||||
|
field_parsers["I3"] = [this](const String value) -> bool {
|
||||||
|
return FieldParser_mA(value, &(this->data.channel_3_battery_current));
|
||||||
|
};
|
||||||
|
field_parsers["IL"] = [this](const String value) -> bool {
|
||||||
|
return FieldParser_mA(value, &(this->data.load_current));
|
||||||
|
};
|
||||||
|
field_parsers["LOAD"] = [this](const String value) -> bool {
|
||||||
|
return FieldParser_ONOFF(value, &(this->data.load_output_state));
|
||||||
|
};
|
||||||
|
field_parsers["T"] = [this](const String value) -> bool {
|
||||||
|
return FieldParser_degC(value, &(this->data.battery_temperature));
|
||||||
|
};
|
||||||
|
field_parsers["P"] = [this](const String value) -> bool {
|
||||||
|
return FieldParser_W(value, &(this->data.instantaneous_power));
|
||||||
|
};
|
||||||
|
field_parsers["CE"] = [this](const String value) -> bool {
|
||||||
|
return FieldParser_mAh(value, &(this->data.consumed_energy));
|
||||||
|
};
|
||||||
|
field_parsers["SOC"] = [this](const String value) -> bool {
|
||||||
|
return FieldParser_permille(value, &(this->data.state_of_charge));
|
||||||
|
};
|
||||||
|
field_parsers["TTG"] = [this](const String value) -> bool {
|
||||||
|
return FieldParser_minutes(value, &(this->data.time_to_go));
|
||||||
|
};
|
||||||
|
field_parsers["Alarm"] = [this](const String value) -> bool {
|
||||||
|
return FieldParser_ONOFF(value, &(this->data.alarm_condition_active));
|
||||||
|
};
|
||||||
|
field_parsers["Relay"] = [this](const String value) -> bool {
|
||||||
|
return FieldParser_ONOFF(value, &(this->data.relay_state));
|
||||||
|
};
|
||||||
|
field_parsers["AR"] = [this](const String value) -> bool {
|
||||||
|
return FieldParser_int(value, &(this->data.alarm_reason));
|
||||||
|
};
|
||||||
|
field_parsers["OR"] = [this](const String value) -> bool {
|
||||||
|
return FieldParser_int(value, &(this->data.off_reason));
|
||||||
|
};
|
||||||
|
field_parsers["H1"] = [this](const String value) -> bool {
|
||||||
|
return FieldParser_mAh(value, &(this->data.depth_of_deepest_discharge));
|
||||||
|
};
|
||||||
|
field_parsers["H2"] = [this](const String value) -> bool {
|
||||||
|
return FieldParser_mAh(value, &(this->data.depth_of_last_discharge));
|
||||||
|
};
|
||||||
|
field_parsers["H3"] = [this](const String value) -> bool {
|
||||||
|
return FieldParser_mAh(value, &(this->data.depth_of_average_discharge));
|
||||||
|
};
|
||||||
|
field_parsers["H4"] = [this](const String value) -> bool {
|
||||||
|
return FieldParser_int(value, &(this->data.number_of_charge_cycles));
|
||||||
|
};
|
||||||
|
field_parsers["H5"] = [this](const String value) -> bool {
|
||||||
|
return FieldParser_int(value, &(this->data.number_of_full_discharges));
|
||||||
|
};
|
||||||
|
field_parsers["H6"] = [this](const String value) -> bool {
|
||||||
|
return FieldParser_mAh(value, &(this->data.cumulative_energy_drawn));
|
||||||
|
};
|
||||||
|
field_parsers["H7"] = [this](const String value) -> bool {
|
||||||
|
return FieldParser_mV(value, &(this->data.minimum_main_voltage));
|
||||||
|
};
|
||||||
|
field_parsers["H8"] = [this](const String value) -> bool {
|
||||||
|
return FieldParser_mV(value, &(this->data.maximum_main_voltage));
|
||||||
|
};
|
||||||
|
field_parsers["H9"] = [this](const String value) -> bool {
|
||||||
|
return FieldParser_seconds(value,
|
||||||
|
&(this->data.seconds_since_last_full_charge));
|
||||||
|
};
|
||||||
|
field_parsers["H10"] = [this](const String value) -> bool {
|
||||||
|
return FieldParser_int(value,
|
||||||
|
&(this->data.number_of_automatic_synchronizations));
|
||||||
|
};
|
||||||
|
field_parsers["H11"] = [this](const String value) -> bool {
|
||||||
|
return FieldParser_int(value,
|
||||||
|
&(this->data.number_of_low_main_voltage_alarms));
|
||||||
|
};
|
||||||
|
field_parsers["H12"] = [this](const String value) -> bool {
|
||||||
|
return FieldParser_int(value,
|
||||||
|
&(this->data.number_of_high_main_voltage_alarms));
|
||||||
|
};
|
||||||
|
field_parsers["H13"] = [this](const String value) -> bool {
|
||||||
|
return FieldParser_int(
|
||||||
|
value, &(this->data.number_of_low_auxiliary_voltage_alarms));
|
||||||
|
};
|
||||||
|
field_parsers["H14"] = [this](const String value) -> bool {
|
||||||
|
return FieldParser_int(
|
||||||
|
value, &(this->data.number_of_high_auxiliary_voltage_alarms));
|
||||||
|
};
|
||||||
|
field_parsers["H15"] = [this](const String value) -> bool {
|
||||||
|
return FieldParser_mV(value, &(this->data.minimum_auxiliary_voltage));
|
||||||
|
};
|
||||||
|
field_parsers["H16"] = [this](const String value) -> bool {
|
||||||
|
return FieldParser_mV(value, &(this->data.maximum_auxiliary_voltage));
|
||||||
|
};
|
||||||
|
field_parsers["H17"] = [this](const String value) -> bool {
|
||||||
|
return FieldParser_kWh(value, &(this->data.amount_of_discharged_energy));
|
||||||
|
};
|
||||||
|
field_parsers["H18"] = [this](const String value) -> bool {
|
||||||
|
return FieldParser_kWh(value, &(this->data.amount_of_charged_energy));
|
||||||
|
};
|
||||||
|
field_parsers["H19"] = [this](const String value) -> bool {
|
||||||
|
return FieldParser_kWh(value, &(this->data.yield_total));
|
||||||
|
};
|
||||||
|
field_parsers["H20"] = [this](const String value) -> bool {
|
||||||
|
return FieldParser_kWh(value, &(this->data.yield_today));
|
||||||
|
};
|
||||||
|
field_parsers["H21"] = [this](const String value) -> bool {
|
||||||
|
return FieldParser_W(value, &(this->data.maximum_power_today));
|
||||||
|
};
|
||||||
|
field_parsers["H22"] = [this](const String value) -> bool {
|
||||||
|
return FieldParser_kWh(value, &(this->data.yield_yesterday));
|
||||||
|
};
|
||||||
|
field_parsers["H23"] = [this](const String value) -> bool {
|
||||||
|
return FieldParser_W(value, &(this->data.maximum_power_yesterday));
|
||||||
|
};
|
||||||
|
field_parsers["ERR"] = [this](const String value) -> bool {
|
||||||
|
return FieldParser_int(value, &(this->data.error_code));
|
||||||
|
};
|
||||||
|
field_parsers["CS"] = [this](const String value) -> bool {
|
||||||
|
return FieldParser_int(value, &(this->data.state_of_operation));
|
||||||
|
};
|
||||||
|
field_parsers["BMV"] = [this](const String value) -> bool {
|
||||||
|
return FieldParser_str(value, &(this->data.model_description));
|
||||||
|
};
|
||||||
|
field_parsers["FW"] = [this](const String value) -> bool {
|
||||||
|
return FieldParser_uint16(value, &(this->data.firmware_version));
|
||||||
|
};
|
||||||
|
field_parsers["FWE"] = [this](const String value) -> bool {
|
||||||
|
return FieldParser_uint24(value, &(this->data.firmware_version_24));
|
||||||
|
};
|
||||||
|
field_parsers["PID"] = [this](const String value) -> bool {
|
||||||
|
return FieldParser_int(value, &(this->data.product_id));
|
||||||
|
};
|
||||||
|
field_parsers["SER#"] = [this](const String value) -> bool {
|
||||||
|
return FieldParser_int(value, &(this->data.serial_number));
|
||||||
|
};
|
||||||
|
field_parsers["HSDS"] = [this](const String value) -> bool {
|
||||||
|
return FieldParser_int(value, &(this->data.day_sequence_number));
|
||||||
|
};
|
||||||
|
field_parsers["MODE"] = [this](const String value) -> bool {
|
||||||
|
return FieldParser_int(value, &(this->data.device_mode));
|
||||||
|
};
|
||||||
|
field_parsers["AC_OUT_V"] = [this](const String value) -> bool {
|
||||||
|
return FieldParser_Vac(value, &(this->data.ac_output_voltage));
|
||||||
|
};
|
||||||
|
field_parsers["AC_OUT_I"] = [this](const String value) -> bool {
|
||||||
|
return FieldParser_Iac(value, &(this->data.ac_output_current));
|
||||||
|
};
|
||||||
|
field_parsers["AC_OUT_S"] = [this](const String value) -> bool {
|
||||||
|
return FieldParser_VA(value, &(this->data.ac_output_apparent_power));
|
||||||
|
};
|
||||||
|
field_parsers["WARN"] = [this](const String value) -> bool {
|
||||||
|
return FieldParser_int(value, &(this->data.warning_reason));
|
||||||
|
};
|
||||||
|
field_parsers["MPPT"] = [this](const String value) -> bool {
|
||||||
|
return FieldParser_int(value, &(this->data.tracker_operation_mode));
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
void Parser::handle(int c) {
|
||||||
|
checksum = (checksum + c) & 255;
|
||||||
|
(this->*(current_state))(c);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Parser::state_start(char c) {
|
||||||
|
cur_offset = 0;
|
||||||
|
current_state = &Parser::state_label;
|
||||||
|
(this->*(current_state))(c);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Parser::state_label(char c) {
|
||||||
|
if (c == '\t') {
|
||||||
|
// done reading the label
|
||||||
|
label_buffer[cur_offset++] = 0;
|
||||||
|
current_state = &Parser::state_tab;
|
||||||
|
(this->*(current_state))(c);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (cur_offset == label_max_length) {
|
||||||
|
// read too much already
|
||||||
|
current_state = &Parser::state_error;
|
||||||
|
(this->*(current_state))(c);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// process the next character of the label
|
||||||
|
label_buffer[cur_offset++] = c;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Parser::state_tab(char c) {
|
||||||
|
cur_offset = 0;
|
||||||
|
if (strcmp(label_buffer, "Checksum") == 0) {
|
||||||
|
// the checksum byte needs special treatment
|
||||||
|
current_state = &Parser::state_checksum_value;
|
||||||
|
} else {
|
||||||
|
current_state = &Parser::state_value;
|
||||||
|
}
|
||||||
|
// we already know we're at a tab, so we can ingest it by doing nothing
|
||||||
|
}
|
||||||
|
|
||||||
|
void Parser::state_value(char c) {
|
||||||
|
if (c == 0x0d) {
|
||||||
|
// beginning of newline -- we got a complete field
|
||||||
|
value_buffer[cur_offset++] = 0;
|
||||||
|
current_state = &Parser::state_received_field;
|
||||||
|
(this->*(current_state))(c);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (cur_offset == value_max_length) {
|
||||||
|
// read too much already
|
||||||
|
current_state = &Parser::state_error;
|
||||||
|
(this->*(current_state))(c);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// process the next character of the value
|
||||||
|
value_buffer[cur_offset++] = c;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Parser::state_checksum_value(char c) {
|
||||||
|
debugD("Checksum: %d (%d) vs %d", checksum, checksum % 256, c);
|
||||||
|
if (checksum == 0) {
|
||||||
|
// checksum matches, we've received the whole block
|
||||||
|
current_state = &Parser::state_received_block;
|
||||||
|
(this->*(current_state))(c);
|
||||||
|
} else {
|
||||||
|
// else the checksum didn't match
|
||||||
|
current_state = &Parser::state_newline;
|
||||||
|
}
|
||||||
|
checksum = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Parser::state_received_field(char c) {
|
||||||
|
debugD("Received field: %s, %s", label_buffer, value_buffer);
|
||||||
|
num_fields_in_block += 1;
|
||||||
|
if (num_fields_in_block > max_fields_in_block) {
|
||||||
|
current_state = &Parser::state_error;
|
||||||
|
(this->*(current_state))(c);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Field f = {.label = label_buffer, .value = value_buffer};
|
||||||
|
field_list.push_front(f);
|
||||||
|
current_state = &Parser::state_newline;
|
||||||
|
(this->*(current_state))(c);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Parser::state_received_block(char c) {
|
||||||
|
debugD("Received block");
|
||||||
|
for (auto const& field : field_list) {
|
||||||
|
// check if the field is present
|
||||||
|
if (field_parsers.find(field.label) == field_parsers.end()) {
|
||||||
|
// not found, ignore
|
||||||
|
debugD("Could not find parser for field %s", field.label.c_str());
|
||||||
|
} else {
|
||||||
|
// found
|
||||||
|
field_parsers[field.label](field.value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
field_list.clear();
|
||||||
|
num_fields_in_block = 0;
|
||||||
|
current_state = &Parser::state_newline;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Parser::state_newline(char c) {
|
||||||
|
if (c != 0xd && c != 0xa) {
|
||||||
|
cur_offset = 0;
|
||||||
|
current_state = &Parser::state_label;
|
||||||
|
(this->*(current_state))(c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Parser::state_error(char c) {
|
||||||
|
if (c == 0xd || c == 0xa) {
|
||||||
|
num_fields_in_block = 0;
|
||||||
|
field_list.clear();
|
||||||
|
current_state = &Parser::state_newline;
|
||||||
|
(this->*(current_state))(c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace VEDirect
|
146
src/system/vedirect_parser.h
Normal file
146
src/system/vedirect_parser.h
Normal file
@ -0,0 +1,146 @@
|
|||||||
|
#ifndef _VEDIRECT_PARSER_H_
|
||||||
|
#define _VEDIRECT_PARSER_H_
|
||||||
|
|
||||||
|
#include <functional>
|
||||||
|
#include <forward_list>
|
||||||
|
#include <map>
|
||||||
|
|
||||||
|
#include "Arduino.h"
|
||||||
|
|
||||||
|
#include "sensesp.h"
|
||||||
|
|
||||||
|
#include "system/observablevalue.h"
|
||||||
|
|
||||||
|
const int max_fields_in_block = 22;
|
||||||
|
const int label_max_length = 9;
|
||||||
|
const int value_max_length = 33;
|
||||||
|
typedef char label_str[label_max_length];
|
||||||
|
typedef char value_str[value_max_length];
|
||||||
|
|
||||||
|
namespace VEDirect {
|
||||||
|
|
||||||
|
struct Field {
|
||||||
|
String label;
|
||||||
|
String value;
|
||||||
|
};
|
||||||
|
|
||||||
|
bool FieldParser_mV(const String value, ObservableValue<float>* observable);
|
||||||
|
bool FieldParser_permille(const String value, ObservableValue<float>* observable);
|
||||||
|
bool FieldParser_W(const String value, ObservableValue<float>* observable);
|
||||||
|
bool FieldParser_mA(const String value, ObservableValue<float>* observable);
|
||||||
|
bool FieldParser_mAh(const String value, ObservableValue<float>* observable);
|
||||||
|
bool FieldParser_degC(const String value, ObservableValue<float>* observable);
|
||||||
|
bool FieldParser_minutes(const String value, ObservableValue<float>* observable);
|
||||||
|
bool FieldParser_seconds(const String value, ObservableValue<float>* observable);
|
||||||
|
bool FieldParser_kWh(const String value, ObservableValue<float>* observable);
|
||||||
|
bool FieldParser_Vac(const String value, ObservableValue<float>* observable);
|
||||||
|
bool FieldParser_Iac(const String value, ObservableValue<float>* observable);
|
||||||
|
bool FieldParser_VA(const String value, ObservableValue<float>* observable);
|
||||||
|
bool FieldParser_int(const String value, ObservableValue<int>* observable);
|
||||||
|
bool FieldParser_ONOFF(const String value, ObservableValue<bool>* observable);
|
||||||
|
bool FieldParser_uint16(const String value, ObservableValue<int>* observable);
|
||||||
|
bool FieldParser_uint24(const String value, ObservableValue<int>* observable);
|
||||||
|
bool FieldParser_str(const String value, ObservableValue<String>* observable);
|
||||||
|
|
||||||
|
struct VEDirectData {
|
||||||
|
ObservableValue<float> channel_1_battery_voltage;
|
||||||
|
ObservableValue<float> channel_2_battery_voltage;
|
||||||
|
ObservableValue<float> channel_3_battery_voltage;
|
||||||
|
ObservableValue<float> auxiliary_voltage;
|
||||||
|
ObservableValue<float> mid_point_voltage;
|
||||||
|
ObservableValue<float> mid_point_deviation;
|
||||||
|
ObservableValue<float> panel_voltage;
|
||||||
|
ObservableValue<float> panel_power;
|
||||||
|
ObservableValue<float> channel_1_battery_current;
|
||||||
|
ObservableValue<float> channel_2_battery_current;
|
||||||
|
ObservableValue<float> channel_3_battery_current;
|
||||||
|
ObservableValue<float> load_current;
|
||||||
|
ObservableValue<bool> load_output_state;
|
||||||
|
ObservableValue<float> battery_temperature;
|
||||||
|
ObservableValue<float> instantaneous_power;
|
||||||
|
ObservableValue<float> consumed_energy;
|
||||||
|
ObservableValue<float> state_of_charge;
|
||||||
|
ObservableValue<float> time_to_go;
|
||||||
|
ObservableValue<bool> alarm_condition_active;
|
||||||
|
ObservableValue<bool> relay_state;
|
||||||
|
ObservableValue<int> alarm_reason;
|
||||||
|
ObservableValue<int> off_reason;
|
||||||
|
ObservableValue<float> depth_of_deepest_discharge;
|
||||||
|
ObservableValue<float> depth_of_last_discharge;
|
||||||
|
ObservableValue<float> depth_of_average_discharge;
|
||||||
|
ObservableValue<int> number_of_charge_cycles;
|
||||||
|
ObservableValue<int> number_of_full_discharges;
|
||||||
|
ObservableValue<float> cumulative_energy_drawn;
|
||||||
|
ObservableValue<float> minimum_main_voltage;
|
||||||
|
ObservableValue<float> maximum_main_voltage;
|
||||||
|
ObservableValue<float> seconds_since_last_full_charge;
|
||||||
|
ObservableValue<int> number_of_automatic_synchronizations;
|
||||||
|
ObservableValue<int> number_of_low_main_voltage_alarms;
|
||||||
|
ObservableValue<int> number_of_high_main_voltage_alarms;
|
||||||
|
ObservableValue<int> number_of_low_auxiliary_voltage_alarms;
|
||||||
|
ObservableValue<int> number_of_high_auxiliary_voltage_alarms;
|
||||||
|
ObservableValue<float> minimum_auxiliary_voltage;
|
||||||
|
ObservableValue<float> maximum_auxiliary_voltage;
|
||||||
|
ObservableValue<float> amount_of_discharged_energy;
|
||||||
|
ObservableValue<float> amount_of_charged_energy;
|
||||||
|
ObservableValue<float> yield_total;
|
||||||
|
ObservableValue<float> yield_today;
|
||||||
|
ObservableValue<float> maximum_power_today;
|
||||||
|
ObservableValue<float> yield_yesterday;
|
||||||
|
ObservableValue<float> maximum_power_yesterday;
|
||||||
|
ObservableValue<int> error_code;
|
||||||
|
ObservableValue<int> state_of_operation;
|
||||||
|
ObservableValue<String> model_description;
|
||||||
|
ObservableValue<int> firmware_version;
|
||||||
|
ObservableValue<int> firmware_version_24;
|
||||||
|
ObservableValue<int> product_id;
|
||||||
|
ObservableValue<int> serial_number;
|
||||||
|
ObservableValue<int> day_sequence_number;
|
||||||
|
ObservableValue<int> device_mode;
|
||||||
|
ObservableValue<float> ac_output_voltage;
|
||||||
|
ObservableValue<float> ac_output_current;
|
||||||
|
ObservableValue<float> ac_output_apparent_power;
|
||||||
|
ObservableValue<int> warning_reason;
|
||||||
|
ObservableValue<int> tracker_operation_mode;
|
||||||
|
};
|
||||||
|
|
||||||
|
class Parser {
|
||||||
|
public:
|
||||||
|
Parser();
|
||||||
|
void handle(int c);
|
||||||
|
VEDirectData data;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void (Parser::*current_state)(char);
|
||||||
|
void state_start(char c);
|
||||||
|
void state_label(char c);
|
||||||
|
void state_tab(char c);
|
||||||
|
void state_value(char c);
|
||||||
|
void state_checksum_value(char c);
|
||||||
|
void state_received_field(char c);
|
||||||
|
void state_received_block(char c);
|
||||||
|
void state_newline(char c);
|
||||||
|
void state_error(char c);
|
||||||
|
|
||||||
|
label_str label_buffer;
|
||||||
|
value_str value_buffer;
|
||||||
|
// current offset of the buffer
|
||||||
|
int cur_offset;
|
||||||
|
|
||||||
|
// number of fields received in the current block so far
|
||||||
|
int num_fields_in_block = 0;
|
||||||
|
// checksum
|
||||||
|
unsigned int checksum = 0;
|
||||||
|
std::map<String, std::function<bool(const String)>> field_parsers;
|
||||||
|
void add_all_field_parsers();
|
||||||
|
|
||||||
|
|
||||||
|
std::forward_list<Field> field_list;
|
||||||
|
|
||||||
|
void parse_fields();
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
Loading…
x
Reference in New Issue
Block a user