'Listing connected BLE devices from CPython in Windows
I am porting Python code running on Linux to Windows, and need to implement 2 simple platform-specific functionalities (there are handled via bluetoothctl
calls under Linux):
- listing all currently connected BLE devices
- e.g.
list_connected()
→[('F9:18:AF:E7:9D:40','HeadPhones'),('aa:93:88:1e:03','SmartWatch')]
- e.g.
- force disconnection of a connected device
- e.g.
disconnect('F9:18:AF:E7:9D:40',timeout=2)
- e.g.
The rest of the BLE functionality is already cross-platform thanks to bleak.
I only have little Windows experience, especially with recent APIs. I looked around a bit and undestand that WinRT (Windows Runtime API) is a part of UWP (Universal Windows Platform) and it can be reached from CPython via pythonnet
and winrt
packages; would that be the correct way for implementation?
Thanks for hints.
Solution 1:[1]
Here are the main pieces of the code for listing & disconnecting devices (the full code was sent to OP in email). I did not include exception handling and wrapping from async to sync because that's specific to Python and might be not that important to others.
import winrt.windows.foundation as wf
import winrt.windows.devices.bluetooth as bt
import winrt.windows.devices.enumeration as denum
import asyncio # to support async / await because winrt doesn't have sync versions of BLE functions
# the naming in the Python wrapper looks a bit messy, underscores mixed up with PascalCase
# another caveat is that we have to call the specific override with the second argument `[]`
# otherwise Python winrt wrapper gets confused with multiple versions of find_all_async
dev_infos = await denum.DeviceInformation.find_all_async(bt.BluetoothLEDevice.get_device_selector(), [])
id_name_pairs = []
for dev_info in dev_infos:
# getting the device itself to access it's "connected" status
le_device = await bt.BluetoothLEDevice.from_id_async(dev_info.id);
if le_device is None: # enumerator sometimes has stale devices; this can also happen when you unplug BLE dongle
continue
# in status context, Microsoft calls it "CONNECTED", but later, when disconnecting, it needs "unpair"
# and there is no any "disconnect" function. Microsoft is messy in this regard
if le_device.connection_status == bt.BluetoothConnectionStatus.CONNECTED:
id_name_pairs.append((le_device.device_id, le_device.name));
Important - Microsoft identifies BLE devices by their full IDs instead of BLE addresses. If we want to be able to disconnect a device later, we have to know the ID. If we don't store the ID anywhere and disconnect by address, we'll have to run the enumeration again and find the ID by address.
To easily extract BLE addresses from device IDs:
addressed_devices = list(map(lambda dev: (dev[0][-17:], dev[1]), id_name_pairs))
To disconnect a device by known ID:
le_device = await bt.BluetoothLEDevice.from_id_async(dev_id)
await le_device.device_information.pairing.unpair_async()
Requirements: Windows 10, October 2018 Update or later, Python for Windows, version 3.7 or later, pip, version 19 or later.
Use pip install winrt
to get the Python winrt wrapper library.
To summarise, a task that seems trivial at a first glance, caused some head-scratching because of .NET / Python naming & override differences, and also because of Microsoft's pairing/connection implementation; it's easy to mix up with classic Bluetooth BR/EDR APIs that won't work with BLE pairing information.
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
Solution | Source |
---|---|
Solution 1 | JustAMartin |