Skip to content
6 changes: 6 additions & 0 deletions include/Configuration.h
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,12 @@ struct BATTERY_CONFIG_T {
float DischargeCurrentLimitBelowSoc;
float DischargeCurrentLimitBelowVoltage;
bool UseBatteryReportedDischargeCurrentLimit;
bool EnableChargeCurrentLimit;
float MaxChargeCurrentLimit;
float MinChargeCurrentLimit;
float ChargeCurrentLimitBelowSoc;
float ChargeCurrentLimitBelowVoltage;
bool UseBatteryReportedChargeCurrentLimit;
};
using BatteryConfig = struct BATTERY_CONFIG_T;

Expand Down
1 change: 1 addition & 0 deletions include/battery/Controller.h
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ class Controller {
void updateSettings();

float getDischargeCurrentLimit();
float getChargeCurrentLimit();

std::shared_ptr<Stats const> getStats() const;

Expand Down
8 changes: 8 additions & 0 deletions include/defaults.h
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,14 @@
#define BATTERY_DISCHARGE_CURRENT_LIMIT_BELOW_SOC 100.0
#define BATTERY_DISCHARGE_CURRENT_LIMIT_BELOW_VOLTAGE 60.0
#define BATTERY_USE_BATTERY_REPORTED_DISCHARGE_CURRENT_LIMIT false

#define BATTERY_ENABLE_CHARGE_CURRENT_LIMIT false
#define BATTERY_CHARGE_CURRENT_LIMIT_MIN 0.0
#define BATTERY_CHARGE_CURRENT_LIMIT_MAX 10.0
#define BATTERY_CHARGE_CURRENT_LIMIT_BELOW_SOC 100.0
#define BATTERY_CHARGE_CURRENT_LIMIT_BELOW_VOLTAGE 60.0
#define BATTERY_USE_BATTERY_REPORTED_CHARGE_CURRENT_LIMIT false

#define BATTERY_ZENDURE_POLLING_INTERVAL 15
#define BATTERY_ZENDURE_DEVICE 0
#define BATTERY_ZENDURE_MIN_SOC 7
Expand Down
1 change: 1 addition & 0 deletions include/solarcharger/Provider.h
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ class Provider {
virtual bool init() = 0;
virtual void deinit() = 0;
virtual void loop() = 0;
virtual void setChargeLimit(float limit, float act_charge_current) = 0;
virtual std::shared_ptr<Stats> getStats() const = 0;
};

Expand Down
1 change: 1 addition & 0 deletions include/solarcharger/mqtt/Provider.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ class Provider : public ::SolarChargers::Provider {
bool init() final;
void deinit() final;
void loop() final { return; } // this class is event-driven
void setChargeLimit(float limit, float act_charge_current) final { return; } // actually the charge-limit isn't used for MQTT. Is there any use-case for that?
std::shared_ptr<::SolarChargers::Stats> getStats() const final { return _stats; }

private:
Expand Down
3 changes: 3 additions & 0 deletions include/solarcharger/victron/Provider.h
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ class Provider : public ::SolarChargers::Provider {
bool init() final;
void deinit() final;
void loop() final;
void setChargeLimit( float limit, float act_charge_current) final;
std::shared_ptr<::SolarChargers::Stats> getStats() const final { return _stats; }

private:
Expand All @@ -31,6 +32,8 @@ class Provider : public ::SolarChargers::Provider {
std::vector<controller_t> _controllers;
std::vector<String> _serialPortOwners;
std::shared_ptr<Stats> _stats = std::make_shared<Stats>();
float _chargeLimit { 0.0f };
float _chargeCurrent { 0.0f };

bool initController(gpio_num_t rx, gpio_num_t tx, uint8_t instance);
};
Expand Down
4 changes: 4 additions & 0 deletions lib/VeDirectFrameHandler/VeDirectData.h
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,8 @@ struct veMpptStruct : veStruct {
std::pair<uint32_t, uint32_t> NetworkTotalDcInputPowerMilliWatts;
std::pair<uint32_t, uint32_t> BatteryAbsorptionMilliVolt;
std::pair<uint32_t, uint32_t> BatteryFloatMilliVolt;
std::pair<uint32_t, uint16_t> BatteryMaximumCurrent;
std::pair<uint32_t, uint16_t> ChargeCurrentLimit;
std::pair<uint32_t, uint8_t> NetworkInfo;
std::pair<uint32_t, uint8_t> NetworkMode;
std::pair<uint32_t, uint8_t> NetworkStatus;
Expand Down Expand Up @@ -145,7 +147,9 @@ enum class VeDirectHexRegister : uint16_t {
HistoryMPPTD30 = 0x10BE,
BatteryAbsorptionVoltage = 0xEDF7,
BatteryFloatVoltage = 0xEDF6,
BatteryMaximumCurrent = 0xEDF0,
TotalChargeCurrent = 0x2013,
ChargeCurrentLimit = 0x2015,
ChargeStateElapsedTime= 0x2007,
BatteryVoltageSense = 0x2002,
LoadCurrent = 0xEDAD,
Expand Down
42 changes: 41 additions & 1 deletion lib/VeDirectFrameHandler/VeDirectMpptController.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,17 @@ void VeDirectMpptController::frameValidEvent() {
}
}

void VeDirectMpptController::setChargeLimit( float limit )
{
// Victron MPPT needs limit with a resolution of 0.1A
if (limit == __FLT_MAX__) {
_chargeLimit = 0xFFFF;
} else if (limit > 0.0f) {
_chargeLimit = static_cast<uint16_t>( limit * 10.0f );
} else {
_chargeLimit = 0;
}
}

void VeDirectMpptController::loop()
{
Expand Down Expand Up @@ -147,6 +158,8 @@ void VeDirectMpptController::loop()
resetTimestamp(_tmpFrame.NetworkTotalDcInputPowerMilliWatts);
resetTimestamp(_tmpFrame.BatteryFloatMilliVolt);
resetTimestamp(_tmpFrame.BatteryAbsorptionMilliVolt);
resetTimestamp(_tmpFrame.BatteryMaximumCurrent);
resetTimestamp(_tmpFrame.ChargeCurrentLimit);

#ifdef PROCESS_NETWORK_STATE
resetTimestamp(_tmpFrame.NetworkInfo);
Expand Down Expand Up @@ -234,6 +247,26 @@ bool VeDirectMpptController::hexDataHandler(VeDirectHexData const &data) {
return true;
break;

case VeDirectHexRegister::BatteryMaximumCurrent:
_tmpFrame.BatteryMaximumCurrent =
{ millis(), static_cast<uint16_t>(data.value) };

ESP_LOGD(TAG, "%s Hex Data: MPPT Bettery Max Current (0x%04X): %.1fA",
_logId, regLog,
_tmpFrame.BatteryMaximumCurrent.second / 10.0);
return true;
break;

case VeDirectHexRegister::ChargeCurrentLimit:
_tmpFrame.ChargeCurrentLimit =
{ millis(), static_cast<uint16_t>(data.value) };

ESP_LOGD(TAG, "%s Hex Data: Charge Current Limit (0x%04X): %.1fA",
_logId, regLog,
static_cast<float>(_tmpFrame.ChargeCurrentLimit.second) / 10.0);
return true;
break;

#ifdef PROCESS_NETWORK_STATE
case VeDirectHexRegister::NetworkInfo:
_tmpFrame.NetworkInfo =
Expand Down Expand Up @@ -316,7 +349,14 @@ void VeDirectMpptController::sendNextHexCommandFromQueue(void) {
(!prio && (_hexQueue[idx]._readPeriod != HIGH_PRIO_COMMAND))) &&
(millisTime - _hexQueue[idx]._lastSendTime) > (_hexQueue[idx]._readPeriod * 1000)) {

sendHexCommand(VeDirectHexCommand::GET, _hexQueue[idx]._hexRegister);
if (_hexQueue[idx]._setCommand)
{
sendHexCommand(VeDirectHexCommand::SET, _hexQueue[idx]._hexRegister, _hexQueue[idx]._data, _hexQueue[idx]._dataLength);
}
else
{
sendHexCommand(VeDirectHexCommand::GET, _hexQueue[idx]._hexRegister);
}
_hexQueue[idx]._lastSendTime = millisTime;

// we need this information to check if we get an answer, see hexDataHandler()
Expand Down
20 changes: 14 additions & 6 deletions lib/VeDirectFrameHandler/VeDirectMpptController.h
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,11 @@ class MovingAverage {

struct VeDirectHexQueue {
VeDirectHexRegister _hexRegister; // hex register
bool _setCommand; // true if the command is a SET-command, false if GET
uint8_t _readPeriod; // time period in sec until we send the command again
uint32_t _lastSendTime; // time stamp in milli sec of last send
uint32_t& _data; // data to send (only at SET-command)
uint8_t _dataLength; // length of data (only at SET-command -> 8/16/32)
};

class VeDirectMpptController : public VeDirectFrameHandler<veMpptStruct> {
Expand All @@ -49,7 +52,7 @@ class VeDirectMpptController : public VeDirectFrameHandler<veMpptStruct> {
void init(gpio_num_t rx, gpio_num_t tx, uint8_t hwSerialPort);

using data_t = veMpptStruct;

void setChargeLimit( float value );
void loop() final;

private:
Expand All @@ -62,12 +65,17 @@ class VeDirectMpptController : public VeDirectFrameHandler<veMpptStruct> {

uint32_t _sendTimeout = 0; // timeout until we send the next command from the queue
size_t _sendQueueNr = 0; // actual queue position;
uint32_t _chargeLimit = 0xFFFF; // limit MPPT to this limit (in 0.1A), default: 0xFFFF (=full current)
uint32_t _no_data = 0; // dummy-value

// for slow changing values we use a send time period of 4 sec
#define HIGH_PRIO_COMMAND 1
Copy link

Copilot AI Jun 18, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] Avoid defining macros inside class headers; consider replacing HIGH_PRIO_COMMAND with a static constexpr uint8_t member or an enum.

Suggested change
#define HIGH_PRIO_COMMAND 1
static constexpr uint8_t HIGH_PRIO_COMMAND = 1;

Copilot uses AI. Check for mistakes.

std::array<VeDirectHexQueue, 5> _hexQueue { VeDirectHexRegister::NetworkTotalDcInputPower, HIGH_PRIO_COMMAND, 0,
VeDirectHexRegister::ChargeControllerTemperature, 4, 0,
VeDirectHexRegister::SmartBatterySenseTemperature, 4, 0,
VeDirectHexRegister::BatteryFloatVoltage, 4, 0,
VeDirectHexRegister::BatteryAbsorptionVoltage, 4, 0 };
std::array<VeDirectHexQueue, 8> _hexQueue { VeDirectHexRegister::NetworkTotalDcInputPower, false, HIGH_PRIO_COMMAND, 0, _no_data, 0,
VeDirectHexRegister::ChargeControllerTemperature, false, 4, 0, _no_data, 0,
VeDirectHexRegister::SmartBatterySenseTemperature, false, 4, 0, _no_data, 0,
VeDirectHexRegister::BatteryFloatVoltage, false, 4, 0, _no_data, 0,
VeDirectHexRegister::BatteryAbsorptionVoltage, false, 4, 0, _no_data, 0,
VeDirectHexRegister::ChargeCurrentLimit, false, 4, 0, _no_data, 0,
VeDirectHexRegister::BatteryMaximumCurrent, false, 4, 0, _no_data, 0,
VeDirectHexRegister::ChargeCurrentLimit, true, 10, 0, _chargeLimit, 16};
};
12 changes: 12 additions & 0 deletions src/Configuration.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,12 @@ void ConfigurationClass::serializeBatteryConfig(BatteryConfig const& source, Jso
target["discharge_current_limit_below_soc"] = config.Battery.DischargeCurrentLimitBelowSoc;
target["discharge_current_limit_below_voltage"] = config.Battery.DischargeCurrentLimitBelowVoltage;
target["use_battery_reported_discharge_current_limit"] = config.Battery.UseBatteryReportedDischargeCurrentLimit;
target["enable_charge_current_limit"] = config.Battery.EnableChargeCurrentLimit;
target["max_charge_current_limit"] = config.Battery.MaxChargeCurrentLimit;
target["min_charge_current_limit"] = config.Battery.MinChargeCurrentLimit;
Comment on lines +139 to +140
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I understand why you added min and max but the min value is a victron or solarcharger specific setting because you don't want to set current limit to 0. But if we focus on the battery alone, do i want to charge my battery with 3 A even if the BMS reports 0 A? Another thing to keep in mind is that ideally a grid charger (only Huawei R48xx right now) also works based on the charge current limit and would keep charging with the minimum current limit.

If you agree that this is a victron/solarcharger only setting and not actually related to charging the battery but how much power the victrons should provide no matter what the BMS tells us, we need to either agree on a good hardcoded default value or create a settings item in the victron config.

what do you think?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here my thoughts:

My battery BMS tuns "Max Charge Current" to 0A if e. g. a cell over voltage warning flag occures. I would like to be able to set the victron to exaxt this value then. When the warning flag disappears, the "Max Charge Current" goes back to 94,5A. This is much more than the Victron could deliver.

I don't know what a victron that could deliver 20A would do with an value above that.

So I would think that such a configuration is an victron only configuration that should be activatable and disactivatable and Victron specific max / min values should be configuratable. (I would adjust it in my case to 20A / 0A).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Victron max current does not need to be configurable within OpenDTU. If the battery allows 90 A but the victron can only do max 30 A, we will set it to 30 A. If you want to lower the max value of the victron you should do so in the victron mppt itself using the victron app.

Talking about this, i wonder why we need the min setting at all? If a inverter connected to the DTU produces power the current drawn by the inverter is part of the current limit send to the victrons and this means that even if the BMS reports 0 A we would still allow the victrons to provide the required power to feed the inverter without taking power from the batteries.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, now I start to understand the philosophy:
The values for the Vicctron max current are the desired Battery charging current plus the solar passthrough current. This means even if battery current demand is 0A the victron will be adjusted to deliver the solar passthrough current.
This means the Victron max current limiter will only apply if all inverters cannot digest the not limitetd Victron current and the battery demands a lower charging current.

Then you are right and we pobably do not need the min setting.

The max setting would apply if there is no desired Battery charging current delivered by the battery. (Does the Max setting in the Victron App limit the Value transmitted by DTU on Battery or will it just be overwitten?)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Exactly.

The max current set in the victron app will be respected by the DTU, we will never request more current then what was set by the victron app. Actually these are two different fields in the victron mppt, and only because of that we are able to always respect that setting. By default this setting is the hardware defined max charge current e.g. 35A for the MPPT 150/35.

target["charge_current_limit_below_soc"] = config.Battery.ChargeCurrentLimitBelowSoc;
target["charge_current_limit_below_voltage"] = config.Battery.ChargeCurrentLimitBelowVoltage;
Comment on lines +141 to +142
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that we should change this to current limit above XYZ because the charge current should get reduced/limited when the battery is getting fuller, not when its getting emptier.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I introduced those settings:
1.) "charge_current_limit_below_xxx": the limit for the charge current when the battery is empty - most batteries don't allow full charge current in this state. You'll need the min, if the BMS doesn't report a limit (e.g. if you don't have a limit from a BMS, but the SOC from an Smart Sense).
2.) "max_charge_current_limit": this limits the charge current when the BMS doesn't report a limit (e.g. no BMS connected) or you want to limit to a lower limit (e.g. an additional charger is attached)
3. )"min_charge_current_limit": this gurantees that the limit doesn't ever goes down to zero. My BMS reports a limit of 0A whenever a cell overvoltage occurs, but stops balancing as soon as the charge current disappears. To continue balancing the charge current has to remain at a low level (~2A).

None of these settings is special for Victron, but for special use-cases.

target["use_battery_reported_charge_current_limit"] = config.Battery.UseBatteryReportedChargeCurrentLimit;
}

void ConfigurationClass::serializeBatteryZendureConfig(BatteryZendureConfig const& source, JsonObject& target)
Expand Down Expand Up @@ -559,6 +565,12 @@ void ConfigurationClass::deserializeBatteryConfig(JsonObject const& source, Batt
target.DischargeCurrentLimitBelowSoc = source["discharge_current_limit_below_soc"] | BATTERY_DISCHARGE_CURRENT_LIMIT_BELOW_SOC;
target.DischargeCurrentLimitBelowVoltage = source["discharge_current_limit_below_voltage"] | BATTERY_DISCHARGE_CURRENT_LIMIT_BELOW_VOLTAGE;
target.UseBatteryReportedDischargeCurrentLimit = source["use_battery_reported_discharge_current_limit"] | BATTERY_USE_BATTERY_REPORTED_DISCHARGE_CURRENT_LIMIT;
target.EnableChargeCurrentLimit = source["enable_charge_current_limit"] | BATTERY_ENABLE_CHARGE_CURRENT_LIMIT;
target.MaxChargeCurrentLimit = source["max_charge_current_limit"] | BATTERY_CHARGE_CURRENT_LIMIT_MAX;
target.MinChargeCurrentLimit = source["min_charge_current_limit"] | BATTERY_CHARGE_CURRENT_LIMIT_MIN;
target.ChargeCurrentLimitBelowSoc = source["charge_current_limit_below_soc"] | BATTERY_CHARGE_CURRENT_LIMIT_BELOW_SOC;
target.ChargeCurrentLimitBelowVoltage = source["charge_current_limit_below_voltage"] | BATTERY_CHARGE_CURRENT_LIMIT_BELOW_VOLTAGE;
target.UseBatteryReportedChargeCurrentLimit = source["use_battery_reported_charge_current_limit"] | BATTERY_USE_BATTERY_REPORTED_CHARGE_CURRENT_LIMIT;
}

void ConfigurationClass::deserializeBatteryZendureConfig(JsonObject const& source, BatteryZendureConfig& target)
Expand Down
59 changes: 59 additions & 0 deletions src/battery/Controller.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -152,4 +152,63 @@ float Controller::getDischargeCurrentLimit()
return std::min(getConfiguredLimit(), getBatteryLimit());
}

float Controller::getChargeCurrentLimit()
{
auto const& config = Configuration.get();

if (!config.Battery.EnableChargeCurrentLimit) { return FLT_MAX; }

/**
* we are looking at three limits: (1) the static max charge current limit
* setup by the user as part of the configuration, which is effective below
* a (SoC or voltage) threshold, (2) the dynamic charge current
* limit reported by the BMS and (3) the static min charge current limit, setup by
* the user which defines the lowest possible charge current limit.
* for the first both types of limits, we will determine its value, then test a bunch
* of excuses why the limit might not be applicable.
*
* the smaller limit will be enforced.
* If the resulting limit is smaller than (3), (3) will be used instead
*/
auto spStats = getStats();

auto getConfiguredMinLimit = [&config,&spStats]() -> float {
auto configuredMinLimit = config.Battery.MinChargeCurrentLimit;
if (configuredMinLimit < 0.0f) { return 0.0f; } // invalid setting

return configuredMinLimit;
};

auto getConfiguredMaxLimit = [&config,&spStats]() -> float {
auto configuredMaxLimit = config.Battery.MaxChargeCurrentLimit;
if (configuredMaxLimit <= 0.0f) { return FLT_MAX; } // invalid setting

bool useSoC = spStats->getSoCAgeSeconds() <= 60 && !config.PowerLimiter.IgnoreSoc;
if (useSoC) {
auto threshold = config.Battery.ChargeCurrentLimitBelowSoc;
if (spStats->getSoC() >= threshold) { return FLT_MAX; }

return configuredMaxLimit;
}

bool voltageValid = spStats->getVoltageAgeSeconds() <= 60;
if (voltageValid) {
auto threshold = config.Battery.ChargeCurrentLimitBelowVoltage;
if (spStats->getVoltage() >= threshold) { return FLT_MAX; }

return configuredMaxLimit;
}
return configuredMaxLimit;
};

auto getBatteryLimit = [&config,&spStats]() -> float {
if (!config.Battery.UseBatteryReportedChargeCurrentLimit) { return FLT_MAX; }

if (spStats->getChargeCurrentLimitAgeSeconds() > 60) { return FLT_MAX; } // unusable

return spStats->getChargeCurrentLimit();
};
auto maxChargeLimit = std::min(getConfiguredMaxLimit(), getBatteryLimit());
return std::max(maxChargeLimit, getConfiguredMinLimit());
}
} // namespace Batteries
7 changes: 7 additions & 0 deletions src/solarcharger/Controller.cpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// SPDX-License-Identifier: GPL-2.0-or-later
#include <Configuration.h>
#include <MqttSettings.h>
#include <battery/Controller.h>
#include <solarcharger/Controller.h>
#include <solarcharger/DummyStats.h>
#include <solarcharger/victron/Provider.h>
Expand Down Expand Up @@ -72,6 +73,12 @@ void Controller::loop()

if (!_upProvider) { return; }

//get charge current limitation from battery
auto stats = Battery.getStats();

float chargeCurrentLimit = Battery.getChargeCurrentLimit();
float actChargeCurrent = stats->getChargeCurrent();
_upProvider->setChargeLimit(chargeCurrentLimit, actChargeCurrent);
_upProvider->loop();

// TODO(schlimmchen): this cannot make sure that transient
Expand Down
60 changes: 60 additions & 0 deletions src/solarcharger/victron/Provider.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -64,11 +64,71 @@ bool Provider::initController(gpio_num_t rx, gpio_num_t tx, uint8_t instance)
return true;
}

void Provider::setChargeLimit( float limit, float act_charge_current )
{
_chargeLimit = limit;
_chargeCurrent = act_charge_current;
}


void Provider::loop()
{
std::lock_guard<std::mutex> lock(_mutex);

float overallChargeCurrent { 0.0f };
float remainingLimit { _chargeLimit };
float reservedChargeCurrent { 0.5f }; // minimum current for a controller whenever the given limit is higher

uint8_t numControllers { 0 };
// calculate the actual charge current of all MPPTs
for (auto const& upController : _controllers) {
overallChargeCurrent += static_cast<float>( upController->getData().batteryCurrent_I_mA ) / 1000.0f;
numControllers++;
}
// increase the charge limit with the current drawn by the inverter(s)
const float inverterCurrent { overallChargeCurrent - _chargeCurrent };
if (inverterCurrent >= 0.0f) {
remainingLimit += inverterCurrent;
}

// reserve a minimum charge-current for every controller, to ensure that we get a good distribution over all MPPTs
const float overallReservedChargeCurrent { reservedChargeCurrent * static_cast<float>(numControllers) };
if (remainingLimit > overallReservedChargeCurrent) {
remainingLimit -= overallReservedChargeCurrent;
} else {
// the limit is lower than the needed reserve --> distribute the allowed limit in a simple way over all MPPTs
reservedChargeCurrent = remainingLimit / static_cast<float>(numControllers);
remainingLimit = 0.0f;
}
for (auto const& upController : _controllers) {
const float batCurrent { static_cast<float>( upController->getData().batteryCurrent_I_mA ) / 1000.0f };
float factor { 0.0f };
// if there is not current for automatic distribution --> distribute the limit uniformly to all controllers
if (overallChargeCurrent <= 0.0f) {
factor = 1.0 / static_cast<float>(numControllers);
} else {
factor = batCurrent / overallChargeCurrent;
}
float controllerLimit { reservedChargeCurrent};
// no limit left for this controller? Only apply the absolute minimum
if (remainingLimit > 0.0f) {
controllerLimit += factor * remainingLimit;
}
// get the maximum allowed battery current of the charger
auto batMaxCurrent { upController->getData().BatteryMaximumCurrent };
// is the data valid?
if (batMaxCurrent.first > 0) {
const float maxControllerCurrent { static_cast<float>( batMaxCurrent.second ) / 10.0f };
// limit to the maximum allowed battery current of the charger
if (controllerLimit > maxControllerCurrent) {
controllerLimit = maxControllerCurrent;
}
}
// substract the set limit from the remaining limit for distribution to the other chargers
remainingLimit -= controllerLimit;
overallChargeCurrent -= batCurrent;

upController->setChargeLimit(controllerLimit);
upController->loop();

if (upController->isDataValid()) {
Expand Down
6 changes: 5 additions & 1 deletion src/solarcharger/victron/Stats.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -251,7 +251,11 @@ void Stats::populateJsonWithInstanceStats(const JsonObject &root, const VeDirect
device["MpptTemperature"]["u"] = "°C";
device["MpptTemperature"]["d"] = "1";
}

if (mpptData.ChargeCurrentLimit.first > 0) {
device["ChargeCurrentLimit"]["v"] = mpptData.ChargeCurrentLimit.second / 10.0;
device["ChargeCurrentLimit"]["u"] = "A";
device["ChargeCurrentLimit"]["d"] = 1;
}
const JsonObject output = values["output"].to<JsonObject>();
output["P"]["v"] = mpptData.batteryOutputPower_W;
output["P"]["u"] = "W";
Expand Down
Loading