Temperror

Bluetooth Low Energy

About

  • BLE
  • dva typy zariadení
    • central
    • peripheral
  • budeme robiť central pre Xiaomi Mijia Bluetooth Temperature Smart Humidity Sensor
  • trošku reverzného inžinierstva, ktoré však urobil niekto iný

Objectives

  • zoznámiť sa s BLE

Content

Introduction

Predtým, ako sa vrhneme do samotného vývoja aplikácie, sa zoznámime s BLE senzorom v zariadení a pozrieme sa na dostupné zariadenia v blízkom okolí.

Úloha

Do svojho zariadenia si nainštalujte aplikáciu nRF Connect for Mobile.

Úloha

Pomocou nástroja nRF Connect zistite, aké BLE zariadenia sa nachádzajú vo vašom okolí.

Aby všetko prebehlo úspešne, vaše zariadenie musí byť vybavené Bluetooth-om a tento musí byť zapnutý. Pre zapnutie Bluetooth vo vašom telefóne hľadajte v rýchlych nastaveniach túto ikonu:

Bluetooth Logo

V zozname zariadení by ste mali vidieť zariadenie s názvom MJ_HT_V1, ktoré reprezentuje uvedený Xiaomi senzor.

Úloha

Zobrazte si advertisement správy zariadenia.

Informácie ako aj advertisement si zobrazíte kliknutím na položku v zozname BLE zariadení.

Na získanie informácií o aktuálnej teplote, vlhkosti a stave batérie sa k zariadeniu nepotrebujete pripájať.

Project Temperror

Projekt Temperror bude fungovať ako mobilná aplikácia pre Xiaomi BLE senzor.

V tomto kroku pripravíme všetko potrebné pre vytvorenie projektu.

Úloha

Vytvorte nový projekt s názvom Temperror, ktorý sa bude nachádzať v balíku sk.tuke.smart.temperror a umiestnite ho do priečinka temperror/.

Úloha

V súbore config.xml doplňte opis aplikácie spolu s informáciami o vás ako o autorovi aplikácie.

Úloha

Pridajte do projektu platformu android a nainštalujte plugin cordova-plugin-ble-central.

Úloha

Nahraďte .html súbor aplikácie a .css súbor jej vzhľadu nasledovnými súbormi.

Ako kostru vašej aplikácie použite tento súbor index.html:

<!DOCTYPE html>
<html>
    <head>
        <meta name="format-detection" content="telephone=no" />
        <meta name="msapplication-tap-highlight" content="no" />
        <meta name="viewport" content="user-scalable=no, initial-scale=1, maximum-scale=1, minimum-scale=1, width=device-width" />
        <link rel="stylesheet" type="text/css" href="css/index.css" />
        <title>Temperror</title>
    </head>

    <body>
        <div id="app">
            <div id="temperature"></div>
            <div id="humidity">0%</div>
            <div id="battery">0%</div>
        </div>

        <script type="text/javascript" src="cordova.js"></script>
        <script type="text/javascript" src="js/index.js"></script>
    </body>
</html>

Ako kostru kaskádnych štýlov vašej aplikácie použite tento .css súbor:

#app {
    display: flex;
    flex-direction: column;
    justify-content: center;
    align-items: center;
    text-align: center;
    min-height: 100vh;
}

#temperature {
    font-size: 7em;
}

#humidity {
    font-size: 2em;
}

Úloha

Overte svoju implementáciu.

Scan and Discover BLE Peripherals

V tomto kroku zistíme, aké BLE zariadenia sa nachádzajú v okolí.

Úloha

Po udalosti deviceready zavolajte funkciu ble.scan().

Funkcia ble.scan() oskanuje a zistí, aké BLE zariadenia sa nachádzajú v blízkom okolí.

ble.scan([], 
        5, 
        function(device) {
            console.log(JSON.stringify(device));
        }, 
        function(error){
            console.error(error);
        }
);

Úloha

Miesto zobrazenia všetkých zariadení vyfiltrujte len tie, ktoré sa volajú MJ_HT_V1.

Filtrovanie vyriešime jednoducho - pomocou príkazu if, v ktorom sa opýtame na názov zariadenia:

ble.scan([], 
        5, 
        function(device) {
            if(device.name === 'MJ_HT_V1'){
                console.log(JSON.stringify(device));
            }
        }, 
        function(error){
            console.error(error);
        }
);

Advertising

V tomto krokou získame informácie o teplote, vlhkosti a úrovni batérie senzora. Tie sa nachádzajú v advertising-u vysielaného zariadenia.

Úloha

Zobrazte obsah členskej premennej advertising objektu zariadenia.

Informácie o aktuálnej teplote, vlhkosti a stave batérie, sa nachádzajú v advertising-u. Ich konkrétnu podobu je možné vidieť práve v aplikácii nRF Connect. Aby sme s týmito údajmi vedeli pracovať, vypíšeme si ich na obrazovku cez objekt zariadenia:

console.log(device);

Výstup môže vyzerať napríklad takto:

{
    advertising: ArrayBuffer(62) {}
    id: "58:2D:34:32:D3:1F"
    name: "MJ_HT_V1"
    rssi: -52
}

Členská premenná advertising je teda typu ArrayBuffer. Aby sme s dátami vedeli pracovať, potrebujeme z neho spraviť pole konkrétneho typu. Vzhľadom na povahu údajov z neho spravíme typ Uint8Array:

const data = new Uint8Array(device.advertising);

S obsahom tohto poľa už nie je problém pracovať - k jednotlivým prvkom je možné pristupovať pomocou index operátora [].

Pri porovnávaní údajov z aplikácie nRF Connect a obsahom poľa data si všimneme, že obsah advertising-u je v aplikácii nRF Connect reprezentovaný v šestnástkovej sústave. Aby sme mohli tieto údaje porovnať, vytvoríme funkciu toHexString(), ktorá príslušnú konverziu poľa zabezpečí:

function toHexString(byteArray) {
    return Array.from(byteArray, function(byte) {
        return ("0" + (byte & 0xff).toString(16)).slice(-2);
    }).join("");
}

Následne môžeme obsah poľa vypísať ako reťazec hex-a znakov:

console.log(toHexString(data));

Následné porovnanie advertising-u z aplikácie nRF Connect (z režimu RAW) a reťazca hex znakov je rovnaké (až na rozličnú dĺžku).

Úloha

Z advertisement-u zistite, aká je teplota a vlhkosť na senzore.

Na to potrebujeme získať údaje služby s id 0xfe95. Aby sme sa k nim dostali, vystrihneme z údajov advertisement-u pole začínajúce od indexu 5. Upravíme teda vytvorenie poľa typu Uint8Array nasledovne:

const data = new Uint8Array(device.advertising).slice(5);

Senzor Xiaomi Mijia posiela 4 rozličné typy správ advertising-u. Tieto správy závisia od hodnoty uloženej na indexe 13. Význam jednotlivých hodnôt ilustruje nasledovná tabuľka:

data[13] type of value bytes to read
0x04 temperature 16 + 17
0x06 humidity 16 + 17
0x0a battery 16
0x0d temperature and humidity temp: 16 + 17
hum: 18 + 19

Po vytvorení 16b z dvoch 8b hodnôt je ešte potrebné hodnotu teploty a vlhkosti vydeliť hodnotou 10. Teplota je udávaná v stupňoch celzia a vlhkosť v percentách.

Obyčajne môžu údaje služby z advertising-u vyzerať napríklad takto:

95 FE 50 20 AA 01 57 29 AE 33 34 2D 58 0D 10 04 DB 00 CC 01 čo zodpovedá teplote 21.9 stupňov celzia a vlhkosti na úrovni 46%.

95 FE 50 20 AA 01 5E 29 AE 33 34 2D 58 06 10 02 CB 01 čo zodpovedá vlhkosti na úrovni 45.9%.

Z uvedených príkladov je možné vidieť, že pre uloženie údajov je použitý malý endián.

Najčastejšie bude dochádzať k odosielaniu správ typu 0x0d. Typ správy si teda môžeme pre kontrolu vypísať:

console.log(data[13]);

Následne môžeme ošetriť výskyt typu 0x0d pomocou príkazu switch. Pre ilustráciu uvádzame zostavenie hodnoty teploty pomocou bitových operácií a zostavenie hodnoty vlhkosti aritmetickým výpočtom:

switch(data[13]){
    case 0x0d:
        const temp = (256 * data[17] + data[16]) / 10;
        const hum = ((data[19] << 8) | data[18]) / 10;
        break;
}

console.log(temp);
console.log(hum);

Podobným spôsobom môžeme ošetriť aj ostatné typy správ. Výsledná podoba volania funkcie ble.scan() môže vyzerať napr. nasledovne:

ble.scan(
    [],
    3,
    function(device) {
        if (device.name === "MJ_HT_V1") {
            const data = new Uint8Array(device.advertising).slice(5);

            switch (data[13]) {
                case 0x0d:
                    app.temperature = (256 * data[17] + data[16]) / 10;
                    app.humidity = ((data[19] << 8) | data[18]) / 10;
                    break;
                case 0x04:
                    app.temperature = (256 * data[17] + data[16]) / 10;
                    break;
                case 0x06:
                    app.humidity = ((data[19] << 8) | data[18]) / 10;
                    break;
                case 0x0a:
                    app.battery = data[16];
                    break;
                default:
                    console.error(`Unknown type of advertising: ${type}.`);
            }
        }

        temperature.innerText = `${app.temperature}°`;
        humidity.innerText = `${app.humidity} %`;
        battery.innerText = `${app.battery} %`;
    },
    function(error) {
        console.error(error);
    }
);

Úloha

Overte správnosť svojej implementácie.

Ak ste postupovali správne, na obrazovke vášho mobilného zariadenia by ste mali vidieť aktuálnu teplotu a vlhkosť v miestnosti, ako aj stav batérie senzora.

Additional Tasks

  1. Zabezpečte, aby sa skenovanie spúšťalo v pravidelných intervaloch. Pre realizáciu úlohy môžete použiť napr. volanie funkcie setInterval(). Dajte si však pozor na to, aby bol čas skenovania kratší, ako interval opätovného spúšťania.
  2. Upravte aplikáciu tak, aby dokázala zobraziť aj zoznam viacerých dostupných senzorov tohto typu a až po kliknutí na vybraný zobrazí jeho aktuálne hodnoty.