/* Graph - A web-based Graph display of ESP8266 data This file is part of the ESP8266WebServer library for Arduino environment. This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA See readme.md for more information. */ //////////////////////////////// // Select the FileSystem by uncommenting one of the lines below //#define USE_SPIFFS #define USE_LITTLEFS //#define USE_SDFS //////////////////////////////// #include #include #include #include #include #if defined USE_SPIFFS #include FS* fileSystem = &SPIFFS; SPIFFSConfig fileSystemConfig = SPIFFSConfig(); #elif defined USE_LITTLEFS #include FS* fileSystem = &LittleFS; LittleFSConfig fileSystemConfig = LittleFSConfig(); #elif defined USE_SDFS #include FS* fileSystem = &SDFS; SDFSConfig fileSystemConfig = SDFSConfig(); // fileSystemConfig.setCSPin(chipSelectPin); #else #error Please select a filesystem first by uncommenting one of the "#define USE_xxx" lines at the beginning of the sketch. #endif #define DBG_OUTPUT_PORT Serial #ifndef STASSID #define STASSID "your-ssid" #define STAPSK "your-password" #endif // Indicate which digital I/Os should be displayed on the chart. // From GPIO16 to GPIO0, a '1' means the corresponding GPIO will be shown // e.g. 0b11111000000111111 unsigned int gpioMask; const char* ssid = STASSID; const char* password = STAPSK; const char* host = "graph"; ESP8266WebServer server(80); static const char TEXT_PLAIN[] PROGMEM = "text/plain"; static const char FS_INIT_ERROR[] PROGMEM = "FS INIT ERROR"; static const char FILE_NOT_FOUND[] PROGMEM = "FileNotFound"; //////////////////////////////// // Utils to return HTTP codes void replyOK() { server.send(200, FPSTR(TEXT_PLAIN), ""); } void replyOKWithMsg(String msg) { server.send(200, FPSTR(TEXT_PLAIN), msg); } void replyNotFound(String msg) { server.send(404, FPSTR(TEXT_PLAIN), msg); } void replyBadRequest(String msg) { DBG_OUTPUT_PORT.println(msg); server.send(400, FPSTR(TEXT_PLAIN), msg + "\r\n"); } void replyServerError(String msg) { DBG_OUTPUT_PORT.println(msg); server.send(500, FPSTR(TEXT_PLAIN), msg + "\r\n"); } //////////////////////////////// // Request handlers /* Read the given file from the filesystem and stream it back to the client */ bool handleFileRead(String path) { DBG_OUTPUT_PORT.println(String("handleFileRead: ") + path); if (path.endsWith("/")) { path += "index.htm"; } String contentType = mime::getContentType(path); if (!fileSystem->exists(path)) { // File not found, try gzip version path = path + ".gz"; } if (fileSystem->exists(path)) { File file = fileSystem->open(path, "r"); if (server.streamFile(file, contentType) != file.size()) { DBG_OUTPUT_PORT.println("Sent less data than expected!"); } file.close(); return true; } return false; } /* The "Not Found" handler catches all URI not explicitly declared in code First try to find and return the requested file from the filesystem, and if it fails, return a 404 page with debug information */ void handleNotFound() { String uri = ESP8266WebServer::urlDecode(server.uri()); // required to read paths with blanks if (handleFileRead(uri)) { return; } // Dump debug data String message; message.reserve(100); message = F("Error: File not found\n\nURI: "); message += uri; message += F("\nMethod: "); message += (server.method() == HTTP_GET) ? "GET" : "POST"; message += F("\nArguments: "); message += server.args(); message += '\n'; for (uint8_t i = 0; i < server.args(); i++) { message += F(" NAME:"); message += server.argName(i); message += F("\n VALUE:"); message += server.arg(i); message += '\n'; } message += "path="; message += server.arg("path"); message += '\n'; DBG_OUTPUT_PORT.print(message); return replyNotFound(message); } void setup(void) { //////////////////////////////// // SERIAL INIT DBG_OUTPUT_PORT.begin(115200); DBG_OUTPUT_PORT.setDebugOutput(true); DBG_OUTPUT_PORT.print('\n'); //////////////////////////////// // FILESYSTEM INIT fileSystemConfig.setAutoFormat(false); fileSystem->setConfig(fileSystemConfig); bool fsOK = fileSystem->begin(); DBG_OUTPUT_PORT.println(fsOK ? F("Filesystem initialized.") : F("Filesystem init failed!")); //////////////////////////////// // PIN INIT pinMode(4, INPUT); pinMode(12, OUTPUT); pinMode(13, OUTPUT); pinMode(15, OUTPUT); //////////////////////////////// // WI-FI INIT DBG_OUTPUT_PORT.printf("Connecting to %s\n", ssid); WiFi.mode(WIFI_STA); WiFi.begin(ssid, password); // Wait for connection while (WiFi.status() != WL_CONNECTED) { delay(500); DBG_OUTPUT_PORT.print("."); } DBG_OUTPUT_PORT.println(""); DBG_OUTPUT_PORT.print(F("Connected! IP address: ")); DBG_OUTPUT_PORT.println(WiFi.localIP()); //////////////////////////////// // MDNS INIT if (MDNS.begin(host)) { MDNS.addService("http", "tcp", 80); DBG_OUTPUT_PORT.print(F("Open http://")); DBG_OUTPUT_PORT.print(host); DBG_OUTPUT_PORT.println(F(".local to open the graph page")); } //////////////////////////////// // WEB SERVER INIT // get heap status, analog input value and all GPIO statuses in one json call server.on("/espData", HTTP_GET, []() { String json; json.reserve(88); json = "{\"time\":"; json += millis(); json += ", \"heap\":"; json += ESP.getFreeHeap(); json += ", \"analog\":"; json += analogRead(A0); json += ", \"gpioMask\":"; json += gpioMask; json += ", \"gpioData\":"; json += (uint32_t)(((GPI | GPO) & 0xFFFF) | ((GP16I & 0x01) << 16)); json += "}"; server.send(200, "text/json", json); }); // Default handler for all URIs not defined above // Use it to read files from filesystem server.onNotFound(handleNotFound); // Start server server.begin(); DBG_OUTPUT_PORT.println("HTTP server started"); DBG_OUTPUT_PORT.println("Please pull GPIO4 low (e.g. press button) to switch output mode:"); DBG_OUTPUT_PORT.println(" 0 (OFF): outputs are off and hidden from chart"); DBG_OUTPUT_PORT.println(" 1 (AUTO): outputs are rotated automatically every second"); DBG_OUTPUT_PORT.println(" 2 (MANUAL): outputs can be toggled from the web page"); } // Return default GPIO mask, that is all I/Os except SD card ones unsigned int defaultMask() { unsigned int mask = 0b11111111111111111; for (auto pin = 0; pin <= 16; pin++) { if (isFlashInterfacePin(pin)) { mask &= ~(1 << pin); } } return mask; } int rgbMode = 1; // 0=off - 1=auto - 2=manual int rgbValue = 0; esp8266::polledTimeout::periodicMs timeToChange(1000); bool modeChangeRequested = false; void loop(void) { server.handleClient(); MDNS.update(); if (digitalRead(4) == 0) { // button pressed modeChangeRequested = true; } // see if one second has passed since last change, otherwise stop here if (!timeToChange) { return; } // see if a mode change was requested if (modeChangeRequested) { // increment mode (reset after 2) rgbMode++; if (rgbMode > 2) { rgbMode = 0; } modeChangeRequested = false; } // act according to mode switch (rgbMode) { case 0: // off gpioMask = defaultMask(); gpioMask &= ~(1 << 12); // Hide GPIO 12 gpioMask &= ~(1 << 13); // Hide GPIO 13 gpioMask &= ~(1 << 15); // Hide GPIO 15 // reset outputs digitalWrite(12, 0); digitalWrite(13, 0); digitalWrite(15, 0); break; case 1: // auto gpioMask = defaultMask(); // increment value (reset after 7) rgbValue++; if (rgbValue > 7) { rgbValue = 0; } // output new values digitalWrite(12, rgbValue & 0b001); digitalWrite(13, rgbValue & 0b010); digitalWrite(15, rgbValue & 0b100); break; case 2: // manual gpioMask = defaultMask(); // keep outputs unchanged break; } }