From cc6761a8a50ab439f65604c245d23117066476ec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20=C3=96ster?= Date: Tue, 31 Dec 2024 00:39:05 +0200 Subject: [PATCH 1/5] Add Tesla LFP balancing feature --- Software/src/battery/TESLA-BATTERY.cpp | 9 ++ Software/src/battery/TESLA-BATTERY.h | 9 +- Software/src/datalayer/datalayer.h | 15 ++++ Software/src/devboard/safety/safety.cpp | 16 ++++ .../src/devboard/webserver/settings_html.cpp | 77 ++++++++++++++++ Software/src/devboard/webserver/webserver.cpp | 87 +++++++++++++++++++ 6 files changed, 209 insertions(+), 4 deletions(-) diff --git a/Software/src/battery/TESLA-BATTERY.cpp b/Software/src/battery/TESLA-BATTERY.cpp index 8644405b..70dbbdb2 100644 --- a/Software/src/battery/TESLA-BATTERY.cpp +++ b/Software/src/battery/TESLA-BATTERY.cpp @@ -901,6 +901,15 @@ void update_values_battery() { //This function maps all the values fetched via datalayer.battery.info.min_cell_voltage_mV = MIN_CELL_VOLTAGE_NCA_NCM; datalayer.battery.info.max_cell_voltage_deviation_mV = MAX_CELL_DEVIATION_NCA_NCM; } + + // During forced balancing request via webserver, we allow the battery to exceed normal safety parameters + if (datalayer.battery.settings.user_requests_balancing) { + datalayer.battery.info.max_design_voltage_dV = datalayer.battery.settings.balancing_max_pack_voltage_dV; + datalayer.battery.info.max_cell_voltage_mV = datalayer.battery.settings.balancing_max_cell_voltage_mV; + datalayer.battery.info.max_cell_voltage_deviation_mV = + datalayer.battery.settings.balancing_max_deviation_cell_voltage_mV; + datalayer.battery.status.max_charge_power_W = datalayer.battery.settings.balancing_float_power_W; + } #endif // TESLA_MODEL_3Y_BATTERY // Update webserver datalayer diff --git a/Software/src/battery/TESLA-BATTERY.h b/Software/src/battery/TESLA-BATTERY.h index 817ca1f6..4a843838 100644 --- a/Software/src/battery/TESLA-BATTERY.h +++ b/Software/src/battery/TESLA-BATTERY.h @@ -10,10 +10,11 @@ #define MAXDISCHARGEPOWERALLOWED 60000 // 60000W we use a define since the value supplied by Tesla is always 0 /* Do not change the defines below */ -#define RAMPDOWN_SOC 900 // 90.0 SOC% to start ramping down from max charge power towards 0 at 100.00% -#define RAMPDOWNPOWERALLOWED 15000 // What power we ramp down from towards top balancing -#define FLOAT_MAX_POWER_W 200 // W, what power to allow for top balancing battery -#define FLOAT_START_MV 20 // mV, how many mV under overvoltage to start float charging +#define RAMPDOWN_SOC 900 // 90.0 SOC% to start ramping down from max charge power towards 0 at 100.00% +#define RAMPDOWNPOWERALLOWED \ + 15000 // What power we ramp down from towards top balancing (usually same as MAXCHARGEPOWERALLOWED) +#define FLOAT_MAX_POWER_W 200 // W, what power to allow for top balancing battery +#define FLOAT_START_MV 20 // mV, how many mV under overvoltage to start float charging #define MAX_PACK_VOLTAGE_SX_NCMA 4600 // V+1, if pack voltage goes over this, charge stops #define MIN_PACK_VOLTAGE_SX_NCMA 3100 // V+1, if pack voltage goes over this, charge stops diff --git a/Software/src/datalayer/datalayer.h b/Software/src/datalayer/datalayer.h index 6064aa15..9a484a7a 100644 --- a/Software/src/datalayer/datalayer.h +++ b/Software/src/datalayer/datalayer.h @@ -121,6 +121,21 @@ typedef struct { /** The user specified maximum allowed discharge voltage, in deciVolt. 3000 = 300.0 V */ uint16_t max_user_set_discharge_voltage_dV = BATTERY_MAX_DISCHARGE_VOLTAGE; + /** Tesla specific settings that are edited on the fly when manually forcing a balance charge for LFP chemistry */ + /* Bool for specifying if user has requested manual balancing */ + bool user_requests_balancing = false; + /* Forced balancing max time & start timestamp */ + uint32_t balancing_time_ms = 3600000; //1h default, (60min*60sec*1000ms) + uint32_t balancing_start_time_ms = 0; //For keeping track when balancing started + /* Max cell voltage during forced balancing */ + uint16_t balancing_max_cell_voltage_mV = 3650; + /* Max cell deviation allowed during forced balancing */ + uint16_t balancing_max_deviation_cell_voltage_mV = 400; + /* Float max power during forced balancing */ + uint16_t balancing_float_power_W = 1000; + /* Maximum voltage for entire battery pack during forced balancing */ + uint16_t balancing_max_pack_voltage_dV = 3940; + } DATALAYER_BATTERY_SETTINGS_TYPE; typedef struct { diff --git a/Software/src/devboard/safety/safety.cpp b/Software/src/devboard/safety/safety.cpp index f662adfe..35d28579 100644 --- a/Software/src/devboard/safety/safety.cpp +++ b/Software/src/devboard/safety/safety.cpp @@ -238,6 +238,22 @@ void update_machineryprotection() { if (datalayer.battery.status.max_charge_power_W == 0) { datalayer.battery.status.max_charge_current_dA = 0; } + + //Decrement the forced balancing timer incase user requested it + if (datalayer.battery.settings.user_requests_balancing) { + // If this is the start of the balancing period, capture the current time + if (datalayer.battery.settings.balancing_start_time_ms == 0) { + datalayer.battery.settings.balancing_start_time_ms = millis(); + //TODO, raise info event? Forced balancing starting! + } + + // Check if the elapsed time exceeds the balancing time + if (millis() - datalayer.battery.settings.balancing_start_time_ms >= datalayer.battery.settings.balancing_time_ms) { + datalayer.battery.settings.user_requests_balancing = false; + datalayer.battery.settings.balancing_start_time_ms = 0; // Reset the start time + //TODO, raise info event? Balancing time elapsed. Turning off... + } + } } //battery pause status begin diff --git a/Software/src/devboard/webserver/settings_html.cpp b/Software/src/devboard/webserver/settings_html.cpp index e21ec722..a576ef38 100644 --- a/Software/src/devboard/webserver/settings_html.cpp +++ b/Software/src/devboard/webserver/settings_html.cpp @@ -97,6 +97,42 @@ String settings_processor(const String& var) { content += ""; #endif +#ifdef TESLA_MODEL_3Y_BATTERY + + // Start a new block with grey background color + content += "
"; + + content += + "

Manual LFP balancing: " + + String(datalayer.battery.settings.user_requests_balancing ? "" + : "") + + "

"; + content += + "

Balancing max time: " + String(datalayer.battery.settings.balancing_time_ms / 60000.0, 1) + + " Minutes

"; + content += + "

Balancing float power: " + String(datalayer.battery.settings.balancing_float_power_W / 1.0, 0) + + " W

"; + content += + "

Max battery voltage: " + String(datalayer.battery.settings.balancing_max_pack_voltage_dV / 10.0, 0) + + " V

"; + content += + "

Max cell voltage: " + String(datalayer.battery.settings.balancing_max_cell_voltage_mV / 1.0, 0) + + " mV

"; + content += + "

Max cell voltage deviation: " + + String(datalayer.battery.settings.balancing_max_deviation_cell_voltage_mV / 1.0, 0) + + " mV

"; + + // Close the block + content += "
"; +#endif + #if defined CHEVYVOLT_CHARGER || defined NISSANLEAF_CHARGER // Start a new block with orange background color @@ -206,6 +242,47 @@ String settings_processor(const String& var) { "between 0 " "and 1000.0');}}}"; +#ifdef TESLA_MODEL_3Y_BATTERY + content += + "function editTeslaBalAct(){var value=prompt('Enable or disable forced LFP balancing. Makes the battery charge " + "to 101percent. This should be performed once every month, to keep LFP batteries balanced. Ensure battery is " + "fully charged before enabling, and also that you have enough sun or grid power to feed power into the battery " + "while balancing is active. Enter 1 for enabled, 0 " + "for disabled');if(value!==null){if(value==0||value==1){var xhr=new " + "XMLHttpRequest();xhr.onload=editComplete;xhr.onerror=editError;xhr.open('GET','/" + "TeslaBalAct?value='+value,true);xhr.send();}}else{alert('Invalid value. Please enter 1 or 0');}}"; + content += + "function editBalTime(){var value=prompt('Enter new max balancing time in " + "minutes');if(value!==null){if(value>=1&&value<=300){var xhr=new " + "XMLHttpRequest();xhr.onload=editComplete;xhr.onerror=editError;xhr.open('GET','/" + "BalTime?value='+value,true);xhr.send();}else{alert('Invalid value. Please enter a value " + "between 1 and 300');}}}"; + content += + "function editBalFloatPower(){var value=prompt('Power level in Watt to float charge during forced " + "balancing');if(value!==null){if(value>=100&&value<=2000){var xhr=new " + "XMLHttpRequest();xhr.onload=editComplete;xhr.onerror=editError;xhr.open('GET','/" + "BalFloatPower?value='+value,true);xhr.send();}else{alert('Invalid value. Please enter a value " + "between 100 and 2000');}}}"; + content += + "function editBalMaxPackV(){var value=prompt('Battery pack max voltage temporarily raised to this value during " + "forced balancing. Value in V');if(value!==null){if(value>=380&&value<=410){var xhr=new " + "XMLHttpRequest();xhr.onload=editComplete;xhr.onerror=editError;xhr.open('GET','/" + "BalMaxPackV?value='+value,true);xhr.send();}else{alert('Invalid value. Please enter a value " + "between 380 and 410');}}}"; + content += + "function editBalMaxCellV(){var value=prompt('Cellvoltage max temporarily raised to this value during forced " + "balancing. Value in mV');if(value!==null){if(value>=3400&&value<=3750){var xhr=new " + "XMLHttpRequest();xhr.onload=editComplete;xhr.onerror=editError;xhr.open('GET','/" + "BalMaxCellV?value='+value,true);xhr.send();}else{alert('Invalid value. Please enter a value " + "between 3400 and 3750');}}}"; + content += + "function editBalMaxDevCellV(){var value=prompt('Cellvoltage max deviation temporarily raised to this value " + "during forced balancing. Value in mV');if(value!==null){if(value>=300&&value<=600){var xhr=new " + "XMLHttpRequest();xhr.onload=editComplete;xhr.onerror=editError;xhr.open('GET','/" + "BalMaxDevCellV?value='+value,true);xhr.send();}else{alert('Invalid value. Please enter a value " + "between 300 and 600');}}}"; +#endif + #ifdef TEST_FAKE_BATTERY content += "function editFakeBatteryVoltage(){var value=prompt('Enter new fake battery " diff --git a/Software/src/devboard/webserver/webserver.cpp b/Software/src/devboard/webserver/webserver.cpp index 88e42481..085244b8 100644 --- a/Software/src/devboard/webserver/webserver.cpp +++ b/Software/src/devboard/webserver/webserver.cpp @@ -395,6 +395,93 @@ void init_webserver() { }); #endif // TEST_FAKE_BATTERY +#ifdef TESLA_MODEL_3Y_BATTERY + + // Route for editing balancing enabled + server.on("/TeslaBalAct", HTTP_GET, [](AsyncWebServerRequest* request) { + if (WEBSERVER_AUTH_REQUIRED && !request->authenticate(http_username, http_password)) + return request->requestAuthentication(); + if (request->hasParam("value")) { + String value = request->getParam("value")->value(); + datalayer.battery.settings.user_requests_balancing = value.toInt(); + store_settings(); + request->send(200, "text/plain", "Updated successfully"); + } else { + request->send(400, "text/plain", "Bad Request"); + } + }); + + // Route for editing balancing max time + server.on("/BalTime", HTTP_GET, [](AsyncWebServerRequest* request) { + if (WEBSERVER_AUTH_REQUIRED && !request->authenticate(http_username, http_password)) + return request->requestAuthentication(); + if (request->hasParam("value")) { + String value = request->getParam("value")->value(); + datalayer.battery.settings.balancing_time_ms = static_cast(value.toFloat() * 60000); + store_settings(); + request->send(200, "text/plain", "Updated successfully"); + } else { + request->send(400, "text/plain", "Bad Request"); + } + }); + + // Route for editing balancing max power + server.on("/BalFloatPower", HTTP_GET, [](AsyncWebServerRequest* request) { + if (WEBSERVER_AUTH_REQUIRED && !request->authenticate(http_username, http_password)) + return request->requestAuthentication(); + if (request->hasParam("value")) { + String value = request->getParam("value")->value(); + datalayer.battery.settings.balancing_float_power_W = static_cast(value.toFloat()); + store_settings(); + request->send(200, "text/plain", "Updated successfully"); + } else { + request->send(400, "text/plain", "Bad Request"); + } + }); + + // Route for editing balancing max pack voltage + server.on("/BalMaxPackV", HTTP_GET, [](AsyncWebServerRequest* request) { + if (WEBSERVER_AUTH_REQUIRED && !request->authenticate(http_username, http_password)) + return request->requestAuthentication(); + if (request->hasParam("value")) { + String value = request->getParam("value")->value(); + datalayer.battery.settings.balancing_max_pack_voltage_dV = static_cast(value.toFloat() * 10); + store_settings(); + request->send(200, "text/plain", "Updated successfully"); + } else { + request->send(400, "text/plain", "Bad Request"); + } + }); + + // Route for editing balancing max cell voltage + server.on("/BalMaxCellV", HTTP_GET, [](AsyncWebServerRequest* request) { + if (WEBSERVER_AUTH_REQUIRED && !request->authenticate(http_username, http_password)) + return request->requestAuthentication(); + if (request->hasParam("value")) { + String value = request->getParam("value")->value(); + datalayer.battery.settings.balancing_max_cell_voltage_mV = static_cast(value.toFloat()); + store_settings(); + request->send(200, "text/plain", "Updated successfully"); + } else { + request->send(400, "text/plain", "Bad Request"); + } + }); + + // Route for editing balancing max cell voltage deviation + server.on("/BalMaxDevCellV", HTTP_GET, [](AsyncWebServerRequest* request) { + if (WEBSERVER_AUTH_REQUIRED && !request->authenticate(http_username, http_password)) + return request->requestAuthentication(); + if (request->hasParam("value")) { + String value = request->getParam("value")->value(); + datalayer.battery.settings.balancing_max_deviation_cell_voltage_mV = static_cast(value.toFloat()); + store_settings(); + request->send(200, "text/plain", "Updated successfully"); + } else { + request->send(400, "text/plain", "Bad Request"); + } + }); +#endif + #if defined CHEVYVOLT_CHARGER || defined NISSANLEAF_CHARGER // Route for editing ChargerTargetV server.on("/updateChargeSetpointV", HTTP_GET, [](AsyncWebServerRequest* request) { From 5ec554708e81b6cf4a453cb047ab3157b14adef8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20=C3=96ster?= Date: Tue, 31 Dec 2024 00:47:57 +0200 Subject: [PATCH 2/5] Force SOC to 99pct during balancing --- Software/src/battery/TESLA-BATTERY.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/Software/src/battery/TESLA-BATTERY.cpp b/Software/src/battery/TESLA-BATTERY.cpp index 70dbbdb2..eca9da8f 100644 --- a/Software/src/battery/TESLA-BATTERY.cpp +++ b/Software/src/battery/TESLA-BATTERY.cpp @@ -904,6 +904,7 @@ void update_values_battery() { //This function maps all the values fetched via // During forced balancing request via webserver, we allow the battery to exceed normal safety parameters if (datalayer.battery.settings.user_requests_balancing) { + datalayer.battery.status.real_soc = 9900; //Force battery to show up as 99% when balancing datalayer.battery.info.max_design_voltage_dV = datalayer.battery.settings.balancing_max_pack_voltage_dV; datalayer.battery.info.max_cell_voltage_mV = datalayer.battery.settings.balancing_max_cell_voltage_mV; datalayer.battery.info.max_cell_voltage_deviation_mV = From a60a1c3b5c8c7d107c9ae61ad4147b699d4462c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20=C3=96ster?= Date: Tue, 7 Jan 2025 16:01:17 +0200 Subject: [PATCH 3/5] Add balancing start/end event --- Software/src/devboard/safety/safety.cpp | 8 ++++++-- Software/src/devboard/utils/events.cpp | 6 ++++++ Software/src/devboard/utils/events.h | 4 +++- 3 files changed, 15 insertions(+), 3 deletions(-) diff --git a/Software/src/devboard/safety/safety.cpp b/Software/src/devboard/safety/safety.cpp index 35d28579..ca69e5fb 100644 --- a/Software/src/devboard/safety/safety.cpp +++ b/Software/src/devboard/safety/safety.cpp @@ -244,14 +244,18 @@ void update_machineryprotection() { // If this is the start of the balancing period, capture the current time if (datalayer.battery.settings.balancing_start_time_ms == 0) { datalayer.battery.settings.balancing_start_time_ms = millis(); - //TODO, raise info event? Forced balancing starting! + set_event(EVENT_BALANCING_START, 0); + } else { + clear_event(EVENT_BALANCING_START); } // Check if the elapsed time exceeds the balancing time if (millis() - datalayer.battery.settings.balancing_start_time_ms >= datalayer.battery.settings.balancing_time_ms) { datalayer.battery.settings.user_requests_balancing = false; datalayer.battery.settings.balancing_start_time_ms = 0; // Reset the start time - //TODO, raise info event? Balancing time elapsed. Turning off... + set_event(EVENT_BALANCING_END, 0); + } else { + clear_event(EVENT_BALANCING_END); } } } diff --git a/Software/src/devboard/utils/events.cpp b/Software/src/devboard/utils/events.cpp index 88546af3..23ad4454 100644 --- a/Software/src/devboard/utils/events.cpp +++ b/Software/src/devboard/utils/events.cpp @@ -159,6 +159,8 @@ void init_events(void) { events.entries[EVENT_SOC_PLAUSIBILITY_ERROR].level = EVENT_LEVEL_WARNING; events.entries[EVENT_SOC_UNAVAILABLE].level = EVENT_LEVEL_WARNING; events.entries[EVENT_KWH_PLAUSIBILITY_ERROR].level = EVENT_LEVEL_INFO; + events.entries[EVENT_BALANCING_START].level = EVENT_LEVEL_INFO; + events.entries[EVENT_BALANCING_END].level = EVENT_LEVEL_INFO; events.entries[EVENT_BATTERY_EMPTY].level = EVENT_LEVEL_INFO; events.entries[EVENT_BATTERY_FULL].level = EVENT_LEVEL_INFO; events.entries[EVENT_BATTERY_FROZEN].level = EVENT_LEVEL_INFO; @@ -302,6 +304,10 @@ const char* get_event_message_string(EVENTS_ENUM_TYPE event) { return "Warning: SOC not sent by BMS. Calibrate BMS via app."; case EVENT_KWH_PLAUSIBILITY_ERROR: return "Info: kWh remaining reported by battery not plausible. Battery needs cycling."; + case EVENT_BALANCING_START: + return "Info: Balancing has started"; + case EVENT_BALANCING_END: + return "Info: Balancing has ended"; case EVENT_BATTERY_EMPTY: return "Info: Battery is completely discharged"; case EVENT_BATTERY_FULL: diff --git a/Software/src/devboard/utils/events.h b/Software/src/devboard/utils/events.h index 972842fd..c0c27b68 100644 --- a/Software/src/devboard/utils/events.h +++ b/Software/src/devboard/utils/events.h @@ -6,7 +6,7 @@ // #define INCLUDE_EVENTS_TEST // Enable to run an event test loop, see events_test_on_target.cpp -#define EE_MAGIC_HEADER_VALUE 0x0018 // 0x0000 to 0xFFFF +#define EE_MAGIC_HEADER_VALUE 0x0019 // 0x0000 to 0xFFFF #define GENERATE_ENUM(ENUM) ENUM, #define GENERATE_STRING(STRING) #STRING, @@ -45,6 +45,8 @@ XX(EVENT_SOC_PLAUSIBILITY_ERROR) \ XX(EVENT_SOC_UNAVAILABLE) \ XX(EVENT_KWH_PLAUSIBILITY_ERROR) \ + XX(EVENT_BALANCING_START) \ + XX(EVENT_BALANCING_END) \ XX(EVENT_BATTERY_EMPTY) \ XX(EVENT_BATTERY_FULL) \ XX(EVENT_BATTERY_FROZEN) \ From 3353bece6c5e236cb60f1f42623be9691875ded5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20=C3=96ster?= Date: Tue, 7 Jan 2025 16:02:02 +0200 Subject: [PATCH 4/5] Increase LFP allowed imbalance mv limit --- Software/src/battery/TESLA-BATTERY.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Software/src/battery/TESLA-BATTERY.h b/Software/src/battery/TESLA-BATTERY.h index 4a843838..9739b0e5 100644 --- a/Software/src/battery/TESLA-BATTERY.h +++ b/Software/src/battery/TESLA-BATTERY.h @@ -23,7 +23,7 @@ #define MAX_PACK_VOLTAGE_3Y_LFP 3880 // V+1, if pack voltage goes over this, charge stops #define MIN_PACK_VOLTAGE_3Y_LFP 2968 // V+1, if pack voltage goes below this, discharge stops #define MAX_CELL_DEVIATION_NCA_NCM 500 //LED turns yellow on the board if mv delta exceeds this value -#define MAX_CELL_DEVIATION_LFP 200 //LED turns yellow on the board if mv delta exceeds this value +#define MAX_CELL_DEVIATION_LFP 400 //LED turns yellow on the board if mv delta exceeds this value #define MAX_CELL_VOLTAGE_NCA_NCM 4250 //Battery is put into emergency stop if one cell goes over this value #define MIN_CELL_VOLTAGE_NCA_NCM 2950 //Battery is put into emergency stop if one cell goes below this value #define MAX_CELL_VOLTAGE_LFP 3550 //Battery is put into emergency stop if one cell goes over this value From d852692b2ec19251f7ef43d74e879adb3c8a2d80 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20=C3=96ster?= Date: Tue, 7 Jan 2025 16:03:01 +0200 Subject: [PATCH 5/5] Increase max LFP allowed cell limit --- Software/src/battery/TESLA-BATTERY.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Software/src/battery/TESLA-BATTERY.h b/Software/src/battery/TESLA-BATTERY.h index 9739b0e5..51439722 100644 --- a/Software/src/battery/TESLA-BATTERY.h +++ b/Software/src/battery/TESLA-BATTERY.h @@ -26,7 +26,7 @@ #define MAX_CELL_DEVIATION_LFP 400 //LED turns yellow on the board if mv delta exceeds this value #define MAX_CELL_VOLTAGE_NCA_NCM 4250 //Battery is put into emergency stop if one cell goes over this value #define MIN_CELL_VOLTAGE_NCA_NCM 2950 //Battery is put into emergency stop if one cell goes below this value -#define MAX_CELL_VOLTAGE_LFP 3550 //Battery is put into emergency stop if one cell goes over this value +#define MAX_CELL_VOLTAGE_LFP 3650 //Battery is put into emergency stop if one cell goes over this value #define MIN_CELL_VOLTAGE_LFP 2800 //Battery is put into emergency stop if one cell goes below this value //#define EXP_TESLA_BMS_DIGITAL_HVIL // Experimental parameter. Forces the transmission of additional CAN frames for experimental purposes, to test potential HVIL issues in 3/Y packs with newer firmware.