How to post bin file for OTA in ESP32 without manually opening the page in the browser hosted in the webserver

Viewed 825

I am trying to trigger OTA update on a webserver hosted on ESP32 externally and without opening the browser address of the webserver.

I have seen some examples where the OTA update action can be achieved if the ESP is connected as client, but I am looking for solutions where we can keep the ESP as the webserver only. Since, this action is assumed to be triggered over network so for that reasons, I will happily accept if any method curl, python/JavaScript http method, node red etc.

This is the server part of code running in ESP32:

#include <WiFi.h>
#include <WiFiClient.h>
#include <WebServer.h> 
#include <ESPmDNS.h> 
#include <Update.h> 
#include <WiFiManager.h>  
WebServer server(80);


const char* serverIndex = 
"<script src='https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js'></script>"
"<form method='POST' action='#' enctype='multipart/form-data' id='upload_form'>"
   "<input type='file' name='update'>"
        "<input type='submit' value='Update'>"
    "</form>"
 "<div id='prg'>progress: 0%</div>"
 "<script>"
  "$('form').submit(function(e){"
  "e.preventDefault();"
  "var form = $('#upload_form')[0];"
  "var data = new FormData(form);"
  " $.ajax({"
  "url: '/update',"
  "type: 'POST',"
  "data: data,"
  "contentType: false,"
  "processData:false,"
  "xhr: function() {"
  "var xhr = new window.XMLHttpRequest();"
  "xhr.upload.addEventListener('progress', function(evt) {"
  "if (evt.lengthComputable) {"
  "var per = evt.loaded / evt.total;"
  "$('#prg').html('progress: ' + Math.round(per*100) + '%');"
  "}"
  "}, false);"
  "window.location.href ='/login';"
  "return xhr;"
  "},"
  "success:function(d, s) {"
  "console.log('successok !')" 
 "},"
 "error: function (a, b, c) {"
 "}"
 "});"
 "});"
 "</script>";

void setup()
{
//redundant parts have been removed intentionally
WiFi.begin(ssid, pass);
int  ct =0;


while (!MDNS.begin(host)) { //http://esp32.local //http://shariq-room.local
    Serial.println("Error setting up MDNS responder!");
    delay(1000);
    ct= ct+1000;

    if (ct>5000) {
      Serial.println("MDNS cannot be set");
      break;
      
    }
  }
  MDNS.addService("http", "tcp", 80);

server.on("/serverIndex", HTTP_GET, []() {
    server.sendHeader("Connection", "close");
    server.send(200, "text/html", serverIndex);
  });
    
        server.on("/update", HTTP_POST, []() {
        server.sendHeader("Connection", "close");
        server.send(200, "text/plain", (Update.hasError()) ? "FAIL" : "OK");
        ESP.restart();
      }, []() {
        Serial.println("OTA started ");
        HTTPUpload& upload = server.upload();
        if (upload.status == UPLOAD_FILE_START) {
          Serial.printf("Update: %s\n", upload.filename.c_str());
          if (!Update.begin(UPDATE_SIZE_UNKNOWN)) { //start with max available size
            Update.printError(Serial);
          }
        } else if (upload.status == UPLOAD_FILE_WRITE) {
          /* flashing firmware to ESP*/
          if (Update.write(upload.buf, upload.currentSize) != upload.currentSize) {
            Update.printError(Serial);
          }
        } else if (upload.status == UPLOAD_FILE_END) {
          if (Update.end(true)) { //true to set the size to the current progress
            Serial.printf("Update Success: %u\nRebooting...\n", upload.totalSize);
          } else {
            Update.printError(Serial);
          }
        }
      });
      server.begin();
}
void loop()
{
  server.handleClient(); 

   delay(400); 
}
  • Now, this will allow me to select and chose a bin file , if I open the localIP and process the OTA if "update" button is clicked.
  • But the same "act" will fail to trigger it when using either of python script of curl command. The curl command being copied from network section of chrome developer tools.

Python script is here.

##python
import requests
data = open("home_automate_inc_ota_v.0.2a.ino.esp32.bin", 'rb').read()
files = {'file': open("home_automate_inc_ota_v.0.2a.ino.esp32.bin", 'rb')}
#headers = {    "Content-Type":"application/binary",}
headers= { 'Accept': 'text/plain'}
print("started")
 upload = requests.post("http://192.168.0.7/update", data=data,headers=headers)
#tried below method too
# final_resp = requests.post("http://192.168.0.7/update", files=files, headers=headers)
print("done");

But I will get error on either of the post method like raise ConnectionError(err, request=request) requests.exceptions.ConnectionError: ('Connection aborted.', RemoteDisconnected('Remote end closed connection without response'))

The same when tried in curl

curl "http://192.168.0.50/update" ^
  -H "Connection: keep-alive" ^
  -H "Accept: */*" ^
  -H "X-Requested-With: XMLHttpRequest" ^
  -H "User-Agent: Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.128 Safari/537.36" ^
  -H "Content-Type: multipart/form-data; boundary=----#some hidden webkit data#" ^
  -H "Origin: http://192.168.0.50" ^
  -H "Referer: http://192.168.0.50/backup" ^
  -H "Accept-Language: en-US,en;q=0.9" ^
  -H "Cookie: ext_name=#some hidden cookie data#" ^
  --data-raw ^"------#some hidden webkit data#^

Content-Disposition: form-data; name=^\^"update^\^"; filename=^\^"home_automate_inc_ota_v.0.2a.ino.esp32.bin^\^"^

Content-Type: application/octet-stream^

^

^

------WebKitFormBoundaryPtpk4VGDdfwqS932--^

^" ^
  --compressed ^
  --insecure

Gives error as curl: (52) Empty reply from server

Hope I have explained the question well. Please let me know if you need any more clarifications.

Regards, Shariq

1 Answers

As Romkey suggested all I had to do was to remove the headers from my request. I found success with my python code , but curl command failed. Here is the script.

#!Python
import requests
files = {'file': open(r"<full path of the bin file including dir>home_automate_inc_ota_v.0.2a.ino.esp32.bin", 'rb')}

final_resp = requests.post("http://192.168.0.50/update", files=files)
Related