mirror of
https://github.com/dalathegreat/Battery-Emulator.git
synced 2025-10-05 19:42:08 +02:00
Merge branch 'main' into feature/double-automatic-contactor
This commit is contained in:
commit
9a7dda5e11
65 changed files with 1049 additions and 396 deletions
13
.github/workflows/compile-all-batteries.yml
vendored
13
.github/workflows/compile-all-batteries.yml
vendored
|
@ -34,19 +34,25 @@ jobs:
|
|||
# These are the batteries for which the code will be compiled.
|
||||
battery:
|
||||
- BMW_I3_BATTERY
|
||||
- BMW_IX_BATTERY
|
||||
- BYD_ATTO_3_BATTERY
|
||||
- CELLPOWER_BMS
|
||||
- CHADEMO_BATTERY
|
||||
- IMIEV_CZERO_ION_BATTERY
|
||||
- JAGUAR_IPACE_BATTERY
|
||||
- KIA_HYUNDAI_64_BATTERY
|
||||
- KIA_E_GMP_BATTERY
|
||||
- KIA_HYUNDAI_HYBRID_BATTERY
|
||||
- MG_5_BATTERY
|
||||
- NISSAN_LEAF_BATTERY
|
||||
- PYLON_BATTERY
|
||||
- RJXZS_BMS
|
||||
- RANGE_ROVER_PHEV_BATTERY
|
||||
- RENAULT_KANGOO_BATTERY
|
||||
- RENAULT_TWIZY_BATTERY
|
||||
- RENAULT_ZOE_GEN1_BATTERY
|
||||
- RENAULT_ZOE_GEN2_BATTERY
|
||||
- SANTA_FE_PHEV_BATTERY
|
||||
- TESLA_MODEL_3Y_BATTERY
|
||||
- VOLVO_SPA_BATTERY
|
||||
- TEST_FAKE_BATTERY
|
||||
|
@ -54,13 +60,6 @@ jobs:
|
|||
# These are the emulated inverter communication protocols for which the code will be compiled.
|
||||
inverter:
|
||||
- BYD_CAN
|
||||
# - BYD_MODBUS
|
||||
# - PYLON_CAN
|
||||
# - SMA_CAN
|
||||
# - SMA_TRIPOWER_CAN
|
||||
# - SOFAR_CAN
|
||||
# - SOLAX_CAN
|
||||
|
||||
# This is the platform GitHub will use to run our workflow.
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
|
|
3
.github/workflows/compile-all-inverters.yml
vendored
3
.github/workflows/compile-all-inverters.yml
vendored
|
@ -42,11 +42,14 @@ jobs:
|
|||
# - TESLA_MODEL_3Y_BATTERY
|
||||
# These are the emulated inverter communication protocols for which the code will be compiled.
|
||||
inverter:
|
||||
- AFORE_CAN
|
||||
- BYD_CAN
|
||||
- BYD_SMA
|
||||
- BYD_MODBUS
|
||||
- FOXESS_CAN
|
||||
- PYLON_LV_CAN
|
||||
- PYLON_CAN
|
||||
- SCHNEIDER_CAN
|
||||
- SMA_CAN
|
||||
- SMA_TRIPOWER_CAN
|
||||
- SOFAR_CAN
|
||||
|
|
|
@ -53,7 +53,7 @@
|
|||
|
||||
Preferences settings; // Store user settings
|
||||
// The current software version, shown on webserver
|
||||
const char* version_number = "7.7.dev";
|
||||
const char* version_number = "7.8.dev";
|
||||
|
||||
// Interval settings
|
||||
uint16_t intervalUpdateValues = INTERVAL_1_S; // Interval at which to update inverter values / Modbus registers
|
||||
|
@ -70,13 +70,11 @@ volatile bool send_ok = 0;
|
|||
static const uint32_t QUARTZ_FREQUENCY = CRYSTAL_FREQUENCY_MHZ * 1000000UL; //MHZ configured in USER_SETTINGS.h
|
||||
ACAN2515 can(MCP2515_CS, SPI, MCP2515_INT);
|
||||
static ACAN2515_Buffer16 gBuffer;
|
||||
#endif
|
||||
#endif //DUAL_CAN
|
||||
#ifdef CAN_FD
|
||||
#include "src/lib/pierremolinaro-ACAN2517FD/ACAN2517FD.h"
|
||||
ACAN2517FD canfd(MCP2517_CS, SPI, MCP2517_INT);
|
||||
#else
|
||||
typedef char CANFDMessage;
|
||||
#endif
|
||||
#endif //CAN_FD
|
||||
|
||||
// ModbusRTU parameters
|
||||
#ifdef MODBUS_INVERTER_SELECTED
|
||||
|
@ -194,11 +192,10 @@ void setup() {
|
|||
init_rs485();
|
||||
|
||||
init_serialDataLink();
|
||||
|
||||
init_inverter();
|
||||
|
||||
init_battery();
|
||||
|
||||
#if defined(CAN_INVERTER_SELECTED) || defined(MODBUS_INVERTER_SELECTED)
|
||||
setup_inverter();
|
||||
#endif
|
||||
setup_battery();
|
||||
#ifdef EQUIPMENT_STOP_BUTTON
|
||||
init_equipment_stop_button();
|
||||
#endif
|
||||
|
@ -581,29 +578,6 @@ void init_rs485() {
|
|||
#endif
|
||||
}
|
||||
|
||||
void init_inverter() {
|
||||
#ifdef SOLAX_CAN
|
||||
datalayer.system.status.inverter_allows_contactor_closing = false; // The inverter needs to allow first
|
||||
intervalUpdateValues = 800; // This protocol also requires the values to be updated faster
|
||||
#endif
|
||||
#ifdef FOXESS_CAN
|
||||
intervalUpdateValues = 950; // This protocol also requires the values to be updated faster
|
||||
#endif
|
||||
#ifdef BYD_SMA
|
||||
datalayer.system.status.inverter_allows_contactor_closing = false; // The inverter needs to allow first
|
||||
pinMode(INVERTER_CONTACTOR_ENABLE_PIN, INPUT);
|
||||
#endif
|
||||
}
|
||||
|
||||
void init_battery() {
|
||||
// Inform user what battery is used and perform setup
|
||||
setup_battery();
|
||||
|
||||
#ifdef CHADEMO_BATTERY
|
||||
intervalUpdateValues = 800; // This mode requires the values to be updated faster
|
||||
#endif
|
||||
}
|
||||
|
||||
#ifdef EQUIPMENT_STOP_BUTTON
|
||||
|
||||
void monitor_equipment_stop_button() {
|
||||
|
@ -1175,6 +1149,9 @@ void receive_can(CAN_frame* rx_frame, int interface) {
|
|||
|
||||
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
|
||||
|
|
|
@ -43,6 +43,7 @@
|
|||
//#define FOXESS_CAN //Enable this line to emulate a "HV2600/ECS4100 battery" over CAN bus
|
||||
//#define PYLON_LV_CAN //Enable this line to emulate a "48V Pylontech battery" over CAN bus
|
||||
//#define PYLON_CAN //Enable this line to emulate a "High Voltage Pylontech battery" over CAN bus
|
||||
//#define SCHNEIDER_CAN //Enable this line to emulate a "Schneider Version 2: SE BMS" over CAN bus
|
||||
//#define SMA_CAN //Enable this line to emulate a "BYD Battery-Box H 8.9kWh, 7 mod" over CAN bus
|
||||
//#define SMA_TRIPOWER_CAN //Enable this line to emulate a "SMA Home Storage battery" over CAN bus
|
||||
//#define SOFAR_CAN //Enable this line to emulate a "Sofar Energy Storage Inverter High Voltage BMS General Protocol (Extended Frame)" over CAN bus
|
||||
|
|
|
@ -20,6 +20,7 @@
|
|||
|
||||
#ifdef CHADEMO_BATTERY
|
||||
#include "CHADEMO-BATTERY.h"
|
||||
#include "CHADEMO-SHUNTS.h"
|
||||
#endif
|
||||
|
||||
#ifdef IMIEV_CZERO_ION_BATTERY
|
||||
|
|
|
@ -1118,9 +1118,8 @@ void send_can_battery() {
|
|||
}
|
||||
|
||||
void setup_battery(void) { // Performs one time setup at startup
|
||||
#ifdef DEBUG_VIA_USB
|
||||
Serial.println("BMW i3 battery selected");
|
||||
#endif //DEBUG_VIA_USB
|
||||
strncpy(datalayer.system.info.battery_protocol, "BMW i3", 63);
|
||||
datalayer.system.info.battery_protocol[63] = '\0';
|
||||
|
||||
//Before we have started up and detected which battery is in use, use 60AH values
|
||||
datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_60AH;
|
||||
|
@ -1129,9 +1128,6 @@ void setup_battery(void) { // Performs one time setup at startup
|
|||
datalayer.system.status.battery_allows_contactor_closing = true;
|
||||
|
||||
#ifdef DOUBLE_BATTERY
|
||||
#ifdef DEBUG_VIA_USB
|
||||
Serial.println("Another BMW i3 battery also selected!");
|
||||
#endif //DEBUG_VIA_USB
|
||||
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_deviation_mV = datalayer.battery.info.max_cell_voltage_deviation_mV;
|
||||
|
|
|
@ -457,17 +457,18 @@ void update_values_battery() { //This function maps all the values fetched via
|
|||
|
||||
datalayer.battery.status.temperature_max_dC = max_battery_temperature;
|
||||
|
||||
if (isStale(min_cell_voltage, datalayer.battery.status.cell_min_voltage_mV, min_cell_voltage_lastchanged)) {
|
||||
datalayer.battery.status.cell_min_voltage_mV = 9999; //Stale values force stop
|
||||
set_event(EVENT_CAN_RX_FAILURE, 0);
|
||||
} else {
|
||||
datalayer.battery.status.cell_min_voltage_mV = min_cell_voltage; //Value is alive
|
||||
}
|
||||
//Check stale values. As values dont change much during idle only consider stale if both parts of this message freeze.
|
||||
bool isMinCellVoltageStale =
|
||||
isStale(min_cell_voltage, datalayer.battery.status.cell_min_voltage_mV, min_cell_voltage_lastchanged);
|
||||
bool isMaxCellVoltageStale =
|
||||
isStale(max_cell_voltage, datalayer.battery.status.cell_max_voltage_mV, max_cell_voltage_lastchanged);
|
||||
|
||||
if (isStale(max_cell_voltage, datalayer.battery.status.cell_max_voltage_mV, max_cell_voltage_lastchanged)) {
|
||||
if (isMinCellVoltageStale && isMaxCellVoltageStale) {
|
||||
datalayer.battery.status.cell_min_voltage_mV = 9999; //Stale values force stop
|
||||
datalayer.battery.status.cell_max_voltage_mV = 9999; //Stale values force stop
|
||||
set_event(EVENT_CAN_RX_FAILURE, 0);
|
||||
} else {
|
||||
datalayer.battery.status.cell_min_voltage_mV = min_cell_voltage; //Value is alive
|
||||
datalayer.battery.status.cell_max_voltage_mV = max_cell_voltage; //Value is alive
|
||||
}
|
||||
|
||||
|
@ -672,7 +673,7 @@ void receive_can_battery(CAN_frame rx_frame) {
|
|||
#ifdef DEBUG_VIA_USB
|
||||
Serial.println("Cell MinMax Qualifier Invalid - Requesting BMS Reset");
|
||||
#endif
|
||||
set_event(EVENT_SOC_UNAVAILABLE, (millis()));
|
||||
//set_event(EVENT_BATTERY_VALUE_UNAVAILABLE, (millis())); //Eventually need new Info level event type
|
||||
transmit_can(&BMWiX_6F4_REQUEST_HARD_RESET, can_config.battery);
|
||||
} else { //Only ingest values if they are not the 10V Error state
|
||||
min_cell_voltage = (rx_frame.data.u8[6] << 8 | rx_frame.data.u8[7]);
|
||||
|
@ -778,9 +779,8 @@ void send_can_battery() {
|
|||
//} //We can always send CAN as the iX BMS will wake up on vehicle comms
|
||||
|
||||
void setup_battery(void) { // Performs one time setup at startup
|
||||
#ifdef DEBUG_VIA_USB
|
||||
Serial.println("BMW iX battery selected");
|
||||
#endif //DEBUG_VIA_USB
|
||||
strncpy(datalayer.system.info.battery_protocol, "BMW iX and i4-7 platform", 63);
|
||||
datalayer.system.info.battery_protocol[63] = '\0';
|
||||
|
||||
//Before we have started up and detected which battery is in use, use 108S values
|
||||
datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_DV;
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
static unsigned long previousMillis50 = 0; // will store last time a 50ms CAN Message was send
|
||||
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 bool SOC_method = false;
|
||||
static uint8_t counter_50ms = 0;
|
||||
static uint8_t counter_100ms = 0;
|
||||
static uint8_t frame6_counter = 0xB;
|
||||
|
@ -61,6 +62,8 @@ static uint16_t BMS2_highest_cell_voltage_mV = 3300;
|
|||
#define POLL_FOR_BATTERY_CELL_MV_MAX 0x2D
|
||||
#define POLL_FOR_BATTERY_CELL_MV_MIN 0x2B
|
||||
#define UNKNOWN_POLL_1 0xFC
|
||||
#define ESTIMATED 0
|
||||
#define MEASURED 1
|
||||
static uint16_t poll_state = POLL_FOR_BATTERY_SOC;
|
||||
|
||||
CAN_frame ATTO_3_12D = {.FD = false,
|
||||
|
@ -108,19 +111,25 @@ void update_values_battery() { //This function maps all the values fetched via
|
|||
datalayer.battery.status.voltage_dV = BMS_voltage * 10;
|
||||
}
|
||||
|
||||
//datalayer.battery.status.real_soc = BMS_SOC * 100; //TODO: This is not yet found!
|
||||
// We instead estimate the SOC% based on the battery voltage
|
||||
// This is a very bad solution, and as soon as an usable SOC% value has been found on CAN, we should switch to that!
|
||||
#ifdef USE_ESTIMATED_SOC
|
||||
// When the battery is crashed hard, it locks itself and SOC becomes unavailable.
|
||||
// We instead estimate the SOC% based on the battery voltage.
|
||||
// This is a bad solution, you wont be able to use 100% of the battery
|
||||
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;
|
||||
SOC_method = MEASURED;
|
||||
#endif
|
||||
|
||||
datalayer.battery.status.current_dA = -BMS_current;
|
||||
|
||||
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 = 10000; //TODO: Map from CAN later on
|
||||
datalayer.battery.status.max_discharge_power_W = MAXPOWER_DISCHARGE_W; //TODO: Map from CAN later on
|
||||
|
||||
datalayer.battery.status.max_charge_power_W = 10000; //TODO: Map from CAN later on
|
||||
datalayer.battery.status.max_charge_power_W = MAXPOWER_CHARGE_W; //TODO: Map from CAN later on
|
||||
|
||||
datalayer.battery.status.cell_max_voltage_mV = BMS_highest_cell_voltage_mV;
|
||||
|
||||
|
@ -144,6 +153,7 @@ void update_values_battery() { //This function maps all the values fetched via
|
|||
datalayer.battery.status.temperature_max_dC = battery_calc_max_temperature * 10;
|
||||
|
||||
// Update webserver datalayer
|
||||
datalayer_extended.bydAtto3.SOC_method = SOC_method;
|
||||
datalayer_extended.bydAtto3.SOC_estimated = datalayer.battery.status.real_soc;
|
||||
//Once we implement switching logic, remember to change from where the estimated is taken
|
||||
datalayer_extended.bydAtto3.SOC_highprec = battery_highprecision_SOC;
|
||||
|
@ -228,6 +238,7 @@ void receive_can_battery(CAN_frame rx_frame) {
|
|||
case 0x444: //9E,01,88,13,64,64,98,65
|
||||
//9A,01,B6,13,64,64,98,3B //407.5V 18deg
|
||||
//9B,01,B8,13,64,64,98,38 //408.5V 14deg
|
||||
//lowprecision_SOC = ???
|
||||
battery_voltage = ((rx_frame.data.u8[1] & 0x0F) << 8) | rx_frame.data.u8[0];
|
||||
//battery_temperature_something = rx_frame.data.u8[7] - 40; resides in frame 7
|
||||
break;
|
||||
|
@ -399,9 +410,8 @@ void send_can_battery() {
|
|||
}
|
||||
|
||||
void setup_battery(void) { // Performs one time setup at startup
|
||||
#ifdef DEBUG_VIA_USB
|
||||
Serial.println("BYD Atto 3 battery selected");
|
||||
#endif
|
||||
strncpy(datalayer.system.info.battery_protocol, "BYD Atto 3", 63);
|
||||
datalayer.system.info.battery_protocol[63] = '\0';
|
||||
datalayer.battery.info.number_of_cells = 126;
|
||||
datalayer.battery.info.chemistry = battery_chemistry_enum::LFP;
|
||||
datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_DV;
|
||||
|
|
|
@ -3,6 +3,12 @@
|
|||
|
||||
#include "../include.h"
|
||||
|
||||
#define USE_ESTIMATED_SOC // If enabled, SOC is estimated from pack voltage. Useful for locked packs. \
|
||||
// Uncomment this only if you know your BMS is unlocked and able to send SOC%
|
||||
#define MAXPOWER_CHARGE_W 10000
|
||||
#define MAXPOWER_DISCHARGE_W 10000
|
||||
|
||||
/* Do not modify the rows below */
|
||||
#define BATTERY_SELECTED
|
||||
#define MAX_PACK_VOLTAGE_DV 4410 //5000 = 500.0V
|
||||
#define MIN_PACK_VOLTAGE_DV 3800
|
||||
|
|
|
@ -333,9 +333,8 @@ void send_can_battery() {
|
|||
}
|
||||
|
||||
void setup_battery(void) { // Performs one time setup at startup
|
||||
#ifdef DEBUG_VIA_USB
|
||||
Serial.println("Cellpower BMS selected");
|
||||
#endif
|
||||
strncpy(datalayer.system.info.battery_protocol, "Cellpower BMS", 63);
|
||||
datalayer.system.info.battery_protocol[63] = '\0';
|
||||
datalayer.system.status.battery_allows_contactor_closing = true;
|
||||
datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_DV;
|
||||
datalayer.battery.info.min_design_voltage_dV = MIN_PACK_VOLTAGE_DV;
|
||||
|
|
|
@ -120,7 +120,7 @@ void update_values_battery() {
|
|||
datalayer.battery.status.voltage_dV = get_measured_voltage() * 10;
|
||||
|
||||
datalayer.battery.info.total_capacity_Wh =
|
||||
((x101_chg_est.RatedBatteryCapacity / 0.11) *
|
||||
((x101_chg_est.RatedBatteryCapacity / 0.1) *
|
||||
1000); //(Added in CHAdeMO v1.0.1), maybe handle hardcoded on lower protocol version?
|
||||
|
||||
/* TODO max charging rate =
|
||||
|
@ -151,8 +151,8 @@ void update_values_battery() {
|
|||
|
||||
inline void process_vehicle_charging_minimums(CAN_frame rx_frame) {
|
||||
x100_chg_lim.MinimumChargeCurrent = rx_frame.data.u8[0];
|
||||
x100_chg_lim.MinimumBatteryVoltage = ((rx_frame.data.u8[2] << 8) | rx_frame.data.u8[3]);
|
||||
x100_chg_lim.MaximumBatteryVoltage = ((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]);
|
||||
x100_chg_lim.MinimumBatteryVoltage = ((rx_frame.data.u8[3] << 8) | rx_frame.data.u8[2]);
|
||||
x100_chg_lim.MaximumBatteryVoltage = ((rx_frame.data.u8[5] << 8) | rx_frame.data.u8[4]);
|
||||
x100_chg_lim.ConstantOfChargingRateIndication = rx_frame.data.u8[6];
|
||||
}
|
||||
|
||||
|
@ -160,15 +160,14 @@ inline void process_vehicle_charging_maximums(CAN_frame rx_frame) {
|
|||
x101_chg_est.MaxChargingTime10sBit = rx_frame.data.u8[1];
|
||||
x101_chg_est.MaxChargingTime1minBit = rx_frame.data.u8[2];
|
||||
x101_chg_est.EstimatedChargingTime = rx_frame.data.u8[3];
|
||||
x101_chg_est.RatedBatteryCapacity = ((rx_frame.data.u8[5] << 8) | rx_frame.data.u8[6]);
|
||||
x101_chg_est.RatedBatteryCapacity = ((rx_frame.data.u8[6] << 8) | rx_frame.data.u8[5]);
|
||||
}
|
||||
|
||||
inline void process_vehicle_charging_session(CAN_frame rx_frame) {
|
||||
|
||||
uint16_t newTargetBatteryVoltage = ((rx_frame.data.u8[1] << 8) | rx_frame.data.u8[2]);
|
||||
uint16_t priorChargingCurrentRequest = x102_chg_session.ChargingCurrentRequest;
|
||||
uint8_t priorTargetBatteryVoltage = x102_chg_session.TargetBatteryVoltage;
|
||||
uint16_t newTargetBatteryVoltage = ((rx_frame.data.u8[2] << 8) | rx_frame.data.u8[1]);
|
||||
uint16_t priorTargetBatteryVoltage = x102_chg_session.TargetBatteryVoltage;
|
||||
uint8_t newChargingCurrentRequest = rx_frame.data.u8[3];
|
||||
uint8_t priorChargingCurrentRequest = x102_chg_session.ChargingCurrentRequest;
|
||||
|
||||
vehicle_can_initialized = true;
|
||||
|
||||
|
@ -187,6 +186,7 @@ inline void process_vehicle_charging_session(CAN_frame rx_frame) {
|
|||
x102_chg_session.s.status.StatusChargingError = bitRead(rx_frame.data.u8[5], 2);
|
||||
x102_chg_session.s.status.StatusVehicle = bitRead(rx_frame.data.u8[5], 3);
|
||||
x102_chg_session.s.status.StatusNormalStopRequest = bitRead(rx_frame.data.u8[5], 4);
|
||||
x102_chg_session.s.status.StatusVehicleDischargeCompatible = bitRead(rx_frame.data.u8[5], 7);
|
||||
|
||||
x102_chg_session.StateOfCharge = rx_frame.data.u8[6];
|
||||
|
||||
|
@ -308,7 +308,7 @@ inline void process_vehicle_charging_session(CAN_frame rx_frame) {
|
|||
inline void process_vehicle_charging_limits(CAN_frame rx_frame) {
|
||||
|
||||
x200_discharge_limits.MaximumDischargeCurrent = rx_frame.data.u8[0];
|
||||
x200_discharge_limits.MinimumDischargeVoltage = ((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]);
|
||||
x200_discharge_limits.MinimumDischargeVoltage = ((rx_frame.data.u8[5] << 8) | rx_frame.data.u8[4]);
|
||||
x200_discharge_limits.MinimumBatteryDischargeLevel = rx_frame.data.u8[6];
|
||||
x200_discharge_limits.MaxRemainingCapacityForCharging = rx_frame.data.u8[7];
|
||||
|
||||
|
@ -338,15 +338,15 @@ inline void process_vehicle_discharge_estimate(CAN_frame rx_frame) {
|
|||
unsigned long currentMillis = millis();
|
||||
|
||||
x201_discharge_estimate.V2HchargeDischargeSequenceNum = rx_frame.data.u8[0];
|
||||
x201_discharge_estimate.ApproxDischargeCompletionTime = ((rx_frame.data.u8[1] << 8) | rx_frame.data.u8[2]);
|
||||
x201_discharge_estimate.AvailableVehicleEnergy = ((rx_frame.data.u8[3] << 8) | rx_frame.data.u8[4]);
|
||||
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
|
||||
if (currentMillis - previousMillis5000 >= INTERVAL_5_S) {
|
||||
previousMillis5000 = currentMillis;
|
||||
Serial.println("x201 availabile vehicle energy, completion time");
|
||||
Serial.print("x201 availabile vehicle energy, completion time: ");
|
||||
Serial.println(x201_discharge_estimate.AvailableVehicleEnergy);
|
||||
Serial.println("x201 approx vehicle completion time");
|
||||
Serial.print("x201 approx vehicle completion time: ");
|
||||
Serial.println(x201_discharge_estimate.ApproxDischargeCompletionTime);
|
||||
}
|
||||
#endif
|
||||
|
@ -364,7 +364,7 @@ inline void process_vehicle_dynamic_control(CAN_frame rx_frame) {
|
|||
inline void process_vehicle_vendor_ID(CAN_frame rx_frame) {
|
||||
x700_vendor_id.AutomakerCode = rx_frame.data.u8[0];
|
||||
x700_vendor_id.OptionalContent =
|
||||
((rx_frame.data.u8[1] << 8) | rx_frame.data.u8[2]); //Actually more bytes, but not needed for our purpose
|
||||
((rx_frame.data.u8[2] << 8) | rx_frame.data.u8[1]); //Actually more bytes, but not needed for our purpose
|
||||
}
|
||||
|
||||
void receive_can_battery(CAN_frame rx_frame) {
|
||||
|
@ -557,7 +557,7 @@ void update_evse_status(CAN_frame& f) {
|
|||
*
|
||||
*/
|
||||
if ((x102_chg_session.TargetBatteryVoltage > x108_evse_cap.available_output_voltage) ||
|
||||
(x100_chg_lim.MaximumBatteryVoltage > x108_evse_cap.threshold_voltage)) {
|
||||
(x100_chg_lim.MaximumBatteryVoltage < x108_evse_cap.threshold_voltage)) {
|
||||
//Toggle battery incompatibility flag 109.5.3
|
||||
x109_evse_state.s.status.EVSE_error = 1;
|
||||
x109_evse_state.s.status.battery_incompatible = 1;
|
||||
|
@ -602,7 +602,8 @@ void update_evse_discharge_estimate(CAN_frame& f) {
|
|||
*/
|
||||
|
||||
CHADEMO_209.data.u8[0] = x209_evse_dischg_est.sequence_control_number;
|
||||
CHADEMO_209.data.u8[1] = x209_evse_dischg_est.remaining_discharge_time;
|
||||
CHADEMO_209.data.u8[1] = lowByte(x209_evse_dischg_est.remaining_discharge_time);
|
||||
CHADEMO_209.data.u8[2] = highByte(x209_evse_dischg_est.remaining_discharge_time);
|
||||
}
|
||||
|
||||
/* x208 EVSE, peer to 0x200 Vehicle */
|
||||
|
@ -751,7 +752,7 @@ void handle_chademo_sequence() {
|
|||
|
||||
/* ------------------- State override conditions checks ------------------- */
|
||||
/* ------------------------------------------------------------------------------ */
|
||||
if (CHADEMO_Status >= CHADEMO_EV_ALLOWED && !x102_chg_session.s.status.StatusVehicleShifterPosition) {
|
||||
if (CHADEMO_Status >= CHADEMO_EV_ALLOWED && x102_chg_session.s.status.StatusVehicleShifterPosition) {
|
||||
#ifdef DEBUG_VIA_USB
|
||||
Serial.println("Vehicle is not parked, abort.");
|
||||
#endif
|
||||
|
@ -777,7 +778,6 @@ void handle_chademo_sequence() {
|
|||
#ifdef DEBUG_VIA_USB
|
||||
// Commented unless needed for debug
|
||||
// Serial.println("CHADEMO plug is not inserted.");
|
||||
//
|
||||
#endif
|
||||
return;
|
||||
}
|
||||
|
@ -1031,9 +1031,18 @@ void handle_chademo_sequence() {
|
|||
}
|
||||
|
||||
void setup_battery(void) { // Performs one time setup at startup
|
||||
#ifdef DEBUG_VIA_USB
|
||||
Serial.println("Chademo battery selected");
|
||||
#endif
|
||||
|
||||
pinMode(CHADEMO_PIN_2, OUTPUT);
|
||||
digitalWrite(CHADEMO_PIN_2, LOW);
|
||||
pinMode(CHADEMO_PIN_10, OUTPUT);
|
||||
digitalWrite(CHADEMO_PIN_10, LOW);
|
||||
pinMode(CHADEMO_LOCK, OUTPUT);
|
||||
digitalWrite(CHADEMO_LOCK, LOW);
|
||||
pinMode(CHADEMO_PIN_4, INPUT);
|
||||
pinMode(CHADEMO_PIN_7, INPUT);
|
||||
|
||||
strncpy(datalayer.system.info.battery_protocol, "Chademo V2X mode", 63);
|
||||
datalayer.system.info.battery_protocol[63] = '\0';
|
||||
|
||||
CHADEMO_Status = CHADEMO_IDLE;
|
||||
|
||||
|
@ -1075,6 +1084,9 @@ void setup_battery(void) { // Performs one time setup at startup
|
|||
x109_evse_state.s.status.ChgDischStopControl = 1;
|
||||
|
||||
handle_chademo_sequence();
|
||||
// ISA_deFAULT(); // ISA Setup - it is sufficient to set it once, because it is saved in SUNT
|
||||
// ISA_initialize(); // ISA Setup - it is sufficient to set it once, because it is saved in SUNT
|
||||
// ISA_RESTART();
|
||||
|
||||
setupMillis = millis();
|
||||
}
|
||||
|
|
|
@ -12,6 +12,11 @@
|
|||
*
|
||||
* 2024 - Modified to make use of ESP32-Arduino-CAN by miwagner
|
||||
*
|
||||
* 2024.11 - Modified byte sequence to Big Endian (this is the default for IVT) and the same as CHAdeMO
|
||||
* - Fixed and Added send functions
|
||||
* - Added some GET functions
|
||||
* by NJbubo
|
||||
*
|
||||
*/
|
||||
#include "../include.h"
|
||||
#ifdef CHADEMO_BATTERY
|
||||
|
@ -74,16 +79,29 @@ uint16_t get_measured_current() {
|
|||
}
|
||||
|
||||
//This is our CAN interrupt service routine to catch inbound frames
|
||||
inline void ISA_handleFrame(CAN_frame* frame) {
|
||||
void ISA_handleFrame(CAN_frame* frame) {
|
||||
|
||||
if (frame->ID < 0x521 || frame->ID > 0x528) {
|
||||
if (frame->ID < 0x510 || frame->ID > 0x528) {
|
||||
return;
|
||||
}
|
||||
|
||||
framecount++;
|
||||
|
||||
switch (frame->ID) {
|
||||
|
||||
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(" ");
|
||||
for (int i = 0; i < frame->DLC; ++i) {
|
||||
Serial.print(frame->data.u8[i], HEX);
|
||||
Serial.print(" ");
|
||||
}
|
||||
Serial.println("");
|
||||
break;
|
||||
|
||||
case 0x521:
|
||||
|
@ -118,7 +136,6 @@ inline void ISA_handleFrame(CAN_frame* frame) {
|
|||
ISA_handle528(frame);
|
||||
break;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -126,7 +143,7 @@ inline void ISA_handleFrame(CAN_frame* frame) {
|
|||
inline void ISA_handle521(CAN_frame* frame) {
|
||||
long current = 0;
|
||||
current =
|
||||
(long)((frame->data.u8[5] << 24) | (frame->data.u8[4] << 16) | (frame->data.u8[3] << 8) | (frame->data.u8[2]));
|
||||
(long)((frame->data.u8[2] << 24) | (frame->data.u8[3] << 16) | (frame->data.u8[4] << 8) | (frame->data.u8[5]));
|
||||
|
||||
milliamps = current;
|
||||
Amperes = current / 1000.0f;
|
||||
|
@ -135,7 +152,7 @@ inline void ISA_handle521(CAN_frame* frame) {
|
|||
//handle frame for Voltage
|
||||
inline void ISA_handle522(CAN_frame* frame) {
|
||||
long volt =
|
||||
(long)((frame->data.u8[5] << 24) | (frame->data.u8[4] << 16) | (frame->data.u8[3] << 8) | (frame->data.u8[2]));
|
||||
(long)((frame->data.u8[2] << 24) | (frame->data.u8[3] << 16) | (frame->data.u8[4] << 8) | (frame->data.u8[5]));
|
||||
|
||||
Voltage = volt / 1000.0f;
|
||||
Voltage1 = Voltage - (Voltage2 + Voltage3);
|
||||
|
@ -158,7 +175,7 @@ inline void ISA_handle522(CAN_frame* frame) {
|
|||
//handle frame for Voltage 2
|
||||
inline void ISA_handle523(CAN_frame* frame) {
|
||||
long volt =
|
||||
(long)((frame->data.u8[5] << 24) | (frame->data.u8[4] << 16) | (frame->data.u8[3] << 8) | (frame->data.u8[2]));
|
||||
(long)((frame->data.u8[2] << 24) | (frame->data.u8[3] << 16) | (frame->data.u8[4] << 8) | (frame->data.u8[5]));
|
||||
|
||||
Voltage2 = volt / 1000.0f;
|
||||
if (Voltage2 > 3)
|
||||
|
@ -177,7 +194,7 @@ inline void ISA_handle523(CAN_frame* frame) {
|
|||
//handle frame for Voltage3
|
||||
inline void ISA_handle524(CAN_frame* frame) {
|
||||
long volt =
|
||||
(long)((frame->data.u8[5] << 24) | (frame->data.u8[4] << 16) | (frame->data.u8[3] << 8) | (frame->data.u8[2]));
|
||||
(long)((frame->data.u8[2] << 24) | (frame->data.u8[3] << 16) | (frame->data.u8[4] << 8) | (frame->data.u8[5]));
|
||||
|
||||
Voltage3 = volt / 1000.0f;
|
||||
|
||||
|
@ -194,7 +211,7 @@ inline void ISA_handle524(CAN_frame* frame) {
|
|||
//handle frame for Temperature
|
||||
inline void ISA_handle525(CAN_frame* frame) {
|
||||
long temp = 0;
|
||||
temp = (long)((frame->data.u8[5] << 24) | (frame->data.u8[4] << 16) | (frame->data.u8[3] << 8) | (frame->data.u8[2]));
|
||||
temp = (long)((frame->data.u8[2] << 24) | (frame->data.u8[3] << 16) | (frame->data.u8[4] << 8) | (frame->data.u8[5]));
|
||||
|
||||
Temperature = temp / 10;
|
||||
}
|
||||
|
@ -202,14 +219,15 @@ inline void ISA_handle525(CAN_frame* frame) {
|
|||
//handle frame for Kilowatts
|
||||
inline void ISA_handle526(CAN_frame* frame) {
|
||||
watt = 0;
|
||||
watt = (long)((frame->data.u8[5] << 24) | (frame->data.u8[4] << 16) | (frame->data.u8[3] << 8) | (frame->data.u8[2]));
|
||||
watt = (long)((frame->data.u8[2] << 24) | (frame->data.u8[3] << 16) | (frame->data.u8[4] << 8) | (frame->data.u8[5]));
|
||||
|
||||
KW = watt / 1000.0f;
|
||||
}
|
||||
|
||||
//handle frame for Ampere-Hours
|
||||
inline void ISA_handle527(CAN_frame* frame) {
|
||||
As = 0;
|
||||
As = (frame->data.u8[5] << 24) | (frame->data.u8[4] << 16) | (frame->data.u8[3] << 8) | (frame->data.u8[2]);
|
||||
As = (long)(frame->data.u8[2] << 24) | (frame->data.u8[3] << 16) | (frame->data.u8[4] << 8) | (frame->data.u8[5]);
|
||||
|
||||
AH += (As - lastAs) / 3600.0f;
|
||||
lastAs = As;
|
||||
|
@ -217,21 +235,21 @@ inline void ISA_handle527(CAN_frame* frame) {
|
|||
|
||||
//handle frame for kiloWatt-hours
|
||||
inline void ISA_handle528(CAN_frame* frame) {
|
||||
wh = (long)((frame->data.u8[5] << 24) | (frame->data.u8[4] << 16) | (frame->data.u8[3] << 8) | (frame->data.u8[2]));
|
||||
wh = (long)((frame->data.u8[2] << 24) | (frame->data.u8[3] << 16) | (frame->data.u8[4] << 8) | (frame->data.u8[5]));
|
||||
KWH += (wh - lastWh) / 1000.0f;
|
||||
lastWh = wh;
|
||||
}
|
||||
|
||||
/*
|
||||
void ISA_initialize() {
|
||||
firstframe = false;
|
||||
STOP();
|
||||
delay(700);
|
||||
for(int i=0;i<9;i++) {
|
||||
Serial.println("initialization \n");
|
||||
ISA_STOP();
|
||||
delay(500);
|
||||
for (int i = 0; i < 8; i++) {
|
||||
Serial.print("ISA Initialization ");
|
||||
Serial.println(i);
|
||||
|
||||
outframe.data.u8[0] = (0x20 + i);
|
||||
outframe.data.u8[1]=0x42;
|
||||
outframe.data.u8[1] = 0x02;
|
||||
outframe.data.u8[2] = 0x02;
|
||||
outframe.data.u8[3] = (0x60 + (i * 18));
|
||||
outframe.data.u8[4] = 0x00;
|
||||
|
@ -239,22 +257,22 @@ void ISA_initialize() {
|
|||
outframe.data.u8[6] = 0x00;
|
||||
outframe.data.u8[7] = 0x00;
|
||||
|
||||
transmit_can((&outframe, can_config.battery);
|
||||
|
||||
delay(500);
|
||||
|
||||
sendSTORE();
|
||||
transmit_can(&outframe, can_config.battery);
|
||||
delay(500);
|
||||
}
|
||||
|
||||
START();
|
||||
ISA_sendSTORE();
|
||||
delay(500);
|
||||
|
||||
ISA_START();
|
||||
delay(500);
|
||||
lastAs = As;
|
||||
lastWh = wh;
|
||||
|
||||
}
|
||||
|
||||
void ISA_STOP() {
|
||||
Serial.println("ISA STOP");
|
||||
|
||||
outframe.data.u8[0] = 0x34;
|
||||
outframe.data.u8[1] = 0x00;
|
||||
outframe.data.u8[2] = 0x01;
|
||||
|
@ -263,11 +281,13 @@ void ISA_STOP() {
|
|||
outframe.data.u8[5] = 0x00;
|
||||
outframe.data.u8[6] = 0x00;
|
||||
outframe.data.u8[7] = 0x00;
|
||||
transmit_can((&outframe, can_config.battery);
|
||||
|
||||
transmit_can(&outframe, can_config.battery);
|
||||
}
|
||||
|
||||
void ISA_sendSTORE() {
|
||||
Serial.println("ISA send STORE");
|
||||
|
||||
outframe.data.u8[0] = 0x32;
|
||||
outframe.data.u8[1] = 0x00;
|
||||
outframe.data.u8[2] = 0x00;
|
||||
|
@ -276,10 +296,13 @@ void ISA_sendSTORE() {
|
|||
outframe.data.u8[5] = 0x00;
|
||||
outframe.data.u8[6] = 0x00;
|
||||
outframe.data.u8[7] = 0x00;
|
||||
transmit_can((&outframe, can_config.battery);
|
||||
|
||||
transmit_can(&outframe, can_config.battery);
|
||||
}
|
||||
|
||||
void ISA_START() {
|
||||
Serial.println("ISA START");
|
||||
|
||||
outframe.data.u8[0] = 0x34;
|
||||
outframe.data.u8[1] = 0x01;
|
||||
outframe.data.u8[2] = 0x01;
|
||||
|
@ -288,11 +311,14 @@ void ISA_START() {
|
|||
outframe.data.u8[5] = 0x00;
|
||||
outframe.data.u8[6] = 0x00;
|
||||
outframe.data.u8[7] = 0x00;
|
||||
transmit_can((&outframe, can_config.battery);
|
||||
|
||||
transmit_can(&outframe, can_config.battery);
|
||||
}
|
||||
|
||||
void ISA_RESTART() {
|
||||
//Has the effect of zeroing AH and KWH
|
||||
Serial.println("ISA RESTART");
|
||||
|
||||
outframe.data.u8[0] = 0x3F;
|
||||
outframe.data.u8[1] = 0x00;
|
||||
outframe.data.u8[2] = 0x00;
|
||||
|
@ -301,11 +327,17 @@ void ISA_RESTART() {
|
|||
outframe.data.u8[5] = 0x00;
|
||||
outframe.data.u8[6] = 0x00;
|
||||
outframe.data.u8[7] = 0x00;
|
||||
transmit_can((&outframe, can_config.battery);
|
||||
|
||||
transmit_can(&outframe, can_config.battery);
|
||||
}
|
||||
|
||||
void ISA_deFAULT() {
|
||||
//Returns module to original defaults
|
||||
ISA_STOP();
|
||||
delay(500);
|
||||
|
||||
Serial.println("ISA RESTART to default");
|
||||
|
||||
outframe.data.u8[0] = 0x3D;
|
||||
outframe.data.u8[1] = 0x00;
|
||||
outframe.data.u8[2] = 0x00;
|
||||
|
@ -314,17 +346,22 @@ void ISA_deFAULT() {
|
|||
outframe.data.u8[5] = 0x00;
|
||||
outframe.data.u8[6] = 0x00;
|
||||
outframe.data.u8[7] = 0x00;
|
||||
transmit_can((&outframe, can_config.battery);
|
||||
|
||||
transmit_can(&outframe, can_config.battery);
|
||||
delay(500);
|
||||
|
||||
ISA_START();
|
||||
delay(500);
|
||||
}
|
||||
|
||||
void ISA_initCurrent() {
|
||||
STOP();
|
||||
ISA_STOP();
|
||||
delay(500);
|
||||
|
||||
Serial.println("initialization \n");
|
||||
Serial.println("ISA Initialization Current");
|
||||
|
||||
outframe.data.u8[0] = 0x21;
|
||||
outframe.data.u8[1]=0x42;
|
||||
outframe.data.u8[1] = 0x02;
|
||||
outframe.data.u8[2] = 0x01;
|
||||
outframe.data.u8[3] = 0x61;
|
||||
outframe.data.u8[4] = 0x00;
|
||||
|
@ -332,18 +369,67 @@ void ISA_initCurrent() {
|
|||
outframe.data.u8[6] = 0x00;
|
||||
outframe.data.u8[7] = 0x00;
|
||||
|
||||
transmit_can((&outframe, can_config.battery);
|
||||
|
||||
transmit_can(&outframe, can_config.battery);
|
||||
delay(500);
|
||||
|
||||
sendSTORE();
|
||||
ISA_sendSTORE();
|
||||
delay(500);
|
||||
|
||||
START();
|
||||
ISA_START();
|
||||
delay(500);
|
||||
lastAs = As;
|
||||
lastWh = wh;
|
||||
}
|
||||
*/
|
||||
|
||||
void ISA_getCONFIG(uint8_t i) {
|
||||
Serial.print("ISA Get Config ");
|
||||
Serial.println(i);
|
||||
|
||||
outframe.data.u8[0] = (0x60 + i);
|
||||
outframe.data.u8[1] = 0x00;
|
||||
outframe.data.u8[2] = 0x00;
|
||||
outframe.data.u8[3] = 0x00;
|
||||
outframe.data.u8[4] = 0x00;
|
||||
outframe.data.u8[5] = 0x00;
|
||||
outframe.data.u8[6] = 0x00;
|
||||
outframe.data.u8[7] = 0x00;
|
||||
|
||||
transmit_can(&outframe, can_config.battery);
|
||||
}
|
||||
|
||||
void ISA_getCAN_ID(uint8_t i) {
|
||||
Serial.print("ISA Get CAN ID ");
|
||||
Serial.println(i);
|
||||
|
||||
outframe.data.u8[0] = (0x50 + i);
|
||||
if (i == 8)
|
||||
outframe.data.u8[0] = 0x5D;
|
||||
if (i == 9)
|
||||
outframe.data.u8[0] = 0x5F;
|
||||
outframe.data.u8[1] = 0x00;
|
||||
outframe.data.u8[2] = 0x00;
|
||||
outframe.data.u8[3] = 0x00;
|
||||
outframe.data.u8[4] = 0x00;
|
||||
outframe.data.u8[5] = 0x00;
|
||||
outframe.data.u8[6] = 0x00;
|
||||
outframe.data.u8[7] = 0x00;
|
||||
|
||||
transmit_can(&outframe, can_config.battery);
|
||||
}
|
||||
|
||||
void ISA_getINFO(uint8_t i) {
|
||||
Serial.print("ISA Get INFO ");
|
||||
Serial.println(i, HEX);
|
||||
|
||||
outframe.data.u8[0] = (0x70 + i);
|
||||
outframe.data.u8[1] = 0x00;
|
||||
outframe.data.u8[2] = 0x00;
|
||||
outframe.data.u8[3] = 0x00;
|
||||
outframe.data.u8[4] = 0x00;
|
||||
outframe.data.u8[5] = 0x00;
|
||||
outframe.data.u8[6] = 0x00;
|
||||
outframe.data.u8[7] = 0x00;
|
||||
|
||||
transmit_can(&outframe, can_config.battery);
|
||||
}
|
||||
#endif
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
|
||||
uint16_t get_measured_voltage();
|
||||
uint16_t get_measured_current();
|
||||
inline void ISA_handler(CAN_frame* frame);
|
||||
void ISA_handleFrame(CAN_frame* frame);
|
||||
inline void ISA_handle521(CAN_frame* frame);
|
||||
inline void ISA_handle522(CAN_frame* frame);
|
||||
inline void ISA_handle523(CAN_frame* frame);
|
||||
|
@ -14,6 +14,16 @@ inline void ISA_handle525(CAN_frame* frame);
|
|||
inline void ISA_handle526(CAN_frame* frame);
|
||||
inline void ISA_handle527(CAN_frame* frame);
|
||||
inline void ISA_handle528(CAN_frame* frame);
|
||||
void ISA_initialize();
|
||||
void ISA_STOP();
|
||||
void ISA_sendSTORE();
|
||||
void ISA_START();
|
||||
void ISA_RESTART();
|
||||
void ISA_deFAULT();
|
||||
void ISA_initCurrent();
|
||||
void ISA_getCONFIG(uint8_t i);
|
||||
void ISA_getCAN_ID(uint8_t i);
|
||||
void ISA_getINFO(uint8_t i);
|
||||
|
||||
void transmit_can(CAN_frame* tx_frame, int interface);
|
||||
|
||||
|
|
|
@ -224,9 +224,8 @@ void send_can_battery() {
|
|||
}
|
||||
|
||||
void setup_battery(void) { // Performs one time setup at startup
|
||||
#ifdef DEBUG_VIA_USB
|
||||
Serial.println("Mitsubishi i-MiEV / Citroen C-Zero / Peugeot Ion battery selected");
|
||||
#endif
|
||||
strncpy(datalayer.system.info.battery_protocol, "I-Miev / C-Zero / Ion Triplet", 63);
|
||||
datalayer.system.info.battery_protocol[63] = '\0';
|
||||
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;
|
||||
|
|
|
@ -254,10 +254,8 @@ void send_can_battery() {
|
|||
}
|
||||
|
||||
void setup_battery(void) { // Performs one time setup at startup
|
||||
#ifdef DEBUG_VIA_USB
|
||||
Serial.println("Jaguar iPace 90kWh battery selected");
|
||||
#endif
|
||||
|
||||
strncpy(datalayer.system.info.battery_protocol, "Jaguar I-PACE", 63);
|
||||
datalayer.system.info.battery_protocol[63] = '\0';
|
||||
datalayer.battery.info.number_of_cells = 108;
|
||||
datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_DV;
|
||||
datalayer.battery.info.min_design_voltage_dV = MIN_PACK_VOLTAGE_DV;
|
||||
|
|
|
@ -1037,14 +1037,12 @@ void send_can_battery() {
|
|||
}
|
||||
|
||||
void setup_battery(void) { // Performs one time setup at startup
|
||||
#ifdef DEBUG_VIA_USB
|
||||
Serial.println("Hyundai E-GMP (Electric Global Modular Platform) battery selected");
|
||||
#endif
|
||||
strncpy(datalayer.system.info.battery_protocol, "Kia/Hyundai EGMP platform", 63);
|
||||
datalayer.system.info.battery_protocol[63] = '\0';
|
||||
|
||||
startMillis = millis(); // Record the starting time
|
||||
|
||||
datalayer.system.status.battery_allows_contactor_closing = true;
|
||||
|
||||
datalayer.battery.info.number_of_cells = 192; // TODO: will vary depending on battery
|
||||
datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_DV;
|
||||
datalayer.battery.info.min_design_voltage_dV = MIN_PACK_VOLTAGE_DV;
|
||||
|
|
|
@ -534,9 +534,8 @@ void send_can_battery() {
|
|||
}
|
||||
|
||||
void setup_battery(void) { // Performs one time setup at startup
|
||||
#ifdef DEBUG_VIA_USB
|
||||
Serial.println("Kia Niro / Hyundai Kona 64kWh battery selected");
|
||||
#endif
|
||||
strncpy(datalayer.system.info.battery_protocol, "Kia/Hyundai 64/40kWh battery", 63);
|
||||
datalayer.system.info.battery_protocol[63] = '\0';
|
||||
datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_98S_DV; //Start with 98S value. Precised later
|
||||
datalayer.battery.info.min_design_voltage_dV = MIN_PACK_VOLTAGE_90S_DV; //Start with 90S value. Precised later
|
||||
datalayer.battery.info.max_cell_voltage_mV = MAX_CELL_VOLTAGE_MV;
|
||||
|
|
|
@ -257,9 +257,9 @@ void send_can_battery() {
|
|||
}
|
||||
|
||||
void setup_battery(void) { // Performs one time setup at startup
|
||||
#ifdef DEBUG_VIA_USB
|
||||
Serial.println("Kia/Hyundai Hybrid battery selected");
|
||||
#endif
|
||||
strncpy(datalayer.system.info.battery_protocol, "Kia/Hyundai Hybrid", 63);
|
||||
datalayer.system.info.battery_protocol[63] = '\0';
|
||||
|
||||
datalayer.battery.info.number_of_cells = 56; // HEV , TODO: Make dynamic according to HEV/PHEV
|
||||
datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_DV;
|
||||
datalayer.battery.info.min_design_voltage_dV = MIN_PACK_VOLTAGE_DV;
|
||||
|
|
|
@ -135,9 +135,8 @@ void send_can_battery() {
|
|||
}
|
||||
|
||||
void setup_battery(void) { // Performs one time setup at startup
|
||||
#ifdef DEBUG_VIA_USB
|
||||
Serial.println("MG 5 battery selected");
|
||||
#endif
|
||||
strncpy(datalayer.system.info.battery_protocol, "MG 5 battery", 63);
|
||||
datalayer.system.info.battery_protocol[63] = '\0';
|
||||
|
||||
datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_DV;
|
||||
datalayer.battery.info.min_design_voltage_dV = MIN_PACK_VOLTAGE_DV;
|
||||
|
|
|
@ -174,11 +174,17 @@ static int16_t battery2_temp_polled_max = 0;
|
|||
static int16_t battery2_temp_polled_min = 0;
|
||||
#endif // DOUBLE_BATTERY
|
||||
|
||||
void print_with_units(char* header, int value, char* units) {
|
||||
Serial.print(header);
|
||||
Serial.print(value);
|
||||
Serial.print(units);
|
||||
}
|
||||
// Clear SOH values
|
||||
static uint8_t stateMachineClearSOH = 0xFF;
|
||||
static uint32_t incomingChallenge = 0xFFFFFFFF;
|
||||
static uint8_t solvedChallenge[8];
|
||||
static bool challengeFailed = false;
|
||||
|
||||
CAN_frame LEAF_CLEAR_SOH = {.FD = false,
|
||||
.ext_ID = false,
|
||||
.DLC = 8,
|
||||
.ID = 0x79B,
|
||||
.data = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}};
|
||||
|
||||
void update_values_battery() { /* This function maps all the values fetched via CAN to the correct parameters used for modbus */
|
||||
/* Start with mapping all values */
|
||||
|
@ -334,17 +340,18 @@ void update_values_battery() { /* This function maps all the values fetched via
|
|||
datalayer_extended.nissanleaf.HeatingStop = battery_Heating_Stop;
|
||||
datalayer_extended.nissanleaf.HeatingStart = battery_Heating_Start;
|
||||
datalayer_extended.nissanleaf.HeaterSendRequest = battery_Batt_Heater_Mail_Send_Request;
|
||||
datalayer_extended.nissanleaf.CryptoChallenge = incomingChallenge;
|
||||
datalayer_extended.nissanleaf.SolvedChallengeMSB =
|
||||
((solvedChallenge[7] << 24) | (solvedChallenge[6] << 16) | (solvedChallenge[5] << 8) | solvedChallenge[4]);
|
||||
datalayer_extended.nissanleaf.SolvedChallengeLSB =
|
||||
((solvedChallenge[3] << 24) | (solvedChallenge[2] << 16) | (solvedChallenge[1] << 8) | solvedChallenge[0]);
|
||||
datalayer_extended.nissanleaf.challengeFailed = challengeFailed;
|
||||
|
||||
/*Finally print out values to serial if configured to do so*/
|
||||
#ifdef DEBUG_VIA_USB
|
||||
Serial.println("Values from battery");
|
||||
print_with_units("Real SOC%: ", (battery_SOC * 0.1), "% ");
|
||||
print_with_units(", GIDS: ", battery_GIDS, " (x77Wh) ");
|
||||
print_with_units(", Battery gen: ", LEAF_battery_Type, " ");
|
||||
print_with_units(", Has heater: ", battery_HeatExist, " ");
|
||||
print_with_units(", Max cell voltage: ", battery_min_max_voltage[1], "mV ");
|
||||
print_with_units(", Min cell voltage: ", battery_min_max_voltage[0], "mV ");
|
||||
#endif
|
||||
// Update requests from webserver datalayer
|
||||
if (datalayer_extended.nissanleaf.UserRequestSOHreset) {
|
||||
stateMachineClearSOH = 0; //Start the statemachine
|
||||
datalayer_extended.nissanleaf.UserRequestSOHreset = false;
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef DOUBLE_BATTERY
|
||||
|
@ -599,7 +606,7 @@ void receive_can_battery2(CAN_frame rx_frame) {
|
|||
}
|
||||
}
|
||||
|
||||
if (stop_battery_query) { //Leafspy is active, stop our own polling
|
||||
if (stop_battery_query) { //Leafspy/Service request is active, stop our own polling
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -836,6 +843,22 @@ void receive_can_battery(CAN_frame rx_frame) {
|
|||
hold_off_with_polling_10seconds = 10; //Polling is paused for 100s
|
||||
break;
|
||||
case 0x7BB:
|
||||
|
||||
// This section checks if we are doing a SOH reset towards BMS
|
||||
if (stateMachineClearSOH < 255) {
|
||||
//Intercept the messages based on state machine
|
||||
if (rx_frame.data.u8[0] == 0x06) { // Incoming challenge data!
|
||||
// BMS should reply with (challenge) 06 67 65 (02 DD 86 43) FF
|
||||
incomingChallenge = ((rx_frame.data.u8[3] << 24) | (rx_frame.data.u8[4] << 16) | (rx_frame.data.u8[5] << 8) |
|
||||
rx_frame.data.u8[6]);
|
||||
}
|
||||
//Error checking
|
||||
if ((rx_frame.data.u8[0] == 0x03) && (rx_frame.data.u8[1] == 0x7F)) {
|
||||
challengeFailed = true;
|
||||
}
|
||||
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];
|
||||
|
@ -1116,6 +1139,10 @@ void send_can_battery() {
|
|||
if (currentMillis - previousMillis100 >= INTERVAL_100_MS) {
|
||||
previousMillis100 = currentMillis;
|
||||
|
||||
if (stateMachineClearSOH < 255) { // Enter the ClearSOH statemachine only if we request it
|
||||
clearSOH();
|
||||
}
|
||||
|
||||
//When battery requests heating pack status change, ack this
|
||||
if (battery_Batt_Heater_Mail_Send_Request) {
|
||||
LEAF_50B.data.u8[6] = 0x20; //Batt_Heater_Mail_Send_OK
|
||||
|
@ -1219,10 +1246,189 @@ uint16_t Temp_fromRAW_to_F(uint16_t temperature) { //This function feels horrib
|
|||
return static_cast<uint16_t>(1094 + (309 - temperature) * 2.5714285714285715);
|
||||
}
|
||||
|
||||
void clearSOH(void) {
|
||||
stop_battery_query = true;
|
||||
hold_off_with_polling_10seconds = 10; // Active battery polling is paused for 100 seconds
|
||||
|
||||
switch (stateMachineClearSOH) {
|
||||
case 0: // Wait until polling actually stops
|
||||
challengeFailed = false;
|
||||
stateMachineClearSOH = 1;
|
||||
break;
|
||||
case 1: // Set CAN_PROCESS_FLAG to 0xC0
|
||||
LEAF_CLEAR_SOH.data = {0x02, 0x10, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00};
|
||||
transmit_can(&LEAF_CLEAR_SOH, can_config.battery);
|
||||
// BMS should reply 02 50 C0 FF FF FF FF FF
|
||||
stateMachineClearSOH = 2;
|
||||
break;
|
||||
case 2: // Set something ?
|
||||
LEAF_CLEAR_SOH.data = {0x02, 0x3E, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00};
|
||||
transmit_can(&LEAF_CLEAR_SOH, can_config.battery);
|
||||
// BMS should reply 7E FF FF FF FF FF FF
|
||||
stateMachineClearSOH = 3;
|
||||
break;
|
||||
case 3: // Request challenge to solve
|
||||
LEAF_CLEAR_SOH.data = {0x02, 0x27, 0x65, 0x00, 0x00, 0x00, 0x00, 0x00};
|
||||
transmit_can(&LEAF_CLEAR_SOH, can_config.battery);
|
||||
// BMS should reply with (challenge) 06 67 65 (02 DD 86 43) FF
|
||||
stateMachineClearSOH = 4;
|
||||
break;
|
||||
case 4: // Send back decoded challenge data
|
||||
decodeChallengeData(incomingChallenge, solvedChallenge);
|
||||
LEAF_CLEAR_SOH.data = {
|
||||
0x10, 0x0A, 0x27, 0x66, solvedChallenge[0], solvedChallenge[1], solvedChallenge[2], solvedChallenge[3]};
|
||||
transmit_can(&LEAF_CLEAR_SOH, can_config.battery);
|
||||
// BMS should reply 7BB 8 30 01 00 FF FF FF FF FF // Proceed with more data (PID ACK)
|
||||
stateMachineClearSOH = 5;
|
||||
break;
|
||||
case 5: // Reply with even more decoded challenge data
|
||||
LEAF_CLEAR_SOH.data = {
|
||||
0x21, solvedChallenge[4], solvedChallenge[5], solvedChallenge[6], solvedChallenge[7], 0x00, 0x00, 0x00};
|
||||
transmit_can(&LEAF_CLEAR_SOH, can_config.battery);
|
||||
// BMS should reply 02 67 66 FF FF FF FF FF // Thank you for the data
|
||||
stateMachineClearSOH = 6;
|
||||
break;
|
||||
case 6: // Check if solved data was OK
|
||||
LEAF_CLEAR_SOH.data = {0x03, 0x31, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00};
|
||||
transmit_can(&LEAF_CLEAR_SOH, can_config.battery);
|
||||
//7BB 8 03 71 03 01 FF FF FF FF // If all is well, BMS replies with 03 71 03 01.
|
||||
//Incase you sent wrong challenge, you get 03 7f 31 12
|
||||
stateMachineClearSOH = 7;
|
||||
break;
|
||||
case 7: // Reset SOH% request
|
||||
LEAF_CLEAR_SOH.data = {0x03, 0x31, 0x03, 0x01, 0x00, 0x00, 0x00, 0x00};
|
||||
transmit_can(&LEAF_CLEAR_SOH, can_config.battery);
|
||||
//7BB 8 03 71 03 02 FF FF FF FF // 03 71 03 02 means that BMS accepted command.
|
||||
//7BB 03 7f 31 12 means your challenge was wrong, so command ignored
|
||||
stateMachineClearSOH = 8;
|
||||
break;
|
||||
case 8: // Please proceed with resetting SOH
|
||||
LEAF_CLEAR_SOH.data = {0x02, 0x10, 0x81, 0x00, 0x00, 0x00, 0x00, 0x00};
|
||||
transmit_can(&LEAF_CLEAR_SOH, can_config.battery);
|
||||
// 7BB 8 02 50 81 FF FF FF FF FF // SOH reset OK
|
||||
stateMachineClearSOH = 255;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
unsigned int CyclicXorHash16Bit(unsigned int param_1, unsigned int param_2) {
|
||||
bool bVar1;
|
||||
unsigned int uVar2, uVar3, uVar4, uVar5, uVar6, uVar7, uVar8, uVar9, uVar10, uVar11, iVar12;
|
||||
|
||||
param_1 = param_1 & 0xffff;
|
||||
param_2 = param_2 & 0xffff;
|
||||
uVar10 = 0xffff;
|
||||
iVar12 = 2;
|
||||
do {
|
||||
uVar2 = param_2;
|
||||
if ((param_1 & 1) == 1) {
|
||||
uVar2 = param_1 >> 1;
|
||||
}
|
||||
uVar3 = param_2;
|
||||
if ((param_1 >> 1 & 1) == 1) {
|
||||
uVar3 = param_1 >> 2;
|
||||
}
|
||||
uVar4 = param_2;
|
||||
if ((param_1 >> 2 & 1) == 1) {
|
||||
uVar4 = param_1 >> 3;
|
||||
}
|
||||
uVar5 = param_2;
|
||||
if ((param_1 >> 3 & 1) == 1) {
|
||||
uVar5 = param_1 >> 4;
|
||||
}
|
||||
uVar6 = param_2;
|
||||
if ((param_1 >> 4 & 1) == 1) {
|
||||
uVar6 = param_1 >> 5;
|
||||
}
|
||||
uVar7 = param_2;
|
||||
if ((param_1 >> 5 & 1) == 1) {
|
||||
uVar7 = param_1 >> 6;
|
||||
}
|
||||
uVar11 = param_1 >> 7;
|
||||
uVar8 = param_2;
|
||||
if ((param_1 >> 6 & 1) == 1) {
|
||||
uVar8 = uVar11;
|
||||
}
|
||||
param_1 = param_1 >> 8;
|
||||
uVar9 = param_2;
|
||||
if ((uVar11 & 1) == 1) {
|
||||
uVar9 = param_1;
|
||||
}
|
||||
uVar10 =
|
||||
(((((((((((((((uVar10 & 0x7fff) << 1 ^ uVar2) & 0x7fff) << 1 ^ uVar3) & 0x7fff) << 1 ^ uVar4) & 0x7fff) << 1 ^
|
||||
uVar5) &
|
||||
0x7fff)
|
||||
<< 1 ^
|
||||
uVar6) &
|
||||
0x7fff)
|
||||
<< 1 ^
|
||||
uVar7) &
|
||||
0x7fff)
|
||||
<< 1 ^
|
||||
uVar8) &
|
||||
0x7fff)
|
||||
<< 1 ^
|
||||
uVar9;
|
||||
bVar1 = iVar12 != 1;
|
||||
iVar12 = iVar12 + -1;
|
||||
} while (bVar1);
|
||||
return uVar10;
|
||||
}
|
||||
unsigned int ComputeMaskedXorProduct(unsigned int param_1, unsigned int param_2, unsigned int param_3) {
|
||||
return (param_3 ^ 0x7F88 | param_2 ^ 0x8FE7) * ((param_1 & 0xffff) >> 8 ^ param_1 & 0xff) & 0xffff;
|
||||
}
|
||||
|
||||
short ShortMaskedSumAndProduct(short param_1, short param_2) {
|
||||
unsigned short uVar1;
|
||||
|
||||
uVar1 = param_2 + param_1 * 0x0006 & 0xff;
|
||||
return (uVar1 + param_1) * (uVar1 + param_2);
|
||||
}
|
||||
|
||||
unsigned int MaskedBitwiseRotateMultiply(unsigned int param_1, unsigned int param_2) {
|
||||
unsigned int uVar1;
|
||||
|
||||
param_1 = param_1 & 0xffff;
|
||||
param_2 = param_2 & 0xffff;
|
||||
uVar1 = param_2 & (param_1 | 0x0006) & 0xf;
|
||||
return ((unsigned int)param_1 >> uVar1 | param_1 << (0x10 - uVar1 & 0x1f)) *
|
||||
(param_2 << uVar1 | (unsigned int)param_2 >> (0x10 - uVar1 & 0x1f)) &
|
||||
0xffff;
|
||||
}
|
||||
|
||||
unsigned int CryptAlgo(unsigned int param_1, unsigned int param_2, unsigned int param_3) {
|
||||
unsigned int uVar1, uVar2, iVar3, iVar4;
|
||||
|
||||
uVar1 = MaskedBitwiseRotateMultiply(param_2, param_3);
|
||||
uVar2 = ShortMaskedSumAndProduct(param_2, param_3);
|
||||
uVar1 = ComputeMaskedXorProduct(param_1, uVar1, uVar2);
|
||||
uVar2 = ComputeMaskedXorProduct(param_1, uVar2, uVar1);
|
||||
iVar3 = CyclicXorHash16Bit(uVar1, 0x8421);
|
||||
iVar4 = CyclicXorHash16Bit(uVar2, 0x8421);
|
||||
return iVar4 + iVar3 * 0x10000;
|
||||
}
|
||||
|
||||
void decodeChallengeData(unsigned int incomingChallenge, unsigned char* solvedChallenge) {
|
||||
unsigned int uVar1, uVar2;
|
||||
|
||||
uVar1 = CryptAlgo(0x54e9, 0x3afd, incomingChallenge >> 0x10);
|
||||
uVar2 = CryptAlgo(incomingChallenge & 0xffff, incomingChallenge >> 0x10, 0x54e9);
|
||||
*solvedChallenge = (unsigned char)uVar1;
|
||||
solvedChallenge[1] = (unsigned char)uVar2;
|
||||
solvedChallenge[2] = (unsigned char)((unsigned int)uVar2 >> 8);
|
||||
solvedChallenge[3] = (unsigned char)((unsigned int)uVar1 >> 8);
|
||||
solvedChallenge[4] = (unsigned char)((unsigned int)uVar2 >> 0x10);
|
||||
solvedChallenge[5] = (unsigned char)((unsigned int)uVar1 >> 0x10);
|
||||
solvedChallenge[6] = (unsigned char)((unsigned int)uVar2 >> 0x18);
|
||||
solvedChallenge[7] = (unsigned char)((unsigned int)uVar1 >> 0x18);
|
||||
return;
|
||||
}
|
||||
|
||||
void setup_battery(void) { // Performs one time setup at startup
|
||||
#ifdef DEBUG_VIA_USB
|
||||
Serial.println("Nissan LEAF battery selected");
|
||||
#endif
|
||||
strncpy(datalayer.system.info.battery_protocol, "Nissan LEAF battery", 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;
|
||||
|
|
|
@ -14,5 +14,13 @@ uint16_t Temp_fromRAW_to_F(uint16_t temperature);
|
|||
bool is_message_corrupt(CAN_frame rx_frame);
|
||||
void setup_battery(void);
|
||||
void transmit_can(CAN_frame* tx_frame, int interface);
|
||||
void clearSOH(void);
|
||||
//Cryptographic functions
|
||||
void decodeChallengeData(unsigned int SeedInput, unsigned char* Crypt_Output_Buffer);
|
||||
unsigned int CyclicXorHash16Bit(unsigned int param_1, unsigned int param_2);
|
||||
unsigned int ComputeMaskedXorProduct(unsigned int param_1, unsigned int param_2, unsigned int param_3);
|
||||
short ShortMaskedSumAndProduct(short param_1, short param_2);
|
||||
unsigned int MaskedBitwiseRotateMultiply(unsigned int param_1, unsigned int param_2);
|
||||
unsigned int CryptAlgo(unsigned int param_1, unsigned int param_2, unsigned int param_3);
|
||||
|
||||
#endif
|
||||
|
|
|
@ -175,10 +175,8 @@ void send_can_battery() {
|
|||
}
|
||||
|
||||
void setup_battery(void) { // Performs one time setup at startup
|
||||
#ifdef DEBUG_VIA_USB
|
||||
Serial.println("Pylon battery selected");
|
||||
#endif
|
||||
|
||||
strncpy(datalayer.system.info.battery_protocol, "Pylon compatible battery", 63);
|
||||
datalayer.system.info.battery_protocol[63] = '\0';
|
||||
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;
|
||||
|
|
|
@ -313,10 +313,8 @@ void send_can_battery() {
|
|||
}
|
||||
|
||||
void setup_battery(void) { // Performs one time setup at startup
|
||||
#ifdef DEBUG_VIA_USB
|
||||
Serial.println("Range Rover PHEV battery (L494 / L405) selected");
|
||||
#endif //DEBUG_VIA_USB
|
||||
|
||||
strncpy(datalayer.system.info.battery_protocol, "Range Rover 13kWh PHEV battery (L494/L405)", 63);
|
||||
datalayer.system.info.battery_protocol[63] = '\0';
|
||||
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;
|
||||
|
|
|
@ -234,9 +234,9 @@ void send_can_battery() {
|
|||
}
|
||||
|
||||
void setup_battery(void) { // Performs one time setup at startup
|
||||
#ifdef DEBUG_VIA_USB
|
||||
Serial.println("Renault Kangoo battery selected");
|
||||
#endif
|
||||
|
||||
strncpy(datalayer.system.info.battery_protocol, "Renault Kangoo", 63);
|
||||
datalayer.system.info.battery_protocol[63] = '\0';
|
||||
|
||||
datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_DV;
|
||||
datalayer.battery.info.min_design_voltage_dV = MIN_PACK_VOLTAGE_DV;
|
||||
|
|
|
@ -132,10 +132,8 @@ void send_can_battery() {
|
|||
}
|
||||
|
||||
void setup_battery(void) { // Performs one time setup at startup
|
||||
#ifdef DEBUG_VIA_USB
|
||||
Serial.println("Renault Twizy battery selected");
|
||||
#endif
|
||||
|
||||
strncpy(datalayer.system.info.battery_protocol, "Renault Twizy", 63);
|
||||
datalayer.system.info.battery_protocol[63] = '\0';
|
||||
datalayer.battery.info.number_of_cells = 14;
|
||||
datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_DV;
|
||||
datalayer.battery.info.min_design_voltage_dV = MIN_PACK_VOLTAGE_DV;
|
||||
|
|
|
@ -518,9 +518,8 @@ void send_can_battery() {
|
|||
}
|
||||
|
||||
void setup_battery(void) { // Performs one time setup at startup
|
||||
#ifdef DEBUG_VIA_USB
|
||||
Serial.println("Renault Zoe 22/40kWh battery selected");
|
||||
#endif
|
||||
strncpy(datalayer.system.info.battery_protocol, "Renault Zoe Gen1 22/40kWh", 63);
|
||||
datalayer.system.info.battery_protocol[63] = '\0';
|
||||
datalayer.system.status.battery_allows_contactor_closing = true;
|
||||
datalayer.battery.info.number_of_cells = 96;
|
||||
datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_DV;
|
||||
|
|
|
@ -385,9 +385,8 @@ void send_can_battery() {
|
|||
}
|
||||
|
||||
void setup_battery(void) { // Performs one time setup at startup
|
||||
#ifdef DEBUG_VIA_USB
|
||||
Serial.println("Renault Zoe 50kWh battery selected");
|
||||
#endif
|
||||
strncpy(datalayer.system.info.battery_protocol, "Renault Zoe Gen2 50kWh", 63);
|
||||
datalayer.system.info.battery_protocol[63] = '\0';
|
||||
datalayer.system.status.battery_allows_contactor_closing = true;
|
||||
datalayer.battery.info.number_of_cells = 96;
|
||||
datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_DV;
|
||||
|
|
|
@ -570,10 +570,8 @@ void send_can_battery() {
|
|||
}
|
||||
|
||||
void setup_battery(void) { // Performs one time setup at startup
|
||||
#ifdef DEBUG_VIA_USB
|
||||
Serial.println("RJXZS BMS selected");
|
||||
#endif
|
||||
|
||||
strncpy(datalayer.system.info.battery_protocol, "RJXZS BMS, DIY battery", 63);
|
||||
datalayer.system.info.battery_protocol[63] = '\0';
|
||||
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;
|
||||
|
|
|
@ -402,9 +402,8 @@ uint8_t CalculateCRC8(CAN_frame rx_frame) {
|
|||
}
|
||||
|
||||
void setup_battery(void) { // Performs one time setup at startup
|
||||
#ifdef DEBUG_VIA_USB
|
||||
Serial.println("Hyundai Santa Fe PHEV battery selected");
|
||||
#endif
|
||||
strncpy(datalayer.system.info.battery_protocol, "Santa Fe PHEV", 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;
|
||||
|
|
|
@ -219,7 +219,8 @@ void update_values_serial_link() {
|
|||
}
|
||||
|
||||
void setup_battery(void) {
|
||||
Serial.println("SERIAL_DATA_LINK_RECEIVER selected");
|
||||
strncpy(datalayer.system.info.battery_protocol, "Serial link to another LilyGo board", 63);
|
||||
datalayer.system.info.battery_protocol[63] = '\0';
|
||||
}
|
||||
// Needed to make the compiler happy
|
||||
void update_values_battery() {}
|
||||
|
|
|
@ -1249,13 +1249,11 @@ void printDebugIfActive(uint8_t symbol, const char* message) {
|
|||
}
|
||||
|
||||
void setup_battery(void) { // Performs one time setup at startup
|
||||
#ifdef DEBUG_VIA_USB
|
||||
Serial.println("Tesla Model S/3/X/Y battery selected");
|
||||
#endif
|
||||
|
||||
datalayer.system.status.battery_allows_contactor_closing = true;
|
||||
|
||||
#ifdef TESLA_MODEL_SX_BATTERY // Always use NCM/A mode on S/X packs
|
||||
strncpy(datalayer.system.info.battery_protocol, "Tesla Model S/X", 63);
|
||||
datalayer.system.info.battery_protocol[63] = '\0';
|
||||
datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_SX_NCMA;
|
||||
datalayer.battery.info.min_design_voltage_dV = MIN_PACK_VOLTAGE_SX_NCMA;
|
||||
datalayer.battery.info.max_cell_voltage_mV = MAX_CELL_VOLTAGE_NCA_NCM;
|
||||
|
@ -1271,6 +1269,8 @@ void setup_battery(void) { // Performs one time setup at startup
|
|||
#endif // TESLA_MODEL_SX_BATTERY
|
||||
|
||||
#ifdef TESLA_MODEL_3Y_BATTERY // Model 3/Y can be either LFP or NCM/A
|
||||
strncpy(datalayer.system.info.battery_protocol, "Tesla Model 3/Y", 63);
|
||||
datalayer.system.info.battery_protocol[63] = '\0';
|
||||
#ifdef LFP_CHEMISTRY
|
||||
datalayer.battery.info.chemistry = battery_chemistry_enum::LFP;
|
||||
datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_3Y_LFP;
|
||||
|
|
|
@ -145,9 +145,8 @@ void send_can_battery() {
|
|||
void setup_battery(void) { // Performs one time setup at startup
|
||||
randomSeed(analogRead(0));
|
||||
|
||||
#ifdef DEBUG_VIA_USB
|
||||
Serial.println("Test mode with fake battery selected");
|
||||
#endif
|
||||
strncpy(datalayer.system.info.battery_protocol, "Fake battery for testing purposes", 63);
|
||||
datalayer.system.info.battery_protocol[63] = '\0';
|
||||
|
||||
datalayer.battery.info.max_design_voltage_dV =
|
||||
4040; // 404.4V, over this, charging is not possible (goes into forced discharge)
|
||||
|
|
|
@ -332,10 +332,8 @@ void send_can_battery() {
|
|||
}
|
||||
|
||||
void setup_battery(void) { // Performs one time setup at startup
|
||||
#ifdef DEBUG_VIA_USB
|
||||
Serial.println("Volvo SPA XC40 Recharge / Polestar2 78kWh battery selected");
|
||||
#endif
|
||||
|
||||
strncpy(datalayer.system.info.battery_protocol, "Volvo / Polestar 78kWh battery", 63);
|
||||
datalayer.system.info.battery_protocol[63] = '\0';
|
||||
datalayer.battery.info.number_of_cells = 108;
|
||||
datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_DV;
|
||||
datalayer.battery.info.min_design_voltage_dV = MIN_PACK_VOLTAGE_DV;
|
||||
|
|
|
@ -127,7 +127,10 @@ typedef struct {
|
|||
} DATALAYER_SHUNT_TYPE;
|
||||
|
||||
typedef struct {
|
||||
// TODO
|
||||
/** array with type of battery used, for displaying on webserver */
|
||||
char battery_protocol[64] = {0};
|
||||
/** array with type of inverter used, for displaying on webserver */
|
||||
char inverter_protocol[64] = {0};
|
||||
} DATALAYER_SYSTEM_INFO_TYPE;
|
||||
|
||||
typedef struct {
|
||||
|
|
|
@ -71,6 +71,9 @@ typedef struct {
|
|||
} DATALAYER_INFO_BMWI3;
|
||||
|
||||
typedef struct {
|
||||
/** bool */
|
||||
/** Which SOC method currently used. 0 = Estimated, 1 = Measured */
|
||||
bool SOC_method = 0;
|
||||
/** uint16_t */
|
||||
/** SOC% estimate. Estimated from total pack voltage */
|
||||
uint16_t SOC_estimated = 0;
|
||||
|
@ -223,6 +226,21 @@ typedef struct {
|
|||
/** bool */
|
||||
/** Heat request sent*/
|
||||
bool HeaterSendRequest = false;
|
||||
/** bool */
|
||||
/** User requesting SOH reset via WebUI*/
|
||||
bool UserRequestSOHreset = false;
|
||||
/** bool */
|
||||
/** True if the crypto challenge response from BMS is signalling a failed attempt*/
|
||||
bool challengeFailed = false;
|
||||
/** uint32_t */
|
||||
/** Cryptographic challenge to be solved */
|
||||
uint32_t CryptoChallenge = 0;
|
||||
/** uint32_t */
|
||||
/** Solution for crypto challenge, MSBs */
|
||||
uint32_t SolvedChallengeMSB = 0;
|
||||
/** uint32_t */
|
||||
/** Solution for crypto challenge, LSBs */
|
||||
uint32_t SolvedChallengeLSB = 0;
|
||||
|
||||
} DATALAYER_INFO_NISSAN_LEAF;
|
||||
|
||||
|
|
|
@ -275,6 +275,8 @@ String advanced_battery_processor(const String& var) {
|
|||
#endif //CELLPOWER_BMS
|
||||
|
||||
#ifdef BYD_ATTO_3_BATTERY
|
||||
static const char* SOCmethod[2] = {"Estimated from voltage", "Measured by BMS"};
|
||||
content += "<h4>SOC method used: " + String(SOCmethod[datalayer_extended.bydAtto3.SOC_method]) + "</h4>";
|
||||
content += "<h4>SOC estimated: " + String(datalayer_extended.bydAtto3.SOC_estimated) + "</h4>";
|
||||
content += "<h4>SOC highprec: " + String(datalayer_extended.bydAtto3.SOC_highprec) + "</h4>";
|
||||
content += "<h4>SOC OBD2: " + String(datalayer_extended.bydAtto3.SOC_polled) + "</h4>";
|
||||
|
@ -331,6 +333,11 @@ String advanced_battery_processor(const String& var) {
|
|||
content += "<h4>Heating stopped: " + String(datalayer_extended.nissanleaf.HeatingStop) + "</h4>";
|
||||
content += "<h4>Heating started: " + String(datalayer_extended.nissanleaf.HeatingStart) + "</h4>";
|
||||
content += "<h4>Heating requested: " + String(datalayer_extended.nissanleaf.HeaterSendRequest) + "</h4>";
|
||||
content += "<button onclick='askResetSOH()'>Reset degradation data</button>";
|
||||
content += "<h4>CryptoChallenge: " + String(datalayer_extended.nissanleaf.CryptoChallenge) + "</h4>";
|
||||
content += "<h4>SolvedChallenge: " + String(datalayer_extended.nissanleaf.SolvedChallengeMSB) +
|
||||
String(datalayer_extended.nissanleaf.SolvedChallengeLSB) + "</h4>";
|
||||
content += "<h4>Challenge failed: " + String(datalayer_extended.nissanleaf.challengeFailed) + "</h4>";
|
||||
#endif
|
||||
|
||||
#ifdef RENAULT_ZOE_GEN2_BATTERY
|
||||
|
@ -388,6 +395,15 @@ String advanced_battery_processor(const String& var) {
|
|||
content += "</div>";
|
||||
|
||||
content += "<script>";
|
||||
content +=
|
||||
"function askResetSOH() { if (window.confirm('Are you sure you want to reset degradation data? "
|
||||
"Note this should only be used on 2011-2017 24/30kWh batteries!')) { "
|
||||
"resetSOH(); } }";
|
||||
content += "function resetSOH() {";
|
||||
content += " var xhr = new XMLHttpRequest();";
|
||||
content += " xhr.open('GET', '/resetSOH', true);";
|
||||
content += " xhr.send();";
|
||||
content += "}";
|
||||
content += "function goToMainPage() { window.location.href = '/'; }";
|
||||
content += "</script>";
|
||||
return content;
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
#include "webserver.h"
|
||||
#include <Preferences.h>
|
||||
#include "../../datalayer/datalayer.h"
|
||||
#include "../../datalayer/datalayer_extended.h"
|
||||
#include "../../lib/bblanchon-ArduinoJson/ArduinoJson.h"
|
||||
#include "../utils/events.h"
|
||||
#include "../utils/led_handler.h"
|
||||
|
@ -231,6 +232,15 @@ void init_webserver() {
|
|||
}
|
||||
});
|
||||
|
||||
// Route for resetting SOH on Nissan LEAF batteries
|
||||
server.on("/resetSOH", HTTP_GET, [](AsyncWebServerRequest* request) {
|
||||
if (WEBSERVER_AUTH_REQUIRED && !request->authenticate(http_username, http_password)) {
|
||||
return request->requestAuthentication();
|
||||
}
|
||||
datalayer_extended.nissanleaf.UserRequestSOHreset = true;
|
||||
request->send(200, "text/plain", "Updated successfully");
|
||||
});
|
||||
|
||||
#ifdef TEST_FAKE_BATTERY
|
||||
// Route for editing FakeBatteryVoltage
|
||||
server.on("/updateFakeBatteryVoltage", HTTP_GET, [](AsyncWebServerRequest* request) {
|
||||
|
@ -486,117 +496,16 @@ String processor(const String& var) {
|
|||
|
||||
// Display which components are used
|
||||
content += "<h4 style='color: white;'>Inverter protocol: ";
|
||||
#ifdef BYD_CAN
|
||||
content += "BYD Battery-Box Premium HVS over CAN Bus";
|
||||
#endif // BYD_CAN
|
||||
#ifdef BYD_MODBUS
|
||||
content += "BYD 11kWh HVM battery over Modbus RTU";
|
||||
#endif // BYD_MODBUS
|
||||
#ifdef FOXESS_CAN
|
||||
content += "FoxESS compatible HV2600/ECS4100 battery";
|
||||
#endif // FOXESS_CAN
|
||||
#ifdef PYLON_CAN
|
||||
content += "Pylontech battery over CAN bus";
|
||||
#endif // PYLON_CAN
|
||||
#ifdef PYLON_LV_CAN
|
||||
content += "Pylontech LV battery over CAN bus";
|
||||
#endif // PYLON_LV_CAN
|
||||
#ifdef SERIAL_LINK_TRANSMITTER
|
||||
content += "Serial link to another LilyGo board";
|
||||
#endif // SERIAL_LINK_TRANSMITTER
|
||||
#ifdef SMA_CAN
|
||||
content += "BYD Battery-Box H 8.9kWh, 7 mod over CAN bus";
|
||||
#endif // SMA_CAN
|
||||
#ifdef SOFAR_CAN
|
||||
content += "Sofar Energy Storage Inverter High Voltage BMS General Protocol (Extended Frame) over CAN bus";
|
||||
#endif // SOFAR_CAN
|
||||
#ifdef SOLAX_CAN
|
||||
content += "SolaX Triple Power LFP over CAN bus";
|
||||
#endif // SOLAX_CAN
|
||||
content += datalayer.system.info.inverter_protocol;
|
||||
content += "</h4>";
|
||||
|
||||
content += "<h4 style='color: white;'>Battery protocol: ";
|
||||
#ifdef BMW_I3_BATTERY
|
||||
content += "BMW i3";
|
||||
#endif // BMW_I3_BATTERY
|
||||
#ifdef BMW_IX_BATTERY
|
||||
content += "BMW iX and i4-7 platform";
|
||||
#endif // BMW_IX_BATTERY
|
||||
#ifdef BYD_ATTO_3_BATTERY
|
||||
content += "BYD Atto 3";
|
||||
#endif // BYD_ATTO_3_BATTERY
|
||||
#ifdef CELLPOWER_BMS
|
||||
content += "Cellpower BMS";
|
||||
#endif // CELLPOWER_BMS
|
||||
#ifdef CHADEMO_BATTERY
|
||||
content += "Chademo V2X mode";
|
||||
#endif // CHADEMO_BATTERY
|
||||
#ifdef IMIEV_CZERO_ION_BATTERY
|
||||
content += "I-Miev / C-Zero / Ion Triplet";
|
||||
#endif // IMIEV_CZERO_ION_BATTERY
|
||||
#ifdef JAGUAR_IPACE_BATTERY
|
||||
content += "Jaguar I-PACE";
|
||||
#endif // JAGUAR_IPACE_BATTERY
|
||||
#ifdef KIA_HYUNDAI_64_BATTERY
|
||||
content += "Kia/Hyundai 64kWh";
|
||||
#endif // KIA_HYUNDAI_64_BATTERY
|
||||
#ifdef KIA_E_GMP_BATTERY
|
||||
content += "Kia/Hyundai EGMP platform";
|
||||
#endif // KIA_E_GMP_BATTERY
|
||||
#ifdef KIA_HYUNDAI_HYBRID_BATTERY
|
||||
content += "Kia/Hyundai Hybrid";
|
||||
#endif // KIA_HYUNDAI_HYBRID_BATTERY
|
||||
#ifdef MG_5_BATTERY
|
||||
content += "MG 5";
|
||||
#endif // MG_5_BATTERY
|
||||
#ifdef NISSAN_LEAF_BATTERY
|
||||
content += "Nissan LEAF";
|
||||
#endif // NISSAN_LEAF_BATTERY
|
||||
#ifdef PYLON_BATTERY
|
||||
content += "Pylon compatible battery";
|
||||
#endif // PYLON_BATTERY
|
||||
#ifdef RJXZS_BMS
|
||||
content += "RJXZS BMS, DIY battery";
|
||||
#endif // RJXZS_BMS
|
||||
#ifdef RANGE_ROVER_PHEV_BATTERY
|
||||
content += "Range Rover 13kWh PHEV battery (L494/L405)";
|
||||
#endif //RANGE_ROVER_PHEV_BATTERY
|
||||
#ifdef RENAULT_KANGOO_BATTERY
|
||||
content += "Renault Kangoo";
|
||||
#endif // RENAULT_KANGOO_BATTERY
|
||||
#ifdef RENAULT_TWIZY_BATTERY
|
||||
content += "Renault Twizy";
|
||||
#endif // RENAULT_TWIZY_BATTERY
|
||||
#ifdef RENAULT_ZOE_GEN1_BATTERY
|
||||
content += "Renault Zoe Gen1 22/40";
|
||||
#endif // RENAULT_ZOE_GEN1_BATTERY
|
||||
#ifdef RENAULT_ZOE_GEN2_BATTERY
|
||||
content += "Renault Zoe Gen2 50";
|
||||
#endif // RENAULT_ZOE_GEN2_BATTERY
|
||||
#ifdef SANTA_FE_PHEV_BATTERY
|
||||
content += "Santa Fe PHEV";
|
||||
#endif // SANTA_FE_PHEV_BATTERY
|
||||
#ifdef SERIAL_LINK_RECEIVER
|
||||
content += "Serial link to another LilyGo board";
|
||||
#endif // SERIAL_LINK_RECEIVER
|
||||
#ifdef TESLA_MODEL_SX_BATTERY
|
||||
content += "Tesla Model S/X";
|
||||
#endif // TESLA_MODEL_SX_BATTERY
|
||||
#ifdef TESLA_MODEL_3Y_BATTERY
|
||||
content += "Tesla Model 3/Y";
|
||||
#endif // TESLA_MODEL_3Y_BATTERY
|
||||
#ifdef VOLVO_SPA_BATTERY
|
||||
content += "Volvo / Polestar 78kWh battery";
|
||||
#endif // VOLVO_SPA_BATTERY
|
||||
#ifdef TEST_FAKE_BATTERY
|
||||
content += "Fake battery for testing purposes";
|
||||
#endif // TEST_FAKE_BATTERY
|
||||
content += datalayer.system.info.battery_protocol;
|
||||
#ifdef DOUBLE_BATTERY
|
||||
content += " (Double battery)";
|
||||
#endif // DOUBLE_BATTERY
|
||||
if (datalayer.battery.info.chemistry == battery_chemistry_enum::LFP) {
|
||||
content += " (LFP)";
|
||||
}
|
||||
#endif // DOUBLE_BATTERY
|
||||
content += "</h4>";
|
||||
|
||||
#if defined CHEVYVOLT_CHARGER || defined NISSANLEAF_CHARGER
|
||||
|
@ -676,16 +585,16 @@ String processor(const String& var) {
|
|||
content +=
|
||||
formatPowerValue("Scaled Remaining capacity", datalayer.battery.status.reported_remaining_capacity_Wh, "h", 1);
|
||||
|
||||
if (emulator_pause_status == NORMAL) {
|
||||
content += formatPowerValue("Max discharge power", datalayer.battery.status.max_discharge_power_W, "", 1);
|
||||
content += formatPowerValue("Max charge power", datalayer.battery.status.max_charge_power_W, "", 1);
|
||||
content += "<h4 style='color: white;'>Max discharge current: " + String(maxCurrentDischargeFloat, 1) + " A</h4>";
|
||||
content += "<h4 style='color: white;'>Max charge current: " + String(maxCurrentChargeFloat, 1) + " A</h4>";
|
||||
} else {
|
||||
if (datalayer.system.settings.equipment_stop_active) {
|
||||
content += formatPowerValue("Max discharge power", datalayer.battery.status.max_discharge_power_W, "", 1, "red");
|
||||
content += formatPowerValue("Max charge power", datalayer.battery.status.max_charge_power_W, "", 1, "red");
|
||||
content += "<h4 style='color: red;'>Max discharge current: " + String(maxCurrentDischargeFloat, 1) + " A</h4>";
|
||||
content += "<h4 style='color: red;'>Max charge current: " + String(maxCurrentChargeFloat, 1) + " A</h4>";
|
||||
} else {
|
||||
content += formatPowerValue("Max discharge power", datalayer.battery.status.max_discharge_power_W, "", 1);
|
||||
content += formatPowerValue("Max charge power", datalayer.battery.status.max_charge_power_W, "", 1);
|
||||
content += "<h4 style='color: white;'>Max discharge current: " + String(maxCurrentDischargeFloat, 1) + " A</h4>";
|
||||
content += "<h4 style='color: white;'>Max charge current: " + String(maxCurrentChargeFloat, 1) + " A</h4>";
|
||||
}
|
||||
|
||||
content += "<h4>Cell max: " + String(datalayer.battery.status.cell_max_voltage_mV) + " mV</h4>";
|
||||
|
@ -805,8 +714,19 @@ String processor(const String& var) {
|
|||
content += formatPowerValue("Real Remaining capacity", datalayer.battery2.status.remaining_capacity_Wh, "h", 1);
|
||||
content +=
|
||||
formatPowerValue("Scaled Remaining capacity", datalayer.battery2.status.reported_remaining_capacity_Wh, "h", 1);
|
||||
|
||||
if (datalayer.system.settings.equipment_stop_active) {
|
||||
content += formatPowerValue("Max discharge power", datalayer.battery2.status.max_discharge_power_W, "", 1, "red");
|
||||
content += formatPowerValue("Max charge power", datalayer.battery2.status.max_charge_power_W, "", 1, "red");
|
||||
content += "<h4 style='color: red;'>Max discharge current: " + String(maxCurrentDischargeFloat, 1) + " A</h4>";
|
||||
content += "<h4 style='color: red;'>Max charge current: " + String(maxCurrentChargeFloat, 1) + " A</h4>";
|
||||
} else {
|
||||
content += formatPowerValue("Max discharge power", datalayer.battery2.status.max_discharge_power_W, "", 1);
|
||||
content += formatPowerValue("Max charge power", datalayer.battery2.status.max_charge_power_W, "", 1);
|
||||
content += "<h4 style='color: white;'>Max discharge current: " + String(maxCurrentDischargeFloat, 1) + " A</h4>";
|
||||
content += "<h4 style='color: white;'>Max charge current: " + String(maxCurrentChargeFloat, 1) + " A</h4>";
|
||||
}
|
||||
|
||||
content += "<h4>Cell max: " + String(datalayer.battery2.status.cell_max_voltage_mV) + " mV</h4>";
|
||||
content += "<h4>Cell min: " + String(datalayer.battery2.status.cell_min_voltage_mV) + " mV</h4>";
|
||||
if (cell_delta_mv > datalayer.battery2.info.max_cell_voltage_deviation_mV) {
|
||||
|
|
|
@ -45,10 +45,4 @@
|
|||
#error No battery selected! Choose one from the USER_SETTINGS.h file
|
||||
#endif
|
||||
|
||||
#ifdef KIA_E_GMP_BATTERY
|
||||
#ifndef CAN_FD
|
||||
#error KIA HYUNDAI EGMP BATTERIES CANNOT BE USED WITHOUT CAN FD
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
|
|
@ -233,4 +233,8 @@ void send_can_inverter() {
|
|||
time_to_send_info = false;
|
||||
}
|
||||
}
|
||||
void setup_inverter(void) { // Performs one time setup at startup over CAN bus
|
||||
strncpy(datalayer.system.info.inverter_protocol, "Afore battery over CAN", 63);
|
||||
datalayer.system.info.inverter_protocol[63] = '\0';
|
||||
}
|
||||
#endif
|
||||
|
|
|
@ -4,8 +4,7 @@
|
|||
|
||||
#define CAN_INVERTER_SELECTED
|
||||
|
||||
void send_system_data();
|
||||
void send_setup_info();
|
||||
void transmit_can(CAN_frame* tx_frame, int interface);
|
||||
void setup_inverter(void);
|
||||
|
||||
#endif
|
||||
|
|
|
@ -76,6 +76,8 @@ static uint8_t inverter_name[7] = {0};
|
|||
static int16_t temperature_average = 0;
|
||||
static uint16_t inverter_voltage = 0;
|
||||
static uint16_t inverter_SOC = 0;
|
||||
static int16_t inverter_current = 0;
|
||||
static int16_t inverter_temperature = 0;
|
||||
static uint16_t remaining_capacity_ah = 0;
|
||||
static uint16_t fully_charged_capacity_ah = 0;
|
||||
static long inverter_timestamp = 0;
|
||||
|
@ -165,6 +167,8 @@ void receive_can_inverter(CAN_frame rx_frame) {
|
|||
case 0x091:
|
||||
datalayer.system.status.CAN_inverter_still_alive = CAN_STILL_ALIVE;
|
||||
inverter_voltage = ((rx_frame.data.u8[0] << 8) | rx_frame.data.u8[1]) * 0.1;
|
||||
inverter_current = ((rx_frame.data.u8[2] << 8) | rx_frame.data.u8[3]) * 0.1;
|
||||
inverter_temperature = ((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 0.1;
|
||||
break;
|
||||
case 0x0D1:
|
||||
datalayer.system.status.CAN_inverter_still_alive = CAN_STILL_ALIVE;
|
||||
|
@ -219,4 +223,8 @@ void send_intial_data() {
|
|||
transmit_can(&BYD_3D0_2, can_config.inverter);
|
||||
transmit_can(&BYD_3D0_3, can_config.inverter);
|
||||
}
|
||||
void setup_inverter(void) { // Performs one time setup at startup over CAN bus
|
||||
strncpy(datalayer.system.info.inverter_protocol, "BYD Battery-Box Premium HVS over CAN Bus", 63);
|
||||
datalayer.system.info.inverter_protocol[63] = '\0';
|
||||
}
|
||||
#endif
|
||||
|
|
|
@ -8,5 +8,6 @@
|
|||
|
||||
void send_intial_data();
|
||||
void transmit_can(CAN_frame* tx_frame, int interface);
|
||||
void setup_inverter(void);
|
||||
|
||||
#endif
|
||||
|
|
|
@ -143,4 +143,8 @@ void verify_inverter_modbus() {
|
|||
history_index = (history_index + 1) % HISTORY_LENGTH;
|
||||
}
|
||||
}
|
||||
void setup_inverter(void) { // Performs one time setup at startup over CAN bus
|
||||
strncpy(datalayer.system.info.inverter_protocol, "BYD 11kWh HVM battery over Modbus RTU", 63);
|
||||
datalayer.system.info.inverter_protocol[63] = '\0';
|
||||
}
|
||||
#endif
|
||||
|
|
|
@ -14,4 +14,5 @@ void verify_temperature_modbus();
|
|||
void verify_inverter_modbus();
|
||||
void handle_update_data_modbusp201_byd();
|
||||
void handle_update_data_modbusp301_byd();
|
||||
void setup_inverter(void);
|
||||
#endif
|
||||
|
|
|
@ -251,4 +251,10 @@ 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);
|
||||
}
|
||||
#endif
|
||||
|
|
|
@ -8,5 +8,6 @@
|
|||
#define STOP_STATE 0x02
|
||||
|
||||
void transmit_can(CAN_frame* tx_frame, int interface);
|
||||
void setup_inverter(void);
|
||||
|
||||
#endif
|
||||
|
|
|
@ -737,4 +737,8 @@ void receive_can_inverter(CAN_frame rx_frame) {
|
|||
}
|
||||
}
|
||||
}
|
||||
void setup_inverter(void) { // Performs one time setup at startup over CAN bus
|
||||
strncpy(datalayer.system.info.inverter_protocol, "FoxESS compatible HV2600/ECS4100 battery", 63);
|
||||
datalayer.system.info.inverter_protocol[63] = '\0';
|
||||
}
|
||||
#endif
|
||||
|
|
|
@ -5,5 +5,6 @@
|
|||
#define CAN_INVERTER_SELECTED
|
||||
|
||||
void transmit_can(CAN_frame* tx_frame, int interface);
|
||||
void setup_inverter(void);
|
||||
|
||||
#endif
|
||||
|
|
|
@ -31,6 +31,10 @@
|
|||
#include "PYLON-LV-CAN.h"
|
||||
#endif
|
||||
|
||||
#ifdef SCHNEIDER_CAN
|
||||
#include "SCHNEIDER-CAN.h"
|
||||
#endif
|
||||
|
||||
#ifdef SMA_CAN
|
||||
#include "SMA-CAN.h"
|
||||
#endif
|
||||
|
|
|
@ -477,4 +477,8 @@ void send_system_data() { //System equipment information
|
|||
transmit_can(&PYLON_4291, can_config.inverter);
|
||||
#endif
|
||||
}
|
||||
void setup_inverter(void) { // Performs one time setup at startup over CAN bus
|
||||
strncpy(datalayer.system.info.inverter_protocol, "Pylontech battery over CAN bus", 63);
|
||||
datalayer.system.info.inverter_protocol[63] = '\0';
|
||||
}
|
||||
#endif
|
||||
|
|
|
@ -7,5 +7,6 @@
|
|||
void send_system_data();
|
||||
void send_setup_info();
|
||||
void transmit_can(CAN_frame* tx_frame, int interface);
|
||||
void setup_inverter(void);
|
||||
|
||||
#endif
|
||||
|
|
|
@ -133,4 +133,8 @@ void send_can_inverter() {
|
|||
transmit_can(&PYLON_35E, can_config.inverter);
|
||||
}
|
||||
}
|
||||
void setup_inverter(void) { // Performs one time setup at startup over CAN bus
|
||||
strncpy(datalayer.system.info.inverter_protocol, "Pylontech LV battery over CAN bus", 63);
|
||||
datalayer.system.info.inverter_protocol[63] = '\0';
|
||||
}
|
||||
#endif
|
||||
|
|
|
@ -12,5 +12,6 @@
|
|||
void send_system_data();
|
||||
void send_setup_info();
|
||||
void transmit_can(CAN_frame* tx_frame, int interface);
|
||||
void setup_inverter(void);
|
||||
|
||||
#endif
|
||||
|
|
305
Software/src/inverter/SCHNEIDER-CAN.cpp
Normal file
305
Software/src/inverter/SCHNEIDER-CAN.cpp
Normal file
|
@ -0,0 +1,305 @@
|
|||
#include "../include.h"
|
||||
#ifdef SCHNEIDER_CAN
|
||||
#include "../datalayer/datalayer.h"
|
||||
#include "SCHNEIDER-CAN.h"
|
||||
|
||||
/* Version 2: SE BMS Communication Protocol
|
||||
Protocol: CAN 2.0 Specification
|
||||
Frame: Extended CAN Bus Frame (29 bit identifier)
|
||||
Bitrate: 500 kbps
|
||||
Endian: Big Endian (MSB, most significant byte of a value received first)*/
|
||||
|
||||
/* TODOs
|
||||
- Figure out how to reply with protocol version in 0x320
|
||||
- Figure out what to set Battery Manufacturer ID to in 0x330
|
||||
- Figure out what to set Battery Model ID in 0x330
|
||||
- We will need CAN logs from existing battery OR contact Schneider for one free number
|
||||
*/
|
||||
|
||||
/* Do not change code below unless you are sure what you are doing */
|
||||
static unsigned long previousMillis10s = 0; // will store last time a 10s CAN Message was send
|
||||
static unsigned long previousMillis2s = 0; // will store last time a 2s CAN Message was send
|
||||
static unsigned long previousMillis500ms = 0; // will store last time a 500ms CAN Message was send
|
||||
|
||||
CAN_frame SE_320 = {.FD = false, //SE BMS Protocol Version
|
||||
.ext_ID = true,
|
||||
.DLC = 2,
|
||||
.ID = 0x320,
|
||||
.data = {0x00, 0x02}}; //TODO: How do we reply with Protocol Version: 0x0002 ?
|
||||
CAN_frame SE_321 = {.FD = false,
|
||||
.ext_ID = true,
|
||||
.DLC = 8,
|
||||
.ID = 0x321,
|
||||
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
|
||||
CAN_frame SE_322 = {.FD = false,
|
||||
.ext_ID = true,
|
||||
.DLC = 8,
|
||||
.ID = 0x322,
|
||||
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
|
||||
CAN_frame SE_323 = {.FD = false,
|
||||
.ext_ID = true,
|
||||
.DLC = 8,
|
||||
.ID = 0x323,
|
||||
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
|
||||
CAN_frame SE_324 = {.FD = false, .ext_ID = true, .DLC = 4, .ID = 0x324, .data = {0x00, 0x00, 0x00, 0x00}};
|
||||
CAN_frame SE_325 = {.FD = false, .ext_ID = true, .DLC = 6, .ID = 0x325, .data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
|
||||
CAN_frame SE_326 = {.FD = false,
|
||||
.ext_ID = true,
|
||||
.DLC = 8,
|
||||
.ID = 0x326,
|
||||
.data = {0x00, STATE_STARTING, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
|
||||
CAN_frame SE_327 = {.FD = false,
|
||||
.ext_ID = true,
|
||||
.DLC = 8,
|
||||
.ID = 0x327,
|
||||
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
|
||||
CAN_frame SE_328 = {.FD = false,
|
||||
.ext_ID = true,
|
||||
.DLC = 8,
|
||||
.ID = 0x328,
|
||||
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
|
||||
CAN_frame SE_330 = {.FD = false,
|
||||
.ext_ID = true,
|
||||
.DLC = 8,
|
||||
.ID = 0x330,
|
||||
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
|
||||
CAN_frame SE_331 = {.FD = false,
|
||||
.ext_ID = true,
|
||||
.DLC = 8,
|
||||
.ID = 0x331,
|
||||
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
|
||||
CAN_frame SE_332 = {.FD = false,
|
||||
.ext_ID = true,
|
||||
.DLC = 8,
|
||||
.ID = 0x332,
|
||||
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
|
||||
CAN_frame SE_333 = {.FD = false,
|
||||
.ext_ID = true,
|
||||
.DLC = 8,
|
||||
.ID = 0x333,
|
||||
.data = {0x53, 0x45, 0x42, 0x4D, 0x53, 0x00, 0x00, 0x00}}; //SEBMS
|
||||
|
||||
static int16_t temperature_average = 0;
|
||||
static uint16_t remaining_capacity_ah = 0;
|
||||
static uint16_t fully_charged_capacity_ah = 0;
|
||||
static uint16_t commands = 0;
|
||||
static uint16_t warnings = 0;
|
||||
static uint16_t faults = 0;
|
||||
static uint16_t state = 0;
|
||||
|
||||
void update_values_can_inverter() { //This function maps all the values fetched from battery CAN to the correct CAN messages
|
||||
|
||||
/* Calculate temperature */
|
||||
temperature_average =
|
||||
((datalayer.battery.status.temperature_max_dC + datalayer.battery.status.temperature_min_dC) / 2);
|
||||
|
||||
/* Calculate capacity, Amp hours(Ah) = Watt hours (Wh) / Voltage (V)*/
|
||||
if (datalayer.battery.status.voltage_dV > 10) { // Only update value when we have voltage available to avoid div0
|
||||
remaining_capacity_ah =
|
||||
((datalayer.battery.status.reported_remaining_capacity_Wh / datalayer.battery.status.voltage_dV) * 100);
|
||||
fully_charged_capacity_ah =
|
||||
((datalayer.battery.info.total_capacity_Wh / datalayer.battery.status.voltage_dV) * 100);
|
||||
}
|
||||
/* Set active commands/warnings/faults/state*/
|
||||
if (datalayer.battery.status.bms_status == FAULT) {
|
||||
state = STATE_FAULTED;
|
||||
//TODO: Map warnings and faults incase an event is set. Low prio, but nice to have
|
||||
commands = COMMAND_STOP;
|
||||
} else { //Battery-Emulator running
|
||||
state = STATE_ONLINE;
|
||||
warnings = 0;
|
||||
faults = 0;
|
||||
if (datalayer.battery.status.reported_soc == 10000) {
|
||||
//Battery full. Only allow discharge
|
||||
commands = COMMAND_ONLY_DISCHARGE_ALLOWED;
|
||||
} else if (datalayer.battery.status.reported_soc == 0) {
|
||||
//Battery empty. Only allow charge
|
||||
commands = COMMAND_ONLY_CHARGE_ALLOWED;
|
||||
} else { //SOC is somewhere between 0.1% and 99.9%. Allow both charge and discharge
|
||||
commands = COMMAND_CHARGE_AND_DISCHARGE_ALLOWED;
|
||||
}
|
||||
}
|
||||
|
||||
//Map values to CAN messages
|
||||
//Max charge voltage+2 (eg 10000.00V = 1000000 , 32bits long)
|
||||
SE_321.data.u8[0] = ((datalayer.battery.info.max_design_voltage_dV * 10) >> 24);
|
||||
SE_321.data.u8[1] = (((datalayer.battery.info.max_design_voltage_dV * 10) & 0x00FF0000) >> 16);
|
||||
SE_321.data.u8[2] = (((datalayer.battery.info.max_design_voltage_dV * 10) & 0x0000FF00) >> 8);
|
||||
SE_321.data.u8[3] = ((datalayer.battery.info.max_design_voltage_dV * 10) & 0x000000FF);
|
||||
//Minimum discharge voltage+2 (eg 10000.00V = 1000000 , 32bits long)
|
||||
SE_321.data.u8[4] = ((datalayer.battery.info.min_design_voltage_dV * 10) >> 24);
|
||||
SE_321.data.u8[5] = (((datalayer.battery.info.min_design_voltage_dV * 10) & 0x00FF0000) >> 16);
|
||||
SE_321.data.u8[6] = (((datalayer.battery.info.min_design_voltage_dV * 10) & 0x0000FF00) >> 8);
|
||||
SE_321.data.u8[7] = ((datalayer.battery.info.min_design_voltage_dV * 10) & 0x000000FF);
|
||||
|
||||
//Maximum charge current+2 (eg 10000.00A = 1000000) TODO: Note s32 bit, which direction?
|
||||
SE_322.data.u8[0] = ((datalayer.battery.status.max_charge_current_dA * 10) >> 24);
|
||||
SE_322.data.u8[1] = (((datalayer.battery.status.max_charge_current_dA * 10) & 0x00FF0000) >> 16);
|
||||
SE_322.data.u8[2] = (((datalayer.battery.status.max_charge_current_dA * 10) & 0x0000FF00) >> 8);
|
||||
SE_322.data.u8[3] = ((datalayer.battery.status.max_charge_current_dA * 10) & 0x000000FF);
|
||||
//Maximum discharge current+2 (eg 10000.00A = 1000000) TODO: Note s32 bit, which direction?
|
||||
SE_322.data.u8[4] = ((datalayer.battery.status.max_discharge_current_dA * 10) >> 24);
|
||||
SE_322.data.u8[5] = (((datalayer.battery.status.max_discharge_current_dA * 10) & 0x00FF0000) >> 16);
|
||||
SE_322.data.u8[6] = (((datalayer.battery.status.max_discharge_current_dA * 10) & 0x0000FF00) >> 8);
|
||||
SE_322.data.u8[7] = ((datalayer.battery.status.max_discharge_current_dA * 10) & 0x000000FF);
|
||||
|
||||
//Voltage (ex 370.00 = 37000, 32bits long)
|
||||
SE_323.data.u8[0] = ((datalayer.battery.status.voltage_dV * 10) >> 24);
|
||||
SE_323.data.u8[1] = (((datalayer.battery.status.voltage_dV * 10) & 0x00FF0000) >> 16);
|
||||
SE_323.data.u8[2] = (((datalayer.battery.status.voltage_dV * 10) & 0x0000FF00) >> 8);
|
||||
SE_323.data.u8[3] = ((datalayer.battery.status.voltage_dV * 10) & 0x000000FF);
|
||||
//Current (ex 81.00A = 8100) TODO: Note s32 bit, which direction?
|
||||
SE_323.data.u8[4] = ((datalayer.battery.status.current_dA * 10) >> 24);
|
||||
SE_323.data.u8[5] = (((datalayer.battery.status.current_dA * 10) & 0x00FF0000) >> 16);
|
||||
SE_323.data.u8[6] = (((datalayer.battery.status.current_dA * 10) & 0x0000FF00) >> 8);
|
||||
SE_323.data.u8[7] = ((datalayer.battery.status.current_dA * 10) & 0x000000FF);
|
||||
|
||||
//Temperature average
|
||||
SE_324.data.u8[0] = (temperature_average >> 8);
|
||||
SE_324.data.u8[1] = (temperature_average & 0x00FF);
|
||||
//SOC (100.0%)
|
||||
SE_324.data.u8[2] = ((datalayer.battery.status.reported_soc / 10) >> 8);
|
||||
SE_324.data.u8[3] = ((datalayer.battery.status.reported_soc / 10) & 0x00FF);
|
||||
//Commands (enum)
|
||||
SE_325.data.u8[0] = (commands >> 8);
|
||||
SE_325.data.u8[1] = (commands & 0x00FF);
|
||||
//Warnings (enum)
|
||||
SE_325.data.u8[2] = (warnings >> 8);
|
||||
SE_325.data.u8[3] = (warnings & 0x00FF);
|
||||
//Faults (enum)
|
||||
SE_325.data.u8[4] = (faults >> 8);
|
||||
SE_325.data.u8[5] = (faults & 0x00FF);
|
||||
|
||||
//State (enum)
|
||||
SE_326.data.u8[0] = (state >> 8);
|
||||
SE_326.data.u8[1] = (state & 0x00FF);
|
||||
//Cycle count (OPTIONAL UINT16)
|
||||
//SE_326.data.u8[2] = Cycle count not tracked by emulator
|
||||
//SE_326.data.u8[3] = Cycle count not tracked by emulator
|
||||
//StateOfHealth (OPTIONAL 0-100%)
|
||||
SE_326.data.u8[4] = (datalayer.battery.status.soh_pptt / 100 >> 8);
|
||||
SE_326.data.u8[5] = (datalayer.battery.status.soh_pptt / 100 & 0x00FF);
|
||||
//Capacity (OPTIONAL, full charge) AH+1
|
||||
SE_326.data.u8[6] = (fully_charged_capacity_ah >> 8);
|
||||
SE_326.data.u8[7] = (fully_charged_capacity_ah & 0x00FF);
|
||||
|
||||
//Cell temp max (OPTIONAL dC)
|
||||
SE_327.data.u8[0] = (datalayer.battery.status.temperature_max_dC >> 8);
|
||||
SE_327.data.u8[1] = (datalayer.battery.status.temperature_max_dC & 0x00FF);
|
||||
//Cell temp min (OPTIONAL dC)
|
||||
SE_327.data.u8[2] = (datalayer.battery.status.temperature_min_dC >> 8);
|
||||
SE_327.data.u8[3] = (datalayer.battery.status.temperature_min_dC & 0x00FF);
|
||||
//Cell max volt (OPTIONAL 4.000V)
|
||||
SE_327.data.u8[4] = (datalayer.battery.status.cell_max_voltage_mV >> 8);
|
||||
SE_327.data.u8[5] = (datalayer.battery.status.cell_max_voltage_mV & 0x00FF);
|
||||
//Cell min volt (OPTIONAL 4.000V)
|
||||
SE_327.data.u8[6] = (datalayer.battery.status.cell_min_voltage_mV >> 8);
|
||||
SE_327.data.u8[7] = (datalayer.battery.status.cell_min_voltage_mV & 0x00FF);
|
||||
|
||||
//Lifetime Charge Energy (OPTIONAL, WH, UINT32)
|
||||
//SE_328.data.u8[0] = Lifetime energy not tracked by emulator
|
||||
//SE_328.data.u8[1] = Lifetime energy not tracked by emulator
|
||||
//SE_328.data.u8[2] = Lifetime energy not tracked by emulator
|
||||
//SE_328.data.u8[3] = Lifetime energy not tracked by emulator
|
||||
//Lifetime Discharge Energy (OPTIONAL, WH, UINT32)
|
||||
//SE_328.data.u8[4] = Lifetime energy not tracked by emulator
|
||||
//SE_328.data.u8[5] = Lifetime energy not tracked by emulator
|
||||
//SE_328.data.u8[6] = Lifetime energy not tracked by emulator
|
||||
//SE_328.data.u8[7] = Lifetime energy not tracked by emulator
|
||||
|
||||
//Battery Manufacturer ID (UINT16)
|
||||
//Unique identifier for each battery manufacturer implementing this protocol. IDs must be requested through Schneider Electric Solar.
|
||||
SE_330.data.u8[0] = 0; //TODO, set Battery Manufacturer ID
|
||||
SE_330.data.u8[1] = 0; //TODO, set Battery Manufacturer ID
|
||||
//Battery Model ID (UINT16)
|
||||
//Unique identifier for each battery model that a manufacturer has implemented this protocol on. IDs must be requested through Schneider Electric Solar.
|
||||
SE_330.data.u8[2] = 0; //TODO, set Battery Model ID
|
||||
SE_330.data.u8[3] = 0; //TODO, set Battery Model ID
|
||||
//Serial numbers
|
||||
//(For instance ABC123 would be represented as:
|
||||
//0x41[char5], 0x42[char4], 0x43[char3], 0x31[char2], 0x32 [char1], 0x33 [char0])
|
||||
SE_330.data.u8[4] = 0x42; //Char 19 - B
|
||||
SE_330.data.u8[5] = 0x41; //Char 18 - A
|
||||
SE_330.data.u8[6] = 0x54; //Char 17 - T
|
||||
SE_330.data.u8[7] = 0x54; //Char 16 - T
|
||||
|
||||
SE_331.data.u8[0] = 0x45; //Char 15 - E
|
||||
SE_331.data.u8[1] = 0x52; //Char 14 - R
|
||||
SE_331.data.u8[2] = 0x59; //Char 13 - Y
|
||||
SE_331.data.u8[3] = 0x45; //Char 12 - E
|
||||
SE_331.data.u8[4] = 0x4D; //Char 11 - M
|
||||
SE_331.data.u8[5] = 0x55; //Char 10 - U
|
||||
SE_331.data.u8[6] = 0x4C; //Char 9 - L
|
||||
SE_331.data.u8[7] = 0x41; //Char 8 - A
|
||||
|
||||
SE_332.data.u8[0] = 0x54; //Char 7 - T
|
||||
SE_332.data.u8[1] = 0x4F; //Char 6 - O
|
||||
SE_332.data.u8[2] = 0x52; //Char 5 - R
|
||||
SE_332.data.u8[3] = 0x30; //Char 4 - 0
|
||||
SE_332.data.u8[4] = 0x31; //Char 3 - 1
|
||||
SE_332.data.u8[5] = 0x32; //Char 2 - 2
|
||||
SE_332.data.u8[6] = 0x33; //Char 1 - 3
|
||||
SE_332.data.u8[7] = 0x34; //Char 0 - 4
|
||||
|
||||
//UNIQUE ID
|
||||
//Schneider Electric Unique string identifier. The value should be an unique string "SEBMS"
|
||||
SE_333.data.u8[0] = 0x53; //Char 5 - S
|
||||
SE_333.data.u8[1] = 0x45; //Char 4 - E
|
||||
SE_333.data.u8[2] = 0x42; //Char 3 - B
|
||||
SE_333.data.u8[3] = 0x4D; //Char 2 - M
|
||||
SE_333.data.u8[4] = 0x53; //Char 1 - S
|
||||
SE_333.data.u8[5] = 0x00; //Char 0 - NULL
|
||||
|
||||
//Protocol version, TODO: How do we reply with protocol version 0x0002 ?
|
||||
SE_320.data.u8[0] = 0x00;
|
||||
SE_320.data.u8[1] = 0x02;
|
||||
}
|
||||
|
||||
void receive_can_inverter(CAN_frame rx_frame) {
|
||||
switch (rx_frame.ID) {
|
||||
case 0x310: // Still alive message from inverter, every 1s
|
||||
datalayer.system.status.CAN_inverter_still_alive = CAN_STILL_ALIVE;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void send_can_inverter() {
|
||||
unsigned long currentMillis = millis();
|
||||
|
||||
// Send 500ms CAN Message
|
||||
if (currentMillis - previousMillis500ms >= INTERVAL_500_MS) {
|
||||
previousMillis500ms = currentMillis;
|
||||
|
||||
transmit_can(&SE_321, can_config.inverter);
|
||||
transmit_can(&SE_322, can_config.inverter);
|
||||
transmit_can(&SE_323, can_config.inverter);
|
||||
transmit_can(&SE_324, can_config.inverter);
|
||||
transmit_can(&SE_325, can_config.inverter);
|
||||
}
|
||||
// Send 2s CAN Message
|
||||
if (currentMillis - previousMillis2s >= INTERVAL_2_S) {
|
||||
previousMillis2s = currentMillis;
|
||||
|
||||
transmit_can(&SE_320, can_config.inverter);
|
||||
transmit_can(&SE_326, can_config.inverter);
|
||||
transmit_can(&SE_327, can_config.inverter);
|
||||
}
|
||||
// Send 10s CAN Message
|
||||
if (currentMillis - previousMillis10s >= INTERVAL_10_S) {
|
||||
previousMillis10s = currentMillis;
|
||||
transmit_can(&SE_328, can_config.inverter);
|
||||
transmit_can(&SE_330, can_config.inverter);
|
||||
transmit_can(&SE_331, can_config.inverter);
|
||||
transmit_can(&SE_332, can_config.inverter);
|
||||
transmit_can(&SE_333, can_config.inverter);
|
||||
}
|
||||
}
|
||||
|
||||
void setup_inverter(void) { // Performs one time setup
|
||||
strncpy(datalayer.system.info.inverter_protocol, "Schneider V2 SE BMS CAN", 63);
|
||||
datalayer.system.info.inverter_protocol[63] = '\0';
|
||||
}
|
||||
|
||||
#endif
|
33
Software/src/inverter/SCHNEIDER-CAN.h
Normal file
33
Software/src/inverter/SCHNEIDER-CAN.h
Normal file
|
@ -0,0 +1,33 @@
|
|||
#ifndef SCHNEIDER_CAN_H
|
||||
#define SCHNEIDER_CAN_H
|
||||
#include "../include.h"
|
||||
|
||||
#define CAN_INVERTER_SELECTED
|
||||
|
||||
#define STATE_OFFLINE 0
|
||||
#define STATE_STANDBY 1
|
||||
#define STATE_STARTING 2
|
||||
#define STATE_ONLINE 3
|
||||
#define STATE_FAULTED 4
|
||||
|
||||
// Same enumerations used for Fault and Warning
|
||||
#define FAULTS_CHARGE_OVERCURRENT 0
|
||||
#define FAULTS_DISCHARGE_OVERCURRENT 1
|
||||
#define FAULTS_OVER_TEMPERATURE 2
|
||||
#define FAULTS_UNDER_TEMPERATURE 3
|
||||
#define FAULTS_OVER_VOLTAGE 4
|
||||
#define FAULTS_UNDER_VOLTAGE 5
|
||||
#define FAULTS_CELL_IMBALANCE 6
|
||||
#define FAULTS_INTERNAL_COM_ERROR 7
|
||||
#define FAULTS_SYSTEM_ERROR 8
|
||||
|
||||
// Commands. Bit0 forced charge request. Bit1 charge permitted. Bit2 discharge permitted. Bit3 Stop
|
||||
#define COMMAND_ONLY_CHARGE_ALLOWED 0x02
|
||||
#define COMMAND_ONLY_DISCHARGE_ALLOWED 0x04
|
||||
#define COMMAND_CHARGE_AND_DISCHARGE_ALLOWED 0x06
|
||||
#define COMMAND_STOP 0x08
|
||||
|
||||
void transmit_can(CAN_frame* tx_frame, int interface);
|
||||
void setup_inverter(void);
|
||||
|
||||
#endif
|
|
@ -128,6 +128,8 @@ void manageSerialLinkTransmitter() {
|
|||
static unsigned long updateDataTime = 0;
|
||||
|
||||
if (currentTime - updateDataTime > INTERVAL_1_S) {
|
||||
strncpy(datalayer.system.info.inverter_protocol, "Serial link to another LilyGo board", 63);
|
||||
datalayer.system.info.inverter_protocol[63] = '\0';
|
||||
updateDataTime = currentTime;
|
||||
dataLinkTransmit.updateData(0, datalayer.battery.status.real_soc);
|
||||
dataLinkTransmit.updateData(1, datalayer.battery.status.soh_pptt);
|
||||
|
|
|
@ -6,5 +6,6 @@
|
|||
#include "../lib/mackelec-SerialDataLink/SerialDataLink.h"
|
||||
|
||||
void manageSerialLinkTransmitter();
|
||||
void setup_inverter(void);
|
||||
|
||||
#endif
|
||||
|
|
|
@ -249,4 +249,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';
|
||||
}
|
||||
#endif
|
||||
|
|
|
@ -8,5 +8,6 @@
|
|||
#define STOP_STATE 0x02
|
||||
|
||||
void transmit_can(CAN_frame* tx_frame, int interface);
|
||||
void setup_inverter(void);
|
||||
|
||||
#endif
|
||||
|
|
|
@ -320,4 +320,9 @@ void send_tripower_init() {
|
|||
transmit_can(&SMA_017, can_config.inverter); // Battery Manufacturer
|
||||
transmit_can(&SMA_018, can_config.inverter); // Battery Name
|
||||
}
|
||||
|
||||
void setup_inverter(void) { // Performs one time setup at startup over CAN bus
|
||||
strncpy(datalayer.system.info.inverter_protocol, "SMA Tripower CAN", 63);
|
||||
datalayer.system.info.inverter_protocol[63] = '\0';
|
||||
}
|
||||
#endif
|
||||
|
|
|
@ -6,5 +6,6 @@
|
|||
|
||||
void send_tripower_init();
|
||||
void transmit_can(CAN_frame* tx_frame, int interface);
|
||||
void setup_inverter(void);
|
||||
|
||||
#endif
|
||||
|
|
|
@ -263,4 +263,9 @@ void send_can_inverter() {
|
|||
transmit_can(&SOFAR_35A, can_config.inverter);
|
||||
}
|
||||
}
|
||||
|
||||
void setup_inverter(void) { // Performs one time setup at startup over CAN bus
|
||||
strncpy(datalayer.system.info.inverter_protocol, "Sofar BMS (Extended Frame) over CAN bus", 63);
|
||||
datalayer.system.info.inverter_protocol[63] = '\0';
|
||||
}
|
||||
#endif
|
||||
|
|
|
@ -5,5 +5,6 @@
|
|||
#define CAN_INVERTER_SELECTED
|
||||
|
||||
void transmit_can(CAN_frame* tx_frame, int interface);
|
||||
void setup_inverter(void);
|
||||
|
||||
#endif
|
||||
|
|
|
@ -252,4 +252,9 @@ void receive_can_inverter(CAN_frame rx_frame) {
|
|||
#endif
|
||||
}
|
||||
}
|
||||
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
|
||||
}
|
||||
#endif
|
||||
|
|
|
@ -15,5 +15,6 @@
|
|||
#define UPDATING_FW 4
|
||||
|
||||
void transmit_can(CAN_frame* tx_frame, int interface);
|
||||
void setup_inverter(void);
|
||||
|
||||
#endif
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue