B4R Tutorial ESP32: Simple OTA/Update via WebServer

This example (adapted from the Arduino examples under Arduino->File->Examples)

- downloads a file from a server (exactly: the update file)
- store this file to the filesystem (SPIFFS)
- starts an update with the downloaded file

How to use:

- Compile this app with "default" options from the Boardmanager
- Create a 2nd app (= the update)
- Go to options/bin and copy the *.bin file to a folder on your server. Change the name to "update.bin" or similar (must be the same as in the code of course)
- Start the app. It will download the *.bin file and try to update the ESP with the file

Notes:

- can be used without a server if you transmit the file via AsyncStreams or similar.
- https works, too

B4X:
#Region Project Attributes
    #AutoFlushLogs: True
    #CheckArrayBounds: True
    #StackBufferSize: 10000
#End Region

Sub Process_Globals
    Public Serial1 As Serial
    Private fs As ESP8266FileSystem
    Private wifi As ESP8266WiFi
    Private bc As ByteConverter
    Private FullPath(200) As Byte
    Private SaveAs(40) As Byte
End Sub

Private Sub AppStart
    Serial1.Initialize(115200)
    Log("AppStart")
    
    For i=1 To 10
        Log("Connecting to WiFi...")
        Log(i)
        
        If wifi.Connect2("SSID","PW") Then
            Log("Connected to wireless network.")
            Log("My ip: ", wifi.LocalIp)
            InitFS
            DownloadFile
            ListFiles
            Exit
        Else
            Log("Failed to connect...")
        End If
    Next
    
End Sub

Sub DownloadFile
    bc.ArrayCopy("http://192.168.178.23/esp32ota/update.bin",FullPath)
    bc.ArrayCopy("/update.bin",SaveAs) '/ is important!
    RunNative("downloadfile", Null)
    RunNative("updateFromFS", Null)
    
End Sub

Sub InitFS
    Log("Initializing Filesystem...")
    
    If(fs.Initialize())=False Then
        Log("Formatting Filesystem. This may take some time")
        fs.Format
        If(fs.Initialize())=True Then
            Log("Formatting succesful...")
            
        Else
            Log("Error formatting Filesystem...")
        End If
    End If
    
    Log("Total size: ", NumberFormat(fs.TotalSize / 1024, 0, 0), " KB")
    Log("Used size: ", NumberFormat(fs.UsedSize / 1024, 0, 0), " KB")
    ListFiles
End Sub

Sub ListFiles
    Log("Files:")
    For Each f As File In fs.ListFiles("/")
        Log("     ",f.Name," Size: ",f.Size)
    Next
End Sub



#if c
#include <HTTPClient.h>
#include "FS.h"
#include "SPIFFS.h"
#include <Update.h>
void downloadfile(B4R::Object* o) {
    
    HTTPClient http;

    printf("Downloading from %s...\n",(char*)b4r_main::_fullpath->data);
    printf("Saving as %s...\n",(char*)b4r_main::_saveas->data);
    
    // configure server and url
    http.begin((char*)b4r_main::_fullpath->data);

    printf("[HTTP] GET...\n");
    // start connection and send HTTP header
    int httpCode = http.GET();
    if(httpCode > 0) {
        // HTTP header has been send and Server response header has been handled
        // file found at server
        if(httpCode == HTTP_CODE_OK) {
            printf("Connected...\n");
            // get lenght of document (is -1 when Server sends no Content-Length header)
            int len = http.getSize();
            printf("Filesize is %i\n", len);
            // create buffer for read
            uint8_t buff[1024] = { 0 };

            // get tcp stream
            WiFiClient * stream = http.getStreamPtr();
            
            //File
            
            File file = SPIFFS.open((char*)b4r_main::_saveas->data, "w");
            if(!file){
                Serial.println("- failed to open file for writing");
                return;
            }
            printf("Writing File...\n");
            //File
            int total=0;
            // read all data from server
            while(http.connected() && (len > 0 || len == -1)) {
                // get available data size
                size_t size = stream->available();
                
                if(size) {
                    // read up to (buffer size) bytes
                    int c = stream->readBytes(buff, ((size > sizeof(buff)) ? sizeof(buff) : size));
                                        
                    //file.print((char *)buff);
                    file.write((byte*) &buff, c);
                    total=total+ c;       
                    //File

                    if(len > 0) {
                        len -= c;
                    }
                }
                delay(1);
            }
            file.close();
            printf("Total Bytes downloaded: %i\n", total);
        }
    } else {
        printf("[HTTP] GET... failed, error: %s\n", http.errorToString(httpCode).c_str());
    }

    http.end();
}

// perform the actual update from a given stream
void performUpdate(Stream &updateSource, size_t updateSize) {
   if (Update.begin(updateSize)) {     
      size_t written = Update.writeStream(updateSource);
      if (written == updateSize) {
         Serial.println("Written : " + String(written) + " successfully");
      }
      else {
         Serial.println("Written only : " + String(written) + "/" + String(updateSize) + ". Retry?");
      }
      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
   {
      Serial.println("Not enough space to begin OTA");
   }
}

// check given FS for valid update.bin and perform update if available
void updateFromFS(B4R::Object* o) {
   File updateBin = SPIFFS.open("/update.bin");
   if (updateBin) {
      if(updateBin.isDirectory()){
         Serial.println("Error, update.bin is not a file");
         updateBin.close();
         return;
      }

      size_t updateSize = updateBin.size();

      if (updateSize > 0) {
         Serial.println("Try to start update");
         performUpdate(updateBin, updateSize);
      }
      else {
         Serial.println("Error, file is empty");
      }

      updateBin.close();
    
      // whe finished remove the binary from sd card to indicate end of the process
      SPIFFS.remove("/update.bin");     
   }
   else {
      Serial.println("Could not load update.bin from sd root");
   }
}

#End If
 

Cableguy

Expert
Licensed User
Longtime User
Question... maybe a stupid one...
After downloading and updating to the downloaded file, the OTA will no longer be an option.... unless it's part of the new updated file... right?
 

KMatle

Expert
Licensed User
Longtime User
Question... maybe a stupid one...
After downloading and updating to the downloaded file, the OTA will no longer be an option.... unless it's part of the new updated file... right?
Correct. The new version must have the code to update, too. Otherwise it's like a normal app. With OTA you have half of the space available as it is "failsafe". The new code is loaded to the 2nd half and only if everything is ok, the ESP switches to it. Otherwise it keeps the old one.
 

BertI

Member
Licensed User
Longtime User
Thank you for making it easier to understand. I tried it pretty much verbatim other than changing the download URL and WiFi credentials. Everything seemed to go fine in terms of getting the file from the site and storing it in SPIFFs but then when it got to performing the update I got the following:

Try to start update
abort() was called at PC 0x401653b9 on core 1
ELF file SHA256: 0000000000000000
Backtrace: 0x4008c8d0:0x3ffb1da0 0x4008cb49:0x3ffb1dc0 0x401653b9:0x3ffb1de0 0x4008a096:0x3ffb1e00 0x40165255:0x3ffb1e20 0x400d6695:0x3ffb1e40 0x400d6258:0x3ffb1e60 0x400d64af:0x3ffb1e80 0x400d10cf:0x3ffb1ea0 0x400d17b7:0x3ffb1ef0 0x400d1863:0x3ffb1f30 0x400d1914:0x3ffb1f60 0x400d1c7e:0x3ffb1f90 0x400d7e9e:0x3ffb1fb0 0x4008db4e:0x3ffb1fd0

Any ideas?

Also trying to understand why such a short piece of code would result in around 800Kb compiled...
 

BertI

Member
Licensed User
Longtime User
Answering my own question - seems to have been a problem with the partition scheme. The instructions say to use the 'default' options from the board manager but the problem is that I am using an AI Thinker ESP32-Cam module and the board manager currently looks like only provides the huge app option for this. So I just switched the board selector to the ESP32 Wrover and was able to select the 'default' scheme which gives a balance between two application partitions as well as a SPIFFs partition roughly 1.3MB each. With this scheme in place the update did work. However.. I presume I'll have to be careful with pin settings if using the camera (currently camera not connected) and also not exactly much headroom. Still puzzled about why about 800KB are required before you can even get started on the main code
 

BertI

Member
Licensed User
Longtime User
Yes, as you describe, I understand that you have to have enough room for the existing code and the updated code so that if the downloaded update code isn't quite right then the existing code can still be used. Again in my non authorative understanding I believe this is what the partition definitions OTA0 and OTA1 are for, i.e that you can't run the code from the SPIFFs area. This suggests that downloading the code to SPIFFs first then effectively requires a 3rd partition of similar size, so limiting the code size to 1/3 of total Flash (after taking away some other overheads). However if you download directly to OTA0/1 then the SPIFFs allocation can be minimal so you can have more for space for OTA0 and 1. Since I came to B4R because I was running away from having to write in C - otherwise why not just use the Arduino IDE? - I still find it difficult to decipher the C examples, though I do still try when I have the time as they say learning more languages can help delay dementia :). Anyhow it looks like the update function can take it's source directly from 'client' which I vaguely guess means that it has a mechanism to transfer the data directly to OTA0/1 area from the server.

As an alternative ... Having realised that my earlier failed attempts to use a slightly tweaked version of the ESP8266 OTA library from this post: https://www.b4x.com/android/forum/threads/esp8266-ota-updater.101387/ had been thwarted by the partition scheme, I re-tried it now with a minimal SPIFFs, OTA1,OTA2 scheme and this now works ok too. It is possible to add more partition schemes for the ESP32-Cam board by editing the boards.txt file pertaining to the Arduino IDE (see this link for explanation: https://robotzero.one/arduino-ide-partitions/). So with a miniimal SPIFFs scheme you can get perhaps 1.7Mb per OTA which is enough to give you some coding headroom beyond my experience of the ~800Kb base level gobbled up by I don't know what yet.
 

Digitek

Member
Hello.
What website do you recommend to host the update file?
I have an FTP server but the example does not support this type of service.
 

BertI

Member
Licensed User
Longtime User
Hello.
What website do you recommend to host the update file?
I have an FTP server but the example does not support this type of service.
You would probably have to adjust/add to the code to access by FTP, otherwise just an http/s server. I'm no expert but for local dabbling I have used XAMPP which is Apache based. You can set it up on a local PC.
 
Top