From dc75ab7b6d18fe902063407e7c57d863d9414942 Mon Sep 17 00:00:00 2001 From: Hieromon Ikasamo Date: Sun, 23 Dec 2018 02:29:29 +0900 Subject: [PATCH] Supports AutoConnectAux --- examples/mqttRSSI/mqttRSSI.ino | 252 +++++++++++++++++++++++++++--- src/AutoConnect.cpp | 26 ++- src/AutoConnect.h | 1 - src/AutoConnectAux.cpp | 184 ++++++++-------------- src/AutoConnectAux.h | 1 + src/AutoConnectDefs.h | 5 + src/AutoConnectElementBasis.h | 1 + src/AutoConnectElementBasisImpl.h | 20 ++- 8 files changed, 347 insertions(+), 143 deletions(-) diff --git a/examples/mqttRSSI/mqttRSSI.ino b/examples/mqttRSSI/mqttRSSI.ino index 1a16c18..2905105 100644 --- a/examples/mqttRSSI/mqttRSSI.ino +++ b/examples/mqttRSSI/mqttRSSI.ino @@ -17,22 +17,134 @@ #include #elif defined(ARDUINO_ARCH_ESP32) #include +#include #endif +#include #include #include -#define MQTT_USER_KEY "****************" // Replace to User API Key. -#define CHANNEL_ID "******" // Replace to Channel ID. -#define CHANNEL_API_KEY_WR "****************" // Replace to the write API Key. +#define PARAM_FILE "/param.json" +#define AUX_SETTING_URI "/mqtt_setting" +#define AUX_SAVE_URI "/mqtt_save" -#define MQTT_UPDATE_INTERVAL 40000 -#define MQTT_TOPIC "channels/" CHANNEL_ID "/publish/" CHANNEL_API_KEY_WR -#define MQTT_USER_ID "anyone" -#define MQTT_SERVER "mqtt.thingspeak.com" +static const char AUX_mqtt_setting[] PROGMEM = R"raw( +[ + { + "title": "MQTT Setting", + "uri": "/mqtt_setting", + "menu": true, + "element": [ + { + "name": "header", + "type": "ACText", + "value": "

MQTT broker settings

", + "style": "text-align:center;color:#2f4f4f;padding:10px;" + }, + { + "name": "caption", + "type": "ACText", + "value": "Publishing the WiFi signal strength to MQTT channel. RSSI value of ESP8266 to the channel created on ThingSpeak", + "style": "font-family:serif;color:#4682b4;" + }, + { + "name": "mqttserver", + "type": "ACInput", + "value": "mqtt.thingspeak.com", + "placeholder": "MQTT broker server", + "label": "Server" + }, + { + "name": "channelid", + "type": "ACInput", + "value": "454951", + "label": "Channel ID" + }, + { + "name": "userkey", + "type": "ACInput", + "value": "NRTFYGJ6TJFGX4RC", + "label": "User Key" + }, + { + "name": "apikey", + "type": "ACInput", + "value": "HBVQ2XV6VYBI4582", + "label": "API Key" + }, + { + "name": "period", + "type": "ACRadio", + "label": "Update period", + "value": [ + "30 sec.", + "60 sec.", + "180 sec." + ], + "arrange": "vertical", + "checked": 1 + }, + { + "name": "newline", + "type": "ACElement", + "value": "
" + }, + { + "name": "uniqueid", + "type": "ACCheckbox", + "value": "unique", + "label": "Use APID unique", + "checked": true + }, + { + "name": "hostname", + "type": "ACInput", + "label": "ESP host name", + "value": "" + }, + { + "name": "save", + "type": "ACSubmit", + "value": "Save&Start", + "uri": "/mqtt_save" + }, + { + "name": "discard", + "type": "ACSubmit", + "value": "Discard", + "uri": "/" + } + ] + }, + { + "title": "MQTT Setting", + "uri": "/mqtt_save", + "name": "mqttsave", + "element": [ + { + "name": "caption", + "type": "ACText", + "value": "

Parameters saved as:

", + "style": "text-align:center;color:#2f4f4f;padding:10px;" + } + ] + } +] +)raw"; AutoConnect portal; +AutoConnectConfig config; WiFiClient wifiClient; PubSubClient mqttClient(wifiClient); +String serverName; +String channelId; +String userKey; +String apiKey; +String apid; +String hostName; +unsigned int updateInterval = 0; +unsigned long lastPub = 0; + +#define MQTT_USER_ID "anyone" bool mqttConnect() { static const char alphanum[] = "0123456789" @@ -42,14 +154,18 @@ bool mqttConnect() { uint8_t retry = 10; while (!mqttClient.connected()) { - Serial.println("Attempting MQTT broker:" MQTT_SERVER); + if (serverName.length() <= 0) + break; + + mqttClient.setServer(serverName.c_str(), 1883); + Serial.println(String("Attempting MQTT broker:") + serverName); for (uint8_t i = 0; i < 8; i++) { clientId[i] = alphanum[random(62)]; } clientId[8] = '\0'; - if (mqttClient.connect(clientId, MQTT_USER_ID, MQTT_USER_KEY)) { + if (mqttClient.connect(clientId, MQTT_USER_ID, userKey.c_str())) { Serial.println("Established:" + String(clientId)); return true; } else { @@ -63,7 +179,7 @@ bool mqttConnect() { } void mqttPublish(String msg) { - String path = String(MQTT_TOPIC); + String path = String("channels/") + channelId + String("/publish/") + apiKey; int tLen = path.length(); char topic[tLen]; path.toCharArray(topic, tLen + 1); @@ -83,16 +199,104 @@ int getStrength(uint8_t points) { rssi += WiFi.RSSI(); delay(20); } - return points ? (int)(rssi / points) : 0; + return points ? static_cast(rssi / points) : 0; } -unsigned long lastPub = 0; +String loadParams(AutoConnectAux& aux, PageArgument& args) { + (void)(args); + SPIFFS.begin(); + File param = SPIFFS.open(PARAM_FILE, "r"); + if (param) { + aux.loadElement(param); + param.close(); + } + else + Serial.println(PARAM_FILE " open failed"); + SPIFFS.end(); + return ""; +} + +String saveParams(AutoConnectAux& aux, PageArgument& args) { + String echo; + + serverName = args.arg("mqttserver"); + serverName.trim(); + echo = "mqttserver: " + serverName + "
"; + + channelId = args.arg("channelid"); + channelId.trim(); + echo += "channelid: " + channelId + "
"; + + userKey = args.arg("userkey"); + userKey.trim(); + echo += "userkey: " + userKey + "
"; + + apiKey = args.arg("apikey"); + apiKey.trim(); + echo += "apikey: " + apiKey + "
"; + + String upd = args.arg("period"); + updateInterval = upd.substring(0, 2).toInt() * 1000; + echo += "period: " + String(updateInterval) + "
"; + + String uniqueid = args.arg("uniqueid"); + echo += "uniqueid: " + uniqueid + "
"; + + hostName = args.arg("hostname"); + hostName.trim(); + echo += "hostname: " + hostName + "
"; + + SPIFFS.begin(); + File param = SPIFFS.open(PARAM_FILE, "w"); + portal.aux("/mqtt_setting")->saveElement(param, { "mqttserver", "channelid", "userkey", "apikey", "period", "uniqueid", "hostname" }); + param.close(); + SPIFFS.end(); + return echo; +} + +void handleRoot() { + String content = + "" + "" + "" + "" + "" + "" + "

" AUTOCONNECT_LINK(COG_24) "

" + "" + ""; + +#if defined(ARDUINO_ARCH_ESP8266) + ESP8266WebServer& server = portal.host(); +#elif defined(ARDUINO_ARCH_ESP32) + WebServer& server = portal.host(); +#endif + server.send(200, "text/html", content); +} void setup() { delay(1000); Serial.begin(115200); Serial.println(); + if (portal.load(FPSTR(AUX_mqtt_setting))) { + AutoConnectAux* setting = portal.aux(AUX_SETTING_URI); + AutoConnectCheckbox& uniqueidElm = setting->getElement("uniqueid"); + AutoConnectInput& hostnameElm = setting->getElement("hostname"); + if (uniqueidElm.checked) { + config.apid = String("ESP") + "_" + String(ESP.getChipId(), HEX); + } + if (hostnameElm.value.length()) { + config.hostName = hostnameElm.value; + } + + portal.on(AUX_SETTING_URI, loadParams); + portal.on(AUX_SAVE_URI, saveParams, AC_EXIT_LATER); + portal.config(config); + } + else + Serial.println("load error"); + Serial.print("WiFi "); if (portal.begin()) { Serial.println("connected:" + WiFi.SSID()); @@ -104,18 +308,26 @@ void setup() { yield(); } } - mqttClient.setServer(MQTT_SERVER, 1883); + +#if defined(ARDUINO_ARCH_ESP8266) + ESP8266WebServer& server = portal.host(); +#elif defined(ARDUINO_ARCH_ESP32) + WebServer& server = portal.host(); +#endif + server.on("/", handleRoot); } void loop() { - if (millis() - lastPub > MQTT_UPDATE_INTERVAL) { - if (!mqttClient.connected()) { - mqttConnect(); + if (updateInterval > 0) { + if (millis() - lastPub > updateInterval) { + if (!mqttClient.connected()) { + mqttConnect(); + } + String item = String("field1=") + String(getStrength(7)); + mqttPublish(item); + mqttClient.loop(); + lastPub = millis(); } - String item = String("field1=") + String(getStrength(7)); - mqttPublish(item); - mqttClient.loop(); - lastPub = millis(); } portal.handleClient(); } diff --git a/src/AutoConnect.cpp b/src/AutoConnect.cpp index e0a55a4..3b5ba11 100644 --- a/src/AutoConnect.cpp +++ b/src/AutoConnect.cpp @@ -656,6 +656,26 @@ String AutoConnect::_invokeResult(PageArgument& args) { */ bool AutoConnect::_classifyHandle(HTTPMethod method, String uri) { AC_DBG("Host:%s, URI:%s", _webServer->hostHeader().c_str(), uri.c_str()); + + // At the time when handleClient calls RequestHandler, the parsed http + // argument remains the last one in the request. + // 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. + if (_webServer->hasArg(String(AUTOCONNECT_AUXURI_PARAM))) { + String auxUri = _webServer->arg(AUTOCONNECT_AUXURI_PARAM); + auxUri.replace(String("/"), String("/")); + AutoConnectAux* aux = _aux.get(); + while (aux) { + if (aux->_uriStr == auxUri) { + aux->_storeElements(_webServer.get()); + break; + } + aux = aux->_next.get(); + } + } + + // Here, classify requested uri if (uri == _uri) { AC_DBG_DUMB(", already allocated\n", _uri.c_str()); return true; // The response page already exists. @@ -747,12 +767,10 @@ wl_status_t AutoConnect::_waitForConnect(unsigned long timeout) { if (millis() - st > timeout) break; } -#ifdef AC_DEBUG - AC_DEBUG_PORT.print('.'); -#endif // !AC_DEBUG + AC_DBG_DUMB("%c", '.'); delay(300); } - AC_DBG("%s IP:%s\n", wifiStatus == WL_CONNECTED ? "established" : "time out", WiFi.localIP().toString().c_str()); + AC_DBG_DUMB("%s IP:%s\n", wifiStatus == WL_CONNECTED ? "established" : "time out", WiFi.localIP().toString().c_str()); return wifiStatus; } diff --git a/src/AutoConnect.h b/src/AutoConnect.h index cac2a1a..52f2769 100644 --- a/src/AutoConnect.h +++ b/src/AutoConnect.h @@ -235,7 +235,6 @@ class AutoConnect { /** Extended pages made up with AutoConnectAux */ std::unique_ptr _aux; - String _auxLastUri; /** Saved configurations */ AutoConnectConfig _apConfig; diff --git a/src/AutoConnectAux.cpp b/src/AutoConnectAux.cpp index ba35df0..aff1889 100644 --- a/src/AutoConnectAux.cpp +++ b/src/AutoConnectAux.cpp @@ -53,7 +53,7 @@ const char AutoConnectAux::_PAGE_AUX[] PROGMEM = { "function _sa(url) {" "var uri=document.createElement('input');" "uri.setAttribute('type','hidden');" - "uri.setAttribute('name','uri');" + "uri.setAttribute('name','" AUTOCONNECT_AUXURI_PARAM "');" "uri.setAttribute('value','{{AUX_URI}}');" "document.getElementById('_aux').appendChild(uri);" "document.getElementById('_aux').action=url;" @@ -75,6 +75,16 @@ AutoConnectAux::~AutoConnectAux() { _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. @@ -89,19 +99,19 @@ void AutoConnectAux::add(AutoConnectElement& addon) { * @param addons AutoConnectElementVT collection. */ void AutoConnectAux::add(AutoConnectElementVT addons) { - for (std::size_t n = 0; n < addons.size(); n++) - add(addons[n]); + for (AutoConnectElement& element : addons) + add(element); } /** -* Get already registered AutoConnectElement. -* @param name Element name -* @return A pointer to the registered AutoConnectElement. -*/ + * Get already registered AutoConnectElement. + * @param name Element name + * @return A pointer to the registered AutoConnectElement. + */ AutoConnectElement* AutoConnectAux::getElement(const String& name) { - for (std::size_t n = 0; n < _addonElm.size(); n++) - if (name.equalsIgnoreCase(_addonElm[n].get().name)) - return &(_addonElm[n].get()); + for (AutoConnectElement& elm : _addonElm) + if (elm.name.equalsIgnoreCase(name)) + return &elm; AC_DBG("Element<%s> not registered\n", name.c_str()); return nullptr; } @@ -127,28 +137,6 @@ bool AutoConnectAux::release(const String& name) { return rc; } -/** - * Set the value to AutoConnectRadio element. - * @param name A string of AutoConnectRadio element name to set the value. - * @param checked A checked value. - * @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 uint8_t checked) { -// AutoConnectElement* elm = getElement(name); -// if (elm) { -// if (elm->typeOf() == AC_Radio) { -// AutoConnectRadio* elmRadio = reinterpret_cast(elm); -// elmRadio->checked = checked; -// return true; -// } -// else -// AC_DBG("Element<%s> value type mismatch\n", name.c_str()); -// } -// return false; -//} - /** * Set the value to specified element. * @param name A string of element name to set the value. @@ -169,7 +157,7 @@ bool AutoConnectAux::setElementValue(const String& name, const String value) { } else if (elm->typeOf() == AC_Radio) { AutoConnectRadio* elmRadio = reinterpret_cast(elm); - elmRadio->checked = static_cast(value.toInt()); + elmRadio->check(value); } else elm->value = value; @@ -251,12 +239,30 @@ void AutoConnectAux::_join(AutoConnect& ac) { _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 = _ac->_auxLastUri; + String lastUri = _uriStr; lastUri.replace(String("/"), String("/")); return lastUri; } @@ -268,35 +274,8 @@ const String AutoConnectAux::_indicateUri(PageArgument& args) { * @return HTML string that should be inserted. */ const String AutoConnectAux::_insertElement(PageArgument& args) { - // Save elements owned by AutoConnectAux that caused the request. - String auxUri = args.arg("uri"); - if (auxUri.length()) { - auxUri.replace(String("/"), String("/")); - AutoConnectAux* auxPage = _ac->_aux.get(); - while (auxPage) { - if (auxPage->_uriStr == auxUri) - break; - auxPage = auxPage->_next.get(); - } - // Caused page is exist, restore elements value. - if (auxPage) { - for (size_t n = 0; n < args.size(); n++) { - String elmName = args.argName(n); - String elmValue = args.arg(n); - AutoConnectElement* elm = auxPage->getElement(elmName); - if (elm) { - if (elm->typeOf() == AC_Checkbox) - elmValue = "checked"; - auxPage->setElementValue(elmName, elmValue); - } - } - } - } - // Save invoked Aux. - _ac->_auxLastUri = _uriStr; + String body = String(""); - // Build the current Aux page. - String body = String(); if (_handler) { if (_order & AC_EXIT_AHEAD) { AC_DBG("CB in AHEAD %s\n", uri()); @@ -304,10 +283,12 @@ const String AutoConnectAux::_insertElement(PageArgument& args) { } } - for (std::size_t n = 0; n < _addonElm.size(); n++) { - AutoConnectElement& addon = _addonElm[n]; + for (AutoConnectElement& addon : _addonElm) body += addon.toHTML(); - } + //for (std::size_t n = 0; n < _addonElm.size(); n++) { + // AutoConnectElement& addon = _addonElm[n]; + // body += addon.toHTML(); + //} if (_handler) { if (_order & AC_EXIT_LATER) { @@ -362,31 +343,21 @@ PageElement* AutoConnectAux::_setupPage(String uri) { } /** - * 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; -} - -/** - * 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. + * 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 */ -AutoConnectElement& AutoConnectAux::_nullElement() { - static AutoConnectElement nullElement("", ""); - return nullElement; +void AutoConnectAux::_storeElements(WebServerClass* webServer) { + for (uint8_t n = 0; n < webServer->args(); n++) { + String elmValue = webServer->arg(n); + AutoConnectElement* elm = getElement(webServer->argName(n)); + if (elm) { + if (elm->typeOf() == AC_Checkbox) + elmValue = "checked"; + setElementValue(webServer->argName(n), elmValue); + } + } } #ifndef AUTOCONNECT_USE_JSON @@ -931,31 +902,24 @@ size_t AutoConnectAux::saveElement(Stream& out, std::vector const& names bufferSize += JSON_OBJECT_SIZE(4); if (amount != 1) bufferSize += JSON_ARRAY_SIZE(amount); - for (size_t n = 0; n < amount; n++) { - for (size_t e = 0; e < stores; e++) { - AutoConnectElement& elm = _addonElm[e]; - if (elm.name.equalsIgnoreCase(names[n])) { + + for (String name : names) + for (AutoConnectElement& elm : _addonElm) + if (elm.name.equalsIgnoreCase(name)) { bufferSize += elm.getObjectSize(); break; } - } - } if (bufferSize > 0) { DynamicJsonBuffer jb(bufferSize); if (amount == 1) { JsonObject& element = jb.createObject(); - for (size_t e = 0; e < stores; e++) { - AutoConnectElement& elm = _addonElm[e]; + for (AutoConnectElement& elm : _addonElm) if (elm.name.equalsIgnoreCase(names[0])) { elm.serialize(element); break; } - } size_n = element.printTo(out); - AC_DBG(""); - element.printTo(Serial); - AC_DBG_DUMB("\n"); } else if (amount == 0) { JsonObject& json = jb.createObject(); @@ -963,32 +927,22 @@ size_t AutoConnectAux::saveElement(Stream& out, std::vector const& names json[F(AUTOCONNECT_JSON_KEY_URI)] = _uriStr; json[F(AUTOCONNECT_JSON_KEY_MENU)] = _menu; JsonArray& elements = json.createNestedArray(F(AUTOCONNECT_JSON_KEY_ELEMENT)); - for (size_t e = 0; e < stores; e++) { + for (AutoConnectElement& elm : _addonElm) { JsonObject& element = elements.createNestedObject(); - AutoConnectElement& elm = _addonElm[e]; elm.serialize(element); } size_n = json.prettyPrintTo(out); - AC_DBG(""); - json.printTo(Serial); - AC_DBG_DUMB("\n"); } else if (amount >= 2) { JsonArray& elements = jb.createArray(); - for (size_t n = 0; n < amount; n++) { - for (size_t e = 0; e < stores; e++) { - AutoConnectElement& elm = _addonElm[e]; - if (elm.name.equalsIgnoreCase(names[n])) { + for (String name : names) + for (AutoConnectElement& elm : _addonElm) + if (elm.name.equalsIgnoreCase(name)) { JsonObject& element = elements.createNestedObject(); elm.serialize(element); break; } - } - } size_n = elements.prettyPrintTo(out); - AC_DBG(""); - elements.printTo(Serial); - AC_DBG_DUMB("\n"); } } return size_n; diff --git a/src/AutoConnectAux.h b/src/AutoConnectAux.h index 674372b..8703c99 100644 --- a/src/AutoConnectAux.h +++ b/src/AutoConnectAux.h @@ -83,6 +83,7 @@ class AutoConnectAux : public PageBuilder { const String _injectTitle(PageArgument& args) const { (void)(args); return _title; } /**< Returns title of this page to PageBuilder */ const String _injectMenu(PageArgument& args); /**< Inject menu title of this page to PageBuilder */ const String _indicateUri(PageArgument& args); /**< Inject the uri that caused the request */ + void _storeElements(WebServerClass* webServer); /**< Store element values from contained in request arguments */ static AutoConnectElement& _nullElement(void); /**< A static returning value as invalid */ #ifdef AUTOCONNECT_USE_JSON diff --git a/src/AutoConnectDefs.h b/src/AutoConnectDefs.h index e9ac948..f564942 100644 --- a/src/AutoConnectDefs.h +++ b/src/AutoConnectDefs.h @@ -65,6 +65,11 @@ #define AUTOCONNECT_HOMEURI "/" #endif // !AUTOCONNECT_HOMEURI +// AutoConnectAux form argument name +#ifndef AUTOCONNECT_AUXURI_PARAM +#define AUTOCONNECT_AUXURI_PARAM "_acuri" +#endif // !AUTOCONNECT_AUXURI_PARAM + // AutoConnect menu title #ifndef AUTOCONNECT_MENU_TITLE #define AUTOCONNECT_MENU_TITLE "AutoConnect" diff --git a/src/AutoConnectElementBasis.h b/src/AutoConnectElementBasis.h index 35fa7f3..ea1167a 100644 --- a/src/AutoConnectElementBasis.h +++ b/src/AutoConnectElementBasis.h @@ -127,6 +127,7 @@ class AutoConnectRadioBasis : virtual public AutoConnectElementBasis { const String toHTML(void) const override; void add(const String value) { _values.push_back(value); } void empty(void) { _values.clear(); } + void check(const String& value); String label; /**< A label for a subsequent radio buttons */ ACArrange_t order; /**< layout order */ diff --git a/src/AutoConnectElementBasisImpl.h b/src/AutoConnectElementBasisImpl.h index 4250fcd..6d32f29 100644 --- a/src/AutoConnectElementBasisImpl.h +++ b/src/AutoConnectElementBasisImpl.h @@ -65,9 +65,9 @@ const String AutoConnectInputBasis::toHTML(void) const { } /** -* Generate an HTML element with an