Node-RED: ESP8266 a DS18B20

Zápisník experimentátora

Hierarchy: Node.js

V dnešním článku propojíme dvě úžasné technologie. Node-RED a jeho toky nám budou sloužit jako server a ESP8266 budeme používat jako HTTP klienta, který bude na server odesílat naměřenou teplotu. V tomto článku využijeme znalosti ze dvou předchozích článků. Arduino nahradíme za ESP8266 a měřenou teplotu nebudeme přenášet přes sériový port, ale pomocí protokolu HTTP. V Node-RED budeme používat HTTP server, na který bude ESP8266 odesílat naměřenou teplotu. Pojďme na to.

Zpět do minulosti

V těchto článcích najdete informace, které mohou být pro vás užitečné. V článcích jsem popisoval Node-RED, ESP8266 a měření teploty pomocí senzoru DS18B20.

Seznam součástek

Budeme potřebovat tyto součástky:

  • ESP8266 {linkESP8266}
  • Breadboard {linkBreadboard}
  • DS18B20 {linkDS18B20}
  • Rezistor 4k7 {linkR}

Součástky jsou zapojeny podle tohoto schématu. Na pinu D6 musí být zapojen pull-up rezistor. Senzor teploty je zapojen normálně na GND a 3,3 V. Pokud máte originální senzory teploty, můžete je zapojit i v parazitické napájení. Můžete použít libovolné množství senzorů. Program v ESP8266 i v Node-RED je na to připraven.

Node-RED

Node-RED je tokově orientované prostředí, ve kterém vytváříte toky dat mezi jednotlivými uzly. Takové prostředí se hodí pro zpracování dat, které vznikají při měření teploty. Na jednom místě, nebo i na více místech se změří teplota a ta se následně někde musí zpracovat. Node-RED na to poskytuje ideální prostředí, protože v něm můžete snadno určit, odkud se data berou a kam se mají následně odeslat. To vše děláte v grafickém prostředí, kde si přidáváte na plochu uzly, které propojujete pomocí propojovacích čar. Čáry představují samotný tok dat z jednoho uzlu do druhého.

Node-RED najdete na Internetu na stránce https://nodered.org/. Na stránce najdete popis, jak vývojové prostředí nainstalovat, ale některé podrobnosti tam dostatečně nezdůrazňují. Důležitá informace je to, že musíte mít oprávnění administrátora. Ty získáte na Linuxu pomocí příkazu sudo a ve Windows tak, že spustíte konzolu v režimu administrátora. Podrobnosti o instalaci naleznete v článku, ve kterém popisuji proces instalace.

V článku o ladění HTTP endpoint jsem popsal, jak lze odladit vstupní bod v toku s názvem HTTP ESP8266 Request. Popsal jsem formát vstupních dat a co se s údaji dále dělá. Základní myšlenkou boto to, že ESP8266 měří teplotu pomocí libovolného množství senzorů DS18B20. Každé jedno měření může mít proto 0-N hodnot. Proto mají vstupní údaje formát pole objektů se dvěma parametry.

[
    {
        "id": "0011223344556677",
        "value": 25
    },
    {
        "id": "0011223344556688",
        "value": 26
    }
]

V článku jsem popsal, jak se tyto údaje validovány a jak se transformují z pole údajů na tok jednotlivých údajů. To má za úkol uzel splitArray. Ten má jeden vstupní bod a tři výstupní body. Vstupem je pole naměřených teplot a výstupem mohou být tři různé body. Uzel validuje vstup a podle toho se rozhodne, zda naměřené teploty akceptuje nebo odmítne.

  • http (400) - Pokud vstupní údaje neodpovídají požadavkům, je vrácena odpověď 400 z HTTP serveru.
  • http (200) - Pokud jsou vstupní údaje v pořádku, je vrácena odpověď 200 z HTTP serveru. V odpovědi je zaslán počet zpracovaných měření. Tuto odpověď lze v HTTP klientovi zkontrolovat a na jejím základě udělat nějaké dodatečné kroky.
  • Valid request - V tomto příkladu je tímto uzlem pouze výpis na debug konzoli. Sem přijdou postupně všechny naměřené teploty a tok je může nějakým způsobem zpracovat. Například je uložit do databáze, nebo odeslat na nějaký další server ke zpracování.

splitArray

Takto vypadá zdrojový kód uzlu splitArray v JavaScriptu. Jednotlivé použité příkazy jsou přizpůsobeny toku, který Node-RED poskytuje. To znamená, že názvy některých proměnných jsou pevně určeny. Je to například proměnná msg, kterou Node-RED používá jako vstupní proměnnou do vaší funkce. První příkaz ověřuje, zda je msg.payload pole. Pokud není, je odeslána první odpověď s chybou. Protože odpověď můžeme nasměrovat do některého ze tří výstupů, musíme ji zapsat jako pole odpovědí. V tomto případě odpověď zasíláme do prvního výstupu, proto je formát zprávy ve tvaru pole [msg, null, null].

Následně můžeme zkontrolovat jednotlivé prvky pole. To provádíme pomocí ověření existence vyžadovaných parametrů a pomocí regulérního výrazu, kterým ověříme, že je obsah parametru id dlouhý 16 znaků a představuje ID čidla teploty DS18B20. Pokud je vše v pořádku, vytvoříme novou zprávu, kterou nasměrujeme do výstupu číslo 3.

V této chvíli ještě nebyla odeslána odpověď do ESP8266, které na ni v té chvíli stále čeká. Ta se odešle až po zpracování všech prvků pole a v odpovědi odešleme počet zpracovaných záznamů pomocí výstupu 2. V té chvíli má ESP8266 možnost zpracovat odpověď ze serveru.

if(Array.isArray(msg.payload)===false) {
    msg.payload = { error: 'Bad request'};
    return [msg, null, null];
}

const regex = /[0-9A-Fa-f]{16}/g;

for(let element in msg.payload) {
    let e = msg.payload[element];

    if(e.id===undefined || e.value===undefined) {
        msg.payload = { error: 'Bad element parameters'};
        return [msg, null, null];
    }
    if(!e.id.match(regex)) {
        msg.payload = { error: 'Bad element id'};
        return [msg, null, null];
    }
    node.send([null, null, {"payload": e}]);
}

msg.payload = { ok: 'Handled: '+msg.payload.length};
return [null, msg, null];

ESP8266

Nyní následuje zdrojový kód programu pro ESP8266. Aby tento program nebyl jen jednoduchou variaci na měření teploty, naprogramoval jsem v něm několik ukázek různých technik, které mohou být pro vás užitečné.

  • Použití struktury pro uložení konfigurace. Struktura je deklarována jako typ RedConfig a obsahuje interval, jak často se má měřit teplota a URL serveru, kam se teplota odesílá. Samotná proměnná, která obsahuje konfiguraci se jmenuje cfg. Implicitní hodnoty snadno vyplníme použitím konstruktoru, což je pouze metoda ve struktuře se stejným názvem.
  • Uložení konfigurace v souborovém systému SPIFFS. Konfigurace, která je natvrdo naprogramovaná ve zdrojovém kódu, má své nevýhody. Například vám nemusí vyhovovat nastavený interval měření a HTTP server v Node-RED bude mít určitě jinou IP adresu. Proto je vhodné mít tuto konfiguraci uloženou tak, aby se dala snadno upravovat. V tomto programu jsem použil uložení konfigurace v souborovém systému SPIFFS. Další možností je například WiFiManager. O načtení konfigurace se stará funkce loadConfig.
  • Použití knihovny ArduinoJson na parsování konfigurace, která je uložena v SPIFFS. Ve funkci loadConfig se nachází i zdrojový kód, který pomocí této knihovny rozparsuje konfiguraci v JSON souboru. Autor knihovny odvedl dobrou práci a parsování pomocí knihovny je směšně jednoduché. Při konverzi některých hodnot na String je třeba kompilátou trošku pomoci a přetypovat hodnotu parametru na const char *. Ostatní konverze knihovna zvládne sama. Zdrojový kód je pro verzi 6. V starších verzích knihovny vypadá zdrojový kód mírně odlišně.
  • Použití knihovny ArduinoJson k vytvoření JSON dokumentu s naměřenou teplotou. Knihovna poskytuje i pohodlné funkce pro vytvoření JSON dokumentu. Příklad použití najdete ve funkci sendData. Opět je na to třeba jen několik řádků zdrojového kódu. A to přesto, že vytváříme pole objektů. Pro každý senzor vytvoříme jeden objekt. Všimněte si, že pro vytvoření parametru id použijeme funkci sprintf. Takové vytvoření řetězce znaků ve světě Arduino vidíte zřídka. Většinou se programátoři uchylují k šíleným konstrukcím místo aby pořádně využili funkce, které jsou k tomu určeny.
  • Ošetření možných chyb HTTP klienta. Ošetření chyb by nemělo chybět v žádném programu. Může totiž nastat mnoho chybových stavů. Například nebude fungovat cílový server, nebo se mu něco nebude zamlouvat na vašich údajích a odmítne s nimi pracovat. Na to vše byste měli myslet a ve zdrojovém kódu najdete několik příkladů, jak se vypořádat s chybami.
#include <ESP8266WiFi.h>
#include <ESP8266HTTPClient.h>
#include <FS.h>
#include "arduino_secret.h"
#include <OneWire.h>
#include <DallasTemperature.h>
#include <ArduinoJson.h>

#define ONE_WIRE_BUS D6

OneWire oneWire(ONE_WIRE_BUS);
DallasTemperature sensors(&oneWire);

struct RedConfig {
  long timeout;
  unsigned long previous;
  String server;

  RedConfig()
  : timeout(30000),
    previous(0),
    server("http://192.168.0.150:1880/sendtemperature")
    {}
};

RedConfig cfg;

void loadConfig() {
  File f = SPIFFS.open("/config.json", "r");
  if (!f) {
    Serial.println("File '/config.json' open failed.");
    return;
  }

  StaticJsonDocument<256> doc;
  deserializeJson(doc, f);
  cfg.server = (const char*)doc["server"];
  cfg.timeout = doc["timeout"];
  f.close();
  Serial.printf("New config server: %s\n", cfg.server.c_str());
  Serial.printf("New config timeout: %d\n", cfg.timeout);
}

void sendData() {
  if (WiFi.status() == WL_CONNECTED) {
    int numberOfDevices = sensors.getDeviceCount();
    if (numberOfDevices > 0) {
      sensors.requestTemperatures();
      StaticJsonDocument<1024> array;

      for (int i = 0; i < numberOfDevices; i++) {
        DeviceAddress tda;
        sensors.getAddress(tda, i);
        char id[17];
        sprintf(id, "%02X%02X%02X%02X%02X%02X%02X%02X",
                tda[0], tda[1], tda[2], tda[3],
                tda[4], tda[5], tda[6], tda[7]);
        float t = sensors.getTempCByIndex(0);

        JsonObject nested = array.createNestedObject();
        nested["id"] = id;
        nested["value"] = t;
      }
      serializeJson(array, Serial);
      Serial.println("");

      HTTPClient http;
      bool connected = http.begin(cfg.server);
      if (connected) {
        http.addHeader("Content-Type", "application/json");
        String output;
        serializeJson(array, output);
        int httpCode = http.POST(output);
        Serial.print("http.POST=");
        Serial.print(httpCode);
        if (httpCode < 0) {
          Serial.print(" ");
          Serial.print(http.errorToString(httpCode));
        }
        Serial.println("");
        if (httpCode == HTTP_CODE_OK || httpCode == HTTP_CODE_BAD_REQUEST) {
          String payload = http.getString();
          Serial.println(payload);
        }
      }
      else
        Serial.println("http.begin error");
    }
  }
}

void setup() {
  pinMode(LED_BUILTIN, OUTPUT);
  Serial.begin(115200);
  WiFi.begin(ssid, password);
  Serial.println("");

  // Wait for connection
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  Serial.println("");
  Serial.print("Connected to ");
  Serial.println(ssid);
  Serial.print("IP address: ");
  Serial.println(WiFi.localIP());

  sensors.begin();
  SPIFFS.begin();
  loadConfig();
}

void loop() {
  unsigned long current = millis();
  if (current - cfg.previous >= cfg.timeout) {
    cfg.previous = current;
    digitalWrite(LED_BUILTIN, LOW);
    sendData();
    digitalWrite(LED_BUILTIN, HIGH);
  }
}

config.json

Soubor config.json je nutné nahrát do SPIFFS pomocí příkazu menu Tools/ESP8266 Sketch Data Upload.

{
"server": "http://192.168.0.150:1880/sendtemperature",
"timeout": 20000
}

Zdrojový kód

Zdrojový kód se nachází na serveru GitHub.


08.09.2019


Menu