From 8f4ca71f72c3c4a2aade1c5bd1f4b280240192a1 Mon Sep 17 00:00:00 2001 From: "Riley L." Date: Wed, 21 Aug 2024 04:59:06 +0200 Subject: [PATCH] Initial commit. --- .gitignore | 5 + .vscode/extensions.json | 10 + .vscode/settings.json | 3 + fw.bin | 1 + include/README | 39 ++++ lib/README | 46 ++++ platformio.ini | 21 ++ src/FIS.c | 0 src/FIS.h | 15 ++ src/RGBW.h | 56 +++++ src/Server.cpp | 499 ++++++++++++++++++++++++++++++++++++++++ src/Server.h | 25 ++ src/StoreCSV.cpp | 132 +++++++++++ src/StoreCSV.h | 23 ++ src/config.c | 100 ++++++++ src/config.h | 28 +++ src/index.h | 141 ++++++++++++ src/main.cpp | 291 +++++++++++++++++++++++ src/main.h | 30 +++ src/pages.h | 207 +++++++++++++++++ test/README | 11 + 21 files changed, 1683 insertions(+) create mode 100644 .gitignore create mode 100644 .vscode/extensions.json create mode 100644 .vscode/settings.json create mode 120000 fw.bin create mode 100644 include/README create mode 100644 lib/README create mode 100644 platformio.ini create mode 100644 src/FIS.c create mode 100644 src/FIS.h create mode 100644 src/RGBW.h create mode 100644 src/Server.cpp create mode 100644 src/Server.h create mode 100644 src/StoreCSV.cpp create mode 100644 src/StoreCSV.h create mode 100644 src/config.c create mode 100644 src/config.h create mode 100644 src/index.h create mode 100644 src/main.cpp create mode 100644 src/main.h create mode 100644 src/pages.h create mode 100644 test/README diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..89cc49c --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +.pio +.vscode/.browse.c_cpp.db* +.vscode/c_cpp_properties.json +.vscode/launch.json +.vscode/ipch diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 0000000..080e70d --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,10 @@ +{ + // See http://go.microsoft.com/fwlink/?LinkId=827846 + // for the documentation about the extensions.json format + "recommendations": [ + "platformio.platformio-ide" + ], + "unwantedRecommendations": [ + "ms-vscode.cpptools-extension-pack" + ] +} diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..70e34ec --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "C_Cpp.errorSquiggles": "disabled" +} \ No newline at end of file diff --git a/fw.bin b/fw.bin new file mode 120000 index 0000000..3d3b614 --- /dev/null +++ b/fw.bin @@ -0,0 +1 @@ +.pio/build/d1_mini_lite/firmware.bin \ No newline at end of file diff --git a/include/README b/include/README new file mode 100644 index 0000000..194dcd4 --- /dev/null +++ b/include/README @@ -0,0 +1,39 @@ + +This directory is intended for project header files. + +A header file is a file containing C declarations and macro definitions +to be shared between several project source files. You request the use of a +header file in your project source file (C, C++, etc) located in `src` folder +by including it, with the C preprocessing directive `#include'. + +```src/main.c + +#include "header.h" + +int main (void) +{ + ... +} +``` + +Including a header file produces the same results as copying the header file +into each source file that needs it. Such copying would be time-consuming +and error-prone. With a header file, the related declarations appear +in only one place. If they need to be changed, they can be changed in one +place, and programs that include the header file will automatically use the +new version when next recompiled. The header file eliminates the labor of +finding and changing all the copies as well as the risk that a failure to +find one copy will result in inconsistencies within a program. + +In C, the usual convention is to give header files names that end with `.h'. +It is most portable to use only letters, digits, dashes, and underscores in +header file names, and at most one dot. + +Read more about using header files in official GCC documentation: + +* Include Syntax +* Include Operation +* Once-Only Headers +* Computed Includes + +https://gcc.gnu.org/onlinedocs/cpp/Header-Files.html diff --git a/lib/README b/lib/README new file mode 100644 index 0000000..2593a33 --- /dev/null +++ b/lib/README @@ -0,0 +1,46 @@ + +This directory is intended for project specific (private) libraries. +PlatformIO will compile them to static libraries and link into executable file. + +The source code of each library should be placed in an own separate directory +("lib/your_library_name/[here are source files]"). + +For example, see a structure of the following two libraries `Foo` and `Bar`: + +|--lib +| | +| |--Bar +| | |--docs +| | |--examples +| | |--src +| | |- Bar.c +| | |- Bar.h +| | |- library.json (optional, custom build options, etc) https://docs.platformio.org/page/librarymanager/config.html +| | +| |--Foo +| | |- Foo.c +| | |- Foo.h +| | +| |- README --> THIS FILE +| +|- platformio.ini +|--src + |- main.c + +and a contents of `src/main.c`: +``` +#include +#include + +int main (void) +{ + ... +} + +``` + +PlatformIO Library Dependency Finder will find automatically dependent +libraries scanning project source files. + +More information about PlatformIO Library Dependency Finder +- https://docs.platformio.org/page/librarymanager/ldf.html diff --git a/platformio.ini b/platformio.ini new file mode 100644 index 0000000..72ab2c0 --- /dev/null +++ b/platformio.ini @@ -0,0 +1,21 @@ +; PlatformIO Project Configuration File +; +; Build options: build flags, source filter +; Upload options: custom upload port, speed and extra flags +; Library options: dependencies, extra library storages +; Advanced options: extra scripting +; +; Please visit documentation for the other options and examples +; https://docs.platformio.org/page/projectconf.html + +[env:d1_mini_lite] +platform = espressif8266 +board = d1_mini_lite +framework = arduino +monitor_speed = 115200 +upload_speed = 460800 +lib_deps = + fastled/FastLED@^3.7.1 + plerup/EspSoftwareSerial@^8.2.0 + links2004/WebSockets@^2.5.3 + vshymanskyy/Preferences@^2.0.0 diff --git a/src/FIS.c b/src/FIS.c new file mode 100644 index 0000000..e69de29 diff --git a/src/FIS.h b/src/FIS.h new file mode 100644 index 0000000..9c9ae1c --- /dev/null +++ b/src/FIS.h @@ -0,0 +1,15 @@ +/* +#ifndef FIS_H +#define FIS_H + +#include +#include + + +void begin(); +void writeText(char text[]); + +#include + +#endif // FIS_H +*/ \ No newline at end of file diff --git a/src/RGBW.h b/src/RGBW.h new file mode 100644 index 0000000..e1a8417 --- /dev/null +++ b/src/RGBW.h @@ -0,0 +1,56 @@ +/* FastLED_RGBW + * + * Hack to enable SK6812 RGBW strips to work with FastLED. + * + * Original code by Jim Bumgardner (http://krazydad.com). + * Modified by David Madison (http://partsnotincluded.com). + * +*/ +#ifndef FastLED_RGBW_h +#define FastLED_RGBW_h + +#include +#include + +struct CRGBW { + union { + struct { + union { + uint8_t g; + uint8_t green; + }; + union { + uint8_t r; + uint8_t red; + }; + union { + uint8_t b; + uint8_t blue; + }; + union { + uint8_t w; + uint8_t white; + }; + }; + uint8_t raw[4]; + }; + CRGBW(){} + CRGBW(uint8_t rd, uint8_t grn, uint8_t blu, uint8_t wht){ + r = rd; + g = grn; + b = blu; + w = wht; + } + inline void operator = (const CRGB c) __attribute__((always_inline)){ + this->r = c.r; + this->g = c.g; + this->b = c.b; + this->white = 0; + } +}; +inline uint16_t getRGBWsize(uint16_t nleds){ + uint16_t nbytes = nleds * 4; + if(nbytes % 3 > 0) return nbytes / 3 + 1; + else return nbytes / 3; +} +#endif diff --git a/src/Server.cpp b/src/Server.cpp new file mode 100644 index 0000000..6abf531 --- /dev/null +++ b/src/Server.cpp @@ -0,0 +1,499 @@ +#include "config.h" +#include "main.h" +#include "pages.h" + +#include +#include "StoreCSV.h" +#include "RGBW.h" +#include +#include +#include +#include +#include +#include + +String convert_utf8_to_iso8859_1(String utf8) { + String iso8859_1 = ""; + for (uint i = 0; i < utf8.length(); i++){ + char c = utf8[i]; // get the character + if ((c & 0x80) == 0) { // is it a single-byte character? + iso8859_1 += c; // if so, just append it + } else if ((c & 0xE0) == 0xC0) { // is it a two-byte sequence? + // extract the Unicode codepoint represented by the sequence + int codepoint = ((c & 0x1F) << 6) | (utf8[++i] & 0x3F); + if (codepoint <= 0xFF) { // is it within the ISO-8859-1 range? + iso8859_1 += (char) codepoint; // if so, append the equivalent ISO-8859-1 character + } + // if not, we simply omit it + } else { + // it's a more-than-two-byte sequence, which is definitely outside the ISO-8859-1 range, + // so we just skip the rest of the sequence + if ((c & 0xF0) == 0xE0) i++; + else if ((c & 0xF8) == 0xF0) i += 2; + // if it's neither, it's a malformed sequence, so we skip it altogether + } + } + return iso8859_1; +} + +ESP8266WebServer server(80); +WebSocketsServer webSocket(81); + +WiFiUDP UDP; +char UDP_packet[90 * 4]; + +WiFiUDP UDP2; +char UDP2_packet[60]; + +String ota_pw; + +bool enableFallbackAP; + +void handleWebClient() { + server.handleClient(); +} + +void handleRoot() { + server.send(200, "text/html", displayForm); +} + +void tickerPage() { + server.send(200, "text/html", tickerForm); +} + +void pickerPage() { + server.send(200, "text/html", colorPickerIndex); +} + +void handleRainbow() { + Serial.print("handleRainbow:"); + if (server.arg("rainbow") == "On") { + rainbow = true; + Serial.println("On"); + server.send(200, "text/html", "Turn Rainbow On"); + } else if (server.arg("rainbow") == "Off") { + rainbow = false; + Serial.println("Off"); + server.send(200, "text/html", "Turn Rainbow Off"); + } + server.send(400, "text/html", "Error in handleRainbow, no value found " + server.arg("rainbow")); +} + +void handleScroller() { + Serial.print("handleScroller:"); + if (server.arg("scroller") == "On") { + scroller = true; + Serial.println("On"); + server.send(200, "text/html", "Turn Scroller On"); + } else if (server.arg("scroller") == "Off") { + scroller = false; + Serial.println("Off"); + server.send(200, "text/html", "Turn Scroller Off"); + } + server.send(400, "text/html", "Error in handeScroller, no value found " + server.arg("scroller")); +} + +char lookup_hex(char c) { + switch (c & 0b00011111) { + case '0': + return 0x0; + case '1': + return 0x1; + case '2': + return 0x2; + case '3': + return 0x3; + case '4': + return 0x4; + case '5': + return 0x5; + case '6': + return 0x6; + case '7': + return 0x7; + case '8': + return 0x8; + case '9': + return 0x9; + + case 'a': + return 0xA; + case 'b': + return 0xB; + case 'c': + return 0xC; + case 'd': + return 0xD; + case 'e': + return 0xE; + case 'f': + return 0xF; + } + + return 0; +} + +void handleSetPixels() { + char hexbuf[720]; // NUM_PIX * bytes * 2 + + Serial.print("handleSetPixels:"); + if (server.hasArg("pix")) { + server.arg("pix").toCharArray(hexbuf, 720); + + uint8_t buf[360]; + uint8_t toggle = 0; + for(int i = 0 ; i < 720 ; i++) { + buf[i/2] = lookup_hex(hexbuf[i]) << (toggle * 8); + + toggle ^= 1; + } + + Serial.println(hexbuf); + server.send(200, "text/html", "Color"); + } else { + server.send(400, "text/html", "Invalid request, no text found"); + } + +} + +void handleDisplayString() { + String message; + Serial.print("handleDisplayString:"); + if (server.hasArg("text")) { + message = server.arg("text"); + + writeText(message.c_str()); + + Serial.println(message); + server.send(200, "text/html", "Display: '" + message +"'"); + } else { + server.send(400, "text/html", "Invalid request, no text found"); + } +} + +std::vector ticker_lines; +uint32_t ticker_timeout; + +/* +void handleTickerEvent() { + time_t now; + + if (ticker_lines.empty()) { + return; + } + + static unsigned long lastLoopTime = millis(); + static int idx = 0; + if(millis() - lastLoopTime < ticker_timeout) { + return; + } + lastLoopTime = millis(); + + if (idx < ticker_lines.size()) + { + SoftSerial.write(0x0A); + SoftSerial.write(0x09); + SoftSerial.write(0x0D); + SoftSerial.print(ticker_lines[idx]); + idx++; + + } else { + idx = 0; + } +} +*/ + +void handleOTA() { + server.sendHeader("Connection", "close"); + server.send(200, "text/html", loginIndex); +} + +void handleServerIndex() { + server.sendHeader("Connection", "close"); + if(server.arg("k") != ota_pw) + server.send(400, "text/html", "invalid credentials."); + else + server.send(200, "text/html", serverIndex); +} + +void handleUpdate() { + server.sendHeader("Connection", "close"); + if(server.arg("k") != ota_pw) { + server.send(400, "text/html", "invalid credentials."); + } else { + server.send(200, "text/html", serverIndex); + server.send(200, "text/plain", (Update.hasError()) ? "FAIL" : "OK"); + ESP.restart(); + } +} + +void handleUpload() { + HTTPUpload& upload = server.upload(); + if (upload.status == UPLOAD_FILE_START) { + Serial.printf("Update: %s\n", upload.filename.c_str()); + if (!Update.begin(upload.contentLength)) { + Update.printError(Serial); + } + } else if (upload.status == UPLOAD_FILE_WRITE) { + if (Update.write(upload.buf, upload.currentSize) != upload.currentSize) { + Update.printError(Serial); + } + } else if (upload.status == UPLOAD_FILE_END) { + if (Update.end(true)) { + Serial.printf("Update Success: %u\nRebooting...\n", upload.totalSize); + } else { + Update.printError(Serial); + } + } +} + +void handleCredentials() { + String ssid; + String wifi_pw; + if (server.hasArg("ssid") && server.hasArg("wifi_pw")) { + ssid = server.arg("ssid"); + wifi_pw = server.arg("wifi_pw"); + ota_pw = server.arg("ota_pw"); + + prefs.begin("wifi", false); + prefs.putString("ssid", ssid); + prefs.putString("wifi_pw", wifi_pw); + prefs.putString("ota_pw", ota_pw); + prefs.end(); + + server.send(200, "text/html", "Saved credentials!"); + } else { + server.send(400, "text/plain", "Bad Request"); + } +} + +void CSVUploadPage() { + server.sendHeader("Connection", "close"); + server.send(200, "text/html", csvUploadPage); +} + +void webSocketEvent(uint8_t num, WStype_t type, uint8_t * payload, size_t length){ + if(type == WStype_TEXT){ + String message = String((char*) payload); + Serial.print("WS:"); + Serial.println(message); + if (message.startsWith("{RGB}=")) { + uint32_t rgb; + rgb = strtol(message.substring(7,13).c_str(), 0, 16); + fillColor(rgb>>16, rgb >> 8, rgb >> 0); + return; + } + + Serial.println(message); + writeText(message.c_str()); + } else if(type == WStype_BIN) { + for(size_t i = 0 ; i < length ; i+=4 ) { + set_pixelw(i/4, CRGBW(payload[i], payload[i+1], payload[i+2], payload[i+3])); + } + + show_pixel(); + } +} + +void handleFallbackRoot() { + Serial.print("handleFallbackRoot"); + prefs.begin("wifi", true); + String ssid = prefs.getString("ssid", ""); + String wifi_pw = prefs.getString("wifi_pw", ""); + String ota_pw = prefs.getString("ota_pw", ""); + prefs.end(); + + char wifiCredentialsPage[1024]; + sprintf(wifiCredentialsPage, wifiCredentialsTemplate, ssid.c_str(), wifi_pw.c_str(), ota_pw.c_str()); + + server.send(200, "text/html", wifiCredentialsPage); +} + +void startAPMode() { + progress(4, SETUP_PROGRESS_MAX, 255, 255, 0); + + WiFi.disconnect(); + WiFi.softAPConfig(FALLBACK_IP, GATEWAY_IP, SUBNET_IP); + + add_progress(); + WiFi.mode(WIFI_AP); + WiFi.softAP(FALLBACK_SSID, FALLBACK_PASSWORD); // Using default credentials + + IPAddress myIP = WiFi.softAPIP(); + Serial.print("AP IP address: "); + Serial.println(myIP); + + server.on("/", handleFallbackRoot); + server.on("/saveCredentials", HTTP_POST, handleCredentials); + + enableFallbackAP = true; + + server.begin(); + add_progress(); +} + +void connectToWiFi() { + add_progress(); + writeText("Connecting To Wifi"); + esp_delay(1000); + + enableFallbackAP = false; + + // Attempt to connect to Wi-Fi with saved credentials + prefs.begin("wifi", true); // Begin preferences in read-only mode + String ssid = prefs.getString("ssid", ""); + const char *cstrssid = ssid.c_str(); + String wifi_pw = prefs.getString("wifi_pw", ""); + ota_pw = prefs.getString("ota_pw", ""); + prefs.end(); + + if (ssid != "" && wifi_pw != "") { + writeText(cstrssid); + + WiFi.begin(cstrssid, wifi_pw.c_str()); + + for(int i = 0 ; i < 5 ; i++ ) { + if(WiFi.isConnected()) { + break; + } + + esp_delay(500); + + add_progress(); + show_progress(); // hopefully fixes some shit + } + + if (WiFi.waitForConnectResult() != WL_CONNECTED) { + Serial.println("Failed to connect to Wi-Fi with saved credentials. Starting AP mode."); + enableFallbackAP = true; + + writeText("invalid credentials"); + esp_delay(5000); + + startAPMode(); + } + } else { + Serial.println("No saved credentials found. Starting AP mode."); + enableFallbackAP = true; + + fillColor(255, 0, 0); + writeText("no credentails"); + esp_delay(5000); + + startAPMode(); + } + + esp_delay(1000); + + if(enableFallbackAP) { + esp_delay(1000); + } + + set_progress(6, SETUP_PROGRESS_MAX); + + IPAddress myIP = enableFallbackAP ? FALLBACK_IP : WiFi.localIP(); + String ip_adress_s = myIP.toString(); + + Serial.print("\nConnected to "); + Serial.println(ssid); + Serial.print("IP address: "); + Serial.println(ip_adress_s.c_str()); + + /*use mdns for host name resolution*/ + if (!MDNS.begin(HOST)) + Serial.println("Error setting up MDNS responder, MDNS will not be used"); + else + Serial.println("mDNS responder started"); + + add_progress(); + + #define CONNECTED "Connected:" + #define FALLBACK "Fallback: " + + if(enableFallbackAP) { + writeText(FALLBACK FALLBACK_SSID); + } else { + char buf[strlen(CONNECTED) + strlen(cstrssid)] = CONNECTED; + strcat(buf, cstrssid); + + writeText(buf); + } + esp_delay(2000); + const char* cstrip = ip_adress_s.c_str(); + + if(enableFallbackAP) { + char buf[strlen(FALLBACK) + strlen(cstrip)] = FALLBACK; + strcat(buf, cstrip); + + writeText(buf); + } else { + char buf[strlen(CONNECTED) + strlen(cstrip)] = CONNECTED; + strcat(buf, cstrip); + + writeText(buf); + } + add_progress(); + + esp_delay(2000); + + if(enableFallbackAP) { + writeText(FALLBACK FALLBACK_SSID); + } +} + +void setupWebServer() { + server.on("/", handleRoot); + server.on("/ticker", HTTP_GET, tickerPage); + server.on("/pickerForm", HTTP_GET, pickerPage); +// server.on("/tickerForm", HTTP_POST, handleTicker); + server.on("/Scroller", HTTP_POST, handleScroller); + server.on("/Rainbow", HTTP_POST, handleRainbow); + server.on("/StringForm", HTTP_POST, handleDisplayString); + server.on("/SetPixels", HTTP_POST, handleSetPixels); + server.on("/OTA", HTTP_GET, handleOTA); + server.on("/serverIndex", HTTP_GET, handleServerIndex); + server.on("/uploadCSV", HTTP_GET, CSVUploadPage); + server.on("/uploadCSV", HTTP_POST, []() { + server.send(200, "text/plain", "CSV Uploaded Successfully"); + }, handleCSVUpload); + + server.on("/update", HTTP_POST, []() { + handleUpdate(); + }, handleUpload); + server.begin(); + + UDP.begin(UDP_PORT); + UDP2.begin(UDP_PORT + 1); + Serial.print("Listening on UDP port "); + Serial.println(UDP_PORT); + + webSocket.onEvent(webSocketEvent); + webSocket.begin(); + +} + +void handleUDP() { + int packetSize = UDP.parsePacket(); + if (packetSize) { + Serial.print("Received packet! Size: "); + Serial.println(packetSize); + int len = UDP.read(UDP_packet, 90 * 4); + + for(int i = 0 ; i < len ; i += 4 ) + set_pixelw(i/4, CRGBW(UDP_packet[i],UDP_packet[i+1],UDP_packet[i+2],UDP_packet[i+3])); + + show_pixel(); + } + + packetSize = UDP2.parsePacket(); + if (packetSize) { + Serial.print("Received packet2! ize: "); + Serial.println(packetSize); + int len = UDP2.read(UDP2_packet, 60); + + UDP2_packet[len] = '\0'; + + Serial.println("Lol nein"); + writeText(UDP2_packet); + } +} \ No newline at end of file diff --git a/src/Server.h b/src/Server.h new file mode 100644 index 0000000..41ffaac --- /dev/null +++ b/src/Server.h @@ -0,0 +1,25 @@ +// SERVER.h +#ifndef SERVER_H +#define SERVER_H + +#include +#include +#include // websocket needs and doesnt include itself :/ +#include +#include +#include + +// TODO: #include + +void connectToWiFi(); +void setupWebServer(); +void handleWebClient(); +void handleTickerEvent(); +void handleUDP(); + +extern ESP8266WebServer server; +extern WebSocketsServer webSocket; + +extern bool enableFallbackAP; + +#endif // SERVER_H \ No newline at end of file diff --git a/src/StoreCSV.cpp b/src/StoreCSV.cpp new file mode 100644 index 0000000..6234cb3 --- /dev/null +++ b/src/StoreCSV.cpp @@ -0,0 +1,132 @@ +#include "StoreCSV.h" +#include "Server.h" + +std::vector entries; +int current_index; + +void handleCSVUpload() { + HTTPUpload& upload = server.upload(); + static File file; + + if (upload.status == UPLOAD_FILE_START) { + Serial.printf("Upload Start: %s\n", upload.filename.c_str()); + + // Initialize LittleFS + if (!LittleFS.begin()) { + Serial.println("An Error has occurred while mounting LittleFS. Formatting..."); + if (LittleFS.format()) { + Serial.println("Format successful. Please try again."); + } else { + Serial.println("Format failed. Please check your hardware or partition scheme."); + } + return; + } + + // Open the file on LittleFS + file = LittleFS.open("/uploaded.csv", "w"); + + if (!file) { + Serial.println("Error opening file for writing"); + return; + } + } else if (upload.status == UPLOAD_FILE_WRITE) { + Serial.printf("Upload Data: %s\n", upload.filename.c_str()); + + // Write the current chunk of data to the file + file.write(upload.buf, upload.currentSize); + } else if (upload.status == UPLOAD_FILE_END) { + Serial.printf("Upload End: %s, %u B\n", upload.filename.c_str(), upload.totalSize); + + // Close the file + file.close(); + + ESP.restart(); + } +} + +void readCSVfromFS() { + // Initialize LittleFS + if (!LittleFS.begin()) { + Serial.println("An Error has occurred while mounting LittleFS"); + return; + } + + // Check if the file exists in the filesystem + if (!LittleFS.exists("/uploaded.csv")) { + Serial.println("No CSV file found. Returning."); + return; + } + + // Open file for reading + File file = LittleFS.open("/uploaded.csv", "r"); + + if (!file) { + Serial.println("Failed to open CSV file for reading."); + return; + } + + Serial.println("Reading CSV file..."); + + // Clear previous entries + entries.clear(); + + // Read line by line + bool isHeader = true; + while (file.available()) { + String line = file.readStringUntil('\n'); + line.trim(); // remove potential CR/LF + + // Ignore the header row + if (isHeader) { + isHeader = false; + continue; + } + + // Split line + int col1pos = line.indexOf(";"); + int col2pos = line.indexOf(";", col1pos + 1); + int col3pos = line.indexOf(";", col2pos + 1); + int col4pos = line.indexOf(";", col3pos + 1); + + if (col3pos != -1) { + String dateStr = line.substring(0, col1pos); + String timeStr = line.substring(col1pos + 1, col2pos); + String name = line.substring(col3pos + 1); + if (col4pos > col3pos) + name = line.substring(col3pos + 1, col4pos); + + struct tm timeinfo; + strptime(dateStr.c_str(), "%Y-%m-%d ", &timeinfo); + int hour = timeStr.substring(0,2).toInt(); + int min = timeStr.substring(2).toInt(); + + timeinfo.tm_hour = hour; + timeinfo.tm_min = min; + timeinfo.tm_sec = 0; + time_t timestamp = mktime(&timeinfo); + + entries.push_back(DataEntry(timestamp, name.c_str())); + } + } + + // Dump entries to Serial + for (const auto& entry : entries) { + struct tm *tm_info = localtime(&entry.timestamp); + char buffer[13]; + strftime(buffer, sizeof(buffer), "%d.%m. %H:%M", tm_info); + Serial.printf("%s (%lld): %s\n", + buffer, entry.timestamp, entry.name.c_str()); + } + + file.close(); + + current_index = 0; + time_t next_event_ts = 0; + time_t now; + time(&now); + while (entries[current_index].timestamp < now) { + current_index++; + } + Serial.printf("Current TS: %lld, Current index: %i, next event @ %lld: %s\n", + now, current_index, entries[current_index].timestamp, entries[current_index].name.c_str()); +} diff --git a/src/StoreCSV.h b/src/StoreCSV.h new file mode 100644 index 0000000..0606ebd --- /dev/null +++ b/src/StoreCSV.h @@ -0,0 +1,23 @@ +// StoreCSV.h +#ifndef STORE_CSV_H +#define STORE_CSV_H + +#include +#include +#include +#include "main.h" + +struct DataEntry { + time_t timestamp; + std::string name; + + DataEntry(time_t t, std::string n): timestamp(t), name(n) {} +}; + +extern std::vector entries; +extern int current_index; + +void handleCSVUpload(); +void readCSVfromFS(); + +#endif diff --git a/src/config.c b/src/config.c new file mode 100644 index 0000000..a3d0707 --- /dev/null +++ b/src/config.c @@ -0,0 +1,100 @@ +#include +#include + +char *lines[] = { + "Gay Space Piracy", + "AAAAAAAAAAAAAAAAAAAAAAAA", + "AAAAAAAAAAAAAAAAAAAAAARM", + "Temperatur: zu warm", + "circle line: barking", + "u fuckin' wat m8?", + "Zug h{{{{{{{{{{lt", + "F1nn5sterLIVE!!1!", + "a bit shit - innit?", + "uff", // 10 + "Toilette Besetzt", + "fucks shit", + "shits fucked", + "u fuckin' twat!", + "Schaffenburg e.V.", + "treffen - schaffen - teilen", + "AKPAC n.e.V.", + "AB\\TEK n.e.V", + "leagilize awoo", + "heute auf gleis dr|lf", // 20 + "be gay do crime", + "be trans be crime", + "do gay be crime", + "do trans be crime", + "be trans do crime", // 25 + "TRAAAAAAAAAAAAAAAAAAAANS", + "Unpolitischer Verein", + "werden sie jetzt Mitglied!", + "No awoo - $350 fine", + "comfii :3", + "Hermit Kingdom", // 30 + "Subscribe To Technoblade!", + "Technoblade Never Dies!", + "TRAAAAAAAAAAAAAAAAAAAIINS", + "Join the Querrdom!", + "Join Ab\\TEK!", // 35 + "Join AKPAC!", + "Toiletten Besetzen!", // plural oder so + "Enjoyer or Consumer?", + "be gay do gay or something", + "Explodierende Ratte", + "Air Compressor Failure Day", + "birds arent real", +// "the gayer the furry, the more a threat to national security they are - SiegedSec, + "daily noodles UwU", + "wear your safety cat ears!", + "All Cats Are Beautiful", + "Schaffenburg e.V. AD DS", + "Mind the Blahaj quota!", + "Do not feed the Google", +// "110010100 days without an accident, + "LED the planet", + "DECT the planet", + "I use arch btw", + "Temple OS, the third temple", + "Ich nutz Bogen bei dem Weg", +// "Everybody loves Systemd...", +// "Plug in EVERY USB u can on wifi", +// "SAMSUNG smart grenade AI", +// "Apple in garden of eden", +// "AI fuck off", +// "Parallel universes everywhere", +// "We need more tops", +// "Gift us irl catgirls", +// "We need more flummox :3", + "Otter, Otter! Space", +// "Satzzeichen = kein Rudeltier", +// "Our labelprinter our propaganda", +// "We need more flags UwU", + "UwUntu OwO OwO :33", +// "ADHS antifa was here", +// "We need only fans to finance this shit", + "Taste the rainbow", +// "Methylphenidat, Methylphenidat", +// "Only max 5 furries per flight", +// "I identify as an apache", +// "nginx / docker ?" +// "It's the V that kills, not A", +// "Bondage after midnight (perhaps)", + "MEEEOOOOWWW :33", +// "oooo u like kissing boys", + "powered by hamter", + "this sign is gay", +// "a cat has programmed this", +// "a fox has programmed this", +// "a fox has written over 60 of this", +// "take your HRT", +// "Linux install: 1. Sticker", +// "Ukraine needs more tops", +// "this escalated quickly... again", + "rm -rf / --no-preserve-root", + "::1 sweet ::1", +}; + +const size_t num_lines = sizeof(lines)/sizeof(*lines); +//*/ \ No newline at end of file diff --git a/src/config.h b/src/config.h new file mode 100644 index 0000000..831cbb9 --- /dev/null +++ b/src/config.h @@ -0,0 +1,28 @@ +#ifndef CONFIG_H +#define CONFIG_H + +#include + +#define lenlines 38 + +extern char *lines[]; +extern const size_t num_lines; + +#define NUM_LEDS 90 +#define DATA_PIN D4 + +// HSV Modifier between leds +#define HSVM 10 + +#define FALLBACK_SSID "zugdingens" +#define FALLBACK_PASSWORD "immernochbesseralspeter" + +#define HOST "zugdingens" + +#define FALLBACK_IP IPAddress(10, 0, 0, 254) +#define GATEWAY_IP IPAddress(10, 0, 0, 254) +#define SUBNET_IP IPAddress(255, 255, 255, 0) + +#define UDP_PORT 4210 + +#endif // CONFIG_H \ No newline at end of file diff --git a/src/index.h b/src/index.h new file mode 100644 index 0000000..f97e449 --- /dev/null +++ b/src/index.h @@ -0,0 +1,141 @@ +/* + * This ESP8266 NodeMCU code was developed by newbiely.com + * + * This ESP8266 NodeMCU code is made available for public use without any restriction + * + * For comprehensive instructions and wiring diagrams, please visit: + * https://newbiely.com/tutorials/esp8266/esp8266-websocket + */ + +const char *HTML_CONTENT = R"=====( + + + +ESP8266 WebSocket + + + + + + +
+

ESP8266 WebSocket

+
+ + WebSocket: CLOSED +
+
+
+ + +
+ + +
+ + +)====="; diff --git a/src/main.cpp b/src/main.cpp new file mode 100644 index 0000000..3f88a21 --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,291 @@ +#include "main.h" + +#include +#include +#include +#include + +#include +#include + +#include +Preferences prefs = Preferences(); + +SoftwareSerial fis(D2, D1, false); +#include + +// toggle for the build-in text scroller +bool scroller = true; +bool rainbow = true; + +CRGBW leds[NUM_LEDS]; +CRGB *ledsRGB = (CRGB *) &leds[0]; + +void sleep(uint mils) { + Serial.print("sleep"); + Serial.println(mils); + + uint end = millis() + mils; + + while(end > millis()) { + delay(10); + } +} + +void setup() { + Serial.begin(115200); + + // databits 5 + (x & 07) -> x = 2 + // docu would be nice + // 7 bits + // even parity + // 2 stopbits? + + Serial.println("FastLED"); + FastLED.addLeds(ledsRGB, getRGBWsize(NUM_LEDS)); + FastLED.setBrightness(255); + + fis.begin(1200, SWSERIAL_7E2); + + fillColorW(255, 255, 255, 127); + FastLED.show(); + + sleep(100); + + writeText("booting, hi :3"); + Serial.println("boot!"); + + sleep(200); + + progress(0, SETUP_PROGRESS_MAX, 0, 254, 0); + + // wifi stuff + connectToWiFi(); + + setupWebServer(); +} + +time_t next = 0; +time_t now; +uint counter = 0; +uint8_t hue; + +int analogVal = 0; + +void loop() { + handleUDP(); + + handleWebClient(); + if (enableFallbackAP) { + return; + } + + webSocket.loop(); + + now = millis(); + + if ( now > next) { + next = now + 5; + } else { + return; + } + + hue = uint8_t(counter); + + // rainbow right to left + if(rainbow) { + for(int i = 0 ; i < NUM_LEDS/2 ; i++ ) { + uint8_t chur = hue + ( HSVM * i ); + + leds[i] = CHSV(chur, 255, 255); + leds[NUM_LEDS-i] = CHSV(chur, 255, 255); + } //*/ + + FastLED.show(); + } + // rainbow center out btoked + /* + int li; + CRGB c; + for(int i = 0 ; i < NUM_LEDS/4 ; i++ ) { + uint8_t chur = hue + ( HSVM * i ); + li = NUM_LEDS/4 - li; + c = CHSV(chur, 255, 255); + + leds[li] = c; + leds[NUM_LEDS/2 - li] = c; + + leds[NUM_LEDS/2 + li] = c; + leds[NUM_LEDS-li] = c; + } +*/ + // loop di loop + /* + for(int i = 0 ; i < NUM_LEDS ; i++ ) + leds[i] = CRGBW(0,0,0,255); + + for(int i = counter%NUM_LEDS ; i < counter%NUM_LEDS + 7 ; i++ ) { + leds[i%NUM_LEDS] = CHSV(hue, 255,255); + } + */ + + /* + fill_stripes(CRGB(91, 206, 250), CRGB(245, 169, 184), CRGB(255, 255, 255), + CRGB(245, 169, 184), CRGB(91, 206, 250)); + */ + + // 1000ms / 5ms -> 10s + if (scroller && ((counter % (10000/5)) == 0)) { + char *a = lines[random(0, num_lines)]; + + Serial.print("@"); + Serial.print(counter); + Serial.print(">"); + Serial.println(a); + + writeText(a); + } + + counter ++; +} + +void writeText(const char text[]) { + // Example string to be sent + // header + terminator + checksum + int strl = strlen(text); + + int len_t = 1 + 1 + 1; + len_t += 16; // at least for padding thingy + + if ( strl > 16 ) + len_t += strl - 16; + + // Create a buffer with a size of 16 bytes + char b[len_t]; + + // Append 'v' to the beginning + b[0] = 'v'; + + // Append the string t to the buffer + memcpy(b+1, text, len_t - 1); + + // Fill the remaining buffer with spaces (0x20) + // from after start til after string before checksum + for (int i = 1 + strl; i < len_t-2; i++) { + b[i] = 0x20; + } + + b[len_t-2] = 0x0D; // trailer + + char checksum = 0x7F; + + // except checksum itself lel + for ( int i = 0 ; i < len_t-1 ; i++ ) + checksum ^= b[i]; + + b[len_t-1] = checksum; // checkum + + // Send the buffer over SoftwareSerial + fis.write(b, len_t); + //for ( uint8 i = 0 ; i < len_t ; i++ ) + // Serial.printf("d %d %x %c\n", i, b[i], b[i]); +} + +/* + ======= + ------- Fast LED stuff + ======= +*/ + +void fill_stripes(CRGB a, CRGB b, CRGB c, CRGB d, CRGB e) { + #define stripewidth (NUM_LEDS / 5 / 2) + + #define STRIPE(I, VARIABLE) do { \ + fillColorFT(stripewidth * I, stripewidth * (I+1), VARIABLE);\ + fillColorFT(NUM_LEDS - (stripewidth * (I+1)), NUM_LEDS - (stripewidth * I), VARIABLE);\ + } while(0); + + STRIPE(0, a) + STRIPE(1, b) + STRIPE(2, c) + STRIPE(3, d) + STRIPE(4, e) +} + +void set_pixelw(int i, CRGBW color) { + leds[i] = color; +} + +void show_pixel() { + FastLED.show(); +} + +void fillColorFT(int from, int to, CRGB color) { + for ( int i = from ; i < to ; i++ ) + leds[i] = color; +} + +void fillColorW(uint8_t r, uint8_t g, uint8_t b, uint8_t w) { + for ( int i = 0 ; i < NUM_LEDS ; i++ ) + leds[i] = CRGBW(r, g, b, w); + + FastLED.show(); +} + +void fillColor(uint8_t r, uint8_t g, uint8_t b) { + for(int i = 0 ; i < NUM_LEDS ; i++ ) + leds[i] = CRGB(r,g,b); + + FastLED.show(); +} + +uint8_t progress_status = 0; +uint8_t progress_total = 1; +uint8_t progress_r = 0; +uint8_t progress_g = 0; +uint8_t progress_b = 0; + +#define PROGRESS_BACKGROUND CRGBW(0,0,0,254); + +// sets progress thingy +void progress_color(uint8 r, uint8 g, uint8 b) { + progress_r = r; progress_g = g; progress_b = b; +} + +void add_progress() { + progress(progress_status+1, progress_total, + progress_r, progress_g, progress_b); +} + +void set_progress(uint8 x, uint8 of) { + progress(x, of, progress_r, progress_g, progress_b); +} + +void show_progress() { + progress(progress_status, progress_total, + progress_r, progress_g, progress_b); +} + +void progress(uint8 progress, uint8 total, uint8 r, uint8 g, uint8 b) { + Serial.print(progress); + Serial.print("/"); + Serial.println(total); + + progress_status = progress; + progress_total = total; + + progress_r = r; progress_g = g; progress_b = b; + + CRGB c = CRGB(r, g, b); + uint8_t lastled = progress * NUM_LEDS/2 / total; + + for(int i = 0 ; i < NUM_LEDS ; i++ ) + leds[i] = PROGRESS_BACKGROUND; + + for(int i = 0 ; i < lastled ; i++ ) { + leds[i] = c; + leds[NUM_LEDS - i] = c; + } + + FastLED.show(); +} \ No newline at end of file diff --git a/src/main.h b/src/main.h new file mode 100644 index 0000000..fd97bd8 --- /dev/null +++ b/src/main.h @@ -0,0 +1,30 @@ +#include + +#ifndef MAIN_H +#define MAIN_H + +#define SETUP_PROGRESS_MAX 8 + +#include +extern Preferences prefs; +extern bool scroller; +extern bool rainbow; + +#include +#include + +void writeText(const char[]); +void fillColorW(uint8_t r, uint8_t g, uint8_t b, uint8_t w); +void fillColor(uint8_t r, uint8_t g, uint8_t b); + +void progress(uint8 progress, uint8 total, uint8 r, uint8 g, uint8 b); +void add_progress(); +void progress_color(uint8 r, uint8 g, uint8 b); +void set_progress(uint8 x, uint8 of); +void show_progress(); +void fill_stripes(CRGB, CRGB, CRGB, CRGB, CRGB); +void fillColorFT(int from, int to, CRGB color); +void set_pixelw(int p, CRGBW); +void show_pixel(); + +#endif // MAIN_H \ No newline at end of file diff --git a/src/pages.h b/src/pages.h new file mode 100644 index 0000000..0915269 --- /dev/null +++ b/src/pages.h @@ -0,0 +1,207 @@ +// Pages.h +#ifndef PAGES_H +#define PAGES_H + +/* + * Form to display strings page + */ +const char* displayForm = +"
" + "
" + " " + "" + "
" +"
" +"
" + "" + "" + "" + "" +"
" +"
" + "" + "" + "" + "" +"
" +"
Status
" +"
Ticker" +"" +""; + +const char* tickerForm = +"
" + "" + "
" + "" + "
" + "" + "
" + "" + "
" + "" + "
" + "" +"
" +""; + +/* + * Login page + */ +const char* colorPickerIndex = +"" +"" +"

Color:

" +""; + +/* + * Login page + */ +const char* loginIndex = + "
" + "" + "" + "" + "
" + "
" + "" + "" + "" + "" + "
" + "
" + "" + "" + "" + "
" + "
" + "" + "" + "" + "" + "
" + "
ESP32 OTA Login Page
" + "
" + "
Username:
Key:
" +"
"; + +/* + * Server Index Page + */ + +const char* serverIndex = +"" +"
" + "" + "" + "
" + "
progress: 0%
" + ""; + +/* + * CSV Upload page + */ +const char* csvUploadPage = +"
" + "" + "
" + "" +"
" +""; + +/* + * Wifi credentials form + */ +const char* wifiCredentialsTemplate = +"" +"
" +"SSID:

" +"WiFi Password:


" +"OTA Password:


" +"
"; + +#endif diff --git a/test/README b/test/README new file mode 100644 index 0000000..9b1e87b --- /dev/null +++ b/test/README @@ -0,0 +1,11 @@ + +This directory is intended for PlatformIO Test Runner and project tests. + +Unit Testing is a software testing method by which individual units of +source code, sets of one or more MCU program modules together with associated +control data, usage procedures, and operating procedures, are tested to +determine whether they are fit for use. Unit testing finds problems early +in the development cycle. + +More information about PlatformIO Unit Testing: +- https://docs.platformio.org/en/latest/advanced/unit-testing/index.html