kostal inverter crc and byte scrambling rewritten

This commit is contained in:
rha 2025-02-05 16:29:51 +02:00
parent dfbaffb000
commit bc5823865e

View file

@ -27,59 +27,44 @@ union f32b {
byte b[4]; byte b[4];
}; };
uint8_t frame1[40] = {0x06, 0xE2, 0xFF, 0x02, 0xFF, 0x29, // Frame header uint8_t BATTERY_INFO[40] = {
0x01, 0x08, 0x80, 0x43, // 256.063 Nominal voltage / 5*51.2=256 first byte 0x01 or 0x04 0x06, 0xE2, 0xFF, 0x02, 0xFF, 0x29, // Frame header
0xE4, 0x70, 0x8A, 0x5C, // These might be Umin & Unax, Uint16 0x01, 0x08, 0x80, 0x43, // 256.063 Nominal voltage / 5*51.2=256 first byte 0x01 or 0x04
0xB5, 0x02, 0xD3, 0x01, // Battery Serial number? Modbus register 527 0xE4, 0x70, 0x8A, 0x5C, // These might be Umin & Unax, Uint16
0x01, 0x05, 0xC8, 0x41, // 25.0024 ? 0xB5, 0x02, 0xD3, 0x01, // Battery Serial number? Modbus register 527
0xC2, 0x18, // Battery Firmware, modbus register 586 0x01, 0x05, 0xC8, 0x41, // 25.0024 ?
0x01, 0x03, 0x59, 0x42, // 0x00005942 = 54.25 ?? 0xC2, 0x18, // Battery Firmware, modbus register 586
0x01, 0x01, 0x01, 0x02, 0x05, 0x02, 0xA0, 0x01, 0x01, 0x02, 0x01, 0x03, 0x59, 0x42, // 0x00005942 = 54.25 ??
0x4D, // CRC 0x01, 0x01, 0x01, 0x02, 0x05, 0x02, 0xA0, 0x01, 0x01, 0x02,
0x00}; // 0x4D, // CRC
0x00}; //
// values in frame2 will be overwritten at update_modbus_registers_inverter() // values in CyclicData will be overwritten at update_modbus_registers_inverter()
uint8_t frame2[64] = { uint8_t CyclicData[64] = {
0x0A, // This may also been 0x06, seen at startup when live values not valid, but also occasionally single frames. 0x00, // First zero byte pointer
0xE2, 0xFF, 0x02, 0xFF, 0x29, // frame Header 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
0x00, 0x00, 0x00, 0x00, // Avg current (1s period?), Bytes 22-25
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
0x00, 0x00, 0xA0, 0x41, // Max charge current (2 byte float) Bit 36-37, ZERO WHEN SOC=100 // Sunspec: AChaMax
0xCD, 0xCC, 0xB4, 0x41, // MaxCellTemp (4 byte float) Bit 38-41
0x00, 0x00, 0xA4, 0x41, // MinCellTemp (4 byte float) Bit 42-45
0xA4, 0x70, 0x55, 0x40, // MaxCellVolt (float), Bit 46-49
0x7D, 0x3F, 0x55, 0x40, // MinCellVolt (float), Bit 50-53
0x1D, 0x5A, 0x85, 0x43, // Current Voltage (float) Modbus register 216, Bytes 6-9 0xFE, 0x04, // Cycle count,
0x01, 0x03, // Unknown, 0x03 seen also 0x0F, 0x07, might hava something to do with current 0x00, // Byte 56
0x8D, 0x43, // Max Voltage (2 byte float), Bytes 12-13 0x40, // When SOC=100 Byte57=0x40, at startup 0x03 (about 7 times), otherwise 0x02
0x01, 0x03, 0xAC, 0x41, // BAttery Temperature (2 byte float) Modbus register 214, Bytes 16-17 0x64, // SOC , Bit 58
0x01, 0x01, 0x01, 0x00, // Unknown,
0x01, // Peak Current (1s period?), Bytes 18-21 - Communication fault seen with some values (>10A?) 0x00, // Unknown,
0x01, 0x01, 0x01, 0x02, // Unknown, Mostly 0x02. seen also 0x01
0x01, // Avg current (1s period?), Bytes 22-25 - Communication fault seen with some values (>10A?) 0x00, // CRC (inverted sum of bytes 1-62 + 0xC0), Bit 62
0x01, 0x03, 0x48, 0x42, // Max discharge current (2 byte float), Bit 26-29,
// Sunspec: ADisChaMax
0x01, 0x03, // Unknown
0xC8, 0x41, // Battery gross capacity, Ah (2 byte float) , Bytes 30-33, Modbus 512
0x01, // Unknown
0x16, // This seems to have something to do with cell temperatures
0xA0, 0x41, // Max charge current (2 byte float) Bit 36-37, ZERO WHEN SOC=100
// Sunspec: AChaMax
0xCD, 0xCC, 0xB4, 0x41, // MaxCellTemp (4 byte float) Bit 38-41
0x01, 0x0C, 0xA4, 0x41, // MinCellTemp (4 byte float) Bit 42-45
0xA4, 0x70, 0x55, 0x40, // MaxCellVolt (float), Bit 46-49
0x7D, 0x3F, 0x55, 0x40, // MinCellVolt (float), Bit 50-53
0xFE, // Cylce count , Bit 54
0x04, // Cycle count? , Bit 55
0x01, // Byte 56
0x40, // When SOC=100 Byte57=0x40, at startup 0x03 (about 7 times), otherwise 0x02
0x64, // SOC , Bit 58
0x01, // Unknown, when byte 57 = 0x03, this 0x02, otherwise 0x01
0x01, // Unknown, Seen only 0x01
0x02, // Unknown, Mostly 0x02. seen also 0x01
0x00, // CRC (inverted sum of bytes 1-62 + 0xC0), Bit 62
0x00}; 0x00};
// FE 04 01 40 xx 01 01 02 yy (fully charged) // FE 04 01 40 xx 01 01 02 yy (fully charged)
@ -97,7 +82,7 @@ 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 frameB1[10] = {0x07, 0x63, 0xFF, 0x02, 0xFF, 0x29, 0x5E, 0x02, 0x16, 0x00};
uint8_t frameB1b[8] = {0x07, 0xE3, 0xFF, 0x02, 0xFF, 0x29, 0xF4, 0x00}; uint8_t frameB1b[8] = {0x07, 0xE3, 0xFF, 0x02, 0xFF, 0x29, 0xF4, 0x00};
uint8_t RS485_RXFRAME[10]; uint8_t RS485_RXFRAME[300];
bool register_content_ok = false; bool register_content_ok = false;
@ -110,13 +95,6 @@ void float2frame(byte* arr, float value, byte framepointer) {
arr[framepointer + 3] = g.b[3]; arr[framepointer + 3] = g.b[3];
} }
void float2frameMSB(byte* arr, float value, byte framepointer) {
f32b g;
g.f = value;
arr[framepointer + 0] = g.b[2];
arr[framepointer + 1] = g.b[3];
}
static void dbg_timestamp(void) { static void dbg_timestamp(void) {
#ifdef DEBUG_KOSTAL_RS485_DATA #ifdef DEBUG_KOSTAL_RS485_DATA
logging.print("["); logging.print("[");
@ -148,17 +126,30 @@ static void dbg_message(const char* msg) {
#endif #endif
} }
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);
last_null_byte = i;
}
}
}
static void send_kostal(byte* frame, int len) { static void send_kostal(byte* frame, int len) {
dbg_frame(frame, len, "TX"); dbg_frame(frame, len, "TX");
Serial2.write(frame, len); Serial2.write(frame, len);
} }
byte calculate_longframe_crc(byte* lfc, int lastbyte) { byte calculate_kostal_crc(byte* lfc, int len) {
unsigned int sum = 0; unsigned int sum = 0;
for (int i = 0; i < lastbyte; ++i) { if (lfc[0] != 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 + 0xc0) & 0xff); return (byte)(-sum & 0xff);
} }
byte calculate_frame1_crc(byte* lfc, int lastbyte) { byte calculate_frame1_crc(byte* lfc, int lastbyte) {
@ -191,60 +182,63 @@ void update_RS485_registers_inverter() {
if (datalayer.system.status.battery_allows_contactor_closing & if (datalayer.system.status.battery_allows_contactor_closing &
datalayer.system.status.inverter_allows_contactor_closing) { datalayer.system.status.inverter_allows_contactor_closing) {
float2frame(frame2, (float)datalayer.battery.status.voltage_dV / 10, 6); // Confirmed OK mapping float2frame(CyclicData, (float)datalayer.battery.status.voltage_dV / 10, 6); // Confirmed OK mapping
frame2[0] = 0x0A; CyclicData[0] = 0x0A;
} else { } else {
frame2[0] = 0x06; CyclicData[0] = 0x06;
float2frame(frame2, 0.0, 6); float2frame(CyclicData, 0.0, 6);
} }
// Set nominal voltage to value between min and max voltage set by battery (Example 400 and 300 results in 350V) // Set nominal voltage to value between min and max voltage set by battery (Example 400 and 300 results in 350V)
nominal_voltage_dV = nominal_voltage_dV =
(((datalayer.battery.info.max_design_voltage_dV - datalayer.battery.info.min_design_voltage_dV) / 2) + (((datalayer.battery.info.max_design_voltage_dV - datalayer.battery.info.min_design_voltage_dV) / 2) +
datalayer.battery.info.min_design_voltage_dV); datalayer.battery.info.min_design_voltage_dV);
float2frameMSB(frame1, (float)nominal_voltage_dV / 10, 8); float2frame(BATTERY_INFO, (float)nominal_voltage_dV / 10, 6);
float2frameMSB(frame2, (float)datalayer.battery.info.max_design_voltage_dV / 10, 12); float2frame(CyclicData, (float)datalayer.battery.info.max_design_voltage_dV / 10, 10);
float2frameMSB(frame2, (float)average_temperature_dC / 10, 16); float2frame(CyclicData, (float)average_temperature_dC / 10, 14);
// Some current values causes communication error, must be resolved, why. // Some current values causes communication error, must be resolved, why.
// float2frameMSB(frame2, (float)datalayer.battery.status.current_dA / 10, 20); // Peak discharge? current (2 byte float) // float2frame(CyclicData, (float)datalayer.battery.status.current_dA / 10, 18); // Peak discharge? current (2 byte float)
// float2frameMSB(frame2, (float)datalayer.battery.status.current_dA / 10, 24); // float2frame(CyclicData, (float)datalayer.battery.status.current_dA / 10, 22);
float2frameMSB(frame2, (float)datalayer.battery.status.max_discharge_current_dA / 10, 28); // BAttery capacity Ah float2frame(CyclicData, (float)datalayer.battery.status.max_discharge_current_dA / 10, 26);
float2frameMSB(frame2, (float)datalayer.battery.status.max_discharge_current_dA / 10, 32);
// When SOC = 100%, drop down allowed charge current down. // When SOC = 100%, drop down allowed charge current down.
if ((datalayer.battery.status.reported_soc / 100) < 100) { if ((datalayer.battery.status.reported_soc / 100) < 100) {
float2frameMSB(frame2, (float)datalayer.battery.status.max_charge_current_dA / 10, 36); float2frame(CyclicData, (float)datalayer.battery.status.max_charge_current_dA / 10, 34);
frame2[57] = 0x02;
frame2[59] = 0x01;
} else { } else {
float2frameMSB(frame2, 0.0, 36); float2frame(CyclicData, 0.0, 34);
//frame2[57]=0x40;
frame2[57] = 0x02;
frame2[59] = 0x01;
} }
// On startup, byte 57 seems to be always 0x03 couple of frames,. // On startup, byte 56 seems to be always 0x00 couple of frames,.
if (f2_startup_count < 9) {
CyclicData[56] = 0x00;
} else {
CyclicData[56] = 0x01;
}
// On startup, byte 59 seems to be always 0x02 couple of frames,.
if (f2_startup_count < 14) { if (f2_startup_count < 14) {
frame2[57] = 0x03; CyclicData[59] = 0x02;
frame2[59] = 0x02; } else {
CyclicData[59] = 0x00;
} }
float2frame(frame2, (float)datalayer.battery.status.temperature_max_dC / 10, 38); float2frame(CyclicData, (float)(datalayer.battery.info.total_capacity_Wh / nominal_voltage_dV * 10),
float2frame(frame2, (float)datalayer.battery.status.temperature_min_dC / 10, 42); 30); // BAttery capacity Ah
float2frame(CyclicData, (float)datalayer.battery.status.temperature_max_dC / 10, 38);
float2frame(CyclicData, (float)datalayer.battery.status.temperature_min_dC / 10, 42);
float2frame(frame2, (float)datalayer.battery.status.cell_max_voltage_mV / 1000, 46); float2frame(CyclicData, (float)datalayer.battery.status.cell_max_voltage_mV / 1000, 46);
float2frame(frame2, (float)datalayer.battery.status.cell_min_voltage_mV / 1000, 50); float2frame(CyclicData, (float)datalayer.battery.status.cell_min_voltage_mV / 1000, 50);
frame2[58] = (byte)(datalayer.battery.status.reported_soc / 100); // Confirmed OK mapping CyclicData[58] = (byte)(datalayer.battery.status.reported_soc / 100); // Confirmed OK mapping
register_content_ok = true; register_content_ok = true;
frame1[38] = calculate_frame1_crc(frame1, 38); BATTERY_INFO[38] = calculate_frame1_crc(BATTERY_INFO, 38);
if (incoming_message_counter > 0) { if (incoming_message_counter > 0) {
incoming_message_counter--; incoming_message_counter--;
@ -328,7 +322,7 @@ void receive_RS485() // Runs as fast as possible to handle the serial stream
} }
if (headerA && (RS485_RXFRAME[6] == 0x4A) && (RS485_RXFRAME[7] == 0x08)) { // "frame 1" if (headerA && (RS485_RXFRAME[6] == 0x4A) && (RS485_RXFRAME[7] == 0x08)) { // "frame 1"
send_kostal(frame1, 40); send_kostal(BATTERY_INFO, 40);
if (!startupMillis) { if (!startupMillis) {
startupMillis = currentMillis; startupMillis = currentMillis;
} }
@ -340,13 +334,10 @@ void receive_RS485() // Runs as fast as possible to handle the serial stream
f2_startup_count++; f2_startup_count++;
} }
byte tmpframe[64]; //copy values to prevent data manipulation during rewrite/crc calculation byte tmpframe[64]; //copy values to prevent data manipulation during rewrite/crc calculation
memcpy(tmpframe, frame2, 64); memcpy(tmpframe, CyclicData, 64);
for (int i = 1; i < 63; i++) {
if (tmpframe[i] == 0x00) { tmpframe[62] = calculate_kostal_crc(tmpframe, 62);
tmpframe[i] = 0x01; scramble_null_bytes(tmpframe, 64);
}
}
tmpframe[62] = calculate_longframe_crc(tmpframe, 62);
send_kostal(tmpframe, 64); send_kostal(tmpframe, 64);
} }
if (headerA && (RS485_RXFRAME[6] == 0x53) && (RS485_RXFRAME[7] == 0x03)) { // "frame 3" if (headerA && (RS485_RXFRAME[6] == 0x53) && (RS485_RXFRAME[7] == 0x03)) { // "frame 3"