Pojďme programovat elektroniku | Bluetooth | Navigace

Pojďme programovat elektroniku: Vyrobíme si štěnici, která bude sledovat pohyb oběti v budově

  • Dnes si vyzkoušíme, jak funguje navigace uvnitř budov
  • Namísto GPS v mobilu nám pomůže drobný Bluetooth vysílač
  • A hromada přijímačů v budově

Minule jsme si postavili pásový tank s infračervenou kamerou pro noční vidění, v dnešním seriálu o programování elektroniky však velkotonážní stroje opustíme a vrátíme se zpět k malým destičkám. Bude jich hned několik a budou rozmístěné po našem redakčním patře.

První destička bude přímo v naší kanceláři, druhá bude v kuchyňce a třetí v nejzazší místnosti, kterou okupuje Tomáš Holčík a dnem a nocí tam stříhá nejen videa pro Živě.cz ale i další weby.

Jak už jsem prozradil v nadpisu, tentokrát si totiž postavíme primitivní lokalizační systém uvnitř budovy – indoor navigaci, a to pomocí drobného čipu CC2541 od Texas Instruments, který podporuje Bluetooth 4.0 Low Energy (BLE).

Zatímco BLE vysílač bude křičet do okolí, že tam někde je, ostatní destičky budou tento řev jako přijímače poslouchat a už skrze Wi-Fi přeposílat do základny, která se pak pokusí odhadnout, v jaké místnosti se BLE čip zrovna nachází.

Čínský bluetooth knoflík s konfigurací pomocí AT příkazů

Čip je připájený na malé čínské destičce, kterou pod názvem iBeacon aj. najdete na eBayi a dalších e-shopech zhruba za stokorunu. Podstatné je to, že na spodní straně destičky je pouzdro pro 3V mincovou baterii CR2032, která poskytne dostatek napětí a proudu k tomu, aby čip periodicky posílal do nejbližšího okolí tzv. advertising data (to je vlastně ten křik). Je to jen pár bajtů, ve kterých jsou ale uloženy všechny základní identifikační informace o vysílači. Tedy především jeho MAC adresa a název.

e07c6bfa-6a3e-429a-9406-d8ddd3b7a5616b8cfbef-f253-4c26-8551-0c64d9e016e9c60f3ed7-c5a8-4fe0-b88e-db1e5506d32938d77ca7-f860-4881-a4fa-f191e4c4af0d
Čínský modul s čipem BLE 4.0 CC2541 a pozdrem na 3V baterii CR2032

Čínská destička s BLE čipem má několik otvorů, na které lze připájet pin header sériové linky, díky které můžete změnit výchozí konfiguraci čipu pomocí několika AT příkazů, jejichž seznam najdete třeba v tomto PDF dokumentu.

3abc9184-cd09-4711-9406-f4c144d940b1e0170445-d069-4670-a917-08ebc93445a936179d85-a314-4969-829c-6c863ca912a5
Připájel jsem na destičku pole pinů pro připojení sériové linky pomocí USB/TTL převodníku

Připojil jsem tedy destičku skrze USB/TTL převodník k počítači, otevřel libovolný terminál sériové linky výchozí rychlostí 9 600 b/s a pomocí příkazu AT+NAMEBLEBOBIK01 změnil název na BLEBOBIK01. Právě pod tímto jménem nyní bude čip vystupovat v síti Bluetooth.

Ve výchozím výrobním stavu nicméně čip vysílá advertising data na můj vkus zbytečně rychle (každých 1 285 ms), a tak jsem prodlevu zvýšil na 3 000 ms příkazem AT+ADVIB (B podle dokumentace představuje právě 3 sekundy).

2219cf8d-c182-43c7-9df2-1a2750afba611ec5d228-5b38-4f04-ae8e-be3006ac35dd
Změna jména knoflíku a intervalu zasílání advertising dat v sériovém terminálu RealTerm

Podobně bych mohl změnit i MAC adresu a další údaje, ale mně toto stačí, na přijímačích totiž budu knoflík identifikovat podle jeho jména. Když přijímač zachytí data od BLE zařízení s názvem BLEBOBIK01, zpracuje je a vše ostatní zahodí.

Podle síly signálu odhadneme místnost

Díky tomu, že veškerá rádiová komunikace knoflíku probíhá skrze úsporný protokol Bluetooth 4.0 Low Energy, během kratičké fáze vysílání čip spaluje jen několik miliampérů proudu a v době klidu ještě o nějaký ten řád méně. Celý modul velikosti knoflíku od kabátu by tak měl vydržet relativně dlouho.

Když si jej strčíme třeba do kapsy od kalhot a zajdeme si s ním do kuchyňky uvařit kávu, při naší cestě budovou jeho pulzy s advertising daty postupně zachytí přijímače, které budou připojené k redakční Wi-Fi a dají o tom vědět centrále.

Kolega Vláďa Kluska si jde do kuchyňky pro kávu. Můj počítač sleduje každý jeho krok:

Centrála bude vždy deset sekund sbírat zprávy od přijímačů, a pokud nějaké dorazí, seřadí je podle kvality signálu mezi přijímačem a knoflíkem. Jelikož bude každý z přijímačů v jiné místnosti, pravděpodobně se budeme nacházet v té, kde přijímač hlásí nejlepší kvalitu spojení.

Ukažme si to na příkladu níže. Během deseti sekund dorazily na centrálu tyto zprávy:

  1. přijímač (redakce Živě.cz): -80 dBm
  2. přijímač (kuchyňka): -32 dBm
  3. přijímač (Tomášova kancelář) -50 dBm

Pokud bychom tyto hodnoty seřadili od nejvyšší (od nejlepšího signálu), nejlépe z toho vyjde kuchyňka (-32 dBm), a tak centrála do sériové linky vypíše, že jsem pravděpodobně v kuchyňce. Z dat je ale patrné, že měl knoflík v posledních deseti sekundách poměrně solidní spojení i s přijímačem u Tomáše (-50 dBm), takže ve skutečnosti jsem možná někde na půli cesty mezi kuchyňkou a Tomášem.

Kdybychom měli po ruce plán budovy, mohli bychom každému ze stacionárních přijímačů přidělit pevné souřadnice X a Y, a podle síly signálu každého z nich odhadnout přesnější polohu.

Ostatně, podobným způsobem funguje třeba lokalizace Googlu jen pomocí Wi-Fi nebo mobilních vysílačů BTS, když není k dispozici GPS. A stejně nakonec funguje i obecný princip rádiové indoor navigace, kdy podle známé polohy stacionárních přijímačů odhadujeme svoji vlastní podle kvality signálu ke každému z nich.

My se dnes však spokojíme jen s tímto diskrétním určením místnosti. Buď budeme v redakci Živě.cz, v kuchyňce, na návštěvě u Tomáše, anebo někde jinde – na nám zatím neznámém místě.

Jako přijímače poslouží desky s čipem ESP32. Vedle Wi-Fi totiž umí i BLE.

Náš knoflík, který do nejbližšího okolí periodicky vysílá BLE advertising data, tedy už máme, ale co ty přijímače? Musejí mít BLE rádio a také Wi-Fi, aby dokázaly údaj o tom, že zachytily zprávu BLE, odeslat do centrály.

0bc70f9e-231e-41de-a0a6-d75fc25e1a32
Jako BLE přijímače poslouží tři různé prorotypovací destičky s čipem ESP32

A modří už vědí – ano, jako přijímač poslouží libovolná prototypovací destička s čipem ESP32, který oproti staršímu a velmi populárnímu čipu ESP8266 disponuje i podporou Bluetooth 4.0. Cena těchto desek je sice relativně vyšší než v případě ESP8266 (čip je mnohem mocnější a novější), ale přesto jsem jich doma pár našel a donesl je do práce.

Přijímací destičky pošlou centrále data skrze UDP a multicast

Ještě bychom měli vymyslet nějaký elegantní způsob, jak dostat data do centrály. Mimochodem, tou centrálou bude laciná destička D1 Mini s čipem ESP8266, která bude odhadovanou polohu vypisovat skrze USB do sériové linky na počítači. Jelikož však bude její čip připojený k redakční Wi-Fi, stejně tak by mohl polohu posílat kamkoliv do internetu.

Nejprve mě přirozeně napadlo, že na D1 Mini poběží jednoduchý HTTP server, kterému budou ostatní destičky zasílat údaj o tom, jestli zachytily můj BLE knoflík. V jejich programech by však musela být natvrdo vypálená IP adresa základny, se kterou se mají spojit, což by nebylo příliš rozumné, protože bych je musel v případě změny všechny přeprogramovat a nahrát do nich nový firmware.

3e5a7691-f2fb-40d8-9c2b-31f1a9323f9b
Schéma celé komunikace. Přijímače na deskách s čipem ESP32 zachytí zprávy našeho BLE knoflíku a skrze UDP a multicast odešlou informaci o signálu do centrály. Ta podle nejsilnějšího signálu určí místnost, ve které se nacházíme.

Namísto toho použiji k odeslání dat multicast a protokol UDP, což jsme si už v našem seriálu vyzkoušeli. Takže namísto toho, aby přijímače odeslaly zprávu na HTTP server zařízení s konkrétní IP adresou, dopraví ji na univerzální multicastovou (hromadnou) IP adresu 224.0.0.0 a třeba port 8888. Naše centrála na této multicastové adrese a portu poslouchá a tyto zprávy zachytí.

e47f773f-c8f0-45ec-89db-2a4a783b58e5
Do centrály postupně přicházejí multicastové zprávičky od přijímačů rozesetých v budově. Centrála je každých deset sekund seřadí podle síly signálu a napíše místnost, kde byl signál mezi knoflíkem a přijímačem nejsilnější, takže jsem se v ní nejspíše nacházel. A skutečně, nejprve jsem šel do kuchyňky, poté do videostudia a nakonec jsem se vrátil ke svému pracovnímu stolu.

Výhoda je zřejmá. Komunikace je z našeho úhlu pohledu naprosto nezávislá na konkrétních koncových IP adresách. Ať už naše centrála a jednotlivé přijímače dostanou od routeru jakékoliv, budou-li všechny krabičky ve stejném subnetu a samotný multicast v něm nebude nikterak blokovaný, zprávy dorazí do centrály.

Zdrojový kód

A to je vlastně celé. Na úplný závěr opět nesmí chybět komentovaný kód. Ten první je určený pro vývojové prostředí Arduino a desky s čipem ESP32, na kterém poběží program přijímače. Druhý kód patří základně s čipem ESP8266. V obou programech jsem použil několik nestandardních knihoven. Tou první je ArduinoStreaming pro snazší zápis do sériové linky ve stylu standardní knihovny C++. Takže namísto:

Serial.println("Ahoj");

mohu napsat:

Serial << "Ahoj" << endl;

Kód přijímače pak potřebuje ještě knihovnu ESP32 BLE Arduino, pomocí které dokážeme zachytit advertising zprávy našeho BLE knoflíku.

Kód přijímače na čipu ESP32, který zachytí advertising data našeho knoflíku:

// PRIJIMAC, KTERY ZACHYTI BLE DATA Z KNOFLIKU
// Kod je urceny pro cip ESP32 v prostredi Arduino

// Knihovny pro praci s WiFi, seriovou linkou a UDP/multicastem
#include <WiFi.h>
#include <Streaming.h>
#include <WiFiUDP.h>

// Knihovny pro prijem dat BLE advertising
#include <BLEDevice.h>
#include <BLEUtils.h>
#include <BLEScan.h>
#include <BLEAdvertisedDevice.h>

// Identifikator prijimace
#define SONDA_ID 2

// Sken BT zarizeni v okoli bude trvat 10 sekund
int delka_skenu = 10;

// Pomocna promenna, jestli jsem uz odeslal data skrze UDP/multicast
bool sken_odeslan = false;

// SSID a heslo Wi-Fi
const char* ssid = "MalyKloboucek";
const char* heslo = "mameradikubucizka";

// Mutlicastova IP adresa 224.0.0.0
IPAddress multicast_ip(224, 0, 0, 0);
// UDP Port, treba 8888
uint16_t udp_port = 8888;
// Objekt UDP spojeni
WiFiUDP udp;

// Trida, jejiz obsah se zpracuje v okamziku, kdyz BLE subsystem cipu
// zachyti advertising data nejakeho zarizeni
class ZpracovaniSkenu : public BLEAdvertisedDeviceCallbacks {
    void onResult(BLEAdvertisedDevice advertisedDevice) {
      extern bool sken_odeslan;
      // Pokud jsem uz odeslal data, ukonci funkci
      if (sken_odeslan) {
        return;
      }

      // Ziskej jmeno zarizeni, ktere vysila advertising data
      // Funkce vrati jmeno v promenne string standardni knihovny C++
      // To je neco jineho nez Arduino String
      // http://www.cplusplus.com/reference/string/string/
      std::string jmeno = advertisedDevice.getName();

      // Ziskej kvalitu signalu mezi prijimacem a BLE zarizenim
      int8_t rssi = advertisedDevice.getRSSI();

      /* Pokud se BLE zarizeni jmenuje BLEBOBIK01,
         odesli multicastem dvoubajtovou zpravu.
         V prvnim bajtu bude ID tohoto prijimace,
         ve druhem bajtu kvalita signalu.
      */
      if (jmeno.compare("BLEBOBIK01") == 0) {
        uint8_t zprava[2] = {SONDA_ID, rssi};
        udp.beginMulticastPacket();
        udp.write(zprava, sizeof(zprava));
        udp.endPacket();
        /* V ramci tohoto skenu, ktery bude trvat az 10 sekund,
           jsem uz data o knofliku odeslal, takze zmenim stav pomocne promenne
           a budu vseechny ostatni BLE zarizeni az do dalsiho skenu ignorovat.
        */
        sken_odeslan = true;
      }
    }
};

// Funkce pro vyhledani okolnich BLE zarizeni
void hledejSondy() {
  // Vynuluj pomocnou promennou, jestli uz byly odeslany data
  sken_odeslan = false;
  // Vytvor novy sken
  BLEScan* sken = BLEDevice::getScan();
  // Pokud cip v ramci skenu prijme nejaka advertising data,
  // zpracuje je jiz drive vytvorena trida ZpracovaniSkenu
  ZpracovaniSkenu zs;
  sken->setAdvertisedDeviceCallbacks(&zs);
  sken->setActiveScan(true);
  // Spust sken a skenuj 10 sekund
  /* Proc 10 sekund, kdyz knoflik posila data kazde 3 sekundy?
     Protoze i kdyz bude v dosahu, kvůli hormade dalsich BLE zarizeni
     v okoli bych jeho data nemusel stacit zachytit. Delka skenu by mela
     byt vzdy delsi nez interval zasilani advertising dat
  */
  sken->start(delka_skenu);
  delay(100);
}

// Hlavni funkce setup, ktera se spusti po startu
void setup() {
  Serial.begin(115200);
  WiFi.mode(WIFI_OFF);
  WiFi.mode(WIFI_STA);
  Serial << "Sonda: " << SONDA_ID << endl;
  Serial << "Pripojuji se k Wi-Fi " << ssid << " ";
  // Pripojeni k Wi-Fi
  WiFi.begin(ssid, heslo);
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial << ".";
  }
  Serial << " OK!" << endl;
  Serial << "Pripojeno jako " << WiFi.localIP() << endl;

  // Nastartovani UDP/multicastu
  udp.beginMulticast(multicast_ip, udp_port);

  // Nastratovani Bluetooth Low Energy
  BLEDevice::init("");
}

// Smycka loop se opakuje stale dokola
// Volam v ni tedy stale dokola funkci pro vyhledani BLE zarizeni
void loop() {
  hledejSondy();
}

Kód centrály na čipu ESP8266, která zachytí multicastové zprávy z přijímačů a do sériové linky vypíše nejpravděpodobnější místnost, ve které se knoflík zrovna nachází:

// CENTRALA, KTERA CEKA NA DATA Z MULTICASTU A ODHADNE POLOHU
// Kod je urceny pro cip ESP8266 v prostredi Arduino

// Knihovny pro praci s WiFi, seriovou linkou a UDP/multicastem
#include <ESP8266WiFi.h>
#include <Streaming.h>
#include <WiFiUDP.h>
// Standardni C++ knihovna pro razeni (std::sort)
#include <algorithm>
// Standardni C++ knihovna pro vektor (dynamicke pole)
#include <vector>

// Kazdych 10 sekund (10 000 ms) odhadnu, kde jsem
#define INTERVAL 10000

// SSID a heslo Wi-Fi
const char* ssid = "MalyKloboucek";
const char* heslo = "mameradikubucizka";

// Mutlicastova IP adresa 224.0.0.0
IPAddress multicast_ip(224, 0, 0, 0);
// UDP Port, treba 8888
uint16_t udp_port = 8888;
// Objekt UDP spojeni
WiFiUDP udp;

// Pomocna promenna
uint64_t posledni_agregace = 0;

/* Trida Zprava, ve ktere bude ulozena multicastova zprava
   Ma definovany specialni operator pro razeni, diky kteremu
   budu moci vektor zprav setridit standardni C++ funkci std::sort
   podle jeji promenne rssi
*/
class Zprava {
  public:
    // SIla singalu
    int8_t rssi;
    // ID sondy/prijimace
    uint8_t sonda;
    bool operator< (const Zprava &z) const {
      return rssi > z.rssi;
    }
};

// C++ vektor (dynamicke pole) zprav
std::vector<Zprava> zpravy;

// Hlavni funkce setup, ktera se spusti po startu
void setup() {
  Serial.begin(115200);
  // Pripojeni k WiFi
  WiFi.mode(WIFI_OFF);
  WiFi.mode(WIFI_STA);
  Serial << "Pripojuji se k Wi-Fi " << ssid << " ";
  WiFi.begin(ssid, heslo);
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial << ".";
  }
  Serial << " OK!" << endl;
  Serial << "Pripojeno jako " << WiFi.localIP() << endl;

  // Nastartovani UDP/multicastu
  udp.beginMulticast(WiFi.localIP(), multicast_ip, udp_port);
}

// Smycka loop se opakuje stale dokola
void loop() {
  // Pokud dorazila nejaka UDP/multicast data
  udp.parsePacket();
  while (udp.available()) {
    // Precti prvni bajt ve kterem je identifikator prijimace
    uint8_t odesilatel = udp.read();
    // precti druhy bajt, ve kterem je kvalita singalu (-127;127)
    int8_t rssi = (int8_t)udp.read();
    Serial << "Prichozi zprava od " << odesilatel << " (" << udp.remoteIP() << "): " << rssi << " dBm" << endl;
    // Vytvor z dat objekt tridy Zprava a uloz jej do vektoru
    Zprava z;
    z.rssi = rssi;
    z.sonda = odesilatel;
    zpravy.push_back(z);
  }
  // Pokud od predchozi kontroly uplynulo deset sekund,
  if (millis() > (posledni_agregace  + INTERVAL)) {
    /* Pokud mam ve vektoru nejake zpravy, setrid je
       Zpravy se setridi podle promenne RSSI, protoze jsem
       v deklaraci tridy Zprava k tomu vytvoril onen
       specialni tridici operator
    */
    if (zpravy.size() > 0) {
      std::sort(zpravy.begin(), zpravy.end());
      Serial << " *** Nejsilnejsi signal v poslednich 10 sekundach " << zpravy[0].rssi << " dBm ma sonda " << zpravy[0].sonda << endl;
      // Vypis do seriove linky mistnost s nejsilnejsim signalem
      // Na indexu 0 je totiz po setrideni zprava s nejsilnejsim signalem
      switch (zpravy[0].sonda) {
        case 1:
          Serial << "Jsem v kuchynce" << endl;
          break;
        case 2:
          Serial << "Jsem v redakci Zive.cz" << endl;
          break;
        case 3:
          Serial << "Jsem u Tomase" << endl;
          break;
      }
      // Vymaz vektor
      zpravy.clear();
    }
    // Nastav pomocnou promennou na aktualni procesorovy cas,
    // aby se tento blok zpracoval zase az za dalsich deset sekund
    posledni_agregace = millis();
  }
}
Diskuze (19) Další článek: Všichni mají stejné baterie. Jak je možné, že z nich Sony vymáčkne delší výdrž?

Témata článku: , , , , , , , , , , , , , , , , , , , , , , , ,