Skip to content

Commit

Permalink
Merge branch 'main' into feature/kostal-rs485
Browse files Browse the repository at this point in the history
  • Loading branch information
dalathegreat committed Nov 22, 2024
2 parents f52efba + 22c47f2 commit 504c6b3
Show file tree
Hide file tree
Showing 74 changed files with 2,840 additions and 936 deletions.
13 changes: 6 additions & 7 deletions .github/workflows/compile-all-batteries.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,33 +34,32 @@ 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
- SERIAL_LINK_RECEIVER
# 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

Expand Down
3 changes: 3 additions & 0 deletions .github/workflows/compile-all-inverters.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
144 changes: 83 additions & 61 deletions Software/Software.ino
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -172,11 +170,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
Expand Down Expand Up @@ -296,7 +293,7 @@ void core_loop(void* task_time_us) {
#ifdef DOUBLE_BATTERY
update_values_battery2();
#endif
update_scaled_values(); // Check if real or calculated SOC% value should be sent
update_calculated_values();
#ifndef SERIAL_LINK_RECEIVER
update_machineryprotection(); // Check safeties (Not on serial link reciever board)
#endif
Expand Down Expand Up @@ -404,11 +401,11 @@ void init_stored_settings() {
}
temp = settings.getUInt("MAXCHARGEAMP", false);
if (temp != 0) {
datalayer.battery.info.max_charge_amp_dA = temp;
datalayer.battery.settings.max_user_set_charge_dA = temp;
}
temp = settings.getUInt("MAXDISCHARGEAMP", false);
if (temp != 0) {
datalayer.battery.info.max_discharge_amp_dA = temp;
datalayer.battery.settings.max_user_set_discharge_dA = temp;
temp = settings.getBool("USE_SCALED_SOC", false);
datalayer.battery.settings.soc_scaling_active = temp; //This bool needs to be checked inside the temp!= block
} // No way to know if it wasnt reset otherwise
Expand Down Expand Up @@ -559,29 +556,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() {
Expand Down Expand Up @@ -619,31 +593,32 @@ void init_equipment_stop_button() {

#endif

#ifdef CAN_FD
// Functions
#ifdef DEBUG_CANFD_DATA
enum frameDirection { MSG_RX, MSG_TX };
void print_canfd_frame(CANFDMessage rx_frame, frameDirection msgDir); // Needs to be declared before it is defined
void print_canfd_frame(CANFDMessage rx_frame, frameDirection msgDir) {
int i = 0;
enum frameDirection { MSG_RX, MSG_TX }; //RX = 0, TX = 1
void print_can_frame(CAN_frame frame, frameDirection msgDir);
void print_can_frame(CAN_frame frame, frameDirection msgDir) {
uint8_t i = 0;
Serial.print(millis());
Serial.print(" ");
(msgDir == 0) ? Serial.print("RX ") : Serial.print("TX ");
Serial.print(rx_frame.id, HEX);
Serial.print(frame.ID, HEX);
Serial.print(" ");
Serial.print(frame.DLC);
Serial.print(" ");
for (i = 0; i < rx_frame.len; i++) {
Serial.print(rx_frame.data[i] < 16 ? "0" : "");
Serial.print(rx_frame.data[i], HEX);
for (i = 0; i < frame.DLC; i++) {
Serial.print(frame.data.u8[i] < 16 ? "0" : "");
Serial.print(frame.data.u8[i], HEX);
Serial.print(" ");
}
Serial.println(" ");
}
#endif

#ifdef CAN_FD
// Functions
void receive_canfd() { // This section checks if we have a complete CAN-FD message incoming
CANFDMessage frame;
if (canfd.available()) {
canfd.receive(frame);
#ifdef DEBUG_CANFD_DATA
print_canfd_frame(frame, frameDirection(MSG_RX));
#endif

CAN_frame rx_frame;
rx_frame.ID = frame.id;
rx_frame.ext_ID = frame.ext;
Expand Down Expand Up @@ -842,7 +817,26 @@ void handle_contactors() {
#endif // CONTACTOR_CONTROL
}

void update_scaled_values() {
void update_calculated_values() {
/* Calculate allowed charge/discharge currents*/
if (datalayer.battery.status.voltage_dV > 10) {
// Only update value when we have voltage available to avoid div0. TODO: This should be based on nominal voltage
datalayer.battery.status.max_charge_current_dA =
((datalayer.battery.status.max_charge_power_W * 100) / datalayer.battery.status.voltage_dV);
datalayer.battery.status.max_discharge_current_dA =
((datalayer.battery.status.max_discharge_power_W * 100) / datalayer.battery.status.voltage_dV);
}
/* Restrict values from user settings if needed*/
if (datalayer.battery.status.max_charge_current_dA > datalayer.battery.settings.max_user_set_charge_dA) {
datalayer.battery.status.max_charge_current_dA = datalayer.battery.settings.max_user_set_charge_dA;
}
if (datalayer.battery.status.max_discharge_current_dA > datalayer.battery.settings.max_user_set_discharge_dA) {
datalayer.battery.status.max_discharge_current_dA = datalayer.battery.settings.max_user_set_discharge_dA;
}
/* Calculate active power based on voltage and current*/
datalayer.battery.status.active_power_W =
(datalayer.battery.status.current_dA * (datalayer.battery.status.voltage_dV / 100));

if (datalayer.battery.settings.soc_scaling_active) {
/** SOC Scaling
*
Expand All @@ -865,6 +859,8 @@ void update_scaled_values() {
* Before we use real_soc, we must make sure that it's within the range of min_percentage and max_percentage.
*/
uint32_t calc_soc;
uint32_t calc_max_capacity;
uint32_t calc_reserved_capacity;
// Make sure that the SOC starts out between min and max percentages
calc_soc = CONSTRAIN(datalayer.battery.status.real_soc, datalayer.battery.settings.min_percentage,
datalayer.battery.settings.max_percentage);
Expand All @@ -875,8 +871,6 @@ void update_scaled_values() {

// Calculate the scaled remaining capacity in Wh
if (datalayer.battery.info.total_capacity_Wh > 0 && datalayer.battery.status.real_soc > 0) {
uint32_t calc_max_capacity;
uint32_t calc_reserved_capacity;
calc_max_capacity = (datalayer.battery.status.remaining_capacity_Wh * 10000 / datalayer.battery.status.real_soc);
calc_reserved_capacity = calc_max_capacity * datalayer.battery.settings.min_percentage / 10000;
// remove % capacity reserved in min_percentage to total_capacity_Wh
Expand All @@ -886,9 +880,31 @@ void update_scaled_values() {
datalayer.battery.status.reported_remaining_capacity_Wh = datalayer.battery.status.remaining_capacity_Wh;
}

} else { // No SOC window wanted. Set scaled to same as real.
#ifdef DOUBLE_BATTERY
/* Calculate active power based on voltage and current*/
datalayer.battery2.status.active_power_W =
(datalayer.battery2.status.current_dA * (datalayer.battery2.status.voltage_dV / 100));

// Calculate the scaled remaining capacity in Wh
if (datalayer.battery2.info.total_capacity_Wh > 0 && datalayer.battery2.status.real_soc > 0) {
calc_max_capacity =
(datalayer.battery2.status.remaining_capacity_Wh * 10000 / datalayer.battery2.status.real_soc);
calc_reserved_capacity = calc_max_capacity * datalayer.battery2.settings.min_percentage / 10000;
// remove % capacity reserved in min_percentage to total_capacity_Wh
datalayer.battery2.status.reported_remaining_capacity_Wh =
datalayer.battery2.status.remaining_capacity_Wh - calc_reserved_capacity;
} else {
datalayer.battery2.status.reported_remaining_capacity_Wh = datalayer.battery2.status.remaining_capacity_Wh;
}
#endif

} else { // soc_scaling_active == false. No SOC window wanted. Set scaled to same as real.
datalayer.battery.status.reported_soc = datalayer.battery.status.real_soc;
datalayer.battery.status.reported_remaining_capacity_Wh = datalayer.battery.status.remaining_capacity_Wh;
#ifdef DOUBLE_BATTERY
datalayer.battery2.status.reported_soc = datalayer.battery2.status.real_soc;
datalayer.battery2.status.reported_remaining_capacity_Wh = datalayer.battery2.status.remaining_capacity_Wh;
#endif
}
#ifdef DOUBLE_BATTERY
// Perform extra SOC sanity checks on double battery setups
Expand Down Expand Up @@ -964,8 +980,8 @@ void storeSettings() {
datalayer.battery.settings.max_percentage / 10); // Divide by 10 for backwards compatibility
settings.putUInt("MINPERCENTAGE",
datalayer.battery.settings.min_percentage / 10); // Divide by 10 for backwards compatibility
settings.putUInt("MAXCHARGEAMP", datalayer.battery.info.max_charge_amp_dA);
settings.putUInt("MAXDISCHARGEAMP", datalayer.battery.info.max_discharge_amp_dA);
settings.putUInt("MAXCHARGEAMP", datalayer.battery.settings.max_user_set_charge_dA);
settings.putUInt("MAXDISCHARGEAMP", datalayer.battery.settings.max_user_set_discharge_dA);
settings.putBool("USE_SCALED_SOC", datalayer.battery.settings.soc_scaling_active);
settings.end();
}
Expand Down Expand Up @@ -1051,6 +1067,9 @@ void transmit_can(CAN_frame* tx_frame, int interface) {
if (!allowed_to_send_CAN) {
return;
}
#ifdef DEBUG_CAN_DATA
print_can_frame(*tx_frame, frameDirection(MSG_TX));
#endif //DEBUG_CAN_DATA

switch (interface) {
case CAN_NATIVE:
Expand Down Expand Up @@ -1093,10 +1112,6 @@ void transmit_can(CAN_frame* tx_frame, int interface) {
send_ok = canfd.tryToSend(MCP2518Frame);
if (!send_ok) {
set_event(EVENT_CANFD_BUFFER_FULL, interface);
} else {
#ifdef DEBUG_CANFD_DATA
print_canfd_frame(MCP2518Frame, frameDirection(MSG_TX));
#endif
}
#else // Interface not compiled, and settings try to use it
set_event(EVENT_INTERFACE_MISSING, interface);
Expand All @@ -1109,8 +1124,15 @@ void transmit_can(CAN_frame* tx_frame, int interface) {
}
void receive_can(CAN_frame* rx_frame, int interface) {

#ifdef DEBUG_CAN_DATA
print_can_frame(*rx_frame, frameDirection(MSG_RX));
#endif //DEBUG_CAN_DATA

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
Expand Down
6 changes: 5 additions & 1 deletion Software/USER_SETTINGS.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

/* Select battery used */
//#define BMW_I3_BATTERY
//#define BMW_IX_BATTERY
//#define BYD_ATTO_3_BATTERY
//#define CELLPOWER_BMS
//#define CHADEMO_BATTERY //NOTE: inherently enables CONTACTOR_CONTROL below
Expand All @@ -22,6 +23,7 @@
//#define NISSAN_LEAF_BATTERY
//#define PYLON_BATTERY
//#define RJXZS_BMS
//#define RANGE_ROVER_PHEV_BATTERY
//#define RENAULT_KANGOO_BATTERY
//#define RENAULT_TWIZY_BATTERY
//#define RENAULT_ZOE_GEN1_BATTERY
Expand All @@ -42,6 +44,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
Expand All @@ -50,10 +53,11 @@
/* Select hardware used for Battery-Emulator */
#define HW_LILYGO
//#define HW_STARK
//#define HW_3LB

/* Other options */
//#define DEBUG_VIA_USB //Enable this line to have the USB port output serial diagnostic data while program runs (WARNING, raises CPU load, do not use for production)
//#define DEBUG_CANFD_DATA //Enable this line to have the USB port output CAN-FD data while program runs (WARNING, raises CPU load, do not use for production)
//#define DEBUG_CAN_DATA //Enable this line to print incoming/outgoing CAN & CAN-FD messages to USB serial (WARNING, raises CPU load, do not use for production)
//#define INTERLOCK_REQUIRED //Nissan LEAF specific setting, if enabled requires both high voltage conenctors to be seated before starting
//#define CONTACTOR_CONTROL //Enable this line to have pins 25,32,33 handle automatic precharge/contactor+/contactor- closing sequence
//#define PWM_CONTACTOR_CONTROL //Enable this line to use PWM for CONTACTOR_CONTROL, which lowers power consumption and heat generation. CONTACTOR_CONTROL must be enabled.
Expand Down
9 changes: 9 additions & 0 deletions Software/src/battery/BATTERIES.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@
#include "BMW-I3-BATTERY.h"
#endif

#ifdef BMW_IX_BATTERY
#include "BMW-IX-BATTERY.h"
#endif

#ifdef BYD_ATTO_3_BATTERY
#include "BYD-ATTO-3-BATTERY.h"
#endif
Expand All @@ -16,6 +20,7 @@

#ifdef CHADEMO_BATTERY
#include "CHADEMO-BATTERY.h"
#include "CHADEMO-SHUNTS.h"
#endif

#ifdef IMIEV_CZERO_ION_BATTERY
Expand Down Expand Up @@ -54,6 +59,10 @@
#include "RJXZS-BMS.h"
#endif

#ifdef RANGE_ROVER_PHEV_BATTERY
#include "RANGE-ROVER-PHEV-BATTERY.h"
#endif

#ifdef RENAULT_KANGOO_BATTERY
#include "RENAULT-KANGOO-BATTERY.h"
#endif
Expand Down
Loading

0 comments on commit 504c6b3

Please sign in to comment.