Supports AutoConnectFile element

pull/57/head
Hieromon Ikasamo 6 years ago
parent bd4ee7fe80
commit cc6a14f529
  1. 3
      .travis.yml
  2. 148
      examples/FileUpload/FileUpload.ino
  3. 4
      keywords.txt
  4. 4
      library.json
  5. 2
      library.properties
  6. 2
      mkdocs/index.md
  7. 30
      src/AutoConnect.cpp
  8. 2
      src/AutoConnect.h
  9. 93
      src/AutoConnectAux.cpp
  10. 7
      src/AutoConnectAux.h
  11. 17
      src/AutoConnectDefs.h
  12. 21
      src/AutoConnectElementBasis.h
  13. 26
      src/AutoConnectElementBasisImpl.h
  14. 60
      src/AutoConnectElementJson.h
  15. 38
      src/AutoConnectElementJsonImpl.h
  16. 38
      src/AutoConnectUpload.h
  17. 143
      src/AutoConnectUploadImpl.h

@ -23,7 +23,7 @@ before_install:
- if [[ "$BOARD" =~ "esp32:esp32:" ]]; then - if [[ "$BOARD" =~ "esp32:esp32:" ]]; then
arduino --install-boards esp32:esp32; arduino --install-boards esp32:esp32;
fi fi
- arduino --install-library PubSubClient,ArduinoJson:5.13.4,PageBuilder:1.3.2 - arduino --install-library PubSubClient,ArduinoJson:5.13.4,PageBuilder:1.3.3
- buildExampleSketch() { arduino --verify --board $BOARD $PWD/examples/$1/$1.ino; } - buildExampleSketch() { arduino --verify --board $BOARD $PWD/examples/$1/$1.ino; }
install: install:
- mkdir -p ~/Arduino/libraries - mkdir -p ~/Arduino/libraries
@ -39,3 +39,4 @@ script:
- buildExampleSketch mqttRSSI - buildExampleSketch mqttRSSI
- buildExampleSketch mqttRSSI_FS - buildExampleSketch mqttRSSI_FS
- buildExampleSketch mqttRSSI_NA - buildExampleSketch mqttRSSI_NA
- buildExampleSketch FileUpload

@ -0,0 +1,148 @@
/*
FileUpload.ino, Example for the AutoConnect library.
Copyright (c) 2019, Hieromon Ikasamo
https://github.com/Hieromon/AutoConnect
This software is released under the MIT License.
https://opensource.org/licenses/MIT
FileUpload.ino writes the file uploaded from the HTTP client to SPIFFS
with its file name. To run this example successfully, you need the
SPIFFS area setting with Arduino IDE Tool menu which is larger than
the size of the upload file.
This example leverages the AutoConnectFile element to upload files to
the flash on the ESP8266/ESP32 module. The necessary basic process
for uploading and storing to flash is already embedded in the
AutoConnectFile element. By the sketch, just place the AutoConnectFile
element on a custom web page.
*/
#if defined(ARDUINO_ARCH_ESP8266)
#include <ESP8266WiFi.h>
#include <ESP8266WebServer.h>
#elif defined(ARDUINO_ARCH_ESP32)
#include <WiFi.h>
#include <WebServer.h>
#include <SPIFFS.h>
#endif
#include <FS.h>
#include <AutoConnect.h>
static const char PAGE_UPLOAD[] PROGMEM = R"(
{
"uri": "/",
"title": "Upload",
"menu": true,
"element": [
{
"name": "caption",
"type": "ACText",
"value": "<h2>File uploading platform<h2>"
},
{
"name": "filename",
"type": "ACFile",
"label": "Select file: ",
"store": "fs"
},
{
"name": "upload",
"type": "ACSubmit",
"value": "UPLOAD",
"uri": "/upload"
}
]
}
)";
static const char PAGE_BROWSE[] PROGMEM = R"(
{
"uri": "/upload",
"title": "Upload",
"menu": false,
"element": [
{
"name": "caption",
"type": "ACText",
"value": "<h2>Uploading ended<h2>"
},
{
"name": "filename",
"type": "ACText"
},
{
"name": "size",
"type": "ACText"
},
{
"name": "content_type",
"type": "ACText",
"value": "Content: "
}
]
}
)";
#if defined(ARDUINO_ARCH_ESP8266)
typedef ESP8266WebServer WiFiWebServer;
#elif defined(ARDUINO_ARCH_ESP32)
typedef WebServer WiFiWebServer;
#endif
WiFiWebServer server;
AutoConnect portal(server);
AutoConnectAux auxUpload;
AutoConnectAux auxBrowse;
/**
* Post uploading, AutoConnectFile's built-in upload handler reads the
* file saved in SPIFFS and displays the file contents on /upload custom
* web page. However, only files with mime type uploaded as text are
* displayed. A custom web page handler is called after upload.
* @param aux AutoConnectAux(/upload)
* @param args PageArgument
* @return Uploaded text content
*/
String postUpload(AutoConnectAux& aux, PageArgument& args) {
String content;
String filename = auxUpload.getElement<AutoConnectFile>("filename").value;
aux.getElement<AutoConnectText>("filename").value = filename;
aux.getElement<AutoConnectText>("size").value = String(auxUpload.getElement<AutoConnectFile>("filename").size) + String("bytes uploaded");
String contentType = auxUpload.getElement<AutoConnectFile>("filename").mimeType;
aux.getElement<AutoConnectText>("content_type").value = contentType;
if (contentType.indexOf("text/") >= 0) {
SPIFFS.begin();
File uploadFile = SPIFFS.open(String("/" + filename).c_str(), "r");
if (uploadFile) {
while (uploadFile.available()) {
char c = uploadFile.read();
if (c == '\n')
content += "<br>";
else
content += c;
}
uploadFile.close();
}
else
content = "Not saved";
SPIFFS.end();
}
return content;
}
void setup() {
delay(1000);
Serial.begin(115200);
Serial.println();
auxUpload.load(PAGE_UPLOAD);
auxBrowse.load(PAGE_BROWSE);
portal.join({ auxUpload, auxBrowse });
auxBrowse.on(postUpload);
portal.begin();
}
void loop() {
portal.handleClient();
}

@ -8,6 +8,7 @@ AutoConnectAux KEYWORD1
AutoConnectButton KEYWORD1 AutoConnectButton KEYWORD1
AutoConnectCheckbox KEYWORD1 AutoConnectCheckbox KEYWORD1
AutoConnectElement KEYWORD1 AutoConnectElement KEYWORD1
AutoConnectFile KEYWORD1
AutoConnectInput KEYWORD1 AutoConnectInput KEYWORD1
AutoConnectRadio KEYWORD1 AutoConnectRadio KEYWORD1
AutoConnectSelect KEYWORD1 AutoConnectSelect KEYWORD1
@ -39,6 +40,7 @@ name KEYWORD2
on KEYWORD2 on KEYWORD2
onDetect KEYWORD2 onDetect KEYWORD2
onNotFound KEYWORD2 onNotFound KEYWORD2
onUpload KEYWORD2
release KEYWORD2 release KEYWORD2
save KEYWORD2 save KEYWORD2
saveElement KEYWORD2 saveElement KEYWORD2
@ -64,6 +66,7 @@ AC_EXIT_BOTH LITERAL1
AC_Button LITERAL1 AC_Button LITERAL1
AC_Checkbox LITERAL1 AC_Checkbox LITERAL1
AC_Element LITERAL1 AC_Element LITERAL1
AC_File LITERAL1
AC_Input LITERAL1 AC_Input LITERAL1
AC_Radio LITERAL1 AC_Radio LITERAL1
AC_Select LITERAL1 AC_Select LITERAL1
@ -76,6 +79,7 @@ AC_Text LITERAL1
ACButton PREPROCESSOR ACButton PREPROCESSOR
ACCheckbox PREPROCESSOR ACCheckbox PREPROCESSOR
ACElement PREPROCESSOR ACElement PREPROCESSOR
ACFile PREPROCESSOR
ACInput PREPROCESSOR ACInput PREPROCESSOR
ACRadio PREPROCESSOR ACRadio PREPROCESSOR
ACSelect PREPROCESSOR ACSelect PREPROCESSOR

@ -12,7 +12,7 @@
[ [
{ {
"name": "PageBuilder", "name": "PageBuilder",
"version": ">=1.3.2" "version": ">=1.3.3"
} }
], ],
"frameworks": "arduino", "frameworks": "arduino",
@ -21,6 +21,6 @@
"espressif8266", "espressif8266",
"espressif32" "espressif32"
], ],
"version": "0.9.7", "version": "0.9.8",
"license": "MIT" "license": "MIT"
} }

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

@ -86,7 +86,7 @@ Install third-party platform using the *Boards Manager* of Arduino IDE. You can
The [PageBuilder](https://github.com/Hieromon/PageBuilder) library to build HTML for ESP8266WebServer is needed. 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.2** **later**.[^1] 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.2** **later**.[^1]
[^1]:Since AutoConnect v0.9.7, PageBuilder v1.3.2 later is required. [^1]:Since AutoConnect v0.9.8, PageBuilder v1.3.3 later is required.
<img src="images/lm.png" width="640"/> <img src="images/lm.png" width="640"/>

@ -392,6 +392,7 @@ void AutoConnect::_startWebServer(void) {
_responsePage->chunked(AUTOCONNECT_HTTP_TRANSFER); _responsePage->chunked(AUTOCONNECT_HTTP_TRANSFER);
_responsePage->reserve(AUTOCONNECT_CONTENTBUFFER_SIZE); _responsePage->reserve(AUTOCONNECT_CONTENTBUFFER_SIZE);
_responsePage->exitCanHandle(std::bind(&AutoConnect::_classifyHandle, this, std::placeholders::_1, std::placeholders::_2)); _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); _responsePage->insert(*_webServer);
_webServer->begin(); _webServer->begin();
@ -598,7 +599,7 @@ void AutoConnect::_stopPortal(void) {
bool AutoConnect::_captivePortal(void) { bool AutoConnect::_captivePortal(void) {
String hostHeader = _webServer->hostHeader(); String hostHeader = _webServer->hostHeader();
if (!_isIP(hostHeader) && (hostHeader != WiFi.localIP().toString()) && (!hostHeader.endsWith(F(".local")))) { if (!_isIP(hostHeader) && (hostHeader != WiFi.localIP().toString()) && (!hostHeader.endsWith(F(".local")))) {
AC_DBG("Detected appliaction, %s, %s\n", hostHeader.c_str(), WiFi.localIP().toString().c_str()); AC_DBG("Detected application, %s, %s\n", hostHeader.c_str(), WiFi.localIP().toString().c_str());
String location = String(F("http://")) + _webServer->client().localIP().toString() + String(AUTOCONNECT_URI); String location = String(F("http://")) + _webServer->client().localIP().toString() + String(AUTOCONNECT_URI);
_webServer->sendHeader(String(F("Location")), location, true); _webServer->sendHeader(String(F("Location")), location, true);
_webServer->send(302, String(F("text/plain")), _emptyString); _webServer->send(302, String(F("text/plain")), _emptyString);
@ -788,7 +789,7 @@ String AutoConnect::_invokeResult(PageArgument& args) {
bool AutoConnect::_classifyHandle(HTTPMethod method, String uri) { bool AutoConnect::_classifyHandle(HTTPMethod method, String uri) {
AC_UNUSED(method); AC_UNUSED(method);
_portalAccessPeriod = millis(); _portalAccessPeriod = millis();
AC_DBG("Host:%s, URI:%s", _webServer->hostHeader().c_str(), uri.c_str()); AC_DBG("Host:%s,URI:%s", _webServer->hostHeader().c_str(), uri.c_str());
// When handleClient calls RequestHandler, the parsed http argument // When handleClient calls RequestHandler, the parsed http argument
// remains the previous request. // remains the previous request.
@ -801,6 +802,8 @@ bool AutoConnect::_classifyHandle(HTTPMethod method, String uri) {
AutoConnectAux* aux = _aux.get(); AutoConnectAux* aux = _aux.get();
while (aux) { while (aux) {
if (aux->_uriStr == _auxUri) { if (aux->_uriStr == _auxUri) {
// Save the value owned by each element contained in the POST body
// of a current HTTP request to AutoConnectElements.
aux->_storeElements(_webServer.get()); aux->_storeElements(_webServer.get());
break; break;
} }
@ -810,11 +813,12 @@ bool AutoConnect::_classifyHandle(HTTPMethod method, String uri) {
// Here, classify requested uri // Here, classify requested uri
if (uri == _uri) { if (uri == _uri) {
AC_DBG_DUMB(", already allocated\n"); AC_DBG_DUMB(",already allocated\n");
return true; // The response page already exists. return true; // The response page already exists.
} }
// Dispose decrepit page // Dispose decrepit page
_prevUri = _uri; // Save current uri for the upload request
_purgePages(); _purgePages();
// Create the page dynamically // Create the page dynamically
@ -824,15 +828,31 @@ bool AutoConnect::_classifyHandle(HTTPMethod method, String uri) {
_currentPageElement = _aux->_setupPage(uri); _currentPageElement = _aux->_setupPage(uri);
} }
if (_currentPageElement != nullptr) { if (_currentPageElement != nullptr) {
AC_DBG_DUMB(", generated:%s", uri.c_str()); AC_DBG_DUMB(",generated:%s", uri.c_str());
_uri = uri; _uri = uri;
_responsePage->addElement(*_currentPageElement); _responsePage->addElement(*_currentPageElement);
_responsePage->setUri(_uri.c_str()); _responsePage->setUri(_uri.c_str());
} }
AC_DBG_DUMB(", %s\n", _currentPageElement != nullptr ? "allocated" : "ignored"); AC_DBG_DUMB(",%s\n", _currentPageElement != nullptr ? " allocated" : "ignored");
return _currentPageElement != nullptr ? true : false; return _currentPageElement != nullptr ? true : false;
} }
/**
* A wrapper of the upload function for the WebServerClass. Invokes the
* upload function of the AutoConnectAux which has a destination URI.
*/
void AutoConnect::_handleUpload(const String& requestUri, const HTTPUpload& upload) {
AutoConnectAux* aux = _aux.get();
while (aux) {
if (aux->_uriStr == requestUri) {
aux->upload(_prevUri, upload);
// aux->upload(requestUri, upload);
break;
}
aux = aux->_next.get();
}
}
/** /**
* Purge allocated pages. * Purge allocated pages.
*/ */

@ -216,6 +216,7 @@ class AutoConnect {
bool _loadAvailCredential(void); bool _loadAvailCredential(void);
void _stopPortal(void); void _stopPortal(void);
bool _classifyHandle(HTTPMethod mothod, String uri); bool _classifyHandle(HTTPMethod mothod, String uri);
void _handleUpload(const String& requestUri, const HTTPUpload& upload);
void _purgePages(void); void _purgePages(void);
virtual PageElement* _setupPage(String uri); virtual PageElement* _setupPage(String uri);
#ifdef AUTOCONNECT_USE_JSON #ifdef AUTOCONNECT_USE_JSON
@ -262,6 +263,7 @@ class AutoConnect {
/** Extended pages made up with AutoConnectAux */ /** Extended pages made up with AutoConnectAux */
std::unique_ptr<AutoConnectAux> _aux; std::unique_ptr<AutoConnectAux> _aux;
String _auxUri; /**< Last accessed AutoConnectAux */ String _auxUri; /**< Last accessed AutoConnectAux */
String _prevUri; /**< Previous generated page uri */
/** Saved configurations */ /** Saved configurations */
AutoConnectConfig _apConfig; AutoConnectConfig _apConfig;

@ -9,6 +9,7 @@
#include <algorithm> #include <algorithm>
#include "AutoConnect.h" #include "AutoConnect.h"
#include "AutoConnectAux.h" #include "AutoConnectAux.h"
#include "AutoConnectUploadImpl.h"
#include "AutoConnectElement.h" #include "AutoConnectElement.h"
#include "AutoConnectElementBasisImpl.h" #include "AutoConnectElementBasisImpl.h"
#ifdef AUTOCONNECT_USE_JSON #ifdef AUTOCONNECT_USE_JSON
@ -204,6 +205,84 @@ bool AutoConnectAux::setElementValue(const String& name, std::vector<String> con
return rc; return rc;
} }
/**
* The upload function that overrides the RequestHandler class
* attached with ESP8266WebServer.
* This function invokes the upload handler registered by the onUpload
* function which will be implemented by the user sketch.
*/
void AutoConnectAux::upload(const String& requestUri, const HTTPUpload& upload) {
if (upload.status == UPLOAD_FILE_START) {
AC_DBG("%s requests upload to %s\n", requestUri.c_str(), _uriStr.c_str());
// Selects a valid upload handler before uploading starts.
// Identify AutoConnectFile with the current upload request and
// save the value and mimeType attributes.
AC_DBG("ACFile %s ", upload.name.c_str());
String logContext = "missing";
AutoConnectElementVT addons;
AutoConnectAux* aux = _ac->_aux.get();
while (aux) {
if (aux->_uriStr == requestUri) {
addons = aux->_addonElm;
break;
}
aux = aux->_next.get();
}
_currentUpload = nullptr;
for (AutoConnectElement& elm : addons) {
if (elm.typeOf() == AC_File) {
_currentUpload = reinterpret_cast<AutoConnectFile*>(&elm);
// Reset previous value
_currentUpload->value = String("");
_currentUpload->mimeType = String("");
_currentUpload->size = 0;
// Overwrite with current upload request
if (upload.name.equalsIgnoreCase(_currentUpload->name)) {
_currentUpload->value = upload.filename;
_currentUpload->mimeType = upload.type;
logContext = "accepted " + _currentUpload->value;
break;
}
}
}
AC_DBG_DUMB("%s, handler ", logContext.c_str());
// If the current upload request is AutoConnectFile without default
// AutoConnectUpload (i.e. the store attribute is AC_File_Ex),
// enable the user-owned upload handler activated by the onUpload.
_upload = nullptr;
if (_currentUpload)
if (_currentUpload->attach(_currentUpload->store)) {
_upload = std::bind(&AutoConnectUploadHandler::upload, _currentUpload->upload(), std::placeholders::_1, std::placeholders::_2);
AC_DBG_DUMB("attached(%d)\n", (int)_currentUpload->store);
}
if (!_upload) {
if (_uploadHandler) {
_upload = _uploadHandler;
AC_DBG_DUMB("enabled\n");
}
else
AC_DBG_DUMB("missing\n");
}
}
// Invokes upload handler
if (_upload) {
_upload(requestUri, upload);
if (_currentUpload)
_currentUpload->size = upload.totalSize;
// Upload ended, purge handler
if (upload.status == UPLOAD_FILE_END || upload.status == UPLOAD_FILE_ABORTED) {
if (_currentUpload)
_currentUpload->detach();
AC_DBG("%ld bytes uploaded\n", upload.totalSize);
}
}
}
/** /**
* Concatenates subsequent AutoConnectAux pages starting from oneself * Concatenates subsequent AutoConnectAux pages starting from oneself
* to the chain list. * to the chain list.
@ -359,9 +438,17 @@ PageElement* AutoConnectAux::_setupPage(const String& uri) {
* @param webServer A pointer to the class object of WebServerClass * @param webServer A pointer to the class object of WebServerClass
*/ */
void AutoConnectAux::_storeElements(WebServerClass* webServer) { void AutoConnectAux::_storeElements(WebServerClass* webServer) {
// Retrieve each element value, Overwrites the value of all cataloged
// AutoConnectElements with arguments inherited from last http request.
for (AutoConnectElement& elm : _addonElm) { for (AutoConnectElement& elm : _addonElm) {
// Overwrite the value of all cataloged AutoConnectElements with
// arguments inherited from the last http request. // The POST body does not contain the value of the AutoConnectFile,
// so it can not be obtained with the WebServerClass::arg function.
// The AutoConnectFile value will be restored from least recent
// upload request.
if (elm.typeOf() == AC_File)
continue;
// Relies on AutoConnectRadio, it restores to false at the being // Relies on AutoConnectRadio, it restores to false at the being
// because the checkbox argument will not pass if it is not checked. // because the checkbox argument will not pass if it is not checked.
if (elm.typeOf() == AC_Checkbox) if (elm.typeOf() == AC_Checkbox)
@ -438,7 +525,7 @@ template<>
AutoConnectFileBasis& AutoConnectAux::getElement(const String& name) { AutoConnectFileBasis& AutoConnectAux::getElement(const String& name) {
AutoConnectElement* elm = getElement(name); AutoConnectElement* elm = getElement(name);
if (elm) { if (elm) {
if (elm->typeOf() == AC_Input) if (elm->typeOf() == AC_File)
return *(reinterpret_cast<AutoConnectFileBasis*>(elm)); return *(reinterpret_cast<AutoConnectFileBasis*>(elm));
else else
AC_DBG("Element<%s> type mismatch<%d>\n", name.c_str(), elm->typeOf()); AC_DBG("Element<%s> type mismatch<%d>\n", name.c_str(), elm->typeOf());

@ -47,7 +47,7 @@ typedef enum {
class AutoConnectAux : public PageBuilder { class AutoConnectAux : public PageBuilder {
public: public:
explicit AutoConnectAux(const String& uri = String(""), const String& title = String(""), const bool menu = true, const AutoConnectElementVT addons = AutoConnectElementVT()) : explicit AutoConnectAux(const String& uri = String(""), const String& title = String(""), const bool menu = true, const AutoConnectElementVT addons = AutoConnectElementVT()) :
_title(title), _menu(menu), _uriStr(String(uri)), _addonElm(addons) { _uri = _uriStr.c_str(); _next.release(); _ac.release(); } _title(title), _menu(menu), _uriStr(String(uri)), _addonElm(addons), _handler(nullptr), _order(AC_EXIT_AHEAD), _uploadHandler(nullptr) { _uri = _uriStr.c_str(); _next.release(); _ac.release(); }
~AutoConnectAux(); ~AutoConnectAux();
void add(AutoConnectElement& addon); /**< Add an element to the auxiliary page */ void add(AutoConnectElement& addon); /**< Add an element to the auxiliary page */
void add(AutoConnectElementVT addons); /**< Add the element set to the auxiliary page */ void add(AutoConnectElementVT addons); /**< Add the element set to the auxiliary page */
@ -61,6 +61,7 @@ class AutoConnectAux : public PageBuilder {
bool setElementValue(const String& name, std::vector<String> const& values); /**< Set values collection to specified element */ bool setElementValue(const String& name, std::vector<String> const& values); /**< Set values collection to specified element */
void setTitle(const String& title) { _title = title; } /**< Set a title of the auxiliary page */ void setTitle(const String& title) { _title = title; } /**< Set a title of the auxiliary page */
void on(const AuxHandlerFunctionT handler, const AutoConnectExitOrder_t order = AC_EXIT_AHEAD) { _handler = handler; _order = order; } /**< Set user handler */ void on(const AuxHandlerFunctionT handler, const AutoConnectExitOrder_t order = AC_EXIT_AHEAD) { _handler = handler; _order = order; } /**< Set user handler */
void onUpload(PageBuilder::UploadFuncT uploadFunc) override { _uploadHandler = uploadFunc; }
#ifdef AUTOCONNECT_USE_JSON #ifdef AUTOCONNECT_USE_JSON
bool load(const String& in); /**< Load whole elements to AutoConnectAux Page */ bool load(const String& in); /**< Load whole elements to AutoConnectAux Page */
@ -73,6 +74,7 @@ class AutoConnectAux : public PageBuilder {
#endif // !AUTOCONNECT_USE_JSON #endif // !AUTOCONNECT_USE_JSON
protected: protected:
void upload(const String& requestUri, const HTTPUpload& upload); /**< Uploader wrapper */
void _concat(AutoConnectAux& aux); /**< Make up chain of AutoConnectAux */ void _concat(AutoConnectAux& aux); /**< Make up chain of AutoConnectAux */
void _join(AutoConnect& ac); /**< Make a link to AutoConnect */ void _join(AutoConnect& ac); /**< Make a link to AutoConnect */
PageElement* _setupPage(const String& uri); /**< AutoConnectAux page builder */ PageElement* _setupPage(const String& uri); /**< AutoConnectAux page builder */
@ -100,7 +102,8 @@ class AutoConnectAux : public PageBuilder {
std::unique_ptr<AutoConnect> _ac; /**< Hosted AutoConnect instance */ std::unique_ptr<AutoConnect> _ac; /**< Hosted AutoConnect instance */
AuxHandlerFunctionT _handler; /**< User sketch callback function when AutoConnectAux page requested. */ AuxHandlerFunctionT _handler; /**< User sketch callback function when AutoConnectAux page requested. */
AutoConnectExitOrder_t _order; /**< The order in which callback functions are called. */ AutoConnectExitOrder_t _order; /**< The order in which callback functions are called. */
PageBuilder::UploadFuncT _uploadHandler; /**< The AutoConnectFile corresponding to current upload */
AutoConnectFile* _currentUpload; /**< AutoConnectFile handling the current upload */
static const char _PAGE_AUX[] PROGMEM; /**< Auxiliary page template */ static const char _PAGE_AUX[] PROGMEM; /**< Auxiliary page template */
// Protected members can be used from AutoConnect which handles AutoConnectAux pages. // Protected members can be used from AutoConnect which handles AutoConnectAux pages.

@ -127,6 +127,23 @@
#define AUTOCONNECT_CONTENTBUFFER_SIZE 0 #define AUTOCONNECT_CONTENTBUFFER_SIZE 0
#endif // !AUTOCONNECT_CONTENTBUFFER_SIZE #endif // !AUTOCONNECT_CONTENTBUFFER_SIZE
// SD pin assignment for AutoConnectFile
#ifndef AUTOCONNECT_SD_CS
#if defined(ARDUINO_ARCH_ESP8266)
#define AUTOCONNECT_SD_CS SD_CHIP_SELECT_PIN
#elif defined(ARDUINO_ARCH_ESP32)
#define AUTOCONNECT_SD_CS SS
#endif
#endif // !AUTOCONNECT_SD_CS
// SPI transfer speed for SD
#ifndef AUTOCONNECT_SD_SPEED
#if defined(ARDUINO_ARCH_ESP8266)
#define AUTOCONNECT_SD_SPEED SPI_HALF_SPEED
#elif defined(ARDUINO_ARCH_ESP32)
#define AUTOCONNECT_SD_SPEED 4000000
#endif
#endif // !AUTOCONNECT_SD_SPEED
// Explicitly avoiding unused warning with token handler of PageBuilder // Explicitly avoiding unused warning with token handler of PageBuilder
#define AC_UNUSED(expr) do { (void)(expr); } while (0) #define AC_UNUSED(expr) do { (void)(expr); } while (0)

@ -12,6 +12,7 @@
#include <vector> #include <vector>
#include <memory> #include <memory>
#include "AutoConnectUpload.h"
typedef enum { typedef enum {
AC_Button, AC_Button,
@ -31,6 +32,12 @@ typedef enum {
AC_Vertical AC_Vertical
} ACArrange_t; /**< The element arrange order */ } ACArrange_t; /**< The element arrange order */
typedef enum {
AC_File_FS = 0,
AC_File_SD,
AC_File_Ext
} ACFile_t; /**< AutoConnectFile media type */
/** /**
* AutoConnectAux element base. * AutoConnectAux element base.
* Placed a raw text that can be added by user sketch. * Placed a raw text that can be added by user sketch.
@ -101,13 +108,23 @@ class AutoConnectCheckboxBasis : virtual public AutoConnectElementBasis {
*/ */
class AutoConnectFileBasis : virtual public AutoConnectElementBasis { class AutoConnectFileBasis : virtual public AutoConnectElementBasis {
public: public:
explicit AutoConnectFileBasis(const char* name = "", const char* value= "", const char* label = "") : AutoConnectElementBasis(name, value), label(String(label)) { explicit AutoConnectFileBasis(const char* name = "", const char* value = "", const char* label = "", const ACFile_t store = AC_File_FS) : AutoConnectElementBasis(name, value), label(String(label)), store(store) {
_type = AC_File; _type = AC_File;
_upload.reset();
} }
virtual ~AutoConnectFileBasis() {} virtual ~AutoConnectFileBasis() {}
const String toHTML(void) const override; const String toHTML(void) const override;
bool attach(const ACFile_t store);
void detach(void) { _upload.reset(); }
AutoConnectUploadHandler* upload(void) const { return _upload.get(); }
String label; /**< A label for a subsequent input box */ String label; /**< A label for a subsequent input box */
ACFile_t store; /**< Type of file store */
String mimeType; /**< Uploading file mime type string */
size_t size; /**< Total uploaded bytes */
protected:
std::unique_ptr<AutoConnectUploadHandler> _upload;
}; };
/** /**

@ -62,6 +62,32 @@ const String AutoConnectFileBasis::toHTML(void) const {
return html; return html;
} }
/**
* Instantiate the upload handler with the specified store type.
* @param store An enumuration value of ACFile_t
*/
bool AutoConnectFileBasis::attach(const ACFile_t store) {
AutoConnectUploadFS* handlerFS;
AutoConnectUploadSD* handlerSD;
// Release previous handler
detach();
// Classify a handler type and create the corresponding handler
switch (store) {
case AC_File_FS:
handlerFS = new AutoConnectUploadFS(SPIFFS);
_upload.reset(reinterpret_cast<AutoConnectUploadHandler*>(handlerFS));
break;
case AC_File_SD:
handlerSD = new AutoConnectUploadSD(SD);
_upload.reset(reinterpret_cast<AutoConnectUploadHandler*>(handlerSD));
break;
case AC_File_Ext:
break;
}
return _upload != false;
}
/** /**
* Generate an HTML <input type=text> element. * Generate an HTML <input type=text> element.
* If the value member is contained, it is reflected in the placeholder * If the value member is contained, it is reflected in the placeholder

@ -13,32 +13,36 @@
#include "AutoConnectElementBasis.h" #include "AutoConnectElementBasis.h"
#include <ArduinoJson.h> #include <ArduinoJson.h>
#define AUTOCONNECT_JSON_KEY_ACTION "action" #define AUTOCONNECT_JSON_KEY_ACTION "action"
#define AUTOCONNECT_JSON_KEY_ARRANGE "arrange" #define AUTOCONNECT_JSON_KEY_ARRANGE "arrange"
#define AUTOCONNECT_JSON_KEY_CHECKED "checked" #define AUTOCONNECT_JSON_KEY_CHECKED "checked"
#define AUTOCONNECT_JSON_KEY_ELEMENT "element" #define AUTOCONNECT_JSON_KEY_ELEMENT "element"
#define AUTOCONNECT_JSON_KEY_HORIZONTAL "horizontal" #define AUTOCONNECT_JSON_KEY_LABEL "label"
#define AUTOCONNECT_JSON_KEY_LABEL "label" #define AUTOCONNECT_JSON_KEY_MENU "menu"
#define AUTOCONNECT_JSON_KEY_MENU "menu" #define AUTOCONNECT_JSON_KEY_NAME "name"
#define AUTOCONNECT_JSON_KEY_NAME "name" #define AUTOCONNECT_JSON_KEY_OPTION "option"
#define AUTOCONNECT_JSON_KEY_OPTION "option" #define AUTOCONNECT_JSON_KEY_PATTERN "pattern"
#define AUTOCONNECT_JSON_KEY_PATTERN "pattern" #define AUTOCONNECT_JSON_KEY_PLACEHOLDER "placeholder"
#define AUTOCONNECT_JSON_KEY_PLACEHOLDER "placeholder" #define AUTOCONNECT_JSON_KEY_STORE "store"
#define AUTOCONNECT_JSON_KEY_STYLE "style" #define AUTOCONNECT_JSON_KEY_STYLE "style"
#define AUTOCONNECT_JSON_KEY_TITLE "title" #define AUTOCONNECT_JSON_KEY_TITLE "title"
#define AUTOCONNECT_JSON_KEY_TYPE "type" #define AUTOCONNECT_JSON_KEY_TYPE "type"
#define AUTOCONNECT_JSON_KEY_URI "uri" #define AUTOCONNECT_JSON_KEY_URI "uri"
#define AUTOCONNECT_JSON_KEY_VALUE "value" #define AUTOCONNECT_JSON_KEY_VALUE "value"
#define AUTOCONNECT_JSON_KEY_VERTICAL "vertical" #define AUTOCONNECT_JSON_TYPE_ACBUTTON "ACButton"
#define AUTOCONNECT_JSON_TYPE_ACBUTTON "ACButton" #define AUTOCONNECT_JSON_TYPE_ACCHECKBOX "ACCheckBox"
#define AUTOCONNECT_JSON_TYPE_ACCHECKBOX "ACCheckBox" #define AUTOCONNECT_JSON_TYPE_ACELEMENT "ACElement"
#define AUTOCONNECT_JSON_TYPE_ACELEMENT "ACElement" #define AUTOCONNECT_JSON_TYPE_ACFILE "ACFile"
#define AUTOCONNECT_JSON_TYPE_ACFILE "ACFile" #define AUTOCONNECT_JSON_TYPE_ACINPUT "ACInput"
#define AUTOCONNECT_JSON_TYPE_ACINPUT "ACInput" #define AUTOCONNECT_JSON_TYPE_ACRADIO "ACRadio"
#define AUTOCONNECT_JSON_TYPE_ACRADIO "ACRadio" #define AUTOCONNECT_JSON_TYPE_ACSELECT "ACSelect"
#define AUTOCONNECT_JSON_TYPE_ACSELECT "ACSelect" #define AUTOCONNECT_JSON_TYPE_ACSUBMIT "ACSubmit"
#define AUTOCONNECT_JSON_TYPE_ACSUBMIT "ACSubmit" #define AUTOCONNECT_JSON_TYPE_ACTEXT "ACText"
#define AUTOCONNECT_JSON_TYPE_ACTEXT "ACText" #define AUTOCONNECT_JSON_VALUE_EXTERNAL "external"
#define AUTOCONNECT_JSON_VALUE_FS "fs"
#define AUTOCONNECT_JSON_VALUE_HORIZONTAL "horizontal"
#define AUTOCONNECT_JSON_VALUE_SD "sd"
#define AUTOCONNECT_JSON_VALUE_VERTICAL "vertical"
/** /**
* AutoConnectAux element base with handling with JSON object. * AutoConnectAux element base with handling with JSON object.
@ -113,13 +117,15 @@ class AutoConnectCheckboxJson : public AutoConnectElementJson, public AutoConnec
* @param value A string value entered by the selected file name. * @param value A string value entered by the selected file name.
* @param label A label string that follows file-select box, optionally. * @param label A label string that follows file-select box, optionally.
* The label is placed in front of file-select box. * The label is placed in front of file-select box.
* @param store An enumuration value of store type.
*/ */
class AutoConnectFileJson : public AutoConnectElementJson, public AutoConnectFileBasis { class AutoConnectFileJson : public AutoConnectElementJson, public AutoConnectFileBasis {
public: public:
explicit AutoConnectFileJson(const char* name = "", const char* value= "", const char* label = "") { explicit AutoConnectFileJson(const char* name = "", const char* value= "", const char* label = "", const ACFile_t store = AC_File_FS) {
AutoConnectFileBasis::name = String(name); AutoConnectFileBasis::name = String(name);
AutoConnectFileBasis::value = String(value); AutoConnectFileBasis::value = String(value);
AutoConnectFileBasis::label = String(label); AutoConnectFileBasis::label = String(label);
AutoConnectFileBasis::store = store;
} }
~AutoConnectFileJson() {} ~AutoConnectFileJson() {}
size_t getObjectSize(void) const override; size_t getObjectSize(void) const override;

@ -145,7 +145,7 @@ void AutoConnectCheckboxJson::serialize(JsonObject& json) {
* @return An object size for JsonBuffer. * @return An object size for JsonBuffer.
*/ */
size_t AutoConnectFileJson::getObjectSize() const { size_t AutoConnectFileJson::getObjectSize() const {
return AutoConnectElementJson::getObjectSize() + JSON_OBJECT_SIZE(1); return AutoConnectElementJson::getObjectSize() + JSON_OBJECT_SIZE(2);
} }
/** /**
@ -160,6 +160,19 @@ bool AutoConnectFileJson::loadMember(const JsonObject& json) {
_setMember(json); _setMember(json);
if (json.containsKey(F(AUTOCONNECT_JSON_KEY_LABEL))) if (json.containsKey(F(AUTOCONNECT_JSON_KEY_LABEL)))
label = json.get<String>(F(AUTOCONNECT_JSON_KEY_LABEL)); label = json.get<String>(F(AUTOCONNECT_JSON_KEY_LABEL));
if (json.containsKey(F(AUTOCONNECT_JSON_KEY_STORE))) {
String media = json.get<String>(F(AUTOCONNECT_JSON_KEY_STORE));
if (media.equalsIgnoreCase(F(AUTOCONNECT_JSON_VALUE_FS)))
store = AC_File_FS;
else if (media.equalsIgnoreCase(F(AUTOCONNECT_JSON_VALUE_SD)))
store = AC_File_SD;
else if (media.equalsIgnoreCase(F(AUTOCONNECT_JSON_VALUE_EXTERNAL)))
store = AC_File_Ext;
else {
AC_DBG("Failed to load %s element, unknown %s\n", name.c_str(), media.c_str());
return false;
}
}
return true; return true;
} }
return false; return false;
@ -174,6 +187,17 @@ void AutoConnectFileJson::serialize(JsonObject& json) {
json.set(F(AUTOCONNECT_JSON_KEY_TYPE), F(AUTOCONNECT_JSON_TYPE_ACFILE)); json.set(F(AUTOCONNECT_JSON_KEY_TYPE), F(AUTOCONNECT_JSON_TYPE_ACFILE));
json.set(F(AUTOCONNECT_JSON_KEY_VALUE), value); json.set(F(AUTOCONNECT_JSON_KEY_VALUE), value);
json.set(F(AUTOCONNECT_JSON_KEY_LABEL), label); json.set(F(AUTOCONNECT_JSON_KEY_LABEL), label);
switch (store) {
case AC_File_FS:
json.set(F(AUTOCONNECT_JSON_KEY_STORE), AUTOCONNECT_JSON_VALUE_FS);
break;
case AC_File_SD:
json.set(F(AUTOCONNECT_JSON_KEY_STORE), AUTOCONNECT_JSON_VALUE_SD);
break;
case AC_File_Ext:
json.set(F(AUTOCONNECT_JSON_KEY_STORE), AUTOCONNECT_JSON_VALUE_EXTERNAL);
break;
}
} }
/** /**
@ -242,10 +266,14 @@ bool AutoConnectRadioJson::loadMember(const JsonObject& json) {
checked = static_cast<uint8_t>(json.get<int>(F(AUTOCONNECT_JSON_KEY_CHECKED))); checked = static_cast<uint8_t>(json.get<int>(F(AUTOCONNECT_JSON_KEY_CHECKED)));
if (json.containsKey(F(AUTOCONNECT_JSON_KEY_ARRANGE))) { if (json.containsKey(F(AUTOCONNECT_JSON_KEY_ARRANGE))) {
String arrange = json.get<String>(F(AUTOCONNECT_JSON_KEY_ARRANGE)); String arrange = json.get<String>(F(AUTOCONNECT_JSON_KEY_ARRANGE));
if (arrange.equalsIgnoreCase(F(AUTOCONNECT_JSON_KEY_VERTICAL))) if (arrange.equalsIgnoreCase(F(AUTOCONNECT_JSON_VALUE_VERTICAL)))
order = AC_Vertical; order = AC_Vertical;
else if (arrange.equalsIgnoreCase(F(AUTOCONNECT_JSON_KEY_HORIZONTAL))) else if (arrange.equalsIgnoreCase(F(AUTOCONNECT_JSON_VALUE_HORIZONTAL)))
order = AC_Horizontal; order = AC_Horizontal;
else {
AC_DBG("Failed to load %s element, unknown %s\n", name.c_str(), arrange.c_str());
return false;
}
} }
if (json.containsKey(F(AUTOCONNECT_JSON_KEY_VALUE))) { if (json.containsKey(F(AUTOCONNECT_JSON_KEY_VALUE))) {
empty(); empty();
@ -271,10 +299,10 @@ void AutoConnectRadioJson::serialize(JsonObject& json) {
values.add(v); values.add(v);
switch (order) { switch (order) {
case AC_Horizontal: case AC_Horizontal:
json.set(F(AUTOCONNECT_JSON_KEY_ARRANGE), AUTOCONNECT_JSON_KEY_HORIZONTAL); json.set(F(AUTOCONNECT_JSON_KEY_ARRANGE), AUTOCONNECT_JSON_VALUE_HORIZONTAL);
break; break;
case AC_Vertical: case AC_Vertical:
json.set(F(AUTOCONNECT_JSON_KEY_ARRANGE), AUTOCONNECT_JSON_KEY_VERTICAL); json.set(F(AUTOCONNECT_JSON_KEY_ARRANGE), AUTOCONNECT_JSON_VALUE_VERTICAL);
break; break;
} }
json.set(F(AUTOCONNECT_JSON_KEY_CHECKED), checked); json.set(F(AUTOCONNECT_JSON_KEY_CHECKED), checked);

@ -0,0 +1,38 @@
/**
* The upload wrapper base class definition and the default up-loader
* class declarations.
* @file AutoConnectUpload.h
* @author hieromon@gmail.com
* @version 0.9.8
* @date 2019-03-19
* @copyright MIT license.
*/
#ifndef _AUTOCONNECTUPLOAD_H_
#define _AUTOCONNECTUPLOAD_H_
#if defined(ARDUINO_ARCH_ESP8266)
#include <ESP8266WiFi.h>
#include <ESP8266WebServer.h>
#elif defined(ARDUINO_ARCH_ESP32)
#include <WiFi.h>
#include <WebServer.h>
#endif
/**
* Uploader base class. This class is a wrapper for the AutoConnectUpload
* class, and only the upload member function is implemented.
*/
class AutoConnectUploadHandler {
public:
explicit AutoConnectUploadHandler() {}
virtual ~AutoConnectUploadHandler() {}
virtual void upload(const String& requestUri, const HTTPUpload& upload);
protected:
virtual bool _open(const char* filename, const char* mode) = 0;
virtual size_t _write(const uint8_t *buf, size_t size) = 0;
virtual void _close(void) = 0;
};
#endif // !_AUTOCONNECTUPLOAD_H_

@ -0,0 +1,143 @@
/**
* The default upload handler implementation.
* @file AutoConnectUploadImpl.h
* @author hieromon@gmail.com
* @version 0.9.8
* @date 2019-03-19
* @copyright MIT license.
*/
#ifndef _AUTOCONNECTUPLOADIMPL_H_
#define _AUTOCONNECTUPLOADIMPL_H_
#if defined(ARDUINO_ARCH_ESP8266)
#include <ESP8266WiFi.h>
#elif defined(ARDUINO_ARCH_ESP32)
#include <WiFi.h>
#include <SPIFFS.h>
#endif
#include <SPI.h>
#include <SD.h>
#define FS_NO_GLOBALS
#include <FS.h>
// Types branching to be code commonly for the file system classes with
// ESP8266 and ESP32.
#if defined(ARDUINO_ARCH_ESP8266)
typedef fs::FS SPIFFST; // SPIFFS:File system class
typedef fs::File SPIFileT; // SPIFFS:File class
typedef SDClass SDClassT; // SD:File system class
typedef File SDFileT; // SD:File class
#elif defined(ARDUINO_ARCH_ESP32)
typedef fs::SPIFFSFS SPIFFST;
typedef fs::File SPIFileT;
typedef fs::SDFS SDClassT;
typedef SDFile SDFileT;
#endif
#include "AutoConnectDefs.h"
#include "AutoConnectUpload.h"
/**
* Handles the default upload process depending on the upload status.
* This handler function supports the status of UPLOAD_FILE_START,
* UPLOAD_FILE_WRITE, UPLOAD_FILE_END and calls open, write and
* close processing respectively.
* @param requestUri A reference to the upload request uri.
* @param upload A reference of HTTPUpload entity.
*/
void AutoConnectUploadHandler::upload(const String& requestUri, const HTTPUpload& upload) {
AC_UNUSED(requestUri);
switch (upload.status) {
case UPLOAD_FILE_START: {
String absFilename = "/" + upload.filename;
(void)_open(absFilename.c_str(), "w");
break;
}
case UPLOAD_FILE_WRITE:
(void)_write(upload.buf, upload.currentSize);
break;
case UPLOAD_FILE_ABORTED:
case UPLOAD_FILE_END:
_close();
break;
}
}
// Default handler for uploading to the standard SPIFFS class embedded in the core.
class AutoConnectUploadFS : public AutoConnectUploadHandler {
public:
explicit AutoConnectUploadFS(SPIFFST& media) : _media(&media) {}
~AutoConnectUploadFS() { _close(); }
protected:
bool _open(const char* filename, const char* mode) override {
#if defined(ARDUINO_ARCH_ESP8266)
if (_media->begin()) {
#elif defined(ARDUINO_ARCH_ESP32)
if (_media->begin(true)) {
#endif
_file = _media->open(filename, mode);
return _file != false;
}
return false;
}
size_t _write(const uint8_t* buf, size_t size) override {
if (_file)
return _file.write(buf, size);
else
return -1;
}
void _close(void) override {
if (_file)
_file.close();
_media->end();
}
SPIFFST* _media;
SPIFileT _file;
};
// Default handler for uploading to the standard SD class embedded in the core.
class AutoConnectUploadSD : public AutoConnectUploadHandler {
public:
explicit AutoConnectUploadSD(SDClassT& media, const uint8_t cs = AUTOCONNECT_SD_CS, const uint32_t speed = AUTOCONNECT_SD_SPEED) : _media(&media), _cs(cs), _speed(speed) {}
~AutoConnectUploadSD() { _close(); }
protected:
bool _open(const char* filename, const char* mode) override {
#if defined(ARDUINO_ARCH_ESP8266)
if (_media->begin(_cs, _speed)) {
uint8_t oflag = *mode == 'w' ? FILE_WRITE : FILE_READ;
#elif defined(ARDUINO_ARCH_ESP32)
if (_media->begin(_cs, SPI, _speed)) {
const char* oflag = mode;
#endif
_file = _media->open(filename, oflag);
return _file != false;
}
return false;
}
size_t _write(const uint8_t* buf, size_t size) override {
if (_file)
return _file.write(buf, size);
else
return -1;
}
void _close(void) override {
if (_file)
_file.close();
_media->end();
}
SDClassT* _media;
SDFileT _file;
uint8_t _cs;
uint8_t _speed;
};
#endif // !_AUTOCONNECTUPLOADIMPL_H_
Loading…
Cancel
Save