Compare commits

...

58 Commits

Author SHA1 Message Date
Hieromon Ikasamo 73f21652bb Describe HTTP authentication 4 years ago
Hieromon Ikasamo 6b1539d551 Fixed an exception with end 4 years ago
Hieromon Ikasamo 26e7a91821 Adopted the required version of PageBuilder 4 years ago
Hieromon Ikasamo 0c81023981 Support LittleFS 4 years ago
Hieromon Ikasamo 3e7b7e0ecf Support LittleFS 4 years ago
Hieromon Ikasamo c8713433be Adds LittleFS description 4 years ago
Hieromon Ikasamo 31dcc2485b Support LittleFS 4 years ago
Hieromon Ikasamo 73b60487f9 Revised the preserveAPMode description 4 years ago
Hieromon Ikasamo a625255954 Revised the preserveAPMode description 4 years ago
Hieromon Ikasamo 5e47e27e39 Supplemented the preserveAPMode description 4 years ago
Hieromon Ikasamo 390a84ba28 Describes the preserveAPMode 4 years ago
Hieromon Ikasamo 9f81d93083 Merge branch 'enhance/v120' of https://github.com/Hieromon/AutoConnect into enhance/v120 4 years ago
Hieromon Ikasamo dacdade6c1 Describes for authentication. 4 years ago
Hieromon Ikasamo 61028083e6 Describe changing the partition. 4 years ago
Hieromon Ikasamo a2a100bc4c Fixed invalid AC_AUTHSCOPE_WITHCP mask #171 4 years ago
Hieromon Ikasamo 4424032c36 Abandon AC_AUTHSCOPE_EXCEPTCP, replace with AC_AUTHSCOPE_WITHCP #171 4 years ago
Hieromon Ikasamo 61f9859e1d Changed the default assumption. 4 years ago
Hieromon Ikasamo 8c5e5a49ea Added AC_AUTHSCOPE_EXCEPTCP with AC_AUTHSCOPE enum. 4 years ago
Hieromon Ikasamo 48bf5cfd1e Changed the log format of the requested URL. 4 years ago
Hieromon Ikasamo 140d1f8768 Adds the description for the authentication. 4 years ago
Hieromon Ikasamo 06982c4ec8 Changed paragraphs order 4 years ago
Hieromon Ikasamo 4397839b0d Changed required PageBuilder version 4 years ago
Hieromon Ikasamo 6020d04cad Minor improvements in wording 4 years ago
Hieromon Ikasamo 55c86de43f Minor improvements in wording 4 years ago
Hieromon Ikasamo b3fb86a025 Removed the id of li tag of menu items 4 years ago
Hieromon Ikasamo e55a8b64ea
Merge pull request #220 from Hieromon/pr/217 4 years ago
Hieromon Ikasamo e033463564
Merge pull request #217 from TKTF50/preserve-AP-Mode 4 years ago
Hieromon Ikasamo 9581d4cb0b Applied the append method 4 years ago
Hieromon Ikasamo 7675c6ebf6 Updated Arduino version for CI 4 years ago
Hieromon Ikasamo b5106af7e0 #issuecomment-626827941 4 years ago
Hieromon Ikasamo a3f826061e Avoid empty-body warning with AC_DEBUG not specified. #218 4 years ago
Hieromon Ikasamo 99bdedd660 Fixed wrong connection waiting time. #216 4 years ago
Hieromon Ikasamo 10ef58dccf Preserve WiFi mode #210 4 years ago
TKTF50 d7dd2153e7 Added preserveAPMode setting 4 years ago
Hieromon Ikasamo bef08e1be3 Changed return type of detach 4 years ago
Hieromon Ikasamo 91f1c0fcf1 Adds description of append, detach and onConnect 4 years ago
Hieromon Ikasamo abac1d0198 Changed name of addMenuItem to append 4 years ago
Hieromon Ikasamo 6d0e30d382 Typo fixes 4 years ago
Hieromon Ikasamo ba7d9fb066 Adds the addMenuItem and the release 4 years ago
Hieromon Ikasamo 45e887b00b Dropped unnecessary attribute 4 years ago
Hieromon Ikasamo 322f043b16 TYpo fixes 4 years ago
Hieromon Ikasamo 46783e7b8e Merge branch 'patches/v120' into enhance/v120 4 years ago
Hieromon Ikasamo fd01566292 Fixed signature mismatch 4 years ago
Hieromon Ikasamo f86ec77f48 Adds getEEPROMUsedSize description 4 years ago
Hieromon Ikasamo 3618841f13 Fixed Ticker discontinue at AutoConnect::begin ends 4 years ago
Hieromon Ikasamo 756c9c28ea Changed the comment to be legible #209 4 years ago
Hieromon Ikasamo 09170d7cac Changed place of AC_DBG literal to code block 4 years ago
Hieromon Ikasamo 23ee85418f Reduced redundant codes. 4 years ago
Hieromon Ikasamo 5716bdd0a0 Changed getEEPROMUsedSize #209 4 years ago
Hieromon Ikasamo 84d18dedae Added EEPROM.ino example #209 4 years ago
Hieromon Ikasamo b8b663d927 Added AutoConnectCredential size acquisition #209 4 years ago
Hieromon Ikasamo 8b18cd0de8 Merge branch 'master' into enhance/v120 4 years ago
Hieromon Ikasamo b6eec6cbac Supports Authentication 4 years ago
Hieromon Ikasamo 58117d8718 Typo fixes 4 years ago
Hieromon Ikasamo 922a07dd01 Bump version 4 years ago
Hieromon Ikasamo 23f2bc5473 Trimmed 4 years ago
Hieromon Ikasamo 2437d51d29 Supports a onConnect exit 4 years ago
Hieromon Ikasamo e9fda58180 Changed interface of the onDetect exit routine's parameter 4 years ago
  1. 11
      .gitignore
  2. 3
      .travis.yml
  3. 2
      README.md
  4. 163
      examples/EEPROM/EEPROM.ino
  5. 39
      examples/Elements/Elements.ino
  6. 832
      examples/FSBrowser/FSBrowser.ino
  7. 152
      examples/FSBrowser/README.md
  8. BIN
      examples/FSBrowser/data/edit.htm.gz
  9. 1128
      examples/FSBrowser/data/edit/index.htm
  10. BIN
      examples/FSBrowser/data/graphs.js.gz
  11. 103
      examples/FSBrowser/data/index.htm
  12. BIN
      examples/FSBrowser/data/pins.png
  13. BIN
      examples/FSBrowser/extras/feathericons.png
  14. BIN
      examples/FSBrowser/extras/index.htm.gz
  15. 529
      examples/FSBrowser/extras/index_htm.h
  16. 60
      examples/FSBrowser/extras/reduce_index.sh
  17. 21
      examples/FileUpload/FileUpload.ino
  18. 2
      examples/HandleClient/HandleClient.ino
  19. 2
      examples/HandlePortal/HandlePortal.ino
  20. 2
      examples/HandlePortalEX/HandlePortalEX.ino
  21. 25
      examples/HelloWorld/HelloWorld.ino
  22. 21
      examples/mqttRSSI/mqttRSSI.ino
  23. 25
      examples/mqttRSSI_FS/mqttRSSI_FS.ino
  24. 17
      examples/mqttRSSI_NA/mqttRSSI_NA.ino
  25. 4
      library.json
  26. 2
      library.properties
  27. 2
      mkdocs.yml
  28. 17
      mkdocs/acjson.md
  29. 437
      mkdocs/advancedusage.md
  30. 107
      mkdocs/api.md
  31. 17
      mkdocs/apiaux.md
  32. 61
      mkdocs/apiconfig.md
  33. 2
      mkdocs/credit.md
  34. 17
      mkdocs/faq.md
  35. BIN
      mkdocs/images/addmenu.gif
  36. BIN
      mkdocs/images/castmenu.png
  37. 428
      mkdocs/images/fsbrowser_ba.svg
  38. BIN
      mkdocs/images/partition.png
  39. 4
      mkdocs/index.md
  40. 2
      mkdocs/menu.md
  41. 34
      mkdocs/menuize.md
  42. 9
      mkdocs/otabrowser.md
  43. 2
      mkdocs/otaupdate.md
  44. 366
      src/AutoConnect.cpp
  45. 141
      src/AutoConnect.h
  46. 30
      src/AutoConnectAux.cpp
  47. 14
      src/AutoConnectAux.h
  48. 6
      src/AutoConnectCredential.cpp
  49. 5
      src/AutoConnectCredential.h
  50. 38
      src/AutoConnectDefs.h
  51. 2
      src/AutoConnectElement.h
  52. 16
      src/AutoConnectElementBasisImpl.h
  53. 7
      src/AutoConnectElementJson.h
  54. 2
      src/AutoConnectElementJsonImpl.h
  55. 7
      src/AutoConnectLabels.h
  56. 9
      src/AutoConnectOTA.cpp
  57. 1
      src/AutoConnectOTA.h
  58. 484
      src/AutoConnectPage.cpp
  59. 2
      src/AutoConnectTicker.cpp
  60. 65
      src/AutoConnectTypes.h
  61. 6
      src/AutoConnectUpdate.cpp
  62. 2
      src/AutoConnectUpdate.h
  63. 8
      src/AutoConnectUploadImpl.h

11
.gitignore vendored

@ -1,12 +1,9 @@
# Files generated by build
.vs/
__vm/
debug/
Release/
img/
*.tmp
*.TMP
[Dd]ebug/
[Rr]elease/
*.[Tt][Mm][Pp]
*.sln
*.vcxproj
*.vcxproj.filters
*.vcxproj*
*.vcxitems

@ -2,7 +2,7 @@ os: linux
language: generic
env:
global:
- IDE_VERSION=1.8.8
- IDE_VERSION=1.8.12
jobs:
- BOARD="esp8266:esp8266:generic:xtal=80,eesz=4M3M,ip=lm2f,exception=disabled,FlashMode=qio,FlashFreq=80" ARDJSON="5.13.5"
- BOARD="esp32:esp32:esp32:FlashFreq=80,FlashSize=4M" ARDJSON="5.13.5"
@ -33,6 +33,7 @@ script:
- buildExampleSketch ConfigIP
- buildExampleSketch Credential
- if [[ "$BOARD" =~ "esp32:esp32:" ]]; then buildExampleSketch CreditMigrate; fi
- if [[ "$BOARD" =~ "esp8266:esp8266:" ]]; then buildExampleSketch EEPROM; fi
- buildExampleSketch Elements
- buildExampleSketch FileUpload
- buildExampleSketch FSBrowser

@ -1,7 +1,7 @@
# AutoConnect for ESP8266/ESP32
[![arduino-library-badge](https://www.ardu-badge.com/badge/AutoConnect.svg?)](https://www.ardu-badge.com/AutoConnect)
[![GitHub release](https://img.shields.io/github/v/release/Hieromon/AutoConnect)](https://github.com/Hieromon/AutoConnect/releases)
[![arduino-library-badge](https://www.ardu-badge.com/badge/AutoConnect.svg?)](https://www.ardu-badge.com/AutoConnect)
[![Build Status](https://travis-ci.org/Hieromon/AutoConnect.svg?branch=master)](https://travis-ci.org/Hieromon/AutoConnect)
[![License](https://img.shields.io/github/license/Hieromon/AutoConnect)](https://github.com/Hieromon/AutoConnect/blob/master/LICENSE)

@ -0,0 +1,163 @@
/*
EEPROM.ino, Example for the AutoConnect library.
Copyright (c) 2020, Hieromon Ikasamo
https://github.com/Hieromon/AutoConnect
This software is released under the MIT License.
https://opensource.org/licenses/MIT
The AutoConnectConfig::boundaryOffset setting allows AutoConnect to
write its data to EEPROM while preserving custom configuration data.
Similarly, when a Sketch writes its own data to EEPROM, it must
preserve the data written by AutoConnect.
This example demonstrates how to use the getEEPROMUsedSize() method to
store custom configuration settings in EEPROM without conflicting with
AutoConnect's use of that storage. (Note: this applies to the ESP8266
only, not the ESP32.)
*/
#ifndef ARDUINO_ARCH_ESP8266
#error "EEPROM.ino sketch is available for ESP8266 only."
#endif
#include <ESP8266WiFi.h>
#include <ESP8266WebServer.h>
#include <EEPROM.h>
#include <AutoConnect.h>
ESP8266WebServer server;
AutoConnect portal(server);
AutoConnectConfig config;
const char AUX_EEPROM_IO[] PROGMEM = R"(
[
{
"title": "EEPROM",
"uri": "/",
"menu": true,
"element": [
{
"name": "data1",
"type": "ACInput",
"label": "DATA1",
"global": true
},
{
"name": "data2",
"type": "ACInput",
"label": "DATA2",
"global": true
},
{
"name": "data3",
"type": "ACInput",
"label": "DATA3",
"global": true
},
{
"name": "write",
"type": "ACSubmit",
"value": "WRITE",
"uri": "/eeprom"
}
]
},
{
"title": "EEPROM",
"uri": "/eeprom",
"menu": false,
"element": [
{
"name": "data1",
"type": "ACText",
"format": "DATA1: %s",
"posterior": "br",
"global": true
},
{
"name": "data2",
"type": "ACText",
"format": "DATA2: %s",
"posterior": "br",
"global": true
},
{
"name": "data3",
"type": "ACText",
"format": "DATA3: %s",
"posterior": "br",
"global": true
}
]
}
]
)";
// Defines the custom data should be stored in EEPROM.
typedef struct {
char data1[8];
char data2[8];
char data3[8];
} EEPROM_CONFIG_t;
String toString(char* c, uint8_t length) {
String r;
while (length-- && *c) {
r += (isAlphaNumeric(*c) ? String(*c) : String(*c, HEX));
c++;
}
return r;
}
// Read from EEPROM
String onEEPROM(AutoConnectAux& page, PageArgument& args) {
EEPROM_CONFIG_t eepromConfig;
EEPROM.begin(sizeof(eepromConfig));
EEPROM.get<EEPROM_CONFIG_t>(0, eepromConfig);
EEPROM.end();
page["data1"].value = toString(eepromConfig.data1, sizeof(EEPROM_CONFIG_t::data1));
page["data2"].value = toString(eepromConfig.data2, sizeof(EEPROM_CONFIG_t::data2));
page["data3"].value = toString(eepromConfig.data3, sizeof(EEPROM_CONFIG_t::data3));
return String();
}
// Write to EEPROM
String onEEPROMWrite(AutoConnectAux& page, PageArgument& args) {
EEPROM_CONFIG_t eepromConfig;
strncpy(eepromConfig.data1, page["data1"].value.c_str(), sizeof(EEPROM_CONFIG_t::data1));
strncpy(eepromConfig.data2, page["data2"].value.c_str(), sizeof(EEPROM_CONFIG_t::data2));
strncpy(eepromConfig.data3, page["data3"].value.c_str(), sizeof(EEPROM_CONFIG_t::data3));
// The actual area size of the EEPROM region to be given to
// EEPROM.begin is the sum of the size of the own custom data and
// the size of the currently stored AutoConnect credentials.
// eg.
// EEPROM.begin(portal.getEEPROMUsedSize())
EEPROM.begin(portal.getEEPROMUsedSize());
EEPROM.put<EEPROM_CONFIG_t>(0, eepromConfig);
EEPROM.commit();
EEPROM.end();
return String();
}
void setup() {
delay(1000);
Serial.begin(115200);
Serial.println();
portal.load(FPSTR(AUX_EEPROM_IO));
portal.on("/", onEEPROM);
portal.on("/eeprom", onEEPROMWrite);
// Specifies to shift the AutoConnect credentials with the custom data region size.
config.boundaryOffset = sizeof(EEPROM_CONFIG_t);
portal.config(config);
portal.begin();
}
void loop() {
portal.handleClient();
}

@ -20,10 +20,25 @@ using WebServerClass = ESP8266WebServer;
#include <SPIFFS.h>
using WebServerClass = WebServer;
#endif
#include <FS.h>
#include <AutoConnect.h>
/*
AC_USE_SPIFFS indicates SPIFFS or LittleFS as available file systems that
will become the AUTOCONNECT_USE_SPIFFS identifier and is exported as showng
the valid file system. After including AutoConnect.h, the Sketch can determine
whether to use FS.h or LittleFS.h by AUTOCONNECT_USE_SPIFFS definition.
*/
#ifdef AUTOCONNECT_USE_SPIFFS
#include <FS.h>
FS& FlashFS = SPIFFS;
#else
#include <LittleFS.h>
FS& FlashFS = LittleFS;
#endif
#define PARAM_FILE "/elements.json"
#define USERNAME "username_you_wish" // For HTTP authentication
#define PASSWORD "password_you_wish" // For HTTP authentication
static const char PAGE_ELEMENTS[] PROGMEM = R"(
{
@ -156,13 +171,13 @@ void setup() {
// Since this handler only supports AutoConnectSubmit called the
// Load, it uses the uri of the custom web page placed to
// determine whether the Load was called me or not.
SPIFFS.begin();
File param = SPIFFS.open(PARAM_FILE, "r");
FlashFS.begin();
File param = FlashFS.open(PARAM_FILE, "r");
if (param) {
aux.loadElement(param, { "text", "check", "input", "radio", "select" } );
param.close();
}
SPIFFS.end();
FlashFS.end();
}
return String();
});
@ -181,28 +196,32 @@ void setup() {
aux["caption"].value = PARAM_FILE;
#if defined(ARDUINO_ARCH_ESP8266)
SPIFFS.begin();
FlashFS.begin();
#elif defined(ARDUINO_ARCH_ESP32)
SPIFFS.begin(true);
FlashFS.begin(true);
#endif
File param = SPIFFS.open(PARAM_FILE, "w");
File param = FlashFS.open(PARAM_FILE, "w");
if (param) {
// Save as a loadable set for parameters.
elementsAux.saveElement(param, { "text", "check", "input", "radio", "select" });
param.close();
// Read the saved elements again to display.
param = SPIFFS.open(PARAM_FILE, "r");
param = FlashFS.open(PARAM_FILE, "r");
aux["echo"].value = param.readString();
param.close();
}
else {
aux["echo"].value = "SPIFFS failed to open.";
aux["echo"].value = "Filesystem failed to open.";
}
SPIFFS.end();
FlashFS.end();
return String();
});
portal.join({ elementsAux, saveAux });
config.auth = AC_AUTH_DIGEST;
config.authScope = AC_AUTHSCOPE_AUX;
config.username = USERNAME;
config.password = PASSWORD;
config.ticker = true;
portal.config(config);
portal.begin();

@ -1,356 +1,668 @@
/*
FSWebServer - Example WebServer with SPIFFS backend for esp8266
/*
FSBrowser - A web-based FileSystem Browser for ESP8266 filesystems
Copyright (c) 2015 Hristo Gochkov. All rights reserved.
This file is part of the ESP8266WebServer library for Arduino environment.
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
upload the contents of the data folder with MkSPIFFS Tool ("ESP8266 Sketch Data Upload" in Tools menu in Arduino IDE)
or you can upload the contents of a folder if you CD in that folder and run the following command:
for file in `\ls -A1`; do curl -F "file=@$PWD/$file" esp8266fs.local/edit; done
access the sample web page at http://esp8266fs.local
edit the page by going to http://esp8266fs.local/edit
See readme.md for more information.
*/
////////////////////////////////
// Select the FileSystem by uncommenting one of the lines below
//#define USE_SPIFFS
#define USE_LITTLEFS
//#define USE_SDFS
// Uncomment the following line to embed a version of the web page in the code
// (program code will be larger, but no file will have to be written to the filesystem).
// Note: the source file "extras/index_htm.h" must have been generated by "extras/reduce_index.sh"
//#define INCLUDE_FALLBACK_INDEX_HTM
////////////////////////////////
#if defined(ARDUINO_ARCH_ESP8266)
#include <ESP8266WiFi.h>
#include <WiFiClient.h>
#include <ESP8266WebServer.h>
#include <ESP8266mDNS.h>
#include <FS.h>
#elif defined(ARDUINO_ARCH_ESP32)
#include <WiFi.h>
#include <WiFiClient.h>
#include <WebServer.h>
#include <ESPmDNS.h>
#include <SPIFFS.h>
#endif
//Add a below line for AutoConnect.
#include <WiFiClient.h>
#include <SPI.h>
#include <AutoConnect.h>
#ifdef INCLUDE_FALLBACK_INDEX_HTM
#include "extras/index_htm.h"
#endif
#if defined USE_SPIFFS
#include <FS.h>
const char* fsName = "SPIFFS";
FS* fileSystem = &SPIFFS;
SPIFFSConfig fileSystemConfig = SPIFFSConfig();
#elif defined USE_LITTLEFS
#include <LittleFS.h>
const char* fsName = "LittleFS";
FS* fileSystem = &LittleFS;
LittleFSConfig fileSystemConfig = LittleFSConfig();
#elif defined USE_SDFS
#include <SDFS.h>
const char* fsName = "SDFS";
FS* fileSystem = &SDFS;
SDFSConfig fileSystemConfig = SDFSConfig();
// fileSystemConfig.setCSPin(chipSelectPin);
#else
#error Please select a filesystem first by uncommenting one of the "#define USE_xxx" lines at the beginning of the sketch.
#endif
#define DBG_OUTPUT_PORT Serial
const char* ssid = "wifi-ssid";
const char* password = "wifi-password";
#if defined(ARDUINO_ARCH_ESP8266)
const char* host = "esp8266fs";
// Exclude unnecessary declarations due to applying AutoConnect
// #ifndef STASSID
// #define STASSID "SHAP-G"
// #define STAPSK "A0309T0312#"
// #endif
// const char* ssid = STASSID;
// const char* password = STAPSK;
const char* host = "fsbrowser";
ESP8266WebServer server(80);
#elif defined(ARDUINO_ARCH_ESP32)
const char* host = "esp32fs";
WebServer server(80);
#endif
//Add a below line for AutoConnect.
static bool fsOK;
String unsupportedFiles = String();
File uploadFile;
// Additional lines as the below to apply AutoConnect
AutoConnect portal(server);
AutoConnectConfig config;
AutoConnectAux FSBedit("/edit", "Edit");
AutoConnectAux FSBlist("/list?dir=\"/\"", "List");
//holds the current upload
File fsUploadFile;
//format bytes
String formatBytes(size_t bytes){
if (bytes < 1024) {
return String(bytes) + "B";
} else if (bytes < (1024 * 1024)) {
return String(bytes / 1024.0) + "KB";
} else if (bytes < (1024 * 1024 * 1024)) {
return String(bytes / 1024.0 / 1024.0) + "MB";
static const char TEXT_PLAIN[] PROGMEM = "text/plain";
static const char FS_INIT_ERROR[] PROGMEM = "FS INIT ERROR";
static const char FILE_NOT_FOUND[] PROGMEM = "FileNotFound";
////////////////////////////////
// Utils to return HTTP codes, and determine content-type
void replyOK() {
server.send(200, FPSTR(TEXT_PLAIN), "");
}
void replyOKWithMsg(String msg) {
server.send(200, FPSTR(TEXT_PLAIN), msg);
}
void replyNotFound(String msg) {
server.send(404, FPSTR(TEXT_PLAIN), msg);
}
void replyBadRequest(String msg) {
DBG_OUTPUT_PORT.println(msg);
server.send(400, FPSTR(TEXT_PLAIN), msg + "\r\n");
}
void replyServerError(String msg) {
DBG_OUTPUT_PORT.println(msg);
server.send(500, FPSTR(TEXT_PLAIN), msg + "\r\n");
}
#ifdef USE_SPIFFS
/*
Checks filename for character combinations that are not supported by FSBrowser (alhtough valid on SPIFFS).
Returns an empty String if supported, or detail of error(s) if unsupported
*/
String checkForUnsupportedPath(String filename) {
String error = String();
if (!filename.startsWith("/")) {
error += F("!NO_LEADING_SLASH! ");
}
if (filename.indexOf("//") != -1) {
error += F("!DOUBLE_SLASH! ");
}
if (filename.endsWith("/")) {
error += F("!TRAILING_SLASH! ");
}
return error;
}
#endif
////////////////////////////////
// Request handlers
/*
Return the FS type, status and size info
*/
void handleStatus() {
DBG_OUTPUT_PORT.println("handleStatus");
FSInfo fs_info;
String json;
json.reserve(128);
json = "{\"type\":\"";
json += fsName;
json += "\", \"isOk\":";
if (fsOK) {
fileSystem->info(fs_info);
json += F("\"true\", \"totalBytes\":\"");
json += fs_info.totalBytes;
json += F("\", \"usedBytes\":\"");
json += fs_info.usedBytes;
json += "\"";
} else {
return String(bytes / 1024.0 / 1024.0 / 1024.0) + "GB";
json += "\"false\"";
}
json += F(",\"unsupportedFiles\":\"");
json += unsupportedFiles;
json += "\"}";
server.send(200, "application/json", json);
}
String getContentType(String filename) {
if (server.hasArg("download")) {
return "application/octet-stream";
} else if (filename.endsWith(".htm")) {
return "text/html";
} else if (filename.endsWith(".html")) {
return "text/html";
} else if (filename.endsWith(".css")) {
return "text/css";
} else if (filename.endsWith(".js")) {
return "application/javascript";
} else if (filename.endsWith(".png")) {
return "image/png";
} else if (filename.endsWith(".gif")) {
return "image/gif";
} else if (filename.endsWith(".jpg")) {
return "image/jpeg";
} else if (filename.endsWith(".ico")) {
return "image/x-icon";
} else if (filename.endsWith(".xml")) {
return "text/xml";
} else if (filename.endsWith(".pdf")) {
return "application/x-pdf";
} else if (filename.endsWith(".zip")) {
return "application/x-zip";
} else if (filename.endsWith(".gz")) {
return "application/x-gzip";
}
return "text/plain";
/*
Return the list of files in the directory specified by the "dir" query string parameter.
Also demonstrates the use of chuncked responses.
*/
void handleFileList() {
if (!fsOK) {
return replyServerError(FPSTR(FS_INIT_ERROR));
}
if (!server.hasArg("dir")) {
return replyBadRequest(F("DIR ARG MISSING"));
}
String path = server.arg("dir");
if (path != "/" && !fileSystem->exists(path)) {
return replyBadRequest("BAD PATH");
}
DBG_OUTPUT_PORT.println(String("handleFileList: ") + path);
Dir dir = fileSystem->openDir(path);
path.clear();
// use HTTP/1.1 Chunked response to avoid building a huge temporary string
if (!server.chunkedResponseModeStart(200, "text/json")) {
server.send(505, F("text/html"), F("HTTP1.1 required"));
return;
}
// use the same string for every line
String output;
output.reserve(64);
while (dir.next()) {
#ifdef USE_SPIFFS
String error = checkForUnsupportedPath(dir.fileName());
if (error.length() > 0) {
DBG_OUTPUT_PORT.println(String("Ignoring ") + error + dir.fileName());
continue;
}
#endif
if (output.length()) {
// send string from previous iteration
// as an HTTP chunk
server.sendContent(output);
output = ',';
} else {
output = '[';
}
output += "{\"type\":\"";
if (dir.isDirectory()) {
output += "dir";
} else {
output += F("file\",\"size\":\"");
output += dir.fileSize();
}
output += F("\",\"name\":\"");
// Always return names without leading "/"
if (dir.fileName()[0] == '/') {
output += &(dir.fileName()[1]);
} else {
output += dir.fileName();
}
output += "\"}";
}
// send last string
output += "]";
server.sendContent(output);
server.chunkedResponseFinalize();
}
/*
Read the given file from the filesystem and stream it back to the client
*/
bool handleFileRead(String path) {
DBG_OUTPUT_PORT.println("handleFileRead: " + path);
DBG_OUTPUT_PORT.println(String("handleFileRead: ") + path);
if (!fsOK) {
replyServerError(FPSTR(FS_INIT_ERROR));
return true;
}
if (path.endsWith("/")) {
path += "index.htm";
}
String contentType = getContentType(path);
String pathWithGz = path + ".gz";
if (SPIFFS.exists(pathWithGz) || SPIFFS.exists(path)) {
if (SPIFFS.exists(pathWithGz)) {
path += ".gz";
String contentType;
if (server.hasArg("download")) {
contentType = F("application/octet-stream");
} else {
contentType = mime::getContentType(path);
}
if (!fileSystem->exists(path)) {
// File not found, try gzip version
path = path + ".gz";
}
if (fileSystem->exists(path)) {
File file = fileSystem->open(path, "r");
if (server.streamFile(file, contentType) != file.size()) {
DBG_OUTPUT_PORT.println("Sent less data than expected!");
}
File file = SPIFFS.open(path, "r");
server.streamFile(file, contentType);
file.close();
return true;
}
return false;
}
/*
As some FS (e.g. LittleFS) delete the parent folder when the last child has been removed,
return the path of the closest parent still existing
*/
String lastExistingParent(String path) {
while (!path.isEmpty() && !fileSystem->exists(path)) {
if (path.lastIndexOf('/') > 0) {
path = path.substring(0, path.lastIndexOf('/'));
} else {
path = String(); // No slash => the top folder does not exist
}
}
DBG_OUTPUT_PORT.println(String("Last existing parent: ") + path);
return path;
}
/*
Handle the creation/rename of a new file
Operation | req.responseText
---------------+--------------------------------------------------------------
Create file | parent of created file
Create folder | parent of created folder
Rename file | parent of source file
Move file | parent of source file, or remaining ancestor
Rename folder | parent of source folder
Move folder | parent of source folder, or remaining ancestor
*/
void handleFileCreate() {
if (!fsOK) {
return replyServerError(FPSTR(FS_INIT_ERROR));
}
String path = server.arg("path");
if (path.isEmpty()) {
return replyBadRequest(F("PATH ARG MISSING"));
}
#ifdef USE_SPIFFS
if (checkForUnsupportedPath(path).length() > 0) {
return replyServerError(F("INVALID FILENAME"));
}
#endif
if (path == "/") {
return replyBadRequest("BAD PATH");
}
if (fileSystem->exists(path)) {
return replyBadRequest(F("PATH FILE EXISTS"));
}
String src = server.arg("src");
if (src.isEmpty()) {
// No source specified: creation
DBG_OUTPUT_PORT.println(String("handleFileCreate: ") + path);
if (path.endsWith("/")) {
// Create a folder
path.remove(path.length() - 1);
if (!fileSystem->mkdir(path)) {
return replyServerError(F("MKDIR FAILED"));
}
} else {
// Create a file
File file = fileSystem->open(path, "w");
if (file) {
file.write((const char *)0);
file.close();
} else {
return replyServerError(F("CREATE FAILED"));
}
}
if (path.lastIndexOf('/') > -1) {
path = path.substring(0, path.lastIndexOf('/'));
}
replyOKWithMsg(path);
} else {
// Source specified: rename
if (src == "/") {
return replyBadRequest("BAD SRC");
}
if (!fileSystem->exists(src)) {
return replyBadRequest(F("SRC FILE NOT FOUND"));
}
DBG_OUTPUT_PORT.println(String("handleFileCreate: ") + path + " from " + src);
if (path.endsWith("/")) {
path.remove(path.length() - 1);
}
if (src.endsWith("/")) {
src.remove(src.length() - 1);
}
if (!fileSystem->rename(src, path)) {
return replyServerError(F("RENAME FAILED"));
}
replyOKWithMsg(lastExistingParent(src));
}
}
/*
Delete the file or folder designed by the given path.
If it's a file, delete it.
If it's a folder, delete all nested contents first then the folder itself
IMPORTANT NOTE: using recursion is generally not recommended on embedded devices and can lead to crashes (stack overflow errors).
This use is just for demonstration purpose, and FSBrowser might crash in case of deeply nested filesystems.
Please don't do this on a production system.
*/
void deleteRecursive(String path) {
File file = fileSystem->open(path, "r");
bool isDir = file.isDirectory();
file.close();
// If it's a plain file, delete it
if (!isDir) {
fileSystem->remove(path);
return;
}
// Otherwise delete its contents first
Dir dir = fileSystem->openDir(path);
while (dir.next()) {
deleteRecursive(path + '/' + dir.fileName());
}
// Then delete the folder itself
fileSystem->rmdir(path);
}
/*
Handle a file deletion request
Operation | req.responseText
---------------+--------------------------------------------------------------
Delete file | parent of deleted file, or remaining ancestor
Delete folder | parent of deleted folder, or remaining ancestor
*/
void handleFileDelete() {
if (!fsOK) {
return replyServerError(FPSTR(FS_INIT_ERROR));
}
String path = server.arg(0);
if (path.isEmpty() || path == "/") {
return replyBadRequest("BAD PATH");
}
DBG_OUTPUT_PORT.println(String("handleFileDelete: ") + path);
if (!fileSystem->exists(path)) {
return replyNotFound(FPSTR(FILE_NOT_FOUND));
}
deleteRecursive(path);
replyOKWithMsg(lastExistingParent(path));
}
/*
Handle a file upload request
*/
void handleFileUpload() {
if (!fsOK) {
return replyServerError(FPSTR(FS_INIT_ERROR));
}
if (server.uri() != "/edit") {
return;
}
HTTPUpload& upload = server.upload();
if (upload.status == UPLOAD_FILE_START) {
String filename = upload.filename;
// Make sure paths always start with "/"
if (!filename.startsWith("/")) {
filename = "/" + filename;
}
DBG_OUTPUT_PORT.print("handleFileUpload Name: "); DBG_OUTPUT_PORT.println(filename);
fsUploadFile = SPIFFS.open(filename, "w");
filename = String();
DBG_OUTPUT_PORT.println(String("handleFileUpload Name: ") + filename);
uploadFile = fileSystem->open(filename, "w");
if (!uploadFile) {
return replyServerError(F("CREATE FAILED"));
}
DBG_OUTPUT_PORT.println(String("Upload: START, filename: ") + filename);
} else if (upload.status == UPLOAD_FILE_WRITE) {
//DBG_OUTPUT_PORT.print("handleFileUpload Data: "); DBG_OUTPUT_PORT.println(upload.currentSize);
if (fsUploadFile) {
fsUploadFile.write(upload.buf, upload.currentSize);
if (uploadFile) {
size_t bytesWritten = uploadFile.write(upload.buf, upload.currentSize);
if (bytesWritten != upload.currentSize) {
return replyServerError(F("WRITE FAILED"));
}
}
DBG_OUTPUT_PORT.println(String("Upload: WRITE, Bytes: ") + upload.currentSize);
} else if (upload.status == UPLOAD_FILE_END) {
if (fsUploadFile) {
fsUploadFile.close();
if (uploadFile) {
uploadFile.close();
}
DBG_OUTPUT_PORT.print("handleFileUpload Size: "); DBG_OUTPUT_PORT.println(upload.totalSize);
DBG_OUTPUT_PORT.println(String("Upload: END, Size: ") + upload.totalSize);
}
}
void handleFileDelete() {
if (server.args() == 0) {
return server.send(500, "text/plain", "BAD ARGS");
}
String path = server.arg(0);
DBG_OUTPUT_PORT.println("handleFileDelete: " + path);
if (path == "/") {
return server.send(500, "text/plain", "BAD PATH");
}
if (!SPIFFS.exists(path)) {
return server.send(404, "text/plain", "FileNotFound");
}
SPIFFS.remove(path);
server.send(200, "text/plain", "");
path = String();
}
void handleFileCreate() {
if (server.args() == 0) {
return server.send(500, "text/plain", "BAD ARGS");
}
String path = server.arg(0);
DBG_OUTPUT_PORT.println("handleFileCreate: " + path);
if (path == "/") {
return server.send(500, "text/plain", "BAD PATH");
/*
The "Not Found" handler catches all URI not explicitely declared in code
First try to find and return the requested file from the filesystem,
and if it fails, return a 404 page with debug information
*/
void handleNotFound() {
if (!fsOK) {
return replyServerError(FPSTR(FS_INIT_ERROR));
}
if (SPIFFS.exists(path)) {
return server.send(500, "text/plain", "FILE EXISTS");
String uri = ESP8266WebServer::urlDecode(server.uri()); // required to read paths with blanks
if (handleFileRead(uri)) {
return;
}
File file = SPIFFS.open(path, "w");
if (file) {
file.close();
} else {
return server.send(500, "text/plain", "CREATE FAILED");
// Dump debug data
String message;
message.reserve(100);
message = F("Error: File not found\n\nURI: ");
message += uri;
message += F("\nMethod: ");
message += (server.method() == HTTP_GET) ? "GET" : "POST";
message += F("\nArguments: ");
message += server.args();
message += '\n';
for (uint8_t i = 0; i < server.args(); i++) {
message += F(" NAME:");
message += server.argName(i);
message += F("\n VALUE:");
message += server.arg(i);
message += '\n';
}
server.send(200, "text/plain", "");
path = String();
message += "path=";
message += server.arg("path");
message += '\n';
DBG_OUTPUT_PORT.print(message);
return replyNotFound(message);
}
void handleFileList() {
if (!server.hasArg("dir")) {
server.send(500, "text/plain", "BAD ARGS");
/*
This specific handler returns the index.htm (or a gzipped version) from the /edit folder.
If the file is not present but the flag INCLUDE_FALLBACK_INDEX_HTM has been set, falls back to the version
embedded in the program code.
Otherwise, fails with a 404 page with debug information
*/
void handleGetEdit() {
if (handleFileRead(F("/edit/index.htm"))) {
return;
}
String path = server.arg("dir");
DBG_OUTPUT_PORT.println("handleFileList: " + path);
#if defined(ARDUINO_ARCH_ESP8266)
Dir dir = SPIFFS.openDir(path);
#elif defined(ARDUINO_ARCH_ESP32)
File root = SPIFFS.open(path);
#endif
path = String();
String output = "[";
#if defined(ARDUINO_ARCH_ESP8266)
while (dir.next()) {
File entry = dir.openFile("r");
if (output != "[") {
output += ',';
}
bool isDir = false;
output += "{\"type\":\"";
output += (isDir) ? "dir" : "file";
output += "\",\"name\":\"";
output += String(entry.name()).substring(1);
output += "\"}";
entry.close();
}
#elif defined(ARDUINO_ARCH_ESP32)
if(root.isDirectory()){
File file = root.openNextFile();
while(file){
if (output != "[") {
output += ',';
}
output += "{\"type\":\"";
output += (file.isDirectory()) ? "dir" : "file";
output += "\",\"name\":\"";
output += String(file.name()).substring(1);
output += "\"}";
file = root.openNextFile();
}
}
#ifdef INCLUDE_FALLBACK_INDEX_HTM
server.sendHeader(F("Content-Encoding"), "gzip");
server.send(200, "text/html", index_htm_gz, index_htm_gz_len);
#else
replyNotFound(FPSTR(FILE_NOT_FOUND));
#endif
output += "]";
server.send(200, "text/json", output);
}
void setup(void){
void setup(void) {
////////////////////////////////
// SERIAL INIT
DBG_OUTPUT_PORT.begin(115200);
DBG_OUTPUT_PORT.print("\n");
DBG_OUTPUT_PORT.setDebugOutput(true);
SPIFFS.begin();
{
#if defined(ARDUINO_ARCH_ESP8266)
Dir dir = SPIFFS.openDir("/");
while (dir.next()) {
String fileName = dir.fileName();
size_t fileSize = dir.fileSize();
DBG_OUTPUT_PORT.printf("FS File: %s, size: %s\n", fileName.c_str(), formatBytes(fileSize).c_str());
}
#elif defined(ARDUINO_ARCH_ESP32)
File root = SPIFFS.open("/");
File file = root.openNextFile();
while(file){
String fileName = file.name();
size_t fileSize = file.size();
DBG_OUTPUT_PORT.printf("FS File: %s, size: %s\n", fileName.c_str(), formatBytes(fileSize).c_str());
file = root.openNextFile();
DBG_OUTPUT_PORT.print('\n');
////////////////////////////////
// FILESYSTEM INIT
fileSystemConfig.setAutoFormat(false);
fileSystem->setConfig(fileSystemConfig);
fsOK = fileSystem->begin();
DBG_OUTPUT_PORT.println(fsOK ? F("Filesystem initialized.") : F("Filesystem init failed!"));
#ifdef USE_SPIFFS
// Debug: dump on console contents of filessytem with no filter and check filenames validity
Dir dir = fileSystem->openDir("");
DBG_OUTPUT_PORT.println(F("List of files at root of filesystem:"));
while (dir.next()) {
String error = checkForUnsupportedPath(dir.fileName());
String fileInfo = dir.fileName() + (dir.isDirectory() ? " [DIR]" : String(" (") + dir.fileSize() + "b)");
DBG_OUTPUT_PORT.println(error + fileInfo);
if (error.length() > 0) {
unsupportedFiles += error + fileInfo + '\n';
}
}
DBG_OUTPUT_PORT.println();
// Keep the "unsupportedFiles" variable to show it, but clean it up
unsupportedFiles.replace("\n", "<br/>");
unsupportedFiles = unsupportedFiles.substring(0, unsupportedFiles.length() - 5);
#endif
DBG_OUTPUT_PORT.printf("\n");
}
//WIFI INIT
DBG_OUTPUT_PORT.printf("Connecting to %s\n", ssid);
//Comment out as follows to make AutoConnect recognition.
//if (String(WiFi.SSID()) != String(ssid)) {
// WiFi.mode(WIFI_STA);
// WiFi.begin(ssid, password);
//}
//while (WiFi.status() != WL_CONNECTED) {
// delay(500);
// DBG_OUTPUT_PORT.print(".");
//}
//SERVER INIT
//list directory
// With applying AutoConnect, making WiFi connection is not necessary.
// WI-FI INIT
// DBG_OUTPUT_PORT.printf("Connecting to %s\n", ssid);
// WiFi.mode(WIFI_STA);
// WiFi.begin(ssid, password);
// // Wait for connection
// while (WiFi.status() != WL_CONNECTED) {
// delay(500);
// DBG_OUTPUT_PORT.print(".");
// }
// DBG_OUTPUT_PORT.println("");
// DBG_OUTPUT_PORT.print(F("Connected! IP address: "));
// DBG_OUTPUT_PORT.println(WiFi.localIP());
////////////////////////////////
// WEB SERVER INIT
// Filesystem status
server.on("/status", HTTP_GET, handleStatus);
// List directory
server.on("/list", HTTP_GET, handleFileList);
//load editor
server.on("/edit", HTTP_GET, []() {
if (!handleFileRead("/edit.htm")) {
server.send(404, "text/plain", "FileNotFound");
}
});
//create file
server.on("/edit", HTTP_PUT, handleFileCreate);
//delete file
server.on("/edit", HTTP_DELETE, handleFileDelete);
//first callback is called after the request has ended with all parsed arguments
//second callback handles file uploads at that location
server.on("/edit", HTTP_POST, []() {
server.send(200, "text/plain", "");
}, handleFileUpload);
//called when the url is not defined here
//use it to load content from SPIFFS
//Replacement as follows to make AutoConnect recognition.
//server.onNotFound([](){
portal.onNotFound([](){
if(!handleFileRead(server.uri()))
server.send(404, "text/plain", "FileNotFound");
});
//get heap status, analog input value and all GPIO statuses in one json call
server.on("/all", HTTP_GET, [](){
String json = "{";
json += "\"heap\":"+String(ESP.getFreeHeap());
json += ", \"analog\":"+String(analogRead(A0));
#if defined(ARDUINO_ARCH_ESP8266)
json += ", \"gpio\":"+String((uint32_t)(((GPI | GPO) & 0xFFFF) | ((GP16I & 0x01) << 16)));
#elif defined(ARDUINO_ARCH_ESP32)
json += ", \"gpio\":" + String((uint32_t)(0));
#endif
json += "}";
server.send(200, "text/json", json);
json = String();
});
//Set menu title
// Load editor
server.on("/edit", HTTP_GET, handleGetEdit);
// Create file
server.on("/edit", HTTP_PUT, handleFileCreate);
// Delete file
server.on("/edit", HTTP_DELETE, handleFileDelete);
// Upload file
// - first callback is called after the request has ended with all parsed arguments
// - second callback handles file upload at that location
server.on("/edit", HTTP_POST, replyOK, handleFileUpload);
// Default handler for all URIs not defined above
// Use it to read files from filesystem
// To make AutoConnect recognize the 404 handler, replace it with:
//server.onNotFound(handleNotFound);
portal.onNotFound(handleNotFound);
// Using AutoConnect does not require the HTTP server to be started
// intentionally. It is launched inside AutoConnect.begin.
// Start server
// server.begin();
// DBG_OUTPUT_PORT.println("HTTP server started");
// Start AutoConnect
config.title = "FSBrowser";
portal.config(config);
//Register AutoConnect menu
portal.join({ FSBedit, FSBlist });
//Replacement as follows to make AutoConnect recognition.
//server.begin();
portal.begin();
portal.append("/edit", "Edit");
portal.append("/list?dir=\"/\"", "List");
if (portal.begin()) {
DBG_OUTPUT_PORT.print(F("Connected! IP address: "));
DBG_OUTPUT_PORT.println(WiFi.localIP());
}
DBG_OUTPUT_PORT.println("HTTP server started");
//Relocation as follows to make AutoConnect recognition.
DBG_OUTPUT_PORT.println("");
DBG_OUTPUT_PORT.print("Connected! IP address: ");
DBG_OUTPUT_PORT.println(WiFi.localIP());
//Relocation as follows to make AutoConnect recognition.
// With applying AutoConnect, the MDNS service must be started after
// establishing a WiFi connection.
// MDNS INIT
if (MDNS.begin(host)) {
MDNS.addService("http", "tcp", 80);
DBG_OUTPUT_PORT.print("Open http://");
DBG_OUTPUT_PORT.print(F("Open http://"));
DBG_OUTPUT_PORT.print(host);
DBG_OUTPUT_PORT.println(".local/edit to see the file browser");
}
else {
DBG_OUTPUT_PORT.print("mDNS start failed");
DBG_OUTPUT_PORT.println(F(".local/edit to open the FileSystem Browser"));
DBG_OUTPUT_PORT.print(F("Open http://"));
DBG_OUTPUT_PORT.print(host);
DBG_OUTPUT_PORT.println(F(".local/_ac to AutoConnect statistics"));
}
}
void loop(void){
//Replacement as follows to make AutoConnect recognition.
//server.handleClient();
void loop(void) {
// To make AutoConnect recognize the client handling, replace it with:
// server.handleClient();
portal.handleClient();
#ifdef ARDUINO_ARCH_ESP8266
MDNS.update();

@ -1,13 +1,149 @@
### FSBrowser from the example of ESP8266WebServer
# FSBrowser with AutoConnect readme
This example sketch is in ESP8266WebServer library for Arduino environment. The [FSWebServer](https://github.com/esp8266/Arduino/tree/master/libraries/ESP8266WebServer/examples/FSBrowser) is cited as an example of combining AutoConnect library.
It is a concrete implementation that applies AutoConnect to **FSBrowser.ino** which is an example of ESP8266WebServer library. A description of the specific changes can be found in the [AutoConnect documentation](https://hieromon.github.io/AutoConnect/menuize.html).
The modification describes in the source code.
Modifying just a few lines of the Sketch you have and applying AutoConnect has the following advantages:
1. Make a wifi connection without pre-coded SSID and password.
2. Integrate the FSBrowser utility separated for each URI into the AutoConnect menu.
3. It does not interfere with the functions of the original FSBrowser.
#### License
The following description is a quote of readme.md that the FSBrowser distribution contains.
FSWebServer - Example WebServer with SPIFFS backend for esp8266
Copyright (c) 2015 Hristo Gochkov. All rights reserved.
This file is part of the ESP8266WebServer library for Arduino environment.
----
## What is this sketch about ?
This example is a FileSystem Browser for the ESP8266 using http requests and a html/javascript frontend,
working for all of SPIFFS, LittleFS and SDFS.
This unified version is based on the previous examples named FSWebServer, FSBrowser and SDWebServer, Copyright (c) 2015 Hristo Gochkov. All rights reserved.
## How to use it ?
1. Uncomment one of the `#define USE_xxx` directives in the sketch
2. Add the credentials of your WiFi network (search for `STASSID`)
3. Compile and upload the sketch to your ESP8266 device
4. For normal use, copy the contents of the `data` folder to the filesystem. To do so:
- for SDFS, copy that contents (not the data folder itself, just its contents) to the root of a FAT/FAT32-formated SD card connected to the SPI port of the ESP8266
- for SPIFFS or LittleFS, please follow the instructions at https://arduino-esp8266.readthedocs.io/en/latest/filesystem.html#uploading-files-to-file-system
5. Once the data and sketch have been uploaded, access the editor by pointing your browser to http://fsbrowser.local/edit
## Options
If you need to free some space on the ESP filesystem, you can delete all the sample files at the root but also replace the `index.htm` file in the `data/edit` subfolder by the `index.htm.gz` file from the `extras` folder. That compressed version is not suited for learning or debugging, but will bring the total FS usage under 7KB.
If you want to use the browser on a an existing filesystem or don't want to perform step 4 above, you have two possibilities :
- either upload the `index.htm` file to the filesystem by opening a command shell in the `data` folder and running the following cURL command:
`curl -F file=@edit/index.htm;filename=/edit/index.htm fsbrowser.local/edit`
- or embed a version of the html page in the source code itself by uncommenting the following line in the sketch and rebuilding:
`#define INCLUDE_FALLBACK_INDEX_HTM`
That embedded version is functionally equivalent and will be returned if no `/edit/index.htm` or `/edit/index.htm.gz` file can be found on the filesystem, at the expense of a higher binary size.
If you use the gzipped or `INCLUDE_FALLBACK_INDEX_HTM` options, please remember to rerun the `reduce_index.sh` script located in the `extras` subfolder and recompile the sketch after each change to the `index.html` file.
## Dependency
The html page uses the [Ace.js](https://ace.c9.io/) (v1.4.9 at the time of writing) text editor which is loaded from a CDN. Consequently, internet access from your web browser is required for the FSBrowser editing feature to work as-is.
If your browser has no web access (e.g. if you are connected to the ESP8266 as an access-point), you can copy the `ace.js` file to the `edit` subfolder of the ESP filesystem, along with optional plugins etc. according to your needs. A typical set might be:
```
ace.js
ext-keybinding_menu.js
ext-searchbox.js
mode-html.js
worker-html.js
worker-css.js
worker-javascript.js
mode-xml.js
worker-xml.js
mode-json.js
worker-json.js
```
(see https://github.com/ajaxorg/ace-builds for a full list).
If `ace.js` cannot be found on the ESP filesystem either, the page will default to a plain text viewer, with a warning message.
## Notes
- See https://arduino-esp8266.readthedocs.io/en/latest/filesystem.html for more information on FileSystems supported by the ESP8266.
- For SDFS, if your card's CS pin is not connected to the default pin (4), uncomment the `fileSystemConfig.setCSPin(chipSelectPin);` line, specifying the GPIO the CS pin is connected to
- `index.htm` is the default index returned if your URL does not end with a filename (works on subfolders as well)
- Filesystem limitations apply. For example, FAT16 is limited to 8.3 filenames - see https://en.wikipedia.org/wiki/8.3_filename - SPIFFS and LittleFS also have limitations, please see https://arduino-esp8266.readthedocs.io/en/latest/filesystem.html#spiffs-file-system-limitations
- Directories are supported on SDFS and LittleFS. On SPIFFS, all files are at the root, although their names may contain the "/" character.
- The convention here is that the root of the filesystem is "/". On SPIFFS, paths not started with a slash are not supported
- For creation, the convention is that a path ending with a "/" means create a folder, while without a "/" we create a file. Having an extension or not does not matter.
## Changelog since original FSBrowser
### Fixes to work on LittleFS based on SDFS
- #define logic to select FS
- switched from SD to SDFS
- begin() does not support parameters > removed SS and added optional config
- LittleFS.open() second parametsr is mandatory > specified "r" where needed
- 'FILE_WRITE' was not declared in this scope > replaced by "w"
### UI/usability improvements
- Never format filesystem, just return "FS INIT ERROR" when FS cannot be mounted
- Tree panel width is now proportional (20%) to see long names on big screens
- Added icons for files, and indented them to the same level as folders
- Changed file/folder icon set to use a lighter and more neutral one, and added specific "text" and "image" icons for formats recognized as such
- Items are now sorted (folders first, then plain files, each in alphabetic order)
- Added file size after each file name
- Added FS status information at the top right
- Made clear that an async operation is in progress by dimming screen and showing operation status
- Filled filename box in header with the name of the last clicked file
- Selecting a file for upload defaults to putting it in the same folder as the last clicked file
- Removed limitation to 8.3 lowercase filenames
- Support Filenames without extension, Dirnames with extension
- Improved recursive refresh of parts of the tree (e.g. refresh folder upon file delete, show last folder upon creating nested file)
- Added Save/Discard/Help buttons to ACE editor, discard confirmation on leave, and refresh tree and status upon save
- Removed "Upload" from context menu (which didn't work anyway)
- Added "Rename/Move" feature to context menu
- Sketch can be used on a preexisting filesystem by embedding the index.htm file in the program
## TODO (maybe)
- ? How can we query the fatType of the SDFS (FAT16 or FAT32) to limit filenames to 8.3 on FAT16 ?
- ? Add a visible root node "/" (with no delete option) + add the FS type next to it, like <i>LittleFS</i>
- ? move "Mkdir" and "MkFile" to context menu, with prompt like for Rename/Move
- ? implement drag/drop for move + make "rename" only a local rename operation (no move)
- ? Optionally present SPIFFS as a hierarchical FS too
- ? Optionally mount several filesystems at the same time (SPIFFS + SDFS or LittleFS + SDFS)
## Test suite
These tests are a checklist of operations to verify the FSBrowser behaviour.
### On SPIFFS
#### 8.3 filenames
- At root : MkFile '/1.txt' / List / Edit / Download / Delete / Upload '/1.png' / View image / Delete image
- In subdir : MkFile '/dir/2.txt' / List / Edit / Download / Delete / Upload '/dir/2.png' / View image
- Create nested file '/a/b.txt' and delete it
- Attempt creation of unsupported filenames
#### Long filenames
- At root : MkFile '/My text file 1.txt' / List / Edit / Download / Delete / Upload '/My image file 1.png' / View image / Delete image
- In subdir : MkFile '/My Directory/My text 2.txt' / List / Edit / Download / Delete / Upload '/My Directory/My image 2.png' / View image
- Create nested file '/My folder/My test file.txt' and delete it
### On LittleFS
#### 8.3 filenames
- At root : MkFile '/1.txt' / List / Edit / Download / Delete / Upload '/1.png' / View image / Delete image / Mkdir '/dir'
- In subdir : MkFile '/dir/2.txt' / List / Edit / Download / Delete / Upload '/dir/2.png' / View image / Mkdir '/dir/sub'
- Delete root folder '/dir'
- Create nested file '/a/b.txt' and delete file 'b.txt'
#### Long filenames
- At root : MkFile '/My text file 1.txt' / List / Edit / Download / Delete / Upload '/My image file 1.png' / View image / Delete image / Mkdir '/My Directory'
- In subdir : MkFile '/My Directory/My text file 2.txt' / List / Edit / Download / Delete / Upload '/My Directory/My image file 2.png' / View image / Mkdir '/My Directory/My Subdirectory'
- Delete root folder '/My Directory'
- Create nested file '/My folder/My test file.txt' and delete file 'My test file.txt'
### On SDFS
#### 8.3 filenames
- At root : MkFile '/1.txt' / List / Edit / Download / Delete / Upload '/1.png' / View image / Delete image / Mkdir '/dir'
- In subdir : MkFile '/dir/2.txt' / List / Edit / Download / Delete / Upload '/dir/2.png' / View image / Mkdir '/dir/sub'
- Delete root folder '/dir'
- Create nested file '/a/b.txt' and delete file 'b.txt', then delete '/a'
#### Long filenames
- At root : MkFile '/My text file 1.txt' / List / Edit / Download / Delete / Upload '/My image file 1.png' / View image / Delete image / Mkdir '/My Directory'
- In subdir : MkFile '/My Directory/My text file 2.txt' / List / Edit / Download / Delete / Upload '/My Directory/My image file 2.png' / View image / Mkdir '/My Directory/My Subdirectory'
- Delete root folder '/My Directory'
- Create nested file '/My folder/My test file.txt' and delete file 'My test file.txt'
## Credits
- Original version of FSBrowser written by me-no-dev, contributions over time by various contributors
- Icons are from https://feathericons.com/ . The resulting PNG is passed first through https://compresspng.com/ before being converted to base64 using https://www.base64-image.de/
- The spinner is based on https://github.com/jlong/css-spinners
- Minifiying of index.htm is done using the command line version of https://kangax.github.io/html-minifier/
- Idea of embedding webpage in code borrowed from https://github.com/me-no-dev/ESPAsyncWebServer
License under the LGPL-2.1.

File diff suppressed because it is too large Load Diff

@ -1,101 +1,22 @@
<!--
FSWebServer - Example Index Page
Copyright (c) 2015 Hristo Gochkov. All rights reserved.
This file is part of the ESP8266WebServer library for Arduino environment.
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
-->
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-type" content="text/html; charset=utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>ESP Monitor</title>
<script type="text/javascript" src="graphs.js"></script>
<script type="text/javascript">
var heap,temp,digi;
var reloadPeriod = 1000;
var running = false;
function loadValues(){
if(!running) return;
var xh = new XMLHttpRequest();
xh.onreadystatechange = function(){
if (xh.readyState == 4){
if(xh.status == 200) {
var res = JSON.parse(xh.responseText);
heap.add(res.heap);
temp.add(res.analog);
digi.add(res.gpio);
if(running) setTimeout(loadValues, reloadPeriod);
} else running = false;
}
};
xh.open("GET", "/all", true);
xh.send(null);
};
function run(){
if(!running){
running = true;
loadValues();
}
<title>ESP Index</title>
<style>
body {
background-color:black;
color:white;
}
</style>
<script type="text/javascript">
function onBodyLoad(){
var refreshInput = document.getElementById("refresh-rate");
refreshInput.value = reloadPeriod;
refreshInput.onchange = function(e){
var value = parseInt(e.target.value);
reloadPeriod = (value > 0)?value:0;
e.target.value = reloadPeriod;
}
var stopButton = document.getElementById("stop-button");
stopButton.onclick = function(e){
running = false;
}
var startButton = document.getElementById("start-button");
startButton.onclick = function(e){
run();
}
// Example with 10K thermistor
//function calcThermistor(v) {
// var t = Math.log(((10230000 / v) - 10000));
// t = (1/(0.001129148+(0.000234125*t)+(0.0000000876741*t*t*t)))-273.15;
// return (t>120)?0:Math.round(t*10)/10;
//}
//temp = createGraph(document.getElementById("analog"), "Temperature", 100, 128, 10, 40, false, "cyan", calcThermistor);
temp = createGraph(document.getElementById("analog"), "Analog Input", 100, 128, 0, 1023, false, "cyan");
heap = createGraph(document.getElementById("heap"), "Current Heap", 100, 125, 0, 30000, true, "orange");
digi = createDigiGraph(document.getElementById("digital"), "GPIO", 100, 146, [0, 4, 5, 16], "gold");
run();
console.log("we are loaded!!");
}
</script>
</head>
<body id="index" style="margin:0; padding:0;" onload="onBodyLoad()">
<div id="controls" style="display: block; border: 1px solid rgb(68, 68, 68); padding: 5px; margin: 5px; width: 362px; background-color: rgb(238, 238, 238);">
<label>Period (ms):</label>
<input type="number" id="refresh-rate"/>
<input type="button" id="start-button" value="Start"/>
<input type="button" id="stop-button" value="Stop"/>
</div>
<div id="heap"></div>
<div id="analog"></div>
<div id="digital"></div>
<p style="padding-top:15px;text-align:center">
<a href="/_ac"><img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAC2klEQVRIS61VvWsUQRSfmU2pon9BUIkQUaKFaCBKgooSb2d3NSSFKbQR/KrEIiIKBiGF2CgRxEpjQNHs7mwOUcghwUQ7g58IsbGxEBWsb2f8zR177s3t3S2cA8ftzPu993vzvoaSnMu2vRKlaqgKp74Q/tE8qjQPyHGcrUrRjwlWShmDbFMURd/a6TcQwNiYUmpFCPElUebcuQ2vz6aNATMVReHEPwzfSSntDcNwNo2rI+DcvQzhpAbA40VKyV0p1Q9snzBG1qYVcYufXV1sREraDcxpyHdXgkfpRBj6Uwm2RsC5dxxmZ9pdOY9cKTISRcHTCmGiUCh4fYyplTwG2mAUbtMTBMHXOgK9QfyXEZr+TkgQ1oUwDA40hEgfIAfj+HuQRaBzAs9eKyUZ5Htx+T3ZODKG8DzOJMANhmGomJVMXPll+hx9UUAlzZrJJ4QNCDG3VEfguu7mcpmcB/gkBOtShhQhchAlu5jlLUgc9ENgyP5gf9+y6LTv+58p5zySkgwzLNOIGc8sEoT1Lc53NMlbCQQuvMxeCME1NNPVVkmH/i3IzzXDtCSA0qQQwZWOCJDY50jsQRjJmkslEOxvTcDRO6zPxOh5xZglKkYLhWM9jMVnkIsTyMT6NBj7IbOCEjm6HxNVVTo2WXqEWJZ1T8rytB6GxizyDkPhWVpBqfiXUtbo/HywYJSpA9kMamNNPZ71R9Hcm+TMHHZNGw3EuraXEUldbfvw25UdOjqOt+JhMwJd7+jSTpZaEiIcaCDwPK83jtWnTkwnunFMtxeL/ge9r4XItt1RNNaj/0GAcV2bR3U5sG3nEh6M61US+Qrfd9Bs31GGulI2GOS/8dgcQZV1w+ApjIxB7TDwF9GcNzJzoA+rD0/8HvPnXQJCt2qFCwbBTfRI7UyXumWVt+HJ9NO4XI++bdsb0YyrqXmlh+AWOLHaLqS5CLQR5EggR3YlcVS9gKeH2hnX8r8Kmi1CAsl36QAAAABJRU5ErkJggg==" border="0" title="AutoConnect menu" alt="AutoConnect menu"/></a>
</p>
<body id="index" onload="onBodyLoad()">
<h1>ESP8266 Pin Functions</h1>
<img src="pins.png" />
</body>
</html>
</html>

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

@ -0,0 +1,529 @@
// WARNING: Auto-generated file. Please do not modify by hand.
// This file is an embeddable version of the gzipped index.htm file.
// To update it, please rerun the `reduce_index.sh` script located in the `extras` subfolder
// then recompile the sketch after each change to the `index.html` file.
unsigned char index_htm_gz[] = {
0x1f, 0x8b, 0x08, 0x08, 0x96, 0xc9, 0xa8, 0x5e, 0x00, 0x03, 0x69, 0x6e,
0x64, 0x65, 0x78, 0x2e, 0x68, 0x74, 0x6d, 0x00, 0xdc, 0x3b, 0x89, 0x7b,
0xda, 0xb8, 0x97, 0xff, 0x8a, 0xe3, 0xee, 0x24, 0xf6, 0x02, 0x06, 0x92,
0xe6, 0x28, 0xc4, 0xc9, 0xe4, 0x4e, 0x9a, 0xb3, 0xb9, 0xd3, 0x6e, 0xf7,
0xfb, 0x04, 0x16, 0xa0, 0xc4, 0xd8, 0xae, 0x2d, 0x07, 0x48, 0xca, 0xfe,
0xed, 0xfb, 0x9e, 0xe4, 0x13, 0x4c, 0x32, 0xdd, 0x9d, 0xf9, 0xcd, 0xec,
0xb6, 0xf3, 0x15, 0x5b, 0x96, 0x9e, 0xde, 0x7d, 0x49, 0xb3, 0x3e, 0xb7,
0x7b, 0xbe, 0x73, 0xfd, 0x70, 0xb1, 0xa7, 0xf4, 0x78, 0xdf, 0x56, 0x2e,
0x6e, 0xb6, 0x4f, 0x8e, 0x76, 0x14, 0xb5, 0x52, 0xad, 0xde, 0x2d, 0xed,
0x54, 0xab, 0xbb, 0xd7, 0xbb, 0xca, 0xfd, 0xe1, 0xf5, 0xe9, 0x89, 0x52,
0x37, 0x6a, 0xca, 0xb5, 0x4f, 0x9c, 0x80, 0x71, 0xe6, 0x3a, 0xc4, 0xae,
0x56, 0xf7, 0xce, 0x54, 0x45, 0xed, 0x71, 0xee, 0x35, 0xaa, 0xd5, 0xc1,
0x60, 0x60, 0x0c, 0x96, 0x0c, 0xd7, 0xef, 0x56, 0xaf, 0x2f, 0xab, 0x43,
0x84, 0x55, 0xc7, 0xc5, 0xd1, 0x63, 0x85, 0x67, 0x56, 0x1a, 0x16, 0xb7,
0xd4, 0x8d, 0x75, 0xb1, 0x9f, 0x4d, 0x9c, 0xae, 0x49, 0x9d, 0x8d, 0x75,
0xce, 0xb8, 0x4d, 0x37, 0xf6, 0x99, 0x4d, 0x95, 0x3e, 0x71, 0x48, 0x97,
0xfa, 0xeb, 0x55, 0x39, 0xb6, 0x1e, 0xf0, 0x11, 0x8e, 0x52, 0x8b, 0x11,
0x33, 0x68, 0xfb, 0x14, 0xa6, 0xb7, 0x5c, 0x6b, 0xf4, 0xda, 0x71, 0x1d,
0x5e, 0x09, 0xd8, 0x0b, 0x6d, 0xd4, 0x17, 0xbd, 0x61, 0x53, 0xbc, 0x76,
0x48, 0x9f, 0xd9, 0xa3, 0xc6, 0x2d, 0xf5, 0x2d, 0x80, 0x52, 0xde, 0xf2,
0x19, 0xb1, 0xcb, 0x57, 0xb0, 0x77, 0x25, 0xa0, 0x3e, 0xeb, 0x8c, 0x8d,
0x36, 0xcc, 0xa2, 0x43, 0x7e, 0x4a, 0x9d, 0xf0, 0xf5, 0xa5, 0xc2, 0x1c,
0x8b, 0x0e, 0x1b, 0x4b, 0xb5, 0x5a, 0xd3, 0x73, 0x25, 0x7a, 0x0d, 0xd2,
0x0a, 0x5c, 0x3b, 0xe4, 0xb4, 0x69, 0xd3, 0x0e, 0x6f, 0x2c, 0x03, 0xe0,
0x96, 0xeb, 0x5b, 0xd4, 0x6f, 0xd4, 0xbd, 0xa1, 0x02, 0x9f, 0x98, 0xa5,
0x7c, 0xf8, 0xf8, 0xf1, 0x63, 0xb3, 0x45, 0xda, 0x4f, 0x5d, 0xdf, 0x0d,
0x1d, 0xab, 0xd2, 0x76, 0x6d, 0xd7, 0x6f, 0x7c, 0xe8, 0x2c, 0xe3, 0xdf,
0xa6, 0xc5, 0x02, 0xcf, 0x26, 0xa3, 0x86, 0xe3, 0x3a, 0x14, 0xd6, 0x0e,
0x2b, 0x41, 0x8f, 0x58, 0xee, 0xa0, 0x51, 0x53, 0x6a, 0x4a, 0xbd, 0x06,
0x40, 0xfc, 0x6e, 0x8b, 0x68, 0xb5, 0x32, 0xfe, 0x35, 0x3e, 0xea, 0xcd,
0x0c, 0x19, 0xf5, 0x98, 0x8c, 0x01, 0x65, 0xdd, 0x1e, 0x6f, 0xac, 0xd6,
0x6a, 0x39, 0x8c, 0x95, 0xd0, 0x7e, 0xb5, 0x59, 0x00, 0xd3, 0x91, 0x25,
0x72, 0x07, 0xee, 0x7a, 0x8d, 0x9a, 0x44, 0xb6, 0xd6, 0xec, 0x13, 0xbf,
0xcb, 0x1c, 0x78, 0xf0, 0x88, 0x65, 0x31, 0xa7, 0xdb, 0x98, 0x58, 0x6f,
0xb3, 0xd7, 0x84, 0x50, 0x9f, 0xda, 0x84, 0xb3, 0x67, 0xda, 0xec, 0x33,
0xa7, 0x32, 0x60, 0x16, 0xef, 0x35, 0x56, 0x00, 0xbd, 0x66, 0x3b, 0xf4,
0x03, 0x20, 0xc7, 0x73, 0x19, 0xac, 0xf3, 0xf3, 0xeb, 0x03, 0x8f, 0x38,
0xaf, 0x11, 0xb9, 0xc8, 0x84, 0x98, 0x56, 0xe6, 0xd8, 0xcc, 0xa1, 0x95,
0x96, 0xed, 0xb6, 0x9f, 0x92, 0xbd, 0x57, 0xbc, 0xe1, 0xe4, 0xee, 0x8d,
0x9e, 0xfb, 0x4c, 0xfd, 0xd7, 0x94, 0x77, 0x02, 0x4c, 0xf1, 0xac, 0xdc,
0x66, 0x94, 0x52, 0x98, 0x15, 0x04, 0xa0, 0x48, 0x94, 0x3e, 0x33, 0x3a,
0x80, 0x69, 0xe5, 0xfc, 0x00, 0xf0, 0x26, 0xa1, 0x3a, 0x65, 0xc4, 0x04,
0xbb, 0x26, 0x80, 0x30, 0xc7, 0x0b, 0xf9, 0xeb, 0xb4, 0xec, 0x5d, 0x8f,
0xb4, 0x19, 0x1f, 0x09, 0xf6, 0x65, 0xe6, 0xbf, 0x4e, 0xc8, 0xaa, 0xd2,
0x77, 0x5f, 0x2a, 0x21, 0x68, 0x16, 0x68, 0x97, 0x4d, 0xdb, 0x5c, 0x4a,
0x04, 0xa4, 0xd7, 0x7a, 0x62, 0x7c, 0xfa, 0xc3, 0xe4, 0xc0, 0x04, 0x32,
0x59, 0x7a, 0x6b, 0xb5, 0xce, 0xb4, 0x20, 0x26, 0x27, 0x47, 0xdc, 0x44,
0xce, 0x55, 0x2c, 0xda, 0x76, 0x7d, 0x22, 0xa8, 0x00, 0xb6, 0x52, 0x1f,
0xe5, 0x51, 0x44, 0x6c, 0xc9, 0x26, 0x2d, 0x6a, 0x97, 0x80, 0x59, 0x31,
0x8b, 0x14, 0xfc, 0xbb, 0xb8, 0x28, 0x84, 0x35, 0x35, 0xfd, 0xbf, 0x60,
0x62, 0x56, 0xa1, 0x27, 0x85, 0x80, 0xc0, 0xca, 0x05, 0x63, 0x8d, 0x46,
0x8b, 0x76, 0x5c, 0x9f, 0xbe, 0xbe, 0x49, 0x84, 0xd8, 0xa2, 0x01, 0xf0,
0x49, 0xcb, 0xa6, 0x96, 0x44, 0x2d, 0x5e, 0x61, 0xd1, 0x0e, 0x09, 0x6d,
0x9e, 0x88, 0xc2, 0x58, 0x29, 0x5c, 0xdc, 0xee, 0xd1, 0xf6, 0x13, 0xb5,
0x00, 0x39, 0xae, 0x25, 0x90, 0xf4, 0x2c, 0xda, 0x42, 0x29, 0x7f, 0x15,
0xef, 0x72, 0x81, 0x68, 0x32, 0x5a, 0x1b, 0xfa, 0xb6, 0x66, 0x11, 0x4e,
0x1a, 0xac, 0x0f, 0xae, 0xaa, 0xea, 0x39, 0x5d, 0xf0, 0x07, 0x01, 0x5d,
0xf9, 0x58, 0x66, 0xb7, 0xdb, 0xe7, 0x97, 0x83, 0xda, 0xf1, 0x41, 0xd7,
0xdd, 0x82, 0x3f, 0x67, 0x57, 0x37, 0xbd, 0xbd, 0x9b, 0x2e, 0x3c, 0x6d,
0xe3, 0xeb, 0x4e, 0x77, 0x67, 0xeb, 0x14, 0x1f, 0x46, 0xcb, 0xc3, 0x41,
0x1f, 0x1f, 0x5a, 0xf5, 0xed, 0xd3, 0xdb, 0xbd, 0xdb, 0xc3, 0xf6, 0xde,
0xe8, 0xae, 0xbf, 0xbc, 0x7c, 0x77, 0xb7, 0xb8, 0xb7, 0xf2, 0xe5, 0xc6,
0xda, 0xfa, 0xb2, 0xb7, 0xcd, 0xc8, 0x41, 0xfd, 0x91, 0x1c, 0xac, 0x56,
0xab, 0xd5, 0xb5, 0xe7, 0xb3, 0xc7, 0xa5, 0xe3, 0x97, 0xd3, 0xd5, 0x9d,
0xe1, 0x69, 0xab, 0xbf, 0x1c, 0x76, 0x4e, 0x5f, 0xda, 0xd5, 0x87, 0x45,
0xeb, 0xc7, 0x90, 0x9f, 0x90, 0x03, 0xe6, 0x2e, 0xaf, 0x75, 0x1f, 0xee,
0x3e, 0x3f, 0x7e, 0xdd, 0xbf, 0xbc, 0xdd, 0xff, 0xfa, 0xf9, 0x7a, 0xaf,
0x7a, 0xf2, 0xd2, 0x2e, 0x3d, 0x07, 0xad, 0x33, 0xeb, 0xfe, 0x76, 0xf5,
0x63, 0xe9, 0xa2, 0xf7, 0x6c, 0x1d, 0xda, 0x41, 0xeb, 0x6e, 0xf1, 0xc9,
0x5b, 0xf9, 0xb1, 0xfa, 0x7c, 0xf2, 0x32, 0x5a, 0x7b, 0x3e, 0x0d, 0xcf,
0xae, 0x5f, 0x3a, 0x4b, 0x9f, 0x4a, 0x3d, 0x77, 0xe5, 0x66, 0x74, 0x7e,
0xb3, 0xb3, 0xdf, 0x7b, 0xb8, 0xbb, 0xb1, 0x97, 0x9d, 0xe7, 0xd5, 0x52,
0xd5, 0x5b, 0xa1, 0x4f, 0x5f, 0x58, 0xf5, 0xe0, 0x12, 0x71, 0xdc, 0xba,
0xbf, 0xbc, 0xba, 0xb6, 0x4f, 0xb7, 0xbe, 0x9c, 0xb7, 0x1e, 0xbe, 0x22,
0x2d, 0x57, 0x97, 0x9f, 0x2f, 0xf7, 0xf6, 0x6f, 0xae, 0x4e, 0x3b, 0xfc,
0xe9, 0x13, 0x1f, 0x0d, 0xd8, 0xd6, 0x97, 0x9e, 0x7b, 0xb3, 0xd5, 0xbb,
0xdd, 0x1a, 0x7c, 0xf6, 0x7e, 0xec, 0x5e, 0xfe, 0xe8, 0x90, 0xe7, 0xe7,
0xb5, 0x17, 0x3b, 0x3c, 0x3b, 0x7e, 0x0a, 0xfd, 0xbd, 0x47, 0xff, 0x61,
0xa9, 0x44, 0x57, 0x3f, 0x7e, 0x66, 0x2f, 0x27, 0xce, 0xe2, 0x5d, 0xbd,
0xcf, 0xb6, 0x8e, 0x87, 0x5e, 0xef, 0x7c, 0xfb, 0x94, 0xde, 0x3c, 0xfc,
0x58, 0x09, 0x0f, 0xab, 0x5b, 0x4b, 0x5b, 0x2b, 0x2b, 0x0f, 0xde, 0xe5,
0xf6, 0xe5, 0x8f, 0xcf, 0x5f, 0xc9, 0xe9, 0xd1, 0x1a, 0x1b, 0x04, 0xb7,
0xd5, 0x1d, 0xeb, 0x74, 0x65, 0x6b, 0x71, 0xf8, 0xb8, 0xec, 0x1c, 0xdd,
0x04, 0xc7, 0xb5, 0x2a, 0xbb, 0xbe, 0xb9, 0xf0, 0x0f, 0xce, 0xfa, 0xb5,
0xd3, 0x9b, 0xdd, 0xa3, 0x27, 0x7a, 0x50, 0xfd, 0xbc, 0xfc, 0x31, 0x3c,
0x67, 0x4f, 0x41, 0xeb, 0x53, 0xef, 0xbe, 0xb7, 0xbc, 0x7c, 0xd1, 0x3b,
0x3a, 0x7a, 0xec, 0x1c, 0x77, 0xad, 0xcf, 0xd7, 0x87, 0x57, 0x7b, 0xa3,
0xc5, 0xea, 0xfe, 0x6e, 0x6d, 0xe5, 0xbe, 0xef, 0x5a, 0x6b, 0x67, 0xe7,
0x03, 0xdf, 0x1f, 0xec, 0xdf, 0x04, 0x5f, 0xfa, 0xf7, 0x5f, 0x0f, 0xbf,
0xf6, 0x7a, 0xf4, 0xe9, 0x70, 0x9b, 0x6d, 0x8f, 0x1e, 0x8e, 0x5c, 0x72,
0xf4, 0x79, 0xeb, 0xf1, 0x62, 0xed, 0xe6, 0xea, 0x8e, 0xed, 0x6c, 0xad,
0x1d, 0xf7, 0xf6, 0xee, 0xd6, 0x6e, 0x0e, 0xae, 0x57, 0x8f, 0x2f, 0xc8,
0xd7, 0xbd, 0x61, 0x70, 0xde, 0x3a, 0x1c, 0xf9, 0x37, 0xdd, 0xeb, 0xa7,
0xc7, 0xeb, 0x97, 0x35, 0x9b, 0x5d, 0xdc, 0x0f, 0x5e, 0x06, 0x7b, 0xdb,
0xa5, 0xf3, 0x8b, 0xfd, 0xdb, 0xe1, 0xe1, 0xde, 0xda, 0xfd, 0x62, 0xfb,
0xe9, 0x72, 0x7b, 0x74, 0x42, 0x6e, 0x47, 0xbd, 0xdb, 0xe3, 0xe1, 0xc5,
0xe2, 0xea, 0xf1, 0x59, 0xc9, 0xd9, 0xe2, 0x87, 0xab, 0x97, 0xcf, 0xa1,
0xbf, 0xb8, 0xeb, 0xaf, 0x2c, 0xd6, 0x39, 0x7d, 0x3a, 0xa5, 0x41, 0xe9,
0x8e, 0x1d, 0xac, 0xad, 0x1c, 0xfa, 0x2b, 0x8f, 0xc7, 0x0f, 0x8f, 0xa5,
0xd5, 0x2f, 0xf5, 0x63, 0xab, 0x76, 0xe1, 0x0d, 0x8f, 0x96, 0xd7, 0xce,
0x82, 0x2f, 0xd6, 0x59, 0x75, 0x71, 0xf9, 0xc5, 0xfe, 0xb2, 0xfb, 0xc5,
0x3a, 0x6e, 0x7d, 0xda, 0x72, 0x4e, 0x57, 0x3a, 0x87, 0x57, 0x07, 0x4f,
0x17, 0xc1, 0x17, 0xf2, 0x99, 0xf4, 0x8f, 0xbc, 0x2f, 0x2f, 0x3b, 0xfe,
0x68, 0xd0, 0xdb, 0xad, 0xb3, 0xeb, 0xc5, 0xfb, 0xa7, 0xe0, 0x64, 0x67,
0x10, 0x54, 0x8f, 0xbe, 0x3e, 0xaf, 0x7d, 0x75, 0x3b, 0xab, 0x7c, 0x71,
0xf9, 0xc1, 0x7e, 0x12, 0x62, 0xba, 0xba, 0xb9, 0x3d, 0xbf, 0x3c, 0x5e,
0xde, 0x79, 0x38, 0x3a, 0x32, 0x75, 0xc5, 0x71, 0x2b, 0x3e, 0xf5, 0x28,
0xe1, 0x7f, 0x82, 0xee, 0x17, 0x0c, 0x25, 0x16, 0x5e, 0x18, 0x03, 0x7a,
0x32, 0x80, 0xd5, 0x21, 0x06, 0x34, 0xc5, 0x70, 0x76, 0x00, 0x9c, 0x16,
0x67, 0x6d, 0x62, 0x57, 0x88, 0xcd, 0xba, 0x4e, 0xa3, 0xcf, 0x2c, 0xcb,
0x2e, 0xf4, 0x2c, 0x19, 0x93, 0xab, 0x24, 0x4e, 0xba, 0xbe, 0x06, 0x31,
0xb4, 0x56, 0xe4, 0x3b, 0x67, 0xce, 0xae, 0x7c, 0x5c, 0x9b, 0x72, 0x6f,
0xb8, 0xc2, 0xe0, 0x43, 0xfe, 0xc6, 0xaa, 0x95, 0x8f, 0xc5, 0xab, 0x58,
0xbf, 0xfb, 0xc6, 0xaa, 0xb5, 0xda, 0xd4, 0xaa, 0x09, 0x97, 0x88, 0xc1,
0xce, 0xe1, 0x0d, 0x55, 0x6d, 0xca, 0xb0, 0x2b, 0x98, 0x92, 0xb8, 0x63,
0x74, 0xc5, 0xe8, 0x93, 0x8b, 0xd9, 0xd4, 0x2c, 0xda, 0xb8, 0xa6, 0x54,
0x96, 0xa6, 0x1d, 0x78, 0x4e, 0x4a, 0x7f, 0x70, 0xd3, 0xb7, 0x7c, 0x6c,
0x29, 0x4f, 0xc6, 0x0c, 0x44, 0x10, 0xee, 0xf8, 0x77, 0x91, 0xad, 0x29,
0x32, 0x5b, 0x53, 0x88, 0x63, 0x29, 0x5a, 0x1c, 0x15, 0x31, 0xdb, 0xb0,
0x00, 0x7a, 0x9b, 0x56, 0x3c, 0x36, 0xa4, 0x76, 0x45, 0x44, 0xad, 0x46,
0x4d, 0x7f, 0xcd, 0x87, 0xda, 0x78, 0x3e, 0x71, 0xc0, 0xc7, 0x0a, 0xe0,
0xf1, 0x80, 0xf5, 0x48, 0xda, 0x40, 0x4a, 0x05, 0x02, 0x69, 0x1f, 0x7f,
0x65, 0x44, 0x75, 0xfd, 0x4a, 0x2b, 0xec, 0x76, 0xd8, 0x10, 0x90, 0xee,
0x30, 0x87, 0x71, 0xaa, 0xd4, 0x83, 0xf1, 0xef, 0x31, 0x98, 0x27, 0x3a,
0xea, 0xf8, 0xa4, 0x4f, 0x03, 0xe5, 0x0f, 0x82, 0x79, 0xed, 0xf8, 0x6e,
0x3f, 0xcd, 0x28, 0xc6, 0xdc, 0xcd, 0xbc, 0x8c, 0xc7, 0x1f, 0x7a, 0x94,
0x40, 0x98, 0x2d, 0xc8, 0x1d, 0x64, 0x42, 0xe6, 0x0b, 0x75, 0x4f, 0x12,
0xb3, 0x48, 0xfd, 0x17, 0x41, 0xa3, 0x92, 0xfc, 0x08, 0x13, 0xca, 0xba,
0x60, 0x3b, 0xa6, 0x85, 0x05, 0x09, 0x25, 0x66, 0x58, 0x99, 0xfc, 0xe7,
0x03, 0xf2, 0x66, 0xc6, 0x86, 0x8b, 0x32, 0x51, 0xe5, 0xdc, 0xed, 0xa7,
0x9b, 0x4a, 0x41, 0x2f, 0xd6, 0x7e, 0x4b, 0xb6, 0x44, 0x33, 0xf8, 0x00,
0xa2, 0x01, 0x32, 0xcb, 0x1f, 0x3c, 0x5f, 0x72, 0xfa, 0x0d, 0x88, 0x31,
0x15, 0x79, 0xc8, 0x00, 0x71, 0x9c, 0xac, 0x9e, 0x46, 0x1b, 0x70, 0x4d,
0x36, 0x5c, 0xc6, 0x0d, 0x03, 0x4e, 0x78, 0x18, 0xcc, 0xd8, 0x67, 0x29,
0xd9, 0x46, 0x70, 0x21, 0x93, 0x61, 0x89, 0xb5, 0x9d, 0xe0, 0x94, 0x42,
0xe2, 0xf0, 0x1a, 0x29, 0x6d, 0xad, 0x96, 0x32, 0xb0, 0x12, 0x61, 0x85,
0x9a, 0xff, 0x61, 0x40, 0x7c, 0x07, 0xc6, 0x5e, 0x63, 0x3f, 0x53, 0x03,
0xaa, 0xa7, 0x50, 0x83, 0x0c, 0xc9, 0xe9, 0xd2, 0x66, 0x92, 0x63, 0x81,
0xba, 0x73, 0xd7, 0xb5, 0x39, 0xf3, 0x0a, 0x90, 0x8b, 0xab, 0x83, 0xc5,
0x84, 0x0b, 0x82, 0x2d, 0x88, 0xc0, 0x33, 0x0b, 0x58, 0x8b, 0xd9, 0x98,
0x96, 0xf4, 0xc0, 0x2a, 0xa9, 0x53, 0x54, 0x0d, 0x74, 0x3a, 0x99, 0x8d,
0x9a, 0x22, 0x49, 0x93, 0x86, 0x8c, 0x5a, 0x47, 0xfd, 0x82, 0xb2, 0x02,
0xe7, 0xc5, 0x8c, 0x5b, 0xca, 0xf3, 0xa2, 0x96, 0xa1, 0x31, 0x4a, 0x8f,
0x13, 0xd4, 0x33, 0xd8, 0x88, 0x47, 0xf0, 0xa5, 0x1f, 0x6c, 0x97, 0x20,
0x98, 0x19, 0x3c, 0xaf, 0x00, 0x7b, 0x9e, 0x7b, 0x79, 0x35, 0xc1, 0xa1,
0x41, 0x33, 0x65, 0x1f, 0x7c, 0x8f, 0x39, 0x00, 0x6f, 0xd3, 0x04, 0x66,
0xab, 0x98, 0x65, 0x3d, 0xcd, 0x98, 0x9b, 0x69, 0xa9, 0xd7, 0x88, 0x06,
0x15, 0x63, 0x39, 0x50, 0x28, 0xe4, 0x47, 0x00, 0xaf, 0xe2, 0x86, 0x3c,
0x41, 0xcf, 0x08, 0x7a, 0xee, 0xc0, 0x79, 0x95, 0x16, 0x13, 0x43, 0xa8,
0x27, 0x9f, 0x2b, 0xfd, 0xa0, 0x5b, 0x1c, 0x5e, 0x66, 0x59, 0x9d, 0xac,
0xd9, 0x40, 0xf4, 0x02, 0x09, 0x70, 0x53, 0xfd, 0x86, 0x78, 0x82, 0x4a,
0x87, 0xde, 0x6b, 0x15, 0xf8, 0xa2, 0x37, 0x33, 0xf2, 0x49, 0x19, 0x2c,
0xdc, 0xe7, 0xef, 0xa9, 0x93, 0x08, 0x3c, 0xe6, 0x38, 0x90, 0xad, 0xa3,
0xf7, 0x79, 0xad, 0xfd, 0xf6, 0x9a, 0xc2, 0xf3, 0x5d, 0xd0, 0x66, 0xaa,
0xd5, 0xf4, 0x31, 0xea, 0xd8, 0xf4, 0x87, 0xa5, 0x95, 0x9a, 0x45, 0xbb,
0xfa, 0x78, 0x6c, 0x64, 0x61, 0xc8, 0xfc, 0xd4, 0xa7, 0x3f, 0x42, 0xe6,
0x43, 0x7e, 0xfa, 0x0e, 0x55, 0x49, 0x69, 0x86, 0x54, 0x21, 0x39, 0x6f,
0xd1, 0x25, 0xc8, 0x2a, 0x4b, 0xda, 0x52, 0x67, 0x99, 0xdd, 0x1c, 0x1c,
0x61, 0xea, 0x14, 0x71, 0x3f, 0x92, 0x6a, 0xdf, 0x4a, 0xaa, 0x7e, 0x68,
0xb7, 0x72, 0xb8, 0x22, 0x34, 0x3e, 0x12, 0xb4, 0xd8, 0xc8, 0x23, 0x3e,
0x68, 0x6d, 0xf2, 0x19, 0xc4, 0x13, 0x06, 0x82, 0x69, 0xb2, 0xc2, 0x65,
0x2f, 0xa8, 0x97, 0xd1, 0x57, 0x18, 0x69, 0xa2, 0x86, 0x76, 0x6c, 0xa8,
0x7a, 0x23, 0xfb, 0x10, 0xea, 0x8f, 0xda, 0x04, 0x21, 0xa8, 0xf2, 0x09,
0xfe, 0xc0, 0xca, 0xa8, 0xe6, 0x44, 0x97, 0x18, 0xa9, 0x9d, 0x08, 0xb8,
0xeb, 0x55, 0x51, 0xae, 0x41, 0xdd, 0xdf, 0xf6, 0x99, 0xc7, 0x37, 0x9e,
0x89, 0xaf, 0xa0, 0xe7, 0x2b, 0x77, 0x82, 0x23, 0xa7, 0xe3, 0x36, 0x3b,
0xa1, 0xd3, 0x46, 0x12, 0x95, 0x80, 0xf2, 0x13, 0xa9, 0x28, 0x1a, 0x2d,
0x73, 0xfd, 0x95, 0xcf, 0xcf, 0x43, 0x94, 0x03, 0x62, 0xa8, 0x61, 0xbb,
0x5d, 0x8d, 0xeb, 0x65, 0xcb, 0x6d, 0x87, 0xe8, 0xdb, 0x8d, 0x2e, 0xe5,
0x7b, 0xd2, 0xcd, 0x6f, 0x8f, 0x8e, 0x2c, 0x4d, 0xcd, 0x28, 0x98, 0xaa,
0x1b, 0x82, 0x53, 0xd8, 0xe4, 0x30, 0xf9, 0xcf, 0x9f, 0xaa, 0x5a, 0xa6,
0x9b, 0xef, 0x2d, 0x84, 0x45, 0x6d, 0x9b, 0x04, 0xc1, 0x09, 0x54, 0x97,
0x06, 0x58, 0xac, 0xa6, 0x0a, 0x4d, 0x56, 0xf5, 0xc6, 0x2f, 0xad, 0xf4,
0x69, 0x1f, 0x18, 0x95, 0x2c, 0x4e, 0xf1, 0xc5, 0xfe, 0x86, 0x21, 0xd8,
0x60, 0xc8, 0x72, 0xc8, 0xa4, 0x9b, 0xea, 0x80, 0x30, 0xae, 0x36, 0xd4,
0xa8, 0x30, 0x52, 0xc7, 0x09, 0x23, 0x7c, 0x08, 0x45, 0x58, 0xf7, 0x5c,
0x81, 0x26, 0x6b, 0x54, 0x7f, 0x65, 0x1d, 0x8d, 0xae, 0xd7, 0x6b, 0x8b,
0x1f, 0x75, 0x9f, 0xf2, 0xd0, 0x77, 0x14, 0x5a, 0x52, 0x95, 0x6d, 0x15,
0xd4, 0xdd, 0xd7, 0x04, 0x37, 0xcd, 0x4a, 0xbd, 0xc9, 0x4b, 0xa5, 0x32,
0xce, 0x59, 0xd7, 0x68, 0xd5, 0x14, 0x93, 0x9b, 0x7a, 0x33, 0x9e, 0x0f,
0xbe, 0x65, 0x1f, 0x82, 0xb3, 0xa5, 0x2d, 0xea, 0xa5, 0x6f, 0xaa, 0x72,
0xcc, 0xb6, 0xd5, 0xb2, 0xaa, 0x9c, 0xca, 0x9f, 0x03, 0xf9, 0x73, 0x2d,
0x7e, 0x2e, 0xe0, 0xdf, 0xef, 0xdf, 0xf8, 0xf7, 0x2c, 0x36, 0x1d, 0x9f,
0x06, 0xbd, 0x2b, 0xe1, 0xf4, 0x35, 0x50, 0xf5, 0x59, 0x1c, 0x91, 0x61,
0x21, 0xc7, 0x7f, 0x55, 0x8b, 0x56, 0xa3, 0x73, 0x30, 0x0c, 0x5d, 0x6d,
0x22, 0xbe, 0xae, 0xe9, 0x40, 0x2e, 0x72, 0x7f, 0x7a, 0x72, 0xc8, 0xb9,
0x77, 0x09, 0x36, 0x44, 0x03, 0xa8, 0x0b, 0x0d, 0xd7, 0x41, 0x9e, 0x9a,
0xf1, 0xc6, 0x9a, 0x20, 0x7c, 0xb1, 0x56, 0x9b, 0x33, 0x5d, 0x43, 0xc2,
0xd6, 0x91, 0xb1, 0xb8, 0x68, 0xcf, 0xf7, 0x81, 0x76, 0x57, 0x6f, 0x52,
0x3b, 0xa0, 0xaf, 0x08, 0x93, 0x9a, 0x9a, 0xd4, 0x27, 0xf3, 0xf3, 0xd5,
0xf9, 0x99, 0x01, 0xfa, 0x1d, 0x50, 0xcd, 0x05, 0x89, 0x04, 0x1e, 0xe8,
0x10, 0xbd, 0x06, 0x7d, 0xd5, 0x75, 0x83, 0x8f, 0x3c, 0xe4, 0x5d, 0x45,
0x51, 0x9b, 0x00, 0x5b, 0x2e, 0x30, 0x58, 0x70, 0xfe, 0xa4, 0xbf, 0x4a,
0x46, 0xd6, 0x4b, 0xa7, 0x84, 0xf7, 0x0c, 0xe1, 0x18, 0xb5, 0x4f, 0x9f,
0xfe, 0x3d, 0x9a, 0x02, 0x95, 0xbe, 0xb5, 0x3d, 0xe2, 0x34, 0xa8, 0x46,
0x03, 0x1c, 0x9c, 0x83, 0x2d, 0x46, 0xf4, 0xb2, 0x63, 0xe6, 0x04, 0x36,
0x35, 0xa3, 0x32, 0x09, 0x44, 0x07, 0x14, 0x80, 0x2b, 0x54, 0x71, 0x3b,
0x8a, 0x5a, 0x7a, 0x7b, 0x31, 0x90, 0x58, 0x32, 0xd5, 0xf5, 0x3e, 0x06,
0x4d, 0x85, 0x59, 0xe6, 0x42, 0x14, 0x40, 0x17, 0x14, 0x48, 0xbc, 0xcc,
0x85, 0xda, 0x82, 0xe2, 0x7a, 0x9c, 0xf5, 0xc3, 0x3e, 0x3e, 0x83, 0x81,
0x9a, 0x0b, 0x9f, 0x60, 0xac, 0x07, 0xe6, 0x07, 0x4f, 0xcb, 0x30, 0x8b,
0x0c, 0xcd, 0x05, 0xf0, 0x6c, 0x0b, 0xca, 0x33, 0xb1, 0x43, 0x6a, 0x2e,
0xa8, 0x25, 0x5e, 0x52, 0x17, 0x14, 0xd1, 0x8b, 0xc3, 0x37, 0x07, 0xde,
0x36, 0xc4, 0xcf, 0x7a, 0x55, 0xec, 0xb2, 0xa1, 0x96, 0x63, 0x84, 0x9d,
0x20, 0xf4, 0x3c, 0xd7, 0xe7, 0xd4, 0xc2, 0x46, 0x5e, 0x30, 0x3f, 0xaf,
0x21, 0x32, 0xca, 0x3a, 0x66, 0xa2, 0x02, 0x99, 0x28, 0x82, 0x2d, 0x6c,
0xdc, 0x6d, 0x5d, 0x9e, 0x1d, 0x9d, 0x1d, 0xc8, 0x2f, 0xc2, 0x20, 0xcc,
0x85, 0x28, 0x9e, 0x2d, 0x88, 0x2e, 0x60, 0x30, 0x0a, 0x38, 0xed, 0xcf,
0x3b, 0xad, 0xc0, 0x6b, 0x62, 0xee, 0x4a, 0x98, 0x13, 0xc8, 0xb7, 0xcc,
0x2e, 0x72, 0xa0, 0x03, 0xd3, 0x1d, 0xf4, 0xda, 0x8d, 0xf5, 0x96, 0x5f,
0x05, 0xdc, 0x66, 0xa0, 0x83, 0x18, 0xe3, 0x7e, 0x1b, 0xd1, 0x8f, 0xaa,
0x8f, 0x51, 0x1d, 0x14, 0xc1, 0x30, 0x81, 0x88, 0xb0, 0x38, 0x73, 0x61,
0x3a, 0xd6, 0x51, 0x2b, 0x0a, 0x1b, 0x83, 0x1e, 0xf8, 0xd0, 0x5c, 0x8f,
0xae, 0xe5, 0xda, 0xd6, 0xc2, 0xc6, 0xd1, 0xd9, 0xd1, 0xb5, 0xb2, 0x77,
0x79, 0x79, 0x7e, 0xa9, 0xcc, 0xc5, 0xe0, 0x9b, 0xbf, 0xa2, 0xf9, 0xb4,
0xac, 0x5e, 0x5d, 0x1c, 0xed, 0xef, 0x5f, 0xa9, 0x73, 0x66, 0x2c, 0x54,
0x50, 0x3e, 0x60, 0xe1, 0x4c, 0x28, 0xfd, 0x27, 0x8b, 0xf9, 0x00, 0x44,
0xfa, 0x89, 0x28, 0xa2, 0x98, 0xaa, 0x0c, 0x29, 0x40, 0xdc, 0xb8, 0x0c,
0x36, 0xe2, 0x51, 0x47, 0x53, 0x0f, 0xf6, 0xae, 0xc1, 0x54, 0xab, 0xd1,
0xb6, 0xe5, 0xb9, 0x9a, 0x0e, 0x9f, 0x02, 0x0a, 0x2a, 0xeb, 0x84, 0xb6,
0xad, 0xa7, 0xb6, 0x9b, 0xb7, 0x17, 0x70, 0x25, 0xc4, 0x86, 0x22, 0x44,
0x53, 0x05, 0x65, 0x0d, 0xe5, 0x9b, 0x5a, 0xa2, 0x91, 0x69, 0x95, 0xd4,
0xef, 0x0a, 0xbe, 0xe5, 0x0c, 0x26, 0x05, 0xd4, 0x26, 0x0e, 0xfa, 0xe6,
0x33, 0x3a, 0xd8, 0x91, 0x85, 0x07, 0x7a, 0x02, 0xe9, 0x5d, 0xe6, 0xe6,
0x66, 0xf3, 0x85, 0x3c, 0xd3, 0x6d, 0x0e, 0x9e, 0xd0, 0x88, 0x3b, 0x39,
0x3f, 0x7f, 0xce, 0xcd, 0x81, 0xf8, 0x3b, 0xcc, 0xef, 0x6b, 0xea, 0x4e,
0x0f, 0xd3, 0xb7, 0x40, 0xe1, 0xae, 0x32, 0x72, 0x43, 0x5f, 0x89, 0xe1,
0x28, 0x03, 0x66, 0xdb, 0x4a, 0x0b, 0x62, 0x9b, 0x1b, 0x70, 0x85, 0x75,
0xf0, 0xab, 0x82, 0x4a, 0xc3, 0x9c, 0x10, 0x18, 0x81, 0x6a, 0xe8, 0x08,
0x8b, 0x01, 0xf0, 0xbb, 0x2c, 0x68, 0x13, 0xdf, 0x82, 0x5d, 0x02, 0x6d,
0xae, 0xae, 0x23, 0x2f, 0x52, 0xac, 0x8b, 0xa7, 0xd1, 0xb7, 0x7c, 0xd8,
0x14, 0xc6, 0xe6, 0x1c, 0x9d, 0x1d, 0x77, 0xac, 0x04, 0x6c, 0x7e, 0x45,
0x8a, 0x02, 0xac, 0xb8, 0x10, 0xb1, 0x76, 0x1f, 0xf4, 0x8a, 0x0a, 0x19,
0x24, 0x4e, 0x19, 0x38, 0xef, 0xf3, 0xe0, 0x8e, 0xf1, 0x9e, 0xa6, 0x56,
0x55, 0xfd, 0xe7, 0x4f, 0x8d, 0x9a, 0xf0, 0x50, 0xa2, 0x7a, 0x99, 0x1a,
0x20, 0xcd, 0xf4, 0x13, 0x92, 0x6c, 0xc2, 0x02, 0x1b, 0xca, 0x2c, 0x48,
0xd2, 0x2a, 0x75, 0x1d, 0xa7, 0x04, 0x61, 0x2b, 0xe0, 0x3e, 0xc6, 0xcb,
0x1a, 0xbc, 0x81, 0xd1, 0xf1, 0x23, 0xcc, 0xef, 0xce, 0x3b, 0x62, 0x4d,
0x56, 0x7a, 0xe0, 0x63, 0x38, 0x3d, 0x14, 0x15, 0x0e, 0xc4, 0x56, 0x07,
0xa3, 0xab, 0x74, 0xc4, 0xb3, 0x28, 0x03, 0x14, 0x48, 0xfa, 0x51, 0xae,
0x8f, 0xbe, 0x6b, 0xaa, 0xa8, 0x22, 0x55, 0xc8, 0x4e, 0x84, 0x4a, 0x9b,
0x2a, 0x1a, 0xac, 0x5a, 0x26, 0x46, 0x3f, 0x44, 0x7b, 0x07, 0x8b, 0x9b,
0xab, 0xc3, 0x1b, 0x9a, 0xb0, 0xa9, 0x62, 0x57, 0x4d, 0x05, 0xed, 0x24,
0x1e, 0x68, 0xae, 0xb5, 0xd3, 0x63, 0xb6, 0xa5, 0x11, 0x5d, 0x84, 0x01,
0xeb, 0xdd, 0x0d, 0x2c, 0x03, 0x9c, 0x8c, 0xea, 0x81, 0x47, 0x3e, 0x12,
0x23, 0x65, 0x2b, 0xda, 0x11, 0xd3, 0x0f, 0x7c, 0x93, 0x7b, 0xe0, 0x04,
0x7c, 0x8b, 0x22, 0xe9, 0xad, 0x70, 0x74, 0xc0, 0x82, 0x89, 0x6d, 0x2d,
0xb9, 0xad, 0x3d, 0x73, 0xdb, 0x56, 0x08, 0xf5, 0x07, 0x48, 0xb2, 0x69,
0x67, 0x43, 0xd8, 0x8d, 0x87, 0x61, 0x69, 0x12, 0x98, 0x2d, 0x81, 0xb1,
0xf7, 0x81, 0x31, 0x41, 0x84, 0x34, 0xee, 0x32, 0x9b, 0xb4, 0x6e, 0x6c,
0xc2, 0xe2, 0x70, 0x66, 0xc3, 0xd3, 0xa7, 0x5d, 0x9c, 0x9a, 0xdf, 0x8f,
0xc9, 0xfd, 0x82, 0xf7, 0xf7, 0x0b, 0xf2, 0xb0, 0xf6, 0x85, 0x70, 0xf2,
0xc0, 0x02, 0x09, 0xac, 0x3d, 0x13, 0x18, 0x3a, 0x3c, 0x00, 0xd5, 0x16,
0xa8, 0xcb, 0xfa, 0x72, 0x5b, 0xc0, 0x07, 0x6f, 0xd3, 0x2e, 0x26, 0x21,
0xbf, 0x43, 0x5b, 0xee, 0xe0, 0xbf, 0x8f, 0xae, 0x2f, 0xf6, 0x88, 0xed,
0xae, 0xec, 0x67, 0xb1, 0x47, 0xcb, 0xc5, 0x21, 0xb9, 0xa1, 0x6c, 0x6e,
0x9c, 0x40, 0xea, 0x6c, 0xaa, 0x4b, 0x50, 0x40, 0xe1, 0x97, 0xd4, 0xe6,
0x6a, 0x80, 0x58, 0x16, 0x03, 0x5f, 0x62, 0xd0, 0x7f, 0x1f, 0x83, 0xbe,
0xc0, 0x20, 0x63, 0xca, 0xe5, 0x7e, 0x16, 0x89, 0xc8, 0x75, 0xe0, 0xe8,
0xec, 0xdd, 0xfa, 0x72, 0xb7, 0xf0, 0xfd, 0xdd, 0x42, 0xb1, 0x5b, 0x8f,
0xda, 0x9e, 0xd8, 0x2a, 0xcc, 0x6e, 0xb5, 0xa9, 0x4e, 0x80, 0x0d, 0x25,
0x58, 0xef, 0x3d, 0x41, 0x79, 0x92, 0x89, 0x51, 0x3c, 0xf0, 0x72, 0x19,
0x58, 0x94, 0xa8, 0xea, 0x93, 0x22, 0xf2, 0xc0, 0xc4, 0x21, 0xe7, 0x6a,
0x0b, 0x27, 0x9c, 0x66, 0x5d, 0x32, 0xdf, 0x84, 0xa4, 0xcb, 0x24, 0x06,
0x1a, 0x76, 0x60, 0x40, 0x34, 0xee, 0xf2, 0x5e, 0x9c, 0x21, 0x45, 0xa3,
0xdf, 0x6a, 0xdf, 0x85, 0xf5, 0x35, 0x9f, 0x5d, 0xa8, 0x35, 0x6a, 0x60,
0x7d, 0x22, 0xbf, 0x28, 0xf0, 0x69, 0xd1, 0x17, 0xe1, 0xd9, 0xa2, 0x67,
0x3d, 0x9e, 0x6e, 0x4e, 0xba, 0xc7, 0x78, 0x42, 0x09, 0x67, 0x73, 0x08,
0x79, 0x36, 0xa2, 0x08, 0x6e, 0xef, 0x29, 0x87, 0xe1, 0x34, 0x7a, 0xf3,
0xf3, 0x8e, 0x81, 0x67, 0x97, 0xd2, 0x5a, 0xb5, 0x14, 0xcb, 0x78, 0x2b,
0x7d, 0x5c, 0x0e, 0x0a, 0x61, 0x49, 0xb2, 0x62, 0xfc, 0xc1, 0x9f, 0xf6,
0x35, 0xbd, 0xa9, 0xaa, 0xa6, 0x69, 0xf2, 0xcd, 0x28, 0x5c, 0x6e, 0x29,
0x71, 0x4e, 0xa2, 0xf4, 0x43, 0x08, 0x49, 0x10, 0x9a, 0xba, 0x50, 0xce,
0x61, 0x7d, 0xc0, 0xf3, 0x8e, 0x3a, 0x5e, 0xb1, 0x1f, 0xe7, 0x30, 0x72,
0x01, 0x54, 0x8b, 0x10, 0x8c, 0x2c, 0x08, 0x6c, 0xbc, 0xa7, 0x10, 0x65,
0xa1, 0xba, 0xa0, 0x00, 0xdf, 0x7d, 0xd2, 0x86, 0x7c, 0x0b, 0x80, 0x48,
0xd4, 0x77, 0x84, 0x68, 0xa1, 0xce, 0x19, 0x83, 0x3f, 0xf8, 0x9f, 0x63,
0x2a, 0x18, 0xa9, 0x14, 0x22, 0xab, 0x4d, 0x60, 0x0b, 0xd2, 0xe1, 0x25,
0x14, 0x0c, 0x24, 0xb1, 0x79, 0x14, 0x00, 0x07, 0xbf, 0x10, 0x07, 0x6e,
0xa0, 0xa9, 0x6a, 0xf0, 0xbd, 0x3f, 0xe3, 0x7b, 0x64, 0x48, 0x38, 0x25,
0x9c, 0x05, 0x02, 0xd2, 0x92, 0xab, 0x1e, 0x64, 0x72, 0xed, 0x10, 0x13,
0x89, 0xf1, 0x64, 0xa4, 0xba, 0x86, 0xe4, 0x18, 0xe2, 0x14, 0x93, 0x24,
0x3b, 0x33, 0xa3, 0x94, 0x1a, 0x35, 0xad, 0x00, 0x7f, 0x3e, 0xd3, 0x44,
0x2c, 0xf6, 0x0c, 0x16, 0x92, 0xa6, 0x44, 0x6f, 0xc6, 0xff, 0x34, 0xd6,
0xe8, 0x91, 0x86, 0xbe, 0x11, 0xfd, 0xa5, 0x63, 0x9c, 0xce, 0xd8, 0xa4,
0x43, 0x7c, 0x67, 0x59, 0xec, 0x4f, 0x67, 0xac, 0x76, 0x26, 0x87, 0x45,
0x4b, 0x01, 0xc7, 0x33, 0xb6, 0xbd, 0xce, 0xfa, 0x5d, 0x25, 0xf0, 0xdb,
0x98, 0xc7, 0x53, 0xcc, 0xea, 0xa3, 0x84, 0x17, 0x92, 0xfe, 0x4a, 0xd2,
0x05, 0xfa, 0xad, 0x89, 0x45, 0x40, 0x25, 0xdb, 0x48, 0x53, 0xa2, 0x3e,
0x31, 0x09, 0xb9, 0xdb, 0x54, 0x72, 0x07, 0x6b, 0xcd, 0x05, 0x05, 0x52,
0xee, 0x8c, 0x40, 0xfe, 0x3f, 0xb1, 0x2b, 0x5b, 0x14, 0xa4, 0x95, 0xc0,
0xc2, 0xc6, 0x56, 0x1b, 0xaa, 0x06, 0xb1, 0x0d, 0xa4, 0x99, 0xa1, 0x6d,
0x09, 0x7b, 0x15, 0xf9, 0x27, 0x64, 0x4c, 0x96, 0x82, 0x9d, 0x63, 0x85,
0xf7, 0xa8, 0x22, 0xce, 0x3c, 0x1d, 0x8a, 0xf6, 0xec, 0xcb, 0xd1, 0x2a,
0x2e, 0xab, 0x92, 0x36, 0x35, 0x1e, 0x03, 0xc5, 0x50, 0x76, 0x65, 0x06,
0x02, 0xee, 0x16, 0x93, 0x5a, 0xcc, 0x52, 0x14, 0xd4, 0x4f, 0xea, 0x43,
0xf1, 0x1b, 0x17, 0x2b, 0xa0, 0xb3, 0xa2, 0x76, 0xe2, 0x43, 0x1e, 0xe7,
0xd2, 0x89, 0xe0, 0x92, 0x36, 0x8b, 0x22, 0x44, 0xb3, 0x00, 0xa5, 0x0d,
0x4c, 0xdf, 0x90, 0x65, 0x33, 0x2f, 0x2a, 0x9b, 0x79, 0x41, 0xd9, 0x3c,
0x93, 0x95, 0x99, 0x2d, 0x81, 0x91, 0x88, 0x5e, 0xf4, 0x6a, 0xf2, 0x1e,
0x0b, 0x72, 0xb9, 0xff, 0xb8, 0xcc, 0xb3, 0xc5, 0x06, 0xb8, 0x6c, 0x2e,
0x2b, 0x8c, 0x4c, 0x5a, 0xe9, 0xa7, 0x4e, 0xa9, 0xaa, 0x6d, 0x36, 0xfe,
0xc3, 0xd0, 0xbe, 0xfd, 0xa7, 0xf1, 0xbd, 0xa4, 0xeb, 0x9b, 0xff, 0x56,
0x35, 0xe8, 0x90, 0xa2, 0xee, 0x7c, 0xab, 0x7f, 0xc7, 0x52, 0x5b, 0x46,
0x09, 0xf0, 0xda, 0x5c, 0x0f, 0xc0, 0x09, 0xb6, 0x7b, 0xe0, 0x8a, 0xb8,
0x7b, 0xe2, 0x02, 0x6b, 0x76, 0x08, 0x54, 0xea, 0xba, 0xfe, 0xda, 0x86,
0x5f, 0xc4, 0x50, 0x6d, 0x88, 0xa7, 0x1e, 0xef, 0xa7, 0x4f, 0x76, 0xf4,
0xf8, 0x18, 0x24, 0x0f, 0x10, 0x4c, 0xe5, 0x63, 0x3b, 0x9e, 0x16, 0xbf,
0x7b, 0x5e, 0xfc, 0x14, 0xc4, 0xd3, 0x87, 0x08, 0x21, 0xca, 0xbb, 0xeb,
0x19, 0x77, 0xd3, 0xff, 0xf3, 0x29, 0xf0, 0x9c, 0x6e, 0x8c, 0xa3, 0x97,
0x3e, 0xd1, 0xf8, 0xb1, 0xcb, 0x3a, 0xd1, 0x13, 0x6b, 0xbb, 0x85, 0x38,
0x85, 0x9a, 0x53, 0x76, 0xf5, 0xa8, 0xa9, 0x31, 0xcb, 0xa9, 0x85, 0x36,
0xf8, 0x34, 0x27, 0x17, 0xcf, 0xa9, 0x1e, 0x69, 0xc9, 0xac, 0x35, 0x36,
0x83, 0x35, 0x34, 0xb7, 0x86, 0xeb, 0x65, 0x6c, 0xa3, 0x6c, 0xaa, 0x78,
0xc7, 0xa0, 0x03, 0x65, 0xa6, 0x05, 0xd1, 0x04, 0x53, 0x6c, 0xb7, 0xa3,
0x80, 0x52, 0x6f, 0x02, 0x8d, 0x93, 0xf6, 0xb3, 0x71, 0x0b, 0x0a, 0x1d,
0x97, 0xc4, 0xa8, 0x24, 0x45, 0xe1, 0x79, 0x3d, 0xdf, 0xf9, 0x4a, 0xf5,
0x30, 0xd8, 0x1e, 0xed, 0x60, 0x77, 0xe0, 0x0c, 0xc2, 0x93, 0xa6, 0x66,
0xae, 0x89, 0x80, 0x3e, 0xc6, 0x81, 0x3c, 0xbf, 0x56, 0xf6, 0xd4, 0x24,
0xba, 0x8e, 0x5e, 0x2e, 0x2a, 0x44, 0xe7, 0xe7, 0xdb, 0x40, 0xc4, 0x58,
0xc4, 0xb7, 0x29, 0x74, 0xf7, 0xc0, 0x40, 0xff, 0x69, 0xe8, 0x32, 0x03,
0x4d, 0xf6, 0xc6, 0xb7, 0x25, 0xda, 0x7d, 0xf8, 0x81, 0x0a, 0xaf, 0x00,
0xf9, 0x0b, 0x19, 0xdf, 0xfe, 0x69, 0xf8, 0x07, 0x02, 0x6f, 0xa1, 0x6e,
0xb3, 0xab, 0xc4, 0x02, 0x75, 0x23, 0x98, 0x72, 0x4e, 0x11, 0xb9, 0xeb,
0x0e, 0x84, 0x0b, 0x4b, 0xa8, 0x24, 0xb3, 0xb3, 0x9f, 0xe6, 0x5f, 0x48,
0x2b, 0x37, 0xdd, 0x37, 0x2a, 0xfd, 0x08, 0xc9, 0x8a, 0x38, 0x50, 0xc0,
0x30, 0x04, 0x61, 0x97, 0x97, 0xd4, 0xcd, 0xf8, 0x83, 0xc9, 0xfd, 0x90,
0xaa, 0xe3, 0x77, 0x2a, 0xdb, 0x02, 0xa6, 0x58, 0x98, 0x0d, 0x4f, 0x31,
0xe5, 0x52, 0xa4, 0x8f, 0xd5, 0x53, 0x40, 0x30, 0xe1, 0x8b, 0xf5, 0x2f,
0x96, 0x7e, 0xe4, 0x4f, 0x3c, 0x08, 0x74, 0x1e, 0xe0, 0x2e, 0x51, 0x52,
0xd4, 0x92, 0x5b, 0x52, 0x21, 0xbc, 0x41, 0x3d, 0x01, 0x2e, 0x28, 0xb4,
0xed, 0x39, 0x93, 0xcf, 0xcf, 0xf3, 0x39, 0xd3, 0x9d, 0x9f, 0x1f, 0x69,
0x6e, 0x19, 0xf2, 0xd7, 0x77, 0xea, 0xec, 0x02, 0x26, 0xd8, 0x7a, 0xd9,
0x2e, 0xd0, 0x0c, 0x6a, 0x53, 0x9e, 0xd2, 0x3f, 0xa3, 0x16, 0xf8, 0xeb,
0x34, 0xa2, 0x8b, 0x7a, 0x9e, 0x7a, 0x66, 0x1b, 0xcf, 0x25, 0xca, 0xce,
0x54, 0xef, 0xa4, 0x28, 0xe1, 0xcc, 0xb6, 0x4f, 0x64, 0xf7, 0xbf, 0xed,
0xbb, 0xb6, 0x7d, 0xed, 0x7a, 0x9b, 0x33, 0xc6, 0xd3, 0xa3, 0x86, 0xf8,
0x21, 0x82, 0x98, 0x4e, 0x29, 0x5b, 0x85, 0x40, 0xb1, 0x24, 0x2e, 0x84,
0x8a, 0x1f, 0xde, 0x03, 0x8b, 0x73, 0xca, 0xb6, 0x49, 0x0d, 0xe0, 0x2c,
0x8c, 0xde, 0x97, 0xac, 0x32, 0x4b, 0xde, 0x1e, 0x4a, 0xa4, 0xe9, 0xca,
0x03, 0x8e, 0x33, 0xd1, 0x6a, 0xc9, 0x32, 0x11, 0x7b, 0x8e, 0x85, 0x49,
0x57, 0x3c, 0x8e, 0xa7, 0x5c, 0xa6, 0x5d, 0x52, 0xb1, 0x54, 0x8f, 0xc7,
0xb8, 0xeb, 0x99, 0x4c, 0x0e, 0x69, 0xce, 0x66, 0xd8, 0x48, 0x24, 0xf9,
0xaf, 0x0c, 0x7a, 0x93, 0x9e, 0x6b, 0xc2, 0xd8, 0x5d, 0xdd, 0x68, 0xe3,
0xcc, 0x33, 0xd7, 0x92, 0x85, 0x6e, 0x74, 0x61, 0xa2, 0xfc, 0x8e, 0x5d,
0xe3, 0x39, 0xcd, 0xa4, 0x69, 0x13, 0x48, 0xc7, 0xa6, 0x15, 0x7b, 0x07,
0x18, 0x4f, 0xbc, 0xe0, 0xef, 0x33, 0xed, 0x99, 0x8e, 0x6e, 0x16, 0xed,
0xe6, 0x5c, 0xfd, 0x4f, 0x33, 0xeb, 0x4b, 0x79, 0x26, 0xf4, 0xf7, 0xd9,
0x75, 0x4f, 0xd8, 0xb5, 0x38, 0x2c, 0x28, 0x10, 0xce, 0xde, 0x10, 0xfe,
0xb5, 0xfe, 0x2f, 0x89, 0xa6, 0x26, 0x29, 0x7a, 0xa7, 0x23, 0x59, 0x20,
0x1f, 0xa6, 0xe7, 0x5b, 0x8f, 0xb3, 0x63, 0x4f, 0x71, 0x47, 0xe2, 0x9f,
0x1a, 0x7b, 0x66, 0xb7, 0x49, 0x0b, 0x98, 0x10, 0xe8, 0xe5, 0xe0, 0xdd,
0xd8, 0x53, 0xdc, 0x3b, 0xfa, 0xcb, 0x63, 0x8f, 0x2e, 0x88, 0x9a, 0x38,
0x48, 0xce, 0x22, 0xef, 0xc6, 0x7d, 0x61, 0xd7, 0x70, 0x3b, 0x9d, 0x80,
0xf2, 0x3b, 0xac, 0xfb, 0xcb, 0xed, 0xe4, 0xfd, 0x50, 0xd4, 0xfd, 0xe2,
0x78, 0xb5, 0xef, 0x86, 0x01, 0x75, 0x43, 0x9e, 0x23, 0x41, 0x4b, 0x3c,
0xff, 0xba, 0xfd, 0xf3, 0x67, 0xf2, 0xb2, 0x61, 0x97, 0x82, 0xf4, 0xf5,
0x61, 0x9d, 0x65, 0x5e, 0x36, 0x58, 0xa9, 0x0d, 0x59, 0xe0, 0x5f, 0x45,
0x7c, 0x2e, 0xe6, 0x7a, 0x7f, 0x2c, 0xe6, 0x0a, 0xb1, 0xba, 0xd8, 0x05,
0xc5, 0xc6, 0x96, 0x89, 0xc7, 0xeb, 0x6a, 0x23, 0x6e, 0x23, 0xbe, 0x93,
0xa8, 0x46, 0x3d, 0xd4, 0xa8, 0x14, 0xf3, 0x21, 0x3e, 0x6c, 0x92, 0xc9,
0xeb, 0x00, 0x58, 0x97, 0x62, 0xa2, 0xce, 0x81, 0xee, 0xa9, 0x8f, 0xac,
0xdf, 0x55, 0xf3, 0x99, 0x2d, 0x24, 0x86, 0xca, 0x3a, 0xdb, 0xd0, 0x26,
0x4e, 0x77, 0x1d, 0x40, 0x48, 0x5f, 0xaf, 0xb2, 0x8d, 0xe9, 0x13, 0x11,
0x3c, 0xdb, 0x2b, 0x50, 0x30, 0xc2, 0x39, 0x05, 0x03, 0xc0, 0x1c, 0x5c,
0x43, 0xf2, 0xf4, 0xb1, 0x9c, 0x28, 0x59, 0x0a, 0xf8, 0x87, 0xb9, 0xe9,
0xd4, 0xc0, 0x8e, 0x18, 0x50, 0x15, 0x35, 0x22, 0x34, 0x71, 0x4c, 0x04,
0x31, 0xf7, 0xc2, 0x77, 0x3d, 0xd2, 0x25, 0xb2, 0x41, 0x50, 0xc6, 0x44,
0x06, 0xa1, 0x89, 0xd3, 0xb2, 0xb2, 0x9b, 0x72, 0xbb, 0xf3, 0xd7, 0x73,
0x7b, 0xf2, 0xf0, 0x48, 0x38, 0xb1, 0x96, 0x0b, 0xf9, 0x40, 0x52, 0x61,
0x3b, 0xf3, 0xf3, 0xf0, 0x9f, 0x46, 0xd2, 0x7e, 0xbb, 0x1a, 0x3f, 0xa9,
0xfa, 0xaf, 0x9e, 0x25, 0x89, 0x4b, 0x8e, 0xa9, 0x78, 0xad, 0x7c, 0xdb,
0x63, 0xea, 0x88, 0x68, 0x56, 0x4f, 0x9c, 0xc4, 0xde, 0x76, 0x7e, 0xbe,
0x17, 0x4b, 0xa2, 0x38, 0x32, 0x24, 0x33, 0x37, 0x49, 0x26, 0x78, 0x36,
0x34, 0x32, 0xe1, 0xaf, 0x11, 0xc6, 0x9f, 0x2d, 0xce, 0x7a, 0x5e, 0x9c,
0x2e, 0x66, 0x55, 0x98, 0x82, 0x44, 0xc4, 0xe7, 0xaf, 0x56, 0x64, 0xee,
0xdb, 0xe0, 0xc9, 0xa9, 0xb8, 0x69, 0xe1, 0x14, 0xdf, 0xb4, 0x70, 0x72,
0x37, 0x2d, 0x66, 0x07, 0x28, 0x91, 0x02, 0xc5, 0x1d, 0x15, 0x79, 0x24,
0x19, 0x4d, 0x10, 0xfc, 0x6d, 0x82, 0xb3, 0x56, 0x6f, 0x4e, 0xb0, 0xc7,
0x60, 0x70, 0xd2, 0x45, 0xf7, 0x30, 0x3f, 0x4f, 0x73, 0xc6, 0x0f, 0x1e,
0x7c, 0x2e, 0x65, 0x81, 0xb8, 0x06, 0x64, 0x04, 0xae, 0xcf, 0xb5, 0xfc,
0x60, 0x7a, 0xb1, 0x05, 0x95, 0x08, 0xc1, 0xc1, 0xef, 0x26, 0x15, 0x47,
0x12, 0x50, 0x5c, 0xb7, 0x89, 0x4d, 0x77, 0x20, 0x72, 0x10, 0x9f, 0x42,
0x59, 0x8d, 0x83, 0x7a, 0x43, 0xce, 0x9d, 0xfa, 0x88, 0x83, 0x71, 0x31,
0xeb, 0xbc, 0x9d, 0x7a, 0xce, 0x3e, 0x2c, 0xcd, 0x29, 0x91, 0xa3, 0x27,
0x37, 0x73, 0x5c, 0xc0, 0x4c, 0x3a, 0x3c, 0xa8, 0x07, 0x6a, 0x4d, 0xb2,
0xee, 0x36, 0x49, 0xa9, 0x24, 0x19, 0x64, 0x41, 0xd6, 0xcd, 0xbf, 0x91,
0xef, 0x4d, 0x2b, 0x3a, 0x41, 0x35, 0x4d, 0xd3, 0x96, 0x74, 0xa0, 0xd3,
0xb3, 0x05, 0xda, 0xf0, 0x83, 0xf7, 0xd9, 0xf4, 0x46, 0x27, 0x19, 0xc2,
0x5e, 0x7d, 0x5e, 0x67, 0xc7, 0x63, 0x88, 0x12, 0x99, 0x6b, 0x2f, 0xce,
0xc4, 0xb5, 0x97, 0xd4, 0xb2, 0x7e, 0x35, 0xb5, 0x78, 0xe3, 0xba, 0xc2,
0x1b, 0xd9, 0x08, 0x18, 0x11, 0x7a, 0xc9, 0xc4, 0xd3, 0x83, 0x91, 0x8e,
0x33, 0x2e, 0x9d, 0x68, 0xc2, 0xc5, 0xfc, 0x61, 0x9d, 0xe4, 0xc5, 0x3a,
0xc9, 0xa5, 0x4e, 0x2a, 0xb0, 0xca, 0x89, 0x2b, 0x87, 0xc9, 0xe3, 0x24,
0x10, 0x05, 0xcf, 0x31, 0x03, 0x92, 0x85, 0xdc, 0xfb, 0x9c, 0x09, 0x1a,
0x18, 0xdd, 0x51, 0xba, 0x20, 0x1c, 0x9b, 0x7a, 0x39, 0xde, 0x95, 0xb3,
0xdf, 0xf0, 0x50, 0x3c, 0xe3, 0x8f, 0x9d, 0xe8, 0xbe, 0xc9, 0x1b, 0xcb,
0x9b, 0x13, 0x97, 0xa7, 0x32, 0x5c, 0xe8, 0x49, 0x45, 0xce, 0x12, 0x5c,
0x2b, 0xab, 0x18, 0x54, 0xb0, 0x7b, 0x1c, 0x35, 0xf4, 0x0d, 0xc3, 0x00,
0x8f, 0x37, 0xec, 0xdb, 0x48, 0x76, 0x41, 0xfb, 0x37, 0xfe, 0x14, 0x37,
0x81, 0x5d, 0x2d, 0x1a, 0x28, 0x23, 0xf0, 0xf4, 0x6b, 0xf6, 0xd6, 0x08,
0xfe, 0x1f, 0x54, 0x9b, 0x16, 0xf3, 0x4d, 0xd8, 0x42, 0x5c, 0x1d, 0x89,
0x67, 0x15, 0x5d, 0x20, 0xb1, 0xb2, 0xe6, 0xec, 0xb9, 0x9e, 0xa6, 0xa3,
0x0d, 0x03, 0x9d, 0x65, 0x9a, 0x99, 0x35, 0x2a, 0xa4, 0x45, 0xe4, 0x6c,
0x19, 0x62, 0xb0, 0x25, 0x1e, 0x5d, 0x3f, 0x92, 0x74, 0xf1, 0x82, 0xd3,
0x42, 0x2e, 0xce, 0x09, 0xf9, 0xaf, 0x10, 0x4d, 0x12, 0xa2, 0x79, 0x6c,
0xc6, 0xb8, 0x68, 0xdf, 0xf5, 0xfb, 0xbb, 0x84, 0x93, 0xa4, 0x62, 0xd4,
0xa2, 0x8b, 0x02, 0x3c, 0xb5, 0x20, 0x08, 0xfe, 0x7e, 0x5b, 0x74, 0xb7,
0xf3, 0xac, 0xba, 0xb8, 0x11, 0xac, 0xc2, 0xe6, 0xbe, 0x3a, 0xc9, 0xa0,
0x0c, 0xdd, 0x5d, 0xe4, 0xce, 0x04, 0xd5, 0x22, 0x6f, 0xfc, 0x5f, 0x88,
0x30, 0xa1, 0x46, 0xcf, 0x34, 0xfd, 0x13, 0x5a, 0xf8, 0x04, 0x2d, 0x53,
0x98, 0xef, 0xee, 0x9d, 0xec, 0x5d, 0xef, 0xcd, 0x42, 0x1e, 0xbc, 0x6b,
0x64, 0x76, 0x3c, 0x57, 0xd5, 0x67, 0x2e, 0xea, 0x63, 0x93, 0x11, 0x0f,
0x93, 0xab, 0xb3, 0x8f, 0x64, 0x26, 0xfc, 0x1d, 0xf0, 0x53, 0x1c, 0x1f,
0xb4, 0x6d, 0x4a, 0xfc, 0x53, 0xc2, 0x9c, 0x0b, 0xe2, 0x50, 0xfb, 0x0f,
0x1d, 0x48, 0xfc, 0xfd, 0x47, 0x42, 0x16, 0x7b, 0x8e, 0x0f, 0x5f, 0xa6,
0xae, 0x74, 0x2f, 0x6c, 0x68, 0xe8, 0x98, 0xc5, 0x69, 0x50, 0x07, 0xaf,
0x8f, 0x29, 0x78, 0xec, 0x03, 0xa2, 0x20, 0xf2, 0x44, 0x37, 0xb9, 0x8e,
0x06, 0x39, 0x1d, 0xc0, 0xd9, 0x50, 0xc7, 0x92, 0x11, 0x19, 0x27, 0x91,
0x0b, 0xe7, 0xd9, 0xdb, 0xac, 0xea, 0x96, 0x9c, 0x84, 0x9a, 0x82, 0x72,
0xc7, 0xbb, 0x80, 0x39, 0x8d, 0xf9, 0xa5, 0x33, 0xb7, 0xe2, 0x36, 0xad,
0x86, 0x07, 0x34, 0x33, 0x7b, 0xfb, 0x78, 0xb6, 0xd1, 0x48, 0x3b, 0xd1,
0x14, 0xd3, 0x5b, 0x98, 0x8e, 0xe7, 0xa3, 0x8d, 0xbc, 0x28, 0x35, 0xcc,
0x53, 0xa2, 0x13, 0xa2, 0xc4, 0xd5, 0x4d, 0xde, 0x18, 0x88, 0xaf, 0xbb,
0x99, 0xd9, 0xeb, 0x6e, 0x7a, 0x8e, 0xe4, 0xcb, 0xe4, 0x2e, 0x28, 0x9e,
0x80, 0x4b, 0x32, 0xa5, 0xe1, 0x27, 0x9e, 0x7c, 0xe6, 0xfc, 0x2c, 0x6b,
0x2a, 0x75, 0xd3, 0x2c, 0xb8, 0xef, 0x94, 0x01, 0xf5, 0x9a, 0x5e, 0x8d,
0xfd, 0xf6, 0xbd, 0xec, 0x98, 0xb4, 0x59, 0xa9, 0x63, 0x72, 0x33, 0xb9,
0x66, 0x7e, 0x7e, 0xe6, 0xe5, 0x35, 0x08, 0x1d, 0x3a, 0x37, 0xbc, 0x30,
0xe8, 0x61, 0x35, 0xe6, 0x14, 0x85, 0x16, 0x3c, 0x7a, 0x77, 0x36, 0xa3,
0x49, 0x08, 0xae, 0x91, 0x2e, 0x10, 0xb6, 0x16, 0x71, 0x2d, 0xbd, 0x97,
0x60, 0xe6, 0xb3, 0x98, 0x09, 0xd7, 0x21, 0xe7, 0x44, 0xe4, 0xfe, 0x8d,
0x6e, 0x52, 0xde, 0xd9, 0x2a, 0x88, 0x20, 0x17, 0xff, 0xdd, 0xc9, 0xb5,
0xf5, 0xb4, 0x0d, 0x43, 0xe1, 0xbf, 0x02, 0xd6, 0x54, 0x25, 0x23, 0x34,
0x30, 0xed, 0x61, 0x4b, 0x49, 0x10, 0xa0, 0x49, 0x93, 0x36, 0xc4, 0xa4,
0xb0, 0x27, 0x84, 0x50, 0x92, 0x06, 0x1a, 0x48, 0x63, 0xd4, 0x84, 0xb1,
0xaa, 0xea, 0x7f, 0xdf, 0xb9, 0x38, 0xa9, 0xed, 0xa6, 0x65, 0xf4, 0xa5,
0x4d, 0x63, 0xc7, 0xb1, 0x8f, 0x8f, 0xcf, 0xf5, 0x3b, 0xbd, 0x8a, 0xb7,
0xc9, 0xc5, 0xd5, 0x6a, 0x19, 0x47, 0x60, 0xb0, 0x88, 0xb5, 0x56, 0xea,
0xb1, 0xb6, 0xb3, 0x5b, 0x31, 0x71, 0xbb, 0x2c, 0x35, 0xdf, 0x4d, 0x8a,
0x6e, 0x91, 0xff, 0x08, 0xcf, 0xe0, 0xb9, 0xd1, 0x72, 0x6d, 0xe8, 0xc2,
0x37, 0x12, 0x4d, 0xec, 0x46, 0xb1, 0xed, 0xdd, 0xa7, 0x4b, 0x05, 0x48,
0xa4, 0xa2, 0x02, 0x59, 0xf4, 0xff, 0x69, 0xc7, 0xaa, 0x4d, 0x3b, 0x56,
0x7a, 0xaa, 0xb4, 0x1b, 0x6a, 0x94, 0xc2, 0xeb, 0x9f, 0x46, 0xab, 0xd4,
0x29, 0xb4, 0x50, 0xe2, 0x54, 0x6f, 0xc0, 0xf4, 0x29, 0xdc, 0x7f, 0x4c,
0xfe, 0x24, 0x0c, 0xc6, 0x37, 0x5a, 0x33, 0x3d, 0x81, 0x0a, 0xdd, 0xb2,
0x3b, 0xbc, 0x32, 0x7a, 0x74, 0x09, 0xd5, 0x7a, 0x75, 0xf9, 0x3c, 0x79,
0xee, 0x4b, 0xd4, 0x76, 0xf9, 0x59, 0xca, 0xbd, 0xc2, 0x06, 0x74, 0x9a,
0x67, 0xc9, 0x6b, 0x42, 0xd8, 0x0a, 0x66, 0xbd, 0x60, 0x7b, 0xa9, 0x2c,
0x65, 0x88, 0xd3, 0x6e, 0x6d, 0xd5, 0x90, 0xbc, 0x40, 0x07, 0x0c, 0x72,
0x67, 0x65, 0xc0, 0xc2, 0x4d, 0x09, 0x37, 0x25, 0xe3, 0xff, 0x40, 0x0a,
0xe7, 0x7a, 0xff, 0x04, 0x5d, 0x46, 0x6e, 0x02, 0x7e, 0xa9, 0x3c, 0x35,
0x7f, 0x35, 0x52, 0xdb, 0xc2, 0xd4, 0x72, 0x5b, 0x28, 0x20, 0x9a, 0x3b,
0x5e, 0x11, 0x62, 0x02, 0x1f, 0x37, 0x1b, 0xe3, 0xc9, 0x1a, 0x52, 0xc4,
0xe4, 0xda, 0xd6, 0x1e, 0x2d, 0x95, 0x3d, 0x3a, 0x18, 0x98, 0x06, 0x69,
0x09, 0x1c, 0x01, 0x4a, 0x54, 0x17, 0x92, 0x8e, 0x2d, 0x35, 0x70, 0x31,
0xb6, 0x4d, 0xa8, 0x61, 0x2d, 0xd6, 0x5e, 0xf8, 0x36, 0xf2, 0x65, 0x27,
0xd5, 0xb9, 0xfe, 0x98, 0x52, 0x8e, 0xbb, 0xaa, 0x5c, 0x05, 0x52, 0x46,
0x02, 0x85, 0x1d, 0x81, 0x4e, 0x9d, 0x02, 0xce, 0x0c, 0xe3, 0x31, 0x9d,
0xd2, 0x32, 0xac, 0x0b, 0xb6, 0x17, 0x62, 0xaa, 0xd2, 0x63, 0x3f, 0x76,
0x23, 0xaa, 0x17, 0x04, 0x2b, 0x12, 0xd6, 0xd6, 0x4a, 0x16, 0x12, 0xc1,
0x36, 0xa5, 0x57, 0xb2, 0x74, 0x25, 0x60, 0x9c, 0xb2, 0x47, 0x7c, 0xb8,
0xad, 0xdc, 0xc8, 0x30, 0x14, 0xad, 0x81, 0x1e, 0xc8, 0x3c, 0x2e, 0x75,
0xc3, 0x98, 0x79, 0x58, 0x9d, 0x39, 0x0e, 0x55, 0x14, 0x48, 0xab, 0x38,
0xaf, 0x6b, 0x5a, 0x04, 0x2e, 0xf8, 0x12, 0x5c, 0x22, 0x47, 0x00, 0x4f,
0xf9, 0x53, 0xb8, 0x42, 0x66, 0xc4, 0xe5, 0x42, 0xc3, 0xf5, 0x24, 0x9f,
0xaa, 0x96, 0x06, 0x2f, 0xa1, 0x49, 0x62, 0xd3, 0x07, 0xa2, 0x7e, 0x4c,
0xf9, 0x18, 0x98, 0x72, 0x78, 0xec, 0x1f, 0x79, 0xeb, 0xc3, 0xfe, 0xae,
0xf3, 0x58, 0xde, 0x37, 0xd7, 0x49, 0x0a, 0x44, 0x39, 0x72, 0x7b, 0x7a,
0x40, 0x13, 0xc5, 0x99, 0x3e, 0xa9, 0xf7, 0x7d, 0x2f, 0x1e, 0x26, 0x25,
0x86, 0xff, 0xce, 0x32, 0x2c, 0x3f, 0xfa, 0x09, 0x5b, 0xa4, 0x9e, 0x84,
0xc6, 0x18, 0x98, 0xf7, 0xd7, 0xac, 0xa8, 0x9a, 0x4b, 0x02, 0x03, 0x11,
0xbb, 0xc1, 0xa6, 0xc8, 0xe9, 0x34, 0xa9, 0xc6, 0x35, 0x46, 0xb6, 0x2e,
0xf8, 0xda, 0x59, 0xa0, 0xdb, 0x19, 0x10, 0x46, 0x53, 0x78, 0x29, 0x1c,
0xd6, 0x1f, 0xf9, 0x3c, 0x58, 0xbc, 0x16, 0x55, 0x20, 0x2e, 0x9a, 0x59,
0x79, 0x18, 0x0b, 0x6f, 0x9a, 0x64, 0xf0, 0x83, 0xfb, 0xc3, 0xef, 0xa5,
0x87, 0x52, 0x2c, 0x30, 0x63, 0x19, 0x2d, 0x70, 0x0c, 0x43, 0x62, 0x57,
0x55, 0x39, 0x0f, 0xf6, 0x8f, 0x97, 0x6f, 0xbd, 0x12, 0xe6, 0x08, 0x2f,
0x4b, 0x25, 0x30, 0x43, 0x07, 0x18, 0xeb, 0x9d, 0xc3, 0x59, 0xd9, 0x1c,
0x4e, 0xac, 0x79, 0xf0, 0xbd, 0xfe, 0xb9, 0xd8, 0x08, 0x34, 0x26, 0x0a,
0xd1, 0x12, 0xb8, 0xc1, 0x11, 0x1c, 0x02, 0x12, 0x9e, 0xf1, 0x58, 0x3f,
0x7f, 0x1e, 0xb9, 0xf4, 0xb4, 0x32, 0xa4, 0x0c, 0xa5, 0xf7, 0x2e, 0x23,
0x6e, 0x33, 0xa8, 0x9d, 0xc4, 0x60, 0x48, 0x98, 0x6c, 0x4d, 0xbe, 0xbd,
0x9f, 0x09, 0x67, 0x0a, 0x5c, 0x88, 0x3b, 0xa1, 0xdb, 0xe7, 0x9b, 0xdf,
0xac, 0xdb, 0x2b, 0xe8, 0xb4, 0x5b, 0x67, 0x0c, 0x1e, 0x79, 0xe7, 0x11,
0xab, 0xb5, 0x0a, 0xa0, 0x4e, 0x1f, 0x4b, 0xcb, 0xf4, 0xc0, 0xc6, 0xf3,
0x52, 0xa6, 0xce, 0x4d, 0x73, 0xeb, 0x2d, 0xd0, 0x90, 0x0c, 0x2a, 0xa0,
0x71, 0xee, 0x76, 0xe7, 0xd3, 0xb2, 0x44, 0xd4, 0x19, 0x95, 0xee, 0x12,
0x3c, 0x53, 0xa2, 0x05, 0x0b, 0x1d, 0xf7, 0x40, 0x08, 0xd0, 0xc1, 0xb8,
0x66, 0x05, 0x4b, 0x34, 0xca, 0x8b, 0x3a, 0xe3, 0xb7, 0xd9, 0x22, 0x7c,
0x88, 0x60, 0x3a, 0xbb, 0xe8, 0x43, 0xa0, 0xe2, 0xa0, 0x7a, 0x86, 0x07,
0x1a, 0x0b, 0x88, 0xfe, 0x52, 0x2a, 0xb2, 0xe3, 0x3e, 0x3d, 0x01, 0xef,
0x02, 0xaf, 0x02, 0x8d, 0xee, 0xa6, 0x94, 0x32, 0x35, 0xb9, 0x10, 0x2b,
0xf8, 0x38, 0x07, 0xd3, 0xcb, 0xe8, 0xc0, 0x97, 0x78, 0x5c, 0x70, 0x76,
0x85, 0x16, 0xd4, 0xab, 0xce, 0xe5, 0x78, 0x4e, 0x11, 0x88, 0x36, 0x4e,
0xbb, 0x58, 0x8e, 0xe0, 0x24, 0x8c, 0xe5, 0x2b, 0x45, 0xb7, 0xb0, 0xd7,
0x70, 0x02, 0x5a, 0x06, 0x24, 0x2e, 0xf0, 0x48, 0x96, 0x3b, 0xfe, 0xcd,
0xe9, 0xe0, 0xf6, 0x00, 0x8c, 0x8c, 0x10, 0xbe, 0xdc, 0x10, 0x2e, 0x06,
0xb7, 0x1f, 0x5d, 0xff, 0xa1, 0x58, 0xdb, 0x5f, 0x09, 0x14, 0x07, 0x55,
0x4d, 0x81, 0x3c, 0xcd, 0x71, 0xd8, 0xd7, 0x1c, 0x07, 0x97, 0x43, 0x2d,
0x86, 0xb9, 0xd3, 0xaa, 0x15, 0x4f, 0x12, 0xee, 0x16, 0xbe, 0xf0, 0x8f,
0x80, 0xe0, 0x8b, 0xc4, 0x9c, 0x3b, 0x32, 0x0a, 0x10, 0x04, 0x97, 0x5a,
0x0b, 0xd2, 0x96, 0xa1, 0x86, 0xf8, 0x14, 0x78, 0x83, 0xac, 0xb1, 0x7c,
0x5d, 0x4b, 0x9e, 0xf8, 0xaa, 0x7e, 0x50, 0xd5, 0x11, 0x12, 0xee, 0x11,
0x6d, 0xcd, 0x3a, 0xf0, 0xfd, 0x6c, 0x5c, 0x3d, 0xa2, 0x03, 0x2a, 0x5f,
0xc6, 0xf7, 0x25, 0xe8, 0x5b, 0x94, 0x29, 0x7e, 0xf2, 0x98, 0xfc, 0xf5,
0xcb, 0x22, 0xad, 0x11, 0x9f, 0xe7, 0x1f, 0x0f, 0x3f, 0x0f, 0xbf, 0xb6,
0x48, 0x3d, 0x04, 0xdf, 0x02, 0x2b, 0x87, 0x2f, 0xcd, 0xfd, 0xe1, 0x97,
0xc8, 0x1e, 0x3a, 0x32, 0x17, 0xaf, 0x7b, 0x4d, 0x96, 0x13, 0x77, 0x91,
0x54, 0xe8, 0x08, 0x92, 0x03, 0xa7, 0x86, 0xee, 0xc0, 0x82, 0xaf, 0x79,
0xea, 0xed, 0x35, 0xb3, 0x39, 0x7b, 0x78, 0x59, 0x52, 0xee, 0x65, 0xf2,
0x79, 0x2e, 0x54, 0xbe, 0x86, 0x5e, 0xb4, 0x39, 0x25, 0xc1, 0xd6, 0x99,
0x3b, 0xe2, 0x0b, 0x02, 0x9b, 0x08, 0x1d, 0x6b, 0x28, 0x3c, 0xd5, 0x92,
0xd4, 0xf3, 0x2a, 0xc3, 0x82, 0x8b, 0x6e, 0x28, 0xa4, 0xae, 0x99, 0xdf,
0xa2, 0x9e, 0x3a, 0x05, 0x31, 0xe5, 0xb2, 0xd7, 0x86, 0x8c, 0x34, 0x76,
0x8a, 0xc8, 0x1d, 0x06, 0xef, 0x9f, 0x77, 0x28, 0x62, 0xb7, 0xb6, 0xbd,
0x89, 0xbb, 0x63, 0xdd, 0xe2, 0x6d, 0xb7, 0x6e, 0x2a, 0xd3, 0x44, 0xb9,
0xd5, 0xfa, 0xbf, 0xcd, 0x58, 0x1d, 0x95, 0xb7, 0x13, 0x75, 0xf5, 0x64,
0x5a, 0x19, 0x67, 0x5b, 0xd4, 0x75, 0x92, 0xce, 0xb8, 0x3f, 0x17, 0x95,
0xe9, 0x65, 0xb0, 0x91, 0x92, 0x44, 0x84, 0xad, 0xa4, 0x71, 0xf9, 0xb3,
0x20, 0x88, 0x0e, 0x8e, 0x67, 0x82, 0x76, 0xfa, 0x27, 0xc4, 0xbd, 0xa3,
0x7f, 0xb9, 0xa1, 0x68, 0x0e, 0x38, 0x4b, 0x00, 0x00
};
unsigned int index_htm_gz_len = 6261;

@ -0,0 +1,60 @@
#/bin/sh
# Processing script to optionally reduce filesystem use by miniying, gzipping and preparing index.htm for embedding in code.
# Please see readme.md for more information.
# Requires xdd which is part of the VIM package
# Requires npm
# sudo apt install npm
# ln -s /usr/bin/nodejs /usr/bin/node
# Requires html-minifier
# sudo npm install html-minifier -g
html-minifier \
--case-sensitive \
--collapse-boolean-attributes \
--collapse-whitespace \
--minify-css true \
--minify-js true \
--process-conditional-comments \
--remove-attribute-quotes \
--remove-comments \
--remove-empty-attributes \
--remove-optional-tags \
--remove-redundant-attributes \
--remove-script-type-attributes \
--remove-style-link-type-attributes \
-o index.htm \
../data/edit/index.htm
if [ $? -ne 0 ]
then
echo "Error minifying index.htm"
exit -1
fi
if [ -e index.htm.gz ]
then
rm index.htm.gz
fi
gzip index.htm
if [ $? -ne 0 ]
then
echo "Error gzipping minified index.htm"
exit -1
fi
echo '// WARNING: Auto-generated file. Please do not modify by hand.' > index_htm.h
echo '// This file is an embeddable version of the gzipped index.htm file.' >> index_htm.h
echo '// To update it, please rerun the `reduce_index.sh` script located in the `extras` subfolder' >> index_htm.h
echo '// then recompile the sketch after each change to the `index.html` file.' >> index_htm.h
xxd -i index.htm.gz >> index_htm.h
if [ $? -ne 0 ]
then
echo "Error creating include file from index.htm.gz"
exit -1
fi
echo Reduce complete.

@ -26,9 +26,22 @@
#include <WebServer.h>
#include <SPIFFS.h>
#endif
#include <FS.h>
#include <AutoConnect.h>
/*
AC_USE_SPIFFS indicates SPIFFS or LittleFS as available file systems that
will become the AUTOCONNECT_USE_SPIFFS identifier and is exported as showng
the valid file system. After including AutoConnect.h, the Sketch can determine
whether to use FS.h or LittleFS.h by AUTOCONNECT_USE_SPIFFS definition.
*/
#ifdef AUTOCONNECT_USE_SPIFFS
#include <FS.h>
FS& FlashFS = SPIFFS;
#else
#include <LittleFS.h>
FS& FlashFS = LittleFS;
#endif
// Upload request custom Web page
static const char PAGE_UPLOAD[] PROGMEM = R"(
{
@ -126,8 +139,8 @@ String postUpload(AutoConnectAux& aux, PageArgument& args) {
// The file saved by the AutoConnect upload handler is read from
// the EEPROM and echoed to a custom web page.
if (upload.mimeType.indexOf("text/") >= 0) {
SPIFFS.begin();
File uploadFile = SPIFFS.open(String("/" + upload.value).c_str(), FILE_MODE_R);
FlashFS.begin();
File uploadFile = FlashFS.open(String("/" + upload.value).c_str(), FILE_MODE_R);
if (uploadFile) {
while (uploadFile.available()) {
char c = uploadFile.read();
@ -140,7 +153,7 @@ String postUpload(AutoConnectAux& aux, PageArgument& args) {
}
else
content = "Not saved";
SPIFFS.end();
FlashFS.end();
}
return content;
}

@ -85,7 +85,7 @@ void sendRedirect(String uri) {
server.client().stop();
}
bool atDetect(IPAddress softapIP) {
bool atDetect(IPAddress& softapIP) {
Serial.println("Captive portal started, SoftAP IP:" + softapIP.toString());
return true;
}

@ -88,7 +88,7 @@ void sendRedirect(String uri) {
server.client().stop();
}
bool atDetect(IPAddress softapIP) {
bool atDetect(IPAddress& softapIP) {
Serial.println("Captive portal started, SoftAP IP:" + softapIP.toString());
return true;
}

@ -119,7 +119,7 @@ void sendRedirect(String uri) {
io.cancel();
}
bool atDetect(IPAddress softapIP) {
bool atDetect(IPAddress& softapIP) {
Serial.println("Captive portal started, SoftAP IP:" + softapIP.toString());
return true;
}

@ -22,14 +22,27 @@ typedef ESP8266WebServer WEBServer;
#include <SPIFFS.h>
typedef WebServer WEBServer;
#endif
#include <FS.h>
#include <AutoConnect.h>
/*
AC_USE_SPIFFS indicates SPIFFS or LittleFS as available file systems that
will become the AUTOCONNECT_USE_SPIFFS identifier and is exported as showng
the valid file system. After including AutoConnect.h, the Sketch can determine
whether to use FS.h or LittleFS.h by AUTOCONNECT_USE_SPIFFS definition.
*/
#ifdef AUTOCONNECT_USE_SPIFFS
#include <FS.h>
FS& FlashFS = SPIFFS;
#else
#include <LittleFS.h>
FS& FlashFS = LittleFS;
#endif
#define HELLO_URI "/hello"
#define PARAM_STYLE "/style.json"
// Declare AutoConnectText with only a value.
// Qualify the Caption by reading style attributes from the SPIFFS style.json file.
// Qualify the Caption by reading style attributes from the style.json file.
ACText(Caption, "Hello, world");
//AutoConnectAux for the custom Web page.
@ -54,15 +67,15 @@ String onHello(AutoConnectAux& aux, PageArgument& args) {
return String();
}
// Load the element from specified file in SPIFFS.
// Load the element from specified file in the flash on board.
void loadParam(const char* fileName) {
SPIFFS.begin();
File param = SPIFFS.open(fileName, "r");
Flash.begin();
File param = FlashFS.open(fileName, "r");
if (param) {
ElementJson = param.readString();
param.close();
}
SPIFFS.end();
FlashFS.end();
}
void setup() {

@ -23,10 +23,23 @@ https://opensource.org/licenses/MIT
#include <HTTPClient.h>
#define GET_CHIPID() ((uint16_t)(ESP.getEfuseMac()>>32))
#endif
#include <FS.h>
#include <PubSubClient.h>
#include <AutoConnect.h>
/*
AC_USE_SPIFFS indicates SPIFFS or LittleFS as available file systems that
will become the AUTOCONNECT_USE_SPIFFS identifier and is exported as showng
the valid file system. After including AutoConnect.h, the Sketch can determine
whether to use FS.h or LittleFS.h by AUTOCONNECT_USE_SPIFFS definition.
*/
#ifdef AUTOCONNECT_USE_SPIFFS
#include <FS.h>
FS& FlashFS = SPIFFS;
#else
#include <LittleFS.h>
FS& FlashFS = LittleFS;
#endif
#define PARAM_FILE "/param.json"
#define AUX_SETTING_URI "/mqtt_setting"
#define AUX_SAVE_URI "/mqtt_save"
@ -253,7 +266,7 @@ void getParams(AutoConnectAux& aux) {
// elements defined in /mqtt_setting JSON.
String loadParams(AutoConnectAux& aux, PageArgument& args) {
(void)(args);
File param = SPIFFS.open(PARAM_FILE, "r");
File param = FlashFS.open(PARAM_FILE, "r");
if (param) {
if (aux.loadElement(param)) {
getParams(aux);
@ -287,7 +300,7 @@ String saveParams(AutoConnectAux& aux, PageArgument& args) {
// The entered value is owned by AutoConnectAux of /mqtt_setting.
// To retrieve the elements of /mqtt_setting, it is necessary to get
// the AutoConnectAux object of /mqtt_setting.
File param = SPIFFS.open(PARAM_FILE, "w");
File param = FlashFS.open(PARAM_FILE, "w");
mqtt_setting.saveElement(param, { "mqttserver", "channelid", "userkey", "apikey", "uniqueid", "period", "hostname" });
param.close();
@ -352,7 +365,7 @@ void setup() {
delay(1000);
Serial.begin(115200);
Serial.println();
SPIFFS.begin();
FlashFS.begin();
if (portal.load(FPSTR(AUX_mqtt_setting))) {
AutoConnectAux& mqtt_setting = *portal.aux(AUX_SETTING_URI);

@ -29,10 +29,23 @@
#include <HTTPClient.h>
#define GET_CHIPID() ((uint16_t)(ESP.getEfuseMac()>>32))
#endif
#include <FS.h>
#include <PubSubClient.h>
#include <AutoConnect.h>
/*
AC_USE_SPIFFS indicates SPIFFS or LittleFS as available file systems that
will become the AUTOCONNECT_USE_SPIFFS identifier and is exported as showng
the valid file system. After including AutoConnect.h, the Sketch can determine
whether to use FS.h or LittleFS.h by AUTOCONNECT_USE_SPIFFS definition.
*/
#ifdef AUTOCONNECT_USE_SPIFFS
#include <FS.h>
FS& FlashFS = SPIFFS;
#else
#include <LittleFS.h>
FS& FlashFS = LittleFS;
#endif
#define PARAM_FILE "/param.json"
#define AUX_MQTTSETTING "/mqtt_setting"
#define AUX_MQTTSAVE "/mqtt_save"
@ -110,7 +123,7 @@ int getStrength(uint8_t points) {
String loadParams(AutoConnectAux& aux, PageArgument& args) {
(void)(args);
File param = SPIFFS.open(PARAM_FILE, "r");
File param = FlashFS.open(PARAM_FILE, "r");
if (param) {
aux.loadElement(param);
param.close();
@ -144,7 +157,7 @@ String saveParams(AutoConnectAux& aux, PageArgument& args) {
// The entered value is owned by AutoConnectAux of /mqtt_setting.
// To retrieve the elements of /mqtt_setting, it is necessary to get
// the AutoConnectAux object of /mqtt_setting.
File param = SPIFFS.open(PARAM_FILE, "w");
File param = FlashFS.open(PARAM_FILE, "w");
portal.aux("/mqtt_setting")->saveElement(param, { "mqttserver", "channelid", "userkey", "apikey", "period", "uniqueid", "hostname" });
param.close();
@ -205,17 +218,17 @@ void handleClearChannel() {
webServer.client().stop();
}
// Load AutoConnectAux JSON from SPIFFS.
// Load AutoConnectAux JSON from the flash on the board.
bool loadAux(const String auxName) {
bool rc = false;
String fn = auxName + ".json";
File fs = SPIFFS.open(fn.c_str(), "r");
File fs = FlashFS.open(fn.c_str(), "r");
if (fs) {
rc = portal.load(fs);
fs.close();
}
else
Serial.println("SPIFFS open failed: " + fn);
Serial.println("Filesystem open failed: " + fn);
return rc;
}

@ -23,10 +23,23 @@ https://opensource.org/licenses/MIT
#include <HTTPClient.h>
#define GET_CHIPID() ((uint16_t)(ESP.getEfuseMac()>>32))
#endif
#include <FS.h>
#include <PubSubClient.h>
#include <AutoConnect.h>
/*
AC_USE_SPIFFS indicates SPIFFS or LittleFS as available file systems that
will become the AUTOCONNECT_USE_SPIFFS identifier and is exported as showng
the valid file system. After including AutoConnect.h, the Sketch can determine
whether to use FS.h or LittleFS.h by AUTOCONNECT_USE_SPIFFS definition.
*/
#ifdef AUTOCONNECT_USE_SPIFFS
#include <FS.h>
FS& FlashFS = SPIFFS;
#else
#include <LittleFS.h>
FS& FlashFS = LittleFS;
#endif
#define PARAM_FILE "/param.json"
#define AUX_SETTING_URI "/mqtt_setting"
#define AUX_SAVE_URI "/mqtt_save"
@ -214,7 +227,7 @@ void setup() {
delay(1000);
Serial.begin(115200);
Serial.println();
SPIFFS.begin();
FlashFS.begin();
if (uniqueid.checked) {
config.apid = String("ESP") + "-" + String(GET_CHIPID(), HEX);

@ -12,7 +12,7 @@
[
{
"name": "PageBuilder",
"version": ">=1.3.6"
"version": ">=1.4.2"
},
{
"name": "ArduinoJson",
@ -25,6 +25,6 @@
"espressif8266",
"espressif32"
],
"version": "1.1.7",
"version": "1.2.0",
"license": "MIT"
}

@ -1,5 +1,5 @@
name=AutoConnect
version=1.1.7
version=1.2.0
author=Hieromon Ikasamo <hieromon@gmail.com>
maintainer=Hieromon Ikasamo <hieromon@gmail.com>
sentence=ESP8266/ESP32 WLAN configuration at runtime with web interface.

@ -33,7 +33,7 @@ nav:
- 'Examples' :
- 'How to embed': howtoembed.md
- 'Tips for data conversion': datatips.md
- 'Attach the menu': menuize.md
- 'Attach the menus': menuize.md
- 'Custom Web pages w/o JSON': wojson.md
- 'Appendix':
- 'Inside AutoConnect::begin': lsbegin.md

@ -25,12 +25,13 @@ AutoConnectAux will configure custom Web pages with JSON objects. The elements t
"title" : title,
"uri" : uri,
"menu" : true | false,
"auth": authentication,
"element" : element_array
}
```
#### <i class="fa fa-key"></i> **title**
: A title of the custom Web page. This is string value. String specified *title* will be displayed in the AutoConnection menu.
: A title of the custom Web page. This is string value and specifies the *title* will be displayed in the AutoConnection menu.
#### <i class="fa fa-key"></i> **uri**
: String of URI path that specifies where to place the custom Web page. It needs to be a location from the root path including '**/**'.
@ -38,6 +39,12 @@ AutoConnectAux will configure custom Web pages with JSON objects. The elements t
#### <i class="fa fa-key"></i> **menu**
: This is a Boolean value indicating whether to include the custom Web page in the AutoConnect menu. If the page only responds to another page and you want to prevent the direct use from the menu, you can exclude from the AutoConnect menu. If this key is false, it will not appear in the menu.
#### <i class="fa fa-key"></i> **auth**
: It allows that this page requires authentication. An *authentication* specifies the following string that represents the authentication scheme.
: - **NONE**: No authentication. This is default.
: - **BASIC**: Apply Basic scheme.
: - **DIGEST**: Apply Digest scheme.
#### <i class="fa fa-key"></i> **element**
: Describe an array of JSON objects as *element_array*. It is a JSON object array of the [AutoConnectElements](#json-object-for-autoconnectelements) that make up the custom Web page.
@ -150,10 +157,14 @@ This is different for each AutoConnectElements, and the key that can be specifie
: - **value** : The file name of the upload file will be stored. The `value` is read-only and will be ignored if specified.
: - **label** : Specifies a label of the file selection box. Its placement is always to the left of the file selection box.
: - **store** : Specifies the destination to save the uploaded file. Its value accepts one of the following:<p>
<b>fs</b>&nbsp;: Save as the SPIFFS file in flash of ESP8266/ESP32 module.<br>
<b>fs</b>&nbsp;: Save as the SPIFFS file in flash of ESP8266/ESP32 module. If the valid file system of the ESP8266 module incorporating the Sketch is [LittleFS](https://arduino-esp8266.readthedocs.io/en/latest/filesystem.html#spiffs-and-littlefs), AutoConnect assumes the file system to be LittleFS. However, it does not sense the actual file system, so If the Sketch implementation does not match the file system on the ESP8266 depends, a file writing error will occur.<br>
<b>sd</b>&nbsp;: Save to an external SD device connected to ESP8266/ESP32 module.<br>
<b>extern</b>&nbsp;: Pass the content of the uploaded file to the uploader which is declared by the Sketch individually. Its uploader must inherit [**AutoConnectUploadHandler**](acupload.md#to-upload-to-a-device-other-than-flash-or-sd) class and implements *_open*, *_write* and *_close* function.</p>
!!! note "A valid filesystem of ESP8266 on board flash"
AutoConnect has assumed [**LittleFS**](https://arduino-esp8266.readthedocs.io/en/latest/filesystem.html#spiffs-and-littlefs) as a valid file system since v1.2.0 enhancement. On the other hand, the ESP8266 arduino core has supported LittleFS officially since a release 2.7.0.<br>
LittleFS support in AutoConnect relies on the FS instance declared by the arduino core used at compile-time per project, and its FS instance will acquire by either the SPIFFS class or the LittleFS class. That is, you need to choose which file system will be available in the actual Sketch and make consistent it with AutoConnect assumed file system. So, you can choose which one uses the file systems per project via adjustment the **AC_USE_SPIFFS** macro enable or disable. AutoConnect determines the available file system by the **AC_USE_SPIFFS** macro which defined in [AutoConnectDefs.h](api.md#defined-macros) header file.
#### <i class="fa fa-caret-right"></i> ACInput
: - **value** : Specifies the initial text string of the input box. If this value is omitted, placeholder is displayed as the initial string.
: - **label** : Specifies a label of the input box. Its placement is always to the left of the input box.
@ -327,7 +338,7 @@ const char aux[] PROGMEM = R"raw(
]
}
)raw";
portal.load(aux);
portal.load(FPSTR(aux));
// Loading from Stream assumes "aux.json" file should be store in SPIFFS.
File aux = SPIFFS.open("aux.json", "r");

@ -2,11 +2,12 @@
### <i class="fa fa-caret-right"></i> 404 handler
Registering the "not found" handler is a different way than ESP8266WebServer (WebServer as ESP32). The *onNotFound* of ESP8266WebServer/WebServer does not work with AutoConnect. AutoConnect overrides *ESP8266WebServer::onNotFound*/*WebServer::onNotFound* to handle a captive portal. To register "not found" handler, use [*AutoConnect::onNotFound*](api.md#onnotfound).
AutoConnect cannot allow the Sketch registers the **"Not-found"** handler (404-handler) to the ESP8266WebServer natively. AutoConnect traps Not-found handler of the ESP8266WebServer for its own page processing. If the Sketch overrides the Not-found handler, AutoConnect will miss the opportunity to control the HTTP session and becomes unresponsive to the menu.
Registering the Not-found handler is a different method than for ESP8266WebServer, use [AutoConnect::onNotFound](api.md#onnotfound). This restriction applies to the WebServer for ESP32 as well.
### <i class="fa fa-caret-right"></i> Access to saved credentials
AutoConnect stores the established WiFi connection in the flash of the ESP8266/ESP32 module and equips the class to access it from the Sketch. You can read, write or erase the credentials using this class individually. It's [AutoConnectCredential](credit.md#autoconnectcredential) class which provides the access method to the saved credentials in the flash. Refer to section [Saved credentials access](credit.md) for details.
AutoConnect stores the credentials of the established WiFi connection in the flash of the ESP8266/ESP32 module and equips the class to access them from the Sketch. The Sketch can read, write, or erase the credentials using this class as the [AutoConnectCredential](credit.md#autoconnectcredential) individually. Refer to section [Saved credentials access](credit.md) for details.
!!! note "Where to store credentials in ESP32 with AutoConnect v1.0.0 or later"
Since v1.0.0, credentials are stored in nvs of ESP32. AutoConnect v1.0.0 or later accesses the credentials area using the **Preferences** class with the arduino esp-32 core. So in ESP32, the credentials are not in the EEPROM, it is in the namespace **AC_CREDT** of the nvs. See [Saved credentials access](credit.md) for details.
@ -14,11 +15,11 @@ AutoConnect stores the established WiFi connection in the flash of the ESP8266/E
### <i class="fa fa-caret-right"></i> Automatic reconnect
AutoConnect changes WIFI mode depending on the situation. The [AutoConnect::begin](lsbegin.md) function starts WIFI in STA mode and starts the webserver if the connection is successful by the 1st-WiFi.begin. But if the connection fails with the least recently established access point, AutoConnect will switch the WIFI mode to AP_STA and starts the DNS server to be able to launch a captive portal.
AutoConnect will change the WiFi mode depending on the situation. The [AutoConnect::begin](lsbegin.md) function starts WiFi mode in **STA** and starts the webserver if the connection is successful by the 1st-WiFi.begin. But if it will fail to connect with the least recently established access point, it will switch the WiFi mode to **AP_STA** and starts the DNS server to be able to launch a captive portal.
When the captive portal is started, SoftAP starts and the STA is disconnected. At this point, the station configuration information that the ESP module has stored on its own (it is known as the SDK's [station_config](https://github.com/esp8266/Arduino/blob/db75d2c448bfccc6dc308bdeb9fbd3efca7927ff/tools/sdk/include/user_interface.h#L249) structure) is discarded.
When the captive portal starts, **SoftAP** starts and STA disconnected. At this point, the station configuration information (it is known as the SDK's [station_config](https://github.com/esp8266/Arduino/blob/db75d2c448bfccc6dc308bdeb9fbd3efca7927ff/tools/sdk/include/user_interface.h#L249) structure) that the ESP module has stored on its own is discarded.
AutoConnect can connect to an access point again using saved credential that has disconnected once, and its control is allowed by [**autoReconnect**](apiconfig.md#autoreconnect). [*AutoConnectConfig::autoReconnect*](apiconfig.md#autoreconnect) option specifies to attempt to reconnect to the past established access point that stored in saved credentials. AutoConnect does not start SoftAP immediately even if 1st-WiFi.begin fails when the [**autoReconnct**](apiconfig.md#autoreconnect) is enabled. It will scan the WiFi signal, and if the same BSSID as the detected BSSID is stored in flash as AutoConnect credentials, explicitly apply it and reruns WiFi.begin still WIFI_STA mode. (The autoReconnect works effectively even if the SSID is a hidden access point)
AutoConnect can connect to an access point again that has disconnected once, and its control is allowed by [*AutoConnectConfig::autoReconnect*](apiconfig.md#autoreconnect) that option specifies to attempt to reconnect to the past established access point using the saved credentials. If the [**autoReconnect**](apiconfig.md#autoreconnect) is enabled, AutoConnect will not launch SoftAP immediately even if 1st-WiFi.begin fails. When AutoConnect fails to connect WiFi, it will scan the WiFi signal to find out which access points it had connected to in the past. Then if it finds the saved BSSID in the broadcasts, AutoConnect will attempt to connect again applying the matched credential explicitly while still in STA mode. (AutoReconnect works well even with hidden SSID access points)
```cpp hl_lines="3"
AutoConnect Portal;
@ -79,7 +80,7 @@ The captive portal will only be activated if 1st-WiFi::begin fails. Sketch can d
```cpp hl_lines="3 13"
AutoConnect Portal;
bool startCP(IPAddress ip) {
bool startCP(IPAddress& ip) {
digitalWrite(BUILTIN_LED, HIGH);
Serial.println("C.P. started, IP:" + WiFi.localIP().toString());
return true;
@ -129,7 +130,7 @@ Basically, the captive portal launch is subject to 1st-WiFi.begin result, but Sk
Once AutoConnect has entered the captive portal state due to the above conditions, it will not exit until a WiFi connection can be established. (But that is the default behavior)
The Sketch can abort the [*AutoConnect::begin*](api.md#begin) by setting the captive portal timeout and returns control to Sketch. AutoConnect has two parameters for timeout control. One is a timeout value used when trying to connect to the specified AP. It behaves the same as general timeout control in connection attempt by WiFi.begin. This control is specified by the third parameter of [*AutoConnect::begin*](api.md#begin). The default value is macro defined by [**AUTOCONNECT_TIMEOUT**](api.md#defined-macros) in the **AutoConnectDefs.h** file.
The Sketch can abort the [*AutoConnect::begin*](api.md#begin) by setting the captive portal timeout and returns control to Sketch. AutoConnect has two parameters for timeout control. One is a timeout value used when trying to connect to the specified AP. It behaves the same as general timeout control in connection attempt by WiFi.begin. This control is specified by the third parameter of [*AutoConnect::begin*](api.md#begin). The default value is macro defined by [**AUTOCONNECT_TIMEOUT**](https://github.com/Hieromon/AutoConnect/blob/master/src/AutoConnectDefs.h#L103) in AutoConnectDefs.h header file.
The other timeout control is for the captive portal itself. It is useful when you want to continue sketch execution with offline even if the WiFi connection is not possible. You can also combine it with the [**immediateStart**](#on-demand-start-the-captive-portal) option to create sketches with high mobility.
@ -214,63 +215,81 @@ void loop() {
}
```
### <i class="fa fa-caret-right"></i> Casts the HTML pages to be add-on into the menu
### <i class="fa fa-caret-right"></i> Capture the legacy web pages as items into the menu
If your sketch handles web pages, you can embed the pages into the AutoConnect menu in continuance enjoying the utility of the WiFi connection feature. Unlike the custom Web pages by [AutoConnectElements](acelements.md), this allows to legacy web pages registered by *ESP8266WebServer::on* or *WebServer::on* function.
You can embed the ordinary page processed by the ESP8266WebServer request handler as an item into the AutoConnect menu. AutoConnect can capture the legacy web pages for ESP8266WebServer or WebServer of ESP32 and extends the menu containing these items.
In ordinary, the Sketch registers the request handler for the page depending on URI using the [ESP8266WebServer::on](https://github.com/esp8266/Arduino/tree/master/libraries/ESP8266WebServer#client-request-handlers) function. AutoConnect allows Sketch to bundle the registered legacy page into a menu. the Sketch is able to include its URI to a menu item using [*AutoConnect::append*](api.md#append) function that creates internally an [**AutoConnectAux**](acintro.md) depended on its URI and integrates into the menu.
To implement embedding your legacy web pages to the AutoConnect menu, you can use AutoConnectAux only constructed with the URI of the page to be embedding. AutoConnectElements is not required. The basic procedure for this as follows:
The following code has a mixture of both AutoConnectAux and the legacy web page. An AutoConnectAux page is menued automatically with the [*AutoConnect::join*](api.md#join) or [*AutoConnect::load*](api.md#load) function. Similarly, a legacy page is integrated by the [*AutoConnect::append*](api.md#append) function.
1. Declare AutoConnectAux for each legacy page. It includes the URI of the page and item string which will display in the AutoConnect menu.
2. Sketch the legacy page handlers.
3. Register those handler functions to ESP8266WebServer/WebServer with the **on** function.
4. Register AutoConnectAux declared with #1 to AutoConnect using [*AutoConnect::join*](api.md#join) function. It serves as a menu item.
5. [Begin](api.md#begin) the portal.
6. Performs [*AutoConnect::handleClient*](api.md#handleClient) in the **loop** function.
```cpp hl_lines="10 28 32"
```cpp hl_lines="26 35"
#include <ESP8266WiFi.h>
#include <ESP8266WebServer.h>
#include <AutoConnect.h>
ESP8266WebServer server;
// Declaration for casting legacy page to AutoConnect menu.
// Specifies an uri and the menu label.
AutoConnect portal(server);
AutoConnectAux hello("/hello", "Hello"); // Step #1 as the above procedure
ESP8266WebServer server;
AutoConnect portal(server);
// Definitions of AutoConnectAux page
static const char PAGE[] PROGMEM = R"(
{
"title": "PAGE",
"uri": "/page",
"menu": true,
"element": [
{
"name": "cap",
"type": "ACText",
"value": "This is a custom web page."
}
]
}
)";
// Step #2 as the above procedure
// A conventional web page driven by the ESP8266WebServer::on handler.
// This is a legacy.
void handleHello() {
server.send(200, "text/html", String(F(
void setup() {
// The Web page handler located to /hello
server.on("/hello", [](){
server.send(200, "text/html", String(F(
"<html>"
"<head><meta name='viewport' content='width=device-width,initial-scale=1.0'></head>"
"<body>Hello, world</body>"
"<body><h2>Hello, world</h2></body>"
"</html>"
)));
}
)));
});
void setup() {
// Step #3 as the above procedure
// Register the "on" handler as usual to ESP8266WebServer.
// Match this URI with the URI of AutoConnectAux to cast.
server.on("/hello", handleHello);
// Step #4 as the above procedure
// Joins AutoConnectAux to cast the page via the handleRoot to AutoConnect.
portal.join({ hello });
portal.begin(); // Step #5 as the above procedure
portal.append("/hello", "HELLO"); // Adds an item as HELLO into the menu
portal.load(FPSTR(PAGE)); // Load AutoConnectAux custom web page
portal.begin();
}
void loop() {
portal.handleClient(); // Step #6 as the above procedure
portal.handleClient();
}
```
<span style="display:block;margin-left:auto;margin-right:auto;width:284px;height:462px;border:1px solid lightgrey;"><img data-gifffer="images/addmenu.gif" data-gifffer-height="460" data-gifffer-width="282" /></span>
The [*AutoConnect::append*](api.md#append) function also has the third parameter that directly specifies the request handler. It has similar efficacy to calling the append and `ESP8266WebSever::on` at once. [^1]
[^1]: However, the pages registered this way remain legacy. Therefore, the AutoConnect menu bar is not appeared.
```cpp
portal.append("/hello", "HELLO", [](){
server.send(200, "text/html", String(F(
"<html>"
"<head><meta name='viewport' content='width=device-width,initial-scale=1.0'></head>"
"<body><h2>Hello, world</h2></body>"
"</html>"
)));
});
```
<img width="232px" src="images/castmenu.png">
For more details, see section [Attach the menus](menuize.md) of Examples page.
For more details, see section [Constructing the menu](menuize.md) of Examples page.
!!! note "An instance of ESP8266WebServer/WebServer is needed"
When calling the append function with request handler parameters, an instance of the WebServer as the registration destination must exist.
AutoConnect can instantiate and host a WebServer internally, but in that case, the point in time to call the [AutoConnct::append](api.md#append) function with a request handler parameter must be after [AutoConnect::begin](api.md#begin).
### <i class="fa fa-caret-right"></i> Change the menu labels
@ -299,7 +318,7 @@ You can change the label text for each menu item but cannot change them at run t
2. Change the label literals for each Arduino project
Another way to change the label literal is to provide a header file that defines the label literals, as mentioned in [Appendix](changelabel.md#change-the-items-label-text). You can also use this method to display label text and fixed text in the local language on the AutoConnect page. See [**Appendix:Change the item's label text**](changelabel.md#change-the-items-label-text) for details.
Another way to change the label literal is to provide a header file that defines the label literals, as mentioned in [Appendix](changelabel.md#change-the-items-label-text). You can also use this method to display label text and fixed text in the local language on the AutoConnect page. See [Change the item's label text](changelabel.md#change-the-items-label-text) section for details.
### <i class="fa fa-caret-right"></i> Combination with mDNS
@ -357,7 +376,7 @@ Combining these two parameters allows you to filter the destination AP when mult
</tr>
<tr>
<td>Specified with the Sketch</td>
<td>Not efective</td>
<td>Not effective</td>
<td>By AutoConnect::begin parameters</td>
<td>Use the specified value of AutoConnectConfig</td>
</tr>
@ -376,14 +395,52 @@ Combining these two parameters allows you to filter the destination AP when mult
### <i class="fa fa-caret-right"></i> Debug print
You can output AutoConnect monitor messages to the **Serial**. A monitor message activation switch is in an include header file [AutoConnectDefs.h](https://github.com/Hieromon/AutoConnect/blob/master/src/AutoConnectDefs.h) of library source. Define [**AC_DEBUG**](https://github.com/Hieromon/AutoConnect/blob/master/src/AutoConnectDefs.h#L14) macro to output the monitor messages.[^1]
You can output AutoConnect monitor messages to the **Serial**. A monitor message activation switch is in an include header file [AutoConnectDefs.h](https://github.com/Hieromon/AutoConnect/blob/master/src/AutoConnectDefs.h) of library source. Define [**AC_DEBUG**](https://github.com/Hieromon/AutoConnect/blob/master/src/AutoConnectDefs.h#L14) macro to output the monitor messages.[^2]
[^1]:The source code placement of common macros for AutoConnect since v0.9.7 has changed.
[^2]:The source code placement of common macros for AutoConnect since v0.9.7 has changed.
```cpp
#define AC_DEBUG
```
### <i class="fa fa-caret-right"></i> Detect WiFi connection establishment with a router
[*AutoConnect::onConnect*](api.md#onconnect) allows the Sketch to detect a WiFi connection to a router. The Sketch uses [*AutoConnect::onConnect*](api.md#onconnect) to register a function to call when WiFi connected.
For example, as the following Sketch, this can be combined with [*AutoConnectConfig::retainPortal*](apiconfig.md#retainportal) to stop **SoftAP** in a **loop()**. It avoids blocking in the captive portal state by AutoConnect and allows the loop to run even without a WiFi connection.
```cpp hl_lines="13 14 15 16 17 18 19"
#include <ESP8266WiFi.h>
#include <ESP8266WebServer.h>
#include <AutoConnect.h>
AutoConnect Portal;
AutoConnectConfig config;
void setup() {
Serial.begin(115200);
config.portalTimeout = 1;
config.retainPortal = true;
portal.config(config);
portal.onConnect([](IPAddress& ip){
Serial.printf("Connected %s\n", ip.toString().c_str());
if (WiFi.getMode() == WI_AP_STA) {
WiFi.softAPdisconnect(false);
WiFi.mode(WiFi_STA);
}
});
portal.begin();
}
void loop() {
// Here, the Sketch can execute without WiFi connection.
// It avoids blocking the state by the captive portal even if the captive portal is available.
portal.handleClient();
}
```
!!! note "It is not an event"
AutoConnect::onConnect has the same effect on the Sketch as the [WiFi.onStationModeConnected](https://arduino-esp8266.readthedocs.io/en/latest/esp8266wifi/generic-class.html#onevent), but AutoConnect does not use the event. Sketch can use `WiFi.onEvent` independently of AutoConnect.
### <i class="fa fa-caret-right"></i> Disable the captive portal
It can also prevent the captive portal from starting even if the connection at the 1st-WiFi.begin fails. In this case, [*AutoConnect::begin*](api.md#begin) behaves same as *WiFi.begin*.
@ -418,7 +475,7 @@ portal.begin();
### <i class="fa fa-caret-right"></i> On-demand start the captive portal
The [default behavior of AutoConnect::begin](lsbegin.md) gives priority to connect to the least recently established access point. In general, We expect this behavior in most situations, but will intentionally launch the captive portal on some occasion.
The [default behavior](lsbegin.md) of [*AutoConnect::begin*](api.md#begin) gives priority to connect to the least recently established access point. In general, We expect this behavior in most situations, but will intentionally launch the captive portal on some occasion.
Here section describes how to launch on demand the captive portal, and suggests two templates that you can use to implement it.
@ -481,7 +538,7 @@ Here section describes how to launch on demand the captive portal, and suggests
}
bool connectWiFi(const char* ssid, const char* password, unsigned long timeout) {
WiFi.mode(WIFI_STA);
WiFi.mode(WiFi_STA);
delay(100);
WiFi.begin(ssid, password);
unsigned long tm = millis();
@ -519,7 +576,7 @@ Here section describes how to launch on demand the captive portal, and suggests
portal = nullptr;
}
}
// Here, ordinally sketch logic.
// Here, ordinary sketch logic.
}
```
@ -585,7 +642,9 @@ void loop() {
### <i class="fa fa-caret-right"></i> Use with the [PageBuilder](https://github.com/Hieromon/PageBuilder) library
In ordinary, the URL handler will respond the request by sending some HTML. [PageBuilder](https://github.com/Hieromon/PageBuilder) library is HTML assembly aid. it can handle predefined HTML as like a template and simplify an HTML string assemble logic, and also the generated HTML send automatically.
In ordinary, the URL handler will respond to the request from the client by sending some HTML. It will dynamically generate the HTML to respond to based on the sensing data etc. for the changing scene, but it contains elements of variable values in the middle of the HTML fixed string. Therefore, sketches tend to be in a tangled that repeats the logic for data handling and string splicing in turn, which tends to be less readable and maintainable.
[PageBuilder](https://github.com/Hieromon/PageBuilder) library is an HTML assembly aid. it can handle predefined HTML like the template and simplify an HTML string assemble logic, and also the generated HTML send automatically.
An example sketch used with the PageBuilder as follows and it explains how it aids for the HTML generating. Details for [Github repository](https://github.com/Hieromon/PageBuilder).
@ -593,24 +652,200 @@ An example sketch used with the PageBuilder as follows and it explains how it ai
## Configuration functions
You can adjust the AutoConnect behave at run-time using [AutoConnectConfig](apiconfig.md). AutoConnectConfig is a class that has only AutoConnect configuration data. You define the behavior of AutoConnect using AutoConnectConfig member variables and give it to AutoConnect via the [AutoConnect::config](api.md#config) function.
AutoConnectConfig can specify the following runtime behavior:
You can adjust the AutoConnect behave at run-time using [AutoConnectConfig](apiconfig.md). AutoConnectConfig is a class that has only AutoConnect configuration settings. You define the behavior of AutoConnect using AutoConnectConfig member variables and give it to AutoConnect via the [*AutoConnect::config*](api.md#config) function.
AutoConnectConfig allows the Sketch controls the behavior of follows:
- [Applying HTTP authentication](#applying-http-authentication)
- [Applying HTTP authentication for Built-in OTA](#applying-http-authentication-for-built-in-ota)
- [Authentication with the captive portal state](#authentication-with-the-captive-portal-state)
- [Assign user sketch's home path](#assign-user-sketchs-home-path)
- [Built-in OTA update](#built-in-ota-update-feature)
- [Change menu title](#change-menu-title)
- [Change SSID and Password for SoftAP](#change-ssid-and-password-for-softap)
- [Configuration for Soft AP and captive portal](#configuration-for-soft-ap-and-captive-portal)
- [Configuration for SoftAP and captive portal](#configuration-for-softap-and-captive-portal)
- [Configure WiFi channel](#configure-wifi-channel)
- [Move the saving area of EEPROM for the credentials](#move-the-saving-area-of-eeprom-for-the-credentials)
- [Preserve WIFI_AP mode](#preserve-wifi_ap-mode)
- [Relocate the AutoConnect home path](#relocate-the-autoconnect-home-path)
- [Static IP assignment](#static-ip-assignment-2)
- [Station host name](#station-host-name)
- [Ticker for WiFi status](#ticker-for-wifi-status)
!!! note "AutoConnect::config before AutoConnect::begin"
*AutoConnect::config* must be executed before *AutoConnect::begin*.
AutoConnect::config must be executed before [*AutoConnect::begin*](api.md#begin).
### <i class="fa fa-caret-right"></i> Applying HTTP authentication
The Sketch may use authentication to protect custom Web pages and prevent unauthorized access. AutoConnect has implemented HTTP authentication based on the [ESP8266WebServer::authenticate](https://github.com/esp8266/Arduino/tree/master/libraries/ESP8266WebServer#authentication) function that allows applied to several scopes.
[*AutoConnectConfig::auth*](apiconfig.md#auth) setting allows the Sketch to HTTP authenticate with "[**BASIC**](https://tools.ietf.org/html/rfc2617#section-2)" or "[**DIGEST**](https://tools.ietf.org/html/rfc2617#section-3)" scheme. [*AutoConnectConfig::authScope*](apiconfig.md#authscope) specifies the scope covered by authentication which is the whole range for all pages of the Sketch, custom web pages, or AutoConnect pages. [*AutoConnectConfig::username*](apiconfig.md#username) and [*AutoConnectConfig::password*](apiconfig.md#password) specifies credential as user-id/password pairs.
The Sketch enables HTTP authentication with the [*AutoConnectConfig::auth*](apiconfig.md#auth) setting, also specifies the authentication scheme:
- **AC_AUTH_NONE**
AutoConnect pages and custom Web pages do not require authentication. Not protected from all HTTP access. This is the default.
- **AC_AUTH_DIGEST**
Protect AutoConnect pages and custom Web pages with DIGEST authentication.
- **AC_AUTH_BASIC**
Protect AutoConnect pages and custom Web pages with BASIC authentication.
Note that the authentication scope specified in [*AutoConnectConfig::authScope*](apiconfig.md#authscope) is different from the protection space shown by [**Realm**](https://tools.ietf.org/html/rfc7235#section-2.2) for HTTP authentication. AutoConnect assumes only one Realm and defines it as [**AUTOCONNECT_AUTH_REALM**](https://github.com/Hieromon/AutoConnect/blob/master/src/AutoConnectDefs.h#L239) in AutoConnectDefs.h header file. Instead, the Sketch will be able to expand or narrow the range of authentication by [*AutoConnectConfig::authScope*](apiconfig.md#authscope) setting, which can be either as follows:
- **AC_AUTHSCOPE_PORTAL**
Require authentication to access for all AutoConnect pages, including custom Web pages.
- **AC_AUTHSCOPE_AUX**
Require authentication to access for all custom Web pages, excepting AutoConnect pages. This is the Default.
- **AC_AUTHSCOPE_PARTIAL**
Authenticate only specific custom Web pages which are specified by [*AutoConnectAux::authentication*](apiaux.md#authentication) function or [JSON](acjson.md#auth).
Also, a credential used for authentication is specified in the Sketch using the [*AutoConnectConfig::username*](apiconfig.md#username) and [*AutoConnectConfig::password*](apiconfig.md#password) settings.[^3]
[^3]:The default user name and password for authentication inherits the setting of [AutoConnectConfig::apid](apiconfig.md#apid) and [AutoConnectConfig::psk](apiconfig.md#psk).
Here's a minimal Sketch with HTTP authentication for the custom Web page:
```cpp hl_lines="26 27 28 29"
#include <ESP8266WiFi.h>
#include <ESP8266WebServer.h>
#include <AutoConnect.h>
static const char PAGE_AUTH[] PROGMEM = R"(
{
"uri": "/auth",
"title": "Auth",
"menu": true,
"element": [
{
"name": "text",
"type": "ACText",
"value": "AutoConnect has authorized",
"style": "font-family:Arial;font-size:18px;font-weight:400;color:#191970"
}
]
}
)";
WebServerClass server;
AutoConnect portal(server);
AutoConnectConfig config;
void setup() {
config.auth = AC_AUTH_DIGEST;
config.authScope = AC_AUTHSCOPE_AUX;
config.username = "user";
config.password = "password";
portal.config(config);
portal.load(FPSTR(PAGE_AUTH));
portal.begin();
}
void loop() {
portal.handleClient();
}
```
If you want to authenticate only specific pages in a Sketch that handles multiple custom Web pages, set **AC_AUTHSCOPE_PARTIAL** to [*AutoConnectConfig::authScope*](apiconfig.md#authscope). Then, it specifies the authentication scheme with the [**auth**](acjson.md#auth) key in the JSON description of the page should be authenticated.
AutoConnect determines which authentication method to use for custom Web pages (such as AutoConnectAux) based on its association with [*AutoConnectConfig::authScope*](apiconfig.md#authscope) setting. The table below shows which authentication scheme will be finally adopted. As shown in this table, the final authentication scheme depends on the [*AutoConnectConfig::authScope*](apiconfig.md#authscope) setting, and if **AC_AUTHSCOPE_PARTIAL** is specified it, [AutoConnectAux's authentication](apiaux.md#authentication) setting takes precedence over the [*AutoConnectConfig::auth*](apiconfig.md#auth) setting.
| AutoConnectConfig::authScope | Authentication scheme for the custom Web page |
|---|---|
| AC_AUTHSCOPE_PORTAL | Specified by AutoConnectConfig::auth |
| AC_AUTHSCOPE_AUX | Specified by AutoConnectConfig::auth |
| AC_AUTHSCOPE_PARTIAL | Specified by [AutoConnectAux::authentication](apiaux.md#authentication), The default values is **AC_AUTH_NONE**. |
Authentication designation for AutoConnectAux can also be specified by giving the following value to the **auth** key by the JSON description:
- "auth" : "basic"
- "auth" : "digest"
- "auth" : "none"
The following example Sketch has two custom Web pages, `Hello` and `Auth`. It applies authentication only to the `Auth` page by setting AC_AUTHSCOPE_PARTIAL to AutoConnectConfig::authScope.
```cpp hl_lines="26 45"
#include <ESP8266WiFi.h>
#include <ESP8266WebServer.h>
#include <AutoConnect.h>
static const char PAGE_HELLO[] PROGMEM = R"(
{
"uri": "/hello",
"title": "Hello",
"menu": true,
"element": [
{
"name": "text",
"type": "ACText",
"value": "Hello, word",
"style": "font-family:Arial;font-size:18px;font-weight:400;color:#191970"
}
]
}
)";
static const char PAGE_AUTH[] PROGMEM = R"(
{
"uri": "/auth",
"title": "Auth",
"menu": true,
"auth": "digest",
"element": [
{
"name": "text",
"type": "ACText",
"value": "AutoConnect has authorized",
"style": "font-family:Arial;font-size:18px;font-weight:400;color:#191970"
}
]
}
)";
WebServerClass server;
AutoConnect portal(server);
AutoConnectConfig config;
void setup() {
// It's a default value but has no meaning in the AC_AUTHSCOPE_PARTIAL setting.
// config.auth = AC_AUTH_NONE;
config.authScope = AC_AUTHSCOPE_PARTIAL;
config.username = "user";
config.password = "password";
portal.config(config);
portal.load(FPSTR(PAGE_HELLO));
portal.load(FPSTR(PAGE_AUTH));
portal.begin();
}
void loop() {
portal.handleClient();
}
```
!!! info "PageBuilder v1.4.0 or later needed"
[PageBuilder](https://github.com/Hieromon/PageBuilder) v1.4.0 or later is required to use HTTP authentication with AutoConnect. Also, v1.4.2 or later is required to eliminate SPIFFS, which is deprecated as a file system for ESP8266 module.
!!! warning "Can not use ESP32 arduino core 1.0.4 stable release"
For ESP32, Arduino core 1.0.4 stable has a bug for HTTP authentication. The upstream of the master is recommended. (or use 1.0.5 later)
### <i class="fa fa-caret-right"></i> Applying HTTP authentication for Built-in OTA
[*AutoConnectConfig::auth*](apiconfig.md#auth) setting also affects the [built-in OTA](otaupdate.md) feature. **AC_AUTH_BASIC** or **AC_AUTH_DIGEST** setting allows Built-in OTA to authenticate with the [UPDATE](otabrowser.md#updates-with-the-web-browserupdated-wv115) page. This setting is valid even if [*AutoConnectConfig::authScope*](apiconfig.md#authscope) is **AC_AUTHSCOPE_PARTIAL**. That is if the AutoConnectConfig::auth setting is BASIC or DIGEST, authentication will be required for Built-in OTA.
### <i class="fa fa-caret-right"></i> Authentication with the captive portal state
When accessing the ESP module from an iOS or Android device in the captive portal state, the HTTP authentication framework is disabled in the OS of the client device. Even if the ESP module responds with a `401 unauthorized` with `WWW-Authenticate`, those client device OSs under the captive portal do not display the login dialog and deprive the user of the opportunity to enter their credentials. There will always be an unauthorized error.
AutoConnect's authentication operation based on HTTP (not HTTPS) depends on the OS of the client device, so in the captive portal state, most devices will unconditionally result in an authentication error. Therefore, the default authentication behavior of AutoConnect does not apply authentication in the captive portal state. (It will be ignored even if the AutoConnect setting is not AC_AUTH_NONE)
However, if you want to deny unauthorized access to the protected page even in the captive portal state, you can use the extension bit of [*AutoConnectConfig::authScope*](apiconfig.md#authScope). The **AC_AUTHSCOPE_WITHCP** flag allows AutoConnect to authentication in the captive portal state. It is set using a logical OR operator for the [*AutoConnectConfig::authScope*](apiconfig.md#authScope) setting and AutoConnect will enable authentication at the captive portal if the **AC_AUTHSCOPE_WITHCP** is ON.
```cpp hl_lines="4"
AutoConnectConfig config;
...
config.auth = AC_AUTH_DIGEST;
config.authScope = AC_AUTHSCOPE_AUX | AC_AUTHSCOPE_WITHCP;
...
```
### <i class="fa fa-caret-right"></i> Assign user sketch's home path
@ -621,11 +856,11 @@ AutoConnectConfig can specify the following runtime behavior:
The Sketch HOME path is closely related to the [bootUri](apiconfig.md#booturi) that specifies the access path on module restart. AutoConnect has the following three parameters concerning control the URIs:
- **AUTOCONNECT_URI**
The **ROOT** of AutoConnect. It is defined in `AutoConnectDefs.h` and is assigned an [AutoConnect statistics screen](menu.md#where-the-from) by default.
The **ROOT** URI of AutoConnect. It is defined in [AutoConnectDefs.h](https://github.com/Hieromon/AutoConnect/blob/master/src/AutoConnectDefs.h#L69) file and is assigned to [AutoConnect statistics screen](menu.md#where-the-from) by default.
- [**AutoConnectConfig::homeUri**](apiconfig.md#homeuri)
It is the hyperlink of listed on the AutoConnect menu list as **HOME**.
It is the hyperlink of listed on the AutoConnect menu as **HOME**.
- [**AutoConnectConfig::bootUri**](apiconfig.md#booturi)
Which page appears at the captive portal, AUTOCONNECT_URI or the homeUri. Its page will pop up automatically when you visit the captive portal.
Which page appears at the captive portal, AUTOCONNECT_URI, or the homeUri. Its page will pop up automatically when you visit the captive portal.
| The definition of **HOME** | Behavior | Specified by | Default value | Possible value |
|---|---|---|---|---|
@ -638,7 +873,7 @@ The Sketch HOME path is closely related to the [bootUri](apiconfig.md#booturi) t
AutoConnect features a built-in OTA function to update ESP module firmware. You can easily make the Sketch that equips OTA and able to operate with the AutoConnect menu.
<span style="display:block;margin-left:auto;margin-right:auto;width:294px;height:482px;border:1px solid lightgrey;"><img data-gifffer="images/webupdate.gif" data-gifffer-height="480" data-gifffer-width="292" /></span>
<span style="display:block;margin-left:auto;margin-right:auto;width:284px;height:462px;border:1px solid lightgrey;"><img data-gifffer="images/webupdate.gif" data-gifffer-height="460" data-gifffer-width="282" /></span>
[*AutoConnectConfig::ota*](apiconfig.md#ota) specifies to import the [built-in OTA update class](otabrowser.md) into the Sketch.
See the [Updates with the Web Browser](otabrowser.md) chapter for details.
@ -684,7 +919,7 @@ Also, you can specify the SSID, password for SoftAP with the constructor of the
```cpp hl_lines="2"
AutoConnect portal;
AutoConnectConfig config("ap_portal", "new_passwrod");
AutoConnectConfig config("ap_portal", "new_password");
void setup() {
portal.config(config);
@ -695,7 +930,7 @@ void setup() {
You can also assign no password to SoftAP launched as a captive portal. Assigning a null string as `String("")` to [*AutoConnectConfig::psk*](apiconfig.md#psk) does not require a password when connecting to SoftAP.
But this method is not recommended. The broadcast radio of SSID emitted from SoftAP will leak and reach several tens of meters.
### <i class="fa fa-caret-right"></i> Configuration for Soft AP and captive portal
### <i class="fa fa-caret-right"></i> Configuration for SoftAP and captive portal
AutoConnect will activate SoftAP at failed the 1st-WiFi.begin. It SoftAP settings are stored in [**AutoConnectConfig**](apiconfig.md#autoconnectconfig) as the following parameters. The Sketch could be configured SoftAP using these parameters, refer the [AutoConnectConfig API](apiconfig.md#public-member-variables) for details.
@ -729,19 +964,65 @@ The [**boundaryOffset**](apiconfig.md#boundaryoffset) in [AutoConnectConfig](api
!!! info "The boundaryOffset ignored with AutoConnect v1.0.0 later on ESP32 arduino core 1.0.3 later"
For ESP32 arduino core 1.0.3 and later, AutoConnect will store credentials to Preferences in the nvs. Since it is defined as the namespace dedicated to AutoConnect and separated from the area used for user sketches. Therefore, the [boundaryOffset](apiconfig.md#boundaryoffset) is ignored with the combination of AutoConnect v1.0.0 or later and the arduino-esp32 1.0.3 or later.
The [*AutoConnectConfig::boundaryOffset*](apiconfig.md#boundaryoffset) setting allows AutoConnect to write its data to EEPROM while preserving custom configuration data. Similarly, when a Sketch writes its own data to EEPROM, it must preserve the data written by AutoConnect.
The EEPROM library for ESP8266 erases the entire flash sector when it writes to any part of the sector. Therefore, when writing data to EEPROM with a sketch that handles the custom data, it is necessary to call `EEPROM.begin` using a total amount of a custom data size and the saved credentials size.
The following code shows how to use the [*AutoConnect::getEEPROMUsedSize*](api.md#geteepromusedsize) function to store custom configuration settings in EEPROM without conflicting with AutoConnect's use of that storage.
```cpp hl_lines="13 21"
AutoConnect portal;
AutoConnectConfig config;
// Defines the custom data should be stored in EEPROM.
typedef struct {
char data1[8];
char data2[8];
char data3[8];
} EEPROM_CONFIG_t;
EEPROM_CONFIG_t eepromConfig;
...
// Declares to reserve the EEPROM_CONFIG_t area for a Sketch using.
config.boundaryOffset = sizeof(eepromConfig);
portal.config(config);
...
strcpy(eepromComfig.data1, "data1");
strcpy(eepromComfig.data2, "data2");
strcpy(eepromComfig.data3, "data3");
// Use getEEPROMUsedSize to access the EEPROM with the appropriate region size.
EEPROM.begin(portal.getEEPROMUsedSize());
EEPROM.put<EEPROM_CONFIG_t>(0, eepromConfig);
EEPROM.commit();
EEPROM.end();
...
```
### <i class="fa fa-caret-right"></i> Preserve WIFI_AP mode
Sketch using AutoConnect can open a gateway to the Internet by connecting to a WiFi router even through use Espressif's peculiar WiFi protocol (eg. [ESP-MESH](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-guides/mesh.html) or [ESP-NOW](https://www.espressif.com/en/products/software/esp-now)). These specific communication protocols require to keeps AP + STA as the WiFi mode. That is, to apply these protocols, it needs to launch SoftAP by a sketch itself and then call [*AutoConnect::begin*](api.md#begin). But the default behavior of [*AutoConnect::begin*](api.md#begin) will turn off SoftAP always then it will unable to open a connection.
[*AutoConnectConfig::preserveAPMode*](apiconfig.md#preserveAPMode) setting maintains WIFI_AP mode without disabling SoftAP inside [*AutoConnect::begin*](api.md#begin). The Sketch can utilize the WiFi connection via AutoConnect with ESP-MESH and ESP-NOW protocol by enabling this option.
The following diagram quoted from the [ESP-MESH documentation](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-guides/mesh.html#mesh-building-a-network) that illustrates the typical topology of the MESH network. The module located at the Root Node bridges between the mesh network and the router by an application that handles two protocols, TCP/IP and ESP-MESH. Its SoftAP communicates with the internal mesh network as an interface of the mesh layer. On the other hand, STA performs station communication with the WiFi router as an interface of the TCP/IP layer. AutoConnect allows assists the connection between the router and the STA of the Root Node using [*AutoConnectConfig::preserveAPMode*](apiconfig.md#preserveapmode) and starting the SoftAP via Sketch separately.
<img src="https://docs.espressif.com/projects/esp-idf/en/latest/esp32/_images/mesh-bidirectional-data-stream.png">
Also in general, the Sketch should set **false** to [*AutoConnectConfig::autoRise*](apiconfig.md#autorise), **true** to [*AutoConnectConfig::immediateStart*](apiconfig.md#immediatestart) when applying to those protocols.
### <i class="fa fa-caret-right"></i> Relocate the AutoConnect home path
A home path of AutoConnect is **/\_ac** by default. You can access from the browser with http://IPADDRESS/\_ac. You can change the home path by revising [**AUTOCONNECT_URI**](https://github.com/Hieromon/AutoConnect/blob/master/src/AutoConnectDefs.h#L69) macro in the include header file as [AutoConnectDefs.h](https://github.com/Hieromon/AutoConnect/blob/master/src/AutoConnectDefs.h).
A home path of AutoConnect is **/\_ac** by default. You can access from the browser with http://IPADDRESS/\_ac. You can change the home path by revising [**AUTOCONNECT_URI**](https://github.com/Hieromon/AutoConnect/blob/master/src/AutoConnectDefs.h#L69) macro in AutoConnectDefs.h header file.
```cpp
#define AUTOCONNECT_URI "/_ac"
```
### <i class="fa fa-caret-right"></i> Static IP assignment [^2]
### <i class="fa fa-caret-right"></i> Static IP assignment
It is also possible to assign static IP Address to ESP8266/ESP32 in STA mode. By default DHCP is enabled and it becomes the IP address assigned by the DHCP server with *WiFi.begin*.
It is also possible to assign static IP Address to ESP8266/ESP32 in STA mode.[^4] By default DHCP is enabled and it becomes the IP address assigned by the DHCP server with *WiFi.begin*.
To assign a static IP to ESP8266/ESP32 with WIFI\_MODE\_STA, the following parameters are required:
[^4]:Static IP address assignment is available from version 0.9.3.
To assign a static IP to ESP8266/ESP32 with WiFi\_MODE\_STA, the following parameters are required:
- IP address.
- Gateway address.
@ -762,8 +1043,6 @@ portal.config(Config);
portal.begin();
```
[^2]:Static IP address assignment is available from version 0.9.3.
### <i class="fa fa-caret-right"></i> Station host name
[*AutoConnectConfig::hostName*](apiconfig.md#hostname) assigns the station DHCP hostname which complies with [RFC952](https://tools.ietf.org/html/rfc952). It must satisfy the following constraints.
@ -790,11 +1069,11 @@ portal.begin();
The AutoConnect ticker indicates the WiFi connection status in the following three flicker patterns:
- Short blink: The ESP module stays in APSTA mode.
- Short blink: The ESP module stays in AP_STA mode.
- Short-on and long-off: No STA connection state. (i.e. WiFi.status != WL_CONNECTED)
- No blink: WiFi connection with access point established and data link enabled. (i.e. WiFi.status = WL_CONNECTED)
The flicker cycle length is defined by some macros in `AutoConnectDefs.h` header file.
The flicker cycle length is defined by some macros in [AutoConnectDefs.h](https://github.com/Hieromon/AutoConnect/blob/master/src/AutoConnectDefs.h) header file.
```cpp
#define AUTOCONNECT_FLICKER_PERIODAP 1000 // [ms]
@ -803,16 +1082,16 @@ The flicker cycle length is defined by some macros in `AutoConnectDefs.h` header
#define AUTOCONNECT_FLICKER_WIDTHDC 16 // (8 bit resolution)
```
- `AUTOCONNECTT_FLICKER_PERIODAP`:
Assigns a flicker period when the ESP module stays in APSTA mode.
- `AUTOCONNECT_FLICKER_PERIODDC`:
- **AUTOCONNECTT_FLICKER_PERIODAP**:
Assigns a flicker period when the ESP module stays in AP_STA mode.
- **AUTOCONNECT_FLICKER_PERIODDC**:
Assigns a flicker period when WiFi is disconnected.
- `AUTOCONNECT_FLICKER_WIDTHAP` and `AUTOCONNECT_FLICKER_WIDTHDC`:
Specify the duty rate for each period[ms] in 8-bit resolution.
- **AUTOCONNECT_FLICKER_WIDTHAP** and **AUTOCONNECT_FLICKER_WIDTHDC**:
Specify the duty rate for each period [ms] in 8-bit resolution.
[*AutoConnectConfig::tickerPort*](apiconfig.md#tickerport) specifies a port that outputs the flicker signal. If you are using an LED-equipped ESP module board, you can assign a LED pin to the tick-port for the WiFi connection monitoring without the external LED. The default pin is arduino valiant's **LED\_BUILTIN**. You can refer to the Arduino IDE's variant information to find out which pin actually on the module assign to **LED\_BUILTIN**.[^3]
[*AutoConnectConfig::tickerPort*](apiconfig.md#tickerport) specifies a port that outputs the flicker signal. If you are using an LED-equipped ESP module board, you can assign a LED pin to the tick-port for the WiFi connection monitoring without the external LED. The default pin is arduino valiant's **LED\_BUILTIN**. You can refer to the Arduino IDE's variant information to find out which pin actually on the module assign to **LED\_BUILTIN**.[^5]
[^3]: It's defined in the `pins_arduino.h` file, located in the sub-folder named **variants** wherein Arduino IDE installed folder.
[^5]: It's defined in the `pins_arduino.h` file, located in the sub-folder named **variants** wherein Arduino IDE installed folder.
[*AutoConnectConfig::tickerOn*](apiconfig.md#tickeron) specifies the active logic level of the flicker signal. This value indicates the active signal level when driving the ticker. For example, if the LED connected to tickPort lights by LOW, the tickerOn is **LOW**. The logic level of LED_BUILTIN for popular modules are as follows:

@ -11,6 +11,7 @@
They contain in ```AutoConnectDefs.h```.
```cpp
#define AC_USE_SPIFFS // Use SPIFFS for the file system on the onboard flash, assumes LittleFS if not defined.
#define AC_DEBUG // Monitor message output activation
#define AC_DEBUG_PORT Serial // Default message output device
#define AUTOCONNECT_AP_IP 0x011CD9AC // Default SoftAP IP
@ -68,6 +69,40 @@ The [**handleClient**](api.md#handleclient) function of AutoConnect can include
## <i class="fa fa-code"></i> Public member functions
### <i class="fa fa-caret-right"></i> append
- ESP8266/ESP32 Common
```cpp
AutoConnectAux* append(const String& uri, const String& title)
```
- For ESP8266
```cpp
AutoConnectAux* append(const String& uri, const String& title, ESP8266WebServer::THandlerFunction handler)
```
- For ESP32
```cpp
AutoConnectAux* append(const String& uri, const String& title, WebServer::THandlerFunction handler)
```
Creates an AutoConnectAux dynamically with the specified URI and integrates it into the menu. Calls with a request handler parameter can use this function as menu registration for a legacy page of ESP8266WebServer/WebServer. If the **handler** parameter specified, also it will register the request handler for the ESP8266WebServer/WebServer.
AutoConnect manages the menu items using a sequence list, and this function always adds the item to the end of the list. Therefore, the order of the menu items is the additional order.
Returns the pointer to created AutoConnectAux instance, the `nullptr` if an AutoConnectAux with the same URI already exists.
<dl class="apidl">
<dt>**Parameter**</dt>
<dd><span class="apidef">uri</span><span class="apidesc">A string of the URI.</span></dd>
<dd><span class="apidef">title</span><span class="apidesc">Title for menu item.</span></dd>
<dd><span class="apidef">handler</span><span class="apidesc">Request handler function as type of **ESP8266WebServer::THandlerFunction**/**WebServer::THandlerFunction**.</span></dd>
<dt>**Return value**</dt>
<dd>A Pointer to a created AutoConnectAux instance.</dd>
</dl>
!!! note "An instance of ESP8266WebServer/WebServer is needed"
The WebServer must have instantiated for calling with a request handler parameter. AutoConnect can instantiate and host a WebServer internally, but in that case, the point in time to call the append function with a request handler parameter must be after AutoConnect::begin.
### <i class="fa fa-caret-right"></i> aux
```cpp
@ -126,13 +161,45 @@ Set SoftAP's WiFi configuration and static IP configuration.
<dd><span class="apidef">false</span><span class="aidesc">Configuration parameter is invalid, some values out of range.</span></dd>
</dl>
### <i class="fa fa-caret-right"></i> detach
```cpp
bool detach(const String& uri)
```
Detach the AutoConnectAux with the specified URI from the management of AutoConnect. An unmanaged AutoConnectAux will no longer appear in menu items, and its page handler will no longer respond even if the URI is accessed directly.
<dl class="apidl">
<dt>**Parameter**</dt>
<dd><span class="apidef">uri</span><span class="apidesc">URI of AutoConnectAux to be detached.</span></dd>
<dt>**Return value**</dt>
<dd><span class="apidef">true</span><span class="apidesc">Successfully detached.</span></dd>
<dd><span class="apidef">false</span><span class="aidesc">An AutoConnectAux with the specified URI does not exist.</span></dd>
</dl>
If the request handler registered in the detaching AutoConnectAux is for a legacy page of the ESP8266WebServer/WebServer, the URI is still valid after detaching. AutoConnect does not delete the request handler registered to ESP8266WebServer/WebServer with the `on` function. (It cannot be removed)
!!! hint "Deleting the AutoConnectAux"
If the AutoConnectAux to detach was added by [AutoConnect::append](api.md#append), it will be automatically removed and freed from memory.
### <i class="fa fa-caret-right"></i> disableMenu
```cpp
void disableMenu(const uint16_t items)
```
Disable the [AutoConnect menu](menu.md) items specified by the items parameter with logical OR value using **AC_MENUITEM_t** constant.
This function only works for AutoConnect primary menu items. It has no effect on disable for AutoConnectAux items. To disable the items by AutoConnectAux, use the [AutoConnectAux::menu](apiaux.md#menu) function.
<dl class="apidl">
<dt>**Parameter**</dt>
<dd><span class="apidef">items</span><span class="apidesc">Specify the combined value of **AC_MENUITEM_t** of the items deleting from the AutoConnect menu. It provides the value calculated from the **logical OR** by the AC_MENUITEM_t value of each item. Refer to the [enableMenu](#enablemenu) about AC_MENUITEM_t.</span></dd>
</dl>
### <i class="fa fa-caret-right"></i> enableMenu
```cpp
void enableMenu(const uint16_t items)
```
Enable the [AutoConnect menu](menu.md) items specified by the items parameter with logical OR value using **AC_MENUITEM_t** constant.
Enable the [AutoConnect menu](menu.md) items specified by the items parameter with logical OR value using **AC_MENUITEM_t** constant.
This function only works for AutoConnect primary menu items. It has no effect on enable for AutoConnectAux items. To enable the items by AutoConnectAux, use the [AutoConnectAux::menu](apiaux.md#menu) function.
<dl class="apidl">
<dt>**Parameter**</dt>
<dd><span class="apidef">items</span><span class="apidesc">Specify the combined value of **AC_MENUITEM_t** of the items applying to the AutoConnect menu. It provides the value calculated from the **logical OR** by the AC_MENUITEM_t value of each item applied as a menu. AC_MENUITEM_t is enumeration type to identify each menu item and it has the below values.</span></dd>
@ -166,19 +233,21 @@ Stops AutoConnect captive portal service. Release ESP8266WebServer/WebServer and
!!! warning "Attention to end"
The end function releases the instance of ESP8266WebServer/WebServer and DNSServer. It can not process them after the end function.
### <i class="fa fa-caret-right"></i> disableMenu
### <i class="fa fa-caret-right"></i> getEEPROMUsedSize
```cpp
void disableMenu(const uint16_t items)
uint16_t getEEPROMUsedSize(void)
```
Disable the [AutoConnect menu](menu.md) items specified by the items parameter with logical OR value using **AC_MENUITEM_t** constant.
Returns the total amount of memory required to hold the AutoConnect credentials and any custom configuration settings stored in EEPROM. The Sketch that writes its own custom data to the EEPROM must call `EEPROM.begin` with this value.
<dl class="apidl">
<dt>**Parameter**</dt>
<dd><span class="apidef">items</span><span class="apidesc">Specify the combined value of **AC_MENUITEM_t** of the items deleting from the AutoConnect menu. It provides the value calculated from the **logical OR** by the AC_MENUITEM_t value of each item. Refer to the [enableMenu](#enablemenu) about AC_MENUITEM_t.</span></dd>
<dt>**Return value**</dt>
<dd>Total amount size of saved AutoConnect credentials and custom data.</dd>
</dl>
!!! note "The getEEPROMUsedSize is available for only ESP8266 use"
It is available for only ESP8266 use and will return 0 when used with ESP32.
### <i class="fa fa-caret-right"></i> handleClient
```cpp
@ -300,6 +369,28 @@ Register the handler function of the AutoConnectAux.
!!! caution "It is not ESP8266WebServer::on, not WebServer::on for ESP32."
This function effects to AutoConnectAux only. However, it coexists with that of ESP8266WebServer::on or WebServer::on of ESP32.
### <i class="fa fa-caret-right"></i> onConnect
```cpp
void onConnect(ConnectExit_ft fn)
```
Register the function which will call from AutoConnect at the WiFi connection established.
<dl class="apidl">
<dt>**Parameter**</dt>
<dd><span class="apidef">fn</span><span class="apidesc">Function called at the WiFi connected.</span></dd>
</dl>
An *fn* specifies the function called when the WiFi connected. Its prototype declaration is defined as "*ConnectExit_ft*".
```cpp
typedef std::function<void(IPAddress& localIP)> ConnectExit_ft;
```
<dl class="apidl">
<dt>**Parameter**</dt>
<dd><span class="apidef">localIP</span><span class="apidesc">An IP address of the ESP module as STA.</span></dd>
</dd>
</dl>
### <i class="fa fa-caret-right"></i> onDetect
```cpp
@ -315,7 +406,7 @@ Register the function which will call from AutoConnect at the start of the capti
An *fn* specifies the function called when the captive portal starts. Its prototype declaration is defined as "*DetectExit_ft*".
```cpp
typedef std::function<bool(IPAddress softapIP)> DetectExit_ft
typedef std::function<bool(IPAddress& softapIP)> DetectExit_ft
```
<dl class="apidl">
<dt>**Parameter**</dt>

@ -42,6 +42,21 @@ Add an element to the AutoConnectAux. An added element is displayed on the custo
<dd><span class="apidef">addons</span><span class="apidesc">An array list of reference of AutoConnectElements. The [list initialization](https://en.cppreference.com/w/cpp/language/list_initialization) with braced-init-list of the [std::vector](https://en.cppreference.com/w/cpp/container/vector) can be used for the addons parameter cause the actual definition of type **AutoConnectElementVT** is `std::vector<std::reference_wrapper<AutoConnectElement>>`.</span></dd>
</dl>
### <i class="fa fa-caret-right"></i> authentication
```cpp
void authentication(const AC_AUTH_t auth)
```
Set to require authentication with access to a page. When you access a page that requires authentication, HTTP authentication will be performed according to the scheme specified with the auth parameter.
<dl class="apidl">
<dt>**Parameters**</dt>
<dd><span class="apidef">auth</span><span class="apidesc">Specifies authentication scheme with the following enumeration value.
- **AC_AUTH_BASIC** : Basic scheme.
- **AC_AUTH_DIGEST** : Digest scheme.
</span></dd>
</dl>
### <i class="fa fa-caret-right"></i> fetchElement
```cpp
@ -179,7 +194,7 @@ Load specified element from JSON document into AutoConnectAux. The JSON document
- Stream : An entity that inherits stream class, generally SPIFFS or SD.
</span></dd>
<dd><span class="apidef">name</span><span class="apidesc">Specifies the name to be load. If the name is not specified, the loadElement function will load all elements contained in the JSON document.</span></dd>
<dd><span class="apidef">names</span><span class="apidesc"> Spefifies an array list of String indicating the name of the element to be loaded. The [list initialization](https://en.cppreference.com/w/cpp/language/list_initialization) with braced-init-list of the [std::vector](https://en.cppreference.com/w/cpp/container/vector) can be used.</span></dd>
<dd><span class="apidef">names</span><span class="apidesc"> Specifies an array list of String indicating the name of the element to be loaded. The [list initialization](https://en.cppreference.com/w/cpp/language/list_initialization) with braced-init-list of the [std::vector](https://en.cppreference.com/w/cpp/container/vector) can be used.</span></dd>
<dt>**Return value**</dt>
<dd><span class="apidef">true</span><span class="apidesc">Specified AutoConnectElements successfully loaded.</span></dd>
<dd><span class="apidef">false</span><span class="apidesc">JSON document loading failed.</span></dd>

@ -40,6 +40,39 @@ Sets IP address for Soft AP in captive portal. When AutoConnect fails the initia
<dd><span class="apidef">IPAddress</span><span class="apidesc">The default value is **172.217.28.1**</span></dd>
</dl>
### <i class="fa fa-caret-right"></i> auth
Apply HTTP authentication with the AutoConnect web page. This This setting allows the Sketch to authenticate with "BASIC" or "DIGEST" scheme. It is given as an enumeration value of **AC_AUTH_t** that indicates the authentication scheme.
This setting determines the default scheme for HTTP authentication with AutoConnect. When the [**AutoConnectConfig::authScope**](#authscope) is **AC_AUTHSCOPE_PARTIAL**, each [AutoConnectAux authentication](apiaux.md#authentication) scheme has priority.
<dl class="apidl">
<dt>**Type**</dt>
<dd>AC_AUTH_t</dd>
<dt>**Value**</dt>
<dd><span class="apidef">AC_AUTH_NONE</span><span class="apidesc"></span><span class="apidef">&nbsp;</span><span class="apidesc">No authentication. This is the default.</span></dd>
<dd><span class="apidef">AC_AUTH_DIGEST</span><span class="apidesc"></span><span class="apidef">&nbsp;</span><span class="apidesc">Use the [digest scheme](https://tools.ietf.org/html/rfc2617#section-3).</span></dd>
<dd><span class="apidef">AC_AUTH_BASIC</span><span class="apidesc"></span><span class="apidef">&nbsp;</span><span class="apidesc">Use the [basic scheme](https://tools.ietf.org/html/rfc2617#section-2).</span></dd>
</dl>
### <i class="fa fa-caret-right"></i> authScope
Specifies the authentication scope of AutoConnect Web pages. The Sketch will be able to expand or narrow the range of authentication by this setting, which can be either as **AC_AUTHSCOPE_t** enumeration value.
<dl class="apidl">
<dt>**Type**</dt>
<dd>AC_AUTHSCOPE_t</dd>
<dt>**Value**</dt>
<dd><span class="apidef">AC_AUTHSCOPE_AUX</span><span class="apidesc"></span><span class="apidef">&nbsp;</span><span class="apidesc">Require authentication to access for all custom Web pages, excepting AutoConnect's pages. This is the Default.</span></dd>
<dd><span class="apidef">AC_AUTHSCOPE_PARTIAL</span><span class="apidesc"></span><span class="apidef">&nbsp;</span><span class="apidesc">Authenticate only specific custom Web pages which are specified by [AutoConnectAux::authentication](apiaux.md#authentication) function or JSON description.</span></dd>
<dd><span class="apidef">AC_AUTHSCOPE_PORTAL</span><span class="apidesc"></span><span class="apidef">&nbsp;</span><span class="apidesc">Require authentication to access for all AutoConnect's pages, including custom Web pages.</span></dd>
</dl>
This setting is available only when [AutoConnectConfig::auth](#auth) is other than **AC_AUTH_NONE**. Ignored if it is AC_AUTH_NONE.
Also, the authScope setting has another bit that indicates to allow authentication in the captive portal state. Its enum value cannot be used alone and is always for qualifying the above three enum values.
<dl class="apidl">
<dt>**Value**</dt>
<dd><span class="apidef">AC_AUTHSCOPE_WITHCP</span><span class="apidesc"></span><span class="apidef">&nbsp;</span><span class="apidesc">Allow authentication with the captive portal state. This value cannot be used alone to declare an authentication scope. It indicates to enable authentication in the captive portal by the logical OR operator with one of the AC_AUTHSCOPE_t values above.</span></dd>
</dl>
### <i class="fa fa-caret-right"></i> autoReconnect
Automatically will try to reconnect with the past established access point (BSSID) when the current configured SSID in ESP8266/ESP32 could not be connected. By enabling this option, *AutoConnect::begin()* function will attempt to reconnect to a known access point using credentials stored in the flash, even if the connection failed by current SSID.
@ -241,6 +274,13 @@ Specifies to import the built-in OTA update class into the Sketch. When this opt
<dd><span class="apidef">AC_OTA_BUILTIN</span><span class="apidesc"></span><span class="apidef">&nbsp;</span><span class="apidesc">Specifies to include AutoConnectOTA in the Sketch.</span></dd>
</dl>
### <i class="fa fa-caret-right"></i> password
Set the password for authentication.
<dl class="apidl">
<dt>**Type**</dt>
<dd><span class="apidef">String</span><span class="apidesc"> The default value is same as [psk](#psk).</span></dd>
</dl>
### <i class="fa fa-caret-right"></i> portalTimeout
@ -250,6 +290,19 @@ Specify the timeout value of the captive portal in [ms] units. It is valid when
<dd><span class="apidef">unsigned long</span><span class="apidesc">Captive portal timeout value. The default value is 0.</span></dd>
</dl>
### <i class="fa fa-caret-right"></i> preserveAPMode
Specifies starting the STA while maintaining the state of the SoftAP mode in the [**AutoConnect::begin**](api.md#begin). This setting only applies when the [**AutoConnectConfig::autoRise**](apiconfig.md#autorise) is false.
<dl class="apidl">
<dt>**Type**</dt>
<dd>bool</dd>
<dt>**Value**</dt>
<dd><span class="apidef">true</span><span class="apidesc">AutoConnect::begin keeps AP mode.</span></dd>
<dd><span class="apidef">false</span>AutoConnect::begin will stop SoftAP at the beginning of the process.<span class="apidesc"></span></dd>
</dl>
Note that this option is not for starting the SoftAP forcibly in [**AutoConnect::begin**](api.md#begin) and only keeps AP mode, SoftAP initiation is left to the Sketch.
### <i class="fa fa-caret-right"></i> principle
Specify the connection order will attempt to connect to one of the highest RSSI values among multiple available access points. It is given as an enumeration value of **AC_PRINCIPLE_t** indicating.
@ -348,6 +401,14 @@ Set the menu title.
<dd>String</dd>
</dl>
### <i class="fa fa-caret-right"></i> username
Set the username for authentication.
<dl class="apidl">
<dt>**Type**</dt>
<dd><span class="apidef">String</span><span class="apidesc"> The default value is same as [apid](#apid).</span></dd>
</dl>
## <i class="fa fa-code"></i> AutoConnectConfig example
```cpp

@ -1,6 +1,6 @@
## Saved credentials in the flash
AutoConnect stores the established WiFi connection in the flash memory of the ESP8266/ESP32 module and equips the class to access the credentials from the sketch. You can read, write or erase the credentials using this class individually. It's [**AutoConnectCredential**](#autoconnectcredential) class which provides the access method to the saved credentials in the flash.[^1]
AutoConnect stores the credentials of the established WiFi connection in the flash memory of the ESP8266/ESP32 module and equips the class to access the credentials from the sketch. You can read, write, or erase the credentials using this class individually. It's the [**AutoConnectCredential**](#autoconnectcredential), which provides the way of access to the credentials stored in flash.[^1]
[^1]:An example using AutoConnectCredential is provided as [an example](https://github.com/Hieromon/AutoConnect/blob/master/examples/Credential/Credential.ino) of a library sketch to delete saved credentials.

@ -183,7 +183,22 @@ Link button to AutoConnect menu can be embedded into Sketch's web page. The root
### Sketch size
It increases about 53K bytes compared to the case without AutoConnect. A sketch size of the most simple example introduced in the Getting started is about 330K bytes. (270K byte without AutoConnect)
1. For ESP8266
It increases about 53K bytes compared to the case without AutoConnect. A sketch size of the most simple example introduced in the Getting started is about 330K bytes. (270K byte without AutoConnect)
2. For ESP32
The BIN size of the sketch grows to over 1M bytes. In the case of a sketch with many custom Web pages, when applying the partition table for the default scheme, the remaining flash size that can be utilized by the user application may be less than 200K bytes. Therefore, it is advisable to resize the partition to make more available space for the application. The ESP32 arduino core has various [partition schemes](https://github.com/espressif/arduino-esp32/tree/master/tools/partitions), and you can choose it according to your Sketch feature.
You can change the partition scheme from the **Tools > Partition Scheme** menu of Arduino IDE.
<img src="images/partition.png">
!!! hint "Change the partition scheme with PlatformIO"
Use `board_build.partitions` directive with `platformio.ini`.
```ini
[env:esp32dev]
board_build.partitions = min_spiffs.csv
```
Details for the [PlatformIO documentation](https://docs.platformio.org/en/latest/platforms/espressif32.html#partition-tables).
### Heap size

Binary file not shown.

After

Width:  |  Height:  |  Size: 143 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

File diff suppressed because it is too large Load Diff

Before

Width:  |  Height:  |  Size: 102 KiB

After

Width:  |  Height:  |  Size: 100 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 87 KiB

@ -94,8 +94,8 @@ Install third-party platform using the *Boards Manager* of Arduino IDE. You can
<i class="fa fa-download"></i> <strong>Additional library (Required)</strong>
The [PageBuilder](https://github.com/Hieromon/PageBuilder) library to build HTML for ESP8266WebServer is needed.
To install the PageBuilder library into your Arduino IDE, you can use the *Library Manager*. Select the board of ESP8266 series in the Arduino IDE, open the library manager and search keyword '**PageBuilder**' with the topic '**Communication**', then you can see the *PageBuilder*. The latest version is required **1.3.6** **later**.[^1]
[^1]:Since AutoConnect v1.1.3, PageBuilder v1.3.6 later is required.
To install the PageBuilder library into your Arduino IDE, you can use the *Library Manager*. Select the board of ESP8266 series in the Arduino IDE, open the library manager and search keyword '**PageBuilder**' with the topic '**Communication**', then you can see the *PageBuilder*. The latest version is required **1.4.2** **later**.[^1]
[^1]:Since AutoConnect v1.2.0, PageBuilder v1.4.2 later is required.
<img src="images/lm.png" width="640"/>

@ -123,4 +123,4 @@ The AutoConnect menu can contain your sketch's web pages as extra items as a cus
<div style="float:left;width:auto;height:420px;"><img style="width:auto;height:420px;" src="images/fsbmenu.png"></div>
<img style="margin-left:70px;width:auto;height:420px;" src="images/fsbmenu_expand.png">
You can improve your sketches by extending the AutoConnect menu by adding the legacy web pages according to the procedure described in section [*Advanced Usage*](advancedusage.md#casts-the-html-pages-to-be-add-on-into-the-menu).
AutoConnect allows capturing the extra pages handled with ESP8266WebServer or WebServer's legacy into the AutoConnect menu. See Section [*Advanced Usage*](advancedusage.md#capture-the-legacy-web-pages-as-items-into-the-menu) for detailed instructions on how to add the extra pages into its menu.

@ -1,21 +1,22 @@
## What menus can be made using AutoConnect
## The feature of menu attaching using AutoConnect
AutoConnect generates a menu dynamically depending on the instantiated [AutoConnectAux](acintro.md#how-it-works) at the Sketch executing time. Usually, it is a collection of [AutoConnectElement](acelements.md). In addition to this, you can generate a menu from only AutoConnectAux, without AutoConnectElements.<br>In other words, you can easily create a built-in menu featuring the WiFi connection facility embedding the legacy web pages.
In this section, it presents numerous ways to customize the AutoConnect menu with your Sketch. AutoConnect dynamically materializes menu items at the Sketch run time with joined [AutoConnectAux](acintro.md#how-it-works) as a sourced configuration. Typically, it has [AutoConnectElements](acintro.md#how-it-works) for page rendering in its composition but can configure a Web page as a menu item without having AutoConnectElements. In other words, the AutoConnect Menu component allows you to easily embed a navigation menu with WiFi connection expansion in your Sketch, which has legacy pages for ESP8266WebServer or WebServer of ESP32.
## Basic mechanism of menu generation
## The basic mechanism for menu generation
the Sketch can display the [AutoConnect menu](menu.md) by following three patterns depending on AutoConnect-API usage.
Sketch can equip the [AutoConnect menu](menu.md) by using three patterns according to the appropriate usage of the [AutoConnect API](api.md).
<i class="fa fa-desktop"></i>&ensp;**Basic menu**
: It is the most basic menu for only connecting WiFi. Sketch can automatically display this menu with the basic call sequence of the AutoConnect API which invokes [AutoConnect::begin](api.md#begin) and [AutoConnect::handleClient](api.md#handleclient).
: It is the most basic menu for a WiFi connection only. Sketch can automatically display it using the typical calling sequence of the [AutoConnect API](api.md) with [AutoConnect::begin](api.md#begin) and [AutoConnect::handleClient](api.md#handleClient).
<i class="fa fa-desktop"></i>&ensp;**Extra menu with custom Web pages which is consisted by [AutoConnectElements](acelements.md)**
: It is an extended menu that appears when the Sketch consists of the custom Web pages with [AutoConnectAux](acintro.md#how-it-works) and AutoConnectElements. Refer to section [*Custom Web pages section*](acintro.md#custom-web-pages-in-autoconnect-menu).
<i class="fa fa-desktop"></i>&ensp;**Extra menu which contains legacy pages**
: It is for the legacy sketches using the **on** handler of ESP8266WebServer/WebServer(for ESP32) class natively and looks the same as the extra menu as above.
: It provides an item for including a legacy page in the AutoConnect menu that natively uses the page request handler attached by the [ESP8266WebServer::on](https://github.com/esp8266/Arduino/tree/master/libraries/ESP8266WebServer#client-request-handlers) function. (Similarly, WebServer::on for ESP32)
The mechanism to generate the AutoConnect menu is simple. It will insert the item as `<li>` tag generated from the [**title**](apiaux.md#autoconnectaux) and [**uri**](apiaux.md#autoconnectaux) member variable of the AutoConnectAux object to the menu list of AutoConnect's built-in HTML. Therefore, the legacy sketches can invoke the web pages from the AutoConnect menu with just declaration the title and URI to AutoConnectAux.
The mechanism by which AutoConnect dynamically generates the menu is simple. The member variables [**title**](apiaux.md#title) and [**uri**](apiaux.md#uri) of AutoConnectAux will be transferred into **&lt;li&gt;** HTML tag as they are. Then all **&lt;li&gt;** elements are included in the form that makes up the menu.
Therefore, the Sketch can register the legacy web pages to the menu by simply declaring the [title](apiaux.md#autoconnectaux) and [URI](apiaux.md#autoconnectaux) with AutoConnectAux and binding it to AutoConnect.
## Place the item for the legacy sketches on the menu
@ -25,16 +26,23 @@ The AutoConnect library package contains an example sketch for ESP8266WebServer
<span style="display:block;margin-left:auto;margin-right:auto;width:282px;height:492px;border:1px solid lightgrey;"><img data-gifffer="images/aux_fsbrowser.gif" data-gifffer-height="490" data-gifffer-width="280" /></span>
### <i class="fa fa-edit"></i> Slightly changes to adapt FSBrowser to AutoConnect menu
The changes I made to adapt the FSBrowser to the AutoConnect menu are slight as follows:
1. Add AutoConnect declaration.
2. Add the menu item named "**Edit**" and "**List**" of AutoConnectAux as each page.
3. Replace the instance of ESP8266WebServer to AutoConnect.
4. Change the menu title to FSBrowser using [AutoConnectConfig::title](apiconfig.md#title).
5. Join the legacy pages to AutoConnect declared at step #1 using [AutoConnect::join](api.md#join).<br>Joining multiple at one time with the [list initialization](https://en.cppreference.com/w/cpp/language/list_initialization) for [std::vector](https://ja.cppreference.com/w/cpp/container/vector/vector).
6. According to the basic procedure of AutoConnect.<br>Establish a connection with [AutoConnect::begin](api.md#begin) and perform [AutoConnect::handleClient](api.md#handleclient) in **loop()**.
2. Add AutoConnectConfig declaration to replace the menu title to `FSBRowser`.
3. Set the menu title using [AutoConnectConfig::title](apiconfig.md#title).
4. Replace the destination of the not found handler (404 handler) from ESP8266WebServer to AutoConnect. [^1]<sup><sub>**IMPORTANT**</sub></sup>
5. Add AutoConnectAux using [AutoConnect::append](api.md#append) and combine an item for **Edit**.
6. Add AutoConnectAux using [AutoConnect::append](api.md#append) and combine an item for **List**.
7. Establish a WiFi connection using [AutoConnect::begin](api.md#begin) and execute [AutoConnect::handleClient](api.md#handleclient) in the **loop**, as in the case of handling the basic menu.
[^1]: Missing this step, AutoConnect cannot handle the menu. Refs: [404 handler](advancedusage.md#404-handler)
### <i class="fa fa-code"></i> FSBrowser with embedded AutoConnect
<i class="fa fa-code"></i>&ensp;**Modification for FSBrowser** <small>(a part of sketch code)</small>
Modification for FSBrowser as follows: <small>(Excerpt of the sketch code)</small>
<div style="overflow:auto"><img style="width:auto;max-width:none;height:840px" src="images/fsbrowser_ba.svg" /></div>

@ -62,7 +62,7 @@ void loop() {
[^1]:For ESP32, change the following items:
- Change the include directives appropriately for the ESP32 environment.
- Change ESP8266WebServer to Web.
- Change ESP8266WebServer to WebServer.
!!! faq "How LED ticking during updates"
AutoConnectOTA applies LED ticking during updates automatically. The destination LED port and ticker drive depends on [AutoConnectConfig::tickerPort](apiconfig.md#tickerport) and [AutoConnectConfig::tickerOn](apiconfig.md#tickeron) specifying.
@ -132,10 +132,9 @@ When the compilation is complete, a binary sketch will save with the extension `
### <i class="fa fa-edit"></i> OTA updates w/browser without using AutoConnectOTA
The legacy OTA method based on ESP8266HTTPUpdateServer without AutoConnectOTA is still valid.
To embed the ESP8266HTTPUpdateServer class with AutoConnect into your sketch, basically follow these steps:
The legacy OTA method based on ESP8266HTTPUpdateServer without AutoConnectOTA is still valid. To embed the ESP8266HTTPUpdateServer class with AutoConnect into your sketch, basically follow these steps:
1. Include `ESP8266HTTPUpdateServer.h`, also `WiFiClient.h`, in addition to the usual directives as `ESP8266WebServer.h` and `AutoConnect.h`.
1. Include `ESP8266HTTPUpdateServer.h`, also `WiFiClient.h`, in addition to the usual directives as `ESP8266WebServer.h` and `AutoConnect.h`.[^2]
2. Declare an ESP8266WebServer object. (In ESP32, as WebServer)
3. Declare an ESP8266HTTPUpdateServer object.
4. Declare an AutoConnect object with an ESP8266WebServer object as an argument.
@ -150,6 +149,8 @@ To embed the ESP8266HTTPUpdateServer class with AutoConnect into your sketch, ba
4. Invokes [AutoConnect::begin](api.md#begin) function.
10. Invokes [AutoConnect::handleClient](api.md#handleclient) function in the `loop()`.
[^2]: The AutoConnect library provides an implementation of the **HTTPUpdateServer** class that ported from ESP8266HTTPUpdateServer class for ESP32 intention. It is contained the **WebUpdate** under the examples folder.
```cpp
#include <ESP8266WiFi.h>
#include <ESP8266WebServer.h>

@ -4,8 +4,6 @@ AutoConnect provides **two type platforms** for updating the binary sketch in th
[**The update behavior using a web browser**](otabrowser.md) as the client that supplies the binary sketch file for update follows the scenario assumed by the ESP8266 Arduino core. Therefore, the user sketch must meet the requirements described in the ESP8266 Arduino Core documentation, but you can easily embed the OTA update feature that able to operate via the web browser by AutoConnect menu. All you need to do is that specify [AutoConnectConfig](apiconfig.md#ota).
[^1]: The AutoConnect library provides an implementation of the **HTTPUpdateServer** class that ported from ESP8266HTTPUpdateServer class for ESP32 intention. It is contained the **WebUpdate** under the examples folder.
<img src="images/webupdatemodel.png" width="420" />
!!! caution "It is for the only the same network"

@ -2,8 +2,8 @@
* AutoConnect class implementation.
* @file AutoConnect.cpp
* @author hieromon@gmail.com
* @version 1.1.5
* @date 2020-04-15
* @version 1.2.0
* @date 2020-04-24
* @copyright MIT license.
*/
@ -79,10 +79,19 @@ bool AutoConnect::begin(const char* ssid, const char* passphrase, unsigned long
// Overwrite for the current timeout value.
_connectTimeout = timeout;
// Start WiFi connection with station mode.
WiFi.softAPdisconnect(true);
WiFi.mode(WIFI_STA);
delay(100);
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())
@ -113,9 +122,7 @@ bool AutoConnect::begin(const char* ssid, const char* passphrase, unsigned long
if (_apConfig.principle == AC_PRINCIPLE_RSSI && (ssid == nullptr && passphrase == nullptr)) {
// AC_PRINCIPLE_RSSI is available when SSID and password are not provided.
// Find the strongest signal from the broadcast among the saved credentials.
if ((cs = _loadAvailCredential(nullptr, AC_PRINCIPLE_RSSI, false))) {
memcpy(current.ssid, _credential.ssid, sizeof(station_config_t::ssid));
memcpy(current.password, _credential.password, sizeof(station_config_t::password));
if ((cs = _loadCurrentCredential(reinterpret_cast<char*>(current.ssid), reinterpret_cast<char*>(current.password), AC_PRINCIPLE_RSSI, false))) {
c_ssid = reinterpret_cast<const char*>(current.ssid);
c_password = reinterpret_cast<const char*>(current.password);
AC_DBG("Adopted:%.32s\n", c_ssid);
@ -143,14 +150,10 @@ bool AutoConnect::begin(const char* ssid, const char* passphrase, unsigned long
// Reconnect with a valid credential as the autoReconnect option is enabled.
if (!cs && _apConfig.autoReconnect && (ssid == nullptr && passphrase == nullptr)) {
// Load a valid credential.
if (_loadAvailCredential(nullptr, _apConfig.principle, true)) {
char ssid_c[sizeof(station_config_t::ssid) + sizeof('\0')];
char password_c[sizeof(station_config_t::password) + sizeof('\0')];
if (_loadCurrentCredential(ssid_c, password_c, _apConfig.principle, true)) {
// Try to reconnect with a stored credential.
char ssid_c[sizeof(station_config_t::ssid) + sizeof('\0')];
char password_c[sizeof(station_config_t::password) + sizeof('\0')];
*ssid_c = '\0';
strncat(ssid_c, reinterpret_cast<const char*>(_credential.ssid), sizeof(ssid_c) - sizeof('\0'));
*password_c = '\0';
strncat(password_c, reinterpret_cast<const char*>(_credential.password), sizeof(password_c) - sizeof('\0'));
AC_DBG("autoReconnect loaded:%s(%s)\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));
@ -167,7 +170,7 @@ bool AutoConnect::begin(const char* ssid, const char* passphrase, unsigned long
if (_update)
_update->enable();
}
// Rushing into the portal.
// Rushing into the portal.
else {
// The captive portal is effective at the autoRise is valid only.
if (_apConfig.autoRise) {
@ -263,11 +266,6 @@ bool AutoConnect::begin(const char* ssid, const char* passphrase, unsigned long
if (!_responsePage)
_startWebServer();
// Stop ticker
if (cs)
if (_ticker)
_ticker->stop();
return cs;
}
@ -378,21 +376,10 @@ bool AutoConnect::_getConfigSTA(station_config_t* config) {
return rc;
}
/**
* 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;
}
/**
* Stops AutoConnect captive portal service.
*/
void AutoConnect::end(void) {
_responsePage.reset();
_currentPageElement.reset();
_ticker.reset();
_update.reset();
@ -404,90 +391,18 @@ void AutoConnect::end(void) {
}
/**
* 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;
}
/**
* 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());
}
/**
* 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.
* 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.
*/
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");
}
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
}
/**
@ -530,7 +445,7 @@ void AutoConnect::handleRequest(void) {
strncat(password_c, reinterpret_cast<const char*>(_credential.password), sizeof(password_c) - 1);
AC_DBG("Attempt:%s Ch(%d)\n", ssid_c, (int)ch);
WiFi.begin(ssid_c, password_c, ch);
if (_waitForConnect(_connectTimeout) == WL_CONNECTED) {
if ((_rsConnect = _waitForConnect(_connectTimeout)) == WL_CONNECTED) {
if (WiFi.BSSID() != NULL) {
memcpy(_credential.bssid, WiFi.BSSID(), sizeof(station_config_t::bssid));
_currentHostIP = WiFi.localIP();
@ -562,12 +477,14 @@ void AutoConnect::handleRequest(void) {
else {
_currentHostIP = WiFi.softAPIP();
_redirectURI = String(F(AUTOCONNECT_URI_FAIL));
_rsConnect = WiFi.status();
_disconnectWiFi(false);
while (WiFi.status() != WL_IDLE_STATUS && WiFi.status() != WL_DISCONNECTED) {
delay(1);
yield();
}
// Restore the ticker
if (_ticker && WiFi.getMode() == WIFI_AP_STA)
_ticker->start(AUTOCONNECT_FLICKER_PERIODAP, (uint8_t)AUTOCONNECT_FLICKER_WIDTHAP);
}
_rfConnect = false;
}
@ -609,6 +526,7 @@ void AutoConnect::handleRequest(void) {
if (!_ota) {
_ota.reset(new AutoConnectOTA());
_ota->attach(*this);
_ota->authentication(_apConfig.auth);
_ota->setTicker(_apConfig.tickerPort, _apConfig.tickerOn);
}
}
@ -624,6 +542,133 @@ void AutoConnect::handleRequest(void) {
// Reflect the menu display specifier from AutoConnectConfig to AutoConnectOTA page
_ota->menu(_apConfig.menuItems & AC_MENUITEM_UPDATE);
}
// Post-process for ticker
if (_ticker) {
if (WiFi.status() == WL_CONNECTED)
_ticker->stop();
}
}
/**
* 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());
}
/**
@ -649,6 +694,14 @@ bool AutoConnect::on(const String& uri, const AuxHandlerFunctionT handler, AutoC
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.
@ -665,6 +718,25 @@ void AutoConnect::onNotFound(WebServerClass::THandlerFunction fn) {
_notFoundHandler = 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.
@ -746,6 +818,47 @@ bool AutoConnect::_loadAvailCredential(const char* ssid, const AC_PRINCIPLE_t pr
return false;
}
/**
* 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");
}
}
/**
* Disconnect from the AP and stop the AutoConnect portal.
* Stops DNS server and flush tcp sending.
@ -991,7 +1104,7 @@ String AutoConnect::_invokeResult(PageArgument& args) {
bool AutoConnect::_classifyHandle(HTTPMethod method, String uri) {
AC_UNUSED(method);
_portalAccessPeriod = millis();
AC_DBG("Host:%s,URI:%s", _webServer->hostHeader().c_str(), uri.c_str());
AC_DBG("Host:%s,%s", _webServer->hostHeader().c_str(), uri.c_str());
// Here, classify requested uri
if (uri == _uri) {
@ -1115,6 +1228,11 @@ wl_status_t AutoConnect::_waitForConnect(unsigned long timeout) {
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);
}
return wifiStatus;
}
@ -1185,6 +1303,16 @@ void AutoConnect::_disconnectWiFi(bool wifiOff) {
delay(1);
yield();
}
// Restart the ticker to indicate that ESP module into the disconnected state.
if (_ticker) {
uint32_t tc = AUTOCONNECT_FLICKER_PERIODDC;
uint8_t tw = AUTOCONNECT_FLICKER_WIDTHDC;
if (WiFi.getMode() == WIFI_AP_STA) {
tc = AUTOCONNECT_FLICKER_PERIODAP;
tw = AUTOCONNECT_FLICKER_WIDTHAP;
}
_ticker->start(tc, tw);
}
}
/**

@ -2,8 +2,8 @@
* Declaration of AutoConnect class and accompanying AutoConnectConfig class.
* @file AutoConnect.h
* @author hieromon@gmail.com
* @version 1.1.5
* @date 2020-04-01
* @version 1.2.0
* @date 2020-04-24
* @copyright MIT license.
*/
@ -33,6 +33,7 @@ using WebServerClass = WebServer;
#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"
@ -43,42 +44,6 @@ class AutoConnectOTA; // Reference to avoid circular
#include "AutoConnectUpdate.h"
class AutoConnectUpdate; // Reference to avoid circular
/**< A type to save established credential at WiFi.begin automatically. */
typedef enum AC_SAVECREDENTIAL {
AC_SAVECREDENTIAL_NEVER,
AC_SAVECREDENTIAL_AUTO
} AC_SAVECREDENTIAL_t;
/**< URI that can be specified to AutoConnectConfig::bootUri. */
typedef enum AC_ONBOOTURI {
AC_ONBOOTURI_ROOT,
AC_ONBOOTURI_HOME
} AC_ONBOOTURI_t;
/** WiFi connection principle, it specifies the order of WiFi connecting with saved credentials. */
typedef enum AC_PRINCIPLE {
AC_PRINCIPLE_RECENT,
AC_PRINCIPLE_RSSI
} AC_PRINCIPLE_t;
/**< An enumerated type of the designated menu items. */
typedef enum AC_MENUITEM {
AC_MENUITEM_NONE = 0x0000,
AC_MENUITEM_CONFIGNEW = 0x0001,
AC_MENUITEM_OPENSSIDS = 0x0002,
AC_MENUITEM_DISCONNECT = 0x0004,
AC_MENUITEM_RESET = 0x0008,
AC_MENUITEM_HOME = 0x0010,
AC_MENUITEM_UPDATE = 0x0020,
AC_MENUITEM_DEVINFO = 0x0040
} AC_MENUITEM_t;
/**< Specifier for using built-in OTA */
typedef enum AC_OTA {
AC_OTA_EXTRA,
AC_OTA_BUILTIN
} AC_OTA_t;
class AutoConnectConfig {
public:
/**
@ -90,8 +55,8 @@ class AutoConnectConfig {
apip(AUTOCONNECT_AP_IP),
gateway(AUTOCONNECT_AP_GW),
netmask(AUTOCONNECT_AP_NM),
apid(String(AUTOCONNECT_APID)),
psk(String(AUTOCONNECT_PSK)),
apid(String(F(AUTOCONNECT_APID))),
psk(String(F(AUTOCONNECT_PSK))),
channel(AUTOCONNECT_AP_CH),
hidden(0),
minRSSI(AUTOCONNECT_MIN_RSSI),
@ -105,12 +70,17 @@ class AutoConnectConfig {
autoReconnect(false),
immediateStart(false),
retainPortal(false),
preserveAPMode(false),
portalTimeout(AUTOCONNECT_CAPTIVEPORTAL_TIMEOUT),
menuItems(AC_MENUITEM_CONFIGNEW | AC_MENUITEM_OPENSSIDS | AC_MENUITEM_DISCONNECT | AC_MENUITEM_RESET | AC_MENUITEM_UPDATE | AC_MENUITEM_HOME),
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),
@ -141,12 +111,17 @@ class AutoConnectConfig {
autoReconnect(false),
immediateStart(false),
retainPortal(false),
preserveAPMode(false),
portalTimeout(portalTimeout),
menuItems(AC_MENUITEM_CONFIGNEW | AC_MENUITEM_OPENSSIDS | AC_MENUITEM_DISCONNECT | AC_MENUITEM_RESET | AC_MENUITEM_UPDATE | AC_MENUITEM_HOME),
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),
@ -177,12 +152,17 @@ class AutoConnectConfig {
autoReconnect = o.autoReconnect;
immediateStart = o.immediateStart;
retainPortal = o.retainPortal;
preserveAPMode = o.preserveAPMode;
portalTimeout = o.portalTimeout;
menuItems = o.menuItems;
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;
@ -213,12 +193,17 @@ class AutoConnectConfig {
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 portalTimeout; /**< Timeout value for stay in the captive portal */
uint16_t menuItems; /**< A compound value of the menu items to be attached */
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 */
@ -235,23 +220,28 @@ class AutoConnect {
public:
AutoConnect();
AutoConnect(WebServerClass& webServer);
~AutoConnect();
AutoConnectAux* aux(const String& uri) const;
bool config(AutoConnectConfig& Config);
bool config(const char* ap, const char* password = nullptr);
void home(const String& uri);
virtual ~AutoConnect();
bool begin(void);
bool begin(const char* ssid, const char* passphrase = nullptr, unsigned long timeout = AUTOCONNECT_TIMEOUT);
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);
String where(void) const { return _auxUri; }
inline void enableMenu(const uint16_t items) { _apConfig.menuItems |= items; }
inline void disableMenu(const uint16_t items) { _apConfig.menuItems &= (0xffff ^ items); }
/** For AutoConnectAux described in JSON */
#ifdef AUTOCONNECT_USE_JSON
@ -261,8 +251,10 @@ class AutoConnect {
bool load(Stream& aux);
#endif // !AUTOCONNECT_USE_JSON
typedef std::function<bool(IPAddress)> DetectExit_ft;
typedef std::function<bool(IPAddress&)> DetectExit_ft;
typedef std::function<void(IPAddress&)> ConnectExit_ft;
void onDetect(DetectExit_ft fn);
void onConnect(ConnectExit_ft fn);
void onNotFound(WebServerClass::THandlerFunction fn);
protected:
@ -270,17 +262,19 @@ class AutoConnect {
AC_RECONNECT_SET,
AC_RECONNECT_RESET
} AC_STARECONNECT_t;
void _authentication(bool allow);
bool _config(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);
void _startWebServer(void);
void _startDNSServer(void);
void _handleNotFound(void);
bool _loadAvailCredential(const char* ssid, const AC_PRINCIPLE_t principle = AC_PRINCIPLE_RECENT, const bool excludeCurrent = false);
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
@ -310,6 +304,7 @@ class AutoConnect {
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;
WebServerClass::THandlerFunction _notFoundHandler;
size_t _freeHeapSize;
@ -363,13 +358,13 @@ class AutoConnect {
/** 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 _CSS_LUXBAR[] PROGMEM;
static const char _ELM_HTML_HEAD[] PROGMEM;
static const char _ELM_MENU_PRE[] PROGMEM;
static const char _ELM_MENU_AUX[] PROGMEM;
@ -392,41 +387,41 @@ class AutoConnect {
/** Token handlers for PageBuilder */
String _token_CSS_BASE(PageArgument& args);
String _token_CSS_UL(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_TABLE(PageArgument& args);
String _token_CSS_SPINNER(PageArgument& args);
String _token_CSS_LUXBAR(PageArgument& args);
String _token_HEAD(PageArgument& args);
String _token_MENU_PRE(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_ESTAB_SSID(PageArgument& args);
String _token_WIFI_MODE(PageArgument& args);
String _token_WIFI_STATUS(PageArgument& args);
String _token_STATION_STATUS(PageArgument& args);
String _token_LOCAL_IP(PageArgument& args);
String _token_SOFTAP_IP(PageArgument& args);
String _token_GATEWAY(PageArgument& args);
String _token_NETMASK(PageArgument& args);
String _token_MENU_PRE(PageArgument& args);
String _token_AP_MAC(PageArgument& args);
String _token_STA_MAC(PageArgument& args);
String _token_BOOTURI(PageArgument& args);
String _token_CHANNEL(PageArgument& args);
String _token_DBM(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_CHIP_ID(PageArgument& args);
String _token_FREE_HEAP(PageArgument& args);
String _token_LIST_SSID(PageArgument& args);
String _token_SSID_COUNT(PageArgument& args);
String _token_GATEWAY(PageArgument& args);
String _token_HEAD(PageArgument& args);
String _token_HIDDEN_COUNT(PageArgument& args);
String _token_CONFIG_STAIP(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_BOOTURI(PageArgument& args);
String _token_CURRENT_SSID(PageArgument& args);
String _token_WIFI_MODE(PageArgument& args);
String _token_WIFI_STATUS(PageArgument& args);
private:
static const String _emptyString; /**< An empty string allocation **/

@ -1,9 +1,9 @@
/**
* Implementation of AutoConnectAux class.
* @file AutoConnectAuxBasisImpl.h
* @file AutoConnectAux.cpp
* @author hieromon@gmail.com
* @version 1.1.1
* @date 2019-10-17
* @version 1.2.0
* @date 2020-04-17
* @copyright MIT license.
*/
#include <algorithm>
@ -333,7 +333,7 @@ void AutoConnectAux::upload(const String& requestUri, const HTTPUpload& upload)
* AutoConnectAux is collected in the chain list and each object is
* chained by the "_next". AutoConnect follows the "_next" to manage
* auxiliary pages. The _concat function concatenates subsequent
* AutoConnectAuxs.
* AutoConnectAuxes.
* @param aux A reference of AutoConnectAux.
*/
void AutoConnectAux::_concat(AutoConnectAux& aux) {
@ -513,6 +513,13 @@ PageElement* AutoConnectAux::_setupPage(const String& uri) {
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.authScope & AC_AUTHSCOPE_PARTIAL) && (_httpAuth != AC_AUTH_NONE));
mother->_authentication(auth);
}
}
return elm;
@ -612,7 +619,7 @@ bool AutoConnect::load(Stream& aux) {
/**
* Load AutoConnectAux page from JSON object.
* @param aux A JsonVariant object that stores each element of AutoConnectAuxl.
* @param aux A JsonVariant object that stores each element of AutoConnectAux.
* @return true Successfully loaded.
*/
bool AutoConnect::_load(JsonVariant& auxJson) {
@ -756,6 +763,13 @@ bool AutoConnectAux::_load(JsonObject& jb) {
_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;
@ -882,7 +896,7 @@ size_t AutoConnectAux::saveElement(Stream& out, std::vector<String> const& names
// Calculate JSON buffer size
if (amount == 0) {
bufferSize += JSON_OBJECT_SIZE(4);
bufferSize += sizeof(AUTOCONNECT_JSON_KEY_TITLE) + _title.length() + 1 + sizeof(AUTOCONNECT_JSON_KEY_URI) + _uriStr.length() + 1 + sizeof(AUTOCONNECT_JSON_KEY_MENU) + sizeof(AUTOCONNECT_JSON_KEY_ELEMENT);
bufferSize += 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
@ -919,6 +933,10 @@ size_t AutoConnectAux::saveElement(Stream& out, std::vector<String> const& names
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();

@ -1,9 +1,9 @@
/**
* Declaration of AutoConnectAux basic class.
* @file AutoConnectAuxBasis.h
* @file AutoConnectAux.h
* @author hieromon@gmail.com
* @version 1.1.1
* @date 2019-10-17
* @version 1.2.0
* @date 2029-04-17
* @copyright MIT license.
*/
@ -20,6 +20,7 @@
#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
@ -53,6 +54,7 @@ class AutoConnectAux : public PageBuilder {
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);
@ -119,7 +121,7 @@ class AutoConnectAux : public PageBuilder {
* 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 souce name to load.
* @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>
@ -146,6 +148,8 @@ class AutoConnectAux : public PageBuilder {
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 */
@ -160,4 +164,4 @@ class AutoConnectAux : public PageBuilder {
friend class AutoConnect;
};
#endif // _AUTOCONNECTAUX_H_
#endif // !_AUTOCONNECTAUX_H_

@ -26,7 +26,7 @@
* ssid: SSID string with null termination.
* password : Password string with null termination.
* bssid : BSSID 6 bytes.
* d : DHCP is in available. 0:DCHP 1:Static IP
* 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)
@ -353,7 +353,7 @@ void AutoConnectCredential::_retrieveEntry(station_config_t* config) {
* ssid: SSID string with null termination.
* password : Password string with null termination.
* bssid : BSSID 6 bytes.
* d : DHCP is in available. 0:DCHP 1:Static IP
* 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)
@ -576,7 +576,7 @@ size_t AutoConnectCredential::_commit(void) {
}
/**
* Actualy delete the credential entry for the specified SSID from Preferences.
* 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.

@ -2,8 +2,8 @@
* Declaration of AutoConnectCredential class.
* @file AutoConnectCredential.h
* @author hieromon@gmail.com
* @version 1.1.0
* @date 2019-10-07
* @version 1.2.0
* @date 2020-04-22
* @copyright MIT license.
*/
@ -79,6 +79,7 @@ class AutoConnectCredentialBase {
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;

@ -2,8 +2,8 @@
* Predefined AutoConnect configuration parameters.
* @file AutoConnectDefs.h
* @author hieromon@gmail.com
* @version 1.1.5
* @date 2020-03-16
* @version 1.2.0
* @date 2020-05-29
* @copyright MIT license.
*/
@ -18,11 +18,11 @@
#define AC_DEBUG_PORT Serial
#endif // !AC_DEBUG_PORT
#ifdef AC_DEBUG
#define AC_DBG_DUMB(...) do {AC_DEBUG_PORT.printf( __VA_ARGS__ );} while (0)
#define AC_DBG(...) do {AC_DEBUG_PORT.print("[AC] "); AC_DEBUG_PORT.printf( __VA_ARGS__ );} while (0)
#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(...)
#define AC_DBG_DUMB(...)
#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.
@ -34,6 +34,19 @@
#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
// Predefined parameters
// SSID that Captive portal started.
#ifndef AUTOCONNECT_APID
@ -113,9 +126,9 @@
#define AUTOCONNECT_STARTUPTIME (AUTOCONNECT_TIMEOUT/1000)
#endif // !AUTOCONNECT_STARTUPTIME
// Response wait time until requesting a result of connection attempt, uint:[s] as String
// Response wait time until requesting a result of connection attempt, uint:[ms]
#ifndef AUTOCONNECT_RESPONSE_WAITTIME
#define AUTOCONNECT_RESPONSE_WAITTIME "2"
#define AUTOCONNECT_RESPONSE_WAITTIME 2000
#endif // !AUTOCONNECT_RESPONSE_WAITTIME
// Default HTTP port
@ -160,14 +173,14 @@
// Flicker pulse width during AP operation (8bit resolution)
#ifndef AUTOCONNECT_FLICKER_WIDTHAP
#define AUTOCONNECT_FLICKER_WIDTHAP 96
#endif // !AUTOCONNECT_FLICKER_WIDTHAPSTA
#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(BUILDIN_LED) || defined(LED_BUILTIN)
#if defined(BUILTIN_LED) || defined(LED_BUILTIN)
#define AUTOCONNECT_TICKER_PORT LED_BUILTIN
#else // Native pin for the arduino
#define AUTOCONNECT_TICKER_PORT 2
@ -234,6 +247,11 @@
#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
// Explicitly avoiding unused warning with token handler of PageBuilder
#define AC_UNUSED(expr) do { (void)(expr); } while (0)

@ -1,6 +1,6 @@
/**
* Alias declarations for an accessible the AutoConnectElement class.
* @file AutoConnectAux.h
* @file AutoConnectElement.h
* @author hieromon@gmail.com
* @version 0.9.8
* @date 2019-03-11

@ -1,9 +1,9 @@
/**
* Implementation of AutoConnectElementBasis classes.
* @file AutoConnectElementImpl.h
* @file AutoConnectElementBasisImpl.h
* @author hieromon@gmail.com
* @version 0.9.11
* @date 2019-06-25
* @version 1.2.0
* @date 2029-05-29
* @copyright MIT license.
*/
@ -19,6 +19,14 @@
#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 accoring by the post attribute.
* @param s An original string
@ -111,7 +119,7 @@ bool AutoConnectFileBasis::attach(const ACFile_t store) {
// Classify a handler type and create the corresponding handler
switch (store) {
case AC_File_FS:
handlerFS = new AutoConnectUploadFS(SPIFFS);
handlerFS = new AutoConnectUploadFS(AutoConnectFS::FLASHFS);
_upload.reset(reinterpret_cast<AutoConnectUploadHandler*>(handlerFS));
break;
case AC_File_SD:

@ -2,8 +2,8 @@
* Declaration of AutoConnectElement extended classes using JSON.
* @file AutoConnectElementJson.h
* @author hieromon@gmail.com
* @version 1.0.0
* @date 2019-09-03
* @version 1.2.0
* @date 2020-04-17
* @copyright MIT license.
*/
@ -15,6 +15,7 @@
#define AUTOCONNECT_JSON_KEY_ACTION "action"
#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"
@ -44,8 +45,10 @@
#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"

@ -1,6 +1,6 @@
/**
* Implementation of AutoConnectElementJson classes.
* @file AutoConnectElementImpl.h
* @file AutoConnectElementJsonImpl.h
* @author hieromon@gmail.com
* @version 1.0.0
* @date 2019-09-03

@ -2,7 +2,7 @@
* AutoConnect proper menu label constant definition.
* @file AutoConnectLabels.h
* @author hieromon@gmail.com
* @version 1.1.6
* @version 1.2.0
* @date 2020-04-17
* @copyright MIT license.
*/
@ -247,6 +247,11 @@
#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"

@ -26,6 +26,15 @@ AutoConnectOTA::~AutoConnectOTA() {
_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

@ -36,6 +36,7 @@ class AutoConnectOTA : public AutoConnectUploadHandler {
AutoConnectOTA() : _status(OTA_IDLE), _tickerPort(-1), _tickerOn(LOW) {}
~AutoConnectOTA();
void attach(AutoConnect& portal);
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. */
AC_OTAStatus_t status(void) const { return _status; } /**< Return current error status of the Update class */

@ -1,8 +1,8 @@
/**
* AutoConnect portal site web page implementation.
* @file AutoConnectPage.h
* @file AutoConnectPage.cpp
* @author hieromon@gmail.com
* @version 1.1.7
* @version 1.2.0
* @date 2020-04-19
* @copyright MIT license.
*/
@ -761,7 +761,7 @@ const char AutoConnect::_PAGE_CONNECTING[] PROGMEM = {
"</div>"
"</div>"
"<script type=\"text/javascript\">"
"setTimeout(\"link()\"," AUTOCONNECT_RESPONSE_WAITTIME ");"
"setTimeout(\"link()\"," AUTOCONNECT_STRING_DEPLOY(AUTOCONNECT_RESPONSE_WAITTIME) ");"
"function link(){location.href='" AUTOCONNECT_URI_RESULT "';}"
"</script>"
"</body>"
@ -912,11 +912,6 @@ String AutoConnect::_token_CSS_BASE(PageArgument& args) {
return String(FPSTR(_CSS_BASE));
}
String AutoConnect::_token_CSS_UL(PageArgument& args) {
AC_UNUSED(args);
return String(FPSTR(_CSS_UL));
}
String AutoConnect::_token_CSS_ICON_LOCK(PageArgument& args) {
AC_UNUSED(args);
return String(FPSTR(_CSS_ICON_LOCK));
@ -932,9 +927,9 @@ String AutoConnect::_token_CSS_INPUT_TEXT(PageArgument& args) {
return String(FPSTR(_CSS_INPUT_TEXT));
}
String AutoConnect::_token_CSS_TABLE(PageArgument& args) {
String AutoConnect::_token_CSS_LUXBAR(PageArgument& args) {
AC_UNUSED(args);
return String(FPSTR(_CSS_TABLE));
return String(FPSTR(_CSS_LUXBAR));
}
String AutoConnect::_token_CSS_SPINNER(PageArgument& args) {
@ -942,9 +937,21 @@ String AutoConnect::_token_CSS_SPINNER(PageArgument& args) {
return String(FPSTR(_CSS_SPINNER));
}
String AutoConnect::_token_HEAD(PageArgument& args) {
String AutoConnect::_token_CSS_TABLE(PageArgument& args) {
AC_UNUSED(args);
return String(FPSTR(_ELM_HTML_HEAD));
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) {
@ -960,13 +967,6 @@ String AutoConnect::_token_MENU_PRE(PageArgument& args) {
return currentMenu;
}
String AutoConnect::_token_MENU_AUX(PageArgument& args) {
String menuItem = String("");
if (_aux)
menuItem = _aux->_injectMenu(args);
return menuItem;
}
String AutoConnect::_token_MENU_POST(PageArgument& args) {
AC_UNUSED(args);
String postMenu = FPSTR(_ELM_MENU_POST);
@ -976,184 +976,115 @@ String AutoConnect::_token_MENU_POST(PageArgument& args) {
return postMenu;
}
String AutoConnect::_token_CSS_LUXBAR(PageArgument& args) {
String AutoConnect::_token_AP_MAC(PageArgument& args) {
AC_UNUSED(args);
return String(FPSTR(_CSS_LUXBAR));
uint8_t macAddress[6];
WiFi.softAPmacAddress(macAddress);
return AutoConnect::_toMACAddressString(macAddress);
}
String AutoConnect::_token_ESTAB_SSID(PageArgument& args) {
String AutoConnect::_token_BOOTURI(PageArgument& args) {
AC_UNUSED(args);
return (WiFi.status() == WL_CONNECTED ? WiFi.SSID() : String(F("N/A")));
return _getBootUri();
}
String AutoConnect::_token_WIFI_MODE(PageArgument& args) {
String AutoConnect::_token_CHANNEL(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("experiment");
}
return String(FPSTR(wifiMode));
return String(WiFi.channel());
}
String AutoConnect::_token_WIFI_STATUS(PageArgument& args) {
String AutoConnect::_token_CHIP_ID(PageArgument& args) {
AC_UNUSED(args);
return String(WiFi.status());
return String(_getChipId());
}
String AutoConnect::_token_STATION_STATUS(PageArgument& args) {
String AutoConnect::_token_CONFIG_STAIP(PageArgument& args) {
AC_UNUSED(args);
PGM_P wlStatusSymbol = PSTR("");
// const char* wlStatusSymbol ="";
PGM_P wlStatusSymbols[] = {
// static const char* 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")
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") }
};
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_LOCAL_IP(PageArgument& args) {
AC_UNUSED(args);
return WiFi.localIP().toString();
}
String AutoConnect::_token_SOFTAP_IP(PageArgument& args) {
AC_UNUSED(args);
return WiFi.softAPIP().toString();
}
char liCont[600];
char* liBuf = liCont;
String AutoConnect::_token_GATEWAY(PageArgument& args) {
AC_UNUSED(args);
return WiFi.gatewayIP().toString();
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_NETMASK(PageArgument& args) {
String AutoConnect::_token_CPU_FREQ(PageArgument& args) {
AC_UNUSED(args);
return WiFi.subnetMask().toString();
return String(ESP.getCpuFreqMHz());
}
String AutoConnect::_token_AP_MAC(PageArgument& args) {
String AutoConnect::_token_CURRENT_SSID(PageArgument& args) {
AC_UNUSED(args);
uint8_t macAddress[6];
WiFi.softAPmacAddress(macAddress);
return AutoConnect::_toMACAddressString(macAddress);
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_STA_MAC(PageArgument& args) {
String AutoConnect::_token_DBM(PageArgument& args) {
AC_UNUSED(args);
uint8_t macAddress[6];
WiFi.macAddress(macAddress);
return AutoConnect::_toMACAddressString(macAddress);
int32_t dBm = WiFi.RSSI();
return (dBm == 31 ? String(F("N/A")) : String(dBm));
}
String AutoConnect::_token_CHANNEL(PageArgument& args) {
String AutoConnect::_token_ESTAB_SSID(PageArgument& args) {
AC_UNUSED(args);
return String(WiFi.channel());
return (WiFi.status() == WL_CONNECTED ? WiFi.SSID() : String(F("N/A")));
}
String AutoConnect::_token_DBM(PageArgument& args) {
String AutoConnect::_token_FLASH_SIZE(PageArgument& args) {
AC_UNUSED(args);
int32_t dBm = WiFi.RSSI();
return (dBm == 31 ? String(F("N/A")) : String(dBm));
return String(_getFlashChipRealSize());
}
String AutoConnect::_token_CPU_FREQ(PageArgument& args) {
String AutoConnect::_token_FREE_HEAP(PageArgument& args) {
AC_UNUSED(args);
return String(ESP.getCpuFreqMHz());
return String(_freeHeapSize);
}
String AutoConnect::_token_FLASH_SIZE(PageArgument& args) {
String AutoConnect::_token_GATEWAY(PageArgument& args) {
AC_UNUSED(args);
return String(_getFlashChipRealSize());
return WiFi.gatewayIP().toString();
}
String AutoConnect::_token_CHIP_ID(PageArgument& args) {
String AutoConnect::_token_HEAD(PageArgument& args) {
AC_UNUSED(args);
return String(_getChipId());
return String(FPSTR(_ELM_HTML_HEAD));
}
String AutoConnect::_token_FREE_HEAP(PageArgument& args) {
String AutoConnect::_token_HIDDEN_COUNT(PageArgument& args) {
AC_UNUSED(args);
return String(_freeHeapSize);
return String(_hiddenSSIDCount);
}
String AutoConnect::_token_LIST_SSID(PageArgument& args) {
@ -1226,53 +1157,14 @@ String AutoConnect::_token_LIST_SSID(PageArgument& args) {
return ssidListStr;
}
String AutoConnect::_token_SSID_COUNT(PageArgument& args) {
AC_UNUSED(args);
return String(_scanCount);
}
String AutoConnect::_token_HIDDEN_COUNT(PageArgument& args) {
String AutoConnect::_token_LOCAL_IP(PageArgument& args) {
AC_UNUSED(args);
return String(_hiddenSSIDCount);
return WiFi.localIP().toString();
}
String AutoConnect::_token_CONFIG_STAIP(PageArgument& args) {
String AutoConnect::_token_NETMASK(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);
return WiFi.subnetMask().toString();
}
String AutoConnect::_token_OPEN_SSID(PageArgument& args) {
@ -1301,7 +1193,7 @@ String AutoConnect::_token_OPEN_SSID(PageArgument& args) {
PGM_P rssiSym = _ssidNA;
PGM_P ssidLock = _ssidNull;
credit.load(i, &entry);
AC_DBG("A credential #%d loaded\n", (int)i);
AC_DBG("Credential #%d loaded\n", (int)i);
for (int8_t sc = 0; sc < (int8_t)_scanCount; sc++) {
if (!memcmp(entry.bssid, WiFi.BSSID(sc), sizeof(station_config_t::bssid))) {
_connectCh = WiFi.channel(sc);
@ -1318,23 +1210,131 @@ String AutoConnect::_token_OPEN_SSID(PageArgument& args) {
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("");
// const char* wlStatusSymbol ="";
PGM_P wlStatusSymbols[] = {
// static const char* 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_BOOTURI(PageArgument& args) {
String AutoConnect::_token_WIFI_MODE(PageArgument& args) {
AC_UNUSED(args);
return _getBootUri();
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_CURRENT_SSID(PageArgument& args) {
String AutoConnect::_token_WIFI_STATUS(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;
return String(WiFi.status());
}
/**
@ -1344,23 +1344,20 @@ String AutoConnect::_token_CURRENT_SSID(PageArgument& args) {
*/
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;
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:
id = PSTR("");
link = PSTR(AUTOCONNECT_URI_CONFIG);
label = PSTR(AUTOCONNECT_MENULABEL_CONFIGNEW);
break;
case AC_MENUITEM_OPENSSIDS:
id = PSTR("");
link = PSTR(AUTOCONNECT_URI_OPEN);
label = PSTR(AUTOCONNECT_MENULABEL_OPENSSIDS);
break;
case AC_MENUITEM_DISCONNECT:
id = PSTR("");
link = PSTR(AUTOCONNECT_URI_DISCON);
label = PSTR(AUTOCONNECT_MENULABEL_DISCONNECT);
break;
@ -1370,12 +1367,10 @@ String AutoConnect::_attachMenuItem(const AC_MENUITEM_t item) {
label = PSTR(AUTOCONNECT_MENULABEL_RESET);
break;
case AC_MENUITEM_HOME:
id = PSTR("");
link = PSTR("HOME_URI");
label = PSTR(AUTOCONNECT_MENULABEL_HOME);
break;
case AC_MENUITEM_DEVINFO:
id = PSTR("");
link = PSTR(AUTOCONNECT_URI);
label = PSTR(AUTOCONNECT_MENULABEL_DEVINFO);
break;
@ -1401,6 +1396,7 @@ String AutoConnect::_attachMenuItem(const AC_MENUITEM_t item) {
*/
PageElement* AutoConnect::_setupPage(String& uri) {
PageElement *elm = new PageElement();
bool reqAuth = false;
// Restore menu title
_menuTitle = _apConfig.title;
@ -1408,7 +1404,8 @@ PageElement* AutoConnect::_setupPage(String& uri) {
// Build the elements of current requested page.
if (uri == String(AUTOCONNECT_URI)) {
// Setup /auto
// 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));
@ -1436,7 +1433,8 @@ PageElement* AutoConnect::_setupPage(String& uri) {
}
else if (uri == String(AUTOCONNECT_URI_CONFIG) && (_apConfig.menuItems & AC_MENUITEM_CONFIGNEW)) {
// Setup /auto/config
// 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));
@ -1455,7 +1453,8 @@ PageElement* AutoConnect::_setupPage(String& uri) {
}
else if (uri == String(AUTOCONNECT_URI_CONNECT) && (_apConfig.menuItems & AC_MENUITEM_CONFIGNEW || _apConfig.menuItems & AC_MENUITEM_OPENSSIDS)) {
// Setup /auto/connect
// 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));
@ -1469,7 +1468,8 @@ PageElement* AutoConnect::_setupPage(String& uri) {
}
else if (uri == String(AUTOCONNECT_URI_OPEN) && (_apConfig.menuItems & AC_MENUITEM_OPENSSIDS)) {
// Setup /auto/open
// 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));
@ -1483,7 +1483,7 @@ PageElement* AutoConnect::_setupPage(String& uri) {
}
else if (uri == String(AUTOCONNECT_URI_DISCON) && (_apConfig.menuItems & AC_MENUITEM_DISCONNECT)) {
// Setup /auto/disc
// 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));
@ -1495,7 +1495,7 @@ PageElement* AutoConnect::_setupPage(String& uri) {
}
else if (uri == String(AUTOCONNECT_URI_RESET) && (_apConfig.menuItems & AC_MENUITEM_RESET)) {
// Setup /auto/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));
@ -1504,13 +1504,13 @@ PageElement* AutoConnect::_setupPage(String& uri) {
}
else if (uri == String(AUTOCONNECT_URI_RESULT)) {
// Setup /auto/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 /auto/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));
@ -1530,7 +1530,7 @@ PageElement* AutoConnect::_setupPage(String& uri) {
}
else if (uri == String(AUTOCONNECT_URI_FAIL)) {
// Setup /auto/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));
@ -1557,7 +1557,47 @@ PageElement* AutoConnect::_setupPage(String& uri) {
_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) {
const char* user = nullptr;
const char* password = nullptr;
HTTPAuthMethod method = _apConfig.auth == AC_AUTH_BASIC ? HTTPAuthMethod::BASIC_AUTH : HTTPAuthMethod::DIGEST_AUTH;
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);
}

@ -2,7 +2,7 @@
* AutoConnectTicker class implementation.
* Provides a service that shows the flicker signal according to WiFi
* connection status.
* @file AutoConnect.cpp
* @file AutoConnectTicker.cpp
* @author hieromon@gmail.com
* @version 0.9.11
* @date 2019-07-09

@ -0,0 +1,65 @@
/**
* AutoConnect quoted type declarations.
* @file AutoConnectTypes.h
* @author hieromon@gmail.com
* @version 1.2.0
* @date 2020-04-17
* @copyright MIT license.
*/
#ifndef _AUTOCONNECTTYPES_H_
#define _AUTOCONNECTTYPES_H_
/**< A type to save established credential at WiFi.begin automatically. */
typedef enum AC_SAVECREDENTIAL {
AC_SAVECREDENTIAL_NEVER,
AC_SAVECREDENTIAL_AUTO
} AC_SAVECREDENTIAL_t;
/**< URI that can be specified to AutoConnectConfig::bootUri. */
typedef enum AC_ONBOOTURI {
AC_ONBOOTURI_ROOT,
AC_ONBOOTURI_HOME
} AC_ONBOOTURI_t;
/** WiFi connection principle, it specifies the order of WiFi connecting with saved credentials. */
typedef enum AC_PRINCIPLE {
AC_PRINCIPLE_RECENT,
AC_PRINCIPLE_RSSI
} AC_PRINCIPLE_t;
/**< An enumerated type of the designated menu items. */
typedef enum AC_MENUITEM {
AC_MENUITEM_NONE = 0x0000,
AC_MENUITEM_CONFIGNEW = 0x0001,
AC_MENUITEM_OPENSSIDS = 0x0002,
AC_MENUITEM_DISCONNECT = 0x0004,
AC_MENUITEM_RESET = 0x0008,
AC_MENUITEM_HOME = 0x0010,
AC_MENUITEM_UPDATE = 0x0020,
AC_MENUITEM_DEVINFO = 0x0040
} AC_MENUITEM_t;
/**< Specifier for using built-in OTA */
typedef enum AC_OTA {
AC_OTA_EXTRA,
AC_OTA_BUILTIN
} AC_OTA_t;
/**< Scope of certification influence */
typedef enum AC_AUTHSCOPE {
AC_AUTHSCOPE_PARTIAL = 0x0001, // Available for particular AUX-pages.
AC_AUTHSCOPE_AUX = 0x0002, // All AUX-pages are affected by an authentication.
AC_AUTHSCOPE_AC = 0x0004, // Allow authentication to AutoConnect pages.
AC_AUTHSCOPE_PORTAL = AC_AUTHSCOPE_AC | AC_AUTHSCOPE_AUX, // All AutoConnect pages are affected by an authentication.
AC_AUTHSCOPE_WITHCP = 0x8000 // Allows authenticating in the standalone state.
} AC_AUTHSCOPE_t;
/**< A type to enable authentication. */
typedef enum AC_AUTH {
AC_AUTH_NONE,
AC_AUTH_DIGEST,
AC_AUTH_BASIC
} AC_AUTH_t;
#endif // !_AUTOCONNECTTYPES_H_

@ -41,7 +41,7 @@
* description:
* [
* {
* "name": "somefoler",
* "name": "somefolder",
* "type": "directory"
* },
* {
@ -488,7 +488,7 @@ String AutoConnectUpdateAct::_onResult(AutoConnectAux& result, PageArgument& arg
switch (_status) {
case UPDATE_SUCCESS:
resForm = String(F(" sucessfully updated. restart..."));
resForm = String(F(" successfully updated. restart..."));
resColor = String(F("blue"));
restart = true;
break;
@ -512,7 +512,7 @@ String AutoConnectUpdateAct::_onResult(AutoConnectAux& result, PageArgument& arg
/**
* A handler for notifying the client of the progress of update processing.
* This handler specifies the URI behavior defined as THndlerFunc of
* This handler specifies the URI behavior defined as THandlerFunc of
* ESP8266 WebServer (WebServer for ESP32).
* Usually, that URI is /_ac/update_progress and will return the
* processed amount of update processing to the client.

@ -116,7 +116,7 @@ class AutoConnectUpdateAct : public AutoConnectUpdateVoid, public HTTPUpdateClas
~AutoConnectUpdateAct();
void attach(AutoConnect& portal) override; /**< Attach the update class to AutoConnect */
void enable(void) override; /**< Enable the AutoConnectUpdateAct */
void disable(const bool activte = false) override; /**< Disable the AutoConnectUpdateAct */
void disable(const bool activate = false) override; /**< Disable the AutoConnectUpdateAct */
void handleUpdate(void) override; /**< Behaves the update process */
bool isEnabled(void) override { return _auxCatalog ? _auxCatalog->isMenu() : false; } /**< Returns current updater effectiveness */
AC_UPDATESTATUS_t status(void) override { return _status; } /**< reports the current update behavior status */

@ -2,8 +2,8 @@
* The default upload handler implementation.
* @file AutoConnectUploadImpl.h
* @author hieromon@gmail.com
* @version 0.9.9
* @date 2019-05-25
* @version 1.2.0
* @date 2020-05-29
* @copyright MIT license.
*/
@ -20,7 +20,11 @@
#include <SPI.h>
#include <SD.h>
#define FS_NO_GLOBALS
#ifdef AUTOCONNECT_USE_SPIFFS
#include <FS.h>
#else
#include <LittleFS.h>
#endif
// Types branching to be code commonly for the file system classes with
// ESP8266 and ESP32.

Loading…
Cancel
Save