diff --git a/include/BatteryStats.h b/include/BatteryStats.h index 2a1ee4a63..14fd2ef49 100644 --- a/include/BatteryStats.h +++ b/include/BatteryStats.h @@ -7,6 +7,7 @@ #include "Arduino.h" #include "JkBmsDataPoints.h" #include "VeDirectShuntController.h" +#include // mandatory interface for all kinds of batteries class BatteryStats { @@ -34,6 +35,9 @@ class BatteryStats { bool isSoCValid() const { return _lastUpdateSoC > 0; } bool isVoltageValid() const { return _lastUpdateVoltage > 0; } + virtual bool getImmediateChargingRequest() const { return false; }; + virtual float getChargeCurrent() const { return 0; }; + virtual float getChargeCurrentLimitation() const { return FLT_MAX; }; // returns true if the battery reached a critically low voltage/SoC, // such that it is in need of charging to prevent degredation. @@ -71,7 +75,9 @@ class PylontechBatteryStats : public BatteryStats { public: void getLiveViewData(JsonVariant& root) const final; void mqttPublish() const final; - bool needsCharging() const final { return _chargeImmediately; } + bool getImmediateChargingRequest() const { return _chargeImmediately; } ; + float getChargeCurrent() const { return _current; } ; + float getChargeCurrentLimitation() const { return _chargeCurrentLimitation; } ; private: void setManufacturer(String&& m) { _manufacturer = std::move(m); } @@ -122,6 +128,7 @@ class JkBmsBatteryStats : public BatteryStats { uint32_t getMqttFullPublishIntervalMs() const final { return 60 * 1000; } void updateFrom(JkBms::DataPointContainer const& dp); + //bool needsCharging() const final { return false; } ; private: void getJsonData(JsonVariant& root, bool verbose) const; @@ -142,6 +149,7 @@ class VictronSmartShuntStats : public BatteryStats { void mqttPublish() const final; void updateFrom(VeDirectShuntController::veShuntStruct const& shuntData); + //bool needsCharging() const final { return false; }; private: float _current; diff --git a/include/Configuration.h b/include/Configuration.h index 14a056670..adf044957 100644 --- a/include/Configuration.h +++ b/include/Configuration.h @@ -240,6 +240,7 @@ struct CONFIG_T { bool Enabled; uint32_t CAN_Controller_Frequency; bool Auto_Power_Enabled; + bool Emergency_Charge_Enabled; float Auto_Power_Voltage_Limit; float Auto_Power_Enable_Voltage_Limit; float Auto_Power_Lower_Power_Limit; diff --git a/include/Huawei_can.h b/include/Huawei_can.h index e9a3a3d79..2b8edc98e 100644 --- a/include/Huawei_can.h +++ b/include/Huawei_can.h @@ -150,6 +150,7 @@ class HuaweiCanClass { uint8_t _autoPowerEnabledCounter = 0; bool _autoPowerEnabled = false; + bool _batteryEmergencyCharging = false; }; extern HuaweiCanClass HuaweiCan; diff --git a/src/Configuration.cpp b/src/Configuration.cpp index a1764f1e7..e37d55a6d 100644 --- a/src/Configuration.cpp +++ b/src/Configuration.cpp @@ -216,6 +216,7 @@ bool ConfigurationClass::write() huawei["enabled"] = config.Huawei.Enabled; huawei["can_controller_frequency"] = config.Huawei.CAN_Controller_Frequency; huawei["auto_power_enabled"] = config.Huawei.Auto_Power_Enabled; + huawei["emergency_charge_enabled"] = config.Huawei.Emergency_Charge_Enabled; huawei["voltage_limit"] = config.Huawei.Auto_Power_Voltage_Limit; huawei["enable_voltage_limit"] = config.Huawei.Auto_Power_Enable_Voltage_Limit; huawei["lower_power_limit"] = config.Huawei.Auto_Power_Lower_Power_Limit; @@ -463,6 +464,7 @@ bool ConfigurationClass::read() config.Huawei.Enabled = huawei["enabled"] | HUAWEI_ENABLED; config.Huawei.CAN_Controller_Frequency = huawei["can_controller_frequency"] | HUAWEI_CAN_CONTROLLER_FREQUENCY; config.Huawei.Auto_Power_Enabled = huawei["auto_power_enabled"] | false; + config.Huawei.Emergency_Charge_Enabled = huawei["emergency_charge_enabled"] | false; config.Huawei.Auto_Power_Voltage_Limit = huawei["voltage_limit"] | HUAWEI_AUTO_POWER_VOLTAGE_LIMIT; config.Huawei.Auto_Power_Enable_Voltage_Limit = huawei["enable_voltage_limit"] | HUAWEI_AUTO_POWER_ENABLE_VOLTAGE_LIMIT; config.Huawei.Auto_Power_Lower_Power_Limit = huawei["lower_power_limit"] | HUAWEI_AUTO_POWER_LOWER_POWER_LIMIT; diff --git a/src/Huawei_can.cpp b/src/Huawei_can.cpp index 87b141aad..7d39dd7fe 100644 --- a/src/Huawei_can.cpp +++ b/src/Huawei_can.cpp @@ -2,6 +2,7 @@ /* * Copyright (C) 2023 Malte Schmidt and others */ +#include "Battery.h" #include "Huawei_can.h" #include "MessageOutput.h" #include "PowerMeter.h" @@ -13,6 +14,7 @@ #include #include #include +#include #include HuaweiCanClass HuaweiCan; @@ -298,18 +300,45 @@ void HuaweiCanClass::loop() digitalWrite(_huaweiPower, 1); } - // *********************** - // Automatic power control - // *********************** - if (_mode == HUAWEI_MODE_AUTO_INT ) { + if (_mode == HUAWEI_MODE_AUTO_INT || _batteryEmergencyCharging) { - // Set voltage limit in periodic intervals + // Set voltage limit in periodic intervals if we're in auto mode or if emergency battery charge is requested. if ( _nextAutoModePeriodicIntMillis < millis()) { MessageOutput.printf("[HuaweiCanClass::loop] Periodically setting voltage limit: %f \r\n", config.Huawei.Auto_Power_Voltage_Limit); _setValue(config.Huawei.Auto_Power_Voltage_Limit, HUAWEI_ONLINE_VOLTAGE); _nextAutoModePeriodicIntMillis = millis() + 60000; } + } + // *********************** + // Emergency charge + // *********************** + auto stats = Battery.getStats(); + if (config.Huawei.Emergency_Charge_Enabled && stats->getImmediateChargingRequest()) { + _batteryEmergencyCharging = true; + + // Set output current + float efficiency = (_rp.efficiency > 0.5 ? _rp.efficiency : 1.0); + float outputCurrent = efficiency * (config.Huawei.Auto_Power_Upper_Power_Limit / _rp.output_voltage); + MessageOutput.printf("[HuaweiCanClass::loop] Emergency Charge Output current %f \r\n", outputCurrent); + _setValue(outputCurrent, HUAWEI_ONLINE_CURRENT); + return; + } + + if (_batteryEmergencyCharging && !stats->getImmediateChargingRequest()) { + // Battery request has changed. Set current to 0, wait for PSU to respond and then clear state + _setValue(0, HUAWEI_ONLINE_CURRENT); + if (_rp.output_current < 1) { + _batteryEmergencyCharging = false; + } + return; + } + + // *********************** + // Automatic power control + // *********************** + + if (_mode == HUAWEI_MODE_AUTO_INT ) { // Check if we should run automatic power calculation at all. // We may have set a value recently and still wait for output stabilization @@ -371,10 +400,16 @@ void HuaweiCanClass::loop() newPowerLimit = config.Huawei.Auto_Power_Upper_Power_Limit; } - // Set the actual output limit + // Calculate output current float efficiency = (_rp.efficiency > 0.5 ? _rp.efficiency : 1.0); - float outputCurrent = efficiency * (newPowerLimit / _rp.output_voltage); - MessageOutput.printf("[HuaweiCanClass::loop] Output current %f \r\n", outputCurrent); + float calculatedCurrent = efficiency * (newPowerLimit / _rp.output_voltage); + + // Limit output current to value requested by BMS + float permissableCurrent = stats->getChargeCurrentLimitation() - (stats->getChargeCurrent() - _rp.output_current); // BMS current limit - current from other sources + float outputCurrent = std::min(calculatedCurrent, permissableCurrent); + outputCurrent= outputCurrent > 0 ? outputCurrent : 0; + + MessageOutput.printf("[HuaweiCanClass::loop] Setting output current to %.2fA. This is the lower value of calculated %.2fA and BMS permissable %.2fA currents\r\n", outputCurrent, calculatedCurrent, permissableCurrent); _autoPowerEnabled = true; _setValue(outputCurrent, HUAWEI_ONLINE_CURRENT); @@ -409,6 +444,7 @@ void HuaweiCanClass::_setValue(float in, uint8_t parameterType) if (in < 0) { MessageOutput.printf("[HuaweiCanClass::_setValue] Error: Tried to set voltage/current to negative value %f \r\n", in); + return; } // Start PSU if needed diff --git a/src/WebApi_Huawei.cpp b/src/WebApi_Huawei.cpp index 778833d11..d0975ddfc 100644 --- a/src/WebApi_Huawei.cpp +++ b/src/WebApi_Huawei.cpp @@ -188,6 +188,7 @@ void WebApiHuaweiClass::onAdminGet(AsyncWebServerRequest* request) root["enabled"] = config.Huawei.Enabled; root["can_controller_frequency"] = config.Huawei.CAN_Controller_Frequency; root["auto_power_enabled"] = config.Huawei.Auto_Power_Enabled; + root["emergency_charge_enabled"] = config.Huawei.Emergency_Charge_Enabled; root["voltage_limit"] = static_cast(config.Huawei.Auto_Power_Voltage_Limit * 100) / 100.0; root["enable_voltage_limit"] = static_cast(config.Huawei.Auto_Power_Enable_Voltage_Limit * 100) / 100.0; root["lower_power_limit"] = config.Huawei.Auto_Power_Lower_Power_Limit; @@ -239,6 +240,7 @@ void WebApiHuaweiClass::onAdminPost(AsyncWebServerRequest* request) if (!(root.containsKey("enabled")) || !(root.containsKey("can_controller_frequency")) || !(root.containsKey("auto_power_enabled")) || + !(root.containsKey("emergency_charge_enabled")) || !(root.containsKey("voltage_limit")) || !(root.containsKey("lower_power_limit")) || !(root.containsKey("upper_power_limit"))) { @@ -253,11 +255,11 @@ void WebApiHuaweiClass::onAdminPost(AsyncWebServerRequest* request) config.Huawei.Enabled = root["enabled"].as(); config.Huawei.CAN_Controller_Frequency = root["can_controller_frequency"].as(); config.Huawei.Auto_Power_Enabled = root["auto_power_enabled"].as(); + config.Huawei.Emergency_Charge_Enabled = root["emergency_charge_enabled"].as(); config.Huawei.Auto_Power_Voltage_Limit = root["voltage_limit"].as(); config.Huawei.Auto_Power_Enable_Voltage_Limit = root["enable_voltage_limit"].as(); config.Huawei.Auto_Power_Lower_Power_Limit = root["lower_power_limit"].as(); config.Huawei.Auto_Power_Upper_Power_Limit = root["upper_power_limit"].as(); - WebApi.writeConfig(retMsg); response->setLength(); diff --git a/webapp/src/locales/de.json b/webapp/src/locales/de.json index f8918de2c..e39733ec0 100644 --- a/webapp/src/locales/de.json +++ b/webapp/src/locales/de.json @@ -824,10 +824,13 @@ "Limits": "Limits", "VoltageLimit": "Ladespannungslimit", "enableVoltageLimit": "Start Spannungslimit", + "stopVoltageLimitHint": "Maximal Spannung des Ladegeräts. Entspricht der geünschten Ladeschlussspannung der Batterie. Verwendet für die Automatische Leistungssteuerung und beim Notfallladen", "enableVoltageLimitHint": "Die automatische Leistungssteuerung wird deaktiviert wenn die Ausgangsspannung über diesem Wert liegt und wenn gleichzeitig die Ausgangsleistung unter die minimale Leistung fällt.\nDie automatische Leistungssteuerung wird re-aktiveiert wenn die Batteriespannung unter diesen Wert fällt.", + "maxPowerLimitHint": "Maximale Ausgangsleistung. Verwendet für die Automatische Leistungssteuerung und beim Notfallladen", "lowerPowerLimit": "Minimale Leistung", "upperPowerLimit": "Maximale Leistung", - "Seconds": "@:base.Seconds" + "Seconds": "@:base.Seconds", + "EnableEmergencyCharge": "Notfallladen: Batterie wird mit maximaler Leistung geladen wenn durch das Batterie BMS angefordert" }, "battery": { "battery": "Batterie", diff --git a/webapp/src/locales/en.json b/webapp/src/locales/en.json index 5aded483d..c28d21d2e 100644 --- a/webapp/src/locales/en.json +++ b/webapp/src/locales/en.json @@ -831,10 +831,13 @@ "Limits": "Limits", "VoltageLimit": "Charge Voltage limit", "enableVoltageLimit": "Re-enable voltage limit", + "stopVoltageLimitHint": "Maximum charger voltage. Equals battery charge voltage limit. Used for automatic power control and when emergency charging", "enableVoltageLimitHint": "Automatic power control is disabled if the output voltage is higher then this value and if the output power drops below the minimum output power limit (set below).\nAutomatic power control is re-enabled if the battery voltage drops below the value set in this field.", + "maxPowerLimitHint": "Maximum output power. Used for automatic power control and when emergency charging", "lowerPowerLimit": "Minimum output power", "upperPowerLimit": "Maximum output power", - "Seconds": "@:base.Seconds" + "Seconds": "@:base.Seconds", + "EnableEmergencyCharge": "Emergency charge. Battery charged with maximum power if requested by Battery BMS" }, "battery": { "battery": "Battery", diff --git a/webapp/src/locales/fr.json b/webapp/src/locales/fr.json index 90be3c99f..135a9ff99 100644 --- a/webapp/src/locales/fr.json +++ b/webapp/src/locales/fr.json @@ -823,10 +823,13 @@ "Limits": "Limits", "VoltageLimit": "Charge Voltage limit", "enableVoltageLimit": "Re-enable voltage limit", + "stopVoltageLimitHint": "Maximum charger voltage. Equals battery charge voltage limit. Used for automatic power control and when emergency charging", "enableVoltageLimitHint": "Automatic power control is disabled if the output voltage is higher then this value and if the output power drops below the minimum output power limit (set below).\nAutomatic power control is re-enabled if the battery voltage drops below the value set in this field.", + "maxPowerLimitHint": "Maximum output power. Used for automatic power control and when emergency charging", "lowerPowerLimit": "Minimum output power", "upperPowerLimit": "Maximum output power", - "Seconds": "@:base.Seconds" + "Seconds": "@:base.Seconds", + "EnableEmergencyCharge": "Emergency charge. Battery charged with maximum power if requested by Battery BMS" }, "battery": { "battery": "Battery", diff --git a/webapp/src/types/AcChargerConfig.ts b/webapp/src/types/AcChargerConfig.ts index 2e1b9ca89..e80a65aaa 100644 --- a/webapp/src/types/AcChargerConfig.ts +++ b/webapp/src/types/AcChargerConfig.ts @@ -6,4 +6,5 @@ export interface AcChargerConfig { enable_voltage_limit: number; lower_power_limit: number; upper_power_limit: number; + emergency_charge_enabled: boolean; } diff --git a/webapp/src/views/AcChargerAdminView.vue b/webapp/src/views/AcChargerAdminView.vue index 44f7f8b97..4f811d26f 100644 --- a/webapp/src/views/AcChargerAdminView.vue +++ b/webapp/src/views/AcChargerAdminView.vue @@ -28,10 +28,17 @@ v-model="acChargerConfigList.auto_power_enabled" type="checkbox" wide/> + + + v-show="acChargerConfigList.auto_power_enabled || acChargerConfigList.emergency_charge_enabled">
- +
W
- +