11.4. Rozgłaszanie i skanowanie¶
Dwa urządzenia BLE, które nigdy wcześniej się nie spotkały, muszą się najpierw odnaleźć. Sieć rozwiązuje to, przydzielając każdemu urządzeniu adres ze wspólnej puli i pozwalając każdej ze stron dotrzeć do drugiej przez routery. BLE nie ma routerów, nie ma wspólnej puli i – między większością par urządzeń – nie ma w ogóle żadnej wcześniejszej relacji. Generic Access Profile (GAP) rozwiązuje wykrywanie wzorcem rozgłaszania i nasłuchiwania. Jedna strona rozgłasza – transmituje krótki pakiet na trzech kanałach rozgłoszeniowych w regularnych odstępach, opisując, kim jest. Druga strona skanuje – przeczesuje te same trzy kanały, nasłuchując tych pakietów.
GAP definiuje wokół tego wzorca cztery role, każda będąca określoną kombinacją rozgłaszania i nasłuchiwania.
11.4.1. Cztery role GAP¶
Cztery role GAP. Oś pionowa to to, czy urządzenie rozgłasza; oś pozioma to to, czy akceptuje (lub inicjuje) połączenia.¶
Peripheral rozgłasza pakiety mówiące „jestem tutaj i możesz się ze mną połączyć”. Gdy inne urządzenie otwiera połączenie, peripheral przestaje rozgłaszać i zaczyna obsługiwać żądania GATT. Pasy do pomiaru tętna, termometry i większość kamer pełniących rolę czujników działają jako peripheral.
Central skanuje w poszukiwaniu peripheral, wybiera jeden i inicjuje połączenie. Po połączeniu komunikuje się przez GATT jako klient. Telefony, laptopy i kamery działające jako kolektory danych są central.
Broadcaster rozgłasza, ale nigdy nie akceptuje połączeń. Jego ładunek rozgłoszeniowy jest danymi – nie ma z czym się łączyć. iBeacons i większość beaconów wykrywających obecność w sklepach to broadcaster.
Observer skanuje w poszukiwaniu tych rozgłoszeń i odczytuje ładunek, również nigdy się nie łącząc. Kamera, która nasłuchuje pobliskich beaconów i reaguje na to, co usłyszy, jest observer.
Pojedyncze urządzenie może pełnić więcej niż jedną rolę jednocześnie – kamera może być peripheral publikującym własny stan oraz central łączącym się z pobliskim czujnikiem. Radio multipleksuje pracę.
11.4.2. Co zawiera pakiet rozgłoszeniowy¶
Pakiet rozgłoszeniowy jest mały: 31 bajtów ładunku lub 62, jeśli rozgłaszający publikuje również scan response, o który skanery mogą poprosić na żądanie. Ładunek to lista krótkich, typowanych pól:
Flags. Możliwość połączenia lub jej brak, ogólna / ograniczona wykrywalność.
Local name. Krótki, przyjazny dla człowieka ciąg znaków – nazwa, którą system operacyjny telefonu lub laptopa wyświetla w swoim menu Bluetooth.
Service UUIDs. Lista identyfikatorów usług GATT, które urządzenie hostuje, dzięki czemu skaner może rozpoznać odpowiednie peripheral bez wcześniejszego łączenia się. Pas do pomiaru tętna rozgłasza
0x180D– standardowy UUID usługi Heart-Rate – a aplikacja do pomiaru tętna na telefonie wie już z samego tego, że warto się z urządzeniem połączyć.Appearance. 16-bitowa wartość z listy przypisanych numerów Bluetooth (czujnik, ogólne media, ogólny zegarek, …) – wskazówka dla central, co wyświetlić.
Manufacturer-specific data. Dowolne bajty poprzedzone identyfikatorem firmy. iBeacons używają tego pola do przenoszenia swojego UUID, major i minor; aplikacje niestandardowe mogą umieścić tu, co tylko zechcą.
Ładunki rozgłoszeniowe są ciasne. Limit 31 bajtów sprawia, że wybór, co uwzględnić, to realna decyzja projektowa – długa, czytelna dla człowieka nazwa może szybko nie zostawić miejsca na UUID usług. API aioble.advertise() przyjmuje każde z tych pól jako argument nazwany i składa bajty za Ciebie, automatycznie przelewając nadmiar do scan response, jeśli główny pakiet się zapełni.
11.4.3. Skanowanie aktywne i pasywne¶
Skaner może działać pasywnie, gdy nasłuchuje pakietów rozgłoszeniowych i parsuje to, co napłynie, lub aktywnie, gdy dodatkowo wysyła scan request do każdego rozgłaszającego i parsuje zwrócony scan response.
Skanowanie pasywne widzi tylko początkowy pakiet rozgłoszeniowy (do 31 bajtów). Skanowanie aktywne podwaja tę wartość – scan response to kolejne 31 bajtów, które peripheral może wykorzystać na pola, które się nie zmieściły. Skanowanie aktywne kosztuje też energię po obu stronach, ponieważ skaner transmituje, a rozgłaszający transmituje dodatkowy pakiet, więc jest to wybór, a nie ustawienie domyślne.
W API aioble ustawienie active=True w aioble.scan() przełącza tryb, a każdy ScanResult udostępnia połączone adv_data plus resp_data, a także funkcje pomocnicze takie jak result.name() i result.services(), które ukrywają parsowanie na poziomie bajtów.
Informacja
Atrybuty adv_data i resp_data to surowe ładunki rozgłoszeniowe i scan response (bytes). Funkcje pomocnicze – name(), services(), manufacturer() – obejmują typowe standardowe pola i są właściwym wyborem w 99% przypadków. Po surowe bajty sięgaj tylko wtedy, gdy potrzebujesz pola producenta, którego funkcje pomocnicze nie parsują (adresy URL Eddystone, UUID/major/minor iBeacon, niestandardowe typy rozgłoszeń). Układ bajtów jest standardowy TLV: każde pole to length, type, value....
11.4.4. Interwał rozgłaszania¶
To, jak często peripheral rozgłasza, jest kompromisem między mocą a opóźnieniem wykrywania. Rozgłoszenia wysyłane co 20 ms są wychwytywane przez skaner niemal natychmiast, ale utrzymują radio zajęte i rozładowują baterię; rozgłoszenia co sekundę zużywają niemal zero energii, ale sprawiają, że skaner wolniej zauważa urządzenie.
interval_us w aioble.advertise() ustawia interwał w mikrosekundach:
20 000 do 100 000 us (20 ms - 100 ms) – szybkie parowanie, aplikacja oczekuje szybkiej odpowiedzi, urządzenie podłączone do zasilania.
250 000 do 1 000 000 us (250 ms - 1 s) – rozsądna wartość domyślna dla peripheral zasilanego bateryjnie, który chce być wykrywalny bez nadmiernego zużywania energii.
Powyżej 1 000 000 us – powolne rozgłaszanie w tle, beacony wysyłające aktualizację pozycji co kilka sekund.
Strona skanera ma własne pokrętła – aioble.scan() przyjmuje interval_us i window_us (jak często skaner budzi swoje radio i jak długo nasłuchuje za każdym razem). Wartości domyślne są w porządku; jedyna typowa zmiana to ustawienie obu równych dla ciągłego skanowania, gdy bateria nie jest problemem.
11.4.5. Wzorce bezpołączeniowe – broadcaster i observer¶
Strony Działanie jako urządzenie peryferyjne i Działanie jako central omawiają połączeniową postać API – gdzie peripheral akceptuje połączenie, a obie strony wymieniają dane przez GATT. Druga postać jest bezpołączeniowa: broadcaster transmituje ładunek jako rozgłoszenie, a dowolny observer w zasięgu może go odczytać, nigdy się nie łącząc. Beacony, czujniki obecności i jednokierunkowa telemetria należą właśnie tutaj.
Broadcaster to aioble.advertise() z connectable=False. Dane specyficzne dla producenta przenoszą ładunek:
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())
Słowo kluczowe timeout_ms kończy wywołanie advertise po sekundzie; pętla zewnętrzna ponawia je z kolejnym numerem sekwencji, dzięki czemu nasłuchujący widzą świeże dane. Flaga connectable=False jest tym, co nadaje rozgłoszeniu charakter broadcaster – kamera nie odpowie na żądanie połączenia, nawet jeśli takie nadejdzie.
Observer to odpowiadający mu skaner tylko do odczytu. Uruchamia aioble.scan() w nieskończoność, parsuje napływające rozgłoszenia i nigdy nie wywołuje 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 skanuje do momentu wyjścia z menedżera kontekstu; active=False utrzymuje własne radio observer w ciszy (brak żądań scan response) dla najniższego poboru mocy. Argument filter= w manufacturer() odrzuca każde rozgłoszenie, które nie pasuje do identyfikatora firmy, więc pętla uruchamia się tylko dla ruchu broadcaster.
11.4.6. Od wykrycia do połączenia¶
Gdy central wybierze peripheral, z którym chce się komunikować, przestaje nasłuchiwać, wysyła connect request na kanale rozgłoszeniowym, którego peripheral ostatnio używał, i obie strony przechodzą do przeskakujących kanałów danych warstwy łącza. Peripheral zazwyczaj przestaje w tym momencie rozgłaszać. Co dzieje się dalej – parametry połączenia, wykrywanie GATT, czas życia łącza – znajdziesz na stronie Połączenia.