← HTTP Polling of Multiple PLCs with Node-RED Alert Levels
Geotechnical slope monitoringESP32 PLC 14 (×4, Ethernet)HTTPCommunication
HTTP Polling of Multiple PLCs with Node-RED Alert Levels — full example
Poll multiple ESP32 PLC stations over HTTP from Node-RED and classify every reading into alert levels 0-4 with a 5-output switch. Full flow code inside.
Complete, runnable program for the ESP32 PLC 14 (×4, Ethernet) (polling-alert-classification-node-red.js): wiring header, requirements and integration notes included.
Download the full project pack — freeThis example + the related ones + bill of materials
Read-only preview.
/*
* COMPLETE EXAMPLE — HTTP polling of multiple PLCs and alert classification
*
* Hardware: Server (Node-RED) + 4x ESP32 PLC 14 Ethernet (Industrial Shields)
* Based on: geotechnical slope monitoring project (4 stations)
*
* Flow architecture (wiring in the Node-RED editor):
*
* [inject every 60 s] --> [function "prepare polling"] --> [http request]
* --> [function "classify alert"] --> [switch "level 0-4" (5 outputs)]
* output 1 (level 0) --> dashboard (normal status)
* output 2 (level 1) --> dashboard (notice)
* output 3 (level 2) --> dashboard + log
* output 4 (level 3) --> dashboard + alert email
* output 5 (level 4) --> dashboard + email + CRITICAL flag
*
* The "http request" node is configured with POST method and the URL
* taken from msg.url (leave the URL field empty in the node). The switch
* evaluates the msg.alert property with 5 "==" rules (0,1,2,3,4).
*
* Each station exposes POST /esp32plc14_XX/status and replies a JSON:
* {"station":31,"status":2,"inputs":{"I0_0":1,"I0_1":1,"I0_2":0,"I0_3":0}}
*/
// ======================================================================
// FUNCTION NODE "prepare polling" (1 input, 1 output)
// Generates one request per station; the http request node processes
// them in series.
// ======================================================================
const STATIONS = [
{ id: 31, ip: "10.10.10.31" },
{ id: 32, ip: "10.10.10.32" },
{ id: 33, ip: "10.10.10.33" },
{ id: 34, ip: "10.10.10.34" }
];
const msgs = STATIONS.map(sta => ({
url: `http://${sta.ip}/esp32plc14_${sta.id}/status`,
method: "POST",
payload: {}, // empty body: we only request status
station: sta.id, // travels with the msg for traceability
topic: `station/${sta.id}`
}));
// Send the 4 messages staggered (200 ms) to avoid flooding
msgs.forEach((m, i) => setTimeout(() => node.send(m), i * 200));
return null;
// ======================================================================
// FUNCTION NODE "classify alert" (1 input, 1 output)
// Wire it to the http request output. Normalizes the response and computes
// msg.alert (0=normal ... 4=critical) feeding the 5-output switch.
// ======================================================================
/*
let data;
try {
data = typeof msg.payload === "string" ? JSON.parse(msg.payload) : msg.payload;
} catch (e) {
data = null;
}
// No response or invalid JSON => the station is silent: treat as critical
if (!data || msg.statusCode !== 200) {
msg.alert = 4;
msg.reason = `station ${msg.station}: no response (HTTP ${msg.statusCode || "timeout"})`;
node.status({ fill: "red", shape: "ring", text: msg.reason });
return msg;
}
// The firmware already reports its 0-4 level; validate and clamp it
const level = Math.max(0, Math.min(4, Number(data.status) || 0));
msg.alert = level;
msg.station = data.station ?? msg.station;
msg.inputs = data.inputs || {};
msg.reason = `station ${msg.station}: level ${level}`;
msg.ts = new Date().toISOString();
// Store the latest status per station (used by the dashboard)
const states = flow.get("states") || {};
states[msg.station] = { level: level, inputs: msg.inputs, ts: msg.ts };
flow.set("states", states);
const colors = ["green", "green", "yellow", "yellow", "red"];
node.status({ fill: colors[level], shape: "dot", text: msg.reason });
return msg;
*/
// ======================================================================
// SWITCH NODE "level 0-4"
// Property: msg.alert — 5 "==" rules with values 0, 1, 2, 3, 4
// ("stopping after first match" mode). Each output routes to its branch:
// dashboard, log or email depending on the criticality shown in the header.
// ======================================================================
// ======================================================================
// FUNCTION NODE "format alert email" (level 3 and 4 branches)
// Prepares the body consumed by the SMTP script / email node.
// ======================================================================
/*
msg.topic = `[SLOPE] Level ${msg.alert} alert at station ${msg.station}`;
msg.payload = `Date: ${msg.ts}\n` +
`Station: ${msg.station}\n` +
`Alert level: ${msg.alert} (0=normal, 4=critical)\n` +
`Inputs: ${JSON.stringify(msg.inputs)}\n` +
`Reason: ${msg.reason}`;
return msg;
*/
Download the full project pack — freeThis example + the related ones + bill of materials