#include <TFT_eSPI.h>
#include <XPT2046_Bitbang.h>
#include "command_map.h"
#include <Adafruit_NeoPixel.h>
#include "FS.h"
#include "SD.h"
#include "SPI.h"
// --- Touchscreen Pins ---
#define MOSI_PIN 32
#define MISO_PIN 39
#define CLK_PIN 25
#define CS_PIN 33
#define NEOPIXEL_PIN 27 // NeoPixel data pin
#define NUM_IND 24 // Number of NeoPixels
// ----------------------------
// SD Reader pins (default VSPI pins)
// ----------------------------
#define SD_SCK 18
#define SD_MISO 19
#define SD_MOSI 23
#define SD_CS 5
#define GREEN_LED 16 // Turns ON at startup for confirmation
#define BLUE_LED 17 // Blinks ON when touched // GREEN TURNS OFF
#define RED_LED 4 // TURNS ON when ERROR
#define NEOPIXEL_PIN 27 // NeoPixel data pin
#define NUM_IND 24 // Number of NeoPixels
HardwareSerial &pc = Serial;
HardwareSerial &megaSerial = Serial2; // or Serial1 if you're using that instead
Adafruit_NeoPixel WORK_IND(NUM_IND, NEOPIXEL_PIN, NEO_GRB + NEO_KHZ800);
// --------------- 8888888888888 state stubs D.N.T.!!! 88888888888888888888888888-------------
bool relayStates[8] = {false, false, false, false, false, false, false, false};
// --- TFT & Touchscreen Instances ---
TFT_eSPI tft = TFT_eSPI();
XPT2046_Bitbang touchscreen(MOSI_PIN, MISO_PIN, CLK_PIN, CS_PIN);
SPIClass mySpi = SPIClass(VSPI);
// --- Button Struct ---
struct PillButton {
const char* label;
int x, y, w, h;
bool pressed;
void (*onPress)();
};
PillButton buttons[6];
// --- Menu Handlers ---
void showMainMenu();
void showWiFiMenu();
void showSDMenu();
void showShowMenu();
void showDiagnosticsMenu();
void showAddonsMenu();
void showSettingsMenu();
void wifiButtonHandler() { Serial.println("WiFi button pressed"); showWiFiMenu(); }
void sdCardButtonHandler() { Serial.println("SD Card button pressed"); showSDMenu(); }
void showMenuButtonHandler() { Serial.println("Show Menu button pressed"); showShowMenu(); }
void diagnosticsButtonHandler(){ Serial.println("Diagnostics button pressed"); showDiagnosticsMenu(); }
void addonsButtonHandler() { Serial.println("Add-ons button pressed"); showAddonsMenu(); }
void settingsButtonHandler() { Serial.println("Settings button pressed"); showSettingsMenu(); }
void backToMainHandler() { Serial.println("Back to Main Menu"); showMainMenu(); }
// --- Labels & Handlers (static for pointer validity) ---
static const char* mainLabels[6] = {"WiFi", "sd card", "show menu", "diagnostics", "add-ons", "settings"};
static void (*mainHandlers[6])() = {wifiButtonHandler, sdCardButtonHandler, showMenuButtonHandler,
diagnosticsButtonHandler, addonsButtonHandler, settingsButtonHandler};
static const char* wifiLabels[6] = {"AP ON", "AP OFF", "Internet", "BT ON", "BT OFF", "Back"};
static void (*wifiHandlers[6])() = {nullptr, nullptr, nullptr, nullptr, nullptr, backToMainHandler};
static const char* sdLabels[6] = {"Back","","","","",""};
static void (*sdHandlers[6])() = {backToMainHandler,nullptr,nullptr,nullptr,nullptr,nullptr};
static const char* showLabels[6] = {"Back","","","","",""};
static void (*showHandlers[6])() = {backToMainHandler,nullptr,nullptr,nullptr,nullptr,nullptr};
static const char* diagLabels[6] = {"Back","","","","",""};
static void (*diagHandlers[6])() = {backToMainHandler,nullptr,nullptr,nullptr,nullptr,nullptr};
static const char* addonLabels[6] = {"Back","","","","",""};
static void (*addonHandlers[6])() = {backToMainHandler,nullptr,nullptr,nullptr,nullptr,nullptr};
static const char* settingsLabels[6] = {"Back","","","","",""};
static void (*settingsHandlers[6])() = {backToMainHandler,nullptr,nullptr,nullptr,nullptr,nullptr};
// --------------- 8888888888888 SD CARD FUNCTIONS d.n.t 88888888888888888888888888-------------
void listDir(fs::FS &fs, const char * dirname, uint8_t levels) {
Serial.printf("Listing directory: %s\n", dirname);
File root = fs.open(dirname);
if (!root) {
Serial.println("Failed to open directory");
return;
}
if (!root.isDirectory()) {
Serial.println("Not a directory");
return;
}
File file = root.openNextFile();
while (file) {
if (file.isDirectory()) {
Serial.print(" DIR : ");
Serial.println(file.name());
if (levels) {
listDir(fs, file.path(), levels - 1);
}
} else {
Serial.print(" FILE: ");
Serial.print(file.name());
Serial.print(" SIZE: ");
Serial.println(file.size());
}
file = root.openNextFile();
}
}
void createDir(fs::FS &fs, const char * path) {
Serial.printf("Creating Dir: %s\n", path);
if (fs.mkdir(path)) {
Serial.println("Dir created");
} else {
Serial.println("mkdir failed");
}
}
void removeDir(fs::FS &fs, const char * path) {
Serial.printf("Removing Dir: %s\n", path);
if (fs.rmdir(path)) {
Serial.println("Dir removed");
} else {
Serial.println("rmdir failed");
}
}
void readFile(fs::FS &fs, const char * path) {
Serial.printf("Reading file: %s\n", path);
File file = fs.open(path);
if (!file) {
Serial.println("Failed to open file for reading");
return;
}
Serial.print("Read from file: ");
while (file.available()) {
Serial.write(file.read());
}
file.close();
}
void writeFile(fs::FS &fs, const char * path, const char * message) {
Serial.printf("Writing file: %s\n", path);
File file = fs.open(path, FILE_WRITE);
if (!file) {
Serial.println("Failed to open file for writing");
return;
}
if (file.print(message)) {
Serial.println("File written");
} else {
Serial.println("Write failed");
}
file.close();
}
void appendFile(fs::FS &fs, const char * path, const char * message) {
Serial.printf("Appending to file: %s\n", path);
File file = fs.open(path, FILE_APPEND);
if (!file) {
Serial.println("Failed to open file for appending");
return;
}
if (file.print(message)) {
Serial.println("Message appended");
} else {
Serial.println("Append failed");
}
file.close();
}
void renameFile(fs::FS &fs, const char * path1, const char * path2) {
Serial.printf("Renaming file %s to %s\n", path1, path2);
if (fs.rename(path1, path2)) {
Serial.println("File renamed");
} else {
Serial.println("Rename failed");
}
}
void deleteFile(fs::FS &fs, const char * path) {
Serial.printf("Deleting file: %s\n", path);
if (fs.remove(path)) {
Serial.println("File deleted");
} else {
Serial.println("Delete failed");
}
}
void testFileIO(fs::FS &fs, const char * path) {
File file = fs.open(path);
static uint8_t buf[512];
size_t len = 0;
uint32_t start = millis();
uint32_t end = start;
if (file) {
len = file.size();
size_t flen = len;
start = millis();
while (len) {
size_t toRead = len;
if (toRead > 512) {
toRead = 512;
}
file.read(buf, toRead);
len -= toRead;
}
end = millis() - start;
Serial.printf("%u bytes read for %u ms\n", flen, end);
file.close();
} else {
Serial.println("Failed to open file for reading");
}
file = fs.open(path, FILE_WRITE);
if (!file) {
Serial.println("Failed to open file for writing");
return;
}
size_t i;
start = millis();
for (i = 0; i < 2048; i++) {
file.write(buf, 512);
}
end = millis() - start;
Serial.printf("%u bytes written for %u ms\n", 2048 * 512, end);
file.close();
}
void SDCARDSETUP(){
SPIClass spi = SPIClass(VSPI);
if (!SD.begin(SS, spi, 80000000)) {
Serial.println("Card Mount Failed");
return;
}
uint8_t cardType = SD.cardType();
// Append total SD space info as a separate formatted line
char sdInfo[50];
unsigned long long totalMB = SD.totalBytes() / (1024 * 1024);
sprintf(sdInfo, "> Total space: %lluMB", totalMB);
tft.drawString(sdInfo, 10, 10 + 6 * 15);
delay(700);
if (cardType == CARD_NONE) {
Serial.println("No SD card attached");
return;
}
Serial.print("SD Card Type: ");
if (cardType == CARD_MMC) {
Serial.println("MMC");
} else if (cardType == CARD_SD) {
Serial.println("SDSC");
} else if (cardType == CARD_SDHC) {
Serial.println("SDHC");
} else {
Serial.println("UNKNOWN");
}
uint64_t cardSize = SD.cardSize() / (1024 * 1024);
Serial.printf("SD Card Size: %lluMB\n", cardSize);
listDir(SD, "/", 0);
createDir(SD, "/mydir");
listDir(SD, "/", 0);
removeDir(SD, "/mydir");
listDir(SD, "/", 2);
writeFile(SD, "/hello.txt", "Hello ");
appendFile(SD, "/hello.txt", "World!\n");
readFile(SD, "/hello.txt");
deleteFile(SD, "/foo.txt");
renameFile(SD, "/hello.txt", "/foo.txt");
readFile(SD, "/foo.txt");
testFileIO(SD, "/test.txt");
Serial.printf("Total space: %lluMB\n", SD.totalBytes() / (1024 * 1024));
Serial.printf("Used space: %lluMB\n", SD.usedBytes() / (1024 * 1024));
}
// --------------- 8888888888888 SD CARD FUNCTIONS d.n.t 88888888888888888888888888-------------
// --------------- 8888888888888 MENU FUNCTIONS d.n.t 88888888888888888888888888-------------
// --- Draw a pill button (narrow, tall) ---
void drawPillButton(PillButton &btn, bool highlight = false) {
uint16_t color = highlight ? tft.color565(180, 0, 0) : TFT_RED;
int radius = 16;
tft.fillRoundRect(btn.x, btn.y, btn.w, btn.h, radius, color);
tft.setTextColor(TFT_WHITE, color);
tft.setTextDatum(MC_DATUM);
tft.drawString(btn.label, btn.x + btn.w/2, btn.y + btn.h/2);
}
// --- Button Setup (narrow pill, tall, spaced for 320x240) ---
void setupButtons(const char* labels[6], void (*handlers[6])()) {
int bw = 72; // Narrow width
int bh = 32; // Tall height
int bx = 75; // Left margin
int by = 38;
int h_spacing = 30;
int v_spacing = 32;
tft.setTextDatum(MC_DATUM);
for (int i = 0; i < 6; i++) {
int row = i / 2;
int col = i % 2;
int x = bx + col * (bw + h_spacing);
int y = by + row * (bh + v_spacing);
buttons[i] = {labels[i], x, y, bw, bh, false, handlers[i]};
if (labels[i] && *labels[i]) drawPillButton(buttons[i]);
}
}
void setupBackButton(void (*handler)()) {
buttons[0] = {"Back", 20, 200, 80, 30, false, handler};
drawPillButton(buttons[0]);
for (int i = 1; i < 6; i++) buttons[i].label = "";
}
// --- Main menu and submenus ---
void showMainMenu() {
// ==== Dynamic Relay Indicator (1-8) ====
for (int i = 0; i < 8; i++) {
int x = 10 + (i % 4) * 20;
int y = (i < 4) ? 40 : 120;
tft.setTextColor(relayStates[i] ? TFT_GREEN : TFT_RED);
tft.drawString(String(i + 1), x, y);
}
}
void showWiFiMenu() {
tft.fillScreen(TFT_BLACK);
tft.setTextColor(TFT_CYAN, TFT_BLACK);
tft.setTextDatum(MC_DATUM);
tft.drawString("WiFi Menu", 160, 18);
setupButtons(wifiLabels, wifiHandlers);
Serial.println("Displayed: WiFi Menu");
}
void showSDMenu() {
tft.fillScreen(TFT_BLACK);
tft.setTextColor(TFT_WHITE, TFT_BLACK);
tft.setTextDatum(MC_DATUM);
tft.drawString("SD Card Menu", 160, 18);
tft.setTextDatum(TL_DATUM);
tft.drawString("- show1.shdo", 30, 80, 2);
tft.drawString("- show2.shdo", 30, 110, 2);
tft.drawString("- config.txt", 30, 140, 2);
setupButtons(sdLabels, sdHandlers);
Serial.println("Displayed: SD Card Menu");
}
void showShowMenu() {
tft.fillScreen(TFT_BLACK);
tft.setTextColor(TFT_WHITE, TFT_BLACK);
tft.setTextDatum(MC_DATUM);
tft.drawString("Show Menu", 160, 18);
tft.setTextDatum(TL_DATUM);
tft.drawString("- TheChamber.shdo", 30, 80, 2);
tft.drawString("- CoffinJump.shdo", 30, 110, 2);
tft.drawString("- GhostFX.shdo", 30, 140, 2);
setupButtons(showLabels, showHandlers);
Serial.println("Displayed: Show Menu");
}
void showDiagnosticsMenu() {
tft.fillScreen(TFT_BLACK);
tft.setTextColor(TFT_WHITE, TFT_BLACK);
tft.setTextDatum(MC_DATUM);
tft.drawString("Diagnostics", 160, 18);
tft.setTextDatum(TL_DATUM);
tft.drawString("Voltage: OK", 30, 80, 2);
tft.drawString("RTC: Synced", 30, 110, 2);
tft.drawString("Relays: Ready", 30, 140, 2);
setupButtons(diagLabels, diagHandlers);
Serial.println("Displayed: Diagnostics Menu");
}
void showAddonsMenu() {
tft.fillScreen(TFT_BLACK);
tft.setTextColor(TFT_WHITE, TFT_BLACK);
tft.setTextDatum(MC_DATUM);
tft.drawString("Add-ons Menu", 160, 18);
tft.setTextDatum(TL_DATUM);
tft.drawString("- Lantern FX", 30, 80, 2);
tft.drawString("- R3 Terminal", 30, 110, 2);
tft.drawString("- Smoke Controller", 30, 140, 2);
setupButtons(addonLabels, addonHandlers);
Serial.println("Displayed: Add-ons Menu");
}
void showSettingsMenu() {
tft.fillScreen(TFT_BLACK);
tft.setTextColor(TFT_WHITE, TFT_BLACK);
tft.setTextDatum(MC_DATUM);
tft.drawString("Settings Menu", 160, 18);
tft.setTextDatum(TL_DATUM);
tft.drawString("- Brightness: 100%", 30, 80, 2);
tft.drawString("- Volume A: 75%", 30, 110, 2);
tft.drawString("- Volume B: 65%", 30, 140, 2);
setupButtons(settingsLabels, settingsHandlers);
Serial.println("Displayed: Settings Menu");
}
// --- Touch Handler for XPT2046_Bitbang ---
// Uses touch.x and touch.y directly (already screen pixels)
void handleTouchInput() {
TouchPoint touch = touchscreen.getTouch();
if (touch.zRaw != 0) {
int x = touch.x;
int y = touch.y;
Serial.print("Touch @ X: ");
Serial.print(x);
Serial.print(", Y: ");
Serial.print(y);
Serial.print(", Z: ");
Serial.println(touch.zRaw);
tft.fillCircle(x, y, 3, TFT_BLUE); // Visual feedback
for (int i = 0; i < 6; i++) {
PillButton &btn = buttons[i];
if (!btn.label || !*btn.label) continue;
bool hit = x >= btn.x && x <= (btn.x + btn.w) &&
y >= btn.y && y <= (btn.y + btn.h);
if (hit && !btn.pressed) {
Serial.print("Button pressed: ");
Serial.println(btn.label);
btn.pressed = true;
drawPillButton(btn, true);
delay(150);
drawPillButton(btn, false);
btn.pressed = false;
if (btn.onPress) btn.onPress();
}
}
delay(200); // debounce
}
}
void showTerminalSplash() {
tft.fillScreen(TFT_BLACK);
tft.setTextDatum(TL_DATUM);
tft.setTextColor(TFT_GREEN);
tft.setTextSize(1);
SDCARDSETUP();
const char *bootLines[] = {
"> Initializing SHOWDUINO system...",
"> Loading interface modules...",
"> Checking relays and ports...",
"> Checking SD card functions...",
"> System online.",
"> Launching control terminal..."
};
// Draw the main boot lines
for (int i = 0; i < 6; i++) {
tft.drawString(bootLines[i], 10, 10 + i * 15);
delay(700);
}
}
void drawRelayIndicators() {
for (int i = 0; i < 8; i++) {
int x = 10 + (i % 4) * 20;
int y = (i < 4) ? 40 : 120;
tft.setTextColor(relayStates[i] ? TFT_GREEN : TFT_RED);
tft.setTextDatum(TL_DATUM);
tft.drawString(String(i + 1), x, y);
}
}
void handleMegaFeedback() {
while (Serial2.available()) {
String line = Serial2.readStringUntil('\n');
line.trim();
if (line.startsWith("RELAY")) {
int idx = line.charAt(5) - '1';
bool isOn = line.endsWith("ON");
if (idx >= 0 && idx < 8) {
relayStates[idx] = isOn;
drawRelayIndicators(); // 🔥 Quick, clean update
}
}
}
}
// --------------- 8888888888888 MENU FUNCTIONS d.n.t 88888888888888888888888888-------------
// --- Setup ---
void setup() {
pc.begin(115200);
megaSerial.begin(9600);
pc.println("ESP32 booting...");
megaSerial.println("HELLO_MEGA");
WORK_IND.begin();
WORK_IND.setBrightness(170);
WORK_IND.setPixelColor(1, 0, 255, 0); // Pixel update
WORK_IND.show();
pinMode(GREEN_LED, OUTPUT);
digitalWrite(GREEN_LED, HIGH); // LED ON for confirmation
pinMode(BLUE_LED, OUTPUT);
digitalWrite(BLUE_LED, LOW); // LED ON for confirmation
pinMode(RED_LED, OUTPUT);
digitalWrite(RED_LED, LOW); // LED ON for confirmation
tft.begin();
tft.setRotation(1); // portrait or landscape as desired
tft.fillScreen(TFT_BLACK);
touchscreen.begin();
showTerminalSplash();
Serial.println("Showduino pill button UI ready");
showMainMenu();
}
// --- Main Loop ---
void loop() {
handleTouchInput();
// Receive from Mega
if (megaSerial.available()) {
String raw = megaSerial.readStringUntil('\n');
pc.println("[FROM MEGA] " + raw);
// Check origin
if (raw.startsWith("MEGA:")) {
String command = raw.substring(5); // Strip "MEGA:"
handleSerialCommand(command); // Optional: act on it
}
}
// Optional: receive from Serial monitor or touchscreen
if (pc.available()) {
String raw = pc.readStringUntil('\n');
raw.trim();
if (raw.length()) {
// Tag this as coming from ESP and forward it to the Mega
megaSerial.println("ESP:" + raw);
handleSerialCommand(raw); // Optional: also process it locally
}
}
}