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