OTA Download not Working on second Update

Hi all,

I’m having some problems with updating my code via OTA. The way I have it set up is that I have a file titled ota_updates.cpp which contains a function called get_system_update(). Elsewhere in my code, I’m calling this get_system_update() function to download a firmware.bin file I upload to a Google Drive folder. It works well the first time but on the second attempt to retrieve the updated code, I receive a “Firmware upgrade fail” output to the console. I think this may be because I only have 1 app ota partition. I removed the second OTA partition because I only have 4MB of flash (I’m using a ESP-WROOM-32D) and each OTA partition should be as big as the app partition. However, my app is almost 1.5 MB in flash. My first question is do I need 2 OTA partitions and could this be what’s causing problems? Is there a way around this by changing the partitions.csv file or the code so that I only use 1 partition and have enough space for my app? If I must have 2 OTA partitions, I have an SD card that I can use. How can I load the app on the SD Card and configure partitions.csv so that it works with this card?

This is how my console output looks like:


.[0;32mI (15263) esp_https_ota: Starting OTA....[0m
.[0;32mI (15265) esp_https_ota: Writing to partition subtype 16 at offset 0x180000.[0m
.[0;31mE (15266) esp_https_ota: esp_ota_begin failed (ESP_ERR_OTA_PARTITION_CONFLICT).[0m
Http Event Disconnected
Http Event Disconnected
Firmware Upgrade Fail

Here’s how my ota_updates.cpp file looks:

#include "OTA_Updates.h"
#include "Arduino.h"
#include "HttpsOTAUpdate.h"


static const char *url = "MY_FIRMWARE.BIN_URL";

static const char *server_certificate = "-----BEGIN CERTIFICATE-----\n" \
"MIIDdTCCAl2gAwIBAgILBAAAAAABFUtaw5QwDQYJKoZIhvcNAQEFBQAwVzELMAkG\n" \
"A1UEBhMCQkUxGTAXBgNVBAoTEEdsb2JhbFNpZ24gbnYtc2ExEDAOBgNVBAsTB1Jv\n" \
"b3QgQ0ExGzAZBgNVBAMTEkdsb2JhbFNpZ24gUm9vdCBDQTAeFw05ODA5MDExMjAw\n" \
"MDBaFw0yODAxMjgxMjAwMDBaMFcxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9i\n" \
"YWxTaWduIG52LXNhMRAwDgYDVQQLEwdSb290IENBMRswGQYDVQQDExJHbG9iYWxT\n" \
"aWduIFJvb3QgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDaDuaZ\n" \
"jc6j40+Kfvvxi4Mla+pIH/EqsLmVEQS98GPR4mdmzxzdzxtIK+6NiY6arymAZavp\n" \
"xy0Sy6scTHAHoT0KMM0VjU/43dSMUBUc71DuxC73/OlS8pF94G3VNTCOXkNz8kHp\n" \
"1Wrjsok6Vjk4bwY8iGlbKk3Fp1S4bInMm/k8yuX9ifUSPJJ4ltbcdG6TRGHRjcdG\n" \
"snUOhugZitVtbNV4FpWi6cgKOOvyJBNPc1STE4U6G7weNLWLBYy5d4ux2x8gkasJ\n" \
"U26Qzns3dLlwR5EiUWMWea6xrkEmCMgZK9FGqkjWZCrXgzT/LCrBbBlDSgeF59N8\n" \
"9iFo7+ryUp9/k5DPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8E\n" \
"BTADAQH/MB0GA1UdDgQWBBRge2YaRQ2XyolQL30EzTSo//z9SzANBgkqhkiG9w0B\n" \
"AQUFAAOCAQEA1nPnfE920I2/7LqivjTFKDK1fPxsnCwrvQmeU79rXqoRSLblCKOz\n" \
"yj1hTdNGCbM+w6DjY1Ub8rrvrTnhQ7k4o+YviiY776BQVvnGCv04zcQLcFGUl5gE\n" \
"38NflNUVyRRBnMRddWQVDf9VMOyGj/8N7yy5Y0b2qvzfvGn9LhJIZJrglfCm7ymP\n" \
"AbEVtQwdpf5pLGkkeB6zpxxxYu7KyJesF12KwvhHhm4qxFYxldBniYUr+WymXUad\n" \
"DKqC5JlR3XC321Y9YeRq4VzW9v493kHMB65jUr9TU/Qr6cf9tveCX4XSQRjbgbME\n" \
"HMUfpIBvFSDJ3gyICh3WZlXi/EjJKSZp4A==\n"\
"-----END CERTIFICATE-----";

HttpsOTAStatus_t otastatus;

void HttpEvent(HttpEvent_t *event)
{
    switch(event->event_id) {
        case HTTP_EVENT_ERROR:
            Serial.println("Http Event Error");
            break;
        case HTTP_EVENT_ON_CONNECTED:
            Serial.println("Http Event On Connected");
            break;
        case HTTP_EVENT_HEADER_SENT:
            Serial.println("Http Event Header Sent");
            break;
        case HTTP_EVENT_ON_HEADER:
            Serial.printf("Http Event On Header, key=%s, value=%s\n", event->header_key, event->header_value);
            break;
        case HTTP_EVENT_ON_DATA:
            break;
        case HTTP_EVENT_ON_FINISH:
            Serial.println("Http Event On Finish");
            break;
        case HTTP_EVENT_DISCONNECTED:
            Serial.println("Http Event Disconnected");
            break;
    }
}


void get_system_update()
{
    Serial.println("gotchu better");
    HttpsOTA.onHttpEvent(HttpEvent);
    Serial.println("Starting OTA");
    HttpsOTA.begin(url, server_certificate); 

    Serial.println("Please Wait it takes some time ...");
    while (true)
    {    
        otastatus = HttpsOTA.status();
        if(otastatus == HTTPS_OTA_SUCCESS) { 
            Serial.println("Firmware written successfully. To reboot device, call API ESP.restart() or PUSH restart button on device");
            delay(5000);
            ESP.restart();
        } else if(otastatus == HTTPS_OTA_FAIL) { 
            Serial.println("Firmware Upgrade Fail");
        }
        delay(1000);
    }
}

Here’s how my partitions.csv file looks:

# ESP-IDF Partition Table
# Name, Type, SubType, Offset, Size, Flags
nvs,data,nvs,0x9000,16K,
otadata,data,ota,0xd000,8K,
phy_init,data,phy,0xf000,4K,
factory,app,factory,0x10000,0x170000,
ota_0,app,ota_0,0x180000,0x170000,

I read through

And it’s my understanding that ESP-IDF wants you to normally have a factory application it can always fallback to if things fail and two OTA partitions, to which it writes and boots from alternatively in the case of OTA updates.

However,

For factory boot settings, the OTA data partition should contain no data (all bytes erased to 0xFF). In this case the esp-idf software bootloader will boot the factory app if it is present in the partition table. If no factory app is included in the partition table, the first available OTA slot (usually ota_0) is booted.

After the first OTA update, the OTA data partition is updated to specify which OTA app slot partition should be booted next.

So my thoughts are:

  1. If you have an app of size ~1.5 MByte at a flash size of 4MByte, you have 0.5MByte minus some overhead for a factory app. You could engineer a factory a app that directly does the OTA update to your actual main application, and it could maybe fit in that slot, and thus still have two 1.5 Mbyte OTA partitions.
  2. You could remove the factory app partition from your partition table so take advantage of the “If no factory app is included in the partition table, the first available OTA slot (usually ota_0) is booted.” behavior, then have two OTA partitions of equal size, aka,
# ESP-IDF Partition Table
# Name, Type, SubType, Offset, Size, Flags
nvs,data,nvs,0x9000,16K,
otadata,data,ota,0xd000,8K,
phy_init,data,phy,0xf000,4K,
ota_0,app,ota_0,0x100000,0x170000,
ota_1,app,ota_1,0x270000,0x170000,

PlatformIO might however not understand this edge case and fail to find the start address for your application. I have not tested this.

Also see here.

Thank you so much for the fast reply and the solution, I tried option 2 and it worked! Going into the future, if my app does get bigger do you know how I can store my app on an SD card?

This says no, you can’t directly run from the SD card. It would be easier to buy a ESP32 with a larger flash memory (8MByte or 16Mbyte are available).

Great, thank you so much for your help once again!