Initial commit.

This commit is contained in:
Riley L. 2024-08-21 04:59:06 +02:00
commit 8f4ca71f72
21 changed files with 1683 additions and 0 deletions

5
.gitignore vendored Normal file
View file

@ -0,0 +1,5 @@
.pio
.vscode/.browse.c_cpp.db*
.vscode/c_cpp_properties.json
.vscode/launch.json
.vscode/ipch

10
.vscode/extensions.json vendored Normal file
View file

@ -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"
]
}

3
.vscode/settings.json vendored Normal file
View file

@ -0,0 +1,3 @@
{
"C_Cpp.errorSquiggles": "disabled"
}

1
fw.bin Symbolic link
View file

@ -0,0 +1 @@
.pio/build/d1_mini_lite/firmware.bin

39
include/README Normal file
View file

@ -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

46
lib/README Normal file
View file

@ -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 <Foo.h>
#include <Bar.h>
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

21
platformio.ini Normal file
View file

@ -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

0
src/FIS.c Normal file
View file

15
src/FIS.h Normal file
View file

@ -0,0 +1,15 @@
/*
#ifndef FIS_H
#define FIS_H
#include <Arduino.h>
#include <SoftwareSerial.h>
void begin();
void writeText(char text[]);
#include <FIS.c>
#endif // FIS_H
*/

56
src/RGBW.h Normal file
View file

@ -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 <Arduino.h>
#include <FastLED.h>
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

499
src/Server.cpp Normal file
View file

@ -0,0 +1,499 @@
#include "config.h"
#include "main.h"
#include "pages.h"
#include <Updater.h>
#include "StoreCSV.h"
#include "RGBW.h"
#include <ESP8266WiFi.h>
#include <ESP8266WebServer.h>
#include <ESP8266mDNS.h>
#include <WiFiUdp.h>
#include <WebSocketsServer.h>
#include <Preferences.h>
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<String> 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);
}
}

25
src/Server.h Normal file
View file

@ -0,0 +1,25 @@
// SERVER.h
#ifndef SERVER_H
#define SERVER_H
#include <ESP8266WiFi.h>
#include <ESP8266WebServer.h>
#include <WiFiClientSecure.h> // websocket needs and doesnt include itself :/
#include <ESP8266mDNS.h>
#include <Updater.h>
#include <WebSocketsServer.h>
// TODO: #include <Update.h>
void connectToWiFi();
void setupWebServer();
void handleWebClient();
void handleTickerEvent();
void handleUDP();
extern ESP8266WebServer server;
extern WebSocketsServer webSocket;
extern bool enableFallbackAP;
#endif // SERVER_H

132
src/StoreCSV.cpp Normal file
View file

@ -0,0 +1,132 @@
#include "StoreCSV.h"
#include "Server.h"
std::vector<DataEntry> 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());
}

23
src/StoreCSV.h Normal file
View file

@ -0,0 +1,23 @@
// StoreCSV.h
#ifndef STORE_CSV_H
#define STORE_CSV_H
#include <LittleFS.h>
#include <vector>
#include <sstream>
#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<DataEntry> entries;
extern int current_index;
void handleCSVUpload();
void readCSVfromFS();
#endif

100
src/config.c Normal file
View file

@ -0,0 +1,100 @@
#include <config.h>
#include <Arduino.h>
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);
//*/

28
src/config.h Normal file
View file

@ -0,0 +1,28 @@
#ifndef CONFIG_H
#define CONFIG_H
#include <Arduino.h>
#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

141
src/index.h Normal file
View file

@ -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"=====(
<!DOCTYPE html>
<!-- saved from url=(0019)http://192.168.0.2/ -->
<html><head><meta http-equiv="Content-Type" content="text/html; charset=windows-1252">
<title>ESP8266 WebSocket</title>
<meta name="viewport" content="width=device-width, initial-scale=0.7">
<link rel="icon" href="https://diyables.io/images/page/diyables.svg">
<style>
/* Add some basic styling for the chat window */
body {
font-size: 16px;
}
.chat-container {
width: 400px;
margin: 0 auto;
padding: 10px;
}
.chat-messages {
height: 250px;
overflow-y: auto;
border: 1px solid #444;
padding: 5px;
margin-bottom: 5px;
}
.user-input {
display: flex;
margin-bottom: 20px;
}
.user-input input {
flex: 1;
border: 1px solid #444;
padding: 5px;
}
.user-input button {
margin-left: 5px;
background-color: #007bff;
color: #fff;
border: none;
padding: 5px 10px;
cursor: pointer;
}
.websocket {
display: flex;
align-items: center;
margin-bottom: 5px;
}
.websocket button {
background-color: #007bff;
color: #fff;
border: none;
padding: 5px 10px;
cursor: pointer;
}
.websocket .label {
margin-left: auto;
}
</style>
<script>
var ws;
var wsm_max_len = 4096; /* bigger length causes uart0 buffer overflow with low speed smart device */
function update_text(text) {
var chat_messages = document.getElementById("chat-messages");
chat_messages.innerHTML += text + '<br>';
chat_messages.scrollTop = chat_messages.scrollHeight;
}
function send_onclick() {
if(ws != null) {
var message = document.getElementById("message").value;
if (message) {
document.getElementById("message").value = "";
ws.send(message + "\n");
update_text('<span style="color:navy">' + message + '</span>');
// You can send the message to the server or process it as needed
}
}
}
function connect_onclick() {
if(ws == null) {
ws = new WebSocket("ws://" + window.location.host + ":81");
document.getElementById("ws_state").innerHTML = "CONNECTING";
ws.onopen = ws_onopen;
ws.onclose = ws_onclose;
ws.onmessage = ws_onmessage;
} else
ws.close();
}
function ws_onopen() {
document.getElementById("ws_state").innerHTML = "<span style='color:blue'>CONNECTED</span>";
document.getElementById("bt_connect").innerHTML = "Disconnect";
document.getElementById("chat-messages").innerHTML = "";
}
function ws_onclose() {
document.getElementById("ws_state").innerHTML = "<span style='color:gray'>CLOSED</span>";
document.getElementById("bt_connect").innerHTML = "Connect";
ws.onopen = null;
ws.onclose = null;
ws.onmessage = null;
ws = null;
}
function ws_onmessage(e_msg) {
e_msg = e_msg || window.event; // MessageEvent
console.log(e_msg.data);
update_text('<span style="color:blue">' + e_msg.data + '</span>');
}
</script>
</head>
<body>
<div class="chat-container">
<h2>ESP8266 WebSocket</h2>
<div class="websocket">
<button class="connect-button" id="bt_connect" onclick="connect_onclick()">Connect</button>
<span class="label">WebSocket: <span id="ws_state"><span style="color:blue">CLOSED</span></span></span>
</div>
<div class="chat-messages" id="chat-messages"></div>
<div class="user-input">
<input type="text" id="message" placeholder="Type your message...">
<button onclick="send_onclick()">Send</button>
</div>
<div class="sponsor">Sponsored by <a href="https://amazon.com/diyables">DIYables</a></div>
</div>
</body></html>
)=====";

291
src/main.cpp Normal file
View file

@ -0,0 +1,291 @@
#include "main.h"
#include <Arduino.h>
#include <FastLED.h>
#include <RGBW.h>
#include <SoftwareSerial.h>
#include <config.h>
#include <Server.h>
#include <Preferences.h>
Preferences prefs = Preferences();
SoftwareSerial fis(D2, D1, false);
#include <FIS.c>
// 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<WS2812B, DATA_PIN, RGB>(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();
}

30
src/main.h Normal file
View file

@ -0,0 +1,30 @@
#include <Arduino.h>
#ifndef MAIN_H
#define MAIN_H
#define SETUP_PROGRESS_MAX 8
#include <Preferences.h>
extern Preferences prefs;
extern bool scroller;
extern bool rainbow;
#include <FastLED.h>
#include <RGBW.h>
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

207
src/pages.h Normal file
View file

@ -0,0 +1,207 @@
// Pages.h
#ifndef PAGES_H
#define PAGES_H
/*
* Form to display strings page
*/
const char* displayForm =
"<form id='StringForm' method='post'>"
"<textarea name='text' rows='2' cols='40'></textarea><br>"
"<input type='submit' value='Senden'>&nbsp;"
"<label for='liveMode'>Live Mode:</label>"
"<input type='checkbox' id='liveMode'><br>"
"</form>"
"<form id='Scroller' method='post'>"
"<input type='hidden' id='scroller' name='scroller'>"
"<label for='scroller'>Enable Scroller:</label>"
"<input type='submit' value='Off' onclick='document.getElementById(\"scroller\").value=\"Off\";'>"
"<input type='submit' value='On' onclick='document.getElementById(\"scroller\").value=\"On\";'>"
"</form>"
"<form id='Rainbow' method='post'>"
"<input type='hidden' id='rainbow' name='rainbow'>"
"<label for='scroller'>Enable Scroller:</label>"
"<input type='submit' value='Off' onclick='document.getElementById(\"rainbow\").value=\"Off\";'>"
"<input type='submit' value='On' onclick='document.getElementById(\"rainbow\").value=\"On\";'>"
"</form>"
"<div id='message'>Status</div>"
"<br><a href='/ticker'>Ticker</a>"
"<script>"
"function handleSubmit(formId) {"
"document.getElementById(formId).addEventListener('submit', function(event) {"
"event.preventDefault();"
"var xhr = new XMLHttpRequest();"
"xhr.open('POST', '/'+formId, true);"
"xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');"
"var data = new FormData(event.target);"
"xhr.send([...data.entries()].map(e => encodeURIComponent(e[0])+'='+encodeURIComponent(e[1])).join('&'));"
"xhr.onloadend = function() {"
"document.getElementById('message').textContent = 'Response (Status ' + xhr.status + '): ' + xhr.responseText;"
"}"
"});"
"}"
"['StringForm', 'Scroller', 'Rainbow'].forEach(handleSubmit);"
"</script>"
"<script>"
"var liveModeCheckbox = document.getElementById('liveMode');"
"var textArea = document.getElementsByName('text')[0];"
"var ws = new WebSocket('ws://' + location.hostname + ':81/');"
"textArea.addEventListener('input', function(e) {"
"if (liveModeCheckbox.checked) ws.send(e.target.value);"
"});"
"</script>";
const char* tickerForm =
"<form id='tickerForm' action='/tickerForm' method='post'>"
"<label for='Line1'>Line 1:</label>"
"<input type='text' id='Line1' name='Line1'><br>"
"<label for='Line2'>Line 2:</label>"
"<input type='text' id='Line2' name='Line2'><br>"
"<label for='Line3'>Line 3:</label>"
"<input type='text' id='Line3' name='Line3'><br>"
"<label for='timeout_ms'>Timeout (ms):</label>"
"<input type='number' name='timeout_ms' step='250' default='2000'></input><br>"
"<label for='toggle'>Enable:</label>"
"<input type='checkbox' id='toggle' name='toggle'><br>"
"<input type='submit' value='Senden'>"
"</form>"
"<script>"
"handleSubmit('tickerForm');"
"</script>";
/*
* Login page
*/
const char* colorPickerIndex =
"<head>"
"<script src='https://cdnjs.cloudflare.com/ajax/libs/jscolor/2.5.1/jscolor.min.js' integrity='sha512-/e+XGe8oSD9M1t0NKNCrUlRsiyeFTiZw4+pmf0g8wTbc8IfiLwJsjTODc/pq3hKhKAdsehJs7STPvX7SkFSpOQ==' crossorigin='anonymous' referrerpolicy='no-referrer'></script>"
"</head><body><p>Color: <input data-jscolor=\"{onInput:\'update(this)\'}\"></p>"
"<script>"
"var ws = new WebSocket('ws://' + location.hostname + ':81/');"
"ws.onopen = function () {"
"console.log('Connected via WebSocket');"
"};"
"ws.onerror = function (error) {"
"console.log('WebSocket Error: ', error);"
"};"
"function update(picker) {"
"console.log('{RGB}='+picker.toHEXString());"
"ws.send('{RGB}='+picker.toHEXString());"
"}"
"</script></body></html>";
/*
* Login page
*/
const char* loginIndex =
"<form action='/serverIndex' method='get'>"
"<table width='20%' bgcolor='A09F9F' align='center'>"
"<tr>"
"<td colspan=2>"
"<center><font size=4><b>ESP32 OTA Login Page</b></font></center>"
"<br>"
"</td>"
"<br>"
"<br>"
"</tr>"
"<td>Username:</td>"
"<td><input size=25><br></td>"
"</tr>"
"<br>"
"<br>"
"<tr>"
"<td>Key:</td>"
"<td><input type='Password' size=25 name='k'><br></td>"
"<br>"
"<br>"
"</tr>"
"<tr>"
"<td><input type='submit' value='Login'></td>"
"</tr>"
"</table>"
"</form>";
/*
* Server Index Page
*/
const char* serverIndex =
"<script src='https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js'></script>"
"<form method='POST' action='#' enctype='multipart/form-data' id='upload_form'>"
"<input type='file' name='update'>"
"<input type='submit' value='Update'>"
"</form>"
"<div id='prg'>progress: 0%</div>"
"<script>"
"$('form').submit(function(e){"
"e.preventDefault();"
"var form = $('#upload_form')[0];"
"var data = new FormData(form);"
" $.ajax({"
"url: `/update?k=${(new URLSearchParams(window.location.search)).get('k')}`,"
"type: 'POST',"
"data: data,"
"contentType: false,"
"processData:false,"
"xhr: function() {"
"var xhr = new window.XMLHttpRequest();"
"xhr.upload.addEventListener('progress', function(evt) {"
"if (evt.lengthComputable) {"
"var per = evt.loaded / evt.total;"
"$('#prg').html('progress: ' + Math.round(per*100) + '%');"
"}"
"}, false);"
"return xhr;"
"},"
"success:function(d, s) {"
"console.log('success!')"
"},"
"error: function (a, b, c) {"
"}"
"});"
"});"
"</script>";
/*
* CSV Upload page
*/
const char* csvUploadPage =
"<form method='POST' action='/uploadCSV' enctype='multipart/form-data' id='upload_csv_form'>"
"<label for='csvFile'>Upload CSV File:</label>"
"<input type='file' name='csvFile' accept='.csv'><br>"
"<input type='submit' value='Upload'>"
"</form>"
"<script>"
"$('#upload_csv_form').submit(function(e){"
"e.preventDefault();"
"var form = $('#upload_csv_form')[0];"
"var data = new FormData(form);"
" $.ajax({"
"url: '/uploadCSV',"
"type: 'POST',"
"data: data,"
"contentType: false,"
"processData:false,"
"success:function(d, s) {"
"console.log('File uploaded successfully!')"
"},"
"error: function (a, b, c) {"
"console.log('Error during file upload.');"
"}"
"});"
"});"
"</script>";
/*
* Wifi credentials form
*/
const char* wifiCredentialsTemplate =
"<html><body>"
"<form method='post' action='/saveCredentials'>"
"SSID:<br><input type='text' name='ssid' text='%s'><br>"
"WiFi Password:<br><input type='text' name='wifi_pw' text='%s'><br><br>"
"OTA Password:<br><input type='text' name='ota_pw' text='%s'><br><br>"
"<input type='submit' value='Save'></form></body></html>";
#endif

11
test/README Normal file
View file

@ -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