11.8. Module aioble

Đặc tả Bluetooth Core cung cấp một từ vựng ánh xạ lên hai module MicroPython.

  • bluetooth -- binding cấp thấp tới BLE controller. Đồng bộ, hướng sự kiện thông qua một hàm gọi lại kiểu IRQ, và được cấu trúc xung quanh bộ đệm byte, handle, và các primitive GATT thô. Nó phơi bày giao thức như đúng bản chất của nó, không phải theo cách các ứng dụng Python muốn sử dụng.

  • aioble -- một wrapper cấp cao hơn, được viết bằng Python trên nền bluetooth, chuyển mọi thao tác từ xa thành một coroutine asyncio và mọi đối tượng BLE (service, characteristic, kết nối, kết quả quét, kênh L2CAP) thành một lớp Python tiện dụng. Các lần quét trở thành async iterator; kết nối trở thành async context manager; thông báo trở thành có thể await.

11.8.1. Khi nào nên dùng module cấp thấp hơn

bluetooth vẫn là câu trả lời đúng cho hai trường hợp hẹp:

  • Bạn đang viết loại code mà chính aioble được xây dựng từ đó -- một pattern mới cần kiểm soát ở cấp IRQ đối với giao thức.

  • Bạn đang chạy trên một mục tiêu phần cứng mà package aioble không có sẵn, và một shim mỏng bọc quanh controller là lựa chọn duy nhất.

Với mọi ứng dụng camera, aioble là câu trả lời đúng.

11.8.2. Các thành phần của một chương trình aioble

Mọi ứng dụng dùng aioble đều có một tập hợp nhỏ các thành phần hoạt động, bất kể vai trò nào nó đảm nhận.

  • Một vòng lặp sự kiện asyncio chạy liên tục. Mọi thứ trong aioble đều là coroutine, vì vậy ứng dụng được cấu trúc như một hoặc nhiều task trên một vòng lặp sự kiện duy nhất. Xem Asyncio để biết chi tiết về vòng lặp, task và exception.

  • Radio đang bật. aioble kích hoạt radio BLE ngầm định khi lần đầu sử dụng, nhưng cũng có thể điều khiển tường minh với aioble.config() (chuyển tiếp tới bluetooth.BLE.config() sau khi đảm bảo radio đã hoạt động) và tắt với aioble.stop().

  • Một hoặc nhiều vai trò đang hoạt động cùng lúc. Phía peripheral: một tập service GATT đã đăng ký (xem aioble.register_services()) và một coroutine aioble.advertise() đang chạy. Phía central: một iterator aioble.scan() đang chạy hoặc một aioble.Device.connect() đang chờ. Radio dồn ghép công việc; ứng dụng thấy mỗi vai trò như một task độc lập.

11.8.3. Một peripheral tối giản

Chương trình aioble hữu ích nhỏ nhất -- một peripheral quảng cáo một characteristic chỉ đọc -- rất ngắn gọn:

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

Một central chỉ kết nối và đọc một lần cũng ngắn gọn tương tự:

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

Cả hai chương trình chỉ khoảng mười lăm dòng và chúng bao gồm toàn bộ luồng từ "radio đang tắt" đến "công việc hữu ích đã hoàn thành".

11.8.4. Tắt radio

Trên camera chạy bằng pin, radio BLE là khoản tiêu thụ điện năng tùy ý lớn nhất trong ngân sách. Hai nút điều chỉnh quan trọng.

Nút đầu tiên là ngầm định: aioble kích hoạt radio khi lần đầu sử dụng, và radio tự động ngủ giữa các sự kiện đã lên lịch (burst quảng cáo, sự kiện kết nối, cửa sổ quét). Chọn khoảng thời gian dài hơn cho aioble.advertise() / aioble.scan() và đồng ý khoảng thời gian kết nối dài hơn tại thời điểm connect() giúp radio tắt nhiều hơn theo tỷ lệ. Bảng quảng cáo trong Quảng bá và quét là hướng dẫn thực tế ở đây.

Nút thứ hai là tắt tường minh

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() hủy kích hoạt radio BLE cơ bản và dừng mọi thứ đang hoạt động -- các kết nối đang mở sẽ đứt, scanner và advertiser hủy, kênh L2CAP đóng lại. Các coroutine đang chờ trên các thao tác đó sẽ ném ra các exception thông thường (DeviceDisconnectedError và các loại tương tự), đây là cơ chế dọn dẹp mà các khối async with xung quanh được viết cho. Gọi bất kỳ coroutine aioble nào sau đó sẽ kích hoạt lại radio từ đầu.

Pattern điển hình cho một camera cảm biến chạy pin theo chu kỳ là:

  • Thức dậy theo lịch (bộ định thời, cảm biến chuyển động, nút nhấn).

  • Chạy burst công việc BLE -- quảng cáo, chấp nhận kết nối, đẩy giá trị, ngắt kết nối.

  • Gọi aioble.stop() và ngủ cho đến lần thức dậy tiếp theo.

11.8.5. Những gì aioble không làm

aioble cố tình bao gồm GATT, GAP và L2CAP -- các lớp mà ứng dụng sử dụng. Ba phần nằm ngoài phạm vi:

  • Bất cứ thứ gì dưới lớp liên kết. Lựa chọn kênh, frequency hopping, xác nhận gói tin và mã hóa lớp liên kết đều xảy ra bên trong BLE port và silicon controller; aioble không cung cấp hook ở cấp đó.

  • Bluetooth cổ điển. aioble chỉ hỗ trợ BLE. Các liên kết âm thanh, RFCOMM, A2DP và các tính năng theo profile cổ điển khác không thuộc API.

  • Bluetooth Mesh. Lớp mạng lưới của Bluetooth SIG (một stack riêng biệt trên nền quảng cáo BLE) không được triển khai trên camera. Camera có thể quảng cáo và quan sát, nhưng không thể tham gia vào các vai trò relay/friend/proxy của mạng mesh.

11.8.6. Các exception

Bốn loại exception xuất phát từ aioble. Mỗi loại được kích hoạt từ bên trong một coroutine đang chờ thao tác khi có sự cố xảy ra; các khối async with thoát ra một cách sạch sẽ khi chúng được lan truyền.

  • aioble.DeviceDisconnectedError -- liên kết BLE tới peer bị ngắt trong khi một thao tác GATT (read, write, notified, indicated, subscribe, exchange_mtu, ...) đang thực hiện. Được ném ra bên trong bất kỳ coroutine nào đang chờ. Exception phổ biến nhất; hãy bắt nó trong mọi code cần kết nối lại khi mất kết nối.

  • aioble.GattError -- một thao tác GATT đã đến được peer nhưng hoàn thành với trạng thái ATT khác không (ghi-có-phản-hồi bị từ chối, indicate không được xác nhận, read-not-permitted, ...). Mã trạng thái nằm trên thuộc tính _status của exception.

  • aioble.L2CAPDisconnectedError -- kênh L2CAP bị ngắt trong khi send(), recvinto() hoặc flush() đang thực hiện. Một trong hai bên có thể đã đóng kênh, hoặc kết nối GAP cơ bản đã biến mất.

  • aioble.L2CAPConnectionError -- được ném ra bởi l2cap_connect() khi listener từ chối hoặc controller không thể thiết lập kênh. Mã trạng thái Bluetooth là đối số vị trí đầu tiên.

Các thao tác nhận một timeout_ms tường minh (các lời gọi connect/discovery/read/write/pair, cộng với timeout() như một wrapper) cũng ném ra asyncio.TimeoutError từ asyncio khi thời hạn hết trước khi thao tác hoàn thành.