Merge branch 'pr/MalteSchm/288' into development

This commit is contained in:
helgeerbe 2023-07-02 14:28:51 +02:00
commit 1f39ed7b9b
15 changed files with 358 additions and 51 deletions

View File

@ -122,6 +122,20 @@ Other settings are:
#### Power Limiter States #### Power Limiter States
![PowerLimiterInverterStates](https://github.com/helgeerbe/OpenDTU-OnBattery/blob/development/docs/PowerLimiterInverterStates.png) ![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 ## Troubleshooting

View File

@ -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_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/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/data_age | R | How old the data is | Seconds |
| huawei/input_voltage | R | Input voltage | Volt (V) | | huawei/input_voltage | R | Input voltage | Volt (V) |
| huawei/input_current | R | Input current | Ampere (A) | | huawei/input_current | R | Input current | Ampere (A) |

View File

@ -157,6 +157,11 @@ struct CONFIG_T {
bool Battery_Enabled; bool Battery_Enabled;
bool Huawei_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]; char Security_Password[WIFI_MAX_PASSWORD_STRLEN + 1];
bool Security_AllowReadonly; bool Security_AllowReadonly;

View File

@ -52,6 +52,16 @@
#define R48xx_DATA_OUTPUT_CURRENT 0x81 #define R48xx_DATA_OUTPUT_CURRENT 0x81
#define R48xx_DATA_OUTPUT_CURRENT1 0x82 #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 { struct RectifierParameters_t {
float input_voltage; float input_voltage;
float input_frequency; float input_frequency;
@ -72,24 +82,33 @@ public:
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 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 loop();
void setValue(float in, uint8_t parameterType); void setValue(float in, uint8_t parameterType);
void setPower(bool power); void setMode(uint8_t mode);
RectifierParameters_t * get(); RectifierParameters_t * get();
uint32_t getLastUpdate(); uint32_t getLastUpdate();
bool getAutoPowerStatus();
private: private:
void sendRequest(); void sendRequest();
void onReceive(uint8_t* frame, uint8_t len); void onReceive(uint8_t* frame, uint8_t len);
uint32_t previousMillis;
uint32_t lastUpdate;
RectifierParameters_t _rp;
SPIClass *spi; SPIClass *spi;
MCP_CAN *CAN; MCP_CAN *CAN;
uint8_t _huawei_irq; bool _initialized = false;
uint8_t _huawei_power; uint8_t _huawei_irq; // IRQ pin
bool initialized = false; 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; extern HuaweiCanClass HuaweiCan;

View File

@ -130,3 +130,7 @@
#define BATTERY_ENABLED false #define BATTERY_ENABLED false
#define HUAWEI_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

View File

@ -173,6 +173,11 @@ bool ConfigurationClass::write()
JsonObject huawei = doc.createNestedObject("huawei"); JsonObject huawei = doc.createNestedObject("huawei");
huawei["enabled"] = config.Huawei_Enabled; 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 // Serialize JSON to file
if (serializeJson(doc, f) == 0) { if (serializeJson(doc, f) == 0) {
@ -374,6 +379,11 @@ bool ConfigurationClass::read()
JsonObject huawei = doc["huawei"]; JsonObject huawei = doc["huawei"];
config.Huawei_Enabled = huawei["enabled"] | HUAWEI_ENABLED; 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(); f.close();
return true; return true;

View File

@ -4,6 +4,8 @@
*/ */
#include "Huawei_can.h" #include "Huawei_can.h"
#include "MessageOutput.h" #include "MessageOutput.h"
#include "PowerMeter.h"
#include "PowerLimiter.h"
#include "Configuration.h" #include "Configuration.h"
#include <SPI.h> #include <SPI.h>
#include <mcp_can.h> #include <mcp_can.h>
@ -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) 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(); 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); CAN = new MCP_CAN(spi, huawei_cs);
if (!CAN->begin(MCP_ANY, CAN_125KBPS, MCP_8MHZ) == CAN_OK) { if (!CAN->begin(MCP_ANY, CAN_125KBPS, MCP_8MHZ) == CAN_OK) {
MessageOutput.println("Error Initializing MCP2515..."); MessageOutput.println("[HuaweiCanClass::init] Error Initializing MCP2515...");
return; return;
} }
MessageOutput.println("MCP2515 Initialized Successfully!"); MessageOutput.println("[HuaweiCanClass::init] MCP2515 Initialized Successfully!");
initialized = true; _initialized = true;
// Change to normal mode to allow messages to be transmitted // Change to normal mode to allow messages to be transmitted
CAN->setMode(MCP_NORMAL); 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); pinMode(huawei_power, OUTPUT);
digitalWrite(huawei_power, HIGH); digitalWrite(huawei_power, HIGH);
_huawei_power = huawei_power; _huawei_power = huawei_power;
if (config.Huawei_Auto_Power_Enabled) {
_mode = HUAWEI_MODE_AUTO_INT;
}
} }
RectifierParameters_t * HuaweiCanClass::get() RectifierParameters_t * HuaweiCanClass::get()
@ -54,23 +62,18 @@ RectifierParameters_t * HuaweiCanClass::get()
uint32_t HuaweiCanClass::getLastUpdate() uint32_t HuaweiCanClass::getLastUpdate()
{ {
return lastUpdate; return _lastUpdateReceivedMillis;
} }
uint8_t data[8] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; uint8_t data[8] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
// Requests current values from Huawei unit. Response is handled in onReceive // Requests current values from Huawei unit. Response is handled in onReceive
void HuaweiCanClass::sendRequest() void HuaweiCanClass::sendRequest()
{ {
if (previousMillis < millis()) {
// Send extended message // Send extended message
byte sndStat = CAN->sendMsgBuf(0x108040FE, 1, 8, data); byte sndStat = CAN->sendMsgBuf(0x108040FE, 1, 8, data);
if(sndStat == CAN_OK) { if(sndStat != CAN_OK) {
MessageOutput.println("Message Sent Successfully!"); MessageOutput.println("[HuaweiCanClass::sendRequest] Error Sending Message...");
} else {
MessageOutput.println("Error Sending Message...");
}
previousMillis += 5000;
} }
} }
@ -97,8 +100,9 @@ void HuaweiCanClass::onReceive(uint8_t* frame, uint8_t len)
case R48xx_DATA_OUTPUT_POWER: case R48xx_DATA_OUTPUT_POWER:
_rp.output_power = value / 1024.0; _rp.output_power = value / 1024.0;
_newOutputPowerReceived = true;
// We'll only update last update on the important params // We'll only update last update on the important params
lastUpdate = millis(); _lastUpdateReceivedMillis = millis();
break; break;
case R48xx_DATA_EFFICIENCY: case R48xx_DATA_EFFICIENCY:
@ -110,7 +114,7 @@ void HuaweiCanClass::onReceive(uint8_t* frame, uint8_t len)
break; break;
case R48xx_DATA_OUTPUT_CURRENT_MAX: case R48xx_DATA_OUTPUT_CURRENT_MAX:
_rp.max_output_current = value / MAX_CURRENT_MULTIPLIER; _rp.max_output_current = static_cast<float>(value) / MAX_CURRENT_MULTIPLIER;
break; break;
case R48xx_DATA_INPUT_VOLTAGE: case R48xx_DATA_INPUT_VOLTAGE:
@ -133,12 +137,16 @@ void HuaweiCanClass::onReceive(uint8_t* frame, uint8_t len)
case R48xx_DATA_OUTPUT_CURRENT: case R48xx_DATA_OUTPUT_CURRENT:
_rp.output_current = value / 1024.0; _rp.output_current = value / 1024.0;
/* This is normally the last parameter received. Print */ if (_rp.output_current > HUAWEI_AUTO_MODE_SHUTDOWN_CURRENT) {
lastUpdate = millis(); // We'll only update last update on the important params _outputCurrentOnSinceMillis = millis();
}
MessageOutput.printf("In: %.02fV, %.02fA, %.02fW\n", _rp.input_voltage, _rp.input_current, _rp.input_power); /* This is normally the last parameter received. Print */
MessageOutput.printf("Out: %.02fV, %.02fA of %.02fA, %.02fW\n", _rp.output_voltage, _rp.output_current, _rp.max_output_current, _rp.output_power); _lastUpdateReceivedMillis = millis(); // We'll only update last update on the important params
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; break;
@ -157,7 +165,7 @@ void HuaweiCanClass::loop()
const CONFIG_T& config = Configuration.get(); const CONFIG_T& config = Configuration.get();
if (!config.Huawei_Enabled || !initialized) { if (!config.Huawei_Enabled || !_initialized) {
return; return;
} }
@ -175,12 +183,110 @@ void HuaweiCanClass::loop()
// https://www.beyondlogic.org/review-huawei-r4850g2-power-supply-53-5vdc-3kw/ // https://www.beyondlogic.org/review-huawei-r4850g2-power-supply-53-5vdc-3kw/
} }
} }
// Request updated values in regular intervals
if (_nextRequestMillis < millis()) {
MessageOutput.println("[HUAWEI********************* Sending request");
sendRequest(); 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) void HuaweiCanClass::setValue(float in, uint8_t parameterType)
{ {
uint16_t value; 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) { if (parameterType == HUAWEI_OFFLINE_VOLTAGE || parameterType == HUAWEI_ONLINE_VOLTAGE) {
value = in * 1024; value = in * 1024;
} else if (parameterType == HUAWEI_OFFLINE_CURRENT || parameterType == HUAWEI_ONLINE_CURRENT) { } 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 // Send extended message
byte sndStat = CAN->sendMsgBuf(0x108180FE, 1, 8, data); byte sndStat = CAN->sendMsgBuf(0x108180FE, 1, 8, data);
if (sndStat == CAN_OK) { if (sndStat != CAN_OK) {
MessageOutput.println("Message Sent Successfully!"); MessageOutput.println("[HuaweiCanClass::setValue] Error Sending Message...");
} else {
MessageOutput.println("Error Sending Message...");
} }
} }
void HuaweiCanClass::setPower(bool power) { void HuaweiCanClass::setMode(uint8_t mode) {
digitalWrite(_huawei_power, !power); 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;
}

View File

@ -12,7 +12,7 @@
#define TOPIC_SUB_LIMIT_ONLINE_VOLTAGE "limit_online_voltage" #define TOPIC_SUB_LIMIT_ONLINE_VOLTAGE "limit_online_voltage"
#define TOPIC_SUB_LIMIT_ONLINE_CURRENT "limit_online_current" #define TOPIC_SUB_LIMIT_ONLINE_CURRENT "limit_online_current"
#define TOPIC_SUB_POWER "power" #define TOPIC_SUB_MODE "mode"
MqttHandleHuaweiClass MqttHandleHuawei; MqttHandleHuaweiClass MqttHandleHuawei;
@ -28,7 +28,7 @@ void MqttHandleHuaweiClass::init()
String topic = MqttSettings.getPrefix(); 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_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_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(); _lastPublish = millis();
@ -108,13 +108,23 @@ void MqttHandleHuaweiClass::onMqttMessage(const espMqttClientTypes::MessagePrope
// Set current limit // Set current limit
MessageOutput.printf("Limit Current: %f A\r\n", payload_val); MessageOutput.printf("Limit Current: %f A\r\n", payload_val);
HuaweiCan.setValue(payload_val, HUAWEI_ONLINE_CURRENT); 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 // Control power on/off
MessageOutput.printf("Power: %f A\r\n", payload_val); if(payload_val == 3) {
if(payload_val > 0) { MessageOutput.println("[Huawei MQTT::] Received MQTT msg. New mode: Full internal control");
HuaweiCan.setPower(true); HuaweiCan.setMode(HUAWEI_MODE_AUTO_INT);
} else { }
HuaweiCan.setPower(false); 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);
} }
} }
} }

View File

@ -9,6 +9,7 @@
#include "Configuration.h" #include "Configuration.h"
#include "MqttSettings.h" #include "MqttSettings.h"
#include "NetworkSettings.h" #include "NetworkSettings.h"
#include "Huawei_can.h"
#include <VeDirectFrameHandler.h> #include <VeDirectFrameHandler.h>
#include "MessageOutput.h" #include "MessageOutput.h"
#include <ctime> #include <ctime>
@ -406,6 +407,14 @@ int32_t PowerLimiterClass::calcPowerLimit(std::shared_ptr<InverterAbstract> inve
if(batteryDischargeEnabled && useFullSolarPassthrough(inverter)) { if(batteryDischargeEnabled && useFullSolarPassthrough(inverter)) {
// Case 5 // Case 5
newPowerLimit = newPowerLimit > adjustedVictronChargePower ? newPowerLimit : adjustedVictronChargePower; 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) // We should use Victron solar power only (corrected by efficiency factor)

View File

@ -190,6 +190,11 @@ void WebApiHuaweiClass::onAdminGet(AsyncWebServerRequest* request)
const CONFIG_T& config = Configuration.get(); const CONFIG_T& config = Configuration.get();
root[F("enabled")] = config.Huawei_Enabled; root[F("enabled")] = config.Huawei_Enabled;
root[F("auto_power_enabled")] = config.Huawei_Auto_Power_Enabled;
root[F("voltage_limit")] = static_cast<int>(config.Huawei_Auto_Power_Voltage_Limit * 100) / 100.0;
root[F("enable_voltage_limit")] = static_cast<int>(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(); response->setLength();
request->send(response); request->send(response);
@ -234,7 +239,11 @@ void WebApiHuaweiClass::onAdminPost(AsyncWebServerRequest* request)
return; 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("message")] = F("Values are missing!");
retMsg[F("code")] = WebApiError::GenericValueMissing; retMsg[F("code")] = WebApiError::GenericValueMissing;
response->setLength(); response->setLength();
@ -244,6 +253,11 @@ void WebApiHuaweiClass::onAdminPost(AsyncWebServerRequest* request)
CONFIG_T& config = Configuration.get(); CONFIG_T& config = Configuration.get();
config.Huawei_Enabled = root[F("enabled")].as<bool>(); config.Huawei_Enabled = root[F("enabled")].as<bool>();
config.Huawei_Auto_Power_Enabled = root[F("auto_power_enabled")].as<bool>();
config.Huawei_Auto_Power_Voltage_Limit = root[F("voltage_limit")].as<float>();
config.Huawei_Auto_Power_Enable_Voltage_Limit = root[F("enable_voltage_limit")].as<float>();
config.Huawei_Auto_Power_Lower_Power_Limit = root[F("lower_power_limit")].as<float>();
config.Huawei_Auto_Power_Upper_Power_Limit = root[F("upper_power_limit")].as<float>();
Configuration.write(); Configuration.write();
retMsg[F("type")] = F("success"); retMsg[F("type")] = F("success");
@ -254,6 +268,7 @@ void WebApiHuaweiClass::onAdminPost(AsyncWebServerRequest* request)
request->send(response); request->send(response);
const PinMapping_t& pin = PinMapping.get(); const PinMapping_t& pin = PinMapping.get();
// Properly turn this on
if (config.Huawei_Enabled) { if (config.Huawei_Enabled) {
MessageOutput.println(F("Initialize Huawei AC charger interface... ")); MessageOutput.println(F("Initialize Huawei AC charger interface... "));
if (PinMapping.isValidHuaweiConfig()) { 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);
} }

View File

@ -733,6 +733,13 @@
"ChargerSettings": "AC Ladegerät Einstellungen", "ChargerSettings": "AC Ladegerät Einstellungen",
"Configuration": "AC Ladegerät Konfiguration", "Configuration": "AC Ladegerät Konfiguration",
"EnableHuawei": "Huawei R4850G2 an CAN Bus Interface aktiv", "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", "Seconds": "@:dtuadmin.Seconds",
"Save": "@:dtuadmin.Save" "Save": "@:dtuadmin.Save"
}, },

View File

@ -738,6 +738,13 @@
"ChargerSettings": "AC Charger Settings", "ChargerSettings": "AC Charger Settings",
"Configuration": "AC Charger Configuration", "Configuration": "AC Charger Configuration",
"EnableHuawei": "Enable Huawei R4850G2 on CAN Bus Interface", "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", "Seconds": "@:dtuadmin.Seconds",
"Save": "@:dtuadmin.Save" "Save": "@:dtuadmin.Save"
}, },

View File

@ -693,6 +693,13 @@
"ChargerSettings": "AC Charger Settings", "ChargerSettings": "AC Charger Settings",
"Configuration": "AC Charger Configuration", "Configuration": "AC Charger Configuration",
"EnableHuawei": "Enable Huawei R4850G2 on CAN Bus Interface", "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", "Seconds": "@:dtuadmin.Seconds",
"Save": "@:dtuadmin.Save" "Save": "@:dtuadmin.Save"
}, },

View File

@ -1,3 +1,8 @@
export interface AcChargerConfig { export interface AcChargerConfig {
enabled: boolean; enabled: boolean;
auto_power_enabled: boolean;
voltage_limit: number;
enable_voltage_limit: number;
lower_power_limit: number;
upper_power_limit: number;
} }

View File

@ -9,6 +9,54 @@
<InputElement :label="$t('acchargeradmin.EnableHuawei')" <InputElement :label="$t('acchargeradmin.EnableHuawei')"
v-model="acChargerConfigList.enabled" v-model="acChargerConfigList.enabled"
type="checkbox" wide/> type="checkbox" wide/>
<InputElement v-show="acChargerConfigList.enabled"
:label="$t('acchargeradmin.EnableAutoPower')"
v-model="acChargerConfigList.auto_power_enabled"
type="checkbox" wide/>
<CardElement :text="$t('acchargeradmin.Limits')" textVariant="text-bg-primary" add-space
v-show="acChargerConfigList.auto_power_enabled">
<div class="row mb-3">
<label for="voltageLimit" class="col-sm-2 col-form-label">{{ $t('acchargeradmin.VoltageLimit') }}:</label>
<div class="col-sm-10">
<div class="input-group">
<input type="number" step="0.01" class="form-control" id="voltageLimit"
placeholder="42" v-model="acChargerConfigList.voltage_limit"
aria-describedby="voltageLimitDescription" min="42" max="58.5" required/>
<span class="input-group-text" id="voltageLimitDescription">V</span>
</div>
</div>
<label for="enableVoltageLimit" class="col-sm-2 col-form-label">{{ $t('acchargeradmin.enableVoltageLimit') }}:
<BIconInfoCircle v-tooltip :title="$t('acchargeradmin.enableVoltageLimitHint')" />
</label>
<div class="col-sm-10">
<div class="input-group">
<input type="number" step="0.01" class="form-control" id="enableVoltageLimit"
placeholder="42" v-model="acChargerConfigList.enable_voltage_limit"
aria-describedby="enableVoltageLimitDescription" min="42" max="58.5" required/>
<span class="input-group-text" id="enableVoltageLimitDescription">V</span>
</div>
</div>
<label for="lowerPowerLimit" class="col-sm-2 col-form-label">{{ $t('acchargeradmin.lowerPowerLimit') }}:</label>
<div class="col-sm-10">
<div class="input-group">
<input type="number" class="form-control" id="lowerPowerLimit"
placeholder="150" v-model="acChargerConfigList.lower_power_limit"
aria-describedby="lowerPowerLimitDescription" min="100" max="3000" required/>
<span class="input-group-text" id="lowerPowerLimitDescription">W</span>
</div>
</div>
<label for="upperPowerLimit" class="col-sm-2 col-form-label">{{ $t('acchargeradmin.upperPowerLimit') }}:</label>
<div class="col-sm-10">
<div class="input-group">
<input type="number" class="form-control" id="upperPowerLimit"
placeholder="2000" v-model="acChargerConfigList.upper_power_limit"
aria-describedby="lowerPowerLimitDescription" min="100" max="3000" required/>
<span class="input-group-text" id="upperPowerLimitDescription">W</span>
</div>
</div>
</div>
</CardElement>
</CardElement> </CardElement>
<button type="submit" class="btn btn-primary mb-3">{{ $t('acchargeradmin.Save') }}</button> <button type="submit" class="btn btn-primary mb-3">{{ $t('acchargeradmin.Save') }}</button>
@ -21,6 +69,7 @@ import BasePage from '@/components/BasePage.vue';
import BootstrapAlert from "@/components/BootstrapAlert.vue"; import BootstrapAlert from "@/components/BootstrapAlert.vue";
import CardElement from '@/components/CardElement.vue'; import CardElement from '@/components/CardElement.vue';
import InputElement from '@/components/InputElement.vue'; import InputElement from '@/components/InputElement.vue';
import { BIconInfoCircle } from 'bootstrap-icons-vue';
import type { AcChargerConfig } from "@/types/AcChargerConfig"; import type { AcChargerConfig } from "@/types/AcChargerConfig";
import { authHeader, handleResponse } from '@/utils/authentication'; import { authHeader, handleResponse } from '@/utils/authentication';
import { defineComponent } from 'vue'; import { defineComponent } from 'vue';
@ -31,6 +80,7 @@ export default defineComponent({
BootstrapAlert, BootstrapAlert,
CardElement, CardElement,
InputElement, InputElement,
BIconInfoCircle,
}, },
data() { data() {
return { return {