The below article and code are about how to OTA update esp32 using GSM or LTE 4g module using TinyGsmClient (based on AT Commands)

OTA updating of esp32 firmware via WiFi is easy, and for this, there are many articles available on the Internet.

However, some YouTubers and freelancers charge money for OTA over GSM/4G LTE Code. below is the code you can use directly or for reference.

I used the code below with Blynk IoT, Internally BlynkGsmClient uses TinyGsmClient which is a private member, so we can not use this TinyGsmClient object for another purpose like OTA update. (as per my experience).

as when BlynkGsmClient init TinyGsmClient it uses the default mux channel which is 0.

So for doing the OTA update, we will create another TinyGsmClient where we will have different mux channel (like 1) numbers on init.

below is the code I used and tested which is working flawlessly.

#if !defined(OTA_h)
#define OTA_h

#define BLYNK_DOMAIN "blr1.blynk.cloud" // you can use BLYNK_DEFAULT_DOMAIN
#define GSM_APN ""
#define GSM_USER ""
#define GSM_PASS ""
#define GSM_PIN "XXXX" // change to your sim pin

#include <Update.h>
#include <Blynk/BlynkDebug.h>
#include <TinyGsmClient.h>

class OTA {
private:
  TinyGsm* modem;
  TinyGsmClient client;
  String overTheAirURL;

public:
  OTA() {}

  void setOverTheAirURL(String url) {
    overTheAirURL = url;
  }

  void enterOTA(TinyGsm& gsm, uint8_t mux = 1) {
    modem = &gsm;
    client.init(modem, mux);

    BLYNK_LOG("OTA: Started..");
    if (!modem->isNetworkConnected()) {
      delay(7000);
      BLYNK_LOG("OTA: Modem init...");
      if (!modem->init(GSM_PIN)) {// if no GSM pin set then use init()
        BLYNK_LOG("OTA: Cannot init Modem");
        delay(1000);
        return;
      }
      BLYNK_LOG("OTA: Waiting for network...");
      delay(8000);
      if (!modem->waitForNetwork()) {
        BLYNK_LOG("OTA: Network Connection fail");
        delay(5000);
        return;
      }
      BLYNK_LOG("OTA: Network Connection Success ✔");
    } else {
      BLYNK_LOG("OTA: Network Connected  ✔");
    }

    // GPRS connection parameters are usually set after network registration
    if (!modem->isGprsConnected()) {
      BLYNK_LOG("OTA: GPRS Connecting ...");
      if (!modem->gprsConnect(GSM_APN, GSM_USER, GSM_PASS)) {
        BLYNK_LOG("OTA: GPRS Connection fails");
        delay(5000);
        return;
      }
      BLYNK_LOG("OTA: GPRS Connection Success ✔");
    } else {
      BLYNK_LOG("OTA: GPRS Connected ✔");
    }

    BLYNK_LOG2("Connecting to ", BLYNK_DOMAIN);
    if (!client.connect(BLYNK_DOMAIN, BLYNK_DEFAULT_PORT)) {
      BLYNK_LOG(" fail");
      delay(5000);
      return;
    }
    BLYNK_LOG2("OTA: Connected to ", BLYNK_DOMAIN);

    BLYNK_LOG2("Firmware update URL: ", overTheAirURL);
    String binUrl = overTheAirURL.substring(overTheAirURL.indexOf("d") + 1);
    char resource[binUrl.length() + 1];
    binUrl.toCharArray(resource, binUrl.length() + 1);
    BLYNK_LOG2("", resource);
    // Make a HTTP GET request:
    client.print(String("GET ") + resource + " HTTP/1.0\r\n");
    client.print(String("Host: ") + BLYNK_DOMAIN + "\r\n");
    client.print("Connection: close\r\n\r\n");

    // Let's see what the entire elapsed time is, from after we send the request.
    uint32_t timeElapsed = millis();
    BLYNK_LOG("Waiting for response header");

    // While we are still looking for the end of the header (i.e. empty line
    // FOLLOWED by a newline), continue to read data into the buffer, parsing each
    // line (data FOLLOWED by a newline). If it takes too long to get data from
    // the client, we need to exit.

    const uint32_t clientReadTimeout = 60000;
    uint32_t clientReadStartTime = millis();
    String headerBuffer;
    bool finishedHeader = false;
    uint32_t contentLength = 0;

    while (!finishedHeader) {
      int nlPos;
      if (client.available()) {
        clientReadStartTime = millis();
        while (client.available()) {
          char c = client.read();
          headerBuffer += c;
          //Serial.print(c);
          // Let's exit and process if we find a new line
          if (headerBuffer.indexOf(F("\r\n")) >= 0) break;
        }
      } else {
        if (millis() - clientReadStartTime > clientReadTimeout) {
          // Time-out waiting for data from client
          BLYNK_LOG(">>> Client Timeout !");
          break;
        }
      }

      // See if we have a new line.
      nlPos = headerBuffer.indexOf(F("\r\n"));

      if (nlPos > 0) {
        headerBuffer.toLowerCase();
        // Check if line contains content-length
        if (headerBuffer.startsWith(F("content-length:"))) {
          contentLength = headerBuffer.substring(headerBuffer.indexOf(':') + 1).toInt();
          BLYNK_LOG2("Got Content Length: ", contentLength);
        }

        headerBuffer.remove(0, nlPos + 2);  // remove the line
      } else if (nlPos == 0) {
        // if the new line is empty (i.e. "\r\n" is at the beginning of the line),
        // we are done with the header.
        finishedHeader = true;
      }
    }

    // The two cases which are not managed properly are as follows:
    // 1. The client doesn't provide data quickly enough to keep up with this
    // loop.
    // 2. If the client data is segmented in the middle of the 'Content-Length: '
    // header,
    //    then that header may be missed/damaged.
    //

    // Begin Update firmware
    BLYNK_LOG2("Start upgrade. firmware size: ", contentLength);
    if (!Update.begin(contentLength)) {
      BLYNK_LOG("Not enough space to begin OTA");
      return;
    }

    uint32_t readLength = 0;
    int progress = 0;
    int writtenTotal = 0;

    if (finishedHeader) {
      BLYNK_LOG("Reading response data");
      clientReadStartTime = millis();

      uint8_t buffer[1024];
      while (readLength < contentLength && client.connected() && millis() - clientReadStartTime < clientReadTimeout) {
        while (client.available()) {
          int len = client.read(buffer, 1024);
          int written = Update.write(buffer, len);
          writtenTotal = writtenTotal + written;
          readLength = readLength + len;
          int newProgress = (readLength * 100) / contentLength;
          if (newProgress - progress >= 5 || newProgress == 100) {
            progress = newProgress;
            BLYNK_LOG3("\r ", progress, "%\n");
          }
          clientReadStartTime = millis();
        }
      }
    }

    timeElapsed = millis() - timeElapsed;
    float duration = float(timeElapsed) / 1000;
    BLYNK_LOG2("Duration : ", duration);
    // Shutdown

    client.stop();
    BLYNK_LOG("BLYNK_DOMAIN disconnected");

    if (readLength != contentLength || readLength != writtenTotal) {
      BLYNK_LOG("------------------# Error #-------------------------------");
      BLYNK_LOG2("Content Length : ", contentLength);
      BLYNK_LOG4("Written only : ", writtenTotal, "/", readLength + ". Retry?");
      BLYNK_LOG("----------------------------------------------------------");
      return;
    }
    ///---------------------------------------------------------------------

    if (!Update.end()) {
      BLYNK_LOG2("Error #", Update.getError());
      return;
    }

    if (!Update.isFinished()) {
      BLYNK_LOG("Update failed.");
      return;
    }

    BLYNK_LOG("=== Update successfully completed. Rebooting. ===");
    delay(1000);
    restartMCU();
  }

  void restartMCU() {
    ESP.restart();
    while (1) {};
  }

  void enterError() {
    BLYNK_LOG("Restarting after error.");
    delay(100);
    restartMCU();
  }
};
#endif

OTA.h

Thanks for reading, if there is any mistake in the above code that you want to correct, please use this URL Contact Page.

BugFix
BugFix
I am an Admin, Author, Editor, Software Developer, and Founder of bugfix.dev
Great! You’ve successfully signed up.
Welcome back! You've successfully signed in.
You've successfully subscribed to BugFix.
Your link has expired.
Success! Check your email for magic link to sign-in.
Success! Your billing info has been updated.
Your billing was not updated.