Communication between BYD Battery and Kostal Plenticore Inverter

Sometimes (every few hours) I get a response starting with 0x06 from the battery. The rest of the content is the same as the 0x0A messages.
And the query from the inverter was the same as well. Somebody a glue on that?
Example from this morning:
Code:
06 E2 FF 02 FF 29 04 40 87 43 01 0F 8D 43 66 66 AA 41 33 33 93 40 7B 14 96 40 01 03 48 42 01 03 C8 41 01 14 A0 41 33 33 B7 41 66 66 A6 41 75 93 58 40 27 31 58 40 FE 02 01 02 4D 01 01 02 65

The checksum for the short messages
Code:
09 62 FF 02 FF 29 53 03 1F
08 E2 FF 02 FF 29 06 EF
09 62 FF 02 FF 29 4A 04 27
is easy, this is just the sum of all bytes inverted, but starting on the 2nd byte. So the first byte is not secured with the checksum.

more complicated is it with the long 0x0A (and 0x06) message. If I do the same calculation, I do not get the correct answer.
I used this one https://www.scadacore.com/tools/programming-calculators/online-checksum-calculator/ and took the CheckSum8 2s Complement result.
Maybe the checksum is calculated before the "defusing" of the 0x00 bytes. I changed all the 0x01 to 0x00 but didn't get the correct answer.

I guess "00" is a special symbol, so the number 0x438D000 is encoded as 0x438D, and the zeroes are omitted.
Also, I think "01" marks the start of a segment, followed by its length. The length byte is included in the total length.
But the length of the messages is not changing over time. While the numbers of 0x01 is changing.
Example message when the battery is fully charged -> allowed charging current is nearly 0 A.
Code:
    0A    E2    FF    02    FF    29    1D    5A    85    43    01    03    8D    43    01    03    AC    41    01    01    01    01    01    01    01    01    01    03    48    42    01    03    C8    41    01    01    01    05    CD    CC    B4    41    01    0C    A4    41    A4    70    55    40    7D    3F    55    40    FE    04    01    40    64    01    01    02    56
The Battery current is 01 01 01 01 (two times) and the maximum charge current is 01 01 01 05
Any Idea for this?

Cycle current is 0xFE by the way. :)
 

Attachments

  • FullyCharged.png
    FullyCharged.png
    14.4 KB · Views: 155
@huntworker
About your last message:
Code:
0A E2 FF 02 FF 29 10 D8 81 43 01 07 8D 43 33 33 9F 41 01 01 01 01 01 01 01 01 01 03 48 42 01 03 C8 41 01 03 A0 41 01 07 90 41 CD CC 80 41 01 08 50 40 64 3B 4F 40 FA 02 01 02 14 01 01 02 C7
I think that correct splitting is:
Code:
0A E2 FF 02 FF 29 1D 5A 85 43
01 03 8D 43
01 03 AC 41
01 01 01 01 01 01 01 01
01 03 48 42
01 03 C8 41
01 01
01 05 CD CC B4 41
01 0C A4 41 A4 70 55 40 7D 3F 55 40 FE
04 01 40 64 01 01 02 56
So, the max charge current is encoded as 01 01, which should be equal to zero.
But the length of the messages is not changing over time. While the numbers of 0x01 is changing.
I agree that the long message is always 64 bytes. We can compensate for the length change by fragmenting the data. Each time we split it, there are additional two bytes: 01 XX (XX is the length of segment).
I was thinking about the long message checksum, and maybe we should exclude from it all "special" symbols (with value < 0x20).
 
Okay, I think I got it (basically)
example:
Code:
0A    E2    FF    02    FF    29    0C    92    85    43                           
01    0F    8D    43    9A    99    99    41    33    33    73    40    32    33    73    40   
01    03    48    42                                                   
01    03    C8    41                                                   
01    03    A0    41                                                   
01    10    8C    41    9A    99    81    41    54    E3    55    40    68    91    55    40    FB
02                                                               
01    02    28                                                       
01    01                                                           
02    CD
every "01 xx" will be replaced by "00 00". Then I get correct data until the "FB" Byte. Because after that I would expect "01" again, because the block is only "10" long. But there is a 02 coming.

Then I tried another block
Code:
0A    E2    FF    02    FF    29    1D    5A    85    43           
01    03    8D    43                                   
01    03    AC    41                                   
01    01                                           
01    01                                           
01    01                                           
01    01                                           
01    03    48    42                                   
01    03    C8    41                                   
01    01                                           
01    05    CD    CC    B4    41                           
01    0C    A4    41    A4    70    55    40    7D    3F    55    40    FE
04                                               
01    40    64                                       
01    01                                           
02    56
and was wondering about the four last lines. I expected them to start with 01, but it is starting with 04. Do you have a glue why?

And, if every 01 xx byte is replaced by 00 00, how is it possible to only add one 00 byte?

To the checksum:
If I replace all the 01 xx bytes by 00 00 and ignore the last 04, 01, 01, 01 and 02, i get the correct checksum with an offset of 5.
So I am still curious about the last bytes. Maybe it will show up when the cycle count gets over 255.
Current cycle count is 254.
 
OK, so we have that long message, and it's divided into the blocks of data. Each block has a header in the format 01 XX, where XX is block length excluding starting byte 01. When some parameter is zero, it's replaced by 01 01 (or 01 01 01 01), and I think that the order of battery parameters is fixed within the message.
Now remains the last block of 8 bytes, and we know for sure there is a SOC byte and checksum byte. But other bytes are still a mystery. There should be some status byte.

One other thing, which is not directly related to the current topic - in the message below, we have 100%, and voltage is 266.76V. That is a somehow low voltage for 100% SOC. I'm expecting a voltage closer to 280V.
Code:
0A E2 FF 02 FF 29 C5 60 85 43 01 07 8D 43 9A 99 A5 41 01 01 01 01 01 01 01 01 01 03 48 42 01 03 C8 41 01 01 01 12 CD CC B0 41 66 66 9E 41 06 81 55 40 DF 4F 55 40 FD 04 01 40 64 01 01 02 D6
Now let's see here:
Code:
06 E2 FF 02 FF 29 04 40 87 43 01 0F 8D 43 66 66 AA 41 33 33 93 40 7B 14 96 40 01 03 48 42 01 03 C8 41 01 14 A0 41 33 33 B7 41 66 66 A6 41 75 93 58 40 27 31 58 40 FE 02 01 02 4D 01 01 02 65
The voltage is 270.5V, and the SOC is 77%. These values are more relevant.
 
One other thing, which is not directly related to the current topic - in the message below, we have 100%, and voltage is 266.76V. That is a somehow low voltage for 100% SOC. I'm expecting a voltage closer to 280V.
Thats quite normal behavior of the batteries. If the battery is charged nearly 100% the voltage is around 276 V. If it is then fully charged and the allowed current drops to 0 A, the voltage drops down as well. That is okay that the voltage is not at maximum the whole time. Thats the reason why you need to do coulomb counting instead of voltage measurement for the soc.
The voltage is 270.5V, and the SOC is 77%. These values are more relevant.
Didn't have a closer look for the current, but if there is a higher charge current, the voltage will be higher.


06 E2 FF 02 FF 29 04 40 87 43 01 0F 8D 43 66 66 AA 41 33 33 93 40 7B 14 96 40 01 03 48 42 01 03 C8 41 01 14 A0 41 33 33 B7 41 66 66 A6 41 75 93 58 40 27 31 58 40 FE 02 01 02 4D 01 01 02 65
What I am more wondering about is this message beginning with 06. These are the messages I want to check today. The 0A messages show an end of a segment after 0x0A Bytes. But this one does not have a 01 at this position. I do not have an explanation why this message starts with 06 instead of 0A. Everything else is similar to the other messages. Is this somehow to insert just 00 instead of two bytes 00?

I will write a small program to calculate the float values if some more points (mostly the 06 frames) are cleared to log the currents, voltages and SOC. Hope to get things even more clear.
 
The cycle counter just passed 256:
Code:
0A E2 FF 02 FF 29 9E BF 83 43 01 0F 8D 43 33 33 A7 41 9A 99 D9 BF 9A 99 D9 BF 01 03 48 42 01 03 C8 41 01 03 A0 41 01 0F A0 41 CD CC 90 41 96 43 53 40 23 DB 51 40 03 01 01 02 50 01 01 02 12
but it is more unclear than before.

In the past the last bytes had been
Code:
FE 04 01 40 xx 01 01 02 yy (fully charged)
FE 02 01 02 xx 01 01 02 yy (charging or discharging)
where FE was the cycle count, "04 01 40"/"02 01 02" for fully charged/normal, xx for SOC and yy for the checksum.
But now it is
Code:
03 01 01 02 xx 01 01 02 yy
while the first bytes need to be somehow 00 01 for the cycle count of 256.

What is now the 03 saying to me? Still curious about this, waiting for the cycle count to be 257...
And if the second byte of the last block is used for the cycle count as well with the 04/02 block of the charge status, is it only the higher nibble?
 
Message packet starts with the FF 02 (address, message type)
Next FF is padding
Next 29 is the number of 2 byte integers / int16 / registers
Last 2 bytes are the modbus CRC
Encoding may be 32 bit numbers by taking a high and low 2 byte integer as per the voltage in an earlier post
Status may then be indicated as a bitmask in one of the 2 byte sections
 
Last edited:
FF Address
02 Message Type
FF Padding
29 Register count
10 Lowest 8 bits of battery voltage (as float) - register 1
D8 Next 8 bits of battery voltage(as float) - register 1
81 Next 8 bits of battery voltage(as float) - register 2
43 First bit is the sign, next 6 bits exponent, last bit top bit of battery voltage - register 2
next register values....
register 29
register 29
CRC
CRC
 
This should help you to find out the rest of the value meanings

1614214980891.png
 
Fine to have another one having to have a look for the data!

Last 2 bytes are the modbus CRC
There is only one byte for checksum. The second last byte is 02 in all long messages.

29 Register count
In the short messages there is as well 29 at this position, so I guess this is part of the address or similar.

10 Lowest 8 bits of battery voltage (as float) - register 1
D8 Next 8 bits of battery voltage(as float) - register 1
81 Next 8 bits of battery voltage(as float) - register 2
43 First bit is the sign, next 6 bits exponent, last bit top bit of battery voltage - register 2
Thats also what we found out.
But: next value is the maximum charging voltage which is 282,0 V (0x438D0700) but transmitted as 0x438D0701.
I guess the values (as well the telegram length, and for sure the checksum) needs to be calculated after the line coding with its replacement of 00's.
So in the first step we need to extract the frames which @yasko found, specially the last bytes. After that we can have a look for the header and trailer and checksum.

Some more frames (most of them already in earlier posts):
Code:
0A E2 FF 02 FF 29 1D 5A 85 43 01 03 8D 43 01 03 AC 41 01 01 01 01 01 01 01 01 01 03 48 42 01 03 C8 41 01 01 01 05 CD CC B4 41 01 0C A4 41 A4 70 55 40 7D 3F 55 40 FE 04 01 40 64 01 01 02 56
0A E2 FF 02 FF 29 96 63 85 43 01 03 8D 43 01 0B A8 41 33 33 73 40 32 33 73 40 01 03 48 42 01 03 C8 41 01 03 A0 41 01 10 B0 41 66 66 9E 41 2D B2 55 40 7D 3F 55 40 FE 02 01 02 38 01 01 02 3E
0A E2 FF 02 FF 29 0C 92 85 43 01 0F 8D 43 9A 99 99 41 33 33 73 40 32 33 73 40 01 03 48 42 01 03 C8 41 01 03 A0 41 01 10 8C 41 9A 99 81 41 54 E3 55 40 68 91 55 40 FB 02 01 02 28 01 01 02 CD
0A E2 FF 02 FF 29 7D DF 82 43 01 0F 8D 43 66 66 A6 41 66 66 26 C0 D7 A3 20 C0 01 03 48 42 01 03 C8 41 01 14 A0 41 9A 99 A1 41 66 66 92 41 71 3D 52 40 60 E5 50 40 FE 02 01 02 4A 01 01 02 BF
06 E2 FF 02 FF 29 04 40 87 43 01 0F 8D 43 66 66 AA 41 33 33 93 40 7B 14 96 40 01 03 48 42 01 03 C8 41 01 14 A0 41 33 33 B7 41 66 66 A6 41 75 93 58 40 27 31 58 40 FE 02 01 02 4D 01 01 02 65
0A E2 FF 02 FF 29 7D DF 82 43 01 0F 8D 43 66 66 A6 41 66 66 26 C0 D7 A3 20 C0 01 03 48 42 01 03 C8 41 01 14 A0 41 9A 99 A1 41 66 66 92 41 71 3D 52 40 60 E5 50 40 FE 02 01 02 4A 01 01 02 BF

09 62 FF 02 FF 29 53 03 1F
08 E2 FF 02 FF 29 06 EF
09 62 FF 02 FF 29 4A 04 27

09 62 FF 02 FF 29 53 03 1F
08 E2 FF 02 FF 29 06 EF
09 62 FF 02 FF 29 4A 04 27

09 62 FF 02 FF 29 53 03 1F
08 E2 FF 02 FF 29 06 EF
09 62 FF 02 FF 29 4A 04 27
 
Are you sure about the messages because the majority of them are float and these are the values from one packet, with my guesses as to the intended values.....

SLSupload.png

The byte values can't alwats be taken as litteral because of the way in which a float value is actually stored in binary :

SLSupload.png
 
Some of the other values will be status flags and maybe float / bulk / balance charge timings
 
The byte values can't alwats be taken as litteral because of the way in which a float value is actually stored in binary
Totally agree with you, but in this case, please try to exchange the 01 bytes by 00 bytes and have again a look for the values.
The 50 Amps, 25 Amps and 20 amps are pretty accurate then.

The float values are
Code:
BattVolt MaxVolt SysTemp BatteryCurrent BatteryCurrent MaxDiscCurr NomDiscCurr MaxChgCurr maxCellTemp minCellTemp maxCellVol minCellVol
 
You can't just randomly change / ignore bits, unless I'm missing something else as to what the 01 (or last bit of the byte) represents and they have implemented some twilight zone 3 byte floats ?

The firmware looks to have been written with 32bit floats and they are just memory copying them out, without trying to minimise the data packet size, which results in the message size being more than double what is required and by using floats they make the storage of some expected exact figures look problematic. An unsigned int can store 65536, even signed to two decimal places the system / array voltage can be represented as divide by 10. 2 bytes instead of 4, no rounding and a lot more easy to use without bit packing libraries (bitconverter in .net for example).

The voltage for example may only have an accuracy of 2 decimal places but the float representation gives way more digits that are just noise. Round it to the decimal places the value is supposed to represent and all is well.

They seem (guessing here) to have used a full byte to represent true / false settings (instead of a single byte and bit packing) I don't know the system so have no idea as to what these may represent.

The last two floats would appear to be the charge and discharge energy divided by 10 for some reason (in kWh as final figure).
 
Cycle count is not a value within the data packet because it is calculated from the discharge energy divided by 6.4. You are looking for a value in the data that does not exist.
 
Totally Agree with you, most of the data are send as 4 byte float. But I am pretty sure that the 50, 25, 20 and 282 are send with 00 as low-byte. It doesn't make any sense that there are 01 written. I know that the float representation is not accurate but it doesn't make any sense to have the 01.

If the cycle count is not included into the data, it would be pure coincidence that it matched for the last values.
The last values are
0x02010103 = 33620227
0x0201015A = 33620314
That doesn't make any sense, total charge energy is 1931.985 kWh, total discharge energy 1641.340 kWh, cycle count is 256.

But: the 5th last Byte exactly matches the state of charge, this cannot be coincidence.

For the checksum, only one byte is used (the few bytes before the last are constant), so the last bytes are not grouped in 4 byte values, but only in 2 byte groups or even single bytes.
 
@completelycharged, thanks for your comments. You are building the theory that communication is MODBUS-based. But there are too many facts, which doesn't fit:
  • Number of parameters (29) indeed that is 0x29 -> 41 decimal. There are no so many parameters in that message.
  • CRC checksum, there is only one byte for checksum, and it's not a CRC
  • MODBUS headers, current headers don't match to MODBUS specification. There is a useful reference here.
  • BUS speed (57600pbs). MODBUS usually operates at 9600 or 19200 bps.
I described all my findings in the posts above and believe that is not MODBUS RTU protocol. I hope to have some free time during the weekend and summarize all facts about that protocol.
@huntworker, what is the final goal of this project? You want to build some monitoring system or connect some other battery to your inverter using this protocol.
 
Just noticed I completely messed up with the 29 as being in hex. I saw the message length in the first log, roughly 60-70 bytes so looked for 25-30 value to reflect the packet length, then fit the outline of the messages with the log data. I did start with the decimal, converted to hex and then blanked it.

Do all of the messages originate from the same device (polling from one and a return from another) ?

Is there a delay / gap in the data stream between messages (which actually shows they are separate messages) ? The first log was just a hex dump with no timing.

Charge / Discharge difference would be efficiency losses and for the unit it looks like about 85% efficient with the figures quoted above.
 
Modbus is just a protocol and does not specify a baud rate.
Quite a lot of equipment / firmware that supports the modbus protocol allows the baud rate to be changed within a protocol message.
 
But: the 5th last Byte exactly matches the state of charge, this cannot be coincidence.
I went off on a tanget with the coincidence that hex 29 matched a decimal number of hypothetical registers...

They seem to copy out float values as 4 bytes (wasting bandwidth) and then for a state of charge which would be a fractional calculation they then mess about to save space and use 1 byte ? If it's the case then a strange approach.

The SOC can potentially be worked out with the values I have seen in the packets in two ways, to instigate thought, flames and bewliderment..

1. The last two floats I had (have not checked other log messages) could be last SOC charge vaue of dischrage energy with the pack at top charge, with another live discharge energy value to work out the difference. This would be even more twilight zone. In such twilight zone the small difference i saw in the valued could have been with the pack at 100% charge state.

2. The two voltage levels (259.67V and 282.06V in the values I have) could be used with a formula to work out the battery SOC without having to resort to coulomb counting on partial charge states - I use this method and it avoids having to reset any coulomb counter to prevent SOC drifting.

Very curious set of messages and on that note, out of fear of going even further and more of a tangent I'l leave it to you guys.
 
Back
Top