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

hi

Based on the firmware, are you using the AT solution instead of the QuecPython solution?

It appears you’re using ESP IDF.
You’d better figure out the OTA process of your module on your PC first. No one can help you check the ESP32 code. You can post a thread telling Anna which command is causing the error, and Quectel can help you analyze the cause. Don’t discuss your IDF code with them.