11.8. Модуль aioble

Специфікація Bluetooth Core дає словник, що відображається на два модулі MicroPython.

  • bluetoothнизькорівневе прив’язування до BLE-контролера. Синхронне, керується подіями через зворотний виклик у стилі 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-реклами) не реалізовано на камері. Камера може рекламувати і спостерігати, але не може брати участь у ролях ретрансляції/друга/проксі мережі Mesh.

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, коли дедлайн минає до завершення операції.