- Microcontroller
- Real-time Clock Module
- Power Supply
- LCD Display
- Buttons
- Buzzer
- Speaker
- Water Pump
- Motor Controller
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
2. Wiring & Pin Mapping
+-------------------------------------------+
| 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):
VCC3.3VGNDGNDSDAGPIO 21SCLGPIO 22SQWGPIO 27 (Configured as input with pull-up for hardware alarm interrupts)
MAX98357A (I2S):
VIN5V (yields highest audio wattage)GNDGNDBCLK(Bit Clock) GPIO 26LRC(Word Select / WS) GPIO 25DIN(Serial Data) GPIO 33GAINGND (sets default gain to 9dB)SD_MODELeft 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:
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.Clock Synchronization: At setup, read the DS3231 to establish local time. The alarm can be scheduled using DS3231 Alarm 1 registers.
Trigger Sequence:
Stage 1 (Audio Alert): Initialize the I2S output and stream the WAV file via
AudioGeneratorWAVandAudioOutputI2S.Stage 2 (Buzzer Accent): Pulse GPIO 19 periodically to cut through ambient noise.
Stage 3 (Squirt Sequence): Set GPIO 23
HIGHfor 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.
#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
+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
Storage (
Preferences.h): Saves alarm configuration (Hour, Minute, Enabled) in non-volatile flash so settings persist across reboots.Access Point & Web Server (
WiFi.h&WebServer.h): Spins up a local SoftAP namedAlarm-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.Audio Pipeline (
ESP8266Audio): Uses asynchronous I2S output to drive the MAX98357A without blocking time checks.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 27is pressed.
3. ESP32 Implementation Code
Install the following libraries in Arduino IDE before compiling:
RTClib by Adafruit
ESP8266Audio by Earle F. Philhower, III
#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
Prepare your audio clip: format it as an uncompressed WAV file (16-bit PCM, 44.1 kHz, Mono). Name the file
alarm.wav.Create a folder named
datain your Arduino sketch directory and movealarm.wavinside it.Use the ESP32 Sketch Data Upload tool in the Arduino IDE to flash the file system into SPIFFS.


No comments:
Post a Comment