mirror of
https://github.com/dalathegreat/Battery-Emulator.git
synced 2025-10-03 17:59:27 +02:00
Add Tesla LFP balancing feature
This commit is contained in:
parent
4d68c653c7
commit
cc6761a8a5
6 changed files with 209 additions and 4 deletions
|
@ -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
|
||||
|
|
|
@ -11,7 +11,8 @@
|
|||
|
||||
/* 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 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
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -97,6 +97,42 @@ String settings_processor(const String& var) {
|
|||
content += "</div>";
|
||||
#endif
|
||||
|
||||
#ifdef TESLA_MODEL_3Y_BATTERY
|
||||
|
||||
// Start a new block with grey background color
|
||||
content += "<div style='background-color: #303E47; padding: 10px; margin-bottom: 10px;border-radius: 50px'>";
|
||||
|
||||
content +=
|
||||
"<h4 style='color: white;'>Manual LFP balancing: <span id='TSL_BAL_ACT'>" +
|
||||
String(datalayer.battery.settings.user_requests_balancing ? "<span>✓</span>"
|
||||
: "<span style='color: red;'>✕</span>") +
|
||||
"</span> <button onclick='editTeslaBalAct()'>Edit</button></h4>";
|
||||
content +=
|
||||
"<h4 style='color: " + String(datalayer.battery.settings.user_requests_balancing ? "white" : "darkgrey") +
|
||||
";'>Balancing max time: " + String(datalayer.battery.settings.balancing_time_ms / 60000.0, 1) +
|
||||
" Minutes </span> <button onclick='editBalTime()'>Edit</button></h4>";
|
||||
content +=
|
||||
"<h4 style='color: " + String(datalayer.battery.settings.user_requests_balancing ? "white" : "darkgrey") +
|
||||
";'>Balancing float power: " + String(datalayer.battery.settings.balancing_float_power_W / 1.0, 0) +
|
||||
" W </span> <button onclick='editBalFloatPower()'>Edit</button></h4>";
|
||||
content +=
|
||||
"<h4 style='color: " + String(datalayer.battery.settings.user_requests_balancing ? "white" : "darkgrey") +
|
||||
";'>Max battery voltage: " + String(datalayer.battery.settings.balancing_max_pack_voltage_dV / 10.0, 0) +
|
||||
" V </span> <button onclick='editBalMaxPackV()'>Edit</button></h4>";
|
||||
content +=
|
||||
"<h4 style='color: " + String(datalayer.battery.settings.user_requests_balancing ? "white" : "darkgrey") +
|
||||
";'>Max cell voltage: " + String(datalayer.battery.settings.balancing_max_cell_voltage_mV / 1.0, 0) +
|
||||
" mV </span> <button onclick='editBalMaxCellV()'>Edit</button></h4>";
|
||||
content +=
|
||||
"<h4 style='color: " + String(datalayer.battery.settings.user_requests_balancing ? "white" : "darkgrey") +
|
||||
";'>Max cell voltage deviation: " +
|
||||
String(datalayer.battery.settings.balancing_max_deviation_cell_voltage_mV / 1.0, 0) +
|
||||
" mV </span> <button onclick='editBalMaxDevCellV()'>Edit</button></h4>";
|
||||
|
||||
// Close the block
|
||||
content += "</div>";
|
||||
#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 "
|
||||
|
|
|
@ -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<uint32_t>(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<uint16_t>(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<uint16_t>(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<uint16_t>(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<uint16_t>(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) {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue