Skip to content

Commit

Permalink
Feature/add advertisements (#132)
Browse files Browse the repository at this point in the history
* fixed redundant config_flow for device discovery
* add detection tests for all BMS types
* check for MAC addr if name is empty
  • Loading branch information
patman15 authored Dec 28, 2024
1 parent 108b47c commit dbf0296
Show file tree
Hide file tree
Showing 4 changed files with 136 additions and 48 deletions.
2 changes: 1 addition & 1 deletion requirements_test.txt
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,4 @@ bluetooth-data-tools
pyserial-asyncio
pyudev
pytest-homeassistant-custom-component==0.13.132

kegtron-ble
128 changes: 104 additions & 24 deletions tests/advertisement_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,24 +5,24 @@
from .bluetooth import generate_advertisement_data

ADVERTISEMENTS: Final[list] = [
# ( # conflicting integrated component: https://github.com/patman15/BMS_BLE-HA/issues/123
# generate_advertisement_data(
# local_name="NWJ20221223010330",#\x11",
# manufacturer_data={65535: b"0UD7\xa2\xd2"},
# service_uuids=["0000ffe0-0000-1000-8000-00805f9b34fb"],
# rssi=-56,
# ),
# "ective_bms",
# ),
# (
# generate_advertisement_data(
# local_name="NWJ20221223010388",#\x11",
# manufacturer_data={65535: b"0UD7b\xec"},
# service_uuids=["0000ffe0-0000-1000-8000-00805f9b34fb"],
# rssi=-47,
# ),
# "ective_bms",
# ),
( # source LOG
generate_advertisement_data(
local_name="NWJ20221223010330\x11",
manufacturer_data={65535: b"0UD7\xa2\xd2"},
service_uuids=["0000ffe0-0000-1000-8000-00805f9b34fb"],
rssi=-56,
),
"ective_bms",
),
( # source LOG
generate_advertisement_data(
local_name="NWJ20221223010388\x11",
manufacturer_data={65535: b"0UD7b\xec"},
service_uuids=["0000ffe0-0000-1000-8000-00805f9b34fb"],
rssi=-47,
),
"ective_bms",
),
(
generate_advertisement_data(
local_name="BatteryOben-00",
Expand All @@ -33,7 +33,7 @@
),
"jikong_bms",
),
(
( # source LOG
generate_advertisement_data(
local_name="BatterieUnten-01",
manufacturer_data={2917: b"\x88\xa0\xc8G\x80\r\x08k"},
Expand All @@ -43,7 +43,7 @@
),
"jikong_bms",
),
(
( # source LOG
generate_advertisement_data(
local_name="JK_B2A8S20P",
manufacturer_data={2917: b"\x88\xa0\xc8G\x80\x14\x88\xb7"},
Expand All @@ -60,7 +60,7 @@
),
"jikong_bms",
),
(
( # source LOG
generate_advertisement_data(
local_name="SP05B2312190075 ",
service_uuids=["0000fff0-0000-1000-8000-00805f9b34fb"],
Expand All @@ -69,7 +69,7 @@
),
"seplos_bms",
),
(
( # source LOG
generate_advertisement_data(
local_name="SP66B2404270002 ",
service_uuids=["0000fff0-0000-1000-8000-00805f9b34fb"],
Expand All @@ -85,7 +85,7 @@
),
"seplos_v2_bms",
),
(
( # source LOG
generate_advertisement_data(
local_name="BP02",
service_uuids=[
Expand All @@ -97,7 +97,7 @@
),
"seplos_v2_bms",
),
(
( # source LOG
generate_advertisement_data(
local_name="LT-12V-1544",
manufacturer_data={33384: b"\x01\x02\x00\x07\x81\xb5N"},
Expand All @@ -106,4 +106,84 @@
),
"ej_bms",
),
( # source LOG
generate_advertisement_data(
local_name="170R000121",
manufacturer_data={
21330: b"!4\xba\x03\xec\x11\x0c\xb4\x01\x05\x00\x01\x00\x00"
},
service_uuids=[
"00001800-0000-1000-8000-00805f9b34fb",
"00001801-0000-1000-8000-00805f9b34fb",
"0000180a-0000-1000-8000-00805f9b34fb",
"0000fd00-0000-1000-8000-00805f9b34fb",
"0000ff90-0000-1000-8000-00805f9b34fb",
"0000ffb0-0000-1000-8000-00805f9b34fb",
"0000ffc0-0000-1000-8000-00805f9b34fb",
"0000ffd0-0000-1000-8000-00805f9b34fb",
"0000ffe0-0000-1000-8000-00805f9b34fb",
"0000ffe5-0000-1000-8000-00805f9b34fb",
"0000fff0-0000-1000-8000-00805f9b34fb",
],
tx_power=0,
rssi=-75,
),
"cbtpwr_bms",
),
( # source PCAP
generate_advertisement_data(
manufacturer_data={54976: b"\x3c\x4f\xac\x50\xff"},
),
"tdt_bms",
),
( # source bluetoothctl (https://github.com/patman15/BMS_BLE-HA/issues/52#issuecomment-2390048120)
generate_advertisement_data(
local_name="TBA-13500277",
service_uuids=[
"00001800-0000-1000-8000-00805f9b34fb",
"00001801-0000-1000-8000-00805f9b34fb",
"0000180a-0000-1000-8000-00805f9b34fb",
"0000fff0-0000-1000-8000-00805f9b34fb",
],
rssi=-72,
),
"dpwrcore_bms",
),
( # source LOG
generate_advertisement_data(
local_name="SmartBat-B15051",
service_uuids=["0000fff0-0000-1000-8000-00805f9b34fb"],
tx_power=3,
rssi=-66,
),
"ogt_bms",
),
( # source PCAP
generate_advertisement_data(
local_name="R-24100BNN160-A00643",
service_uuids=[
"0000ffe0-0000-1000-8000-00805f9b34fb",
],
manufacturer_data={22618: b"\xc8\x47\x80\x15\xd8\x34"},
),
"redodo_bms",
),
( # source LOG (https://github.com/patman15/BMS_BLE-HA/issues/89)
generate_advertisement_data(
local_name="DL-46640102XXXX",
manufacturer_data={25670: b"\x01\x02\t\xac"},
service_uuids=["0000fff0-0000-1000-8000-00805f9b34fb"],
tx_power=-127,
rssi=-58,
),
"daly_bms",
),
( # source nRF (https://github.com/patman15/BMS_BLE-HA/issues/22#issuecomment-2198586195)
generate_advertisement_data(
local_name="SX100P-B230201", # Supervolt Battery
service_uuids=["0000ff00-0000-1000-8000-00805f9b34fb"],
manufacturer_data={31488: "\x02\xFF\xFF\x7D"},
),
"jbd_bms",
),
]
39 changes: 18 additions & 21 deletions tests/test_config_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ def bms_advertisement(request) -> BluetoothServiceInfoBleak:
address: Final[str] = "c0:ff:ee:c0:ff:ee"
return BluetoothServiceInfoBleak(
name=str(dev.local_name),
address=request.param[1],
address=f"{address}_{request.param[1]}",
device=generate_ble_device(address=address, name=dev.local_name),
rssi=dev.rssi,
service_uuids=dev.service_uuids,
Expand All @@ -56,46 +56,43 @@ def bms_advertisement(request) -> BluetoothServiceInfoBleak:
)


async def test_device_discovery(
advertisement: BluetoothServiceInfoBleak, hass: HomeAssistant
async def test_bluetooth_discovery(
hass: HomeAssistant, advertisement: BluetoothServiceInfoBleak
) -> None:
"""Test discovery via bluetooth with a valid device."""
"""Test bluetooth device discovery."""

result: ConfigFlowResult = await hass.config_entries.flow.async_init(
DOMAIN,
context={"source": SOURCE_BLUETOOTH},
data=advertisement,
)
inject_bluetooth_service_info_bleak(hass, advertisement)
await hass.async_block_till_done(wait_background_tasks=True)

assert result.get("type") == FlowResultType.FORM
result: ConfigFlowResult = hass.config_entries.flow.async_progress_by_handler(DOMAIN)[0]
assert result.get("step_id") == "bluetooth_confirm"
assert result.get("description_placeholders") == {"name": advertisement.name}

inject_bluetooth_service_info_bleak(hass, advertisement)
assert result.get("context", {}).get("unique_id") == advertisement.address

result = await hass.config_entries.flow.async_configure(
result["flow_id"], user_input={"not": "empty"}
)
await hass.async_block_till_done()
assert result.get("type") == FlowResultType.CREATE_ENTRY
assert result.get("title") == advertisement.name
assert (
result.get("title") == advertisement.name or advertisement.address
) # address is used as name by Bleak if name is not available

# BluetoothServiceInfoBleak contains BMS type in the address, see bms_advertisement
# BluetoothServiceInfoBleak contains BMS type as trailer to the address, see bms_advertisement
assert (
hass.config_entries.async_entries()[1].data["type"]
== f"custom_components.bms_ble.plugins.{advertisement.address}"
== f"custom_components.bms_ble.plugins.{advertisement.address.split('_',1)[-1]}"
)


async def test_device_setup(
monkeypatch,
patch_bleakclient,
patch_bleakclient: None,
BTdiscovery: BluetoothServiceInfoBleak,
hass: HomeAssistant,
) -> None:
"""Test discovery via bluetooth with a valid device."""

result = await hass.config_entries.flow.async_init(
result: ConfigFlowResult = await hass.config_entries.flow.async_init(
DOMAIN,
context={"source": SOURCE_BLUETOOTH},
data=BTdiscovery,
Expand Down Expand Up @@ -154,7 +151,7 @@ async def test_invalid_plugin(monkeypatch, BTdiscovery, hass: HomeAssistant) ->
"""

monkeypatch.delattr(BaseBMS, "supported")
result = await hass.config_entries.flow.async_init(
result: ConfigFlowResult = await hass.config_entries.flow.async_init(
DOMAIN,
context={"source": SOURCE_BLUETOOTH},
data=BTdiscovery,
Expand Down Expand Up @@ -231,7 +228,7 @@ async def test_user_setup(

inject_bluetooth_service_info_bleak(hass, BTdiscovery)

result = await hass.config_entries.flow.async_init(
result: ConfigFlowResult = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": SOURCE_USER}
)
assert result.get("type") == FlowResultType.FORM
Expand Down Expand Up @@ -297,7 +294,7 @@ def patch_async_current_ids(_self) -> set[str | None]:

inject_bluetooth_service_info_bleak(hass, BTdiscovery)

result = await hass.config_entries.flow.async_init(
result: ConfigFlowResult = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": SOURCE_USER}
)
assert result.get("type") == FlowResultType.ABORT
Expand Down
15 changes: 13 additions & 2 deletions tests/test_plugins.py
Original file line number Diff line number Diff line change
@@ -1,17 +1,28 @@
"""Test the BLE Battery Management System base class functions."""

from custom_components.bms_ble.const import BMS_TYPES
from custom_components.bms_ble.plugins.basebms import BaseBMS

from .advertisement_data import ADVERTISEMENTS

def test_device_info(plugin_fixture) -> None:

def test_device_info(plugin_fixture: BaseBMS) -> None:
"""Test that the BMS returns valid device information."""
bms_instance: BaseBMS = plugin_fixture
result = bms_instance.device_info()
assert "manufacturer" in result
assert "model" in result


def test_matcher_dict(plugin_fixture) -> None:
def test_matcher_dict(plugin_fixture: BaseBMS) -> None:
"""Test that the BMS returns BT matcher."""
bms_instance: BaseBMS = plugin_fixture
assert len(bms_instance.matcher_dict_list())

def test_advertisements_complete() -> None:
"""Check that each BMS has at least one advertisement."""
bms_unchecked: list[str] = BMS_TYPES
for _adv, bms in ADVERTISEMENTS:
if bms in bms_unchecked:
bms_unchecked.remove(bms)
assert not bms_unchecked, f"{len(bms_unchecked)} missing BMS type advertisements: {bms_unchecked}"

0 comments on commit dbf0296

Please sign in to comment.