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.
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.
Budeme potrebovať tieto súčiastky:
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 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.
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];
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é.
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.loadConfig
.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.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é.
#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);
}
}
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 sa nachádza na serveri GitHub.
07.09.2019