VE.Direct: process more values and refactor variable names

* process "IL", "AR" and "MON"
* discard "BMV" and (unsolicited) History Data
* simplify isDataValid()
* veMpptStruct, veStruct: new, verbose variable names, including units,
  and replace floats (save values with original integer precision)
* comment on rollover situation in isDataValid()
This commit is contained in:
SW-Nico 2024-04-03 21:54:52 +02:00 committed by Bernhard Kirchen
parent 3934906001
commit b9ad1e3054
12 changed files with 144 additions and 115 deletions

View File

@ -134,7 +134,7 @@ frozen::string const& veStruct::getPidAsString() const
{ 0xA3F0, "Smart BuckBoost 12V/12V-50A" },
};
return getAsString(values, PID);
return getAsString(values, productID_PID);
}
/*
@ -154,7 +154,7 @@ frozen::string const& veMpptStruct::getCsAsString() const
{ 252, "External Control" }
};
return getAsString(values, CS);
return getAsString(values, currentState_CS);
}
/*
@ -168,7 +168,7 @@ frozen::string const& veMpptStruct::getMpptAsString() const
{ 2, "MPP Tracker active" }
};
return getAsString(values, MPPT);
return getAsString(values, stateOfTracker_MPPT);
}
/*
@ -199,7 +199,7 @@ frozen::string const& veMpptStruct::getErrAsString() const
{ 118, "User settings invalid" }
};
return getAsString(values, ERR);
return getAsString(values, errorCode_ERR);
}
/*
@ -220,7 +220,7 @@ frozen::string const& veMpptStruct::getOrAsString() const
{ 0x00000100, "Analysing input voltage" }
};
return getAsString(values, OR);
return getAsString(values, offReason_OR);
}
frozen::string const& VeDirectHexData::getResponseAsString() const

View File

@ -7,32 +7,33 @@
#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
float V = 0; // battery voltage in V
float I = 0; // battery current in A
float E = 0; // efficiency in percent (calculated, moving average)
uint16_t productID_PID = 0; // product id
char serialNr_SER[VE_MAX_VALUE_LEN]; // serial number
char firmwareNr_FW[VE_MAX_VALUE_LEN]; // firmware release number
uint32_t batteryVoltage_V_mV = 0; // battery voltage in mV
int32_t batteryCurrent_I_mA = 0; // battery current in mA (can be negative)
float mpptEfficiency_Percent = 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)
float VPV; // panel voltage in V
float 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
float H19; // yield total kWh
float H20; // yield today kWh
int32_t H21; // maximum power today W
float H22; // yield yesterday kWh
int32_t H23; // maximum power yesterday W
uint8_t stateOfTracker_MPPT; // state of MPP tracker
uint16_t panelPower_PPV_W; // panel power in W
uint32_t panelVoltage_VPV_mV; // panel voltage in mV
uint32_t panelCurrent_mA; // panel current in mA (calculated)
int16_t batteryOutputPower_W; // battery output power in W (calculated, can be negative if load output is used)
uint32_t loadCurrent_IL_mA; // Load current in mA (Available only for models with a load output)
bool loadOutputState_LOAD; // virtual load output state (on if battery voltage reaches upper limit, off if battery reaches lower limit)
uint8_t currentState_CS; // current state of operation e.g. OFF or Bulk
uint8_t errorCode_ERR; // error code
uint32_t offReason_OR; // off reason
uint16_t daySequenceNr_HSDS; // day sequence number 1...365
uint32_t yieldTotal_H19_Wh; // yield total resetable Wh
uint32_t yieldToday_H20_Wh; // yield today Wh
uint16_t maxPowerToday_H21_W; // maximum power today W
uint32_t yieldYesterday_H22_Wh; // yield yesterday Wh
uint16_t maxPowerYesterday_H23_W; // maximum power yesterday W
// these are values communicated through the HEX protocol. the pair's first
// value is the timestamp the respective info was last received. if it is
@ -59,7 +60,7 @@ struct veShuntStruct : veStruct {
int32_t SOC; // State-of-charge
uint32_t TTG; // Time-to-go
bool ALARM; // Alarm condition active
uint32_t AR; // Alarm Reason
uint16_t alarmReason_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
@ -78,6 +79,7 @@ struct veShuntStruct : veStruct {
int32_t H16; // Maximum auxiliary (battery) voltage
int32_t H17; // Amount of discharged energy
int32_t H18; // Amount of charged energy
int8_t dcMonitorMode_MON; // DC monitor mode
};
enum class VeDirectHexCommand : uint8_t {
@ -120,7 +122,9 @@ enum class VeDirectHexRegister : uint16_t {
SmartBatterySenseTemperature = 0xEDEC,
NetworkInfo = 0x200D,
NetworkMode = 0x200E,
NetworkStatus = 0x200F
NetworkStatus = 0x200F,
HistoryTotal = 0x104F,
HistoryMPPTD30 = 0x10BE
};
struct VeDirectHexData {

View File

@ -104,10 +104,10 @@ void VeDirectFrameHandler<T>::loop()
_lastByteMillis = millis();
}
// there will never be a large gap between two bytes of the same frame.
// there will never be a large gap between two bytes.
// if such a large gap is observed, reset the state machine so it tries
// to decode a new frame once more data arrives.
if (State::IDLE != _state && (millis() - _lastByteMillis) > 500) {
// to decode a new frame / hex messages once more data arrives.
if ((State::IDLE != _state) && ((millis() - _lastByteMillis) > 500)) {
_msgOut->printf("%s Resetting state machine (was %d) after timeout\r\n",
_logId, static_cast<unsigned>(_state));
if (_verboseLogging) { dumpDebugBuffer(); }
@ -236,27 +236,27 @@ void VeDirectFrameHandler<T>::processTextData(std::string const& name, std::stri
if (processTextDataDerived(name, value)) { return; }
if (name == "PID") {
_tmpFrame.PID = strtol(value.c_str(), nullptr, 0);
_tmpFrame.productID_PID = strtol(value.c_str(), nullptr, 0);
return;
}
if (name == "SER") {
strcpy(_tmpFrame.SER, value.c_str());
strcpy(_tmpFrame.serialNr_SER, value.c_str());
return;
}
if (name == "FW") {
strcpy(_tmpFrame.FW, value.c_str());
strcpy(_tmpFrame.firmwareNr_FW, value.c_str());
return;
}
if (name == "V") {
_tmpFrame.V = round(atof(value.c_str()) / 10.0) / 100.0;
_tmpFrame.batteryVoltage_V_mV = atol(value.c_str());
return;
}
if (name == "I") {
_tmpFrame.I = round(atof(value.c_str()) / 10.0) / 100.0;
_tmpFrame.batteryCurrent_I_mA = atol(value.c_str());
return;
}
@ -307,7 +307,10 @@ typename VeDirectFrameHandler<T>::State VeDirectFrameHandler<T>::hexRxEvent(uint
template<typename T>
bool VeDirectFrameHandler<T>::isDataValid() const
{
return strlen(_tmpFrame.SER) > 0 && _lastUpdate > 0 && (millis() - _lastUpdate) < (10 * 1000);
// VE.Direct text frame data is valid if we receive a device serialnumber and
// the data is not older as 10 seconds
// we accept a glitch where the data is valid for ten seconds when serialNr_SER != "" and (millis() - _lastUpdate) overflows
return strlen(_tmpFrame.serialNr_SER) > 0 && (millis() - _lastUpdate) < (10 * 1000);
}
template<typename T>

View File

@ -74,7 +74,7 @@ private:
char _value[VE_MAX_VALUE_LEN]; // buffer for the field value
std::array<uint8_t, 512> _debugBuffer;
unsigned _debugIn;
uint32_t _lastByteMillis;
uint32_t _lastByteMillis; // time of last parsed byte
/**
* not every frame contains every value the device is communicating, i.e.,

View File

@ -93,6 +93,12 @@ bool VeDirectFrameHandler<T>::disassembleHexData(VeDirectHexData &data) {
case Response::ASYNC:
data.addr = static_cast<VeDirectHexRegister>(AsciiHexLE2Int(buffer+2, 4));
// future option: Up to now we do not use historical data
if ((data.addr >= VeDirectHexRegister::HistoryTotal) && (data.addr <= VeDirectHexRegister::HistoryMPPTD30)) {
state = true;
break;
}
// future option: to analyse the flags here?
data.flags = AsciiHexLE2Int(buffer+6, 2);

View File

@ -19,56 +19,60 @@ void VeDirectMpptController::init(int8_t rx, int8_t tx, Print* msgOut, bool verb
bool VeDirectMpptController::processTextDataDerived(std::string const& name, std::string const& value)
{
if (name == "IL") {
_tmpFrame.loadCurrent_IL_mA = atol(value.c_str());
return true;
}
if (name == "LOAD") {
_tmpFrame.LOAD = (value == "ON");
_tmpFrame.loadOutputState_LOAD = (value == "ON");
return true;
}
if (name == "CS") {
_tmpFrame.CS = atoi(value.c_str());
_tmpFrame.currentState_CS = atoi(value.c_str());
return true;
}
if (name == "ERR") {
_tmpFrame.ERR = atoi(value.c_str());
_tmpFrame.errorCode_ERR = atoi(value.c_str());
return true;
}
if (name == "OR") {
_tmpFrame.OR = strtol(value.c_str(), nullptr, 0);
_tmpFrame.offReason_OR = strtol(value.c_str(), nullptr, 0);
return true;
}
if (name == "MPPT") {
_tmpFrame.MPPT = atoi(value.c_str());
_tmpFrame.stateOfTracker_MPPT = atoi(value.c_str());
return true;
}
if (name == "HSDS") {
_tmpFrame.HSDS = atoi(value.c_str());
_tmpFrame.daySequenceNr_HSDS = atoi(value.c_str());
return true;
}
if (name == "VPV") {
_tmpFrame.VPV = round(atof(value.c_str()) / 10.0) / 100.0;
_tmpFrame.panelVoltage_VPV_mV = atol(value.c_str());
return true;
}
if (name == "PPV") {
_tmpFrame.PPV = atoi(value.c_str());
_tmpFrame.panelPower_PPV_W = atoi(value.c_str());
return true;
}
if (name == "H19") {
_tmpFrame.H19 = atof(value.c_str()) / 100.0;
_tmpFrame.yieldTotal_H19_Wh = atol(value.c_str()) * 10;
return true;
}
if (name == "H20") {
_tmpFrame.H20 = atof(value.c_str()) / 100.0;
_tmpFrame.yieldToday_H20_Wh = atol(value.c_str()) * 10;
return true;
}
if (name == "H21") {
_tmpFrame.H21 = atoi(value.c_str());
_tmpFrame.maxPowerToday_H21_W = atoi(value.c_str());
return true;
}
if (name == "H22") {
_tmpFrame.H22 = atof(value.c_str()) / 100.0;
_tmpFrame.yieldYesterday_H22_Wh = atol(value.c_str()) * 10;
return true;
}
if (name == "H23") {
_tmpFrame.H23 = atoi(value.c_str());
_tmpFrame.maxPowerYesterday_H23_W = atoi(value.c_str());
return true;
}
@ -80,15 +84,15 @@ bool VeDirectMpptController::processTextDataDerived(std::string const& name, std
* This function is called at the end of the received frame.
*/
void VeDirectMpptController::frameValidEvent() {
_tmpFrame.P = _tmpFrame.V * _tmpFrame.I;
_tmpFrame.batteryOutputPower_W = static_cast<int16_t>(_tmpFrame.batteryVoltage_V_mV * _tmpFrame.batteryCurrent_I_mA / 1000000);
if (_tmpFrame.VPV > 0) {
_tmpFrame.IPV = _tmpFrame.PPV / _tmpFrame.VPV;
if ((_tmpFrame.panelVoltage_VPV_mV > 0) && (_tmpFrame.panelPower_PPV_W >= 1)) {
_tmpFrame.panelCurrent_mA = static_cast<uint32_t>(_tmpFrame.panelPower_PPV_W * 1000000) / _tmpFrame.panelVoltage_VPV_mV;
}
if (_tmpFrame.PPV > 0) {
_efficiency.addNumber(static_cast<float>(_tmpFrame.P * 100) / _tmpFrame.PPV);
_tmpFrame.E = _efficiency.getAverage();
if (_tmpFrame.panelPower_PPV_W > 0) {
_efficiency.addNumber(static_cast<float>(_tmpFrame.batteryOutputPower_W * 100) / _tmpFrame.panelPower_PPV_W);
_tmpFrame.mpptEfficiency_Percent = _efficiency.getAverage();
}
if (!_canSend) { return; }
@ -98,7 +102,7 @@ void VeDirectMpptController::frameValidEvent() {
// charger periodically sends human readable (TEXT) data to the serial port. For firmware
// versions v1.53 and above, the charger always periodically sends TEXT data to the serial port.
// --> We just use hex commandes for firmware >= 1.53 to keep text messages alive
if (atoi(_tmpFrame.FW) < 153) { return; }
if (atoi(_tmpFrame.firmwareNr_FW) < 153) { return; }
using Command = VeDirectHexCommand;
using Register = VeDirectHexRegister;

View File

@ -35,6 +35,10 @@ bool VeDirectShuntController::processTextDataDerived(std::string const& name, st
_tmpFrame.ALARM = (value == "ON");
return true;
}
if (name == "AR") {
_tmpFrame.alarmReason_AR = atoi(value.c_str());
return true;
}
if (name == "H1") {
_tmpFrame.H1 = atoi(value.c_str());
return true;
@ -107,6 +111,14 @@ bool VeDirectShuntController::processTextDataDerived(std::string const& name, st
_tmpFrame.H18 = atoi(value.c_str());
return true;
}
if (name == "BMV") {
// This field contains a textual description of the BMV model,
// for example 602S or 702. It is deprecated, refer to the field PID instead.
return true;
}
if (name == "MON") {
_tmpFrame.dcMonitorMode_MON = static_cast<int8_t>(atoi(value.c_str()));
return true;
}
return false;
}

View File

@ -374,10 +374,10 @@ void JkBmsBatteryStats::updateFrom(JkBms::DataPointContainer const& dp)
}
void VictronSmartShuntStats::updateFrom(VeDirectShuntController::data_t const& shuntData) {
BatteryStats::setVoltage(shuntData.V, millis());
BatteryStats::setVoltage(shuntData.batteryVoltage_V_mV / 1000.0, millis());
BatteryStats::setSoC(static_cast<float>(shuntData.SOC) / 10, 1/*precision*/, millis());
_current = shuntData.I;
_current = static_cast<float>(shuntData.batteryCurrent_I_mA) / 1000;
_modelName = shuntData.getPidAsString().data();
_chargeCycles = shuntData.H4;
_timeToGo = shuntData.TTG / 60;
@ -390,11 +390,11 @@ void VictronSmartShuntStats::updateFrom(VeDirectShuntController::data_t const& s
_consumedAmpHours = static_cast<float>(shuntData.CE) / 1000;
_lastFullCharge = shuntData.H9 / 60;
// shuntData.AR is a bitfield, so we need to check each bit individually
_alarmLowVoltage = shuntData.AR & 1;
_alarmHighVoltage = shuntData.AR & 2;
_alarmLowSOC = shuntData.AR & 4;
_alarmLowTemperature = shuntData.AR & 32;
_alarmHighTemperature = shuntData.AR & 64;
_alarmLowVoltage = shuntData.alarmReason_AR & 1;
_alarmHighVoltage = shuntData.alarmReason_AR & 2;
_alarmLowSOC = shuntData.alarmReason_AR & 4;
_alarmLowTemperature = shuntData.alarmReason_AR & 32;
_alarmHighTemperature = shuntData.alarmReason_AR & 64;
_lastUpdate = VeDirectShunt.getLastUpdate();
}

View File

@ -95,7 +95,7 @@ void MqttHandleVedirectHassClass::publishSensor(const char *caption, const char
const char *unitOfMeasurement,
const VeDirectMpptController::data_t &mpptData)
{
String serial = mpptData.SER;
String serial = mpptData.serialNr_SER;
String sensorId = caption;
sensorId.replace(" ", "_");
@ -153,7 +153,7 @@ void MqttHandleVedirectHassClass::publishBinarySensor(const char *caption, const
const char *payload_on, const char *payload_off,
const VeDirectMpptController::data_t &mpptData)
{
String serial = mpptData.SER;
String serial = mpptData.serialNr_SER;
String sensorId = caption;
sensorId.replace(" ", "_");
@ -198,7 +198,7 @@ void MqttHandleVedirectHassClass::publishBinarySensor(const char *caption, const
void MqttHandleVedirectHassClass::createDeviceInfo(JsonObject &object,
const VeDirectMpptController::data_t &mpptData)
{
String serial = mpptData.SER;
String serial = mpptData.serialNr_SER;
object["name"] = "Victron(" + serial + ")";
object["ids"] = serial;
object["cu"] = String("http://") + NetworkSettings.localIP().toString();

View File

@ -62,10 +62,10 @@ void MqttHandleVedirectClass::loop()
std::optional<VeDirectMpptController::data_t> optMpptData = VictronMppt.getData(idx);
if (!optMpptData.has_value()) { continue; }
auto const& kvFrame = _kvFrames[optMpptData->SER];
auto const& kvFrame = _kvFrames[optMpptData->serialNr_SER];
publish_mppt_data(*optMpptData, kvFrame);
if (!_PublishFull) {
_kvFrames[optMpptData->SER] = *optMpptData;
_kvFrames[optMpptData->serialNr_SER] = *optMpptData;
}
}
@ -100,7 +100,7 @@ void MqttHandleVedirectClass::publish_mppt_data(const VeDirectMpptController::da
const VeDirectMpptController::data_t &previousData) const {
String value;
String topic = "victron/";
topic.concat(currentData.SER);
topic.concat(currentData.serialNr_SER);
topic.concat("/");
#define PUBLISH(sm, t, val) \
@ -108,26 +108,26 @@ void MqttHandleVedirectClass::publish_mppt_data(const VeDirectMpptController::da
MqttSettings.publish(topic + t, String(val)); \
}
PUBLISH(PID, "PID", currentData.getPidAsString().data());
PUBLISH(SER, "SER", currentData.SER);
PUBLISH(FW, "FW", currentData.FW);
PUBLISH(LOAD, "LOAD", (currentData.LOAD ? "ON" : "OFF"));
PUBLISH(CS, "CS", currentData.getCsAsString().data());
PUBLISH(ERR, "ERR", currentData.getErrAsString().data());
PUBLISH(OR, "OR", currentData.getOrAsString().data());
PUBLISH(MPPT, "MPPT", currentData.getMpptAsString().data());
PUBLISH(HSDS, "HSDS", currentData.HSDS);
PUBLISH(V, "V", currentData.V);
PUBLISH(I, "I", currentData.I);
PUBLISH(P, "P", currentData.P);
PUBLISH(VPV, "VPV", currentData.VPV);
PUBLISH(IPV, "IPV", currentData.IPV);
PUBLISH(PPV, "PPV", currentData.PPV);
PUBLISH(E, "E", currentData.E);
PUBLISH(H19, "H19", currentData.H19);
PUBLISH(H20, "H20", currentData.H20);
PUBLISH(H21, "H21", currentData.H21);
PUBLISH(H22, "H22", currentData.H22);
PUBLISH(H23, "H23", currentData.H23);
PUBLISH(productID_PID, "PID", currentData.getPidAsString().data());
PUBLISH(serialNr_SER, "SER", currentData.serialNr_SER);
PUBLISH(firmwareNr_FW, "FW", currentData.firmwareNr_FW);
PUBLISH(loadOutputState_LOAD, "LOAD", (currentData.loadOutputState_LOAD ? "ON" : "OFF"));
PUBLISH(currentState_CS, "CS", currentData.getCsAsString().data());
PUBLISH(errorCode_ERR, "ERR", currentData.getErrAsString().data());
PUBLISH(offReason_OR, "OR", currentData.getOrAsString().data());
PUBLISH(stateOfTracker_MPPT, "MPPT", currentData.getMpptAsString().data());
PUBLISH(daySequenceNr_HSDS, "HSDS", currentData.daySequenceNr_HSDS);
PUBLISH(batteryVoltage_V_mV, "V", currentData.batteryVoltage_V_mV / 1000.0);
PUBLISH(batteryCurrent_I_mA, "I", currentData.batteryCurrent_I_mA / 1000.0);
PUBLISH(batteryOutputPower_W, "P", currentData.batteryOutputPower_W);
PUBLISH(panelVoltage_VPV_mV, "VPV", currentData.panelVoltage_VPV_mV / 1000.0);
PUBLISH(panelCurrent_mA, "IPV", currentData.panelCurrent_mA / 1000.0);
PUBLISH(panelPower_PPV_W, "PPV", currentData.panelPower_PPV_W);
PUBLISH(mpptEfficiency_Percent, "E", currentData.mpptEfficiency_Percent);
PUBLISH(yieldTotal_H19_Wh, "H19", currentData.yieldTotal_H19_Wh / 1000.0);
PUBLISH(yieldToday_H20_Wh, "H20", currentData.yieldToday_H20_Wh / 1000.0);
PUBLISH(maxPowerToday_H21_W, "H21", currentData.maxPowerToday_H21_W);
PUBLISH(yieldYesterday_H22_Wh, "H22", currentData.yieldYesterday_H22_Wh / 1000.0);
PUBLISH(maxPowerYesterday_H23_W, "H23", currentData.maxPowerYesterday_H23_W);
#undef PUBLILSH
}

View File

@ -148,10 +148,10 @@ int32_t VictronMpptClass::getPowerOutputWatts() const
// the calculated efficiency of the connected charge controller.
auto networkPower = upController->getData().NetworkTotalDcInputPowerMilliWatts;
if (networkPower.first > 0) {
return static_cast<int32_t>(networkPower.second / 1000.0 * upController->getData().E / 100);
return static_cast<int32_t>(networkPower.second / 1000.0 * upController->getData().mpptEfficiency_Percent / 100);
}
sum += upController->getData().P;
sum += upController->getData().batteryOutputPower_W;
}
return sum;
@ -172,7 +172,7 @@ int32_t VictronMpptClass::getPanelPowerWatts() const
return static_cast<int32_t>(networkPower.second / 1000.0);
}
sum += upController->getData().PPV;
sum += upController->getData().panelPower_PPV_W;
}
return sum;
@ -184,7 +184,7 @@ float VictronMpptClass::getYieldTotal() const
for (const auto& upController : _controllers) {
if (!upController->isDataValid()) { continue; }
sum += upController->getData().H19;
sum += upController->getData().yieldTotal_H19_Wh / 1000.0;
}
return sum;
@ -196,7 +196,7 @@ float VictronMpptClass::getYieldDay() const
for (const auto& upController : _controllers) {
if (!upController->isDataValid()) { continue; }
sum += upController->getData().H20;
sum += upController->getData().yieldToday_H20_Wh / 1000.0;
}
return sum;
@ -208,7 +208,7 @@ float VictronMpptClass::getOutputVoltage() const
for (const auto& upController : _controllers) {
if (!upController->isDataValid()) { continue; }
float volts = upController->getData().V;
float volts = upController->getData().batteryVoltage_V_mV / 1000.0;
if (min == -1) { min = volts; }
min = std::min(min, volts);
}

View File

@ -128,7 +128,7 @@ void WebApiWsVedirectLiveClass::generateJsonResponse(JsonVariant& root, bool ful
if (!fullUpdate && !hasUpdate(idx)) { continue; }
String serial(optMpptData->SER);
String serial(optMpptData->serialNr_SER);
if (serial.isEmpty()) { continue; } // serial required as index
const JsonObject &nested = array.createNestedObject(serial);
@ -147,17 +147,17 @@ void WebApiWsVedirectLiveClass::generateJsonResponse(JsonVariant& root, bool ful
void WebApiWsVedirectLiveClass::populateJson(const JsonObject &root, const VeDirectMpptController::data_t &mpptData) {
root["product_id"] = mpptData.getPidAsString();
root["firmware_version"] = String(mpptData.FW);
root["firmware_version"] = String(mpptData.firmwareNr_FW);
const JsonObject &values = root.createNestedObject("values");
const JsonObject &device = values.createNestedObject("device");
device["LOAD"] = mpptData.LOAD ? "ON" : "OFF";
device["LOAD"] = mpptData.loadOutputState_LOAD ? "ON" : "OFF";
device["CS"] = mpptData.getCsAsString();
device["MPPT"] = mpptData.getMpptAsString();
device["OR"] = mpptData.getOrAsString();
device["ERR"] = mpptData.getErrAsString();
device["HSDS"]["v"] = mpptData.HSDS;
device["HSDS"]["v"] = mpptData.daySequenceNr_HSDS;
device["HSDS"]["u"] = "d";
if (mpptData.MpptTemperatureMilliCelsius.first > 0) {
device["MpptTemperature"]["v"] = mpptData.MpptTemperatureMilliCelsius.second / 1000.0;
@ -166,16 +166,16 @@ void WebApiWsVedirectLiveClass::populateJson(const JsonObject &root, const VeDir
}
const JsonObject &output = values.createNestedObject("output");
output["P"]["v"] = mpptData.P;
output["P"]["v"] = mpptData.batteryOutputPower_W;
output["P"]["u"] = "W";
output["P"]["d"] = 0;
output["V"]["v"] = mpptData.V;
output["V"]["v"] = mpptData.batteryVoltage_V_mV / 1000.0;
output["V"]["u"] = "V";
output["V"]["d"] = 2;
output["I"]["v"] = mpptData.I;
output["I"]["v"] = mpptData.batteryCurrent_I_mA / 1000.0;
output["I"]["u"] = "A";
output["I"]["d"] = 2;
output["E"]["v"] = mpptData.E;
output["E"]["v"] = mpptData.mpptEfficiency_Percent;
output["E"]["u"] = "%";
output["E"]["d"] = 1;
@ -185,28 +185,28 @@ void WebApiWsVedirectLiveClass::populateJson(const JsonObject &root, const VeDir
input["NetworkPower"]["u"] = "W";
input["NetworkPower"]["d"] = "0";
}
input["PPV"]["v"] = mpptData.PPV;
input["PPV"]["v"] = mpptData.panelPower_PPV_W;
input["PPV"]["u"] = "W";
input["PPV"]["d"] = 0;
input["VPV"]["v"] = mpptData.VPV;
input["VPV"]["v"] = mpptData.panelVoltage_VPV_mV / 1000.0;
input["VPV"]["u"] = "V";
input["VPV"]["d"] = 2;
input["IPV"]["v"] = mpptData.IPV;
input["IPV"]["v"] = mpptData.panelCurrent_mA / 1000.0;
input["IPV"]["u"] = "A";
input["IPV"]["d"] = 2;
input["YieldToday"]["v"] = mpptData.H20;
input["YieldToday"]["v"] = mpptData.yieldToday_H20_Wh / 1000.0;
input["YieldToday"]["u"] = "kWh";
input["YieldToday"]["d"] = 3;
input["YieldYesterday"]["v"] = mpptData.H22;
input["YieldYesterday"]["v"] = mpptData.yieldYesterday_H22_Wh / 1000.0;
input["YieldYesterday"]["u"] = "kWh";
input["YieldYesterday"]["d"] = 3;
input["YieldTotal"]["v"] = mpptData.H19;
input["YieldTotal"]["v"] = mpptData.yieldTotal_H19_Wh / 1000.0;
input["YieldTotal"]["u"] = "kWh";
input["YieldTotal"]["d"] = 3;
input["MaximumPowerToday"]["v"] = mpptData.H21;
input["MaximumPowerToday"]["v"] = mpptData.maxPowerToday_H21_W;
input["MaximumPowerToday"]["u"] = "W";
input["MaximumPowerToday"]["d"] = 0;
input["MaximumPowerYesterday"]["v"] = mpptData.H23;
input["MaximumPowerYesterday"]["v"] = mpptData.maxPowerYesterday_H23_W;
input["MaximumPowerYesterday"]["u"] = "W";
input["MaximumPowerYesterday"]["d"] = 0;
}