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 += "" + String(cmd.title) +
+ " ";
-#ifdef BMW_IX_BATTERY
- content += "Close Contactors ";
- content += "Open Contactors ";
- 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 += "Perform NVROL reset ";
- 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 += "Erase DTC ";
- content += "Read DTC (result must be checked in CANlog) ";
- content += "Restart BECM module ";
-#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 += "Erase DTC ";
- content += "Read DTC (result must be checked in CANlog) ";
- content += "Restart BECM module ";
-#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 += "Back to main page ";
@@ -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 += "
Back to main page ";
}
- content +=
- "
Min/Max ";
-
- // Close the block
- content += "
";
-
- content += "Back to main page ";
-#endif // DOUBLE_BATTERY
content += "