Rubber Duck Alarm Clock

 





Parts:
  • Microcontroller
  • Real-time Clock Module
  • Power Supply
  • LCD Display
  • Buttons
  • Buzzer
  • Speaker
  • Water Pump
  • Motor Controller

Create a design for an alarm clock that squirts water when the alarm goes off, and plays an audio clip, using an ESP 32, real-time clock module, buzzer, , MAX 98357A, amplifier, and speaker

This system uses an ESP32 microcontroller as the central controller, scheduling alarms using an external RTC (DS3231), outputting audio through an I2S DAC/Amp (MAX98357A), and actuating a 12V peristaltic or submersible water pump via a transistor or Mosfet switch.

1. Hardware Architecture & Bill of Materials

ComponentPurposeKey Connections / Specs
ESP32 DevKitCentral controller & I2S host3.3V logic, Wi-Fi/BLE (for optional web setup)
DS3231 RTC ModuleBattery-backed precise timekeepingI2C (SDA, SCL, active-low SQW/INT)
MAX98357AI2S 3.2W Class-D mono amplifierI2S (BCLK, LRC/WS, DIN)
SpeakerAlarm playback4Ω to 8Ω, 3W
Active BuzzerHigh-frequency wake-up toneControlled via a general-purpose GPIO
12V DC Pump / Solenoid ValveSquirting mechanismDriven via logic-level N-channel MOSFET
N-Channel MOSFET (IRLZ44N or similar)Safe pump switchingGate resistor (100–220Ω), pulldown (10kΩ)
1N4007 DiodeFlyback suppression across pumpAnti-parallel across pump inductive terminals
12V Power Supply + Buck ConverterDual-rail power delivery12V for pump; 5V step-down for ESP32 & MAX98357A

2. Wiring & Pin Mapping

Plaintext
               +-------------------------------------------+
               |             12V DC Power Supply           |
               +-----+-------------------------------+-----+
                     |                               |
                   [GND]                           [+12V]
                     |                               |
             +-------+-------+                       |
             | LM2596 Step-  |                       |
             | Down to 5V    |                       |
             +-------+-------+                       |
                     |                               |
                  [+5V, GND]                         |
                     |                               |
                     v                               v
             +---------------+               +---------------+
             |     ESP32     |               |    12V Pump   |
             +-------+-------+               +-------+-------+
                     |                               |
                     +-- GPIO 23 (Gate) --> MOSFET --+ (Cathode)
                     |                      (Flyback Diode across 12V/Drain)
                     +-- I2C (GPIO 21/22) -> DS3231 RTC
                     +-- I2S (GPIO 25/26/22) -> MAX98357A -> Speaker
                     +-- GPIO 19 ----------> Active Buzzer

Pinout Details

  • DS3231 (I2C):

    • VCC 3.3V

    • GND GND

    • SDA GPIO 21

    • SCL GPIO 22

    • SQW GPIO 27 (Configured as input with pull-up for hardware alarm interrupts)

  • MAX98357A (I2S):

    • VIN 5V (yields highest audio wattage)

    • GND GND

    • BCLK (Bit Clock) GPIO 26

    • LRC (Word Select / WS) GPIO 25

    • DIN (Serial Data) GPIO 33

    • GAIN GND (sets default gain to 9dB)

    • SD_MODE Left floating or pulled to 3.3V via 100kΩ for mono mix

  • Water Pump Driver:

    • GPIO 23 220Ω Resistor MOSFET Gate

    • 10kΩ Resistor between MOSFET Gate and GND (ensures pump stays OFF during boot)

    • MOSFET Source GND

    • MOSFET Drain Pump Negative (-) terminal

    • 12V (+) rail Pump Positive (+) terminal

    • Flyback Diode (1N4007): Cathode (striped side) to Pump (+), Anode to Pump (-)

  • Buzzer:

    • GPIO 19 1kΩ Resistor NPN Transistor Base (2N2222) or directly driven if small 3.3V active buzzer .

3. Firmware Structure (ESP-IDF / Arduino Framework)

The firmware relies on ESP8266Audio (or standard ESP-IDF I2S drivers) and the RTClib library:

  1. Storage: An audio clip (alarm.wav, 16-bit PCM, 44.1kHz mono) is stored in the ESP32’s on-board flash via SPIFFS or LittleFS, or read from a micro-SD card module.

  2. Clock Synchronization: At setup, read the DS3231 to establish local time. The alarm can be scheduled using DS3231 Alarm 1 registers.

  3. Trigger Sequence:

    • Stage 1 (Audio Alert): Initialize the I2S output and stream the WAV file via AudioGeneratorWAV and AudioOutputI2S.

    • Stage 2 (Buzzer Accent): Pulse GPIO 19 periodically to cut through ambient noise.

    • Stage 3 (Squirt Sequence): Set GPIO 23 HIGH for a discrete, brief burst (e.g., 250–500 ms) to avoid soaking electronics or bedding, repeating every 10 seconds until an alarm-dismiss switch (e.g., a cap-touch sensor or mechanical button) is pressed.

C++
#include <Wire.h>
#include "RTClib.h"
#include "AudioFileSourceSPIFFS.h"
#include "AudioGeneratorWAV.h"
#include "AudioOutputI2S.h"

#define PUMP_PIN   23
#define BUZZER_PIN 19

RTC_DS3231 rtc;
AudioGeneratorWAV *wav;
AudioFileSourceSPIFFS *file;
AudioOutputI2S *out;

void setup() {
  pinMode(PUMP_PIN, OUTPUT);
  pinMode(BUZZER_PIN, OUTPUT);
  digitalWrite(PUMP_PIN, LOW);
  digitalWrite(BUZZER_PIN, LOW);

  Wire.begin(21, 22);
  rtc.begin();

  out = new AudioOutputI2S();
  out->SetPinout(26, 25, 33); // BCLK, LRC, DIN
  out->SetGain(0.8);
}

void triggerAlarmSequence() {
  // 1. Start audio playback
  file = new AudioFileSourceSPIFFS("/wake_up.wav");
  wav = new AudioGeneratorWAV();
  wav->begin(file, out);

  // 2. Play audio & fire squirt burst
  digitalWrite(PUMP_PIN, HIGH);
  delay(350); // Short spray burst
  digitalWrite(PUMP_PIN, LOW);

  while (wav->isRunning()) {
    if (!wav->loop()) wav->stop();
  }
}

4. Fluid Dynamics & Safety Considerations

  • Physical Isolation: House the reservoir, pump, and nozzle in a segregated compartment or standalone base. Ensure all tubing joints use barbed fittings with zip-ties to prevent pressure leaks inside the electronics enclosure.

  • Siphon Prevention: Keep the nozzle tip higher than the maximum water level of the reservoir. Submersible pumps and open peristaltic lines will otherwise passively siphon water out continuously onto your nightstand.

  • Inductive Back-EMF: Missing the flyback diode across the pump will inject voltage spikes (>50V) back into your power rails, causing spontaneous ESP32 brownouts, flash corruption, or destroyed GPIO pins.

1. Detailed Circuit Schematic

Plaintext
                                +12V DC Adapter
                                      |
         +----------------------------+-----------------------------+
         |                                                          |
         |                                                          |
    +----+----+                                                     |
    | LM2596  |                                                     |
    | Buck    |                                                     |
    | Step-   |                                                     |
    | Down    |                                                     |
    +----+----+                                                     |
         | +5V Regulated                                            |
         |                                                          |
         +-------------+---------------------+                      |
         |             |                     |                      |
         |             |                     |                      |
      [5V/VIN]       [VIN]                   |                      |
     +-------+    +---------+                |                      |
     | ESP32 |    | MAX98357|                |                      |
     | Dev   |    | Amp     |                |                      |
     | Board |    +----+----+                |                      |
     +---+---+         |                     |                      |
         | [3.3V]      |                     |                      |
         +--------+    |                     |                      |
         |        |    |                     |                      |
         |      [VCC]  |                     |                      |
         |    +-------++-+                   |                      |
         |    |  DS3231  |                   |                      |
         |    |  RTC     |                   |                      |
         |    +----+-----+                   |                      |
         |         |                         |                      |
 [GND] --+---------+----+--------------------+----------+           |
         |              |                    |          |           |
         |              |                    |       [Source]       |
         |              |                    |     +----------+     |
         |              |                    |     | IRLZ44N  |     |
         |              |                    |     | N-MOSFET |     |
         |              |                    |     +----+-----+     |
         |              |                    |       [Drain]        |
         |              |                    |          |           |
         |              |                    |          |    [Cathode / Striped]
         |              |                    |          +-------+ [1N4007 Diode]
         |              |                    |          |       +-------+
         |              |                    |          |    [Anode]    |
         |              |                    |          |               |
         |              |                    |       [ (-) ]         [ (+) ]
         |              |                    |     +-------------------------+
         |              |                    |     |      12V DC Pump        |
         |              |                    |     +-------------------------+
         |              |                    |                          |
         |              |                    +--------------------------+
         |              |
         |              +--- 10k Resistor ---+
         |                                   |
         +--- [GPIO 23] ---- 220 Resistor ---+ (Gate)
         |
         +--- [GPIO 19] ---- 1k Resistor ----+ (Base)
         |                                   |
         |                             [Collector] -> Active Buzzer (-)
         |                           +-----------+
         |                           | 2N2222    |    Active Buzzer (+) -> 3.3V
         |                           | NPN       |
         |                           +-----+-----+
         |                               [Emitter] -> GND
         |
         +--- [GPIO 21] -------------------> SDA (DS3231)
         +--- [GPIO 22] -------------------> SCL (DS3231)
         +--- [GPIO 26] -------------------> BCLK (MAX98357A)
         +--- [GPIO 25] -------------------> LRC / WS (MAX98357A)
         +--- [GPIO 33] -------------------> DIN (MAX98357A)
         +--- [GPIO 27] -------------------> Dismiss Button (Normally Open -> GND)
                                             (Uses internal PULLUP)

2. Software Architecture

  1. Storage (Preferences.h): Saves alarm configuration (Hour, Minute, Enabled) in non-volatile flash so settings persist across reboots.

  2. Access Point & Web Server (WiFi.h & WebServer.h): Spins up a local SoftAP named Alarm-Clock-Config (IP: 192.168.4.1). Any phone or laptop connecting to it is served an HTML form to adjust system time and alarms.

  3. Audio Pipeline (ESP8266Audio): Uses asynchronous I2S output to drive the MAX98357A without blocking time checks.

  4. Alarm State Machine: Checks the DS3231 every second. When matched, it runs the alert routine (audio + buzzer + brief pulsed water spray) until the dismiss button on GPIO 27 is pressed.

3. ESP32 Implementation Code

Install the following libraries in Arduino IDE before compiling:

  • RTClib by Adafruit

  • ESP8266Audio by Earle F. Philhower, III

C++
#include <WiFi.h>
#include <WebServer.h>
#include <Preferences.h>
#include <Wire.h>
#include "RTClib.h"
#include "AudioFileSourceSPIFFS.h"
#include "AudioGeneratorWAV.h"
#include "AudioOutputI2S.h"
#include <SPIFFS.h>

// --- Pin Definitions ---
#define PIN_PUMP        23
#define PIN_BUZZER      19
#define PIN_BUTTON      27
#define PIN_I2S_BCLK    26
#define PIN_I2S_LRC     25
#define PIN_I2S_DIN     33

// --- Peripherals & Handlers ---
RTC_DS3231 rtc;
WebServer server(80);
Preferences prefs;

AudioGeneratorWAV *wav = nullptr;
AudioFileSourceSPIFFS *file = nullptr;
AudioOutputI2S *out = nullptr;

// --- State Variables ---
int alarmHour = 7;
int alarmMinute = 0;
bool alarmEnabled = true;
bool alarmTriggered = false;
unsigned long lastPumpPulse = 0;
unsigned long lastRtcCheck = 0;

const char* apSSID = "Alarm-Clock-Config";
const char* apPass = "12345678";

// --- HTML Web Interface ---
const char INDEX_HTML[] PROGMEM = R"rawliteral(
<!DOCTYPE html>
<html>
<head>
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Alarm Controller</title>
  <style>
    body { font-family: -apple-system, sans-serif; background: #121212; color: #fff; text-align: center; padding: 20px; }
    .card { background: #1e1e1e; border-radius: 12px; padding: 24px; max-width: 400px; margin: auto; box-shadow: 0 4px 10px rgba(0,0,0,0.5); }
    h2 { margin-top: 0; color: #00bcd4; }
    .time-display { font-size: 2.2rem; font-weight: bold; margin: 15px 0; color: #4caf50; }
    input[type="time"] { font-size: 1.5rem; padding: 8px; border-radius: 6px; border: 1px solid #333; background: #2a2a2a; color: #fff; }
    .btn { background: #00bcd4; color: #121212; border: none; padding: 12px 24px; font-size: 1rem; border-radius: 6px; cursor: pointer; font-weight: bold; margin-top: 15px; }
    .switch-label { margin: 15px; display: block; font-size: 1.1rem; }
  </style>
</head>
<body>
  <div class="card">
    <h2>Squirting Alarm Clock</h2>
    <div id="rtcTime" class="time-display">--:--:--</div>
    <form action="/save" method="POST">
      <label class="switch-label">
        <input type="checkbox" name="enabled" %ENABLED_CHECKED%> Enable Alarm
      </label>
      <input type="time" name="alarmTime" value="%ALARM_TIME%" required><br>
      <input type="submit" value="Save Settings" class="btn">
    </form>
  </div>
  <script>
    setInterval(() => {
      fetch('/time').then(r => r.text()).then(t => {
        document.getElementById('rtcTime').innerText = t;
      });
    }, 1000);
  </script>
</body>
</html>
)rawliteral";

// --- Audio Subsystem ---
void stopAudio() {
  if (wav) {
    wav->stop();
    delete wav;
    wav = nullptr;
  }
  if (file) {
    delete file;
    file = nullptr;
  }
}

void startAudio() {
  stopAudio();
  file = new AudioFileSourceSPIFFS("/alarm.wav");
  wav = new AudioGeneratorWAV();
  wav->begin(file, out);
}

// --- Web Server Handlers ---
void handleRoot() {
  DateTime now = rtc.now();
  char timeBuf[6];
  snprintf(timeBuf, sizeof(timeBuf), "%02d:%02d", alarmHour, alarmMinute);

  String html = FPSTR(INDEX_HTML);
  html.replace("%ALARM_TIME%", String(timeBuf));
  html.replace("%ENABLED_CHECKED%", alarmEnabled ? "checked" : "");
  server.send(200, "text/html", html);
}

void handleSave() {
  if (server.hasArg("alarmTime")) {
    String t = server.arg("alarmTime");
    alarmHour = t.substring(0, 2).toInt();
    alarmMinute = t.substring(3, 5).toInt();
    alarmEnabled = server.hasArg("enabled");

    prefs.putInt("hour", alarmHour);
    prefs.putInt("min", alarmMinute);
    prefs.putBool("en", alarmEnabled);
  }
  server.sendHeader("Location", "/");
  server.send(303);
}

void handleGetTime() {
  DateTime now = rtc.now();
  char buf[9];
  snprintf(buf, sizeof(buf), "%02d:%02d:%02d", now.hour(), now.minute(), now.second());
  server.send(200, "text/plain", buf);
}

void setup() {
  pinMode(PIN_PUMP, OUTPUT);
  pinMode(PIN_BUZZER, OUTPUT);
  pinMode(PIN_BUTTON, INPUT_PULLUP);

  digitalWrite(PIN_PUMP, LOW);
  digitalWrite(PIN_BUZZER, LOW);

  Wire.begin(21, 22);
  rtc.begin();

  SPIFFS.begin(true);

  // Load persistent configurations
  prefs.begin("alarm", false);
  alarmHour = prefs.getInt("hour", 7);
  alarmMinute = prefs.getInt("min", 0);
  alarmEnabled = prefs.getBool("en", true);

  // Initialize I2S Audio Output
  out = new AudioOutputI2S();
  out->SetPinout(PIN_I2S_BCLK, PIN_I2S_LRC, PIN_I2S_DIN);
  out->SetGain(0.9);

  // Configure SoftAP
  WiFi.softAP(apSSID, apPass);
  server.on("/", HTTP_GET, handleRoot);
  server.on("/save", HTTP_POST, handleSave);
  server.on("/time", HTTP_GET, handleGetTime);
  server.begin();
}

void loop() {
  server.handleClient();

  // Keep I2S buffer filled if playing
  if (wav && wav->isRunning()) {
    if (!wav->loop()) {
      startAudio(); // Loop audio file
    }
  }

  // Poll RTC once per second
  if (millis() - lastRtcCheck >= 1000) {
    lastRtcCheck = millis();
    DateTime now = rtc.now();

    if (alarmEnabled && !alarmTriggered) {
      if (now.hour() == alarmHour && now.minute() == alarmMinute && now.second() == 0) {
        alarmTriggered = true;
        startAudio();
      }
    }
  }

  // Active alarm routine
  if (alarmTriggered) {
    // Pulse buzzer every 500 ms
    digitalWrite(PIN_BUZZER, (millis() / 500) % 2);

    // Pulse water pump every 8 seconds for a 250 ms squirt burst
    if (millis() - lastPumpPulse > 8000) {
      lastPumpPulse = millis();
      digitalWrite(PIN_PUMP, HIGH);
      delay(250); 
      digitalWrite(PIN_PUMP, LOW);
    }

    // Dismiss alarm if hardware button is pressed (active low)
    if (digitalRead(PIN_BUTTON) == LOW) {
      delay(50); // Debounce
      if (digitalRead(PIN_BUTTON) == LOW) {
        alarmTriggered = false;
        digitalWrite(PIN_BUZZER, LOW);
        digitalWrite(PIN_PUMP, LOW);
        stopAudio();
      }
    }
  }
}

4. Uploading Audio to ESP32 SPIFFS

  1. Prepare your audio clip: format it as an uncompressed WAV file (16-bit PCM, 44.1 kHz, Mono). Name the file alarm.wav.

  2. Create a folder named data in your Arduino sketch directory and move alarm.wav inside it.

  3. Use the ESP32 Sketch Data Upload tool in the Arduino IDE to flash the file system into SPIFFS.




No comments:

Post a Comment

Nov 3rd Class- Experimenting with Neopixels; MIDI Prototyping

Above: Kerem makes calculations...  The planned projects seemed to have coalesced into two projects: Project 1(Stanley-Kexin-Rohan) is a clo...