Merge branch 'main' into main

This commit is contained in:
Daniel Öster 2025-06-03 23:16:21 +03:00 committed by GitHub
commit eb22302ac9
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
53 changed files with 2646 additions and 2318 deletions

View file

@ -5,37 +5,33 @@
![GitHub actions](https://img.shields.io/github/actions/workflow/status/dalathegreat/BYD-Battery-Emulator-For-Gen24/compile-all-batteries.yml?color=0E810E)
![Static Badge](https://img.shields.io/badge/made-with_love-blue?color=%23008000)
This software enables EV battery packs to be used for stationary storage. It achieves this by converting the EV battery CAN data into a brand battery format that solar inverters can understand. This makes it extremely cheap and easy to use large EV batteries in a true plug'n'play fashion!
## What is Battery Emulator?
Many manufacturers sell home battery systems to enable homeowners to store power collected from the grid, or renewable sources, to use at times when electricity demand is higher. However almost all of these home battery systems charge a high cost for every kilowatt hour (kWh) of capacity you buy.
At the same time, EV manufacturers have been putting high capacity battery packs into their cars, with no firm plan for what should happen to those batteries if the car is damaged in a crash, or reaches the end of its life as a vehicle.
**Battery Emulator** enables EV battery packs to be repurposed for stationary storage. It acts as a translation layer between the EV battery and the home inverter. This makes it extremely cheap and easy to use large EV batteries in a true plug'n'play fashion!
> [!CAUTION]
> Working with high voltage is dangerous. Always follow local laws and regulations regarding high voltage work. If you are unsure about the rules in your country, consult a licensed electrician for more information.
![Fronius](https://github.com/dalathegreat/Battery-Emulator/assets/26695010/741c3237-8074-4891-9cd1-f47f0fe45cb5)
## Quickstart guide 📜
- Pick a [supported solar inverter](https://github.com/dalathegreat/Battery-Emulator/wiki#supported-inverters-list) (solar panels optional) :sun_with_face:
- Pick a [supported inverter](https://github.com/dalathegreat/Battery-Emulator/wiki#supported-inverters-list) (solar panels optional) :sun_with_face:
- Pick a [supported battery](https://github.com/dalathegreat/Battery-Emulator/wiki#supported-batteries-list) :battery:
- Order the Battery-Emulator [compatible hardware](https://github.com/dalathegreat/Battery-Emulator/wiki#where-do-i-get-the-hardware-needed) :robot:
- Follow the [installation guidelines](https://github.com/dalathegreat/Battery-Emulator/wiki/Installation-guidelines) section for how to install and commission your battery properly :notebook:
## Installation basics 🪛
1. Connect one end of the LilyGo RS485 to the Gen24 Modbus
2. Connect the other end of the LilyGo to the CAN side of the battery
3. Wire up high voltage cable between the Gen24 and the battery
4. Add a 5-12V power source to power the LilyGo and 12V to the battery (uninterruptible PSU or 12V lead acid recommended in parallel)
5. Some batteries need manual pre-charge circuit and positive/negative contactor control. Others are automatic. See the wiki for more info.
1. Connect your Battery Emulator hardware to your EV battery
2. Connect your Battery Emulator hardware to your inverter
3. Wire up high voltage cable between the inverter and the battery
4. Add a low voltage power supply to your Battery Emulator hardware
5. Configure any additional requirements to allow Battery Emulator to switch on your EV battery (also referred to as 'closing contactors')
6. Enjoy a big cheap grid connected battery!
## Wiring example, LEAF battery 💡
Here's how to wire up the communication between the components.
![Wiring](https://github.com/dalathegreat/Battery-Emulator/assets/26695010/29edeeda-1002-4826-9183-39a027b3b9ed)
Here's how to connect the high voltage lines
![HighVoltageWiring](https://github.com/dalathegreat/Battery-Emulator/assets/26695010/f70e6262-d630-4148-9a39-dad32e79b3d6)
For more examples showing wiring, see each battery types own Wiki page. For instance the [Nissan LEAF page](https://github.com/dalathegreat/Battery-Emulator/wiki/Battery:-Nissan-LEAF---e%E2%80%90NV200)
For examples showing wiring, see each battery type's own Wiki page. For instance the [Nissan LEAF page](https://github.com/dalathegreat/Battery-Emulator/wiki/Battery:-Nissan-LEAF---e%E2%80%90NV200)
## How to compile the software 💻

View file

@ -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
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));
#endif // DOUBLE_BATTERY
}
if (datalayer.battery.settings.soc_scaling_active) {
/** SOC Scaling
@ -445,7 +443,7 @@ void update_calculated_values() {
datalayer.battery.status.reported_remaining_capacity_Wh = datalayer.battery.status.remaining_capacity_Wh;
}
#ifdef DOUBLE_BATTERY
if (battery2) {
// If battery info is valid
if (datalayer.battery2.info.total_capacity_Wh > 0 && datalayer.battery.status.real_soc > 0) {
@ -462,40 +460,45 @@ void update_calculated_values() {
//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
datalayer.battery.status.reported_remaining_capacity_Wh +=
datalayer.battery2.status.reported_remaining_capacity_Wh;
}
} 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
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;
#endif
}
#ifdef DOUBLE_BATTERY
}
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
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
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
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++;

View file

@ -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.

View file

@ -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

View file

@ -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

View file

@ -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 += "<h4>SOC raw: " + String(datalayer_extended.bmwi3.SOC_raw) + "</h4>";
content += "<h4>SOC dash: " + String(datalayer_extended.bmwi3.SOC_dash) + "</h4>";
content += "<h4>SOC OBD2: " + String(datalayer_extended.bmwi3.SOC_OBD2) + "</h4>";
static const char* statusText[16] = {
"Not evaluated", "OK", "Error!", "Invalid signal", "", "", "", "", "", "", "", "", "", "", "", ""};
content += "<h4>Interlock: " + String(statusText[datalayer_extended.bmwi3.ST_interlock]) + "</h4>";
content += "<h4>Isolation external: " + String(statusText[datalayer_extended.bmwi3.ST_iso_ext]) + "</h4>";
content += "<h4>Isolation internal: " + String(statusText[datalayer_extended.bmwi3.ST_iso_int]) + "</h4>";
content += "<h4>Isolation: " + String(statusText[datalayer_extended.bmwi3.ST_isolation]) + "</h4>";
content += "<h4>Cooling valve: " + String(statusText[datalayer_extended.bmwi3.ST_valve_cooling]) + "</h4>";
content += "<h4>Emergency: " + String(statusText[datalayer_extended.bmwi3.ST_EMG]) + "</h4>";
static const char* prechargeText[16] = {"Not evaluated",
"Not active, closing not blocked",
"Error precharge blocked",
"Invalid signal",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
""};
content += "<h4>Precharge: " + String(prechargeText[datalayer_extended.bmwi3.ST_precharge]) +
"</h4>"; //Still unclear of enum
static const char* DCSWText[16] = {"Contactors open",
"Precharge ongoing",
"Contactors engaged",
"Invalid signal",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
""};
content += "<h4>Contactor status: " + String(DCSWText[datalayer_extended.bmwi3.ST_DCSW]) + "</h4>";
static const char* contText[16] = {"Contactors OK",
"One contactor welded!",
"Two contactors welded!",
"Invalid signal",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
""};
content += "<h4>Contactor weld: " + String(contText[datalayer_extended.bmwi3.ST_WELD]) + "</h4>";
static const char* valveText[16] = {"OK",
"Short circuit to GND",
"Short circuit to 12V",
"Line break",
"",
"",
"Driver error",
"",
"",
"",
"",
"",
"Stuck",
"Stuck",
"",
"Invalid Signal"};
content += "<h4>Cold shutoff valve: " + String(contText[datalayer_extended.bmwi3.ST_cold_shutoff_valve]) + "</h4>";
return content;
}
};
#endif

View file

@ -2,6 +2,7 @@
#define BMW_IX_BATTERY_H
#include <Arduino.h>
#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;

View file

@ -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 +=
"<h4>Battery Voltage after Contactor: " + String(datalayer_extended.bmwix.battery_voltage_after_contactor) +
" dV</h4>";
content += "<h4>Max Design Voltage: " + String(datalayer.battery.info.max_design_voltage_dV) + " dV</h4>";
content += "<h4>Min Design Voltage: " + String(datalayer.battery.info.min_design_voltage_dV) + " dV</h4>";
content += "<h4>Max Cell Design Voltage: " + String(datalayer.battery.info.max_cell_voltage_mV) + " mV</h4>";
content += "<h4>Min Cell Design Voltage: " + String(datalayer.battery.info.min_cell_voltage_mV) + " mV</h4>";
content +=
"<h4>Min Cell Voltage Data Age: " + String(datalayer_extended.bmwix.min_cell_voltage_data_age) + " ms</h4>";
content +=
"<h4>Max Cell Voltage Data Age: " + String(datalayer_extended.bmwix.max_cell_voltage_data_age) + " ms</h4>";
content += "<h4>Allowed Discharge Power: " + String(datalayer.battery.status.max_discharge_power_W) + " W</h4>";
content += "<h4>Allowed Charge Power: " + String(datalayer.battery.status.max_charge_power_W) + " W</h4>";
content += "<h4>T30 Terminal Voltage: " + String(datalayer_extended.bmwix.T30_Voltage) + " mV</h4>";
content += "<h4>Detected Cell Count: " + String(datalayer.battery.info.number_of_cells) + "</h4>";
static const char* balanceText[5] = {"0 No balancing mode active", "1 Voltage-Controlled Balancing Mode",
"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 += "<h4>Balancing: " + String((balanceText[datalayer_extended.bmwix.balancing_status])) + "</h4>";
static const char* hvilText[2] = {"Error (Loop Open)", "OK (Loop Closed)"};
content += "<h4>HVIL Status: " + String(hvilText[datalayer_extended.bmwix.hvil_status]) + "</h4>";
content += "<h4>BMS Uptime: " + String(datalayer_extended.bmwix.bms_uptime) + " seconds</h4>";
content += "<h4>BMS Allowed Charge Amps: " + String(datalayer_extended.bmwix.allowable_charge_amps) + " A</h4>";
content +=
"<h4>BMS Allowed Disharge Amps: " + String(datalayer_extended.bmwix.allowable_discharge_amps) + " A</h4>";
content += "<br>";
content += "<h3>HV Isolation (2147483647kOhm = maximum/invalid)</h3>";
content += "<h4>Isolation Positive: " + String(datalayer_extended.bmwix.iso_safety_positive) + " kOhm</h4>";
content += "<h4>Isolation Negative: " + String(datalayer_extended.bmwix.iso_safety_negative) + " kOhm</h4>";
content += "<h4>Isolation Parallel: " + String(datalayer_extended.bmwix.iso_safety_parallel) + " kOhm</h4>";
static const char* pyroText[5] = {"0 Value Invalid", "1 Successfully Blown", "2 Disconnected",
"3 Not Activated - Pyro Intact", "4 Unknown"};
content += "<h4>Pyro Status PSS1: " + String((pyroText[datalayer_extended.bmwix.pyro_status_pss1])) + "</h4>";
content += "<h4>Pyro Status PSS4: " + String((pyroText[datalayer_extended.bmwix.pyro_status_pss4])) + "</h4>";
content += "<h4>Pyro Status PSS6: " + String((pyroText[datalayer_extended.bmwix.pyro_status_pss6])) + "</h4>";
return content;
}
};
#endif

View file

@ -2,6 +2,7 @@
#define BMW_PHEV_BATTERY_H
#include <Arduino.h>
#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;

View file

@ -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 +=
"<h4>Battery Voltage after Contactor: " + String(datalayer_extended.bmwphev.battery_voltage_after_contactor) +
" dV</h4>";
content += "<h4>Allowed Discharge Power: " + String(datalayer.battery.status.max_discharge_power_W) + " W</h4>";
content += "<h4>Allowed Charge Power: " + String(datalayer.battery.status.max_charge_power_W) + " W</h4>";
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 += "<h4>Balancing: " + String((balanceText[datalayer_extended.bmwphev.balancing_status])) + "</h4>";
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 += "<h4>Interlock: " + String(statusText[datalayer_extended.bmwphev.ST_interlock]) + "</h4>";
content += "<h4>Isolation external: " + String(statusText[datalayer_extended.bmwphev.ST_iso_ext]) + "</h4>";
content += "<h4>Isolation internal: " + String(statusText[datalayer_extended.bmwphev.ST_iso_int]) + "</h4>";
content += "<h4>Isolation: " + String(statusText[datalayer_extended.bmwphev.ST_isolation]) + "</h4>";
content += "<h4>Cooling valve: " + String(statusText[datalayer_extended.bmwphev.ST_valve_cooling]) + "</h4>";
content += "<h4>Emergency: " + String(statusText[datalayer_extended.bmwphev.ST_EMG]) + "</h4>";
static const char* prechargeText[16] = {"Not evaluated",
"Not active, closing not blocked",
"Error precharge blocked",
"Invalid signal",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
""};
content += "<h4>Precharge: " + String(prechargeText[datalayer_extended.bmwphev.ST_precharge]) +
"</h4>"; //Still unclear of enum
static const char* DCSWText[16] = {"Contactors open",
"Precharge ongoing",
"Contactors engaged",
"Invalid signal",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
""};
content += "<h4>Contactor status: " + String(DCSWText[datalayer_extended.bmwphev.ST_DCSW]) + "</h4>";
static const char* contText[16] = {"Contactors OK",
"One contactor welded!",
"Two contactors welded!",
"Invalid signal",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
""};
content += "<h4>Contactor weld: " + String(contText[datalayer_extended.bmwphev.ST_WELD]) + "</h4>";
static const char* valveText[16] = {"OK",
"Short circuit to GND",
"Short circuit to 12V",
"Line break",
"",
"",
"Driver error",
"",
"",
"",
"",
"",
"Stuck",
"Stuck",
"",
"Invalid Signal"};
content +=
"<h4>Cold shutoff valve: " + String(valveText[datalayer_extended.bmwphev.ST_cold_shutoff_valve]) + "</h4>";
content +=
"<h4>Min Cell Voltage Data Age: " + String(datalayer_extended.bmwphev.min_cell_voltage_data_age) + " ms</h4>";
content +=
"<h4>Max Cell Voltage Data Age: " + String(datalayer_extended.bmwphev.max_cell_voltage_data_age) + " ms</h4>";
content += "<h4>Max Design Voltage: " + String(datalayer.battery.info.max_design_voltage_dV) + " dV</h4>";
content += "<h4>Min Design Voltage: " + String(datalayer.battery.info.min_design_voltage_dV) + " dV</h4>";
content += "<h4>BMS Allowed Charge Amps: " + String(datalayer_extended.bmwphev.allowable_charge_amps) + " A</h4>";
content +=
"<h4>BMS Allowed Disharge Amps: " + String(datalayer_extended.bmwphev.allowable_discharge_amps) + " A</h4>";
content += "<h4>Detected Cell Count: " + String(datalayer.battery.info.number_of_cells) + "</h4>";
content += "<h4>iso_safety_int_kohm: " + String(datalayer_extended.bmwphev.iso_safety_int_kohm) + "</h4>";
content += "<h4>iso_safety_ext_kohm: " + String(datalayer_extended.bmwphev.iso_safety_ext_kohm) + "</h4>";
content += "<h4>iso_safety_trg_kohm: " + String(datalayer_extended.bmwphev.iso_safety_trg_kohm) + "</h4>";
content += "<h4>iso_safety_ext_plausible: " + String(datalayer_extended.bmwphev.iso_safety_ext_plausible) + "</h4>";
content += "<h4>iso_safety_int_plausible: " + String(datalayer_extended.bmwphev.iso_safety_int_plausible) + "</h4>";
content += "<h4>iso_safety_trg_plausible: " + String(datalayer_extended.bmwphev.iso_safety_trg_plausible) + "</h4>";
content += "<h4>iso_safety_kohm: " + String(datalayer_extended.bmwphev.iso_safety_kohm) + "</h4>";
content += "<h4>iso_safety_kohm_quality: " + String(datalayer_extended.bmwphev.iso_safety_kohm_quality) + "</h4>";
content += "<br>";
content += "<h4>Todo";
content += "<br>";
content += "<h4>Max Cell Design Voltage: " + String(datalayer.battery.info.max_cell_voltage_mV) + " mV</h4>";
content += "<h4>Min Cell Design Voltage: " + String(datalayer.battery.info.min_cell_voltage_mV) + " mV</h4>";
content += "<h4>T30 Terminal Voltage: " + String(datalayer_extended.bmwphev.T30_Voltage) + " mV</h4>";
content += "<br>";
return content;
}
};
#endif

View file

@ -3,6 +3,7 @@
#include <Arduino.h>
#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;

View file

@ -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 += "<h4>5V Reference: " + String(datalayer_extended.boltampera.battery_5V_ref) + "</h4>";
content += "<h4>Module 1 temp: " + String(datalayer_extended.boltampera.battery_module_temp_1) + "</h4>";
content += "<h4>Module 2 temp: " + String(datalayer_extended.boltampera.battery_module_temp_2) + "</h4>";
content += "<h4>Module 3 temp: " + String(datalayer_extended.boltampera.battery_module_temp_3) + "</h4>";
content += "<h4>Module 4 temp: " + String(datalayer_extended.boltampera.battery_module_temp_4) + "</h4>";
content += "<h4>Module 5 temp: " + String(datalayer_extended.boltampera.battery_module_temp_5) + "</h4>";
content += "<h4>Module 6 temp: " + String(datalayer_extended.boltampera.battery_module_temp_6) + "</h4>";
content +=
"<h4>Cell average voltage: " + String(datalayer_extended.boltampera.battery_cell_average_voltage) + "</h4>";
content +=
"<h4>Cell average voltage 2: " + String(datalayer_extended.boltampera.battery_cell_average_voltage_2) + "</h4>";
content += "<h4>Terminal voltage: " + String(datalayer_extended.boltampera.battery_terminal_voltage) + "</h4>";
content +=
"<h4>Ignition power mode: " + String(datalayer_extended.boltampera.battery_ignition_power_mode) + "</h4>";
content += "<h4>Battery current (7E7): " + String(datalayer_extended.boltampera.battery_current_7E7) + "</h4>";
content += "<h4>Capacity MY17-18: " + String(datalayer_extended.boltampera.battery_capacity_my17_18) + "</h4>";
content += "<h4>Capacity MY19+: " + String(datalayer_extended.boltampera.battery_capacity_my19plus) + "</h4>";
content += "<h4>SOC Display: " + String(datalayer_extended.boltampera.battery_SOC_display) + "</h4>";
content += "<h4>SOC Raw highprec: " + String(datalayer_extended.boltampera.battery_SOC_raw_highprec) + "</h4>";
content += "<h4>Max temp: " + String(datalayer_extended.boltampera.battery_max_temperature) + "</h4>";
content += "<h4>Min temp: " + String(datalayer_extended.boltampera.battery_min_temperature) + "</h4>";
content += "<h4>Cell max mV: " + String(datalayer_extended.boltampera.battery_max_cell_voltage) + "</h4>";
content += "<h4>Cell min mV: " + String(datalayer_extended.boltampera.battery_min_cell_voltage) + "</h4>";
content += "<h4>Lowest cell: " + String(datalayer_extended.boltampera.battery_lowest_cell) + "</h4>";
content += "<h4>Highest cell: " + String(datalayer_extended.boltampera.battery_highest_cell) + "</h4>";
content +=
"<h4>Internal resistance: " + String(datalayer_extended.boltampera.battery_internal_resistance) + "</h4>";
content += "<h4>Voltage: " + String(datalayer_extended.boltampera.battery_voltage_polled) + "</h4>";
content += "<h4>Isolation Ohm: " + String(datalayer_extended.boltampera.battery_vehicle_isolation) + "</h4>";
content += "<h4>Isolation kOhm: " + String(datalayer_extended.boltampera.battery_isolation_kohm) + "</h4>";
content += "<h4>HV locked: " + String(datalayer_extended.boltampera.battery_HV_locked) + "</h4>";
content += "<h4>Crash event: " + String(datalayer_extended.boltampera.battery_crash_event) + "</h4>";
content += "<h4>HVIL: " + String(datalayer_extended.boltampera.battery_HVIL) + "</h4>";
content += "<h4>HVIL status: " + String(datalayer_extended.boltampera.battery_HVIL_status) + "</h4>";
content += "<h4>Current (7E4): " + String(datalayer_extended.boltampera.battery_current_7E4) + "</h4>";
return content;
}
};
#endif

View file

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

View file

@ -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 += "<h4>SOC method used: " + String(SOCmethod[byd_datalayer->SOC_method]) + "</h4>";
content += "<h4>SOC estimated: " + String(byd_datalayer->SOC_estimated) + "</h4>";
content += "<h4>SOC highprec: " + String(byd_datalayer->SOC_highprec) + "</h4>";
content += "<h4>SOC OBD2: " + String(byd_datalayer->SOC_polled) + "</h4>";
content += "<h4>Voltage periodic: " + String(byd_datalayer->voltage_periodic) + "</h4>";
content += "<h4>Voltage OBD2: " + String(byd_datalayer->voltage_polled) + "</h4>";
content += "<h4>Temperature sensor 1: " + String(byd_datalayer->battery_temperatures[0]) + "</h4>";
content += "<h4>Temperature sensor 2: " + String(byd_datalayer->battery_temperatures[1]) + "</h4>";
content += "<h4>Temperature sensor 3: " + String(byd_datalayer->battery_temperatures[2]) + "</h4>";
content += "<h4>Temperature sensor 4: " + String(byd_datalayer->battery_temperatures[3]) + "</h4>";
content += "<h4>Temperature sensor 5: " + String(byd_datalayer->battery_temperatures[4]) + "</h4>";
content += "<h4>Temperature sensor 6: " + String(byd_datalayer->battery_temperatures[5]) + "</h4>";
content += "<h4>Temperature sensor 7: " + String(byd_datalayer->battery_temperatures[6]) + "</h4>";
content += "<h4>Temperature sensor 8: " + String(byd_datalayer->battery_temperatures[7]) + "</h4>";
content += "<h4>Temperature sensor 9: " + String(byd_datalayer->battery_temperatures[8]) + "</h4>";
content += "<h4>Temperature sensor 10: " + String(byd_datalayer->battery_temperatures[9]) + "</h4>";
content += "<h4>Unknown0: " + String(byd_datalayer->unknown0) + "</h4>";
content += "<h4>Unknown1: " + String(byd_datalayer->unknown1) + "</h4>";
content += "<h4>Charge power raw: " + String(byd_datalayer->chargePower) + "</h4>";
content += "<h4>Unknown3: " + String(byd_datalayer->unknown3) + "</h4>";
content += "<h4>Unknown4: " + String(byd_datalayer->unknown4) + "</h4>";
content += "<h4>Unknown5: " + String(byd_datalayer->unknown5) + "</h4>";
content += "<h4>Unknown6: " + String(byd_datalayer->unknown6) + "</h4>";
content += "<h4>Unknown7: " + String(byd_datalayer->unknown7) + "</h4>";
content += "<h4>Unknown8: " + String(byd_datalayer->unknown8) + "</h4>";
content += "<h4>Unknown9: " + String(byd_datalayer->unknown9) + "</h4>";
content += "<h4>Unknown10: " + String(byd_datalayer->unknown10) + "</h4>";
content += "<h4>Unknown11: " + String(byd_datalayer->unknown11) + "</h4>";
content += "<h4>Unknown12: " + String(byd_datalayer->unknown12) + "</h4>";
content += "<h4>Unknown13: " + String(byd_datalayer->unknown12) + "</h4>";
return content;
}
private:
DATALAYER_INFO_BYDATTO3* byd_datalayer;
};
#endif

View file

@ -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<float>(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

View file

@ -2,6 +2,7 @@
#define CELLPOWER_BMS_H
#include <Arduino.h>
#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;

View file

@ -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 += "<h3>States:</h3>";
content += "<h4>Discharge: " + String(falseTrue[datalayer_extended.cellpower.system_state_discharge]) + "</h4>";
content += "<h4>Charge: " + String(falseTrue[datalayer_extended.cellpower.system_state_charge]) + "</h4>";
content +=
"<h4>Cellbalancing: " + String(falseTrue[datalayer_extended.cellpower.system_state_cellbalancing]) + "</h4>";
content +=
"<h4>Tricklecharging: " + String(falseTrue[datalayer_extended.cellpower.system_state_tricklecharge]) + "</h4>";
content += "<h4>Idle: " + String(falseTrue[datalayer_extended.cellpower.system_state_idle]) + "</h4>";
content += "<h4>Charge completed: " + String(falseTrue[datalayer_extended.cellpower.system_state_chargecompleted]) +
"</h4>";
content +=
"<h4>Maintenance charge: " + String(falseTrue[datalayer_extended.cellpower.system_state_maintenancecharge]) +
"</h4>";
content += "<h3>IO:</h3>";
content +=
"<h4>Main positive relay: " + String(falseTrue[datalayer_extended.cellpower.IO_state_main_positive_relay]) +
"</h4>";
content +=
"<h4>Main negative relay: " + String(falseTrue[datalayer_extended.cellpower.IO_state_main_negative_relay]) +
"</h4>";
content +=
"<h4>Charge enabled: " + String(falseTrue[datalayer_extended.cellpower.IO_state_charge_enable]) + "</h4>";
content +=
"<h4>Precharge relay: " + String(falseTrue[datalayer_extended.cellpower.IO_state_precharge_relay]) + "</h4>";
content +=
"<h4>Discharge enable: " + String(falseTrue[datalayer_extended.cellpower.IO_state_discharge_enable]) + "</h4>";
content += "<h4>IO 6: " + String(falseTrue[datalayer_extended.cellpower.IO_state_IO_6]) + "</h4>";
content += "<h4>IO 7: " + String(falseTrue[datalayer_extended.cellpower.IO_state_IO_7]) + "</h4>";
content += "<h4>IO 8: " + String(falseTrue[datalayer_extended.cellpower.IO_state_IO_8]) + "</h4>";
content += "<h3>Errors:</h3>";
content +=
"<h4>Cell overvoltage: " + String(falseTrue[datalayer_extended.cellpower.error_Cell_overvoltage]) + "</h4>";
content +=
"<h4>Cell undervoltage: " + String(falseTrue[datalayer_extended.cellpower.error_Cell_undervoltage]) + "</h4>";
content += "<h4>Cell end of life voltage: " +
String(falseTrue[datalayer_extended.cellpower.error_Cell_end_of_life_voltage]) + "</h4>";
content +=
"<h4>Cell voltage misread: " + String(falseTrue[datalayer_extended.cellpower.error_Cell_voltage_misread]) +
"</h4>";
content +=
"<h4>Cell over temperature: " + String(falseTrue[datalayer_extended.cellpower.error_Cell_over_temperature]) +
"</h4>";
content +=
"<h4>Cell under temperature: " + String(falseTrue[datalayer_extended.cellpower.error_Cell_under_temperature]) +
"</h4>";
content += "<h4>Cell unmanaged: " + String(falseTrue[datalayer_extended.cellpower.error_Cell_unmanaged]) + "</h4>";
content +=
"<h4>LMU over temperature: " + String(falseTrue[datalayer_extended.cellpower.error_LMU_over_temperature]) +
"</h4>";
content +=
"<h4>LMU under temperature: " + String(falseTrue[datalayer_extended.cellpower.error_LMU_under_temperature]) +
"</h4>";
content += "<h4>Temp sensor open circuit: " +
String(falseTrue[datalayer_extended.cellpower.error_Temp_sensor_open_circuit]) + "</h4>";
content += "<h4>Temp sensor short circuit: " +
String(falseTrue[datalayer_extended.cellpower.error_Temp_sensor_short_circuit]) + "</h4>";
content += "<h4>SUB comm: " + String(falseTrue[datalayer_extended.cellpower.error_SUB_communication]) + "</h4>";
content += "<h4>LMU comm: " + String(falseTrue[datalayer_extended.cellpower.error_LMU_communication]) + "</h4>";
content +=
"<h4>Over current In: " + String(falseTrue[datalayer_extended.cellpower.error_Over_current_IN]) + "</h4>";
content +=
"<h4>Over current Out: " + String(falseTrue[datalayer_extended.cellpower.error_Over_current_OUT]) + "</h4>";
content += "<h4>Short circuit: " + String(falseTrue[datalayer_extended.cellpower.error_Short_circuit]) + "</h4>";
content += "<h4>Leak detected: " + String(falseTrue[datalayer_extended.cellpower.error_Leak_detected]) + "</h4>";
content +=
"<h4>Leak detection failed: " + String(falseTrue[datalayer_extended.cellpower.error_Leak_detection_failed]) +
"</h4>";
content +=
"<h4>Voltage diff: " + String(falseTrue[datalayer_extended.cellpower.error_Voltage_difference]) + "</h4>";
content += "<h4>BMCU supply overvoltage: " +
String(falseTrue[datalayer_extended.cellpower.error_BMCU_supply_over_voltage]) + "</h4>";
content += "<h4>BMCU supply undervoltage: " +
String(falseTrue[datalayer_extended.cellpower.error_BMCU_supply_under_voltage]) + "</h4>";
content += "<h4>Main positive contactor: " +
String(falseTrue[datalayer_extended.cellpower.error_Main_positive_contactor]) + "</h4>";
content += "<h4>Main negative contactor: " +
String(falseTrue[datalayer_extended.cellpower.error_Main_negative_contactor]) + "</h4>";
content += "<h4>Precharge contactor: " + String(falseTrue[datalayer_extended.cellpower.error_Precharge_contactor]) +
"</h4>";
content +=
"<h4>Midpack contactor: " + String(falseTrue[datalayer_extended.cellpower.error_Midpack_contactor]) + "</h4>";
content +=
"<h4>Precharge timeout: " + String(falseTrue[datalayer_extended.cellpower.error_Precharge_timeout]) + "</h4>";
content += "<h4>EMG connector override: " +
String(falseTrue[datalayer_extended.cellpower.error_Emergency_connector_override]) + "</h4>";
content += "<h3>Warnings:</h3>";
content +=
"<h4>High cell voltage: " + String(falseTrue[datalayer_extended.cellpower.warning_High_cell_voltage]) + "</h4>";
content +=
"<h4>Low cell voltage: " + String(falseTrue[datalayer_extended.cellpower.warning_Low_cell_voltage]) + "</h4>";
content +=
"<h4>High cell temperature: " + String(falseTrue[datalayer_extended.cellpower.warning_High_cell_temperature]) +
"</h4>";
content +=
"<h4>Low cell temperature: " + String(falseTrue[datalayer_extended.cellpower.warning_Low_cell_temperature]) +
"</h4>";
content +=
"<h4>High LMU temperature: " + String(falseTrue[datalayer_extended.cellpower.warning_High_LMU_temperature]) +
"</h4>";
content +=
"<h4>Low LMU temperature: " + String(falseTrue[datalayer_extended.cellpower.warning_Low_LMU_temperature]) +
"</h4>";
content +=
"<h4>SUB comm interf: " + String(falseTrue[datalayer_extended.cellpower.warning_SUB_communication_interfered]) +
"</h4>";
content +=
"<h4>LMU comm interf: " + String(falseTrue[datalayer_extended.cellpower.warning_LMU_communication_interfered]) +
"</h4>";
content +=
"<h4>High current In: " + String(falseTrue[datalayer_extended.cellpower.warning_High_current_IN]) + "</h4>";
content +=
"<h4>High current Out: " + String(falseTrue[datalayer_extended.cellpower.warning_High_current_OUT]) + "</h4>";
content += "<h4>Pack resistance diff: " +
String(falseTrue[datalayer_extended.cellpower.warning_Pack_resistance_difference]) + "</h4>";
content +=
"<h4>High pack resistance: " + String(falseTrue[datalayer_extended.cellpower.warning_High_pack_resistance]) +
"</h4>";
content += "<h4>Cell resistance diff: " +
String(falseTrue[datalayer_extended.cellpower.warning_Cell_resistance_difference]) + "</h4>";
content +=
"<h4>High cell resistance: " + String(falseTrue[datalayer_extended.cellpower.warning_High_cell_resistance]) +
"</h4>";
content += "<h4>High BMCU supply voltage: " +
String(falseTrue[datalayer_extended.cellpower.warning_High_BMCU_supply_voltage]) + "</h4>";
content += "<h4>Low BMCU supply voltage: " +
String(falseTrue[datalayer_extended.cellpower.warning_Low_BMCU_supply_voltage]) + "</h4>";
content += "<h4>Low SOC: " + String(falseTrue[datalayer_extended.cellpower.warning_Low_SOC]) + "</h4>";
content += "<h4>Balancing required: " +
String(falseTrue[datalayer_extended.cellpower.warning_Balancing_required_OCV_model]) + "</h4>";
content += "<h4>Charger not responding: " +
String(falseTrue[datalayer_extended.cellpower.warning_Charger_not_responding]) + "</h4>";
return content;
}
};
#endif

View file

@ -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

View file

@ -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 += "<h4>SOC U: " + String(datalayer_extended.CMFAEV.soc_u) + "percent</h4>";
content += "<h4>SOC Z: " + String(datalayer_extended.CMFAEV.soc_z) + "percent</h4>";
content += "<h4>SOH Average: " + String(datalayer_extended.CMFAEV.soh_average) + "pptt</h4>";
content += "<h4>12V voltage: " + String(datalayer_extended.CMFAEV.lead_acid_voltage) + "mV</h4>";
content += "<h4>Highest cell number: " + String(datalayer_extended.CMFAEV.highest_cell_voltage_number) + "</h4>";
content += "<h4>Lowest cell number: " + String(datalayer_extended.CMFAEV.lowest_cell_voltage_number) + "</h4>";
content += "<h4>Max regen power: " + String(datalayer_extended.CMFAEV.max_regen_power) + "</h4>";
content += "<h4>Max discharge power: " + String(datalayer_extended.CMFAEV.max_discharge_power) + "</h4>";
content += "<h4>Max charge power: " + String(datalayer_extended.CMFAEV.maximum_charge_power) + "</h4>";
content += "<h4>SOH available power: " + String(datalayer_extended.CMFAEV.SOH_available_power) + "</h4>";
content += "<h4>SOH generated power: " + String(datalayer_extended.CMFAEV.SOH_generated_power) + "</h4>";
content += "<h4>Average temperature: " + String(datalayer_extended.CMFAEV.average_temperature) + "dC</h4>";
content += "<h4>Maximum temperature: " + String(datalayer_extended.CMFAEV.maximum_temperature) + "dC</h4>";
content += "<h4>Minimum temperature: " + String(datalayer_extended.CMFAEV.minimum_temperature) + "dC</h4>";
content +=
"<h4>Cumulative energy discharged: " + String(datalayer_extended.CMFAEV.cumulative_energy_when_discharging) +
"Wh</h4>";
content += "<h4>Cumulative energy charged: " + String(datalayer_extended.CMFAEV.cumulative_energy_when_charging) +
"Wh</h4>";
content +=
"<h4>Cumulative energy regen: " + String(datalayer_extended.CMFAEV.cumulative_energy_in_regen) + "Wh</h4>";
return content;
}
};
#endif

View file

@ -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

View file

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

View file

@ -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 += "<h4>Main Connector State: ";
if (datalayer_extended.stellantisECMP.MainConnectorState == 0) {
content += "Contactors open</h4>";
} else if (datalayer_extended.stellantisECMP.MainConnectorState == 0x01) {
content += "Precharged</h4>";
} else {
content += "Invalid</h4>";
}
content +=
"<h4>Insulation Resistance: " + String(datalayer_extended.stellantisECMP.InsulationResistance) + "kOhm</h4>";
return content;
}
};
#endif

View file

@ -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

View file

@ -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 += "<h4>Serial number: " + String(readableSoftwareVersion) + "</h4>";
content += "<h4>Software version: " + String(readableSerialNumber) + "</h4>";
content += "<h4>Hardware version: " + String(readableHardwareVersion) + "</h4>";
content += "<h4>SOC display: " + String(datalayer_extended.geometryC.soc) + "ppt</h4>";
content += "<h4>CC2 voltage: " + String(datalayer_extended.geometryC.CC2voltage) + "mV</h4>";
content += "<h4>Cell max voltage number: " + String(datalayer_extended.geometryC.cellMaxVoltageNumber) + "</h4>";
content += "<h4>Cell min voltage number: " + String(datalayer_extended.geometryC.cellMinVoltageNumber) + "</h4>";
content += "<h4>Cell total amount: " + String(datalayer_extended.geometryC.cellTotalAmount) + "S</h4>";
content += "<h4>Specificial Voltage: " + String(datalayer_extended.geometryC.specificialVoltage) + "dV</h4>";
content += "<h4>Unknown1: " + String(datalayer_extended.geometryC.unknown1) + "</h4>";
content += "<h4>Raw SOC max: " + String(datalayer_extended.geometryC.rawSOCmax) + "</h4>";
content += "<h4>Raw SOC min: " + String(datalayer_extended.geometryC.rawSOCmin) + "</h4>";
content += "<h4>Unknown4: " + String(datalayer_extended.geometryC.unknown4) + "</h4>";
content += "<h4>Capacity module max: " + String((datalayer_extended.geometryC.capModMax / 10)) + "Ah</h4>";
content += "<h4>Capacity module min: " + String((datalayer_extended.geometryC.capModMin / 10)) + "Ah</h4>";
content += "<h4>Unknown7: " + String(datalayer_extended.geometryC.unknown7) + "</h4>";
content += "<h4>Unknown8: " + String(datalayer_extended.geometryC.unknown8) + "</h4>";
content +=
"<h4>Module 1 temperature: " + String(datalayer_extended.geometryC.ModuleTemperatures[0]) + " &deg;C</h4>";
content +=
"<h4>Module 2 temperature: " + String(datalayer_extended.geometryC.ModuleTemperatures[1]) + " &deg;C</h4>";
content +=
"<h4>Module 3 temperature: " + String(datalayer_extended.geometryC.ModuleTemperatures[2]) + " &deg;C</h4>";
content +=
"<h4>Module 4 temperature: " + String(datalayer_extended.geometryC.ModuleTemperatures[3]) + " &deg;C</h4>";
content +=
"<h4>Module 5 temperature: " + String(datalayer_extended.geometryC.ModuleTemperatures[4]) + " &deg;C</h4>";
content +=
"<h4>Module 6 temperature: " + String(datalayer_extended.geometryC.ModuleTemperatures[5]) + " &deg;C</h4>";
return content;
}
};
#endif

View file

@ -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

View file

@ -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 += "<h4>Cells: " + String(data.total_cell_count) + "S</h4>";
content += "<h4>12V voltage: " + String(data.battery_12V / 10.0, 1) + "</h4>";
content += "<h4>Waterleakage: " + String(data.waterleakageSensor) + "</h4>";
content += "<h4>Temperature, water inlet: " + String(data.temperature_water_inlet) + "</h4>";
content += "<h4>Temperature, power relay: " + String(data.powerRelayTemperature) + "</h4>";
content += "<h4>Batterymanagement mode: " + String(data.batteryManagementMode) + "</h4>";
content += "<h4>BMS ignition: " + String(data.BMS_ign) + "</h4>";
content += "<h4>Battery relay: " + String(data.batteryRelay) + "</h4>";
};
print_hyundai(*kia_datalayer);
return content;
}
private:
DATALAYER_INFO_KIAHYUNDAI64* kia_datalayer;
};
#endif

View file

@ -3,6 +3,7 @@
#include <Arduino.h>
#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;

View file

@ -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 ? "<h4>Service disconnect switch: Missing!</h4>"
: "<h4>Service disconnect switch: OK</h4>";
content += datalayer_extended.meb.pilotline ? "<h4>Pilotline: Open!</h4>" : "<h4>Pilotline: OK</h4>";
content += datalayer_extended.meb.transportmode ? "<h4>Transportmode: Locked!</h4>" : "<h4>Transportmode: OK</h4>";
content += datalayer_extended.meb.shutdown_active ? "<h4>Shutdown: Active!</h4>" : "<h4>Shutdown: No</h4>";
content += datalayer_extended.meb.componentprotection ? "<h4>Component protection: Active!</h4>"
: "<h4>Component protection: No</h4>";
content += "<h4>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 += "</h4><h4>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 += "</h4><h4>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("</h4><h4>Charging: ") + (datalayer_extended.meb.charging_active ? "active" : "not active");
content += String("</h4><h4>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("</h4><h4>Slow charging: ") + (datalayer_extended.meb.balancing_request ? "requested" : "not requested");
content += "</h4><h4>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 += "</h4><h4>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 += "</h4>";
content += datalayer_extended.meb.BMS_fault_performance ? "<h4>BMS fault performance: Active!</h4>"
: "<h4>BMS fault performance: Off</h4>";
content += datalayer_extended.meb.BMS_fault_emergency_shutdown_crash
? "<h4>BMS fault emergency shutdown crash: Active!</h4>"
: "<h4>BMS fault emergency shutdown crash: Off</h4>";
content += datalayer_extended.meb.BMS_error_shutdown_request ? "<h4>BMS error shutdown request: Active!</h4>"
: "<h4>BMS error shutdown request: Inactive</h4>";
content += datalayer_extended.meb.BMS_error_shutdown ? "<h4>BMS error shutdown: Active!</h4>"
: "<h4>BMS error shutdown: Off</h4>";
content += "<h4>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 += "</h4><h4>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 += "</h4><h4>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 += "</h4><h4>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 += "</h4><h4>BMS voltage: " + String(datalayer_extended.meb.BMS_voltage_dV / 10.0, 1) + "</h4>";
content += datalayer_extended.meb.BMS_OBD_MIL ? "<h4>OBD MIL: ON!</h4>" : "<h4>OBD MIL: Off</h4>";
content +=
datalayer_extended.meb.BMS_error_lamp_req ? "<h4>Red error lamp: ON!</h4>" : "<h4>Red error lamp: Off</h4>";
content += datalayer_extended.meb.BMS_warning_lamp_req ? "<h4>Yellow warning lamp: ON!</h4>"
: "<h4>Yellow warning lamp: Off</h4>";
content += "<h4>Isolation resistance: " + String(datalayer_extended.meb.isolation_resistance) + " kOhm</h4>";
content +=
datalayer_extended.meb.battery_heating ? "<h4>Battery heating: Active!</h4>" : "<h4>Battery heating: Off</h4>";
const char* rt_enum[] = {"No", "Error level 1", "Error level 2", "Error level 3"};
content += "<h4>Overcurrent: " + String(rt_enum[datalayer_extended.meb.rt_overcurrent & 0x03]) + "</h4>";
content += "<h4>CAN fault: " + String(rt_enum[datalayer_extended.meb.rt_CAN_fault & 0x03]) + "</h4>";
content += "<h4>Overcharged: " + String(rt_enum[datalayer_extended.meb.rt_overcharge & 0x03]) + "</h4>";
content += "<h4>SOC too high: " + String(rt_enum[datalayer_extended.meb.rt_SOC_high & 0x03]) + "</h4>";
content += "<h4>SOC too low: " + String(rt_enum[datalayer_extended.meb.rt_SOC_low & 0x03]) + "</h4>";
content += "<h4>SOC jumping: " + String(rt_enum[datalayer_extended.meb.rt_SOC_jumping & 0x03]) + "</h4>";
content += "<h4>Temp difference: " + String(rt_enum[datalayer_extended.meb.rt_temp_difference & 0x03]) + "</h4>";
content += "<h4>Cell overtemp: " + String(rt_enum[datalayer_extended.meb.rt_cell_overtemp & 0x03]) + "</h4>";
content += "<h4>Cell undertemp: " + String(rt_enum[datalayer_extended.meb.rt_cell_undertemp & 0x03]) + "</h4>";
content +=
"<h4>Battery overvoltage: " + String(rt_enum[datalayer_extended.meb.rt_battery_overvolt & 0x03]) + "</h4>";
content +=
"<h4>Battery undervoltage: " + String(rt_enum[datalayer_extended.meb.rt_battery_undervol & 0x03]) + "</h4>";
content += "<h4>Cell overvoltage: " + String(rt_enum[datalayer_extended.meb.rt_cell_overvolt & 0x03]) + "</h4>";
content += "<h4>Cell undervoltage: " + String(rt_enum[datalayer_extended.meb.rt_cell_undervol & 0x03]) + "</h4>";
content += "<h4>Cell imbalance: " + String(rt_enum[datalayer_extended.meb.rt_cell_imbalance & 0x03]) + "</h4>";
content +=
"<h4>Battery unathorized: " + String(rt_enum[datalayer_extended.meb.rt_battery_unathorized & 0x03]) + "</h4>";
content +=
"<h4>Battery temperature: " + String(datalayer_extended.meb.battery_temperature_dC / 10.f, 1) + " &deg;C</h4>";
for (int i = 0; i < 3; i++) {
content += "<h4>Temperature points " + String(i * 6 + 1) + "-" + String(i * 6 + 6) + " :";
for (int j = 0; j < 6; j++)
content += " &nbsp;" + String(datalayer_extended.meb.temp_points[i * 6 + j], 1);
content += " &deg;C</h4>";
}
bool temps_done = false;
for (int i = 0; i < 7 && !temps_done; i++) {
content += "<h4>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 += " &nbsp;" + String(datalayer_extended.meb.celltemperature_dC[i * 8 + j] / 10.f, 1);
}
}
content += " &deg;C</h4>";
}
content +=
"<h4>Total charged: " + String(datalayer.battery.status.total_charged_battery_Wh / 1000.0, 1) + " kWh</h4>";
content += "<h4>Total discharged: " + String(datalayer.battery.status.total_discharged_battery_Wh / 1000.0, 1) +
" kWh</h4>";
return content;
}
};
#endif

View file

@ -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 */

View file

@ -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

View file

@ -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 += "<h4>LEAF generation: " + String(LEAFgen[datalayer_extended.nissanleaf.LEAF_gen]) + "</h4>";
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 += "<h4>Serial number: " + String(readableSerialNumber) + "</h4>";
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 += "<h4>Part number: " + String(readablePartNumber) + "</h4>";
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 += "<h4>BMS ID: " + String(readableBMSID) + "</h4>";
content += "<h4>GIDS: " + String(datalayer_extended.nissanleaf.GIDS) + "</h4>";
content += "<h4>Regen kW: " + String(datalayer_extended.nissanleaf.ChargePowerLimit) + "</h4>";
content += "<h4>Charge kW: " + String(datalayer_extended.nissanleaf.MaxPowerForCharger) + "</h4>";
content += "<h4>Interlock: " + String(datalayer_extended.nissanleaf.Interlock) + "</h4>";
content += "<h4>Insulation: " + String(datalayer_extended.nissanleaf.Insulation) + "</h4>";
content += "<h4>Relay cut request: " + String(datalayer_extended.nissanleaf.RelayCutRequest) + "</h4>";
content += "<h4>Failsafe status: " + String(datalayer_extended.nissanleaf.FailsafeStatus) + "</h4>";
content += "<h4>Fully charged: " + String(datalayer_extended.nissanleaf.Full) + "</h4>";
content += "<h4>Battery empty: " + String(datalayer_extended.nissanleaf.Empty) + "</h4>";
content += "<h4>Main relay ON: " + String(datalayer_extended.nissanleaf.MainRelayOn) + "</h4>";
content += "<h4>Heater present: " + String(datalayer_extended.nissanleaf.HeatExist) + "</h4>";
content += "<h4>Heating stopped: " + String(datalayer_extended.nissanleaf.HeatingStop) + "</h4>";
content += "<h4>Heating started: " + String(datalayer_extended.nissanleaf.HeatingStart) + "</h4>";
content += "<h4>Heating requested: " + String(datalayer_extended.nissanleaf.HeaterSendRequest) + "</h4>";
content += "<h4>CryptoChallenge: " + String(datalayer_extended.nissanleaf.CryptoChallenge) + "</h4>";
content += "<h4>SolvedChallenge: " + String(datalayer_extended.nissanleaf.SolvedChallengeMSB) +
String(datalayer_extended.nissanleaf.SolvedChallengeLSB) + "</h4>";
content += "<h4>Challenge failed: " + String(datalayer_extended.nissanleaf.challengeFailed) + "</h4>";
return content;
}
};
#endif

View file

@ -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

View file

@ -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

View file

@ -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 += "<h4>CUV " + String(datalayer_extended.zoe.CUV) + "</h4>";
content += "<h4>HVBIR " + String(datalayer_extended.zoe.HVBIR) + "</h4>";
content += "<h4>HVBUV " + String(datalayer_extended.zoe.HVBUV) + "</h4>";
content += "<h4>EOCR " + String(datalayer_extended.zoe.EOCR) + "</h4>";
content += "<h4>HVBOC " + String(datalayer_extended.zoe.HVBOC) + "</h4>";
content += "<h4>HVBOT " + String(datalayer_extended.zoe.HVBOT) + "</h4>";
content += "<h4>HVBOV " + String(datalayer_extended.zoe.HVBOV) + "</h4>";
content += "<h4>COV " + String(datalayer_extended.zoe.COV) + "</h4>";
return content;
}
};
#endif

View file

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

View file

@ -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 += "<h4>soc: " + String(datalayer_extended.zoePH2.battery_soc) + "</h4>";
content += "<h4>usable soc: " + String(datalayer_extended.zoePH2.battery_usable_soc) + "</h4>";
content += "<h4>soh: " + String(datalayer_extended.zoePH2.battery_soh) + "</h4>";
content += "<h4>pack voltage: " + String(datalayer_extended.zoePH2.battery_pack_voltage) + "</h4>";
content += "<h4>max cell voltage: " + String(datalayer_extended.zoePH2.battery_max_cell_voltage) + "</h4>";
content += "<h4>min cell voltage: " + String(datalayer_extended.zoePH2.battery_min_cell_voltage) + "</h4>";
content += "<h4>12v: " + String(datalayer_extended.zoePH2.battery_12v) + "</h4>";
content += "<h4>avg temp: " + String(datalayer_extended.zoePH2.battery_avg_temp) + "</h4>";
content += "<h4>min temp: " + String(datalayer_extended.zoePH2.battery_min_temp) + "</h4>";
content += "<h4>max temp: " + String(datalayer_extended.zoePH2.battery_max_temp) + "</h4>";
content += "<h4>max power: " + String(datalayer_extended.zoePH2.battery_max_power) + "</h4>";
content += "<h4>interlock: " + String(datalayer_extended.zoePH2.battery_interlock) + "</h4>";
content += "<h4>kwh: " + String(datalayer_extended.zoePH2.battery_kwh) + "</h4>";
content += "<h4>current: " + String(datalayer_extended.zoePH2.battery_current) + "</h4>";
content += "<h4>current offset: " + String(datalayer_extended.zoePH2.battery_current_offset) + "</h4>";
content += "<h4>max generated: " + String(datalayer_extended.zoePH2.battery_max_generated) + "</h4>";
content += "<h4>max available: " + String(datalayer_extended.zoePH2.battery_max_available) + "</h4>";
content += "<h4>current voltage: " + String(datalayer_extended.zoePH2.battery_current_voltage) + "</h4>";
content += "<h4>charging status: " + String(datalayer_extended.zoePH2.battery_charging_status) + "</h4>";
content += "<h4>remaining charge: " + String(datalayer_extended.zoePH2.battery_remaining_charge) + "</h4>";
content +=
"<h4>balance capacity total: " + String(datalayer_extended.zoePH2.battery_balance_capacity_total) + "</h4>";
content += "<h4>balance time total: " + String(datalayer_extended.zoePH2.battery_balance_time_total) + "</h4>";
content +=
"<h4>balance capacity sleep: " + String(datalayer_extended.zoePH2.battery_balance_capacity_sleep) + "</h4>";
content += "<h4>balance time sleep: " + String(datalayer_extended.zoePH2.battery_balance_time_sleep) + "</h4>";
content +=
"<h4>balance capacity wake: " + String(datalayer_extended.zoePH2.battery_balance_capacity_wake) + "</h4>";
content += "<h4>balance time wake: " + String(datalayer_extended.zoePH2.battery_balance_time_wake) + "</h4>";
content += "<h4>bms state: " + String(datalayer_extended.zoePH2.battery_bms_state) + "</h4>";
content += "<h4>balance switches: " + String(datalayer_extended.zoePH2.battery_balance_switches) + "</h4>";
content += "<h4>energy complete: " + String(datalayer_extended.zoePH2.battery_energy_complete) + "</h4>";
content += "<h4>energy partial: " + String(datalayer_extended.zoePH2.battery_energy_partial) + "</h4>";
content += "<h4>slave failures: " + String(datalayer_extended.zoePH2.battery_slave_failures) + "</h4>";
content += "<h4>mileage: " + String(datalayer_extended.zoePH2.battery_mileage) + "</h4>";
content += "<h4>fan speed: " + String(datalayer_extended.zoePH2.battery_fan_speed) + "</h4>";
content += "<h4>fan period: " + String(datalayer_extended.zoePH2.battery_fan_period) + "</h4>";
content += "<h4>fan control: " + String(datalayer_extended.zoePH2.battery_fan_control) + "</h4>";
content += "<h4>fan duty: " + String(datalayer_extended.zoePH2.battery_fan_duty) + "</h4>";
content += "<h4>temporisation: " + String(datalayer_extended.zoePH2.battery_temporisation) + "</h4>";
content += "<h4>time: " + String(datalayer_extended.zoePH2.battery_time) + "</h4>";
content += "<h4>pack time: " + String(datalayer_extended.zoePH2.battery_pack_time) + "</h4>";
content += "<h4>soc min: " + String(datalayer_extended.zoePH2.battery_soc_min) + "</h4>";
content += "<h4>soc max: " + String(datalayer_extended.zoePH2.battery_soc_max) + "</h4>";
return content;
}
};
#endif

View file

@ -10,6 +10,8 @@ class RS485Battery : public Battery {
public:
virtual void receive_RS485() = 0;
virtual void transmit_rs485(unsigned long currentMillis) = 0;
String interface_name() { return "RS485"; }
};
#endif

View file

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

View file

@ -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 =

View file

@ -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<float>(datalayer_extended.tesla.battery_beginning_of_life);
float battTempPct = static_cast<float>(datalayer_extended.tesla.battery_battTempPct) * 0.4;
float dcdcLvBusVolt = static_cast<float>(datalayer_extended.tesla.battery_dcdcLvBusVolt) * 0.0390625;
float dcdcHvBusVolt = static_cast<float>(datalayer_extended.tesla.battery_dcdcHvBusVolt) * 0.146484;
float dcdcLvOutputCurrent = static_cast<float>(datalayer_extended.tesla.battery_dcdcLvOutputCurrent) * 0.1;
float nominal_full_pack_energy =
static_cast<float>(datalayer_extended.tesla.battery_nominal_full_pack_energy) * 0.1;
float nominal_full_pack_energy_m0 =
static_cast<float>(datalayer_extended.tesla.battery_nominal_full_pack_energy_m0) * 0.02;
float nominal_energy_remaining =
static_cast<float>(datalayer_extended.tesla.battery_nominal_energy_remaining) * 0.1;
float nominal_energy_remaining_m0 =
static_cast<float>(datalayer_extended.tesla.battery_nominal_energy_remaining_m0) * 0.02;
float ideal_energy_remaining = static_cast<float>(datalayer_extended.tesla.battery_ideal_energy_remaining) * 0.1;
float ideal_energy_remaining_m0 =
static_cast<float>(datalayer_extended.tesla.battery_ideal_energy_remaining_m0) * 0.02;
float energy_to_charge_complete =
static_cast<float>(datalayer_extended.tesla.battery_energy_to_charge_complete) * 0.1;
float energy_to_charge_complete_m1 =
static_cast<float>(datalayer_extended.tesla.battery_energy_to_charge_complete_m1) * 0.02;
float energy_buffer = static_cast<float>(datalayer_extended.tesla.battery_energy_buffer) * 0.1;
float energy_buffer_m1 = static_cast<float>(datalayer_extended.tesla.battery_energy_buffer_m1) * 0.01;
float expected_energy_remaining_m1 =
static_cast<float>(datalayer_extended.tesla.battery_expected_energy_remaining_m1) * 0.02;
float total_discharge = static_cast<float>(datalayer.battery.status.total_discharged_battery_Wh) * 0.001;
float total_charge = static_cast<float>(datalayer.battery.status.total_charged_battery_Wh) * 0.001;
float packMass = static_cast<float>(datalayer_extended.tesla.battery_packMass);
float platformMaxBusVoltage =
static_cast<float>(datalayer_extended.tesla.battery_platformMaxBusVoltage) * 0.1 + 375;
float bms_min_voltage = static_cast<float>(datalayer_extended.tesla.battery_bms_min_voltage) * 0.01 * 2;
float bms_max_voltage = static_cast<float>(datalayer_extended.tesla.battery_bms_max_voltage) * 0.01 * 2;
float max_charge_current = static_cast<float>(datalayer_extended.tesla.battery_max_charge_current);
float max_discharge_current = static_cast<float>(datalayer_extended.tesla.battery_max_discharge_current);
float soc_ave = static_cast<float>(datalayer_extended.tesla.battery_soc_ave) * 0.1;
float soc_max = static_cast<float>(datalayer_extended.tesla.battery_soc_max) * 0.1;
float soc_min = static_cast<float>(datalayer_extended.tesla.battery_soc_min) * 0.1;
float soc_ui = static_cast<float>(datalayer_extended.tesla.battery_soc_ui) * 0.1;
float BrickVoltageMax = static_cast<float>(datalayer_extended.tesla.battery_BrickVoltageMax) * 0.002;
float BrickVoltageMin = static_cast<float>(datalayer_extended.tesla.battery_BrickVoltageMin) * 0.002;
float BrickModelTMax = static_cast<float>(datalayer_extended.tesla.battery_BrickModelTMax) * 0.5 - 40;
float BrickModelTMin = static_cast<float>(datalayer_extended.tesla.battery_BrickModelTMin) * 0.5 - 40;
float isolationResistance = static_cast<float>(datalayer_extended.tesla.battery_BMS_isolationResistance) * 10;
float PCS_dcdcMaxOutputCurrentAllowed =
static_cast<float>(datalayer_extended.tesla.battery_PCS_dcdcMaxOutputCurrentAllowed) * 0.1;
float PCS_dcdcTemp = static_cast<float>(datalayer_extended.tesla.PCS_dcdcTemp) * 0.1 + 40;
float PCS_ambientTemp = static_cast<float>(datalayer_extended.tesla.PCS_ambientTemp) * 0.1 + 40;
float PCS_chgPhATemp = static_cast<float>(datalayer_extended.tesla.PCS_chgPhATemp) * 0.1 + 40;
float PCS_chgPhBTemp = static_cast<float>(datalayer_extended.tesla.PCS_chgPhBTemp) * 0.1 + 40;
float PCS_chgPhCTemp = static_cast<float>(datalayer_extended.tesla.PCS_chgPhCTemp) * 0.1 + 40;
float BMS_maxRegenPower = static_cast<float>(datalayer_extended.tesla.BMS_maxRegenPower) * 0.01;
float BMS_maxDischargePower = static_cast<float>(datalayer_extended.tesla.BMS_maxDischargePower) * 0.013;
float BMS_maxStationaryHeatPower = static_cast<float>(datalayer_extended.tesla.BMS_maxStationaryHeatPower) * 0.01;
float BMS_hvacPowerBudget = static_cast<float>(datalayer_extended.tesla.BMS_hvacPowerBudget) * 0.02;
float BMS_powerDissipation = static_cast<float>(datalayer_extended.tesla.BMS_powerDissipation) * 0.02;
float BMS_flowRequest = static_cast<float>(datalayer_extended.tesla.BMS_flowRequest) * 0.3;
float BMS_inletActiveCoolTargetT =
static_cast<float>(datalayer_extended.tesla.BMS_inletActiveCoolTargetT) * 0.25 - 25;
float BMS_inletPassiveTargetT = static_cast<float>(datalayer_extended.tesla.BMS_inletPassiveTargetT) * 0.25 - 25;
float BMS_inletActiveHeatTargetT =
static_cast<float>(datalayer_extended.tesla.BMS_inletActiveHeatTargetT) * 0.25 - 25;
float BMS_packTMin = static_cast<float>(datalayer_extended.tesla.BMS_packTMin) * 0.25 - 25;
float BMS_packTMax = static_cast<float>(datalayer_extended.tesla.BMS_packTMax) * 0.25 - 25;
float PCS_dcdcMaxLvOutputCurrent = static_cast<float>(datalayer_extended.tesla.PCS_dcdcMaxLvOutputCurrent) * 0.1;
float PCS_dcdcCurrentLimit = static_cast<float>(datalayer_extended.tesla.PCS_dcdcCurrentLimit) * 0.1;
float PCS_dcdcLvOutputCurrentTempLimit =
static_cast<float>(datalayer_extended.tesla.PCS_dcdcLvOutputCurrentTempLimit) * 0.1;
float PCS_dcdcUnifiedCommand = static_cast<float>(datalayer_extended.tesla.PCS_dcdcUnifiedCommand) * 0.001;
float PCS_dcdcCLAControllerOutput =
static_cast<float>(datalayer_extended.tesla.PCS_dcdcCLAControllerOutput * 0.001);
float PCS_dcdcTankVoltage = static_cast<float>(datalayer_extended.tesla.PCS_dcdcTankVoltage);
float PCS_dcdcTankVoltageTarget = static_cast<float>(datalayer_extended.tesla.PCS_dcdcTankVoltageTarget);
float PCS_dcdcClaCurrentFreq = static_cast<float>(datalayer_extended.tesla.PCS_dcdcClaCurrentFreq) * 0.0976563;
float PCS_dcdcTCommMeasured = static_cast<float>(datalayer_extended.tesla.PCS_dcdcTCommMeasured) * 0.00195313;
float PCS_dcdcShortTimeUs = static_cast<float>(datalayer_extended.tesla.PCS_dcdcShortTimeUs) * 0.000488281;
float PCS_dcdcHalfPeriodUs = static_cast<float>(datalayer_extended.tesla.PCS_dcdcHalfPeriodUs) * 0.000488281;
float PCS_dcdcIntervalMaxFrequency = static_cast<float>(datalayer_extended.tesla.PCS_dcdcIntervalMaxFrequency);
float PCS_dcdcIntervalMaxHvBusVolt =
static_cast<float>(datalayer_extended.tesla.PCS_dcdcIntervalMaxHvBusVolt) * 0.1;
float PCS_dcdcIntervalMaxLvBusVolt =
static_cast<float>(datalayer_extended.tesla.PCS_dcdcIntervalMaxLvBusVolt) * 0.1;
float PCS_dcdcIntervalMaxLvOutputCurr =
static_cast<float>(datalayer_extended.tesla.PCS_dcdcIntervalMaxLvOutputCurr);
float PCS_dcdcIntervalMinFrequency = static_cast<float>(datalayer_extended.tesla.PCS_dcdcIntervalMinFrequency);
float PCS_dcdcIntervalMinHvBusVolt =
static_cast<float>(datalayer_extended.tesla.PCS_dcdcIntervalMinHvBusVolt) * 0.1;
float PCS_dcdcIntervalMinLvBusVolt =
static_cast<float>(datalayer_extended.tesla.PCS_dcdcIntervalMinLvBusVolt) * 0.1;
float PCS_dcdcIntervalMinLvOutputCurr =
static_cast<float>(datalayer_extended.tesla.PCS_dcdcIntervalMinLvOutputCurr);
float PCS_dcdc12vSupportLifetimekWh =
static_cast<float>(datalayer_extended.tesla.PCS_dcdc12vSupportLifetimekWh) * 0.01;
float HVP_hvp1v5Ref = static_cast<float>(datalayer_extended.tesla.HVP_hvp1v5Ref) * 0.1;
float HVP_shuntCurrentDebug = static_cast<float>(datalayer_extended.tesla.HVP_shuntCurrentDebug) * 0.1;
float HVP_dcLinkVoltage = static_cast<float>(datalayer_extended.tesla.HVP_dcLinkVoltage) * 0.1;
float HVP_packVoltage = static_cast<float>(datalayer_extended.tesla.HVP_packVoltage) * 0.1;
float HVP_fcLinkVoltage = static_cast<float>(datalayer_extended.tesla.HVP_fcLinkVoltage) * 0.1;
float HVP_packContVoltage = static_cast<float>(datalayer_extended.tesla.HVP_packContVoltage) * 0.1;
float HVP_packNegativeV = static_cast<float>(datalayer_extended.tesla.HVP_packNegativeV) * 0.1;
float HVP_packPositiveV = static_cast<float>(datalayer_extended.tesla.HVP_packPositiveV) * 0.1;
float HVP_pyroAnalog = static_cast<float>(datalayer_extended.tesla.HVP_pyroAnalog) * 0.1;
float HVP_dcLinkNegativeV = static_cast<float>(datalayer_extended.tesla.HVP_dcLinkNegativeV) * 0.1;
float HVP_dcLinkPositiveV = static_cast<float>(datalayer_extended.tesla.HVP_dcLinkPositiveV) * 0.1;
float HVP_fcLinkNegativeV = static_cast<float>(datalayer_extended.tesla.HVP_fcLinkNegativeV) * 0.1;
float HVP_fcContCoilCurrent = static_cast<float>(datalayer_extended.tesla.HVP_fcContCoilCurrent) * 0.1;
float HVP_fcContVoltage = static_cast<float>(datalayer_extended.tesla.HVP_fcContVoltage) * 0.1;
float HVP_hvilInVoltage = static_cast<float>(datalayer_extended.tesla.HVP_hvilInVoltage) * 0.1;
float HVP_hvilOutVoltage = static_cast<float>(datalayer_extended.tesla.HVP_hvilOutVoltage) * 0.1;
float HVP_fcLinkPositiveV = static_cast<float>(datalayer_extended.tesla.HVP_fcLinkPositiveV) * 0.1;
float HVP_packContCoilCurrent = static_cast<float>(datalayer_extended.tesla.HVP_packContCoilCurrent) * 0.1;
float HVP_battery12V = static_cast<float>(datalayer_extended.tesla.HVP_battery12V) * 0.1;
float HVP_shuntRefVoltageDbg = static_cast<float>(datalayer_extended.tesla.HVP_shuntRefVoltageDbg) * 0.001;
float HVP_shuntAuxCurrentDbg = static_cast<float>(datalayer_extended.tesla.HVP_shuntAuxCurrentDbg) * 0.1;
float HVP_shuntBarTempDbg = static_cast<float>(datalayer_extended.tesla.HVP_shuntBarTempDbg) * 0.01;
float HVP_shuntAsicTempDbg = static_cast<float>(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 += "<h4>Contactor Status: " + String(contactorText[datalayer_extended.tesla.status_contactor]) + "</h4>";
content += "<h4>HVIL: " + String(hvilStatusState[datalayer_extended.tesla.hvil_status]) + "</h4>";
content +=
"<h4>Negative contactor: " + String(contactorState[datalayer_extended.tesla.packContNegativeState]) + "</h4>";
content +=
"<h4>Positive contactor: " + String(contactorState[datalayer_extended.tesla.packContPositiveState]) + "</h4>";
content += "<h4>Closing allowed?: " + String(noYes[datalayer_extended.tesla.packCtrsClosingAllowed]) + "</h4>";
content += "<h4>Pyrotest in Progress: " + String(noYes[datalayer_extended.tesla.pyroTestInProgress]) + "</h4>";
content += "<h4>Contactors Open Now Requested: " +
String(noYes[datalayer_extended.tesla.battery_packCtrsOpenNowRequested]) + "</h4>";
content +=
"<h4>Contactors Open Requested: " + String(noYes[datalayer_extended.tesla.battery_packCtrsOpenRequested]) +
"</h4>";
content += "<h4>Contactors Request Status: " +
String(HVP_contactor[datalayer_extended.tesla.battery_packCtrsRequestStatus]) + "</h4>";
content += "<h4>Contactors Reset Request Required: " +
String(noYes[datalayer_extended.tesla.battery_packCtrsResetRequestRequired]) + "</h4>";
content +=
"<h4>DC Link Allowed to Energize: " + String(noYes[datalayer_extended.tesla.battery_dcLinkAllowedToEnergize]) +
"</h4>";
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 += "<h4>BMS Serial number: " + String(readableSerialNumber) + "</h4>";
// Comment what data you would like to display, order can be changed.
//0x352 850 BMS_energyStatus
if (datalayer_extended.tesla.BMS352_mux == false) {
content += "<h3>BMS 0x352 w/o mux</h3>"; //if using older BMS <2021 and comment 0x352 without MUX
content += "<h4>Calculated SOH: " + String(nominal_full_pack_energy * 100 / beginning_of_life) + "</h4>";
content += "<h4>Nominal Full Pack Energy: " + String(nominal_full_pack_energy) + " KWh</h4>";
content += "<h4>Nominal Energy Remaining: " + String(nominal_energy_remaining) + " KWh</h4>";
content += "<h4>Ideal Energy Remaining: " + String(ideal_energy_remaining) + " KWh</h4>";
content += "<h4>Energy to Charge Complete: " + String(energy_to_charge_complete) + " KWh</h4>";
content += "<h4>Energy Buffer: " + String(energy_buffer) + " KWh</h4>";
content += "<h4>Full Charge Complete: " + String(noYes[datalayer_extended.tesla.battery_full_charge_complete]) +
"</h4>"; //bool
}
//0x352 850 BMS_energyStatus
if (datalayer_extended.tesla.BMS352_mux == true) {
content += "<h3>BMS 0x352 w/ mux</h3>"; //if using newer BMS >2021 and comment 0x352 with MUX
content += "<h4>Calculated SOH: " + String(nominal_full_pack_energy_m0 * 100 / beginning_of_life) + "</h4>";
content += "<h4>Nominal Full Pack Energy: " + String(nominal_full_pack_energy_m0) + " KWh</h4>";
content += "<h4>Nominal Energy Remaining: " + String(nominal_energy_remaining_m0) + " KWh</h4>";
content += "<h4>Ideal Energy Remaining: " + String(ideal_energy_remaining_m0) + " KWh</h4>";
content += "<h4>Energy to Charge Complete: " + String(energy_to_charge_complete_m1) + " KWh</h4>";
content += "<h4>Energy Buffer: " + String(energy_buffer_m1) + " KWh</h4>";
content += "<h4>Expected Energy Remaining: " + String(expected_energy_remaining_m1) + " KWh</h4>";
content += "<h4>Fully Charged: " + String(noYes[datalayer_extended.tesla.battery_fully_charged]) + "</h4>";
}
//0x3D2 978 BMS_kwhCounter
content += "<h4>Total Discharge: " + String(total_discharge) + " KWh</h4>";
content += "<h4>Total Charge: " + String(total_charge) + " KWh</h4>";
//0x292 658 BMS_socStates
content += "<h4>Battery Beginning of Life: " + String(beginning_of_life) + " KWh</h4>";
content += "<h4>Battery SOC UI: " + String(soc_ui) + " </h4>";
content += "<h4>Battery SOC Ave: " + String(soc_ave) + " </h4>";
content += "<h4>Battery SOC Max: " + String(soc_max) + " </h4>";
content += "<h4>Battery SOC Min: " + String(soc_min) + " </h4>";
content += "<h4>Battery Temp Percent: " + String(battTempPct) + " </h4>";
//0x2B4 PCS_dcdcRailStatus
content += "<h4>PCS Lv Output: " + String(dcdcLvOutputCurrent) + " A</h4>";
content += "<h4>PCS Lv Bus: " + String(dcdcLvBusVolt) + " V</h4>";
content += "<h4>PCS Hv Bus: " + String(dcdcHvBusVolt) + " V</h4>";
//0x392 BMS_packConfig
//content += "<h4>packConfigMultiplexer: " + String(datalayer_extended.tesla.battery_packConfigMultiplexer) + "</h4>"; // Not giving useable data
//content += "<h4>moduleType: " + String(datalayer_extended.tesla.battery_moduleType) + "</h4>"; // Not giving useable data
//content += "<h4>reserveConfig: " + String(datalayer_extended.tesla.battery_reservedConfig) + "</h4>"; // Not giving useable data
content += "<h4>Battery Pack Mass: " + String(packMass) + " KG</h4>";
content += "<h4>Platform Max Bus Voltage: " + String(platformMaxBusVoltage) + " V</h4>";
//0x2D2 722 BMSVAlimits
content += "<h4>BMS Min Voltage: " + String(bms_min_voltage) + " V</h4>";
content += "<h4>BMS Max Voltage: " + String(bms_max_voltage) + " V</h4>";
content += "<h4>Max Charge Current: " + String(max_charge_current) + " A</h4>";
content += "<h4>Max Discharge Current: " + String(max_discharge_current) + " A</h4>";
//0x332 818 BMS_bmbMinMax
content += "<h4>Brick Voltage Max: " + String(BrickVoltageMax) + " V</h4>";
content += "<h4>Brick Voltage Min: " + String(BrickVoltageMin) + " V</h4>";
content += "<h4>Brick Temp Max Num: " + String(datalayer_extended.tesla.battery_BrickTempMaxNum) + " </h4>";
content += "<h4>Brick Temp Min Num: " + String(datalayer_extended.tesla.battery_BrickTempMinNum) + " </h4>";
//content += "<h4>Brick Model Temp Max: " + String(BrickModelTMax) + " C</h4>";// Not giving useable data
//content += "<h4>Brick Model Temp Min: " + String(BrickModelTMin) + " C</h4>";// Not giving useable data
//0x2A4 676 PCS_thermalStatus
content += "<h4>PCS dcdc Temp: " + String(PCS_dcdcTemp) + " DegC</h4>";
content += "<h4>PCS Ambient Temp: " + String(PCS_ambientTemp) + " DegC</h4>";
content += "<h4>PCS Chg PhA Temp: " + String(PCS_chgPhATemp) + " DegC</h4>";
content += "<h4>PCS Chg PhB Temp: " + String(PCS_chgPhBTemp) + " DegC</h4>";
content += "<h4>PCS Chg PhC Temp: " + String(PCS_chgPhCTemp) + " DegC</h4>";
//0x252 594 BMS_powerAvailable
content += "<h4>Max Regen Power: " + String(BMS_maxRegenPower) + " KW</h4>";
content += "<h4>Max Discharge Power: " + String(BMS_maxDischargePower) + " KW</h4>";
//content += "<h4>Max Stationary Heat Power: " + String(BMS_maxStationaryHeatPower) + " KWh</h4>"; // Not giving useable data
//content += "<h4>HVAC Power Budget: " + String(BMS_hvacPowerBudget) + " KW</h4>"; // Not giving useable data
//content += "<h4>Not Enough Power For Heat Pump: " + String(noYes[datalayer_extended.tesla.BMS_notEnoughPowerForHeatPump]) + "</h4>"; // Not giving useable data
content +=
"<h4>Power Limit State: " + String(BMS_powerLimitState[datalayer_extended.tesla.BMS_powerLimitState]) + "</h4>";
//content += "<h4>Inverter TQF: " + String(datalayer_extended.tesla.BMS_inverterTQF) + "</h4>"; // Not giving useable data
//0x212 530 BMS_status
content += "<h4>Isolation Resistance: " + String(isolationResistance) + " kOhms</h4>";
content +=
"<h4>BMS Contactor State: " + String(BMS_contactorState[datalayer_extended.tesla.battery_BMS_contactorState]) +
"</h4>";
content += "<h4>BMS State: " + String(BMS_state[datalayer_extended.tesla.battery_BMS_state]) + "</h4>";
content += "<h4>BMS HV State: " + String(BMS_hvState[datalayer_extended.tesla.battery_BMS_hvState]) + "</h4>";
content += "<h4>BMS UI Charge Status: " + String(BMS_uiChargeStatus[datalayer_extended.tesla.battery_BMS_hvState]) +
"</h4>";
content +=
"<h4>BMS PCS PWM Enabled: " + String(Fault[datalayer_extended.tesla.battery_BMS_pcsPwmEnabled]) + "</h4>";
//0x312 786 BMS_thermalStatus
content += "<h4>Power Dissipation: " + String(BMS_powerDissipation) + " kW</h4>";
content += "<h4>Flow Request: " + String(BMS_flowRequest) + " LPM</h4>";
content += "<h4>Inlet Active Cool Target Temp: " + String(BMS_inletActiveCoolTargetT) + " DegC</h4>";
content += "<h4>Inlet Passive Target Temp: " + String(BMS_inletPassiveTargetT) + " DegC</h4>";
content += "<h4>Inlet Active Heat Target Temp: " + String(BMS_inletActiveHeatTargetT) + " DegC</h4>";
content += "<h4>Pack Temp Min: " + String(BMS_packTMin) + " DegC</h4>";
content += "<h4>Pack Temp Max: " + String(BMS_packTMax) + " DegC</h4>";
content += "<h4>PCS No Flow Request: " + String(Fault[datalayer_extended.tesla.BMS_pcsNoFlowRequest]) + "</h4>";
content += "<h4>BMS No Flow Request: " + String(Fault[datalayer_extended.tesla.BMS_noFlowRequest]) + "</h4>";
//0x224 548 PCS_dcdcStatus
content +=
"<h4>Precharge Status: " + String(PCS_dcdcStatus[datalayer_extended.tesla.battery_PCS_dcdcPrechargeStatus]) +
"</h4>";
content +=
"<h4>12V Support Status: " + String(PCS_dcdcStatus[datalayer_extended.tesla.battery_PCS_dcdc12VSupportStatus]) +
"</h4>";
content += "<h4>HV Bus Discharge Status: " +
String(PCS_dcdcStatus[datalayer_extended.tesla.battery_PCS_dcdcHvBusDischargeStatus]) + "</h4>";
content +=
"<h4>Main State: " + String(PCS_dcdcMainState[datalayer_extended.tesla.battery_PCS_dcdcMainState]) + "</h4>";
content +=
"<h4>Sub State: " + String(PCS_dcdcSubState[datalayer_extended.tesla.battery_PCS_dcdcSubState]) + "</h4>";
content += "<h4>PCS Faulted: " + String(Fault[datalayer_extended.tesla.battery_PCS_dcdcFaulted]) + "</h4>";
content +=
"<h4>Output Is Limited: " + String(Fault[datalayer_extended.tesla.battery_PCS_dcdcOutputIsLimited]) + "</h4>";
content += "<h4>Max Output Current Allowed: " + String(PCS_dcdcMaxOutputCurrentAllowed) + " A</h4>";
content += "<h4>Precharge Rty Cnt: " + String(falseTrue[datalayer_extended.tesla.battery_PCS_dcdcPrechargeRtyCnt]) +
"</h4>";
content +=
"<h4>12V Support Rty Cnt: " + String(falseTrue[datalayer_extended.tesla.battery_PCS_dcdc12VSupportRtyCnt]) +
"</h4>";
content += "<h4>Discharge Rty Cnt: " + String(falseTrue[datalayer_extended.tesla.battery_PCS_dcdcDischargeRtyCnt]) +
"</h4>";
content +=
"<h4>PWM Enable Line: " + String(Fault[datalayer_extended.tesla.battery_PCS_dcdcPwmEnableLine]) + "</h4>";
content += "<h4>Supporting Fixed LV Target: " +
String(Fault[datalayer_extended.tesla.battery_PCS_dcdcSupportingFixedLvTarget]) + "</h4>";
content += "<h4>Precharge Restart Cnt: " +
String(falseTrue[datalayer_extended.tesla.battery_PCS_dcdcPrechargeRestartCnt]) + "</h4>";
content += "<h4>Initial Precharge Substate: " +
String(PCS_dcdcSubState[datalayer_extended.tesla.battery_PCS_dcdcInitialPrechargeSubState]) + "</h4>";
//0x2C4 708 PCS_logging
content += "<h4>PCS_dcdcMaxLvOutputCurrent: " + String(PCS_dcdcMaxLvOutputCurrent) + " A</h4>";
content += "<h4>PCS_dcdcCurrentLimit: " + String(PCS_dcdcCurrentLimit) + " A</h4>";
content += "<h4>PCS_dcdcLvOutputCurrentTempLimit: " + String(PCS_dcdcLvOutputCurrentTempLimit) + " A</h4>";
content += "<h4>PCS_dcdcUnifiedCommand: " + String(PCS_dcdcUnifiedCommand) + "</h4>";
content += "<h4>PCS_dcdcCLAControllerOutput: " + String(PCS_dcdcCLAControllerOutput) + "</h4>";
content += "<h4>PCS_dcdcTankVoltage: " + String(PCS_dcdcTankVoltage) + " V</h4>";
content += "<h4>PCS_dcdcTankVoltageTarget: " + String(PCS_dcdcTankVoltageTarget) + " V</h4>";
content += "<h4>PCS_dcdcClaCurrentFreq: " + String(PCS_dcdcClaCurrentFreq) + " kHz</h4>";
content += "<h4>PCS_dcdcTCommMeasured: " + String(PCS_dcdcTCommMeasured) + " us</h4>";
content += "<h4>PCS_dcdcShortTimeUs: " + String(PCS_dcdcShortTimeUs) + " us</h4>";
content += "<h4>PCS_dcdcHalfPeriodUs: " + String(PCS_dcdcHalfPeriodUs) + " us</h4>";
content += "<h4>PCS_dcdcIntervalMaxFrequency: " + String(PCS_dcdcIntervalMaxFrequency) + " kHz</h4>";
content += "<h4>PCS_dcdcIntervalMaxHvBusVolt: " + String(PCS_dcdcIntervalMaxHvBusVolt) + " V</h4>";
content += "<h4>PCS_dcdcIntervalMaxLvBusVolt: " + String(PCS_dcdcIntervalMaxLvBusVolt) + " V</h4>";
content += "<h4>PCS_dcdcIntervalMaxLvOutputCurr: " + String(PCS_dcdcIntervalMaxLvOutputCurr) + " A</h4>";
content += "<h4>PCS_dcdcIntervalMinFrequency: " + String(PCS_dcdcIntervalMinFrequency) + " kHz</h4>";
content += "<h4>PCS_dcdcIntervalMinHvBusVolt: " + String(PCS_dcdcIntervalMinHvBusVolt) + " V</h4>";
content += "<h4>PCS_dcdcIntervalMinLvBusVolt: " + String(PCS_dcdcIntervalMinLvBusVolt) + " V</h4>";
content += "<h4>PCS_dcdcIntervalMinLvOutputCurr: " + String(PCS_dcdcIntervalMinLvOutputCurr) + " A</h4>";
content += "<h4>PCS_dcdc12vSupportLifetimekWh: " + String(PCS_dcdc12vSupportLifetimekWh) + " kWh</h4>";
//0x7AA 1962 HVP_debugMessage
content += "<h4>HVP_battery12V: " + String(HVP_battery12V) + " V</h4>";
content += "<h4>HVP_dcLinkVoltage: " + String(HVP_dcLinkVoltage) + " V</h4>";
content += "<h4>HVP_packVoltage: " + String(HVP_packVoltage) + " V</h4>";
content += "<h4>HVP_packContVoltage: " + String(HVP_packContVoltage) + " V</h4>";
content += "<h4>HVP_packContCoilCurrent: " + String(HVP_packContCoilCurrent) + " A</h4>";
content += "<h4>HVP_pyroAnalog: " + String(HVP_pyroAnalog) + " V</h4>";
content += "<h4>HVP_hvp1v5Ref: " + String(HVP_hvp1v5Ref) + " V</h4>";
content += "<h4>HVP_hvilInVoltage: " + String(HVP_hvilInVoltage) + " V</h4>";
content += "<h4>HVP_hvilOutVoltage: " + String(HVP_hvilOutVoltage) + " V</h4>";
content +=
"<h4>HVP_gpioPassivePyroDepl: " + String(Fault[datalayer_extended.tesla.HVP_gpioPassivePyroDepl]) + "</h4>";
content += "<h4>HVP_gpioPyroIsoEn: " + String(Fault[datalayer_extended.tesla.HVP_gpioPyroIsoEn]) + "</h4>";
content += "<h4>HVP_gpioCpFaultIn: " + String(Fault[datalayer_extended.tesla.HVP_gpioCpFaultIn]) + "</h4>";
content +=
"<h4>HVP_gpioPackContPowerEn: " + String(Fault[datalayer_extended.tesla.HVP_gpioPackContPowerEn]) + "</h4>";
content += "<h4>HVP_gpioHvCablesOk: " + String(Fault[datalayer_extended.tesla.HVP_gpioHvCablesOk]) + "</h4>";
content += "<h4>HVP_gpioHvpSelfEnable: " + String(Fault[datalayer_extended.tesla.HVP_gpioHvpSelfEnable]) + "</h4>";
content += "<h4>HVP_gpioLed: " + String(Fault[datalayer_extended.tesla.HVP_gpioLed]) + "</h4>";
content += "<h4>HVP_gpioCrashSignal: " + String(Fault[datalayer_extended.tesla.HVP_gpioCrashSignal]) + "</h4>";
content +=
"<h4>HVP_gpioShuntDataReady: " + String(Fault[datalayer_extended.tesla.HVP_gpioShuntDataReady]) + "</h4>";
content += "<h4>HVP_gpioFcContPosAux: " + String(Fault[datalayer_extended.tesla.HVP_gpioFcContPosAux]) + "</h4>";
content += "<h4>HVP_gpioFcContNegAux: " + String(Fault[datalayer_extended.tesla.HVP_gpioFcContNegAux]) + "</h4>";
content += "<h4>HVP_gpioBmsEout: " + String(Fault[datalayer_extended.tesla.HVP_gpioBmsEout]) + "</h4>";
content += "<h4>HVP_gpioCpFaultOut: " + String(Fault[datalayer_extended.tesla.HVP_gpioCpFaultOut]) + "</h4>";
content += "<h4>HVP_gpioPyroPor: " + String(Fault[datalayer_extended.tesla.HVP_gpioPyroPor]) + "</h4>";
content += "<h4>HVP_gpioShuntEn: " + String(Fault[datalayer_extended.tesla.HVP_gpioShuntEn]) + "</h4>";
content += "<h4>HVP_gpioHvpVerEn: " + String(Fault[datalayer_extended.tesla.HVP_gpioHvpVerEn]) + "</h4>";
content +=
"<h4>HVP_gpioPackCoontPosFlywheel: " + String(Fault[datalayer_extended.tesla.HVP_gpioPackCoontPosFlywheel]) +
"</h4>";
content += "<h4>HVP_gpioCpLatchEnable: " + String(Fault[datalayer_extended.tesla.HVP_gpioCpLatchEnable]) + "</h4>";
content += "<h4>HVP_gpioPcsEnable: " + String(Fault[datalayer_extended.tesla.HVP_gpioPcsEnable]) + "</h4>";
content +=
"<h4>HVP_gpioPcsDcdcPwmEnable: " + String(Fault[datalayer_extended.tesla.HVP_gpioPcsDcdcPwmEnable]) + "</h4>";
content += "<h4>HVP_gpioPcsChargePwmEnable: " + String(Fault[datalayer_extended.tesla.HVP_gpioPcsChargePwmEnable]) +
"</h4>";
content +=
"<h4>HVP_gpioFcContPowerEnable: " + String(Fault[datalayer_extended.tesla.HVP_gpioFcContPowerEnable]) + "</h4>";
content += "<h4>HVP_gpioHvilEnable: " + String(Fault[datalayer_extended.tesla.HVP_gpioHvilEnable]) + "</h4>";
content += "<h4>HVP_gpioSecDrdy: " + String(Fault[datalayer_extended.tesla.HVP_gpioSecDrdy]) + "</h4>";
content += "<h4>HVP_shuntCurrentDebug: " + String(HVP_shuntCurrentDebug) + " A</h4>";
content += "<h4>HVP_packCurrentMia: " + String(noYes[datalayer_extended.tesla.HVP_packCurrentMia]) + "</h4>";
content += "<h4>HVP_auxCurrentMia: " + String(noYes[datalayer_extended.tesla.HVP_auxCurrentMia]) + "</h4>";
content += "<h4>HVP_currentSenseMia: " + String(noYes[datalayer_extended.tesla.HVP_currentSenseMia]) + "</h4>";
content +=
"<h4>HVP_shuntRefVoltageMismatch: " + String(noYes[datalayer_extended.tesla.HVP_shuntRefVoltageMismatch]) +
"</h4>";
content +=
"<h4>HVP_shuntThermistorMia: " + String(noYes[datalayer_extended.tesla.HVP_shuntThermistorMia]) + "</h4>";
content += "<h4>HVP_shuntHwMia: " + String(noYes[datalayer_extended.tesla.HVP_shuntHwMia]) + "</h4>";
//content += "<h4>HVP_fcLinkVoltage: " + String(HVP_fcLinkVoltage) + " V</h4>"; // Not giving useable data
//content += "<h4>HVP_packNegativeV: " + String(HVP_packNegativeV) + " V</h4>"; // Not giving useable data
//content += "<h4>HVP_packPositiveV: " + String(HVP_packPositiveV) + " V</h4>"; // Not giving useable data
//content += "<h4>HVP_dcLinkNegativeV: " + String(HVP_dcLinkNegativeV) + " V</h4>"; // Not giving useable data
//content += "<h4>HVP_dcLinkPositiveV: " + String(HVP_dcLinkPositiveV) + " V</h4>"; // Not giving useable data
//content += "<h4>HVP_fcLinkNegativeV: " + String(HVP_fcLinkNegativeV) + " V</h4>"; // Not giving useable data
//content += "<h4>HVP_fcContCoilCurrent: " + String(HVP_fcContCoilCurrent) + " A</h4>"; // Not giving useable data
//content += "<h4>HVP_fcContVoltage: " + String(HVP_fcContVoltage) + " V</h4>"; // Not giving useable data
//content += "<h4>HVP_fcLinkPositiveV: " + String(HVP_fcLinkPositiveV) + " V</h4>"; // Not giving useable data
//content += "<h4>HVP_shuntRefVoltageDbg: " + String(HVP_shuntRefVoltageDbg) + " V</h4>"; // Not giving useable data
//content += "<h4>HVP_shuntAuxCurrentDbg: " + String(HVP_shuntAuxCurrentDbg) + " A</h4>"; // Not giving useable data
//content += "<h4>HVP_shuntBarTempDbg: " + String(HVP_shuntBarTempDbg) + " DegC</h4>"; // Not giving useable data
//content += "<h4>HVP_shuntAsicTempDbg: " + String(HVP_shuntAsicTempDbg) + " DegC</h4>"; // Not giving useable data
//content += "<h4>HVP_shuntAuxCurrentStatus: " + String(HVP_status[datalayer_extended.tesla.HVP_shuntAuxCurrentStatus]) + "</h4>"; // Not giving useable data
//content += "<h4>HVP_shuntBarTempStatus: " + String(HVP_status[datalayer_extended.tesla.HVP_shuntBarTempStatus]) + "</h4>"; // Not giving useable data
//content += "<h4>HVP_shuntAsicTempStatus: " + String(HVP_status[datalayer_extended.tesla.HVP_shuntAsicTempStatus]) + "</h4>"; // Not giving useable data
return content;
}
};
#endif

View file

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

View file

@ -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();

View file

@ -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 += "<h4>BECM reported SOC: " + String(datalayer_extended.VolvoPolestar.soc_bms) + "</h4>";
content += "<h4>Calculated SOC: " + String(datalayer_extended.VolvoPolestar.soc_calc) + "</h4>";
content += "<h4>Rescaled SOC: " + String(datalayer_extended.VolvoPolestar.soc_rescaled / 10) + "</h4>";
content += "<h4>BECM reported SOH: " + String(datalayer_extended.VolvoPolestar.soh_bms) + "</h4>";
content += "<h4>BECM supply voltage: " + String(datalayer_extended.VolvoPolestar.BECMsupplyVoltage) + " mV</h4>";
content += "<h4>HV voltage: " + String(datalayer_extended.VolvoPolestar.BECMBatteryVoltage) + " V</h4>";
content += "<h4>HV current: " + String(datalayer_extended.VolvoPolestar.BECMBatteryCurrent) + " A</h4>";
content += "<h4>Dynamic max voltage: " + String(datalayer_extended.VolvoPolestar.BECMUDynMaxLim) + " V</h4>";
content += "<h4>Dynamic min voltage: " + String(datalayer_extended.VolvoPolestar.BECMUDynMinLim) + " V</h4>";
content +=
"<h4>Discharge power limit 1: " + String(datalayer_extended.VolvoPolestar.HvBattPwrLimDcha1) + " kW</h4>";
content +=
"<h4>Discharge soft power limit: " + String(datalayer_extended.VolvoPolestar.HvBattPwrLimDchaSoft) + " kW</h4>";
content +=
"<h4>Discharge power limit slow aging: " + String(datalayer_extended.VolvoPolestar.HvBattPwrLimDchaSlowAgi) +
" kW</h4>";
content +=
"<h4>Charge power limit slow aging: " + String(datalayer_extended.VolvoPolestar.HvBattPwrLimChrgSlowAgi) +
" kW</h4>";
content += "<h4>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 += "</h4><h4>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 += "</h4><h4>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 += "</h4><h4>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

View file

@ -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

View file

@ -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 += "<h4>BECM reported SOC: " + String(datalayer_extended.VolvoHybrid.soc_bms) + "</h4>";
content += "<h4>Calculated SOC: " + String(datalayer_extended.VolvoHybrid.soc_calc) + "</h4>";
content += "<h4>Rescaled SOC: " + String(datalayer_extended.VolvoHybrid.soc_rescaled / 10) + "</h4>";
content += "<h4>BECM reported SOH: " + String(datalayer_extended.VolvoHybrid.soh_bms) + "</h4>";
content += "<h4>BECM supply voltage: " + String(datalayer_extended.VolvoHybrid.BECMsupplyVoltage) + " mV</h4>";
content += "<h4>HV voltage: " + String(datalayer_extended.VolvoHybrid.BECMBatteryVoltage) + " V</h4>";
content += "<h4>HV current: " + String(datalayer_extended.VolvoHybrid.BECMBatteryCurrent) + " A</h4>";
content += "<h4>Dynamic max voltage: " + String(datalayer_extended.VolvoHybrid.BECMUDynMaxLim) + " V</h4>";
content += "<h4>Dynamic min voltage: " + String(datalayer_extended.VolvoHybrid.BECMUDynMinLim) + " V</h4>";
content += "<h4>Discharge power limit 1: " + String(datalayer_extended.VolvoHybrid.HvBattPwrLimDcha1) + " kW</h4>";
content +=
"<h4>Discharge soft power limit: " + String(datalayer_extended.VolvoHybrid.HvBattPwrLimDchaSoft) + " kW</h4>";
content += "<h4>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 += "</h4><h4>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 += "</h4><h4>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 += "</h4><h4>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

View file

@ -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 */

View file

@ -241,7 +241,8 @@ void update_machineryprotection() {
}
}
#ifdef DOUBLE_BATTERY // Additional Double-Battery safeties are checked here
// 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
@ -297,8 +298,7 @@ void update_machineryprotection() {
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) {
@ -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
if (battery2) {
datalayer.battery2.status.max_discharge_power_W = 0;
datalayer.battery2.status.max_charge_power_W = 0;
#endif
}
} else {
clear_event(EVENT_PAUSE_BEGIN);

View file

@ -0,0 +1,18 @@
#ifndef _BATTERY_HTML_RENDERER_H
#define _BATTERY_HTML_RENDERER_H
#include <WString.h>
// 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

File diff suppressed because it is too large Load diff

View file

@ -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<bool(Battery*)> condition;
// Function that executes the command for the given battery.
std::function<void(Battery*)> action;
};
extern std::vector<BatteryCommand> battery_commands;
#endif

View file

@ -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
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;}";
#endif
} 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
if (battery2) {
content += "#valueDisplay, #valueDisplay2 {text-align: left;font-weight: bold;margin-top: 10px;}";
#else
} else {
content += "#valueDisplay {text-align: left;font-weight: bold;margin-top: 10px;}";
#endif
}
content += "</style>";
content += "<button onclick='home()'>Back to main page</button>";
@ -68,7 +71,7 @@ String cellmonitor_processor(const String& var) {
// Close the block
content += "</div>";
#ifdef DOUBLE_BATTERY
if (battery2) {
// Start a new block with a specific background color
content += "<div style='background-color: #303E41; padding: 10px; margin-bottom: 10px; border-radius: 50px'>";
@ -104,7 +107,7 @@ String cellmonitor_processor(const String& var) {
content += "</div>";
content += "<button onclick='home()'>Back to main page</button>";
#endif // DOUBLE_BATTERY
}
content += "<script>";
// Populate cell data
@ -237,7 +240,7 @@ String cellmonitor_processor(const String& var) {
"available';";
content += "}";
#ifdef DOUBLE_BATTERY
if (battery2) {
// Populate cell data
content += "const data2 = [";
for (uint8_t i = 0u; i < datalayer.battery2.info.number_of_cells; i++) {
@ -362,11 +365,11 @@ String cellmonitor_processor(const String& var) {
content += "}";
content += "else {";
content +=
"document.getElementById('voltageValues2').textContent = 'Cell information not yet fetched, or information not "
"document.getElementById('voltageValues2').textContent = 'Cell information not yet fetched, or information "
"not "
"available';";
content += "}";
#endif //DOUBLE_BATTERY
}
// Automatic refresh is nice
content += "setTimeout(function(){ location.reload(true); }, 20000);";

View file

@ -2,6 +2,7 @@
#include <Arduino.h>
#include "../../charger/CHARGERS.h"
#include "../../datalayer/datalayer.h"
#include "../../include.h"
String settings_processor(const String& var) {
if (var == "X") {
@ -26,18 +27,13 @@ String settings_processor(const String& var) {
"<h4 style='color: white;'>Password: ######## <span id='Password'></span> <button "
"onclick='editPassword()'>Edit</button></h4>";
#ifndef RS485_BATTERY_SELECTED
content += "<h4 style='color: white;'>Battery interface: <span id='Battery'>" +
String(getCANInterfaceName(can_config.battery)) + "</span></h4>";
#endif
#ifdef RS485_BATTERY_SELECTED
content += "<h4 style='color: white;'>Battery interface: RS485<span id='Battery'></span></h4>";
#endif
content +=
"<h4 style='color: white;'>Battery interface: <span id='Battery'>" + battery->interface_name() + "</span></h4>";
#ifdef DOUBLE_BATTERY
content += "<h4 style='color: white;'>Battery #2 interface: <span id='Battery'>" +
String(getCANInterfaceName(can_config.battery_double)) + "</span></h4>";
#endif // DOUBLE_BATTERY
if (battery2) {
content += "<h4 style='color: white;'>Battery #2 interface: <span id='Battery'>" + battery->interface_name() +
"</span></h4>";
}
if (inverter) {
content += "<h4 style='color: white;'>Inverter interface: <span id='Inverter'>" +
@ -93,20 +89,14 @@ String settings_processor(const String& var) {
// Close the block
content += "</div>";
#ifdef TEST_FAKE_BATTERY
// Start a new block with blue background color
if (battery->supports_set_fake_voltage()) {
content += "<div style='background-color: #2E37AD; padding: 10px; margin-bottom: 10px;border-radius: 50px'>";
float voltageFloat =
static_cast<float>(datalayer.battery.status.voltage_dV) / 10.0; // Convert to float and divide by 10
content += "<h4 style='color: white;'>Fake battery voltage: " + String(voltageFloat, 1) +
content += "<h4 style='color: white;'>Fake battery voltage: " + String(battery->get_voltage(), 1) +
" V </span> <button onclick='editFakeBatteryVoltage()'>Edit</button></h4>";
// Close the block
content += "</div>";
#endif
#ifdef TESLA_MODEL_3Y_BATTERY
}
if (battery->supports_manual_balancing()) {
// Start a new block with grey background color
content += "<div style='background-color: #303E47; padding: 10px; margin-bottom: 10px;border-radius: 50px'>";
@ -139,7 +129,7 @@ String settings_processor(const String& var) {
// Close the block
content += "</div>";
#endif
}
if (charger) {
// Start a new block with orange background color
@ -252,7 +242,6 @@ String settings_processor(const String& var) {
"between 0 "
"and 1000.0');}}}";
#ifdef TESLA_MODEL_3Y_BATTERY
content +=
"function editTeslaBalAct(){var value=prompt('Enable or disable forced LFP balancing. Makes the battery charge "
"to 101percent. This should be performed once every month, to keep LFP batteries balanced. Ensure battery is "
@ -291,16 +280,15 @@ String settings_processor(const String& var) {
"XMLHttpRequest();xhr.onload=editComplete;xhr.onerror=editError;xhr.open('GET','/"
"BalMaxDevCellV?value='+value,true);xhr.send();}else{alert('Invalid value. Please enter a value "
"between 300 and 600');}}}";
#endif
#ifdef TEST_FAKE_BATTERY
if (battery->supports_set_fake_voltage()) {
content +=
"function editFakeBatteryVoltage(){var value=prompt('Enter new fake battery "
"voltage');if(value!==null){if(value>=0&&value<=5000){var xhr=new "
"XMLHttpRequest();xhr.onload=editComplete;xhr.onerror=editError;xhr.open('GET','/"
"updateFakeBatteryVoltage?value='+value,true);xhr.send();}else{alert('Invalid value. Please enter a value "
"between 0 and 1000');}}}";
#endif
}
if (charger) {
content +=

View file

@ -2,6 +2,8 @@
#include <Preferences.h>
#include <ctime>
#include "../../../USER_SECRETS.h"
#include "../../battery/BATTERIES.h"
#include "../../battery/Battery.h"
#include "../../datalayer/datalayer.h"
#include "../../datalayer/datalayer_extended.h"
#include "../../lib/bblanchon-ArduinoJson/ArduinoJson.h"
@ -530,6 +532,32 @@ void init_webserver() {
}
});
for (const auto& cmd : battery_commands) {
auto route = String("/") + cmd.identifier;
server.on(
route.c_str(), HTTP_PUT,
[cmd](AsyncWebServerRequest* request) {
if (WEBSERVER_AUTH_REQUIRED && !request->authenticate(http_username, http_password)) {
return request->requestAuthentication();
}
},
nullptr,
[cmd](AsyncWebServerRequest* request, uint8_t* data, size_t len, size_t index, size_t total) {
String battIndex = "";
if (len > 0) {
battIndex += (char)data[0];
}
Battery* batt = battery;
if (battIndex == "1") {
batt = battery2;
}
if (batt) {
cmd.action(batt);
}
request->send(200, "text/plain", "Command performed.");
});
}
// Route for editing BATTERY_USE_VOLTAGE_LIMITS
server.on("/updateUseVoltageLimit", HTTP_GET, [](AsyncWebServerRequest* request) {
if (WEBSERVER_AUTH_REQUIRED && !request->authenticate(http_username, http_password))
@ -572,100 +600,6 @@ void init_webserver() {
}
});
// Route for clearing isolation faults on Tesla
server.on("/teslaClearIsolation", HTTP_GET, [](AsyncWebServerRequest* request) {
if (WEBSERVER_AUTH_REQUIRED && !request->authenticate(http_username, http_password)) {
return request->requestAuthentication();
}
datalayer.battery.settings.user_requests_tesla_isolation_clear = true;
request->send(200, "text/plain", "Updated successfully");
});
// Route for resetting BMS on Tesla
server.on("/teslaResetBMS", HTTP_GET, [](AsyncWebServerRequest* request) {
if (WEBSERVER_AUTH_REQUIRED && !request->authenticate(http_username, http_password)) {
return request->requestAuthentication();
}
datalayer.battery.settings.user_requests_tesla_bms_reset = true;
request->send(200, "text/plain", "Updated successfully");
});
// Route for triggering NVROL reset on Zoe Gen2 batteries
server.on("/triggerNVROL", HTTP_GET, [](AsyncWebServerRequest* request) {
if (WEBSERVER_AUTH_REQUIRED && !request->authenticate(http_username, http_password)) {
return request->requestAuthentication();
}
datalayer_extended.zoePH2.UserRequestNVROLReset = true;
request->send(200, "text/plain", "Updated successfully");
});
// Route for closing BMW iX Contactors
server.on("/bmwIxCloseContactorRequest", HTTP_GET, [](AsyncWebServerRequest* request) {
if (WEBSERVER_AUTH_REQUIRED && !request->authenticate(http_username, http_password)) {
return request->requestAuthentication();
}
datalayer_extended.bmwix.UserRequestContactorClose = true;
request->send(200, "text/plain", "Updated successfully");
});
// Route for opening BMW iX Contactors
server.on("/bmwIxOpenContactorRequest", HTTP_GET, [](AsyncWebServerRequest* request) {
if (WEBSERVER_AUTH_REQUIRED && !request->authenticate(http_username, http_password)) {
return request->requestAuthentication();
}
datalayer_extended.bmwix.UserRequestContactorOpen = true;
request->send(200, "text/plain", "Updated successfully");
});
// Route for resetting SOH on Nissan LEAF batteries
server.on("/resetSOH", HTTP_GET, [](AsyncWebServerRequest* request) {
if (WEBSERVER_AUTH_REQUIRED && !request->authenticate(http_username, http_password)) {
return request->requestAuthentication();
}
datalayer_extended.nissanleaf.UserRequestSOHreset = true;
request->send(200, "text/plain", "Updated successfully");
});
// Route for resetting Crash data on BYD Atto3 batteries
server.on("/resetCrash", HTTP_GET, [](AsyncWebServerRequest* request) {
if (WEBSERVER_AUTH_REQUIRED && !request->authenticate(http_username, http_password)) {
return request->requestAuthentication();
}
datalayer_extended.bydAtto3.UserRequestCrashReset = true;
request->send(200, "text/plain", "Updated successfully");
});
// Route for erasing DTC on Volvo/Polestar batteries
server.on("/volvoEraseDTC", HTTP_GET, [](AsyncWebServerRequest* request) {
if (WEBSERVER_AUTH_REQUIRED && !request->authenticate(http_username, http_password)) {
return request->requestAuthentication();
}
datalayer_extended.VolvoPolestar.UserRequestDTCreset = true;
datalayer_extended.VolvoHybrid.UserRequestDTCreset = true;
request->send(200, "text/plain", "Updated successfully");
});
// Route for reading DTC on Volvo/Polestar batteries
server.on("/volvoReadDTC", HTTP_GET, [](AsyncWebServerRequest* request) {
if (WEBSERVER_AUTH_REQUIRED && !request->authenticate(http_username, http_password)) {
return request->requestAuthentication();
}
datalayer_extended.VolvoPolestar.UserRequestDTCreadout = true;
datalayer_extended.VolvoHybrid.UserRequestDTCreadout = true;
request->send(200, "text/plain", "Updated successfully");
});
// Route for performing ECU reset on Volvo/Polestar batteries
server.on("/volvoBECMecuReset", HTTP_GET, [](AsyncWebServerRequest* request) {
if (WEBSERVER_AUTH_REQUIRED && !request->authenticate(http_username, http_password)) {
return request->requestAuthentication();
}
datalayer_extended.VolvoPolestar.UserRequestBECMecuReset = true;
datalayer_extended.VolvoHybrid.UserRequestBECMecuReset = true;
request->send(200, "text/plain", "Updated successfully");
});
#ifdef TEST_FAKE_BATTERY
// Route for editing FakeBatteryVoltage
server.on("/updateFakeBatteryVoltage", HTTP_GET, [](AsyncWebServerRequest* request) {
if (WEBSERVER_AUTH_REQUIRED && !request->authenticate(http_username, http_password))
@ -677,13 +611,10 @@ void init_webserver() {
String value = request->getParam("value")->value();
float val = value.toFloat();
datalayer.battery.status.voltage_dV = val * 10;
battery->set_fake_voltage(val);
request->send(200, "text/plain", "Updated successfully");
});
#endif // TEST_FAKE_BATTERY
#ifdef TESLA_MODEL_3Y_BATTERY
// Route for editing balancing enabled
server.on("/TeslaBalAct", HTTP_GET, [](AsyncWebServerRequest* request) {
@ -768,7 +699,6 @@ void init_webserver() {
request->send(400, "text/plain", "Bad Request");
}
});
#endif
if (charger) {
// Route for editing ChargerTargetV
@ -1022,9 +952,9 @@ String processor(const String& var) {
content += "</h4>";
content += "<h4 style='color: white;'>Battery protocol: ";
content += datalayer.system.info.battery_protocol;
#ifdef DOUBLE_BATTERY
if (battery2) {
content += " (Double battery)";
#endif // DOUBLE_BATTERY
}
if (datalayer.battery.info.chemistry == battery_chemistry_enum::LFP) {
content += " (LFP)";
}
@ -1045,14 +975,14 @@ String processor(const String& var) {
// Close the block
content += "</div>";
#ifdef DOUBLE_BATTERY
if (battery2) {
// Start a new block with a specific background color. Color changes depending on BMS status
content += "<div style='display: flex; width: 100%;'>";
content += "<div style='flex: 1; background-color: ";
#else
} else {
// Start a new block with a specific background color. Color changes depending on system status
content += "<div style='background-color: ";
#endif // DOUBLE_BATTERY
}
switch (led_get_color()) {
case led_color::GREEN:
@ -1176,7 +1106,7 @@ String processor(const String& var) {
}
content += "</h4>";
#ifdef MEB_BATTERY
if (battery->supports_real_BMS_status()) {
content += "<h4>Battery BMS status: ";
switch (datalayer.battery.status.real_bms_status) {
case BMS_ACTIVE:
@ -1196,7 +1126,7 @@ String processor(const String& var) {
break;
}
content += "</h4>";
#endif
}
if (datalayer.battery.status.current_dA == 0) {
content += "<h4>Battery idle</h4>";
@ -1286,7 +1216,7 @@ String processor(const String& var) {
// Close the block
content += "</div>";
#ifdef DOUBLE_BATTERY
if (battery2) {
content += "<div style='flex: 1; background-color: ";
switch (datalayer.battery.status.bms_status) {
case ACTIVE:
@ -1342,14 +1272,16 @@ String processor(const String& var) {
content += formatPowerValue("Remaining capacity", datalayer.battery2.status.remaining_capacity_Wh, "h", 1);
if (datalayer.system.settings.equipment_stop_active) {
content += formatPowerValue("Max discharge power", datalayer.battery2.status.max_discharge_power_W, "", 1, "red");
content +=
formatPowerValue("Max discharge power", datalayer.battery2.status.max_discharge_power_W, "", 1, "red");
content += formatPowerValue("Max charge power", datalayer.battery2.status.max_charge_power_W, "", 1, "red");
content += "<h4 style='color: red;'>Max discharge current: " + String(maxCurrentDischargeFloat, 1) + " A</h4>";
content += "<h4 style='color: red;'>Max charge current: " + String(maxCurrentChargeFloat, 1) + " A</h4>";
} else {
content += formatPowerValue("Max discharge power", datalayer.battery2.status.max_discharge_power_W, "", 1);
content += formatPowerValue("Max charge power", datalayer.battery2.status.max_charge_power_W, "", 1);
content += "<h4 style='color: white;'>Max discharge current: " + String(maxCurrentDischargeFloat, 1) + " A</h4>";
content +=
"<h4 style='color: white;'>Max discharge current: " + String(maxCurrentDischargeFloat, 1) + " A</h4>";
content += "<h4 style='color: white;'>Max charge current: " + String(maxCurrentChargeFloat, 1) + " A</h4>";
}
@ -1360,8 +1292,8 @@ String processor(const String& var) {
} else {
content += "<h4>Cell delta: " + String(cell_delta_mv) + " mV</h4>";
}
content +=
"<h4>Temperature min/max: " + String(tempMinFloat, 1) + " &deg;C / " + String(tempMaxFloat, 1) + " &deg;C</h4>";
content += "<h4>Temperature min/max: " + String(tempMinFloat, 1) + " &deg;C / " + String(tempMaxFloat, 1) +
" &deg;C</h4>";
if (datalayer.battery.status.bms_status == ACTIVE) {
content += "<h4>System status: OK </h4>";
} else if (datalayer.battery.status.bms_status == UPDATING) {
@ -1438,7 +1370,7 @@ String processor(const String& var) {
content += "</div>";
content += "</div>";
#endif // DOUBLE_BATTERY
}
if (charger) {
// Start a new block with orange background color