Supports AutoConnectFile element

pull/57/head
Hieromon Ikasamo 5 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
arduino --install-boards esp32:esp32;
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; }
install:
- mkdir -p ~/Arduino/libraries
@ -39,3 +39,4 @@ script:
- buildExampleSketch mqttRSSI
- buildExampleSketch mqttRSSI_FS
- 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
AutoConnectCheckbox KEYWORD1
AutoConnectElement KEYWORD1
AutoConnectFile KEYWORD1
AutoConnectInput KEYWORD1
AutoConnectRadio KEYWORD1
AutoConnectSelect KEYWORD1
@ -39,6 +40,7 @@ name KEYWORD2
on KEYWORD2
onDetect KEYWORD2
onNotFound KEYWORD2
onUpload KEYWORD2
release KEYWORD2
save KEYWORD2
saveElement KEYWORD2
@ -64,6 +66,7 @@ AC_EXIT_BOTH LITERAL1
AC_Button LITERAL1
AC_Checkbox LITERAL1
AC_Element LITERAL1
AC_File LITERAL1
AC_Input LITERAL1
AC_Radio LITERAL1
AC_Select LITERAL1
@ -76,6 +79,7 @@ AC_Text LITERAL1
ACButton PREPROCESSOR
ACCheckbox PREPROCESSOR
ACElement PREPROCESSOR
ACFile PREPROCESSOR
ACInput PREPROCESSOR
ACRadio PREPROCESSOR
ACSelect PREPROCESSOR

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

@ -1,5 +1,5 @@
name=AutoConnect
version=0.9.7
version=0.9.8
author=Hieromon Ikasamo <hieromon@gmail.com>
maintainer=Hieromon Ikasamo <hieromon@gmail.com>
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.
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"/>

@ -392,6 +392,7 @@ void AutoConnect::_startWebServer(void) {
_responsePage->chunked(AUTOCONNECT_HTTP_TRANSFER);
_responsePage->reserve(AUTOCONNECT_CONTENTBUFFER_SIZE);
_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();
@ -598,7 +599,7 @@ void AutoConnect::_stopPortal(void) {
bool AutoConnect::_captivePortal(void) {
String hostHeader = _webServer->hostHeader();
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);
_webServer->sendHeader(String(F("Location")), location, true);
_webServer->send(302, String(F("text/plain")), _emptyString);
@ -788,7 +789,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,URI:%s", _webServer->hostHeader().c_str(), uri.c_str());
// When handleClient calls RequestHandler, the parsed http argument
// remains the previous request.
@ -801,6 +802,8 @@ bool AutoConnect::_classifyHandle(HTTPMethod method, String uri) {
AutoConnectAux* aux = _aux.get();
while (aux) {
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());
break;
}
@ -810,11 +813,12 @@ bool AutoConnect::_classifyHandle(HTTPMethod method, String uri) {
// Here, classify requested uri
if (uri == _uri) {
AC_DBG_DUMB(", already allocated\n");
AC_DBG_DUMB(",already allocated\n");
return true; // The response page already exists.
}
// Dispose decrepit page
_prevUri = _uri; // Save current uri for the upload request
_purgePages();
// Create the page dynamically
@ -824,15 +828,31 @@ bool AutoConnect::_classifyHandle(HTTPMethod method, String uri) {
_currentPageElement = _aux->_setupPage(uri);
}
if (_currentPageElement != nullptr) {
AC_DBG_DUMB(", generated:%s", uri.c_str());
AC_DBG_DUMB(",generated:%s", uri.c_str());
_uri = uri;
_responsePage->addElement(*_currentPageElement);
_responsePage->setUri(_uri.c_str());
}
AC_DBG_DUMB(", %s\n", _currentPageElement != nullptr ? "allocated" : "ignored");
AC_DBG_DUMB(",%s\n", _currentPageElement != nullptr ? " allocated" : "ignored");
return _currentPageElement != nullptr ? true : false;
}
/**
* A wrapper of the upload function for the WebServerClass. Invokes the
* upload function of the AutoConnectAux which has a destination URI.
*/
void AutoConnect::_handleUpload(const String& requestUri, const HTTPUpload& upload) {
AutoConnectAux* aux = _aux.get();
while (aux) {
if (aux->_uriStr == requestUri) {
aux->upload(_prevUri, upload);
// aux->upload(requestUri, upload);
break;
}
aux = aux->_next.get();
}
}
/**
* Purge allocated pages.
*/

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

@ -9,6 +9,7 @@
#include <algorithm>
#include "AutoConnect.h"
#include "AutoConnectAux.h"
#include "AutoConnectUploadImpl.h"
#include "AutoConnectElement.h"
#include "AutoConnectElementBasisImpl.h"
#ifdef AUTOCONNECT_USE_JSON
@ -204,6 +205,84 @@ bool AutoConnectAux::setElementValue(const String& name, std::vector<String> con
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
* to the chain list.
@ -359,9 +438,17 @@ PageElement* AutoConnectAux::_setupPage(const String& uri) {
* @param webServer A pointer to the class object of WebServerClass
*/
void AutoConnectAux::_storeElements(WebServerClass* webServer) {
// Retrieve each element value, Overwrites the value of all cataloged
// AutoConnectElements with arguments inherited from last http request.
for (AutoConnectElement& elm : _addonElm) {
// 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
// because the checkbox argument will not pass if it is not checked.
if (elm.typeOf() == AC_Checkbox)
@ -438,7 +525,7 @@ template<>
AutoConnectFileBasis& AutoConnectAux::getElement(const String& name) {
AutoConnectElement* elm = getElement(name);
if (elm) {
if (elm->typeOf() == AC_Input)
if (elm->typeOf() == AC_File)
return *(reinterpret_cast<AutoConnectFileBasis*>(elm));
else
AC_DBG("Element<%s> type mismatch<%d>\n", name.c_str(), elm->typeOf());

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

@ -127,6 +127,23 @@
#define AUTOCONNECT_CONTENTBUFFER_SIZE 0
#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
#define AC_UNUSED(expr) do { (void)(expr); } while (0)

@ -12,6 +12,7 @@
#include <vector>
#include <memory>
#include "AutoConnectUpload.h"
typedef enum {
AC_Button,
@ -31,6 +32,12 @@ typedef enum {
AC_Vertical
} 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.
* 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 {
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;
_upload.reset();
}
virtual ~AutoConnectFileBasis() {}
const String toHTML(void) const override;
bool attach(const ACFile_t store);
void detach(void) { _upload.reset(); }
AutoConnectUploadHandler* upload(void) const { return _upload.get(); }
String label; /**< A label for a subsequent input box */
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;
}
/**
* 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.
* If the value member is contained, it is reflected in the placeholder

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

@ -145,7 +145,7 @@ void AutoConnectCheckboxJson::serialize(JsonObject& json) {
* @return An object size for JsonBuffer.
*/
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);
if (json.containsKey(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 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_VALUE), value);
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)));
if (json.containsKey(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;
else if (arrange.equalsIgnoreCase(F(AUTOCONNECT_JSON_KEY_HORIZONTAL)))
else if (arrange.equalsIgnoreCase(F(AUTOCONNECT_JSON_VALUE_HORIZONTAL)))
order = AC_Horizontal;
else {
AC_DBG("Failed to load %s element, unknown %s\n", name.c_str(), arrange.c_str());
return false;
}
}
if (json.containsKey(F(AUTOCONNECT_JSON_KEY_VALUE))) {
empty();
@ -271,10 +299,10 @@ void AutoConnectRadioJson::serialize(JsonObject& json) {
values.add(v);
switch (order) {
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;
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;
}
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