diff --git a/Software/Software.ino b/Software/Software.ino index 3820398c..4fccaea9 100644 --- a/Software/Software.ino +++ b/Software/Software.ino @@ -170,7 +170,7 @@ void setup() { init_rs485(); init_serialDataLink(); -#if defined(CAN_INVERTER_SELECTED) || defined(MODBUS_INVERTER_SELECTED) +#if defined(CAN_INVERTER_SELECTED) || defined(MODBUS_INVERTER_SELECTED) || defined(RS485_INVERTER_SELECTED) setup_inverter(); #endif setup_battery(); diff --git a/Software/src/inverter/INVERTERS.h b/Software/src/inverter/INVERTERS.h index bf933c8c..f1944ecf 100644 --- a/Software/src/inverter/INVERTERS.h +++ b/Software/src/inverter/INVERTERS.h @@ -72,6 +72,7 @@ void update_modbus_registers_inverter(); #ifdef RS485_INVERTER_SELECTED void receive_RS485(); void update_RS485_registers_inverter(); +void setup_inverter(); #endif #endif diff --git a/Software/src/inverter/KOSTAL-RS485.cpp b/Software/src/inverter/KOSTAL-RS485.cpp index 797ef39b..02d8c4a4 100644 --- a/Software/src/inverter/KOSTAL-RS485.cpp +++ b/Software/src/inverter/KOSTAL-RS485.cpp @@ -10,8 +10,6 @@ static const uint8_t KOSTAL_FRAMEHEADER[5] = {0x62, 0xFF, 0x02, 0xFF, 0x29}; static const uint8_t KOSTAL_FRAMEHEADER2[5] = {0x63, 0xFF, 0x02, 0xFF, 0x29}; static uint16_t nominal_voltage_dV = 0; -static uint16_t discharge_current_dA = 0; -static uint16_t charge_current_dA = 0; static int16_t average_temperature_dC = 0; static uint8_t incoming_message_counter = RS485_HEALTHY; static int8_t f2_startup_count = 0; @@ -29,31 +27,33 @@ union f32b { byte b[4]; }; -uint8_t BATTERY_INFO[40] = {0x00, 0xE2, 0xFF, 0x02, 0xFF, 0x29, // Frame header - 0x00, 0x00, 0x80, 0x43, // 256.063 Nominal voltage / 5*51.2=256 first byte 0x01 or 0x04 - 0xE4, 0x70, 0x8A, 0x5C, // Manufacture date (Epoch time) (BYD: GetBatteryInfo this[0x10ac]) - 0xB5, 0x00, 0xD3, 0x00, // Battery Serial number? Modbus register 527 - 0x10b0 - 0x00, 0x00, 0xC8, 0x41, // 0x10b4 - 0xC2, 0x18, // Battery Firmware, modbus register 586 (0x10b8) - 0x00, // Static (BYD: GetBatteryInfo this[0x10ba]) - 0x00, // ? - 0x59, 0x42, // Static (BYD: GetBatteryInfo this[0x10bc]) - 0x00, 0x00, // Static (BYD: GetBatteryInfo this[0x10be]) - 0x00, 0x00, - 0x05, 0x00, 0xA0, 0x00, 0x00, 0x00, - 0x4D, // CRC - 0x00}; // +uint8_t BATTERY_INFO[40] = { + 0x00, 0xE2, 0xFF, 0x02, 0xFF, 0x29, // Frame header + 0x00, 0x00, 0x80, 0x43, // 256.063 Nominal voltage / 5*51.2=256 first byte 0x01 or 0x04 + 0xE4, 0x70, 0x8A, 0x5C, // Manufacture date (Epoch time) (BYD: GetBatteryInfo this[0x10ac]) + 0xB5, 0x00, 0xD3, 0x00, // Battery Serial number? Modbus register 527 - 0x10b0 + 0x00, 0x00, 0xC8, 0x41, // 0x10b4 + 0xC2, 0x18, // Battery Firmware, modbus register 586 (0x10b8) + 0x00, // Static (BYD: GetBatteryInfo this[0x10ba]) + 0x00, // ? + 0x59, 0x42, // Static (BYD: GetBatteryInfo this[0x10bc]) + 0x00, 0x00, // Static (BYD: GetBatteryInfo this[0x10be]) + 0x00, 0x00, 0x05, 0x00, 0xA0, 0x00, 0x00, 0x00, + 0x4D, // CRC + 0x00}; // // values in CyclicData will be overwritten at update_modbus_registers_inverter() uint8_t CyclicData[64] = { 0x00, // First zero byte pointer 0xE2, 0xFF, 0x02, 0xFF, 0x29, // frame Header - 0x1D, 0x5A, 0x85, 0x43, // Current Voltage (float) Modbus register 216, Bytes 6-9 - 0x00, 0x00, 0x8D, 0x43, // Max Voltage (2 byte float), Bytes 12-13 - 0x00, 0x00, 0xAC, 0x41, // BAttery Temperature (2 byte float) Modbus register 214, Bytes 16-17 - 0x00, 0x00, 0x00, 0x00, // Peak Current (1s period?), Bytes 18-21 - Communication fault seen with some values (>10A?) - 0x00, 0x00, 0x00, 0x00, // Avg current (1s period?), Bytes 22-25 - Communication fault seen with some values (>10A?) + 0x1D, 0x5A, 0x85, 0x43, // Current Voltage (float) Modbus register 216, Bytes 6-9 + 0x00, 0x00, 0x8D, 0x43, // Max Voltage (2 byte float), Bytes 12-13 + 0x00, 0x00, 0xAC, 0x41, // BAttery Temperature (2 byte float) Modbus register 214, Bytes 16-17 + 0x00, 0x00, 0x00, + 0x00, // Peak Current (1s period?), Bytes 18-21 - Communication fault seen with some values (>10A?) + 0x00, 0x00, 0x00, + 0x00, // Avg current (1s period?), Bytes 22-25 - Communication fault seen with some values (>10A?) 0x00, 0x00, 0x48, 0x42, // Max discharge current (2 byte float), Bit 26-29, // Sunspec: ADisChaMax 0x00, 0x00, 0xC8, 0x41, // Battery gross capacity, Ah (2 byte float) , Bytes 30-33, Modbus 512 @@ -64,14 +64,14 @@ uint8_t CyclicData[64] = { 0xA4, 0x70, 0x55, 0x40, // MaxCellVolt (float), Bit 46-49 0x7D, 0x3F, 0x55, 0x40, // MinCellVolt (float), Bit 50-53 - 0xFE, 0x04, // Cycle count, - 0x00, // Byte 56 - 0x00, // When SOC=100 Byte57=0x40, at startup 0x03 (about 7 times), otherwise 0x02 - 0x64, // SOC , Bit 58 - 0x00, // Unknown, - 0x00, // Unknown, - 0x00, // Unknown, - 0x00, // CRC (inverted sum of bytes 1-62 + 0xC0), Bit 62 + 0xFE, 0x04, // Cycle count, + 0x00, // Byte 56 + 0x00, // When SOC=100 Byte57=0x40, at startup 0x03 (about 7 times), otherwise 0x02 + 0x64, // SOC , Bit 58 + 0x00, // Unknown, + 0x00, // Unknown, + 0x00, // Unknown, + 0x00, // CRC (inverted sum of bytes 1-62 + 0xC0), Bit 62 0x00}; // FE 04 01 40 xx 01 01 02 yy (fully charged) @@ -84,7 +84,7 @@ uint8_t frame3[9] = { 0x00 //endbyte }; -uint8_t frame4[8] = {0x07, 0xE3, 0xFF, 0x02, 0xFF, 0x29, 0xF4, 0x00}; +uint8_t frame4[8] = {0x07, 0xE3, 0xFF, 0x02, 0xFF, 0x29, 0xF4, 0x00}; uint8_t frameB1[10] = {0x07, 0x63, 0xFF, 0x02, 0xFF, 0x29, 0x5E, 0x02, 0x16, 0x00}; uint8_t RS485_RXFRAME[10]; @@ -115,25 +115,25 @@ void send_kostal(byte* arr, int alen) { Serial2.write(arr, alen); } -void scramble_null_bytes(byte *lfc, int len) { +void scramble_null_bytes(byte* lfc, int len) { int last_null_byte = 0; for (int i = 0; i < len; i++) { if (lfc[i] == '\0') { - lfc[last_null_byte] = (byte) (i - last_null_byte); + lfc[last_null_byte] = (byte)(i - last_null_byte); last_null_byte = i; } } } -byte calculate_kostal_crc(byte *lfc, int len) { +byte calculate_kostal_crc(byte* lfc, int len) { unsigned int sum = 0; if (lfc[0] != 0) { - printf("WARNING: first byte should be 0, but is 0x%02x\n", lfc[0]); + printf("WARNING: first byte should be 0, but is 0x%02x\n", lfc[0]); } for (int i = 1; i < len; i++) { - sum += lfc[i]; + sum += lfc[i]; } - return (byte) (-sum & 0xff); + return (byte)(-sum & 0xff); } byte calculate_frame1_crc(byte* lfc, int lastbyte) { @@ -158,39 +158,14 @@ bool check_kostal_frame_crc() { void update_RS485_registers_inverter() { - if (datalayer.battery.status.voltage_dV > 10) { // Only update value when we have voltage available to avoid div0 - charge_current_dA = - ((datalayer.battery.status.max_charge_power_W * 10) / - datalayer.battery.status.voltage_dV); //Charge power in W , max volt in V+1decimal (P=UI, solve for I) - //The above calculation results in (30 000*10)/3700=81A - charge_current_dA = (charge_current_dA * 10); //Value needs a decimal before getting sent to inverter (81.0A) - - discharge_current_dA = - ((datalayer.battery.status.max_discharge_power_W * 10) / - datalayer.battery.status.voltage_dV); //Charge power in W , max volt in V+1decimal (P=UI, solve for I) - //The above calculation results in (30 000*10)/3700=81A - discharge_current_dA = (discharge_current_dA * 10); //Value needs a decimal before getting sent to inverter (81.0A) - } - - if (charge_current_dA > datalayer.battery.info.max_charge_amp_dA) { - charge_current_dA = - datalayer.battery.info - .max_charge_amp_dA; //Cap the value to the max allowed Amp. Some inverters cannot handle large values. - } - - if (discharge_current_dA > datalayer.battery.info.max_discharge_amp_dA) { - discharge_current_dA = - datalayer.battery.info - .max_discharge_amp_dA; //Cap the value to the max allowed Amp. Some inverters cannot handle large values. - } - average_temperature_dC = ((datalayer.battery.status.temperature_max_dC + datalayer.battery.status.temperature_min_dC) / 2); if (datalayer.battery.status.temperature_min_dC < 0) { average_temperature_dC = 0; } - if (datalayer.system.status.battery_allows_contactor_closing & datalayer.system.status.inverter_allows_contactor_closing ) { + if (datalayer.system.status.battery_allows_contactor_closing & + datalayer.system.status.inverter_allows_contactor_closing) { float2frame(CyclicData, (float)datalayer.battery.status.voltage_dV / 10, 6); // Confirmed OK mapping } else { float2frame(CyclicData, 0.0, 6); @@ -206,17 +181,18 @@ void update_RS485_registers_inverter() { float2frame(CyclicData, (float)average_temperature_dC / 10, 14); // Some current values causes communication error, must be resolved, why. - float2frame(CyclicData, (float)datalayer.battery.status.current_dA / 10, 18); // Peak discharge? current (2 byte float) + float2frame(CyclicData, (float)datalayer.battery.status.current_dA / 10, + 18); // Peak discharge? current (2 byte float) float2frame(CyclicData, (float)datalayer.battery.status.current_dA / 10, 22); - float2frame(CyclicData, (float)discharge_current_dA / 10, 26); // BAttery capacity Ah + float2frame(CyclicData, (float)datalayer.battery.status.max_discharge_current_dA / 10, 26); // BAttery capacity Ah - float2frame(CyclicData, (float)discharge_current_dA / 10, 30); + float2frame(CyclicData, (float)datalayer.battery.status.max_discharge_current_dA / 10, 30); // When SOC = 100%, drop down allowed charge current down. if ((datalayer.battery.status.reported_soc / 100) < 100) { - float2frame(CyclicData, (float)charge_current_dA / 10, 34); + float2frame(CyclicData, (float)datalayer.battery.status.max_charge_current_dA / 10, 34); } else { float2frame(CyclicData, 0.0, 34); } @@ -231,7 +207,6 @@ void update_RS485_registers_inverter() { register_content_ok = true; - if (incoming_message_counter > 0) { incoming_message_counter--; } @@ -258,7 +233,7 @@ void receive_RS485() // Runs as fast as possible to handle the serial stream if (startupMillis) { if (((currentMillis - startupMillis) >= INTERVAL_2_S & currentMillis - startupMillis <= 7000) & - datalayer.system.status.inverter_allows_contactor_closing) { + datalayer.system.status.inverter_allows_contactor_closing) { // Disconnect allowed only, when curren zero if (datalayer.battery.status.current_dA == 0) { datalayer.system.status.inverter_allows_contactor_closing = false; @@ -294,29 +269,25 @@ void receive_RS485() // Runs as fast as possible to handle the serial stream if (RS485_RXFRAME[1] == 'c') { if (RS485_RXFRAME[6] == 0x47) { - // Set time function - Do nothing. - send_kostal(frame4, 8); // ACK + // Set time function - Do nothing. + send_kostal(frame4, 8); // ACK } if (RS485_RXFRAME[6] == 0x5E) { // Set State function if (RS485_RXFRAME[7] == 0x01) { // State X - } - else if (RS485_RXFRAME[7] == 0x04) { + } else if (RS485_RXFRAME[7] == 0x04) { // INVALID - } - else { + } else { // State Y } - send_kostal(frame4, 8); // ACK + send_kostal(frame4, 8); // ACK } - } - else if (RS485_RXFRAME[1] == 'b') { + } else if (RS485_RXFRAME[1] == 'b') { if (RS485_RXFRAME[6] == 0x50) { //Reverse polarity, do nothing - } - else { - int code=RS485_RXFRAME[6] + RS485_RXFRAME[7]*0x100; + } else { + int code = RS485_RXFRAME[6] + RS485_RXFRAME[7] * 0x100; if (code == 0x44a) { //Send cyclic data update_values_battery(); @@ -327,7 +298,7 @@ void receive_RS485() // Runs as fast as possible to handle the serial stream byte tmpframe[64]; //copy values to prevent data manipulation during rewrite/crc calculation memcpy(tmpframe, CyclicData, 64); tmpframe[62] = calculate_kostal_crc(tmpframe, 62); - scramble_null_bytes(tmpframe,64); + scramble_null_bytes(tmpframe, 64); send_kostal(tmpframe, 64); } if (code == 0x84a) { @@ -358,4 +329,9 @@ void receive_RS485() // Runs as fast as possible to handle the serial stream } } +void setup_inverter(void) { // Performs one time setup at startup + strncpy(datalayer.system.info.inverter_protocol, "BYD battery via Kostal RS485", 63); + datalayer.system.info.inverter_protocol[63] = '\0'; +} + #endif diff --git a/Software/src/inverter/KOSTAL-RS485.h b/Software/src/inverter/KOSTAL-RS485.h index 22598b29..3de0697d 100644 --- a/Software/src/inverter/KOSTAL-RS485.h +++ b/Software/src/inverter/KOSTAL-RS485.h @@ -4,6 +4,6 @@ #include "../include.h" #define RS485_INVERTER_SELECTED -#define DEBUG_KOSTAL_RS485_DATA // Enable this line to get TX / RX printed out via serial +//#define DEBUG_KOSTAL_RS485_DATA // Enable this line to get TX / RX printed out via serial #endif