Birthday Wishes Display

Interactive Birthday Wish Box - with ESP32S3 as access point!

BeginnerFull instructions provided56
Birthday Wishes Display

Things used in this project

Story

Read more

Schematics

Schematics

Code

Birthday Code

C/C++
#include <WiFi.h>
#include <ESPAsyncWebServer.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include "pitches.h"

// OLED Configuration
#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64
#define OLED_RESET -1
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);

// Buzzer Pin
#define BUZZER_PIN D10

// Wi-Fi AP
const char* ssid = "BirthdayWishes";
const char* password = "12345678";

// Web Server
AsyncWebServer server(80);

// Message System
String currentMessage = "";
bool hasMessage = false;

// Display Control
unsigned long lastAnimTime = 0;
const unsigned long animInterval = 30;
int fadeState = 0;
int brightness = 0;
const int maxBrightness = 15;

const int LINE_HEIGHT = 10;
const int MAX_LINES = 4;
String displayedLines[MAX_LINES];
int lineCount = 0;

void setup() {
  Serial.begin(115200);

  if (!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) {
    Serial.println(F("OLED failed"));
    for (;;);
  }
  display.clearDisplay();
  display.display();

  pinMode(BUZZER_PIN, OUTPUT);
  digitalWrite(BUZZER_PIN, LOW);

  tone(BUZZER_PIN, NOTE_A4, 200);
  delay(200);
  noTone(BUZZER_PIN);

  WiFi.softAP(ssid, password);
  displayConnectionInfo();

  setupWebServer();
  server.begin();
}

void displayConnectionInfo() {
  display.clearDisplay();
  display.setTextSize(1);
  display.setTextColor(SSD1306_WHITE);
  display.setCursor(0, 0);
  display.println("Connect to:");
  display.println(ssid);
  display.println("IP:");
  display.println(WiFi.softAPIP());
  display.display();
}

void setupWebServer() {
  server.on("/", HTTP_GET, [](AsyncWebServerRequest *request) {
    String html = R"rawliteral(
    <!DOCTYPE html><html>
    <head><title>Birthday Wishes</title>
    <meta name='viewport' content='width=device-width, initial-scale=1'>
    <style>
      body { font-family: Arial; background: #f0f8ff; padding: 20px; }
      .container { max-width: 500px; margin: auto; background: white; padding: 20px; border-radius: 15px; box-shadow: 0 0 10px rgba(0,0,0,0.1); }
      h2 { color: #ff6b6b; text-align: center; }
      textarea { width: 100%; height: 100px; border: 1px solid #ccc; border-radius: 10px; padding: 10px; font-size: 16px; }
      button { width: 100%; padding: 12px; margin: 10px 0; font-size: 16px; font-weight: bold; border: none; border-radius: 10px; cursor: pointer; }
      .btn-submit { background: #4CAF50; color: white; }
      .btn-music { background: #2196F3; color: white; }
      .btn-reset { background: #f44336; color: white; }
      button:hover { opacity: 0.9; }
    </style></head>
    <body>
    <div class="container">
      <h2>Birthday Message Center</h2>
      <form id="messageForm">
        <textarea id="messageInput" maxlength="120" placeholder="Type your birthday message here..."></textarea>
        <button type="submit" class="btn-submit">Display Message</button>
      </form>
      <button onclick="playMusic()" class="btn-music">Play Birthday Song</button>
      <button onclick="resetDisplay()" class="btn-reset">Reset Display</button>
    </div>
    <script>
      function playMusic() { fetch('/play'); }
      function resetDisplay() {
        if (confirm("Clear current message?")) {
          fetch('/reset');
        }
      }
      document.getElementById('messageForm').onsubmit = function(e) {
        e.preventDefault();
        const msg = document.getElementById('messageInput').value;
        if (msg.trim()) {
          fetch('/submit?msg=' + encodeURIComponent(msg));
        }
      };
    </script></body></html>
    )rawliteral";
    request->send(200, "text/html", html);
  });

  server.on("/submit", HTTP_GET, [](AsyncWebServerRequest *request) {
    if (request->hasParam("msg")) {
      currentMessage = request->getParam("msg")->value();
      currentMessage.replace("+", " ");
      prepareTextForDisplay(currentMessage);
      hasMessage = true;
      fadeState = 0;
      brightness = 0;
      playSuccessTone();
      request->send(200, "text/plain", "Message displayed");
    }
  });

  server.on("/play", HTTP_GET, [](AsyncWebServerRequest *request) {
    playBirthdaySong();
    request->send(200, "text/plain", "Playing song");
  });

  server.on("/reset", HTTP_GET, [](AsyncWebServerRequest *request) {
    currentMessage = "";
    hasMessage = false;
    display.clearDisplay();
    display.display();
    request->send(200, "text/plain", "Display reset");
  });
}

void prepareTextForDisplay(String text) {
  for (int i = 0; i < MAX_LINES; i++) displayedLines[i] = "";
  lineCount = 0;

  int newlinePos = text.indexOf('\n');
  while (newlinePos != -1 && lineCount < MAX_LINES - 1) {
    displayedLines[lineCount++] = text.substring(0, newlinePos);
    text = text.substring(newlinePos + 1);
    newlinePos = text.indexOf('\n');
  }

  if (lineCount < MAX_LINES && text.length() > 0) {
    displayedLines[lineCount++] = text;
  }
}

void playBirthdaySong() {
  noTone(BUZZER_PIN);

  // 🎵 Happy birthday to you
  tone(BUZZER_PIN, NOTE_C4); delay(200);
  tone(BUZZER_PIN, NOTE_C4); delay(200);
  tone(BUZZER_PIN, NOTE_D4); delay(400);
  tone(BUZZER_PIN, NOTE_C4); delay(400);
  tone(BUZZER_PIN, NOTE_F4); delay(400);
  tone(BUZZER_PIN, NOTE_E4); delay(800);

  // 🎵 Happy birthday to you
  tone(BUZZER_PIN, NOTE_C4); delay(200);
  tone(BUZZER_PIN, NOTE_C4); delay(200);
  tone(BUZZER_PIN, NOTE_D4); delay(400);
  tone(BUZZER_PIN, NOTE_C4); delay(400);
  tone(BUZZER_PIN, NOTE_G4); delay(400);
  tone(BUZZER_PIN, NOTE_F4); delay(800);

  noTone(BUZZER_PIN);
}

void playSuccessTone() {
  tone(BUZZER_PIN, NOTE_E6, 200); delay(250);
  tone(BUZZER_PIN, NOTE_G6, 200); delay(250);
  noTone(BUZZER_PIN);
}

void displayCurrentMessage() {
  display.clearDisplay();

  if (!hasMessage) return;

  display.setTextSize(1);
  display.setTextColor(SSD1306_WHITE);

  int totalHeight = lineCount * LINE_HEIGHT;
  int startY = (SCREEN_HEIGHT - totalHeight) / 2;

  for (int i = 0; i < lineCount; i++) {
    int16_t x1, y1;
    uint16_t w, h;
    display.getTextBounds(displayedLines[i], 0, 0, &x1, &y1, &w, &h);
    display.setCursor((SCREEN_WIDTH - w) / 2, startY + (i * LINE_HEIGHT));
    display.println(displayedLines[i]);
  }

  display.dim(brightness < maxBrightness);
  display.display();
}

void loop() {
  if (!hasMessage) return;

  if (millis() - lastAnimTime > animInterval) {
    lastAnimTime = millis();

    switch (fadeState) {
      case 0: brightness++;
              if (brightness >= maxBrightness) {
                brightness = maxBrightness;
                fadeState = 1;
                lastAnimTime = millis() + 3000;
              }
              break;

      case 1: if (millis() >= lastAnimTime) fadeState = 2;
              break;

      case 2: brightness--;
              if (brightness <= 0) {
                brightness = 0;
                fadeState = 0;
              }
              break;
    }

    displayCurrentMessage();
  }
}

pitches.h code

C/C++
#define NOTE_C4  262
#define NOTE_D4  294
#define NOTE_E4  330
#define NOTE_F4  349
#define NOTE_G4  392
#define NOTE_A4  440
#define NOTE_AS4 466
#define NOTE_C5  523
#define NOTE_E6  1319
#define NOTE_G6  1568

Credits

Noval Aryansah
1 project • 0 followers
Tech Enthusiast, Coding is (F)un
Dzovani Sandy Putra Prayitno
1 project • 0 followers
Naufal Fadillah
1 project • 0 followers
Rafid Afiyanto
1 project • 0 followers

Comments