ALIVE LCD CAPTIVE PORTAL
Web-Configurable Server Monitor - Round GC9A01 Display
CAPTIVE PORTAL FEATURES
Web Configuration
Configure WiFi, servers, and settings via browser
WiFi Network Scan
Automatically discovers available networks
Persistent Storage
Settings saved to flash - survives reboot
Auto Config Mode
Enters setup on first boot or BOOT button hold
SETUP WORKFLOW
1
Power on ESP32
(hold BOOT for config)
(hold BOOT for config)
2
Connect to
"ALIVE-LCD-Setup" WiFi
"ALIVE-LCD-Setup" WiFi
3
Browser opens
config page
config page
4
Save settings
ESP32 reboots
ESP32 reboots
ARDUINO SKETCH
AliveLCD_Portal.ino
/* * Server Status Dashboard - "ALIVE" (LCD + Captive Portal) * ESP32-S3 Super Mini + GC9A01 1.28" Round LCD + Status LEDs * * Features: * - Web-based configuration (no hardcoded credentials) * - WiFi network scanning * - Configurable servers (up to 5), check interval, display settings * - Settings persist in flash memory * * Config Mode Triggers: * - First boot (no saved WiFi) * - Hold BOOT button during power-on * * Hardware: * - GC9A01 240x240 Round LCD (SPI): SCK=12, MOSI=11, CS=8, DC=9, RST=10, BL=7 * - Status LEDs: Blue=GPIO 5, Red=GPIO 10 */ #include <WiFi.h> #include <ESP32Ping.h> #include <Arduino_GFX_Library.h> #include <WebServer.h> #include <DNSServer.h> #include <Preferences.h> // ============== PIN DEFINITIONS ============== // GC9A01 Display (SPI) #define TFT_SCK 12 #define TFT_MOSI 11 #define TFT_CS 8 #define TFT_DC 9 #define TFT_RST 10 #define TFT_BL 7 // Status LEDs #define LED_BLUE 5 #define LED_RED 10 #define BOOT_BTN 0 // GPIO0 is BOOT button // ============== DISPLAY SETTINGS ============== #define SCREEN_WIDTH 240 #define SCREEN_HEIGHT 240 #define CX (SCREEN_WIDTH / 2) #define CY (SCREEN_HEIGHT / 2) // Colors (RGB565) #define COLOR_BG 0x0821 // Dark blue-black #define COLOR_TEXT 0xB5B6 // Light gray #define COLOR_CYAN 0x07FF // Neon Cyan #define COLOR_MAGENTA 0xF81F // Neon Magenta #define COLOR_YELLOW 0xFFE0 // Neon Yellow #define COLOR_GREEN 0x07E0 // Neon Green #define COLOR_RED 0xF800 // Red // ============== CONFIGURATION STRUCTURE ============== struct Config { char ssid[33]; char password[65]; char servers[5][64]; char serverNames[5][16]; int numServers; unsigned long checkInterval; uint8_t brightness; }; Config config; Preferences prefs; // Display setup using Arduino_GFX Arduino_DataBus *bus = new Arduino_ESP32SPI(TFT_DC, TFT_CS, TFT_SCK, TFT_MOSI, GFX_NOT_DEFINED, HSPI); Arduino_GFX *gfx = new Arduino_GC9A01(bus, TFT_RST, 0, true); WebServer server(80); DNSServer dnsServer; // ============== STATE VARIABLES ============== bool configMode = false; bool serverStatus[5]; bool allServersUp = true; unsigned long lastCheck = 0; unsigned long lastBlink = 0; unsigned long lastFlashUpdate = 0; bool redLedState = false; bool flashState = true; // ============== DEFAULT CONFIGURATION ============== void setDefaults() { memset(&config, 0, sizeof(config)); config.numServers = 3; config.checkInterval = 30000; config.brightness = 255; strcpy(config.servers[0], "8.8.8.8"); strcpy(config.serverNames[0], "Google"); strcpy(config.servers[1], "1.1.1.1"); strcpy(config.serverNames[1], "Cloudflare"); strcpy(config.servers[2], "192.168.1.1"); strcpy(config.serverNames[2], "Router"); } // ============== LOAD/SAVE CONFIG ============== void loadConfig() { prefs.begin("alivelcd", true); if (!prefs.isKey("ssid")) { prefs.end(); setDefaults(); return; } strcpy(config.ssid, prefs.getString("ssid", "").c_str()); strcpy(config.password, prefs.getString("pass", "").c_str()); config.numServers = prefs.getInt("numSrv", 3); config.checkInterval = prefs.getULong("interval", 30000); config.brightness = prefs.getUChar("bright", 255); for (int i = 0; i < 5; i++) { String srvKey = "srv" + String(i); String nameKey = "name" + String(i); strcpy(config.servers[i], prefs.getString(srvKey.c_str(), "").c_str()); strcpy(config.serverNames[i], prefs.getString(nameKey.c_str(), "").c_str()); } prefs.end(); } void saveConfig() { prefs.begin("alivelcd", false); prefs.putString("ssid", config.ssid); prefs.putString("pass", config.password); prefs.putInt("numSrv", config.numServers); prefs.putULong("interval", config.checkInterval); prefs.putUChar("bright", config.brightness); for (int i = 0; i < 5; i++) { String srvKey = "srv" + String(i); String nameKey = "name" + String(i); prefs.putString(srvKey.c_str(), config.servers[i]); prefs.putString(nameKey.c_str(), config.serverNames[i]); } prefs.end(); } // ============== DRAW HELPERS ============== void drawGlowRing(int cx, int cy, int r, uint16_t color) { gfx->drawCircle(cx, cy, r, color); gfx->drawCircle(cx, cy, r - 1, color); } void drawCenteredText(const char* text, int y, uint16_t color, uint8_t size) { gfx->setTextSize(size); gfx->setTextColor(color); int16_t x1, y1; uint16_t w, h; gfx->getTextBounds(text, 0, 0, &x1, &y1, &w, &h); gfx->setCursor(CX - w/2, y); gfx->print(text); } // ============== CONFIG PAGE HTML ============== const char* configPage = R"rawhtml( <!DOCTYPE html> <html> <head> <meta name="viewport" content="width=device-width,initial-scale=1"> <title>ALIVE LCD Setup</title> <style> *{box-sizing:border-box;margin:0;padding:0} body{font-family:'Courier New',monospace;background:#0a0a1a;color:#b0b0b0;padding:20px} h1{color:#ff00ff;text-align:center;margin-bottom:20px;text-shadow:0 0 10px #ff00ff} .card{background:#12121f;border:1px solid #333;border-radius:8px;padding:15px;margin-bottom:15px} .card h2{color:#00ffff;font-size:0.9rem;margin-bottom:10px;letter-spacing:2px} label{display:block;color:#888;font-size:0.75rem;margin-bottom:3px} input,select{width:100%;padding:8px;background:#1a1a2e;border:1px solid #00ffff;color:#fff;border-radius:4px;margin-bottom:10px;font-family:inherit} input:focus{outline:none;box-shadow:0 0 5px #00ffff} .row{display:flex;gap:10px} .row>*{flex:1} button{width:100%;padding:12px;background:#ff00ff;border:none;color:#000;font-weight:bold;cursor:pointer;border-radius:4px;font-family:inherit;letter-spacing:2px;margin-top:10px} button:hover{box-shadow:0 0 15px #ff00ff} .scan-btn{background:transparent;border:1px solid #00ffff;color:#00ffff;padding:8px;margin-bottom:10px} .scan-btn:hover{background:#00ffff;color:#000} #networks{max-height:150px;overflow-y:auto;border:1px solid #333;border-radius:4px;margin-bottom:10px} .net{padding:8px;cursor:pointer;border-bottom:1px solid #222} .net:hover{background:#1a1a2e} .net .rssi{float:right;color:#666} </style> </head> <body> <h1>[ALIVE LCD SETUP]</h1> <form action="/save" method="POST"> <div class="card"> <h2>WiFi NETWORK</h2> <button type="button" class="scan-btn" onclick="scanNetworks()">SCAN NETWORKS</button> <div id="networks"></div> <label>SSID</label> <input type="text" name="ssid" id="ssid" maxlength="32" required> <label>PASSWORD</label> <input type="password" name="pass" maxlength="64"> </div> <div class="card"> <h2>SERVERS TO MONITOR</h2> <div class="row"> <div><label>Server 1 IP/Host</label><input type="text" name="srv0" maxlength="63"></div> <div><label>Name</label><input type="text" name="name0" maxlength="15"></div> </div> <div class="row"> <div><label>Server 2 IP/Host</label><input type="text" name="srv1" maxlength="63"></div> <div><label>Name</label><input type="text" name="name1" maxlength="15"></div> </div> <div class="row"> <div><label>Server 3 IP/Host</label><input type="text" name="srv2" maxlength="63"></div> <div><label>Name</label><input type="text" name="name2" maxlength="15"></div> </div> <div class="row"> <div><label>Server 4 IP/Host</label><input type="text" name="srv3" maxlength="63"></div> <div><label>Name</label><input type="text" name="name3" maxlength="15"></div> </div> <div class="row"> <div><label>Server 5 IP/Host</label><input type="text" name="srv4" maxlength="63"></div> <div><label>Name</label><input type="text" name="name4" maxlength="15"></div> </div> </div> <div class="card"> <h2>DISPLAY SETTINGS</h2> <div class="row"> <div> <label>Check Interval (sec)</label> <input type="number" name="interval" value="30" min="10" max="300"> </div> <div> <label>Brightness (0-255)</label> <input type="number" name="bright" value="255" min="0" max="255"> </div> </div> </div> <button type="submit">SAVE & REBOOT</button> </form> <script> function scanNetworks() { var box = document.getElementById('networks'); box.innerHTML = '<div style="color:#00ff00">Scanning...</div>'; var xhr = new XMLHttpRequest(); xhr.open('GET', '/scan', true); xhr.onreadystatechange = function() { if (xhr.readyState === 4) { if (xhr.status === 200) { try { var nets = JSON.parse(xhr.responseText); if (!nets || nets.length === 0) { box.innerHTML = '<div style="color:#888">No networks found</div>'; return; } var html = ''; for (var i = 0; i < nets.length; i++) { var n = nets[i]; html += '<div class="net" onclick="document.getElementById(\'ssid\').value=\'' + n.ssid.replace(/'/g, '') + '\'">' + n.ssid + '<span class="rssi">' + n.rssi + ' dBm</span></div>'; } box.innerHTML = html; } catch (e) { box.innerHTML = '<div style="color:#f00">Parse error: ' + e.message + '</div>'; } } else { box.innerHTML = '<div style="color:#f00">HTTP error: ' + xhr.status + '</div>'; } } }; xhr.onerror = function() { box.innerHTML = '<div style="color:#f00">Network error</div>'; }; xhr.send(); } </script> </body> </html> )rawhtml"; // ============== WEB SERVER HANDLERS ============== void handleRoot() { server.send(200, "text/html", configPage); } String escapeJson(String s) { s.replace("\\", "\\\\"); s.replace("\"", "\\\""); return s; } void handleScan() { Serial.println("Scanning WiFi networks..."); int n = WiFi.scanNetworks(); Serial.printf("Found %d networks\n", n); if (n < 0) { server.send(500, "application/json", "[]"); return; } String json = "["; for (int i = 0; i < n; i++) { if (i > 0) json += ","; json += "{\"ssid\":\"" + escapeJson(WiFi.SSID(i)) + "\",\"rssi\":" + WiFi.RSSI(i) + "}"; } json += "]"; server.send(200, "application/json", json); WiFi.scanDelete(); } void handleSave() { strcpy(config.ssid, server.arg("ssid").c_str()); strcpy(config.password, server.arg("pass").c_str()); config.numServers = 0; for (int i = 0; i < 5; i++) { String srv = server.arg("srv" + String(i)); String name = server.arg("name" + String(i)); if (srv.length() > 0) { strcpy(config.servers[i], srv.c_str()); strcpy(config.serverNames[i], name.length() > 0 ? name.c_str() : srv.c_str()); config.numServers++; } } config.checkInterval = server.arg("interval").toInt() * 1000; config.brightness = server.arg("bright").toInt(); if (config.numServers == 0) config.numServers = 1; if (config.checkInterval < 10000) config.checkInterval = 30000; saveConfig(); server.send(200, "text/html", "<h1 style='color:#0f0;text-align:center;font-family:monospace'>SAVED! Rebooting...</h1>"); delay(1000); ESP.restart(); } // ============== CONFIG MODE DISPLAY ============== void drawConfigScreen() { gfx->fillScreen(COLOR_BG); drawGlowRing(CX, CY, 115, COLOR_MAGENTA); // Header drawCenteredText("CONFIG", 30, COLOR_MAGENTA, 2); drawCenteredText("MODE", 55, COLOR_MAGENTA, 2); // Instructions gfx->setTextSize(1); gfx->setTextColor(COLOR_TEXT); drawCenteredText("Connect WiFi to:", 100, COLOR_TEXT, 1); drawCenteredText("ALIVE-LCD-Setup", 125, COLOR_CYAN, 1); drawCenteredText("Then open:", 160, COLOR_TEXT, 1); drawCenteredText("192.168.4.1", 185, COLOR_YELLOW, 1); } // ============== START CONFIG MODE ============== void startConfigMode() { configMode = true; Serial.println("Starting Config Mode..."); drawConfigScreen(); // Start AP WiFi.mode(WIFI_AP); WiFi.softAP("ALIVE-LCD-Setup"); // Start DNS (captive portal) dnsServer.start(53, "*", WiFi.softAPIP()); // Start web server server.on("/", handleRoot); server.on("/scan", handleScan); server.on("/save", HTTP_POST, handleSave); server.onNotFound(handleRoot); // Redirect all to config server.begin(); Serial.println("AP IP: 192.168.4.1"); } // ============== CONNECT WiFi ============== bool connectWiFi() { if (strlen(config.ssid) == 0) return false; gfx->fillScreen(COLOR_BG); drawGlowRing(CX, CY, 115, COLOR_YELLOW); drawCenteredText("WIFI", CY - 40, COLOR_YELLOW, 2); drawCenteredText("Connecting...", CY - 10, COLOR_TEXT, 1); drawCenteredText(config.ssid, CY + 10, COLOR_CYAN, 1); // Progress bar int barX = 40, barY = CY + 35, barW = 160, barH = 12; gfx->drawRect(barX, barY, barW, barH, COLOR_CYAN); WiFi.mode(WIFI_STA); WiFi.begin(config.ssid, config.password); int attempts = 0; while (WiFi.status() != WL_CONNECTED && attempts < 30) { delay(500); attempts++; int progress = (attempts * 100) / 30; int fillW = (barW - 4) * progress / 100; gfx->fillRect(barX + 2, barY + 2, fillW, barH - 4, COLOR_CYAN); } if (WiFi.status() == WL_CONNECTED) { Serial.println("WiFi connected: " + WiFi.localIP().toString()); gfx->fillScreen(COLOR_BG); drawGlowRing(CX, CY, 115, COLOR_CYAN); drawCenteredText("CONNECTED", CY - 20, COLOR_CYAN, 2); drawCenteredText(WiFi.localIP().toString().c_str(), CY + 15, COLOR_GREEN, 1); delay(1000); return true; } Serial.println("WiFi connection failed!"); return false; } // ============== STARTUP SCREEN ============== void drawStartupScreen() { gfx->fillScreen(COLOR_BG); drawGlowRing(CX, CY, 115, COLOR_CYAN); drawCenteredText("ALIVE", 35, COLOR_CYAN, 3); drawCenteredText("SERVER MONITOR", 195, COLOR_TEXT, 1); // Triangle animation int triR = 45; for (int frame = 0; frame < 20; frame++) { float angle = frame * 0.3; for (int p = 0; p < 3; p++) { float a1 = angle + (p * 2.094); float a2 = angle + ((p + 1) * 2.094); int x1 = CX + cos(a1) * triR; int y1 = CY + sin(a1) * triR; int x2 = CX + cos(a2) * triR; int y2 = CY + sin(a2) * triR; gfx->drawLine(x1, y1, x2, y2, COLOR_CYAN); } delay(40); for (int p = 0; p < 3; p++) { float a1 = angle + (p * 2.094); float a2 = angle + ((p + 1) * 2.094); int x1 = CX + cos(a1) * triR; int y1 = CY + sin(a1) * triR; int x2 = CX + cos(a2) * triR; int y2 = CY + sin(a2) * triR; gfx->drawLine(x1, y1, x2, y2, COLOR_BG); } } } // ============== SETUP ============== void setup() { Serial.begin(115200); delay(100); Serial.println("\n=== ALIVE LCD - Captive Portal Version ==="); // Init LEDs pinMode(LED_BLUE, OUTPUT); pinMode(LED_RED, OUTPUT); pinMode(BOOT_BTN, INPUT_PULLUP); digitalWrite(LED_BLUE, HIGH); // Blue on during boot // Init backlight pinMode(TFT_BL, OUTPUT); digitalWrite(TFT_BL, HIGH); // Init display gfx->begin(); gfx->setRotation(0); gfx->fillScreen(COLOR_BG); // Load saved config loadConfig(); // Apply brightness analogWrite(TFT_BL, config.brightness); // Check if BOOT button held (force config mode) bool forceConfig = (digitalRead(BOOT_BTN) == LOW); delay(100); // Debounce forceConfig = forceConfig && (digitalRead(BOOT_BTN) == LOW); // Enter config if no WiFi saved OR button held if (forceConfig || strlen(config.ssid) == 0) { startConfigMode(); return; } // Show startup animation drawStartupScreen(); // Try to connect to saved WiFi if (!connectWiFi()) { startConfigMode(); return; } // Initial server check checkAllServers(); updateDisplay(); lastCheck = millis(); lastFlashUpdate = millis(); } // ============== CHECK ALL SERVERS ============== void checkAllServers() { Serial.println("\n--- Checking servers ---"); allServersUp = true; for (int i = 0; i < config.numServers; i++) { Serial.print("Pinging "); Serial.print(config.servers[i]); Serial.print("... "); bool success = Ping.ping(config.servers[i], 2); serverStatus[i] = success; if (success) { Serial.println("OK"); } else { Serial.println("FAILED"); allServersUp = false; } } } // ============== UPDATE DISPLAY ============== void updateDisplay() { gfx->fillScreen(COLOR_BG); // Draw outer status ring uint16_t ringColor = allServersUp ? COLOR_CYAN : COLOR_MAGENTA; drawGlowRing(CX, CY, 115, ringColor); // Draw header drawCenteredText("ALIVE", 28, COLOR_CYAN, 2); // Draw server status list int startY = 50; int lineHeight = 26; int maxVisible = min(config.numServers, 6); gfx->setTextSize(1); for (int i = 0; i < maxVisible; i++) { int yPos = startY + (i * lineHeight); // Status indicator circle if (serverStatus[i]) { gfx->fillCircle(45, yPos + 6, 6, COLOR_CYAN); } else { gfx->fillCircle(45, yPos + 6, 6, COLOR_MAGENTA); gfx->drawLine(42, yPos + 3, 48, yPos + 9, COLOR_BG); gfx->drawLine(48, yPos + 3, 42, yPos + 9, COLOR_BG); } // Server name gfx->setTextColor(COLOR_TEXT); gfx->setCursor(58, yPos + 3); String name = String(config.serverNames[i]); if (name.length() > 12) { name = name.substring(0, 11) + "."; } gfx->print(name); // Status text gfx->setCursor(170, yPos + 3); if (serverStatus[i]) { gfx->setTextColor(COLOR_GREEN); gfx->print("OK"); } else { gfx->setTextColor(COLOR_RED); gfx->print("DOWN"); } } } // ============== FLASH DOWN TEXT ============== void flashDownText(bool showText) { int startY = 50; int lineHeight = 26; int maxVisible = min(config.numServers, 6); gfx->setTextSize(1); for (int i = 0; i < maxVisible; i++) { if (!serverStatus[i]) { int yPos = startY + (i * lineHeight); gfx->fillRect(170, yPos + 1, 30, 10, COLOR_BG); if (showText) { gfx->setTextColor(COLOR_RED); gfx->setCursor(170, yPos + 3); gfx->print("DOWN"); } } } } // ============== UPDATE LEDs ============== void updateLEDs() { digitalWrite(LED_BLUE, HIGH); // Blue always on if (allServersUp) { digitalWrite(LED_RED, LOW); redLedState = false; } else { if (millis() - lastBlink >= 500) { redLedState = !redLedState; digitalWrite(LED_RED, redLedState ? HIGH : LOW); lastBlink = millis(); } } } // ============== MAIN LOOP ============== void loop() { if (configMode) { dnsServer.processNextRequest(); server.handleClient(); return; } // Maintain WiFi connection if (WiFi.status() != WL_CONNECTED) { if (!connectWiFi()) { startConfigMode(); return; } } // Periodic server check if (millis() - lastCheck >= config.checkInterval) { checkAllServers(); updateDisplay(); lastCheck = millis(); lastFlashUpdate = millis(); flashState = true; } // Flash DOWN text if (!allServersUp && millis() - lastFlashUpdate >= 300) { flashState = !flashState; flashDownText(flashState); lastFlashUpdate = millis(); } // Update LEDs updateLEDs(); delay(50); }
SETUP INSTRUCTIONS
- First Boot: ESP32 automatically enters config mode (no WiFi saved yet).
- Force Config Mode: Hold BOOT button while powering on to enter setup anytime.
- Connect: Join the "ALIVE-LCD-Setup" WiFi network from your phone/laptop.
- Configure: Browser should auto-open config page, or go to 192.168.4.1
- Scan Networks: Click "SCAN NETWORKS" to find available WiFi networks.
- Save: Fill in settings and click "SAVE & REBOOT" - ESP32 restarts with new config.
REQUIRED LIBRARIES
Arduino_GFX_Libraryby moononournation - Display driverESP32Pingby Matteo Dainese - ICMP ping
Note: WebServer, DNSServer, and Preferences are built into ESP32 Arduino core - no install needed.
HARDWARE PINOUT
GC9A01 240x240 Round LCD (SPI):
- SCK: GPIO 12
- MOSI: GPIO 11
- CS: GPIO 8
- DC: GPIO 9
- RST: GPIO 10
- BL: GPIO 7
Status LEDs:
- Blue LED: GPIO 5 (Always ON - power indicator)
- Red LED: GPIO 10 (Flashing when server down)
DIFFERENCES FROM BASIC VERSION
- No hardcoded credentials - WiFi and servers configured via web interface
- Persistent storage - Settings saved to ESP32 flash memory (NVS)
- Captive portal - Auto-redirects to config page when in AP mode
- Network scanning - Discovers available WiFi networks
- Configurable settings - Check interval, brightness, up to 5 servers
- Arduino_GFX Library - Uses Arduino_GFX instead of Adafruit libraries
ARDUINO PROJECT SETUP
- Create Project Folder: Create a folder named
ALIVE_Portalin your Arduino sketches directory (usuallyDocuments/Arduino/) - Main Sketch: Save the code as
ALIVE_Portal.ino- filename MUST match folder name - Single File Project: This project uses only one .ino file - no additional .h files required
ALIVE_Portal/
└── ALIVE_Portal.ino // Complete sketch (copy the code above)
COMPONENTS NEEDED
| ESP32-S3 Super Mini | Main microcontroller | AliExpress |
| GC9A01 Round Display | 240x240 1.28" TFT | Amazon |
| Blue LED (5mm) | Status indicator - always ON | Amazon |
| Red LED (5mm) | Alert indicator - flashes on server down | Amazon |
| 330Ω Resistors (x2) | Current limiting for LEDs | Amazon |
| Dupont Wires | For connections | Amazon |
WIRING DIAGRAM
| GC9A01 Display | ESP32-S3 |
|---|---|
| VCC | 3.3V |
| GND | GND |
| SCL (SCK) | GPIO 12 |
| SDA (MOSI) | GPIO 11 |
| RES (RST) | GPIO 10 |
| DC | GPIO 9 |
| CS | GPIO 8 |
| BLK (Backlight) | GPIO 7 |
| Status LEDs | ESP32-S3 |
|---|---|
| Blue LED (+) → 330Ω | GPIO 5 |
| Red LED (+) → 330Ω | GPIO 10 |
| Both LED (-) | GND |
ARDUINO IDE 3.x BOARD SETTINGS
| Board | ESP32S3 Dev Module |
| USB CDC On Boot | Enabled |
| Flash Size | 4MB (32Mb) |
| Partition Scheme | Default 4MB with spiffs |
| Upload Speed | 921600 |
ESP32 Board URL: Add this in File → Preferences → Additional Board Manager URLs:
https://raw.githubusercontent.com/espressif/arduino-esp32/gh-pages/package_esp32_index.json
TROUBLESHOOTING
- Upload fails / Guru Meditation Error: Hold BOOT button while clicking Upload, release after "Connecting..." appears.
- Display is white/blank: Check wiring, especially CS, DC, and RST pins. Verify 3.3V power.
- Can't find "ALIVE-LCD-Setup" network: Wait 10 seconds after power on. Hold BOOT to force config mode.
- Captive portal doesn't auto-open: Manually navigate to 192.168.4.1 in your browser.
- WiFi won't connect after config: Double-check SSID and password. ESP32 only supports 2.4GHz networks.
- Settings not saved: Make sure you click "SAVE & REBOOT" button. Check Serial Monitor for errors.
- All servers show DOWN: Verify server IPs are correct. Check if firewall blocks ICMP ping.
- LEDs not working: Check LED polarity (long leg = positive). Verify 330Ω resistor is connected.
- Serial Monitor empty: Set baud rate to 115200. Enable "USB CDC On Boot" in board settings.
BUILD CHECKLIST
- □ Install Arduino IDE 3.x
- □ Add ESP32 board package URL and install ESP32 boards
- □ Install required libraries: Arduino_GFX_Library, ESP32Ping
- □ Create project folder:
ALIVE_Portal/ - □ Copy sketch as
ALIVE_Portal.ino - □ Wire GC9A01 display to ESP32-S3
- □ Wire status LEDs with 330Ω resistors
- □ Connect ESP32-S3 via USB-C
- □ Select ESP32S3 Dev Module and correct COM port
- □ Upload sketch (hold BOOT if needed)
- □ Connect to "ALIVE-LCD-Setup" WiFi network
- □ Open 192.168.4.1 in browser
- □ Scan and select your WiFi network
- □ Add server IPs and names
- □ Click "SAVE & REBOOT" - Done!