11.8. Moduł aioble

Specyfikacja Bluetooth Core dostarcza słownictwa, które odwzorowuje się na dwa moduły MicroPython.

  • bluetoothniskopoziomowe powiązanie z kontrolerem BLE. Synchroniczne, sterowane zdarzeniami poprzez wywołanie zwrotne w stylu IRQ, zbudowane wokół buforów bajtowych, uchwytów i nagich prymitywów GATT. Eksponuje protokół takim, jaki jest, a nie takim, jakim chcą go konsumować aplikacje Python.

  • aioble – wyżej poziomowa nakładka, napisana w Pythonie na bazie bluetooth, która zamienia każdą zdalną operację w korutynę asyncio, a każdy obiekt BLE (usługi, charakterystyki, połączenia, wyniki skanowania, kanały L2CAP) w ergonomiczną klasę Pythona. Skanowania stają się asynchronicznymi iteratorami; połączenia stają się asynchronicznymi menedżerami kontekstu; powiadomienia stają się oczekiwalne.

11.8.1. Kiedy sięgnąć po moduł niższego poziomu

bluetooth jest nadal właściwym wyborem w dwóch wąskich przypadkach:

  • Piszesz rodzaj kodu, z którego zbudowany jest sam aioble – nowy wzorzec wymagający kontroli nad protokołem na poziomie IRQ.

  • Działasz na docelowym sprzęcie, na którym pakiet aioble nie jest dostępny, a cienka warstwa pośrednicząca wokół kontrolera jest jedyną opcją.

Dla każdej aplikacji kamery właściwą odpowiedzią jest aioble.

11.8.2. Elementy programu aioble

Każda aplikacja oparta na aioble ma niewielki zestaw ruchomych części, niezależnie od tego, jakie role pełni.

  • Długo działająca pętla zdarzeń asyncio. Wszystko w aioble jest korutyną, więc aplikacja jest zbudowana jako jedno lub więcej zadań na pojedynczej pętli zdarzeń. Szczegółowe informacje o pętli, zadaniach i wyjątkach znajdziesz w Asyncio.

  • Włączone radio. aioble aktywuje radio BLE niejawnie przy pierwszym użyciu, ale można je też kontrolować jawnie za pomocą aioble.config() (która przekazuje wywołanie do bluetooth.BLE.config() po upewnieniu się, że radio jest włączone) i wyłączyć za pomocą aioble.stop().

  • Jedna lub więcej ról działających jednocześnie. Po stronie urządzenia peryferyjnego: zarejestrowany zestaw usług GATT (zobacz aioble.register_services()) i działająca korutyna aioble.advertise(). Po stronie urządzenia centralnego: działający iterator aioble.scan() lub oczekujące aioble.Device.connect(). Radio multipleksuje pracę; aplikacja widzi każdą rolę jako niezależne zadanie.

11.8.3. Minimalne urządzenie peryferyjne

Najmniejszy użyteczny program aioble – urządzenie peryferyjne rozgłaszające pojedynczą charakterystykę tylko do odczytu – jest krótki:

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())

Urządzenie centralne, które nie robi nic więcej niż połączenie i jednorazowy odczyt, jest podobnie krótkie:

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())

Oba programy mają około piętnastu linii i obejmują cały przepływ od „radio jest wyłączone” do „użyteczna praca wykonana”.

11.8.4. Wyłączanie radia

W kamerze zasilanej z baterii radio BLE jest największym uznaniowym obciążeniem budżetu. Znaczenie mają dwa pokrętła.

Pierwsze jest niejawne: aioble aktywuje radio przy pierwszym użyciu, a radio automatycznie usypia pomiędzy zaplanowanymi zdarzeniami (impulsami rozgłaszania, zdarzeniami połączeń, oknami skanowania). Wybranie dłuższych interwałów w aioble.advertise() / aioble.scan() oraz uzgodnienie dłuższego interwału połączenia w momencie connect() proporcjonalnie zwiększa czas wyłączenia radia. Praktycznym przewodnikiem jest tu tabela rozgłaszania w Rozgłaszanie i skanowanie.

Drugim jest jawne wyłączenie:

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() dezaktywuje leżące pod spodem radio BLE i likwiduje wszystko, co jest w trakcie – otwarte połączenia są zrywane, skanery i nadajniki rozgłaszające są anulowane, kanały L2CAP zamykane. Korutyny, które oczekiwały na te operacje, zgłaszają swoje zwykłe wyjątki (DeviceDisconnectedError i pokrewne), co jest mechanizmem sprzątania, dla którego napisano otaczające bloki async with. Wywołanie dowolnej korutyny aioble później ponownie aktywuje radio od zimnego stanu.

Typowy wzorzec dla okresowej, zasilanej z baterii kamery sensorowej to:

  • Wybudzenie według harmonogramu (licznik czasu (timer), sensor ruchu, przycisk).

  • Wykonanie serii pracy BLE – rozgłaszanie, przyjęcie połączenia, wypchnięcie wartości, rozłączenie.

  • Wywołanie aioble.stop() i uśpienie do następnego wybudzenia.

11.8.5. Czego aioble nie robi

aioble celowo obejmuje GATT, GAP i L2CAP – warstwy, których używa aplikacja. Trzy elementy są poza zakresem:

  • Cokolwiek poniżej warstwy łącza. Wybór kanału, przeskakiwanie częstotliwości, potwierdzenia pakietów i szyfrowanie warstwy łącza dzieją się wewnątrz portu BLE i krzemu kontrolera; aioble nie eksponuje haków na tym poziomie.

  • Klasyczny Bluetooth. aioble obsługuje wyłącznie BLE. Łącza audio, RFCOMM, A2DP i inne funkcje profili klasycznych nie są częścią tego API.

  • Bluetooth Mesh. Warstwa sieci kratowej Bluetooth SIG (osobny stos na wierzchu rozgłaszania BLE) nie jest zaimplementowana na kamerze. Kamera może rozgłaszać i obserwować, ale nie może uczestniczyć w rolach przekaźnika / przyjaciela / proxy sieci kratowej.

11.8.6. Wyjątki

Z aioble wychodzą cztery typy wyjątków. Każdy jest zgłaszany z wnętrza korutyny, która oczekiwała na operację, gdy coś poszło nie tak; bloki async with rozwijają się czysto, gdy się propagują.

  • aioble.DeviceDisconnectedError – łącze BLE do partnera zostało zerwane podczas trwania operacji GATT (read, write, notified, indicated, subscribe, exchange_mtu, …). Zgłaszany wewnątrz tej korutyny, która oczekiwała. Zdecydowanie najczęstszy wyjątek; przechwytuj go w każdym kodzie, który powinien wznawiać połączenie po jego utracie.

  • aioble.GattError – operacja GATT dotarła do partnera, ale zakończyła się niezerowym statusem ATT (zapis z odpowiedzią odrzucony, wskazanie niepotwierdzone, odczyt niedozwolony, …). Kod statusu znajduje się w atrybucie _status wyjątku.

  • aioble.L2CAPDisconnectedError – kanał L2CAP został zerwany podczas trwania send(), recvinto() lub flush(). Kanał mogła zamknąć którakolwiek ze stron lub zniknęło leżące pod spodem połączenie GAP.

  • aioble.L2CAPConnectionError – zgłaszany przez l2cap_connect(), gdy strona nasłuchująca odmówiła lub kontroler nie zdołał skonfigurować kanału. Kod statusu Bluetooth jest pierwszym argumentem pozycyjnym.

Operacje, które przyjmują jawne timeout_ms (wywołania connect / discovery / read / write / pair, a także timeout() jako nakładka), dodatkowo zgłaszają asyncio.TimeoutError z asyncio, gdy termin upłynie przed zakończeniem operacji.