/** * Implementation of AutoConnectAux class. * @file AutoConnectAuxBasisImpl.h * @author hieromon@gmail.com * @version 0.9.10 * @date 2019-06-12 * @copyright MIT license. */ #include #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 *
block with a class defined in 'base-panel' and is held by a *
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}}" "{{AUX_TITLE}}" "" "" "" "
" "{{MENU_PRE}}" "{{MENU_AUX}}" "{{MENU_POST}}" "
" "" "
    " "{{AUX_ELEMENT}}" "
" "" "
" "
" "" "" "" }; /** * Destructs container of AutoConnectElement and release a unique * pointer of AutoConnect instance. */ AutoConnectAux::~AutoConnectAux() { _addonElm.clear(); _addonElm.swap(_addonElm); if (_ac) _ac.release(); } /** * 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("/", "/"); AC_DBG("fetch %s", _ac->_auxUri.c_str()); AutoConnectAux* aux = _ac->_aux.get(); 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(); } } } /** * 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(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 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(elm); elmSelect->select(value); } else { if (elm->typeOf() == AC_Checkbox) { if (value == "checked") { AutoConnectCheckbox* elmCheckbox = reinterpret_cast(elm); elmCheckbox->checked = true; } } else if (elm->typeOf() == AC_Radio) { AutoConnectRadio* elmRadio = reinterpret_cast(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 const& values) { bool rc = false; AutoConnectElement* elm = getElement(name); if (elm) { switch (elm->typeOf()) { case AC_Radio: { AutoConnectRadio* elmRadio = reinterpret_cast(elm); elmRadio->empty(); for (String v : values) elmRadio->add(v); rc = true; break; } case AC_Select: { AutoConnectSelect* elmSelect = reinterpret_cast(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.get(); while (aux) { if (aux->_uriStr == requestUri) { addons = aux->_addonElm; break; } aux = aux->_next.get(); } _currentUpload = nullptr; for (AutoConnectElement& elm : addons) { if (elm.typeOf() == AC_File) { _currentUpload = reinterpret_cast(&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.reset(&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.reset(&ac); // Chain to subsequent AutoConnectAux in the list. if (_next) _next->_join(ac); } /** * Inject the
  • 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
  • elements for the menu item of * AutoConnect. */ const String AutoConnectAux::_injectMenu(PageArgument& args) { String menuItem; if (_menu) menuItem = String(FPSTR("
  • ") + _title + String(FPSTR("
  • ")); 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 = "/"; 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.get(); // 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(elm).checked = false; // Seek by argument, store the value to its element. for (int8_t n = 0; n < static_cast(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.get(); while (aux) { if (aux != this) aux->setElementValue(elm.name, elmValue); aux = aux->_next.get(); } } } } } 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(reinterpret_cast(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(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(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(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()) { ArduinoJsonArray jb = auxJson.as(); 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(); 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(); switch (_asElementType(type)) { case AC_Element: case AC_Unknown: elm = new AutoConnectElement; break; case AC_Button: { AutoConnectButton* cert_elm = new AutoConnectButton; return reinterpret_cast(cert_elm); } case AC_Checkbox: { AutoConnectCheckbox* cert_elm = new AutoConnectCheckbox; return reinterpret_cast(cert_elm); } case AC_File: { AutoConnectFile* cert_elm = new AutoConnectFile; return reinterpret_cast(cert_elm); } case AC_Input: { AutoConnectInput* cert_elm = new AutoConnectInput; return reinterpret_cast(cert_elm); } case AC_Radio: { AutoConnectRadio* cert_elm = new AutoConnectRadio; return reinterpret_cast(cert_elm); } case AC_Select: { AutoConnectSelect* cert_elm = new AutoConnectSelect; return reinterpret_cast(cert_elm); } case AC_Style: { AutoConnectStyle* cert_elm = new AutoConnectStyle; return reinterpret_cast(cert_elm); } case AC_Submit: { AutoConnectSubmit* cert_elm = new AutoConnectSubmit; return reinterpret_cast(cert_elm); } case AC_Text: { AutoConnectText* cert_elm = new AutoConnectText; return reinterpret_cast(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(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(reinterpret_cast(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(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(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(); _uriStr = jb[F(AUTOCONNECT_JSON_KEY_URI)].as(); _uri = _uriStr.c_str(); _menu = jb[F(AUTOCONNECT_JSON_KEY_MENU)].as(); 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(reinterpret_cast(in), name); } bool AutoConnectAux::loadElement(const __FlashStringHelper* in, const String& name) { return _parseElement(in, name); } bool AutoConnectAux::loadElement(const String& in, const String& name) { return _parseElement(in, name); } bool AutoConnectAux::loadElement(Stream& in, const String& name) { return _parseElement(in, name); } bool AutoConnectAux::loadElement(PGM_P in, std::vector const& names) { return _parseElement const&>(reinterpret_cast(in), names); } bool AutoConnectAux::loadElement(const __FlashStringHelper* in, std::vector const& names) { return _parseElement const&>(in, names); } bool AutoConnectAux::loadElement(const String& in, std::vector const& names) { return _parseElement const&>(in, names); } bool AutoConnectAux::loadElement(Stream& in, std::vector const& names) { return _parseElement const&>(in, names); } bool AutoConnectAux::_loadElement(JsonVariant& jb, std::vector 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()) { ArduinoJsonArray elements = jb.as(); 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())) continue; } AutoConnectElement& elm = _loadElement(element, name); if (elm.name.length()) rc = true; if (name.length()) break; } } else { ArduinoJsonObject element = jb.as(); 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(); 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 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