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
* @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<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_

@ -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 <functional>
#include <type_traits>
#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 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
* 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<UpdateVariedClass>(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<AutoConnectText>(String(F("binname")));
AutoConnectElement* binName = progress.getElement(String(F("binname")));
_binName = _catalog->getElement<AutoConnectRadio>(String(F("firmwares"))).value();
binName.value = _binName;
AutoConnectText& url = progress.getElement<AutoConnectText>(String("url"));
url.value = host + ':' + port;
AutoConnectElement& inprogress = progress.getElement<AutoConnectElement>(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);
}
}

@ -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> _WiFiClient; /**< Provide to HTTPUpdate class */

@ -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", "<div style=\"display:inline-block\"", nullptr },
{ AC_Text, "binname", nullptr, "%s&ensp;from&ensp;" },
{ AC_Text, "url", nullptr, "%s</div>" },
{ AC_Element, "progress", "<div id=\"progress\">Updating...&ensp;<meter min=\"0\"></meter></div>", 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_Element, "c1", "<div style=\"display:inline-block\">", nullptr },
{ AC_Element, "binname", nullptr, 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_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 = {
AUTOCONNECT_URI_UPDATE_ACT, "Update", false, AutoConnectUpdate::_elmProgress

@ -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 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.
* 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<SDClassT>(_media);
}
private:

Loading…
Cancel
Save