├── .gitignore ├── .gitattributes ├── data ├── edit.htm.gz ├── index.htm.gz └── config.jsn ├── LICENSE ├── README.md └── IFTTT-Dash-Button.ino /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | .vscode/arduino.json 3 | .vscode/c_cpp_properties.json 4 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /data/edit.htm.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Luigi-Pizzolito/IFTTT-Dash-Button/HEAD/data/edit.htm.gz -------------------------------------------------------------------------------- /data/index.htm.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Luigi-Pizzolito/IFTTT-Dash-Button/HEAD/data/index.htm.gz -------------------------------------------------------------------------------- /data/config.jsn: -------------------------------------------------------------------------------- 1 | { 2 | "ssid": "SSID Here", 3 | "pass": "Password Here", 4 | "host": "examplewebsite.com", 5 | "is_ip": false, 6 | "uri": "/makemyrequest/", 7 | "wc_p": 10, 8 | "gr_p": 10, 9 | "s_vcc": false, 10 | "vcc_p": "batt" 11 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Luigi Pizzolito 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # IFTTT Dash Button 2 | Push a button, do a GET Request. Save battery to last ages. 3 | More documentation on [Instructables](https://www.instructables.com/id/Tiny-ESP8266-Dash-Button-Re-Configurable/). 4 | 5 | - [ESP-12E Weather Station (in development)](#esp-12e-weather-station-in-development) 6 | - [Planned Features (And Progress)](#planned-features-and-progress) 7 | - [GET Requests](#get-requests) 8 | - [Triggering Actions](#triggering-actions) 9 | - [Monitoring Battery (in progress)](#monitoring-battery-in-progress) 10 | - [Power Saving](#power-saving) 11 | - [OTA / WebUpdate (in progress)](#ota-webupdate-in-progress) 12 | - [Useful Links and References](#useful-links-and-references) 13 | 14 | ## Features 15 | - [X] GET Requests 16 | - [X] Trigger Action 17 | - [X] Monitor Battery 18 | - [X] Power Saving 19 | - [X] Reconfigurable 20 | 21 | ## About 22 | Inspired by Bitluni's Lab. This is a tiny dashbutton. That connects to IFTTT, and saves battery. The code has been extended to make it easy to use and universally configurable without the need for re-programming. Just flash the `.bin` file from the releases page and the SPIFFS data, enter configuration mode, set it up and you are ready to go. 23 | ![Built Button](https://luigi-pizzolito.github.io/Gangster45671.github.io/IFTTT-Dash-Button/pictures/20180220_163702.jpg) 24 | 25 | ### GET Requests 26 | When the button is pushed a GET request is made to a webpage. 27 | #### Triggering Actions 28 | Depending on the webpage, many different actions can be triggered by the button. I suggest hooking it up to the IFTTT Maker WebHooks. 29 | #### Monitoring Battery 30 | When a request is made, if the setting is set, the button will also pass on the battery voltage with your web request. This way you can monitor the battery's charge. The server will recieve: 31 | > yoururl.com/yourrequest/_?VCC_Param.=_**VCC_Voltage** 32 | 33 | ### Power Saving 34 | To keep the button down to a small size, a small battery needs to be used. To maintain a decent battery life, the ESP is almost always in deep-sleep mode. Pressing the button resets the ESP. The ESP reboots, makes a GET request and goes back to deep-sleep. 35 | 36 | ### Configuration 37 | You dont need to take apart your button to re-program the url or action. If you connect `GPIO_03[RX]` to `GND` during startup the button will enter configuration mode. Then you can 38 | 1. Connect to 'ESP_Button' WiFi Access Point, with the password 'wifibutton' 39 | 2. Visit http://192.168.4.1 to open the configuration page 40 | 3. After setting your values, click on the 'Save' button then the 'Restart' 41 | ![Configuration Interface](https://luigi-pizzolito.github.io/Gangster45671.github.io/IFTTT-Dash-Button/pictures/Config.png) 42 | 43 | ## Useful Links and References 44 | - Similliar Projects 45 | - [Bitluni's DashButton] (https://github.com/bitluni/wifiButton) 46 | - ESP Info 47 | - Pinouts 48 | - [ESP-12 Pinout](https://esp8266.github.io/Arduino/versions/2.0.0/doc/esp12.png) 49 | - [ESP-01 Pinout](https://os.mbed.com/media/uploads/sschocke/esp8266-pinout_etch_copper_top.png) 50 | - General Guides 51 | - [Sparkfun Guide](https://learn.sparkfun.com/tutorials/esp8266-thing-hookup-guide/using-the-arduino-addon) 52 | - [Excellent Beginner's Guide](https://github.com/tttapa/ESP8266) 53 | - GitHub Markdown Info 54 | - [Formatting and Syntax](https://help.github.com/articles/basic-writing-and-formatting-syntax/) 55 | - [Emoji Cheat Sheet](https://www.webpagefx.com/tools/emoji-cheat-sheet/) 56 | -------------------------------------------------------------------------------- /IFTTT-Dash-Button.ino: -------------------------------------------------------------------------------- 1 | /* 2 | Libraries needed 3 | */ 4 | //Normal Mode 5 | #include 6 | #include 7 | //Config. Mode 8 | #include 9 | #include 10 | #include 11 | 12 | /* 13 | Configurable Settings 14 | 15 | Connect CONFIG_PIN(Default GPIO_03[RX]) to GND during startup to enter Config. Mode: 16 | 1. Connect to 'ESP_Button' WiFi Access Point, with the password 'wifibutton' 17 | 2. Visit http://192.168.4.1 to open the configuration page, or http://192.168.4.1/edit to see the filesystem. 18 | 3. After seeting your values, click on the 'Save' button then the 'Restart' 19 | 4. Your button is now configured. 20 | */ 21 | String ssid; 22 | String password; 23 | String host; 24 | String url; 25 | int wc_p; // max. time in seconds to connect to wifi, before giving up 26 | int gr_p; // max. times of attemps to perform GET request, before giving up 27 | bool s_vcc; //wether to send VCC voltage as a parameter in the url request. 28 | bool is_ip; //wether host adress is IP 29 | String vcc_parm; //parameter to pass VCC voltage by. 30 | 31 | /* 32 | System Variables 33 | */ 34 | //#define NOT_DEBUG //wether to enable debug or to show indication lights instead 35 | //Normal Mode 36 | int failCount = 0; 37 | ADC_MODE(ADC_VCC); 38 | bool su_mode = true; 39 | //Config. Mode 40 | #define CONFIG_PIN 3 41 | ESP8266WebServer server(80); 42 | File fsUploadFile; 43 | const char *APssid = "ESP_Button"; 44 | const char *APpass = "wifibutton"; 45 | 46 | void setup() 47 | { 48 | //start serial monitor, SPIFFS and Config. Pin 49 | Serial.begin(115200); 50 | pinMode(CONFIG_PIN, INPUT_PULLUP); 51 | delay(10); 52 | SPIFFS.begin(); 53 | Serial.println(); 54 | Serial.println("Button Booting..."); 55 | Serial.println("SPIFFS Content: "); 56 | { 57 | Dir dir = SPIFFS.openDir("/"); 58 | while (dir.next()) 59 | { 60 | String fileName = dir.fileName(); 61 | size_t fileSize = dir.fileSize(); 62 | Serial.printf("FS File: %s, size: %s\n", fileName.c_str(), formatBytes(fileSize).c_str()); 63 | } 64 | Serial.printf("\n"); 65 | } 66 | 67 | //read and parse config.json from SPIFFS 68 | readConfig(); 69 | 70 | //read Config. Pin 71 | su_mode = !digitalRead(CONFIG_PIN); 72 | if (su_mode) 73 | { 74 | 75 | //start Config. Mode 76 | #ifdef NOT_DEBUG 77 | pinMode(LED_BUILTIN, OUTPUT); 78 | digitalWrite(LED_BUILTIN, LOW); 79 | #endif 80 | Serial.println("Entering Config. Mode!"); 81 | 82 | //start WiFi Access Point 83 | Serial.println("Configuring access point..."); 84 | WiFi.mode(WIFI_AP_STA); 85 | WiFi.softAP(APssid, APpass); 86 | 87 | IPAddress myIP = WiFi.softAPIP(); 88 | Serial.print("AP IP address: "); 89 | Serial.println(myIP); 90 | 91 | //start HTTP server 92 | 93 | //edit pages 94 | server.on("/list", HTTP_GET, handleFileList); 95 | server.on("/edit", HTTP_GET, []() { 96 | if (!handleFileRead("/edit.htm")) 97 | server.send(404, "text/plain", "FileNotFound"); 98 | }); 99 | server.on("/edit", HTTP_PUT, handleFileCreate); 100 | server.on("/edit", HTTP_DELETE, handleFileDelete); 101 | server.on("/edit", HTTP_POST, []() { server.send(200, "text/plain", ""); }, handleFileUpload); 102 | 103 | //pages from SPIFFS 104 | server.onNotFound([]() { 105 | if (!handleFileRead(server.uri())) 106 | { 107 | server.send(404, "text/plain", "FileNotFound"); 108 | } 109 | }); 110 | 111 | server.begin(); 112 | Serial.println("HTTP server started"); 113 | } 114 | else 115 | { 116 | //start Normal Mode 117 | 118 | //connect to WiFi 119 | Serial.println(); 120 | Serial.println(); 121 | Serial.print("Connecting to "); 122 | Serial.println(ssid); 123 | 124 | WiFi.mode(WIFI_STA); 125 | WiFi.begin(ssid.c_str(), password.c_str()); 126 | 127 | while (WiFi.status() != WL_CONNECTED) 128 | { 129 | delay(500); 130 | Serial.print("."); 131 | //if we are taking too long to connect to WiFi give up. 132 | failCount++; 133 | if (failCount == wc_p * 2) 134 | { 135 | Serial.println("Session Terminated. Giving up after 21 tries connecting to WiFi."); 136 | Serial.println("entering deep sleep"); 137 | delay(20); 138 | fail(); 139 | ESP.deepSleep(0); 140 | } 141 | } 142 | 143 | Serial.println(""); 144 | Serial.println("WiFi connected"); 145 | Serial.print("IP address: "); 146 | Serial.println(WiFi.localIP()); 147 | failCount = 0; 148 | } 149 | } 150 | 151 | void loop() 152 | { 153 | if (su_mode) 154 | { 155 | server.handleClient(); 156 | } 157 | else 158 | { 159 | //if we have tried too many times to make a GET Request give up. 160 | ++failCount; 161 | if (failCount == gr_p + 1) 162 | { 163 | Serial.println("Session Terminated. Giving up after 10 tries doing GET Request."); 164 | Serial.println("entering deep sleep"); 165 | delay(20); 166 | fail(); 167 | ESP.deepSleep(0); 168 | } 169 | 170 | Serial.print("Try: "); 171 | Serial.println(failCount); 172 | Serial.print("connecting to "); 173 | Serial.println(host); 174 | 175 | //try to connect to the host with TCP 176 | WiFiClient client; 177 | const int httpPort = 80; 178 | if (is_ip) 179 | { 180 | IPAddress addr; 181 | if (addr.fromString(host)) 182 | { 183 | if (!client.connect(addr, httpPort)) 184 | { 185 | //try again if the connection fails. 186 | Serial.println("connection to IP failed"); 187 | delay(10); 188 | return; 189 | } 190 | } 191 | else 192 | { 193 | Serial.println("failed to convert IP String to IP Address."); 194 | while (1) 195 | ; 196 | } 197 | } 198 | else 199 | { 200 | if (!client.connect(host.c_str(), httpPort)) 201 | { 202 | //try again if the connection fails. 203 | Serial.println("connection failed"); 204 | delay(10); 205 | return; 206 | } 207 | } 208 | 209 | //create the URI for the request 210 | if (s_vcc) 211 | { 212 | if (url.indexOf("?") > 0) { 213 | // if the configured url already has a querystring, append an ampersand 214 | url += "&"; 215 | } else { 216 | // otherwise start the new querystring 217 | url += "?"; 218 | } 219 | url += vcc_parm; 220 | url += "="; 221 | uint32_t getVcc = ESP.getVcc(); 222 | String VccVol = String((getVcc / 1000U) % 10) + "." + String((getVcc / 100U) % 10) + String((getVcc / 10U) % 10) + String((getVcc / 1U) % 10); 223 | url += VccVol; 224 | } 225 | 226 | //request url to server 227 | Serial.print("Requesting URL: "); 228 | Serial.println(url); 229 | 230 | client.print(String("GET ") + url + " HTTP/1.1\r\n" + 231 | "Host: " + host.c_str() + "\r\n" + 232 | "Connection: close\r\n\r\n"); 233 | 234 | unsigned long timeout = millis(); 235 | while (client.available() == 0) 236 | { 237 | if (millis() - timeout > 60000) 238 | { 239 | //give up if the server takes too long to reply. 240 | Serial.println(">>> Client Timeout !"); 241 | client.stop(); 242 | //return; 243 | Serial.println("entering deep sleep"); 244 | ESP.deepSleep(0); 245 | } 246 | } 247 | 248 | //print response to serial 249 | while (client.available()) 250 | { 251 | String line = client.readStringUntil('\r'); 252 | Serial.print(line); 253 | } 254 | //finish request and close connections 255 | client.stop(); 256 | Serial.println(); 257 | Serial.println("closing connection"); 258 | 259 | //enter Deep Sleep 260 | Serial.println("entering deep sleep"); 261 | delay(100); 262 | yay(); 263 | ESP.deepSleep(0); 264 | } 265 | } 266 | 267 | /* 268 | Universal Functions 269 | */ 270 | 271 | void fail() 272 | { 273 | //something has gone wrong, blink an indicator on the LED. 274 | pinMode(LED_BUILTIN, OUTPUT); 275 | digitalWrite(LED_BUILTIN, LOW); 276 | delay(250); 277 | digitalWrite(LED_BUILTIN, HIGH); 278 | delay(250); 279 | digitalWrite(LED_BUILTIN, LOW); 280 | delay(250); 281 | digitalWrite(LED_BUILTIN, HIGH); 282 | delay(250); 283 | digitalWrite(LED_BUILTIN, LOW); 284 | delay(250); 285 | digitalWrite(LED_BUILTIN, HIGH); 286 | delay(250); 287 | digitalWrite(LED_BUILTIN, LOW); 288 | delay(250); 289 | digitalWrite(LED_BUILTIN, HIGH); 290 | delay(250); 291 | digitalWrite(LED_BUILTIN, LOW); 292 | delay(250); 293 | digitalWrite(LED_BUILTIN, HIGH); 294 | delay(250); 295 | digitalWrite(LED_BUILTIN, LOW); 296 | delay(500); 297 | digitalWrite(LED_BUILTIN, HIGH); 298 | } 299 | 300 | void yay() 301 | { 302 | //everything worked, blink an indicator on the LED. 303 | pinMode(LED_BUILTIN, OUTPUT); 304 | digitalWrite(LED_BUILTIN, LOW); 305 | delay(1500); 306 | digitalWrite(LED_BUILTIN, HIGH); 307 | delay(250); 308 | digitalWrite(LED_BUILTIN, LOW); 309 | delay(350); 310 | digitalWrite(LED_BUILTIN, HIGH); 311 | } 312 | 313 | void readConfig() 314 | { 315 | //read config.json and load configuration to variables. 316 | File configFile = SPIFFS.open("/config.jsn", "r"); 317 | if (!configFile) 318 | { 319 | Serial.println("Failed to open config file"); 320 | while (1) 321 | ; 322 | } 323 | size_t size = configFile.size(); 324 | if (size > 1024) 325 | { 326 | Serial.println("Config file size is too large"); 327 | while (1) 328 | ; 329 | } 330 | std::unique_ptr buf(new char[size]); 331 | configFile.readBytes(buf.get(), size); 332 | StaticJsonBuffer<300> jsonBuffer; 333 | JsonObject &json = jsonBuffer.parseObject(buf.get()); 334 | if (!json.success()) 335 | { 336 | Serial.println("Failed to parse config file"); 337 | while (1) 338 | ; 339 | } 340 | 341 | ssid = (const char *)json["ssid"]; 342 | password = (const char *)json["pass"]; 343 | host = (const char *)json["host"]; 344 | url = (const char *)json["uri"]; 345 | wc_p = json["wc_p"]; 346 | gr_p = json["gr_p"]; 347 | s_vcc = json["s_vcc"]; 348 | is_ip = json["is_ip"]; 349 | vcc_parm = (const char *)json["vcc_p"]; 350 | 351 | Serial.println("Parsed JSON Config."); 352 | Serial.print("Loaded ssid: "); 353 | Serial.println(ssid); 354 | Serial.print("Loaded password: "); 355 | Serial.println(password); 356 | Serial.print("Loaded host: "); 357 | Serial.println(host); 358 | Serial.print("Loaded IsIP: "); 359 | Serial.println(is_ip); 360 | Serial.print("Loaded uri: "); 361 | Serial.println(url); 362 | Serial.print("Loaded WiFi Connect Persistance: "); 363 | Serial.println(wc_p); 364 | Serial.print("Loaded GET Request Persistance: "); 365 | Serial.println(gr_p); 366 | Serial.print("Loaded Send VCC: "); 367 | Serial.println(s_vcc); 368 | Serial.print("Loaded VCC Param.: "); 369 | Serial.println(vcc_parm); 370 | Serial.println(); 371 | } 372 | 373 | /* 374 | Config. Mode Functions 375 | */ 376 | 377 | //edit functions 378 | String formatBytes(size_t bytes) 379 | { 380 | if (bytes < 1024) 381 | { 382 | return String(bytes) + "B"; 383 | } 384 | else if (bytes < (1024 * 1024)) 385 | { 386 | return String(bytes / 1024.0) + "KB"; 387 | } 388 | else if (bytes < (1024 * 1024 * 1024)) 389 | { 390 | return String(bytes / 1024.0 / 1024.0) + "MB"; 391 | } 392 | else 393 | { 394 | return String(bytes / 1024.0 / 1024.0 / 1024.0) + "GB"; 395 | } 396 | } 397 | 398 | String getContentType(String filename) 399 | { 400 | if (server.hasArg("download")) 401 | return "application/octet-stream"; 402 | else if (filename.endsWith(".htm")) 403 | return "text/html"; 404 | else if (filename.endsWith(".html")) 405 | return "text/html"; 406 | else if (filename.endsWith(".css")) 407 | return "text/css"; 408 | else if (filename.endsWith(".js")) 409 | return "application/javascript"; 410 | else if (filename.endsWith(".png")) 411 | return "image/png"; 412 | else if (filename.endsWith(".gif")) 413 | return "image/gif"; 414 | else if (filename.endsWith(".jpg")) 415 | return "image/jpeg"; 416 | else if (filename.endsWith(".ico")) 417 | return "image/x-icon"; 418 | else if (filename.endsWith(".xml")) 419 | return "text/xml"; 420 | else if (filename.endsWith(".pdf")) 421 | return "application/x-pdf"; 422 | else if (filename.endsWith(".zip")) 423 | return "application/x-zip"; 424 | else if (filename.endsWith(".gz")) 425 | return "application/x-gzip"; 426 | return "text/plain"; 427 | } 428 | 429 | bool handleFileRead(String path) 430 | { 431 | Serial.println("handleFileRead: " + path); 432 | if ((path == "/") && (server.argName(0) == "restart" && server.arg(0) == "true")) 433 | { 434 | Serial.println(F("requested reset from admin page!")); 435 | server.send(200, "text/plain", "Restarting!"); 436 | #ifdef NOT_DEBUG 437 | digitalWrite(LED_BUILTIN, HIGH); 438 | #endif 439 | delay(200); 440 | wdt_reset(); 441 | ESP.restart(); 442 | while (1) 443 | { 444 | wdt_reset(); 445 | } 446 | //WiFi.forceSleepBegin(); wdt_reset(); ESP.restart(); while (1)wdt_reset(); 447 | } 448 | if (path.endsWith("/")) 449 | path += "index.htm"; 450 | String contentType = getContentType(path); 451 | String pathWithGz = path + ".gz"; 452 | if (SPIFFS.exists(pathWithGz) || SPIFFS.exists(path)) 453 | { 454 | if (SPIFFS.exists(pathWithGz)) 455 | path += ".gz"; 456 | File file = SPIFFS.open(path, "r"); 457 | size_t sent = server.streamFile(file, contentType); 458 | file.close(); 459 | return true; 460 | } 461 | return false; 462 | } 463 | 464 | void handleFileUpload() 465 | { 466 | if (server.uri() != "/edit") 467 | return; 468 | HTTPUpload &upload = server.upload(); 469 | if (upload.status == UPLOAD_FILE_START) 470 | { 471 | String filename = upload.filename; 472 | if (!filename.startsWith("/")) 473 | filename = "/" + filename; 474 | Serial.print("handleFileUpload Name: "); 475 | Serial.println(filename); 476 | fsUploadFile = SPIFFS.open(filename, "w"); 477 | filename = String(); 478 | } 479 | else if (upload.status == UPLOAD_FILE_WRITE) 480 | { 481 | //Serial.print("handleFileUpload Data: "); Serial.println(upload.currentSize); 482 | if (fsUploadFile) 483 | fsUploadFile.write(upload.buf, upload.currentSize); 484 | } 485 | else if (upload.status == UPLOAD_FILE_END) 486 | { 487 | if (fsUploadFile) 488 | fsUploadFile.close(); 489 | Serial.print("handleFileUpload Size: "); 490 | Serial.println(upload.totalSize); 491 | } 492 | } 493 | 494 | void handleFileDelete() 495 | { 496 | if (server.args() == 0) 497 | return server.send(500, "text/plain", "BAD ARGS"); 498 | String path = server.arg(0); 499 | Serial.println("handleFileDelete: " + path); 500 | if (path == "/") 501 | return server.send(500, "text/plain", "BAD PATH"); 502 | if (!SPIFFS.exists(path)) 503 | return server.send(404, "text/plain", "FileNotFound"); 504 | SPIFFS.remove(path); 505 | server.send(200, "text/plain", ""); 506 | path = String(); 507 | } 508 | 509 | void handleFileCreate() 510 | { 511 | if (server.args() == 0) 512 | return server.send(500, "text/plain", "BAD ARGS"); 513 | String path = server.arg(0); 514 | Serial.println("handleFileCreate: " + path); 515 | if (path == "/") 516 | return server.send(500, "text/plain", "BAD PATH"); 517 | if (SPIFFS.exists(path)) 518 | return server.send(500, "text/plain", "FILE EXISTS"); 519 | File file = SPIFFS.open(path, "w"); 520 | if (file) 521 | file.close(); 522 | else 523 | return server.send(500, "text/plain", "CREATE FAILED"); 524 | server.send(200, "text/plain", ""); 525 | path = String(); 526 | } 527 | 528 | void handleFileList() 529 | { 530 | if (!server.hasArg("dir")) 531 | { 532 | server.send(500, "text/plain", "BAD ARGS"); 533 | return; 534 | } 535 | 536 | String path = server.arg("dir"); 537 | Serial.println("handleFileList: " + path); 538 | Dir dir = SPIFFS.openDir(path); 539 | path = String(); 540 | 541 | String output = "["; 542 | while (dir.next()) 543 | { 544 | File entry = dir.openFile("r"); 545 | if (output != "[") 546 | output += ','; 547 | bool isDir = false; 548 | output += "{\"type\":\""; 549 | output += (isDir) ? "dir" : "file"; 550 | output += "\",\"name\":\""; 551 | output += String(entry.name()).substring(1); 552 | output += "\"}"; 553 | entry.close(); 554 | } 555 | 556 | output += "]"; 557 | server.send(200, "text/json", output); 558 | } 559 | --------------------------------------------------------------------------------