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) {