Support AutoConnectUpdate

pull/123/head
Hieromon Ikasamo 6 years ago
parent 8b950d5d5c
commit 836ec3376b
  1. 16
      src/AutoConnectDefs.h
  2. 131
      src/AutoConnectUpdate.cpp
  3. 11
      src/AutoConnectUpdate.h
  4. 22
      src/AutoConnectUpdatePage.h
  5. 22
      src/AutoConnectUploadImpl.h

@ -3,7 +3,7 @@
* @file AutoConnectDefs.h * @file AutoConnectDefs.h
* @author hieromon@gmail.com * @author hieromon@gmail.com
* @version 0.9.9 * @version 0.9.9
* @date 2019-04-30 * @date 2019-05-14
* @copyright MIT license. * @copyright MIT license.
*/ */
@ -200,4 +200,18 @@
// 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)
// Generates a template that determines whether the class owns the
// specified member function.
// The purpose of this macro is to avoid the use of invalid member
// functions due to differences in the version of the library which
// AutoConnect depends on.
#define AC_HAS_FUNC(func) \
template<typename T> \
struct has_func_##func { \
static auto check(...) -> decltype(std::false_type()); \
template<typename U> \
static auto check(U&) -> decltype(static_cast<decltype(U::func)*>(&U::func), std::true_type()); \
enum : bool { value = decltype(check(std::declval<T&>()))::value }; \
}
#endif // _AUTOCONNECTDEFS_H_ #endif // _AUTOCONNECTDEFS_H_

@ -3,11 +3,12 @@
* @file AutoConnectUpdate.cpp * @file AutoConnectUpdate.cpp
* @author hieromon@gmail.com * @author hieromon@gmail.com
* @version 0.9.9 * @version 0.9.9
* @date 2019-05-03 * @date 2019-05-14
* @copyright MIT license. * @copyright MIT license.
*/ */
#include <functional> #include <functional>
#include <type_traits>
#include "AutoConnectUpdate.h" #include "AutoConnectUpdate.h"
#include "AutoConnectUpdatePage.h" #include "AutoConnectUpdatePage.h"
#include "AutoConnectJsonDefs.h" #include "AutoConnectJsonDefs.h"
@ -65,6 +66,40 @@
* HTTP and also needs to attach an MD5 hash value to the x-MD5 header. * HTTP and also needs to attach an MD5 hash value to the x-MD5 header.
*/ */
/**
* The following two templates absorb callbacks that are enabled/disabled
* by the Update library version in the core.
* The old UpdateClass of the ESP8266/ESP32 arduino core does not have
* the onProgress function for registering callbacks. These templates
* avoid the onProgress calls on older library versions.
* In versions where the update function callback is disabled, the
* dialog on the client browser does not show the progress of the update.
*/
#if defined(ARDUINO_ARCH_ESP8266)
using UpdateVariedClass = UpdaterClass;
#elif defined(ARDUINO_ARCH_ESP32)
using UpdateVariedClass = UpdateClass;
#endif
namespace AutoConnectUtil {
AC_HAS_FUNC(onProgress);
template<typename T>
typename std::enable_if<AutoConnectUtil::has_func_onProgress<T>::value, AutoConnectUpdate::AC_UPDATEDIALOG_t>::type onProgress(const T& updater, std::function<void(size_t, size_t)> fn) {
updater.onProgress(fn);
AC_DBG("Callback enabled\n");
return AutoConnectUpdate::UPDATEDIALOG_METER;
}
template<typename T>
typename std::enable_if<!AutoConnectUtil::has_func_onProgress<T>::value, AutoConnectUpdate::AC_UPDATEDIALOG_t>::type onProgress(const T& updater, std::function<void(size_t, size_t)> fn) {
(void)(updater);
(void)(fn);
AC_DBG("Callback disabled\n");
return AutoConnectUpdate::UPDATEDIALOG_LOADER;
}
}
/** /**
* A destructor. Release the update processing dialogue page generated * A destructor. Release the update processing dialogue page generated
* as AutoConnectAux. * as AutoConnectAux.
@ -108,9 +143,29 @@ void AutoConnectUpdate::attach(AutoConnect& portal) {
_status = UPDATE_IDLE; _status = UPDATE_IDLE;
#ifdef ARDUINO_ARCH_ESP32 // Register the callback to inform the update progress
Update.onProgress(std::bind(&AutoConnectUpdate::_inProgress, this, std::placeholders::_1, std::placeholders::_2)); _dialog = AutoConnectUtil::onProgress<UpdateVariedClass>(Update, std::bind(&AutoConnectUpdate::_inProgress, this, std::placeholders::_1, std::placeholders::_2));
#endif // Update.onProgress(std::bind(&AutoConnectUpdate::_inProgress, this, std::placeholders::_1, std::placeholders::_2));
// _dialog = UPDATEDIALOG_METER;
// Adjust the client dialog pattern according to the callback validity
// of the UpdateClass.
AutoConnectElement* loader = _progress->getElement(String(F("loader")));
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")));
AutoConnectElement* inprogress_loader = _progress->getElement(String(F("inprogress_loader")));
switch (_dialog) {
case UPDATEDIALOG_LOADER:
progress_meter->enable =false;
inprogress_meter->enable = false;
break;
case UPDATEDIALOG_METER:
loader->enable = false;
progress_loader->enable =false;
inprogress_loader->enable = false;
break;
}
// Attach this to the AutoConnectUpdate // Attach this to the AutoConnectUpdate
portal._update.reset(this); portal._update.reset(this);
@ -161,8 +216,22 @@ void AutoConnectUpdate::handleUpdate(void) {
// Evaluate the processing status of AutoConnectUpdate and // Evaluate the processing status of AutoConnectUpdate and
// 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) {
update(); _ws->loop(); // Crawl the connection request.
unsigned long tm = millis();
while (_ws->connectedClients() <= 0) {
if (millis() - tm > AUTOCONNECT_UPDATE_TIMEOUT) {
AC_DBG("WebSocket client connection timeout, update ignored\n");
break;
}
_ws->loop(); // Crawl the connection request.
}
// Launch the update
if (_ws->connectedClients())
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());
ESP.restart(); ESP.restart();
@ -181,10 +250,6 @@ void AutoConnectUpdate::handleUpdate(void) {
* @return AC_UPDATESTATUS_t * @return AC_UPDATESTATUS_t
*/ */
AC_UPDATESTATUS_t AutoConnectUpdate::update(void) { AC_UPDATESTATUS_t AutoConnectUpdate::update(void) {
// Crawl queued requests.
if (_ws)
_ws->loop();
// Start update // Start update
String uriBin = uri + '/' + _binName; String uriBin = uri + '/' + _binName;
if (_binName.length()) { if (_binName.length()) {
@ -198,6 +263,7 @@ AC_UPDATESTATUS_t AutoConnectUpdate::update(void) {
case HTTP_UPDATE_FAILED: case HTTP_UPDATE_FAILED:
_status = UPDATE_FAIL; _status = UPDATE_FAIL;
AC_DBG_DUMB(" %s\n", getLastErrorString().c_str()); AC_DBG_DUMB(" %s\n", getLastErrorString().c_str());
AC_DBG("update returns HTTP_UPDATE_FAILED\n");
break; break;
case HTTP_UPDATE_NO_UPDATES: case HTTP_UPDATE_NO_UPDATES:
_status = UPDATE_IDLE; _status = UPDATE_IDLE;
@ -210,11 +276,7 @@ AC_UPDATESTATUS_t AutoConnectUpdate::update(void) {
} }
_WiFiClient.reset(nullptr); _WiFiClient.reset(nullptr);
// Request the client to close the WebSocket. // Request the client to close the WebSocket.
if (_ws) { _ws->sendTXT(_wsClient, "#e", 2);
String cmdClose = String("#e");
_ws->sendTXT(_wsClient, cmdClose);
_ws->loop();
}
} }
else { else {
AC_DBG("An update has not specified"); AC_DBG("An update has not specified");
@ -375,26 +437,23 @@ String AutoConnectUpdate::_onCatalog(AutoConnectAux& catalog, PageArgument& args
String AutoConnectUpdate::_onUpdate(AutoConnectAux& progress, PageArgument& args) { String AutoConnectUpdate::_onUpdate(AutoConnectAux& progress, PageArgument& args) {
AC_UNUSED(args); AC_UNUSED(args);
// launch the WebSocket server // launch the WebSocket server
WebSocketsServer* ws = new WebSocketsServer(AUTOCONNECT_WEBSOCKETPORT); _ws.reset(new WebSocketsServer(AUTOCONNECT_WEBSOCKETPORT));
if (ws) { if (_ws) {
ws->begin(); _ws->begin();
ws->onEvent(std::bind(&AutoConnectUpdate::_wsEvent, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4)); _ws->onEvent(std::bind(&AutoConnectUpdate::_wsEvent, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4));
} }
else { else
AC_DBG("WebSocketsServer allocation failed\n"); AC_DBG("WebSocketsServer allocation failed\n");
}
_ws.reset(ws);
// Constructs the dialog page. // Constructs the dialog page.
AutoConnectText& binName = progress.getElement<AutoConnectText>(String(F("binname"))); AutoConnectElement* binName = progress.getElement(String(F("binname")));
_binName = _catalog->getElement<AutoConnectRadio>(String(F("firmwares"))).value(); _binName = _catalog->getElement<AutoConnectRadio>(String(F("firmwares"))).value();
binName.value = _binName; binName->value = _binName;
AutoConnectText& url = progress.getElement<AutoConnectText>(String("url")); AutoConnectElement* url = progress.getElement(String("url"));
url.value = host + ':' + port; url->value = host + ':' + port;
AutoConnectElement& inprogress = progress.getElement<AutoConnectElement>(String(F("inprogress"))); AutoConnectElement* wsurl = progress.getElement(String(F("wsurl")));
String js = inprogress.value; wsurl->value = "ws://" + WiFi.localIP().toString() + ':' + AUTOCONNECT_WEBSOCKETPORT;
js.replace(String(F("#wsserver#")), WiFi.localIP().toString() + ':' + AUTOCONNECT_WEBSOCKETPORT); AC_DBG("Cast WS %s\n", wsurl->value.c_str());
inprogress.value = js;
_status = UPDATE_START; _status = UPDATE_START;
return String(""); return String("");
} }
@ -412,6 +471,11 @@ String AutoConnectUpdate::_onResult(AutoConnectAux& result, PageArgument& args)
String resColor; String resColor;
bool restart = false; bool restart = false;
if (_ws) {
_ws->close();
_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..."));
@ -443,16 +507,11 @@ void AutoConnectUpdate::_inProgress(size_t amount, size_t size) {
_binSize = size; _binSize = size;
String payload = "#p," + String(_amount) + ':' + String(_binSize); String payload = "#p," + String(_amount) + ':' + String(_binSize);
_ws->sendTXT(_wsClient, payload); _ws->sendTXT(_wsClient, payload);
_ws->loop();
} }
} }
void AutoConnectUpdate::_wsEvent(uint8_t client, WStype_t event, uint8_t* payload, size_t length) { void AutoConnectUpdate::_wsEvent(uint8_t client, WStype_t event, uint8_t* payload, size_t length) {
AC_DBG("WS event(%d)\n", event); AC_DBG("WS client:%d event(%d)\n", client, event);
if (event == WStype_CONNECTED) if (event == WStype_CONNECTED)
_wsClient = client; _wsClient = client;
else if (event == WStype_DISCONNECTED) {
if (client == _wsClient)
_ws.reset(nullptr);
}
} }

@ -22,7 +22,7 @@
* @file AutoConnectUpdate.h * @file AutoConnectUpdate.h
* @author hieromon@gmail.com * @author hieromon@gmail.com
* @version 0.9.9 * @version 0.9.9
* @date 2019-05-03 * @date 2019-05-14
* @copyright MIT license. * @copyright MIT license.
*/ */
@ -92,6 +92,12 @@ class AutoConnectUpdate : public HTTPUpdateClass {
uint16_t port; /**< Port number of the update server */ 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 */ String uri; /**< The path on the update server that contains the sketch binary to be updated */
//
typedef enum {
UPDATEDIALOG_LOADER,
UPDATEDIALOG_METER
} AC_UPDATEDIALOG_t;
protected: protected:
// Attribute definition of the element to be placed on the update page. // Attribute definition of the element to be placed on the update page.
typedef struct { typedef struct {
@ -129,7 +135,8 @@ class AutoConnectUpdate : public HTTPUpdateClass {
size_t _binSize; /**< Updater binary size */ size_t _binSize; /**< Updater binary size */
private: private:
AC_UPDATESTATUS_t _status; 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 */ String _binName; /**< .bin name to update */
unsigned long _period; /**< Duration of WiFiClient holding for the connection with the update server */ unsigned long _period; /**< Duration of WiFiClient holding for the connection with the update server */
std::unique_ptr<WiFiClient> _WiFiClient; /**< Provide to HTTPUpdate class */ std::unique_ptr<WiFiClient> _WiFiClient; /**< Provide to HTTPUpdate class */

@ -4,7 +4,7 @@
* @file AutoConnectUpdatePage.h * @file AutoConnectUpdatePage.h
* @author hieromon@gmail.com * @author hieromon@gmail.com
* @version 0.9.9 * @version 0.9.9
* @date 2019-05-04 * @date 2019-05-14
* @copyright MIT license. * @copyright MIT license.
*/ */
@ -25,12 +25,22 @@ const AutoConnectUpdate::ACPage_t AutoConnectUpdate::_auxCatalog PROGMEM = {
// Define the AUTOCONNECT_URI_UPDATE_ACT page to display during the // Define the AUTOCONNECT_URI_UPDATE_ACT page to display during the
// update process. // update process.
const AutoConnectUpdate::ACElementProp_t AutoConnectUpdate::_elmProgress[] PROGMEM = { const AutoConnectUpdate::ACElementProp_t AutoConnectUpdate::_elmProgress[] PROGMEM = {
{ AC_Element, "caption", "<div style=\"display:inline-block\"", nullptr }, { AC_Element, "loader", "<style>.loader{border:2px solid #f3f3f3;border-radius:50%;border-top:2px solid #555;width: 12px;height:12px;-webkit-animation:spin 1s linear infinite;animation:spin 1s linear infinite}@-webkit-keyframes spin{0%{-webkit-transform:rotate(0deg)}100%{-webkit-transform:rotate(360deg)}}@keyframes spin{0%{transform:rotate(0deg)}100%{transform:rotate(360deg)}}</style>", nullptr },
{ AC_Text, "binname", nullptr, "%s&ensp;from&ensp;" }, { AC_Element, "c1", "<div style=\"display:inline-block\">", nullptr },
{ AC_Text, "url", nullptr, "%s</div>" }, { AC_Element, "binname", nullptr, nullptr },
{ AC_Element, "progress", "<div id=\"progress\">Updating...&ensp;<meter min=\"0\"></meter></div>", nullptr }, { AC_Element, "c2", "&ensp;from&ensp;", nullptr },
{ AC_Element, "url", "</dv>", 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_loader", "<div class=\"loader\" />", nullptr },
{ AC_Element, "c4", "</span></div></div>", nullptr },
{ AC_Text, "status", nullptr, nullptr }, { AC_Text, "status", nullptr, nullptr },
{ AC_Element, "inprogress", "<script type='text/javascript'>var ws;window.onload=function(){ws=new WebSocket('ws://'+'#wsserver#');ws.onopen=function(e){ws.onmessage=function(e){var pl=e.data.split(',');if(pl[0]=='#p'){var iv=pl[1].split(':');var pb=document.getElementById('progress').getElementsByTagName('meter');pb[0].setAttribute('value',iv[0]);pb[0].setAttribute('max',iv[1]);}else if(pl[0]=='#e'){location.href='" AUTOCONNECT_URI_UPDATE_RESULT "';}};ws.onclose=function(e){console.log('WS close('+e.code+') '+e.reason);};};ws.onerror=function(e){console.log(e);document.getElementById('status').textContent='Connection failed.';};};window.onbeforeunload=function(){ws.close();};</script>", nullptr } { AC_Element, "c5", "<script type=\"text/javascript\">var ws;window.onload=function(){ws=new WebSocket('", nullptr },
{ AC_Element, "wsurl", nullptr, nullptr },
{ AC_Element, "c6", "');ws.onopen=function(){ws.onmessage=function(e){var pl=e.data.split(',');if(pl[0]=='#e'){location.href='/_ac/update_result';}else if(pl[0]=='#p'){incr(pl[1]);}};};ws.onclose=function(e){console.log('WS close('+e.code+')'+e.reason);if(e.code!=1000){document.getElementById('status').textContent='WebSocket connection closed. ('+e.code+')';}};ws.onerror=function(e){if(ws.readyState==1){document.getElementById('status').textContent='WebSocket '+e.type;}};};window.onbeforeunload=function(){ws.close();};", nullptr },
{ AC_Element, "inprogress_meter", "function incr(pv){var iv=pv.split(':');var pb=document.getElementById('progress').getElementsByTagName('meter');pb[0].setAttribute('value',iv[0]);pb[0].setAttribute('max',iv[1]);}", nullptr },
{ AC_Element, "inprogress_loader", "function incr(pv){}", nullptr },
{ AC_Element, "c7", "</script>", nullptr },
}; };
const AutoConnectUpdate::ACPage_t AutoConnectUpdate::_auxProgress PROGMEM = { const AutoConnectUpdate::ACPage_t AutoConnectUpdate::_auxProgress PROGMEM = {
AUTOCONNECT_URI_UPDATE_ACT, "Update", false, AutoConnectUpdate::_elmProgress AUTOCONNECT_URI_UPDATE_ACT, "Update", false, AutoConnectUpdate::_elmProgress

@ -2,8 +2,8 @@
* The default upload handler implementation. * The default upload handler implementation.
* @file AutoConnectUploadImpl.h * @file AutoConnectUploadImpl.h
* @author hieromon@gmail.com * @author hieromon@gmail.com
* @version 0.9.8 * @version 0.9.9
* @date 2019-03-19 * @date 2019-05-14
* @copyright MIT license. * @copyright MIT license.
*/ */
@ -39,6 +39,20 @@ typedef SDFile SDFileT;
#include "AutoConnectDefs.h" #include "AutoConnectDefs.h"
#include "AutoConnectUpload.h" #include "AutoConnectUpload.h"
namespace AutoConnectUtil {
AC_HAS_FUNC(end);
template<typename T>
typename std::enable_if<AutoConnectUtil::has_func_end<T>::value, void>::type end(const T* media) {
media->end();
}
template<typename T>
typename std::enable_if<!AutoConnectUtil::has_func_end<T>::value, void>::type end(const T* media) {
(void)(media);
}
}
/** /**
* Handles the default upload process depending on the upload status. * Handles the default upload process depending on the upload status.
* This handler function supports the status of UPLOAD_FILE_START, * This handler function supports the status of UPLOAD_FILE_START,
@ -133,9 +147,7 @@ class AutoConnectUploadSD : public AutoConnectUploadHandler {
void _close(void) override { void _close(void) override {
if (_file) if (_file)
_file.close(); _file.close();
#if defined(ARDUINO_ARCH_ESP32) || (defined(ARDUINO_ARCH_ESP8266) && (!defined(ARDUINO_ESP8266_RELEASE_2_4_0) && !defined(ARDUINO_ESP8266_RELEASE_2_4_1) && !defined(ARDUINO_ESP8266_RELEASE_2_4_2))) AutoConnectUtil::end<SDClassT>(_media);
_media->end();
#endif
} }
private: private:

Loading…
Cancel
Save