mirror of
https://github.com/dalathegreat/Battery-Emulator.git
synced 2025-10-03 17:59:27 +02:00
Merge pull request #1487 from dalathegreat/improvement/estimated-charge
Feature: Add manual override charge/discharge limits
This commit is contained in:
commit
a8532b6f78
14 changed files with 66 additions and 34 deletions
|
@ -60,7 +60,7 @@ void BmwIXBattery::update_values() { //This function maps all the values fetche
|
||||||
|
|
||||||
datalayer.battery.status.soh_pptt = min_soh_state;
|
datalayer.battery.status.soh_pptt = min_soh_state;
|
||||||
|
|
||||||
datalayer.battery.status.max_discharge_power_W = MAX_DISCHARGE_POWER_ALLOWED_W;
|
datalayer.battery.status.max_discharge_power_W = datalayer.battery.status.override_discharge_power_W;
|
||||||
|
|
||||||
//datalayer.battery.status.max_charge_power_W = 3200; //10000; //Aux HV Port has 100A Fuse Moved to Ramping
|
//datalayer.battery.status.max_charge_power_W = 3200; //10000; //Aux HV Port has 100A Fuse Moved to Ramping
|
||||||
|
|
||||||
|
@ -70,10 +70,10 @@ void BmwIXBattery::update_values() { //This function maps all the values fetche
|
||||||
} else if (datalayer.battery.status.real_soc > RAMPDOWN_SOC) {
|
} else if (datalayer.battery.status.real_soc > RAMPDOWN_SOC) {
|
||||||
// When real SOC is between RAMPDOWN_SOC-99%, ramp the value between Max<->0
|
// When real SOC is between RAMPDOWN_SOC-99%, ramp the value between Max<->0
|
||||||
datalayer.battery.status.max_charge_power_W =
|
datalayer.battery.status.max_charge_power_W =
|
||||||
MAX_CHARGE_POWER_ALLOWED_W *
|
datalayer.battery.status.override_charge_power_W *
|
||||||
(1 - (datalayer.battery.status.real_soc - RAMPDOWN_SOC) / (10000.0 - RAMPDOWN_SOC));
|
(1 - (datalayer.battery.status.real_soc - RAMPDOWN_SOC) / (10000.0 - RAMPDOWN_SOC));
|
||||||
} else { // No limits, max charging power allowed
|
} else { // No limits, max charging power allowed
|
||||||
datalayer.battery.status.max_charge_power_W = MAX_CHARGE_POWER_ALLOWED_W;
|
datalayer.battery.status.max_charge_power_W = datalayer.battery.status.override_charge_power_W;
|
||||||
}
|
}
|
||||||
|
|
||||||
datalayer.battery.status.temperature_min_dC = min_battery_temperature;
|
datalayer.battery.status.temperature_min_dC = min_battery_temperature;
|
||||||
|
|
|
@ -48,8 +48,6 @@ class BmwIXBattery : public CanBattery {
|
||||||
static const int MAX_CELL_DEVIATION_MV = 250;
|
static const int MAX_CELL_DEVIATION_MV = 250;
|
||||||
static const int MAX_CELL_VOLTAGE_MV = 4300; //Battery is put into emergency stop if one cell goes over this value
|
static const int MAX_CELL_VOLTAGE_MV = 4300; //Battery is put into emergency stop if one cell goes over this value
|
||||||
static const int MIN_CELL_VOLTAGE_MV = 2800; //Battery is put into emergency stop if one cell goes below this value
|
static const int MIN_CELL_VOLTAGE_MV = 2800; //Battery is put into emergency stop if one cell goes below this value
|
||||||
static const int MAX_DISCHARGE_POWER_ALLOWED_W = 10000;
|
|
||||||
static const int MAX_CHARGE_POWER_ALLOWED_W = 10000;
|
|
||||||
static const int MAX_CHARGE_POWER_WHEN_TOPBALANCING_W = 500;
|
static const int MAX_CHARGE_POWER_WHEN_TOPBALANCING_W = 500;
|
||||||
static const int RAMPDOWN_SOC =
|
static const int RAMPDOWN_SOC =
|
||||||
9000; // (90.00) SOC% to start ramping down from max charge power towards 0 at 100.00%
|
9000; // (90.00) SOC% to start ramping down from max charge power towards 0 at 100.00%
|
||||||
|
|
|
@ -96,14 +96,14 @@ void BoltAmperaBattery::update_values() { //This function maps all the values f
|
||||||
} else if (datalayer.battery.status.real_soc > RAMPDOWN_SOC) {
|
} else if (datalayer.battery.status.real_soc > RAMPDOWN_SOC) {
|
||||||
// When real SOC is between RAMPDOWN_SOC-99%, ramp the value between Max<->0
|
// When real SOC is between RAMPDOWN_SOC-99%, ramp the value between Max<->0
|
||||||
datalayer.battery.status.max_charge_power_W =
|
datalayer.battery.status.max_charge_power_W =
|
||||||
MAX_CHARGE_POWER_ALLOWED_W *
|
datalayer.battery.status.override_charge_power_W *
|
||||||
(1 - (datalayer.battery.status.real_soc - RAMPDOWN_SOC) / (10000.0 - RAMPDOWN_SOC));
|
(1 - (datalayer.battery.status.real_soc - RAMPDOWN_SOC) / (10000.0 - RAMPDOWN_SOC));
|
||||||
} else { // No limits, max charging power allowed
|
} else { // No limits, max charging power allowed
|
||||||
datalayer.battery.status.max_charge_power_W = MAX_CHARGE_POWER_ALLOWED_W;
|
datalayer.battery.status.max_charge_power_W = datalayer.battery.status.override_charge_power_W;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Discharge power is also set in .h file (TODO: Remove this estimation when real value has been found)
|
// Discharge power is also set in .h file (TODO: Remove this estimation when real value has been found)
|
||||||
datalayer.battery.status.max_discharge_power_W = MAX_DISCHARGE_POWER_ALLOWED_W;
|
datalayer.battery.status.max_discharge_power_W = datalayer.battery.status.override_discharge_power_W;
|
||||||
|
|
||||||
datalayer.battery.status.temperature_min_dC = temperature_lowest_C * 10;
|
datalayer.battery.status.temperature_min_dC = temperature_lowest_C * 10;
|
||||||
|
|
||||||
|
|
|
@ -16,8 +16,6 @@ class BoltAmperaBattery : public CanBattery {
|
||||||
|
|
||||||
private:
|
private:
|
||||||
BoltAmperaHtmlRenderer renderer;
|
BoltAmperaHtmlRenderer renderer;
|
||||||
static const int MAX_DISCHARGE_POWER_ALLOWED_W = 10000;
|
|
||||||
static const int MAX_CHARGE_POWER_ALLOWED_W = 10000;
|
|
||||||
static const int MAX_CHARGE_POWER_WHEN_TOPBALANCING_W = 500;
|
static const int MAX_CHARGE_POWER_WHEN_TOPBALANCING_W = 500;
|
||||||
static const int RAMPDOWN_SOC =
|
static const int RAMPDOWN_SOC =
|
||||||
9000; // (90.00) SOC% to start ramping down from max charge power towards 0 at 100.00%
|
9000; // (90.00) SOC% to start ramping down from max charge power towards 0 at 100.00%
|
||||||
|
|
|
@ -20,9 +20,11 @@ void CellPowerBms::update_values() {
|
||||||
|
|
||||||
datalayer.battery.status.current_dA = battery_pack_current_dA;
|
datalayer.battery.status.current_dA = battery_pack_current_dA;
|
||||||
|
|
||||||
datalayer.battery.status.max_charge_power_W = 5000; //TODO, is this available via CAN?
|
datalayer.battery.status.max_charge_power_W =
|
||||||
|
datalayer.battery.status.override_charge_power_W; //TODO, is this available via CAN?
|
||||||
|
|
||||||
datalayer.battery.status.max_discharge_power_W = 5000; //TODO, is this available via CAN?
|
datalayer.battery.status.max_discharge_power_W =
|
||||||
|
datalayer.battery.status.override_discharge_power_W; //TODO, is this available via CAN?
|
||||||
|
|
||||||
datalayer.battery.status.temperature_min_dC = (int16_t)(pack_temperature_low_C * 10);
|
datalayer.battery.status.temperature_min_dC = (int16_t)(pack_temperature_low_C * 10);
|
||||||
|
|
||||||
|
|
|
@ -20,9 +20,11 @@ void ImievCZeroIonBattery::
|
||||||
(static_cast<double>(datalayer.battery.status.real_soc) / 10000) * datalayer.battery.info.total_capacity_Wh);
|
(static_cast<double>(datalayer.battery.status.real_soc) / 10000) * datalayer.battery.info.total_capacity_Wh);
|
||||||
|
|
||||||
//We do not know the max charge/discharge power is sent by the battery. We hardcode value for now.
|
//We do not know the max charge/discharge power is sent by the battery. We hardcode value for now.
|
||||||
datalayer.battery.status.max_charge_power_W = 10000; // 10kW //TODO: Fix when CAN is decoded
|
datalayer.battery.status.max_charge_power_W =
|
||||||
|
datalayer.battery.status.override_charge_power_W; //TODO: Fix when CAN is decoded
|
||||||
|
|
||||||
datalayer.battery.status.max_discharge_power_W = 10000; // 10kW //TODO: Fix when CAN is decoded
|
datalayer.battery.status.max_discharge_power_W =
|
||||||
|
datalayer.battery.status.override_discharge_power_W; //TODO: Fix when CAN is decoded
|
||||||
|
|
||||||
static int n = sizeof(cell_voltages) / sizeof(cell_voltages[0]);
|
static int n = sizeof(cell_voltages) / sizeof(cell_voltages[0]);
|
||||||
max_volt_cel = cell_voltages[0]; // Initialize max with the first element of the array
|
max_volt_cel = cell_voltages[0]; // Initialize max with the first element of the array
|
||||||
|
|
|
@ -33,20 +33,20 @@ void RjxzsBms::update_values() {
|
||||||
datalayer.battery.status.current_dA = total_current;
|
datalayer.battery.status.current_dA = total_current;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Charge power is set in .h file
|
// Charge power is manually set
|
||||||
if (datalayer.battery.status.real_soc > 9900) {
|
if (datalayer.battery.status.real_soc > 9900) {
|
||||||
datalayer.battery.status.max_charge_power_W = MAX_CHARGE_POWER_WHEN_TOPBALANCING_W;
|
datalayer.battery.status.max_charge_power_W = MAX_CHARGE_POWER_WHEN_TOPBALANCING_W;
|
||||||
} else if (datalayer.battery.status.real_soc > RAMPDOWN_SOC) {
|
} else if (datalayer.battery.status.real_soc > RAMPDOWN_SOC) {
|
||||||
// When real SOC is between RAMPDOWN_SOC-99%, ramp the value between Max<->0
|
// When real SOC is between RAMPDOWN_SOC-99%, ramp the value between Max<->0
|
||||||
datalayer.battery.status.max_charge_power_W =
|
datalayer.battery.status.max_charge_power_W =
|
||||||
MAX_CHARGE_POWER_ALLOWED_W *
|
datalayer.battery.status.override_charge_power_W *
|
||||||
(1 - (datalayer.battery.status.real_soc - RAMPDOWN_SOC) / (10000.0 - RAMPDOWN_SOC));
|
(1 - (datalayer.battery.status.real_soc - RAMPDOWN_SOC) / (10000.0 - RAMPDOWN_SOC));
|
||||||
} else { // No limits, max charging power allowed
|
} else { // No limits, max charging power allowed
|
||||||
datalayer.battery.status.max_charge_power_W = MAX_CHARGE_POWER_ALLOWED_W;
|
datalayer.battery.status.max_charge_power_W = datalayer.battery.status.override_charge_power_W;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Discharge power is also set in .h file
|
// Discharge power is manually set
|
||||||
datalayer.battery.status.max_discharge_power_W = MAX_DISCHARGE_POWER_ALLOWED_W;
|
datalayer.battery.status.max_discharge_power_W = datalayer.battery.status.override_discharge_power_W;
|
||||||
|
|
||||||
uint16_t temperatures[] = {
|
uint16_t temperatures[] = {
|
||||||
module_1_temperature, module_2_temperature, module_3_temperature, module_4_temperature,
|
module_1_temperature, module_2_temperature, module_3_temperature, module_4_temperature,
|
||||||
|
|
|
@ -15,9 +15,6 @@ class RjxzsBms : public CanBattery {
|
||||||
static constexpr const char* Name = "RJXZS BMS, DIY battery";
|
static constexpr const char* Name = "RJXZS BMS, DIY battery";
|
||||||
|
|
||||||
private:
|
private:
|
||||||
/* Tweak these according to your battery build */
|
|
||||||
static const int MAX_DISCHARGE_POWER_ALLOWED_W = 5000;
|
|
||||||
static const int MAX_CHARGE_POWER_ALLOWED_W = 5000;
|
|
||||||
static const int MAX_CHARGE_POWER_WHEN_TOPBALANCING_W = 500;
|
static const int MAX_CHARGE_POWER_WHEN_TOPBALANCING_W = 500;
|
||||||
static const int RAMPDOWN_SOC =
|
static const int RAMPDOWN_SOC =
|
||||||
9000; // (90.00) SOC% to start ramping down from max charge power towards 0 at 100.00%
|
9000; // (90.00) SOC% to start ramping down from max charge power towards 0 at 100.00%
|
||||||
|
|
|
@ -627,8 +627,8 @@ void TeslaBattery::
|
||||||
// Define the allowed discharge power
|
// Define the allowed discharge power
|
||||||
datalayer.battery.status.max_discharge_power_W = (battery_max_discharge_current * (battery_volts / 10));
|
datalayer.battery.status.max_discharge_power_W = (battery_max_discharge_current * (battery_volts / 10));
|
||||||
// Cap the allowed discharge power if higher than the maximum discharge power allowed
|
// Cap the allowed discharge power if higher than the maximum discharge power allowed
|
||||||
if (datalayer.battery.status.max_discharge_power_W > MAXDISCHARGEPOWERALLOWED) {
|
if (datalayer.battery.status.max_discharge_power_W > datalayer.battery.status.override_discharge_power_W) {
|
||||||
datalayer.battery.status.max_discharge_power_W = MAXDISCHARGEPOWERALLOWED;
|
datalayer.battery.status.max_discharge_power_W = datalayer.battery.status.override_discharge_power_W;
|
||||||
}
|
}
|
||||||
|
|
||||||
//The allowed charge power behaves strangely. We instead estimate this value
|
//The allowed charge power behaves strangely. We instead estimate this value
|
||||||
|
@ -649,7 +649,7 @@ void TeslaBattery::
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else { // No limits, max charging power allowed
|
} else { // No limits, max charging power allowed
|
||||||
datalayer.battery.status.max_charge_power_W = MAXCHARGEPOWERALLOWED;
|
datalayer.battery.status.max_charge_power_W = datalayer.battery.status.override_charge_power_W;
|
||||||
}
|
}
|
||||||
|
|
||||||
datalayer.battery.status.temperature_min_dC = battery_min_temp;
|
datalayer.battery.status.temperature_min_dC = battery_min_temp;
|
||||||
|
|
|
@ -39,15 +39,6 @@ class TeslaBattery : public CanBattery {
|
||||||
TeslaHtmlRenderer renderer;
|
TeslaHtmlRenderer renderer;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
/* Modify these if needed */
|
|
||||||
static const int MAXCHARGEPOWERALLOWED =
|
|
||||||
15000; // 15000W we use a define since the value supplied by Tesla is always 0
|
|
||||||
static const int MAXDISCHARGEPOWERALLOWED =
|
|
||||||
60000; // 60000W we use a define since the value supplied by Tesla is always 0
|
|
||||||
|
|
||||||
// Set this to true to try to close contactors/full startup even with no inverter defined/connected
|
|
||||||
bool batteryTestOverride = false;
|
|
||||||
|
|
||||||
/* Do not change anything below this line! */
|
/* Do not change anything below this line! */
|
||||||
static const int RAMPDOWN_SOC = 900; // 90.0 SOC% to start ramping down from max charge power towards 0 at 100.00%
|
static const int RAMPDOWN_SOC = 900; // 90.0 SOC% to start ramping down from max charge power towards 0 at 100.00%
|
||||||
static const int RAMPDOWNPOWERALLOWED = 10000; // What power we ramp down from towards top balancing
|
static const int RAMPDOWNPOWERALLOWED = 10000; // What power we ramp down from towards top balancing
|
||||||
|
|
|
@ -156,6 +156,10 @@ void init_stored_settings() {
|
||||||
datalayer.system.info.SD_logging_active = settings.getBool("SDLOGENABLED", false);
|
datalayer.system.info.SD_logging_active = settings.getBool("SDLOGENABLED", false);
|
||||||
datalayer.battery.status.led_mode = (led_mode_enum)settings.getUInt("LEDMODE", false);
|
datalayer.battery.status.led_mode = (led_mode_enum)settings.getUInt("LEDMODE", false);
|
||||||
|
|
||||||
|
//Some early integrations need manually set allowed charge/discharge power
|
||||||
|
datalayer.battery.status.override_charge_power_W = settings.getUInt("CHGPOWER", 1000);
|
||||||
|
datalayer.battery.status.override_discharge_power_W = settings.getUInt("DCHGPOWER", 1000);
|
||||||
|
|
||||||
// WIFI AP is enabled by default unless disabled in the settings
|
// WIFI AP is enabled by default unless disabled in the settings
|
||||||
wifiap_enabled = settings.getBool("WIFIAPENABLED", true);
|
wifiap_enabled = settings.getBool("WIFIAPENABLED", true);
|
||||||
wifi_channel = settings.getUInt("WIFICHANNEL", 2000);
|
wifi_channel = settings.getUInt("WIFICHANNEL", 2000);
|
||||||
|
|
|
@ -69,6 +69,13 @@ struct DATALAYER_BATTERY_STATUS_TYPE {
|
||||||
/** Instantaneous battery current in deciAmpere. 95 = 9.5 A */
|
/** Instantaneous battery current in deciAmpere. 95 = 9.5 A */
|
||||||
int16_t current_dA;
|
int16_t current_dA;
|
||||||
|
|
||||||
|
/* Some early integrations do not support reading allowed charge power from battery
|
||||||
|
On these integrations we need to have the user specify what limits the battery can take */
|
||||||
|
/** Overriden allowed battery discharge power in Watts. Set by user */
|
||||||
|
uint32_t override_discharge_power_W = 0;
|
||||||
|
/** Overriden allowed battery charge power in Watts. Set by user */
|
||||||
|
uint32_t override_charge_power_W = 0;
|
||||||
|
|
||||||
/** uint16_t */
|
/** uint16_t */
|
||||||
/** State of health in integer-percent x 100. 9900 = 99.00% */
|
/** State of health in integer-percent x 100. 9900 = 99.00% */
|
||||||
uint16_t soh_pptt = 9900;
|
uint16_t soh_pptt = 9900;
|
||||||
|
|
|
@ -299,6 +299,14 @@ String settings_processor(const String& var, BatteryEmulatorSettingsStore& setti
|
||||||
return String(settings.getUInt("WIFICHANNEL", 0));
|
return String(settings.getUInt("WIFICHANNEL", 0));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (var == "CHGPOWER") {
|
||||||
|
return String(settings.getUInt("CHGPOWER", 0));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (var == "DCHGPOWER") {
|
||||||
|
return String(settings.getUInt("DCHGPOWER", 0));
|
||||||
|
}
|
||||||
|
|
||||||
if (var == "LOCALIP1") {
|
if (var == "LOCALIP1") {
|
||||||
return String(settings.getUInt("LOCALIP1", 0));
|
return String(settings.getUInt("LOCALIP1", 0));
|
||||||
}
|
}
|
||||||
|
@ -869,6 +877,17 @@ const char* getCANInterfaceName(CAN_Interface interface) {
|
||||||
display: contents;
|
display: contents;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
form .if-estimated { display: none; } /* Integrations with manually set charge/discharge power */
|
||||||
|
form[data-battery="3"] .if-estimated,
|
||||||
|
form[data-battery="4"] .if-estimated,
|
||||||
|
form[data-battery="6"] .if-estimated,
|
||||||
|
form[data-battery="14"] .if-estimated,
|
||||||
|
form[data-battery="24"] .if-estimated,
|
||||||
|
form[data-battery="32"] .if-estimated,
|
||||||
|
form[data-battery="33"] .if-estimated {
|
||||||
|
display: contents;
|
||||||
|
}
|
||||||
|
|
||||||
form .if-dblbtr { display: none; }
|
form .if-dblbtr { display: none; }
|
||||||
form[data-dblbtr="true"] .if-dblbtr {
|
form[data-dblbtr="true"] .if-dblbtr {
|
||||||
display: contents;
|
display: contents;
|
||||||
|
@ -963,6 +982,14 @@ const char* getCANInterfaceName(CAN_Interface interface) {
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="if-estimated">
|
||||||
|
<label>Manual charging power, watt: </label>
|
||||||
|
<input name='CHGPOWER' pattern="^[0-9]+$" type='text' value='%CHGPOWER%' />
|
||||||
|
|
||||||
|
<label>Manual discharge power, watt: </label>
|
||||||
|
<input name='DCHGPOWER' pattern="^[0-9]+$" type='text' value='%DCHGPOWER%' />
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="if-battery">
|
<div class="if-battery">
|
||||||
<label for='BATTCOMM'>Battery comm I/F: </label><select name='BATTCOMM' id='BATTCOMM'>
|
<label for='BATTCOMM'>Battery comm I/F: </label><select name='BATTCOMM' id='BATTCOMM'>
|
||||||
%BATTCOMM%
|
%BATTCOMM%
|
||||||
|
|
|
@ -467,6 +467,12 @@ void init_webserver() {
|
||||||
} else if (p->name() == "WIFICHANNEL") {
|
} else if (p->name() == "WIFICHANNEL") {
|
||||||
auto type = atoi(p->value().c_str());
|
auto type = atoi(p->value().c_str());
|
||||||
settings.saveUInt("WIFICHANNEL", type);
|
settings.saveUInt("WIFICHANNEL", type);
|
||||||
|
} else if (p->name() == "DCHGPOWER") {
|
||||||
|
auto type = atoi(p->value().c_str());
|
||||||
|
settings.saveUInt("DCHGPOWER", type);
|
||||||
|
} else if (p->name() == "CHGPOWER") {
|
||||||
|
auto type = atoi(p->value().c_str());
|
||||||
|
settings.saveUInt("CHGPOWER", type);
|
||||||
} else if (p->name() == "LOCALIP1") {
|
} else if (p->name() == "LOCALIP1") {
|
||||||
auto type = atoi(p->value().c_str());
|
auto type = atoi(p->value().c_str());
|
||||||
settings.saveUInt("LOCALIP1", type);
|
settings.saveUInt("LOCALIP1", type);
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue