11.14. Recapitulare

Ați parcurs Bluetooth Low Energy de la nivelul radio până la API-ul Python folosit pentru a-l controla:

  • Motivația – BLE este răspunsul atunci când camera dorește să comunice cu ceva din apropiere fără nicio infrastructură între ele. Un telefon în aceeași încăpere, un dispozitiv purtabil pe încheietură, un beacon pe un perete. Rază scurtă, nicio rețea de accesat, putere aproape nulă.

  • Radioul – 2.4 GHz, 40 de canale: trei pentru advertising, 37 pentru datele de conexiune, parcurse prin salt pe o secvență pseudo-aleatoare cu evitarea adaptivă a canalelor zgomotoase. Pachete scurte, radiouri în mare parte adormite.

  • Nivelul de legătură – încadrarea pachetelor, adresarea, programarea conexiunii, retransmisia și criptarea la nivelul de legătură. Niciunul dintre acestea nu se configurează din Python; toate se reflectă în parametrii de conexiune și în MTU.

  • Generic Access Profile (GAP) – descoperirea și gestionarea conexiunilor. Patru roluri: peripheral și broadcaster (advertise), central și observer (scanare). Sarcinile utile de advertising transportă numele local, UUID-urile de servicii, aparența și datele specifice producătorului – 31 de octeți plus un scan response opțional de 31 de octeți. Intervalul de conexiune, latența perifericului și timeout-ul de supraveghere guvernează modul în care se comportă o conexiune deschisă.

  • Generic Attribute Profile (GATT) – un arbore de servicii, fiecare conținând caracteristici, fiecare conținând opțional descriptori, identificate prin UUID-uri (pe 16 biți pentru standardele Bluetooth-SIG, pe 128 de biți pentru cele personalizate). Cinci operații: read și write (extragere, inițiate de client), notify și indicate (împingere, inițiate de server, abonate prin Client Characteristic Configuration Descriptor). Dimensiunea sarcinii utile este limitată de MTU-ul negociat.

  • API-ul Pythonaioble transformă fiecare model BLE într-o corutină asyncio. Un periferic este aioble.advertise() care iterează peste conexiuni, cu obiecte Service / Characteristic construite o singură dată și înregistrate prin aioble.register_services(). Un central este aioble.scan() pentru a găsi un peer, connect() pentru a deschide legătura, service() și characteristic() pentru a parcurge arborele GATT la distanță, apoi read() / write() / subscribe() / notified() pentru datele propriu-zise. Deconectările apar ca aioble.DeviceDisconnectedError în interiorul corutinei care aștepta.

  • Canalele L2CAP – soluția de rezervă pentru fluxuri de octeți în masă care nu se potrivesc modelului cheie/valoare al GATT. aioble.DeviceConnection.l2cap_accept() / l2cap_connect() deschid un canal per-aplicație peste conexiunea GAP, cu trimitere / recepție controlate prin flux de credite și un MTU mai mare decât poate transporta GATT.

  • Pairing și criptare – legăturile BLE sunt publice în mod implicit. aioble.DeviceConnection.pair() inițiază un schimb de chei care produce o legătură criptată; bond=True (valoarea implicită) păstrează cheile astfel încât conexiunile ulterioare să sară peste handshake. Fără mitm=True și o capacitate IO utilizabilă, criptarea protejează împotriva ascultătorilor pasivi neautorizați, dar nu împotriva unei redirecționări active în timpul pairing-ului original.

Acest lucru este suficient pentru a scrie aplicații de cameră care publică starea ca periferic, citesc date de la senzori ca central, împing valori live către un telefon prin BLE, securizează legătura cu un pas de pairing-și-bonding și – pentru cazul rar de transfer în masă – ies din GATT către un canal L2CAP.

11.14.1. Depanare

Eșecurile BLE sunt în mare parte nepotriviri între ceea ce se așteaptă cele două părți, iar un inspector pe partea telefonului este cea mai rapidă modalitate de a vedea ale cui așteptări sunt greșite. Instrumentul standard este nRF Connect for Mobile (Nordic Semiconductor, gratuit pe Android și iOS): scanează, se conectează, parcurge baza de date GATT, citește și scrie caracteristici și se abonează la notificări – astfel încât comportamentul pe partea camerei poate fi testat izolat, fără a scrie deloc o aplicație însoțitoare.

Modurile comune de eșec:

  • „Dispozitivul meu apare în scanner, dar nu se conectează.” Cel mai adesea pachetul de advertising are connectable=False (modul broadcaster), sau o conexiune anterioară este încă deschisă și camera a trecut deja de aioble.advertise(). Adăugați instrucțiuni print în jurul apelului de advertise pentru a confirma.

  • „exchange_mtu(512) a rulat, dar notificările mele sunt încă limitate la 20 de octeți.” MTU-ul negociat este min(local, peer) – este posibil ca telefonul sau biblioteca centralului să nu fi solicitat un MTU mai mare pe partea sa, caz în care conexiunea rămâne la 23. Inspectați mtu după ce exchange_mtu() revine. Rețineți de asemenea că exchange_mtu() funcționează o singură dată pe conexiune; apelați-l înainte de prima operație mare.

  • „Pairing-ul eșuează cu o eroare generică.” Două cauze obișnuite: nepotrivirea capacității IO (solicitarea mitm=True pe o cameră care declară io=3 / fără intrare fără ieșire – nu există nicio modalitate de a confirma codul numeric, așa că motorul de pairing renunță) și o oră de perete complet greșită pe cameră atunci când peer-ul o solicită. Setați ceasul cu ntptime.settime() înainte de prima încercare de pairing.

  • „Notificările nu ajung niciodată la client.” Două lucruri de verificat, în ordine: (a) a fost caracteristica declarată cu notify=True? – bitul de proprietate trebuie setat pe partea serverului; (b) a apelat clientul subscribe()? – fără a scrie Client Characteristic Configuration Descriptor (CCCD), serverului i se spune că niciun client nu dorește notificări și le elimină în tăcere.

  • „Numele afișat în advertising este trunchiat sau lipsește.” Sarcina utilă de advertising are 31 de octeți, iar câmpurile de flags + UUID de serviciu + aparență iau fiecare octeți din total. Un name= lung plus mai multe UUID-uri de servicii depășesc limita. Fie scurtați numele, fie folosiți scanarea activă astfel încât scan response (alți 31 de octeți) să transporte surplusul. nRF Connect afișează cele două jumătăți separat, ceea ce face evidentă împărțirea.

  • „L2CAP connect generează imediat o eroare.” De obicei o nepotrivire de PSM – ambele părți trebuie să convină asupra aceluiași număr PSM în afara benzii. O L2CAPConnectionError poartă codul de stare Bluetooth ca prim argument; starea 2 („PSM not supported”) este indiciul revelator.

  • „Conexiunile legate declanșează în continuare un handshake complet de pairing la fiecare reconectare.” aioble.security.load_secrets() nu a fost apelată la pornire. Fără ea, cheile salvate sunt în memoria flash, dar nu sunt încărcate niciodată în memorie, așa că identitatea peer-ului este necunoscută și pairing-ul rulează de la zero de fiecare dată.

Când toate celelalte eșuează, modulul bluetooth de nivel scăzut expune un callback IRQ care se declanșează pentru fiecare eveniment subiacent; abonarea la acesta pentru scurt timp și afișarea evenimentelor este echivalentul unei urme Wireshark pentru partea camerei.

11.14.2. Folosirea acestei referințe mai târziu

Tratați capitolele despre Bluetooth ca material de referință; revenirea pentru aranjamentul exact al sarcinii utile de advertising a unui periferic sau pentru fluxul de scanare-și-abonare al unui central este utilizarea intenționată. Paginile de referință aioble — BLE asincron și bluetooth — Bluetooth de nivel jos listează fiecare metodă, flag și constantă într-un singur loc atunci când întrebarea este pur și simplu „care este numele exact al acestui apel”.