Introduction #
This guide walks through the steps to perform Over-The-Air (OTA) updates on an ESP32 microcontroller by retrieving firmware from a GitHub repository and applying the update over Wi-Fi. ( https://github.com/IndustrialArduino/OTA-on-ESP/tree/main/ESP32_OTA_Github_Updater )
Requirements #
Requirements –
• ESP32 Development Board
• Wi-Fi network
• GitHub repository to host firmware binaries and a version file
• Arduino IDE or compatible development environment
Libraries –
• WiFi.h (included in ESP32 core)
• WebServer.h (included in ESP32 core)
• ESPmDNS.h (for local name resolution)
• Update.h (for OTA functionality)
• HTTPClient.h and WiFiClientSecure.h (for OTA functionality)
• Secrets.h
OTA update process #
Prepare the ESP32 Code:
• Install required libraries: WiFi.h, HTTPClient.h, Update.h, etc.
• Connect the ESP32 to your Wi-Fi network.
• Create a Secrets.h file for storing Wi-Fi credentials.
Create a GitHub Repository:
• Host your firmware binary (e.g., firmware.bin) in the repository.
• Add a version.txt file with the latest firmware version (e.g., 1.0.1).
Implement OTA Check in ESP32:
• In your ESP32 code, periodically check GitHub for updates using HTTPClient.
• Compare the current firmware version with the one in version.txt.
Perform OTA Update:
• If a new version is available, download firmware.bin from GitHub.
• Use the Update library to write the new firmware to the ESP32.
• Reboot the device after the update.
Initial Upload and Future OTA Updates:
• Manually upload the first firmware via USB.
• Future updates are applied automatically over Wi-Fi once you upload new firmware and version details to GitHub.
Project overview #
The firmware update process works as follows:
Current Version Check: The ESP32 checks the version.txt file in the GitHub repository to determine the latest firmware version.
New Firmware Detection: If a new version is detected (i.e., different from the current firmware version running on the ESP32), the device downloads the new .bin firmware file.
OTA Update: The ESP32 initiates the OTA update and installs the new firmware. Once the update is complete, the ESP32 restarts and runs the new firmware.
Branch Setup
The release branch of your GitHub repository should contain:
The latest firmware file (firmware_vX.X.X.bin)
A version.txt file that stores the version number of the latest firmware.
File Structure in Release Branch
/release/
├── firmware_v1.0.1.bin
├── version.txt
version.txt: This file should contain only the version number of the latest firmware, e.g., 1.0.1
Main components #
WiFi Connection: The ESP32 connects to the internet via WiFi and fetches the latest firmware from the GitHub repository.
HTTPClient Library: Handles the GET request to fetch the firmware version and download the binary file.
ArduinoOTA Libraries: Used to handle the OTA update on the ESP32.
Code Overview #
- Part 1
#include <WiFi.h>
#include <WebServer.h>
#include <ESPmDNS.h>
#include <Update.h>
#include <HTTPClient.h>
#include <WiFiClientSecure.h>
#include "Secrets.h"
// GitHub version and firmware URLs
String firmware_url;
const char* version_url = "https://raw.githubusercontent.com/IndustrialArduino/OTA-on-ESP/release/version.txt";
String current_version = "1.0.0"; // Current firmware version
String new_version;
// SSL/TLS certificate for the GitHub server
const char* root_ca = \
"-----BEGIN CERTIFICATE-----\n" \
"MIIEyDCCA7CgAwIBAgIQDPW9BitWAvR6uFAsI8zwZjANBgkqhkiG9w0BAQsFADBh\n" \
"MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3\n" \
"d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBH\n" \
"MjAeFw0yMTAzMzAwMDAwMDBaFw0zMTAzMjkyMzU5NTlaMFkxCzAJBgNVBAYTAlVT\n" \
"MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxMzAxBgNVBAMTKkRpZ2lDZXJ0IEdsb2Jh\n" \
"bCBHMiBUTFMgUlNBIFNIQTI1NiAyMDIwIENBMTCCASIwDQYJKoZIhvcNAQEBBQAD\n" \
"ggEPADCCAQoCggEBAMz3EGJPprtjb+2QUlbFbSd7ehJWivH0+dbn4Y+9lavyYEEV\n" \
"cNsSAPonCrVXOFt9slGTcZUOakGUWzUb+nv6u8W+JDD+Vu/E832X4xT1FE3LpxDy\n" \
"FuqrIvAxIhFhaZAmunjZlx/jfWardUSVc8is/+9dCopZQ+GssjoP80j812s3wWPc\n" \
"3kbW20X+fSP9kOhRBx5Ro1/tSUZUfyyIxfQTnJcVPAPooTncaQwywa8WV0yUR0J8\n" \
"osicfebUTVSvQpmowQTCd5zWSOTOEeAqgJnwQ3DPP3Zr0UxJqyRewg2C/Uaoq2yT\n" \
"zGJSQnWS+Jr6Xl6ysGHlHx+5fwmY6D36g39HaaECAwEAAaOCAYIwggF+MBIGA1Ud\n" \
"EwEB/wQIMAYBAf8CAQAwHQYDVR0OBBYEFHSFgMBmx9833s+9KTeqAx2+7c0XMB8G\n" \
"A1UdIwQYMBaAFE4iVCAYlebjbuYP+vq5Eu0GF485MA4GA1UdDwEB/wQEAwIBhjAd\n" \
"BgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwdgYIKwYBBQUHAQEEajBoMCQG\n" \
"CCsGAQUFBzABhhhodHRwOi8vb2NzcC5kaWdpY2VydC5jb20wQAYIKwYBBQUHMAKG\n" \
"NGh0dHA6Ly9jYWNlcnRzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydEdsb2JhbFJvb3RH\n" \
"Mi5jcnQwQgYDVR0fBDswOTA3oDWgM4YxaHR0cDovL2NybDMuZGlnaWNlcnQuY29t\n" \
"L0RpZ2lDZXJ0R2xvYmFsUm9vdEcyLmNybDA9BgNVHSAENjA0MAsGCWCGSAGG/WwC\n" \
"ATAHBgVngQwBATAIBgZngQwBAgEwCAYGZ4EMAQICMAgGBmeBDAECAzANBgkqhkiG\n" \
"9w0BAQsFAAOCAQEAkPFwyyiXaZd8dP3A+iZ7U6utzWX9upwGnIrXWkOH7U1MVl+t\n" \
"wcW1BSAuWdH/SvWgKtiwla3JLko716f2b4gp/DA/JIS7w7d7kwcsr4drdjPtAFVS\n" \
"slme5LnQ89/nD/7d+MS5EHKBCQRfz5eeLjJ1js+aWNJXMX43AYGyZm0pGrFmCW3R\n" \
"bpD0ufovARTFXFZkAdl9h6g4U5+LXUZtXMYnhIHUfoyMo5tS58aI7Dd8KvvwVVo4\n" \
"chDYABPPTHPbqjc1qCmBaZx2vN4Ye5DUys/vZwP9BFohFrH/6j/f3IL16/RZkiMN\n" \
"JCqVJUzKoZHm1Lesh3Sz8W2jmdv51b2EQJ8HmA==\n" \
"-----END CERTIFICATE-----\n" ;
Here’s a breakdown of the included libraries and their purposes:
WiFi.h: This library provides functions for connecting to a Wi-Fi network. It is essential for communication with the internet and downloading firmware updates.
WebServer.h: This library allows you to create a simple web server on the ESP32. It can be used to serve a web page for managing firmware updates.
ESPmDNS.h: This library enables the ESP32 to use the mDNS protocol for local network discovery. It allows you to access the device using a friendly name (e.g., “myesp32”) instead of its IP address.
Update.h: This library provides functions for updating the firmware of the ESP32. It can be used to download a new firmware image from a specified URL and install it on the device.
HTTPClient.h: This library allows you to make HTTP requests to web servers. It can be used to download the latest firmware version from a GitHub repository.
- WiFiClientSecure.h: This library provides functions for secure communication over HTTPS. It is used to connect to the GitHub server and download the firmware securely.
Constants:
CURRENT_VERSION_ADDR: This constant defines the memory address where the current firmware version is stored.
Firmware_url: This string variable stores the URL from which the firmware update will be downloaded.
Version_url: This string variable stores the URL from which the latest firmware version will be checked.
Current_version: This string variable stores the current firmware version installed on the ESP32.
New_version: This string variable stores the latest firmware version available.
root_ca: This string variable stores the root certificate for the GitHub server, used for HTTPS verification.
This code part provides the necessary libraries and constants for implementing OTA firmware updates on an ESP32.
- Part 2
//variabls to blink without delay:
const int led1 = 2;
const int led2 = 13;
const int led3 = 12;
const int led4 = 14;
unsigned long previousMillis = 0; // will store last time LED was updated
const long interval = 1000; // interval at which to blink (milliseconds)
int ledState = LOW; // ledState used to set the LED
WebServer server(80); // Web server for OTA Web Updater
WiFiClientSecure client;
void setup() {
pinMode(led1, OUTPUT);
pinMode(led2, OUTPUT);
pinMode(led3, OUTPUT);
pinMode(led4, OUTPUT);
Serial.begin(115200);
delay(100);
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println("\nWiFi connected");
if (MDNS.begin("esp32")) {
Serial.println("MDNS responder started");
}
client.setCACert(root_ca); // Set the CA certificate
This code continuously blinks the LEDs at a specified interval and also provides a web server for managing OTA firmware updates. The web server functionality is not shown in the provided code, but it would typically involve handling HTTP requests and updating the firmware of the device.
- Part 3
// Web Updater endpoints
server.on("/", HTTP_GET, []() {
server.sendHeader("Connection", "close");
server.send(200, "text/html", "<form method='POST' action='/update' enctype='multipart/form-data'><input type='file' name='update'><input type='submit' value='Update'></form>");
});
server.on("/update", HTTP_POST, []() {
server.sendHeader("Connection", "close");
server.send(200, "text/plain", (Update.hasError()) ? "FAIL" : "OK");
ESP.restart();
}, []() {
HTTPUpload& upload = server.upload();
if (upload.status == UPLOAD_FILE_START) {
Serial.printf("Update: %s\n", upload.filename.c_str());
if (!Update.begin(UPDATE_SIZE_UNKNOWN)) {
Update.printError(Serial);
}
} else if (upload.status == UPLOAD_FILE_WRITE) {
if (Update.write(upload.buf, upload.currentSize) != upload.currentSize) {
Update.printError(Serial);
}
} else if (upload.status == UPLOAD_FILE_END) {
if (Update.end(true)) {
Serial.printf("Update Success: %u\nRebooting...\n", upload.totalSize);
} else {
Update.printError(Serial);
}
}
});
Web Updater Endpoints:
server.on(“/”, HTTP_GET, []() {}): Defines a handler for HTTP GET requests to the root URL (/) of the web server. It serves an HTML form that allows users to upload a firmware update file.
server.on(“/update”, HTTP_POST, []() {}): Defines a handler for HTTP POST requests to the /update URL. This endpoint is used to process the uploaded firmware file and update the device’s firmware.
Firmware Update Logic:
HTTPUpload& upload = server.upload(): Creates an instance of the HTTPUpload class to handle the uploaded file.
If (upload.status == UPLOAD_FILE_START): Checks if the file upload has started. If so, it prints the filename to the serial console and starts the firmware update process using the Update.begin() function.
If (upload.status == UPLOAD_FILE_WRITE): Checks if the file is being written. If so, it writes the current chunk of data to the firmware update using the Update.write() function.
If (upload.status == UPLOAD_FILE_END): Checks if the file upload has finished. If so, it ends the firmware update process using the Update.end() function and prints the result to the serial console. If the update was successful, it reboots the device.
This code enables the ESP32 to serve a web page that allows users to upload a firmware update file. When a file is uploaded, the code processes it and updates the device’s firmware. This provides a convenient way to update the device’s software without physically accessing it.
- Part 4
server.begin();
}
void loop() {
server.handleClient(); // Handle web updater requests
delay(1000);
// Check for OTA updates on GitHub once at startup
if (checkForUpdate(firmware_url)) {
performOTA(firmware_url.c_str());
}
delay(1000);
//loop to blink without delay
unsigned long currentMillis = millis();
if (currentMillis - previousMillis >= interval) {
// save the last time you blinked the LED
previousMillis = currentMillis;
// if the LED is off turn it on and vice-versa:
ledState = not(ledState);
// set the LED with the ledState of the variable:
digitalWrite(led1, ledState);
digitalWrite(led2, ledState);
digitalWrite(led3, ledState);
digitalWrite(led4, ledState);
}
}
This code handles web server requests, checks for and performs OTA firmware updates, and blinks the LEDs at a specified interval. The checkForUpdate() and performOTA() functions are not shown in the provided code snippet, but they would likely involve making HTTP requests to GitHub to check for new firmware versions and download and install the update.
- Part 5
// Function to check for updates
bool checkForUpdate(String &firmware_url) {
HTTPClient http;
http.begin(version_url);
int httpCode = http.GET();
if (httpCode == HTTP_CODE_OK) {
new_version = http.getString();
new_version.trim();
Serial.println("Current version: " + current_version);
Serial.println("Available version: " + new_version);
if (new_version != current_version) {
Serial.println("New version available. Updating...");
firmware_url = String("https://raw.githubusercontent.com/IndustrialArduino/OTA-on-ESP/release/firmware_v")+ new_version + String(".bin");
Serial.println("Firmware URL: " + firmware_url);
return true;
} else {
Serial.println("Already on the latest version");
}
} else {
Serial.println("Failed to check for update, HTTP code: " + String(httpCode));
}
http.end();
return false;
This code function checks for new firmware updates by fetching the latest version information from a remote server and comparing it with the current version running on the device. If an update is available, it updates the firmware URL and returns true. Otherwise, it returns false or prints an error message.
- Part 6
void performOTA(const char* firmware_url) {
HTTPClient http;
http.begin(firmware_url);
int httpCode = http.GET();
if (httpCode == HTTP_CODE_OK) {
int contentLength = http.getSize();
bool canBegin = Update.begin(contentLength);
if (canBegin) {
size_t written = Update.writeStream(http.getStream());
if (written == contentLength) {
Serial.println("Written : " + String(written) + " successfully");
} else {
Serial.println("Written only : " + String(written) + "/" + String(contentLength) + ". Retry?");
}
if (Update.end()) {
Serial.println("OTA done!");
if (Update.isFinished()) {
Serial.println("Update successfully completed. Rebooting.");
delay(300);
ESP.restart();
} else {
Serial.println("Update not finished? Something went wrong!");
}
} else {
Serial.println("Error Occurred. Error #: " + String(Update.getError()));
}
} else {
Serial.println("Not enough space to begin OTA");
}
} else {
Serial.println("Cannot download firmware. HTTP code: " + String(httpCode));
}
http.end();
}
This code functions download the firmware from the specified firmware_url, writes it to the device’s flash memory, and reboots the device if the update is successful. It handles potential errors and provides informative messages to the user.
Main parts of the code #
Setup Your APN and Firmware URLs
You need to update the following variables in the code:
const char* ssid = "your-ssid"; // Your WiFi SSID
const char* password = "your-password"; // Your WiFi password
const char* version_url = "https://raw.githubusercontent.com/YourUsername/OTA-on-ESP/release/version.txt"; // Path to the version.txt file
String firmware_url = "https://raw.githubusercontent.com/YourUsername/OTA-on-ESP/release/firmware_vX.X.X.bin"; // Path to the firmware file
SSID: Replace your-ssid with your WiFi SSID.
Password: Replace your-password with your WiFi password.
Version_url: Update the URL to point to your GitHub repository’s version.txt file.
Firmware_url: This is automatically generated in the code, but you can manually change it if needed to point to the correct path.
Uploading new firmware
Build your new firmware: Compile your code and save the .bin file.
Upload to GitHub:
- Upload the new `.bin` file to the release branch of your GitHub repository.
- Name the file in the format `firmware_vX.X.X.bin`, where `X.X.X` is the version number (e.g., `firmware_v1.0.1.bin`).
Update version.txt:Update the version.txt file with the new version number (e.g., 1.0.1).
Updating the firmware version on ESP32
Before uploading the initial code to the ESP32, ensure that the current version variable in the code matches the firmware version currently on the device.
String current_version = "1.0.0"; // Update this when you deploy new firmware to the ESP32
After a successful OTA update, this variable must be updated in the code to reflect the new firmware version, so that future updates will work correctly.
Uploading the CA root certificate
Since GitHub uses HTTPS to serve files, the CA root certificate for raw.githubusercontent.com needs to be uploaded to the ESP. This certificate ensures that the module can securely download the firmware from GitHub.
The CA certificate is provided in the cert.h file in this repository. Make sure to upload this certificate to your SIM module using the appropriate AT command.
Example command to upload the CA certificate:
client.setCACert(root_ca);
Replace root_ca with the correct path to the certificate file on your system.
Running the OTA update
Once the code is deployed, the ESP32 will check the version.txt file periodically.
If a new version is available, the device will download the corresponding .bin file and perform the update.
After the update, the ESP32 restarts with the new firmware.
Example Output
Result of Serial Monitor during the OTA process.
Connecting to WiFi... OK
Checking for updates...
Making GET request to fetch version...
Current version: 1.0.0
Available version: 1.0.1
New version available. Updating...
Downloading firmware from: https://raw.githubusercontent.com/YourUsername/OTA-on-ESP/release/firmware_v1.0.1.bin
OTA Update in progress...
Firmware downloaded successfully. Rebooting.
Error Handling #
When implementing OTA updates for the ESP32 via Wi-Fi, it is important to handle potential errors gracefully. Network connectivity issues, failed firmware downloads, and memory limitations are common problems that can arise. Implementing mechanisms such as checking Wi-Fi status before initiating the update, retrying failed downloads, and validating the firmware file size and integrity (using checksums or hash functions) will help prevent corrupted updates. Additionally, logging errors using the Update.printError() function can help diagnose the root cause of failures during the update process.
Troubleshooting #
Troubleshooting OTA updates involves verifying the connection to the firmware server (like GitHub), ensuring the ESP32 has enough available flash memory, and checking for any error messages printed via the serial monitor. If the update fails, check the URL of the firmware binary, confirm that the ESP32 has a stable internet connection, and ensure there are no interruptions during the download. For more advanced troubleshooting, consider using a rollback mechanism that can revert to a previous version of the firmware if the update is unsuccessful.