/ Adafruit_ESP32S2_TFT_WebServer / Adafruit_ESP32S2_TFT_WebServer.ino
Adafruit_ESP32S2_TFT_WebServer.ino
1 // SPDX-FileCopyrightText: 2015 Hristo Gochkov 2 // 3 // SPDX-License-Identifier: LGPL-2.1-or-later 4 5 /* 6 TFT SPI Flash Server - Example WebServer with internal SPI flash storage 7 for Adafruit ESP32-S2 TFT Feather 8 9 Copyright (c) 2015 Hristo Gochkov. All rights reserved. 10 This file is part of the WebServer library for Arduino environment. 11 12 This library is free software; you can redistribute it and/or 13 modify it under the terms of the GNU Lesser General Public 14 License as published by the Free Software Foundation; either 15 version 2.1 of the License, or (at your option) any later version. 16 17 This library is distributed in the hope that it will be useful, 18 but WITHOUT ANY WARRANTY; without even the implied warranty of 19 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 20 Lesser General Public License for more details. 21 22 You should have received a copy of the GNU Lesser General Public 23 License along with this library; if not, write to the Free Software 24 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA 25 */ 26 27 #include <WiFi.h> 28 #include <WiFiClient.h> 29 #include <WebServer.h> 30 #include <ESPmDNS.h> 31 #include <SPI.h> 32 #include "SdFat.h" 33 #include "Adafruit_SPIFlash.h" 34 #include "Adafruit_TinyUSB.h" 35 #include <Adafruit_ST7789.h> 36 #include <Fonts/FreeSans9pt7b.h> 37 #include <ArduinoJson.h> 38 39 const char *secrets_filename = "secrets.json"; 40 41 #define DEFAULT_SSID "MY_SSID" 42 char ssid[80] = DEFAULT_SSID; 43 44 #define DEFAULT_PASSWORD "MY_PASSWORD" 45 char password[80] = DEFAULT_PASSWORD; 46 47 #define DEFAULT_AP "MY_AP" 48 char ap[80] = DEFAULT_SSID; 49 50 #define DEFAULT_AP_PASSWORD "MY_AP_PASSWORD" 51 char ap_password[80] = DEFAULT_PASSWORD; 52 53 #define DEFAULT_HOSTNAME "esp32sd" 54 char hostname[80] = DEFAULT_HOSTNAME; 55 56 StaticJsonDocument<512> doc; 57 58 #define EXPOSE_FS_ON_MSD 59 volatile bool fs_changed = false; 60 61 #define DBG_OUTPUT_PORT Serial 62 Adafruit_ST7789 display = Adafruit_ST7789(TFT_CS, TFT_DC, TFT_RST); 63 64 Adafruit_FlashTransport_ESP32 flashTransport; // internal SPI flash access 65 Adafruit_SPIFlash flash(&flashTransport); 66 FatFileSystem fatfs; // file system object from SdFat 67 68 #if defined(EXPOSE_FS_ON_MSD) 69 // USB Mass Storage object 70 Adafruit_USBD_MSC usb_msc; 71 #endif 72 73 WebServer server(80); 74 75 76 77 void setup(void) { 78 #if !defined(EXPOSE_FS_ON_MSD) 79 DBG_OUTPUT_PORT.begin(115200); 80 while (!DBG_OUTPUT_PORT) delay(10); 81 delay(1000); 82 #endif 83 84 // turn on TFT by default 85 pinMode(TFT_I2C_POWER, OUTPUT); 86 digitalWrite(TFT_I2C_POWER, HIGH); 87 pinMode(TFT_BACKLITE, OUTPUT); 88 digitalWrite(TFT_BACKLITE, LOW); 89 delay(10); 90 display.init(135, 240); // Init ST7789 240x135 91 display.setRotation(3); 92 display.fillScreen(ST77XX_BLACK); 93 digitalWrite(TFT_BACKLITE, HIGH); 94 95 display.setTextColor(ST77XX_WHITE); 96 display.setCursor(0, 0); 97 display.setTextSize(3); 98 99 char *projectname = "WordGuesser!"; 100 for (uint i=0; i<strlen(projectname); i++) { 101 if (i % 3 == 0) display.setTextColor(ST77XX_GREEN); 102 if (i % 3 == 1) display.setTextColor(ST77XX_YELLOW); 103 if (i % 3 == 2) display.setTextColor(ST77XX_BLUE); 104 display.print(projectname[i]); 105 } 106 107 display.setFont(&FreeSans9pt7b); 108 display.setTextSize(1); 109 display.setTextColor(ST77XX_WHITE); 110 display.setCursor(0, 40); 111 112 if (!flash.begin()) { 113 DBG_OUTPUT_PORT.println("failed to load flash"); 114 display.setTextColor(ST77XX_RED); 115 display.println("Failed to load flash"); 116 while (1) yield(); 117 } 118 119 #if defined(EXPOSE_FS_ON_MSD) 120 // Set disk vendor id, product id and revision with string up to 8, 16, 4 characters respectively 121 usb_msc.setID("Adafruit", "External Flash", "1.0"); 122 123 // Set callback 124 usb_msc.setReadWriteCallback(msc_read_cb, msc_write_cb, msc_flush_cb); 125 126 // Set disk size, block size should be 512 regardless of spi flash page size 127 usb_msc.setCapacity(flash.size()/512, 512); 128 129 // MSC is ready for read/write 130 usb_msc.setUnitReady(true); 131 132 usb_msc.begin(); 133 #endif 134 135 // Init file system on the flash 136 fatfs.begin(&flash); 137 138 DBG_OUTPUT_PORT.begin(115200); 139 DBG_OUTPUT_PORT.setDebugOutput(true); 140 DBG_OUTPUT_PORT.println("Adafruit TinyUSB Mass Storage External Flash example"); 141 DBG_OUTPUT_PORT.print("JEDEC ID: 0x"); 142 DBG_OUTPUT_PORT.println(flash.getJEDECID(), HEX); 143 DBG_OUTPUT_PORT.print("Flash size: "); 144 DBG_OUTPUT_PORT.print(flash.size() / 1024); 145 DBG_OUTPUT_PORT.println(" KB"); 146 DBG_OUTPUT_PORT.print("\n"); 147 //display.print("Flash size: "); 148 //display.print(flash.size() / 1024); 149 //display.println(" KB"); 150 151 File root, file; 152 if (root.open("/") ) { 153 DBG_OUTPUT_PORT.println("Flash contents:"); 154 155 // Open next file in root. 156 // Warning, openNext starts at the current directory position 157 // so a rewind of the directory may be required. 158 while ( file.openNext(&root, O_RDONLY) ) 159 { 160 file.printFileSize(&DBG_OUTPUT_PORT); 161 DBG_OUTPUT_PORT.write(' '); 162 file.printName(&DBG_OUTPUT_PORT); 163 if ( file.isDir() ) 164 { 165 // Indicate a directory. 166 DBG_OUTPUT_PORT.write('/'); 167 } 168 DBG_OUTPUT_PORT.println(); 169 file.close(); 170 } 171 172 root.close(); 173 DBG_OUTPUT_PORT.println(); 174 } 175 176 parseSecrets(); 177 178 WiFi.mode(WIFI_STA); 179 WiFi.begin(ssid, password); 180 DBG_OUTPUT_PORT.print("Connecting to "); 181 DBG_OUTPUT_PORT.println(ssid); 182 183 // Wait for connection 184 uint8_t i = 0; 185 while ((WiFi.status() != WL_CONNECTED) && (i++ < 20)) { //wait 10 seconds 186 delay(500); 187 } 188 if (i == 21) { 189 DBG_OUTPUT_PORT.print("Could not connect to"); 190 DBG_OUTPUT_PORT.println(ssid); 191 display.print("Couldnt connect to "); 192 display.println(ssid); 193 194 // try making access point 195 WiFi.mode(WIFI_AP); 196 display.print("AP: "); 197 display.print(ap); 198 display.print(" / "); 199 display.println(ap_password); 200 WiFi.softAP(ap, ap_password); 201 IPAddress myIP = WiFi.softAPIP(); 202 DBG_OUTPUT_PORT.print("IP address: "); 203 DBG_OUTPUT_PORT.println(myIP); 204 display.print("IP addr: "); 205 display.println(myIP); 206 } else { 207 display.print("Connected to "); 208 display.println(ssid); 209 DBG_OUTPUT_PORT.print("Connected! IP address: "); 210 DBG_OUTPUT_PORT.println(WiFi.localIP()); 211 display.print("IP addr: "); 212 display.println(WiFi.localIP()); 213 } 214 215 if (MDNS.begin(hostname)) { 216 MDNS.addService("http", "tcp", 80); 217 DBG_OUTPUT_PORT.println("MDNS responder started"); 218 DBG_OUTPUT_PORT.print("You can now connect to http://"); 219 DBG_OUTPUT_PORT.print(hostname); 220 DBG_OUTPUT_PORT.println(".local"); 221 display.print("mDNS: "); 222 display.print(hostname); 223 display.println(".local"); 224 } 225 226 server.on("/list", HTTP_GET, printDirectory); 227 server.onNotFound(handleNotFound); 228 229 server.begin(); 230 DBG_OUTPUT_PORT.println("HTTP server started"); 231 232 } 233 234 void loop(void) { 235 server.handleClient(); 236 delay(2);//allow the cpu to switch to other tasks 237 } 238 239 240 241 // Callback invoked when received READ10 command. 242 // Copy disk's data to buffer (up to bufsize) and 243 // return number of copied bytes (must be multiple of block size) 244 int32_t msc_read_cb (uint32_t lba, void* buffer, uint32_t bufsize) 245 { 246 // Note: SPIFLash Block API: readBlocks/writeBlocks/syncBlocks 247 // already include 4K sector caching internally. We don't need to cache it, yahhhh!! 248 return flash.readBlocks(lba, (uint8_t*) buffer, bufsize/512) ? bufsize : -1; 249 } 250 251 // Callback invoked when received WRITE10 command. 252 // Process data in buffer to disk's storage and 253 // return number of written bytes (must be multiple of block size) 254 int32_t msc_write_cb (uint32_t lba, uint8_t* buffer, uint32_t bufsize) 255 { 256 digitalWrite(LED_BUILTIN, HIGH); 257 258 // Note: SPIFLash Block API: readBlocks/writeBlocks/syncBlocks 259 // already include 4K sector caching internally. We don't need to cache it, yahhhh!! 260 return flash.writeBlocks(lba, buffer, bufsize/512) ? bufsize : -1; 261 } 262 263 // Callback invoked when WRITE10 command is completed (status received and accepted by host). 264 // used to flush any pending cache. 265 void msc_flush_cb (void) 266 { 267 // sync with flash 268 flash.syncBlocks(); 269 270 // clear file system's cache to force refresh 271 fatfs.cacheClear(); 272 273 fs_changed = true; 274 275 digitalWrite(LED_BUILTIN, LOW); 276 } 277 278 279 280 bool parseSecrets() { 281 // open file for parsing 282 File secretsFile = fatfs.open(secrets_filename); 283 if (!secretsFile) { 284 DBG_OUTPUT_PORT.println("ERROR: Could not open secrets.json file for reading!"); 285 return false; 286 } 287 288 // check if we can deserialize the secrets.json file 289 DeserializationError err = deserializeJson(doc, secretsFile); 290 if (err) { 291 DBG_OUTPUT_PORT.println("ERROR: deserializeJson() failed with code "); 292 DBG_OUTPUT_PORT.println(err.c_str()); 293 294 return false; 295 } 296 297 // next, we detect the network interface from the `secrets.json` 298 DBG_OUTPUT_PORT.println("Attempting to find network interface..."); 299 strlcpy(ssid, doc["ssid"] | DEFAULT_SSID, sizeof(ssid)); 300 strlcpy(password, doc["password"] | DEFAULT_PASSWORD, sizeof(password)); 301 strlcpy(ap, doc["ap"] | DEFAULT_AP, sizeof(ap)); 302 strlcpy(ap_password, doc["ap_password"] | DEFAULT_AP_PASSWORD, sizeof(ap_password)); 303 strlcpy(hostname, doc["hostname"] | DEFAULT_HOSTNAME, sizeof(hostname)); 304 305 // close the tempFile 306 secretsFile.close(); 307 return true; 308 } 309 310 311 void returnOK() { 312 server.send(200, "text/plain", ""); 313 } 314 315 void returnFail(String msg) { 316 server.send(500, "text/plain", msg + "\r\n"); 317 } 318 319 bool loadFromFlash(String path) { 320 String dataType = "text/plain"; 321 if (path.endsWith("/")) { 322 path += "index.html"; 323 } 324 325 if (path.endsWith(".src")) { 326 path = path.substring(0, path.lastIndexOf(".")); 327 } else if (path.endsWith(".htm")) { 328 dataType = "text/html"; 329 } else if (path.endsWith(".html")) { 330 dataType = "text/html"; 331 } else if (path.endsWith(".css")) { 332 dataType = "text/css"; 333 } else if (path.endsWith(".js")) { 334 dataType = "application/javascript"; 335 } else if (path.endsWith(".png")) { 336 dataType = "image/png"; 337 } else if (path.endsWith(".gif")) { 338 dataType = "image/gif"; 339 } else if (path.endsWith(".jpg")) { 340 dataType = "image/jpeg"; 341 } else if (path.endsWith(".ico")) { 342 dataType = "image/x-icon"; 343 } else if (path.endsWith(".xml")) { 344 dataType = "text/xml"; 345 } else if (path.endsWith(".pdf")) { 346 dataType = "application/pdf"; 347 } else if (path.endsWith(".zip")) { 348 dataType = "application/zip"; 349 } 350 351 DBG_OUTPUT_PORT.print(path.c_str()); 352 353 if (! fatfs.exists(path.c_str())) { 354 DBG_OUTPUT_PORT.println("..doesnt exist?"); 355 return false; 356 } 357 358 File dataFile = fatfs.open(path.c_str()); 359 if (! dataFile) { 360 DBG_OUTPUT_PORT.println("..couldn't open?"); 361 return false; 362 } 363 364 if (dataFile.isDir()) { 365 path += "/index.html"; 366 dataType = "text/html"; 367 dataFile = fatfs.open(path.c_str()); 368 } 369 370 371 if (server.hasArg("download")) { 372 dataType = "application/octet-stream"; 373 } 374 375 if (server.streamFile(dataFile, dataType) != dataFile.size()) { 376 DBG_OUTPUT_PORT.println("Sent less data than expected!"); 377 } 378 379 dataFile.close(); 380 return true; 381 } 382 383 384 void printDirectory() { 385 if (!server.hasArg("dir")) { 386 return returnFail("BAD ARGS"); 387 } 388 String path = server.arg("dir"); 389 if (path != "/" && !fatfs.exists((char *)path.c_str())) { 390 return returnFail("BAD PATH"); 391 } 392 File dir = fatfs.open((char *)path.c_str()); 393 path = String(); 394 if (!dir.isDir()) { 395 dir.close(); 396 return returnFail("NOT DIR"); 397 } 398 dir.rewindDirectory(); 399 server.setContentLength(CONTENT_LENGTH_UNKNOWN); 400 server.send(200, "text/json", ""); 401 WiFiClient client = server.client(); 402 403 server.sendContent("["); 404 for (int cnt = 0; true; ++cnt) { 405 File entry; 406 if (!entry.openNext(&dir, O_RDONLY)) { 407 break; 408 } 409 410 String output; 411 if (cnt > 0) { 412 output = ','; 413 } 414 415 output += "{\"type\":\""; 416 output += (entry.isDir()) ? "dir" : "file"; 417 output += "\",\"name\":\""; 418 419 //output += entry.path(); 420 output += "\""; 421 output += "}"; 422 server.sendContent(output); 423 entry.close(); 424 } 425 server.sendContent("]"); 426 dir.close(); 427 } 428 429 void handleNotFound() { 430 if (loadFromFlash(server.uri())) { 431 return; 432 } 433 String message = "Internal Flash Not Detected\n\n"; 434 message += "URI: "; 435 message += server.uri(); 436 message += "\nMethod: "; 437 message += (server.method() == HTTP_GET) ? "GET" : "POST"; 438 message += "\nArguments: "; 439 message += server.args(); 440 message += "\n"; 441 for (uint8_t i = 0; i < server.args(); i++) { 442 message += " NAME:" + server.argName(i) + "\n VALUE:" + server.arg(i) + "\n"; 443 } 444 server.send(404, "text/plain", message); 445 DBG_OUTPUT_PORT.print(message); 446 }