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" }, { 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" } { 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" } { 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" } { 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" } { 0x00000100, "Analysing input voltage" }
}; };
return getAsString(values, OR); return getAsString(values, offReason_OR);
} }
frozen::string const& VeDirectHexData::getResponseAsString() const 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 #define VE_MAX_HEX_LEN 100 // Maximum size of hex frame - max payload 34 byte (=68 char) + safe buffer
typedef struct { typedef struct {
uint16_t PID = 0; // product id uint16_t productID_PID = 0; // product id
char SER[VE_MAX_VALUE_LEN]; // serial number char serialNr_SER[VE_MAX_VALUE_LEN]; // serial number
char FW[VE_MAX_VALUE_LEN]; // firmware release number char firmwareNr_FW[VE_MAX_VALUE_LEN]; // firmware release number
float V = 0; // battery voltage in V uint32_t batteryVoltage_V_mV = 0; // battery voltage in mV
float I = 0; // battery current in A int32_t batteryCurrent_I_mA = 0; // battery current in mA (can be negative)
float E = 0; // efficiency in percent (calculated, moving average) float mpptEfficiency_Percent = 0; // efficiency in percent (calculated, moving average)
frozen::string const& getPidAsString() const; // product ID as string frozen::string const& getPidAsString() const; // product ID as string
} veStruct; } veStruct;
struct veMpptStruct : veStruct { struct veMpptStruct : veStruct {
uint8_t MPPT; // state of MPP tracker uint8_t stateOfTracker_MPPT; // state of MPP tracker
int32_t PPV; // panel power in W uint16_t panelPower_PPV_W; // panel power in W
int32_t P; // battery output power in W (calculated) uint32_t panelVoltage_VPV_mV; // panel voltage in mV
float VPV; // panel voltage in V uint32_t panelCurrent_mA; // panel current in mA (calculated)
float IPV; // panel current in A (calculated) int16_t batteryOutputPower_W; // battery output power in W (calculated, can be negative if load output is used)
bool LOAD; // virtual load output state (on if battery voltage reaches upper limit, off if battery reaches lower limit) uint32_t loadCurrent_IL_mA; // Load current in mA (Available only for models with a load output)
uint8_t CS; // current state of operation e.g. OFF or Bulk bool loadOutputState_LOAD; // virtual load output state (on if battery voltage reaches upper limit, off if battery reaches lower limit)
uint8_t ERR; // error code uint8_t currentState_CS; // current state of operation e.g. OFF or Bulk
uint32_t OR; // off reason uint8_t errorCode_ERR; // error code
uint32_t HSDS; // day sequence number 1...365 uint32_t offReason_OR; // off reason
float H19; // yield total kWh uint16_t daySequenceNr_HSDS; // day sequence number 1...365
float H20; // yield today kWh uint32_t yieldTotal_H19_Wh; // yield total resetable Wh
int32_t H21; // maximum power today W uint32_t yieldToday_H20_Wh; // yield today Wh
float H22; // yield yesterday kWh uint16_t maxPowerToday_H21_W; // maximum power today W
int32_t H23; // maximum power yesterday 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 // 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 // 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 int32_t SOC; // State-of-charge
uint32_t TTG; // Time-to-go uint32_t TTG; // Time-to-go
bool ALARM; // Alarm condition active 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 H1; // Depth of the deepest discharge
int32_t H2; // Depth of the last discharge int32_t H2; // Depth of the last discharge
int32_t H3; // Depth of the average 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 H16; // Maximum auxiliary (battery) voltage
int32_t H17; // Amount of discharged energy int32_t H17; // Amount of discharged energy
int32_t H18; // Amount of charged energy int32_t H18; // Amount of charged energy
int8_t dcMonitorMode_MON; // DC monitor mode
}; };
enum class VeDirectHexCommand : uint8_t { enum class VeDirectHexCommand : uint8_t {
@ -120,7 +122,9 @@ enum class VeDirectHexRegister : uint16_t {
SmartBatterySenseTemperature = 0xEDEC, SmartBatterySenseTemperature = 0xEDEC,
NetworkInfo = 0x200D, NetworkInfo = 0x200D,
NetworkMode = 0x200E, NetworkMode = 0x200E,
NetworkStatus = 0x200F NetworkStatus = 0x200F,
HistoryTotal = 0x104F,
HistoryMPPTD30 = 0x10BE
}; };
struct VeDirectHexData { struct VeDirectHexData {

View File

@ -104,10 +104,10 @@ void VeDirectFrameHandler<T>::loop()
_lastByteMillis = millis(); _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 // if such a large gap is observed, reset the state machine so it tries
// to decode a new frame once more data arrives. // to decode a new frame / hex messages once more data arrives.
if (State::IDLE != _state && (millis() - _lastByteMillis) > 500) { if ((State::IDLE != _state) && ((millis() - _lastByteMillis) > 500)) {
_msgOut->printf("%s Resetting state machine (was %d) after timeout\r\n", _msgOut->printf("%s Resetting state machine (was %d) after timeout\r\n",
_logId, static_cast<unsigned>(_state)); _logId, static_cast<unsigned>(_state));
if (_verboseLogging) { dumpDebugBuffer(); } if (_verboseLogging) { dumpDebugBuffer(); }
@ -236,27 +236,27 @@ void VeDirectFrameHandler<T>::processTextData(std::string const& name, std::stri
if (processTextDataDerived(name, value)) { return; } if (processTextDataDerived(name, value)) { return; }
if (name == "PID") { if (name == "PID") {
_tmpFrame.PID = strtol(value.c_str(), nullptr, 0); _tmpFrame.productID_PID = strtol(value.c_str(), nullptr, 0);
return; return;
} }
if (name == "SER") { if (name == "SER") {
strcpy(_tmpFrame.SER, value.c_str()); strcpy(_tmpFrame.serialNr_SER, value.c_str());
return; return;
} }
if (name == "FW") { if (name == "FW") {
strcpy(_tmpFrame.FW, value.c_str()); strcpy(_tmpFrame.firmwareNr_FW, value.c_str());
return; return;
} }
if (name == "V") { if (name == "V") {
_tmpFrame.V = round(atof(value.c_str()) / 10.0) / 100.0; _tmpFrame.batteryVoltage_V_mV = atol(value.c_str());
return; return;
} }
if (name == "I") { if (name == "I") {
_tmpFrame.I = round(atof(value.c_str()) / 10.0) / 100.0; _tmpFrame.batteryCurrent_I_mA = atol(value.c_str());
return; return;
} }
@ -307,7 +307,10 @@ typename VeDirectFrameHandler<T>::State VeDirectFrameHandler<T>::hexRxEvent(uint
template<typename T> template<typename T>
bool VeDirectFrameHandler<T>::isDataValid() const 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> template<typename T>

View File

@ -74,7 +74,7 @@ private:
char _value[VE_MAX_VALUE_LEN]; // buffer for the field value char _value[VE_MAX_VALUE_LEN]; // buffer for the field value
std::array<uint8_t, 512> _debugBuffer; std::array<uint8_t, 512> _debugBuffer;
unsigned _debugIn; 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., * 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: case Response::ASYNC:
data.addr = static_cast<VeDirectHexRegister>(AsciiHexLE2Int(buffer+2, 4)); 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? // future option: to analyse the flags here?
data.flags = AsciiHexLE2Int(buffer+6, 2); 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) 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") { if (name == "LOAD") {
_tmpFrame.LOAD = (value == "ON"); _tmpFrame.loadOutputState_LOAD = (value == "ON");
return true; return true;
} }
if (name == "CS") { if (name == "CS") {
_tmpFrame.CS = atoi(value.c_str()); _tmpFrame.currentState_CS = atoi(value.c_str());
return true; return true;
} }
if (name == "ERR") { if (name == "ERR") {
_tmpFrame.ERR = atoi(value.c_str()); _tmpFrame.errorCode_ERR = atoi(value.c_str());
return true; return true;
} }
if (name == "OR") { if (name == "OR") {
_tmpFrame.OR = strtol(value.c_str(), nullptr, 0); _tmpFrame.offReason_OR = strtol(value.c_str(), nullptr, 0);
return true; return true;
} }
if (name == "MPPT") { if (name == "MPPT") {
_tmpFrame.MPPT = atoi(value.c_str()); _tmpFrame.stateOfTracker_MPPT = atoi(value.c_str());
return true; return true;
} }
if (name == "HSDS") { if (name == "HSDS") {
_tmpFrame.HSDS = atoi(value.c_str()); _tmpFrame.daySequenceNr_HSDS = atoi(value.c_str());
return true; return true;
} }
if (name == "VPV") { if (name == "VPV") {
_tmpFrame.VPV = round(atof(value.c_str()) / 10.0) / 100.0; _tmpFrame.panelVoltage_VPV_mV = atol(value.c_str());
return true; return true;
} }
if (name == "PPV") { if (name == "PPV") {
_tmpFrame.PPV = atoi(value.c_str()); _tmpFrame.panelPower_PPV_W = atoi(value.c_str());
return true; return true;
} }
if (name == "H19") { if (name == "H19") {
_tmpFrame.H19 = atof(value.c_str()) / 100.0; _tmpFrame.yieldTotal_H19_Wh = atol(value.c_str()) * 10;
return true; return true;
} }
if (name == "H20") { if (name == "H20") {
_tmpFrame.H20 = atof(value.c_str()) / 100.0; _tmpFrame.yieldToday_H20_Wh = atol(value.c_str()) * 10;
return true; return true;
} }
if (name == "H21") { if (name == "H21") {
_tmpFrame.H21 = atoi(value.c_str()); _tmpFrame.maxPowerToday_H21_W = atoi(value.c_str());
return true; return true;
} }
if (name == "H22") { if (name == "H22") {
_tmpFrame.H22 = atof(value.c_str()) / 100.0; _tmpFrame.yieldYesterday_H22_Wh = atol(value.c_str()) * 10;
return true; return true;
} }
if (name == "H23") { if (name == "H23") {
_tmpFrame.H23 = atoi(value.c_str()); _tmpFrame.maxPowerYesterday_H23_W = atoi(value.c_str());
return true; 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. * This function is called at the end of the received frame.
*/ */
void VeDirectMpptController::frameValidEvent() { 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) { if ((_tmpFrame.panelVoltage_VPV_mV > 0) && (_tmpFrame.panelPower_PPV_W >= 1)) {
_tmpFrame.IPV = _tmpFrame.PPV / _tmpFrame.VPV; _tmpFrame.panelCurrent_mA = static_cast<uint32_t>(_tmpFrame.panelPower_PPV_W * 1000000) / _tmpFrame.panelVoltage_VPV_mV;
} }
if (_tmpFrame.PPV > 0) { if (_tmpFrame.panelPower_PPV_W > 0) {
_efficiency.addNumber(static_cast<float>(_tmpFrame.P * 100) / _tmpFrame.PPV); _efficiency.addNumber(static_cast<float>(_tmpFrame.batteryOutputPower_W * 100) / _tmpFrame.panelPower_PPV_W);
_tmpFrame.E = _efficiency.getAverage(); _tmpFrame.mpptEfficiency_Percent = _efficiency.getAverage();
} }
if (!_canSend) { return; } if (!_canSend) { return; }
@ -98,7 +102,7 @@ void VeDirectMpptController::frameValidEvent() {
// charger periodically sends human readable (TEXT) data to the serial port. For firmware // 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. // 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 // --> 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 Command = VeDirectHexCommand;
using Register = VeDirectHexRegister; using Register = VeDirectHexRegister;

View File

@ -35,6 +35,10 @@ bool VeDirectShuntController::processTextDataDerived(std::string const& name, st
_tmpFrame.ALARM = (value == "ON"); _tmpFrame.ALARM = (value == "ON");
return true; return true;
} }
if (name == "AR") {
_tmpFrame.alarmReason_AR = atoi(value.c_str());
return true;
}
if (name == "H1") { if (name == "H1") {
_tmpFrame.H1 = atoi(value.c_str()); _tmpFrame.H1 = atoi(value.c_str());
return true; return true;
@ -107,6 +111,14 @@ bool VeDirectShuntController::processTextDataDerived(std::string const& name, st
_tmpFrame.H18 = atoi(value.c_str()); _tmpFrame.H18 = atoi(value.c_str());
return true; 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; return false;
} }

View File

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

View File

@ -95,7 +95,7 @@ void MqttHandleVedirectHassClass::publishSensor(const char *caption, const char
const char *unitOfMeasurement, const char *unitOfMeasurement,
const VeDirectMpptController::data_t &mpptData) const VeDirectMpptController::data_t &mpptData)
{ {
String serial = mpptData.SER; String serial = mpptData.serialNr_SER;
String sensorId = caption; String sensorId = caption;
sensorId.replace(" ", "_"); sensorId.replace(" ", "_");
@ -153,7 +153,7 @@ void MqttHandleVedirectHassClass::publishBinarySensor(const char *caption, const
const char *payload_on, const char *payload_off, const char *payload_on, const char *payload_off,
const VeDirectMpptController::data_t &mpptData) const VeDirectMpptController::data_t &mpptData)
{ {
String serial = mpptData.SER; String serial = mpptData.serialNr_SER;
String sensorId = caption; String sensorId = caption;
sensorId.replace(" ", "_"); sensorId.replace(" ", "_");
@ -198,7 +198,7 @@ void MqttHandleVedirectHassClass::publishBinarySensor(const char *caption, const
void MqttHandleVedirectHassClass::createDeviceInfo(JsonObject &object, void MqttHandleVedirectHassClass::createDeviceInfo(JsonObject &object,
const VeDirectMpptController::data_t &mpptData) const VeDirectMpptController::data_t &mpptData)
{ {
String serial = mpptData.SER; String serial = mpptData.serialNr_SER;
object["name"] = "Victron(" + serial + ")"; object["name"] = "Victron(" + serial + ")";
object["ids"] = serial; object["ids"] = serial;
object["cu"] = String("http://") + NetworkSettings.localIP().toString(); 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); std::optional<VeDirectMpptController::data_t> optMpptData = VictronMppt.getData(idx);
if (!optMpptData.has_value()) { continue; } if (!optMpptData.has_value()) { continue; }
auto const& kvFrame = _kvFrames[optMpptData->SER]; auto const& kvFrame = _kvFrames[optMpptData->serialNr_SER];
publish_mppt_data(*optMpptData, kvFrame); publish_mppt_data(*optMpptData, kvFrame);
if (!_PublishFull) { 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 { const VeDirectMpptController::data_t &previousData) const {
String value; String value;
String topic = "victron/"; String topic = "victron/";
topic.concat(currentData.SER); topic.concat(currentData.serialNr_SER);
topic.concat("/"); topic.concat("/");
#define PUBLISH(sm, t, val) \ #define PUBLISH(sm, t, val) \
@ -108,26 +108,26 @@ void MqttHandleVedirectClass::publish_mppt_data(const VeDirectMpptController::da
MqttSettings.publish(topic + t, String(val)); \ MqttSettings.publish(topic + t, String(val)); \
} }
PUBLISH(PID, "PID", currentData.getPidAsString().data()); PUBLISH(productID_PID, "PID", currentData.getPidAsString().data());
PUBLISH(SER, "SER", currentData.SER); PUBLISH(serialNr_SER, "SER", currentData.serialNr_SER);
PUBLISH(FW, "FW", currentData.FW); PUBLISH(firmwareNr_FW, "FW", currentData.firmwareNr_FW);
PUBLISH(LOAD, "LOAD", (currentData.LOAD ? "ON" : "OFF")); PUBLISH(loadOutputState_LOAD, "LOAD", (currentData.loadOutputState_LOAD ? "ON" : "OFF"));
PUBLISH(CS, "CS", currentData.getCsAsString().data()); PUBLISH(currentState_CS, "CS", currentData.getCsAsString().data());
PUBLISH(ERR, "ERR", currentData.getErrAsString().data()); PUBLISH(errorCode_ERR, "ERR", currentData.getErrAsString().data());
PUBLISH(OR, "OR", currentData.getOrAsString().data()); PUBLISH(offReason_OR, "OR", currentData.getOrAsString().data());
PUBLISH(MPPT, "MPPT", currentData.getMpptAsString().data()); PUBLISH(stateOfTracker_MPPT, "MPPT", currentData.getMpptAsString().data());
PUBLISH(HSDS, "HSDS", currentData.HSDS); PUBLISH(daySequenceNr_HSDS, "HSDS", currentData.daySequenceNr_HSDS);
PUBLISH(V, "V", currentData.V); PUBLISH(batteryVoltage_V_mV, "V", currentData.batteryVoltage_V_mV / 1000.0);
PUBLISH(I, "I", currentData.I); PUBLISH(batteryCurrent_I_mA, "I", currentData.batteryCurrent_I_mA / 1000.0);
PUBLISH(P, "P", currentData.P); PUBLISH(batteryOutputPower_W, "P", currentData.batteryOutputPower_W);
PUBLISH(VPV, "VPV", currentData.VPV); PUBLISH(panelVoltage_VPV_mV, "VPV", currentData.panelVoltage_VPV_mV / 1000.0);
PUBLISH(IPV, "IPV", currentData.IPV); PUBLISH(panelCurrent_mA, "IPV", currentData.panelCurrent_mA / 1000.0);
PUBLISH(PPV, "PPV", currentData.PPV); PUBLISH(panelPower_PPV_W, "PPV", currentData.panelPower_PPV_W);
PUBLISH(E, "E", currentData.E); PUBLISH(mpptEfficiency_Percent, "E", currentData.mpptEfficiency_Percent);
PUBLISH(H19, "H19", currentData.H19); PUBLISH(yieldTotal_H19_Wh, "H19", currentData.yieldTotal_H19_Wh / 1000.0);
PUBLISH(H20, "H20", currentData.H20); PUBLISH(yieldToday_H20_Wh, "H20", currentData.yieldToday_H20_Wh / 1000.0);
PUBLISH(H21, "H21", currentData.H21); PUBLISH(maxPowerToday_H21_W, "H21", currentData.maxPowerToday_H21_W);
PUBLISH(H22, "H22", currentData.H22); PUBLISH(yieldYesterday_H22_Wh, "H22", currentData.yieldYesterday_H22_Wh / 1000.0);
PUBLISH(H23, "H23", currentData.H23); PUBLISH(maxPowerYesterday_H23_W, "H23", currentData.maxPowerYesterday_H23_W);
#undef PUBLILSH #undef PUBLILSH
} }

View File

@ -148,10 +148,10 @@ int32_t VictronMpptClass::getPowerOutputWatts() const
// the calculated efficiency of the connected charge controller. // the calculated efficiency of the connected charge controller.
auto networkPower = upController->getData().NetworkTotalDcInputPowerMilliWatts; auto networkPower = upController->getData().NetworkTotalDcInputPowerMilliWatts;
if (networkPower.first > 0) { 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; return sum;
@ -172,7 +172,7 @@ int32_t VictronMpptClass::getPanelPowerWatts() const
return static_cast<int32_t>(networkPower.second / 1000.0); return static_cast<int32_t>(networkPower.second / 1000.0);
} }
sum += upController->getData().PPV; sum += upController->getData().panelPower_PPV_W;
} }
return sum; return sum;
@ -184,7 +184,7 @@ float VictronMpptClass::getYieldTotal() const
for (const auto& upController : _controllers) { for (const auto& upController : _controllers) {
if (!upController->isDataValid()) { continue; } if (!upController->isDataValid()) { continue; }
sum += upController->getData().H19; sum += upController->getData().yieldTotal_H19_Wh / 1000.0;
} }
return sum; return sum;
@ -196,7 +196,7 @@ float VictronMpptClass::getYieldDay() const
for (const auto& upController : _controllers) { for (const auto& upController : _controllers) {
if (!upController->isDataValid()) { continue; } if (!upController->isDataValid()) { continue; }
sum += upController->getData().H20; sum += upController->getData().yieldToday_H20_Wh / 1000.0;
} }
return sum; return sum;
@ -208,7 +208,7 @@ float VictronMpptClass::getOutputVoltage() const
for (const auto& upController : _controllers) { for (const auto& upController : _controllers) {
if (!upController->isDataValid()) { continue; } if (!upController->isDataValid()) { continue; }
float volts = upController->getData().V; float volts = upController->getData().batteryVoltage_V_mV / 1000.0;
if (min == -1) { min = volts; } if (min == -1) { min = volts; }
min = std::min(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; } if (!fullUpdate && !hasUpdate(idx)) { continue; }
String serial(optMpptData->SER); String serial(optMpptData->serialNr_SER);
if (serial.isEmpty()) { continue; } // serial required as index if (serial.isEmpty()) { continue; } // serial required as index
const JsonObject &nested = array.createNestedObject(serial); 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) { void WebApiWsVedirectLiveClass::populateJson(const JsonObject &root, const VeDirectMpptController::data_t &mpptData) {
root["product_id"] = mpptData.getPidAsString(); 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 &values = root.createNestedObject("values");
const JsonObject &device = values.createNestedObject("device"); const JsonObject &device = values.createNestedObject("device");
device["LOAD"] = mpptData.LOAD ? "ON" : "OFF"; device["LOAD"] = mpptData.loadOutputState_LOAD ? "ON" : "OFF";
device["CS"] = mpptData.getCsAsString(); device["CS"] = mpptData.getCsAsString();
device["MPPT"] = mpptData.getMpptAsString(); device["MPPT"] = mpptData.getMpptAsString();
device["OR"] = mpptData.getOrAsString(); device["OR"] = mpptData.getOrAsString();
device["ERR"] = mpptData.getErrAsString(); device["ERR"] = mpptData.getErrAsString();
device["HSDS"]["v"] = mpptData.HSDS; device["HSDS"]["v"] = mpptData.daySequenceNr_HSDS;
device["HSDS"]["u"] = "d"; device["HSDS"]["u"] = "d";
if (mpptData.MpptTemperatureMilliCelsius.first > 0) { if (mpptData.MpptTemperatureMilliCelsius.first > 0) {
device["MpptTemperature"]["v"] = mpptData.MpptTemperatureMilliCelsius.second / 1000.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"); const JsonObject &output = values.createNestedObject("output");
output["P"]["v"] = mpptData.P; output["P"]["v"] = mpptData.batteryOutputPower_W;
output["P"]["u"] = "W"; output["P"]["u"] = "W";
output["P"]["d"] = 0; output["P"]["d"] = 0;
output["V"]["v"] = mpptData.V; output["V"]["v"] = mpptData.batteryVoltage_V_mV / 1000.0;
output["V"]["u"] = "V"; output["V"]["u"] = "V";
output["V"]["d"] = 2; output["V"]["d"] = 2;
output["I"]["v"] = mpptData.I; output["I"]["v"] = mpptData.batteryCurrent_I_mA / 1000.0;
output["I"]["u"] = "A"; output["I"]["u"] = "A";
output["I"]["d"] = 2; output["I"]["d"] = 2;
output["E"]["v"] = mpptData.E; output["E"]["v"] = mpptData.mpptEfficiency_Percent;
output["E"]["u"] = "%"; output["E"]["u"] = "%";
output["E"]["d"] = 1; output["E"]["d"] = 1;
@ -185,28 +185,28 @@ void WebApiWsVedirectLiveClass::populateJson(const JsonObject &root, const VeDir
input["NetworkPower"]["u"] = "W"; input["NetworkPower"]["u"] = "W";
input["NetworkPower"]["d"] = "0"; input["NetworkPower"]["d"] = "0";
} }
input["PPV"]["v"] = mpptData.PPV; input["PPV"]["v"] = mpptData.panelPower_PPV_W;
input["PPV"]["u"] = "W"; input["PPV"]["u"] = "W";
input["PPV"]["d"] = 0; input["PPV"]["d"] = 0;
input["VPV"]["v"] = mpptData.VPV; input["VPV"]["v"] = mpptData.panelVoltage_VPV_mV / 1000.0;
input["VPV"]["u"] = "V"; input["VPV"]["u"] = "V";
input["VPV"]["d"] = 2; input["VPV"]["d"] = 2;
input["IPV"]["v"] = mpptData.IPV; input["IPV"]["v"] = mpptData.panelCurrent_mA / 1000.0;
input["IPV"]["u"] = "A"; input["IPV"]["u"] = "A";
input["IPV"]["d"] = 2; input["IPV"]["d"] = 2;
input["YieldToday"]["v"] = mpptData.H20; input["YieldToday"]["v"] = mpptData.yieldToday_H20_Wh / 1000.0;
input["YieldToday"]["u"] = "kWh"; input["YieldToday"]["u"] = "kWh";
input["YieldToday"]["d"] = 3; input["YieldToday"]["d"] = 3;
input["YieldYesterday"]["v"] = mpptData.H22; input["YieldYesterday"]["v"] = mpptData.yieldYesterday_H22_Wh / 1000.0;
input["YieldYesterday"]["u"] = "kWh"; input["YieldYesterday"]["u"] = "kWh";
input["YieldYesterday"]["d"] = 3; input["YieldYesterday"]["d"] = 3;
input["YieldTotal"]["v"] = mpptData.H19; input["YieldTotal"]["v"] = mpptData.yieldTotal_H19_Wh / 1000.0;
input["YieldTotal"]["u"] = "kWh"; input["YieldTotal"]["u"] = "kWh";
input["YieldTotal"]["d"] = 3; input["YieldTotal"]["d"] = 3;
input["MaximumPowerToday"]["v"] = mpptData.H21; input["MaximumPowerToday"]["v"] = mpptData.maxPowerToday_H21_W;
input["MaximumPowerToday"]["u"] = "W"; input["MaximumPowerToday"]["u"] = "W";
input["MaximumPowerToday"]["d"] = 0; input["MaximumPowerToday"]["d"] = 0;
input["MaximumPowerYesterday"]["v"] = mpptData.H23; input["MaximumPowerYesterday"]["v"] = mpptData.maxPowerYesterday_H23_W;
input["MaximumPowerYesterday"]["u"] = "W"; input["MaximumPowerYesterday"]["u"] = "W";
input["MaximumPowerYesterday"]["d"] = 0; input["MaximumPowerYesterday"]["d"] = 0;
} }