Merge branch 'main' into hal2

This commit is contained in:
Jaakko Haakana 2025-06-29 10:11:26 +03:00
commit c63ae6eb23
20 changed files with 588 additions and 328 deletions

View file

@ -142,7 +142,7 @@
#define HA_AUTODISCOVERY // Enable this line to send Home Assistant autodiscovery messages. If not enabled manual configuration of Home Assitant is required #define HA_AUTODISCOVERY // Enable this line to send Home Assistant autodiscovery messages. If not enabled manual configuration of Home Assitant is required
/* Battery settings */ /* Battery settings */
// Predefined total energy capacity of the battery in Watt-hours // Predefined total energy capacity of the battery in Watt-hours (updates automatically from battery data when available)
#define BATTERY_WH_MAX 30000 #define BATTERY_WH_MAX 30000
// Increases battery life. If true will rescale SOC between the configured min/max-percentage // Increases battery life. If true will rescale SOC between the configured min/max-percentage
#define BATTERY_USE_SCALED_SOC true #define BATTERY_USE_SCALED_SOC true

View file

@ -21,7 +21,6 @@ class BmwI3Battery : public CanBattery {
contactor_closing_allowed = contactor_closing_allowed_ptr; contactor_closing_allowed = contactor_closing_allowed_ptr;
allows_contactor_closing = nullptr; allows_contactor_closing = nullptr;
wakeup_pin = wakeup; wakeup_pin = wakeup;
*allows_contactor_closing = true;
//Init voltage to 0 to allow contactor check to operate without fear of default values colliding //Init voltage to 0 to allow contactor check to operate without fear of default values colliding
battery_volts = 0; battery_volts = 0;

View file

@ -25,13 +25,37 @@ class BmwIXHtmlRenderer : public BatteryHtmlRenderer {
content += "<h4>Allowed Charge Power: " + String(datalayer.battery.status.max_charge_power_W) + " W</h4>"; content += "<h4>Allowed Charge Power: " + String(datalayer.battery.status.max_charge_power_W) + " W</h4>";
content += "<h4>T30 Terminal Voltage: " + String(datalayer_extended.bmwix.T30_Voltage) + " mV</h4>"; content += "<h4>T30 Terminal Voltage: " + String(datalayer_extended.bmwix.T30_Voltage) + " mV</h4>";
content += "<h4>Detected Cell Count: " + String(datalayer.battery.info.number_of_cells) + "</h4>"; content += "<h4>Detected Cell Count: " + String(datalayer.battery.info.number_of_cells) + "</h4>";
static const char* balanceText[5] = {"0 No balancing mode active", "1 Voltage-Controlled Balancing Mode", content += "<h4>Balancing: ";
"2 Time-Controlled Balancing Mode with Demand Calculation at End of Charging", switch (datalayer_extended.bmwix.balancing_status) {
"3 Time-Controlled Balancing Mode with Demand Calculation at Resting Voltage", case 0:
"4 No balancing mode active, qualifier invalid"}; content += "0 No balancing mode active</h4>";
content += "<h4>Balancing: " + String((balanceText[datalayer_extended.bmwix.balancing_status])) + "</h4>"; break;
static const char* hvilText[2] = {"Error (Loop Open)", "OK (Loop Closed)"}; case 1:
content += "<h4>HVIL Status: " + String(hvilText[datalayer_extended.bmwix.hvil_status]) + "</h4>"; content += "1 Voltage-Controlled Balancing Mode</h4>";
break;
case 2:
content += "2 Time-Controlled Balancing Mode with Demand Calculation at End of Charging</h4>";
break;
case 3:
content += "3 Time-Controlled Balancing Mode with Demand Calculation at Resting Voltage</h4>";
break;
case 4:
content += "4 No balancing mode active, qualifier invalid</h4>";
break;
default:
content += "Unknown</h4>";
}
content += "<h4>HVIL Status: ";
switch (datalayer_extended.bmwix.hvil_status) {
case 0:
content += "Error (Loop Open)</h4>";
break;
case 1:
content += "OK (Loop Closed)</h4>";
break;
default:
content += "Unknown</h4>";
}
content += "<h4>BMS Uptime: " + String(datalayer_extended.bmwix.bms_uptime) + " seconds</h4>"; content += "<h4>BMS Uptime: " + String(datalayer_extended.bmwix.bms_uptime) + " seconds</h4>";
content += "<h4>BMS Allowed Charge Amps: " + String(datalayer_extended.bmwix.allowable_charge_amps) + " A</h4>"; content += "<h4>BMS Allowed Charge Amps: " + String(datalayer_extended.bmwix.allowable_charge_amps) + " A</h4>";
content += content +=
@ -41,11 +65,66 @@ class BmwIXHtmlRenderer : public BatteryHtmlRenderer {
content += "<h4>Isolation Positive: " + String(datalayer_extended.bmwix.iso_safety_positive) + " kOhm</h4>"; content += "<h4>Isolation Positive: " + String(datalayer_extended.bmwix.iso_safety_positive) + " kOhm</h4>";
content += "<h4>Isolation Negative: " + String(datalayer_extended.bmwix.iso_safety_negative) + " kOhm</h4>"; content += "<h4>Isolation Negative: " + String(datalayer_extended.bmwix.iso_safety_negative) + " kOhm</h4>";
content += "<h4>Isolation Parallel: " + String(datalayer_extended.bmwix.iso_safety_parallel) + " kOhm</h4>"; content += "<h4>Isolation Parallel: " + String(datalayer_extended.bmwix.iso_safety_parallel) + " kOhm</h4>";
static const char* pyroText[5] = {"0 Value Invalid", "1 Successfully Blown", "2 Disconnected", content += "<h4>Pyro Status PSS1: ";
"3 Not Activated - Pyro Intact", "4 Unknown"}; switch (datalayer_extended.bmwix.pyro_status_pss1) {
content += "<h4>Pyro Status PSS1: " + String((pyroText[datalayer_extended.bmwix.pyro_status_pss1])) + "</h4>"; case 0:
content += "<h4>Pyro Status PSS4: " + String((pyroText[datalayer_extended.bmwix.pyro_status_pss4])) + "</h4>"; content += "0 Value Invalid</h4>";
content += "<h4>Pyro Status PSS6: " + String((pyroText[datalayer_extended.bmwix.pyro_status_pss6])) + "</h4>"; break;
case 1:
content += "1 Successfully Blown</h4>";
break;
case 2:
content += "2 Disconnected</h4>";
break;
case 3:
content += "3 Not Activated - Pyro Intact</h4>";
break;
case 4:
content += "4 Unknown</h4>";
break;
default:
content += "Unknown</h4>";
}
content += "<h4>Pyro Status PSS4: ";
switch (datalayer_extended.bmwix.pyro_status_pss4) {
case 0:
content += "0 Value Invalid</h4>";
break;
case 1:
content += "1 Successfully Blown</h4>";
break;
case 2:
content += "2 Disconnected</h4>";
break;
case 3:
content += "3 Not Activated - Pyro Intact</h4>";
break;
case 4:
content += "4 Unknown</h4>";
break;
default:
content += "Unknown</h4>";
}
content += "<h4>Pyro Status PSS6: ";
switch (datalayer_extended.bmwix.pyro_status_pss6) {
case 0:
content += "0 Value Invalid</h4>";
break;
case 1:
content += "1 Successfully Blown</h4>";
break;
case 2:
content += "2 Disconnected</h4>";
break;
case 3:
content += "3 Not Activated - Pyro Intact</h4>";
break;
case 4:
content += "4 Unknown</h4>";
break;
default:
content += "Unknown</h4>";
}
return content; return content;
} }

View file

@ -15,90 +15,203 @@ class BmwPhevHtmlRenderer : public BatteryHtmlRenderer {
" dV</h4>"; " dV</h4>";
content += "<h4>Allowed Discharge Power: " + String(datalayer.battery.status.max_discharge_power_W) + " W</h4>"; content += "<h4>Allowed Discharge Power: " + String(datalayer.battery.status.max_discharge_power_W) + " W</h4>";
content += "<h4>Allowed Charge Power: " + String(datalayer.battery.status.max_charge_power_W) + " W</h4>"; content += "<h4>Allowed Charge Power: " + String(datalayer.battery.status.max_charge_power_W) + " W</h4>";
static const char* balanceText[5] = {"0 Balancing Inactive - Balancing not needed", "1 Balancing Active", content += "<h4>Balancing: ";
"2 Balancing Inactive - Cells not in rest break wait 10mins", switch (datalayer_extended.bmwphev.balancing_status) {
"3 Balancing Inactive", "4 Unknown"}; case 0:
content += "<h4>Balancing: " + String((balanceText[datalayer_extended.bmwphev.balancing_status])) + "</h4>"; content += String("0 Balancing Inactive - Balancing not needed</h4>");
static const char* pyroText[5] = {"0 Value Invalid", "1 Successfully Blown", "2 Disconnected", break;
"3 Not Activated - Pyro Intact", "4 Unknown"}; case 1:
static const char* statusText[16] = { content += String("1 Balancing Active</h4>");
"Not evaluated", "OK", "Error!", "Invalid signal", "", "", "", "", "", "", "", "", "", "", "", ""}; break;
content += "<h4>Interlock: " + String(statusText[datalayer_extended.bmwphev.ST_interlock]) + "</h4>"; case 2:
content += "<h4>Isolation external: " + String(statusText[datalayer_extended.bmwphev.ST_iso_ext]) + "</h4>"; content += String("2 Balancing Inactive - Cells not in rest break wait 10mins</h4>");
content += "<h4>Isolation internal: " + String(statusText[datalayer_extended.bmwphev.ST_iso_int]) + "</h4>"; break;
content += "<h4>Isolation: " + String(statusText[datalayer_extended.bmwphev.ST_isolation]) + "</h4>"; case 3:
content += "<h4>Cooling valve: " + String(statusText[datalayer_extended.bmwphev.ST_valve_cooling]) + "</h4>"; content += String("3 Balancing Inactive</h4>");
content += "<h4>Emergency: " + String(statusText[datalayer_extended.bmwphev.ST_EMG]) + "</h4>"; break;
static const char* prechargeText[16] = {"Not evaluated", case 4:
"Not active, closing not blocked", content += String("4 Unknown</h4>");
"Error precharge blocked", break;
"Invalid signal", default:
"", content += String("Unknown</h4>");
"", }
"", content += "<h4>Interlock: ";
"", switch (datalayer_extended.bmwphev.ST_interlock) {
"", case 0:
"", content += String("Not Evaluated</h4>");
"", break;
"", case 1:
"", content += String("OK</h4>");
"", break;
"", case 2:
""}; content += String("Error! Not seated!</h4>");
content += "<h4>Precharge: " + String(prechargeText[datalayer_extended.bmwphev.ST_precharge]) + break;
"</h4>"; //Still unclear of enum case 3:
static const char* DCSWText[16] = {"Contactors open", content += String("Invalid signal</h4>");
"Precharge ongoing", break;
"Contactors engaged", default:
"Invalid signal", content += String("Unknown</h4>");
"", }
"", content += "<h4>Isolation external: ";
"", switch (datalayer_extended.bmwphev.ST_iso_ext) {
"", case 0:
"", content += String("Not Evaluated</h4>");
"", break;
"", case 1:
"", content += String("OK</h4>");
"", break;
"", case 2:
"", content += String("Error!</h4>");
""}; break;
content += "<h4>Contactor status: " + String(DCSWText[datalayer_extended.bmwphev.ST_DCSW]) + "</h4>"; case 3:
static const char* contText[16] = {"Contactors OK", content += String("Invalid signal</h4>");
"One contactor welded!", break;
"Two contactors welded!", default:
"Invalid signal", content += String("Unknown</h4>");
"", }
"", content += "<h4>Isolation internal: ";
"", switch (datalayer_extended.bmwphev.ST_iso_int) {
"", case 0:
"", content += String("Not Evaluated</h4>");
"", break;
"", case 1:
"", content += String("OK</h4>");
"", break;
"", case 2:
"", content += String("Error!</h4>");
""}; break;
content += "<h4>Contactor weld: " + String(contText[datalayer_extended.bmwphev.ST_WELD]) + "</h4>"; case 3:
static const char* valveText[16] = {"OK", content += String("Invalid signal</h4>");
"Short circuit to GND", break;
"Short circuit to 12V", default:
"Line break", content += String("Unknown</h4>");
"", }
"", content += "<h4>Isolation: ";
"Driver error", switch (datalayer_extended.bmwphev.ST_isolation) {
"", case 0:
"", content += String("Not Evaluated</h4>");
"", break;
"", case 1:
"", content += String("OK</h4>");
"Stuck", break;
"Stuck", case 2:
"", content += String("Error!</h4>");
"Invalid Signal"}; break;
content += case 3:
"<h4>Cold shutoff valve: " + String(valveText[datalayer_extended.bmwphev.ST_cold_shutoff_valve]) + "</h4>"; content += String("Invalid signal</h4>");
break;
default:
content += String("Unknown</h4>");
}
content += "<h4>Cooling valve: ";
switch (datalayer_extended.bmwphev.ST_valve_cooling) {
case 0:
content += String("Not Evaluated</h4>");
break;
case 1:
content += String("OK</h4>");
break;
case 2:
content += String("Error!</h4>");
break;
case 3:
content += String("Invalid signal</h4>");
break;
default:
content += String("Unknown</h4>");
}
content += "<h4>Emergency: ";
switch (datalayer_extended.bmwphev.ST_EMG) {
case 0:
content += String("Not Evaluated</h4>");
break;
case 1:
content += String("OK</h4>");
break;
case 2:
content += String("Error!</h4>");
break;
case 3:
content += String("Invalid signal</h4>");
break;
default:
content += String("Unknown</h4>");
}
content += "<h4>Precharge: ";
switch (datalayer_extended.bmwphev.ST_precharge) {
case 0:
content += String("Not Evaluated</h4>");
break;
case 1:
content += String("Not active, closing not blocked</h4>");
break;
case 2:
content += String("Error precharge blocked</h4>");
break;
case 3:
content += String("Invalid signal</h4>");
break;
default:
content += String("Unknown</h4>"); //Still unclear of enum
}
content += "<h4>Contactor status: ";
switch (datalayer_extended.bmwphev.ST_DCSW) {
case 0:
content += String("Contactors open</h4>");
break;
case 1:
content += String("Precharge ongoing</h4>");
break;
case 2:
content += String("Contactors engaged</h4>");
break;
case 3:
content += String("Invalid signal</h4>");
break;
default:
content += String("Unknown</h4>");
}
content += "<h4>Contactor weld: ";
switch (datalayer_extended.bmwphev.ST_WELD) {
case 0:
content += String("Contactors OK</h4>");
break;
case 1:
content += String("One contactor welded!</h4>");
break;
case 2:
content += String("Two contactors welded!</h4>");
break;
case 3:
content += String("Invalid signal</h4>");
break;
default:
content += String("Unknown</h4>");
}
content += "<h4>Cold shutoff valve: ";
switch (datalayer_extended.bmwphev.ST_cold_shutoff_valve) {
case 0:
content += String("OK</h4>");
break;
case 1:
content += String("Short circuit to GND</h4>");
break;
case 2:
content += String("Short circuit to 12V</h4>");
break;
case 3:
content += String("Line break</h4>");
break;
case 6:
content += String("Driver error</h4>");
break;
case 12:
case 13:
content += String("Stuck</h4>");
break;
default:
content += String("Invalid Signal</h4>");
}
content += content +=
"<h4>Min Cell Voltage Data Age: " + String(datalayer_extended.bmwphev.min_cell_voltage_data_age) + " ms</h4>"; "<h4>Min Cell Voltage Data Age: " + String(datalayer_extended.bmwphev.min_cell_voltage_data_age) + " ms</h4>";
content += content +=

View file

@ -23,10 +23,10 @@ After battery has been unlocked, you can remove the "USE_ESTIMATED_SOC" from the
#define POLL_MAX_CHARGE_POWER 0x000A #define POLL_MAX_CHARGE_POWER 0x000A
#define UNKNOWN_POLL_3 0x000B //0x00B1 (177 interesting!) #define UNKNOWN_POLL_3 0x000B //0x00B1 (177 interesting!)
#define UNKNOWN_POLL_4 0x000E //0x0B27 (2855 interesting!) #define UNKNOWN_POLL_4 0x000E //0x0B27 (2855 interesting!)
#define UNKNOWN_POLL_5 0x000F //0x00237B (9083 interesting!) #define POLL_TOTAL_CHARGED_AH 0x000F
#define UNKNOWN_POLL_6 0x0010 //0x00231B (8987 interesting!) #define POLL_TOTAL_DISCHARGED_AH 0x0010
#define UNKNOWN_POLL_7 0x0011 //0x0E4E (3662 interesting!) #define POLL_TOTAL_CHARGED_KWH 0x0011
#define UNKNOWN_POLL_8 0x0012 //0x0E27 (3623 interesting) #define POLL_TOTAL_DISCHARGED_KWH 0x0012
#define UNKNOWN_POLL_9 0x0004 //0x0034 (52 interesting!) #define UNKNOWN_POLL_9 0x0004 //0x0034 (52 interesting!)
#define UNKNOWN_POLL_10 0x002A //0x5B #define UNKNOWN_POLL_10 0x002A //0x5B
#define UNKNOWN_POLL_11 0x002E //0x08 (probably module number, or cell number?) #define UNKNOWN_POLL_11 0x002E //0x08 (probably module number, or cell number?)
@ -180,6 +180,9 @@ void BydAttoBattery::
datalayer_battery->status.cell_min_voltage_mV = BMS_lowest_cell_voltage_mV; datalayer_battery->status.cell_min_voltage_mV = BMS_lowest_cell_voltage_mV;
datalayer_battery->status.total_discharged_battery_Wh = BMS_total_discharged_kwh * 1000;
datalayer_battery->status.total_charged_battery_Wh = BMS_total_charged_kwh * 1000;
//Map all cell voltages to the global array //Map all cell voltages to the global array
memcpy(datalayer_battery->status.cell_voltages_mV, battery_cellvoltages, CELLCOUNT_EXTENDED * sizeof(uint16_t)); memcpy(datalayer_battery->status.cell_voltages_mV, battery_cellvoltages, CELLCOUNT_EXTENDED * sizeof(uint16_t));
@ -276,10 +279,10 @@ void BydAttoBattery::
datalayer_bydatto->chargePower = BMS_allowed_charge_power; datalayer_bydatto->chargePower = BMS_allowed_charge_power;
datalayer_bydatto->unknown3 = BMS_unknown3; datalayer_bydatto->unknown3 = BMS_unknown3;
datalayer_bydatto->unknown4 = BMS_unknown4; datalayer_bydatto->unknown4 = BMS_unknown4;
datalayer_bydatto->unknown5 = BMS_unknown5; datalayer_bydatto->total_charged_ah = BMS_total_charged_ah;
datalayer_bydatto->unknown6 = BMS_unknown6; datalayer_bydatto->total_discharged_ah = BMS_total_discharged_ah;
datalayer_bydatto->unknown7 = BMS_unknown7; datalayer_bydatto->total_charged_kwh = BMS_total_charged_kwh;
datalayer_bydatto->unknown8 = BMS_unknown8; datalayer_bydatto->total_discharged_kwh = BMS_total_discharged_kwh;
datalayer_bydatto->unknown9 = BMS_unknown9; datalayer_bydatto->unknown9 = BMS_unknown9;
datalayer_bydatto->unknown10 = BMS_unknown10; datalayer_bydatto->unknown10 = BMS_unknown10;
datalayer_bydatto->unknown11 = BMS_unknown11; datalayer_bydatto->unknown11 = BMS_unknown11;
@ -442,17 +445,17 @@ void BydAttoBattery::handle_incoming_can_frame(CAN_frame rx_frame) {
case UNKNOWN_POLL_4: case UNKNOWN_POLL_4:
BMS_unknown4 = (rx_frame.data.u8[5] << 8) | rx_frame.data.u8[4]; BMS_unknown4 = (rx_frame.data.u8[5] << 8) | rx_frame.data.u8[4];
break; break;
case UNKNOWN_POLL_5: case POLL_TOTAL_CHARGED_AH:
BMS_unknown5 = (rx_frame.data.u8[5] << 8) | rx_frame.data.u8[4]; BMS_total_charged_ah = (rx_frame.data.u8[5] << 8) | rx_frame.data.u8[4];
break; break;
case UNKNOWN_POLL_6: case POLL_TOTAL_DISCHARGED_AH:
BMS_unknown6 = (rx_frame.data.u8[5] << 8) | rx_frame.data.u8[4]; BMS_total_discharged_ah = (rx_frame.data.u8[5] << 8) | rx_frame.data.u8[4];
break; break;
case UNKNOWN_POLL_7: case POLL_TOTAL_CHARGED_KWH:
BMS_unknown7 = (rx_frame.data.u8[5] << 8) | rx_frame.data.u8[4]; BMS_total_charged_kwh = (rx_frame.data.u8[5] << 8) | rx_frame.data.u8[4];
break; break;
case UNKNOWN_POLL_8: case POLL_TOTAL_DISCHARGED_KWH:
BMS_unknown8 = (rx_frame.data.u8[5] << 8) | rx_frame.data.u8[4]; BMS_total_discharged_kwh = (rx_frame.data.u8[5] << 8) | rx_frame.data.u8[4];
break; break;
case UNKNOWN_POLL_9: case UNKNOWN_POLL_9:
BMS_unknown9 = (rx_frame.data.u8[5] << 8) | rx_frame.data.u8[4]; BMS_unknown9 = (rx_frame.data.u8[5] << 8) | rx_frame.data.u8[4];
@ -622,26 +625,26 @@ void BydAttoBattery::transmit_can(unsigned long currentMillis) {
case UNKNOWN_POLL_4: case UNKNOWN_POLL_4:
ATTO_3_7E7_POLL.data.u8[2] = (uint8_t)((UNKNOWN_POLL_4 & 0xFF00) >> 8); ATTO_3_7E7_POLL.data.u8[2] = (uint8_t)((UNKNOWN_POLL_4 & 0xFF00) >> 8);
ATTO_3_7E7_POLL.data.u8[3] = (uint8_t)(UNKNOWN_POLL_4 & 0x00FF); ATTO_3_7E7_POLL.data.u8[3] = (uint8_t)(UNKNOWN_POLL_4 & 0x00FF);
poll_state = UNKNOWN_POLL_5; poll_state = POLL_TOTAL_CHARGED_AH;
break; break;
case UNKNOWN_POLL_5: case POLL_TOTAL_CHARGED_AH:
ATTO_3_7E7_POLL.data.u8[2] = (uint8_t)((UNKNOWN_POLL_5 & 0xFF00) >> 8); ATTO_3_7E7_POLL.data.u8[2] = (uint8_t)((POLL_TOTAL_CHARGED_AH & 0xFF00) >> 8);
ATTO_3_7E7_POLL.data.u8[3] = (uint8_t)(UNKNOWN_POLL_5 & 0x00FF); ATTO_3_7E7_POLL.data.u8[3] = (uint8_t)(POLL_TOTAL_CHARGED_AH & 0x00FF);
poll_state = UNKNOWN_POLL_6; poll_state = POLL_TOTAL_DISCHARGED_AH;
break; break;
case UNKNOWN_POLL_6: case POLL_TOTAL_DISCHARGED_AH:
ATTO_3_7E7_POLL.data.u8[2] = (uint8_t)((UNKNOWN_POLL_6 & 0xFF00) >> 8); ATTO_3_7E7_POLL.data.u8[2] = (uint8_t)((POLL_TOTAL_DISCHARGED_AH & 0xFF00) >> 8);
ATTO_3_7E7_POLL.data.u8[3] = (uint8_t)(UNKNOWN_POLL_6 & 0x00FF); ATTO_3_7E7_POLL.data.u8[3] = (uint8_t)(POLL_TOTAL_DISCHARGED_AH & 0x00FF);
poll_state = UNKNOWN_POLL_7; poll_state = POLL_TOTAL_CHARGED_KWH;
break; break;
case UNKNOWN_POLL_7: case POLL_TOTAL_CHARGED_KWH:
ATTO_3_7E7_POLL.data.u8[2] = (uint8_t)((UNKNOWN_POLL_7 & 0xFF00) >> 8); ATTO_3_7E7_POLL.data.u8[2] = (uint8_t)((POLL_TOTAL_CHARGED_KWH & 0xFF00) >> 8);
ATTO_3_7E7_POLL.data.u8[3] = (uint8_t)(UNKNOWN_POLL_7 & 0x00FF); ATTO_3_7E7_POLL.data.u8[3] = (uint8_t)(POLL_TOTAL_CHARGED_KWH & 0x00FF);
poll_state = UNKNOWN_POLL_8; poll_state = POLL_TOTAL_DISCHARGED_KWH;
break; break;
case UNKNOWN_POLL_8: case POLL_TOTAL_DISCHARGED_KWH:
ATTO_3_7E7_POLL.data.u8[2] = (uint8_t)((UNKNOWN_POLL_8 & 0xFF00) >> 8); ATTO_3_7E7_POLL.data.u8[2] = (uint8_t)((POLL_TOTAL_DISCHARGED_KWH & 0xFF00) >> 8);
ATTO_3_7E7_POLL.data.u8[3] = (uint8_t)(UNKNOWN_POLL_8 & 0x00FF); ATTO_3_7E7_POLL.data.u8[3] = (uint8_t)(POLL_TOTAL_DISCHARGED_KWH & 0x00FF);
poll_state = UNKNOWN_POLL_9; poll_state = UNKNOWN_POLL_9;
break; break;
case UNKNOWN_POLL_9: case UNKNOWN_POLL_9:

View file

@ -46,6 +46,7 @@ class BydAttoBattery : public CanBattery {
static constexpr char* Name = "BYD Atto 3"; static constexpr char* Name = "BYD Atto 3";
bool supports_charged_energy() { return true; }
bool supports_reset_crash() { return true; } bool supports_reset_crash() { return true; }
void reset_crash() { datalayer_bydatto->UserRequestCrashReset = true; } void reset_crash() { datalayer_bydatto->UserRequestCrashReset = true; }
@ -117,10 +118,10 @@ class BydAttoBattery : public CanBattery {
uint16_t BMS_allowed_charge_power = 0; uint16_t BMS_allowed_charge_power = 0;
uint16_t BMS_unknown3 = 0; uint16_t BMS_unknown3 = 0;
uint16_t BMS_unknown4 = 0; uint16_t BMS_unknown4 = 0;
uint16_t BMS_unknown5 = 0; uint16_t BMS_total_charged_ah = 0;
uint16_t BMS_unknown6 = 0; uint16_t BMS_total_discharged_ah = 0;
uint16_t BMS_unknown7 = 0; uint16_t BMS_total_charged_kwh = 0;
uint16_t BMS_unknown8 = 0; uint16_t BMS_total_discharged_kwh = 0;
uint16_t BMS_unknown9 = 0; uint16_t BMS_unknown9 = 0;
uint8_t BMS_unknown10 = 0; uint8_t BMS_unknown10 = 0;
uint8_t BMS_unknown11 = 0; uint8_t BMS_unknown11 = 0;

View file

@ -34,10 +34,10 @@ class BydAtto3HtmlRenderer : public BatteryHtmlRenderer {
content += "<h4>Charge power raw: " + String(byd_datalayer->chargePower) + "</h4>"; content += "<h4>Charge power raw: " + String(byd_datalayer->chargePower) + "</h4>";
content += "<h4>Unknown3: " + String(byd_datalayer->unknown3) + "</h4>"; content += "<h4>Unknown3: " + String(byd_datalayer->unknown3) + "</h4>";
content += "<h4>Unknown4: " + String(byd_datalayer->unknown4) + "</h4>"; content += "<h4>Unknown4: " + String(byd_datalayer->unknown4) + "</h4>";
content += "<h4>Unknown5: " + String(byd_datalayer->unknown5) + "</h4>"; content += "<h4>Total charged Ah: " + String(byd_datalayer->total_charged_ah) + "</h4>";
content += "<h4>Unknown6: " + String(byd_datalayer->unknown6) + "</h4>"; content += "<h4>Total discharged Ah: " + String(byd_datalayer->total_discharged_ah) + "</h4>";
content += "<h4>Unknown7: " + String(byd_datalayer->unknown7) + "</h4>"; content += "<h4>Total charged kWh: " + String(byd_datalayer->total_charged_kwh) + "</h4>";
content += "<h4>Unknown8: " + String(byd_datalayer->unknown8) + "</h4>"; content += "<h4>Total discharged kWh: " + String(byd_datalayer->total_discharged_kwh) + "</h4>";
content += "<h4>Unknown9: " + String(byd_datalayer->unknown9) + "</h4>"; content += "<h4>Unknown9: " + String(byd_datalayer->unknown9) + "</h4>";
content += "<h4>Unknown10: " + String(byd_datalayer->unknown10) + "</h4>"; content += "<h4>Unknown10: " + String(byd_datalayer->unknown10) + "</h4>";
content += "<h4>Unknown11: " + String(byd_datalayer->unknown11) + "</h4>"; content += "<h4>Unknown11: " + String(byd_datalayer->unknown11) + "</h4>";

View file

@ -20,7 +20,9 @@ void ChademoBattery::update_values() {
datalayer.battery.status.max_discharge_power_W = datalayer.battery.status.max_discharge_power_W =
(x200_discharge_limits.MaximumDischargeCurrent * x100_chg_lim.MaximumBatteryVoltage); //In Watts, Convert A to P (x200_discharge_limits.MaximumDischargeCurrent * x100_chg_lim.MaximumBatteryVoltage); //In Watts, Convert A to P
if (vehicle_can_received) { // Only update the value sent towards inverter if vehicle is connected (avoids false positive events)
datalayer.battery.status.voltage_dV = get_measured_voltage() * 10; datalayer.battery.status.voltage_dV = get_measured_voltage() * 10;
}
datalayer.battery.info.total_capacity_Wh = (x101_chg_est.RatedBatteryCapacity * 100); datalayer.battery.info.total_capacity_Wh = (x101_chg_est.RatedBatteryCapacity * 100);
//(Added in CHAdeMO v1.0.1), maybe handle hardcoded on lower protocol version? //(Added in CHAdeMO v1.0.1), maybe handle hardcoded on lower protocol version?
@ -201,7 +203,7 @@ void ChademoBattery::process_vehicle_charging_session(CAN_frame rx_frame) {
} }
#ifdef DEBUG_LOG #ifdef DEBUG_LOG
logging.println("UNHANDLED STATE IN process_vehicle_charging_session()"); logging.println("UNHANDLED CHADEMO STATE, try unplugging chademo cable, reboot emulator, and retry!");
#endif #endif
return; return;
} }
@ -228,6 +230,10 @@ void ChademoBattery::process_vehicle_charging_limits(CAN_frame rx_frame) {
if (get_measured_voltage() <= x200_discharge_limits.MinimumDischargeVoltage && CHADEMO_Status > CHADEMO_NEGOTIATE) { if (get_measured_voltage() <= x200_discharge_limits.MinimumDischargeVoltage && CHADEMO_Status > CHADEMO_NEGOTIATE) {
#ifdef DEBUG_LOG #ifdef DEBUG_LOG
logging.println("x200 minimum discharge voltage met or exceeded, stopping."); logging.println("x200 minimum discharge voltage met or exceeded, stopping.");
logging.print("Measured: ");
logging.print(get_measured_voltage());
logging.print("Minimum voltage: ");
logging.print(x200_discharge_limits.MinimumDischargeVoltage);
#endif #endif
CHADEMO_Status = CHADEMO_STOP; CHADEMO_Status = CHADEMO_STOP;
} }

View file

@ -140,7 +140,7 @@ uint8_t CHADEMO_seq = 0x0;
} status; } status;
} s; } s;
uint8_t StateOfCharge = 0; //6 state of charge? uint8_t StateOfCharge = 50; //6 state of charge?
}; };
/* ---------- CHARGING: EVSE Data structures */ /* ---------- CHARGING: EVSE Data structures */
@ -193,7 +193,7 @@ uint8_t CHADEMO_seq = 0x0;
//H200 - Vehicle - Discharge limits //H200 - Vehicle - Discharge limits
struct x200_Vehicle_Discharge_Limits { struct x200_Vehicle_Discharge_Limits {
uint8_t MaximumDischargeCurrent = 0xFF; uint8_t MaximumDischargeCurrent = 0xFF;
uint16_t MinimumDischargeVoltage = 0; uint16_t MinimumDischargeVoltage = 260; //Initialized to a semi-sane value, updates via CAN later
uint16_t MinimumBatteryDischargeLevel = 0; uint16_t MinimumBatteryDischargeLevel = 0;
uint16_t MaxRemainingCapacityForCharging = 0; uint16_t MaxRemainingCapacityForCharging = 0;
}; };

View file

@ -27,68 +27,68 @@ uint16_t CmfaEvBattery::rescale_raw_SOC(uint32_t raw_SOC) {
void CmfaEvBattery:: void CmfaEvBattery::
update_values() { //This function maps all the values fetched via CAN to the correct parameters used for modbus update_values() { //This function maps all the values fetched via CAN to the correct parameters used for modbus
datalayer.battery.status.soh_pptt = (SOH * 100); datalayer_battery->status.soh_pptt = (SOH * 100);
datalayer.battery.status.real_soc = rescale_raw_SOC(SOC_raw); datalayer_battery->status.real_soc = rescale_raw_SOC(SOC_raw);
datalayer.battery.status.current_dA = current * 10; datalayer_battery->status.current_dA = current * 10;
datalayer.battery.status.voltage_dV = average_voltage_of_cells / 100; datalayer_battery->status.voltage_dV = average_voltage_of_cells / 100;
datalayer.battery.info.total_capacity_Wh = 27000; datalayer_battery->info.total_capacity_Wh = 27000;
//Calculate the remaining Wh amount from SOC% and max Wh value. //Calculate the remaining Wh amount from SOC% and max Wh value.
datalayer.battery.status.remaining_capacity_Wh = static_cast<uint32_t>( datalayer_battery->status.remaining_capacity_Wh = static_cast<uint32_t>(
(static_cast<double>(datalayer.battery.status.real_soc) / 10000) * datalayer.battery.info.total_capacity_Wh); (static_cast<double>(datalayer_battery->status.real_soc) / 10000) * datalayer_battery->info.total_capacity_Wh);
datalayer.battery.status.max_discharge_power_W = discharge_power_w; datalayer_battery->status.max_discharge_power_W = discharge_power_w;
datalayer.battery.status.max_charge_power_W = charge_power_w; datalayer_battery->status.max_charge_power_W = charge_power_w;
datalayer.battery.status.temperature_min_dC = (lowest_cell_temperature * 10); datalayer_battery->status.temperature_min_dC = (lowest_cell_temperature * 10);
datalayer.battery.status.temperature_max_dC = (highest_cell_temperature * 10); datalayer_battery->status.temperature_max_dC = (highest_cell_temperature * 10);
datalayer.battery.status.cell_min_voltage_mV = lowest_cell_voltage_mv; datalayer_battery->status.cell_min_voltage_mV = lowest_cell_voltage_mv;
datalayer.battery.status.cell_max_voltage_mV = highest_cell_voltage_mv; datalayer_battery->status.cell_max_voltage_mV = highest_cell_voltage_mv;
//Map all cell voltages to the global array //Map all cell voltages to the global array
memcpy(datalayer.battery.status.cell_voltages_mV, cellvoltages_mv, 72 * sizeof(uint16_t)); memcpy(datalayer_battery->status.cell_voltages_mV, cellvoltages_mv, 72 * sizeof(uint16_t));
if (lead_acid_voltage < 11000) { //11.000V if (lead_acid_voltage < 11000) { //11.000V
set_event(EVENT_12V_LOW, lead_acid_voltage); set_event(EVENT_12V_LOW, lead_acid_voltage);
} }
// Update webserver datalayer // Update webserver datalayer
datalayer_extended.CMFAEV.soc_u = soc_u; datalayer_cmfa->soc_u = soc_u;
datalayer_extended.CMFAEV.soc_z = soc_z; datalayer_cmfa->soc_z = soc_z;
datalayer_extended.CMFAEV.lead_acid_voltage = lead_acid_voltage; datalayer_cmfa->lead_acid_voltage = lead_acid_voltage;
datalayer_extended.CMFAEV.highest_cell_voltage_number = highest_cell_voltage_number; datalayer_cmfa->highest_cell_voltage_number = highest_cell_voltage_number;
datalayer_extended.CMFAEV.lowest_cell_voltage_number = lowest_cell_voltage_number; datalayer_cmfa->lowest_cell_voltage_number = lowest_cell_voltage_number;
datalayer_extended.CMFAEV.max_regen_power = max_regen_power; datalayer_cmfa->max_regen_power = max_regen_power;
datalayer_extended.CMFAEV.max_discharge_power = max_discharge_power; datalayer_cmfa->max_discharge_power = max_discharge_power;
datalayer_extended.CMFAEV.average_temperature = average_temperature; datalayer_cmfa->average_temperature = average_temperature;
datalayer_extended.CMFAEV.minimum_temperature = minimum_temperature; datalayer_cmfa->minimum_temperature = minimum_temperature;
datalayer_extended.CMFAEV.maximum_temperature = maximum_temperature; datalayer_cmfa->maximum_temperature = maximum_temperature;
datalayer_extended.CMFAEV.maximum_charge_power = maximum_charge_power; datalayer_cmfa->maximum_charge_power = maximum_charge_power;
datalayer_extended.CMFAEV.SOH_available_power = SOH_available_power; datalayer_cmfa->SOH_available_power = SOH_available_power;
datalayer_extended.CMFAEV.SOH_generated_power = SOH_generated_power; datalayer_cmfa->SOH_generated_power = SOH_generated_power;
datalayer_extended.CMFAEV.cumulative_energy_when_discharging = cumulative_energy_when_discharging; datalayer_cmfa->cumulative_energy_when_discharging = cumulative_energy_when_discharging;
datalayer_extended.CMFAEV.cumulative_energy_when_charging = cumulative_energy_when_charging; datalayer_cmfa->cumulative_energy_when_charging = cumulative_energy_when_charging;
datalayer_extended.CMFAEV.cumulative_energy_in_regen = cumulative_energy_in_regen; datalayer_cmfa->cumulative_energy_in_regen = cumulative_energy_in_regen;
datalayer_extended.CMFAEV.soh_average = soh_average; datalayer_cmfa->soh_average = soh_average;
} }
void CmfaEvBattery::handle_incoming_can_frame(CAN_frame rx_frame) { void CmfaEvBattery::handle_incoming_can_frame(CAN_frame rx_frame) {
switch (rx_frame.ID) { //These frames are transmitted by the battery switch (rx_frame.ID) { //These frames are transmitted by the battery
case 0x127: //10ms , Same structure as old Zoe 0x155 message! case 0x127: //10ms , Same structure as old Zoe 0x155 message!
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE; datalayer_battery->status.CAN_battery_still_alive = CAN_STILL_ALIVE;
current = (((((rx_frame.data.u8[1] & 0x0F) << 8) | rx_frame.data.u8[2]) * 0.25) - 500); current = (((((rx_frame.data.u8[1] & 0x0F) << 8) | rx_frame.data.u8[2]) * 0.25) - 500);
SOC_raw = ((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]); SOC_raw = ((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]);
break; break;
case 0x3D6: //100ms, Same structure as old Zoe 0x424 message! case 0x3D6: //100ms, Same structure as old Zoe 0x424 message!
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE; datalayer_battery->status.CAN_battery_still_alive = CAN_STILL_ALIVE;
charge_power_w = rx_frame.data.u8[2] * 500; charge_power_w = rx_frame.data.u8[2] * 500;
discharge_power_w = rx_frame.data.u8[3] * 500; discharge_power_w = rx_frame.data.u8[3] * 500;
lowest_cell_temperature = (rx_frame.data.u8[4] - 40); lowest_cell_temperature = (rx_frame.data.u8[4] - 40);
@ -97,34 +97,34 @@ void CmfaEvBattery::handle_incoming_can_frame(CAN_frame rx_frame) {
highest_cell_temperature = (rx_frame.data.u8[7] - 40); highest_cell_temperature = (rx_frame.data.u8[7] - 40);
break; break;
case 0x3D7: //100ms case 0x3D7: //100ms
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE; datalayer_battery->status.CAN_battery_still_alive = CAN_STILL_ALIVE;
pack_voltage = ((rx_frame.data.u8[6] << 4 | (rx_frame.data.u8[5] & 0x0F))); pack_voltage = ((rx_frame.data.u8[6] << 4 | (rx_frame.data.u8[5] & 0x0F)));
break; break;
case 0x3D8: //100ms case 0x3D8: //100ms
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE; datalayer_battery->status.CAN_battery_still_alive = CAN_STILL_ALIVE;
//counter_3D8 = rx_frame.data.u8[3]; //? //counter_3D8 = rx_frame.data.u8[3]; //?
//CRC_3D8 = rx_frame.data.u8[4]; //? //CRC_3D8 = rx_frame.data.u8[4]; //?
break; break;
case 0x43C: //100ms case 0x43C: //100ms
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE; datalayer_battery->status.CAN_battery_still_alive = CAN_STILL_ALIVE;
heartbeat2 = rx_frame.data.u8[2]; //Alternates between 0x55 and 0xAA every 5th frame heartbeat2 = rx_frame.data.u8[2]; //Alternates between 0x55 and 0xAA every 5th frame
break; break;
case 0x431: //100ms case 0x431: //100ms
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE; datalayer_battery->status.CAN_battery_still_alive = CAN_STILL_ALIVE;
//byte0 9C always //byte0 9C always
//byte1 40 always //byte1 40 always
break; break;
case 0x5A9: case 0x5A9:
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE; datalayer_battery->status.CAN_battery_still_alive = CAN_STILL_ALIVE;
break; break;
case 0x5AB: case 0x5AB:
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE; datalayer_battery->status.CAN_battery_still_alive = CAN_STILL_ALIVE;
break; break;
case 0x5C8: case 0x5C8:
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE; datalayer_battery->status.CAN_battery_still_alive = CAN_STILL_ALIVE;
break; break;
case 0x5E1: case 0x5E1:
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE; datalayer_battery->status.CAN_battery_still_alive = CAN_STILL_ALIVE;
break; break;
case 0x7BB: // Reply from battery case 0x7BB: // Reply from battery
if (rx_frame.data.u8[0] == 0x10) { //PID header if (rx_frame.data.u8[0] == 0x10) { //PID header
@ -943,10 +943,10 @@ void CmfaEvBattery::setup(void) { // Performs one time setup at startup
strncpy(datalayer.system.info.battery_protocol, Name, 63); strncpy(datalayer.system.info.battery_protocol, Name, 63);
datalayer.system.info.battery_protocol[63] = '\0'; datalayer.system.info.battery_protocol[63] = '\0';
datalayer.system.status.battery_allows_contactor_closing = true; datalayer.system.status.battery_allows_contactor_closing = true;
datalayer.battery.info.number_of_cells = 72; datalayer_battery->info.number_of_cells = 72;
datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_DV; 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.min_design_voltage_dV = MIN_PACK_VOLTAGE_DV;
datalayer.battery.info.max_cell_voltage_mV = MAX_CELL_VOLTAGE_MV; 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.min_cell_voltage_mV = MIN_CELL_VOLTAGE_MV;
datalayer.battery.info.max_cell_voltage_deviation_mV = MAX_CELL_DEVIATION_MV; datalayer_battery->info.max_cell_voltage_deviation_mV = MAX_CELL_DEVIATION_MV;
} }

View file

@ -11,6 +11,23 @@
class CmfaEvBattery : public CanBattery { class CmfaEvBattery : public CanBattery {
public: public:
// Use this constructor for the second battery.
CmfaEvBattery(DATALAYER_BATTERY_TYPE* datalayer_ptr, DATALAYER_INFO_CMFAEV* extended, CAN_Interface targetCan)
: CanBattery(targetCan) {
datalayer_battery = datalayer_ptr;
allows_contactor_closing = nullptr;
datalayer_cmfa = extended;
average_voltage_of_cells = 0;
}
// Use the default constructor to create the first or single battery.
CmfaEvBattery() {
datalayer_battery = &datalayer.battery;
allows_contactor_closing = &datalayer.system.status.battery_allows_contactor_closing;
datalayer_cmfa = &datalayer_extended.CMFAEV;
}
virtual void setup(void); virtual void setup(void);
virtual void handle_incoming_can_frame(CAN_frame rx_frame); virtual void handle_incoming_can_frame(CAN_frame rx_frame);
virtual void update_values(); virtual void update_values();
@ -22,6 +39,12 @@ class CmfaEvBattery : public CanBattery {
private: private:
CmfaEvHtmlRenderer renderer; CmfaEvHtmlRenderer renderer;
DATALAYER_BATTERY_TYPE* datalayer_battery;
DATALAYER_INFO_CMFAEV* datalayer_cmfa;
// If not null, this battery decides when the contactor can be closed and writes the value here.
bool* allows_contactor_closing;
uint16_t rescale_raw_SOC(uint32_t raw_SOC); uint16_t rescale_raw_SOC(uint32_t raw_SOC);
static const int MAX_PACK_VOLTAGE_DV = 3040; // 5000 = 500.0V static const int MAX_PACK_VOLTAGE_DV = 3040; // 5000 = 500.0V

View file

@ -7,8 +7,7 @@
/* TODO: /* TODO:
This integration is still ongoing. Here is what still needs to be done in order to use this battery type This integration is still ongoing. Here is what still needs to be done in order to use this battery type
- Disable the isolation resistance requirement that opens contactors after 30s - Disable the isolation resistance requirement that opens contactors after 30s under load. Factory mode?
- Battery says it might need 37E and 485, but no logs of this?
*/ */
/* Do not change code below unless you are sure what you are doing */ /* Do not change code below unless you are sure what you are doing */
@ -20,7 +19,7 @@ void EcmpBattery::update_values() {
datalayer.battery.status.voltage_dV = battery_voltage * 10; datalayer.battery.status.voltage_dV = battery_voltage * 10;
datalayer.battery.status.current_dA = battery_current * 10; datalayer.battery.status.current_dA = -(battery_current * 10);
datalayer.battery.status.active_power_W = //Power in watts, Negative = charging batt datalayer.battery.status.active_power_W = //Power in watts, Negative = charging batt
((datalayer.battery.status.voltage_dV * datalayer.battery.status.current_dA) / 100); ((datalayer.battery.status.voltage_dV * datalayer.battery.status.current_dA) / 100);
@ -374,19 +373,31 @@ void EcmpBattery::handle_incoming_can_frame(CAN_frame rx_frame) {
if (datalayer_extended.stellantisECMP.UserRequestDisableIsoMonitoring) { if (datalayer_extended.stellantisECMP.UserRequestDisableIsoMonitoring) {
if ((rx_frame.data.u8[0] == 0x06) && (rx_frame.data.u8[1] == 0x50) && (rx_frame.data.u8[2] == 0x03)) { if ((rx_frame.data.u8[0] == 0x06) && (rx_frame.data.u8[1] == 0x50) && (rx_frame.data.u8[2] == 0x03)) {
//06,50,03,00,C8,00,14,00, //06,50,03,00,C8,00,14,00,
DisableIsoMonitoringStatemachine = 2; //Send ECMP_FACTORY_MODE_ACTIVATION next loop DisableIsoMonitoringStatemachine = 2; //Send ECMP_ACK_MESSAGE (02 3e 00)
} }
//if ((rx_frame.data.u8[0] == 0x03) && (rx_frame.data.u8[1] == 0x7F) && (rx_frame.data.u8[2] == 0x2E)) { if ((rx_frame.data.u8[0] == 0x02) && (rx_frame.data.u8[1] == 0x7E) && (rx_frame.data.u8[2] == 0x00)) {
if (DisableIsoMonitoringStatemachine == 3) { //Expected 02,7E,00
DisableIsoMonitoringStatemachine = 4; DisableIsoMonitoringStatemachine = 4; //Send ECMP_FACTORY_MODE_ACTIVATION next loop
} }
//if ((rx_frame.data.u8[0] == 0x03) && (rx_frame.data.u8[1] == 0x7F) && (rx_frame.data.u8[2] == 0x2E)) { if ((rx_frame.data.u8[0] == 0x03) && (rx_frame.data.u8[1] == 0x6E) && (rx_frame.data.u8[2] == 0xD9)) {
if (DisableIsoMonitoringStatemachine == 5) { //Factory mode ENTRY: 2E.D9.00.01
DisableIsoMonitoringStatemachine = 6; DisableIsoMonitoringStatemachine = 6; //Send ECMP_DISABLE_ISOLATION_REQ next loop
} }
//if ((rx_frame.data.u8[0] == 0x03) && (rx_frame.data.u8[1] == 0x7F) && (rx_frame.data.u8[2] == 0x31)) { if ((rx_frame.data.u8[0] == 0x03) && (rx_frame.data.u8[1] == 0x7F) && (rx_frame.data.u8[2] == 0x2E)) {
if (DisableIsoMonitoringStatemachine == 7) { //Factory mode fails to enter with 7F
//UNKNOWN? 03,7F,31,24 (or 7F?) set_event(EVENT_PID_FAILED, rx_frame.data.u8[2]);
DisableIsoMonitoringStatemachine =
6; //Send ECMP_DISABLE_ISOLATION_REQ next loop (pointless, since it will fail)
}
if ((rx_frame.data.u8[0] == 0x04) && (rx_frame.data.u8[1] == 0x31) && (rx_frame.data.u8[2] == 0x02)) {
//Disable isolation successful 04 31 02 df e1
DisableIsoMonitoringStatemachine = COMPLETED_STATE;
datalayer_extended.stellantisECMP.UserRequestDisableIsoMonitoring = false;
timeSpentDisableIsoMonitoring = COMPLETED_STATE;
}
if ((rx_frame.data.u8[0] == 0x03) && (rx_frame.data.u8[1] == 0x7F) && (rx_frame.data.u8[2] == 0x31)) {
//Disable Isolation fails to enter with 7F
set_event(EVENT_PID_FAILED, rx_frame.data.u8[2]);
DisableIsoMonitoringStatemachine = COMPLETED_STATE; DisableIsoMonitoringStatemachine = COMPLETED_STATE;
datalayer_extended.stellantisECMP.UserRequestDisableIsoMonitoring = false; datalayer_extended.stellantisECMP.UserRequestDisableIsoMonitoring = false;
timeSpentDisableIsoMonitoring = COMPLETED_STATE; timeSpentDisableIsoMonitoring = COMPLETED_STATE;
@ -534,10 +545,10 @@ void EcmpBattery::handle_incoming_can_frame(CAN_frame rx_frame) {
pid_avg_cell_voltage = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]; pid_avg_cell_voltage = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
break; break;
case PID_CURRENT: case PID_CURRENT:
pid_current = (((rx_frame.data.u8[4] << 24) | (rx_frame.data.u8[5] << 16) | (rx_frame.data.u8[6] << 8) | pid_current = -(((rx_frame.data.u8[4] << 24) | (rx_frame.data.u8[5] << 16) | (rx_frame.data.u8[6] << 8) |
rx_frame.data.u8[7]) - rx_frame.data.u8[7]) -
76800) * 76800) *
10; 20;
break; break;
case PID_INSULATION_NEG: case PID_INSULATION_NEG:
pid_insulation_res_neg = ((rx_frame.data.u8[4] << 24) | (rx_frame.data.u8[5] << 16) | pid_insulation_res_neg = ((rx_frame.data.u8[4] << 24) | (rx_frame.data.u8[5] << 16) |
@ -578,7 +589,7 @@ void EcmpBattery::handle_incoming_can_frame(CAN_frame rx_frame) {
pid_lowest_cell_voltage_num = (rx_frame.data.u8[4]); pid_lowest_cell_voltage_num = (rx_frame.data.u8[4]);
break; break;
case PID_SUM_OF_CELLS: case PID_SUM_OF_CELLS:
pid_sum_of_cells = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]; pid_sum_of_cells = ((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) / 2;
break; break;
case PID_CELL_MIN_CAPACITY: case PID_CELL_MIN_CAPACITY:
pid_cell_min_capacity = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]; pid_cell_min_capacity = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
@ -845,15 +856,15 @@ void EcmpBattery::transmit_can(unsigned long currentMillis) {
DisableIsoMonitoringStatemachine = 1; DisableIsoMonitoringStatemachine = 1;
} }
if (DisableIsoMonitoringStatemachine == 2) { if (DisableIsoMonitoringStatemachine == 2) {
transmit_can_frame(&ECMP_FACTORY_MODE_ACTIVATION_NEW, can_config.battery); transmit_can_frame(&ECMP_ACK_MESSAGE, can_config.battery);
DisableIsoMonitoringStatemachine = 3; DisableIsoMonitoringStatemachine = 3;
} }
if (DisableIsoMonitoringStatemachine == 4) { if (DisableIsoMonitoringStatemachine == 4) {
transmit_can_frame(&ECMP_DISABLE_ISOLATION_REQ, can_config.battery); transmit_can_frame(&ECMP_FACTORY_MODE_ACTIVATION, can_config.battery);
DisableIsoMonitoringStatemachine = 5; DisableIsoMonitoringStatemachine = 5;
} }
if (DisableIsoMonitoringStatemachine == 6) { if (DisableIsoMonitoringStatemachine == 6) {
transmit_can_frame(&ECMP_FACTORY_MODE_ACTIVATION, can_config.battery); transmit_can_frame(&ECMP_DISABLE_ISOLATION_REQ, can_config.battery);
DisableIsoMonitoringStatemachine = 7; DisableIsoMonitoringStatemachine = 7;
} }
timeSpentDisableIsoMonitoring++; timeSpentDisableIsoMonitoring++;
@ -1321,10 +1332,12 @@ void EcmpBattery::transmit_can(unsigned long currentMillis) {
transmit_can_frame(&ECMP_0C5, can_config.battery); //DC2_0C5 transmit_can_frame(&ECMP_0C5, can_config.battery); //DC2_0C5
transmit_can_frame(&ECMP_17B, can_config.battery); //VCU_PCANInfo_17B transmit_can_frame(&ECMP_17B, can_config.battery); //VCU_PCANInfo_17B
transmit_can_frame(&ECMP_0F2, can_config.battery); //CtrlMCU1_0F2 transmit_can_frame(&ECMP_0F2, can_config.battery); //CtrlMCU1_0F2
if (simulateEntireCar) {
transmit_can_frame(&ECMP_111, can_config.battery); transmit_can_frame(&ECMP_111, can_config.battery);
transmit_can_frame(&ECMP_110, can_config.battery); transmit_can_frame(&ECMP_110, can_config.battery);
transmit_can_frame(&ECMP_114, can_config.battery); transmit_can_frame(&ECMP_114, can_config.battery);
} }
}
// Send 20ms periodic CAN Message simulating the car still being attached // Send 20ms periodic CAN Message simulating the car still being attached
if (currentMillis - previousMillis20 >= INTERVAL_20_MS) { if (currentMillis - previousMillis20 >= INTERVAL_20_MS) {
@ -1355,7 +1368,7 @@ void EcmpBattery::transmit_can(unsigned long currentMillis) {
ECMP_27A.data = {0x4F, 0x58, 0x00, 0x02, 0x24, 0x00, 0x00, 0x00}; ECMP_27A.data = {0x4F, 0x58, 0x00, 0x02, 0x24, 0x00, 0x00, 0x00};
} }
transmit_can_frame(&ECMP_230, can_config.battery); //OBC3_230 transmit_can_frame(&ECMP_230, can_config.battery); //OBC3_230
transmit_can_frame(&ECMP_27A, can_config.battery); //PSA specific VCU message (VCU_BSI_Wakeup_27A) transmit_can_frame(&ECMP_27A, can_config.battery); //VCU_BSI_Wakeup_27A
} }
// Send 100ms periodic CAN Message simulating the car still being attached // Send 100ms periodic CAN Message simulating the car still being attached
if (currentMillis - previousMillis100 >= INTERVAL_100_MS) { if (currentMillis - previousMillis100 >= INTERVAL_100_MS) {
@ -1463,21 +1476,24 @@ void EcmpBattery::transmit_can(unsigned long currentMillis) {
transmit_can_frame(&ECMP_345, can_config.battery); //DC1_345 transmit_can_frame(&ECMP_345, can_config.battery); //DC1_345
transmit_can_frame(&ECMP_3A2, can_config.battery); //OBC2_3A2 transmit_can_frame(&ECMP_3A2, can_config.battery); //OBC2_3A2
transmit_can_frame(&ECMP_3A3, can_config.battery); //OBC1_3A3 transmit_can_frame(&ECMP_3A3, can_config.battery); //OBC1_3A3
transmit_can_frame(&ECMP_010, can_config.battery); //VCU_BCM_Crash
if (simulateEntireCar) {
transmit_can_frame(&ECMP_31E, can_config.battery); transmit_can_frame(&ECMP_31E, can_config.battery);
transmit_can_frame(&ECMP_383, can_config.battery); transmit_can_frame(&ECMP_383, can_config.battery);
transmit_can_frame(&ECMP_010, can_config.battery);
transmit_can_frame(&ECMP_0A6, can_config.battery); //Not in all logs transmit_can_frame(&ECMP_0A6, can_config.battery); //Not in all logs
transmit_can_frame(&ECMP_37F, can_config.battery); //Seems to be temperatures of some sort transmit_can_frame(&ECMP_37F, can_config.battery); //Seems to be temperatures of some sort
transmit_can_frame(&ECMP_372, can_config.battery); transmit_can_frame(&ECMP_372, can_config.battery);
transmit_can_frame(&ECMP_351, can_config.battery); transmit_can_frame(&ECMP_351, can_config.battery);
transmit_can_frame(&ECMP_31D, can_config.battery); transmit_can_frame(&ECMP_31D, can_config.battery);
} }
}
// Send 500ms periodic CAN Message simulating the car still being attached // Send 500ms periodic CAN Message simulating the car still being attached
if (currentMillis - previousMillis500 >= INTERVAL_500_MS) { if (currentMillis - previousMillis500 >= INTERVAL_500_MS) {
previousMillis500 = currentMillis; previousMillis500 = currentMillis;
if (simulateEntireCar) {
transmit_can_frame(&ECMP_0AE, can_config.battery); transmit_can_frame(&ECMP_0AE, can_config.battery);
} }
}
// Send 1s CAN Message // Send 1s CAN Message
if (currentMillis - previousMillis1000 >= INTERVAL_1_S) { if (currentMillis - previousMillis1000 >= INTERVAL_1_S) {
previousMillis1000 = currentMillis; previousMillis1000 = currentMillis;
@ -1499,21 +1515,24 @@ void EcmpBattery::transmit_can(unsigned long currentMillis) {
ECMP_552.data.u8[2] = ((ticks_552 & 0x0000FF00) >> 8); ECMP_552.data.u8[2] = ((ticks_552 & 0x0000FF00) >> 8);
ECMP_552.data.u8[3] = (ticks_552 & 0x000000FF); ECMP_552.data.u8[3] = (ticks_552 & 0x000000FF);
transmit_can_frame(&ECMP_439, can_config.battery); //PSA Specific? Not in all logs transmit_can_frame(&ECMP_439, can_config.battery); //OBC4
transmit_can_frame(&ECMP_552, can_config.battery); //VCU_552 timetracking
if (simulateEntireCar) {
transmit_can_frame(&ECMP_486, can_config.battery); //Not in all logs transmit_can_frame(&ECMP_486, can_config.battery); //Not in all logs
transmit_can_frame(&ECMP_041, can_config.battery); //Not in all logs transmit_can_frame(&ECMP_041, can_config.battery); //Not in all logs
transmit_can_frame(&ECMP_786, can_config.battery); //Not in all logs transmit_can_frame(&ECMP_786, can_config.battery); //Not in all logs
transmit_can_frame(&ECMP_591, can_config.battery); //Not in all logs transmit_can_frame(&ECMP_591, can_config.battery); //Not in all logs
transmit_can_frame(&ECMP_552, can_config.battery); //VCU_552 timetracking
transmit_can_frame(&ECMP_794, can_config.battery); //Not in all logs transmit_can_frame(&ECMP_794, can_config.battery); //Not in all logs
} }
}
// Send 5s periodic CAN Message simulating the car still being attached // Send 5s periodic CAN Message simulating the car still being attached
if (currentMillis - previousMillis5000 >= INTERVAL_5_S) { if (currentMillis - previousMillis5000 >= INTERVAL_5_S) {
previousMillis5000 = currentMillis; previousMillis5000 = currentMillis;
if (simulateEntireCar) {
transmit_can_frame(&ECMP_55F, can_config.battery); transmit_can_frame(&ECMP_55F, can_config.battery);
} }
} }
}
void EcmpBattery::setup(void) { // Performs one time setup at startup void EcmpBattery::setup(void) { // Performs one time setup at startup
strncpy(datalayer.system.info.battery_protocol, Name, 63); strncpy(datalayer.system.info.battery_protocol, Name, 63);

View file

@ -39,6 +39,8 @@ class EcmpBattery : public CanBattery {
static const int MAX_CELL_DEVIATION_MV = 100; static const int MAX_CELL_DEVIATION_MV = 100;
static const int MAX_CELL_VOLTAGE_MV = 4250; static const int MAX_CELL_VOLTAGE_MV = 4250;
static const int MIN_CELL_VOLTAGE_MV = 2700; static const int MIN_CELL_VOLTAGE_MV = 2700;
bool simulateEntireCar =
false; //Set this to true to simulate the whole car (useful for when using external diagnostic tools)
static const int NOT_SAMPLED_YET = 255; static const int NOT_SAMPLED_YET = 255;
static const int COMPLETED_STATE = 0; static const int COMPLETED_STATE = 0;
bool battery_RelayOpenRequest = false; bool battery_RelayOpenRequest = false;
@ -219,7 +221,7 @@ class EcmpBattery : public CanBattery {
uint16_t poll_state = PID_WELD_CHECK; uint16_t poll_state = PID_WELD_CHECK;
uint16_t incoming_poll = 0; uint16_t incoming_poll = 0;
CAN_frame ECMP_010 = {.FD = false, .ext_ID = false, .DLC = 1, .ID = 0x010, .data = {0xB4}}; CAN_frame ECMP_010 = {.FD = false, .ext_ID = false, .DLC = 1, .ID = 0x010, .data = {0xB4}}; //VCU_BCM_Crash 100ms
CAN_frame ECMP_041 = {.FD = false, .ext_ID = false, .DLC = 1, .ID = 0x041, .data = {0x00}}; CAN_frame ECMP_041 = {.FD = false, .ext_ID = false, .DLC = 1, .ID = 0x041, .data = {0x00}};
CAN_frame ECMP_0A6 = {.FD = false, CAN_frame ECMP_0A6 = {.FD = false,
.ext_ID = false, .ext_ID = false,
@ -335,7 +337,7 @@ class EcmpBattery : public CanBattery {
.DLC = 8, .DLC = 8,
.ID = 0x3A3, .ID = 0x3A3,
.data = {0x4A, 0x4A, 0x40, 0x00, 0x00, 0x08, 0x00, 0x0F}}; .data = {0x4A, 0x4A, 0x40, 0x00, 0x00, 0x08, 0x00, 0x0F}};
CAN_frame ECMP_439 = {.FD = false, //??? 1s periodic (Perfectly emulated in Battery-Emulator) CAN_frame ECMP_439 = {.FD = false, //OBC4 1s periodic (Perfectly emulated in Battery-Emulator)
.ext_ID = false, //Same content always, fully static .ext_ID = false, //Same content always, fully static
.DLC = 8, .DLC = 8,
.ID = 0x439, .ID = 0x439,
@ -414,16 +416,12 @@ class EcmpBattery : public CanBattery {
.DLC = 5, .DLC = 5,
.ID = 0x6B4, .ID = 0x6B4,
.data = {0x04, 0x2E, 0xD9, 0x00, 0x01}}; .data = {0x04, 0x2E, 0xD9, 0x00, 0x01}};
CAN_frame ECMP_FACTORY_MODE_ACTIVATION_NEW = {.FD = false,
.ext_ID = false,
.DLC = 4,
.ID = 0x6B4,
.data = {0x04, 0x2E, 0x19, 0x01}};
CAN_frame ECMP_DISABLE_ISOLATION_REQ = {.FD = false, CAN_frame ECMP_DISABLE_ISOLATION_REQ = {.FD = false,
.ext_ID = false, .ext_ID = false,
.DLC = 5, .DLC = 5,
.ID = 0x6B4, .ID = 0x6B4,
.data = {0x04, 0x31, 0x02, 0xDF, 0xE1}}; .data = {0x04, 0x31, 0x02, 0xDF, 0xE1}};
CAN_frame ECMP_ACK_MESSAGE = {.FD = false, .ext_ID = false, .DLC = 3, .ID = 0x6B4, .data = {0x02, 0x3E, 0x00}};
uint8_t data_010_CRC[8] = {0xB4, 0x96, 0x78, 0x5A, 0x3C, 0x1E, 0xF0, 0xD2}; uint8_t data_010_CRC[8] = {0xB4, 0x96, 0x78, 0x5A, 0x3C, 0x1E, 0xF0, 0xD2};
uint8_t data_3A2_CRC[16] = {0x0C, 0x1B, 0x2A, 0x39, 0x48, 0x57, uint8_t data_3A2_CRC[16] = {0x0C, 0x1B, 0x2A, 0x39, 0x48, 0x57,
0x66, 0x75, 0x84, 0x93, 0xA2, 0xB1}; // NOTE. Changes on BMS state 0x66, 0x75, 0x84, 0x93, 0xA2, 0xB1}; // NOTE. Changes on BMS state

View file

@ -111,11 +111,9 @@ void KiaHyundai64Battery::
} }
void KiaHyundai64Battery::update_number_of_cells() { void KiaHyundai64Battery::update_number_of_cells() {
//If we have cell values and number_of_cells not initialized yet
if (cellvoltages_mv[0] > 0 && datalayer_battery->info.number_of_cells == 0) {
// Check if we have 98S or 90S battery. If the 98th cell is valid range, we are on a 98S battery // Check if we have 98S or 90S battery. If the 98th cell is valid range, we are on a 98S battery
if ((datalayer_battery->status.cell_voltages_mV[97] > 2000) && if ((datalayer_battery->status.cell_voltages_mV[97] > 2000) &&
(datalayer_battery->status.cell_voltages_mV[97] < 4300)) { (datalayer_battery->status.cell_voltages_mV[97] < 4500)) {
datalayer_battery->info.number_of_cells = 98; datalayer_battery->info.number_of_cells = 98;
datalayer_battery->info.max_design_voltage_dV = MAX_PACK_VOLTAGE_98S_DV; datalayer_battery->info.max_design_voltage_dV = MAX_PACK_VOLTAGE_98S_DV;
datalayer_battery->info.min_design_voltage_dV = MIN_PACK_VOLTAGE_98S_DV; datalayer_battery->info.min_design_voltage_dV = MIN_PACK_VOLTAGE_98S_DV;
@ -127,7 +125,6 @@ void KiaHyundai64Battery::update_number_of_cells() {
datalayer_battery->info.total_capacity_Wh = 40000; datalayer_battery->info.total_capacity_Wh = 40000;
} }
} }
}
void KiaHyundai64Battery::handle_incoming_can_frame(CAN_frame rx_frame) { void KiaHyundai64Battery::handle_incoming_can_frame(CAN_frame rx_frame) {
switch (rx_frame.ID) { switch (rx_frame.ID) {
@ -364,6 +361,7 @@ void KiaHyundai64Battery::handle_incoming_can_frame(CAN_frame rx_frame) {
} }
break; break;
case 0x26: //Sixth datarow in PID group case 0x26: //Sixth datarow in PID group
if (poll_data_pid == 5) {
//We have read all cells, check that content is valid: //We have read all cells, check that content is valid:
for (uint8_t i = 85; i < 97; ++i) { for (uint8_t i = 85; i < 97; ++i) {
if (cellvoltages_mv[i] < 300) { // Zero the value if it's below 300 if (cellvoltages_mv[i] < 300) { // Zero the value if it's below 300
@ -374,6 +372,7 @@ void KiaHyundai64Battery::handle_incoming_can_frame(CAN_frame rx_frame) {
memcpy(datalayer_battery->status.cell_voltages_mV, cellvoltages_mv, 98 * sizeof(uint16_t)); memcpy(datalayer_battery->status.cell_voltages_mV, cellvoltages_mv, 98 * sizeof(uint16_t));
//Update number of cells //Update number of cells
update_number_of_cells(); update_number_of_cells();
}
break; break;
case 0x27: //Seventh datarow in PID group case 0x27: //Seventh datarow in PID group
if (poll_data_pid == 1) { if (poll_data_pid == 1) {

View file

@ -20,119 +20,119 @@ class MebHtmlRenderer : public BatteryHtmlRenderer {
content += "<h4>HVIL status: "; content += "<h4>HVIL status: ";
switch (datalayer_extended.meb.HVIL) { switch (datalayer_extended.meb.HVIL) {
case 0: case 0:
content += String("Init"); content += "Init";
break; break;
case 1: case 1:
content += String("Closed"); content += "Closed";
break; break;
case 2: case 2:
content += String("Open!"); content += "Open!";
break; break;
case 3: case 3:
content += String("Fault"); content += "Fault";
break; break;
default: default:
content += String("?"); content += "?";
} }
content += "</h4><h4>KL30C status: "; content += "</h4><h4>KL30C status: ";
switch (datalayer_extended.meb.BMS_Kl30c_Status) { switch (datalayer_extended.meb.BMS_Kl30c_Status) {
case 0: case 0:
content += String("Init"); content += "Init";
break; break;
case 1: case 1:
content += String("Closed"); content += "Closed";
break; break;
case 2: case 2:
content += String("Open!"); content += "Open!";
break; break;
case 3: case 3:
content += String("Fault"); content += "Fault";
break; break;
default: default:
content += String("?"); content += "?";
} }
content += "</h4><h4>BMS mode: "; content += "</h4><h4>BMS mode: ";
switch (datalayer_extended.meb.BMS_mode) { switch (datalayer_extended.meb.BMS_mode) {
case 0: case 0:
content += String("HV inactive"); content += "HV inactive";
break; break;
case 1: case 1:
content += String("HV active"); content += "HV active";
break; break;
case 2: case 2:
content += String("Balancing"); content += "Balancing";
break; break;
case 3: case 3:
content += String("Extern charging"); content += "Extern charging";
break; break;
case 4: case 4:
content += String("AC charging"); content += "AC charging";
break; break;
case 5: case 5:
content += String("Battery error"); content += "Battery error";
break; break;
case 6: case 6:
content += String("DC charging"); content += "DC charging";
break; break;
case 7: case 7:
content += String("Init"); content += "Init";
break; break;
default: default:
content += String("?"); content += "?";
} }
content += String("</h4><h4>Charging: ") + (datalayer_extended.meb.charging_active ? "active" : "not active"); content += String("</h4><h4>Charging: ") + (datalayer_extended.meb.charging_active ? "active" : "not active");
content += String("</h4><h4>Balancing: "); content += String("</h4><h4>Balancing: ");
switch (datalayer_extended.meb.balancing_active) { switch (datalayer_extended.meb.balancing_active) {
case 0: case 0:
content += String("init"); content += "init";
break; break;
case 1: case 1:
content += String("active"); content += "active";
break; break;
case 2: case 2:
content += String("inactive"); content += "inactive";
break; break;
default: default:
content += String("?"); content += "?";
} }
content += content +=
String("</h4><h4>Slow charging: ") + (datalayer_extended.meb.balancing_request ? "requested" : "not requested"); String("</h4><h4>Slow charging: ") + (datalayer_extended.meb.balancing_request ? "requested" : "not requested");
content += "</h4><h4>Diagnostic: "; content += "</h4><h4>Diagnostic: ";
switch (datalayer_extended.meb.battery_diagnostic) { switch (datalayer_extended.meb.battery_diagnostic) {
case 0: case 0:
content += String("Init"); content += "Init";
break; break;
case 1: case 1:
content += String("Battery display"); content += "Battery display";
break; break;
case 4: case 4:
content += String("Battery display OK"); content += "Battery display OK";
break; break;
case 6: case 6:
content += String("Battery display check"); content += "Battery display check";
break; break;
case 7: case 7:
content += String("Fault"); content += "Fault";
break; break;
default: default:
content += String("?"); content += "?";
} }
content += "</h4><h4>HV line status: "; content += "</h4><h4>HV line status: ";
switch (datalayer_extended.meb.status_HV_line) { switch (datalayer_extended.meb.status_HV_line) {
case 0: case 0:
content += String("Init"); content += "Init";
break; break;
case 1: case 1:
content += String("No open HV line detected"); content += "No open HV line detected";
break; break;
case 2: case 2:
content += String("Open HV line"); content += "Open HV line";
break; break;
case 3: case 3:
content += String("Fault"); content += "Fault";
break; break;
default: default:
content += String("? ") + String(datalayer_extended.meb.status_HV_line); content += "? " + String(datalayer_extended.meb.status_HV_line);
} }
content += "</h4>"; content += "</h4>";
content += datalayer_extended.meb.BMS_fault_performance ? "<h4>BMS fault performance: Active!</h4>" content += datalayer_extended.meb.BMS_fault_performance ? "<h4>BMS fault performance: Active!</h4>"
@ -147,83 +147,83 @@ class MebHtmlRenderer : public BatteryHtmlRenderer {
content += "<h4>Welded contactors: "; content += "<h4>Welded contactors: ";
switch (datalayer_extended.meb.BMS_welded_contactors_status) { switch (datalayer_extended.meb.BMS_welded_contactors_status) {
case 0: case 0:
content += String("Init"); content += "Init";
break; break;
case 1: case 1:
content += String("No contactor welded"); content += "No contactor welded";
break; break;
case 2: case 2:
content += String("At least 1 contactor welded"); content += "At least 1 contactor welded";
break; break;
case 3: case 3:
content += String("Protection status detection error"); content += "Protection status detection error";
break; break;
default: default:
content += String("?"); content += "?";
} }
content += "</h4><h4>Warning support: "; content += "</h4><h4>Warning support: ";
switch (datalayer_extended.meb.warning_support) { switch (datalayer_extended.meb.warning_support) {
case 0: case 0:
content += String("OK"); content += "OK";
break; break;
case 1: case 1:
content += String("Not OK"); content += "Not OK";
break; break;
case 6: case 6:
content += String("Init"); content += "Init";
break; break;
case 7: case 7:
content += String("Fault"); content += "Fault";
break; break;
default: default:
content += String("?"); content += "?";
} }
content += "</h4><h4>Interm. Voltage (" + String(datalayer_extended.meb.BMS_voltage_intermediate_dV / 10.0, 1) + content += "</h4><h4>Interm. Voltage (" + String(datalayer_extended.meb.BMS_voltage_intermediate_dV / 10.0, 1) +
"V) status: "; "V) status: ";
switch (datalayer_extended.meb.BMS_status_voltage_free) { switch (datalayer_extended.meb.BMS_status_voltage_free) {
case 0: case 0:
content += String("Init"); content += "Init";
break; break;
case 1: case 1:
content += String("BMS interm circuit voltage free (U<20V)"); content += "BMS interm circuit voltage free (U<20V)";
break; break;
case 2: case 2:
content += String("BMS interm circuit not voltage free (U >= 25V)"); content += "BMS interm circuit not voltage free (U >= 25V)";
break; break;
case 3: case 3:
content += String("Error"); content += "Error";
break; break;
default: default:
content += String("?"); content += "?";
} }
content += "</h4><h4>BMS error status: "; content += "</h4><h4>BMS error status: ";
switch (datalayer_extended.meb.BMS_error_status) { switch (datalayer_extended.meb.BMS_error_status) {
case 0: case 0:
content += String("Component IO"); content += "Component IO";
break; break;
case 1: case 1:
content += String("Iso Error 1"); content += "Iso Error 1";
break; break;
case 2: case 2:
content += String("Iso Error 2"); content += "Iso Error 2";
break; break;
case 3: case 3:
content += String("Interlock"); content += "Interlock";
break; break;
case 4: case 4:
content += String("SD"); content += "SD";
break; break;
case 5: case 5:
content += String("Performance red"); content += "Performance red";
break; break;
case 6: case 6:
content += String("No component function"); content += "No component function";
break; break;
case 7: case 7:
content += String("Init"); content += "Init";
break; break;
default: default:
content += String("?"); content += "?";
} }
content += "</h4><h4>BMS voltage: " + String(datalayer_extended.meb.BMS_voltage_dV / 10.0, 1) + "</h4>"; content += "</h4><h4>BMS voltage: " + String(datalayer_extended.meb.BMS_voltage_dV / 10.0, 1) + "</h4>";
content += datalayer_extended.meb.BMS_OBD_MIL ? "<h4>OBD MIL: ON!</h4>" : "<h4>OBD MIL: Off</h4>"; content += datalayer_extended.meb.BMS_OBD_MIL ? "<h4>OBD MIL: ON!</h4>" : "<h4>OBD MIL: Off</h4>";

View file

@ -10,8 +10,20 @@ class NissanLeafHtmlRenderer : public BatteryHtmlRenderer {
String get_status_html() { String get_status_html() {
String content; String content;
static const char* LEAFgen[] = {"ZE0", "AZE0", "ZE1"}; content += "<h4>LEAF generation: ";
content += "<h4>LEAF generation: " + String(LEAFgen[datalayer_extended.nissanleaf.LEAF_gen]) + "</h4>"; switch (datalayer_extended.nissanleaf.LEAF_gen) {
case 0:
content += String("ZE0</h4>");
break;
case 1:
content += String("AZE0</h4>");
break;
case 2:
content += String("ZE1</h4>");
break;
default:
content += String("Unknown</h4>");
}
char readableSerialNumber[16]; // One extra space for null terminator char readableSerialNumber[16]; // One extra space for null terminator
memcpy(readableSerialNumber, datalayer_extended.nissanleaf.BatterySerialNumber, memcpy(readableSerialNumber, datalayer_extended.nissanleaf.BatterySerialNumber,
sizeof(datalayer_extended.nissanleaf.BatterySerialNumber)); sizeof(datalayer_extended.nissanleaf.BatterySerialNumber));

View file

@ -27,7 +27,11 @@ class VolvoSpaBattery : public CanBattery {
bool supports_reset_BECM() { return true; } bool supports_reset_BECM() { return true; }
void reset_BECM() { datalayer_extended.VolvoPolestar.UserRequestBECMecuReset = true; } void reset_BECM() { datalayer_extended.VolvoPolestar.UserRequestBECMecuReset = true; }
BatteryHtmlRenderer& get_status_renderer() { return renderer; }
private: private:
VolvoSpaHtmlRenderer renderer;
void readCellVoltages(); void readCellVoltages();
static const int MAX_PACK_VOLTAGE_108S_DV = 4540; static const int MAX_PACK_VOLTAGE_108S_DV = 4540;

View file

@ -197,10 +197,10 @@ typedef struct {
uint16_t chargePower = 0; uint16_t chargePower = 0;
uint16_t unknown3 = 0; uint16_t unknown3 = 0;
uint16_t unknown4 = 0; uint16_t unknown4 = 0;
uint16_t unknown5 = 0; uint16_t total_charged_ah = 0;
uint16_t unknown6 = 0; uint16_t total_discharged_ah = 0;
uint16_t unknown7 = 0; uint16_t total_charged_kwh = 0;
uint16_t unknown8 = 0; uint16_t total_discharged_kwh = 0;
uint16_t unknown9 = 0; uint16_t unknown9 = 0;
uint8_t unknown10 = 0; uint8_t unknown10 = 0;
uint8_t unknown11 = 0; uint8_t unknown11 = 0;

View file

@ -121,6 +121,7 @@ void init_events(void) {
events.entries[EVENT_RJXZS_LOG].level = EVENT_LEVEL_INFO; events.entries[EVENT_RJXZS_LOG].level = EVENT_LEVEL_INFO;
events.entries[EVENT_PAUSE_BEGIN].level = EVENT_LEVEL_WARNING; events.entries[EVENT_PAUSE_BEGIN].level = EVENT_LEVEL_WARNING;
events.entries[EVENT_PAUSE_END].level = EVENT_LEVEL_INFO; events.entries[EVENT_PAUSE_END].level = EVENT_LEVEL_INFO;
events.entries[EVENT_PID_FAILED].level = EVENT_LEVEL_INFO;
events.entries[EVENT_WIFI_CONNECT].level = EVENT_LEVEL_INFO; events.entries[EVENT_WIFI_CONNECT].level = EVENT_LEVEL_INFO;
events.entries[EVENT_WIFI_DISCONNECT].level = EVENT_LEVEL_INFO; events.entries[EVENT_WIFI_DISCONNECT].level = EVENT_LEVEL_INFO;
events.entries[EVENT_MQTT_CONNECT].level = EVENT_LEVEL_INFO; events.entries[EVENT_MQTT_CONNECT].level = EVENT_LEVEL_INFO;
@ -351,6 +352,8 @@ const char* get_event_message_string(EVENTS_ENUM_TYPE event) {
return "The emulator is trying to pause the battery."; return "The emulator is trying to pause the battery.";
case EVENT_PAUSE_END: case EVENT_PAUSE_END:
return "The emulator is attempting to resume battery operation from pause."; return "The emulator is attempting to resume battery operation from pause.";
case EVENT_PID_FAILED:
return "Failed to write PID request to battery";
case EVENT_WIFI_CONNECT: case EVENT_WIFI_CONNECT:
return "Wifi connected."; return "Wifi connected.";
case EVENT_WIFI_DISCONNECT: case EVENT_WIFI_DISCONNECT:

View file

@ -95,6 +95,7 @@
XX(EVENT_RJXZS_LOG) \ XX(EVENT_RJXZS_LOG) \
XX(EVENT_PAUSE_BEGIN) \ XX(EVENT_PAUSE_BEGIN) \
XX(EVENT_PAUSE_END) \ XX(EVENT_PAUSE_END) \
XX(EVENT_PID_FAILED) \
XX(EVENT_WIFI_CONNECT) \ XX(EVENT_WIFI_CONNECT) \
XX(EVENT_WIFI_DISCONNECT) \ XX(EVENT_WIFI_DISCONNECT) \
XX(EVENT_MQTT_CONNECT) \ XX(EVENT_MQTT_CONNECT) \