You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
AutoConnect/src/AutoConnectAux.cpp

975 lines
33 KiB

/**
* Implementation of AutoConnectAux class.
* @file AutoConnectAuxBasisImpl.h
* @author hieromon@gmail.com
* @version 1.1.1
* @date 2019-10-17
* @copyright MIT license.
*/
#include <algorithm>
#include "AutoConnect.h"
#include "AutoConnectAux.h"
#include "AutoConnectAuxImpl.h"
#include "AutoConnectUploadImpl.h"
#include "AutoConnectElementBasisImpl.h"
#ifdef AUTOCONNECT_USE_JSON
#include "AutoConnectElementJsonImpl.h"
#endif
/**
* Template for auxiliary page composed with AutoConnectAux of user sketch.
* The structure of the auxiliary page depends on this template for
* the purpose to be incorporated into the AutoConnect Menu.
* The page element implemented by AutoConnectElement is placed at the
* position of {{AUX_ELEMENT}} token. This token is contained in a
* <div> block with a class defined in 'base-panel' and is held by a
* <form> element with an ID '_aux'.
* The JavaScript that named 'sa' at the end of the template determines
* the behavior of AutoConnectSubmit.
*/
const char AutoConnectAux::_PAGE_AUX[] PROGMEM = {
"{{HEAD}}"
"<title>{{AUX_TITLE}}</title>"
"<style type=\"text/css\">"
"{{CSS_BASE}}"
"{{CSS_UL}}"
"{{CSS_INPUT_BUTTON}}"
"{{CSS_INPUT_TEXT}}"
"{{CSS_LUXBAR}}"
"{{AUX_CSS}}"
"</style>"
"</head>"
"<body style=\"padding-top:58px;\">"
"<div class=\"container\">"
"{{MENU_PRE}}"
"{{MENU_AUX}}"
"{{MENU_POST}}"
"<div class=\"base-panel\"><div class=\"aux-page\">"
"<form id='_aux' method=\"post\" onsubmit=\"return false;\" {{ENC_TYPE}}>"
"<ul class=\"noorder\">"
"{{AUX_ELEMENT}}"
"</ul>"
"</form>"
"</div></div>"
"</div>"
"<script>"
"function _bu(url) {"
"var uri=document.createElement('input');"
"uri.setAttribute('type','hidden');"
"uri.setAttribute('name','" AUTOCONNECT_AUXURI_PARAM "');"
"uri.setAttribute('value','{{AUX_URI}}');"
"var fm=document.getElementById('_aux');"
"fm.appendChild(uri);"
"fm.action=url;"
"return fm;"
"}"
"function _sa(url) {"
"_bu(url).submit();"
"}"
"</script>"
"</body>"
"</html>"
};
/**
* Destructs container of AutoConnectElement and release a unique
* pointer of AutoConnect instance.
*/
AutoConnectAux::~AutoConnectAux() {
_addonElm.clear();
_addonElm.swap(_addonElm);
}
/**
* Returns a null element as static storage.
* This static element is referred by invalid JSON data.
* @return A reference of a static element defined by name as null.
*/
AutoConnectElement& AutoConnectAux::_nullElement() {
static AutoConnectElement nullElement("", "");
return nullElement;
}
/**
* Add an AutoConnectElement
* @param addon A reference of AutoConnectElement.
*/
void AutoConnectAux::add(AutoConnectElement& addon) {
_addonElm.push_back(addon);
AC_DBG("%s placed on %s\n", addon.name.length() ? addon.name.c_str() : "*noname", uri());
}
/**
* Add an AutoConnectElement vector container to the AutoConnectAux page.
* @param addons AutoConnectElementVT collection.
*/
void AutoConnectAux::add(AutoConnectElementVT addons) {
for (AutoConnectElement& element : addons)
add(element);
}
/**
* Parses the query parameters contained in the http request and fetches
* the value of AutoConnectElements carried by AutoConnectAux.
*/
void AutoConnectAux::fetchElement(void) {
WebServerClass* _webServer = _ac->_webServer.get();
if (_webServer->hasArg(String(F(AUTOCONNECT_AUXURI_PARAM)))) {
_ac->_auxUri = _webServer->arg(String(F(AUTOCONNECT_AUXURI_PARAM)));
_ac->_auxUri.replace("&#47;", "/");
AC_DBG("fetch %s", _ac->_auxUri.c_str());
AutoConnectAux* aux = _ac->_aux;
while (aux) {
if (aux->_uriStr == _ac->_auxUri) {
// Save the value owned by each element contained in the POST body
// of a current HTTP request to AutoConnectElements.
aux->_storeElements(_webServer);
break;
}
aux = aux->_next;
}
}
}
/**
* Get already registered AutoConnectElement.
* @param name Element name
* @return A pointer to the registered AutoConnectElement.
*/
AutoConnectElement* AutoConnectAux::getElement(const String& name) {
for (AutoConnectElement& elm : _addonElm)
if (elm.name.equalsIgnoreCase(name))
return &elm;
AC_DBG("Element<%s> not registered\n", name.c_str());
return nullptr;
}
/**
* Validate all AutoConnectInputs value.
* @return true Validation successfull
* @return false Some elements failed validation.
*/
bool AutoConnectAux::isValid(void) const {
bool rc = true;
for (AutoConnectElement& elm : _addonElm)
if (elm.typeOf() == AC_Input) {
AutoConnectInput& elmInput = reinterpret_cast<AutoConnectInput&>(elm);
rc &= elmInput.isValid();
}
return rc;
}
/**
* Releases the AutoConnectElements with the specified name from
* the AutoConnectAux page. Releases all AutoConnectElements with
* the same name in AutoConnectAux.
* @param name
* @return true The specified AutoConnectElements have been released.
* @return false The specified AutoConnectElement not found in AutoConnectAux.
*/
bool AutoConnectAux::release(const String& name) {
auto itr = std::remove_if(_addonElm.begin(), _addonElm.end(),
[&](std::reference_wrapper<AutoConnectElement> const elm) {
return elm.get().name.equalsIgnoreCase(name);
});
return _addonElm.erase(itr, _addonElm.end()) != _addonElm.end();
}
/**
* Set the value to specified element.
* @param name A string of element name to set the value.
* @param value Setting value. (String)
* @return true The value was set.
* @return false An element specified name is not registered,
* or its element value does not match storage type.
*/
bool AutoConnectAux::setElementValue(const String& name, const String value) {
AutoConnectElement* elm = getElement(name);
if (elm) {
if (elm->typeOf() == AC_Select) {
AutoConnectSelect* elmSelect = reinterpret_cast<AutoConnectSelect*>(elm);
elmSelect->select(value);
}
else {
if (elm->typeOf() == AC_Checkbox) {
if (value == "checked") {
AutoConnectCheckbox* elmCheckbox = reinterpret_cast<AutoConnectCheckbox*>(elm);
elmCheckbox->checked = true;
}
}
else if (elm->typeOf() == AC_Radio) {
AutoConnectRadio* elmRadio = reinterpret_cast<AutoConnectRadio*>(elm);
elmRadio->check(value);
}
else
elm->value = value;
return true;
}
}
return false;
}
/**
* Set the value to specified element.
* @param name A string of element name to set the value.
* @param value Setting value. (String)
* @return true The value was set.
* @return false An element specified name is not registered,
* or its element value must be array.
*/
bool AutoConnectAux::setElementValue(const String& name, std::vector<String> const& values) {
bool rc = false;
AutoConnectElement* elm = getElement(name);
if (elm) {
switch (elm->typeOf()) {
case AC_Radio: {
AutoConnectRadio* elmRadio = reinterpret_cast<AutoConnectRadio*>(elm);
elmRadio->empty();
for (String v : values)
elmRadio->add(v);
rc = true;
break;
}
case AC_Select: {
AutoConnectSelect* elmSelect = reinterpret_cast<AutoConnectSelect*>(elm);
elmSelect->empty();
for (String o : values)
elmSelect->add(o);
rc = true;
break;
}
default: {
AC_DBG("Element<%s> value type mismatch\n", name.c_str());
break;
}
}
}
return rc;
}
/**
* The upload function that overrides the RequestHandler class
* attached with ESP8266WebServer.
* This function invokes the upload handler registered by the onUpload
* function which will be implemented by the user sketch.
*/
void AutoConnectAux::upload(const String& requestUri, const HTTPUpload& upload) {
if (upload.status == UPLOAD_FILE_START) {
AC_DBG("%s requests upload to %s\n", requestUri.c_str(), _uriStr.c_str());
// Selects a valid upload handler before uploading starts.
// Identify AutoConnectFile with the current upload request and
// save the value and mimeType attributes.
AC_DBG("ACFile %s ", upload.name.c_str());
String logContext = "missing";
AutoConnectElementVT addons;
AutoConnectAux* aux = _ac->_aux;
while (aux) {
if (aux->_uriStr == requestUri) {
addons = aux->_addonElm;
break;
}
aux = aux->_next;
}
_currentUpload = nullptr;
for (AutoConnectElement& elm : addons) {
if (elm.typeOf() == AC_File) {
_currentUpload = reinterpret_cast<AutoConnectFile*>(&elm);
// Reset previous value
_currentUpload->value = String("");
_currentUpload->mimeType = String("");
_currentUpload->size = 0;
// Overwrite with current upload request
if (upload.name.equalsIgnoreCase(_currentUpload->name)) {
_currentUpload->value = upload.filename;
_currentUpload->mimeType = upload.type;
logContext = "accepted " + _currentUpload->value;
break;
}
}
}
AC_DBG_DUMB("%s, handler ", logContext.c_str());
// If the current upload request is AutoConnectFile without default
// AutoConnectUpload (i.e. the store attribute is AC_File_Ex),
// enable the user-owned upload handler activated by the onUpload.
_upload = nullptr;
if (_currentUpload)
if (_currentUpload->attach(_currentUpload->store)) {
_upload = std::bind(&AutoConnectUploadHandler::upload, _currentUpload->upload(), std::placeholders::_1, std::placeholders::_2);
AC_DBG_DUMB("attached(%d)\n", (int)_currentUpload->store);
}
if (!_upload) {
if (_uploadHandler) {
_upload = _uploadHandler;
AC_DBG_DUMB("enabled\n");
}
else {
AC_DBG_DUMB("missing\n");
}
}
}
// Invokes upload handler
if (_upload) {
_upload(requestUri, upload);
if (_currentUpload)
_currentUpload->size = upload.totalSize;
// Upload ended, purge handler
if (upload.status == UPLOAD_FILE_END || upload.status == UPLOAD_FILE_ABORTED) {
if (_currentUpload)
_currentUpload->detach();
AC_DBG("%d bytes uploaded\n", upload.totalSize);
}
}
}
/**
* Concatenates subsequent AutoConnectAux pages starting from oneself
* to the chain list.
* AutoConnectAux is collected in the chain list and each object is
* chained by the "_next". AutoConnect follows the "_next" to manage
* auxiliary pages. The _concat function concatenates subsequent
* AutoConnectAuxs.
* @param aux A reference of AutoConnectAux.
*/
void AutoConnectAux::_concat(AutoConnectAux& aux) {
if (_next)
_next->_concat(aux);
else
_next = &aux;
}
/**
* Register the AutoConnect that owns itself.
* AutoConnectAux needs to access the AutoConnect member. Also
* AutoConnectAux is cataloged by chain list. The _join function
* registers AutoConnect in the following AutoConnectAux chain list.
* @param ac A reference of AutoConnect.
*/
void AutoConnectAux::_join(AutoConnect& ac) {
_ac = &ac;
// Chain to subsequent AutoConnectAux in the list.
if (_next)
_next->_join(ac);
}
/**
* Inject the <li> element depending on the "luxbar-item" attribute
* for implementing the AutoConnect menu.
* @param args A reference of PageArgument but it's only used for
* interface alignment and is not actually used.
* @return A concatenated string of <li> elements for the menu item of
* AutoConnect.
*/
const String AutoConnectAux::_injectMenu(PageArgument& args) {
String menuItem;
if (_menu)
menuItem = String(FPSTR("<li class=\"lb-item\"><a href=\"")) + String(_uri) + String("\">") + _title + String(FPSTR("</a></li>"));
if (_next)
menuItem += _next->_injectMenu(args);
return menuItem;
}
/**
* Insert the uri that caused the request to the aux.
*/
const String AutoConnectAux::_indicateUri(PageArgument& args) {
AC_UNUSED(args);
String lastUri = _uriStr;
// The following code contains adding and trimming a blank that is
// wasteful for this function. It exists for avoiding the bug of
// WString::replace of ESP8266 arduino core 2.5.2.
// https://github.com/esp8266/Arduino/issues/6192
String reps = "/";
String replacement = "&#47;";
if (lastUri.length() == reps.length() + replacement.length())
lastUri += " ";
lastUri.replace(reps, replacement);
lastUri.trim();
return lastUri;
}
/**
* Modifying the form of attribute depending on the type of `input` tag
* contained. If the custom web page contains `input type=file` then
* allows multipart as ENCTYPE attribute.
* @param args A reference of PageArgument but unused.
* @return HTML string that should be inserted.
*/
const String AutoConnectAux::_indicateEncType(PageArgument& args) {
AC_UNUSED(args);
String encType = String("");
for (AutoConnectElement& elm : _addonElm)
if (elm.typeOf() == AC_File) {
return String(F("enctype='multipart/form-data'"));
}
return AutoConnect::_emptyString;
}
/**
* Insert the token handler of PageBuilder. This handler inserts HTML
* elements generated by the whole AutoConnectElements to the auxiliary page.
* @param args A reference of PageArgument but unused.
* @return HTML string that should be inserted.
*/
const String AutoConnectAux::_insertElement(PageArgument& args) {
String body = String("");
// When WebServerClass::handleClient calls RequestHandler, the parsed
// http argument has been prepared.
// If the current request argument contains AutoConnectElement, it is
// the form data of the AutoConnectAux page and with this timing save
// the value of each element.
fetchElement();
// Call user handler before HTML generation.
if (_handler) {
if (_order & AC_EXIT_AHEAD) {
AC_DBG("CB in AHEAD %s\n", uri());
body += _handler(*this, args);
}
}
// Generate HTML for all AutoConnectElements contained in the page.
for (AutoConnectElement& addon : _addonElm) {
// Since the style sheet has already drained at the time of the
// _insertElement function call, it skips the call to the HTML
// generator by each element.
if (addon.typeOf() != AC_Style)
// Invoke an HTML generator by each element
body += addon.toHTML();
}
// Call user handler after HTML generation.
if (_handler) {
if (_order & AC_EXIT_LATER) {
AC_DBG("CB in LATER %s\n", uri());
body += _handler(*this, args);
}
}
return body;
}
/**
* Insert user defined CSS code to AutoConnectAux page.
* @param args A reference of PageArgument but unused.
* @return HTML string that should be inserted.
*/
const String AutoConnectAux::_insertStyle(PageArgument& args) {
String css = String("");
for (AutoConnectElement& elm : _addonElm) {
if (elm.typeOf() == AC_Style)
css += elm.toHTML();
}
return css;
}
/**
* Generate an auxiliary page assembled with the AutoConnectElement.
* This function is the core procedure of AutoConnectAux, and uses
* PageBuilder from the _PAGE_AUX template to build an AutoConnect
* menu and insert HTML elements. A template of an auxiliary page is
* fixed and its structure inherits from the AutoConnect.
* @param uri An uri of the auxiliary page.
* @return A PageElement of auxiliary page.
*/
PageElement* AutoConnectAux::_setupPage(const String& uri) {
PageElement* elm = nullptr;
if (_ac) {
if (uri != String(_uri)) {
if (_next) {
elm = _next->_setupPage(uri);
}
} else {
AutoConnect* mother = _ac;
// Overwrite actual AutoConnectMenu title to the Aux. page title
if (_title.length())
mother->_menuTitle = _title;
elm = new PageElement();
// Construct the auxiliary page
elm->setMold(_PAGE_AUX);
elm->addToken(String(FPSTR("HEAD")), std::bind(&AutoConnect::_token_HEAD, mother, std::placeholders::_1));
elm->addToken(String(FPSTR("AUX_TITLE")), std::bind(&AutoConnectAux::_injectTitle, this, std::placeholders::_1));
elm->addToken(String(FPSTR("CSS_BASE")), std::bind(&AutoConnect::_token_CSS_BASE, mother, std::placeholders::_1));
elm->addToken(String(FPSTR("CSS_UL")), std::bind(&AutoConnect::_token_CSS_UL, mother, std::placeholders::_1));
elm->addToken(String(FPSTR("CSS_INPUT_BUTTON")), std::bind(&AutoConnect::_token_CSS_INPUT_BUTTON, mother, std::placeholders::_1));
elm->addToken(String(FPSTR("CSS_INPUT_TEXT")), std::bind(&AutoConnect::_token_CSS_INPUT_TEXT, mother, std::placeholders::_1));
elm->addToken(String(FPSTR("CSS_LUXBAR")), std::bind(&AutoConnect::_token_CSS_LUXBAR, mother, std::placeholders::_1));
elm->addToken(String(FPSTR("AUX_CSS")), std::bind(&AutoConnectAux::_insertStyle, this, std::placeholders::_1));
elm->addToken(String(FPSTR("MENU_PRE")), std::bind(&AutoConnect::_token_MENU_PRE, mother, std::placeholders::_1));
elm->addToken(String(FPSTR("MENU_AUX")), std::bind(&AutoConnect::_token_MENU_AUX, mother, std::placeholders::_1));
elm->addToken(String(FPSTR("MENU_POST")), std::bind(&AutoConnect::_token_MENU_POST, mother, std::placeholders::_1));
elm->addToken(String(FPSTR("AUX_URI")), std::bind(&AutoConnectAux::_indicateUri, this, std::placeholders::_1));
elm->addToken(String(FPSTR("ENC_TYPE")), std::bind(&AutoConnectAux::_indicateEncType, this, std::placeholders::_1));
elm->addToken(String(FPSTR("AUX_ELEMENT")), std::bind(&AutoConnectAux::_insertElement, this, std::placeholders::_1));
// Restore transfer mode by each page
mother->_responsePage->chunked(chunk);
}
}
return elm;
}
/**
* Store element values owned by AutoConnectAux that caused the request.
* Save the current arguments remaining in the Web server object when
* this function invoked.
* @param webServer A pointer to the class object of WebServerClass
*/
void AutoConnectAux::_storeElements(WebServerClass* webServer) {
// Retrieve each element value, Overwrites the value of all cataloged
// AutoConnectElements with arguments inherited from last http request.
for (AutoConnectElement& elm : _addonElm) {
// The POST body does not contain the value of the AutoConnectFile,
// so it can not be obtained with the WebServerClass::arg function.
// The AutoConnectFile value will be restored from least recent
// upload request.
if (elm.typeOf() == AC_File)
continue;
// Relies on AutoConnectRadio, it restores to false at the being
// because the checkbox argument will not pass if it is not checked.
if (elm.typeOf() == AC_Checkbox)
reinterpret_cast<AutoConnectCheckbox&>(elm).checked = false;
// Seek by argument, store the value to its element.
for (int8_t n = 0; n < static_cast<int8_t>(webServer->args()); n++) {
if (webServer->argName(n).equalsIgnoreCase(elm.name)) {
String elmValue = webServer->arg(n);
if (elm.typeOf() == AC_Checkbox)
elmValue = "checked";
setElementValue(elm.name, elmValue);
// Copy a value to other elements declared as global.
if (elm.global) {
AutoConnectAux* aux = _ac->_aux;
while (aux) {
if (aux != this)
aux->setElementValue(elm.name, elmValue);
aux = aux->_next;
}
}
}
}
}
AC_DBG_DUMB(",elements stored\n");
}
#ifdef AUTOCONNECT_USE_JSON
/**
* Load AutoConnectAux page from JSON description stored in PROGMEM.
* This function can load AutoConnectAux for multiple AUX pages written
* in JSON and is registered in AutoConnect.
* @param aux JSON description to be load.
* @return true Successfully loaded.
*/
bool AutoConnect::load(PGM_P aux) {
return _parseJson<const __FlashStringHelper*>(reinterpret_cast<const __FlashStringHelper*>(aux));
}
/**
* Load AutoConnectAux page from JSON description stored in PROGMEM.
* This function can load AutoConnectAux for multiple AUX pages written
* in JSON and is registered in AutoConnect.
* @param aux JSON description to be load.
* @return true Successfully loaded.
*/
bool AutoConnect::load(const __FlashStringHelper* aux) {
return _parseJson<const __FlashStringHelper*>(aux);
}
/**
* Load AutoConnectAux page from JSON description stored in the sketch.
* This function can load AutoConnectAux for multiple AUX pages written
* in JSON and is registered in AutoConnect.
* @param aux JSON description to be load.
* @return true Successfully loaded.
*/
bool AutoConnect::load(const String& aux) {
return _parseJson<const String&>(aux);
}
/**
* Load AutoConnectAux page from JSON description from the stream.
* This function can load AutoConnectAux for multiple AUX pages written
* in JSON and is registered in AutoConnect.
* @param aux Stream for read AutoConnectAux elements.
* @return true Successfully loaded.
*/
bool AutoConnect::load(Stream& aux) {
return _parseJson<Stream&>(aux);
}
/**
* Load AutoConnectAux page from JSON object.
* @param aux A JsonVariant object that stores each element of AutoConnectAuxl.
* @return true Successfully loaded.
*/
bool AutoConnect::_load(JsonVariant& auxJson) {
bool rc = true;
if (auxJson.is<JsonArray>()) {
ArduinoJsonArray jb = auxJson.as<JsonArray>();
for (ArduinoJsonObject auxJson : jb) {
AutoConnectAux* newAux = new AutoConnectAux;
if (newAux->_load(auxJson))
join(*newAux);
else {
delete newAux;
rc = false;
break;
}
}
}
else {
ArduinoJsonObject jb = auxJson.as<JsonObject>();
AutoConnectAux* newAux = new AutoConnectAux;
if (newAux->_load(jb))
join(*newAux);
else {
delete newAux;
rc = false;
}
}
return rc;
}
/**
* Create an instance from the AutoConnectElement of the JSON object.
* @param json A reference of JSON
* @return A pointer of created AutoConnectElement instance.
*/
AutoConnectElement* AutoConnectAux::_createElement(const JsonObject& json) {
AutoConnectElement* elm = nullptr;
String type = json[F(AUTOCONNECT_JSON_KEY_TYPE)].as<String>();
switch (_asElementType(type)) {
case AC_Element:
case AC_Unknown:
elm = new AutoConnectElement;
break;
case AC_Button: {
AutoConnectButton* cert_elm = new AutoConnectButton;
return reinterpret_cast<AutoConnectElement*>(cert_elm);
}
case AC_Checkbox: {
AutoConnectCheckbox* cert_elm = new AutoConnectCheckbox;
return reinterpret_cast<AutoConnectElement*>(cert_elm);
}
case AC_File: {
AutoConnectFile* cert_elm = new AutoConnectFile;
return reinterpret_cast<AutoConnectElement*>(cert_elm);
}
case AC_Input: {
AutoConnectInput* cert_elm = new AutoConnectInput;
return reinterpret_cast<AutoConnectElement*>(cert_elm);
}
case AC_Radio: {
AutoConnectRadio* cert_elm = new AutoConnectRadio;
return reinterpret_cast<AutoConnectElement*>(cert_elm);
}
case AC_Select: {
AutoConnectSelect* cert_elm = new AutoConnectSelect;
return reinterpret_cast<AutoConnectElement*>(cert_elm);
}
case AC_Style: {
AutoConnectStyle* cert_elm = new AutoConnectStyle;
return reinterpret_cast<AutoConnectElement*>(cert_elm);
}
case AC_Submit: {
AutoConnectSubmit* cert_elm = new AutoConnectSubmit;
return reinterpret_cast<AutoConnectElement*>(cert_elm);
}
case AC_Text: {
AutoConnectText* cert_elm = new AutoConnectText;
return reinterpret_cast<AutoConnectElement*>(cert_elm);
}
}
return elm;
}
/**
* Constructs an AutoConnectAux instance by reading all the
* AutoConnectElements of the specified URI from the elements defined
* JSON stored in a constant character string.
* @param in AutoConnectAux element data which is described by JSON.
* @return true The element collection successfully loaded.
* @return false Invalid JSON data occurred.
*/
bool AutoConnectAux::load(const String& in) {
return _parseJson<const String&>(in);
}
/**
* Constructs an AutoConnectAux instance by reading all the
* AutoConnectElements of the specified URI from the elements passing
* pointer to JSON stored in pgm_data array.
* @param in AutoConnectAux element data which is described by JSON.
* @return true The element collection successfully loaded.
* @return false Invalid JSON data occurred.
*/
bool AutoConnectAux::load(PGM_P in) {
return _parseJson<const __FlashStringHelper*>(reinterpret_cast<const __FlashStringHelper*>(in));
}
/**
* Constructs an AutoConnectAux instance by reading all the
* AutoConnectElements of the specified URI from the elements defined
* JSON stored in pgm_data array.
* @param in AutoConnectAux element data which is described by JSON.
* @return true The element collection successfully loaded.
* @return false Invalid JSON data occurred.
*/
bool AutoConnectAux::load(const __FlashStringHelper* in) {
return _parseJson<const __FlashStringHelper*>(in);
}
/**
* Constructs an AutoConnectAux instance by reading all the
* AutoConnectElements of the specified URI from the elements defined
* JSON stored in a Stream.
* @param in AutoConnectAux element data which is described by JSON.
* @return true The element collection successfully loaded.
* @return false Invalid JSON data occurred.
*/
bool AutoConnectAux::load(Stream& in) {
return _parseJson<Stream&>(in);
}
/**
* Load all elements of AutoConectAux page from JSON object.
* @param jb Reference of JSON object
* @return true Successfully loaded.
* @return false loading unsuccessful, JSON parsing error occurred.
*/
bool AutoConnectAux::_load(JsonObject& jb) {
_title = jb[F(AUTOCONNECT_JSON_KEY_TITLE)].as<String>();
_uriStr = jb[F(AUTOCONNECT_JSON_KEY_URI)].as<String>();
_uri = _uriStr.c_str();
_menu = jb[F(AUTOCONNECT_JSON_KEY_MENU)].as<bool>();
JsonVariant elements = jb[F(AUTOCONNECT_JSON_KEY_ELEMENT)];
(void)_loadElement(elements, "");
return true;
}
/**
* Load element specified by the name parameter from the stream
* described by JSON. Usually, the Stream is specified a storm file of
* SD or SPIFFS. The Stream must be opened before invoking the function.
* @param in Reference of the Stream which contains the parameter
* data described by JSON.
* @param name The element name to be loaded. '*'specifies that all
* elements are to be loaded.
* @return A reference of loaded AutoConnectElement instance.
*/
bool AutoConnectAux::loadElement(PGM_P in, const String& name) {
return _parseElement<const __FlashStringHelper*, const String&>(reinterpret_cast<const __FlashStringHelper*>(in), name);
}
bool AutoConnectAux::loadElement(const __FlashStringHelper* in, const String& name) {
return _parseElement<const __FlashStringHelper*, const String&>(in, name);
}
bool AutoConnectAux::loadElement(const String& in, const String& name) {
return _parseElement<const String&, const String&>(in, name);
}
bool AutoConnectAux::loadElement(Stream& in, const String& name) {
return _parseElement<Stream&, const String&>(in, name);
}
bool AutoConnectAux::loadElement(PGM_P in, std::vector<String> const& names) {
return _parseElement<const __FlashStringHelper*, std::vector<String> const&>(reinterpret_cast<const __FlashStringHelper*>(in), names);
}
bool AutoConnectAux::loadElement(const __FlashStringHelper* in, std::vector<String> const& names) {
return _parseElement<const __FlashStringHelper*, std::vector<String> const&>(in, names);
}
bool AutoConnectAux::loadElement(const String& in, std::vector<String> const& names) {
return _parseElement<const String&, std::vector<String> const&>(in, names);
}
bool AutoConnectAux::loadElement(Stream& in, std::vector<String> const& names) {
return _parseElement<Stream&, std::vector<String> const&>(in, names);
}
bool AutoConnectAux::_loadElement(JsonVariant& jb, std::vector<String> const& names) {
bool rc = true;
for (const String& name : names)
rc &= _loadElement(jb, name);
return rc;
}
bool AutoConnectAux::_loadElement(JsonVariant& jb, const String& name) {
bool rc = false;
if (jb.is<JsonArray>()) {
ArduinoJsonArray elements = jb.as<JsonArray>();
for (ArduinoJsonObject element : elements) {
if (name.length()) {
//Finds an element with the specified name in the JSON array and loads it.
if (!name.equalsIgnoreCase(element[F(AUTOCONNECT_JSON_KEY_NAME)].as<String>()))
continue;
}
AutoConnectElement& elm = _loadElement(element, name);
if (elm.name.length())
rc = true;
if (name.length())
break;
}
}
else {
ArduinoJsonObject element = jb.as<JsonObject>();
AutoConnectElement& elm = _loadElement(element, name);
if (elm.name.length())
rc = true;
}
return rc;
}
AutoConnectElement& AutoConnectAux::_loadElement(JsonObject& element, const String& name) {
AutoConnectElement* auxElm = nullptr;
String elmName = element[F(AUTOCONNECT_JSON_KEY_NAME)].as<String>();
if (!name.length() || name.equalsIgnoreCase(elmName)) {
// The specified element is defined in the JSON stream.
// Loads from JSON object.
auxElm = getElement(elmName);
// The element is not created yet, create new one.
if (!auxElm) {
if (elmName.length()) {
if ((auxElm = _createElement(element))) {
AC_DBG("%s<%d> of %s created\n", elmName.c_str(), (int)(auxElm->typeOf()), uri());
add(*auxElm); // Insert to AutoConnect
}
else {
AC_DBG("%s unknown element type\n", elmName.c_str());
}
}
else {
AC_DBG("Element name missing\n");
}
}
if (auxElm) {
if (auxElm->loadMember(element))
AC_DBG("%s<%d> of %s loaded\n", auxElm->name.c_str(), (int)auxElm->typeOf(), uri());
else {
// Element type mismatch
AC_DBG("Type of %s element mismatched\n", elmName.c_str());
}
}
}
return auxElm ? *auxElm : _nullElement();
}
/**
* Serialize an element specified the name into the stream.
* @param name An element name to be output.
* @return Number of bytes output
*/
size_t AutoConnectAux::saveElement(Stream& out, std::vector<String> const& names) {
size_t bufferSize = 0;
size_t amount = names.size();
size_t size_n = 0;
// Calculate JSON buffer size
if (amount == 0) {
bufferSize += JSON_OBJECT_SIZE(4);
bufferSize += sizeof(AUTOCONNECT_JSON_KEY_TITLE) + _title.length() + 1 + sizeof(AUTOCONNECT_JSON_KEY_URI) + _uriStr.length() + 1 + sizeof(AUTOCONNECT_JSON_KEY_MENU) + sizeof(AUTOCONNECT_JSON_KEY_ELEMENT);
bufferSize += JSON_ARRAY_SIZE(_addonElm.size());
}
else
bufferSize += JSON_ARRAY_SIZE(amount);
for (AutoConnectElement& elmEach : _addonElm) {
AutoConnectElement* elm = &elmEach;
if (amount > 0) {
String& elmName = elm->name;
auto aim = std::find_if(names.begin(), names.end(), [&](const String& n) { return n.equalsIgnoreCase(elmName); });
if (aim == names.end())
continue;
}
bufferSize += elm->getObjectSize();
}
// Round up to 16 boundary
bufferSize = bufferSize > 0 ? ((bufferSize + 16) & (~0xf)) : bufferSize;
AC_DBG("JSON buffer size:%d\n", bufferSize);
// Serialization
if (bufferSize > 0) {
ArduinoJsonBuffer jb(bufferSize);
if (amount == 1) {
ArduinoJsonObject element = ARDUINOJSON_CREATEOBJECT(jb);
for (AutoConnectElement& elm : _addonElm)
if (elm.name.equalsIgnoreCase(names[0])) {
elm.serialize(element);
break;
}
size_n = ARDUINOJSON_PRINT(element, out);
}
else if (amount == 0) {
ArduinoJsonObject json = ARDUINOJSON_CREATEOBJECT(jb);
json[F(AUTOCONNECT_JSON_KEY_TITLE)] = _title;
json[F(AUTOCONNECT_JSON_KEY_URI)] = _uriStr;
json[F(AUTOCONNECT_JSON_KEY_MENU)] = _menu;
ArduinoJsonArray elements = json.createNestedArray(F(AUTOCONNECT_JSON_KEY_ELEMENT));
for (AutoConnectElement& elm : _addonElm) {
ArduinoJsonObject element = elements.createNestedObject();
elm.serialize(element);
}
size_n = ARDUINOJSON_PRETTYPRINT(json, out);
}
else if (amount >= 2) {
ArduinoJsonArray elements = ARDUINOJSON_CREATEARRAY(jb);
for (String name : names)
for (AutoConnectElement& elm : _addonElm)
if (elm.name.equalsIgnoreCase(name)) {
ArduinoJsonObject element = elements.createNestedObject();
elm.serialize(element);
break;
}
size_n = ARDUINOJSON_PRETTYPRINT(elements, out);
}
}
return size_n;
}
/**
* Convert element type from type as String.
* @param type An element type as String
* @return A type of ACElement_t
*/
ACElement_t AutoConnectAux::_asElementType(const String& type) {
typedef struct {
const char* tName;
ACElement_t tEnum;
} ACElementType_t;
static const ACElementType_t types[] PROGMEM = {
{ AUTOCONNECT_JSON_TYPE_ACBUTTON, AC_Button },
{ AUTOCONNECT_JSON_TYPE_ACCHECKBOX, AC_Checkbox },
{ AUTOCONNECT_JSON_TYPE_ACELEMENT, AC_Element },
{ AUTOCONNECT_JSON_TYPE_ACFILE, AC_File },
{ AUTOCONNECT_JSON_TYPE_ACINPUT, AC_Input },
{ AUTOCONNECT_JSON_TYPE_ACRADIO, AC_Radio },
{ AUTOCONNECT_JSON_TYPE_ACSELECT, AC_Select },
{ AUTOCONNECT_JSON_TYPE_ACSTYLE, AC_Style },
{ AUTOCONNECT_JSON_TYPE_ACSUBMIT, AC_Submit },
{ AUTOCONNECT_JSON_TYPE_ACTEXT, AC_Text }
};
ACElement_t t = AC_Unknown;
for (size_t n = 0; n < (sizeof(types) / sizeof(ACElementType_t)); n++) {
if (type.equalsIgnoreCase(String(FPSTR(types[n].tName))))
return types[n].tEnum;
}
return t;
}
#endif // !AUTOCONNECT_USE_JSON