prampec / IotWebConf

ESP8266/ESP32 non-blocking WiFi/AP web configuration Arduino library
MIT License
523 stars 140 forks source link

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

Open societyofrobots opened 2 years ago

societyofrobots commented 2 years ago

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 <prampec+arduino@gmail.com>
 *
 * 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