diff --git a/Software/SOLAX-CAN.cpp b/Software/SOLAX-CAN.cpp index 0d6342e5..26ade1b1 100644 --- a/Software/SOLAX-CAN.cpp +++ b/Software/SOLAX-CAN.cpp @@ -3,47 +3,114 @@ #include "CAN_config.h" /* Do not change code below unless you are sure what you are doing */ -static unsigned long previousMillis2s = 0; // will store last time a 2s CAN Message was send -static unsigned long previousMillis10s = 0; // will store last time a 10s CAN Message was send -static unsigned long previousMillis60s = 0; // will store last time a 60s CAN Message was send -static const int interval2s = 2000; // interval (ms) at which send CAN Messages -static const int interval10s = 10000; // interval (ms) at which send CAN Messages -static const int interval60s = 60000; // interval (ms) at which send CAN Messages +static unsigned long previousMillis100ms = 0; // will store last time a 100ms CAN Message was sent +static const int interval100ms = 100; // interval (ms) at which send CAN Messages +static int temp = 0; //Temporary variable used for bitshifting +static int max_charge_rate_amp = 0; +static int max_discharge_rate_amp = 0; -CAN_frame_t SOLAX_1872 = {.FIR = {.B = {.DLC = 8,.FF = CAN_frame_ext,}},.MsgID = 0x1872,.data = {0x03, 0x16, 0x00, 0x66, 0x00, 0x33, 0x02, 0x09}}; +//CAN message translations from this amazing repository: https://github.com/rand12345/solax_can_bus + +CAN_frame_t SOLAX_1872 = {.FIR = {.B = {.DLC = 8,.FF = CAN_frame_ext,}},.MsgID = 0x1872,.data = {0x8A, 0xF, 0x52, 0xC, 0xCD, 0x0, 0x5E, 0x1}}; //BMS_Limits +CAN_frame_t SOLAX_1873 = {.FIR = {.B = {.DLC = 8,.FF = CAN_frame_ext,}},.MsgID = 0x1873,.data = {0x6D, 0xD, 0x0, 0x0, 0x5D, 0x0, 0xA3, 0x1}}; //BMS_PackData +CAN_frame_t SOLAX_1874 = {.FIR = {.B = {.DLC = 8,.FF = CAN_frame_ext,}},.MsgID = 0x1874,.data = {0xCE, 0x0, 0xBC, 0x0, 0x29, 0x0, 0x28, 0x0}}; //BMS_CellData +CAN_frame_t SOLAX_1875 = {.FIR = {.B = {.DLC = 8,.FF = CAN_frame_ext,}},.MsgID = 0x1875,.data = {0x0, 0x0, 0x2, 0x0, 0x0, 0x0, 0x3A, 0x0}}; //BMS_Status +CAN_frame_t SOLAX_1876 = {.FIR = {.B = {.DLC = 8,.FF = CAN_frame_ext,}},.MsgID = 0x1876,.data = {0x0, 0x0, 0xD4, 0x0F, 0x0, 0x0, 0xC9, 0x0F}}; //BMS_PackTemps +CAN_frame_t SOLAX_1877 = {.FIR = {.B = {.DLC = 8,.FF = CAN_frame_ext,}},.MsgID = 0x1877,.data = {0x0, 0x0, 0x0, 0x0, 0x53, 0x0, 0x1D, 0x10}}; +CAN_frame_t SOLAX_1878 = {.FIR = {.B = {.DLC = 8,.FF = CAN_frame_ext,}},.MsgID = 0x1878,.data = {0x6D, 0xD, 0x0, 0x0, 0xB0, 0x3, 0x4, 0x0}}; //BMS_PackStats +CAN_frame_t SOLAX_1879 = {.FIR = {.B = {.DLC = 8,.FF = CAN_frame_ext,}},.MsgID = 0x1879,.data = {0x1, 0x8, 0x1, 0x2, 0x1, 0x2, 0x0, 0x3}}; +CAN_frame_t SOLAX_1801 = {.FIR = {.B = {.DLC = 8,.FF = CAN_frame_ext,}},.MsgID = 0x1801,.data = {0x2, 0x0, 0x1, 0x0, 0x1, 0x0, 0x0, 0x0}}; +CAN_frame_t SOLAX_1881 = {.FIR = {.B = {.DLC = 8,.FF = CAN_frame_ext,}},.MsgID = 0x1881,.data = {0x0, 0x36, 0x53, 0x42, 0x4D, 0x53, 0x46, 0x41}}; // 0 6 S B M S F A +CAN_frame_t SOLAX_1882 = {.FIR = {.B = {.DLC = 8,.FF = CAN_frame_ext,}},.MsgID = 0x1882,.data = {0x0, 0x32, 0x33, 0x41, 0x42, 0x30, 0x35, 0x32}}; // 0 2 3 A B 0 5 2 +CAN_frame_t SOLAX_100A001 = {.FIR = {.B = {.DLC = 8,.FF = CAN_frame_ext,}},.MsgID = 0x100A001,.data = {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}}; void update_values_can_solax() { //This function maps all the values fetched from battery CAN to the correct CAN messages + + //SOC (100.00%) + temp = SOC/100; //Remove decimals, inverter takes only integer in a byte + SOLAX_1873.data.u8[4] = temp; + + //max_target_charge_power (30000W max) + if(SOC > 9999) //99.99% + { //Additional safety incase SOC% is 100, then do not charge battery further + max_charge_rate_amp = 0; + } + else + { //We can pass on the battery charge rate (in W) to the inverter (that takes A) + if(max_target_charge_power >= 30000) + { + max_charge_rate_amp = 75; //Incase battery can take over 30kW, cap value to 75A + } + else + { //Calculate the W value into A + max_charge_rate_amp = (max_target_charge_power/(battery_voltage*0.1)); // P/U = I + } + } + //Increase decimal amount + max_charge_rate_amp = max_charge_rate_amp*10; + + //Write the calculated charge rate to the CAN message + SOLAX_1872.data.u8[4] = (uint8_t) max_charge_rate_amp; //TODO, test that values are OK + SOLAX_1872.data.u8[5] = (max_charge_rate_amp << 8); + + //max_target_discharge_power (30000W max) + if(SOC < 100) //1.00% + { //Additional safety incase SOC% is below 1, then do not charge battery further + max_discharge_rate_amp = 0; + } + else + { //We can pass on the battery discharge rate to the inverter + if(max_target_discharge_power >= 30000) + { + max_discharge_rate_amp = 75; //Incase battery can be charged with over 30kW, cap value to 75A + } + else + { //Calculate the W value into A + max_discharge_rate_amp = (max_target_discharge_power/(battery_voltage*0.1)); // P/U = I + } + } + //Increase decimal amount + max_discharge_rate_amp = max_discharge_rate_amp*10; + + //Write the calculated charge rate to the CAN message + SOLAX_1872.data.u8[6] = (uint8_t) max_discharge_rate_amp; //TODO, test that values are OK + SOLAX_1872.data.u8[7] = (max_discharge_rate_amp << 8); + + //Todo (ranked in priority) + //Add current + //Add voltage + //Add remaining kWh + //Add temperature + //Add cell voltages + //Add pack voltage min/max for alarms + //Add cell voltage min/max for alarms } void send_can_solax() { unsigned long currentMillis = millis(); - // Send 2s CAN Message - if (currentMillis - previousMillis2s >= interval2s) + // Send 100ms CAN Message + if (currentMillis - previousMillis100ms >= interval100ms) { - previousMillis2s = currentMillis; + previousMillis100ms = currentMillis; - } - // Send 10s CAN Message - if (currentMillis - previousMillis10s >= interval10s) - { - previousMillis10s = currentMillis; + ESP32Can.CANWriteFrame(&SOLAX_1872); + ESP32Can.CANWriteFrame(&SOLAX_1873); + ESP32Can.CANWriteFrame(&SOLAX_1874); + ESP32Can.CANWriteFrame(&SOLAX_1875); + ESP32Can.CANWriteFrame(&SOLAX_1876); + ESP32Can.CANWriteFrame(&SOLAX_1877); + ESP32Can.CANWriteFrame(&SOLAX_1878); - //Serial.println("CAN 10s done"); - } - //Send 60s message - if (currentMillis - previousMillis60s >= interval60s) - { - previousMillis60s = currentMillis; - - //ESP32Can.CANWriteFrame(&BYD_190); - //Serial.println("CAN 60s done"); + //Todo, how often should the messages be sent? And the other messages, only on bootup? } } void receive_can_solax(CAN_frame_t rx_frame) { - Serial.println("Inverter sending CAN message"); + //Serial.println("Inverter sending CAN message"); + //0x1871 [0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00] + //Todo, should we respond with something once the inverter sends a message? } \ No newline at end of file diff --git a/Software/SOLAX-CAN.h b/Software/SOLAX-CAN.h index 6a91c7f0..6df213c1 100644 --- a/Software/SOLAX-CAN.h +++ b/Software/SOLAX-CAN.h @@ -27,7 +27,7 @@ extern uint16_t max_volt_byd_can; #define FAULT 4 #define UPDATING 5 -void update_values_can_byd(); +void update_values_can_solax(); void send_can_solax(); void receive_can_solax(CAN_frame_t rx_frame); #endif \ No newline at end of file diff --git a/Software/Software.ino b/Software/Software.ino index 1dee6f69..54416851 100644 --- a/Software/Software.ino +++ b/Software/Software.ino @@ -28,7 +28,7 @@ CAN_device_t CAN_cfg; // CAN Config const int rx_queue_size = 10; // Receive Queue size //Interval settings -const int intervalModbusTask = 4800; //Interval at which to refresh modbus registers +const int intervalInverterTask = 4800; //Interval at which to refresh modbus registers / inverter values const int interval10 = 10; //ModbusRTU parameters @@ -58,7 +58,7 @@ uint16_t StateOfHealth = 9900; //SOH 0-100.00% //Updates later on from CAN uint16_t capacity_Wh = BATTERY_WH_MAX; //Updates later on from CAN uint16_t remaining_capacity_Wh = BATTERY_WH_MAX; //Updates later on from CAN uint16_t max_target_discharge_power = 0; //0W (0W > restricts to no discharge) //Updates later on from CAN -uint16_t max_target_charge_power = 4312; //4.3kW (during charge), both 307&308 can be set (>0) at the same time //Updates later on from CAN +uint16_t max_target_charge_power = 4312; //4.3kW (during charge), both 307&308 can be set (>0) at the same time //Updates later on from CAN. Max value is 30000W uint16_t temperature_max = 50; //reads from battery later uint16_t temperature_min = 60; //reads from battery later uint16_t bms_char_dis_status; //0 idle, 1 discharging, 2, charging @@ -170,10 +170,10 @@ void loop() handle_contactors(); //Take care of startup precharge/contactor closing } - if (millis() - previousMillisModbus >= intervalModbusTask) //every 5s + if (millis() - previousMillisModbus >= intervalInverterTask) //every 5s { previousMillisModbus = millis(); - handle_modbus(); //Update values heading towards modbus + handle_inverter(); //Update values heading towards inverter } } @@ -227,7 +227,7 @@ void handle_can() #endif } -void handle_modbus() +void handle_inverter() { #ifdef BATTERY_TYPE_LEAF update_values_leaf_battery(); //Map the values to the correct registers @@ -238,8 +238,16 @@ void handle_modbus() #ifdef RENAULT_ZOE_BATTERY update_values_zoe_battery(); //Map the values to the correct registers #endif - handle_update_data_modbusp201(); //Updata for ModbusRTU Server for GEN24 - handle_update_data_modbusp301(); //Updata for ModbusRTU Server for GEN24 + #ifdef SOLAX_CAN + update_values_can_solax(); + #endif + #ifdef CAN_BYD + update_values_can_byd(); + #endif + + //Updata for ModbusRTU Server for GEN24 + handle_update_data_modbusp201(); + handle_update_data_modbusp301(); } void handle_contactors()