diff --git a/include/BatteryStats.h b/include/BatteryStats.h index 5c5f06b1..ca669aa8 100644 --- a/include/BatteryStats.h +++ b/include/BatteryStats.h @@ -147,7 +147,7 @@ class VictronSmartShuntStats : public BatteryStats { void getLiveViewData(JsonVariant& root) const final; void mqttPublish() const final; - void updateFrom(VeDirectShuntController::veShuntStruct const& shuntData); + void updateFrom(VeDirectShuntController::data_t const& shuntData); private: float _current; diff --git a/include/MqttHandleVedirect.h b/include/MqttHandleVedirect.h index c420d088..897dc00a 100644 --- a/include/MqttHandleVedirect.h +++ b/include/MqttHandleVedirect.h @@ -21,7 +21,7 @@ public: void forceUpdate(); private: void loop(); - std::map _kvFrames; + std::map _kvFrames; Task _loopTask; @@ -34,7 +34,7 @@ private: bool _PublishFull; void publish_mppt_data(const VeDirectMpptController::spData_t &spMpptData, - VeDirectMpptController::veMpptStruct &frame) const; + const VeDirectMpptController::data_t &frame) const; }; extern MqttHandleVedirectClass MqttHandleVedirect; diff --git a/lib/VeDirectFrameHandler/VeDirectData.cpp b/lib/VeDirectFrameHandler/VeDirectData.cpp new file mode 100644 index 00000000..20c2cdf6 --- /dev/null +++ b/lib/VeDirectFrameHandler/VeDirectData.cpp @@ -0,0 +1,224 @@ +#include "VeDirectData.h" + +template +static frozen::string const& getAsString(frozen::map const& values, T val) +{ + auto pos = values.find(val); + if (pos == values.end()) { + static constexpr frozen::string dummy("???"); + return dummy; + } + return pos->second; +} + +/* + * This function returns the product id (PID) as readable text. + */ +frozen::string const& veStruct::getPidAsString() const +{ + /** + * this map is rendered from [1], which is more recent than [2]. Phoenix + * inverters are not included in the map. unfortunately, the documents do + * not fully align. PID 0xA07F is only present in [1]. PIDs 0xA048, 0xA110, + * and 0xA111 are only present in [2]. PIDs 0xA06D and 0xA078 are rev3 in + * [1] but rev2 in [2]. + * + * [1] https://www.victronenergy.com/upload/documents/VE.Direct-Protocol-3.33.pdf + * [2] https://www.victronenergy.com/upload/documents/BlueSolar-HEX-protocol.pdf + */ + static constexpr frozen::map values = { + { 0x0203, "BMV-700" }, + { 0x0204, "BMV-702" }, + { 0x0205, "BMV-700H" }, + { 0x0300, "BlueSolar MPPT 70|15" }, + { 0xA040, "BlueSolar MPPT 75|50" }, + { 0xA041, "BlueSolar MPPT 150|35" }, + { 0xA042, "BlueSolar MPPT 75|15" }, + { 0xA043, "BlueSolar MPPT 100|15" }, + { 0xA044, "BlueSolar MPPT 100|30" }, + { 0xA045, "BlueSolar MPPT 100|50" }, + { 0xA046, "BlueSolar MPPT 150|70" }, + { 0xA047, "BlueSolar MPPT 150|100" }, + { 0xA048, "BlueSolar MPPT 75|50 rev2" }, + { 0xA049, "BlueSolar MPPT 100|50 rev2" }, + { 0xA04A, "BlueSolar MPPT 100|30 rev2" }, + { 0xA04B, "BlueSolar MPPT 150|35 rev2" }, + { 0xA04C, "BlueSolar MPPT 75|10" }, + { 0xA04D, "BlueSolar MPPT 150|45" }, + { 0xA04E, "BlueSolar MPPT 150|60" }, + { 0xA04F, "BlueSolar MPPT 150|85" }, + { 0xA050, "SmartSolar MPPT 250|100" }, + { 0xA051, "SmartSolar MPPT 150|100" }, + { 0xA052, "SmartSolar MPPT 150|85" }, + { 0xA053, "SmartSolar MPPT 75|15" }, + { 0xA054, "SmartSolar MPPT 75|10" }, + { 0xA055, "SmartSolar MPPT 100|15" }, + { 0xA056, "SmartSolar MPPT 100|30" }, + { 0xA057, "SmartSolar MPPT 100|50" }, + { 0xA058, "SmartSolar MPPT 150|35" }, + { 0xA059, "SmartSolar MPPT 150|100 rev2" }, + { 0xA05A, "SmartSolar MPPT 150|85 rev2" }, + { 0xA05B, "SmartSolar MPPT 250|70" }, + { 0xA05C, "SmartSolar MPPT 250|85" }, + { 0xA05D, "SmartSolar MPPT 250|60" }, + { 0xA05E, "SmartSolar MPPT 250|45" }, + { 0xA05F, "SmartSolar MPPT 100|20" }, + { 0xA060, "SmartSolar MPPT 100|20 48V" }, + { 0xA061, "SmartSolar MPPT 150|45" }, + { 0xA062, "SmartSolar MPPT 150|60" }, + { 0xA063, "SmartSolar MPPT 150|70" }, + { 0xA064, "SmartSolar MPPT 250|85 rev2" }, + { 0xA065, "SmartSolar MPPT 250|100 rev2" }, + { 0xA066, "BlueSolar MPPT 100|20" }, + { 0xA067, "BlueSolar MPPT 100|20 48V" }, + { 0xA068, "SmartSolar MPPT 250|60 rev2" }, + { 0xA069, "SmartSolar MPPT 250|70 rev2" }, + { 0xA06A, "SmartSolar MPPT 150|45 rev2" }, + { 0xA06B, "SmartSolar MPPT 150|60 rev2" }, + { 0xA06C, "SmartSolar MPPT 150|70 rev2" }, + { 0xA06D, "SmartSolar MPPT 150|85 rev3" }, + { 0xA06E, "SmartSolar MPPT 150|100 rev3" }, + { 0xA06F, "BlueSolar MPPT 150|45 rev2" }, + { 0xA070, "BlueSolar MPPT 150|60 rev2" }, + { 0xA071, "BlueSolar MPPT 150|70 rev2" }, + { 0xA072, "BlueSolar MPPT 150|45 rev3" }, + { 0xA073, "SmartSolar MPPT 150|45 rev3" }, + { 0xA074, "SmartSolar MPPT 75|10 rev2" }, + { 0xA075, "SmartSolar MPPT 75|15 rev2" }, + { 0xA076, "BlueSolar MPPT 100|30 rev3" }, + { 0xA077, "BlueSolar MPPT 100|50 rev3" }, + { 0xA078, "BlueSolar MPPT 150|35 rev3" }, + { 0xA079, "BlueSolar MPPT 75|10 rev2" }, + { 0xA07A, "BlueSolar MPPT 75|15 rev2" }, + { 0xA07B, "BlueSolar MPPT 100|15 rev2" }, + { 0xA07C, "BlueSolar MPPT 75|10 rev3" }, + { 0xA07D, "BlueSolar MPPT 75|15 rev3" }, + { 0xA07E, "SmartSolar MPPT 100|30 12V" }, + { 0xA07F, "All-In-1 SmartSolar MPPT 75|15 12V" }, + { 0xA102, "SmartSolar MPPT VE.Can 150|70" }, + { 0xA103, "SmartSolar MPPT VE.Can 150|45" }, + { 0xA104, "SmartSolar MPPT VE.Can 150|60" }, + { 0xA105, "SmartSolar MPPT VE.Can 150|85" }, + { 0xA106, "SmartSolar MPPT VE.Can 150|100" }, + { 0xA107, "SmartSolar MPPT VE.Can 250|45" }, + { 0xA108, "SmartSolar MPPT VE.Can 250|60" }, + { 0xA109, "SmartSolar MPPT VE.Can 250|70" }, + { 0xA10A, "SmartSolar MPPT VE.Can 250|85" }, + { 0xA10B, "SmartSolar MPPT VE.Can 250|100" }, + { 0xA10C, "SmartSolar MPPT VE.Can 150|70 rev2" }, + { 0xA10D, "SmartSolar MPPT VE.Can 150|85 rev2" }, + { 0xA10E, "SmartSolar MPPT VE.Can 150|100 rev2" }, + { 0xA10F, "BlueSolar MPPT VE.Can 150|100" }, + { 0xA110, "SmartSolar MPPT RS 450|100" }, + { 0xA111, "SmartSolar MPPT RS 450|200" }, + { 0xA112, "BlueSolar MPPT VE.Can 250|70" }, + { 0xA113, "BlueSolar MPPT VE.Can 250|100" }, + { 0xA114, "SmartSolar MPPT VE.Can 250|70 rev2" }, + { 0xA115, "SmartSolar MPPT VE.Can 250|100 rev2" }, + { 0xA116, "SmartSolar MPPT VE.Can 250|85 rev2" }, + { 0xA117, "BlueSolar MPPT VE.Can 150|100 rev2" }, + { 0xA340, "Phoenix Smart IP43 Charger 12|50 (1+1)" }, + { 0xA341, "Phoenix Smart IP43 Charger 12|50 (3)" }, + { 0xA342, "Phoenix Smart IP43 Charger 24|25 (1+1)" }, + { 0xA343, "Phoenix Smart IP43 Charger 24|25 (3)" }, + { 0xA344, "Phoenix Smart IP43 Charger 12|30 (1+1)" }, + { 0xA345, "Phoenix Smart IP43 Charger 12|30 (3)" }, + { 0xA346, "Phoenix Smart IP43 Charger 24|16 (1+1)" }, + { 0xA347, "Phoenix Smart IP43 Charger 24|16 (3)" }, + { 0xA381, "BMV-712 Smart" }, + { 0xA382, "BMV-710H Smart" }, + { 0xA383, "BMV-712 Smart Rev2" }, + { 0xA389, "SmartShunt 500A/50mV" }, + { 0xA38A, "SmartShunt 1000A/50mV" }, + { 0xA38B, "SmartShunt 2000A/50mV" }, + { 0xA3F0, "Smart BuckBoost 12V/12V-50A" }, + }; + + return getAsString(values, PID); +} + +/* + * This function returns the state of operations (CS) as readable text. + */ +frozen::string const& veMpptStruct::getCsAsString() const +{ + static constexpr frozen::map values = { + { 0, "OFF" }, + { 2, "Fault" }, + { 3, "Bulk" }, + { 4, "Absorbtion" }, + { 5, "Float" }, + { 7, "Equalize (manual)" }, + { 245, "Starting-up" }, + { 247, "Auto equalize / Recondition" }, + { 252, "External Control" } + }; + + return getAsString(values, CS); +} + +/* + * This function returns the state of MPPT (MPPT) as readable text. + */ +frozen::string const& veMpptStruct::getMpptAsString() const +{ + static constexpr frozen::map values = { + { 0, "OFF" }, + { 1, "Voltage or current limited" }, + { 2, "MPP Tracker active" } + }; + + return getAsString(values, MPPT); +} + +/* + * This function returns error state (ERR) as readable text. + */ +frozen::string const& veMpptStruct::getErrAsString() const +{ + static constexpr frozen::map values = { + { 0, "No error" }, + { 2, "Battery voltage too high" }, + { 17, "Charger temperature too high" }, + { 18, "Charger over current" }, + { 19, "Charger current reversed" }, + { 20, "Bulk time limit exceeded" }, + { 21, "Current sensor issue(sensor bias/sensor broken)" }, + { 26, "Terminals overheated" }, + { 28, "Converter issue (dual converter models only)" }, + { 33, "Input voltage too high (solar panel)" }, + { 34, "Input current too high (solar panel)" }, + { 38, "Input shutdown (due to excessive battery voltage)" }, + { 39, "Input shutdown (due to current flow during off mode)" }, + { 40, "Input" }, + { 65, "Lost communication with one of devices" }, + { 67, "Synchronisedcharging device configuration issue" }, + { 68, "BMS connection lost" }, + { 116, "Factory calibration data lost" }, + { 117, "Invalid/incompatible firmware" }, + { 118, "User settings invalid" } + }; + + return getAsString(values, ERR); +} + +/* + * This function returns the off reason (OR) as readable text. + */ +frozen::string const& veMpptStruct::getOrAsString() const +{ + static constexpr frozen::map values = { + { 0x00000000, "Not off" }, + { 0x00000001, "No input power" }, + { 0x00000002, "Switched off (power switch)" }, + { 0x00000004, "Switched off (device moderegister)" }, + { 0x00000008, "Remote input" }, + { 0x00000010, "Protection active" }, + { 0x00000020, "Paygo" }, + { 0x00000040, "BMS" }, + { 0x00000080, "Engine shutdown detection" }, + { 0x00000100, "Analysing input voltage" } + }; + + return getAsString(values, OR); +} diff --git a/lib/VeDirectFrameHandler/VeDirectData.h b/lib/VeDirectFrameHandler/VeDirectData.h new file mode 100644 index 00000000..0c43b555 --- /dev/null +++ b/lib/VeDirectFrameHandler/VeDirectData.h @@ -0,0 +1,70 @@ +#pragma once + +#include +#include + +#define VE_MAX_VALUE_LEN 33 // VE.Direct Protocol: max value size is 33 including /0 +#define VE_MAX_HEX_LEN 100 // Maximum size of hex frame - max payload 34 byte (=68 char) + safe buffer + +typedef struct { + uint16_t PID = 0; // product id + char SER[VE_MAX_VALUE_LEN]; // serial number + char FW[VE_MAX_VALUE_LEN]; // firmware release number + double V = 0; // battery voltage in V + double I = 0; // battery current in A + double E = 0; // efficiency in percent (calculated, moving average) + + frozen::string const& getPidAsString() const; // product ID as string +} veStruct; + +struct veMpptStruct : veStruct { + uint8_t MPPT; // state of MPP tracker + int32_t PPV; // panel power in W + int32_t P; // battery output power in W (calculated) + double VPV; // panel voltage in V + double IPV; // panel current in A (calculated) + bool LOAD; // virtual load output state (on if battery voltage reaches upper limit, off if battery reaches lower limit) + uint8_t CS; // current state of operation e.g. OFF or Bulk + uint8_t ERR; // error code + uint32_t OR; // off reason + uint32_t HSDS; // day sequence number 1...365 + double H19; // yield total kWh + double H20; // yield today kWh + int32_t H21; // maximum power today W + double H22; // yield yesterday kWh + int32_t H23; // maximum power yesterday W + + frozen::string const& getMpptAsString() const; // state of mppt as string + frozen::string const& getCsAsString() const; // current state as string + frozen::string const& getErrAsString() const; // error state as string + frozen::string const& getOrAsString() const; // off reason as string +}; + +struct veShuntStruct : veStruct { + int32_t T; // Battery temperature + bool tempPresent; // Battery temperature sensor is attached to the shunt + int32_t P; // Instantaneous power + int32_t CE; // Consumed Amp Hours + int32_t SOC; // State-of-charge + uint32_t TTG; // Time-to-go + bool ALARM; // Alarm condition active + uint32_t AR; // Alarm Reason + int32_t H1; // Depth of the deepest discharge + int32_t H2; // Depth of the last discharge + int32_t H3; // Depth of the average discharge + int32_t H4; // Number of charge cycles + int32_t H5; // Number of full discharges + int32_t H6; // Cumulative Amp Hours drawn + int32_t H7; // Minimum main (battery) voltage + int32_t H8; // Maximum main (battery) voltage + int32_t H9; // Number of seconds since last full charge + int32_t H10; // Number of automatic synchronizations + int32_t H11; // Number of low main voltage alarms + int32_t H12; // Number of high main voltage alarms + int32_t H13; // Number of low auxiliary voltage alarms + int32_t H14; // Number of high auxiliary voltage alarms + int32_t H15; // Minimum auxiliary (battery) voltage + int32_t H16; // Maximum auxiliary (battery) voltage + int32_t H17; // Amount of discharged energy + int32_t H18; // Amount of charged energy +}; diff --git a/lib/VeDirectFrameHandler/VeDirectFrameHandler.cpp b/lib/VeDirectFrameHandler/VeDirectFrameHandler.cpp index 8fb8800e..a308f7f9 100644 --- a/lib/VeDirectFrameHandler/VeDirectFrameHandler.cpp +++ b/lib/VeDirectFrameHandler/VeDirectFrameHandler.cpp @@ -58,7 +58,8 @@ class Silent : public Print { static Silent MessageOutputDummy; -VeDirectFrameHandler::VeDirectFrameHandler() : +template +VeDirectFrameHandler::VeDirectFrameHandler() : _msgOut(&MessageOutputDummy), _lastUpdate(0), _state(IDLE), @@ -72,7 +73,8 @@ VeDirectFrameHandler::VeDirectFrameHandler() : { } -void VeDirectFrameHandler::init(char const* who, int8_t rx, int8_t tx, Print* msgOut, bool verboseLogging, uint16_t hwSerialPort) +template +void VeDirectFrameHandler::init(char const* who, int8_t rx, int8_t tx, Print* msgOut, bool verboseLogging, uint16_t hwSerialPort) { _vedirectSerial = std::make_unique(hwSerialPort); _vedirectSerial->begin(19200, SERIAL_8N1, rx, tx); @@ -84,7 +86,8 @@ void VeDirectFrameHandler::init(char const* who, int8_t rx, int8_t tx, Print* ms if (_verboseLogging) { _msgOut->printf("%s init complete\r\n", _logId); } } -void VeDirectFrameHandler::dumpDebugBuffer() { +template +void VeDirectFrameHandler::dumpDebugBuffer() { _msgOut->printf("%s serial input (%d Bytes):", _logId, _debugIn); for (int i = 0; i < _debugIn; ++i) { if (i % 16 == 0) { @@ -96,7 +99,16 @@ void VeDirectFrameHandler::dumpDebugBuffer() { _debugIn = 0; } -void VeDirectFrameHandler::loop() +template +void VeDirectFrameHandler::reset() +{ + _checksum = 0; + _state = IDLE; + _textData.clear(); +} + +template +void VeDirectFrameHandler::loop() { while ( _vedirectSerial->available()) { rxData(_vedirectSerial->read()); @@ -109,8 +121,7 @@ void VeDirectFrameHandler::loop() if (IDLE != _state && _lastByteMillis + 500 < millis()) { _msgOut->printf("%s Resetting state machine (was %d) after timeout\r\n", _logId, _state); if (_verboseLogging) { dumpDebugBuffer(); } - _checksum = 0; - _state = IDLE; + reset(); } } @@ -119,7 +130,8 @@ void VeDirectFrameHandler::loop() * This function is called by loop() which passes a byte of serial data * Based on Victron's example code. But using String and Map instead of pointer and arrays */ -void VeDirectFrameHandler::rxData(uint8_t inbyte) +template +void VeDirectFrameHandler::rxData(uint8_t inbyte) { if (_verboseLogging) { _debugBuffer[_debugIn] = inbyte; @@ -186,7 +198,7 @@ void VeDirectFrameHandler::rxData(uint8_t inbyte) case '\n': if ( _textPointer < (_value + sizeof(_value)) ) { *_textPointer = 0; // make zero ended - textRxEvent(_name, _value); + _textData.push_back({_name, _value}); } _state = RECORD_BEGIN; break; @@ -201,14 +213,18 @@ void VeDirectFrameHandler::rxData(uint8_t inbyte) break; case CHECKSUM: { - bool valid = _checksum == 0; - if (!valid) { - _msgOut->printf("%s checksum 0x%02x != 0, invalid frame\r\n", _logId, _checksum); - } if (_verboseLogging) { dumpDebugBuffer(); } - _checksum = 0; - _state = IDLE; - if (valid) { frameValidEvent(); } + if (_checksum == 0) { + for (auto const& event : _textData) { + processTextData(event.first, event.second); + } + _lastUpdate = millis(); + frameValidEvent(); + } + else { + _msgOut->printf("%s checksum 0x%02x != 0x00, invalid frame\r\n", _logId, _checksum); + } + reset(); break; } case RECORD_HEX: @@ -218,41 +234,44 @@ void VeDirectFrameHandler::rxData(uint8_t inbyte) } /* - * textRxEvent * This function is called every time a new name/value is successfully parsed. It writes the values to the temporary buffer. */ -bool VeDirectFrameHandler::textRxEvent(char* name, char* value, veStruct& frame) { +template +void VeDirectFrameHandler::processTextData(std::string const& name, std::string const& value) { if (_verboseLogging) { - _msgOut->printf("%s Text Event %s: Value: %s\r\n", - _logId, name, value ); + _msgOut->printf("%s Text Data '%s' = '%s'\r\n", + _logId, name.c_str(), value.c_str()); } - if (strcmp(name, "PID") == 0) { - frame.PID = strtol(value, nullptr, 0); - return true; + if (processTextDataDerived(name, value)) { return; } + + if (name == "PID") { + _tmpFrame.PID = strtol(value.c_str(), nullptr, 0); + return; } - if (strcmp(name, "SER") == 0) { - strcpy(frame.SER, value); - return true; + if (name == "SER") { + strcpy(_tmpFrame.SER, value.c_str()); + return; } - if (strcmp(name, "FW") == 0) { - strcpy(frame.FW, value); - return true; + if (name == "FW") { + strcpy(_tmpFrame.FW, value.c_str()); + return; } - if (strcmp(name, "V") == 0) { - frame.V = round(atof(value) / 10.0) / 100.0; - return true; + if (name == "V") { + _tmpFrame.V = round(atof(value.c_str()) / 10.0) / 100.0; + return; } - if (strcmp(name, "I") == 0) { - frame.I = round(atof(value) / 10.0) / 100.0; - return true; + if (name == "I") { + _tmpFrame.I = round(atof(value.c_str()) / 10.0) / 100.0; + return; } - return false; + _msgOut->printf("%s Unknown text data '%s' (value '%s')\r\n", + _logId, name.c_str(), value.c_str()); } @@ -261,7 +280,9 @@ bool VeDirectFrameHandler::textRxEvent(char* name, char* value, veStruct& frame) * hexRxEvent * This function records hex answers or async messages */ -int VeDirectFrameHandler::hexRxEvent(uint8_t inbyte) { +template +int VeDirectFrameHandler::hexRxEvent(uint8_t inbyte) +{ int ret=RECORD_HEX; // default - continue recording until end of frame switch (inbyte) { @@ -282,138 +303,14 @@ int VeDirectFrameHandler::hexRxEvent(uint8_t inbyte) { return ret; } -bool VeDirectFrameHandler::isDataValid(veStruct const& frame) const { - return strlen(frame.SER) > 0 && _lastUpdate > 0 && (millis() - _lastUpdate) < (10 * 1000); +template +bool VeDirectFrameHandler::isDataValid() const +{ + return strlen(_tmpFrame.SER) > 0 && _lastUpdate > 0 && (millis() - _lastUpdate) < (10 * 1000); } -uint32_t VeDirectFrameHandler::getLastUpdate() const +template +uint32_t VeDirectFrameHandler::getLastUpdate() const { return _lastUpdate; } - -/* - * getPidAsString - * This function returns the product id (PID) as readable text. - */ -frozen::string const& VeDirectFrameHandler::veStruct::getPidAsString() const -{ - /** - * this map is rendered from [1], which is more recent than [2]. Phoenix - * inverters are not included in the map. unfortunately, the documents do - * not fully align. PID 0xA07F is only present in [1]. PIDs 0xA048, 0xA110, - * and 0xA111 are only present in [2]. PIDs 0xA06D and 0xA078 are rev3 in - * [1] but rev2 in [2]. - * - * [1] https://www.victronenergy.com/upload/documents/VE.Direct-Protocol-3.33.pdf - * [2] https://www.victronenergy.com/upload/documents/BlueSolar-HEX-protocol.pdf - */ - static constexpr frozen::map values = { - { 0x0203, "BMV-700" }, - { 0x0204, "BMV-702" }, - { 0x0205, "BMV-700H" }, - { 0x0300, "BlueSolar MPPT 70|15" }, - { 0xA040, "BlueSolar MPPT 75|50" }, - { 0xA041, "BlueSolar MPPT 150|35" }, - { 0xA042, "BlueSolar MPPT 75|15" }, - { 0xA043, "BlueSolar MPPT 100|15" }, - { 0xA044, "BlueSolar MPPT 100|30" }, - { 0xA045, "BlueSolar MPPT 100|50" }, - { 0xA046, "BlueSolar MPPT 150|70" }, - { 0xA047, "BlueSolar MPPT 150|100" }, - { 0xA048, "BlueSolar MPPT 75|50 rev2" }, - { 0xA049, "BlueSolar MPPT 100|50 rev2" }, - { 0xA04A, "BlueSolar MPPT 100|30 rev2" }, - { 0xA04B, "BlueSolar MPPT 150|35 rev2" }, - { 0xA04C, "BlueSolar MPPT 75|10" }, - { 0xA04D, "BlueSolar MPPT 150|45" }, - { 0xA04E, "BlueSolar MPPT 150|60" }, - { 0xA04F, "BlueSolar MPPT 150|85" }, - { 0xA050, "SmartSolar MPPT 250|100" }, - { 0xA051, "SmartSolar MPPT 150|100" }, - { 0xA052, "SmartSolar MPPT 150|85" }, - { 0xA053, "SmartSolar MPPT 75|15" }, - { 0xA054, "SmartSolar MPPT 75|10" }, - { 0xA055, "SmartSolar MPPT 100|15" }, - { 0xA056, "SmartSolar MPPT 100|30" }, - { 0xA057, "SmartSolar MPPT 100|50" }, - { 0xA058, "SmartSolar MPPT 150|35" }, - { 0xA059, "SmartSolar MPPT 150|100 rev2" }, - { 0xA05A, "SmartSolar MPPT 150|85 rev2" }, - { 0xA05B, "SmartSolar MPPT 250|70" }, - { 0xA05C, "SmartSolar MPPT 250|85" }, - { 0xA05D, "SmartSolar MPPT 250|60" }, - { 0xA05E, "SmartSolar MPPT 250|45" }, - { 0xA05F, "SmartSolar MPPT 100|20" }, - { 0xA060, "SmartSolar MPPT 100|20 48V" }, - { 0xA061, "SmartSolar MPPT 150|45" }, - { 0xA062, "SmartSolar MPPT 150|60" }, - { 0xA063, "SmartSolar MPPT 150|70" }, - { 0xA064, "SmartSolar MPPT 250|85 rev2" }, - { 0xA065, "SmartSolar MPPT 250|100 rev2" }, - { 0xA066, "BlueSolar MPPT 100|20" }, - { 0xA067, "BlueSolar MPPT 100|20 48V" }, - { 0xA068, "SmartSolar MPPT 250|60 rev2" }, - { 0xA069, "SmartSolar MPPT 250|70 rev2" }, - { 0xA06A, "SmartSolar MPPT 150|45 rev2" }, - { 0xA06B, "SmartSolar MPPT 150|60 rev2" }, - { 0xA06C, "SmartSolar MPPT 150|70 rev2" }, - { 0xA06D, "SmartSolar MPPT 150|85 rev3" }, - { 0xA06E, "SmartSolar MPPT 150|100 rev3" }, - { 0xA06F, "BlueSolar MPPT 150|45 rev2" }, - { 0xA070, "BlueSolar MPPT 150|60 rev2" }, - { 0xA071, "BlueSolar MPPT 150|70 rev2" }, - { 0xA072, "BlueSolar MPPT 150|45 rev3" }, - { 0xA073, "SmartSolar MPPT 150|45 rev3" }, - { 0xA074, "SmartSolar MPPT 75|10 rev2" }, - { 0xA075, "SmartSolar MPPT 75|15 rev2" }, - { 0xA076, "BlueSolar MPPT 100|30 rev3" }, - { 0xA077, "BlueSolar MPPT 100|50 rev3" }, - { 0xA078, "BlueSolar MPPT 150|35 rev3" }, - { 0xA079, "BlueSolar MPPT 75|10 rev2" }, - { 0xA07A, "BlueSolar MPPT 75|15 rev2" }, - { 0xA07B, "BlueSolar MPPT 100|15 rev2" }, - { 0xA07C, "BlueSolar MPPT 75|10 rev3" }, - { 0xA07D, "BlueSolar MPPT 75|15 rev3" }, - { 0xA07E, "SmartSolar MPPT 100|30 12V" }, - { 0xA07F, "All-In-1 SmartSolar MPPT 75|15 12V" }, - { 0xA102, "SmartSolar MPPT VE.Can 150|70" }, - { 0xA103, "SmartSolar MPPT VE.Can 150|45" }, - { 0xA104, "SmartSolar MPPT VE.Can 150|60" }, - { 0xA105, "SmartSolar MPPT VE.Can 150|85" }, - { 0xA106, "SmartSolar MPPT VE.Can 150|100" }, - { 0xA107, "SmartSolar MPPT VE.Can 250|45" }, - { 0xA108, "SmartSolar MPPT VE.Can 250|60" }, - { 0xA109, "SmartSolar MPPT VE.Can 250|70" }, - { 0xA10A, "SmartSolar MPPT VE.Can 250|85" }, - { 0xA10B, "SmartSolar MPPT VE.Can 250|100" }, - { 0xA10C, "SmartSolar MPPT VE.Can 150|70 rev2" }, - { 0xA10D, "SmartSolar MPPT VE.Can 150|85 rev2" }, - { 0xA10E, "SmartSolar MPPT VE.Can 150|100 rev2" }, - { 0xA10F, "BlueSolar MPPT VE.Can 150|100" }, - { 0xA110, "SmartSolar MPPT RS 450|100" }, - { 0xA111, "SmartSolar MPPT RS 450|200" }, - { 0xA112, "BlueSolar MPPT VE.Can 250|70" }, - { 0xA113, "BlueSolar MPPT VE.Can 250|100" }, - { 0xA114, "SmartSolar MPPT VE.Can 250|70 rev2" }, - { 0xA115, "SmartSolar MPPT VE.Can 250|100 rev2" }, - { 0xA116, "SmartSolar MPPT VE.Can 250|85 rev2" }, - { 0xA117, "BlueSolar MPPT VE.Can 150|100 rev2" }, - { 0xA340, "Phoenix Smart IP43 Charger 12|50 (1+1)" }, - { 0xA341, "Phoenix Smart IP43 Charger 12|50 (3)" }, - { 0xA342, "Phoenix Smart IP43 Charger 24|25 (1+1)" }, - { 0xA343, "Phoenix Smart IP43 Charger 24|25 (3)" }, - { 0xA344, "Phoenix Smart IP43 Charger 12|30 (1+1)" }, - { 0xA345, "Phoenix Smart IP43 Charger 12|30 (3)" }, - { 0xA346, "Phoenix Smart IP43 Charger 24|16 (1+1)" }, - { 0xA347, "Phoenix Smart IP43 Charger 24|16 (3)" }, - { 0xA381, "BMV-712 Smart" }, - { 0xA382, "BMV-710H Smart" }, - { 0xA383, "BMV-712 Smart Rev2" }, - { 0xA389, "SmartShunt 500A/50mV" }, - { 0xA38A, "SmartShunt 1000A/50mV" }, - { 0xA38B, "SmartShunt 2000A/50mV" }, - { 0xA3F0, "Smart BuckBoost 12V/12V-50A" }, - }; - - return getAsString(values, PID); -} diff --git a/lib/VeDirectFrameHandler/VeDirectFrameHandler.h b/lib/VeDirectFrameHandler/VeDirectFrameHandler.h index a3947f3d..aa8857a4 100644 --- a/lib/VeDirectFrameHandler/VeDirectFrameHandler.h +++ b/lib/VeDirectFrameHandler/VeDirectFrameHandler.h @@ -13,17 +13,17 @@ #include #include -#include -#include #include +#include +#include +#include "VeDirectData.h" -#define VE_MAX_VALUE_LEN 33 // VE.Direct Protocol: max value size is 33 including /0 -#define VE_MAX_HEX_LEN 100 // Maximum size of hex frame - max payload 34 byte (=68 char) + safe buffer - +template class VeDirectFrameHandler { public: void loop(); // main loop to read ve.direct data uint32_t getLastUpdate() const; // timestamp of last successful frame read + bool isDataValid() const; // return true if data valid and not outdated protected: VeDirectFrameHandler(); @@ -33,36 +33,14 @@ protected: Print* _msgOut; uint32_t _lastUpdate; - typedef struct { - uint16_t PID = 0; // product id - char SER[VE_MAX_VALUE_LEN]; // serial number - char FW[VE_MAX_VALUE_LEN]; // firmware release number - double V = 0; // battery voltage in V - double I = 0; // battery current in A - double E = 0; // efficiency in percent (calculated, moving average) - - frozen::string const& getPidAsString() const; // product ID as string - } veStruct; - - bool textRxEvent(char* name, char* value, veStruct& frame); - bool isDataValid(veStruct const& frame) const; // return true if data valid and not outdated - - template - static frozen::string const& getAsString(frozen::map const& values, T val) - { - auto pos = values.find(val); - if (pos == values.end()) { - static constexpr frozen::string dummy("???"); - return dummy; - } - return pos->second; - } + T _tmpFrame; private: - void setLastUpdate(); // set timestampt after successful frame read + void reset(); void dumpDebugBuffer(); void rxData(uint8_t inbyte); // byte of serial data - virtual void textRxEvent(char *, char *) = 0; + void processTextData(std::string const& name, std::string const& value); + virtual bool processTextDataDerived(std::string const& name, std::string const& value) = 0; virtual void frameValidEvent() = 0; int hexRxEvent(uint8_t); @@ -78,4 +56,18 @@ private: unsigned _debugIn; uint32_t _lastByteMillis; char _logId[32]; + + /** + * not every frame contains every value the device is communicating, i.e., + * a set of values can be fragmented across multiple frames. frames can be + * invalid. in order to only process data from valid frames, we add data + * to this queue and only process it once the frame was found to be valid. + * this also handles fragmentation nicely, since there is no need to reset + * our data buffer. we simply update the interpreted data from this event + * queue, which is fine as we know the source frame was valid. + */ + std::deque> _textData; }; + +template class VeDirectFrameHandler; +template class VeDirectFrameHandler; diff --git a/lib/VeDirectFrameHandler/VeDirectMpptController.cpp b/lib/VeDirectFrameHandler/VeDirectMpptController.cpp index 16df560c..06616202 100644 --- a/lib/VeDirectFrameHandler/VeDirectMpptController.cpp +++ b/lib/VeDirectFrameHandler/VeDirectMpptController.cpp @@ -7,58 +7,62 @@ void VeDirectMpptController::init(int8_t rx, int8_t tx, Print* msgOut, bool verb _spData = std::make_shared(); } -bool VeDirectMpptController::isDataValid() const { - return VeDirectFrameHandler::isDataValid(*_spData); -} - -void VeDirectMpptController::textRxEvent(char* name, char* value) +bool VeDirectMpptController::processTextDataDerived(std::string const& name, std::string const& value) { - if (VeDirectFrameHandler::textRxEvent(name, value, _tmpFrame)) { - return; + if (name == "LOAD") { + _tmpFrame.LOAD = (value == "ON"); + return true; + } + if (name == "CS") { + _tmpFrame.CS = atoi(value.c_str()); + return true; + } + if (name == "ERR") { + _tmpFrame.ERR = atoi(value.c_str()); + return true; + } + if (name == "OR") { + _tmpFrame.OR = strtol(value.c_str(), nullptr, 0); + return true; + } + if (name == "MPPT") { + _tmpFrame.MPPT = atoi(value.c_str()); + return true; + } + if (name == "HSDS") { + _tmpFrame.HSDS = atoi(value.c_str()); + return true; + } + if (name == "VPV") { + _tmpFrame.VPV = round(atof(value.c_str()) / 10.0) / 100.0; + return true; + } + if (name == "PPV") { + _tmpFrame.PPV = atoi(value.c_str()); + return true; + } + if (name == "H19") { + _tmpFrame.H19 = atof(value.c_str()) / 100.0; + return true; + } + if (name == "H20") { + _tmpFrame.H20 = atof(value.c_str()) / 100.0; + return true; + } + if (name == "H21") { + _tmpFrame.H21 = atoi(value.c_str()); + return true; + } + if (name == "H22") { + _tmpFrame.H22 = atof(value.c_str()) / 100.0; + return true; + } + if (name == "H23") { + _tmpFrame.H23 = atoi(value.c_str()); + return true; } - if (strcmp(name, "LOAD") == 0) { - if (strcmp(value, "ON") == 0) - _tmpFrame.LOAD = true; - else - _tmpFrame.LOAD = false; - } - else if (strcmp(name, "CS") == 0) { - _tmpFrame.CS = atoi(value); - } - else if (strcmp(name, "ERR") == 0) { - _tmpFrame.ERR = atoi(value); - } - else if (strcmp(name, "OR") == 0) { - _tmpFrame.OR = strtol(value, nullptr, 0); - } - else if (strcmp(name, "MPPT") == 0) { - _tmpFrame.MPPT = atoi(value); - } - else if (strcmp(name, "HSDS") == 0) { - _tmpFrame.HSDS = atoi(value); - } - else if (strcmp(name, "VPV") == 0) { - _tmpFrame.VPV = round(atof(value) / 10.0) / 100.0; - } - else if (strcmp(name, "PPV") == 0) { - _tmpFrame.PPV = atoi(value); - } - else if (strcmp(name, "H19") == 0) { - _tmpFrame.H19 = atof(value) / 100.0; - } - else if (strcmp(name, "H20") == 0) { - _tmpFrame.H20 = atof(value) / 100.0; - } - else if (strcmp(name, "H21") == 0) { - _tmpFrame.H21 = atoi(value); - } - else if (strcmp(name, "H22") == 0) { - _tmpFrame.H22 = atof(value) / 100.0; - } - else if (strcmp(name, "H23") == 0) { - _tmpFrame.H23 = atoi(value); - } + return false; } /* @@ -68,108 +72,14 @@ void VeDirectMpptController::textRxEvent(char* name, char* value) void VeDirectMpptController::frameValidEvent() { _tmpFrame.P = _tmpFrame.V * _tmpFrame.I; - _tmpFrame.IPV = 0; if (_tmpFrame.VPV > 0) { _tmpFrame.IPV = _tmpFrame.PPV / _tmpFrame.VPV; } - _tmpFrame.E = 0; - if ( _tmpFrame.PPV > 0) { + if (_tmpFrame.PPV > 0) { _efficiency.addNumber(static_cast(_tmpFrame.P * 100) / _tmpFrame.PPV); _tmpFrame.E = _efficiency.getAverage(); } _spData = std::make_shared(_tmpFrame); - _tmpFrame = {}; - _lastUpdate = millis(); -} - -/* - * getCsAsString - * This function returns the state of operations (CS) as readable text. - */ -frozen::string const& VeDirectMpptController::veMpptStruct::getCsAsString() const -{ - static constexpr frozen::map values = { - { 0, "OFF" }, - { 2, "Fault" }, - { 3, "Bulk" }, - { 4, "Absorbtion" }, - { 5, "Float" }, - { 7, "Equalize (manual)" }, - { 245, "Starting-up" }, - { 247, "Auto equalize / Recondition" }, - { 252, "External Control" } - }; - - return getAsString(values, CS); -} - -/* - * getMpptAsString - * This function returns the state of MPPT (MPPT) as readable text. - */ -frozen::string const& VeDirectMpptController::veMpptStruct::getMpptAsString() const -{ - static constexpr frozen::map values = { - { 0, "OFF" }, - { 1, "Voltage or current limited" }, - { 2, "MPP Tracker active" } - }; - - return getAsString(values, MPPT); -} - -/* - * getErrAsString - * This function returns error state (ERR) as readable text. - */ -frozen::string const& VeDirectMpptController::veMpptStruct::getErrAsString() const -{ - static constexpr frozen::map values = { - { 0, "No error" }, - { 2, "Battery voltage too high" }, - { 17, "Charger temperature too high" }, - { 18, "Charger over current" }, - { 19, "Charger current reversed" }, - { 20, "Bulk time limit exceeded" }, - { 21, "Current sensor issue(sensor bias/sensor broken)" }, - { 26, "Terminals overheated" }, - { 28, "Converter issue (dual converter models only)" }, - { 33, "Input voltage too high (solar panel)" }, - { 34, "Input current too high (solar panel)" }, - { 38, "Input shutdown (due to excessive battery voltage)" }, - { 39, "Input shutdown (due to current flow during off mode)" }, - { 40, "Input" }, - { 65, "Lost communication with one of devices" }, - { 67, "Synchronisedcharging device configuration issue" }, - { 68, "BMS connection lost" }, - { 116, "Factory calibration data lost" }, - { 117, "Invalid/incompatible firmware" }, - { 118, "User settings invalid" } - }; - - return getAsString(values, ERR); -} - -/* - * getOrAsString - * This function returns the off reason (OR) as readable text. - */ -frozen::string const& VeDirectMpptController::veMpptStruct::getOrAsString() const -{ - static constexpr frozen::map values = { - { 0x00000000, "Not off" }, - { 0x00000001, "No input power" }, - { 0x00000002, "Switched off (power switch)" }, - { 0x00000004, "Switched off (device moderegister)" }, - { 0x00000008, "Remote input" }, - { 0x00000010, "Protection active" }, - { 0x00000020, "Paygo" }, - { 0x00000040, "BMS" }, - { 0x00000080, "Engine shutdown detection" }, - { 0x00000100, "Analysing input voltage" } - }; - - return getAsString(values, OR); } diff --git a/lib/VeDirectFrameHandler/VeDirectMpptController.h b/lib/VeDirectFrameHandler/VeDirectMpptController.h index 08574252..277a7376 100644 --- a/lib/VeDirectFrameHandler/VeDirectMpptController.h +++ b/lib/VeDirectFrameHandler/VeDirectMpptController.h @@ -1,6 +1,7 @@ #pragma once #include +#include "VeDirectData.h" #include "VeDirectFrameHandler.h" template @@ -35,43 +36,19 @@ private: size_t _count; }; -class VeDirectMpptController : public VeDirectFrameHandler { +class VeDirectMpptController : public VeDirectFrameHandler { public: VeDirectMpptController() = default; void init(int8_t rx, int8_t tx, Print* msgOut, bool verboseLogging, uint16_t hwSerialPort); - bool isDataValid() const; // return true if data valid and not outdated - struct veMpptStruct : veStruct { - uint8_t MPPT; // state of MPP tracker - int32_t PPV; // panel power in W - int32_t P; // battery output power in W (calculated) - double VPV; // panel voltage in V - double IPV; // panel current in A (calculated) - bool LOAD; // virtual load output state (on if battery voltage reaches upper limit, off if battery reaches lower limit) - uint8_t CS; // current state of operation e.g. OFF or Bulk - uint8_t ERR; // error code - uint32_t OR; // off reason - uint32_t HSDS; // day sequence number 1...365 - double H19; // yield total kWh - double H20; // yield today kWh - int32_t H21; // maximum power today W - double H22; // yield yesterday kWh - int32_t H23; // maximum power yesterday W - - frozen::string const& getMpptAsString() const; // state of mppt as string - frozen::string const& getCsAsString() const; // current state as string - frozen::string const& getErrAsString() const; // error state as string - frozen::string const& getOrAsString() const; // off reason as string - }; - - using spData_t = std::shared_ptr; + using data_t = veMpptStruct; + using spData_t = std::shared_ptr; spData_t getData() const { return _spData; } private: - void textRxEvent(char* name, char* value) final; + bool processTextDataDerived(std::string const& name, std::string const& value) final; void frameValidEvent() final; spData_t _spData = nullptr; - veMpptStruct _tmpFrame{}; // private struct for received name and value pairs MovingAverage _efficiency; }; diff --git a/lib/VeDirectFrameHandler/VeDirectShuntController.cpp b/lib/VeDirectFrameHandler/VeDirectShuntController.cpp index 351712de..be9c8e11 100644 --- a/lib/VeDirectFrameHandler/VeDirectShuntController.cpp +++ b/lib/VeDirectFrameHandler/VeDirectShuntController.cpp @@ -3,94 +3,112 @@ VeDirectShuntController VeDirectShunt; -VeDirectShuntController::VeDirectShuntController() -{ -} - void VeDirectShuntController::init(int8_t rx, int8_t tx, Print* msgOut, bool verboseLogging) { VeDirectFrameHandler::init("SmartShunt", rx, tx, msgOut, verboseLogging, 2); } -void VeDirectShuntController::textRxEvent(char* name, char* value) +bool VeDirectShuntController::processTextDataDerived(std::string const& name, std::string const& value) { - if (VeDirectFrameHandler::textRxEvent(name, value, _tmpFrame)) { - return; + if (name == "T") { + _tmpFrame.T = atoi(value.c_str()); + _tmpFrame.tempPresent = true; + return true; + } + if (name == "P") { + _tmpFrame.P = atoi(value.c_str()); + return true; + } + if (name == "CE") { + _tmpFrame.CE = atoi(value.c_str()); + return true; + } + if (name == "SOC") { + _tmpFrame.SOC = atoi(value.c_str()); + return true; + } + if (name == "TTG") { + _tmpFrame.TTG = atoi(value.c_str()); + return true; + } + if (name == "ALARM") { + _tmpFrame.ALARM = (value == "ON"); + return true; + } + if (name == "H1") { + _tmpFrame.H1 = atoi(value.c_str()); + return true; + } + if (name == "H2") { + _tmpFrame.H2 = atoi(value.c_str()); + return true; + } + if (name == "H3") { + _tmpFrame.H3 = atoi(value.c_str()); + return true; + } + if (name == "H4") { + _tmpFrame.H4 = atoi(value.c_str()); + return true; + } + if (name == "H5") { + _tmpFrame.H5 = atoi(value.c_str()); + return true; + } + if (name == "H6") { + _tmpFrame.H6 = atoi(value.c_str()); + return true; + } + if (name == "H7") { + _tmpFrame.H7 = atoi(value.c_str()); + return true; + } + if (name == "H8") { + _tmpFrame.H8 = atoi(value.c_str()); + return true; + } + if (name == "H9") { + _tmpFrame.H9 = atoi(value.c_str()); + return true; + } + if (name == "H10") { + _tmpFrame.H10 = atoi(value.c_str()); + return true; + } + if (name == "H11") { + _tmpFrame.H11 = atoi(value.c_str()); + return true; + } + if (name == "H12") { + _tmpFrame.H12 = atoi(value.c_str()); + return true; + } + if (name == "H13") { + _tmpFrame.H13 = atoi(value.c_str()); + return true; + } + if (name == "H14") { + _tmpFrame.H14 = atoi(value.c_str()); + return true; + } + if (name == "H15") { + _tmpFrame.H15 = atoi(value.c_str()); + return true; + } + if (name == "H16") { + _tmpFrame.H16 = atoi(value.c_str()); + return true; + } + if (name == "H17") { + _tmpFrame.H17 = atoi(value.c_str()); + return true; + } + if (name == "H18") { + _tmpFrame.H18 = atoi(value.c_str()); + return true; } - if (strcmp(name, "T") == 0) { - _tmpFrame.T = atoi(value); - _tmpFrame.tempPresent = true; - } - else if (strcmp(name, "P") == 0) { - _tmpFrame.P = atoi(value); - } - else if (strcmp(name, "CE") == 0) { - _tmpFrame.CE = atoi(value); - } - else if (strcmp(name, "SOC") == 0) { - _tmpFrame.SOC = atoi(value); - } - else if (strcmp(name, "TTG") == 0) { - _tmpFrame.TTG = atoi(value); - } - else if (strcmp(name, "ALARM") == 0) { - _tmpFrame.ALARM = (strcmp(value, "ON") == 0); - } - else if (strcmp(name, "H1") == 0) { - _tmpFrame.H1 = atoi(value); - } - else if (strcmp(name, "H2") == 0) { - _tmpFrame.H2 = atoi(value); - } - else if (strcmp(name, "H3") == 0) { - _tmpFrame.H3 = atoi(value); - } - else if (strcmp(name, "H4") == 0) { - _tmpFrame.H4 = atoi(value); - } - else if (strcmp(name, "H5") == 0) { - _tmpFrame.H5 = atoi(value); - } - else if (strcmp(name, "H6") == 0) { - _tmpFrame.H6 = atoi(value); - } - else if (strcmp(name, "H7") == 0) { - _tmpFrame.H7 = atoi(value); - } - else if (strcmp(name, "H8") == 0) { - _tmpFrame.H8 = atoi(value); - } - else if (strcmp(name, "H9") == 0) { - _tmpFrame.H9 = atoi(value); - } - else if (strcmp(name, "H10") == 0) { - _tmpFrame.H10 = atoi(value); - } - else if (strcmp(name, "H11") == 0) { - _tmpFrame.H11 = atoi(value); - } - else if (strcmp(name, "H12") == 0) { - _tmpFrame.H12 = atoi(value); - } - else if (strcmp(name, "H13") == 0) { - _tmpFrame.H13 = atoi(value); - } - else if (strcmp(name, "H14") == 0) { - _tmpFrame.H14 = atoi(value); - } - else if (strcmp(name, "H15") == 0) { - _tmpFrame.H15 = atoi(value); - } - else if (strcmp(name, "H16") == 0) { - _tmpFrame.H16 = atoi(value); - } - else if (strcmp(name, "H17") == 0) { - _tmpFrame.H17 = atoi(value); - } - else if (strcmp(name, "H18") == 0) { - _tmpFrame.H18 = atoi(value); - } + return false; } /* @@ -98,12 +116,5 @@ void VeDirectShuntController::textRxEvent(char* name, char* value) * This function is called at the end of the received frame. */ void VeDirectShuntController::frameValidEvent() { - // other than in the MPPT controller, the SmartShunt seems to split all data - // into two seperate messagesas. Thus we update veFrame only every second message - // after a value for PID has been received - if (_tmpFrame.PID == 0) { return; } - veFrame = _tmpFrame; - _tmpFrame = {}; - _lastUpdate = millis(); } diff --git a/lib/VeDirectFrameHandler/VeDirectShuntController.h b/lib/VeDirectFrameHandler/VeDirectShuntController.h index 9e1a5f13..641ec10e 100644 --- a/lib/VeDirectFrameHandler/VeDirectShuntController.h +++ b/lib/VeDirectFrameHandler/VeDirectShuntController.h @@ -1,49 +1,21 @@ #pragma once #include +#include "VeDirectData.h" #include "VeDirectFrameHandler.h" -class VeDirectShuntController : public VeDirectFrameHandler { +class VeDirectShuntController : public VeDirectFrameHandler { public: - VeDirectShuntController(); + VeDirectShuntController() = default; void init(int8_t rx, int8_t tx, Print* msgOut, bool verboseLogging); - struct veShuntStruct : veStruct { - int32_t T; // Battery temperature - bool tempPresent = false; // Battery temperature sensor is attached to the shunt - int32_t P; // Instantaneous power - int32_t CE; // Consumed Amp Hours - int32_t SOC; // State-of-charge - uint32_t TTG; // Time-to-go - bool ALARM; // Alarm condition active - uint32_t AR; // Alarm Reason - int32_t H1; // Depth of the deepest discharge - int32_t H2; // Depth of the last discharge - int32_t H3; // Depth of the average discharge - int32_t H4; // Number of charge cycles - int32_t H5; // Number of full discharges - int32_t H6; // Cumulative Amp Hours drawn - int32_t H7; // Minimum main (battery) voltage - int32_t H8; // Maximum main (battery) voltage - int32_t H9; // Number of seconds since last full charge - int32_t H10; // Number of automatic synchronizations - int32_t H11; // Number of low main voltage alarms - int32_t H12; // Number of high main voltage alarms - int32_t H13; // Number of low auxiliary voltage alarms - int32_t H14; // Number of high auxiliary voltage alarms - int32_t H15; // Minimum auxiliary (battery) voltage - int32_t H16; // Maximum auxiliary (battery) voltage - int32_t H17; // Amount of discharged energy - int32_t H18; // Amount of charged energy - }; - - veShuntStruct veFrame{}; + using data_t = veShuntStruct; + data_t veFrame{}; private: - void textRxEvent(char * name, char * value) final; + bool processTextDataDerived(std::string const& name, std::string const& value) final; void frameValidEvent() final; - veShuntStruct _tmpFrame{}; // private struct for received name and value pairs }; extern VeDirectShuntController VeDirectShunt; diff --git a/src/BatteryStats.cpp b/src/BatteryStats.cpp index 563562c8..12a6b601 100644 --- a/src/BatteryStats.cpp +++ b/src/BatteryStats.cpp @@ -373,7 +373,7 @@ void JkBmsBatteryStats::updateFrom(JkBms::DataPointContainer const& dp) _lastUpdate = millis(); } -void VictronSmartShuntStats::updateFrom(VeDirectShuntController::veShuntStruct const& shuntData) { +void VictronSmartShuntStats::updateFrom(VeDirectShuntController::data_t const& shuntData) { BatteryStats::setVoltage(shuntData.V, millis()); BatteryStats::setSoC(static_cast(shuntData.SOC) / 10, 1/*precision*/, millis()); diff --git a/src/MqttHandleVedirect.cpp b/src/MqttHandleVedirect.cpp index 8cfd6efc..92c3d788 100644 --- a/src/MqttHandleVedirect.cpp +++ b/src/MqttHandleVedirect.cpp @@ -70,7 +70,7 @@ void MqttHandleVedirectClass::loop() VeDirectMpptController::spData_t &spMpptData = spOptMpptData.value(); - VeDirectMpptController::veMpptStruct _kvFrame = _kvFrames[spMpptData->SER]; + VeDirectMpptController::data_t _kvFrame = _kvFrames[spMpptData->SER]; publish_mppt_data(spMpptData, _kvFrame); if (!_PublishFull) { _kvFrames[spMpptData->SER] = *spMpptData; @@ -105,7 +105,7 @@ void MqttHandleVedirectClass::loop() } void MqttHandleVedirectClass::publish_mppt_data(const VeDirectMpptController::spData_t &spMpptData, - VeDirectMpptController::veMpptStruct &frame) const { + const VeDirectMpptController::data_t &frame) const { String value; String topic = "victron/"; topic.concat(spMpptData->SER);