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.

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.
Budeme potřebovat tyto součástky:
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 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.
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];
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é.
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.loadConfig.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ě.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.
#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);
}
}
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 se nachází na serveru GitHub.
08.09.2019