← Remote OTA Firmware Updates on ESP32 PLC Ethernet Stations
Geotechnical slope monitoringESP32 PLC 14 (×4, Ethernet)HTTPResilience / OTA
Remote OTA Firmware Updates on ESP32 PLC Ethernet Stations — full example
Update firmware on remote ESP32 PLC stations over Ethernet with a built-in web server: /status JSON endpoint plus /update OTA. Full Arduino example.
Complete, runnable program for the ESP32 PLC 14 (×4, Ethernet) (webserver-status-ota-ethernet.ino): 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 — Remote Ethernet station: HTTP status + centralized OTA
*
* Hardware: ESP32 PLC 14 with Ethernet (Industrial Shields)
* Based on: geotechnical slope monitoring project (4 stations)
*
* Wiring:
* I0_0..I0_3 Station sensors (tilt, detectors...)
*
* Architecture (replicable across N stations, one IP per station):
* - GET /status -> JSON with sensor status and alert level 0-4
* - POST /update -> OTA firmware update from the concentrator
* - Every 60 s the station also PUSHES its status to the central Node-RED,
* which classifies the alert level and triggers email/FTP/dashboard.
*
* With several identical stations, only STATION_ID and IP change — the
* concentrator polls them and pushes firmware without visiting the site.
*/
#include // ESP32 PLC Ethernet (see Industrial Shields docs)
#include
#include
#include
const int STATION_ID = 31;
const char *VERSION = "1.0.0";
const char *CENTRAL = "http://NODERED_IP:1880/station/status";
IPAddress ip(10, 10, 10, 31), gw(10, 10, 10, 1), mask(255, 255, 255, 0);
WebServer server(80);
const int pins[] = {I0_0, I0_1, I0_2, I0_3};
const int NPINS = 4;
uint32_t tPush = 0;
// Alert level 0-4 based on active sensors (example threshold)
int alertLevel() {
int n = 0;
for (int i = 0; i < NPINS; i++) n += digitalRead(pins[i]);
return n; // 0=normal ... 4=critical
}
String statusJson() {
String j = "{\"station\":" + String(STATION_ID) +
",\"version\":\"" + VERSION +
"\",\"status\":" + String(alertLevel()) + ",\"inputs\":{";
for (int i = 0; i < NPINS; i++)
j += "\"I0_" + String(i) + "\":" + String(digitalRead(pins[i])) +
(i < NPINS - 1 ? "," : "");
j += "}}";
return j;
}
void setup() {
Serial.begin(115200);
for (int i = 0; i < NPINS; i++) pinMode(pins[i], INPUT);
ETH.begin();
ETH.config(ip, gw, mask);
// --- GET /status: the concentrator (or a browser) queries the station
server.on("/status", HTTP_GET, [] {
server.send(200, "application/json", statusJson());
});
// --- POST /update: OTA — upload the new .bin from Node-RED or curl:
// curl -F "[email protected]" http://10.10.10.31/update
server.on("/update", HTTP_POST,
[] {
server.sendHeader("Connection", "close");
server.send(200, "text/plain", Update.hasError() ? "FAIL" : "OK");
delay(1000);
ESP.restart(); // boots with the new firmware
},
[] {
HTTPUpload &up = server.upload();
if (up.status == UPLOAD_FILE_START) {
Serial.printf("OTA: %s\n", up.filename.c_str());
Update.begin(UPDATE_SIZE_UNKNOWN);
} else if (up.status == UPLOAD_FILE_WRITE) {
Update.write(up.buf, up.currentSize);
} else if (up.status == UPLOAD_FILE_END) {
if (Update.end(true)) Serial.printf("OTA OK: %u bytes\n", up.totalSize);
}
});
server.begin();
Serial.printf("Station %d at http://%s\n", STATION_ID, ETH.localIP().toString().c_str());
}
void loop() {
server.handleClient();
// Periodic push to the concentrator: if a station goes quiet, the hub notices
if (millis() - tPush > 60000) {
tPush = millis();
HTTPClient http;
http.begin(CENTRAL);
http.addHeader("Content-Type", "application/json");
int code = http.POST(statusJson());
Serial.printf("push -> %d\n", code);
http.end();
}
}
Download the full project pack — freeThis example + the related ones + bill of materials