Skip to Content

← 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