Merge branch 'main' into feature/geely-geometry-battery

This commit is contained in:
Daniel Öster 2025-04-18 23:48:10 +03:00
commit fc3c410db7
19 changed files with 1566 additions and 73 deletions

View file

@ -55,6 +55,7 @@ jobs:
- BYD_ATTO_3_BATTERY
- CELLPOWER_BMS
- CHADEMO_BATTERY
- CMFA_EV_BATTERY
- FOXESS_BATTERY
- IMIEV_CZERO_ION_BATTERY
- JAGUAR_IPACE_BATTERY

View file

@ -49,7 +49,7 @@
volatile unsigned long long bmsResetTimeOffset = 0;
// The current software version, shown on webserver
const char* version_number = "8.10.dev";
const char* version_number = "8.11.dev";
// Interval timers
unsigned long previousMillis10ms = 0;
@ -336,6 +336,9 @@ void check_interconnect_available() {
#endif // DOUBLE_BATTERY
void update_calculated_values() {
/* Update CPU temperature*/
datalayer.system.info.CPU_temperature = temperatureRead();
/* Calculate allowed charge/discharge currents*/
if (datalayer.battery.status.voltage_dV > 10) {
// Only update value when we have voltage available to avoid div0. TODO: This should be based on nominal voltage
@ -427,6 +430,12 @@ void update_calculated_values() {
calc_soc = 10000 * (calc_soc - datalayer.battery.settings.min_percentage);
calc_soc = calc_soc / (datalayer.battery.settings.max_percentage - datalayer.battery.settings.min_percentage);
datalayer.battery.status.reported_soc = calc_soc;
//Extra safety since we allow scaling negatively, if real% is < 1.00%, zero it out
if (datalayer.battery.status.real_soc < 100) {
datalayer.battery.status.reported_soc = 0;
} else {
datalayer.battery.status.reported_soc = calc_soc;
}
// Calculate the scaled remaining capacity in Wh
if (datalayer.battery.info.total_capacity_Wh > 0 && datalayer.battery.status.real_soc > 0) {

View file

@ -18,6 +18,7 @@
//#define CELLPOWER_BMS
//#define CHADEMO_BATTERY //NOTE: inherently enables CONTACTOR_CONTROL below
#define GEELY_GEOMETRY_C_BATTERY
//#define CMFA_EV_BATTERY
//#define IMIEV_CZERO_ION_BATTERY
//#define JAGUAR_IPACE_BATTERY
//#define KIA_E_GMP_BATTERY

View file

@ -38,6 +38,10 @@ void setup_can_shunt();
#include "CHADEMO-SHUNTS.h"
#endif
#ifdef CMFA_EV_BATTERY
#include "CMFA-EV-BATTERY.h"
#endif
#ifdef FOXESS_BATTERY
#include "FOXESS-BATTERY.h"
#endif

View file

@ -12,6 +12,10 @@
*/
/* Do not change code below unless you are sure what you are doing */
#define NOT_DETERMINED_YET 0
#define STANDARD_RANGE 1
#define EXTENDED_RANGE 2
static uint8_t battery_type = NOT_DETERMINED_YET;
static unsigned long previousMillis50 = 0; // will store last time a 50ms CAN Message was send
static unsigned long previousMillis100 = 0; // will store last time a 100ms CAN Message was send
static unsigned long previousMillis500 = 0; // will store last time a 500ms CAN Message was send
@ -37,8 +41,7 @@ static int16_t BMS_average_cell_temperature = 0;
static uint16_t BMS_lowest_cell_voltage_mV = 3300;
static uint16_t BMS_highest_cell_voltage_mV = 3300;
static uint8_t battery_frame_index = 0;
#define NOF_CELLS 126
static uint16_t battery_cellvoltages[NOF_CELLS] = {0};
static uint16_t battery_cellvoltages[CELLCOUNT_EXTENDED] = {0};
#ifdef DOUBLE_BATTERY
static int16_t battery2_temperature_ambient = 0;
static int16_t battery2_daughterboard_temperatures[10];
@ -56,7 +59,7 @@ static int16_t BMS2_average_cell_temperature = 0;
static uint16_t BMS2_lowest_cell_voltage_mV = 3300;
static uint16_t BMS2_highest_cell_voltage_mV = 3300;
static uint8_t battery2_frame_index = 0;
static uint16_t battery2_cellvoltages[NOF_CELLS] = {0};
static uint16_t battery2_cellvoltages[CELLCOUNT_EXTENDED] = {0};
#endif //DOUBLE_BATTERY
#define POLL_FOR_BATTERY_SOC 0x05
#define POLL_FOR_BATTERY_VOLTAGE 0x08
@ -90,20 +93,39 @@ CAN_frame ATTO_3_7E7_POLL = {.FD = false,
// Define the data points for %SOC depending on pack voltage
const uint8_t numPoints = 14;
const uint16_t SOC[numPoints] = {10000, 9970, 9490, 8470, 7750, 6790, 5500, 4900, 3910, 3000, 2280, 1600, 480, 0};
const uint16_t voltage[numPoints] = {4400, 4230, 4180, 4171, 4169, 4160, 4130,
const uint16_t voltage_extended[numPoints] = {4400, 4230, 4180, 4171, 4169, 4160, 4130,
4121, 4119, 4100, 4070, 4030, 3950, 3800};
const uint16_t voltage_standard[numPoints] = {3620, 3485, 3443, 3435, 3433, 3425, 3400,
3392, 3390, 3375, 3350, 3315, 3250, 3140};
uint16_t estimateSOC(uint16_t packVoltage) { // Linear interpolation function
if (packVoltage >= voltage[0]) {
uint16_t estimateSOCextended(uint16_t packVoltage) { // Linear interpolation function
if (packVoltage >= voltage_extended[0]) {
return SOC[0];
}
if (packVoltage <= voltage[numPoints - 1]) {
if (packVoltage <= voltage_extended[numPoints - 1]) {
return SOC[numPoints - 1];
}
for (int i = 1; i < numPoints; ++i) {
if (packVoltage >= voltage[i]) {
double t = (packVoltage - voltage[i]) / (voltage[i - 1] - voltage[i]);
if (packVoltage >= voltage_extended[i]) {
double t = (packVoltage - voltage_extended[i]) / (voltage_extended[i - 1] - voltage_extended[i]);
return SOC[i] + t * (SOC[i - 1] - SOC[i]);
}
}
return 0; // Default return for safety, should never reach here
}
uint16_t estimateSOCstandard(uint16_t packVoltage) { // Linear interpolation function
if (packVoltage >= voltage_standard[0]) {
return SOC[0];
}
if (packVoltage <= voltage_standard[numPoints - 1]) {
return SOC[numPoints - 1];
}
for (int i = 1; i < numPoints; ++i) {
if (packVoltage >= voltage_standard[i]) {
double t = (packVoltage - voltage_standard[i]) / (voltage_standard[i - 1] - voltage_standard[i]);
return SOC[i] + t * (SOC[i - 1] - SOC[i]);
}
}
@ -120,7 +142,12 @@ void update_values_battery() { //This function maps all the values fetched via
// When the battery is crashed hard, it locks itself and SOC becomes unavailable.
// We instead estimate the SOC% based on the battery voltage.
// This is a bad solution, you wont be able to use 100% of the battery
datalayer.battery.status.real_soc = estimateSOC(datalayer.battery.status.voltage_dV);
if (battery_type == EXTENDED_RANGE) {
datalayer.battery.status.real_soc = estimateSOCextended(datalayer.battery.status.voltage_dV);
}
if (battery_type == STANDARD_RANGE) {
datalayer.battery.status.real_soc = estimateSOCstandard(datalayer.battery.status.voltage_dV);
}
SOC_method = ESTIMATED;
#else // Pack is not crashed, we can use periodically transmitted SOC
datalayer.battery.status.real_soc = battery_highprecision_SOC * 10;
@ -141,7 +168,41 @@ void update_values_battery() { //This function maps all the values fetched via
datalayer.battery.status.cell_min_voltage_mV = BMS_lowest_cell_voltage_mV;
//Map all cell voltages to the global array
memcpy(datalayer.battery.status.cell_voltages_mV, battery_cellvoltages, NOF_CELLS * sizeof(uint16_t));
memcpy(datalayer.battery.status.cell_voltages_mV, battery_cellvoltages, CELLCOUNT_EXTENDED * sizeof(uint16_t));
// Check if we are on Standard range or Extended range battery.
// We use a variety of checks to ensure we catch a potential Standard range battery
if ((battery_cellvoltages[125] > 0) && (battery_type == NOT_DETERMINED_YET)) {
battery_type = EXTENDED_RANGE;
}
if ((battery_cellvoltages[104] == 4095) && (battery_type == NOT_DETERMINED_YET)) {
battery_type = STANDARD_RANGE; //This cell reading is always 4095 on Standard range
}
if ((battery_daughterboard_temperatures[9] == 215) && (battery_type == NOT_DETERMINED_YET)) {
battery_type = STANDARD_RANGE; //Sensor 10 is missing on Standard range
}
if ((battery_daughterboard_temperatures[8] == 215) && (battery_type == NOT_DETERMINED_YET)) {
battery_type = STANDARD_RANGE; //Sensor 9 is missing on Standard range
}
switch (battery_type) {
case STANDARD_RANGE:
datalayer.battery.info.total_capacity_Wh = 50000;
datalayer.battery.info.number_of_cells = CELLCOUNT_STANDARD;
datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_STANDARD_DV;
datalayer.battery.info.min_design_voltage_dV = MIN_PACK_VOLTAGE_STANDARD_DV;
break;
case EXTENDED_RANGE:
datalayer.battery.info.total_capacity_Wh = 60000;
datalayer.battery.info.number_of_cells = CELLCOUNT_EXTENDED;
datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_EXTENDED_DV;
datalayer.battery.info.min_design_voltage_dV = MIN_PACK_VOLTAGE_EXTENDED_DV;
break;
case NOT_DETERMINED_YET:
default:
//Do nothing
break;
}
#ifdef SKIP_TEMPERATURE_SENSOR_NUMBER
// Initialize min and max variables for temperature calculation
@ -263,7 +324,7 @@ void handle_incoming_can_frame_battery(CAN_frame rx_frame) {
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
battery_frame_index = rx_frame.data.u8[0];
if (battery_frame_index < (NOF_CELLS / 3)) {
if (battery_frame_index < (CELLCOUNT_EXTENDED / 3)) {
uint8_t base_index = battery_frame_index * 3;
for (uint8_t i = 0; i < 3; i++) {
battery_cellvoltages[base_index + i] =
@ -447,23 +508,19 @@ void transmit_can_battery() {
void setup_battery(void) { // Performs one time setup at startup
strncpy(datalayer.system.info.battery_protocol, "BYD Atto 3", 63);
datalayer.system.info.battery_protocol[63] = '\0';
datalayer.battery.info.number_of_cells = 126;
datalayer.battery.info.chemistry = battery_chemistry_enum::LFP;
datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_DV;
datalayer.battery.info.min_design_voltage_dV = MIN_PACK_VOLTAGE_DV;
datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_EXTENDED_DV; //Startup in extremes
datalayer.battery.info.min_design_voltage_dV = MIN_PACK_VOLTAGE_STANDARD_DV; //We later determine range
datalayer.battery.info.max_cell_voltage_mV = MAX_CELL_VOLTAGE_MV;
datalayer.battery.info.min_cell_voltage_mV = MIN_CELL_VOLTAGE_MV;
datalayer.battery.info.max_cell_voltage_deviation_mV = MAX_CELL_DEVIATION_MV;
//Due to the Datalayer having 370.0V as startup value, which is 10V lower than the Atto 3 min voltage 380.0V
//We now init the value to 380.1V to avoid false positive events.
datalayer.battery.status.voltage_dV = MIN_PACK_VOLTAGE_DV + 1;
#ifdef DOUBLE_BATTERY
datalayer.battery2.info.number_of_cells = 126;
datalayer.battery2.info.number_of_cells = CELLCOUNT_STANDARD;
datalayer.battery2.info.chemistry = battery_chemistry_enum::LFP;
datalayer.battery2.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_DV;
datalayer.battery2.info.min_design_voltage_dV = MIN_PACK_VOLTAGE_DV;
datalayer.battery2.info.max_cell_voltage_mV = MAX_CELL_VOLTAGE_MV;
datalayer.battery2.info.min_cell_voltage_mV = MIN_CELL_VOLTAGE_MV;
datalayer.battery2.info.max_design_voltage_dV = datalayer.battery.info.max_design_voltage_dV;
datalayer.battery2.info.min_design_voltage_dV = datalayer.battery.info.min_design_voltage_dV;
datalayer.battery2.info.max_cell_voltage_mV = datalayer.battery.info.max_cell_voltage_mV;
datalayer.battery2.info.min_cell_voltage_mV = datalayer.battery.info.min_cell_voltage_mV;
datalayer.battery2.info.max_cell_voltage_deviation_mV = MAX_CELL_DEVIATION_MV;
#endif //DOUBLE_BATTERY
}
@ -478,7 +535,12 @@ void update_values_battery2() { //This function maps all the values fetched via
// We instead estimate the SOC% based on the battery2 voltage
// This is a very bad solution, and as soon as an usable SOC% value has been found on CAN, we should switch to that!
datalayer.battery2.status.real_soc = estimateSOC(datalayer.battery2.status.voltage_dV);
if (battery_type == EXTENDED_RANGE) {
datalayer.battery2.status.real_soc = estimateSOCextended(datalayer.battery2.status.voltage_dV);
}
if (battery_type == STANDARD_RANGE) {
datalayer.battery2.status.real_soc = estimateSOCstandard(datalayer.battery2.status.voltage_dV);
}
datalayer.battery2.status.current_dA = -BMS2_current;
@ -498,7 +560,12 @@ void update_values_battery2() { //This function maps all the values fetched via
datalayer.battery2.status.temperature_max_dC = BMS2_highest_cell_temperature * 10;
//Map all cell voltages to the global array
memcpy(datalayer.battery2.status.cell_voltages_mV, battery2_cellvoltages, NOF_CELLS * sizeof(uint16_t));
memcpy(datalayer.battery2.status.cell_voltages_mV, battery2_cellvoltages, CELLCOUNT_EXTENDED * sizeof(uint16_t));
datalayer.battery2.info.total_capacity_Wh = datalayer.battery.info.total_capacity_Wh;
datalayer.battery2.info.number_of_cells = datalayer.battery.info.number_of_cells;
datalayer.battery2.info.max_design_voltage_dV = datalayer.battery.info.max_design_voltage_dV;
datalayer.battery2.info.min_design_voltage_dV = datalayer.battery.info.min_design_voltage_dV;
}
void handle_incoming_can_frame_battery2(CAN_frame rx_frame) {
@ -515,7 +582,10 @@ void handle_incoming_can_frame_battery2(CAN_frame rx_frame) {
case 0x286:
datalayer.battery2.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
break;
case 0x334 datalayer.battery2.status.CAN_battery_still_alive = CAN_STILL_ALIVE; break; case 0x338:
case 0x334:
datalayer.battery2.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
break;
case 0x338:
datalayer.battery2.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
break;
case 0x344:
@ -568,7 +638,7 @@ void handle_incoming_can_frame_battery2(CAN_frame rx_frame) {
case 0x43D:
datalayer.battery2.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
battery2_frame_index = rx_frame.data.u8[0];
if (battery2_frame_index < (NOF_CELLS / 3)) {
if (battery2_frame_index < (CELLCOUNT_EXTENDED / 3)) {
uint8_t base2_index = battery2_frame_index * 3;
for (uint8_t i = 0; i < 3; i++) {
battery2_cellvoltages[base2_index + i] =

View file

@ -4,7 +4,7 @@
#include "../include.h"
#define USE_ESTIMATED_SOC // If enabled, SOC is estimated from pack voltage. Useful for locked packs. \
// Uncomment this only if you know your BMS is unlocked and able to send SOC%
// Comment out this only if you know your BMS is unlocked and able to send SOC%
#define MAXPOWER_CHARGE_W 10000
#define MAXPOWER_DISCHARGE_W 10000
@ -14,8 +14,12 @@
/* Do not modify the rows below */
#define BATTERY_SELECTED
#define MAX_PACK_VOLTAGE_DV 4410 //5000 = 500.0V
#define MIN_PACK_VOLTAGE_DV 3800
#define CELLCOUNT_EXTENDED 126
#define CELLCOUNT_STANDARD 104
#define MAX_PACK_VOLTAGE_EXTENDED_DV 4410 //Extended range
#define MIN_PACK_VOLTAGE_EXTENDED_DV 3800 //Extended range
#define MAX_PACK_VOLTAGE_STANDARD_DV 3640 //Standard range
#define MIN_PACK_VOLTAGE_STANDARD_DV 3136 //Standard range
#define MAX_CELL_DEVIATION_MV 150
#define MAX_CELL_VOLTAGE_MV 3800 //Battery is put into emergency stop if one cell goes over this value
#define MIN_CELL_VOLTAGE_MV 2800 //Battery is put into emergency stop if one cell goes below this value

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,158 @@
#ifndef CMFA_EV_BATTERY_H
#define CMFA_EV_BATTERY_H
#include "../include.h"
#define BATTERY_SELECTED
#define MAX_PACK_VOLTAGE_DV 3040 //5000 = 500.0V
#define MIN_PACK_VOLTAGE_DV 2185
#define MAX_CELL_DEVIATION_MV 100
#define MAX_CELL_VOLTAGE_MV 4250 //Battery is put into emergency stop if one cell goes over this value
#define MIN_CELL_VOLTAGE_MV 2700 //Battery is put into emergency stop if one cell goes below this value
// OBD2 PID polls. Some of these have been reverse engineered, but there are many unknown values still
#define PID_POLL_SOCZ 0x9001 //122 in log
#define PID_POLL_USOC 0x9002 //5531 (Possible SOC candidate)
#define PID_POLL_SOH_AVERAGE 0x9003
#define PID_POLL_AVERAGE_VOLTAGE_OF_CELLS 0x9006
#define PID_POLL_HIGHEST_CELL_VOLTAGE 0x9007
#define PID_POLL_CELL_NUMBER_HIGHEST_VOLTAGE 0x9008
#define PID_POLL_LOWEST_CELL_VOLTAGE 0x9009
#define PID_POLL_CELL_NUMBER_LOWEST_VOLTAGE 0x900A
#define PID_POLL_CURRENT_OFFSET 0x900C
#define PID_POLL_INSTANT_CURRENT 0x900D
#define PID_POLL_MAX_REGEN 0x900E
#define PID_POLL_MAX_DISCHARGE_POWER 0x900F
#define PID_POLL_12V_BATTERY 0x9011
#define PID_POLL_AVERAGE_TEMPERATURE 0x9012 //749 in log
#define PID_POLL_MIN_TEMPERATURE 0x9013 //736 in log
#define PID_POLL_MAX_TEMPERATURE 0x9014 //752 in log
#define PID_POLL_MAX_CHARGE_POWER 0x9018
#define PID_POLL_END_OF_CHARGE_FLAG 0x9019
#define PID_POLL_INTERLOCK_FLAG 0x901A
#define PID_POLL_BATTERY_IDENTIFICATION 0x901B // Multi frame message
#define PID_POLL_CELL_1 0x9021
#define PID_POLL_CELL_2 0x9022
#define PID_POLL_CELL_3 0x9023
#define PID_POLL_CELL_4 0x9024
#define PID_POLL_CELL_5 0x9025
#define PID_POLL_CELL_6 0x9026
#define PID_POLL_CELL_7 0x9027
#define PID_POLL_CELL_8 0x9028
#define PID_POLL_CELL_9 0x9029
#define PID_POLL_CELL_10 0x902A
#define PID_POLL_CELL_11 0x902B
#define PID_POLL_CELL_12 0x902C
#define PID_POLL_CELL_13 0x902D
#define PID_POLL_CELL_14 0x902E
#define PID_POLL_CELL_15 0x902F
#define PID_POLL_CELL_16 0x9030
#define PID_POLL_CELL_17 0x9031
#define PID_POLL_CELL_18 0x9032
#define PID_POLL_CELL_19 0x9033
#define PID_POLL_CELL_20 0x9034
#define PID_POLL_CELL_21 0x9035
#define PID_POLL_CELL_22 0x9036
#define PID_POLL_CELL_23 0x9037
#define PID_POLL_CELL_24 0x9038
#define PID_POLL_CELL_25 0x9039
#define PID_POLL_CELL_26 0x903A
#define PID_POLL_CELL_27 0x903B
#define PID_POLL_CELL_28 0x903C
#define PID_POLL_CELL_29 0x903D
#define PID_POLL_CELL_30 0x903E
#define PID_POLL_CELL_31 0x903F
#define PID_POLL_DIDS_SUPPORTED_IN_RANGE_9041_9060 0x9040
#define PID_POLL_CELL_32 0x9041
#define PID_POLL_CELL_33 0x9042
#define PID_POLL_CELL_34 0x9043
#define PID_POLL_CELL_35 0x9044
#define PID_POLL_CELL_36 0x9045
#define PID_POLL_CELL_37 0x9046
#define PID_POLL_CELL_38 0x9047
#define PID_POLL_CELL_39 0x9048
#define PID_POLL_CELL_40 0x9049
#define PID_POLL_CELL_41 0x904A
#define PID_POLL_CELL_42 0x904B
#define PID_POLL_CELL_43 0x904C
#define PID_POLL_CELL_44 0x904D
#define PID_POLL_CELL_45 0x904E
#define PID_POLL_CELL_46 0x904F
#define PID_POLL_CELL_47 0x9050
#define PID_POLL_CELL_48 0x9051
#define PID_POLL_CELL_49 0x9052
#define PID_POLL_CELL_50 0x9053
#define PID_POLL_CELL_51 0x9054
#define PID_POLL_CELL_52 0x9055
#define PID_POLL_CELL_53 0x9056
#define PID_POLL_CELL_54 0x9057
#define PID_POLL_CELL_55 0x9058
#define PID_POLL_CELL_56 0x9059
#define PID_POLL_CELL_57 0x905A
#define PID_POLL_CELL_58 0x905B
#define PID_POLL_CELL_59 0x905C
#define PID_POLL_CELL_60 0x905D
#define PID_POLL_CELL_61 0x905E
#define PID_POLL_CELL_62 0x905F
#define PID_POLL_DIDS_SUPPORTED_IN_RANGE_9061_9080 0x9060
#define PID_POLL_CELL_63 0x9061
#define PID_POLL_CELL_64 0x9062
#define PID_POLL_CELL_65 0x9063
#define PID_POLL_CELL_66 0x9064
#define PID_POLL_CELL_67 0x9065
#define PID_POLL_CELL_68 0x9066
#define PID_POLL_CELL_69 0x9067
#define PID_POLL_CELL_70 0x9068
#define PID_POLL_CELL_71 0x9069
#define PID_POLL_CELL_72 0x906A
/*
#define PID_POLL_UNKNOWNX 0x912F // Multi frame message, empty
#define PID_POLL_UNKNOWNX 0x9129
#define PID_POLL_UNKNOWNX 0x9131
#define PID_POLL_UNKNOWNX 0x9132
#define PID_POLL_UNKNOWNX 0x9133
#define PID_POLL_UNKNOWNX 0x9134
#define PID_POLL_UNKNOWNX 0x9135
#define PID_POLL_UNKNOWNX 0x9136
#define PID_POLL_UNKNOWNX 0x9137
#define PID_POLL_UNKNOWNX 0x9138
#define PID_POLL_UNKNOWNX 0x9139
#define PID_POLL_UNKNOWNX 0x913A
#define PID_POLL_UNKNOWNX 0x913B
#define PID_POLL_UNKNOWNX 0x913C
#define PID_POLL_UNKNOWN5 0x912F
#define PID_POLL_UNKNOWNX 0x91B7
*/
#define PID_POLL_SOH_AVAILABLE_POWER_CALCULATION 0x91BC // 0-100%
#define PID_POLL_SOH_GENERATED_POWER_CALCULATION 0x91BD // 0-100%
/*
#define PID_POLL_UNKNOWNX 0x91C1
#define PID_POLL_UNKNOWNX 0x91CD
#define PID_POLL_UNKNOWNX 0x91CF
#define PID_POLL_UNKNOWNX 0x91F6
#define PID_POLL_UNKNOWNX 0x91F7
#define PID_POLL_UNKNOWNX 0x920F
#define PID_POLL_UNKNOWNx 0x9242
*/
#define PID_POLL_CUMULATIVE_ENERGY_WHEN_CHARGING 0x9243
#define PID_POLL_CUMULATIVE_ENERGY_WHEN_DISCHARGING 0x9245
#define PID_POLL_CUMULATIVE_ENERGY_IN_REGEN 0x9247
/*
#define PID_POLL_UNKNOWNx 0x9256
#define PID_POLL_UNKNOWNx 0x9261
#define PID_POLL_UNKNOWN7 0x9284
#define PID_POLL_UNKNOWNx 0xF012
#define PID_POLL_UNKNOWNx 0xF1A0
#define PID_POLL_UNKNOWNx 0xF182
#define PID_POLL_UNKNOWNx 0xF187
#define PID_POLL_UNKNOWNx 0xF188
#define PID_POLL_UNKNOWNx 0xF18A
#define PID_POLL_UNKNOWNx 0xF18C
#define PID_POLL_UNKNOWNx 0xF191
#define PID_POLL_UNKNOWNx 0xF194
#define PID_POLL_UNKNOWNx 0xF195
*/
void setup_battery(void);
void transmit_can_frame(CAN_frame* tx_frame, int interface);
#endif

View file

@ -74,15 +74,16 @@ const uint16_t SOC[] = {10000, 9900, 9800, 9700, 9600, 9500, 9400, 9300, 9200, 9
2500, 2400, 2300, 2200, 2100, 2000, 1900, 1800, 1700, 1600, 1500, 1400, 1300, 1200, 1100,
1000, 900, 800, 700, 600, 500, 400, 300, 200, 100, 0};
const uint16_t voltage[] = {4200, 4171, 4143, 4117, 4093, 4070, 4050, 4031, 4013, 3998, 3985, 3973, 3964, 3957, 3952,
3950, 3941, 3933, 3924, 3916, 3907, 3899, 3890, 3881, 3873, 3864, 3856, 3847, 3839, 3830,
3821, 3813, 3804, 3796, 3787, 3779, 3770, 3761, 3753, 3744, 3736, 3727, 3719, 3710, 3701,
3693, 3684, 3676, 3667, 3659, 3650, 3641, 3633, 3624, 3616, 3607, 3599, 3590, 3581, 3573,
3564, 3556, 3547, 3539, 3530, 3521, 3513, 3504, 3496, 3487, 3479, 3470, 3461, 3453, 3444,
3436, 3427, 3419, 3410, 3401, 3393, 3384, 3376, 3367, 3359, 3350, 3333, 3315, 3297, 3278,
3258, 3237, 3215, 3192, 3166, 3139, 3108, 3074, 3033, 2979, 2850};
const uint16_t voltage[] = {4200, 4173, 4148, 4124, 4102, 4080, 4060, 4041, 4023, 4007, 3993, 3980, 3969, 3959, 3953,
3950, 3941, 3932, 3924, 3915, 3907, 3898, 3890, 3881, 3872, 3864, 3855, 3847, 3838, 3830,
3821, 3812, 3804, 3795, 3787, 3778, 3770, 3761, 3752, 3744, 3735, 3727, 3718, 3710, 3701,
3692, 3684, 3675, 3667, 3658, 3650, 3641, 3632, 3624, 3615, 3607, 3598, 3590, 3581, 3572,
3564, 3555, 3547, 3538, 3530, 3521, 3512, 3504, 3495, 3487, 3478, 3470, 3461, 3452, 3444,
3435, 3427, 3418, 3410, 3401, 3392, 3384, 3375, 3367, 3358, 3350, 3338, 3325, 3313, 3299,
3285, 3271, 3255, 3239, 3221, 3202, 3180, 3156, 3127, 3090, 3000};
uint16_t estimateSOC(uint16_t cellVoltage) { // Linear interpolation function
// Function to estimate SOC based on cell voltage
uint16_t estimateSOCFromCell(uint16_t cellVoltage) {
if (cellVoltage >= voltage[0]) {
return SOC[0];
}
@ -92,7 +93,7 @@ uint16_t estimateSOC(uint16_t cellVoltage) { // Linear interpolation function
for (int i = 1; i < numPoints; ++i) {
if (cellVoltage >= voltage[i]) {
// Fix: Cast to float or double to ensure proper floating-point division
// Cast to float for proper division
float t = (float)(cellVoltage - voltage[i]) / (float)(voltage[i - 1] - voltage[i]);
// Calculate interpolated SOC value
@ -105,12 +106,56 @@ uint16_t estimateSOC(uint16_t cellVoltage) { // Linear interpolation function
return 0; // Default return for safety, should never reach here
}
// Simplified version of the pack-based SOC estimation with compensation
uint16_t estimateSOC(uint16_t packVoltage, uint16_t cellCount, int16_t currentAmps) {
// If cell count is still the default 192 but we haven't confirmed it yet
if (!set_voltage_limits && cellCount == 192) {
// Fall back to BMS-reported SOC while cell count is uncertain
return (SOC_Display * 10);
}
if (cellCount == 0)
return 0;
// Convert pack voltage (decivolts) to millivolts
uint32_t packVoltageMv = packVoltage * 100;
// Apply internal resistance compensation
// Current is in deciamps (-150 = -15.0A, 150 = 15.0A)
// Resistance is in milliohms
int32_t voltageDrop = (currentAmps * PACK_INTERNAL_RESISTANCE_MOHM) / 10;
// Compensate the pack voltage (add the voltage drop)
uint32_t compensatedPackVoltageMv = packVoltageMv + voltageDrop;
// Calculate average cell voltage in millivolts
uint16_t avgCellVoltage = compensatedPackVoltageMv / cellCount;
#ifdef DEBUG_LOG
logging.print("Pack: ");
logging.print(packVoltage / 10.0);
logging.print("V, Current: ");
logging.print(currentAmps / 10.0);
logging.print("A, Drop: ");
logging.print(voltageDrop / 1000.0);
logging.print("V, Comp Pack: ");
logging.print(compensatedPackVoltageMv / 1000.0);
logging.print("V, Avg Cell: ");
logging.print(avgCellVoltage);
logging.println("mV");
#endif
// Use the cell voltage lookup table to estimate SOC
return estimateSOCFromCell(avgCellVoltage);
}
// Fix: Change parameter types to uint16_t to match SOC values
uint16_t selectSOC(uint16_t SOC_low, uint16_t SOC_high) {
if (SOC_low == 0 || SOC_high == 0) {
return 0; // If either value is 0, return 0
}
if (SOC_low == 10000 || SOC_high == 10000) {
return 10000; // If either value is 100, return 100
return 10000; // If either value is 100%, return 100%
}
return (SOC_low < SOC_high) ? SOC_low : SOC_high; // Otherwise, return the lowest value
}
@ -687,9 +732,12 @@ static uint8_t calculateCRC(CAN_frame rx_frame, uint8_t length, uint8_t initial_
void update_values_battery() { //This function maps all the values fetched via CAN to the correct parameters used for modbus
#ifdef ESTIMATE_SOC_FROM_CELLVOLTAGE
SOC_estimated_lowest = estimateSOC(CellVoltMin_mV);
SOC_estimated_highest = estimateSOC(CellVoltMax_mV);
datalayer.battery.status.real_soc = selectSOC(SOC_estimated_lowest, SOC_estimated_highest);
// Use the simplified pack-based SOC estimation with proper compensation
datalayer.battery.status.real_soc = estimateSOC(batteryVoltage, datalayer.battery.info.number_of_cells, batteryAmps);
// For comparison or fallback, we can still calculate from min/max cell voltages
SOC_estimated_lowest = estimateSOCFromCell(CellVoltMin_mV);
SOC_estimated_highest = estimateSOCFromCell(CellVoltMax_mV);
#else
datalayer.battery.status.real_soc = (SOC_Display * 10); //increase SOC range from 0-100.0 -> 100.00
#endif

View file

@ -19,6 +19,10 @@ extern ACAN2517FD canfd;
#define RAMPDOWN_SOC 9000 // 90.00 SOC% to start ramping down from max charge power towards 0 at 100.00%
#define RAMPDOWNPOWERALLOWED 10000 // What power we ramp down from towards top balancing
// Used for SoC compensation - Define internal resistance value in milliohms for the entire pack
// How to calculate: voltage_drop_under_known_load [Volts] / load [Amps] = Resistance
#define PACK_INTERNAL_RESISTANCE_MOHM 200 // 200 milliohms for the whole pack
void setup_battery(void);
void transmit_can_frame(CAN_frame* tx_frame, int interface);

View file

@ -8,9 +8,11 @@
/* Do not change code below unless you are sure what you are doing */
/* Credits: Most of the code comes from Per Carlen's bms_comms_tesla_model3.py (https://gitlab.com/pelle8/batt2gen24/) */
static unsigned long previousMillis10 = 0; // will store last time a 50ms CAN Message was send
static unsigned long previousMillis50 = 0; // will store last time a 50ms CAN Message was send
static unsigned long previousMillis100 = 0; // will store last time a 100ms CAN Message was send
static unsigned long previousMillis10 = 0; // will store last time a 50ms CAN Message was sent
static unsigned long previousMillis50 = 0; // will store last time a 50ms CAN Message was sent
static unsigned long previousMillis100 = 0; // will store last time a 100ms CAN Message was sent
static unsigned long previousMillis500 = 0; // will store last time a 500ms CAN Message was sent
static unsigned long previousMillis1000 = 0; // will store last time a 1000ms CAN Message was sent
static bool alternate243 = false;
//0x221 545 VCFRONT_LVPowerState: "GenMsgCycleTime" 50ms
CAN_frame TESLA_221_1 = {
@ -50,12 +52,14 @@ CAN_frame TESLA_129 = {.FD = false,
.DLC = 8,
.ID = 0x129,
.data = {0x21, 0x24, 0x36, 0x5F, 0x00, 0x20, 0xFF, 0x3F}};
//0x612 UDS diagnostic requests - on demand
CAN_frame TESLA_602 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x602,
.data = {0x02, 0x27, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00}}; //Diagnostic request
.data = {0x02, 0x27, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00}};
static uint8_t stateMachineClearIsolationFault = 0xFF;
static uint8_t stateMachineBMSReset = 0xFF;
static uint16_t sendContactorClosingMessagesStill = 300;
static uint16_t battery_cell_max_v = 3300;
static uint16_t battery_cell_min_v = 3300;
@ -866,9 +870,18 @@ void update_values_battery() { //This function maps all the values fetched via
#endif // TESLA_MODEL_3Y_BATTERY
// Check if user requests some action
if (datalayer.battery.settings.user_requests_isolation_clear) {
stateMachineClearIsolationFault = 0; //Start the statemachine
datalayer.battery.settings.user_requests_isolation_clear = false;
if (datalayer.battery.settings.user_requests_tesla_isolation_clear) {
stateMachineClearIsolationFault = 0; //Start the isolation fault statemachine
datalayer.battery.settings.user_requests_tesla_isolation_clear = false;
}
if (datalayer.battery.settings.user_requests_tesla_bms_reset) {
if (battery_contactor == 1 && battery_BMS_a180_SW_ECU_reset_blocked == false) {
//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;
} else {
logging.println("ERROR: BMS reset failed due to contactors not being open, or BMS ECU not allowing it");
}
}
// Update webserver datalayer
@ -1801,6 +1814,15 @@ void handle_incoming_can_frame_battery(CAN_frame rx_frame) {
BMS_SerialNumber[13] = rx_frame.data.u8[7];
}
break;
case 0x612: // CAN UDS responses for BMS ECU reset
if (memcmp(rx_frame.data.u8, "\x02\x67\x06\xAA\xAA\xAA\xAA\xAA", 8) == 0) {
logging.println("CAN UDS response: ECU unlocked");
} else if (memcmp(rx_frame.data.u8, "\x03\x7F\x11\x78\xAA\xAA\xAA\xAA", 8) == 0) {
logging.println("CAN UDS response: 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 response: ECU reset positive response, 1 second downtime");
}
break;
default:
break;
}
@ -1949,6 +1971,7 @@ the first, for a few cycles, then stop all messages which causes the contactor
case 4:
TESLA_602.data = {0x22, 0x3E, 0x39, 0x38, 0x3B, 0x3A, 0x00, 0x00};
transmit_can_frame(&TESLA_602, can_config.battery);
//Should generate a CAN UDS log message indicating ECU unlocked
stateMachineClearIsolationFault = 5;
break;
case 5:
@ -1961,6 +1984,58 @@ the first, for a few cycles, then stop all messages which causes the contactor
stateMachineClearIsolationFault = 0xFF;
break;
}
if (stateMachineBMSReset != 0xFF) {
//This implementation should be rewritten to actually replying to the UDS replied sent by the BMS
//While this may work, it is not the correct way to implement this clearing logic
switch (stateMachineBMSReset) {
case 0:
TESLA_602.data = {0x02, 0x27, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00};
transmit_can_frame(&TESLA_602, can_config.battery);
stateMachineBMSReset = 1;
break;
case 1:
TESLA_602.data = {0x30, 0x00, 0x0A, 0x00, 0x00, 0x00, 0x00, 0x00};
transmit_can_frame(&TESLA_602, can_config.battery);
stateMachineBMSReset = 2;
break;
case 2:
TESLA_602.data = {0x10, 0x12, 0x27, 0x06, 0x35, 0x34, 0x37, 0x36};
transmit_can_frame(&TESLA_602, can_config.battery);
stateMachineBMSReset = 3;
break;
case 3:
TESLA_602.data = {0x21, 0x31, 0x30, 0x33, 0x32, 0x3D, 0x3C, 0x3F};
transmit_can_frame(&TESLA_602, can_config.battery);
stateMachineBMSReset = 4;
break;
case 4:
TESLA_602.data = {0x22, 0x3E, 0x39, 0x38, 0x3B, 0x3A, 0x00, 0x00};
transmit_can_frame(&TESLA_602, can_config.battery);
//Should generate a CAN UDS log message indicating ECU unlocked
stateMachineBMSReset = 5;
break;
case 5:
TESLA_602.data = {0x02, 0x10, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00};
transmit_can_frame(&TESLA_602, can_config.battery);
stateMachineBMSReset = 6;
break;
case 6:
TESLA_602.data = {0x02, 0x10, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00};
transmit_can_frame(&TESLA_602, can_config.battery);
stateMachineBMSReset = 7;
break;
case 7:
TESLA_602.data = {0x02, 0x11, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00};
transmit_can_frame(&TESLA_602, can_config.battery);
//Should generate a CAN UDS log message(s) indicating ECU has reset
stateMachineBMSReset = 0xFF;
break;
default:
//Something went wrong. Reset all and cancel
stateMachineBMSReset = 0xFF;
break;
}
}
}
}
}

View file

@ -113,7 +113,7 @@ typedef struct {
/** Minimum percentage setting. Set this value to the lowest real SOC
* you want the inverter to be able to use. At this real SOC, the inverter
* will "see" 0% */
uint16_t min_percentage = BATTERY_MINPERCENTAGE;
int16_t min_percentage = BATTERY_MINPERCENTAGE;
/** Maximum percentage setting. Set this value to the highest real SOC
* you want the inverter to be able to use. At this real SOC, the inverter
* will "see" 100% */
@ -141,7 +141,8 @@ typedef struct {
/** 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 function */
bool user_requests_balancing = false;
bool user_requests_isolation_clear = false;
bool user_requests_tesla_isolation_clear = false;
bool user_requests_tesla_bms_reset = 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
@ -215,6 +216,8 @@ typedef struct {
} DATALAYER_SHUNT_TYPE;
typedef struct {
/** ESP32 main CPU temperature, for displaying on webserver and for safeties */
float CPU_temperature = 0;
/** array with type of battery used, for displaying on webserver */
char battery_protocol[64] = {0};
/** array with type of inverter protocol used, for displaying on webserver */

View file

@ -252,6 +252,26 @@ typedef struct {
bool warning_Charger_not_responding = false;
} DATALAYER_INFO_CELLPOWER;
typedef struct {
uint16_t soc_z = 0;
uint16_t soc_u = 0;
uint16_t soh_average = 0;
uint16_t max_regen_power = 0;
uint16_t max_discharge_power = 0;
int16_t average_temperature = 0;
int16_t minimum_temperature = 0;
int16_t maximum_temperature = 0;
uint16_t maximum_charge_power = 0;
uint16_t SOH_available_power = 0;
uint16_t SOH_generated_power = 0;
uint16_t lead_acid_voltage = 0;
uint8_t highest_cell_voltage_number = 0;
uint8_t lowest_cell_voltage_number = 0;
uint64_t cumulative_energy_when_discharging = 0;
uint64_t cumulative_energy_when_charging = 0;
uint64_t cumulative_energy_in_regen = 0;
} DATALAYER_INFO_CMFAEV;
typedef struct {
/** uint8_t */
/** Battery serial numbers, stores raw HEX values for ASCII chars */
@ -740,6 +760,7 @@ class DataLayerExtended {
DATALAYER_INFO_BMWI3 bmwi3;
DATALAYER_INFO_BYDATTO3 bydAtto3;
DATALAYER_INFO_CELLPOWER cellpower;
DATALAYER_INFO_CMFAEV CMFAEV;
DATALAYER_INFO_GEELY_GEOMETRY_C geometryC;
DATALAYER_INFO_KIAHYUNDAI64 KiaHyundai64;
DATALAYER_INFO_TESLA tesla;

View file

@ -19,6 +19,13 @@ battery_pause_status emulator_pause_status = NORMAL;
//battery pause status end
void update_machineryprotection() {
// Check if the CPU is too hot
if (datalayer.system.info.CPU_temperature > 80.0f) {
set_event(EVENT_CPU_OVERHEAT, 0);
} else {
clear_event(EVENT_CPU_OVERHEAT);
}
// Check health status of CAN interfaces
if (datalayer.system.info.can_native_send_fail) {
set_event(EVENT_CAN_NATIVE_TX_FAILURE, 0);

View file

@ -47,6 +47,7 @@ void init_events(void) {
events.entries[EVENT_CAN_CHARGER_MISSING].level = EVENT_LEVEL_INFO;
events.entries[EVENT_CAN_INVERTER_MISSING].level = EVENT_LEVEL_WARNING;
events.entries[EVENT_CONTACTOR_WELDED].level = EVENT_LEVEL_WARNING;
events.entries[EVENT_CPU_OVERHEAT].level = EVENT_LEVEL_WARNING;
events.entries[EVENT_WATER_INGRESS].level = EVENT_LEVEL_ERROR;
events.entries[EVENT_CHARGE_LIMIT_EXCEEDED].level = EVENT_LEVEL_INFO;
events.entries[EVENT_DISCHARGE_LIMIT_EXCEEDED].level = EVENT_LEVEL_INFO;
@ -191,6 +192,8 @@ const char* get_event_message_string(EVENTS_ENUM_TYPE event) {
return "Inverter not sending messages via CAN for the last 60 seconds. Check wiring!";
case EVENT_CONTACTOR_WELDED:
return "Contactors sticking/welded. Inspect battery with caution!";
case EVENT_CPU_OVERHEAT:
return "Battery-Emulator CPU overheating! Increase airflow/cooling to increase hardware lifespan!";
case EVENT_CHARGE_LIMIT_EXCEEDED:
return "Inverter is charging faster than battery is allowing.";
case EVENT_DISCHARGE_LIMIT_EXCEEDED:
@ -348,7 +351,7 @@ const char* get_event_message_string(EVENTS_ENUM_TYPE event) {
case EVENT_MQTT_DISCONNECT:
return "MQTT disconnected.";
case EVENT_EQUIPMENT_STOP:
return "EQUIPMENT STOP ACTIVATED!!!";
return "User requested stop, either via equipment stop circuit or webserver Open Contactor button";
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:

View file

@ -21,6 +21,7 @@
XX(EVENT_CAN_NATIVE_TX_FAILURE) \
XX(EVENT_CHARGE_LIMIT_EXCEEDED) \
XX(EVENT_CONTACTOR_WELDED) \
XX(EVENT_CPU_OVERHEAT) \
XX(EVENT_DISCHARGE_LIMIT_EXCEEDED) \
XX(EVENT_WATER_INGRESS) \
XX(EVENT_12V_LOW) \

View file

@ -432,6 +432,30 @@ String advanced_battery_processor(const String& var) {
String(falseTrue[datalayer_extended.cellpower.warning_Charger_not_responding]) + "</h4>";
#endif //CELLPOWER_BMS
#ifdef CMFA_EV_BATTERY
content += "<h4>SOC U: " + String(datalayer_extended.CMFAEV.soc_u) + "percent</h4>";
content += "<h4>SOC Z: " + String(datalayer_extended.CMFAEV.soc_z) + "percent</h4>";
content += "<h4>SOH Average: " + String(datalayer_extended.CMFAEV.soh_average) + "pptt</h4>";
content += "<h4>12V voltage: " + String(datalayer_extended.CMFAEV.lead_acid_voltage) + "mV</h4>";
content += "<h4>Highest cell number: " + String(datalayer_extended.CMFAEV.highest_cell_voltage_number) + "</h4>";
content += "<h4>Lowest cell number: " + String(datalayer_extended.CMFAEV.lowest_cell_voltage_number) + "</h4>";
content += "<h4>Max regen power: " + String(datalayer_extended.CMFAEV.max_regen_power) + "</h4>";
content += "<h4>Max discharge power: " + String(datalayer_extended.CMFAEV.max_discharge_power) + "</h4>";
content += "<h4>Max charge power: " + String(datalayer_extended.CMFAEV.maximum_charge_power) + "</h4>";
content += "<h4>SOH available power: " + String(datalayer_extended.CMFAEV.SOH_available_power) + "</h4>";
content += "<h4>SOH generated power: " + String(datalayer_extended.CMFAEV.SOH_generated_power) + "</h4>";
content += "<h4>Average temperature: " + String(datalayer_extended.CMFAEV.average_temperature) + "dC</h4>";
content += "<h4>Maximum temperature: " + String(datalayer_extended.CMFAEV.maximum_temperature) + "dC</h4>";
content += "<h4>Minimum temperature: " + String(datalayer_extended.CMFAEV.minimum_temperature) + "dC</h4>";
content +=
"<h4>Cumulative energy discharged: " + String(datalayer_extended.CMFAEV.cumulative_energy_when_discharging) +
"Wh</h4>";
content += "<h4>Cumulative energy charged: " + String(datalayer_extended.CMFAEV.cumulative_energy_when_charging) +
"Wh</h4>";
content +=
"<h4>Cumulative energy regen: " + String(datalayer_extended.CMFAEV.cumulative_energy_in_regen) + "Wh</h4>";
#endif //CMFA_EV_BATTERY
#ifdef GEELY_GEOMETRY_C_BATTERY
char readableSerialNumber[29]; // One extra space for null terminator
memcpy(readableSerialNumber, datalayer_extended.geometryC.BatterySerialNumber,
@ -664,7 +688,8 @@ content += "<h4>Serial number: " + String(readableSerialNumber) + "</h4>";
static const char* Fault[] = {"NOT_ACTIVE", "ACTIVE"};
//Buttons for user action
content += "<button onclick='askClearIsolation()'>Clear isolation fault</button>";
content += "<button onclick='askTeslaClearIsolation()'>Clear isolation fault</button>";
content += "<button onclick='askTeslaResetBMS()'>BMS reset</button>";
//0x20A 522 HVP_contatorState
content += "<h4>Contactor Status: " + String(contactorText[datalayer_extended.tesla.status_contactor]) + "</h4>";
content += "<h4>HVIL: " + String(hvilStatusState[datalayer_extended.tesla.hvil_status]) + "</h4>";
@ -691,7 +716,7 @@ content += "<h4>Serial number: " + String(readableSerialNumber) + "</h4>";
sizeof(datalayer_extended.tesla.BMS_SerialNumber));
readableSerialNumber[14] = '\0'; // Null terminate the string
content += "<h4>BMS Serial number: " + String(readableSerialNumber) + "</h4>";
// Comment what data you would like to dislay, order can be changed.
// Comment what data you would like to display, order can be changed.
//0x352 850 BMS_energyStatus
if (datalayer_extended.tesla.BMS352_mux == false) {
content += "<h3>BMS 0x352 w/o mux</h3>"; //if using older BMS <2021 and comment 0x352 without MUX
@ -1454,19 +1479,32 @@ content += "<h4>Serial number: " + String(readableSerialNumber) + "</h4>";
!defined(TESLA_BATTERY) && !defined(NISSAN_LEAF_BATTERY) && !defined(BMW_I3_BATTERY) && \
!defined(BYD_ATTO_3_BATTERY) && !defined(RENAULT_ZOE_GEN2_BATTERY) && !defined(CELLPOWER_BMS) && \
!defined(MEB_BATTERY) && !defined(VOLVO_SPA_BATTERY) && !defined(VOLVO_SPA_HYBRID_BATTERY) && \
!defined(KIA_HYUNDAI_64_BATTERY) && !defined(GEELY_GEOMETRY_C_BATTERY) //Only the listed types have extra info
!defined(KIA_HYUNDAI_64_BATTERY) && !defined(GEELY_GEOMETRY_C_BATTERY) && \
!defined(CMFA_EV_BATTERY) //Only the listed types have extra info
content += "No extra information available for this battery type";
#endif
content += "</div>";
content += "<script>";
content +=
"function askClearIsolation() { if (window.confirm('Are you sure you want to clear any active isolation "
"function askTeslaClearIsolation() { if (window.confirm('Are you sure you want to clear any active isolation "
"fault?')) { "
"clearIsolation(); } }";
content += "function clearIsolation() {";
"teslaClearIsolation(); } }";
content += "function teslaClearIsolation() {";
content += " var xhr = new XMLHttpRequest();";
content += " xhr.open('GET', '/clearIsolation', true);";
content += " xhr.open('GET', '/teslaClearIsolation', true);";
content += " xhr.send();";
content += "}";
content += "function goToMainPage() { window.location.href = '/'; }";
content += "</script>";
content += "<script>";
content +=
"function askTeslaResetBMS() { if (window.confirm('Are you sure you want to reset the "
"BMS?')) { "
"teslaResetBMS(); } }";
content += "function teslaResetBMS() {";
content += " var xhr = new XMLHttpRequest();";
content += " xhr.open('GET', '/teslaResetBMS', true);";
content += " xhr.send();";
content += "}";
content += "function goToMainPage() { window.location.href = '/'; }";

View file

@ -209,10 +209,11 @@ String settings_processor(const String& var) {
"100.0');}}}";
content +=
"function editSocMin(){var value=prompt('Inverter will see completely discharged (0pct)SOC when this value is "
"reached. Enter new minimum SOC value that battery will discharge to "
"(0-50.0):');if(value!==null){if(value>=0&&value<=50){var xhr=new "
"reached. Advanced users can set to negative values. Enter new minimum SOC value that battery will discharge "
"to "
"(-10.0to50.0):');if(value!==null){if(value>=-10&&value<=50){var xhr=new "
"XMLHttpRequest();xhr.onload=editComplete;xhr.onerror=editError;xhr.open('GET','/"
"updateSocMin?value='+value,true);xhr.send();}else{alert('Invalid value. Please enter a value between 0 and "
"updateSocMin?value='+value,true);xhr.send();}else{alert('Invalid value. Please enter a value between -10 and "
"50.0');}}}";
content +=
"function editMaxChargeA(){var value=prompt('Some inverters needs to be artificially limited. Enter new "

View file

@ -571,11 +571,20 @@ void init_webserver() {
});
// Route for clearing isolation faults on Tesla
server.on("/clearIsolation", HTTP_GET, [](AsyncWebServerRequest* request) {
server.on("/teslaClearIsolation", HTTP_GET, [](AsyncWebServerRequest* request) {
if (WEBSERVER_AUTH_REQUIRED && !request->authenticate(http_username, http_password)) {
return request->requestAuthentication();
}
datalayer.battery.settings.user_requests_isolation_clear = true;
datalayer.battery.settings.user_requests_tesla_isolation_clear = true;
request->send(200, "text/plain", "Updated successfully");
});
// Route for resetting BMS on Tesla
server.on("/teslaResetBMS", HTTP_GET, [](AsyncWebServerRequest* request) {
if (WEBSERVER_AUTH_REQUIRED && !request->authenticate(http_username, http_password)) {
return request->requestAuthentication();
}
datalayer.battery.settings.user_requests_tesla_bms_reset = true;
request->send(200, "text/plain", "Updated successfully");
});
@ -928,7 +937,7 @@ String processor(const String& var) {
#ifdef HW_STARK
content += " Hardware: Stark CMR Module";
#endif // HW_STARK
content += "</h4>";
content += " @ " + String(datalayer.system.info.CPU_temperature, 1) + " &deg;C</h4>";
content += "<h4>Uptime: " + uptime_formatter::getUptime() + "</h4>";
#ifdef FUNCTION_TIME_MEASUREMENT
// Load information