From a84a1d6580253f40b5abfac644ca661eb55b4b75 Mon Sep 17 00:00:00 2001 From: wjcloudy <56305354+wjcloudy@users.noreply.github.com> Date: Sun, 19 Jan 2025 18:27:57 +0000 Subject: [PATCH] Add Beta BMW PHEV Support (#804) * Add Beta BMW PHEV Support --- .github/workflows/compile-all-batteries.yml | 1 + ...ll-combinations-part1-batteries-A-to-M.yml | 1 + Software/USER_SETTINGS.h | 1 + Software/src/battery/BATTERIES.h | 4 + Software/src/battery/BMW-PHEV-BATTERY.cpp | 1088 +++++++++++++++++ Software/src/battery/BMW-PHEV-BATTERY.h | 22 + Software/src/datalayer/datalayer_extended.h | 57 + Software/src/devboard/mqtt/mqtt.cpp | 6 + .../webserver/advanced_battery_html.cpp | 126 +- 9 files changed, 1302 insertions(+), 4 deletions(-) create mode 100644 Software/src/battery/BMW-PHEV-BATTERY.cpp create mode 100644 Software/src/battery/BMW-PHEV-BATTERY.h diff --git a/.github/workflows/compile-all-batteries.yml b/.github/workflows/compile-all-batteries.yml index ae6bafb6..a3e2d7df 100644 --- a/.github/workflows/compile-all-batteries.yml +++ b/.github/workflows/compile-all-batteries.yml @@ -51,6 +51,7 @@ jobs: battery: - BMW_I3_BATTERY - BMW_IX_BATTERY + - BMW_PHEV_BATTERY - BYD_ATTO_3_BATTERY - CELLPOWER_BMS - CHADEMO_BATTERY diff --git a/.github/workflows/compile-all-combinations-part1-batteries-A-to-M.yml b/.github/workflows/compile-all-combinations-part1-batteries-A-to-M.yml index 14381ea3..2aed0464 100644 --- a/.github/workflows/compile-all-combinations-part1-batteries-A-to-M.yml +++ b/.github/workflows/compile-all-combinations-part1-batteries-A-to-M.yml @@ -56,6 +56,7 @@ jobs: battery: - BMW_I3_BATTERY - BMW_IX_BATTERY + - BMW_PHEV_BATTERY - BYD_ATTO_3_BATTERY - CELLPOWER_BMS - CHADEMO_BATTERY diff --git a/Software/USER_SETTINGS.h b/Software/USER_SETTINGS.h index b8c1341b..e768878c 100644 --- a/Software/USER_SETTINGS.h +++ b/Software/USER_SETTINGS.h @@ -11,6 +11,7 @@ /* Select battery used */ //#define BMW_I3_BATTERY //#define BMW_IX_BATTERY +//#define BMW_PHEV_BATTERY //#define BOLT_AMPERA_BATTERY //#define BYD_ATTO_3_BATTERY //#define CELLPOWER_BMS diff --git a/Software/src/battery/BATTERIES.h b/Software/src/battery/BATTERIES.h index af62c8e9..ac8ea1b6 100644 --- a/Software/src/battery/BATTERIES.h +++ b/Software/src/battery/BATTERIES.h @@ -17,6 +17,10 @@ void setup_can_shunt(); #include "BMW-IX-BATTERY.h" #endif +#ifdef BMW_PHEV_BATTERY +#include "BMW-PHEV-BATTERY.h" +#endif + #ifdef BOLT_AMPERA_BATTERY #include "BOLT-AMPERA-BATTERY.h" #endif diff --git a/Software/src/battery/BMW-PHEV-BATTERY.cpp b/Software/src/battery/BMW-PHEV-BATTERY.cpp new file mode 100644 index 00000000..0f04d0ba --- /dev/null +++ b/Software/src/battery/BMW-PHEV-BATTERY.cpp @@ -0,0 +1,1088 @@ +#include "../include.h" +#ifdef BMW_PHEV_BATTERY +#include "../datalayer/datalayer.h" +#include "../datalayer/datalayer_extended.h" +#include "../devboard/utils/events.h" +#include "BMW-PHEV-BATTERY.h" + +/* Do not change code below unless you are sure what you are doing */ +static unsigned long previousMillis20 = 0; // will store last time a 20ms CAN Message was send +static unsigned long previousMillis100 = 0; // will store last time a 100ms CAN Message was send +static unsigned long previousMillis200 = 0; // will store last time a 200ms CAN Message was send +static unsigned long previousMillis500 = 0; // will store last time a 500ms CAN Message was send +static unsigned long previousMillis640 = 0; // will store last time a 600ms CAN Message was send +static unsigned long previousMillis1000 = 0; // will store last time a 1000ms CAN Message was send +static unsigned long previousMillis5000 = 0; // will store last time a 5000ms CAN Message was send +static unsigned long previousMillis10000 = 0; // will store last time a 10000ms CAN Message was send + +#define ALIVE_MAX_VALUE 14 // BMW CAN messages contain alive counter, goes from 0...14 + +enum CmdState { SOH, CELL_VOLTAGE_MINMAX, SOC, CELL_VOLTAGE_CELLNO, CELL_VOLTAGE_CELLNO_LAST }; + +static CmdState cmdState = SOC; + +// A structure to keep track of the ongoing multi-frame UDS response +typedef struct { + bool UDS_inProgress; // Are we currently receiving a multi-frame message? + uint16_t UDS_expectedLength; // Expected total payload length + uint16_t UDS_bytesReceived; // How many bytes have been stored so far + uint8_t UDS_moduleID; // The "module" indicated by the first frame + uint8_t receivedInBatch; // Number of CFs received in the current batch + uint8_t UDS_buffer[256]; // Buffer for the reassembled data + unsigned long UDS_lastFrameMillis; // Timestamp of last frame (for timeouts, if desired) +} UDS_RxContext; + +// A single global UDS context, since only one module can respond at a time +static UDS_RxContext gUDSContext; + +const unsigned char crc8_table[256] = + { // CRC8_SAE_J1850_ZER0 formula,0x1D Poly,initial value 0x3F,Final XOR value varies + 0x00, 0x1D, 0x3A, 0x27, 0x74, 0x69, 0x4E, 0x53, 0xE8, 0xF5, 0xD2, 0xCF, 0x9C, 0x81, 0xA6, 0xBB, 0xCD, 0xD0, + 0xF7, 0xEA, 0xB9, 0xA4, 0x83, 0x9E, 0x25, 0x38, 0x1F, 0x02, 0x51, 0x4C, 0x6B, 0x76, 0x87, 0x9A, 0xBD, 0xA0, + 0xF3, 0xEE, 0xC9, 0xD4, 0x6F, 0x72, 0x55, 0x48, 0x1B, 0x06, 0x21, 0x3C, 0x4A, 0x57, 0x70, 0x6D, 0x3E, 0x23, + 0x04, 0x19, 0xA2, 0xBF, 0x98, 0x85, 0xD6, 0xCB, 0xEC, 0xF1, 0x13, 0x0E, 0x29, 0x34, 0x67, 0x7A, 0x5D, 0x40, + 0xFB, 0xE6, 0xC1, 0xDC, 0x8F, 0x92, 0xB5, 0xA8, 0xDE, 0xC3, 0xE4, 0xF9, 0xAA, 0xB7, 0x90, 0x8D, 0x36, 0x2B, + 0x0C, 0x11, 0x42, 0x5F, 0x78, 0x65, 0x94, 0x89, 0xAE, 0xB3, 0xE0, 0xFD, 0xDA, 0xC7, 0x7C, 0x61, 0x46, 0x5B, + 0x08, 0x15, 0x32, 0x2F, 0x59, 0x44, 0x63, 0x7E, 0x2D, 0x30, 0x17, 0x0A, 0xB1, 0xAC, 0x8B, 0x96, 0xC5, 0xD8, + 0xFF, 0xE2, 0x26, 0x3B, 0x1C, 0x01, 0x52, 0x4F, 0x68, 0x75, 0xCE, 0xD3, 0xF4, 0xE9, 0xBA, 0xA7, 0x80, 0x9D, + 0xEB, 0xF6, 0xD1, 0xCC, 0x9F, 0x82, 0xA5, 0xB8, 0x03, 0x1E, 0x39, 0x24, 0x77, 0x6A, 0x4D, 0x50, 0xA1, 0xBC, + 0x9B, 0x86, 0xD5, 0xC8, 0xEF, 0xF2, 0x49, 0x54, 0x73, 0x6E, 0x3D, 0x20, 0x07, 0x1A, 0x6C, 0x71, 0x56, 0x4B, + 0x18, 0x05, 0x22, 0x3F, 0x84, 0x99, 0xBE, 0xA3, 0xF0, 0xED, 0xCA, 0xD7, 0x35, 0x28, 0x0F, 0x12, 0x41, 0x5C, + 0x7B, 0x66, 0xDD, 0xC0, 0xE7, 0xFA, 0xA9, 0xB4, 0x93, 0x8E, 0xF8, 0xE5, 0xC2, 0xDF, 0x8C, 0x91, 0xB6, 0xAB, + 0x10, 0x0D, 0x2A, 0x37, 0x64, 0x79, 0x5E, 0x43, 0xB2, 0xAF, 0x88, 0x95, 0xC6, 0xDB, 0xFC, 0xE1, 0x5A, 0x47, + 0x60, 0x7D, 0x2E, 0x33, 0x14, 0x09, 0x7F, 0x62, 0x45, 0x58, 0x0B, 0x16, 0x31, 0x2C, 0x97, 0x8A, 0xAD, 0xB0, + 0xE3, 0xFE, 0xD9, 0xC4}; + +/* +INFO + +V0.1 very basic implementation reading Gen3/4 BMW PHEV SME. +Supported: +-Pre/Post contactor voltage +-Min/ Cell Temp +-SOC + +VEHICLE CANBUS +i3 messages don't affect contact close block except 0x380 which gives an error on PHEV (Possibly VIN number) 0x56, 0x5A, 0x37, 0x39, 0x34, 0x34, 0x34 + +BROADCAST MAP +0x112 20ms Status Of High-Voltage Battery 2 +0x1F1 1000ms Status Of High-Voltage Battery 1 +0x239 200ms predicted charge condition and predicted target +0x295 1000ms ? [1] Alive Counter 50-5F? +0x2A5 200ms ? +0x2F5 100ms High-Voltage Battery Charge/Discharge Limitations +0x33e 5000ms? +0x40D 1000ms ? +0x430 1000ms Charging status of high-voltage battery - 2 +0x431 200ms Data High-Voltage Battery Unit +0x432 200ms SOC% info + +UDS MAP +22 D6 CF - CSC Temps +22 DD C0 - Min Max temps +22 DF A5 - All Cell voltages +22 E5 EA - Alternate all cell voltages +22 DE 7E - Voltage limits. 62 DD 73 9D 5A 69 26 = 269.18V - 402.82V +22 DD 7D - Current limits 62 DD 7D 08 20 0B EA = 305A max discharge 208A max charge +22 E5 E9 DD 7D - Individual cell SOC +22 DD 69 - Current in Amps 62 DD 69 00 00 00 00 = 0 Amps +22 DD 7B - SOH 62 DD 7B 62 = 98% +22 DD 62 - HVIL Status 62 DD 64 01 = OK/Closed +22 DD BF - Cell min max alt - f18 ZELLSPANNUNGEN_MIN_MAX < doesn't work +22 DD 6A - Isolation values 62 DD 6A 07 D0 07 D0 07 D0 01 01 01 = in operation plausible/2000kOhm, in follow up plausible/2000kohm, internal iso open contactors (measured on request) pluasible/2000kohm +31 03 AD 61 - Isolation measurement status 71 03 AD 61 00 FF = Nmeasurement status - not successful / fault satate - not defined +22 DF A0 - Cell voltage and temps summary including min/max/average, Ah, (ZUSTAND_SPEICHER) + + +TODO: + BMWPHEV_6F1_REQUEST_LAST_ISO_READING - add results to advanced + BMWPHEV_6F1_REQUEST_PACK_INFO - add cell count reading + Find current measurement reading + +*/ + +//Vehicle CAN START + +CAN_frame BMWiX_0C0 = { + .FD = false, + .ext_ID = false, + .DLC = 2, + .ID = 0x0C0, + .data = { + 0xF0, + 0x08}}; // Keep Alive 2 BDC>SME 200ms First byte cycles F0 > FE second byte 08 static - MINIMUM ID TO KEEP SME AWAKE + +CAN_frame BMW_13E = {.FD = false, + .ext_ID = false, + .DLC = 8, + .ID = 0x13E, + .data = {0xFF, 0x31, 0xFA, 0xFA, 0xFA, 0xFA, 0x0C, 0x00}}; + +//Vehicle CAN END + +//Request Data CAN START + +CAN_frame BMW_PHEV_BUS_WAKEUP_REQUEST = { + .FD = false, + .ext_ID = false, + .DLC = 4, + .ID = 0x554, + .data = { + 0x5A, 0xA5, 0x5A, + 0xA5}}; // Won't work at 500kbps! Ideally sent at 50kbps - but can also achieve wakeup at 100kbps (helps with library support but might not be as reliable). Might need to be sent twice + clear buffer + +CAN_frame BMWPHEV_6F1_REQUEST_SOC = {.FD = false, + .ext_ID = false, + .DLC = 5, + .ID = 0x6F1, + .data = {0x07, 0x03, 0x22, 0xDD, 0xC4}}; // SOC% + +CAN_frame BMWPHEV_6F1_REQUEST_SOH = {.FD = false, + .ext_ID = false, + .DLC = 5, + .ID = 0x6F1, + .data = {0x07, 0x03, 0x22, 0xDD, 0x7B}}; // SOH% + +CAN_frame BMWPHEV_6F1_REQUEST_CURRENT = {.FD = false, + .ext_ID = false, + .DLC = 5, + .ID = 0x6F1, + .data = {0x07, 0x03, 0x22, 0xDD, 0x69}}; // SOH% + +CAN_frame BMWPHEV_6F1_REQUEST_VOLTAGE_LIMITS = { + .FD = false, + .ext_ID = false, + .DLC = 5, + .ID = 0x6F1, + .data = {0x07, 0x03, 0x22, 0xDD, 0x7E}}; // Pack Voltage Limits Multi Frame + +CAN_frame BMWPHEV_6F1_REQUEST_PAIRED_VIN = {.FD = false, + .ext_ID = false, + .DLC = 5, + .ID = 0x6F1, + .data = {0x07, 0x03, 0x22, 0xF1, 0x90}}; // SME Paired VIN + +CAN_frame BMWPHEV_6F1_REQUEST_ISO_READING1 = { + .FD = false, + .ext_ID = false, + .DLC = 5, + .ID = 0x6F1, + .data = { + 0x07, 0x03, 0x22, 0xDD, + 0x6A}}; // MULTI FRAME ISOLATIONSWIDERSTAND 62 DD 6A [07 D0] [07 D0] [07 D0] [01] [01] [01] 00 00 00 00 00 [EXT Reading] [INT reading] [ EXT - 0 not plausible, 1 plausible] + +CAN_frame BMWPHEV_6F1_REQUEST_ISO_READING2 = { + .FD = false, + .ext_ID = false, + .DLC = 5, + .ID = 0x6F1, + .data = {0x07, 0x03, 0x22, 0xD6, + 0xD9}}; // R_ISO_ROH 62 D6 D9 [07 FF] [13] (2047kohm) quality of reading 0-21 (19) + +CAN_frame BMWPHEV_6F1_REQUEST_PACK_INFO = { + .FD = false, + .ext_ID = false, + .DLC = 5, + .ID = 0x6F1, + .data = {0x07, 0x03, 0x22, 0xDF, 0x71}}; // 62 DF 71 00 60 1C 25 1C? Cell Count, Module Count + +CAN_frame BMWPHEV_6F1_REQUEST_CURRENT_LIMITS = { + .FD = false, + .ext_ID = false, + .DLC = 5, + .ID = 0x6F1, + .data = {0x07, 0x03, 0x22, 0xDD, 0x7D}}; // Pack Current Limits Multi Frame + +CAN_frame BMWPHEV_6F1_REQUEST_MAINVOLTAGE_PRECONTACTOR = { + .FD = false, + .ext_ID = false, + .DLC = 5, + .ID = 0x6F1, + .data = {0x07, 0x03, 0x22, 0xDD, 0xB4}}; //Main Battery Voltage (Pre Contactor) + +CAN_frame BMWPHEV_6F1_REQUEST_MAINVOLTAGE_POSTCONTACTOR = { + .FD = false, + .ext_ID = false, + .DLC = 5, + .ID = 0x6F1, + .data = {0x07, 0x03, 0x22, 0xDD, 0x66}}; //Main Battery Voltage (After Contactor) + +CAN_frame BMWPHEV_6F1_REQUEST_CELLSUMMARY = { + .FD = false, + .ext_ID = false, + .DLC = 5, + .ID = 0x6F1, + .data = {0x07, 0x03, 0x22, 0xDF, 0xA0}}; //Min and max cell voltage + temps 6.55V = Qualifier Invalid? + +CAN_frame BMWPHEV_6F1_REQUEST_CELLS_INDIVIDUAL_VOLTS = { + .FD = false, + .ext_ID = false, + .DLC = 5, + .ID = 0x6F1, + .data = {0x07, 0x03, 0x22, 0xDF, 0xA5}}; //All individual cell voltages + +CAN_frame BMWPHEV_6F1_REQUEST_CELL_TEMP = { + .FD = false, + .ext_ID = false, + .DLC = 5, + .ID = 0x6F1, + .data = { + 0x07, 0x03, 0x22, 0xDD, + 0xC0}}; // UDS Request Cell Temperatures min max avg. Has continue frame min in first, then max + avg in second frame + +CAN_frame BMW_6F1_REQUEST_CONTINUE_MULTIFRAME = { + .FD = false, + .ext_ID = false, + .DLC = 8, + .ID = 0x6F1, + .data = { + 0x07, 0x30, 0x03, 0x00, 0x00, 0x00, 0x00, + 0x00}}; //Request continued frames from UDS Multiframe request byte[2] is the request messages to return per continue. default 0x03, all is 0x00 + +CAN_frame BMW_6F1_REQUEST_HARD_RESET = {.FD = false, + .ext_ID = false, + .DLC = 4, + .ID = 0x6F1, + .data = {0x07, 0x03, 0x11, 0x01}}; // Reset BMS - TBC + +CAN_frame BMWPHEV_6F1_REQUEST_CONTACTORS_CLOSE = { + .FD = false, + .ext_ID = false, + .DLC = 8, + .ID = 0x6F1, + .data = {0x07, 0x04, 0x2E, 0xDD, 0x61, 0x01, 0x00, 0x00}}; // Request Contactors Close - Unconfirmed +CAN_frame BMWPHEV_6F1_REQUEST_CONTACTORS_OPEN = { + .FD = false, + .ext_ID = false, + .DLC = 6, + .ID = 0x6F1, + .data = {0x07, 0x04, 0x2E, 0xDD, 0x61, 0x00, 0x00, 0x00}}; // Request Contactors Open - Unconfirmed + +CAN_frame BMWPHEV_6F1_REQUEST_BALANCING_STATUS = { + .FD = false, + .ext_ID = false, + .DLC = 8, + .ID = 0x6F1, + .data = {0x07, 0x04, 0x31, 0x03, 0xAD, 0x6B, 0x00, + 0x00}}; // Balancing status. Response 7DLC F1 05 71 03 AD 6B 01 (01 = active) (03 not active) + +CAN_frame BMWPHEV_6F1_REQUEST_ISOLATION_TEST = { + .FD = false, + .ext_ID = false, + .DLC = 8, + .ID = 0x6F1, + .data = {0x07, 0x04, 0x31, 0x01, 0xAD, 0x61, 0x00, 0x00}}; // Start Isolation Test + +CAN_frame BMWPHEV_6F1_REQUEST_BALANCING_START = { + .FD = false, + .ext_ID = false, + .DLC = 8, + .ID = 0x6F1, + .data = {0x07, 0x04, 0x31, 0x01, 0xAD, 0x6B, 0x00, 0x00}}; // Balancing start request + +CAN_frame BMWPHEV_6F1_REQUEST_BALANCING_STOP = { + .FD = false, + .ext_ID = false, + .DLC = 8, + .ID = 0x6F1, + .data = {0x07, 0x04, 0x31, 0x02, 0xAD, 0x6B, 0x00, 0x00}}; // Balancing stop request + +//Action Requests: +CAN_frame BMW_10B = {.FD = false, + .ext_ID = false, + .DLC = 3, + .ID = 0x10B, + .data = {0xCD, 0x00, 0xFC}}; // Contactor closing command? + +CAN_frame BMWPHEV_6F1_CELL_SOC = {.FD = false, + .ext_ID = false, + .DLC = 5, + .ID = 0x6F1, + .data = {0x07, 0x03, 0x22, 0xE5, 0x9A}}; +CAN_frame BMWPHEV_6F1_CELL_TEMP = {.FD = false, + .ext_ID = false, + .DLC = 5, + .ID = 0x6F1, + .data = {0x07, 0x03, 0x22, 0xE5, 0xCA}}; +//Request Data CAN End + +static bool battery_awake = false; + +//Setup Fast UDS values to poll for +CAN_frame* UDS_REQUESTS_FAST[] = {&BMWPHEV_6F1_REQUEST_CELLSUMMARY, + &BMWPHEV_6F1_REQUEST_SOC, + &BMWPHEV_6F1_REQUEST_CURRENT, + &BMWPHEV_6F1_REQUEST_VOLTAGE_LIMITS, + &BMWPHEV_6F1_REQUEST_MAINVOLTAGE_PRECONTACTOR, + &BMWPHEV_6F1_REQUEST_MAINVOLTAGE_POSTCONTACTOR}; +int numFastUDSreqs = sizeof(UDS_REQUESTS_FAST) / sizeof(UDS_REQUESTS_FAST[0]); //Store Number of elements in the array + +//Setup Slow UDS values to poll for +CAN_frame* UDS_REQUESTS_SLOW[] = {&BMWPHEV_6F1_REQUEST_ISO_READING1, &BMWPHEV_6F1_REQUEST_ISO_READING2, + &BMWPHEV_6F1_REQUEST_CURRENT_LIMITS, &BMWPHEV_6F1_REQUEST_SOH, + &BMWPHEV_6F1_REQUEST_CELLS_INDIVIDUAL_VOLTS, &BMWPHEV_6F1_REQUEST_CELL_TEMP, + &BMWPHEV_6F1_REQUEST_BALANCING_STATUS, &BMWPHEV_6F1_REQUEST_PAIRED_VIN}; +int numSlowUDSreqs = sizeof(UDS_REQUESTS_SLOW) / sizeof(UDS_REQUESTS_SLOW[0]); // Store Number of elements in the array + +//PHEV intermediate vars +//#define UDS_LOG //Useful for logging multiframe handling +static uint16_t battery_max_charge_voltage = 0; +static int16_t battery_max_charge_amperage = 0; +static uint16_t battery_min_discharge_voltage = 0; +static int16_t battery_max_discharge_amperage = 0; + +static uint8_t startup_counter_contactor = 0; +static uint8_t alive_counter_20ms = 0; +static uint8_t BMW_13E_counter = 0; + +static uint32_t battery_BEV_available_power_shortterm_charge = 0; +static uint32_t battery_BEV_available_power_shortterm_discharge = 0; +static uint32_t battery_BEV_available_power_longterm_charge = 0; +static uint32_t battery_BEV_available_power_longterm_discharge = 0; + +static uint16_t battery_predicted_energy_charge_condition = 0; +static uint16_t battery_predicted_energy_charging_target = 0; + +static uint16_t battery_prediction_voltage_shortterm_charge = 0; +static uint16_t battery_prediction_voltage_shortterm_discharge = 0; +static uint16_t battery_prediction_voltage_longterm_charge = 0; +static uint16_t battery_prediction_voltage_longterm_discharge = 0; + +static uint8_t battery_status_service_disconnection_plug = 0; +static uint8_t battery_status_measurement_isolation = 0; +static uint8_t battery_request_abort_charging = 0; +static uint16_t battery_prediction_duration_charging_minutes = 0; +static uint8_t battery_prediction_time_end_of_charging_minutes = 0; +static uint16_t battery_energy_content_maximum_kWh = 0; + +static uint8_t battery_request_operating_mode = 0; +static uint16_t battery_target_voltage_in_CV_mode = 0; +static uint8_t battery_request_charging_condition_minimum = 0; +static uint8_t battery_request_charging_condition_maximum = 0; +static uint16_t battery_display_SOC = 0; + +static uint8_t battery_status_error_isolation_external_Bordnetz = 0; +static uint8_t battery_status_error_isolation_internal_Bordnetz = 0; +static uint8_t battery_request_cooling = 0; +static uint8_t battery_status_valve_cooling = 0; +static uint8_t battery_status_error_locking = 0; +static uint8_t battery_status_precharge_locked = 0; +static uint8_t battery_status_disconnecting_switch = 0; +static uint8_t battery_status_emergency_mode = 0; +static uint8_t battery_request_service = 0; +static uint8_t battery_error_emergency_mode = 0; +static uint8_t battery_status_error_disconnecting_switch = 0; +static uint8_t battery_status_warning_isolation = 0; +static uint8_t battery_status_cold_shutoff_valve = 0; +static int16_t battery_temperature_HV = 0; +static int16_t battery_temperature_heat_exchanger = 0; +static int16_t battery_temperature_max = 0; +static int16_t battery_temperature_min = 0; +static bool pack_limit_info_available = false; +static bool cell_limit_info_available = false; + +//iX Intermediate vars + +static uint32_t battery_serial_number = 0; +static int32_t battery_current = 0; +static int16_t battery_voltage = 3700; //Initialize as valid - should be fixed in future +static int16_t terminal30_12v_voltage = 0; +static int16_t battery_voltage_after_contactor = 0; +static int16_t min_soc_state = 5000; +static int16_t avg_soc_state = 5000; +static int16_t max_soc_state = 5000; +static int16_t min_soh_state = 9999; // Uses E5 45, also available in 78 73 +static int16_t avg_soh_state = 9999; // Uses E5 45, also available in 78 73 +static int16_t max_soh_state = 9999; // Uses E5 45, also available in 78 73 +static uint16_t max_design_voltage = 0; +static uint16_t min_design_voltage = 0; +static int32_t remaining_capacity = 0; +static int32_t max_capacity = 0; + +static int16_t main_contactor_temperature = 0; +static int16_t min_cell_voltage = 3700; //Initialize as valid - should be fixed in future +static int16_t max_cell_voltage = 3700; //Initialize as valid - should be fixed in future +static unsigned long min_cell_voltage_lastchanged = 0; +static unsigned long max_cell_voltage_lastchanged = 0; +static unsigned min_cell_voltage_lastreceived = 0; +static unsigned max_cell_voltage_lastreceived = 0; +static int16_t allowable_charge_amps = 0; //E5 62 +static int16_t allowable_discharge_amps = 0; //E5 62 + +static int32_t iso_safety_int_kohm = 0; //STAT_ISOWIDERSTAND_INT_WERT +static int32_t iso_safety_ext_kohm = 0; //STAT_ISOWIDERSTAND_EXT_STD_WERT +static int32_t iso_safety_trg_kohm = 0; +static int32_t iso_safety_ext_plausible = 0; //STAT_ISOWIDERSTAND_EXT_TRG_PLAUS +static int32_t iso_safety_int_plausible = 0; //STAT_ISOWIDERSTAND_EXT_TRG_WERT +static int32_t iso_safety_trg_plausible = 0; +static int32_t iso_safety_kohm = 0; //STAT_R_ISO_ROH_01_WERT +static int32_t iso_safety_kohm_quality = 0; //STAT_R_ISO_ROH_QAL_01_INFO Quality of measurement 0-21 (higher better) + +static uint8_t paired_vin[] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; //17 Byte array for paired VIN + +static int16_t count_full_charges = 0; //TODO 42 +static int16_t count_charges = 0; //TODO 42 +static int16_t hvil_status = 0; +static int16_t voltage_qualifier_status = 0; //0 = Valid, 1 = Invalid +static int16_t balancing_status = 0; //4 = not active +static uint8_t contactors_closed = 0; //TODO E5 BF or E5 51 +static uint8_t contactor_status_precharge = 0; //TODO E5 BF +static uint8_t contactor_status_negative = 0; //TODO E5 BF +static uint8_t contactor_status_positive = 0; //TODO E5 BF +static uint8_t uds_fast_req_id_counter = 0; +static uint8_t uds_slow_req_id_counter = 0; +static uint8_t detected_number_of_cells = 96; +const unsigned long STALE_PERIOD = + STALE_PERIOD_CONFIG; // Time in milliseconds to check for staleness (e.g., 5000 ms = 5 seconds) + +static byte iX_0C0_counter = 0xF0; // Initialize to 0xF0 + +//End iX Intermediate vars + +static uint8_t current_cell_polled = 0; + +// Function to check if a value has gone stale over a specified time period +bool isStale(int16_t currentValue, uint16_t& lastValue, unsigned long& lastChangeTime) { + unsigned long currentTime = millis(); + + // Check if the value has changed + if (currentValue != lastValue) { + // Update the last change time and value + lastChangeTime = currentTime; + lastValue = currentValue; + return false; // Value is fresh because it has changed + } + + // Check if the value has stayed the same for the specified staleness period + return (currentTime - lastChangeTime >= STALE_PERIOD); +} + +static uint8_t calculateCRC(CAN_frame rx_frame, uint8_t length, uint8_t initial_value) { + uint8_t crc = initial_value; + for (uint8_t j = 1; j < length; j++) { //start at 1, since 0 is the CRC + crc = crc8_table[(crc ^ static_cast(rx_frame.data.u8[j])) % 256]; + } + return crc; +} + +static uint8_t increment_uds_req_id_counter(uint8_t index, int numReqs) { + index++; + if (index >= numReqs) { + index = 0; + } + return index; +} + +/* -------------------------------------------------------------------------- + UDS Multi-Frame Helpers + -------------------------------------------------------------------------- */ + +void startUDSMultiFrameReception(uint16_t totalLength, uint8_t moduleID) { + gUDSContext.UDS_inProgress = true; + gUDSContext.UDS_expectedLength = totalLength; + gUDSContext.UDS_bytesReceived = 0; + gUDSContext.UDS_moduleID = moduleID; + memset(gUDSContext.UDS_buffer, 0, sizeof(gUDSContext.UDS_buffer)); + gUDSContext.UDS_lastFrameMillis = millis(); // if you want to track timeouts +} + +bool storeUDSPayload(const uint8_t* payload, uint8_t length) { + if (gUDSContext.UDS_bytesReceived + length > sizeof(gUDSContext.UDS_buffer)) { + // Overflow => abort + gUDSContext.UDS_inProgress = false; +#ifdef DEBUG_LOG + logging.println("UDS Payload Overflow"); +#endif // DEBUG_LOG + return false; + } + memcpy(&gUDSContext.UDS_buffer[gUDSContext.UDS_bytesReceived], payload, length); + gUDSContext.UDS_bytesReceived += length; + gUDSContext.UDS_lastFrameMillis = millis(); + + // If we’ve reached or exceeded the expected length, mark complete + if (gUDSContext.UDS_bytesReceived >= gUDSContext.UDS_expectedLength) { + gUDSContext.UDS_inProgress = false; + // #ifdef DEBUG_LOG + // logging.println("Recived all expected UDS bytes"); + // #endif // DEBUG_LOG + } + return true; +} + +bool isUDSMessageComplete() { + return (!gUDSContext.UDS_inProgress && gUDSContext.UDS_bytesReceived > 0); +} + +static uint8_t increment_alive_counter(uint8_t counter) { + counter++; + if (counter > ALIVE_MAX_VALUE) { + counter = 0; + } + return counter; +} + +static byte increment_0C0_counter(byte counter) { + counter++; + // Reset to 0xF0 if it exceeds 0xFE + if (counter > 0xFE) { + counter = 0xF0; + } + return counter; +} + +void processCellVoltages() { + const int startByte = 3; // Start reading at byte 3 + const int numVoltages = 96; // Number of cell voltage values to process + int voltage_index = 0; // Starting index for the destination array + + // Loop through 96 voltage values + for (int i = 0; i < numVoltages; i++) { + // Calculate the index of the first and second bytes in the input array + int byteIndex = startByte + (i * 2); + + // Combine two bytes to form a 16-bit value + uint16_t voltageRaw = (gUDSContext.UDS_buffer[byteIndex] << 8) | gUDSContext.UDS_buffer[byteIndex + 1]; + + // Store the result in the destination array + datalayer.battery.status.cell_voltages_mV[voltage_index] = voltageRaw; + + // Increment the destination array index + voltage_index++; + } +} + +void wake_battery_via_canbus() { + //TJA1055 transceiver remote wake requires pulses on the bus of + // Dominant for at least ~7 µs (min) and at most ~38 µs (max) + // Followed by a Recessive interval of at least ~3 µs (min) and at most ~10 µs (max) + // Then a second dominant pulse of similar timing. + + CAN_cfg.speed = CAN_SPEED_100KBPS; //Slow down canbus to achieve wakeup timings + ESP32Can.CANInit(); // ReInit native CAN module at new speed + transmit_can_frame(&BMW_PHEV_BUS_WAKEUP_REQUEST, can_config.battery); + transmit_can_frame(&BMW_PHEV_BUS_WAKEUP_REQUEST, can_config.battery); + CAN_cfg.speed = CAN_SPEED_500KBPS; //Resume fullspeed + ESP32Can.CANInit(); // ReInit native CAN module at new speed + +#ifdef DEBUG_LOG + logging.println("Sent magic wakeup packet to SME at 100kbps..."); +#endif +} +void update_values_battery() { //This function maps all the values fetched via CAN to the battery datalayer + + datalayer.battery.status.real_soc = avg_soc_state; + + datalayer.battery.status.voltage_dV = battery_voltage; + + datalayer.battery.status.current_dA = battery_current; + + datalayer.battery.info.total_capacity_Wh = (battery_energy_content_maximum_kWh * 1000); // Convert kWh to Wh + + datalayer.battery.status.remaining_capacity_Wh = battery_predicted_energy_charge_condition; + + datalayer.battery.status.soh_pptt = min_soh_state; + + datalayer.battery.status.max_discharge_power_W = battery_BEV_available_power_longterm_discharge; + + //datalayer.battery.status.max_charge_power_W = 3200; //10000; //Aux HV Port has 100A Fuse Moved to Ramping + + // Charge power is set in .h file + if (datalayer.battery.status.real_soc > 9900) { + datalayer.battery.status.max_charge_power_W = MAX_CHARGE_POWER_WHEN_TOPBALANCING_W; + } else if (datalayer.battery.status.real_soc > RAMPDOWN_SOC) { + // When real SOC is between RAMPDOWN_SOC-99%, ramp the value between Max<->0 + datalayer.battery.status.max_charge_power_W = + battery_BEV_available_power_longterm_charge * + (1 - (datalayer.battery.status.real_soc - RAMPDOWN_SOC) / (10000.0 - RAMPDOWN_SOC)); + } else { // No limits, max charging power allowed + datalayer.battery.status.max_charge_power_W = battery_BEV_available_power_longterm_charge; + } + + datalayer.battery.status.temperature_min_dC = battery_temperature_min * 10; // Add a decimal + + datalayer.battery.status.temperature_max_dC = battery_temperature_max * 10; // Add a decimal + + //Check stale values. As values dont change much during idle only consider stale if both parts of this message freeze. + bool isMinCellVoltageStale = + isStale(min_cell_voltage, datalayer.battery.status.cell_min_voltage_mV, min_cell_voltage_lastchanged); + bool isMaxCellVoltageStale = + isStale(max_cell_voltage, datalayer.battery.status.cell_max_voltage_mV, max_cell_voltage_lastchanged); + + if (isMinCellVoltageStale && isMaxCellVoltageStale && + battery_current != 0) { //Ignore stale values if there is no current flowing + datalayer.battery.status.cell_min_voltage_mV = 9999; //Stale values force stop + datalayer.battery.status.cell_max_voltage_mV = 9999; //Stale values force stop + set_event(EVENT_CAN_RX_FAILURE, 0); +#ifdef DEBUG_LOG + logging.println("Stale Min/Max voltage values detected during charge/discharge sending - 9999mV..."); +#endif // DEBUG_LOG + } else { + + datalayer.battery.status.cell_min_voltage_mV = min_cell_voltage; //Value is alive + datalayer.battery.status.cell_max_voltage_mV = max_cell_voltage; //Value is alive + } + + datalayer.battery.info.max_design_voltage_dV = max_design_voltage; + + datalayer.battery.info.min_design_voltage_dV = min_design_voltage; + + datalayer.battery.info.number_of_cells = detected_number_of_cells; + + datalayer_extended.bmwphev.min_cell_voltage_data_age = (millis() - min_cell_voltage_lastchanged); + + datalayer_extended.bmwphev.max_cell_voltage_data_age = (millis() - max_cell_voltage_lastchanged); + + datalayer_extended.bmwphev.T30_Voltage = terminal30_12v_voltage; + + datalayer_extended.bmwphev.hvil_status = hvil_status; + + datalayer_extended.bmwphev.allowable_charge_amps = allowable_charge_amps; + + datalayer_extended.bmwphev.allowable_discharge_amps = allowable_discharge_amps; + + datalayer_extended.bmwphev.balancing_status = balancing_status; + + datalayer_extended.bmwphev.battery_voltage_after_contactor = battery_voltage_after_contactor; + + // Update webserver datalayer + + datalayer_extended.bmwphev.ST_iso_ext = battery_status_error_isolation_external_Bordnetz; + datalayer_extended.bmwphev.ST_iso_int = battery_status_error_isolation_internal_Bordnetz; + datalayer_extended.bmwphev.ST_valve_cooling = battery_status_valve_cooling; + datalayer_extended.bmwphev.ST_interlock = battery_status_error_locking; + datalayer_extended.bmwphev.ST_precharge = battery_status_precharge_locked; + datalayer_extended.bmwphev.ST_DCSW = battery_status_disconnecting_switch; + datalayer_extended.bmwphev.ST_EMG = battery_status_emergency_mode; + datalayer_extended.bmwphev.ST_WELD = battery_status_error_disconnecting_switch; + datalayer_extended.bmwphev.ST_isolation = battery_status_warning_isolation; + datalayer_extended.bmwphev.ST_cold_shutoff_valve = battery_status_cold_shutoff_valve; + datalayer_extended.bmwphev.iso_safety_int_kohm = iso_safety_int_kohm; + datalayer_extended.bmwphev.iso_safety_ext_kohm = iso_safety_ext_kohm; + datalayer_extended.bmwphev.iso_safety_trg_kohm = iso_safety_trg_kohm; + datalayer_extended.bmwphev.iso_safety_ext_plausible = iso_safety_ext_plausible; + datalayer_extended.bmwphev.iso_safety_int_plausible = iso_safety_int_plausible; + datalayer_extended.bmwphev.iso_safety_kohm = iso_safety_kohm; + datalayer_extended.bmwphev.iso_safety_kohm_quality = iso_safety_kohm_quality; + + if (pack_limit_info_available) { + // If we have pack limit data from battery - override the defaults to suit + datalayer.battery.info.max_design_voltage_dV = max_design_voltage; + datalayer.battery.info.min_design_voltage_dV = min_design_voltage; + } + if (cell_limit_info_available) { + // If we have cell limit data from battery - override the defaults to suit + datalayer.battery.info.max_cell_voltage_mV = MAX_CELL_VOLTAGE_MV; + datalayer.battery.info.min_cell_voltage_mV = MIN_CELL_VOLTAGE_MV; + } +} +void handle_incoming_can_frame_battery(CAN_frame rx_frame) { + + battery_awake = true; + switch (rx_frame.ID) { + case 0x112: + battery_awake = true; + datalayer.battery.status.CAN_battery_still_alive = + CAN_STILL_ALIVE; //This message is only sent if 30C (Wakeup pin on battery) is energized with 12V + break; + case 0x2F5: //BMS [100ms] High-Voltage Battery Charge/Discharge Limitations + battery_max_charge_voltage = (rx_frame.data.u8[1] << 8 | rx_frame.data.u8[0]); + battery_max_charge_amperage = (((rx_frame.data.u8[3] << 8) | rx_frame.data.u8[2]) - 819.2); + battery_min_discharge_voltage = (rx_frame.data.u8[5] << 8 | rx_frame.data.u8[4]); + battery_max_discharge_amperage = (((rx_frame.data.u8[7] << 8) | rx_frame.data.u8[6]) - 819.2); + break; + case 0x239: //BMS [200ms] + battery_predicted_energy_charge_condition = (rx_frame.data.u8[2] << 8 | rx_frame.data.u8[1]); //Wh + battery_predicted_energy_charging_target = ((rx_frame.data.u8[4] << 8 | rx_frame.data.u8[3]) * 0.02); //kWh + break; + case 0x40D: //BMS [1s] Charging status of high-voltage storage - 1 + battery_BEV_available_power_shortterm_charge = (rx_frame.data.u8[1] << 8 | rx_frame.data.u8[0]) * 3; + battery_BEV_available_power_shortterm_discharge = (rx_frame.data.u8[3] << 8 | rx_frame.data.u8[2]) * 3; + battery_BEV_available_power_longterm_charge = (rx_frame.data.u8[5] << 8 | rx_frame.data.u8[4]) * 3; + battery_BEV_available_power_longterm_discharge = (rx_frame.data.u8[7] << 8 | rx_frame.data.u8[6]) * 3; + break; + case 0x430: //BMS [1s] - Charging status of high-voltage battery - 2 + battery_prediction_voltage_shortterm_charge = (rx_frame.data.u8[1] << 8 | rx_frame.data.u8[0]); + battery_prediction_voltage_shortterm_discharge = (rx_frame.data.u8[3] << 8 | rx_frame.data.u8[2]); + battery_prediction_voltage_longterm_charge = (rx_frame.data.u8[5] << 8 | rx_frame.data.u8[4]); + battery_prediction_voltage_longterm_discharge = (rx_frame.data.u8[7] << 8 | rx_frame.data.u8[6]); + break; + case 0x431: //BMS [200ms] Data High-Voltage Battery Unit + battery_status_service_disconnection_plug = (rx_frame.data.u8[0] & 0x0F); + battery_status_measurement_isolation = (rx_frame.data.u8[0] & 0x0C) >> 2; + battery_request_abort_charging = (rx_frame.data.u8[0] & 0x30) >> 4; + battery_prediction_duration_charging_minutes = (rx_frame.data.u8[3] << 8 | rx_frame.data.u8[2]); + battery_prediction_time_end_of_charging_minutes = rx_frame.data.u8[4]; + battery_energy_content_maximum_kWh = (((rx_frame.data.u8[6] & 0x0F) << 8 | rx_frame.data.u8[5])) / 50; + break; + case 0x432: //BMS [200ms] SOC% info + battery_request_operating_mode = (rx_frame.data.u8[0] & 0x03); + battery_target_voltage_in_CV_mode = ((rx_frame.data.u8[1] << 4 | rx_frame.data.u8[0] >> 4)) / 10; + battery_request_charging_condition_minimum = (rx_frame.data.u8[2] / 2); + battery_request_charging_condition_maximum = (rx_frame.data.u8[3] / 2); + battery_display_SOC = rx_frame.data.u8[4]; + break; + case 0x607: //SME responds to UDS requests on 0x607 + { + // UDS Multi Frame vars - Top nibble indicates Frame Type: SF (0), FF (1), CF (2), FC (3) + // Extended addressing => data[0] is ext address, data[1] is PCI + uint8_t extAddr = rx_frame.data.u8[0]; // e.g., 0xF1 + uint8_t pciByte = rx_frame.data.u8[1]; // e.g., 0x10, 0x21, etc. + uint8_t pciType = pciByte >> 4; // top nibble => 0=SF,1=FF,2=CF,3=FC + uint8_t pciLower = pciByte & 0x0F; // bottom nibble => length nibble or sequence + + switch (pciType) { + case 0x0: { + // Single Frame reponse + // SF payload length is in pciLower + uint8_t sfLength = pciLower; + uint8_t moduleID = rx_frame.data.u8[5]; + + if (rx_frame.DLC = 8 && rx_frame.data.u8[3] == 0xDD && rx_frame.data.u8[4] == 0xC4) { // SOC% + avg_soc_state = (rx_frame.data.u8[5] << 8 | rx_frame.data.u8[6]); + } + if (rx_frame.DLC = 8 && rx_frame.data.u8[3] == 0xDD && rx_frame.data.u8[4] == 0x7B) { // SOH% + min_soh_state = (rx_frame.data.u8[5]) * 100; + } + + if (rx_frame.DLC = 8 && rx_frame.data.u8[3] == 0xD6 && rx_frame.data.u8[4] == 0xD9) { // Isolation Reading 2 + iso_safety_kohm = (rx_frame.data.u8[5] << 8 | rx_frame.data.u8[6]); //STAT_R_ISO_ROH_01_WERT + iso_safety_kohm_quality = + (rx_frame.data.u8[7]); //STAT_R_ISO_ROH_QAL_01_INFO Quality of measurement 0-21 (higher better) + } + + if (rx_frame.DLC = 8 && rx_frame.data.u8[3] == 0xDD && + rx_frame.data.u8[4] == 0xB4) { //Main Battery Voltage (Pre Contactor) + battery_voltage = (rx_frame.data.u8[5] << 8 | rx_frame.data.u8[6]) / 10; + } + + if (rx_frame.DLC = 7 && rx_frame.data.u8[3] == 0xDD && + rx_frame.data.u8[4] == 0x66) { //Main Battery Voltage (Post Contactor) + battery_voltage_after_contactor = (rx_frame.data.u8[5] << 8 | rx_frame.data.u8[6]) / 10; + } + + if (rx_frame.DLC = 7 && rx_frame.data.u8[1] == 0x05 && rx_frame.data.u8[2] == 0x71 && + rx_frame.data.u8[3] == 0x03 && + rx_frame.data.u8[4] == + 0xAD) { //Balancing Status 01 Active 03 Not Active 7DLC F1 05 71 03 AD 6B 01 + balancing_status = (rx_frame.data.u8[6]); + // #ifdef DEBUG_LOG + + // logging.println("Balancing Status received"); + + // #endif // DEBUG_LOG + } + + break; + } + case 0x1: { + // total length = (pciLower << 8) + data[2] + uint16_t totalLength = ((uint16_t)pciLower << 8) | rx_frame.data.u8[2]; + uint8_t moduleID = rx_frame.data.u8[5]; +#if defined(DEBUG_LOG) && defined(UDS_LOG) + logging.print("FF arrived! moduleID=0x"); + logging.print(moduleID, HEX); + logging.print(", totalLength="); + logging.println(totalLength); +#endif // DEBUG_LOG && UDS_LOG + + // Start the multi-frame + startUDSMultiFrameReception(totalLength, moduleID); + gUDSContext.receivedInBatch = 0; // Reset batch count + // The FF payload is at data[3..7] (5 bytes) for an 8-byte CAN frame in extended addressing + const uint8_t* ffPayload = &rx_frame.data.u8[3]; + uint8_t ffPayloadSize = 5; + storeUDSPayload(ffPayload, ffPayloadSize); + +#if defined(DEBUG_LOG) && defined(UDS_LOG) + logging.print("After FF, UDS_bytesReceived="); + logging.println(gUDSContext.UDS_bytesReceived); +#endif // DEBUG_LOG && UDS_LOG +#if defined(DEBUG_LOG) && defined(UDS_LOG) + logging.println("Requesting continue frame..."); +#endif // DEBUG_LOG && UDS_LOG + transmit_can_frame(&BMW_6F1_REQUEST_CONTINUE_MULTIFRAME, can_config.battery); + break; + } + + case 0x2: { + // The sequence number is in (data[0] & 0x0F), but we often don’t need it if frames are in order. + // Make sure we *are* in progress + if (!gUDSContext.UDS_inProgress) { +// Unexpected CF. Possibly ignore or reset. +#if defined(DEBUG_LOG) && defined(UDS_LOG) + uint8_t seq = pciByte & 0x0F; + logging.print("Unexpected CF --- seq=0x"); + logging.print(seq, HEX); + logging.print(" for moduleID=0x"); + logging.println(gUDSContext.UDS_moduleID, HEX); +#endif // DEBUG_LOG && UDS_LOG + return; + } +#if defined(DEBUG_LOG) && defined(UDS_LOG) + uint8_t seq = pciByte & 0x0F; + logging.print("CF seq=0x"); + logging.print(seq, HEX); + logging.print("CF pcibyte=0x"); + logging.print(pciByte, HEX); + logging.print(" for moduleID=0x"); + logging.println(gUDSContext.UDS_moduleID, HEX); +#endif // DEBUG_LOG && UDS_LOG + + storeUDSPayload(&rx_frame.data.u8[2], 6); + // Increment batch counter + gUDSContext.receivedInBatch++; +#if defined(DEBUG_LOG) && defined(UDS_LOG) + logging.print("After CF seq=0x"); + logging.print(seq, HEX); + logging.print(", moduleID=0x"); + logging.print(gUDSContext.UDS_moduleID, HEX); + logging.print(", UDS_bytesReceived="); + logging.println(gUDSContext.UDS_bytesReceived); +#endif // DEBUG_LOG && UDS_LOG + + // Check if the batch is complete + if (gUDSContext.receivedInBatch >= 3) { //BMW PHEV Using batch size of 3 in continue message + // Send the next Flow Control +#if defined(DEBUG_LOG) && defined(UDS_LOG) + logging.println("Batch Complete - Requesting continue frame..."); +#endif // DEBUG_LOG && UDS_LOG + transmit_can_frame(&BMW_6F1_REQUEST_CONTINUE_MULTIFRAME, can_config.battery); + gUDSContext.receivedInBatch = 0; // Reset batch count + Serial.println("Sent FC for next batch of 3 frames."); + } + + break; + } + + case 0x3: { + // Flow Control Frame from ECU -> Tester (rare in a typical request/response flow) + // Typically we only *send* FC. If the ECU sends one, parse or ignore here. + break; + } + } + + // Optionally, check if message is complete + if (isUDSMessageComplete()) { + // We have a complete UDS/ISO-TP response in gUDSContext.UDS_buffer +#if defined(DEBUG_LOG) && defined(UDS_LOG) + logging.print("UDS message complete for module ID 0x"); + logging.println(gUDSContext.UDS_moduleID, HEX); + + logging.print("Total bytes: "); + logging.println(gUDSContext.UDS_bytesReceived); + + logging.print("Received data: "); + for (uint16_t i = 0; i < gUDSContext.UDS_bytesReceived; i++) { + // Optional leading zero for single-digit hex + if (gUDSContext.UDS_buffer[i] < 0x10) { + logging.print("0"); + } + logging.print(gUDSContext.UDS_buffer[i], HEX); + logging.print(" "); + } + logging.println(); // new line at the end +#endif // DEBUG_LOG + + //Cell Voltages + if (gUDSContext.UDS_moduleID == 0xA5) { //We have a complete set of cell voltages - pass to data layer + processCellVoltages(); + } + //Current measurement + if (gUDSContext.UDS_moduleID == 0x69) { //Current (32bit mA? negative = discharge) + battery_current = ((int32_t)((gUDSContext.UDS_buffer[3] << 24) | (gUDSContext.UDS_buffer[4] << 16) | + (gUDSContext.UDS_buffer[5] << 8) | gUDSContext.UDS_buffer[6])) * + 0.1; +#ifdef DEBUG_LOG + logging.print("Received current/amps measurement data: "); + logging.print(battery_current); + logging.print(" - "); + for (uint16_t i = 0; i < gUDSContext.UDS_bytesReceived; i++) { + // Optional leading zero for single-digit hex + if (gUDSContext.UDS_buffer[i] < 0x10) { + logging.print("0"); + } + logging.print(gUDSContext.UDS_buffer[i], HEX); + logging.print(" "); + } + logging.println(); // new line at the end +#endif // DEBUG_LOG + } + + //Cell Min/Max + if (gUDSContext.UDS_moduleID == + 0xA0) { //We have a complete frame for cell min max - pass to data layer UNCONFIRMED IF THESE ARE CORRECT BYTES + + //Check values are valid + if (gUDSContext.UDS_buffer[9] != 0xFF && gUDSContext.UDS_buffer[10] != 0xFF && + gUDSContext.UDS_buffer[11] != 0xFF && gUDSContext.UDS_buffer[12] != 0xFF && + gUDSContext.UDS_buffer[9] != 0x00 && gUDSContext.UDS_buffer[10] != 0x00 && + gUDSContext.UDS_buffer[11] != 0x00 && gUDSContext.UDS_buffer[12] != 0x00) { + min_cell_voltage = (gUDSContext.UDS_buffer[9] << 8 | gUDSContext.UDS_buffer[10]) / 10; + max_cell_voltage = (gUDSContext.UDS_buffer[11] << 8 | gUDSContext.UDS_buffer[12]) / 10; + } else { +#ifdef DEBUG_LOG + logging.println("Cell Min Max Invalid 65535 or 0..."); + logging.print("Received data: "); + for (uint16_t i = 0; i < gUDSContext.UDS_bytesReceived; i++) { + // Optional leading zero for single-digit hex + if (gUDSContext.UDS_buffer[i] < 0x10) { + logging.print("0"); + } + logging.print(gUDSContext.UDS_buffer[i], HEX); + logging.print(" "); + } + logging.println(); // new line at the end +#endif // DEBUG_LOG + } + } + + if (gUDSContext.UDS_moduleID == 0x7E) { // Voltage Limits + max_design_voltage = (gUDSContext.UDS_buffer[3] << 8 | gUDSContext.UDS_buffer[4]) / 10; + min_design_voltage = (gUDSContext.UDS_buffer[5] << 8 | gUDSContext.UDS_buffer[6]) / 10; + pack_limit_info_available = true; + } + + if (gUDSContext.UDS_moduleID == 0x7D) { // Current Limits + allowable_charge_amps = (gUDSContext.UDS_buffer[3] << 8 | gUDSContext.UDS_buffer[4]) / 10; + allowable_discharge_amps = (gUDSContext.UDS_buffer[5] << 8 | gUDSContext.UDS_buffer[6]) / 10; + } + + if (gUDSContext.UDS_moduleID == 0x90) { // Paired VIN + for (int i = 0; i < 17; i++) { + paired_vin[i] = gUDSContext.UDS_buffer[4 + i]; + } + } + if (gUDSContext.UDS_moduleID == 0x6A) { // Iso Reading 1 + iso_safety_int_kohm = + (gUDSContext.UDS_buffer[7] << 8 | gUDSContext.UDS_buffer[8]); //STAT_ISOWIDERSTAND_INT_WERT + iso_safety_ext_kohm = + (gUDSContext.UDS_buffer[3] << 8 | gUDSContext.UDS_buffer[4]); //STAT_ISOWIDERSTAND_EXT_STD_WERT + iso_safety_trg_kohm = (gUDSContext.UDS_buffer[5] << 8 | gUDSContext.UDS_buffer[6]); + iso_safety_ext_plausible = gUDSContext.UDS_buffer[9]; //STAT_ISOWIDERSTAND_EXT_TRG_PLAUS + iso_safety_trg_plausible = gUDSContext.UDS_buffer[10]; //STAT_ISOWIDERSTAND_EXT_TRG_WERT + iso_safety_int_plausible = gUDSContext.UDS_buffer[11]; //STAT_ISOWIDERSTAND_EXT_TRG_WERT + } + } + + break; + } + case 0x1FA: //BMS [1000ms] Status Of High-Voltage Battery - 1 + battery_status_error_isolation_external_Bordnetz = (rx_frame.data.u8[0] & 0x03); + battery_status_error_isolation_internal_Bordnetz = (rx_frame.data.u8[0] & 0x0C) >> 2; + battery_request_cooling = (rx_frame.data.u8[0] & 0x30) >> 4; + battery_status_valve_cooling = (rx_frame.data.u8[0] & 0xC0) >> 6; + battery_status_error_locking = (rx_frame.data.u8[1] & 0x03); + battery_status_precharge_locked = (rx_frame.data.u8[1] & 0x0C) >> 2; + battery_status_disconnecting_switch = (rx_frame.data.u8[1] & 0x30) >> 4; + battery_status_emergency_mode = (rx_frame.data.u8[1] & 0xC0) >> 6; + battery_request_service = (rx_frame.data.u8[2] & 0x03); + battery_error_emergency_mode = (rx_frame.data.u8[2] & 0x0C) >> 2; + battery_status_error_disconnecting_switch = (rx_frame.data.u8[2] & 0x30) >> 4; + battery_status_warning_isolation = (rx_frame.data.u8[2] & 0xC0) >> 6; + battery_status_cold_shutoff_valve = (rx_frame.data.u8[3] & 0x0F); + battery_temperature_HV = (rx_frame.data.u8[4] - 50); + battery_temperature_heat_exchanger = (rx_frame.data.u8[5] - 50); + if (rx_frame.data.u8[6] > 0 && rx_frame.data.u8[6] < 255) { + battery_temperature_min = (rx_frame.data.u8[6] - 50); + } else { +#ifdef DEBUG_LOG + logging.println("Pre parsed Cell Temp Min is Invalid "); +#endif + } + if (rx_frame.data.u8[7] > 0 && rx_frame.data.u8[7] < 255) { + battery_temperature_max = (rx_frame.data.u8[7] - 50); + } else { +#ifdef DEBUG_LOG + logging.println("Pre parsed Cell Temp Max is Invalid "); +#endif + } + + break; + default: + break; + } +} + +void transmit_can_battery() { + unsigned long currentMillis = millis(); + + //if (battery_awake) { //We can always send CAN as the PHEV BMS will wake up on vehicle comms + + if (currentMillis - previousMillis20 >= INTERVAL_20_MS) { + previousMillis20 = currentMillis; + + if (startup_counter_contactor < 160) { + startup_counter_contactor++; + } else { //After 160 messages, turn on the request + BMW_10B.data.u8[1] = 0x10; // Close contactors + } + + BMW_10B.data.u8[1] = ((BMW_10B.data.u8[1] & 0xF0) + alive_counter_20ms); + BMW_10B.data.u8[0] = calculateCRC(BMW_10B, 3, 0x3F); + + alive_counter_20ms = increment_alive_counter(alive_counter_20ms); + + BMW_13E_counter++; + BMW_13E.data.u8[4] = BMW_13E_counter; + + //if (datalayer.battery.status.bms_status == FAULT) { //ALLOW ANY TIME - TEST ONLY + //} //If battery is not in Fault mode, allow contactor to close by sending 10B + //else { + transmit_can_frame(&BMW_10B, can_config.battery); + //} + } + + // Send 100ms CAN Message + if (currentMillis - previousMillis100 >= INTERVAL_100_MS) { + previousMillis100 = currentMillis; + } + // Send 200ms CAN Message + if (currentMillis - previousMillis200 >= INTERVAL_200_MS) { + previousMillis200 = currentMillis; + uds_fast_req_id_counter = increment_uds_req_id_counter( + uds_fast_req_id_counter, numFastUDSreqs); //Loop through and send a different UDS request each cycle + transmit_can_frame(UDS_REQUESTS_FAST[uds_fast_req_id_counter], can_config.battery); + } + // Send 1000ms CAN Message + if (currentMillis - previousMillis1000 >= INTERVAL_1_S) { + previousMillis1000 = currentMillis; + + uds_slow_req_id_counter = increment_uds_req_id_counter( + uds_slow_req_id_counter, numSlowUDSreqs); //Loop through and send a different UDS request each cycle + transmit_can_frame(UDS_REQUESTS_SLOW[uds_slow_req_id_counter], can_config.battery); + } + // Send 5000ms CAN Message + if (currentMillis - previousMillis5000 >= INTERVAL_5_S) { + previousMillis5000 = currentMillis; + + // transmit_can_frame(&BMWPHEV_6F1_REQUEST_CONTACTORS_CLOSE, + // can_config.battery); // Attempt contactor close - experimental + } + // Send 10000ms CAN Message + if (currentMillis - previousMillis10000 >= INTERVAL_10_S) { + previousMillis10000 = currentMillis; + transmit_can_frame(&BMWPHEV_6F1_REQUEST_BALANCING_START, + can_config.battery); // Enable Balancing + } +} + +void setup_battery(void) { // Performs one time setup at startup + strncpy(datalayer.system.info.battery_protocol, "BMW PHEV Battery", 63); + datalayer.system.info.battery_protocol[63] = '\0'; + //Wakeup the SME + wake_battery_via_canbus(); + + transmit_can_frame(&BMWPHEV_6F1_REQUEST_ISOLATION_TEST, + can_config.battery); // Run Internal Isolation Test at startup + + 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_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; + datalayer.system.status.battery_allows_contactor_closing = true; +} + +#endif diff --git a/Software/src/battery/BMW-PHEV-BATTERY.h b/Software/src/battery/BMW-PHEV-BATTERY.h new file mode 100644 index 00000000..7aaccb7d --- /dev/null +++ b/Software/src/battery/BMW-PHEV-BATTERY.h @@ -0,0 +1,22 @@ +#ifndef BMW_PHEV_BATTERY_H +#define BMW_PHEV_BATTERY_H +#include +#include "../include.h" + +#define BATTERY_SELECTED + +#define MAX_PACK_VOLTAGE_DV 4650 //4650 = 465.0V +#define MIN_PACK_VOLTAGE_DV 3000 +#define MAX_CELL_DEVIATION_MV 250 +#define MAX_CELL_VOLTAGE_MV 4300 //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 +#define MAX_DISCHARGE_POWER_ALLOWED_W 10000 +#define MAX_CHARGE_POWER_ALLOWED_W 10000 +#define MAX_CHARGE_POWER_WHEN_TOPBALANCING_W 500 +#define RAMPDOWN_SOC 9000 // (90.00) SOC% to start ramping down from max charge power towards 0 at 100.00% +#define STALE_PERIOD_CONFIG \ + 3600000; //Number of milliseconds before critical values are classed as stale/stuck 1800000 = 3600 seconds / 60mins +void setup_battery(void); +void transmit_can_frame(CAN_frame* tx_frame, int interface); + +#endif diff --git a/Software/src/datalayer/datalayer_extended.h b/Software/src/datalayer/datalayer_extended.h index 91054cb8..27a86131 100644 --- a/Software/src/datalayer/datalayer_extended.h +++ b/Software/src/datalayer/datalayer_extended.h @@ -64,6 +64,62 @@ typedef struct { } DATALAYER_INFO_BMWIX; +typedef struct { + /** uint8_t */ + /** Status isolation external, 0 not evaluated, 1 OK, 2 error active, 3 Invalid signal*/ + uint8_t ST_iso_ext = 0; + /** uint8_t */ + /** Status isolation external, 0 not evaluated, 1 OK, 2 error active, 3 Invalid signal*/ + uint8_t ST_iso_int = 0; + /** uint8_t */ + /** Status cooling valve error, 0 not evaluated, 1 OK valve closed, 2 error active valve open, 3 Invalid signal*/ + uint8_t ST_valve_cooling = 0; + /** uint8_t */ + /** Status interlock error, 0 not evaluated, 1 OK, 2 error active, 3 Invalid signal*/ + uint8_t ST_interlock = 0; + /** uint8_t */ + /** Status precharge, 0 no statement, 1 Not active closing not blocked, 2 error precharge blocked, 3 Invalid signal*/ + uint8_t ST_precharge = 0; + /** uint8_t */ + /** Status DC switch, 0 contactors open, 1 precharge ongoing, 2 contactors engaged, 3 Invalid signal*/ + uint8_t ST_DCSW = 0; + /** uint8_t */ + /** Status emergency, 0 not evaluated, 1 OK, 2 error active, 3 Invalid signal*/ + uint8_t ST_EMG = 0; + /** uint8_t */ + /** Status welding detection, 0 Contactors OK, 1 One contactor welded, 2 Two contactors welded, 3 Invalid signal*/ + uint8_t ST_WELD = 0; + /** uint8_t */ + /** Status isolation, 0 not evaluated, 1 OK, 2 error active, 3 Invalid signal*/ + uint8_t ST_isolation = 0; + /** uint8_t */ + /** Status cold shutoff valve, 0 OK, 1 Short circuit to GND, 2 Short circuit to 12V, 3 Line break, 6 Driver error, 12 Stuck, 13 Stuck, 15 Invalid Signal*/ + uint8_t ST_cold_shutoff_valve = 0; + /** uint16_t */ + /** Terminal 30 - 12V SME Supply Voltage */ + uint16_t T30_Voltage = 0; + /** Status HVIL, 1 HVIL OK, 0 HVIL disconnected*/ + uint8_t hvil_status = 0; + /** Min/Max Cell SOH*/ + uint16_t min_soh_state = 0; + uint16_t max_soh_state = 0; + int32_t allowable_charge_amps = 0; + int32_t allowable_discharge_amps = 0; + int16_t balancing_status = 0; + int16_t battery_voltage_after_contactor = 0; + unsigned long min_cell_voltage_data_age = 0; + unsigned long max_cell_voltage_data_age = 0; + int32_t iso_safety_int_kohm = 0; //STAT_ISOWIDERSTAND_INT_WERT + int32_t iso_safety_ext_kohm = 0; //STAT_ISOWIDERSTAND_EXT_STD_WERT + int32_t iso_safety_trg_kohm = 0; + int32_t iso_safety_ext_plausible = 0; //STAT_ISOWIDERSTAND_EXT_TRG_PLAUS + int32_t iso_safety_int_plausible = 0; + int32_t iso_safety_trg_plausible = 0; + int32_t iso_safety_kohm = 0; //STAT_R_ISO_ROH_01_WERT + int32_t iso_safety_kohm_quality = 0; //STAT_R_ISO_ROH_QAL_01_INFO Quality of measurement 0-21 (higher better) + +} DATALAYER_INFO_BMWPHEV; + typedef struct { /** uint16_t */ /** SOC% raw battery value. Might not always reach 100% */ @@ -619,6 +675,7 @@ class DataLayerExtended { public: DATALAYER_INFO_BOLTAMPERA boltampera; DATALAYER_INFO_BMWIX bmwix; + DATALAYER_INFO_BMWPHEV bmwphev; DATALAYER_INFO_BMWI3 bmwi3; DATALAYER_INFO_BYDATTO3 bydAtto3; DATALAYER_INFO_CELLPOWER cellpower; diff --git a/Software/src/devboard/mqtt/mqtt.cpp b/Software/src/devboard/mqtt/mqtt.cpp index e0aef893..15e97331 100644 --- a/Software/src/devboard/mqtt/mqtt.cpp +++ b/Software/src/devboard/mqtt/mqtt.cpp @@ -59,6 +59,7 @@ SensorConfig sensorConfigs[] = { {"battery_current", "Battery Current", "{{ value_json.battery_current }}", "A", "current"}, {"cell_max_voltage", "Cell Max Voltage", "{{ value_json.cell_max_voltage }}", "V", "voltage"}, {"cell_min_voltage", "Cell Min Voltage", "{{ value_json.cell_min_voltage }}", "V", "voltage"}, + {"cell_voltage_delta", "Cell Voltage Delta", "{{ value_json.cell_voltage_delta }}", "mV", "voltage"}, {"battery_voltage", "Battery Voltage", "{{ value_json.battery_voltage }}", "V", "voltage"}, {"total_capacity", "Battery Total Capacity", "{{ value_json.total_capacity }}", "Wh", "energy"}, {"remaining_capacity", "Battery Remaining Capacity (scaled)", "{{ value_json.remaining_capacity }}", "Wh", @@ -79,6 +80,7 @@ SensorConfig sensorConfigs[] = { {"battery_current_2", "Battery 2 Current", "{{ value_json.battery_current_2 }}", "A", "current"}, {"cell_max_voltage_2", "Cell Max Voltage 2", "{{ value_json.cell_max_voltage_2 }}", "V", "voltage"}, {"cell_min_voltage_2", "Cell Min Voltage 2", "{{ value_json.cell_min_voltage_2 }}", "V", "voltage"}, + {"cell_voltage_delta_2", "Cell Voltage Delta 2", "{{ value_json.cell_voltage_delta_2 }}", "mV", "voltage"}, {"battery_voltage_2", "Battery 2 Voltage", "{{ value_json.battery_voltage_2 }}", "V", "voltage"}, {"total_capacity_2", "Battery 2 Total Capacity", "{{ value_json.total_capacity_2 }}", "Wh", "energy"}, {"remaining_capacity_2", "Battery 2 Remaining Capacity (scaled)", "{{ value_json.remaining_capacity_2 }}", "Wh", @@ -174,6 +176,8 @@ static void publish_common_info(void) { datalayer.battery.status.cell_voltages_mV[datalayer.battery.info.number_of_cells - 1] != 0u) { doc["cell_max_voltage"] = ((float)datalayer.battery.status.cell_max_voltage_mV) / 1000.0; doc["cell_min_voltage"] = ((float)datalayer.battery.status.cell_min_voltage_mV) / 1000.0; + doc["cell_voltage_delta"] = ((float)datalayer.battery.status.cell_max_voltage_mV) - + ((float)datalayer.battery.status.cell_min_voltage_mV); } doc["total_capacity"] = ((float)datalayer.battery.info.total_capacity_Wh); doc["remaining_capacity_real"] = ((float)datalayer.battery.status.remaining_capacity_Wh); @@ -197,6 +201,8 @@ static void publish_common_info(void) { datalayer.battery2.status.cell_voltages_mV[datalayer.battery2.info.number_of_cells - 1] != 0u) { doc["cell_max_voltage_2"] = ((float)datalayer.battery2.status.cell_max_voltage_mV) / 1000.0; doc["cell_min_voltage_2"] = ((float)datalayer.battery2.status.cell_min_voltage_mV) / 1000.0; + doc["cell_voltage_delta_2"] = ((float)datalayer.battery2.status.cell_max_voltage_mV) - + ((float)datalayer.battery2.status.cell_min_voltage_mV); } doc["total_capacity_2"] = ((float)datalayer.battery2.info.total_capacity_Wh); doc["remaining_capacity_real_2"] = ((float)datalayer.battery2.status.remaining_capacity_Wh); diff --git a/Software/src/devboard/webserver/advanced_battery_html.cpp b/Software/src/devboard/webserver/advanced_battery_html.cpp index 04a0ba3d..469c5c7f 100644 --- a/Software/src/devboard/webserver/advanced_battery_html.cpp +++ b/Software/src/devboard/webserver/advanced_battery_html.cpp @@ -96,6 +96,123 @@ String advanced_battery_processor(const String& var) { content += "

Pyro Status PSS6: " + String((pyroText[datalayer_extended.bmwix.pyro_status_pss6])) + "

"; #endif //BMW_IX_BATTERY +#ifdef BMW_PHEV_BATTERY + content += + "

Battery Voltage after Contactor: " + String(datalayer_extended.bmwphev.battery_voltage_after_contactor) + + " dV

"; + content += "

Allowed Discharge Power: " + String(datalayer.battery.status.max_discharge_power_W) + " W

"; + content += "

Allowed Charge Power: " + String(datalayer.battery.status.max_charge_power_W) + " W

"; + static const char* balanceText[5] = {"0 Balancing Inactive - Balancing not needed", "1 Balancing Active", + "2 Balancing Inactive - Cells not in rest break wait 10mins", + "3 Balancing Inactive", "4 Unknown"}; + content += "

Balancing: " + String((balanceText[datalayer_extended.bmwphev.balancing_status])) + "

"; + static const char* pyroText[5] = {"0 Value Invalid", "1 Successfully Blown", "2 Disconnected", + "3 Not Activated - Pyro Intact", "4 Unknown"}; + static const char* statusText[16] = { + "Not evaluated", "OK", "Error!", "Invalid signal", "", "", "", "", "", "", "", "", "", "", "", ""}; + content += "

Interlock: " + String(statusText[datalayer_extended.bmwphev.ST_interlock]) + "

"; + content += "

Isolation external: " + String(statusText[datalayer_extended.bmwphev.ST_iso_ext]) + "

"; + content += "

Isolation internal: " + String(statusText[datalayer_extended.bmwphev.ST_iso_int]) + "

"; + content += "

Isolation: " + String(statusText[datalayer_extended.bmwphev.ST_isolation]) + "

"; + content += "

Cooling valve: " + String(statusText[datalayer_extended.bmwphev.ST_valve_cooling]) + "

"; + content += "

Emergency: " + String(statusText[datalayer_extended.bmwphev.ST_EMG]) + "

"; + static const char* prechargeText[16] = {"Not evaluated", + "Not active, closing not blocked", + "Error precharge blocked", + "Invalid signal", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + ""}; + content += "

Precharge: " + String(prechargeText[datalayer_extended.bmwphev.ST_precharge]) + + "

"; //Still unclear of enum + static const char* DCSWText[16] = {"Contactors open", + "Precharge ongoing", + "Contactors engaged", + "Invalid signal", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + ""}; + content += "

Contactor status: " + String(DCSWText[datalayer_extended.bmwphev.ST_DCSW]) + "

"; + static const char* contText[16] = {"Contactors OK", + "One contactor welded!", + "Two contactors welded!", + "Invalid signal", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + ""}; + content += "

Contactor weld: " + String(contText[datalayer_extended.bmwphev.ST_WELD]) + "

"; + static const char* valveText[16] = {"OK", + "Short circuit to GND", + "Short circuit to 12V", + "Line break", + "", + "", + "Driver error", + "", + "", + "", + "", + "", + "Stuck", + "Stuck", + "", + "Invalid Signal"}; + content += + "

Cold shutoff valve: " + String(valveText[datalayer_extended.bmwphev.ST_cold_shutoff_valve]) + "

"; + content += + "

Min Cell Voltage Data Age: " + String(datalayer_extended.bmwphev.min_cell_voltage_data_age) + " ms

"; + content += + "

Max Cell Voltage Data Age: " + String(datalayer_extended.bmwphev.max_cell_voltage_data_age) + " ms

"; + content += "

Max Design Voltage: " + String(datalayer.battery.info.max_design_voltage_dV) + " dV

"; + content += "

Min Design Voltage: " + String(datalayer.battery.info.min_design_voltage_dV) + " dV

"; + content += "

BMS Allowed Charge Amps: " + String(datalayer_extended.bmwphev.allowable_charge_amps) + " A

"; + content += + "

BMS Allowed Disharge Amps: " + String(datalayer_extended.bmwphev.allowable_discharge_amps) + " A

"; + content += "

Detected Cell Count: " + String(datalayer.battery.info.number_of_cells) + "

"; + content += "

iso_safety_int_kohm: " + String(datalayer_extended.bmwphev.iso_safety_int_kohm) + "

"; + content += "

iso_safety_ext_kohm: " + String(datalayer_extended.bmwphev.iso_safety_ext_kohm) + "

"; + content += "

iso_safety_trg_kohm: " + String(datalayer_extended.bmwphev.iso_safety_trg_kohm) + "

"; + content += "

iso_safety_ext_plausible: " + String(datalayer_extended.bmwphev.iso_safety_ext_plausible) + "

"; + content += "

iso_safety_int_plausible: " + String(datalayer_extended.bmwphev.iso_safety_int_plausible) + "

"; + content += "

iso_safety_trg_plausible: " + String(datalayer_extended.bmwphev.iso_safety_trg_plausible) + "

"; + content += "

iso_safety_kohm: " + String(datalayer_extended.bmwphev.iso_safety_kohm) + "

"; + content += "

iso_safety_kohm_quality: " + String(datalayer_extended.bmwphev.iso_safety_kohm_quality) + "

"; + content += "
"; + content += "

Todo"; + content += "
"; + content += "

Max Cell Design Voltage: " + String(datalayer.battery.info.max_cell_voltage_mV) + " mV

"; + content += "

Min Cell Design Voltage: " + String(datalayer.battery.info.min_cell_voltage_mV) + " mV

"; + content += "

T30 Terminal Voltage: " + String(datalayer_extended.bmwphev.T30_Voltage) + " mV

"; + content += "
"; +#endif //BMW_PHEV_BATTERY + #ifdef BMW_I3_BATTERY content += "

SOC raw: " + String(datalayer_extended.bmwi3.SOC_raw) + "

"; content += "

SOC dash: " + String(datalayer_extended.bmwi3.SOC_dash) + "

"; @@ -1139,10 +1256,11 @@ String advanced_battery_processor(const String& var) { content += ""; #endif // VOLVO_SPA_BATTERY -#if !defined(BMW_IX_BATTERY) && !defined(BOLT_AMPERA_BATTERY) && !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(KIA_HYUNDAI_64_BATTERY) //Only the listed types have extra info +#if !defined(BMW_PHEV_BATTERY) && !defined(BMW_IX_BATTERY) && !defined(BOLT_AMPERA_BATTERY) && \ + !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(KIA_HYUNDAI_64_BATTERY) //Only the listed types have extra info content += "No extra information available for this battery type"; #endif