Commit 80a71660 authored by Eric Duminil's avatar Eric Duminil
Browse files

Trying to add autoconnect libraries

parent a5807d86
/**
* AutoConnect class implementation.
* @file AutoConnect.cpp
* @author hieromon@gmail.com
* @version 1.2.0
* @date 2020-11-15
* @copyright MIT license.
*/
#include "AutoConnect.h"
#ifdef ARDUINO_ARCH_ESP32
#include <esp_wifi.h>
#endif
/**
* An actual reset function dependent on the architecture
*/
#if defined(ARDUINO_ARCH_ESP8266)
#define SOFT_RESET() ESP.reset()
#define SET_HOSTNAME(x) do { WiFi.hostname(x); } while(0)
#elif defined(ARDUINO_ARCH_ESP32)
#define SOFT_RESET() ESP.restart()
#define SET_HOSTNAME(x) do { WiFi.setHostname(x); } while(0)
#endif
/**
* AutoConnect default constructor. This entry activates WebServer
* internally and the web server is allocated internal.
*/
AutoConnect::AutoConnect()
: _scanCount( 0 )
, _menuTitle( _apConfig.title )
{
memset(&_credential, 0x00, sizeof(station_config_t));
}
/**
* Run the AutoConnect site using the externally ensured ESP 8266 WebServer.
* User's added URI handler response can be included in handleClient method.
* @param webServer A reference of ESP8266WebServer instance.
*/
AutoConnect::AutoConnect(WebServerClass& webServer)
: AutoConnect()
{
_webServer = WebserverUP(&webServer, [](WebServerClass*){});
}
/**
* A destructor. Free AutoConnect web pages and release Web server.
* When the server is hosted it will be purged.
*/
AutoConnect::~AutoConnect() {
end();
}
/**
* Starts establishing WiFi connection without SSID and password.
*/
bool AutoConnect::begin(void) {
return begin(nullptr, nullptr, _apConfig.beginTimeout);
}
/**
* Starts establishing WiFi connection.
* Before establishing, start the Web server and DNS server for the captive
* portal. Then begins connection establishment in WIFI_STA mode. If
* connection can not established with the specified SSID and password,
* switch to WIFI_AP_STA mode and activate SoftAP.
* @param ssid SSID to be connected.
* @param passphrase Password for connection.
* @param timeout A time out value in milliseconds for waiting connection.
* @return true Connection established, AutoConnect service started with WIFI_STA mode.
* @return false Could not connected, Captive portal started with WIFI_AP_STA mode.
*/
bool AutoConnect::begin(const char* ssid, const char* passphrase, unsigned long timeout) {
bool cs;
// Overwrite for the current timeout value.
if (timeout == 0)
timeout = _apConfig.beginTimeout;
if (_apConfig.preserveAPMode && !_apConfig.autoRise) {
// Captive portal will not be started on connection failure. Enable Station mode
// without disabling any current soft AP.
cs = WiFi.enableSTA(true);
AC_DBG("WiFi mode %d maintained, STA %s\n", WiFi.getMode(), cs ? "enabled" : "unavailable");
}
else {
// Start WiFi connection with station mode.
WiFi.softAPdisconnect(true);
if (!WiFi.mode(WIFI_STA))
AC_DBG("Unable start WIFI_STA\n");
delay(100);
}
// Set host name
if (_apConfig.hostName.length())
SET_HOSTNAME(_apConfig.hostName.c_str());
// Start Ticker according to the WiFi condition with Ticker is available.
if (_apConfig.ticker) {
_ticker.reset(new AutoConnectTicker(_apConfig.tickerPort, _apConfig.tickerOn));
if (WiFi.status() != WL_CONNECTED)
_ticker->start(AUTOCONNECT_FLICKER_PERIODDC, (uint8_t)AUTOCONNECT_FLICKER_WIDTHDC);
}
// If the portal is requested promptly skip the first WiFi.begin and
// immediately start the portal.
if (_apConfig.immediateStart) {
cs = false;
AC_DBG("Start the portal immediately\n");
}
else {
station_config_t current;
// Restore current STA configuration
if (_getConfigSTA(&current))
AC_DBG("Current:%.32s\n", current.ssid);
// Prepare valid configuration according to the WiFi connection right order.
cs = true;
_rfAdHocBegin = ssid == nullptr ? false : (strlen(ssid) > 0);
if (_rfAdHocBegin) {
// Save for autoReconnect
strcpy(reinterpret_cast<char*>(_credential.ssid), ssid);
if (passphrase)
strcpy(reinterpret_cast<char*>(_credential.password), passphrase);
}
else {
// AC_PRINCIPLE_RSSI is available when SSID and password are not provided.
if (_apConfig.principle == AC_PRINCIPLE_RSSI) {
// Find the strongest signal from the broadcast among the saved credentials.
if ((cs = _loadCurrentCredential(reinterpret_cast<char*>(current.ssid), reinterpret_cast<char*>(current.password), AC_PRINCIPLE_RSSI, false))) {
ssid = reinterpret_cast<const char*>(current.ssid);
passphrase = reinterpret_cast<const char*>(current.password);
AC_DBG("Adopted:%.32s\n", ssid);
}
}
_loadAvailCredential(reinterpret_cast<const char*>(current.ssid));
}
if (cs) {
// Advance configuration for STA mode. Restore previous configuration of STA.
// _loadAvailCredential(reinterpret_cast<const char*>(current.ssid));
if (!_configSTA(_apConfig.staip, _apConfig.staGateway, _apConfig.staNetmask, _apConfig.dns1, _apConfig.dns2))
return false;
// Try to connect by STA immediately.
if (!_rfAdHocBegin)
cs = WiFi.begin() != WL_CONNECT_FAILED;
else {
_disconnectWiFi(false);
cs = WiFi.begin(ssid, passphrase) != WL_CONNECT_FAILED;
}
AC_DBG("WiFi.begin(%s%s%s)", ssid == nullptr ? "" : ssid, passphrase == nullptr ? "" : ",", passphrase == nullptr ? "" : passphrase);
if (cs)
cs = _waitForConnect(timeout) == WL_CONNECTED;
else {
AC_DBG_DUMB(" failed\n");
}
}
// Reconnect with a valid credential as the autoReconnect option is enabled.
if (!cs && _apConfig.autoReconnect && !_rfAdHocBegin) {
// Load a valid credential.
char ssid_c[sizeof(station_config_t::ssid) + sizeof('\0')];
char password_c[sizeof(station_config_t::password) + sizeof('\0')];
AC_DBG("autoReconnect");
if ((cs = _loadCurrentCredential(ssid_c, password_c, _apConfig.principle, strlen(reinterpret_cast<const char*>(current.ssid)) > 0))) {
// Try to reconnect with a stored credential.
AC_DBG_DUMB(", %s(%s) loaded\n", ssid_c, _apConfig.principle == AC_PRINCIPLE_RECENT ? "RECENT" : "RSSI");
const char* psk = strlen(password_c) ? password_c : nullptr;
_configSTA(IPAddress(_credential.config.sta.ip), IPAddress(_credential.config.sta.gateway), IPAddress(_credential.config.sta.netmask), IPAddress(_credential.config.sta.dns1), IPAddress(_credential.config.sta.dns2));
cs = WiFi.begin(ssid_c, psk) != WL_CONNECT_FAILED;
AC_DBG("WiFi.begin(%s%s%s)", ssid_c, psk == nullptr ? "" : ",", psk == nullptr ? "" : psk);
if (cs)
cs = _waitForConnect(timeout) == WL_CONNECTED;
}
if (!cs) {
AC_DBG_DUMB(" failed\n");
}
}
}
_currentHostIP = WiFi.localIP();
// End first begin process, the captive portal specific process starts here.
if (cs) {
// Activate AutoConnectUpdate if it is attached and incorporate it into the AutoConnect menu.
if (_update)
_update->enable();
}
// Rushing into the portal.
else {
// Connection unsuccessful, launch the captive portal.
// The captive portal is effective at the autoRise is valid only.
if (_apConfig.autoRise) {
// Change WiFi working mode, Enable AP with STA
WiFi.setAutoConnect(false);
_disconnectWiFi(false);
// Activate the AP mode with configured softAP and start the access point.
_softAP();
_currentHostIP = WiFi.softAPIP();
// Fork to the exit routine that starts captive portal.
cs = _onDetectExit ? _onDetectExit(_currentHostIP) : true;
// Start Web server when TCP connection is enabled.
_startWebServer();
// Start captive portal without cancellation by DetectExit.
if (cs) {
// Prepare for redirecting captive portal detection.
// Pass all URL requests to _captivePortal to disguise the captive portal.
_startDNSServer();
// The following two lines are the trick statements.
// They have the effect of avoiding unintended automatic
// reconnection by autoReconnect within handleClient.
// Also retainPortal too.
bool actReconnect = _apConfig.autoReconnect;
bool actRetainPortal = _apConfig.retainPortal;
_apConfig.autoReconnect = false;
_apConfig.retainPortal = true;
// Start the captive portal to make a new connection
bool hasTimeout = false;
_portalAccessPeriod = millis();
while (WiFi.status() != WL_CONNECTED && !_rfReset) {
handleClient();
// By an exit routine to escape from Captive portal
if (_whileCaptivePortal) {
if (!_whileCaptivePortal())
break;
}
// Force execution of queued processes.
yield();
// Check timeout
if ((hasTimeout = _hasTimeout(_apConfig.portalTimeout))) {
AC_DBG("CP timeout exceeded:%ld\n", millis() - _portalAccessPeriod);
break;
}
}
cs = WiFi.status() == WL_CONNECTED;
// Restore actual autoReconnect and retainPortal settings.
_apConfig.autoReconnect = actReconnect;
_apConfig.retainPortal = actRetainPortal;
// Captive portal staying time exceeds timeout,
// Close the portal if an option for keeping the portal is false.
if (!cs && hasTimeout) {
if (_apConfig.retainPortal) {
_purgePages();
AC_DBG("Maintain portal\n");
}
else
_stopPortal();
}
}
}
else {
AC_DBG("Suppress autoRise\n");
}
}
// It doesn't matter the connection status for launching the Web server.
if (!_responsePage)
_startWebServer();
return cs;
}
/**
* Configure AutoConnect portal access point.
* @param ap SSID for access point.
* @param psk Password for access point.
*/
bool AutoConnect::config(const char* ap, const char* password) {
_apConfig.apid = String(ap);
_apConfig.psk = String(password);
return true; //_config();
}
/**
* Configure AutoConnect portal access point.
* @param Config AutoConnectConfig class instance.
*/
bool AutoConnect::config(AutoConnectConfig& Config) {
_apConfig = Config;
return true; //_config();
}
/**
* Configure access point.
* Set up access point with internal AutoConnectConfig parameter corrected
* by Config method.
*/
bool AutoConnect::_configAP(void) {
if (static_cast<uint32_t>(_apConfig.apip) == 0U || static_cast<uint32_t>(_apConfig.gateway) == 0U || static_cast<uint32_t>(_apConfig.netmask) == 0U) {
AC_DBG("Warning: Contains invalid SoftAPIP address(es).\n");
}
bool rc = WiFi.softAPConfig(_apConfig.apip, _apConfig.gateway, _apConfig.netmask);
AC_DBG("SoftAP configure %s, %s, %s %s\n", _apConfig.apip.toString().c_str(), _apConfig.gateway.toString().c_str(), _apConfig.netmask.toString().c_str(), rc ? "" : "failed");
return rc;
}
/**
* Advance configuration for STA mode.
* @param ip IP address
* @param gateway Gateway address
* @param netmask Netmask
* @param dns1 Primary DNS address
* @param dns2 Secondary DNS address
* @return true Station successfully configured
* @return false WiFi.config failed
*/
bool AutoConnect::_configSTA(const IPAddress& ip, const IPAddress& gateway, const IPAddress& netmask, const IPAddress& dns1, const IPAddress& dns2) {
bool rc;
AC_DBG("WiFi.config(IP=%s, Gateway=%s, Subnetmask=%s, DNS1=%s, DNS2=%s)\n", ip.toString().c_str(), gateway.toString().c_str(), netmask.toString().c_str(), dns1.toString().c_str(), dns2.toString().c_str());
if (!(rc = WiFi.config(ip, gateway, netmask, dns1, dns2))) {
AC_DBG("failed, AC::begin abort\n");
}
#ifdef ARDUINO_ARCH_ESP8266
AC_DBG("DHCP client(%s)\n", wifi_station_dhcpc_status() == DHCP_STOPPED ? "STOPPED" : "STARTED");
#endif
return rc;
}
/**
* Get URI to redirect at boot. It uses the URI according to the
* AutoConnectConfig::bootUti setting with the AutoConnectConfig::homeUri
* as the boot path.
* @return the boot uri.
*/
String AutoConnect::_getBootUri(void) {
if (_apConfig.bootUri == AC_ONBOOTURI_ROOT)
return String(AUTOCONNECT_URI);
else if (_apConfig.bootUri == AC_ONBOOTURI_HOME)
return _apConfig.homeUri.length() > 0 ? _apConfig.homeUri : String("/");
else
return _emptyString;
}
/**
* Obtains the currently established AP connection to determine if the
* station configuration needs to run before the first WiFi.begin.
* Get the SSID of the currently connected AP stored in the ESP module
* by using the SDK API natively.
* AutoConnect::begin retrieves the IP configuration from the stored
* credentials of AutoConnectCredential based on that SSID and executes
* WiFi.config before WiFi.begin.
* @param config Station configuration stored in the ESP module.
* @return true The config parameter has obtained configuration.
* @return false Station configuration does not exist.
*/
bool AutoConnect::_getConfigSTA(station_config_t* config) {
bool rc;
uint8_t* ssid;
uint8_t* bssid;
#if defined(ARDUINO_ARCH_ESP8266)
struct station_config current;
ssid = current.ssid;
bssid = current.bssid;
rc = wifi_station_get_config(&current);
#elif defined(ARDUINO_ARCH_ESP32)
wifi_config_t current;
ssid = current.sta.ssid;
bssid = current.sta.bssid;
rc = (esp_wifi_get_config(WIFI_IF_STA, &current) == ESP_OK);
#endif
if (rc) {
memcpy(config->ssid, ssid, sizeof(station_config_t::ssid));
memcpy(config->bssid, bssid, sizeof(station_config_t::bssid));
}
return rc;
}
/**
* Stops AutoConnect captive portal service.
*/
void AutoConnect::end(void) {
_currentPageElement.reset();
_ticker.reset();
_update.reset();
_ota.reset();
_stopPortal();
_dnsServer.reset();
_webServer.reset();
}
/**
* Get the total amount of memory required to hold the AutoConnect credentials
* and any custom configuration settings stored in EEPROM.
* This function is available only for ESP8266 use.
* @return Combined size of AutoConnect credentials and custom settings.
*/
uint16_t AutoConnect::getEEPROMUsedSize(void) {
#if defined(ARDUINO_ARCH_ESP8266)
AutoConnectCredential credentials(_apConfig.boundaryOffset);
return _apConfig.boundaryOffset + credentials.dataSize();
#elif defined(ARDUINO_ARCH_ESP32)
return 0;
#endif
}
/**
* Handling for the AutoConnect web interface.
* Invoke the handleClient of parent web server to process client request of
* AutoConnect WEB interface.
* No effects when the web server is not available.
*/
void AutoConnect::handleClient(void) {
// Is there DNS Server process next request?
if (_dnsServer)
_dnsServer->processNextRequest();
// handleClient valid only at _webServer activated.
if (_webServer)
_webServer->handleClient();
handleRequest();
}
/**
* Handling for the AutoConnect menu request.
*/
void AutoConnect::handleRequest(void) {
bool skipPostTicker;
// Controls reconnection and portal startup when WiFi is disconnected.
if (WiFi.status() != WL_CONNECTED) {
// Launch the captive portal when SoftAP is active and autoRise is
// specified to be maintained.
// Pass all URL requests to _captivePortal to disguise the captive portal.
if (_apConfig.retainPortal && _apConfig.autoRise) {
if (!(WiFi.getMode() & WIFI_AP)) {
_softAP();
_currentHostIP = WiFi.softAPIP();
}
if (!_dnsServer) {
bool cs = _onDetectExit ? _onDetectExit(_currentHostIP) : true;
if (cs)
_startDNSServer();
}
}
// AutoConnectConfig::reconnectInterval allows a dynamic connection
// to a known access point without blocking the execution of
// Sketch's loop function.
if (_apConfig.autoReconnect && _apConfig.reconnectInterval > 0) {
int8_t sc = WiFi.scanComplete();
// Scan has not triggered then starts asynchrony scan and repeats at
// intervals of time with AutoConnectConfig::reconnectInterval value
// multiplied by AUTOCONNECT_UNITTIME.
if (sc == WIFI_SCAN_FAILED) {
if (millis() - _attemptPeriod > ((unsigned long)_apConfig.reconnectInterval * AUTOCONNECT_UNITTIME * 1000)) {
int8_t sn = WiFi.scanNetworks(true, true);
AC_DBG("autoReconnect %s\n", sn == WIFI_SCAN_RUNNING ? "running" : "failed");
_attemptPeriod = millis();
(void)(sn);
}
}
// After the background scan is complete, seek a connectable
// access point. If it is found, it will generate a connection
// request inside.
else if (sc != WIFI_SCAN_RUNNING) {
AC_DBG("%d network(s) found\n", (int)sc);
if (sc > 0) {
if (_seekCredential(_apConfig.principle, _rfAdHocBegin ? AC_SEEKMODE_CURRENT : AC_SEEKMODE_ANY))
_rfConnect = true;
}
WiFi.scanDelete();
}
}
}
else
_attemptPeriod = millis();
// Handling processing requests to AutoConnect.
if (_rfConnect) {
// Leave from the AP currently.
if (WiFi.status() == WL_CONNECTED)
_disconnectWiFi(true);
// Leave current AP, reconfigure station
_configSTA(_apConfig.staip, _apConfig.staGateway, _apConfig.staNetmask, _apConfig.dns1, _apConfig.dns2);
// Purge scan results to initialize the asynchronous network scan that
// will be triggered by disconnection during handleRequests.
WiFi.scanDelete();
// An attempt to establish a new AP.
int32_t ch = _connectCh == 0 ? _apConfig.channel : _connectCh;
char ssid_c[sizeof(station_config_t::ssid) + 1];
char password_c[sizeof(station_config_t::password) + 1];
*ssid_c = '\0';
strncat(ssid_c, reinterpret_cast<const char*>(_credential.ssid), sizeof(ssid_c) - 1);
*password_c = '\0';
strncat(password_c, reinterpret_cast<const char*>(_credential.password), sizeof(password_c) - 1);
AC_DBG("WiFi.begin(%s%s%s) ch(%d)", ssid_c, strlen(password_c) ? "," : "", strlen(password_c) ? password_c : "", (int)ch);
if (WiFi.begin(ssid_c, password_c, ch) != WL_CONNECT_FAILED) {
// Wait for the connection attempt to complete and send a response
// page to notify the connection result.
// End the current session to complete a response page transmission.
_rsConnect = _waitForConnect(_apConfig.beginTimeout);
do {
_webServer->handleClient();
} while (_webServer->client());
if (_rsConnect == WL_CONNECTED) {
// WLAN successfully connected then release the DNS server.
// Also, stop WIFI_AP if retainPortal not specified.
_stopDNSServer();
if (!_apConfig.retainPortal) {
WiFi.softAPdisconnect(true);
WiFi.enableAP(false);
}
else {
AC_DBG("Maintain SoftAP\n");
}
// It will automatically save the credential which was able to
// establish current connection.
if (WiFi.BSSID() != NULL) {
memcpy(_credential.bssid, WiFi.BSSID(), sizeof(station_config_t::bssid));
_currentHostIP = WiFi.localIP();
_redirectURI = String(F(AUTOCONNECT_URI_SUCCESS));
// Save current credential
if (_apConfig.autoSave == AC_SAVECREDENTIAL_AUTO) {
AutoConnectCredential credit(_apConfig.boundaryOffset);
if (credit.save(&_credential)) {
AC_DBG("%.*s credential saved\n", sizeof(_credential.ssid), reinterpret_cast<const char*>(_credential.ssid));
}
else {
AC_DBG("credential %.*s save failed\n", sizeof(_credential.ssid), reinterpret_cast<const char*>(_credential.ssid));
}
}
// Ensures that keeps a connection with the current AP
// while the portal behaves.
_setReconnect(AC_RECONNECT_SET);
}
else {
AC_DBG("%.*s has no BSSID, saving is unavailable\n", sizeof(_credential.ssid), reinterpret_cast<const char*>(_credential.ssid));
}
// Activate AutoConnectUpdate if it is attached and incorporate
// it into the AutoConnect menu.
if (_update)
_update->enable();
}
else {
_currentHostIP = WiFi.softAPIP();
_redirectURI = String(F(AUTOCONNECT_URI_FAIL));
_disconnectWiFi(false);
wl_status_t wl = WiFi.status();
unsigned long tm = millis();
while (wl != WL_IDLE_STATUS && wl != WL_DISCONNECTED && wl != WL_NO_SSID_AVAIL) {
if (millis() - tm > 3000)
break;
delay(1);
yield();
wl = WiFi.status();
}
AC_DBG("Quit connecting, status(%d)\n", wl);
}
}
_rfConnect = false;
}
if (_rfReset) {
// Reset or disconnect by portal operation result
_stopPortal();
AC_DBG("Reset\n");
delay(1000);
SOFT_RESET();
delay(1000);
}
if (_rfDisconnect) {
// Response for disconnection request is not completed while
// the session exists.
if (!_webServer->client()) {
// Disconnect from the current AP.
_disconnectWiFi(false);
while (WiFi.status() == WL_CONNECTED) {
delay(10);
yield();
}
AC_DBG("Disconnected ");
if ((WiFi.getMode() & WIFI_AP) && !_apConfig.retainPortal) {
_stopPortal();
}
else {
if (_dnsServer)
AC_DBG_DUMB("- Portal maintained");
AC_DBG_DUMB("\n");
}
// Reset disconnection request
_rfDisconnect = false;
if (_apConfig.autoReset) {
delay(1000);
SOFT_RESET();
delay(1000);
}
}
}
// Indicate that not disturb the ticker cycle during OTA.
// It will be set to true during OTA in progress due to
// subsequent OTA handling.
skipPostTicker = false;
// Handle the update behaviors for attached AutoConnectUpdate.
if (_update) {
_update->handleUpdate();
if (_update->status() == UPDATE_PROGRESS)
skipPostTicker = true;
}
// Attach AutoConnectOTA if OTA is available.
if (_apConfig.ota == AC_OTA_BUILTIN) {
if (!_ota) {
_ota.reset(new AutoConnectOTA());
_ota->attach(*this);
_ota->authentication(_apConfig.auth);
_ota->setTicker(_apConfig.tickerPort, _apConfig.tickerOn);
}
}
// Post-process for AutoConnectOTA
if (_ota) {
if (_ota->status() == AutoConnectOTA::OTA_RIP) {
// Indicate the reboot at the next handleClient turn
// with on completion of the update via OTA.
if (_webServer->client().connected()) {
_webServer->client().setNoDelay(true);
_ota->reset();
}
if (_ota->dest() == AutoConnectOTA::OTA_DEST_FIRM)
// OTA for firmware update requires module reset.
_rfReset = true;
}
else if (_ota->status() == AutoConnectOTA::OTA_PROGRESS)
skipPostTicker = true;
// Reflect the menu display specifier from AutoConnectConfig to
// AutoConnectOTA page
_ota->menu(_apConfig.menuItems & AC_MENUITEM_UPDATE);
}
// Post-process for ticker
// Adjust the ticker cycle to the latest WiFi connection state.
if (_ticker && !skipPostTicker) {
uint8_t tWidth;
uint32_t tCycle = WiFi.status() == WL_CONNECTED ? 0 : AUTOCONNECT_FLICKER_PERIODDC;
tWidth = AUTOCONNECT_FLICKER_WIDTHDC;
if (WiFi.getMode() & WIFI_AP) {
tCycle = AUTOCONNECT_FLICKER_PERIODAP;
tWidth = AUTOCONNECT_FLICKER_WIDTHAP;
}
if (tCycle != _ticker->getCycle()) {
_ticker->stop();
if (tCycle)
_ticker->start(tCycle, tWidth);
}
}
}
/**
* Put a user site's home URI.
* The URI specified by home is linked from "HOME" in the AutoConnect
* portal menu.
* @param uri A URI string of user site's home.
*/
void AutoConnect::home(const String& uri) {
_apConfig.homeUri = uri;
}
/**
* Returns the current hosted ESP8266WebServer.
*/
WebServerClass& AutoConnect::host(void) {
return *_webServer;
}
/**
* Returns AutoConnectAux instance of specified.
* @param uri An uri string.
* @return A pointer of AutoConnectAux instance.
*/
AutoConnectAux* AutoConnect::aux(const String& uri) const {
AutoConnectAux* aux_p = _aux;
while (aux_p) {
if (!strcmp(aux_p->uri(), uri.c_str()))
break;
aux_p = aux_p->_next;
}
return aux_p;
}
/**
* Creates an AutoConnectAux dynamically with the specified URI and
* integrates it into the menu. Returns false if a menu item with
* the same URI already exists.
* @param uri An uri of a new item to add
* @param title Title of the menu item
* @return A pointer to an added AutoConnectAux
*/
AutoConnectAux* AutoConnect::append(const String& uri, const String& title) {
AutoConnectAux* reg = aux(uri);
if (!reg) {
reg = new AutoConnectAux(uri, title);
reg->_deletable = true;
join(*reg);
return reg;
}
AC_DBG("%s has already listed\n", uri.c_str());
return nullptr;
}
/**
* Creates an AutoConnectAux dynamically with the specified URI and
* integrates it into the menu. It will register the request handler
* for the WebServer after the addMenuItem works. It has similar
* efficacy to calling addMenuItem and WebSever::on at once.
* @param uri An uri of a new item to add
* @param title Title of the menu item
* @param handler Function of the request handler for WebServer class
* @return true Added
* @return A pointer to an added AutoConnectAux
*/
AutoConnectAux* AutoConnect::append(const String& uri, const String& title, WebServerClass::THandlerFunction handler) {
if (_webServer) {
AutoConnectAux* reg = append(uri, title);
if (reg)
_webServer->on(uri, handler);
return reg;
}
AC_DBG("No WebServer instance\n");
return nullptr;
}
/**
* Detach a AutoConnectAux from the portal.
* @param uri An uri of the AutoConnectAux should be released
* @return true Specified AUX has released
* @return false Specified AUX not registered
*/
bool AutoConnect::detach(const String &uri) {
AutoConnectAux** self = &_aux;
while (*self) {
if (!strcmp((*self)->uri(), uri.c_str())) {
AC_DBG("%s released\n", (*self)->uri());
AutoConnectAux* ref = *self;
*self = (*self)->_next;
if (ref->_deletable)
delete ref;
return true;
}
self = &((*self)->_next);
}
AC_DBG("%s not listed\n", uri.c_str());
return false;
}
/**
* Append auxiliary pages made up with AutoConnectAux.
* @param aux A reference to AutoConnectAux that made up
* the auxiliary page to be added.
*/
void AutoConnect::join(AutoConnectAux& aux) {
if (_aux)
_aux->_concat(aux);
else
_aux = &aux;
aux._join(*this);
AC_DBG("%s on hands\n", aux.uri());
}
/**
* Append auxiliary pages made up with AutoConnectAux.
* @param aux A vector of reference to AutoConnectAux that made up
* the auxiliary page to be added.
*/
void AutoConnect::join(AutoConnectAuxVT auxVector) {
for (std::reference_wrapper<AutoConnectAux> aux : auxVector)
join(aux.get());
}
/**
* Register the exit routine for AutoConnectAux.
* @param uri Specify the URI of the AutoConnectAux page that
* registers the exit routine.
* @param handler A handler function of the exit routine.
* @param order Specify an enumeration type of
* AutoConnectExitOrder_t for the call timing of the exit routine.
* @return true An exit routine registered.
* @return false AutoConnectAux page for the specified URI is not
* registered.
*/
bool AutoConnect::on(const String& uri, const AuxHandlerFunctionT handler, AutoConnectExitOrder_t order) {
AutoConnectAux* aux = _aux;
while (aux) {
if (!strcmp(uri.c_str(), aux->uri())) {
aux->on(handler, order);
return true;
}
aux = aux->_next;
}
return false;
}
/**
* Register the exit routine that is being called when WiFi connected.
* @param fn A function of the exit routine.
*/
void AutoConnect::onConnect(ConnectExit_ft fn) {
_onConnectExit = fn;
}
/**
* Register the exit routine for the starting captive portal.
* @param fn A function of the exit routine.
*/
void AutoConnect::onDetect(DetectExit_ft fn) {
_onDetectExit = fn;
}
/**
* Register the handler function for undefined url request detected.
* @param fn A function of the not found handler.
*/
void AutoConnect::onNotFound(WebServerClass::THandlerFunction fn) {
_notFoundHandler = fn;
}
/**
* Register an exit routine to call during the captive portal.
* @param fn A function of the exit routine that calls while captive portal.
*/
void AutoConnect::whileCaptivePortal(WhileCaptivePortalExit_ft fn) {
_whileCaptivePortal = fn;
}
/**
* Load current available credential
* @param ssid A pointer to the buffer that SSID should be stored.
* @param password A pointer to the buffer that password should be stored.
* @param principle WiFi connection principle.
* @param excludeCurrent Skip loading the current SSID.
* @return true Current SSID and password returned.
* @return false There is no available SSID.
*/
bool AutoConnect::_loadCurrentCredential(char* ssid, char* password, const AC_PRINCIPLE_t principle, const bool excludeCurrent) {
bool rc;
if ((rc = _loadAvailCredential(nullptr, principle, excludeCurrent))) {
strcpy(ssid, reinterpret_cast<const char*>(_credential.ssid));
strcpy(password, reinterpret_cast<const char*>(_credential.password));
}
return rc;
}
/**
* Load stored credentials that match nearby WLANs.
* @param ssid SSID which should be loaded. If nullptr is assigned, search SSID with WiFi.scan.
* @param principle WiFi connection principle.
* @param excludeCurrent Skip loading the current SSID.
* @return true A matched credential of BSSID was loaded.
*/
bool AutoConnect::_loadAvailCredential(const char* ssid, const AC_PRINCIPLE_t principle, const bool excludeCurrent) {
AutoConnectCredential credential(_apConfig.boundaryOffset);
if (credential.entries() > 0) {
// Scan the vicinity only when the saved credentials are existing.
if (!ssid) {
int8_t nn = WiFi.scanNetworks(false, true);
AC_DBG_DUMB(", %d network(s) found", (int)nn);
if (nn > 0)
return _seekCredential(principle, excludeCurrent ? AC_SEEKMODE_NEWONE : AC_SEEKMODE_ANY);
}
// The SSID to load was specified.
// Set the IP configuration globally from the saved credential.
else if (strlen(ssid))
if (credential.load(ssid, &_credential) >= 0) {
if (_credential.dhcp == STA_STATIC) {
_apConfig.staip = static_cast<IPAddress>(_credential.config.sta.ip);
_apConfig.staGateway = static_cast<IPAddress>(_credential.config.sta.gateway);
_apConfig.staNetmask = static_cast<IPAddress>(_credential.config.sta.netmask);
_apConfig.dns1 = static_cast<IPAddress>(_credential.config.sta.dns1);
_apConfig.dns2 = static_cast<IPAddress>(_credential.config.sta.dns2);
}
return true;
}
}
return false;
}
/**
* Aims a connectable access point by seeking with the WiFi scan results.
* The collation uses the saved credentials, and the connection priority
* follows AutoConnectConfig::principle.
* Either BSSID or SSID of the collation key is determined at compile
* time according to the AUTOCONNECT_APKEY_SSID definition.
* @param principle WiFi connection principle.
* @param mode Seek mode for whether to target a specific SSID.
* @return true A matched credential of BSSID was loaded.
*/
bool AutoConnect::_seekCredential(const AC_PRINCIPLE_t principle, const AC_SEEKMODE_t mode) {
AutoConnectCredential credential(_apConfig.boundaryOffset);
station_config_t validConfig; // Temporary to find the strongest RSSI.
int32_t minRSSI = -120; // Min value to find the strongest RSSI.
// Seek SSID
const char* currentSSID = WiFi.SSID().c_str();
for (uint8_t n = 0; n < WiFi.scanComplete(); n++) {
if (mode == AC_SEEKMODE_CURRENT) {
// It finds a specific access point that matches the SSID
// specified by AutoConnect::begin.
if (!strcmp(WiFi.SSID(n).c_str(), reinterpret_cast<const char*>(_credential.ssid)))
return true;
}
else {
// Seek valid configuration according to the WiFi connection principle.
// Verify that an available SSIDs meet AC_PRINCIPLE_t requirements.
for (uint8_t i = 0; i < credential.entries(); i++) {
credential.load(i, &_credential);
// The access point collation key is determined at compile time
// according to the AUTOCONNECT_APKEY_SSID definition, which is
// either BSSID or SSID.
if (_isValidAP(_credential, n)) {
if ((mode == AC_SEEKMODE_NEWONE) && (strlen(currentSSID) > 0))
continue;
if (WiFi.RSSI(n) < _apConfig.minRSSI) {
// Excepts SSID that has weak RSSI under the lower limit.
AC_DBG("%s:%" PRId32 "dBm, rejected\n", reinterpret_cast<const char*>(_credential.ssid), WiFi.RSSI(n));
continue;
}
// Determine valid credential
switch (principle) {
case AC_PRINCIPLE_RECENT:
// By BSSID, exit to keep the credential just loaded.
return true;
case AC_PRINCIPLE_RSSI:
// Verify that most strong radio signal.
// Continue seeking to find the strongest WIFI signal SSID.
if (WiFi.RSSI(n) > minRSSI) {
minRSSI = WiFi.RSSI(n);
memcpy(&validConfig, &_credential, sizeof(station_config_t));
}
break;
}
break;
}
}
}
}
// Increasing the minSSI will indicate the successfully sought for AC_PRINCIPLE_RSSI.
// Restore the credential that has maximum RSSI.
if (minRSSI > -120) {
memcpy(&_credential, &validConfig, sizeof(station_config_t));
return true;
}
return false;
}
/**
* Changes WiFi mode to enable SoftAP and configure IPs with current
* AutoConnectConfig settings then start SoftAP.
*/
void AutoConnect::_softAP(void) {
WiFi.enableAP(true);
while (!(WiFi.getMode() & WIFI_AP)) {
delay(1);
yield();
}
#if defined(ARDUINO_ARCH_ESP8266)
_configAP();
#endif
WiFi.softAP(_apConfig.apid.c_str(), _apConfig.psk.c_str(), _apConfig.channel, _apConfig.hidden);
do {
delay(100);
yield();
} while (!WiFi.softAPIP());
#if defined(ARDUINO_ARCH_ESP32)
_configAP();
#endif
if (_apConfig.apip) {
do {
delay(100);
yield();
} while (WiFi.softAPIP() != _apConfig.apip);
}
AC_DBG("SoftAP %s/%s Ch(%d) IP:%s %s\n", _apConfig.apid.c_str(), _apConfig.psk.c_str(), _apConfig.channel, WiFi.softAPIP().toString().c_str(), _apConfig.hidden ? "hidden" : "");
}
/**
* Starts Web server for AutoConnect service.
*/
void AutoConnect::_startWebServer(void) {
// Boot Web server
if (!_webServer) {
// Only when hosting WebServer internally
_webServer = WebserverUP(new WebServerClass(AUTOCONNECT_HTTPPORT), std::default_delete<WebServerClass>() );
AC_DBG("WebServer allocated\n");
}
// Discard the original the not found handler to redirect captive portal detection.
// It is supposed to evacuate but ESP8266WebServer::_notFoundHandler is not accessible.
_webServer->onNotFound(std::bind(&AutoConnect::_handleNotFound, this));
// here, Prepare PageBuilders for captive portal
if (!_responsePage) {
_responsePage.reset( new PageBuilder() );
_responsePage->exitCanHandle(std::bind(&AutoConnect::_classifyHandle, this, std::placeholders::_1, std::placeholders::_2));
_responsePage->onUpload(std::bind(&AutoConnect::_handleUpload, this, std::placeholders::_1, std::placeholders::_2));
_responsePage->insert(*_webServer);
_webServer->begin();
AC_DBG("http server started\n");
}
else {
AC_DBG("http server readied\n");
}
}
/**
* Starts DNS server for Captive portal.
*/
void AutoConnect::_startDNSServer(void) {
// Boot DNS server, set up for captive portal redirection.
if (!_dnsServer) {
_dnsServer.reset(new DNSServer());
_dnsServer->setErrorReplyCode(DNSReplyCode::NoError);
_dnsServer->start(AUTOCONNECT_DNSPORT, "*", WiFi.softAPIP());
AC_DBG("DNS server started\n");
}
}
/**
* Stops DNS server.
* Free its instance to avoid multiple launches when the retainPortal enabled.
*/
void AutoConnect::_stopDNSServer(void) {
if (_dnsServer) {
_dnsServer->stop();
_dnsServer.reset();
AC_DBG("DNS server stopped\n");
}
}
/**
* Disconnect from the AP and stop the AutoConnect portal.
* Stops DNS server and flush tcp sending.
*/
void AutoConnect::_stopPortal(void) {
_stopDNSServer();
if (_webServer) {
_webServer->client().stop();
delay(1000);
}
_setReconnect(AC_RECONNECT_RESET);
WiFi.softAPdisconnect(false);
AC_DBG("Portal stopped\n");
}
/**
* Redirect to captive portal if we got a request for another domain.
* Return true in that case so the page handler do not try to handle the request again.
*/
bool AutoConnect::_captivePortal(void) {
String hostHeader = _webServer->hostHeader();
if (!_isIP(hostHeader) && (hostHeader != WiFi.localIP().toString()) && (!hostHeader.endsWith(F(".local")))) {
AC_DBG("Detected application, %s, %s\n", hostHeader.c_str(), WiFi.localIP().toString().c_str());
String location = String(F("http://")) + _webServer->client().localIP().toString() + _getBootUri();
_webServer->sendHeader(String(F("Location")), location, true);
_webServer->send(302, String(F("text/plain")), _emptyString);
_webServer->client().flush();
_webServer->client().stop();
return true;
}
return false;
}
/**
* Check whether the stay-time in the captive portal has a timeout.
* If the station is connected, the time measurement will be reset.
* @param timeout The time limit for keeping the captive portal.
* @return true There is no connection from the station even the time limit exceeds.
* @return false Connectionless duration has not exceeded yet.
*/
bool AutoConnect::_hasTimeout(unsigned long timeout) {
uint8_t staNum;
if (!_apConfig.portalTimeout)
return false;
#if defined(ARDUINO_ARCH_ESP8266)
staNum = 0;
struct station_info* station = wifi_softap_get_station_info();
while (station) {
staNum++;
station = STAILQ_NEXT(station, next);
}
wifi_softap_free_station_info();
#elif defined(ARDUINO_ARCH_ESP32)
staNum = WiFi.softAPgetStationNum();
#endif
if (staNum)
_portalAccessPeriod = millis();
return (millis() - _portalAccessPeriod > timeout) ? true : false;
}
/**
* A handler that redirects access to the captive portal to the connection
* configuration page.
*/
void AutoConnect::_handleNotFound(void) {
if (!_captivePortal()) {
if (_notFoundHandler) {
_notFoundHandler();
}
else {
PageElement page404(_PAGE_404, { { String(F("HEAD")), std::bind(&AutoConnect::_token_HEAD, this, std::placeholders::_1) } });
String html = page404.build();
_webServer->sendHeader(String(F("Cache-Control")), String(F("no-cache, no-store, must-revalidate")), true);
_webServer->sendHeader(String(F("Pragma")), String(F("no-cache")));
_webServer->sendHeader(String(F("Expires")), String("-1"));
_webServer->sendHeader(String(F("Content-Length")), String(html.length()));
_webServer->send(404, String(F("text/html")), html);
}
}
}
/**
* Reset the ESP8266 module.
* It is called from the PageBuilder of the disconnect page and indicates
* the request for disconnection. It will be into progress after handleClient.
*/
String AutoConnect::_induceReset(PageArgument& args) {
AC_UNUSED(args);
_rfReset = true;
return String(F(AUTOCONNECT_BUTTONLABEL_RESET " in progress..."));
}
/**
* Disconnect from AP.
* It is called from the PageBuilder of the disconnect page and indicates
* the request for disconnection. It will be into progress after handleClient.
*/
String AutoConnect::_induceDisconnect(PageArgument& args) {
AC_UNUSED(args);
_rfDisconnect = true;
return _emptyString;
}
/**
* Indicates a connection establishment request and returns a redirect
* response to the waiting for connection page. This is called from
* handling of the current request by PageBuilder triggered by handleClient().
* If "Credential" exists in POST parameter, it reads from EEPROM.
* @param args http request arguments.
* @return A redirect response including "Location:" header.
*/
String AutoConnect::_induceConnect(PageArgument& args) {
// Retrieve credential from the post method content.
if (args.hasArg(String(F(AUTOCONNECT_PARAMID_CRED)))) {
// Read from EEPROM
AutoConnectCredential credential(_apConfig.boundaryOffset);
credential.load(args.arg(String(F(AUTOCONNECT_PARAMID_CRED))).c_str(), &_credential);
#ifdef AC_DEBUG
IPAddress staip = IPAddress(_credential.config.sta.ip);
AC_DBG("Credential loaded:%.*s(%s)\n", sizeof(station_config_t::ssid), reinterpret_cast<const char*>(_credential.ssid), _credential.dhcp == STA_DHCP ? "DHCP" : staip.toString().c_str());
#endif
}
else {
AC_DBG("Queried SSID:%s\n", args.arg(AUTOCONNECT_PARAMID_SSID).c_str());
// Credential had by the post parameter.
strncpy(reinterpret_cast<char*>(_credential.ssid), args.arg(String(F(AUTOCONNECT_PARAMID_SSID))).c_str(), sizeof(_credential.ssid));
strncpy(reinterpret_cast<char*>(_credential.password), args.arg(String(F(AUTOCONNECT_PARAMID_PASS))).c_str(), sizeof(_credential.password));
// Static IP detection
if (args.hasArg(String(F(AUTOCONNECT_PARAMID_DHCP)))) {
_credential.dhcp = STA_DHCP;
_credential.config.sta.ip = _credential.config.sta.gateway = _credential.config.sta.netmask = _credential.config.sta.dns1 = _credential.config.sta.dns2 = 0U;
}
else {
_credential.dhcp = STA_STATIC;
const String paramId[] = {
String(F(AUTOCONNECT_PARAMID_STAIP)),
String(F(AUTOCONNECT_PARAMID_GTWAY)),
String(F(AUTOCONNECT_PARAMID_NTMSK)),
String(F(AUTOCONNECT_PARAMID_DNS1)),
String(F(AUTOCONNECT_PARAMID_DNS2))
};
for (uint8_t i = 0; i < sizeof(station_config_t::_config::addr) / sizeof(uint32_t); i++) {
if (args.hasArg(paramId[i])) {
IPAddress ip;
if (ip.fromString(args.arg(paramId[i])))
_credential.config.addr[i] = static_cast<uint32_t>(ip);
}
}
}
}
// Restore the configured IPs to STA configuration
_apConfig.staip = static_cast<IPAddress>(_credential.config.sta.ip);
_apConfig.staGateway = static_cast<IPAddress>(_credential.config.sta.gateway);
_apConfig.staNetmask = static_cast<IPAddress>(_credential.config.sta.netmask);
_apConfig.dns1 = static_cast<IPAddress>(_credential.config.sta.dns1);
_apConfig.dns2 = static_cast<IPAddress>(_credential.config.sta.dns2);
// Determine the connection channel based on the scan result.
_connectCh = 0;
for (uint8_t nn = 0; nn < _scanCount; nn++) {
String ssid = WiFi.SSID(nn);
if (!strncmp(ssid.c_str(), reinterpret_cast<const char*>(_credential.ssid), sizeof(station_config_t::ssid))) {
_connectCh = WiFi.channel(nn);
break;
}
}
// Turn on the trigger to start WiFi.begin().
_rfConnect = true;
// Since v0.9.7, the redirect method changed from a 302 response to the
// meta tag with refresh attribute.
// This approach for ESP32 makes an inefficient choice. The waiting
// procedure for a connection attempt should be the server side. Also,
// the proper value of waiting time until refreshing is unknown. But
// AutoConnect cannot avoid the following error as affairs stand now
// that occurs at connection establishment.
// [WiFiClient.cpp:463] connected(): Disconnected: RES: 0, ERR: 128
// When connecting as a station, TCP reset caused by switching of the
// radio channel occurs. Although the Espressif view is true. However,
// the actual TCP reset occurs not at the time of switching the channel.
// It occurs at the connection from the ESP32 to the AP is established
// and it is possible that TCP reset is occurring in other situations.
// So, it may not be the real cause. Client-origin redirects with HTML
// refresh depend on the behavior of the arduino-esp32 library. Thus,
// the implementations for redirects with HTML will continue until
// the arduino-esp32 core supports reconnection.
// Redirect to waiting URI while executing connection request.
// String url = String(F("http://")) + _webServer->client().localIP().toString() + String(AUTOCONNECT_URI_RESULT);
// _webServer->sendHeader(F("Location"), url, true);
// _webServer->send(302, F("text/plain"), "");
// _webServer->client().flush();
// _webServer->client().stop();
// _responsePage->cancel();
return _emptyString;
}
/**
* Responds response as redirect to the connection result page.
* A destination as _redirectURI is indicated by loop to establish connection.
*/
String AutoConnect::_invokeResult(PageArgument& args) {
AC_UNUSED(args);
String redirect = String(F("http://"));
// The host address to which the connection result for ESP32 responds
// changed from v0.9.7. This change is a measure according to the
// implementation of the arduino-esp32 1.0.1.
#if defined(ARDUINO_ARCH_ESP32)
// In ESP32, the station IP address just established could not be reached.
redirect += _webServer->client().localIP().toString();
#elif defined(ARDUINO_ARCH_ESP8266)
// In ESP8266, the host address that responds for the connection
// successful is the IP address of ESP8266 as a station.
// This is the specification as before.
redirect += _currentHostIP.toString();
#endif
AC_DBG("Redirect to %s\n", redirect.c_str());
redirect += _redirectURI;
_webServer->sendHeader(String(F("Location")), redirect, true);
_webServer->send(302, String(F("text/plain")), _emptyString);
_webServer->client().flush();
_webServer->client().stop();
_waitForEndTransmission(); // Wait for response transmission complete
_responsePage->cancel();
return _emptyString;
}
/**
* Classify the requested URI to responsive page builder.
* There is always only one PageBuilder instance that can exist in
* AutoConnect for saving RAM. Invokes a subordinate function that
* dynamically generates a response page at handleRequest. This is
* a part of the handling of http request originated from handleClient.
*/
bool AutoConnect::_classifyHandle(HTTPMethod method, String uri) {
AC_UNUSED(method);
_portalAccessPeriod = millis();
AC_DBG("Host:%s,%s", _webServer->hostHeader().c_str(), uri.c_str());
// Here, classify requested uri
if (uri == _uri) {
AC_DBG_DUMB(",already allocated\n");
return true; // The response page already exists.
}
// Dispose decrepit page
if (_uri.length())
_prevUri = _uri; // Save current uri for the upload request
_purgePages();
// Create the page dynamically
_currentPageElement.reset( _setupPage(uri) );
if (!_currentPageElement && _aux) {
// Requested URL is not a normal page, exploring AUX pages
_currentPageElement.reset(_aux->_setupPage(uri));
}
if (_currentPageElement) {
AC_DBG_DUMB(",generated:%s", uri.c_str());
_uri = uri;
_responsePage->addElement(*_currentPageElement);
_responsePage->setUri(_uri.c_str());
}
AC_DBG_DUMB(",%s\n", _currentPageElement != nullptr ? " allocated" : "ignored");
return _currentPageElement != nullptr ? true : false;
}
/**
* A wrapper of the upload function for the WebServerClass. Invokes the
* upload function of the AutoConnectAux which has a destination URI.
*/
void AutoConnect::_handleUpload(const String& requestUri, const HTTPUpload& upload) {
AutoConnectAux* aux = _aux;
while (aux) {
if (aux->_uriStr == requestUri) {
aux->upload(_prevUri, upload);
break;
}
aux = aux->_next;
}
}
/**
* Purge allocated pages.
*/
void AutoConnect::_purgePages(void) {
_responsePage->clearElement();
if (_currentPageElement) {
_currentPageElement.reset();
_uri = String("");
}
}
/**
* It checks whether the specified character string is a valid IP address.
* @param ipStr IP string for validation.
* @return true Valid.
*/
bool AutoConnect::_isIP(String ipStr) {
for (uint8_t i = 0; i < ipStr.length(); i++) {
char c = ipStr.charAt(i);
if (c != '.' && (c < '0' || c > '9'))
return false;
}
return true;
}
/**
* Convert MAC address in uint8_t array to Sting XX:XX:XX:XX:XX:XX format.
* @param mac Array of MAC address 6 bytes.
* @return MAC address string in XX:XX:XX:XX:XX:XX format.
*/
String AutoConnect::_toMACAddressString(const uint8_t mac[]) {
String macAddr = String("");
for (uint8_t i = 0; i < 6; i++) {
char buf[3];
sprintf(buf, "%02X", mac[i]);
macAddr += buf;
if (i < 5)
macAddr += ':';
}
return macAddr;
}
/**
* Convert dBm to the wifi signal quality.
* @param rssi dBm.
* @return A signal quality percentage.
*/
unsigned int AutoConnect::_toWiFiQuality(int32_t rssi) {
unsigned int qu;
if (rssi == 31) // WiFi signal is weak and RSSI value is unreliable.
qu = 0;
else if (rssi <= -100)
qu = 0;
else if (rssi >= -50)
qu = 100;
else
qu = 2 * (rssi + 100);
return qu;
}
/**
* Wait for establishment of the connection until the specified time expires.
* @param timeout Expiration time by millisecond unit.
* @return wl_status_t
*/
wl_status_t AutoConnect::_waitForConnect(unsigned long timeout) {
wl_status_t wifiStatus;
unsigned long st = millis();
while ((wifiStatus = WiFi.status()) != WL_CONNECTED) {
if (timeout) {
if (millis() - st > timeout)
break;
}
AC_DBG_DUMB("%c", '.');
delay(300);
}
AC_DBG_DUMB("%s IP:%s\n", wifiStatus == WL_CONNECTED ? "established" : "time out", WiFi.localIP().toString().c_str());
if (WiFi.status() == WL_CONNECTED)
if (_onConnectExit) {
IPAddress localIP = WiFi.localIP();
_onConnectExit(localIP);
}
_attemptPeriod = millis(); // Save to measure the interval between an autoReconnect.
return wifiStatus;
}
/**
* Control the automatic reconnection behaves. Reconnection behavior
* to the AP connected during captive portal operation is activated
* by an order as the argument.
* @param order AC_RECONNECT_SET or AC_RECONNECT_RESET
*/
void AutoConnect::_setReconnect(const AC_STARECONNECT_t order) {
#if defined(ARDUINO_ARCH_ESP32)
if (order == AC_RECONNECT_SET) {
_disconnectEventId = WiFi.onEvent([](WiFiEvent_t e, WiFiEventInfo_t info) {
AC_DBG("STA lost connection:%d\n", info.disconnected.reason);
AC_DBG("STA connection %s\n", WiFi.reconnect() ? "restored" : "failed");
}, WiFiEvent_t::SYSTEM_EVENT_AP_STADISCONNECTED);
AC_DBG("Event<%d> handler registered\n", static_cast<int>(WiFiEvent_t::SYSTEM_EVENT_AP_STADISCONNECTED));
}
else if (order == AC_RECONNECT_RESET) {
if (_disconnectEventId) {
WiFi.removeEvent(_disconnectEventId);
AC_DBG("Event<%d> handler released\n", static_cast<int>(WiFiEvent_t::SYSTEM_EVENT_AP_STADISCONNECTED));
}
}
#elif defined(ARDUINO_ARCH_ESP8266)
bool strc = order == AC_RECONNECT_SET ? true : false;
WiFi.setAutoReconnect(strc);
AC_DBG("STA reconnection:%s\n", strc ? "EN" : "DIS");
#endif
}
/**
* Wait for the end of transmission of the http response by closed
* from the http client.
*/
void AutoConnect::_waitForEndTransmission(void) {
#ifdef AC_DEBUG
AC_DBG("Leaves:");
unsigned long lt = millis();
#endif
while (_webServer->client().connected()) {
delay(1);
yield();
}
#ifdef AC_DEBUG
// Notifies of the time taken to end the session. If the http client
// times out, AC_DEBUG must be enabled and it is necessary to confirm
// that the http response is being transmitted correctly.
// To trace the correctness the close sequence of TCP connection,
// enable the debug log of the Arduino core side. If the normal,
// a message of closed the TCP connection will be logged between
// "Leaves:" and "the time taken to end the session" of the log.
AC_DBG_DUMB("%d[ms]\n", static_cast<int>(millis() - lt));
#endif
}
/**
* Disconnects the station from an associated access point.
* @param wifiOff The station mode turning switch.
*/
void AutoConnect::_disconnectWiFi(bool wifiOff) {
#if defined(ARDUINO_ARCH_ESP8266)
WiFi.disconnect(wifiOff);
#elif defined(ARDUINO_ARCH_ESP32)
WiFi.disconnect(wifiOff, true);
#endif
while (WiFi.status() == WL_CONNECTED) {
delay(1);
yield();
}
}
/**
* Initialize an empty string to allow returning const String& with nothing.
*/
const String AutoConnect::_emptyString = String("");
/**
* Declaration of AutoConnect class and accompanying AutoConnectConfig class.
* @file AutoConnect.h
* @author hieromon@gmail.com
* @version 1.2.0
* @date 2020-11-15
* @copyright MIT license.
*/
#ifndef _AUTOCONNECT_H_
#define _AUTOCONNECT_H_
#include <vector>
#include <memory>
#include <functional>
#include <DNSServer.h>
#if defined(ARDUINO_ARCH_ESP8266)
#include <ESP8266WiFi.h>
#include <ESP8266WebServer.h>
extern "C" {
#include <user_interface.h>
}
using WebServerClass = ESP8266WebServer;
#elif defined(ARDUINO_ARCH_ESP32)
#include <WiFi.h>
#include <WebServer.h>
using WebServerClass = WebServer;
#endif
#include <EEPROM.h>
#include <PageBuilder.h>
#include "AutoConnectDefs.h"
#include "AutoConnectPage.h"
#include "AutoConnectCredential.h"
#include "AutoConnectTicker.h"
#include "AutoConnectAux.h"
#include "AutoConnectTypes.h"
// The realization of AutoConnectOTA is effective only by the explicit
#include "AutoConnectOTA.h"
class AutoConnectOTA; // Reference to avoid circular
// The realization of AutoConnectUpdate is effective only by the explicit
// definition of AUTOCONNECT_USE_UPDATE
#include "AutoConnectUpdate.h"
class AutoConnectUpdate; // Reference to avoid circular
class AutoConnectConfig {
public:
/**
* AutoConnectConfig default constructor.
* SSID for the captive portal access point assumes AUTOCONNECT_APID which
* assigned from macro. Password is same as above too.
*/
AutoConnectConfig() :
apip(AUTOCONNECT_AP_IP),
gateway(AUTOCONNECT_AP_GW),
netmask(AUTOCONNECT_AP_NM),
apid(String(F(AUTOCONNECT_APID))),
psk(String(F(AUTOCONNECT_PSK))),
channel(AUTOCONNECT_AP_CH),
hidden(0),
minRSSI(AUTOCONNECT_MIN_RSSI),
autoSave(AC_SAVECREDENTIAL_AUTO),
bootUri(AC_ONBOOTURI_ROOT),
principle(AC_PRINCIPLE_RECENT),
boundaryOffset(AC_IDENTIFIER_OFFSET),
uptime(AUTOCONNECT_STARTUPTIME),
autoRise(true),
autoReset(true),
autoReconnect(false),
immediateStart(false),
retainPortal(false),
preserveAPMode(false),
beginTimeout(AUTOCONNECT_TIMEOUT),
portalTimeout(AUTOCONNECT_CAPTIVEPORTAL_TIMEOUT),
menuItems(AC_MENUITEM_CONFIGNEW | AC_MENUITEM_OPENSSIDS | AC_MENUITEM_DISCONNECT | AC_MENUITEM_RESET | AC_MENUITEM_UPDATE | AC_MENUITEM_HOME),
reconnectInterval(0),
ticker(false),
tickerPort(AUTOCONNECT_TICKER_PORT),
tickerOn(LOW),
ota(AC_OTA_EXTRA),
auth(AC_AUTH_NONE),
authScope(AC_AUTHSCOPE_AUX),
username(String("")),
password(String("")),
hostName(String("")),
homeUri(AUTOCONNECT_HOMEURI),
title(AUTOCONNECT_MENU_TITLE),
staip(0U),
staGateway(0U),
staNetmask(0U),
dns1(0U),
dns2(0U) {}
/**
* Configure by SSID for the captive portal access point and password.
*/
AutoConnectConfig(const char* ap, const char* password, const unsigned long portalTimeout = 0, const uint8_t channel = AUTOCONNECT_AP_CH) :
apip(AUTOCONNECT_AP_IP),
gateway(AUTOCONNECT_AP_GW),
netmask(AUTOCONNECT_AP_NM),
apid(String(ap)),
psk(String(password)),
channel(channel),
hidden(0),
minRSSI(AUTOCONNECT_MIN_RSSI),
autoSave(AC_SAVECREDENTIAL_AUTO),
bootUri(AC_ONBOOTURI_ROOT),
principle(AC_PRINCIPLE_RECENT),
boundaryOffset(AC_IDENTIFIER_OFFSET),
uptime(AUTOCONNECT_STARTUPTIME),
autoRise(true),
autoReset(true),
autoReconnect(false),
immediateStart(false),
retainPortal(false),
preserveAPMode(false),
beginTimeout(AUTOCONNECT_TIMEOUT),
portalTimeout(portalTimeout),
menuItems(AC_MENUITEM_CONFIGNEW | AC_MENUITEM_OPENSSIDS | AC_MENUITEM_DISCONNECT | AC_MENUITEM_RESET | AC_MENUITEM_UPDATE | AC_MENUITEM_HOME),
reconnectInterval(0),
ticker(false),
tickerPort(AUTOCONNECT_TICKER_PORT),
tickerOn(LOW),
ota(AC_OTA_EXTRA),
auth(AC_AUTH_NONE),
authScope(AC_AUTHSCOPE_AUX),
username(String("")),
password(String("")),
hostName(String("")),
homeUri(AUTOCONNECT_HOMEURI),
title(AUTOCONNECT_MENU_TITLE),
staip(0U),
staGateway(0U),
staNetmask(0U),
dns1(0U),
dns2(0U) {}
~AutoConnectConfig() {}
AutoConnectConfig& operator=(const AutoConnectConfig& o) {
apip = o.apip;
gateway = o.gateway;
netmask = o.netmask;
apid = o.apid;
psk = o.psk;
channel = o.channel;
hidden = o.hidden;
minRSSI=o.minRSSI;
autoSave = o.autoSave;
bootUri = o.bootUri;
principle = o.principle;
boundaryOffset = o.boundaryOffset;
uptime = o.uptime;
autoRise = o.autoRise;
autoReset = o.autoReset;
autoReconnect = o.autoReconnect;
immediateStart = o.immediateStart;
retainPortal = o.retainPortal;
preserveAPMode = o.preserveAPMode;
beginTimeout = o.beginTimeout;
portalTimeout = o.portalTimeout;
menuItems = o.menuItems;
reconnectInterval = o.reconnectInterval;
ticker = o.ticker;
tickerPort = o.tickerPort;
tickerOn = o.tickerOn;
ota = o.ota;
auth = o.auth;
authScope = o.authScope;
username = o.username;
password = o.password;
hostName = o.hostName;
homeUri = o.homeUri;
title = o.title;
staip = o.staip;
staGateway = o.staGateway;
staNetmask = o.staNetmask;
dns1 = o.dns1;
dns2 = o.dns2;
return *this;
}
IPAddress apip; /**< SoftAP IP address */
IPAddress gateway; /**< SoftAP gateway address */
IPAddress netmask; /**< SoftAP subnet mask */
String apid; /**< SoftAP SSID */
String psk; /**< SoftAP password */
uint8_t channel; /**< SoftAP used wifi channel */
uint8_t hidden; /**< SoftAP SSID hidden */
int16_t minRSSI; /**< Lowest WiFi signal strength (RSSI) that can be connected. */
AC_SAVECREDENTIAL_t autoSave; /**< Auto save credential */
AC_ONBOOTURI_t bootUri; /**< An uri invoking after reset */
AC_PRINCIPLE_t principle; /**< WiFi connection principle */
uint16_t boundaryOffset; /**< The save storage offset of EEPROM */
int uptime; /**< Length of start up time */
bool autoRise; /**< Automatic starting the captive portal */
bool autoReset; /**< Reset ESP8266 module automatically when WLAN disconnected. */
bool autoReconnect; /**< Automatic reconnect with past SSID */
bool immediateStart; /**< Skips WiFi.begin(), start portal immediately */
bool retainPortal; /**< Even if the captive portal times out, it maintains the portal state. */
bool preserveAPMode; /**< Keep existing AP WiFi mode if captive portal won't be started. */
unsigned long beginTimeout; /**< Timeout value for WiFi.begin */
unsigned long portalTimeout; /**< Timeout value for stay in the captive portal */
uint16_t menuItems; /**< A compound value of the menu items to be attached */
uint8_t reconnectInterval; /**< Auto-reconnect attempt interval uint */
bool ticker; /**< Drives LED flicker according to WiFi connection status. */
uint8_t tickerPort; /**< GPIO for flicker */
uint8_t tickerOn; /**< A signal for flicker turn on */
AC_OTA_t ota; /**< Attach built-in OTA */
AC_AUTH_t auth; /**< Enable authentication */
uint16_t authScope; /**< Authetication scope */
String username; /**< User name for authentication */
String password; /**< Authentication password */
String hostName; /**< host name */
String homeUri; /**< A URI of user site */
String title; /**< Menu title */
IPAddress staip; /**< Station static IP address */
IPAddress staGateway; /**< Station gateway address */
IPAddress staNetmask; /**< Station subnet mask */
IPAddress dns1; /**< Primary DNS server */
IPAddress dns2; /**< Secondary DNS server */
};
typedef std::vector<std::reference_wrapper<AutoConnectAux>> AutoConnectAuxVT;
class AutoConnect {
public:
AutoConnect();
AutoConnect(WebServerClass& webServer);
virtual ~AutoConnect();
bool begin(void);
bool begin(const char* ssid, const char* passphrase = nullptr, unsigned long timeout = 0);
bool config(AutoConnectConfig& Config);
bool config(const char* ap, const char* password = nullptr);
void end(void);
uint16_t getEEPROMUsedSize(void);
void handleClient(void);
void handleRequest(void);
void home(const String& uri);
WebServerClass& host(void);
String where(void) const { return _auxUri; }
AutoConnectAux* aux(const String& uri) const;
AutoConnectAux* append(const String& uri, const String& title);
AutoConnectAux* append(const String& uri, const String& title, WebServerClass::THandlerFunction handler);
bool detach(const String& uri);
inline void disableMenu(const uint16_t items) { _apConfig.menuItems &= (0xffff ^ items); }
inline void enableMenu(const uint16_t items) { _apConfig.menuItems |= items; }
void join(AutoConnectAux& aux);
void join(AutoConnectAuxVT auxVector);
bool on(const String& uri, const AuxHandlerFunctionT handler, AutoConnectExitOrder_t order = AC_EXIT_AHEAD);
/** For AutoConnectAux described in JSON */
#ifdef AUTOCONNECT_USE_JSON
bool load(PGM_P aux);
bool load(const __FlashStringHelper* aux);
bool load(const String& aux);
bool load(Stream& aux);
#endif // !AUTOCONNECT_USE_JSON
typedef std::function<bool(IPAddress&)> DetectExit_ft;
typedef std::function<void(IPAddress&)> ConnectExit_ft;
typedef std::function<bool(void)> WhileCaptivePortalExit_ft;
void onDetect(DetectExit_ft fn);
void onConnect(ConnectExit_ft fn);
void onNotFound(WebServerClass::THandlerFunction fn);
void whileCaptivePortal(WhileCaptivePortalExit_ft fn);
protected:
typedef enum {
AC_RECONNECT_SET,
AC_RECONNECT_RESET
} AC_STARECONNECT_t;
typedef enum {
AC_SEEKMODE_ANY,
AC_SEEKMODE_NEWONE,
AC_SEEKMODE_CURRENT
} AC_SEEKMODE_t;
void _authentication(bool allow);
void _authentication(bool allow, const HTTPAuthMethod method);
bool _configAP(void);
bool _configSTA(const IPAddress& ip, const IPAddress& gateway, const IPAddress& netmask, const IPAddress& dns1, const IPAddress& dns2);
String _getBootUri(void);
bool _getConfigSTA(station_config_t* config);
bool _loadAvailCredential(const char* ssid, const AC_PRINCIPLE_t principle = AC_PRINCIPLE_RECENT, const bool excludeCurrent = false);
bool _loadCurrentCredential(char* ssid, char* password, const AC_PRINCIPLE_t principle, const bool excludeCurrent);
bool _seekCredential(const AC_PRINCIPLE_t principle, const AC_SEEKMODE_t mode);
void _startWebServer(void);
void _startDNSServer(void);
void _stopDNSServer(void);
void _stopPortal(void);
bool _classifyHandle(HTTPMethod mothod, String uri);
void _handleUpload(const String& requestUri, const HTTPUpload& upload);
void _handleNotFound(void);
void _purgePages(void);
virtual PageElement* _setupPage(String& uri);
#ifdef AUTOCONNECT_USE_JSON
template<typename T>
bool _parseJson(T in);
bool _load(JsonVariant& aux);
#endif // !AUTOCONNECT_USE_JSON
/** Request handlers implemented by Page Builder */
String _induceConnect(PageArgument& args);
String _induceDisconnect(PageArgument& args);
String _induceReset(PageArgument& args);
String _invokeResult(PageArgument& args);
/** For portal control */
bool _captivePortal(void);
bool _hasTimeout(unsigned long timeout);
bool _isIP(String ipStr);
void _softAP(void);
wl_status_t _waitForConnect(unsigned long timeout);
void _waitForEndTransmission(void);
void _disconnectWiFi(bool wifiOff);
void _setReconnect(const AC_STARECONNECT_t order);
/** Utilities */
String _attachMenuItem(const AC_MENUITEM_t item);
static uint32_t _getChipId(void);
static uint32_t _getFlashChipRealSize(void);
static String _toMACAddressString(const uint8_t mac[]);
static unsigned int _toWiFiQuality(int32_t rssi);
ConnectExit_ft _onConnectExit;
DetectExit_ft _onDetectExit;
WhileCaptivePortalExit_ft _whileCaptivePortal;
WebServerClass::THandlerFunction _notFoundHandler;
size_t _freeHeapSize;
/** Servers which works in concert. */
typedef std::unique_ptr<WebServerClass, std::function<void(WebServerClass *)> > WebserverUP;
WebserverUP _webServer = WebserverUP(nullptr, std::default_delete<WebServerClass>());
std::unique_ptr<DNSServer> _dnsServer;
/**
* Dynamically hold one page of AutoConnect menu.
* Every time a GET/POST HTTP request occurs, an AutoConnect
* menu page corresponding to the URI is generated.
*/
std::unique_ptr<PageBuilder> _responsePage;
std::unique_ptr<PageElement> _currentPageElement;
/** Extended pages made up with AutoConnectAux */
AutoConnectAux* _aux = nullptr; /**< A top of registered AutoConnectAux */
String _auxUri; /**< Last accessed AutoConnectAux */
String _prevUri; /**< Previous generated page uri */
/** Available updater, only reset by AutoConnectUpdate::attach is valid */
std::unique_ptr<AutoConnectUpdate> _update;
/** OTA updater */
std::unique_ptr<AutoConnectOTA> _ota;
/** Saved configurations */
AutoConnectConfig _apConfig;
station_config_t _credential;
uint8_t _hiddenSSIDCount;
int16_t _scanCount;
uint8_t _connectCh;
unsigned long _portalAccessPeriod;
unsigned long _attemptPeriod;
/** The control indicators */
bool _rfAdHocBegin = false; /**< Specified with AutoConnect::begin */
bool _rfConnect = false; /**< URI /connect requested */
bool _rfDisconnect = false; /**< URI /disc requested */
bool _rfReset = false; /**< URI /reset requested */
wl_status_t _rsConnect; /**< connection result */
#ifdef ARDUINO_ARCH_ESP32
WiFiEventId_t _disconnectEventId = -1; /**< STA disconnection event handler registered id */
#endif
/** Only available with ticker enabled */
std::unique_ptr<AutoConnectTicker> _ticker;
/** HTTP header information of the currently requested page. */
IPAddress _currentHostIP; /**< host IP address */
String _uri; /**< Requested URI */
String _redirectURI; /**< Redirect destination */
String _menuTitle; /**< Title string of the page */
/** PageElements of AutoConnect site. */
static const char _CSS_BASE[] PROGMEM;
static const char _CSS_LUXBAR[] PROGMEM;
static const char _CSS_UL[] PROGMEM;
static const char _CSS_ICON_LOCK[] PROGMEM;
static const char _CSS_INPUT_BUTTON[] PROGMEM;
static const char _CSS_INPUT_TEXT[] PROGMEM;
static const char _CSS_TABLE[] PROGMEM;
static const char _CSS_SPINNER[] PROGMEM;
static const char _ELM_HTML_HEAD[] PROGMEM;
static const char _ELM_MENU_PRE[] PROGMEM;
static const char _ELM_MENU_AUX[] PROGMEM;
static const char _ELM_MENU_POST[] PROGMEM;
static const char _PAGE_STAT[] PROGMEM;
static const char _PAGE_CONFIGNEW[] PROGMEM;
static const char _PAGE_CONNECTING[] PROGMEM;
static const char _PAGE_OPENCREDT[] PROGMEM;
static const char _PAGE_SUCCESS[] PROGMEM;
static const char _PAGE_RESETTING[] PROGMEM;
static const char _PAGE_DISCONN[] PROGMEM;
static const char _PAGE_FAIL[] PROGMEM;
static const char _PAGE_404[] PROGMEM;
static const struct PageTranserModeST {
const char* uri;
const TransferEncoding_t transMode;
const size_t rSize;
} _pageBuildMode[];
/** Token handlers for PageBuilder */
String _token_CSS_BASE(PageArgument& args);
String _token_CSS_ICON_LOCK(PageArgument& args);
String _token_CSS_INPUT_BUTTON(PageArgument& args);
String _token_CSS_INPUT_TEXT(PageArgument& args);
String _token_CSS_LUXBAR(PageArgument& args);
String _token_CSS_SPINNER(PageArgument& args);
String _token_CSS_TABLE(PageArgument& args);
String _token_CSS_UL(PageArgument& args);
String _token_MENU_AUX(PageArgument& args);
String _token_MENU_POST(PageArgument& args);
String _token_MENU_PRE(PageArgument& args);
String _token_AP_MAC(PageArgument& args);
String _token_BOOTURI(PageArgument& args);
String _token_CHANNEL(PageArgument& args);
String _token_CHIP_ID(PageArgument& args);
String _token_CONFIG_STAIP(PageArgument& args);
String _token_CPU_FREQ(PageArgument& args);
String _token_CURRENT_SSID(PageArgument& args);
String _token_DBM(PageArgument& args);
String _token_ESTAB_SSID(PageArgument& args);
String _token_FLASH_SIZE(PageArgument& args);
String _token_FREE_HEAP(PageArgument& args);
String _token_GATEWAY(PageArgument& args);
String _token_HEAD(PageArgument& args);
String _token_HIDDEN_COUNT(PageArgument& args);
String _token_LIST_SSID(PageArgument& args);
String _token_LOCAL_IP(PageArgument& args);
String _token_NETMASK(PageArgument& args);
String _token_OPEN_SSID(PageArgument& args);
String _token_SOFTAP_IP(PageArgument& args);
String _token_SSID_COUNT(PageArgument& args);
String _token_STA_MAC(PageArgument& args);
String _token_STATION_STATUS(PageArgument& args);
String _token_UPTIME(PageArgument& args);
String _token_WIFI_MODE(PageArgument& args);
String _token_WIFI_STATUS(PageArgument& args);
private:
// The access point collation key is determined at compile time
// according to the AUTOCONNECT_APKEY_SSID definition, which is
inline bool _isValidAP(const station_config_t& config, const uint8_t item) const {
#if defined(AUTOCONNECT_APKEY_SSID)
return !strcmp(reinterpret_cast<const char*>(config.ssid), WiFi.SSID(item).c_str());
#else
return !memcmp(config.bssid, WiFi.BSSID(item), sizeof(station_config_t::bssid));
#endif
}
static const String _emptyString; /**< An empty string allocation **/
#if defined(ARDUINO_ARCH_ESP8266)
friend ESP8266WebServer;
#elif defined(ARDUINO_ARCH_ESP32)
friend class WebServer;
#endif
friend class AutoConnectAux;
friend class AutoConnectUpdate;
};
#endif // _AUTOCONNECT_H_
/**
* Implementation of AutoConnectAux class.
* @file AutoConnectAux.cpp
* @author hieromon@gmail.com
* @version 1.2.0
* @date 2020-10-30
* @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
* AutoConnectAuxes.
* @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);
// Register authentication
// Determine the necessity of authentication from the conditions of
// AutoConnectConfig::authScope and derive the method.
bool auth = ((mother->_apConfig.authScope & AC_AUTHSCOPE_AUX) && (mother->_apConfig.auth != AC_AUTH_NONE))
|| ((mother->_apConfig.authScope & AC_AUTHSCOPE_PARTIAL) && (_httpAuth != AC_AUTH_NONE));
HTTPAuthMethod method;
if (mother->_apConfig.authScope & AC_AUTHSCOPE_PARTIAL)
method = _httpAuth == AC_AUTH_BASIC ? HTTPAuthMethod::BASIC_AUTH : HTTPAuthMethod::DIGEST_AUTH;
else
method = mother->_apConfig.auth == AC_AUTH_BASIC ? HTTPAuthMethod::BASIC_AUTH : HTTPAuthMethod::DIGEST_AUTH;
mother->_authentication(auth, method);
}
}
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 AutoConnectAux.
* @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>();
String auth = jb[F(AUTOCONNECT_JSON_KEY_AUTH)].as<String>();
if (auth.equalsIgnoreCase(F(AUTOCONNECT_JSON_VALUE_BASIC)))
_httpAuth = AC_AUTH_BASIC;
else if (auth.equalsIgnoreCase(F(AUTOCONNECT_JSON_VALUE_DIGEST)))
_httpAuth = AC_AUTH_DIGEST;
if (auth.equalsIgnoreCase(F(AUTOCONNECT_JSON_VALUE_NONE)))
_httpAuth = AC_AUTH_NONE;
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) + sizeof(AUTOCONNECT_JSON_KEY_AUTH) + sizeof(AUTOCONNECT_JSON_VALUE_DIGEST);
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;
if (_httpAuth == AC_AUTH_BASIC)
json[F(AUTOCONNECT_JSON_KEY_AUTH)] = String(F(AUTOCONNECT_JSON_VALUE_BASIC));
else if (_httpAuth == AC_AUTH_DIGEST)
json[F(AUTOCONNECT_JSON_KEY_AUTH)] = String(F(AUTOCONNECT_JSON_VALUE_DIGEST));
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
/**
* Declaration of AutoConnectAux basic class.
* @file AutoConnectAux.h
* @author hieromon@gmail.com
* @version 1.2.0
* @date 2029-04-17
* @copyright MIT license.
*/
#ifndef _AUTOCONNECTAUX_H_
#define _AUTOCONNECTAUX_H_
#include "AutoConnectDefs.h"
#include <vector>
#include <memory>
#include <functional>
#include <type_traits>
#ifdef AUTOCONNECT_USE_JSON
#include <Stream.h>
#endif // !AUTOCONNECT_USE_JSON
#include <PageBuilder.h>
#include "AutoConnectElement.h"
#include "AutoConnectTypes.h"
class AutoConnect; // Reference to avoid circular
class AutoConnectAux; // Reference to avoid circular
// Manage placed AutoConnectElement with a vector
typedef std::vector<std::reference_wrapper<AutoConnectElement>> AutoConnectElementVT;
// A type of callback function when AutoConnectAux page requested.
typedef std::function<String(AutoConnectAux&, PageArgument&)> AuxHandlerFunctionT;
// A type for the order in which callback functions are called.
typedef enum {
AC_EXIT_AHEAD = 1, /**< Callback before building HTML */
AC_EXIT_LATER = 2, /**< Callback after building HTML */
AC_EXIT_BOTH = 3 /**< Callback twice before and after building HTML */
} AutoConnectExitOrder_t;
/**
* A class that handles an auxiliary page with AutoConnectElement
* that placed on it by binding it to the AutoConnect menu.
* @param uri An uri string of this page.
* @param title A title string of this page.
* @param addons A set of AutoConnectElement vector.
* @param menu A switch for item displaying in AutoConnect menu.
*/
class AutoConnectAux : public PageBuilder {
public:
explicit AutoConnectAux(const String& uri = String(""), const String& title = String(""), const bool menu = true, const AutoConnectElementVT addons = AutoConnectElementVT()) :
chunk(PB_Chunk), _title(title), _menu(menu), _uriStr(String(uri)), _addonElm(addons), _handler(nullptr), _order(AC_EXIT_AHEAD), _uploadHandler(nullptr) { _uri = _uriStr.c_str(); }
~AutoConnectAux();
AutoConnectElement& operator[](const String& name) { return *getElement(name); }
void add(AutoConnectElement& addon); /**< Add an element to the auxiliary page */
void add(AutoConnectElementVT addons); /**< Add the element set to the auxiliary page */
void authentication(const AC_AUTH_t auth) { _httpAuth = auth; } /**< Set certain page authentication */
void fetchElement(void); /**< Fetch AutoConnectElements values from http query parameters */
template<typename T>
T& getElement(const String& name);
AutoConnectElement* getElement(const String& name); /**< Get registered AutoConnectElement as specified name */
AutoConnectElementVT& getElements(void) { return _addonElm; } /**< Get vector of all elements */
void menu(const bool post) { _menu = post; } /**< Set or reset the display as menu item for this aux */
bool isMenu(void) { return _menu; } /**< Return whether embedded in the menu or not */
bool isValid(void) const; /**< Validate all AutoConnectInput value */
bool release(const String& name); /**< Release an AutoConnectElement */
bool setElementValue(const String& name, const String value); /**< Set value to specified element */
bool setElementValue(const String& name, std::vector<String> const& values); /**< Set values collection to specified element */
void setTitle(const String& title) { _title = title; } /**< Set a title of the auxiliary page */
void on(const AuxHandlerFunctionT handler, const AutoConnectExitOrder_t order = AC_EXIT_AHEAD) { _handler = handler; _order = order; } /**< Set user handler */
void onUpload(PageBuilder::UploadFuncT uploadFunc) override { _uploadHandler = uploadFunc; }
template<typename T>
void onUpload(T& uploadClass) {
static_assert(std::is_base_of<AutoConnectUploadHandler, T>::value, "onUpload type must be inherited AutoConnectUploadHandler");
_uploadHandler = std::bind(&T::upload, &uploadClass, std::placeholders::_1, std::placeholders::_2);
}
#ifdef AUTOCONNECT_USE_JSON
bool load(PGM_P in); /**< Load whole elements to AutoConnectAux Page */
bool load(const __FlashStringHelper* in); /**< Load whole elements to AutoConnectAux Page */
bool load(const String& in); /**< Load whole elements to AutoConnectAux Page */
bool load(Stream& in); /**< Load whole elements to AutoConnectAux Page */
bool loadElement(PGM_P in, const String& name = String("")); /**< Load specified element */
bool loadElement(PGM_P in, std::vector<String> const& names); /**< Load any specified elements */
bool loadElement(const __FlashStringHelper* in, const String& name = String("")); /**< Load specified element */
bool loadElement(const __FlashStringHelper* in, std::vector<String> const& names); /**< Load any specified elements */
bool loadElement(const String& in, const String& name = String("")); /**< Load specified element */
bool loadElement(const String& in, std::vector<String> const& names);/**< Load any specified elements */
bool loadElement(Stream& in, const String& name = String("")); /**< Load specified element */
bool loadElement(Stream& in, std::vector<String> const& names); /**< Load any specified elements */
size_t saveElement(Stream& out, std::vector<String> const& names = {}); /**< Write elements of AutoConnectAux to the stream */
#endif // !AUTOCONNECT_USE_JSON
TransferEncoding_t chunk; /**< Chunked transfer specified */
protected:
void upload(const String& requestUri, const HTTPUpload& upload); /**< Uploader wrapper */
void _concat(AutoConnectAux& aux); /**< Make up chain of AutoConnectAux */
void _join(AutoConnect& ac); /**< Make a link to AutoConnect */
PageElement* _setupPage(const String& uri); /**< AutoConnectAux page builder */
const String _insertElement(PageArgument& args); /**< Insert a generated HTML to the page built by PageBuilder */
const String _insertStyle(PageArgument& args); /**< Insert CSS style */
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 */
const String _indicateEncType(PageArgument& args); /**< Inject the ENCTYPE attribute */
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
template<typename T>
bool _parseJson(T in);
bool _load(JsonObject& in); /**< Load all elements from JSON object */
bool _loadElement(JsonVariant& in, const String& name); /**< Load an element as specified name from JSON object */
bool _loadElement(JsonVariant& in, std::vector<String> const& names); /**< Load any elements as specified name from JSON object */
AutoConnectElement& _loadElement(JsonObject& in, const String& name); /**< Load an element as specified name from JSON object */
AutoConnectElement* _createElement(const JsonObject& json); /**< Create an AutoConnectElement instance from JSON object */
static ACElement_t _asElementType(const String& type); /**< Convert a string of element type to the enumeration value */
/**
* Parse and load a JSON document which declares one of the AutoConnectElement.
* The compiler instantiates this template according to the stored data type that contains the JSON document.
* This template also generates different parsing function calls depending on the ArduinoJson version.
* @param T An object type of the JSON document which must be a passable object to ArduinoJson.
* @param U An instance of a source name to load.
*/
template<typename T, typename U,
typename std::enable_if<std::is_same<U, const String&>::value || std::is_same<U, std::vector<String> const&>::value>::type* = nullptr>
bool _parseElement(T in, U name) {
ArduinoJsonBuffer jsonBuffer(AUTOCONNECT_JSONBUFFER_PRIMITIVE_SIZE);
JsonVariant jb;
#if ARDUINOJSON_VERSION_MAJOR<=5
jb = jsonBuffer.parse(in);
if (!jb.success()) {
AC_DBG("JSON parse error\n");
return false;
}
#else
DeserializationError err = deserializeJson(jsonBuffer, in);
if (err) {
AC_DBG("Deserialize:%s\n", err.c_str());
return false;
}
jb = jsonBuffer.as<JsonVariant>();
#endif
return _loadElement(jb, name);
}
#endif // !AUTOCONNECT_USE_JSON
String _title; /**< A title of the page */
bool _menu; /**< Switch for menu displaying */
bool _deletable = false; /**< Allow deleting itself. */
AC_AUTH_t _httpAuth = AC_AUTH_NONE; /**< Applying HTTP authentication */
String _uriStr; /**< uri as String */
AutoConnectElementVT _addonElm; /**< A vector set of AutoConnectElements placed on this auxiliary page */
AutoConnectAux* _next = nullptr; /**< Auxiliary pages chain list */
AutoConnect* _ac = nullptr; /**< Hosted AutoConnect instance */
AuxHandlerFunctionT _handler; /**< User sketch callback function when AutoConnectAux page requested. */
AutoConnectExitOrder_t _order; /**< The order in which callback functions are called. */
PageBuilder::UploadFuncT _uploadHandler; /**< The AutoConnectFile corresponding to current upload */
AutoConnectFile* _currentUpload; /**< AutoConnectFile handling the current upload */
static const char _PAGE_AUX[] PROGMEM; /**< Auxiliary page template */
// Protected members can be used from AutoConnect which handles AutoConnectAux pages.
friend class AutoConnect;
};
#endif // !_AUTOCONNECTAUX_H_
/**
* Implementation of template functions of AutoConnect and AutoConnectAux.
* This implementation instantiates completely the void AutoConnectElement
* as each type and also absorbs interface differences due to ArduinoJson
* version differences.
* @file AutoConnectAuxImpl.h
* @author hieromon@gmail.com
* @version 0.9.8
* @date 2019-03-21
* @copyright MIT license.
*/
#ifndef _AUTOCONNECTAUXIMPL_H_
#define _AUTOCONNECTAUXIMPL_H_
#include "AutoConnectDefs.h"
#ifndef AUTOCONNECT_USE_JSON
/**
* Get AutoConnectElementBasis element.
* @param name an element name.
* @return A reference of AutoConnectElement class.
*/
template<>
AutoConnectElementBasis& AutoConnectAux::getElement(const String& name) {
AutoConnectElement* elm = getElement(name);
if (elm) {
return *(reinterpret_cast<AutoConnectElementBasis*>(elm));
}
return reinterpret_cast<AutoConnectElementBasis&>(_nullElement());
}
/**
* Get AutoConnectButtonBasis element.
* @param name An element name.
* @return A reference of AutoConnectButton class.
*/
template<>
AutoConnectButtonBasis& AutoConnectAux::getElement(const String& name) {
AutoConnectElement* elm = getElement(name);
if (elm) {
if (elm->typeOf() == AC_Button)
return *(reinterpret_cast<AutoConnectButtonBasis*>(elm));
else
AC_DBG("Element<%s> type mismatch<%d>\n", name.c_str(), elm->typeOf());
}
return reinterpret_cast<AutoConnectButtonBasis&>(_nullElement());
}
/**
* Get AutoConnectCheckboxBasis element.
* @param name An element name.
* @return A reference of AutoConnectCheckbox class.
*/
template<>
AutoConnectCheckboxBasis& AutoConnectAux::getElement(const String& name) {
AutoConnectElement* elm = getElement(name);
if (elm) {
if (elm->typeOf() == AC_Checkbox)
return *(reinterpret_cast<AutoConnectCheckboxBasis*>(elm));
else
AC_DBG("Element<%s> type mismatch<%d>\n", name.c_str(), elm->typeOf());
}
return reinterpret_cast<AutoConnectCheckboxBasis&>(_nullElement());
}
/**
* Get AutoConnectFileBasis element.
* @param name An element name.
* @return A reference of AutoConnectFile class.
*/
template<>
AutoConnectFileBasis& AutoConnectAux::getElement(const String& name) {
AutoConnectElement* elm = getElement(name);
if (elm) {
if (elm->typeOf() == AC_File)
return *(reinterpret_cast<AutoConnectFileBasis*>(elm));
else
AC_DBG("Element<%s> type mismatch<%d>\n", name.c_str(), elm->typeOf());
}
return reinterpret_cast<AutoConnectFileBasis&>(_nullElement());
}
/**
* Get AutoConnectInputBasis element.
* @param name An element name.
* @return A reference of AutoConnectInput class.
*/
template<>
AutoConnectInputBasis& AutoConnectAux::getElement(const String& name) {
AutoConnectElement* elm = getElement(name);
if (elm) {
if (elm->typeOf() == AC_Input)
return *(reinterpret_cast<AutoConnectInputBasis*>(elm));
else
AC_DBG("Element<%s> type mismatch<%d>\n", name.c_str(), elm->typeOf());
}
return reinterpret_cast<AutoConnectInputBasis&>(_nullElement());
}
/**
* Get AutoConnectRadioBasis element.
* @param name An element name.
* @return A reference of AutoConnectRadio class.
*/
template<>
AutoConnectRadioBasis& AutoConnectAux::getElement(const String& name) {
AutoConnectElement* elm = getElement(name);
if (elm) {
if (elm->typeOf() == AC_Radio)
return *(reinterpret_cast<AutoConnectRadioBasis*>(elm));
else
AC_DBG("Element<%s> type mismatch<%d>\n", name.c_str(), elm->typeOf());
}
return reinterpret_cast<AutoConnectRadioBasis&>(_nullElement());
}
/**
* Get AutoConnectSelectBasis element.
* @param name An element name.
* @return A reference of AutoConnectSelect class.
*/
template<>
AutoConnectSelectBasis& AutoConnectAux::getElement(const String& name) {
AutoConnectElement* elm = getElement(name);
if (elm) {
if (elm->typeOf() == AC_Select)
return *(reinterpret_cast<AutoConnectSelectBasis*>(elm));
else
AC_DBG("Element<%s> type mismatch<%d>\n", name.c_str(), elm->typeOf());
}
return reinterpret_cast<AutoConnectSelectBasis&>(_nullElement());
}
/**
* Get AutoConnectStyleBasis element.
* @param name An element name.
* @return A reference of AutoConnectStyle class.
*/
template<>
AutoConnectStyleBasis& AutoConnectAux::getElement(const String& name) {
AutoConnectElement* elm = getElement(name);
if (elm) {
if (elm->typeOf() == AC_Style)
return *(reinterpret_cast<AutoConnectStyleBasis*>(elm));
else
AC_DBG("Element<%s> type mismatch<%d>\n", name.c_str(), elm->typeOf());
}
return reinterpret_cast<AutoConnectStyleBasis&>(_nullElement());
}
/**
* Get AutoConnectSubmitBasis element.
* @param name An element name.
* @return A reference of AutoConnectSubmit class.
*/
template<>
AutoConnectSubmitBasis& AutoConnectAux::getElement(const String& name) {
AutoConnectElement* elm = getElement(name);
if (elm) {
if (elm->typeOf() == AC_Submit)
return *(reinterpret_cast<AutoConnectSubmitBasis*>(elm));
else
AC_DBG("Element<%s> type mismatch<%d>\n", name.c_str(), elm->typeOf());
}
return reinterpret_cast<AutoConnectSubmitBasis&>(_nullElement());
}
/**
* Get AutoConnectTextBasis element.
* @param name An element name.
* @return A reference of AutoConnectText class.
*/
template<>
AutoConnectTextBasis& AutoConnectAux::getElement(const String& name) {
AutoConnectElement* elm = getElement(name);
if (elm) {
if (elm->typeOf() == AC_Text)
return *(reinterpret_cast<AutoConnectTextBasis*>(elm));
else
AC_DBG("Element<%s> type mismatch<%d>\n", name.c_str(), elm->typeOf());
}
return reinterpret_cast<AutoConnectTextBasis&>(_nullElement());
}
#else
/**
* Parse and load a JSON document which marks up multiple custom web
* pages. The compiler instantiates this template according to the stored
* data type that contains the JSON document.
* This template also generates different parsing function calls
* depending on the ArduinoJson version.
* @param T An object type of the JSON document which must be a
* passable object to ArduinoJson.
* @param in An instance of a source JSON document to load.
*/
template<typename T>
bool AutoConnect::_parseJson(T in) {
ArduinoJsonBuffer jsonBuffer(AUTOCONNECT_JSONBUFFER_PRIMITIVE_SIZE);
JsonVariant jv;
#if ARDUINOJSON_VERSION_MAJOR<=5
jv = jsonBuffer.parse(in);
if (!jv.success()) {
AC_DBG("JSON parse error\n");
return false;
}
#else
DeserializationError err = deserializeJson(jsonBuffer, in);
if (err) {
AC_DBG("Deserialize error:%s\n", err.c_str());
return false;
}
jv = jsonBuffer.as<JsonVariant>();
#endif
return _load(jv);
}
/**
* Parse and load a JSON document which marks up a custom web page.
* The compiler instantiates this template according to the stored data
* type that contains the JSON document.
* This template also generates different parsing function calls
* depending on the ArduinoJson version.
* @param T An object type of the JSON document which must be a
* passable object to ArduinoJson.
* @param in An instance of a source JSON document to load.
*/
template<typename T>
bool AutoConnectAux::_parseJson(T in) {
ArduinoJsonBuffer jsonBuffer(AUTOCONNECT_JSONBUFFER_PRIMITIVE_SIZE);
#if ARDUINOJSON_VERSION_MAJOR<=5
JsonObject& jb = jsonBuffer.parseObject(in);
if (!jb.success()) {
AC_DBG("JSON parse error\n");
return false;
}
#else
DeserializationError err = deserializeJson(jsonBuffer, in);
if (err) {
AC_DBG("Deserialize:%s\n", err.c_str());
return false;
}
JsonObject jb = jsonBuffer.as<JsonObject>();
#endif
return _load(jb);
}
/**
* Get AutoConnectElementJson element.
* @param name an element name.
* @return A reference of AutoConnectElement class.
*/
template<>
AutoConnectElementJson& AutoConnectAux::getElement(const String& name) {
AutoConnectElement* elm = getElement(name);
if (elm) {
return *(reinterpret_cast<AutoConnectElementJson*>(elm));
}
return reinterpret_cast<AutoConnectElementJson&>(_nullElement());
}
/**
* Get AutoConnectButtonJson element.
* @param name An element name.
* @return A reference of AutoConnectButton class.
*/
template<>
AutoConnectButtonJson& AutoConnectAux::getElement(const String& name) {
AutoConnectElement* elm = getElement(name);
if (elm) {
if (elm->typeOf() == AC_Button)
return *(reinterpret_cast<AutoConnectButtonJson*>(elm));
else {
AC_DBG("Element<%s> type mismatch<%d>\n", name.c_str(), elm->typeOf());
}
}
return reinterpret_cast<AutoConnectButtonJson&>(_nullElement());
}
/**
* Get AutoConnectCheckboxJson element.
* @param name An element name.
* @return A reference of AutoConnectCheckbox class.
*/
template<>
AutoConnectCheckboxJson& AutoConnectAux::getElement(const String& name) {
AutoConnectElement* elm = getElement(name);
if (elm) {
if (elm->typeOf() == AC_Checkbox)
return *(reinterpret_cast<AutoConnectCheckboxJson*>(elm));
else {
AC_DBG("Element<%s> type mismatch<%d>\n", name.c_str(), elm->typeOf());
}
}
return reinterpret_cast<AutoConnectCheckboxJson&>(_nullElement());
}
/**
* Get AutoConnectFile element.
* @param name An element name.
* @return A reference of AutoConnectFile class.
*/
template<>
AutoConnectFileJson& AutoConnectAux::getElement(const String& name) {
AutoConnectElement* elm = getElement(name);
if (elm) {
if (elm->typeOf() == AC_File)
return *(reinterpret_cast<AutoConnectFileJson*>(elm));
else {
AC_DBG("Element<%s> type mismatch<%d>\n", name.c_str(), elm->typeOf());
}
}
return reinterpret_cast<AutoConnectFileJson&>(_nullElement());
}
/**
* Get AutoConnectInputJson element.
* @param name An element name.
* @return A reference of AutoConnectInput class.
*/
template<>
AutoConnectInputJson& AutoConnectAux::getElement(const String& name) {
AutoConnectElement* elm = getElement(name);
if (elm) {
if (elm->typeOf() == AC_Input)
return *(reinterpret_cast<AutoConnectInputJson*>(elm));
else {
AC_DBG("Element<%s> type mismatch<%d>\n", name.c_str(), elm->typeOf());
}
}
return reinterpret_cast<AutoConnectInputJson&>(_nullElement());
}
/**
* Get AutoConnectRadioJson element.
* @param name An element name.
* @return A reference of AutoConnectRadio class.
*/
template<>
AutoConnectRadioJson& AutoConnectAux::getElement(const String& name) {
AutoConnectElement* elm = getElement(name);
if (elm) {
if (elm->typeOf() == AC_Radio)
return *(reinterpret_cast<AutoConnectRadioJson*>(elm));
else {
AC_DBG("Element<%s> type mismatch<%d>\n", name.c_str(), elm->typeOf());
}
}
return reinterpret_cast<AutoConnectRadioJson&>(_nullElement());
}
/**
* Get AutoConnectSelectJson element.
* @param name An element name.
* @return A reference of AutoConnectSelect class.
*/
template<>
AutoConnectSelectJson& AutoConnectAux::getElement(const String& name) {
AutoConnectElement* elm = getElement(name);
if (elm) {
if (elm->typeOf() == AC_Select)
return *(reinterpret_cast<AutoConnectSelectJson*>(elm));
else {
AC_DBG("Element<%s> type mismatch<%d>\n", name.c_str(), elm->typeOf());
}
}
return reinterpret_cast<AutoConnectSelectJson&>(_nullElement());
}
/**
* Get AutoConnectSubmitJson element.
* @param name An element name.
* @return A reference of AutoConnectSubmit class.
*/
template<>
AutoConnectSubmitJson& AutoConnectAux::getElement(const String& name) {
AutoConnectElement* elm = getElement(name);
if (elm) {
if (elm->typeOf() == AC_Submit)
return *(reinterpret_cast<AutoConnectSubmitJson*>(elm));
else {
AC_DBG("Element<%s> type mismatch<%d>\n", name.c_str(), elm->typeOf());
}
}
return reinterpret_cast<AutoConnectSubmitJson&>(_nullElement());
}
/**
* Get AutoConnectTextJson element.
* @param name An element name.
* @return A reference of AutoConnectText class.
*/
template<>
AutoConnectTextJson& AutoConnectAux::getElement(const String& name) {
AutoConnectElement* elm = getElement(name);
if (elm) {
if (elm->typeOf() == AC_Text)
return *(reinterpret_cast<AutoConnectTextJson*>(elm));
else {
AC_DBG("Element<%s> type mismatch<%d>\n", name.c_str(), elm->typeOf());
}
}
return reinterpret_cast<AutoConnectTextJson&>(_nullElement());
}
#endif // !AUTOCONNECT_USE_JSON
#endif // !_AUTOCONNECTAUXIMPL_H_
/**
* AutoConnectCredential class dispatcher.
* @file AutoConnectCredential.cpp
* @author hieromon@gmail.com
* @version 1.1.0
* @date 2019-10-07
* @copyright MIT license.
*/
#include "AutoConnectCredential.h"
#if AC_CREDENTIAL_PREFERENCES == 0
#define AC_HEADERSIZE ((int)(_offset + sizeof(AC_IDENTIFIER) - 1 + sizeof(uint8_t) + sizeof(uint16_t)))
/**
* AutoConnectCredential constructor takes the available count of saved
* entries.
* A stored credential data structure in EEPROM.
* 0 7 8 9a b (u) (u+16) (t)
* +--------+-+--+-----------------+-+--+--+--+----+----+-----------------+--+
* |AC_CREDT|e|ss|ssid\0pass\0bssid|d|ip|gw|nm|dns1|dns2|ssid\0pass\0bssid|\0|
* +--------+-+--+-----------------+-+--+--+--+----+----+-----------------+--+
* AC_CREDT : Identifier. 8 characters.
* e : Number of contained entries(uint8_t).
* ss : Container size, excluding ID and number of entries(uint16_t).
* ssid: SSID string with null termination.
* password : Password string with null termination.
* bssid : BSSID 6 bytes.
* d : DHCP is in available. 0:DHCP 1:Static IP
* ip - dns2 : Optional fields for static IPs configuration, these fields are available when d=1.
* ip : Static IP (uint32_t)
* gw : Gateway address (uint32_t)
* nm : Netmask (uint32_t)
* dns1 : Primary DNS (uint32)
* dns2 : Secondary DNS (uint32_t)
* t : The end of the container is a continuous '\0'.
* The AC_CREDT identifier is at the beginning of the area.
* SSID and PASSWORD are terminated by '\ 0'.
* Free area are filled with FF, which is reused as an area for insertion.
*/
AutoConnectCredential::AutoConnectCredential() {
_offset = AC_IDENTIFIER_OFFSET;
_allocateEntry();
}
AutoConnectCredential::AutoConnectCredential(uint16_t offset) {
// Save offset for the credential area.
_offset = offset;
_allocateEntry();
}
void AutoConnectCredential::_allocateEntry(void) {
char id_c[sizeof(AC_IDENTIFIER) - 1];
_eeprom.reset(new EEPROMClass);
_eeprom->begin(AC_HEADERSIZE);
// Validate the save area of the EEPROM.
// If it is a valid area, retrieve the stored number of entries,
// if the identifier is not saved, initialize the EEPROM area.
_dp = _offset;
for (uint8_t c = 0; c < sizeof(id_c); c++)
id_c[c] = static_cast<char>(_eeprom->read(_dp++));
if (!strncmp(id_c, AC_IDENTIFIER, sizeof(id_c))) {
_entries = _eeprom->read(static_cast<int>(_dp++));
_containSize = _eeprom->read(static_cast<int>(_dp++));
_containSize += _eeprom->read(static_cast<int>(_dp)) << 8;
}
else {
_entries = 0;
_containSize = 0;
}
_eeprom->end();
}
/**
* The destructor ends EEPROM access.
*/
AutoConnectCredential::~AutoConnectCredential() {
_eeprom->end();
_eeprom.reset();
}
/**
* Delete the credential entry for the specified SSID in the EEPROM.
* @param ssid A SSID character string to be deleted.
* @retval true The entry successfully delete.
* false Could not deleted.
*/
bool AutoConnectCredential::del(const char* ssid) {
station_config_t entry;
bool rc = false;
if (load(ssid, &entry) >= 0) {
// Saved credential detected, _ep has the entry location.
_eeprom->begin(AC_HEADERSIZE + _containSize);
_dp = _ep;
// Erase SSID
while (_eeprom->read(_dp) != 0x00)
_eeprom->write(_dp++, 0xff);
// Erase Password
_eeprom->write(_dp++, 0xff);
while (_eeprom->read(_dp) != 0x00)
_eeprom->write(_dp++, 0xff);
// Erase BSSID
_eeprom->write(_dp++, 0xff);
for (uint8_t i = 0; i < sizeof(station_config_t::bssid); i++)
_eeprom->write(_dp++, 0xff);
// Erase ip configuration extention
if (_eeprom->read(_dp) == STA_STATIC) {
for (uint8_t i = 0; i < sizeof(station_config_t::_config); i++)
_eeprom->write(_dp++, 0xff);
}
// End 0xff writing, update headers.
_entries--;
_eeprom->write(_offset + static_cast<int>(sizeof(AC_IDENTIFIER)) - 1, _entries);
// commit it.
rc = _eeprom->commit();
delay(10);
_eeprom->end();
}
return rc;
}
/**
* Load the credential entry for the specified SSID from the EEPROM.
* The credentials are stored to the station_config structure which specified
* by *config as the SSID and password.
* @param ssid A SSID character string to be loaded.
* @param config A station_config structure pointer.
* @retval The entry number of the SSID in EEPROM. If the number less than 0,
* the specified SSID was not found.
*/
int8_t AutoConnectCredential::load(const char* ssid, station_config_t* config) {
int8_t entry = -1;
_dp = AC_HEADERSIZE;
if (_entries) {
_eeprom->begin(AC_HEADERSIZE + _containSize);
for (uint8_t i = 0; i < _entries; i++) {
_retrieveEntry(config);
if (!strcmp(ssid, reinterpret_cast<const char*>(config->ssid))) {
entry = i;
break;
}
}
_eeprom->end();
}
return entry;
}
/**
* Load the credential entry for the specified number from the EEPROM.
* The credentials are stored to the station_config structure which specified
* by *config as the SSID and password.
* @param entry A number of entry to be loaded.
* @param config A station_config structure pointer.
* @retval true The entry number of the SSID in EEPROM.
* false The number is not available.
*/
bool AutoConnectCredential::load(int8_t entry, station_config_t* config) {
_dp = AC_HEADERSIZE;
if (_entries && entry < _entries) {
_eeprom->begin(AC_HEADERSIZE + _containSize);
while (entry-- >= 0)
_retrieveEntry(config);
_eeprom->end();
return true;
}
else {
return false;
}
}
/**
* Save SSID and password to EEPROM.
* When the same SSID already exists, it will be replaced. If the current
* entry size insufficient for the new entry, the entry will be appended
* and increase whole size. Its previous areas are freed with FF and reused.
* @param config A pointer to the station_config structure storing SSID and password.
* @retval true Successfully saved.
* @retval false EEPROM commit failed.
*/
bool AutoConnectCredential::save(const station_config_t* config) {
static const char _id[] = AC_IDENTIFIER;
station_config_t stage;
int8_t entry;
bool rep = false;
bool rc;
// Detect same entry for replacement.
entry = load(reinterpret_cast<const char*>(config->ssid), &stage);
// Saving start.
_eeprom->begin(AC_HEADERSIZE + _containSize + sizeof(station_config_t));
// Determine insertion or replacement.
if (entry >= 0) {
// An entry with the same SSID was found, release the area for replacement.
_dp = _ep;
for (uint8_t dm = 2; dm; _dp++) {
if (_eeprom->read(_dp) == '\0')
dm--;
_eeprom->write(_dp, 0xff); // Clear SSID, Passphrase
}
for (uint8_t i = 0; i < sizeof(station_config_t::bssid); i++) {
_eeprom->write(_dp++, 0xff); // Clear BSSID
}
uint8_t ss = _eeprom->read(_dp); // Read dhcp assignment flag
_eeprom->write(_dp++, 0xff); // Clear dhcp
if (ss == (uint8_t)STA_STATIC) {
for (uint8_t i = 0 ; i < sizeof(station_config_t::_config); i++)
_eeprom->write(_dp++, 0xff); // Clear static IPs
}
}
else {
// Same entry not found. increase the entry.
_entries++;
int i;
for (i = 0; i < static_cast<int>(sizeof(_id)) - 1; i++)
_eeprom->write(i + _offset, (uint8_t)_id[i]);
_eeprom->write(i + _offset, _entries);
}
rc = _eeprom->commit();
delay(10);
// Seek insertion point, evaluate capacity to insert the new entry.
uint16_t eSize = strlen(reinterpret_cast<const char*>(config->ssid)) + strlen(reinterpret_cast<const char*>(config->password)) + sizeof(station_config_t::bssid) + sizeof(station_config_t::dhcp);
if (config->dhcp == (uint8_t)STA_STATIC)
eSize += sizeof(station_config_t::_config);
eSize += sizeof('\0') + sizeof('\0');
for (_dp = AC_HEADERSIZE; _dp < _containSize + AC_HEADERSIZE; _dp++) {
uint8_t c = _eeprom->read(_dp);
if (c == 0xff) {
uint16_t fp = _dp;
while (_eeprom->read(++_dp) == 0xff) {}
if (_dp - fp >= eSize) {
_dp = fp;
rep = true;
break;
}
_dp--;
}
}
// Save new entry
uint8_t c;
const uint8_t* dt;
dt = config->ssid;
do { // Write SSID
c = *dt++;
_eeprom->write(_dp++, c);
} while (c != '\0');
dt = config->password;
do { // Write password
c = *dt++;
_eeprom->write(_dp++, c);
} while (c != '\0');
for (uint8_t i = 0; i < sizeof(station_config_t::bssid); i++)
_eeprom->write(_dp++, config->bssid[i]); // write BSSID
_eeprom->write(_dp++, config->dhcp); // write dhcp flag
if (config->dhcp == (uint8_t)STA_STATIC) {
for (uint8_t e = 0; e < sizeof(station_config_t::_config::addr) / sizeof(uint32_t); e++) {
uint32_t ip = config->config.addr[e];
for (uint8_t b = 1; b <= sizeof(ip); b++)
_eeprom->write(_dp++, ((uint8_t*)&ip)[sizeof(ip) - b]);
}
}
// Terminate container, mark to the end of credential area.
// When the entry is replaced, not mark a terminator.
if (!rep) {
_eeprom->write(_dp, '\0');
// Update container size
_containSize = _dp - AC_HEADERSIZE;
_eeprom->write(_offset + sizeof(AC_IDENTIFIER) - 1 + sizeof(uint8_t), (uint8_t)_containSize);
_eeprom->write(_offset + sizeof(AC_IDENTIFIER) - 1 + sizeof(uint8_t) + 1, (uint8_t)(_containSize >> 8));
}
rc &= _eeprom->commit();
delay(10);
_eeprom->end();
return rc;
}
/**
* Get the SSID and password from EEPROM indicated by _dp as the pointer
* of current read address. FF is skipped as unavailable area.
* @param ssid A SSID storing address.
* @param password A password storing address.
*/
void AutoConnectCredential::_retrieveEntry(station_config_t* config) {
uint8_t ec;
// Skip unavailable entry.
while ((ec = _eeprom->read(_dp++)) == 0xff) {}
_ep = _dp - 1;
// Retrieve SSID
uint8_t* bp = config->ssid;
*bp++ = ec;
do {
ec = _eeprom->read(_dp++);
*bp++ = ec;
} while (ec != '\0');
// Retrieve Password
bp = config->password;
do {
ec = _eeprom->read(_dp++);
*bp++ = ec;
} while (ec != '\0');
// Retrieve BSSID
for (uint8_t i = 0; i < sizeof(station_config_t::bssid); i++)
config->bssid[i] = _eeprom->read(_dp++);
// Extended readout for static IP
config->dhcp = _eeprom->read(_dp++);
if (config->dhcp == (uint8_t)STA_STATIC) {
for (uint8_t e = 0; e < sizeof(station_config_t::_config::addr) / sizeof(uint32_t); e++) {
uint32_t* ip = &config->config.addr[e];
*ip = 0;
for (uint8_t b = 0; b < sizeof(uint32_t); b++) {
*ip <<= 8;
*ip += _eeprom->read(_dp++);
}
}
}
}
#else
/**
* AutoConnectCredential constructor takes the available count of saved
* entries.
* The credential area in the flash used by AutoConnect was moved from
* EEPROM to NVS with v.1.0.0. A stored credential data structure of
* Preferences is as follows. It has no identifier as AC_CREDT.
* 0 12 3 (u) (u+16) (t)
* +-+--+-----------------+-+--+--+--+----+----+-----------------+--+
* |e|ss|ssid\0pass\0bssid|d|ip|gw|nm|dns1|dns2|ssid\0pass\0bssid|\0|
* +-+--+-----------------+-+--+--+--+----+----+-----------------+--+
* e : Number of contained entries(uint8_t).
* ss : Container size, excluding ID and number of entries(uint16_t).
* ssid: SSID string with null termination.
* password : Password string with null termination.
* bssid : BSSID 6 bytes.
* d : DHCP is in available. 0:DHCP 1:Static IP
* ip - dns2 : Optional fields for static IPs configuration, these fields are available when d=1.
* ip : Static IP (uint32_t)
* gw : Gateway address (uint32_t)
* nm : Netmask (uint32_t)
* dns1 : Primary DNS (uint32)
* dns2 : Secondary DNS (uint32_t)
* t : The end of the container is a continuous '\0'.
* SSID and PASSWORD are terminated by '\ 0'.
*/
AutoConnectCredential::AutoConnectCredential() {
_allocateEntry();
}
AutoConnectCredential::AutoConnectCredential(uint16_t offset) {
// In ESP32, always use from the beginning of the Preferences area.
// The offset parameter is invalid but preserved for backward compatibility.
(void)(offset);
_allocateEntry();
}
void AutoConnectCredential::_allocateEntry(void) {
_pref.reset(new Preferences);
_entries = _import();
}
AutoConnectCredential::~AutoConnectCredential() {
_credit.clear();
_pref.reset();
}
/**
* Delete the credential entry for the specified SSID in the EEPROM.
* @param ssid A SSID character string to be deleted.
* @retval true The entry successfully delete.
* false Could not deleted.
*/
inline bool AutoConnectCredential::del(const char* ssid) {
return _del(ssid, true);
}
/**
* Refresh the number of stored credential entries.
* @retval A number of entries.
*/
inline uint8_t AutoConnectCredential::entries(void) {
_entries = _credit.size();
return _entries;
}
/**
* Load the credential entry for the specified SSID from the internal
* dictionary. The credentials are stored to the station_config
* structure which specified by *config as the SSID and password.
* @param ssid A SSID character string to be loaded.
* @param config A station_config structure pointer.
* @retval The entry number of the SSID. If the number less than 0,
* the specified SSID was not found.
*/
int8_t AutoConnectCredential::load(const char* ssid, station_config_t* config) {
// Determine the number in entries
int8_t en = 0;
_entries = _import(); // Reload the saved credentials
for (decltype(_credit)::iterator it = _credit.begin(), e = _credit.end(); it != e; ++it) {
String key = it->first;
if (!strcmp(ssid, key.c_str())) {
_obtain(it, config);
return en;
}
en++;
}
return -1;
}
/**
* Load the credential entry for the specified number from the internal
* dictionary. The credentials are stored to the station_config
* structure which specified *config as the SSID and password.
* @param entry A number of entry to be loaded.
* @param config A station_config structure pointer.
* @retval true The entry number of the SSID.
* false The number is not available.
*/
bool AutoConnectCredential::load(int8_t entry, station_config_t* config) {
_entries = _import();
for (decltype(_credit)::iterator it = _credit.begin(), e = _credit.end(); it != e; ++it) {
if (!entry--) {
_obtain(it, config);
return true;
}
}
return false;
}
/**
* Save SSID and password to Preferences.
* When the same SSID already exists, it will be replaced. If the current
* entry size insufficient for the new entry, the entry will be appended
* and increase whole size. Its previous areas are freed with FF and reused.
* @param config A pointer to the station_config structure storing SSID and password.
* @retval true Successfully saved.
* @retval false Preferences commit failed.
*/
bool AutoConnectCredential::save(const station_config_t* config) {
if (_add(config)) {
return _commit() > 0 ? true : false;
}
return false;
}
/**
* Add an entry to internal dictionary that is std::map structure.
* It adds an entry by the insert after will delete the same entry
* caused std:: map does not accept duplicate keys.
* @param config A pointer to the station_config structure storing SSID and password.
* @retval true Successfully saved.
* @retval false Preferences commit failed.
*/
bool AutoConnectCredential::_add(const station_config_t* config) {
const String ssid = String(reinterpret_cast<const char*>(config->ssid));
if (ssid.length() > 0) {
// Remove a same entry to insert a new one.
_del(ssid.c_str(), false);
// Insert
AC_CREDTBODY_t credtBody;
credtBody.password = String(reinterpret_cast<const char*>(config->password));
memcpy(credtBody.bssid, config->bssid, sizeof(AC_CREDTBODY_t::bssid));
credtBody.dhcp = config->dhcp;
for (uint8_t e = 0; e < sizeof(AC_CREDTBODY_t::ip) / sizeof(uint32_t); e++)
credtBody.ip[e] = credtBody.dhcp == (uint8_t)STA_STATIC ? config->config.addr[e] : 0U;
std::pair<AC_CREDT_t::iterator, bool> rc = _credit.insert(std::make_pair(ssid, credtBody));
_entries = _credit.size();
#ifdef AC_DBG
if (!rc.second) {
AC_DBG("Failed to save a credential %s\n", config->ssid);
}
#endif
return rc.second;
}
return false;
}
/**
* Serialize the AutoConnectCredential instance and write it back to NVS.
*/
size_t AutoConnectCredential::_commit(void) {
AC_CREDTBODY_t credtBody;
String ssid;
// Calculate the serialization size for each entry and add the size of 'e' with the size of 'ss' to it.
size_t sz = 0;
for (const auto& credt : _credit) {
ssid = credt.first;
credtBody = credt.second;
sz += ssid.length() + sizeof('\0') + credtBody.password.length() + sizeof('\0') + sizeof(AC_CREDTBODY_t::bssid) + sizeof(AC_CREDTBODY_t::dhcp);
if (credtBody.dhcp == (uint32_t)STA_STATIC) {
for (uint8_t e = 0; e < sizeof(AC_CREDTBODY_t::ip) / sizeof(uint32_t); e++)
sz += sizeof(uint32_t);
}
}
// When the entry is not empty, the size of container terminator as '\0' must be added.
_containSize = sz + (_entries ? sizeof('\0') : 0);
// Calculate the nvs pool size for saving to NVS. Add size of 'e' and 'ss' field.
size_t psz = _containSize + sizeof(uint8_t) + sizeof(uint16_t);
// Dump container to serialization pool and write it back to NVS.
uint8_t* credtPool = (uint8_t*)malloc(psz);
if (credtPool) {
uint16_t dp = 0;
credtPool[dp++] = _entries; // 'e'
credtPool[dp++] = (uint8_t)(psz & 0x00ff); // 'ss' low byte
credtPool[dp++] = (uint8_t)(psz >> 8); // 'ss' high byte
// Starts dump of credential entries
for (const auto& credt : _credit) {
ssid = credt.first; // Retrieve SSID
credtBody = credt.second; // Retrieve an entry
// SSID
size_t itemLen = ssid.length() + sizeof('\0');
ssid.toCharArray(reinterpret_cast<char*>(&credtPool[dp]), itemLen);
// Password
dp += itemLen;
itemLen = credtBody.password.length() + sizeof('\0');
credtBody.password.toCharArray(reinterpret_cast<char*>(&credtPool[dp]), itemLen);
// BSSID
dp += itemLen;
memcpy(&credtPool[dp], credtBody.bssid, sizeof(station_config_t::bssid));
dp += sizeof(station_config_t::bssid);
// DHCP/Static IP indicator
credtPool[dp++] = (uint8_t)credtBody.dhcp;
// Static IP configuration
if (credtBody.dhcp == STA_STATIC) {
for (uint8_t e = 0; e < sizeof(AC_CREDTBODY_t::ip) / sizeof(uint32_t); e++) {
for (uint8_t b = 1; b <= sizeof(credtBody.ip[e]); b++)
credtPool[dp++] = ((uint8_t*)&credtBody.ip[e])[sizeof(credtBody.ip[e]) - b];
}
}
}
if (_credit.size() > 0)
credtPool[dp] = '\0'; // Terminates a container
// Write back to the nvs
if (_pref->begin(AC_CREDENTIAL_NVSNAME, false)) {
sz = _pref->putBytes(AC_CREDENTIAL_NVSKEY, credtPool, psz);
_pref->end();
}
#ifdef AC_DBG
else {
AC_DBG("Preferences begin failed to save " AC_CREDENTIAL_NVSKEY "\n");
}
#endif
free(credtPool);
}
#ifdef AC_DBG
else {
AC_DBG("Preferences pool %d(B) allocation failed\n", psz);
}
#endif
return sz;
}
/**
* Delete the credential entry Actually for the specified SSID from Preferences.
* @param ssid A SSID character string to be deleted.
* @param commit If false, delete only a credential entry without updating Preferences.
* @retval true The entry successfully delete.
* false Could not deleted.
*/
bool AutoConnectCredential::_del(const char* ssid, const bool commit) {
decltype(_credit)::iterator it = _credit.find(String(ssid));
if (it != _credit.end()) {
_credit.erase(it);
_entries = _credit.size();
if (commit)
_commit();
return true;
}
return false;
}
/**
* Import the credentials bulk data as Preferences from NVS.
* In ESP32, AutoConnect stores credentials in NVS from v1.0.0.
*/
uint8_t AutoConnectCredential::_import(void) {
uint8_t cn = 0;
if (_pref->begin(AC_CREDENTIAL_NVSNAME, true)) {
size_t psz = _getPrefBytesLength<Preferences>(_pref.get(), AC_CREDENTIAL_NVSKEY);
if (psz) {
uint8_t* credtPool = (uint8_t*)malloc(psz);
if (credtPool) {
_pref->getBytes(AC_CREDENTIAL_NVSKEY, static_cast<void*>(credtPool), psz);
_credit.clear();
uint16_t dp = 0;
cn = credtPool[dp++]; // Retrieve 'e'
_containSize = (uint16_t)credtPool[dp++];
_containSize += (uint16_t)(credtPool[dp++] << 8); // Retrieve size of 'ss'
// Starts import
while (dp < psz - sizeof('\0')) {
AC_CREDTBODY_t credtBody;
// SSID
String ssid = String(reinterpret_cast<const char*>(&credtPool[dp]));
// Password
dp += ssid.length() + sizeof('\0');
credtBody.password = String(reinterpret_cast<const char*>(&credtPool[dp]));
// BSSID
dp += credtBody.password.length() + sizeof('\0');
memcpy(credtBody.bssid, &credtPool[dp], sizeof(AC_CREDTBODY_t::bssid));
dp += sizeof(AC_CREDTBODY_t::bssid);
// DHCP/Static IP indicator
credtBody.dhcp = credtPool[dp++];
// Static IP configuration
for (uint8_t e = 0; e < sizeof(AC_CREDTBODY_t::ip) / sizeof(uint32_t); e++) {
uint32_t* ip = &credtBody.ip[e];
*ip = 0U;
if (credtBody.dhcp == (uint8_t)STA_STATIC) {
for (uint8_t b = 0; b < sizeof(uint32_t); b++) {
*ip <<= 8;
*ip += credtPool[dp++];
}
}
}
// Make an entry
_credit.insert(std::make_pair(ssid, credtBody));
}
free(credtPool);
}
#ifdef AC_DBG
else {
AC_DBG("Preferences pool %d(B) allocation failed\n", psz);
}
#endif
}
_pref->end();
}
#ifdef AC_DBG
else {
AC_DBG("Preferences begin failed to import " AC_CREDENTIAL_NVSKEY "\n");
}
#endif
return cn;
}
/**
* Obtains an entry pointed to by the specified iterator from the
* dictionary as the std::map that maintains the credentials into the
* station_config structure.
* @param it An iterator to an entry
* @param config the station_config structure storing SSID and password.
*/
void AutoConnectCredential::_obtain(AC_CREDT_t::iterator const& it, station_config_t* config) {
String ssid = it->first;
AC_CREDTBODY_t& credtBody = it->second;
ssid.toCharArray(reinterpret_cast<char*>(config->ssid), sizeof(station_config_t::ssid));
credtBody.password.toCharArray(reinterpret_cast<char*>(config->password), sizeof(station_config_t::password));
memcpy(config->bssid, credtBody.bssid, sizeof(station_config_t::bssid));
config->dhcp = credtBody.dhcp;
for (uint8_t e = 0; e < sizeof(AC_CREDTBODY_t::ip) / sizeof(uint32_t); e++)
config->config.addr[e] = credtBody.dhcp == (uint8_t)STA_STATIC ? credtBody.ip[e] : 0U;
}
#endif
/**
* Declaration of AutoConnectCredential class.
* @file AutoConnectCredential.h
* @author hieromon@gmail.com
* @version 1.2.0
* @date 2020-04-22
* @copyright MIT license.
*/
#ifndef _AUTOCONNECTCREDENTIAL_H_
#define _AUTOCONNECTCREDENTIAL_H_
// The AUTOCONNECT_USE_PREFERENCES macro indicates which classes apply
// to the credentials storage structure either EEPROM or Preferences.
// It is a valid indicator only for ESP32.
// Undefine this macro, it maintains backward compatibility by applying
// EEPROM to the credentials storage class in the arduino-esp32 core
// v1.0.2 and earlier.
#define AUTOCONNECT_USE_PREFERENCES
#include <Arduino.h>
#include <memory>
#if defined(ARDUINO_ARCH_ESP8266)
#define AC_CREDENTIAL_PREFERENCES 0
extern "C" {
#include <user_interface.h>
}
#elif defined(ARDUINO_ARCH_ESP32)
#ifdef AUTOCONNECT_USE_PREFERENCES
#define AC_CREDENTIAL_PREFERENCES 1
#else
#define AC_CREDENTIAL_PREFERENCES 0
#endif
#include <esp_wifi.h>
#endif
#include "AutoConnectDefs.h"
/**
* Credential storage area offset specifier in EEPROM.
* By defining AC_IDENTIFIER_OFFSET macro in the user sketch, the credential
* storage area can be shifted in EEPROM.
*/
#ifndef AC_IDENTIFIER_OFFSET
#define AC_IDENTIFIER_OFFSET 0
#endif
/**
* Storage identifier for AutoConnect credentials. It is global constant
* and reserved.
*/
#ifndef AC_IDENTIFIER
#define AC_IDENTIFIER "AC_CREDT"
#endif
typedef enum {
STA_DHCP = 0,
STA_STATIC
} station_config_dhcp;
typedef struct {
uint8_t ssid[32];
uint8_t password[64];
uint8_t bssid[6];
uint8_t dhcp; /**< 0:DHCP, 1:Static IP */
union _config {
uint32_t addr[5];
struct _sta {
uint32_t ip;
uint32_t gateway;
uint32_t netmask;
uint32_t dns1;
uint32_t dns2;
} sta;
} config;
} station_config_t;
class AutoConnectCredentialBase {
public:
explicit AutoConnectCredentialBase() : _entries(0), _containSize(0) {}
virtual ~AutoConnectCredentialBase() {}
virtual uint8_t entries(void) { return _entries; }
virtual uint16_t dataSize(void) const { return sizeof(AC_IDENTIFIER) - 1 + sizeof(uint8_t) + sizeof(uint16_t) + _containSize; }
virtual bool del(const char* ssid) = 0;
virtual int8_t load(const char* ssid, station_config_t* config) = 0;
virtual bool load(int8_t entry, station_config_t* config) = 0;
virtual bool save(const station_config_t* config) = 0;
protected:
virtual void _allocateEntry(void) = 0; /**< Initialize storage for credentials. */
uint8_t _entries; /**< Count of the available entry */
uint16_t _containSize; /**< Container size */
};
#if AC_CREDENTIAL_PREFERENCES == 0
// #pragma message "AutoConnectCredential applies the EEPROM"
#define NO_GLOBAL_EEPROM
#include <EEPROM.h>
/** AutoConnectCredential class using EEPROM for ESP8266 */
class AutoConnectCredential : public AutoConnectCredentialBase {
public:
AutoConnectCredential();
explicit AutoConnectCredential(uint16_t offset);
~AutoConnectCredential();
bool del(const char* ssid) override;
int8_t load(const char* ssid, station_config_t* config) override;
bool load(int8_t entry, station_config_t* config) override;
bool save(const station_config_t* config) override;
protected:
void _allocateEntry(void) override; /**< Initialize storage for credentials. */
private:
void _retrieveEntry(station_config_t* config); /**< Read an available entry. */
int _dp; /**< The current address in EEPROM */
int _ep; /**< The current entry address in EEPROM */
uint16_t _offset; /**< The offset for the saved area of credentials in EEPROM. */
std::unique_ptr<EEPROMClass> _eeprom; /**< shared EEPROM class */
};
#else
// #pragma message "AutoConnectCredential applies the Preferences"
#include <type_traits>
#include <map>
#include <Preferences.h>
#include <nvs.h>
#define AC_CREDENTIAL_NVSNAME AC_IDENTIFIER
#define AC_CREDENTIAL_NVSKEY AC_CREDENTIAL_NVSNAME
/** Declare the member function existence determination */
namespace AutoConnectUtil {
AC_HAS_FUNC(getBytesLength);
}
/** AutoConnectCredential class using Preferences for ESP32 */
class AutoConnectCredential : public AutoConnectCredentialBase {
public:
AutoConnectCredential();
explicit AutoConnectCredential(uint16_t offset);
~AutoConnectCredential();
bool del(const char* ssid) override;
uint8_t entries(void) override;
int8_t load(const char* ssid, station_config_t* config) override;
bool load(int8_t entry, station_config_t* config) override;
bool save(const station_config_t* config) override;
protected:
void _allocateEntry(void) override; /**< Initialize storage for credentials. */
private:
typedef struct {
String password;
uint8_t bssid[6];
uint8_t dhcp; /**< 1:DHCP, 2:Static IP */
uint32_t ip[5];
} AC_CREDTBODY_t; /**< Credential entry */
typedef std::map<String, AC_CREDTBODY_t> AC_CREDT_t;
bool _add(const station_config_t* config); /**< Add an entry */
size_t _commit(void); /**< Write back to the nvs */
bool _del(const char* ssid, const bool commit); /**< Deletes an entry */
uint8_t _import(void); /**< Import from the nvs */
void _obtain(AC_CREDT_t::iterator const& it, station_config_t* config); /**< Obtain an entry from iterator */
template<typename T>
typename std::enable_if<AutoConnectUtil::has_func_getBytesLength<T>::value, size_t>::type _getPrefBytesLength(T* pref, const char* key) {
return pref->getBytesLength(key);
}
template<typename T>
typename std::enable_if<!AutoConnectUtil::has_func_getBytesLength<T>::value, size_t>::type _getPrefBytesLength(T* pref, const char* key) {
AC_UNUSED(pref);
uint32_t handle;
size_t len;
esp_err_t err = nvs_open(AC_CREDENTIAL_NVSNAME, NVS_READONLY, &handle);
if (err)
len = 0;
else {
(void)nvs_get_blob(handle, key, NULL, &len);
nvs_close(handle);
}
return len;
}
AC_CREDT_t _credit; /**< Dictionary to maintain the credentials */
std::unique_ptr<Preferences> _pref; /**< Preferences class instance to access the nvs */
};
#endif
#endif // _AUTOCONNECTCREDENTIAL_H_
/**
* Predefined AutoConnect configuration parameters.
* @file AutoConnectDefs.h
* @author hieromon@gmail.com
* @version 1.2.2
* @date 2020-12-11
* @copyright MIT license.
*/
#ifndef _AUTOCONNECTDEFS_H_
#define _AUTOCONNECTDEFS_H_
// Uncomment the following AC_DEBUG to enable debug output.
//#define AC_DEBUG
// Debug output destination can be defined externally with AC_DEBUG_PORT
#ifndef AC_DEBUG_PORT
#define AC_DEBUG_PORT Serial
#endif // !AC_DEBUG_PORT
#ifdef AC_DEBUG
#define AC_DBG_DUMB(fmt, ...) do {AC_DEBUG_PORT.printf_P((PGM_P)PSTR(fmt), ## __VA_ARGS__ );} while (0)
#define AC_DBG(fmt, ...) do {AC_DEBUG_PORT.printf_P((PGM_P)PSTR("[AC] " fmt), ## __VA_ARGS__ );} while (0)
#else
#define AC_DBG(...) do {(void)0;} while(0)
#define AC_DBG_DUMB(...) do {(void)0;} while(0)
#endif // !AC_DEBUG
// Indicator to specify that AutoConnectAux handles elements with JSON.
// Comment out the AUTOCONNECT_USE_JSON macro to detach the ArduinoJson.
#ifndef AUTOCONNECT_NOUSE_JSON
#define AUTOCONNECT_USE_JSON
// Indicator of whether to use the AutoConnectUpdate feature.
#define AUTOCONNECT_USE_UPDATE
#endif
// SPIFFS has deprecated on EP8266 core. This flag indicates that
// the migration to LittleFS has not completed.
//#define AC_USE_SPIFFS
// Deploys SPIFFS usage flag to the global.
#if defined(ARDUINO_ARCH_ESP8266)
#ifdef AC_USE_SPIFFS
#define AUTOCONNECT_USE_SPIFFS
#endif
#elif defined(ARDUINO_ARCH_ESP32)
#define AUTOCONNECT_USE_SPIFFS
#endif
// Whether or not it points to the target access point is determined by
// matching the SSID or BSSID. The default key to collate is BSSID.
// The BSSID is usually fixed to the MAC address unique to its AP,
// but when using some mobile hotspots, the BSSID may change even for
// the same access point.
// If you operate inconvenience in aiming at the access point by BSSID,
// you can change the collation key to SSID by uncommenting the below.
// If the AUTOCONNECT_APKEY_SSID is defined at compile-time, the access
// point will be collated by the SSID.
//#define AUTOCONNECT_APKEY_SSID
// Predefined parameters
// SSID that Captive portal started.
#ifndef AUTOCONNECT_APID
#if defined(ARDUINO_ARCH_ESP8266)
#define AUTOCONNECT_APID "esp8266ap"
#elif defined(ARDUINO_ARCH_ESP32)
#define AUTOCONNECT_APID "esp32ap"
#endif // !ARDUINO_ARCH_ESP8266
#endif // !AUTOCONNECT_APID
// Password that Captive portal started.
#ifndef AUTOCONNECT_PSK
#define AUTOCONNECT_PSK "12345678"
#endif // !AUTOCONNECT_PSK
#ifndef AUTOCONNECT_AP_IP
// #define AUTOCONNECT_AP_IP 0x01F4A8C0 //*< 192.168.244.1 */
#define AUTOCONNECT_AP_IP 0x011CD9AC //*< 172.217.28.1 */
#endif // !AUTOCONNECT_AP_IP
#ifndef AUTOCONNECT_AP_GW
// #define AUTOCONNECT_AP_GW 0x01F4A8C0 //*< 192.168.244.1 */
#define AUTOCONNECT_AP_GW 0x011CD9AC //*< 172.217.28.1 */
#endif // !AUTOCONNECT_AP_GW
#ifndef AUTOCONNECT_AP_NM
#define AUTOCONNECT_AP_NM 0x00FFFFFF //*< 255.255.255.0 */
#endif // !AUTOCONNECT_AP_NM
#ifndef AUTOCONNECT_AP_CH
#define AUTOCONNECT_AP_CH 1
#endif // !AUTOCONNECT_AP_CH
// AutoConnect menu root path
#ifndef AUTOCONNECT_URI
#define AUTOCONNECT_URI "/_ac"
#endif // !AUTOCONNECT_URI
// Root URI of home path prepared by user sketch
#ifndef AUTOCONNECT_HOMEURI
#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"
#endif // !AUTOCONNECT_MENU_TITLE
// URIs of AutoConnect menu collection
#define AUTOCONNECT_URI_CONFIG AUTOCONNECT_URI "/config"
#define AUTOCONNECT_URI_CONNECT AUTOCONNECT_URI "/connect"
#define AUTOCONNECT_URI_RESULT AUTOCONNECT_URI "/result"
#define AUTOCONNECT_URI_OPEN AUTOCONNECT_URI "/open"
#define AUTOCONNECT_URI_DISCON AUTOCONNECT_URI "/disc"
#define AUTOCONNECT_URI_RESET AUTOCONNECT_URI "/reset"
#define AUTOCONNECT_URI_SUCCESS AUTOCONNECT_URI "/success"
#define AUTOCONNECT_URI_FAIL AUTOCONNECT_URI "/fail"
#define AUTOCONNECT_URI_UPDATE AUTOCONNECT_URI "/update"
#define AUTOCONNECT_URI_UPDATE_ACT AUTOCONNECT_URI "/update_act"
#define AUTOCONNECT_URI_UPDATE_PROGRESS AUTOCONNECT_URI "/update_progress"
#define AUTOCONNECT_URI_UPDATE_RESULT AUTOCONNECT_URI "/update_result"
// Number of seconds in uint time [s]
#ifndef AUTOCONNECT_UNITTIME
#define AUTOCONNECT_UNITTIME 30
#endif
// Time-out limitation when AutoConnect::begin [ms]
#ifndef AUTOCONNECT_TIMEOUT
#define AUTOCONNECT_TIMEOUT 30000
#endif // !AUTOCONNECT_TIMEOUT
// Captive portal timeout value [ms]
#ifndef AUTOCONNECT_CAPTIVEPORTAL_TIMEOUT
#define AUTOCONNECT_CAPTIVEPORTAL_TIMEOUT 0
#endif // !AUTOCONNECT_CAPTIVEPORTAL_TIMEOUT
// Advance wait time [s]
#ifndef AUTOCONNECT_STARTUPTIME
#define AUTOCONNECT_STARTUPTIME (AUTOCONNECT_TIMEOUT/1000)
#endif // !AUTOCONNECT_STARTUPTIME
// Response wait time until requesting a result of connection attempt, uint:[ms]
#ifndef AUTOCONNECT_RESPONSE_WAITTIME
#define AUTOCONNECT_RESPONSE_WAITTIME 2000
#endif // !AUTOCONNECT_RESPONSE_WAITTIME
// Default HTTP port
#ifndef AUTOCONNECT_HTTPPORT
#define AUTOCONNECT_HTTPPORT 80
#endif // !AUTOCONNECT_HTTPPORT
// DNS port
#ifndef AUTOCONNECT_DNSPORT
#define AUTOCONNECT_DNSPORT 53
#endif // !AUTOCONNECT_DNSPORT
// http response transfer method
#ifndef AUTOCONNECT_HTTP_TRANSFER
#define AUTOCONNECT_HTTP_TRANSFER PB_ByteStream
#endif // !AUTOCONNECT_HTTP_TRANSFER
// Reserved buffer size to build content
#ifndef AUTOCONNECT_CONTENTBUFFER_SIZE
#define AUTOCONNECT_CONTENTBUFFER_SIZE (13 * 1024)
#endif // !AUTOCONNECT_CONTENTBUFFER_SIZE
// Number of unit lines in the page that lists available SSIDs
#ifndef AUTOCONNECT_SSIDPAGEUNIT_LINES
#define AUTOCONNECT_SSIDPAGEUNIT_LINES 5
#endif // !AUTOCONNECT_SSIDPAGEUNIT_LINES
// SPI transfer speed for SD [Hz]
#ifndef AUTOCONNECT_SD_SPEED
#define AUTOCONNECT_SD_SPEED 4000000
#endif // !AUTOCONNECT_SD_SPEED
// Flicker signal related factors
// Flicker cycle during AP operation [ms]
#ifndef AUTOCONNECT_FLICKER_PERIODAP
#define AUTOCONNECT_FLICKER_PERIODAP 1000
#endif // !AUTOCONNECT_FLICKER_PERIODAP
// Flicker cycle while WiFi is not connected [ms]
#ifndef AUTOCONNECT_FLICKER_PERIODDC
#define AUTOCONNECT_FLICKER_PERIODDC (AUTOCONNECT_FLICKER_PERIODAP << 1)
#endif // !AUTOCONNECT_FLICKER_PERIODDC
// Flicker pulse width during AP operation (8bit resolution)
#ifndef AUTOCONNECT_FLICKER_WIDTHAP
#define AUTOCONNECT_FLICKER_WIDTHAP 96
#endif // !AUTOCONNECT_FLICKER_WIDTHAP
// Flicker pulse width while WiFi is not connected (8bit resolution)
#ifndef AUTOCONNECT_FLICKER_WIDTHDC
#define AUTOCONNECT_FLICKER_WIDTHDC 16
#endif // !AUTOCONNECT_FLICKER_WIDTHDISCON
// Ticker port
#ifndef AUTOCONNECT_TICKER_PORT
#if defined(BUILTIN_LED) || defined(LED_BUILTIN)
#define AUTOCONNECT_TICKER_PORT LED_BUILTIN
#else // Native pin for the arduino
#define AUTOCONNECT_TICKER_PORT 2
#endif
#endif
// Lowest WiFi signal strength (RSSI) that can be connected.
#ifndef AUTOCONNECT_MIN_RSSI
#define AUTOCONNECT_MIN_RSSI -120 // No limit
#endif // !AUTOCONNECT_MIN_RSSI
// ArduinoJson buffer size
#ifndef AUTOCONNECT_JSONBUFFER_SIZE
#define AUTOCONNECT_JSONBUFFER_SIZE 256
#endif // !AUTOCONNECT_JSONBUFFER_SIZE
#ifndef AUTOCONNECT_JSONDOCUMENT_SIZE
#define AUTOCONNECT_JSONDOCUMENT_SIZE (8 * 1024)
#endif // !AUTOCONNECT_JSONDOCUMENT_SIZE
#ifndef AUTOCONNECT_JSONPSRAM_SIZE
#define AUTOCONNECT_JSONPSRAM_SIZE (16* 1024)
#endif // !AUTOCONNECT_JSONPSRAM_SIZE
// Available HTTP port number for the update
#ifndef AUTOCONNECT_UPDATE_PORT
#define AUTOCONNECT_UPDATE_PORT 8000
#endif // !AUTOCONNECT_UPDATE_PORT
// HTTP client timeout limitation for the update [ms]
#ifndef AUTOCONNECT_UPDATE_TIMEOUT
#define AUTOCONNECT_UPDATE_TIMEOUT 8000
#endif // !AUTOCONNECT_UPDATE_TIMEOUT
// Maximum wait time until transitioning AutoConnectUpdate dialog page [ms]
#ifndef AUTOCONNECT_UPDATE_DURATION
#define AUTOCONNECT_UPDATE_DURATION 180000
#endif // !AUTOCONNECT_UPDATE_DURATION
// Interval time of progress status periodical inquiry [ms]
#ifndef AUTOCONNECT_UPDATE_INTERVAL
#define AUTOCONNECT_UPDATE_INTERVAL 400
#endif // !AUTOCONNECT_UPDATE_INTERVAL
// Wait timer for rebooting after updated
#ifndef AUTOCONNECT_UPDATE_WAITFORREBOOT
#define AUTOCONNECT_UPDATE_WAITFORREBOOT 15000
#endif // !AUTOCONNECT_UPDATE_WAITFORREBOOT
// A signal value that the board dependent LED turns on.
// As a typical example, the ON signal of built-in LED such as the
// NodeMCU is LOW and the HIGH for the NodeMCU-32S as another example.
#ifndef AUTOCONNECT_UPDATE_LEDON
// #define AUTOCONNECT_UPDATE_LEDON HIGH
#define AUTOCONNECT_UPDATE_LEDON LOW
#endif // !AUTOCONNECT_UPDATE_LEDON
// URIs of the behaviors owned by the update server
#ifndef AUTOCONNECT_UPDATE_CATALOG
#define AUTOCONNECT_UPDATE_CATALOG "/_catalog"
#endif // !AUTOCONNECT_UPDATE_CATALOG
#ifndef AUTOCONNECT_UPDATE_DOWNLOAD
#define AUTOCONNECT_UPDATE_DOWNLOAD "/"
#endif // !AUTOCONNECT_UPDATE_DOWNLOAD
#ifndef AUTOCONNECT_UPDATE_CATALOG_JSONBUFFER_SIZE
#define AUTOCONNECT_UPDATE_CATALOG_JSONBUFFER_SIZE 256
#endif // !AUTOCONNECT_UPDATE_CATALOG_JSONBUFFER_SIZE
// HTTP authentication default realm
#ifndef AUTOCONNECT_AUTH_REALM
#define AUTOCONNECT_AUTH_REALM "AUTOCONNECT"
#endif // !AUTOCONNECT_AUTH_REALM
// Flename pattern that AutoConnectOTA considers to be firmware.
// The extension used as the criterion for uploading destination is
// fixed.
// AUTOCONNECT_UPLOAD_ASFIRMWARE_USE_REGEXP allows you to use regular
// expressions with the extension to determine the uploading destination.
#ifndef AUTOCONNECT_UPLOAD_ASFIRMWARE
#ifdef AUTOCONNECT_UPLOAD_ASFIRMWARE_USE_REGEXP
#define AUTOCONNECT_UPLOAD_ASFIRMWARE "^.+\\.bin$"
#else
#define AUTOCONNECT_UPLOAD_ASFIRMWARE ".bin"
#endif
#endif
// Explicitly avoiding unused warning with token handler of PageBuilder
#define AC_UNUSED(expr) do { (void)(expr); } while (0)
// Numeric to string deployment
#define AUTOCONNECT_STRING_DEPLOY(n) _AUTOCONNECT_STRING_DEPLOY(n)
#define _AUTOCONNECT_STRING_DEPLOY(s) #s
// Generates a template that determines whether the class owns the
// specified member function.
// The purpose of this macro is to avoid the use of invalid member
// functions due to differences in the version of the library which
// AutoConnect depends on.
#define AC_HAS_FUNC(func) \
template<typename T> \
struct has_func_##func { \
private: \
typedef char one; \
typedef long two; \
template<typename U> static one test(decltype(&U::func)); \
template<typename U> static two test(...); \
public: \
enum { value = sizeof(test<T>(0)) == sizeof(char) }; \
}
#endif // _AUTOCONNECTDEFS_H_
/**
* Alias declarations for an accessible the AutoConnectElement class.
* @file AutoConnectElement.h
* @author hieromon@gmail.com
* @version 0.9.8
* @date 2019-03-11
* @copyright MIT license.
*/
#ifndef _AUTOCONNECTELEMENT_H_
#define _AUTOCONNECTELEMENT_H_
#include "AutoConnectElementBasis.h"
#ifdef AUTOCONNECT_USE_JSON
#include <ArduinoJson.h>
#include "AutoConnectElementJson.h"
using AutoConnectElement = AutoConnectElementJson;
using AutoConnectButton = AutoConnectButtonJson;
using AutoConnectCheckbox = AutoConnectCheckboxJson;
using AutoConnectFile = AutoConnectFileJson;
using AutoConnectInput = AutoConnectInputJson;
using AutoConnectRadio = AutoConnectRadioJson;
using AutoConnectSelect = AutoConnectSelectJson;
using AutoConnectStyle = AutoConnectStyleJson;
using AutoConnectSubmit = AutoConnectSubmitJson;
using AutoConnectText = AutoConnectTextJson;
#define AUTOCONNECT_JSON_BUFFER_SIZE 256
#else
using AutoConnectElement = AutoConnectElementBasis;
using AutoConnectButton = AutoConnectButtonBasis;
using AutoConnectCheckbox = AutoConnectCheckboxBasis;
using AutoConnectFile = AutoConnectFileBasis;
using AutoConnectInput = AutoConnectInputBasis;
using AutoConnectRadio = AutoConnectRadioBasis;
using AutoConnectSelect = AutoConnectSelectBasis;
using AutoConnectStyle = AutoConnectStyleBasis;
using AutoConnectSubmit = AutoConnectSubmitBasis;
using AutoConnectText = AutoConnectTextBasis;
#endif // !AUTOCONNECT_USE_JSON
/**
* Support declare the AutoConnectElement variable with reducing the
* arguments. These macros declare the AutoConnectElement variable
* with the same name as a "name" argument.
*/
#define ACElement(n, v) AutoConnectElement n(#n, v)
#define ACButton(n, ...) AutoConnectButton n(#n, ##__VA_ARGS__)
#define ACCheckbox(n, ...) AutoConnectCheckbox n(#n, ##__VA_ARGS__)
#define ACFile(n, ...) AutoConnectFile n(#n, ##__VA_ARGS__)
#define ACInput(n, ...) AutoConnectInput n(#n, ##__VA_ARGS__)
#define ACRadio(n, ...) AutoConnectRadio n(#n, ##__VA_ARGS__)
#define ACSelect(n, ...) AutoConnectSelect n(#n, ##__VA_ARGS__)
#define ACSubmit(n, ...) AutoConnectSubmit n(#n, ##__VA_ARGS__)
#define ACStyle(n, ...) AutoConnectStyle n(#n, ##__VA_ARGS__)
#define ACText(n, ...) AutoConnectText n(#n, ##__VA_ARGS__)
#endif // _AUTOCONNECTELEMENT_H_
/**
* Declaration of AutoConnectElement basic class.
* @file AutoConnectElementBasis.h
* @author hieromon@gmail.com
* @version 1.2.0
* @date 2020-11-11
* @copyright MIT license.
*/
#ifndef _AUTOCONNECTELEMENTBASIS_H_
#define _AUTOCONNECTELEMENTBASIS_H_
#include <vector>
#include <memory>
#include "AutoConnectUpload.h"
// AC_AUTOCONNECTELEMENT_ON_VIRTUAL macro absorbs the difference of
// inheritance attribute of AutoConnectElement depending on the use of JSON.
// In a configuration using JSON, the base class of each
// AutoConnectElement is a virtual inheritance.
#ifdef AUTOCONNECT_USE_JSON
#define AC_AUTOCONNECTELEMENT_ON_VIRTUAL virtual
#else
#define AC_AUTOCONNECTELEMENT_ON_VIRTUAL
#endif
// A set of enumerators of types and attributes subordinate to
// AutoConnectElements
typedef enum {
AC_Button,
AC_Checkbox,
AC_Element,
AC_File,
AC_Input,
AC_Radio,
AC_Select,
AC_Style,
AC_Submit,
AC_Text,
AC_Unknown = -1
} ACElement_t; /**< AutoConnectElement class type */
typedef enum {
AC_Horizontal,
AC_Vertical
} ACArrange_t; /**< The element arrange order */
typedef enum {
AC_File_FS = 0,
AC_File_SD,
AC_File_Extern
} ACFile_t; /**< AutoConnectFile media type */
typedef enum {
AC_Tag_None = 0,
AC_Tag_BR = 1,
AC_Tag_P = 2
} ACPosterior_t; /**< Tag to be generated following element */
typedef enum {
AC_Infront,
AC_Behind
} ACPosition_t; /**< Position of label subordinate to element */
typedef enum {
AC_Input_Text,
AC_Input_Password,
AC_Input_Number
} ACInput_t; /** Input box type attribute */
/**
* AutoConnectAux element base.
* Placed a raw text that can be added by user sketch.
* @param name A name string for the element.
* @param value A raw text to be placed in HTML.
*/
class AutoConnectElementBasis {
public:
explicit AutoConnectElementBasis(const char* name = "", const char* value = "", const ACPosterior_t post = AC_Tag_None) : name(String(name)), value(String(value)), post(post), enable(true), global(false) {
_type = AC_Element;
}
virtual ~AutoConnectElementBasis() {}
virtual const String toHTML(void) const { return enable ? posterior(value) : String(""); }
ACElement_t typeOf(void) const { return _type; }
const String posterior(const String& s) const;
#ifndef AUTOCONNECT_USE_JSON
template<typename T>
T& as(void);
#endif
String name; /**< Element name */
String value; /**< Element value */
ACPosterior_t post; /**< Tag to be generated with posterior */
bool enable; /**< Enabling the element */
bool global; /**< The value available in global scope */
protected:
ACElement_t _type; /**< Element type identifier */
};
/**
* Button arrangement class, a part of AutoConnectAux element.
* Place a labeled button that can be added by user sketch.
* @param name Button element name string.
* @param value Value string with the placed button.
* @param action Script code to execute with the button pushed.
*/
class AutoConnectButtonBasis : AC_AUTOCONNECTELEMENT_ON_VIRTUAL public AutoConnectElementBasis {
public:
explicit AutoConnectButtonBasis(const char* name = "", const char* value = "", const String& action = String(""), const ACPosterior_t post = AC_Tag_None) : AutoConnectElementBasis(name, value, post), action(String(action)) {
_type = AC_Button;
}
virtual ~AutoConnectButtonBasis() {}
const String toHTML(void) const override;
String action;
};
/**
* Checkbox arrangement class, a part of AutoConnectAux element.
* Place a optionally labeled input-box that can be added by user sketch.
* @param name Checkbox name string.
* @param value A string value associated with the input.
* @param label A label string that follows checkbox, optionally.
* The label is placed on the right side of the checkbox.
*/
class AutoConnectCheckboxBasis : AC_AUTOCONNECTELEMENT_ON_VIRTUAL public AutoConnectElementBasis {
public:
explicit AutoConnectCheckboxBasis(const char* name = "", const char* value = "", const char* label = "", const bool checked = false, const ACPosition_t labelPosition = AC_Behind, const ACPosterior_t post = AC_Tag_BR) : AutoConnectElementBasis(name, value, post), label(String(label)), checked(checked), labelPosition(labelPosition) {
_type = AC_Checkbox;
}
virtual ~AutoConnectCheckboxBasis() {}
const String toHTML(void) const override;
String label; /**< A label for a subsequent input box */
bool checked; /**< The element should be pre-selected */
ACPosition_t labelPosition; /**< Output label according to ACPosition_t */
};
/**
* File-select input arrangement class, a part of AutoConnectAux element.
* Place a optionally labeled file-select input box that can be added by user sketch.
* @param name File-select input box name string.
* @param value A string value entered by the selected file name.
* @param label A label string that follows file-select box, optionally.
* The label is placed in front of file-select box.
*/
class AutoConnectFileBasis : AC_AUTOCONNECTELEMENT_ON_VIRTUAL public AutoConnectElementBasis {
public:
explicit AutoConnectFileBasis(const char* name = "", const char* value = "", const char* label = "", const ACFile_t store = AC_File_FS, const ACPosterior_t post = AC_Tag_BR) : AutoConnectElementBasis(name, value, post), label(String(label)), store(store), size(0) {
_type = AC_File;
_upload.reset();
}
virtual ~AutoConnectFileBasis() {}
const String toHTML(void) const override;
bool attach(const ACFile_t store);
void detach(void) { _upload.reset(); }
AutoConnectUploadHandler* upload(void) const { return _upload.get(); }
String label; /**< A label for a subsequent input box */
ACFile_t store; /**< Type of file store */
String mimeType; /**< Uploading file mime type string */
size_t size; /**< Total uploaded bytes */
protected:
std::unique_ptr<AutoConnectUploadHandler> _upload;
};
/**
* Input-box arrangement class, a part of AutoConnectAux element.
* Place a optionally labeled input-box that can be added by user sketch.
* @param name Input-box name string.
* @param value Default value string. This string display as a placeholder by the default.
* @param label A label string that follows Input-box, optionally.
* The label is placed in front of Input-box.
*/
class AutoConnectInputBasis : AC_AUTOCONNECTELEMENT_ON_VIRTUAL public AutoConnectElementBasis {
public:
explicit AutoConnectInputBasis(const char* name = "", const char* value = "", const char* label = "", const char* pattern = "", const char* placeholder = "", const ACPosterior_t post = AC_Tag_BR, const ACInput_t apply = AC_Input_Text) : AutoConnectElementBasis(name, value, post), label(String(label)), pattern(String(pattern)), placeholder(String(placeholder)), apply(apply) {
_type = AC_Input;
}
virtual ~AutoConnectInputBasis() {}
const String toHTML(void) const override;
bool isValid(void) const;
String label; /**< A label for a subsequent input box */
String pattern; /**< Format pattern to aid validation of input value */
String placeholder; /**< Pre-filled placeholder */
ACInput_t apply; /**< An input element type attribute */
};
/**
* Radio-button arrangement class, a part of AutoConnectAux element.
* Place a group of radio-button items and selectable mark checked.
* @param name Radio-button name string.
* @param options Array of value collection.
* @param label A label string that follows radio-buttons group.
* @param checked Index of check marked item.
*/
class AutoConnectRadioBasis : AC_AUTOCONNECTELEMENT_ON_VIRTUAL public AutoConnectElementBasis {
public:
explicit AutoConnectRadioBasis(const char* name = "", std::vector<String> const& values = {}, const char* label = "", const ACArrange_t order = AC_Vertical, const uint8_t checked = 0, const ACPosterior_t post = AC_Tag_BR) : AutoConnectElementBasis(name, "", post), label(label), order(order), checked(checked), _values(values) {
_type = AC_Radio;
}
virtual ~AutoConnectRadioBasis() {}
const String toHTML(void) const override;
const String& operator[] (const std::size_t n) const { return at(n); }
void add(const String& value) { _values.push_back(String(value)); }
size_t size(void) const { return _values.size(); }
const String& at(const std::size_t n) const { return _values.at(n); }
void check(const String& value);
void empty(const size_t reserve = 0);
const String& value(void) const;
String label; /**< A label for a subsequent radio buttons */
ACArrange_t order; /**< layout order */
uint8_t checked; /**< Index of check marked item */
std::vector<String> tags; /**< For private API: Tag of each value */
protected:
std::vector<String> _values; /**< Items in a group */
};
/**
* Selection-box arrangement class, A part of AutoConnectAux element.
* Place a optionally labeled Selection-box that can be added by user sketch.
* @param name Input-box name string.
* @param options String array display in a selection list.
* @param label A label string that follows Input-box, optionally.
* The label is placed in front of Input-box.
*/
class AutoConnectSelectBasis : AC_AUTOCONNECTELEMENT_ON_VIRTUAL public AutoConnectElementBasis {
public:
explicit AutoConnectSelectBasis(const char* name = "", std::vector<String> const& options = {}, const char* label = "", const uint8_t selected = 0, const ACPosterior_t post = AC_Tag_BR) : AutoConnectElementBasis(name, "", post), label(String(label)), selected(selected), _options(options) {
_type = AC_Select;
}
virtual ~AutoConnectSelectBasis() {}
const String toHTML(void) const override;
const String& operator[] (const std::size_t n) const { return at(n); }
void add(const String& option) { _options.push_back(String(option)); }
size_t size(void) const { return _options.size(); }
const String& at(const std::size_t n) const { return _options.at(n); }
void select(const String& value);
void empty(const size_t reserve = 0);
const String& value(void) const;
String label; /**< A label for a subsequent input box */
uint8_t selected; /**< Index of checked value (1-based) */
protected:
std::vector<String> _options; /**< List options array */
};
/**
* An element class for inserting CSS in AutoConnectAux page.
* @param name Style name string.
* @param value CSS Native code.
*/
class AutoConnectStyleBasis : AC_AUTOCONNECTELEMENT_ON_VIRTUAL public AutoConnectElementBasis {
public:
explicit AutoConnectStyleBasis(const char* name = "", const char* value = "") : AutoConnectElementBasis(name, value, AC_Tag_None) {
_type = AC_Style;
}
virtual ~AutoConnectStyleBasis() {}
};
/**
* Submit button arrangement class, a part of AutoConnectAux element.
* Place a submit button with a label that can be added by user sketch.
* With the button behavior, the values of the elements contained in
* the form would be sent using the post method.
* @param name Button name string.
* @param value Sending value string.
* @param uri Sending uri string.
*/
class AutoConnectSubmitBasis : AC_AUTOCONNECTELEMENT_ON_VIRTUAL public AutoConnectElementBasis {
public:
explicit AutoConnectSubmitBasis(const char* name = "", const char* value = "", const char* uri = "", const ACPosterior_t post = AC_Tag_None) : AutoConnectElementBasis(name, value, post), uri(String(uri)) {
_type = AC_Submit;
}
virtual ~AutoConnectSubmitBasis() {}
const String toHTML(void) const override;
String uri; /**< An url of submitting to */
};
/**
* Text arrangement class, a part of AutoConnectAux element.
* @param
* @param name Text name string.
* @param value Text value string.
* @param style A string of style-code for decoration, optionally.
* @param format C string that contains the value to be formatted.
* An arrangement text would be placed with <div> contains. A string
* of style-codes are given for '<div style=>'.
*/
class AutoConnectTextBasis : AC_AUTOCONNECTELEMENT_ON_VIRTUAL public AutoConnectElementBasis {
public:
explicit AutoConnectTextBasis(const char* name = "", const char* value = "", const char* style = "", const char* format = "", const ACPosterior_t post = AC_Tag_None) : AutoConnectElementBasis(name, value, post), style(String(style)), format(String(format)) {
_type = AC_Text;
}
virtual ~AutoConnectTextBasis() {}
const String toHTML(void) const override;
String style; /**< CSS style modifier native code */
String format; /**< C string that contains the text to be written */
};
#ifndef AUTOCONNECT_USE_JSON
/**
* Casts only a class derived from the AutoConnectElement class to the
* actual element class.
*/
template<>
inline AutoConnectButtonBasis& AutoConnectElementBasis::as<AutoConnectButtonBasis>(void) {
if (typeOf() != AC_Button)
AC_DBG("%s mismatched type as <%d>\n", name.c_str(), (int)typeOf());
return *(reinterpret_cast<AutoConnectButtonBasis*>(this));
}
template<>
inline AutoConnectCheckboxBasis& AutoConnectElementBasis::as<AutoConnectCheckboxBasis>(void) {
if (typeOf() != AC_Checkbox)
AC_DBG("%s mismatched type as <%d>\n", name.c_str(), (int)typeOf());
return *(reinterpret_cast<AutoConnectCheckboxBasis*>(this));
}
template<>
inline AutoConnectFileBasis& AutoConnectElementBasis::as<AutoConnectFileBasis>(void) {
if (typeOf() != AC_File)
AC_DBG("%s mismatched type as <%d>\n", name.c_str(), (int)typeOf());
return *(reinterpret_cast<AutoConnectFileBasis*>(this));
}
template<>
inline AutoConnectInputBasis& AutoConnectElementBasis::as<AutoConnectInputBasis>(void) {
if (typeOf() != AC_Input)
AC_DBG("%s mismatched type as <%d>\n", name.c_str(), (int)typeOf());
return *(reinterpret_cast<AutoConnectInputBasis*>(this));
}
template<>
inline AutoConnectRadioBasis& AutoConnectElementBasis::as<AutoConnectRadioBasis>(void) {
if (typeOf() != AC_Radio)
AC_DBG("%s mismatched type as <%d>\n", name.c_str(), (int)typeOf());
return *(reinterpret_cast<AutoConnectRadioBasis*>(this));
}
template<>
inline AutoConnectSelectBasis& AutoConnectElementBasis::as<AutoConnectSelectBasis>(void) {
if (typeOf() != AC_Select)
AC_DBG("%s mismatched type as <%d>\n", name.c_str(), (int)typeOf());
return *(reinterpret_cast<AutoConnectSelectBasis*>(this));
}
template<>
inline AutoConnectStyleBasis& AutoConnectElementBasis::as<AutoConnectStyleBasis>(void) {
if (typeOf() != AC_Style)
AC_DBG("%s mismatched type as <%d>\n", name.c_str(), (int)typeOf());
return *(reinterpret_cast<AutoConnectStyleBasis*>(this));
}
template<>
inline AutoConnectSubmitBasis& AutoConnectElementBasis::as<AutoConnectSubmitBasis>(void) {
if (typeOf() != AC_Submit)
AC_DBG("%s mismatched type as <%d>\n", name.c_str(), (int)typeOf());
return *(reinterpret_cast<AutoConnectSubmitBasis*>(this));
}
template<>
inline AutoConnectTextBasis& AutoConnectElementBasis::as<AutoConnectTextBasis>(void) {
if (typeOf() != AC_Text)
AC_DBG("%s mismatched type as <%d>\n", name.c_str(), (int)typeOf());
return *(reinterpret_cast<AutoConnectTextBasis*>(this));
}
#endif
#endif // _AUTOCONNECTELEMENTBASIS_H_
/**
* Implementation of AutoConnectElementBasis classes.
* @file AutoConnectElementBasisImpl.h
* @author hieromon@gmail.com
* @version 1.2.0
* @date 2020-11-11
* @copyright MIT license.
*/
#ifndef _AUTOCONNECTELEMENTBASISIMPL_H_
#define _AUTOCONNECTELEMENTBASISIMPL_H_
#include <stdlib.h>
#include <stdio.h>
#if defined(ARDUINO_ARCH_ESP8266)
#include <regex.h>
#elif defined(ARDUINO_ARCH_ESP32)
#include <regex>
#endif
#include "AutoConnectElementBasis.h"
// Preserve a valid global Filesystem instance.
// It allows the interface to the actual filesystem for migration to LittleFS.
#ifdef AUTOCONNECT_USE_SPIFFS
namespace AutoConnectFS { SPIFFST& FLASHFS = SPIFFS; };
#else
namespace AutoConnectFS { SPIFFST& FLASHFS = LittleFS; };
#endif
/**
* Append post-tag according by the post attribute.
* @param s An original string
* @return A string that appended the post tag
*/
const String AutoConnectElementBasis::posterior(const String& s) const {
String html;
if (post == AC_Tag_BR)
html = s + String(F("<br>"));
else if (post == AC_Tag_P)
html = String("<p>") + s + String(F("</p>"));
else
html = s;
return html;
}
/**
* Generate an HTML <button> element. The onclick behavior depends on
* the code held in factionf member.
* @return An HTML string.
*/
const String AutoConnectButtonBasis::toHTML(void) const {
String html = String("");
if (enable) {
html = String(F("<button type=\"button\" name=\"")) + name + String(F("\" value=\"")) + value + String(F("\" onclick=\"")) + action + String("\">") + value + String(F("</button>"));
html = AutoConnectElementBasis::posterior(html);
}
return html;
}
/**
* Generate an HTML <input type=checkbox> element.
* A "value" is associated with the input tag and sent by the form
* action as the value of "name". If the label member is contained, it
* is placed to the right side of the checkbox to be labeled.
* f the label member is empty, only the checkbox is placed.
* @return An HTML string.
*/
const String AutoConnectCheckboxBasis::toHTML(void) const {
String html = String("");
if (enable) {
html = String(F("<input type=\"checkbox\" name=\"")) + name + String(F("\" value=\"")) + value + String("\"");
if (checked)
html += String(F(" checked"));
if (label.length())
html += String(F(" id=\"")) + name + String("\">");
if (label.length()) {
String labelTag = String(F("<label for=\"")) + name + String(F("\">")) + label + String(F("</label>"));
if (labelPosition == AC_Infront)
html = labelTag + html;
else if (labelPosition == AC_Behind)
html += labelTag;
}
html = AutoConnectElementBasis::posterior(html);
}
return html;
}
/**
* Generate an HTML <input type=file> element.
* The entered value can be obtained using the user callback function
* registered by AutoConnectAux::on after the form is sent in
* combination with AutoConnectSubmit.
* @return String an HTML string.
*/
const String AutoConnectFileBasis::toHTML(void) const {
String html = String("");
if (enable) {
if (label.length())
html = String(F("<label for=\"")) + name + String(F("\">")) + label + String(F("</label>"));
html += String(F("<input type=\"file\" id=\"")) + name + String(F("\" name=\"")) + name + String("\">");
html = AutoConnectElementBasis::posterior(html);
}
return html;
}
/**
* Instantiate the upload handler with the specified store type.
* @param store An enumeration value of ACFile_t
*/
bool AutoConnectFileBasis::attach(const ACFile_t store) {
AutoConnectUploadFS* handlerFS;
AutoConnectUploadSD* handlerSD;
// Release previous handler
detach();
// Classify a handler type and create the corresponding handler
switch (store) {
case AC_File_FS:
handlerFS = new AutoConnectUploadFS(AutoConnectFS::FLASHFS);
_upload.reset(reinterpret_cast<AutoConnectUploadHandler*>(handlerFS));
break;
case AC_File_SD:
handlerSD = new AutoConnectUploadSD(SD);
_upload.reset(reinterpret_cast<AutoConnectUploadHandler*>(handlerSD));
break;
case AC_File_Extern:
break;
}
return _upload ? true : false;
}
/**
* Generate an HTML <input type=text> element.
* If the value member is contained, it is reflected in the placeholder
* attribute. The entered value can be obtained using the user callback
* function registered by AutoConnectAux::on after the form is sent in
* combination with AutoConnectSubmit.
* @return String an HTML string.
*/
const String AutoConnectInputBasis::toHTML(void) const {
String html = String("");
if (enable) {
if (label.length())
html = String(F("<label for=\"")) + name + String("\">") + label + String(F("</label>"));
PGM_P applyType;
switch (apply) {
case AC_Input_Number:
applyType = PSTR("number");
break;
case AC_Input_Password:
applyType = PSTR("password");
break;
case AC_Input_Text:
default:
applyType = PSTR("text");
break;
}
html += String(F("<input type=\"")) + String(applyType) + String(F("\" id=\"")) + name + String(F("\" name=\"")) + name + String("\"");
if (pattern.length())
html += String(F(" pattern=\"")) + pattern + String("\"");
if (placeholder.length())
html += String(F(" placeholder=\"")) + placeholder + String("\"");
if (value.length())
html += String(F(" value=\"")) + value + String("\"");
html += String(">");
html = AutoConnectElementBasis::posterior(html);
}
return html;
}
/**
* Evaluate the pattern as a regexp and return whether value matches.
* Always return true if the pattern is undefined.
* @return true The value matches a pattern.
* @return false The value does not match a pattern.
*/
bool AutoConnectInputBasis::isValid(void) const {
bool rc = true;
if (pattern.length()) {
#if defined(ARDUINO_ARCH_ESP8266)
regex_t preg;
if (regcomp(&preg, pattern.c_str(), REG_EXTENDED) != 0) {
AC_DBG("%s regex compile failed\n", pattern.c_str());
rc = false;
}
else {
regmatch_t p_match[1];
rc = regexec(&preg, value.c_str(), 1, p_match, 0) == 0 ? true : false;
regfree(&preg);
}
#elif defined(ARDUINO_ARCH_ESP32)
const std::regex re(std::string(pattern.c_str()));
rc = std::regex_match(value.c_str(), re);
#endif
}
return rc;
}
/**
* 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++) {
if (at(n).equalsIgnoreCase(value)) {
checked = n + 1;
break;
}
}
}
/**
* Clear value items of AutoConnectRadio and reallocate new storage.
* All hold items are released.
* @param reserve If 'reserve' is greater than 0, this function
* allocates new holding storage with the value.
*/
void AutoConnectRadioBasis::empty(const size_t reserve) {
_values.clear();
std::vector<String>().swap(_values);
if (reserve)
_values.reserve(reserve);
checked = 0;
}
/**
* 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("");
if (enable) {
if (label.length()) {
html = label;
if (order == AC_Vertical)
html += String(F("<br>"));
}
uint8_t n = 0;
for (const String value : _values) {
n++;
String id = name + "_" + String(n);
html += String(F("<input type=\"radio\" name=\"")) + name + String(F("\" id=\"")) + id + String(F("\" value=\"")) + value + String("\"");
if (n == checked)
html += String(F(" checked"));
html += String(F("><label for=\"")) + id + String("\">") + value + String(F("</label>"));
if (n <= tags.size())
html += tags[n - 1];
if (order == AC_Vertical)
html += String(F("<br>"));
}
html = AutoConnectElementBasis::posterior(html);
}
return html;
}
/**
* Returns current selected value in the radio same group
*/
const String& AutoConnectRadioBasis::value(void) const {
static const String _nullString = String();
return checked ? _values.at(checked - 1) : _nullString;
}
/**
* Clear option items of AutoConnectSelect and reallocate new storage.
* All hold items are released.
* @param reserve If 'reserve' is greater than 0, this function
* allocates new holding storage with the value.
*/
void AutoConnectSelectBasis::empty(const size_t reserve) {
_options.clear();
std::vector<String>().swap(_options);
if (reserve)
_options.reserve(reserve);
selected = 0;
}
/**
* Indicate an entry with the specified value in the value's collection.
* @param value The value to indicates in the collection.
*/
void AutoConnectSelectBasis::select(const String& value) {
for (std::size_t n = 0; n < _options.size(); n++) {
if (at(n).equalsIgnoreCase(value)) {
selected = n + 1;
break;
}
}
}
/**
* Generate an HTML <select> element with an <option> element.
* The attribute value of the <option> element is given to the
* AutoConnectSelect class as a string array, which would be stored
* in the 'options' member. If a label member is contained, the <label>
* element would be generated the preface of <select>.
* @return String an HTML string.
*/
const String AutoConnectSelectBasis::toHTML(void) const {
String html = String("");
if (enable) {
if (label.length())
html = String(F("<label for=\"")) + name + String("\">") + label + String(F("</label>"));
html += String(F("<select name=\"")) + name + String(F("\" id=\"")) + name + String("\">");
uint8_t n = 1;
for (const String option : _options) {
html += String(F("<option value=\"")) + option + "\"";
if (n++ == selected)
html += String(F(" selected"));
html += ">" + option + String(F("</option>"));
}
html += String(F("</select>"));
html = AutoConnectElementBasis::posterior(html);
}
return html;
}
/**
* Returns current selected value in the radio same group
*/
const String& AutoConnectSelectBasis::value(void) const {
static const String _nullString = String();
return selected ? _options.at(selected - 1) : _nullString;
}
/**
* Generate an HTML <input type=button> element. This element is used
* for form submission. An 'onclick' attribute calls fixed JavaScript
* code as 'sa' named and it's included in the template.
* @return String an HTML string.
*/
const String AutoConnectSubmitBasis::toHTML(void) const {
String html = String("");
if (enable) {
html = String(F("<input type=\"button\" name=\"")) + name + String(F("\" value=\"")) + value + String(F("\" onclick=\"_sa('")) + uri + String("')\">");
html = AutoConnectElementBasis::posterior(html);
}
return html;
}
/**
* Generate an HTML text element from a string of the value member. If a style
* exists, it gives a style attribute.
* @return String an HTML string.
*/
const String AutoConnectTextBasis::toHTML(void) const {
String html = String("");
if (enable) {
html = String(F("<div id=\"")) + name + String('"');
String value_f = value;
if (style.length())
html += String(F(" style=\"")) + style + String("\"");
html += String(">");
if (format.length()) {
int buflen = (value.length() + format.length() + 16 + 1) & (~0xf);
char* buffer;
if ((buffer = (char*)malloc(buflen))) {
snprintf(buffer, buflen, format.c_str(), value.c_str());
value_f = String(buffer);
free(buffer);
}
}
html += value_f + String(F("</div>"));
html = AutoConnectElementBasis::posterior(html);
}
return html;
}
#endif // _AUTOCONNECTELEMENTBASISIMPL_H_
/**
* Declaration of AutoConnectElement extended classes using JSON.
* @file AutoConnectElementJson.h
* @author hieromon@gmail.com
* @version 1.2.0
* @date 2020-11-11
* @copyright MIT license.
*/
#ifndef _AUTOCONNECTELEMENTJSON_H_
#define _AUTOCONNECTELEMENTJSON_H_
#include "AutoConnectElementBasis.h"
#include "AutoConnectJsonDefs.h"
#define AUTOCONNECT_JSON_KEY_ACTION "action"
#define AUTOCONNECT_JSON_KEY_APPLY "apply"
#define AUTOCONNECT_JSON_KEY_ARRANGE "arrange"
#define AUTOCONNECT_JSON_KEY_AUTH "auth"
#define AUTOCONNECT_JSON_KEY_CHECKED "checked"
#define AUTOCONNECT_JSON_KEY_ELEMENT "element"
#define AUTOCONNECT_JSON_KEY_FORMAT "format"
#define AUTOCONNECT_JSON_KEY_GLOBAL "global"
#define AUTOCONNECT_JSON_KEY_LABEL "label"
#define AUTOCONNECT_JSON_KEY_LABELPOSITION "labelposition"
#define AUTOCONNECT_JSON_KEY_MENU "menu"
#define AUTOCONNECT_JSON_KEY_NAME "name"
#define AUTOCONNECT_JSON_KEY_OPTION "option"
#define AUTOCONNECT_JSON_KEY_PATTERN "pattern"
#define AUTOCONNECT_JSON_KEY_PLACEHOLDER "placeholder"
#define AUTOCONNECT_JSON_KEY_POSTERIOR "posterior"
#define AUTOCONNECT_JSON_KEY_SELECTED "selected"
#define AUTOCONNECT_JSON_KEY_STORE "store"
#define AUTOCONNECT_JSON_KEY_STYLE "style"
#define AUTOCONNECT_JSON_KEY_TITLE "title"
#define AUTOCONNECT_JSON_KEY_TYPE "type"
#define AUTOCONNECT_JSON_KEY_URI "uri"
#define AUTOCONNECT_JSON_KEY_VALUE "value"
#define AUTOCONNECT_JSON_TYPE_ACBUTTON "ACButton"
#define AUTOCONNECT_JSON_TYPE_ACCHECKBOX "ACCheckBox"
#define AUTOCONNECT_JSON_TYPE_ACELEMENT "ACElement"
#define AUTOCONNECT_JSON_TYPE_ACFILE "ACFile"
#define AUTOCONNECT_JSON_TYPE_ACINPUT "ACInput"
#define AUTOCONNECT_JSON_TYPE_ACRADIO "ACRadio"
#define AUTOCONNECT_JSON_TYPE_ACSELECT "ACSelect"
#define AUTOCONNECT_JSON_TYPE_ACSTYLE "ACStyle"
#define AUTOCONNECT_JSON_TYPE_ACSUBMIT "ACSubmit"
#define AUTOCONNECT_JSON_TYPE_ACTEXT "ACText"
#define AUTOCONNECT_JSON_VALUE_BASIC "basic"
#define AUTOCONNECT_JSON_VALUE_BEHIND "behind"
#define AUTOCONNECT_JSON_VALUE_BR "br"
#define AUTOCONNECT_JSON_VALUE_DIGEST "digest"
#define AUTOCONNECT_JSON_VALUE_EXTERNAL "extern"
#define AUTOCONNECT_JSON_VALUE_FS "fs"
#define AUTOCONNECT_JSON_VALUE_HORIZONTAL "horizontal"
#define AUTOCONNECT_JSON_VALUE_INFRONT "infront"
#define AUTOCONNECT_JSON_VALUE_NONE "none"
#define AUTOCONNECT_JSON_VALUE_NUMBER "number"
#define AUTOCONNECT_JSON_VALUE_PAR "par"
#define AUTOCONNECT_JSON_VALUE_PASSWORD "password"
#define AUTOCONNECT_JSON_VALUE_TEXT "text"
#define AUTOCONNECT_JSON_VALUE_SD "sd"
#define AUTOCONNECT_JSON_VALUE_VERTICAL "vertical"
/**
* AutoConnectAux element base with handling with JSON object.
* Placed a raw text that can be added by user sketch.
* @param name A name string for the element.
* @param value A raw text to be placed in HTML.
*/
class AutoConnectElementJson : virtual public AutoConnectElementBasis {
public:
explicit AutoConnectElementJson(const char* name = "", const char* value = "", const ACPosterior_t post = AC_Tag_None) : _defaultPost(AC_Tag_None) {
AutoConnectElementBasis::name = String(name);
AutoConnectElementBasis::value = String(value);
AutoConnectElementBasis::post = post;
}
~AutoConnectElementJson() {}
virtual size_t getObjectSize(void) const;
virtual bool loadMember(const JsonObject& json);
virtual void serialize(JsonObject& json);
template<typename T>
T& as(void);
protected:
void _setMember(const JsonObject& json);
void _serialize(JsonObject& json);
protected:
ACPosterior_t _defaultPost;
};
/**
* Button arrangement class, a part of AutoConnectAux element with
* handling JSON object.
* Place a labeled button that can be added by user sketch.
* @param name Button element name string.
* @param value Value string with the placed button.
* @param action Script code to execute with the button pushed.
*/
class AutoConnectButtonJson : public AutoConnectElementJson, public AutoConnectButtonBasis {
public:
explicit AutoConnectButtonJson(const char* name = "", const char* value = "", const String& action = String(""), const ACPosterior_t post = AC_Tag_None) {
AutoConnectButtonBasis::name = String(name);
AutoConnectButtonBasis::value = String(value);
AutoConnectButtonBasis::action = String(action);
AutoConnectButtonBasis::post = post;
_defaultPost = AC_Tag_None;
}
~AutoConnectButtonJson() {}
size_t getObjectSize(void) const override;
bool loadMember(const JsonObject& json) override;
void serialize(JsonObject& json) override;
};
/**
* Checkbox arrangement class, a part of AutoConnectAux element with
* handling JSON object.
* Place a optionally labeled input-box that can be added by user sketch.
* @param name Checkbox name string.
* @param value A string value associated with the input.
* @param label A label string that follows checkbox, optionally.
* The label is placed on the right side of the checkbox.
*/
class AutoConnectCheckboxJson : public AutoConnectElementJson, public AutoConnectCheckboxBasis {
public:
explicit AutoConnectCheckboxJson(const char* name = "", const char* value = "", const char* label = "", const bool checked = false, const ACPosition_t labelPosition = AC_Behind, const ACPosterior_t post = AC_Tag_BR) {
AutoConnectCheckboxBasis::name = String(name);
AutoConnectCheckboxBasis::value = String(value);
AutoConnectCheckboxBasis::label = String(label);
AutoConnectCheckboxBasis::checked = checked;
AutoConnectCheckboxBasis::labelPosition = labelPosition;
AutoConnectCheckboxBasis::post = post;
_defaultPost = AC_Tag_BR;
}
~AutoConnectCheckboxJson() {}
size_t getObjectSize(void) const override;
bool loadMember(const JsonObject& json) override;
void serialize(JsonObject& json) override;
};
/**
* File-select input arrangement class, a part of AutoConnectAux element.
* Place a optionally labeled file-select input box that can be added by user sketch.
* @param name File-select input box name string.
* @param value A string value entered by the selected file name.
* @param label A label string that follows file-select box, optionally.
* The label is placed in front of file-select box.
* @param store An enumeration value of store type.
*/
class AutoConnectFileJson : public AutoConnectElementJson, public AutoConnectFileBasis {
public:
explicit AutoConnectFileJson(const char* name = "", const char* value= "", const char* label = "", const ACFile_t store = AC_File_FS, const ACPosterior_t post = AC_Tag_BR) {
AutoConnectFileBasis::name = String(name);
AutoConnectFileBasis::value = String(value);
AutoConnectFileBasis::label = String(label);
AutoConnectFileBasis::store = store;
AutoConnectFileBasis::post = post;
_defaultPost = AC_Tag_BR;
}
~AutoConnectFileJson() {}
size_t getObjectSize(void) const override;
bool loadMember(const JsonObject& json) override;
void serialize(JsonObject& json) override;
};
/**
* Input-box arrangement class, a part of AutoConnectAux element with
* handling JSON object.
* Place a optionally labeled input-box that can be added by user sketch.
* @param name Input-box name string.
* @param value Default value string. This string display as a placeholder by the default.
* @param label A label string that follows Input-box, optionally.
* The label is placed in front of Input-box.
*/
class AutoConnectInputJson : public AutoConnectElementJson, public AutoConnectInputBasis {
public:
explicit AutoConnectInputJson(const char* name = "", const char* value = "", const char* label = "", const char* pattern = "", const char* placeholder = "", const ACPosterior_t post = AC_Tag_BR, const ACInput_t apply = AC_Input_Text) {
AutoConnectInputBasis::name = String(name);
AutoConnectInputBasis::value = String(value);
AutoConnectInputBasis::label = String(label);
AutoConnectInputBasis::pattern = String(pattern);
AutoConnectInputBasis::placeholder = String(placeholder);
AutoConnectInputBasis::apply = apply;
AutoConnectInputBasis::post = post;
_defaultPost = AC_Tag_BR;
}
~AutoConnectInputJson() {}
size_t getObjectSize(void) const override;
bool loadMember(const JsonObject& json) override;
void serialize(JsonObject& json) override;
};
/**
* Radio-button arrangement class, a part of AutoConnectAux element.
* Place a group of radio-button items and selectable mark checked.
* @param name Radio-button name string.
* @param options Array of value collection.
* @param label A label string that follows radio-buttons group.
* @param checked Index of check marked item.
*/
class AutoConnectRadioJson : public AutoConnectElementJson, public AutoConnectRadioBasis {
public:
explicit AutoConnectRadioJson(const char* name = "", std::vector<String> const& values = {}, const char* label = "", const ACArrange_t order = AC_Vertical, const uint8_t checked = 0, const ACPosterior_t post = AC_Tag_BR) {
AutoConnectRadioBasis::name = String(name);
AutoConnectRadioBasis::_values = values;
AutoConnectRadioBasis::label = String(label);
AutoConnectRadioBasis::order = order;
AutoConnectRadioBasis::checked = checked;
AutoConnectRadioBasis::post = post;
_defaultPost = AC_Tag_BR;
}
~AutoConnectRadioJson() {}
size_t getObjectSize(void) const override;
bool loadMember(const JsonObject& json) override;
void serialize(JsonObject& json) override;
};
/**
* Selection-box arrangement class, A part of AutoConnectAux element.
* Place a optionally labeled Selection-box that can be added by user sketch.
* @param name Input-box name string.
* @param options String array display in a selection list.
* @param label A label string that follows Input-box, optionally.
* The label is placed in front of Input-box.
*/
class AutoConnectSelectJson : public AutoConnectElementJson, public AutoConnectSelectBasis {
public:
explicit AutoConnectSelectJson(const char* name = "", std::vector<String> const& options = {}, const char* label = "", const uint8_t selected = 0, const ACPosterior_t post = AC_Tag_BR) {
AutoConnectSelectBasis::name = String(name);
AutoConnectSelectBasis::_options = options;
AutoConnectSelectBasis::label = String(label);
AutoConnectSelectBasis::selected = selected;
AutoConnectSelectBasis::post = post;
_defaultPost = AC_Tag_BR;
}
~AutoConnectSelectJson() {}
size_t getObjectSize(void) const override;
bool loadMember(const JsonObject& json) override;
void serialize(JsonObject& json) override;
};
/**
* CSS style arrangement class, a part of AutoConnectAux element.
* This element assumes CSS that came into effect as a style code will
* assign. Therefore, it does not check whether the CSS error exists in
* the value set in AutoConnectStyle. Also, because AutoConnect inserts
* its style code at the end of the style block on the AutoConnectAux
* page, it may affect the AutoConnect web page elements.
* @param name A style name string.
* @param value CSS style code.
*/
class AutoConnectStyleJson : public AutoConnectElementJson, public AutoConnectStyleBasis {
public:
explicit AutoConnectStyleJson(const char* name = "", const char* value = "") {
AutoConnectStyleBasis::name = String(name);
AutoConnectStyleBasis::value = String(value);
AutoConnectStyleBasis::post = AC_Tag_None;
}
~AutoConnectStyleJson() {}
bool loadMember(const JsonObject& json) override;
void serialize(JsonObject& json) override;
};
/**
* Submit button arrangement class, a part of AutoConnectAux element.
* Place a submit button with a label that can be added by user sketch.
* With the button behavior, the values of the elements contained in
* the form would be sent using the post method.
* @param name Button name string.
* @param value Sending value string.
* @param uri Sending uri string.
*/
class AutoConnectSubmitJson : public AutoConnectElementJson, public AutoConnectSubmitBasis {
public:
explicit AutoConnectSubmitJson(const char* name = "", const char* value = "", const char* uri = "", const ACPosterior_t post = AC_Tag_None) {
AutoConnectSubmitBasis::name = String(name);
AutoConnectSubmitBasis::value = String(value);
AutoConnectSubmitBasis::uri = String(uri);
AutoConnectSubmitBasis::post = post;
_defaultPost = AC_Tag_None;
}
~AutoConnectSubmitJson() {}
size_t getObjectSize(void) const override;
bool loadMember(const JsonObject& json) override;
void serialize(JsonObject& json) override;
};
/**
* Text arrangement class, a part of AutoConnectAux element.
* @param
* @param name Text name string.
* @param value Text value string.
* @param style A string of style-code for decoration, optionally.
* An arrangement text would be placed with <div> contains. A string
* of style-codes are given for '<div style=>'.
*/
class AutoConnectTextJson : public AutoConnectElementJson, public AutoConnectTextBasis {
public:
explicit AutoConnectTextJson(const char* name = "", const char* value = "", const char* style = "", const char* format = "", const ACPosterior_t post = AC_Tag_None) {
AutoConnectTextBasis::name = String(name);
AutoConnectTextBasis::value = String(value);
AutoConnectTextBasis::style = String(style);
AutoConnectTextBasis::format = String(format);
AutoConnectTextBasis::post = post;
_defaultPost = AC_Tag_None;
}
~AutoConnectTextJson() {}
size_t getObjectSize(void) const override;
bool loadMember(const JsonObject& json) override;
void serialize(JsonObject& json) override;
};
/**
* Casts only a class derived from the AutoConnectElement class to the
* actual element class.
*/
template<>
inline AutoConnectButtonJson& AutoConnectElementJson::as<AutoConnectButtonJson>(void) {
if (typeOf() != AC_Button) {
AC_DBG("%s mismatched type as <%d>\n", name.c_str(), (int)typeOf());
}
return *(reinterpret_cast<AutoConnectButtonJson*>(this));
}
template<>
inline AutoConnectCheckboxJson& AutoConnectElementJson::as<AutoConnectCheckboxJson>(void) {
if (typeOf() != AC_Checkbox) {
AC_DBG("%s mismatched type as <%d>\n", name.c_str(), (int)typeOf());
}
return *(reinterpret_cast<AutoConnectCheckboxJson*>(this));
}
template<>
inline AutoConnectFileJson& AutoConnectElementJson::as<AutoConnectFileJson>(void) {
if (typeOf() != AC_File) {
AC_DBG("%s mismatched type as <%d>\n", name.c_str(), (int)typeOf());
}
return *(reinterpret_cast<AutoConnectFileJson*>(this));
}
template<>
inline AutoConnectInputJson& AutoConnectElementJson::as<AutoConnectInputJson>(void) {
if (typeOf() != AC_Input) {
AC_DBG("%s mismatched type as <%d>\n", name.c_str(), (int)typeOf());
}
return *(reinterpret_cast<AutoConnectInputJson*>(this));
}
template<>
inline AutoConnectRadioJson& AutoConnectElementJson::as<AutoConnectRadioJson>(void) {
if (typeOf() != AC_Radio) {
AC_DBG("%s mismatched type as <%d>\n", name.c_str(), (int)typeOf());
}
return *(reinterpret_cast<AutoConnectRadioJson*>(this));
}
template<>
inline AutoConnectSelectJson& AutoConnectElementJson::as<AutoConnectSelectJson>(void) {
if (typeOf() != AC_Select) {
AC_DBG("%s mismatched type as <%d>\n", name.c_str(), (int)typeOf());
}
return *(reinterpret_cast<AutoConnectSelectJson*>(this));
}
template<>
inline AutoConnectStyleJson& AutoConnectElementJson::as<AutoConnectStyleJson>(void) {
if (typeOf() != AC_Style) {
AC_DBG("%s mismatched type as <%d>\n", name.c_str(), (int)typeOf());
}
return *(reinterpret_cast<AutoConnectStyleJson*>(this));
}
template<>
inline AutoConnectSubmitJson& AutoConnectElementJson::as<AutoConnectSubmitJson>(void) {
if (typeOf() != AC_Submit) {
AC_DBG("%s mismatched type as <%d>\n", name.c_str(), (int)typeOf());
}
return *(reinterpret_cast<AutoConnectSubmitJson*>(this));
}
template<>
inline AutoConnectTextJson& AutoConnectElementJson::as<AutoConnectTextJson>(void) {
if (typeOf() != AC_Text) {
AC_DBG("%s mismatched type as <%d>\n", name.c_str(), (int)typeOf());
}
return *(reinterpret_cast<AutoConnectTextJson*>(this));
}
#endif // _AUTOCONNECTELEMENTJSON_H_
/**
* Implementation of AutoConnectElementJson classes.
* @file AutoConnectElementJsonImpl.h
* @author hieromon@gmail.com
* @version 1.2.0
* @date 2020-11-11
* @copyright MIT license.
*/
#ifndef _AUTOCONNECTELEMENTJSONIMPL_H_
#define _AUTOCONNECTELEMENTJSONIMPL_H_
#include "AutoConnectElementJson.h"
/**
* Returns JSON object size.
* @return An object size for JsonBuffer.
*/
size_t AutoConnectElementJson::getObjectSize(void) const {
size_t size = JSON_OBJECT_SIZE(3);
size += sizeof(AUTOCONNECT_JSON_KEY_NAME) + sizeof(AUTOCONNECT_JSON_KEY_TYPE) + sizeof(AUTOCONNECT_JSON_KEY_VALUE) + sizeof(AUTOCONNECT_JSON_TYPE_ACELEMENT);
size += name.length() + 1 + value.length() + 1;
if (post != _defaultPost)
size += sizeof(AUTOCONNECT_JSON_KEY_POSTERIOR) + (sizeof(AUTOCONNECT_JSON_VALUE_BR) > sizeof(AUTOCONNECT_JSON_VALUE_PAR) ? sizeof(AUTOCONNECT_JSON_VALUE_BR) : sizeof(AUTOCONNECT_JSON_VALUE_PAR));
return size;
}
/**
* Load an element member value from the JSON object.
* @param json JSON object with the definition of AutoConnectElement.
* @return true AutoConnectElement loaded
* @return false Type of AutoConnectElement is mismatched.
*/
bool AutoConnectElementJson::loadMember(const JsonObject& json) {
String type = json[F(AUTOCONNECT_JSON_KEY_TYPE)].as<String>();
if (type.equalsIgnoreCase(F(AUTOCONNECT_JSON_TYPE_ACELEMENT))) {
_setMember(json);
return true;
}
return false;
}
/**
* Serialize AutoConnectElement to JSON.
* @param json JSON object to be serialized.
*/
void AutoConnectElementJson::serialize(JsonObject& json) {
_serialize(json);
json[F(AUTOCONNECT_JSON_KEY_TYPE)] = String(F(AUTOCONNECT_JSON_TYPE_ACELEMENT));
json[F(AUTOCONNECT_JSON_KEY_VALUE)] = value;
}
/**
* Serialize AutoConnectElement to JSON.
* This function is base for each element.
* @param json JSON object to be serialized.
*/
void AutoConnectElementJson::_serialize(JsonObject& json) {
json[F(AUTOCONNECT_JSON_KEY_NAME)] = name;
if (post != _defaultPost) {
PGM_P posterior;
switch (post) {
case AC_Tag_BR:
posterior = PSTR(AUTOCONNECT_JSON_VALUE_BR);
break;
case AC_Tag_P:
posterior = PSTR(AUTOCONNECT_JSON_VALUE_PAR);
break;
case AC_Tag_None:
default:
posterior = PSTR(AUTOCONNECT_JSON_VALUE_NONE);
break;
}
json[F(AUTOCONNECT_JSON_KEY_POSTERIOR)] = String(FPSTR(posterior));
}
if (global)
json[F(AUTOCONNECT_JSON_KEY_GLOBAL)] = true;
}
/**
* Set items common to any type of AutoConnectElement from JSON objects.
* @param json JSON object with the definition of AutoConnectElement.
*/
void AutoConnectElementJson::_setMember(const JsonObject& json) {
name = json[F(AUTOCONNECT_JSON_KEY_NAME)].as<String>();
if (json.containsKey(F(AUTOCONNECT_JSON_KEY_VALUE)))
value = json[F(AUTOCONNECT_JSON_KEY_VALUE)].as<String>();
if (json.containsKey(F(AUTOCONNECT_JSON_KEY_POSTERIOR))) {
String posterior = json[F(AUTOCONNECT_JSON_KEY_POSTERIOR)].as<String>();
if (posterior.equalsIgnoreCase(F(AUTOCONNECT_JSON_VALUE_NONE)))
post = AC_Tag_None;
else if (posterior.equalsIgnoreCase(F(AUTOCONNECT_JSON_VALUE_BR)))
post = AC_Tag_BR;
else if (posterior.equalsIgnoreCase(F(AUTOCONNECT_JSON_VALUE_PAR)))
post = AC_Tag_P;
else
AC_DBG("Warning '%s' loading, unknown posterior '%s'\n", name.c_str(), posterior.c_str());
}
if (json.containsKey(F(AUTOCONNECT_JSON_KEY_GLOBAL))) {
global = json[F(AUTOCONNECT_JSON_KEY_GLOBAL)].as<bool>();
}
}
/**
* Returns JSON object size.
* @return An object size for JsonBuffer.
*/
size_t AutoConnectButtonJson::getObjectSize(void) const {
size_t size = AutoConnectElementJson::getObjectSize() + JSON_OBJECT_SIZE(1);
size += sizeof(AUTOCONNECT_JSON_KEY_ACTION) + action.length() + 1;
return size;
}
/**
* Load a button element attribute member from the JSON object.
* @param json JSON object with the definition of AutoConnectElement.
* @return true AutoConnectElement loaded
* @return false Type of AutoConnectElement is mismatched.
*/
bool AutoConnectButtonJson::loadMember(const JsonObject& json) {
String type = json[F(AUTOCONNECT_JSON_KEY_TYPE)].as<String>();
if (type.equalsIgnoreCase(F(AUTOCONNECT_JSON_TYPE_ACBUTTON))) {
_setMember(json);
if (json.containsKey(F(AUTOCONNECT_JSON_KEY_ACTION)))
action = json[F(AUTOCONNECT_JSON_KEY_ACTION)].as<String>();
return true;
}
return false;
}
/**
* Serialize AutoConnectButton to JSON.
* @param json JSON object to be serialized.
*/
void AutoConnectButtonJson::serialize(JsonObject& json) {
_serialize(json);
json[F(AUTOCONNECT_JSON_KEY_TYPE)] = String(F(AUTOCONNECT_JSON_TYPE_ACBUTTON));
json[F(AUTOCONNECT_JSON_KEY_VALUE)] = value;
json[F(AUTOCONNECT_JSON_KEY_ACTION)] = action;
}
/**
* Returns JSON object size.
* @return An object size for JsonBuffer.
*/
size_t AutoConnectCheckboxJson::getObjectSize(void) const {
size_t size = AutoConnectElementJson::getObjectSize() + JSON_OBJECT_SIZE(2);
size += sizeof(AUTOCONNECT_JSON_KEY_LABEL) + label.length() + 1 + sizeof(AUTOCONNECT_JSON_KEY_CHECKED) + sizeof(AUTOCONNECT_JSON_KEY_LABELPOSITION) + sizeof(AUTOCONNECT_JSON_VALUE_INFRONT);
return size;
}
/**
* Load a checkbox element attribute member from the JSON object.
* @param json JSON object with the definition of AutoConnectElement.
* @return true AutoConnectElement loaded
* @return false Type of AutoConnectElement is mismatched.
*/
bool AutoConnectCheckboxJson::loadMember(const JsonObject& json) {
String type = json[F(AUTOCONNECT_JSON_KEY_TYPE)].as<String>();
if (type.equalsIgnoreCase(F(AUTOCONNECT_JSON_TYPE_ACCHECKBOX))) {
_setMember(json);
if (json.containsKey(F(AUTOCONNECT_JSON_KEY_LABEL)))
label = json[F(AUTOCONNECT_JSON_KEY_LABEL)].as<String>();
if (json.containsKey(F(AUTOCONNECT_JSON_KEY_CHECKED)))
checked = json[F(AUTOCONNECT_JSON_KEY_CHECKED)].as<bool>();
if (json.containsKey(F(AUTOCONNECT_JSON_KEY_LABELPOSITION))) {
String position = json[F(AUTOCONNECT_JSON_KEY_LABELPOSITION)].as<String>();
if (position.equalsIgnoreCase(F(AUTOCONNECT_JSON_VALUE_BEHIND)))
labelPosition = AC_Behind;
else if (position.equalsIgnoreCase(F(AUTOCONNECT_JSON_VALUE_INFRONT)))
labelPosition = AC_Infront;
else {
AC_DBG("Failed to load %s element, unknown label position:%s\n", name.c_str(), position.c_str());
return false;
}
}
return true;
}
return false;
}
/**
* Serialize AutoConnectCheckbox to JSON.
* @param json JSON object to be serialized.
*/
void AutoConnectCheckboxJson::serialize(JsonObject& json) {
_serialize(json);
json[F(AUTOCONNECT_JSON_KEY_TYPE)] = String(F(AUTOCONNECT_JSON_TYPE_ACCHECKBOX));
json[F(AUTOCONNECT_JSON_KEY_NAME)] = name;
json[F(AUTOCONNECT_JSON_KEY_VALUE)] = value;
json[F(AUTOCONNECT_JSON_KEY_LABEL)] = label;
json[F(AUTOCONNECT_JSON_KEY_CHECKED)] = checked;
if (labelPosition == AC_Infront)
json[F(AUTOCONNECT_JSON_KEY_LABELPOSITION)] = AUTOCONNECT_JSON_VALUE_INFRONT;
}
/**
* Returns JSON object size.
* @return An object size for JsonBuffer.
*/
size_t AutoConnectFileJson::getObjectSize(void) const {
size_t size = AutoConnectElementJson::getObjectSize() + JSON_OBJECT_SIZE(2);
size += sizeof(AUTOCONNECT_JSON_KEY_LABEL) + label.length() + 1 + sizeof(AUTOCONNECT_JSON_KEY_STORE) + sizeof(AUTOCONNECT_JSON_VALUE_EXTERNAL);
return size;
}
/**
* Load a file-select element attribute member from the JSON object.
* @param json JSON object with the definition of AutoConnectElement.
* @return true AutoConnectElement loaded
* @return false Type of AutoConnectElement is mismatched.
*/
bool AutoConnectFileJson::loadMember(const JsonObject& json) {
String type = json[F(AUTOCONNECT_JSON_KEY_TYPE)].as<String>();
if (type.equalsIgnoreCase(F(AUTOCONNECT_JSON_TYPE_ACFILE))) {
_setMember(json);
if (json.containsKey(F(AUTOCONNECT_JSON_KEY_LABEL)))
label = json[F(AUTOCONNECT_JSON_KEY_LABEL)].as<String>();
if (json.containsKey(F(AUTOCONNECT_JSON_KEY_STORE))) {
String media = json[F(AUTOCONNECT_JSON_KEY_STORE)].as<String>();
if (media.equalsIgnoreCase(F(AUTOCONNECT_JSON_VALUE_FS)))
store = AC_File_FS;
else if (media.equalsIgnoreCase(F(AUTOCONNECT_JSON_VALUE_SD)))
store = AC_File_SD;
else if (media.equalsIgnoreCase(F(AUTOCONNECT_JSON_VALUE_EXTERNAL)))
store = AC_File_Extern;
else {
AC_DBG("Failed to load %s element, unknown %s\n", name.c_str(), media.c_str());
return false;
}
}
return true;
}
return false;
}
/**
* Serialize AutoConnectFile to JSON.
* @param json JSON object to be serialized.
*/
void AutoConnectFileJson::serialize(JsonObject& json) {
_serialize(json);
json[F(AUTOCONNECT_JSON_KEY_TYPE)] = String(F(AUTOCONNECT_JSON_TYPE_ACFILE));
json[F(AUTOCONNECT_JSON_KEY_VALUE)] = value;
json[F(AUTOCONNECT_JSON_KEY_LABEL)] = label;
PGM_P media;
switch (store) {
case AC_File_SD:
media = PSTR(AUTOCONNECT_JSON_VALUE_SD);
break;
case AC_File_Extern:
media = PSTR(AUTOCONNECT_JSON_VALUE_EXTERNAL);
break;
case AC_File_FS:
default:
media = PSTR(AUTOCONNECT_JSON_VALUE_FS);
break;
}
json[F(AUTOCONNECT_JSON_KEY_STORE)] = String(FPSTR(media));
}
/**
* Returns JSON object size.
* @return An object size for JsonBuffer.
*/
size_t AutoConnectInputJson::getObjectSize(void) const {
size_t size = AutoConnectElementJson::getObjectSize() + JSON_OBJECT_SIZE(3);
size += sizeof(AUTOCONNECT_JSON_KEY_LABEL) + label.length() + 1 + sizeof(AUTOCONNECT_JSON_KEY_PATTERN) + pattern.length() + 1 + sizeof(AUTOCONNECT_JSON_KEY_PLACEHOLDER) + placeholder.length() + sizeof(AUTOCONNECT_JSON_KEY_APPLY) + sizeof(AUTOCONNECT_JSON_VALUE_PASSWORD) + 1;
return size;
}
/**
* Load a input-box element attribute member from the JSON object.
* @param json JSON object with the definition of AutoConnectElement.
* @return true AutoConnectElement loaded
* @return false Type of AutoConnectElement is mismatched.
*/
bool AutoConnectInputJson::loadMember(const JsonObject& json) {
String type = json[F(AUTOCONNECT_JSON_KEY_TYPE)].as<String>();
if (type.equalsIgnoreCase(F(AUTOCONNECT_JSON_TYPE_ACINPUT))) {
_setMember(json);
if (json.containsKey(F(AUTOCONNECT_JSON_KEY_LABEL)))
label = json[F(AUTOCONNECT_JSON_KEY_LABEL)].as<String>();
if (json.containsKey(F(AUTOCONNECT_JSON_KEY_PATTERN)))
pattern = json[F(AUTOCONNECT_JSON_KEY_PATTERN)].as<String>();
if (json.containsKey(F(AUTOCONNECT_JSON_KEY_PLACEHOLDER)))
placeholder = json[F(AUTOCONNECT_JSON_KEY_PLACEHOLDER)].as<String>();
if (json.containsKey(F(AUTOCONNECT_JSON_KEY_APPLY))) {
String applyType = json[F(AUTOCONNECT_JSON_KEY_APPLY)].as<String>();
if (applyType.equalsIgnoreCase(F(AUTOCONNECT_JSON_VALUE_TEXT)))
apply = AC_Input_Text;
else if (applyType.equalsIgnoreCase(F(AUTOCONNECT_JSON_VALUE_PASSWORD)))
apply = AC_Input_Password;
else if (applyType.equalsIgnoreCase(F(AUTOCONNECT_JSON_VALUE_NUMBER)))
apply = AC_Input_Number;
else {
AC_DBG("Failed to load %s element, unknown %s\n", name.c_str(), applyType.c_str());
return false;
}
}
return true;
}
return false;
}
/**
* Serialize AutoConnectInput to JSON.
* @param json JSON object to be serialized.
*/
void AutoConnectInputJson::serialize(JsonObject& json) {
_serialize(json);
json[F(AUTOCONNECT_JSON_KEY_TYPE)] = String(F(AUTOCONNECT_JSON_TYPE_ACINPUT));
json[F(AUTOCONNECT_JSON_KEY_VALUE)] = value;
json[F(AUTOCONNECT_JSON_KEY_LABEL)] = label;
json[F(AUTOCONNECT_JSON_KEY_PATTERN)] = pattern;
json[F(AUTOCONNECT_JSON_KEY_PLACEHOLDER)] = placeholder;
PGM_P applyType;
switch (apply) {
case AC_Input_Password:
applyType = PSTR(AUTOCONNECT_JSON_VALUE_PASSWORD);
break;
case AC_Input_Number:
applyType = PSTR(AUTOCONNECT_JSON_VALUE_NUMBER);
break;
case AC_Input_Text:
default:
applyType = PSTR(AUTOCONNECT_JSON_VALUE_TEXT);
break;
}
json[F(AUTOCONNECT_JSON_KEY_APPLY)] = String(FPSTR(applyType));
}
/**
* Returns JSON object size.
* @return An object size for JsonBuffer.
*/
size_t AutoConnectRadioJson::getObjectSize(void) const {
size_t size = AutoConnectElementJson::getObjectSize() + JSON_OBJECT_SIZE(3) + JSON_ARRAY_SIZE(_values.size());
size += sizeof(AUTOCONNECT_JSON_KEY_LABEL) + label.length() + 1 + sizeof(AUTOCONNECT_JSON_KEY_ARRANGE) + sizeof(AUTOCONNECT_JSON_VALUE_HORIZONTAL) + sizeof(AUTOCONNECT_JSON_KEY_CHECKED);
for (const String& _value : _values)
size += _value.length() + 1;
return size;
}
/**
* Load a radio-button element attribute member from the JSON object.
* @param json JSON object with the definition of AutoConnectElement.
* @return true AutoConnectElement loaded
* @return false Type of AutoConnectElement is mismatched.
*/
bool AutoConnectRadioJson::loadMember(const JsonObject& json) {
String type = json[F(AUTOCONNECT_JSON_KEY_TYPE)].as<String>();
if (type.equalsIgnoreCase(F(AUTOCONNECT_JSON_TYPE_ACRADIO))) {
_setMember(json);
if (json.containsKey(F(AUTOCONNECT_JSON_KEY_LABEL)))
label = json[F(AUTOCONNECT_JSON_KEY_LABEL)].as<String>();
if (json.containsKey(F(AUTOCONNECT_JSON_KEY_VALUE))) {
ArduinoJsonArray optionArray = json[AUTOCONNECT_JSON_KEY_VALUE];
empty(optionArray.size());
for (auto value : optionArray)
add(value.as<String>());
}
if (json.containsKey(F(AUTOCONNECT_JSON_KEY_CHECKED)))
checked = static_cast<uint8_t>(json[F(AUTOCONNECT_JSON_KEY_CHECKED)].as<int>());
if (json.containsKey(F(AUTOCONNECT_JSON_KEY_ARRANGE))) {
String arrange = json[F(AUTOCONNECT_JSON_KEY_ARRANGE)].as<String>();
if (arrange.equalsIgnoreCase(F(AUTOCONNECT_JSON_VALUE_VERTICAL)))
order = AC_Vertical;
else if (arrange.equalsIgnoreCase(F(AUTOCONNECT_JSON_VALUE_HORIZONTAL)))
order = AC_Horizontal;
else {
AC_DBG("Failed to load %s element, unknown %s\n", name.c_str(), arrange.c_str());
return false;
}
}
return true;
}
return false;
}
/**
* Serialize AutoConnectRadio to JSON.
* @param json JSON object to be serialized.
*/
void AutoConnectRadioJson::serialize(JsonObject& json) {
_serialize(json);
json[F(AUTOCONNECT_JSON_KEY_TYPE)] = String(F(AUTOCONNECT_JSON_TYPE_ACRADIO));
json[F(AUTOCONNECT_JSON_KEY_LABEL)] = label;
ArduinoJsonArray values = json.createNestedArray(F(AUTOCONNECT_JSON_KEY_VALUE));
for (const String& v : _values)
values.add(v);
PGM_P direction;
switch (order) {
case AC_Horizontal:
direction = PSTR(AUTOCONNECT_JSON_VALUE_HORIZONTAL);
break;
case AC_Vertical:
default:
direction = PSTR(AUTOCONNECT_JSON_VALUE_VERTICAL);
break;
}
json[F(AUTOCONNECT_JSON_KEY_ARRANGE)] = String(FPSTR(direction));
if (checked > 0)
json[F(AUTOCONNECT_JSON_KEY_CHECKED)] = checked;
}
/**
* Returns JSON object size.
* @return An object size for JsonBuffer.
*/
size_t AutoConnectSelectJson::getObjectSize(void) const {
size_t size = AutoConnectElementJson::getObjectSize() + JSON_OBJECT_SIZE(2) + JSON_ARRAY_SIZE(_options.size());
size += sizeof(AUTOCONNECT_JSON_KEY_LABEL) + label.length() + 1 + sizeof(AUTOCONNECT_JSON_KEY_SELECTED);
for (const String& _option : _options)
size += _option.length() + 1;
return size;
}
/**
* Load a select element attribute member from the JSON object.
* @param json JSON object with the definition of AutoConnectElement.
* @return true AutoConnectElement loaded
* @return false Type of AutoConnectElement is mismatched.
*/
bool AutoConnectSelectJson::loadMember(const JsonObject& json) {
String type = json[F(AUTOCONNECT_JSON_KEY_TYPE)].as<String>();
if (type.equalsIgnoreCase(F(AUTOCONNECT_JSON_TYPE_ACSELECT))) {
_setMember(json);
if (json.containsKey(F(AUTOCONNECT_JSON_KEY_LABEL)))
label = json[F(AUTOCONNECT_JSON_KEY_LABEL)].as<String>();
if (json.containsKey(F(AUTOCONNECT_JSON_KEY_OPTION))) {
ArduinoJsonArray optionArray = json[AUTOCONNECT_JSON_KEY_OPTION];
empty(optionArray.size());
for (auto value : optionArray)
add(value.as<String>());
}
if (json.containsKey(F(AUTOCONNECT_JSON_KEY_SELECTED)))
selected = static_cast<uint8_t>(json[F(AUTOCONNECT_JSON_KEY_SELECTED)].as<int>());
return true;
}
return false;
}
/**
* Serialize AutoConnectSelect to JSON.
* @param json JSON object to be serialized.
*/
void AutoConnectSelectJson::serialize(JsonObject& json) {
_serialize(json);
json[F(AUTOCONNECT_JSON_KEY_TYPE)] = String(F(AUTOCONNECT_JSON_TYPE_ACSELECT));
ArduinoJsonArray options = json.createNestedArray(F(AUTOCONNECT_JSON_KEY_OPTION));
for (String o : _options)
options.add(o);
json[F(AUTOCONNECT_JSON_KEY_LABEL)] = label;
if (selected > 0)
json[F(AUTOCONNECT_JSON_KEY_SELECTED)] = selected;
}
/**
* Load an element member value from the JSON object.
* @param json JSON object with the definition of AutoConnectStyle.
* @return true AutoConnectStyle loaded
* @return false Type of AutoConnectStyle is mismatched.
*/
bool AutoConnectStyleJson::loadMember(const JsonObject& json) {
String type = json[F(AUTOCONNECT_JSON_KEY_TYPE)].as<String>();
if (type.equalsIgnoreCase(F(AUTOCONNECT_JSON_TYPE_ACSTYLE))) {
_setMember(json);
return true;
}
return false;
}
/**
* Serialize AutoConnectStyle to JSON.
* @param json JSON object to be serialized.
*/
void AutoConnectStyleJson::serialize(JsonObject& json) {
_serialize(json);
json[F(AUTOCONNECT_JSON_KEY_TYPE)] = String(F(AUTOCONNECT_JSON_TYPE_ACSTYLE));
json[F(AUTOCONNECT_JSON_KEY_VALUE)] = value;
}
/**
* Returns JSON object size.
* @return An object size for JsonBuffer.
*/
size_t AutoConnectSubmitJson::getObjectSize(void) const {
size_t size = AutoConnectElementJson::getObjectSize();
size += sizeof(AUTOCONNECT_JSON_KEY_URI) + uri.length() + 1;
return size;
}
/**
* Load a submit element attribute member from the JSON object.
* @param json JSON object with the definition of AutoConnectElement.
* @return true AutoConnectElement loaded
* @return false Type of AutoConnectElement is mismatched.
*/
bool AutoConnectSubmitJson::loadMember(const JsonObject& json) {
String type = json[F(AUTOCONNECT_JSON_KEY_TYPE)].as<String>();
if (type.equalsIgnoreCase(F(AUTOCONNECT_JSON_TYPE_ACSUBMIT))) {
_setMember(json);
if (json.containsKey(F(AUTOCONNECT_JSON_KEY_URI)))
uri = json[F(AUTOCONNECT_JSON_KEY_URI)].as<String>();
return true;
}
return false;
}
/**
* Serialize AutoConnectSubmit to JSON.
* @param json JSON object to be serialized.
*/
void AutoConnectSubmitJson::serialize(JsonObject& json) {
_serialize(json);
json[F(AUTOCONNECT_JSON_KEY_TYPE)] = String(F(AUTOCONNECT_JSON_TYPE_ACSUBMIT));
json[F(AUTOCONNECT_JSON_KEY_VALUE)] = value;
json[F(AUTOCONNECT_JSON_KEY_URI)] = uri;
}
/**
* Returns JSON object size.
* @return An object size for JsonBuffer.
*/
size_t AutoConnectTextJson::getObjectSize(void) const {
size_t size = AutoConnectElementJson::getObjectSize() + JSON_OBJECT_SIZE(2);
size += sizeof(AUTOCONNECT_JSON_KEY_STYLE) + style.length() + 1 + sizeof(AUTOCONNECT_JSON_KEY_FORMAT) + format.length() + 1;
return size;
}
/**
* Load a text element attribute member from the JSON object.
* @param json JSON object with the definition of AutoConnectElement.
* @return true AutoConnectElement loaded
* @return false Type of AutoConnectElement is mismatched.
*/
bool AutoConnectTextJson::loadMember(const JsonObject& json) {
String type = json[F(AUTOCONNECT_JSON_KEY_TYPE)].as<String>();
if (type.equalsIgnoreCase(F(AUTOCONNECT_JSON_TYPE_ACTEXT))) {
_setMember(json);
if (json.containsKey(F(AUTOCONNECT_JSON_KEY_STYLE)))
style = json[F(AUTOCONNECT_JSON_KEY_STYLE)].as<String>();
if (json.containsKey(F(AUTOCONNECT_JSON_KEY_FORMAT)))
format = json[F(AUTOCONNECT_JSON_KEY_FORMAT)].as<String>();
return true;
}
return false;
}
/**
* Serialize AutoConnectText to JSON.
* @param json JSON object to be serialized.
*/
void AutoConnectTextJson::serialize(JsonObject& json) {
_serialize(json);
json[F(AUTOCONNECT_JSON_KEY_TYPE)] = String(F(AUTOCONNECT_JSON_TYPE_ACTEXT));
json[F(AUTOCONNECT_JSON_KEY_VALUE)] = value;
json[F(AUTOCONNECT_JSON_KEY_STYLE)] = style;
json[F(AUTOCONNECT_JSON_KEY_FORMAT)] = format;
}
#endif // _AUTOCONNECTELEMENTJSONIMPL_H_
/**
* Wrapping definition to ensure version compatibility of ArduinoJson.
* @file AutoConnectJsonDefs.h
* @author hieromon@gmail.com
* @version 1.0.0
* @date 2019-04-25
* @copyright MIT license.
*/
#ifndef _AUTOCONNECTJSONDEFS_H_
#define _AUTOCONNECTJSONDEFS_H_
#include <ArduinoJson.h>
/**
* Make the Json types and functions consistent with the ArduinoJson
* version. These declarations share the following type definitions:
* - Difference between reference and proxy of JsonObject and JsonArray.
* - Difference of check whether the parsing succeeded or not.
* - The print function name difference.
* - The buffer class difference.
* - When PSRAM present, enables the buffer allocation it with ESP32 and
* supported version.
*/
#if ARDUINOJSON_VERSION_MAJOR<=5
#define ArduinoJsonStaticBuffer StaticJsonBuffer
#define ARDUINOJSON_CREATEOBJECT(doc) doc.createObject()
#define ARDUINOJSON_CREATEARRAY(doc) doc.createArray()
#define ARDUINOJSON_PRETTYPRINT(doc, out) ({ size_t s = doc.prettyPrintTo(out); s; })
#define ARDUINOJSON_PRINT(doc, out) ({ size_t s = doc.printTo(out); s; })
using ArduinoJsonObject = JsonObject&;
using ArduinoJsonArray = JsonArray&;
using ArduinoJsonBuffer = DynamicJsonBuffer;
#define AUTOCONNECT_JSONBUFFER_PRIMITIVE_SIZE AUTOCONNECT_JSONBUFFER_SIZE
#else
#define ArduinoJsonStaticBuffer StaticJsonDocument
#define ARDUINOJSON_CREATEOBJECT(doc) doc.to<JsonObject>()
#define ARDUINOJSON_CREATEARRAY(doc) doc.to<JsonArray>()
#define ARDUINOJSON_PRETTYPRINT(doc, out) ({ size_t s = serializeJsonPretty(doc, out); s; })
#define ARDUINOJSON_PRINT(doc, out) ({ size_t s = serializeJson(doc, out); s; })
using ArduinoJsonObject = JsonObject;
using ArduinoJsonArray = JsonArray;
#if defined(BOARD_HAS_PSRAM) && ((ARDUINOJSON_VERSION_MAJOR==6 && ARDUINOJSON_VERSION_MINOR>=10) || ARDUINOJSON_VERSION_MAJOR>6)
// JsonDocument is assigned to PSRAM by ArduinoJson's custom allocator.
struct SpiRamAllocatorST {
void* allocate(size_t size) {
uint32_t caps;
if (psramFound())
caps = MALLOC_CAP_SPIRAM;
else {
caps = MALLOC_CAP_8BIT;
AC_DBG("PSRAM not found, JSON buffer allocates to the heap.\n");
}
return heap_caps_malloc(size, caps);
}
void deallocate(void* pointer) {
heap_caps_free(pointer);
}
};
#define AUTOCONNECT_JSONBUFFER_PRIMITIVE_SIZE AUTOCONNECT_JSONPSRAM_SIZE
using ArduinoJsonBuffer = BasicJsonDocument<SpiRamAllocatorST>;
#else
#define AUTOCONNECT_JSONBUFFER_PRIMITIVE_SIZE AUTOCONNECT_JSONDOCUMENT_SIZE
using ArduinoJsonBuffer = DynamicJsonDocument;
#endif
#endif
#endif // _AUTOCONNECTJSONDEFS_H_
/**
* AutoConnect proper menu label constant definition.
* @file AutoConnectLabels.h
* @author hieromon@gmail.com
* @version 1.2.0
* @date 2020-04-17
* @copyright MIT license.
*/
#ifndef _AUTOCONNECTLABELS_H_
#define _AUTOCONNECTLABELS_H_
/**
* The upper row of each of the following definition items is the
* default value. The lower row is an alternative label string as
* a sample and can be changed you wish.
*
* Notes:
* You can find some tokens for the PageBuilder if you parse
* AutoConnectPage.cpp. Some of the tokens are valid at the time
* the AutoConnect menu will generate. For example, the token
* `{{CUR_SSID}}` returns the SSID of the currently participating
* AP by AutoConnect::_token_CURRENT_SSID function.
* You can use these tokens to display dynamic menus showing the
* current situation, but you need to know the internal structure
* of AutoConnect in order to display the appropriate menu.
*/
// Menu item: Configure new AP
#ifndef AUTOCONNECT_MENULABEL_CONFIGNEW
#define AUTOCONNECT_MENULABEL_CONFIGNEW "Configure new AP"
//#define AUTOCONNECT_MENULABEL_CONFIGNEW "Establish a new AP"
#endif // !AUTOCONNECT_MENULABEL_CONFIGNEW
// Menu item: Open SSIDs
#ifndef AUTOCONNECT_MENULABEL_OPENSSIDS
#define AUTOCONNECT_MENULABEL_OPENSSIDS "Open SSIDs"
//#define AUTOCONNECT_MENULABEL_OPENSSIDS "Open credentials"
#endif // !AUTOCONNECT_MENULABEL_OPENSSIDS
// Menu item: Disconnect
#ifndef AUTOCONNECT_MENULABEL_DISCONNECT
#define AUTOCONNECT_MENULABEL_DISCONNECT "Disconnect"
//#define AUTOCONNECT_MENULABEL_DISCONNECT "Leave {{CUR_SSID}}"
#endif // !AUTOCONNECT_MENULABEL_DISCONNECT
// Menu item: Reset...
#ifndef AUTOCONNECT_MENULABEL_RESET
#define AUTOCONNECT_MENULABEL_RESET "Reset..."
//#define AUTOCONNECT_MENULABEL_RESET "Reboot..."
#endif // !AUTOCONNECT_MENULABEL_RESET
// Menu item: HOME
#ifndef AUTOCONNECT_MENULABEL_HOME
#define AUTOCONNECT_MENULABEL_HOME "HOME"
//#define AUTOCONNECT_MENULABEL_HOME "Main"
#endif // !AUTOCONNECT_MENULABEL_HOME
// Menu item: Update
#ifndef AUTOCONNECT_MENULABEL_UPDATE
#define AUTOCONNECT_MENULABEL_UPDATE "Update"
#endif // !AUTOCONNECT_MENULABEL_UPDATE
// Menu item: Device Info
#ifndef AUTOCONNECT_MENULABEL_DEVINFO
#define AUTOCONNECT_MENULABEL_DEVINFO "Device info"
#endif // !AUTOCONNECT_MENULABEL_DEVINFO
// Button label: RESET
#ifndef AUTOCONNECT_BUTTONLABEL_RESET
#define AUTOCONNECT_BUTTONLABEL_RESET "RESET"
//#define AUTOCONNECT_BUTTONLABEL_RESET "Reboot"
#endif // !AUTOCONNECT_BUTTONLABEL_RESET
// Button label: UPDATE
#ifndef AUTOCONNECT_BUTTONLABEL_UPDATE
#define AUTOCONNECT_BUTTONLABEL_UPDATE "UPDATE"
#endif // !AUTOCONNECT_BUTTONLABEL_UPDATE
// Page title: Page not found
#ifndef AUTOCONNECT_PAGETITLE_NOTFOUND
#define AUTOCONNECT_PAGETITLE_NOTFOUND "Page not found"
#endif // !AUTOCONNECT_PAGETITLE_NOTFOUND
// Page title: AutoConnect resetting
#ifndef AUTOCONNECT_PAGETITLE_RESETTING
#define AUTOCONNECT_PAGETITLE_RESETTING "AutoConnect resetting"
#endif // !AUTOCONNECT_PAGETITLE_RESETTING
// Page title: AutoConnect statistics
#ifndef AUTOCONNECT_PAGETITLE_STATISTICS
#define AUTOCONNECT_PAGETITLE_STATISTICS "AutoConnect statistics"
#endif // !AUTOCONNECT_PAGETITLE_STATISTICS
// Page statistics row: Established connection
#ifndef AUTOCONNECT_PAGESTATS_ESTABLISHEDCONNECTION
#define AUTOCONNECT_PAGESTATS_ESTABLISHEDCONNECTION "Established connection"
#endif // !AUTOCONNECT_PAGESTATS_ESTABLISHEDCONNECTION
// Page statistics row: Mode
#ifndef AUTOCONNECT_PAGESTATS_MODE
#define AUTOCONNECT_PAGESTATS_MODE "Mode"
#endif // !AUTOCONNECT_PAGESTATS_MODE
// Page statistics row: IP
#ifndef AUTOCONNECT_PAGESTATS_IP
#define AUTOCONNECT_PAGESTATS_IP "IP"
#endif // !AUTOCONNECT_PAGESTATS_IP
// Page statistics row: GW
#ifndef AUTOCONNECT_PAGESTATS_GATEWAY
#define AUTOCONNECT_PAGESTATS_GATEWAY "GW"
#endif // !AUTOCONNECT_PAGESTATS_GATEWAY
// Page statistics row: Subnet mask
#ifndef AUTOCONNECT_PAGESTATS_SUBNETMASK
#define AUTOCONNECT_PAGESTATS_SUBNETMASK "Subnet mask"
#endif // !AUTOCONNECT_PAGESTATS_SUBNETMASK
// Page statistics row: SoftAP IP
#ifndef AUTOCONNECT_PAGESTATS_SOFTAPIP
#define AUTOCONNECT_PAGESTATS_SOFTAPIP "SoftAP IP"
#endif // !AUTOCONNECT_PAGESTATS_SOFTAPIP
// Page statistics row: AP MAC
#ifndef AUTOCONNECT_PAGESTATS_APMAC
#define AUTOCONNECT_PAGESTATS_APMAC "AP MAC"
#endif // !AUTOCONNECT_PAGESTATS_APMAC
// Page statistics row: STA MAC
#ifndef AUTOCONNECT_PAGESTATS_STAMAC
#define AUTOCONNECT_PAGESTATS_STAMAC "STA MAC"
#endif // !AUTOCONNECT_PAGESTATS_STAMAC
// Page statistics row: Channel
#ifndef AUTOCONNECT_PAGESTATS_CHANNEL
#define AUTOCONNECT_PAGESTATS_CHANNEL "Channel"
#endif // !AUTOCONNECT_PAGESTATS_CHANNEL
// Page statistics row: dBm
#ifndef AUTOCONNECT_PAGESTATS_DBM
#define AUTOCONNECT_PAGESTATS_DBM "dBm"
#endif // !AUTOCONNECT_PAGESTATS_DBM
// Page statistics row: Chip ID
#ifndef AUTOCONNECT_PAGESTATS_CHIPID
#define AUTOCONNECT_PAGESTATS_CHIPID "Chip ID"
#endif // !AUTOCONNECT_PAGESTATS_CHIPID
// Page statistics row: CPU Freq.
#ifndef AUTOCONNECT_PAGESTATS_CPUFREQ
#define AUTOCONNECT_PAGESTATS_CPUFREQ "CPU Freq."
#endif // !AUTOCONNECT_PAGESTATS_CPUFREQ
// Page statistics row: Flash size
#ifndef AUTOCONNECT_PAGESTATS_FLASHSIZE
#define AUTOCONNECT_PAGESTATS_FLASHSIZE "Flash size"
#endif // !AUTOCONNECT_PAGESTATS_FLASHSIZE
// Page statistics row: Free memory
#ifndef AUTOCONNECT_PAGESTATS_FREEMEM
#define AUTOCONNECT_PAGESTATS_FREEMEM "Free memory"
#endif // !AUTOCONNECT_PAGESTATS_FREEMEM
// Page title: AutoConnect config
#ifndef AUTOCONNECT_PAGETITLE_CONFIG
#define AUTOCONNECT_PAGETITLE_CONFIG "AutoConnect config"
#endif // !AUTOCONNECT_PAGETITLE_CONFIG
// Page config text: Total:
#ifndef AUTOCONNECT_PAGECONFIG_TOTAL
#define AUTOCONNECT_PAGECONFIG_TOTAL "Total:"
#endif // !AUTOCONNECT_PAGECONFIG_TOTAL
// Page config text: Hidden:
#ifndef AUTOCONNECT_PAGECONFIG_HIDDEN
#define AUTOCONNECT_PAGECONFIG_HIDDEN "Hidden:"
#endif // !AUTOCONNECT_PAGECONFIG_HIDDEN
// Page config text: SSID
#ifndef AUTOCONNECT_PAGECONFIG_SSID
#define AUTOCONNECT_PAGECONFIG_SSID "SSID"
#endif // !AUTOCONNECT_PAGECONFIG_SSID
// Page config text: Passphrase
#ifndef AUTOCONNECT_PAGECONFIG_PASSPHRASE
#define AUTOCONNECT_PAGECONFIG_PASSPHRASE "Passphrase"
#endif // !AUTOCONNECT_PAGECONFIG_PASSPHRASE
// Page config text: Enable DHCP
#ifndef AUTOCONNECT_PAGECONFIG_ENABLEDHCP
#define AUTOCONNECT_PAGECONFIG_ENABLEDHCP "Enable DHCP"
#endif // !AUTOCONNECT_PAGECONFIG_ENABLEDHCP
// Page config text: Apply
#ifndef AUTOCONNECT_PAGECONFIG_APPLY
#define AUTOCONNECT_PAGECONFIG_APPLY "Apply"
#endif // !AUTOCONNECT_PAGECONFIG_APPLY
// Page title: AutoConnect credentials
#ifndef AUTOCONNECT_PAGETITLE_CREDENTIALS
#define AUTOCONNECT_PAGETITLE_CREDENTIALS "AutoConnect credentials"
#endif // !AUTOCONNECT_PAGETITLE_CREDENTIALS
// Page title: AutoConnect connecting
#ifndef AUTOCONNECT_PAGETITLE_CONNECTING
#define AUTOCONNECT_PAGETITLE_CONNECTING "AutoConnect connecting"
#endif // !AUTOCONNECT_PAGETITLE_CONNECTING
// Page title: AutoConnect connection failed
#ifndef AUTOCONNECT_PAGETITLE_CONNECTIONFAILED
#define AUTOCONNECT_PAGETITLE_CONNECTIONFAILED "AutoConnect connection failed"
#endif // !AUTOCONNECT_PAGETITLE_CONNECTIONFAILED
// Page connection failed: Connection Failed
#ifndef AUTOCONNECT_PAGECONNECTIONFAILED_CONNECTIONFAILED
#define AUTOCONNECT_PAGECONNECTIONFAILED_CONNECTIONFAILED "Connection Failed"
#endif // !AUTOCONNECT_PAGECONNECTIONFAILED_CONNECTIONFAILED
// Page title: AutoConnect disconnected
#ifndef AUTOCONNECT_PAGETITLE_DISCONNECTED
#define AUTOCONNECT_PAGETITLE_DISCONNECTED "AutoConnect disconnected"
#endif // !AUTOCONNECT_PAGETITLE_DISCONNECTED
// Text: No saved credentials.
#ifndef AUTOCONNECT_TEXT_NOSAVEDCREDENTIALS
#define AUTOCONNECT_TEXT_NOSAVEDCREDENTIALS "No saved credentials."
#endif // !AUTOCONNECT_TEXT_NOSAVEDCREDENTIALS
// Text: The update page caption
#ifndef AUTOCONNECT_TEXT_UPDATINGFIRMWARE
#define AUTOCONNECT_TEXT_UPDATINGFIRMWARE "Updating firmware"
#endif // !AUTOCONNECT_TEXT_UPDATINGFIRMWARE
// Text: The update page's file selection button label
#ifndef AUTOCONNECT_TEXT_SELECTFIRMWARE
#define AUTOCONNECT_TEXT_SELECTFIRMWARE "Select file: "
#endif // !AUTOCONNECT_TEXT_SELECTFIRMWARE
// Text: OTA success
#ifndef AUTOCONNECT_TEXT_OTASUCCESS
#define AUTOCONNECT_TEXT_OTASUCCESS "Successfully updated, rebooting..."
#endif // !AUTOCONNECT_TEXT_OTASUCCESS
// Text: OTA success
#ifndef AUTOCONNECT_TEXT_OTAUPLOADED
#define AUTOCONNECT_TEXT_OTAUPLOADED "Successfully uploaded"
#endif // !AUTOCONNECT_TEXT_OTAUPLOADED
// Text: OTA failure
#ifndef AUTOCONNECT_TEXT_OTAFAILURE
#define AUTOCONNECT_TEXT_OTAFAILURE "Failed to update: "
#endif // !AUTOCONNECT_TEXT_OTAFAILURE
// Text: Authenticaton failed
#ifndef AUTOCONNECT_TEXT_AUTHFAILED
#define AUTOCONNECT_TEXT_AUTHFAILED "Authenticaton failed"
#endif // !AUTOCONNECT_TEXT_AUTHFAILED
// Menu Text: Connecting
#ifndef AUTOCONNECT_MENUTEXT_CONNECTING
#define AUTOCONNECT_MENUTEXT_CONNECTING "Connecting"
#endif // !AUTOCONNECT_MENUTEXT_CONNECTING
// Menu Text: Disconnect
#ifndef AUTOCONNECT_MENUTEXT_DISCONNECT
#define AUTOCONNECT_MENUTEXT_DISCONNECT "Disconnect"
#endif // !AUTOCONNECT_MENUTEXT_DISCONNECT
// Menu Text: Failed
#ifndef AUTOCONNECT_MENUTEXT_FAILED
#define AUTOCONNECT_MENUTEXT_FAILED "Failed"
#endif // !AUTOCONNECT_MENUTEXT_FAILED
// Menu colors
// The following three color code items determine the color scheme of
// the menu. In addition to hexadecimal color values, you can specify
// 140 standard color names that apply to CSS.
// To be no sense of discomfort, select the same series of values for
// the background and the active item.
// Menu text foreground color
#ifndef AUTOCONNECT_MENUCOLOR_TEXT
#define AUTOCONNECT_MENUCOLOR_TEXT "#fff"
//#define AUTOCONNECT_MENUCOLOR_TEXT "#fffacd"
#endif // !AUTOCONNECT_MENUCOLOR_TEXT
// Menu background color
#ifndef AUTOCONNECT_MENUCOLOR_BACKGROUND
#define AUTOCONNECT_MENUCOLOR_BACKGROUND "#263238"
// #define AUTOCONNECT_MENUCOLOR_BACKGROUND "#696969"
#endif // !AUTOCONNECT_MENUCOLOR_BACKGROUND
// Background color with active menu items and mouse hover
#ifndef AUTOCONNECT_MENUCOLOR_ACTIVE
#define AUTOCONNECT_MENUCOLOR_ACTIVE "#37474f"
// #define AUTOCONNECT_MENUCOLOR_ACTIVE "#808080"
#endif
/**< Override the hardcoded strings contained in the AutoConnect pages. */
/**< e.g. for PlatformIO, you can add your environment in platformio.ini */
/**< along with AC_LABLES macro which specifies the user-defined label */
/**< constants as follows: */
/**< */
/**< build_flags = */
/**< -DAC_LABELS='"${PROJECT_SRC_DIR}/mylabels.h"' */
/**< */
/**< And places mylabels.h, it needs a structure of the define directive */
/**< provided per label string definition for the change your wants. */
/**< */
/**< #ifdef [ID YOU WANT TO CHANGE] */
/**< #undef [ID YOU WANT TO CHANGE] */
/**< #define [ID YOU WANT TO CHANGE] "NEW_STRING_FOR_THISONE" */
/**< #endif */
/**< */
/**< example:
#ifdef AUTOCONNECT_MENULABEL_CONFIGNEW
#undef AUTOCONNECT_MENULABEL_CONFIGNEW
#define AUTOCONNECT_MENULABEL_CONFIGNEW "NEW_STRING_FOR_THISONE"
#endif
*/
#ifdef AC_LABELS
#include AC_LABELS
#endif
#endif
/**
* AutoConnectOTA class implementation.
* @file AutoConnectOTA.cpp
* @author hieromon@gmail.com
* @version 1.2.2
* @date 2020-12-11
* @copyright MIT license.
*/
#include <functional>
#include <stdio.h>
#if defined(ARDUINO_ARCH_ESP8266)
#include <WiFiUdp.h>
#include <Updater.h>
#ifdef AUTOCONNECT_UPLOAD_ASFIRMWARE_USE_REGEXP
#include <regex.h>
#endif
#elif defined(ARDUINO_ARCH_ESP32)
extern "C" {
#include <esp_spiffs.h>
}
#include <Update.h>
#ifdef AUTOCONNECT_UPLOAD_ASFIRMWARE_USE_REGEXP
#include <regex>
#include <esp_pthread.h>
#include <thread>
#endif
#endif
#include <StreamString.h>
#include "AutoConnectOTA.h"
#include "AutoConnectOTAPage.h"
// Preserve a valid global Filesystem instance.
// It allows the interface to the actual filesystem for migration to LittleFS.
#ifdef AUTOCONNECT_USE_SPIFFS
namespace AutoConnectFS { SPIFFST &OTAFS = SPIFFS; };
#else
namespace AutoConnectFS { SPIFFST &OTAFS = LittleFS; };
#endif
/**
* A destructor. Release the OTA operation pages.
*/
AutoConnectOTA::~AutoConnectOTA() {
_auxUpdate.reset(nullptr);
_auxResult.reset(nullptr);
}
/**
* Request authentication with an OTA page access
* @param auth Authentication method
*/
void AutoConnectOTA::authentication(const AC_AUTH_t auth) {
if (_auxUpdate)
_auxUpdate->authentication(auth);
}
/**
* Attach the AutoConnectOTA to hosted AutoConnect which constitutes
* the update process. This function creates an OTA operation page as
* AutoConnectAux instance and allows it to receive binary updates.
* @param portal A reference of AutoConnect
*/
void AutoConnectOTA::attach(AutoConnect& portal) {
AutoConnectAux* updatePage;
updatePage = new AutoConnectAux(String(FPSTR(_pageUpdate.uri)), String(FPSTR(_pageUpdate.title)), _pageUpdate.menu);
_buildAux(updatePage, &_pageUpdate, lengthOf(_elmUpdate));
_auxUpdate.reset(updatePage);
updatePage = new AutoConnectAux(String(FPSTR(_pageResult.uri)), String(FPSTR(_pageResult.title)), _pageResult.menu);
_buildAux(updatePage, &_pageResult, lengthOf(_elmResult));
_auxResult.reset(updatePage);
_auxResult->on(std::bind(&AutoConnectOTA::_updated, this, std::placeholders::_1, std::placeholders::_2));
_auxResult->onUpload<AutoConnectOTA>(*this);
portal.join(*_auxUpdate.get());
portal.join(*_auxResult.get());
}
/**
* Create the update operation pages using a predefined page structure
* with two structures as ACPage_t and ACElementProp_t which describe
* for AutoConnectAux configuration.
* This function receives instantiated AutoConnectAux, instantiates
* defined AutoConnectElements by ACPage_t, and configures it into
* received AutoConnectAux.
* @param aux An instantiated AutoConnectAux that will configure according to ACPage_t.
* @param page Pre-defined ACPage_t
* @param elementNum Number of AutoConnectElements to configure.
*/
void AutoConnectOTA::_buildAux(AutoConnectAux* aux, const AutoConnectOTA::ACPage_t* page, const size_t elementNum) {
for (size_t n = 0; n < elementNum; n++) {
if (page->element[n].type == AC_Button) {
AutoConnectButton* element = new AutoConnectButton;
element->name = String(FPSTR(page->element[n].name));
if (page->element[n].value)
element->value = String(FPSTR(page->element[n].value));
if (page->element[n].peculiar)
element->action = String(FPSTR(page->element[n].peculiar));
aux->add(reinterpret_cast<AutoConnectElement&>(*element));
}
else if (page->element[n].type == AC_Element) {
AutoConnectElement* element = new AutoConnectElement;
element->name = String(FPSTR(page->element[n].name));
if (page->element[n].value)
element->value = String(FPSTR(page->element[n].value));
aux->add(reinterpret_cast<AutoConnectElement&>(*element));
}
else if (page->element[n].type == AC_File) {
AutoConnectFile* element = new AutoConnectFile;
element->name = String(FPSTR(page->element[n].name));
element->label = String(FPSTR(page->element[n].peculiar));
element->store = ACFile_t::AC_File_Extern;
aux->add(reinterpret_cast<AutoConnectElement&>(*element));
}
else if (page->element[n].type == AC_Style) {
AutoConnectStyle* element = new AutoConnectStyle;
element->name = String(FPSTR(page->element[n].name));
if (page->element[n].value)
element->value = String(FPSTR(page->element[n].value));
aux->add(reinterpret_cast<AutoConnectElement&>(*element));
}
else if (page->element[n].type == AC_Text) {
AutoConnectText* element = new AutoConnectText;
element->name = String(FPSTR(page->element[n].name));
if (page->element[n].value)
element->value = String(FPSTR(page->element[n].value));
if (page->element[n].peculiar)
element->style = String(FPSTR(page->element[n].peculiar));
aux->add(reinterpret_cast<AutoConnectText&>(*element));
}
}
}
/**
* Check the space needed for the update
* This function overrides AutoConnectUploadHandler::_open.
* @param filename An updater bin file name
* @param mode File access mode, but it is not be used.
* @return true Ready for update
* @return false Not enough FLASH space to update.
*/
bool AutoConnectOTA::_open(const char* filename, const char* mode) {
AC_UNUSED(mode);
bool bc;
_binName = String(filename + sizeof('/'));
#ifdef ARDUINO_ARCH_ESP8266
WiFiUDP::stopAll();
#endif
// Uploading filename extension used as the criterion for uploading
// destination is fixed as the default that is assigned by
// AUTOCONNECT_UPLOAD_ASFIRMWARE.
// AUTOCONNECT_UPLOAD_ASFIRMWARE_USE_REGEXP allows you to use regular
// expressions with the extension to determine the uploading destination.
#ifndef AUTOCONNECT_UPLOAD_ASFIRMWARE_USE_REGEXP
String asBin(F(AUTOCONNECT_UPLOAD_ASFIRMWARE));
_binName.toLowerCase();
asBin.toLowerCase();
_dest = _binName.endsWith(asBin) == true ? OTA_DEST_FIRM : OTA_DEST_FILE;
#else
// It is recommended to avoid introducing regular expressions in the
// judgment to make the file a firmware upload as much as possible.
#if defined(ARDUINO_ARCH_ESP8266)
regex_t preg;
if (!regcomp(&preg, AUTOCONNECT_UPLOAD_ASFIRMWARE, REG_ICASE | REG_EXTENDED | REG_NOSUB)) {
regmatch_t p_match[1];
_dest = !regexec(&preg, _binName.c_str(), 1, p_match, 0) ? OTA_DEST_FIRM : OTA_DEST_FILE;
regfree(&preg);
}
else {
_setError("regex failed:" AUTOCONNECT_UPLOAD_ASFIRMWARE);
return false;
}
#elif defined(ARDUINO_ARCH_ESP32)
// Especially on the ESP32 platform, std::regex fattens the elf, also
// requires many stack area. It easily eats up the FreeRTOS stack
// (8KB by default) allocated to an Arduino loop thread.
// To avoid this, AutoConnectOTA spawns a thread exclusively for regular
// expression evaluation just. But it comes with a heavy price and no meaning.
esp_pthread_cfg_t t_cfg;
if (esp_pthread_get_cfg(&t_cfg) != ESP_OK) {
// t_cfg = esp_pthread_get_default_config();
}
t_cfg.stack_size = 1024 * 10;
esp_pthread_set_cfg(&t_cfg);
std::thread t_regex([&]() {
const std::regex re(std::string(AUTOCONNECT_UPLOAD_ASFIRMWARE), std::regex_constants::icase | std::regex_constants::extended | std::regex_constants::nosubs);
_dest = std::regex_match(_binName.c_str(), re) ? OTA_DEST_FIRM : OTA_DEST_FILE;
});
t_regex.join();
#endif
#endif
AC_DBG("OTA:%s %s\n", _dest == OTA_DEST_FIRM ? "app" : "fs", _binName.c_str());
if (_dest == OTA_DEST_FIRM) {
uint32_t maxSketchSpace = (ESP.getFreeSketchSpace() - 0x1000) & 0xFFFFF000;
// It only supports FLASH as a sketch area for updating.
bc = Update.begin(maxSketchSpace, U_FLASH);
}
else {
// Allocate fs::FS according to the pinned Filesystem.
_fs = &AutoConnectFS::OTAFS;
#if defined(ARDUINO_ARCH_ESP8266)
FSInfo info;
if (_fs->info(info))
bc = true;
else
bc = _fs->begin();
#elif defined(ARDUINO_ARCH_ESP32)
if (esp_spiffs_mounted(NULL))
bc = true;
else
bc = _fs->begin(true);
#endif
if (bc) {
_file = _fs->open(filename, "w");
if (!_file) {
bc = false;
_setError((_binName + String(F(" open failed"))).c_str());
}
}
else
_setError("FS mount failed");
}
if (bc) {
if (_tickerPort != -1)
pinMode(static_cast<uint8_t>(_tickerPort), OUTPUT);
_status = OTA_START;
AC_DBG("%s up%s start\n", filename, _dest == OTA_DEST_FIRM ? "dating" : "loading");
}
return bc;
}
/**
* Writes received updater to the flash.
* This function overrides AutoConnectUploadHandler::_write.
* @param buf Buffer address where received update file was stored.
* @param size Size to be written.
* @return the amount written
*/
size_t AutoConnectOTA::_write(const uint8_t *buf, const size_t size) {
size_t wsz = 0;
if (_tickerPort != -1)
digitalWrite(_tickerPort, digitalRead(_tickerPort) ^ 0x01);
if (!_err.length()) {
_status = OTA_PROGRESS;
if (_dest == OTA_DEST_FIRM) {
wsz = Update.write(const_cast<uint8_t*>(buf), size);
if (wsz != size)
_setError();
}
else {
wsz = _file.write(buf, size);
if (wsz != size)
_setError("Incomplete writing");
}
}
return wsz;
}
/**
* All bytes are written, this call writes the config to reboot.
* If there is an error this will clear everything.
* This function overrides AutoConnectUploadHandler::_close.
* @param status Updater binary upload completion status.
*/
void AutoConnectOTA::_close(const HTTPUploadStatus status) {
AC_DBG("OTA up%s ", _dest == OTA_DEST_FIRM ? "date" : "load");
if (!_err.length()) {
if (status == UPLOAD_FILE_END) {
bool ec = true;
if (_dest == OTA_DEST_FIRM)
ec = Update.end(true);
else {
_file.close();
}
if (ec) {
_status = OTA_SUCCESS;
AC_DBG_DUMB("succeeds");
}
else {
_setError();
AC_DBG_DUMB("flash failed");
Update.end(false);
}
}
else {
_setError("Aborted");
AC_DBG_DUMB("aborted");
}
}
AC_DBG_DUMB(". %s\n", _err.c_str());
if (_tickerPort != -1)
digitalWrite(_tickerPort, !_tickerOn);
}
/**
* Callback of the update operation page as AutoConnectAux.
* Reflect the flash result of Update class to the page.
* @param result Upload post-process page
* @param args Unused
* @return Empty string
*/
String AutoConnectOTA::_updated(AutoConnectAux& result, PageArgument& args) {
AC_UNUSED(args);
PGM_P stColor;
String st;
// Build an updating result caption.
// Change the color of the bin name depending on the result of the update.
if (_status == OTA_SUCCESS) {
st = _dest == OTA_DEST_FIRM ? String(F(AUTOCONNECT_TEXT_OTASUCCESS)) : String(F(AUTOCONNECT_TEXT_OTAUPLOADED));
stColor = PSTR("3d7e9a");
// Notify to the handleClient of loop() thread that it can reboot.
_status = OTA_RIP;
}
else {
st = String(F(AUTOCONNECT_TEXT_OTAFAILURE)) + _err;
stColor = PSTR("e66157");
}
result["bin"].as<AutoConnectText>().value = _binName;
result["bin"].as<AutoConnectText>().style += String(stColor);
result["result"].as<AutoConnectText>().value = st;
// AutoConnectOTA result page generates a transition after reboot
// according to the error code from the Update class. By setting the
// error code of the Update class into the rc element, this page will
// automatically GET the homepage of the updated sketch.
// When for firmware updating, it's a numeric and will induce redirect
// to HOME according to the module restarts.
// When for file uploading, it's not numeric and has the effect of
// staying on the upload page.
result["rc"].value = _dest == OTA_DEST_FIRM ? String(Update.getError()) : String('L');
return String("");
}
/**
* Save the last error
*/
void AutoConnectOTA::_setError(void) {
StreamString eStr;
Update.printError(eStr);
_err = String(eStr.c_str());
_status = OTA_FAIL;
}
void AutoConnectOTA::_setError(const char* err) {
_err = String(err);
_status = OTA_FAIL;
}
/**
* Declaration of AutoConnectOTA class.
* The AutoConnecOTA class is a class for web updating a Sketch binary
* via OTA and implements with an AutoConnectAux page handler that
* inherits from AutoConnectUploadHandler.
* By overriding the _write function of AutoConnectUploadHandler to
* write the executable binary using the Update class, it can update
* the module firmware in synchronization with the upload of the sketch
* binary file.
* @file AutoConnectOTA.h
* @author hieromon@gmail.com
* @version 1.2.2
* @date 2020-12-11
* @copyright MIT license.
*/
#ifndef _AUTOCONNECTOTA_H_
#define _AUTOCONNECTOTA_H_
#include <memory>
#include "AutoConnect.h"
#include "AutoConnectUpload.h"
#include <FS.h>
#if defined(ARDUINO_ARCH_ESP8266)
#ifndef AUTOCONNECT_USE_SPIFFS
#include <LittleFS.h>
#endif
typedef fs::FS SPIFFST;
#elif defined(ARDUINO_ARCH_ESP32)
#include <SPIFFS.h>
typedef fs::SPIFFSFS SPIFFST;
#endif
class AutoConnectOTA : public AutoConnectUploadHandler {
public:
// Updating process status
typedef enum {
OTA_IDLE, /**< Update process has not started */
OTA_START, /**< Update process has started */
OTA_PROGRESS, /**< Update process in progress */
OTA_SUCCESS, /**< A binary updater has uploaded fine */
OTA_RIP, /**< Ready for module restart */
OTA_FAIL /**< Failed to save binary updater by Update class */
} AC_OTAStatus_t;
// The treating destination of OTA transferred data
typedef enum {
OTA_DEST_FILE, /**< To be upload the file */
OTA_DEST_FIRM /**< To update the firmware */
} AC_OTADest_t;
AutoConnectOTA() : _dest(OTA_DEST_FIRM), _status(OTA_IDLE), _tickerPort(-1), _tickerOn(LOW) {};
~AutoConnectOTA();
void attach(AutoConnect& portal); /**< Attach itself to AutoConnect */
void authentication(const AC_AUTH_t auth); /**< Set certain page authentication */
String error(void) const { return _err; } /**< Returns current error string */
void menu(const bool post) { _auxUpdate->menu(post); } /**< Enabel or disable arranging a created AutoConnectOTA page in the menu. */
void reset(void) { _status = OTA_IDLE; } /**< Reset the status */
AC_OTAStatus_t status(void) const { return _status; } /**< Return a current error status of the Update class */
AC_OTADest_t dest(void) const { return _dest; } /**< Return a current uploading destination */
void setTicker(int8_t pin, uint8_t on) { _tickerPort = pin, _tickerOn = on; } /**< Set ticker LED port */
protected:
// Attribute definition of the element to be placed on the update page.
typedef struct {
const ACElement_t type;
const char* name; /**< Name to assign to AutoConnectElement */
const char* value; /**< Value owned by an element */
const char* peculiar; /**< Specific ornamentation for the element */
} ACElementProp_t;
// Attributes to treat included update pages as AutoConnectAux.
typedef struct {
const char* uri; /**< URI for the page */
const char* title; /**< Menu title of update page */
const bool menu; /**< Whether to display in menu */
const ACElementProp_t* element;
} ACPage_t;
template <typename T, size_t N> constexpr size_t lengthOf(T (&)[N]) noexcept {
return N;
}
void _buildAux(AutoConnectAux* aux, const AutoConnectOTA::ACPage_t* page, const size_t elementNum);
bool _open(const char* filename, const char* mode) override;
size_t _write(const uint8_t *buf, const size_t size) override;
void _close(const HTTPUploadStatus status) override;
String _updated(AutoConnectAux& result, PageArgument& args);
std::unique_ptr<AutoConnectAux> _auxUpdate; /**< An update operation page */
std::unique_ptr<AutoConnectAux> _auxResult; /**< An update result page */
private:
void _setError(void);
void _setError(const char* err);
AC_OTADest_t _dest; /**< Destination of OTA transferred data */
AC_OTAStatus_t _status; /**< Status for update progress */
int8_t _tickerPort; /**< GPIO for flicker */
uint8_t _tickerOn; /**< A signal for flicker turn on */
String _binName; /**< An updater file name */
String _err; /**< Occurred error stamp */
SPIFFST* _fs; /**< Filesystem for the native file uploading */
fs::File _file; /**< File handler for the native file uploading */
static const ACPage_t _pageUpdate PROGMEM;
static const ACElementProp_t _elmUpdate[] PROGMEM;
static const ACPage_t _pageResult PROGMEM;
static const ACElementProp_t _elmResult[] PROGMEM;
};
#endif // !_AUTOCONNECTOTA_H_
/**
* Define pages to operate updates using the AutoConnectUpdate class.
* @file AutoConnectOTAPage.h
* @author hieromon@gmail.com
* @version 1.1.5
* @date 2020-04-09
* @copyright MIT license.
*/
#ifndef _AUTOCONNECTOTAPAGE_H_
#define _AUTOCONNECTOTAPAGE_H_
const AutoConnectOTA::ACElementProp_t AutoConnectOTA::_elmUpdate[] PROGMEM = {
{ AC_Style, "s_rc", ".s_rc{display:none}", nullptr },
{ AC_Text, "cap", "<h3>" AUTOCONNECT_TEXT_UPDATINGFIRMWARE "<h3>", nullptr },
{ AC_File, "bin", nullptr, AUTOCONNECT_TEXT_SELECTFIRMWARE },
{ AC_Button, "update", AUTOCONNECT_BUTTONLABEL_UPDATE, "_upd(this,'bin','" AUTOCONNECT_URI_UPDATE_ACT "')" },
{ AC_Element, "js", "<script type=\"text/javascript\">function _upd(e,t,n){var r=document.getElementById(t);if(r.files.length>0){par=e.parentNode,par.removeChild(e),pb=document.createElement(\"progress\"),pb.setAttribute(\"id\",\"pb\"),pb.setAttribute(\"style\",\"margin-top:1.0em\"),pb.setAttribute(\"value\",\"0\"),pb.setAttribute(\"max\",r.files[0].size),par.appendChild(pb);var p=new FormData(_bu(n)),o=new XMLHttpRequest;o.upload.addEventListener(\"progress\",function(e){pb.setAttribute(\"value\",e.loaded)},!1),o.addEventListener(\"load\",function(){document.body.innerHTML=o.response.body.innerHTML,\"0\"==document.getElementById(\"rc\").innerText&&setTimeout(function(){location.href=\"" AUTOCONNECT_HOMEURI "\"}," AUTOCONNECT_STRING_DEPLOY(AUTOCONNECT_UPDATE_WAITFORREBOOT) ")},!1),o.open(\"POST\",n),o.responseType=\"document\",o.send(p)}}var par,pb;</script>", nullptr }
};
// The definition of the OTA update operation page, which will be located to AUTOCONNECT_URI_UPDATE.
const AutoConnectOTA::ACPage_t AutoConnectOTA::_pageUpdate PROGMEM = {
AUTOCONNECT_URI_UPDATE, AUTOCONNECT_MENULABEL_UPDATE, true, AutoConnectOTA::_elmUpdate
};
const AutoConnectOTA::ACElementProp_t AutoConnectOTA::_elmResult[] PROGMEM = {
{ AC_Text, "bin", nullptr, "margin-bottom:0.5em;font-size:1.2em;font-weight:bold;color:#" },
{ AC_Text, "result", nullptr, nullptr },
{ AC_Element, "dvo", "<div class=\"s_rc\" id=\"rc\">", nullptr },
{ AC_Element, "rc", "", nullptr },
{ AC_Element, "dvc", "</div>", nullptr }
};
// The definition of the OTA update result display page.
// This page is assigned to AUTOCONNECT_URI_UPDATE_ACT, but the actual
// HTML document is dynamically rewritten on AUTOCONNECT_URI_UPDATE page
// by the JavaScript function included in the _pageUpdate AutoConnectAux.
const AutoConnectOTA::ACPage_t AutoConnectOTA::_pageResult PROGMEM = {
AUTOCONNECT_URI_UPDATE_ACT, AUTOCONNECT_MENULABEL_UPDATE, false, AutoConnectOTA::_elmResult
};
#endif // !_AUTOCONNECTOTAPAGE_H_
/**
* AutoConnect portal site web page implementation.
* @file AutoConnectPage.cpp
* @author hieromon@gmail.com
* @version 1.2.0
* @date 2020-04-19
* @copyright MIT license.
*/
#if defined(ARDUINO_ARCH_ESP8266)
#include <ESP8266WiFi.h>
extern "C" {
#include <user_interface.h>
}
#elif defined(ARDUINO_ARCH_ESP32)
#include <esp_spi_flash.h>
#include <WiFi.h>
#define ENC_TYPE_NONE WIFI_AUTH_OPEN
#endif
#include "AutoConnect.h"
#include "AutoConnectPage.h"
#include "AutoConnectCredential.h"
/**< Basic CSS common to all pages */
const char AutoConnect::_CSS_BASE[] PROGMEM = {
"html{"
"font-family:Helvetica,Arial,sans-serif;"
"font-size:16px;"
"-ms-text-size-adjust:100%;"
"-webkit-text-size-adjust:100%;"
"-moz-osx-font-smoothing:grayscale;"
"-webkit-font-smoothing:antialiased"
"}"
"body{"
"margin:0;"
"padding:0"
"}"
".base-panel{"
"margin:0 22px 0 22px"
"}"
".base-panel * label :not(.bins){"
"display:inline-block;"
"width:3.0em;"
"text-align:right"
"}"
".base-panel * .slist{"
"width:auto;"
"font-size:0.9em;"
"margin-left:10px;"
"text-align:left"
"}"
"input{"
"-moz-appearance:none;"
"-webkit-appearance:none;"
"font-size:0.9em;"
"margin:8px 0 auto"
"}"
".lap{"
"visibility:collapse"
"}"
".lap:target{"
"visibility:visible"
"}"
".lap:target .overlap{"
"opacity:0.7;"
"transition:0.3s"
"}"
".lap:target .modal_button{"
"opacity:1;"
"transition:0.3s"
"}"
".overlap{"
"top:0;"
"left:0;"
"width:100%;"
"height:100%;"
"position:fixed;"
"opacity:0;"
"background:#000;"
"z-index:1000"
"}"
".modal_button{"
"border-radius:13px;"
"background:#660033;"
"color:#ffffcc;"
"padding:20px 30px;"
"text-align:center;"
"text-decoration:none;"
"letter-spacing:1px;"
"font-weight:bold;"
"display:inline-block;"
"top:40%;"
"left:40%;"
"width:20%;"
"position:fixed;"
"opacity:0;"
"z-index:1001"
"}"
};
/**< non-marked list for UL */
const char AutoConnect::_CSS_UL[] PROGMEM = {
".noorder,.exp{"
"padding:0;"
"list-style:none;"
"display:table"
"}"
".noorder li,.exp{"
"display:table-row-group"
"}"
".noorder li label, .exp li *{"
"display:table-cell;"
"width:auto;"
"text-align:right;"
"padding:10px 0.5em"
"}"
".noorder input[type=\"checkbox\"]{"
"-moz-appearance:checkbox;"
"-webkit-appearance:checkbox"
"}"
".noorder input[type=\"radio\"]{"
"margin-right:0.5em;"
"-moz-appearance:radio;"
"-webkit-appearance:radio"
"}"
".noorder input[type=\"text\"]{"
"width:auto"
"}"
".noorder input[type=\"text\"]:invalid{"
"background:#fce4d6"
"}"
};
/**< Image icon for inline expansion, the lock mark. */
const char AutoConnect::_CSS_ICON_LOCK[] PROGMEM = {
".img-lock{"
"width:22px;"
"height:22px;"
"margin-top:14px;"
"float:right;"
"background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAACXBIWXMAAAsTAAALEwEAmpwYAAAB1ElEQVRIibWVu0scURTGf3d2drBQFAWbbRQVCwuVLIZdi2gnWIiF/4GtKyuJGAJh8mgTcU0T8T8ICC6kiIVu44gvtFEQQWwsbExQJGHXmZtiZsOyzCN3Vz+4cDjfvec7j7l3QAF95onRZ54YKmdE1IbnS0c9mnAyAjkBxDy3LRHrjtRyu7OD52HntTAyvbw/HxP2hkCearrRb2WSCSuTTGi60S+QpzFhbwznDl/VVMHw0sF7hEjFbW2qkB38lfp8nNDipWcATil+uDM3cDWyeNRSijnfkHJnezb5Vkkgvbg3IOXD2e1ts93S+icnkZOAVaalZK3YQMa4L+pC6L1WduhYSeCf0PLBdxzOjZ93Lwvm6APAiLmlF1ubPiHotmaS41ExQjH0ZbfNM1NAFpgD0lVcICIrANqAVaAd+AFIYAy4BqaBG+Wsq5AH3vgk8xpYrzf4KLAZwhe8PYEIvQe4vc6H8Hnc2dQs0AFchvAXQGdEDF8s4A5TZS34BQqqQNaS1WMI3KD4WUbNoBJfce9CO7BSr4BfBe8A21vmUwh0VdjdTyHwscL+UK+AHxoD7FDoAX6/Cnpxn4ay/egCjcCL/w1chkqLakLQ/6ABhT57uAd+Vzv/Ara3iY6fK4WxAAAAAElFTkSuQmCC) no-repeat"
"}"
};
/**< INPUT button and submit style */
const char AutoConnect::_CSS_INPUT_BUTTON[] PROGMEM = {
"input[type=\"button\"],input[type=\"submit\"],button[type=\"submit\"],button[type=\"button\"]{"
"padding:8px 0.5em;"
"font-weight:bold;"
"letter-spacing:0.8px;"
"color:#fff;"
"border:1px solid;"
"border-radius:2px;"
"margin-top:12px"
"}"
"input[type=\"button\"],button[type=\"button\"]{"
"background-color:#1b5e20;"
"border-color:#1b5e20;"
"width:15em"
"}"
".aux-page input[type=\"button\"],button[type=\"button\"]{"
"font-weight:normal;"
"padding:8px 14px;"
"margin:12px;"
"width:auto"
"}"
"input#sb[type=\"submit\"]{"
"width:15em"
"}"
"input[type=\"submit\"],button[type=\"submit\"]{"
"padding:8px 30px;"
"background-color:#006064;"
"border-color:#006064"
"}"
"input[type=\"button\"],input[type=\"submit\"],button[type=\"submit\"]:focus,"
"input[type=\"button\"],input[type=\"submit\"],button[type=\"submit\"]:active{"
"outline:none;"
"text-decoration:none"
"}"
};
/**< INPUT text style */
const char AutoConnect::_CSS_INPUT_TEXT[] PROGMEM = {
"input[type=\"text\"],input[type=\"password\"], .aux-page select{"
"background-color:#fff;"
"border:1px solid #ccc;"
"border-radius:2px;"
"color:#444;"
"margin:8px 0 8px 0;"
"padding:10px"
"}"
"input[type=\"text\"],input[type=\"password\"]{"
"font-weight:300;"
"width:auto;"
"-webkit-transition:all 0.20s ease-in;"
"-moz-transition:all 0.20s ease-in;"
"-o-transition:all 0.20s ease-in;"
"-ms-transition:all 0.20s ease-in;"
"transition:all 0.20s ease-in"
"}"
"input[type=\"text\"]:focus,input[type=\"password\"]:focus{"
"outline:none;"
"border-color:#5C9DED;"
"box-shadow:0 0 3px #4B8CDC"
"}"
"input.error,input.error:focus{"
"border-color:#ED5564;"
"color:#D9434E;"
"box-shadow:0 0 3px #D9434E"
"}"
"input:disabled{"
"opacity:0.6;"
"background-color:#f7f7f7"
"}"
"input:disabled:hover{"
"cursor:not-allowed"
"}"
"input.error::-webkit-input-placeholder,"
"input.error::-moz-placeholder,"
"input.error::-ms-input-placeholder{"
"color:#D9434E"
"}"
".aux-page label{"
"display:inline;"
"padding:10px 0.5em;"
"}"
};
/**< TABLE style */
const char AutoConnect::_CSS_TABLE[] PROGMEM = {
"table{"
"border-collapse:collapse;"
"border-spacing:0;"
"border:1px solid #ddd;"
"color:#444;"
"background-color:#fff;"
"margin-bottom:20px"
"}"
"table.info,"
"table.info>tfoot,"
"table.info>thead{"
"width:100%;"
"border-color:#5C9DED"
"}"
"table.info>thead{"
"background-color:#5C9DED"
"}"
"table.info>thead>tr>th{"
"color:#fff"
"}"
"td,"
"th{"
"padding:10px 22px"
"}"
"thead{"
"background-color:#f3f3f3;"
"border-bottom:1px solid #ddd"
"}"
"thead>tr>th{"
"font-weight:400;"
"text-align:left"
"}"
"tfoot{"
"border-top:1px solid #ddd"
"}"
"tbody,"
"tbody>tr:nth-child(odd){"
"background-color:#fff"
"}"
"tbody>tr>td,"
"tfoot>tr>td{"
"font-weight:300;"
"font-size:.88em"
"}"
"tbody>tr:nth-child(even){"
"background-color:#f7f7f7"
"}"
"table.info tbody>tr:nth-child(even){"
"background-color:#EFF5FD"
"}"
};
/**< SVG animation for spinner */
const char AutoConnect::_CSS_SPINNER[] PROGMEM = {
".spinner{"
"width:40px;"
"height:40px;"
"position:relative;"
"margin:100px auto"
"}"
".dbl-bounce1, .dbl-bounce2{"
"width:100%;"
"height:100%;"
"border-radius:50%;"
"background-color:#a3cccc;"
"opacity:0.6;"
"position:absolute;"
"top:0;"
"left:0;"
"-webkit-animation:sk-bounce 2.0s infinite ease-in-out;"
"animation:sk-bounce 2.0s infinite ease-in-out"
"}"
".dbl-bounce2{"
"-webkit-animation-delay:-1.0s;"
"animation-delay:-1.0s"
"}"
"@-webkit-keyframes sk-bounce{"
"0%, 100%{-webkit-transform:scale(0.0)}"
"50%{-webkit-transform:scale(1.0)}"
"}"
"@keyframes sk-bounce{"
"0%,100%{"
"transform:scale(0.0);"
"-webkit-transform:scale(0.0);"
"}50%{"
"transform:scale(1.0);"
"-webkit-transform:scale(1.0);"
"}"
"}"
};
/**< Common menu bar. This style quotes LuxBar. */
/**< balzss/luxbar is licensed under the MIT License https://github.com/balzss/luxbar */
const char AutoConnect::_CSS_LUXBAR[] PROGMEM = {
".lb-fixed{"
"width:100%;"
"position:fixed;"
"top:0;"
"left:0;"
"z-index:1000;"
"box-shadow:0 1px 3px rgba(0, 0, 0, 0.12), 0 1px 2px rgba(0, 0, 0, 0.24)"
"}"
".lb-burger span,"
".lb-burger span::before,"
".lb-burger span::after{"
"display:block;"
"height:2px;"
"width:26px;"
"transition:0.6s ease"
"}"
".lb-cb:checked~.lb-menu li .lb-burger span{"
"background-color:transparent"
"}"
".lb-cb:checked~.lb-menu li .lb-burger span::before,"
".lb-cb:checked~.lb-menu li .lb-burger span::after{"
"margin-top:0"
"}"
".lb-header{"
"display:flex;"
"flex-direction:row;"
"justify-content:space-between;"
"align-items:center;"
"height:58px"
"}"
".lb-menu-right .lb-burger{"
"margin-left:auto"
"}"
".lb-brand{"
"font-size:1.6em;"
"padding:18px 24px 18px 24px"
"}"
".lb-menu{"
"min-height:58px;"
"transition:0.6s ease;"
"width:100%"
"}"
".lb-navigation{"
"display:flex;"
"flex-direction:column;"
"list-style:none;"
"padding-left:0;"
"margin:0"
"}"
".lb-menu a,"
".lb-item a{"
"text-decoration:none;"
"color:inherit;"
"cursor:pointer"
"}"
".lb-item{"
"height:58px"
"}"
".lb-item a{"
"padding:18px 24px 18px 24px;"
"display:block"
"}"
".lb-burger{"
"padding:18px 24px 18px 24px;"
"position:relative;"
"cursor:pointer"
"}"
".lb-burger span::before,"
".lb-burger span::after{"
"content:'';"
"position:absolute"
"}"
".lb-burger span::before{"
"margin-top:-8px"
"}"
".lb-burger span::after{"
"margin-top:8px"
"}"
".lb-cb{"
"display:none"
"}"
".lb-cb:not(:checked)~.lb-menu{"
"overflow:hidden;"
"height:58px"
"}"
".lb-cb:checked~.lb-menu{"
"transition:height 0.6s ease;"
"height:100vh;"
"overflow:auto"
"}"
".dropdown{"
"position:relative;"
"height:auto;"
"min-height:58px"
"}"
".dropdown:hover>ul{"
"position:relative;"
"display:block;"
"min-width:100%"
"}"
".dropdown>a::after{"
"position:absolute;"
"content:'';"
"right:10px;"
"top:25px;"
"border-width:5px 5px 0;"
"border-color:transparent;"
"border-style:solid"
"}"
".dropdown>ul{"
"display:block;"
"overflow-x:hidden;"
"list-style:none;"
"padding:0"
"}"
".dropdown>ul .lb-item{"
"min-width:100%;"
"height:29px;"
"padding:5px 10px 5px 40px"
"}"
".dropdown>ul .lb-item a{"
"min-height:29px;"
"line-height:29px;"
"padding:0"
"}"
"@media screen and (min-width:768px){"
".lb-navigation{"
"flex-flow:row;"
"justify-content:flex-end;"
"}"
".lb-burger{"
"display:none;"
"}"
".lb-cb:not(:checked)~.lb-menu{"
"overflow:visible;"
"}"
".lb-cb:checked~.lb-menu{"
"height:58px;"
"}"
".lb-menu .lb-item{"
"border-top:0;"
"}"
".lb-menu-right .lb-header{"
"margin-right:auto;"
"}"
".dropdown{"
"height:58px;"
"}"
".dropdown:hover>ul{"
"position:absolute;"
"left:0;"
"top:58px;"
"padding:0;"
"}"
".dropdown>ul{"
"display:none;"
"}"
".dropdown>ul .lb-item{"
"padding:5px 10px;"
"}"
".dropdown>ul .lb-item a{"
"white-space:nowrap;"
"}"
"}"
".lb-cb:checked+.lb-menu .lb-burger-dblspin span::before{"
"transform:rotate(225deg)"
"}"
".lb-cb:checked+.lb-menu .lb-burger-dblspin span::after{"
"transform:rotate(-225deg)"
"}"
".lb-menu-material,"
".lb-menu-material .dropdown ul{"
"background-color:" AUTOCONNECT_MENUCOLOR_BACKGROUND ";"
"color:" AUTOCONNECT_MENUCOLOR_TEXT
"}"
".lb-menu-material .active,"
".lb-menu-material .lb-item:hover{"
"background-color:" AUTOCONNECT_MENUCOLOR_ACTIVE
"}"
".lb-menu-material .lb-burger span,"
".lb-menu-material .lb-burger span::before,"
".lb-menu-material .lb-burger span::after{"
"background-color:" AUTOCONNECT_MENUCOLOR_TEXT
"}"
};
/**< Common html document header. */
const char AutoConnect::_ELM_HTML_HEAD[] PROGMEM = {
"<!DOCTYPE html>"
"<html>"
"<head>"
"<meta charset=\"UTF-8\" name=\"viewport\" content=\"width=device-width,initial-scale=1\">"
};
/**< LuxBar menu element. */
const char AutoConnect::_ELM_MENU_PRE[] PROGMEM = {
"<header id=\"lb\" class=\"lb-fixed\">"
"<input type=\"checkbox\" class=\"lb-cb\" id=\"lb-cb\"/>"
"<div class=\"lb-menu lb-menu-right lb-menu-material\">"
"<ul class=\"lb-navigation\">"
"<li class=\"lb-header\">"
"<a href=\"BOOT_URI\" class=\"lb-brand\">MENU_TITLE</a>"
"<label class=\"lb-burger lb-burger-dblspin\" id=\"lb-burger\" for=\"lb-cb\"><span></span></label>"
"</li>"
"MENU_LIST"
};
const char AutoConnect::_ELM_MENU_AUX[] PROGMEM = {
"{{AUX_MENU}}"
};
const char AutoConnect::_ELM_MENU_POST[] PROGMEM = {
"MENU_HOME"
"MENU_DEVINFO"
"</ul>"
"</div>"
"<div class=\"lap\" id=\"rdlg\"><a href=\"#reset\" class=\"overlap\"></a>"
"<div class=\"modal_button\"><h2><a href=\"" AUTOCONNECT_URI_RESET "\" class=\"modal_button\">" AUTOCONNECT_BUTTONLABEL_RESET "</a></h2></div>"
"</div>"
"</header>"
};
/**< The 404 page content. */
const char AutoConnect::_PAGE_404[] PROGMEM = {
"{{HEAD}}"
"<title>" AUTOCONNECT_PAGETITLE_NOTFOUND "</title>"
"</head>"
"<body>"
"404 Not found"
"</body>"
"</html>"
};
/**< The page that started the reset. */
const char AutoConnect::_PAGE_RESETTING[] PROGMEM = {
"{{HEAD}}"
"<meta http-equiv=\"refresh\" content=\"{{UPTIME}};url={{BOOTURI}}\">"
"<title>" AUTOCONNECT_PAGETITLE_RESETTING "</title>"
"</head>"
"<body>"
"<h3><div style=\"display:inline-block\"><span>{{RESET}}</span><span id=\"cd\"></span></div></h3>"
"<script type=\"text/javascript\">"
"window.onload=function(){"
"var t={{UPTIME}},elm=document.getElementById(\"cd\"),ct=setInterval(function(){--t?elm.innerHTML=String(t)+\"&nbsp;sec.\":(elm.innerHTML=\"expiry\",clearInterval(ct))},1e3);"
"};"
"</script>"
"</body>"
"</html>"
};
/**< AutoConnect portal page. */
const char AutoConnect::_PAGE_STAT[] PROGMEM = {
"{{HEAD}}"
"<title>" AUTOCONNECT_PAGETITLE_STATISTICS "</title>"
"<style type=\"text/css\">"
"{{CSS_BASE}}"
"{{CSS_TABLE}}"
"{{CSS_LUXBAR}}"
"</style>"
"</head>"
"<body style=\"padding-top:58px;\">"
"<div class=\"container\">"
"{{MENU_PRE}}"
"{{MENU_AUX}}"
"{{MENU_POST}}"
"<div>"
"<table class=\"info\" style=\"border:none;\">"
"<tbody>"
"<tr>"
"<td>" AUTOCONNECT_PAGESTATS_ESTABLISHEDCONNECTION "</td>"
"<td>{{ESTAB_SSID}}</td>"
"</tr>"
"<tr>"
"<td>" AUTOCONNECT_PAGESTATS_MODE "</td>"
"<td>{{WIFI_MODE}}({{WIFI_STATUS}})</td>"
"</tr>"
"<tr>"
"<td>" AUTOCONNECT_PAGESTATS_IP "</td>"
"<td>{{LOCAL_IP}}</td>"
"</tr>"
"<tr>"
"<td>" AUTOCONNECT_PAGESTATS_GATEWAY "</td>"
"<td>{{GATEWAY}}</td>"
"</tr>"
"<tr>"
"<td>" AUTOCONNECT_PAGESTATS_SUBNETMASK "</td>"
"<td>{{NETMASK}}</td>"
"</tr>"
"<tr>"
"<td>" AUTOCONNECT_PAGESTATS_SOFTAPIP "</td>"
"<td>{{SOFTAP_IP}}</td>"
"</tr>"
"<tr>"
"<td>" AUTOCONNECT_PAGESTATS_APMAC "</td>"
"<td>{{AP_MAC}}</td>"
"</tr>"
"<tr>"
"<td>" AUTOCONNECT_PAGESTATS_STAMAC "</td>"
"<td>{{STA_MAC}}</td>"
"</tr>"
"<tr>"
"<td>" AUTOCONNECT_PAGESTATS_CHANNEL "</td>"
"<td>{{CHANNEL}}</td>"
"</tr>"
"<tr>"
"<td>" AUTOCONNECT_PAGESTATS_DBM "</td>"
"<td>{{DBM}}</td>"
"</tr>"
"<tr>"
"<td>" AUTOCONNECT_PAGESTATS_CHIPID "</td>"
"<td>{{CHIP_ID}}</td>"
"</tr>"
"<tr>"
"<td>" AUTOCONNECT_PAGESTATS_CPUFREQ "</td>"
"<td>{{CPU_FREQ}}MHz</td>"
"</tr>"
"<tr>"
"<td>" AUTOCONNECT_PAGESTATS_FLASHSIZE "</td>"
"<td>{{FLASH_SIZE}}</td>"
"</tr>"
"<tr>"
"<td>" AUTOCONNECT_PAGESTATS_FREEMEM "</td>"
"<td>{{FREE_HEAP}}</td>"
"</tr>"
"</tbody>"
"</table>"
"</div>"
"</div>"
"</body>"
"</html>"
};
/**< A page that specifies the new configuration. */
const char AutoConnect::_PAGE_CONFIGNEW[] PROGMEM = {
"{{HEAD}}"
"<title>" AUTOCONNECT_PAGETITLE_CONFIG "</title>"
"<style type=\"text/css\">"
"{{CSS_BASE}}"
"{{CSS_ICON_LOCK}}"
"{{CSS_UL}}"
"{{CSS_INPUT_BUTTON}}"
"{{CSS_INPUT_TEXT}}"
"{{CSS_LUXBAR}}"
"</style>"
"</head>"
"<body style=\"padding-top:58px;\">"
"<div class=\"container\">"
"{{MENU_PRE}}"
"{{MENU_AUX}}"
"{{MENU_POST}}"
"<div class=\"base-panel\">"
"<form action=\"" AUTOCONNECT_URI_CONNECT "\" method=\"post\">"
"<button style=\"width:0;height:0;padding:0;border:0;margin:0\" aria-hidden=\"true\" tabindex=\"-1\" type=\"submit\" name=\"apply\" value=\"apply\"></button>"
"{{LIST_SSID}}"
"<div style=\"margin:16px 0 8px 0;border-bottom:solid 1px #263238;\">" AUTOCONNECT_PAGECONFIG_TOTAL "{{SSID_COUNT}} " AUTOCONNECT_PAGECONFIG_HIDDEN "{{HIDDEN_COUNT}}</div>"
"<ul class=\"noorder\">"
"<li>"
"<label for=\"ssid\">" AUTOCONNECT_PAGECONFIG_SSID "</label>"
"<input id=\"ssid\" type=\"text\" name=\"" AUTOCONNECT_PARAMID_SSID "\" placeholder=\"" AUTOCONNECT_PAGECONFIG_SSID "\">"
"</li>"
"<li>"
"<label for=\"passphrase\">" AUTOCONNECT_PAGECONFIG_PASSPHRASE "</label>"
"<input id=\"passphrase\" type=\"password\" name=\"" AUTOCONNECT_PARAMID_PASS "\" placeholder=\"" AUTOCONNECT_PAGECONFIG_PASSPHRASE "\">"
"</li>"
"<li>"
"<label for=\"dhcp\">" AUTOCONNECT_PAGECONFIG_ENABLEDHCP "</label>"
"<input id=\"dhcp\" type=\"checkbox\" name=\"dhcp\" value=\"en\" checked onclick=\"vsw(this.checked);\">"
"</li>"
"{{CONFIG_IP}}"
"<li><input type=\"submit\" name=\"apply\" value=\"" AUTOCONNECT_PAGECONFIG_APPLY "\"></li>"
"</ul>"
"</form>"
"</div>"
"</div>"
"<script type=\"text/javascript\">"
"window.onload=function(){"
"['" AUTOCONNECT_PARAMID_STAIP "','" AUTOCONNECT_PARAMID_GTWAY "','" AUTOCONNECT_PARAMID_NTMSK "','" AUTOCONNECT_PARAMID_DNS1 "','" AUTOCONNECT_PARAMID_DNS2 "'].forEach(function(n,o,t){"
"io=document.getElementById(n),io.placeholder='0.0.0.0',io.pattern='^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$'});"
"vsw(true)};"
"function onFocus(e){"
"document.getElementById('ssid').value=e,document.getElementById('passphrase').focus()"
"}"
"function vsw(e){"
"var t;t=e?'none':'table-row';for(const n of document.getElementsByClassName('exp'))n.style.display=t,n.getElementsByTagName('input')[0].disabled=e;e||document.getElementById('sip').focus()"
"}"
"</script>"
"</body>"
"</html>"
};
/**< A page that reads stored authentication information and starts connection. */
const char AutoConnect::_PAGE_OPENCREDT[] PROGMEM = {
"{{HEAD}}"
"<title>" AUTOCONNECT_PAGETITLE_CREDENTIALS "</title>"
"<style type=\"text/css\">"
"{{CSS_BASE}}"
"{{CSS_ICON_LOCK}}"
"{{CSS_INPUT_BUTTON}}"
"{{CSS_LUXBAR}}"
"</style>"
"</head>"
"<body style=\"padding-top:58px;\">"
"<div class=\"container\">"
"{{MENU_PRE}}"
"{{MENU_AUX}}"
"{{MENU_POST}}"
"<div class=\"base-panel\">"
"<form action=\"" AUTOCONNECT_URI_CONNECT "\" method=\"post\">"
"{{OPEN_SSID}}"
"</form>"
"</div>"
"</div>"
"</body>"
"</html>"
};
/**< A page that informs during a connection attempting. */
const char AutoConnect::_PAGE_CONNECTING[] PROGMEM = {
"{{REQ}}"
"{{HEAD}}"
"<title>" AUTOCONNECT_PAGETITLE_CONNECTING "</title>"
"<style type=\"text/css\">"
"{{CSS_BASE}}"
"{{CSS_SPINNER}}"
"{{CSS_LUXBAR}}"
"</style>"
"</head>"
"<body style=\"padding-top:58px;\">"
"<div class=\"container\">"
"{{MENU_PRE}}"
"{{MENU_POST}}"
"<div class=\"spinner\">"
"<div class=\"dbl-bounce1\"></div>"
"<div class=\"dbl-bounce2\"></div>"
"<div style=\"position:absolute;left:-100%;right:-100%;text-align:center;margin:10px auto;font-weight:bold;color:#0b0b33;\">{{CUR_SSID}}</div>"
"</div>"
"</div>"
"<script type=\"text/javascript\">"
"setTimeout(\"link()\"," AUTOCONNECT_STRING_DEPLOY(AUTOCONNECT_RESPONSE_WAITTIME) ");"
"function link(){location.href='" AUTOCONNECT_URI_RESULT "';}"
"</script>"
"</body>"
"</html>"
};
/**< A page announcing that a connection has been established. */
const char AutoConnect::_PAGE_SUCCESS[] PROGMEM = {
"{{HEAD}}"
"<title>" AUTOCONNECT_PAGETITLE_STATISTICS "</title>"
"<style type=\"text/css\">"
"{{CSS_BASE}}"
"{{CSS_TABLE}}"
"{{CSS_LUXBAR}}"
"</style>"
"</head>"
"<body style=\"padding-top:58px;\">"
"<div class=\"container\">"
"{{MENU_PRE}}"
"{{MENU_AUX}}"
"{{MENU_POST}}"
"<div>"
"<table class=\"info\" style=\"border:none;\">"
"<tbody>"
"<tr>"
"<td>" AUTOCONNECT_PAGESTATS_ESTABLISHEDCONNECTION "</td>"
"<td>{{ESTAB_SSID}}</td>"
"</tr>"
"<tr>"
"<td>" AUTOCONNECT_PAGESTATS_MODE "</td>"
"<td>{{WIFI_MODE}}({{WIFI_STATUS}})</td>"
"</tr>"
"<tr>"
"<td>" AUTOCONNECT_PAGESTATS_IP "</td>"
"<td>{{LOCAL_IP}}</td>"
"</tr>"
"<td>" AUTOCONNECT_PAGESTATS_GATEWAY "</td>"
"<td>{{GATEWAY}}</td>"
"</tr>"
"<tr>"
"<td>" AUTOCONNECT_PAGESTATS_SUBNETMASK "</td>"
"<td>{{NETMASK}}</td>"
"</tr>"
"<tr>"
"<td>" AUTOCONNECT_PAGESTATS_CHANNEL "</td>"
"<td>{{CHANNEL}}</td>"
"</tr>"
"<tr>"
"<td>" AUTOCONNECT_PAGESTATS_DBM "</td>"
"<td>{{DBM}}</td>"
"</tr>"
"</tbody>"
"</table>"
"</div>"
"</div>"
"</body>"
"</html>"
};
/**< A response page for connection failed. */
const char AutoConnect::_PAGE_FAIL[] PROGMEM = {
"{{HEAD}}"
"<title>" AUTOCONNECT_PAGETITLE_CONNECTIONFAILED "</title>"
"<style type=\"text/css\">"
"{{CSS_BASE}}"
"{{CSS_TABLE}}"
"{{CSS_LUXBAR}}"
"</style>"
"</head>"
"<body style=\"padding-top:58px;\">"
"<div class=\"container\">"
"{{MENU_PRE}}"
"{{MENU_AUX}}"
"{{MENU_POST}}"
"<div>"
"<table class=\"info\" style=\"border:none;\">"
"<tbody>"
"<tr>"
"<td>" AUTOCONNECT_PAGECONNECTIONFAILED_CONNECTIONFAILED "</td>"
"<td>{{STATION_STATUS}}</td>"
"</tr>"
"</tbody>"
"</table>"
"</div>"
"</div>"
"</body>"
"</html>"
};
/**< A response page for disconnected from the AP. */
const char AutoConnect::_PAGE_DISCONN[] PROGMEM = {
"{{DISCONNECT}}"
"{{HEAD}}"
"<title>" AUTOCONNECT_PAGETITLE_DISCONNECTED "</title>"
"<style type=\"text/css\">"
"{{CSS_BASE}}"
"{{CSS_LUXBAR}}"
"</style>"
"</head>"
"<body style=\"padding-top:58px;\">"
"<div class=\"container\">"
"{{MENU_PRE}}"
"{{MENU_POST}}"
"</div>"
"</body>"
"</html>"
};
// Each page of AutoConnect is http transferred by the content transfer
// mode of Page Builder. The default transfer mode is
// AUTOCONNECT_HTTP_TRANSFER defined in AutoConnectDefs.h. The page to
// which default transfer mode is not applied, specifies the enumeration
// value of PageBuilder::TransferEncoding_t. The content construction
// buffer can be reserved with the chunked transfer, and its size is
// macro defined by AUTOCONNECT_CONTENTBUFFER_SIZE.
const AutoConnect::PageTranserModeST AutoConnect::_pageBuildMode[] = {
{ AUTOCONNECT_URI, AUTOCONNECT_HTTP_TRANSFER, 0 },
{ AUTOCONNECT_URI_CONFIG, PB_Chunk, AUTOCONNECT_CONTENTBUFFER_SIZE },
{ AUTOCONNECT_URI_CONNECT, AUTOCONNECT_HTTP_TRANSFER, 0 },
{ AUTOCONNECT_URI_RESULT, AUTOCONNECT_HTTP_TRANSFER, 0 },
{ AUTOCONNECT_URI_OPEN, AUTOCONNECT_HTTP_TRANSFER, 0 },
{ AUTOCONNECT_URI_DISCON, AUTOCONNECT_HTTP_TRANSFER, 0 },
{ AUTOCONNECT_URI_RESET, AUTOCONNECT_HTTP_TRANSFER, 0 },
{ AUTOCONNECT_URI_SUCCESS, AUTOCONNECT_HTTP_TRANSFER, 0 },
{ AUTOCONNECT_URI_FAIL, AUTOCONNECT_HTTP_TRANSFER, 0 }
};
uint32_t AutoConnect::_getChipId() {
#if defined(ARDUINO_ARCH_ESP8266)
return ESP.getChipId();
#elif defined(ARDUINO_ARCH_ESP32)
uint64_t chipId;
chipId = ESP.getEfuseMac();
return (uint32_t)(chipId >> 32);
#endif
}
uint32_t AutoConnect::_getFlashChipRealSize() {
#if defined(ARDUINO_ARCH_ESP8266)
return ESP.getFlashChipRealSize();
#elif defined(ARDUINO_ARCH_ESP32)
return (uint32_t)spi_flash_get_chip_size();
#endif
}
String AutoConnect::_token_CSS_BASE(PageArgument& args) {
AC_UNUSED(args);
return String(FPSTR(_CSS_BASE));
}
String AutoConnect::_token_CSS_ICON_LOCK(PageArgument& args) {
AC_UNUSED(args);
return String(FPSTR(_CSS_ICON_LOCK));
}
String AutoConnect::_token_CSS_INPUT_BUTTON(PageArgument& args) {
AC_UNUSED(args);
return String(FPSTR(_CSS_INPUT_BUTTON));
}
String AutoConnect::_token_CSS_INPUT_TEXT(PageArgument& args) {
AC_UNUSED(args);
return String(FPSTR(_CSS_INPUT_TEXT));
}
String AutoConnect::_token_CSS_LUXBAR(PageArgument& args) {
AC_UNUSED(args);
return String(FPSTR(_CSS_LUXBAR));
}
String AutoConnect::_token_CSS_SPINNER(PageArgument& args) {
AC_UNUSED(args);
return String(FPSTR(_CSS_SPINNER));
}
String AutoConnect::_token_CSS_TABLE(PageArgument& args) {
AC_UNUSED(args);
return String(FPSTR(_CSS_TABLE));
}
String AutoConnect::_token_CSS_UL(PageArgument& args) {
AC_UNUSED(args);
return String(FPSTR(_CSS_UL));
}
String AutoConnect::_token_MENU_AUX(PageArgument& args) {
String menuItem = String("");
if (_aux)
menuItem = _aux->_injectMenu(args);
return menuItem;
}
String AutoConnect::_token_MENU_PRE(PageArgument& args) {
String currentMenu = FPSTR(_ELM_MENU_PRE);
String menuItem = _attachMenuItem(AC_MENUITEM_CONFIGNEW) +
_attachMenuItem(AC_MENUITEM_OPENSSIDS) +
_attachMenuItem(AC_MENUITEM_DISCONNECT) +
_attachMenuItem(AC_MENUITEM_RESET);
currentMenu.replace(String(F("MENU_LIST")), menuItem);
currentMenu.replace(String(F("BOOT_URI")), _getBootUri());
currentMenu.replace(String(F("MENU_TITLE")), _menuTitle);
currentMenu.replace(String(F("{{CUR_SSID}}")), _token_ESTAB_SSID(args));
return currentMenu;
}
String AutoConnect::_token_MENU_POST(PageArgument& args) {
AC_UNUSED(args);
String postMenu = FPSTR(_ELM_MENU_POST);
postMenu.replace(String(F("MENU_HOME")), _attachMenuItem(AC_MENUITEM_HOME));
postMenu.replace(String(F("HOME_URI")), _apConfig.homeUri);
postMenu.replace(String(F("MENU_DEVINFO")), _attachMenuItem(AC_MENUITEM_DEVINFO));
return postMenu;
}
String AutoConnect::_token_AP_MAC(PageArgument& args) {
AC_UNUSED(args);
uint8_t macAddress[6];
WiFi.softAPmacAddress(macAddress);
return AutoConnect::_toMACAddressString(macAddress);
}
String AutoConnect::_token_BOOTURI(PageArgument& args) {
AC_UNUSED(args);
return _getBootUri();
}
String AutoConnect::_token_CHANNEL(PageArgument& args) {
AC_UNUSED(args);
return String(WiFi.channel());
}
String AutoConnect::_token_CHIP_ID(PageArgument& args) {
AC_UNUSED(args);
return String(_getChipId());
}
String AutoConnect::_token_CONFIG_STAIP(PageArgument& args) {
AC_UNUSED(args);
static const char _configIPList[] PROGMEM =
"<li class=\"exp\">"
"<label for=\"%s\">%s</label>"
"<input id=\"%s\" type=\"text\" name=\"%s\" value=\"%s\">"
"</li>";
struct _reps {
PGM_P lid;
PGM_P lbl;
} static const reps[] = {
{ PSTR(AUTOCONNECT_PARAMID_STAIP), PSTR("IP Address") },
{ PSTR(AUTOCONNECT_PARAMID_GTWAY), PSTR("Gateway") },
{ PSTR(AUTOCONNECT_PARAMID_NTMSK), PSTR("Netmask") },
{ PSTR(AUTOCONNECT_PARAMID_DNS1), PSTR("DNS1") },
{ PSTR(AUTOCONNECT_PARAMID_DNS2), PSTR("DNS2") }
};
char liCont[600];
char* liBuf = liCont;
for (uint8_t i = 0; i < 5; i++) {
IPAddress* ip = nullptr;
if (i == 0)
ip = &_apConfig.staip;
else if (i == 1)
ip = &_apConfig.staGateway;
else if (i == 2)
ip = &_apConfig.staNetmask;
else if (i == 3)
ip = &_apConfig.dns1;
else if (i == 4)
ip = &_apConfig.dns2;
String ipStr = ip != nullptr ? ip->toString() : String(F("0.0.0.0"));
snprintf_P(liBuf, sizeof(liCont) - (liBuf - liCont), (PGM_P)_configIPList, reps[i].lid, reps[i].lbl, reps[i].lid, reps[i].lid, ipStr.c_str());
liBuf += strlen(liBuf);
}
return String(liCont);
}
String AutoConnect::_token_CPU_FREQ(PageArgument& args) {
AC_UNUSED(args);
return String(ESP.getCpuFreqMHz());
}
String AutoConnect::_token_CURRENT_SSID(PageArgument& args) {
AC_UNUSED(args);
char ssid_c[sizeof(station_config_t::ssid) + 1];
*ssid_c = '\0';
strncat(ssid_c, reinterpret_cast<char*>(_credential.ssid), sizeof(ssid_c) - 1);
String ssid = String(ssid_c);
return ssid;
}
String AutoConnect::_token_DBM(PageArgument& args) {
AC_UNUSED(args);
int32_t dBm = WiFi.RSSI();
return (dBm == 31 ? String(F("N/A")) : String(dBm));
}
String AutoConnect::_token_ESTAB_SSID(PageArgument& args) {
AC_UNUSED(args);
return (WiFi.status() == WL_CONNECTED ? WiFi.SSID() : String(F("N/A")));
}
String AutoConnect::_token_FLASH_SIZE(PageArgument& args) {
AC_UNUSED(args);
return String(_getFlashChipRealSize());
}
String AutoConnect::_token_FREE_HEAP(PageArgument& args) {
AC_UNUSED(args);
return String(_freeHeapSize);
}
String AutoConnect::_token_GATEWAY(PageArgument& args) {
AC_UNUSED(args);
return WiFi.gatewayIP().toString();
}
String AutoConnect::_token_HEAD(PageArgument& args) {
AC_UNUSED(args);
return String(FPSTR(_ELM_HTML_HEAD));
}
String AutoConnect::_token_HIDDEN_COUNT(PageArgument& args) {
AC_UNUSED(args);
return String(_hiddenSSIDCount);
}
String AutoConnect::_token_LIST_SSID(PageArgument& args) {
// Obtain the page number to display.
// When the display request is the first page, it will be obtained
// from the scan results of the WiFiScan class if it has already been
// scanned.
uint8_t page = 0;
if (args.hasArg(String(F("page"))))
page = args.arg("page").toInt();
else {
// Scan at a first time
_scanCount = WiFi.scanNetworks(false, true);
AC_DBG("%d network(s) found, ", (int)_scanCount);
}
// Prepare SSID list content building buffer
size_t bufSize = sizeof('\0') + 192 * (_scanCount > AUTOCONNECT_SSIDPAGEUNIT_LINES ? AUTOCONNECT_SSIDPAGEUNIT_LINES : _scanCount);
bufSize += 88 * (_scanCount > AUTOCONNECT_SSIDPAGEUNIT_LINES ? (_scanCount > (AUTOCONNECT_SSIDPAGEUNIT_LINES * 2) ? 2 : 1) : 0);
AC_DBG_DUMB("%d buf", bufSize);
char* ssidList = (char*)malloc(bufSize);
if (!ssidList) {
AC_DBG_DUMB(" alloc. failed\n");
WiFi.scanDelete();
return _emptyString;
}
AC_DBG_DUMB("\n");
// Locate to the page and build SSD list content.
static const char _ssidList[] PROGMEM =
"<input type=\"button\" onClick=\"onFocus(this.getAttribute('value'))\" value=\"%s\">"
"<label class=\"slist\">%d&#037;&ensp;Ch.%d</label>%s<br>";
static const char _ssidEnc[] PROGMEM =
"<span class=\"img-lock\"></span>";
static const char _ssidPage[] PROGMEM =
"<button type=\"submit\" name=\"page\" value=\"%d\" formaction=\"" AUTOCONNECT_URI_CONFIG "\">%s</button>&emsp;";
_hiddenSSIDCount = 0;
uint8_t validCount = 0;
uint8_t dispCount = 0;
char* slBuf = ssidList;
*slBuf = '\0';
for (uint8_t i = 0; i < _scanCount; i++) {
String ssid = WiFi.SSID(i);
if (ssid.length() > 0) {
// An available SSID may be listed.
// AUTOCONNECT_SSIDPAGEUNIT_LINES determines the number of lines
// per page in the available SSID list.
if (validCount >= page * AUTOCONNECT_SSIDPAGEUNIT_LINES && validCount <= (page + 1) * AUTOCONNECT_SSIDPAGEUNIT_LINES - 1) {
if (++dispCount <= AUTOCONNECT_SSIDPAGEUNIT_LINES) {
snprintf_P(slBuf, bufSize - (slBuf - ssidList), (PGM_P)_ssidList, ssid.c_str(), AutoConnect::_toWiFiQuality(WiFi.RSSI(i)), WiFi.channel(i), WiFi.encryptionType(i) != ENC_TYPE_NONE ? (PGM_P)_ssidEnc : "");
slBuf += strlen(slBuf);
}
}
// The validCount counts the found SSIDs that is not the Hidden
// attribute to determines the next button should be displayed.
validCount++;
}
else
_hiddenSSIDCount++;
}
// Prepare perv. button
if (page >= 1) {
snprintf_P(slBuf, bufSize - (slBuf - ssidList), (PGM_P)_ssidPage, page - 1, PSTR("Prev."));
slBuf = ssidList + strlen(ssidList);
}
// Prepare next button
if (validCount > (page + 1) * AUTOCONNECT_SSIDPAGEUNIT_LINES) {
snprintf_P(slBuf, bufSize - (slBuf - ssidList), (PGM_P)_ssidPage, page + 1, PSTR("Next"));
}
String ssidListStr = String(ssidList);
free(ssidList);
return ssidListStr;
}
String AutoConnect::_token_LOCAL_IP(PageArgument& args) {
AC_UNUSED(args);
return WiFi.localIP().toString();
}
String AutoConnect::_token_NETMASK(PageArgument& args) {
AC_UNUSED(args);
return WiFi.subnetMask().toString();
}
String AutoConnect::_token_OPEN_SSID(PageArgument& args) {
AC_UNUSED(args);
static const char _ssidList[] PROGMEM = "<input id=\"sb\" type=\"submit\" name=\"%s\" value=\"%s\"><label class=\"slist\">%s</label>%s<br>";
static const char _ssidRssi[] PROGMEM = "%d&#037;&ensp;Ch.%d";
static const char _ssidNA[] PROGMEM = "N/A";
static const char _ssidLock[] PROGMEM = "<span class=\"img-lock\"></span>";
static const char _ssidNull[] PROGMEM = "";
String ssidList;
station_config_t entry;
char slCont[176];
char rssiCont[32];
AutoConnectCredential credit(_apConfig.boundaryOffset);
uint8_t creEntries = credit.entries();
if (creEntries > 0) {
ssidList = String("");
_scanCount = WiFi.scanNetworks(false, true);
}
else
ssidList = String(F("<p><b>" AUTOCONNECT_TEXT_NOSAVEDCREDENTIALS "</b></p>"));
for (uint8_t i = 0; i < creEntries; i++) {
rssiCont[0] = '\0';
PGM_P rssiSym = _ssidNA;
PGM_P ssidLock = _ssidNull;
credit.load(i, &entry);
AC_DBG("Credential #%d loaded\n", (int)i);
for (int8_t sc = 0; sc < (int8_t)_scanCount; sc++) {
if (_isValidAP(entry, sc)) {
// The access point collation key is determined at compile time
// according to the AUTOCONNECT_APKEY_SSID definition, which is
// either BSSID or SSID.
_connectCh = WiFi.channel(sc);
snprintf_P(rssiCont, sizeof(rssiCont), (PGM_P)_ssidRssi, AutoConnect::_toWiFiQuality(WiFi.RSSI(sc)), _connectCh);
rssiSym = rssiCont;
if (WiFi.encryptionType(sc) != ENC_TYPE_NONE)
ssidLock = _ssidLock;
break;
}
}
snprintf_P(slCont, sizeof(slCont), (PGM_P)_ssidList, AUTOCONNECT_PARAMID_CRED, reinterpret_cast<char*>(entry.ssid), rssiSym, ssidLock);
ssidList += String(slCont);
}
return ssidList;
}
String AutoConnect::_token_SOFTAP_IP(PageArgument& args) {
AC_UNUSED(args);
return WiFi.softAPIP().toString();
}
String AutoConnect::_token_SSID_COUNT(PageArgument& args) {
AC_UNUSED(args);
return String(_scanCount);
}
String AutoConnect::_token_STA_MAC(PageArgument& args) {
AC_UNUSED(args);
uint8_t macAddress[6];
WiFi.macAddress(macAddress);
return AutoConnect::_toMACAddressString(macAddress);
}
String AutoConnect::_token_STATION_STATUS(PageArgument& args) {
AC_UNUSED(args);
PGM_P wlStatusSymbol = PSTR("");
PGM_P wlStatusSymbols[] = {
#if defined(ARDUINO_ARCH_ESP8266)
PSTR("IDLE"),
PSTR("CONNECTING"),
PSTR("WRONG_PASSWORD"),
PSTR("NO_AP_FOUND"),
PSTR("CONNECT_FAIL"),
PSTR("GOT_IP")
};
switch (wifi_station_get_connect_status()) {
case STATION_IDLE:
wlStatusSymbol = wlStatusSymbols[0];
break;
case STATION_CONNECTING:
wlStatusSymbol = wlStatusSymbols[1];
break;
case STATION_WRONG_PASSWORD:
wlStatusSymbol = wlStatusSymbols[2];
break;
case STATION_NO_AP_FOUND:
wlStatusSymbol = wlStatusSymbols[3];
break;
case STATION_CONNECT_FAIL:
wlStatusSymbol = wlStatusSymbols[4];
break;
case STATION_GOT_IP:
wlStatusSymbol = wlStatusSymbols[5];
break;
#elif defined(ARDUINO_ARCH_ESP32)
PSTR("IDLE"),
PSTR("NO_SSID_AVAIL"),
PSTR("SCAN_COMPLETED"),
PSTR("CONNECTED"),
PSTR("CONNECT_FAILED"),
PSTR("CONNECTION_LOST"),
PSTR("DISCONNECTED"),
PSTR("NO_SHIELD")
};
switch (_rsConnect) {
case WL_IDLE_STATUS:
wlStatusSymbol = wlStatusSymbols[0];
break;
case WL_NO_SSID_AVAIL:
wlStatusSymbol = wlStatusSymbols[1];
break;
case WL_SCAN_COMPLETED:
wlStatusSymbol = wlStatusSymbols[2];
break;
case WL_CONNECTED:
wlStatusSymbol = wlStatusSymbols[3];
break;
case WL_CONNECT_FAILED:
wlStatusSymbol = wlStatusSymbols[4];
break;
case WL_CONNECTION_LOST:
wlStatusSymbol = wlStatusSymbols[5];
break;
case WL_DISCONNECTED:
wlStatusSymbol = wlStatusSymbols[6];
break;
case WL_NO_SHIELD:
wlStatusSymbol = wlStatusSymbols[7];
break;
#endif
}
return String("(") + String(_rsConnect) + String(") ") + String(FPSTR(wlStatusSymbol));
}
String AutoConnect::_token_UPTIME(PageArgument& args) {
AC_UNUSED(args);
return String(_apConfig.uptime);
}
String AutoConnect::_token_WIFI_MODE(PageArgument& args) {
AC_UNUSED(args);
PGM_P wifiMode;
switch (WiFi.getMode()) {
case WIFI_OFF:
wifiMode = PSTR("OFF");
break;
case WIFI_STA:
wifiMode = PSTR("STA");
break;
case WIFI_AP:
wifiMode = PSTR("AP");
break;
case WIFI_AP_STA:
wifiMode = PSTR("AP_STA");
break;
#ifdef ARDUINO_ARCH_ESP32
case WIFI_MODE_MAX:
wifiMode = PSTR("MAX");
break;
#endif
default:
wifiMode = PSTR("experimental");
}
return String(FPSTR(wifiMode));
}
String AutoConnect::_token_WIFI_STATUS(PageArgument& args) {
AC_UNUSED(args);
return String(WiFi.status());
}
/**
* Generate AutoConnect menu item configured by AutoConnectConfig::attachMenu.
* @param item An enumerated value for the generating item configured in AutoConnectConfig.
* @return HTML string of a li tag with the menu item.
*/
String AutoConnect::_attachMenuItem(const AC_MENUITEM_t item) {
static const char _liTempl[] PROGMEM = "<li class=\"lb-item\"%s><a href=\"%s\">%s</a></li>";
PGM_P id = PSTR("");
PGM_P link;
PGM_P label;
switch (static_cast<AC_MENUITEM_t>(_apConfig.menuItems & static_cast<uint16_t>(item))) {
case AC_MENUITEM_CONFIGNEW:
link = PSTR(AUTOCONNECT_URI_CONFIG);
label = PSTR(AUTOCONNECT_MENULABEL_CONFIGNEW);
break;
case AC_MENUITEM_OPENSSIDS:
link = PSTR(AUTOCONNECT_URI_OPEN);
label = PSTR(AUTOCONNECT_MENULABEL_OPENSSIDS);
break;
case AC_MENUITEM_DISCONNECT:
link = PSTR(AUTOCONNECT_URI_DISCON);
label = PSTR(AUTOCONNECT_MENULABEL_DISCONNECT);
break;
case AC_MENUITEM_RESET:
id = PSTR(" id=\"reset\"");
link = PSTR("#rdlg");
label = PSTR(AUTOCONNECT_MENULABEL_RESET);
break;
case AC_MENUITEM_HOME:
link = PSTR("HOME_URI");
label = PSTR(AUTOCONNECT_MENULABEL_HOME);
break;
case AC_MENUITEM_DEVINFO:
link = PSTR(AUTOCONNECT_URI);
label = PSTR(AUTOCONNECT_MENULABEL_DEVINFO);
break;
default:
id = nullptr;
link = nullptr;
label = nullptr;
break;
}
char li[128] = { '\0' };
if (!!id && !!link && !!label)
snprintf(li, sizeof(li), (PGM_P)_liTempl, id, link, label);
return String(li);
}
/**
* This function dynamically build up the response pages that conform to
* the requested URI. A PageBuilder instance is stored in _responsePage
* as the response page.
* @param Requested URI.
* @retval true A response page generated.
* @retval false Requested uri is not defined.
*/
PageElement* AutoConnect::_setupPage(String& uri) {
PageElement *elm = new PageElement();
bool reqAuth = false;
// Restore menu title
_menuTitle = _apConfig.title;
// Build the elements of current requested page.
if (uri == String(AUTOCONNECT_URI)) {
// Setup /_ac
reqAuth = true;
_freeHeapSize = ESP.getFreeHeap();
elm->setMold(_PAGE_STAT);
elm->addToken(String(FPSTR("HEAD")), std::bind(&AutoConnect::_token_HEAD, this, std::placeholders::_1));
elm->addToken(String(FPSTR("CSS_BASE")), std::bind(&AutoConnect::_token_CSS_BASE, this, std::placeholders::_1));
elm->addToken(String(FPSTR("CSS_TABLE")), std::bind(&AutoConnect::_token_CSS_TABLE, this, std::placeholders::_1));
elm->addToken(String(FPSTR("CSS_LUXBAR")), std::bind(&AutoConnect::_token_CSS_LUXBAR, this, std::placeholders::_1));
elm->addToken(String(FPSTR("MENU_PRE")), std::bind(&AutoConnect::_token_MENU_PRE, this, std::placeholders::_1));
elm->addToken(String(FPSTR("MENU_AUX")), std::bind(&AutoConnect::_token_MENU_AUX, this, std::placeholders::_1));
elm->addToken(String(FPSTR("MENU_POST")), std::bind(&AutoConnect::_token_MENU_POST, this, std::placeholders::_1));
elm->addToken(String(FPSTR("ESTAB_SSID")), std::bind(&AutoConnect::_token_ESTAB_SSID, this, std::placeholders::_1));
elm->addToken(String(FPSTR("WIFI_MODE")), std::bind(&AutoConnect::_token_WIFI_MODE, this, std::placeholders::_1));
elm->addToken(String(FPSTR("WIFI_STATUS")), std::bind(&AutoConnect::_token_WIFI_STATUS, this, std::placeholders::_1));
elm->addToken(String(FPSTR("LOCAL_IP")), std::bind(&AutoConnect::_token_LOCAL_IP, this, std::placeholders::_1));
elm->addToken(String(FPSTR("SOFTAP_IP")), std::bind(&AutoConnect::_token_SOFTAP_IP, this, std::placeholders::_1));
elm->addToken(String(FPSTR("GATEWAY")), std::bind(&AutoConnect::_token_GATEWAY, this, std::placeholders::_1));
elm->addToken(String(FPSTR("NETMASK")), std::bind(&AutoConnect::_token_NETMASK, this, std::placeholders::_1));
elm->addToken(String(FPSTR("AP_MAC")), std::bind(&AutoConnect::_token_AP_MAC, this, std::placeholders::_1));
elm->addToken(String(FPSTR("STA_MAC")), std::bind(&AutoConnect::_token_STA_MAC, this, std::placeholders::_1));
elm->addToken(String(FPSTR("CHANNEL")), std::bind(&AutoConnect::_token_CHANNEL, this, std::placeholders::_1));
elm->addToken(String(FPSTR("DBM")), std::bind(&AutoConnect::_token_DBM, this, std::placeholders::_1));
elm->addToken(String(FPSTR("CPU_FREQ")), std::bind(&AutoConnect::_token_CPU_FREQ, this, std::placeholders::_1));
elm->addToken(String(FPSTR("FLASH_SIZE")), std::bind(&AutoConnect::_token_FLASH_SIZE, this, std::placeholders::_1));
elm->addToken(String(FPSTR("CHIP_ID")), std::bind(&AutoConnect::_token_CHIP_ID, this, std::placeholders::_1));
elm->addToken(String(FPSTR("FREE_HEAP")), std::bind(&AutoConnect::_token_FREE_HEAP, this, std::placeholders::_1));
}
else if (uri == String(AUTOCONNECT_URI_CONFIG) && (_apConfig.menuItems & AC_MENUITEM_CONFIGNEW)) {
// Setup /_ac/config
reqAuth = true;
elm->setMold(_PAGE_CONFIGNEW);
elm->addToken(String(FPSTR("HEAD")), std::bind(&AutoConnect::_token_HEAD, this, std::placeholders::_1));
elm->addToken(String(FPSTR("CSS_BASE")), std::bind(&AutoConnect::_token_CSS_BASE, this, std::placeholders::_1));
elm->addToken(String(FPSTR("CSS_UL")), std::bind(&AutoConnect::_token_CSS_UL, this, std::placeholders::_1));
elm->addToken(String(FPSTR("CSS_ICON_LOCK")), std::bind(&AutoConnect::_token_CSS_ICON_LOCK, this, std::placeholders::_1));
elm->addToken(String(FPSTR("CSS_INPUT_BUTTON")), std::bind(&AutoConnect::_token_CSS_INPUT_BUTTON, this, std::placeholders::_1));
elm->addToken(String(FPSTR("CSS_INPUT_TEXT")), std::bind(&AutoConnect::_token_CSS_INPUT_TEXT, this, std::placeholders::_1));
elm->addToken(String(FPSTR("CSS_LUXBAR")), std::bind(&AutoConnect::_token_CSS_LUXBAR, this, std::placeholders::_1));
elm->addToken(String(FPSTR("MENU_PRE")), std::bind(&AutoConnect::_token_MENU_PRE, this, std::placeholders::_1));
elm->addToken(String(FPSTR("MENU_AUX")), std::bind(&AutoConnect::_token_MENU_AUX, this, std::placeholders::_1));
elm->addToken(String(FPSTR("MENU_POST")), std::bind(&AutoConnect::_token_MENU_POST, this, std::placeholders::_1));
elm->addToken(String(FPSTR("LIST_SSID")), std::bind(&AutoConnect::_token_LIST_SSID, this, std::placeholders::_1));
elm->addToken(String(FPSTR("SSID_COUNT")), std::bind(&AutoConnect::_token_SSID_COUNT, this, std::placeholders::_1));
elm->addToken(String(FPSTR("HIDDEN_COUNT")), std::bind(&AutoConnect::_token_HIDDEN_COUNT, this, std::placeholders::_1));
elm->addToken(String(FPSTR("CONFIG_IP")), std::bind(&AutoConnect::_token_CONFIG_STAIP, this, std::placeholders::_1));
}
else if (uri == String(AUTOCONNECT_URI_CONNECT) && (_apConfig.menuItems & AC_MENUITEM_CONFIGNEW || _apConfig.menuItems & AC_MENUITEM_OPENSSIDS)) {
// Setup /_ac/connect
reqAuth = true;
_menuTitle = FPSTR(AUTOCONNECT_MENUTEXT_CONNECTING);
elm->setMold(_PAGE_CONNECTING);
elm->addToken(String(FPSTR("REQ")), std::bind(&AutoConnect::_induceConnect, this, std::placeholders::_1));
elm->addToken(String(FPSTR("HEAD")), std::bind(&AutoConnect::_token_HEAD, this, std::placeholders::_1));
elm->addToken(String(FPSTR("CSS_BASE")), std::bind(&AutoConnect::_token_CSS_BASE, this, std::placeholders::_1));
elm->addToken(String(FPSTR("CSS_SPINNER")), std::bind(&AutoConnect::_token_CSS_SPINNER, this, std::placeholders::_1));
elm->addToken(String(FPSTR("CSS_LUXBAR")), std::bind(&AutoConnect::_token_CSS_LUXBAR, this, std::placeholders::_1));
elm->addToken(String(FPSTR("MENU_PRE")), std::bind(&AutoConnect::_token_MENU_PRE, this, std::placeholders::_1));
elm->addToken(String(FPSTR("MENU_POST")), std::bind(&AutoConnect::_token_MENU_POST, this, std::placeholders::_1));
elm->addToken(String(FPSTR("CUR_SSID")), std::bind(&AutoConnect::_token_CURRENT_SSID, this, std::placeholders::_1));
}
else if (uri == String(AUTOCONNECT_URI_OPEN) && (_apConfig.menuItems & AC_MENUITEM_OPENSSIDS)) {
// Setup /_ac/open
reqAuth = true;
elm->setMold(_PAGE_OPENCREDT);
elm->addToken(String(FPSTR("HEAD")), std::bind(&AutoConnect::_token_HEAD, this, std::placeholders::_1));
elm->addToken(String(FPSTR("CSS_BASE")), std::bind(&AutoConnect::_token_CSS_BASE, this, std::placeholders::_1));
elm->addToken(String(FPSTR("CSS_ICON_LOCK")), std::bind(&AutoConnect::_token_CSS_ICON_LOCK, this, std::placeholders::_1));
elm->addToken(String(FPSTR("CSS_INPUT_BUTTON")), std::bind(&AutoConnect::_token_CSS_INPUT_BUTTON, this, std::placeholders::_1));
elm->addToken(String(FPSTR("CSS_LUXBAR")), std::bind(&AutoConnect::_token_CSS_LUXBAR, this, std::placeholders::_1));
elm->addToken(String(FPSTR("MENU_PRE")), std::bind(&AutoConnect::_token_MENU_PRE, this, std::placeholders::_1));
elm->addToken(String(FPSTR("MENU_AUX")), std::bind(&AutoConnect::_token_MENU_AUX, this, std::placeholders::_1));
elm->addToken(String(FPSTR("MENU_POST")), std::bind(&AutoConnect::_token_MENU_POST, this, std::placeholders::_1));
elm->addToken(String(FPSTR("OPEN_SSID")), std::bind(&AutoConnect::_token_OPEN_SSID, this, std::placeholders::_1));
}
else if (uri == String(AUTOCONNECT_URI_DISCON) && (_apConfig.menuItems & AC_MENUITEM_DISCONNECT)) {
// Setup /_ac/disc
_menuTitle = FPSTR(AUTOCONNECT_MENUTEXT_DISCONNECT);
elm->setMold(_PAGE_DISCONN);
elm->addToken(String(FPSTR("DISCONNECT")), std::bind(&AutoConnect::_induceDisconnect, this, std::placeholders::_1));
elm->addToken(String(FPSTR("HEAD")), std::bind(&AutoConnect::_token_HEAD, this, std::placeholders::_1));
elm->addToken(String(FPSTR("CSS_BASE")), std::bind(&AutoConnect::_token_CSS_BASE, this, std::placeholders::_1));
elm->addToken(String(FPSTR("CSS_LUXBAR")), std::bind(&AutoConnect::_token_CSS_LUXBAR, this, std::placeholders::_1));
elm->addToken(String(FPSTR("MENU_PRE")), std::bind(&AutoConnect::_token_MENU_PRE, this, std::placeholders::_1));
elm->addToken(String(FPSTR("MENU_POST")), std::bind(&AutoConnect::_token_MENU_POST, this, std::placeholders::_1));
}
else if (uri == String(AUTOCONNECT_URI_RESET) && (_apConfig.menuItems & AC_MENUITEM_RESET)) {
// Setup /_ac/reset
elm->setMold(_PAGE_RESETTING);
elm->addToken(String(FPSTR("HEAD")), std::bind(&AutoConnect::_token_HEAD, this, std::placeholders::_1));
elm->addToken(String(FPSTR("BOOTURI")), std::bind(&AutoConnect::_token_BOOTURI, this, std::placeholders::_1));
elm->addToken(String(FPSTR("UPTIME")), std::bind(&AutoConnect::_token_UPTIME, this, std::placeholders::_1));
elm->addToken(String(FPSTR("RESET")), std::bind(&AutoConnect::_induceReset, this, std::placeholders::_1));
}
else if (uri == String(AUTOCONNECT_URI_RESULT)) {
// Setup /_ac/result
elm->setMold("{{RESULT}}");
elm->addToken(String(FPSTR("RESULT")), std::bind(&AutoConnect::_invokeResult, this, std::placeholders::_1));
}
else if (uri == String(AUTOCONNECT_URI_SUCCESS)) {
// Setup /_ac/success
elm->setMold(_PAGE_SUCCESS);
elm->addToken(String(FPSTR("HEAD")), std::bind(&AutoConnect::_token_HEAD, this, std::placeholders::_1));
elm->addToken(String(FPSTR("CSS_BASE")), std::bind(&AutoConnect::_token_CSS_BASE, this, std::placeholders::_1));
elm->addToken(String(FPSTR("CSS_TABLE")), std::bind(&AutoConnect::_token_CSS_TABLE, this, std::placeholders::_1));
elm->addToken(String(FPSTR("CSS_LUXBAR")), std::bind(&AutoConnect::_token_CSS_LUXBAR, this, std::placeholders::_1));
elm->addToken(String(FPSTR("MENU_PRE")), std::bind(&AutoConnect::_token_MENU_PRE, this, std::placeholders::_1));
elm->addToken(String(FPSTR("MENU_AUX")), std::bind(&AutoConnect::_token_MENU_AUX, this, std::placeholders::_1));
elm->addToken(String(FPSTR("MENU_POST")), std::bind(&AutoConnect::_token_MENU_POST, this, std::placeholders::_1));
elm->addToken(String(FPSTR("ESTAB_SSID")), std::bind(&AutoConnect::_token_ESTAB_SSID, this, std::placeholders::_1));
elm->addToken(String(FPSTR("WIFI_MODE")), std::bind(&AutoConnect::_token_WIFI_MODE, this, std::placeholders::_1));
elm->addToken(String(FPSTR("WIFI_STATUS")), std::bind(&AutoConnect::_token_WIFI_STATUS, this, std::placeholders::_1));
elm->addToken(String(FPSTR("LOCAL_IP")), std::bind(&AutoConnect::_token_LOCAL_IP, this, std::placeholders::_1));
elm->addToken(String(FPSTR("GATEWAY")), std::bind(&AutoConnect::_token_GATEWAY, this, std::placeholders::_1));
elm->addToken(String(FPSTR("NETMASK")), std::bind(&AutoConnect::_token_NETMASK, this, std::placeholders::_1));
elm->addToken(String(FPSTR("CHANNEL")), std::bind(&AutoConnect::_token_CHANNEL, this, std::placeholders::_1));
elm->addToken(String(FPSTR("DBM")), std::bind(&AutoConnect::_token_DBM, this, std::placeholders::_1));
}
else if (uri == String(AUTOCONNECT_URI_FAIL)) {
// Setup /_ac/fail
_menuTitle = FPSTR(AUTOCONNECT_MENUTEXT_FAILED);
elm->setMold(_PAGE_FAIL);
elm->addToken(String(FPSTR("HEAD")), std::bind(&AutoConnect::_token_HEAD, this, std::placeholders::_1));
elm->addToken(String(FPSTR("CSS_BASE")), std::bind(&AutoConnect::_token_CSS_BASE, this, std::placeholders::_1));
elm->addToken(String(FPSTR("CSS_TABLE")), std::bind(&AutoConnect::_token_CSS_TABLE, this, std::placeholders::_1));
elm->addToken(String(FPSTR("CSS_LUXBAR")), std::bind(&AutoConnect::_token_CSS_LUXBAR, this, std::placeholders::_1));
elm->addToken(String(FPSTR("MENU_PRE")), std::bind(&AutoConnect::_token_MENU_PRE, this, std::placeholders::_1));
elm->addToken(String(FPSTR("MENU_AUX")), std::bind(&AutoConnect::_token_MENU_AUX, this, std::placeholders::_1));
elm->addToken(String(FPSTR("MENU_POST")), std::bind(&AutoConnect::_token_MENU_POST, this, std::placeholders::_1));
elm->addToken(String(FPSTR("STATION_STATUS")), std::bind(&AutoConnect::_token_STATION_STATUS, this, std::placeholders::_1));
}
else {
delete elm;
elm = nullptr;
}
// Restore the page transfer mode and the content build buffer
// reserved size corresponding to each URI defined in structure
// _pageBuildMode.
if (elm) {
for (uint8_t n = 0; n < sizeof(_pageBuildMode) / sizeof(PageTranserModeST); n++)
if (!strcmp(_pageBuildMode[n].uri, uri.c_str())) {
_responsePage->reserve(_pageBuildMode[n].rSize);
_responsePage->chunked(_pageBuildMode[n].transMode);
break;
}
// Regiter authentication
// Determine the necessity of authentication from the AutoConnectConfig settings
bool auth = (_apConfig.auth != AC_AUTH_NONE) &&
(_apConfig.authScope & AC_AUTHSCOPE_AC) &&
reqAuth;
_authentication(auth);
}
return elm;
}
/**
* Allow the page set upped to authenticate.
* The argument parameter indicates that authentication is allowed with
* the condition of the AutoConnect.authScope setting.
* It determines to admit authentication in the captive portal state
* when the AP_AUTHSCOPE_WITHCP is enabled.
* @param allow Indication of whether to authenticate with the page.
*/
void AutoConnect::_authentication(bool allow) {
HTTPAuthMethod method = _apConfig.auth == AC_AUTH_BASIC ? HTTPAuthMethod::BASIC_AUTH : HTTPAuthMethod::DIGEST_AUTH;
_authentication(allow, method);
}
/**
* Allow the page set upped to authenticate.
* The argument parameter indicates that authentication is allowed with
* the condition of the AutoConnect.authScope setting.
* It determines to admit authentication in the captive portal state
* when the AP_AUTHSCOPE_WITHCP is enabled.
* @param allow Indication of whether to authenticate with the page.
*/
void AutoConnect::_authentication(bool allow, const HTTPAuthMethod method) {
const char* user = nullptr;
const char* password = nullptr;
String fails;
// Enable authentication by setting of AC_AUTHSCOPE_DISCONNECTED even if WiFi is not connected.
if (WiFi.status() != WL_CONNECTED && (WiFi.getMode() & WIFI_AP)) {
String accUrl = _webServer->hostHeader();
if ((accUrl != WiFi.softAPIP().toString()) && !accUrl.endsWith(F(".local"))) {
if (!(_apConfig.authScope & AC_AUTHSCOPE_WITHCP))
allow = false;
}
}
if (allow) {
// Regiter authentication method
user = _apConfig.username.length() ? _apConfig.username.c_str() : _apConfig.apid.c_str();
password = _apConfig.password.length() ? _apConfig.password.c_str() : _apConfig.psk.c_str();
fails = String(FPSTR(AutoConnect::_ELM_HTML_HEAD)) + String(F("</head><body>" AUTOCONNECT_TEXT_AUTHFAILED "</body></html>"));
AC_DBG_DUMB(",%s+%s/%s", method == HTTPAuthMethod::BASIC_AUTH ? "BASIC" : "DIGEST", user, password);
}
_responsePage->authentication(user, password, method, AUTOCONNECT_AUTH_REALM, fails);
}
/**
* AutoConnect portal site web page declaration.
* @file AutoConnectPage.h
* @author hieromon@gmail.com
* @version 1.1.0
* @date 2019-10-11
* @copyright MIT license.
*/
#ifndef _AUTOCONNECTPAGE_H_
#define _AUTOCONNECTPAGE_H_
#include "AutoConnectLabels.h"
#define AUTOCONNECT_PARAMID_SSID "SSID"
#define AUTOCONNECT_PARAMID_PASS "Passphrase"
#define AUTOCONNECT_PARAMID_CRED "Credential"
#define AUTOCONNECT_PARAMID_DHCP "dhcp"
#define AUTOCONNECT_PARAMID_STAIP "sip"
#define AUTOCONNECT_PARAMID_GTWAY "gw"
#define AUTOCONNECT_PARAMID_NTMSK "nm"
#define AUTOCONNECT_PARAMID_DNS1 "ns1"
#define AUTOCONNECT_PARAMID_DNS2 "ns2"
// AutoConnect menu hyper-link as image
#define AUTOCONNECT_GLYPH_COG_16 "iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABmJLR0QA/wD/AP+gvaeTAAAA" \
"CXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH4wUQCDEa8dG0EwAAAfdJREFUOMuVUkFrE2EQ" \
"ffPtwhLqiqdCbhXDbuxasxD/QaEaMLtrwIMHvQjtQUT0ouBdxItYj3qTRtGDu98m9Eeo2NYS" \
"s6m5F4QiVLyk+42HJN24yaHObR5vZt7MPMKM8DwPAF0EaHuIKCtN0712uz3F1QAgCAJUq662" \
"sFAq9XrdgyRJYNvnXwJwhjQ602rFHwHA972lYvHKT9ctotvtggBgZWVNGMb+DhE5ALaY+T0R" \
"PZmcpBQ/EoJujpp+M024Gxuh0gGgUNhfZKbRNLhE5OalCkFPJ9Klw0M+C6AvACCKol1m7OLk" \
"8VnKqA8Aola7Phb5dpLBzGtShiRlSMx85991VBMAGo0GNMexyrZdXieie1mxWo1j+Wqc93rJ" \
"J9suHwCoAQARXbYse5E5TcjzAs7rkzKkPLa8fFXMzenp1G3yADPPXNo0/9AsXABwmPndGCAi" \
"1OvB7TxRqdMTd2AAeAMoS2gadeI4usHMj7MmeO15/mrmTP8ugBeZStyXMryVpukeZaRgC0Dl" \
"hG/cljJ0AQyd6PvXLGZO/sMHYFZOHMuOAABd//UjMxLvKJU+nFHzAMCXEeerYfS/H39hMDBV" \
"ofC7wswXpIwqrVb8DOBmVktNKcPnUoaXlFLn5udPVQeDEh+vkI96vQ4hdJuZu2O5R0dGZ3Pz" \
"wxT3LwiEzg76GcotAAAAAElFTkSuQmCC"
#define AUTOCONNECT_GLYPH_COG_24 "iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAC2klEQVRIS61VvWsUQRSfmU2p" \
"on9BUIkQUaKFaCBKgooSb2d3NSSFKbQR/KrEIiIKBiGF2CgRxEpjQNHs7mwOUcghwUQ7g58I" \
"sbGxEBWsb2f8zR177s3t3S2cA8ftzPu993vzvoaSnMu2vRKlaqgKp74Q/tE8qjQPyHGcrUrR" \
"jwlWShmDbFMURd/a6TcQwNiYUmpFCPElUebcuQ2vz6aNATMVReHEPwzfSSntDcNwNo2rI+Dc" \
"vQzhpAbA40VKyV0p1Q9snzBG1qYVcYufXV1sREraDcxpyHdXgkfpRBj6Uwm2RsC5dxxmZ9pd" \
"OY9cKTISRcHTCmGiUCh4fYyplTwG2mAUbtMTBMHXOgK9QfyXEZr+TkgQ1oUwDA40hEgfIAfj" \
"+HuQRaBzAs9eKyUZ5Htx+T3ZODKG8DzOJMANhmGomJVMXPll+hx9UUAlzZrJJ4QNCDG3VEfg" \
"uu7mcpmcB/gkBOtShhQhchAlu5jlLUgc9ENgyP5gf9+y6LTv+58p5zySkgwzLNOIGc8sEoT1" \
"Lc53NMlbCQQuvMxeCME1NNPVVkmH/i3IzzXDtCSA0qQQwZWOCJDY50jsQRjJmkslEOxvTcDR" \
"O6zPxOh5xZglKkYLhWM9jMVnkIsTyMT6NBj7IbOCEjm6HxNVVTo2WXqEWJZ1T8rytB6Gxizy" \
"DkPhWVpBqfiXUtbo/HywYJSpA9kMamNNPZ71R9Hcm+TMHHZNGw3EuraXEUldbfvw25UdOjqO" \
"t+JhMwJd7+jSTpZaEiIcaCDwPK83jtWnTkwnunFMtxeL/ge9r4XItt1RNNaj/0GAcV2bR3U5" \
"sG3nEh6M61US+Qrfd9Bs31GGulI2GOS/8dgcQZV1w+ApjIxB7TDwF9GcNzJzoA+rD0/8HvPn" \
"XQJCt2qFCwbBTfRI7UyXumWVt+HJ9NO4XI++bdsb0YyrqXmlh+AWOLHaLqS5CLQR5EggR3Yl" \
"cVS9gKeH2hnX8r8Kmi1CAsl36QAAAABJRU5ErkJggg=="
#define AUTOCONNECT_GLYPH_COG_32 "iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAETElEQVRYR61XX0hbVxg/5yYx" \
"QlEnzM46HYw+7KVuq826Vba1UIRmevNHltHCZEKn7g9lLzJwD8W+dAXZy6i2jaV92BRcMZpc" \
"g7Q+zLaTPXQb7E9toaCUwVbWOZlBk5jcc/Y7WCW5uffuXNvAJSH3933f73zn933nO5Rs4xMI" \
"BN4kRLlebMpeSiQSvzh1R50aCHwgEPoGX5FiWz6cSMS7nfpzTACrr8Pq7yOQ2xBsTddz9clk" \
"ctkJCVMCoVBoP2P8bcbcw1NT4/cKHapq8BSl9KRFkN5EYvKLwnfw9TJjrJtz90VNi/1ktCsh" \
"IILrOrtGqVIlwJyTWQS84PW6Y+l0ukZRXD8QQmstCCzmcp5X0+kdqaqq5Xdg+yGwrz3CLisK" \
"aZmcnPyx0LaIgKq2v0JIfmYzuCHIMlZSqYCBXYqBWQdmFZhqE1wJiSICwWBoEKw/crKH28B+" \
"jm36bNOuiAAE9iIE9vM2nMqacM713ZqmLZoSEH+2tYXmsFfNsh4d4q5i9UcsNfCoxt/F91cO" \
"HUvBKeXt8Xh8wpaA3+/3ejzePy1EJOoiw7lyjlJ9NJPJ3Flfr9MrKlYaFYV1oHp6IMAyczb8" \
"wcrKvw2zs7N5WwKqqj7HOV0wUztWcB8qb8Ue3jYLgipqArGkWZnCLo39f9bYqLZE6PdHatzu" \
"9S4EQe0q9cYAwoHLRX1I4bxdviFkH+y/B8bYKQlj5A/oK+p2K9FYLCayTCgazz68OIHfR/F4" \
"rZ3zAfT6T2U2W1XD57GQHissFpMDEaGFQYqDhcs4JUT+tAOB10HgpoxfWQK8tnanNxqN5mSc" \
"tra2Vrtcnn9ksFIEkDLm8zV5+vv7mYzTJ05ABIWC91ip30gqGAy+gUq6IUOWtrWFD6CGP8bx" \
"G7GuYUGAn9G0eJ+M00AgGIW+u2ywa/A4gufsVhmGw+Gd+Tx7H8w/gEIbTIzXOHft07Txu3Yk" \
"NmYJMmdWhhDyAkp0CIPLpc1+UDIPIH0Nus4XrRoRHL9l1QvEcU4p04B5xoQkjmi2C3NjyrYT" \
"RiKRsmw29wAgs/NcqCGD9A6hHY+kUpXzFRVL4Ko0Yovew+pE2ksakAgIHf9VV1dbb6ykkgyo" \
"augYpWRUZq+dYtAbjiJ7Y7YZAIFvQeCQU+cyeFEZmjZx0JIA+vgLSKOtyGQC2WGM5WwYycID" \
"2Mvexw1iT4B/iXL+ZBNjHMl8jCkzKMOnjE4goiX8J4ZSz/8QRI0ToXSzSvgbcjxceIMqEaE4" \
"TotJsO+g+KHycs94NputxrtbFn2CiHkhn8/vXV1dTVVWVgdQkj3Y9xaQQRz2EOP9YYjwV1sR" \
"ipcbZzrt1HXlfDI58VuhAU5P0Q1Pm2UBAfowcZ0pfIcB53no6jhIjxmDC5zjqxkcPo17w+8w" \
"LTeQyOJS0jA9feWhEw05JiCc4/5wGfeHzuJA7GvsbYeT4NvKgDDamP1Y0RWLMdo8NTUhRjFH" \
"n/8AoRLGHM6hJDMAAAAASUVORK5CYII="
#define AUTOCONNECT_GLYPH_BAR_24 "iVBORw0KGgoAAAANSUhEUgAAABgAAAAYBAMAAAASWSDLAAAALVBMVEUAAAAAAAAAAAAAAAAA" \
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD////BHg4sAAAADXRSTlMACA8YREhJg5mn" \
"u8rdcKICYgAAAAFiS0dEDm+9ME8AAABFSURBVBjTY2AgDjBWdEBBuwADy104cGBgQ3ASGJj3" \
"nIGC0wZEmszANGsVFKxUYGBFmBaAykFRRiRAcQ6KQ1G8gOI54gAAQlFeCYGJCTQAAAAASUVO" \
"RK5CYII="
#define AUTOCONNECT_GLYPH_BAR_32 "iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAApklEQVRYR2NkGGDAOMD2M4w6" \
"YDQEkEMgEJggZwCxGI0T5mug+alAvBFkD7IDXtLBcpjfXgEZ4ugOeAETpHEIgIwHeVYC3QH+" \
"0CgAS9AQgCwHRcFmdAfQ0E7cRo9mw0EVAqPlAKhwEKVTVsBZDsyiQ2k4Wg6gxPKgyoZ0Sn+o" \
"1iCHQBBQaiYQi9DYJTjbAyAJWluOtz0wWg7QOOqxGz+aDUdDYMBDAACA0x4hs/MPrwAAAABJ" \
"RU5ErkJggg=="
#define AUTOCONNECT_GLYPH_BAR_48 "iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAABIUlEQVRoQ+1YywrCQBCzF72J" \
"fqD6AYKKIPgLKiKKd/1DH0dPpgdBilvtTMp2JcJcLMnsJNtu2qyV+C9LfP0tDRDbQTkgB5wK" \
"aAs5BXTDiw50wLhGDVE9NzuX4A66M2qBeryoiwPscWHC7UtnO4BxGhrg0kDliwpc8Uf/bwfY" \
"YbIZ3XQuYb7GeciBNi6sUKN3m7j9zWz51jmhlmU3sZk9FlAHWSzlQ/dA7PVU7q8tVFkyMuBT" \
"FtqixwDVJffy0v2UhY7oMvZ2qhlfmoVuDVS+UhZKfoAU4vTXLLSBZ018oVEWqvnhYqPXSWzT" \
"jYeSAzwtbUzKQjbdXChlIZd8BHDphy1lIYLCIQploRrFtVPrJLZrx0HKAY6OdhY5YNeOg0ze" \
"gScMDDAxQXzA7QAAAABJRU5ErkJggg=="
// AutoConnect menu href
#define AUTOCONNECT_LINK(s) "<a href=\"" AUTOCONNECT_URI "\"><img src=\"data:image/png;base64," AUTOCONNECT_GLYPH_ ##s "\" border=\"0\" title=\"AutoConnect menu\" alt=\"AutoConnect menu\"/></a>"
#endif // _AutoConnectPage_H_
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment