diff --git a/Software/Software.ino b/Software/Software.ino index df63aa87..1ea9b3c6 100644 --- a/Software/Software.ino +++ b/Software/Software.ino @@ -319,7 +319,6 @@ void init_serial() { #endif // DEBUG_VIA_USB } -#ifdef DOUBLE_BATTERY void check_interconnect_available() { if (datalayer.battery.status.voltage_dV == 0 || datalayer.battery2.status.voltage_dV == 0) { return; // Both voltage values need to be available to start check @@ -339,7 +338,6 @@ void check_interconnect_available() { set_event(EVENT_VOLTAGE_DIFFERENCE, (uint8_t)(voltage_diff / 10)); } } -#endif // DOUBLE_BATTERY void update_calculated_values() { /* Update CPU temperature*/ @@ -399,11 +397,11 @@ void update_calculated_values() { } } -#ifdef DOUBLE_BATTERY - /* Calculate active power based on voltage and current for battery 2*/ - datalayer.battery2.status.active_power_W = - (datalayer.battery2.status.current_dA * (datalayer.battery2.status.voltage_dV / 100)); -#endif // DOUBLE_BATTERY + if (battery2) { + /* Calculate active power based on voltage and current for battery 2*/ + datalayer.battery2.status.active_power_W = + (datalayer.battery2.status.current_dA * (datalayer.battery2.status.voltage_dV / 100)); + } if (datalayer.battery.settings.soc_scaling_active) { /** SOC Scaling @@ -445,57 +443,62 @@ void update_calculated_values() { datalayer.battery.status.reported_remaining_capacity_Wh = datalayer.battery.status.remaining_capacity_Wh; } -#ifdef DOUBLE_BATTERY - // If battery info is valid - if (datalayer.battery2.info.total_capacity_Wh > 0 && datalayer.battery.status.real_soc > 0) { + if (battery2) { + // If battery info is valid + if (datalayer.battery2.info.total_capacity_Wh > 0 && datalayer.battery.status.real_soc > 0) { - datalayer.battery2.info.reported_total_capacity_Wh = scaled_total_capacity; - // Scale remaining capacity based on scaled SOC - datalayer.battery2.status.reported_remaining_capacity_Wh = (scaled_total_capacity * scaled_soc) / 10000; + datalayer.battery2.info.reported_total_capacity_Wh = scaled_total_capacity; + // Scale remaining capacity based on scaled SOC + datalayer.battery2.status.reported_remaining_capacity_Wh = (scaled_total_capacity * scaled_soc) / 10000; - } else { - // Fallback if scaling cannot be performed - datalayer.battery2.info.reported_total_capacity_Wh = datalayer.battery2.info.total_capacity_Wh; - datalayer.battery2.status.reported_remaining_capacity_Wh = datalayer.battery2.status.remaining_capacity_Wh; + } else { + // Fallback if scaling cannot be performed + datalayer.battery2.info.reported_total_capacity_Wh = datalayer.battery2.info.total_capacity_Wh; + datalayer.battery2.status.reported_remaining_capacity_Wh = datalayer.battery2.status.remaining_capacity_Wh; + } + + //Since we are running double battery, the scaled value of battery1 becomes the sum of battery1+battery2 + //This way the inverter connected to the system sees both batteries as one large battery + datalayer.battery.info.reported_total_capacity_Wh += datalayer.battery2.info.reported_total_capacity_Wh; + datalayer.battery.status.reported_remaining_capacity_Wh += + datalayer.battery2.status.reported_remaining_capacity_Wh; } - //Since we are running double battery, the scaled value of battery1 becomes the sum of battery1+battery2 - //This way the inverter connected to the system sees both batteries as one large battery - datalayer.battery.info.reported_total_capacity_Wh += datalayer.battery2.info.reported_total_capacity_Wh; - datalayer.battery.status.reported_remaining_capacity_Wh += datalayer.battery2.status.reported_remaining_capacity_Wh; - -#endif // DOUBLE_BATTERY - } else { // soc_scaling_active == false. No SOC window wanted. Set scaled to same as real. datalayer.battery.status.reported_soc = datalayer.battery.status.real_soc; datalayer.battery.status.reported_remaining_capacity_Wh = datalayer.battery.status.remaining_capacity_Wh; datalayer.battery.info.reported_total_capacity_Wh = datalayer.battery.info.total_capacity_Wh; -#ifdef DOUBLE_BATTERY - datalayer.battery2.status.reported_soc = datalayer.battery2.status.real_soc; - datalayer.battery2.status.reported_remaining_capacity_Wh = datalayer.battery2.status.remaining_capacity_Wh; - datalayer.battery2.info.reported_total_capacity_Wh = datalayer.battery2.info.total_capacity_Wh; -#endif - } -#ifdef DOUBLE_BATTERY - // Perform extra SOC sanity checks on double battery setups - if (datalayer.battery.status.real_soc < 100) { //If this battery is under 1.00%, use this as SOC instead of average - datalayer.battery.status.reported_soc = datalayer.battery.status.real_soc; - datalayer.battery.status.reported_remaining_capacity_Wh = datalayer.battery.status.remaining_capacity_Wh; - } - if (datalayer.battery2.status.real_soc < 100) { //If this battery is under 1.00%, use this as SOC instead of average - datalayer.battery.status.reported_soc = datalayer.battery2.status.real_soc; - datalayer.battery.status.reported_remaining_capacity_Wh = datalayer.battery2.status.remaining_capacity_Wh; + + if (battery2) { + datalayer.battery2.status.reported_soc = datalayer.battery2.status.real_soc; + datalayer.battery2.status.reported_remaining_capacity_Wh = datalayer.battery2.status.remaining_capacity_Wh; + datalayer.battery2.info.reported_total_capacity_Wh = datalayer.battery2.info.total_capacity_Wh; + } } - if (datalayer.battery.status.real_soc > 9900) { //If this battery is over 99.00%, use this as SOC instead of average - datalayer.battery.status.reported_soc = datalayer.battery.status.real_soc; - datalayer.battery.status.reported_remaining_capacity_Wh = datalayer.battery.status.remaining_capacity_Wh; + if (battery2) { + // Perform extra SOC sanity checks on double battery setups + if (datalayer.battery.status.real_soc < 100) { //If this battery is under 1.00%, use this as SOC instead of average + datalayer.battery.status.reported_soc = datalayer.battery.status.real_soc; + datalayer.battery.status.reported_remaining_capacity_Wh = datalayer.battery.status.remaining_capacity_Wh; + } + if (datalayer.battery2.status.real_soc < + 100) { //If this battery is under 1.00%, use this as SOC instead of average + datalayer.battery.status.reported_soc = datalayer.battery2.status.real_soc; + datalayer.battery.status.reported_remaining_capacity_Wh = datalayer.battery2.status.remaining_capacity_Wh; + } + + if (datalayer.battery.status.real_soc > + 9900) { //If this battery is over 99.00%, use this as SOC instead of average + datalayer.battery.status.reported_soc = datalayer.battery.status.real_soc; + datalayer.battery.status.reported_remaining_capacity_Wh = datalayer.battery.status.remaining_capacity_Wh; + } + if (datalayer.battery2.status.real_soc > + 9900) { //If this battery is over 99.00%, use this as SOC instead of average + datalayer.battery.status.reported_soc = datalayer.battery2.status.real_soc; + datalayer.battery.status.reported_remaining_capacity_Wh = datalayer.battery2.status.remaining_capacity_Wh; + } } - if (datalayer.battery2.status.real_soc > 9900) { //If this battery is over 99.00%, use this as SOC instead of average - datalayer.battery.status.reported_soc = datalayer.battery2.status.real_soc; - datalayer.battery.status.reported_remaining_capacity_Wh = datalayer.battery2.status.remaining_capacity_Wh; - } -#endif // DOUBLE_BATTERY // Check if millis has overflowed. Used in events to keep better track of time if (currentMillis < lastMillisOverflowCheck) { // Overflow detected datalayer.system.status.millisrolloverCount++; diff --git a/Software/src/battery/BATTERIES.cpp b/Software/src/battery/BATTERIES.cpp index a20ad111..12d54dec 100644 --- a/Software/src/battery/BATTERIES.cpp +++ b/Software/src/battery/BATTERIES.cpp @@ -11,11 +11,8 @@ // to support battery class selection at compile-time #ifdef SELECTED_BATTERY_CLASS -static Battery* battery = nullptr; - -#ifdef DOUBLE_BATTERY -static Battery* battery2 = nullptr; -#endif +Battery* battery = nullptr; +Battery* battery2 = nullptr; void setup_battery() { // Instantiate the battery only once just in case this function gets called multiple times. diff --git a/Software/src/battery/BATTERIES.h b/Software/src/battery/BATTERIES.h index 9012ad51..95ce6399 100644 --- a/Software/src/battery/BATTERIES.h +++ b/Software/src/battery/BATTERIES.h @@ -2,6 +2,13 @@ #define BATTERIES_H #include "../../USER_SETTINGS.h" +class Battery; + +// Currently initialized objects for primary and secondary battery. +// Null value indicates that battery is not configured/initialized +extern Battery* battery; +extern Battery* battery2; + #ifdef BMW_SBOX #include "BMW-SBOX.h" void handle_incoming_can_frame_shunt(CAN_frame rx_frame); @@ -162,9 +169,7 @@ void handle_incoming_can_frame_battery(CAN_frame rx_frame); void transmit_can_battery(unsigned long currentMillis); #endif -#ifdef DOUBLE_BATTERY void update_values_battery2(); void handle_incoming_can_frame_battery2(CAN_frame rx_frame); -#endif #endif diff --git a/Software/src/battery/BMW-I3-BATTERY.h b/Software/src/battery/BMW-I3-BATTERY.h index 3234aeb5..9a60a0ce 100644 --- a/Software/src/battery/BMW-I3-BATTERY.h +++ b/Software/src/battery/BMW-I3-BATTERY.h @@ -4,6 +4,7 @@ #include "../datalayer/datalayer.h" #include "../datalayer/datalayer_extended.h" #include "../include.h" +#include "BMW-I3-HTML.h" #include "CanBattery.h" #define BATTERY_SELECTED @@ -12,11 +13,12 @@ class BmwI3Battery : public CanBattery { public: // Use this constructor for the second battery. - BmwI3Battery(DATALAYER_BATTERY_TYPE* datalayer_ptr, bool* contactor_closing_allowed_ptr, int targetCan, int wakeup) { + BmwI3Battery(DATALAYER_BATTERY_TYPE* datalayer_ptr, bool* contactor_closing_allowed_ptr, CAN_Interface targetCan, + int wakeup) + : CanBattery(targetCan) { datalayer_battery = datalayer_ptr; contactor_closing_allowed = contactor_closing_allowed_ptr; allows_contactor_closing = nullptr; - can_interface = targetCan; wakeup_pin = wakeup; *allows_contactor_closing = true; @@ -29,7 +31,6 @@ class BmwI3Battery : public CanBattery { datalayer_battery = &datalayer.battery; allows_contactor_closing = &datalayer.system.status.battery_allows_contactor_closing; contactor_closing_allowed = nullptr; - can_interface = can_config.battery; wakeup_pin = WUP_PIN1; } @@ -38,6 +39,11 @@ class BmwI3Battery : public CanBattery { virtual void update_values(); virtual void transmit_can(unsigned long currentMillis); + BatteryHtmlRenderer& get_status_renderer() { return renderer; } + + private: + BmwI3HtmlRenderer renderer; + private: const int MAX_CELL_VOLTAGE_60AH = 4110; // Battery is put into emergency stop if one cell goes over this value const int MIN_CELL_VOLTAGE_60AH = 2700; // Battery is put into emergency stop if one cell goes below this value @@ -63,7 +69,6 @@ class BmwI3Battery : public CanBattery { bool* contactor_closing_allowed; int wakeup_pin; - int can_interface; unsigned long previousMillis20 = 0; // will store last time a 20ms CAN Message was send unsigned long previousMillis100 = 0; // will store last time a 100ms CAN Message was send diff --git a/Software/src/battery/BMW-I3-HTML.h b/Software/src/battery/BMW-I3-HTML.h new file mode 100644 index 00000000..88322a6d --- /dev/null +++ b/Software/src/battery/BMW-I3-HTML.h @@ -0,0 +1,98 @@ +#ifndef _BMW_I3_HTML_H +#define _BMW_I3_HTML_H + +#include "../datalayer/datalayer.h" +#include "../datalayer/datalayer_extended.h" +#include "src/devboard/webserver/BatteryHtmlRenderer.h" + +class BmwI3HtmlRenderer : public BatteryHtmlRenderer { + public: + String get_status_html() { + String content; + + content += "

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

"; + content += "

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

"; + content += "

SOC OBD2: " + String(datalayer_extended.bmwi3.SOC_OBD2) + "

"; + static const char* statusText[16] = { + "Not evaluated", "OK", "Error!", "Invalid signal", "", "", "", "", "", "", "", "", "", "", "", ""}; + content += "

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

"; + content += "

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

"; + content += "

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

"; + content += "

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

"; + content += "

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

"; + content += "

Emergency: " + String(statusText[datalayer_extended.bmwi3.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.bmwi3.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.bmwi3.ST_DCSW]) + "

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

Contactor weld: " + String(contText[datalayer_extended.bmwi3.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(contText[datalayer_extended.bmwi3.ST_cold_shutoff_valve]) + "

"; + + return content; + } +}; + +#endif diff --git a/Software/src/battery/BMW-IX-BATTERY.h b/Software/src/battery/BMW-IX-BATTERY.h index a1068c76..68935fad 100644 --- a/Software/src/battery/BMW-IX-BATTERY.h +++ b/Software/src/battery/BMW-IX-BATTERY.h @@ -2,6 +2,7 @@ #define BMW_IX_BATTERY_H #include #include "../include.h" +#include "BMW-IX-HTML.h" #include "CanBattery.h" #define BATTERY_SELECTED @@ -13,8 +14,15 @@ class BmwIXBattery : public CanBattery { virtual void handle_incoming_can_frame(CAN_frame rx_frame); virtual void update_values(); virtual void transmit_can(unsigned long currentMillis); + BatteryHtmlRenderer& get_status_renderer() { return renderer; } + + bool supports_contactor_close() { return true; } + + void request_open_contactors() { datalayer_extended.bmwix.UserRequestContactorOpen = true; } + void request_close_contactors() { datalayer_extended.bmwix.UserRequestContactorClose = true; } private: + BmwIXHtmlRenderer renderer; static const int MAX_PACK_VOLTAGE_DV = 4650; //4650 = 465.0V static const int MIN_PACK_VOLTAGE_DV = 3000; static const int MAX_CELL_DEVIATION_MV = 250; diff --git a/Software/src/battery/BMW-IX-HTML.h b/Software/src/battery/BMW-IX-HTML.h new file mode 100644 index 00000000..64b88fb9 --- /dev/null +++ b/Software/src/battery/BMW-IX-HTML.h @@ -0,0 +1,54 @@ +#ifndef _BMW_IX_HTML_H +#define _BMW_IX_HTML_H + +#include "../datalayer/datalayer.h" +#include "../datalayer/datalayer_extended.h" +#include "src/devboard/webserver/BatteryHtmlRenderer.h" + +class BmwIXHtmlRenderer : public BatteryHtmlRenderer { + public: + String get_status_html() { + String content; + + content += + "

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

"; + 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 += "

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 += + "

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

"; + content += + "

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

"; + 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

"; + content += "

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

"; + content += "

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

"; + static const char* balanceText[5] = {"0 No balancing mode active", "1 Voltage-Controlled Balancing Mode", + "2 Time-Controlled Balancing Mode with Demand Calculation at End of Charging", + "3 Time-Controlled Balancing Mode with Demand Calculation at Resting Voltage", + "4 No balancing mode active, qualifier invalid"}; + content += "

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

"; + static const char* hvilText[2] = {"Error (Loop Open)", "OK (Loop Closed)"}; + content += "

HVIL Status: " + String(hvilText[datalayer_extended.bmwix.hvil_status]) + "

"; + content += "

BMS Uptime: " + String(datalayer_extended.bmwix.bms_uptime) + " seconds

"; + content += "

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

"; + content += + "

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

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

HV Isolation (2147483647kOhm = maximum/invalid)

"; + content += "

Isolation Positive: " + String(datalayer_extended.bmwix.iso_safety_positive) + " kOhm

"; + content += "

Isolation Negative: " + String(datalayer_extended.bmwix.iso_safety_negative) + " kOhm

"; + content += "

Isolation Parallel: " + String(datalayer_extended.bmwix.iso_safety_parallel) + " kOhm

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

Pyro Status PSS1: " + String((pyroText[datalayer_extended.bmwix.pyro_status_pss1])) + "

"; + content += "

Pyro Status PSS4: " + String((pyroText[datalayer_extended.bmwix.pyro_status_pss4])) + "

"; + content += "

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

"; + + return content; + } +}; + +#endif diff --git a/Software/src/battery/BMW-PHEV-BATTERY.h b/Software/src/battery/BMW-PHEV-BATTERY.h index 0e39fc78..c50992c5 100644 --- a/Software/src/battery/BMW-PHEV-BATTERY.h +++ b/Software/src/battery/BMW-PHEV-BATTERY.h @@ -2,6 +2,7 @@ #define BMW_PHEV_BATTERY_H #include #include "../include.h" +#include "BMW-PHEV-HTML.h" #include "CanBattery.h" #define BATTERY_SELECTED @@ -14,7 +15,11 @@ class BmwPhevBattery : public CanBattery { virtual void update_values(); virtual void transmit_can(unsigned long currentMillis); + BatteryHtmlRenderer& get_status_renderer() { return renderer; } + private: + BmwPhevHtmlRenderer renderer; + static const int MAX_PACK_VOLTAGE_DV = 4650; //4650 = 465.0V static const int MIN_PACK_VOLTAGE_DV = 3000; static const int MAX_CELL_DEVIATION_MV = 250; diff --git a/Software/src/battery/BMW-PHEV-HTML.h b/Software/src/battery/BMW-PHEV-HTML.h new file mode 100644 index 00000000..812fd101 --- /dev/null +++ b/Software/src/battery/BMW-PHEV-HTML.h @@ -0,0 +1,132 @@ +#ifndef _BMW_PHEV_HTML_H +#define _BMW_PHEV_HTML_H + +#include "../datalayer/datalayer.h" +#include "../datalayer/datalayer_extended.h" +#include "src/devboard/webserver/BatteryHtmlRenderer.h" + +class BmwPhevHtmlRenderer : public BatteryHtmlRenderer { + public: + String get_status_html() { + String content; + + 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 += "
"; + + return content; + } +}; + +#endif diff --git a/Software/src/battery/BOLT-AMPERA-BATTERY.h b/Software/src/battery/BOLT-AMPERA-BATTERY.h index 07872b7d..57fdba84 100644 --- a/Software/src/battery/BOLT-AMPERA-BATTERY.h +++ b/Software/src/battery/BOLT-AMPERA-BATTERY.h @@ -3,6 +3,7 @@ #include #include "../include.h" +#include "BOLT-AMPERA-HTML.h" #include "CanBattery.h" #define BATTERY_SELECTED @@ -15,7 +16,10 @@ class BoltAmperaBattery : public CanBattery { virtual void update_values(); virtual void transmit_can(unsigned long currentMillis); + BatteryHtmlRenderer& get_status_renderer() { return renderer; } + private: + BoltAmperaHtmlRenderer renderer; static const int MAX_PACK_VOLTAGE_DV = 4150; //5000 = 500.0V static const int MIN_PACK_VOLTAGE_DV = 2500; static const int MAX_CELL_DEVIATION_MV = 150; diff --git a/Software/src/battery/BOLT-AMPERA-HTML.h b/Software/src/battery/BOLT-AMPERA-HTML.h new file mode 100644 index 00000000..33487473 --- /dev/null +++ b/Software/src/battery/BOLT-AMPERA-HTML.h @@ -0,0 +1,52 @@ +#ifndef _BOLT_AMPERA_HTML_H +#define _BOLT_AMPERA_HTML_H + +#include "../datalayer/datalayer_extended.h" +#include "src/devboard/webserver/BatteryHtmlRenderer.h" + +class BoltAmperaHtmlRenderer : public BatteryHtmlRenderer { + public: + String get_status_html() { + String content; + + content += "

5V Reference: " + String(datalayer_extended.boltampera.battery_5V_ref) + "

"; + content += "

Module 1 temp: " + String(datalayer_extended.boltampera.battery_module_temp_1) + "

"; + content += "

Module 2 temp: " + String(datalayer_extended.boltampera.battery_module_temp_2) + "

"; + content += "

Module 3 temp: " + String(datalayer_extended.boltampera.battery_module_temp_3) + "

"; + content += "

Module 4 temp: " + String(datalayer_extended.boltampera.battery_module_temp_4) + "

"; + content += "

Module 5 temp: " + String(datalayer_extended.boltampera.battery_module_temp_5) + "

"; + content += "

Module 6 temp: " + String(datalayer_extended.boltampera.battery_module_temp_6) + "

"; + content += + "

Cell average voltage: " + String(datalayer_extended.boltampera.battery_cell_average_voltage) + "

"; + content += + "

Cell average voltage 2: " + String(datalayer_extended.boltampera.battery_cell_average_voltage_2) + "

"; + content += "

Terminal voltage: " + String(datalayer_extended.boltampera.battery_terminal_voltage) + "

"; + content += + "

Ignition power mode: " + String(datalayer_extended.boltampera.battery_ignition_power_mode) + "

"; + content += "

Battery current (7E7): " + String(datalayer_extended.boltampera.battery_current_7E7) + "

"; + content += "

Capacity MY17-18: " + String(datalayer_extended.boltampera.battery_capacity_my17_18) + "

"; + content += "

Capacity MY19+: " + String(datalayer_extended.boltampera.battery_capacity_my19plus) + "

"; + content += "

SOC Display: " + String(datalayer_extended.boltampera.battery_SOC_display) + "

"; + content += "

SOC Raw highprec: " + String(datalayer_extended.boltampera.battery_SOC_raw_highprec) + "

"; + content += "

Max temp: " + String(datalayer_extended.boltampera.battery_max_temperature) + "

"; + content += "

Min temp: " + String(datalayer_extended.boltampera.battery_min_temperature) + "

"; + content += "

Cell max mV: " + String(datalayer_extended.boltampera.battery_max_cell_voltage) + "

"; + content += "

Cell min mV: " + String(datalayer_extended.boltampera.battery_min_cell_voltage) + "

"; + content += "

Lowest cell: " + String(datalayer_extended.boltampera.battery_lowest_cell) + "

"; + content += "

Highest cell: " + String(datalayer_extended.boltampera.battery_highest_cell) + "

"; + content += + "

Internal resistance: " + String(datalayer_extended.boltampera.battery_internal_resistance) + "

"; + content += "

Voltage: " + String(datalayer_extended.boltampera.battery_voltage_polled) + "

"; + content += "

Isolation Ohm: " + String(datalayer_extended.boltampera.battery_vehicle_isolation) + "

"; + content += "

Isolation kOhm: " + String(datalayer_extended.boltampera.battery_isolation_kohm) + "

"; + content += "

HV locked: " + String(datalayer_extended.boltampera.battery_HV_locked) + "

"; + content += "

Crash event: " + String(datalayer_extended.boltampera.battery_crash_event) + "

"; + content += "

HVIL: " + String(datalayer_extended.boltampera.battery_HVIL) + "

"; + content += "

HVIL status: " + String(datalayer_extended.boltampera.battery_HVIL_status) + "

"; + content += "

Current (7E4): " + String(datalayer_extended.boltampera.battery_current_7E4) + "

"; + + return content; + } +}; + +#endif diff --git a/Software/src/battery/BYD-ATTO-3-BATTERY.h b/Software/src/battery/BYD-ATTO-3-BATTERY.h index 3330eaff..ff04ee15 100644 --- a/Software/src/battery/BYD-ATTO-3-BATTERY.h +++ b/Software/src/battery/BYD-ATTO-3-BATTERY.h @@ -5,6 +5,7 @@ #include "../datalayer/datalayer_extended.h" #include "../include.h" +#include "BYD-ATTO-3-HTML.h" #include "CanBattery.h" #define USE_ESTIMATED_SOC // If enabled, SOC is estimated from pack voltage. Useful for locked packs. \ @@ -33,18 +34,17 @@ class BydAttoBattery : public CanBattery { public: // Use this constructor for the second battery. - BydAttoBattery(DATALAYER_BATTERY_TYPE* datalayer_ptr, DATALAYER_INFO_BYDATTO3* extended, int targetCan) { + BydAttoBattery(DATALAYER_BATTERY_TYPE* datalayer_ptr, DATALAYER_INFO_BYDATTO3* extended, CAN_Interface targetCan) + : CanBattery(targetCan), renderer(extended) { datalayer_battery = datalayer_ptr; datalayer_bydatto = extended; allows_contactor_closing = nullptr; - can_interface = targetCan; } // Use the default constructor to create the first or single battery. - BydAttoBattery() { + BydAttoBattery() : renderer(&datalayer_extended.bydAtto3) { datalayer_battery = &datalayer.battery; allows_contactor_closing = &datalayer.system.status.battery_allows_contactor_closing; - can_interface = can_config.battery; datalayer_bydatto = &datalayer_extended.bydAtto3; } @@ -53,13 +53,18 @@ class BydAttoBattery : public CanBattery { virtual void update_values(); virtual void transmit_can(unsigned long currentMillis); + bool supports_reset_crash() { return true; } + + void reset_crash() { datalayer_bydatto->UserRequestCrashReset = true; } + + BatteryHtmlRenderer& get_status_renderer() { return renderer; } + private: + BydAtto3HtmlRenderer renderer; DATALAYER_BATTERY_TYPE* datalayer_battery; DATALAYER_INFO_BYDATTO3* datalayer_bydatto; bool* allows_contactor_closing; - int can_interface; - static const int POLL_FOR_BATTERY_SOC = 0x0005; static const uint8_t NOT_DETERMINED_YET = 0; static const uint8_t STANDARD_RANGE = 1; diff --git a/Software/src/battery/BYD-ATTO-3-HTML.h b/Software/src/battery/BYD-ATTO-3-HTML.h new file mode 100644 index 00000000..b133db84 --- /dev/null +++ b/Software/src/battery/BYD-ATTO-3-HTML.h @@ -0,0 +1,54 @@ +#ifndef _BYD_ATTO_3_HTML_H +#define _BYD_ATTO_3_HTML_H + +#include "../datalayer/datalayer.h" +#include "../datalayer/datalayer_extended.h" +#include "src/devboard/webserver/BatteryHtmlRenderer.h" + +class BydAtto3HtmlRenderer : public BatteryHtmlRenderer { + public: + BydAtto3HtmlRenderer(DATALAYER_INFO_BYDATTO3* dl) : byd_datalayer(dl) {} + + String get_status_html() { + String content; + + static const char* SOCmethod[2] = {"Estimated from voltage", "Measured by BMS"}; + content += "

SOC method used: " + String(SOCmethod[byd_datalayer->SOC_method]) + "

"; + content += "

SOC estimated: " + String(byd_datalayer->SOC_estimated) + "

"; + content += "

SOC highprec: " + String(byd_datalayer->SOC_highprec) + "

"; + content += "

SOC OBD2: " + String(byd_datalayer->SOC_polled) + "

"; + content += "

Voltage periodic: " + String(byd_datalayer->voltage_periodic) + "

"; + content += "

Voltage OBD2: " + String(byd_datalayer->voltage_polled) + "

"; + content += "

Temperature sensor 1: " + String(byd_datalayer->battery_temperatures[0]) + "

"; + content += "

Temperature sensor 2: " + String(byd_datalayer->battery_temperatures[1]) + "

"; + content += "

Temperature sensor 3: " + String(byd_datalayer->battery_temperatures[2]) + "

"; + content += "

Temperature sensor 4: " + String(byd_datalayer->battery_temperatures[3]) + "

"; + content += "

Temperature sensor 5: " + String(byd_datalayer->battery_temperatures[4]) + "

"; + content += "

Temperature sensor 6: " + String(byd_datalayer->battery_temperatures[5]) + "

"; + content += "

Temperature sensor 7: " + String(byd_datalayer->battery_temperatures[6]) + "

"; + content += "

Temperature sensor 8: " + String(byd_datalayer->battery_temperatures[7]) + "

"; + content += "

Temperature sensor 9: " + String(byd_datalayer->battery_temperatures[8]) + "

"; + content += "

Temperature sensor 10: " + String(byd_datalayer->battery_temperatures[9]) + "

"; + content += "

Unknown0: " + String(byd_datalayer->unknown0) + "

"; + content += "

Unknown1: " + String(byd_datalayer->unknown1) + "

"; + content += "

Charge power raw: " + String(byd_datalayer->chargePower) + "

"; + content += "

Unknown3: " + String(byd_datalayer->unknown3) + "

"; + content += "

Unknown4: " + String(byd_datalayer->unknown4) + "

"; + content += "

Unknown5: " + String(byd_datalayer->unknown5) + "

"; + content += "

Unknown6: " + String(byd_datalayer->unknown6) + "

"; + content += "

Unknown7: " + String(byd_datalayer->unknown7) + "

"; + content += "

Unknown8: " + String(byd_datalayer->unknown8) + "

"; + content += "

Unknown9: " + String(byd_datalayer->unknown9) + "

"; + content += "

Unknown10: " + String(byd_datalayer->unknown10) + "

"; + content += "

Unknown11: " + String(byd_datalayer->unknown11) + "

"; + content += "

Unknown12: " + String(byd_datalayer->unknown12) + "

"; + content += "

Unknown13: " + String(byd_datalayer->unknown12) + "

"; + + return content; + } + + private: + DATALAYER_INFO_BYDATTO3* byd_datalayer; +}; + +#endif diff --git a/Software/src/battery/Battery.h b/Software/src/battery/Battery.h index 9a63be1b..bc380ef0 100644 --- a/Software/src/battery/Battery.h +++ b/Software/src/battery/Battery.h @@ -1,12 +1,56 @@ #ifndef BATTERY_H #define BATTERY_H +#include "../datalayer/datalayer.h" +#include "src/devboard/webserver/BatteryHtmlRenderer.h" + // Abstract base class for next-generation battery implementations. // Defines the interface to call battery specific functionality. class Battery { public: virtual void setup(void) = 0; virtual void update_values() = 0; + + // The name of the comm interface the battery is using. + virtual String interface_name() = 0; + + // These are commands from external I/O (UI, MQTT etc.) + // Override in battery if it supports them. Otherwise they are NOP. + + virtual bool supports_clear_isolation() { return false; } + virtual bool supports_reset_BMS() { return false; } + virtual bool supports_reset_crash() { return false; } + virtual bool supports_reset_NVROL() { return false; } + virtual bool supports_reset_DTC() { return false; } + virtual bool supports_read_DTC() { return false; } + virtual bool supports_reset_SOH() { return false; } + virtual bool supports_reset_BECM() { return false; } + virtual bool supports_contactor_close() { return false; } + virtual bool supports_set_fake_voltage() { return false; } + virtual bool supports_manual_balancing() { return false; } + virtual bool supports_real_BMS_status() { return false; } + + virtual void clear_isolation() {} + virtual void reset_BMS() {} + virtual void reset_crash() {} + virtual void reset_NVROL() {} + virtual void reset_DTC() {} + virtual void read_DTC() {} + virtual void reset_SOH() {} + virtual void reset_BECM() {} + virtual void request_open_contactors() {} + virtual void request_close_contactors() {} + + virtual void set_fake_voltage(float v) {} + virtual float get_voltage() { static_cast(datalayer.battery.status.voltage_dV) / 10.0; } + + // This allows for battery specific SOC plausibility calculations to be performed. + virtual bool soc_plausible() { return true; } + + virtual BatteryHtmlRenderer& get_status_renderer() { return defaultRenderer; } + + private: + BatteryDefaultRenderer defaultRenderer; }; #endif diff --git a/Software/src/battery/CELLPOWER-BMS.h b/Software/src/battery/CELLPOWER-BMS.h index 75076d46..15a4662f 100644 --- a/Software/src/battery/CELLPOWER-BMS.h +++ b/Software/src/battery/CELLPOWER-BMS.h @@ -2,6 +2,7 @@ #define CELLPOWER_BMS_H #include #include "../include.h" +#include "CELLPOWER-HTML.h" #include "CanBattery.h" #define BATTERY_SELECTED @@ -14,7 +15,10 @@ class CellPowerBms : public CanBattery { virtual void update_values(); virtual void transmit_can(unsigned long currentMillis); + BatteryHtmlRenderer& get_status_renderer() { return renderer; } + private: + CellpowerHtmlRenderer renderer; /* Tweak these according to your battery build */ static const int MAX_PACK_VOLTAGE_DV = 5000; //5000 = 500.0V static const int MIN_PACK_VOLTAGE_DV = 1500; diff --git a/Software/src/battery/CELLPOWER-HTML.h b/Software/src/battery/CELLPOWER-HTML.h new file mode 100644 index 00000000..f4f6a7ef --- /dev/null +++ b/Software/src/battery/CELLPOWER-HTML.h @@ -0,0 +1,150 @@ +#ifndef _CELLPOWER_HTML_H +#define _CELLPOWER_HTML_H + +#include "../datalayer/datalayer.h" +#include "../datalayer/datalayer_extended.h" +#include "src/devboard/webserver/BatteryHtmlRenderer.h" + +class CellpowerHtmlRenderer : public BatteryHtmlRenderer { + public: + String get_status_html() { + String content; + + static const char* falseTrue[2] = {"False", "True"}; + content += "

States:

"; + content += "

Discharge: " + String(falseTrue[datalayer_extended.cellpower.system_state_discharge]) + "

"; + content += "

Charge: " + String(falseTrue[datalayer_extended.cellpower.system_state_charge]) + "

"; + content += + "

Cellbalancing: " + String(falseTrue[datalayer_extended.cellpower.system_state_cellbalancing]) + "

"; + content += + "

Tricklecharging: " + String(falseTrue[datalayer_extended.cellpower.system_state_tricklecharge]) + "

"; + content += "

Idle: " + String(falseTrue[datalayer_extended.cellpower.system_state_idle]) + "

"; + content += "

Charge completed: " + String(falseTrue[datalayer_extended.cellpower.system_state_chargecompleted]) + + "

"; + content += + "

Maintenance charge: " + String(falseTrue[datalayer_extended.cellpower.system_state_maintenancecharge]) + + "

"; + content += "

IO:

"; + content += + "

Main positive relay: " + String(falseTrue[datalayer_extended.cellpower.IO_state_main_positive_relay]) + + "

"; + content += + "

Main negative relay: " + String(falseTrue[datalayer_extended.cellpower.IO_state_main_negative_relay]) + + "

"; + content += + "

Charge enabled: " + String(falseTrue[datalayer_extended.cellpower.IO_state_charge_enable]) + "

"; + content += + "

Precharge relay: " + String(falseTrue[datalayer_extended.cellpower.IO_state_precharge_relay]) + "

"; + content += + "

Discharge enable: " + String(falseTrue[datalayer_extended.cellpower.IO_state_discharge_enable]) + "

"; + content += "

IO 6: " + String(falseTrue[datalayer_extended.cellpower.IO_state_IO_6]) + "

"; + content += "

IO 7: " + String(falseTrue[datalayer_extended.cellpower.IO_state_IO_7]) + "

"; + content += "

IO 8: " + String(falseTrue[datalayer_extended.cellpower.IO_state_IO_8]) + "

"; + content += "

Errors:

"; + content += + "

Cell overvoltage: " + String(falseTrue[datalayer_extended.cellpower.error_Cell_overvoltage]) + "

"; + content += + "

Cell undervoltage: " + String(falseTrue[datalayer_extended.cellpower.error_Cell_undervoltage]) + "

"; + content += "

Cell end of life voltage: " + + String(falseTrue[datalayer_extended.cellpower.error_Cell_end_of_life_voltage]) + "

"; + content += + "

Cell voltage misread: " + String(falseTrue[datalayer_extended.cellpower.error_Cell_voltage_misread]) + + "

"; + content += + "

Cell over temperature: " + String(falseTrue[datalayer_extended.cellpower.error_Cell_over_temperature]) + + "

"; + content += + "

Cell under temperature: " + String(falseTrue[datalayer_extended.cellpower.error_Cell_under_temperature]) + + "

"; + content += "

Cell unmanaged: " + String(falseTrue[datalayer_extended.cellpower.error_Cell_unmanaged]) + "

"; + content += + "

LMU over temperature: " + String(falseTrue[datalayer_extended.cellpower.error_LMU_over_temperature]) + + "

"; + content += + "

LMU under temperature: " + String(falseTrue[datalayer_extended.cellpower.error_LMU_under_temperature]) + + "

"; + content += "

Temp sensor open circuit: " + + String(falseTrue[datalayer_extended.cellpower.error_Temp_sensor_open_circuit]) + "

"; + content += "

Temp sensor short circuit: " + + String(falseTrue[datalayer_extended.cellpower.error_Temp_sensor_short_circuit]) + "

"; + content += "

SUB comm: " + String(falseTrue[datalayer_extended.cellpower.error_SUB_communication]) + "

"; + content += "

LMU comm: " + String(falseTrue[datalayer_extended.cellpower.error_LMU_communication]) + "

"; + content += + "

Over current In: " + String(falseTrue[datalayer_extended.cellpower.error_Over_current_IN]) + "

"; + content += + "

Over current Out: " + String(falseTrue[datalayer_extended.cellpower.error_Over_current_OUT]) + "

"; + content += "

Short circuit: " + String(falseTrue[datalayer_extended.cellpower.error_Short_circuit]) + "

"; + content += "

Leak detected: " + String(falseTrue[datalayer_extended.cellpower.error_Leak_detected]) + "

"; + content += + "

Leak detection failed: " + String(falseTrue[datalayer_extended.cellpower.error_Leak_detection_failed]) + + "

"; + content += + "

Voltage diff: " + String(falseTrue[datalayer_extended.cellpower.error_Voltage_difference]) + "

"; + content += "

BMCU supply overvoltage: " + + String(falseTrue[datalayer_extended.cellpower.error_BMCU_supply_over_voltage]) + "

"; + content += "

BMCU supply undervoltage: " + + String(falseTrue[datalayer_extended.cellpower.error_BMCU_supply_under_voltage]) + "

"; + content += "

Main positive contactor: " + + String(falseTrue[datalayer_extended.cellpower.error_Main_positive_contactor]) + "

"; + content += "

Main negative contactor: " + + String(falseTrue[datalayer_extended.cellpower.error_Main_negative_contactor]) + "

"; + content += "

Precharge contactor: " + String(falseTrue[datalayer_extended.cellpower.error_Precharge_contactor]) + + "

"; + content += + "

Midpack contactor: " + String(falseTrue[datalayer_extended.cellpower.error_Midpack_contactor]) + "

"; + content += + "

Precharge timeout: " + String(falseTrue[datalayer_extended.cellpower.error_Precharge_timeout]) + "

"; + content += "

EMG connector override: " + + String(falseTrue[datalayer_extended.cellpower.error_Emergency_connector_override]) + "

"; + content += "

Warnings:

"; + content += + "

High cell voltage: " + String(falseTrue[datalayer_extended.cellpower.warning_High_cell_voltage]) + "

"; + content += + "

Low cell voltage: " + String(falseTrue[datalayer_extended.cellpower.warning_Low_cell_voltage]) + "

"; + content += + "

High cell temperature: " + String(falseTrue[datalayer_extended.cellpower.warning_High_cell_temperature]) + + "

"; + content += + "

Low cell temperature: " + String(falseTrue[datalayer_extended.cellpower.warning_Low_cell_temperature]) + + "

"; + content += + "

High LMU temperature: " + String(falseTrue[datalayer_extended.cellpower.warning_High_LMU_temperature]) + + "

"; + content += + "

Low LMU temperature: " + String(falseTrue[datalayer_extended.cellpower.warning_Low_LMU_temperature]) + + "

"; + content += + "

SUB comm interf: " + String(falseTrue[datalayer_extended.cellpower.warning_SUB_communication_interfered]) + + "

"; + content += + "

LMU comm interf: " + String(falseTrue[datalayer_extended.cellpower.warning_LMU_communication_interfered]) + + "

"; + content += + "

High current In: " + String(falseTrue[datalayer_extended.cellpower.warning_High_current_IN]) + "

"; + content += + "

High current Out: " + String(falseTrue[datalayer_extended.cellpower.warning_High_current_OUT]) + "

"; + content += "

Pack resistance diff: " + + String(falseTrue[datalayer_extended.cellpower.warning_Pack_resistance_difference]) + "

"; + content += + "

High pack resistance: " + String(falseTrue[datalayer_extended.cellpower.warning_High_pack_resistance]) + + "

"; + content += "

Cell resistance diff: " + + String(falseTrue[datalayer_extended.cellpower.warning_Cell_resistance_difference]) + "

"; + content += + "

High cell resistance: " + String(falseTrue[datalayer_extended.cellpower.warning_High_cell_resistance]) + + "

"; + content += "

High BMCU supply voltage: " + + String(falseTrue[datalayer_extended.cellpower.warning_High_BMCU_supply_voltage]) + "

"; + content += "

Low BMCU supply voltage: " + + String(falseTrue[datalayer_extended.cellpower.warning_Low_BMCU_supply_voltage]) + "

"; + content += "

Low SOC: " + String(falseTrue[datalayer_extended.cellpower.warning_Low_SOC]) + "

"; + content += "

Balancing required: " + + String(falseTrue[datalayer_extended.cellpower.warning_Balancing_required_OCV_model]) + "

"; + content += "

Charger not responding: " + + String(falseTrue[datalayer_extended.cellpower.warning_Charger_not_responding]) + "

"; + + return content; + } +}; + +#endif diff --git a/Software/src/battery/CMFA-EV-BATTERY.h b/Software/src/battery/CMFA-EV-BATTERY.h index d3c8a9a1..0455838e 100644 --- a/Software/src/battery/CMFA-EV-BATTERY.h +++ b/Software/src/battery/CMFA-EV-BATTERY.h @@ -2,6 +2,7 @@ #define CMFA_EV_BATTERY_H #include "../include.h" +#include "CMFA-EV-HTML.h" #include "CanBattery.h" #define BATTERY_SELECTED @@ -14,7 +15,11 @@ class CmfaEvBattery : public CanBattery { virtual void update_values(); virtual void transmit_can(unsigned long currentMillis); + BatteryHtmlRenderer& get_status_renderer() { return renderer; } + private: + CmfaEvHtmlRenderer renderer; + uint16_t rescale_raw_SOC(uint32_t raw_SOC); static const int MAX_PACK_VOLTAGE_DV = 3040; // 5000 = 500.0V diff --git a/Software/src/battery/CMFA-EV-HTML.h b/Software/src/battery/CMFA-EV-HTML.h new file mode 100644 index 00000000..2d1f445f --- /dev/null +++ b/Software/src/battery/CMFA-EV-HTML.h @@ -0,0 +1,39 @@ +#ifndef _CMFA_EV_HTML_H +#define _CMFA_EV_HTML_H + +#include "../datalayer/datalayer.h" +#include "../datalayer/datalayer_extended.h" +#include "src/devboard/webserver/BatteryHtmlRenderer.h" + +class CmfaEvHtmlRenderer : public BatteryHtmlRenderer { + public: + String get_status_html() { + String content; + + content += "

SOC U: " + String(datalayer_extended.CMFAEV.soc_u) + "percent

"; + content += "

SOC Z: " + String(datalayer_extended.CMFAEV.soc_z) + "percent

"; + content += "

SOH Average: " + String(datalayer_extended.CMFAEV.soh_average) + "pptt

"; + content += "

12V voltage: " + String(datalayer_extended.CMFAEV.lead_acid_voltage) + "mV

"; + content += "

Highest cell number: " + String(datalayer_extended.CMFAEV.highest_cell_voltage_number) + "

"; + content += "

Lowest cell number: " + String(datalayer_extended.CMFAEV.lowest_cell_voltage_number) + "

"; + content += "

Max regen power: " + String(datalayer_extended.CMFAEV.max_regen_power) + "

"; + content += "

Max discharge power: " + String(datalayer_extended.CMFAEV.max_discharge_power) + "

"; + content += "

Max charge power: " + String(datalayer_extended.CMFAEV.maximum_charge_power) + "

"; + content += "

SOH available power: " + String(datalayer_extended.CMFAEV.SOH_available_power) + "

"; + content += "

SOH generated power: " + String(datalayer_extended.CMFAEV.SOH_generated_power) + "

"; + content += "

Average temperature: " + String(datalayer_extended.CMFAEV.average_temperature) + "dC

"; + content += "

Maximum temperature: " + String(datalayer_extended.CMFAEV.maximum_temperature) + "dC

"; + content += "

Minimum temperature: " + String(datalayer_extended.CMFAEV.minimum_temperature) + "dC

"; + content += + "

Cumulative energy discharged: " + String(datalayer_extended.CMFAEV.cumulative_energy_when_discharging) + + "Wh

"; + content += "

Cumulative energy charged: " + String(datalayer_extended.CMFAEV.cumulative_energy_when_charging) + + "Wh

"; + content += + "

Cumulative energy regen: " + String(datalayer_extended.CMFAEV.cumulative_energy_in_regen) + "Wh

"; + + return content; + } +}; + +#endif diff --git a/Software/src/battery/CanBattery.h b/Software/src/battery/CanBattery.h index 78b66c5f..2fc2bc28 100644 --- a/Software/src/battery/CanBattery.h +++ b/Software/src/battery/CanBattery.h @@ -10,6 +10,15 @@ class CanBattery : public Battery { public: virtual void handle_incoming_can_frame(CAN_frame rx_frame) = 0; virtual void transmit_can(unsigned long currentMillis) = 0; + + String interface_name() { return getCANInterfaceName(can_interface); } + + protected: + CAN_Interface can_interface; + + CanBattery() { can_interface = can_config.battery; } + + CanBattery(CAN_Interface interface) { can_interface = interface; } }; #endif diff --git a/Software/src/battery/ECMP-BATTERY.h b/Software/src/battery/ECMP-BATTERY.h index 36fc82ec..ad3fb041 100644 --- a/Software/src/battery/ECMP-BATTERY.h +++ b/Software/src/battery/ECMP-BATTERY.h @@ -4,6 +4,7 @@ #include "../include.h" #include "CanBattery.h" +#include "ECMP-HTML.h" #define BATTERY_SELECTED #define SELECTED_BATTERY_CLASS EcmpBattery @@ -15,7 +16,10 @@ class EcmpBattery : public CanBattery { virtual void update_values(); virtual void transmit_can(unsigned long currentMillis); + BatteryHtmlRenderer& get_status_renderer() { return renderer; } + private: + EcmpHtmlRenderer renderer; static const int MAX_PACK_VOLTAGE_DV = 4546; static const int MIN_PACK_VOLTAGE_DV = 3210; static const int MAX_CELL_DEVIATION_MV = 100; diff --git a/Software/src/battery/ECMP-HTML.h b/Software/src/battery/ECMP-HTML.h new file mode 100644 index 00000000..aa09aa54 --- /dev/null +++ b/Software/src/battery/ECMP-HTML.h @@ -0,0 +1,28 @@ +#ifndef _ECMP_HTML_H +#define _ECMP_HTML_H + +#include "../datalayer/datalayer.h" +#include "../datalayer/datalayer_extended.h" +#include "src/devboard/webserver/BatteryHtmlRenderer.h" + +class EcmpHtmlRenderer : public BatteryHtmlRenderer { + public: + String get_status_html() { + String content; + + content += "

Main Connector State: "; + if (datalayer_extended.stellantisECMP.MainConnectorState == 0) { + content += "Contactors open

"; + } else if (datalayer_extended.stellantisECMP.MainConnectorState == 0x01) { + content += "Precharged"; + } else { + content += "Invalid"; + } + content += + "

Insulation Resistance: " + String(datalayer_extended.stellantisECMP.InsulationResistance) + "kOhm

"; + + return content; + } +}; + +#endif diff --git a/Software/src/battery/GEELY-GEOMETRY-C-BATTERY.h b/Software/src/battery/GEELY-GEOMETRY-C-BATTERY.h index 62d0612c..f724c937 100644 --- a/Software/src/battery/GEELY-GEOMETRY-C-BATTERY.h +++ b/Software/src/battery/GEELY-GEOMETRY-C-BATTERY.h @@ -4,6 +4,7 @@ #include "../datalayer/datalayer_extended.h" #include "../include.h" #include "CanBattery.h" +#include "GEELY-GEOMETRY-C-HTML.h" #define BATTERY_SELECTED #define SELECTED_BATTERY_CLASS GeelyGeometryCBattery @@ -36,7 +37,10 @@ class GeelyGeometryCBattery : public CanBattery { virtual void update_values(); virtual void transmit_can(unsigned long currentMillis); + BatteryHtmlRenderer& get_status_renderer() { return renderer; } + private: + GeelyGeometryCHtmlRenderer renderer; const int MAX_PACK_VOLTAGE_70_DV 4420 //70kWh const int MIN_PACK_VOLTAGE_70_DV 2860 const int MAX_PACK_VOLTAGE_53_DV 4160 //53kWh const int MIN_PACK_VOLTAGE_53_DV 2700 const int MAX_CELL_DEVIATION_MV 150 const int diff --git a/Software/src/battery/GEELY-GEOMETRY-C-HTML.h b/Software/src/battery/GEELY-GEOMETRY-C-HTML.h new file mode 100644 index 00000000..bb4b5602 --- /dev/null +++ b/Software/src/battery/GEELY-GEOMETRY-C-HTML.h @@ -0,0 +1,59 @@ +#ifndef _GEELY_GEOMETRY_C_HTML_H +#define _GEELY_GEOMETRY_C_HTML_H + +#include "../datalayer/datalayer.h" +#include "../datalayer/datalayer_extended.h" +#include "src/devboard/webserver/BatteryHtmlRenderer.h" + +class GeelyGeometryCHtmlRenderer : public BatteryHtmlRenderer { + public: + String get_status_html() { + String content; + + char readableSerialNumber[29]; // One extra space for null terminator + memcpy(readableSerialNumber, datalayer_extended.geometryC.BatterySerialNumber, + sizeof(datalayer_extended.geometryC.BatterySerialNumber)); + readableSerialNumber[28] = '\0'; // Null terminate the string + char readableSoftwareVersion[17]; // One extra space for null terminator + memcpy(readableSoftwareVersion, datalayer_extended.geometryC.BatterySoftwareVersion, + sizeof(datalayer_extended.geometryC.BatterySoftwareVersion)); + readableSoftwareVersion[16] = '\0'; // Null terminate the string + char readableHardwareVersion[17]; // One extra space for null terminator + memcpy(readableHardwareVersion, datalayer_extended.geometryC.BatteryHardwareVersion, + sizeof(datalayer_extended.geometryC.BatteryHardwareVersion)); + readableHardwareVersion[16] = '\0'; // Null terminate the string + content += "

Serial number: " + String(readableSoftwareVersion) + "

"; + content += "

Software version: " + String(readableSerialNumber) + "

"; + content += "

Hardware version: " + String(readableHardwareVersion) + "

"; + content += "

SOC display: " + String(datalayer_extended.geometryC.soc) + "ppt

"; + content += "

CC2 voltage: " + String(datalayer_extended.geometryC.CC2voltage) + "mV

"; + content += "

Cell max voltage number: " + String(datalayer_extended.geometryC.cellMaxVoltageNumber) + "

"; + content += "

Cell min voltage number: " + String(datalayer_extended.geometryC.cellMinVoltageNumber) + "

"; + content += "

Cell total amount: " + String(datalayer_extended.geometryC.cellTotalAmount) + "S

"; + content += "

Specificial Voltage: " + String(datalayer_extended.geometryC.specificialVoltage) + "dV

"; + content += "

Unknown1: " + String(datalayer_extended.geometryC.unknown1) + "

"; + content += "

Raw SOC max: " + String(datalayer_extended.geometryC.rawSOCmax) + "

"; + content += "

Raw SOC min: " + String(datalayer_extended.geometryC.rawSOCmin) + "

"; + content += "

Unknown4: " + String(datalayer_extended.geometryC.unknown4) + "

"; + content += "

Capacity module max: " + String((datalayer_extended.geometryC.capModMax / 10)) + "Ah

"; + content += "

Capacity module min: " + String((datalayer_extended.geometryC.capModMin / 10)) + "Ah

"; + content += "

Unknown7: " + String(datalayer_extended.geometryC.unknown7) + "

"; + content += "

Unknown8: " + String(datalayer_extended.geometryC.unknown8) + "

"; + content += + "

Module 1 temperature: " + String(datalayer_extended.geometryC.ModuleTemperatures[0]) + " °C

"; + content += + "

Module 2 temperature: " + String(datalayer_extended.geometryC.ModuleTemperatures[1]) + " °C

"; + content += + "

Module 3 temperature: " + String(datalayer_extended.geometryC.ModuleTemperatures[2]) + " °C

"; + content += + "

Module 4 temperature: " + String(datalayer_extended.geometryC.ModuleTemperatures[3]) + " °C

"; + content += + "

Module 5 temperature: " + String(datalayer_extended.geometryC.ModuleTemperatures[4]) + " °C

"; + content += + "

Module 6 temperature: " + String(datalayer_extended.geometryC.ModuleTemperatures[5]) + " °C

"; + + return content; + } +}; + +#endif diff --git a/Software/src/battery/KIA-HYUNDAI-64-BATTERY.h b/Software/src/battery/KIA-HYUNDAI-64-BATTERY.h index 16ecf0d1..62dbbde0 100644 --- a/Software/src/battery/KIA-HYUNDAI-64-BATTERY.h +++ b/Software/src/battery/KIA-HYUNDAI-64-BATTERY.h @@ -5,6 +5,7 @@ #include "../datalayer/datalayer_extended.h" #include "../include.h" #include "CanBattery.h" +#include "KIA-HYUNDAI-64-HTML.h" #define BATTERY_SELECTED #define SELECTED_BATTERY_CLASS KiaHyundai64Battery @@ -13,20 +14,19 @@ class KiaHyundai64Battery : public CanBattery { public: // Use this constructor for the second battery. KiaHyundai64Battery(DATALAYER_BATTERY_TYPE* datalayer_ptr, DATALAYER_INFO_KIAHYUNDAI64* extended_ptr, - bool* contactor_closing_allowed_ptr, int targetCan) { + bool* contactor_closing_allowed_ptr, CAN_Interface targetCan) + : CanBattery(targetCan), renderer(extended_ptr) { datalayer_battery = datalayer_ptr; contactor_closing_allowed = contactor_closing_allowed_ptr; allows_contactor_closing = nullptr; - can_interface = targetCan; datalayer_battery_extended = extended_ptr; } // Use the default constructor to create the first or single battery. - KiaHyundai64Battery() { + KiaHyundai64Battery() : renderer(&datalayer_extended.KiaHyundai64) { datalayer_battery = &datalayer.battery; allows_contactor_closing = &datalayer.system.status.battery_allows_contactor_closing; contactor_closing_allowed = nullptr; - can_interface = can_config.battery; datalayer_battery_extended = &datalayer_extended.KiaHyundai64; } @@ -35,7 +35,11 @@ class KiaHyundai64Battery : public CanBattery { virtual void update_values(); virtual void transmit_can(unsigned long currentMillis); + BatteryHtmlRenderer& get_status_renderer() { return renderer; } + private: + KiaHyundai64HtmlRenderer renderer; + DATALAYER_BATTERY_TYPE* datalayer_battery; DATALAYER_INFO_KIAHYUNDAI64* datalayer_battery_extended; @@ -45,8 +49,6 @@ class KiaHyundai64Battery : public CanBattery { // If not null, this battery listens to this boolean to determine whether contactor closing is allowed bool* contactor_closing_allowed; - int can_interface; - void update_number_of_cells(); static const int MAX_PACK_VOLTAGE_98S_DV = 4110; //5000 = 500.0V diff --git a/Software/src/battery/KIA-HYUNDAI-64-HTML.h b/Software/src/battery/KIA-HYUNDAI-64-HTML.h new file mode 100644 index 00000000..23d5916e --- /dev/null +++ b/Software/src/battery/KIA-HYUNDAI-64-HTML.h @@ -0,0 +1,35 @@ +#ifndef _KIA_HYUNDAI_64_HTML_H +#define _KIA_HYUNDAI_64_HTML_H + +#include "../datalayer/datalayer.h" +#include "../datalayer/datalayer_extended.h" +#include "src/devboard/webserver/BatteryHtmlRenderer.h" + +class KiaHyundai64HtmlRenderer : public BatteryHtmlRenderer { + public: + KiaHyundai64HtmlRenderer(DATALAYER_INFO_KIAHYUNDAI64* dl) : kia_datalayer(dl) {} + + String get_status_html() { + String content; + + auto print_hyundai = [&content](DATALAYER_INFO_KIAHYUNDAI64& data) { + content += "

Cells: " + String(data.total_cell_count) + "S

"; + content += "

12V voltage: " + String(data.battery_12V / 10.0, 1) + "

"; + content += "

Waterleakage: " + String(data.waterleakageSensor) + "

"; + content += "

Temperature, water inlet: " + String(data.temperature_water_inlet) + "

"; + content += "

Temperature, power relay: " + String(data.powerRelayTemperature) + "

"; + content += "

Batterymanagement mode: " + String(data.batteryManagementMode) + "

"; + content += "

BMS ignition: " + String(data.BMS_ign) + "

"; + content += "

Battery relay: " + String(data.batteryRelay) + "

"; + }; + + print_hyundai(*kia_datalayer); + + return content; + } + + private: + DATALAYER_INFO_KIAHYUNDAI64* kia_datalayer; +}; + +#endif diff --git a/Software/src/battery/MEB-BATTERY.h b/Software/src/battery/MEB-BATTERY.h index 6aff0b87..184d5c8f 100644 --- a/Software/src/battery/MEB-BATTERY.h +++ b/Software/src/battery/MEB-BATTERY.h @@ -3,6 +3,7 @@ #include #include "../include.h" #include "CanBattery.h" +#include "MEB-HTML.h" #define BATTERY_SELECTED #define SELECTED_BATTERY_CLASS MebBattery @@ -13,8 +14,12 @@ class MebBattery : public CanBattery { virtual void handle_incoming_can_frame(CAN_frame rx_frame); virtual void update_values(); virtual void transmit_can(unsigned long currentMillis); + bool supports_real_BMS_status() { return true; } + + BatteryHtmlRenderer& get_status_renderer() { return renderer; } private: + MebHtmlRenderer renderer; static const int MAX_PACK_VOLTAGE_84S_DV = 3528; //5000 = 500.0V static const int MIN_PACK_VOLTAGE_84S_DV = 2520; static const int MAX_PACK_VOLTAGE_96S_DV = 4032; diff --git a/Software/src/battery/MEB-HTML.h b/Software/src/battery/MEB-HTML.h new file mode 100644 index 00000000..401236fd --- /dev/null +++ b/Software/src/battery/MEB-HTML.h @@ -0,0 +1,286 @@ +#ifndef _MEB_HTML_H +#define _MEB_HTML_H + +#include "../datalayer/datalayer.h" +#include "../datalayer/datalayer_extended.h" +#include "src/devboard/webserver/BatteryHtmlRenderer.h" + +class MebHtmlRenderer : public BatteryHtmlRenderer { + public: + String get_status_html() { + String content; + + content += datalayer_extended.meb.SDSW ? "

Service disconnect switch: Missing!

" + : "

Service disconnect switch: OK

"; + content += datalayer_extended.meb.pilotline ? "

Pilotline: Open!

" : "

Pilotline: OK

"; + content += datalayer_extended.meb.transportmode ? "

Transportmode: Locked!

" : "

Transportmode: OK

"; + content += datalayer_extended.meb.shutdown_active ? "

Shutdown: Active!

" : "

Shutdown: No

"; + content += datalayer_extended.meb.componentprotection ? "

Component protection: Active!

" + : "

Component protection: No

"; + content += "

HVIL status: "; + switch (datalayer_extended.meb.HVIL) { + case 0: + content += String("Init"); + break; + case 1: + content += String("Closed"); + break; + case 2: + content += String("Open!"); + break; + case 3: + content += String("Fault"); + break; + default: + content += String("?"); + } + content += "

KL30C status: "; + switch (datalayer_extended.meb.BMS_Kl30c_Status) { + case 0: + content += String("Init"); + break; + case 1: + content += String("Closed"); + break; + case 2: + content += String("Open!"); + break; + case 3: + content += String("Fault"); + break; + default: + content += String("?"); + } + content += "

BMS mode: "; + switch (datalayer_extended.meb.BMS_mode) { + case 0: + content += String("HV inactive"); + break; + case 1: + content += String("HV active"); + break; + case 2: + content += String("Balancing"); + break; + case 3: + content += String("Extern charging"); + break; + case 4: + content += String("AC charging"); + break; + case 5: + content += String("Battery error"); + break; + case 6: + content += String("DC charging"); + break; + case 7: + content += String("Init"); + break; + default: + content += String("?"); + } + content += String("

Charging: ") + (datalayer_extended.meb.charging_active ? "active" : "not active"); + content += String("

Balancing: "); + switch (datalayer_extended.meb.balancing_active) { + case 0: + content += String("init"); + break; + case 1: + content += String("active"); + break; + case 2: + content += String("inactive"); + break; + default: + content += String("?"); + } + content += + String("

Slow charging: ") + (datalayer_extended.meb.balancing_request ? "requested" : "not requested"); + content += "

Diagnostic: "; + switch (datalayer_extended.meb.battery_diagnostic) { + case 0: + content += String("Init"); + break; + case 1: + content += String("Battery display"); + break; + case 4: + content += String("Battery display OK"); + break; + case 6: + content += String("Battery display check"); + break; + case 7: + content += String("Fault"); + break; + default: + content += String("?"); + } + content += "

HV line status: "; + switch (datalayer_extended.meb.status_HV_line) { + case 0: + content += String("Init"); + break; + case 1: + content += String("No open HV line detected"); + break; + case 2: + content += String("Open HV line"); + break; + case 3: + content += String("Fault"); + break; + default: + content += String("? ") + String(datalayer_extended.meb.status_HV_line); + } + content += "

"; + content += datalayer_extended.meb.BMS_fault_performance ? "

BMS fault performance: Active!

" + : "

BMS fault performance: Off

"; + content += datalayer_extended.meb.BMS_fault_emergency_shutdown_crash + ? "

BMS fault emergency shutdown crash: Active!

" + : "

BMS fault emergency shutdown crash: Off

"; + content += datalayer_extended.meb.BMS_error_shutdown_request ? "

BMS error shutdown request: Active!

" + : "

BMS error shutdown request: Inactive

"; + content += datalayer_extended.meb.BMS_error_shutdown ? "

BMS error shutdown: Active!

" + : "

BMS error shutdown: Off

"; + content += "

Welded contactors: "; + switch (datalayer_extended.meb.BMS_welded_contactors_status) { + case 0: + content += String("Init"); + break; + case 1: + content += String("No contactor welded"); + break; + case 2: + content += String("At least 1 contactor welded"); + break; + case 3: + content += String("Protection status detection error"); + break; + default: + content += String("?"); + } + content += "

Warning support: "; + switch (datalayer_extended.meb.warning_support) { + case 0: + content += String("OK"); + break; + case 1: + content += String("Not OK"); + break; + case 6: + content += String("Init"); + break; + case 7: + content += String("Fault"); + break; + default: + content += String("?"); + } + content += "

Interm. Voltage (" + String(datalayer_extended.meb.BMS_voltage_intermediate_dV / 10.0, 1) + + "V) status: "; + switch (datalayer_extended.meb.BMS_status_voltage_free) { + case 0: + content += String("Init"); + break; + case 1: + content += String("BMS interm circuit voltage free (U<20V)"); + break; + case 2: + content += String("BMS interm circuit not voltage free (U >= 25V)"); + break; + case 3: + content += String("Error"); + break; + default: + content += String("?"); + } + content += "

BMS error status: "; + switch (datalayer_extended.meb.BMS_error_status) { + case 0: + content += String("Component IO"); + break; + case 1: + content += String("Iso Error 1"); + break; + case 2: + content += String("Iso Error 2"); + break; + case 3: + content += String("Interlock"); + break; + case 4: + content += String("SD"); + break; + case 5: + content += String("Performance red"); + break; + case 6: + content += String("No component function"); + break; + case 7: + content += String("Init"); + break; + default: + content += String("?"); + } + content += "

BMS voltage: " + String(datalayer_extended.meb.BMS_voltage_dV / 10.0, 1) + "

"; + content += datalayer_extended.meb.BMS_OBD_MIL ? "

OBD MIL: ON!

" : "

OBD MIL: Off

"; + content += + datalayer_extended.meb.BMS_error_lamp_req ? "

Red error lamp: ON!

" : "

Red error lamp: Off

"; + content += datalayer_extended.meb.BMS_warning_lamp_req ? "

Yellow warning lamp: ON!

" + : "

Yellow warning lamp: Off

"; + content += "

Isolation resistance: " + String(datalayer_extended.meb.isolation_resistance) + " kOhm

"; + content += + datalayer_extended.meb.battery_heating ? "

Battery heating: Active!

" : "

Battery heating: Off

"; + const char* rt_enum[] = {"No", "Error level 1", "Error level 2", "Error level 3"}; + content += "

Overcurrent: " + String(rt_enum[datalayer_extended.meb.rt_overcurrent & 0x03]) + "

"; + content += "

CAN fault: " + String(rt_enum[datalayer_extended.meb.rt_CAN_fault & 0x03]) + "

"; + content += "

Overcharged: " + String(rt_enum[datalayer_extended.meb.rt_overcharge & 0x03]) + "

"; + content += "

SOC too high: " + String(rt_enum[datalayer_extended.meb.rt_SOC_high & 0x03]) + "

"; + content += "

SOC too low: " + String(rt_enum[datalayer_extended.meb.rt_SOC_low & 0x03]) + "

"; + content += "

SOC jumping: " + String(rt_enum[datalayer_extended.meb.rt_SOC_jumping & 0x03]) + "

"; + content += "

Temp difference: " + String(rt_enum[datalayer_extended.meb.rt_temp_difference & 0x03]) + "

"; + content += "

Cell overtemp: " + String(rt_enum[datalayer_extended.meb.rt_cell_overtemp & 0x03]) + "

"; + content += "

Cell undertemp: " + String(rt_enum[datalayer_extended.meb.rt_cell_undertemp & 0x03]) + "

"; + content += + "

Battery overvoltage: " + String(rt_enum[datalayer_extended.meb.rt_battery_overvolt & 0x03]) + "

"; + content += + "

Battery undervoltage: " + String(rt_enum[datalayer_extended.meb.rt_battery_undervol & 0x03]) + "

"; + content += "

Cell overvoltage: " + String(rt_enum[datalayer_extended.meb.rt_cell_overvolt & 0x03]) + "

"; + content += "

Cell undervoltage: " + String(rt_enum[datalayer_extended.meb.rt_cell_undervol & 0x03]) + "

"; + content += "

Cell imbalance: " + String(rt_enum[datalayer_extended.meb.rt_cell_imbalance & 0x03]) + "

"; + content += + "

Battery unathorized: " + String(rt_enum[datalayer_extended.meb.rt_battery_unathorized & 0x03]) + "

"; + content += + "

Battery temperature: " + String(datalayer_extended.meb.battery_temperature_dC / 10.f, 1) + " °C

"; + for (int i = 0; i < 3; i++) { + content += "

Temperature points " + String(i * 6 + 1) + "-" + String(i * 6 + 6) + " :"; + for (int j = 0; j < 6; j++) + content += "  " + String(datalayer_extended.meb.temp_points[i * 6 + j], 1); + content += " °C

"; + } + bool temps_done = false; + for (int i = 0; i < 7 && !temps_done; i++) { + content += "

Cell temperatures " + String(i * 8 + 1) + "-" + String(i * 8 + 8) + " :"; + for (int j = 0; j < 8; j++) { + if (datalayer_extended.meb.celltemperature_dC[i * 8 + j] == 865) { + temps_done = true; + break; + } else { + content += "  " + String(datalayer_extended.meb.celltemperature_dC[i * 8 + j] / 10.f, 1); + } + } + content += " °C

"; + } + content += + "

Total charged: " + String(datalayer.battery.status.total_charged_battery_Wh / 1000.0, 1) + " kWh

"; + content += "

Total discharged: " + String(datalayer.battery.status.total_discharged_battery_Wh / 1000.0, 1) + + " kWh

"; + + return content; + } +}; + +#endif diff --git a/Software/src/battery/NISSAN-LEAF-BATTERY.cpp b/Software/src/battery/NISSAN-LEAF-BATTERY.cpp index ac82666f..761636a2 100644 --- a/Software/src/battery/NISSAN-LEAF-BATTERY.cpp +++ b/Software/src/battery/NISSAN-LEAF-BATTERY.cpp @@ -20,6 +20,11 @@ short ShortMaskedSumAndProduct(short param_1, short param_2); unsigned int MaskedBitwiseRotateMultiply(unsigned int param_1, unsigned int param_2); unsigned int CryptAlgo(unsigned int param_1, unsigned int param_2, unsigned int param_3); +// Note this should only be allowed/used on 2011-2017 24/30kWh batteries! +bool NissanLeafBattery::supports_reset_SOH() { + return LEAF_battery_Type != ZE1_BATTERY; +} + void NissanLeafBattery:: update_values() { /* This function maps all the values fetched via CAN to the correct parameters used for modbus */ /* Start with mapping all values */ diff --git a/Software/src/battery/NISSAN-LEAF-BATTERY.h b/Software/src/battery/NISSAN-LEAF-BATTERY.h index 2044bb84..a488f071 100644 --- a/Software/src/battery/NISSAN-LEAF-BATTERY.h +++ b/Software/src/battery/NISSAN-LEAF-BATTERY.h @@ -5,6 +5,7 @@ #include "../datalayer/datalayer_extended.h" #include "../include.h" #include "CanBattery.h" +#include "NISSAN-LEAF-HTML.h" #define BATTERY_SELECTED #define SELECTED_BATTERY_CLASS NissanLeafBattery @@ -19,11 +20,12 @@ class NissanLeafBattery : public CanBattery { public: // Use this constructor for the second battery. - NissanLeafBattery(DATALAYER_BATTERY_TYPE* datalayer_ptr, DATALAYER_INFO_NISSAN_LEAF* extended, int targetCan) { + NissanLeafBattery(DATALAYER_BATTERY_TYPE* datalayer_ptr, DATALAYER_INFO_NISSAN_LEAF* extended, + CAN_Interface targetCan) + : CanBattery(targetCan) { datalayer_battery = datalayer_ptr; allows_contactor_closing = nullptr; datalayer_nissan = extended; - can_interface = targetCan; battery_Total_Voltage2 = 0; } @@ -33,7 +35,6 @@ class NissanLeafBattery : public CanBattery { datalayer_battery = &datalayer.battery; allows_contactor_closing = &datalayer.system.status.battery_allows_contactor_closing; datalayer_nissan = &datalayer_extended.nissanleaf; - can_interface = can_config.battery; } virtual void setup(void); @@ -41,7 +42,15 @@ class NissanLeafBattery : public CanBattery { virtual void update_values(); virtual void transmit_can(unsigned long currentMillis); + bool supports_reset_SOH(); + + void reset_SOH() { datalayer_extended.nissanleaf.UserRequestSOHreset = true; } + + BatteryHtmlRenderer& get_status_renderer() { return renderer; } + private: + NissanLeafHtmlRenderer renderer; + bool is_message_corrupt(CAN_frame rx_frame); void clearSOH(void); @@ -51,8 +60,6 @@ class NissanLeafBattery : public CanBattery { // If not null, this battery decides when the contactor can be closed and writes the value here. bool* allows_contactor_closing; - int can_interface; - unsigned long previousMillis10 = 0; // will store last time a 10ms CAN Message was send unsigned long previousMillis100 = 0; // will store last time a 100ms CAN Message was send unsigned long previousMillis10s = 0; // will store last time a 1s CAN Message was send @@ -60,6 +67,10 @@ class NissanLeafBattery : public CanBattery { uint8_t mprun10 = 0; //counter 0-3 uint8_t mprun100 = 0; //counter 0-3 + static const int ZE0_BATTERY = 0; + static const int AZE0_BATTERY = 1; + static const int ZE1_BATTERY = 2; + // These CAN messages need to be sent towards the battery to keep it alive CAN_frame LEAF_1F2 = {.FD = false, .ext_ID = false, @@ -116,10 +127,7 @@ class NissanLeafBattery : public CanBattery { 196, 65, 75, 206, 76, 201, 195, 70, 215, 82, 88, 221, 255, 122, 112, 245, 100, 225, 235, 110, 175, 42, 32, 165, 52, 177, 187, 62, 28, 153, 147, 22, 135, 2, 8, 141}; -//Nissan LEAF battery parameters from constantly sent CAN -#define ZE0_BATTERY 0 -#define AZE0_BATTERY 1 -#define ZE1_BATTERY 2 + //Nissan LEAF battery parameters from constantly sent CAN uint8_t LEAF_battery_Type = ZE0_BATTERY; bool battery_can_alive = false; #define WH_PER_GID 77 //One GID is this amount of Watt hours diff --git a/Software/src/battery/NISSAN-LEAF-HTML.h b/Software/src/battery/NISSAN-LEAF-HTML.h new file mode 100644 index 00000000..18aac585 --- /dev/null +++ b/Software/src/battery/NISSAN-LEAF-HTML.h @@ -0,0 +1,52 @@ +#ifndef _NISSAN_LEAF_HTML_H +#define _NISSAN_LEAF_HTML_H + +#include "../datalayer/datalayer.h" +#include "../datalayer/datalayer_extended.h" +#include "src/devboard/webserver/BatteryHtmlRenderer.h" + +class NissanLeafHtmlRenderer : public BatteryHtmlRenderer { + public: + String get_status_html() { + String content; + + static const char* LEAFgen[] = {"ZE0", "AZE0", "ZE1"}; + content += "

LEAF generation: " + String(LEAFgen[datalayer_extended.nissanleaf.LEAF_gen]) + "

"; + char readableSerialNumber[16]; // One extra space for null terminator + memcpy(readableSerialNumber, datalayer_extended.nissanleaf.BatterySerialNumber, + sizeof(datalayer_extended.nissanleaf.BatterySerialNumber)); + readableSerialNumber[15] = '\0'; // Null terminate the string + content += "

Serial number: " + String(readableSerialNumber) + "

"; + char readablePartNumber[8]; // One extra space for null terminator + memcpy(readablePartNumber, datalayer_extended.nissanleaf.BatteryPartNumber, + sizeof(datalayer_extended.nissanleaf.BatteryPartNumber)); + readablePartNumber[7] = '\0'; // Null terminate the string + content += "

Part number: " + String(readablePartNumber) + "

"; + char readableBMSID[9]; // One extra space for null terminator + memcpy(readableBMSID, datalayer_extended.nissanleaf.BMSIDcode, sizeof(datalayer_extended.nissanleaf.BMSIDcode)); + readableBMSID[8] = '\0'; // Null terminate the string + content += "

BMS ID: " + String(readableBMSID) + "

"; + content += "

GIDS: " + String(datalayer_extended.nissanleaf.GIDS) + "

"; + content += "

Regen kW: " + String(datalayer_extended.nissanleaf.ChargePowerLimit) + "

"; + content += "

Charge kW: " + String(datalayer_extended.nissanleaf.MaxPowerForCharger) + "

"; + content += "

Interlock: " + String(datalayer_extended.nissanleaf.Interlock) + "

"; + content += "

Insulation: " + String(datalayer_extended.nissanleaf.Insulation) + "

"; + content += "

Relay cut request: " + String(datalayer_extended.nissanleaf.RelayCutRequest) + "

"; + content += "

Failsafe status: " + String(datalayer_extended.nissanleaf.FailsafeStatus) + "

"; + content += "

Fully charged: " + String(datalayer_extended.nissanleaf.Full) + "

"; + content += "

Battery empty: " + String(datalayer_extended.nissanleaf.Empty) + "

"; + content += "

Main relay ON: " + String(datalayer_extended.nissanleaf.MainRelayOn) + "

"; + content += "

Heater present: " + String(datalayer_extended.nissanleaf.HeatExist) + "

"; + content += "

Heating stopped: " + String(datalayer_extended.nissanleaf.HeatingStop) + "

"; + content += "

Heating started: " + String(datalayer_extended.nissanleaf.HeatingStart) + "

"; + content += "

Heating requested: " + String(datalayer_extended.nissanleaf.HeaterSendRequest) + "

"; + content += "

CryptoChallenge: " + String(datalayer_extended.nissanleaf.CryptoChallenge) + "

"; + content += "

SolvedChallenge: " + String(datalayer_extended.nissanleaf.SolvedChallengeMSB) + + String(datalayer_extended.nissanleaf.SolvedChallengeLSB) + "

"; + content += "

Challenge failed: " + String(datalayer_extended.nissanleaf.challengeFailed) + "

"; + + return content; + } +}; + +#endif diff --git a/Software/src/battery/PYLON-BATTERY.h b/Software/src/battery/PYLON-BATTERY.h index be0ac089..cf21aeb5 100644 --- a/Software/src/battery/PYLON-BATTERY.h +++ b/Software/src/battery/PYLON-BATTERY.h @@ -12,11 +12,11 @@ class PylonBattery : public CanBattery { public: // Use this constructor for the second battery. - PylonBattery(DATALAYER_BATTERY_TYPE* datalayer_ptr, bool* contactor_closing_allowed_ptr, int targetCan) { + PylonBattery(DATALAYER_BATTERY_TYPE* datalayer_ptr, bool* contactor_closing_allowed_ptr, CAN_Interface targetCan) + : CanBattery(targetCan) { datalayer_battery = datalayer_ptr; contactor_closing_allowed = contactor_closing_allowed_ptr; allows_contactor_closing = nullptr; - can_interface = targetCan; } // Use the default constructor to create the first or single battery. @@ -24,7 +24,6 @@ class PylonBattery : public CanBattery { datalayer_battery = &datalayer.battery; allows_contactor_closing = &datalayer.system.status.battery_allows_contactor_closing; contactor_closing_allowed = nullptr; - can_interface = can_config.battery; } virtual void setup(void); @@ -48,8 +47,6 @@ class PylonBattery : public CanBattery { // If not null, this battery listens to this boolean to determine whether contactor closing is allowed bool* contactor_closing_allowed; - int can_interface; - unsigned long previousMillis1000 = 0; // will store last time a 1s CAN Message was sent //Actual content messages diff --git a/Software/src/battery/RENAULT-ZOE-GEN1-BATTERY.h b/Software/src/battery/RENAULT-ZOE-GEN1-BATTERY.h index dee94a35..1e566b04 100644 --- a/Software/src/battery/RENAULT-ZOE-GEN1-BATTERY.h +++ b/Software/src/battery/RENAULT-ZOE-GEN1-BATTERY.h @@ -2,6 +2,7 @@ #define RENAULT_ZOE_GEN1_BATTERY_H #include "CanBattery.h" +#include "RENAULT-ZOE-GEN1-HTML.h" #define BATTERY_SELECTED #define SELECTED_BATTERY_CLASS RenaultZoeGen1Battery @@ -18,6 +19,11 @@ class RenaultZoeGen1Battery : public CanBattery { virtual void handle_incoming_can_frame(CAN_frame rx_frame); virtual void update_values(); virtual void transmit_can(unsigned long currentMillis); + + BatteryHtmlRenderer& get_status_renderer() { return renderer; } + + private: + RenaultZoeGen1HtmlRenderer renderer; }; #endif diff --git a/Software/src/battery/RENAULT-ZOE-GEN1-HTML.h b/Software/src/battery/RENAULT-ZOE-GEN1-HTML.h new file mode 100644 index 00000000..ff8765cd --- /dev/null +++ b/Software/src/battery/RENAULT-ZOE-GEN1-HTML.h @@ -0,0 +1,26 @@ +#ifndef _RENAULT_ZOE_GEN1_HTML_H +#define _RENAULT_ZOE_GEN1_HTML_H + +#include "../datalayer/datalayer.h" +#include "../datalayer/datalayer_extended.h" +#include "src/devboard/webserver/BatteryHtmlRenderer.h" + +class RenaultZoeGen1HtmlRenderer : public BatteryHtmlRenderer { + public: + String get_status_html() { + String content; + + content += "

CUV " + String(datalayer_extended.zoe.CUV) + "

"; + content += "

HVBIR " + String(datalayer_extended.zoe.HVBIR) + "

"; + content += "

HVBUV " + String(datalayer_extended.zoe.HVBUV) + "

"; + content += "

EOCR " + String(datalayer_extended.zoe.EOCR) + "

"; + content += "

HVBOC " + String(datalayer_extended.zoe.HVBOC) + "

"; + content += "

HVBOT " + String(datalayer_extended.zoe.HVBOT) + "

"; + content += "

HVBOV " + String(datalayer_extended.zoe.HVBOV) + "

"; + content += "

COV " + String(datalayer_extended.zoe.COV) + "

"; + + return content; + } +}; + +#endif diff --git a/Software/src/battery/RENAULT-ZOE-GEN2-BATTERY.h b/Software/src/battery/RENAULT-ZOE-GEN2-BATTERY.h index 31e4cfde..b3b2b5d7 100644 --- a/Software/src/battery/RENAULT-ZOE-GEN2-BATTERY.h +++ b/Software/src/battery/RENAULT-ZOE-GEN2-BATTERY.h @@ -3,6 +3,7 @@ #include "../include.h" #include "CanBattery.h" +#include "RENAULT-ZOE-GEN2-HTML.h" #define BATTERY_SELECTED #define SELECTED_BATTERY_CLASS RenaultZoeGen2Battery @@ -14,7 +15,14 @@ class RenaultZoeGen2Battery : public CanBattery { virtual void update_values(); virtual void transmit_can(unsigned long currentMillis); + bool supports_reset_NVROL() { return true; } + + void reset_NVROL() { datalayer_extended.zoePH2.UserRequestNVROLReset = true; } + + BatteryHtmlRenderer& get_status_renderer() { return renderer; } + private: + RenaultZoeGen2HtmlRenderer renderer; static const int MAX_PACK_VOLTAGE_DV = 4100; //5000 = 500.0V static const int MIN_PACK_VOLTAGE_DV = 3000; static const int MAX_CELL_DEVIATION_MV = 150; diff --git a/Software/src/battery/RENAULT-ZOE-GEN2-HTML.h b/Software/src/battery/RENAULT-ZOE-GEN2-HTML.h new file mode 100644 index 00000000..217e94a7 --- /dev/null +++ b/Software/src/battery/RENAULT-ZOE-GEN2-HTML.h @@ -0,0 +1,62 @@ +#ifndef _RENAULT_ZOE_GEN2_HTML_H +#define _RENAULT_ZOE_GEN2_HTML_H + +#include "../datalayer/datalayer.h" +#include "../datalayer/datalayer_extended.h" +#include "src/devboard/webserver/BatteryHtmlRenderer.h" + +class RenaultZoeGen2HtmlRenderer : public BatteryHtmlRenderer { + public: + String get_status_html() { + String content; + + content += "

soc: " + String(datalayer_extended.zoePH2.battery_soc) + "

"; + content += "

usable soc: " + String(datalayer_extended.zoePH2.battery_usable_soc) + "

"; + content += "

soh: " + String(datalayer_extended.zoePH2.battery_soh) + "

"; + content += "

pack voltage: " + String(datalayer_extended.zoePH2.battery_pack_voltage) + "

"; + content += "

max cell voltage: " + String(datalayer_extended.zoePH2.battery_max_cell_voltage) + "

"; + content += "

min cell voltage: " + String(datalayer_extended.zoePH2.battery_min_cell_voltage) + "

"; + content += "

12v: " + String(datalayer_extended.zoePH2.battery_12v) + "

"; + content += "

avg temp: " + String(datalayer_extended.zoePH2.battery_avg_temp) + "

"; + content += "

min temp: " + String(datalayer_extended.zoePH2.battery_min_temp) + "

"; + content += "

max temp: " + String(datalayer_extended.zoePH2.battery_max_temp) + "

"; + content += "

max power: " + String(datalayer_extended.zoePH2.battery_max_power) + "

"; + content += "

interlock: " + String(datalayer_extended.zoePH2.battery_interlock) + "

"; + content += "

kwh: " + String(datalayer_extended.zoePH2.battery_kwh) + "

"; + content += "

current: " + String(datalayer_extended.zoePH2.battery_current) + "

"; + content += "

current offset: " + String(datalayer_extended.zoePH2.battery_current_offset) + "

"; + content += "

max generated: " + String(datalayer_extended.zoePH2.battery_max_generated) + "

"; + content += "

max available: " + String(datalayer_extended.zoePH2.battery_max_available) + "

"; + content += "

current voltage: " + String(datalayer_extended.zoePH2.battery_current_voltage) + "

"; + content += "

charging status: " + String(datalayer_extended.zoePH2.battery_charging_status) + "

"; + content += "

remaining charge: " + String(datalayer_extended.zoePH2.battery_remaining_charge) + "

"; + content += + "

balance capacity total: " + String(datalayer_extended.zoePH2.battery_balance_capacity_total) + "

"; + content += "

balance time total: " + String(datalayer_extended.zoePH2.battery_balance_time_total) + "

"; + content += + "

balance capacity sleep: " + String(datalayer_extended.zoePH2.battery_balance_capacity_sleep) + "

"; + content += "

balance time sleep: " + String(datalayer_extended.zoePH2.battery_balance_time_sleep) + "

"; + content += + "

balance capacity wake: " + String(datalayer_extended.zoePH2.battery_balance_capacity_wake) + "

"; + content += "

balance time wake: " + String(datalayer_extended.zoePH2.battery_balance_time_wake) + "

"; + content += "

bms state: " + String(datalayer_extended.zoePH2.battery_bms_state) + "

"; + content += "

balance switches: " + String(datalayer_extended.zoePH2.battery_balance_switches) + "

"; + content += "

energy complete: " + String(datalayer_extended.zoePH2.battery_energy_complete) + "

"; + content += "

energy partial: " + String(datalayer_extended.zoePH2.battery_energy_partial) + "

"; + content += "

slave failures: " + String(datalayer_extended.zoePH2.battery_slave_failures) + "

"; + content += "

mileage: " + String(datalayer_extended.zoePH2.battery_mileage) + "

"; + content += "

fan speed: " + String(datalayer_extended.zoePH2.battery_fan_speed) + "

"; + content += "

fan period: " + String(datalayer_extended.zoePH2.battery_fan_period) + "

"; + content += "

fan control: " + String(datalayer_extended.zoePH2.battery_fan_control) + "

"; + content += "

fan duty: " + String(datalayer_extended.zoePH2.battery_fan_duty) + "

"; + content += "

temporisation: " + String(datalayer_extended.zoePH2.battery_temporisation) + "

"; + content += "

time: " + String(datalayer_extended.zoePH2.battery_time) + "

"; + content += "

pack time: " + String(datalayer_extended.zoePH2.battery_pack_time) + "

"; + content += "

soc min: " + String(datalayer_extended.zoePH2.battery_soc_min) + "

"; + content += "

soc max: " + String(datalayer_extended.zoePH2.battery_soc_max) + "

"; + + return content; + } +}; + +#endif diff --git a/Software/src/battery/RS485Battery.h b/Software/src/battery/RS485Battery.h index 8045bff7..dc014f67 100644 --- a/Software/src/battery/RS485Battery.h +++ b/Software/src/battery/RS485Battery.h @@ -10,6 +10,8 @@ class RS485Battery : public Battery { public: virtual void receive_RS485() = 0; virtual void transmit_rs485() = 0; + + String interface_name() { return "RS485"; } }; #endif diff --git a/Software/src/battery/SANTA-FE-PHEV-BATTERY.h b/Software/src/battery/SANTA-FE-PHEV-BATTERY.h index 91a11afe..f73e80e3 100644 --- a/Software/src/battery/SANTA-FE-PHEV-BATTERY.h +++ b/Software/src/battery/SANTA-FE-PHEV-BATTERY.h @@ -11,17 +11,15 @@ class SantaFePhevBattery : public CanBattery { public: // Use this constructor for the second battery. - SantaFePhevBattery(DATALAYER_BATTERY_TYPE* datalayer_ptr, int targetCan) { + SantaFePhevBattery(DATALAYER_BATTERY_TYPE* datalayer_ptr, CAN_Interface targetCan) : CanBattery(targetCan) { datalayer_battery = datalayer_ptr; allows_contactor_closing = nullptr; - can_interface = targetCan; } // Use the default constructor to create the first or single battery. SantaFePhevBattery() { datalayer_battery = &datalayer.battery; allows_contactor_closing = &datalayer.system.status.battery_allows_contactor_closing; - can_interface = can_config.battery; } virtual void setup(void); @@ -35,8 +33,6 @@ class SantaFePhevBattery : public CanBattery { // If not null, this battery decides when the contactor can be closed and writes the value here. bool* allows_contactor_closing; - int can_interface; - static const int MAX_PACK_VOLTAGE_DV = 4040; //5000 = 500.0V static const int MIN_PACK_VOLTAGE_DV = 2880; static const int MAX_CELL_DEVIATION_MV = 250; diff --git a/Software/src/battery/TESLA-BATTERY.h b/Software/src/battery/TESLA-BATTERY.h index 25fd59d7..cbbab2e3 100644 --- a/Software/src/battery/TESLA-BATTERY.h +++ b/Software/src/battery/TESLA-BATTERY.h @@ -3,6 +3,7 @@ #include "../datalayer/datalayer.h" #include "../include.h" #include "CanBattery.h" +#include "TESLA-HTML.h" #define BATTERY_SELECTED #ifdef TESLA_MODEL_3Y_BATTERY @@ -23,6 +24,17 @@ class TeslaBattery : public CanBattery { virtual void update_values(); virtual void transmit_can(unsigned long currentMillis); + bool supports_clear_isolation() { return true; } + void clear_isolation() { datalayer.battery.settings.user_requests_tesla_isolation_clear = true; } + + bool supports_reset_BMS() { return true; } + void reset_BMS() { datalayer.battery.settings.user_requests_tesla_bms_reset = true; } + + BatteryHtmlRenderer& get_status_renderer() { return renderer; } + + private: + TeslaHtmlRenderer renderer; + protected: /* Modify these if needed */ static const int MAXCHARGEPOWERALLOWED = diff --git a/Software/src/battery/TESLA-HTML.h b/Software/src/battery/TESLA-HTML.h new file mode 100644 index 00000000..111c7eba --- /dev/null +++ b/Software/src/battery/TESLA-HTML.h @@ -0,0 +1,431 @@ +#ifndef _TESLA_HTML_H +#define _TESLA_HTML_H + +#include "../datalayer/datalayer.h" +#include "../datalayer/datalayer_extended.h" +#include "src/devboard/webserver/BatteryHtmlRenderer.h" + +class TeslaHtmlRenderer : public BatteryHtmlRenderer { + public: + String get_status_html() { + String content; + + float beginning_of_life = static_cast(datalayer_extended.tesla.battery_beginning_of_life); + float battTempPct = static_cast(datalayer_extended.tesla.battery_battTempPct) * 0.4; + float dcdcLvBusVolt = static_cast(datalayer_extended.tesla.battery_dcdcLvBusVolt) * 0.0390625; + float dcdcHvBusVolt = static_cast(datalayer_extended.tesla.battery_dcdcHvBusVolt) * 0.146484; + float dcdcLvOutputCurrent = static_cast(datalayer_extended.tesla.battery_dcdcLvOutputCurrent) * 0.1; + float nominal_full_pack_energy = + static_cast(datalayer_extended.tesla.battery_nominal_full_pack_energy) * 0.1; + float nominal_full_pack_energy_m0 = + static_cast(datalayer_extended.tesla.battery_nominal_full_pack_energy_m0) * 0.02; + float nominal_energy_remaining = + static_cast(datalayer_extended.tesla.battery_nominal_energy_remaining) * 0.1; + float nominal_energy_remaining_m0 = + static_cast(datalayer_extended.tesla.battery_nominal_energy_remaining_m0) * 0.02; + float ideal_energy_remaining = static_cast(datalayer_extended.tesla.battery_ideal_energy_remaining) * 0.1; + float ideal_energy_remaining_m0 = + static_cast(datalayer_extended.tesla.battery_ideal_energy_remaining_m0) * 0.02; + float energy_to_charge_complete = + static_cast(datalayer_extended.tesla.battery_energy_to_charge_complete) * 0.1; + float energy_to_charge_complete_m1 = + static_cast(datalayer_extended.tesla.battery_energy_to_charge_complete_m1) * 0.02; + float energy_buffer = static_cast(datalayer_extended.tesla.battery_energy_buffer) * 0.1; + float energy_buffer_m1 = static_cast(datalayer_extended.tesla.battery_energy_buffer_m1) * 0.01; + float expected_energy_remaining_m1 = + static_cast(datalayer_extended.tesla.battery_expected_energy_remaining_m1) * 0.02; + float total_discharge = static_cast(datalayer.battery.status.total_discharged_battery_Wh) * 0.001; + float total_charge = static_cast(datalayer.battery.status.total_charged_battery_Wh) * 0.001; + float packMass = static_cast(datalayer_extended.tesla.battery_packMass); + float platformMaxBusVoltage = + static_cast(datalayer_extended.tesla.battery_platformMaxBusVoltage) * 0.1 + 375; + float bms_min_voltage = static_cast(datalayer_extended.tesla.battery_bms_min_voltage) * 0.01 * 2; + float bms_max_voltage = static_cast(datalayer_extended.tesla.battery_bms_max_voltage) * 0.01 * 2; + float max_charge_current = static_cast(datalayer_extended.tesla.battery_max_charge_current); + float max_discharge_current = static_cast(datalayer_extended.tesla.battery_max_discharge_current); + float soc_ave = static_cast(datalayer_extended.tesla.battery_soc_ave) * 0.1; + float soc_max = static_cast(datalayer_extended.tesla.battery_soc_max) * 0.1; + float soc_min = static_cast(datalayer_extended.tesla.battery_soc_min) * 0.1; + float soc_ui = static_cast(datalayer_extended.tesla.battery_soc_ui) * 0.1; + float BrickVoltageMax = static_cast(datalayer_extended.tesla.battery_BrickVoltageMax) * 0.002; + float BrickVoltageMin = static_cast(datalayer_extended.tesla.battery_BrickVoltageMin) * 0.002; + float BrickModelTMax = static_cast(datalayer_extended.tesla.battery_BrickModelTMax) * 0.5 - 40; + float BrickModelTMin = static_cast(datalayer_extended.tesla.battery_BrickModelTMin) * 0.5 - 40; + float isolationResistance = static_cast(datalayer_extended.tesla.battery_BMS_isolationResistance) * 10; + float PCS_dcdcMaxOutputCurrentAllowed = + static_cast(datalayer_extended.tesla.battery_PCS_dcdcMaxOutputCurrentAllowed) * 0.1; + float PCS_dcdcTemp = static_cast(datalayer_extended.tesla.PCS_dcdcTemp) * 0.1 + 40; + float PCS_ambientTemp = static_cast(datalayer_extended.tesla.PCS_ambientTemp) * 0.1 + 40; + float PCS_chgPhATemp = static_cast(datalayer_extended.tesla.PCS_chgPhATemp) * 0.1 + 40; + float PCS_chgPhBTemp = static_cast(datalayer_extended.tesla.PCS_chgPhBTemp) * 0.1 + 40; + float PCS_chgPhCTemp = static_cast(datalayer_extended.tesla.PCS_chgPhCTemp) * 0.1 + 40; + float BMS_maxRegenPower = static_cast(datalayer_extended.tesla.BMS_maxRegenPower) * 0.01; + float BMS_maxDischargePower = static_cast(datalayer_extended.tesla.BMS_maxDischargePower) * 0.013; + float BMS_maxStationaryHeatPower = static_cast(datalayer_extended.tesla.BMS_maxStationaryHeatPower) * 0.01; + float BMS_hvacPowerBudget = static_cast(datalayer_extended.tesla.BMS_hvacPowerBudget) * 0.02; + float BMS_powerDissipation = static_cast(datalayer_extended.tesla.BMS_powerDissipation) * 0.02; + float BMS_flowRequest = static_cast(datalayer_extended.tesla.BMS_flowRequest) * 0.3; + float BMS_inletActiveCoolTargetT = + static_cast(datalayer_extended.tesla.BMS_inletActiveCoolTargetT) * 0.25 - 25; + float BMS_inletPassiveTargetT = static_cast(datalayer_extended.tesla.BMS_inletPassiveTargetT) * 0.25 - 25; + float BMS_inletActiveHeatTargetT = + static_cast(datalayer_extended.tesla.BMS_inletActiveHeatTargetT) * 0.25 - 25; + float BMS_packTMin = static_cast(datalayer_extended.tesla.BMS_packTMin) * 0.25 - 25; + float BMS_packTMax = static_cast(datalayer_extended.tesla.BMS_packTMax) * 0.25 - 25; + float PCS_dcdcMaxLvOutputCurrent = static_cast(datalayer_extended.tesla.PCS_dcdcMaxLvOutputCurrent) * 0.1; + float PCS_dcdcCurrentLimit = static_cast(datalayer_extended.tesla.PCS_dcdcCurrentLimit) * 0.1; + float PCS_dcdcLvOutputCurrentTempLimit = + static_cast(datalayer_extended.tesla.PCS_dcdcLvOutputCurrentTempLimit) * 0.1; + float PCS_dcdcUnifiedCommand = static_cast(datalayer_extended.tesla.PCS_dcdcUnifiedCommand) * 0.001; + float PCS_dcdcCLAControllerOutput = + static_cast(datalayer_extended.tesla.PCS_dcdcCLAControllerOutput * 0.001); + float PCS_dcdcTankVoltage = static_cast(datalayer_extended.tesla.PCS_dcdcTankVoltage); + float PCS_dcdcTankVoltageTarget = static_cast(datalayer_extended.tesla.PCS_dcdcTankVoltageTarget); + float PCS_dcdcClaCurrentFreq = static_cast(datalayer_extended.tesla.PCS_dcdcClaCurrentFreq) * 0.0976563; + float PCS_dcdcTCommMeasured = static_cast(datalayer_extended.tesla.PCS_dcdcTCommMeasured) * 0.00195313; + float PCS_dcdcShortTimeUs = static_cast(datalayer_extended.tesla.PCS_dcdcShortTimeUs) * 0.000488281; + float PCS_dcdcHalfPeriodUs = static_cast(datalayer_extended.tesla.PCS_dcdcHalfPeriodUs) * 0.000488281; + float PCS_dcdcIntervalMaxFrequency = static_cast(datalayer_extended.tesla.PCS_dcdcIntervalMaxFrequency); + float PCS_dcdcIntervalMaxHvBusVolt = + static_cast(datalayer_extended.tesla.PCS_dcdcIntervalMaxHvBusVolt) * 0.1; + float PCS_dcdcIntervalMaxLvBusVolt = + static_cast(datalayer_extended.tesla.PCS_dcdcIntervalMaxLvBusVolt) * 0.1; + float PCS_dcdcIntervalMaxLvOutputCurr = + static_cast(datalayer_extended.tesla.PCS_dcdcIntervalMaxLvOutputCurr); + float PCS_dcdcIntervalMinFrequency = static_cast(datalayer_extended.tesla.PCS_dcdcIntervalMinFrequency); + float PCS_dcdcIntervalMinHvBusVolt = + static_cast(datalayer_extended.tesla.PCS_dcdcIntervalMinHvBusVolt) * 0.1; + float PCS_dcdcIntervalMinLvBusVolt = + static_cast(datalayer_extended.tesla.PCS_dcdcIntervalMinLvBusVolt) * 0.1; + float PCS_dcdcIntervalMinLvOutputCurr = + static_cast(datalayer_extended.tesla.PCS_dcdcIntervalMinLvOutputCurr); + float PCS_dcdc12vSupportLifetimekWh = + static_cast(datalayer_extended.tesla.PCS_dcdc12vSupportLifetimekWh) * 0.01; + float HVP_hvp1v5Ref = static_cast(datalayer_extended.tesla.HVP_hvp1v5Ref) * 0.1; + float HVP_shuntCurrentDebug = static_cast(datalayer_extended.tesla.HVP_shuntCurrentDebug) * 0.1; + float HVP_dcLinkVoltage = static_cast(datalayer_extended.tesla.HVP_dcLinkVoltage) * 0.1; + float HVP_packVoltage = static_cast(datalayer_extended.tesla.HVP_packVoltage) * 0.1; + float HVP_fcLinkVoltage = static_cast(datalayer_extended.tesla.HVP_fcLinkVoltage) * 0.1; + float HVP_packContVoltage = static_cast(datalayer_extended.tesla.HVP_packContVoltage) * 0.1; + float HVP_packNegativeV = static_cast(datalayer_extended.tesla.HVP_packNegativeV) * 0.1; + float HVP_packPositiveV = static_cast(datalayer_extended.tesla.HVP_packPositiveV) * 0.1; + float HVP_pyroAnalog = static_cast(datalayer_extended.tesla.HVP_pyroAnalog) * 0.1; + float HVP_dcLinkNegativeV = static_cast(datalayer_extended.tesla.HVP_dcLinkNegativeV) * 0.1; + float HVP_dcLinkPositiveV = static_cast(datalayer_extended.tesla.HVP_dcLinkPositiveV) * 0.1; + float HVP_fcLinkNegativeV = static_cast(datalayer_extended.tesla.HVP_fcLinkNegativeV) * 0.1; + float HVP_fcContCoilCurrent = static_cast(datalayer_extended.tesla.HVP_fcContCoilCurrent) * 0.1; + float HVP_fcContVoltage = static_cast(datalayer_extended.tesla.HVP_fcContVoltage) * 0.1; + float HVP_hvilInVoltage = static_cast(datalayer_extended.tesla.HVP_hvilInVoltage) * 0.1; + float HVP_hvilOutVoltage = static_cast(datalayer_extended.tesla.HVP_hvilOutVoltage) * 0.1; + float HVP_fcLinkPositiveV = static_cast(datalayer_extended.tesla.HVP_fcLinkPositiveV) * 0.1; + float HVP_packContCoilCurrent = static_cast(datalayer_extended.tesla.HVP_packContCoilCurrent) * 0.1; + float HVP_battery12V = static_cast(datalayer_extended.tesla.HVP_battery12V) * 0.1; + float HVP_shuntRefVoltageDbg = static_cast(datalayer_extended.tesla.HVP_shuntRefVoltageDbg) * 0.001; + float HVP_shuntAuxCurrentDbg = static_cast(datalayer_extended.tesla.HVP_shuntAuxCurrentDbg) * 0.1; + float HVP_shuntBarTempDbg = static_cast(datalayer_extended.tesla.HVP_shuntBarTempDbg) * 0.01; + float HVP_shuntAsicTempDbg = static_cast(datalayer_extended.tesla.HVP_shuntAsicTempDbg) * 0.01; + + static const char* contactorText[] = {"UNKNOWN(0)", "OPEN", "CLOSING", "BLOCKED", "OPENING", + "CLOSED", "UNKNOWN(6)", "WELDED", "POS_CL", "NEG_CL", + "UNKNOWN(10)", "UNKNOWN(11)", "UNKNOWN(12)"}; + static const char* hvilStatusState[] = {"NOT Ok", + "STATUS_OK", + "CURRENT_SOURCE_FAULT", + "INTERNAL_OPEN_FAULT", + "VEHICLE_OPEN_FAULT", + "PENTHOUSE_LID_OPEN_FAULT", + "UNKNOWN_LOCATION_OPEN_FAULT", + "VEHICLE_NODE_FAULT", + "NO_12V_SUPPLY", + "VEHICLE_OR_PENTHOUSE_LID_OPENFAULT", + "UNKNOWN(10)", + "UNKNOWN(11)", + "UNKNOWN(12)", + "UNKNOWN(13)", + "UNKNOWN(14)", + "UNKNOWN(15)"}; + static const char* contactorState[] = {"SNA", "OPEN", "PRECHARGE", "BLOCKED", + "PULLED_IN", "OPENING", "ECONOMIZED", "WELDED", + "UNKNOWN(8)", "UNKNOWN(9)", "UNKNOWN(10)", "UNKNOWN(11)"}; + static const char* BMS_state[] = {"STANDBY", "DRIVE", "SUPPORT", "CHARGE", "FEIM", + "CLEAR_FAULT", "FAULT", "WELD", "TEST", "SNA"}; + static const char* BMS_contactorState[] = {"SNA", "OPEN", "OPENING", "CLOSING", "CLOSED", "WELDED", "BLOCKED"}; + static const char* BMS_hvState[] = {"DOWN", "COMING_UP", "GOING_DOWN", "UP_FOR_DRIVE", + "UP_FOR_CHARGE", "UP_FOR_DC_CHARGE", "UP"}; + static const char* BMS_uiChargeStatus[] = {"DISCONNECTED", "NO_POWER", "ABOUT_TO_CHARGE", + "CHARGING", "CHARGE_COMPLETE", "CHARGE_STOPPED"}; + static const char* PCS_dcdcStatus[] = {"IDLE", "ACTIVE", "FAULTED"}; + static const char* PCS_dcdcMainState[] = {"STANDBY", "12V_SUPPORT_ACTIVE", "PRECHARGE_STARTUP", + "PRECHARGE_ACTIVE", "DIS_HVBUS_ACTIVE", "SHUTDOWN", + "FAULTED"}; + static const char* PCS_dcdcSubState[] = {"PWR_UP_INIT", + "STANDBY", + "12V_SUPPORT_ACTIVE", + "DIS_HVBUS", + "PCHG_FAST_DIS_HVBUS", + "PCHG_SLOW_DIS_HVBUS", + "PCHG_DWELL_CHARGE", + "PCHG_DWELL_WAIT", + "PCHG_DI_RECOVERY_WAIT", + "PCHG_ACTIVE", + "PCHG_FLT_FAST_DIS_HVBUS", + "SHUTDOWN", + "12V_SUPPORT_FAULTED", + "DIS_HVBUS_FAULTED", + "PCHG_FAULTED", + "CLEAR_FAULTS", + "FAULTED", + "NUM"}; + static const char* BMS_powerLimitState[] = {"NOT_CALCULATED_FOR_DRIVE", "CALCULATED_FOR_DRIVE"}; + static const char* HVP_status[] = {"INVALID", "NOT_AVAILABLE", "STALE", "VALID"}; + static const char* HVP_contactor[] = {"NOT_ACTIVE", "ACTIVE", "COMPLETED"}; + static const char* falseTrue[] = {"False", "True"}; + static const char* noYes[] = {"No", "Yes"}; + static const char* Fault[] = {"NOT_ACTIVE", "ACTIVE"}; + + //0x20A 522 HVP_contatorState + content += "

Contactor Status: " + String(contactorText[datalayer_extended.tesla.status_contactor]) + "

"; + content += "

HVIL: " + String(hvilStatusState[datalayer_extended.tesla.hvil_status]) + "

"; + content += + "

Negative contactor: " + String(contactorState[datalayer_extended.tesla.packContNegativeState]) + "

"; + content += + "

Positive contactor: " + String(contactorState[datalayer_extended.tesla.packContPositiveState]) + "

"; + content += "

Closing allowed?: " + String(noYes[datalayer_extended.tesla.packCtrsClosingAllowed]) + "

"; + content += "

Pyrotest in Progress: " + String(noYes[datalayer_extended.tesla.pyroTestInProgress]) + "

"; + content += "

Contactors Open Now Requested: " + + String(noYes[datalayer_extended.tesla.battery_packCtrsOpenNowRequested]) + "

"; + content += + "

Contactors Open Requested: " + String(noYes[datalayer_extended.tesla.battery_packCtrsOpenRequested]) + + "

"; + content += "

Contactors Request Status: " + + String(HVP_contactor[datalayer_extended.tesla.battery_packCtrsRequestStatus]) + "

"; + content += "

Contactors Reset Request Required: " + + String(noYes[datalayer_extended.tesla.battery_packCtrsResetRequestRequired]) + "

"; + content += + "

DC Link Allowed to Energize: " + String(noYes[datalayer_extended.tesla.battery_dcLinkAllowedToEnergize]) + + "

"; + char readableSerialNumber[15]; // One extra space for null terminator + memcpy(readableSerialNumber, datalayer_extended.tesla.BMS_SerialNumber, + sizeof(datalayer_extended.tesla.BMS_SerialNumber)); + readableSerialNumber[14] = '\0'; // Null terminate the string + content += "

BMS Serial number: " + String(readableSerialNumber) + "

"; + // Comment what data you would like to display, order can be changed. + //0x352 850 BMS_energyStatus + if (datalayer_extended.tesla.BMS352_mux == false) { + content += "

BMS 0x352 w/o mux

"; //if using older BMS <2021 and comment 0x352 without MUX + content += "

Calculated SOH: " + String(nominal_full_pack_energy * 100 / beginning_of_life) + "

"; + content += "

Nominal Full Pack Energy: " + String(nominal_full_pack_energy) + " KWh

"; + content += "

Nominal Energy Remaining: " + String(nominal_energy_remaining) + " KWh

"; + content += "

Ideal Energy Remaining: " + String(ideal_energy_remaining) + " KWh

"; + content += "

Energy to Charge Complete: " + String(energy_to_charge_complete) + " KWh

"; + content += "

Energy Buffer: " + String(energy_buffer) + " KWh

"; + content += "

Full Charge Complete: " + String(noYes[datalayer_extended.tesla.battery_full_charge_complete]) + + "

"; //bool + } + //0x352 850 BMS_energyStatus + if (datalayer_extended.tesla.BMS352_mux == true) { + content += "

BMS 0x352 w/ mux

"; //if using newer BMS >2021 and comment 0x352 with MUX + content += "

Calculated SOH: " + String(nominal_full_pack_energy_m0 * 100 / beginning_of_life) + "

"; + content += "

Nominal Full Pack Energy: " + String(nominal_full_pack_energy_m0) + " KWh

"; + content += "

Nominal Energy Remaining: " + String(nominal_energy_remaining_m0) + " KWh

"; + content += "

Ideal Energy Remaining: " + String(ideal_energy_remaining_m0) + " KWh

"; + content += "

Energy to Charge Complete: " + String(energy_to_charge_complete_m1) + " KWh

"; + content += "

Energy Buffer: " + String(energy_buffer_m1) + " KWh

"; + content += "

Expected Energy Remaining: " + String(expected_energy_remaining_m1) + " KWh

"; + content += "

Fully Charged: " + String(noYes[datalayer_extended.tesla.battery_fully_charged]) + "

"; + } + //0x3D2 978 BMS_kwhCounter + content += "

Total Discharge: " + String(total_discharge) + " KWh

"; + content += "

Total Charge: " + String(total_charge) + " KWh

"; + //0x292 658 BMS_socStates + content += "

Battery Beginning of Life: " + String(beginning_of_life) + " KWh

"; + content += "

Battery SOC UI: " + String(soc_ui) + "

"; + content += "

Battery SOC Ave: " + String(soc_ave) + "

"; + content += "

Battery SOC Max: " + String(soc_max) + "

"; + content += "

Battery SOC Min: " + String(soc_min) + "

"; + content += "

Battery Temp Percent: " + String(battTempPct) + "

"; + //0x2B4 PCS_dcdcRailStatus + content += "

PCS Lv Output: " + String(dcdcLvOutputCurrent) + " A

"; + content += "

PCS Lv Bus: " + String(dcdcLvBusVolt) + " V

"; + content += "

PCS Hv Bus: " + String(dcdcHvBusVolt) + " V

"; + //0x392 BMS_packConfig + //content += "

packConfigMultiplexer: " + String(datalayer_extended.tesla.battery_packConfigMultiplexer) + "

"; // Not giving useable data + //content += "

moduleType: " + String(datalayer_extended.tesla.battery_moduleType) + "

"; // Not giving useable data + //content += "

reserveConfig: " + String(datalayer_extended.tesla.battery_reservedConfig) + "

"; // Not giving useable data + content += "

Battery Pack Mass: " + String(packMass) + " KG

"; + content += "

Platform Max Bus Voltage: " + String(platformMaxBusVoltage) + " V

"; + //0x2D2 722 BMSVAlimits + content += "

BMS Min Voltage: " + String(bms_min_voltage) + " V

"; + content += "

BMS Max Voltage: " + String(bms_max_voltage) + " V

"; + content += "

Max Charge Current: " + String(max_charge_current) + " A

"; + content += "

Max Discharge Current: " + String(max_discharge_current) + " A

"; + //0x332 818 BMS_bmbMinMax + content += "

Brick Voltage Max: " + String(BrickVoltageMax) + " V

"; + content += "

Brick Voltage Min: " + String(BrickVoltageMin) + " V

"; + content += "

Brick Temp Max Num: " + String(datalayer_extended.tesla.battery_BrickTempMaxNum) + "

"; + content += "

Brick Temp Min Num: " + String(datalayer_extended.tesla.battery_BrickTempMinNum) + "

"; + //content += "

Brick Model Temp Max: " + String(BrickModelTMax) + " C

";// Not giving useable data + //content += "

Brick Model Temp Min: " + String(BrickModelTMin) + " C

";// Not giving useable data + //0x2A4 676 PCS_thermalStatus + content += "

PCS dcdc Temp: " + String(PCS_dcdcTemp) + " DegC

"; + content += "

PCS Ambient Temp: " + String(PCS_ambientTemp) + " DegC

"; + content += "

PCS Chg PhA Temp: " + String(PCS_chgPhATemp) + " DegC

"; + content += "

PCS Chg PhB Temp: " + String(PCS_chgPhBTemp) + " DegC

"; + content += "

PCS Chg PhC Temp: " + String(PCS_chgPhCTemp) + " DegC

"; + //0x252 594 BMS_powerAvailable + content += "

Max Regen Power: " + String(BMS_maxRegenPower) + " KW

"; + content += "

Max Discharge Power: " + String(BMS_maxDischargePower) + " KW

"; + //content += "

Max Stationary Heat Power: " + String(BMS_maxStationaryHeatPower) + " KWh

"; // Not giving useable data + //content += "

HVAC Power Budget: " + String(BMS_hvacPowerBudget) + " KW

"; // Not giving useable data + //content += "

Not Enough Power For Heat Pump: " + String(noYes[datalayer_extended.tesla.BMS_notEnoughPowerForHeatPump]) + "

"; // Not giving useable data + content += + "

Power Limit State: " + String(BMS_powerLimitState[datalayer_extended.tesla.BMS_powerLimitState]) + "

"; + //content += "

Inverter TQF: " + String(datalayer_extended.tesla.BMS_inverterTQF) + "

"; // Not giving useable data + //0x212 530 BMS_status + content += "

Isolation Resistance: " + String(isolationResistance) + " kOhms

"; + content += + "

BMS Contactor State: " + String(BMS_contactorState[datalayer_extended.tesla.battery_BMS_contactorState]) + + "

"; + content += "

BMS State: " + String(BMS_state[datalayer_extended.tesla.battery_BMS_state]) + "

"; + content += "

BMS HV State: " + String(BMS_hvState[datalayer_extended.tesla.battery_BMS_hvState]) + "

"; + content += "

BMS UI Charge Status: " + String(BMS_uiChargeStatus[datalayer_extended.tesla.battery_BMS_hvState]) + + "

"; + content += + "

BMS PCS PWM Enabled: " + String(Fault[datalayer_extended.tesla.battery_BMS_pcsPwmEnabled]) + "

"; + //0x312 786 BMS_thermalStatus + content += "

Power Dissipation: " + String(BMS_powerDissipation) + " kW

"; + content += "

Flow Request: " + String(BMS_flowRequest) + " LPM

"; + content += "

Inlet Active Cool Target Temp: " + String(BMS_inletActiveCoolTargetT) + " DegC

"; + content += "

Inlet Passive Target Temp: " + String(BMS_inletPassiveTargetT) + " DegC

"; + content += "

Inlet Active Heat Target Temp: " + String(BMS_inletActiveHeatTargetT) + " DegC

"; + content += "

Pack Temp Min: " + String(BMS_packTMin) + " DegC

"; + content += "

Pack Temp Max: " + String(BMS_packTMax) + " DegC

"; + content += "

PCS No Flow Request: " + String(Fault[datalayer_extended.tesla.BMS_pcsNoFlowRequest]) + "

"; + content += "

BMS No Flow Request: " + String(Fault[datalayer_extended.tesla.BMS_noFlowRequest]) + "

"; + //0x224 548 PCS_dcdcStatus + content += + "

Precharge Status: " + String(PCS_dcdcStatus[datalayer_extended.tesla.battery_PCS_dcdcPrechargeStatus]) + + "

"; + content += + "

12V Support Status: " + String(PCS_dcdcStatus[datalayer_extended.tesla.battery_PCS_dcdc12VSupportStatus]) + + "

"; + content += "

HV Bus Discharge Status: " + + String(PCS_dcdcStatus[datalayer_extended.tesla.battery_PCS_dcdcHvBusDischargeStatus]) + "

"; + content += + "

Main State: " + String(PCS_dcdcMainState[datalayer_extended.tesla.battery_PCS_dcdcMainState]) + "

"; + content += + "

Sub State: " + String(PCS_dcdcSubState[datalayer_extended.tesla.battery_PCS_dcdcSubState]) + "

"; + content += "

PCS Faulted: " + String(Fault[datalayer_extended.tesla.battery_PCS_dcdcFaulted]) + "

"; + content += + "

Output Is Limited: " + String(Fault[datalayer_extended.tesla.battery_PCS_dcdcOutputIsLimited]) + "

"; + content += "

Max Output Current Allowed: " + String(PCS_dcdcMaxOutputCurrentAllowed) + " A

"; + content += "

Precharge Rty Cnt: " + String(falseTrue[datalayer_extended.tesla.battery_PCS_dcdcPrechargeRtyCnt]) + + "

"; + content += + "

12V Support Rty Cnt: " + String(falseTrue[datalayer_extended.tesla.battery_PCS_dcdc12VSupportRtyCnt]) + + "

"; + content += "

Discharge Rty Cnt: " + String(falseTrue[datalayer_extended.tesla.battery_PCS_dcdcDischargeRtyCnt]) + + "

"; + content += + "

PWM Enable Line: " + String(Fault[datalayer_extended.tesla.battery_PCS_dcdcPwmEnableLine]) + "

"; + content += "

Supporting Fixed LV Target: " + + String(Fault[datalayer_extended.tesla.battery_PCS_dcdcSupportingFixedLvTarget]) + "

"; + content += "

Precharge Restart Cnt: " + + String(falseTrue[datalayer_extended.tesla.battery_PCS_dcdcPrechargeRestartCnt]) + "

"; + content += "

Initial Precharge Substate: " + + String(PCS_dcdcSubState[datalayer_extended.tesla.battery_PCS_dcdcInitialPrechargeSubState]) + "

"; + //0x2C4 708 PCS_logging + content += "

PCS_dcdcMaxLvOutputCurrent: " + String(PCS_dcdcMaxLvOutputCurrent) + " A

"; + content += "

PCS_dcdcCurrentLimit: " + String(PCS_dcdcCurrentLimit) + " A

"; + content += "

PCS_dcdcLvOutputCurrentTempLimit: " + String(PCS_dcdcLvOutputCurrentTempLimit) + " A

"; + content += "

PCS_dcdcUnifiedCommand: " + String(PCS_dcdcUnifiedCommand) + "

"; + content += "

PCS_dcdcCLAControllerOutput: " + String(PCS_dcdcCLAControllerOutput) + "

"; + content += "

PCS_dcdcTankVoltage: " + String(PCS_dcdcTankVoltage) + " V

"; + content += "

PCS_dcdcTankVoltageTarget: " + String(PCS_dcdcTankVoltageTarget) + " V

"; + content += "

PCS_dcdcClaCurrentFreq: " + String(PCS_dcdcClaCurrentFreq) + " kHz

"; + content += "

PCS_dcdcTCommMeasured: " + String(PCS_dcdcTCommMeasured) + " us

"; + content += "

PCS_dcdcShortTimeUs: " + String(PCS_dcdcShortTimeUs) + " us

"; + content += "

PCS_dcdcHalfPeriodUs: " + String(PCS_dcdcHalfPeriodUs) + " us

"; + content += "

PCS_dcdcIntervalMaxFrequency: " + String(PCS_dcdcIntervalMaxFrequency) + " kHz

"; + content += "

PCS_dcdcIntervalMaxHvBusVolt: " + String(PCS_dcdcIntervalMaxHvBusVolt) + " V

"; + content += "

PCS_dcdcIntervalMaxLvBusVolt: " + String(PCS_dcdcIntervalMaxLvBusVolt) + " V

"; + content += "

PCS_dcdcIntervalMaxLvOutputCurr: " + String(PCS_dcdcIntervalMaxLvOutputCurr) + " A

"; + content += "

PCS_dcdcIntervalMinFrequency: " + String(PCS_dcdcIntervalMinFrequency) + " kHz

"; + content += "

PCS_dcdcIntervalMinHvBusVolt: " + String(PCS_dcdcIntervalMinHvBusVolt) + " V

"; + content += "

PCS_dcdcIntervalMinLvBusVolt: " + String(PCS_dcdcIntervalMinLvBusVolt) + " V

"; + content += "

PCS_dcdcIntervalMinLvOutputCurr: " + String(PCS_dcdcIntervalMinLvOutputCurr) + " A

"; + content += "

PCS_dcdc12vSupportLifetimekWh: " + String(PCS_dcdc12vSupportLifetimekWh) + " kWh

"; + //0x7AA 1962 HVP_debugMessage + content += "

HVP_battery12V: " + String(HVP_battery12V) + " V

"; + content += "

HVP_dcLinkVoltage: " + String(HVP_dcLinkVoltage) + " V

"; + content += "

HVP_packVoltage: " + String(HVP_packVoltage) + " V

"; + content += "

HVP_packContVoltage: " + String(HVP_packContVoltage) + " V

"; + content += "

HVP_packContCoilCurrent: " + String(HVP_packContCoilCurrent) + " A

"; + content += "

HVP_pyroAnalog: " + String(HVP_pyroAnalog) + " V

"; + content += "

HVP_hvp1v5Ref: " + String(HVP_hvp1v5Ref) + " V

"; + content += "

HVP_hvilInVoltage: " + String(HVP_hvilInVoltage) + " V

"; + content += "

HVP_hvilOutVoltage: " + String(HVP_hvilOutVoltage) + " V

"; + content += + "

HVP_gpioPassivePyroDepl: " + String(Fault[datalayer_extended.tesla.HVP_gpioPassivePyroDepl]) + "

"; + content += "

HVP_gpioPyroIsoEn: " + String(Fault[datalayer_extended.tesla.HVP_gpioPyroIsoEn]) + "

"; + content += "

HVP_gpioCpFaultIn: " + String(Fault[datalayer_extended.tesla.HVP_gpioCpFaultIn]) + "

"; + content += + "

HVP_gpioPackContPowerEn: " + String(Fault[datalayer_extended.tesla.HVP_gpioPackContPowerEn]) + "

"; + content += "

HVP_gpioHvCablesOk: " + String(Fault[datalayer_extended.tesla.HVP_gpioHvCablesOk]) + "

"; + content += "

HVP_gpioHvpSelfEnable: " + String(Fault[datalayer_extended.tesla.HVP_gpioHvpSelfEnable]) + "

"; + content += "

HVP_gpioLed: " + String(Fault[datalayer_extended.tesla.HVP_gpioLed]) + "

"; + content += "

HVP_gpioCrashSignal: " + String(Fault[datalayer_extended.tesla.HVP_gpioCrashSignal]) + "

"; + content += + "

HVP_gpioShuntDataReady: " + String(Fault[datalayer_extended.tesla.HVP_gpioShuntDataReady]) + "

"; + content += "

HVP_gpioFcContPosAux: " + String(Fault[datalayer_extended.tesla.HVP_gpioFcContPosAux]) + "

"; + content += "

HVP_gpioFcContNegAux: " + String(Fault[datalayer_extended.tesla.HVP_gpioFcContNegAux]) + "

"; + content += "

HVP_gpioBmsEout: " + String(Fault[datalayer_extended.tesla.HVP_gpioBmsEout]) + "

"; + content += "

HVP_gpioCpFaultOut: " + String(Fault[datalayer_extended.tesla.HVP_gpioCpFaultOut]) + "

"; + content += "

HVP_gpioPyroPor: " + String(Fault[datalayer_extended.tesla.HVP_gpioPyroPor]) + "

"; + content += "

HVP_gpioShuntEn: " + String(Fault[datalayer_extended.tesla.HVP_gpioShuntEn]) + "

"; + content += "

HVP_gpioHvpVerEn: " + String(Fault[datalayer_extended.tesla.HVP_gpioHvpVerEn]) + "

"; + content += + "

HVP_gpioPackCoontPosFlywheel: " + String(Fault[datalayer_extended.tesla.HVP_gpioPackCoontPosFlywheel]) + + "

"; + content += "

HVP_gpioCpLatchEnable: " + String(Fault[datalayer_extended.tesla.HVP_gpioCpLatchEnable]) + "

"; + content += "

HVP_gpioPcsEnable: " + String(Fault[datalayer_extended.tesla.HVP_gpioPcsEnable]) + "

"; + content += + "

HVP_gpioPcsDcdcPwmEnable: " + String(Fault[datalayer_extended.tesla.HVP_gpioPcsDcdcPwmEnable]) + "

"; + content += "

HVP_gpioPcsChargePwmEnable: " + String(Fault[datalayer_extended.tesla.HVP_gpioPcsChargePwmEnable]) + + "

"; + content += + "

HVP_gpioFcContPowerEnable: " + String(Fault[datalayer_extended.tesla.HVP_gpioFcContPowerEnable]) + "

"; + content += "

HVP_gpioHvilEnable: " + String(Fault[datalayer_extended.tesla.HVP_gpioHvilEnable]) + "

"; + content += "

HVP_gpioSecDrdy: " + String(Fault[datalayer_extended.tesla.HVP_gpioSecDrdy]) + "

"; + content += "

HVP_shuntCurrentDebug: " + String(HVP_shuntCurrentDebug) + " A

"; + content += "

HVP_packCurrentMia: " + String(noYes[datalayer_extended.tesla.HVP_packCurrentMia]) + "

"; + content += "

HVP_auxCurrentMia: " + String(noYes[datalayer_extended.tesla.HVP_auxCurrentMia]) + "

"; + content += "

HVP_currentSenseMia: " + String(noYes[datalayer_extended.tesla.HVP_currentSenseMia]) + "

"; + content += + "

HVP_shuntRefVoltageMismatch: " + String(noYes[datalayer_extended.tesla.HVP_shuntRefVoltageMismatch]) + + "

"; + content += + "

HVP_shuntThermistorMia: " + String(noYes[datalayer_extended.tesla.HVP_shuntThermistorMia]) + "

"; + content += "

HVP_shuntHwMia: " + String(noYes[datalayer_extended.tesla.HVP_shuntHwMia]) + "

"; + //content += "

HVP_fcLinkVoltage: " + String(HVP_fcLinkVoltage) + " V

"; // Not giving useable data + //content += "

HVP_packNegativeV: " + String(HVP_packNegativeV) + " V

"; // Not giving useable data + //content += "

HVP_packPositiveV: " + String(HVP_packPositiveV) + " V

"; // Not giving useable data + //content += "

HVP_dcLinkNegativeV: " + String(HVP_dcLinkNegativeV) + " V

"; // Not giving useable data + //content += "

HVP_dcLinkPositiveV: " + String(HVP_dcLinkPositiveV) + " V

"; // Not giving useable data + //content += "

HVP_fcLinkNegativeV: " + String(HVP_fcLinkNegativeV) + " V

"; // Not giving useable data + //content += "

HVP_fcContCoilCurrent: " + String(HVP_fcContCoilCurrent) + " A

"; // Not giving useable data + //content += "

HVP_fcContVoltage: " + String(HVP_fcContVoltage) + " V

"; // Not giving useable data + //content += "

HVP_fcLinkPositiveV: " + String(HVP_fcLinkPositiveV) + " V

"; // Not giving useable data + //content += "

HVP_shuntRefVoltageDbg: " + String(HVP_shuntRefVoltageDbg) + " V

"; // Not giving useable data + //content += "

HVP_shuntAuxCurrentDbg: " + String(HVP_shuntAuxCurrentDbg) + " A

"; // Not giving useable data + //content += "

HVP_shuntBarTempDbg: " + String(HVP_shuntBarTempDbg) + " DegC

"; // Not giving useable data + //content += "

HVP_shuntAsicTempDbg: " + String(HVP_shuntAsicTempDbg) + " DegC

"; // Not giving useable data + //content += "

HVP_shuntAuxCurrentStatus: " + String(HVP_status[datalayer_extended.tesla.HVP_shuntAuxCurrentStatus]) + "

"; // Not giving useable data + //content += "

HVP_shuntBarTempStatus: " + String(HVP_status[datalayer_extended.tesla.HVP_shuntBarTempStatus]) + "

"; // Not giving useable data + //content += "

HVP_shuntAsicTempStatus: " + String(HVP_status[datalayer_extended.tesla.HVP_shuntAsicTempStatus]) + "

"; // Not giving useable data + + return content; + } +}; + +#endif diff --git a/Software/src/battery/TEST-FAKE-BATTERY.h b/Software/src/battery/TEST-FAKE-BATTERY.h index 1b6caa3f..ed79c8b1 100644 --- a/Software/src/battery/TEST-FAKE-BATTERY.h +++ b/Software/src/battery/TEST-FAKE-BATTERY.h @@ -10,16 +10,14 @@ class TestFakeBattery : public CanBattery { public: // Use this constructor for the second battery. - TestFakeBattery(DATALAYER_BATTERY_TYPE* datalayer_ptr, int targetCan) { + TestFakeBattery(DATALAYER_BATTERY_TYPE* datalayer_ptr, CAN_Interface targetCan) : CanBattery(targetCan) { datalayer_battery = datalayer_ptr; - can_interface = targetCan; allows_contactor_closing = nullptr; } // Use the default constructor to create the first or single battery. TestFakeBattery() { datalayer_battery = &datalayer.battery; - can_interface = can_config.battery; allows_contactor_closing = &datalayer.system.status.battery_allows_contactor_closing; } @@ -28,9 +26,11 @@ class TestFakeBattery : public CanBattery { virtual void update_values(); virtual void transmit_can(unsigned long currentMillis); + bool supports_set_fake_voltage() { return true; } + void set_fake_voltage(float val) { datalayer.battery.status.voltage_dV = val * 10; } + private: DATALAYER_BATTERY_TYPE* datalayer_battery; - int can_interface; // If not null, this battery decides when the contactor can be closed and writes the value here. bool* allows_contactor_closing; diff --git a/Software/src/battery/VOLVO-SPA-BATTERY.h b/Software/src/battery/VOLVO-SPA-BATTERY.h index 53b777ee..4ea009ed 100644 --- a/Software/src/battery/VOLVO-SPA-BATTERY.h +++ b/Software/src/battery/VOLVO-SPA-BATTERY.h @@ -4,6 +4,7 @@ #include "../include.h" #include "CanBattery.h" +#include "VOLVO-SPA-HTML.h" #define BATTERY_SELECTED #define SELECTED_BATTERY_CLASS VolvoSpaBattery @@ -15,6 +16,15 @@ class VolvoSpaBattery : public CanBattery { virtual void update_values(); virtual void transmit_can(unsigned long currentMillis); + bool supports_reset_DTC() { return true; } + void reset_DTC() { datalayer_extended.VolvoPolestar.UserRequestDTCreset = true; } + + bool supports_read_DTC() { return true; } + void read_DTC() { datalayer_extended.VolvoPolestar.UserRequestDTCreadout = true; } + + bool supports_reset_BECM() { return true; } + void reset_BECM() { datalayer_extended.VolvoPolestar.UserRequestBECMecuReset = true; } + private: void readCellVoltages(); diff --git a/Software/src/battery/VOLVO-SPA-HTML.h b/Software/src/battery/VOLVO-SPA-HTML.h new file mode 100644 index 00000000..f4726532 --- /dev/null +++ b/Software/src/battery/VOLVO-SPA-HTML.h @@ -0,0 +1,108 @@ +#ifndef _VOLVO_SPA_HTML_H +#define _VOLVO_SPA_HTML_H + +#include "../datalayer/datalayer.h" +#include "../datalayer/datalayer_extended.h" +#include "src/devboard/webserver/BatteryHtmlRenderer.h" + +class VolvoSpaHtmlRenderer : public BatteryHtmlRenderer { + public: + String get_status_html() { + String content; + + content += "

BECM reported SOC: " + String(datalayer_extended.VolvoPolestar.soc_bms) + "

"; + content += "

Calculated SOC: " + String(datalayer_extended.VolvoPolestar.soc_calc) + "

"; + content += "

Rescaled SOC: " + String(datalayer_extended.VolvoPolestar.soc_rescaled / 10) + "

"; + content += "

BECM reported SOH: " + String(datalayer_extended.VolvoPolestar.soh_bms) + "

"; + content += "

BECM supply voltage: " + String(datalayer_extended.VolvoPolestar.BECMsupplyVoltage) + " mV

"; + + content += "

HV voltage: " + String(datalayer_extended.VolvoPolestar.BECMBatteryVoltage) + " V

"; + content += "

HV current: " + String(datalayer_extended.VolvoPolestar.BECMBatteryCurrent) + " A

"; + content += "

Dynamic max voltage: " + String(datalayer_extended.VolvoPolestar.BECMUDynMaxLim) + " V

"; + content += "

Dynamic min voltage: " + String(datalayer_extended.VolvoPolestar.BECMUDynMinLim) + " V

"; + + content += + "

Discharge power limit 1: " + String(datalayer_extended.VolvoPolestar.HvBattPwrLimDcha1) + " kW

"; + content += + "

Discharge soft power limit: " + String(datalayer_extended.VolvoPolestar.HvBattPwrLimDchaSoft) + " kW

"; + content += + "

Discharge power limit slow aging: " + String(datalayer_extended.VolvoPolestar.HvBattPwrLimDchaSlowAgi) + + " kW

"; + content += + "

Charge power limit slow aging: " + String(datalayer_extended.VolvoPolestar.HvBattPwrLimChrgSlowAgi) + + " kW

"; + + content += "

HV system relay status: "; + switch (datalayer_extended.VolvoPolestar.HVSysRlySts) { + case 0: + content += String("Open"); + break; + case 1: + content += String("Closed"); + break; + case 2: + content += String("KeepStatus"); + break; + case 3: + content += String("OpenAndRequestActiveDischarge"); + break; + default: + content += String("Not valid"); + } + content += "

HV system relay status 1: "; + switch (datalayer_extended.VolvoPolestar.HVSysDCRlySts1) { + case 0: + content += String("Open"); + break; + case 1: + content += String("Closed"); + break; + case 2: + content += String("KeepStatus"); + break; + case 3: + content += String("Fault"); + break; + default: + content += String("Not valid"); + } + content += "

HV system relay status 2: "; + switch (datalayer_extended.VolvoPolestar.HVSysDCRlySts2) { + case 0: + content += String("Open"); + break; + case 1: + content += String("Closed"); + break; + case 2: + content += String("KeepStatus"); + break; + case 3: + content += String("Fault"); + break; + default: + content += String("Not valid"); + } + content += "

HV system isolation resistance monitoring status: "; + switch (datalayer_extended.VolvoPolestar.HVSysIsoRMonrSts) { + case 0: + content += String("Not valid 1"); + break; + case 1: + content += String("False"); + break; + case 2: + content += String("True"); + break; + case 3: + content += String("Not valid 2"); + break; + default: + content += String("Not valid"); + } + + return content; + } +}; + +#endif diff --git a/Software/src/battery/VOLVO-SPA-HYBRID-BATTERY.h b/Software/src/battery/VOLVO-SPA-HYBRID-BATTERY.h index 54800815..70b58cb8 100644 --- a/Software/src/battery/VOLVO-SPA-HYBRID-BATTERY.h +++ b/Software/src/battery/VOLVO-SPA-HYBRID-BATTERY.h @@ -4,6 +4,7 @@ #include "../include.h" #include "CanBattery.h" +#include "VOLVO-SPA-HYBRID-HTML.h" #define BATTERY_SELECTED #define SELECTED_BATTERY_CLASS VolvoSpaHybridBattery @@ -15,7 +16,19 @@ class VolvoSpaHybridBattery : public CanBattery { virtual void update_values(); virtual void transmit_can(unsigned long currentMillis); + bool supports_reset_DTC() { return true; } + void reset_DTC() { datalayer_extended.VolvoHybrid.UserRequestDTCreset = true; } + + bool supports_read_DTC() { return true; } + void read_DTC() { datalayer_extended.VolvoHybrid.UserRequestDTCreadout = true; } + + bool supports_reset_BECM() { return true; } + void reset_BECM() { datalayer_extended.VolvoHybrid.UserRequestBECMecuReset = true; } + + BatteryHtmlRenderer& get_status_renderer() { return renderer; } + private: + VolvoSpaHybridHtmlRenderer renderer; void readCellVoltages(); static const int MAX_PACK_VOLTAGE_DV = 4294; //5000 = 500.0V diff --git a/Software/src/battery/VOLVO-SPA-HYBRID-HTML.h b/Software/src/battery/VOLVO-SPA-HYBRID-HTML.h new file mode 100644 index 00000000..3c02d7cc --- /dev/null +++ b/Software/src/battery/VOLVO-SPA-HYBRID-HTML.h @@ -0,0 +1,101 @@ +#ifndef _VOLVO_SPA_HYBRID_HTML_H +#define _VOLVO_SPA_HYBRID_HTML_H + +#include "../datalayer/datalayer.h" +#include "../datalayer/datalayer_extended.h" +#include "src/devboard/webserver/BatteryHtmlRenderer.h" + +class VolvoSpaHybridHtmlRenderer : public BatteryHtmlRenderer { + public: + String get_status_html() { + String content; + + content += "

BECM reported SOC: " + String(datalayer_extended.VolvoHybrid.soc_bms) + "

"; + content += "

Calculated SOC: " + String(datalayer_extended.VolvoHybrid.soc_calc) + "

"; + content += "

Rescaled SOC: " + String(datalayer_extended.VolvoHybrid.soc_rescaled / 10) + "

"; + content += "

BECM reported SOH: " + String(datalayer_extended.VolvoHybrid.soh_bms) + "

"; + content += "

BECM supply voltage: " + String(datalayer_extended.VolvoHybrid.BECMsupplyVoltage) + " mV

"; + + content += "

HV voltage: " + String(datalayer_extended.VolvoHybrid.BECMBatteryVoltage) + " V

"; + content += "

HV current: " + String(datalayer_extended.VolvoHybrid.BECMBatteryCurrent) + " A

"; + content += "

Dynamic max voltage: " + String(datalayer_extended.VolvoHybrid.BECMUDynMaxLim) + " V

"; + content += "

Dynamic min voltage: " + String(datalayer_extended.VolvoHybrid.BECMUDynMinLim) + " V

"; + + content += "

Discharge power limit 1: " + String(datalayer_extended.VolvoHybrid.HvBattPwrLimDcha1) + " kW

"; + content += + "

Discharge soft power limit: " + String(datalayer_extended.VolvoHybrid.HvBattPwrLimDchaSoft) + " kW

"; + + content += "

HV system relay status: "; + switch (datalayer_extended.VolvoHybrid.HVSysRlySts) { + case 0: + content += String("Open"); + break; + case 1: + content += String("Closed"); + break; + case 2: + content += String("KeepStatus"); + break; + case 3: + content += String("OpenAndRequestActiveDischarge"); + break; + default: + content += String("Not valid"); + } + content += "

HV system relay status 1: "; + switch (datalayer_extended.VolvoHybrid.HVSysDCRlySts1) { + case 0: + content += String("Open"); + break; + case 1: + content += String("Closed"); + break; + case 2: + content += String("KeepStatus"); + break; + case 3: + content += String("Fault"); + break; + default: + content += String("Not valid"); + } + content += "

HV system relay status 2: "; + switch (datalayer_extended.VolvoHybrid.HVSysDCRlySts2) { + case 0: + content += String("Open"); + break; + case 1: + content += String("Closed"); + break; + case 2: + content += String("KeepStatus"); + break; + case 3: + content += String("Fault"); + break; + default: + content += String("Not valid"); + } + content += "

HV system isolation resistance monitoring status: "; + switch (datalayer_extended.VolvoHybrid.HVSysIsoRMonrSts) { + case 0: + content += String("Not valid 1"); + break; + case 1: + content += String("False"); + break; + case 2: + content += String("True"); + break; + case 3: + content += String("Not valid 2"); + break; + default: + content += String("Not valid"); + } + + return content; + } +}; + +#endif diff --git a/Software/src/datalayer/datalayer.h b/Software/src/datalayer/datalayer.h index f4860502..a00b06d5 100644 --- a/Software/src/datalayer/datalayer.h +++ b/Software/src/datalayer/datalayer.h @@ -310,12 +310,12 @@ typedef struct { /** True if the inverter allows for the contactors to close */ bool inverter_allows_contactor_closing = true; -#ifdef CONTACTOR_CONTROL + /** True if the contactor controlled by battery-emulator is closed */ bool contactors_engaged = false; /** True if the contactor controlled by battery-emulator is closed. Determined by check_interconnect_available(); if voltage is OK */ bool contactors_battery2_engaged = false; -#endif + /** True if the BMS is being reset, by cutting power towards it */ bool BMS_reset_in_progress = false; /** True if the BMS is starting up */ diff --git a/Software/src/devboard/safety/safety.cpp b/Software/src/devboard/safety/safety.cpp index 6e40b30b..8c011ffc 100644 --- a/Software/src/devboard/safety/safety.cpp +++ b/Software/src/devboard/safety/safety.cpp @@ -241,65 +241,65 @@ void update_machineryprotection() { } } -#ifdef DOUBLE_BATTERY // Additional Double-Battery safeties are checked here - // Check if the Battery 2 BMS is still sending CAN messages. If we go 60s without messages we raise a warning + // Additional Double-Battery safeties are checked here + if (battery2) { + // Check if the Battery 2 BMS is still sending CAN messages. If we go 60s without messages we raise a warning - // Pause function is on - if (emulator_pause_request_ON) { - datalayer.battery2.status.max_discharge_power_W = 0; - datalayer.battery2.status.max_charge_power_W = 0; - } - - if (!datalayer.battery2.status.CAN_battery_still_alive) { - set_event(EVENT_CAN_BATTERY2_MISSING, can_config.battery_double); - } else { - datalayer.battery2.status.CAN_battery_still_alive--; - clear_event(EVENT_CAN_BATTERY2_MISSING); - } - - // Too many malformed CAN messages recieved! - if (datalayer.battery2.status.CAN_error_counter > MAX_CAN_FAILURES) { - set_event(EVENT_CAN_CORRUPTED_WARNING, can_config.battery_double); - } else { - clear_event(EVENT_CAN_CORRUPTED_WARNING); - } - - // Cell overvoltage, critical latching error without automatic reset. Requires user action. - if (datalayer.battery2.status.cell_max_voltage_mV >= datalayer.battery2.info.max_cell_voltage_mV) { - set_event(EVENT_CELL_OVER_VOLTAGE, 0); - } - // Cell undervoltage, critical latching error without automatic reset. Requires user action. - if (datalayer.battery2.status.cell_min_voltage_mV <= datalayer.battery2.info.min_cell_voltage_mV) { - set_event(EVENT_CELL_UNDER_VOLTAGE, 0); - } - - // Check diff between highest and lowest cell - cell_deviation_mV = (datalayer.battery2.status.cell_max_voltage_mV - datalayer.battery2.status.cell_min_voltage_mV); - if (cell_deviation_mV > datalayer.battery2.info.max_cell_voltage_deviation_mV) { - set_event(EVENT_CELL_DEVIATION_HIGH, (cell_deviation_mV / 20)); - } else { - clear_event(EVENT_CELL_DEVIATION_HIGH); - } - - // Check if SOH% between the packs is too large - if ((datalayer.battery.status.soh_pptt != 9900) && (datalayer.battery2.status.soh_pptt != 9900)) { - // Both values available, check diff - uint16_t soh_diff_pptt; - if (datalayer.battery.status.soh_pptt > datalayer.battery2.status.soh_pptt) { - soh_diff_pptt = datalayer.battery.status.soh_pptt - datalayer.battery2.status.soh_pptt; - } else { - soh_diff_pptt = datalayer.battery2.status.soh_pptt - datalayer.battery.status.soh_pptt; + // Pause function is on + if (emulator_pause_request_ON) { + datalayer.battery2.status.max_discharge_power_W = 0; + datalayer.battery2.status.max_charge_power_W = 0; } - if (soh_diff_pptt > MAX_SOH_DEVIATION_PPTT) { - set_event(EVENT_SOH_DIFFERENCE, (uint8_t)(MAX_SOH_DEVIATION_PPTT / 100)); + if (!datalayer.battery2.status.CAN_battery_still_alive) { + set_event(EVENT_CAN_BATTERY2_MISSING, can_config.battery_double); } else { - clear_event(EVENT_SOH_DIFFERENCE); + datalayer.battery2.status.CAN_battery_still_alive--; + clear_event(EVENT_CAN_BATTERY2_MISSING); + } + + // Too many malformed CAN messages recieved! + if (datalayer.battery2.status.CAN_error_counter > MAX_CAN_FAILURES) { + set_event(EVENT_CAN_CORRUPTED_WARNING, can_config.battery_double); + } else { + clear_event(EVENT_CAN_CORRUPTED_WARNING); + } + + // Cell overvoltage, critical latching error without automatic reset. Requires user action. + if (datalayer.battery2.status.cell_max_voltage_mV >= datalayer.battery2.info.max_cell_voltage_mV) { + set_event(EVENT_CELL_OVER_VOLTAGE, 0); + } + // Cell undervoltage, critical latching error without automatic reset. Requires user action. + if (datalayer.battery2.status.cell_min_voltage_mV <= datalayer.battery2.info.min_cell_voltage_mV) { + set_event(EVENT_CELL_UNDER_VOLTAGE, 0); + } + + // Check diff between highest and lowest cell + cell_deviation_mV = (datalayer.battery2.status.cell_max_voltage_mV - datalayer.battery2.status.cell_min_voltage_mV); + if (cell_deviation_mV > datalayer.battery2.info.max_cell_voltage_deviation_mV) { + set_event(EVENT_CELL_DEVIATION_HIGH, (cell_deviation_mV / 20)); + } else { + clear_event(EVENT_CELL_DEVIATION_HIGH); + } + + // Check if SOH% between the packs is too large + if ((datalayer.battery.status.soh_pptt != 9900) && (datalayer.battery2.status.soh_pptt != 9900)) { + // Both values available, check diff + uint16_t soh_diff_pptt; + if (datalayer.battery.status.soh_pptt > datalayer.battery2.status.soh_pptt) { + soh_diff_pptt = datalayer.battery.status.soh_pptt - datalayer.battery2.status.soh_pptt; + } else { + soh_diff_pptt = datalayer.battery2.status.soh_pptt - datalayer.battery.status.soh_pptt; + } + + if (soh_diff_pptt > MAX_SOH_DEVIATION_PPTT) { + set_event(EVENT_SOH_DIFFERENCE, (uint8_t)(MAX_SOH_DEVIATION_PPTT / 100)); + } else { + clear_event(EVENT_SOH_DIFFERENCE); + } } } -#endif // DOUBLE_BATTERY - //Safeties verified, Zero charge/discharge ampere values incase any safety wrote the W to 0 if (datalayer.battery.status.max_discharge_power_W == 0) { datalayer.battery.status.max_discharge_current_dA = 0; @@ -357,10 +357,10 @@ void setBatteryPause(bool pause_battery, bool pause_CAN, bool equipment_stop, bo emulator_pause_status = PAUSING; datalayer.battery.status.max_discharge_power_W = 0; datalayer.battery.status.max_charge_power_W = 0; -#ifdef DOUBLE_BATTERY - datalayer.battery2.status.max_discharge_power_W = 0; - datalayer.battery2.status.max_charge_power_W = 0; -#endif + if (battery2) { + datalayer.battery2.status.max_discharge_power_W = 0; + datalayer.battery2.status.max_charge_power_W = 0; + } } else { clear_event(EVENT_PAUSE_BEGIN); diff --git a/Software/src/devboard/webserver/BatteryHtmlRenderer.h b/Software/src/devboard/webserver/BatteryHtmlRenderer.h new file mode 100644 index 00000000..ad33ad24 --- /dev/null +++ b/Software/src/devboard/webserver/BatteryHtmlRenderer.h @@ -0,0 +1,18 @@ +#ifndef _BATTERY_HTML_RENDERER_H +#define _BATTERY_HTML_RENDERER_H + +#include + +// Each battery can implement this interface to render more battery specific HTML +// content +class BatteryHtmlRenderer { + public: + virtual String get_status_html() = 0; +}; + +class BatteryDefaultRenderer : public BatteryHtmlRenderer { + public: + String get_status_html() { return String("No extra information available for this battery type"); } +}; + +#endif diff --git a/Software/src/devboard/webserver/advanced_battery_html.cpp b/Software/src/devboard/webserver/advanced_battery_html.cpp index 5ebc6894..fb0d44d9 100644 --- a/Software/src/devboard/webserver/advanced_battery_html.cpp +++ b/Software/src/devboard/webserver/advanced_battery_html.cpp @@ -1,9 +1,63 @@ #include "advanced_battery_html.h" #include +#include #include "../../datalayer/datalayer.h" #include "../../datalayer/datalayer_extended.h" #include "../../include.h" +// Available generic battery commands that are taken into use based on what the selected battery supports. +std::vector battery_commands = { + {"clearIsolation", "Clear isolation fault", "clear any active isolation fault?", + [](Battery* b) { return b && b->supports_clear_isolation(); }, + [](Battery* b) { + b->clear_isolation(); + }}, + {"resetBMS", "BMS reset", "reset the BMS?", [](Battery* b) { return b && b->supports_reset_BMS(); }, + [](Battery* b) { + b->reset_BMS(); + }}, + {"resetCrash", "Unlock crashed BMS", + "reset crash data? Note this will unlock your BMS and enable contactor closing and SOC calculation.", + [](Battery* b) { return b && b->supports_reset_crash(); }, + [](Battery* b) { + b->reset_crash(); + }}, + {"resetNVROL", "Perform NVROL reset", + "trigger an NVROL reset? Battery will be unavailable for 30 seconds while this is active!", + [](Battery* b) { return b && b->supports_reset_NVROL(); }, + [](Battery* b) { + b->reset_NVROL(); + }}, + {"resetDTC", "Erase DTC", "erase DTCs?", [](Battery* b) { return b && b->supports_reset_DTC(); }, + [](Battery* b) { + b->reset_DTC(); + }}, + {"readDTC", "Read DTC (result must be checked in CANlog)", nullptr, + [](Battery* b) { return b && b->supports_read_DTC(); }, + [](Battery* b) { + b->read_DTC(); + }}, + {"resetBECM", "Restart BECM module", "restart BECM??", [](Battery* b) { return b && b->supports_reset_DTC(); }, + [](Battery* b) { + b->reset_DTC(); + }}, + {"contactorClose", "Close Contactors", "a contactor close request?", + [](Battery* b) { return b && b->supports_contactor_close(); }, + [](Battery* b) { + b->request_close_contactors(); + }}, + {"contactorOpen", "Open Contactors", "a contactor open request?", + [](Battery* b) { return b && b->supports_contactor_close(); }, + [](Battery* b) { + b->request_open_contactors(); + }}, + {"resetSOH", "Reset degradation data", "reset degradation data?", + [](Battery* b) { return b && b->supports_reset_SOH(); }, + [](Battery* b) { + b->reset_SOH(); + }}, +}; + String advanced_battery_processor(const String& var) { if (var == "X") { String content = ""; @@ -20,1670 +74,50 @@ String advanced_battery_processor(const String& var) { // Start a new block with a specific background color content += "
"; -#ifdef BOLT_AMPERA_BATTERY - content += "

5V Reference: " + String(datalayer_extended.boltampera.battery_5V_ref) + "

"; - content += "

Module 1 temp: " + String(datalayer_extended.boltampera.battery_module_temp_1) + "

"; - content += "

Module 2 temp: " + String(datalayer_extended.boltampera.battery_module_temp_2) + "

"; - content += "

Module 3 temp: " + String(datalayer_extended.boltampera.battery_module_temp_3) + "

"; - content += "

Module 4 temp: " + String(datalayer_extended.boltampera.battery_module_temp_4) + "

"; - content += "

Module 5 temp: " + String(datalayer_extended.boltampera.battery_module_temp_5) + "

"; - content += "

Module 6 temp: " + String(datalayer_extended.boltampera.battery_module_temp_6) + "

"; - content += - "

Cell average voltage: " + String(datalayer_extended.boltampera.battery_cell_average_voltage) + "

"; - content += - "

Cell average voltage 2: " + String(datalayer_extended.boltampera.battery_cell_average_voltage_2) + "

"; - content += "

Terminal voltage: " + String(datalayer_extended.boltampera.battery_terminal_voltage) + "

"; - content += - "

Ignition power mode: " + String(datalayer_extended.boltampera.battery_ignition_power_mode) + "

"; - content += "

Battery current (7E7): " + String(datalayer_extended.boltampera.battery_current_7E7) + "

"; - content += "

Capacity MY17-18: " + String(datalayer_extended.boltampera.battery_capacity_my17_18) + "

"; - content += "

Capacity MY19+: " + String(datalayer_extended.boltampera.battery_capacity_my19plus) + "

"; - content += "

SOC Display: " + String(datalayer_extended.boltampera.battery_SOC_display) + "

"; - content += "

SOC Raw highprec: " + String(datalayer_extended.boltampera.battery_SOC_raw_highprec) + "

"; - content += "

Max temp: " + String(datalayer_extended.boltampera.battery_max_temperature) + "

"; - content += "

Min temp: " + String(datalayer_extended.boltampera.battery_min_temperature) + "

"; - content += "

Cell max mV: " + String(datalayer_extended.boltampera.battery_max_cell_voltage) + "

"; - content += "

Cell min mV: " + String(datalayer_extended.boltampera.battery_min_cell_voltage) + "

"; - content += "

Lowest cell: " + String(datalayer_extended.boltampera.battery_lowest_cell) + "

"; - content += "

Highest cell: " + String(datalayer_extended.boltampera.battery_highest_cell) + "

"; - content += - "

Internal resistance: " + String(datalayer_extended.boltampera.battery_internal_resistance) + "

"; - content += "

Voltage: " + String(datalayer_extended.boltampera.battery_voltage_polled) + "

"; - content += "

Isolation Ohm: " + String(datalayer_extended.boltampera.battery_vehicle_isolation) + "

"; - content += "

Isolation kOhm: " + String(datalayer_extended.boltampera.battery_isolation_kohm) + "

"; - content += "

HV locked: " + String(datalayer_extended.boltampera.battery_HV_locked) + "

"; - content += "

Crash event: " + String(datalayer_extended.boltampera.battery_crash_event) + "

"; - content += "

HVIL: " + String(datalayer_extended.boltampera.battery_HVIL) + "

"; - content += "

HVIL status: " + String(datalayer_extended.boltampera.battery_HVIL_status) + "

"; - content += "

Current (7E4): " + String(datalayer_extended.boltampera.battery_current_7E4) + "

"; -#endif //BOLT_AMPERA_BATTERY + // Render buttons dynamically based on what commands the battery supports. + auto render_command_buttons = [&content](Battery* batt, int ix) { + for (const auto& cmd : battery_commands) { + if (cmd.condition(batt)) { + // Button for user action + content += ""; -#ifdef BMW_IX_BATTERY - content += ""; - content += ""; - content += - "

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

"; - 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 += "

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 += - "

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

"; - content += - "

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

"; - 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

"; - content += "

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

"; - content += "

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

"; - static const char* balanceText[5] = {"0 No balancing mode active", "1 Voltage-Controlled Balancing Mode", - "2 Time-Controlled Balancing Mode with Demand Calculation at End of Charging", - "3 Time-Controlled Balancing Mode with Demand Calculation at Resting Voltage", - "4 No balancing mode active, qualifier invalid"}; - content += "

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

"; - static const char* hvilText[2] = {"Error (Loop Open)", "OK (Loop Closed)"}; - content += "

HVIL Status: " + String(hvilText[datalayer_extended.bmwix.hvil_status]) + "

"; - content += "

BMS Uptime: " + String(datalayer_extended.bmwix.bms_uptime) + " seconds

"; - content += "

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

"; - content += - "

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

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

HV Isolation (2147483647kOhm = maximum/invalid)

"; - content += "

Isolation Positive: " + String(datalayer_extended.bmwix.iso_safety_positive) + " kOhm

"; - content += "

Isolation Negative: " + String(datalayer_extended.bmwix.iso_safety_negative) + " kOhm

"; - content += "

Isolation Parallel: " + String(datalayer_extended.bmwix.iso_safety_parallel) + " kOhm

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

Pyro Status PSS1: " + String((pyroText[datalayer_extended.bmwix.pyro_status_pss1])) + "

"; - content += "

Pyro Status PSS4: " + String((pyroText[datalayer_extended.bmwix.pyro_status_pss4])) + "

"; - content += "

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

"; -#endif //BMW_IX_BATTERY + // Script that calls the backend to perform the command + content += ""; } } - content += " °C

"; - } - content += - "

Total charged: " + String(datalayer.battery.status.total_charged_battery_Wh / 1000.0, 1) + " kWh

"; - content += "

Total discharged: " + String(datalayer.battery.status.total_discharged_battery_Wh / 1000.0, 1) + - " kWh

"; -#endif //MEB_BATTERY + }; -#ifdef RENAULT_ZOE_GEN1_BATTERY - content += "

CUV " + String(datalayer_extended.zoe.CUV) + "

"; - content += "

HVBIR " + String(datalayer_extended.zoe.HVBIR) + "

"; - content += "

HVBUV " + String(datalayer_extended.zoe.HVBUV) + "

"; - content += "

EOCR " + String(datalayer_extended.zoe.EOCR) + "

"; - content += "

HVBOC " + String(datalayer_extended.zoe.HVBOC) + "

"; - content += "

HVBOT " + String(datalayer_extended.zoe.HVBOT) + "

"; - content += "

HVBOV " + String(datalayer_extended.zoe.HVBOV) + "

"; - content += "

COV " + String(datalayer_extended.zoe.COV) + "

"; -#endif //RENAULT_ZOE_GEN1_BATTERY - -#ifdef RENAULT_ZOE_GEN2_BATTERY - content += ""; - content += "

soc: " + String(datalayer_extended.zoePH2.battery_soc) + "

"; - content += "

usable soc: " + String(datalayer_extended.zoePH2.battery_usable_soc) + "

"; - content += "

soh: " + String(datalayer_extended.zoePH2.battery_soh) + "

"; - content += "

pack voltage: " + String(datalayer_extended.zoePH2.battery_pack_voltage) + "

"; - content += "

max cell voltage: " + String(datalayer_extended.zoePH2.battery_max_cell_voltage) + "

"; - content += "

min cell voltage: " + String(datalayer_extended.zoePH2.battery_min_cell_voltage) + "

"; - content += "

12v: " + String(datalayer_extended.zoePH2.battery_12v) + "

"; - content += "

avg temp: " + String(datalayer_extended.zoePH2.battery_avg_temp) + "

"; - content += "

min temp: " + String(datalayer_extended.zoePH2.battery_min_temp) + "

"; - content += "

max temp: " + String(datalayer_extended.zoePH2.battery_max_temp) + "

"; - content += "

max power: " + String(datalayer_extended.zoePH2.battery_max_power) + "

"; - content += "

interlock: " + String(datalayer_extended.zoePH2.battery_interlock) + "

"; - content += "

kwh: " + String(datalayer_extended.zoePH2.battery_kwh) + "

"; - content += "

current: " + String(datalayer_extended.zoePH2.battery_current) + "

"; - content += "

current offset: " + String(datalayer_extended.zoePH2.battery_current_offset) + "

"; - content += "

max generated: " + String(datalayer_extended.zoePH2.battery_max_generated) + "

"; - content += "

max available: " + String(datalayer_extended.zoePH2.battery_max_available) + "

"; - content += "

current voltage: " + String(datalayer_extended.zoePH2.battery_current_voltage) + "

"; - content += "

charging status: " + String(datalayer_extended.zoePH2.battery_charging_status) + "

"; - content += "

remaining charge: " + String(datalayer_extended.zoePH2.battery_remaining_charge) + "

"; - content += - "

balance capacity total: " + String(datalayer_extended.zoePH2.battery_balance_capacity_total) + "

"; - content += "

balance time total: " + String(datalayer_extended.zoePH2.battery_balance_time_total) + "

"; - content += - "

balance capacity sleep: " + String(datalayer_extended.zoePH2.battery_balance_capacity_sleep) + "

"; - content += "

balance time sleep: " + String(datalayer_extended.zoePH2.battery_balance_time_sleep) + "

"; - content += - "

balance capacity wake: " + String(datalayer_extended.zoePH2.battery_balance_capacity_wake) + "

"; - content += "

balance time wake: " + String(datalayer_extended.zoePH2.battery_balance_time_wake) + "

"; - content += "

bms state: " + String(datalayer_extended.zoePH2.battery_bms_state) + "

"; - content += "

balance switches: " + String(datalayer_extended.zoePH2.battery_balance_switches) + "

"; - content += "

energy complete: " + String(datalayer_extended.zoePH2.battery_energy_complete) + "

"; - content += "

energy partial: " + String(datalayer_extended.zoePH2.battery_energy_partial) + "

"; - content += "

slave failures: " + String(datalayer_extended.zoePH2.battery_slave_failures) + "

"; - content += "

mileage: " + String(datalayer_extended.zoePH2.battery_mileage) + "

"; - content += "

fan speed: " + String(datalayer_extended.zoePH2.battery_fan_speed) + "

"; - content += "

fan period: " + String(datalayer_extended.zoePH2.battery_fan_period) + "

"; - content += "

fan control: " + String(datalayer_extended.zoePH2.battery_fan_control) + "

"; - content += "

fan duty: " + String(datalayer_extended.zoePH2.battery_fan_duty) + "

"; - content += "

temporisation: " + String(datalayer_extended.zoePH2.battery_temporisation) + "

"; - content += "

time: " + String(datalayer_extended.zoePH2.battery_time) + "

"; - content += "

pack time: " + String(datalayer_extended.zoePH2.battery_pack_time) + "

"; - content += "

soc min: " + String(datalayer_extended.zoePH2.battery_soc_min) + "

"; - content += "

soc max: " + String(datalayer_extended.zoePH2.battery_soc_max) + "

"; -#endif //RENAULT_ZOE_GEN2_BATTERY - -#ifdef VOLVO_SPA_BATTERY - content += "

BECM reported SOC: " + String(datalayer_extended.VolvoPolestar.soc_bms) + "

"; - content += "

Calculated SOC: " + String(datalayer_extended.VolvoPolestar.soc_calc) + "

"; - content += "

Rescaled SOC: " + String(datalayer_extended.VolvoPolestar.soc_rescaled / 10) + "

"; - content += "

BECM reported SOH: " + String(datalayer_extended.VolvoPolestar.soh_bms) + "

"; - content += "

BECM supply voltage: " + String(datalayer_extended.VolvoPolestar.BECMsupplyVoltage) + " mV

"; - - content += "

HV voltage: " + String(datalayer_extended.VolvoPolestar.BECMBatteryVoltage) + " V

"; - content += "

HV current: " + String(datalayer_extended.VolvoPolestar.BECMBatteryCurrent) + " A

"; - content += "

Dynamic max voltage: " + String(datalayer_extended.VolvoPolestar.BECMUDynMaxLim) + " V

"; - content += "

Dynamic min voltage: " + String(datalayer_extended.VolvoPolestar.BECMUDynMinLim) + " V

"; - - content += - "

Discharge power limit 1: " + String(datalayer_extended.VolvoPolestar.HvBattPwrLimDcha1) + " kW

"; - content += - "

Discharge soft power limit: " + String(datalayer_extended.VolvoPolestar.HvBattPwrLimDchaSoft) + " kW

"; - content += - "

Discharge power limit slow aging: " + String(datalayer_extended.VolvoPolestar.HvBattPwrLimDchaSlowAgi) + - " kW

"; - content += - "

Charge power limit slow aging: " + String(datalayer_extended.VolvoPolestar.HvBattPwrLimChrgSlowAgi) + - " kW

"; - - content += "

HV system relay status: "; - switch (datalayer_extended.VolvoPolestar.HVSysRlySts) { - case 0: - content += String("Open"); - break; - case 1: - content += String("Closed"); - break; - case 2: - content += String("KeepStatus"); - break; - case 3: - content += String("OpenAndRequestActiveDischarge"); - break; - default: - content += String("Not valid"); - } - content += "

HV system relay status 1: "; - switch (datalayer_extended.VolvoPolestar.HVSysDCRlySts1) { - case 0: - content += String("Open"); - break; - case 1: - content += String("Closed"); - break; - case 2: - content += String("KeepStatus"); - break; - case 3: - content += String("Fault"); - break; - default: - content += String("Not valid"); - } - content += "

HV system relay status 2: "; - switch (datalayer_extended.VolvoPolestar.HVSysDCRlySts2) { - case 0: - content += String("Open"); - break; - case 1: - content += String("Closed"); - break; - case 2: - content += String("KeepStatus"); - break; - case 3: - content += String("Fault"); - break; - default: - content += String("Not valid"); - } - content += "

HV system isolation resistance monitoring status: "; - switch (datalayer_extended.VolvoPolestar.HVSysIsoRMonrSts) { - case 0: - content += String("Not valid 1"); - break; - case 1: - content += String("False"); - break; - case 2: - content += String("True"); - break; - case 3: - content += String("Not valid 2"); - break; - default: - content += String("Not valid"); + if (battery) { + content += battery->get_status_renderer().get_status_html(); + render_command_buttons(battery, 0); } - content += "


"; - content += "
"; - content += ""; -#endif // VOLVO_SPA_BATTERY - -#ifdef VOLVO_SPA_HYBRID_BATTERY - content += "

BECM reported SOC: " + String(datalayer_extended.VolvoHybrid.soc_bms) + "

"; - content += "

Calculated SOC: " + String(datalayer_extended.VolvoHybrid.soc_calc) + "

"; - content += "

Rescaled SOC: " + String(datalayer_extended.VolvoHybrid.soc_rescaled / 10) + "

"; - content += "

BECM reported SOH: " + String(datalayer_extended.VolvoHybrid.soh_bms) + "

"; - content += "

BECM supply voltage: " + String(datalayer_extended.VolvoHybrid.BECMsupplyVoltage) + " mV

"; - - content += "

HV voltage: " + String(datalayer_extended.VolvoHybrid.BECMBatteryVoltage) + " V

"; - content += "

HV current: " + String(datalayer_extended.VolvoHybrid.BECMBatteryCurrent) + " A

"; - content += "

Dynamic max voltage: " + String(datalayer_extended.VolvoHybrid.BECMUDynMaxLim) + " V

"; - content += "

Dynamic min voltage: " + String(datalayer_extended.VolvoHybrid.BECMUDynMinLim) + " V

"; - - content += "

Discharge power limit 1: " + String(datalayer_extended.VolvoHybrid.HvBattPwrLimDcha1) + " kW

"; - content += - "

Discharge soft power limit: " + String(datalayer_extended.VolvoHybrid.HvBattPwrLimDchaSoft) + " kW

"; - - content += "

HV system relay status: "; - switch (datalayer_extended.VolvoHybrid.HVSysRlySts) { - case 0: - content += String("Open"); - break; - case 1: - content += String("Closed"); - break; - case 2: - content += String("KeepStatus"); - break; - case 3: - content += String("OpenAndRequestActiveDischarge"); - break; - default: - content += String("Not valid"); + if (battery2) { + content += "

Values from battery 2

"; + content += battery2->get_status_renderer().get_status_html(); + render_command_buttons(battery2, 1); } - content += "

HV system relay status 1: "; - switch (datalayer_extended.VolvoHybrid.HVSysDCRlySts1) { - case 0: - content += String("Open"); - break; - case 1: - content += String("Closed"); - break; - case 2: - content += String("KeepStatus"); - break; - case 3: - content += String("Fault"); - break; - default: - content += String("Not valid"); - } - content += "

HV system relay status 2: "; - switch (datalayer_extended.VolvoHybrid.HVSysDCRlySts2) { - case 0: - content += String("Open"); - break; - case 1: - content += String("Closed"); - break; - case 2: - content += String("KeepStatus"); - break; - case 3: - content += String("Fault"); - break; - default: - content += String("Not valid"); - } - content += "

HV system isolation resistance monitoring status: "; - switch (datalayer_extended.VolvoHybrid.HVSysIsoRMonrSts) { - case 0: - content += String("Not valid 1"); - break; - case 1: - content += String("False"); - break; - case 2: - content += String("True"); - break; - case 3: - content += String("Not valid 2"); - break; - default: - content += String("Not valid"); - } - - content += "


"; - content += "
"; - content += ""; -#endif // VOLVO_SPA_HYBRID_BATTERY - -#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(VOLVO_SPA_HYBRID_BATTERY) && \ - !defined(KIA_HYUNDAI_64_BATTERY) && !defined(CMFA_EV_BATTERY) && !defined(STELLANTIS_ECMP_BATTERY) && \ - !defined(KIA_HYUNDAI_64_BATTERY) && !defined(GEELY_GEOMETRY_C_BATTERY) && !defined(CMFA_EV_BATTERY) && \ - !defined(RENAULT_ZOE_GEN1_BATTERY) //Only the listed types have extra info - content += "No extra information available for this battery type"; -#endif content += ""; - content += ""; - - content += ""; - - content += ""; - content += ""; - - content += ""; - - content += ""; - - content += ""; - - content += ""; - - content += ""; - - content += ""; - - // Additial functions added content += ""; return content; diff --git a/Software/src/devboard/webserver/advanced_battery_html.h b/Software/src/devboard/webserver/advanced_battery_html.h index 52485d58..2ca88309 100644 --- a/Software/src/devboard/webserver/advanced_battery_html.h +++ b/Software/src/devboard/webserver/advanced_battery_html.h @@ -13,4 +13,28 @@ */ String advanced_battery_processor(const String& var); +class Battery; + +// Each BatteryCommand defines a command that can be performed by a battery. +// Whether the selected battery supports the command is determined at run-time +// by calling the condition callback. +struct BatteryCommand { + // The unique name of the route in the API to execute the command or a function in Javascript + const char* identifier; + + // Display name for the command. Can be used in the UI. + const char* title; + + // Are you sure? prompt text. If null, no confirmation is asked. + const char* prompt; + + // Function to determine whether the given battery supports this command. + std::function condition; + + // Function that executes the command for the given battery. + std::function action; +}; + +extern std::vector battery_commands; + #endif diff --git a/Software/src/devboard/webserver/cellmonitor_html.cpp b/Software/src/devboard/webserver/cellmonitor_html.cpp index 5aaa9a56..d3e95e78 100644 --- a/Software/src/devboard/webserver/cellmonitor_html.cpp +++ b/Software/src/devboard/webserver/cellmonitor_html.cpp @@ -16,21 +16,24 @@ String cellmonitor_processor(const String& var) { content += ".cell { width: 48%; margin: 1%; padding: 10px; border: 1px solid white; text-align: center; }"; content += ".low-voltage { color: red; }"; // Style for low voltage text content += ".voltage-values { margin-bottom: 10px; }"; // Style for voltage values section -#ifdef DOUBLE_BATTERY - content += - "#graph, #graph2 {display: flex;align-items: flex-end;height: 200px;border: 1px solid #ccc;position: " - "relative;}"; -#else - content += "#graph {display: flex;align-items: flex-end;height: 200px;border: 1px solid #ccc;position: relative;}"; -#endif + + if (battery2) { + content += + "#graph, #graph2 {display: flex;align-items: flex-end;height: 200px;border: 1px solid #ccc;position: " + "relative;}"; + } else { + content += + "#graph {display: flex;align-items: flex-end;height: 200px;border: 1px solid #ccc;position: relative;}"; + } content += ".bar {margin: 0 0px;background-color: blue;display: inline-block;position: relative;cursor: pointer;border: " "1px solid white; /* Add this line */}"; -#ifdef DOUBLE_BATTERY - content += "#valueDisplay, #valueDisplay2 {text-align: left;font-weight: bold;margin-top: 10px;}"; -#else - content += "#valueDisplay {text-align: left;font-weight: bold;margin-top: 10px;}"; -#endif + + if (battery2) { + content += "#valueDisplay, #valueDisplay2 {text-align: left;font-weight: bold;margin-top: 10px;}"; + } else { + content += "#valueDisplay {text-align: left;font-weight: bold;margin-top: 10px;}"; + } content += ""; content += ""; @@ -68,43 +71,43 @@ String cellmonitor_processor(const String& var) { // Close the block content += ""; -#ifdef DOUBLE_BATTERY - // Start a new block with a specific background color - content += "
"; + if (battery2) { + // Start a new block with a specific background color + content += "
"; - // Display max, min, and deviation voltage values - content += "
"; - // Display cells - content += "
"; - // Display bars - content += "
"; - // Display single hovered value - content += "
Value: ...
"; - //Legend for graph - content += - "Idle"; - - bool battery2_balancing = false; - for (uint8_t i = 0u; i < datalayer.battery2.info.number_of_cells; i++) { - battery2_balancing = datalayer.battery2.status.cell_balancing_status[i]; - if (battery2_balancing) - break; - } - if (battery2_balancing) { + // Display max, min, and deviation voltage values + content += "
"; + // Display cells + content += "
"; + // Display bars + content += "
"; + // Display single hovered value + content += "
Value: ...
"; + //Legend for graph content += - "Balancing"; + "Idle"; + + bool battery2_balancing = false; + for (uint8_t i = 0u; i < datalayer.battery2.info.number_of_cells; i++) { + battery2_balancing = datalayer.battery2.status.cell_balancing_status[i]; + if (battery2_balancing) + break; + } + if (battery2_balancing) { + content += + "Balancing"; + } + content += + "Min/Max"; + + // Close the block + content += "
"; + + content += ""; } - content += - "Min/Max"; - - // Close the block - content += "
"; - - content += ""; -#endif // DOUBLE_BATTERY content += "