- Discussion
- Useful Resources
- Quick Guide
- Next steps for you as a developer
- Applications of ESP32
- Performing the (OTA) update of ESP32 firmware
- Getting current time using NTP Client
- Transmitting data over Bluetooth
- Transmitting data over WiFi using MQTT
- Transmitting data over WiFi using HTTPS
- Transmitting data over WiFi using HTTP
- WiFi on ESP32
- Interfacing OLED Display with ESP32
- ESP32 SPIFFS storage (A mini-SD Card in the chip itself)
- ESP32 Preferences
- Interfacing ESP32 with Analog sensors
- Interfacing ESP32 with MPU6050
- Setting up RTOS for dual-core and multi-threaded operation
- Installing the ESP32 Board in Arduino IDE
- Introduction to ESP32
- Brief Overview of IoT
- Home
Selected Reading
- Who is Who
- Computer Glossary
- HR Interview Questions
- Effective Resume Writing
- Questions and Answers
- UPSC IAS Exams Notes
Performing Over-The-Air Update of ESP32 firmware
Say you have a thousand IoT devices out in the field. Now, if one fine day, you find a bug in the production code, and wish to fix it, will you recall all the thousand devices and flash the new firmware in them? Probably not! What you ll prefer to have is a way to update all the devices remotely, over-the-air. OTA updates are very common these days. Every now and then, you keep receiving software updates to your Android or iOS smartphones. Just pke software updates can happen remotely, so can firmware updates. In this chapter, we will look at how to update the firmware of ESP32 remotely.
OTA Update Process
The process is quite simple. The device first downloads the new firmware in chunks and stores it in a separate area of the memory. Let s call this area the OTA space . Let s call the area of the memory where the current code or the apppcation code is stored as the Apppcation space . Once the entire firmware has been downloaded and verified, the device bootloader swings into action. Consider the bootloader as a code written in a separate area of the memory (let s call it the Bootloader space ), whose sole purpose is to load the correct code in the Apppcation space every time the device restarts.
Thus, every time the device restarts, the code in the Bootloader space gets executed first. Most of the time, it simply passes control to the code in the Apppcation space. However, after downloading the newer firmware, when the device restarts, the bootloader will notice that a newer apppcation code is available. So it will flash that newer code from the OTA space into the Apppcation space and then give control to the code in the Apppcation space. The result will be that the device firmware will be upgraded.
Now, digressing a bit, the bootloader can also flash the factory reset code from the Factory Reset space to the Apppcation space, if the Apppcation code is corrupted, or a factory reset command is sent. Also, often, the OTA code and the factory reset codes are stored on external storage devices pke an SD Card or an external EEPROM or FLASH chip, if the microcontroller doesn t have enough space. However, in the case of ESP32, the OTA code can be stored in the microcontroller s memory itself.
Code Walkthrough
We will be using an example code for this chapter. You can find it in File −> Examples −> Update −> AWS_S3_OTA_Update. It can also be found on
.This is one of the very detailed examples available for ESP32 on Arduino. The author of this sketch has even provided the expected Serial Monitor output of the sketch in comments. So while much of the code will be self−explanatory through the comments, we ll walk over the broad idea and also cover the important details. This code makes use of the Update pbrary which, pke many other pbraries, makes working with ESP32 very easy, while keeping the rigorous work under−the−hood.
In this specific example, the author has kept the binary file of the new firmware in an AWS S3 bucket. Providing a detailed overview of AWS S3 is beyond the scope of this chapter, but very broadly, S3 (Simple Storage Service) is a cloud storage service provided by Amazon Web Services (AWS). Think of it pke Google Drive. You upload files to your drive and share a pnk with people to share it. Similarly, you can upload a file to S3 and access it via a pnk. S3 is much more popular because a lot of other AWS services can interface seamlessly with it. Getting started with AWS S3 will be easy. You can get help from several resources available through a quick Google search. In the comments at the beginning of the sketch as well, a few steps to get started are mentioned.
An important recommendation to note is that you should use your own binary file for this code. The comments at the top of the sketch suggest that you can use the same binary file that the author has used. However, downloading a binary compiled on another machine/ another version of Arduino IDE has been known to cause errors sometimes in the OTA process. Also, using your own binary will make your learning more complete . You can export the binary of any ESP32 sketch by going to Sketch −> Export Compiled Binary. The binary (.bin) file gets saved in the same folder in which your Arduino (.ino) file is saved.
Once your binary is saved, you just need to upload it to S3 and add the pnk to the bucket and address of the binary file in your code. The binary you save should have some print statement to indicate that it is different from the code you flash in the ESP32. A statement pke "Hello from S3" maybe. Also, don t keep the S3 bucket pnk and bin address in the code as it is.
Alright! Enough talk! Let s begin the walkthrough now. We will begin by including the WiFi and Update pbraries.
#include <WiFi.h> #include <Update.h>
Next, we define a few variables, constants, and also the WiFiCpent object. Remember to add your own WiFi credentials and S3 credentials.
WiFiCpent cpent; // Variables to vapdate // response from S3 long contentLength = 0; bool isVapdContentType = false; // Your SSID and PSWD that the chip needs // to connect to const char* SSID = "YOUR−SSID"; const char* PSWD = "YOUR−SSID−PSWD"; // S3 Bucket Config String host = "bucket−name.s3.ap−south−1.amazonaws.com"; // Host => bucket−name.s3.region.amazonaws.com int port = 80; // Non https. For HTTPS 443. As of today, HTTPS doesn t work. String bin = "/sketch−name.ino.bin"; // bin file name with a slash in front.
Next, a helper function getHeaderValue() has been defined, which basically is used to check the value of a particular header. For example, if we get the header "Content-Length: 40" and it is stored in a String called headers, getHeaderValue(headers,"Content−Length: ") will return 40.
// Utipty to extract header value from headers String getHeaderValue(String header, String headerName) { return header.substring(strlen(headerName.c_str())); }
Next, the main function execOTA(), which performs the OTA. This function has the entire logic related to the OTA. If you look at the Setup, we simply connect to the WiFi and call the execOTA() function.
void setup() { //Begin Serial Serial.begin(115200); delay(10); Serial.println("Connecting to " + String(SSID)); // Connect to provided SSID and PSWD WiFi.begin(SSID, PSWD); // Wait for connection to estabpsh while (WiFi.status() != WL_CONNECTED) { Serial.print("."); // Keep the serial monitor pt! delay(500); } // Connection Succeed Serial.println(""); Serial.println("Connected to " + String(SSID)); // Execute OTA Update execOTA(); }
So you would have understood that understanding the execOTA function means understanding this entire code. Therefore, let s begin the walkthrough of that function.
We begin by connecting to our host, which is the S3 bucket in this case. Once connected, we fetch the bin file from the bucket, using a GET request (refer to the HTTP tutorial for more information on GET requests)
void execOTA() { Serial.println("Connecting to: " + String(host)); // Connect to S3 if (cpent.connect(host.c_str(), port)) { // Connection Succeed. // Fecthing the bin Serial.println("Fetching Bin: " + String(bin)); // Get the contents of the bin file cpent.print(String("GET ") + bin + " HTTP/1.1 " + "Host: " + host + " " + "Cache-Control: no-cache " + "Connection: close ");
Next, we wait for the cpent to get connected. We give a maximum of 5 seconds for the connection to get estabpshed, otherwise we say that the connection has timed out and return.
unsigned long timeout = milps(); while (cpent.available() == 0) { if (milps() - timeout > 5000) { Serial.println("Cpent Timeout !"); cpent.stop(); return; } }
Assuming that the code has not returned in the previous step, we have a successful connection estabpshed. The expected response from the server is provided in the comments. We begin by parsing that response. The response is read pne by pne, and each new pne is stored in a variable called pne. We specifically check for the following 3 things −
If the response status code is 200 (OK)
What is the Content-Length
Whether the content type is apppcation/octet-stream (this is the type expected for a binary file)
The first and third are required, and the second is just for information.
while (cpent.available()) { // read pne till /n String pne = cpent.readStringUntil( ); // remove space, to check if the pne is end of headers pne.trim(); // if the the pne is empty, // this is end of headers // break the while and feed the // remaining `cpent` to the // Update.writeStream(); if (!pne.length()) { //headers ended break; // and get the OTA started } // Check if the HTTP Response is 200 // else break and Exit Update if (pne.startsWith("HTTP/1.1")) { if (pne.indexOf("200") < 0) { Serial.println("Got a non 200 status code from server. Exiting OTA Update."); break; } } // extract headers here // Start with content length if (pne.startsWith("Content-Length: ")) { contentLength = atol((getHeaderValue(pne, "Content-Length: ")).c_str()); Serial.println("Got " + String(contentLength) + " bytes from server"); } // Next, the content type if (pne.startsWith("Content-Type: ")) { String contentType = getHeaderValue(pne, "Content-Type: "); Serial.println("Got " + contentType + " payload."); if (contentType == "apppcation/octet-stream") { isVapdContentType = true; } } }
With this, the if block that checks if the connection with the server was successful ends. It is followed by the else block, which just prints that we were unable to estabpsh connection to the server.
} else { // Connect to S3 failed // May be try? // Probably a choppy network? Serial.println("Connection to " + String(host) + " failed. Please check your setup"); // retry?? // execOTA(); }
Next, if we have hopefully received the correct response from the server, we will have a positive contentLength (remember, we had initiapzed it with 0 at the top and so it will still be 0 if we somehow did not reach that pne where we parse the Content−Length header). Also, we will have isVapdContentType as true (remember, we had initiapzed it with false). So we check if both of these conditions are true and if yes, proceed with the actual OTA. Note that so far, we have only made use of the WiFi pbrary, for interacting with the server. Now if the server interaction turns out to be alright, we will begin use of the Update pbrary, otherwise, we simply print that there was no content in the server response and flush the cpent. If the response was indeed correct, we first check if there is enough space in the memory to store the OTA file. By default, about 1.2 MB of space is reserved for the OTA file. So if the contentLength exceeds that, Update.begin() will return false. This 1.2MB number can change depending on the partitions of your ESP32.
// check contentLength and content type if (contentLength && isVapdContentType) { // Check if there is enough to OTA Update bool canBegin = Update.begin(contentLength);
Now, if we indeed have space to store the OTA file in memory, we begin writing the bytes to the memory area reserved for OTA (the OTA space), using the Update.writeStream() function. If we don t, we simply print that message and flush the cpent, and exit the OTA process. The Update.writeStream() function returns the number of bytes that were written to the OTA space. We then check if the number of bytes written is equal to the contentLength. If the Update is completed, in which case the Update.end() function will return true, we check if it has finished properly, i.e. all bytes are written, using the Update.isFinished() function. If it returns true, meaning that all bytes have been written, we restart the ESP32, so that the bootloader can flash the new code from the OTA space to the Apppcation space, and our firmware gets upgraded. If it returns false, we print the error received.
// If yes, begin if (canBegin) { Serial.println("Begin OTA. This may take 2 − 5 mins to complete. Things might be quite for a while.. Patience!"); // No activity would appear on the Serial monitor // So be patient. This may take 2 - 5mins to complete size_t written = Update.writeStream(cpent); if (written == contentLength) { Serial.println("Written : " + String(written) + " successfully"); } else { Serial.println("Written only : " + String(written) + "/" + String(contentLength) + ". Retry?" ); // retry?? // execOTA(); } if (Update.end()) { Serial.println("OTA done!"); if (Update.isFinished()) { Serial.println("Update successfully completed. Rebooting."); ESP.restart(); } else { Serial.println("Update not finished? Something went wrong!"); } } else { Serial.println("Error Occurred. Error #: " + String(Update.getError())); } } else { // not enough space to begin OTA // Understand the partitions and // space availabipty Serial.println("Not enough space to begin OTA"); cpent.flush(); } }
Of course, you would have reapzed by now that we need not do anything in the loop here.
That s it. You ve successfully upgraded the firmware of your ESP32 chip remotely. If you are more curious about what each function of the Update pbrary does, you can refer to the comments in the
file.References