From 69581197231974421635a2213bd0d6bb421bf241 Mon Sep 17 00:00:00 2001 From: MalteSchm Date: Mon, 18 Mar 2024 09:08:40 +0100 Subject: [PATCH] Feature: BMS initiated emergency charging This change logically connects the AC-Charger with the BMS to add BMS initiated emergency charging and respecting BMS current limits. --- include/BatteryStats.h | 10 ++++- include/Configuration.h | 1 + include/Huawei_can.h | 1 + src/Configuration.cpp | 2 + src/Huawei_can.cpp | 52 +++++++++++++++++++++---- src/WebApi_Huawei.cpp | 4 +- webapp/src/locales/de.json | 5 ++- webapp/src/locales/en.json | 5 ++- webapp/src/locales/fr.json | 5 ++- webapp/src/types/AcChargerConfig.ts | 1 + webapp/src/views/AcChargerAdminView.vue | 15 +++++-- 11 files changed, 84 insertions(+), 17 deletions(-) diff --git a/include/BatteryStats.h b/include/BatteryStats.h index 86e1750b2..5c5f06b1c 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 { @@ -37,7 +38,10 @@ class BatteryStats { // returns true if the battery reached a critically low voltage/SoC, // such that it is in need of charging to prevent degredation. - virtual bool needsCharging() const { return false; } + virtual bool getImmediateChargingRequest() const { return false; }; + + virtual float getChargeCurrent() const { return 0; }; + virtual float getChargeCurrentLimitation() const { return FLT_MAX; }; protected: virtual void mqttPublish() const; @@ -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); } diff --git a/include/Configuration.h b/include/Configuration.h index 077ba9414..c14b1842b 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 c00617cf7..b0a77faf1 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; @@ -464,6 +465,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 df1d5eb24..db464a20a 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 @@ -377,10 +406,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); @@ -415,6 +450,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 ff372786e..3ee53c03e 100644 --- a/webapp/src/locales/de.json +++ b/webapp/src/locales/de.json @@ -836,10 +836,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 f7e7da98b..77c087463 100644 --- a/webapp/src/locales/en.json +++ b/webapp/src/locales/en.json @@ -843,10 +843,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 df56ac072..d60cb5b65 100644 --- a/webapp/src/locales/fr.json +++ b/webapp/src/locales/fr.json @@ -834,10 +834,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
- +