/ 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  }