ESP32 & ESP8266 MicroPython OTA Updates Using Python Server
Implement over-the-air (OTA) firmware updates for ESP32/ESP8266 running MicroPython using a Flask server. Covers the server setup, device-side update script, and applying the new firmware.
Over-the-Air (OTA) updates let you push new firmware to ESP32/ESP8266 devices over Wi-Fi without physical access. This guide builds a simple OTA system: a Flask server serves the firmware file, and the device downloads and applies it.
Prerequisites
- ESP32 or ESP8266 with MicroPython installed
- Flask for the update server
- Both the device and server on the same network (or server accessible via internet)
Part 1: The Flask Update Server
The server hosts the firmware file and serves it on demand.
Install Flask
pip install flask
Create app.py
from flask import Flask, send_file, jsonify
import os
app = Flask(__name__)
FIRMWARE_PATH = "firmware.py" # path to your firmware file
FIRMWARE_VERSION = "1.2.0"
@app.route("/version")
def version():
"""Returns the current firmware version."""
return jsonify({"version": FIRMWARE_VERSION})
@app.route("/update")
def update_firmware():
"""Serves the firmware file for download."""
if not os.path.exists(FIRMWARE_PATH):
return jsonify({"error": "Firmware not found"}), 404
return send_file(FIRMWARE_PATH, as_attachment=True)
if __name__ == "__main__":
app.run(host="0.0.0.0", port=5000)
Run the Server
python app.py
Place your updated firmware.py (the MicroPython script to deploy) in the same directory. The server exposes:
GET /version— returns the available firmware versionGET /update— downloads the firmware file
Part 2: The Device Update Script
This runs on the ESP32/ESP8266. It connects to Wi-Fi, checks for a new version, downloads it, replaces the current main.py, and reboots.
Create updater.py and upload it to the device:
import network
import urequests
import os
import machine
import time
WIFI_SSID = "your_wifi_ssid"
WIFI_PASSWORD = "your_wifi_password"
SERVER_URL = "http://192.168.1.100:5000" # your server's IP
CURRENT_VERSION = "1.1.0" # this device's current version
def connect_wifi():
sta = network.WLAN(network.STA_IF)
sta.active(True)
if not sta.isconnected():
sta.connect(WIFI_SSID, WIFI_PASSWORD)
timeout = 15
while not sta.isconnected() and timeout > 0:
time.sleep(1)
timeout -= 1
if sta.isconnected():
print("Connected:", sta.ifconfig()[0])
return True
print("Wi-Fi connection failed")
return False
def check_for_update():
try:
response = urequests.get(SERVER_URL + "/version", timeout=10)
data = response.json()
response.close()
return data.get("version")
except Exception as e:
print("Version check failed:", e)
return None
def download_and_apply(filename="main.py"):
try:
print("Downloading firmware...")
response = urequests.get(SERVER_URL + "/update", timeout=30)
if response.status_code != 200:
print("Download failed, status:", response.status_code)
response.close()
return False
# Write firmware to a temporary file first
with open("firmware_new.py", "wb") as f:
f.write(response.content)
response.close()
# Verify the file was written successfully
stat = os.stat("firmware_new.py")
if stat[6] == 0:
print("Downloaded file is empty, aborting")
os.remove("firmware_new.py")
return False
# Atomically replace current firmware
try:
os.remove(filename)
except OSError:
pass
os.rename("firmware_new.py", filename)
print("Firmware updated successfully")
return True
except Exception as e:
print("Update failed:", e)
# Clean up temp file if it exists
try:
os.remove("firmware_new.py")
except OSError:
pass
return False
def run_ota():
if not connect_wifi():
return
server_version = check_for_update()
if not server_version:
print("Could not reach update server")
return
if server_version == CURRENT_VERSION:
print("Already up to date:", CURRENT_VERSION)
return
print(f"Update available: {CURRENT_VERSION} → {server_version}")
if download_and_apply():
print("Rebooting in 3 seconds...")
time.sleep(3)
machine.reset()
else:
print("Update failed, keeping current firmware")
# Call at startup or on a schedule
run_ota()
Running OTA at Boot
To check for updates on every boot, call run_ota() from your main.py before your main application logic:
# main.py
import updater
updater.run_ota()
# ... rest of your application ...
Or check for updates only every N boots to save bandwidth:
import updater
import machine
# Check every 10 boots
boot_count = 0
try:
with open("boot_count.txt") as f:
boot_count = int(f.read())
except OSError:
pass
boot_count = (boot_count + 1) % 10
with open("boot_count.txt", "w") as f:
f.write(str(boot_count))
if boot_count == 0:
updater.run_ota()
Deploying to the Device
Upload updater.py via ampy, rshell, or Thonny:
# Using ampy
ampy --port /dev/ttyUSB0 put updater.py
# Using rshell
rshell cp updater.py /pyboard/
Conclusion
This OTA approach covers the core pattern: version check, conditional download, safe write-then-rename, and reboot. For production deployments, add HTTPS to the Flask server and verify firmware integrity with a checksum or HMAC signature before applying updates.