Supports AutoConnectFile

pull/57/head
Hieromon Ikasamo 5 years ago
parent 6dfd9ccf48
commit 5935ceaf58
  1. 4
      mkdocs/acelements.md
  2. 2
      mkdocs/acjson.md
  3. 367
      mkdocs/acupload.md
  4. 22
      mkdocs/apiaux.md
  5. 2
      mkdocs/apielements.md
  6. 973
      mkdocs/images/ac_upload_flow.svg
  7. BIN
      mkdocs/images/upload.gif
  8. 2
      src/AutoConnectAux.h
  9. 2
      src/AutoConnectElementBasis.h
  10. 2
      src/AutoConnectElementBasisImpl.h
  11. 4
      src/AutoConnectElementJsonImpl.h

@ -196,7 +196,7 @@ Specifies the destination to save the uploaded file. The destination can be spec
- AC_File_FS: Save as the SPIFFS file in flash of ESP8266/ESP32 module.
- AC_File_SD: Save to an external SD device connected to ESP8266/ESP32 module.
- AC_File_Ext: Pass the content of the uploaded file to the uploader which is declared by the sketch individually. Its uploader must inherit **AutoConnectUploadHandler** class and implements *_open*, *_write* and *_close* function.
- AC_File_Extern: 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.
!!! note "Built-in uploader is ready."
AutoConnect already equips the built-in uploader for saving to the SPIFFS as AC_File_FS and the external SD as AC_File_SD. It is already implemented inside AutoConnect and will store uploaded file automatically.
@ -390,7 +390,7 @@ ACButton ( *name* <small>\[</small> , *value* <small>\]</small> <small>\[</small
ACCheckbox ( *name* <small>\[</small> , *value* <small>\]</small> <small>\[</small> , *label* <small>\]</small> <small>\[</small> , **true** | **false** <small>\]</small> )
ACFile ( *name* <small>\[</small> , *value* <small>\]</small> <small>\[</small> , *label* <small>\]</small> <small>\[</small> , **AC\_File\_FS** | **AC\_File\_SD** | **AC\_File\_Ext** <small>\]</small> )
ACFile ( *name* <small>\[</small> , *value* <small>\]</small> <small>\[</small> , *label* <small>\]</small> <small>\[</small> , **AC\_File\_FS** | **AC\_File\_SD** | **AC\_File\_Extern** <small>\]</small> )
ACInput ( *name* <small>\[</small> , *value* <small>\]</small> <small>\[</small> , *label* <small>\]</small> <small>\[</small> , *pattern* <small>\]</small> <small>\[</small> , *placeholder* <small>\]</small> )

@ -149,7 +149,7 @@ This is different for each AutoConnectElements, and the key that can be specifie
: - **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>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** class and implements *_open*, *_write* and *_close* function.</p>
<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>
#### <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.

@ -0,0 +1,367 @@
## Uploading file from Web Browser
If you have to write some data individually to the ESP8266/ESP32 module for the sketch behavior, the [AutoConnectFile](acelements.md#autoconnectfile) element will assist with your wants implementation. The AutoConnectFile element produces an HTML `<input type="file">` tag and can save uploaded file to the flash or external SD of the ESP8266/ESP32 module. The handler for saving is built into AutoConnect. You can use it to inject any sketch data such as the initial values for the custom Web page into the ESP module via OTA without using the sketch data upload tool of Arduino-IDE.
<p style="display:block;margin-left:auto;margin-right:auto;width:603px;height:368px;border:1px solid lightgray;"><img data-gifffer="images/upload.gif" data-gifffer-width="601" data-gifffer-height="366""/></p>
## Basic steps of the file upload sketch
Here is the basic procedure of the sketch which can upload files from the client browser using AutoConnectFile:[^1]
[^1]:The AutoConnectFile element can be used with other AutoConnectElements on the same page.
1. Place AutoConnectFile on a custom Web page by writing JSON or constructor code directly with the sketch.
2. Place other AutoConnectElements as needed.
3. Place AutoConnectSubmit on the same custom Web page.
4. Perform the following process in the on-handler of submitting destination:
- Retrieve the [AutoConnectFile instance](apielements.md#autoconnectfile) from the custom Web page where you placed the AutoConnectFile element using the [AutoConnectAux::getElement](apiaux.md#getelement) function or the [operator \[\]](apiaux.md#operator).
- Start access to the device specified as the upload destination. In usually, it depends on the file system's begin function. For example, if you specified Flash's SPIFFS as the upload destination, invokes *SPIFFS.begin()*.
- The [value member](acelements.md#value_3) of AutoConnectFile contains the file name of the upload file. Use its file name to access the uploaded file on the device.
- Invokes the end function associated with the begin to close the device. It is the *SPIFFS.end()** if the flash on the ESP module has been begun for SPIFFS.
The following sketch is an example that implements the above basic steps. The *postUpload* function is the on-handler and retrieves the AutoConnectFile as named `upload_file`. You should note that this handler is **not** for a custom Web page placed with its AutoConnectFile element. The uploaded file should be processed by the handler for the transition destination page from the AutoConnectFile element placed page. AutoConnect built-in upload handler will save the uploaded file to the specified device before invoking the *postUpload* function.
However, If you use uploaded files in different situations, it may be more appropriate to place the actual handling process outside the handler. It applies for the parameter file, etc. The important thing is that you do not have to sketch file reception and storing logic by using the AutoConnectFile element and the upload handler built into the AutoConnect.
```cpp hl_lines="14 53 64 67 70 86"
#include <ESP8266WiFi.h>
#include <ESP8266WebServer.h>
#include <FS.h>
#include <AutoConnect.h>
// Upload request custom Web page
static const char PAGE_UPLOAD[] PROGMEM = R"(
{
"uri": "/",
"title": "Upload",
"menu": true,
"element": [
{ "name":"caption", "type":"ACText", "value":"<h2>File uploading platform<h2>" },
{ "name":"upload_file", "type":"ACFile", "label":"Select file: ", "store":"fs" },
{ "name":"upload", "type":"ACSubmit", "value":"UPLOAD", "uri":"/upload" }
]
}
)";
// Upload result display
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", "format":"%s bytes uploaded" },
{ "name":"content_type", "type":"ACText", "format":"Content: %s" }
]
}
)";
ESP8266WebServer server;
AutoConnect portal(server);
// Declare AutoConnectAux separately as a custom web page to access
// easily for each page in the post-upload handler.
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;
AutoConnectFile& upload = auxUpload["upload_file"].as<AutoConnectFile>();
AutoConnectText& aux_filename = aux["filename"].as<AutoConnectText>();
AutoConnectText& aux_size = aux["size"].as<AutoConnectText>();
AutoConnectText& aux_contentType = aux["content_type"].as<AutoConnectText>();
// Assignment operator can be used for the element attribute.
aux_filename.value = upload.value;
aux_size.value = String(upload.size);
aux_contentType.value = upload.mimeType;
// The file saved by the AutoConnect upload handler is read from
// the EEPROM and echoed to a custom web page.
SPIFFS.begin();
File uploadFile = SPIFFS.open(String("/" + upload.value).c_str(), "r");
if (uploadFile) {
while (uploadFile.available()) {
char c = uploadFile.read();
Serial.print(c);
}
uploadFile.close();
}
else
content = "Not saved";
SPIFFS.end();
return String();
}
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();
}
```
## Where will the file upload
The AutoConnect built-in upload handler can save the upload file to three locations:
1. Flash memory embedded in the ESP8266/ESP32 module
2. SD device externally connected to the ESP8266/ESP32 module
3. Other character devices
You can specify the device type to save with the [**store**](acelements.md#store) attribute of AutoConenctFile, and it accepts the following values:
- Flash : `AC_File_FS` for the API parameter or `fs` for the JSON document
- SD : `AC_File_SD` for the API parameter or `sd` for the JSON document
- Other : `AC_File_Extern` for the API parameter or `extern` for the JSON document
The substance of AC_File_FS (fs) is a SPIFFS file system implemented by the ESP8266/ESP32 core, and then AutoConnect uses the Global Instance **SPIFFS** to access SPIFFS.
Also, the substance of AC_File_SD (sd) is a FAT file of Arduino SD library ported to the ESP8266/ESP32 core, and then AutoConnect uses the Global Instance **SD** to access SD. When saving to an external SD device, there are additional required parameters for the connection interface and is defined as the macro in AutoConnectDefs.h.
```cpp
#define AUTOCONNECT_SD_CS SS
#define AUTOCONNECT_SD_SPEED 4000000
```
`AUTOCONNECT_SD_CS` defines which GPIO for the CS (Chip Select, or SS as Slave Select) pin. This definition is derived from pins_arduino.h, which is included in the Arduino core distribution. If you want to assign the CS pin to another GPIO, you need to change the macro definition of AutoConnectDefs.h.
`AUTOCONNECT_SD_SPEED` defines SPI clock speed depending on the connected device.
!!! info "Involves both the begin() and the end()"
The built-in uploader executes the begin and end functions regardless of the sketch whence the file system of the device will terminate with the uploader termination. Therefore, to use the device in the sketch after uploading, you need to **restart it with the begin** function.
## When it will be uploaded
The below diagram shows the file uploading sequence. Upload handler will be launched by ESP8266WebServer/WebServer(ESP32) library which is triggered by receiving an HTTP stream of POST BODY including file content. Its launching occurs before invoking the page handler.
At the time of the page handler behaves, the uploaded file already saved to the device, and the [member variables](acelements.md#name_3) of AutoConnectFile reflects the file name and transfer size.
<img src="images/ac_upload_flow.svg">
## The file name for the uploaded file
AutoConnetFile saves the uploaded file with the file name you selected by `<input type="file">` tag on the browser. The file name used for uploading is stored in the AutoConnetFile's value member, which you can access after uploading. (i.e. In the handler of the destination page by the AutoConnectSubmit element.) You can not save it with a different name. It can be renamed after upload if you need to change the name.
## To upload to a device other than Flash or SD
You can output the file to any device using a custom uploader by specifying [**extern**](acjson.md#acfile) with the [store](acjson.md#acfile) attribute of AutoConnectFile (or specifying [**AC_File_Extern**](acelements.md#store) for the store member variable) and can customize the uploader according to the need to upload files to other than Flash or SD. Implements your own uploader with inheriting the [**AutoConnectUploadHandler**](#upload-handler-base-class) class which is the base class of the upload handler.
!!! note "It's not so difficult"
Implementing the custom uploader requires a little knowledge of the c++ language. If you are less attuned to programming c++, you may find it difficult. But don't worry. You can make it in various situations by just modifying the sketch skeleton that appears at the end of this page.
### <i class="fa fa-code"></i> Upload handler base class
AutoConnectUploadHandler is a base class of upload handler and It has one public member function and three protected functions.
#### <i class="fa fa-caret-right"></i> Constructor
```cpp
AutoConnectUploadHandler()
```
#### <i class="fa fa-caret-right"></i> Member functions
The **upload** public function is an entry point which will be invoked from the ESP8266WebServer (the WebServer for ESP32) library for each file content divided into chunks.
Also, the **\_open**, **\_write** and **\_close** protected functions are actually responsible for saving files and are declared as pure virtual functions. A custom uploader class that inherits from the AutoConnectUploadHandler class need to implement these functions.
```cpp
public virtual void upload(const String& requestUri, const HTTPUpload& upload)
```
<dl class="apidl">
<dt>**Parameters**</dt>
<dd><span class="apidef">requestUri</span><span class="apidesc">URI of upload request source.</span></dd>
<dd><span class="apidef">upload</span><span class="apidesc">A data structure of the upload file as <b>HTTPUpload</b>. It is defined in the ESP8266WebServer (the WebServer for ESP32) library as follows:
```cpp
typedef struct {
HTTPUploadStatus status;
String filename;
String name;
String type;
size_t totalSize;
size_t currentSize;
size_t contentLength;
uint8_t buf[HTTP_UPLOAD_BUFLEN];
} HTTPUpload;
```
</span></dd>
</dl>
The upload handler needs to implement processing based on the enumeration value of HTTPUpload.status HTTPUploadStatus enum type. HTTPUploadStatus enumeration is as follows:
- UPLOAD_FILE_START: Invokes to the \_open.
- UPLOAD_FILE_WRITE: Invokes to the \_write.
- UPLOAD_FILE_END: Invokes to the \_close.
- UPLOAD_FILE_ABORTED: Invokes to the \_close.
```cpp
protected virtual bool _open(const char* filename, const char* mode) = 0
```
The \_open function will be invoked when HTTPUploadStatus is **UPLOAD_FILE_START**. Usually, the implementation of an inherited class will usually open the file.
<dl class="apidl">
<dt>**Parameters**</dt>
<dd><span class="apidef">filename</span><span class="apidesc">Uploading file name.</span></dd>
<dd><span class="apidef">mode</span><span class="apidesc">An indicator for the file access mode, a "w" for writing.</span></dd>
<dt>**Return value**</dt>
<dd><span class="apidef">true</span><span class="apidesc">File open successful.</span></dd>
<dd><span class="apidef">false</span><span class="apidesc">Failed to open.</span></dd>
</dl>
```cpp
protected virtual size_t _write(const uint8_t *buf, size_t size))= 0
```
The \_write function will be invoked when HTTPUploadStatus is **UPLOAD_FILE_WRITE**. The content of the upload file is divided and the \_write will be invoked in multiple times. Usually, the implementation of an inherited class will write data.
<dl class="apidl">
<dt>**Parameters**</dt>
<dd><span class="apidef">buf</span><span class="apidesc">File content block.</span></dd>
<dd><span class="apidef">size</span><span class="apidesc">File block size to write.</span></dd>
<dt>**Return value**</dt>
<dd>Size written.</dd>
</dl>
```cpp
protected virtual void _close(void) = 0
```
The \_close function will be invoked when HTTPUploadStatus is **UPLOAD_FILE_END** or **UPLOAD_FILE_ABORTED**. Usually, the implementation of an inherited class will close the file.
For reference, the following AutoConnectUploadFS class is AutoConnect built-in uploader. This class implementation also inherits from AutoConnectUploadHandler.
```cpp
class AutoConnectUploadFS : public AutoConnectUploadHandler {
public:
explicit AutoConnectUploadFS(SPIFFST& media) : _media(&media) {}
~AutoConnectUploadFS() { _close(); }
protected:
bool _open(const char* filename, const char* mode) override {
if (_media->begin()) {
_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;
};
```
### <i class="fa fa-code"></i> Register custom upload handler
In order to upload a file by the custom uploader, it is necessary to register it to the custom Web page beforehand. To register a custom uploader, specify the custom uploader class for the template argument and use the [AutoConnectAux::onUpload](apiaux.md#onupload) function.
The rough structure of the sketches that completed these implementations will be as follows:
```cpp
#include <ESP8266WiFi.h>
#include <ESP8266WebServer.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":"upload_file", "type":"ACFile", "label":"Select file: ", "store":"extern" },
{ "name":"upload", "type":"ACSubmit", "value":"UPLOAD", "uri":"/upload" }
]
}
)";
static const char PAGE_RECEIVED[] PROGMEM = R"(
{
"uri": "/upload",
"title": "Upload ended",
"menu": false,
"element": [
{ "name":"caption", "type":"ACText", "value":"<h2>File uploading ended<h2>" }
]
}
)";
// Custom upload handler class
class CustomUploader : public AutoConnectUploadHandler {
public:
CustomUploader() {}
~CustomUploader() {}
protected:
bool _open(const char* filename, const char* mode) override;
size_t _write(const uint8_t *buf, size_t size) override;
void _close(void) override;
};
// _open for custom open
bool CustomUploader::_open(const char* filename, const char* mode) {
// Here, an implementation for the open file.
})
// _open for custom write
size_t CustomUploader::_write(const uint8_t *buf, size_t size) {
// Here, an implementation for the writing the file data.
}
// _open for custom close
void CustomUploader::_close(void) {
// Here, an implementation for the close file.
}
AutoConnect portal;
AutoConnectAux uploadPage;
AutoConnectAux receivePage;
CustomUploader uploader; // Declare the custom uploader
void setup() {
uploadPage.load(PAGE_UPLOAD);
receivePage.load(PAGE_RECEIVED);
portal.join({ uploadPage, receivePage });
receivePage.onUpload<CustomUploader>(uploader); // Register the custom uploader
portal.begin();
}
void loop() {
portal.handleClient();
}
```
!!! note "Don't forget to specify the store"
When using a custom uploader, remember to specify the **extern** for the store attribute of AutoConnectFile.
<script>
window.onload = function() {
Gifffer();
};
</script>

@ -184,13 +184,31 @@ Register the handler function of the AutoConnectAux.
### <i class="fa fa-caret-right"></i> onUpload
```cpp
void onUpload<T&>(T handler)
```
```cpp
void onUpload(PageBuilder::UploadFuncT uploadFunc)
```
Register the upload handler of the AutoConnectAux.
Register the upload handler of the AutoConnectAux.
<dl class="apidl">
<dt>**Parameters**</dt>
<dd><span class="apidef">uploadFunc</span><span class="apidesc">A function that behaves when request to upload with the AutoConnectAux page. UploadFuncT type is defined by the following declaration.<p class="apidesc">`void(const String&, const HTTPUpload&)`</p></span></dd>
<dd><span class="apidef">handler</span><span class="apidesc">Specifies the custom uploader inherited from [AutoConnectUploadHandler](acupload.md#upload-handler-base-class) class. Refer to the [appendix](acupload.md#to-upload-to-a-device-other-than-flash-or-sd) for details.</span></dd>
<dd><span class="apidef">uploadFunc</span><span class="apidesc">A function that behaves when request to upload with the AutoConnectAux page. UploadFuncT type is defined by the following declaration.<p class="apidesc">`void(const String&, const HTTPUpload&)`</p><p>A data structure of the upload file as HTTPUpload. It is defined in the ESP8266WebServer (the WebServer for ESP32) library as follows:
```cpp
typedef struct {
HTTPUploadStatus status;
String filename;
String name;
String type;
size_t totalSize;
size_t currentSize;
size_t contentLength;
uint8_t buf[HTTP_UPLOAD_BUFLEN];
} HTTPUpload;
```
</p>Refer to '[To upload to a device other than Flash or SD](acupload.md#to-upload-to-a-device-other-than-flash-or-sd)' in section [appendix](acupload.md) for details.</span></dd>
</dl>
### <i class="fa fa-caret-right"></i> release

@ -223,7 +223,7 @@ Specifies the save destination of the uploaded file. You can use the built-in up
- **`AC_File_FS`** : Save the uploaded file to SPIFFS in the flash.
- **`AC_File_SD`** : Save the uploaded file to SD.
- **`AC_File_Ext`** : Save the file using your own upload handler.
- **`AC_File_Extern`** : Save the file using your own upload handler.
</span></dd>
</dl>

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 120 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 179 KiB

@ -63,6 +63,8 @@ class AutoConnectAux : public PageBuilder {
void setTitle(const String& title) { _title = title; } /**< Set a title of the auxiliary page */
void on(const AuxHandlerFunctionT handler, const AutoConnectExitOrder_t order = AC_EXIT_AHEAD) { _handler = handler; _order = order; } /**< Set user handler */
void onUpload(PageBuilder::UploadFuncT uploadFunc) override { _uploadHandler = uploadFunc; }
template<typename T>
void onUpload(T& uploadClass) { _uploadHandler = std::bind(&T::upload, &uploadClass, std::placeholders::_1, std::placeholders::_2); }
#ifdef AUTOCONNECT_USE_JSON
bool load(const String& in); /**< Load whole elements to AutoConnectAux Page */

@ -35,7 +35,7 @@ typedef enum {
typedef enum {
AC_File_FS = 0,
AC_File_SD,
AC_File_Ext
AC_File_Extern
} ACFile_t; /**< AutoConnectFile media type */
/**

@ -84,7 +84,7 @@ bool AutoConnectFileBasis::attach(const ACFile_t store) {
handlerSD = new AutoConnectUploadSD(SD);
_upload.reset(reinterpret_cast<AutoConnectUploadHandler*>(handlerSD));
break;
case AC_File_Ext:
case AC_File_Extern:
break;
}
return _upload != false;

@ -175,7 +175,7 @@ bool AutoConnectFileJson::loadMember(const JsonObject& json) {
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;
store = AC_File_Extern;
else {
AC_DBG("Failed to load %s element, unknown %s\n", name.c_str(), media.c_str());
return false;
@ -202,7 +202,7 @@ void AutoConnectFileJson::serialize(JsonObject& json) {
case AC_File_SD:
json[F(AUTOCONNECT_JSON_KEY_STORE)] = AUTOCONNECT_JSON_VALUE_SD;
break;
case AC_File_Ext:
case AC_File_Extern:
json[F(AUTOCONNECT_JSON_KEY_STORE)] = AUTOCONNECT_JSON_VALUE_EXTERNAL;
break;
}

Loading…
Cancel
Save