diff --git a/pyproject.toml b/pyproject.toml index 652b723..5cf0d12 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -26,8 +26,8 @@ packages = [ [tool.poetry.dependencies] python = ">=3.7,<4.0" -bleak = "^0.14.3" -cryptography = "^37.0.2" +bleak = "^0.20.2" +cryptography = ">=37.0.2" typing-extensions = { version = "^4.2.0", python = "<3.8" } importlib-metadata = {version = "^4.11.4", python = "<3.8"} diff --git a/pysesameos2/ble.py b/pysesameos2/ble.py index 152ffe2..d9bbb11 100644 --- a/pysesameos2/ble.py +++ b/pysesameos2/ble.py @@ -6,6 +6,7 @@ from bleak import BleakScanner from bleak.backends.device import BLEDevice +from bleak.backends.scanner import AdvertisementData from bleak.exc import BleakError from pysesameos2.const import ( @@ -291,7 +292,7 @@ def getPayload(self) -> bytes: class BLEAdvertisement: - def __init__(self, dev: BLEDevice, manufacturer_data: dict) -> None: + def __init__(self, dev: BLEDevice, manufacturer_data: dict, rssi: int) -> None: if not isinstance(dev, BLEDevice): raise TypeError("Invalid dev") if not isinstance(manufacturer_data, dict): @@ -299,7 +300,7 @@ def __init__(self, dev: BLEDevice, manufacturer_data: dict) -> None: self._address = dev.address self._device = dev - self._rssi = dev.rssi + self._rssi = rssi self._advBytes = next(iter(manufacturer_data.values())) self._productModel = CHProductModel.getByValue(self._advBytes[0]) @@ -334,32 +335,31 @@ def isRegistered(self) -> bool: class CHBleManager: - def device_factory(self, dev: BLEDevice) -> Union["CHSesame2", "CHSesameBot"]: - """Return a device object corresponding to a BLE advertisement. + def device_factory( + self, dev: BLEDevice, adv_data: AdvertisementData + ) -> Union["CHSesame2", "CHSesameBot"]: + """Return a device object corresponding to a BLEDevice and AdvertisementData. Args: dev (BLEDevice): The discovered BLE device. + adv_data (AdvertisementData): break's AdvertisementData when discovered. Returns: Union[CHSesame2, CHSesameBot]: The candyhouse device. """ - if not isinstance(dev, BLEDevice): + if not isinstance(dev, BLEDevice) and not isinstance( + adv_data, AdvertisementData + ): raise TypeError("Invalid dev") if dev.name is None: raise ValueError("Failed to find the device name") - # `BLEDevice.metadata` should return device specific details in OS-agnostically way. - # https://bleak.readthedocs.io/en/latest/api.html#bleak.backends.device.BLEDevice.metadata - if ( - dev.metadata is None - or "uuids" not in dev.metadata - or "manufacturer_data" not in dev.metadata - ): - raise ValueError("Failed to find the device metadata") + if adv_data.service_uuids is None or adv_data.manufacturer_data is None: + raise ValueError("Failed to find the uuid/manufacture data") - if SERVICE_UUID in dev.metadata["uuids"]: - adv = BLEAdvertisement(dev, dev.metadata["manufacturer_data"]) + if SERVICE_UUID in adv_data.service_uuids: + adv = BLEAdvertisement(dev, adv_data.manufacturer_data, adv_data.rssi) device = adv.getProductModel().deviceFactory()() device.setAdvertisement(adv) @@ -381,11 +381,15 @@ async def scan( logger.info("Starting scan for SESAME devices...") ret = {} try: - devices = await asyncio.wait_for(BleakScanner.discover(), scan_duration) + discovers = await asyncio.wait_for( + BleakScanner.discover(return_adv=True), scan_duration + ) - for device in devices: + for discover in discovers.values(): + device = discover[0] + adv_data = discover[1] try: - obj = self.device_factory(device) + obj = self.device_factory(device, adv_data) except NotImplementedError: logger.warning("Unsupported SESAME device is found, skip.") continue @@ -421,30 +425,29 @@ async def scan_by_address( # `BleakScanner.find_device_by_address`. # # The problem is, the reposence (`BLEDevice`) of `find_device_by_address` does not - # contain a proper `matadata` which is heavily utilized in `device_factory`. - # - # `discover` internally calls `discovered_devices` which provides - # OS-agnostic `metadata` of the device. - # https://github.com/hbldh/bleak/blob/55a2d34cc96bb842be278485794806704caa2d2c/bleak/backends/scanner.py#L101 - # https://github.com/hbldh/bleak/blob/ce63ed4d92430f154ce33ab812e313961b26f7a4/bleak/backends/bluezdbus/scanner.py#L213-L237 + # provide the advertisement data. So we cannot identify the device model etc. - devices = await asyncio.wait_for(BleakScanner.discover(), scan_duration) + discovers = await asyncio.wait_for( + BleakScanner.discover(return_adv=True), scan_duration + ) - device = next( - (d for d in devices if d.address.lower() == ble_device_identifier.lower()), + discovered = next( + ( + d + for d in discovers.values() + if d[0].address.lower() == ble_device_identifier.lower() + ), None, ) - if device is None: + if discovered is None: raise ConnectionRefusedError("Scan completed: the device not found") try: - obj = self.device_factory(device) + obj = self.device_factory(discovered[0], discovered[1]) except NotImplementedError: raise NotImplementedError("This device is not supported.") except ValueError: raise ValueError("This is not a SESAME device.") - obj = self.device_factory(device) - logger.info("Scan completed: found the device") return obj diff --git a/pysesameos2/chsesame2.py b/pysesameos2/chsesame2.py index a6567a8..8d104cc 100644 --- a/pysesameos2/chsesame2.py +++ b/pysesameos2/chsesame2.py @@ -181,7 +181,7 @@ async def connect(self) -> None: logger.info(f"Connected to the device: {self.getDeviceUUID()}") self.setDeviceStatus(CHSesame2Status.WaitingGatt) - services = await self._client.get_services() + services = await self._client.services for s in services: if s.uuid == SERVICE_UUID: self.setCharacteristicTX(s.get_characteristic(TX_UUID)) diff --git a/tests/test_ble.py b/tests/test_ble.py index 3163620..5358f61 100644 --- a/tests/test_ble.py +++ b/tests/test_ble.py @@ -7,6 +7,7 @@ import pytest from bleak.backends.device import BLEDevice +from bleak.backends.scanner import AdvertisementData from bleak.exc import BleakError from pysesameos2.ble import ( @@ -197,9 +198,10 @@ def test_BLEAdvertisement(self): "0000fd81-0000-1000-8000-00805f9b34fb", ], rssi=-60, + details=None, manufacturer_data={1370: b"\x00\x00\x01"}, ) - b = BLEAdvertisement(dev=d, manufacturer_data={1370: b"\x00\x00\x01"}) + b = BLEAdvertisement(dev=d, manufacturer_data={1370: b"\x00\x00\x01"}, rssi=-60) assert b.getAddress() == "AA:BB:CC:11:22:33" assert b.getDevice() == d @@ -226,11 +228,21 @@ def test_CHBleManager_device_factory_not_supported_device(self): "0000fd81-0000-1000-8000-00805f9b34fb", ], rssi=-60, + details=None, manufacturer_data={1370: b"\xff\x00\x01"}, ) + adv = AdvertisementData( + None, + {1370: b"\xff\x00\x01"}, + {}, + ["0000fd81-0000-1000-8000-00805f9b34fb"], + None, + -60, + (), + ) with pytest.raises(NotImplementedError): - assert CHBleManager().device_factory(bled) + assert CHBleManager().device_factory(bled, adv) def test_CHBleManager_device_factory(self): bled = BLEDevice( @@ -240,36 +252,70 @@ def test_CHBleManager_device_factory(self): "0000fd81-0000-1000-8000-00805f9b34fb", ], rssi=-60, + details=None, manufacturer_data={1370: b"\x00\x00\x01"}, ) + adv = AdvertisementData( + None, + {1370: b"\x00\x00\x01"}, + {}, + ["0000fd81-0000-1000-8000-00805f9b34fb"], + None, + -60, + (), + ) - d = CHBleManager().device_factory(bled) + d = CHBleManager().device_factory(bled, adv) assert isinstance(d, CHSesame2) @pytest.mark.asyncio async def test_CHBleManager_scan_returns_None(self, bleak_scanner): async def _scan(*args, **kwargs): """Simulate a scanning response""" - return [ - BLEDevice( - "AA:BB:CC:11:22:33", - "QpGK0YFUSv+9H/DN6IqN4Q", - uuids=[ - "0000fd81-0000-1000-8000-00805f9b34fb", - ], - rssi=-60, - manufacturer_data={1370: b"\xff\x00\x01"}, + return { + "AA:BB:CC:11:22:33": ( + BLEDevice( + "AA:BB:CC:11:22:33", + "QpGK0YFUSv+9H/DN6IqN4Q", + uuids=[ + "0000fd81-0000-1000-8000-00805f9b34fb", + ], + rssi=-60, + details=None, + manufacturer_data={1370: b"\xff\x00\x01"}, + ), + AdvertisementData( + None, + {1370: b"\xff\x00\x01"}, + {}, + ["0000fd81-0000-1000-8000-00805f9b34fb"], + None, + -60, + (), + ), ), - BLEDevice( - "AA:BB:CC:44:55:66", - "QpGK0YFUSv+9H/DN6IqN4Q", - uuids=[ - "ffffffff-0000-1000-8000-00805f9b34fb", - ], - rssi=-60, - manufacturer_data={1370: b"\x00\x00\x01"}, + "AA:BB:CC:44:55:66": ( + BLEDevice( + "AA:BB:CC:44:55:66", + "QpGK0YFUSv+9H/DN6IqN4Q", + uuids=[ + "ffffffff-0000-1000-8000-00805f9b34fb", + ], + rssi=-60, + details=None, + manufacturer_data={1370: b"\x00\x00\x01"}, + ), + AdvertisementData( + None, + {1370: b"\x00\x00\x01"}, + {}, + ["ffffffff-0000-1000-8000-00805f9b34fb"], + None, + -60, + (), + ), ), - ] + } bleak_scanner.discover.side_effect = _scan @@ -293,26 +339,50 @@ async def raise_exception(*args, **kwargs): async def test_CHBleManager_scan(self, bleak_scanner): async def _scan(*args, **kwargs): """Simulate a scanning response""" - return [ - BLEDevice( - "AA:BB:CC:11:22:33", - "QpGK0YFUSv+9H/DN6IqN4Q", - uuids=[ - "0000fd81-0000-1000-8000-00805f9b34fb", - ], - rssi=-60, - manufacturer_data={1370: b"\x00\x00\x01"}, + return { + "AA:BB:CC:11:22:33": ( + BLEDevice( + "AA:BB:CC:11:22:33", + "QpGK0YFUSv+9H/DN6IqN4Q", + uuids=[ + "0000fd81-0000-1000-8000-00805f9b34fb", + ], + rssi=-60, + details=None, + manufacturer_data={1370: b"\x00\x00\x01"}, + ), + AdvertisementData( + None, + {1370: b"\x00\x00\x01"}, + {}, + ["0000fd81-0000-1000-8000-00805f9b34fb"], + None, + -60, + (), + ), ), - BLEDevice( - "AA:BB:CC:44:55:66", - "Em09ZpIiTlq83gxmKdSNQw", - uuids=[ - "0000fd81-0000-1000-8000-00805f9b34fb", - ], - rssi=-70, - manufacturer_data={1370: b"\x00\x00\x01"}, + "AA:BB:CC:44:55:66": ( + BLEDevice( + "AA:BB:CC:44:55:66", + "Em09ZpIiTlq83gxmKdSNQw", + uuids=[ + "0000fd81-0000-1000-8000-00805f9b34fb", + ], + rssi=-70, + details=None, + manufacturer_data={1370: b"\x00\x00\x01"}, + ), + AdvertisementData( + None, + {1370: b"\x00\x00\x01"}, + {}, + ["0000fd81-0000-1000-8000-00805f9b34fb"], + None, + -60, + (), + ), ), - ] + } bleak_scanner.discover.side_effect = _scan @@ -330,7 +400,7 @@ async def test_CHBleManager_scan_by_address_raises_exception_on_device_missing( ): async def _scan(*args, **kwargs): """Simulate a scanning response""" - return [] + return {} bleak_scanner.discover.side_effect = _scan @@ -345,49 +415,109 @@ async def test_CHBleManager_scan_by_address_raises_exception_on_broken_advertise ): async def _scan(*args, **kwargs): """Simulate a scanning response""" - return [ - BLEDevice( - "AA:BB:CC:11:22:33", - "INVALID_NAME", - uuids=[ - "0000fd81-0000-1000-8000-00805f9b34fb", - ], - rssi=-60, - manufacturer_data={1370: b"\x00\x00\x01"}, + return { + "AA:BB:CC:11:22:33": ( + BLEDevice( + "AA:BB:CC:11:22:33", + "INVALID_NAME", + uuids=[ + "0000fd81-0000-1000-8000-00805f9b34fb", + ], + rssi=-60, + details=None, + manufacturer_data={1370: b"\x00\x00\x01"}, + ), + AdvertisementData( + None, + {1370: b"\x00\x00\x01"}, + {}, + ["0000fd81-0000-1000-8000-00805f9b34fb"], + None, + -60, + (), + ), ), - BLEDevice( - "AA:BB:CC:44:55:66", - None, - uuids=[ - "0000fd81-0000-1000-8000-00805f9b34fb", - ], - rssi=-60, - manufacturer_data={1370: b"\x00\x00\x01"}, + "AA:BB:CC:44:55:66": ( + BLEDevice( + "AA:BB:CC:44:55:66", + None, + uuids=[ + "0000fd81-0000-1000-8000-00805f9b34fb", + ], + rssi=-60, + details=None, + manufacturer_data={1370: b"\x00\x00\x01"}, + ), + AdvertisementData( + None, + {1370: b"\x00\x00\x01"}, + {}, + ["0000fd81-0000-1000-8000-00805f9b34fb"], + None, + -60, + (), + ), ), - BLEDevice( - "AA:BB:CC:77:88:99", - "QpGK0YFUSv+9H/DN6IqN4Q", - uuids=[ - "0000fd81-0000-1000-8000-00805f9b34fb", - ], - rssi=-60, + "AA:BB:CC:77:88:99": ( + BLEDevice( + "AA:BB:CC:77:88:99", + "QpGK0YFUSv+9H/DN6IqN4Q", + uuids=[ + "0000fd81-0000-1000-8000-00805f9b34fb", + ], + rssi=-60, + details=None, + ), + AdvertisementData( + None, + None, + {}, + ["0000fd81-0000-1000-8000-00805f9b34fb"], + None, + -60, + (), + ), ), - BLEDevice( - "AA:BB:CC:AA:BB:CC", - "QpGK0YFUSv+9H/DN6IqN4Q", - rssi=-60, - manufacturer_data={1370: b"\x02\x00\x01"}, + "AA:BB:CC:AA:BB:CC": ( + BLEDevice( + "AA:BB:CC:AA:BB:CC", + "QpGK0YFUSv+9H/DN6IqN4Q", + rssi=-60, + details=None, + manufacturer_data={1370: b"\x02\x00\x01"}, + ), + AdvertisementData( + None, + {1370: b"\x02\x00\x01"}, + {}, + ["00000000-0000-0000-0000-000000000000"], + None, + -60, + (), + ), ), - BLEDevice( - "AA:BB:CC:DD:EE:FF", - "QpGK0YFUSv+9H/DN6IqN4Q", - uuids=[ - "ffffffff-0000-1000-8000-00805f9b34fb", - ], - rssi=-60, - manufacturer_data={1370: b"\x02\x00\x01"}, + "AA:BB:CC:DD:EE:FF": ( + BLEDevice( + "AA:BB:CC:DD:EE:FF", + "QpGK0YFUSv+9H/DN6IqN4Q", + uuids=[ + "ffffffff-0000-1000-8000-00805f9b34fb", + ], + rssi=-60, + details=None, + manufacturer_data={1370: b"\x02\x00\x01"}, + ), + AdvertisementData( + None, + {1370: b"\x02\x00\x01"}, + {}, + ["ffffffff-0000-1000-8000-00805f9b34fb"], + None, + -60, + (), + ), ), - ] + } bleak_scanner.discover.side_effect = _scan @@ -414,17 +544,29 @@ async def test_CHBleManager_scan_by_address_raises_exception_for_non_supported_d ): async def _scan(*args, **kwargs): """Simulate a scanning response""" - return [ - BLEDevice( - "AA:BB:CC:11:22:33", - "QpGK0YFUSv+9H/DN6IqN4Q", - uuids=[ - "0000fd81-0000-1000-8000-00805f9b34fb", - ], - rssi=-60, - manufacturer_data={1370: b"\xff\x00\x01"}, + return { + "AA:BB:CC:11:22:33": ( + BLEDevice( + "AA:BB:CC:11:22:33", + "QpGK0YFUSv+9H/DN6IqN4Q", + uuids=[ + "0000fd81-0000-1000-8000-00805f9b34fb", + ], + rssi=-60, + details=None, + manufacturer_data={1370: b"\xff\x00\x01"}, + ), + AdvertisementData( + None, + {1370: b"\xff\x00\x01"}, + {}, + ["0000fd81-0000-1000-8000-00805f9b34fb"], + None, + -60, + (), + ), ) - ] + } bleak_scanner.discover.side_effect = _scan @@ -437,17 +579,29 @@ async def _scan(*args, **kwargs): async def test_CHBleManager_scan_by_address(self, bleak_scanner): async def _scan(*args, **kwargs): """Simulate a scanning response""" - return [ - BLEDevice( - "AA:BB:CC:11:22:33", - "QpGK0YFUSv+9H/DN6IqN4Q", - uuids=[ - "0000fd81-0000-1000-8000-00805f9b34fb", - ], - rssi=-60, - manufacturer_data={1370: b"\x00\x00\x01"}, - ) - ] + return { + "AA:BB:CC:11:22:33": ( + BLEDevice( + "AA:BB:CC:11:22:33", + "QpGK0YFUSv+9H/DN6IqN4Q", + uuids=[ + "0000fd81-0000-1000-8000-00805f9b34fb", + ], + rssi=-60, + details=None, + manufacturer_data={1370: b"\x00\x00\x01"}, + ), + AdvertisementData( + None, + {1370: b"\x00\x00\x01"}, + {}, + ["0000fd81-0000-1000-8000-00805f9b34fb"], + None, + -60, + (), + ), + ), + } bleak_scanner.discover.side_effect = _scan diff --git a/tests/test_device.py b/tests/test_device.py index b856077..e0aad51 100644 --- a/tests/test_device.py +++ b/tests/test_device.py @@ -26,10 +26,11 @@ def ble_advertisement(): "0000fd81-0000-1000-8000-00805f9b34fb", ], rssi=-60, + details=None, manufacturer_data={1370: b"\x00\x00\x01"}, ) ble_advertisement = BLEAdvertisement( - dev=bledevice, manufacturer_data={1370: b"\x00\x00\x01"} + dev=bledevice, manufacturer_data={1370: b"\x00\x00\x01"}, rssi=-60 ) return ble_advertisement @@ -43,10 +44,11 @@ def ble_advertisement_not_registed_device(): "0000fd81-0000-1000-8000-00805f9b34fb", ], rssi=-60, + details=None, manufacturer_data={1370: b"\x00\x00\x00"}, ) ble_advertisement = BLEAdvertisement( - dev=bledevice, manufacturer_data={1370: b"\x00\x00\x00"} + dev=bledevice, manufacturer_data={1370: b"\x00\x00\x00"}, rssi=-60 ) return ble_advertisement