Arduino Easy Smart Home Series: Medicine Reminder & Smart Dispenser (RTC + ESP32 Alerts)
Ardunova Maker Series: Medicine Reminder & Smart Dispenser (RTC + ESP32 Alerts)
Affordable smart dispenser that drops medicine at scheduled times (RTC), detects whether it's taken (IR sensor), and if not taken within a timeout, the ESP32 broadcasts an alert across the local Wi-Fi (UDP broadcast).
Table of Contents
- Introduction
- Required Hardware
- How It Works
- Arduino IDE Setup
- Wiring
- ESP32 Code (RTC + Dispenser + UDP Alert)
- How to Receive Alerts on Devices
- Testing
- Advanced Mods
- Troubleshooting
- Learning Insights
- Conclusion
1. Introduction
Automatic reminders + physical dispenser for pills. Uses DS3231 RTC for reliable timekeeping. At scheduled times a servo actuates the dispenser to present the pill. An IR break-beam or reflective sensor watches the pill position; if the pill is not taken within a configurable timeout (e.g., 2 minutes), the ESP32 sends a UDP broadcast alert to the LAN so any listening device can show a notification.
2. Required Hardware
- ESP32 development board (NodeMCU-32S or similar)
- DS3231 RTC module (I2C)
- Micro servo (SG90) or small dispenser mechanism
- IR break-beam sensor or IR reflective sensor / microswitch
- 5V servo supply (external recommended)
- Jumper wires, small breadboard
- Optional: buzzer, status LED
3. How It Works
- ESP32 keeps Wi-Fi connected and reads DS3231 time.
- User defines schedule(s) in code (or via web UI later).
- At schedule: servo moves to dispense and returns.
- If IR doesn't detect "taken" within TIMEOUT, ESP32 broadcasts UDP alert on port 4210.
- Local listeners (phone/PC/other ESP) receive JSON alert and notify user.
4. Arduino IDE Setup
- Install Arduino IDE (or VSCode + PlatformIO).
- Add ESP32 board support (Espressif) to Boards Manager.
- Install libraries: RTClib, Wire, Servo, WiFi, WiFiUdp.
- Select board: Tools → Board → "ESP32 Dev Module".
5. Wiring
- RTC: SDA → GPIO21, SCL → GPIO22 (ESP32 default I2C). Power per module spec (3.3V/5V) and common GND.
- Servo signal → GPIO18 (or any PWM pin). Servo VCC → 5V external supply. Common GND required.
- IR receiver digital out → GPIO15 (or any digital input). IR transmitter per module spec.
- Buzzer (optional) → GPIO19 via transistor if high current.
6. ESP32 Code (RTC + Dispenser + UDP Alert)
Paste this into Arduino IDE. All '<' and '>' are escaped so Blogger won't break the code block. Replace YOUR_SSID and YOUR_PASSWORD, and calibrate servo angles & sensor logic.
#include <Wire.h>
#include "RTClib.h"
#include <WiFi.h>
#include <WiFiUdp.h>
#include <Servo.h>
///// CONFIG /////
const char* ssid = "YOUR_SSID";
const char* password = "YOUR_PASSWORD";
const int UDP_PORT = 4210;
RTC_DS3231 rtc;
WiFiUDP Udp;
Servo dispenser;
const int servoPin = 18; // servo signal pin
const int irPin = 15; // IR receiver digital out (LOW when beam broken or HIGH depending on module)
const int ledPin = 2; // status LED (onboard)
const unsigned long TAKE_TIMEOUT = 120000UL; // 2 minutes timeout
// Define schedule: {hour, minute}. Add multiple entries.
const int NUM_SCHEDULES = 3;
int schedules[NUM_SCHEDULES][2] = {
{8, 0}, // 08:00
{13, 0}, // 13:00
{20, 0} // 20:00
};
///// END CONFIG /////
unsigned long lastDispenseMillis = 0;
bool waitingForTake = false;
unsigned long dispenseStartMillis = 0;
void setup() {
Serial.begin(115200);
pinMode(irPin, INPUT);
pinMode(ledPin, OUTPUT);
digitalWrite(ledPin, LOW);
// init servo
dispenser.attach(servoPin);
dispenser.write(0); // locked/idle position
// RTC init
Wire.begin();
if(!rtc.begin()) {
Serial.println("Couldn't find RTC");
while(1);
}
if (rtc.lostPower()) {
Serial.println("RTC lost power, set the time!");
// rtc.adjust(DateTime(F(__DATE__), F(__TIME__))); // optional: set compile time
}
// connect WiFi
WiFi.mode(WIFI_STA);
WiFi.begin(ssid, password);
Serial.print("Connecting WiFi");
int wifiAttempts = 0;
while (WiFi.status() != WL_CONNECTED && wifiAttempts < 30) {
delay(500);
Serial.print(".");
wifiAttempts++;
}
Serial.println();
if (WiFi.status() == WL_CONNECTED) {
Serial.print("WiFi connected. IP: ");
Serial.println(WiFi.localIP());
Udp.begin(UDP_PORT);
} else {
Serial.println("WiFi failed to connect. Alerts disabled.");
}
}
String makeAlertPayload(const char* type, const char* msg){
DateTime now = rtc.now();
char buf[200];
snprintf(buf, sizeof(buf),
"{\"type\":\"%s\",\"msg\":\"%s\",\"ts\":\"%04d-%02d-%02d %02d:%02d:%02d\",\"ip\":\"%s\"}",
type, msg,
now.year(), now.month(), now.day(), now.hour(), now.minute(), now.second(),
WiFi.localIP().toString().c_str()
);
return String(buf);
}
void broadcastAlert(const char* type, const char* msg) {
if (WiFi.status() != WL_CONNECTED) {
Serial.println("Not on WiFi: can't send alert");
return;
}
String payload = makeAlertPayload(type, msg);
Serial.print("Broadcasting: "); Serial.println(payload);
IPAddress broadcastIp = ~WiFi.subnetMask() | WiFi.gatewayIP(); // get broadcast
// fallback to 255.255.255.255 if above is wrong:
Udp.beginPacket(broadcastIp, UDP_PORT);
Udp.write(payload.c_str());
Udp.endPacket();
// also send to 255.255.255.255 (some networks require this)
Udp.beginPacket(IPAddress(255,255,255,255), UDP_PORT);
Udp.write(payload.c_str());
Udp.endPacket();
}
void dispenseOnce(){
Serial.println("Dispensing pill...");
digitalWrite(ledPin, HIGH);
// Move servo to dispense position (adjust angles to your mechanism)
dispenser.write(90);
delay(700); // adjust for your dispenser
dispenser.write(0);
delay(300);
digitalWrite(ledPin, LOW);
waitingForTake = true;
dispenseStartMillis = millis();
lastDispenseMillis = millis();
}
bool checkScheduleAndTrigger(){
DateTime now = rtc.now();
for(int i=0;i<NUM_SCHEDULES;i++){
if(now.hour() == schedules[i][0] && now.minute() == schedules[i][1] && now.second() == 0){
// avoid double trigger: ensure last dispense was not the same minute
if(millis() - lastDispenseMillis > 60000UL){
return true;
}
}
}
return false;
}
bool pillTaken(){
// depends on sensor: adjust logic for your module
// For typical break-beam IR module: when beam is broken, sensor might read LOW or HIGH.
// Calibrate in your hardware. Here we assume DIGITAL LOW means beam broken (pill removed).
int v = digitalRead(irPin);
// Serial.printf("IR=%d\n", v);
// If using reflective sensor or switch, invert logic accordingly.
if(v == LOW) return true;
return false;
}
void loop() {
// check schedule
if(!waitingForTake && checkScheduleAndTrigger()){
dispenseOnce();
}
// if waiting for take, monitor sensor and timeout
if(waitingForTake){
if(pillTaken()){
Serial.println("Pill taken detected.");
waitingForTake = false;
// optional: send confirmation broadcast
broadcastAlert("TAKEN", "Medicine taken on time.");
} else {
// not yet taken: check timeout
if(millis() - dispenseStartMillis > TAKE_TIMEOUT){
waitingForTake = false;
Serial.println("Pill NOT taken: timeout. Sending alert.");
broadcastAlert("MISSED", "Medicine not taken within timeout!");
// you can also retry dispense or blink LED or escalate
}
}
}
// small delay
delay(200);
}
7. How to Receive Alerts on Devices
ESP32 broadcasts UDP packets on port 4210. Devices on same Wi-Fi can listen. Example listeners: Android UDP app, small Python script on PC, or another ESP/ESP32.
Simple Python UDP listener (for PC)
# Python 3 UDP listener example
import socket
PORT = 4210
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
sock.bind(("0.0.0.0", PORT))
print("Listening for alerts on port", PORT)
while True:
data, addr = sock.recvfrom(1024)
print("Alert from", addr, ":", data.decode())
8. Testing
- Power ESP32 and confirm RTC time; set RTC once if needed.
- Set schedules a minute ahead for quick test.
- Upload code, open serial monitor, run a UDP listener on PC/phone.
- At scheduled time servo should move and dispenser activate.
- If you break the IR beam (take pill) listener gets "TAKEN"; if not, after timeout listener gets "MISSED".
9. Advanced Mods
- Web UI on ESP32 to set schedules and timeout from browser.
- MQTT integration to Home Assistant or local broker.
- ESP32-CAM capture on missed doses (privacy warning).
- Multiple compartments with servo selector or stepper motor.
- Battery backup + low-power sleep between checks.
10. Troubleshooting
- No Wi-Fi: Check SSID/password; use 2.4GHz; move board closer to router.
- RTC wrong: Replace CR2032; set time with rtc.adjust()
- Servo jitter: Use separate 5V supply, common ground, add capacitor across servo Vcc/GND.
- IR false: Reposition sensor or use microswitch/weight sensor for reliability.
- UDP blocked: Try 255.255.255.255 broadcast (code sends both) or use MQTT/HTTP.
11. Learning Insights
- Combine RTC, networking, and mechanical actuation.
- UDP broadcast is a lightweight local alert mechanism.
- Sensor choice affects reliability—test with real pills and mechanism.
- Keep alerts local if privacy is a concern; cloud services optional.
12. Conclusion
This medicine reminder + smart dispenser is practical and affordable. Start with a single dispenser + ESP32 + DS3231, then expand with web UI, MQTT, or multiple compartments. Calibrate sensors and servo angles for your hardware. Replace Wi-Fi alerts with MQTT or cloud push services if broadcasts are blocked by router.
By Kaushal Haladi
Comments
Post a Comment