/*
* Vend-Aid — Automated Medicine Dispenser
* Team 16, CS145 IoT Cup X
*
* Simulation notes (Wokwi):
* - GM861S QR scanner → green pushbutton (GPIO 2)
* - ESP32-Cam → blue pushbutton (GPIO 19)
* - 10 DC coil motors → 10 servo motors
* - MOSIP + backend → mock prescription array
*
* Pin map:
* D13,D12,D4,D15,D5,D18,D23,D32,D35,D34 → motors 0-9
* D14 → HC-SR04 TRIG | D27 → HC-SR04 ECHO
* D21 → LCD SDA | D22 → LCD SCL
* D2 → QR button | D19 → Cam button
* D25 → LED green | D26 → LED red | D33 → LED yellow
*/
#include <Wire.h>
#include <LiquidCrystal_I2C.h>
#include <ESP32Servo.h>
// ── Medicine slot names ────────────────────────────────────────
const char* MED[10] = {
"Losartan", // slot 0
"Amlodipine", // slot 1
"Metformin", // slot 2
"Simvastatin", // slot 3
"Medicine 5", // slot 4
"Medicine 6", // slot 5
"Medicine 7", // slot 6
"Medicine 8", // slot 7
"Medicine 9", // slot 8
"Medicine 10" // slot 9
};
// ── Motor GPIO pins (one per slot) ────────────────────────────
const int MOTOR_PINS[10] = { 13, 12, 4, 15, 5, 18, 23, 32, 35, 34 };
// ── Other pins ────────────────────────────────────────────────
#define TRIG_PIN 14
#define ECHO_PIN 27
#define BTN_QR 2
#define BTN_CAM 19
#define LED_OK 25
#define LED_FAIL 26
#define LED_DISP 33
// ── Peripherals ───────────────────────────────────────────────
LiquidCrystal_I2C lcd(0x27, 16, 2);
Servo motors[10];
// ── Mock prescription ─────────────────────────────────────────
// In the real system this is fetched from the backend after
// MOSIP authentication. Edit this array to test different combos.
bool prescription[10] = {
true, // Losartan
true, // Amlodipine
false, // Metformin
true, // Simvastatin
false, false, false, false, false, false
};
// ── State machine ─────────────────────────────────────────────
enum State {
IDLE,
QR_DONE,
CAM_DONE,
AUTHENTICATING,
DISPENSING,
DONE,
ERRORED
};
State state = IDLE;
unsigned long stateTimer = 0;
int currentSlot = 0;
int totalMeds = 0;
int dispensedCount = 0;
// ── Helpers ───────────────────────────────────────────────────
void lcdPrint(const char* l1, const char* l2 = "") {
lcd.clear();
lcd.setCursor(0, 0); lcd.print(l1);
lcd.setCursor(0, 1); lcd.print(l2);
}
void setLeds(bool ok, bool fail, bool disp) {
digitalWrite(LED_OK, ok ? HIGH : LOW);
digitalWrite(LED_FAIL, fail ? HIGH : LOW);
digitalWrite(LED_DISP, disp ? HIGH : LOW);
}
float readDistance() {
digitalWrite(TRIG_PIN, LOW);
delayMicroseconds(2);
digitalWrite(TRIG_PIN, HIGH);
delayMicroseconds(10);
digitalWrite(TRIG_PIN, LOW);
long dur = pulseIn(ECHO_PIN, HIGH, 30000);
return dur * 0.034 / 2.0;
}
int countMeds() {
int n = 0;
for (int i = 0; i < 10; i++) if (prescription[i]) n++;
return n;
}
void waitForRelease(int pin) {
while (digitalRead(pin) == LOW) delay(10);
delay(50); // debounce
}
// ── Setup ─────────────────────────────────────────────────────
void setup() {
Serial.begin(115200);
Serial.println("=== Vend-Aid booting ===");
pinMode(TRIG_PIN, OUTPUT);
pinMode(ECHO_PIN, INPUT);
pinMode(BTN_QR, INPUT_PULLUP);
pinMode(BTN_CAM, INPUT_PULLUP);
pinMode(LED_OK, OUTPUT);
pinMode(LED_FAIL, OUTPUT);
pinMode(LED_DISP, OUTPUT);
for (int i = 0; i < 10; i++) {
motors[i].attach(MOTOR_PINS[i]);
motors[i].write(0); // all slots closed
}
lcd.init();
lcd.backlight();
lcdPrint(" Vend-Aid v1.0", " Scan your ID");
setLeds(false, false, false);
Serial.println("[READY] Press GREEN button to simulate QR scan.");
}
// ── Loop ──────────────────────────────────────────────────────
void loop() {
switch (state) {
// ── Step 1: Wait for QR scan ───────────────────────────
case IDLE:
if (digitalRead(BTN_QR) == LOW) {
waitForRelease(BTN_QR);
Serial.println("[QR] National ID scanned.");
lcdPrint("ID Scanned!", "Look at camera");
state = QR_DONE;
}
break;
// ── Step 2: Wait for face capture ─────────────────────
case QR_DONE:
if (digitalRead(BTN_CAM) == LOW) {
waitForRelease(BTN_CAM);
Serial.println("[CAM] Face captured.");
lcdPrint("Verifying...", "Please wait");
setLeds(false, false, false);
stateTimer = millis();
state = AUTHENTICATING;
}
break;
// ── Step 3: Simulated MOSIP auth (2s delay) ────────────
case AUTHENTICATING:
if (millis() - stateTimer < 2000) break;
{
// ── In the real system: send QR data + face image
// to backend, receive auth result + prescription.
// Here we mock the result.
bool authOk = true; // set false to test rejection flow
if (!authOk) {
Serial.println("[AUTH] FAILED — patient ineligible.");
setLeds(false, true, false);
lcdPrint("Not eligible", "See health staff");
stateTimer = millis();
state = ERRORED;
break;
}
totalMeds = countMeds();
if (totalMeds == 0) {
Serial.println("[AUTH] OK — no medicines due this month.");
lcdPrint("No meds due", "this month");
stateTimer = millis();
state = DONE;
break;
}
Serial.print("[AUTH] OK. Medicines to dispense: ");
Serial.println(totalMeds);
setLeds(true, false, false);
lcdPrint("Auth OK!", "Starting...");
delay(800);
currentSlot = 0;
dispensedCount = 0;
state = DISPENSING;
}
break;
// ── Step 4: Dispense each prescribed slot ──────────────
case DISPENSING:
setLeds(false, false, true);
// Skip slots not in prescription
while (currentSlot < 10 && !prescription[currentSlot]) {
currentSlot++;
}
// All slots processed — session complete
if (currentSlot >= 10) {
char buf[17];
snprintf(buf, sizeof(buf), "Got %d/%d meds!", dispensedCount, totalMeds);
lcdPrint("All done!", buf);
Serial.print("[DONE] Dispensed ");
Serial.print(dispensedCount);
Serial.print(" of ");
Serial.print(totalMeds);
Serial.println(" medicines.");
setLeds(true, false, false);
stateTimer = millis();
state = DONE;
break;
}
// Dispense current slot
{
char line2[17];
snprintf(line2, sizeof(line2), "%d/%d: %s",
dispensedCount + 1, totalMeds, MED[currentSlot]);
lcdPrint("Dispensing...", line2);
Serial.print("[SLOT ");
Serial.print(currentSlot);
Serial.print("] Dispensing ");
Serial.println(MED[currentSlot]);
// Rotate motor to push medicine out
motors[currentSlot].write(90);
delay(800);
motors[currentSlot].write(0);
delay(500);
// Ultrasonic confirmation
float dist = readDistance();
if (dist > 0 && dist < 10.0) {
Serial.println(" -> Confirmed in tray.");
dispensedCount++;
} else {
Serial.print(" -> Not detected (dist=");
Serial.print(dist);
Serial.println("cm). Possible jam — logged.");
// Real system: flag for staff, do not decrement stock
}
currentSlot++;
}
break;
// ── Step 5: Session complete ───────────────────────────
case DONE:
if (millis() - stateTimer >= 5000) {
setLeds(false, false, false);
lcdPrint(" Vend-Aid v1.0", " Scan your ID");
currentSlot = 0;
dispensedCount = 0;
totalMeds = 0;
state = IDLE;
Serial.println("[RESET] Ready for next patient.");
}
break;
// ── Error / rejection ──────────────────────────────────
case ERRORED:
if (millis() - stateTimer >= 5000) {
setLeds(false, false, false);
lcdPrint(" Vend-Aid v1.0", " Scan your ID");
state = IDLE;
Serial.println("[RESET] Error cleared. Ready.");
}
break;
}
delay(50);
}