/*
* Project: IoT-Based Smart Pill Dispenser (UTeM Diploma Project)
* Corrected Version: Logic-locked and Schematic-aligned
* Student: LUA WEN JING
*/
#define BLYNK_TEMPLATE_ID "TMPL6euSk1FWM"
#define BLYNK_TEMPLATE_NAME "smart pill dispenser"
#define BLYNK_AUTH_TOKEN "MaXSqpOc_pefA26yPH_d6ZwaNCsfvuIn"
#include <WiFi.h>
#include <BlynkSimpleEsp32.h>
#include <DHT.h>
#include <ESP32Servo.h>
#include <LiquidCrystal_I2C.h>
#include "time.h"
// --- Pin Map (Matches your final EasyEDA Schematic) ---
const int BUTTON_PIN = 5;
const int IR_PIN = 12; // IR sensor for intake verification
const int BUZZER_PIN = 4;
const int LED_PIN = 2;
const int DHTPIN = 15;
const int TRIG_PIN = 14;
const int ECHO_PIN = 27;
const int SERVO_PIN = 13;
#define DHTTYPE DHT22
// --- Schedule Configuration ---
const int scheduleHour = 8; // Medication at 08:00 AM
const int scheduleMinute = 0;
// --- Objects ---
DHT dht(DHTPIN, DHTTYPE);
Servo myservo;
LiquidCrystal_I2C lcd(0x27, 16, 2);
BlynkTimer timer;
// --- System State Variables ---
bool isAlarming = false;
bool doseTaken = false;
bool dispensedToday = false; // Prevents multiple triggering in the same minute
unsigned long alarmStartTime = 0;
bool missedDoseSent = false;
// --- Network & Time Config ---
char ssid[] = "Wokwi-GUEST";
char pass[] = "";
const char* ntpServer = "pool.ntp.org";
const long gmtOffset_sec = 28800; // Malaysia GMT+8
void setup() {
Serial.begin(115200);
// 1. SYSTEM INITIALIZATION (Proposal Page 9, Step 1)
pinMode(BUTTON_PIN, INPUT_PULLUP);
pinMode(IR_PIN, INPUT_PULLUP);
pinMode(BUZZER_PIN, OUTPUT);
pinMode(LED_PIN, OUTPUT);
pinMode(TRIG_PIN, OUTPUT);
pinMode(ECHO_PIN, INPUT);
dht.begin();
myservo.attach(SERVO_PIN);
myservo.write(0); // Start in locked position
lcd.init();
lcd.backlight();
lcd.print("UTeM IoT Smart");
lcd.setCursor(0, 1);
lcd.print("Pill Dispenser");
Blynk.begin(BLYNK_AUTH_TOKEN, ssid, pass);
configTime(gmtOffset_sec, 0, ntpServer);
// Setup Sensor Update Timer
timer.setInterval(2000L, processSensors);
}
// 2. DATA ACQUISITION & 3. UPLOAD TO CLOUD (Proposal Page 9, Step 2 & 3)
void processSensors() {
float temp = dht.readTemperature();
float hum = dht.readHumidity();
// Stock Level (Ultrasonic)
digitalWrite(TRIG_PIN, LOW); delayMicroseconds(2);
digitalWrite(TRIG_PIN, HIGH); delayMicroseconds(10);
digitalWrite(TRIG_PIN, LOW);
long duration = pulseIn(ECHO_PIN, HIGH);
int stock_cm = duration * 0.034 / 2;
// Real-time Update to Blynk Dashboard
Blynk.virtualWrite(V0, temp);
Blynk.virtualWrite(V1, hum);
Blynk.virtualWrite(V2, stock_cm);
// Local Monitoring (LCD)
lcd.setCursor(0, 0);
lcd.print("T:"); lcd.print(temp, 0); lcd.print("C ");
lcd.print("Stock:"); lcd.print(stock_cm); lcd.print("cm ");
// Reset daily flag at midnight
struct tm timeinfo;
if (getLocalTime(&timeinfo)) {
if (timeinfo.tm_hour == 0 && timeinfo.tm_min == 0) {
dispensedToday = false;
}
}
}
// 4. DECISION & 5. ACTIVATE OUTPUTS (Proposal Page 9, Step 4 & 5)
void checkTimeAndDispense() {
struct tm timeinfo;
if (!getLocalTime(&timeinfo)) return;
if (timeinfo.tm_hour == scheduleHour && timeinfo.tm_min == scheduleMinute) {
if (!dispensedToday) {
dispensedToday = true;
executeDispense();
}
}
}
void executeDispense() {
lcd.clear();
lcd.print("TIME FOR PILLS!");
myservo.write(180); // Open/Rotate
delay(2000);
myservo.write(0); // Close
isAlarming = true;
doseTaken = false;
missedDoseSent = false;
alarmStartTime = millis();
digitalWrite(BUZZER_PIN, HIGH);
digitalWrite(LED_PIN, HIGH);
Blynk.logEvent("medication_alert", "Medication Dispensed! Please take your pill.");
}
void loop() {
Blynk.run();
timer.run();
checkTimeAndDispense();
// 6. INTAKE VERIFICATION (Proposal Page 9, Step 6)
if (isAlarming) {
// Check if Pill was removed (IR goes HIGH) or Button was pressed
if (digitalRead(IR_PIN) == HIGH || digitalRead(BUTTON_PIN) == LOW) {
isAlarming = false;
doseTaken = true;
digitalWrite(BUZZER_PIN, LOW);
digitalWrite(LED_PIN, LOW);
lcd.setCursor(0, 1);
lcd.print("DOSE TAKEN: OK ");
Blynk.logEvent("medication_taken", "Patient has taken the dose.");
}
// 10 Minute Timeout (600,000 ms)
if (!doseTaken && (millis() - alarmStartTime > 600000)) {
if (!missedDoseSent) {
Blynk.logEvent("missed_dose_alert", "CRITICAL: Dose not taken after 10 mins!");
lcd.setCursor(0, 1);
lcd.print("ALARM: MISSED! ");
missedDoseSent = true;
// Buzzer stays ON as per Page 9 logic
}
}
}
}