first version

This commit is contained in:
helgeerbe 2023-02-21 22:06:47 +01:00
parent 30440472f7
commit 1e7f6b8f0f
7 changed files with 239 additions and 136 deletions

View File

@ -19,7 +19,7 @@ public:
void init(); void init();
void loop(); void loop();
private: private:
std::map<String, String> _kv_map; veStruct _kvFrame;
uint32_t _lastPublish; uint32_t _lastPublish;
}; };

View File

@ -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. // The name of the record that contains the checksum.
static constexpr char checksumTagName[] = "CHECKSUM"; static constexpr char checksumTagName[] = "CHECKSUM";
// state machine
enum States {
IDLE,
RECORD_BEGIN,
RECORD_NAME,
RECORD_VALUE,
CHECKSUM,
RECORD_HEX
};
HardwareSerial VedirectSerial(1); HardwareSerial VedirectSerial(1);
VeDirectFrameHandler VeDirect; VeDirectFrameHandler VeDirect;
@ -49,8 +59,10 @@ VeDirectFrameHandler::VeDirectFrameHandler() :
//mStop(false), // don't know what Victron uses this for, not using //mStop(false), // don't know what Victron uses this for, not using
_state(IDLE), _state(IDLE),
_checksum(0), _checksum(0),
_textPointer(0),
_name(""), _name(""),
_value(""), _value(""),
_tmpFrame(),
_pollInterval(5), _pollInterval(5),
_lastPoll(0) _lastPoll(0)
{ {
@ -107,7 +119,8 @@ void VeDirectFrameHandler::rxData(uint8_t inbyte)
} }
break; break;
case RECORD_BEGIN: case RECORD_BEGIN:
_name = (char) inbyte; _textPointer = _name;
*_textPointer++ = inbyte;
_state = RECORD_NAME; _state = RECORD_NAME;
break; break;
case RECORD_NAME: case RECORD_NAME:
@ -115,18 +128,22 @@ void VeDirectFrameHandler::rxData(uint8_t inbyte)
switch(inbyte) { switch(inbyte) {
case '\t': case '\t':
// the Checksum record indicates a EOR // the Checksum record indicates a EOR
if (_name.equals(checksumTagName)) { if ( _textPointer < (_name + sizeof(_name)) ) {
_state = CHECKSUM; *_textPointer = 0; /* Zero terminate */
break; if (strcmp(_name, checksumTagName) == 0) {
_state = CHECKSUM;
break;
}
} }
_textPointer = _value; /* Reset value pointer */
_state = RECORD_VALUE; _state = RECORD_VALUE;
_value = "";
break; break;
case '#': /* Ignore # from serial number*/ case '#': /* Ignore # from serial number*/
break; break;
default: default:
// add byte to name, but do no overflow // add byte to name, but do no overflow
_name += (char) inbyte; if ( _textPointer < (_name + sizeof(_name)) )
*_textPointer++ = inbyte;
break; break;
} }
break; break;
@ -134,14 +151,18 @@ void VeDirectFrameHandler::rxData(uint8_t inbyte)
// The record value is being received. The \r indicates a new record. // The record value is being received. The \r indicates a new record.
switch(inbyte) { switch(inbyte) {
case '\n': case '\n':
_tmpMap[_name] = _value; if ( _textPointer < (_value + sizeof(_value)) ) {
*_textPointer = 0; // make zero ended
textRxEvent(_name, _value);
}
_state = RECORD_BEGIN; _state = RECORD_BEGIN;
break; break;
case '\r': /* Skip */ case '\r': /* Skip */
break; break;
default: default:
// add byte to value, but do no overflow // add byte to value, but do no overflow
_value += (char) inbyte; if ( _textPointer < (_value + sizeof(_value)) )
*_textPointer++ = inbyte;
break; break;
} }
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 * 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. * 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) { void VeDirectFrameHandler::frameEndEvent(bool valid) {
if ( valid ) { if ( valid ) {
veMap = _tmpMap; veFrame = _tmpFrame;
setLastUpdate(); setLastUpdate();
} }
_tmpMap.clear(); _tmpFrame = {};
} }
/* /*
@ -199,15 +286,12 @@ bool VeDirectFrameHandler::hexRxEvent(uint8_t inbyte) {
} }
bool VeDirectFrameHandler::isDataValid() { bool VeDirectFrameHandler::isDataValid() {
if (veMap.empty()) {
return false;
}
if ((millis() - getLastUpdate()) / 1000 > _pollInterval * 5) { if ((millis() - getLastUpdate()) / 1000 > _pollInterval * 5) {
return false; return false;
} }
if (veMap.find("SER") == veMap.end()) { if (strlen(veFrame.SER) == 0) {
return false; return false;
} }
return true; return true;
} }
@ -229,12 +313,11 @@ void VeDirectFrameHandler::setLastUpdate()
* getPidAsString * getPidAsString
* This function returns the product id (PID) as readable text. * This function returns the product id (PID) as readable text.
*/ */
String VeDirectFrameHandler::getPidAsString(const char* pid) String VeDirectFrameHandler::getPidAsString(uint16_t pid)
{ {
String strPID =""; String strPID ="";
long lPID = strtol(pid, nullptr, 0); switch(pid) {
switch(lPID) {
case 0x0300: case 0x0300:
strPID = "BlueSolar MPPT 70|15"; strPID = "BlueSolar MPPT 70|15";
break; break;
@ -452,12 +535,11 @@ String VeDirectFrameHandler::getPidAsString(const char* pid)
* getCsAsString * getCsAsString
* This function returns the state of operations (CS) as readable text. * 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 =""; String strCS ="";
int iCS = atoi(cs); switch(cs) {
switch(iCS) {
case 0: case 0:
strCS = "OFF"; strCS = "OFF";
break; break;
@ -495,12 +577,11 @@ String VeDirectFrameHandler::getCsAsString(const char* cs)
* getErrAsString * getErrAsString
* This function returns error state (ERR) as readable text. * This function returns error state (ERR) as readable text.
*/ */
String VeDirectFrameHandler::getErrAsString(const char* err) String VeDirectFrameHandler::getErrAsString(uint8_t err)
{ {
String strERR =""; String strERR ="";
int iERR = atoi(err); switch(err) {
switch(iERR) {
case 0: case 0:
strERR = "No error"; strERR = "No error";
break; break;
@ -571,12 +652,11 @@ String VeDirectFrameHandler::getErrAsString(const char* err)
* getOrAsString * getOrAsString
* This function returns the off reason (OR) as readable text. * This function returns the off reason (OR) as readable text.
*/ */
String VeDirectFrameHandler::getOrAsString(const char* offReason) String VeDirectFrameHandler::getOrAsString(uint32_t offReason)
{ {
String strOR =""; String strOR ="";
long lOR = strtol(offReason, nullptr, 0); switch(offReason) {
switch(lOR) {
case 0x00000000: case 0x00000000:
strOR = "Not off"; strOR = "Not off";
break; break;
@ -617,14 +697,13 @@ String VeDirectFrameHandler::getOrAsString(const char* offReason)
* getMpptAsString * getMpptAsString
* This function returns the state of MPPT (MPPT) as readable text. * 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 =""; String strMPPT ="";
int iMPPT = atoi(mppt); switch(mppt) {
switch(iMPPT) {
case 0: case 0:
strMPPT = "Off"; strMPPT = "OFF";
break; break;
case 1: case 1:
strMPPT = "Voltage or current limited"; strMPPT = "Voltage or current limited";

View File

@ -22,7 +22,29 @@
#define VICTRON_PIN_RX 22 // HardwareSerial RX Pin #define VICTRON_PIN_RX 22 // HardwareSerial RX Pin
#endif #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 { class VeDirectFrameHandler {
@ -34,37 +56,29 @@ public:
void loop(); // main loop to read ve.direct data void loop(); // main loop to read ve.direct data
unsigned long getLastUpdate(); // timestamp of last successful frame read unsigned long getLastUpdate(); // timestamp of last successful frame read
bool isDataValid(); // return true if data valid and not outdated bool isDataValid(); // return true if data valid and not outdated
String getPidAsString(const char* pid); // product id as string String getPidAsString(uint16_t pid); // product id as string
String getCsAsString(const char* cs); // current state as string String getCsAsString(uint8_t cs); // current state as string
String getErrAsString(const char* err); // errer state as string String getErrAsString(uint8_t err); // errer state as string
String getOrAsString(const char* offReason); // off reason as string String getOrAsString(uint32_t offReason); // off reason as string
String getMpptAsString(const char* mppt); // state of mppt as string String getMpptAsString(uint8_t mppt); // state of mppt as string
std::map<String, String> veMap; // public map for received name and value pairs veStruct veFrame; // public map for received name and value pairs
private: private:
void setLastUpdate(); // set timestampt after successful frame read void setLastUpdate(); // set timestampt after successful frame read
void rxData(uint8_t inbyte); // byte of serial data void rxData(uint8_t inbyte); // byte of serial data
void textRxEvent(char *, char *);
void frameEndEvent(bool); // copy temp map to public map void frameEndEvent(bool); // copy temp map to public map
void logE(const char *, const char *); void logE(const char *, const char *);
bool hexRxEvent(uint8_t); bool hexRxEvent(uint8_t);
//bool mStop; // not sure what Victron uses this for, not using //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 int _state; // current state
uint8_t _checksum; // checksum value uint8_t _checksum; // checksum value
String _name; // buffer for the field name char * _textPointer; // pointer to the private buffer we're writing to, name or value
String _value; // buffer for the field value char _name[VE_MAX_VALUE_LEN]; // buffer for the field name
std::map<String, String> _tmpMap; // private map for received name and value pairs 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 _pollInterval;
unsigned long _lastPoll; unsigned long _lastPoll;
}; };

View File

@ -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 ) 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; String sensorId = caption;
sensorId.replace(" ", "_"); sensorId.replace(" ", "_");
@ -93,7 +93,10 @@ void MqttHandleVedirectHassClass::publishSensor(const char* caption, const char*
+ "/" + sensorId + "/" + sensorId
+ "/config"; + "/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); DynamicJsonDocument root(1024);
root[F("name")] = caption; 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) 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; String sensorId = caption;
sensorId.replace(" ", "_"); sensorId.replace(" ", "_");
@ -134,7 +137,10 @@ void MqttHandleVedirectHassClass::publishBinarySensor(const char* caption, const
+ "/" + sensorId + "/" + sensorId
+ "/config"; + "/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); DynamicJsonDocument root(1024);
root[F("name")] = caption; root[F("name")] = caption;
@ -153,11 +159,12 @@ void MqttHandleVedirectHassClass::publishBinarySensor(const char* caption, const
void MqttHandleVedirectHassClass::createDeviceInfo(JsonObject& object) void MqttHandleVedirectHassClass::createDeviceInfo(JsonObject& object)
{ {
object[F("name")] = "Victron(" + VeDirect.veMap["SER"] + ")"; String serial = VeDirect.veFrame.SER;
object[F("ids")] = VeDirect.veMap["SER"]; object[F("name")] = "Victron(" + serial + ")";
object[F("ids")] = serial;
object[F("cu")] = String(F("http://")) + NetworkSettings.localIP().toString(); object[F("cu")] = String(F("http://")) + NetworkSettings.localIP().toString();
object[F("mf")] = F("OpenDTU"); 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; object[F("sw")] = AUTO_GIT_HASH;
} }

View File

@ -29,65 +29,69 @@ void MqttHandleVedirectClass::loop()
} }
if (millis() - _lastPublish > (config.Mqtt_PublishInterval * 1000)) { if (millis() - _lastPublish > (config.Mqtt_PublishInterval * 1000)) {
String key;
String value; String value;
String mapedValue; String topic = "victron/";
bool bChanged = false; topic.concat(VeDirect.veFrame.SER);
String serial = VeDirect.veMap["SER"]; topic.concat("/");
String topic = ""; if (!config.Vedirect_UpdatesOnly || VeDirect.veFrame.PID != _kvFrame.PID)
for (auto it = VeDirect.veMap.begin(); it != VeDirect.veMap.end(); ++it) { MqttSettings.publish(topic + "PID", VeDirect.getPidAsString(VeDirect.veFrame.PID));
key = it->first; if (!config.Vedirect_UpdatesOnly || strcmp(VeDirect.veFrame.SER, _kvFrame.SER) != 0)
value = it->second; MqttSettings.publish(topic + "SER", VeDirect.veFrame.SER );
if (!config.Vedirect_UpdatesOnly || strcmp(VeDirect.veFrame.FW, _kvFrame.FW) != 0)
if (config.Vedirect_UpdatesOnly){ MqttSettings.publish(topic + "FW", VeDirect.veFrame.FW);
// Mark changed values if (!config.Vedirect_UpdatesOnly || VeDirect.veFrame.LOAD != _kvFrame.LOAD)
auto a = _kv_map.find(key); MqttSettings.publish(topic + "LOAD", VeDirect.veFrame.LOAD == true ? "ON": "OFF");
bChanged = true; if (!config.Vedirect_UpdatesOnly || VeDirect.veFrame.CS != _kvFrame.CS)
if (a != _kv_map.end()) { MqttSettings.publish(topic + "CS", VeDirect.getCsAsString(VeDirect.veFrame.CS));
if (a->first.equals(value)) { if (!config.Vedirect_UpdatesOnly || VeDirect.veFrame.ERR != _kvFrame.ERR)
bChanged = false; 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));
// publish only changed key, values pairs if (!config.Vedirect_UpdatesOnly || VeDirect.veFrame.HSDS != _kvFrame.HSDS) {
if (!config.Vedirect_UpdatesOnly || (bChanged && config.Vedirect_UpdatesOnly)) { value = VeDirect.veFrame.HSDS;
topic = "victron/" + serial + "/"; MqttSettings.publish(topic + "HSDS", value);
topic.concat(key); }
if (key.equals("PID")) { if (!config.Vedirect_UpdatesOnly || VeDirect.veFrame.V != _kvFrame.V) {
mapedValue = VeDirect.getPidAsString(value.c_str()); value = VeDirect.veFrame.V;
} MqttSettings.publish(topic + "V", value);
else if (key.equals("CS")) { }
mapedValue = VeDirect.getCsAsString(value.c_str()); if (!config.Vedirect_UpdatesOnly || VeDirect.veFrame.I != _kvFrame.I) {
} value = VeDirect.veFrame.I;
else if (key.equals("ERR")) { MqttSettings.publish(topic + "I", value);
mapedValue = VeDirect.getErrAsString(value.c_str()); }
} if (!config.Vedirect_UpdatesOnly || VeDirect.veFrame.VPV != _kvFrame.VPV) {
else if (key.equals("OR")) { value = VeDirect.veFrame.VPV;
mapedValue = VeDirect.getOrAsString(value.c_str()); MqttSettings.publish(topic + "VPV", value);
} }
else if (key.equals("MPPT")) { if (!config.Vedirect_UpdatesOnly || VeDirect.veFrame.PPV != _kvFrame.PPV) {
mapedValue = VeDirect.getMpptAsString(value.c_str()); value = VeDirect.veFrame.PPV;
} MqttSettings.publish(topic + "PPV", value);
else if (key.equals("V") || }
key.equals("I") || if (!config.Vedirect_UpdatesOnly || VeDirect.veFrame.H19 != _kvFrame.H19) {
key.equals("VPV")) { value = VeDirect.veFrame.H19;
mapedValue = round(value.toDouble() / 10.0) / 100.0; MqttSettings.publish(topic + "H19", value);
} }
else if (key.equals("H19") || if (!config.Vedirect_UpdatesOnly || VeDirect.veFrame.H20 != _kvFrame.H20) {
key.equals("H20") || value = VeDirect.veFrame.H20;
key.equals("H22")) { MqttSettings.publish(topic + "H20", value);
mapedValue = value.toDouble() / 100.0; }
} if (!config.Vedirect_UpdatesOnly || VeDirect.veFrame.H21 != _kvFrame.H21) {
else { value = VeDirect.veFrame.H21;
mapedValue = value; MqttSettings.publish(topic + "H21", value);
} }
MqttSettings.publish(topic.c_str(), mapedValue.c_str()); 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){ if (config.Vedirect_UpdatesOnly){
_kv_map = VeDirect.veMap; _kvFrame= VeDirect.veFrame;
} }
_lastPublish = millis(); _lastPublish = millis();
} }

View File

@ -190,12 +190,11 @@ bool PowerLimiterClass::canUseDirectSolarPower()
CONFIG_T& config = Configuration.get(); CONFIG_T& config = Configuration.get();
if (!config.PowerLimiter_SolarPassTroughEnabled if (!config.PowerLimiter_SolarPassTroughEnabled
|| !config.Vedirect_Enabled || !config.Vedirect_Enabled) {
|| !VeDirect.veMap.count("PPV")) {
return false; return false;
} }
if (VeDirect.veMap["PPV"].toInt() < 10) { if (VeDirect.veFrame.PPV < 10.0) {
// Not enough power // Not enough power
return false; return false;
} }
@ -209,7 +208,7 @@ uint32_t PowerLimiterClass::getDirectSolarPower()
return 0; return 0;
} }
return VeDirect.veMap["PPV"].toInt(); return (uint32_t) round(VeDirect.veFrame.PPV);
} }
float PowerLimiterClass::getLoadCorrectedVoltage(std::shared_ptr<InverterAbstract> inverter) float PowerLimiterClass::getLoadCorrectedVoltage(std::shared_ptr<InverterAbstract> inverter)

View File

@ -82,37 +82,37 @@ void WebApiWsVedirectLiveClass::generateJsonResponse(JsonVariant& root)
// device info // device info
root["data_age"] = (millis() - VeDirect.getLastUpdate() ) / 1000; root["data_age"] = (millis() - VeDirect.getLastUpdate() ) / 1000;
root["age_critical"] = !VeDirect.isDataValid(); root["age_critical"] = !VeDirect.isDataValid();
root["PID"] = VeDirect.getPidAsString(VeDirect.veMap["PID"].c_str()); root["PID"] = VeDirect.getPidAsString(VeDirect.veFrame.PID);
root["SER"] = VeDirect.veMap["SER"]; root["SER"] = VeDirect.veFrame.SER;
root["FW"] = VeDirect.veMap["FW"]; root["FW"] = VeDirect.veFrame.FW;
root["LOAD"] = VeDirect.veMap["LOAD"]; root["LOAD"] = VeDirect.veFrame.LOAD == true ? "ON" : "OFF";
root["CS"] = VeDirect.getCsAsString(VeDirect.veMap["CS"].c_str()); root["CS"] = VeDirect.getCsAsString(VeDirect.veFrame.CS);
root["ERR"] = VeDirect.getErrAsString(VeDirect.veMap["ERR"].c_str()); root["ERR"] = VeDirect.getErrAsString(VeDirect.veFrame.ERR);
root["OR"] = VeDirect.getOrAsString(VeDirect.veMap["OR"].c_str()); root["OR"] = VeDirect.getOrAsString(VeDirect.veFrame.OR);
root["MPPT"] = VeDirect.getMpptAsString(VeDirect.veMap["MPPT"].c_str()); root["MPPT"] = VeDirect.getMpptAsString(VeDirect.veFrame.MPPT);
root["HSDS"]["v"] = VeDirect.veMap["HSDS"].toInt(); root["HSDS"]["v"] = VeDirect.veFrame.HSDS;
root["HSDS"]["u"] = "Days"; root["HSDS"]["u"] = "Days";
// battery info // 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["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"; root["I"]["u"] = "A";
// panel info // 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["VPV"]["u"] = "V";
root["PPV"]["v"] = VeDirect.veMap["PPV"].toInt(); root["PPV"]["v"] = VeDirect.veFrame.PPV;
root["PPV"]["u"] = "W"; root["PPV"]["u"] = "W";
root["H19"]["v"] = VeDirect.veMap["H19"].toDouble() / 100.0; root["H19"]["v"] = VeDirect.veFrame.H19;
root["H19"]["u"] = "kWh"; root["H19"]["u"] = "kWh";
root["H20"]["v"] = VeDirect.veMap["H20"].toDouble() / 100.0; root["H20"]["v"] = VeDirect.veFrame.H20;
root["H20"]["u"] = "kWh"; root["H20"]["u"] = "kWh";
root["H21"]["v"] = VeDirect.veMap["H21"].toInt(); root["H21"]["v"] = VeDirect.veFrame.H21;
root["H21"]["u"] = "W"; root["H21"]["u"] = "W";
root["H22"]["v"] = VeDirect.veMap["H22"].toDouble() / 100.0; root["H22"]["v"] = VeDirect.veFrame.H22;
root["H22"]["u"] = "kWh"; root["H22"]["u"] = "kWh";
root["H23"]["v"] = VeDirect.veMap["H23"].toInt(); root["H23"]["v"] = VeDirect.veFrame.H23;
root["H23"]["u"] = "W"; root["H23"]["u"] = "W";
if (VeDirect.getLastUpdate() > _newestVedirectTimestamp) { if (VeDirect.getLastUpdate() > _newestVedirectTimestamp) {