/**
* Showduino ESP32-S3 Professional Show Controller — v2.3 (Wokwi Edition)
* Wokwi-compatible build for ESP32-S3 WROOM-1
*
* Notes:
* - TFT_eSPI not rendered in Wokwi, so display output is mirrored to Serial.
* - Relays are represented as LEDs in Wokwi diagram.
* - Multiplexer inputs simulated with Wokwi pushbuttons.
*/
#define USE_ANALOG_BUTTONS 0
#define BUTTON_ANALOG_THRESHOLD 500
#include <Arduino.h>
#include <EEPROM.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
// ==================== PIN MAP ====================
// Multiplexer address pins
#define MUX_S0 12
#define MUX_S1 13
#define MUX_S2 14
#define MUX_S3 15
#define MUX1_EN 16
#define MUX2_EN 17
#define MUX1_SIG 18
#define MUX2_SIG 19
// Button NeoPixels (simulated with Serial in Wokwi)
#define BUTTON_LED_PIN 8
#define BUTTON_LED_COUNT 25
// Relay outputs (LEDs in Wokwi)
#define RELAY_1_PIN 4
#define RELAY_2_PIN 5
#define RELAY_3_PIN 6
#define RELAY_4_PIN 7
#define RELAY_5_PIN 9
#define RELAY_6_PIN 10
#define RELAY_7_PIN 45
#define RELAY_8_PIN 46
// System
#define EMERGENCY_PIN 38 // pushbutton in Wokwi
#define STATUS_LED_PIN 40 // onboard/status LED
// OLED Display (for testing)
#define OLED_SDA 20 // I2C Data
#define OLED_SCL 21 // I2C Clock
#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64
#define OLED_RESET -1 // Reset pin (or -1 if not used)
// ==================== SYSTEM CONFIG ====================
#define MAX_SHOW_DURATION_MS 300000
#define EEPROM_SIZE 512
#define EEPROM_SETTINGS_ADDR 0
#define BUTTON_DEBOUNCE_MS 50
#define DISPLAY_UPDATE_MS 250
#define AUTO_SHUTOFF_MS 600000
// ==================== 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;
bool buttonStates[32] = {false};
bool lastButtonStates[32] = {false};
uint32_t lastButtonPress[32] = {0};
// Scene selection system
char sceneDigits[4] = {0, 0, 0, 0}; // 3 digits + null terminator
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;
struct ShowStep { uint16_t duration; uint8_t relayMask; const char* action; };
struct Show {
const char* name;
ShowStep* steps;
uint8_t stepCount;
uint16_t totalDuration; // Added missing field
};
// ==================== SHOW LIBRARY ====================
// Create 35 different scenes
ShowStep scene001Steps[] = {{3000, 0b00000001, "Scene 001"}, {1000, 0b00000000, "Off"}};
ShowStep scene002Steps[] = {{3000, 0b00000010, "Scene 002"}, {1000, 0b00000000, "Off"}};
ShowStep scene003Steps[] = {{3000, 0b00000100, "Scene 003"}, {1000, 0b00000000, "Off"}};
ShowStep scene004Steps[] = {{3000, 0b00001000, "Scene 004"}, {1000, 0b00000000, "Off"}};
ShowStep scene005Steps[] = {{3000, 0b00010000, "Scene 005"}, {1000, 0b00000000, "Off"}};
ShowStep scene006Steps[] = {{3000, 0b00100000, "Scene 006"}, {1000, 0b00000000, "Off"}};
ShowStep scene007Steps[] = {{3000, 0b01000000, "Scene 007"}, {1000, 0b00000000, "Off"}};
ShowStep scene008Steps[] = {{3000, 0b10000000, "Scene 008"}, {1000, 0b00000000, "Off"}};
ShowStep scene009Steps[] = {{3000, 0b11111111, "Scene 009 All"}, {1000, 0b00000000, "Off"}};
ShowStep scene010Steps[] = {{2000, 0b00000011, "Scene 010"}, {1000, 0b00000000, "Off"}};
ShowStep scene011Steps[] = {{2000, 0b00000101, "Scene 011"}, {1000, 0b00000000, "Off"}};
ShowStep scene012Steps[] = {{2000, 0b00001010, "Scene 012"}, {1000, 0b00000000, "Off"}};
ShowStep scene013Steps[] = {{2000, 0b00010100, "Scene 013"}, {1000, 0b00000000, "Off"}};
ShowStep scene014Steps[] = {{2000, 0b00101000, "Scene 014"}, {1000, 0b00000000, "Off"}};
ShowStep scene015Steps[] = {{2000, 0b01010000, "Scene 015"}, {1000, 0b00000000, "Off"}};
ShowStep scene016Steps[] = {{2000, 0b10100000, "Scene 016"}, {1000, 0b00000000, "Off"}};
ShowStep scene017Steps[] = {{1500, 0b00000001, "Scene 017"}, {500, 0b00000010, "Flash"}, {1000, 0b00000000, "Off"}};
ShowStep scene018Steps[] = {{1500, 0b00000100, "Scene 018"}, {500, 0b00001000, "Flash"}, {1000, 0b00000000, "Off"}};
ShowStep scene019Steps[] = {{1500, 0b00010000, "Scene 019"}, {500, 0b00100000, "Flash"}, {1000, 0b00000000, "Off"}};
ShowStep scene020Steps[] = {{1500, 0b01000000, "Scene 020"}, {500, 0b10000000, "Flash"}, {1000, 0b00000000, "Off"}};
ShowStep scene021Steps[] = {{2000, 0b00001111, "Scene 021 Low"}, {1000, 0b00000000, "Off"}};
ShowStep scene022Steps[] = {{2000, 0b11110000, "Scene 022 High"}, {1000, 0b00000000, "Off"}};
ShowStep scene023Steps[] = {{1000, 0b01010101, "Scene 023 Alt1"}, {1000, 0b10101010, "Alt2"}, {1000, 0b00000000, "Off"}};
ShowStep scene024Steps[] = {{4000, 0b11001100, "Scene 024"}, {1000, 0b00000000, "Off"}};
ShowStep scene025Steps[] = {{4000, 0b00110011, "Scene 025"}, {1000, 0b00000000, "Off"}};
ShowStep scene026Steps[] = {{3000, 0b10011001, "Scene 026"}, {1000, 0b00000000, "Off"}};
ShowStep scene027Steps[] = {{3000, 0b01100110, "Scene 027"}, {1000, 0b00000000, "Off"}};
ShowStep scene028Steps[] = {{2500, 0b00011000, "Scene 028"}, {1000, 0b00000000, "Off"}};
ShowStep scene029Steps[] = {{2500, 0b11000011, "Scene 029"}, {1000, 0b00000000, "Off"}};
ShowStep scene030Steps[] = {{3500, 0b00111100, "Scene 030"}, {1000, 0b00000000, "Off"}};
ShowStep scene031Steps[] = {{3500, 0b11000011, "Scene 031"}, {1000, 0b00000000, "Off"}};
ShowStep scene032Steps[] = {{4000, 0b01111110, "Scene 032"}, {1000, 0b00000000, "Off"}};
ShowStep scene033Steps[] = {{4000, 0b10000001, "Scene 033"}, {1000, 0b00000000, "Off"}};
ShowStep scene034Steps[] = {{5000, 0b11111110, "Scene 034"}, {1000, 0b00000000, "Off"}};
ShowStep scene035Steps[] = {{5000, 0b01111111, "Scene 035 Final"}, {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 object
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);
bool oledAvailable = false;
// ==================== PROTOTYPES ====================
void initializeHardware();
void updateDisplay();
void triggerShow(int showIndex);
void updateShow();
void executeShowStep(int stepIndex);
void stopShow();
void setRelay(int index, bool state);
void IRAM_ATTR emergencyISR();
void handleEmergencyMode();
void performSafetyChecks();
void calculateShowDurations();
void loadSettings();
void readButtons();
void handleButtonPresses();
bool readMuxInput(int muxNumber, int channel);
void setMuxAddress(int channel);
void handleSceneEntry(int digit);
void executeSceneSelection();
void resetSceneEntry();
int getSceneNumber();
void toggleRelay(int relayIndex);
void initOLED();
void updateOLEDDisplay();
// ==================== SETUP ====================
void setup() {
Serial.begin(115200);
delay(1000); // Give serial time to initialize
Serial.println("=== SHOWDUINO v2.3 Wokwi Edition ===");
Serial.println("Starting initialization...");
EEPROM.begin(EEPROM_SIZE);
loadSettings();
Serial.println("Settings loaded");
initializeHardware();
Serial.println("Hardware initialized");
initOLED();
Serial.println("OLED initialization complete");
calculateShowDurations();
sysState.lastActivity = millis();
Serial.println("35 Scene Controller Ready");
updateDisplay();
if (oledAvailable) updateOLEDDisplay();
}
// ==================== LOOP ====================
void loop() {
handleEmergencyMode();
if (sysState.emergencyActive) { updateDisplay(); if (oledAvailable) updateOLEDDisplay(); delay(100); return; }
readButtons();
handleButtonPresses();
if (sysState.showRunning) updateShow();
static uint32_t lastDisp = 0;
if (millis() - lastDisp > DISPLAY_UPDATE_MS) {
updateDisplay();
if (oledAvailable) updateOLEDDisplay();
lastDisp = millis();
}
performSafetyChecks();
delay(10);
}
// ==================== HARDWARE ====================
void initializeHardware() {
Serial.println("Initializing relay pins...");
int relayPins[8] = {RELAY_1_PIN, RELAY_2_PIN, RELAY_3_PIN, RELAY_4_PIN,
RELAY_5_PIN, RELAY_6_PIN, RELAY_7_PIN, RELAY_8_PIN};
for (int i=0;i<8;i++) {
pinMode(relayPins[i], OUTPUT);
digitalWrite(relayPins[i], LOW);
}
// Initialize I2C for OLED
Serial.println("Initializing I2C...");
Wire.begin(OLED_SDA, OLED_SCL);
// Initialize multiplexer pins
Serial.println("Initializing multiplexer pins...");
pinMode(MUX_S0, OUTPUT);
pinMode(MUX_S1, OUTPUT);
pinMode(MUX_S2, OUTPUT);
pinMode(MUX_S3, OUTPUT);
pinMode(MUX1_EN, OUTPUT);
pinMode(MUX2_EN, OUTPUT);
pinMode(MUX1_SIG, INPUT_PULLUP);
pinMode(MUX2_SIG, INPUT_PULLUP);
// Enable both multiplexers (active low)
digitalWrite(MUX1_EN, LOW); // Enable MUX1
digitalWrite(MUX2_EN, LOW); // Enable MUX2
Serial.printf("MUX1_EN pin %d set to: %d\n", MUX1_EN, digitalRead(MUX1_EN));
Serial.printf("MUX2_EN pin %d set to: %d\n", MUX2_EN, digitalRead(MUX2_EN));
pinMode(EMERGENCY_PIN, INPUT_PULLUP);
pinMode(STATUS_LED_PIN, OUTPUT);
attachInterrupt(digitalPinToInterrupt(EMERGENCY_PIN), emergencyISR, FALLING);
Serial.println("Hardware initialization complete");
}
// ==================== 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.");
}
// ==================== RELAYS ====================
void setRelay(int idx,bool state){
int relayPins[8]={RELAY_1_PIN,RELAY_2_PIN,RELAY_3_PIN,RELAY_4_PIN,
RELAY_5_PIN,RELAY_6_PIN,RELAY_7_PIN,RELAY_8_PIN};
digitalWrite(relayPins[idx],state?HIGH:LOW);
sysState.relayStates[idx]=state;
}
void toggleRelay(int relayIndex) {
bool newState = !sysState.relayStates[relayIndex];
setRelay(relayIndex, newState);
Serial.printf("Manual relay %d: %s\n", relayIndex + 1, newState ? "ON" : "OFF");
}
// ==================== EMERGENCY ====================
void IRAM_ATTR emergencyISR(){ sysState.emergencyActive=true; }
void handleEmergencyMode(){
if(sysState.emergencyActive){
for(int i=0;i<8;i++) setRelay(i,false);
digitalWrite(STATUS_LED_PIN, !digitalRead(STATUS_LED_PIN));
}
}
// ==================== SAFETY ====================
void performSafetyChecks(){
if(settings.autoShutoff && (millis()-sysState.lastActivity)>AUTO_SHUTOFF_MS){
stopShow();
sysState.lastActivity=millis();
Serial.println("Auto-shutdown");
}
}
// ==================== DISPLAY ====================
void updateDisplay(){
if(sysState.emergencyActive){
Serial.println("[DISPLAY] EMERGENCY STOP - Press START or 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 - press START\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]);
}
for(int i = sysState.digitIndex; i < 3; i++) {
Serial.printf("_");
}
Serial.println("");
} else {
Serial.printf("[DISPLAY] Ready | Shows run: %d\n", sysState.showsTriggered);
}
}
// ==================== UTILS ====================
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();
}
}
// ==================== BUTTON HANDLING ====================
void setMuxAddress(int channel) {
digitalWrite(MUX_S0, (channel & 0x01) ? HIGH : LOW);
digitalWrite(MUX_S1, (channel & 0x02) ? HIGH : LOW);
digitalWrite(MUX_S2, (channel & 0x04) ? HIGH : LOW);
digitalWrite(MUX_S3, (channel & 0x08) ? HIGH : LOW);
delayMicroseconds(10); // Allow mux to settle
}
bool readMuxInput(int muxNumber, int channel) {
setMuxAddress(channel);
int pinToRead = (muxNumber == 1) ? MUX1_SIG : MUX2_SIG;
bool rawValue = digitalRead(pinToRead);
bool buttonPressed = !rawValue; // Inverted because buttons pull to ground
// Debug specific channels occasionally
static uint32_t lastChannelDebug = 0;
if (millis() - lastChannelDebug > 5000 && channel == 0) {
Serial.printf("MUX%d Ch%d: raw=%d, processed=%d\n",
muxNumber, channel, rawValue, buttonPressed);
lastChannelDebug = millis();
}
return buttonPressed;
}
void readButtons() {
// Save previous button states
for (int i = 0; i < 32; i++) {
sysState.lastButtonStates[i] = sysState.buttonStates[i];
}
// Read MUX1 (channels 0-15, buttons 0-15)
for (int i = 0; i < 16; i++) {
sysState.buttonStates[i] = readMuxInput(1, i);
}
// Read MUX2 (channels 0-15, buttons 16-31)
for (int i = 0; i < 16; i++) {
sysState.buttonStates[i + 16] = readMuxInput(2, i);
}
// Debug: Print button states periodically
static uint32_t lastDebug = 0;
if (millis() - lastDebug > 2000) { // Every 2 seconds
Serial.println("=== BUTTON DEBUG ===");
Serial.printf("MUX1_SIG pin %d: %d\n", MUX1_SIG, digitalRead(MUX1_SIG));
Serial.printf("MUX2_SIG pin %d: %d\n", MUX2_SIG, digitalRead(MUX2_SIG));
// Test a few channels
Serial.printf("MUX1 Ch0: %d, Ch1: %d, Ch2: %d\n",
readMuxInput(1, 0), readMuxInput(1, 1), readMuxInput(1, 2));
Serial.printf("MUX2 Ch0: %d, Ch1: %d, Ch2: %d\n",
readMuxInput(2, 0), readMuxInput(2, 1), readMuxInput(2, 2));
// Show any active buttons
bool anyPressed = false;
for (int i = 0; i < 32; i++) {
if (sysState.buttonStates[i]) {
Serial.printf("Button %d is pressed\n", i);
anyPressed = true;
}
}
if (!anyPressed) Serial.println("No buttons currently pressed");
lastDebug = millis();
}
}
void handleButtonPresses() {
uint32_t currentTime = millis();
for (int i = 0; i < 32; i++) {
// Check for button press (rising edge with debounce)
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);
// MUX1 - Scene digit entry (buttons 0-9)
if (i >= 0 && i <= 9) {
handleSceneEntry(i);
}
// MUX2 - Control buttons
else if (i == 16) { // Button 0 on MUX2 = START/ACCEPT
if (sysState.emergencyActive) {
// Emergency reset function
sysState.emergencyActive = false;
digitalWrite(STATUS_LED_PIN, LOW);
Serial.println("Emergency reset via START button");
} else {
executeSceneSelection();
}
}
else if (i == 17) { // Button 1 on MUX2 = RESET
if (sysState.emergencyActive) {
// Emergency reset function
sysState.emergencyActive = false;
digitalWrite(STATUS_LED_PIN, LOW);
Serial.println("Emergency reset via RESET button");
} else {
resetSceneEntry();
}
}
else if (i == 18) { // Button 2 on MUX2 = Relay 1
if (!sysState.showRunning) toggleRelay(0);
}
else if (i == 19) { // Button 3 on MUX2 = Relay 2
if (!sysState.showRunning) toggleRelay(1);
}
else if (i == 20) { // Button 4 on MUX2 = Relay 3
if (!sysState.showRunning) toggleRelay(2);
}
else if (i == 21) { // Button 5 on MUX2 = Relay 4
if (!sysState.showRunning) toggleRelay(3);
}
else if (i == 22) { // Button 6 on MUX2 = Relay 5
if (!sysState.showRunning) toggleRelay(4);
}
else if (i == 23) { // Button 7 on MUX2 = Relay 6
if (!sysState.showRunning) toggleRelay(5);
}
else if (i == 24) { // Button 8 on MUX2 = Relay 7
if (!sysState.showRunning) toggleRelay(6);
}
else if (i == 29) { // Button 13 on MUX2 = Relay 8
if (!sysState.showRunning) toggleRelay(7);
}
// Buttons 25,26,27,28,30,31 (MUX2 buttons 9,10,11,12,14,15) remain unused
}
}
}
// Auto-reset scene entry after 5 seconds of inactivity
if (sysState.digitIndex > 0 && (currentTime - sysState.lastDigitTime) > 5000) {
resetSceneEntry();
}
}
void handleSceneEntry(int digit) {
if (sysState.digitIndex < 3) {
sysState.sceneDigits[sysState.digitIndex] = digit;
sysState.digitIndex++;
sysState.lastDigitTime = millis();
Serial.printf("Scene entry: ");
for (int i = 0; i < sysState.digitIndex; i++) {
Serial.printf("%d", sysState.sceneDigits[i]);
}
Serial.println("");
if (sysState.digitIndex == 3) {
sysState.sceneReady = true;
Serial.printf("Scene %03d ready - press START to execute\n", getSceneNumber());
}
}
}
void executeSceneSelection() {
if (sysState.sceneReady) {
int sceneNum = getSceneNumber();
if (sceneNum >= 1 && sceneNum <= 35) {
triggerShow(sceneNum - 1); // Convert to 0-based index
resetSceneEntry();
} else {
Serial.printf("Invalid scene number: %03d\n", sceneNum);
resetSceneEntry();
}
} else {
Serial.println("No scene selected - enter 3 digits first");
}
}
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];
}
// ==================== OLED DISPLAY ====================
void initOLED() {
Serial.println("Attempting OLED initialization on pins 20/21...");
// Set a timeout for OLED initialization
unsigned long startTime = millis();
bool initSuccess = false;
// Try to initialize with a 2-second timeout
while (millis() - startTime < 2000) {
if(display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) {
initSuccess = true;
break;
}
delay(100);
}
if (!initSuccess) {
Serial.println("OLED timeout - continuing without display");
oledAvailable = false;
return;
}
Serial.println("OLED initialized successfully!");
oledAvailable = true;
display.clearDisplay();
display.setTextSize(1);
display.setTextColor(SSD1306_WHITE);
display.setCursor(0, 0);
display.println(F("SHOWDUINO v2.3"));
display.println(F("35-Scene Controller"));
display.println(F(""));
display.println(F("Initializing..."));
display.display();
}
void updateOLEDDisplay() {
if (!oledAvailable) return; // Skip if OLED not available
display.clearDisplay();
display.setCursor(0, 0);
display.setTextSize(1);
// Title
display.println(F("SHOWDUINO v2.3"));
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"));
display.setTextSize(1);
display.setCursor(5, 55);
display.println(F("Press START or RESET"));
}
else if (sysState.showRunning) {
display.setCursor(0, 15);
display.println(F("RUNNING:"));
display.setTextSize(1);
display.setCursor(0, 25);
display.println(showLibrary[sysState.currentShow].name);
// Show relay status
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) {
// After 3 digits entered - show scene number and wait for start
display.setCursor(0, 15);
display.setTextSize(1);
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 button"));
}
else if (sysState.digitIndex > 0) {
// Display as user types the numbers
display.setCursor(0, 15);
display.setTextSize(1);
display.println(F("Enter Scene Number:"));
display.setTextSize(3);
display.setCursor(15, 30);
// Show entered digits
for (int i = 0; i < sysState.digitIndex; i++) {
display.printf("%d", sysState.sceneDigits[i]);
}
// Show underscores for remaining digits
for (int i = sysState.digitIndex; i < 3; i++) {
display.print("_");
}
display.setTextSize(1);
display.setCursor(0, 55);
display.printf("Digit %d of 3", sysState.digitIndex + 1);
}
else {
// Default ready state
display.setCursor(0, 15);
display.setTextSize(1);
display.println(F("READY FOR INPUT"));
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("number (001-035)"));
}
display.display();
}