14.8. The aioble module¶
The Bluetooth Core specification gives a vocabulary that maps onto two MicroPython modules.
bluetooth– the low-level binding to the BLE controller. Synchronous, event-driven through an IRQ-style callback, and structured around byte buffers, handles, and the bare GATT primitives. It exposes the protocol as it is, not as Python applications want to consume it.aioble– a higher-level wrapper, written in Python on top ofbluetooth, that turns every remote operation into anasynciocoroutine and every BLE object (services, characteristics, connections, scan results, L2CAP channels) into an ergonomic Python class. Scans become async iterators; connections become async context managers; notifications become awaitable.
14.8.1. When to reach for the lower-level module¶
bluetooth is still the right answer for two
narrow cases:
You are writing the kind of code
aiobleitself is built out of – a new pattern that needs IRQ-level control over the protocol.You are running on a hardware target where the
aioblepackage is not available, and a thin shim around the controller is the only option.
For every camera application, aioble is the
right answer.
14.8.2. Pieces of an aioble program¶
Every aioble-based application has a small set of
moving parts, regardless of which roles it plays.
A long-running
asyncioevent loop. Everything inaiobleis a coroutine, so the application is structured as one or more tasks on a single event loop. See Asyncio for the loop, tasks, and exceptions in detail.A radio that is on.
aiobleactivates the BLE radio implicitly on first use, but it can also be controlled explicitly withaioble.config()(which forwards tobluetooth.BLE.config()after ensuring the radio is up) and shut down withaioble.stop().One or more roles in flight at once. On the peripheral side: a registered set of GATT services (see
aioble.register_services()) and a runningaioble.advertise()coroutine. On the central side: a runningaioble.scan()iterator or a pendingaioble.Device.connect(). The radio multiplexes the work; the application sees each role as an independent task.
14.8.3. A minimal peripheral¶
The smallest useful aioble program – a
peripheral that advertises a single read-only
characteristic – is short:
import aioble
import asyncio
import bluetooth
SERVICE_UUID = bluetooth.UUID(0x181A) # Environmental Sensing
TEMP_UUID = bluetooth.UUID(0x2A6E) # Temperature
service = aioble.Service(SERVICE_UUID)
temp = aioble.Characteristic(service, TEMP_UUID, read=True)
aioble.register_services(service)
async def main():
while True:
conn = await aioble.advertise(
interval_us=250000,
name="openmv-temp",
services=[SERVICE_UUID],
)
async with conn:
await conn.disconnected()
asyncio.run(main())
A central that does nothing more than connect and read once is similarly short:
import aioble
import asyncio
import bluetooth
SERVICE_UUID = bluetooth.UUID(0x181A)
TEMP_UUID = bluetooth.UUID(0x2A6E)
async def main():
device = None
async with aioble.scan(duration_ms=5000, active=True) as scanner:
async for result in scanner:
if SERVICE_UUID in result.services():
device = result.device
break
if device is None:
return
async with await device.connect() as conn:
service = await conn.service(SERVICE_UUID)
char = await service.characteristic(TEMP_UUID)
print(await char.read())
asyncio.run(main())
Both programs are about fifteen lines and they cover the whole flow from “radio is off” to “useful work done”.
14.8.4. Turning the radio off¶
On a battery-powered camera the BLE radio is the biggest discretionary draw on the budget. Two knobs matter.
The first is implicit: aioble activates the
radio on first use, and the radio sleeps between
scheduled events (advertising bursts, connection
events, scan windows) automatically. Picking longer
intervals on
aioble.advertise() / aioble.scan() and
agreeing on a longer connection interval at
connect() time keeps the radio
off proportionally more of the time. The advertising
table in
Advertising and scanning
is the practical guide here.
The second is explicit shutdown:
import aioble
await do_burst_of_ble_work()
aioble.stop() # radio deactivated; in-flight tasks unwound
await asyncio.sleep(60) # sleep with the radio off
# ... next aioble call brings the radio back up automatically
aioble.stop() deactivates the underlying BLE
radio and tears down anything in flight – open
connections drop, scanners and advertisers cancel,
L2CAP channels close. Coroutines that were waiting on
those operations raise their usual exceptions
(DeviceDisconnectedError and friends),
which is the cleanup mechanism the surrounding
async with blocks were written for. Calling any
aioble coroutine afterwards activates the
radio again from cold.
The typical pattern for a periodic battery-powered sensor cam is:
Wake on a schedule (timer, motion sensor, button).
Run the burst of BLE work – advertise, accept a connection, push the value, disconnect.
Call
aioble.stop()and sleep until the next wake.
14.8.5. What aioble does not do¶
aioble deliberately covers GATT, GAP, and L2CAP
– the layers an application uses. Three pieces are
out of scope:
Anything below the link layer. Channel selection, frequency hopping, packet acknowledgements, and link-layer encryption all happen inside the BLE port and the controller silicon;
aiobledoes not expose hooks at that level.Classic Bluetooth.
aiobleis BLE-only. Audio links, RFCOMM, A2DP, and other classic-profile features are not part of the API.Bluetooth Mesh. The Bluetooth SIG’s mesh networking layer (a separate stack on top of BLE advertising) is not implemented on the camera. The cam can advertise and observe, but it cannot participate in a mesh network’s relay / friend / proxy roles.
14.8.6. Exceptions¶
Four exception types come out of aioble. Each
fires from inside a coroutine that was awaiting an
operation when something went wrong; async with
blocks unwind cleanly when they propagate.
aioble.DeviceDisconnectedError– the BLE link to the peer dropped while a GATT operation (read,write,notified,indicated,subscribe,exchange_mtu, …) was in flight. Raised inside whichever coroutine was waiting. The most common exception by far; catch it in any code that should reconnect on loss.aioble.GattError– a GATT operation reached the peer but completed with a non-zero ATT status (write-with-response rejected, indicate not acknowledged, read-not-permitted, …). The status code is on the exception’s_statusattribute.aioble.L2CAPDisconnectedError– the L2CAP channel dropped while asend(),recvinto(), orflush()was in flight. Either side may have closed the channel, or the underlying GAP connection went away.aioble.L2CAPConnectionError– raised byl2cap_connect()when the listener refused or the controller failed the channel setup. The Bluetooth status code is the first positional argument.
Operations that take an explicit timeout_ms (the
connect / discovery / read / write / pair calls, plus
timeout() as a wrapper)
additionally raise asyncio.TimeoutError from
asyncio when the deadline elapses before the
operation completes.