Merge branch 'main' into bugfix/SMA-tripower-enable-line

This commit is contained in:
Daniel Öster 2024-12-28 14:56:10 +03:00 committed by GitHub
commit ea2ea2e590
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
83 changed files with 9436 additions and 2023 deletions

View file

@ -10,6 +10,10 @@
#include "BMW-IX-BATTERY.h"
#endif
#ifdef BOLT_AMPERA_BATTERY
#include "BOLT-AMPERA-BATTERY.h"
#endif
#ifdef BYD_ATTO_3_BATTERY
#include "BYD-ATTO-3-BATTERY.h"
#endif
@ -43,6 +47,10 @@
#include "KIA-HYUNDAI-HYBRID-BATTERY.h"
#endif
#ifdef MEB_BATTERY
#include "MEB-BATTERY.h"
#endif
#ifdef MG_5_BATTERY
#include "MG-5-BATTERY.h"
#endif

View file

@ -785,7 +785,7 @@ void receive_can_battery2(CAN_frame rx_frame) {
datalayer.battery2.status.cell_voltages_mV[3] = ((rx_frame.data.u8[4] * 10) + 1800);
datalayer.battery2.status.cell_voltages_mV[4] = ((rx_frame.data.u8[5] * 10) + 1800);
datalayer.battery2.status.cell_voltages_mV[5] = ((rx_frame.data.u8[6] * 10) + 1800);
datalayer.battery2.status.cell_voltages_mV[5] = ((rx_frame.data.u8[7] * 10) + 1800);
datalayer.battery2.status.cell_voltages_mV[6] = ((rx_frame.data.u8[7] * 10) + 1800);
}
break;
case 0x430: //BMS [1s] - Charging status of high-voltage battery - 2

View file

@ -670,8 +670,8 @@ void receive_can_battery(CAN_frame rx_frame) {
if ((rx_frame.data.u8[6] << 8 | rx_frame.data.u8[7]) == 10000 ||
(rx_frame.data.u8[8] << 8 | rx_frame.data.u8[9]) == 10000) { //Qualifier Invalid Mode - Request Reboot
#ifdef DEBUG_VIA_USB
Serial.println("Cell MinMax Qualifier Invalid - Requesting BMS Reset");
#ifdef DEBUG_LOG
logging.println("Cell MinMax Qualifier Invalid - Requesting BMS Reset");
#endif
//set_event(EVENT_BATTERY_VALUE_UNAVAILABLE, (millis())); //Eventually need new Info level event type
transmit_can(&BMWiX_6F4_REQUEST_HARD_RESET, can_config.battery);

View file

@ -0,0 +1,789 @@
#include "../include.h"
#ifdef BOLT_AMPERA_BATTERY
#include "../datalayer/datalayer.h"
#include "../datalayer/datalayer_extended.h"
#include "../devboard/utils/events.h"
#include "BOLT-AMPERA-BATTERY.h"
/*
TODOs left for this implementation
- The battery has 3 CAN ports. One of them is responsible for the 7E4 polls, the other for the 7E7 polls
- Current implementation only seems to get the 7E7 polls working.
- Could on of the CAN channels be GMLAN?
- The values missing for a working implementation is:
- SOC% missing! This is absolutely mandatory to fix before starting to use this!
- Capacity (kWh) (can be estimated)
- Charge max power (can be estimated)
- Discharge max power (can be estimated)
- SOH% (low prio))
*/
/* Do not change code below unless you are sure what you are doing */
static unsigned long previousMillis20ms = 0; // will store last time a 20ms CAN Message was send
static unsigned long previousMillis100ms = 0; // will store last time a 100ms CAN Message was send
static unsigned long previousMillis120ms = 0; // will store last time a 120ms CAN Message was send
CAN_frame BOLT_778 = {.FD = false, // Unsure of what this message is, added only as example
.ext_ID = false,
.DLC = 7,
.ID = 0x778,
.data = {0x00, 0x31, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame BOLT_POLL_7E4 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x7E4,
.data = {0x03, 0x22, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame BOLT_ACK_7E4 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x7E4,
.data = {0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame BOLT_POLL_7E7 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x7E7,
.data = {0x03, 0x22, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame BOLT_ACK_7E7 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x7E7,
.data = {0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
// 7E4 Battery , reply 000007EC
// 7E7 Battery (Cell voltages), reply 000007EF
static uint16_t battery_cell_voltages[96]; //array with all the cellvoltages
static uint16_t battery_capacity_my17_18 = 0;
static uint16_t battery_capacity_my19plus = 0;
static uint16_t battery_SOC_display = 0;
static uint16_t battery_SOC_raw_highprec = 0;
static uint16_t battery_max_temperature = 0;
static uint16_t battery_min_temperature = 0;
static uint16_t battery_min_cell_voltage = 0;
static uint16_t battery_max_cell_voltage = 0;
static uint16_t battery_internal_resistance = 0;
static uint16_t battery_lowest_cell = 0;
static uint16_t battery_highest_cell = 0;
static uint16_t battery_voltage_polled = 0;
static uint16_t battery_voltage_periodic = 0;
static uint16_t battery_vehicle_isolation = 0;
static uint16_t battery_isolation_kohm = 0;
static uint16_t battery_HV_locked = 0;
static uint16_t battery_crash_event = 0;
static uint16_t battery_HVIL = 0;
static uint16_t battery_HVIL_status = 0;
static uint16_t battery_5V_ref = 0;
static int16_t battery_current_7E4 = 0;
static int16_t battery_module_temp_1 = 0;
static int16_t battery_module_temp_2 = 0;
static int16_t battery_module_temp_3 = 0;
static int16_t battery_module_temp_4 = 0;
static int16_t battery_module_temp_5 = 0;
static int16_t battery_module_temp_6 = 0;
static uint16_t battery_cell_average_voltage = 0;
static uint16_t battery_cell_average_voltage_2 = 0;
static uint16_t battery_terminal_voltage = 0;
static uint16_t battery_ignition_power_mode = 0;
static int16_t battery_current_7E7 = 0;
static int16_t temperature_1 = 0;
static int16_t temperature_2 = 0;
static int16_t temperature_3 = 0;
static int16_t temperature_4 = 0;
static int16_t temperature_5 = 0;
static int16_t temperature_6 = 0;
static int16_t temperature_highest = 0;
static int16_t temperature_lowest = 0;
static uint8_t mux = 0;
static uint8_t poll_index_7E4 = 0;
static uint16_t currentpoll_7E4 = POLL_7E4_CAPACITY_EST_GEN1;
static uint16_t reply_poll_7E4 = 0;
static uint8_t poll_index_7E7 = 0;
static uint16_t currentpoll_7E7 = POLL_7E7_CURRENT;
static uint16_t reply_poll_7E7 = 0;
const uint16_t poll_commands_7E4[19] = {POLL_7E4_CAPACITY_EST_GEN1,
POLL_7E4_CAPACITY_EST_GEN2,
POLL_7E4_SOC_DISPLAY,
POLL_7E4_SOC_RAW_HIGHPREC,
POLL_7E4_MAX_TEMPERATURE,
POLL_7E4_MIN_TEMPERATURE,
POLL_7E4_MIN_CELL_V,
POLL_7E4_MAX_CELL_V,
POLL_7E4_INTERNAL_RES,
POLL_7E4_LOWEST_CELL_NUMBER,
POLL_7E4_HIGHEST_CELL_NUMBER,
POLL_7E4_VOLTAGE,
POLL_7E4_VEHICLE_ISOLATION,
POLL_7E4_ISOLATION_TEST_KOHM,
POLL_7E4_HV_LOCKED_OUT,
POLL_7E4_CRASH_EVENT,
POLL_7E4_HVIL,
POLL_7E4_HVIL_STATUS,
POLL_7E4_CURRENT};
const uint16_t poll_commands_7E7[108] = {POLL_7E7_CURRENT, POLL_7E7_5V_REF,
POLL_7E7_MODULE_TEMP_1, POLL_7E7_MODULE_TEMP_2,
POLL_7E7_MODULE_TEMP_3, POLL_7E7_MODULE_TEMP_4,
POLL_7E7_MODULE_TEMP_5, POLL_7E7_MODULE_TEMP_6,
POLL_7E7_CELL_AVG_VOLTAGE, POLL_7E7_CELL_AVG_VOLTAGE_2,
POLL_7E7_TERMINAL_VOLTAGE, POLL_7E7_IGNITION_POWER_MODE,
POLL_7E7_CELL_01, POLL_7E7_CELL_02,
POLL_7E7_CELL_03, POLL_7E7_CELL_04,
POLL_7E7_CELL_05, POLL_7E7_CELL_06,
POLL_7E7_CELL_07, POLL_7E7_CELL_08,
POLL_7E7_CELL_09, POLL_7E7_CELL_10,
POLL_7E7_CELL_11, POLL_7E7_CELL_12,
POLL_7E7_CELL_13, POLL_7E7_CELL_14,
POLL_7E7_CELL_15, POLL_7E7_CELL_16,
POLL_7E7_CELL_17, POLL_7E7_CELL_18,
POLL_7E7_CELL_19, POLL_7E7_CELL_20,
POLL_7E7_CELL_21, POLL_7E7_CELL_22,
POLL_7E7_CELL_23, POLL_7E7_CELL_24,
POLL_7E7_CELL_25, POLL_7E7_CELL_26,
POLL_7E7_CELL_27, POLL_7E7_CELL_28,
POLL_7E7_CELL_29, POLL_7E7_CELL_30,
POLL_7E7_CELL_31, POLL_7E7_CELL_32,
POLL_7E7_CELL_33, POLL_7E7_CELL_34,
POLL_7E7_CELL_35, POLL_7E7_CELL_36,
POLL_7E7_CELL_37, POLL_7E7_CELL_38,
POLL_7E7_CELL_39, POLL_7E7_CELL_40,
POLL_7E7_CELL_41, POLL_7E7_CELL_42,
POLL_7E7_CELL_43, POLL_7E7_CELL_44,
POLL_7E7_CELL_45, POLL_7E7_CELL_46,
POLL_7E7_CELL_47, POLL_7E7_CELL_48,
POLL_7E7_CELL_49, POLL_7E7_CELL_50,
POLL_7E7_CELL_51, POLL_7E7_CELL_52,
POLL_7E7_CELL_53, POLL_7E7_CELL_54,
POLL_7E7_CELL_55, POLL_7E7_CELL_56,
POLL_7E7_CELL_57, POLL_7E7_CELL_58,
POLL_7E7_CELL_59, POLL_7E7_CELL_60,
POLL_7E7_CELL_61, POLL_7E7_CELL_62,
POLL_7E7_CELL_63, POLL_7E7_CELL_64,
POLL_7E7_CELL_65, POLL_7E7_CELL_66,
POLL_7E7_CELL_67, POLL_7E7_CELL_68,
POLL_7E7_CELL_69, POLL_7E7_CELL_70,
POLL_7E7_CELL_71, POLL_7E7_CELL_72,
POLL_7E7_CELL_73, POLL_7E7_CELL_74,
POLL_7E7_CELL_75, POLL_7E7_CELL_76,
POLL_7E7_CELL_77, POLL_7E7_CELL_78,
POLL_7E7_CELL_79, POLL_7E7_CELL_80,
POLL_7E7_CELL_81, POLL_7E7_CELL_82,
POLL_7E7_CELL_83, POLL_7E7_CELL_84,
POLL_7E7_CELL_85, POLL_7E7_CELL_86,
POLL_7E7_CELL_87, POLL_7E7_CELL_88,
POLL_7E7_CELL_89, POLL_7E7_CELL_90,
POLL_7E7_CELL_91, POLL_7E7_CELL_92,
POLL_7E7_CELL_93, POLL_7E7_CELL_94,
POLL_7E7_CELL_95, POLL_7E7_CELL_96};
void update_values_battery() { //This function maps all the values fetched via CAN to the battery datalayer
datalayer.battery.status.real_soc = battery_SOC_display;
//datalayer.battery.status.voltage_dV = battery_voltage * 0.52;
datalayer.battery.status.voltage_dV = (battery_voltage_periodic / 8) * 10;
datalayer.battery.status.current_dA = battery_current_7E7;
datalayer.battery.info.total_capacity_Wh;
datalayer.battery.status.remaining_capacity_Wh;
datalayer.battery.status.soh_pptt;
datalayer.battery.status.max_discharge_power_W;
datalayer.battery.status.max_charge_power_W;
// Store temperatures in an array
int16_t temperatures[] = {temperature_1, temperature_2, temperature_3, temperature_4, temperature_5, temperature_6};
// Initialize highest and lowest to the first element
temperature_highest = temperatures[0];
temperature_lowest = temperatures[0];
// Iterate through the array to find the highest and lowest values
for (uint8_t i = 1; i < 6; ++i) {
if (temperatures[i] > temperature_highest) {
temperature_highest = temperatures[i];
}
if (temperatures[i] < temperature_lowest) {
temperature_lowest = temperatures[i];
}
}
datalayer.battery.status.temperature_min_dC = temperature_lowest * 10;
datalayer.battery.status.temperature_max_dC = temperature_highest * 10;
//Map all cell voltages to the global array
memcpy(datalayer.battery.status.cell_voltages_mV, battery_cell_voltages, 96 * sizeof(uint16_t));
// Update webserver datalayer
datalayer_extended.boltampera.battery_5V_ref = battery_5V_ref;
datalayer_extended.boltampera.battery_module_temp_1 = battery_module_temp_1;
datalayer_extended.boltampera.battery_module_temp_2 = battery_module_temp_2;
datalayer_extended.boltampera.battery_module_temp_3 = battery_module_temp_3;
datalayer_extended.boltampera.battery_module_temp_4 = battery_module_temp_4;
datalayer_extended.boltampera.battery_module_temp_5 = battery_module_temp_5;
datalayer_extended.boltampera.battery_module_temp_6 = battery_module_temp_6;
datalayer_extended.boltampera.battery_cell_average_voltage = battery_cell_average_voltage;
datalayer_extended.boltampera.battery_cell_average_voltage_2 = battery_cell_average_voltage_2;
datalayer_extended.boltampera.battery_terminal_voltage = battery_terminal_voltage;
datalayer_extended.boltampera.battery_ignition_power_mode = battery_ignition_power_mode;
datalayer_extended.boltampera.battery_current_7E7 = battery_current_7E7;
datalayer_extended.boltampera.battery_capacity_my17_18 = battery_capacity_my17_18;
datalayer_extended.boltampera.battery_capacity_my19plus = battery_capacity_my19plus;
datalayer_extended.boltampera.battery_SOC_display = battery_SOC_display;
datalayer_extended.boltampera.battery_SOC_raw_highprec = battery_SOC_raw_highprec;
datalayer_extended.boltampera.battery_max_temperature = battery_max_temperature;
datalayer_extended.boltampera.battery_min_temperature = battery_min_temperature;
datalayer_extended.boltampera.battery_min_cell_voltage = battery_min_cell_voltage;
datalayer_extended.boltampera.battery_max_cell_voltage = battery_max_cell_voltage;
datalayer_extended.boltampera.battery_lowest_cell = battery_lowest_cell;
datalayer_extended.boltampera.battery_highest_cell = battery_highest_cell;
datalayer_extended.boltampera.battery_internal_resistance = battery_internal_resistance;
datalayer_extended.boltampera.battery_voltage_polled = battery_voltage_polled;
datalayer_extended.boltampera.battery_vehicle_isolation = battery_vehicle_isolation;
datalayer_extended.boltampera.battery_isolation_kohm = battery_isolation_kohm;
datalayer_extended.boltampera.battery_HV_locked = battery_HV_locked;
datalayer_extended.boltampera.battery_crash_event = battery_crash_event;
datalayer_extended.boltampera.battery_HVIL = battery_HVIL;
datalayer_extended.boltampera.battery_HVIL_status = battery_HVIL_status;
datalayer_extended.boltampera.battery_current_7E4 = battery_current_7E4;
}
void receive_can_battery(CAN_frame rx_frame) {
switch (rx_frame.ID) {
case 0x200:
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
mux = ((rx_frame.data.u8[6] & 0xE0) >> 5); //goes from 0-7
break;
case 0x202:
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
mux = ((rx_frame.data.u8[6] & 0xE0) >> 5); //goes from 0-7
break;
case 0x204:
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
mux = ((rx_frame.data.u8[6] & 0xE0) >> 5); //goes from 0-7
break;
case 0x206:
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
mux = ((rx_frame.data.u8[6] & 0xE0) >> 5); //goes from 0-7
break;
case 0x208:
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
mux = ((rx_frame.data.u8[6] & 0xE0) >> 5); //goes from 0-7
break;
case 0x20C:
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
break;
case 0x216:
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
break;
case 0x2C7:
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
battery_voltage_periodic = (rx_frame.data.u8[3] << 4) | (rx_frame.data.u8[4] >> 4);
break;
case 0x260:
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
break;
case 0x270:
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
break;
case 0x272:
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
break;
case 0x274:
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
break;
case 0x302:
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
temperature_1 = ((rx_frame.data.u8[1] / 2) - 40); //Module 1 Temperature
temperature_2 = ((rx_frame.data.u8[2] / 2) - 40); //Module 2 Temperature
temperature_3 = ((rx_frame.data.u8[3] / 2) - 40); //Module 3 Temperature
temperature_4 = ((rx_frame.data.u8[4] / 2) - 40); //Module 4 Temperature
temperature_5 = ((rx_frame.data.u8[5] / 2) - 40); //Module 5 Temperature
temperature_6 = ((rx_frame.data.u8[6] / 2) - 40); //Module 6 Temperature
break;
case 0x3E3:
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
break;
case 0x460:
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
break;
case 0x5EF:
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
break;
case 0x7EC: //When polling 7E4 BMS replies with 7EC ??
if (rx_frame.data.u8[0] == 0x10) { //"PID Header"
transmit_can(&BOLT_ACK_7E4, can_config.battery);
}
//Frame 2 & 3 contains reply
reply_poll_7E4 = (rx_frame.data.u8[2] << 8) | rx_frame.data.u8[3];
switch (reply_poll_7E4) {
case POLL_7E4_CAPACITY_EST_GEN1:
battery_capacity_my17_18 = ((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]);
break;
case POLL_7E4_CAPACITY_EST_GEN2:
battery_capacity_my19plus = ((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]);
break;
case POLL_7E4_SOC_DISPLAY:
battery_SOC_display = ((rx_frame.data.u8[4] * 100) / 255);
break;
case POLL_7E4_SOC_RAW_HIGHPREC:
battery_SOC_raw_highprec = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 100) / 65535);
break;
case POLL_7E4_MAX_TEMPERATURE:
battery_max_temperature = (rx_frame.data.u8[4] - 40);
break;
case POLL_7E4_MIN_TEMPERATURE:
battery_min_temperature = (rx_frame.data.u8[4] - 40);
break;
case POLL_7E4_MIN_CELL_V:
battery_min_cell_voltage = ((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) / 1666;
break;
case POLL_7E4_MAX_CELL_V:
battery_max_cell_voltage = ((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) / 1666;
break;
case POLL_7E4_INTERNAL_RES:
battery_internal_resistance = ((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) / 2;
break;
case POLL_7E4_LOWEST_CELL_NUMBER:
battery_lowest_cell = rx_frame.data.u8[4];
break;
case POLL_7E4_HIGHEST_CELL_NUMBER:
battery_highest_cell = rx_frame.data.u8[4];
break;
case POLL_7E4_VOLTAGE:
battery_voltage_polled = (((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 0.52);
break;
case POLL_7E4_VEHICLE_ISOLATION:
battery_vehicle_isolation = ((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]);
break;
case POLL_7E4_ISOLATION_TEST_KOHM:
battery_isolation_kohm = (rx_frame.data.u8[4] * 25);
break;
case POLL_7E4_HV_LOCKED_OUT:
battery_HV_locked = rx_frame.data.u8[4];
break;
case POLL_7E4_CRASH_EVENT:
battery_crash_event = rx_frame.data.u8[4];
break;
case POLL_7E4_HVIL:
battery_HVIL = rx_frame.data.u8[4];
break;
case POLL_7E4_HVIL_STATUS:
battery_HVIL_status = rx_frame.data.u8[4];
break;
case POLL_7E4_CURRENT:
battery_current_7E4 = (((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) / (-6.675));
break;
default:
break;
}
break;
case 0x7EF: //When polling 7E7 BMS replies with 7EF
if (rx_frame.data.u8[0] == 0x10) { //"PID Header"
transmit_can(&BOLT_ACK_7E7, can_config.battery);
}
//Frame 2 & 3 contains reply
reply_poll_7E7 = (rx_frame.data.u8[2] << 8) | rx_frame.data.u8[3];
switch (reply_poll_7E7) {
case POLL_7E7_CURRENT:
battery_current_7E7 = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
break;
case POLL_7E7_5V_REF:
battery_5V_ref = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5) / 65535);
break;
case POLL_7E7_MODULE_TEMP_1:
battery_module_temp_1 = (rx_frame.data.u8[4] - 40);
break;
case POLL_7E7_MODULE_TEMP_2:
battery_module_temp_2 = (rx_frame.data.u8[4] - 40);
break;
case POLL_7E7_MODULE_TEMP_3:
battery_module_temp_3 = (rx_frame.data.u8[4] - 40);
break;
case POLL_7E7_MODULE_TEMP_4:
battery_module_temp_4 = (rx_frame.data.u8[4] - 40);
break;
case POLL_7E7_MODULE_TEMP_5:
battery_module_temp_5 = (rx_frame.data.u8[4] - 40);
break;
case POLL_7E7_MODULE_TEMP_6:
battery_module_temp_6 = (rx_frame.data.u8[4] - 40);
break;
case POLL_7E7_CELL_AVG_VOLTAGE:
battery_cell_average_voltage = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
break;
case POLL_7E7_CELL_AVG_VOLTAGE_2:
battery_cell_average_voltage_2 = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) / 8000) * 1000);
break;
case POLL_7E7_TERMINAL_VOLTAGE:
battery_terminal_voltage = rx_frame.data.u8[4] * 2;
break;
case POLL_7E7_IGNITION_POWER_MODE:
battery_ignition_power_mode = rx_frame.data.u8[4];
break;
case POLL_7E7_CELL_01:
battery_cell_voltages[0] = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
break;
case POLL_7E7_CELL_02:
battery_cell_voltages[1] = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
break;
case POLL_7E7_CELL_03:
battery_cell_voltages[2] = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
break;
case POLL_7E7_CELL_04:
battery_cell_voltages[3] = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
break;
case POLL_7E7_CELL_05:
battery_cell_voltages[4] = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
break;
case POLL_7E7_CELL_06:
battery_cell_voltages[5] = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
break;
case POLL_7E7_CELL_07:
battery_cell_voltages[6] = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
break;
case POLL_7E7_CELL_08:
battery_cell_voltages[7] = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
break;
case POLL_7E7_CELL_09:
battery_cell_voltages[8] = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
break;
case POLL_7E7_CELL_10:
battery_cell_voltages[9] = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
break;
case POLL_7E7_CELL_11:
battery_cell_voltages[10] = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
break;
case POLL_7E7_CELL_12:
battery_cell_voltages[11] = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
break;
case POLL_7E7_CELL_13:
battery_cell_voltages[12] = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
break;
case POLL_7E7_CELL_14:
battery_cell_voltages[13] = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
break;
case POLL_7E7_CELL_15:
battery_cell_voltages[14] = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
break;
case POLL_7E7_CELL_16:
battery_cell_voltages[15] = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
break;
case POLL_7E7_CELL_17:
battery_cell_voltages[16] = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
break;
case POLL_7E7_CELL_18:
battery_cell_voltages[17] = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
break;
case POLL_7E7_CELL_19:
battery_cell_voltages[18] = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
break;
case POLL_7E7_CELL_20:
battery_cell_voltages[19] = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
break;
case POLL_7E7_CELL_21:
battery_cell_voltages[20] = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
break;
case POLL_7E7_CELL_22:
battery_cell_voltages[21] = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
break;
case POLL_7E7_CELL_23:
battery_cell_voltages[22] = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
break;
case POLL_7E7_CELL_24:
battery_cell_voltages[23] = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
break;
case POLL_7E7_CELL_25:
battery_cell_voltages[24] = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
break;
case POLL_7E7_CELL_26:
battery_cell_voltages[25] = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
break;
case POLL_7E7_CELL_27:
battery_cell_voltages[26] = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
break;
case POLL_7E7_CELL_28:
battery_cell_voltages[27] = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
break;
case POLL_7E7_CELL_29:
battery_cell_voltages[28] = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
break;
case POLL_7E7_CELL_30:
battery_cell_voltages[29] = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
break;
case POLL_7E7_CELL_31:
battery_cell_voltages[30] = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
break;
case POLL_7E7_CELL_32:
battery_cell_voltages[31] = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
break;
case POLL_7E7_CELL_33:
battery_cell_voltages[32] = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
break;
case POLL_7E7_CELL_34:
battery_cell_voltages[33] = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
break;
case POLL_7E7_CELL_35:
battery_cell_voltages[34] = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
break;
case POLL_7E7_CELL_36:
battery_cell_voltages[35] = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
break;
case POLL_7E7_CELL_37:
battery_cell_voltages[36] = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
break;
case POLL_7E7_CELL_38:
battery_cell_voltages[37] = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
break;
case POLL_7E7_CELL_39:
battery_cell_voltages[38] = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
break;
case POLL_7E7_CELL_40:
battery_cell_voltages[39] = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
break;
case POLL_7E7_CELL_41:
battery_cell_voltages[40] = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
break;
case POLL_7E7_CELL_42:
battery_cell_voltages[41] = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
break;
case POLL_7E7_CELL_43:
battery_cell_voltages[42] = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
break;
case POLL_7E7_CELL_44:
battery_cell_voltages[43] = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
break;
case POLL_7E7_CELL_45:
battery_cell_voltages[44] = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
break;
case POLL_7E7_CELL_46:
battery_cell_voltages[45] = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
break;
case POLL_7E7_CELL_47:
battery_cell_voltages[46] = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
break;
case POLL_7E7_CELL_48:
battery_cell_voltages[47] = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
break;
case POLL_7E7_CELL_49:
battery_cell_voltages[48] = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
break;
case POLL_7E7_CELL_50:
battery_cell_voltages[49] = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
break;
case POLL_7E7_CELL_51:
battery_cell_voltages[50] = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
break;
case POLL_7E7_CELL_52:
battery_cell_voltages[51] = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
break;
case POLL_7E7_CELL_53:
battery_cell_voltages[52] = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
break;
case POLL_7E7_CELL_54:
battery_cell_voltages[53] = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
break;
case POLL_7E7_CELL_55:
battery_cell_voltages[54] = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
break;
case POLL_7E7_CELL_56:
battery_cell_voltages[55] = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
break;
case POLL_7E7_CELL_57:
battery_cell_voltages[56] = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
break;
case POLL_7E7_CELL_58:
battery_cell_voltages[57] = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
break;
case POLL_7E7_CELL_59:
battery_cell_voltages[58] = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
break;
case POLL_7E7_CELL_60:
battery_cell_voltages[59] = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
break;
case POLL_7E7_CELL_61:
battery_cell_voltages[60] = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
break;
case POLL_7E7_CELL_62:
battery_cell_voltages[61] = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
break;
case POLL_7E7_CELL_63:
battery_cell_voltages[62] = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
break;
case POLL_7E7_CELL_64:
battery_cell_voltages[63] = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
break;
case POLL_7E7_CELL_65:
battery_cell_voltages[64] = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
break;
case POLL_7E7_CELL_66:
battery_cell_voltages[65] = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
break;
case POLL_7E7_CELL_67:
battery_cell_voltages[66] = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
break;
case POLL_7E7_CELL_68:
battery_cell_voltages[67] = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
break;
case POLL_7E7_CELL_69:
battery_cell_voltages[68] = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
break;
case POLL_7E7_CELL_70:
battery_cell_voltages[69] = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
break;
case POLL_7E7_CELL_71:
battery_cell_voltages[70] = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
break;
case POLL_7E7_CELL_72:
battery_cell_voltages[71] = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
break;
case POLL_7E7_CELL_73:
battery_cell_voltages[72] = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
break;
case POLL_7E7_CELL_74:
battery_cell_voltages[73] = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
break;
case POLL_7E7_CELL_75:
battery_cell_voltages[74] = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
break;
case POLL_7E7_CELL_76:
battery_cell_voltages[75] = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
break;
case POLL_7E7_CELL_77:
battery_cell_voltages[76] = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
break;
case POLL_7E7_CELL_78:
battery_cell_voltages[77] = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
break;
case POLL_7E7_CELL_79:
battery_cell_voltages[78] = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
break;
case POLL_7E7_CELL_80:
battery_cell_voltages[79] = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
break;
case POLL_7E7_CELL_81:
battery_cell_voltages[80] = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
break;
case POLL_7E7_CELL_82:
battery_cell_voltages[81] = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
break;
case POLL_7E7_CELL_83:
battery_cell_voltages[82] = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
break;
case POLL_7E7_CELL_84:
battery_cell_voltages[83] = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
break;
case POLL_7E7_CELL_85:
battery_cell_voltages[84] = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
break;
case POLL_7E7_CELL_86:
battery_cell_voltages[85] = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
break;
case POLL_7E7_CELL_87:
battery_cell_voltages[86] = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
break;
case POLL_7E7_CELL_88:
battery_cell_voltages[87] = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
break;
case POLL_7E7_CELL_89:
battery_cell_voltages[88] = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
break;
case POLL_7E7_CELL_90:
battery_cell_voltages[89] = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
break;
case POLL_7E7_CELL_91:
battery_cell_voltages[90] = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
break;
case POLL_7E7_CELL_92:
battery_cell_voltages[91] = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
break;
case POLL_7E7_CELL_93:
battery_cell_voltages[92] = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
break;
case POLL_7E7_CELL_94:
battery_cell_voltages[93] = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
break;
case POLL_7E7_CELL_95:
battery_cell_voltages[94] = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
break;
case POLL_7E7_CELL_96:
battery_cell_voltages[95] = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
break;
default:
break;
}
default:
break;
}
}
void send_can_battery() {
unsigned long currentMillis = millis();
//Send 20ms message
if (currentMillis - previousMillis20ms >= INTERVAL_20_MS) {
// Check if sending of CAN messages has been delayed too much.
if ((currentMillis - previousMillis20ms >= INTERVAL_20_MS_DELAYED) && (currentMillis > BOOTUP_TIME)) {
set_event(EVENT_CAN_OVERRUN, (currentMillis - previousMillis20ms));
} else {
clear_event(EVENT_CAN_OVERRUN);
}
previousMillis20ms = currentMillis;
transmit_can(&BOLT_778, can_config.battery);
}
//Send 100ms message
if (currentMillis - previousMillis100ms >= INTERVAL_100_MS) {
previousMillis100ms = currentMillis;
// Update current poll from the 7E7 array
currentpoll_7E7 = poll_commands_7E7[poll_index_7E7];
poll_index_7E7 = (poll_index_7E7 + 1) % 108;
BOLT_POLL_7E7.data.u8[2] = (uint8_t)((currentpoll_7E7 & 0xFF00) >> 8);
BOLT_POLL_7E7.data.u8[3] = (uint8_t)(currentpoll_7E7 & 0x00FF);
transmit_can(&BOLT_POLL_7E7, can_config.battery);
}
//Send 120ms message
if (currentMillis - previousMillis120ms >= 120) {
previousMillis120ms = currentMillis;
// Update current poll from the 7E4 array
currentpoll_7E4 = poll_commands_7E4[poll_index_7E4];
poll_index_7E4 = (poll_index_7E4 + 1) % 19;
BOLT_POLL_7E4.data.u8[2] = (uint8_t)((currentpoll_7E4 & 0xFF00) >> 8);
BOLT_POLL_7E4.data.u8[3] = (uint8_t)(currentpoll_7E4 & 0x00FF);
transmit_can(&BOLT_POLL_7E4, can_config.battery);
}
}
void setup_battery(void) { // Performs one time setup at startup
strncpy(datalayer.system.info.battery_protocol, "Chevrolet Bolt EV/Opel Ampera-e", 63);
datalayer.system.info.battery_protocol[63] = '\0';
datalayer.battery.info.number_of_cells = 96;
datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_DV;
datalayer.battery.info.min_design_voltage_dV = MIN_PACK_VOLTAGE_DV;
datalayer.battery.info.max_cell_voltage_mV = MAX_CELL_VOLTAGE_MV;
datalayer.battery.info.min_cell_voltage_mV = MIN_CELL_VOLTAGE_MV;
datalayer.battery.info.max_cell_voltage_deviation_mV = MAX_CELL_DEVIATION_MV;
datalayer.system.status.battery_allows_contactor_closing = true;
}
#endif

View file

@ -0,0 +1,146 @@
#ifndef BOLT_AMPERA_BATTERY_H
#define BOLT_AMPERA_BATTERY_H
#include <Arduino.h>
#include "../include.h"
#define BATTERY_SELECTED
#define MAX_PACK_VOLTAGE_DV 4150 //5000 = 500.0V
#define MIN_PACK_VOLTAGE_DV 2500
#define MAX_CELL_DEVIATION_MV 500
#define MAX_CELL_VOLTAGE_MV 4250 //Battery is put into emergency stop if one cell goes over this value
#define MIN_CELL_VOLTAGE_MV 2700 //Battery is put into emergency stop if one cell goes below this value
#define POLL_7E4_CAPACITY_EST_GEN1 0x41A3
#define POLL_7E4_CAPACITY_EST_GEN2 0x45F9
#define POLL_7E4_SOC_DISPLAY 0x8334
#define POLL_7E4_SOC_RAW_HIGHPREC 0x43AF
#define POLL_7E4_MAX_TEMPERATURE 0x4349
#define POLL_7E4_MIN_TEMPERATURE 0x434A
#define POLL_7E4_MIN_CELL_V 0x4329
#define POLL_7E4_MAX_CELL_V 0x432B
#define POLL_7E4_INTERNAL_RES 0x40E9
#define POLL_7E4_LOWEST_CELL_NUMBER 0x433B
#define POLL_7E4_HIGHEST_CELL_NUMBER 0x433C
#define POLL_7E4_VOLTAGE 0x432D
#define POLL_7E4_VEHICLE_ISOLATION 0x41EC
#define POLL_7E4_ISOLATION_TEST_KOHM 0x43A6
#define POLL_7E4_HV_LOCKED_OUT 0x44F8
#define POLL_7E4_CRASH_EVENT 0x4522
#define POLL_7E4_HVIL 0x4310
#define POLL_7E4_HVIL_STATUS 0x4311
#define POLL_7E4_CURRENT 0x4356
#define POLL_7E7_CURRENT 0x40D4
#define POLL_7E7_5V_REF 0x40D3
#define POLL_7E7_MODULE_TEMP_1 0x40D7
#define POLL_7E7_MODULE_TEMP_2 0x40D9
#define POLL_7E7_MODULE_TEMP_3 0x40DB
#define POLL_7E7_MODULE_TEMP_4 0x40DD
#define POLL_7E7_MODULE_TEMP_5 0x40DF
#define POLL_7E7_MODULE_TEMP_6 0x40E1
#define POLL_7E7_CELL_AVG_VOLTAGE 0xC218
#define POLL_7E7_CELL_AVG_VOLTAGE_2 0x44B9
#define POLL_7E7_TERMINAL_VOLTAGE 0x82A3
#define POLL_7E7_IGNITION_POWER_MODE 0x8002
#define POLL_7E7_CELL_01 0x4181
#define POLL_7E7_CELL_02 0x4182
#define POLL_7E7_CELL_03 0x4183
#define POLL_7E7_CELL_04 0x4184
#define POLL_7E7_CELL_05 0x4185
#define POLL_7E7_CELL_06 0x4186
#define POLL_7E7_CELL_07 0x4187
#define POLL_7E7_CELL_08 0x4188
#define POLL_7E7_CELL_09 0x4189
#define POLL_7E7_CELL_10 0x418A
#define POLL_7E7_CELL_11 0x418B
#define POLL_7E7_CELL_12 0x418C
#define POLL_7E7_CELL_13 0x418D
#define POLL_7E7_CELL_14 0x418E
#define POLL_7E7_CELL_15 0x418F
#define POLL_7E7_CELL_16 0x4190
#define POLL_7E7_CELL_17 0x4191
#define POLL_7E7_CELL_18 0x4192
#define POLL_7E7_CELL_19 0x4193
#define POLL_7E7_CELL_20 0x4194
#define POLL_7E7_CELL_21 0x4195
#define POLL_7E7_CELL_22 0x4196
#define POLL_7E7_CELL_23 0x4197
#define POLL_7E7_CELL_24 0x4198
#define POLL_7E7_CELL_25 0x4199
#define POLL_7E7_CELL_26 0x419A
#define POLL_7E7_CELL_27 0x419B
#define POLL_7E7_CELL_28 0x419C
#define POLL_7E7_CELL_29 0x419D
#define POLL_7E7_CELL_30 0x419E
#define POLL_7E7_CELL_31 0x419F
#define POLL_7E7_CELL_32 0x4200
#define POLL_7E7_CELL_33 0x4201
#define POLL_7E7_CELL_34 0x4202
#define POLL_7E7_CELL_35 0x4203
#define POLL_7E7_CELL_36 0x4204
#define POLL_7E7_CELL_37 0x4205
#define POLL_7E7_CELL_38 0x4206
#define POLL_7E7_CELL_39 0x4207
#define POLL_7E7_CELL_40 0x4208
#define POLL_7E7_CELL_41 0x4209
#define POLL_7E7_CELL_42 0x420A
#define POLL_7E7_CELL_43 0x420B
#define POLL_7E7_CELL_44 0x420C
#define POLL_7E7_CELL_45 0x420D
#define POLL_7E7_CELL_46 0x420E
#define POLL_7E7_CELL_47 0x420F
#define POLL_7E7_CELL_48 0x4210
#define POLL_7E7_CELL_49 0x4211
#define POLL_7E7_CELL_50 0x4212
#define POLL_7E7_CELL_51 0x4213
#define POLL_7E7_CELL_52 0x4214
#define POLL_7E7_CELL_53 0x4215
#define POLL_7E7_CELL_54 0x4216
#define POLL_7E7_CELL_55 0x4217
#define POLL_7E7_CELL_56 0x4218
#define POLL_7E7_CELL_57 0x4219
#define POLL_7E7_CELL_58 0x421A
#define POLL_7E7_CELL_59 0x421B
#define POLL_7E7_CELL_60 0x421C
#define POLL_7E7_CELL_61 0x421D
#define POLL_7E7_CELL_62 0x421E
#define POLL_7E7_CELL_63 0x421F
#define POLL_7E7_CELL_64 0x4220
#define POLL_7E7_CELL_65 0x4221
#define POLL_7E7_CELL_66 0x4222
#define POLL_7E7_CELL_67 0x4223
#define POLL_7E7_CELL_68 0x4224
#define POLL_7E7_CELL_69 0x4225
#define POLL_7E7_CELL_70 0x4226
#define POLL_7E7_CELL_71 0x4227
#define POLL_7E7_CELL_72 0x4228
#define POLL_7E7_CELL_73 0x4229
#define POLL_7E7_CELL_74 0x422A
#define POLL_7E7_CELL_75 0x422B
#define POLL_7E7_CELL_76 0x422C
#define POLL_7E7_CELL_77 0x422D
#define POLL_7E7_CELL_78 0x422E
#define POLL_7E7_CELL_79 0x422F
#define POLL_7E7_CELL_80 0x4230
#define POLL_7E7_CELL_81 0x4231
#define POLL_7E7_CELL_82 0x4232
#define POLL_7E7_CELL_83 0x4233
#define POLL_7E7_CELL_84 0x4234
#define POLL_7E7_CELL_85 0x4235
#define POLL_7E7_CELL_86 0x4236
#define POLL_7E7_CELL_87 0x4237
#define POLL_7E7_CELL_88 0x4238
#define POLL_7E7_CELL_89 0x4239
#define POLL_7E7_CELL_90 0x423A
#define POLL_7E7_CELL_91 0x423B
#define POLL_7E7_CELL_92 0x423C
#define POLL_7E7_CELL_93 0x423D
#define POLL_7E7_CELL_94 0x423E
#define POLL_7E7_CELL_95 0x423F
#define POLL_7E7_CELL_96 0x4240
void setup_battery(void);
void transmit_can(CAN_frame* tx_frame, int interface);
#endif

View file

@ -118,7 +118,7 @@ void update_values_battery() { //This function maps all the values fetched via
datalayer.battery.status.real_soc = estimateSOC(datalayer.battery.status.voltage_dV);
SOC_method = ESTIMATED;
#else // Pack is not crashed, we can use periodically transmitted SOC
datalayer.battery.status.real_soc = battery_highprecision_SOC * 100;
datalayer.battery.status.real_soc = battery_highprecision_SOC * 10;
SOC_method = MEASURED;
#endif
@ -419,6 +419,9 @@ void setup_battery(void) { // Performs one time setup at startup
datalayer.battery.info.max_cell_voltage_mV = MAX_CELL_VOLTAGE_MV;
datalayer.battery.info.min_cell_voltage_mV = MIN_CELL_VOLTAGE_MV;
datalayer.battery.info.max_cell_voltage_deviation_mV = MAX_CELL_DEVIATION_MV;
//Due to the Datalayer having 370.0V as startup value, which is 10V lower than the Atto 3 min voltage 380.0V
//We now init the value to 380.1V to avoid false positive events.
datalayer.battery.status.voltage_dV = MIN_PACK_VOLTAGE_DV + 1;
#ifdef DOUBLE_BATTERY
datalayer.battery2.info.number_of_cells = 126;
datalayer.battery2.info.chemistry = battery_chemistry_enum::LFP;

View file

@ -197,13 +197,13 @@ inline void process_vehicle_charging_session(CAN_frame rx_frame) {
x102_chg_session.ChargingCurrentRequest = newChargingCurrentRequest;
x102_chg_session.TargetBatteryVoltage = newTargetBatteryVoltage;
#ifdef DEBUG_VIA_USB
#ifdef DEBUG_LOG
//Note on p131
uint8_t chargingrate = 0;
if (x100_chg_lim.ConstantOfChargingRateIndication > 0) {
chargingrate = x102_chg_session.StateOfCharge / x100_chg_lim.ConstantOfChargingRateIndication * 100;
Serial.print("Charge Rate (kW): ");
Serial.println(chargingrate);
logging.print("Charge Rate (kW): ");
logging.println(chargingrate);
}
#endif
@ -217,40 +217,40 @@ inline void process_vehicle_charging_session(CAN_frame rx_frame) {
*/
if ((CHADEMO_Status == CHADEMO_INIT && vehicle_permission) ||
(x102_chg_session.s.status.StatusVehicleChargingEnabled && !vehicle_permission)) {
#ifdef DEBUG_VIA_USB
Serial.println("Inconsistent charge/discharge state.");
#ifdef DEBUG_LOG
logging.println("Inconsistent charge/discharge state.");
#endif
CHADEMO_Status = CHADEMO_FAULT;
return;
}
if (x102_chg_session.f.fault.FaultBatteryOverVoltage) {
#ifdef DEBUG_VIA_USB
Serial.println("Vehicle indicates fault, battery over voltage.");
#ifdef DEBUG_LOG
logging.println("Vehicle indicates fault, battery over voltage.");
#endif
CHADEMO_Status = CHADEMO_STOP;
return;
}
if (x102_chg_session.f.fault.FaultBatteryUnderVoltage) {
#ifdef DEBUG_VIA_USB
Serial.println("Vehicle indicates fault, battery under voltage.");
#ifdef DEBUG_LOG
logging.println("Vehicle indicates fault, battery under voltage.");
#endif
CHADEMO_Status = CHADEMO_STOP;
return;
}
if (x102_chg_session.f.fault.FaultBatteryCurrentDeviation) {
#ifdef DEBUG_VIA_USB
Serial.println("Vehicle indicates fault, battery current deviation. Possible EVSE issue?");
#ifdef DEBUG_LOG
logging.println("Vehicle indicates fault, battery current deviation. Possible EVSE issue?");
#endif
CHADEMO_Status = CHADEMO_STOP;
return;
}
if (x102_chg_session.f.fault.FaultBatteryVoltageDeviation) {
#ifdef DEBUG_VIA_USB
Serial.println("Vehicle indicates fault, battery voltage deviation. Possible EVSE issue?");
#ifdef DEBUG_LOG
logging.println("Vehicle indicates fault, battery voltage deviation. Possible EVSE issue?");
#endif
CHADEMO_Status = CHADEMO_STOP;
return;
@ -264,8 +264,8 @@ inline void process_vehicle_charging_session(CAN_frame rx_frame) {
//FIXME condition nesting or more stanzas needed here for clear determination of cessation reason
if (CHADEMO_Status == CHADEMO_POWERFLOW && EVSE_mode == CHADEMO_CHARGE && !vehicle_permission) {
#ifdef DEBUG_VIA_USB
Serial.println("State of charge ceiling reached or charging interrupted, stop charging");
#ifdef DEBUG_LOG
logging.println("State of charge ceiling reached or charging interrupted, stop charging");
#endif
CHADEMO_Status = CHADEMO_STOP;
return;
@ -273,8 +273,8 @@ inline void process_vehicle_charging_session(CAN_frame rx_frame) {
if (vehicle_permission && CHADEMO_Status == CHADEMO_NEGOTIATE) {
CHADEMO_Status = CHADEMO_EV_ALLOWED;
#ifdef DEBUG_VIA_USB
Serial.println("STATE shift to CHADEMO_EV_ALLOWED in process_vehicle_charging_session()");
#ifdef DEBUG_LOG
logging.println("STATE shift to CHADEMO_EV_ALLOWED in process_vehicle_charging_session()");
#endif
return;
}
@ -284,22 +284,22 @@ inline void process_vehicle_charging_session(CAN_frame rx_frame) {
// consider relocating
if (vehicle_permission && CHADEMO_Status == CHADEMO_EVSE_PREPARE && priorTargetBatteryVoltage == 0 &&
newTargetBatteryVoltage > 0 && x102_chg_session.s.status.StatusVehicleChargingEnabled) {
#ifdef DEBUG_VIA_USB
Serial.println("STATE SHIFT to EVSE_START reached in process_vehicle_charging_session()");
#ifdef DEBUG_LOG
logging.println("STATE SHIFT to EVSE_START reached in process_vehicle_charging_session()");
#endif
CHADEMO_Status = CHADEMO_EVSE_START;
return;
}
if (vehicle_permission && evse_permission && CHADEMO_Status == CHADEMO_POWERFLOW) {
#ifdef DEBUG_VIA_USB
Serial.println("updating vehicle request in process_vehicle_charging_session()");
#ifdef DEBUG_LOG
logging.println("updating vehicle request in process_vehicle_charging_session()");
#endif
return;
}
#ifdef DEBUG_VIA_USB
Serial.println("UNHANDLED STATE IN process_vehicle_charging_session()");
#ifdef DEBUG_LOG
logging.println("UNHANDLED STATE IN process_vehicle_charging_session()");
#endif
return;
}
@ -312,20 +312,20 @@ inline void process_vehicle_charging_limits(CAN_frame rx_frame) {
x200_discharge_limits.MinimumBatteryDischargeLevel = rx_frame.data.u8[6];
x200_discharge_limits.MaxRemainingCapacityForCharging = rx_frame.data.u8[7];
#ifdef DEBUG_VIA_USB
#ifdef DEBUG_LOG
/* unsigned long currentMillis = millis();
if (currentMillis - previousMillis5000 >= INTERVAL_5_S) {
previousMillis5000 = currentMillis;
Serial.println("x200 Max remaining capacity for charging/discharging:");
logging.println("x200 Max remaining capacity for charging/discharging:");
// initially this is set to 0, which is represented as 0xFF
Serial.println(0xFF - x200_discharge_limits.MaxRemainingCapacityForCharging);
logging.println(0xFF - x200_discharge_limits.MaxRemainingCapacityForCharging);
}
*/
#endif
if (get_measured_voltage() <= x200_discharge_limits.MinimumDischargeVoltage && CHADEMO_Status > CHADEMO_NEGOTIATE) {
#ifdef DEBUG_VIA_USB
Serial.println("x200 minimum discharge voltage met or exceeded, stopping.");
#ifdef DEBUG_LOG
logging.println("x200 minimum discharge voltage met or exceeded, stopping.");
#endif
CHADEMO_Status = CHADEMO_STOP;
}
@ -341,13 +341,13 @@ inline void process_vehicle_discharge_estimate(CAN_frame rx_frame) {
x201_discharge_estimate.ApproxDischargeCompletionTime = ((rx_frame.data.u8[2] << 8) | rx_frame.data.u8[1]);
x201_discharge_estimate.AvailableVehicleEnergy = ((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[3]);
#ifdef DEBUG_VIA_USB
#ifdef DEBUG_LOG
if (currentMillis - previousMillis5000 >= INTERVAL_5_S) {
previousMillis5000 = currentMillis;
Serial.print("x201 availabile vehicle energy, completion time: ");
Serial.println(x201_discharge_estimate.AvailableVehicleEnergy);
Serial.print("x201 approx vehicle completion time: ");
Serial.println(x201_discharge_estimate.ApproxDischargeCompletionTime);
logging.print("x201 availabile vehicle energy, completion time: ");
logging.println(x201_discharge_estimate.AvailableVehicleEnergy);
logging.print("x201 approx vehicle completion time: ");
logging.println(x201_discharge_estimate.ApproxDischargeCompletionTime);
}
#endif
}
@ -369,17 +369,17 @@ inline void process_vehicle_vendor_ID(CAN_frame rx_frame) {
void receive_can_battery(CAN_frame rx_frame) {
#ifdef CH_CAN_DEBUG
Serial.print(millis()); // Example printout, time, ID, length, data: 7553 1DB 8 FF C0 B9 EA 0 0 2 5D
Serial.print(" ");
Serial.print(rx_frame.ID, HEX);
Serial.print(" ");
Serial.print(rx_frame.DLC);
Serial.print(" ");
logging.print(millis()); // Example printout, time, ID, length, data: 7553 1DB 8 FF C0 B9 EA 0 0 2 5D
logging.print(" ");
logging.print(rx_frame.ID, HEX);
logging.print(" ");
logging.print(rx_frame.DLC);
logging.print(" ");
for (int i = 0; i < rx_frame.DLC; ++i) {
Serial.print(rx_frame.data.u8[i], HEX);
Serial.print(" ");
logging.print(rx_frame.data.u8[i], HEX);
logging.print(" ");
}
Serial.println("");
logging.println("");
#endif
// CHADEMO coexists with a CAN-based shunt. Only process CHADEMO-specific IDs
@ -713,9 +713,9 @@ void send_can_battery() {
// TODO need an update_evse_dynamic_control(..) function above before we send 118
// 110.0.0
if (x102_chg_session.ControlProtocolNumberEV >= 0x03) { //Only send the following on Chademo 2.0 vehicles?
#ifdef DEBUG_VIA_USB
#ifdef DEBUG_LOG
//FIXME REMOVE
Serial.println("REMOVE: proto 2.0");
logging.println("REMOVE: proto 2.0");
#endif
transmit_can(&CHADEMO_118, can_config.battery);
}
@ -753,15 +753,15 @@ void handle_chademo_sequence() {
/* ------------------- State override conditions checks ------------------- */
/* ------------------------------------------------------------------------------ */
if (CHADEMO_Status >= CHADEMO_EV_ALLOWED && x102_chg_session.s.status.StatusVehicleShifterPosition) {
#ifdef DEBUG_VIA_USB
Serial.println("Vehicle is not parked, abort.");
#ifdef DEBUG_LOG
logging.println("Vehicle is not parked, abort.");
#endif
CHADEMO_Status = CHADEMO_STOP;
}
if (CHADEMO_Status >= CHADEMO_EV_ALLOWED && !vehicle_permission) {
#ifdef DEBUG_VIA_USB
Serial.println("Vehicle charge/discharge permission ended, stop.");
#ifdef DEBUG_LOG
logging.println("Vehicle charge/discharge permission ended, stop.");
#endif
CHADEMO_Status = CHADEMO_STOP;
}
@ -775,24 +775,24 @@ void handle_chademo_sequence() {
plug_inserted = digitalRead(CHADEMO_PIN_7);
if (!plug_inserted) {
#ifdef DEBUG_VIA_USB
#ifdef DEBUG_LOG
// Commented unless needed for debug
// Serial.println("CHADEMO plug is not inserted.");
// logging.println("CHADEMO plug is not inserted.");
#endif
return;
}
CHADEMO_Status = CHADEMO_CONNECTED;
#ifdef DEBUG_VIA_USB
Serial.println("CHADEMO plug is inserted. Provide EVSE power to vehicle to trigger initialization.");
#ifdef DEBUG_LOG
logging.println("CHADEMO plug is inserted. Provide EVSE power to vehicle to trigger initialization.");
#endif
break;
case CHADEMO_CONNECTED:
#ifdef DEBUG_VIA_USB
#ifdef DEBUG_LOG
// Commented unless needed for debug
//Serial.println("CHADEMO_CONNECTED State");
//logging.println("CHADEMO_CONNECTED State");
#endif
/* plug_inserted is .. essentially a volatile of sorts, so verify */
if (plug_inserted) {
@ -810,8 +810,8 @@ void handle_chademo_sequence() {
* with timers to have higher confidence of certain conditions hitting
* a steady state
*/
#ifdef DEBUG_VIA_USB
Serial.println("CHADEMO plug is not inserted, cannot connect d2 relay to begin initialization.");
#ifdef DEBUG_LOG
logging.println("CHADEMO plug is not inserted, cannot connect d2 relay to begin initialization.");
#endif
CHADEMO_Status = CHADEMO_IDLE;
}
@ -821,8 +821,8 @@ void handle_chademo_sequence() {
* Used for triggers/error handling elsewhere;
* State change to CHADEMO_NEGOTIATE occurs in receive_can_battery(..)
*/
#ifdef DEBUG_VIA_USB
// Serial.println("Awaiting initial vehicle CAN to trigger negotiation");
#ifdef DEBUG_LOG
// logging.println("Awaiting initial vehicle CAN to trigger negotiation");
#endif
evse_init();
break;
@ -830,16 +830,16 @@ void handle_chademo_sequence() {
/* Vehicle and EVSE dance */
//TODO if pin 4 / j goes high,
#ifdef DEBUG_VIA_USB
#ifdef DEBUG_LOG
// Commented unless needed for debug
// Serial.println("CHADEMO_NEGOTIATE State");
// logging.println("CHADEMO_NEGOTIATE State");
#endif
x109_evse_state.s.status.ChgDischStopControl = 1;
break;
case CHADEMO_EV_ALLOWED:
#ifdef DEBUG_VIA_USB
#ifdef DEBUG_LOG
// Commented unless needed for debug
Serial.println("CHADEMO_EV_ALLOWED State");
logging.println("CHADEMO_EV_ALLOWED State");
#endif
// If we are in this state, vehicle_permission was already set to true...but re-verify
// that pin 4 (j) reads high
@ -855,9 +855,9 @@ void handle_chademo_sequence() {
}
break;
case CHADEMO_EVSE_PREPARE:
#ifdef DEBUG_VIA_USB
#ifdef DEBUG_LOG
// Commented unless needed for debug
Serial.println("CHADEMO_EVSE_PREPARE State");
logging.println("CHADEMO_EVSE_PREPARE State");
#endif
/* TODO voltage check of output < 20v
* insulation test hypothetically happens here before triggering PIN 10 high
@ -878,7 +878,7 @@ void handle_chademo_sequence() {
digitalWrite(CHADEMO_PIN_10, HIGH);
evse_permission = true;
} else {
Serial.println("Insulation check measures > 20v ");
logging.println("Insulation check measures > 20v ");
}
// likely unnecessary but just to be sure. consider removal
@ -891,9 +891,9 @@ void handle_chademo_sequence() {
//state changes to CHADEMO_EVSE_START only upon receipt of charging session request
break;
case CHADEMO_EVSE_START:
#ifdef DEBUG_VIA_USB
#ifdef DEBUG_LOG
// Commented unless needed for debug
Serial.println("CHADEMO_EVSE_START State");
logging.println("CHADEMO_EVSE_START State");
#endif
datalayer.system.status.battery_allows_contactor_closing = true;
x109_evse_state.s.status.ChgDischStopControl = 1;
@ -901,8 +901,8 @@ void handle_chademo_sequence() {
CHADEMO_Status = CHADEMO_EVSE_CONTACTORS_ENABLED;
#ifdef DEBUG_VIA_USB
Serial.println("Initiating contactors");
#ifdef DEBUG_LOG
logging.println("Initiating contactors");
#endif
/* break rather than fall through because contactors are not instantaneous;
@ -911,17 +911,17 @@ void handle_chademo_sequence() {
break;
case CHADEMO_EVSE_CONTACTORS_ENABLED:
#ifdef DEBUG_VIA_USB
#ifdef DEBUG_LOG
// Commented unless needed for debug
Serial.println("CHADEMO_EVSE_CONTACTORS State");
logging.println("CHADEMO_EVSE_CONTACTORS State");
#endif
/* check whether contactors ready, because externally dependent upon inverter allow during discharge */
if (contactors_ready) {
#ifdef DEBUG_VIA_USB
Serial.println("Contactors ready");
Serial.print("Voltage: ");
Serial.println(get_measured_voltage());
#ifdef DEBUG_LOG
logging.println("Contactors ready");
logging.print("Voltage: ");
logging.println(get_measured_voltage());
#endif
/* transition to POWERFLOW state if discharge compatible on both sides */
if (x109_evse_state.discharge_compatible && x102_chg_session.s.status.StatusVehicleDischargeCompatible &&
@ -941,9 +941,9 @@ void handle_chademo_sequence() {
/* break or fall through ? TODO */
break;
case CHADEMO_POWERFLOW:
#ifdef DEBUG_VIA_USB
#ifdef DEBUG_LOG
// Commented unless needed for debug
Serial.println("CHADEMO_POWERFLOW State");
logging.println("CHADEMO_POWERFLOW State");
#endif
/* POWERFLOW for charging, discharging, and bidirectional */
/* Interpretation */
@ -961,8 +961,8 @@ void handle_chademo_sequence() {
}
if (get_measured_voltage() <= x200_discharge_limits.MinimumDischargeVoltage) {
#ifdef DEBUG_VIA_USB
Serial.println("x200 minimum discharge voltage met or exceeded, stopping.");
#ifdef DEBUG_LOG
logging.println("x200 minimum discharge voltage met or exceeded, stopping.");
#endif
CHADEMO_Status = CHADEMO_STOP;
}
@ -972,9 +972,9 @@ void handle_chademo_sequence() {
x109_evse_state.s.status.EVSE_status = 1;
break;
case CHADEMO_STOP:
#ifdef DEBUG_VIA_USB
#ifdef DEBUG_LOG
// Commented unless needed for debug
Serial.println("CHADEMO_STOP State");
logging.println("CHADEMO_STOP State");
#endif
/* back to CHADEMO_IDLE after teardown */
x109_evse_state.s.status.ChgDischStopControl = 1;
@ -1000,16 +1000,16 @@ void handle_chademo_sequence() {
break;
case CHADEMO_FAULT:
#ifdef DEBUG_VIA_USB
#ifdef DEBUG_LOG
// Commented unless needed for debug
Serial.println("CHADEMO_FAULT State");
logging.println("CHADEMO_FAULT State");
#endif
/* Once faulted, never departs CHADEMO_FAULT state unless device is power cycled as a safety measure */
x109_evse_state.s.status.EVSE_error = 1;
x109_evse_state.s.status.ChgDischError = 1;
x109_evse_state.s.status.ChgDischStopControl = 1;
#ifdef DEBUG_VIA_USB
Serial.println("CHADEMO fault encountered, tearing down to make safe");
#ifdef DEBUG_LOG
logging.println("CHADEMO fault encountered, tearing down to make safe");
#endif
digitalWrite(CHADEMO_PIN_10, LOW);
digitalWrite(CHADEMO_PIN_2, LOW);
@ -1020,8 +1020,8 @@ void handle_chademo_sequence() {
break;
default:
#ifdef DEBUG_VIA_USB
Serial.println("UNHANDLED CHADEMO_STATE, setting FAULT");
#ifdef DEBUG_LOG
logging.println("UNHANDLED CHADEMO_STATE, setting FAULT");
#endif
CHADEMO_Status = CHADEMO_FAULT;
break;

View file

@ -91,17 +91,17 @@ void ISA_handleFrame(CAN_frame* frame) {
case 0x510:
case 0x511:
Serial.print(millis()); // Example printout, time, ID, length, data: 7553 1DB 8 FF C0 B9 EA 0 0 2 5D
Serial.print(" ");
Serial.print(frame->ID, HEX);
Serial.print(" ");
Serial.print(frame->DLC);
Serial.print(" ");
logging.print(millis()); // Example printout, time, ID, length, data: 7553 1DB 8 FF C0 B9 EA 0 0 2 5D
logging.print(" ");
logging.print(frame->ID, HEX);
logging.print(" ");
logging.print(frame->DLC);
logging.print(" ");
for (int i = 0; i < frame->DLC; ++i) {
Serial.print(frame->data.u8[i], HEX);
Serial.print(" ");
logging.print(frame->data.u8[i], HEX);
logging.print(" ");
}
Serial.println("");
logging.println("");
break;
case 0x521:
@ -245,8 +245,8 @@ void ISA_initialize() {
ISA_STOP();
delay(500);
for (int i = 0; i < 8; i++) {
Serial.print("ISA Initialization ");
Serial.println(i);
logging.print("ISA Initialization ");
logging.println(i);
outframe.data.u8[0] = (0x20 + i);
outframe.data.u8[1] = 0x02;
@ -271,7 +271,7 @@ void ISA_initialize() {
}
void ISA_STOP() {
Serial.println("ISA STOP");
logging.println("ISA STOP");
outframe.data.u8[0] = 0x34;
outframe.data.u8[1] = 0x00;
@ -286,7 +286,7 @@ void ISA_STOP() {
}
void ISA_sendSTORE() {
Serial.println("ISA send STORE");
logging.println("ISA send STORE");
outframe.data.u8[0] = 0x32;
outframe.data.u8[1] = 0x00;
@ -301,7 +301,7 @@ void ISA_sendSTORE() {
}
void ISA_START() {
Serial.println("ISA START");
logging.println("ISA START");
outframe.data.u8[0] = 0x34;
outframe.data.u8[1] = 0x01;
@ -317,7 +317,7 @@ void ISA_START() {
void ISA_RESTART() {
//Has the effect of zeroing AH and KWH
Serial.println("ISA RESTART");
logging.println("ISA RESTART");
outframe.data.u8[0] = 0x3F;
outframe.data.u8[1] = 0x00;
@ -336,7 +336,7 @@ void ISA_deFAULT() {
ISA_STOP();
delay(500);
Serial.println("ISA RESTART to default");
logging.println("ISA RESTART to default");
outframe.data.u8[0] = 0x3D;
outframe.data.u8[1] = 0x00;
@ -358,7 +358,7 @@ void ISA_initCurrent() {
ISA_STOP();
delay(500);
Serial.println("ISA Initialization Current");
logging.println("ISA Initialization Current");
outframe.data.u8[0] = 0x21;
outframe.data.u8[1] = 0x02;
@ -382,8 +382,8 @@ void ISA_initCurrent() {
}
void ISA_getCONFIG(uint8_t i) {
Serial.print("ISA Get Config ");
Serial.println(i);
logging.print("ISA Get Config ");
logging.println(i);
outframe.data.u8[0] = (0x60 + i);
outframe.data.u8[1] = 0x00;
@ -398,8 +398,8 @@ void ISA_getCONFIG(uint8_t i) {
}
void ISA_getCAN_ID(uint8_t i) {
Serial.print("ISA Get CAN ID ");
Serial.println(i);
logging.print("ISA Get CAN ID ");
logging.println(i);
outframe.data.u8[0] = (0x50 + i);
if (i == 8)
@ -418,8 +418,8 @@ void ISA_getCAN_ID(uint8_t i) {
}
void ISA_getINFO(uint8_t i) {
Serial.print("ISA Get INFO ");
Serial.println(i, HEX);
logging.print("ISA Get INFO ");
logging.println(i, HEX);
outframe.data.u8[0] = (0x70 + i);
outframe.data.u8[1] = 0x00;

View file

@ -103,29 +103,29 @@ void update_values_battery() { //This function maps all the values fetched via
}
if (!BMU_Detected) {
#ifdef DEBUG_VIA_USB
Serial.println("BMU not detected, check wiring!");
#ifdef DEBUG_LOG
logging.println("BMU not detected, check wiring!");
#endif
}
#ifdef DEBUG_VIA_USB
Serial.println("Battery Values");
Serial.print("BMU SOC: ");
Serial.print(BMU_SOC);
Serial.print(" BMU Current: ");
Serial.print(BMU_Current);
Serial.print(" BMU Battery Voltage: ");
Serial.print(BMU_PackVoltage);
Serial.print(" BMU_Power: ");
Serial.print(BMU_Power);
Serial.print(" Cell max voltage: ");
Serial.print(max_volt_cel);
Serial.print(" Cell min voltage: ");
Serial.print(min_volt_cel);
Serial.print(" Cell max temp: ");
Serial.print(max_temp_cel);
Serial.print(" Cell min temp: ");
Serial.println(min_temp_cel);
#ifdef DEBUG_LOG
logging.println("Battery Values");
logging.print("BMU SOC: ");
logging.print(BMU_SOC);
logging.print(" BMU Current: ");
logging.print(BMU_Current);
logging.print(" BMU Battery Voltage: ");
logging.print(BMU_PackVoltage);
logging.print(" BMU_Power: ");
logging.print(BMU_Power);
logging.print(" Cell max voltage: ");
logging.print(max_volt_cel);
logging.print(" Cell min voltage: ");
logging.print(min_volt_cel);
logging.print(" Cell max temp: ");
logging.print(max_temp_cel);
logging.print(" Cell min temp: ");
logging.println(min_temp_cel);
#endif
}

View file

@ -57,9 +57,9 @@ CAN_frame ipace_keep_alive = {.FD = false,
.data = {0x9E, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}};*/
void print_units(char* header, int value, char* units) {
Serial.print(header);
Serial.print(value);
Serial.print(units);
logging.print(header);
logging.print(value);
logging.print(units);
}
void update_values_battery() {
@ -104,8 +104,8 @@ void update_values_battery() {
}
/*Finally print out values to serial if configured to do so*/
#ifdef DEBUG_VIA_USB
Serial.println("Values going to inverter");
#ifdef DEBUG_LOG
logging.println("Values going to inverter");
print_units("SOH%: ", (datalayer.battery.status.soh_pptt * 0.01), "% ");
print_units(", SOC%: ", (datalayer.battery.status.reported_soc * 0.01), "% ");
print_units(", Voltage: ", (datalayer.battery.status.voltage_dV * 0.1), "V ");
@ -115,7 +115,7 @@ void update_values_battery() {
print_units(", Min temp: ", (datalayer.battery.status.temperature_min_dC * 0.1), "°C ");
print_units(", Max cell voltage: ", datalayer.battery.status.cell_max_voltage_mV, "mV ");
print_units(", Min cell voltage: ", datalayer.battery.status.cell_min_voltage_mV, "mV ");
Serial.println("");
logging.println("");
#endif
}
@ -229,17 +229,17 @@ void receive_can_battery(CAN_frame rx_frame) {
}
// All CAN messages recieved will be logged via serial
Serial.print(millis()); // Example printout, time, ID, length, data: 7553 1DB 8 FF C0 B9 EA 0 0 2 5D
Serial.print(" ");
Serial.print(rx_frame.ID, HEX);
Serial.print(" ");
Serial.print(rx_frame.DLC);
Serial.print(" ");
logging.print(millis()); // Example printout, time, ID, length, data: 7553 1DB 8 FF C0 B9 EA 0 0 2 5D
logging.print(" ");
logging.print(rx_frame.ID, HEX);
logging.print(" ");
logging.print(rx_frame.DLC);
logging.print(" ");
for (int i = 0; i < rx_frame.DLC; ++i) {
Serial.print(rx_frame.data.u8[i], HEX);
Serial.print(" ");
logging.print(rx_frame.data.u8[i], HEX);
logging.print(" ");
}
Serial.println("");
logging.println("");
}
void send_can_battery() {

View file

@ -690,63 +690,63 @@ void update_values_battery() { //This function maps all the values fetched via
/* Safeties verified. Perform USB serial printout if configured to do so */
#ifdef DEBUG_VIA_USB
Serial.println(); //sepatator
Serial.println("Values from battery: ");
Serial.print("SOC BMS: ");
Serial.print((uint16_t)SOC_BMS / 10.0, 1);
Serial.print("% | SOC Display: ");
Serial.print((uint16_t)SOC_Display / 10.0, 1);
Serial.print("% | SOH ");
Serial.print((uint16_t)batterySOH / 10.0, 1);
Serial.println("%");
Serial.print((int16_t)batteryAmps / 10.0, 1);
Serial.print(" Amps | ");
Serial.print((uint16_t)batteryVoltage / 10.0, 1);
Serial.print(" Volts | ");
Serial.print((int16_t)datalayer.battery.status.active_power_W);
Serial.println(" Watts");
Serial.print("Allowed Charge ");
Serial.print((uint16_t)allowedChargePower * 10);
Serial.print(" W | Allowed Discharge ");
Serial.print((uint16_t)allowedDischargePower * 10);
Serial.println(" W");
Serial.print("MaxCellVolt ");
Serial.print(CellVoltMax_mV);
Serial.print(" mV No ");
Serial.print(CellVmaxNo);
Serial.print(" | MinCellVolt ");
Serial.print(CellVoltMin_mV);
Serial.print(" mV No ");
Serial.println(CellVminNo);
Serial.print("TempHi ");
Serial.print((int16_t)temperatureMax);
Serial.print("°C TempLo ");
Serial.print((int16_t)temperatureMin);
Serial.print("°C WaterInlet ");
Serial.print((int8_t)temperature_water_inlet);
Serial.print("°C PowerRelay ");
Serial.print((int8_t)powerRelayTemperature * 2);
Serial.println("°C");
Serial.print("Aux12volt: ");
Serial.print((int16_t)leadAcidBatteryVoltage / 10.0, 1);
Serial.println("V | ");
Serial.print("BmsManagementMode ");
Serial.print((uint8_t)batteryManagementMode, BIN);
#ifdef DEBUG_LOG
logging.println(); //sepatator
logging.println("Values from battery: ");
logging.print("SOC BMS: ");
logging.print((uint16_t)SOC_BMS / 10.0, 1);
logging.print("% | SOC Display: ");
logging.print((uint16_t)SOC_Display / 10.0, 1);
logging.print("% | SOH ");
logging.print((uint16_t)batterySOH / 10.0, 1);
logging.println("%");
logging.print((int16_t)batteryAmps / 10.0, 1);
logging.print(" Amps | ");
logging.print((uint16_t)batteryVoltage / 10.0, 1);
logging.print(" Volts | ");
logging.print((int16_t)datalayer.battery.status.active_power_W);
logging.println(" Watts");
logging.print("Allowed Charge ");
logging.print((uint16_t)allowedChargePower * 10);
logging.print(" W | Allowed Discharge ");
logging.print((uint16_t)allowedDischargePower * 10);
logging.println(" W");
logging.print("MaxCellVolt ");
logging.print(CellVoltMax_mV);
logging.print(" mV No ");
logging.print(CellVmaxNo);
logging.print(" | MinCellVolt ");
logging.print(CellVoltMin_mV);
logging.print(" mV No ");
logging.println(CellVminNo);
logging.print("TempHi ");
logging.print((int16_t)temperatureMax);
logging.print("°C TempLo ");
logging.print((int16_t)temperatureMin);
logging.print("°C WaterInlet ");
logging.print((int8_t)temperature_water_inlet);
logging.print("°C PowerRelay ");
logging.print((int8_t)powerRelayTemperature * 2);
logging.println("°C");
logging.print("Aux12volt: ");
logging.print((int16_t)leadAcidBatteryVoltage / 10.0, 1);
logging.println("V | ");
logging.print("BmsManagementMode ");
logging.print((uint8_t)batteryManagementMode, BIN);
if (bitRead((uint8_t)BMS_ign, 2) == 1) {
Serial.print(" | BmsIgnition ON");
logging.print(" | BmsIgnition ON");
} else {
Serial.print(" | BmsIgnition OFF");
logging.print(" | BmsIgnition OFF");
}
if (bitRead((uint8_t)batteryRelay, 0) == 1) {
Serial.print(" | PowerRelay ON");
logging.print(" | PowerRelay ON");
} else {
Serial.print(" | PowerRelay OFF");
logging.print(" | PowerRelay OFF");
}
Serial.print(" | Inverter ");
Serial.print(inverterVoltage);
Serial.println(" Volts");
logging.print(" | Inverter ");
logging.print(inverterVoltage);
logging.println(" Volts");
#endif
}
@ -808,7 +808,7 @@ void receive_can_battery(CAN_frame rx_frame) {
// print_canfd_frame(frame);
switch (rx_frame.data.u8[0]) {
case 0x10: //"PID Header"
// Serial.println ("Send ack");
// logging.println ("Send ack");
poll_data_pid = rx_frame.data.u8[4];
// if (rx_frame.data.u8[4] == poll_data_pid) {
transmit_can(&EGMP_7E4_ack, can_config.battery); //Send ack to BMS if the same frame is sent as polled

View file

@ -142,63 +142,63 @@ void update_values_battery() { //This function maps all the values fetched via
/* Safeties verified. Perform USB serial printout if configured to do so */
#ifdef DEBUG_VIA_USB
Serial.println(); //sepatator
Serial.println("Values from battery: ");
Serial.print("SOC BMS: ");
Serial.print((uint16_t)SOC_BMS / 10.0, 1);
Serial.print("% | SOC Display: ");
Serial.print((uint16_t)SOC_Display / 10.0, 1);
Serial.print("% | SOH ");
Serial.print((uint16_t)batterySOH / 10.0, 1);
Serial.println("%");
Serial.print((int16_t)batteryAmps / 10.0, 1);
Serial.print(" Amps | ");
Serial.print((uint16_t)batteryVoltage / 10.0, 1);
Serial.print(" Volts | ");
Serial.print((int16_t)datalayer.battery.status.active_power_W);
Serial.println(" Watts");
Serial.print("Allowed Charge ");
Serial.print((uint16_t)allowedChargePower * 10);
Serial.print(" W | Allowed Discharge ");
Serial.print((uint16_t)allowedDischargePower * 10);
Serial.println(" W");
Serial.print("MaxCellVolt ");
Serial.print(CellVoltMax_mV);
Serial.print(" mV No ");
Serial.print(CellVmaxNo);
Serial.print(" | MinCellVolt ");
Serial.print(CellVoltMin_mV);
Serial.print(" mV No ");
Serial.println(CellVminNo);
Serial.print("TempHi ");
Serial.print((int16_t)temperatureMax);
Serial.print("°C TempLo ");
Serial.print((int16_t)temperatureMin);
Serial.print("°C WaterInlet ");
Serial.print((int8_t)temperature_water_inlet);
Serial.print("°C PowerRelay ");
Serial.print((int8_t)powerRelayTemperature * 2);
Serial.println("°C");
Serial.print("Aux12volt: ");
Serial.print((int16_t)leadAcidBatteryVoltage / 10.0, 1);
Serial.println("V | ");
Serial.print("BmsManagementMode ");
Serial.print((uint8_t)batteryManagementMode, BIN);
#ifdef DEBUG_LOG
logging.println(); //sepatator
logging.println("Values from battery: ");
logging.print("SOC BMS: ");
logging.print((uint16_t)SOC_BMS / 10.0, 1);
logging.print("% | SOC Display: ");
logging.print((uint16_t)SOC_Display / 10.0, 1);
logging.print("% | SOH ");
logging.print((uint16_t)batterySOH / 10.0, 1);
logging.println("%");
logging.print((int16_t)batteryAmps / 10.0, 1);
logging.print(" Amps | ");
logging.print((uint16_t)batteryVoltage / 10.0, 1);
logging.print(" Volts | ");
logging.print((int16_t)datalayer.battery.status.active_power_W);
logging.println(" Watts");
logging.print("Allowed Charge ");
logging.print((uint16_t)allowedChargePower * 10);
logging.print(" W | Allowed Discharge ");
logging.print((uint16_t)allowedDischargePower * 10);
logging.println(" W");
logging.print("MaxCellVolt ");
logging.print(CellVoltMax_mV);
logging.print(" mV No ");
logging.print(CellVmaxNo);
logging.print(" | MinCellVolt ");
logging.print(CellVoltMin_mV);
logging.print(" mV No ");
logging.println(CellVminNo);
logging.print("TempHi ");
logging.print((int16_t)temperatureMax);
logging.print("°C TempLo ");
logging.print((int16_t)temperatureMin);
logging.print("°C WaterInlet ");
logging.print((int8_t)temperature_water_inlet);
logging.print("°C PowerRelay ");
logging.print((int8_t)powerRelayTemperature * 2);
logging.println("°C");
logging.print("Aux12volt: ");
logging.print((int16_t)leadAcidBatteryVoltage / 10.0, 1);
logging.println("V | ");
logging.print("BmsManagementMode ");
logging.print((uint8_t)batteryManagementMode, BIN);
if (bitRead((uint8_t)BMS_ign, 2) == 1) {
Serial.print(" | BmsIgnition ON");
logging.print(" | BmsIgnition ON");
} else {
Serial.print(" | BmsIgnition OFF");
logging.print(" | BmsIgnition OFF");
}
if (bitRead((uint8_t)batteryRelay, 0) == 1) {
Serial.print(" | PowerRelay ON");
logging.print(" | PowerRelay ON");
} else {
Serial.print(" | PowerRelay OFF");
logging.print(" | PowerRelay OFF");
}
Serial.print(" | Inverter ");
Serial.print(inverterVoltage);
Serial.println(" Volts");
logging.print(" | Inverter ");
logging.print(inverterVoltage);
logging.println(" Volts");
#endif
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,138 @@
#ifndef MEB_BATTERY_H
#define MEB_BATTERY_H
#include <Arduino.h>
#include "../include.h"
#define BATTERY_SELECTED
#define MAX_PACK_VOLTAGE_84S_DV 3528 //5000 = 500.0V
#define MIN_PACK_VOLTAGE_84S_DV 2520
#define MAX_PACK_VOLTAGE_96S_DV 4032
#define MIN_PACK_VOLTAGE_96S_DV 2880
#define MAX_PACK_VOLTAGE_108S_DV 4536
#define MIN_PACK_VOLTAGE_108S_DV 3240
#define MAX_CELL_DEVIATION_MV 150
#define MAX_CELL_VOLTAGE_MV 4250 //Battery is put into emergency stop if one cell goes over this value
#define MIN_CELL_VOLTAGE_MV 2700 //Battery is put into emergency stop if one cell goes below this value
#define PID_SOC 0x028C
#define PID_VOLTAGE 0x1E3B
#define PID_CURRENT 0x1E3D
#define PID_MAX_TEMP 0x1E0E
#define PID_MIN_TEMP 0x1E0F
#define PID_MAX_CHARGE_VOLTAGE 0x5171
#define PID_MIN_DISCHARGE_VOLTAGE 0x5170
#define PID_ALLOWED_CHARGE_POWER 0x1E1B
#define PID_ALLOWED_DISCHARGE_POWER 0x1E1C
#define PID_CELLVOLTAGE_CELL_1 0x1E40
#define PID_CELLVOLTAGE_CELL_2 0x1E41
#define PID_CELLVOLTAGE_CELL_3 0x1E42
#define PID_CELLVOLTAGE_CELL_4 0x1E43
#define PID_CELLVOLTAGE_CELL_5 0x1E44
#define PID_CELLVOLTAGE_CELL_6 0x1E45
#define PID_CELLVOLTAGE_CELL_7 0x1E46
#define PID_CELLVOLTAGE_CELL_8 0x1E47
#define PID_CELLVOLTAGE_CELL_9 0x1E48
#define PID_CELLVOLTAGE_CELL_10 0x1E49
#define PID_CELLVOLTAGE_CELL_11 0x1E4A
#define PID_CELLVOLTAGE_CELL_12 0x1E4B
#define PID_CELLVOLTAGE_CELL_13 0x1E4C
#define PID_CELLVOLTAGE_CELL_14 0x1E4D
#define PID_CELLVOLTAGE_CELL_15 0x1E4E
#define PID_CELLVOLTAGE_CELL_16 0x1E4F
#define PID_CELLVOLTAGE_CELL_17 0x1E50
#define PID_CELLVOLTAGE_CELL_18 0x1E51
#define PID_CELLVOLTAGE_CELL_19 0x1E52
#define PID_CELLVOLTAGE_CELL_20 0x1E53
#define PID_CELLVOLTAGE_CELL_21 0x1E54
#define PID_CELLVOLTAGE_CELL_22 0x1E55
#define PID_CELLVOLTAGE_CELL_23 0x1E56
#define PID_CELLVOLTAGE_CELL_24 0x1E57
#define PID_CELLVOLTAGE_CELL_25 0x1E58
#define PID_CELLVOLTAGE_CELL_26 0x1E59
#define PID_CELLVOLTAGE_CELL_27 0x1E5A
#define PID_CELLVOLTAGE_CELL_28 0x1E5B
#define PID_CELLVOLTAGE_CELL_29 0x1E5C
#define PID_CELLVOLTAGE_CELL_30 0x1E5D
#define PID_CELLVOLTAGE_CELL_31 0x1E5E
#define PID_CELLVOLTAGE_CELL_32 0x1E5F
#define PID_CELLVOLTAGE_CELL_33 0x1E60
#define PID_CELLVOLTAGE_CELL_34 0x1E61
#define PID_CELLVOLTAGE_CELL_35 0x1E62
#define PID_CELLVOLTAGE_CELL_36 0x1E63
#define PID_CELLVOLTAGE_CELL_37 0x1E64
#define PID_CELLVOLTAGE_CELL_38 0x1E65
#define PID_CELLVOLTAGE_CELL_39 0x1E66
#define PID_CELLVOLTAGE_CELL_40 0x1E67
#define PID_CELLVOLTAGE_CELL_41 0x1E68
#define PID_CELLVOLTAGE_CELL_42 0x1E69
#define PID_CELLVOLTAGE_CELL_43 0x1E6A
#define PID_CELLVOLTAGE_CELL_44 0x1E6B
#define PID_CELLVOLTAGE_CELL_45 0x1E6C
#define PID_CELLVOLTAGE_CELL_46 0x1E6D
#define PID_CELLVOLTAGE_CELL_47 0x1E6E
#define PID_CELLVOLTAGE_CELL_48 0x1E6F
#define PID_CELLVOLTAGE_CELL_49 0x1E70
#define PID_CELLVOLTAGE_CELL_50 0x1E71
#define PID_CELLVOLTAGE_CELL_51 0x1E72
#define PID_CELLVOLTAGE_CELL_52 0x1E73
#define PID_CELLVOLTAGE_CELL_53 0x1E74
#define PID_CELLVOLTAGE_CELL_54 0x1E75
#define PID_CELLVOLTAGE_CELL_55 0x1E76
#define PID_CELLVOLTAGE_CELL_56 0x1E77
#define PID_CELLVOLTAGE_CELL_57 0x1E78
#define PID_CELLVOLTAGE_CELL_58 0x1E79
#define PID_CELLVOLTAGE_CELL_59 0x1E7A
#define PID_CELLVOLTAGE_CELL_60 0x1E7B
#define PID_CELLVOLTAGE_CELL_61 0x1E7C
#define PID_CELLVOLTAGE_CELL_62 0x1E7D
#define PID_CELLVOLTAGE_CELL_63 0x1E7E
#define PID_CELLVOLTAGE_CELL_64 0x1E7F
#define PID_CELLVOLTAGE_CELL_65 0x1E80
#define PID_CELLVOLTAGE_CELL_66 0x1E81
#define PID_CELLVOLTAGE_CELL_67 0x1E82
#define PID_CELLVOLTAGE_CELL_68 0x1E83
#define PID_CELLVOLTAGE_CELL_69 0x1E84
#define PID_CELLVOLTAGE_CELL_70 0x1E85
#define PID_CELLVOLTAGE_CELL_71 0x1E86
#define PID_CELLVOLTAGE_CELL_72 0x1E87
#define PID_CELLVOLTAGE_CELL_73 0x1E88
#define PID_CELLVOLTAGE_CELL_74 0x1E89
#define PID_CELLVOLTAGE_CELL_75 0x1E8A
#define PID_CELLVOLTAGE_CELL_76 0x1E8B
#define PID_CELLVOLTAGE_CELL_77 0x1E8C
#define PID_CELLVOLTAGE_CELL_78 0x1E8D
#define PID_CELLVOLTAGE_CELL_79 0x1E8E
#define PID_CELLVOLTAGE_CELL_80 0x1E8F
#define PID_CELLVOLTAGE_CELL_81 0x1E90
#define PID_CELLVOLTAGE_CELL_82 0x1E91
#define PID_CELLVOLTAGE_CELL_83 0x1E92
#define PID_CELLVOLTAGE_CELL_84 0x1E93
#define PID_CELLVOLTAGE_CELL_85 0x1E94
#define PID_CELLVOLTAGE_CELL_86 0x1E95
#define PID_CELLVOLTAGE_CELL_87 0x1E96
#define PID_CELLVOLTAGE_CELL_88 0x1E97
#define PID_CELLVOLTAGE_CELL_89 0x1E98
#define PID_CELLVOLTAGE_CELL_90 0x1E99
#define PID_CELLVOLTAGE_CELL_91 0x1E9A
#define PID_CELLVOLTAGE_CELL_92 0x1E9B
#define PID_CELLVOLTAGE_CELL_93 0x1E9C
#define PID_CELLVOLTAGE_CELL_94 0x1E9D
#define PID_CELLVOLTAGE_CELL_95 0x1E9E
#define PID_CELLVOLTAGE_CELL_96 0x1E9F
#define PID_CELLVOLTAGE_CELL_97 0x1EA0
#define PID_CELLVOLTAGE_CELL_98 0x1EA1
#define PID_CELLVOLTAGE_CELL_99 0x1EA2
#define PID_CELLVOLTAGE_CELL_100 0x1EA3
#define PID_CELLVOLTAGE_CELL_101 0x1EA4
#define PID_CELLVOLTAGE_CELL_102 0x1EA5
#define PID_CELLVOLTAGE_CELL_103 0x1EA6
#define PID_CELLVOLTAGE_CELL_104 0x1EA7
#define PID_CELLVOLTAGE_CELL_105 0x1EA8
#define PID_CELLVOLTAGE_CELL_106 0x1EA9
#define PID_CELLVOLTAGE_CELL_107 0x1EAA
#define PID_CELLVOLTAGE_CELL_108 0x1EAB
void setup_battery(void);
void transmit_can(CAN_frame* tx_frame, int interface);
#endif

View file

@ -42,10 +42,6 @@ void update_values_battery() { //This function maps all the values fetched via
datalayer.battery.status.temperature_min_dC;
datalayer.battery.status.temperature_max_dC;
#ifdef DEBUG_VIA_USB
#endif
}
void receive_can_battery(CAN_frame rx_frame) {

View file

@ -38,11 +38,13 @@ CAN_frame LEAF_1D4 = {.FD = false,
.ID = 0x1D4,
.data = {0x6E, 0x6E, 0x00, 0x04, 0x07, 0x46, 0xE0, 0x44}};
// Active polling messages
uint8_t PIDgroups[] = {0x01, 0x02, 0x04, 0x83, 0x84, 0x90};
uint8_t PIDindex = 0;
CAN_frame LEAF_GROUP_REQUEST = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x79B,
.data = {2, 0x21, 1, 0, 0, 0, 0, 0}};
.data = {0x02, 0x21, PIDgroups[0], 0, 0, 0, 0, 0}};
CAN_frame LEAF_NEXT_LINE_REQUEST = {.FD = false,
.ext_ID = false,
.DLC = 8,
@ -107,7 +109,6 @@ static bool battery_Batt_Heater_Mail_Send_Request = false; //Stores info when a
// Nissan LEAF battery data from polled CAN messages
static uint8_t battery_request_idx = 0;
static uint8_t group_7bb = 0;
static uint8_t group = 1;
static bool stop_battery_query = true;
static uint8_t hold_off_with_polling_10seconds = 10;
static uint16_t battery_cell_voltages[97]; //array with all the cellvoltages
@ -124,7 +125,9 @@ static uint16_t battery_temp_raw_max = 0;
static uint16_t battery_temp_raw_min = 0;
static int16_t battery_temp_polled_max = 0;
static int16_t battery_temp_polled_min = 0;
static uint8_t BatterySerialNumber[15] = {0}; // Stores raw HEX values for ASCII chars
static uint8_t BatteryPartNumber[7] = {0}; // Stores raw HEX values for ASCII chars
static uint8_t BMSIDcode[8] = {0};
#ifdef DOUBLE_BATTERY
static uint8_t LEAF_battery2_Type = ZE0_BATTERY;
static bool battery2_can_alive = false;
@ -326,6 +329,9 @@ void update_values_battery() { /* This function maps all the values fetched via
}
// Update webserver datalayer
memcpy(datalayer_extended.nissanleaf.BatterySerialNumber, BatterySerialNumber, sizeof(BatterySerialNumber));
memcpy(datalayer_extended.nissanleaf.BatteryPartNumber, BatteryPartNumber, sizeof(BatteryPartNumber));
memcpy(datalayer_extended.nissanleaf.BMSIDcode, BMSIDcode, sizeof(BMSIDcode));
datalayer_extended.nissanleaf.LEAF_gen = LEAF_battery_Type;
datalayer_extended.nissanleaf.GIDS = battery_GIDS;
datalayer_extended.nissanleaf.ChargePowerLimit = battery_Charge_Power_Limit;
@ -598,20 +604,16 @@ void receive_can_battery2(CAN_frame rx_frame) {
hold_off_with_polling_10seconds = 10; //Polling is paused for 100s
break;
case 0x7BB:
//First check which group data we are getting
if (rx_frame.data.u8[0] == 0x10) { //First message of a group
battery2_group_7bb = rx_frame.data.u8[3];
if (battery2_group_7bb != 1 && battery2_group_7bb != 2 &&
battery2_group_7bb != 4) { //We are only interested in groups 1,2 and 4
break;
}
}
if (stop_battery_query) { //Leafspy/Service request is active, stop our own polling
break;
}
transmit_can(&LEAF_NEXT_LINE_REQUEST, can_config.battery_double);
//First check which group data we are getting
if (rx_frame.data.u8[0] == 0x10) { //First message of a group
battery2_group_7bb = rx_frame.data.u8[3];
transmit_can(&LEAF_NEXT_LINE_REQUEST, can_config.battery_double);
}
if (battery2_group_7bb == 1) //High precision SOC, Current, voltages etc.
{
@ -845,7 +847,7 @@ void receive_can_battery(CAN_frame rx_frame) {
break;
case 0x7BB:
// This section checks if we are doing a SOH reset towards BMS
// This section checks if we are doing a SOH reset towards BMS. If we do, all 7BB handling is halted
if (stateMachineClearSOH < 255) {
//Intercept the messages based on state machine
if (rx_frame.data.u8[0] == 0x06) { // Incoming challenge data!
@ -860,18 +862,16 @@ void receive_can_battery(CAN_frame rx_frame) {
break;
}
//First check which group data we are getting
if (rx_frame.data.u8[0] == 0x10) { //First message of a group
group_7bb = rx_frame.data.u8[3];
if (group_7bb != 1 && group_7bb != 2 && group_7bb != 4) { //We are only interested in groups 1,2 and 4
break;
}
}
if (stop_battery_query) { //Leafspy is active, stop our own polling
break;
}
transmit_can(&LEAF_NEXT_LINE_REQUEST, can_config.battery); //Request the next frame for the group
//First check which group data we are getting
if (rx_frame.data.u8[0] == 0x10) { //First message of a group
group_7bb = rx_frame.data.u8[3];
transmit_can(&LEAF_NEXT_LINE_REQUEST, can_config.battery); //Request the next frame for the group
}
if (group_7bb == 1) //High precision SOC, Current, voltages etc.
{
@ -991,6 +991,66 @@ void receive_can_battery(CAN_frame rx_frame) {
}
}
if (group_7bb == 0x83) //BatteryPartNumber
{
if (rx_frame.data.u8[0] == 0x10) { //First frame (101A6183334E4B32)
BatteryPartNumber[0] = rx_frame.data.u8[4];
BatteryPartNumber[1] = rx_frame.data.u8[5];
BatteryPartNumber[2] = rx_frame.data.u8[6];
BatteryPartNumber[3] = rx_frame.data.u8[7];
}
if (rx_frame.data.u8[0] == 0x21) { //Second frame (2141524205170000)
BatteryPartNumber[4] = rx_frame.data.u8[1];
BatteryPartNumber[5] = rx_frame.data.u8[2];
BatteryPartNumber[6] = rx_frame.data.u8[3];
}
if (rx_frame.data.u8[0] == 0x22) { //Third frame (2200000002101311)
}
if (rx_frame.data.u8[0] == 0x23) { //Fourth frame (23000000000080FF)
}
}
if (group_7bb == 0x84) { //BatterySerialNumber
if (rx_frame.data.u8[0] == 0x10) { //First frame (10 16 61 84 32 33 30 55)
BatterySerialNumber[0] = rx_frame.data.u8[7];
}
if (rx_frame.data.u8[0] == 0x21) { //Second frame (21 4B 31 31 39 32 45 30)
BatterySerialNumber[1] = rx_frame.data.u8[1];
BatterySerialNumber[2] = rx_frame.data.u8[2];
BatterySerialNumber[3] = rx_frame.data.u8[3];
BatterySerialNumber[4] = rx_frame.data.u8[4];
BatterySerialNumber[5] = rx_frame.data.u8[5];
BatterySerialNumber[6] = rx_frame.data.u8[6];
BatterySerialNumber[7] = rx_frame.data.u8[7];
}
if (rx_frame.data.u8[0] == 0x22) { //Third frame (22 30 31 34 38 32 20 A0)
BatterySerialNumber[8] = rx_frame.data.u8[1];
BatterySerialNumber[9] = rx_frame.data.u8[2];
BatterySerialNumber[10] = rx_frame.data.u8[3];
BatterySerialNumber[11] = rx_frame.data.u8[4];
BatterySerialNumber[12] = rx_frame.data.u8[5];
BatterySerialNumber[13] = rx_frame.data.u8[6];
BatterySerialNumber[14] = rx_frame.data.u8[7];
}
if (rx_frame.data.u8[0] == 0x23) { //Fourth frame (23 00 00 00 00 00 00 00)
}
}
if (group_7bb == 0x90) { //BMSIDcode
if (rx_frame.data.u8[0] == 0x10) { //First frame (100A619044434131)
BMSIDcode[0] = rx_frame.data.u8[4];
BMSIDcode[1] = rx_frame.data.u8[5];
BMSIDcode[2] = rx_frame.data.u8[6];
BMSIDcode[3] = rx_frame.data.u8[7];
}
if (rx_frame.data.u8[0] == 0x21) { //Second frame (2130303535FFFFFF)
BMSIDcode[4] = rx_frame.data.u8[1];
BMSIDcode[5] = rx_frame.data.u8[2];
BMSIDcode[6] = rx_frame.data.u8[3];
BMSIDcode[7] = rx_frame.data.u8[4];
}
}
break;
default:
break;
@ -1190,9 +1250,11 @@ void send_can_battery() {
//Every 10s, ask diagnostic data from the battery. Don't ask if someone is already polling on the bus (Leafspy?)
if (!stop_battery_query) {
group = (group == 1) ? 2 : (group == 2) ? 4 : 1;
// Cycle between group 1, 2, and 4 using ternary operation
LEAF_GROUP_REQUEST.data.u8[2] = group;
// Move to the next group
PIDindex = (PIDindex + 1) % 6; // 6 = amount of elements in the PIDgroups[]
LEAF_GROUP_REQUEST.data.u8[2] = PIDgroups[PIDindex];
transmit_can(&LEAF_GROUP_REQUEST, can_config.battery);
#ifdef DOUBLE_BATTERY
transmit_can(&LEAF_GROUP_REQUEST, can_config.battery_double);

View file

@ -103,36 +103,36 @@ void update_values_battery() { //This function maps all the values fetched via
datalayer.battery.status.cell_max_voltage_mV = LB_Cell_Max_Voltage;
#ifdef DEBUG_VIA_USB
Serial.println("Values going to inverter:");
Serial.print("SOH%: ");
Serial.print(datalayer.battery.status.soh_pptt);
Serial.print(", SOC% scaled: ");
Serial.print(datalayer.battery.status.reported_soc);
Serial.print(", Voltage: ");
Serial.print(datalayer.battery.status.voltage_dV);
Serial.print(", Max discharge power: ");
Serial.print(datalayer.battery.status.max_discharge_power_W);
Serial.print(", Max charge power: ");
Serial.print(datalayer.battery.status.max_charge_power_W);
Serial.print(", Max temp: ");
Serial.print(datalayer.battery.status.temperature_max_dC);
Serial.print(", Min temp: ");
Serial.print(datalayer.battery.status.temperature_min_dC);
Serial.print(", BMS Status (3=OK): ");
Serial.print(datalayer.battery.status.bms_status);
#ifdef DEBUG_LOG
logging.println("Values going to inverter:");
logging.print("SOH%: ");
logging.print(datalayer.battery.status.soh_pptt);
logging.print(", SOC% scaled: ");
logging.print(datalayer.battery.status.reported_soc);
logging.print(", Voltage: ");
logging.print(datalayer.battery.status.voltage_dV);
logging.print(", Max discharge power: ");
logging.print(datalayer.battery.status.max_discharge_power_W);
logging.print(", Max charge power: ");
logging.print(datalayer.battery.status.max_charge_power_W);
logging.print(", Max temp: ");
logging.print(datalayer.battery.status.temperature_max_dC);
logging.print(", Min temp: ");
logging.print(datalayer.battery.status.temperature_min_dC);
logging.print(", BMS Status (3=OK): ");
logging.print(datalayer.battery.status.bms_status);
Serial.println("Battery values: ");
Serial.print("Real SOC: ");
Serial.print(LB_SOC);
Serial.print(", Current: ");
Serial.print(LB_Current);
Serial.print(", kWh remain: ");
Serial.print(LB_kWh_Remaining);
Serial.print(", max mV: ");
Serial.print(LB_Cell_Max_Voltage);
Serial.print(", min mV: ");
Serial.print(LB_Cell_Min_Voltage);
logging.println("Battery values: ");
logging.print("Real SOC: ");
logging.print(LB_SOC);
logging.print(", Current: ");
logging.print(LB_Current);
logging.print(", kWh remain: ");
logging.print(LB_kWh_Remaining);
logging.print(", max mV: ");
logging.print(LB_Cell_Max_Voltage);
logging.print(", min mV: ");
logging.print(LB_Cell_Min_Voltage);
#endif
}

View file

@ -1,6 +1,5 @@
#include "../include.h"
#ifdef RENAULT_ZOE_GEN1_BATTERY
#include <algorithm> // For std::min and std::max
#include "../datalayer/datalayer.h"
#include "../devboard/utils/events.h"
#include "RENAULT-ZOE-GEN1-BATTERY.h"
@ -9,20 +8,24 @@
https://github.com/openvehicles/Open-Vehicle-Monitoring-System-3/blob/master/vehicle/OVMS.V3/components/vehicle_renaultzoe/src/vehicle_renaultzoe.cpp
The Zoe BMS apparently does not send total pack voltage, so we use the polled 96x cellvoltages summed up as total voltage
Still TODO:
- Find max discharge and max charge values (for now hardcoded to 5kW)
- Fix the missing cell96 issue (Only cells 1-95 is shown)
- Find current sensor value (OVMS code reads this from inverter, which we dont have)
- Figure out why SOH% is not read (low prio)
- Fix the missing cell96 issue (Only cells 1-95 is shown on cellmonitor page)
- Automatically detect if we are on 22 or 41kWh battery (Nice to have, requires log file from 22kWh battery)
/*
/* Do not change code below unless you are sure what you are doing */
static uint16_t LB_SOC = 50;
static uint16_t LB_Display_SOC = 50;
static uint16_t LB_SOH = 99;
static int16_t LB_Average_Temperature = 0;
static uint32_t LB_Charge_Power_W = 0;
static int32_t LB_Current = 0;
static uint32_t LB_Charging_Power_W = 0;
static uint32_t LB_Regen_allowed_W = 0;
static uint32_t LB_Discharge_allowed_W = 0;
static int16_t LB_Current = 0;
static int16_t LB_Cell_minimum_temperature = 0;
static int16_t LB_Cell_maximum_temperature = 0;
static uint16_t LB_kWh_Remaining = 0;
static uint16_t LB_Battery_Voltage = 3700;
static uint8_t LB_Heartbeat = 0;
static uint8_t frame0 = 0;
static uint8_t current_poll = 0;
static uint8_t requested_poll = 0;
@ -77,32 +80,17 @@ void update_values_battery() { //This function maps all the values fetched via
datalayer.battery.status.soh_pptt = (LB_SOH * 100); // Increase range from 99% -> 99.00%
datalayer.battery.status.real_soc = SOC_polled;
//datalayer.battery.status.real_soc = LB_Display_SOC; //Alternative would be to use Dash SOC%
datalayer.battery.status.current_dA = LB_Current; //TODO: Take from CAN
datalayer.battery.status.current_dA = LB_Current * 10; //Convert A to dA
//Calculate the remaining Wh amount from SOC% and max Wh value.
datalayer.battery.status.remaining_capacity_Wh = static_cast<uint32_t>(
(static_cast<double>(datalayer.battery.status.real_soc) / 10000) * datalayer.battery.info.total_capacity_Wh);
datalayer.battery.status.max_discharge_power_W = 5000; //TODO: Take from CAN
datalayer.battery.status.max_discharge_power_W = LB_Discharge_allowed_W;
datalayer.battery.status.max_charge_power_W = 5000; //TODO: Take from CAN
// TODO: Remove this hacky wacky scaling down charge power when we find value from CAN
if (datalayer.battery.status.real_soc > 9500) {
datalayer.battery.status.max_charge_power_W = 3000;
}
if (datalayer.battery.status.real_soc > 9600) {
datalayer.battery.status.max_charge_power_W = 2000;
}
if (datalayer.battery.status.real_soc > 9700) {
datalayer.battery.status.max_charge_power_W = 1000;
}
if (datalayer.battery.status.real_soc > 9800) {
datalayer.battery.status.max_charge_power_W = 500;
}
if (datalayer.battery.status.real_soc > 9900) {
datalayer.battery.status.max_charge_power_W = 50;
}
datalayer.battery.status.max_charge_power_W = LB_Regen_allowed_W;
int16_t temperatures[] = {cell_1_temperature_polled, cell_2_temperature_polled, cell_3_temperature_polled,
cell_4_temperature_polled, cell_5_temperature_polled, cell_6_temperature_polled,
@ -145,28 +133,61 @@ void update_values_battery() { //This function maps all the values fetched via
datalayer.battery.status.cell_min_voltage_mV = min_cell_mv_value;
datalayer.battery.status.cell_max_voltage_mV = max_cell_mv_value;
datalayer.battery.status.voltage_dV = static_cast<uint32_t>((calculated_total_pack_voltage_mV / 100)); // mV to dV
#ifdef DEBUG_VIA_USB
#endif
}
void receive_can_battery(CAN_frame rx_frame) {
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
switch (rx_frame.ID) {
case 0x427:
LB_Charge_Power_W = rx_frame.data.u8[5] * 300;
case 0x155: //10ms - Charging power, current and SOC
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
LB_Charging_Power_W = rx_frame.data.u8[0] * 300;
LB_Current = (((((rx_frame.data.u8[1] & 0x0F) << 8) | rx_frame.data.u8[2]) * 0.25) - 500);
LB_Display_SOC = ((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]);
break;
case 0x427: // NOTE: Not present on 41kWh battery!
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
LB_kWh_Remaining = (((((rx_frame.data.u8[6] << 8) | (rx_frame.data.u8[7])) >> 6) & 0x3ff) * 0.1);
break;
case 0x42E: //HV SOC & Battery Temp & Charging Power
case 0x42E: //NOTE: Not present on 41kWh battery!
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
LB_Battery_Voltage = (((((rx_frame.data.u8[3] << 8) | (rx_frame.data.u8[4])) >> 5) & 0x3ff) * 0.5); //0.5V/bit
LB_Average_Temperature = (((((rx_frame.data.u8[5] << 8) | (rx_frame.data.u8[6])) >> 5) & 0x7F) - 40);
break;
case 0x424: //100ms - Charge limits, Temperatures, SOH
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
LB_Regen_allowed_W = rx_frame.data.u8[2] * 500;
LB_Discharge_allowed_W = rx_frame.data.u8[3] * 500;
LB_Cell_minimum_temperature = (rx_frame.data.u8[4] - 40);
LB_SOH = rx_frame.data.u8[5];
LB_Heartbeat = rx_frame.data.u8[6]; // Alternates between 0x55 and 0xAA every 500ms (Same as on Nissan LEAF)
LB_Cell_maximum_temperature = (rx_frame.data.u8[7] - 40);
break;
case 0x425: //100ms Unknown content
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
//Sent only? by 41kWh battery
break;
case 0x445: //100ms
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
//Sent only? by 41kWh battery (potential use for detecting which generation we are on)
break;
case 0x4AE: //3000ms
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
//Sent only? by 41kWh battery (potential use for detecting which generation we are on)
break;
case 0x4AF: //100ms
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
//Sent only? by 41kWh battery (potential use for detecting which generation we are on)
break;
case 0x654: //SOC
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
LB_SOC = rx_frame.data.u8[3];
break;
case 0x658: //SOH
LB_SOH = (rx_frame.data.u8[4] & 0x7F);
case 0x658: //SOH - NOTE: Not present on 41kWh battery! (Is this message on 21kWh?)
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
//LB_SOH = (rx_frame.data.u8[4] & 0x7F);
break;
case 0x659: //3000ms
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
//Sent only? by 41kWh battery (potential use for detecting which generation we are on)
break;
case 0x7BB: //Reply from active polling
frame0 = rx_frame.data.u8[0];

View file

@ -216,10 +216,6 @@ void update_values_battery() { //This function maps all the values fetched via
datalayer_extended.zoePH2.battery_pack_time = battery_pack_time;
datalayer_extended.zoePH2.battery_soc_min = battery_soc_min;
datalayer_extended.zoePH2.battery_soc_max = battery_soc_max;
#ifdef DEBUG_VIA_USB
#endif
}
void receive_can_battery(CAN_frame rx_frame) {

View file

@ -162,17 +162,17 @@ void receive_can_battery(CAN_frame rx_frame) {
/*
// All CAN messages recieved will be logged via serial
Serial.print(millis()); // Example printout, time, ID, length, data: 7553 1DB 8 FF C0 B9 EA 0 0 2 5D
Serial.print(" ");
Serial.print(rx_frame.ID, HEX);
Serial.print(" ");
Serial.print(rx_frame.DLC);
Serial.print(" ");
logging.print(millis()); // Example printout, time, ID, length, data: 7553 1DB 8 FF C0 B9 EA 0 0 2 5D
logging.print(" ");
logging.print(rx_frame.ID, HEX);
logging.print(" ");
logging.print(rx_frame.DLC);
logging.print(" ");
for (int i = 0; i < rx_frame.DLC; ++i) {
Serial.print(rx_frame.data.u8[i], HEX);
Serial.print(" ");
logging.print(rx_frame.data.u8[i], HEX);
logging.print(" ");
}
Serial.println("");
logging.println("");
*/
switch (rx_frame.ID) {
case 0xF5: // This is the only message is sent from BMS

View file

@ -18,6 +18,8 @@ static unsigned long previousMillis10 = 0; // will store last time a 10ms CAN
static unsigned long previousMillis100 = 0; // will store last time a 100ms CAN Message was send
static unsigned long previousMillis500 = 0; // will store last time a 500ms CAN Message was send
static uint8_t poll_data_pid = 0;
static uint8_t counter_200 = 0;
static uint8_t checksum_200 = 0;
static uint16_t SOC_Display = 0;
static uint16_t batterySOH = 100;
@ -32,11 +34,27 @@ static int16_t leadAcidBatteryVoltage = 120;
static int8_t temperatureMax = 0;
static int8_t temperatureMin = 0;
static int16_t batteryAmps = 0;
static uint8_t counter_200 = 0;
static uint8_t checksum_200 = 0;
static uint8_t StatusBattery = 0;
static uint16_t cellvoltages_mv[96];
#ifdef DOUBLE_BATTERY
static uint16_t battery2_SOC_Display = 0;
static uint16_t battery2_SOH = 100;
static uint16_t battery2_CellVoltMax_mV = 3700;
static uint16_t battery2_CellVoltMin_mV = 3700;
static uint8_t battery2_CellVmaxNo = 0;
static uint8_t battery2_CellVminNo = 0;
static uint16_t battery2_allowedDischargePower = 0;
static uint16_t battery2_allowedChargePower = 0;
static uint16_t battery2_batteryVoltage = 0;
static int16_t battery2_leadAcidBatteryVoltage = 120;
static int8_t battery2_temperatureMax = 0;
static int8_t battery2_temperatureMin = 0;
static int16_t battery2_batteryAmps = 0;
static uint8_t battery2_StatusBattery = 0;
static uint16_t battery2_cellvoltages_mv[96];
#endif //DOUBLE_BATTERY
CAN_frame SANTAFE_200 = {.FD = false,
.ext_ID = false,
.DLC = 8,
@ -96,10 +114,6 @@ void update_values_battery() { //This function maps all the values fetched via
if (leadAcidBatteryVoltage < 110) {
set_event(EVENT_12V_LOW, leadAcidBatteryVoltage);
}
#ifdef DEBUG_VIA_USB
#endif
}
void receive_can_battery(CAN_frame rx_frame) {
@ -338,10 +352,13 @@ void send_can_battery() {
SANTAFE_200.data.u8[7] = checksum_200;
transmit_can(&SANTAFE_200, can_config.battery);
transmit_can(&SANTAFE_2A1, can_config.battery);
transmit_can(&SANTAFE_2F0, can_config.battery);
#ifdef DOUBLE_BATTERY
transmit_can(&SANTAFE_200, can_config.battery_double);
transmit_can(&SANTAFE_2A1, can_config.battery_double);
transmit_can(&SANTAFE_2F0, can_config.battery_double);
#endif //DOUBLE_BATTERY
counter_200++;
if (counter_200 > 0xF) {
@ -354,36 +371,279 @@ void send_can_battery() {
previousMillis100 = currentMillis;
transmit_can(&SANTAFE_523, can_config.battery);
#ifdef DOUBLE_BATTERY
transmit_can(&SANTAFE_523, can_config.battery_double);
#endif //DOUBLE_BATTERY
}
// Send 500ms CAN Message
if (currentMillis - previousMillis500 >= INTERVAL_500_MS) {
previousMillis500 = currentMillis;
//PID data is polled after last message sent from battery:
if (poll_data_pid >= 5) { //polling one of 5 PIDs at 100ms, resolution = 500ms
poll_data_pid = 0;
}
poll_data_pid++;
if (poll_data_pid == 1) {
SANTAFE_7E4_poll.data.u8[3] = 0x01;
transmit_can(&SANTAFE_7E4_poll, can_config.battery);
} else if (poll_data_pid == 2) {
SANTAFE_7E4_poll.data.u8[3] = 0x02;
transmit_can(&SANTAFE_7E4_poll, can_config.battery);
} else if (poll_data_pid == 3) {
SANTAFE_7E4_poll.data.u8[3] = 0x03;
transmit_can(&SANTAFE_7E4_poll, can_config.battery);
} else if (poll_data_pid == 4) {
SANTAFE_7E4_poll.data.u8[3] = 0x04;
transmit_can(&SANTAFE_7E4_poll, can_config.battery);
} else if (poll_data_pid == 5) {
SANTAFE_7E4_poll.data.u8[3] = 0x05;
transmit_can(&SANTAFE_7E4_poll, can_config.battery);
}
// PID data is polled after last message sent from battery:
poll_data_pid = (poll_data_pid % 5) + 1;
SANTAFE_7E4_poll.data.u8[3] = (uint8_t)poll_data_pid;
transmit_can(&SANTAFE_7E4_poll, can_config.battery);
#ifdef DOUBLE_BATTERY
transmit_can(&SANTAFE_7E4_poll, can_config.battery_double);
#endif //DOUBLE_BATTERY
}
}
#ifdef DOUBLE_BATTERY
void update_values_battery2() { //This function maps all the values fetched via CAN to the correct parameters used for modbus
datalayer.battery2.status.real_soc = (battery2_SOC_Display * 10); //increase SOC range from 0-100.0 -> 100.00
datalayer.battery2.status.soh_pptt = (battery2_SOH * 100); //Increase decimals from 100% -> 100.00%
datalayer.battery2.status.voltage_dV = battery2_batteryVoltage;
datalayer.battery2.status.current_dA = -battery2_batteryAmps;
datalayer.battery2.status.remaining_capacity_Wh = static_cast<uint32_t>(
(static_cast<double>(datalayer.battery2.status.real_soc) / 10000) * datalayer.battery2.info.total_capacity_Wh);
datalayer.battery2.status.max_discharge_power_W = battery2_allowedDischargePower * 10;
datalayer.battery2.status.max_charge_power_W = battery2_allowedChargePower * 10;
//Power in watts, Negative = charging batt
datalayer.battery2.status.active_power_W =
((datalayer.battery2.status.voltage_dV * datalayer.battery2.status.current_dA) / 100);
datalayer.battery2.status.cell_max_voltage_mV = battery2_CellVoltMax_mV;
datalayer.battery2.status.cell_min_voltage_mV = battery2_CellVoltMin_mV;
datalayer.battery2.status.temperature_min_dC = battery2_temperatureMin * 10; //Increase decimals, 17C -> 17.0C
datalayer.battery2.status.temperature_max_dC = battery2_temperatureMax * 10; //Increase decimals, 18C -> 18.0C
if (battery2_leadAcidBatteryVoltage < 110) {
set_event(EVENT_12V_LOW, battery2_leadAcidBatteryVoltage);
}
}
void receive_can_battery2(CAN_frame rx_frame) {
switch (rx_frame.ID) {
case 0x1FF:
datalayer.battery2.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
battery2_StatusBattery = (rx_frame.data.u8[0] & 0x0F);
break;
case 0x4D5:
break;
case 0x4DD:
break;
case 0x4DE:
datalayer.battery2.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
break;
case 0x4E0:
break;
case 0x542:
datalayer.battery2.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
battery2_SOC_Display = ((rx_frame.data.u8[1] << 8) + rx_frame.data.u8[0]) / 2;
break;
case 0x588:
datalayer.battery2.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
battery2_batteryVoltage = ((rx_frame.data.u8[1] << 8) + rx_frame.data.u8[0]);
break;
case 0x597:
break;
case 0x5A6:
break;
case 0x5A7:
break;
case 0x5AD:
datalayer.battery2.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
battery2_batteryAmps = (rx_frame.data.u8[3] << 8) + rx_frame.data.u8[2];
break;
case 0x5AE:
break;
case 0x5F1:
break;
case 0x620:
datalayer.battery2.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
battery2_leadAcidBatteryVoltage = rx_frame.data.u8[1];
battery2_temperatureMin = rx_frame.data.u8[6]; //Lowest temp in battery
battery2_temperatureMax = rx_frame.data.u8[7]; //Highest temp in battery
break;
case 0x670:
datalayer.battery2.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
battery2_allowedChargePower = ((rx_frame.data.u8[1] << 8) | rx_frame.data.u8[0]);
battery2_allowedDischargePower = ((rx_frame.data.u8[3] << 8) | rx_frame.data.u8[2]);
break;
case 0x671:
datalayer.battery2.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
break;
case 0x7EC: //Data From polled PID group, BigEndian
switch (rx_frame.data.u8[0]) {
case 0x10: //"PID Header"
if (rx_frame.data.u8[4] == poll_data_pid) {
transmit_can(&SANTAFE_7E4_ack,
can_config.battery_double); //Send ack to BMS if the same frame is sent as polled
}
break;
case 0x21: //First frame in PID group
if (poll_data_pid == 1) {
} else if (poll_data_pid == 2) {
battery2_cellvoltages_mv[0] = (rx_frame.data.u8[2] * 20);
battery2_cellvoltages_mv[1] = (rx_frame.data.u8[3] * 20);
battery2_cellvoltages_mv[2] = (rx_frame.data.u8[4] * 20);
battery2_cellvoltages_mv[3] = (rx_frame.data.u8[5] * 20);
battery2_cellvoltages_mv[4] = (rx_frame.data.u8[6] * 20);
battery2_cellvoltages_mv[5] = (rx_frame.data.u8[7] * 20);
} else if (poll_data_pid == 3) {
battery2_cellvoltages_mv[32] = (rx_frame.data.u8[2] * 20);
battery2_cellvoltages_mv[33] = (rx_frame.data.u8[3] * 20);
battery2_cellvoltages_mv[34] = (rx_frame.data.u8[4] * 20);
battery2_cellvoltages_mv[35] = (rx_frame.data.u8[5] * 20);
battery2_cellvoltages_mv[36] = (rx_frame.data.u8[6] * 20);
battery2_cellvoltages_mv[37] = (rx_frame.data.u8[7] * 20);
} else if (poll_data_pid == 4) {
battery2_cellvoltages_mv[64] = (rx_frame.data.u8[2] * 20);
battery2_cellvoltages_mv[65] = (rx_frame.data.u8[3] * 20);
battery2_cellvoltages_mv[66] = (rx_frame.data.u8[4] * 20);
battery2_cellvoltages_mv[67] = (rx_frame.data.u8[5] * 20);
battery2_cellvoltages_mv[68] = (rx_frame.data.u8[6] * 20);
battery2_cellvoltages_mv[69] = (rx_frame.data.u8[7] * 20);
}
break;
case 0x22: //Second datarow in PID group
if (poll_data_pid == 2) {
battery2_cellvoltages_mv[6] = (rx_frame.data.u8[1] * 20);
battery2_cellvoltages_mv[7] = (rx_frame.data.u8[2] * 20);
battery2_cellvoltages_mv[8] = (rx_frame.data.u8[3] * 20);
battery2_cellvoltages_mv[9] = (rx_frame.data.u8[4] * 20);
battery2_cellvoltages_mv[10] = (rx_frame.data.u8[5] * 20);
battery2_cellvoltages_mv[11] = (rx_frame.data.u8[6] * 20);
battery2_cellvoltages_mv[12] = (rx_frame.data.u8[7] * 20);
} else if (poll_data_pid == 3) {
battery2_cellvoltages_mv[38] = (rx_frame.data.u8[1] * 20);
battery2_cellvoltages_mv[39] = (rx_frame.data.u8[2] * 20);
battery2_cellvoltages_mv[40] = (rx_frame.data.u8[3] * 20);
battery2_cellvoltages_mv[41] = (rx_frame.data.u8[4] * 20);
battery2_cellvoltages_mv[42] = (rx_frame.data.u8[5] * 20);
battery2_cellvoltages_mv[43] = (rx_frame.data.u8[6] * 20);
battery2_cellvoltages_mv[44] = (rx_frame.data.u8[7] * 20);
} else if (poll_data_pid == 4) {
battery2_cellvoltages_mv[70] = (rx_frame.data.u8[1] * 20);
battery2_cellvoltages_mv[71] = (rx_frame.data.u8[2] * 20);
battery2_cellvoltages_mv[72] = (rx_frame.data.u8[3] * 20);
battery2_cellvoltages_mv[73] = (rx_frame.data.u8[4] * 20);
battery2_cellvoltages_mv[74] = (rx_frame.data.u8[5] * 20);
battery2_cellvoltages_mv[75] = (rx_frame.data.u8[6] * 20);
battery2_cellvoltages_mv[76] = (rx_frame.data.u8[7] * 20);
} else if (poll_data_pid == 6) {
}
break;
case 0x23: //Third datarow in PID group
if (poll_data_pid == 1) {
battery2_CellVoltMax_mV = (rx_frame.data.u8[7] * 20); //(volts *50) *20 =mV
} else if (poll_data_pid == 2) {
battery2_cellvoltages_mv[13] = (rx_frame.data.u8[1] * 20);
battery2_cellvoltages_mv[14] = (rx_frame.data.u8[2] * 20);
battery2_cellvoltages_mv[15] = (rx_frame.data.u8[3] * 20);
battery2_cellvoltages_mv[16] = (rx_frame.data.u8[4] * 20);
battery2_cellvoltages_mv[17] = (rx_frame.data.u8[5] * 20);
battery2_cellvoltages_mv[18] = (rx_frame.data.u8[6] * 20);
battery2_cellvoltages_mv[19] = (rx_frame.data.u8[7] * 20);
} else if (poll_data_pid == 3) {
battery2_cellvoltages_mv[45] = (rx_frame.data.u8[1] * 20);
battery2_cellvoltages_mv[46] = (rx_frame.data.u8[2] * 20);
battery2_cellvoltages_mv[47] = (rx_frame.data.u8[3] * 20);
battery2_cellvoltages_mv[48] = (rx_frame.data.u8[4] * 20);
battery2_cellvoltages_mv[49] = (rx_frame.data.u8[5] * 20);
battery2_cellvoltages_mv[50] = (rx_frame.data.u8[6] * 20);
battery2_cellvoltages_mv[51] = (rx_frame.data.u8[7] * 20);
} else if (poll_data_pid == 4) {
battery2_cellvoltages_mv[77] = (rx_frame.data.u8[1] * 20);
battery2_cellvoltages_mv[78] = (rx_frame.data.u8[2] * 20);
battery2_cellvoltages_mv[79] = (rx_frame.data.u8[3] * 20);
battery2_cellvoltages_mv[80] = (rx_frame.data.u8[4] * 20);
battery2_cellvoltages_mv[81] = (rx_frame.data.u8[5] * 20);
battery2_cellvoltages_mv[82] = (rx_frame.data.u8[6] * 20);
battery2_cellvoltages_mv[83] = (rx_frame.data.u8[7] * 20);
} else if (poll_data_pid == 5) {
if (rx_frame.data.u8[6] > 0) {
battery2_SOH = rx_frame.data.u8[6];
}
if (battery2_SOH > 100) {
battery2_SOH = 100;
}
}
break;
case 0x24: //Fourth datarow in PID group
if (poll_data_pid == 1) {
battery2_CellVmaxNo = rx_frame.data.u8[1];
battery2_CellVminNo = rx_frame.data.u8[3];
CellVoltMin_mV = (rx_frame.data.u8[2] * 20); //(volts *50) *20 =mV
} else if (poll_data_pid == 2) {
battery2_cellvoltages_mv[20] = (rx_frame.data.u8[1] * 20);
battery2_cellvoltages_mv[21] = (rx_frame.data.u8[2] * 20);
battery2_cellvoltages_mv[22] = (rx_frame.data.u8[3] * 20);
battery2_cellvoltages_mv[23] = (rx_frame.data.u8[4] * 20);
battery2_cellvoltages_mv[24] = (rx_frame.data.u8[5] * 20);
battery2_cellvoltages_mv[25] = (rx_frame.data.u8[6] * 20);
battery2_cellvoltages_mv[26] = (rx_frame.data.u8[7] * 20);
} else if (poll_data_pid == 3) {
battery2_cellvoltages_mv[52] = (rx_frame.data.u8[1] * 20);
battery2_cellvoltages_mv[53] = (rx_frame.data.u8[2] * 20);
battery2_cellvoltages_mv[54] = (rx_frame.data.u8[3] * 20);
battery2_cellvoltages_mv[55] = (rx_frame.data.u8[4] * 20);
battery2_cellvoltages_mv[56] = (rx_frame.data.u8[5] * 20);
battery2_cellvoltages_mv[57] = (rx_frame.data.u8[6] * 20);
battery2_cellvoltages_mv[58] = (rx_frame.data.u8[7] * 20);
} else if (poll_data_pid == 4) {
battery2_cellvoltages_mv[84] = (rx_frame.data.u8[1] * 20);
battery2_cellvoltages_mv[85] = (rx_frame.data.u8[2] * 20);
battery2_cellvoltages_mv[86] = (rx_frame.data.u8[3] * 20);
battery2_cellvoltages_mv[87] = (rx_frame.data.u8[4] * 20);
battery2_cellvoltages_mv[88] = (rx_frame.data.u8[5] * 20);
battery2_cellvoltages_mv[89] = (rx_frame.data.u8[6] * 20);
battery2_cellvoltages_mv[90] = (rx_frame.data.u8[7] * 20);
} else if (poll_data_pid == 5) {
}
break;
case 0x25: //Fifth datarow in PID group
if (poll_data_pid == 2) {
battery2_cellvoltages_mv[27] = (rx_frame.data.u8[1] * 20);
battery2_cellvoltages_mv[28] = (rx_frame.data.u8[2] * 20);
battery2_cellvoltages_mv[29] = (rx_frame.data.u8[3] * 20);
battery2_cellvoltages_mv[30] = (rx_frame.data.u8[4] * 20);
battery2_cellvoltages_mv[31] = (rx_frame.data.u8[5] * 20);
} else if (poll_data_pid == 3) {
battery2_cellvoltages_mv[59] = (rx_frame.data.u8[1] * 20);
battery2_cellvoltages_mv[60] = (rx_frame.data.u8[2] * 20);
battery2_cellvoltages_mv[61] = (rx_frame.data.u8[3] * 20);
battery2_cellvoltages_mv[62] = (rx_frame.data.u8[4] * 20);
battery2_cellvoltages_mv[63] = (rx_frame.data.u8[5] * 20);
} else if (poll_data_pid == 4) {
battery2_cellvoltages_mv[91] = (rx_frame.data.u8[1] * 20);
battery2_cellvoltages_mv[92] = (rx_frame.data.u8[2] * 20);
battery2_cellvoltages_mv[93] = (rx_frame.data.u8[3] * 20);
battery2_cellvoltages_mv[94] = (rx_frame.data.u8[4] * 20);
battery2_cellvoltages_mv[95] = (rx_frame.data.u8[5] * 20);
//Map all cell voltages to the global array, we have sampled them all!
memcpy(datalayer.battery2.status.cell_voltages_mV, battery2_cellvoltages_mv, 96 * sizeof(uint16_t));
} else if (poll_data_pid == 5) {
}
break;
case 0x26: //Sixth datarow in PID group
break;
case 0x27: //Seventh datarow in PID group
break;
case 0x28: //Eighth datarow in PID group
break;
}
break;
default:
break;
}
}
#endif //DOUBLE_BATTERY
uint8_t CalculateCRC8(CAN_frame rx_frame) {
int crc = 0;
@ -409,6 +669,16 @@ void setup_battery(void) { // Performs one time setup at startup
datalayer.battery.info.min_design_voltage_dV = MIN_PACK_VOLTAGE_DV;
datalayer.battery.info.max_cell_voltage_mV = MAX_CELL_VOLTAGE_MV;
datalayer.battery.info.min_cell_voltage_mV = MIN_CELL_VOLTAGE_MV;
datalayer.battery.info.max_cell_voltage_deviation_mV = MAX_CELL_DEVIATION_MV;
#ifdef DOUBLE_BATTERY
datalayer.battery2.info.number_of_cells = datalayer.battery.info.number_of_cells;
datalayer.battery2.info.max_design_voltage_dV = datalayer.battery.info.max_design_voltage_dV;
datalayer.battery2.info.min_design_voltage_dV = datalayer.battery.info.min_design_voltage_dV;
datalayer.battery2.info.max_cell_voltage_mV = datalayer.battery.info.max_cell_voltage_mV;
datalayer.battery2.info.min_cell_voltage_mV = datalayer.battery.info.min_cell_voltage_mV;
datalayer.battery2.info.max_cell_voltage_deviation_mV = datalayer.battery.info.max_cell_voltage_deviation_mV;
#endif //DOUBLE_BATTERY
}
#endif

View file

@ -94,8 +94,8 @@ void manageSerialLinkReceiver() {
bool readError = dataLinkReceive.checkReadError(true); // check for error & clear error flag
if (readError) {
Serial.print(currentTime);
Serial.println(" - ERROR: SerialDataLink - Read Error");
logging.print(currentTime);
logging.println(" - ERROR: SerialDataLink - Read Error");
lasterror = true;
errors++;
}
@ -112,8 +112,8 @@ void manageSerialLinkReceiver() {
//bms_status = ACTIVE; // just testing
if (lasterror) {
lasterror = false;
Serial.print(currentTime);
Serial.println(" - RECOVERY: SerialDataLink - Read GOOD");
logging.print(currentTime);
logging.println(" - RECOVERY: SerialDataLink - Read GOOD");
}
}
@ -134,34 +134,34 @@ void manageSerialLinkReceiver() {
// report Lost data & Max charge / Discharge reductions
if (minutesLost != last_minutesLost) {
last_minutesLost = minutesLost;
Serial.print(currentTime);
logging.print(currentTime);
if (batteryFault) {
Serial.print("Battery Fault (minutes) : ");
logging.print("Battery Fault (minutes) : ");
} else {
Serial.print(" - Minutes without data : ");
logging.print(" - Minutes without data : ");
}
Serial.print(minutesLost);
Serial.print(", max Charge = ");
Serial.print(datalayer.battery.status.max_charge_power_W);
Serial.print(", max Discharge = ");
Serial.println(datalayer.battery.status.max_discharge_power_W);
logging.print(minutesLost);
logging.print(", max Charge = ");
logging.print(datalayer.battery.status.max_charge_power_W);
logging.print(", max Discharge = ");
logging.println(datalayer.battery.status.max_discharge_power_W);
}
}
if (currentTime - reportTime > 59999) {
reportTime = currentTime;
Serial.print(currentTime);
Serial.print(" SerialDataLink-Receiver - NewData :");
Serial.print(reads);
Serial.print(" Errors : ");
Serial.println(errors);
logging.print(currentTime);
logging.print(" SerialDataLink-Receiver - NewData :");
logging.print(reads);
logging.print(" Errors : ");
logging.println(errors);
reads = 0;
errors = 0;
// --- printUsefullData();
//Serial.print("SOC = ");
//Serial.println(SOC);
#ifdef DEBUG_VIA_USB
//logging.print("SOC = ");
//logging.println(SOC);
#ifdef DEBUG_LOG
update_values_serial_link();
#endif
}
@ -179,43 +179,43 @@ void manageSerialLinkReceiver() {
}
void update_values_serial_link() {
Serial.println("Values from battery: ");
Serial.print("SOC: ");
Serial.print(datalayer.battery.status.real_soc);
Serial.print(" SOH: ");
Serial.print(datalayer.battery.status.soh_pptt);
Serial.print(" Voltage: ");
Serial.print(datalayer.battery.status.voltage_dV);
Serial.print(" Current: ");
Serial.print(datalayer.battery.status.current_dA);
Serial.print(" Capacity: ");
Serial.print(datalayer.battery.info.total_capacity_Wh);
Serial.print(" Remain cap: ");
Serial.print(datalayer.battery.status.remaining_capacity_Wh);
Serial.print(" Max discharge W: ");
Serial.print(datalayer.battery.status.max_discharge_power_W);
Serial.print(" Max charge W: ");
Serial.print(datalayer.battery.status.max_charge_power_W);
Serial.print(" BMS status: ");
Serial.print(datalayer.battery.status.bms_status);
Serial.print(" Power: ");
Serial.print(datalayer.battery.status.active_power_W);
Serial.print(" Temp min: ");
Serial.print(datalayer.battery.status.temperature_min_dC);
Serial.print(" Temp max: ");
Serial.print(datalayer.battery.status.temperature_max_dC);
Serial.print(" Cell max: ");
Serial.print(datalayer.battery.status.cell_max_voltage_mV);
Serial.print(" Cell min: ");
Serial.print(datalayer.battery.status.cell_min_voltage_mV);
Serial.print(" LFP : ");
Serial.print(datalayer.battery.info.chemistry);
Serial.print(" Battery Allows Contactor Closing: ");
Serial.print(datalayer.system.status.battery_allows_contactor_closing);
Serial.print(" Inverter Allows Contactor Closing: ");
Serial.print(datalayer.system.status.inverter_allows_contactor_closing);
logging.println("Values from battery: ");
logging.print("SOC: ");
logging.print(datalayer.battery.status.real_soc);
logging.print(" SOH: ");
logging.print(datalayer.battery.status.soh_pptt);
logging.print(" Voltage: ");
logging.print(datalayer.battery.status.voltage_dV);
logging.print(" Current: ");
logging.print(datalayer.battery.status.current_dA);
logging.print(" Capacity: ");
logging.print(datalayer.battery.info.total_capacity_Wh);
logging.print(" Remain cap: ");
logging.print(datalayer.battery.status.remaining_capacity_Wh);
logging.print(" Max discharge W: ");
logging.print(datalayer.battery.status.max_discharge_power_W);
logging.print(" Max charge W: ");
logging.print(datalayer.battery.status.max_charge_power_W);
logging.print(" BMS status: ");
logging.print(datalayer.battery.status.bms_status);
logging.print(" Power: ");
logging.print(datalayer.battery.status.active_power_W);
logging.print(" Temp min: ");
logging.print(datalayer.battery.status.temperature_min_dC);
logging.print(" Temp max: ");
logging.print(datalayer.battery.status.temperature_max_dC);
logging.print(" Cell max: ");
logging.print(datalayer.battery.status.cell_max_voltage_mV);
logging.print(" Cell min: ");
logging.print(datalayer.battery.status.cell_min_voltage_mV);
logging.print(" LFP : ");
logging.print(datalayer.battery.info.chemistry);
logging.print(" Battery Allows Contactor Closing: ");
logging.print(datalayer.system.status.battery_allows_contactor_closing);
logging.print(" Inverter Allows Contactor Closing: ");
logging.print(datalayer.system.status.inverter_allows_contactor_closing);
Serial.println("");
logging.println("");
}
void setup_battery(void) {

File diff suppressed because it is too large Load diff

View file

@ -15,9 +15,9 @@ CAN_frame TEST = {.FD = false,
.data = {0x10, 0x64, 0x00, 0xB0, 0x00, 0x1E, 0x00, 0x8F}};
void print_units(char* header, int value, char* units) {
Serial.print(header);
Serial.print(value);
Serial.print(units);
logging.print(header);
logging.print(value);
logging.print(units);
}
void update_values_battery() { /* This function puts fake values onto the parameters sent towards the inverter */
@ -54,8 +54,8 @@ void update_values_battery() { /* This function puts fake values onto the parame
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
/*Finally print out values to serial if configured to do so*/
#ifdef DEBUG_VIA_USB
Serial.println("FAKE Values going to inverter");
#ifdef DEBUG_LOG
logging.println("FAKE Values going to inverter");
print_units("SOH%: ", (datalayer.battery.status.soh_pptt * 0.01), "% ");
print_units(", SOC%: ", (datalayer.battery.status.reported_soc * 0.01), "% ");
print_units(", Voltage: ", (datalayer.battery.status.voltage_dV * 0.1), "V ");
@ -65,7 +65,7 @@ void update_values_battery() { /* This function puts fake values onto the parame
print_units(", Min temp: ", (datalayer.battery.status.temperature_min_dC * 0.1), "°C ");
print_units(", Max cell voltage: ", datalayer.battery.status.cell_max_voltage_mV, "mV ");
print_units(", Min cell voltage: ", datalayer.battery.status.cell_min_voltage_mV, "mV ");
Serial.println("");
logging.println("");
#endif
}
@ -107,8 +107,8 @@ void update_values_battery2() { // Handle the values coming in from battery #2
datalayer.battery2.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
/*Finally print out values to serial if configured to do so*/
#ifdef DEBUG_VIA_USB
Serial.println("FAKE Values battery 2 going to inverter");
#ifdef DEBUG_LOG
logging.println("FAKE Values battery 2 going to inverter");
print_units("SOH 2 %: ", (datalayer.battery2.status.soh_pptt * 0.01), "% ");
print_units(", SOC 2 %: ", (datalayer.battery2.status.reported_soc * 0.01), "% ");
print_units(", Voltage 2: ", (datalayer.battery2.status.voltage_dV * 0.1), "V ");
@ -118,7 +118,7 @@ void update_values_battery2() { // Handle the values coming in from battery #2
print_units(", Min temp 2: ", (datalayer.battery2.status.temperature_min_dC * 0.1), "°C ");
print_units(", Max cell voltage 2: ", datalayer.battery2.status.cell_max_voltage_mV, "mV ");
print_units(", Min cell voltage 2: ", datalayer.battery2.status.cell_min_voltage_mV, "mV ");
Serial.println("");
logging.println("");
#endif
}

View file

@ -94,49 +94,49 @@ void update_values_battery() { //This function maps all the values fetched via
datalayer.battery.status.cell_voltages_mV[i] = cell_voltages[i];
}
#ifdef DEBUG_VIA_USB
Serial.print("BMS reported SOC%: ");
Serial.println(SOC_BMS);
Serial.print("Calculated SOC%: ");
Serial.println(SOC_CALC);
Serial.print("Rescaled SOC%: ");
Serial.println(datalayer.battery.status.reported_soc / 100);
Serial.print("Battery current: ");
Serial.println(BATT_I);
Serial.print("Battery voltage: ");
Serial.println(BATT_U);
Serial.print("Battery maximum voltage limit: ");
Serial.println(MAX_U);
Serial.print("Battery minimum voltage limit: ");
Serial.println(MIN_U);
Serial.print("Remaining Energy: ");
Serial.println(remaining_capacity);
Serial.print("Discharge limit: ");
Serial.println(HvBattPwrLimDchaSoft);
Serial.print("Battery Error Indication: ");
Serial.println(BATT_ERR_INDICATION);
Serial.print("Maximum battery temperature: ");
Serial.println(BATT_T_MAX / 10);
Serial.print("Minimum battery temperature: ");
Serial.println(BATT_T_MIN / 10);
Serial.print("Average battery temperature: ");
Serial.println(BATT_T_AVG / 10);
Serial.print("BMS Highest cell voltage: ");
Serial.println(CELL_U_MAX * 10);
Serial.print("BMS Lowest cell voltage: ");
Serial.println(CELL_U_MIN * 10);
Serial.print("BMS Highest cell nr: ");
Serial.println(CELL_ID_U_MAX);
Serial.print("Highest cell voltage: ");
Serial.println(min_max_voltage[1]);
Serial.print("Lowest cell voltage: ");
Serial.println(min_max_voltage[0]);
Serial.print("Cell voltage,");
#ifdef DEBUG_LOG
logging.print("BMS reported SOC%: ");
logging.println(SOC_BMS);
logging.print("Calculated SOC%: ");
logging.println(SOC_CALC);
logging.print("Rescaled SOC%: ");
logging.println(datalayer.battery.status.reported_soc / 100);
logging.print("Battery current: ");
logging.println(BATT_I);
logging.print("Battery voltage: ");
logging.println(BATT_U);
logging.print("Battery maximum voltage limit: ");
logging.println(MAX_U);
logging.print("Battery minimum voltage limit: ");
logging.println(MIN_U);
logging.print("Remaining Energy: ");
logging.println(remaining_capacity);
logging.print("Discharge limit: ");
logging.println(HvBattPwrLimDchaSoft);
logging.print("Battery Error Indication: ");
logging.println(BATT_ERR_INDICATION);
logging.print("Maximum battery temperature: ");
logging.println(BATT_T_MAX / 10);
logging.print("Minimum battery temperature: ");
logging.println(BATT_T_MIN / 10);
logging.print("Average battery temperature: ");
logging.println(BATT_T_AVG / 10);
logging.print("BMS Highest cell voltage: ");
logging.println(CELL_U_MAX * 10);
logging.print("BMS Lowest cell voltage: ");
logging.println(CELL_U_MIN * 10);
logging.print("BMS Highest cell nr: ");
logging.println(CELL_ID_U_MAX);
logging.print("Highest cell voltage: ");
logging.println(min_max_voltage[1]);
logging.print("Lowest cell voltage: ");
logging.println(min_max_voltage[0]);
logging.print("Cell voltage,");
while (cnt < 108) {
Serial.print(cell_voltages[cnt++]);
Serial.print(",");
logging.print(cell_voltages[cnt++]);
logging.print(",");
}
Serial.println(";");
logging.println(";");
#endif
}
@ -148,8 +148,8 @@ void receive_can_battery(CAN_frame rx_frame) {
BATT_I = (0 - ((((rx_frame.data.u8[6] & 0x7F) * 256.0 + rx_frame.data.u8[7]) * 0.1) - 1638));
else {
BATT_I = 0;
#ifdef DEBUG_VIA_USB
Serial.println("BATT_I not valid");
#ifdef DEBUG_LOG
logging.println("BATT_I not valid");
#endif
}
@ -157,22 +157,22 @@ void receive_can_battery(CAN_frame rx_frame) {
MAX_U = (((rx_frame.data.u8[2] & 0x07) * 256.0 + rx_frame.data.u8[3]) * 0.25);
else {
//MAX_U = 0;
//Serial.println("MAX_U not valid"); // Value toggles between true/false from BMS
//logging.println("MAX_U not valid"); // Value toggles between true/false from BMS
}
if ((rx_frame.data.u8[4] & 0x08) == 0x08)
MIN_U = (((rx_frame.data.u8[4] & 0x07) * 256.0 + rx_frame.data.u8[5]) * 0.25);
else {
//MIN_U = 0;
//Serial.println("MIN_U not valid"); // Value toggles between true/false from BMS
//logging.println("MIN_U not valid"); // Value toggles between true/false from BMS
}
if ((rx_frame.data.u8[0] & 0x08) == 0x08)
BATT_U = (((rx_frame.data.u8[0] & 0x07) * 256.0 + rx_frame.data.u8[1]) * 0.25);
else {
BATT_U = 0;
#ifdef DEBUG_VIA_USB
Serial.println("BATT_U not valid");
#ifdef DEBUG_LOG
logging.println("BATT_U not valid");
#endif
}
break;
@ -189,8 +189,8 @@ void receive_can_battery(CAN_frame rx_frame) {
BATT_ERR_INDICATION = ((rx_frame.data.u8[0] & 0x40) >> 6);
else {
BATT_ERR_INDICATION = 0;
#ifdef DEBUG_VIA_USB
Serial.println("BATT_ERR_INDICATION not valid");
#ifdef DEBUG_LOG
logging.println("BATT_ERR_INDICATION not valid");
#endif
}
if ((rx_frame.data.u8[0] & 0x20) == 0x20) {
@ -201,8 +201,8 @@ void receive_can_battery(CAN_frame rx_frame) {
BATT_T_MAX = 0;
BATT_T_MIN = 0;
BATT_T_AVG = 0;
#ifdef DEBUG_VIA_USB
Serial.println("BATT_T not valid");
#ifdef DEBUG_LOG
logging.println("BATT_T not valid");
#endif
}
break;
@ -211,8 +211,8 @@ void receive_can_battery(CAN_frame rx_frame) {
HvBattPwrLimDchaSoft = (((rx_frame.data.u8[6] & 0x03) * 256 + rx_frame.data.u8[6]) >> 2);
} else {
HvBattPwrLimDchaSoft = 0;
#ifdef DEBUG_VIA_USB
Serial.println("HvBattPwrLimDchaSoft not valid");
#ifdef DEBUG_LOG
logging.println("HvBattPwrLimDchaSoft not valid");
#endif
}
break;
@ -221,8 +221,8 @@ void receive_can_battery(CAN_frame rx_frame) {
SOC_BMS = ((rx_frame.data.u8[6] & 0x03) * 256 + rx_frame.data.u8[7]);
} else {
SOC_BMS = 0;
#ifdef DEBUG_VIA_USB
Serial.println("SOC_BMS not valid");
#ifdef DEBUG_LOG
logging.println("SOC_BMS not valid");
#endif
}
@ -230,8 +230,8 @@ void receive_can_battery(CAN_frame rx_frame) {
CELL_U_MAX = ((rx_frame.data.u8[2] & 0x01) * 256 + rx_frame.data.u8[3]);
else {
CELL_U_MAX = 0;
#ifdef DEBUG_VIA_USB
Serial.println("CELL_U_MAX not valid");
#ifdef DEBUG_LOG
logging.println("CELL_U_MAX not valid");
#endif
}
@ -239,8 +239,8 @@ void receive_can_battery(CAN_frame rx_frame) {
CELL_U_MIN = ((rx_frame.data.u8[0] & 0x01) * 256.0 + rx_frame.data.u8[1]);
else {
CELL_U_MIN = 0;
#ifdef DEBUG_VIA_USB
Serial.println("CELL_U_MIN not valid");
#ifdef DEBUG_LOG
logging.println("CELL_U_MIN not valid");
#endif
}
@ -248,8 +248,8 @@ void receive_can_battery(CAN_frame rx_frame) {
CELL_ID_U_MAX = ((rx_frame.data.u8[4] & 0x01) * 256.0 + rx_frame.data.u8[5]);
else {
CELL_ID_U_MAX = 0;
#ifdef DEBUG_VIA_USB
Serial.println("CELL_ID_U_MAX not valid");
#ifdef DEBUG_LOG
logging.println("CELL_ID_U_MAX not valid");
#endif
}
break;

View file

@ -101,8 +101,8 @@ void receive_can_charger(CAN_frame rx_frame) {
case 0x308:
break;
default:
#ifdef DEBUG_VIA_USB
Serial.printf("CAN Rcv unknown frame MsgID=%x\n", rx_frame.MsgID);
#ifdef DEBUG_LOG
logging.printf("CAN Rcv unknown frame MsgID=%x\n", rx_frame.MsgID);
#endif
break;
}
@ -177,15 +177,15 @@ void send_can_charger() {
transmit_can(&charger_set_targets, can_config.charger);
}
#ifdef DEBUG_VIA_USB
#ifdef DEBUG_LOG
/* Serial echo every 5s of charger stats */
if (currentMillis - previousMillis5000ms >= INTERVAL_5_S) {
previousMillis5000ms = currentMillis;
Serial.printf("Charger AC in IAC=%fA VAC=%fV\n", charger_stat_ACcur, charger_stat_ACvol);
Serial.printf("Charger HV out IDC=%fA VDC=%fV\n", charger_stat_HVcur, charger_stat_HVvol);
Serial.printf("Charger LV out IDC=%fA VDC=%fV\n", charger_stat_LVcur, charger_stat_LVvol);
Serial.printf("Charger mode=%s\n", (charger_mode > MODE_DISABLED) ? "Enabled" : "Disabled");
Serial.printf("Charger HVset=%uV,%uA finishCurrent=%uA\n", setpoint_HV_VDC, setpoint_HV_IDC, setpoint_HV_IDC_END);
logging.printf("Charger AC in IAC=%fA VAC=%fV\n", charger_stat_ACcur, charger_stat_ACvol);
logging.printf("Charger HV out IDC=%fA VDC=%fV\n", charger_stat_HVcur, charger_stat_HVvol);
logging.printf("Charger LV out IDC=%fA VDC=%fV\n", charger_stat_LVcur, charger_stat_LVvol);
logging.printf("Charger mode=%s\n", (charger_mode > MODE_DISABLED) ? "Enabled" : "Disabled");
logging.printf("Charger HVset=%uV,%uA finishCurrent=%uA\n", setpoint_HV_VDC, setpoint_HV_IDC, setpoint_HV_IDC_END);
}
#endif
}

View file

@ -0,0 +1,328 @@
#include "comm_can.h"
#include "../../include.h"
// Parameters
CAN_device_t CAN_cfg; // CAN Config
const int rx_queue_size = 10; // Receive Queue size
volatile bool send_ok = 0;
#ifdef CAN_ADDON
static const uint32_t QUARTZ_FREQUENCY = CRYSTAL_FREQUENCY_MHZ * 1000000UL; //MHZ configured in USER_SETTINGS.h
SPIClass SPI2515;
ACAN2515 can(MCP2515_CS, SPI2515, MCP2515_INT);
static ACAN2515_Buffer16 gBuffer;
#endif //CAN_ADDON
#ifdef CANFD_ADDON
SPIClass SPI2517;
ACAN2517FD canfd(MCP2517_CS, SPI2517, MCP2517_INT);
#endif //CANFD_ADDON
// Initialization functions
void init_CAN() {
// CAN pins
#ifdef CAN_SE_PIN
pinMode(CAN_SE_PIN, OUTPUT);
digitalWrite(CAN_SE_PIN, LOW);
#endif // CAN_SE_PIN
CAN_cfg.speed = CAN_SPEED_500KBPS;
#ifdef NATIVECAN_250KBPS // Some component is requesting lower CAN speed
CAN_cfg.speed = CAN_SPEED_250KBPS;
#endif // NATIVECAN_250KBPS
CAN_cfg.tx_pin_id = CAN_TX_PIN;
CAN_cfg.rx_pin_id = CAN_RX_PIN;
CAN_cfg.rx_queue = xQueueCreate(rx_queue_size, sizeof(CAN_frame_t));
// Init CAN Module
ESP32Can.CANInit();
#ifdef CAN_ADDON
#ifdef DEBUG_LOG
logging.println("Dual CAN Bus (ESP32+MCP2515) selected");
#endif // DEBUG_LOG
gBuffer.initWithSize(25);
SPI2515.begin(MCP2515_SCK, MCP2515_MISO, MCP2515_MOSI);
ACAN2515Settings settings2515(QUARTZ_FREQUENCY, 500UL * 1000UL); // CAN bit rate 500 kb/s
settings2515.mRequestedMode = ACAN2515Settings::NormalMode;
const uint16_t errorCode2515 = can.begin(settings2515, [] { can.isr(); });
if (errorCode2515 == 0) {
#ifdef DEBUG_LOG
logging.println("Can ok");
#endif // DEBUG_LOG
} else {
#ifdef DEBUG_LOG
logging.print("Error Can: 0x");
logging.println(errorCode2515, HEX);
#endif // DEBUG_LOG
set_event(EVENT_CANMCP2515_INIT_FAILURE, (uint8_t)errorCode2515);
}
#endif // CAN_ADDON
#ifdef CANFD_ADDON
#ifdef DEBUG_LOG
logging.println("CAN FD add-on (ESP32+MCP2517) selected");
#endif // DEBUG_LOG
SPI2517.begin(MCP2517_SCK, MCP2517_SDO, MCP2517_SDI);
ACAN2517FDSettings settings2517(CANFD_ADDON_CRYSTAL_FREQUENCY_MHZ, 500 * 1000,
DataBitRateFactor::x4); // Arbitration bit rate: 500 kbit/s, data bit rate: 2 Mbit/s
#ifdef USE_CANFD_INTERFACE_AS_CLASSIC_CAN
settings2517.mRequestedMode = ACAN2517FDSettings::Normal20B; // ListenOnly / Normal20B / NormalFD
#else // not USE_CANFD_INTERFACE_AS_CLASSIC_CAN
settings2517.mRequestedMode = ACAN2517FDSettings::NormalFD; // ListenOnly / Normal20B / NormalFD
#endif // USE_CANFD_INTERFACE_AS_CLASSIC_CAN
const uint32_t errorCode2517 = canfd.begin(settings2517, [] { canfd.isr(); });
canfd.poll();
if (errorCode2517 == 0) {
#ifdef DEBUG_LOG
logging.print("Bit Rate prescaler: ");
logging.println(settings2517.mBitRatePrescaler);
logging.print("Arbitration Phase segment 1: ");
logging.print(settings2517.mArbitrationPhaseSegment1);
logging.print(" segment 2: ");
logging.print(settings2517.mArbitrationPhaseSegment2);
logging.print(" SJW: ");
logging.println(settings2517.mArbitrationSJW);
logging.print("Actual Arbitration Bit Rate: ");
logging.print(settings2517.actualArbitrationBitRate());
logging.print(" bit/s");
logging.print(" (Exact:");
logging.println(settings2517.exactArbitrationBitRate() ? "yes)" : "no)");
logging.print("Arbitration Sample point: ");
logging.print(settings2517.arbitrationSamplePointFromBitStart());
logging.println("%");
#endif // DEBUG_LOG
} else {
#ifdef DEBUG_LOG
logging.print("CAN-FD Configuration error 0x");
logging.println(errorCode2517, HEX);
#endif // DEBUG_LOG
set_event(EVENT_CANMCP2517FD_INIT_FAILURE, (uint8_t)errorCode2517);
}
#endif // CANFD_ADDON
}
// Transmit functions
void transmit_can(CAN_frame* tx_frame, int interface) {
if (!allowed_to_send_CAN) {
return;
}
print_can_frame(*tx_frame, frameDirection(MSG_TX));
switch (interface) {
case CAN_NATIVE:
CAN_frame_t frame;
frame.MsgID = tx_frame->ID;
frame.FIR.B.FF = tx_frame->ext_ID ? CAN_frame_ext : CAN_frame_std;
frame.FIR.B.DLC = tx_frame->DLC;
frame.FIR.B.RTR = CAN_no_RTR;
for (uint8_t i = 0; i < tx_frame->DLC; i++) {
frame.data.u8[i] = tx_frame->data.u8[i];
}
ESP32Can.CANWriteFrame(&frame);
break;
case CAN_ADDON_MCP2515: {
#ifdef CAN_ADDON
//Struct with ACAN2515 library format, needed to use the MCP2515 library for CAN2
CANMessage MCP2515Frame;
MCP2515Frame.id = tx_frame->ID;
MCP2515Frame.ext = tx_frame->ext_ID ? CAN_frame_ext : CAN_frame_std;
MCP2515Frame.len = tx_frame->DLC;
MCP2515Frame.rtr = false;
for (uint8_t i = 0; i < MCP2515Frame.len; i++) {
MCP2515Frame.data[i] = tx_frame->data.u8[i];
}
can.tryToSend(MCP2515Frame);
#else // Interface not compiled, and settings try to use it
set_event(EVENT_INTERFACE_MISSING, interface);
#endif //CAN_ADDON
} break;
case CANFD_NATIVE:
case CANFD_ADDON_MCP2518: {
#ifdef CANFD_ADDON
CANFDMessage MCP2518Frame;
if (tx_frame->FD) {
MCP2518Frame.type = CANFDMessage::CANFD_WITH_BIT_RATE_SWITCH;
} else { //Classic CAN message
MCP2518Frame.type = CANFDMessage::CAN_DATA;
}
MCP2518Frame.id = tx_frame->ID;
MCP2518Frame.ext = tx_frame->ext_ID ? CAN_frame_ext : CAN_frame_std;
MCP2518Frame.len = tx_frame->DLC;
for (uint8_t i = 0; i < MCP2518Frame.len; i++) {
MCP2518Frame.data[i] = tx_frame->data.u8[i];
}
send_ok = canfd.tryToSend(MCP2518Frame);
if (!send_ok) {
set_event(EVENT_CANFD_BUFFER_FULL, interface);
} else {
clear_event(EVENT_CANFD_BUFFER_FULL);
}
#else // Interface not compiled, and settings try to use it
set_event(EVENT_INTERFACE_MISSING, interface);
#endif //CANFD_ADDON
} break;
default:
// Invalid interface sent with function call. TODO: Raise event that coders messed up
break;
}
}
void send_can() {
if (!allowed_to_send_CAN) {
return;
}
send_can_battery();
#ifdef CAN_INVERTER_SELECTED
send_can_inverter();
#endif // CAN_INVERTER_SELECTED
#ifdef CHARGER_SELECTED
send_can_charger();
#endif // CHARGER_SELECTED
}
// Receive functions
void receive_can(CAN_frame* rx_frame, int interface) {
print_can_frame(*rx_frame, frameDirection(MSG_RX));
if (interface == can_config.battery) {
receive_can_battery(*rx_frame);
#ifdef CHADEMO_BATTERY
ISA_handleFrame(rx_frame);
#endif
}
if (interface == can_config.inverter) {
#ifdef CAN_INVERTER_SELECTED
receive_can_inverter(*rx_frame);
#endif
}
if (interface == can_config.battery_double) {
#ifdef DOUBLE_BATTERY
receive_can_battery2(*rx_frame);
#endif
}
if (interface == can_config.charger) {
#ifdef CHARGER_SELECTED
receive_can_charger(*rx_frame);
#endif
}
}
void receive_can_native() { // This section checks if we have a complete CAN message incoming on native CAN port
CAN_frame_t rx_frame_native;
if (xQueueReceive(CAN_cfg.rx_queue, &rx_frame_native, 0) == pdTRUE) {
CAN_frame rx_frame;
rx_frame.ID = rx_frame_native.MsgID;
if (rx_frame_native.FIR.B.FF == CAN_frame_std) {
rx_frame.ext_ID = false;
} else { //CAN_frame_ext == 1
rx_frame.ext_ID = true;
}
rx_frame.DLC = rx_frame_native.FIR.B.DLC;
for (uint8_t i = 0; i < rx_frame.DLC && i < 8; i++) {
rx_frame.data.u8[i] = rx_frame_native.data.u8[i];
}
//message incoming, pass it on to the handler
receive_can(&rx_frame, CAN_NATIVE);
}
}
#ifdef CAN_ADDON
void receive_can_addon() { // This section checks if we have a complete CAN message incoming on add-on CAN port
CAN_frame rx_frame; // Struct with our CAN format
CANMessage MCP2515Frame; // Struct with ACAN2515 library format, needed to use the MCP2515 library
if (can.available()) {
can.receive(MCP2515Frame);
rx_frame.ID = MCP2515Frame.id;
rx_frame.ext_ID = MCP2515Frame.ext ? CAN_frame_ext : CAN_frame_std;
rx_frame.DLC = MCP2515Frame.len;
for (uint8_t i = 0; i < MCP2515Frame.len && i < 8; i++) {
rx_frame.data.u8[i] = MCP2515Frame.data[i];
}
//message incoming, pass it on to the handler
receive_can(&rx_frame, CAN_ADDON_MCP2515);
}
}
#endif // CAN_ADDON
#ifdef CANFD_ADDON
// Functions
void receive_canfd_addon() { // This section checks if we have a complete CAN-FD message incoming
CANFDMessage frame;
int count = 0;
while (canfd.available() && count++ < 16) {
canfd.receive(frame);
CAN_frame rx_frame;
rx_frame.ID = frame.id;
rx_frame.ext_ID = frame.ext;
rx_frame.DLC = frame.len;
memcpy(rx_frame.data.u8, frame.data, MIN(rx_frame.DLC, 64));
//message incoming, pass it on to the handler
receive_can(&rx_frame, CANFD_ADDON_MCP2518);
receive_can(&rx_frame, CANFD_NATIVE);
}
}
#endif // CANFD_ADDON
// Support functions
void print_can_frame(CAN_frame frame, frameDirection msgDir) {
#ifdef DEBUG_CAN_DATA // If enabled in user settings, print out the CAN messages via USB
uint8_t i = 0;
Serial.print("(");
Serial.print(millis() / 1000.0);
(msgDir == MSG_RX) ? Serial.print(") RX0 ") : Serial.print(") TX1 ");
Serial.print(frame.ID, HEX);
Serial.print(" [");
Serial.print(frame.DLC);
Serial.print("] ");
for (i = 0; i < frame.DLC; i++) {
Serial.print(frame.data.u8[i] < 16 ? "0" : "");
Serial.print(frame.data.u8[i], HEX);
if (i < frame.DLC - 1)
Serial.print(" ");
}
Serial.println("");
#endif // DEBUG_CAN_DATA
if (datalayer.system.info.can_logging_active) { // If user clicked on CAN Logging page in webserver, start recording
char* message_string = datalayer.system.info.logged_can_messages;
int offset = datalayer.system.info.logged_can_messages_offset; // Keeps track of the current position in the buffer
size_t message_string_size = sizeof(datalayer.system.info.logged_can_messages);
if (offset + 128 > sizeof(datalayer.system.info.logged_can_messages)) {
// Not enough space, reset and start from the beginning
offset = 0;
}
unsigned long currentTime = millis();
// Add timestamp
offset += snprintf(message_string + offset, message_string_size - offset, "(%lu.%03lu) ", currentTime / 1000,
currentTime % 1000);
// Add direction. The 0 and 1 after RX and TX ensures that SavvyCAN puts TX and RX in a different bus.
offset +=
snprintf(message_string + offset, message_string_size - offset, "%s ", (msgDir == MSG_RX) ? "RX0" : "TX1");
// Add ID and DLC
offset += snprintf(message_string + offset, message_string_size - offset, "%X [%u] ", frame.ID, frame.DLC);
// Add data bytes
for (uint8_t i = 0; i < frame.DLC; i++) {
if (i < frame.DLC - 1) {
offset += snprintf(message_string + offset, message_string_size - offset, "%02X ", frame.data.u8[i]);
} else {
offset += snprintf(message_string + offset, message_string_size - offset, "%02X", frame.data.u8[i]);
}
}
// Add linebreak
offset += snprintf(message_string + offset, message_string_size - offset, "\n");
datalayer.system.info.logged_can_messages_offset = offset; // Update offset in buffer
}
}

View file

@ -0,0 +1,93 @@
#ifndef _COMM_CAN_H_
#define _COMM_CAN_H_
#include "../../include.h"
#include "../../datalayer/datalayer.h"
#include "../../devboard/utils/events.h"
#include "../../devboard/utils/value_mapping.h"
#include "../../lib/me-no-dev-ESPAsyncWebServer/src/ESPAsyncWebServer.h"
#include "../../lib/miwagner-ESP32-Arduino-CAN/ESP32CAN.h"
#ifdef CAN_ADDON
#include "../../lib/pierremolinaro-acan2515/ACAN2515.h"
#endif //CAN_ADDON
#ifdef CANFD_ADDON
#include "../../lib/pierremolinaro-ACAN2517FD/ACAN2517FD.h"
#endif //CANFD_ADDON
enum frameDirection { MSG_RX, MSG_TX }; //RX = 0, TX = 1
/**
* @brief Initialization function for CAN.
*
* @param[in] void
*
* @return void
*/
void init_CAN();
/**
* @brief Transmit one CAN frame
*
* @param[in] CAN_frame* tx_frame
* @param[in] int interface
*
* @return void
*/
void transmit_can();
/**
* @brief Send CAN messages to all components
*
* @param[in] void
*
* @return void
*/
void send_can();
/**
* @brief Receive CAN messages from all interfaces
*
* @param[in] void
*
* @return void
*/
void receive_can();
/**
* @brief Receive CAN messages from CAN tranceiver natively installed on Lilygo hardware
*
* @param[in] void
*
* @return void
*/
void receive_can_native();
/**
* @brief Receive CAN messages from CAN addon chip
*
* @param[in] void
*
* @return void
*/
void receive_can_addon();
/**
* @brief Receive CAN messages from CANFD addon chip
*
* @param[in] void
*
* @return void
*/
void receive_canfd_addon();
/**
* @brief print CAN frames via USB
*
* @param[in] void
*
* @return void
*/
void print_can_frame(CAN_frame frame, frameDirection msgDir);
#endif

View file

@ -0,0 +1,204 @@
#include "comm_contactorcontrol.h"
#include "../../include.h"
// Parameters
#ifndef CONTACTOR_CONTROL
#ifdef PWM_CONTACTOR_CONTROL
#error CONTACTOR_CONTROL needs to be enabled for PWM_CONTACTOR_CONTROL
#endif
#endif
#ifdef CONTACTOR_CONTROL
enum State { DISCONNECTED, START_PRECHARGE, PRECHARGE, POSITIVE, PRECHARGE_OFF, COMPLETED, SHUTDOWN_REQUESTED };
State contactorStatus = DISCONNECTED;
#define ON 1
#define OFF 0
#ifdef NC_CONTACTORS //Normally closed contactors use inverted logic
#undef ON
#define ON 0
#undef OFF
#define OFF 1
#endif //NC_CONTACTORS
#define MAX_ALLOWED_FAULT_TICKS 1000
#define NEGATIVE_CONTACTOR_TIME_MS \
500 // Time after negative contactor is turned on, to start precharge (not actual precharge time!)
#define PRECHARGE_COMPLETED_TIME_MS \
1000 // After successful precharge, resistor is turned off after this delay (and contactors are economized if PWM enabled)
#define PWM_Freq 20000 // 20 kHz frequency, beyond audible range
#define PWM_Res 10 // 10 Bit resolution 0 to 1023, maps 'nicely' to 0% 100%
#define PWM_HOLD_DUTY 250
#define PWM_OFF_DUTY 0
#define PWM_ON_DUTY 1023
#define PWM_Positive_Channel 0
#define PWM_Negative_Channel 1
unsigned long prechargeStartTime = 0;
unsigned long negativeStartTime = 0;
unsigned long prechargeCompletedTime = 0;
unsigned long timeSpentInFaultedMode = 0;
#endif
void set(uint8_t pin, bool direction, uint32_t pwm_freq = 0xFFFF) {
#ifdef PWM_CONTACTOR_CONTROL
if (pwm_freq != 0xFFFF) {
ledcWrite(pin, pwm_freq);
return;
}
#endif
if (direction == 1) {
digitalWrite(pin, HIGH);
} else { // 0
digitalWrite(pin, LOW);
}
}
// Initialization functions
void init_contactors() {
// Init contactor pins
#ifdef CONTACTOR_CONTROL
#ifdef PWM_CONTACTOR_CONTROL
// Setup PWM Channel Frequency and Resolution
ledcAttachChannel(POSITIVE_CONTACTOR_PIN, PWM_Freq, PWM_Res, PWM_Positive_Channel);
ledcAttachChannel(NEGATIVE_CONTACTOR_PIN, PWM_Freq, PWM_Res, PWM_Negative_Channel);
// Set all pins OFF (0% PWM)
ledcWrite(POSITIVE_CONTACTOR_PIN, PWM_OFF_DUTY);
ledcWrite(NEGATIVE_CONTACTOR_PIN, PWM_OFF_DUTY);
#else //Normal CONTACTOR_CONTROL
pinMode(POSITIVE_CONTACTOR_PIN, OUTPUT);
set(POSITIVE_CONTACTOR_PIN, OFF);
pinMode(NEGATIVE_CONTACTOR_PIN, OUTPUT);
set(NEGATIVE_CONTACTOR_PIN, OFF);
#endif // Precharge never has PWM regardless of setting
pinMode(PRECHARGE_PIN, OUTPUT);
set(PRECHARGE_PIN, OFF);
#endif // CONTACTOR_CONTROL
#ifdef CONTACTOR_CONTROL_DOUBLE_BATTERY
pinMode(SECOND_POSITIVE_CONTACTOR_PIN, OUTPUT);
set(SECOND_POSITIVE_CONTACTOR_PIN, OFF);
pinMode(SECOND_NEGATIVE_CONTACTOR_PIN, OUTPUT);
set(SECOND_NEGATIVE_CONTACTOR_PIN, OFF);
#endif // CONTACTOR_CONTROL_DOUBLE_BATTERY
// Init BMS contactor
#ifdef HW_STARK // TODO: Rewrite this so LilyGo can also handle this BMS contactor
pinMode(BMS_POWER, OUTPUT);
digitalWrite(BMS_POWER, HIGH);
#endif // HW_STARK
}
// Main functions
void handle_contactors() {
#ifdef BYD_SMA
datalayer.system.status.inverter_allows_contactor_closing = digitalRead(INVERTER_CONTACTOR_ENABLE_PIN);
#endif // BYD_SMA
#ifdef CONTACTOR_CONTROL_DOUBLE_BATTERY
handle_contactors_battery2();
#endif // CONTACTOR_CONTROL_DOUBLE_BATTERY
#ifdef CONTACTOR_CONTROL
// First check if we have any active errors, incase we do, turn off the battery
if (datalayer.battery.status.bms_status == FAULT) {
timeSpentInFaultedMode++;
} else {
timeSpentInFaultedMode = 0;
}
//handle contactor control SHUTDOWN_REQUESTED
if (timeSpentInFaultedMode > MAX_ALLOWED_FAULT_TICKS) {
contactorStatus = SHUTDOWN_REQUESTED;
}
if (contactorStatus == SHUTDOWN_REQUESTED) {
set(PRECHARGE_PIN, OFF);
set(NEGATIVE_CONTACTOR_PIN, OFF, PWM_OFF_DUTY);
set(POSITIVE_CONTACTOR_PIN, OFF, PWM_OFF_DUTY);
set_event(EVENT_ERROR_OPEN_CONTACTOR, 0);
datalayer.system.status.contactors_engaged = false;
return; // A fault scenario latches the contactor control. It is not possible to recover without a powercycle (and investigation why fault occured)
}
// After that, check if we are OK to start turning on the battery
if (contactorStatus == DISCONNECTED) {
set(PRECHARGE_PIN, OFF);
set(NEGATIVE_CONTACTOR_PIN, OFF, PWM_OFF_DUTY);
set(POSITIVE_CONTACTOR_PIN, OFF, PWM_OFF_DUTY);
if (datalayer.system.status.battery_allows_contactor_closing &&
datalayer.system.status.inverter_allows_contactor_closing && !datalayer.system.settings.equipment_stop_active) {
contactorStatus = START_PRECHARGE;
}
}
// In case the inverter requests contactors to open, set the state accordingly
if (contactorStatus == COMPLETED) {
//Incase inverter (or estop) requests contactors to open, make state machine jump to Disconnected state (recoverable)
if (!datalayer.system.status.inverter_allows_contactor_closing || datalayer.system.settings.equipment_stop_active) {
contactorStatus = DISCONNECTED;
}
// Skip running the state machine below if it has already completed
return;
}
unsigned long currentTime = millis();
if (currentTime < INTERVAL_10_S) {
// Skip running the state machine before system has started up.
// Gives the system some time to detect any faults from battery before blindly just engaging the contactors
return;
}
// Handle actual state machine. This first turns on Negative, then Precharge, then Positive, and finally turns OFF precharge
switch (contactorStatus) {
case START_PRECHARGE:
set(NEGATIVE_CONTACTOR_PIN, ON, PWM_ON_DUTY);
prechargeStartTime = currentTime;
contactorStatus = PRECHARGE;
break;
case PRECHARGE:
if (currentTime - prechargeStartTime >= NEGATIVE_CONTACTOR_TIME_MS) {
set(PRECHARGE_PIN, ON);
negativeStartTime = currentTime;
contactorStatus = POSITIVE;
}
break;
case POSITIVE:
if (currentTime - negativeStartTime >= PRECHARGE_TIME_MS) {
set(POSITIVE_CONTACTOR_PIN, ON, PWM_ON_DUTY);
prechargeCompletedTime = currentTime;
contactorStatus = PRECHARGE_OFF;
}
break;
case PRECHARGE_OFF:
if (currentTime - prechargeCompletedTime >= PRECHARGE_COMPLETED_TIME_MS) {
set(PRECHARGE_PIN, OFF);
set(NEGATIVE_CONTACTOR_PIN, ON, PWM_HOLD_DUTY);
set(POSITIVE_CONTACTOR_PIN, ON, PWM_HOLD_DUTY);
contactorStatus = COMPLETED;
datalayer.system.status.contactors_engaged = true;
}
break;
default:
break;
}
#endif // CONTACTOR_CONTROL
}
#ifdef CONTACTOR_CONTROL_DOUBLE_BATTERY
void handle_contactors_battery2() {
if ((contactorStatus == COMPLETED) && datalayer.system.status.battery2_allows_contactor_closing) {
set(SECOND_NEGATIVE_CONTACTOR_PIN, ON);
set(SECOND_POSITIVE_CONTACTOR_PIN, ON);
datalayer.system.status.contactors_battery2_engaged = true;
} else { // Closing contactors on secondary battery not allowed
set(SECOND_NEGATIVE_CONTACTOR_PIN, OFF);
set(SECOND_POSITIVE_CONTACTOR_PIN, OFF);
datalayer.system.status.contactors_battery2_engaged = false;
}
}
#endif // CONTACTOR_CONTROL_DOUBLE_BATTERY

View file

@ -0,0 +1,36 @@
#ifndef _COMM_CONTACTORCONTROL_H_
#define _COMM_CONTACTORCONTROL_H_
#include "../../include.h"
#include "../../datalayer/datalayer.h"
#include "../../devboard/utils/events.h"
/**
* @brief Contactor initialization
*
* @param[in] void
*
* @return void
*/
void init_contactors();
/**
* @brief Handle contactors
*
* @param[in] void
*
* @return void
*/
void handle_contactors();
/**
* @brief Handle contactors of battery 2
*
* @param[in] void
*
* @return void
*/
void handle_contactors_battery2();
#endif

View file

@ -0,0 +1,51 @@
#include "comm_equipmentstopbutton.h"
#include "../../include.h"
// Parameters
#ifdef EQUIPMENT_STOP_BUTTON
const unsigned long equipment_button_long_press_duration =
15000; // 15 seconds for long press in case of MOMENTARY_SWITCH
const unsigned long equipment_button_debounce_duration = 200; // 200ms for debouncing the button
unsigned long timeSincePress = 0; // Variable to store the time since the last press
DebouncedButton equipment_stop_button; // Debounced button object
#endif // EQUIPMENT_STOP_BUTTON
// Initialization functions
#ifdef EQUIPMENT_STOP_BUTTON
void init_equipment_stop_button() {
//using external pullup resistors NC
pinMode(EQUIPMENT_STOP_PIN, INPUT);
// Initialize the debounced button with NC switch type and equipment_button_debounce_duration debounce time
initDebouncedButton(equipment_stop_button, EQUIPMENT_STOP_PIN, NC, equipment_button_debounce_duration);
}
#endif // EQUIPMENT_STOP_BUTTON
// Main functions
#ifdef EQUIPMENT_STOP_BUTTON
void monitor_equipment_stop_button() {
ButtonState changed_state = debounceButton(equipment_stop_button, timeSincePress);
if (equipment_stop_behavior == LATCHING_SWITCH) {
if (changed_state == PRESSED) {
// Changed to ON initiating equipment stop.
setBatteryPause(true, false, true);
} else if (changed_state == RELEASED) {
// Changed to OFF ending equipment stop.
setBatteryPause(false, false, false);
}
} else if (equipment_stop_behavior == MOMENTARY_SWITCH) {
if (changed_state == RELEASED) { // button is released
if (timeSincePress < equipment_button_long_press_duration) {
// Short press detected, trigger equipment stop
setBatteryPause(true, false, true);
} else {
// Long press detected, reset equipment stop state
setBatteryPause(false, false, false);
}
}
}
}
#endif // EQUIPMENT_STOP_BUTTON

View file

@ -0,0 +1,28 @@
#ifndef _COMM_EQUIPMENTSTOPBUTTON_H_
#define _COMM_EQUIPMENTSTOPBUTTON_H_
#include "../../include.h"
#ifdef EQUIPMENT_STOP_BUTTON
#include "../../devboard/utils/debounce_button.h"
#endif
/**
* @brief Initialization of equipment stop button
*
* @param[in] void
*
* @return void
*/
void init_equipment_stop_button();
/**
* @brief Monitor equipment stop button
*
* @param[in] void
*
* @return void
*/
void monitor_equipment_stop_button();
#endif

View file

@ -0,0 +1,125 @@
#include "comm_nvm.h"
#include "../../include.h"
// Parameters
Preferences settings; // Store user settings
// Initialization functions
void init_stored_settings() {
static uint32_t temp = 0;
// ATTENTION ! The maximum length for settings keys is 15 characters
settings.begin("batterySettings", false);
// Always get the equipment stop status
datalayer.system.settings.equipment_stop_active = settings.getBool("EQUIPMENT_STOP", false);
if (datalayer.system.settings.equipment_stop_active) {
set_event(EVENT_EQUIPMENT_STOP, 1);
}
#ifndef LOAD_SAVED_SETTINGS_ON_BOOT
settings.clear(); // If this clear function is executed, no settings will be read from storage
//always save the equipment stop status
settings.putBool("EQUIPMENT_STOP", datalayer.system.settings.equipment_stop_active);
#endif // LOAD_SAVED_SETTINGS_ON_BOOT
#ifdef WIFI
char tempSSIDstring[63]; // Allocate buffer with sufficient size
size_t lengthSSID = settings.getString("SSID", tempSSIDstring, sizeof(tempSSIDstring));
if (lengthSSID > 0) { // Successfully read the string from memory. Set it to SSID!
ssid = tempSSIDstring;
} else { // Reading from settings failed. Do nothing with SSID. Raise event?
}
char tempPasswordString[63]; // Allocate buffer with sufficient size
size_t lengthPassword = settings.getString("PASSWORD", tempPasswordString, sizeof(tempPasswordString));
if (lengthPassword > 7) { // Successfully read the string from memory. Set it to password!
password = tempPasswordString;
} else { // Reading from settings failed. Do nothing with SSID. Raise event?
}
#endif // WIFI
temp = settings.getUInt("BATTERY_WH_MAX", false);
if (temp != 0) {
datalayer.battery.info.total_capacity_Wh = temp;
}
temp = settings.getUInt("MAXPERCENTAGE", false);
if (temp != 0) {
datalayer.battery.settings.max_percentage = temp * 10; // Multiply by 10 for backwards compatibility
}
temp = settings.getUInt("MINPERCENTAGE", false);
if (temp != 0) {
datalayer.battery.settings.min_percentage = temp * 10; // Multiply by 10 for backwards compatibility
}
temp = settings.getUInt("MAXCHARGEAMP", false);
if (temp != 0) {
datalayer.battery.settings.max_user_set_charge_dA = temp;
}
temp = settings.getUInt("MAXDISCHARGEAMP", false);
if (temp != 0) {
datalayer.battery.settings.max_user_set_discharge_dA = temp;
}
datalayer.battery.settings.soc_scaling_active = settings.getBool("USE_SCALED_SOC", false);
temp = settings.getUInt("TARGETCHVOLT", false);
if (temp != 0) {
datalayer.battery.settings.max_user_set_charge_voltage_dV = temp;
}
temp = settings.getUInt("TARGETDISCHVOLT", false);
if (temp != 0) {
datalayer.battery.settings.max_user_set_discharge_voltage_dV = temp;
}
datalayer.battery.settings.user_set_voltage_limits_active = settings.getBool("USEVOLTLIMITS", false);
settings.end();
}
void store_settings_equipment_stop() {
settings.begin("batterySettings", false);
settings.putBool("EQUIPMENT_STOP", datalayer.system.settings.equipment_stop_active);
settings.end();
}
void store_settings() {
// ATTENTION ! The maximum length for settings keys is 15 characters
if (!settings.begin("batterySettings", false)) {
set_event(EVENT_PERSISTENT_SAVE_INFO, 0);
return;
}
#ifdef WIFI
if (!settings.putString("SSID", String(ssid.c_str()))) {
set_event(EVENT_PERSISTENT_SAVE_INFO, 1);
}
if (!settings.putString("PASSWORD", String(password.c_str()))) {
set_event(EVENT_PERSISTENT_SAVE_INFO, 2);
}
#endif
if (!settings.putUInt("BATTERY_WH_MAX", datalayer.battery.info.total_capacity_Wh)) {
set_event(EVENT_PERSISTENT_SAVE_INFO, 3);
}
if (!settings.putBool("USE_SCALED_SOC", datalayer.battery.settings.soc_scaling_active)) {
set_event(EVENT_PERSISTENT_SAVE_INFO, 4);
}
if (!settings.putUInt("MAXPERCENTAGE", datalayer.battery.settings.max_percentage / 10)) {
set_event(EVENT_PERSISTENT_SAVE_INFO, 5);
}
if (!settings.putUInt("MINPERCENTAGE", datalayer.battery.settings.min_percentage / 10)) {
set_event(EVENT_PERSISTENT_SAVE_INFO, 6);
}
if (!settings.putUInt("MAXCHARGEAMP", datalayer.battery.settings.max_user_set_charge_dA)) {
set_event(EVENT_PERSISTENT_SAVE_INFO, 7);
}
if (!settings.putUInt("MAXDISCHARGEAMP", datalayer.battery.settings.max_user_set_discharge_dA)) {
set_event(EVENT_PERSISTENT_SAVE_INFO, 8);
}
if (!settings.putBool("USEVOLTLIMITS", datalayer.battery.settings.user_set_voltage_limits_active)) {
set_event(EVENT_PERSISTENT_SAVE_INFO, 9);
}
if (!settings.putUInt("TARGETCHVOLT", datalayer.battery.settings.max_user_set_charge_voltage_dV)) {
set_event(EVENT_PERSISTENT_SAVE_INFO, 10);
}
if (!settings.putUInt("TARGETDISCHVOLT", datalayer.battery.settings.max_user_set_discharge_voltage_dV)) {
set_event(EVENT_PERSISTENT_SAVE_INFO, 11);
}
settings.end(); // Close preferences handle
}

View file

@ -0,0 +1,37 @@
#ifndef _COMM_NVM_H_
#define _COMM_NVM_H_
#include "../../include.h"
#include "../../datalayer/datalayer.h"
#include "../../devboard/utils/events.h"
#include "../../devboard/wifi/wifi.h"
/**
* @brief Initialization of setting storage
*
* @param[in] void
*
* @return void
*/
void init_stored_settings();
/**
* @brief Store settings of equipment stop button
*
* @param[in] void
*
* @return void
*/
void store_settings_equipment_stop();
/**
* @brief Store settings
*
* @param[in] void
*
* @return void
*/
void store_settings();
#endif

View file

@ -0,0 +1,51 @@
#include "comm_rs485.h"
#include "../../include.h"
// Parameters
#ifdef MODBUS_INVERTER_SELECTED
#define MB_RTU_NUM_VALUES 13100
uint16_t mbPV[MB_RTU_NUM_VALUES]; // Process variable memory
// Create a ModbusRTU server instance listening on Serial2 with 2000ms timeout
ModbusServerRTU MBserver(Serial2, 2000);
#endif
#if defined(SERIAL_LINK_RECEIVER) || defined(SERIAL_LINK_TRANSMITTER)
#define SERIAL_LINK_BAUDRATE 112500
#endif
// Initialization functions
void init_rs485() {
// Set up Modbus RTU Server
#ifdef RS485_EN_PIN
pinMode(RS485_EN_PIN, OUTPUT);
digitalWrite(RS485_EN_PIN, HIGH);
#endif // RS485_EN_PIN
#ifdef RS485_SE_PIN
pinMode(RS485_SE_PIN, OUTPUT);
digitalWrite(RS485_SE_PIN, HIGH);
#endif // RS485_SE_PIN
#ifdef PIN_5V_EN
pinMode(PIN_5V_EN, OUTPUT);
digitalWrite(PIN_5V_EN, HIGH);
#endif // PIN_5V_EN
#ifdef RS485_INVERTER_SELECTED
Serial2.begin(57600, SERIAL_8N1, RS485_RX_PIN, RS485_TX_PIN);
#endif // RS485_INVERTER_SELECTED
#ifdef MODBUS_INVERTER_SELECTED
#ifdef BYD_MODBUS
// Init Static data to the RTU Modbus
handle_static_data_modbus_byd();
#endif // BYD_MODBUS
// Init Serial2 connected to the RTU Modbus
RTUutils::prepareHardwareSerial(Serial2);
Serial2.begin(9600, SERIAL_8N1, RS485_RX_PIN, RS485_TX_PIN);
// Register served function code worker for server
MBserver.registerWorker(MBTCP_ID, READ_HOLD_REGISTER, &FC03);
MBserver.registerWorker(MBTCP_ID, WRITE_HOLD_REGISTER, &FC06);
MBserver.registerWorker(MBTCP_ID, WRITE_MULT_REGISTERS, &FC16);
MBserver.registerWorker(MBTCP_ID, R_W_MULT_REGISTERS, &FC23);
// Start ModbusRTU background task
MBserver.begin(Serial2, MODBUS_CORE);
#endif // MODBUS_INVERTER_SELECTED
}

View file

@ -0,0 +1,19 @@
#ifndef _COMM_RS485_H_
#define _COMM_RS485_H_
#include "../../include.h"
#include "../../lib/eModbus-eModbus/Logging.h"
#include "../../lib/eModbus-eModbus/ModbusServerRTU.h"
#include "../../lib/eModbus-eModbus/scripts/mbServerFCs.h"
/**
* @brief Initialization of RS485
*
* @param[in] void
*
* @return void
*/
void init_rs485();
#endif

View file

@ -0,0 +1,35 @@
#include "comm_seriallink.h"
#include "../../include.h"
// Parameters
#if defined(SERIAL_LINK_RECEIVER) || defined(SERIAL_LINK_TRANSMITTER)
#define SERIAL_LINK_BAUDRATE 112500
#endif
// Initialization functions
void init_serialDataLink() {
#if defined(SERIAL_LINK_RECEIVER) || defined(SERIAL_LINK_TRANSMITTER)
Serial2.begin(SERIAL_LINK_BAUDRATE, SERIAL_8N1, RS485_RX_PIN, RS485_TX_PIN);
#endif // SERIAL_LINK_RECEIVER || SERIAL_LINK_TRANSMITTER
}
// Main functions
#if defined(SERIAL_LINK_RECEIVER) || defined(SERIAL_LINK_TRANSMITTER)
void run_serialDataLink() {
static unsigned long updateTime = 0;
unsigned long currentMillis = millis();
if ((currentMillis - updateTime) > 1) { //Every 2ms
updateTime = currentMillis;
#ifdef SERIAL_LINK_RECEIVER
manageSerialLinkReceiver();
#endif
#ifdef SERIAL_LINK_TRANSMITTER
manageSerialLinkTransmitter();
#endif
}
}
#endif // SERIAL_LINK_RECEIVER || SERIAL_LINK_TRANSMITTER

View file

@ -0,0 +1,17 @@
#ifndef _COMM_SERIALLINK_H_
#define _COMM_SERIALLINK_H_
#include "../../include.h"
/**
* @brief Initialization of serial data link
*
* @param[in] void
*
* @return void
*/
void init_serialDataLink();
void run_serialDataLink();
#endif

View file

@ -107,10 +107,20 @@ typedef struct {
* you want the inverter to be able to use. At this real SOC, the inverter
* will "see" 100% */
uint16_t max_percentage = BATTERY_MAXPERCENTAGE;
/** The user specified maximum allowed charge rate, in deciAmpere. 300 = 30.0 A */
uint16_t max_user_set_charge_dA = BATTERY_MAX_CHARGE_AMP;
/** The user specified maximum allowed discharge rate, in deciAmpere. 300 = 30.0 A */
uint16_t max_user_set_discharge_dA = BATTERY_MAX_DISCHARGE_AMP;
/** User specified discharge/charge voltages in use. Set to true to use user specified values */
/** Some inverters like to see a specific target voltage for charge/discharge. Use these values to override automatic voltage limits*/
bool user_set_voltage_limits_active = BATTERY_USE_VOLTAGE_LIMITS;
/** The user specified maximum allowed charge voltage, in deciVolt. 4000 = 400.0 V */
uint16_t max_user_set_charge_voltage_dV = BATTERY_MAX_CHARGE_VOLTAGE;
/** The user specified maximum allowed discharge voltage, in deciVolt. 3000 = 300.0 V */
uint16_t max_user_set_discharge_voltage_dV = BATTERY_MAX_DISCHARGE_VOLTAGE;
} DATALAYER_BATTERY_SETTINGS_TYPE;
typedef struct {
@ -131,6 +141,12 @@ typedef struct {
char battery_protocol[64] = {0};
/** array with type of inverter used, for displaying on webserver */
char inverter_protocol[64] = {0};
/** array with incoming CAN messages, for displaying on webserver */
char logged_can_messages[15000] = {0};
size_t logged_can_messages_offset = 0;
/** bool, determines if CAN messages should be logged for webserver */
bool can_logging_active = false;
} DATALAYER_SYSTEM_INFO_TYPE;
typedef struct {

View file

@ -3,6 +3,42 @@
#include "../include.h"
typedef struct {
/** uint16_t */
/** PID polling parameters */
uint16_t battery_5V_ref = 0;
int16_t battery_module_temp_1 = 0;
int16_t battery_module_temp_2 = 0;
int16_t battery_module_temp_3 = 0;
int16_t battery_module_temp_4 = 0;
int16_t battery_module_temp_5 = 0;
int16_t battery_module_temp_6 = 0;
uint16_t battery_cell_average_voltage = 0;
uint16_t battery_cell_average_voltage_2 = 0;
uint16_t battery_terminal_voltage = 0;
uint16_t battery_ignition_power_mode = 0;
int16_t battery_current_7E7 = 0;
uint16_t battery_capacity_my17_18 = 0;
uint16_t battery_capacity_my19plus = 0;
uint16_t battery_SOC_display = 0;
uint16_t battery_SOC_raw_highprec = 0;
uint16_t battery_max_temperature = 0;
uint16_t battery_min_temperature = 0;
uint16_t battery_max_cell_voltage = 0;
uint16_t battery_min_cell_voltage = 0;
uint16_t battery_lowest_cell = 0;
uint16_t battery_highest_cell = 0;
uint16_t battery_internal_resistance = 0;
uint16_t battery_voltage_polled = 0;
uint16_t battery_vehicle_isolation = 0;
uint16_t battery_isolation_kohm = 0;
uint16_t battery_HV_locked = 0;
uint16_t battery_crash_event = 0;
uint16_t battery_HVIL = 0;
uint16_t battery_HVIL_status = 0;
int16_t battery_current_7E4 = 0;
} DATALAYER_INFO_BOLTAMPERA;
typedef struct {
/** uint16_t */
/** Terminal 30 - 12V SME Supply Voltage */
@ -179,11 +215,179 @@ typedef struct {
uint8_t packCtrsClosingAllowed = 0;
/** uint8_t */
/** Pyro test in progress */
uint8_t pyroTestInProgress = 0;
bool pyroTestInProgress = false;
bool battery_packCtrsOpenNowRequested = false;
bool battery_packCtrsOpenRequested = false;
uint8_t battery_packCtrsRequestStatus = 0;
bool battery_packCtrsResetRequestRequired = false;
bool battery_dcLinkAllowedToEnergize = false;
uint8_t battery_beginning_of_life = 0;
uint8_t battery_battTempPct = 0;
uint16_t battery_dcdcLvBusVolt = 0;
uint16_t battery_dcdcHvBusVolt = 0;
uint16_t battery_dcdcLvOutputCurrent = 0;
uint16_t battery_nominal_full_pack_energy = 0;
uint16_t battery_nominal_full_pack_energy_m0 = 0;
uint16_t battery_nominal_energy_remaining = 0;
uint16_t battery_nominal_energy_remaining_m0 = 0;
uint16_t battery_ideal_energy_remaining = 0;
uint16_t battery_ideal_energy_remaining_m0 = 0;
uint16_t battery_energy_to_charge_complete = 0;
uint16_t battery_energy_to_charge_complete_m1 = 0;
uint16_t battery_energy_buffer = 0;
uint16_t battery_energy_buffer_m1 = 0;
uint16_t battery_expected_energy_remaining = 0;
uint16_t battery_expected_energy_remaining_m1 = 0;
bool battery_full_charge_complete = false;
bool battery_fully_charged = false;
uint16_t battery_total_discharge = 0;
uint16_t battery_total_charge = 0;
uint16_t battery_BrickVoltageMax = 0;
uint16_t battery_BrickVoltageMin = 0;
uint8_t battery_BrickVoltageMaxNum = 0;
uint8_t battery_BrickVoltageMinNum = 0;
uint8_t battery_BrickTempMaxNum = 0;
uint8_t battery_BrickTempMinNum = 0;
uint8_t battery_BrickModelTMax = 0;
uint8_t battery_BrickModelTMin = 0;
uint16_t battery_packConfigMultiplexer = 0;
uint16_t battery_moduleType = 0;
uint16_t battery_reservedConfig = 0;
uint32_t battery_packMass = 0;
uint32_t battery_platformMaxBusVoltage = 0;
uint32_t battery_bms_min_voltage = 0;
uint32_t battery_bms_max_voltage = 0;
uint32_t battery_max_charge_current = 0;
uint32_t battery_max_discharge_current = 0;
uint32_t battery_soc_min = 0;
uint32_t battery_soc_max = 0;
uint32_t battery_soc_ave = 0;
uint32_t battery_soc_ui = 0;
uint8_t battery_BMS_contactorState = 0;
uint8_t battery_BMS_state = 0;
uint8_t battery_BMS_hvState = 0;
uint16_t battery_BMS_isolationResistance = 0;
uint8_t battery_BMS_uiChargeStatus = 0;
bool battery_BMS_diLimpRequest = false;
uint16_t battery_BMS_chgPowerAvailable = 0;
bool battery_BMS_pcsPwmEnabled = false;
uint8_t battery_PCS_dcdcPrechargeStatus = 0;
uint8_t battery_PCS_dcdc12VSupportStatus = 0;
uint8_t battery_PCS_dcdcHvBusDischargeStatus = 0;
uint8_t battery_PCS_dcdcMainState = 0;
uint8_t battery_PCS_dcdcSubState = 0;
bool battery_PCS_dcdcFaulted = false;
bool battery_PCS_dcdcOutputIsLimited = false;
uint16_t battery_PCS_dcdcMaxOutputCurrentAllowed = 0;
uint8_t battery_PCS_dcdcPrechargeRtyCnt = 0;
uint8_t battery_PCS_dcdc12VSupportRtyCnt = 0;
uint8_t battery_PCS_dcdcDischargeRtyCnt = 0;
uint8_t battery_PCS_dcdcPwmEnableLine = 0;
uint8_t battery_PCS_dcdcSupportingFixedLvTarget = 0;
uint8_t battery_PCS_dcdcPrechargeRestartCnt = 0;
uint8_t battery_PCS_dcdcInitialPrechargeSubState = 0;
uint16_t BMS_maxRegenPower = 0;
uint16_t BMS_maxDischargePower = 0;
uint16_t BMS_maxStationaryHeatPower = 0;
uint16_t BMS_hvacPowerBudget = 0;
uint8_t BMS_notEnoughPowerForHeatPump = 0;
uint8_t BMS_powerLimitState = 0;
uint8_t BMS_inverterTQF = 0;
uint16_t BMS_powerDissipation = 0;
uint8_t BMS_flowRequest = 0;
uint16_t BMS_inletActiveCoolTargetT = 0;
uint16_t BMS_inletPassiveTargetT = 0;
uint16_t BMS_inletActiveHeatTargetT = 0;
uint16_t BMS_packTMin = 0;
uint16_t BMS_packTMax = 0;
bool BMS_pcsNoFlowRequest = false;
bool BMS_noFlowRequest = false;
uint16_t PCS_dcdcTemp = 0;
uint16_t PCS_ambientTemp = 0;
uint16_t PCS_dcdcMaxLvOutputCurrent = 0;
uint16_t PCS_dcdcCurrentLimit = 0;
uint16_t PCS_dcdcLvOutputCurrentTempLimit = 0;
uint16_t PCS_dcdcUnifiedCommand = 0;
uint16_t PCS_dcdcCLAControllerOutput = 0;
uint16_t PCS_dcdcTankVoltage = 0;
uint16_t PCS_dcdcTankVoltageTarget = 0;
uint16_t PCS_dcdcClaCurrentFreq = 0;
uint16_t PCS_dcdcTCommMeasured = 0;
uint16_t PCS_dcdcShortTimeUs = 0;
uint16_t PCS_dcdcHalfPeriodUs = 0;
uint16_t PCS_dcdcIntervalMaxFrequency = 0;
uint16_t PCS_dcdcIntervalMaxHvBusVolt = 0;
uint16_t PCS_dcdcIntervalMaxLvBusVolt = 0;
uint16_t PCS_dcdcIntervalMaxLvOutputCurr = 0;
uint16_t PCS_dcdcIntervalMinFrequency = 0;
uint16_t PCS_dcdcIntervalMinHvBusVolt = 0;
uint16_t PCS_dcdcIntervalMinLvBusVolt = 0;
uint16_t PCS_dcdcIntervalMinLvOutputCurr = 0;
uint32_t PCS_dcdc12vSupportLifetimekWh = 0;
bool HVP_gpioPassivePyroDepl = false;
bool HVP_gpioPyroIsoEn = false;
bool HVP_gpioCpFaultIn = false;
bool HVP_gpioPackContPowerEn = false;
bool HVP_gpioHvCablesOk = false;
bool HVP_gpioHvpSelfEnable = false;
bool HVP_gpioLed = false;
bool HVP_gpioCrashSignal = false;
bool HVP_gpioShuntDataReady = false;
bool HVP_gpioFcContPosAux = false;
bool HVP_gpioFcContNegAux = false;
bool HVP_gpioBmsEout = false;
bool HVP_gpioCpFaultOut = false;
bool HVP_gpioPyroPor = false;
bool HVP_gpioShuntEn = false;
bool HVP_gpioHvpVerEn = false;
bool HVP_gpioPackCoontPosFlywheel = false;
bool HVP_gpioCpLatchEnable = false;
bool HVP_gpioPcsEnable = false;
bool HVP_gpioPcsDcdcPwmEnable = false;
bool HVP_gpioPcsChargePwmEnable = false;
bool HVP_gpioFcContPowerEnable = false;
bool HVP_gpioHvilEnable = false;
bool HVP_gpioSecDrdy = false;
uint16_t HVP_hvp1v5Ref = 0;
uint16_t HVP_shuntCurrentDebug = 0;
bool HVP_packCurrentMia = false;
bool HVP_auxCurrentMia = false;
bool HVP_currentSenseMia = false;
bool HVP_shuntRefVoltageMismatch = false;
bool HVP_shuntThermistorMia = false;
uint8_t HVP_shuntHwMia = 0;
uint16_t HVP_dcLinkVoltage = 0;
uint16_t HVP_packVoltage = 0;
uint16_t HVP_fcLinkVoltage = 0;
uint16_t HVP_packContVoltage = 0;
uint16_t HVP_packNegativeV = 0;
uint16_t HVP_packPositiveV = 0;
uint16_t HVP_pyroAnalog = 0;
uint16_t HVP_dcLinkNegativeV = 0;
uint16_t HVP_dcLinkPositiveV = 0;
uint16_t HVP_fcLinkNegativeV = 0;
uint16_t HVP_fcContCoilCurrent = 0;
uint16_t HVP_fcContVoltage = 0;
uint16_t HVP_hvilInVoltage = 0;
uint16_t HVP_hvilOutVoltage = 0;
uint16_t HVP_fcLinkPositiveV = 0;
uint16_t HVP_packContCoilCurrent = 0;
uint16_t HVP_battery12V = 0;
uint16_t HVP_shuntRefVoltageDbg = 0;
uint16_t HVP_shuntAuxCurrentDbg = 0;
uint16_t HVP_shuntBarTempDbg = 0;
uint16_t HVP_shuntAsicTempDbg = 0;
uint8_t HVP_shuntAuxCurrentStatus = 0;
uint8_t HVP_shuntBarTempStatus = 0;
uint8_t HVP_shuntAsicTempStatus = 0;
} DATALAYER_INFO_TESLA;
typedef struct {
/** uint8_t */
/** Battery info, stores raw HEX values for ASCII chars */
uint8_t BatterySerialNumber[15] = {0};
uint8_t BatteryPartNumber[7] = {0};
uint8_t BMSIDcode[8] = {0};
/** uint8_t */
/** Enum, ZE0 = 0, AZE0 = 1, ZE1 = 2 */
uint8_t LEAF_gen = 0;
@ -247,6 +451,78 @@ typedef struct {
} DATALAYER_INFO_NISSAN_LEAF;
typedef struct {
/** uint8_t */
/** Service disconnect switch status */
bool SDSW = 0;
/** uint8_t */
/** Pilotline status */
bool pilotline = 0;
/** uint8_t */
/** Transportation mode status */
bool transportmode = 0;
/** uint8_t */
/** Componentprotection mode status */
bool componentprotection = 0;
/** uint8_t */
/** Shutdown status */
bool shutdown_active = 0;
/** uint8_t */
/** Battery heating status */
bool battery_heating = 0;
/** uint8_t */
/** All realtime_ warnings have same enumeration, 0 = no fault, 1 = error level 1, 2 error level 2, 3 error level 3 */
uint8_t rt_overcurrent = 0;
uint8_t rt_CAN_fault = 0;
uint8_t rt_overcharge = 0;
uint8_t rt_SOC_high = 0;
uint8_t rt_SOC_low = 0;
uint8_t rt_SOC_jumping = 0;
uint8_t rt_temp_difference = 0;
uint8_t rt_cell_overtemp = 0;
uint8_t rt_cell_undertemp = 0;
uint8_t rt_battery_overvolt = 0;
uint8_t rt_battery_undervol = 0;
uint8_t rt_cell_overvolt = 0;
uint8_t rt_cell_undervol = 0;
uint8_t rt_cell_imbalance = 0;
uint8_t rt_battery_unathorized = 0;
/** uint8_t */
/** HVIL status, 0 = Init, 1 = Closed, 2 = Open!, 3 = Fault */
uint8_t HVIL = 0;
/** uint8_t */
/** 0 = HV inactive, 1 = HV active, 2 = Balancing, 3 = Extern charging, 4 = AC charging, 5 = Battery error, 6 = DC charging, 7 = init */
uint8_t BMS_mode = 0;
/** uint8_t */
/** 1 = Battery display, 4 = Battery display OK, 4 = Display battery charging, 6 = Display battery check, 7 = Fault */
uint8_t battery_diagnostic = 0;
/** uint8_t */
/** 0 = init, 1 = no open HV line detected, 2 = open HV line , 3 = fault */
uint8_t status_HV_line = 0;
/** uint8_t */
/** 0 = OK, 1 = Not OK, 0x06 = init, 0x07 = fault */
uint8_t warning_support = 0;
/** uint32_t */
/** Isolation resistance in kOhm */
uint32_t isolation_resistance = 0;
/** uint8_t */
/** 0=Init, 1=BMS intermediate circuit voltage-free (U_Zwkr < 20V), 2=BMS intermediate circuit not voltage-free (U_Zwkr >/= 25V, hysteresis), 3=Error */
uint8_t BMS_status_voltage_free = 0;
/** uint8_t */
/** 0 Component_IO, 1 Restricted_CompFkt_Isoerror_I, 2 Restricted_CompFkt_Isoerror_II, 3 Restricted_CompFkt_Interlock, 4 Restricted_CompFkt_SD, 5 Restricted_CompFkt_Performance red, 6 = No component function, 7 = Init */
uint8_t BMS_error_status = 0;
/** uint8_t */
/** 0 init, 1 closed, 2 open, 3 fault */
uint8_t BMS_Kl30c_Status = 0;
/** bool */
/** true if BMS requests error/warning light */
bool BMS_OBD_MIL = 0;
bool BMS_error_lamp_req = 0;
bool BMS_warning_lamp_req = 0;
int32_t BMS_voltage_intermediate_dV = 0;
int32_t BMS_voltage_dV = 0;
} DATALAYER_INFO_MEB;
typedef struct {
/** uint16_t */
/** Values WIP*/
@ -295,12 +571,14 @@ typedef struct {
class DataLayerExtended {
public:
DATALAYER_INFO_BOLTAMPERA boltampera;
DATALAYER_INFO_BMWIX bmwix;
DATALAYER_INFO_BMWI3 bmwi3;
DATALAYER_INFO_BYDATTO3 bydAtto3;
DATALAYER_INFO_CELLPOWER cellpower;
DATALAYER_INFO_TESLA tesla;
DATALAYER_INFO_NISSAN_LEAF nissanleaf;
DATALAYER_INFO_MEB meb;
DATALAYER_INFO_ZOE_PH2 zoePH2;
};

View file

@ -26,14 +26,14 @@
// CAN2 defines below
// DUAL_CAN defines
// CAN_ADDON defines
#define MCP2515_SCK 12 // SCK input of MCP2515
#define MCP2515_MOSI 5 // SDI input of MCP2515
#define MCP2515_MISO 34 // SDO output of MCP2515 | Pin 34 is input only, without pullup/down resistors
#define MCP2515_CS 18 // CS input of MCP2515
#define MCP2515_INT 35 // INT output of MCP2515 | | Pin 35 is input only, without pullup/down resistors
// CAN_FD defines
// CANFD_ADDON defines
#define MCP2517_SCK 17 // SCK input of MCP2517
#define MCP2517_SDI 23 // SDI input of MCP2517
#define MCP2517_SDO 39 // SDO output of MCP2517
@ -80,17 +80,17 @@
#endif
#ifdef CHADEMO_BATTERY
#ifdef DUAL_CAN
#error CHADEMO and DUAL_CAN cannot coexist due to overlapping GPIO pin usage
#ifdef CAN_ADDON
#error CHADEMO and CAN_ADDON cannot coexist due to overlapping GPIO pin usage
#endif
#endif
#ifdef EQUIPMENT_STOP_BUTTON
#ifdef DUAL_CAN
#error EQUIPMENT_STOP_BUTTON and DUAL_CAN cannot coexist due to overlapping GPIO pin usage
#ifdef CAN_ADDON
#error EQUIPMENT_STOP_BUTTON and CAN_ADDON cannot coexist due to overlapping GPIO pin usage
#endif
#ifdef CAN_FD
#error EQUIPMENT_STOP_BUTTON and CAN_FD cannot coexist due to overlapping GPIO pin usage
#ifdef CANFD_ADDON
#error EQUIPMENT_STOP_BUTTON and CANFD_ADDON cannot coexist due to overlapping GPIO pin usage
#endif
#ifdef CHADEMO_BATTERY
#error EQUIPMENT_STOP_BUTTON and CHADEMO_BATTERY cannot coexist due to overlapping GPIO pin usage

View file

@ -26,14 +26,14 @@
// CAN2 defines below
// DUAL_CAN defines
// CAN_ADDON defines
#define MCP2515_SCK 12 // SCK input of MCP2515
#define MCP2515_MOSI 5 // SDI input of MCP2515
#define MCP2515_MISO 34 // SDO output of MCP2515 | Pin 34 is input only, without pullup/down resistors
#define MCP2515_CS 18 // CS input of MCP2515
#define MCP2515_INT 35 // INT output of MCP2515 | | Pin 35 is input only, without pullup/down resistors
// CAN_FD defines
// CANFD_ADDON defines
#define MCP2517_SCK 12 // SCK input of MCP2517
#define MCP2517_SDI 5 // SDI input of MCP2517
#define MCP2517_SDO 34 // SDO output of MCP2517
@ -76,17 +76,17 @@
#endif
#ifdef CHADEMO_BATTERY
#ifdef DUAL_CAN
#error CHADEMO and DUAL_CAN cannot coexist due to overlapping GPIO pin usage
#ifdef CAN_ADDON
#error CHADEMO and CAN_ADDON cannot coexist due to overlapping GPIO pin usage
#endif
#endif
#ifdef EQUIPMENT_STOP_BUTTON
#ifdef DUAL_CAN
#error EQUIPMENT_STOP_BUTTON and DUAL_CAN cannot coexist due to overlapping GPIO pin usage
#ifdef CAN_ADDON
#error EQUIPMENT_STOP_BUTTON and CAN_ADDON cannot coexist due to overlapping GPIO pin usage
#endif
#ifdef CAN_FD
#error EQUIPMENT_STOP_BUTTON and CAN_FD cannot coexist due to overlapping GPIO pin usage
#ifdef CANFD_ADDON
#error EQUIPMENT_STOP_BUTTON and CANFD_ADDON cannot coexist due to overlapping GPIO pin usage
#endif
#ifdef CHADEMO_BATTERY
#error EQUIPMENT_STOP_BUTTON and CHADEMO_BATTERY cannot coexist due to overlapping GPIO pin usage

View file

@ -38,7 +38,7 @@ GPIOs on extra header
#define CAN_RX_PIN GPIO_NUM_26
// #define CAN_SE_PIN 23 // (No function, GPIO 23 used instead as MCP_SCK)
// CAN_FD defines
// CANFD_ADDON defines
#define MCP2517_SCK 17 // SCK input of MCP2517
#define MCP2517_SDI 5 // SDI input of MCP2517
#define MCP2517_SDO 34 // SDO output of MCP2517

View file

@ -2,6 +2,7 @@
#include <Arduino.h>
#include <WiFi.h>
#include <freertos/FreeRTOS.h>
#include "../../../USER_SECRETS.h"
#include "../../../USER_SETTINGS.h"
#include "../../battery/BATTERIES.h"
#include "../../datalayer/datalayer.h"
@ -194,9 +195,9 @@ static void publish_common_info(void) {
#endif // DOUBLE_BATTERY
serializeJson(doc, mqtt_msg);
if (!mqtt_publish(state_topic.c_str(), mqtt_msg, false)) {
#ifdef DEBUG_VIA_USB
Serial.println("Common info MQTT msg could not be sent");
#endif // DEBUG_VIA_USB
#ifdef DEBUG_LOG
logging.println("Common info MQTT msg could not be sent");
#endif // DEBUG_LOG
}
doc.clear();
#ifdef HA_AUTODISCOVERY
@ -292,9 +293,9 @@ static void publish_cell_voltages(void) {
serializeJson(doc, mqtt_msg, sizeof(mqtt_msg));
if (!mqtt_publish(state_topic.c_str(), mqtt_msg, false)) {
#ifdef DEBUG_VIA_USB
Serial.println("Cell voltage MQTT msg could not be sent");
#endif // DEBUG_VIA_USB
#ifdef DEBUG_LOG
logging.println("Cell voltage MQTT msg could not be sent");
#endif // DEBUG_LOG
}
doc.clear();
}
@ -312,9 +313,9 @@ static void publish_cell_voltages(void) {
serializeJson(doc, mqtt_msg, sizeof(mqtt_msg));
if (!mqtt_publish(state_topic_2.c_str(), mqtt_msg, false)) {
#ifdef DEBUG_VIA_USB
Serial.println("Cell voltage MQTT msg could not be sent");
#endif // DEBUG_VIA_USB
#ifdef DEBUG_LOG
logging.println("Cell voltage MQTT msg could not be sent");
#endif // DEBUG_LOG
}
doc.clear();
}
@ -384,9 +385,9 @@ void publish_events() {
serializeJson(doc, mqtt_msg);
if (!mqtt_publish(state_topic.c_str(), mqtt_msg, false)) {
#ifdef DEBUG_VIA_USB
Serial.println("Common info MQTT msg could not be sent");
#endif // DEBUG_VIA_USB
#ifdef DEBUG_LOG
logging.println("Common info MQTT msg could not be sent");
#endif // DEBUG_LOG
} else {
set_event_MQTTpublished(event_handle);
}
@ -402,9 +403,9 @@ void publish_events() {
/* If we lose the connection, get it back */
static bool reconnect() {
// attempt one reconnection
#ifdef DEBUG_VIA_USB
Serial.print("Attempting MQTT connection... ");
#endif // DEBUG_VIA_USB
#ifdef DEBUG_LOG
logging.print("Attempting MQTT connection... ");
#endif // DEBUG_LOG
char clientId[64]; // Adjust the size as needed
snprintf(clientId, sizeof(clientId), "BatteryEmulatorClient-%s", WiFi.getHostname());
// Attempt to connect
@ -413,19 +414,19 @@ static bool reconnect() {
clear_event(EVENT_MQTT_DISCONNECT);
set_event(EVENT_MQTT_CONNECT, 0);
reconnectAttempts = 0; // Reset attempts on successful connection
#ifdef DEBUG_VIA_USB
Serial.println("connected");
#endif // DEBUG_VIA_USB
#ifdef DEBUG_LOG
logging.println("connected");
#endif // DEBUG_LOG
clear_event(EVENT_MQTT_CONNECT);
} else {
if (connected_once)
set_event(EVENT_MQTT_DISCONNECT, 0);
reconnectAttempts++; // Count failed attempts
#ifdef DEBUG_VIA_USB
Serial.print("failed, rc=");
Serial.print(client.state());
Serial.println(" try again in 5 seconds");
#endif // DEBUG_VIA_USB
#ifdef DEBUG_LOG
logging.print("failed, rc=");
logging.print(client.state());
logging.println(" try again in 5 seconds");
#endif // DEBUG_LOG
// Wait 5 seconds before retrying
}
return client.connected();
@ -449,9 +450,9 @@ void init_mqtt(void) {
#endif
client.setServer(MQTT_SERVER, MQTT_PORT);
#ifdef DEBUG_VIA_USB
Serial.println("MQTT initialized");
#endif // DEBUG_VIA_USB
#ifdef DEBUG_LOG
logging.println("MQTT initialized");
#endif // DEBUG_LOG
client.setKeepAlive(30); // Increase keepalive to manage network latency better. default is 15
@ -478,8 +479,8 @@ void mqtt_loop(void) {
if (reconnect()) {
lastReconnectAttempt = 0;
} else if (reconnectAttempts >= maxReconnectAttempts) {
#ifdef DEBUG_VIA_USB
Serial.println("Too many failed reconnect attempts, restarting client.");
#ifdef DEBUG_LOG
logging.println("Too many failed reconnect attempts, restarting client.");
#endif
client.disconnect(); // Force close the MQTT client connection
reconnectAttempts = 0; // Reset attempts to avoid infinite loop

View file

@ -97,7 +97,7 @@ void update_machineryprotection() {
clear_event(EVENT_SOH_LOW);
}
#if !defined(PYLON_BATTERY) && !defined(RENAULT_TWIZY_BATTERY)
#ifdef NISSAN_LEAF_BATTERY
// Check if SOC% is plausible
if (datalayer.battery.status.voltage_dV >
(datalayer.battery.info.max_design_voltage_dV -
@ -108,10 +108,11 @@ void update_machineryprotection() {
clear_event(EVENT_SOC_PLAUSIBILITY_ERROR);
}
}
#endif
#endif //NISSAN_LEAF_BATTERY
// Check diff between highest and lowest cell
cell_deviation_mV = (datalayer.battery.status.cell_max_voltage_mV - datalayer.battery.status.cell_min_voltage_mV);
cell_deviation_mV =
std::abs(datalayer.battery.status.cell_max_voltage_mV - datalayer.battery.status.cell_min_voltage_mV);
if (cell_deviation_mV > datalayer.battery.info.max_cell_voltage_deviation_mV) {
set_event(EVENT_CELL_DEVIATION_HIGH, (cell_deviation_mV / 20));
} else {

View file

@ -118,15 +118,15 @@ void init_events(void) {
// Push changes to eeprom
EEPROM.commit();
#ifdef DEBUG_VIA_USB
Serial.println("EEPROM wasn't ready");
#ifdef DEBUG_LOG
logging.println("EEPROM wasn't ready");
#endif
} else {
events.event_log_head_index = EEPROM.readUShort(EE_EVENT_LOG_HEAD_INDEX_ADDRESS);
events.event_log_tail_index = EEPROM.readUShort(EE_EVENT_LOG_TAIL_INDEX_ADDRESS);
#ifdef DEBUG_VIA_USB
Serial.println("EEPROM was initialized for event logging");
Serial.println("head: " + String(events.event_log_head_index) + ", tail: " + String(events.event_log_tail_index));
#ifdef DEBUG_LOG
logging.println("EEPROM was initialized for event logging");
logging.println("head: " + String(events.event_log_head_index) + ", tail: " + String(events.event_log_tail_index));
#endif
print_event_log();
}
@ -140,10 +140,11 @@ void init_events(void) {
events.entries[i].MQTTpublished = false; // Not published by default
}
events.entries[EVENT_CANFD_INIT_FAILURE].level = EVENT_LEVEL_WARNING;
events.entries[EVENT_CANMCP_INIT_FAILURE].level = EVENT_LEVEL_WARNING;
events.entries[EVENT_CANMCP2517FD_INIT_FAILURE].level = EVENT_LEVEL_WARNING;
events.entries[EVENT_CANMCP2515_INIT_FAILURE].level = EVENT_LEVEL_WARNING;
events.entries[EVENT_CANFD_BUFFER_FULL].level = EVENT_LEVEL_WARNING;
events.entries[EVENT_CAN_OVERRUN].level = EVENT_LEVEL_INFO;
events.entries[EVENT_CANFD_RX_OVERRUN].level = EVENT_LEVEL_WARNING;
events.entries[EVENT_CAN_RX_FAILURE].level = EVENT_LEVEL_ERROR;
events.entries[EVENT_CAN2_RX_FAILURE].level = EVENT_LEVEL_WARNING;
events.entries[EVENT_CANFD_RX_FAILURE].level = EVENT_LEVEL_ERROR;
@ -190,6 +191,7 @@ void init_events(void) {
events.entries[EVENT_DUMMY_DEBUG].level = EVENT_LEVEL_DEBUG;
events.entries[EVENT_DUMMY_WARNING].level = EVENT_LEVEL_WARNING;
events.entries[EVENT_DUMMY_ERROR].level = EVENT_LEVEL_ERROR;
events.entries[EVENT_PERSISTENT_SAVE_INFO].level = EVENT_LEVEL_INFO;
events.entries[EVENT_SERIAL_RX_WARNING].level = EVENT_LEVEL_WARNING;
events.entries[EVENT_SERIAL_RX_FAILURE].level = EVENT_LEVEL_ERROR;
events.entries[EVENT_SERIAL_TX_FAILURE].level = EVENT_LEVEL_ERROR;
@ -262,14 +264,16 @@ void set_event_MQTTpublished(EVENTS_ENUM_TYPE event) {
const char* get_event_message_string(EVENTS_ENUM_TYPE event) {
switch (event) {
case EVENT_CANFD_INIT_FAILURE:
case EVENT_CANMCP2517FD_INIT_FAILURE:
return "CAN-FD initialization failed. Check hardware or bitrate settings";
case EVENT_CANMCP_INIT_FAILURE:
case EVENT_CANMCP2515_INIT_FAILURE:
return "CAN-MCP addon initialization failed. Check hardware";
case EVENT_CANFD_BUFFER_FULL:
return "CAN-FD buffer overflowed. Some CAN messages were not sent. Contact developers.";
case EVENT_CAN_OVERRUN:
return "CAN message failed to send within defined time. Contact developers, CPU load might be too high.";
case EVENT_CANFD_RX_OVERRUN:
return "CAN-FD failed to receive all messages from CAN bus. Contact developers, CPU load might be too high.";
case EVENT_CAN_RX_FAILURE:
return "No CAN communication detected for 60s. Shutting down battery control.";
case EVENT_CAN2_RX_FAILURE:
@ -365,6 +369,8 @@ const char* get_event_message_string(EVENTS_ENUM_TYPE event) {
return "The dummy warning event was set!"; // Don't change this event message!
case EVENT_DUMMY_ERROR:
return "The dummy error event was set!"; // Don't change this event message!
case EVENT_PERSISTENT_SAVE_INFO:
return "Info: Failed to save user settings. Namespace full?";
case EVENT_SERIAL_RX_WARNING:
return "Error in serial function: No data received for some time, see data for minutes";
case EVENT_SERIAL_RX_FAILURE:
@ -465,6 +471,10 @@ static void set_event(EVENTS_ENUM_TYPE event, uint8_t data, bool latched) {
if (events.entries[event].log) {
log_event(event, events.entries[event].millisrolloverCount, events.entries[event].timestamp, data);
}
#ifdef DEBUG_LOG
logging.print("Event: ");
logging.println(get_event_message_string(event));
#endif
}
// We should set the event, update event info
@ -478,10 +488,6 @@ static void set_event(EVENTS_ENUM_TYPE event, uint8_t data, bool latched) {
events.level = max(events.level, events.entries[event].level);
update_bms_status();
#ifdef DEBUG_VIA_USB
Serial.println(get_event_message_string(event));
#endif
}
static void update_bms_status(void) {
@ -555,8 +561,8 @@ static void log_event(EVENTS_ENUM_TYPE event, uint8_t millisrolloverCount, uint3
// Store the new indices
EEPROM.writeUShort(EE_EVENT_LOG_HEAD_INDEX_ADDRESS, events.event_log_head_index);
EEPROM.writeUShort(EE_EVENT_LOG_TAIL_INDEX_ADDRESS, events.event_log_tail_index);
//Serial.println("Wrote event " + String(event) + " to " + String(entry_address));
//Serial.println("head: " + String(events.event_log_head_index) + ", tail: " + String(events.event_log_tail_index));
//logging.println("Wrote event " + String(event) + " to " + String(entry_address));
//logging.println("head: " + String(events.event_log_head_index) + ", tail: " + String(events.event_log_tail_index));
// We don't need the exact number, it's just for deciding to store or not
events.nof_logged_events += (events.nof_logged_events < 255) ? 1 : 0;
@ -565,8 +571,8 @@ static void log_event(EVENTS_ENUM_TYPE event, uint8_t millisrolloverCount, uint3
static void print_event_log(void) {
// If the head actually points to the tail, the log is probably blank
if (events.event_log_head_index == events.event_log_tail_index) {
#ifdef DEBUG_VIA_USB
Serial.println("No events in log");
#ifdef DEBUG_LOG
logging.println("No events in log");
#endif
return;
}
@ -582,9 +588,9 @@ static void print_event_log(void) {
// The entry is a blank that has been left behind somehow
continue;
}
#ifdef DEBUG_VIA_USB
Serial.println("Event: " + String(get_event_enum_string(entry.event)) + ", data: " + String(entry.data) +
", time: " + String(entry.timestamp));
#ifdef DEBUG_LOG
logging.println("Event: " + String(get_event_enum_string(entry.event)) + ", data: " + String(entry.data) +
", time: " + String(entry.timestamp));
#endif
if (index == events.event_log_head_index) {
break;

View file

@ -6,7 +6,7 @@
// #define INCLUDE_EVENTS_TEST // Enable to run an event test loop, see events_test_on_target.cpp
#define EE_MAGIC_HEADER_VALUE 0x0017 // 0x0000 to 0xFFFF
#define EE_MAGIC_HEADER_VALUE 0x0018 // 0x0000 to 0xFFFF
#define GENERATE_ENUM(ENUM) ENUM,
#define GENERATE_STRING(STRING) #STRING,
@ -26,10 +26,11 @@
*/
#define EVENTS_ENUM_TYPE(XX) \
XX(EVENT_CANFD_INIT_FAILURE) \
XX(EVENT_CANMCP_INIT_FAILURE) \
XX(EVENT_CANMCP2517FD_INIT_FAILURE) \
XX(EVENT_CANMCP2515_INIT_FAILURE) \
XX(EVENT_CANFD_BUFFER_FULL) \
XX(EVENT_CAN_OVERRUN) \
XX(EVENT_CANFD_RX_OVERRUN) \
XX(EVENT_CAN_RX_FAILURE) \
XX(EVENT_CAN2_RX_FAILURE) \
XX(EVENT_CANFD_RX_FAILURE) \
@ -78,6 +79,7 @@
XX(EVENT_DUMMY_DEBUG) \
XX(EVENT_DUMMY_WARNING) \
XX(EVENT_DUMMY_ERROR) \
XX(EVENT_PERSISTENT_SAVE_INFO) \
XX(EVENT_SERIAL_RX_WARNING) \
XX(EVENT_SERIAL_RX_FAILURE) \
XX(EVENT_SERIAL_TX_FAILURE) \

View file

@ -25,9 +25,9 @@ void run_sequence_on_target(void) {
case ETOT_INIT:
timer.set_interval(10000);
events_test_state = ETOT_FIRST_WAIT;
Serial.println("Events test: initialized");
Serial.print("datalayer.battery.status.bms_status: ");
Serial.println(datalayer.battery.status.bms_status);
logging.println("Events test: initialized");
logging.print("datalayer.battery.status.bms_status: ");
logging.println(datalayer.battery.status.bms_status);
break;
case ETOT_FIRST_WAIT:
if (timer.elapsed()) {
@ -35,9 +35,9 @@ void run_sequence_on_target(void) {
events_test_state = ETOT_INFO;
set_event(EVENT_DUMMY_INFO, 123);
set_event(EVENT_DUMMY_INFO, 234); // 234 should show, occurrence 1
Serial.println("Events test: info event set, data: 234");
Serial.print("datalayer.battery.status.bms_status: ");
Serial.println(datalayer.battery.status.bms_status);
logging.println("Events test: info event set, data: 234");
logging.print("datalayer.battery.status.bms_status: ");
logging.println(datalayer.battery.status.bms_status);
}
break;
case ETOT_INFO:
@ -45,9 +45,9 @@ void run_sequence_on_target(void) {
timer.set_interval(8000);
clear_event(EVENT_DUMMY_INFO);
events_test_state = ETOT_INFO_CLEAR;
Serial.println("Events test : info event cleared");
Serial.print("datalayer.battery.status.bms_status: ");
Serial.println(datalayer.battery.status.bms_status);
logging.println("Events test : info event cleared");
logging.print("datalayer.battery.status.bms_status: ");
logging.println(datalayer.battery.status.bms_status);
}
break;
case ETOT_INFO_CLEAR:
@ -56,9 +56,9 @@ void run_sequence_on_target(void) {
events_test_state = ETOT_DEBUG;
set_event(EVENT_DUMMY_DEBUG, 111);
set_event(EVENT_DUMMY_DEBUG, 222); // 222 should show, occurrence 1
Serial.println("Events test : debug event set, data: 222");
Serial.print("datalayer.battery.status.bms_status: ");
Serial.println(datalayer.battery.status.bms_status);
logging.println("Events test : debug event set, data: 222");
logging.print("datalayer.battery.status.bms_status: ");
logging.println(datalayer.battery.status.bms_status);
}
break;
case ETOT_DEBUG:
@ -66,9 +66,9 @@ void run_sequence_on_target(void) {
timer.set_interval(8000);
clear_event(EVENT_DUMMY_DEBUG);
events_test_state = ETOT_DEBUG_CLEAR;
Serial.println("Events test : info event cleared");
Serial.print("datalayer.battery.status.bms_status: ");
Serial.println(datalayer.battery.status.bms_status);
logging.println("Events test : info event cleared");
logging.print("datalayer.battery.status.bms_status: ");
logging.println(datalayer.battery.status.bms_status);
}
break;
case ETOT_DEBUG_CLEAR:
@ -77,9 +77,9 @@ void run_sequence_on_target(void) {
events_test_state = ETOT_WARNING;
set_event(EVENT_DUMMY_WARNING, 234);
set_event(EVENT_DUMMY_WARNING, 121); // 121 should show, occurrence 1
Serial.println("Events test : warning event set, data: 121");
Serial.print("datalayer.battery.status.bms_status: ");
Serial.println(datalayer.battery.status.bms_status);
logging.println("Events test : warning event set, data: 121");
logging.print("datalayer.battery.status.bms_status: ");
logging.println(datalayer.battery.status.bms_status);
}
break;
case ETOT_WARNING:
@ -87,9 +87,9 @@ void run_sequence_on_target(void) {
timer.set_interval(8000);
clear_event(EVENT_DUMMY_WARNING);
events_test_state = ETOT_WARNING_CLEAR;
Serial.println("Events test : warning event cleared");
Serial.print("datalayer.battery.status.bms_status: ");
Serial.println(datalayer.battery.status.bms_status);
logging.println("Events test : warning event cleared");
logging.print("datalayer.battery.status.bms_status: ");
logging.println(datalayer.battery.status.bms_status);
}
break;
case ETOT_WARNING_CLEAR:
@ -98,9 +98,9 @@ void run_sequence_on_target(void) {
events_test_state = ETOT_ERROR;
set_event(EVENT_DUMMY_ERROR, 221);
set_event(EVENT_DUMMY_ERROR, 133); // 133 should show, occurrence 1
Serial.println("Events test : error event set, data: 133");
Serial.print("datalayer.battery.status.bms_status: ");
Serial.println(datalayer.battery.status.bms_status);
logging.println("Events test : error event set, data: 133");
logging.print("datalayer.battery.status.bms_status: ");
logging.println(datalayer.battery.status.bms_status);
}
break;
case ETOT_ERROR:
@ -108,9 +108,9 @@ void run_sequence_on_target(void) {
timer.set_interval(8000);
clear_event(EVENT_DUMMY_ERROR);
events_test_state = ETOT_ERROR_CLEAR;
Serial.println("Events test : error event cleared");
Serial.print("datalayer.battery.status.bms_status: ");
Serial.println(datalayer.battery.status.bms_status);
logging.println("Events test : error event cleared");
logging.print("datalayer.battery.status.bms_status: ");
logging.println(datalayer.battery.status.bms_status);
}
break;
case ETOT_ERROR_CLEAR:
@ -119,9 +119,9 @@ void run_sequence_on_target(void) {
events_test_state = ETOT_ERROR_LATCHED;
set_event_latched(EVENT_DUMMY_ERROR, 221);
set_event_latched(EVENT_DUMMY_ERROR, 133); // 133 should show, occurrence 1
Serial.println("Events test : latched error event set, data: 133");
Serial.print("datalayer.battery.status.bms_status: ");
Serial.println(datalayer.battery.status.bms_status);
logging.println("Events test : latched error event set, data: 133");
logging.print("datalayer.battery.status.bms_status: ");
logging.println(datalayer.battery.status.bms_status);
}
break;
case ETOT_ERROR_LATCHED:
@ -129,9 +129,9 @@ void run_sequence_on_target(void) {
timer.set_interval(8000);
clear_event(EVENT_DUMMY_ERROR);
events_test_state = ETOT_DONE;
Serial.println("Events test : latched error event cleared?");
Serial.print("datalayer.battery.status.bms_status: ");
Serial.println(datalayer.battery.status.bms_status);
logging.println("Events test : latched error event cleared?");
logging.print("datalayer.battery.status.bms_status: ");
logging.println(datalayer.battery.status.bms_status);
}
break;
case ETOT_DONE:

View file

@ -0,0 +1,86 @@
#include "logging.h"
#include "../../datalayer/datalayer.h"
size_t Logging::write(const uint8_t* buffer, size_t size) {
#ifdef DEBUG_LOG
char* message_string = datalayer.system.info.logged_can_messages;
int offset = datalayer.system.info.logged_can_messages_offset; // Keeps track of the current position in the buffer
size_t message_string_size = sizeof(datalayer.system.info.logged_can_messages);
unsigned long currentTime = millis();
#ifdef DEBUG_VIA_USB
size_t n = 0;
while (size--) {
if (Serial.write(*buffer++))
n++;
else
break;
}
return n;
#endif
#ifdef DEBUG_VIA_WEB
if (datalayer.system.info.can_logging_active) {
return 0;
}
if (offset + size + 13 > sizeof(datalayer.system.info.logged_can_messages)) {
offset = 0;
}
if (buffer[0] != '\r' && buffer[0] != '\n' &&
(offset == 0 || message_string[offset - 1] == '\r' || message_string[offset - 1] == '\n')) {
offset += snprintf(message_string + offset, message_string_size - offset - 1, "%8lu.%03lu ", currentTime / 1000,
currentTime % 1000);
}
memcpy(message_string + offset, buffer, size);
datalayer.system.info.logged_can_messages_offset = offset + size; // Update offset in buffer
return size;
#endif // DEBUG_VIA_WEB
#endif // DEBUG_LOG
return 0;
}
void Logging::printf(const char* fmt, ...) {
#ifdef DEBUG_LOG
char* message_string = datalayer.system.info.logged_can_messages;
int offset = datalayer.system.info.logged_can_messages_offset; // Keeps track of the current position in the buffer
size_t message_string_size = sizeof(datalayer.system.info.logged_can_messages);
#ifdef DEBUG_VIA_USB
static char buf[128];
message_string = buf;
offset = 0;
message_string_size = sizeof(buf);
#endif
#ifdef DEBUG_VIA_WEB
if (datalayer.system.info.can_logging_active) {
return;
}
message_string = datalayer.system.info.logged_can_messages;
offset = datalayer.system.info.logged_can_messages_offset; // Keeps track of the current position in the buffer
message_string_size = sizeof(datalayer.system.info.logged_can_messages);
#endif
if (offset + 128 > sizeof(datalayer.system.info.logged_can_messages)) {
// Not enough space, reset and start from the beginning
offset = 0;
}
unsigned long currentTime = millis();
// Add timestamp
offset += snprintf(message_string + offset, message_string_size - offset - 1, "%8lu.%03lu ", currentTime / 1000,
currentTime % 1000);
va_list(args);
va_start(args, fmt);
offset += vsnprintf(message_string + offset, message_string_size - offset - 1, fmt, args);
va_end(args);
if (datalayer.system.info.can_logging_active) {
size_t size = offset;
size_t n = 0;
while (size--) {
if (Serial.write(*message_string++))
n++;
else
break;
}
} else {
datalayer.system.info.logged_can_messages_offset = offset; // Update offset in buffer
}
#endif // DEBUG_LOG
}

View file

@ -0,0 +1,16 @@
#ifndef __LOGGING_H__
#define __LOGGING_H__
#include <inttypes.h>
#include "Print.h"
class Logging : public Print {
public:
virtual size_t write(const uint8_t* buffer, size_t size);
virtual size_t write(uint8_t) { return 0; }
void printf(const char* fmt, ...);
Logging() {}
};
extern Logging logging;
#endif // __LOGGING_H__

View file

@ -13,7 +13,9 @@ enum led_color { GREEN, YELLOW, RED, BLUE, RGB };
#define INTERVAL_10_MS 10
#define INTERVAL_20_MS 20
#define INTERVAL_30_MS 30
#define INTERVAL_40_MS 40
#define INTERVAL_50_MS 50
#define INTERVAL_70_MS 70
#define INTERVAL_100_MS 100
#define INTERVAL_200_MS 200
#define INTERVAL_250_MS 250

View file

@ -9,6 +9,10 @@ String advanced_battery_processor(const String& var) {
//Page format
content += "<style>";
content += "body { background-color: black; color: white; }";
content +=
"button { background-color: #505E67; color: white; border: none; padding: 10px 20px; margin-bottom: 20px; "
"cursor: pointer; border-radius: 10px; }";
content += "button:hover { background-color: #3A4A52; }";
content += "</style>";
content += "<button onclick='goToMainPage()'>Back to main page</button>";
@ -16,6 +20,44 @@ String advanced_battery_processor(const String& var) {
// Start a new block with a specific background color
content += "<div style='background-color: #303E47; padding: 10px; margin-bottom: 10px;border-radius: 50px'>";
#ifdef BOLT_AMPERA_BATTERY
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>";
#endif //BOLT_AMPERA_BATTERY
#ifdef BMW_IX_BATTERY
content +=
"<h4>Battery Voltage after Contactor: " + String(datalayer_extended.bmwix.battery_voltage_after_contactor) +
@ -285,11 +327,122 @@ String advanced_battery_processor(const String& var) {
#endif //BYD_ATTO_3_BATTERY
#ifdef TESLA_BATTERY
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_extended.tesla.battery_total_discharge);
float total_charge = static_cast<float>(datalayer_extended.tesla.battery_total_charge);
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_BrickTempMinNum) * 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 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)"};
content += "<h4>Contactor Status: " + String(contactorText[datalayer_extended.tesla.status_contactor]) + "</h4>";
static const char* hvilStatusState[] = {"NOT OK",
static const char* hvilStatusState[] = {"NOT Ok",
"STATUS_OK",
"CURRENT_SOURCE_FAULT",
"INTERNAL_OPEN_FAULT",
@ -305,22 +458,311 @@ String advanced_battery_processor(const String& var) {
"UNKNOWN(13)",
"UNKNOWN(14)",
"UNKNOWN(15)"};
content += "<h4>HVIL: " + String(hvilStatusState[datalayer_extended.tesla.hvil_status]) + "</h4>";
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>";
static const char* falseTrue[] = {"False", "True"};
content += "<h4>Closing allowed?: " + String(falseTrue[datalayer_extended.tesla.packCtrsClosingAllowed]) + "</h4>";
content += "<h4>Pyrotest: " + String(falseTrue[datalayer_extended.tesla.pyroTestInProgress]) + "</h4>";
content +=
"<h4>Closing allowed?: " + String(noYes[datalayer_extended.tesla.packCtrsClosingAllowed]) + "</h4>"; //bool
content +=
"<h4>Pyrotest in Progress: " + String(noYes[datalayer_extended.tesla.pyroTestInProgress]) + "</h4>"; //bool
content += "<h4>Contactors Open Now Requested: " +
String(noYes[datalayer_extended.tesla.battery_packCtrsOpenNowRequested]) + "</h4>"; //bool
content +=
"<h4>Contactors Open Requested: " + String(noYes[datalayer_extended.tesla.battery_packCtrsOpenRequested]) +
"</h4>"; //bool
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>"; //bool
content +=
"<h4>DC Link Allowed to Energize: " + String(noYes[datalayer_extended.tesla.battery_dcLinkAllowedToEnergize]) +
"</h4>"; //bool
// Comment what data you would like to dislay, order can be changed.
//0x292 658 BMS_socStates
content += "<h4>Battery Beginning of Life: " + String(beginning_of_life) + " KWh</h4>";
content += "<h4>BattTempPct: " + String(battTempPct) + " </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 SOC UI: " + String(soc_ui) + " </h4>";
//0x2B4 PCS_dcdcRailStatus
content += "<h4>PCS Lv Bus: " + String(dcdcLvBusVolt) + " V</h4>";
content += "<h4>PCS Hv Bus: " + String(dcdcHvBusVolt) + " V</h4>";
content += "<h4>PCS Lv Output: " + String(dcdcLvOutputCurrent) + " A</h4>";
//0x2A4 676 PCS_thermalStatus
content += "<h4>PCS dcdc Temp: " + String(PCS_dcdcTemp) + " DegC</h4>";
content += "<h4>PCS Ambient Temp: " + String(PCS_ambientTemp) + " DegC</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>"; //bool
content += "<h4>Output Is Limited: " + String(Fault[datalayer_extended.tesla.battery_PCS_dcdcOutputIsLimited]) +
"</h4>"; //bool
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>"; //bool
content +=
"<h4>12V Support Rty Cnt: " + String(falseTrue[datalayer_extended.tesla.battery_PCS_dcdc12VSupportRtyCnt]) +
"</h4>"; // bool
content += "<h4>Discharge Rty Cnt: " + String(falseTrue[datalayer_extended.tesla.battery_PCS_dcdcDischargeRtyCnt]) +
"</h4>"; //bool
content += "<h4>PWM Enable Line: " + String(Fault[datalayer_extended.tesla.battery_PCS_dcdcPwmEnableLine]) +
"</h4>"; //bool
content += "<h4>Supporting Fixed LV Target: " +
String(Fault[datalayer_extended.tesla.battery_PCS_dcdcSupportingFixedLvTarget]) + "</h4>"; //bool
content += "<h4>Precharge Restart Cnt: " +
String(falseTrue[datalayer_extended.tesla.battery_PCS_dcdcPrechargeRestartCnt]) + "</h4>"; //bool
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>";
//0x3D2 978 BMS_kwhCounter
content += "<h4>Total Discharge: " + String(total_discharge) + " KWh</h4>";
content += "<h4>Total Charge: " + String(total_charge) + " KWh</h4>";
//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>"; //bool
//0x352 850 BMS_energyStatus
content += "<h3>Early BMS 0x352:</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
content += "<h3>Late BMS 0x352 with 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>"; //bool
//0x392 BMS_packConfig
//content += "<h4>packConfigMultiplexer: " + String(datalayer_extended.tesla.battery_packConfigMultiplexer) + "</h4>";
//content += "<h4>moduleType: " + String(datalayer_extended.tesla.battery_moduleType) + "</h4>";
//content += "<h4>reserveConfig: " + String(datalayer_extended.tesla.battery_reservedConfig) + "</h4>";
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>";
content += "<h4>Brick Model Temp Min: " + String(BrickModelTMin) + " C</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>";
content += "<h4>HVAC Power Budget: " + String(BMS_hvacPowerBudget) + " KW</h4>";
content += "<h4>Not Enough Power For Heat Pump: " +
String(falseTrue[datalayer_extended.tesla.BMS_notEnoughPowerForHeatPump]) + "</h4>"; //bool
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>";
//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>"; //bool
content +=
"<h4>BMS No Flow Request: " + String(Fault[datalayer_extended.tesla.BMS_noFlowRequest]) + "</h4>"; //bool
//0x7AA 1962 HVP_debugMessage
content += "<h4>HVP_gpioPassivePyroDepl: " + String(Fault[datalayer_extended.tesla.HVP_gpioPassivePyroDepl]) +
"</h4>"; //bool
content += "<h4>HVP_gpioPyroIsoEn: " + String(Fault[datalayer_extended.tesla.HVP_gpioPyroIsoEn]) + "</h4>"; //bool
content += "<h4>HVP_gpioCpFaultIn: " + String(Fault[datalayer_extended.tesla.HVP_gpioCpFaultIn]) + "</h4>"; //bool
content += "<h4>HVP_gpioPackContPowerEn: " + String(Fault[datalayer_extended.tesla.HVP_gpioPackContPowerEn]) +
"</h4>"; //bool
content +=
"<h4>HVP_gpioHvCablesOk: " + String(Fault[datalayer_extended.tesla.HVP_gpioHvCablesOk]) + "</h4>"; //bool
content +=
"<h4>HVP_gpioHvpSelfEnable: " + String(Fault[datalayer_extended.tesla.HVP_gpioHvpSelfEnable]) + "</h4>"; //bool
content += "<h4>HVP_gpioLed: " + String(Fault[datalayer_extended.tesla.HVP_gpioLed]) + "</h4>"; //bool
content +=
"<h4>HVP_gpioCrashSignal: " + String(Fault[datalayer_extended.tesla.HVP_gpioCrashSignal]) + "</h4>"; //bool
content += "<h4>HVP_gpioShuntDataReady: " + String(Fault[datalayer_extended.tesla.HVP_gpioShuntDataReady]) +
"</h4>"; //bool
content +=
"<h4>HVP_gpioFcContPosAux: " + String(Fault[datalayer_extended.tesla.HVP_gpioFcContPosAux]) + "</h4>"; //bool
content +=
"<h4>HVP_gpioFcContNegAux: " + String(Fault[datalayer_extended.tesla.HVP_gpioFcContNegAux]) + "</h4>"; //bool
content += "<h4>HVP_gpioBmsEout: " + String(Fault[datalayer_extended.tesla.HVP_gpioBmsEout]) + "</h4>"; //bool
content +=
"<h4>HVP_gpioCpFaultOut: " + String(Fault[datalayer_extended.tesla.HVP_gpioCpFaultOut]) + "</h4>"; //bool
content += "<h4>HVP_gpioPyroPor: " + String(Fault[datalayer_extended.tesla.HVP_gpioPyroPor]) + "</h4>"; //bool
content += "<h4>HVP_gpioShuntEn: " + String(Fault[datalayer_extended.tesla.HVP_gpioShuntEn]) + "</h4>"; //bool
content += "<h4>HVP_gpioHvpVerEn: " + String(Fault[datalayer_extended.tesla.HVP_gpioHvpVerEn]) + "</h4>"; //bool
content +=
"<h4>HVP_gpioPackCoontPosFlywheel: " + String(Fault[datalayer_extended.tesla.HVP_gpioPackCoontPosFlywheel]) +
"</h4>"; //bool
content +=
"<h4>HVP_gpioCpLatchEnable: " + String(Fault[datalayer_extended.tesla.HVP_gpioCpLatchEnable]) + "</h4>"; //bool
content += "<h4>HVP_gpioPcsEnable: " + String(Fault[datalayer_extended.tesla.HVP_gpioPcsEnable]) + "</h4>"; //bool
content += "<h4>HVP_gpioPcsDcdcPwmEnable: " + String(Fault[datalayer_extended.tesla.HVP_gpioPcsDcdcPwmEnable]) +
"</h4>"; //bool
content += "<h4>HVP_gpioPcsChargePwmEnable: " + String(Fault[datalayer_extended.tesla.HVP_gpioPcsChargePwmEnable]) +
"</h4>"; //bool
content += "<h4>HVP_gpioFcContPowerEnable: " + String(Fault[datalayer_extended.tesla.HVP_gpioFcContPowerEnable]) +
"</h4>"; //bool
content +=
"<h4>HVP_gpioHvilEnable: " + String(Fault[datalayer_extended.tesla.HVP_gpioHvilEnable]) + "</h4>"; //bool
content += "<h4>HVP_gpioSecDrdy: " + String(Fault[datalayer_extended.tesla.HVP_gpioSecDrdy]) + "</h4>"; //bool
content += "<h4>HVP_hvp1v5Ref: " + String(HVP_hvp1v5Ref) + " V</h4>";
content += "<h4>HVP_shuntCurrentDebug: " + String(HVP_shuntCurrentDebug) + " A</h4>";
content +=
"<h4>HVP_packCurrentMia: " + String(noYes[datalayer_extended.tesla.HVP_packCurrentMia]) + "</h4>"; //bool
content += "<h4>HVP_auxCurrentMia: " + String(noYes[datalayer_extended.tesla.HVP_auxCurrentMia]) + "</h4>"; //bool
content +=
"<h4>HVP_currentSenseMia: " + String(noYes[datalayer_extended.tesla.HVP_currentSenseMia]) + "</h4>"; //bool
content +=
"<h4>HVP_shuntRefVoltageMismatch: " + String(noYes[datalayer_extended.tesla.HVP_shuntRefVoltageMismatch]) +
"</h4>"; //bool
content += "<h4>HVP_shuntThermistorMia: " + String(noYes[datalayer_extended.tesla.HVP_shuntThermistorMia]) +
"</h4>"; //bool
content += "<h4>HVP_shuntHwMia: " + String(noYes[datalayer_extended.tesla.HVP_shuntHwMia]) + "</h4>"; //bool
content += "<h4>HVP_dcLinkVoltage: " + String(HVP_dcLinkVoltage) + " V</h4>";
content += "<h4>HVP_packVoltage: " + String(HVP_packVoltage) + " V</h4>";
content += "<h4>HVP_fcLinkVoltage: " + String(HVP_fcLinkVoltage) + " V</h4>";
content += "<h4>HVP_packContVoltage: " + String(HVP_packContVoltage) + " V</h4>";
content += "<h4>HVP_packNegativeV: " + String(HVP_packNegativeV) + " V</h4>";
content += "<h4>HVP_packPositiveV: " + String(HVP_packPositiveV) + " V</h4>";
content += "<h4>HVP_pyroAnalog: " + String(HVP_pyroAnalog) + " V</h4>";
content += "<h4>HVP_dcLinkNegativeV: " + String(HVP_dcLinkNegativeV) + " V</h4>";
content += "<h4>HVP_dcLinkPositiveV: " + String(HVP_dcLinkPositiveV) + " V</h4>";
content += "<h4>HVP_fcLinkNegativeV: " + String(HVP_fcLinkNegativeV) + " V</h4>";
content += "<h4>HVP_fcContCoilCurrent: " + String(HVP_fcContCoilCurrent) + " A</h4>";
content += "<h4>HVP_fcContVoltage: " + String(HVP_fcContVoltage) + " V</h4>";
content += "<h4>HVP_hvilInVoltage: " + String(HVP_hvilInVoltage) + " V</h4>";
content += "<h4>HVP_hvilOutVoltage: " + String(HVP_hvilOutVoltage) + " V</h4>";
content += "<h4>HVP_fcLinkPositiveV: " + String(HVP_fcLinkPositiveV) + " V</h4>";
content += "<h4>HVP_packContCoilCurrent: " + String(HVP_packContCoilCurrent) + " A</h4>";
content += "<h4>HVP_battery12V: " + String(HVP_battery12V) + " V</h4>";
content += "<h4>HVP_shuntRefVoltageDbg: " + String(HVP_shuntRefVoltageDbg) + " V</h4>";
content += "<h4>HVP_shuntAuxCurrentDbg: " + String(HVP_shuntAuxCurrentDbg) + " A</h4>";
content += "<h4>HVP_shuntBarTempDbg: " + String(HVP_shuntBarTempDbg) + " DegC</h4>";
content += "<h4>HVP_shuntAsicTempDbg: " + String(HVP_shuntAsicTempDbg) + " DegC</h4>";
content +=
"<h4>HVP_shuntAuxCurrentStatus: " + String(HVP_status[datalayer_extended.tesla.HVP_shuntAuxCurrentStatus]) +
"</h4>";
content +=
"<h4>HVP_shuntBarTempStatus: " + String(HVP_status[datalayer_extended.tesla.HVP_shuntBarTempStatus]) + "</h4>";
content += "<h4>HVP_shuntAsicTempStatus: " + String(HVP_status[datalayer_extended.tesla.HVP_shuntAsicTempStatus]) +
"</h4>";
#endif
#ifdef NISSAN_LEAF_BATTERY
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>";
@ -342,6 +784,205 @@ String advanced_battery_processor(const String& var) {
content += "<h4>Challenge failed: " + String(datalayer_extended.nissanleaf.challengeFailed) + "</h4>";
#endif
#ifdef MEB_BATTERY
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 += "</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><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]) + "</h4>";
content += "<h4>CAN fault: " + String(rt_enum[datalayer_extended.meb.rt_CAN_fault]) + "</h4>";
content += "<h4>Overcharged: " + String(rt_enum[datalayer_extended.meb.rt_overcharge]) + "</h4>";
content += "<h4>SOC too high: " + String(rt_enum[datalayer_extended.meb.rt_SOC_high]) + "</h4>";
content += "<h4>SOC too low: " + String(rt_enum[datalayer_extended.meb.rt_SOC_low]) + "</h4>";
content += "<h4>SOC jumping: " + String(rt_enum[datalayer_extended.meb.rt_SOC_jumping]) + "</h4>";
content += "<h4>Temp difference: " + String(rt_enum[datalayer_extended.meb.rt_temp_difference]) + "</h4>";
content += "<h4>Cell overtemp: " + String(rt_enum[datalayer_extended.meb.rt_cell_overtemp]) + "</h4>";
content += "<h4>Cell undertemp: " + String(rt_enum[datalayer_extended.meb.rt_cell_undertemp]) + "</h4>";
content += "<h4>Battery overvoltage: " + String(rt_enum[datalayer_extended.meb.rt_battery_overvolt]) + "</h4>";
content += "<h4>Battery undervoltage: " + String(rt_enum[datalayer_extended.meb.rt_battery_undervol]) + "</h4>";
content += "<h4>Cell overvoltage: " + String(rt_enum[datalayer_extended.meb.rt_cell_overvolt]) + "</h4>";
content += "<h4>Cell undervoltage: " + String(rt_enum[datalayer_extended.meb.rt_cell_undervol]) + "</h4>";
content += "<h4>Cell imbalance: " + String(rt_enum[datalayer_extended.meb.rt_cell_imbalance]) + "</h4>";
content += "<h4>Battery unathorized: " + String(rt_enum[datalayer_extended.meb.rt_battery_unathorized]) + "</h4>";
#endif //MEB_BATTERY
#ifdef RENAULT_ZOE_GEN2_BATTERY
content += "<h4>soc: " + String(datalayer_extended.zoePH2.battery_soc) + "</h4>";
content += "<h4>usable soc: " + String(datalayer_extended.zoePH2.battery_usable_soc) + "</h4>";
@ -389,8 +1030,10 @@ String advanced_battery_processor(const String& var) {
content += "<h4>soc max: " + String(datalayer_extended.zoePH2.battery_soc_max) + "</h4>";
#endif //RENAULT_ZOE_GEN2_BATTERY
#if !defined(TESLA_BATTERY) && !defined(NISSAN_LEAF_BATTERY) && !defined(BMW_I3_BATTERY) && \
!defined(BYD_ATTO_3_BATTERY) && !defined(RENAULT_ZOE_GEN2_BATTERY) && !defined(CELLPOWER_BMS)
#if !defined(BMW_IX_BATTERY) && !defined(BOLT_AMPERA_BATTERY) && !defined(TESLA_BATTERY) && \
!defined(NISSAN_LEAF_BATTERY) && !defined(BMW_I3_BATTERY) && !defined(BYD_ATTO_3_BATTERY) && \
!defined(RENAULT_ZOE_GEN2_BATTERY) && !defined(CELLPOWER_BMS) && \
!defined(MEB_BATTERY) // Only the listed types have extra info
content += "No extra information available for this battery type";
#endif

View file

@ -0,0 +1,62 @@
#include "can_logging_html.h"
#include <Arduino.h>
#include "../../datalayer/datalayer.h"
String can_logger_processor(const String& var) {
if (var == "X") {
if (!datalayer.system.info.can_logging_active) {
datalayer.system.info.logged_can_messages_offset = 0;
datalayer.system.info.logged_can_messages[0] = '\0';
}
datalayer.system.info.can_logging_active =
true; // Signal to main loop that we should log messages. Disabled by default for performance reasons
String content = "";
// Page format
content += "<style>";
content += "body { background-color: black; color: white; font-family: Arial, sans-serif; }";
content +=
"button { background-color: #505E67; color: white; border: none; padding: 10px 20px; margin-bottom: 20px; "
"cursor: pointer; border-radius: 10px; }";
content += "button:hover { background-color: #3A4A52; }";
content +=
".can-message { background-color: #404E57; margin-bottom: 5px; padding: 10px; border-radius: 5px; font-family: "
"monospace; }";
content += "</style>";
content += "<button onclick='refreshPage()'>Refresh data</button> ";
content += "<button onclick='exportLog()'>Export to .txt</button> ";
content += "<button onclick='stopLoggingAndGoToMainPage()'>Back to main page</button>";
// Start a new block for the CAN messages
content += "<div style='background-color: #303E47; padding: 20px; border-radius: 15px'>";
// Check for messages
if (datalayer.system.info.logged_can_messages[0] == 0) {
content += "CAN logger started! Refresh page to display incoming(RX) and outgoing(TX) messages";
} else {
// Split the messages using the newline character
String messages = String(datalayer.system.info.logged_can_messages);
int startIndex = 0;
int endIndex = messages.indexOf('\n');
while (endIndex != -1) {
// Extract a single message and wrap it in a styled div
String singleMessage = messages.substring(startIndex, endIndex);
content += "<div class='can-message'>" + singleMessage + "</div>";
startIndex = endIndex + 1; // Move past the newline character
endIndex = messages.indexOf('\n', startIndex);
}
}
content += "</div>";
// Add JavaScript for navigation
content += "<script>";
content += "function refreshPage(){ location.reload(true); }";
content += "function exportLog() { window.location.href = '/export_can_log'; }";
content += "function stopLoggingAndGoToMainPage() {";
content += " fetch('/stop_can_logging').then(() => window.location.href = '/');";
content += "}";
content += "</script>";
return content;
}
return String();
}

View file

@ -0,0 +1,16 @@
#ifndef CANLOGGER_H
#define CANLOGGER_H
#include <Arduino.h>
#include <string>
/**
* @brief Replaces placeholder with content section in web page
*
* @param[in] var
*
* @return String
*/
String can_logger_processor(const String& var);
#endif

View file

@ -8,6 +8,10 @@ String cellmonitor_processor(const String& var) {
// Page format
content += "<style>";
content += "body { background-color: black; color: white; }";
content +=
"button { background-color: #505E67; color: white; border: none; padding: 10px 20px; margin-bottom: 20px; "
"cursor: pointer; border-radius: 10px; }";
content += "button:hover { background-color: #3A4A52; }";
content += ".container { display: flex; flex-wrap: wrap; justify-content: space-around; }";
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

View file

@ -0,0 +1,36 @@
#include "debug_logging_html.h"
#include <Arduino.h>
#include "../../datalayer/datalayer.h"
#ifdef DEBUG_VIA_WEB
String debug_logger_processor(const String& var) {
String content = "";
// Page format
content += "<style>";
content += "body { background-color: black; color: white; font-family: Arial, sans-serif; }";
content +=
"button { background-color: #505E67; color: white; border: none; padding: 10px 20px; margin-bottom: 20px; "
"cursor: pointer; border-radius: 10px; }";
content += "button:hover { background-color: #3A4A52; }";
content +=
".can-message { background-color: #404E57; margin-bottom: 5px; padding: 10px; border-radius: 5px; font-family: "
"monospace; }";
content += "</style>";
content += "<button onclick='refreshPage()'>Refresh data</button> ";
content += "<button onclick='exportLog()'>Export to .txt</button> ";
content += "<button onclick='goToMainPage()'>Back to main page</button>";
// Start a new block for the debug log messages
content += "<PRE style='text-align: left'>";
content += String(datalayer.system.info.logged_can_messages);
content += "</PRE>";
// Add JavaScript for navigation
content += "<script>";
content += "function refreshPage(){ location.reload(true); }";
content += "function exportLog() { window.location.href = '/export_log'; }";
content += "function goToMainPage() { window.location.href = '/'; }";
content += "</script>";
return content;
}
#endif // DEBUG_VIA_WEB

View file

@ -0,0 +1,16 @@
#ifndef DEBUGLOGGER_H
#define DEBUGLOGGER_H
#include <Arduino.h>
#include <string>
/**
* @brief Replaces placeholder with content section in web page
*
* @param[in] var
*
* @return String
*/
String debug_logger_processor(const String& var);
#endif

View file

@ -5,6 +5,8 @@ const char EVENTS_HTML_START[] = R"=====(
)=====";
const char EVENTS_HTML_END[] = R"=====(
</div></div>
<style> button { background-color: #505E67; color: white; border: none; padding: 10px 20px; margin-bottom: 20px; cursor: pointer; border-radius: 10px; }
button:hover { background-color: #3A4A52; }</style>
<button onclick="askClear()">Clear all events</button>
<button onclick="home()">Back to main page</button>
<style>.event:nth-child(even){background-color:#455a64}.event:nth-child(odd){background-color:#394b52}</style><script>function showEvent(){document.querySelectorAll(".event").forEach(function(e){var n=e.querySelector(".sec-ago");n&&(n.innerText=new Date(Date.now()-(4294967296*+n.innerText.split(";")[0]+ +n.innerText.split(";")[1])).toLocaleString())})}function askClear(){window.confirm("Are you sure you want to clear all events?")&&(window.location.href="/clearevents")}function home(){window.location.href="/"}window.onload=function(){showEvent()}</script>
@ -37,11 +39,11 @@ String events_processor(const String& var) {
for (const auto& event : order_events) {
EVENTS_ENUM_TYPE event_handle = event.event_handle;
event_pointer = event.event_pointer;
#ifdef DEBUG_VIA_USB
Serial.println("Event: " + String(get_event_enum_string(event_handle)) +
" count: " + String(event_pointer->occurences) + " seconds: " + String(event_pointer->timestamp) +
" data: " + String(event_pointer->data) +
" level: " + String(get_event_level_string(event_handle)));
#ifdef DEBUG_LOG
logging.println("Showing Event: " + String(get_event_enum_string(event_handle)) +
" count: " + String(event_pointer->occurences) + " seconds: " + String(event_pointer->timestamp) +
" data: " + String(event_pointer->data) +
" level: " + String(get_event_level_string(event_handle)));
#endif
content.concat("<div class='event'>");
content.concat("<div>" + String(get_event_enum_string(event_handle)) + "</div>");
@ -58,8 +60,8 @@ String events_processor(const String& var) {
order_events.clear();
content.concat(FPSTR(EVENTS_HTML_END));
return content;
return String();
}
return String();
}
/* Script for displaying event log before it gets minified

View file

@ -8,6 +8,10 @@ String settings_processor(const String& var) {
//Page format
content += "<style>";
content += "body { background-color: black; color: white; }";
content +=
"button { background-color: #505E67; color: white; border: none; padding: 10px 20px; margin-bottom: 20px; "
"cursor: pointer; border-radius: 10px; }";
content += "button:hover { background-color: #3A4A52; }";
content += "</style>";
content += "<button onclick='goToMainPage()'>Back to main page</button>";
@ -63,6 +67,21 @@ String settings_processor(const String& var) {
content += "<h4 style='color: white;'>Max discharge speed: " +
String(datalayer.battery.settings.max_user_set_discharge_dA / 10.0, 1) +
" A </span> <button onclick='editMaxDischargeA()'>Edit</button></h4>";
content += "<h4 style='color: white;'>Manual charge voltage limits: <span id='BATTERY_USE_VOLTAGE_LIMITS'>" +
String(datalayer.battery.settings.user_set_voltage_limits_active
? "<span>&#10003;</span>"
: "<span style='color: red;'>&#10005;</span>") +
"</span> <button onclick='editUseVoltageLimit()'>Edit</button></h4>";
content +=
"<h4 style='color: " +
String(datalayer.battery.settings.user_set_voltage_limits_active ? "white" : "darkgrey") +
";'>Target charge voltage: " + String(datalayer.battery.settings.max_user_set_charge_voltage_dV / 10.0, 1) +
" V </span> <button onclick='editMaxChargeVoltage()'>Edit</button></h4>";
content += "<h4 style='color: " +
String(datalayer.battery.settings.user_set_voltage_limits_active ? "white" : "darkgrey") +
";'>Target discharge voltage: " +
String(datalayer.battery.settings.max_user_set_discharge_voltage_dV / 10.0, 1) +
" V </span> <button onclick='editMaxDischargeVoltage()'>Edit</button></h4>";
// Close the block
content += "</div>";
@ -126,7 +145,9 @@ String settings_processor(const String& var) {
"updateBatterySize?value='+value,true);xhr.send();}else{alert('Invalid value. Please enter a value between 1 "
"and 120000.');}}}";
content +=
"function editUseScaledSOC(){var value=prompt('Should SOC% be scaled? (0 = No, 1 = "
"function editUseScaledSOC(){var value=prompt('Extends battery life by rescaling the SOC within the configured "
"minimum "
"and maximum percentage. Should SOC scaling be applied? (0 = No, 1 = "
"Yes):');if(value!==null){if(value==0||value==1){var xhr=new "
"XMLHttpRequest();xhr.onload=editComplete;xhr.onerror=editError;xhr.open('GET','/"
"updateUseScaledSOC?value='+value,true);xhr.send();}else{alert('Invalid value. Please enter a value between 0 "
@ -157,6 +178,33 @@ String settings_processor(const String& var) {
"XMLHttpRequest();xhr.onload=editComplete;xhr.onerror=editError;xhr.open('GET','/"
"updateMaxDischargeA?value='+value,true);xhr.send();}else{alert('Invalid value. Please enter a value between 0 "
"and 1000.0');}}}";
content +=
"function editUseVoltageLimit(){var value=prompt('Enable this option to manually restrict charge/discharge to "
"a specific voltage set below."
"If disabled the emulator automatically determines this based on battery limits. Restrict manually? (0 = No, 1 "
"= Yes)"
":');if(value!==null){if(value==0||value==1){var xhr=new "
"XMLHttpRequest();xhr.onload=editComplete;xhr.onerror=editError;xhr.open('GET','/"
"updateUseVoltageLimit?value='+value,true);xhr.send();}else{alert('Invalid value. Please enter a value between "
"0 "
"and 1.');}}}";
content +=
"function editMaxChargeVoltage(){var value=prompt('Some inverters needs to be artificially limited. Enter new "
"voltage setpoint batttery should charge to (0-1000.0):');if(value!==null){if(value>=0&&value<=1000){var "
"xhr=new "
"XMLHttpRequest();xhr.onload=editComplete;xhr.onerror=editError;xhr.open('GET','/"
"updateMaxChargeVoltage?value='+value,true);xhr.send();}else{alert('Invalid value. Please enter a value "
"between 0 "
"and 1000.0');}}}";
content +=
"function editMaxDischargeVoltage(){var value=prompt('Some inverters needs to be artificially limited. Enter "
"new "
"voltage setpoint batttery should discharge to (0-1000.0):');if(value!==null){if(value>=0&&value<=1000){var "
"xhr=new "
"XMLHttpRequest();xhr.onload=editComplete;xhr.onerror=editError;xhr.open('GET','/"
"updateMaxDischargeVoltage?value='+value,true);xhr.send();}else{alert('Invalid value. Please enter a value "
"between 0 "
"and 1000.0');}}}";
#ifdef TEST_FAKE_BATTERY
content +=
@ -223,7 +271,7 @@ const char* getCANInterfaceName(CAN_Interface interface) {
#endif
case CAN_ADDON_MCP2515:
return "Add-on CAN via GPIO MCP2515";
case CAN_ADDON_FD_MCP2518:
case CANFD_ADDON_MCP2518:
#ifdef USE_CANFD_INTERFACE_AS_CLASSIC_CAN
return "Add-on CAN-FD via GPIO MCP2518 (Classic CAN)";
#else

View file

@ -1,5 +1,7 @@
#include "webserver.h"
#include <Preferences.h>
#include <ctime>
#include "../../../USER_SECRETS.h"
#include "../../datalayer/datalayer.h"
#include "../../datalayer/datalayer_extended.h"
#include "../../lib/bblanchon-ArduinoJson/ArduinoJson.h"
@ -14,7 +16,9 @@ AsyncWebServer server(80);
unsigned long ota_progress_millis = 0;
#include "advanced_battery_html.h"
#include "can_logging_html.h"
#include "cellmonitor_html.h"
#include "debug_logging_html.h"
#include "events_html.h"
#include "index_html.cpp"
#include "settings_html.h"
@ -56,6 +60,78 @@ void init_webserver() {
request->send_P(200, "text/html", index_html, advanced_battery_processor);
});
// Route for going to CAN logging web page
server.on("/canlog", HTTP_GET, [](AsyncWebServerRequest* request) {
request->send_P(200, "text/html", index_html, can_logger_processor);
});
#ifdef DEBUG_VIA_WEB
// Route for going to debug logging web page
server.on("/log", HTTP_GET, [](AsyncWebServerRequest* request) {
request->send_P(200, "text/html", index_html, debug_logger_processor);
});
#endif // DEBUG_VIA_WEB
// Define the handler to stop can logging
server.on("/stop_can_logging", HTTP_GET, [](AsyncWebServerRequest* request) {
datalayer.system.info.can_logging_active = false;
request->send_P(200, "text/plain", "Logging stopped");
});
// Define the handler to export can log
server.on("/export_can_log", HTTP_GET, [](AsyncWebServerRequest* request) {
String logs = String(datalayer.system.info.logged_can_messages);
if (logs.length() == 0) {
logs = "No logs available.";
}
// Get the current time
time_t now = time(nullptr);
struct tm timeinfo;
localtime_r(&now, &timeinfo);
// Ensure time retrieval was successful
char filename[32];
if (strftime(filename, sizeof(filename), "canlog_%H-%M-%S.txt", &timeinfo)) {
// Valid filename created
} else {
// Fallback filename if automatic timestamping failed
strcpy(filename, "battery_emulator_can_log.txt");
}
// Use request->send with dynamic headers
AsyncWebServerResponse* response = request->beginResponse(200, "text/plain", logs);
response->addHeader("Content-Disposition", String("attachment; filename=\"") + String(filename) + "\"");
request->send(response);
});
// Define the handler to export debug log
server.on("/export_log", HTTP_GET, [](AsyncWebServerRequest* request) {
String logs = String(datalayer.system.info.logged_can_messages);
if (logs.length() == 0) {
logs = "No logs available.";
}
// Get the current time
time_t now = time(nullptr);
struct tm timeinfo;
localtime_r(&now, &timeinfo);
// Ensure time retrieval was successful
char filename[32];
if (strftime(filename, sizeof(filename), "log_%H-%M-%S.txt", &timeinfo)) {
// Valid filename created
} else {
// Fallback filename if automatic timestamping failed
strcpy(filename, "battery_emulator_log.txt");
}
// Use request->send with dynamic headers
AsyncWebServerResponse* response = request->beginResponse(200, "text/plain", logs);
response->addHeader("Content-Disposition", String("attachment; filename=\"") + String(filename) + "\"");
request->send(response);
});
// Route for going to cellmonitor web page
server.on("/cellmonitor", HTTP_GET, [](AsyncWebServerRequest* request) {
if (WEBSERVER_AUTH_REQUIRED && !request->authenticate(http_username, http_password))
@ -91,7 +167,7 @@ void init_webserver() {
String value = request->getParam("value")->value();
if (value.length() <= 63) { // Check if SSID is within the allowable length
ssid = value.c_str();
storeSettings();
store_settings();
request->send(200, "text/plain", "Updated successfully");
} else {
request->send(400, "text/plain", "SSID must be 63 characters or less");
@ -108,7 +184,7 @@ void init_webserver() {
String value = request->getParam("value")->value();
if (value.length() > 8) { // Check if password is within the allowable length
password = value.c_str();
storeSettings();
store_settings();
request->send(200, "text/plain", "Updated successfully");
} else {
request->send(400, "text/plain", "Password must be atleast 8 characters");
@ -125,7 +201,7 @@ void init_webserver() {
if (request->hasParam("value")) {
String value = request->getParam("value")->value();
datalayer.battery.info.total_capacity_Wh = value.toInt();
storeSettings();
store_settings();
request->send(200, "text/plain", "Updated successfully");
} else {
request->send(400, "text/plain", "Bad Request");
@ -139,7 +215,7 @@ void init_webserver() {
if (request->hasParam("value")) {
String value = request->getParam("value")->value();
datalayer.battery.settings.soc_scaling_active = value.toInt();
storeSettings();
store_settings();
request->send(200, "text/plain", "Updated successfully");
} else {
request->send(400, "text/plain", "Bad Request");
@ -153,7 +229,7 @@ void init_webserver() {
if (request->hasParam("value")) {
String value = request->getParam("value")->value();
datalayer.battery.settings.max_percentage = static_cast<uint16_t>(value.toFloat() * 100);
storeSettings();
store_settings();
request->send(200, "text/plain", "Updated successfully");
} else {
request->send(400, "text/plain", "Bad Request");
@ -197,7 +273,7 @@ void init_webserver() {
if (request->hasParam("value")) {
String value = request->getParam("value")->value();
datalayer.battery.settings.min_percentage = static_cast<uint16_t>(value.toFloat() * 100);
storeSettings();
store_settings();
request->send(200, "text/plain", "Updated successfully");
} else {
request->send(400, "text/plain", "Bad Request");
@ -211,7 +287,7 @@ void init_webserver() {
if (request->hasParam("value")) {
String value = request->getParam("value")->value();
datalayer.battery.settings.max_user_set_charge_dA = static_cast<uint16_t>(value.toFloat() * 10);
storeSettings();
store_settings();
request->send(200, "text/plain", "Updated successfully");
} else {
request->send(400, "text/plain", "Bad Request");
@ -225,7 +301,49 @@ void init_webserver() {
if (request->hasParam("value")) {
String value = request->getParam("value")->value();
datalayer.battery.settings.max_user_set_discharge_dA = static_cast<uint16_t>(value.toFloat() * 10);
storeSettings();
store_settings();
request->send(200, "text/plain", "Updated successfully");
} else {
request->send(400, "text/plain", "Bad Request");
}
});
// Route for editing BATTERY_USE_VOLTAGE_LIMITS
server.on("/updateUseVoltageLimit", HTTP_GET, [](AsyncWebServerRequest* request) {
if (WEBSERVER_AUTH_REQUIRED && !request->authenticate(http_username, http_password))
return request->requestAuthentication();
if (request->hasParam("value")) {
String value = request->getParam("value")->value();
datalayer.battery.settings.user_set_voltage_limits_active = value.toInt();
store_settings();
request->send(200, "text/plain", "Updated successfully");
} else {
request->send(400, "text/plain", "Bad Request");
}
});
// Route for editing MaxChargeVoltage
server.on("/updateMaxChargeVoltage", HTTP_GET, [](AsyncWebServerRequest* request) {
if (WEBSERVER_AUTH_REQUIRED && !request->authenticate(http_username, http_password))
return request->requestAuthentication();
if (request->hasParam("value")) {
String value = request->getParam("value")->value();
datalayer.battery.settings.max_user_set_charge_voltage_dV = static_cast<uint16_t>(value.toFloat() * 10);
store_settings();
request->send(200, "text/plain", "Updated successfully");
} else {
request->send(400, "text/plain", "Bad Request");
}
});
// Route for editing MaxDischargeVoltage
server.on("/updateMaxDischargeVoltage", HTTP_GET, [](AsyncWebServerRequest* request) {
if (WEBSERVER_AUTH_REQUIRED && !request->authenticate(http_username, http_password))
return request->requestAuthentication();
if (request->hasParam("value")) {
String value = request->getParam("value")->value();
datalayer.battery.settings.max_user_set_discharge_voltage_dV = static_cast<uint16_t>(value.toFloat() * 10);
store_settings();
request->send(200, "text/plain", "Updated successfully");
} else {
request->send(400, "text/plain", "Bad Request");
@ -443,20 +561,25 @@ String processor(const String& var) {
//Page format
content += "<style>";
content += "body { background-color: black; color: white; }";
content +=
"button { background-color: #505E67; color: white; border: none; padding: 10px 20px; margin-bottom: 20px; "
"cursor: pointer; border-radius: 10px; }";
content += "button:hover { background-color: #3A4A52; }";
content += "</style>";
// Start a new block with a specific background color
content += "<div style='background-color: #303E47; padding: 10px; margin-bottom: 10px;border-radius: 50px'>";
// Show version number
content += "<h4>Software: " + String(version_number) + "</h4>";
content += "<h4>Software: " + String(version_number);
// Show hardware used:
#ifdef HW_LILYGO
content += "<h4>Hardware: LilyGo T-CAN485</h4>";
content += " Hardware: LilyGo T-CAN485";
#endif // HW_LILYGO
#ifdef HW_STARK
content += "<h4>Hardware: Stark CMR Module</h4>";
content += " Hardware: Stark CMR Module";
#endif // HW_STARK
content += "</h4>";
content += "<h4>Uptime: " + uptime_formatter::getUptime() + "</h4>";
#ifdef FUNCTION_TIME_MEASUREMENT
// Load information
@ -480,11 +603,14 @@ String processor(const String& var) {
wl_status_t status = WiFi.status();
// Display ssid of network connected to and, if connected to the WiFi, its own IP
content += "<h4>SSID: " + String(ssid.c_str()) + "</h4>";
content += "<h4>SSID: " + String(ssid.c_str());
if (status == WL_CONNECTED) {
// Get and display the signal strength (RSSI) and channel
content += " RSSI:" + String(WiFi.RSSI()) + " dBm Ch: " + String(WiFi.channel());
}
content += "</h4>";
if (status == WL_CONNECTED) {
content += "<h4>IP: " + WiFi.localIP().toString() + "</h4>";
// Get and display the signal strength (RSSI) and channel
content += "<h4>Signal strength: " + String(WiFi.RSSI()) + " dBm, at channel " + String(WiFi.channel()) + "</h4>";
} else {
content += "<h4>Wifi state: " + getConnectResultString(status) + "</h4>";
}
@ -606,13 +732,30 @@ String processor(const String& var) {
}
content += "<h4>Temperature max: " + String(tempMaxFloat, 1) + " C</h4>";
content += "<h4>Temperature min: " + String(tempMinFloat, 1) + " C</h4>";
if (datalayer.battery.status.bms_status == ACTIVE) {
content += "<h4>System status: OK </h4>";
} else if (datalayer.battery.status.bms_status == UPDATING) {
content += "<h4>System status: UPDATING </h4>";
} else {
content += "<h4>System status: FAULT </h4>";
content += "<h4>System status: ";
switch (datalayer.battery.status.bms_status) {
case ACTIVE:
content += String("OK");
break;
case UPDATING:
content += String("UPDATING");
break;
case FAULT:
content += String("FAULT");
break;
case INACTIVE:
content += String("INACTIVE");
break;
case STANDBY:
content += String("STANDBY");
break;
default:
content += String("??");
break;
}
content += "</h4>";
if (datalayer.battery.status.current_dA == 0) {
content += "<h4>Battery idle</h4>";
} else if (datalayer.battery.status.current_dA < 0) {
@ -641,7 +784,7 @@ String processor(const String& var) {
content += "<h4 style='color: red;'>Power status: " + String(get_emulator_pause_status().c_str()) + " </h4>";
#ifdef CONTACTOR_CONTROL
content += "<h4>Contactors controlled by Battery-Emulator: ";
content += "<h4>Contactors controlled by emulator, state: ";
if (datalayer.system.status.contactors_engaged) {
content += "<span style='color: green;'>ON</span>";
} else {
@ -649,13 +792,21 @@ String processor(const String& var) {
}
content += "</h4>";
content += "<h4>Pre Charge: ";
if (digitalRead(PRECHARGE_PIN) == HIGH) {
content += "<span style='color: green;'>&#10003;</span>";
content += "<h4>Precharge: (";
content += PRECHARGE_TIME_MS;
content += " ms) Cont. Neg.: ";
#ifdef PWM_CONTACTOR_CONTROL
if (datalayer.system.status.contactors_engaged) {
content += "<span style='color: green;'>Economized</span>";
content += " Cont. Pos.: ";
content += "<span style='color: green;'>Economized</span>";
} else {
content += "<span style='color: red;'>&#10005;</span>";
content += " Cont. Pos.: ";
content += "<span style='color: red;'>&#10005;</span>";
}
content += " Cont. Neg.: ";
#else // No PWM_CONTACTOR_CONTROL , we can read the pin and see feedback. Helpful if channel overloaded
if (digitalRead(NEGATIVE_CONTACTOR_PIN) == HIGH) {
content += "<span style='color: green;'>&#10003;</span>";
} else {
@ -668,6 +819,7 @@ String processor(const String& var) {
} else {
content += "<span style='color: red;'>&#10005;</span>";
}
#endif //no PWM_CONTACTOR_CONTROL
content += "</h4>";
#endif
@ -772,7 +924,7 @@ String processor(const String& var) {
content += "<h4 style='color: red;'>Power status: " + String(get_emulator_pause_status().c_str()) + " </h4>";
#ifdef CONTACTOR_CONTROL
content += "<h4>Contactors controlled by Battery-Emulator: ";
content += "<h4>Contactors controlled by emulator, state: ";
if (datalayer.system.status.contactors_battery2_engaged) {
content += "<span style='color: green;'>ON</span>";
} else {
@ -780,13 +932,19 @@ String processor(const String& var) {
}
content += "</h4>";
#ifdef CONTACTOR_CONTROL_DOUBLE_BATTERY
content += "<h4>Pre Charge: ";
if (digitalRead(SECOND_PRECHARGE_PIN) == HIGH) {
content += "<span style='color: green;'>&#10003;</span>";
content += "<h4>Cont. Neg.: ";
#ifdef PWM_CONTACTOR_CONTROL
if (datalayer.system.status.contactors_battery2_engaged) {
content += "<span style='color: green;'>Economized</span>";
content += " Cont. Pos.: ";
content += "<span style='color: green;'>Economized</span>";
} else {
content += "<span style='color: red;'>&#10005;</span>";
content += " Cont. Pos.: ";
content += "<span style='color: red;'>&#10005;</span>";
}
content += " Cont. Neg.: ";
#else // No PWM_CONTACTOR_CONTROL , we can read the pin and see feedback. Helpful if channel overloaded
if (digitalRead(SECOND_NEGATIVE_CONTACTOR_PIN) == HIGH) {
content += "<span style='color: green;'>&#10003;</span>";
} else {
@ -799,6 +957,7 @@ String processor(const String& var) {
} else {
content += "<span style='color: red;'>&#10005;</span>";
}
#endif //no PWM_CONTACTOR_CONTROL
content += "</h4>";
#endif // CONTACTOR_CONTROL_DOUBLE_BATTERY
#endif // CONTACTOR_CONTROL
@ -874,6 +1033,10 @@ String processor(const String& var) {
content += "<button onclick='OTA()'>Perform OTA update</button> ";
content += "<button onclick='Settings()'>Change Settings</button> ";
content += "<button onclick='Advanced()'>More Battery Info</button> ";
content += "<button onclick='CANlog()'>CAN logger</button> ";
#ifdef DEBUG_VIA_WEB
content += "<button onclick='Log()'>Log</button> ";
#endif // DEBUG_VIA_WEB
content += "<button onclick='Cellmon()'>Cellmonitor</button> ";
content += "<button onclick='Events()'>Events</button> ";
content += "<button onclick='askReboot()'>Reboot Emulator</button>";
@ -898,6 +1061,8 @@ String processor(const String& var) {
content += "function Cellmon() { window.location.href = '/cellmonitor'; }";
content += "function Settings() { window.location.href = '/settings'; }";
content += "function Advanced() { window.location.href = '/advanced'; }";
content += "function CANlog() { window.location.href = '/canlog'; }";
content += "function Log() { window.location.href = '/log'; }";
content += "function Events() { window.location.href = '/events'; }";
content +=
"function askReboot() { if (window.confirm('Are you sure you want to reboot the emulator? NOTE: If "
@ -958,9 +1123,9 @@ void onOTAProgress(size_t current, size_t final) {
// Log every 1 second
if (millis() - ota_progress_millis > 1000) {
ota_progress_millis = millis();
#ifdef DEBUG_VIA_USB
Serial.printf("OTA Progress Current: %u bytes, Final: %u bytes\n", current, final);
#endif // DEBUG_VIA_USB
#ifdef DEBUG_LOG
logging.printf("OTA Progress Current: %u bytes, Final: %u bytes\n", current, final);
#endif // DEBUG_LOG
// Reset the "watchdog"
ota_timeout_timer.reset();
}
@ -977,13 +1142,13 @@ void onOTAEnd(bool success) {
// Max Charge/Discharge = 0; CAN = stop; contactors = open
setBatteryPause(true, true, true, false);
// a reboot will be done by the OTA library. no need to do anything here
#ifdef DEBUG_VIA_USB
Serial.println("OTA update finished successfully!");
#endif // DEBUG_VIA_USB
#ifdef DEBUG_LOG
logging.println("OTA update finished successfully!");
#endif // DEBUG_LOG
} else {
#ifdef DEBUG_VIA_USB
Serial.println("There was an error during OTA update!");
#endif // DEBUG_VIA_USB
#ifdef DEBUG_LOG
logging.println("There was an error during OTA update!");
#endif // DEBUG_LOG
//try to Resume the battery pause and CAN communication
setBatteryPause(false, false);
}

View file

@ -104,7 +104,7 @@ void onOTAEnd(bool success);
template <typename T>
String formatPowerValue(String label, T value, String unit, int precision, String color = "white");
extern void storeSettings();
extern void store_settings();
void ota_monitor();

View file

@ -29,12 +29,8 @@ static bool connected_once = false;
void init_WiFi() {
#ifdef WIFIAP
if (AccessPointEnabled) {
WiFi.mode(WIFI_AP_STA); // Simultaneous WiFi AP and Router connection
init_WiFi_AP();
} else {
WiFi.mode(WIFI_STA); // Only Router connection
}
WiFi.mode(WIFI_AP_STA); // Simultaneous WiFi AP and Router connection
init_WiFi_AP();
#else
WiFi.mode(WIFI_STA); // Only Router connection
#endif // WIFIAP
@ -72,28 +68,28 @@ void wifi_monitor() {
// Increase the current check interval if it's not at the maximum
if (current_check_interval + STEP_WIFI_CHECK_INTERVAL <= MAX_STEP_WIFI_CHECK_INTERVAL)
current_check_interval += STEP_WIFI_CHECK_INTERVAL;
#ifdef DEBUG_VIA_USB
Serial.println("Wi-Fi not connected, attempting to reconnect...");
#ifdef DEBUG_LOG
logging.println("Wi-Fi not connected, attempting to reconnect...");
#endif
// Try WiFi.reconnect() if it was successfully connected at least once
if (hasConnectedBefore) {
lastReconnectAttempt = millis(); // Reset reconnection attempt timer
#ifdef DEBUG_VIA_USB
Serial.println("Wi-Fi reconnect attempt...");
#ifdef DEBUG_LOG
logging.println("Wi-Fi reconnect attempt...");
#endif
if (WiFi.reconnect()) {
#ifdef DEBUG_VIA_USB
Serial.println("Wi-Fi reconnect attempt sucess...");
#ifdef DEBUG_LOG
logging.println("Wi-Fi reconnect attempt sucess...");
#endif
reconnectAttempts = 0; // Reset the attempt counter on successful reconnect
} else {
#ifdef DEBUG_VIA_USB
Serial.println("Wi-Fi reconnect attempt error...");
#ifdef DEBUG_LOG
logging.println("Wi-Fi reconnect attempt error...");
#endif
reconnectAttempts++;
if (reconnectAttempts >= MAX_RECONNECT_ATTEMPTS) {
#ifdef DEBUG_VIA_USB
Serial.println("Failed to reconnect multiple times, forcing a full connection attempt...");
#ifdef DEBUG_LOG
logging.println("Failed to reconnect multiple times, forcing a full connection attempt...");
#endif
FullReconnectToWiFi();
}
@ -101,8 +97,8 @@ void wifi_monitor() {
} else {
// If no previous connection, force a full connection attempt
if (currentMillis - lastReconnectAttempt > current_full_reconnect_interval) {
#ifdef DEBUG_VIA_USB
Serial.println("No previous OK connection, force a full connection attempt...");
#ifdef DEBUG_LOG
logging.println("No previous OK connection, force a full connection attempt...");
#endif
FullReconnectToWiFi();
}
@ -127,13 +123,13 @@ static void FullReconnectToWiFi() {
static void connectToWiFi() {
if (WiFi.status() != WL_CONNECTED) {
lastReconnectAttempt = millis(); // Reset the reconnect attempt timer
#ifdef DEBUG_VIA_USB
Serial.println("Connecting to Wi-Fi...");
#ifdef DEBUG_LOG
logging.println("Connecting to Wi-Fi...");
#endif
WiFi.begin(ssid.c_str(), password.c_str(), wifi_channel);
} else {
#ifdef DEBUG_VIA_USB
Serial.println("Wi-Fi already connected.");
#ifdef DEBUG_LOG
logging.println("Wi-Fi already connected.");
#endif
}
}
@ -143,10 +139,11 @@ static void onWifiConnect(WiFiEvent_t event, WiFiEventInfo_t info) {
clear_event(EVENT_WIFI_DISCONNECT);
set_event(EVENT_WIFI_CONNECT, 0);
connected_once = true;
#ifdef DEBUG_VIA_USB
Serial.println("Wi-Fi connected.");
Serial.print("IP address: ");
Serial.println(WiFi.localIP());
#ifdef DEBUG_LOG
logging.print("Wi-Fi connected. RSSI: ");
logging.print(-WiFi.RSSI());
logging.print(" dBm, IP address: ");
logging.println(WiFi.localIP().toString());
#endif
hasConnectedBefore = true; // Mark as successfully connected at least once
reconnectAttempts = 0; // Reset the attempt counter
@ -159,10 +156,10 @@ static void onWifiConnect(WiFiEvent_t event, WiFiEventInfo_t info) {
static void onWifiGotIP(WiFiEvent_t event, WiFiEventInfo_t info) {
//clear disconnects events if we got a IP
clear_event(EVENT_WIFI_DISCONNECT);
#ifdef DEBUG_VIA_USB
Serial.println("Wi-Fi Got IP.");
Serial.print("IP address: ");
Serial.println(WiFi.localIP());
#ifdef DEBUG_LOG
logging.print("Wi-Fi Got IP. ");
logging.print("IP address: ");
logging.println(WiFi.localIP().toString());
#endif
}
@ -170,8 +167,8 @@ static void onWifiGotIP(WiFiEvent_t event, WiFiEventInfo_t info) {
static void onWifiDisconnect(WiFiEvent_t event, WiFiEventInfo_t info) {
if (connected_once)
set_event(EVENT_WIFI_DISCONNECT, 0);
#ifdef DEBUG_VIA_USB
Serial.println("Wi-Fi disconnected.");
#ifdef DEBUG_LOG
logging.println("Wi-Fi disconnected.");
#endif
//we dont do anything here, the reconnect will be handled by the monitor
//too many events received when the connection is lost
@ -188,8 +185,8 @@ void init_mDNS() {
// Initialize mDNS .local resolution
if (!MDNS.begin(mdnsHost)) {
#ifdef DEBUG_VIA_USB
Serial.println("Error setting up MDNS responder!");
#ifdef DEBUG_LOG
logging.println("Error setting up MDNS responder!");
#endif
} else {
// Advertise via bonjour the service so we can auto discover these battery emulators on the local network.
@ -200,16 +197,16 @@ void init_mDNS() {
#ifdef WIFIAP
void init_WiFi_AP() {
#ifdef DEBUG_VIA_USB
Serial.println("Creating Access Point: " + String(ssidAP));
Serial.println("With password: " + String(passwordAP));
#ifdef DEBUG_LOG
logging.println("Creating Access Point: " + String(ssidAP));
logging.println("With password: " + String(passwordAP));
#endif
WiFi.softAP(ssidAP, passwordAP);
IPAddress IP = WiFi.softAPIP();
#ifdef DEBUG_VIA_USB
Serial.println("Access Point created.");
Serial.print("IP address: ");
Serial.println(IP);
#ifdef DEBUG_LOG
logging.println("Access Point created.");
logging.print("IP address: ");
logging.println(IP);
#endif
}
#endif // WIFIAP

View file

@ -9,6 +9,7 @@
#include "devboard/hal/hal.h"
#include "devboard/safety/safety.h"
#include "devboard/utils/logging.h"
#include "devboard/utils/time_meas.h"
#include "devboard/utils/types.h"
@ -22,15 +23,15 @@
#error You must select a HW to run on!
#endif
#if defined(DUAL_CAN) && defined(CAN_FD)
#if defined(CAN_ADDON) && defined(CANFD_ADDON)
// Check that user did not try to use dual can and fd-can on same hardware pins
#error CAN-FD AND DUAL-CAN CANNOT BE USED SIMULTANEOUSLY
#error CAN_ADDON AND CANFD_ADDON CANNOT BE USED SIMULTANEOUSLY
#endif
#ifdef USE_CANFD_INTERFACE_AS_CLASSIC_CAN
#if !defined(CAN_FD)
#if !defined(CANFD_ADDON)
// Check that user did not try to use classic CAN over FD, without FD component
#error PLEASE ENABLE CAN_FD TO USE CLASSIC CAN OVER CANFD INTERFACE
#error PLEASE ENABLE CANFD_ADDON TO USE CLASSIC CAN OVER CANFD INTERFACE
#endif
#endif

View file

@ -8,6 +8,8 @@ static unsigned long previousMillis2s = 0; // will store last time a 2s CAN Me
static unsigned long previousMillis10s = 0; // will store last time a 10s CAN Message was send
static unsigned long previousMillis60s = 0; // will store last time a 60s CAN Message was send
#define VOLTAGE_OFFSET_DV 20
CAN_frame BYD_250 = {.FD = false,
.ext_ID = false,
.DLC = 8,
@ -98,12 +100,22 @@ void update_values_can_inverter() { //This function maps all the values fetched
}
//Map values to CAN messages
//Maxvoltage (eg 400.0V = 4000 , 16bits long)
BYD_110.data.u8[0] = (datalayer.battery.info.max_design_voltage_dV >> 8);
BYD_110.data.u8[1] = (datalayer.battery.info.max_design_voltage_dV & 0x00FF);
//Minvoltage (eg 300.0V = 3000 , 16bits long)
BYD_110.data.u8[2] = (datalayer.battery.info.min_design_voltage_dV >> 8);
BYD_110.data.u8[3] = (datalayer.battery.info.min_design_voltage_dV & 0x00FF);
if (datalayer.battery.settings.user_set_voltage_limits_active) { //If user is requesting a specific voltage
//Target charge voltage (eg 400.0V = 4000 , 16bits long)
BYD_110.data.u8[0] = (datalayer.battery.settings.max_user_set_charge_voltage_dV >> 8);
BYD_110.data.u8[1] = (datalayer.battery.settings.max_user_set_charge_voltage_dV & 0x00FF);
//Target discharge voltage (eg 300.0V = 3000 , 16bits long)
BYD_110.data.u8[2] = (datalayer.battery.settings.max_user_set_discharge_voltage_dV >> 8);
BYD_110.data.u8[3] = (datalayer.battery.settings.max_user_set_discharge_voltage_dV & 0x00FF);
} else { //Use the voltage based on battery reported design voltage +- offset to avoid triggering events
//Target charge voltage (eg 400.0V = 4000 , 16bits long)
BYD_110.data.u8[0] = ((datalayer.battery.info.max_design_voltage_dV - VOLTAGE_OFFSET_DV) >> 8);
BYD_110.data.u8[1] = ((datalayer.battery.info.max_design_voltage_dV - VOLTAGE_OFFSET_DV) & 0x00FF);
//Target discharge voltage (eg 300.0V = 3000 , 16bits long)
BYD_110.data.u8[2] = ((datalayer.battery.info.min_design_voltage_dV + VOLTAGE_OFFSET_DV) >> 8);
BYD_110.data.u8[3] = ((datalayer.battery.info.min_design_voltage_dV + VOLTAGE_OFFSET_DV) & 0x00FF);
}
//Maximum discharge power allowed (Unit: A+1)
BYD_110.data.u8[4] = (datalayer.battery.status.max_discharge_current_dA >> 8);
BYD_110.data.u8[5] = (datalayer.battery.status.max_discharge_current_dA & 0x00FF);
@ -141,13 +153,13 @@ void update_values_can_inverter() { //This function maps all the values fetched
BYD_210.data.u8[2] = (datalayer.battery.status.temperature_min_dC >> 8);
BYD_210.data.u8[3] = (datalayer.battery.status.temperature_min_dC & 0x00FF);
#ifdef DEBUG_VIA_USB
#ifdef DEBUG_LOG
if (inverter_name[0] != 0) {
Serial.print("Detected inverter: ");
logging.print("Detected inverter: ");
for (uint8_t i = 0; i < 7; i++) {
Serial.print((char)inverter_name[i]);
logging.print((char)inverter_name[i]);
}
Serial.println();
logging.println();
}
#endif
}

View file

@ -4,7 +4,7 @@
#define MODBUS_INVERTER_SELECTED
#define MB_RTU_NUM_VALUES 30000
#define MB_RTU_NUM_VALUES 13100
#define MAX_POWER 40960 //BYD Modbus specific value
extern uint16_t mbPV[MB_RTU_NUM_VALUES];

View file

@ -130,10 +130,18 @@ void update_values_can_inverter() { //This function maps all the values fetched
}
//Error bits
if (!datalayer.system.status.inverter_allows_contactor_closing) {
SMA_158.data.u8[2] = 0x6A;
} else {
if (datalayer.system.status.inverter_allows_contactor_closing) {
SMA_158.data.u8[2] = 0xAA;
#ifdef INVERTER_CONTACTOR_ENABLE_LED_PIN
digitalWrite(INVERTER_CONTACTOR_ENABLE_LED_PIN,
HIGH); // Turn on LED to indicate that SMA inverter allows contactor closing
#endif // INVERTER_CONTACTOR_ENABLE_LED_PIN
} else {
SMA_158.data.u8[2] = 0x6A;
#ifdef INVERTER_CONTACTOR_ENABLE_LED_PIN
digitalWrite(INVERTER_CONTACTOR_ENABLE_LED_PIN,
LOW); // Turn off LED to indicate that SMA inverter allows contactor closing
#endif // INVERTER_CONTACTOR_ENABLE_LED_PIN
}
/*
@ -251,10 +259,15 @@ void send_can_inverter() {
}
}
}
void setup_inverter(void) { // Performs one time setup at startup over CAN bus
strncpy(datalayer.system.info.inverter_protocol, "BYD Battery-Box HVS over SMA CAN", 63);
datalayer.system.info.inverter_protocol[63] = '\0';
datalayer.system.status.inverter_allows_contactor_closing = false; // The inverter needs to allow first
pinMode(INVERTER_CONTACTOR_ENABLE_PIN, INPUT);
#ifdef INVERTER_CONTACTOR_ENABLE_LED_PIN
pinMode(INVERTER_CONTACTOR_ENABLE_LED_PIN, OUTPUT);
digitalWrite(INVERTER_CONTACTOR_ENABLE_LED_PIN, LOW); // Turn LED off, until inverter allows contactor closing
#endif // INVERTER_CONTACTOR_ENABLE_LED_PIN
}
#endif

View file

@ -595,8 +595,8 @@ void send_can_inverter() { // This function loops as fast as possible
// Send a subset of messages per iteration to avoid overloading the CAN bus / transmit buffer
switch (can_message_cellvolt_index) {
case 0:
#ifdef DEBUG_VIA_USB
Serial.println("Sending large batch");
#ifdef DEBUG_LOG
logging.println("Sending large batch");
#endif
transmit_can(&FOXESS_0C1D, can_config.inverter);
transmit_can(&FOXESS_0C21, can_config.inverter);
@ -655,8 +655,8 @@ void send_can_inverter() { // This function loops as fast as possible
transmit_can(&FOXESS_0D49, can_config.inverter);
transmit_can(&FOXESS_0D51, can_config.inverter);
transmit_can(&FOXESS_0D59, can_config.inverter);
#ifdef DEBUG_VIA_USB
Serial.println("Sending completed");
#ifdef DEBUG_LOG
logging.println("Sending completed");
#endif
send_cellvoltages = false;
break;
@ -679,14 +679,14 @@ void receive_can_inverter(CAN_frame rx_frame) {
if (rx_frame.data.u8[0] == 0x03) { //0x1871 [0x03, 0x06, 0x17, 0x05, 0x09, 0x09, 0x28, 0x22]
//This message is sent by the inverter every '6' seconds (0.5s after the pack serial numbers)
//and contains a timestamp in bytes 2-7 i.e. <YY>,<MM>,<DD>,<HH>,<mm>,<ss>
#ifdef DEBUG_VIA_USB
Serial.println("Inverter sends current time and date");
#ifdef DEBUG_LOG
logging.println("Inverter sends current time and date");
#endif
} else if (rx_frame.data.u8[0] == 0x01) {
if (rx_frame.data.u8[4] == 0x00) {
// Inverter wants to know bms info (every 1s)
#ifdef DEBUG_VIA_USB
Serial.println("Inverter requests 1s BMS info, we reply");
#ifdef DEBUG_LOG
logging.println("Inverter requests 1s BMS info, we reply");
#endif
transmit_can(&FOXESS_1872, can_config.inverter);
transmit_can(&FOXESS_1873, can_config.inverter);
@ -698,8 +698,8 @@ void receive_can_inverter(CAN_frame rx_frame) {
transmit_can(&FOXESS_1879, can_config.inverter);
} else if (rx_frame.data.u8[4] == 0x01) { // b4 0x01 , 0x1871 [0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00]
//Inverter wants to know all individual cellvoltages (occurs 6 seconds after valid BMS reply)
#ifdef DEBUG_VIA_USB
Serial.println("Inverter requests individual battery pack status, we reply");
#ifdef DEBUG_LOG
logging.println("Inverter requests individual battery pack status, we reply");
#endif
transmit_can(&FOXESS_0C05, can_config.inverter); //TODO, should we limit this incase NUMBER_OF_PACKS =! 8?
transmit_can(&FOXESS_0C06, can_config.inverter);
@ -711,19 +711,19 @@ void receive_can_inverter(CAN_frame rx_frame) {
transmit_can(&FOXESS_0C0C, can_config.inverter);
} else if (rx_frame.data.u8[4] == 0x04) { // b4 0x01 , 0x1871 [0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00]
//Inverter wants to know all individual cellvoltages (occurs 6 seconds after valid BMS reply)
#ifdef DEBUG_VIA_USB
Serial.println("Inverter requests cellvoltages and temps, we reply");
#ifdef DEBUG_LOG
logging.println("Inverter requests cellvoltages and temps, we reply");
#endif
send_cellvoltages = true;
}
} else if (rx_frame.data.u8[0] == 0x02) { //0x1871 [0x02, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00]
// Ack message
#ifdef DEBUG_VIA_USB
Serial.println("Inverter acks, no reply needed");
#ifdef DEBUG_LOG
logging.println("Inverter acks, no reply needed");
#endif
} else if (rx_frame.data.u8[0] == 0x05) { //0x1871 [0x05, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00]
#ifdef DEBUG_VIA_USB
Serial.println("Inverter wants to know serial numbers, we reply");
#ifdef DEBUG_LOG
logging.println("Inverter wants to know serial numbers, we reply");
#endif
for (uint8_t i = 0; i < (NUMBER_OF_PACKS + 1); i++) {
FOXESS_1881.data.u8[0] = (uint8_t)i;

View file

@ -119,15 +119,15 @@ void float2frameMSB(byte* arr, float value, byte framepointer) {
void send_kostal(byte* arr, int alen) {
#ifdef DEBUG_KOSTAL_RS485_DATA
Serial.print("TX: ");
logging.print("TX: ");
for (int i = 0; i < alen; i++) {
if (arr[i] < 0x10) {
Serial.print("0");
logging.print("0");
}
Serial.print(arr[i], HEX);
Serial.print(" ");
logging.print(arr[i], HEX);
logging.print(" ");
}
Serial.println("\n");
logging.println("\n");
#endif
Serial2.write(arr, alen);
}
@ -274,12 +274,12 @@ void receive_RS485() // Runs as fast as possible to handle the serial stream
if (RS485_RXFRAME[rx_index - 1] == 0x00) {
if ((rx_index == 10) && (RS485_RXFRAME[0] == 0x09) && register_content_ok) {
#ifdef DEBUG_KOSTAL_RS485_DATA
Serial.print("RX: ");
logging.print("RX: ");
for (uint8_t i = 0; i < 10; i++) {
Serial.print(RS485_RXFRAME[i], HEX);
Serial.print(" ");
logging.print(RS485_RXFRAME[i], HEX);
logging.print(" ");
}
Serial.println("");
logging.println("");
#endif
rx_index = 0;
if (check_kostal_frame_crc()) {

View file

@ -60,8 +60,8 @@ void manageSerialLinkTransmitter() {
}
bool sendError = dataLinkTransmit.checkTransmissionError(true);
if (sendError) {
Serial.print(currentTime);
Serial.println(" - ERROR: Serial Data Link - SEND Error");
logging.print(currentTime);
logging.println(" - ERROR: Serial Data Link - SEND Error");
lasterror = true;
transmitGoodSince = currentTime;
}
@ -82,17 +82,17 @@ void manageSerialLinkTransmitter() {
if (lasterror && (ackReceived > 0)) {
lasterror = false;
Serial.print(currentTime);
Serial.println(" - RECOVERY: Serial Data Link - Send GOOD");
logging.print(currentTime);
logging.println(" - RECOVERY: Serial Data Link - Send GOOD");
}
//--- reporting every 60 seconds that transmission is good
if (currentTime - transmitGoodSince > INTERVAL_60_S) {
transmitGoodSince = currentTime;
Serial.print(currentTime);
Serial.println(" - Transmit Good");
logging.print(currentTime);
logging.println(" - Transmit Good");
// printUsefullData();
#ifdef DEBUG_VIA_USB
#ifdef DEBUG_LOG
void printSendingValues();
#endif
}
@ -100,13 +100,13 @@ void manageSerialLinkTransmitter() {
//--- report that Errors been ocurring for > 60 seconds
if (currentTime - lastGood > INTERVAL_60_S) {
lastGood = currentTime;
Serial.print(currentTime);
Serial.println(" - Transmit Failed : 60 seconds");
logging.print(currentTime);
logging.println(" - Transmit Failed : 60 seconds");
// print the max_ data
Serial.println("SerialDataLink : bms_status=4");
Serial.println("SerialDataLink : LEDcolor = RED");
Serial.println("SerialDataLink : max_target_discharge_power = 0");
Serial.println("SerialDataLink : max_target_charge_power = 0");
logging.println("SerialDataLink : bms_status=4");
logging.println("SerialDataLink : LEDcolor = RED");
logging.println("SerialDataLink : max_target_discharge_power = 0");
logging.println("SerialDataLink : max_target_charge_power = 0");
datalayer.battery.status.max_discharge_power_W = 0;
datalayer.battery.status.max_charge_power_W = 0;
@ -117,8 +117,8 @@ void manageSerialLinkTransmitter() {
// lastMessageReceived from CAN bus (Battery)
if (currentTime - lastMessageReceived > (5 * 60000) ) // 5 minutes
{
Serial.print(millis());
Serial.println(" - Data Stale : 5 minutes");
logging.print(millis());
logging.println(" - Data Stale : 5 minutes");
// throw error
// stop transmitting until fresh
@ -154,42 +154,42 @@ void manageSerialLinkTransmitter() {
}
void printSendingValues() {
Serial.println("Values from battery: ");
Serial.print("SOC: ");
Serial.print(datalayer.battery.status.real_soc);
Serial.print(" SOH: ");
Serial.print(datalayer.battery.status.soh_pptt);
Serial.print(" Voltage: ");
Serial.print(datalayer.battery.status.voltage_dV);
Serial.print(" Current: ");
Serial.print(datalayer.battery.status.current_dA);
Serial.print(" Capacity: ");
Serial.print(datalayer.battery.info.total_capacity_Wh);
Serial.print(" Remain cap: ");
Serial.print(datalayer.battery.status.remaining_capacity_Wh);
Serial.print(" Max discharge W: ");
Serial.print(datalayer.battery.status.max_discharge_power_W);
Serial.print(" Max charge W: ");
Serial.print(datalayer.battery.status.max_charge_power_W);
Serial.print(" BMS status: ");
Serial.print(datalayer.battery.status.bms_status);
Serial.print(" Power: ");
Serial.print(datalayer.battery.status.active_power_W);
Serial.print(" Temp min: ");
Serial.print(datalayer.battery.status.temperature_min_dC);
Serial.print(" Temp max: ");
Serial.print(datalayer.battery.status.temperature_max_dC);
Serial.print(" Cell max: ");
Serial.print(datalayer.battery.status.cell_max_voltage_mV);
Serial.print(" Cell min: ");
Serial.print(datalayer.battery.status.cell_min_voltage_mV);
Serial.print(" LFP : ");
Serial.print(datalayer.battery.info.chemistry);
Serial.print(" Battery Allows Contactor Closing: ");
Serial.print(datalayer.system.status.battery_allows_contactor_closing);
Serial.print(" Inverter Allows Contactor Closing: ");
Serial.print(datalayer.system.status.inverter_allows_contactor_closing);
logging.println("Values from battery: ");
logging.print("SOC: ");
logging.print(datalayer.battery.status.real_soc);
logging.print(" SOH: ");
logging.print(datalayer.battery.status.soh_pptt);
logging.print(" Voltage: ");
logging.print(datalayer.battery.status.voltage_dV);
logging.print(" Current: ");
logging.print(datalayer.battery.status.current_dA);
logging.print(" Capacity: ");
logging.print(datalayer.battery.info.total_capacity_Wh);
logging.print(" Remain cap: ");
logging.print(datalayer.battery.status.remaining_capacity_Wh);
logging.print(" Max discharge W: ");
logging.print(datalayer.battery.status.max_discharge_power_W);
logging.print(" Max charge W: ");
logging.print(datalayer.battery.status.max_charge_power_W);
logging.print(" BMS status: ");
logging.print(datalayer.battery.status.bms_status);
logging.print(" Power: ");
logging.print(datalayer.battery.status.active_power_W);
logging.print(" Temp min: ");
logging.print(datalayer.battery.status.temperature_min_dC);
logging.print(" Temp max: ");
logging.print(datalayer.battery.status.temperature_max_dC);
logging.print(" Cell max: ");
logging.print(datalayer.battery.status.cell_max_voltage_mV);
logging.print(" Cell min: ");
logging.print(datalayer.battery.status.cell_min_voltage_mV);
logging.print(" LFP : ");
logging.print(datalayer.battery.info.chemistry);
logging.print(" Battery Allows Contactor Closing: ");
logging.print(datalayer.system.status.battery_allows_contactor_closing);
logging.print(" Inverter Allows Contactor Closing: ");
logging.print(datalayer.system.status.inverter_allows_contactor_closing);
Serial.println("");
logging.println("");
}
#endif

View file

@ -127,10 +127,18 @@ void update_values_can_inverter() { //This function maps all the values fetched
}
//Error bits
if (!datalayer.system.status.inverter_allows_contactor_closing) {
SMA_158.data.u8[2] = 0x6A;
} else {
if (datalayer.system.status.inverter_allows_contactor_closing) {
SMA_158.data.u8[2] = 0xAA;
#ifdef INVERTER_CONTACTOR_ENABLE_LED_PIN
digitalWrite(INVERTER_CONTACTOR_ENABLE_LED_PIN,
HIGH); // Turn on LED to indicate that SMA inverter allows contactor closing
#endif // INVERTER_CONTACTOR_ENABLE_LED_PIN
} else {
SMA_158.data.u8[2] = 0x6A;
#ifdef INVERTER_CONTACTOR_ENABLE_LED_PIN
digitalWrite(INVERTER_CONTACTOR_ENABLE_LED_PIN,
LOW); // Turn off LED to indicate that SMA inverter allows contactor closing
#endif // INVERTER_CONTACTOR_ENABLE_LED_PIN
}
/*
@ -253,5 +261,9 @@ void send_can_inverter() {
void setup_inverter(void) { // Performs one time setup at startup over CAN bus
strncpy(datalayer.system.info.inverter_protocol, "SMA CAN", 63);
datalayer.system.info.inverter_protocol[63] = '\0';
#ifdef INVERTER_CONTACTOR_ENABLE_LED_PIN
pinMode(INVERTER_CONTACTOR_ENABLE_LED_PIN, OUTPUT);
digitalWrite(INVERTER_CONTACTOR_ENABLE_LED_PIN, LOW); // Turn LED off, until inverter allows contactor closing
#endif // INVERTER_CONTACTOR_ENABLE_LED_PIN
}
#endif

View file

@ -64,6 +64,31 @@ CAN_frame SOLAX_1879 = {.FD = false,
.DLC = 8,
.ID = 0x1879,
.data = {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}};
CAN_frame SOLAX_187E = {.FD = false, //Needed for Ultra
.ext_ID = true,
.DLC = 8,
.ID = 0x187E,
.data = {0x0, 0x2D, 0x0, 0x0, 0x0, 0x5F, 0x0, 0x0}};
CAN_frame SOLAX_187D = {.FD = false, //Needed for Ultra
.ext_ID = true,
.DLC = 8,
.ID = 0x187D,
.data = {0x8B, 0x01, 0x0, 0x0, 0x8B, 0x1, 0x0, 0x0}};
CAN_frame SOLAX_187C = {.FD = false, //Needed for Ultra
.ext_ID = true,
.DLC = 8,
.ID = 0x187C,
.data = {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}};
CAN_frame SOLAX_187B = {.FD = false, //Needed for Ultra
.ext_ID = true,
.DLC = 8,
.ID = 0x187B,
.data = {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}};
CAN_frame SOLAX_187A = {.FD = false, //Needed for Ultra
.ext_ID = true,
.DLC = 8,
.ID = 0x187A,
.data = {0x01, 0x40, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}};
CAN_frame SOLAX_1881 = {.FD = false,
.ext_ID = true,
.DLC = 8,
@ -182,8 +207,8 @@ void receive_can_inverter(CAN_frame rx_frame) {
LastFrameTime = millis();
switch (STATE) {
case (BATTERY_ANNOUNCE):
#ifdef DEBUG_VIA_USB
Serial.println("Solax Battery State: Announce");
#ifdef DEBUG_LOG
logging.println("Solax Battery State: Announce");
#endif
datalayer.system.status.inverter_allows_contactor_closing = false;
SOLAX_1875.data.u8[4] = (0x00); // Inform Inverter: Contactor 0=off, 1=on.
@ -214,8 +239,8 @@ void receive_can_inverter(CAN_frame rx_frame) {
transmit_can(&SOLAX_1878, can_config.inverter);
transmit_can(&SOLAX_1801, can_config.inverter); // Announce that the battery will be connected
STATE = CONTACTOR_CLOSED; // Jump to Contactor Closed State
#ifdef DEBUG_VIA_USB
Serial.println("Solax Battery State: Contactor Closed");
#ifdef DEBUG_LOG
logging.println("Solax Battery State: Contactor Closed");
#endif
break;
@ -242,13 +267,13 @@ void receive_can_inverter(CAN_frame rx_frame) {
if (rx_frame.ID == 0x1871 && rx_frame.data.u64 == __builtin_bswap64(0x0500010000000000)) {
transmit_can(&SOLAX_1881, can_config.inverter);
transmit_can(&SOLAX_1882, can_config.inverter);
#ifdef DEBUG_VIA_USB
Serial.println("1871 05-frame received from inverter");
#ifdef DEBUG_LOG
logging.println("1871 05-frame received from inverter");
#endif
}
if (rx_frame.ID == 0x1871 && rx_frame.data.u8[0] == (0x03)) {
#ifdef DEBUG_VIA_USB
Serial.println("1871 03-frame received from inverter");
#ifdef DEBUG_LOG
logging.println("1871 03-frame received from inverter");
#endif
}
}
@ -256,5 +281,12 @@ void setup_inverter(void) { // Performs one time setup at startup
strncpy(datalayer.system.info.inverter_protocol, "SolaX Triple Power LFP over CAN bus", 63);
datalayer.system.info.inverter_protocol[63] = '\0';
datalayer.system.status.inverter_allows_contactor_closing = false; // The inverter needs to allow first
// Sending these messages once towards the inverter makes SOC% work on the Ultra variant
transmit_can(&SOLAX_187E, can_config.inverter);
transmit_can(&SOLAX_187D, can_config.inverter);
transmit_can(&SOLAX_187C, can_config.inverter);
transmit_can(&SOLAX_187B, can_config.inverter);
transmit_can(&SOLAX_187A, can_config.inverter);
}
#endif

View file

@ -35,6 +35,24 @@ extern "C"{
* TCP/IP Event Task
* */
#define TAG "AsyncTCP"
// https://github.com/espressif/arduino-esp32/issues/10526
#ifdef CONFIG_LWIP_TCPIP_CORE_LOCKING
#define TCP_MUTEX_LOCK() \
if (!sys_thread_tcpip(LWIP_CORE_LOCK_QUERY_HOLDER)) { \
LOCK_TCPIP_CORE(); \
}
#define TCP_MUTEX_UNLOCK() \
if (sys_thread_tcpip(LWIP_CORE_LOCK_QUERY_HOLDER)) { \
UNLOCK_TCPIP_CORE(); \
}
#else // CONFIG_LWIP_TCPIP_CORE_LOCKING
#define TCP_MUTEX_LOCK()
#define TCP_MUTEX_UNLOCK()
#endif // CONFIG_LWIP_TCPIP_CORE_LOCKING
typedef enum {
LWIP_TCP_SENT, LWIP_TCP_RECV, LWIP_TCP_FIN, LWIP_TCP_ERROR, LWIP_TCP_POLL, LWIP_TCP_CLEAR, LWIP_TCP_ACCEPT, LWIP_TCP_CONNECTED, LWIP_TCP_DNS
} lwip_event_t;
@ -688,8 +706,10 @@ bool AsyncClient::connect(IPAddress ip, uint16_t port){
addr.type = IPADDR_TYPE_V4;
addr.u_addr.ip4.addr = ip;
TCP_MUTEX_LOCK();
tcp_pcb* pcb = tcp_new_ip_type(IPADDR_TYPE_V4);
if (!pcb){
TCP_MUTEX_UNLOCK();
log_e("pcb == NULL");
return false;
}
@ -699,6 +719,7 @@ bool AsyncClient::connect(IPAddress ip, uint16_t port){
tcp_recv(pcb, &_tcp_recv);
tcp_sent(pcb, &_tcp_sent);
tcp_poll(pcb, &_tcp_poll, 1);
TCP_MUTEX_UNLOCK();
//_tcp_connect(pcb, &addr, port,(tcp_connected_fn)&_s_connected);
_tcp_connect(pcb, _closed_slot, &addr, port,(tcp_connected_fn)&_tcp_connected);
return true;
@ -711,8 +732,9 @@ bool AsyncClient::connect(const char* host, uint16_t port){
log_e("failed to start task");
return false;
}
TCP_MUTEX_LOCK();
err_t err = dns_gethostbyname(host, &addr, (dns_found_callback)&_tcp_dns_found, this);
TCP_MUTEX_UNLOCK();
if(err == ERR_OK) {
return connect(IPAddress(addr.u_addr.ip4.addr), port);
} else if(err == ERR_INPROGRESS) {
@ -800,11 +822,13 @@ int8_t AsyncClient::_close(){
int8_t err = ERR_OK;
if(_pcb) {
//log_i("");
TCP_MUTEX_LOCK();
tcp_arg(_pcb, NULL);
tcp_sent(_pcb, NULL);
tcp_recv(_pcb, NULL);
tcp_err(_pcb, NULL);
tcp_poll(_pcb, NULL, 0);
TCP_MUTEX_UNLOCK();
_tcp_clear_events(this);
err = _tcp_close(_pcb, _closed_slot);
if(err != ERR_OK) {
@ -1271,7 +1295,9 @@ void AsyncServer::begin(){
return;
}
int8_t err;
TCP_MUTEX_LOCK();
_pcb = tcp_new_ip_type(IPADDR_TYPE_V4);
TCP_MUTEX_UNLOCK();
if (!_pcb){
log_e("_pcb == NULL");
return;
@ -1294,14 +1320,18 @@ void AsyncServer::begin(){
log_e("listen_pcb == NULL");
return;
}
TCP_MUTEX_LOCK();
tcp_arg(_pcb, (void*) this);
tcp_accept(_pcb, &_s_accept);
TCP_MUTEX_UNLOCK();
}
void AsyncServer::end(){
if(_pcb){
TCP_MUTEX_LOCK();
tcp_arg(_pcb, NULL);
tcp_accept(_pcb, NULL);
TCP_MUTEX_UNLOCK();
if(tcp_close(_pcb) != ERR_OK){
_tcp_abort(_pcb, -1);
}