From e279ce08c4ba84d363a732974cfbaf48e58bd812 Mon Sep 17 00:00:00 2001 From: MalteSchm Date: Tue, 25 Apr 2023 09:35:20 +0200 Subject: [PATCH] Dynamic power control and more power control modes on the Huawei PSU --- README_onBattery.md | 14 ++ docs/MQTT_Topics.md | 2 +- include/Configuration.h | 5 + include/Huawei_can.h | 35 ++++- include/PowerLimiter.h | 2 +- include/defaults.h | 4 + src/Configuration.cpp | 10 ++ src/Huawei_can.cpp | 190 ++++++++++++++++++++---- src/MqttHandleHuawei.cpp | 26 +++- src/PowerLimiter.cpp | 15 +- src/WebApi_Huawei.cpp | 32 +++- src/WebApi_ws_vedirect_live.cpp | 2 +- webapp/src/locales/de.json | 7 + webapp/src/locales/en.json | 7 + webapp/src/locales/fr.json | 7 + webapp/src/types/AcChargerConfig.ts | 5 + webapp/src/views/AcChargerAdminView.vue | 50 +++++++ 17 files changed, 360 insertions(+), 53 deletions(-) diff --git a/README_onBattery.md b/README_onBattery.md index c52decc92..41af14c09 100644 --- a/README_onBattery.md +++ b/README_onBattery.md @@ -122,6 +122,20 @@ Other settings are: #### Power Limiter States ![PowerLimiterInverterStates](https://github.com/helgeerbe/OpenDTU-OnBattery/blob/development/docs/PowerLimiterInverterStates.png) +### Huawei PSU + +The Huawei PSU can be used to charge a battery. This can be be useful if an external (AC) connected solar system shall be utilized or if variable energy prices should be exploited. + +Some points for consideration are: +* Make sure to consider the PSU voltage range when selecting the battery voltage as lower voltages <42V are not supported. +* The PSU runs a noisy fan and it is therefore desireable to switch it off when not being used. Some users have found that switching the slot detect pins with a relay accomplishes this. A GPIO pin is made available from the ESP to turn the PSU on/off + +#### Operation modes + +openDTU-onBattery supports three operation modes for the Huawei PSU: +1. Fully manual - In this mode the PSU needs to be turned on/off externally using MQTT and voltage and current limits need to be provided. See [MQTT Documentation](docs/MQTT_Topics.md) for details on these commands +2. Manual with auto power on / off - In this mode the PSU is turned on when a current limit > 1A is set. If the current limit is < 1A for some time the PSU is turned off. Current and voltage limits need to be provided externally using MQTT. See [MQTT Documentation](docs/MQTT_Topics.md) for details on these commands. +3. Automatic - In this mode the PSU power is controlled by the Power Meter and information provided in the web-interface. If excess power is present the PSU is turned on. The voltage limit is set as per web-interface and the current limit is set so that the maximum PSU output power equals the Power Meter value. Minium and maximum PSU power levels as configured in the web-interface are respected in this process. The PSU is turned off if the output current is limited and the output power drops below the minium power level. This will disable automatic mode until the battery is discharged below the start voltage level (set in the web-interface). This mode can be enabled using the web-interface and MQTT. See [MQTT Documentation](docs/MQTT_Topics.md) ## Troubleshooting diff --git a/docs/MQTT_Topics.md b/docs/MQTT_Topics.md index 7289ab652..ec3885f66 100644 --- a/docs/MQTT_Topics.md +++ b/docs/MQTT_Topics.md @@ -158,7 +158,7 @@ cmd topics are used to set values. Status topics are updated from values set in | --------------------------------------- | ----- | ---------------------------------------------------- | -------------------------- | | huawei/cmd/limit_online_voltage | W | Online voltage (i.e. CAN bus connected) | Volt (V) | | huawei/cmd/limit_online_current | W | Online current (i.e. CAN bus connected) | Ampere (A) | -| huawei/cmd/power | W | Controls output pin GPIO to drive solid state relais | 0 / 1 | +| huawei/cmd/mode | W | Controls GPIO output pin to switch slot detect | 0 (off) / 1 (on) / 2 (set automatically depending on online_current value) / 3 (set automatically based on Power Meter reading ) | | huawei/data_age | R | How old the data is | Seconds | | huawei/input_voltage | R | Input voltage | Volt (V) | | huawei/input_current | R | Input current | Ampere (A) | diff --git a/include/Configuration.h b/include/Configuration.h index e109ab645..75c6c7020 100644 --- a/include/Configuration.h +++ b/include/Configuration.h @@ -157,6 +157,11 @@ struct CONFIG_T { bool Battery_Enabled; bool Huawei_Enabled; + bool Huawei_Auto_Power_Enabled; + float Huawei_Auto_Power_Voltage_Limit; + float Huawei_Auto_Power_Enable_Voltage_Limit; + float Huawei_Auto_Power_Lower_Power_Limit; + float Huawei_Auto_Power_Upper_Power_Limit; char Security_Password[WIFI_MAX_PASSWORD_STRLEN + 1]; bool Security_AllowReadonly; diff --git a/include/Huawei_can.h b/include/Huawei_can.h index fd3ccbe7f..f49a31fb2 100644 --- a/include/Huawei_can.h +++ b/include/Huawei_can.h @@ -52,6 +52,16 @@ #define R48xx_DATA_OUTPUT_CURRENT 0x81 #define R48xx_DATA_OUTPUT_CURRENT1 0x82 +#define HUAWEI_MODE_OFF 0 +#define HUAWEI_MODE_ON 1 +#define HUAWEI_MODE_AUTO_EXT 2 +#define HUAWEI_MODE_AUTO_INT 3 + +// Wait time/current before shuting down the PSU / charger +// This is set to allow the fan to run for some time +#define HUAWEI_AUTO_MODE_SHUTDOWN_DELAY 60000 +#define HUAWEI_AUTO_MODE_SHUTDOWN_CURRENT 1.0 + struct RectifierParameters_t { float input_voltage; float input_frequency; @@ -72,24 +82,33 @@ class HuaweiCanClass { void init(uint8_t huawei_miso, uint8_t huawei_mosi, uint8_t huawei_clk, uint8_t huawei_irq, uint8_t huawei_cs, uint8_t huawei_power); void loop(); void setValue(float in, uint8_t parameterType); - void setPower(bool power); + void setMode(uint8_t mode); RectifierParameters_t * get(); uint32_t getLastUpdate(); + bool getAutoPowerStatus(); private: void sendRequest(); void onReceive(uint8_t* frame, uint8_t len); - uint32_t previousMillis; - uint32_t lastUpdate; - RectifierParameters_t _rp; - SPIClass *spi; MCP_CAN *CAN; - uint8_t _huawei_irq; - uint8_t _huawei_power; - bool initialized = false; + bool _initialized = false; + uint8_t _huawei_irq; // IRQ pin + uint8_t _huawei_power; // Power pin + uint8_t _mode = HUAWEI_MODE_AUTO_EXT; + + RectifierParameters_t _rp; + + uint32_t _lastUpdateReceivedMillis; // Timestamp for last data seen from the PSU + uint32_t _nextRequestMillis = 0; // When to send next data request to PSU + uint32_t _nextAutoModePeriodicIntMillis; // When to send the next output volume request in Automatic mode + uint32_t _lastPowerMeterUpdateReceivedMillis; // Timestamp of last power meter value + uint32_t _outputCurrentOnSinceMillis; // Timestamp since when the PSU was idle at zero amps + bool _newOutputPowerReceived = false; + uint8_t _autoPowerEnabled = false; + bool _autoPowerActive = false; }; extern HuaweiCanClass HuaweiCan; diff --git a/include/PowerLimiter.h b/include/PowerLimiter.h index 24694030a..797d0e5e3 100644 --- a/include/PowerLimiter.h +++ b/include/PowerLimiter.h @@ -33,7 +33,7 @@ class PowerLimiterClass { void init(); void loop(); uint8_t getPowerLimiterState(); - int32_t getLastRequestedPowewrLimit(); + int32_t getLastRequestedPowerLimit(); void setMode(uint8_t mode); bool getMode(); void calcNextInverterRestart(); diff --git a/include/defaults.h b/include/defaults.h index 3f58741f8..c069ca109 100644 --- a/include/defaults.h +++ b/include/defaults.h @@ -130,3 +130,7 @@ #define BATTERY_ENABLED false #define HUAWEI_ENABLED false +#define HUAWEI_AUTO_POWER_VOLTAGE_LIMIT 42.0 +#define HUAWEI_AUTO_POWER_ENABLE_VOLTAGE_LIMIT 42.0 +#define HUAWEI_AUTO_POWER_LOWER_POWER_LIMIT 150 +#define HUAWEI_AUTO_POWER_UPPER_POWER_LIMIT 2000 diff --git a/src/Configuration.cpp b/src/Configuration.cpp index 741ac1d39..e078e749a 100644 --- a/src/Configuration.cpp +++ b/src/Configuration.cpp @@ -173,6 +173,11 @@ bool ConfigurationClass::write() JsonObject huawei = doc.createNestedObject("huawei"); huawei["enabled"] = config.Huawei_Enabled; + huawei["auto_power_enabled"] = config.Huawei_Auto_Power_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; + huawei["upper_power_limit"] = config.Huawei_Auto_Power_Upper_Power_Limit; // Serialize JSON to file if (serializeJson(doc, f) == 0) { @@ -374,6 +379,11 @@ bool ConfigurationClass::read() JsonObject huawei = doc["huawei"]; config.Huawei_Enabled = huawei["enabled"] | HUAWEI_ENABLED; + config.Huawei_Auto_Power_Enabled = huawei["auto_power_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; + config.Huawei_Auto_Power_Upper_Power_Limit = huawei["upper_power_limit"] | HUAWEI_AUTO_POWER_UPPER_POWER_LIMIT; f.close(); return true; diff --git a/src/Huawei_can.cpp b/src/Huawei_can.cpp index 3e112f9fb..daef2ac25 100644 --- a/src/Huawei_can.cpp +++ b/src/Huawei_can.cpp @@ -4,6 +4,8 @@ */ #include "Huawei_can.h" #include "MessageOutput.h" +#include "PowerMeter.h" +#include "PowerLimiter.h" #include "Configuration.h" #include #include @@ -14,7 +16,9 @@ HuaweiCanClass HuaweiCan; void HuaweiCanClass::init(uint8_t huawei_miso, uint8_t huawei_mosi, uint8_t huawei_clk, uint8_t huawei_irq, uint8_t huawei_cs, uint8_t huawei_power) { - initialized = false; + if (_initialized) { + return; + } const CONFIG_T& config = Configuration.get(); @@ -32,12 +36,12 @@ void HuaweiCanClass::init(uint8_t huawei_miso, uint8_t huawei_mosi, uint8_t huaw CAN = new MCP_CAN(spi, huawei_cs); if (!CAN->begin(MCP_ANY, CAN_125KBPS, MCP_8MHZ) == CAN_OK) { - MessageOutput.println("Error Initializing MCP2515..."); + MessageOutput.println("[HuaweiCanClass::init] Error Initializing MCP2515..."); return; } - MessageOutput.println("MCP2515 Initialized Successfully!"); - initialized = true; + MessageOutput.println("[HuaweiCanClass::init] MCP2515 Initialized Successfully!"); + _initialized = true; // Change to normal mode to allow messages to be transmitted CAN->setMode(MCP_NORMAL); @@ -45,6 +49,10 @@ void HuaweiCanClass::init(uint8_t huawei_miso, uint8_t huawei_mosi, uint8_t huaw pinMode(huawei_power, OUTPUT); digitalWrite(huawei_power, HIGH); _huawei_power = huawei_power; + + if (config.Huawei_Auto_Power_Enabled) { + _mode = HUAWEI_MODE_AUTO_INT; + } } RectifierParameters_t * HuaweiCanClass::get() @@ -54,23 +62,18 @@ RectifierParameters_t * HuaweiCanClass::get() uint32_t HuaweiCanClass::getLastUpdate() { - return lastUpdate; + return _lastUpdateReceivedMillis; } + uint8_t data[8] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; // Requests current values from Huawei unit. Response is handled in onReceive void HuaweiCanClass::sendRequest() { - if (previousMillis < millis()) { - // Send extended message - byte sndStat = CAN->sendMsgBuf(0x108040FE, 1, 8, data); - if(sndStat == CAN_OK) { - MessageOutput.println("Message Sent Successfully!"); - } else { - MessageOutput.println("Error Sending Message..."); - } - - previousMillis += 5000; + // Send extended message + byte sndStat = CAN->sendMsgBuf(0x108040FE, 1, 8, data); + if(sndStat != CAN_OK) { + MessageOutput.println("[HuaweiCanClass::sendRequest] Error Sending Message..."); } } @@ -97,8 +100,9 @@ void HuaweiCanClass::onReceive(uint8_t* frame, uint8_t len) case R48xx_DATA_OUTPUT_POWER: _rp.output_power = value / 1024.0; + _newOutputPowerReceived = true; // We'll only update last update on the important params - lastUpdate = millis(); + _lastUpdateReceivedMillis = millis(); break; case R48xx_DATA_EFFICIENCY: @@ -110,7 +114,7 @@ void HuaweiCanClass::onReceive(uint8_t* frame, uint8_t len) break; case R48xx_DATA_OUTPUT_CURRENT_MAX: - _rp.max_output_current = value / MAX_CURRENT_MULTIPLIER; + _rp.max_output_current = static_cast(value) / MAX_CURRENT_MULTIPLIER; break; case R48xx_DATA_INPUT_VOLTAGE: @@ -133,12 +137,16 @@ void HuaweiCanClass::onReceive(uint8_t* frame, uint8_t len) case R48xx_DATA_OUTPUT_CURRENT: _rp.output_current = value / 1024.0; + if (_rp.output_current > HUAWEI_AUTO_MODE_SHUTDOWN_CURRENT) { + _outputCurrentOnSinceMillis = millis(); + } + /* This is normally the last parameter received. Print */ - lastUpdate = millis(); // We'll only update last update on the important params + _lastUpdateReceivedMillis = millis(); // We'll only update last update on the important params - MessageOutput.printf("In: %.02fV, %.02fA, %.02fW\n", _rp.input_voltage, _rp.input_current, _rp.input_power); - MessageOutput.printf("Out: %.02fV, %.02fA of %.02fA, %.02fW\n", _rp.output_voltage, _rp.output_current, _rp.max_output_current, _rp.output_power); - MessageOutput.printf("Eff: %.01f%%, Temp in: %.01fC, Temp out: %.01fC\n", _rp.efficiency * 100, _rp.input_temp, _rp.output_temp); + MessageOutput.printf("[HuaweiCanClass::onReceive] In: %.02fV, %.02fA, %.02fW\n", _rp.input_voltage, _rp.input_current, _rp.input_power); + MessageOutput.printf("[HuaweiCanClass::onReceive] Out: %.02fV, %.02fA of %.02fA, %.02fW\n", _rp.output_voltage, _rp.output_current, _rp.max_output_current, _rp.output_power); + MessageOutput.printf("[HuaweiCanClass::onReceive] Eff: %.01f%%, Temp in: %.01fC, Temp out: %.01fC\n", _rp.efficiency * 100, _rp.input_temp, _rp.output_temp); break; @@ -157,7 +165,7 @@ void HuaweiCanClass::loop() const CONFIG_T& config = Configuration.get(); - if (!config.Huawei_Enabled || !initialized) { + if (!config.Huawei_Enabled || !_initialized) { return; } @@ -175,12 +183,110 @@ void HuaweiCanClass::loop() // https://www.beyondlogic.org/review-huawei-r4850g2-power-supply-53-5vdc-3kw/ } } - sendRequest(); + + // Request updated values in regular intervals + if (_nextRequestMillis < millis()) { + MessageOutput.println("[HUAWEI********************* Sending request"); + sendRequest(); + _nextRequestMillis = millis() + 5000; + } + + // If the output current is low for a long time, shutdown PSU + if (_outputCurrentOnSinceMillis + HUAWEI_AUTO_MODE_SHUTDOWN_DELAY < millis() && + (_mode == HUAWEI_MODE_AUTO_EXT || _mode == HUAWEI_MODE_AUTO_INT)) { + digitalWrite(_huawei_power, 1); + } + + // *********************** + // Automatic power control + // *********************** + + if (_mode == HUAWEI_MODE_AUTO_INT ) { + + // Set voltage limit in periodic intervals + 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; + } + + // Re-enable automatic power control if the output voltage has dropped below threshold + if(_rp.output_voltage < config.Huawei_Auto_Power_Enable_Voltage_Limit ) { + _autoPowerEnabled = 10; + } + + if ((PowerLimiter.getPowerLimiterState() == PL_UI_STATE_INACTIVE || + PowerLimiter.getPowerLimiterState() == PL_UI_STATE_CHARGING) && + PowerMeter.getLastPowerMeterUpdate() > _lastPowerMeterUpdateReceivedMillis && + _newOutputPowerReceived && + _autoPowerEnabled > 0) { + // Power Limiter is inactive and we have received both: + // a new PowerMeter and a new output power value. Also we're _autoPowerEnabled + // So we're good to calculate a new limit + + _newOutputPowerReceived = false; + _lastPowerMeterUpdateReceivedMillis = PowerMeter.getLastPowerMeterUpdate(); + + // Calculate new power limit + float newPowerLimit = -1 * round(PowerMeter.getPowerTotal()); + newPowerLimit += _rp.output_power; + MessageOutput.printf("[HuaweiCanClass::loop] PL: %f, OP: %f \r\n", newPowerLimit, _rp.output_power); + + if (newPowerLimit > config.Huawei_Auto_Power_Lower_Power_Limit) { + + // Check if the output power has dropped below the lower limit (i.e. the battery is full) + // and if the PSU should be turned off. Also we use a simple counter mechanism here to be able + // to ramp up from zero output power when starting up + if (_rp.output_power < config.Huawei_Auto_Power_Lower_Power_Limit) { + MessageOutput.printf("[HuaweiCanClass::loop] Power and voltage limit reached. Disabling automatic power control .... \r\n"); + _autoPowerEnabled--; + if (_autoPowerEnabled == 0) { + _autoPowerActive = false; + setValue(0, HUAWEI_ONLINE_CURRENT); + return; + } + } else { + _autoPowerEnabled = 10; + } + + // Limit power to maximum + if (newPowerLimit > config.Huawei_Auto_Power_Upper_Power_Limit) { + newPowerLimit = config.Huawei_Auto_Power_Upper_Power_Limit; + } + + // Set the actual output limit + 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); + _autoPowerActive = true; + setValue(outputCurrent, HUAWEI_ONLINE_CURRENT); + + // Issue next request for updated output values in 2s to allow for output stabilization + _nextRequestMillis = millis() + 2000; + } else { + // requested PL is below minium. Set current to 0 + _autoPowerActive = false; + setValue(0.0, HUAWEI_ONLINE_CURRENT); + } + } + } } void HuaweiCanClass::setValue(float in, uint8_t parameterType) { uint16_t value; + + if (in < 0) { + MessageOutput.printf("[HuaweiCanClass::setValue] Error: Tried to set voltage/current to negative value %f \r\n", in); + } + + // Start PSU if needed + if (in > HUAWEI_AUTO_MODE_SHUTDOWN_CURRENT && parameterType == HUAWEI_ONLINE_CURRENT && + (_mode == HUAWEI_MODE_AUTO_EXT || _mode == HUAWEI_MODE_AUTO_INT)) { + digitalWrite(_huawei_power, 0); + _outputCurrentOnSinceMillis = millis(); + } + if (parameterType == HUAWEI_OFFLINE_VOLTAGE || parameterType == HUAWEI_ONLINE_VOLTAGE) { value = in * 1024; } else if (parameterType == HUAWEI_OFFLINE_CURRENT || parameterType == HUAWEI_ONLINE_CURRENT) { @@ -193,13 +299,39 @@ void HuaweiCanClass::setValue(float in, uint8_t parameterType) // Send extended message byte sndStat = CAN->sendMsgBuf(0x108180FE, 1, 8, data); - if (sndStat == CAN_OK) { - MessageOutput.println("Message Sent Successfully!"); - } else { - MessageOutput.println("Error Sending Message..."); + if (sndStat != CAN_OK) { + MessageOutput.println("[HuaweiCanClass::setValue] Error Sending Message..."); } } -void HuaweiCanClass::setPower(bool power) { - digitalWrite(_huawei_power, !power); +void HuaweiCanClass::setMode(uint8_t mode) { + const CONFIG_T& config = Configuration.get(); + + if(mode == HUAWEI_MODE_OFF) { + digitalWrite(_huawei_power, 1); + _mode = HUAWEI_MODE_OFF; + } + if(mode == HUAWEI_MODE_ON) { + digitalWrite(_huawei_power, 0); + _mode = HUAWEI_MODE_ON; + } + + if (mode == HUAWEI_MODE_AUTO_INT && !config.Huawei_Auto_Power_Enabled ) { + MessageOutput.println("[HuaweiCanClass::setMode] WARNING: Trying to setmode to internal automatic power control without being enabled in the UI. Ignoring command"); + return; + } + + if (_mode == HUAWEI_MODE_AUTO_INT && mode != HUAWEI_MODE_AUTO_INT) { + _autoPowerActive = false; + setValue(0, HUAWEI_ONLINE_CURRENT); + } + + if(mode == HUAWEI_MODE_AUTO_EXT || mode == HUAWEI_MODE_AUTO_INT) { + _mode = mode; + } +} + +bool HuaweiCanClass::getAutoPowerStatus() { + return _autoPowerActive; } + diff --git a/src/MqttHandleHuawei.cpp b/src/MqttHandleHuawei.cpp index cae27c40f..1047d564c 100644 --- a/src/MqttHandleHuawei.cpp +++ b/src/MqttHandleHuawei.cpp @@ -12,7 +12,7 @@ #define TOPIC_SUB_LIMIT_ONLINE_VOLTAGE "limit_online_voltage" #define TOPIC_SUB_LIMIT_ONLINE_CURRENT "limit_online_current" -#define TOPIC_SUB_POWER "power" +#define TOPIC_SUB_MODE "mode" MqttHandleHuaweiClass MqttHandleHuawei; @@ -28,7 +28,7 @@ void MqttHandleHuaweiClass::init() String topic = MqttSettings.getPrefix(); MqttSettings.subscribe(String(topic + "huawei/cmd/" + TOPIC_SUB_LIMIT_ONLINE_VOLTAGE).c_str(), 0, std::bind(&MqttHandleHuaweiClass::onMqttMessage, this, _1, _2, _3, _4, _5, _6)); MqttSettings.subscribe(String(topic + "huawei/cmd/" + TOPIC_SUB_LIMIT_ONLINE_CURRENT).c_str(), 0, std::bind(&MqttHandleHuaweiClass::onMqttMessage, this, _1, _2, _3, _4, _5, _6)); - MqttSettings.subscribe(String(topic + "huawei/cmd/" + TOPIC_SUB_POWER).c_str(), 0, std::bind(&MqttHandleHuaweiClass::onMqttMessage, this, _1, _2, _3, _4, _5, _6)); + MqttSettings.subscribe(String(topic + "huawei/cmd/" + TOPIC_SUB_MODE).c_str(), 0, std::bind(&MqttHandleHuaweiClass::onMqttMessage, this, _1, _2, _3, _4, _5, _6)); _lastPublish = millis(); @@ -108,13 +108,23 @@ void MqttHandleHuaweiClass::onMqttMessage(const espMqttClientTypes::MessagePrope // Set current limit MessageOutput.printf("Limit Current: %f A\r\n", payload_val); HuaweiCan.setValue(payload_val, HUAWEI_ONLINE_CURRENT); - } else if (!strcmp(setting, TOPIC_SUB_POWER)) { + } else if (!strcmp(setting, TOPIC_SUB_MODE)) { // Control power on/off - MessageOutput.printf("Power: %f A\r\n", payload_val); - if(payload_val > 0) { - HuaweiCan.setPower(true); - } else { - HuaweiCan.setPower(false); + if(payload_val == 3) { + MessageOutput.println("[Huawei MQTT::] Received MQTT msg. New mode: Full internal control"); + HuaweiCan.setMode(HUAWEI_MODE_AUTO_INT); + } + if(payload_val == 2) { + MessageOutput.println("[Huawei MQTT::] Received MQTT msg. New mode: Internal on/off control, external power limit"); + HuaweiCan.setMode(HUAWEI_MODE_AUTO_EXT); + } + if(payload_val == 1) { + MessageOutput.println("[Huawei MQTT::] Received MQTT msg. New mode: Turned ON"); + HuaweiCan.setMode(HUAWEI_MODE_ON); + } + if(payload_val == 0) { + MessageOutput.println("[Huawei MQTT::] Received MQTT msg. New mode: Turned OFF"); + HuaweiCan.setMode(HUAWEI_MODE_OFF); } } } \ No newline at end of file diff --git a/src/PowerLimiter.cpp b/src/PowerLimiter.cpp index cbaf80dcd..9803a244a 100644 --- a/src/PowerLimiter.cpp +++ b/src/PowerLimiter.cpp @@ -9,6 +9,7 @@ #include "Configuration.h" #include "MqttSettings.h" #include "NetworkSettings.h" +#include "Huawei_can.h" #include #include "MessageOutput.h" #include @@ -199,8 +200,8 @@ uint8_t PowerLimiterClass::getPowerLimiterState() { return PL_UI_STATE_INACTIVE; } -int32_t PowerLimiterClass::getLastRequestedPowewrLimit() { - return _lastRequestedPowerLimit; +int32_t PowerLimiterClass::getLastRequestedPowerLimit() { + return _lastRequestedPowerLimit; } bool PowerLimiterClass::getMode() { @@ -279,7 +280,15 @@ int32_t PowerLimiterClass::calcPowerLimit(std::shared_ptr inve if(batteryDischargeEnabled && useFullSolarPassthrough(inverter)) { // Case 5 newPowerLimit = newPowerLimit > adjustedVictronChargePower ? newPowerLimit : adjustedVictronChargePower; - } + } else { + // We check if the PSU is on and disable the Power Limiter in this case. + // The PSU should reduce power or shut down first before the Power Limiter kicks in + // The only case where this is not desired is if the battery is over the Full Solar Passthrough Threshold + // In this case the Power Limiter should start. The PSU will shutdown when the Power Limiter is active + if (HuaweiCan.getAutoPowerStatus()) { + return 0; + } + } // We should use Victron solar power only (corrected by efficiency factor) if ((solarPowerEnabled && !batteryDischargeEnabled) || (_mode == PL_MODE_SOLAR_PT_ONLY)) { diff --git a/src/WebApi_Huawei.cpp b/src/WebApi_Huawei.cpp index cd296cc04..f8f75e234 100644 --- a/src/WebApi_Huawei.cpp +++ b/src/WebApi_Huawei.cpp @@ -190,6 +190,11 @@ void WebApiHuaweiClass::onAdminGet(AsyncWebServerRequest* request) const CONFIG_T& config = Configuration.get(); root[F("enabled")] = config.Huawei_Enabled; + root[F("auto_power_enabled")] = config.Huawei_Auto_Power_Enabled; + root[F("voltage_limit")] = static_cast(config.Huawei_Auto_Power_Voltage_Limit * 100) / 100.0; + root[F("enable_voltage_limit")] = static_cast(config.Huawei_Auto_Power_Enable_Voltage_Limit * 100) / 100.0; + root[F("lower_power_limit")] = config.Huawei_Auto_Power_Lower_Power_Limit; + root[F("upper_power_limit")] = config.Huawei_Auto_Power_Upper_Power_Limit; response->setLength(); request->send(response); @@ -234,7 +239,11 @@ void WebApiHuaweiClass::onAdminPost(AsyncWebServerRequest* request) return; } - if (!(root.containsKey("enabled"))) { + if (!(root.containsKey("enabled")) || + !(root.containsKey("auto_power_enabled")) || + !(root.containsKey("voltage_limit")) || + !(root.containsKey("lower_power_limit")) || + !(root.containsKey("upper_power_limit"))) { retMsg[F("message")] = F("Values are missing!"); retMsg[F("code")] = WebApiError::GenericValueMissing; response->setLength(); @@ -244,6 +253,11 @@ void WebApiHuaweiClass::onAdminPost(AsyncWebServerRequest* request) CONFIG_T& config = Configuration.get(); config.Huawei_Enabled = root[F("enabled")].as(); + config.Huawei_Auto_Power_Enabled = root[F("auto_power_enabled")].as(); + config.Huawei_Auto_Power_Voltage_Limit = root[F("voltage_limit")].as(); + config.Huawei_Auto_Power_Enable_Voltage_Limit = root[F("enable_voltage_limit")].as(); + config.Huawei_Auto_Power_Lower_Power_Limit = root[F("lower_power_limit")].as(); + config.Huawei_Auto_Power_Upper_Power_Limit = root[F("upper_power_limit")].as(); Configuration.write(); retMsg[F("type")] = F("success"); @@ -254,6 +268,7 @@ void WebApiHuaweiClass::onAdminPost(AsyncWebServerRequest* request) request->send(response); const PinMapping_t& pin = PinMapping.get(); + // Properly turn this on if (config.Huawei_Enabled) { MessageOutput.println(F("Initialize Huawei AC charger interface... ")); if (PinMapping.isValidHuaweiConfig()) { @@ -265,5 +280,18 @@ void WebApiHuaweiClass::onAdminPost(AsyncWebServerRequest* request) } } - HuaweiCan.setPower(config.Huawei_Enabled); + // Properly turn this off + if (!config.Huawei_Enabled) { + HuaweiCan.setValue(0, HUAWEI_ONLINE_CURRENT); + delay(500); + HuaweiCan.setMode(HUAWEI_MODE_OFF); + return; + } + + if (config.Huawei_Auto_Power_Enabled) { + HuaweiCan.setMode(HUAWEI_MODE_AUTO_INT); + return; + } + + HuaweiCan.setMode(HUAWEI_MODE_AUTO_EXT); } diff --git a/src/WebApi_ws_vedirect_live.cpp b/src/WebApi_ws_vedirect_live.cpp index 539917a74..ec243ad39 100644 --- a/src/WebApi_ws_vedirect_live.cpp +++ b/src/WebApi_ws_vedirect_live.cpp @@ -145,7 +145,7 @@ void WebApiWsVedirectLiveClass::generateJsonResponse(JsonVariant& root) root["dpl"]["PLSTATE"] = -1; if (Configuration.get().PowerLimiter_Enabled) root["dpl"]["PLSTATE"] = PowerLimiter.getPowerLimiterState(); - root["dpl"]["PLLIMIT"] = PowerLimiter.getLastRequestedPowewrLimit(); + root["dpl"]["PLLIMIT"] = PowerLimiter.getLastRequestedPowerLimit(); if (VeDirect.getLastUpdate() > _newestVedirectTimestamp) { _newestVedirectTimestamp = VeDirect.getLastUpdate(); diff --git a/webapp/src/locales/de.json b/webapp/src/locales/de.json index de2a324ac..37cf38047 100644 --- a/webapp/src/locales/de.json +++ b/webapp/src/locales/de.json @@ -733,6 +733,13 @@ "ChargerSettings": "AC Ladegerät Einstellungen", "Configuration": "AC Ladegerät Konfiguration", "EnableHuawei": "Huawei R4850G2 an CAN Bus Interface aktiv", + "EnableAutoPower": "Automatische Leistungssteuerung", + "Limits": "Limits", + "VoltageLimit": "Ladespannungslimit", + "enableVoltageLimit": "Start Spannungslimit", + "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.", + "lowerPowerLimit": "Minimale Leistung", + "upperPowerLimit": "Maximale Leistung", "Seconds": "@:dtuadmin.Seconds", "Save": "@:dtuadmin.Save" }, diff --git a/webapp/src/locales/en.json b/webapp/src/locales/en.json index 725ab9e18..7404f285a 100644 --- a/webapp/src/locales/en.json +++ b/webapp/src/locales/en.json @@ -738,6 +738,13 @@ "ChargerSettings": "AC Charger Settings", "Configuration": "AC Charger Configuration", "EnableHuawei": "Enable Huawei R4850G2 on CAN Bus Interface", + "EnableAutoPower": "Automatic power control", + "Limits": "Limits", + "VoltageLimit": "Charge Voltage limit", + "enableVoltageLimit": "Re-enable voltage limit", + "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.", + "lowerPowerLimit": "Minimum output power", + "upperPowerLimit": "Maximum output power", "Seconds": "@:dtuadmin.Seconds", "Save": "@:dtuadmin.Save" }, diff --git a/webapp/src/locales/fr.json b/webapp/src/locales/fr.json index bca16e2a8..657a815a3 100644 --- a/webapp/src/locales/fr.json +++ b/webapp/src/locales/fr.json @@ -693,6 +693,13 @@ "ChargerSettings": "AC Charger Settings", "Configuration": "AC Charger Configuration", "EnableHuawei": "Enable Huawei R4850G2 on CAN Bus Interface", + "EnableAutoPower": "Automatic power control", + "Limits": "Limits", + "VoltageLimit": "Charge Voltage limit", + "enableVoltageLimit": "Re-enable voltage limit", + "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.", + "lowerPowerLimit": "Minimum output power", + "upperPowerLimit": "Maximum output power", "Seconds": "@:dtuadmin.Seconds", "Save": "@:dtuadmin.Save" }, diff --git a/webapp/src/types/AcChargerConfig.ts b/webapp/src/types/AcChargerConfig.ts index 1c265e13f..e50dc7aae 100644 --- a/webapp/src/types/AcChargerConfig.ts +++ b/webapp/src/types/AcChargerConfig.ts @@ -1,3 +1,8 @@ export interface AcChargerConfig { enabled: boolean; + auto_power_enabled: boolean; + voltage_limit: number; + enable_voltage_limit: number; + lower_power_limit: number; + upper_power_limit: number; } diff --git a/webapp/src/views/AcChargerAdminView.vue b/webapp/src/views/AcChargerAdminView.vue index e4a6c9f06..d6473769c 100644 --- a/webapp/src/views/AcChargerAdminView.vue +++ b/webapp/src/views/AcChargerAdminView.vue @@ -9,6 +9,54 @@ + + + +
+ +
+
+ + V +
+
+ +
+
+ + V +
+
+ +
+
+ + W +
+
+ +
+
+ + W +
+
+
+
@@ -21,6 +69,7 @@ import BasePage from '@/components/BasePage.vue'; import BootstrapAlert from "@/components/BootstrapAlert.vue"; import CardElement from '@/components/CardElement.vue'; import InputElement from '@/components/InputElement.vue'; +import { BIconInfoCircle } from 'bootstrap-icons-vue'; import type { AcChargerConfig } from "@/types/AcChargerConfig"; import { authHeader, handleResponse } from '@/utils/authentication'; import { defineComponent } from 'vue'; @@ -31,6 +80,7 @@ export default defineComponent({ BootstrapAlert, CardElement, InputElement, + BIconInfoCircle, }, data() { return {