ALIVE SERVER MONITOR OLED VERSION

Arduino Sketch - SSD1306 0.96" OLED Display

+ CAPTIVE PORTAL VERSION WiFi config via web interface

KEY FEATURES

>
Non-Blocking Ping Scans one server per loop - scrolling never pauses
>
Scrolling Status Display Animated "ServerName ALIVE" when all OK
>
LED Alerts Blue always ON, Red flashes on server down
>
Cyberpunk Yellow/Blue UI Optimized for dual-color OLED displays

DISPLAY PREVIEW

SSD1306 128x64 (2x scale)
BLUE (OK)
RED (ALERT)

TOGGLE SERVERS

Ready - Click RUN BOOT SEQUENCE

OLED vs ROUND LCD COMPARISON

Feature SSD1306 OLED GC9A01 Round LCD
Resolution 128 x 64 240 x 240
Colors Monochrome 65K Colors
Interface I2C (4 wires) SPI (8 wires)
Size 0.96" diagonal 1.28" diameter
Power ~20mA (varies by content) ~30mA (backlight)
Best For Compact builds, text display Visual dashboards, graphics

ARDUINO SKETCH

AliveOLED.ino
/*
 * Server Status Dashboard - "ALIVE" (OLED Version)
 * ESP32-S3 Super Mini + SSD1306 0.96" OLED + Status LEDs
 *
 * Uses I2C for simple 4-wire connection
 * Blue LED: Always ON (power indicator)
 * Red LED: Flashing when any server is down
 */

#include <WiFi.h>
#include <ESP32Ping.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>

// ============== CONFIGURATION ==============

// WiFi credentials
const char* WIFI_SSID = "YOUR_WIFI_SSID";
const char* WIFI_PASSWORD = "YOUR_WIFI_PASSWORD";

// Server list - add your servers here (IP addresses or hostnames)
const char* servers[] = {
  "1.1.1.1",         // Server 1
  "1.1.1.1",         // Server 2
  "1.1.1.1",         // Server 3
  "1.1.1.1",         // Server 4
  "1.1.1.1"          // Server 5
};
const int NUM_SERVERS = sizeof(servers) / sizeof(servers[0]);

// Optional: Friendly names for display (keep short for OLED)
const char* serverNames[] = {
  "Server1",
  "Server2",
  "Server3",
  "Server4",
  "Server5"
};

// Check interval (milliseconds)
const unsigned long CHECK_INTERVAL = 30000;  // 30 seconds

// ============== PIN DEFINITIONS ==============

// I2C for SSD1306 OLED
#define OLED_SDA 8
#define OLED_SCL 9

// Status LEDs
#define LED_BLUE 5
#define LED_RED  10

// ============== DISPLAY SETTINGS ==============
#define SCREEN_WIDTH  128
#define SCREEN_HEIGHT 64
#define OLED_RESET    -1   // No reset pin
#define OLED_ADDR     0x3C // I2C address (try 0x3D if this doesn't work)

// Create display object
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);

// ============== STATUS TRACKING ==============
bool serverStatus[NUM_SERVERS];
bool allServersUp = true;
unsigned long lastCheck = 0;
unsigned long lastBlink = 0;
bool redLedState = false;

// Non-blocking scan state
int scanIndex = -1;          // -1 = not scanning, 0+ = server being checked
bool scanInProgress = false;

// Scrolling display
int scrollX = 128;           // Start from right edge
int currentScrollServer = 0; // Which server is scrolling
unsigned long lastScroll = 0;

// ============== SETUP ==============
void setup() {
  Serial.begin(115200);
  delay(100);
  Serial.println("\n=== ALIVE - Server Status Dashboard (OLED) ===");

  // Initialize LED pins
  pinMode(LED_BLUE, OUTPUT);
  pinMode(LED_RED, OUTPUT);
  digitalWrite(LED_BLUE, LOW);
  digitalWrite(LED_RED, LOW);

  // Initialize I2C with custom pins
  Wire.begin(OLED_SDA, OLED_SCL);

  // Initialize OLED display
  if (!display.begin(SSD1306_SWITCHCAPVCC, OLED_ADDR)) {
    Serial.println("SSD1306 allocation failed!");
    // Flash both LEDs to indicate error
    while (1) {
      digitalWrite(LED_BLUE, HIGH);
      digitalWrite(LED_RED, HIGH);
      delay(200);
      digitalWrite(LED_BLUE, LOW);
      digitalWrite(LED_RED, LOW);
      delay(200);
    }
  }

  display.clearDisplay();
  display.setTextColor(SSD1306_WHITE);

  // Show startup screen
  drawStartupScreen();

  // Connect to WiFi
  connectWiFi();

  // Initial server check
  checkAllServers();
  updateDisplay();
}

// ============== MAIN LOOP ==============
void loop() {
  // Maintain WiFi connection
  if (WiFi.status() != WL_CONNECTED) {
    connectWiFi();
  }

  // Start a new scan cycle periodically
  if (!scanInProgress && millis() - lastCheck >= CHECK_INTERVAL) {
    startScan();
  }

  // If scan in progress, check one server per loop iteration
  if (scanInProgress) {
    checkNextServer();
  }

  // Continuous display update (scrolling when OK, blinking when alert)
  updateDisplay();

  // Handle LED states
  updateLEDs();

  delay(50);
}

// ============== STARTUP SCREEN ==============
void drawStartupScreen() {
  display.clearDisplay();

  // === YELLOW ZONE (y=0-15) - Title bar ===
  display.fillRect(0, 0, 128, 15, SSD1306_WHITE);
  display.setTextColor(SSD1306_BLACK);
  display.setTextSize(1);
  display.setCursor(35, 4);
  display.print("BOOTING...");
  display.setTextColor(SSD1306_WHITE);

  // === BLUE ZONE (y=16-63) - Content ===

  // Cyberpunk angular corners in blue zone
  display.drawLine(0, 16, 15, 16, SSD1306_WHITE);
  display.drawLine(0, 16, 0, 26, SSD1306_WHITE);
  display.drawLine(112, 16, 127, 16, SSD1306_WHITE);
  display.drawLine(127, 16, 127, 26, SSD1306_WHITE);
  display.drawLine(0, 63, 15, 63, SSD1306_WHITE);
  display.drawLine(0, 53, 0, 63, SSD1306_WHITE);
  display.drawLine(112, 63, 127, 63, SSD1306_WHITE);
  display.drawLine(127, 53, 127, 63, SSD1306_WHITE);

  // Title
  display.setTextSize(2);
  display.setCursor(34, 24);
  display.print("ALIVE");

  // Underline accent
  display.drawFastHLine(34, 41, 60, SSD1306_WHITE);
  display.drawFastHLine(36, 43, 56, SSD1306_WHITE);

  // Subtitle
  display.setTextSize(1);
  display.setCursor(22, 50);
  display.print(">> SYS_MONITOR");

  display.display();
  delay(500);
}

// ============== WiFi CONNECTION ==============
void connectWiFi() {
  display.clearDisplay();

  // === YELLOW ZONE (y=0-15) ===
  display.fillRect(0, 0, 128, 15, SSD1306_WHITE);
  display.setTextColor(SSD1306_BLACK);
  display.setTextSize(1);
  display.setCursor(25, 4);
  display.print("NET_CONNECT");
  display.setTextColor(SSD1306_WHITE);

  // === BLUE ZONE (y=16-63) ===
  display.setCursor(10, 24);
  display.print("SSID: ");
  display.print(WIFI_SSID);

  display.display();

  WiFi.mode(WIFI_STA);
  WiFi.begin(WIFI_SSID, WIFI_PASSWORD);

  int attempts = 0;
  display.setCursor(10, 42);
  display.print("[");

  while (WiFi.status() != WL_CONNECTED && attempts < 30) {
    delay(500);
    Serial.print(".");
    display.print("=");
    display.display();
    attempts++;
  }

  if (WiFi.status() == WL_CONNECTED) {
    Serial.println("\nWiFi connected!");
    Serial.print("IP: ");
    Serial.println(WiFi.localIP());

    display.clearDisplay();

    // === YELLOW ZONE ===
    display.fillRect(0, 0, 128, 15, SSD1306_WHITE);
    display.setTextColor(SSD1306_BLACK);
    display.setTextSize(1);
    display.setCursor(25, 4);
    display.print("LINK_ACTIVE");
    display.setTextColor(SSD1306_WHITE);

    // === BLUE ZONE ===
    display.setCursor(45, 24);
    display.print("NODE:");

    display.setCursor(25, 36);
    display.print(WiFi.localIP());

    display.setCursor(45, 52);
    display.print("[OK]");

    display.display();
    delay(750);
  } else {
    Serial.println("\nWiFi failed!");

    display.clearDisplay();

    // === YELLOW ZONE ===
    display.fillRect(0, 0, 128, 15, SSD1306_WHITE);
    display.setTextColor(SSD1306_BLACK);
    display.setTextSize(1);
    display.setCursor(25, 4);
    display.print("LINK_FAILED");
    display.setTextColor(SSD1306_WHITE);

    // === BLUE ZONE ===
    display.setCursor(30, 35);
    display.print(">> RETRY...");

    display.display();
    delay(1000);
  }
}

// ============== NON-BLOCKING SERVER CHECK ==============
void startScan() {
  Serial.println("\n--- Starting server scan ---");
  scanInProgress = true;
  scanIndex = 0;
  allServersUp = true;
}

void checkNextServer() {
  if (scanIndex >= NUM_SERVERS) {
    // Scan complete
    scanInProgress = false;
    lastCheck = millis();
    Serial.print("Scan complete. All up: ");
    Serial.println(allServersUp ? "YES" : "NO");
    return;
  }

  Serial.print("Pinging ");
  Serial.print(servers[scanIndex]);
  Serial.print("... ");

  bool success = Ping.ping(servers[scanIndex], 1);
  serverStatus[scanIndex] = success;

  if (success) {
    Serial.println("OK");
  } else {
    Serial.println("FAILED");
    allServersUp = false;
  }
  scanIndex++;
}

// Initial blocking check at startup
void checkAllServers() {
  Serial.println("\n--- Initial server check ---");
  allServersUp = true;

  for (int i = 0; i < NUM_SERVERS; i++) {
    Serial.print("Pinging ");
    Serial.print(servers[i]);
    Serial.print("... ");

    bool success = Ping.ping(servers[i], 2);
    serverStatus[i] = success;

    if (success) {
      Serial.println("OK");
    } else {
      Serial.println("FAILED");
      allServersUp = false;
    }
  }
  lastCheck = millis();
}

// ============== UPDATE DISPLAY ==============
void updateDisplay() {
  display.clearDisplay();

  if (allServersUp) {
    // === ALL SERVERS UP - Scrolling display ===

    // === YELLOW ZONE (y=0-15) - Header ===
    display.fillRect(0, 0, 120, 14, SSD1306_WHITE);
    display.fillTriangle(120, 0, 120, 14, 127, 0, SSD1306_WHITE);

    display.setTextColor(SSD1306_BLACK);
    display.setTextSize(1);
    display.setCursor(4, 4);
    display.print("ALIVE");
    display.setCursor(50, 4);
    if (scanInProgress) {
      display.print("[SCAN ");
      display.print(scanIndex + 1);
      display.print("/");
      display.print(NUM_SERVERS);
      display.print("]");
    } else {
      display.print("[NOMINAL]");
    }
    display.setTextColor(SSD1306_WHITE);

    // === BLUE ZONE (y=16-63) - Scrolling server name ===

    // Build scroll text: "ServerName ALIVE"
    String scrollText = String(serverNames[currentScrollServer]) + " ALIVE";
    int textWidth = scrollText.length() * 12;  // Size 2 = ~12px per char

    // Draw scrolling text - centered vertically in blue zone
    display.setTextSize(2);
    display.setTextWrap(false);  // Prevent text wrapping
    display.setCursor(scrollX, 32);
    display.print(scrollText);

    // Update scroll position
    if (millis() - lastScroll > 30) {
      scrollX -= 3;  // Scroll speed
      lastScroll = millis();

      // When text is fully off screen, move to next server
      if (scrollX < -textWidth) {
        scrollX = 128;
        currentScrollServer++;
        if (currentScrollServer >= NUM_SERVERS) {
          currentScrollServer = 0;
        }
      }
    }

    // === Bottom status with blinking chevrons ===
    display.setTextSize(1);

    // Blinking pattern: alternates every 300ms
    int blinkPhase = (millis() / 300) % 2;

    // Outer chevrons
    if (blinkPhase == 0) {
      display.setCursor(10, 56);
      display.print(">");
      display.setCursor(111, 56);
      display.print("<");
    }

    // Inner chevrons
    if (blinkPhase == 1) {
      display.setCursor(16, 56);
      display.print(">");
      display.setCursor(105, 56);
      display.print("<");
    }

    // Static text
    display.setCursor(25, 56);
    display.print("System Active");

  } else {
    // === SERVER DOWN - Warning display ===

    // Find first down server
    int downIndex = -1;
    for (int i = 0; i < NUM_SERVERS; i++) {
      if (!serverStatus[i]) {
        downIndex = i;
        break;
      }
    }

    // Blinking effect using millis
    bool blinkOn = (millis() / 300) % 2 == 0;

    // === YELLOW ZONE - Blinking WARNING ===
    if (blinkOn) {
      display.fillRect(0, 0, 128, 15, SSD1306_WHITE);
      display.setTextColor(SSD1306_BLACK);
      display.setTextSize(1);
      display.setCursor(40, 4);
      display.print("WARNING");
      display.setTextColor(SSD1306_WHITE);
    }

    // === BLUE ZONE - Flashing server info ===
    if (blinkOn && downIndex >= 0) {
      display.setTextSize(1);
      display.setCursor(45, 24);
      display.print("[DEAD]");

      display.setTextSize(2);
      display.setCursor(10, 36);
      display.print(serverNames[downIndex]);

      display.setTextSize(1);
      display.setCursor(25, 54);
      display.print(servers[downIndex]);
    }
  }

  display.display();
}

// ============== UPDATE LEDs ==============
void updateLEDs() {
  // Blue always on
  digitalWrite(LED_BLUE, HIGH);

  if (allServersUp) {
    // All OK: Red off
    digitalWrite(LED_RED, LOW);
    redLedState = false;
  } else {
    // Problem: Red flashing
    if (millis() - lastBlink >= 500) {
      redLedState = !redLedState;
      digitalWrite(LED_RED, redLedState ? HIGH : LOW);
      lastBlink = millis();
    }
  }
}

SETUP INSTRUCTIONS

  • WiFi: Replace YOUR_WIFI_SSID and YOUR_WIFI_PASSWORD with your WiFi credentials.
  • Servers: Edit the servers[] array to add your server IP addresses or hostnames.
  • Server Names: Keep names short (4-8 chars) for the compact OLED display.
  • I2C Address: Default is 0x3C. If display doesn't work, try 0x3D.
  • Libraries: Install via Arduino Library Manager:
    • Adafruit GFX Library
    • Adafruit SSD1306
    • ESP32Ping by Matteo Dainese
  • Board: Select "ESP32S3 Dev Module" in Arduino IDE.
  • LEDs: GPIO 5 (Blue - always ON) and GPIO 10 (Red - flashes on alert).
  • Display: Uses cyberpunk yellow/blue zone layout for dual-color OLEDs. Features scrolling server names when all OK.

I2C TROUBLESHOOTING

  • Display not working? Run an I2C scanner sketch to find your display's address.
  • Common addresses: 0x3C (most common) or 0x3D (some modules).
  • No display output? Check wiring: SDA to GPIO 8, SCL to GPIO 9, VCC to 3.3V, GND to GND.
  • Flickering? Add 4.7K pull-up resistors on SDA and SCL lines (some modules have them built-in).
  • Dual-color OLED: Top ~16 rows display yellow, bottom rows display blue. Code is optimized for this layout.

I2C SCANNER (OPTIONAL)

I2C_Scanner.ino
/*
 * I2C Scanner - Find your OLED address
 * Upload this if display doesn't work to find correct address
 */

#include <Wire.h>

#define OLED_SDA 8
#define OLED_SCL 9

void setup() {
  Serial.begin(115200);
  delay(2000);
  Serial.println("\nI2C Scanner");

  Wire.begin(OLED_SDA, OLED_SCL);
}

void loop() {
  byte error, address;
  int nDevices = 0;

  Serial.println("Scanning...");

  for (address = 1; address < 127; address++) {
    Wire.beginTransmission(address);
    error = Wire.endTransmission();

    if (error == 0) {
      Serial.print("I2C device found at 0x");
      if (address < 16) Serial.print("0");
      Serial.println(address, HEX);
      nDevices++;
    }
  }

  if (nDevices == 0) {
    Serial.println("No I2C devices found!");
  }

  delay(5000);
}

ARDUINO PROJECT SETUP

  • Create Project Folder: Create a folder named ALIVE_OLED in your Arduino sketches directory (usually Documents/Arduino/)
  • Main Sketch: Save the code as ALIVE_OLED.ino - filename MUST match folder name
  • Single File Project: This project uses only one .ino file - no additional .h files required
  • Optional: Save the I2C Scanner as a separate sketch for troubleshooting
ALIVE_OLED/
└── ALIVE_OLED.ino   // Complete sketch (copy the main code above)

I2C_Scanner/         // Optional - for troubleshooting
└── I2C_Scanner.ino

COMPONENTS NEEDED

ESP32-S3 Super Mini Main microcontroller AliExpress
SSD1306 OLED Display 128x64 0.96" I2C (dual-color yellow/blue recommended) Amazon
Blue LED (5mm) Status indicator - always ON when running Amazon
Red LED (5mm) Alert indicator - flashes when server down Amazon
330Ω Resistors (x2) Current limiting for LEDs Amazon
Dupont Wires For connections (F-F for OLED) Amazon

WIRING DIAGRAM

SSD1306 OLED ESP32-S3
VCC 3.3V
GND GND
SDA GPIO 8
SCL GPIO 9
Status LEDs ESP32-S3
Blue LED (+) → 330Ω GPIO 5
Red LED (+) → 330Ω GPIO 10
Both LED (-) GND

Note: Only 4 wires needed for OLED (I2C is simpler than SPI displays).

REQUIRED LIBRARIES

Install via Arduino Library Manager (Sketch → Include Library → Manage Libraries):

Adafruit GFX Library Graphics primitives
Adafruit SSD1306 OLED display driver
ESP32Ping by Matteo Craciunescu - ICMP ping

Note: ESP32Ping may need manual install from GitHub if not found in Library Manager.

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.
  • OLED shows nothing: Run I2C Scanner to find correct address. Try 0x3C or 0x3D.
  • I2C Scanner finds no devices: Check wiring - SDA to GPIO 8, SCL to GPIO 9, VCC to 3.3V.
  • Display flickers: Add 4.7K pull-up resistors on SDA and SCL (some modules have built-in).
  • WiFi won't connect: Double-check SSID and password. ESP32 only supports 2.4GHz networks.
  • 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 resistors connected.
  • Serial Monitor empty: Set baud rate to 115200. Enable "USB CDC On Boot" in board settings.

BUILD CHECKLIST

  1. □ Install Arduino IDE 3.x
  2. □ Add ESP32 board package URL and install ESP32 boards
  3. □ Install required libraries: Adafruit GFX, Adafruit SSD1306, ESP32Ping
  4. □ Create project folder: ALIVE_OLED/
  5. □ Copy sketch as ALIVE_OLED.ino
  6. □ Edit code: Set WiFi SSID and password
  7. □ Edit code: Add your server IPs to servers[] array
  8. □ Edit code: Set short names in serverNames[] array (4-8 chars)
  9. □ Wire SSD1306 OLED to ESP32-S3 (4 wires: VCC, GND, SDA, SCL)
  10. □ Wire status LEDs with 330Ω resistors
  11. □ Connect ESP32-S3 via USB-C
  12. □ Select ESP32S3 Dev Module and correct COM port
  13. □ Upload sketch (hold BOOT if needed)
  14. □ Check Serial Monitor for WiFi connection
  15. □ Verify scrolling server names on display - Done!