An Overview
Bluetooth Low Energy (BLE) is one of the most popular communication protocols for IoT devices because of its simplicity and power efficiency. BLE is also known as Bluetooth Smart. However, one requires proper management of time-consuming tasks to make BLE communication efficient, especially when dealing with multiple devices. One way to handle this is by using asynchronous programming, and that is where the Bleak library is useful.
In BLE communication, the computer or smartphone typically acts as the central device, connecting to peripherals like sensors.
In this blog, we explore how the Bleak library works, how it leverages asynchronous programming, and why it is a perfect choice for managing BLE communication effectively.
What is the Bleak Library?
Bleak is one of several Python libraries designed for interacting with BLE devices. As a cross platform Python API, Bleak is designed to work seamlessly across Linux, macOS, and Windows. It provides an efficient way to scan, connect, and communicate with BLE devices using Python. The library abstracts the complexities of handling low-level Bluetooth operations, which makes it easier to integrate BLE capabilities into Python applications.
Key features of Bleak:
- Cross Platform: It works on different platforms such as Windows, Linux, and Mac.
- Async Support: It is built with Async and handles BLE communication asynchronously.
- Device Scanning: It allows one to scan and discover nearby BLE devices.
- Device Connection: It helps connect to BLE devices and interact with their characteristics.
- Read and Write Characteristics: It allows reading from and writing to BLE device characteristics.
- Notifications: It enables one to subscribe to BLE characteristics and receive notifications when their values change.
How Does the BLE Connection Work using Bleak?
BLE follows a client-server architecture where:
- Client is typically a device like a smartphone or a computer that communicates with the server (the BLE peripheral).
- Server is the BLE peripheral device, such as a sensor, a fitness tracker, or any other smart gadget.
The BLE communication process follow these stages:
- Scanning for nearby devices: It finds the BLE devices that are nearby and advertising.
- Connecting a device: Once the device is discovered, a connection is established with its GATT (Generic Attribute) server.
- Interacting with the device: Read/Write data to the device’s characteristics (e.g., battery level, temperature, etc.)
- Disconnecting from the device: Once the interaction is finished with a BLE device, it is necessary to disconnect properly to free up resources and ensure that the connection does not remain open unnecessarily.
Scanning for BLE Devices
Before connecting to a BLE device, one needs to discover nearby devices. BLE devices periodically advertise their presence, and one can scan these advertisements to find the devices. In BLE terminology, the device performing the scan acts as a scanner (or observer), detecting advertisements from nearby peripherals. The advertisement contains information like the device name, supported services, and other metadata.
For example:
devices = await discover()logger.info(“Scanning for BLE devices..”)
for device in devices:
logger.info (“Ble device found-” device.name,device.address)asyncio.run(device_scanning)
This example uses bleak.discover() to scan for nearby BLE devices. It returns a list of devices, each with attributes like name, address, metadata, etc.
Connecting to a Device
After the BLE device is discovered, one can initiate a connection with it. This process involves setting up a connection with the GATT server of the device, which is a collection of services and characteristics that represent the data on the device.
Once the device desired is identified (via its MAC address), one can connect to it using its address or name.
async with BleakClient(addr) as client:
print(“Ble device is connected-“, client.is_connected)# We can also perform actions such as reading or writing characteristics, like for example
characteristic_uuid = “XXXXXX-XXXX-XXXX-XXXX-XXXXXXXXX” # Replace with your actual UUID.char_data = await client.read_gatt_char(characteristic_uuid)
print(“Characteristic data-“, char_data)
In the example above, a Bleak Client is used to connect to the device using its address (device_address). The connection is established asynchronously. Once connected, it is easy to read or write characteristics from the BLE device using the read_gatt_char() and write_gatt_char() methods.
Interacting with Services and Characteristics
BLE devices can expose data and capabilities through services and characteristics.
- Service: It is a collection of related characteristics. For example, a health monitoring service.
- Characteristic: It represents a single piece of data, such as a temperature sensor reading.
Each characteristic has a unique identifier known as UUID and can be either read-only, write-only, or read-write.
It is possible to interact with different characteristics on the device after connecting with the device.
data = await client.read_gatt_char(characteristic_uuid)
print(f”Data reading: {data}”)
One might also subscribe to notifications, where the device will send updates to the client automatically when the characteristic value changes.
BLE devices can also send notifications when certain characteristics change. For example, one might want to receive notifications when a device sensor reading changes.
print(“Notification from”, {sender}, “:”, {data})async def sub_notifaction(addr):
async with BleakClient(addr) as client:
characteristic_uuid = “XXXXXX-XXXX-XXXX-XXXX-XXXXXXXXX” # Always replace with the actual UUIDawait client.start_notify(char_uuid, notification_handler)
await asyncio.sleep(15)
await client.stop_notify(char_uuid)asyncio.run(sub_notifcation(addr))
This explains how we can subscribe to a characteristic’s notifications. The notification_handler function will be called whenever the value of the characteristic changes.
The start_notify() function will start to receive notifications for the input characteristic.
Disconnecting from a Device
It is possible to disconnect from the device to free the resources once done.
The async with BleakClient() will ensure that the connection is closed automatically when the block exits. However, if one wants to manually disconnect the device, use: “await client.disconnect()”
What Happens When a Device Suddenly Disconnects?
Sometimes, BLE devices disconnect unexpectedly due to factors like signal interference, power loss on a peripheral, or poor connection quality.
If the connection is lost in Bleak, we get an exception (e.g., BleakError) which we can easily handle in the code to reconnect or attempt another operation.
We can also check the connection status with client.is_connected to ensure we are still connected.
Checking Connection Status
if client.is_connected:
print(“Device is still connected.”)
else:
print(“Device is not connected.”)dev_addr = “your device address”
asyncio.run(check_connection(dev_addr))
Bleak can also be used as follows:
We can also use Bleak to search for or discover services and characteristics even if we are not sure of their UUIDs.
This is immensely helpful when we want to work with personalized and custom built BLE devices.
async with BleakClient(dev_addr) as client:
servs = await client.get_services()
print(“Services and Characteristics:”)
for s in servs:
print(“Services UUID – “, s.uuid)
for char in s.characteristics:
print(“Characteristics UUID – “, char.uuid)loop = asyncio.get_event_loop()
loop.run_until_complete(discover_ble_services())
- get_services() fetches the list of services found by the BLE device.
- service.characteristics shows the characteristics of each service.
This is very handy when we work with any custom BLE firmware or other devices when we do not know the UUIDs specifications.
Summary
With the help of Bleak, we can create many powerful and intelligent Python applications to connect with a variety of BLE devices, including sensors such as heart rate monitors and environmental sensors. BLE communication in Python is widely used for connecting to these Bluetooth devices, enabling data collection and device control in both simple and complex IoT systems.
Python BLE applications are supported on multiple platforms, including Android, iOS, and various Linux distributions. On iOS, proper permissions and usage descriptions must be included in the Info.plist file to access the Core Bluetooth API, while on Windows, BLE features are supported starting from the Fall Creators Update. Bleak and similar libraries are tested for compatibility across these operating systems.
The Bluetooth Special Interest Group defines the standards and reserved UUIDs for BLE protocols, ensuring interoperability between devices. BLE devices acting as GATT servers can be accessed and controlled by Python applications, providing fine-grained control over Bluetooth devices and their services. With a high-level interface for device discovery, connection management, and GATT service interaction (including reading, writing, and notifications), Bleak enables developers to quickly develop reliable and portable software for BLE-enabled devices.





