def async_on_advertisement(self, adv: BluetoothLEAdvertisement) -> None: """Call the registered callback.""" now = time.monotonic() address = ":".join(TWO_CHAR.findall("%012X" % adv.address)) # must be upper advertisement_data = AdvertisementData( # type: ignore[no-untyped-call] local_name=None if adv.name == "" else adv.name, manufacturer_data=adv.manufacturer_data, service_data=adv.service_data, service_uuids=adv.service_uuids, ) device = BLEDevice( # type: ignore[no-untyped-call] address=address, name=adv.name, details={}, rssi=adv.rssi, ) self._discovered_devices[address] = device self._discovered_device_timestamps[address] = now self._new_info_callback( BluetoothServiceInfoBleak( name=advertisement_data.local_name or device.name or device.address, address=device.address, rssi=device.rssi, manufacturer_data=advertisement_data.manufacturer_data, service_data=advertisement_data.service_data, service_uuids=advertisement_data.service_uuids, source=self._source, device=device, advertisement=advertisement_data, connectable=False, time=now, ))
def fixture_scanner(hass): """Fixture for scanner.""" devices = [BLEDevice("1.1.1.1", "COOKERHOOD_FJAR")] class MockScanner(BaseBleakScanner): """Mock Scanner.""" async def start(self): """Start scanning for devices.""" for device in devices: self._callback(device, AdvertisementData()) async def stop(self): """Stop scanning for devices.""" @property def discovered_devices(self) -> list[BLEDevice]: """Return discovered devices.""" return devices def set_scanning_filter(self, **kwargs): """Set the scanning filter.""" with patch( "homeassistant.components.fjaraskupan.config_flow.BleakScanner", new=MockScanner ), patch( "homeassistant.components.fjaraskupan.config_flow.CONST_WAIT_TIME", new=0.01 ): yield devices
async def get_discovered_devices(self) -> List[BLEDevice]: found = [] peripherals = cbapp.central_manager_delegate.peripheral_list for i, peripheral in enumerate(peripherals): address = peripheral.identifier().UUIDString() name = peripheral.name() or "Unknown" details = peripheral advertisementData = cbapp.central_manager_delegate.advertisement_data_list[i] manufacturer_binary_data = advertisementData.get("kCBAdvDataManufacturerData") manufacturer_data = {} if manufacturer_binary_data: manufacturer_id = int.from_bytes( manufacturer_binary_data[0:2], byteorder="little" ) manufacturer_value = bytes(manufacturer_binary_data[2:]) manufacturer_data = {manufacturer_id: manufacturer_value} uuids = [ # converting to lower case to match other platforms str(u).lower() for u in advertisementData.get("kCBAdvDataServiceUUIDs", []) ] found.append( BLEDevice( address, name, details, uuids=uuids, manufacturer_data=manufacturer_data ) ) return found
def discovered_devices(self) -> List[BLEDevice]: # Reduce output. discovered_devices = [] for path, props in self._devices.items(): if not props: logger.debug( "Disregarding %s since no properties could be obtained." % path ) continue name, address, _, path = _device_info(path, props) if address is None: continue uuids = props.get("UUIDs", []) manufacturer_data = props.get("ManufacturerData", {}) discovered_devices.append( BLEDevice( address, name, {"path": path, "props": props}, props.get("RSSI", 0), uuids=uuids, manufacturer_data=manufacturer_data, ) ) return discovered_devices
def parse_eventargs(event_args): bdaddr = _format_bdaddr(event_args.BluetoothAddress) uuids = [] for u in event_args.Advertisement.ServiceUuids: uuids.append(u.ToString()) data = {} for m in event_args.Advertisement.ManufacturerData: with BleakDataReader(m.Data) as reader: data[m.CompanyId] = reader.read() local_name = event_args.Advertisement.LocalName return BLEDevice(bdaddr, local_name, event_args, uuids=uuids, manufacturer_data=data)
def discovered_devices(self) -> List[BLEDevice]: found = [] peripherals = self._manager.central_manager.retrievePeripheralsWithIdentifiers_( NSArray(self._identifiers.keys()), ) for peripheral in peripherals: address = peripheral.identifier().UUIDString() name = peripheral.name() or "Unknown" details = peripheral rssi = self._manager.devices[address].rssi advertisementData = self._identifiers[peripheral.identifier()] manufacturer_binary_data = advertisementData.get( "kCBAdvDataManufacturerData" ) manufacturer_data = {} if manufacturer_binary_data: manufacturer_id = int.from_bytes( manufacturer_binary_data[0:2], byteorder="little" ) manufacturer_value = bytes(manufacturer_binary_data[2:]) manufacturer_data = {manufacturer_id: manufacturer_value} uuids = [ cb_uuid_to_str(u) for u in advertisementData.get("kCBAdvDataServiceUUIDs", []) ] service_data = {} adv_service_data = advertisementData.get("kCBAdvDataServiceData", []) for u in adv_service_data: service_data[cb_uuid_to_str(u)] = bytes(adv_service_data[u]) found.append( BLEDevice( address, name, details, rssi=rssi, uuids=uuids, manufacturer_data=manufacturer_data, service_data=service_data, delegate=self._manager.central_manager.delegate(), ) ) return found
def callback(p: CBPeripheral, a: Dict[str, Any], r: int) -> None: # update identifiers for scanned device self._identifiers.setdefault(p.identifier(), {}).update(a) if not self._callback: return # Process service data service_data_dict_raw = a.get("kCBAdvDataServiceData", {}) service_data = { cb_uuid_to_str(k): bytes(v) for k, v in service_data_dict_raw.items() } # Process manufacturer data into a more friendly format manufacturer_binary_data = a.get("kCBAdvDataManufacturerData") manufacturer_data = {} if manufacturer_binary_data: manufacturer_id = int.from_bytes( manufacturer_binary_data[0:2], byteorder="little" ) manufacturer_value = bytes(manufacturer_binary_data[2:]) manufacturer_data[manufacturer_id] = manufacturer_value service_uuids = [ cb_uuid_to_str(u) for u in a.get("kCBAdvDataServiceUUIDs", []) ] advertisement_data = AdvertisementData( local_name=p.name(), manufacturer_data=manufacturer_data, service_data=service_data, service_uuids=service_uuids, platform_data=(p, a, r), ) device = BLEDevice( p.identifier().UUIDString(), p.name(), p, r, uuids=service_uuids, manufacturer_data=manufacturer_data, service_data=service_data, delegate=self._manager.central_manager.delegate(), ) self._callback(device, advertisement_data)
async def main(): devices = [BLEDevice(addr, None) for addr in sys.argv[1:]] tasks = [] for device in devices: tasks.append(asyncio.create_task(get_device_services(device))) if tasks: results = await asyncio.gather(*tasks) else: results = [] for device, services in results: print(f'Device {device}') if services: for service in services: print(f'\tService {services}') else: print(f'\tunknown') print('all done')
def make_advertisement(address: str, payload: bytes) -> BluetoothServiceInfoBleak: """Make a dummy advertisement.""" return BluetoothServiceInfoBleak( name="Test Device", address=address, device=BLEDevice(address, None), rssi=-56, manufacturer_data={}, service_data={ "0000181c-0000-1000-8000-00805f9b34fb": payload, }, service_uuids=["0000181c-0000-1000-8000-00805f9b34fb"], source="local", advertisement=AdvertisementData(local_name="Test Device"), time=0, connectable=False, )
def parse_eventargs(event_args): bdaddr = _format_bdaddr(event_args.BluetoothAddress) uuids = [] for u in event_args.Advertisement.ServiceUuids: uuids.append(u.ToString()) data = {} for m in event_args.Advertisement.ManufacturerData: md = IBuffer(m.Data) b = Array.CreateInstance(Byte, md.Length) reader = DataReader.FromBuffer(md) reader.ReadBytes(b) data[m.CompanyId] = bytes(b) local_name = event_args.Advertisement.LocalName return BLEDevice(bdaddr, local_name, event_args, uuids=uuids, manufacturer_data=data)
def _invoke_callback(self, path: str, message: Message) -> None: """Invokes the advertising data callback. Args: message: The D-Bus message that triggered the callback. """ if self._callback is None: return props = self._devices[path] # Get all the information wanted to pack in the advertisement data _local_name = props.get("Name") _manufacturer_data = { k: bytes(v) for k, v in props.get("ManufacturerData", {}).items() } _service_data = { k: bytes(v) for k, v in props.get("ServiceData", {}).items() } _service_uuids = props.get("UUIDs", []) # Pack the advertisement data advertisement_data = AdvertisementData( local_name=_local_name, manufacturer_data=_manufacturer_data, service_data=_service_data, service_uuids=_service_uuids, platform_data=(props, message), ) device = BLEDevice( props["Address"], props["Alias"], { "path": path, "props": props }, props.get("RSSI", 0), ) self._callback(device, advertisement_data)
def _parse_event_args(event_args): bdaddr = _format_bdaddr(event_args.bluetooth_address) uuids = [] try: for u in event_args.advertisement.service_uuids: uuids.append(str(u)) except NotImplementedError as e: # Cannot get service uuids for this device... pass data = {} for m in event_args.advertisement.manufacturer_data: data[m.company_id] = bytes(m.data) local_name = event_args.advertisement.local_name rssi = event_args.raw_signal_strength_in_d_bm return BLEDevice(bdaddr, local_name, event_args, rssi, uuids=uuids, manufacturer_data=data)
def AdvertisementWatcher_Received(sender, e): if sender == watcher: # logger.debug("Received {0}.".format(_format_event_args(e))) l_bdaddr = _format_bdaddr(e.BluetoothAddress) l_uuids = [] for l_u in e.Advertisement.ServiceUuids: l_uuids.append(l_u.ToString()) l_data = {} for l_m in e.Advertisement.ManufacturerData: l_md = IBuffer(l_m.Data) l_b = Array.CreateInstance(Byte, l_md.Length) l_reader = DataReader.FromBuffer(l_md) l_reader.ReadBytes(l_b) l_data[l_m.CompanyId] = bytes(l_b) local_name = e.Advertisement.LocalName logger.debug(f'>>> bdaddr:{l_bdaddr} local_name:{local_name} mfdata:{l_data}') if q: q.put(BLEDevice( l_bdaddr, local_name, e, uuids=l_uuids, manufacturer_data=l_data, ))
async def discover(timeout: float = 5.0, **kwargs) -> List[BLEDevice]: """Perform a Bluetooth LE Scan using Windows.Devices.Bluetooth.Advertisement Args: timeout (float): Time to scan for. Keyword Args: SignalStrengthFilter (Windows.Devices.Bluetooth.BluetoothSignalStrengthFilter): A BluetoothSignalStrengthFilter object used for configuration of Bluetooth LE advertisement filtering that uses signal strength-based filtering. AdvertisementFilter (Windows.Devices.Bluetooth.Advertisement.BluetoothLEAdvertisementFilter): A BluetoothLEAdvertisementFilter object used for configuration of Bluetooth LE advertisement filtering that uses payload section-based filtering. string_output (bool): If set to false, ``discover`` returns .NET device objects instead. Returns: List of strings or objects found. """ signal_strength_filter = kwargs.get("SignalStrengthFilter", None) advertisement_filter = kwargs.get("AdvertisementFilter", None) watcher = BluetoothLEAdvertisementWatcher() devices = {} scan_responses = {} def _format_bdaddr(a): return ":".join("{:02X}".format(x) for x in a.to_bytes(6, byteorder="big")) def _format_event_args(e): try: return "{0}: {1}".format( _format_bdaddr(e.BluetoothAddress), e.Advertisement.LocalName or "Unknown", ) except Exception: return e.BluetoothAddress def AdvertisementWatcher_Received(sender, e): if sender == watcher: logger.debug("Received {0}.".format(_format_event_args(e))) if e.AdvertisementType == BluetoothLEAdvertisementType.ScanResponse: if e.BluetoothAddress not in scan_responses: scan_responses[e.BluetoothAddress] = e else: if e.BluetoothAddress not in devices: devices[e.BluetoothAddress] = e def AdvertisementWatcher_Stopped(sender, e): if sender == watcher: logger.debug("{0} devices found. Watcher status: {1}.".format( len(devices), watcher.Status)) watcher.Received += AdvertisementWatcher_Received watcher.Stopped += AdvertisementWatcher_Stopped watcher.ScanningMode = BluetoothLEScanningMode.Active if signal_strength_filter is not None: watcher.SignalStrengthFilter = signal_strength_filter if advertisement_filter is not None: watcher.AdvertisementFilter = advertisement_filter # Watcher works outside of the Python process. watcher.Start() await asyncio.sleep(timeout) watcher.Stop() try: watcher.Received -= AdvertisementWatcher_Received watcher.Stopped -= AdvertisementWatcher_Stopped except Exception as e: logger.debug("Could not remove event handlers: {0}...".format(e)) found = [] for d in list(devices.values()): bdaddr = _format_bdaddr(d.BluetoothAddress) uuids = [] for u in d.Advertisement.ServiceUuids: uuids.append(u.ToString()) data = {} for m in d.Advertisement.ManufacturerData: with BleakDataReader(m.Data) as reader: data[m.CompanyId] = reader.read() local_name = d.Advertisement.LocalName if not local_name and d.BluetoothAddress in scan_responses: local_name = scan_responses[ d.BluetoothAddress].Advertisement.LocalName found.append( BLEDevice( bdaddr, local_name, d, uuids=uuids, manufacturer_data=data, )) return found
"""Tests for the Fjäråskupan integration.""" from bleak.backends.device import BLEDevice from bleak.backends.scanner import AdvertisementData from homeassistant.components.bluetooth import BluetoothServiceInfoBleak COOKER_SERVICE_INFO = BluetoothServiceInfoBleak( name="COOKERHOOD_FJAR", address="AA:BB:CC:DD:EE:FF", rssi=-60, manufacturer_data={}, service_uuids=[], service_data={}, source="local", device=BLEDevice(address="AA:BB:CC:DD:EE:FF", name="COOKERHOOD_FJAR"), advertisement=AdvertisementData(), time=0, connectable=True, )
async def discover_by_enumeration(timeout: float = 5.0, **kwargs) -> List[BLEDevice]: """Perform a Bluetooth LE Scan using Windows.Devices.Enumeration Args: timeout (float): Time to scan for. Keyword Args: string_output (bool): If set to false, ``discover`` returns .NET device objects instead. Returns: List of strings or objects found. """ requested_properties = Array[str]([ "System.Devices.Aep.DeviceAddress", "System.Devices.Aep.IsConnected", "System.Devices.Aep.Bluetooth.Le.IsConnectable", "System.ItemNameDisplay", "System.Devices.Aep.Manufacturer", "System.Devices.Manufacturer", "System.Devices.Aep.ModelName", "System.Devices.ModelName", "System.Devices.Aep.SignalStrength", ]) aqs_all_bluetooth_le_devices = ('(System.Devices.Aep.ProtocolId:="' '{bb7bb05e-5972-42b5-94fc-76eaa7084d49}")') watcher = Enumeration.DeviceInformation.CreateWatcher( aqs_all_bluetooth_le_devices, requested_properties, Enumeration.DeviceInformationKind.AssociationEndpoint, ) devices = {} def _format_device_info(d): try: return "{0}: {1}".format( d.Id.split("-")[-1], d.Name if d.Name else "Unknown") except Exception: return d.Id def DeviceWatcher_Added(sender, dinfo): if sender == watcher: logger.debug("Added {0}.".format(_format_device_info(dinfo))) if dinfo.Id not in devices: devices[dinfo.Id] = dinfo def DeviceWatcher_Updated(sender, dinfo_update): if sender == watcher: if dinfo_update.Id in devices: logger.debug("Updated {0}.".format( _format_device_info(devices[dinfo_update.Id]))) devices[dinfo_update.Id].Update(dinfo_update) def DeviceWatcher_Removed(sender, dinfo_update): if sender == watcher: logger.debug("Removed {0}.".format( _format_device_info(devices[dinfo_update.Id]))) if dinfo_update.Id in devices: devices.pop(dinfo_update.Id) def DeviceWatcher_EnumCompleted(sender, obj): if sender == watcher: logger.debug( "{0} devices found. Enumeration completed. Watching for updates..." .format(len(devices))) def DeviceWatcher_Stopped(sender, obj): if sender == watcher: logger.debug("{0} devices found. Watcher status: {1}.".format( len(devices), watcher.Status)) watcher.Added += DeviceWatcher_Added watcher.Updated += DeviceWatcher_Updated watcher.Removed += DeviceWatcher_Removed watcher.EnumerationCompleted += DeviceWatcher_EnumCompleted watcher.Stopped += DeviceWatcher_Stopped # Watcher works outside of the Python process. watcher.Start() await asyncio.sleep(timeout) watcher.Stop() try: watcher.Added -= DeviceWatcher_Added watcher.Updated -= DeviceWatcher_Updated watcher.Removed -= DeviceWatcher_Removed watcher.EnumerationCompleted -= DeviceWatcher_EnumCompleted watcher.Stopped -= DeviceWatcher_Stopped except Exception as e: logger.debug("Could not remove event handlers: {0}...".format(e)) found = [] for d in devices.values(): properties = {p.Key: p.Value for p in d.Properties} found.append( BLEDevice( properties["System.Devices.Aep.DeviceAddress"], d.Name, d, uuids=[], manufacturer_data=b"", )) return found
async def discover(timeout: float = 5.0, loop: AbstractEventLoop = None, **kwargs) -> List[BLEDevice]: """Perform a Bluetooth LE Scan. Args: timeout (float): duration of scaning period loop (Event Loop): Event Loop to use """ loop = loop if loop else asyncio.get_event_loop() devices = {} if not cbapp.central_manager_delegate.enabled: raise BleakError("Bluetooth device is turned off") scan_options = {"timeout": timeout} await cbapp.central_manager_delegate.scanForPeripherals_(scan_options) # CoreBluetooth doesn't explicitly use MAC addresses to identify peripheral # devices because private devices may obscure their MAC addresses. To cope # with this, CoreBluetooth utilizes UUIDs for each peripheral. We'll use # this for the BLEDevice address on macOS found = [] peripherals = cbapp.central_manager_delegate.peripheral_list for i, peripheral in enumerate(peripherals): address = peripheral.identifier().UUIDString() name = peripheral.name() or "Unknown" details = peripheral advertisementData = cbapp.central_manager_delegate.advertisement_data_list[ i] manufacturer_binary_data = ( advertisementData["kCBAdvDataManufacturerData"] if "kCBAdvDataManufacturerData" in advertisementData.keys() else None) manufacturer_data = {} if manufacturer_binary_data: manufacturer_id = int.from_bytes(manufacturer_binary_data[0:2], byteorder="little") manufacturer_value = "".join( list( map( lambda x: format(x, "x") if len(format(x, "x")) == 2 else "0{}".format( format(x, "x")), list(manufacturer_binary_data)[2:], ))) manufacturer_data = {manufacturer_id: manufacturer_value} found.append( BLEDevice(address, name, details, manufacturer_data=manufacturer_data)) return found
from homeassistant.components.bluetooth import BluetoothServiceInfoBleak YALE_ACCESS_LOCK_DISCOVERY_INFO = BluetoothServiceInfoBleak( name="M1012LU", address="AA:BB:CC:DD:EE:FF", rssi=-60, manufacturer_data={ 465: b"\x00\x00\xd1\xf0b;\xd8\x1dE\xd6\xba\xeeL\xdd]\xf5\xb2\xe9", 76: b"\x061\x00Z\x8f\x93\xb2\xec\x85\x06\x00i\x00\x02\x02Q\xed\x1d\xf0", }, service_uuids=[], service_data={}, source="local", device=BLEDevice(address="AA:BB:CC:DD:EE:FF", name="M1012LU"), advertisement=AdvertisementData(), time=0, connectable=True, ) LOCK_DISCOVERY_INFO_UUID_ADDRESS = BluetoothServiceInfoBleak( name="M1012LU", address="61DE521B-F0BF-9F44-64D4-75BBE1738105", rssi=-60, manufacturer_data={ 465: b"\x00\x00\xd1\xf0b;\xd8\x1dE\xd6\xba\xeeL\xdd]\xf5\xb2\xe9", 76: b"\x061\x00Z\x8f\x93\xb2\xec\x85\x06\x00i\x00\x02\x02Q\xed\x1d\xf0", }, service_uuids=[],
WOHAND_SERVICE_INFO = BluetoothServiceInfoBleak( name="WoHand", manufacturer_data={89: b"\xfd`0U\x92W"}, service_data={"00000d00-0000-1000-8000-00805f9b34fb": b"H\x90\xd9"}, service_uuids=["cba20d00-224d-11e6-9fb8-0002a5d5c51b"], address="aa:bb:cc:dd:ee:ff", rssi=-60, source="local", advertisement=AdvertisementData( local_name="WoHand", manufacturer_data={89: b"\xfd`0U\x92W"}, service_data={"00000d00-0000-1000-8000-00805f9b34fb": b"H\x90\xd9"}, service_uuids=["cba20d00-224d-11e6-9fb8-0002a5d5c51b"], ), device=BLEDevice("aa:bb:cc:dd:ee:ff", "WoHand"), time=0, connectable=True, ) WOHAND_SERVICE_INFO_NOT_CONNECTABLE = BluetoothServiceInfoBleak( name="WoHand", manufacturer_data={89: b"\xfd`0U\x92W"}, service_data={"00000d00-0000-1000-8000-00805f9b34fb": b"H\x90\xd9"}, service_uuids=["cba20d00-224d-11e6-9fb8-0002a5d5c51b"], address="aa:bb:cc:dd:ee:ff", rssi=-60, source="local", advertisement=AdvertisementData( local_name="WoHand", manufacturer_data={89: b"\xfd`0U\x92W"},
"""Tests for the LED BLE Bluetooth integration.""" from bleak.backends.device import BLEDevice from bleak.backends.scanner import AdvertisementData from homeassistant.components.bluetooth import BluetoothServiceInfoBleak LED_BLE_DISCOVERY_INFO = BluetoothServiceInfoBleak( name="Triones:F30200000152C", address="AA:BB:CC:DD:EE:FF", rssi=-60, manufacturer_data={}, service_uuids=[], service_data={}, source="local", device=BLEDevice(address="AA:BB:CC:DD:EE:FF", name="Triones:F30200000152C"), advertisement=AdvertisementData(), time=0, connectable=True, ) UNSUPPORTED_LED_BLE_DISCOVERY_INFO = BluetoothServiceInfoBleak( name="LEDnetWFF30200000152C", address="AA:BB:CC:DD:EE:FF", rssi=-60, manufacturer_data={}, service_uuids=[], service_data={}, source="local", device=BLEDevice(address="AA:BB:CC:DD:EE:FF", name="LEDnetWFF30200000152C"), advertisement=AdvertisementData(), time=0,
def parse_msg(self, message): if message.member == "InterfacesAdded": msg_path = message.body[0] try: device_interface = message.body[1].get(defs.DEVICE_INTERFACE, {}) except Exception as e: raise e self._devices[msg_path] = ({ **self._devices[msg_path], **device_interface } if msg_path in self._devices else device_interface) elif message.member == "PropertiesChanged": iface, changed, invalidated = message.body if iface != defs.DEVICE_INTERFACE: return msg_path = message.path # the PropertiesChanged signal only sends changed properties, so we # need to get remaining properties from cached_devices. However, we # don't want to add all cached_devices to the devices dict since # they may not actually be nearby or powered on. if msg_path not in self._devices and msg_path in self._cached_devices: self._devices[msg_path] = self._cached_devices[msg_path] self._devices[msg_path] = ({ **self._devices[msg_path], **changed } if msg_path in self._devices else changed) if self._callback is None: return props = self._devices[msg_path] # Get all the information wanted to pack in the advertisement data _local_name = props.get("Name") _manufacturer_data = { k: bytes(v) for k, v in props.get("ManufacturerData", {}).items() } _service_data = { k: bytes(v) for k, v in props.get("ServiceData", {}).items() } _service_uuids = props.get("UUIDs", []) # Pack the advertisement data advertisement_data = AdvertisementData( local_name=_local_name, manufacturer_data=_manufacturer_data, service_data=_service_data, service_uuids=_service_uuids, platform_data=(props, message), ) device = BLEDevice(props["Address"], props["Alias"], props, props.get("RSSI", 0)) self._callback(device, advertisement_data) elif (message.member == "InterfacesRemoved" and message.body[1][0] == defs.BATTERY_INTERFACE): logger.debug("{0}, {1} ({2}): {3}".format(message.member, message.interface, message.path, message.body)) return else: msg_path = message.path logger.debug("{0}, {1} ({2}): {3}".format(message.member, message.interface, message.path, message.body)) logger.debug("{0}, {1} ({2} dBm), Object Path: {3}".format( *_device_info(msg_path, self._devices.get(msg_path))))
async def discover(timeout: float = 5.0, loop: AbstractEventLoop = None, **kwargs) -> List[BLEDevice]: """Perform a Bluetooth LE Scan using Windows.Devices.Bluetooth.Advertisement Args: timeout (float): Time to scan for. loop (Event Loop): The event loop to use. Keyword Args: string_output (bool): If set to false, ``discover`` returns .NET device objects instead. Returns: List of strings or objects found. """ loop = loop if loop else asyncio.get_event_loop() watcher = BluetoothLEAdvertisementWatcher() devices = {} scan_responses = {} def _format_bdaddr(a): return ":".join("{:02X}".format(x) for x in a.to_bytes(6, byteorder="big")) def _format_event_args(e): try: return "{0}: {1}".format( _format_bdaddr(e.BluetoothAddress), e.Advertisement.LocalName or "Unknown", ) except Exception: return e.BluetoothAddress def AdvertisementWatcher_Received(sender, e): if sender == watcher: logger.debug("Received {0}.".format(_format_event_args(e))) if e.AdvertisementType == BluetoothLEAdvertisementType.ScanResponse: if e.BluetoothAddress not in scan_responses: scan_responses[e.BluetoothAddress] = e else: if e.BluetoothAddress not in devices: devices[e.BluetoothAddress] = e def AdvertisementWatcher_Stopped(sender, e): if sender == watcher: logger.debug("{0} devices found. Watcher status: {1}.".format( len(devices), watcher.Status)) watcher.Received += AdvertisementWatcher_Received watcher.Stopped += AdvertisementWatcher_Stopped watcher.ScanningMode = BluetoothLEScanningMode.Active # Watcher works outside of the Python process. watcher.Start() await asyncio.sleep(timeout, loop=loop) watcher.Stop() try: watcher.Received -= AdvertisementWatcher_Received watcher.Stopped -= AdvertisementWatcher_Stopped except Exception as e: logger.debug("Could not remove event handlers: {0}...".format(e)) found = [] for d in devices.values(): bdaddr = _format_bdaddr(d.BluetoothAddress) uuids = [] for u in d.Advertisement.ServiceUuids: uuids.append(u.ToString()) data = {} for m in d.Advertisement.ManufacturerData: md = IBuffer(m.Data) b = Array.CreateInstance(Byte, md.Length) reader = DataReader.FromBuffer(md) reader.ReadBytes(b) data[m.CompanyId] = bytes(b) local_name = d.Advertisement.LocalName if not local_name and d.BluetoothAddress in scan_responses: local_name = scan_responses[ d.BluetoothAddress].Advertisement.LocalName found.append( BLEDevice( bdaddr, local_name, d, uuids=uuids, manufacturer_data=data, )) return found
"""Tests for the BThome integration.""" from bleak.backends.device import BLEDevice from bleak.backends.scanner import AdvertisementData from homeassistant.components.bluetooth import BluetoothServiceInfoBleak TEMP_HUMI_SERVICE_INFO = BluetoothServiceInfoBleak( name="ATC 8D18B2", address="A4:C1:38:8D:18:B2", device=BLEDevice("A4:C1:38:8D:18:B2", None), rssi=-63, manufacturer_data={}, service_data={ "0000181c-0000-1000-8000-00805f9b34fb": b"#\x02\xca\t\x03\x03\xbf\x13" }, service_uuids=["0000181c-0000-1000-8000-00805f9b34fb"], source="local", advertisement=AdvertisementData(local_name="Not it"), time=0, connectable=False, ) TEMP_HUMI_ENCRYPTED_SERVICE_INFO = BluetoothServiceInfoBleak( name="TEST DEVICE 8F80A5", address="54:48:E6:8F:80:A5", device=BLEDevice("54:48:E6:8F:80:A5", None), rssi=-63, manufacturer_data={}, service_data={ "0000181e-0000-1000-8000-00805f9b34fb":
async def discover(timeout=5.0, loop=None, **kwargs): """Discover nearby Bluetooth Low Energy devices. Args: timeout (float): Duration to scan for. loop (asyncio.AbstractEventLoop): Optional event loop to use. Keyword Args: device (str): Bluetooth device to use for discovery. Returns: List of tuples containing name, address and signal strength of nearby devices. """ device = kwargs.get("device", "hci0") loop = loop if loop else asyncio.get_event_loop() cached_devices = {} devices = {} rules = list() def parse_msg(message): if message.member == "InterfacesAdded": msg_path = message.body[0] try: device_interface = message.body[1].get("org.bluez.Device1", {}) except Exception as e: raise e devices[msg_path] = ( {**devices[msg_path], **device_interface} if msg_path in devices else device_interface ) elif message.member == "PropertiesChanged": iface, changed, invalidated = message.body if iface != defs.DEVICE_INTERFACE: return msg_path = message.path # the PropertiesChanged signal only sends changed properties, so we # need to get remaining properties from cached_devices. However, we # don't want to add all cached_devices to the devices dict since # they may not actually be nearby or powered on. if msg_path not in devices and msg_path in cached_devices: devices[msg_path] = cached_devices[msg_path] devices[msg_path] = ( {**devices[msg_path], **changed} if msg_path in devices else changed ) elif ( message.member == "InterfacesRemoved" and message.body[1][0] == defs.BATTERY_INTERFACE ): logger.info( "{0}, {1} ({2}): {3}".format( message.member, message.interface, message.path, message.body ) ) return else: msg_path = message.path logger.info( "{0}, {1} ({2}): {3}".format( message.member, message.interface, message.path, message.body ) ) logger.info( "{0}, {1} ({2} dBm), Object Path: {3}".format( *_device_info(msg_path, devices.get(msg_path)) ) ) bus = await client.connect(reactor, "system").asFuture(loop) # Add signal listeners rules.append( await bus.addMatch( parse_msg, interface="org.freedesktop.DBus.ObjectManager", member="InterfacesAdded", ).asFuture(loop) ) rules.append( await bus.addMatch( parse_msg, interface="org.freedesktop.DBus.ObjectManager", member="InterfacesRemoved", ).asFuture(loop) ) rules.append( await bus.addMatch( parse_msg, interface="org.freedesktop.DBus.Properties", member="PropertiesChanged", ).asFuture(loop) ) # Find the HCI device to use for scanning and get cached device properties objects = await bus.callRemote( "/", "GetManagedObjects", interface=defs.OBJECT_MANAGER_INTERFACE, destination=defs.BLUEZ_SERVICE, ).asFuture(loop) adapter_path, interface = _filter_on_adapter(objects, device) cached_devices = dict(_filter_on_device(objects)) # dd = {'objectPath': '/org/bluez/hci0', 'methodName': 'StartDiscovery', # 'interface': 'org.bluez.Adapter1', 'destination': 'org.bluez', # 'signature': '', 'body': (), 'expectReply': True, 'autoStart': True, # 'timeout': None, 'returnSignature': ''} # Running Discovery loop. await bus.callRemote( adapter_path, "SetDiscoveryFilter", interface="org.bluez.Adapter1", destination="org.bluez", signature="a{sv}", body=[{"Transport": "le"}], ).asFuture(loop) await bus.callRemote( adapter_path, "StartDiscovery", interface="org.bluez.Adapter1", destination="org.bluez", ).asFuture(loop) await asyncio.sleep(timeout) await bus.callRemote( adapter_path, "StopDiscovery", interface="org.bluez.Adapter1", destination="org.bluez", ).asFuture(loop) # Reduce output. # out = [] # for path, props in devices.items(): # properties = await cli.callRemote( # path, 'GetAll', # interface=defs.PROPERTIES_INTERFACE, # destination=defs.BLUEZ_SERVICE, # signature='s', # body=[defs.DEVICE_INTERFACE, ], # returnSignature='a{sv}').asFuture(loop) # print(properties) # discovered_devices = [] for path, props in devices.items(): if not props: logger.debug( "Disregarding %s since no properties could be obtained." % path ) continue name, address, _, path = _device_info(path, props) uuids = props.get("UUIDs", []) manufacturer_data = props.get("ManufacturerData", {}) discovered_devices.append( BLEDevice( address, name, {"path": path, "props": props}, uuids=uuids, manufacturer_data=manufacturer_data, ) ) for rule in rules: await bus.delMatch(rule).asFuture(loop) bus.disconnect() return discovered_devices
async def discover(timeout=5.0, loop=None, **kwargs): """Discover nearby Bluetooth Low Energy devices. Args: timeout (float): Duration to scan for. loop (asyncio.AbstractEventLoop): Optional event loop to use. Keyword Args: device (str): Bluetooth device to use for discovery. Returns: List of tuples containing name, address and signal strength of nearby devices. """ device = kwargs.get("device", "hci0") loop = loop if loop else asyncio.get_event_loop() devices = {} def parse_msg(message): if message.member in ("InterfacesAdded", "InterfacesRemoved"): msg_path = message.body[0] device_interface = message.body[1].get("org.bluez.Device1", {}) devices[msg_path] = { **devices[msg_path], **device_interface } if msg_path in devices else device_interface elif message.member == "PropertiesChanged": iface, changed, invalidated = message.body if iface != defs.DEVICE_INTERFACE: return msg_path = message.path devices[msg_path] = { **devices[msg_path], **changed } if msg_path in devices else changed else: msg_path = message.path logger.info("{0}, {1} ({2}): {3}".format(message.member, message.interface, message.path, message.body)) logger.info("{0}, {1} ({2} dBm), Object Path: {3}".format( *_device_info(msg_path, devices.get(msg_path)))) # Find the HCI device to use for scanning. bus = await client.connect(reactor, "system").asFuture(loop) objects = await bus.callRemote( "/", "GetManagedObjects", interface=defs.OBJECT_MANAGER_INTERFACE, destination=defs.BLUEZ_SERVICE, ).asFuture(loop) adapter_path, interface = _filter_on_adapter(objects, device) # Add signal listeners await bus.addMatch( parse_msg, interface="org.freedesktop.DBus.ObjectManager", member="InterfacesAdded", ).asFuture(loop) await bus.addMatch( parse_msg, interface="org.freedesktop.DBus.ObjectManager", member="InterfacesRemoved", ).asFuture(loop) await bus.addMatch( parse_msg, interface="org.freedesktop.DBus.Properties", member="PropertiesChanged", ).asFuture(loop) await bus.addMatch(parse_msg, interface="org.bluez.Adapter1", member="PropertyChanged").asFuture(loop) # dd = {'objectPath': '/org/bluez/hci0', 'methodName': 'StartDiscovery', # 'interface': 'org.bluez.Adapter1', 'destination': 'org.bluez', # 'signature': '', 'body': (), 'expectReply': True, 'autoStart': True, # 'timeout': None, 'returnSignature': ''} # Running Discovery loop. await bus.callRemote( adapter_path, "StartDiscovery", interface="org.bluez.Adapter1", destination="org.bluez", ).asFuture(loop) await asyncio.sleep(timeout) await bus.callRemote( adapter_path, "StopDiscovery", interface="org.bluez.Adapter1", destination="org.bluez", ).asFuture(loop) # Reduce output. # out = [] # for path, props in devices.items(): # properties = await cli.callRemote( # path, 'GetAll', # interface=defs.PROPERTIES_INTERFACE, # destination=defs.BLUEZ_SERVICE, # signature='s', # body=[defs.DEVICE_INTERFACE, ], # returnSignature='a{sv}').asFuture(loop) # print(properties) # discovered_devices = [] for path, props in devices.items(): name, address, _, path = _device_info(path, props) discovered_devices.append(BLEDevice(address, name, path)) return discovered_devices
async def discover(timeout=5.0, loop=None, **kwargs): """Discover nearby Bluetooth Low Energy devices. For possible values for `filters`, see the parameters to the ``SetDiscoveryFilter`` method in the `BlueZ docs <https://git.kernel.org/pub/scm/bluetooth/bluez.git/tree/doc/adapter-api.txt?h=5.48&id=0d1e3b9c5754022c779da129025d493a198d49cf>`_ The ``Transport`` parameter is always set to ``le`` by default in Bleak. Args: timeout (float): Duration to scan for. loop (asyncio.AbstractEventLoop): Optional event loop to use. Keyword Args: device (str): Bluetooth device to use for discovery. filters (dict): A dict of filters to be applied on discovery. Returns: List of tuples containing name, address and signal strength of nearby devices. """ device = kwargs.get("device", "hci0") loop = loop if loop else asyncio.get_event_loop() cached_devices = {} devices = {} rules = list() reactor = get_reactor(loop) # Discovery filters filters = kwargs.get("filters", {}) filters["Transport"] = "le" def parse_msg(message): if message.member == "InterfacesAdded": msg_path = message.body[0] try: device_interface = message.body[1].get("org.bluez.Device1", {}) except Exception as e: raise e devices[msg_path] = ({ **devices[msg_path], **device_interface } if msg_path in devices else device_interface) elif message.member == "PropertiesChanged": iface, changed, invalidated = message.body if iface != defs.DEVICE_INTERFACE: return msg_path = message.path # the PropertiesChanged signal only sends changed properties, so we # need to get remaining properties from cached_devices. However, we # don't want to add all cached_devices to the devices dict since # they may not actually be nearby or powered on. if msg_path not in devices and msg_path in cached_devices: devices[msg_path] = cached_devices[msg_path] devices[msg_path] = ({ **devices[msg_path], **changed } if msg_path in devices else changed) elif (message.member == "InterfacesRemoved" and message.body[1][0] == defs.BATTERY_INTERFACE): logger.info("{0}, {1} ({2}): {3}".format(message.member, message.interface, message.path, message.body)) return else: msg_path = message.path logger.info("{0}, {1} ({2}): {3}".format(message.member, message.interface, message.path, message.body)) logger.info("{0}, {1} ({2} dBm), Object Path: {3}".format( *_device_info(msg_path, devices.get(msg_path)))) bus = await client.connect(reactor, "system").asFuture(loop) # Add signal listeners rules.append(await bus.addMatch( parse_msg, interface="org.freedesktop.DBus.ObjectManager", member="InterfacesAdded", path_namespace="/org/bluez", ).asFuture(loop)) rules.append(await bus.addMatch( parse_msg, interface="org.freedesktop.DBus.ObjectManager", member="InterfacesRemoved", path_namespace="/org/bluez", ).asFuture(loop)) rules.append(await bus.addMatch( parse_msg, interface="org.freedesktop.DBus.Properties", member="PropertiesChanged", path_namespace="/org/bluez", ).asFuture(loop)) # Find the HCI device to use for scanning and get cached device properties objects = await bus.callRemote( "/", "GetManagedObjects", interface=defs.OBJECT_MANAGER_INTERFACE, destination=defs.BLUEZ_SERVICE, ).asFuture(loop) adapter_path, interface = _filter_on_adapter(objects, device) cached_devices = dict(_filter_on_device(objects)) # Running Discovery loop. await bus.callRemote( adapter_path, "SetDiscoveryFilter", interface="org.bluez.Adapter1", destination="org.bluez", signature="a{sv}", body=[filters], ).asFuture(loop) await bus.callRemote( adapter_path, "StartDiscovery", interface="org.bluez.Adapter1", destination="org.bluez", ).asFuture(loop) await asyncio.sleep(timeout) await bus.callRemote( adapter_path, "StopDiscovery", interface="org.bluez.Adapter1", destination="org.bluez", ).asFuture(loop) # Reduce output. discovered_devices = [] for path, props in devices.items(): if not props: logger.debug( "Disregarding %s since no properties could be obtained." % path) continue name, address, _, path = _device_info(path, props) if address is None: continue uuids = props.get("UUIDs", []) manufacturer_data = props.get("ManufacturerData", {}) discovered_devices.append( BLEDevice( address, name, { "path": path, "props": props }, uuids=uuids, manufacturer_data=manufacturer_data, )) for rule in rules: await bus.delMatch(rule).asFuture(loop) # Try to disconnect the System Bus. try: bus.disconnect() except Exception as e: logger.error("Attempt to disconnect system bus failed: {0}".format(e)) return discovered_devices
"""Tests for the SensorPush integration.""" from bleak.backends.device import BLEDevice from bleak.backends.scanner import AdvertisementData from homeassistant.components.bluetooth import BluetoothServiceInfoBleak NOT_SENSOR_PUSH_SERVICE_INFO = BluetoothServiceInfoBleak( name="Not it", address="00:00:00:00:00:00", device=BLEDevice("00:00:00:00:00:00", None), rssi=-63, manufacturer_data={3234: b"\x00\x01"}, service_data={}, service_uuids=[], source="local", advertisement=AdvertisementData(local_name="Not it"), time=0, connectable=False, ) LYWSDCGQ_SERVICE_INFO = BluetoothServiceInfoBleak( name="LYWSDCGQ", address="58:2D:34:35:93:21", device=BLEDevice("00:00:00:00:00:00", None), rssi=-63, manufacturer_data={}, service_data={ "0000fe95-0000-1000-8000-00805f9b34fb": b"P \xaa\x01\xda!\x9354-X\r\x10\x04\xfe\x00H\x02" },