ESP32/ESP8266: MicroPython OTA Updates via PHP Server

In this project, we’ll show you an example of how you can do OTA (over-the-air) updates to your ESP32/ESP8266 boards running MicroPython code via a PHP server. In summary, you’ll create a PHP server in which you can submit a new code that you want to run on the ESP. When there is a new code/update available, the ESP downloads the new code, reboots, and starts running the newly downloaded code.

ESP32 ESP8266 NodeMCU MicroPython OTA Updates via PHP Server

This project was created by one of our readers: David Flory. Thank you for your contributions. He provided the example codes and some explanations, and we’re documenting the whole process in this tutorial.

Not familiar with MicroPython? Start here.

Project Overview

OTA (Over-the-Air) update is the process of loading new firmware to the ESP32 or ESP8266 board using a Wi-Fi connection rather than a serial communication. This functionality is extremely useful in case of no physical access to the board.

There are different ways to perform OTA updates. We have several examples for OTA updates using the ESP32/ESP8266 boards with Arduino IDE. In this tutorial, we’ll create a PHP server that will host new/updated code. The ESP will periodically communicate with the server to check if there is a new updated code available and download it to the board if that’s the case.

The following diagram explains how everything works.

ESP32 ESP8266 OTA micropython PHP server
  1. The ESP32 or ESP8266 executes whatever tasks you define.
  2. Meanwhile, you upload a new python file that you want to run on your ESP to the Rpi PHP server.
  3. The ESP checks for updates periodically by making a request to the get_ESP_data.php URL to search for a new program.py.
  4. If there is a new file available, the ESP will download it to its filesystem and reboot.
  5. When it restarts, it will be running the newly uploaded python file.
  6. This new file has an instruction at the beginning of the code to make a request to the delete_ESP_data.php URL to delete the python file from the PHP server because it is already running the most updated code.
  7. When you upload a new code, the process repeats. Note that the new python file must also contain the instructions to delete the previous file from the PHP server and to check for updates.

Alternative method:

Here’s an alternative method that doesn’t require deleting previous files and allows you to update multiple boards.

  1. The ESP32 or ESP8266 executes whatever tasks you define.
  2.  The files you upload have a version number on the name.
  3. The URL for updates is taken directly from the new program you upload (it should indicate what the next uploaded file will be called).
  4. You don’t need to delete any files because all versions have a unique name. So, multiple boards running the same code can update themselves as we no longer delete the files from code, so the delete_ESP_data.php file can be removed.

We provide the files for this method at the end of the tutorial.

Taking it further:

A better way to do it might be to create an SQL database on your server, with a record for each ESP with fields for ID (could be MAC address), the version running, time file downloaded (instructions not included).

The PHP could be adjusted to:

  • Ask for ID;
  • Supply the update;
  • Update the record for the ESP making the request.

Prerequisites

Before proceeding, make sure you check the following prerequisites.

1) MicroPython Programming

MicroPython Logo

In this tutorial, we’ll program the ESP32/ESP8266 boards using MicroPython. Your boards must be burned with MicroPython firmware and you must have a MicroPython IDE to program your boards. We’ll use Thonny IDE, but you can use any other MicroPython-compatible software.

  1. Getting Started with MicroPython on ESP32 and ESP8266
  2. Choose a MicroPython IDE for ESP32 and ESP8266
  3. Burn MicroPyhon firmware using the selected IDE

2) PHP Server

PHP Logo

The ESP will communicate with the PHP server periodically to check if there are any updates (a new file with a new MicroPython code). You can create a local PHP server on a Raspberry Pi, for example, or alternatively, you can create a PHP server on the cloud (hosting + domain name) that can be accessed from anywhere.

Local PHP Server

For this example, we’ll create a local PHP server on a Raspberry Pi. So, you also need a Raspberry Pi for this tutorial:

  1. Get a Raspberry Pi Board: check the best Raspberry Pi Starter Kits

After getting a Raspberry Pi, follow the next instructions to set it up:

  1. Install Raspberry Pi OS, Set Up Wi-Fi, Enable and Connect with SSH

We’ll provide the instructions to create the local PHP server in this tutorial. You’ll also need a way to upload files to your PHP server remotely. We’ll use Filezilla. You can use any other suitable method.

  1. To download Filezilla, click here and download the FileZilla client.

Cloud PHP Server

Alternatively, you can create your PHP server on the cloud using web hosting and a domain name. We recommend Bluehost or Digital Ocean. This tutorial doesn’t provide the instructions for this, but if you’re familiar with hosting your PHP server, you’ll easily figure out how to do it.

PART 1 – Creating the PHP Server

In this tutorial, we’ll create a local PHP server on a Raspberry Pi. Alternatively, you can create a cloud PHP server (instructions not included).

Before proceeding make sure you have installed the Raspberry Pi OS, set up Wi-Fi and SSH.

You can either run the next commands on a Raspberry Pi set as a desktop computer or using an SSH connection.

Updating and Upgrading

 Run the following commands to update your Pi. It will take a few minutes to complete.

pi@raspberrypi:~ $ sudo apt update && sudo apt upgrade -y

Change permissions so that the pi user can make modifications to files.

pi@raspberrypi:~ $ sudo chown pi:pi*

Installing Apache2

Next, install Apache2. Apache2 is the most widely used web server software. To install Apache2 on your Raspberry Pi, run the next command:

pi@raspberrypi:~ $ sudo apt install apache2 -y

That’s it! Apache is now installed. To test your installation, change to the /var/www/html directory and list the files:

pi@raspberrypi:~ $ cd /var/www/html
pi@raspberrypi:/var/www/html $ ls -al
index.html

You should have an index.html file in that folder. To open that page in your browser, you need to know the Raspberry Pi IP address. Use:

pi@raspberrypi:/var/www/html $ hostname -I
Raspberry Pi change directory RPi IP Address

In my case, the Raspberry Pi IP address is 192.168.1.86. If you open your RPi IP address in any browser in your local network, a similar web page should load (http://192.168.1.86):

Raspberry Pi Apache2 Installed

Installing PHP on Raspberry Pi

PHP is a server side scripting language. PHP (Hypertext Preprocessor) is used to develop dynamic web applications. A PHP file contains <?php … ?> tags and ends with the extension .PHP.

To install PHP on Raspberry Pi, run:

pi@raspberrypi:/var/www/html $ sudo apt install php -y

You can remove the index.html and create a PHP script to test the installation:

pi@raspberrypi:/var/www/html $ sudo rm index.html
pi@raspberrypi:/var/www/html $ sudo nano index.php

In your index.php file add the following code to echo the “hello world” message:

<?php echo "hello world"; ?>
Raspberry Pi Create PHP Test File Hello World

To save your file: press Ctrl+X, followed by y, and press Enter to exit.

Finally, restart Apache2:

pi@raspberrypi:/var/www/html $ sudo service apache2 restart

To test if Apache2 is serving .php files, open the Raspberry Pi IP address and it should display the “hello world” message from the index.php script created earlier.

Raspberry Pi test PHP File Hello World message web browser

If everything is working, you can remove index.php file from the /var/www/html directory:

pi@raspberrypi:/var/www/html $ sudo rm index.php

Creating the php server – get_ESP_data.php

Now, create a file called get_ESP_data.php.

pi@raspberrypi: sudo nano get_ESP_data.php

Enter the following PHP code:

<?php
  $file = $_GET['file'];
  $dir = getcwd();
  $file = $dir.'/'.$file;
  $myfile = fopen($file, "r") or die("FAIL");
  echo file_get_contents($file);
  fclose($myfile);
?>

Press Ctrl+X, followed by y, and press Enter to exit and save the file. This previous script reads the updated file (new code uploaded to the same directory that those files are located) and echo’s it back in response to a request made by the ESP when it calls the check_for_updates function as we’ll see later on.

Creating the php server – delete_ESP_data.php

Create another file called delete_ESP_data.php.

pi@raspberrypi: sudo nano delete_ESP_data.php

Enter the following PHP code:

<?php
  $file = $_GET['file'];
  $dir = getcwd();
  $file = $dir.'/'.$file;
  unlink($file);
?>

Press Ctrl+X, followed by y, and press Enter to exit and save the file. This file will delete the update file from the PHP server after the ESP has downloaded it.

That’s it. The PHP server is created and able to echo a new file to the ESP and delete it in response to different requests.

PART 2 – Programming the ESP32/ESP8266

Before proceeding make sure you burned your boards with MicroPython firmware.

To test the PHP server and integrate it with the ESP boards to make OTA updates, we’ll show you a simple example. This example needs three files: boot.py, main.py, and program.py.

boot.py

Using your favorite MicroPython IDE, create a file called boot.py with the following content and upload it to your board.

# OTA updater for ESP32 running Micropython by David Flory
# Tutorial: https://randomnerdtutorials.com/esp32-esp8266-micropython-ota-updates/

try:
  import usocket as socket
except:
  import socket

try:
  import urequests as requests
except:
  import requests
import os 
import sys
import esp
esp.osdebug(None)
import gc
gc.collect()
from utime import sleep
from utime import sleep_ms
import network

OTA = 0

View raw code

This file imports the required libraries for network connection. It also creates a boolean variable called OTA that indicates whether it’s time to perform an OTA update (download the new file from the PHP server).

OTA = 0

The ESP runs the boot.py first. The first time it runs, there aren’t any available updates, so we set the OTA variable to 0.

main.py

Create a file called main.py with the following content and upload it to your board. You need to modify the file to include your own network credentials and the PHP server URL for the updates.


"""
OTA updater for ESP32 running Micropython by David Flory.
Tutorial: https://randomnerdtutorials.com/esp32-esp8266-micropython-ota-updates/

This is a simple way to run a python program on the ESP32 and download updates or a different program
to run from an HTTP server, either from the web or your local network.

Put all the imports needed for internet in boot, plus a global variable called OTA
The main file creates the internet connection then runs whatever program is required. I call the program 'program.py'

program.py is the main program, and can be whatever you want. The only requisite for this to work with OTA is that
it must be imported into the module 'main' and it must check periodically for updates.
The Program should have a function that can be called by 'main' to start it.

We can overwrite program.py while it is running, because it has been imported on boot into the namespace of 'main'.
All that is required is a reboot after a new Program.py is downloaded. In this case the deep sleep function will reboot the ESP32.

"""
#When prog.py finishes, if OTA = True, main downloads it and overwrites program.py, then effectively reboots.
#If OTA is false main just exits.

#this is the program to be executed. Note we do not use the '.py' extension.
import program

import machine #needed for the deep sleep function

#URLs for the updates
#replace with your PHP server URL or local IP address (Raspberry Pi IP Address)
upd_url="http://192.168.1.XXX/get_ESP_data.php?file=program.py"


#change your wifi credentials here. 
ssid = 'REPLACE_WITH_YOUR_SSID'
password = 'REPLACE_WITH_YOUR_PASSWORD'

print('OTA is ' + str(OTA))#debug

#here we set up the network connection
station = network.WLAN(network.STA_IF)
station.active(False)
station.active(True)
#station.config(reconnects = 5)

station.connect(ssid,password)

while station.isconnected() == False:
  pass

#print board local IP address
print(station.ifconfig())

if OTA == 0:
    print('starting program')
    OTA = program.mainprog(OTA)
    
#mainprog() is the starting function for my program. OTA is set to 0 on boot so the first time this code
#is run, it sets up the network connection and then runs the program.
#The following code only runs when program.py exits with OTA = 1

if OTA == 1:
    print('Downloading update')
    #download the update and overwrite program.py
    response = requests.get(upd_url)
    x = response.text.find("FAIL")
    if x > 15:
        #download twice and compare for security
        x = response.text
        response = requests.get(upd_url)
        if response.text == x:
            f = open("program.py","w")
            f.write(response.text)
            f.flush()
            f.close
            
            #soft reboot 
            print('reboot now')
            machine.deepsleep(5000)

View raw code

Here’s a summary of what this file does:

  • connects your board to the internet;
  • runs your program tasks (whatever you define in the program.py file we’ll take a look later)
  • when you’re done with your tasks, if there is an updated, it will download it and overwrite the existing program.py file
  • after that, it goes into deep sleep for a few seconds;
  • after waking from deep sleep, it starts all over again with the new downloaded code (OTA update successful).

Let’s take a quick look at the code.

The following line imports the program.py> file—this is the program to be executed, it must contain the tasks that you want your ESP to do. Note that we do not use the .py extension.

import program

Then, import the machine module that is needed for deep sleep function.

import machine

Modify the upd_url variable with your PHP server IP address or URL. In my case, the Raspberry Pi server PHP address is 192.168.1.86, so the upd_url should be as follows:

upd_url="http://192.168.1.86/get_ESP_data.php?file=program.py"

Insert your network credentials in the following variables, so that the ESP can connect to your network.

ssid = 'REPLACE_WITH_YOUR_SSID'
password = 'REPLACE_WITH_YOUR_PASSWORD'

The following line prints the current state of the OTA variable for debugging purposes.

print('OTA is ' + str(OTA))

The next snippet of code connects the board to your local network and prints its IP address:

#here we set up the network connection
station = network.WLAN(network.STA_IF)
station.active(False)
station.active(True)
station.config(reconnects = 5)

station.connect(ssid,password)

while station.isconnected() == False:
  pass

#print board local IP address
print(station.ifconfig())

Finally, it checks the value of the OTA variable. If it is 0, it runs the main program that contains your tasks (main.py).

mainprog() is the starting function for our program. OTA is set to 0 on boot so the first time this code runs, it sets up the network connection and then runs the program.

if OTA == 0:
    print('starting program')
    OTA = program.mainprog(OTA)

The following code only runs when program.py exits with OTA = 1. This section of the code gets the content from the program.py that is in the PHP server and saves it on the board in the program.py program, so it overwrites the currently existing program.py file. After that, it goes to sleep for five seconds (it’s like a soft reboot). The next time the board reboots, it will run the new code.

if OTA == 1:
    print('Downloading update')
    #download the update and overwrite program.py
    response = requests.get(upd_url)
    x = response.text.find("FAIL")
    if x > 15:
        #download twice and compare for security
        x = response.text
        response = requests.get(upd_url)
        if response.text == x:
            f = open("program.py","w")
            f.write(response.text)
            f.flush()
            f.close
            
            #soft reboot 
            print('reboot now')
            machine.deepsleep(5000)

program.py

Create a file called program.py. Modify the code to include your local server IP address. Then, upload it to your board.

This is a simple program to demonstrate OTA update functionality in conjunction with main.py. The program can be as simple or complicated as you like but must contain these functions: check_for_updates(), mainprog() and program_tasks().

"""
OTA updater for ESP32 running Micropython by David Flory.
Tutorial: https://randomnerdtutorials.com/esp32-esp8266-micropython-ota-updates/

A simple program to demonstrate OTA update functionality in conjunction with main.py
The program can be as simple or complicated as you like, but must contain these functions.
The variable 'url' should be the full web address to the location of the update file and the PHP script which
returns it. below is the PHP script, named 'get_ESP_data.php'.

<?php
    $file = $_GET['file'];
    $dir = getcwd();
    $file = $dir.'/'.$file;
    $myfile = fopen($file, "r") or die("FAIL");
    echo file_get_contents($file);
    fclose($myfile);
?>

The script reads the update file and echo's it back in response to the request made by the
'check_for_updates' function.

Here is the delete script for deleting the updates.
<?php
    $file = $_GET['file'];
    $dir = getcwd();
    $file = $dir.'/'.$file;
    unlink($file);
?>

"""
import urequests
from utime import sleep
#The URL to use to get updates, full path to the PHP script (Raspberry Pi IP Address). The update file must be in the same
#directory as the PHP script.
#REPLACE WITH YOUR RASPBERRY PI IP ADDRESS
upd_url="http://192.168.1.XXX/get_ESP_data.php?file=program.py"
del_url = "http://192.168.1.XXX/delete_ESP_data.php?file=program.py"

def check_for_updates(OTA):
    try:
        #print ('Checking for updates')
        response = urequests.get(upd_url)
        x = response.text.find("FAIL")
        if x > 15:
            OTA = 1
            print('There is an update available')
            return(OTA)
        else:
            print('There are no updates available.')
            return(OTA)

    except:
        print('unable to reach internet')
        return(OTA)


def mainprog(OTA):
    print('Mainprog - OTA is' + str(OTA))
    #This is the entry point for your program code
    #first delete any update files on server. The reasoning here is that if this program is an update we
    #do not want to download it again. If this program is not an update, it is unlikely there will be an
    #update present anyway. If we do not delete it, the program will keep on updating on every loop and rebooting.
    response = urequests.get(del_url)
    print('Program start')
    while OTA == 0:
        program_tasks()
        OTA = check_for_updates(OTA)
        if OTA == 1:
           return(OTA)
        print('OTA = ' + str(OTA))

def program_tasks():
    #do program tasks. If continuous loop, use counter or sleep to pass some time between
    #update checks. At your designated point, check for updates.
    sleep(2)
    print('Tasks completed, entering loop 2')
    sleep(2)
    print('Tasks completed, entering loop 3')
    sleep(2)
    print('Tasks completed, entering loop 4')
    sleep(2)
    print('Tasks completed, entering loop 5')
    sleep(2)
    print('5 loops completed, checking for updates')

View raw code

Let’s take a quick look at the code to see how it works.

First, import any modules that might be required for your tasks. You must include the urequests library so that the ESP can make requests to the PHP server.

import urequests
from utime import sleep

Update the following variables to include the IP address of your local PHP server or the URL of your cloud web server. Only change the section with the IP address. The rest of the URL should remain as shown.

upd_url="http://192.168.1.86/get_ESP_data.php?file=program.py"
del_url = "http://192.168.1.86/delete_ESP_data.php?file=program.py"

The check_for_updates() function checks if there are updates available by making a request on the upd_url. It returns the OTA variable value: 0 if there aren’t any updates, or 1 if there is an update:

def check_for_updates(OTA):
    try:
        #print ('Checking for updates')
        response = urequests.get(upd_url)
        x = response.text.find("FAIL")
        if x > 15:
            OTA = 1
            print('There is an update available')
            return(OTA)
        else:
            print('There are no updates available.')
            return(OTA)

    except:
        print('unable to reach internet')
        return(OTA)

The mainprog() is the entry point for your program code (this function is called on the main.py file).

def mainprog(OTA):
    print('Mainprog - OTA is' + str(OTA))
    response = urequests.get(del_url)
    print('Program start')
    while OTA == 0:
        program_tasks()
        OTA = check_for_updates(OTA)
        if OTA == 1:
           return(OTA)
        print('OTA = ' + str(OTA))

First, it deletes any update files on the server by making a request on the del_url. The reasoning here is that if this program is an update we do not want to download it again. If this program is not an update, it is unlikely there will be an update present anyway. If we do not delete it, the program will keep on updating on every loop and rebooting.

Finally, the program_tasks() function should include the tasks you want to do with your board like sensor readings, data logging, controlling outputs, or any other tasks. For simplicity, we’re simply printing messages on the MicropPython shell.

def program_tasks():
    #do program tasks. If continuous loop, use counter or sleep to pass some time between
    #update checks. At your designated point, check for updates.
    sleep(2)
    print('Tasks completed, entering loop 2')
    sleep(2)
    print('Tasks completed, entering loop 3')
    sleep(2)
    print('Tasks completed, entering loop 4')
    sleep(2)
    print('Tasks completed, entering loop 5')
    sleep(2)
    print('5 loops completed, checking for updates')

If you’ll use a continuous loop, use a counter or sleep to pass some time between update checks. At your designated point, check for updates.

If you haven’t yet, upload all the previous files to your board in this order: boot.py, main.py and program.py. Reset your board so that it starts running the code. It should be printing the following messages on the shell (this is in the program_tasks function).

MicroPython Shell OTA update example

Let the board connected to your computer so that we can see on the shell if the updates are successful and for you to understand how it works. This example is solely for demonstration purposes as the aim of OTA is that you don’t need to have your board connected to your computer.

PART 3 – Demonstration: OTA Updates

Now, create a new file also called program.py and save it on your computer. This file will contain the updated code. In our case, we’ll only change the messages printed on the Shell. We’ll upload this code over-the-air to your ESP board via the PHP server.

# OTA updater for ESP32 running Micropython by David Flory
# Tutorial: https://randomnerdtutorials.com/esp32-esp8266-micropython-ota-updates/

#This program essentially identical to program1, except the tasks are slightly
#different to demonstrate a different program is now running.

import urequests
from utime import sleep
#The URL to use to get updates, full path to the PHP script (Raspberry Pi IP Address). The update file must be in the same
#directory as the PHP script.
#REPLACE WITH YOUR RASPBERRY PI IP ADDRESS
upd_url="http://192.168.1.XXX/get_ESP_data.php?file=program.py"
del_url = "http://192.168.1.XXX/delete_ESP_data.php?file=program.py"

def check_for_updates(OTA):
    try:
        #print ('Checking for updates')
        response = urequests.get(upd_url)
        x = response.text.find("FAIL")
        if x > 15:
            OTA = 1
            print('There is an update available')
            return(OTA)
        else:
            print('There are no updates available.')
            return(OTA)

    except:
        print('unable to reach internet')
        return(OTA)


def mainprog(OTA):
    print('Mainprog - OTA is' + str(OTA))
    #This is the entry point for your program code
    #first delete any update files on server. The reasoning here is that if this program is an update we
    #do not want to download it again. If this program is not an update, it is unlikely there will be an
    #update present anyway. If we do not delete it, the program will keep on updating on every loop and rebooting.
    response = urequests.get(del_url)
    print(response)
    print('Program start')
    while OTA == 0:
        program_tasks()
        OTA = check_for_updates(OTA)
        if OTA == 1:
           return(OTA)
        print('OTA = ' + str(OTA))

def program_tasks():
    #do program tasks. If continuous loop, use counter or sleep to pass some time between
    #update checks. At your designated point, check for updates.
    sleep(2)
    print('Updated Program 2, entering loop 2')
    sleep(2)
    print('Updated Program 2, entering loop 3')
    sleep(2)
    print('Updated Program 2, entering loop 4')
    sleep(2)
    print('Updated Program 2, entering loop 5')
    sleep(2)
    print('5 loops completed, checking for updates')

View raw code

Note: in a practical application, make sure your code is working properly before making an over-the-air update. If the new code crashes, you might need to connect your board again to your computer to be able to fix the issue.

To upload the new program to the PHP server, we’ll use Filezilla. You can use any other suitable method—just take into account that you must place the program.py in the same directory as the PHP files we created previously. If you’ve followed everything as in the tutorial, the file should be placed on the /var/www/html directory.

To establish a communication with the Pi, these are the settings (by default):

  • Host: raspberrypi
  • Username: pi
  • Password: raspberry
  • Port: 22
MicroPython OTA Updates Filezilla

After uploading the new file to Filezilla, the ESP should detect there is a new file and will download it. Then, it will go into deep sleep, and start running the newly downloaded code. This new code prints a different message than the previous one.

ESP32 ESP8266 MicroPython OTA Updates via PHP Server Demonstration

Alternative Method

As mentioned previously, here’s an alternative method that doesn’t require deleting previous files and allows you to update multiple boards.

  1. The ESP32 or ESP8266 executes whatever tasks you define.
  2.  The files you upload have a version number.
  3. The URL for updates is taken directly from the new program you upload (it should indicate what the next uploaded file will be called).
  4. You don’t need to delete any files because all versions have a unique name. So, multiple boards running the same code can update themselves as we no longer delete the files from the code, so the delete_ESP_data.php file can be removed.
  5. Users will have to delete their own files periodically after all boards have been updated.

The previous files were modified for this alternative method. Here are the files:

Wrapping Up

In this tutorial, you learned how to update a new code over the air to your ESP32 or ESP8266 boards (programmed with MicroPython) using a PHP server. As an example, we’ve built a local PHP web server on a Raspberry Pi.

We hope you’ve found this tutorial useful. If you want to learn more about MicroPython, check out our resources:

Thanks for reading.



Learn how to build a home automation system and we’ll cover the following main subjects: Node-RED, Node-RED Dashboard, Raspberry Pi, ESP32, ESP8266, MQTT, and InfluxDB database DOWNLOAD »
Learn how to build a home automation system and we’ll cover the following main subjects: Node-RED, Node-RED Dashboard, Raspberry Pi, ESP32, ESP8266, MQTT, and InfluxDB database DOWNLOAD »

Enjoyed this project? Stay updated by subscribing our newsletter!

41 thoughts on “ESP32/ESP8266: MicroPython OTA Updates via PHP Server”

  1. Please do not repeat this PHP code in your projects. This code is vulnerable and provides an opportunity to receive any data from your server, as well as to remotely execute code and commands.

    Reply
  2. I have a number of ESP32’s. What is the best strategy if I want to be able to update all of them from time to time?

    My first thoughts are to use a different URL for each ESP32 using the same filename program.py. Alternatively, use a single URL and name the files something like program-xxx.py where xxx identifies the ESP32 board.

    Reply
    • Hi David
      One possible way would be to upload the updated programs to your webserver named program.1, program.2 etc. like a version number.
      If the current running program is ‘version 1’, change the upd_url in program.py to the URL plus ?file=program.2 (or whatever the latest version is).
      Then the update function (running in version 1) will fail if program.2 is not found.
      You would also comment out the line that deletes the update files on the server.

      In the main() program that downloads the updates, change the line initialising upd_url to this.
      upd_url = program.upd_url

      All your ESPs could run the same code, and only download updates if they are a later version.
      Please note I have not tested this theory, but I will later in the week.

      Reply
    • I was thinking about the same thing having a couple of esp32’s monitoring temperature each controlling it’s on BOX AC switch in different rooms. So in my case a single URL hosting different files is probably the best approach. Something like kids_room.py or master_room.py etc. So I guess each ESP32 would be looking for it own unique file name.

      Reply
  3. You do not validate the input parameter in PHP script. I can delete or get every file (which is allowed to used by php script user on server).

    PHP is also not configurated. I can get the stacktrace an read the server file structure to plan attacs.

    Its also not quite cool only to file_put_contents() the binary data for server. Where is the HTTP header to correctly respond via HTTP protocol?

    Reply
    • The PHP is really just a means to demonstrate the Python code, and primarily for private local networks. Should you want to deploy on a public server you should re-write your PHP code accordingly.
      All the HTTP headers are created by the built in requests library.

      Reply
  4. Hi,
    Maybe this’ an error but I don’t understand why at first run when OTA = 0 program.py file is erased with the start code:
    if OTA == 0:
    print(‘Starting program’)
    OTA = program.mainprog(OTA)
    Should check_for_updates(OTA) start first?
    Thanks.

    Reply
    • Hi Emilio
      When main() is run, which happens after every deep sleep, program.py is called and the first thing it does is delete any update files on the server, otherwise it would keep up an endless loop of updating itself. The reasoning for this is explained in the comments at the beginning of mainprog().
      When run for the very first time, there is not expected to be any update files present on the server. You would typically upload these when the program was active.

      Reply
  5. Hi, nice article. However, be aware that your PHP scripts are vulnerable to injections. Indeed, an attacker Can get content, or delete, any file in your server. Even ssh keys… You must sanitize input data before using it.
    Regards

    Reply
    • Hi.
      Yes, you’re right.
      But, if you’re using it on your local network, I don’t think there is any problem, right? As long as your network is protected.
      Regards,
      Sara

      Reply
      • Hi Sara, no, you’re not safe event on a local network. The only way to be safe is to NOT have vulnerabilities. In that case, it’s easy to fix it.
        First, you can check extension of required file to only deal with .py files. Then, make sure such files are located ONLY in a specific directory (to prevent path traversal attacks).
        Additionally, if .py files are named following a pattern, (e.g. update-XXX.py), you can check filename validates such pattern with a regex.
        Last, but not least, use credentials to only allow an authenticated user (ESP32 in this case) to secure access to your .php scripts.

        Generally, never trust user input. Don’t think you’re too small to be attacked. Bots don’t care.

        With all these recommendations, your system should be safe.

        Regards,

        Reply
  6. Hello,
    Thank you for this very well documented tutorial.
    However I use arduino IDE because I don’t know python.
    I hope to see soon the same tutorial for arduino IDE.
    I find nothing about similar process boot.py and main.py for arduino IDO like boot.ino and main.ino.
    I will modify the code by sending the version of the script from the ESP and if there is a newer one, I will upload it without deleting anything from the PHP server for my other ESPs doing the same thing.

    Reply
  7. Wow! Perfect for my needs. I added the “htmlspecialchars()” into the php code to secure the code.
    Is it possible to show me how to update more than one files with this method?

    Again, good work!

    Reply
    • Hi Emilio,
      See the comment I made to David on March 27th regarding running multiple ESPs and numbering your update files.
      Using these alterations [which I have now tested] it is no longer necessary to delete the update files from code. You can remove the line “response = urequests.get(del_url)” and also remove “delete_ESP_data.php” from the server.

      Reply
      • Hi David…. I see, I also made changes in the code. I run most of the functions in main.py leaving the program.py running only “program tasks” and added a VERSION = “1.0.0” line. So, the code in main.py checks for a new version (the older one is stored in a data.dat file) and if exists, it downloads it. It’s just one of many ways of doing it. BTW, I’m using a private directory for this which is password protected.

        Reply
  8. I think I have everything set up correctly but I get: %Run -c $EDITOR_CONTENT only and no other Shell readout when running program.py

    Reply
    • Use print statements to see how far you’re getting.
      Place “http://192.168.1.XXX/get_ESP_data.php?file=program.py” in your browser and see what prints back. You should see your file’s content.
      “print(response.text)” should print the file’s content as well.

      Reply
      • Meant to reply to David before…

        Thanks!

        Main gives the error: ValueError: unknown config param (on line 42)

        line 42: station.config(reconnects = 5)

        This line seems to be the culprit. If I comment it out, the code works fine with all print statements firing when they should. Not sure if I really need the 5 reconnects.

        Reply
  9. You can have a file “doit.php”. In this file there are yours procedures to check the origin url, the mac address of the module, etc. to perform actions according to a code transmitted by $_GET. You can also add encryption of transmitted data.

    Reply
  10. Thanks!

    Main gives the error: ValueError: unknown config param (on line 42)

    line 42: station.config(reconnects = 5)

    This line seems to be the culprit. If I comment it out, the code works fine with all print statements firing when they should. Not sure if I really need the 5 reconnects.

    Reply
  11. Hello.
    Do you have idea why it always say ‘unable to reach internet’.
    I tried to send get request to the php by using browser, it’s working.
    If I tried to print out the exception, using print(Exception), it is [Error no 12] ENOMEM.

    Reply
  12. Hello
    Do anyone have idea about OSError: [Errno 12] ENOMEM.
    This occurs when esp32 sending the urequest to the php api.
    Seems like it related to no sufficient memory according to my google.

    Reply
  13. Thank you for you regular messages you send out about IoT subjects.

    Hello,
    I’ve been reading your article about MicroPython OTA updates via PHP server.

    https://randomnerdtutorials.com/esp32-esp8266-micropython-ota-updates/#more-109647

    Can I ask if you have tried this technique with a Raspberry Pi Pico-W ??
    Do you know if there is a fundamental reason why the technique couldn’t be applied to a Pico-W?

    Kind regards, David (Dempster)

    Reply
  14. Hi, nice project. I am testing it on ESP8266/Nodemcu v3.
    There is a problem. When it finds updates, it starts downloading them and…gives an error. It doesn’t download anything, but it deletes the old file. The other problem is that it “finds” an update without me putting the program.py file on the server. The error it gives is the same:

    Completed 5 cycles, checking for updates
    There is an update available
    Download an update
    Backtracking (last last call):
    File “main.py”, line 63, in
    File “urequests.py”, line 116, in get
    File “urequests.py”, line 60, in request
    OSError: [Errno 103] ECONNABORTED

    The firmware is the latest version, but I tested it with another and the result is the same.
    The only difference from the example given is that my server is on a local computer, not a Raspberry. I don’t think that matters in this case.

    Regards,
    Nikolay Alexandrov

    Reply

Leave a Reply to Kody Cancel reply

Download Our Free eBooks and Resources

Get instant access to our FREE eBooks, Resources, and Exclusive Electronics Projects by entering your email address below.