From a718d99672bb174b6448e15be949de18dca47ff9 Mon Sep 17 00:00:00 2001 From: Jonny Date: Sat, 16 Aug 2025 12:20:30 +0100 Subject: [PATCH] Make Solax use type settings in COMMON_IMAGE mode, add ignore contactors feature --- Software/src/inverter/SOLAX-CAN.cpp | 59 +++++++++++++++++++++++------ Software/src/inverter/SOLAX-CAN.h | 12 ++++++ 2 files changed, 59 insertions(+), 12 deletions(-) diff --git a/Software/src/inverter/SOLAX-CAN.cpp b/Software/src/inverter/SOLAX-CAN.cpp index a3f77a3e..16df4f2a 100644 --- a/Software/src/inverter/SOLAX-CAN.cpp +++ b/Software/src/inverter/SOLAX-CAN.cpp @@ -4,11 +4,7 @@ #include "../datalayer/datalayer.h" #include "../devboard/utils/events.h" #include "../devboard/utils/logging.h" - -#define NUMBER_OF_MODULES 0 -#define BATTERY_TYPE 0x50 -// If you are having BattVoltFault issues, configure the above values according to wiki page -// https://github.com/dalathegreat/Battery-Emulator/wiki/Solax-inverters +#include "../inverter/INVERTERS.h" // __builtin_bswap64 needed to convert to ESP32 little endian format // Byte[4] defines the requested contactor state: 1 = Closed , 0 = Open @@ -18,7 +14,7 @@ void SolaxInverter:: update_values() { //This function maps all the values fetched from battery CAN to the correct CAN messages // If not receiveing any communication from the inverter, open contactors and return to battery announce state - if (millis() - LastFrameTime >= SolaxTimeout) { + if (millis() - LastFrameTime >= SolaxTimeout && !configured_ignore_contactors) { datalayer.system.status.inverter_allows_contactor_closing = false; STATE = BATTERY_ANNOUNCE; } @@ -73,8 +69,8 @@ void SolaxInverter:: //BMS_Status SOLAX_1875.data.u8[0] = (uint8_t)temperature_average; SOLAX_1875.data.u8[1] = (temperature_average >> 8); - SOLAX_1875.data.u8[2] = (uint8_t)NUMBER_OF_MODULES; // Number of slave batteries - SOLAX_1875.data.u8[4] = (uint8_t)0; // Contactor Status 0=off, 1=on. + SOLAX_1875.data.u8[2] = (uint8_t)configured_number_of_modules; // Number of slave batteries + SOLAX_1875.data.u8[4] = (uint8_t)0; // Contactor Status 0=off, 1=on. //BMS_PackTemps (strange name, since it has voltages?) SOLAX_1876.data.u8[0] = (int8_t)datalayer.battery.status.temperature_max_dC; @@ -88,8 +84,8 @@ void SolaxInverter:: SOLAX_1876.data.u8[7] = (datalayer.battery.status.cell_min_voltage_mV >> 8); //Unknown - SOLAX_1877.data.u8[4] = (uint8_t)BATTERY_TYPE; // Battery type (Default 0x50) - SOLAX_1877.data.u8[6] = (uint8_t)0x22; // Firmware version? + SOLAX_1877.data.u8[4] = (uint8_t)configured_battery_type; // Battery type (Default 0x50) + SOLAX_1877.data.u8[6] = (uint8_t)0x22; // Firmware version? SOLAX_1877.data.u8[7] = (uint8_t)0x02; // The above firmware version applies to:02 = Master BMS, 10 = S1, 20 = S2, 30 = S3, 40 = S4 @@ -129,6 +125,26 @@ void SolaxInverter::map_can_frame_to_variable(CAN_frame rx_frame) { if (rx_frame.ID == 0x1871 && rx_frame.data.u8[0] == (0x01) || rx_frame.ID == 0x1871 && rx_frame.data.u8[0] == (0x02)) { LastFrameTime = millis(); + + if (configured_ignore_contactors) { + // Skip the state machine since we're not going to open/close contactors, + // and the Solax would otherwise wait forever for us to do so. + + datalayer.system.status.inverter_allows_contactor_closing = true; + SOLAX_1875.data.u8[4] = (0x01); // Inform Inverter: Contactor 0=off, 1=on. + transmit_can_frame(&SOLAX_187E); + transmit_can_frame(&SOLAX_187A); + transmit_can_frame(&SOLAX_1872); + transmit_can_frame(&SOLAX_1873); + transmit_can_frame(&SOLAX_1874); + transmit_can_frame(&SOLAX_1875); + transmit_can_frame(&SOLAX_1876); + transmit_can_frame(&SOLAX_1877); + transmit_can_frame(&SOLAX_1878); + transmit_can_frame(&SOLAX_100A001); + return; + } + switch (STATE) { case (BATTERY_ANNOUNCE): #ifdef DEBUG_LOG @@ -208,7 +224,26 @@ void SolaxInverter::map_can_frame_to_variable(CAN_frame rx_frame) { } } -bool SolaxInverter::setup(void) { // Performs one time setup at startup - datalayer.system.status.inverter_allows_contactor_closing = false; // The inverter needs to allow first +bool SolaxInverter::setup(void) { // Performs one time setup at startup + // Use user selected values if nonzero, otherwise use defaults + if (user_selected_inverter_modules > 0) { + configured_number_of_modules = user_selected_inverter_modules; + } else { + configured_number_of_modules = NUMBER_OF_MODULES; + } + + if (user_selected_inverter_battery_type > 0) { + configured_battery_type = user_selected_inverter_battery_type; + } else { + configured_battery_type = BATTERY_TYPE; + } + + configured_ignore_contactors = user_selected_inverter_ignore_contactors; + + if (!configured_ignore_contactors) { + // Only prevent closing if we're not ignoring contactors + datalayer.system.status.inverter_allows_contactor_closing = false; // The inverter needs to allow first + } + return true; } diff --git a/Software/src/inverter/SOLAX-CAN.h b/Software/src/inverter/SOLAX-CAN.h index 0a3d8efa..6a67e8bf 100644 --- a/Software/src/inverter/SOLAX-CAN.h +++ b/Software/src/inverter/SOLAX-CAN.h @@ -17,6 +17,11 @@ class SolaxInverter : public CanInverterProtocol { static constexpr const char* Name = "SolaX Triple Power LFP over CAN bus"; private: + static const int NUMBER_OF_MODULES = 0; + static const int BATTERY_TYPE = 0x50; + // If you are having BattVoltFault issues, configure the above values according to wiki page + // https://github.com/dalathegreat/Battery-Emulator/wiki/Solax-inverters + // Timeout in milliseconds static const int SolaxTimeout = 2000; @@ -34,6 +39,13 @@ class SolaxInverter : public CanInverterProtocol { uint16_t capped_capacity_Wh; uint16_t capped_remaining_capacity_Wh; + int configured_number_of_modules = 0; + int configured_battery_type = 0; + // If true, the integration will ignore the inverter's requests to open the + // battery contactors. Useful for batteries that can't open contactors on + // request. + bool configured_ignore_contactors = false; + //CAN message translations from this amazing repository: https://github.com/rand12345/solax_can_bus CAN_frame SOLAX_1801 = {.FD = false,