11.8. Модуль aioble¶
Спецификация Bluetooth Core даёт словарь терминов, который отображается на два модуля MicroPython.
bluetooth– низкоуровневая привязка к контроллеру BLE. Синхронная, управляемая событиями через callback в стиле IRQ и построенная вокруг байтовых буферов, дескрипторов и голых примитивов GATT. Она предоставляет протокол как он есть, а не так, как хотят его использовать приложения на Python.aioble– более высокоуровневая обёртка, написанная на Python поверхbluetooth, которая превращает каждую удалённую операцию в корутинуasyncio, а каждый объект BLE (сервисы, характеристики, подключения, результаты сканирования, каналы L2CAP) – в эргономичный класс Python. Сканирования становятся асинхронными итераторами; подключения становятся асинхронными контекстными менеджерами; уведомления становятся ожидаемыми.
11.8.1. Когда обращаться к низкоуровневому модулю¶
bluetooth по-прежнему остаётся правильным выбором в двух узких случаях:
Вы пишете тот тип кода, из которого построен сам
aioble– новый паттерн, требующий управления протоколом на уровне IRQ.Вы работаете на аппаратной платформе, где пакет
aiobleнедоступен, и тонкая прослойка вокруг контроллера – единственный вариант.
Для любого приложения камеры aioble – правильный выбор.
11.8.2. Составные части программы на aioble¶
Каждое приложение на основе aioble имеет небольшой набор подвижных частей, независимо от того, какие роли оно играет.
Долгоживущий цикл событий
asyncio. Всё вaiobleявляется корутиной, поэтому приложение структурировано как одна или несколько задач в одном цикле событий. Подробнее о цикле, задачах и исключениях см. Asyncio.Включённый радиомодуль.
aiobleнеявно активирует радиомодуль BLE при первом использовании, но им также можно управлять явно с помощьюaioble.config()(которая перенаправляет вызов вbluetooth.BLE.config()после того, как убедится, что радиомодуль включён) и отключать с помощьюaioble.stop().Одна или несколько ролей в работе одновременно. На стороне периферийного устройства: зарегистрированный набор GATT-сервисов (см.
aioble.register_services()) и запущенная корутинаaioble.advertise(). На стороне центрального устройства: запущенный итераторaioble.scan()или ожидающийaioble.Device.connect(). Радиомодуль мультиплексирует работу; приложение видит каждую роль как независимую задачу.
11.8.3. Минимальное периферийное устройство¶
Наименьшая полезная программа на aioble – периферийное устройство, рекламирующее одну характеристику только для чтения – коротка:
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())
Центральное устройство, которое не делает ничего, кроме как подключается и читает один раз, столь же коротко:
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())
Обе программы занимают около пятнадцати строк и охватывают весь процесс от «радиомодуль выключен» до «полезная работа выполнена».
11.8.4. Выключение радиомодуля¶
На камере с питанием от батареи радиомодуль BLE – самый крупный произвольный потребитель в энергобюджете. Важны две регулировки.
Первая неявная: aioble активирует радиомодуль при первом использовании, а радиомодуль автоматически засыпает между запланированными событиями (всплесками рекламы, событиями подключения, окнами сканирования). Выбор более длительных интервалов в aioble.advertise() / aioble.scan() и согласование более длительного интервала подключения во время connect() пропорционально дольше держит радиомодуль выключенным. Таблица рекламы в Реклама и сканирование – практическое руководство по этому вопросу.
Вторая – явное выключение:
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() деактивирует лежащий в основе радиомодуль BLE и разрушает всё, что находится в работе – открытые подключения разрываются, сканеры и рекламодатели отменяются, каналы L2CAP закрываются. Корутины, которые ожидали этих операций, возбуждают свои обычные исключения (DeviceDisconnectedError и аналогичные), что является механизмом очистки, для которого были написаны окружающие блоки async with. Вызов любой корутины aioble после этого снова активирует радиомодуль с нуля.
Типичный паттерн для периодической сенсорной камеры с питанием от батареи таков:
Пробуждение по расписанию (таймер, датчик движения, кнопка).
Выполнение всплеска работы BLE – реклама, приём подключения, отправка значения, отключение.
Вызов
aioble.stop()и сон до следующего пробуждения.
11.8.5. Чего aioble не делает¶
aioble сознательно охватывает GATT, GAP и L2CAP – уровни, которые использует приложение. Три части находятся вне области охвата:
Всё, что ниже канального уровня. Выбор канала, скачкообразная перестройка частоты, подтверждения пакетов и шифрование канального уровня – всё это происходит внутри порта BLE и кремния контроллера;
aiobleне предоставляет хуков на этом уровне.Классический Bluetooth.
aiobleработает только с BLE. Аудиоканалы, RFCOMM, A2DP и другие возможности классических профилей не являются частью API.Bluetooth Mesh. Уровень ячеистой сети Bluetooth SIG (отдельный стек поверх рекламы BLE) не реализован на камере. Камера может рекламировать и наблюдать, но она не может участвовать в ролях ретранслятора / друга / прокси ячеистой сети.
11.8.6. Исключения¶
Из aioble выходят четыре типа исключений. Каждое возникает изнутри корутины, которая ожидала операцию, когда что-то пошло не так; блоки async with корректно сворачиваются при их распространении.
aioble.DeviceDisconnectedError– BLE-канал к пиру был разорван, пока выполнялась операция GATT (read,write,notified,indicated,subscribe,exchange_mtu, …). Возбуждается внутри той корутины, которая ожидала. Безусловно, самое частое исключение; перехватывайте его в любом коде, который должен переподключаться при потере.aioble.GattError– операция GATT достигла пира, но завершилась с ненулевым статусом ATT (запись с ответом отклонена, индикация не подтверждена, чтение не разрешено, …). Код статуса находится в атрибуте_statusисключения.aioble.L2CAPDisconnectedError– канал L2CAP был разорван, пока выполнялисьsend(),recvinto()илиflush(). Канал мог закрыть любая из сторон, либо исчезло лежащее в основе GAP-подключение.aioble.L2CAPConnectionError– возбуждается методомl2cap_connect(), когда слушатель отказал или контроллер не смог настроить канал. Код статуса Bluetooth является первым позиционным аргументом.
Операции, принимающие явный timeout_ms (вызовы connect / discovery / read / write / pair, а также timeout() в качестве обёртки), дополнительно возбуждают asyncio.TimeoutError из asyncio, когда крайний срок истекает до завершения операции.