11.4. Mainostus ja skannaus

Kahden BLE-laitteen, jotka eivät ole koskaan aiemmin tavanneet, on ensin löydettävä toisensa. Verkkoyhteys ratkaisee tämän antamalla jokaiselle laitteelle osoitteen jaetusta varannosta ja antamalla kummankin osapuolen tavoittaa toisensa reitittimien kautta. BLE:ssä ei ole reitittimiä, ei jaettua varantoa eikä – useimpien laiteparien välillä – mitään aiempaa suhdetta lainkaan. Generic Access Profile (GAP) ratkaisee löytämisen sen sijaan lähetä-ja-kuuntele-mallilla. Toinen osapuoli mainostaa – se lähettää lyhyen paketin kolmella mainoskanavalla säännöllisin väliajoin kuvaten keitä se on. Toinen osapuoli skannaa – se käy läpi samat kolme kanavaa kuunnellen näitä paketteja.

GAP määrittää neljä roolia tämän mallin ympärille, kukin tietty mainostuksen ja kuuntelun yhdistelmä.

11.4.1. Neljä GAP-roolia

Kaksi kertaa kaksi -matriisi. Rivit on otsikoitu "mainostaa" ja "ei mainosta". Sarakkeet on otsikoitu "hyväksyy yhteydet" ja "ei hyväksy yhteyksiä". Neljä solua sisältävät roolinimet: Peripheral, Broadcaster, Central, Observer.

Neljä GAP-roolia. Pystyakseli on se, mainostaako laite; vaaka-akseli on se, hyväksyykö (vai aloittaako) se yhteyksiä.

  • Peripheral mainostaa paketteja, jotka kertovat ”olen täällä ja voit yhdistää minuun”. Kun toinen laite avaa yhteyden, peripheral lopettaa mainostamisen ja alkaa palvella GATT-pyyntöjä. Sykevyöt, lämpömittarit ja useimmat sensorina toimivat kamerat toimivat peripheral-laitteina.

  • Central skannaa peripheral-laitteita, valitsee yhden ja aloittaa yhteyden. Yhdistettyään se puhuu GATT:ia asiakkaana. Puhelimet, kannettavat ja datankerääjinä toimivat kamerat ovat central-laitteita.

  • Broadcaster mainostaa mutta ei koskaan hyväksy yhteyksiä. Sen mainoskuorma on data – ei ole mitään, mihin yhdistää. iBeaconit ja useimmat kaupan läsnäolomajakat ovat broadcaster-laitteita.

  • Observer skannaa näitä mainoksia ja lukee kuorman, jälleen koskaan yhdistämättä. Kamera, joka kuuntelee lähellä olevia majakoita ja toimii kuulemansa perusteella, on observer.

Yksittäinen laite voi näytellä useampaa kuin yhtä roolia samanaikaisesti – kamera voi olla peripheral, joka julkaisee oman tilansa, ja central, joka yhdistää lähellä olevaan sensoriin. Radio limittää työn.

11.4.2. Mitä mainospaketti sisältää

Mainospaketti on pieni: 31 tavua kuormaa, tai 62, jos mainostaja julkaisee myös scan response -vastauksen, jota skannaajat voivat pyytää lennossa. Kuorma on lista lyhyitä tyypitettyjä kenttiä:

  • Flags. Yhdistettävissä vai ei, yleisesti / rajoitetusti löydettävissä.

  • Local name. Lyhyt, ihmisystävällinen merkkijono – nimi, jonka puhelimen tai kannettavan käyttöjärjestelmä näyttää Bluetooth-valikossaan.

  • Service UUIDs. Lista laitteen isännöimien GATT-palvelujen tunnisteita, jotta skannaaja voi tunnistaa kykenevät peripheral-laitteet yhdistämättä ensin. Sykevyö mainostaa 0x180D – vakiona käytetyn Heart-Rate-palvelun UUID:n – ja puhelimen sykesovellus tietää pelkästään siitä, että laite on yhdistämisen arvoinen.

  • Appearance. 16-bittinen arvo Bluetoothin määriteltyjen numeroiden listasta (sensori, geneerinen media, geneerinen kello, …) – vihje central-laitteelle siitä, mitä näyttää.

  • Manufacturer-specific data. Vapaamuotoiset tavut, joiden edessä on yritystunnus. iBeaconit käyttävät tätä kenttää kantamaan UUID:tään, major- ja minor-arvojaan; mukautetut sovellukset voivat laittaa tähän mitä haluavat.

Mainoskuormat ovat tiukkoja. 31 tavun raja tekee sisällytettävän valitsemisesta todellisen suunnittelupäätöksen – pitkä ihmisluettava nimi voi nopeasti jättää tilaa palvelu-UUID:eille olemattomaksi. aioble.advertise() -API ottaa kunkin näistä avainsana-argumenttina ja kokoaa tavut puolestasi vuotaen automaattisesti scan response -vastaukseen, jos pääpaketti täyttyy.

11.4.3. Aktiivinen ja passiivinen skannaus

Skannaaja voi toimia passiivisesti, jolloin se kuuntelee mainospaketteja ja jäsentää saapuvan, tai aktiivisesti, jolloin se myös lähettää scan request -pyynnön kullekin mainostajalle ja jäsentää palaavan scan response -vastauksen.

Passiivinen skannaus näkee vain alkuperäisen mainospaketin (enintään 31 tavua). Aktiivinen skannaus kaksinkertaistaa tämän – scan response on toiset 31 tavua, joita peripheral voi käyttää kentille, jotka eivät mahtuneet. Aktiivinen skannaus myös kuluttaa virtaa molemmilla puolilla, koska skannaaja lähettää ja mainostaja lähettää ylimääräisen paketin, joten se on valinta eikä oletus.

aioble -API:ssa active=True aioble.scan() -funktiossa vaihtaa tilan, ja kukin ScanResult paljastaa yhdistetyn adv_data plus resp_data -datan sekä apuvälineet kuten result.name() ja result.services(), jotka piilottavat tavutason jäsennyksen.

Muista

adv_data ja resp_data -attribuutit ovat raakoja mainos- ja scan-response-kuormia (bytes). Apuvälineet – name(), services(), manufacturer() – kattavat yleiset vakiokentät ja ovat oikea valinta 99 % ajasta. Tartu raakatavuihin vain, kun tarvitset valmistajakentän, jota apuvälineet eivät jäsennä (Eddystone-URL:t, iBeacon-UUID/major/minor, mukautetut mainostyypit). Tavurakenne on standardin TLV-muotoinen: kukin kenttä on length, type, value....

11.4.4. Mainosväli

Kuinka usein peripheral lähettää, on virrankulutuksen / löytymisviiveen kompromissi. Mainokset, jotka lähtevät joka 20 ms, skannaaja poimii lähes välittömästi mutta ne pitävät radion kiireisenä ja tyhjentävät akun; mainokset joka sekunti käyttävät lähes lainkaan virtaa mutta tekevät skannaajan läpikäynnistä hitaamman huomata laitteen.

interval_us aioble.advertise() -funktiossa asettaa välin mikrosekunteina:

  • 20 000 - 100 000 us (20 ms - 100 ms) – nopea pariutus, sovellus odottaa nopeaa vastausta, verkkovirtaan kytketty laite.

  • 250 000 - 1 000 000 us (250 ms - 1 s) – järkevä oletus akkukäyttöiselle peripheral-laitteelle, joka haluaa olla löydettävissä kuluttamatta varausta.

  • Yli 1 000 000 us – hidas taustalähetys, majakat, jotka lähettävät sijaintipäivityksen muutaman sekunnin välein.

Skannaajan puolella on omat säätönsä – aioble.scan() ottaa parametrit interval_us ja window_us (kuinka usein skannaaja herättää radionsa ja kuinka pitkään se kuuntelee kerrallaan). Oletukset ovat hyvät; ainoa yleinen muutos on asettaa molemmat yhtä suuriksi jatkuvaa skannausta varten, kun akku ei ole huolenaihe.

11.4.5. Yhteydettömät mallit – broadcaster ja observer

Sivut Oheislaitteena toimiminen ja Toimiminen keskusyksikkönä käyvät läpi API:n yhdistettävän muodon – jossa peripheral hyväksyy yhteyden ja molemmat osapuolet vaihtavat dataa GATT:in kautta. Toinen muoto on yhteydetön: broadcaster lähettää kuorman mainoksena, ja kuka tahansa kantaman sisällä oleva observer voi lukea sen koskaan yhdistämättä. Majakat, läsnäolosensorit ja kaikki yksisuuntainen telemetria elävät täällä.

Broadcaster on aioble.advertise(), jolla on connectable=False. Manufacturer-specific data kantaa kuorman:

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

timeout_ms -avainsana päättää mainostuskutsun sekunnin jälkeen; ulompi silmukka antaa sen uudelleen seuraavalla järjestysnumerolla, jotta kuuntelijat näkevät tuoretta dataa. connectable=False -lippu on se, mikä tekee mainoksesta broadcaster-tyylisen – kamera ei vastaa yhteyspyyntöön, vaikka sellainen saapuisi.

Observer on vastaava vain luku -skannaaja. Se ajaa aioble.scan() -funktiota ikuisesti, jäsentää saapuvat mainokset eikä koskaan kutsu 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 skannaa, kunnes kontekstinhallinta päättyy; active=False pitää observerin oman radion hiljaisena (ei scan-response-pyyntöjä) pienimmän virrankulutuksen saavuttamiseksi. filter= -argumentti manufacturer() -funktiossa hylkää jokaisen mainoksen, joka ei vastaa yritystunnusta, joten silmukka laukeaa vain broadcasterin liikenteelle.

11.4.6. Löytämisestä yhteyteen

Kun central valitsee peripheral-laitteen, jonka kanssa keskustella, se lopettaa kuuntelun, lähettää connect request -pyynnön mainoskanavalla, jota peripheral viimeksi käytti, ja molemmat osapuolet siirtyvät linkkikerroksen hyppiville datakanaville. Peripheral lopettaa tyypillisesti mainostamisen tässä vaiheessa. Mitä seuraavaksi tapahtuu – yhteysparametrit, GATT-löytäminen, linkin elinkaari – on sivulla Yhteydet.