Compare commits

...

19 commits
v9.1.1 ... main

Author SHA1 Message Date
Daniel Öster
f3a6157080
Merge pull request #1592 from dalathegreat/improvement/stark-BMS-on-by-default
Stark CMR: Make BMS power on by default
2025-10-02 14:27:47 +03:00
Daniel Öster
867512eecd Make Stark BMS power on by default 2025-10-01 21:13:41 +03:00
Daniel Öster
f9109d0348
Merge pull request #1587 from dalathegreat/bugfix/passwords-dashes
Bugfix: Allow dash as special character in usernames/passwords
2025-10-01 20:08:27 +03:00
Daniel Öster
9943406836 Make Wifi use actual set SSID for AP 2025-09-30 23:29:08 +03:00
Daniel Öster
18ee9d6c27 Allow dash as special character in usernames/passwords 2025-09-30 22:16:05 +03:00
Daniel Öster
31ea1f0928
Update Software.cpp 2025-09-30 14:43:14 +03:00
Daniel Öster
dad97b36e1
Update Software.cpp 2025-09-30 14:37:22 +03:00
Daniel Öster
19a1634f4e
Merge pull request #1580 from jonny5532/fix/settings-validation
Fix hostname/MQTT settings validation
2025-09-28 14:08:04 +03:00
Jonny
aa375dc36a Fix broken hostname/MQTT settings validation 2025-09-28 09:05:30 +01:00
Jonny
a91f8ab4d4 Remove redundant ^$ on settings input patterns 2025-09-28 08:59:38 +01:00
Daniel Öster
7fbc9ffcc6
Merge pull request #1575 from greenoem/tesla-feature-events
Feature: Tesla - add initial/basic events
2025-09-27 23:15:05 +03:00
Daniel Öster
579e0e0bcc
Update Software.cpp 2025-09-27 22:09:42 +03:00
James Brookes
b26c451eaf Fix % in SOC_RECALIBRATION string 2025-09-27 19:27:15 +01:00
James Brookes
ee9e78e80b Merge branch 'tesla-feature-events' of https://github.com/greenoem/Battery-Emulator into tesla-feature-events 2025-09-27 18:04:10 +01:00
James Brookes
706b4a7cea Add contactor weld warning event, change events to native latch type 2025-09-27 18:03:56 +01:00
greenoem
f5ba607fa6
Merge branch 'dalathegreat:main' into tesla-feature-events 2025-09-26 21:21:46 +01:00
James Brookes
04d9a36292 Add event and logging text for SOC Reset 2025-09-26 10:38:56 +01:00
James Brookes
c6b7ff82c0 Alter BMS_a145_SW_SOC_Change event to once only 2025-09-25 19:09:46 +01:00
James Brookes
95ee6ff9ae Add events for BMS_a145_SW_SOC_Change, BMS reset and SOC reset 2025-09-25 19:03:54 +01:00
7 changed files with 99 additions and 46 deletions

View file

@ -34,7 +34,7 @@
#endif
// The current software version, shown on webserver
const char* version_number = "9.1.1";
const char* version_number = "9.2.dev";
// Interval timers
volatile unsigned long currentMillis = 0;

View file

@ -483,11 +483,24 @@ void TeslaBattery::
} else {
clear_event(EVENT_BATTERY_FUSE);
}
// Raise any Tesla BMS events in BE
// Events: Informational
if (BMS_a145_SW_SOC_Change) { // BMS has newly recalibrated pack SOC
set_event_latched(EVENT_BATTERY_SOC_RECALIBRATION, 0); // Latcched as BMS_a145 can be active for a while
} else if (!BMS_a145_SW_SOC_Change) {
clear_event(EVENT_BATTERY_SOC_RECALIBRATION);
}
// Events: Warning
if (BMS_contactorState == 5) { // BMS has detected welded contactor(s)
set_event_latched(EVENT_CONTACTOR_WELDED, 0);
} else if (BMS_contactorState != 5) {
clear_event(EVENT_CONTACTOR_WELDED);
}
if (user_selected_tesla_GTW_chassisType > 1) { //{{0, "Model S"}, {1, "Model X"}, {2, "Model 3"}, {3, "Model Y"}};
// Autodetect algoritm for chemistry on 3/Y packs.
// Autodetect algorithm for chemistry on 3/Y packs.
// NCM/A batteries have 96s, LFP has 102-108s
// Drawback with this check is that it takes 3-5minutes before all cells have been counted!
// Drawback with this check is that it takes 3-5 minutes before all cells have been counted!
if (datalayer.battery.info.number_of_cells > 101) {
datalayer.battery.info.chemistry = battery_chemistry_enum::LFP;
}
@ -528,23 +541,28 @@ void TeslaBattery::
//Start the BMS ECU reset statemachine, only if contactors are OPEN and BMS ECU allows it
stateMachineBMSReset = 0;
datalayer.battery.settings.user_requests_tesla_bms_reset = false;
logging.println("BMS reset requested");
logging.println("INFO: BMS reset requested");
} else {
logging.println("ERROR: BMS reset failed due to contactors not being open, or BMS ECU not allowing it");
stateMachineBMSReset = 0xFF;
datalayer.battery.settings.user_requests_tesla_bms_reset = false;
set_event(EVENT_BMS_RESET_REQ_FAIL, 0);
clear_event(EVENT_BMS_RESET_REQ_FAIL);
}
}
if (datalayer.battery.settings.user_requests_tesla_soc_reset) {
if (datalayer.battery.status.real_soc < 1500 || datalayer.battery.status.real_soc > 9000) {
//Start the SOC reset statemachine, only if SOC < 15% or > 90%
if ((datalayer.battery.status.real_soc < 1500 || datalayer.battery.status.real_soc > 9000) &&
battery_contactor == 1) {
//Start the SOC reset statemachine, only if SOC less than 15% or greater than 90%, and contactors open
stateMachineSOCReset = 0;
datalayer.battery.settings.user_requests_tesla_soc_reset = false;
logging.println("SOC reset requested");
logging.println("INFO: SOC reset requested");
} else {
logging.println("ERROR: SOC reset failed due to SOC not being less than 15 or greater than 90");
logging.println("ERROR: SOC reset failed, SOC not < 15 or > 90, or contactors not open");
stateMachineSOCReset = 0xFF;
datalayer.battery.settings.user_requests_tesla_soc_reset = false;
set_event(EVENT_BATTERY_SOC_RESET_FAIL, 0);
clear_event(EVENT_BATTERY_SOC_RESET_FAIL);
}
}
@ -779,7 +797,7 @@ void TeslaBattery::
datalayer_extended.tesla.HVP_shuntBarTempStatus = HVP_shuntBarTempStatus;
datalayer_extended.tesla.HVP_shuntAsicTempStatus = HVP_shuntAsicTempStatus;
//Safety checks for CAN message sesnding
//Safety checks for CAN message sending
if ((datalayer.system.status.inverter_allows_contactor_closing == true) &&
(datalayer.battery.status.bms_status != FAULT) && (!datalayer.system.settings.equipment_stop_active)) {
// Carry on: 0x221 DRIVE state & reset power down timer
@ -1667,10 +1685,10 @@ void TeslaBattery::handle_incoming_can_frame(CAN_frame rx_frame) {
}
*/
break;
case 0x612: // CAN UDSs for BMS
case 0x612: // CAN UDS responses for BMS
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
//BMS Query
if (stateMachineBMSQuery != 0xFF && stateMachineBMSReset == 0xFF) {
if (stateMachineBMSQuery != 0xFF && stateMachineBMSReset == 0xFF && stateMachineSOCReset == 0xFF) {
if (memcmp(rx_frame.data.u8, "\x02\x50\x03\xAA\xAA\xAA\xAA\xAA", 8) == 0) {
//Received initial response, proceed to actual query
logging.println("CAN UDS: Received BMS query initial handshake reply");
@ -1713,15 +1731,28 @@ void TeslaBattery::handle_incoming_can_frame(CAN_frame rx_frame) {
break;
}
}
//BMS Reset
if (stateMachineBMSQuery == 0xFF) { // Make sure this is reset request not query
//BMS ECU responses
if (memcmp(rx_frame.data.u8, "\x02\x67\x06\xAA\xAA\xAA\xAA\xAA", 8) == 0) {
logging.println("CAN UDS: ECU unlocked");
} else if (memcmp(rx_frame.data.u8, "\x03\x7F\x11\x78\xAA\xAA\xAA\xAA", 8) == 0) {
logging.println("CAN UDS: ECU reset request successful but ECU busy, response pending");
} else if (memcmp(rx_frame.data.u8, "\x02\x51\x01\xAA\xAA\xAA\xAA\xAA", 8) == 0) {
logging.println("CAN UDS: ECU reset positive response, 1 second downtime");
logging.println("CAN UDS: BMS ECU unlocked");
}
if (memcmp(rx_frame.data.u8, "\x03\x7F\x11\x78\xAA\xAA\xAA\xAA", 8) == 0) {
logging.println("CAN UDS: BMS ECU reset request successful but ECU busy, response pending");
}
if (memcmp(rx_frame.data.u8, "\x02\x51\x01\xAA\xAA\xAA\xAA\xAA", 8) == 0) {
logging.println("CAN UDS: BMS ECU reset positive response, 1 second downtime");
set_event(EVENT_BMS_RESET_REQ_SUCCESS, 0);
clear_event(EVENT_BMS_RESET_REQ_SUCCESS);
}
if (memcmp(rx_frame.data.u8, "\x05\x71\x01\x04\x07\x01\xAA\xAA", 8) == 0) {
logging.println("CAN UDS: BMS SOC reset accepted, resetting BMS ECU");
set_event(EVENT_BATTERY_SOC_RESET_SUCCESS, 0);
clear_event(EVENT_BATTERY_SOC_RESET_SUCCESS);
stateMachineBMSReset = 6; // BMS ECU already unlocked etc. so we jump straight to reset
}
if (memcmp(rx_frame.data.u8, "\x05\x71\x01\x04\x07\x00\xAA\xAA", 8) == 0) {
logging.println("CAN UDS: BMS SOC reset failed");
set_event(EVENT_BATTERY_SOC_RESET_FAIL, 0);
clear_event(EVENT_BATTERY_SOC_RESET_FAIL);
}
break;
default:
@ -2308,7 +2339,7 @@ void TeslaBattery::printFaultCodesIfActive() {
printDebugIfActive(BMS_a139_SW_DC_Link_V_Irrational, "ERROR: BMS_a139_SW_DC_Link_V_Irrational");
printDebugIfActive(BMS_a141_SW_BMB_Status_Warning, "ERROR: BMS_a141_SW_BMB_Status_Warning");
printDebugIfActive(BMS_a144_Hvp_Config_Mismatch, "ERROR: BMS_a144_Hvp_Config_Mismatch");
printDebugIfActive(BMS_a145_SW_SOC_Change, "ERROR: BMS_a145_SW_SOC_Change");
printDebugIfActive(BMS_a145_SW_SOC_Change, "INFO: BMS_a145_SW_SOC_Change");
printDebugIfActive(BMS_a146_SW_Brick_Overdischarged, "ERROR: BMS_a146_SW_Brick_Overdischarged");
printDebugIfActive(BMS_a149_SW_Missing_Config_Block, "ERROR: BMS_a149_SW_Missing_Config_Block");
printDebugIfActive(BMS_a151_SW_external_isolation, "ERROR: BMS_a151_SW_external_isolation");

View file

@ -21,6 +21,9 @@ class StarkHal : public Esp32Hal {
public:
const char* name() { return "Stark CMR Module"; }
//Always enable BMS power on Stark CMR, it does not collide with any pin definitions
virtual bool always_enable_bms_power() { return true; }
// Not needed, GPIO 16 has hardware pullup for PSRAM compatibility
virtual gpio_num_t PIN_5V_EN() { return GPIO_NUM_NC; }

View file

@ -67,6 +67,9 @@ void init_events(void) {
events.entries[EVENT_BATTERY_UNDERVOLTAGE].level = EVENT_LEVEL_WARNING;
events.entries[EVENT_BATTERY_VALUE_UNAVAILABLE].level = EVENT_LEVEL_WARNING;
events.entries[EVENT_BATTERY_ISOLATION].level = EVENT_LEVEL_WARNING;
events.entries[EVENT_BATTERY_SOC_RECALIBRATION].level = EVENT_LEVEL_INFO;
events.entries[EVENT_BATTERY_SOC_RESET_SUCCESS].level = EVENT_LEVEL_INFO;
events.entries[EVENT_BATTERY_SOC_RESET_FAIL].level = EVENT_LEVEL_INFO;
events.entries[EVENT_VOLTAGE_DIFFERENCE].level = EVENT_LEVEL_INFO;
events.entries[EVENT_SOH_DIFFERENCE].level = EVENT_LEVEL_WARNING;
events.entries[EVENT_SOH_LOW].level = EVENT_LEVEL_ERROR;
@ -124,6 +127,8 @@ void init_events(void) {
events.entries[EVENT_EQUIPMENT_STOP].level = EVENT_LEVEL_ERROR;
events.entries[EVENT_SD_INIT_FAILED].level = EVENT_LEVEL_WARNING;
events.entries[EVENT_PERIODIC_BMS_RESET].level = EVENT_LEVEL_INFO;
events.entries[EVENT_BMS_RESET_REQ_SUCCESS].level = EVENT_LEVEL_INFO;
events.entries[EVENT_BMS_RESET_REQ_FAIL].level = EVENT_LEVEL_INFO;
events.entries[EVENT_BATTERY_TEMP_DEVIATION_HIGH].level = EVENT_LEVEL_WARNING;
events.entries[EVENT_GPIO_CONFLICT].level = EVENT_LEVEL_ERROR;
events.entries[EVENT_GPIO_NOT_DEFINED].level = EVENT_LEVEL_ERROR;
@ -244,6 +249,12 @@ String get_event_message_string(EVENTS_ENUM_TYPE event) {
return "Battery measurement unavailable. Check 12V power supply and battery wiring!";
case EVENT_BATTERY_ISOLATION:
return "Battery reports isolation error. High voltage might be leaking to ground. Check battery!";
case EVENT_BATTERY_SOC_RECALIBRATION:
return "The BMS updated the HV battery State of Charge (SOC) by more than 3pct based on SocByOcv.";
case EVENT_BATTERY_SOC_RESET_SUCCESS:
return "SOC reset routine was successful.";
case EVENT_BATTERY_SOC_RESET_FAIL:
return "SOC reset routine failed - check SOC is < 15 or > 90, and contactors are open.";
case EVENT_VOLTAGE_DIFFERENCE:
return "Too large voltage diff between the batteries. Second battery cannot join the DC-link";
case EVENT_SOH_DIFFERENCE:
@ -361,7 +372,11 @@ String get_event_message_string(EVENTS_ENUM_TYPE event) {
case EVENT_SD_INIT_FAILED:
return "SD card initialization failed, check hardware. Power must be removed to reset the SD card.";
case EVENT_PERIODIC_BMS_RESET:
return "BMS Reset Event Completed.";
return "BMS reset event completed.";
case EVENT_BMS_RESET_REQ_SUCCESS:
return "BMS reset request completed successfully.";
case EVENT_BMS_RESET_REQ_FAIL:
return "BMS reset request failed - check contactors are open.";
case EVENT_GPIO_CONFLICT:
return "GPIO Pin Conflict: The pin used by '" + esp32hal->failed_allocator() + "' is already allocated by '" +
esp32hal->conflicting_allocator() + "'. Please check your configuration and assign different pins.";

View file

@ -49,6 +49,9 @@
XX(EVENT_BATTERY_ISOLATION) \
XX(EVENT_BATTERY_REQUESTS_HEAT) \
XX(EVENT_BATTERY_WARMED_UP) \
XX(EVENT_BATTERY_SOC_RECALIBRATION) \
XX(EVENT_BATTERY_SOC_RESET_SUCCESS) \
XX(EVENT_BATTERY_SOC_RESET_FAIL) \
XX(EVENT_VOLTAGE_DIFFERENCE) \
XX(EVENT_SOH_DIFFERENCE) \
XX(EVENT_SOH_LOW) \
@ -107,6 +110,8 @@
XX(EVENT_AUTOMATIC_PRECHARGE_FAILURE) \
XX(EVENT_SD_INIT_FAILED) \
XX(EVENT_PERIODIC_BMS_RESET) \
XX(EVENT_BMS_RESET_REQ_SUCCESS) \
XX(EVENT_BMS_RESET_REQ_FAIL) \
XX(EVENT_BATTERY_TEMP_DEVIATION_HIGH) \
XX(EVENT_GPIO_NOT_DEFINED) \
XX(EVENT_GPIO_CONFLICT) \

View file

@ -1058,19 +1058,19 @@ const char* getCANInterfaceName(CAN_Interface interface) {
<div class="if-cbms">
<label>Battery max design voltage (V): </label>
<input name='BATTPVMAX' pattern="^[0-9]+(\.[0-9]+)?$" type='text' value='%BATTPVMAX%'
<input name='BATTPVMAX' pattern="[0-9]+(\.[0-9]+)?" type='text' value='%BATTPVMAX%'
title="Maximum safe voltage for the entire battery pack in volts. Used as charge target and protection limits." />
<label>Battery min design voltage (V): </label>
<input name='BATTPVMIN' pattern="^[0-9]+(\.[0-9]+)?$" type='text' value='%BATTPVMIN%'
<input name='BATTPVMIN' pattern="[0-9]+(\.[0-9]+)?" type='text' value='%BATTPVMIN%'
title="Minimum safe voltage for the entire battery pack in volts. Further discharge not possible below this limit." />
<label>Cell max design voltage (mV): </label>
<input name='BATTCVMAX' pattern="^[0-9]+$" type='text' value='%BATTCVMAX%'
<input name='BATTCVMAX' pattern="[0-9]+" type='text' value='%BATTCVMAX%'
title="Maximum voltage per individual cell in millivolts. Charging stops if one cell reaches this voltage." />
<label>Cell min design voltage (mV): </label>
<input name='BATTCVMIN' pattern="^[0-9]+$" type='text' value='%BATTCVMIN%'
<input name='BATTCVMIN' pattern="[0-9]+$" type='text' value='%BATTCVMIN%'
title="Minimum voltage per individual cell in millivolts. Discharge stops if one cell drops to this voltage." />
</div>
@ -1104,33 +1104,33 @@ const char* getCANInterfaceName(CAN_Interface interface) {
<div class="if-sofar">
<label>Sofar Battery ID (0-15): </label>
<input name='SOFAR_ID' type='text' value="%SOFAR_ID%" pattern="^[0-9]{1,2}$" />
<input name='SOFAR_ID' type='text' value="%SOFAR_ID%" pattern="[0-9]{1,2}" />
</div>
<div class="if-pylonish">
<label>Reported cell count (0 for default): </label>
<input name='INVCELLS' type='text' value="%INVCELLS%" pattern="^[0-9]+$" />
<input name='INVCELLS' type='text' value="%INVCELLS%" pattern="[0-9]+" />
</div>
<div class="if-pylonish if-solax">
<label>Reported module count (0 for default): </label>
<input name='INVMODULES' type='text' value="%INVMODULES%" pattern="^[0-9]+$" />
<input name='INVMODULES' type='text' value="%INVMODULES%" pattern="[0-9]+" />
</div>
<div class="if-pylonish">
<label>Reported cells per module (0 for default): </label>
<input name='INVCELLSPER' type='text' value="%INVCELLSPER%" pattern="^[0-9]+$" />
<input name='INVCELLSPER' type='text' value="%INVCELLSPER%" pattern="[0-9]+" />
<label>Reported voltage level (0 for default): </label>
<input name='INVVLEVEL' type='text' value="%INVVLEVEL%" pattern="^[0-9]+$" />
<input name='INVVLEVEL' type='text' value="%INVVLEVEL%" pattern="[0-9]+" />
<label>Reported Ah capacity (0 for default): </label>
<input name='INVCAPACITY' type='text' value="%INVCAPACITY%" pattern="^[0-9]+$" />
<input name='INVCAPACITY' type='text' value="%INVCAPACITY%" pattern="[0-9]+" />
</div>
<div class="if-solax">
<label>Reported battery type (in decimal): </label>
<input name='INVBTYPE' type='text' value="%INVBTYPE%" pattern="^[0-9]+$" />
<input name='INVBTYPE' type='text' value="%INVBTYPE%" pattern="[0-9]+" />
<label>Inverter should ignore contactors: </label>
<input type='checkbox' name='INVICNT' value='on' %INVICNT% />
@ -1227,7 +1227,7 @@ const char* getCANInterfaceName(CAN_Interface interface) {
<div class="if-extprecharge">
<label>Precharge, maximum ms before fault: </label>
<input name='MAXPRETIME' type='text' value="%MAXPRETIME%" pattern="^[0-9]+$" />
<input name='MAXPRETIME' type='text' value="%MAXPRETIME%" pattern="[0-9]+" />
<label>Normally Open (NO) inverter disconnect contactor: </label>
<input type='checkbox' name='NOINVDISC' value='on' %NOINVDISC% />
@ -1249,14 +1249,14 @@ const char* getCANInterfaceName(CAN_Interface interface) {
<label>Access point name: </label>
<input type='text' name='APNAME' value="%APNAME%"
pattern="[A-Za-z0-9!#*]{8,63}"
title="Name must be 8-63 characters long and may only contain letters, numbers and some special characters: !#*"
pattern="[A-Za-z0-9!#*-]{8,63}"
title="Name must be 8-63 characters long and may only contain letters, numbers and some special characters: !#*-"
required />
<label>Access point password: </label>
<input type='text' name='APPASSWORD' value="%APPASSWORD%"
pattern="[A-Za-z0-9!#*]{8,63}"
title="Password must be 8-63 characters long and may only contain letters, numbers and some special characters: !#*"
pattern="[A-Za-z0-9!#*-]{8,63}"
title="Password must be 8-63 characters long and may only contain letters, numbers and some special characters: !#*-"
required />
<label>Wifi channel 0-14: </label>
@ -1266,8 +1266,8 @@ const char* getCANInterfaceName(CAN_Interface interface) {
<label>Custom Wifi hostname: </label>
<input type='text' name='HOSTNAME' value="%HOSTNAME%"
pattern="[A-Za-z0-9!*]"
title="Optional: Hostname may only contain only letters, numbers, ! and *" />
pattern="[A-Za-z0-9!#*-]+"
title="Optional: Hostname may only contain letters, numbers and some special characters: !#*-" />
<label>Use static IP address: </label>
<input type='checkbox' name='STATICIP' value='on' %STATICIP% />
@ -1305,18 +1305,18 @@ const char* getCANInterfaceName(CAN_Interface interface) {
<div class='if-mqtt'>
<label>MQTT server: </label>
<input type='text' name='MQTTSERVER' value="%MQTTSERVER%"
pattern="^([A-Za-z0-9.-]+|)/$"
pattern="[A-Za-z0-9.-]+"
title="Hostname (letters, numbers, dots, hyphens)" />
<label>MQTT port: </label>
<input type='number' name='MQTTPORT' value="%MQTTPORT%"
min="1" max="65535" step="1"
title="Port number (1-65535)" />
<label>MQTT user: </label><input type='text' name='MQTTUSER' value="%MQTTUSER%"
pattern="[A-Za-z0-9!#*]"
title="MQTT username can only contain letters, numbers and some special characters: !#*" />
pattern="[A-Za-z0-9!#*-]+"
title="MQTT username can only contain letters, numbers and some special characters: !#*-" />
<label>MQTT password: </label><input type='password' name='MQTTPASSWORD' value="%MQTTPASSWORD%"
pattern="[A-Za-z0-9!#*]"
title="MQTT password can only contain letters, numbers and some special characters: !#*" />
pattern="[A-Za-z0-9!#*-]+"
title="MQTT password can only contain letters, numbers and some special characters: !#*-" />
<label>MQTT timeout ms: </label>
<input name='MQTTTIMEOUT' type='number' value="%MQTTTIMEOUT%"
min="1" max="60000" step="1"

View file

@ -240,7 +240,6 @@ void init_mDNS() {
}
void init_WiFi_AP() {
ssidAP = std::string("BatteryEmulator") + WiFi.macAddress().c_str();
DEBUG_PRINTF("Creating Access Point: %s\n", ssidAP.c_str());
DEBUG_PRINTF("With password: %s\n", passwordAP.c_str());