parent
62b53a31f7
commit
52e03c81fd
@ -0,0 +1,213 @@ |
||||
/**
|
||||
* AutoConnectOTA class implementation. |
||||
* @file AutoConnectOTA.cpp |
||||
* @author hieromon@gmail.com |
||||
* @version 1.1.5 |
||||
* @date 2020-04-09 |
||||
* @copyright MIT license. |
||||
*/ |
||||
|
||||
#include <functional> |
||||
#include "AutoConnectOTA.h" |
||||
#include "AutoConnectOTAPage.h" |
||||
#include <StreamString.h> |
||||
|
||||
/**
|
||||
* A destructor. Release the OTA operation pages. |
||||
*/ |
||||
AutoConnectOTA::~AutoConnectOTA() { |
||||
_auxUpdate.reset(nullptr); |
||||
_auxResult.reset(nullptr); |
||||
} |
||||
|
||||
/**
|
||||
* Attach the AutoConnectOTA to hosted AutoConnect which constitutes |
||||
* the update process. This function creates an OTA operation page as |
||||
* AutoConnectAux instance and allows it to receive binary updates. |
||||
* @param portal A reference of AutoConnect |
||||
*/ |
||||
void AutoConnectOTA::attach(AutoConnect& portal) { |
||||
AutoConnectAux* updatePage; |
||||
|
||||
updatePage = new AutoConnectAux(String(FPSTR(_pageUpdate.uri)), String(FPSTR(_pageUpdate.title)), _pageUpdate.menu); |
||||
_buildAux(updatePage, &_pageUpdate, lengthOf(_elmUpdate)); |
||||
_auxUpdate.reset(updatePage); |
||||
|
||||
updatePage = new AutoConnectAux(String(FPSTR(_pageResult.uri)), String(FPSTR(_pageResult.title)), _pageResult.menu); |
||||
_buildAux(updatePage, &_pageResult, lengthOf(_elmResult)); |
||||
_auxResult.reset(updatePage); |
||||
|
||||
_auxResult->on(std::bind(&AutoConnectOTA::_updated, this, std::placeholders::_1, std::placeholders::_2)); |
||||
_auxResult->onUpload<AutoConnectOTA>(*this); |
||||
|
||||
portal.join(*_auxUpdate.get()); |
||||
portal.join(*_auxResult.get()); |
||||
} |
||||
|
||||
/**
|
||||
* Create the update operation pages using a predefined page structure |
||||
* with two structures as ACPage_t and ACElementProp_t which describe |
||||
* for AutoConnectAux configuration. |
||||
* This function receives instantiated AutoConnectAux, instantiates |
||||
* defined AutoConnectElements by ACPage_t, and configures it into |
||||
* received AutoConnectAux. |
||||
* @param aux An instantiated AutoConnectAux that will configure according to ACPage_t. |
||||
* @param page Pre-defined ACPage_t |
||||
* @param elementNum Number of AutoConnectElements to configure.
|
||||
*/ |
||||
void AutoConnectOTA::_buildAux(AutoConnectAux* aux, const AutoConnectOTA::ACPage_t* page, const size_t elementNum) { |
||||
for (size_t n = 0; n < elementNum; n++) { |
||||
if (page->element[n].type == AC_Button) { |
||||
AutoConnectButton* element = new AutoConnectButton; |
||||
element->name = String(FPSTR(page->element[n].name)); |
||||
if (page->element[n].value) |
||||
element->value = String(FPSTR(page->element[n].value)); |
||||
if (page->element[n].peculiar) |
||||
element->action = String(FPSTR(page->element[n].peculiar)); |
||||
aux->add(reinterpret_cast<AutoConnectElement&>(*element)); |
||||
} |
||||
else if (page->element[n].type == AC_Element) { |
||||
AutoConnectElement* element = new AutoConnectElement; |
||||
element->name = String(FPSTR(page->element[n].name)); |
||||
if (page->element[n].value) |
||||
element->value = String(FPSTR(page->element[n].value)); |
||||
aux->add(reinterpret_cast<AutoConnectElement&>(*element)); |
||||
} |
||||
else if (page->element[n].type == AC_File) { |
||||
AutoConnectFile* element = new AutoConnectFile; |
||||
element->name = String(FPSTR(page->element[n].name)); |
||||
element->label = String(FPSTR(page->element[n].peculiar)); |
||||
element->store = ACFile_t::AC_File_Extern; |
||||
aux->add(reinterpret_cast<AutoConnectElement&>(*element)); |
||||
} |
||||
else if (page->element[n].type == AC_Style) { |
||||
AutoConnectStyle* element = new AutoConnectStyle; |
||||
element->name = String(FPSTR(page->element[n].name)); |
||||
if (page->element[n].value) |
||||
element->value = String(FPSTR(page->element[n].value)); |
||||
aux->add(reinterpret_cast<AutoConnectElement&>(*element)); |
||||
} |
||||
else if (page->element[n].type == AC_Text) { |
||||
AutoConnectText* element = new AutoConnectText; |
||||
element->name = String(FPSTR(page->element[n].name)); |
||||
if (page->element[n].value) |
||||
element->value = String(FPSTR(page->element[n].value)); |
||||
if (page->element[n].peculiar) |
||||
element->style = String(FPSTR(page->element[n].peculiar)); |
||||
aux->add(reinterpret_cast<AutoConnectText&>(*element)); |
||||
} |
||||
} |
||||
} |
||||
|
||||
/**
|
||||
* Check the space needed for the update |
||||
* This function overrides AutoConnectUploadHandler::_open. |
||||
* @param filename An updater bin file name |
||||
* @param mode File access mode, but it is not be used. |
||||
* @return true Ready for update |
||||
* @return false Not enough FLASH space to update. |
||||
*/ |
||||
bool AutoConnectOTA::_open(const char* filename, const char* mode) { |
||||
AC_UNUSED(mode); |
||||
_binName = String(strchr(filename, '/') + sizeof(char)); |
||||
WiFiUDP::stopAll(); |
||||
uint32_t maxSketchSpace = (ESP.getFreeSketchSpace() - 0x1000) & 0xFFFFF000; |
||||
// It only supports FLASH as a sketch area for updating.
|
||||
if (Update.begin(maxSketchSpace, U_FLASH, _tickerPort, _tickerOn)) { |
||||
_status = OTA_START; |
||||
AC_DBG("%s updating start\n", filename); |
||||
return true; |
||||
} |
||||
_setError(); |
||||
return false; |
||||
} |
||||
|
||||
/**
|
||||
* Writes received updater to the flash. |
||||
* This function overrides AutoConnectUploadHandler::_write. |
||||
* @param buf Buffer address where received update file was stored. |
||||
* @param size Size to be written. |
||||
* @return the amount written |
||||
*/ |
||||
size_t AutoConnectOTA::_write(const uint8_t *buf, const size_t size) { |
||||
size_t wsz = 0; |
||||
if (!_err.length()) { |
||||
_status = OTA_PROGRESS; |
||||
wsz = Update.write(const_cast<uint8_t*>(buf), size); |
||||
if (wsz != size) |
||||
_setError(); |
||||
} |
||||
return wsz; |
||||
} |
||||
|
||||
/**
|
||||
* All bytes are written, this call writes the config to reboot. |
||||
* If there is an error this will clear everything. |
||||
* This function overrides AutoConnectUploadHandler::_close. |
||||
* @param status Updater binary upload completion status. |
||||
*/ |
||||
void AutoConnectOTA::_close(const HTTPUploadStatus status) { |
||||
if (!_err.length()) { |
||||
AC_DBG("OTA update "); |
||||
if (status == UPLOAD_FILE_END) { |
||||
if (Update.end(true)) { |
||||
_status = OTA_SUCCESS; |
||||
AC_DBG_DUMB("succeeds, turn to reboot.\n"); |
||||
} |
||||
else |
||||
_setError(); |
||||
} |
||||
else { |
||||
Update.end(false); |
||||
AC_DBG_DUMB(" aborted\n"); |
||||
} |
||||
} |
||||
} |
||||
|
||||
/**
|
||||
* Callback of the update operation page as AutoConnectAux. |
||||
* Reflect the flash result of Update class to the page. |
||||
* @param result Upload post-process page |
||||
* @param args Unused |
||||
* @return none |
||||
*/ |
||||
String AutoConnectOTA::_updated(AutoConnectAux& result, PageArgument& args) { |
||||
AC_UNUSED(args); |
||||
PGM_P stColor; |
||||
String st; |
||||
|
||||
// Build an updating result caption.
|
||||
// Change the color of the bin name depending on the result of the update.
|
||||
if (_status == OTA_SUCCESS) { |
||||
st = String(F(AUTOCONNECT_TEXT_OTASUCCESS)); |
||||
stColor = PSTR("3d7e9a"); |
||||
// Notify to the handleClient of loop() thread that it can reboot.
|
||||
_status = OTA_RIP; |
||||
} |
||||
else { |
||||
st = String(F(AUTOCONNECT_TEXT_OTAFAILURE)) + _err; |
||||
stColor = PSTR("e66157"); |
||||
} |
||||
result["bin"].as<AutoConnectText>().value = _binName; |
||||
result["bin"].as<AutoConnectText>().style += String(stColor); |
||||
result["result"].as<AutoConnectText>().value = st; |
||||
|
||||
// The rc element on the result page has an update return code
|
||||
// which is a hidden field.
|
||||
// By setting the error code of the Update class into its field,
|
||||
// the homepage after reboot will automatically GET by the JavaScript
|
||||
// on the result page.
|
||||
result["rc"].value.replace("%d", String(Update.getError())); |
||||
return String(""); |
||||
} |
||||
|
||||
/**
|
||||
* Save the last error |
||||
*/ |
||||
void AutoConnectOTA::_setError(void) { |
||||
StreamString eStr; |
||||
Update.printError(eStr); |
||||
_err = String(eStr.c_str()); |
||||
AC_DBG("%s\n", _err.c_str()); |
||||
_status = OTA_FAIL; |
||||
} |
@ -0,0 +1,86 @@ |
||||
/**
|
||||
* Declaration of AutoConnectOTA class. |
||||
* The AutoConnecOTA class is a class for web updating a Sketch binary |
||||
* via OTA and implements with an AutoConnectAux page handler that |
||||
* inherits from AutoConnectUploadHandler. |
||||
* By overriding the _write function of AutoConnectUploadHandler to |
||||
* write the executable binary using the Update class, it can update |
||||
* the module firmware in synchronization with the upload of the sketch |
||||
* binary file. |
||||
* @file AutoConnectOTA.h |
||||
* @author hieromon@gmail.com |
||||
* @version 1.1.5 |
||||
* @date 2020-04-09 |
||||
* @copyright MIT license. |
||||
*/ |
||||
|
||||
#ifndef _AUTOCONNECTOTA_H_ |
||||
#define _AUTOCONNECTOTA_H_ |
||||
|
||||
#include <memory> |
||||
#include "AutoConnect.h" |
||||
#include "AutoConnectUpload.h" |
||||
|
||||
class AutoConnectOTA : public AutoConnectUploadHandler { |
||||
public: |
||||
// Updating process status
|
||||
typedef enum { |
||||
OTA_IDLE, /**< Update process has not started */ |
||||
OTA_START, /**< Update process has started */ |
||||
OTA_PROGRESS, /**< Update process in progress */ |
||||
OTA_SUCCESS, /**< A binary updater has uploaded fine */ |
||||
OTA_RIP, /**< Ready for module restart */ |
||||
OTA_FAIL /**< Failed to save binary updater by Update class */ |
||||
} AC_OTAStatus_t; |
||||
|
||||
AutoConnectOTA() : _status(OTA_IDLE), _tickerPort(-1), _tickerOn(LOW) {} |
||||
~AutoConnectOTA(); |
||||
void attach(AutoConnect& portal); |
||||
String error(void) const { return _err; } |
||||
AC_OTAStatus_t status(void) const { return _status; } |
||||
void setTicker(uint8_t pin, uint8_t on) { _tickerPort = pin, _tickerOn = on; } |
||||
|
||||
protected: |
||||
// Attribute definition of the element to be placed on the update page.
|
||||
typedef struct { |
||||
const ACElement_t type; |
||||
const char* name; /**< Name to assign to AutoConnectElement */ |
||||
const char* value; /**< Value owned by an element */ |
||||
const char* peculiar; /**< Specific ornamentation for the element */ |
||||
} ACElementProp_t; |
||||
|
||||
// Attributes to treat included update pages as AutoConnectAux.
|
||||
typedef struct { |
||||
const char* uri; /**< URI for the page */ |
||||
const char* title; /**< Menu title of update page */ |
||||
const bool menu; /**< Whether to display in menu */ |
||||
const ACElementProp_t* element; |
||||
} ACPage_t; |
||||
|
||||
template<typename T, size_t N> constexpr |
||||
size_t lengthOf(T(&)[N]) noexcept { return N; } |
||||
void _buildAux(AutoConnectAux* aux, const AutoConnectOTA::ACPage_t* page, const size_t elementNum); |
||||
bool _open(const char* filename, const char* mode) override; |
||||
size_t _write(const uint8_t *buf, const size_t size) override; |
||||
void _close(const HTTPUploadStatus status) override; |
||||
String _updated(AutoConnectAux& result, PageArgument& args); |
||||
|
||||
std::unique_ptr<AutoConnectAux> _auxUpdate; /**< An update operation page */ |
||||
std::unique_ptr<AutoConnectAux> _auxResult; /**< An update result page */ |
||||
|
||||
private: |
||||
void _setError(void); |
||||
|
||||
AC_OTAStatus_t _status; /**< Status for update progress */ |
||||
uint8_t _tickerPort; /**< GPIO for flicker */ |
||||
uint8_t _tickerOn; /**< A signal for flicker turn on */ |
||||
String _binName; /**< An updater file name */ |
||||
String _err; /**< Occurred error stamp */ |
||||
|
||||
static const ACPage_t _pageUpdate PROGMEM; |
||||
static const ACElementProp_t _elmUpdate[] PROGMEM; |
||||
static const ACPage_t _pageResult PROGMEM; |
||||
static const ACElementProp_t _elmResult[] PROGMEM; |
||||
}; |
||||
|
||||
#endif // !_AUTOCONNECTOTA_H_
|
@ -0,0 +1,39 @@ |
||||
/**
|
||||
* Define pages to operate updates using the AutoConnectUpdate class. |
||||
* @file AutoConnectOTAPage.h |
||||
* @author hieromon@gmail.com |
||||
* @version 1.1.5 |
||||
* @date 2020-04-09 |
||||
* @copyright MIT license. |
||||
*/ |
||||
|
||||
#ifndef _AUTOCONNECTOTAPAGE_H_ |
||||
#define _AUTOCONNECTOTAPAGE_H_ |
||||
|
||||
const AutoConnectOTA::ACElementProp_t AutoConnectOTA::_elmUpdate[] PROGMEM = { |
||||
{ AC_Style, "s_rc", ".s_rc{display:none}", nullptr }, |
||||
{ AC_Text, "caption", "<h3>" AUTOCONNECT_TEXT_UPDATINGFIRMWARE "<h3>", nullptr }, |
||||
{ AC_File, "bin", nullptr, AUTOCONNECT_TEXT_SELECTFIRMWARE }, |
||||
{ AC_Button, "update", AUTOCONNECT_BUTTONLABEL_UPDATE, "_upd(this, 'bin', '" AUTOCONNECT_URI_UPDATE_ACT "')" }, |
||||
{ AC_Element, "js", "<script type=\"text/javascript\">function _upd(e,t,n){var r=document.getElementById(t);if(r.files.length>0){par=e.parentNode,par.removeChild(e),pb=document.createElement(\"progress\"),pb.setAttribute(\"id\",\"pb\"),pb.setAttribute(\"style\",\"margin-top:1.0em\"),pb.setAttribute(\"value\",\"0\"),pb.setAttribute(\"max\",r.files[0].size),par.appendChild(pb);var p=new FormData(_bu(n)),o=new XMLHttpRequest;o.upload.addEventListener(\"progress\",function(e){pb.setAttribute(\"value\",e.loaded)},!1),o.addEventListener(\"load\",function(){document.body.innerHTML=o.response.body.innerHTML,\"0\"==document.getElementById(\"rc\").innerText&&setTimeout(function(){location.href=\"" AUTOCONNECT_HOMEURI "\"}," AUTOCONNECT_STRING_DEPLOY(AUTOCONNECT_UPDATE_WAITFORREBOOT) ")},!1),o.open(\"POST\",n),o.responseType=\"document\",o.send(p)}}var par,pb;</script>", nullptr } |
||||
}; |
||||
|
||||
// The definition of the OTA update operation page, which will be located to AUTOCONNECT_URI_UPDATE.
|
||||
const AutoConnectOTA::ACPage_t AutoConnectOTA::_pageUpdate PROGMEM = { |
||||
AUTOCONNECT_URI_UPDATE, AUTOCONNECT_MENULABEL_UPDATE, true, AutoConnectOTA::_elmUpdate |
||||
}; |
||||
|
||||
const AutoConnectOTA::ACElementProp_t AutoConnectOTA::_elmResult[] PROGMEM = { |
||||
{ AC_Text, "bin", nullptr, "margin-bottom:0.5em;font-size:1.2em;font-weight:bold;color:#" }, |
||||
{ AC_Text, "result", nullptr, nullptr }, |
||||
{ AC_Element, "rc", "<div class=\"s_rc\" id=\"rc\">%d</div>", nullptr } |
||||
}; |
||||
|
||||
// The definition of the OTA update result display page.
|
||||
// This page is assigned to AUTOCONNECT_URI_UPDATE_ACT, but the actual
|
||||
// HTML document is dynamically rewritten on AUTOCONNECT_URI_UPDATE page
|
||||
// by the JavaScript function included in the _pageUpdate AutoConnectAux.
|
||||
const AutoConnectOTA::ACPage_t AutoConnectOTA::_pageResult PROGMEM = { |
||||
AUTOCONNECT_URI_UPDATE_ACT, AUTOCONNECT_MENULABEL_UPDATE, false, AutoConnectOTA::_elmResult |
||||
}; |
||||
#endif // !_AUTOCONNECTOTAPAGE_H_
|
Loading…
Reference in new issue