Merge pull request #457 from amarofarinha/feature/pause-battery-before-ota

Add Battery Pause Feature (Max Charge/Discharge Power) and OTA Update Optimization
This commit is contained in:
amarofarinha 2024-09-15 21:03:27 +01:00 committed by GitHub
commit f95d8a8c96
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
12 changed files with 457 additions and 372 deletions

View file

@ -56,6 +56,7 @@ SensorConfig sensorConfigs[] = {
{"max_charge_power", "Battery Emulator Battery Max Charge Power", "{{ value_json.max_charge_power }}", "W",
"power"},
{"bms_status", "Battery Emulator BMS Status", "{{ value_json.bms_status }}", "", ""},
{"pause_status", "Battery Emulator Pause Status", "{{ value_json.pause_status }}", "", ""},
};
@ -112,25 +113,30 @@ static void publish_common_info(void) {
} else {
#endif // HA_AUTODISCOVERY
doc["SOC"] = ((float)datalayer.battery.status.reported_soc) / 100.0;
doc["SOC_real"] = ((float)datalayer.battery.status.real_soc) / 100.0;
doc["state_of_health"] = ((float)datalayer.battery.status.soh_pptt) / 100.0;
doc["temperature_min"] = ((float)((int16_t)datalayer.battery.status.temperature_min_dC)) / 10.0;
doc["temperature_max"] = ((float)((int16_t)datalayer.battery.status.temperature_max_dC)) / 10.0;
doc["stat_batt_power"] = ((float)((int32_t)datalayer.battery.status.active_power_W));
doc["battery_current"] = ((float)((int16_t)datalayer.battery.status.current_dA)) / 10.0;
doc["battery_voltage"] = ((float)datalayer.battery.status.voltage_dV) / 10.0;
// publish only if cell voltages have been populated...
if (datalayer.battery.info.number_of_cells != 0u &&
datalayer.battery.status.cell_voltages_mV[datalayer.battery.info.number_of_cells - 1] != 0u) {
doc["cell_max_voltage"] = ((float)datalayer.battery.status.cell_max_voltage_mV) / 1000.0;
doc["cell_min_voltage"] = ((float)datalayer.battery.status.cell_min_voltage_mV) / 1000.0;
}
doc["total_capacity"] = ((float)datalayer.battery.info.total_capacity_Wh);
doc["remaining_capacity"] = ((float)datalayer.battery.status.remaining_capacity_Wh);
doc["max_discharge_power"] = ((float)datalayer.battery.status.max_discharge_power_W);
doc["max_charge_power"] = ((float)datalayer.battery.status.max_charge_power_W);
doc["bms_status"] = getBMSStatus(datalayer.battery.status.bms_status);
doc["pause_status"] = get_emulator_pause_status();
//only publish these values if BMS is active and we are comunication with the battery (can send CAN messages to the battery)
if (datalayer.battery.status.bms_status == ACTIVE && can_send_CAN && millis() > BOOTUP_TIME) {
doc["SOC"] = ((float)datalayer.battery.status.reported_soc) / 100.0;
doc["SOC_real"] = ((float)datalayer.battery.status.real_soc) / 100.0;
doc["state_of_health"] = ((float)datalayer.battery.status.soh_pptt) / 100.0;
doc["temperature_min"] = ((float)((int16_t)datalayer.battery.status.temperature_min_dC)) / 10.0;
doc["temperature_max"] = ((float)((int16_t)datalayer.battery.status.temperature_max_dC)) / 10.0;
doc["stat_batt_power"] = ((float)((int32_t)datalayer.battery.status.active_power_W));
doc["battery_current"] = ((float)((int16_t)datalayer.battery.status.current_dA)) / 10.0;
doc["battery_voltage"] = ((float)datalayer.battery.status.voltage_dV) / 10.0;
// publish only if cell voltages have been populated...
if (datalayer.battery.info.number_of_cells != 0u &&
datalayer.battery.status.cell_voltages_mV[datalayer.battery.info.number_of_cells - 1] != 0u) {
doc["cell_max_voltage"] = ((float)datalayer.battery.status.cell_max_voltage_mV) / 1000.0;
doc["cell_min_voltage"] = ((float)datalayer.battery.status.cell_min_voltage_mV) / 1000.0;
}
doc["total_capacity"] = ((float)datalayer.battery.info.total_capacity_Wh);
doc["remaining_capacity"] = ((float)datalayer.battery.status.remaining_capacity_Wh);
doc["max_discharge_power"] = ((float)datalayer.battery.status.max_discharge_power_W);
doc["max_charge_power"] = ((float)datalayer.battery.status.max_charge_power_W);
}
serializeJson(doc, mqtt_msg);
if (!mqtt_publish(state_topic.c_str(), mqtt_msg, false)) {

View file

@ -9,9 +9,23 @@ static bool battery_empty_event_fired = false;
#define MAX_SOH_DEVIATION_PPTT 2500
//battery pause status begin
bool emulator_pause_request_ON = false;
bool emulator_pause_CAN_send_ON = false;
bool can_send_CAN = true;
battery_pause_status emulator_pause_status = NORMAL;
//battery pause status end
void update_machineryprotection() {
// Start checking that the battery is within reason. Incase we see any funny business, raise an event!
// Pause function is on
if (emulator_pause_request_ON) {
datalayer.battery.status.max_discharge_power_W = 0;
datalayer.battery.status.max_charge_power_W = 0;
}
// Battery is overheated!
if (datalayer.battery.status.temperature_max_dC > 500) {
set_event(EVENT_BATTERY_OVERHEAT, datalayer.battery.status.temperature_max_dC);
@ -138,6 +152,13 @@ void update_machineryprotection() {
#ifdef DOUBLE_BATTERY // Additional Double-Battery safeties are checked here
// Check if the Battery 2 BMS is still sending CAN messages. If we go 60s without messages we raise an error
// Pause function is on
if (emulator_pause_request_ON) {
datalayer.battery2.status.max_discharge_power_W = 0;
datalayer.battery2.status.max_charge_power_W = 0;
}
if (!datalayer.battery2.status.CAN_battery_still_alive) {
set_event(EVENT_CAN2_RX_FAILURE, 0);
} else {
@ -171,3 +192,67 @@ void update_machineryprotection() {
#endif // DOUBLE_BATTERY
}
//battery pause status begin
void setBatteryPause(bool pause_battery, bool pause_CAN) {
emulator_pause_CAN_send_ON = pause_CAN;
if (pause_battery) {
set_event(EVENT_PAUSE_BEGIN, 1);
emulator_pause_request_ON = true;
emulator_pause_status = PAUSING;
datalayer.battery.status.max_discharge_power_W = 0;
datalayer.battery.status.max_charge_power_W = 0;
#ifdef DOUBLE_BATTERY
datalayer.battery2.status.max_discharge_power_W = 0;
datalayer.battery2.status.max_charge_power_W = 0;
#endif
} else {
clear_event(EVENT_PAUSE_BEGIN);
set_event(EVENT_PAUSE_END, 0);
emulator_pause_request_ON = false;
emulator_pause_CAN_send_ON = false;
emulator_pause_status = RESUMING;
clear_event(EVENT_PAUSE_END);
}
}
/// @brief handle emulator pause status
/// @return true if CAN messages should be sent to battery, false if not
void emulator_pause_state_send_CAN_battery() {
if (emulator_pause_status == NORMAL)
can_send_CAN = true;
// in some inverters this values are not accurate, so we need to check if we are consider 1.8 amps as the limit
if (emulator_pause_request_ON && emulator_pause_status == PAUSING && datalayer.battery.status.current_dA < 18 &&
datalayer.battery.status.current_dA > -18) {
emulator_pause_status = PAUSED;
}
if (!emulator_pause_request_ON && emulator_pause_status == RESUMING) {
emulator_pause_status = NORMAL;
can_send_CAN = true;
}
can_send_CAN = (!emulator_pause_CAN_send_ON || emulator_pause_status == NORMAL);
}
std::string get_emulator_pause_status() {
switch (emulator_pause_status) {
case NORMAL:
return "RUNNING";
case PAUSING:
return "PAUSING";
case PAUSED:
return "PAUSED";
case RESUMING:
return "RESUMING";
default:
return "UNKNOWN";
}
}
//battery pause status

View file

@ -1,11 +1,26 @@
#ifndef SAFETY_H
#define SAFETY_H
#include <Arduino.h>
#include <string>
#define MAX_CAN_FAILURES 50
#define MAX_CHARGE_DISCHARGE_LIMIT_FAILURES 1
//battery pause status begin
enum battery_pause_status { NORMAL = 0, PAUSING = 1, PAUSED = 2, RESUMING = 3 };
extern bool emulator_pause_request_ON;
extern bool emulator_pause_CAN_send_ON;
extern battery_pause_status emulator_pause_status;
extern bool can_send_CAN;
//battery pause status end
void update_machineryprotection();
//battery pause status begin
void setBatteryPause(bool pause_battery, bool pause_CAN);
void emulator_pause_state_send_CAN_battery();
std::string get_emulator_pause_status();
//battery pause status end
#endif

View file

@ -198,6 +198,8 @@ void init_events(void) {
events.entries[EVENT_RESET_EFUSE].level = EVENT_LEVEL_INFO;
events.entries[EVENT_RESET_PWR_GLITCH].level = EVENT_LEVEL_INFO;
events.entries[EVENT_RESET_CPU_LOCKUP].level = EVENT_LEVEL_WARNING;
events.entries[EVENT_PAUSE_BEGIN].level = EVENT_LEVEL_WARNING;
events.entries[EVENT_PAUSE_END].level = EVENT_LEVEL_INFO;
events.entries[EVENT_EEPROM_WRITE].log = false; // Don't log the logger...
@ -367,6 +369,10 @@ const char* get_event_message_string(EVENTS_ENUM_TYPE event) {
return "Info: The board was reset due to a detected power glitch";
case EVENT_RESET_CPU_LOCKUP:
return "Warning: The board was reset due to CPU lockup. Inform developers!";
case EVENT_PAUSE_BEGIN:
return "Warning: The emulator is trying to pause the battery.";
case EVENT_PAUSE_END:
return "Info: The emulator is attempting to resume battery operation from pause.";
default:
return "";
}

View file

@ -93,6 +93,8 @@
XX(EVENT_RESET_EFUSE) \
XX(EVENT_RESET_PWR_GLITCH) \
XX(EVENT_RESET_CPU_LOCKUP) \
XX(EVENT_PAUSE_BEGIN) \
XX(EVENT_PAUSE_END) \
XX(EVENT_NOF_EVENTS)
typedef enum { EVENTS_ENUM_TYPE(GENERATE_ENUM) } EVENTS_ENUM_TYPE;

View file

@ -1,6 +1,7 @@
#include "webserver.h"
#include <Preferences.h>
#include "../../datalayer/datalayer.h"
#include "../../lib/bblanchon-ArduinoJson/ArduinoJson.h"
#include "../utils/events.h"
#include "../utils/led_handler.h"
#include "../utils/timer.h"
@ -34,6 +35,7 @@ unsigned const long MAX_WIFI_RETRY_INTERVAL = 90000; // Maximum wifi ret
unsigned long last_wifi_monitor_time = millis(); //init millis so wifi monitor doesn't run immediately
unsigned long wifi_reconnect_interval = DEFAULT_WIFI_RECONNECT_INTERVAL;
unsigned long last_wifi_attempt_time = millis(); //init millis so wifi monitor doesn't run immediately
const char get_firmware_info_html[] = R"rawliteral(%X%)rawliteral";
void init_webserver() {
// Configure WiFi
@ -53,6 +55,13 @@ void init_webserver() {
server.on("/logout", HTTP_GET, [](AsyncWebServerRequest* request) { request->send(401); });
// Route for firmware info from ota update page
server.on("/GetFirmwareInfo", HTTP_GET, [](AsyncWebServerRequest* request) {
if (WEBSERVER_AUTH_REQUIRED && !request->authenticate(http_username, http_password))
return request->requestAuthentication();
request->send_P(200, "application/json", get_firmware_info_html, get_firmware_info_processor);
});
// Route for root / web page
server.on("/", HTTP_GET, [](AsyncWebServerRequest* request) {
if (WEBSERVER_AUTH_REQUIRED && !request->authenticate(http_username, http_password))
@ -159,6 +168,19 @@ void init_webserver() {
}
});
// Route for pause/resume Battery emulator
server.on("/pause", HTTP_GET, [](AsyncWebServerRequest* request) {
if (WEBSERVER_AUTH_REQUIRED && !request->authenticate(http_username, http_password))
return request->requestAuthentication();
if (request->hasParam("p")) {
String valueStr = request->getParam("p")->value();
setBatteryPause(valueStr == "true" || valueStr == "1", false);
request->send(200, "text/plain", "Updated successfully");
} else {
request->send(400, "text/plain", "Bad Request");
}
});
// Route for editing SOCMin
server.on("/updateSocMin", HTTP_GET, [](AsyncWebServerRequest* request) {
if (WEBSERVER_AUTH_REQUIRED && !request->authenticate(http_username, http_password))
@ -414,10 +436,8 @@ void wifi_monitor() {
if (ota_active && ota_timeout_timer.elapsed()) {
// OTA timeout, try to restore can and clear the update event
ESP32Can.CANInit();
clear_event(EVENT_OTA_UPDATE);
set_event(EVENT_OTA_UPDATE_TIMEOUT, 0);
ota_active = false;
onOTAEnd(false);
}
}
@ -444,6 +464,24 @@ void init_ElegantOTA() {
ElegantOTA.onEnd(onOTAEnd);
}
String get_firmware_info_processor(const String& var) {
if (var == "X") {
String content = "";
static JsonDocument doc;
#ifdef HW_LILYGO
doc["hardware"] = "LilyGo T-CAN485";
#endif // HW_LILYGO
#ifdef HW_STARK
doc["hardware"] = "Stark CMR Module";
#endif // HW_STARK
doc["firmware"] = String(version_number);
serializeJson(doc, content);
return content;
}
return String();
}
String processor(const String& var) {
if (var == "X") {
String content = "";
@ -696,6 +734,10 @@ String processor(const String& var) {
} else {
content += "<span style='color: red;'>&#10005;</span></h4>";
}
if (emulator_pause_status == NORMAL)
content += "<h4>Pause status: " + String(get_emulator_pause_status().c_str()) + " </h4>";
else
content += "<h4 style='color: red;'>Pause status: " + String(get_emulator_pause_status().c_str()) + " </h4>";
// Close the block
content += "</div>";
@ -772,6 +814,10 @@ String processor(const String& var) {
} else {
content += "<span style='color: red;'>&#10005;</span></h4>";
}
if (emulator_pause_status == NORMAL)
content += "<h4>Pause status: " + String(get_emulator_pause_status().c_str()) + " </h4>";
else
content += "<h4 style='color: red;'>Pause status: " + String(get_emulator_pause_status().c_str()) + " </h4>";
content += "</div>";
content += "</div>";
@ -833,6 +879,11 @@ String processor(const String& var) {
content += "</div>";
#endif // defined CHEVYVOLT_CHARGER || defined NISSANLEAF_CHARGER
if (emulator_pause_request_ON)
content += "<button onclick='PauseBattery(false)'>Resume Battery</button>";
else
content += "<button onclick='PauseBattery(true)'>Pause Battery</button>";
content += "<button onclick='OTA()'>Perform OTA update</button>";
content += " ";
content += "<button onclick='Settings()'>Change Settings</button>";
@ -866,6 +917,12 @@ String processor(const String& var) {
content += " setTimeout(function(){ window.open(\"/\",\"_self\"); }, 1000);";
content += "}";
}
content += "function PauseBattery(pause){";
content +=
"var xhr=new "
"XMLHttpRequest();xhr.onload=function() { "
"window.location.reload();};xhr.open('GET','/pause?p='+pause,true);xhr.send();";
content += "}";
content += "</script>";
@ -880,8 +937,10 @@ String processor(const String& var) {
}
void onOTAStart() {
//try to Pause the battery
setBatteryPause(true, true);
// Log when OTA has started
ESP32Can.CANStop();
set_event(EVENT_OTA_UPDATE, 0);
// If already set, make a new attempt
@ -903,8 +962,13 @@ void onOTAProgress(size_t current, size_t final) {
}
void onOTAEnd(bool success) {
ota_active = false;
clear_event(EVENT_OTA_UPDATE);
// Log when OTA has finished
if (success) {
// a reboot will be done by the OTA library. no need to do anything here
#ifdef DEBUG_VIA_USB
Serial.println("OTA update finished successfully!");
#endif // DEBUG_VIA_USB
@ -912,12 +976,9 @@ void onOTAEnd(bool success) {
#ifdef DEBUG_VIA_USB
Serial.println("There was an error during OTA update!");
#endif // DEBUG_VIA_USB
// If we fail without a timeout, try to restore CAN
ESP32Can.CANInit();
//try to Resume the battery pause and CAN communication
setBatteryPause(false, false);
}
ota_active = false;
clear_event(EVENT_OTA_UPDATE);
}
template <typename T> // This function makes power values appear as W when under 1000, and kW when over

View file

@ -103,6 +103,7 @@ void init_ElegantOTA();
* @return String
*/
String processor(const String& var);
String get_firmware_info_processor(const String& var);
/**
* @brief Executes on OTA start

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,30 @@
Modifying HTML for the ElegantOTA Library
This guide provides the necessary steps to update and modify the HTML used in the ElegantOTA library.
Follow these steps carefully to ensure that your changes are properly integrated.
Steps to Modify the HTML:
1 - Edit the CurrentPlainHTML.txt:
Locate the file CurrentPlainHTML.txt in your project directory.
Modify the HTML content as needed. This file contains the plain HTML code that will be served through the OTA interface.
Convert the HTML to GZIP and Decimal Format:
Copy the content of the updated CurrentPlainHTML.txt.
Navigate to the CyberChef tool for encoding and compression.
Apply the following recipe:
Gzip with the "Dynamic Huffman Coding" option enabled.
Convert to Decimal with a comma separator.
Use this link for the process: https://gchq.github.io/CyberChef/#recipe=Gzip('Dynamic%20Huffman%20Coding','','',false)To_Decimal('Comma',false)
2 - Update the ELEGANT_HTML Array:
Copy the resulting decimal output from CyberChef.
Replace the existing content of the ELEGANT_HTML array in elop.cpp with the new decimal data from CyberChef.
3 - Adjust the ELEGANT_HTML Array Size:
After updating the ELEGANT_HTML array in both elop.h and elop.cpp, update the array size to match the length of the new output from CyberChef.
Ensure that the array size reflects the new length of the compressed HTML to avoid errors during compilation.

View file

@ -43,6 +43,15 @@ void ElegantOTAClass::begin(ELEGANTOTA_WEBSERVER *server, const char * username,
return request->requestAuthentication();
}
// Pre-OTA update callback
if (preUpdateCallback != NULL) preUpdateCallback();
// Sleep for 3 seconds to allow asynchronous preUpdateCallback tasks to complete
unsigned long sleepStart = millis();
while (millis() - sleepStart < 3000) { // Sleep for 3 second
delay(1); // Yield to other tasks
}
// Get header x-ota-mode value, if present
OTA_Mode mode = OTA_MODE_FIRMWARE;
// Get mode from arg
@ -73,7 +82,7 @@ void ElegantOTAClass::begin(ELEGANTOTA_WEBSERVER *server, const char * username,
#endif
// Pre-OTA update callback
if (preUpdateCallback != NULL) preUpdateCallback();
//if (preUpdateCallback != NULL) preUpdateCallback();
// Start update process
#if defined(ESP8266)

File diff suppressed because one or more lines are too long

View file

@ -3,6 +3,6 @@
#include <Arduino.h>
extern const uint8_t ELEGANT_HTML[10214];
extern const uint8_t ELEGANT_HTML[40500];
#endif