Supports AutoConnectAux

pull/41/head
Hieromon Ikasamo 6 years ago
parent c48ecfff4a
commit dc75ab7b6d
  1. 252
      examples/mqttRSSI/mqttRSSI.ino
  2. 26
      src/AutoConnect.cpp
  3. 1
      src/AutoConnect.h
  4. 184
      src/AutoConnectAux.cpp
  5. 1
      src/AutoConnectAux.h
  6. 5
      src/AutoConnectDefs.h
  7. 1
      src/AutoConnectElementBasis.h
  8. 20
      src/AutoConnectElementBasisImpl.h

@ -17,22 +17,134 @@
#include <ESP8266WiFi.h>
#elif defined(ARDUINO_ARCH_ESP32)
#include <WiFi.h>
#include <SPIFFS.h>
#endif
#include <FS.h>
#include <PubSubClient.h>
#include <AutoConnect.h>
#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": "<h2>MQTT broker settings</h2>",
"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": "<hr>"
},
{
"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": "<h4>Parameters saved as:</h4>",
"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<int>(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 + "<br>";
channelId = args.arg("channelid");
channelId.trim();
echo += "channelid: " + channelId + "<br>";
userKey = args.arg("userkey");
userKey.trim();
echo += "userkey: " + userKey + "<br>";
apiKey = args.arg("apikey");
apiKey.trim();
echo += "apikey: " + apiKey + "<br>";
String upd = args.arg("period");
updateInterval = upd.substring(0, 2).toInt() * 1000;
echo += "period: " + String(updateInterval) + "<br>";
String uniqueid = args.arg("uniqueid");
echo += "uniqueid: " + uniqueid + "<br>";
hostName = args.arg("hostname");
hostName.trim();
echo += "hostname: " + hostName + "<br>";
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 =
"<html>"
"<head>"
"<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">"
"</head>"
"<body>"
"<iframe width=\"450\" height=\"260\" style=\"transform:scale(0.79);-o-transform:scale(0.79);-webkit-transform:scale(0.79);-moz-transform:scale(0.79);-ms-transform:scale(0.79);transform-origin:0 0;-o-transform-origin:0 0;-webkit-transform-origin:0 0;-moz-transform-origin:0 0;-ms-transform-origin:0 0;border: 1px solid #cccccc;\" src=\"https://thingspeak.com/channels/454951/charts/1?bgcolor=%23ffffff&color=%23d62020&dynamic=true&type=line\"></iframe>"
"<p style=\"padding-top:10px;text-align:center\">" AUTOCONNECT_LINK(COG_24) "</p>"
"</body>"
"</html>";
#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<AutoConnectCheckbox>("uniqueid");
AutoConnectInput& hostnameElm = setting->getElement<AutoConnectInput>("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();
}

@ -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("&#47;"), 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;
}

@ -235,7 +235,6 @@ class AutoConnect {
/** Extended pages made up with AutoConnectAux */
std::unique_ptr<AutoConnectAux> _aux;
String _auxLastUri;
/** Saved configurations */
AutoConnectConfig _apConfig;

@ -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<AutoConnectRadio*>(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<AutoConnectRadio*>(elm);
elmRadio->checked = static_cast<uint8_t>(value.toInt());
elmRadio->check(value);
}
else
elm->value = value;
@ -251,12 +239,30 @@ void AutoConnectAux::_join(AutoConnect& ac) {
_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=\"luxbar-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 = _ac->_auxLastUri;
String lastUri = _uriStr;
lastUri.replace(String("/"), String("&#47;"));
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("&#47;"), 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 <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=\"luxbar-item\"><a href=\"")) + String(_uri) + String("\">") + _title + String(FPSTR("</a></li>"));
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<String> 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<String> 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;

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

@ -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"

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

@ -65,9 +65,9 @@ const String AutoConnectInputBasis::toHTML(void) const {
}
/**
* Generate an HTML <input type=radio> element with an <option> element.
* @return String an HTML string.
*/
* Generate an HTML <input type=radio> element with an <option> element.
* @return String an HTML string.
*/
const String AutoConnectRadioBasis::toHTML(void) const {
String html = String();
@ -88,6 +88,20 @@ const String AutoConnectRadioBasis::toHTML(void) const {
return html;
}
/**
* Indicate an entry with the specified value in the value's collection.
* @param value The value to indicates in the collection.
*/
void AutoConnectRadioBasis::check(const String& value) {
for (std::size_t n = 0; n < _values.size(); n++) {
String& v = _values[n];
if (v.equalsIgnoreCase(value)) {
checked = n + 1;
break;
}
}
}
/**
* Generate an HTML <select> element with an <option> element.
* The attribute value of the <option> element is given to the

Loading…
Cancel
Save