AutoConnectUpdate-rc1

pull/123/head
Hieromon Ikasamo 6 years ago
parent a9b2ae0f98
commit f0dde87064
  1. 98
      src/AutoConnectUpdate.cpp
  2. 23
      src/AutoConnectUpdate.h
  3. 18
      src/AutoConnectUpdatePage.h

@ -89,14 +89,20 @@ namespace AutoConnectUtil {
AC_HAS_FUNC(onProgress);
template<typename T>
typename std::enable_if<AutoConnectUtil::has_func_onProgress<T>::value, AutoConnectUpdateAct::AC_UPDATEDIALOG_t>::type onProgress(T& updater, std::function<void(size_t, size_t)> 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);
AC_DBG("Updater keeps callback\n");
return AutoConnectUpdateAct::UPDATEDIALOG_METER;
#else
(void)(updater);
(void)(fn);
return AutoConnectUpdateAct::UPDATEDIALOG_LOADER;
#endif
}
template<typename T>
typename std::enable_if<!AutoConnectUtil::has_func_onProgress<T>::value, AutoConnectUpdateAct::AC_UPDATEDIALOG_t>::type onProgress(T& updater, std::function<void(size_t, size_t)> fn) {
typename std::enable_if<!AutoConnectUtil::has_func_onProgress<T>::value, AutoConnectUpdateAct::AC_UPDATEDIALOG_t>::type onProgress(T& updater, UpdateVariedClass::THandlerFunction_Progress fn) {
(void)(updater);
(void)(fn);
return AutoConnectUpdateAct::UPDATEDIALOG_LOADER;
@ -111,7 +117,6 @@ AutoConnectUpdateAct::~AutoConnectUpdateAct() {
_catalog.reset(nullptr);
_progress.reset(nullptr);
_result.reset(nullptr);
_WiFiClient.reset(nullptr);
_ws.reset(nullptr);
}
@ -136,7 +141,7 @@ void AutoConnectUpdateAct::attach(AutoConnect& portal) {
updatePage = new AutoConnectAux(String(FPSTR(_auxResult.uri)), String(FPSTR(_auxResult.title)), _auxResult.menu);
_buildAux(updatePage, &_auxResult, lengthOf(_elmResult));
_result.reset(updatePage);
_result->chunk = PB_ByteStream;
_catalog->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);
_result->on(std::bind(&AutoConnectUpdateAct::_onResult, this, std::placeholders::_1, std::placeholders::_2), AC_EXIT_AHEAD);
@ -159,7 +164,6 @@ void AutoConnectUpdateAct::attach(AutoConnect& portal) {
// of the UpdateClass.
AutoConnectElement* loader = _progress->getElement(String(F("loader")));
AutoConnectElement* enable_loader = _progress->getElement(String(F("enable_loader")));
AutoConnectElement* enable_loader_post = _progress->getElement(String(F("enable_loader_post")));
AutoConnectElement* progress_meter = _progress->getElement(String(F("progress_meter")));
AutoConnectElement* inprogress_meter = _progress->getElement(String(F("inprogress_meter")));
AutoConnectElement* progress_loader = _progress->getElement(String(F("progress_loader")));
@ -172,7 +176,6 @@ void AutoConnectUpdateAct::attach(AutoConnect& portal) {
case UPDATEDIALOG_METER:
loader->enable = false;
enable_loader->enable =false;
enable_loader_post->enable =false;
progress_loader->enable =false;
inprogress_loader->enable = false;
break;
@ -183,11 +186,10 @@ void AutoConnectUpdateAct::attach(AutoConnect& portal) {
* Detach the update item from the current AutoConnect menu.
* AutoConnectUpdateAct still active.
*/
void AutoConnectUpdateAct::disable(void) {
void AutoConnectUpdateAct::disable(const bool activate) {
_enable = activate;
if (_catalog) {
_catalog->menu(false);
if (_WiFiClient)
_WiFiClient.reset(nullptr);
AC_DBG("AutoConnectUpdate disabled\n");
}
}
@ -197,23 +199,19 @@ void AutoConnectUpdateAct::disable(void) {
* function into the menu.
*/
void AutoConnectUpdateAct::enable(void) {
_enable = true;
_status = UPDATE_IDLE;
if (_catalog) {
_catalog->menu(true);
_status = UPDATE_IDLE;
_period = 0;
_catalog->menu(WiFi.status() == WL_CONNECTED);
AC_DBG("AutoConnectUpdate enabled\n");
}
}
void AutoConnectUpdateAct::handleUpdate(void) {
// Purge WiFiClient instances that have exceeded their retention
// period to avoid running out of memory.
if (_WiFiClient) {
if (millis() - _period > AUTOCONNECT_UPDATE_DURATION)
if (_status != UPDATE_START) {
_WiFiClient.reset(nullptr);
AC_DBG("Purged WifiClient due to duration expired.\n");
}
// Activate the update menu conditional with WiFi connected.
if (!isEnable() && _enable) {
if (WiFi.status() == WL_CONNECTED)
enable();
}
if (isEnable()) {
@ -222,6 +220,7 @@ void AutoConnectUpdateAct::handleUpdate(void) {
// execute it accordingly. It is only this process point that
// requests update processing.
if (_status == UPDATE_START) {
AC_DBG("Waiting for WS connection\n");
unsigned long tm = millis();
while (!_wsConnected) {
if (millis() - tm > AUTOCONNECT_TIMEOUT) {
@ -229,6 +228,7 @@ void AutoConnectUpdateAct::handleUpdate(void) {
break;
}
_ws->loop(); // Crawl the connection request.
yield();
}
// Launch the update
if (_wsConnected)
@ -244,7 +244,7 @@ void AutoConnectUpdateAct::handleUpdate(void) {
// If WiFi is not connected, disables the update menu.
// However, the AutoConnectUpdateAct class stills active.
else
disable();
disable(_enable);
}
}
@ -258,11 +258,12 @@ AC_UPDATESTATUS_t AutoConnectUpdateAct::update(void) {
String uriBin = uri + '/' + _binName;
if (_binName.length()) {
AC_DBG("%s:%d/%s update in progress...", host.c_str(), port, uriBin.c_str());
if (!_WiFiClient) {
_WiFiClient.reset(new WiFiClient);
_period = millis();
}
t_httpUpdate_return ret = HTTPUpdateClass::update(*_WiFiClient, host, port, uriBin);
#if defined(ARDUINO_ARCH_ESP8266)
t_httpUpdate_return ret = HTTPUpdateClass::update(host, port, uriBin);
#elif defined(ARDUINO_ARCH_ESP32)
WiFiClient wifiClient;
t_httpUpdate_return ret = HTTPUpdateClass::update(wifiClient, host, port, uriBin);
#endif
switch (ret) {
case HTTP_UPDATE_FAILED:
_status = UPDATE_FAIL;
@ -278,7 +279,6 @@ AC_UPDATESTATUS_t AutoConnectUpdateAct::update(void) {
AC_DBG_DUMB(" completed\n");
break;
}
_WiFiClient.reset(nullptr);
// Request the client to close the WebSocket.
_ws->sendTXT(_wsClient, "#e");
}
@ -349,19 +349,14 @@ String AutoConnectUpdateAct::_onCatalog(AutoConnectAux& catalog, PageArgument& a
AC_UNUSED(args);
HTTPClient httpClient;
// Reallocate WiFiClient if it is not existence.
if (!_WiFiClient) {
_WiFiClient.reset(new WiFiClient);
_period = millis();
}
// Reallocate available firmwares list.
_binName = String("");
AutoConnectText& caption = catalog.getElement<AutoConnectText>(String(F("caption")));
AutoConnectRadio& firmwares = catalog.getElement<AutoConnectRadio>(String(F("firmwares")));
AutoConnectSubmit& submit = catalog.getElement<AutoConnectSubmit>(String(F("update")));
firmwares.empty();
firmwares.tags.clear();
submit.enable = false;
_binName = String("");
String qs = String(F(AUTOCONNECT_UPDATE_CATALOG)) + '?' + String(F("op=list&path=")) + uri;
AC_DBG("Query %s:%d%s\n", host.c_str(), port, qs.c_str());
@ -369,7 +364,7 @@ String AutoConnectUpdateAct::_onCatalog(AutoConnectAux& catalog, PageArgument& a
// Throw a query to the update server and parse the response JSON
// document. After that, display the bin type file name contained in
// its JSON document as available updaters to the page.
if (httpClient.begin(*_WiFiClient.get(), host, port, qs, false)) {
if (httpClient.begin(host, port, qs)) {
int responseCode = httpClient.GET();
if (responseCode == HTTP_CODE_OK) {
@ -378,7 +373,7 @@ String AutoConnectUpdateAct::_onCatalog(AutoConnectAux& catalog, PageArgument& a
char beginOfList[] = "[";
char endOfEntry[] = ",";
char endOfList[] = "]";
Stream& responseBody = httpClient.getStream();
WiFiClient& responseBody = httpClient.getStream();
// Read partially and repeatedly the responded http stream that is
// including the JSON array to reduce the buffer size for parsing
@ -491,9 +486,15 @@ String AutoConnectUpdateAct::_onResult(AutoConnectAux& result, PageArgument& arg
bool restart = false;
if (_ws) {
_ws->close();
while (_wsConnected)
_ws->loop();
_ws->loop();
while (_wsConnected) {
IPAddress ipAny;
if (_ws->remoteIP(_wsClient) == ipAny)
_wsConnected = false;
else
_ws->close();
yield();
}
_ws.reset(nullptr);
}
@ -519,9 +520,18 @@ String AutoConnectUpdateAct::_onResult(AutoConnectAux& result, PageArgument& arg
result.getElement<AutoConnectElement>(String(F("restart"))).enable = restart;
if (restart)
_status = UPDATE_RESET;
return String("");
}
/**
* 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) {
if (_ws) {
_amount = amount;
@ -531,6 +541,16 @@ void AutoConnectUpdateAct::_inProgress(size_t amount, size_t size) {
}
}
/**
* An event handler of WebSocket which should be opened to monitor
* update progress. AutoConnectUpdate will send #s to start the update
* progress notification to the client when the WebSocket client
* connected. This has the meaning of ACK.
* @param client WS client number of WebSocketsServer
* @param event Event id
* @param payload Payload content
* @param length payload length
*/
void AutoConnectUpdateAct::_wsEvent(uint8_t client, WStype_t event, uint8_t* payload, size_t length) {
AC_DBG("WS client:%d event(%d)\n", client, event);
if (event == WStype_CONNECTED) {

@ -88,7 +88,7 @@ class AutoConnectUpdateVoid {
virtual ~AutoConnectUpdateVoid() {}
virtual void attach(AutoConnect& portal) { AC_UNUSED(portal); }
virtual void enable(void) {}
virtual void disable(void) {}
virtual void disable(const bool activate = false) { AC_UNUSED(activate); }
virtual void handleUpdate(void) {}
virtual bool isEnable(void) { return false; }
virtual AC_UPDATESTATUS_t status(void) { return UPDATE_IDLE; }
@ -100,12 +100,12 @@ class AutoConnectUpdateVoid {
class AutoConnectUpdateAct : public AutoConnectUpdateVoid, public HTTPUpdateClass {
public:
explicit AutoConnectUpdateAct(const String& host = String(""), const uint16_t port = AUTOCONNECT_UPDATE_PORT, const String& uri = String("."), const int timeout = AUTOCONNECT_UPDATE_TIMEOUT)
: HTTPUpdateClass(timeout), host(host), port(port), uri(uri), _status(UPDATE_IDLE), _binName(String()), _period(0) {
: 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()) {
UPDATE_SETLED(LOW); /**< LED blinking during the update that is the default. */
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)
: HTTPUpdateClass(timeout), host(host), port(port), uri(uri), _status(UPDATE_IDLE), _binName(String()), _period(0) {
: 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()) {
UPDATE_SETLED(LOW);
rebootOnUpdate(false);
attach(portal);
@ -113,7 +113,7 @@ class AutoConnectUpdateAct : public AutoConnectUpdateVoid, public HTTPUpdateClas
~AutoConnectUpdateAct();
void attach(AutoConnect& portal) override; /**< Attach the update class to AutoConnect */
void enable(void) override; /**< Enable the AutoConnectUpdateAct */
void disable(void) override; /**< Disable the AutoConnectUpdateAct */
void disable(const bool activte = false) override; /**< Disable the AutoConnectUpdateAct */
void handleUpdate(void) override; /**< Behaves the update process */
bool isEnable(void) override { return _catalog ? _catalog->isMenu() : false; } /**< Returns current updater effectiveness */
AC_UPDATESTATUS_t status(void) override { return _status; } /**< reports the current update behavior status */
@ -123,10 +123,10 @@ class AutoConnectUpdateAct : public AutoConnectUpdateVoid, public HTTPUpdateClas
uint16_t port; /**< Port number of the update server */
String uri; /**< The path on the update server that contains the sketch binary to be updated */
//
// Indicate the type of progress dialog
typedef enum {
UPDATEDIALOG_LOADER,
UPDATEDIALOG_METER
UPDATEDIALOG_LOADER, /**< Cyclic loader icon */
UPDATEDIALOG_METER /**< Progress meter */
} AC_UPDATEDIALOG_t;
protected:
@ -141,7 +141,7 @@ class AutoConnectUpdateAct : public AutoConnectUpdateVoid, public HTTPUpdateClas
// Attributes to treat included update pages as AutoConnectAux.
typedef struct {
const char* uri; /**< URI for the page */
const char* title; /**< Menut title of update page */
const char* title; /**< Menu title of update page */
const bool menu; /**< Whether to display in menu */
const ACElementProp_t* element;
} ACPage_t;
@ -153,7 +153,7 @@ class AutoConnectUpdateAct : public AutoConnectUpdateVoid, public HTTPUpdateClas
String _onUpdate(AutoConnectAux& update, 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);
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> _progress; /**< An update in-progress page */
@ -162,15 +162,14 @@ class AutoConnectUpdateAct : public AutoConnectUpdateVoid, public HTTPUpdateClas
std::unique_ptr<WebSocketsServer> _ws; /**< Reports the update progress measure */
uint8_t _wsClient; /**< WebSocket client id */
bool _wsConnected; /**< WebSocket connection status */
size_t _amount; /**< Received amound bytes */
size_t _amount; /**< Received amount bytes */
size_t _binSize; /**< Updater binary size */
private:
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 */
unsigned long _period; /**< Duration of WiFiClient holding for the connection with the update server */
std::unique_ptr<WiFiClient> _WiFiClient; /**< Provide to HTTPUpdate class */
static const ACPage_t _auxCatalog PROGMEM;
static const ACElementProp_t _elmCatalog[] PROGMEM;

@ -14,7 +14,7 @@
// Define the AUTOCONNECT_URI_UPDATE page to select the sketch binary
// for update and order update execution.
const AutoConnectUpdateAct::ACElementProp_t AutoConnectUpdateAct::_elmCatalog[] PROGMEM = {
{ AC_Element, "binSty", "<style type\"text/css\">.bins{display:grid;font-size:14px;grid-gap:10px 0;grid-template-columns:1em repeat(4,max-content);overflow-x:auto}.bins input[type=radio]{-moz-appearance:radio;-webkit-appearance:radio;margin:0;vertical-align:middle}.noorder .bins label,span{margin:0 .5em 0 .5em;padding:0;text-align:left}</style>", nullptr },
{ AC_Element, "binSty", "<style type=\"text/css\">.bins{display:grid;font-size:14px;grid-gap:10px 0;grid-template-columns:1em repeat(4,max-content);overflow-x:auto}.bins input[type=radio]{-moz-appearance:radio;-webkit-appearance:radio;margin:0;vertical-align:middle}.noorder .bins label,span{margin:0 .5em 0 .5em;padding:0;text-align:left}</style>", nullptr },
{ AC_Text, "caption", nullptr, nullptr },
{ AC_Element, "c1", "<div class=\"bins\">", nullptr },
{ AC_Radio, "firmwares", nullptr, nullptr },
@ -38,16 +38,14 @@ const AutoConnectUpdateAct::ACElementProp_t AutoConnectUpdateAct::_elmProgress[]
{ AC_Element, "progress_loader", "<div id=\"ld\" />", nullptr },
{ AC_Element, "c4", "</span></div></div>", nullptr },
{ AC_Text, "status", nullptr, nullptr },
{ AC_Element, "c5", "<script type=\"text/javascript\">function rd(){location.href=\"" AUTOCONNECT_URI_UPDATE_RESULT "\"}var ws;window.onload=function(){(ws=new WebSocket(\"", 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, "wsurl", nullptr, nullptr },
{ AC_Element, "c6", "\")).onopen=function(){ws.onmessage=function(e){var o=e.data.split(\",\");\"#s\"==o[0]?", nullptr },
{ AC_Element, "enable_loader", "(document.getElementById(\"ld\").className=\"loader\",", nullptr },
{ AC_Element, "c7", "window.setTimeout(rd()," AUTOCONNECT_STRING_DEPLOY(AUTOCONNECT_UPDATE_DURATION) ")", nullptr },
{ AC_Element, "enable_loader_post", ")", nullptr },
{ AC_Element, "c8", ":\"#e\"==o[0]?ws.close():\"#p\"==o[0]&&incr(o[1])}},ws.onclose=function(e){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, "inprogress_meter", "function incr(e){var t=e.split(\":\"),r=document.getElementById(\"progress\").getElementsByTagName(\"meter\");r[0].setAttribute(\"value\",t[0]),r[0].setAttribute(\"max\",t[1])}", nullptr },
{ AC_Element, "c6", "\")).onopen=function(){ws.onmessage=function(e){var t=e.data.split(\",\");\"#s\"==t[0]?(", nullptr },
{ AC_Element, "enable_loader", "document.getElementById(\"ld\").className=\"loader\",", 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, "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, "c9", "</script>", nullptr }
{ AC_Element, "c8", "window.onload=bar;</script>", nullptr }
};
const AutoConnectUpdateAct::ACPage_t AutoConnectUpdateAct::_auxProgress PROGMEM = {
AUTOCONNECT_URI_UPDATE_ACT, "Update", false, AutoConnectUpdateAct::_elmProgress
@ -56,7 +54,7 @@ const AutoConnectUpdateAct::ACPage_t AutoConnectUpdateAct::_auxProgress PROGMEM
// Definition of the AUTOCONNECT_URI_UPDATE_RESULT page to notify update results
const AutoConnectUpdateAct::ACElementProp_t AutoConnectUpdateAct::_elmResult[] PROGMEM = {
{ AC_Text, "status", nullptr, nullptr },
{ AC_Element, "restart", "<script type=\"text/javascript\">setTimeout(\"location.href='" AUTOCONNECT_HOMEURI "'\",1000*10);</script>", nullptr }
{ AC_Element, "restart", "<script type=\"text/javascript\">setTimeout(\"location.href='" AUTOCONNECT_HOMEURI "'\",1e4);</script>", nullptr }
};
const AutoConnectUpdateAct::ACPage_t AutoConnectUpdateAct::_auxResult PROGMEM = {
AUTOCONNECT_URI_UPDATE_RESULT, "Update", false, AutoConnectUpdateAct::_elmResult

Loading…
Cancel
Save