Bluetooth

Lager makes it simple to connect to and test BLE devices using Bleak. First, find the address of the device to which you wish to connect:

~  lager ble scan
Name                          Address   rssi
Nordic_UART         E4:B6:8E:F3:A0:2E    -31

Then, you can enumerate the services, characteristics, and descriptors with the following program.

ble.py
import asyncio
import sys

from bleak import BleakClient

async def run(address):
    async with BleakClient(address) as client:
        print(f"Connected: {client.is_connected}")
        for service in client.services:
            print(f"[Service] {service}")
            for char in service.characteristics:
                if "read" in char.properties:
                    try:
                        value = bytes(await client.read_gatt_char(char.uuid))
                        print(
                            f"\t[Characteristic] {char} ({','.join(char.properties)}), Value: {value}"
                        )
                    except Exception as e:
                        print(
                            f"\t[Characteristic] {char} ({','.join(char.properties)}), Value: {e}"
                        )
                else:
                    value = None
                    print(
                        f"\t[Characteristic] {char} ({','.join(char.properties)}), Value: {value}"
                    )
                for descriptor in char.descriptors:
                    try:
                        value = bytes(
                            await client.read_gatt_descriptor(descriptor.handle)
                        )
                        print(f"\t\t[Descriptor] {descriptor}) | Value: {value}")
                    except Exception as e:
                        print(f"\t\t[Descriptor] {descriptor}) | Value: {e}")

def main():
    if len(sys.argv) != 2:
        print('Usage: lager python connect.py BLE_ADDRESS', file=sys.stderr)
        raise SystemExit(1)
    ble_address = sys.argv[1]
    loop = asyncio.get_event_loop()
    loop.run_until_complete(run(ble_address))

if __name__ == '__main__':
    main()

Run it with the address of the device you'd like to enumerate:

~  lager python ble.py E4:B6:8E:F3:A0:2E
Connected: True
[Service] 6e400001-b5a3-f393-e0a9-e50e24dcca9e (Handle: 11): Nordic UART Service
   [Characteristic] 6e400003-b5a3-f393-e0a9-e50e24dcca9e (Handle: 14): Nordic UART TX (notify), Value: None
      [Descriptor] 00002902-0000-1000-8000-00805f9b34fb (Handle: 16): Client Characteristic Configuration) | Value: b'\x00\x00'
   [Characteristic] 6e400002-b5a3-f393-e0a9-e50e24dcca9e (Handle: 12): Nordic UART RX (write-without-response,write), Value: None
[Service] 00001801-0000-1000-8000-00805f9b34fb (Handle: 10): Generic Attribute Profile