From 1e7f6b8f0f970c66ab4de71885a1f5719751e4f8 Mon Sep 17 00:00:00 2001 From: helgeerbe Date: Tue, 21 Feb 2023 22:06:47 +0100 Subject: [PATCH] first version --- include/MqttHandleVedirect.h | 2 +- .../VeDirectFrameHandler.cpp | 143 ++++++++++++++---- .../VeDirectFrameHandler.h | 52 ++++--- src/MqttHandlVedirectHass.cpp | 21 ++- src/MqttHandleVedirect.cpp | 114 +++++++------- src/PowerLimiter.cpp | 7 +- src/WebApi_ws_vedirect_live.cpp | 36 ++--- 7 files changed, 239 insertions(+), 136 deletions(-) diff --git a/include/MqttHandleVedirect.h b/include/MqttHandleVedirect.h index 5c752dd5..719b8fc7 100644 --- a/include/MqttHandleVedirect.h +++ b/include/MqttHandleVedirect.h @@ -19,7 +19,7 @@ public: void init(); void loop(); private: - std::map _kv_map; + veStruct _kvFrame; uint32_t _lastPublish; }; diff --git a/lib/VeDirectFrameHandler/VeDirectFrameHandler.cpp b/lib/VeDirectFrameHandler/VeDirectFrameHandler.cpp index c06894fc..2b0c8cac 100644 --- a/lib/VeDirectFrameHandler/VeDirectFrameHandler.cpp +++ b/lib/VeDirectFrameHandler/VeDirectFrameHandler.cpp @@ -41,6 +41,16 @@ char MODULE[] = "VE.Frame"; // Victron seems to use this to find out where loggi // The name of the record that contains the checksum. static constexpr char checksumTagName[] = "CHECKSUM"; +// state machine +enum States { + IDLE, + RECORD_BEGIN, + RECORD_NAME, + RECORD_VALUE, + CHECKSUM, + RECORD_HEX +}; + HardwareSerial VedirectSerial(1); VeDirectFrameHandler VeDirect; @@ -49,8 +59,10 @@ VeDirectFrameHandler::VeDirectFrameHandler() : //mStop(false), // don't know what Victron uses this for, not using _state(IDLE), _checksum(0), + _textPointer(0), _name(""), _value(""), + _tmpFrame(), _pollInterval(5), _lastPoll(0) { @@ -107,7 +119,8 @@ void VeDirectFrameHandler::rxData(uint8_t inbyte) } break; case RECORD_BEGIN: - _name = (char) inbyte; + _textPointer = _name; + *_textPointer++ = inbyte; _state = RECORD_NAME; break; case RECORD_NAME: @@ -115,18 +128,22 @@ void VeDirectFrameHandler::rxData(uint8_t inbyte) switch(inbyte) { case '\t': // the Checksum record indicates a EOR - if (_name.equals(checksumTagName)) { - _state = CHECKSUM; - break; + if ( _textPointer < (_name + sizeof(_name)) ) { + *_textPointer = 0; /* Zero terminate */ + if (strcmp(_name, checksumTagName) == 0) { + _state = CHECKSUM; + break; + } } + _textPointer = _value; /* Reset value pointer */ _state = RECORD_VALUE; - _value = ""; break; case '#': /* Ignore # from serial number*/ break; default: // add byte to name, but do no overflow - _name += (char) inbyte; + if ( _textPointer < (_name + sizeof(_name)) ) + *_textPointer++ = inbyte; break; } break; @@ -134,14 +151,18 @@ void VeDirectFrameHandler::rxData(uint8_t inbyte) // The record value is being received. The \r indicates a new record. switch(inbyte) { case '\n': - _tmpMap[_name] = _value; + if ( _textPointer < (_value + sizeof(_value)) ) { + *_textPointer = 0; // make zero ended + textRxEvent(_name, _value); + } _state = RECORD_BEGIN; break; case '\r': /* Skip */ break; default: // add byte to value, but do no overflow - _value += (char) inbyte; + if ( _textPointer < (_value + sizeof(_value)) ) + *_textPointer++ = inbyte; break; } break; @@ -164,6 +185,72 @@ 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. + */ +void VeDirectFrameHandler::textRxEvent(char * name, char * value) { + if (strcmp(name, "PID") == 0) { + _tmpFrame.PID = strtol(value, nullptr, 0); + } + else if (strcmp(name, "SER") == 0) { + strcpy(_tmpFrame.SER, value); + } + else if (strcmp(name, "FW") == 0) { + strcpy(_tmpFrame.FW, value); + } + else 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, "V") == 0) { + _tmpFrame.V = round(atof(value) / 10.0) / 100.0; + } + else if (strcmp(name, "I") == 0) { + _tmpFrame.I = round(atof(value) / 10.0) / 100.0; + } + 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); + } + +} + + /* * frameEndEvent * This function is called at the end of the received frame. If the checksum is valid, the temp buffer is read line by line. @@ -172,10 +259,10 @@ void VeDirectFrameHandler::rxData(uint8_t inbyte) */ void VeDirectFrameHandler::frameEndEvent(bool valid) { if ( valid ) { - veMap = _tmpMap; + veFrame = _tmpFrame; setLastUpdate(); } - _tmpMap.clear(); + _tmpFrame = {}; } /* @@ -199,15 +286,12 @@ bool VeDirectFrameHandler::hexRxEvent(uint8_t inbyte) { } bool VeDirectFrameHandler::isDataValid() { - if (veMap.empty()) { - return false; - } if ((millis() - getLastUpdate()) / 1000 > _pollInterval * 5) { return false; } - if (veMap.find("SER") == veMap.end()) { - return false; - } + if (strlen(veFrame.SER) == 0) { + return false; + } return true; } @@ -229,12 +313,11 @@ void VeDirectFrameHandler::setLastUpdate() * getPidAsString * This function returns the product id (PID) as readable text. */ -String VeDirectFrameHandler::getPidAsString(const char* pid) +String VeDirectFrameHandler::getPidAsString(uint16_t pid) { String strPID =""; - long lPID = strtol(pid, nullptr, 0); - switch(lPID) { + switch(pid) { case 0x0300: strPID = "BlueSolar MPPT 70|15"; break; @@ -452,12 +535,11 @@ String VeDirectFrameHandler::getPidAsString(const char* pid) * getCsAsString * This function returns the state of operations (CS) as readable text. */ -String VeDirectFrameHandler::getCsAsString(const char* cs) +String VeDirectFrameHandler::getCsAsString(uint8_t cs) { String strCS =""; - int iCS = atoi(cs); - switch(iCS) { + switch(cs) { case 0: strCS = "OFF"; break; @@ -495,12 +577,11 @@ String VeDirectFrameHandler::getCsAsString(const char* cs) * getErrAsString * This function returns error state (ERR) as readable text. */ -String VeDirectFrameHandler::getErrAsString(const char* err) +String VeDirectFrameHandler::getErrAsString(uint8_t err) { String strERR =""; - int iERR = atoi(err); - switch(iERR) { + switch(err) { case 0: strERR = "No error"; break; @@ -571,12 +652,11 @@ String VeDirectFrameHandler::getErrAsString(const char* err) * getOrAsString * This function returns the off reason (OR) as readable text. */ -String VeDirectFrameHandler::getOrAsString(const char* offReason) +String VeDirectFrameHandler::getOrAsString(uint32_t offReason) { String strOR =""; - long lOR = strtol(offReason, nullptr, 0); - switch(lOR) { + switch(offReason) { case 0x00000000: strOR = "Not off"; break; @@ -617,14 +697,13 @@ String VeDirectFrameHandler::getOrAsString(const char* offReason) * getMpptAsString * This function returns the state of MPPT (MPPT) as readable text. */ -String VeDirectFrameHandler::getMpptAsString(const char* mppt) +String VeDirectFrameHandler::getMpptAsString(uint8_t mppt) { String strMPPT =""; - int iMPPT = atoi(mppt); - switch(iMPPT) { + switch(mppt) { case 0: - strMPPT = "Off"; + strMPPT = "OFF"; break; case 1: strMPPT = "Voltage or current limited"; diff --git a/lib/VeDirectFrameHandler/VeDirectFrameHandler.h b/lib/VeDirectFrameHandler/VeDirectFrameHandler.h index 0facdd8e..dca5a050 100644 --- a/lib/VeDirectFrameHandler/VeDirectFrameHandler.h +++ b/lib/VeDirectFrameHandler/VeDirectFrameHandler.h @@ -22,7 +22,29 @@ #define VICTRON_PIN_RX 22 // HardwareSerial RX Pin #endif +#define VE_MAX_NAME_LEN 9 // VE.Direct Protocol: max name size is 9 including /0 +#define VE_MAX_VALUE_LEN 33 // VE.Direct Protocol: max value size is 33 including /0 +typedef struct { + uint16_t PID; // pruduct id + char SER[VE_MAX_VALUE_LEN]; // serial number + char FW[VE_MAX_VALUE_LEN]; // firmware release number + 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 + uint8_t MPPT; // state of MPP tracker + uint16_t HSDS; // day sequence number 1...365 + double V; // battery voltage in V + double I; // battery current in A + double VPV; // panel voltage in V + double PPV; // panel power in W + double H19; // yield total kWh + double H20; // yield today kWh + uint16_t H21; // maximum power today W + double H22; // yield yesterday kWh + uint16_t H23; // maximum power yesterday W +} veStruct; class VeDirectFrameHandler { @@ -34,37 +56,29 @@ public: void loop(); // main loop to read ve.direct data unsigned long getLastUpdate(); // timestamp of last successful frame read bool isDataValid(); // return true if data valid and not outdated - String getPidAsString(const char* pid); // product id as string - String getCsAsString(const char* cs); // current state as string - String getErrAsString(const char* err); // errer state as string - String getOrAsString(const char* offReason); // off reason as string - String getMpptAsString(const char* mppt); // state of mppt as string + String getPidAsString(uint16_t pid); // product id as string + String getCsAsString(uint8_t cs); // current state as string + String getErrAsString(uint8_t err); // errer state as string + String getOrAsString(uint32_t offReason); // off reason as string + String getMpptAsString(uint8_t mppt); // state of mppt as string - std::map veMap; // public map for received name and value pairs + veStruct veFrame; // public map for received name and value pairs private: void setLastUpdate(); // set timestampt after successful frame read void rxData(uint8_t inbyte); // byte of serial data + void textRxEvent(char *, char *); void frameEndEvent(bool); // copy temp map to public map void logE(const char *, const char *); bool hexRxEvent(uint8_t); //bool mStop; // not sure what Victron uses this for, not using - - enum States { // state machine - IDLE, - RECORD_BEGIN, - RECORD_NAME, - RECORD_VALUE, - CHECKSUM, - RECORD_HEX - }; - int _state; // current state uint8_t _checksum; // checksum value - String _name; // buffer for the field name - String _value; // buffer for the field value - std::map _tmpMap; // private map for received name and value pairs + char * _textPointer; // pointer to the private buffer we're writing to, name or value + char _name[VE_MAX_VALUE_LEN]; // buffer for the field name + char _value[VE_MAX_VALUE_LEN]; // buffer for the field value + veStruct _tmpFrame; // private struct for received name and value pairs unsigned long _pollInterval; unsigned long _lastPoll; }; diff --git a/src/MqttHandlVedirectHass.cpp b/src/MqttHandlVedirectHass.cpp index 931d30bc..d31d2777 100644 --- a/src/MqttHandlVedirectHass.cpp +++ b/src/MqttHandlVedirectHass.cpp @@ -83,7 +83,7 @@ void MqttHandleVedirectHassClass::publishConfig() void MqttHandleVedirectHassClass::publishSensor(const char* caption, const char* subTopic, const char* deviceClass, const char* stateClass, const char* unitOfMeasurement ) { - String serial = VeDirect.veMap["SER"]; + String serial = VeDirect.veFrame.SER; String sensorId = caption; sensorId.replace(" ", "_"); @@ -93,7 +93,10 @@ void MqttHandleVedirectHassClass::publishSensor(const char* caption, const char* + "/" + sensorId + "/config"; - String statTopic = MqttSettings.getPrefix() + "victron/" + VeDirect.veMap["SER"] + "/" + subTopic; + String statTopic = MqttSettings.getPrefix() + "victron/"; + statTopic.concat(VeDirect.veFrame.SER); + statTopic.concat("/"); + statTopic.concat(subTopic); DynamicJsonDocument root(1024); root[F("name")] = caption; @@ -124,7 +127,7 @@ void MqttHandleVedirectHassClass::publishSensor(const char* caption, const char* } void MqttHandleVedirectHassClass::publishBinarySensor(const char* caption, const char* subTopic, const char* payload_on, const char* payload_off) { - String serial = VeDirect.veMap["SER"]; + String serial = VeDirect.veFrame.SER; String sensorId = caption; sensorId.replace(" ", "_"); @@ -134,7 +137,10 @@ void MqttHandleVedirectHassClass::publishBinarySensor(const char* caption, const + "/" + sensorId + "/config"; - String statTopic = MqttSettings.getPrefix() + "victron/" + VeDirect.veMap["SER"] + "/" + subTopic; + String statTopic = MqttSettings.getPrefix() + "victron/"; + statTopic.concat(VeDirect.veFrame.SER); + statTopic.concat("/"); + statTopic.concat(subTopic); DynamicJsonDocument root(1024); root[F("name")] = caption; @@ -153,11 +159,12 @@ void MqttHandleVedirectHassClass::publishBinarySensor(const char* caption, const void MqttHandleVedirectHassClass::createDeviceInfo(JsonObject& object) { - object[F("name")] = "Victron(" + VeDirect.veMap["SER"] + ")"; - object[F("ids")] = VeDirect.veMap["SER"]; + String serial = VeDirect.veFrame.SER; + object[F("name")] = "Victron(" + serial + ")"; + object[F("ids")] = serial; object[F("cu")] = String(F("http://")) + NetworkSettings.localIP().toString(); object[F("mf")] = F("OpenDTU"); - object[F("mdl")] = VeDirect.getPidAsString(VeDirect.veMap["PID"].c_str()); + object[F("mdl")] = VeDirect.getPidAsString(VeDirect.veFrame.PID); object[F("sw")] = AUTO_GIT_HASH; } diff --git a/src/MqttHandleVedirect.cpp b/src/MqttHandleVedirect.cpp index 3ccf689d..66c1d1bf 100644 --- a/src/MqttHandleVedirect.cpp +++ b/src/MqttHandleVedirect.cpp @@ -29,65 +29,69 @@ void MqttHandleVedirectClass::loop() } if (millis() - _lastPublish > (config.Mqtt_PublishInterval * 1000)) { - String key; String value; - String mapedValue; - bool bChanged = false; - String serial = VeDirect.veMap["SER"]; + String topic = "victron/"; + topic.concat(VeDirect.veFrame.SER); + topic.concat("/"); - String topic = ""; - for (auto it = VeDirect.veMap.begin(); it != VeDirect.veMap.end(); ++it) { - key = it->first; - value = it->second; - - if (config.Vedirect_UpdatesOnly){ - // Mark changed values - auto a = _kv_map.find(key); - bChanged = true; - if (a != _kv_map.end()) { - if (a->first.equals(value)) { - bChanged = false; - } - } - } - - // publish only changed key, values pairs - if (!config.Vedirect_UpdatesOnly || (bChanged && config.Vedirect_UpdatesOnly)) { - topic = "victron/" + serial + "/"; - topic.concat(key); - if (key.equals("PID")) { - mapedValue = VeDirect.getPidAsString(value.c_str()); - } - else if (key.equals("CS")) { - mapedValue = VeDirect.getCsAsString(value.c_str()); - } - else if (key.equals("ERR")) { - mapedValue = VeDirect.getErrAsString(value.c_str()); - } - else if (key.equals("OR")) { - mapedValue = VeDirect.getOrAsString(value.c_str()); - } - else if (key.equals("MPPT")) { - mapedValue = VeDirect.getMpptAsString(value.c_str()); - } - else if (key.equals("V") || - key.equals("I") || - key.equals("VPV")) { - mapedValue = round(value.toDouble() / 10.0) / 100.0; - } - else if (key.equals("H19") || - key.equals("H20") || - key.equals("H22")) { - mapedValue = value.toDouble() / 100.0; - } - else { - mapedValue = value; - } - MqttSettings.publish(topic.c_str(), mapedValue.c_str()); - } + if (!config.Vedirect_UpdatesOnly || VeDirect.veFrame.PID != _kvFrame.PID) + MqttSettings.publish(topic + "PID", VeDirect.getPidAsString(VeDirect.veFrame.PID)); + if (!config.Vedirect_UpdatesOnly || strcmp(VeDirect.veFrame.SER, _kvFrame.SER) != 0) + MqttSettings.publish(topic + "SER", VeDirect.veFrame.SER ); + if (!config.Vedirect_UpdatesOnly || strcmp(VeDirect.veFrame.FW, _kvFrame.FW) != 0) + MqttSettings.publish(topic + "FW", VeDirect.veFrame.FW); + if (!config.Vedirect_UpdatesOnly || VeDirect.veFrame.LOAD != _kvFrame.LOAD) + MqttSettings.publish(topic + "LOAD", VeDirect.veFrame.LOAD == true ? "ON": "OFF"); + if (!config.Vedirect_UpdatesOnly || VeDirect.veFrame.CS != _kvFrame.CS) + MqttSettings.publish(topic + "CS", VeDirect.getCsAsString(VeDirect.veFrame.CS)); + if (!config.Vedirect_UpdatesOnly || VeDirect.veFrame.ERR != _kvFrame.ERR) + MqttSettings.publish(topic + "ERR", VeDirect.getErrAsString(VeDirect.veFrame.ERR)); + if (!config.Vedirect_UpdatesOnly || VeDirect.veFrame.OR != _kvFrame.OR) + MqttSettings.publish(topic + "OR", VeDirect.getOrAsString(VeDirect.veFrame.OR)); + if (!config.Vedirect_UpdatesOnly || VeDirect.veFrame.MPPT != _kvFrame.MPPT) + MqttSettings.publish(topic + "MPPT", VeDirect.getMpptAsString(VeDirect.veFrame.MPPT)); + if (!config.Vedirect_UpdatesOnly || VeDirect.veFrame.HSDS != _kvFrame.HSDS) { + value = VeDirect.veFrame.HSDS; + MqttSettings.publish(topic + "HSDS", value); + } + if (!config.Vedirect_UpdatesOnly || VeDirect.veFrame.V != _kvFrame.V) { + value = VeDirect.veFrame.V; + MqttSettings.publish(topic + "V", value); + } + if (!config.Vedirect_UpdatesOnly || VeDirect.veFrame.I != _kvFrame.I) { + value = VeDirect.veFrame.I; + MqttSettings.publish(topic + "I", value); + } + if (!config.Vedirect_UpdatesOnly || VeDirect.veFrame.VPV != _kvFrame.VPV) { + value = VeDirect.veFrame.VPV; + MqttSettings.publish(topic + "VPV", value); + } + if (!config.Vedirect_UpdatesOnly || VeDirect.veFrame.PPV != _kvFrame.PPV) { + value = VeDirect.veFrame.PPV; + MqttSettings.publish(topic + "PPV", value); + } + if (!config.Vedirect_UpdatesOnly || VeDirect.veFrame.H19 != _kvFrame.H19) { + value = VeDirect.veFrame.H19; + MqttSettings.publish(topic + "H19", value); + } + if (!config.Vedirect_UpdatesOnly || VeDirect.veFrame.H20 != _kvFrame.H20) { + value = VeDirect.veFrame.H20; + MqttSettings.publish(topic + "H20", value); + } + if (!config.Vedirect_UpdatesOnly || VeDirect.veFrame.H21 != _kvFrame.H21) { + value = VeDirect.veFrame.H21; + MqttSettings.publish(topic + "H21", value); + } + if (!config.Vedirect_UpdatesOnly || VeDirect.veFrame.H22 != _kvFrame.H22) { + value = VeDirect.veFrame.H22; + MqttSettings.publish(topic + "H22", value); + } + if (!config.Vedirect_UpdatesOnly || VeDirect.veFrame.H23 != _kvFrame.H23) { + value = VeDirect.veFrame.H23; + MqttSettings.publish(topic + "H23", value); } if (config.Vedirect_UpdatesOnly){ - _kv_map = VeDirect.veMap; + _kvFrame= VeDirect.veFrame; } _lastPublish = millis(); } diff --git a/src/PowerLimiter.cpp b/src/PowerLimiter.cpp index e4bfc504..826b070b 100644 --- a/src/PowerLimiter.cpp +++ b/src/PowerLimiter.cpp @@ -190,12 +190,11 @@ bool PowerLimiterClass::canUseDirectSolarPower() CONFIG_T& config = Configuration.get(); if (!config.PowerLimiter_SolarPassTroughEnabled - || !config.Vedirect_Enabled - || !VeDirect.veMap.count("PPV")) { + || !config.Vedirect_Enabled) { return false; } - if (VeDirect.veMap["PPV"].toInt() < 10) { + if (VeDirect.veFrame.PPV < 10.0) { // Not enough power return false; } @@ -209,7 +208,7 @@ uint32_t PowerLimiterClass::getDirectSolarPower() return 0; } - return VeDirect.veMap["PPV"].toInt(); + return (uint32_t) round(VeDirect.veFrame.PPV); } float PowerLimiterClass::getLoadCorrectedVoltage(std::shared_ptr inverter) diff --git a/src/WebApi_ws_vedirect_live.cpp b/src/WebApi_ws_vedirect_live.cpp index 09d6be26..fcf1224e 100644 --- a/src/WebApi_ws_vedirect_live.cpp +++ b/src/WebApi_ws_vedirect_live.cpp @@ -82,37 +82,37 @@ void WebApiWsVedirectLiveClass::generateJsonResponse(JsonVariant& root) // device info root["data_age"] = (millis() - VeDirect.getLastUpdate() ) / 1000; root["age_critical"] = !VeDirect.isDataValid(); - root["PID"] = VeDirect.getPidAsString(VeDirect.veMap["PID"].c_str()); - root["SER"] = VeDirect.veMap["SER"]; - root["FW"] = VeDirect.veMap["FW"]; - root["LOAD"] = VeDirect.veMap["LOAD"]; - root["CS"] = VeDirect.getCsAsString(VeDirect.veMap["CS"].c_str()); - root["ERR"] = VeDirect.getErrAsString(VeDirect.veMap["ERR"].c_str()); - root["OR"] = VeDirect.getOrAsString(VeDirect.veMap["OR"].c_str()); - root["MPPT"] = VeDirect.getMpptAsString(VeDirect.veMap["MPPT"].c_str()); - root["HSDS"]["v"] = VeDirect.veMap["HSDS"].toInt(); + root["PID"] = VeDirect.getPidAsString(VeDirect.veFrame.PID); + root["SER"] = VeDirect.veFrame.SER; + root["FW"] = VeDirect.veFrame.FW; + root["LOAD"] = VeDirect.veFrame.LOAD == true ? "ON" : "OFF"; + root["CS"] = VeDirect.getCsAsString(VeDirect.veFrame.CS); + root["ERR"] = VeDirect.getErrAsString(VeDirect.veFrame.ERR); + root["OR"] = VeDirect.getOrAsString(VeDirect.veFrame.OR); + root["MPPT"] = VeDirect.getMpptAsString(VeDirect.veFrame.MPPT); + root["HSDS"]["v"] = VeDirect.veFrame.HSDS; root["HSDS"]["u"] = "Days"; // battery info - root["V"]["v"] = round(VeDirect.veMap["V"].toDouble() / 10.0) / 100.0; + root["V"]["v"] = VeDirect.veFrame.V; root["V"]["u"] = "V"; - root["I"]["v"] = round(VeDirect.veMap["I"].toDouble() / 10.0) / 100.0; + root["I"]["v"] = VeDirect.veFrame.I; root["I"]["u"] = "A"; // panel info - root["VPV"]["v"] = round(VeDirect.veMap["VPV"].toDouble() / 10.0) / 100.0; + root["VPV"]["v"] = VeDirect.veFrame.VPV; root["VPV"]["u"] = "V"; - root["PPV"]["v"] = VeDirect.veMap["PPV"].toInt(); + root["PPV"]["v"] = VeDirect.veFrame.PPV; root["PPV"]["u"] = "W"; - root["H19"]["v"] = VeDirect.veMap["H19"].toDouble() / 100.0; + root["H19"]["v"] = VeDirect.veFrame.H19; root["H19"]["u"] = "kWh"; - root["H20"]["v"] = VeDirect.veMap["H20"].toDouble() / 100.0; + root["H20"]["v"] = VeDirect.veFrame.H20; root["H20"]["u"] = "kWh"; - root["H21"]["v"] = VeDirect.veMap["H21"].toInt(); + root["H21"]["v"] = VeDirect.veFrame.H21; root["H21"]["u"] = "W"; - root["H22"]["v"] = VeDirect.veMap["H22"].toDouble() / 100.0; + root["H22"]["v"] = VeDirect.veFrame.H22; root["H22"]["u"] = "kWh"; - root["H23"]["v"] = VeDirect.veMap["H23"].toInt(); + root["H23"]["v"] = VeDirect.veFrame.H23; root["H23"]["u"] = "W"; if (VeDirect.getLastUpdate() > _newestVedirectTimestamp) {