#include <Wire.h>
#include <LiquidCrystal_I2C.h>
#include <RTClib.h>
#include <ESP32Servo.h>
#include <WiFi.h>
#include "ThingSpeak.h"
// ---------------- Pins ----------------
#define BUZZER_PIN 25
#define BTN_MANUAL 26 // Manual open all button pin
#define BTN_ACK 27
int SERVO_PINS[4] = {14, 12, 13, 15};
// ---------------- Devices --------------
LiquidCrystal_I2C lcd(0x27, 16, 2);
RTC_DS3231 rtc;
Servo servos[4];
// ---------------- WiFi & ThingSpeak ----
const char* ssid = "Wokwi-GUEST";
const char* password = "";
WiFiClient client;
unsigned long myChannelNumber = 3048833;
const char * myWriteAPIKey = "JWPYIA560IAC0D7Q";
// ---------------- Tablets per slot -----
int tabletsPerSlot[4] = {1, 2, 3, 2}; // tablets per slot
// ---------------- Schedule struct & times ----
struct MedTime {
uint8_t hour;
uint8_t minute;
};
#define TIMES_PER_SLOT 3
MedTime schedule[4][TIMES_PER_SLOT] = {
{ {6,0}, {18,0}, {0,0} },
{ {12,0}, {21,45}, {0,0} },
{ {17,43}, {21,54}, {0,0} },
{ {21,54}, {0,0}, {0,0} }
};
bool triggeredThisMinute[4][TIMES_PER_SLOT];
int lastMinuteSeen = -1;
// ---------------- Course Plan ----------
int totalCourseDays = 7; // <-- set course length (days)
int currentDay = 1; // start at Day 1
bool courseCompleted = false; // flag when course ends
// ---------------- Tablet Tracking ------
int initialTabletStock = 100; // <-- manually enter how many tablets loaded
int dispensedCount = 0;
int remainingTablets;
// ---------------- Functions ------------
void beep(int n=1, int duration=500) {
for (int i=0; i<n; i++) {
digitalWrite(BUZZER_PIN, HIGH);
delay(duration);
digitalWrite(BUZZER_PIN, LOW);
delay(200);
}
}
bool buttonPressed(int pin) {
return digitalRead(pin) == LOW;
}
void sendToThingSpeak(int slot, int tablets, bool taken) {
ThingSpeak.setField(1, slot+1);
ThingSpeak.setField(2, tablets);
ThingSpeak.setField(3, taken ? 1 : 0);
int x = ThingSpeak.writeFields(myChannelNumber, myWriteAPIKey);
if (x == 200) {
Serial.println("ThingSpeak update successful.");
} else {
Serial.print("ThingSpeak update failed. HTTP error code ");
Serial.println(x);
}
}
void openDose(int idx) {
lcd.clear();
lcd.setCursor(0,0);
lcd.print("Slot ");
lcd.print(idx+1);
lcd.print(" Open");
lcd.setCursor(0,1);
lcd.print("Take ");
lcd.print(tabletsPerSlot[idx]);
lcd.print(" Tablet");
if (tabletsPerSlot[idx] > 1) lcd.print("s");
beep(3, 300);
servos[idx].write(90); // open slot
unsigned long start = millis();
bool ack = false;
while (millis() - start < 15000) { // 15 sec timeout
if (buttonPressed(BTN_ACK)) {
ack = true;
break;
}
delay(50);
}
servos[idx].write(0); // close slot
lcd.clear();
if (ack) {
lcd.print("Dose Taken: ");
lcd.print(tabletsPerSlot[idx]);
sendToThingSpeak(idx, tabletsPerSlot[idx], true);
// update counters
dispensedCount += tabletsPerSlot[idx];
remainingTablets = initialTabletStock - dispensedCount;
} else {
lcd.print("Dose Missed!");
sendToThingSpeak(idx, tabletsPerSlot[idx], false);
}
delay(3000);
}
void checkSchedule(DateTime now) {
if (courseCompleted) return; // stop if course finished
if (now.minute() != lastMinuteSeen) {
for (int i=0; i<4; i++) {
for (int j=0; j<TIMES_PER_SLOT; j++) {
triggeredThisMinute[i][j] = false;
}
}
lastMinuteSeen = now.minute();
}
for (int i=0; i<4; i++) {
for (int j=0; j<TIMES_PER_SLOT; j++) {
if (!triggeredThisMinute[i][j] &&
now.hour() == schedule[i][j].hour &&
now.minute() == schedule[i][j].minute) {
triggeredThisMinute[i][j] = true;
openDose(i);
}
}
}
// Check end of day at 23:59
if (now.hour() == 23 && now.minute() == 59 && now.second() == 50) {
currentDay++;
if (currentDay > totalCourseDays) {
courseCompleted = true;
lcd.clear();
lcd.print("Course Finished!");
// final stats
for (int i=0; i<15; i++) {
lcd.clear();
lcd.setCursor(0,0);
lcd.print("Dispensed:");
lcd.print(dispensedCount);
lcd.setCursor(0,1);
lcd.print("Remain:");
lcd.print(remainingTablets);
beep(1, 700);
delay(1500);
}
} else {
lcd.clear();
lcd.print("Day ");
lcd.print(currentDay);
lcd.print(" Started");
delay(2000);
}
}
}
// ---------------- Setup ----------------
void setup() {
Serial.begin(115200);
lcd.init();
lcd.backlight();
pinMode(BUZZER_PIN, OUTPUT);
pinMode(BTN_MANUAL, INPUT_PULLUP);
pinMode(BTN_ACK, INPUT_PULLUP);
for (int i=0; i<4; i++) {
servos[i].attach(SERVO_PINS[i]);
servos[i].write(0);
}
if (!rtc.begin()) {
lcd.print("RTC ERROR!");
while(1);
}
// WiFi setup
WiFi.begin(ssid, password);
lcd.clear();
lcd.print("Connecting WiFi");
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
lcd.clear();
lcd.print("WiFi Connected");
delay(2000);
ThingSpeak.begin(client);
// Initialize counters
dispensedCount = 0;
remainingTablets = initialTabletStock;
lcd.clear();
lcd.print("Day 1 Started");
delay(2000);
lcd.clear();
}
// ---------------- Loop -----------------
void loop() {
DateTime now = rtc.now();
lcd.setCursor(0,0);
char buf[17];
snprintf(buf, sizeof(buf), "%02d:%02d:%02d", now.hour(), now.minute(), now.second());
lcd.print(buf);
if (!courseCompleted) {
if (buttonPressed(BTN_MANUAL)) {
lcd.clear();
lcd.print("Manual: Open All");
for (int i=0; i<4; i++) servos[i].write(90);
delay(10000);
for (int i=0; i<4; i++) servos[i].write(0);
lcd.clear();
lcd.print("All Closed");
delay(2000);
}
checkSchedule(now);
} else {
lcd.setCursor(0,1);
lcd.print("Plan Over!");
}
delay(500);
}