# Buggy code
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


# Traceback (bleak.exc.BleakError)
/Users/zaytsev/PycharmProjects/bluetooth/venv/bin/python /Users/zaytsev/PycharmProjects/bluetooth/bleak_demo.py
DEBUG:bleak.backends.corebluetooth.CentralManagerDelegate:Bluetooth powered on
DEBUG:bleak.backends.corebluetooth.CentralManagerDelegate:Discovered device A0E49DB2-B7F1-4A65-AB2E-D75121192329: Unknown @ RSSI: -79
A0E49DB2-B7F1-4A65-AB2E-D75121192329: HUAWEI Band 2-b6e
DEBUG:bleak.backends.corebluetooth.client:Connecting to BLE device @ A0E49DB2-B7F1-4A65-AB2E-D75121192329
DEBUG:bleak.backends.corebluetooth.CentralManagerDelegate:Successfully connected to device uuid A0E49DB2-B7F1-4A65-AB2E-D75121192329
WARNING:bleak.backends.corebluetooth.PeripheralDelegate:PeripheralDelegate is not compliant
DEBUG:bleak.backends.corebluetooth.client:Retrieving services...
DEBUG:bleak.backends.corebluetooth.PeripheralDelegate:Services discovered
DEBUG:bleak.backends.corebluetooth.client:Retrieving characteristics for service 180F
DEBUG:bleak.backends.corebluetooth.PeripheralDelegate:Characteristics discovered
DEBUG:bleak.backends.corebluetooth.client:Retrieving descriptors for characteristic 2A19
DEBUG:bleak.backends.corebluetooth.PeripheralDelegate:Descriptor discovered 2A19
DEBUG:bleak.backends.corebluetooth.client:Retrieving characteristics for service 180A
DEBUG:bleak.backends.corebluetooth.PeripheralDelegate:Characteristics discovered
DEBUG:bleak.backends.corebluetooth.client:Retrieving descriptors for characteristic 2A29
DEBUG:bleak.backends.corebluetooth.PeripheralDelegate:Descriptor discovered 2A29
DEBUG:bleak.backends.corebluetooth.client:Retrieving descriptors for characteristic 2A24
DEBUG:bleak.backends.corebluetooth.PeripheralDelegate:Descriptor discovered 2A24
DEBUG:bleak.backends.corebluetooth.client:Retrieving descriptors for characteristic 2A26
DEBUG:bleak.backends.corebluetooth.PeripheralDelegate:Descriptor discovered 2A26
DEBUG:bleak.backends.corebluetooth.client:Retrieving descriptors for characteristic 2A28
DEBUG:bleak.backends.corebluetooth.PeripheralDelegate:Descriptor discovered 2A28
DEBUG:bleak.backends.corebluetooth.client:Retrieving descriptors for characteristic 2A50
DEBUG:bleak.backends.corebluetooth.PeripheralDelegate:Descriptor discovered 2A50
DEBUG:bleak.backends.corebluetooth.client:Retrieving characteristics for service 180D
DEBUG:bleak.backends.corebluetooth.PeripheralDelegate:Characteristics discovered
DEBUG:bleak.backends.corebluetooth.client:Retrieving descriptors for characteristic 2A37
DEBUG:bleak.backends.corebluetooth.PeripheralDelegate:Descriptor discovered 2A37
DEBUG:bleak.backends.corebluetooth.client:Retrieving descriptors for characteristic 2A38
DEBUG:bleak.backends.corebluetooth.PeripheralDelegate:Descriptor discovered 2A38
DEBUG:bleak.backends.corebluetooth.client:Retrieving characteristics for service FE86
DEBUG:bleak.backends.corebluetooth.PeripheralDelegate:Characteristics discovered
DEBUG:bleak.backends.corebluetooth.client:Retrieving descriptors for characteristic FE01
DEBUG:bleak.backends.corebluetooth.PeripheralDelegate:Descriptor discovered FE01
DEBUG:bleak.backends.corebluetooth.client:Retrieving descriptors for characteristic FE02
DEBUG:bleak.backends.corebluetooth.PeripheralDelegate:Descriptor discovered FE02
DEBUG:bleak.backends.corebluetooth.client:Retrieving descriptors for characteristic FE03
DEBUG:bleak.backends.corebluetooth.PeripheralDelegate:Descriptor discovered FE03
DEBUG:bleak.backends.corebluetooth.client:Retrieving descriptors for characteristic FE04
DEBUG:bleak.backends.corebluetooth.PeripheralDelegate:Descriptor discovered FE04
DEBUG:bleak.backends.corebluetooth.client:Retrieving services...
DEBUG:bleak.backends.corebluetooth.client:Retrieving characteristics for service 180F
DEBUG:bleak.backends.corebluetooth.CentralManagerDelegate:Peripheral Device disconnected!
Traceback (most recent call last):
File "/Users/zaytsev/PycharmProjects/bluetooth/bleak_demo.py", line 25, in <module>
loop.run_until_complete(ble_get_services(address, loop))
File "/usr/local/Cellar/python/3.7.4/Frameworks/Python.framework/Versions/3.7/lib/python3.7/asyncio/base_events.py", line 579, in run_until_complete
return future.result()
File "/Users/zaytsev/PycharmProjects/bluetooth/bleak_demo.py", line 18, in ble_get_services
svcs = await client.get_services()
File "/Users/zaytsev/PycharmProjects/bluetooth/venv/lib/python3.7/site-packages/bleak/backends/corebluetooth/client.py", line 118, in get_services
self.services.add_service(BleakGATTServiceCoreBluetooth(service))
File "/Users/zaytsev/PycharmProjects/bluetooth/venv/lib/python3.7/site-packages/bleak/backends/service.py", line 101, in add_service
"This service is already present in this BleakGATTServiceCollection!"
bleak.exc.BleakError: This service is already present in this BleakGATTServiceCollection!

# Fixed code
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)
        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)

    bus.disconnect()

    return discovered_devices