/**
* SHOWDUINO ESP32-S3 PROFESSIONAL SHOW CONTROLLER v2.5
* Complete system with working multiplexer, OLED, 35 scenes, emergency stop, EEPROM
* Using only visible pins from ESP32-S3 WROOM-1
*/
#include <Arduino.h>
#include <EEPROM.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
// ==================== PIN CONFIGURATION ====================
// MUX control pins (only visible pins)
int s0 = 12; // GPIO12
int s1 = 14; // GPIO14
int s2 = 13; // GPIO13
int s3 = 21; // GPIO21
// MUX signal pins
int mux1SignalPin = 4; // GPIO4
int mux2SignalPin = 5; // GPIO5
// Relay outputs (8 relays using visible pins)
const int relayPins[8] = {6, 7, 15, 16, 17, 18, 8, 3};
// System pins
#define STATUS_LED_PIN 2 // Built-in LED
// Emergency stop now on MUX channel 15 (button 15)
// OLED Display (I2C)
#define OLED_SDA 9 // I2C Data (visible pin)
#define OLED_SCL 10 // I2C Clock (visible pin)
#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64
#define OLED_RESET -1
// ==================== SYSTEM CONFIG ====================
#define EEPROM_SIZE 512
#define EEPROM_SETTINGS_ADDR 0
#define BUTTON_DEBOUNCE_MS 200
#define DISPLAY_UPDATE_MS 250
#define AUTO_SHUTOFF_MS 600000
#define BUTTON_THRESHOLD 2000
// ==================== GLOBALS ====================
struct SystemState {
bool relayStates[8] = {false};
uint32_t relayOnTime[8] = {0};
bool emergencyActive = false;
bool showRunning = false;
int8_t currentShow = -1;
uint32_t showStartTime = 0;
uint32_t lastActivity = 0;
uint16_t showsTriggered = 0;
// Button system
bool buttonStates[32] = {false};
bool lastButtonStates[32] = {false};
uint32_t lastButtonPress[32] = {0};
// Scene selection system
char sceneDigits[4] = {0, 0, 0, 0};
uint8_t digitIndex = 0;
uint32_t lastDigitTime = 0;
bool sceneReady = false;
} sysState;
struct ShowSettings {
uint16_t magic = 0xABCD;
bool autoShutoff = true;
uint8_t maxShowDuration = 5;
bool enableSafetyLimits = true;
} settings;
// Show system
struct ShowStep {
uint16_t duration;
uint8_t relayMask;
const char* action;
};
struct Show {
const char* name;
ShowStep* steps;
uint8_t stepCount;
uint16_t totalDuration;
};
// ==================== SHOW LIBRARY (35 SCENES) ====================
ShowStep scene001Steps[] = {{3000, 0b00000001, "Relay 1 Only"}, {1000, 0b00000000, "Off"}};
ShowStep scene002Steps[] = {{3000, 0b00000010, "Relay 2 Only"}, {1000, 0b00000000, "Off"}};
ShowStep scene003Steps[] = {{3000, 0b00000100, "Relay 3 Only"}, {1000, 0b00000000, "Off"}};
ShowStep scene004Steps[] = {{3000, 0b00001000, "Relay 4 Only"}, {1000, 0b00000000, "Off"}};
ShowStep scene005Steps[] = {{3000, 0b00010000, "Relay 5 Only"}, {1000, 0b00000000, "Off"}};
ShowStep scene006Steps[] = {{3000, 0b00100000, "Relay 6 Only"}, {1000, 0b00000000, "Off"}};
ShowStep scene007Steps[] = {{3000, 0b01000000, "Relay 7 Only"}, {1000, 0b00000000, "Off"}};
ShowStep scene008Steps[] = {{3000, 0b10000000, "Relay 8 Only"}, {1000, 0b00000000, "Off"}};
ShowStep scene009Steps[] = {{4000, 0b11111111, "All Relays On"}, {1000, 0b00000000, "Off"}};
ShowStep scene010Steps[] = {{2000, 0b00000011, "Relays 1&2"}, {1000, 0b00000000, "Off"}};
ShowStep scene011Steps[] = {{2000, 0b00000101, "Alternating 1"}, {1000, 0b00000000, "Off"}};
ShowStep scene012Steps[] = {{2000, 0b00001010, "Alternating 2"}, {1000, 0b00000000, "Off"}};
ShowStep scene013Steps[] = {{2000, 0b00010100, "Skip Pattern 1"}, {1000, 0b00000000, "Off"}};
ShowStep scene014Steps[] = {{2000, 0b00101000, "Skip Pattern 2"}, {1000, 0b00000000, "Off"}};
ShowStep scene015Steps[] = {{2000, 0b01010000, "High Alternating"}, {1000, 0b00000000, "Off"}};
ShowStep scene016Steps[] = {{2000, 0b10100000, "High Skip"}, {1000, 0b00000000, "Off"}};
ShowStep scene017Steps[] = {{1500, 0b00000001, "Flash 1"}, {500, 0b00000010, "Flash 2"}, {1000, 0b00000000, "Off"}};
ShowStep scene018Steps[] = {{1500, 0b00000100, "Flash 3"}, {500, 0b00001000, "Flash 4"}, {1000, 0b00000000, "Off"}};
ShowStep scene019Steps[] = {{1500, 0b00010000, "Flash 5"}, {500, 0b00100000, "Flash 6"}, {1000, 0b00000000, "Off"}};
ShowStep scene020Steps[] = {{1500, 0b01000000, "Flash 7"}, {500, 0b10000000, "Flash 8"}, {1000, 0b00000000, "Off"}};
ShowStep scene021Steps[] = {{2500, 0b00001111, "Low Four"}, {1000, 0b00000000, "Off"}};
ShowStep scene022Steps[] = {{2500, 0b11110000, "High Four"}, {1000, 0b00000000, "Off"}};
ShowStep scene023Steps[] = {{1000, 0b01010101, "Chess 1"}, {1000, 0b10101010, "Chess 2"}, {1000, 0b00000000, "Off"}};
ShowStep scene024Steps[] = {{3000, 0b11001100, "Center Pairs"}, {1000, 0b00000000, "Off"}};
ShowStep scene025Steps[] = {{3000, 0b00110011, "Edge Pairs"}, {1000, 0b00000000, "Off"}};
ShowStep scene026Steps[] = {{2500, 0b10011001, "Diagonal 1"}, {1000, 0b00000000, "Off"}};
ShowStep scene027Steps[] = {{2500, 0b01100110, "Diagonal 2"}, {1000, 0b00000000, "Off"}};
ShowStep scene028Steps[] = {{3000, 0b00011000, "Center Only"}, {1000, 0b00000000, "Off"}};
ShowStep scene029Steps[] = {{3000, 0b11000011, "Edges Only"}, {1000, 0b00000000, "Off"}};
ShowStep scene030Steps[] = {{3500, 0b00111100, "Inner Block"}, {1000, 0b00000000, "Off"}};
ShowStep scene031Steps[] = {{3500, 0b11000011, "Outer Block"}, {1000, 0b00000000, "Off"}};
ShowStep scene032Steps[] = {{4000, 0b01111110, "Almost All"}, {1000, 0b00000000, "Off"}};
ShowStep scene033Steps[] = {{4000, 0b10000001, "Bookends"}, {1000, 0b00000000, "Off"}};
ShowStep scene034Steps[] = {{5000, 0b11111110, "All But Last"}, {1000, 0b00000000, "Off"}};
ShowStep scene035Steps[] = {{5000, 0b01111111, "All But First"}, {1000, 0b00000000, "Off"}};
Show showLibrary[] = {
{"Scene 001", scene001Steps, 2, 0}, {"Scene 002", scene002Steps, 2, 0},
{"Scene 003", scene003Steps, 2, 0}, {"Scene 004", scene004Steps, 2, 0},
{"Scene 005", scene005Steps, 2, 0}, {"Scene 006", scene006Steps, 2, 0},
{"Scene 007", scene007Steps, 2, 0}, {"Scene 008", scene008Steps, 2, 0},
{"Scene 009", scene009Steps, 2, 0}, {"Scene 010", scene010Steps, 2, 0},
{"Scene 011", scene011Steps, 2, 0}, {"Scene 012", scene012Steps, 2, 0},
{"Scene 013", scene013Steps, 2, 0}, {"Scene 014", scene014Steps, 2, 0},
{"Scene 015", scene015Steps, 2, 0}, {"Scene 016", scene016Steps, 2, 0},
{"Scene 017", scene017Steps, 3, 0}, {"Scene 018", scene018Steps, 3, 0},
{"Scene 019", scene019Steps, 3, 0}, {"Scene 020", scene020Steps, 3, 0},
{"Scene 021", scene021Steps, 2, 0}, {"Scene 022", scene022Steps, 2, 0},
{"Scene 023", scene023Steps, 3, 0}, {"Scene 024", scene024Steps, 2, 0},
{"Scene 025", scene025Steps, 2, 0}, {"Scene 026", scene026Steps, 2, 0},
{"Scene 027", scene027Steps, 2, 0}, {"Scene 028", scene028Steps, 2, 0},
{"Scene 029", scene029Steps, 2, 0}, {"Scene 030", scene030Steps, 2, 0},
{"Scene 031", scene031Steps, 2, 0}, {"Scene 032", scene032Steps, 2, 0},
{"Scene 033", scene033Steps, 2, 0}, {"Scene 034", scene034Steps, 2, 0},
{"Scene 035", scene035Steps, 2, 0},
};
#define SHOW_COUNT (sizeof(showLibrary)/sizeof(Show))
// OLED Display
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);
bool oledAvailable = false;
// ==================== MULTIPLEXER FUNCTIONS ====================
int readMux(int muxNumber, int channel) {
int controlPin[] = {s0, s1, s2, s3};
int muxChannel[16][4] = {
{0,0,0,0}, {1,0,0,0}, {0,1,0,0}, {1,1,0,0},
{0,0,1,0}, {1,0,1,0}, {0,1,1,0}, {1,1,1,0},
{0,0,0,1}, {1,0,0,1}, {0,1,0,1}, {1,1,0,1},
{0,0,1,1}, {1,0,1,1}, {0,1,1,1}, {1,1,1,1}
};
// Set channel
for(int i = 0; i < 4; i++) {
digitalWrite(controlPin[i], muxChannel[channel][i]);
}
delayMicroseconds(50);
// Read from appropriate signal pin
int signalPin = (muxNumber == 1) ? mux1SignalPin : mux2SignalPin;
return analogRead(signalPin);
}
bool isButtonPressed(int buttonNum) {
int muxNumber = (buttonNum < 16) ? 1 : 2;
int channel = buttonNum % 16;
return (readMux(muxNumber, channel) > BUTTON_THRESHOLD);
}
void readButtons() {
for(int i = 0; i < 32; i++) {
sysState.lastButtonStates[i] = sysState.buttonStates[i];
sysState.buttonStates[i] = isButtonPressed(i);
}
}
// ==================== SHOW CONTROL ====================
void triggerShow(int idx) {
if(idx >= SHOW_COUNT) return;
if(sysState.showRunning) stopShow();
sysState.currentShow = idx;
sysState.showRunning = true;
sysState.showStartTime = millis();
sysState.showsTriggered++;
executeShowStep(0);
Serial.printf("Show started: %s\n", showLibrary[idx].name);
}
void updateShow() {
static int currentStep = 0;
static uint32_t stepStart = 0;
Show* sh = &showLibrary[sysState.currentShow];
if(stepStart == 0) stepStart = millis();
if(millis() - stepStart >= sh->steps[currentStep].duration) {
currentStep++;
if(currentStep < sh->stepCount) {
executeShowStep(currentStep);
stepStart = millis();
} else {
stopShow();
currentStep = 0;
stepStart = 0;
}
}
}
void executeShowStep(int stepIndex) {
Show* sh = &showLibrary[sysState.currentShow];
ShowStep* step = &sh->steps[stepIndex];
for(int i = 0; i < 8; i++) {
bool on = (step->relayMask >> i) & 1;
setRelay(i, on);
}
Serial.printf("Step %d: %s\n", stepIndex, step->action);
}
void stopShow() {
for(int i = 0; i < 8; i++) setRelay(i, false);
sysState.showRunning = false;
sysState.currentShow = -1;
Serial.println("Show stopped.");
}
// ==================== RELAY CONTROL ====================
void setRelay(int idx, bool state) {
if(idx >= 0 && idx < 8) {
digitalWrite(relayPins[idx], state ? HIGH : LOW);
sysState.relayStates[idx] = state;
if(state) sysState.relayOnTime[idx] = millis();
}
}
void toggleRelay(int relayIndex) {
bool newState = !sysState.relayStates[relayIndex];
setRelay(relayIndex, newState);
Serial.printf("Manual relay %d: %s\n", relayIndex + 1, newState ? "ON" : "OFF");
}
// ==================== SCENE SELECTION ====================
void handleSceneEntry(int digit) {
if(sysState.digitIndex < 3) {
sysState.sceneDigits[sysState.digitIndex] = digit;
sysState.digitIndex++;
sysState.lastDigitTime = millis();
if(sysState.digitIndex == 3) {
sysState.sceneReady = true;
Serial.printf("Scene %03d ready - press START\n", getSceneNumber());
}
}
}
void executeSceneSelection() {
if(sysState.sceneReady) {
int sceneNum = getSceneNumber();
if(sceneNum >= 1 && sceneNum <= 35) {
triggerShow(sceneNum - 1);
resetSceneEntry();
} else {
Serial.printf("Invalid scene: %03d\n", sceneNum);
resetSceneEntry();
}
}
}
void resetSceneEntry() {
sysState.digitIndex = 0;
sysState.sceneReady = false;
memset(sysState.sceneDigits, 0, sizeof(sysState.sceneDigits));
Serial.println("Scene entry reset");
}
int getSceneNumber() {
return (sysState.sceneDigits[0] * 100) + (sysState.sceneDigits[1] * 10) + sysState.sceneDigits[2];
}
// ==================== BUTTON HANDLING ====================
void handleButtonPresses() {
uint32_t currentTime = millis();
for(int i = 0; i < 32; i++) {
if(sysState.buttonStates[i] && !sysState.lastButtonStates[i]) {
if(currentTime - sysState.lastButtonPress[i] > BUTTON_DEBOUNCE_MS) {
sysState.lastButtonPress[i] = currentTime;
sysState.lastActivity = currentTime;
Serial.printf("Button %d pressed\n", i);
// Scene digit entry (buttons 0-9)
if(i >= 0 && i <= 9) {
handleSceneEntry(i);
}
// Control buttons
if(i == 15) { // EMERGENCY STOP
Serial.println("=== EMERGENCY STOP ACTIVATED ===");
sysState.emergencyActive = true;
stopShow(); // Stop any running show immediately
for(int j = 0; j < 8; j++) setRelay(j, false); // Turn off all relays
}
else if(i == 16) { // START/ACCEPT
if(sysState.emergencyActive) {
sysState.emergencyActive = false;
digitalWrite(STATUS_LED_PIN, LOW);
Serial.println("Emergency cleared via START button");
} else {
executeSceneSelection();
}
}
else if(i == 17) { // RESET
if(sysState.emergencyActive) {
sysState.emergencyActive = false;
digitalWrite(STATUS_LED_PIN, LOW);
Serial.println("Emergency cleared via RESET button");
} else {
resetSceneEntry();
}
}
// Relay toggles
else if(i >= 18 && i <= 25) {
int relayIdx = i - 18; // Button 18 = Relay 0, etc.
if(relayIdx < 8 && !sysState.showRunning) {
toggleRelay(relayIdx);
}
}
}
}
}
// Auto-reset scene entry
if(sysState.digitIndex > 0 && (currentTime - sysState.lastDigitTime) > 5000) {
resetSceneEntry();
}
}
// ==================== EMERGENCY SYSTEM ====================
void handleEmergencyMode() {
if(sysState.emergencyActive) {
// Flash status LED to indicate emergency state
static uint32_t lastFlash = 0;
if(millis() - lastFlash > 500) {
digitalWrite(STATUS_LED_PIN, !digitalRead(STATUS_LED_PIN));
lastFlash = millis();
}
// Ensure all relays stay off during emergency
for(int i = 0; i < 8; i++) {
setRelay(i, false);
}
}
}
// ==================== DISPLAY SYSTEMS ====================
void updateDisplay() {
if(sysState.emergencyActive) {
Serial.println("[DISPLAY] ⚠️ EMERGENCY STOP ACTIVE - Press START/RESET to clear");
} else if(sysState.showRunning) {
Serial.printf("[DISPLAY] Running: %s\n", showLibrary[sysState.currentShow].name);
} else if(sysState.sceneReady) {
Serial.printf("[DISPLAY] Scene %03d ready\n", getSceneNumber());
} else if(sysState.digitIndex > 0) {
Serial.printf("[DISPLAY] Entering: ");
for(int i = 0; i < sysState.digitIndex; i++) {
Serial.printf("%d", sysState.sceneDigits[i]);
}
Serial.println("");
} else {
Serial.printf("[DISPLAY] Ready | Shows: %d\n", sysState.showsTriggered);
}
}
void initOLED() {
Serial.println("Initializing OLED...");
Wire.begin(OLED_SDA, OLED_SCL);
if(display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) {
oledAvailable = true;
display.clearDisplay();
display.setTextSize(1);
display.setTextColor(SSD1306_WHITE);
display.setCursor(0, 0);
display.println(F("SHOWDUINO v2.5"));
display.println(F("Professional"));
display.println(F("Show Controller"));
display.println(F(""));
display.println(F("35 Scenes Ready"));
display.display();
Serial.println("OLED initialized successfully!");
} else {
oledAvailable = false;
Serial.println("OLED not found, continuing without display");
}
}
void updateOLEDDisplay() {
if(!oledAvailable) return;
display.clearDisplay();
display.setCursor(0, 0);
display.setTextSize(1);
display.println(F("SHOWDUINO v2.5"));
display.drawLine(0, 10, 127, 10, SSD1306_WHITE);
if(sysState.emergencyActive) {
display.setTextSize(2);
display.setCursor(10, 20);
display.println(F("EMERGENCY"));
display.setCursor(30, 40);
display.println(F("STOP"));
}
else if(sysState.showRunning) {
display.setCursor(0, 15);
display.println(F("RUNNING:"));
display.setCursor(0, 25);
display.println(showLibrary[sysState.currentShow].name);
display.setCursor(0, 40);
display.print(F("Relays: "));
for(int i = 0; i < 8; i++) {
display.print(sysState.relayStates[i] ? "1" : "0");
}
}
else if(sysState.sceneReady) {
display.setCursor(0, 15);
display.println(F("SCENE READY:"));
display.setTextSize(3);
display.setCursor(25, 28);
display.printf("%03d", getSceneNumber());
display.setTextSize(1);
display.setCursor(10, 52);
display.println(F("Press START"));
}
else if(sysState.digitIndex > 0) {
display.setCursor(0, 15);
display.println(F("Enter Scene:"));
display.setTextSize(3);
display.setCursor(15, 30);
for(int i = 0; i < sysState.digitIndex; i++) {
display.printf("%d", sysState.sceneDigits[i]);
}
for(int i = sysState.digitIndex; i < 3; i++) {
display.print("_");
}
}
else {
display.setCursor(0, 15);
display.println(F("READY"));
display.setCursor(0, 30);
display.printf("Shows Run: %d", sysState.showsTriggered);
display.setCursor(0, 40);
display.println(F("Enter 3-digit scene"));
display.setCursor(0, 50);
display.println(F("001-035"));
}
display.display();
}
// ==================== UTILITIES ====================
void calculateShowDurations() {
for(int i = 0; i < SHOW_COUNT; i++) {
uint16_t total = 0;
for(int j = 0; j < showLibrary[i].stepCount; j++) {
total += showLibrary[i].steps[j].duration;
}
showLibrary[i].totalDuration = total;
}
}
void loadSettings() {
EEPROM.get(EEPROM_SETTINGS_ADDR, settings);
if(settings.magic != 0xABCD) {
settings = ShowSettings();
EEPROM.put(EEPROM_SETTINGS_ADDR, settings);
EEPROM.commit();
}
}
void performSafetyChecks() {
if(settings.autoShutoff && (millis() - sysState.lastActivity) > AUTO_SHUTOFF_MS) {
stopShow();
sysState.lastActivity = millis();
Serial.println("Auto-shutdown");
}
}
// ==================== SETUP ====================
void setup() {
Serial.begin(115200);
delay(2000);
Serial.println("=== SHOWDUINO ESP32-S3 PROFESSIONAL v2.5 ===");
Serial.println("Complete system with OLED, 35 scenes, emergency stop");
// Initialize EEPROM
EEPROM.begin(EEPROM_SIZE);
loadSettings();
// Setup relay pins
Serial.println("Setting up relays...");
for(int i = 0; i < 8; i++) {
pinMode(relayPins[i], OUTPUT);
digitalWrite(relayPins[i], LOW);
}
// Setup MUX control pins
Serial.println("Setting up multiplexer...");
pinMode(s0, OUTPUT);
pinMode(s1, OUTPUT);
pinMode(s2, OUTPUT);
pinMode(s3, OUTPUT);
digitalWrite(s0, LOW);
digitalWrite(s1, LOW);
digitalWrite(s2, LOW);
digitalWrite(s3, LOW);
// Setup system pins
pinMode(STATUS_LED_PIN, OUTPUT);
// Emergency stop now handled via MUX button 15
// Initialize OLED
initOLED();
// Calculate show durations
calculateShowDurations();
sysState.lastActivity = millis();
Serial.println("Configuration:");
Serial.printf("Control: s0=%d, s1=%d, s2=%d, s3=%d\n", s0, s1, s2, s3);
Serial.printf("Signals: MUX1=%d, MUX2=%d\n", mux1SignalPin, mux2SignalPin);
Serial.printf("Relays: 6,7,15,16,17,18,8,3\n");
Serial.printf("OLED: %s\n", oledAvailable ? "Available" : "Not found");
Serial.println("");
Serial.println("=== 35 SCENE PROFESSIONAL SHOW CONTROLLER READY ===");
Serial.println("Button Map:");
Serial.println(" 0-9: Scene digits | 15: EMERGENCY STOP | 16: START | 17: RESET");
Serial.println(" 18-25: Relay toggles | Emergency cleared via START/RESET");
Serial.println("");
}
// ==================== MAIN LOOP ====================
void loop() {
// Emergency handling
handleEmergencyMode();
if(sysState.emergencyActive) {
updateDisplay();
if(oledAvailable) updateOLEDDisplay();
delay(100);
return;
}
// Button processing
readButtons();
handleButtonPresses();
// Show processing
if(sysState.showRunning) updateShow();
// Display updates
static uint32_t lastDisp = 0;
if(millis() - lastDisp > DISPLAY_UPDATE_MS) {
updateDisplay();
if(oledAvailable) updateOLEDDisplay();
lastDisp = millis();
}
// Safety checks
performSafetyChecks();
delay(10);
}