Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add battery_management_system_protocol #410

Open
wants to merge 15 commits into
base: master
Choose a base branch
from
246 changes: 246 additions & 0 deletions hardware/battery/battery_management_system_protocol.ksy
Original file line number Diff line number Diff line change
@@ -0,0 +1,246 @@
meta:
id: battery_management_system_protocol
title: Communication protocol of smart battery management systems from LLT power
license: CC0-1.0
ks-version: 0.9
endian: be
Jakeler marked this conversation as resolved.
Show resolved Hide resolved
bit-endian: be

doc: |
Many modern general purpose BMS include a UART/Bluetooth based communication interface.
After sending read requests they respond with various information's about the battery state in
a custom binary format.

doc-ref: https://www.lithiumbatterypcb.com/Protocol%20English%20Version.rar

seq:
- id: magic_start
contents: [0xdd]
- id: cmd
type: u1
Jakeler marked this conversation as resolved.
Show resolved Hide resolved
-affected-by: 778

- size: 0
if: ofs_body_start < 0 # storing current position
- id: body
type:
switch-on: cmd
cases:
commands::read.to_i: read_req
commands::write.to_i: write_req
_: response(cmd)
- size: 0
if: ofs_body_end < 0 # storing current position

- id: checksum
type: u2
doc: |
Should be equal to the result from: 0x10000 - sum(body)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was wondering what happens if sum(body) > 0x10000 and I was surprised that the format authors would not think about it, but I believe that you did not quite follow the specification (page 2):

Red is the checked byte, the sum of all bytes; the latter two are the checked result, which is the sum of all the previous checks and reverses + 1.

It is a little bit weird English, but it makes sense to me and it also matches your interpretation (but solves the problem in case sum(body) > 0x10000).

For this value of checksum_input (actually taken from https://github.com/Jakeler/bms-parser/blob/dfb9e05e3f4ee0e70cd71ab2114bff1c66661899/py/test.py#L10):

checksum_input_str = '00 1b 11 38 00 62 00 a4 04 b0 00 00 27 6e 02 82 00 00 00 00 21 0e 03 0b 02 0b 22 0b 10'
checksum_input = bytes.fromhex(checksum_input_str)

the sum is

print(sum(checksum_input)) # => 958

The expected_checksum is then

print(0x10000 - 958) # 64578

according to you, and

print((~958 + 1) & 0xffff) # 64578
# which can be using the relationship -a = ~a + 1
# (from definition of the [two's complement representation]
# (https://en.wikipedia.org/wiki/Two%27s_complement)) equivalently rewritten as:
print(-958 & 0xffff) # 64578

according to the spec.

In fact, 0x10000 - sum(checksum_input) would work, but it would also have to be clamped to the lower 16 bits like (0x10000 - sum(checksum_input)) & 0xffff. However, if you only take the lower 16 bits from 0x10000 - a, it's the same as if you did 0x7af20000 - a, 0xff0000 - a or just 0x0000 - a, because all these values have the value of 0 as far as the 16 bits are concerned.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I admit that I have simplified the formula a bit, but this is really just a theoretical problem. Even if we are assuming the complete data consists of 0xff... you would need 256 bytes to reach the 16 bits max value. The data length field is just one byte, so the body can not be larger than 256 bytes.
Yes, there is also a status byte (which is 0x00 on every valid packet) and the length itself, but real data is never always 0xff of course, so this easily would work in practice even if the length is pushed to the limit.

In reality there are even earlier limits, for example the balancing flags are for max. 32 cells, that would produce only 64 + 2 = 66 bytes in the cell voltages response and the other structures are constant size.
But I can add a note to the doc...

Copy link
Member

@generalmimon generalmimon Mar 6, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I admit that I have simplified the formula a bit, but this is really just a theoretical problem.

Perhaps, but I don't perceive this as a solid reason to intentionally write a checksum formula there that does not work in general case, especially when the official one in the original specification does. That's not how reliable software is written. What is the point? To simplify the calculation? Is it worth to study the entire format and evaluate all its edge cases to make sure that in absolutely no case cannot the sum of the payload bytes exceed 65536, only to be able to "simplify" the expression from -sum(checksum_input) & 0xffff to 0x10000 - sum(checksum_input)? What if the format will be extended at some point in the future (some structure will be added) and the sum(checksum_input) will be able to go over that 65536?

Where sum() is calculated over the value of every byte individually.
Body includes everything besides magic start/end byte, cmd and checksum,
so excluding 2 bytes at the beginning and 3 bytes at the end.
- id: magic_end
contents: [0x77]

instances:
ofs_body_start:
value: _io.pos
ofs_body_end:
value: _io.pos
checksum_input:
-affected-by: 84
pos: ofs_body_start
size: ofs_body_end - ofs_body_start

enums:
commands:
0xa5: read
0x5a: write

types:
read_req:
Copy link
Contributor

@KOLANICH KOLANICH Mar 4, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess read_req is the same as write_req, just without a payload. I wonder if it can be validated.

seq:
- id: req_cmd
type: u1
Jakeler marked this conversation as resolved.
Show resolved Hide resolved
doc: Same value as cmd for response
- id: data_len
contents: [0x00]
write_req:
seq:
- id: req_cmd
type: u1
doc: Same value as cmd for response
- id: data_len
type: u1
Jakeler marked this conversation as resolved.
Show resolved Hide resolved
- id: write_data
size: data_len

basic_info:
seq:
- id: total
type: voltage
Jakeler marked this conversation as resolved.
Show resolved Hide resolved
- id: current
type: current
- id: remain_cap
type: capacity
- id: typ_cap
type: capacity
- id: cycles
type: u2
doc: Cycle times
- id: prod_date
type: u2
doc: Production date
Jakeler marked this conversation as resolved.
Show resolved Hide resolved
- id: balance_status
type: balance_list
doc: List of balance bits
- id: prot_status
type: prot_list
doc: List of protection bits
- id: software_version
type: u1
- id: remain_cap_percent
type: u1
doc: Portion of remaining capacity
- id: fet_status
type: fet_bits
- id: cell_count
type: u1
- id: ntc_count
type: u1
Jakeler marked this conversation as resolved.
Show resolved Hide resolved
- id: temps
type: temp
size: 2
repeat: expr
repeat-expr: ntc_count
types:
balance_list:
seq:
- id: is_balancing
type: b1
repeat: expr
repeat-expr: 32
prot_list:
seq:
- id: reserved
type: b3
- id: is_fet_lock
type: b1
- id: is_ic_error
type: b1
- id: is_ocp_short
type: b1
- id: is_ocp_discharge
type: b1
- id: is_ocp_charge
type: b1
- id: is_utp_discharge
type: b1
- id: is_otp_discharge
type: b1
- id: is_utp_charge
type: b1
- id: is_otp_charge
type: b1
- id: is_uvp_pack
type: b1
- id: is_ovp_pack
type: b1
- id: is_uvp_cell
type: b1
- id: is_ovp_cell
type: b1
fet_bits:
seq:
- id: reserved
type: b6
- id: is_discharge_enabled
type: b1
- id: is_charge_enabled
type: b1
voltage:
Jakeler marked this conversation as resolved.
Show resolved Hide resolved
-affected-by: 522
seq:
- id: raw
type: u2
doc: Pack voltage (raw)
instances:
volt:
value: raw * 0.01
doc: Pack voltage (V)
capacity:
-affected-by: 522
seq:
- id: raw
type: u2
doc: Capacity (raw)
instances:
amp_hour:
value: raw * 0.01
doc: Capacity (Ah)
current:
-affected-by: 522
seq:
- id: raw
type: s2
doc: Actual current (raw)
instances:
amp:
value: raw * 0.01
doc: Actual current (A)
temp:
-affected-by: 522
seq:
- id: raw
type: u2
instances:
celsius:
Jakeler marked this conversation as resolved.
Show resolved Hide resolved
value: raw * 0.1 - 273.1

cell_voltages:
seq:
- id: cells
type: voltage
repeat: eos
types:
voltage:
-affected-by: 522
seq:
- id: raw
type: u2
doc: Cell voltage (raw)
instances:
volt:
value: raw * 0.001
doc: Cell voltage (V)

hardware:
seq:
- id: version
type: str
encoding: ascii
size-eos: true
doc: BMS model and version specification

response:
params:
- id: cmd
type: u1
enums:
status:
0x00: ok
0x80: fail
seq:
- id: status
type: u1
enum: status
- id: data_len
type: u1
- id: data
type:
switch-on: cmd
cases:
0x03: basic_info
0x04: cell_voltages
0x05: hardware
size: data_len