AutoConnectUpdate-rc2

Abolished WebSocket in-process notification for update progress notification. Its method consumes too much memory on the ESP8266. Replace with Ajax.
pull/123/head
Hieromon Ikasamo 6 years ago
parent f0dde87064
commit 40cb4cf8e0
  1. 35
      src/AutoConnectDefs.h
  2. 283
      src/AutoConnectUpdate.cpp
  3. 66
      src/AutoConnectUpdate.h
  4. 23
      src/AutoConnectUpdatePage.h

@ -90,8 +90,9 @@
#define AUTOCONNECT_URI_SUCCESS AUTOCONNECT_URI "/success" #define AUTOCONNECT_URI_SUCCESS AUTOCONNECT_URI "/success"
#define AUTOCONNECT_URI_FAIL AUTOCONNECT_URI "/fail" #define AUTOCONNECT_URI_FAIL AUTOCONNECT_URI "/fail"
#define AUTOCONNECT_URI_UPDATE AUTOCONNECT_URI "/update" #define AUTOCONNECT_URI_UPDATE AUTOCONNECT_URI "/update"
#define AUTOCONNECT_URI_UPDATE_ACT AUTOCONNECT_URI "/update_act" #define AUTOCONNECT_URI_UPDATE_ACT AUTOCONNECT_URI "/update_act"
#define AUTOCONNECT_URI_UPDATE_RESULT AUTOCONNECT_URI "/update_result" #define AUTOCONNECT_URI_UPDATE_PROGRESS AUTOCONNECT_URI "/update_progress"
#define AUTOCONNECT_URI_UPDATE_RESULT AUTOCONNECT_URI "/update_result"
// Time-out limitation when AutoConnect::begin [ms] // Time-out limitation when AutoConnect::begin [ms]
#ifndef AUTOCONNECT_TIMEOUT #ifndef AUTOCONNECT_TIMEOUT
@ -138,7 +139,7 @@
#define AUTOCONNECT_SSIDPAGEUNIT_LINES 5 #define AUTOCONNECT_SSIDPAGEUNIT_LINES 5
#endif // !AUTOCONNECT_SSIDPAGEUNIT_LINES #endif // !AUTOCONNECT_SSIDPAGEUNIT_LINES
// SPI transfer speed for SD // SPI transfer speed for SD [Hz]
#ifndef AUTOCONNECT_SD_SPEED #ifndef AUTOCONNECT_SD_SPEED
#define AUTOCONNECT_SD_SPEED 4000000 #define AUTOCONNECT_SD_SPEED 4000000
#endif // !AUTOCONNECT_SD_SPEED #endif // !AUTOCONNECT_SD_SPEED
@ -154,20 +155,39 @@
#define AUTOCONNECT_JSONPSRAM_SIZE (16* 1024) #define AUTOCONNECT_JSONPSRAM_SIZE (16* 1024)
#endif // !AUTOCONNECT_JSONPSRAM_SIZE #endif // !AUTOCONNECT_JSONPSRAM_SIZE
// Available HTTP port number for the update // Available HTTP port number for the update [ms]
#ifndef AUTOCONNECT_UPDATE_PORT #ifndef AUTOCONNECT_UPDATE_PORT
#define AUTOCONNECT_UPDATE_PORT 8000 #define AUTOCONNECT_UPDATE_PORT 8000
#endif // !AUTOCONNECT_UPDATE_PORT #endif // !AUTOCONNECT_UPDATE_PORT
// HTTP client timeout limitation for the update // HTTP client timeout limitation for the update [ms]
#ifndef AUTOCONNECT_UPDATE_TIMEOUT #ifndef AUTOCONNECT_UPDATE_TIMEOUT
#define AUTOCONNECT_UPDATE_TIMEOUT 8000 #define AUTOCONNECT_UPDATE_TIMEOUT 8000
#endif // !AUTOCONNECT_UPDATE_TIMEOUT #endif // !AUTOCONNECT_UPDATE_TIMEOUT
// Maximum wait time until transitioning AutoConnectUpdate dialog page [ms]
#ifndef AUTOCONNECT_UPDATE_DURATION #ifndef AUTOCONNECT_UPDATE_DURATION
#define AUTOCONNECT_UPDATE_DURATION 180000 #define AUTOCONNECT_UPDATE_DURATION 180000
#endif // !AUTOCONNECT_UPDATE_DURATION #endif // !AUTOCONNECT_UPDATE_DURATION
// Interval time of progress status periodical inquiry [ms]
#ifndef AUTOCONNECT_UPDATE_INTERVAL
#define AUTOCONNECT_UPDATE_INTERVAL 400
#endif // !AUTOCONNECT_UPDATE_INTERVAL
// Wait timer for rebooting after updated
#ifndef AUTOCONNECT_UPDATE_WAITFORREBOOT
#define AUTOCONNECT_UPDATE_WAITFORREBOOT 9000
#endif // !AUTOCONNECT_UPDATE_WAITFORREBOOT
// A signal value that the board dependent LED turns on.
// As a typical example, the ON signal of built-in LED such as the
// NodeMCU is LOW and the HIGH for the NodeMCU-32S as another example.
#ifndef AUTOCONNECT_UPDATE_LEDON
// #define AUTOCONNECT_UPDATE_LEDON HIGH
#define AUTOCONNECT_UPDATE_LEDON LOW
#endif // !AUTOCONNECT_UPDATE_LEDON
// URIs of the behaviors owned by the update server // URIs of the behaviors owned by the update server
#ifndef AUTOCONNECT_UPDATE_CATALOG #ifndef AUTOCONNECT_UPDATE_CATALOG
#define AUTOCONNECT_UPDATE_CATALOG "/_catalog" #define AUTOCONNECT_UPDATE_CATALOG "/_catalog"
@ -179,11 +199,6 @@
#define AUTOCONNECT_UPDATE_CATALOG_JSONBUFFER_SIZE 256 #define AUTOCONNECT_UPDATE_CATALOG_JSONBUFFER_SIZE 256
#endif // !AUTOCONNECT_UPDATE_CATALOG_JSONBUFFER_SIZE #endif // !AUTOCONNECT_UPDATE_CATALOG_JSONBUFFER_SIZE
// Default WebSocket port for the update progress measur
#ifndef AUTOCONNECT_WEBSOCKETPORT
#define AUTOCONNECT_WEBSOCKETPORT 81
#endif // !AUTOCONNECT_WEBSOCKETPORT
// Explicitly avoiding unused warning with token handler of PageBuilder // Explicitly avoiding unused warning with token handler of PageBuilder
#define AC_UNUSED(expr) do { (void)(expr); } while (0) #define AC_UNUSED(expr) do { (void)(expr); } while (0)

@ -30,7 +30,7 @@
* queries from the AutoConnectUpdateAct class. The catalog script accepts * queries from the AutoConnectUpdateAct class. The catalog script accepts
* the queries such as '/catalog?op=list&path='. * the queries such as '/catalog?op=list&path='.
* - op: * - op:
* An op parameter speicies the query operation. In the current * An op parameter specifies the query operation. In the current
* version, available query operation is a list only. * version, available query operation is a list only.
* - list: * - list:
* The query operation list responds with a list of available sketch * The query operation list responds with a list of available sketch
@ -90,15 +90,9 @@ AC_HAS_FUNC(onProgress);
template<typename T> template<typename T>
typename std::enable_if<AutoConnectUtil::has_func_onProgress<T>::value, AutoConnectUpdateAct::AC_UPDATEDIALOG_t>::type onProgress(T& updater, UpdateVariedClass::THandlerFunction_Progress fn) { typename std::enable_if<AutoConnectUtil::has_func_onProgress<T>::value, AutoConnectUpdateAct::AC_UPDATEDIALOG_t>::type onProgress(T& updater, UpdateVariedClass::THandlerFunction_Progress fn) {
#if defined(ARDUINO_ARCH_ESP32) || (defined(ARDUINO_ARCH_ESP8266) && (WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP8266_ASYNC))
updater.onProgress(fn); updater.onProgress(fn);
AC_DBG("Updater keeps callback\n"); AC_DBG("Updater keeps callback\n");
return AutoConnectUpdateAct::UPDATEDIALOG_METER; return AutoConnectUpdateAct::UPDATEDIALOG_METER;
#else
(void)(updater);
(void)(fn);
return AutoConnectUpdateAct::UPDATEDIALOG_LOADER;
#endif
} }
template<typename T> template<typename T>
@ -109,15 +103,23 @@ typename std::enable_if<!AutoConnectUtil::has_func_onProgress<T>::value, AutoCon
} }
} }
/**
* Definitions of notification commands to synchronize update processing
* with the Web client.
*/
#define UPDATE_NOTIFY_START "#s"
#define UPDATE_NOTIFY_PROGRESS "#p"
#define UPDATE_NOTIFY_END "#e"
#define UPDATE_NOTIFY_REBOOT "#r"
/** /**
* A destructor. Release the update processing dialogue page generated * A destructor. Release the update processing dialogue page generated
* as AutoConnectAux. * as AutoConnectAux.
*/ */
AutoConnectUpdateAct::~AutoConnectUpdateAct() { AutoConnectUpdateAct::~AutoConnectUpdateAct() {
_catalog.reset(nullptr); _auxCatalog.reset(nullptr);
_progress.reset(nullptr); _auxProgress.reset(nullptr);
_result.reset(nullptr); _auxResult.reset(nullptr);
_ws.reset(nullptr);
} }
/** /**
@ -130,44 +132,35 @@ AutoConnectUpdateAct::~AutoConnectUpdateAct() {
void AutoConnectUpdateAct::attach(AutoConnect& portal) { void AutoConnectUpdateAct::attach(AutoConnect& portal) {
AutoConnectAux* updatePage; AutoConnectAux* updatePage;
updatePage = new AutoConnectAux(String(FPSTR(_auxCatalog.uri)), String(FPSTR(_auxCatalog.title)), _auxCatalog.menu); updatePage = new AutoConnectAux(String(FPSTR(_pageCatalog.uri)), String(FPSTR(_pageCatalog.title)), _pageCatalog.menu);
_buildAux(updatePage, &_auxCatalog, lengthOf(_elmCatalog)); _buildAux(updatePage, &_pageCatalog, lengthOf(_elmCatalog));
_catalog.reset(updatePage); _auxCatalog.reset(updatePage);
updatePage = new AutoConnectAux(String(FPSTR(_auxProgress.uri)), String(FPSTR(_auxProgress.title)), _auxProgress.menu); updatePage = new AutoConnectAux(String(FPSTR(_pageProgress.uri)), String(FPSTR(_pageProgress.title)), _pageProgress.menu);
_buildAux(updatePage, &_auxProgress, lengthOf(_elmProgress)); _buildAux(updatePage, &_pageProgress, lengthOf(_elmProgress));
_progress.reset(updatePage); _auxProgress.reset(updatePage);
updatePage = new AutoConnectAux(String(FPSTR(_auxResult.uri)), String(FPSTR(_auxResult.title)), _auxResult.menu); updatePage = new AutoConnectAux(String(FPSTR(_pageResult.uri)), String(FPSTR(_pageResult.title)), _pageResult.menu);
_buildAux(updatePage, &_auxResult, lengthOf(_elmResult)); _buildAux(updatePage, &_pageResult, lengthOf(_elmResult));
_result.reset(updatePage); _auxResult.reset(updatePage);
_catalog->on(std::bind(&AutoConnectUpdateAct::_onCatalog, this, std::placeholders::_1, std::placeholders::_2), AC_EXIT_AHEAD); _auxCatalog->on(std::bind(&AutoConnectUpdateAct::_onCatalog, this, std::placeholders::_1, std::placeholders::_2), AC_EXIT_AHEAD);
_progress->on(std::bind(&AutoConnectUpdateAct::_onUpdate, this, std::placeholders::_1, std::placeholders::_2), AC_EXIT_AHEAD); _auxProgress->on(std::bind(&AutoConnectUpdateAct::_onUpdate, this, std::placeholders::_1, std::placeholders::_2), AC_EXIT_AHEAD);
_result->on(std::bind(&AutoConnectUpdateAct::_onResult, this, std::placeholders::_1, std::placeholders::_2), AC_EXIT_AHEAD); _auxResult->on(std::bind(&AutoConnectUpdateAct::_onResult, this, std::placeholders::_1, std::placeholders::_2), AC_EXIT_AHEAD);
portal.join(*_catalog.get()); portal.join(*_auxCatalog.get());
portal.join(*_progress.get()); portal.join(*_auxProgress.get());
portal.join(*_result.get()); portal.join(*_auxResult.get());
_status = UPDATE_IDLE;
// Attach this to the AutoConnectUpdateAct
portal._update.reset(this);
AC_DBG("AutoConnectUpdate attached\n");
if (WiFi.status() == WL_CONNECTED)
enable();
// Register the callback to inform the update progress // Register the callback to inform the update progress
_dialog = AutoConnectUtil::onProgress<UpdateVariedClass>(Update, std::bind(&AutoConnectUpdateAct::_inProgress, this, std::placeholders::_1, std::placeholders::_2)); _dialog = AutoConnectUtil::onProgress<UpdateVariedClass>(Update, std::bind(&AutoConnectUpdateAct::_inProgress, this, std::placeholders::_1, std::placeholders::_2));
// Adjust the client dialog pattern according to the callback validity // Adjust the client dialog pattern according to the callback validity
// of the UpdateClass. // of the UpdateClass.
AutoConnectElement* loader = _progress->getElement(String(F("loader"))); AutoConnectElement* loader = _auxProgress->getElement(String(F("loader")));
AutoConnectElement* enable_loader = _progress->getElement(String(F("enable_loader"))); AutoConnectElement* progress_meter = _auxProgress->getElement(String(F("progress_meter")));
AutoConnectElement* progress_meter = _progress->getElement(String(F("progress_meter"))); AutoConnectElement* progress_loader = _auxProgress->getElement(String(F("progress_loader")));
AutoConnectElement* inprogress_meter = _progress->getElement(String(F("inprogress_meter"))); AutoConnectElement* enable_loader = _auxProgress->getElement(String(F("enable_loader")));
AutoConnectElement* progress_loader = _progress->getElement(String(F("progress_loader"))); AutoConnectElement* inprogress_meter = _auxProgress->getElement(String(F("inprogress_meter")));
AutoConnectElement* inprogress_loader = _progress->getElement(String(F("inprogress_loader")));
switch (_dialog) { switch (_dialog) {
case UPDATEDIALOG_LOADER: case UPDATEDIALOG_LOADER:
progress_meter->enable =false; progress_meter->enable =false;
@ -175,11 +168,23 @@ void AutoConnectUpdateAct::attach(AutoConnect& portal) {
break; break;
case UPDATEDIALOG_METER: case UPDATEDIALOG_METER:
loader->enable = false; loader->enable = false;
enable_loader->enable =false;
progress_loader->enable =false; progress_loader->enable =false;
inprogress_loader->enable = false; enable_loader->enable =false;
break; break;
} }
// Attach this to the AutoConnectUpdateAct
portal._update.reset(this);
AC_DBG("AutoConnectUpdate attached\n");
if (WiFi.status() == WL_CONNECTED)
enable();
// Attach the update progress monitoring handler
_webServer = &(portal.host());
_webServer->on(String(F(AUTOCONNECT_URI_UPDATE_PROGRESS)), HTTP_ANY, std::bind(&AutoConnectUpdateAct::_progress, this));
// Reset the update progress status
_status = UPDATE_IDLE;
} }
/** /**
@ -188,8 +193,8 @@ void AutoConnectUpdateAct::attach(AutoConnect& portal) {
*/ */
void AutoConnectUpdateAct::disable(const bool activate) { void AutoConnectUpdateAct::disable(const bool activate) {
_enable = activate; _enable = activate;
if (_catalog) { if (_auxCatalog) {
_catalog->menu(false); _auxCatalog->menu(false);
AC_DBG("AutoConnectUpdate disabled\n"); AC_DBG("AutoConnectUpdate disabled\n");
} }
} }
@ -201,12 +206,19 @@ void AutoConnectUpdateAct::disable(const bool activate) {
void AutoConnectUpdateAct::enable(void) { void AutoConnectUpdateAct::enable(void) {
_enable = true; _enable = true;
_status = UPDATE_IDLE; _status = UPDATE_IDLE;
if (_catalog) { if (_auxCatalog) {
_catalog->menu(WiFi.status() == WL_CONNECTED); _auxCatalog->menu(WiFi.status() == WL_CONNECTED);
AC_DBG("AutoConnectUpdate enabled\n"); AC_DBG("AutoConnectUpdate enabled\n");
} }
} }
/**
* An entry point of the process loop as AutoConnect::handleClient.
* The handleClient function of the AutoConnect that later accompanied
* the AutoConnectUpdate class will invoke this entry.
* This entry point will be called from the process loop of handleClient
* function only if the class is associated with the AutoConnect class.
*/
void AutoConnectUpdateAct::handleUpdate(void) { void AutoConnectUpdateAct::handleUpdate(void) {
// Activate the update menu conditional with WiFi connected. // Activate the update menu conditional with WiFi connected.
if (!isEnable() && _enable) { if (!isEnable() && _enable) {
@ -220,21 +232,8 @@ void AutoConnectUpdateAct::handleUpdate(void) {
// execute it accordingly. It is only this process point that // execute it accordingly. It is only this process point that
// requests update processing. // requests update processing.
if (_status == UPDATE_START) { if (_status == UPDATE_START) {
AC_DBG("Waiting for WS connection\n"); _status = UPDATE_PROGRESS;
unsigned long tm = millis(); update();
while (!_wsConnected) {
if (millis() - tm > AUTOCONNECT_TIMEOUT) {
AC_DBG("WebSocket client connection timeout, update ignored\n");
break;
}
_ws->loop(); // Crawl the connection request.
yield();
}
// Launch the update
if (_wsConnected)
update();
else
_status = UPDATE_IDLE;
} }
else if (_status == UPDATE_RESET) { else if (_status == UPDATE_RESET) {
AC_DBG("Restart on %s updated...\n", _binName.c_str()); AC_DBG("Restart on %s updated...\n", _binName.c_str());
@ -257,13 +256,9 @@ AC_UPDATESTATUS_t AutoConnectUpdateAct::update(void) {
// Start update // Start update
String uriBin = uri + '/' + _binName; String uriBin = uri + '/' + _binName;
if (_binName.length()) { if (_binName.length()) {
AC_DBG("%s:%d/%s update in progress...", host.c_str(), port, uriBin.c_str());
#if defined(ARDUINO_ARCH_ESP8266)
t_httpUpdate_return ret = HTTPUpdateClass::update(host, port, uriBin);
#elif defined(ARDUINO_ARCH_ESP32)
WiFiClient wifiClient; WiFiClient wifiClient;
AC_DBG("%s:%d/%s update in progress...", host.c_str(), port, uriBin.c_str());
t_httpUpdate_return ret = HTTPUpdateClass::update(wifiClient, host, port, uriBin); t_httpUpdate_return ret = HTTPUpdateClass::update(wifiClient, host, port, uriBin);
#endif
switch (ret) { switch (ret) {
case HTTP_UPDATE_FAILED: case HTTP_UPDATE_FAILED:
_status = UPDATE_FAIL; _status = UPDATE_FAIL;
@ -279,8 +274,6 @@ AC_UPDATESTATUS_t AutoConnectUpdateAct::update(void) {
AC_DBG_DUMB(" completed\n"); AC_DBG_DUMB(" completed\n");
break; break;
} }
// Request the client to close the WebSocket.
_ws->sendTXT(_wsClient, "#e");
} }
else { else {
AC_DBG("An update has not specified"); AC_DBG("An update has not specified");
@ -335,6 +328,20 @@ void AutoConnectUpdateAct::_buildAux(AutoConnectAux* aux, const AutoConnectUpdat
} }
} }
/**
* An update callback function in HTTPUpdate::update.
* This callback handler acts as an HTTPUpdate::update callback and
* sends the updated amount over the web socket to advance the progress
* of the progress meter displayed in the browser.
* @param amount Already transferred size.
* @param size Total size of the binary to update.
*/
void AutoConnectUpdateAct::_inProgress(size_t amount, size_t size) {
_amount = amount;
_binSize = size;
_webServer->handleClient();
}
/** /**
* AUTOCONNECT_URI_UPDATE page handler. * AUTOCONNECT_URI_UPDATE page handler.
* It queries the update server for cataloged sketch binary and * It queries the update server for cataloged sketch binary and
@ -347,7 +354,8 @@ void AutoConnectUpdateAct::_buildAux(AutoConnectAux* aux, const AutoConnectUpdat
*/ */
String AutoConnectUpdateAct::_onCatalog(AutoConnectAux& catalog, PageArgument& args) { String AutoConnectUpdateAct::_onCatalog(AutoConnectAux& catalog, PageArgument& args) {
AC_UNUSED(args); AC_UNUSED(args);
HTTPClient httpClient; WiFiClient wifiClient;
HTTPClient httpClient;
// Reallocate available firmwares list. // Reallocate available firmwares list.
_binName = String(""); _binName = String("");
@ -364,11 +372,11 @@ String AutoConnectUpdateAct::_onCatalog(AutoConnectAux& catalog, PageArgument& a
// Throw a query to the update server and parse the response JSON // Throw a query to the update server and parse the response JSON
// document. After that, display the bin type file name contained in // document. After that, display the bin type file name contained in
// its JSON document as available updaters to the page. // its JSON document as available updaters to the page.
if (httpClient.begin(host, port, qs)) { if (httpClient.begin(wifiClient, host, port, qs)) {
int responseCode = httpClient.GET(); int responseCode = httpClient.GET();
if (responseCode == HTTP_CODE_OK) { if (responseCode == HTTP_CODE_OK) {
JsonVariant jb; // JsonVariant jb;
bool parse; bool parse;
char beginOfList[] = "["; char beginOfList[] = "[";
char endOfEntry[] = ","; char endOfEntry[] = ",";
@ -437,6 +445,7 @@ String AutoConnectUpdateAct::_onCatalog(AutoConnectAux& catalog, PageArgument& a
AC_DBG("%s\n", caption.value.c_str()); AC_DBG("%s\n", caption.value.c_str());
} }
_status = UPDATE_IDLE;
return String(""); return String("");
} }
@ -449,26 +458,13 @@ String AutoConnectUpdateAct::_onCatalog(AutoConnectAux& catalog, PageArgument& a
*/ */
String AutoConnectUpdateAct::_onUpdate(AutoConnectAux& progress, PageArgument& args) { String AutoConnectUpdateAct::_onUpdate(AutoConnectAux& progress, PageArgument& args) {
AC_UNUSED(args); AC_UNUSED(args);
// launch the WebSocket server
_ws.reset(new WebSocketsServer(AUTOCONNECT_WEBSOCKETPORT));
if (_ws) {
_ws->begin();
_ws->onEvent(std::bind(&AutoConnectUpdateAct::_wsEvent, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4));
}
else
AC_DBG("WebSocketsServer allocation failed\n");
// Constructs the dialog page. // Constructs the dialog page.
AutoConnectElement* binName = progress.getElement(String(F("binname"))); AutoConnectElement* binName = progress.getElement(String(F("binname")));
_binName = _catalog->getElement<AutoConnectRadio>(String(F("firmwares"))).value(); _binName = _auxCatalog->getElement<AutoConnectRadio>(String(F("firmwares"))).value();
binName->value = _binName; binName->value = _binName;
AutoConnectElement* url = progress.getElement(String("url")); AutoConnectElement* url = progress.getElement(String("url"));
url->value = host + ':' + port; url->value = host + ':' + port;
AutoConnectElement* wsurl = progress.getElement(String(F("wsurl")));
wsurl->value = "ws://" + WiFi.localIP().toString() + ':' + AUTOCONNECT_WEBSOCKETPORT;
AC_DBG("Cast WS %s\n", wsurl->value.c_str());
_wsConnected = false;
_status = UPDATE_START;
return String(""); return String("");
} }
@ -485,19 +481,6 @@ String AutoConnectUpdateAct::_onResult(AutoConnectAux& result, PageArgument& arg
String resColor; String resColor;
bool restart = false; bool restart = false;
if (_ws) {
_ws->loop();
while (_wsConnected) {
IPAddress ipAny;
if (_ws->remoteIP(_wsClient) == ipAny)
_wsConnected = false;
else
_ws->close();
yield();
}
_ws.reset(nullptr);
}
switch (_status) { switch (_status) {
case UPDATE_SUCCESS: case UPDATE_SUCCESS:
resForm = String(F(" sucessfully updated. restart...")); resForm = String(F(" sucessfully updated. restart..."));
@ -518,48 +501,84 @@ String AutoConnectUpdateAct::_onResult(AutoConnectAux& result, PageArgument& arg
resultElm.value = _binName + resForm; resultElm.value = _binName + resForm;
resultElm.style = String(F("font-size:120%;color:")) + resColor; resultElm.style = String(F("font-size:120%;color:")) + resColor;
result.getElement<AutoConnectElement>(String(F("restart"))).enable = restart; result.getElement<AutoConnectElement>(String(F("restart"))).enable = restart;
if (restart)
_status = UPDATE_RESET;
return String(""); return String("");
} }
/** /**
* An update callback function in HTTPUpdate::update. * A handler for notifying the client of the progress of update processing.
* This callback handler acts as an HTTPUpdate::update callback and * This handler specifies the URI behavior defined as THndlerFunc of
* sends the updated amount over the web socket to advance the progress * ESP8266 WebServer (WebServer for ESP32).
* of the progress meter displayed in the browser. * Usually, that URI is /_ac/update_progress and will return the
* @param amount Already transferred size. * processed amount of update processing to the client.
* @param size Total size of the binary to update.
*/ */
void AutoConnectUpdateAct::_inProgress(size_t amount, size_t size) { void AutoConnectUpdateAct::_progress(void) {
if (_ws) { String reqOperation = "op";
_amount = amount; String reqOperand;
_binSize = size; String payload = String("");
String payload = "#p," + String(_amount) + ':' + String(_binSize); int httpCode;
_ws->sendTXT(_wsClient, payload); static const char reply_msg_op[] PROGMEM = "invalid operation";
} static const char reply_msg_seq[] PROGMEM = "invalid sequence";
} static const char reply_msg_inv[] PROGMEM = "invalid request";
static const char* content_type = "text/plain";
switch (_webServer->method()) {
case HTTP_POST:
if (_webServer->hasArg(reqOperation)) {
reqOperand = _webServer->arg(reqOperation);
if (reqOperand == String(UPDATE_NOTIFY_START)) {
if (_status == UPDATE_IDLE) {
httpCode = 200;
_status = UPDATE_START;
}
else {
payload = String(FPSTR(reply_msg_seq));
httpCode = 500;
}
}
else if (reqOperand == String(UPDATE_NOTIFY_REBOOT)) {
if (_status == UPDATE_SUCCESS) {
_status = UPDATE_RESET;
httpCode = 200;
}
else {
payload = String(FPSTR(reply_msg_seq));
httpCode = 500;
}
}
}
else {
payload = String(FPSTR(reply_msg_op));
httpCode = 500;
}
break;
/** case HTTP_GET:
* An event handler of WebSocket which should be opened to monitor switch (_status) {
* update progress. AutoConnectUpdate will send #s to start the update case UPDATE_PROGRESS:
* progress notification to the client when the WebSocket client payload = String(UPDATE_NOTIFY_PROGRESS) + ',' + String(_amount) + ':' + String(_binSize);
* connected. This has the meaning of ACK. httpCode = 200;
* @param client WS client number of WebSocketsServer break;
* @param event Event id case UPDATE_IDLE:
* @param payload Payload content case UPDATE_SUCCESS:
* @param length payload length case UPDATE_NOAVAIL:
*/ case UPDATE_FAIL:
void AutoConnectUpdateAct::_wsEvent(uint8_t client, WStype_t event, uint8_t* payload, size_t length) { payload = String(UPDATE_NOTIFY_END);
AC_DBG("WS client:%d event(%d)\n", client, event); httpCode = 200;
if (event == WStype_CONNECTED) { break;
_wsConnected = true; default:
_wsClient = client; payload = String(FPSTR(reply_msg_seq));
_ws->sendTXT(_wsClient, "#s"); httpCode = 500;
}
break;
default:
httpCode = 500;
payload = String(FPSTR(reply_msg_inv));
} }
else if (event == WStype_DISCONNECTED)
_wsConnected = false; _webServer->send(httpCode, content_type, payload);
} }
#endif // !AUTOCONNECT_USE_UPDATE #endif // !AUTOCONNECT_USE_UPDATE

@ -45,7 +45,7 @@ using HTTPUpdateClass = ESP8266HTTPUpdate;
#include <HTTPUpdate.h> #include <HTTPUpdate.h>
using HTTPUpdateClass = HTTPUpdate; using HTTPUpdateClass = HTTPUpdate;
#endif #endif
#include <WebSocketsServer.h> // #include <WebSocketsServer.h>
// Quote the true AutoConnectUpdate class according to AUTOCONNECT_USE_UPDATE. // Quote the true AutoConnectUpdate class according to AUTOCONNECT_USE_UPDATE.
#define AutoConnectUpdate AutoConnectUpdateAct #define AutoConnectUpdate AutoConnectUpdateAct
#else // !AUTOCONNECT_USE_UPDATE! #else // !AUTOCONNECT_USE_UPDATE!
@ -54,10 +54,10 @@ using HTTPUpdateClass = HTTPUpdate;
#include "AutoConnect.h" #include "AutoConnect.h"
// Support LED flashing only the board with built-in LED. // Support LED flashing only the board with built-in LED.
#ifdef LED_BUILTIN #if defined(BUILTIN_LED) || defined(LED_BUILTIN)
#define UPDATE_SETLED(s) do {setLedPin(LED_BUILTIN, s);} while(0) #define AC_SETLED(s) do {setLedPin(LED_BUILTIN, s);} while(0)
#else #else
#define UPDATE_SETLED(s) do {} while(0) #define AC_SETLED(s) do {} while(0)
#endif #endif
// Indicate an update process loop // Indicate an update process loop
@ -65,6 +65,7 @@ typedef enum AC_UPDATESTATUS {
UPDATE_RESET, /**< Update process ended, need to reset */ UPDATE_RESET, /**< Update process ended, need to reset */
UPDATE_IDLE, /**< Update process has not started */ UPDATE_IDLE, /**< Update process has not started */
UPDATE_START, /**< Update process has been started */ UPDATE_START, /**< Update process has been started */
UPDATE_PROGRESS, /**< Update process has been started */
UPDATE_SUCCESS, /**< Update successfully completed */ UPDATE_SUCCESS, /**< Update successfully completed */
UPDATE_NOAVAIL, /**< No available update */ UPDATE_NOAVAIL, /**< No available update */
UPDATE_FAIL /**< Update fails */ UPDATE_FAIL /**< Update fails */
@ -72,18 +73,20 @@ typedef enum AC_UPDATESTATUS {
class AutoConnectUpdateVoid { class AutoConnectUpdateVoid {
public: public:
explicit AutoConnectUpdateVoid(const String& host = String(""), const uint16_t port = AUTOCONNECT_UPDATE_PORT, const String& uri = String("."), const int timeout = AUTOCONNECT_UPDATE_TIMEOUT) { explicit AutoConnectUpdateVoid(const String& host = String(""), const uint16_t port = AUTOCONNECT_UPDATE_PORT, const String& uri = String("."), const int timeout = AUTOCONNECT_UPDATE_TIMEOUT, const uint8_t ledOn = LOW) {
AC_UNUSED(host); AC_UNUSED(host);
AC_UNUSED(port); AC_UNUSED(port);
AC_UNUSED(uri); AC_UNUSED(uri);
AC_UNUSED(timeout); AC_UNUSED(timeout);
AC_UNUSED(ledOn);
} }
AutoConnectUpdateVoid(AutoConnect& portal, const String& host = String(""), const uint16_t port = AUTOCONNECT_UPDATE_PORT, const String& uri = String("."), const int timeout = AUTOCONNECT_UPDATE_TIMEOUT) { AutoConnectUpdateVoid(AutoConnect& portal, const String& host = String(""), const uint16_t port = AUTOCONNECT_UPDATE_PORT, const String& uri = String("."), const int timeout = AUTOCONNECT_UPDATE_TIMEOUT, const uint8_t ledOn = LOW) {
AC_UNUSED(portal); AC_UNUSED(portal);
AC_UNUSED(host); AC_UNUSED(host);
AC_UNUSED(port); AC_UNUSED(port);
AC_UNUSED(uri); AC_UNUSED(uri);
AC_UNUSED(timeout); AC_UNUSED(timeout);
AC_UNUSED(ledOn);
} }
virtual ~AutoConnectUpdateVoid() {} virtual ~AutoConnectUpdateVoid() {}
virtual void attach(AutoConnect& portal) { AC_UNUSED(portal); } virtual void attach(AutoConnect& portal) { AC_UNUSED(portal); }
@ -99,14 +102,14 @@ class AutoConnectUpdateVoid {
class AutoConnectUpdateAct : public AutoConnectUpdateVoid, public HTTPUpdateClass { class AutoConnectUpdateAct : public AutoConnectUpdateVoid, public HTTPUpdateClass {
public: public:
explicit AutoConnectUpdateAct(const String& host = String(""), const uint16_t port = AUTOCONNECT_UPDATE_PORT, const String& uri = String("."), const int timeout = AUTOCONNECT_UPDATE_TIMEOUT) explicit AutoConnectUpdateAct(const String& host = String(""), const uint16_t port = AUTOCONNECT_UPDATE_PORT, const String& uri = String("."), const int timeout = AUTOCONNECT_UPDATE_TIMEOUT, const uint8_t ledOn = AUTOCONNECT_UPDATE_LEDON)
: HTTPUpdateClass(timeout), host(host), port(port), uri(uri), _wsClient(0), _wsConnected(false), _amount(0), _binSize(0), _enable(false), _dialog(UPDATEDIALOG_LOADER), _status(UPDATE_IDLE), _binName(String()) { : HTTPUpdateClass(timeout), host(host), port(port), uri(uri), _amount(0), _binSize(0), _enable(false), _dialog(UPDATEDIALOG_LOADER), _status(UPDATE_IDLE), _binName(String()), _webServer(nullptr) {
UPDATE_SETLED(LOW); /**< LED blinking during the update that is the default. */ AC_SETLED(ledOn); /**< LED blinking during the update that is the default. */
rebootOnUpdate(false); /**< Default reboot mode */ rebootOnUpdate(false); /**< Default reboot mode */
} }
AutoConnectUpdateAct(AutoConnect& portal, const String& host = String(""), const uint16_t port = AUTOCONNECT_UPDATE_PORT, const String& uri = String("."), const int timeout = AUTOCONNECT_UPDATE_TIMEOUT) AutoConnectUpdateAct(AutoConnect& portal, const String& host = String(""), const uint16_t port = AUTOCONNECT_UPDATE_PORT, const String& uri = String("."), const int timeout = AUTOCONNECT_UPDATE_TIMEOUT, const uint8_t ledOn = AUTOCONNECT_UPDATE_LEDON)
: HTTPUpdateClass(timeout), host(host), port(port), uri(uri), _wsClient(0), _wsConnected(false), _amount(0), _binSize(0), _enable(false), _dialog(UPDATEDIALOG_LOADER), _status(UPDATE_IDLE), _binName(String()) { : HTTPUpdateClass(timeout), host(host), port(port), uri(uri), _amount(0), _binSize(0), _enable(false), _dialog(UPDATEDIALOG_LOADER), _status(UPDATE_IDLE), _binName(String()), _webServer(nullptr) {
UPDATE_SETLED(LOW); AC_SETLED(ledOn);
rebootOnUpdate(false); rebootOnUpdate(false);
attach(portal); attach(portal);
} }
@ -115,7 +118,7 @@ class AutoConnectUpdateAct : public AutoConnectUpdateVoid, public HTTPUpdateClas
void enable(void) override; /**< Enable the AutoConnectUpdateAct */ void enable(void) override; /**< Enable the AutoConnectUpdateAct */
void disable(const bool activte = false) override; /**< Disable the AutoConnectUpdateAct */ void disable(const bool activte = false) override; /**< Disable the AutoConnectUpdateAct */
void handleUpdate(void) override; /**< Behaves the update process */ void handleUpdate(void) override; /**< Behaves the update process */
bool isEnable(void) override { return _catalog ? _catalog->isMenu() : false; } /**< Returns current updater effectiveness */ bool isEnable(void) override { return _auxCatalog ? _auxCatalog->isMenu() : false; } /**< Returns current updater effectiveness */
AC_UPDATESTATUS_t status(void) override { return _status; } /**< reports the current update behavior status */ AC_UPDATESTATUS_t status(void) override { return _status; } /**< reports the current update behavior status */
AC_UPDATESTATUS_t update(void) override; /**< behaves update */ AC_UPDATESTATUS_t update(void) override; /**< behaves update */
@ -152,33 +155,38 @@ class AutoConnectUpdateAct : public AutoConnectUpdateVoid, public HTTPUpdateClas
String _onCatalog(AutoConnectAux& catalog, PageArgument& args); String _onCatalog(AutoConnectAux& catalog, PageArgument& args);
String _onUpdate(AutoConnectAux& update, PageArgument& args); String _onUpdate(AutoConnectAux& update, PageArgument& args);
String _onResult(AutoConnectAux& result, PageArgument& args); String _onResult(AutoConnectAux& result, PageArgument& args);
void _wsEvent(uint8_t client, WStype_t event, uint8_t* payload, size_t length);
void _inProgress(size_t amount, size_t size); /**< UpdateClass::THandlerFunction_Progress */ void _inProgress(size_t amount, size_t size); /**< UpdateClass::THandlerFunction_Progress */
std::unique_ptr<AutoConnectAux> _catalog; /**< A catalog page for internally generated update binaries */ std::unique_ptr<AutoConnectAux> _auxCatalog; /**< A catalog page for internally generated update binaries */
std::unique_ptr<AutoConnectAux> _progress; /**< An update in-progress page */ std::unique_ptr<AutoConnectAux> _auxProgress; /**< An update in-progress page */
std::unique_ptr<AutoConnectAux> _result; /**< A update result page */ std::unique_ptr<AutoConnectAux> _auxResult; /**< A update result page */
std::unique_ptr<WebSocketsServer> _ws; /**< Reports the update progress measure */ size_t _amount; /**< Received amount bytes */
uint8_t _wsClient; /**< WebSocket client id */ size_t _binSize; /**< Updater binary size */
bool _wsConnected; /**< WebSocket connection status */
size_t _amount; /**< Received amount bytes */
size_t _binSize; /**< Updater binary size */
private: private:
bool _enable; /**< Validation status of the Update class */ void _progress(void); /**< A Handler that returns progress status to the web client */
AC_UPDATEDIALOG_t _dialog; /**< The type of updating dialog displayed on the client */
AC_UPDATESTATUS_t _status; /**< Status of update processing during the cycle of receiving a request */
String _binName; /**< .bin name to update */
static const ACPage_t _auxCatalog PROGMEM; bool _enable; /**< Validation status of the Update class */
AC_UPDATEDIALOG_t _dialog; /**< The type of updating dialog displayed on the client */
AC_UPDATESTATUS_t _status; /**< Status of update processing during the cycle of receiving a request */
String _binName; /**< .bin name to update */
WebServerClass* _webServer; /**< Hosted WebServer for XMLHttpRequest */
static const ACPage_t _pageCatalog PROGMEM;
static const ACElementProp_t _elmCatalog[] PROGMEM; static const ACElementProp_t _elmCatalog[] PROGMEM;
static const ACPage_t _auxProgress PROGMEM; static const ACPage_t _pageProgress PROGMEM;
static const ACElementProp_t _elmProgress[] PROGMEM; static const ACElementProp_t _elmProgress[] PROGMEM;
static const ACPage_t _auxResult PROGMEM; static const ACPage_t _pageResult PROGMEM;
static const ACElementProp_t _elmResult[] PROGMEM; static const ACElementProp_t _elmResult[] PROGMEM;
#if defined(ARDUINO_ARCH_ESP8266)
friend class ESP8266WebServer;
#elif defined(ARDUINO_ARCH_ESP32)
friend class WebServer;
#endif
}; };
#endif // !AUTOCONNECT_USE_UPDATE #endif // !AUTOCONNECT_USE_UPDATE

@ -21,7 +21,7 @@ const AutoConnectUpdateAct::ACElementProp_t AutoConnectUpdateAct::_elmCatalog[]
{ AC_Element, "c1", "</div>", nullptr }, { AC_Element, "c1", "</div>", nullptr },
{ AC_Submit, "update", "UPDATE", AUTOCONNECT_URI_UPDATE_ACT } { AC_Submit, "update", "UPDATE", AUTOCONNECT_URI_UPDATE_ACT }
}; };
const AutoConnectUpdateAct::ACPage_t AutoConnectUpdateAct::_auxCatalog PROGMEM = { const AutoConnectUpdateAct::ACPage_t AutoConnectUpdateAct::_pageCatalog PROGMEM = {
AUTOCONNECT_URI_UPDATE, "Update", false, AutoConnectUpdateAct::_elmCatalog AUTOCONNECT_URI_UPDATE, "Update", false, AutoConnectUpdateAct::_elmCatalog
}; };
@ -36,27 +36,24 @@ const AutoConnectUpdateAct::ACElementProp_t AutoConnectUpdateAct::_elmProgress[]
{ AC_Element, "c3", "<div id=\"progress\">Updating...<span style=\"display:inline-block;vertical-align:middle;margin-left:7px\">", nullptr }, { AC_Element, "c3", "<div id=\"progress\">Updating...<span style=\"display:inline-block;vertical-align:middle;margin-left:7px\">", nullptr },
{ AC_Element, "progress_meter", "<meter min=\"0\" />", nullptr }, { AC_Element, "progress_meter", "<meter min=\"0\" />", nullptr },
{ AC_Element, "progress_loader", "<div id=\"ld\" />", nullptr }, { AC_Element, "progress_loader", "<div id=\"ld\" />", nullptr },
{ AC_Element, "c4", "</span></div></div>", nullptr }, { AC_Element, "c4", "</span></div>", nullptr },
{ AC_Text, "status", nullptr, nullptr }, { AC_Text, "status", nullptr, nullptr },
{ AC_Element, "c5", "<script type=\"text/javascript\">var ws,conn=!1;function rd(){location.href=\"" AUTOCONNECT_URI_UPDATE_RESULT "\"}function bar(){(ws=new WebSocket(\"", nullptr }, { AC_Element, "c5", "<script type=\"text/javascript\">var lap,cls;function rd(){clearInterval(lap),location.href=\"" AUTOCONNECT_URI_UPDATE_RESULT "\"}function bar(){var t=new FormData;t.append(\"op\",\"#s\");var e=new XMLHttpRequest;e.timeout=" AUTOCONNECT_STRING_DEPLOY(AUTOCONNECT_UPDATE_TIMEOUT) ",e.open(\"POST\",\"" AUTOCONNECT_URI_UPDATE_PROGRESS "\",!0),e.onreadystatechange=function(){4==e.readyState&&(200==e.status?(cls=!1,lap=setInterval(upd," AUTOCONNECT_STRING_DEPLOY(AUTOCONNECT_UPDATE_INTERVAL) ")):document.getElementById(\"status\").textContent=\"Could not start (\"+e.status+\"): \"+e.responseText)},e.send(t)}function upd(){if(!cls){var t=new XMLHttpRequest;t.onload=function(){var t=this.responseText.split(\",\");\"#s\"==t[0]?(window.setTimeout(rd()," AUTOCONNECT_STRING_DEPLOY(AUTOCONNECT_UPDATE_DURATION) ")", nullptr },
{ AC_Element, "wsurl", nullptr, nullptr }, { AC_Element, "enable_loader", ",document.getElementById(\"ld\").className=\"loader\"", nullptr },
{ AC_Element, "c6", "\")).onopen=function(){ws.onmessage=function(e){var t=e.data.split(\",\");\"#s\"==t[0]?(", nullptr }, { AC_Element, "c6", "):\"#e\"==t[0]?(cls=!0,rd()):\"#p\"==t[0]&&incr(t[1])},t.onerror=function(){console.log(\"http err:%d %s\",t.status,t.responseText)},t.open(\"GET\",\"" AUTOCONNECT_URI_UPDATE_PROGRESS "\",!0),t.send()}}function incr(t){", nullptr },
{ AC_Element, "enable_loader", "document.getElementById(\"ld\").className=\"loader\",", nullptr }, { AC_Element, "inprogress_meter", "var e=t.split(\":\"),n=document.getElementById(\"progress\").getElementsByTagName(\"meter\");n[0].setAttribute(\"value\",e[0]),n[0].setAttribute(\"max\",e[1])", nullptr },
{ AC_Element, "c7", "window.setTimeout(rd()," AUTOCONNECT_STRING_DEPLOY(AUTOCONNECT_UPDATE_DURATION) "),conn=!0):\"#e\"==t[0]?(ws.close(),conn=!1):\"#p\"==t[0]&&incr(t[1])}},ws.onclose=function(e){conn?setTimeout(function(){bar()},2e3):rd()},ws.onerror=function(e){console.log(\"WS err(\"+e.code+\")\"+e.reason),1==ws.readyState&&(document.getElementById(\"status\").textContent=\"WebSocket \"+e.type)}}", nullptr }, { AC_Element, "c7", "}window.onload=bar;</script>", nullptr }
{ AC_Element, "inprogress_meter", "function incr(e){var t=e.split(\":\"),n=document.getElementById(\"progress\").getElementsByTagName(\"meter\");n[0].setAttribute(\"value\",t[0]),n[0].setAttribute(\"max\",t[1])}", nullptr },
{ AC_Element, "inprogress_loader", "function incr(pv){}", nullptr },
{ AC_Element, "c8", "window.onload=bar;</script>", nullptr }
}; };
const AutoConnectUpdateAct::ACPage_t AutoConnectUpdateAct::_auxProgress PROGMEM = { const AutoConnectUpdateAct::ACPage_t AutoConnectUpdateAct::_pageProgress PROGMEM = {
AUTOCONNECT_URI_UPDATE_ACT, "Update", false, AutoConnectUpdateAct::_elmProgress AUTOCONNECT_URI_UPDATE_ACT, "Update", false, AutoConnectUpdateAct::_elmProgress
}; };
// Definition of the AUTOCONNECT_URI_UPDATE_RESULT page to notify update results // Definition of the AUTOCONNECT_URI_UPDATE_RESULT page to notify update results
const AutoConnectUpdateAct::ACElementProp_t AutoConnectUpdateAct::_elmResult[] PROGMEM = { const AutoConnectUpdateAct::ACElementProp_t AutoConnectUpdateAct::_elmResult[] PROGMEM = {
{ AC_Text, "status", nullptr, nullptr }, { AC_Text, "status", nullptr, nullptr },
{ AC_Element, "restart", "<script type=\"text/javascript\">setTimeout(\"location.href='" AUTOCONNECT_HOMEURI "'\",1e4);</script>", nullptr } { AC_Element, "restart", "<script type=\"text/javascript\">window.onload=function(){var e=new FormData;e.append(\"op\",\"#r\");var o=new XMLHttpRequest;o.timeout=" AUTOCONNECT_STRING_DEPLOY(AUTOCONNECT_UPDATE_TIMEOUT) ",o.onloadend=function(){setTimeout(\"location.href='" AUTOCONNECT_HOMEURI "'\"," AUTOCONNECT_STRING_DEPLOY(AUTOCONNECT_UPDATE_WAITFORREBOOT) ")},o.open(\"POST\",\"" AUTOCONNECT_URI_UPDATE_PROGRESS "\",!0),o.send(e)};</script>", nullptr }
}; };
const AutoConnectUpdateAct::ACPage_t AutoConnectUpdateAct::_auxResult PROGMEM = { const AutoConnectUpdateAct::ACPage_t AutoConnectUpdateAct::_pageResult PROGMEM = {
AUTOCONNECT_URI_UPDATE_RESULT, "Update", false, AutoConnectUpdateAct::_elmResult AUTOCONNECT_URI_UPDATE_RESULT, "Update", false, AutoConnectUpdateAct::_elmResult
}; };

Loading…
Cancel
Save