import unittest 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): def test_read_frame_checksum(self): self.assertEqual(read_frame(0x03).hex().upper(), "DDA50300FFFD77") self.assertEqual(read_frame(0x04).hex().upper(), "DDA50400FFFC77") 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") payload[2:4] = (65536 - 752).to_bytes(2, "big") payload[4:6] = (10750).to_bytes(2, "big") payload[6:8] = (16000).to_bytes(2, "big") payload[8:10] = (3).to_bytes(2, "big") payload[10:12] = ((25 << 9) | (7 << 5) | 27).to_bytes(2, "big") payload[16:18] = (0x0010).to_bytes(2, "big") payload[18] = 21 payload[19] = 67 payload[20] = 3 payload[22] = 2 payload[23:25] = (2981).to_bytes(2, "big") payload[25:27] = (2991).to_bytes(2, "big") frame = response_frame(0x03, payload) buffer = bytearray(b"\x00" + frame + frame) frames = split_frames(buffer) self.assertEqual(len(frames), 2) state = DeviceState() decode_response(frames[0], state) self.assertAlmostEqual(state.telemetry.voltage, 13.28) self.assertAlmostEqual(state.telemetry.current, -7.52) self.assertEqual(state.telemetry.soc, 67) self.assertAlmostEqual(state.telemetry.remaining_capacity, 107.5) self.assertAlmostEqual(state.telemetry.nominal_capacity, 160.0) self.assertEqual(state.telemetry.cycle_count, 3) self.assertEqual(state.telemetry.production_date, "2025-7-27") self.assertTrue(state.telemetry.charge_mos) self.assertTrue(state.telemetry.discharge_mos) self.assertTrue(state.telemetry.protection) self.assertEqual(state.telemetry.temperatures, [25.0, 26.0]) self.assertEqual(state.telemetry.time_label, "Time till empty") def test_decode_cell_voltages(self): payload = b"".join(v.to_bytes(2, "big") for v in [3306, 3312, 3308, 3311]) 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") self.assertIsNone(berger_match_reason("Keyboard", [])) if __name__ == "__main__": unittest.main()