11.4. Quảng bá và quét

Hai thiết bị BLE chưa từng gặp nhau phải tìm thấy nhau trước. Mạng giải quyết điều đó bằng cách cấp cho mỗi thiết bị một địa chỉ từ pool chung và cho phép bất kỳ bên nào tiếp cận bên kia qua router. BLE không có router, không có pool chung, và -- giữa hầu hết các cặp thiết bị -- không có mối quan hệ nào trước đó cả. Generic Access Profile (GAP) giải quyết việc phát hiện bằng mô hình broadcast-và-lắng nghe. Một bên quảng bá -- nó truyền một gói tin ngắn trên ba kênh quảng bá ở khoảng thời gian đều đặn, mô tả nó là ai. Bên kia quét -- nó quét trên ba kênh tương tự để lắng nghe các gói tin đó.

GAP định nghĩa bốn vai trò xung quanh mô hình đó, mỗi vai trò là sự kết hợp cụ thể của quảng bá và lắng nghe.

11.4.1. Bốn vai trò GAP

A two-by-two matrix. Rows are labelled "advertises" and "does not advertise". Columns are labelled "accepts connections" and "does not accept connections". The four cells contain the role names: Peripheral, Broadcaster, Central, Observer.

Bốn vai trò GAP. Trục dọc là thiết bị có quảng bá hay không; trục ngang là thiết bị có chấp nhận (hoặc khởi tạo) kết nối hay không.

  • Một peripheral quảng bá các gói tin cho biết "Tôi ở đây và bạn có thể kết nối với tôi". Khi một thiết bị khác mở kết nối, peripheral dừng quảng bá và bắt đầu phục vụ các yêu cầu GATT. Đai đo nhịp tim, nhiệt kế, và hầu hết camera-dưới-dạng-cảm biến đóng vai trò peripheral.

  • Một central quét tìm peripheral, chọn một trong số chúng, và khởi tạo kết nối. Sau khi kết nối, nó giao tiếp GATT với tư cách client. Điện thoại, máy tính xách tay, và camera đóng vai trò thu thập dữ liệu là các central.

  • Một broadcaster quảng bá nhưng không bao giờ chấp nhận kết nối. Payload quảng bá của nó chính là dữ liệu -- không có gì để kết nối vào. iBeacon và hầu hết beacon hiện diện cửa hàng là broadcaster.

  • Một observer quét tìm các quảng bá đó và đọc payload, cũng không bao giờ kết nối. Camera lắng nghe beacon gần đó và hành động theo những gì nghe được là một observer.

Một thiết bị đơn có thể đóng nhiều vai trò cùng một lúc -- camera có thể vừa là peripheral phát trạng thái của mình vừa là central kết nối với cảm biến gần đó. Radio ghép kênh công việc đó.

11.4.2. Nội dung của một gói tin quảng bá

Gói tin quảng bá rất nhỏ: 31 byte payload, hoặc 62 byte nếu advertiser cũng phát scan response mà scanner có thể yêu cầu ngay. Payload là danh sách các trường có kiểu ngắn:

  • Flags. Có thể kết nối hay không, có thể phát hiện chung/giới hạn.

  • Tên cục bộ. Một chuỗi ngắn, thân thiện với người dùng -- tên mà hệ điều hành trên điện thoại hoặc máy tính xách tay hiển thị trong menu Bluetooth.

  • UUID dịch vụ. Danh sách định danh dịch vụ GATT mà thiết bị lưu trữ, để scanner có thể nhận ra peripheral phù hợp mà không cần kết nối trước. Đai đo nhịp tim quảng bá 0x180D -- UUID dịch vụ Heart-Rate chuẩn -- và ứng dụng đo nhịp tim trên điện thoại biết từ đó rằng thiết bị đáng kết nối.

  • Appearance. Một giá trị 16-bit từ danh sách số được gán của Bluetooth (cảm biến, media chung, đồng hồ chung, ...) -- gợi ý cho central về những gì cần hiển thị.

  • Dữ liệu đặc thù nhà sản xuất. Byte tự do được tiền tố với ID công ty. iBeacon dùng trường này để mang UUID, major và minor của chúng; ứng dụng tùy chỉnh có thể đặt bất cứ thứ gì vào đây.

Payload quảng bá rất chật hẹp. Giới hạn 31 byte khiến việc chọn nội dung đưa vào trở thành quyết định thiết kế thực sự -- một tên dài đọc được bởi người dùng có thể nhanh chóng không còn chỗ cho UUID dịch vụ. API aioble.advertise() nhận mỗi mục này như một đối số từ khóa và tự động hợp thành các byte, tự động tràn sang scan response nếu gói tin chính bị đầy.

11.4.3. Quét chủ động và thụ động

Scanner có thể chạy ở chế độ thụ động, nơi nó chỉ lắng nghe các gói tin quảng bá và phân tích những gì đến, hoặc chủ động, nơi nó cũng gửi scan request đến từng advertiser và phân tích scan response trả về.

Quét thụ động chỉ thấy gói tin quảng bá ban đầu (tối đa 31 byte). Quét chủ động tăng gấp đôi -- scan response là thêm 31 byte peripheral có thể dùng cho các trường không vừa. Quét chủ động cũng tốn điện cho cả hai phía, vì scanner truyền và advertiser truyền một gói tin thêm, nên đây là lựa chọn chứ không phải mặc định.

Trong API aioble, active=True trên aioble.scan() chuyển chế độ, và mỗi ScanResult hiển thị adv_dataresp_data kết hợp cũng như các helper như result.name()result.services() che giấu việc phân tích cú pháp ở cấp byte.

Ghi chú

Thuộc tính adv_dataresp_data là payload quảng bá và scan-response thô (bytes). Các helper -- name(), services(), manufacturer() -- bao gồm các trường chuẩn phổ biến và là lựa chọn đúng đắn 99% trường hợp. Chỉ dùng đến byte thô khi bạn cần trường nhà sản xuất mà helper không phân tích (URL Eddystone, UUID/major/minor iBeacon, kiểu quảng bá tùy chỉnh). Bố cục byte là TLV chuẩn: mỗi trường gồm length, type, value....

11.4.4. Khoảng thời gian quảng bá

Tần suất peripheral phát quảng bá là sự đánh đổi giữa điện năng và độ trễ phát hiện. Quảng bá phát mỗi 20 ms được scanner phát hiện gần như ngay lập tức nhưng giữ radio bận và làm cạn pin; quảng bá mỗi giây hầu như không tốn điện nhưng khiến scanner quét chậm hơn để nhận ra thiết bị.

interval_us trên aioble.advertise() đặt khoảng thời gian theo microsecond:

  • 20.000 đến 100.000 us (20 ms - 100 ms) -- ghép đôi nhanh, ứng dụng kỳ vọng phản hồi nhanh, thiết bị cắm điện.

  • 250.000 đến 1.000.000 us (250 ms - 1 s) -- mặc định hợp lý cho peripheral chạy pin muốn được phát hiện mà không tốn quá nhiều điện.

  • Trên 1.000.000 us -- phát quảng bá nền chậm, beacon gửi cập nhật vị trí mỗi vài giây.

Phía scanner cũng có các tham số riêng -- aioble.scan() nhận interval_uswindow_us (tần suất scanner bật radio và thời gian lắng nghe mỗi lần). Các giá trị mặc định là ổn; thay đổi phổ biến duy nhất là đặt cả hai bằng nhau để quét liên tục khi không lo về pin.

11.4.5. Mô hình không kết nối -- broadcaster và observer

Các trang về Hoạt động như một thiết bị ngoại viHoạt động như một central đi qua hình dạng có thể kết nối của API -- nơi peripheral chấp nhận kết nối và hai bên trao đổi dữ liệu qua GATT. Hình dạng kia là không kết nối: broadcaster truyền payload-dưới-dạng-quảng bá, và bất kỳ observer nào trong tầm thu đều có thể đọc mà không bao giờ kết nối. Beacon, cảm biến hiện diện, và telemetry một chiều đều thuộc đây.

Broadcaster là aioble.advertise() với connectable=False. Dữ liệu đặc thù nhà sản xuất mang payload:

import aioble
import asyncio
import struct

_COMPANY_ID = const(0xFFFF)                # 0xFFFF is "no specific vendor"

async def beacon():
    seq = 0
    while True:
        seq = (seq + 1) & 0xFFFF
        payload = struct.pack("<H", seq)
        await aioble.advertise(
            interval_us=500000,
            connectable=False,
            name="openmv-beacon",
            manufacturer=(_COMPANY_ID, payload),
            timeout_ms=1000,                # one cycle, then loop
        )

asyncio.run(beacon())

Từ khóa timeout_ms kết thúc lệnh gọi advertise sau một giây; vòng lặp ngoài phát lại với số thứ tự tiếp theo để người nghe thấy dữ liệu mới. Cờ connectable=False là thứ làm cho quảng bá theo kiểu broadcaster -- cam sẽ không phản hồi yêu cầu kết nối ngay cả khi có yêu cầu đến.

Observer là scanner chỉ đọc tương ứng. Nó chạy aioble.scan() mãi mãi, phân tích quảng bá đến, và không bao giờ gọi connect()

import aioble
import asyncio

_COMPANY_ID = const(0xFFFF)

async def watch():
    async with aioble.scan(duration_ms=0, active=False) as scanner:
        async for result in scanner:
            for company, data in result.manufacturer(filter=_COMPANY_ID):
                print(result.device.addr_hex(),
                      "rssi", result.rssi, "data", data)

asyncio.run(watch())

duration_ms=0 quét cho đến khi context manager thoát; active=False giữ radio của observer im lặng (không gửi scan-response request) để tiêu thụ điện thấp nhất. Đối số filter= trên manufacturer() loại bỏ mọi quảng bá không khớp với ID công ty, nên vòng lặp chỉ kích hoạt với lưu lượng của broadcaster.

11.4.6. Từ phát hiện đến kết nối

Khi central chọn một peripheral để nói chuyện, nó dừng lắng nghe, gửi yêu cầu kết nối trên kênh quảng bá peripheral đã dùng lần cuối, và cả hai bên chuyển vào các kênh dữ liệu nhảy tần của link layer. Peripheral thường dừng quảng bá tại thời điểm này. Những gì xảy ra tiếp theo -- tham số kết nối, khám phá GATT, vòng đời của liên kết -- nằm ở Kết nối.