Bluetooth Low Energy Simulator-A New Hope in IoT Development

A XXI. században élünk, ahol az állatok talán még mindig nem beszélnek hozzánk, de a dolgok határozottan kezdenek beszélni. Olyan küldetésirányító központokkal (alias okostelefonokkal) járkálunk, amelyek képesek kezelni a pénzünket, a kommunikációnkat, a híreinket, az okosotthonainkat és minden más okos megoldást, ami körülvesz minket, egyszóval az életünket.

Tréning egy versenyre? Megteheted a Humon okos izom-oxigén monitorral. Szeretné javítani az alvásminőségét? Szerezzen be egy okospántot, okosórát vagy alváskövető készüléket (ezek mindegyike Bluetooth Low Energy-t használ és kommunikál a telefonjával). Biciklivel próbál eljutni valahová? Használjon BLE-kompatibilis navigációt, hogy minimalizálja az olyan zavaró tényezőket, mint az értesítések, de mégis tudja, hogyan juthat el a kívánt helyre. Esetleg szeretné tudni, mikor van kész az irodai kávé? Megoldottuk, ember!

De a dolgok internetének megjelenésével és a világ egyre növekvő összekapcsolódásával új kihívások elé állított minket, fejlesztőket.

BLE-képes alkalmazás fejlesztése

Ha dolgozott már olyan mobilalkalmazáson, amely Bluetooth Low Energy eszközhöz csatlakozik, tudja, hogy ez nem a legkönnyebb feladat. Az alkalmazásban számos képernyő és belső állapot van, amelyek a perifériához és annak viselkedéséhez kapcsolódnak. Az alkalmazás gyakran az eszköz firmware-ével (néha még hardverével (!!!)) együtt készül, a kapcsolat szeszélyes lehet, a külső interfész instabil, a belső logika hibás. Ennek eredményeként a csapat rengeteg időt pazarol a Known Issues™-ra, amikor a funkciókat teszteli.

Általában kétféleképpen lehet ezt megközelíteni:

  1. Először is van egy egyszerű, időigényes módszer: használjunk egy fizikai okostelefont és egy BLE-eszközt, menjünk végig a sok vesződségen, hogy csatlakoztassuk és beállítsuk az eszközt abba az állapotba, amiben nekünk kell, és hozzuk létre újra a tesztelési körülményeket.
  2. Aztán ott van a nehéz út: absztraháljuk az eszközt, létrehozunk egy egyszerű mock-upot, amely képes elrejteni a tényleges BLE-kezelést. Ez lehetővé teszi, hogy egy Android emulátoron/iOS szimulátoron dolgozz, így később időt takaríthatsz meg, és automatizált teszteket futtathatsz a CI-ken. Ugyanakkor növeli a karbantartási költségeket, és új kockázatot vezet be azzal, hogy nem teszteli a tényleges kommunikációt minden egyes alkalommal, amikor a kódját futtatja. Elvégre az a Bluetooth-periféria valószínűleg az alkalmazásunk középpontjában áll, és nem szabad, hogy váratlanul megszakadjon a kapcsolat, vagy furcsán viselkedjen.

Barátaink a Frontside-nál – egy austini székhelyű frontend szoftverfejlesztő és architektúra tanácsadó cégnél – felismerték, hogy szükség van egy hatékonyabb megoldásra. Megkértek minket, hogy fejlesszünk ki egy nyílt forráskódú megoldást a megbízható BLE-képes alkalmazásfejlesztéshez, amelyből mindenki profitálhat.

És így jön…

BLEmulator /pronun.: bleh-mulator/, egy Bluetooth Low Energy szimulátor.

BLEmulator azért van itt, hogy megkönnyítse az életed! Kezeli az összes BLE-vel kapcsolatos termelési kódodat, és szimulálja egy valódi periféria és rendszer Bluetooth stackjének viselkedését. Egyszerű és rugalmas, lehetővé teszi egy BLE-kompatibilis eszköz alapvető mockjának és teljes szimulációjának létrehozását egyaránt. És a legjobb dolog – nyílt forráskódú!

Ezzel tesztelheti az eszköz mögötti koncepciót a hardveres prototípusok költségei nélkül. Lehetővé teszi, hogy a mobilcsapata továbblépjen anélkül, hogy firmware-re vagy prototípusokra várna, csupán egy specifikációval. Lehetővé teszi, hogy csak Android emulátorral vagy iOS szimulátorral dolgozzon, így nagyobb mobilitást, könnyebb távmunkát tesz lehetővé, és elkerülheti a fizikai okostelefonok korlátozott elérhetőségét. Lehetővé teszi az alkalmazások tesztelését a CI által futtatott automatizált tesztekben.

A BLEmulator jelenleg a Flutterhez érhető el, és csak a FlutterBleLibünkkel működik.

Hogyan működik

A Flutter, mint multiplatform keretrendszer, mindkét platformhoz natív függőségeket igényel. Normális esetben ezek magának a könyvtárnak a részei lennének, de mi itt más megközelítést alkalmaztunk. A Polidea rendelkezik egy React Native BLE könyvtárral, react-native-ble-plx néven, ami kollégáink fantasztikus munkája. Úgy döntöttünk, hogy az összes natív logikát kivonjuk belőle egy külön könyvtárba, Multiplatform BLE Adapter néven. Így létrehoztunk egy közös magot, amelyet a react-native-ble-plx és a Flutter pluginunk, a FlutterBleLib is használ. Mellékhatásként létrehoztunk egy közös, a natív hídban használt absztrakciót, a BleAdapter-t, amely tökéletes belépési pont a szimulációhoz!

Így néz ki a FlutterBleLib adatáramlása:

  1. A FlutterBleLib egyik objektumának (BleManager, Peripheral, Service, Characteristic)
  2. A Dart kód átküldi a metódus nevét és paramétereit a natív hídnak
  3. A natív híd fogadja az adatokat és szükség esetén deserializálja azokat
  4. A natív híd meghívja a megfelelő metódust a BleAdapter példányon a Multiplatform BLE adapterből
  5. A BleAdapter meghívja a metódust az RxAndroidBle vagy az RxBluetoothKit-en, a platformtól függően
  6. RxAndroidBle/RxBluetoothKit meghív egy rendszermódszert
  7. A rendszer visszaadja a választ a közvetítőnek
  8. A közvetítő visszaadja a választ a BleAdapternek
  9. BleAdapter válaszol a natív hídnak
  10. Native bridge leképezi a választ és átküldi a Dartnak
  11. Dart elemzi a választ és visszaküldi az eredeti hívónak

Megvolt a dolgunk – a 4-9. pontok az ideális belépési pontok egy meghatározott külső szerződéssel. A BleAdapter egy másik implementációjának injektálásával bármikor bekapcsolhatjuk a szimulációt, amikor csak akarjuk.

A következőkben el kellett döntenünk, hogy hol történjen a szimuláció. Úgy döntöttünk, hogy a szimuláció minél nagyobb részét a Dartban tartjuk, két fő okból:

  1. Egy definíció mindkét platformra

Nagyon fontos, hogy minimalizáljuk mind a hibalehetőségek számát, mind a szimulált periféria létrehozásához és karbantartásához szükséges munka mennyiségét.

  1. A platformra natív nyelv

Ez néhány előnnyel jár. Először is, a fejlesztők hatékonyabban fognak dolgozni egy ismert eszközzel, így elkerülhetjük további nyelvek bevezetését. Másodszor, nem akartuk korlátozni a szimulált periférián elvégezhető dolgokat. Ha választ szeretne kérni néhány HTTP-kiszolgálótól (esetleg egy fejlettebb szimulációval, amely magát a firmware-t futtatja?), akkor ezt gond nélkül megteheti ugyanazzal a kóddal, amit bármely más HTTP-kommunikációhoz írna az alkalmazásában.

A szimulált hívási útvonal így néz ki:

  1. Hívjon meg egy metódust az egyik FlutterBleLib objektumon (BleManager, Peripheral, Service, Characteristic)
  2. A Dart kód átküldi a metódus nevét és paramétereit a natív hídnak
  3. A natív híd megkapja az adatokat és szükség esetén deserializálja azokat
  4. A natív híd meghívja a megfelelő metódust a SimulatedAdapteren

    Changed rész most kezdődik

  5. BleAdapter – ebben az esetben a szimulátorból származó – továbbítja a hívást a BLEmulátor natív hídjának
  6. BLEmulátor vagy maga végzi a logikát (ha az nem érinti a perifériát), vagy meghívja a megfelelő metódust a szállított periférián. a felhasználó által
  7. A választ továbbítja a BLEmulator natív hídjának
  8. BLEmulator natív hídja továbbítja a választ a SimulatedAdapternek
  9. SimulatedAdapter válaszol a natív hídnak

    vissza az eredeti folyamathoz

  10. Natív híd. leképezi a választ és átküldi a Dartnak
  11. Dart elemzi a választ és visszaküldi az eredeti hívónak

Így az összes BLE kezelő kódodat használod és a FlutterBleLib által biztosított típusokon dolgozol, függetlenül attól, hogy melyik backendet használod, legyen az a valós rendszer BT stack vagy a szimuláció. Ez azt is jelenti, hogy a perifériával való interakciót a CI automatizált tesztjeiben is tesztelheted!

Hogyan használd

Megbeszéltük, hogyan működik és milyen lehetőségeket nyújt, ezért most ugorjunk bele a használatába.

  1. Add hozzá a függőséget a blemulator-hoz a pubspec-ben.yml
  2. Hozzon létre saját szimulált perifériát a plugin által biztosított osztályok felhasználásával SimulatedPeripheral, SimulatedService és SimulatedCharacteristic (a következő részben részletesen kitérek rá)
  3. Adja hozzá a perifériát a BLEmulátorhoz a Blemulator.addPeripheral(SimulatedPeripheral)
  4. Hívja a Blemulator.simulate()-t, mielőtt a BleManager.createClient()-t hívja a FlutterBleLib-ből

Ez minden, csak négy lépés, és máris működőképes! Nos, elismerem, hogy a legbonyolultabb lépést mondhatni kihagytuk, ezért beszéljünk a második pontról – a periféria definiálásáról.

Periféria szerződés

A következő példákat a Texas Instruments CC2541 SensorTagjére fogom alapozni, az IR hőmérséklet érzékelőre koncentrálva.

Meg kell tudnunk, hogyan néz ki a szolgáltatás UUID-je és jellemzői. A dokumentációban két hely érdekel minket.

UUID-k érdekelnek minket:

  • IR hőmérséklet szolgáltatás: F000AA00-0451-4000-B000-000000000000 Ez a szolgáltatás tartalmazza a hőmérséklet-érzékelőre vonatkozó összes jellemzőt.
  • IR hőmérsékleti adatok: F000AA01-0451-4000-B000-000000000000

    A kiolvasható vagy nyomon követhető hőmérsékleti adatok. Az adatok formátuma: ObjectLSB:ObjectMSB:AmbientLSB:AmbientMSB. Minden konfigurálható időszakonként értesítést küld, amíg megfigyelés alatt van.

  • IR hőmérséklet config: F000AA02-0451-4000-B000-000000000000

    Az érzékelő be- és kikapcsolása. Két érvényes értéke van ennek a jellemzőnek::

    • 00-érzékelő alvó üzemmódba helyezve (az IR-hőmérsékleti adatok négy bájtnyi nulla lesz)
    • 01-érzékelő bekapcsolva (az IR-hőmérsékleti adatok helyes értékeket fognak kibocsátani)
  • IR hőmérséklet periódus: F000AA03-0451-4000-B000-000000000000

    Az értesítések közötti időköz.Az alsó határ 300 ms, a felső határ 1000 ms. A jellemző értékét megszorozzuk 10-zel, így a támogatott értékek 30 és 100 között vannak.

Ez minden, amit kerestünk, tehát térjünk rá a megvalósításra!

Legegyszerűbb periféria

A legegyszerűbb szimuláció bármilyen értéket elfogad, és minden művelet sikeres lesz.

Dartban ez így néz ki:

Rövid és tömör, majdnem úgy néz ki, mint egy JSON.

Íme, mi történik: létrehoztunk egy SensorTag nevű perifériát, amelynek van egy futásidőben megadott azonosítója (bármilyen string jó, de a BLEmulator által ismert perifériák között egyedinek kell lennie). Amíg a perifériás szkennelés be van kapcsolva, addig 800 ezredmásodpercenként hirdetni fog az alapértelmezett szkennelési információkkal. Egy szolgáltatást tartalmaz, amelynek UUID azonosítóját a hirdetési adatok tartalmazzák. A szolgáltatás 3 jellemzőt tartalmaz, akárcsak egy valódi eszközön, és mindegyik olvasható. Az első jellemző nem írható, de támogatja az értesítéseket; a másik kettő nem ellenőrizhető, de írható. A jellemzőknek nincsenek érvénytelen értékei. A convenienceName argumentumot a BLEmulátor semmilyen módon nem használja, de megkönnyíti a definíció olvashatóságát.

IR Temperature Config és IR Temperature Period elfogad és beállít bármilyen átadott értéket. Az IR Temperature Data jellemző támogatja az értesítéseket, de valójában soha nem küld, mivel nem definiáltuk őket semmilyen módon.

A BLEmulator out of the box biztosítja az alapvető viselkedést, gondoskodik az összes konstrukció boldogító útjáról, minimalizálva a szükséges munka mennyiségét. Bár néhány teszthez és az alapvető specifikációkövetési ellenőrzésekhez elegendő lehet, nem próbál úgy viselkedni, mint egy valódi eszköz.

Egyéni viselkedést kell implementálnunk!

A BLEmulator létrehozásakor egyszerre tartottuk meg az egyszerűséget és a rugalmasságot. A fejlesztőknek meg akartuk adni a szükséges kontrollt a létrehozott periféria minden aspektusa felett, miközben a lehető legkevesebb munkát igényeltük tőlük egy működő definíció létrehozásához. E cél elérése érdekében úgy döntöttünk, hogy létrehozzuk az összes olyan metódus alapértelmezett implementációját, amely az egyéni viselkedés belépési pontja lehet, és hagyjuk, hogy eldöntsük, mit írjunk felül.

Most, adjunk hozzá némi logikát.

Legyen a csatlakozás időigényes

Egy valódi periféria valószínűleg eltart egy ideig, mielőtt csatlakozik. Ennek eléréséhez csak egy metódust kell felülírnunk:

Ez minden. Most a csatlakozás 200 ezredmásodpercig tart!

A kapcsolat megtagadása

Egy hasonló eset. Szeretnénk megtartani a késleltetést, de hibaüzenetet küldeni, hogy a kapcsolatot nem sikerült létrehozni. Felülírhatnánk a metódust, és dobhatnánk egy SimulatedBleError-t magunknak, de azt is megtehetjük:

 @override Future<bool> onConnectRequest() async { await Future.delayed(Duration(milliseconds: 200)); return false; }

A periféria által inicializált kapcsolat megszakítása

Tegyük fel, hogy ellenőrizni akarjuk az újracsatlakozás folyamatát, vagy szimulálni akarjuk a hatótávolságon kívülre kerülést. Megkérhetünk egy kollégát, hogy szaladjon át az iroda másik végébe egy valódi perifériával, vagy hozzáadhatunk valamilyen hibakereső gombot, és annak onPressjában

yourPeripheralInstance.onDisconnect();

(Bár az első lehetőség kielégítőbbnek tűnik.)

RSSI módosítása a letapogatási információban

Jól van, mondjuk, hogy a perifériákat az érzékelt jelerősségük szerint szeretnénk rendezni, és ezt kell tesztelnünk. Létrehozunk néhány szimulált perifériát, az egyiket statikus RSSI-vel hagyjuk, a másikban pedig így teszünk:

@overrideScanResult scanResult() { scanInfo.rssi = -20 - Random.nextInt(50); return super.scanResult();}

Ezzel pár eszköz változó RSSI-vel és tesztelhetjük a funkciót.

MTU tárgyalása

A BLEmulator a logika nagy részét magától elvégzi, így az MTU-t a 23 és 512 közötti támogatott tartományra korlátozza, de ha tovább kell korlátozni, akkor felül kell írni a requestMtu() módszert:

A BLEmulator automatikusan a legmagasabb támogatott MTU-t tárgyalja az iOS-en.

Az értékek kényszerítése a támogatott tartományba

A jellemző által elfogadott értékek korlátozásához egy új osztályt kell létrehozni, amely a SimulatedCharacteristic kiterjesztése.

A jellemző mostantól az első bájton 0-ra vagy 1-re korlátozza a bemenetet, a további bájtokat figyelmen kívül hagyja, és hibát ad vissza, ha az érték meghaladja a támogatott tartományt. A hiba visszaküldésének támogatásához a karakterisztikának választ kell adnia az írási műveletre, ezért a writableWithoutResponse értéket false-ra kell állítani.

Az érzékelő bekapcsolása

A hőmérsékletérzékelő be- és kikapcsolásának időpontját szeretnénk tudni.

Ezért létrehozunk egy új szolgáltatást keményen kódolt UUID-kkel:

Az általunk definiált jellemzőt nem lehet a FlutterBleLib-en keresztül monitorozni, mivel hiányzik isNotifiable: true, de a BLEmulator szintjén a könnyebbség kedvéért monitorozható. Ez megkönnyíti az általunk szimulált teljes áramlás ellenőrzését, egyszerűsíti a struktúrát és lehetővé teszi, hogy elkerüljük az alaposztályok felesleges kiterjesztéseit.

Értesítések kibocsátása

Az IR Temperature Data jellemzőből még mindig hiányzik az értesítések kibocsátása. Gondoskodjunk róla.

_emitTemperature() a Temperature Service konstruktorában hívódik meg, és végtelen ciklusban fut. Minden egyes intervallumban, amelyet az IR Temperature Period jellemző értéke határoz meg, ellenőrzi, hogy van-e hallgató (isNotifying). Ha van, akkor adatokat ír (nullákat vagy véletlen értéket, attól függően, hogy az érzékelő be- vagy kikapcsolt állapotban van-e) az IR Temperature Data jellemzőbe. SimulatedCharacteristic.write() értesít minden aktív hallgatót az új értékről.

Fejlettebb periféria működésben

Egy fejlettebb periféria teljes példáját a BLEmulator adattárában találja. Ha szeretnéd kipróbálni, csak klónozd a repository-t és futtasd a példáját.

Használat az automatizált tesztelésben

Ezúton is nagy köszönet Polidean társamnak, Paweł Byszewskinek a BLEmulator automatizált tesztekben való használatának kutatásáért.

A Flutter különböző végrehajtási kontextust biztosít a tesztelt alkalmazás és maga a teszt számára, vagyis nem lehet csak úgy megosztani egy szimulált perifériát a kettő között és módosítani a viselkedést a tesztből. Amit tehetsz, hogy a enableFlutterDriverExtension(handler: DataHandler) használatával hozzáadsz egy adatkezelőt a teszt vezérlőhöz, átadod a szimulált perifériákat az alkalmazásod main()-jének, és az alkalmazás végrehajtási kontextusán belül string üzeneteket adsz át a kezelőnek.

Ez a következőre fut ki:Wrapper for app

A perifériád

A teszted

A mechanizmusnak köszönhetően úgy inicializálhatod a perifériát, ahogy akarod, és bármelyik előre definiált viselkedést meghívhatod a szimulált eszközödön belül.

Nézd meg magad

A legjobb az egészben, hogy nem kell hinned nekem! Nézd meg magad a GitHubon. Használd, szórakozz vele, próbáld meg feltörni, és mesélj róla nekünk!

Szeretnéd más platformokon is látni? Keress meg minket, és megvalósítjuk! Ne felejtsd el megnézni a többi könyvtárunkat is, és ne habozz felvenni velünk a kapcsolatot, ha szeretnéd, hogy dolgozzunk a te projekteden.