and elements introduced. Since this section will be added at the end of the body,
27 | * it is also possible to override the style of the elements already present:
28 | * for example the background color of body will be overridden with a different color
29 | */
30 | static const char custom_css[] PROGMEM = R"EOF(
31 | pre{
32 | font-family: Monaco,Menlo,Consolas,'Courier New',monospace;
33 | color: #333;
34 | line-height: 20px;
35 | background-color: #f5f5f5;
36 | border: 1px solid rgba(0,0,0,0.15);
37 | border-radius: 6px;
38 | overflow-y: scroll;
39 | min-height: 350px;
40 | font-size: 85%;
41 | width: 95%;
42 | }
43 | )EOF";
44 |
45 |
46 | /*
47 | * Also the JavaScript will be added at the bottom of body
48 | * In this example a simple 'click' event listener will be added for the button
49 | * with id='fetch' (added as HTML). The listener will execute the function 'fetchEndpoint'
50 | * in order to fetch a remote resource and show the response in a text box.
51 | *
52 | * The instruction $('') is a "Jquery like" selector already defined
53 | * so you can use for your purposes:
54 | * var $ = function(el) {
55 | * return document.getElementById(el);
56 | * };
57 | */
58 | static const char custom_script[] PROGMEM = R"EOF(
59 | function fetchEndpoint() {
60 | var mt;
61 | document.getElementsByName('httpmethod').forEach(el => {
62 | if (el.checked)
63 | mt = el.value;
64 | })
65 |
66 | var url = $('url').value + mt.toLowerCase();
67 | var bd = (mt != 'GET') ? 'body: ""' : '';
68 | var options = {
69 | method: mt,
70 | bd
71 | };
72 | fetch(url, options)
73 | .then(response => response.text())
74 | .then(txt => {
75 | $('payload').innerHTML = txt;
76 | });
77 | }
78 |
79 | $('fetch').addEventListener('click', fetchEndpoint);
80 | )EOF";
81 |
82 |
83 | static const char base64_logo[] PROGMEM = R"EOF(
84 | iVBORw0KGgoAAAANSUhEUgAAAGAAAABgCAMAAADVRocKAAAABGdBTUEAALGPC/xhBQAAAAFzUkdC
85 | AK7OHOkAAAIKUExURQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
86 | AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
87 | AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
88 | AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
89 | AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
90 | AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
91 | AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
92 | AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
93 | AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
94 | AAAAAAAAAAAAAAAAAAAAAAAAAAAAAIhNjGoAAACtdFJOUwBl/fwyM2b+Bfn7mRAHBgEa+veYfx4C
95 | PNsD6Lbr3T49T6zLhOYOzN7uWmx671yOjR0PU1EJTtTzaj8x9hP1wFDwfrg5gC1fIXeTYrIEVQry
96 | +I+crg3XFoE1lFnR07HShyVdbW7f1cdo5aPxq8n0cO1xxJsRW2kM52QYwRkLYa0v4LNYb2BEUpd5
97 | 1siCFIaVpbAm7FbkiJKmF2ODxmfqmldJNn0gG0rCRtpNxUVMXLiLfgAABc5JREFUaN7tWflXE0kQ
98 | bifAQAgJBAiBhAQMEATCfYsKoigIKIKioogHgnjggfe967Guq3vf933O/7gz1T0zfUyGwA4/7Hup
99 | N/BSNT319VfTXd1TjVBa0pKWtPx/xHtkYTh0vGyrJG0tOx4aXvjR66T34jM7uxRFZq5LO88Ur91T
100 | fZFoyzu13cV7Vy/1n2v7qTyxfVF9cv/dsryLM1W/UybbSFl9Ne9flruT+e9wKRxC9tthse8GA7jC
101 | l7NZ/4ri7rD2v3u/9hiNUNKpKKsBKPJ8lOm/atm/28q/J4hZN+TrwW9yySmJ1Ki/ivwGbAl6LAAq
102 | cb+2lRB9Tw10kO24VFAg8QzUq2aPznkbtlwQ/Y+AL9nVRvRIWGYA3Ocahwabb3q9N5sHh5q+LmAD
103 | Fo6Qx9rIiBsRhvoSJreX6K1+Ogg5J461sO1b3vogh27hbyU39mK9n58kT3BnK4m6xa33Tv2rjeVb
104 | vbT8r2opEu4txPwQW/axjStk8HWWvJwdZgQU/0lPsnHtOek3o1SwgxjP4ndUwWQaPIJcPU4klh48
105 | +IJ0tjqNO3vUmdR1FHOqMy3tVWC6Ve0MQMstAKhqNyx1+M0/cCr7PsD+DAql5fBaZkudAiidBYfl
106 | usMKPAxanVtAWvHQ0gdSCAjNB5wDCMyDyxCZMC5gkEUytCg+/blR2qry91k3UyULGLjxBL0CSv99
107 | 7P9SjiC9cKd5IuinrW8Q6mWahff9agDc74dOXwHlL6BTSAjkiOk4Q7sx1sVZ1Qcy+ZafG0toIehX
108 | tZ+HcdqJGgBshlb/NIDf3fyCk6sCCOvQ+zpABCeow1peg3tVAVuA7LiSEoD8gz5Sq8CiZcCDQOYg
109 | sg3Rn6LVKkSybCRR7Paa+usQQH2MbBm8llNjoHykA3wP6iGErrtgKbzDApzPouQ97Z1p1se0NWoA
110 | xEA/D79zRomje6C6rqMeoBJHbIg2cTMnF6ybOWsmNcpQFl77jE1MHPQI6oZeVCKWgQigWQUAzEAH
111 | AAYGwEMI2g00AUDTzjOYBr0RTUIvEs4zSACDSXQRGkWdB4gAwLvkXcxxIYplUDKuz/yXWH/kYUO0
112 | AtYYF6IPQS9HeOvRh+znQRO7v/vtGc2AvpaMnNoHlmV0AG7krQKQ4KzSqyQAd83vCrAcQBIw8SH7
113 | VDFVyxuzkGWq+NIA8OGNMcJ7Wd8qDLRXyN6onbJicHHUBMBUUwwRQq8kLhetWADM3ENCiJaBSd8q
114 | IVLl2V2JsTYJIfrkp3ZqDPeBbRnFAXyOYxDLpGRc34o+wvpLYFBoMFgBa8bPPmaSzAGD8hQnGiM1
115 | XLrOsGylT7RJYJJYJRcxsplbcKwBEnBvEk1AL3Y5z2AaGDSiGyml63UAVJJ03UNShuMhKicLDlky
116 | v+CWzE2UqEtmlNYfc6PIEuCOBEumR1/0x+wnWq646L+2BxjTF32yv/jOfqLlitZ/7EN0zdi24I3X
117 | YmCNDOLZtgwCi8bGi2wdS9YG4P4b2QKUmFtHdBXIvFhTiLrGhEWflRfm5pds3xvG7bbvb2jd/8dE
118 | MzTupbf3jIw3AIPL+AMEb5xv232AlNK6kfN94pcHkdv0BwgKAUCnk59QnQAQMusImrQ5B/Ace6xg
119 | P2NnHKMQmGE/Y9UPcWD01CmAp3j41lGlBGBU5VApoRq7W2wXiiHDG1UMQd4gALgizpRzAIAp5+gD
120 | KW4WpMxi2ad2BSmzHVWQAumwLakdY0tqv1iW1GKWJbVKy5IaKl7CVUe9KPjcr1BVR6ui4AkmLZpF
121 | QWwRioJoBBMzyprRMJvecFlzyuudKh4cajxXwN79NmqUNbFhRGR8YYMLs8gTxI8xpeVUateK1GSW
122 | lrHFsrSsFcc1KaKL46mUrjtLzCcGwGJdHEeoQ83bctF/K+8P2JT34YCiSDygsAMQDygGlOQHFEmO
123 | WBLbk5T5rY9YBurXcUh0Wjwk6lrXIZHdMdc3n+nHXLOh4YUjjh5zpSUtaUnLBsu/XvNl5HUuawwA
124 | AAAASUVORK5CYII=
125 | )EOF";
--------------------------------------------------------------------------------
/examples/customHTML/thingsboard.h:
--------------------------------------------------------------------------------
1 |
2 |
3 | static const char thingsboard_htm[] PROGMEM = R"EOF(
4 |
5 |
If you don't have a valid
device token press button "Device Provisioning" to start procedure in order to get a new token from ThingsBoard server.
6 |
To perform
device provisioning , this functionality must be enabled in the ThingsBoard profile of your devices.
7 |
17 |
18 | )EOF";
19 |
20 |
21 | static const char thingsboard_script[] PROGMEM = R"EOF(
22 | const TB_DEVICE_NAME = 'Device Name';
23 | const TB_DEVICE_LAT = 'Device Latitude';
24 | const TB_DEVICE_LON = 'Device Longitude';
25 | const TB_SERVER = 'ThingsBoard server address';
26 | const TB_PORT = 'ThingsBoard server port';
27 | const TB_DEVICE_TOKEN = 'ThingsBoard device token';
28 | const TB_DEVICE_KEY = 'Provisioning device key';
29 | const TB_SECRET_KEY = 'Provisioning secret key';
30 |
31 | function getUrl(host, port) {
32 | var url;
33 | if (port === '80')
34 | url = 'http://' + host + '/api/v1/';
35 | else if (port === '443')
36 | url = 'https://' + host + '/api/v1/'
37 | else
38 | url = 'http://' + host + ':'+ port + '/api/v1/'
39 | return url;
40 | }
41 |
42 | function deviceProvisioning() {
43 | console.log('Device provisioning');
44 | var server = $(TB_SERVER).value;
45 | var port = $(TB_PORT).value;
46 | var token = $(TB_DEVICE_TOKEN).value;
47 | if (token === '')
48 | token = 'xxxxx';
49 | const url = 'https://corsproxy.io/?' + encodeURIComponent(getUrl(server, port) + token + '/attributes');
50 | fetch(url, {
51 | method: "GET"
52 | })
53 | .then((response) => {
54 | if (response.ok) {
55 | openModalMessage('Device provisioning', 'Device already provisioned. Do you want update device client attributes?', setDeviceClientAttribute);
56 | return;
57 | }
58 | throw new Error('Device token not present. Provisioning new device');
59 | })
60 | .catch((error) => {
61 | openModalMessage('New device', 'A new device will be provisioned on ThingsBoard server', createNewDevice);
62 | });
63 | }
64 |
65 | function createNewDevice() {
66 | var server = $(TB_SERVER).value;
67 | var port = $(TB_PORT).value;
68 | var key = $(TB_DEVICE_KEY).value;
69 | var secret = $(TB_SECRET_KEY).value;
70 | var name = $(TB_DEVICE_NAME).value;
71 | const url = 'https://corsproxy.io/?' + encodeURIComponent(getUrl(server, port) + 'provision');
72 | var payload = {
73 | 'deviceName': name,
74 | 'provisionDeviceKey': key,
75 | 'provisionDeviceSecret': secret
76 | };
77 | fetch(url, {
78 | headers: {
79 | 'Accept': 'application/json',
80 | 'Content-Type': 'application/json'
81 | },
82 | method: 'POST',
83 | body: JSON.stringify(payload)
84 | })
85 | .then(response => response.json())
86 | .then(obj => {
87 | var token = $(TB_DEVICE_TOKEN);
88 | token.focus();
89 | token.value = obj.credentialsValue;
90 | options[token.id] = token.value; // Manual update, because, it doesn't fire "change" event...
91 | openModal('Write device attributes', 'Device provisioned correctly. Do you want to set client attributes on ThingsBoard server?', setDeviceClientAttribute);
92 | });
93 | }
94 |
95 |
96 | function setDeviceClientAttribute(){
97 | var server = $(TB_SERVER).value;
98 | var port = $(TB_PORT).value;
99 | var token = $(TB_DEVICE_TOKEN).value;
100 | var name = $(TB_DEVICE_NAME).value;
101 | var latitude = $(TB_DEVICE_LAT).value;
102 | var longitude = $(TB_DEVICE_LON).value;
103 | const url = 'https://corsproxy.io/?' + encodeURIComponent(getUrl(server, port) + token + '/attributes');
104 | var payload = {
105 | 'DeviceName': name,
106 | 'latitude': latitude,
107 | 'longitude': longitude,
108 | };
109 | fetch(url, {
110 | method: 'POST',
111 | body: JSON.stringify(payload),
112 | })
113 | .then(response => response.text())
114 | .then(() => {
115 | openModalMessage('Device properties', 'Device client properties updated!');
116 | });
117 | }
118 | $('device-provisioning').addEventListener('click', deviceProvisioning);
119 | )EOF";
120 |
--------------------------------------------------------------------------------
/examples/customOptions/.vscode/arduino.json:
--------------------------------------------------------------------------------
1 | {
2 | "configuration": "JTAGAdapter=default,PSRAM=disabled,FlashMode=qio,FlashSize=4M,LoopCore=1,EventsCore=1,USBMode=hwcdc,CDCOnBoot=default,MSCOnBoot=default,DFUOnBoot=default,UploadMode=default,PartitionScheme=default,CPUFreq=240,UploadSpeed=921600,DebugLevel=none,EraseFlash=none,ZigbeeMode=default",
3 | "board": "esp32:esp32:esp32s3",
4 | "sketch": "customOptions.ino",
5 | "port": "COM12"
6 | }
--------------------------------------------------------------------------------
/examples/customOptions/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "files.associations": {
3 | "string": "cpp",
4 | "*.tcc": "cpp",
5 | "fstream": "cpp",
6 | "bitset": "cpp",
7 | "vector": "cpp",
8 | "memory": "cpp",
9 | "string_view": "cpp",
10 | "system_error": "cpp",
11 | "array": "cpp",
12 | "atomic": "cpp",
13 | "cctype": "cpp",
14 | "clocale": "cpp",
15 | "cmath": "cpp",
16 | "cstdarg": "cpp",
17 | "cstddef": "cpp",
18 | "cstdint": "cpp",
19 | "cstdio": "cpp",
20 | "cstdlib": "cpp",
21 | "cstring": "cpp",
22 | "ctime": "cpp",
23 | "cwchar": "cpp",
24 | "cwctype": "cpp",
25 | "deque": "cpp",
26 | "unordered_map": "cpp",
27 | "exception": "cpp",
28 | "algorithm": "cpp",
29 | "functional": "cpp",
30 | "iterator": "cpp",
31 | "map": "cpp",
32 | "memory_resource": "cpp",
33 | "numeric": "cpp",
34 | "optional": "cpp",
35 | "random": "cpp",
36 | "regex": "cpp",
37 | "tuple": "cpp",
38 | "type_traits": "cpp",
39 | "utility": "cpp",
40 | "initializer_list": "cpp",
41 | "iosfwd": "cpp",
42 | "istream": "cpp",
43 | "limits": "cpp",
44 | "new": "cpp",
45 | "ostream": "cpp",
46 | "sstream": "cpp",
47 | "stdexcept": "cpp",
48 | "streambuf": "cpp",
49 | "cinttypes": "cpp",
50 | "typeinfo": "cpp",
51 | "condition_variable": "cpp",
52 | "bit": "cpp",
53 | "compare": "cpp",
54 | "concepts": "cpp",
55 | "list": "cpp",
56 | "ratio": "cpp",
57 | "mutex": "cpp",
58 | "numbers": "cpp",
59 | "semaphore": "cpp",
60 | "stop_token": "cpp",
61 | "thread": "cpp"
62 | },
63 | "cmake.configureOnOpen": false
64 | }
--------------------------------------------------------------------------------
/examples/customOptions/customOptions.ino:
--------------------------------------------------------------------------------
1 | #include
2 | #include
3 | #include //https://github.com/cotestatnt/async-esp-fs-webserver
4 |
5 | #define FILESYSTEM LittleFS
6 | bool captiveRun = false;
7 |
8 | // AsyncFsWebServer server(80, FILESYSTEM, "esphost");
9 | AsyncFsWebServer server(80, FILESYSTEM);
10 |
11 | #ifndef LED_BUILTIN
12 | #define LED_BUILTIN 2
13 | #endif
14 |
15 | #define BTN_SAVE 5
16 |
17 | // Test "options" values
18 | uint8_t ledPin = LED_BUILTIN;
19 | bool boolVar = true;
20 | uint32_t longVar = 1234567890;
21 | float floatVar = 15.5F;
22 | String stringVar = "Test option String";
23 |
24 | // In order to show a dropdown list box in /setup page
25 | // we need a list of values and a variable to store the selected option
26 | #define LIST_SIZE 7
27 | const char* dropdownList[LIST_SIZE] =
28 | {"Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"};
29 | String dropdownSelected;
30 |
31 | // Var labels (in /setup webpage)
32 | #define LED_LABEL "The LED pin number"
33 | #define BOOL_LABEL "A bool variable"
34 | #define LONG_LABEL "A long variable"
35 | #define FLOAT_LABEL "A float variable"
36 | #define STRING_LABEL "A String variable"
37 | #define DROPDOWN_LABEL "A dropdown listbox"
38 |
39 | // Timezone definition to get properly time from NTP server
40 | #define MYTZ "CET-1CEST,M3.5.0,M10.5.0/3"
41 | struct tm Time;
42 |
43 | static const char save_btn_htm[] PROGMEM = R"EOF(
44 |
47 | )EOF";
48 |
49 | static const char button_script[] PROGMEM = R"EOF(
50 | /* Add click listener to button */
51 | document.getElementById('reload-btn').addEventListener('click', reload);
52 | function reload() {
53 | console.log('Reload configuration options');
54 | fetch('/reload')
55 | .then((response) => {
56 | if (response.ok) {
57 | openModal('Options loaded', 'Options was reloaded from configuration file');
58 | return;
59 | }
60 | throw new Error('Something goes wrong with fetch');
61 | })
62 | .catch((error) => {
63 | openModal('Error', 'Something goes wrong with your request');
64 | });
65 | }
66 | )EOF";
67 |
68 |
69 | //////////////////////////////// Filesystem /////////////////////////////////////////
70 | bool startFilesystem() {
71 | if (FILESYSTEM.begin()){
72 | server.printFileList(FILESYSTEM, "/", 2);
73 | return true;
74 | }
75 | else {
76 | Serial.println("ERROR on mounting filesystem. It will be reformatted!");
77 | FILESYSTEM.format();
78 | ESP.restart();
79 | }
80 | return false;
81 | }
82 |
83 | /*
84 | * Getting FS info (total and free bytes) is strictly related to
85 | * filesystem library used (LittleFS, FFat, SPIFFS etc etc) and ESP framework
86 | */
87 | #ifdef ESP32
88 | void getFsInfo(fsInfo_t* fsInfo) {
89 | fsInfo->fsName = "LittleFS";
90 | fsInfo->totalBytes = LittleFS.totalBytes();
91 | fsInfo->usedBytes = LittleFS.usedBytes();
92 | }
93 | #endif
94 |
95 |
96 | //////////////////// Load application options from filesystem ////////////////////
97 | bool loadOptions() {
98 | if (FILESYSTEM.exists(server.getConfiFileName())) {
99 | server.getOptionValue(LED_LABEL, ledPin);
100 | server.getOptionValue(BOOL_LABEL, boolVar);
101 | server.getOptionValue(LONG_LABEL, longVar);
102 | server.getOptionValue(FLOAT_LABEL, floatVar);
103 | server.getOptionValue(STRING_LABEL, stringVar);
104 | server.getOptionValue(DROPDOWN_LABEL, dropdownSelected);
105 |
106 | Serial.println("\nThis are the current values stored: \n");
107 | Serial.printf("LED pin value: %d\n", ledPin);
108 | Serial.printf("Bool value: %s\n", boolVar ? "true" : "false");
109 | Serial.printf("Long value: %u\n", longVar);
110 | Serial.printf("Float value: %d.%d\n", (int)floatVar, (int)(floatVar*1000)%1000);
111 | Serial.printf("String value: %s\n", stringVar.c_str());
112 | Serial.printf("Dropdown selected value: %s\n\n", dropdownSelected.c_str());
113 | return true;
114 | }
115 | else
116 | Serial.println(F("Config file not exist"));
117 | return false;
118 | }
119 |
120 | void saveOptions() {
121 | // server.saveOptionValue(LED_LABEL, ledPin);
122 | // server.saveOptionValue(BOOL_LABEL, boolVar);
123 | // server.saveOptionValue(LONG_LABEL, longVar);
124 | // server.saveOptionValue(FLOAT_LABEL, floatVar);
125 | // server.saveOptionValue(STRING_LABEL, stringVar);
126 | // server.saveOptionValue(DROPDOWN_LABEL, dropdownSelected);
127 | Serial.println(F("Application options saved."));
128 | }
129 |
130 | //////////////////////////// HTTP Request Handlers ////////////////////////////////////
131 | void handleLoadOptions(AsyncWebServerRequest *request) {
132 | request->send(200, "text/plain", "Options loaded");
133 | loadOptions();
134 | Serial.println("Application option loaded after web request");
135 | }
136 |
137 |
138 | void setup() {
139 | Serial.begin(115200);
140 | pinMode(BTN_SAVE, INPUT_PULLUP);
141 |
142 | // FILESYSTEM INIT
143 | if (startFilesystem()){
144 | // Load configuration (if not present, default will be created when webserver will start)
145 | if (loadOptions())
146 | Serial.println(F("Application option loaded"));
147 | else
148 | Serial.println(F("Application options NOT loaded!"));
149 | }
150 |
151 | // Try to connect to WiFi (will start AP if not connected after timeout)
152 | if (!server.startWiFi(10000)) {
153 | Serial.println("\nWiFi not connected! Starting AP mode...");
154 | server.startCaptivePortal("ESP_AP", "123456789", "/setup");
155 | captiveRun = true;
156 | }
157 |
158 | // Add custom page handlers to webserver
159 | server.on("/reload", HTTP_GET, handleLoadOptions);
160 |
161 | // Configure /setup page and start Web Server
162 | server.addOptionBox("My Options");
163 |
164 | server.addOption(BOOL_LABEL, boolVar);
165 | server.addOption(LED_LABEL, ledPin);
166 | server.addOption(LONG_LABEL, longVar);
167 | server.addOption(FLOAT_LABEL, floatVar, 1.0, 100.0, 0.01);
168 | server.addOption(STRING_LABEL, stringVar);
169 | server.addDropdownList(DROPDOWN_LABEL, dropdownList, LIST_SIZE);
170 |
171 | server.addHTML(save_btn_htm, "buttons", /*overwrite*/ false);
172 | server.addJavascript(button_script, "js", /*overwrite*/ false);
173 |
174 | // Enable ACE FS file web editor and add FS info callback function
175 | server.enableFsCodeEditor();
176 | #ifdef ESP32
177 | server.setFsInfoCallback(getFsInfo);
178 | #endif
179 |
180 | // set /setup and /edit page authentication
181 | server.setAuthentication("admin", "admin");
182 |
183 | // Start server
184 | server.init();
185 | Serial.print(F("\nESP Web Server started on IP Address: "));
186 | Serial.println(server.getServerIP());
187 | Serial.println(F(
188 | "This is \"customOptions.ino\" example.\n"
189 | "Open /setup page to configure optional parameters.\n"
190 | "Open /edit page to view, edit or upload example or your custom webserver source files."
191 | ));
192 | }
193 |
194 | void loop() {
195 | if (captiveRun)
196 | server.updateDNS();
197 |
198 | // Savew options also on button click
199 | if (!digitalRead(BTN_SAVE)) {
200 | saveOptions();
201 | delay(1000);
202 | }
203 |
204 | // This delay is required in order to avoid loopTask() WDT reset on ESP32
205 | delay(1);
206 | }
207 |
--------------------------------------------------------------------------------
/examples/esp32-cam/data/index.htm:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
9 | Page Redirection
10 |
11 |
12 |
13 | If you are not redirected automatically, follow this link .
14 |
15 |
--------------------------------------------------------------------------------
/examples/esp32-cam/data/www/app.js:
--------------------------------------------------------------------------------
1 | const imgFolder = '/img/';
2 | var fileList = document.getElementById('file-list');
3 | var intervalID;
4 |
5 | // Load list of files every x milliseconds (used in order to get fresh list)
6 | var myInterval = function(val){
7 | if (val !== 0)
8 | intervalID = setInterval(listFiles, val*1000 + 1000);
9 | else
10 | clearInterval(intervalID);
11 | };
12 |
13 | // Fetch the list of files and fill the filelist
14 | function listFiles() {
15 | fetch('/list?dir=/img') // Do the request
16 | .then(response => response.json()) // Parse the response
17 | .then(obj => { // DO something with response
18 | fileList.innerHTML = '';
19 | obj.forEach(function(entry, i) {
20 | addEntry(entry.name);
21 | });
22 | });
23 | }
24 |
25 | // Load selected image inside the preview content
26 | function loadFile(filename) {
27 | clearInterval(intervalID);
28 | var name = document.getElementById("image-name");
29 | var content = document.getElementById("image-content");
30 |
31 | name.innerHTML = 'Image path: ' + filename;
32 | content.src = filename;
33 | content.alt = 'SD: ' + filename;
34 |
35 | // Get the modal
36 | var modal = document.getElementById("modal-container");
37 |
38 | // Get the image and insert it inside the modal - use its "alt" text as a caption
39 | var img = document.getElementById("image-content");
40 | var modalImg = document.getElementById("modal-image");
41 | var captionText = document.getElementById("caption");
42 | img.onclick = function(){
43 | modal.style.display = "block";
44 | modalImg.src = this.src;
45 | captionText.innerHTML = this.alt;
46 | };
47 |
48 | // Get the element that closes the modal
49 | var span = document.getElementsByClassName("close")[0];
50 |
51 | // When the user clicks on (x), close the modal
52 | span.onclick = function() {
53 | modal.style.display = "none";
54 | };
55 | }
56 |
57 | // Delete selected file in SD
58 | async function deleteFile(filename) {
59 | console.log(filename);
60 | const data = new URLSearchParams();
61 | data.append('path', filename);
62 | fetch('/edit', {
63 | method: 'DELETE',
64 | body: data
65 | });
66 |
67 | // Update the file browser.
68 | listFiles();
69 | }
70 |
71 | async function deleteAll() {
72 | let isExecuted = confirm("Are you sure to delete all files in /img/ folder?");
73 | if(isExecuted){
74 | var ul = document.getElementById("file-list");
75 | var items = ul.getElementsByClassName("edit-file");
76 |
77 | for (var i=0; i response.text()) // Parse the response
121 | .then(txt => { // DO something with response
122 | console.log(txt);
123 | addEntry(txt);
124 | loadFile(imgFolder + txt);
125 | });
126 | });
127 |
128 | getInterval.addEventListener('click', function(e) {
129 | e.preventDefault();
130 | let val = document.getElementById('interval').value;
131 |
132 | fetch('/setInterval?val='+ val) // Do the request
133 | .then(response => response.text()) // Parse the response
134 | .then(txt => { // DO something with response
135 | console.log('Set interval ' + txt);
136 | myInterval(val);
137 | });
138 | });
139 |
140 | // Start the web page
141 | clearInterval(intervalID);
142 | listFiles();
143 | loadFile('espressif.jpg');
--------------------------------------------------------------------------------
/examples/esp32-cam/data/www/espressif.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cotestatnt/async-esp-fs-webserver/f6303024b51f3086c6364932fa6447190c8ac852/examples/esp32-cam/data/www/espressif.jpg
--------------------------------------------------------------------------------
/examples/esp32-cam/data/www/index.htm:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | ESP32-CAM-WEBPAGE.ino
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
ESP32-CAM web interface
15 |
16 |
37 |
38 |
39 |
Select image
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
--------------------------------------------------------------------------------
/examples/esp32-cam/data/www/styles.css:
--------------------------------------------------------------------------------
1 | *, *:before, *:after {
2 | -moz-box-sizing: border-box;
3 | -webkit-box-sizing: border-box;
4 | box-sizing: border-box;
5 | }
6 |
7 | html {
8 | font-family: Helvetica, Arial, sans-serif;
9 | font-size: 100%;
10 | background: #333;
11 | color: #33383D;
12 | -webkit-font-smoothing: antialiased;
13 | }
14 |
15 | #page-wrapper {
16 | width: 960px;
17 | background: #FFF;
18 | padding: 1.25rem;
19 | margin: 1rem auto;
20 | min-height: 300px;
21 | border-top: 5px solid #69c773;
22 | box-shadow: 0 2px 10px rgba(0,0,0,0.8);
23 | }
24 |
25 |
26 | h2 {
27 | margin-top: 0;
28 | font-size: 0.9rem;
29 | letter-spacing: 1px;
30 | color: #999;
31 | }
32 |
33 | p {
34 | font-size: 0.9rem;
35 | margin: 0.5rem 0 1.5rem 0;
36 | }
37 |
38 | a,
39 | a:visited {
40 | color: #08C;
41 | text-decoration: none;
42 | }
43 |
44 | a:hover,
45 | a:focus {
46 | color: #69c773;
47 | cursor: pointer;
48 | }
49 |
50 | a.delete-file,
51 | a.delete-file:visited {
52 | color: #CC0000;
53 | margin-left: 0.5rem;
54 | vertical-align: middle;
55 | }
56 |
57 |
58 |
59 | .field {
60 | margin-bottom: 1rem;
61 | }
62 |
63 | button {
64 | width: 150px;
65 | display: inline-block;
66 | border-radius: 3px;
67 | border: none;
68 | font-size: 0.9rem;
69 | padding: 0.6rem 1em;
70 | background: #86b32d;
71 | border-bottom: 1px solid #5d7d1f;
72 | color: white;
73 | margin: 0 0.25rem;
74 | text-align: center;
75 | }
76 |
77 | button:hover {
78 | opacity: 0.75;
79 | cursor: pointer;
80 | }
81 |
82 | #file-form {
83 | width: 75%;
84 | float: left;
85 | }
86 |
87 | #files {
88 | width: 23%;
89 | float: right;
90 | }
91 |
92 | #files ul {
93 | margin: 0;
94 | padding: 0.5rem 1rem;
95 | max-height: 600px;
96 | overflow-y: auto;
97 | list-style: square;
98 | background: #F7F7F7;
99 | border: 1px solid #D9D9D9;
100 | border-radius: 3px;
101 | box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.1);
102 | }
103 |
104 | #files li {
105 | margin-left: 8px;
106 | font-size: 14px;
107 | }
108 |
109 |
110 | /* Clearfix Utils */
111 |
112 | .clearfix {
113 | *zoom: 1;
114 | }
115 |
116 | .clearfix:before,
117 | .clearfix:after {
118 | display: table;
119 | line-height: 0;
120 | content: "";
121 | }
122 |
123 | .clearfix:after {
124 | clear: both;
125 | }
126 |
127 | #image-content {
128 | border-radius: 5px;
129 | cursor: pointer;
130 | transition: 0.3s;
131 | }
132 |
133 | #image-content:hover {opacity: 0.7;}
134 |
135 | /* The Modal (background) */
136 | .modal {
137 | display: none; /* Hidden by default */
138 | position: fixed; /* Stay in place */
139 | z-index: 1; /* Sit on top */
140 | padding-top: 100px; /* Location of the box */
141 | left: 0;
142 | top: 0;
143 | width: 100%; /* Full width */
144 | height: 100%; /* Full height */
145 | overflow: auto; /* Enable scroll if needed */
146 | background-color: rgb(0,0,0); /* Fallback color */
147 | background-color: rgba(0,0,0,0.9); /* Black w/ opacity */
148 | }
149 |
150 |
151 | /* Modal Content (image) */
152 | .modal-content {
153 | margin: auto;
154 | display: block;
155 | width: 90%;
156 | }
157 |
158 | /* Caption of Modal Image */
159 | #caption {
160 | margin: auto;
161 | display: block;
162 | text-align: center;
163 | color: #ccc;
164 | padding: 10px 0;
165 | }
166 |
167 | /* Add Animation */
168 | .modal-content, #caption {
169 | -webkit-animation-name: zoom;
170 | -webkit-animation-duration: 0.6s;
171 | animation-name: zoom;
172 | animation-duration: 0.6s;
173 | }
174 |
175 | @-webkit-keyframes zoom {
176 | from {-webkit-transform:scale(0)}
177 | to {-webkit-transform:scale(1)}
178 | }
179 |
180 | @keyframes zoom {
181 | from {transform:scale(0)}
182 | to {transform:scale(1)}
183 | }
184 |
185 | /* The Close Button */
186 | .close {
187 | position: absolute;
188 | top: 15px;
189 | right: 35px;
190 | color: #f1f1f1;
191 | font-size: 40px;
192 | font-weight: bold;
193 | transition: 0.3s;
194 | }
195 |
196 | .close:hover,
197 | .close:focus {
198 | color: #bbb;
199 | text-decoration: none;
200 | cursor: pointer;
201 | }
202 |
203 | .delete {
204 | font-size: 30px;
205 | font-weight: bold;
206 | transition: 0.3s;
207 | }
208 |
209 | .delete-all{
210 | color: #f44336;
211 | font-size: 12px;
212 | background-color: transparent;
213 | background-repeat: no-repeat;
214 | border: none;
215 | cursor: pointer;
216 | overflow: hidden;
217 | outline: none;
218 | }
219 | /* 100% Image Width on Smaller Screens */
220 | @media only screen and (max-width: 700px){
221 | .modal-content {
222 | width: 100%;
223 | }
224 | }
225 |
--------------------------------------------------------------------------------
/examples/esp32-cam/esp32-cam.ino:
--------------------------------------------------------------------------------
1 |
2 | #include
3 | #include
4 | #include // https://github.com/cotestatnt/async-esp-fs-webserver/
5 | #include "esp_camera.h"
6 | #include "soc/soc.h" // Brownout error fix
7 | #include "soc/rtc_cntl_reg.h" // Brownout error fix
8 |
9 | #if ESP_ARDUINO_VERSION_MAJOR >= 3
10 | #include "soc/soc_caps.h"
11 | #endif
12 |
13 | #define FILESYSTEM SD_MMC
14 | AsyncFsWebServer server(80, FILESYSTEM);
15 |
16 | // Local include files
17 | #include "camera_pins.h"
18 |
19 | uint16_t grabInterval = 0; // Grab a picture every x seconds
20 | uint32_t lastGrabTime = 0;
21 |
22 | // Timezone definition to get properly time from NTP server
23 | #define MYTZ "CET-1CEST,M3.5.0,M10.5.0/3"
24 |
25 | // Struct for saving time datas (needed for time-naming the image files)
26 | struct tm tInfo;
27 |
28 | // Functions prototype
29 | void listDir(const char *, uint8_t);
30 | void setLamp(int);
31 |
32 | // Grab a picture from CAM and store on SD or in flash
33 | void getPicture(AsyncWebServerRequest *);
34 | const char* getFolder = "/img";
35 |
36 | /////////////////////////////////// SETUP ///////////////////////////////////////
37 | void setup() {
38 | WRITE_PERI_REG(RTC_CNTL_BROWN_OUT_REG, 0); // disable brownout detect
39 |
40 | // Flash LED setup
41 | pinMode(LAMP_PIN, OUTPUT); // set the lamp pin as output
42 | #if ESP_ARDUINO_VERSION_MAJOR >= 3
43 | ledcAttach(LAMP_PIN, 1000, 8);
44 | #else
45 | ledcSetup(lampChannel, pwmfreq, pwmresolution); // configure LED PWM channel
46 | ledcAttachPin(LAMP_PIN, lampChannel);
47 | #endif
48 | setLamp(0); // set default value
49 |
50 | Serial.begin(115200);
51 | Serial.println();
52 |
53 | // Try to connect to WiFi (will start AP if not connected after timeout)
54 | if (!server.startWiFi(10000)) {
55 | Serial.println("\nWiFi not connected! Starting AP mode...");
56 | server.startCaptivePortal("ESP32CAM_AP", "123456789", "/setup");
57 | }
58 |
59 | // Sync time with NTP
60 | configTzTime(MYTZ, "time.google.com", "time.windows.com", "pool.ntp.org");
61 |
62 | /*
63 | Init onboard SD filesystem (format if necessary)
64 | SD_MMC.begin(const char * mountpoint, bool mode1bit, bool format_if_mount_failed, int sdmmc_frequency, uint8_t maxOpenFiles)
65 | To avoid led glowing, set mode1bit = true (SD HS_DATA1 is tied to GPIO4, the same of on-board flash led)
66 | */
67 | if (!SD_MMC.begin("/sdcard", true, true, SDMMC_FREQ_HIGHSPEED, 5)) {
68 | Serial.println("\nSD Mount Failed.\n");
69 | }
70 |
71 | if (!SD_MMC.exists(getFolder)) {
72 | if(SD_MMC.mkdir(getFolder))
73 | Serial.println("Dir created");
74 | else
75 | Serial.println("mkdir failed");
76 | }
77 | listDir(getFolder, 1);
78 |
79 | // Enable ACE FS file web editor and add FS info callback function
80 | server.enableFsCodeEditor();
81 | server.setFsInfoCallback([](fsInfo_t* fsInfo) {
82 | fsInfo->totalBytes = SD_MMC.totalBytes();
83 | fsInfo->usedBytes = SD_MMC.usedBytes();
84 | });
85 |
86 | // Add custom handlers to webserver
87 | server.on("/getPicture", getPicture);
88 | server.on("/setInterval", setInterval);
89 |
90 | // Start server with built-in websocket event handler
91 | server.init();
92 | Serial.print(F("\nESP Web Server started on IP Address: "));
93 | Serial.println(server.getServerIP());
94 | Serial.println(F(
95 | "This is \"remoteOTA.ino\" example.\n"
96 | "Open /setup page to configure optional parameters.\n"
97 | "Open /edit page to view, edit or upload example or your custom webserver source files."
98 | ));
99 |
100 | // Init the camera module (according the camera_config_t defined)
101 | init_camera();
102 | }
103 |
104 | /////////////////////////////////// LOOP ///////////////////////////////////////
105 | void loop() {
106 | if (grabInterval) {
107 | if (millis() - lastGrabTime > grabInterval *1000) {
108 | lastGrabTime = millis();
109 | getPicture(nullptr);
110 | }
111 | }
112 | }
113 |
114 | ////////////////////////////////// FUNCTIONS//////////////////////////////////////
115 | void setInterval(AsyncWebServerRequest *request) {
116 | if (request->hasArg("val")) {
117 | grabInterval = request->arg("val").toInt();
118 | Serial.printf("Set grab interval every %d seconds\n", grabInterval);
119 | }
120 | request->send(200, "text/plain", "OK");
121 | }
122 |
123 | // Lamp Control
124 | void setLamp(int newVal) {
125 | if (newVal != -1) {
126 | // Apply a logarithmic function to the scale.
127 | int brightness = round((pow(2, (1 + (newVal * 0.02))) - 2) / 6 * pwmMax);
128 | ledcWrite(lampChannel, brightness);
129 | Serial.print("Lamp: ");
130 | Serial.print(newVal);
131 | Serial.print("%, pwm = ");
132 | Serial.println(brightness);
133 | }
134 | }
135 |
136 | // Send a picture taken from CAM to a Telegram chat
137 | void getPicture(AsyncWebServerRequest *request) {
138 |
139 | // Take Picture with Camera;
140 | Serial.println("Camera capture requested");
141 |
142 | // Take Picture with Camera and store in ram buffer fb
143 | setLamp(100);
144 | delay(100);
145 | camera_fb_t *fb = esp_camera_fb_get();
146 | setLamp(0);
147 |
148 | if (!fb) {
149 | Serial.println("Camera capture failed");
150 | if (request != nullptr)
151 | request->send(500, "text/plain", "ERROR. Image grab failed");
152 | return;
153 | }
154 |
155 | // Keep files on SD memory, filename is time based (YYYYMMDD_HHMMSS.jpg)
156 | // Embedded filesystem is too small to keep all images, overwrite the same file
157 | char filename[20];
158 | time_t now = time(nullptr);
159 | tInfo = *localtime(&now);
160 | strftime(filename, sizeof(filename), "%Y%m%d_%H%M%S.jpg", &tInfo);
161 |
162 | char filePath[30];
163 | strcpy(filePath, getFolder);
164 | strcat(filePath, "/");
165 | strcat(filePath, filename);
166 | File file = SD_MMC.open(filePath, "w");
167 | if (!file) {
168 | Serial.println("Failed to open file in writing mode");
169 | if(request != nullptr)
170 | request->send(500, "text/plain", "ERROR. Image grab failed");
171 | return;
172 | }
173 | // size_t _jpg_buf_len = 0;
174 | // uint8_t *_jpg_buf = NULL;
175 | // bool jpeg_converted = frame2jpg(fb, 80, &_jpg_buf, &_jpg_buf_len);
176 | file.write(fb->buf, fb->len);
177 | file.close();
178 | Serial.printf("Saved file to path: %s - %zu bytes\n", filePath, fb->len);
179 |
180 | // Clear buffer
181 | esp_camera_fb_return(fb);
182 | if (request != nullptr)
183 | request->send(200, "text/plain", filename);
184 | }
185 |
186 | // List all files saved in the selected filesystem
187 | void listDir(const char *dirname, uint8_t levels) {
188 | uint32_t freeBytes = SD_MMC.totalBytes() - SD_MMC.usedBytes();
189 | Serial.print("\nTotal space: ");
190 | Serial.println(SD_MMC.totalBytes());
191 | Serial.print("Free space: ");
192 | Serial.println(freeBytes);
193 |
194 | Serial.printf("Listing directory: %s\r\n", dirname);
195 | File root = SD_MMC.open(dirname);
196 | if (!root) {
197 | Serial.println("- failed to open directory\n");
198 | return;
199 | }
200 | if (!root.isDirectory()) {
201 | Serial.println(" - not a directory\n");
202 | return;
203 | }
204 | File file = root.openNextFile();
205 | while (file) {
206 | if (file.isDirectory()) {
207 | if (levels)
208 | listDir(file.name(), levels - 1);
209 | }
210 | else {
211 | Serial.printf("|__ FILE: %s (%d bytes)\n",file.name(), file.size());
212 | }
213 | file = root.openNextFile();
214 | }
215 | }
216 |
--------------------------------------------------------------------------------
/examples/gpio_list/data/index.htm:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | ESP GPIO dinamic list
6 |
7 |
8 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 | GPIO status list
29 |
30 |
31 |
32 |
33 | Pin Name
34 | Pin number
35 | Type
36 | Level
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
--------------------------------------------------------------------------------
/examples/gpio_list/data/pico.classless.min.css.gz:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cotestatnt/async-esp-fs-webserver/f6303024b51f3086c6364932fa6447190c8ac852/examples/gpio_list/data/pico.classless.min.css.gz
--------------------------------------------------------------------------------
/examples/gpio_list/data/script.js:
--------------------------------------------------------------------------------
1 | const svgLightOn = ' ' ;
2 | const svgLightOff = ' ';
3 |
4 | /**
5 | * Custom selector "JQuery style", but in plain "Vanilla JS"
6 | */
7 | var $ = function(el) {
8 | return document.getElementById(el);
9 | };
10 |
11 | /**
12 | * Start a websocket client and set event callback functions
13 | */
14 | function ws_connect() {
15 | var ws = new WebSocket('ws://'+document.location.host+'/ws',['arduino']);
16 | ws.onopen = function() {
17 | ws.send('Connected - ' + new Date());
18 | getGpioList();
19 | };
20 | ws.onmessage = function(e) {
21 | parseMessage(e.data);
22 | };
23 | ws.onclose = function(e) {
24 | setTimeout(function() {
25 | ws_connect();
26 | }, 1000);
27 | };
28 | ws.onerror = function(err) {
29 | ws.close();
30 | };
31 | return ws;
32 | }
33 |
34 | /**
35 | * Send data "cmds" to ESP
36 | */
37 | function sendCommand(cmd, pin, level) {
38 | var data = {
39 | cmd: cmd,
40 | pin: parseInt(pin),
41 | level: level
42 | };
43 | console.log(data);
44 | connection.send(JSON.stringify(data));
45 | }
46 |
47 | /**
48 | * Parse messages receveid via websocket
49 | */
50 | function parseMessage(msg) {
51 | const obj = JSON.parse(msg);
52 | if (typeof obj === 'object' && obj !== null) {
53 | if (obj.esptime !== null) {
54 | var date = new Date(0); // The 0 sets the date to epoch
55 | if( date.setUTCSeconds(obj.esptime))
56 | document.getElementById("esp-time").innerHTML = date;
57 | }
58 | updateGpiosList(obj);
59 | }
60 | }
61 |
62 | /**
63 | * Read GPIO list status
64 | */
65 | function getGpioList() {
66 | fetch('/getGpioList') // Do the request
67 | .then(response => response.json()) // Parse the response
68 | .then(obj => { // DO something with response
69 | updateGpiosList(obj);
70 | });
71 | }
72 |
73 |
74 | /**
75 | * Iterate to the gpio list passed as parameter anc create DOMs dinamically
76 | */
77 | function updateGpiosList(elems) {
78 |
79 | // Get reference to gpio-list element and clear content
80 | const list = document.querySelector('#gpio-list');
81 | list.innerHTML = "";
82 |
83 | // Draw all input rows
84 | const inputs = Object.entries(elems).filter((item) => item[1].type === 'input');
85 |
86 | inputs.forEach(el => {
87 | const obj = el[1];
88 | var lbl = obj.level ? `${svgLightOn} HIGH` : `${svgLightOff} LOW`;
89 | // Create a single row with all columns
90 | var row = document.createElement('tr');
91 | row.innerHTML = '' + obj.label + ' ';
92 | row.innerHTML += '' + obj.pin + ' ';
93 | row.innerHTML += '' + obj.type + ' ';
94 | row.innerHTML += `${lbl} ` ;
95 | // Append this row to list
96 | list.appendChild(row);
97 | });
98 |
99 | // Draw all output rows
100 | const outputs = Object.entries(elems).filter((item) => item[1].type === 'output');
101 |
102 | outputs.forEach(el => {
103 | const obj = el[1];
104 | var lbl = obj.level ? ` checked>Turn OFF` : `>Turn ON`;
105 | // Create a single row with all columns
106 | var row = document.createElement('tr');
107 | row.innerHTML = '' + obj.label + ' ';
108 | row.innerHTML += '' + obj.pin + ' ';
109 | row.innerHTML += '' + obj.type + ' ';
110 | row.innerHTML += ` `;
111 | // Append this row to list
112 | list.appendChild(row);
113 | });
114 |
115 | // Add event listener to each out switch
116 | const outSwitches = document.querySelectorAll('input[type="checkbox"]');
117 | outSwitches.forEach( (outSw) => {
118 | outSw.addEventListener('change', function() {
119 | sendCommand('writeOut', this.dataset.pin, this.checked);
120 | });
121 | });
122 | }
123 |
124 | var connection = ws_connect();
125 | window.addEventListener('load', getGpioList);
126 |
--------------------------------------------------------------------------------
/examples/gpio_list/gpio_list.ino:
--------------------------------------------------------------------------------
1 | #include
2 | #include
3 | #include // https://github.com/cotestatnt/async-esp-fs-webserver/
4 |
5 | #define FILESYSTEM LittleFS
6 | AsyncFsWebServer server(80, FILESYSTEM);
7 |
8 | // Define a struct for store all info about each gpio
9 | struct gpio_type {
10 | const char* type;
11 | const char* label;
12 | int pin;
13 | bool level;
14 | };
15 |
16 | // Define an array of struct GPIO and initialize with values
17 |
18 | /* (ESP32-C3) */
19 | /*
20 | gpio_type gpios[NUM_GPIOS] = {
21 | {"input", "INPUT 2", 2},
22 | {"input", "INPUT 4", 4},
23 | {"input", "INPUT 5", 5},
24 | {"output", "OUTPUT 6", 6},
25 | {"output", "OUTPUT 7", 7},
26 | {"output", "LED BUILTIN", 3} // Led ON with signal HIGH
27 | };
28 | */
29 |
30 | /* ESP8266 - Wemos D1-mini */
31 | /*
32 | gpio_type gpios[] = {
33 | {"input", "INPUT 5", D5},
34 | {"input", "INPUT 6", D6},
35 | {"input", "INPUT 7", D7},
36 | {"output", "OUTPUT 2", D2},
37 | {"output", "OUTPUT 3", D3},
38 | {"output", "LED BUILTIN", LED_BUILTIN} // Led ON with signal LOW usually
39 | };
40 | */
41 |
42 | /* ESP32 - NodeMCU-32S */
43 | gpio_type gpios[] = {
44 | {"input", "INPUT 18", 18},
45 | {"input", "INPUT 19", 19},
46 | {"input", "INPUT 21", 21},
47 | {"output", "OUTPUT 4", 4},
48 | {"output", "OUTPUT 5", 5},
49 | {"output", "LED BUILTIN", 2} // Led ON with signal HIGH
50 | };
51 |
52 |
53 | //////////////////////////////// WebSocket Handler /////////////////////////////
54 | void onWsEvent(AsyncWebSocket * server, AsyncWebSocketClient * client, AwsEventType type, void * arg, uint8_t *data, size_t len){
55 | switch (type) {
56 | case WS_EVT_DISCONNECT:
57 | Serial.print("WebSocket client disconnected!\n");
58 | break;
59 | case WS_EVT_CONNECT: {
60 | IPAddress ip = client->remoteIP();
61 | Serial.printf("WebSocket client %d.%d.%d.%d connected.\n", ip[0], ip[1], ip[2], ip[3]);
62 | client->printf("%s", "{\"Connected\": true}");
63 | }
64 | break;
65 | case WS_EVT_DATA: {
66 | AwsFrameInfo * info = (AwsFrameInfo*)arg;
67 | String msg = "";
68 | if(info->final && info->index == 0 && info->len == len){
69 | //the whole message is in a single frame and we got all of it's data
70 | if (info->opcode == WS_TEXT){
71 | for(size_t i=0; i < info->len; i++) {
72 | msg += (char) data[i];
73 | }
74 | }
75 | Serial.printf("WS message: %s\n",msg.c_str());
76 | if (msg[0] == '{')
77 | parseMessage(msg);
78 | }
79 | }
80 | default:
81 | break;
82 | }
83 | }
84 |
85 |
86 | void parseMessage(const String json) {
87 | DynamicJsonDocument doc(512);
88 | DeserializationError error = deserializeJson(doc, json);
89 |
90 | if (!error) {
91 | // If this is a "writeOut" command, set the pin level to value
92 | const char* cmd = doc["cmd"];
93 | if (strcmp(cmd, "writeOut") == 0) {
94 | int pin = doc["pin"];
95 | int level = doc["level"];
96 | for (gpio_type &gpio : gpios) {
97 | if (gpio.pin == pin) {
98 | Serial.printf("Set pin %d to %d\n", pin, level);
99 | gpio.level = level ;
100 | digitalWrite(pin, level);
101 | updateGpioList(nullptr);
102 | return;
103 | }
104 | }
105 | }
106 | }
107 | Serial.print(F("deserializeJson() failed: "));
108 | Serial.println(error.f_str());
109 | }
110 |
111 | void updateGpioList(AsyncWebServerRequest *request) {
112 | StaticJsonDocument<512> doc;
113 | JsonArray array = doc.to();
114 |
115 | // Create a JSON message with current GPIO state
116 | for (gpio_type &gpio : gpios) {
117 | JsonObject obj = array.createNestedObject();
118 | obj["type"] = gpio.type;
119 | obj["pin"] = gpio.pin;
120 | obj["label"] = gpio.label;
121 | obj["level"] = gpio.level;
122 | }
123 | // Update client via websocket
124 | String json;
125 | serializeJson(doc, json);
126 | server.wsBroadcast(json.c_str());
127 |
128 | if (request != nullptr)
129 | request->send(200, "text/plain", json);
130 | }
131 |
132 | bool updateGpioState() {
133 | // Iterate the array of GPIO struct and check level of inputs
134 | for (gpio_type &gpio : gpios) {
135 | if (strcmp(gpio.type, "input") == 0) {
136 | // Input value != from last read
137 | if (digitalRead(gpio.pin) != gpio.level) {
138 | gpio.level = digitalRead(gpio.pin);
139 | return true;
140 | }
141 | }
142 | }
143 | return false;
144 | }
145 |
146 |
147 |
148 | void setup() {
149 | Serial.begin(115200);
150 |
151 | // FILESYSTEM initialization
152 | if (!FILESYSTEM.begin()) {
153 | Serial.println("ERROR on mounting filesystem.");
154 | //FILESYSTEM.format();
155 | ESP.restart();
156 | }
157 |
158 | // Try to connect to WiFi (will start AP if not connected after timeout)
159 | if (!server.startWiFi(10000)) {
160 | Serial.println("\nWiFi not connected! Starting AP mode...");
161 | server.startCaptivePortal("ESP_AP", "123456789", "/setup");
162 | }
163 |
164 | // Enable ACE FS file web editor and add FS info callback function
165 | server.enableFsCodeEditor();
166 |
167 | /*
168 | * Getting FS info (total and free bytes) is strictly related to
169 | * filesystem library used (LittleFS, FFat, SPIFFS etc etc) and ESP framework
170 | */
171 | #ifdef ESP32
172 | server.setFsInfoCallback([](fsInfo_t* fsInfo) {
173 | fsInfo->fsName = "LittleFS";
174 | fsInfo->totalBytes = LittleFS.totalBytes();
175 | fsInfo->usedBytes = LittleFS.usedBytes();
176 | });
177 | #endif
178 |
179 | // Add custom page handlers
180 | server.on("/getGpioList", HTTP_GET, updateGpioList);
181 |
182 | // Start server with custom websocket event handler
183 | server.init(onWsEvent);
184 | Serial.print(F("ESP Web Server started on IP Address: "));
185 | Serial.println(server.getServerIP());
186 | Serial.println(F(
187 | "This is \"gpio_list.ino\" example.\n"
188 | "Open /setup page to configure optional parameters.\n"
189 | "Open /edit page to view, edit or upload example or your custom webserver source files."
190 | ));
191 |
192 | // GPIOs configuration
193 | for (gpio_type &gpio : gpios) {
194 | if (strcmp(gpio.type, "input") == 0)
195 | pinMode(gpio.pin, INPUT_PULLUP);
196 | else
197 | pinMode(gpio.pin, OUTPUT);
198 | }
199 | }
200 |
201 | void loop() {
202 |
203 | // True on pin state change
204 | if (updateGpioState()) {
205 | updateGpioList(nullptr); // Push new state to web clients via websocket
206 | }
207 | }
--------------------------------------------------------------------------------
/examples/gpio_list/readme.md:
--------------------------------------------------------------------------------
1 | In this example the content of webpage will be created at runtime with Javascript depending from a JSON list of gpios (pin number, pin label, type, actual state) received from ESP device.
2 |
3 | 
4 |
--------------------------------------------------------------------------------
/examples/handleFormData/data/index.htm:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
88 |
89 |
90 |
91 |
92 |
Collapsible form option menu
93 |
Open Section 1
94 |
95 |
This example show how to handle a form without reload web page on submit.
96 |
In addition show a way to build a nice collapsible menu with CSS and JS
97 |
98 |
99 |
Open Section 2
100 |
101 |
102 |
103 |
104 |
114 |
115 |
116 |
117 |
118 |
Open Section 3
119 |
120 |
121 |
128 |
129 |
130 |
131 |
132 |
133 |
134 |
184 |
185 |
186 |
187 |
--------------------------------------------------------------------------------
/examples/handleFormData/data/myScript.js:
--------------------------------------------------------------------------------
1 | // Expand or collapse the content according to current state
2 | function expandCollapse() {
3 | // Get the HTML element immediately following the button (content)
4 | var content = this.nextElementSibling;
5 | if (content.style.maxHeight)
6 | content.style.maxHeight = null;
7 | else
8 | content.style.maxHeight = content.scrollHeight + "px";
9 | }
10 |
11 | // Select all "+" HTML buttons in webpage and add a listener for each one
12 | const btnList = document.querySelectorAll(".collapsible");
13 | btnList.forEach(elem => {
14 | elem.addEventListener("click", expandCollapse ); // Set callback function linked to the button click
15 | });
16 |
17 | // This listener will execute an "arrow" function once the page was fully loaded
18 | document.addEventListener("DOMContentLoaded", () => {
19 | console.log('Webpage is fully loaded');
20 |
21 | // At first, get the default values for form input elements from ESP
22 | fetch('/getDefault')
23 | .then(response => response.json()) // Parse the server response
24 | .then(jsonObj => { // Do something with parsed response
25 | console.log(jsonObj);
26 | document.getElementById('cars').value = jsonObj.car;
27 | document.getElementById('fname').value = jsonObj.firstname;
28 | document.getElementById('lname').value = jsonObj.lastname;
29 | document.getElementById('age').value = jsonObj.age;
30 | });
31 | });
32 |
33 | // This listener will prevent each form to reload page after submitting data
34 | document.addEventListener("submit", (e) => {
35 | const form = e.target; // Store reference to form to make later code easier to read
36 | fetch(form.action, { // Send form data to server using the Fetch API
37 | method: form.method,
38 | body: new FormData(form),
39 | })
40 |
41 | .then(response => response.text()) // Parse the server response
42 | .then(text => { // Do something with parsed response
43 | console.log(text);
44 | const resEl = document.getElementById(form.dataset.result).innerHTML= text;
45 | });
46 |
47 | e.preventDefault(); // Prevent the default form submit wich reload page
48 | });
49 | ////////////////////////////////////////////////////////////////////////////////////////////
--------------------------------------------------------------------------------
/examples/handleFormData/data/myStyle.css:
--------------------------------------------------------------------------------
1 |
2 | .container {
3 | box-shadow:
4 | 0 0.125rem 1rem rgba(27, 40, 50, 0.04),
5 | 0 0.125rem 2rem rgba(27, 40, 50, 0.08),
6 | 0 0 0 0.0625rem rgba(27, 40, 50, 0.024);
7 | border-radius: 5px;
8 | box-sizing: border-box;
9 | width: 100%;
10 | margin: auto;
11 | max-width: 860px;
12 | padding: 40px;
13 | }
14 |
15 | h3 {
16 | text-align: center;
17 | }
18 |
19 | .collapsible {
20 | background-color: #777;
21 | color: white;
22 | cursor: pointer;
23 | padding: 18px;
24 | width: 100%;
25 | border: none;
26 | text-align: left;
27 | outline: none;
28 | font-size: 15px;
29 | max-width: 800px;
30 | }
31 |
32 | .collapsible:after {
33 | content: '\002B'; /* The '+' character*/
34 | color: white;
35 | font-weight: bold;
36 | float: right;
37 | margin-left: 5px;
38 | }
39 |
40 | .content {
41 | padding: 0 15px;
42 | max-height: 0;
43 | overflow: hidden;
44 | transition: max-height 0.4s ease-out;
45 | background-color: #f1f1f1;
46 | border-bottom: solid 1px #f1f1f1;
47 | }
48 |
49 | p{
50 | margin-left: 10px;
51 | padding: 5px;
52 | }
53 |
54 | /* Form button styling */
55 | input, select {
56 | border-radius: 5px;
57 | border: solid 1px #ccc;
58 | padding: 10px 20px;
59 | cursor: pointer;
60 | margin-top: 5px;
61 | }
62 |
63 | input[type=submit] {
64 | background-color: green;
65 | color: white;
66 | }
67 |
68 | input[type=submit]:hover {
69 | background-color: #45a049;
70 | border: solid 1px #000;
71 | }
72 |
73 | label {
74 | text-align: right;
75 | margin: auto 0 auto 0;
76 | }
77 |
78 | form {
79 | margin-top: 10px;
80 | display: grid;
81 | grid-template-columns: 1fr 1fr 1fr;
82 | grid-gap: 20px;
83 | }
84 |
--------------------------------------------------------------------------------
/examples/handleFormData/handleFormData.ino:
--------------------------------------------------------------------------------
1 | #include
2 | #include
3 | #include // https://github.com/cotestatnt/async-esp-fs-webserver/
4 |
5 | #define FILESYSTEM LittleFS
6 | AsyncFsWebServer server(80, LittleFS, "esphost");
7 |
8 | //////////////////////////// HTTP Request Handlers ////////////////////////////////////
9 | void getDefaultValue (AsyncWebServerRequest *request) {
10 | // Send to client default values as JSON string because it's very easy to parse JSON in Javascript
11 | String defaultVal = "{\"car\":\"Ferrari\", \"firstname\":\"Enzo\", \"lastname\":\"Ferrari\",\"age\":90}";
12 | request->send(200, "text/json", defaultVal);
13 | }
14 |
15 | void handleForm1(AsyncWebServerRequest *request) {
16 | String reply;
17 | if(request->hasArg("cars")) {
18 | reply += "You have submitted with Form1: ";
19 | reply += request->arg("cars");
20 | }
21 | Serial.println(reply);
22 | request->send(200, "text/plain", reply);
23 | }
24 |
25 | void handleForm2(AsyncWebServerRequest *request) {
26 | String reply;
27 | if(request->hasArg("firstname")) {
28 | reply += "You have submitted with Form2: ";
29 | reply += request->arg("firstname");
30 | }
31 | if(request->hasArg("lastname")) {
32 | reply += " ";
33 | reply += request->arg("lastname");
34 | }
35 | if(request->hasArg("age")) {
36 | reply += ", age: ";
37 | reply += request->arg("age");
38 | }
39 | Serial.println(reply);
40 | request->send(200, "text/plain", reply);
41 | }
42 |
43 | //////////////////////////////// Filesystem /////////////////////////////////////////
44 | void listDir(fs::FS &fs, const char * dirname, uint8_t levels){
45 | Serial.printf("\nListing directory: %s\n", dirname);
46 | File root = fs.open(dirname, "r");
47 | if (!root) {
48 | Serial.println("- failed to open directory");
49 | return;
50 | }
51 | if (!root.isDirectory()) {
52 | Serial.println(" - not a directory");
53 | return;
54 | }
55 | File file = root.openNextFile();
56 | while (file) {
57 | if (file.isDirectory()) {
58 | if (levels) {
59 | #ifdef ESP32
60 | listDir(fs, file.path(), levels - 1);
61 | #elif defined(ESP8266)
62 | listDir(fs, file.fullName(), levels - 1);
63 | #endif
64 | }
65 | } else {
66 | Serial.printf("|__ FILE: %s (%d bytes)\n",file.name(), file.size());
67 | }
68 | file = root.openNextFile();
69 | }
70 | }
71 |
72 | bool startFilesystem() {
73 | if (FILESYSTEM.begin()){
74 | listDir(FILESYSTEM, "/", 1);
75 | return true;
76 | }
77 | else {
78 | Serial.println("ERROR on mounting filesystem. It will be reformatted!");
79 | FILESYSTEM.format();
80 | ESP.restart();
81 | }
82 | return false;
83 | }
84 |
85 |
86 | void setup(){
87 | Serial.begin(115200);
88 |
89 | // FILESYSTEM INIT
90 | startFilesystem();
91 |
92 | // Try to connect to WiFi (will start AP if not connected after timeout)
93 | if (!server.startWiFi(10000)) {
94 | Serial.println("\nWiFi not connected! Starting AP mode...");
95 | server.startCaptivePortal("ESP_AP", "123456789", "/setup");
96 | }
97 |
98 | // Add custom page handlers to webserver
99 | server.on("/getDefault", HTTP_GET, getDefaultValue);
100 | server.on("/setForm1", HTTP_POST, handleForm1);
101 | server.on("/setForm2", HTTP_POST, handleForm2);
102 |
103 | // Enable ACE FS file web editor and add FS info callback function
104 | server.enableFsCodeEditor();
105 | /*
106 | * Getting FS info (total and free bytes) is strictly related to
107 | * filesystem library used (LittleFS, FFat, SPIFFS etc etc) and ESP framework
108 | * (On ESP8266 will be used "built-in" fsInfo data type)
109 | */
110 | #ifdef ESP32
111 | server.setFsInfoCallback( [](fsInfo_t* fsInfo) {
112 | fsInfo->fsName = "LittleFS";
113 | fsInfo->totalBytes = LittleFS.totalBytes();
114 | fsInfo->usedBytes = LittleFS.usedBytes();
115 | });
116 | #endif
117 |
118 | // Start server
119 | server.init();
120 | Serial.print(F("ESP Web Server started on IP Address: "));
121 | Serial.println(server.getServerIP());
122 | Serial.println(F(
123 | "This is \"handleFormData.ino\" example.\n"
124 | "Open /setup page to configure optional parameters.\n"
125 | "Open /edit page to view, edit or upload example or your custom webserver source files."
126 | ));
127 | }
128 |
129 |
130 | void loop() {
131 |
132 | // This delay is required in order to avoid loopTask() WDT reset on ESP32
133 | delay(1);
134 | }
135 |
--------------------------------------------------------------------------------
/examples/highcharts/data/index.htm:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | ESP Heap Memory
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 | ESP FS WebServer - Heap Memory
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/examples/highcharts/data/script.js:
--------------------------------------------------------------------------------
1 | const timezone = new Date().getTimezoneOffset();
2 |
3 | let chart = Highcharts.chart('container', {
4 | time: {
5 | timezoneOffset: timezone
6 | },
7 | chart: {
8 | zoomType: 'x'
9 | },
10 | title: {
11 | text: 'Total heap size and max free block of RAM'
12 | },
13 | subtitle: {
14 | text: 'click and drag in the plot area to zoom in'
15 | },
16 | xAxis: {
17 | type: 'datetime'
18 | },
19 | yAxis: {
20 | title: {
21 | text: 'Bytes'
22 | }
23 | },
24 | legend: {
25 | enabled: false
26 | },
27 | plotOptions: {
28 | area: {
29 | fillColor: {
30 | linearGradient: {
31 | x1: 0,
32 | y1: 0,
33 | x2: 0,
34 | y2: 1
35 | },
36 | stops: [
37 | [0, Highcharts.getOptions().colors[0]],
38 | [1, Highcharts.color(Highcharts.getOptions().colors[0]).setOpacity(0).get('rgba')]
39 | ]
40 | },
41 | marker: {
42 | radius: 2
43 | },
44 | lineWidth: 1,
45 | states: {
46 | hover: {
47 | lineWidth: 1
48 | }
49 | },
50 | threshold: null
51 | }
52 | },
53 |
54 | series: [{
55 | type: 'area',
56 | name: 'Total heap size',
57 | data: []
58 | },
59 | {
60 | type: 'area',
61 | name: 'Max contiguos RAM block',
62 | data: []
63 | },
64 | ]
65 | });
66 |
67 |
68 | function addPoint(timestamp, total, max) {
69 | chart.series[0].addPoint([timestamp, total]);
70 | chart.series[1].addPoint([timestamp, max]);
71 | }
72 |
73 |
74 | // WebSocket handling
75 | function ws_connect() {
76 | var ws = new WebSocket('ws://'+document.location.host+'/ws',['arduino']);
77 | ws.onopen = function() { ws.send('Connected - ' + new Date());};
78 | ws.onmessage = function(e) {
79 | parseMessage(e.data);
80 | };
81 | ws.onclose = function(e) {
82 | setTimeout(function() {
83 | ws_connect();
84 | }, 1000);
85 | };
86 | ws.onerror = function(err) {
87 | ws.close();
88 | };
89 | }
90 |
91 | function parseMessage(msg) {
92 | try {
93 | const obj = JSON.parse(msg);
94 | if (typeof obj === 'object' && obj !== null) {
95 | // Add new point to chart message
96 | if (obj.addPoint !== null) {
97 |
98 | // Updated the ESP timedate
99 | var date = new Date(obj.timestamp*1000);
100 | document.getElementById("esp-time").innerHTML = date;
101 |
102 | // Add the new point
103 | addPoint(obj.timestamp*1000, obj.totalHeap, obj.maxBlock);
104 | }
105 | }
106 | } catch {
107 | console.log('Error on parse message ' + msg);
108 | }
109 | }
110 |
111 | ws_connect();
112 |
--------------------------------------------------------------------------------
/examples/highcharts/data/style.css:
--------------------------------------------------------------------------------
1 | body {
2 | background-color: #47597E;
3 | color: white;
4 | margin: auto;
5 | max-width: 820px;
6 | }
7 |
8 | button {
9 | padding: 5px;
10 | }
11 |
12 | .buttons {
13 | display: flex;
14 | padding: 10px;
15 | border: 1px solid #f69c55;
16 | }
17 |
18 | #demo {
19 | margin: auto;
20 | }
21 |
22 | .highcharts-figure,
23 | .highcharts-data-table table {
24 | min-width: 360px;
25 | max-width: 800px;
26 | margin: 1em auto;
27 | }
28 |
29 | .highcharts-data-table table {
30 | font-family: Verdana, sans-serif;
31 | border-collapse: collapse;
32 | border: 1px solid #EBEBEB;
33 | margin: 10px auto;
34 | text-align: center;
35 | width: 100%;
36 | max-width: 500px;
37 | }
38 |
39 | .highcharts-data-table caption {
40 | padding: 1em 0;
41 | font-size: 1.2em;
42 | color: #555;
43 | }
44 |
45 | .highcharts-data-table th {
46 | font-weight: 600;
47 | padding: 0.5em;
48 | }
49 |
50 | .highcharts-data-table td,
51 | .highcharts-data-table th,
52 | .highcharts-data-table caption {
53 | padding: 0.5em;
54 | }
55 |
56 | .highcharts-data-table thead tr,
57 | .highcharts-data-table tr:nth-child(even) {
58 | background: #f8f8f8;
59 | }
60 |
61 | .highcharts-data-table tr:hover {
62 | background: #f1f7ff;
63 | }
--------------------------------------------------------------------------------
/examples/highcharts/highcharts.ino:
--------------------------------------------------------------------------------
1 | #if defined(ESP8266)
2 | #include
3 | #elif defined(ESP32)
4 | #include
5 | #endif
6 | #include
7 | #include
8 | #include // https://github.com/cotestatnt/async-esp-fs-webserver/
9 |
10 | #define FILESYSTEM LittleFS
11 | AsyncFsWebServer server(80, FILESYSTEM);
12 |
13 | #ifndef LED_BUILTIN
14 | #define LED_BUILTIN 2
15 | #endif
16 |
17 | // In order to set SSID and password open the /setup webserver page
18 | // const char* ssid;
19 | // const char* password;
20 | const char* hostname = "heap-chart";
21 |
22 | // Timezone definition to get properly time from NTP server
23 | #define MYTZ "CET-1CEST,M3.5.0,M10.5.0/3"
24 | struct tm Time;
25 |
26 | //////////////////////////////// WebSocket Handler /////////////////////////////
27 | void onWsEvent(AsyncWebSocket * server, AsyncWebSocketClient * client, AwsEventType type, void * arg, uint8_t *data, size_t len){
28 | switch (type) {
29 | case WS_EVT_DISCONNECT:
30 | Serial.print("WebSocket client disconnected!\n");
31 | break;
32 | case WS_EVT_CONNECT: {
33 | IPAddress ip = client->remoteIP();
34 | Serial.printf("WebSocket client %d.%d.%d.%d connected.\n", ip[0], ip[1], ip[2], ip[3]);
35 | client->printf("%s", "{\"Connected\": true}");
36 | }
37 | break;
38 | default:
39 | break;
40 | }
41 | }
42 |
43 |
44 | //////////////////////////////// NTP Time /////////////////////////////////////
45 | void getUpdatedtime(const uint32_t timeout)
46 | {
47 | uint32_t start = millis();
48 | Serial.print("Sync time...");
49 | while (millis() - start < timeout && Time.tm_year <= (1970 - 1900)) {
50 | time_t now = time(nullptr);
51 | Time = *localtime(&now);
52 | delay(5);
53 | }
54 | Serial.println(" done.");
55 | }
56 |
57 |
58 | //////////////////////////////// Filesystem /////////////////////////////////////////
59 | void listDir(fs::FS &fs, const char * dirname, uint8_t levels){
60 | Serial.printf("\nListing directory: %s\n", dirname);
61 | File root = fs.open(dirname, "r");
62 | if (!root) {
63 | Serial.println("- failed to open directory");
64 | return;
65 | }
66 | if (!root.isDirectory()) {
67 | Serial.println(" - not a directory");
68 | return;
69 | }
70 | File file = root.openNextFile();
71 | while (file) {
72 | if (file.isDirectory()) {
73 | if (levels) {
74 | #ifdef ESP32
75 | listDir(fs, file.path(), levels - 1);
76 | #elif defined(ESP8266)
77 | listDir(fs, file.fullName(), levels - 1);
78 | #endif
79 | }
80 | } else {
81 | Serial.printf("|__ FILE: %s (%d bytes)\n",file.name(), file.size());
82 | }
83 | file = root.openNextFile();
84 | }
85 | }
86 |
87 | bool startFilesystem() {
88 | if (FILESYSTEM.begin()){
89 | listDir(FILESYSTEM, "/", 1);
90 | return true;
91 | }
92 | else {
93 | Serial.println("ERROR on mounting filesystem. It will be reformatted!");
94 | FILESYSTEM.format();
95 | ESP.restart();
96 | }
97 | return false;
98 | }
99 |
100 |
101 |
102 | void setup() {
103 | pinMode(LED_BUILTIN, OUTPUT);
104 | Serial.begin(115200);
105 |
106 | // FILESYSTEM INIT
107 | startFilesystem();
108 |
109 | // Try to connect to WiFi (will start AP if not connected after timeout)
110 | if (!server.startWiFi(10000)) {
111 | Serial.println("\nWiFi not connected! Starting AP mode...");
112 | server.startCaptivePortal("ESP_AP", "123456789", "/setup");
113 | }
114 |
115 | // Enable ACE FS file web editor and add FS info callback function
116 | server.enableFsCodeEditor();
117 |
118 | /*
119 | * Getting FS info (total and free bytes) is strictly related to
120 | * filesystem library used (LittleFS, FFat, SPIFFS etc etc) and ESP framework
121 | */
122 | #ifdef ESP32
123 | server.setFsInfoCallback([](fsInfo_t* fsInfo) {
124 | fsInfo->fsName = "LittleFS";
125 | fsInfo->totalBytes = LittleFS.totalBytes();
126 | fsInfo->usedBytes = LittleFS.usedBytes();
127 | });
128 | #endif
129 |
130 | // Start server with custom websocket event handler
131 | server.init(onWsEvent);
132 | Serial.print(F("ESP Web Server started on IP Address: "));
133 | Serial.println(server.getServerIP());
134 | Serial.println(F(
135 | "This is \"highcharts.ino\" example.\n"
136 | "Open /setup page to configure optional parameters.\n"
137 | "Open /edit page to view, edit or upload example or your custom webserver source files."
138 | ));
139 |
140 | // Start MDNS responder
141 | if (WiFi.status() == WL_CONNECTED) {
142 | // Set hostname
143 | #ifdef ESP8266
144 | WiFi.hostname(hostname);
145 | #elif defined(ESP32)
146 | WiFi.setHostname(hostname);
147 | #endif
148 | if (MDNS.begin(hostname)) {
149 | Serial.println(F("MDNS responder started."));
150 | Serial.printf("You should be able to connect with address\t http://%s.local/\n", hostname);
151 | // Add service to MDNS-SD
152 | MDNS.addService("http", "tcp", 80);
153 | }
154 | }
155 | }
156 |
157 |
158 | void loop() {
159 |
160 | if (WiFi.status() == WL_CONNECTED) {
161 | #ifdef ESP8266
162 | MDNS.update();
163 | #endif
164 | }
165 |
166 | // Send ESP system time (epoch) and heap stats to WS client
167 | static uint32_t sendToClientTime;
168 | if (millis() - sendToClientTime > 1000 ) {
169 | sendToClientTime = millis();
170 | digitalWrite(LED_BUILTIN, !digitalRead(LED_BUILTIN));
171 |
172 | time_t now = time(nullptr);
173 | StaticJsonDocument<1024> doc;
174 | doc["addPoint"] = true;
175 | doc["timestamp"] = now;
176 | #ifdef ESP32
177 | doc["totalHeap"] = heap_caps_get_free_size(0);
178 | doc["maxBlock"] = heap_caps_get_largest_free_block(0);
179 | #elif defined(ESP8266)
180 | uint32_t free;
181 | uint32_t max;
182 | ESP.getHeapStats(&free, &max, nullptr);
183 | doc["totalHeap"] = free;
184 | doc["maxBlock"] = max;
185 | #endif
186 | String msg;
187 | serializeJson(doc, msg);
188 | server.wsBroadcast(msg.c_str());
189 | }
190 |
191 | }
--------------------------------------------------------------------------------
/examples/highcharts/readme.md:
--------------------------------------------------------------------------------
1 | ## esp-fs-webserver
2 | This example is the most advanced since now.
3 |
4 | With the help of WebSocket technology, we can send message from server-to-clients or from client-to-server in a **full-duplex communication channels over a single TCP connection.**
5 |
6 | We use the **WebSocket client** to populate asynchronously an _"area chart"_ with real time datas from ESP regarding the **total size of heap memory** and size of **max contiguous block of memory**.
7 |
8 | Off course, on the ESP MCU we will run also a WebSocket server together to the web server.
9 | I've used this library [arduinoWebSockets](https://github.com/Links2004/arduinoWebSockets), so it is needed to compile, but you can choose which you prefer.
10 |
11 | >N.B.
12 | This example will run only if an internet connection is available because [Highcharts](https://www.highcharts.com/) Javascript resources is necessary
13 | (to run offline in AP mode, you can download all the used js files, put it in the flash memory (better if gzipped to speed-up page loading) and edit the [index.htm](https://github.com/cotestatnt/esp-fs-webserver/blob/main/examples/highcharts/data/index.htm))
14 |
15 | In this example, web page CSS and JavaScript source file is stored in a separated file inside the folder **/app**
16 |
17 | 
18 |
--------------------------------------------------------------------------------
/examples/localRFID/JsonDB.hpp:
--------------------------------------------------------------------------------
1 | #include
2 | #include
3 | #include
4 |
5 | class TableManager {
6 | public:
7 | TableManager(const char* filename) : filename(filename) {
8 | // Initialize as empty JSON array (will store our records)
9 | document.to();
10 | }
11 |
12 | // Loads JSON data from LittleFS file into memory
13 | bool loadTable() {
14 | File file = LittleFS.open(filename, FILE_READ);
15 | if (!file) {
16 | // File doesn't exist - initialize empty table (not an error)
17 | document.to();
18 | return true;
19 | }
20 |
21 | document.clear();
22 | DeserializationError error = deserializeJson(document, file);
23 | file.close();
24 |
25 | if (error) {
26 | Serial.print(F("deserializeJson() failed: "));
27 | Serial.println(error.f_str());
28 | return false;
29 | }
30 | return true;
31 | }
32 |
33 | // Checks if a record with same uniqueKey value already exists
34 | bool isDuplicate(JsonObject& newRecord, const char* uniqueKey) {
35 | JsonArray table = document.as();
36 | if (newRecord[uniqueKey].is()) {
37 | const char* idValue = newRecord[uniqueKey];
38 | for (JsonObject record : table) {
39 | if (record[uniqueKey].is() &&
40 | strcmp(record[uniqueKey].as(), idValue) == 0) {
41 | return true;
42 | }
43 | }
44 | }
45 | return false;
46 | }
47 |
48 | // Adds new record to the table with optional duplicate check
49 | bool addRecord(JsonObject newRecord, const char* uniqueKey = nullptr) {
50 | JsonArray table = document.as();
51 |
52 | // Skip duplicate check if uniqueKey not provided
53 | if (uniqueKey && isDuplicate(newRecord, uniqueKey)) {
54 | return false;
55 | }
56 |
57 | if (table.add(newRecord))
58 | return saveTable();
59 | return false;
60 | }
61 |
62 | // Version that checks multiple unique keys
63 | bool addRecord(JsonObject newRecord, const char* uniqueKeys[], int numUniqueKeys) {
64 | JsonArray table = document.as();
65 |
66 | // Check all specified unique keys for duplicates
67 | for (int i = 0; i < numUniqueKeys; i++) {
68 | if (isDuplicate(newRecord, uniqueKeys[i]))
69 | return false;
70 | }
71 |
72 | if (table.add(newRecord))
73 | return saveTable();
74 | return false;
75 | }
76 |
77 | // Delete existing record matching key-value pair
78 | bool deleteRecord(const char* key, const char* value) {
79 | JsonArray table = document.as();
80 |
81 | // Iterate through the array to find the matching record
82 | for (size_t i = 0; i < table.size(); i++) {
83 | JsonObject record = table[i].as();
84 | if (record[key] == value) {
85 | table.remove(i);
86 | return saveTable();
87 | }
88 | }
89 | return false; // Record not found
90 | }
91 |
92 | // Finds first record matching key-value pair
93 | JsonObject findRecord(const char* key, const char* value) {
94 | JsonArray table = document.as();
95 | for (JsonObject record : table) {
96 | if (strcmp(record[key].as(), value) == 0) {
97 | return record;
98 | }
99 | }
100 | return JsonObject();
101 | }
102 |
103 | JsonArray getUsers() {
104 | loadTable();
105 | return document.as();
106 | }
107 |
108 | // Persists current in-memory table to LittleFS
109 | bool saveTable() {
110 | File file = LittleFS.open(filename, FILE_WRITE);
111 | if (!file) return false;
112 |
113 | bool success = serializeJsonPretty(document, file) > 0;
114 | file.close();
115 | return success;
116 | }
117 |
118 | // Debug helper - prints current table to Serial
119 | void printTable() {
120 | serializeJsonPretty(document, Serial);
121 | Serial.println();
122 | }
123 |
124 | private:
125 | const char* filename; // LittleFS filename for persistence
126 | JsonDocument document; // In-memory JSON data storage
127 | };
128 |
--------------------------------------------------------------------------------
/examples/localRFID/data/login:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Login
7 |
21 |
22 |
23 |
24 |
25 | ESP32 RFID Logs - Login
26 |
27 |
42 |
48 |
49 |
50 |
133 |
134 |
--------------------------------------------------------------------------------
/examples/localRFID/localRFID.ino:
--------------------------------------------------------------------------------
1 | #include
2 | #include
3 | #include
4 | #include
5 | #include // https://github.com/cotestatnt/async-esp-fs-webserver
6 | #include // https://github.com/OSSLibraries/Arduino_MFRC522v2
7 | #include
8 | #include
9 | #include
10 | #include
11 |
12 | #define MYTZ "CET-1CEST,M3.5.0,M10.5.0/3" // Timezone definition to get properly time from NTP server
13 |
14 | /*
15 | * To customize the appearance of the web pages, upload the login and rfid files
16 | * (without extensions) to the microcontroller's flash memory using built-in functionality (/setup webpage).
17 | * Set the following #define to false for send your pages instead of those embedded in the firmware.
18 | * You can find both files in the "/data" folder.
19 | */
20 | #define USE_EMBEDDED_HTM true
21 |
22 | #if USE_EMBEDDED_HTM
23 | #include "data/html_login.h"
24 | #include "data/html_rfid.h"
25 | #endif
26 |
27 | #define PIN_MISO 15
28 | #define PIN_MOSI 16
29 | #define PIN_SCLK 17
30 | #define PIN_CS 18
31 |
32 | uint32_t chipId = 0; // Unique ID of the ESP chip
33 | bool addLogRecord = true; // Flag to enable/disable log recording
34 |
35 | MFRC522DriverPinSimple ss_pin(PIN_CS); // Configurable, see typical pin layout above.
36 | MFRC522DriverSPI driver{ss_pin}; // Create SPI driver.
37 | MFRC522 mfrc522{driver}; // Create MFRC522 instance.
38 | struct tm ntp; // Time structure holding current time from NTP
39 |
40 | #include "JsonDB.hpp"
41 | #include "webserver.hpp"
42 |
43 | TableManager usersTable("/users.json");
44 |
45 | struct User_t{
46 | const char* username;
47 | const char* password;
48 | const char* name;
49 | const char* email;
50 | const char* tag;
51 | uint8_t level;
52 | } ;
53 |
54 | // Some test users
55 | User_t users[] = {
56 | {"admin", "admin", "DB adminitrator", "", "", 5},
57 | {"user1", "", "John Doe", "jhon.doe@email.com", "12345", 1},
58 | {"user2", "", "Mark twain", "mark65@email.com", "67890", 1}
59 | };
60 |
61 | const char* uniqueKeys[] = {"tag", "username"};
62 |
63 | //////////////////////////// Append a row to csv file ///////////////////////////////////
64 | bool appendRow(const char* username, uint64_t tag) {
65 | getLocalTime(&ntp, 10);
66 |
67 | char filename[32];
68 | snprintf(filename, sizeof(filename),"/logs/%04d_%02d_%02d.csv", ntp.tm_year + 1900, ntp.tm_mon + 1, ntp.tm_mday);
69 |
70 | File file = LittleFS.open("/logs");
71 | if (!file) {
72 | LittleFS.mkdir("/logs");
73 | Serial.println("Created /logs directory");
74 | }
75 |
76 | if (LittleFS.exists(filename)) {
77 | file = LittleFS.open(filename, "a"); // Append to existing file
78 | }
79 | else {
80 | file = LittleFS.open(filename, "w"); // Create a new file
81 | file.println("reader, username, tag, timestamp");
82 | Serial.printf("Created file %s\n", filename);
83 | }
84 |
85 | if (file) {
86 | char timestamp[25];
87 | strftime(timestamp, sizeof(timestamp), "%c", &ntp);
88 | char row[64];
89 | snprintf(row, sizeof(row), "%d, %s, %llu, %s", chipId, username, tag, timestamp);
90 | Serial.println(row);
91 | file.println(row);
92 | file.close();
93 | return true;
94 | }
95 | return false;
96 | }
97 |
98 | void setup() {
99 | SPI.begin(PIN_SCLK, PIN_MISO, PIN_MOSI, PIN_CS);
100 | Serial.begin(115200);
101 | Serial.println("\n\n\n\nStarting ESP32 RFID gateway...");
102 |
103 | mfrc522.PCD_Init(); // Init MFRC522 board.
104 | MFRC522Debug::PCD_DumpVersionToSerial(mfrc522, Serial); // Show details of PCD - MFRC522 Card Reader details.
105 | Serial.println(F("Scan PICC to see UID, SAK, type, and data blocks..."));
106 |
107 | if (!LittleFS.begin()) {
108 | Serial.println("ERROR on mounting filesystem. It will be reformatted!");
109 | LittleFS.format();
110 | ESP.restart();
111 | }
112 |
113 | // LittleFS.remove("/users.json");
114 |
115 | // Load the users table
116 | usersTable.loadTable();
117 |
118 | for (User_t user: users){
119 | JsonDocument userDoc;
120 | userDoc["username"] = user.username;
121 | userDoc["password"] = getSHA256(user.password);
122 | userDoc["name"] = user.name;
123 | userDoc["email"] = user.email;
124 | userDoc["tag"] = user.tag;
125 | userDoc["level"] = user.level;
126 |
127 | if (usersTable.addRecord(userDoc.as(), uniqueKeys, 2))
128 | Serial.println("Record added to table");
129 | else
130 | Serial.println("Error or record alreay present.");
131 | }
132 |
133 | // Start webserver
134 | startWebServer();
135 | // Set NTP servers
136 | #ifdef ESP8266
137 | configTime(MYTZ, "time.google.com", "time.windows.com", "pool.ntp.org");
138 | #elif defined(ESP32)
139 | configTzTime(MYTZ, "time.google.com", "time.windows.com", "pool.ntp.org");
140 | #endif
141 | // Wait for NTP sync (with timeout)
142 | getLocalTime(&ntp, 5000);
143 |
144 | #if defined(ESP32)
145 | for(byte i=0; i<17; i=i+8) {
146 | chipId |= ((ESP.getEfuseMac() >> (40 - i)) & 0xff) << i;
147 | }
148 | #elif defined(ESP8266)
149 | chipId = ESP.getChipId();
150 | #endif
151 | Serial.print("ESP Chip ID: ");
152 | Serial.println(chipId);
153 | }
154 |
155 | void loop() {
156 | delay(10);
157 |
158 | if (mfrc522.PICC_IsNewCardPresent() && mfrc522.PICC_ReadCardSerial()) {
159 | static uint32_t readTime = millis();
160 | static uint64_t oldCode = 0;
161 |
162 | // The UID of an RFID tag can be up to 64bit long
163 | uint64_t tagCode = 0;
164 |
165 | // tagCode is swapped, but it doesn't matter We need only it's a unique number
166 | for(byte i = 0; i < mfrc522.uid.size; i++) {
167 | tagCode |= mfrc522.uid.uidByte[i] << (8*i);
168 | }
169 |
170 | if ((tagCode != oldCode) || (millis() - readTime > 5000)) {
171 | readTime = millis();
172 | oldCode = tagCode;
173 |
174 | // Serial.printf("\nTag code: %llu\n", tagCode);
175 | if (addLogRecord) {
176 | JsonObject user = usersTable.findRecord("tag", String(tagCode).c_str());
177 | if (user) {
178 | const char* username = user["username"].as();
179 | appendRow(username, tagCode);
180 | }
181 | else {
182 | appendRow("not allowed", tagCode);
183 | }
184 | }
185 | }
186 | }
187 | }
188 |
189 |
190 |
--------------------------------------------------------------------------------
/examples/localRFID/readme.md:
--------------------------------------------------------------------------------
1 | In this example, a simple access control system has been implemented using RFID tags and the [Arduino_MFRC522v2](https://github.com/OSSLibraries/Arduino_MFRC522v2) library.
2 |
3 | The system allows enabling or disabling any type of user, each of whom can be assigned a different role.
4 |
5 | 
6 |
7 | For instance, modifying or deleting user accounts requires logging in with a role level of at least 5 (such as the admin user).
8 | Users with lower levels can still log in, but only to view data.
9 |
10 | # Logs
11 | 
12 |
13 | # User management
14 | 
15 |
16 | # Customization
17 | By default, this example includes web pages embedded in the firmware as C arrays, compressed using Gzip.
18 |
19 | To customize the appearance of the web pages,
20 | upload the [`login`](https://github.com/cotestatnt/async-esp-fs-webserver/blob/master/examples/localRFID/data/login)
21 | and [`rfid`](https://github.com/cotestatnt/async-esp-fs-webserver/blob/master/examples/localRFID/data/rfid) files (without extensions) to the microcontroller's flash memory using built-in functionality (/setup webpage).
22 | You can find both files in the `"/data"` folder.
23 |
24 | Then set `#define USE_EMBEDDED_HTM false` in order to serve files webpage instead embedded.
25 |
26 | In the [mysqlRFID.ino](https://github.com/cotestatnt/async-esp-fs-webserver/blob/master/examples/mysqlRFID/mysqlRFID.ino) example, a MySQL database is used through the [Arduino-MySQL](https://github.com/cotestatnt/Arduino-MySQL) library.
27 | Since the database is centralized, it supports multiple RFID tag readers, logging each access point for tracking purposes.
28 | The web pages in this example are embedded as `const char*` strings, making them easy to edit directly within the project.
29 |
--------------------------------------------------------------------------------
/examples/mysqlRFID/mysqlRFID.ino:
--------------------------------------------------------------------------------
1 | #include
2 | #include
3 | #include // https://github.com/cotestatnt/Arduino-MySQL
4 | #include // https://github.com/cotestatnt/async-esp-fs-webserver
5 | #include // https://github.com/OSSLibraries/Arduino_MFRC522v2
6 | #include
7 | #include
8 | #include
9 |
10 | #define PIN_MISO 15
11 | #define PIN_MOSI 16
12 | #define PIN_SCLK 17
13 | #define PIN_CS 18
14 |
15 | // This set of variables can be updated using webpage http:///setup
16 | String user = "xxxxxxxxxxx"; // MySQL user login username
17 | String password = "xxxxxxxxxxxx"; // MySQL user login password
18 | String dbHost = "192.168.1.1"; // MySQL hostname/URL
19 | String database = "xxxxxxxxxxx"; // Database name
20 | uint16_t dbPort = 3306; // MySQL host port
21 |
22 | // Var labels (in /setup webpage)
23 | #define MY_SQL_HOST "MySQL Hostname"
24 | #define MY_SQL_PORT "MySQL Port"
25 | #define MY_SQL_DB "MySQL Database name"
26 | #define MY_SQL_USER "MySQL Username"
27 | #define MY_SQL_PASS "MySQL Password"
28 | #define RFID_READER_ID "RFID Reader ID"
29 |
30 | MFRC522DriverPinSimple ss_pin(PIN_CS); // Configurable, see typical pin layout above.
31 | MFRC522DriverSPI driver{ss_pin}; // Create SPI driver.
32 | MFRC522 mfrc522{driver}; // Create MFRC522 instance.
33 |
34 | uint32_t chipId = 0;
35 | bool addLogRecord = true;
36 |
37 |
38 | #include "mysql_impl.h"
39 | #include "webserver_impl.h"
40 |
41 | void setup() {
42 | SPI.begin(PIN_SCLK, PIN_MISO, PIN_MOSI, PIN_CS);
43 | Serial.begin(115200);
44 | Serial.println("\n\n\n\nStarting ESP32 RFID gateway...");
45 |
46 | mfrc522.PCD_Init(); // Init MFRC522 board.
47 | MFRC522Debug::PCD_DumpVersionToSerial(mfrc522, Serial); // Show details of PCD - MFRC522 Card Reader details.
48 | Serial.println(F("Scan PICC to see UID, SAK, type, and data blocks..."));
49 |
50 | /* Init and start configuration webserver */
51 | if (startWebServer()) {
52 |
53 | /* Init and start MySQL task */
54 | if (connectToDatabase()) {
55 | /*
56 | * Check if working table exists and create if not exists.
57 | * If not already present, also a default admin user will be created (admin, admin);
58 | */
59 | if (!checkAndCreateTables()) {
60 | Serial.println("Error! Tables not created properly");
61 | }
62 | }
63 | else {
64 | Serial.println("\nDatabase connection failed.\nCheck your connection");
65 | }
66 | }
67 |
68 | #if defined(ESP32)
69 | for(byte i=0; i<17; i=i+8) {
70 | chipId |= ((ESP.getEfuseMac() >> (40 - i)) & 0xff) << i;
71 | }
72 | #elif defined(ESP8266)
73 | chipId = ESP.getChipId();
74 | #endif
75 | Serial.print("ESP Chip ID: ");
76 | Serial.println(chipId);
77 | }
78 |
79 | void loop() {
80 | delay(10);
81 |
82 | if (mfrc522.PICC_IsNewCardPresent() && mfrc522.PICC_ReadCardSerial()) {
83 | static uint32_t readTime = millis();
84 | static uint64_t oldCode = 0;
85 |
86 | // The UID of an RFID tag can be up to 64bit long
87 | uint64_t tagCode = 0;
88 |
89 | // tagCode is swapped, but it doesn't matter We need only it's a unique number
90 | for(byte i = 0; i < mfrc522.uid.size; i++) {
91 | tagCode |= mfrc522.uid.uidByte[i] << (8*i);
92 | }
93 |
94 | if ((tagCode != oldCode) || (millis() - readTime > 5000)) {
95 | oldCode = tagCode;
96 | readTime = millis();
97 | Serial.printf("\nTag code: %llu\n", tagCode);
98 |
99 | if (addLogRecord) {
100 | DataQuery_t data;
101 | if (queryExecute(data, "SELECT username, level FROM users WHERE tag_code = %llu;", tagCode)) {
102 | sql.printResult(data, Serial);
103 | String SQL = "INSERT INTO logs (epoch, username, tag_code, reader) VALUES (UNIX_TIMESTAMP(), '";
104 | SQL += data.getRowValue(0, "username"); SQL += "', ";
105 | SQL += tagCode; SQL += ", ";
106 | SQL += chipId; SQL += ");";
107 |
108 | if (queryExecute(data, SQL.c_str())) {
109 | Serial.printf("\"%s\" registered on reader %lu\n", data.getRowValue(0, "username"), chipId);
110 | }
111 | }
112 | }
113 | }
114 | }
115 | }
116 |
117 |
--------------------------------------------------------------------------------
/examples/mysqlRFID/mysql_impl.h:
--------------------------------------------------------------------------------
1 | #include // https://github.com/cotestatnt/Arduino-MySQL
2 | #include
3 |
4 | extern String getSHA256(const char*);
5 |
6 | // MySQL client class
7 | WiFiClient client;
8 | MySQL sql(&client, dbHost.c_str(), dbPort);
9 |
10 | #define MAX_QUERY_LEN 512 // MAX query string length
11 |
12 | static const char createUsersTable[] PROGMEM = R"string_literal(
13 | CREATE TABLE %s (
14 | id INT AUTO_INCREMENT PRIMARY KEY,
15 | username VARCHAR(32) UNIQUE,
16 | password VARCHAR(128),
17 | name VARCHAR(64),
18 | email VARCHAR(64),
19 | tag_code BIGINT UNSIGNED UNIQUE,
20 | level INT
21 | );
22 |
23 |
24 | )string_literal";
25 |
26 | static const char createLogTable[] PROGMEM = R"string_literal(
27 | CREATE TABLE %s (
28 | id INT AUTO_INCREMENT PRIMARY KEY,
29 | epoch BIGINT,
30 | username VARCHAR(32),
31 | tag_code BIGINT UNSIGNED,
32 | reader INT UNSIGNED
33 | );
34 | )string_literal";
35 |
36 |
37 | // Insert or update a device (if ble_id already defined keep it's actual value)
38 | static const char newUpdateUser[] PROGMEM = R"string_literal(
39 | INSERT INTO users (username, password, name, email, tag_code, level)
40 | VALUES ('%s', '%s', '%s', '%s', %s, %s)
41 | ON DUPLICATE KEY UPDATE
42 | username = VALUES(username),
43 | password = VALUES(password),
44 | name = VALUES(name),
45 | email = VALUES(email),
46 | tag_code = VALUES(tag_code),
47 | level = VALUES(level);
48 | )string_literal";
49 |
50 |
51 | // Establish connection with MySQL database according to the variables defined (/setup webpage)
52 | bool connectToDatabase() {
53 | if (sql.connected()) {
54 | return true;
55 | }
56 | Serial.printf("\nConnecting to MySQL server %s on DataBase %s...\n", dbHost.c_str(), database.c_str());
57 | if (sql.connect(user.c_str(), password.c_str(), database.c_str())) {
58 | delay(200);
59 | return true;
60 | }
61 | Serial.println("Fails!");
62 | sql.disconnect();
63 | return false;
64 | }
65 |
66 |
67 | // Variadic function that will execute the query selected with passed parameters
68 | bool queryExecute(DataQuery_t& data, const char* queryStr, ...) {
69 | if (connectToDatabase()) {
70 | char buf[MAX_QUERY_LEN];
71 | va_list args;
72 | va_start (args, queryStr);
73 | vsnprintf (buf, sizeof(buf), queryStr, args);
74 | va_end (args);
75 |
76 | // Execute the query
77 | Serial.printf("Execute SQL query: %s\n", buf);
78 | return sql.query(data, buf);
79 | }
80 | return false;
81 | }
82 |
83 | bool checkAndCreateTables() {
84 | // Create tables if not exists
85 | Serial.println("\nCreate table if not exists");
86 | DataQuery_t data;
87 | if (!queryExecute(data, createUsersTable, "users")) {
88 | if (strcmp(sql.getLastSQLSTATE(), "42S01") != 0)
89 | return false;
90 | }
91 |
92 | if (!queryExecute(data, createLogTable, "logs")) {
93 | if (strcmp(sql.getLastSQLSTATE(), "42S01") != 0)
94 | return false;
95 | }
96 |
97 | String hashed = getSHA256("admin");
98 | if (queryExecute(data, newUpdateUser, "admin", hashed.c_str(), "Update password!", "", "0", "5")) {
99 | Serial.println("admin@admin user created");
100 | }
101 | return true;
102 | }
103 |
104 |
--------------------------------------------------------------------------------
/examples/remoteOTA/data/espressif.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cotestatnt/async-esp-fs-webserver/f6303024b51f3086c6364932fa6447190c8ac852/examples/remoteOTA/data/espressif.jpg
--------------------------------------------------------------------------------
/examples/remoteOTA/data/index.htm:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | ESP Index
6 |
7 |
8 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 | ESP FS WebServer - Remote OTA update
20 |
21 |
22 |
23 | Check for New firmware
24 |
25 | Current firmware version:
26 |
27 |
28 |
29 |
30 |
31 |
32 | Toggle built-in LED
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
134 |
135 |
136 |
137 |
--------------------------------------------------------------------------------
/examples/remoteOTA/fw-esp32/firmware.bin:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cotestatnt/async-esp-fs-webserver/f6303024b51f3086c6364932fa6447190c8ac852/examples/remoteOTA/fw-esp32/firmware.bin
--------------------------------------------------------------------------------
/examples/remoteOTA/fw-esp32/readme.md:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/examples/remoteOTA/fw-esp8266/firmware.bin:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cotestatnt/async-esp-fs-webserver/f6303024b51f3086c6364932fa6447190c8ac852/examples/remoteOTA/fw-esp8266/firmware.bin
--------------------------------------------------------------------------------
/examples/remoteOTA/fw-esp8266/readme.md:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/examples/remoteOTA/version-esp32.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "1.0.1",
3 | "raw_url": "https://github.com/cotestatnt/async-esp-fs-webserver/raw/master/examples/remoteOTA/fw-esp32/firmware.bin"
4 | }
--------------------------------------------------------------------------------
/examples/remoteOTA/version-esp8266.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "1.0.1",
3 | "raw_url": "https://github.com/cotestatnt/async-esp-fs-webserver/raw/master/examples/remoteOTA/fw-esp8266/firmware.bin"
4 | }
--------------------------------------------------------------------------------
/examples/simpleServer/data/css/pico.fluid.classless.css.gz:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cotestatnt/async-esp-fs-webserver/f6303024b51f3086c6364932fa6447190c8ac852/examples/simpleServer/data/css/pico.fluid.classless.css.gz
--------------------------------------------------------------------------------
/examples/simpleServer/data/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cotestatnt/async-esp-fs-webserver/f6303024b51f3086c6364932fa6447190c8ac852/examples/simpleServer/data/favicon.ico
--------------------------------------------------------------------------------
/examples/simpleServer/data/img/espressif.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cotestatnt/async-esp-fs-webserver/f6303024b51f3086c6364932fa6447190c8ac852/examples/simpleServer/data/img/espressif.jpg
--------------------------------------------------------------------------------
/examples/simpleServer/data/index.htm:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | ESP simpleServer.ino
6 |
7 |
8 |
9 |
10 |
11 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 | ESP Async FS WebServer - LED Switcher - simpleServer.ino
35 |
36 |
37 |
38 | Toggle built-in LED
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
65 |
66 |
67 |
68 |
--------------------------------------------------------------------------------
/examples/simpleServer/simpleServer.ino:
--------------------------------------------------------------------------------
1 | #include
2 | #include
3 | #include
4 |
5 | AsyncFsWebServer server(80, LittleFS, "myServer");
6 | int testInt = 150;
7 | float testFloat = 123.456;
8 |
9 | #ifndef LED_BUILTIN
10 | #define LED_BUILTIN 2
11 | #endif
12 | const uint8_t ledPin = LED_BUILTIN;
13 |
14 |
15 | // FILESYSTEM INIT
16 | bool startFilesystem(){
17 | if (LittleFS.begin()){
18 | File root = LittleFS.open("/", "r");
19 | File file = root.openNextFile();
20 | while (file){
21 | Serial.printf("FS File: %s, size: %d\n", file.name(), file.size());
22 | file = root.openNextFile();
23 | }
24 | return true;
25 | }
26 | else {
27 | Serial.println("ERROR on mounting filesystem. It will be reformatted!");
28 | LittleFS.format();
29 | ESP.restart();
30 | }
31 | return false;
32 | }
33 |
34 |
35 | /*
36 | * Getting FS info (total and free bytes) is strictly related to
37 | * filesystem library used (LittleFS, FFat, SPIFFS etc etc) and ESP framework
38 | */
39 | #ifdef ESP32
40 | void getFsInfo(fsInfo_t* fsInfo) {
41 | fsInfo->fsName = "LittleFS";
42 | fsInfo->totalBytes = LittleFS.totalBytes();
43 | fsInfo->usedBytes = LittleFS.usedBytes();
44 | }
45 | #endif
46 |
47 |
48 | //---------------------------------------
49 | void handleLed(AsyncWebServerRequest *request) {
50 | static int value = false;
51 | // http://xxx.xxx.xxx.xxx/led?val=1
52 | if(request->hasParam("val")) {
53 | value = request->arg("val").toInt();
54 | digitalWrite(ledPin, value);
55 | }
56 | String reply = "LED is now ";
57 | reply += value ? "ON" : "OFF";
58 | request->send(200, "text/plain", reply);
59 | }
60 |
61 |
62 | void setup() {
63 | pinMode(ledPin, OUTPUT);
64 | Serial.begin(115200);
65 | delay(1000);
66 | if (startFilesystem()) {
67 | Serial.println("LittleFS filesystem ready!");
68 | File config = server.getConfigFile("r");
69 | if (config) {
70 | DynamicJsonDocument doc(config.size() * 1.33);
71 | deserializeJson(doc, config);
72 | testInt = doc["Test int variable"];
73 | testFloat = doc["Test float variable"];
74 | }
75 | Serial.printf("Stored \"testInt\" value: %d\n", testInt);
76 | Serial.printf("Stored \"testFloat\" value: %3.2f\n", testFloat);
77 | }
78 | else
79 | Serial.println("LittleFS error!");
80 |
81 | // Try to connect to WiFi (will start AP if not connected after timeout)
82 | if (!server.startWiFi(10000)) {
83 | Serial.println("\nWiFi not connected! Starting AP mode...");
84 | server.startCaptivePortal("ESP_AP", "123456789", "/setup");
85 | }
86 |
87 | server.addOptionBox("Custom options");
88 | server.addOption("Test int variable", testInt);
89 | server.addOption("Test float variable", testFloat);
90 | server.setSetupPageTitle("Simple Async ESP FS WebServer");
91 |
92 | // Enable ACE FS file web editor and add FS info callback function
93 | server.enableFsCodeEditor();
94 | #ifdef ESP32
95 | server.setFsInfoCallback(getFsInfo);
96 | #endif
97 |
98 | server.on("/led", HTTP_GET, handleLed);
99 |
100 | // Start server
101 | server.init();
102 | Serial.print(F("Async ESP Web Server started on IP Address: "));
103 | Serial.println(server.getServerIP());
104 | Serial.println(F(
105 | "This is \"simpleServer.ino\" example.\n"
106 | "Open /setup page to configure optional parameters.\n"
107 | "Open /edit page to view, edit or upload example or your custom webserver source files."
108 | ));
109 |
110 | }
111 |
112 | void loop() {
113 | // This delay is required in order to avoid loopTask() WDT reset on ESP32
114 | delay(1);
115 |
116 | }
117 |
--------------------------------------------------------------------------------
/examples/simpleServerCaptive/.vscode/arduino.json:
--------------------------------------------------------------------------------
1 | {
2 | "configuration": "JTAGAdapter=default,PSRAM=disabled,FlashMode=qio,FlashSize=4M,LoopCore=1,EventsCore=1,USBMode=hwcdc,CDCOnBoot=default,MSCOnBoot=default,DFUOnBoot=default,UploadMode=default,PartitionScheme=default,CPUFreq=240,UploadSpeed=921600,DebugLevel=none,EraseFlash=none",
3 | "board": "esp32:esp32:esp32s3",
4 | "sketch": "simpleServerCaptive.ino"
5 | }
--------------------------------------------------------------------------------
/examples/simpleServerCaptive/data/css/pico.fluid.classless.css.gz:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cotestatnt/async-esp-fs-webserver/f6303024b51f3086c6364932fa6447190c8ac852/examples/simpleServerCaptive/data/css/pico.fluid.classless.css.gz
--------------------------------------------------------------------------------
/examples/simpleServerCaptive/data/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cotestatnt/async-esp-fs-webserver/f6303024b51f3086c6364932fa6447190c8ac852/examples/simpleServerCaptive/data/favicon.ico
--------------------------------------------------------------------------------
/examples/simpleServerCaptive/data/img/espressif.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cotestatnt/async-esp-fs-webserver/f6303024b51f3086c6364932fa6447190c8ac852/examples/simpleServerCaptive/data/img/espressif.jpg
--------------------------------------------------------------------------------
/examples/simpleServerCaptive/data/index.htm:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | ESP simpleServerCaptive.ino
6 |
7 |
8 |
9 |
10 |
11 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 | ESP Async FS WebServer - LED Switcher - simpleServerCaptive.ino
35 |
36 |
37 |
38 | Toggle built-in LED
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
65 |
66 |
67 |
68 |
--------------------------------------------------------------------------------
/examples/simpleServerCaptive/simpleServerCaptive.ino:
--------------------------------------------------------------------------------
1 | #include
2 | #include
3 | #include
4 |
5 | AsyncFsWebServer server(80, LittleFS, "myServer");
6 | bool captiveRun = false;
7 |
8 | #ifndef LED_BUILTIN
9 | #define LED_BUILTIN 2
10 | #endif
11 | const int ledPin = LED_BUILTIN;
12 |
13 | //////////////////////////////// Filesystem /////////////////////////////////////////
14 | bool startFilesystem() {
15 | if (LittleFS.begin()){
16 | server.printFileList(LittleFS, "/", 1);
17 | return true;
18 | }
19 | else {
20 | Serial.println("ERROR on mounting filesystem. It will be formatted!");
21 | LittleFS.format();
22 | ESP.restart();
23 | }
24 | return false;
25 | }
26 |
27 |
28 | /*
29 | * Getting FS info (total and free bytes) is strictly related to
30 | * filesystem library used (LittleFS, FFat, SPIFFS etc etc) and ESP framework
31 | */
32 | #ifdef ESP32
33 | void getFsInfo(fsInfo_t* fsInfo) {
34 | fsInfo->fsName = "LittleFS";
35 | fsInfo->totalBytes = LittleFS.totalBytes();
36 | fsInfo->usedBytes = LittleFS.usedBytes();
37 | }
38 | #endif
39 |
40 |
41 | //---------------------------------------
42 | void handleLed(AsyncWebServerRequest *request) {
43 | static int value = false;
44 | // http://xxx.xxx.xxx.xxx/led?val=1
45 | if(request->hasParam("val")) {
46 | value = request->arg("val").toInt();
47 | digitalWrite(ledPin, value);
48 | }
49 | String reply = "LED is now ";
50 | reply += value ? "ON" : "OFF";
51 | request->send(200, "text/plain", reply);
52 | Serial.print("handleLed:");
53 | Serial.println(reply);
54 | }
55 |
56 |
57 | void setup() {
58 | pinMode(ledPin, OUTPUT);
59 | Serial.begin(115200);
60 | delay(1000);
61 |
62 | // Init and start LittleFS file system
63 | startFilesystem();
64 |
65 | // Try to connect to WiFi (will start AP if not connected after timeout)
66 | if (!server.startWiFi(10000)) {
67 | Serial.println("\nWiFi not connected! Starting AP mode...");
68 | server.startCaptivePortal("ESP_AP", "123456789", "/setup");
69 | captiveRun = true;
70 | }
71 |
72 | // Set a custom /setup page title
73 | server.setSetupPageTitle("Simple Async FS Captive Web Server");
74 |
75 | // Enable ACE FS file web editor and add FS info callback function
76 | server.enableFsCodeEditor();
77 | #ifdef ESP32
78 | server.setFsInfoCallback(getFsInfo);
79 | #endif
80 |
81 | // Add 0 callback function handler
82 | server.on("/led", HTTP_GET, handleLed);
83 |
84 | // Start server
85 | server.init();
86 | Serial.print(F("Async ESP Web Server started on IP Address: "));
87 | Serial.println(server.getServerIP());
88 | Serial.println(F(
89 | "This is \"simpleServerCaptive.ino\" example.\n"
90 | "Open /setup page to configure optional parameters.\n"
91 | "Open /edit page to view, edit or upload example or your custom webserver source files."
92 | ));
93 |
94 | }
95 |
96 | void loop() {
97 | if (captiveRun)
98 | server.updateDNS();
99 |
100 | // This delay is required in order to avoid loopTask() WDT reset on ESP32
101 | delay(1);
102 | }
103 |
--------------------------------------------------------------------------------
/examples/withWebSocket/index_htm.h:
--------------------------------------------------------------------------------
1 | static const char homepage[] PROGMEM = R"EOF(
2 |
3 |
4 |
5 |
6 | WebSocket test page
7 |
8 |
18 |
19 |
81 |
82 |
83 |
84 |
91 |
92 |
93 |
ESP current time, sync with NTP server and sent to this client via WebSocket!
94 |
Waiting websocket connection...
95 |
96 |
97 |
98 | $
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 | )EOF";
--------------------------------------------------------------------------------
/examples/withWebSocket/readme.md:
--------------------------------------------------------------------------------
1 | ## esp-fs-webserver
2 | This example is a little bit more advanced respect to the [simpleServer](https://github.com/cotestatnt/esp-fs-webserver/tree/main/examples/simpleServer) example.
3 |
4 | Basically is the same HTML code like simpleServer, but with the addition of a **WebSocket client** inside the webpage.
5 |
6 | Off course, on the ESP MCU we will run also a **WebSocket server** together to the web server.
7 | I've used the library [arduinoWebSockets](https://github.com/Links2004/arduinoWebSockets), so it is needed to compile, but you can choose which you prefer.
8 |
9 | With the help of WebSocket technology, we can send message from server-to-clients or from client-to-server in a **full-duplex communication channels over a single TCP connection.**
10 |
11 | In this very simple example is used only to push from ESP side a message (the NTP updated ESP system time) to the connected clients, just to show hot to setup a system like this.
12 |
13 | 
14 |
--------------------------------------------------------------------------------
/keywords.txt:
--------------------------------------------------------------------------------
1 | WebServer KEYWORD1
2 | webserver KEYWORD3
3 | FSWebServer KEYWORD1
4 |
5 | begin KEYWORD2
6 | run KEYWORD2
7 | addHandler KEYWORD2
8 | setCaptiveWebage KEYWORD2
9 | setAPmode KEYWORD2
10 | startWiFi KEYWORD2
11 | getRequest KEYWORD2
12 | addOptionBox KEYWORD2
13 | addJavascript KEYWORD2
14 | addHTML KEYWORD2
15 | addOption KEYWORD2
16 | getOptionValue KEYWORD2
17 | saveOptionValue KEYWORD2
18 |
--------------------------------------------------------------------------------
/library.properties:
--------------------------------------------------------------------------------
1 | name=AsyncEspFsWebserver
2 | version=2.0.2
3 | author=Tolentino Cotesta
4 | maintainer=Tolentino Cotesta
5 | sentence=Based on the library ESPAsyncWebserver
6 | paragraph=Async ESP32/ESP8266 web server, WiFi manager and ACE web text editor all in one Arduino library.
7 | category=Communication
8 | url=https://github.com/cotestatnt/async-esp-fs-webserver
9 | architectures=esp32, esp8266
10 | depends=ArduinoJson, ESPAsyncWebServer, AsyncTCP, ESPAsyncTCP
11 |
--------------------------------------------------------------------------------
/partitions-4MB.csv:
--------------------------------------------------------------------------------
1 | # Name ,Type ,SubType ,Offset ,Size ,Flags
2 | nvs ,data ,nvs ,36K ,20K ,
3 | otadata ,data ,ota ,56K ,8K ,
4 | app0 ,app ,ota_0 ,64K ,1856K ,
5 | app1 ,app ,ota_1 ,1920K ,1856K ,
6 | spiffs ,data ,spiffs ,3776K ,256K ,
7 | coredump ,data ,coredump ,4032K ,64K ,
8 |
--------------------------------------------------------------------------------
/pio_ci_ex_ALL.bat:
--------------------------------------------------------------------------------
1 | @echo off
2 | rem Build all listed examples of this library on PIO CLI; may take some time
3 | rem Verifies that library and all examples still compile
4 |
5 | setlocal EnableDelayedExpansion enableextensions
6 | set CI_DIR=%~dp0..\async-esp-fs-webserver.pio-ci
7 | set EXAMPLES=simpleServerCaptive simpleServer withWebSocket customHTML customOptions gpio_list handleFormData highcharts remoteOTA esp32-cam
8 | ::set EXAMPLES=simpleServerCaptive
9 | :: simpleServer withWebSocket customHTML
10 | set BOARDS= esp32dev esp12e
11 | set OPT_esp12e=-O "lib_deps=ArduinoJson@6.21.4"
12 | set OPT_esp32dev=-O "lib_deps=ArduinoJson@6.21.4"
13 | set NOT_esp12e=esp32-cam
14 | set NOT_esp32dev=esp8266-app
15 | set EXCLIB= --exclude=lib\*\.git* --exclude=lib\*\.pio --exclude=lib\*\.vscode --exclude=lib\*\built-in-webpages --exclude=lib\*\examples
16 |
17 | FOR %%B IN (%BOARDS%) DO (
18 | FOR %%E IN (%EXAMPLES%) DO (
19 | if %%E==!NOT_%%B! (
20 | echo ### not compiling %%E for %%B
21 | ) else (
22 | set CIEXDIR=%CI_DIR%\ci_ex_%%B_%%E
23 | IF EXIST "!CIEXDIR!" RMDIR /s/q "!CIEXDIR!"
24 | MKDIR "!CIEXDIR!"
25 | set OPT=!OPT_%%B!
26 | set OUT="!CIEXDIR!\build.out.txt"
27 | set ERR="!CIEXDIR!\build.err.txt"
28 | echo ### Compiling %%E for %%B
29 | echo +pio ci -b %%B !OPT! --keep-build-dir --build-dir="!CIEXDIR!\" --lib=. %EXCLIB% .\examples\%%E\*.* 1^>!OUT! 2^>!ERR!
30 | pio ci -b %%B !OPT! --keep-build-dir --build-dir="!CIEXDIR!" --lib=. %EXCLIB% .\examples\%%E\*.* >!OUT! 2>!ERR!
31 | FOR /f %%i IN (!ERR!) DO IF EXIST %%i IF %%~zi GTR 0 ECHO ###ERROR in %%i && TYPE %%i
32 | )
33 | echo ### DONE
34 | )
35 | )
36 |
37 | :: note activation pio verbose option '-v' generates a lot of output (~25k/compile, ~2MB/pio-ci)
38 |
39 |
--------------------------------------------------------------------------------
/platformio.ini:
--------------------------------------------------------------------------------
1 | [platformio]
2 | default_envs = arduino-2, arduino-3, esp8266
3 | lib_dir = .
4 |
5 | [env]
6 | framework = arduino
7 | platform = https://github.com/pioarduino/platform-espressif32/releases/download/53.03.13/platform-espressif32.zip
8 | board = esp32dev
9 | build_flags =
10 | -Og
11 | -Wall -Wextra
12 | -Wno-unused-parameter
13 | ; -D CONFIG_ARDUHAL_LOG_COLORS
14 | -D CORE_DEBUG_LEVEL=ARDUHAL_LOG_LEVEL_VERBOSE
15 | -D CONFIG_ASYNC_TCP_MAX_ACK_TIME=5000
16 | -D CONFIG_ASYNC_TCP_PRIORITY=10
17 | -D CONFIG_ASYNC_TCP_QUEUE_SIZE=64
18 | -D CONFIG_ASYNC_TCP_RUNNING_CORE=1
19 | -D CONFIG_ASYNC_TCP_STACK_SIZE=4096
20 | upload_protocol = esptool
21 | monitor_speed = 115200
22 | monitor_filters = esp32_exception_decoder, log2file
23 | ; monitor_filters = esp8266_exception_decoder, log2file
24 | lib_compat_mode = strict
25 | lib_ldf_mode = chain
26 | lib_deps =
27 | bblanchon/ArduinoJson @ 7.3.1
28 | ESP32Async/AsyncTCP @ 3.3.7
29 | ESP32Async/ESPAsyncWebServer @ 3.7.3
30 | https://github.com/cotestatnt/Arduino-MySQL.git
31 | https://github.com/OSSLibraries/Arduino_MFRC522v2.git
32 |
33 | board_build.partitions = partitions-4MB.csv
34 | board_build.filesystem = littlefs
35 |
36 | [env:arduino-2]
37 | platform = espressif32@6.10.0
38 |
39 | [env:arduino-3]
40 |
41 | [env:arduino-3-latest]
42 | platform = https://github.com/pioarduino/platform-espressif32/releases/download/54.03.20-rc1/platform-espressif32.zip
43 |
44 | [env:arduino-3-latest-asynctcp]
45 | lib_deps =
46 | https://github.com/ESP32Async/AsyncTCP
47 | https://github.com/ESP32Async/ESPAsyncWebServer
48 | https://github.com/cotestatnt/Arduino-MySQL
49 | https://github.com/OSSLibraries/Arduino_MFRC522v2
50 |
51 | [env:arduino-3-no-chunk-inflight]
52 | build_flags = ${env.build_flags}
53 | -D ASYNCWEBSERVER_USE_CHUNK_INFLIGHT=0
54 |
55 | [env:AsyncTCPSock]
56 | lib_deps =
57 | https://github.com/ESP32Async/AsyncTCPSock/archive/refs/tags/v1.0.3-dev.zip
58 | build_flags = ${env.build_flags}
59 |
60 | [env:esp8266]
61 | platform = espressif8266
62 | ; board = huzzah
63 | board = d1_mini
64 | lib_deps =
65 | bblanchon/ArduinoJson @ 7.3.1
66 | ESP32Async/ESPAsyncTCP @ 2.0.0
67 | ESP32Async/ESPAsyncWebServer @ 3.7.3
68 | https://github.com/cotestatnt/Arduino-MySQL.git
69 | https://github.com/OSSLibraries/Arduino_MFRC522v2.git
70 |
71 | [env:ci-arduino-2]
72 | platform = espressif32@6.10.0
73 | board = ${sysenv.PIO_BOARD}
74 |
75 | [env:ci-arduino-3]
76 | board = ${sysenv.PIO_BOARD}
77 |
78 | [env:ci-arduino-3-latest]
79 | platform = https://github.com/pioarduino/platform-espressif32/releases/download/54.03.20-rc1/platform-espressif32.zip
80 | board = ${sysenv.PIO_BOARD}
81 |
82 | [env:ci-arduino-3-latest-asynctcp]
83 | lib_deps =
84 | bblanchon/ArduinoJson @ 7.3.1
85 | https://github.com/ESP32Async/AsyncTCP
86 | https://github.com/ESP32Async/ESPAsyncWebServer
87 | https://github.com/cotestatnt/Arduino-MySQL.git
88 | https://github.com/OSSLibraries/Arduino_MFRC522v2.git
89 |
90 | [env:ci-arduino-3-no-chunk-inflight]
91 | board = ${sysenv.PIO_BOARD}
92 | build_flags = ${env.build_flags}
93 | -D ASYNCWEBSERVER_USE_CHUNK_INFLIGHT=1
94 |
95 | [env:ci-esp8266]
96 | platform = espressif8266
97 | board = ${sysenv.PIO_BOARD}
98 | lib_deps =
99 | bblanchon/ArduinoJson @ 7.3.1
100 | ESP32Async/ESPAsyncTCP @ 2.0.0
101 | ESP32Async/ESPAsyncWebServer @ 3.7.3
102 |
103 |
--------------------------------------------------------------------------------
/src/CaptivePortal.hpp:
--------------------------------------------------------------------------------
1 |
2 | // Captive Portal - what is needed
3 | // - DNS Server + Catch-all Handler class
4 | // - OR: DNS-Server + device specific request handlers (/generate_204, /success.html, etc.)
5 | // -> these seem to work with recent stock Android and iPhones
6 | //
7 | // - IP-config seems to be necessary for all Samsung phones (since they use hard-coded
8 | // DNS for connectivitycheck.gstatic.com)
9 |
10 | #ifdef ESP32
11 | #include
12 | #elif defined(ESP8266)
13 | #include
14 | #else
15 | #error "Captive portal needs ESP32 or ESP8266 platform"
16 | #endif
17 | #include
18 | #include "ESPAsyncWebServer.h"
19 | #include "AsyncFsWebServer.h"
20 |
21 |
22 | // Inspiration: https://github.com/andig/vzero/blob/master/src/webserver.cpp
23 | class CaptiveRequestHandler : public AsyncWebHandler
24 | {
25 | public:
26 | explicit CaptiveRequestHandler(String redirectTargetURL) :
27 | targetURL("http://" + WiFi.softAPIP().toString() + redirectTargetURL)
28 | {
29 | }
30 | virtual ~CaptiveRequestHandler() {}
31 |
32 | const String targetURL;
33 |
34 | bool canHandle(AsyncWebServerRequest *request) const override {
35 | if (request->host() != WiFi.softAPIP().toString())
36 | return true;
37 | else
38 | return false;
39 | }
40 |
41 | void handleRequest(AsyncWebServerRequest *request) override
42 | {
43 | request->redirect(targetURL);
44 | log_info("Captive handler triggered. Requested %s%s -> redirecting to %s", request->host().c_str(), request->url().c_str(), targetURL.c_str());
45 | }
46 | };
47 |
--------------------------------------------------------------------------------
/src/SerialLog.h:
--------------------------------------------------------------------------------
1 | #ifndef __SERIALLOG_H__
2 | #define __SERIALLOG_H__
3 |
4 | #include
5 | #include
6 |
7 | #ifdef __cplusplus
8 | extern "C"
9 | {
10 | #endif
11 |
12 | #define DBG_OUTPUT_PORT Serial
13 | #define LOG_LEVEL 1 // (0 disable, 1 error, 2 info, 3 debug)
14 |
15 | #define __SOURCE_FILE_NAME__ (strrchr(__FILE__, '\\') ? strrchr(__FILE__, '\\') + 1 : __FILE__)
16 | #define _LOG_FORMAT(letter, format) "\n["#letter"][%s:%u] %s():\t" format, __SOURCE_FILE_NAME__, __LINE__, __FUNCTION__
17 |
18 | #if LOG_LEVEL == 0
19 | #define log_error(format, ...)
20 | #define log_info(format, ...)
21 | #define log_debug(format, ...)
22 | #endif
23 |
24 | #if LOG_LEVEL > 0
25 | #define log_error(format, ...) DBG_OUTPUT_PORT.printf(_LOG_FORMAT(E, format), ##__VA_ARGS__)
26 | #define log_info(format, ...)
27 | #define log_debug(format, ...)
28 | #endif
29 |
30 | #if LOG_LEVEL > 1
31 | #undef log_info
32 | #define log_info(format, ...) DBG_OUTPUT_PORT.printf(_LOG_FORMAT(I, format), ##__VA_ARGS__)
33 | #endif
34 |
35 | #if LOG_LEVEL > 2
36 | #undef log_debug
37 | #define log_debug(format, ...) DBG_OUTPUT_PORT.printf(_LOG_FORMAT(D, format), ##__VA_ARGS__)
38 | #endif
39 |
40 |
41 |
42 | #ifdef __cplusplus
43 | }
44 | #endif
45 |
46 | #endif
--------------------------------------------------------------------------------
/src/Version.cpp:
--------------------------------------------------------------------------------
1 | #include "AsyncFsWebServer.h"
2 | const char* AsyncFsWebServer::getVersion() {
3 | return "2.0.2";
4 | }
--------------------------------------------------------------------------------