Improve Berger BLE telemetry and controls

This commit is contained in:
2026-07-31 21:03:29 +02:00
parent b390792e34
commit 6cd6a31435
2 changed files with 262 additions and 27 deletions
+61 -9
View File
@@ -1,6 +1,22 @@
import unittest
from berger_gui import DeviceState, berger_match_reason, decode_response, read_frame, split_frames, write_frame
from berger_gui import (
DeviceState,
berger_match_reason,
cell_statistics,
decode_response,
mos_mode,
read_frame,
split_frames,
validate_frame,
write_frame,
)
def response_frame(command, payload):
body = bytes([0xDD, command, 0x00, len(payload), *payload])
check = (-sum(body[2:])) & 0xFFFF
return body + check.to_bytes(2, "big") + b"\x77"
class BergerParserTest(unittest.TestCase):
@@ -11,6 +27,12 @@ class BergerParserTest(unittest.TestCase):
def test_write_switch_checksum(self):
self.assertEqual(write_frame(0xE1, b"\x00\x00").hex().upper(), "DD5AE1020000FF1D77")
def test_mos_modes(self):
self.assertEqual(mos_mode(True, True), 0)
self.assertEqual(mos_mode(False, True), 1)
self.assertEqual(mos_mode(True, False), 2)
self.assertEqual(mos_mode(False, False), 3)
def test_decode_base_data(self):
payload = bytearray(27 + 4)
payload[0:2] = (1328).to_bytes(2, "big")
@@ -26,10 +48,7 @@ class BergerParserTest(unittest.TestCase):
payload[22] = 2
payload[23:25] = (2981).to_bytes(2, "big")
payload[25:27] = (2991).to_bytes(2, "big")
body = bytes([0xDD, 0x03, 0x00, len(payload), *payload])
total = sum(body[1:]) & 0xFFFF
check = ((total ^ 0xFFFF) + 1) & 0xFFFF
frame = body + check.to_bytes(2, "big") + b"\x77"
frame = response_frame(0x03, payload)
buffer = bytearray(b"\x00" + frame + frame)
frames = split_frames(buffer)
@@ -52,14 +71,47 @@ class BergerParserTest(unittest.TestCase):
def test_decode_cell_voltages(self):
payload = b"".join(v.to_bytes(2, "big") for v in [3306, 3312, 3308, 3311])
body = bytes([0xDD, 0x04, 0x00, len(payload), *payload])
total = sum(body[1:]) & 0xFFFF
check = ((total ^ 0xFFFF) + 1) & 0xFFFF
frame = body + check.to_bytes(2, "big") + b"\x77"
frame = response_frame(0x04, payload)
state = DeviceState()
decode_response(frame, state)
self.assertEqual(state.telemetry.cells, [3.306, 3.312, 3.308, 3.311])
def test_captured_fragmented_base_data(self):
chunks = [
bytes.fromhex("DD030026053400005F8C75300000348900000000"),
bytes.fromhex("000063520304030BB70BA60BAA00000075305F8C"),
bytes.fromhex("0000F8DD77"),
]
buffer = bytearray()
frames = []
for chunk in chunks:
buffer.extend(chunk)
frames.extend(split_frames(buffer))
self.assertEqual(len(frames), 1)
self.assertTrue(validate_frame(frames[0]))
state = DeviceState()
decode_response(frames[0], state)
self.assertEqual(state.telemetry.voltage, 13.32)
self.assertEqual(state.telemetry.remaining_capacity, 244.6)
self.assertEqual(state.telemetry.nominal_capacity, 300.0)
self.assertEqual(state.telemetry.soc, 82)
self.assertEqual(state.telemetry.production_date, "2026-4-9")
self.assertEqual(state.telemetry.temperatures, [26.8, 25.1, 25.5])
self.assertTrue(state.telemetry.charge_mos)
self.assertTrue(state.telemetry.discharge_mos)
def test_captured_cell_voltages(self):
frame = bytes.fromhex("DD0400080D030D040D040D04FFB577")
self.assertTrue(validate_frame(frame))
state = DeviceState()
decode_response(frame, state)
self.assertEqual(state.telemetry.cells, [3.331, 3.332, 3.332, 3.332])
self.assertEqual(cell_statistics(state.telemetry.cells), (3.331, 3.332, 0.001))
def test_empty_cell_statistics(self):
self.assertIsNone(cell_statistics([]))
def test_scan_filter(self):
self.assertEqual(berger_match_reason(None, ["0000ff00-0000-1000-8000-00805f9b34fb"]), "service:ff00")
self.assertEqual(berger_match_reason("JBD-SP04S", []), "name")