11.4. Hirdetés és pásztázás

Két BLE eszköznek, amelyek korábban sosem találkoztak, először meg kell találniuk egymást. A hálózatkezelés ezt úgy oldja meg, hogy minden eszköznek egy közös készletből oszt címet, és lehetővé teszi, hogy bármelyik oldal elérje a másikat útválasztókon keresztül. A BLE-nek nincsenek útválasztói, nincs közös készlete, és – a legtöbb eszközpár között – egyáltalán nincs korábbi kapcsolata sem. A Generic Access Profile (GAP) ehelyett szórás-és-figyelés mintával oldja meg a felfedezést. Az egyik oldal hirdet – rendszeres időközönként rövid csomagot sugároz a három hirdetési csatornán, leírva, hogy ki ő. A másik oldal pásztáz – ugyanazt a három csatornát figyeli ezekre a csomagokra várva.

A GAP négy szerepet definiál e minta köré, mindegyik a hirdetés és a figyelés egy-egy konkrét kombinációja.

11.4.1. A négy GAP szerep

Egy kétszer kettes mátrix. A sorok címkéje "hirdet" és "nem hirdet". Az oszlopok címkéje "elfogad kapcsolatokat" és "nem fogad el kapcsolatokat". A négy cella tartalmazza a szerepneveket: Peripheral, Broadcaster, Central, Observer.

A négy GAP szerep. A függőleges tengely az, hogy az eszköz hirdet-e; a vízszintes tengely az, hogy elfogad-e (vagy kezdeményez-e) kapcsolatokat.

  • A peripheral olyan csomagokat hirdet, amelyek azt mondják: „itt vagyok, és csatlakozhatsz hozzám”. Amikor egy másik eszköz kapcsolatot nyit, a peripheral abbahagyja a hirdetést, és elkezdi kiszolgálni a GATT kéréseket. A pulzusmérő övek, a hőmérők és a legtöbb érzékelőként működő kamera peripheralként viselkedik.

  • A central peripheralokat pásztáz, kiválaszt egyet, és kapcsolatot kezdeményez. A csatlakozás után kliensként beszéli a GATT-ot. A telefonok, laptopok és az adatgyűjtőként működő kamerák centralok.

  • A broadcaster hirdet, de sosem fogad el kapcsolatokat. A hirdetési hasznos tartalma maga az adat – nincs mihez csatlakozni. Az iBeaconok és a legtöbb bolti jelenlét-jeladó broadcaster.

  • Egy observer ezeket a hirdetéseket pásztázza, és leolvassa a hasznos tartalmat, szintén anélkül, hogy valaha csatlakozna. Egy kamera, amely a közeli jeladókat figyeli, és cselekszik az alapján, amit hall, observer.

Egyetlen eszköz egyszerre több szerepet is játszhat – egy kamera lehet peripheral, amely a saját állapotát teszi közzé, és central, amely egy közeli érzékelőhöz csatlakozik. A rádió multiplexeli a munkát.

11.4.2. Mit tartalmaz egy hirdetési csomag

Egy hirdetési csomag kicsi: 31 bájt hasznos tartalom, vagy 62, ha a hirdető egy scan response-t is közzétesz, amelyet a pásztázók menet közben kérhetnek le. A hasznos tartalom rövid, típusos mezők listája:

  • Flags. Csatlakoztatható-e vagy sem, általános / korlátozott felfedezhetőség.

  • Local name. Egy rövid, ember számára barátságos karakterlánc – az a név, amelyet egy telefon vagy laptop operációs rendszere megjelenít a Bluetooth menüjében.

  • Service UUIDs. Az eszköz által üzemeltetett GATT szolgáltatás-azonosítók listája, így a pásztázó már csatlakozás előtt felismerheti az alkalmas peripheralokat. Egy pulzusmérő öv a 0x180D-t hirdeti – a szabványos Heart-Rate szolgáltatás UUID-jét –, és a telefonon futó pulzusmérő alkalmazás már ennyiből tudja, hogy érdemes csatlakozni az eszközhöz.

  • Appearance. Egy 16 bites érték a Bluetooth hozzárendelt-számok listájáról (érzékelő, általános média, általános óra, …) – egy tipp a central számára arról, hogy mit jelenítsen meg.

  • Manufacturer-specific data. Szabad formátumú bájtok, egy cégazonosítóval előtaggal ellátva. Az iBeaconok ezt a mezőt használják az UUID-jük, major és minor értékük hordozására; az egyéni alkalmazások bármit beletehetnek, amit szeretnének.

A hirdetési hasznos tartalmak szűkösek. A 31 bájtos korlát valódi tervezési döntéssé teszi azt, hogy mit foglaljunk bele – egy hosszú, ember által olvasható név gyorsan nem hagyhat helyet a szolgáltatás-UUID-knek. Az aioble.advertise() API ezeket mindegyiket kulcsszó argumentumként veszi át, és összeállítja helyetted a bájtokat, automatikusan átcsorgatva a scan response-ba, ha a fő csomag megtelik.

11.4.3. Aktív és passzív pásztázás

Egy pásztázó futhat passzívan, amikor a hirdetési csomagokat figyeli és elemzi, ami érkezik, vagy aktívan, amikor minden hirdetőnek egy scan request-et is küld, és elemzi a visszaérkező scan response-t.

A passzív pásztázás csak a kezdeti hirdetési csomagot látja (legfeljebb 31 bájt). Az aktív pásztázás ezt megduplázza – a scan response újabb 31 bájt, amelyet a peripheral olyan mezőkre használhat, amelyek nem fértek el. Az aktív pásztázás emellett mindkét oldalon energiába kerül, mivel a pásztázó ad, és a hirdető egy extra csomagot ad, így ez inkább választás, mint alapértelmezés.

Az aioble API-ban az active=True az aioble.scan() függvényen vált módot, és minden ScanResult elérhetővé teszi az egyesített adv_data és resp_data adatot, valamint olyan segédfüggvényeket, mint a result.name() és a result.services(), amelyek elrejtik a bájtszintű elemzést.

Megjegyzés

Az adv_data és resp_data attribútumok a nyers hirdetési és scan-response hasznos tartalmak (bytes). A segédfüggvények – name(), services(), manufacturer() – lefedik a gyakori szabványos mezőket, és az esetek 99%-ában a helyes választás. A nyers bájtokhoz csak akkor nyúlj, ha olyan gyártói mezőre van szükséged, amelyet a segédfüggvények nem elemeznek (Eddystone URL-ek, iBeacon UUID/major/minor, egyéni hirdetési típusok). A bájtelrendezés a szabványos TLV: minden mező length, type, value... formátumú.

11.4.4. A hirdetési időköz

Az, hogy a peripheral milyen gyakran sugároz, energia/felfedezési-késleltetés kompromisszum. A 20 ms-onként kimenő hirdetéseket egy pásztázó szinte azonnal felveszi, de lefoglalják a rádiót és lemerítik az akkumulátort; a másodpercenként kimenő hirdetések szinte semmi energiát nem használnak, de lassabban veteti észre a pásztázóval az eszközt.

Az interval_us az aioble.advertise() függvényen az időközt mikroszekundumban állítja be:

  • 20 000 - 100 000 us (20 ms - 100 ms) – gyors párosítás, az alkalmazás gyors választ vár, hálózatra kötött eszköz.

  • 250 000 - 1 000 000 us (250 ms - 1 s) – ésszerű alapértelmezés egy akkumulátorról működő peripheral számára, amely felfedezhető akar lenni anélkül, hogy túl sok töltést égetne el.

  • 1 000 000 us felett – lassú háttérszórás, jeladók, amelyek néhány másodpercenként küldenek pozíciófrissítést.

A pásztázó oldalnak megvannak a saját szabályozói – az aioble.scan() átveszi az interval_us és window_us paramétereket (milyen gyakran ébreszti fel a pásztázó a rádióját, és mennyi ideig figyel alkalmanként). Az alapértelmezések megfelelőek; az egyetlen gyakori módosítás az, hogy mindkettőt egyenlőre állítjuk a folyamatos pásztázáshoz, amikor az akkumulátor nem szempont.

11.4.5. Kapcsolat nélküli minták – broadcaster és observer

A Periféria szerepkörben és a Központként való működés oldalak az API csatlakoztatható formáját dolgozzák fel – ahol egy peripheral elfogad egy kapcsolatot, és a két fél a GATT-on keresztül cserél adatot. A másik forma a kapcsolat nélküli: egy broadcaster a hasznos tartalmat hirdetésként sugározza, és bármely hatótávon belüli observer leolvashatja anélkül, hogy valaha csatlakozna. A jeladók, a jelenlét-érzékelők és minden egyirányú telemetria ide tartozik.

Egy broadcaster az aioble.advertise() connectable=False paraméterrel. A gyártóspecifikus adat hordozza a hasznos tartalmat:

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

A timeout_ms kulcsszó egy másodperc után befejezi a hirdetési hívást; a külső ciklus a következő sorszámmal újra kibocsátja, így a figyelők friss adatot látnak. A connectable=False jelző az, ami broadcaster-stílusúvá teszi a hirdetést – a kamera nem válaszol egy csatlakozási kérésre, még ha érkezik is egy.

Egy observer a hozzá illő, csak olvasó pásztázó. Örökké futtatja az aioble.scan() függvényt, elemzi a beérkező hirdetéseket, és sosem hívja a connect() függvényt:

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

A duration_ms=0 addig pásztáz, amíg a kontextuskezelő ki nem lép; az active=False némán tartja az observer saját rádióját (nincsenek scan-response kérések) a legalacsonyabb fogyasztás érdekében. A filter= argumentum a manufacturer() függvényen elveti az összes olyan hirdetést, amely nem egyezik a cégazonosítóval, így a ciklus csak a broadcaster forgalmára fut le.

11.4.6. A felfedezéstől a kapcsolatig

Amint egy central kiválaszt egy peripheralt, amellyel beszélni szeretne, abbahagyja a figyelést, küld egy connect request-et azon a hirdetési csatornán, amelyet a peripheral utoljára használt, és mindkét fél átvált a kapcsolati réteg ugráló adatcsatornáira. A peripheral ezen a ponton általában abbahagyja a hirdetést. Hogy mi történik ezután – kapcsolati paraméterek, GATT felfedezés, a kapcsolat élettartama –, az a Kapcsolatok oldalon olvasható.