Skip to Content

← All functionalities

Textile monitoring (weaving)ESP32 PLCHTTPResilience / OTA

Updating PLC firmware from the browser with an embedded web server

Web server OTA firmware updates turn a ladder-and-laptop job into a browser upload. When ESP32 PLCs sit next to every machine on a factory floor, reflashing over USB means climbing to each cabinet. This example embeds a web server with a status page, a JSON endpoint and a POST /update route that flashes a compiled .bin and reboots — exactly as used in a real textile machine monitoring deployment across two plants.

Three endpoints, one tiny server

GET / serves a human-friendly status page with uptime, free heap and the firmware upload form. GET /status returns the same data plus all digital inputs as JSON — handy for shell scripts and for the Node-RED concentrator to scrape periodically. POST /update receives the firmware binary itself. The whole server adds just a single server.handleClient() call to the loop, costing mere microseconds per iteration.

How the OTA upload works

The /update route uses two callbacks: the second receives the file in chunks and feeds them to Update.begin(), Update.write() and Update.end() from the ESP32 core; the first sends OK or FAIL once the transfer ends and restarts the PLC into the new firmware. Export the compiled binary from Arduino IDE with "Export compiled binary" and upload the .bin from any browser.

Why this matters in production

Firmware iterates fast in a real deployment — a new XML field to parse, a tweak to the snapshot period. With OTA, rolling a fix to a fleet of PLCs takes minutes from a desk instead of a plant walk with a ladder and a laptop. If an update fails mid-transfer, Update.hasError() reports it and the PLC keeps running the old image.

A snippet from the implementation

Straight from the example as deployed on the ESP32 PLC — copy it freely:

void setupOTA() {
  server.on("/update", HTTP_POST, []() {
      server.sendHeader("Connection", "close");
      server.send(200, "text/plain", Update.hasError() ? "FAIL" : "OK - rebooting");
      delay(1000);
      ESP.restart();                          // boots with the new firmware
    }, []() {
      HTTPUpload &up = server.upload();
      if (up.status == UPLOAD_FILE_START) {
        Serial.println("OTA: receiving " + up.filename);
        if (!Update.begin(UPDATE_SIZE_UNKNOWN)) Update.printError(Serial);
      } else if (up.status == UPLOAD_FILE_WRITE) {
        if (Update.write(up.buf, up.currentSize) != up.currentSize)
          Update.printError(Serial);
      } else if (up.status == UPLOAD_FILE_END) {
        if (Update.end(true))
          Serial.println("OTA OK: " + String(up.totalSize) + " bytes");
        else
          Update.printError(Serial);
      }
    });
}

The full example is a complete program — wiring header, setup and main loop — ready to adapt to your application.

Frequently asked questions

Is it safe to leave the OTA endpoint open on the plant network?

Keep the PLCs on an isolated VLAN and add HTTP basic authentication to the /update route in production. The plant network should never be reachable from outside the facility.

What happens if the upload is interrupted halfway?

Update.end() never gets called, so the new image is not activated and the PLC continues on the current firmware. You simply retry the upload.

Can I trigger diagnostics without the browser?

Yes. The same deployment pairs this with MQTT remote commands, so a Node-RED console can request system info or SD listings fleet-wide, while the web server covers per-device work like flashing.

Related functionalities