Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

suggested code: add upload progress bar for firmware update #260

Open
societyofrobots opened this issue Jun 21, 2022 · 0 comments
Open

suggested code: add upload progress bar for firmware update #260

societyofrobots opened this issue Jun 21, 2022 · 0 comments

Comments

@societyofrobots
Copy link

I hacked together some code to improve the firmware update page with a progress bar, and better scaling for small devices.

Replace IotWebConfESP32HTTPUpdateServer.h with the below code (only added code is the bottom third).

/**
 * IotWebConfESP32HTTPUpdateServer.h -- IotWebConf is an ESP8266/ESP32
 *   non blocking WiFi/AP web configuration library for Arduino.
 *   https://github.com/prampec/IotWebConf
 *
 * Copyright (C) 2020 Balazs Kelemen <[email protected]>
 *
 * This software may be modified and distributed under the terms
 * of the MIT license.  See the LICENSE file for details.
 *
 * Notes on IotWebConfESP32HTTPUpdateServer:
 * ESP32 doesn't implement a HTTPUpdateServer. However it seams, that to code
 * from ESP8266 covers nearly all the same functionality.
 * So we need to implement our own HTTPUpdateServer for ESP32, and code is
 * reused from
 * https://github.com/esp8266/Arduino/blob/master/libraries/ESP8266HTTPUpdateServer/src/
 * version: 41de43a26381d7c9d29ce879dd5d7c027528371b
 */
#ifdef ESP32

#ifndef __HTTP_UPDATE_SERVER_H
#define __HTTP_UPDATE_SERVER_H

#include <WiFi.h>
#include <WiFiClient.h>
#include <WebServer.h>
#include <StreamString.h>
#include <Update.h>

#define emptyString F("")

class WebServer;

class HTTPUpdateServer
{
  public:
    HTTPUpdateServer(bool serial_debug=false)
    {
        _serial_output = serial_debug;
        _server = nullptr;
        _username = emptyString;
        _password = emptyString;
        _authenticated = false;
    }


    void setup(WebServer *server)
    {
      setup(server, emptyString, emptyString);
    }

    void setup(WebServer *server, const String& path)
    {
      setup(server, path, emptyString, emptyString);
    }

    void setup(WebServer *server, const String& username, const String& password)
    {
      setup(server, "/update", username, password);
    }

    void setup(WebServer *server, const String& path, const String& username, const String& password)
    {
      _server = server;
      _username = username;
      _password = password;

      // handler for the /update form page
      _server->on(path.c_str(), HTTP_GET, [&](){
          if(_username != emptyString && _password != emptyString && !_server->authenticate(_username.c_str(), _password.c_str()))
              return _server->requestAuthentication();
          _server->send_P(200, PSTR("text/html"), serverIndex);
      });

      // handler for the /update form POST (once file upload finishes)
      _server->on(path.c_str(), HTTP_POST, [&](){
          if(!_authenticated)
              return _server->requestAuthentication();
          if (Update.hasError()) {
              _server->send(200, F("text/html"), String(F("Update error: ")) + _updaterError);
          } else {
              _server->client().setNoDelay(true);
              _server->send_P(200, PSTR("text/html"), successResponse);
              delay(100);
              _server->client().stop();
              ESP.restart();
          }
      },[&](){
          // handler for the file upload, get's the sketch bytes, and writes
          // them through the Update object
          HTTPUpload& upload = _server->upload();

          if(upload.status == UPLOAD_FILE_START){
              _updaterError = String();
              if (_serial_output)
                  Serial.setDebugOutput(true);

              _authenticated = (_username == emptyString || _password == emptyString || _server->authenticate(_username.c_str(), _password.c_str()));
              if(!_authenticated){
                  if (_serial_output)
                      Serial.printf("Unauthenticated Update\n");
                  return;
              }

  ///        WiFiUDP::stopAll();
              if (_serial_output)
                  Serial.printf("Update: %s\n", upload.filename.c_str());
  ///        uint32_t maxSketchSpace = (ESP.getFreeSketchSpace() - 0x1000) & 0xFFFFF000;
  ///        if(!Update.begin(maxSketchSpace)){//start with max available size
              if(!Update.begin(UPDATE_SIZE_UNKNOWN)){//start with max available size
                  _setUpdaterError();
              }
          } else if(_authenticated && upload.status == UPLOAD_FILE_WRITE && !_updaterError.length()){
              if (_serial_output) Serial.printf(".");
              if(Update.write(upload.buf, upload.currentSize) != upload.currentSize){
                  _setUpdaterError();
              }
          } else if(_authenticated && upload.status == UPLOAD_FILE_END && !_updaterError.length()){
              if(Update.end(true)){ //true to set the size to the current progress
                  if (_serial_output) Serial.printf("Update Success: %u\nRebooting...\n", upload.totalSize);
              } else {
                  _setUpdaterError();
              }
              if (_serial_output) Serial.setDebugOutput(false);
          } else if(_authenticated && upload.status == UPLOAD_FILE_ABORTED){
              Update.end();
              if (_serial_output) Serial.println("Update was aborted");
          }
          delay(0);
      });
    }

    void updateCredentials(const String& username, const String& password)
    {
      _username = username;
      _password = password;
    }

  protected:
    void _setUpdaterError()
    {
        if (_serial_output) Update.printError(Serial);
        StreamString str;
        Update.printError(str);
        _updaterError = str.c_str();
    }

  private:
    bool _serial_output;
    WebServer *_server;
    String _username;
    String _password;
    bool _authenticated;
    String _updaterError;
    const char* serverIndex PROGMEM =
R"=====(<html><body>
<!--
<form method='POST' action='' enctype='multipart/form-data'>
                  <p style="font-size:30%;"><input type='file' name='update'></p>
                  <p style="font-size:30%;"><input type='submit' value='Update'></p>
               </form>

below script from https://codepen.io/PerfectIsShit/pen/zogMXP
-->

<p style="font-size:8vw;">Firmware Updater</p>
<form id="upload_form" enctype="multipart/form-data" method="post">
  <input type="file" name="update" id="update" onchange="uploadFile()" value="Update" style="width:100%;font-size:3vw;"><br>
  <progress id="progressBar" value="0" max="100" style="width:300px;"></progress>
  <h3 id="status"></h3>
  <p id="loaded_n_total"></p>
</form>
  
      <script id="rendered-js" >
function _(el) {
  return document.getElementById(el);
}

function uploadFile() {
  var file = _("update").files[0];
  // alert(file.name+" | "+file.size+" | "+file.type);
  var formdata = new FormData();
  formdata.append("update", file);
  var ajax = new XMLHttpRequest();
  ajax.upload.addEventListener("progress", progressHandler, false);
  ajax.addEventListener("load", completeHandler, false);
  ajax.addEventListener("error", errorHandler, false);
  ajax.addEventListener("abort", abortHandler, false);
  ajax.open("POST", "");  //use file_upload_parser.php from above url
  ajax.send(formdata);
}

function progressHandler(event) {
  _("loaded_n_total").innerHTML = "Uploaded " + event.loaded + " bytes of " + event.total;
  var percent = event.loaded / event.total * 100;
  _("progressBar").value = Math.round(percent);
  _("status").innerHTML = Math.round(percent) + "% uploaded... please wait";
}

function completeHandler(event) {
  _("status").innerHTML = event.target.responseText;
  _("progressBar").value = 0; //wil clear progress bar after successful upload
}

function errorHandler(event) {
  _("status").innerHTML = "Upload Failed";
}

function abortHandler(event) {
  _("status").innerHTML = "Upload Aborted";
}
//# sourceURL=pen.js
    </script>

         </body></html>)=====";
  const char* successResponse PROGMEM =
"<META http-equiv=\"refresh\" content=\"15;URL=/\">Update Success! Rebooting...\n";
};

/////////////////////////////////////////////////////////////////////////////////

#endif

#endif

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant