AT commands to perform OTA using cellural network

I have module named “ESP32-S3 EC200U 4G LTE Cat-1 GNSS WiFi Bluetooth IoT Modem - 7Semi” and i have a server with a .bin file i want to download and flash it in es32s3 using cellural network can anybody help me with at commands i’m using arduino. void updatefirmware() {
Serial.println(“— Starting CELLULAR OTA Firmware Update —”);
Serial.print("Target URL: ");
Serial.println(FIRMWARE_URL);

// 1. Temporarily disconnect from MQTT (to free up modem context for HTTP)
Serial.println(“Stopping modem MQTT connection…”);
sendATCommand(“AT+QMTDISC=0”, 5000);

// Disable WiFi SoftAP during OTA to reduce conflicts
Serial.println(“Disabling WiFi SoftAP for OTA…”);
WiFi.softAPdisconnect(true);
delay(1000);

// Variables used throughout the process
bool otaSuccess = false;
int contentLength = 0;
bool stepOK = true; // Flag to manage sequential flow
esp_ota_handle_t ota_handle = 0; // OTA handle for low-level ops
esp_err_t ota_err = ESP_OK; // Track overall OTA error

// 2. Configure HTTP context (assuming PDP context 1 is already active from MQTT setup)
Serial.println(“Configuring HTTP connection…”);
sendATCommand(“AT+QHTTPCFG="contextid",” + String(HTTP_CONTEXT_ID), 1000);
// Set response header format to 0 (no response header) for simplicity
sendATCommand(“AT+QHTTPCFG="responseheader",0”, 1000);

// 3. Set the URL
if (stepOK) {
Serial.print(“Setting URL (length “);
Serial.print(strlen(FIRMWARE_URL));
Serial.println(”)…”);

// Command: AT+QHTTPURL=<url_len>,<timeout>
String urlCommand = "AT+QHTTPURL=" + String(strlen(FIRMWARE_URL)) + ",60"; // 60s timeout
String urlResponse = sendATCommand(urlCommand, 500); // Wait for CONNECT prompt

if (urlResponse.indexOf("CONNECT") != -1) {
    SerialAT.println(FIRMWARE_URL); // Send the URL payload
} else {
    Serial.println("ERROR: Failed to prepare URL for HTTP. Aborting URL set.");
    stepOK = false;
}

// Wait for response after sending URL (Expecting OK or ERROR)
if (stepOK) {
  urlResponse = sendATCommand("", 10000); 

  if (urlResponse.indexOf("OK") == -1) {
      Serial.println("ERROR: Modem rejected URL. Aborting URL set.");
      stepOK = false;
  }
}

}

// 4. Send GET Request and parse size
int httpCode = 0;
if (stepOK) {
Serial.println(“Sending HTTP GET request…”);
// Wait up to 65 seconds for the entire GET operation
String getResponse = sendATCommand(“AT+QHTTPGET=60”, 65000);

// Check for success code and parse it
int getResponseIndex = getResponse.indexOf("+QHTTPGET:");
if (getResponseIndex != -1) {
    int colonIndex = getResponse.indexOf(':', getResponseIndex);
    if (colonIndex != -1) {
        String dataPart = getResponse.substring(colonIndex + 1);
        dataPart.trim();  // Remove leading/trailing whitespace (handles the space after :)
        
        // dataPart now = "0,200,329472"
        String errStr = getValue(dataPart, ',', 0);
        if (errStr == "0") {
            String codeStr = getValue(dataPart, ',', 1);
            httpCode = codeStr.toInt();
            
            // Extract the content size (index 2, if present)
            String sizeStr = getValue(dataPart, ',', 2);
            contentLength = sizeStr.toInt();
        }
    }
}

// Use the parsed code to determine success
if (httpCode != 200 && httpCode != 301) {
    Serial.printf("ERROR: HTTP GET failed. Code: %d. Aborting GET.\n", httpCode);
    stepOK = false;
    contentLength = 0; // Ensure content length is reset if GET failed
} else {
  Serial.printf("HTTP GET successful. Code: %d. Firmware size: %d bytes\n", httpCode, contentLength);
}

}

// 5. Check Content Length (extracted in Step 4)
if (stepOK) {
if (contentLength <= 0) {
Serial.println(“ERROR: Content length reported as 0 or could not be extracted from +QHTTPGET response. Aborting content read.”);
stepOK = false;
} else {
Serial.printf(“Content length confirmed: %d bytes\n”, contentLength);
}
}

// 6. Validate and start OTA process
if (stepOK) {
const esp_partition_t *next_part = esp_ota_get_next_update_partition(NULL);
if (!next_part || next_part->size < contentLength) {
Serial.printf(“ERROR: OTA partition invalid (size: %d, needed: %d)\n”, next_part ? next_part->size : 0, contentLength);
stepOK = false;
return;
}
Serial.printf(“OTA to partition ‘%s’ at 0x%08x (size: %d bytes)\n”, next_part->label, next_part->address, next_part->size);

esp_err_t init_err = esp_ota_begin(next_part, contentLength, &ota_handle);
if (init_err != ESP_OK) {
    Serial.printf("ERROR: esp_ota_begin failed: %s (code %d)\n", esp_err_to_name(init_err), init_err);
    stepOK = false;
    ota_err = init_err;
    return;
}
Serial.println("OTA update partition ready. Reading and writing firmware...");

}

// 7. Request to read the entire file and stream it
if (stepOK) {
Serial.println(“Requesting binary stream read…”);
String readCommand = “AT+QHTTPREAD=60”; // wait_time=60s (max interval between packets)
SerialAT.println(readCommand);
Serial.print("\n> ");
Serial.println(readCommand);

// Read initial response until CONNECT (up to 30 seconds)
String initialResponse;
unsigned long startTime = millis();
while (millis() - startTime < 30000) { 
    while (SerialAT.available()) {
        char c = (char)SerialAT.read();
        initialResponse += c;
    }
    if (initialResponse.indexOf("CONNECT") != -1) {
        break;
    }
}

if (initialResponse.indexOf("CONNECT") == -1) {
    Serial.println("ERROR: Did not receive CONNECT prompt for binary read. Aborting stream.");
    // Attempt to cancel any pending operation
    SerialAT.write(0x1A); // Send Ctrl+Z to break out of data mode
    stepOK = false;
    ota_err = ESP_ERR_TIMEOUT;
} else {
  // Stream the binary data
  Serial.println("Streaming binary data from modem...");
  
  size_t written = 0;
  size_t totalRead = 0;
  const size_t chunkSize = 4096; // Sector-aligned for ESP32-S3 flash
  uint8_t buffer[chunkSize] __attribute__((aligned(4)));  // 4-byte aligned
  
  // Use a longer timeout for the entire stream process (increased to 5 minutes)
  unsigned long streamTimeout = 300000; // 300 seconds
  unsigned long streamStartTime = millis(); 
  unsigned long lastDataTime = millis(); // Track last data receipt for per-packet timeout

  // Loop to read data directly from SerialAT and write to OTA.
  while (totalRead < contentLength) {
      // Check for overall stream timeout
      if (millis() - streamStartTime > streamTimeout) {
          Serial.println("ERROR: Streaming timeout reached.");
          ota_err = ESP_ERR_TIMEOUT;
          break; 
      }
      
      // Check for available data
      if (SerialAT.available()) {
          size_t bytesToRead = SerialAT.available();
          if (totalRead + bytesToRead > contentLength) {
              // Ensure we don't read past the declared content length
              bytesToRead = contentLength - totalRead;
          }
          if (bytesToRead > chunkSize) {
              bytesToRead = chunkSize; // Cap at chunk size
          }

          // Read bytes directly into the buffer
          size_t bytesRead = SerialAT.readBytes((uint8_t*)buffer, bytesToRead);
          if (bytesRead > 0) {
              // Write to OTA using low-level API and check for errors
              esp_err_t write_err = esp_ota_write(ota_handle, (const void*)buffer, bytesRead);
              if (write_err != ESP_OK) {
                  Serial.printf("OTA write error: %s (code %d)\n", esp_err_to_name(write_err), write_err);
                  ota_err = write_err;
                  stepOK = false;
                  break;
              }
              written += bytesRead;
              totalRead += bytesRead;
              
              // Update timers
              streamStartTime = millis(); 
              lastDataTime = millis();
              
              // Optional: print progress every 10% and log bytes per chunk for debugging
              int progress = (totalRead * 100) / contentLength;
              if (progress != ((totalRead - bytesRead) * 100) / contentLength) {
                  Serial.printf("Progress: %d%% (chunk: %d bytes)\n", progress, bytesRead);
              } else if (totalRead % 10000 == 0) { // Log every ~10KB for ongoing chunks
                  Serial.printf("Streaming: %d/%d bytes read\n", totalRead, contentLength);
              }
          }
      } else {
          // No data available; check if we've waited too long since last packet (align with QHTTPREAD wait_time)
          if (millis() - lastDataTime > 60000) { // 60s no-data timeout
              Serial.println("ERROR: No data received for 60 seconds. Aborting stream.");
              ota_err = ESP_ERR_TIMEOUT;
              stepOK = false;
              break;
          }
          vTaskDelay(pdMS_TO_TICKS(5)); // Yield to flash task (better than delay for S3)
      }
  }

  Serial.printf("Finished reading stream. Total bytes expected: %d, Total bytes read: %d, Total bytes written to OTA: %d\n", contentLength, totalRead, written);

  // 8. Finalize Update and check modem status response
  if (stepOK && written == contentLength) {
      esp_err_t end_err = esp_ota_end(ota_handle);
      ota_err = end_err;  // Update overall error
      if (end_err == ESP_OK) {
          const esp_partition_t *next_part = esp_ota_get_next_update_partition(NULL);
          esp_err_t set_err = esp_ota_set_boot_partition(next_part);
          if (set_err == ESP_OK) {
              Serial.println("Update successful. Finalizing and restarting...");
              otaSuccess = true;
              // After a successful OTA, restart the ESP32 to boot the new firmware
              Serial.println("Restarting ESP32...");
              delay(100); 
              ESP.restart(); // Add an explicit restart command
          } else {
              Serial.printf("ERROR: esp_ota_set_boot_partition failed: %s (code %d)\n", esp_err_to_name(set_err), set_err);
              ota_err = set_err;
          }
      } else {
          Serial.printf("ERROR: esp_ota_end failed: %s (code %d)\n", esp_err_to_name(end_err), end_err);
      }
  } else {
      esp_ota_end(ota_handle);  // Clean up even on failure
      Serial.printf("ERROR: Update failed. Wrote %d of %d bytes. OTA Error Code: %d (%s)\n", written, contentLength, ota_err, esp_err_to_name(ota_err));
  }
}

}

// Explicitly close the HTTP read session
if (stepOK || !otaSuccess) {
Serial.println(“Closing HTTP read session…”);
sendATCommand(“AT+QHTTPREAD=0”, 5000);
}

// 9. Cleanup: This block runs regardless of success or failure.
if (!otaSuccess) {
Serial.println(“Update process finished. Resuming operation…”);
}

// Clear HTTP context
sendATCommand(“AT+QHTTPURL=0”, 500); // Close URL session

// Re-establish WiFi SoftAP
WiFi.softAP(ssid, password);
Serial.println(“WiFi SoftAP restarted.”);

// Re-establish MQTT connection (via modem)
connectMQTT();
}

Blockquote

Hi, I sent you a private OTA upgrade document for your reference. May I ask if this is the upgrade program you wrote yourself, is it?

yes i have written this code but it not working as i want. i want to upgrade esp32s3 module code attached to ec200u-cn module over cellular network.

Hi, I have shifted the topic to Python. Let’s see if they can help you check the code.

hi
May I ask which module model you are using? And what is the firmware version?

module : 7Semi ESP32-S3 EC200U 4G LTE Cat-1 WiFi Bluetooth GNSS IoT Smart Modem
firmware : Quectel EC200U EC200UCNAAR03A15M08