Node-RED: ESP8266 a DS18B20

Zápisník experimentátora

Hierarchy: Node.js

V dnešnom článku prepojíme dve úžasné technológie. Node-RED a jeho toky nám budú slúžiť ako server a ESP8266 budeme používať ako HTTP klienta, ktorý bude na server odosielať nameranú teplotu. V tomto článku využijeme vedomosti z dvoch predchádzajúcich článkov. Arduino nahradíme za ESP8266 a meranú teplotu nebudeme prenášať cez sériový port, ale pomocou protokolu HTTP. V Node-RED budeme používať HTTP server, na ktorý bude ESP8266 odosielať nameranú teplotu. Poďme na to.

Naspäť do minulosti

V týchto článkoch nájdete informácie, ktoré môžu byť pre vás užitočné. V článkoch som popisoval Node-RED, ESP8266 a meranie teploty pomocou senzora DS18B20.

Zoznam súčiastok

Budeme potrebovať tieto súčiastky:

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

Súčiastky sú zapojené podľa tejto schémy. Na pine D6 musí byť zapojený pull-up rezistor. Senzor teploty je zapojený normálne na GND a 3,3 V. Ak máte originálne senzory teploty, môžete ho zapojiť aj v parazitickom napájaní. Môžete použiť ľubovoľné množstvo senzorov. Program v ESP8266 aj v Node-RED je na to pripravený.

Node-RED

Node-RED je tokovo orientované prostredie, v ktorom vytvárate toky dát medzi jednotlivými uzlami. Takéto prostredie sa hodí na spracovanie údajov, ktoré vznikajú pri meraní teploty. Na jednom mieste, alebo aj na viacerých miestach sa odmerá teplota a tá sa následne niekde musí spracovať. Node-RED na to poskytuje ideálne prostredie, pretože v ňom môžete ľahko určiť, odkiaľ sa dáta berú a kam sa majú následne odoslať. To všetko robíte v grafickom prostredí, kde si pridávate na plochu uzly, ktoré prepájate pomocou prepojovacích čiar. Čiary predstavujú samotný tok dát z jedného uzla do druhého.

Node-RED nájdete na Internete na stránke https://nodered.org/. Na stránke nájdete popis, ako vývojové prostredie nainštalovať, ale niektoré podrobnosti tam dostatočne nezdôrazňujú. Dôležitá informácia je to, že musíte mať oprávnenia administrátora. Tie získate na Linuxe pomocou príkazu sudo a vo Windows tak, že spustíte konzolu v režime administrátora. Podrobnosti o inštalácii nájdete v článku, v ktorom popisujem proces inštalácie.

V článku o ladení HTTP endpointu som opísal, ako sa dá odladiť vstupný bod v toku s názvom HTTP ESP8266 Request. Opísal som formát vstupných údajov a čo sa s údajmi ďalej robí. Základnou myšlienkou boto to, že ESP8266 meria teplotu pomocou ľubovoľného množstva senzorov DS18B20. Každé jedno meranie môže mať preto 0-N hodnôt. Preto majú vstupné údaje formát poľa objektov s dvomi parametrami.

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

V článku som opísal, ako sa tieto údaje validujú a ako sa transformujú z poľa údajov na tok jednotlivých údajov. To má za úlohu uzol splitArray. Ten má jeden vstupný bod a tri výstupné body. Vstupom je pole nameraných teplot a výstupom môžu byť tri rôzne body. Uzol validuje vstup a podľa toho sa rozhodne, či namerané teploty akceptuje alebo odmietne.

  • http (400) - Ak vstupné údaje nezodpovedajú požiadavkám, je vrátená odpoveď 400 z HTTP servera.
  • http (200) - Ak sú vstupné údaje v poriadku, je vrátená odpoveď 200 z HTTP servera. V odpovedi je zaslaný počet spracovaných meraní. Túto odpoveď môžete v HTTP klientovi skontrolovať a na jej základe urobiť nejaké dodatočné kroky.
  • Valid request - V tomto príklade je týmto uzlom iba výpis na debug konzolu. Sem prídu postupne všetky namerané teploty a tok ich môže nejakým spôsobom spracovať. Napríklad ich uložiť do databázy, alebo odoslať na nejaký ďalší server na spracovanie.

splitArray

Takto vyzerá zdrojový kód uzla splitArray v javascripte. Jednotlivé použité príkazy sú prispôsobené toku, ktorý Node-RED poskytuje. To znamená, že názvy niektorých premenných sú pevne určené. Je to napríklad premenná msg, ktorú Node-RED používa ako vstupnú premennú do vašej funkcie. Prvý príkaz overuje, či je msg.payload pole. Ak nie je, je odoslaná prvá odpoveď s chybou. Pretože odpoveď môžeme nasmerovať do niektorého z troch výstupov, musíme ju zapísať ako pole odpovedí. V tomto prípade odpoveď zasielame do prvého výstupu, preto je formát správy v tvare poľa [msg, null, null].

Následne môžeme skontrolovať jednotlivé prvky poľa. To robíme pomocou overenia existencie vyžadovaných parametrov a pomocou regulérneho výrazu, ktorým overíme, že je obsah parametra id dlhý 16 znakov a predstavuje ID senzora teploty DS18B20. Ak je všetko v poriadku, vytvoríme novú správu, ktorú nasmerujeme do výstupu číslo 3.

V tejto chvíli ešte nebola odoslaná odpoveď do ESP8266, ktoré na ňu v tej chvíli stále čaká. Tá sa odošle až po spracovaní všetkých prvkov poľa a v odpovedi odošleme počet spracovaných záznamov pomocou výstupu 2. V tej chvíli má ESP8266 možnosť spracovať odpoveď zo servera.

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

Teraz nasleduje zdrojový kód programu pre ESP8266. Aby tento program nebol len jednoduchou variáciu na meranie teploty, naprogramoval som v ňom niekoľko ukážok rôznych techník, ktoré môžu byť pre vás užitočné.

  • Použitie štruktúry na uloženie konfigurácie. Štruktúra je deklarovaná ako typ RedConfig a obsahuje interval, ako často sa má merať teplota a URL servera, kam sa teplota odosiela. Samotná premenná, ktorá obsahuje konfiguráciu sa volá cfg. Implicitné hodnoty ľahko vyplníme použitím konštruktora, čo je iba metóda v štruktúre s rovnakým názvom.
  • Uloženie konfigurácie v súborovom systéme SPIFFS. Konfigurácia, ktorá je natvrdo naprogramovaná v zdrojovom kóde, má svoje nevýhody. Napríklad vám nemusí vyhovovať nastavený interval merania a HTTP server v Node-RED bude mať určite inú IP adresu. Preto je vhodné mať túto konfiguráciu uloženú tak, aby sa dala ľahko upravovať. V tomto programe som použil uloženie konfigurácie v súborovom systéme SPIFFS. Ďalšou možnosťou je napríklad WiFiManager. O načítanie konfigurácie sa stará funkcia loadConfig.
  • Použitie knižnice ArduinoJson na parsovanie konfigurácie, ktorá je uložená v SPIFFS. Vo funkcii loadConfig sa nachádza aj zdrojový kód, ktorý pomocou tejto knižnice rozparsuje konfiguráciu v JSON súbore. Autor knižnice odviedol dobrú prácu a parsovanie pomocou knižnice je smiešne jednoduché. Pri konverzii niektorých hodnôt na String je potrebné kompilátou trošku pomôcť a pretypovať hodnotu parametra na const char*. Ostatné konverzie knižnica zvládne sama. Zdrojový kód je pre verziu 6. V starších verziách knižnice vyzerá zdrojový kód mierne odlišne.
  • Použitie knižnice ArduinoJson na vytvorenie JSON dokumentu s nameranou teplotou. Knižnica poskytuje aj pohodlné funkcie na vytvorenie JSON dokumentu. Príklad použitia nájdete vo funkcii sendData. Opäť je na to treba iba niekoľko riadkov zdrojového kódu. A to napriek tomu, že vytvárame pole objektov. Pre každý senzor vytvoríme jeden objekt. Všimnite si, že na vytvorenie parametra id použijeme funkciu sprintf. Takéto vytvorenie reťazca znakov vo svete Arduina vidíte zriedkavo. Väčšinou sa programátori uchyľujú k šialeným konštrukciam namiesto toho, aby poriadne využili funkcie, ktoré sú na to určené.
  • Ošetrenie možných chýb HTTP klienta. Ošetrenie chýb by nemalo chýbať v žiadnom programe. Môže totiž nastať veľa chybových stavov. Napríklad nebude fungovať cieľový server, alebo sa mu niečo nebude pozdávať na vašich údajoch a odmietne s nimi pracovať. Na to všetko by ste mali myslieť a v zdrojovom kóde nájdete niekoľko príkladov, ako sa vysporiadať 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

Súbor config.json je nutné nahrať do SPIFFS pomocou príkazu menu Tools/ESP8266 Sketch Data Upload.

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

Zdrojový kód

Zdrojový kód sa nachádza na serveri GitHub.


07.09.2019


Menu