diff --git a/include/Configuration.h b/include/Configuration.h index fe9c7d7ef..4e6df2316 100644 --- a/include/Configuration.h +++ b/include/Configuration.h @@ -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; diff --git a/include/battery/Controller.h b/include/battery/Controller.h index c80b30312..74b3d88e9 100644 --- a/include/battery/Controller.h +++ b/include/battery/Controller.h @@ -15,6 +15,7 @@ class Controller { void updateSettings(); float getDischargeCurrentLimit(); + float getChargeCurrentLimit(); std::shared_ptr getStats() const; diff --git a/include/defaults.h b/include/defaults.h index 85bf5c30d..260210db5 100644 --- a/include/defaults.h +++ b/include/defaults.h @@ -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 diff --git a/include/solarcharger/Provider.h b/include/solarcharger/Provider.h index 8c8af735b..8c6d58b15 100644 --- a/include/solarcharger/Provider.h +++ b/include/solarcharger/Provider.h @@ -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 getStats() const = 0; }; diff --git a/include/solarcharger/mqtt/Provider.h b/include/solarcharger/mqtt/Provider.h index 760256ae9..888ba096b 100644 --- a/include/solarcharger/mqtt/Provider.h +++ b/include/solarcharger/mqtt/Provider.h @@ -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: diff --git a/include/solarcharger/victron/Provider.h b/include/solarcharger/victron/Provider.h index b1a71ba1c..417da5228 100644 --- a/include/solarcharger/victron/Provider.h +++ b/include/solarcharger/victron/Provider.h @@ -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: @@ -31,6 +32,8 @@ class Provider : public ::SolarChargers::Provider { std::vector _controllers; std::vector _serialPortOwners; std::shared_ptr _stats = std::make_shared(); + float _chargeLimit { 0.0f }; + float _chargeCurrent { 0.0f }; bool initController(gpio_num_t rx, gpio_num_t tx, uint8_t instance); }; diff --git a/lib/VeDirectFrameHandler/VeDirectData.h b/lib/VeDirectFrameHandler/VeDirectData.h index 4cda91274..297bed311 100644 --- a/lib/VeDirectFrameHandler/VeDirectData.h +++ b/lib/VeDirectFrameHandler/VeDirectData.h @@ -56,6 +56,8 @@ struct veMpptStruct : veStruct { std::pair NetworkTotalDcInputPowerMilliWatts; std::pair BatteryAbsorptionMilliVolt; std::pair BatteryFloatMilliVolt; + std::pair BatteryMaximumCurrent; + std::pair ChargeCurrentLimit; std::pair NetworkInfo; std::pair NetworkMode; std::pair NetworkStatus; @@ -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, diff --git a/lib/VeDirectFrameHandler/VeDirectMpptController.cpp b/lib/VeDirectFrameHandler/VeDirectMpptController.cpp index 17465e340..8b6f52075 100644 --- a/lib/VeDirectFrameHandler/VeDirectMpptController.cpp +++ b/lib/VeDirectFrameHandler/VeDirectMpptController.cpp @@ -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( limit * 10.0f ); + } else { + _chargeLimit = 0; + } +} void VeDirectMpptController::loop() { @@ -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); @@ -234,6 +247,26 @@ bool VeDirectMpptController::hexDataHandler(VeDirectHexData const &data) { return true; break; + case VeDirectHexRegister::BatteryMaximumCurrent: + _tmpFrame.BatteryMaximumCurrent = + { millis(), static_cast(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(data.value) }; + + ESP_LOGD(TAG, "%s Hex Data: Charge Current Limit (0x%04X): %.1fA", + _logId, regLog, + static_cast(_tmpFrame.ChargeCurrentLimit.second) / 10.0); + return true; + break; + #ifdef PROCESS_NETWORK_STATE case VeDirectHexRegister::NetworkInfo: _tmpFrame.NetworkInfo = @@ -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() diff --git a/lib/VeDirectFrameHandler/VeDirectMpptController.h b/lib/VeDirectFrameHandler/VeDirectMpptController.h index 6c3013563..612bca109 100644 --- a/lib/VeDirectFrameHandler/VeDirectMpptController.h +++ b/lib/VeDirectFrameHandler/VeDirectMpptController.h @@ -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 { @@ -49,7 +52,7 @@ class VeDirectMpptController : public VeDirectFrameHandler { 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: @@ -62,12 +65,17 @@ class VeDirectMpptController : public VeDirectFrameHandler { 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 - std::array _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 _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}; }; diff --git a/src/Configuration.cpp b/src/Configuration.cpp index d1fb639fe..7aef27e8f 100644 --- a/src/Configuration.cpp +++ b/src/Configuration.cpp @@ -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; + target["charge_current_limit_below_soc"] = config.Battery.ChargeCurrentLimitBelowSoc; + target["charge_current_limit_below_voltage"] = config.Battery.ChargeCurrentLimitBelowVoltage; + target["use_battery_reported_charge_current_limit"] = config.Battery.UseBatteryReportedChargeCurrentLimit; } void ConfigurationClass::serializeBatteryZendureConfig(BatteryZendureConfig const& source, JsonObject& target) @@ -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) diff --git a/src/battery/Controller.cpp b/src/battery/Controller.cpp index 879de52eb..ba60340cc 100644 --- a/src/battery/Controller.cpp +++ b/src/battery/Controller.cpp @@ -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 diff --git a/src/solarcharger/Controller.cpp b/src/solarcharger/Controller.cpp index 096ed63da..7429b7848 100644 --- a/src/solarcharger/Controller.cpp +++ b/src/solarcharger/Controller.cpp @@ -1,6 +1,7 @@ // SPDX-License-Identifier: GPL-2.0-or-later #include #include +#include #include #include #include @@ -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 diff --git a/src/solarcharger/victron/Provider.cpp b/src/solarcharger/victron/Provider.cpp index 0446969a8..bca4c9096 100644 --- a/src/solarcharger/victron/Provider.cpp +++ b/src/solarcharger/victron/Provider.cpp @@ -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 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( 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(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(numControllers); + remainingLimit = 0.0f; + } for (auto const& upController : _controllers) { + const float batCurrent { static_cast( 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(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( 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()) { diff --git a/src/solarcharger/victron/Stats.cpp b/src/solarcharger/victron/Stats.cpp index 3ad95f431..5afa232ca 100644 --- a/src/solarcharger/victron/Stats.cpp +++ b/src/solarcharger/victron/Stats.cpp @@ -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(); output["P"]["v"] = mpptData.batteryOutputPower_W; output["P"]["u"] = "W"; diff --git a/webapp/src/locales/de.json b/webapp/src/locales/de.json index b6680e011..6adc5d092 100644 --- a/webapp/src/locales/de.json +++ b/webapp/src/locales/de.json @@ -200,7 +200,8 @@ "RELAY": "Status Fehlerrelais", "ERR": "Fehlerbeschreibung", "HSDS": "Anzahl der Tage (0..364)", - "MpptTemperature": "Ladereglertemperatur" + "MpptTemperature": "Ladereglertemperatur", + "ChargeCurrentLimit": "Ladestromlimit" }, "section_output": "Ausgang (Batterie)", "output": { @@ -791,6 +792,17 @@ "BatteryReportedDischargeCurrentLimitInfo": "Hinweis: Das niedrigste Limit wird angewendet, wobei das von der Batterie übermittelte Entladestromlimit nur verwendet wird, wenn in der letzten Minute ein Update eingegangen ist. Andernfalls dient das zuvor festgelegte Limit als Fallback.", "MqttDischargeCurrentLimitTopic": "Topic für Entladestromlimit", "MqttAmperageUnit": "@:base.Unit", + "ChargeCurrentLimitConfiguration": "Einstellungen Ladestromlimit", + "LimitChargeCurrent": "Ladestrom limitieren", + "MinChargeCurrentLimit": "min. Ladestrom", + "MinChargeCurrentLimitInfo": "Das höchste Limit wird angewendet, wobei das von der Batterie übermittelte Ladestromlimit nur verwendet wird, wenn in der letzten Minute ein Update eingegangen ist; andernfalls dient das zuvor festgelegte Limit als Fallback.", + "MaxChargeCurrentLimit": "max. Ladestrom", + "ChargeCurrentLimitBelowSoc": "Limitieren unter SoC", + "ChargeCurrentLimitBelowSocInfo": "Das max Ladestromlimit wird nur unter dieser SoC-Schwelle angewendet (wird nicht verwendet, falls 'Batterie SoC ignorieren' in den DPL-Einstellungen aktiviert ist).", + "ChargeCurrentLimitBelowVoltage": "Limitieren unter Spannung", + "ChargeCurrentLimitBelowVoltageInfo": "Das max Ladestromlimit wird nur unter dieser Spannungs-Schwelle angewendet (wenn 'Batterie SoC ignorieren' in den DPL-Einstellungen aktiviert ist oder SoC nicht verfügbar ist).", + "UseBatteryReportedChargeCurrentLimit": "Von der Batterie übermitteltes Limit verwenden", + "BatteryReportedChargeCurrentLimitInfo": "Hinweis: Das niedrigste Limit wird angewendet, wobei das von der Batterie übermittelte Ladestromlimit nur verwendet wird, wenn in der letzten Minute ein Update eingegangen ist; andernfalls dient das zuvor festgelegte Limit als Fallback.", "ZendureConfiguration": "Einstellungen", "ZendureDeviceType": "Produkt Typ", "ZendureDeviceId": "Produkt Identifikation", diff --git a/webapp/src/locales/en.json b/webapp/src/locales/en.json index 44de8cc57..2a97d78ae 100644 --- a/webapp/src/locales/en.json +++ b/webapp/src/locales/en.json @@ -200,7 +200,8 @@ "RELAY": "Error relay state", "ERR": "Error code", "HSDS": "Day sequence number (0..364)", - "MpptTemperature": "Charge controller temperature" + "MpptTemperature": "Charge controller temperature", + "ChargeCurrentLimit": "Charge current limit" }, "section_output": "Output (Battery)", "output": { @@ -792,6 +793,17 @@ "BatteryReportedDischargeCurrentLimitInfo": "Hint: The lowest limit will be applied, with the battery-reported discharge current limit used only if an update was received in the last minute; otherwise, the previously specified limit will act as a fallback.", "MqttDischargeCurrentLimitTopic": "Discharge Current Limit Value Topic", "MqttAmperageUnit": "@:base.Unit", + "ChargeCurrentLimitConfiguration": "Charge Current Limit Settings", + "LimitChargeCurrent": "Limit Charge Current", + "MinChargeCurrentLimit": "min. Charge Current", + "MinChargeCurrentLimitInfo": "The highest limit will be applied, with the battery-reported discharge current limit used only if an update was received in the last minute; otherwise, the previously specified limit will act as a fallback.", + "MaxChargeCurrentLimit": "max. Charge Current", + "ChargeCurrentLimitBelowSoc": "Apply max limit below SoC", + "ChargeCurrentLimitBelowSocInfo": "The charge max current limit is only applied below this SoC (not used if 'Ignore Battery SoC' is enabled in the DPL settings).", + "ChargeCurrentLimitBelowVoltage": "Apply max limit below voltage", + "ChargeCurrentLimitBelowVoltageInfo": "The max charge current limit is only applied below this voltage (used if 'Ignore Battery SoC' is enabled in the DPL settings or when SoC is unavailable).", + "UseBatteryReportedChargeCurrentLimit": "Use Battery-Reported limit", + "BatteryReportedChargeCurrentLimitInfo": "Hint: The lowest limit will be applied, with the battery-reported discharge current limit used only if an update was received in the last minute; otherwise, the previously specified limit will act as a fallback.", "ZendureConfiguration": "Configuration", "ZendureDeviceType": "Product Type", "ZendureDeviceId": "Product ID", diff --git a/webapp/src/types/BatteryConfig.ts b/webapp/src/types/BatteryConfig.ts index 44fba6498..7443f1caa 100644 --- a/webapp/src/types/BatteryConfig.ts +++ b/webapp/src/types/BatteryConfig.ts @@ -50,4 +50,10 @@ export interface BatteryConfig { discharge_current_limit_below_soc: number; discharge_current_limit_below_voltage: number; use_battery_reported_discharge_current_limit: boolean; + enable_charge_current_limit: boolean; + min_charge_current_limit: number; + max_charge_current_limit: number; + charge_current_limit_below_soc: number; + charge_current_limit_below_voltage: number; + use_battery_reported_charge_current_limit: boolean; } diff --git a/webapp/src/views/BatteryAdminView.vue b/webapp/src/views/BatteryAdminView.vue index d1cdae2c5..e4aa169e7 100644 --- a/webapp/src/views/BatteryAdminView.vue +++ b/webapp/src/views/BatteryAdminView.vue @@ -276,6 +276,93 @@ + + + + + +