Skip to content

Commit

Permalink
clean protocol
Browse files Browse the repository at this point in the history
make errors timeout
add part of MAC to name to distinguish BMSs
adapted tests
  • Loading branch information
patman15 committed Dec 4, 2024
1 parent 3a29080 commit 1efd4dc
Show file tree
Hide file tree
Showing 3 changed files with 59 additions and 35 deletions.
2 changes: 1 addition & 1 deletion custom_components/bms_ble/plugins/basebms.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ def __init__(
disconnected_callback=self._on_disconnect,
services=[*self.uuid_services()],
)
self.name: Final[str] = self._ble_device.name or "undefined"
self.name: str = self._ble_device.name or "undefined"
self._data_event: Final[asyncio.Event] = asyncio.Event()

@staticmethod
Expand Down
11 changes: 4 additions & 7 deletions custom_components/bms_ble/plugins/jbd_bms.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,9 @@ class BMS(BaseBMS):
def __init__(self, ble_device: BLEDevice, reconnect: bool = False) -> None:
"""Intialize private BMS members."""
super().__init__(LOGGER, self._notification_handler, ble_device, reconnect)
self.name: str = str(self._ble_device.name) + self._ble_device.address[-4:]
self._data: bytearray = bytearray()
self._data_final: bytearray | None = None
self._data_final: bytearray = bytearray()

@staticmethod
def matcher_dict_list() -> list[dict[str, Any]]:
Expand Down Expand Up @@ -123,7 +124,6 @@ def _notification_handler(self, _sender, data: bytearray) -> None:
LOGGER.debug(
"%s: incorrect frame end (length: %i).", self.name, len(self._data)
)
self._data_event.set()
return

crc: Final[int] = BMS._crc(self._data[2 : frame_end - 2])
Expand All @@ -134,10 +134,9 @@ def _notification_handler(self, _sender, data: bytearray) -> None:
int.from_bytes(self._data[frame_end - 2 : frame_end], "big"),
crc,
)
self._data_final = None # reset invalid data
else:
self._data_final = self._data
return

self._data_final = self._data
self._data_event.set()

@staticmethod
Expand Down Expand Up @@ -194,8 +193,6 @@ async def _async_update(self) -> BMSsample:
await self._client.write_gatt_char(BMS.uuid_tx(), data=cmd)
await asyncio.wait_for(self._wait_event(), timeout=BAT_TIMEOUT)

if self._data_final is None:
continue
if (
len(self._data_final) != BMS.INFO_LEN + self._data_final[3]
or len(self._data_final) < BMS.INFO_LEN + exp_len
Expand Down
81 changes: 54 additions & 27 deletions tests/test_jbd_bms.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
from bleak.backends.characteristic import BleakGATTCharacteristic
from bleak.exc import BleakError
from bleak.uuids import normalize_uuid_str
import pytest

from custom_components.bms_ble.plugins.jbd_bms import BMS

Expand Down Expand Up @@ -62,32 +63,32 @@ async def write_gatt_char(
self._notify_callback("MockJBDBleakClient", notify_data)


class MockInvalidBleakClient(MockJBDBleakClient):
"""Emulate a JBD BMS BleakClient returning wrong data."""
# class MockInvalidBleakClient(MockJBDBleakClient):
# """Emulate a JBD BMS BleakClient returning wrong data."""

def _response(
self, char_specifier: BleakGATTCharacteristic | int | str | UUID, data: Buffer
) -> bytearray:
if (
isinstance(char_specifier, str)
and normalize_uuid_str(char_specifier) == normalize_uuid_str("ff02")
and bytearray(data)[0] == self.HEAD_CMD
):
if bytearray(data)[1:3] == self.CMD_INFO:
return bytearray( # wrong end
b"\xdd\x03\x00\x1D\x06\x18\xFE\xE1\x01\xF2\x01\xF4\x00\x2A\x2C\x7C\x00\x00\x00"
b"\x00\x00\x00\x80\x64\x03\x04\x03\x0B\x8B\x0B\x8A\x0B\x84\xf8\x84\xdd"
)
# def _response(
# self, char_specifier: BleakGATTCharacteristic | int | str | UUID, data: Buffer
# ) -> bytearray:
# if (
# isinstance(char_specifier, str)
# and normalize_uuid_str(char_specifier) == normalize_uuid_str("ff02")
# and bytearray(data)[0] == self.HEAD_CMD
# ):
# if bytearray(data)[1:3] == self.CMD_INFO:
# return bytearray( # wrong end
# b"\xdd\x03\x00\x1D\x06\x18\xFE\xE1\x01\xF2\x01\xF4\x00\x2A\x2C\x7C\x00\x00\x00"
# b"\x00\x00\x00\x80\x64\x03\x04\x03\x0B\x8B\x0B\x8A\x0B\x84\xf8\x84\xdd"
# )

return ( # wrong CRC
bytearray(b"\xdd\x03\x00\x1d") + bytearray(31) + bytearray(b"\x77")
)
# return ( # wrong CRC
# bytearray(b"\xdd\x04\x00\x1d") + bytearray(31) + bytearray(b"\x77")
# )

return bytearray()
# return bytearray()

async def disconnect(self) -> bool:
"""Mock disconnect to raise BleakError."""
raise BleakError
# async def disconnect(self) -> bool:
# """Mock disconnect to raise BleakError."""
# raise BleakError


class MockOversizedBleakClient(MockJBDBleakClient):
Expand Down Expand Up @@ -166,19 +167,45 @@ async def test_update(monkeypatch, reconnect_fixture) -> None:
await bms.disconnect()


async def test_invalid_response(monkeypatch) -> None:
@pytest.fixture(
name="wrong_response",
params=[
bytearray( # wrong end
b"\xdd\x03\x00\x1D\x06\x18\xFE\xE1\x01\xF2\x01\xF4\x00\x2A\x2C\x7C\x00\x00\x00"
b"\x00\x00\x00\x80\x64\x03\x04\x03\x0B\x8B\x0B\x8A\x0B\x84\xf8\x84\xdd"
),
bytearray(b"\xdd\x04\x00\x1d")
+ bytearray(31)
+ bytearray(b"\x77"), # wrong CRC
],
)
def response(request) -> bytearray:
"""Return all possible BMS variants."""
return request.param


async def test_invalid_response(monkeypatch, wrong_response) -> None:
"""Test data update with BMS returning invalid data (wrong CRC)."""

monkeypatch.setattr(
"custom_components.bms_ble.plugins.jbd_bms.BAT_TIMEOUT",
0.1,
)

monkeypatch.setattr(
"tests.test_jbd_bms.MockJBDBleakClient._response",
lambda _s, _c_, d: wrong_response,
)

monkeypatch.setattr(
"custom_components.bms_ble.plugins.basebms.BleakClient",
MockInvalidBleakClient,
MockJBDBleakClient,
)

bms = BMS(generate_ble_device("cc:cc:cc:cc:cc:cc", "MockBLEdevice", None, -73))

result = await bms.async_update()

assert result == {}
with pytest.raises(TimeoutError):
_result = await bms.async_update()

await bms.disconnect()

Expand Down

0 comments on commit 1efd4dc

Please sign in to comment.