Merge branch 'main' into feature/kostal-rs485

This commit is contained in:
Daniel Öster 2024-11-22 19:00:04 +02:00
commit 504c6b3d7a
74 changed files with 2835 additions and 931 deletions

View file

@ -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
@ -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
@ -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
@ -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
@ -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() {
@ -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;
(msgDir == 0) ? Serial.print("RX ") : Serial.print("TX ");
Serial.print(rx_frame.id, HEX);
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(" ");
for (i = 0; i < rx_frame.len; i++) {
Serial.print(rx_frame.data[i] < 16 ? "0" : "");
Serial.print(rx_frame.data[i], HEX);
(msgDir == 0) ? Serial.print("RX ") : Serial.print("TX ");
Serial.print(frame.ID, HEX);
Serial.print(" ");
Serial.print(frame.DLC);
Serial.print(" ");
for (i = 0; i < frame.DLC; i++) {
Serial.print(frame.data.u8[i] < 16 ? "0" : "");
Serial.print(frame.data.u8[i], HEX);
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;
@ -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
*
@ -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);
@ -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
@ -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
@ -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();
}
@ -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:
@ -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);
@ -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