mirror of
https://github.com/dalathegreat/Battery-Emulator.git
synced 2025-10-04 18:29:48 +02:00
Merge branch 'main' into feature/geely-geometry-battery
This commit is contained in:
commit
fc3c410db7
19 changed files with 1566 additions and 73 deletions
1
.github/workflows/compile-all-batteries.yml
vendored
1
.github/workflows/compile-all-batteries.yml
vendored
|
@ -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
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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] =
|
||||
|
|
|
@ -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
|
||||
|
|
1036
Software/src/battery/CMFA-EV-BATTERY.cpp
Normal file
1036
Software/src/battery/CMFA-EV-BATTERY.cpp
Normal file
File diff suppressed because it is too large
Load diff
158
Software/src/battery/CMFA-EV-BATTERY.h
Normal file
158
Software/src/battery/CMFA-EV-BATTERY.h
Normal 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
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 */
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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) \
|
||||
|
|
|
@ -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 = '/'; }";
|
||||
|
|
|
@ -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 "
|
||||
|
|
|
@ -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) + " °C</h4>";
|
||||
content += "<h4>Uptime: " + uptime_formatter::getUptime() + "</h4>";
|
||||
#ifdef FUNCTION_TIME_MEASUREMENT
|
||||
// Load information
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue