diff --git a/src/AutoConnectDefs.h b/src/AutoConnectDefs.h index b02fc0e..a29ac80 100644 --- a/src/AutoConnectDefs.h +++ b/src/AutoConnectDefs.h @@ -3,7 +3,7 @@ * @file AutoConnectDefs.h * @author hieromon@gmail.com * @version 0.9.9 - * @date 2019-04-30 + * @date 2019-05-14 * @copyright MIT license. */ @@ -200,4 +200,18 @@ // Explicitly avoiding unused warning with token handler of PageBuilder #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 \ +struct has_func_##func { \ + static auto check(...) -> decltype(std::false_type()); \ + template \ + static auto check(U&) -> decltype(static_cast(&U::func), std::true_type()); \ + enum : bool { value = decltype(check(std::declval()))::value }; \ +} + #endif // _AUTOCONNECTDEFS_H_ diff --git a/src/AutoConnectUpdate.cpp b/src/AutoConnectUpdate.cpp index 4f4d8f2..3d1bdea 100644 --- a/src/AutoConnectUpdate.cpp +++ b/src/AutoConnectUpdate.cpp @@ -3,11 +3,12 @@ * @file AutoConnectUpdate.cpp * @author hieromon@gmail.com * @version 0.9.9 - * @date 2019-05-03 + * @date 2019-05-14 * @copyright MIT license. */ #include +#include #include "AutoConnectUpdate.h" #include "AutoConnectUpdatePage.h" #include "AutoConnectJsonDefs.h" @@ -65,6 +66,40 @@ * 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 std::enable_if::value, AutoConnectUpdate::AC_UPDATEDIALOG_t>::type onProgress(const T& updater, std::function fn) { + updater.onProgress(fn); + AC_DBG("Callback enabled\n"); + return AutoConnectUpdate::UPDATEDIALOG_METER; +} + +template +typename std::enable_if::value, AutoConnectUpdate::AC_UPDATEDIALOG_t>::type onProgress(const T& updater, std::function fn) { + (void)(updater); + (void)(fn); + AC_DBG("Callback disabled\n"); + return AutoConnectUpdate::UPDATEDIALOG_LOADER; +} +} + /** * A destructor. Release the update processing dialogue page generated * as AutoConnectAux. @@ -108,9 +143,29 @@ void AutoConnectUpdate::attach(AutoConnect& portal) { _status = UPDATE_IDLE; -#ifdef ARDUINO_ARCH_ESP32 - Update.onProgress(std::bind(&AutoConnectUpdate::_inProgress, this, std::placeholders::_1, std::placeholders::_2)); -#endif + // Register the callback to inform the update progress + _dialog = AutoConnectUtil::onProgress(Update, std::bind(&AutoConnectUpdate::_inProgress, this, std::placeholders::_1, std::placeholders::_2)); + // 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 portal._update.reset(this); @@ -161,8 +216,22 @@ void AutoConnectUpdate::handleUpdate(void) { // Evaluate the processing status of AutoConnectUpdate and // execute it accordingly. It is only this process point that // requests update processing. - if (_status == UPDATE_START) - update(); + if (_status == UPDATE_START) { + _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) { AC_DBG("Restart on %s updated...\n", _binName.c_str()); ESP.restart(); @@ -181,10 +250,6 @@ void AutoConnectUpdate::handleUpdate(void) { * @return AC_UPDATESTATUS_t */ AC_UPDATESTATUS_t AutoConnectUpdate::update(void) { - // Crawl queued requests. - if (_ws) - _ws->loop(); - // Start update String uriBin = uri + '/' + _binName; if (_binName.length()) { @@ -198,6 +263,7 @@ AC_UPDATESTATUS_t AutoConnectUpdate::update(void) { case HTTP_UPDATE_FAILED: _status = UPDATE_FAIL; AC_DBG_DUMB(" %s\n", getLastErrorString().c_str()); + AC_DBG("update returns HTTP_UPDATE_FAILED\n"); break; case HTTP_UPDATE_NO_UPDATES: _status = UPDATE_IDLE; @@ -210,11 +276,7 @@ AC_UPDATESTATUS_t AutoConnectUpdate::update(void) { } _WiFiClient.reset(nullptr); // Request the client to close the WebSocket. - if (_ws) { - String cmdClose = String("#e"); - _ws->sendTXT(_wsClient, cmdClose); - _ws->loop(); - } + _ws->sendTXT(_wsClient, "#e", 2); } else { 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) { AC_UNUSED(args); // launch the WebSocket server - WebSocketsServer* ws = new WebSocketsServer(AUTOCONNECT_WEBSOCKETPORT); - if (ws) { - ws->begin(); - ws->onEvent(std::bind(&AutoConnectUpdate::_wsEvent, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4)); + _ws.reset(new WebSocketsServer(AUTOCONNECT_WEBSOCKETPORT)); + if (_ws) { + _ws->begin(); + _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"); - } - _ws.reset(ws); // Constructs the dialog page. - AutoConnectText& binName = progress.getElement(String(F("binname"))); + AutoConnectElement* binName = progress.getElement(String(F("binname"))); _binName = _catalog->getElement(String(F("firmwares"))).value(); - binName.value = _binName; - AutoConnectText& url = progress.getElement(String("url")); - url.value = host + ':' + port; - AutoConnectElement& inprogress = progress.getElement(String(F("inprogress"))); - String js = inprogress.value; - js.replace(String(F("#wsserver#")), WiFi.localIP().toString() + ':' + AUTOCONNECT_WEBSOCKETPORT); - inprogress.value = js; + binName->value = _binName; + AutoConnectElement* url = progress.getElement(String("url")); + 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()); _status = UPDATE_START; return String(""); } @@ -412,6 +471,11 @@ String AutoConnectUpdate::_onResult(AutoConnectAux& result, PageArgument& args) String resColor; bool restart = false; + if (_ws) { + _ws->close(); + _ws.reset(nullptr); + } + switch (_status) { case UPDATE_SUCCESS: resForm = String(F(" sucessfully updated. restart...")); @@ -443,16 +507,11 @@ void AutoConnectUpdate::_inProgress(size_t amount, size_t size) { _binSize = size; String payload = "#p," + String(_amount) + ':' + String(_binSize); _ws->sendTXT(_wsClient, payload); - _ws->loop(); } } 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) _wsClient = client; - else if (event == WStype_DISCONNECTED) { - if (client == _wsClient) - _ws.reset(nullptr); - } } diff --git a/src/AutoConnectUpdate.h b/src/AutoConnectUpdate.h index 30894d5..879d908 100644 --- a/src/AutoConnectUpdate.h +++ b/src/AutoConnectUpdate.h @@ -22,7 +22,7 @@ * @file AutoConnectUpdate.h * @author hieromon@gmail.com * @version 0.9.9 - * @date 2019-05-03 + * @date 2019-05-14 * @copyright MIT license. */ @@ -92,6 +92,12 @@ class AutoConnectUpdate : public HTTPUpdateClass { 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 */ + // + typedef enum { + UPDATEDIALOG_LOADER, + UPDATEDIALOG_METER + } AC_UPDATEDIALOG_t; + protected: // Attribute definition of the element to be placed on the update page. typedef struct { @@ -129,7 +135,8 @@ class AutoConnectUpdate : public HTTPUpdateClass { size_t _binSize; /**< Updater binary size */ 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 */ unsigned long _period; /**< Duration of WiFiClient holding for the connection with the update server */ std::unique_ptr _WiFiClient; /**< Provide to HTTPUpdate class */ diff --git a/src/AutoConnectUpdatePage.h b/src/AutoConnectUpdatePage.h index b0a4527..663f578 100644 --- a/src/AutoConnectUpdatePage.h +++ b/src/AutoConnectUpdatePage.h @@ -4,7 +4,7 @@ * @file AutoConnectUpdatePage.h * @author hieromon@gmail.com * @version 0.9.9 - * @date 2019-05-04 + * @date 2019-05-14 * @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 // update process. const AutoConnectUpdate::ACElementProp_t AutoConnectUpdate::_elmProgress[] PROGMEM = { - { AC_Element, "caption", "
" }, - { AC_Element, "progress", "
Updating... 
", nullptr }, + { AC_Element, "loader", "", nullptr }, + { AC_Element, "c1", "
", nullptr }, + { AC_Element, "binname", nullptr, nullptr }, + { AC_Element, "c2", " from ", nullptr }, + { AC_Element, "url", "", nullptr }, + { AC_Element, "c3", "
Updating...", nullptr }, + { AC_Element, "progress_meter", "", nullptr }, + { AC_Element, "progress_loader", "
", nullptr }, + { AC_Element, "c4", "
", nullptr }, { AC_Text, "status", nullptr, nullptr }, - { AC_Element, "inprogress", "", nullptr } + { AC_Element, "c5", "", nullptr }, }; const AutoConnectUpdate::ACPage_t AutoConnectUpdate::_auxProgress PROGMEM = { AUTOCONNECT_URI_UPDATE_ACT, "Update", false, AutoConnectUpdate::_elmProgress diff --git a/src/AutoConnectUploadImpl.h b/src/AutoConnectUploadImpl.h index baf935e..2073e61 100644 --- a/src/AutoConnectUploadImpl.h +++ b/src/AutoConnectUploadImpl.h @@ -2,8 +2,8 @@ * The default upload handler implementation. * @file AutoConnectUploadImpl.h * @author hieromon@gmail.com - * @version 0.9.8 - * @date 2019-03-19 + * @version 0.9.9 + * @date 2019-05-14 * @copyright MIT license. */ @@ -39,6 +39,20 @@ typedef SDFile SDFileT; #include "AutoConnectDefs.h" #include "AutoConnectUpload.h" +namespace AutoConnectUtil { +AC_HAS_FUNC(end); + +template +typename std::enable_if::value, void>::type end(const T* media) { + media->end(); +} + +template +typename std::enable_if::value, void>::type end(const T* media) { + (void)(media); +} +} + /** * Handles the default upload process depending on the upload status. * This handler function supports the status of UPLOAD_FILE_START, @@ -133,9 +147,7 @@ class AutoConnectUploadSD : public AutoConnectUploadHandler { void _close(void) override { if (_file) _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))) - _media->end(); -#endif + AutoConnectUtil::end(_media); } private: