Mark Lewis
Created November 8, 2019 © CC BY-NC-ND

PillMinder

Insure a senior takes the right medication at the right time by monitoring and controlling medication intake with caregiver notifications.

AdvancedFull instructions provided24 hours40
PillMinder

Things used in this project

Hardware components

Custom PC Board
×1
Photon
Particle Photon
×1
Adafruit 16-Channel 12-bit PWM/Servo Shield - I2C interface
Adafruit 16-Channel 12-bit PWM/Servo Shield - I2C interface
×1
3 mm LED: Green
3 mm LED: Green
×7
Buzzer
Buzzer
×1
Capacitive switch
×1
micro servo GH37A
×7

Software apps and online services

Blynk
Blynk
Used for UI
SMS Messaging API
Twilio SMS Messaging API
Used for SMS messaging
Heroku Dynos
Heroku Dynos
Connector between Particle Cloud and Twillio

Hand tools and fabrication machines

Raise 3D N2 Printer
3D print used to make parts

Story

Read more

Custom parts and enclosures

Base

Cover

Lid

Lens

Schematics

Schematic

Code

Photon Code

C/C++
SYSTEM_THREAD(ENABLED);
// This #include statement was automatically added by the Particle IDE.
#include <blynk.h>
#include <Adafruit_PWMServoDriver.h>
// called this way, it uses the default address 0x40
Adafruit_PWMServoDriver pwm = Adafruit_PWMServoDriver();
/* Function prototypes -------------------------------------------------------*/
#define BLYNK_PRINT Serial
#define SERVOMIN  150 // this is the 'minimum' pulse length count (out of 4096)
#define SERVOMAX  600 // this is the 'maximum' pulse length count (out of 4096)

char auth[] = "QnbZ5V15eDpxUK_o9cjSouJ_BdwNsG3r";

int previousState = 0;
int lastSync;
int ONE_DAY_MILLIS = (24 * 60 * 60 * 1000);
int alarmTime = 0;
int currentTimeInSecs ;
int LEDday[] = {0,A1, A2, A3, A4, A5, A6, A7};//Lights
int lid[] = {0,0,0,0,0,0,0,0};//lid status
// int lidDay[]= {0,D1, D2, D3, D4, D5, D6, D7};//switches
int pFull[] = {0,0,0,0,0,0,0,0}; //Pill status
int speaker = D2;//Speaker pin
int button = D3;
int takePills =0;
int openLid = 0;
int lidSpeed = 2;
char LEDdayNow;
int tTaken = 0;
int localTimeZone =0;
int clientAC =0;
int clientNumber =0;
int care1AC = 0;
int care1Number =0;
int care2AC = 0;
int care2Number =0;
int refillBox = 0;
int buttonStatus = 0;
int pillsRemaining;
int reminderTime = 15; //time in minutes
int careWarningTime = 15;
int reminderNum = 1;
int creminderNum = 1;
int pillMin = 0;
int audibleAlerts = 0;
int today = 0;
int second = 0;
int minute = 0;
int hour = 0;
int day = 0 ;
int refillToday = 0; //prevents box from alerting on the day a refill was done
int i = 0; //counter
int vFID;
int vEID;
int vUID;
char SMS[32]; //for sneding text info
char SMScare1[32];
char SMScare2[32];
char message[60];
char boxName[12];
char deviceName[20];
String deviceEN;
String deviceN;
String dayOfWeek[8];
//BLYNK Functions
BLYNK_WRITE(V0) {
   String deviceEN = param[0].asStr();  //From Blynk
   strcpy (deviceName, deviceEN);
   EEPROM.put(300, deviceName) ;
   Serial.print("Name Set to: ");
   Serial.println(deviceName);
}
BLYNK_WRITE(V1) {
  TimeInputParam t(param);
  int startTimeInSecs = param[0].asInt();
  localTimeZone = t.getTZ_Offset()/3600;
  alarmTime = startTimeInSecs;
  EEPROM.put(0, alarmTime);
  EEPROM.put(10, localTimeZone);
}
BLYNK_WRITE(V2) {
  clientAC = param[0].asInt();
  EEPROM.put(20,clientAC);
}
BLYNK_WRITE(V3) {
  clientNumber = param[0].asInt();
  EEPROM.put(30,clientNumber);
}
BLYNK_WRITE(V4) {
  care1AC = param[0].asInt();
  EEPROM.put(40,care1AC);
}
BLYNK_WRITE(V5) {
  care1Number = param[0].asInt();
  EEPROM.put(50,care1Number);
}
BLYNK_WRITE(V6) {
  care2AC = param[0].asInt();
  EEPROM.put(60,care2AC);
}
BLYNK_WRITE(V7) {
  care2Number = param[0].asInt();
  EEPROM.put(70,care2Number);
}
BLYNK_WRITE(V9){
  pillMin = param[0].asInt();
  EEPROM.put(90,pillMin);
}
BLYNK_WRITE(V10) {
  refillBox = param[0].asInt();
  Serial.println("BLYNK STATUS - Refilling SET ");
  Serial.println(refillBox);
  if (refillBox == 1) {
    if (currentTimeInSecs >= alarmTime) {
      refillToday = 1;
    }
    Blynk.virtualWrite(V8, 0);
    EEPROM.put(110,1);
    EEPROM.put(120,1);
    EEPROM.put(130,1);
    EEPROM.put(140,1);
    EEPROM.put(150,1);
    EEPROM.put(160,1);
    EEPROM.put(170,1);
    int k=0;
    while (k<7) {
        k++;
        pFull[k]=1; 
        vFID = (20 + k);
        vEID = (30 + k);
        vUID = (40 + k);
        Blynk.virtualWrite(vFID, 255);
        Blynk.virtualWrite(vEID, 0);
        Blynk.virtualWrite(vUID, 0);
        // Open all Lids
        for (uint16_t pulselen = SERVOMIN; pulselen < SERVOMAX; pulselen++) {
          pwm.setPWM(k, 0, pulselen);
          delay(lidSpeed);
        }
    }
  }
  if (refillBox == 0) {
      int k=0;
      while (k<7) {
        k++;
        for (uint16_t pulselen = SERVOMAX; pulselen > SERVOMIN; pulselen--) {
        pwm.setPWM(k, 0, pulselen);
        delay(lidSpeed);
        } 
      }   
  }
}
BLYNK_WRITE(V11) {
  audibleAlerts = param[0].asInt();
  EEPROM.put(80,audibleAlerts);
}
BLYNK_WRITE(V12) {
  reminderTime = param[0].asInt();
  EEPROM.put(200,reminderTime);
}
BLYNK_WRITE(V13) {
  careWarningTime = param[0].asInt();
  EEPROM.put(210,careWarningTime);
}
BLYNK_WRITE(V14){
    int k = param[0].asInt();
    if (k == 1){
        activate();
    }
}
BLYNK_WRITE(V51) {
  int k = param[0].asInt();
  if (k == 0) {
      i=1;
      closeRefill();
  }  
  if (k == 1) {
      i=1;
      writeRefill();
  }
   
}
BLYNK_WRITE(V52) {
  int k = param[0].asInt();
  if (k == 1) {
      i=2;
      writeRefill();
  }
  if (k == 0) {
      i=2;
      closeRefill();
  } 
}
BLYNK_WRITE(V53) {
  int k = param[0].asInt();
  if (k == 1) {
      i=3;
      writeRefill();
  }
  if (k == 0) {
      i=3;
      closeRefill();
  } 
}
BLYNK_WRITE(V54) {
  int k = param[0].asInt();
  if (k == 1) {
      i=4;
      writeRefill();
  }
  if (k == 0) {
      i=4;
      closeRefill();
  } 
}
BLYNK_WRITE(V55) {
  int k = param[0].asInt();
  if (k == 1) {
      i=5; 
      writeRefill();
  } 
  if (k == 0) {
      i=5;
      closeRefill();
  } 
}
BLYNK_WRITE(V56) {
  int k = param[0].asInt();
  if (k == 1) {
      i=6;
      writeRefill();
  }
  if (k == 0) {
      i=6;
      closeRefill();
  } 
}
BLYNK_WRITE(V57) {
  int k = param[0].asInt();
  if (k == 1) {
      i=7;
      writeRefill();
  }
  if (k == 0) {
      i=7;
      closeRefill();
  } 
}

void writeRefill() {
      //open the lid
      for (uint16_t pulselen = SERVOMIN; pulselen < SERVOMAX; pulselen++) {
      pwm.setPWM(i, 0, pulselen);
      delay(lidSpeed);
    }
      int l = ( 100 + (i *10));
      EEPROM.put(l,1);
      pillsRemaining = pillsRemaining + 1;
      pFull[i] = 1;
      vFID = (20 + i);
      vEID = (30 + i);
      vUID = (40 + i);
      Blynk.virtualWrite(vFID, 255);
      Blynk.virtualWrite(vEID, 0);
      Blynk.virtualWrite(vUID, 0);
      if (currentTimeInSecs >= alarmTime && i == today) {
          refillToday = 1;
      }
 }

void closeRefill() {
   for (uint16_t pulselen = SERVOMAX; pulselen > SERVOMIN; pulselen--) {
      pwm.setPWM(i, 0, pulselen);
      delay(lidSpeed);
   }   
}

void setup() {
    // Retrive NV Variables
    EEPROM.get(0, alarmTime);
    EEPROM.get(110, pFull[1]);
    EEPROM.get(120, pFull[2]);
    EEPROM.get(130, pFull[3]);
    EEPROM.get(140, pFull[4]);
    EEPROM.get(150, pFull[5]);
    EEPROM.get(160, pFull[6]);
    EEPROM.get(170, pFull[7]);
    EEPROM.get(10, localTimeZone);
    EEPROM.get(20, clientAC);
    EEPROM.get(30, clientNumber);
    EEPROM.get(40, care1AC);
    EEPROM.get(50, care1Number);
    EEPROM.get(60, care2AC);
    EEPROM.get(70, care2Number);
    EEPROM.get(80, audibleAlerts);
    EEPROM.get(90, pillMin);
    EEPROM.get(200, reminderTime);
    EEPROM.get(210, careWarningTime);
    
    const int STRING_BUF_SIZE = 20;
    char stringBuf[STRING_BUF_SIZE];
        EEPROM.get(300, stringBuf);
        strcpy (deviceName, stringBuf);
  
  // LED Lights
  pinMode(LEDday[1], OUTPUT);
  pinMode(LEDday[2], OUTPUT);
  pinMode(LEDday[3], OUTPUT);
  pinMode(LEDday[4], OUTPUT);
  pinMode(LEDday[5], OUTPUT);
  pinMode(LEDday[6], OUTPUT);
  pinMode(LEDday[7], OUTPUT);
  
  pinMode(speaker,OUTPUT);
  
  pinMode(button, INPUT_PULLDOWN);
  attachInterrupt(button, buttonPressed, RISING);



  // Debug console
  Serial.begin(9600);

  tone(speaker,1000,500);

  i=0;
  while (i<7) {
    i++;
    digitalWrite(LEDday[i], HIGH);
  }

  delay(3000); // Allow board to settle
  
  i=0;
  while (i<7) {
    i++;
    digitalWrite(LEDday[i], LOW);
  }
  
  // start and setup servos
  pwm.begin();
  pwm.setPWMFreq(60);  // Analog servos run at ~60 Hz updates
  
  //Close all Lids
  i=0;
  while (i<7) {
      i++;
      pwm.setPWM(i, 0, SERVOMIN);
      delay(100);
  }
  
  Blynk.begin(auth);
  
  pillsRemaining = 0; //calculate pills remaing for initial value
  i = 0; 
  while (i<7) {
    i++;
    if (pFull[i] > 0) {
    pillsRemaining = pillsRemaining + 1;
    }
  // write inital status to app
  i=0;
  while (i<7) {
    i++; 
    vFID = (20 + i);
    vEID = (30 + i);
    vUID = (40 + i);
    if (pFull[i] == 1) {
       Blynk.virtualWrite(vFID, 255);
       Blynk.virtualWrite(vEID, 0);
       Blynk.virtualWrite(vUID, 0);
    }
    if (pFull[i] == 0){
       Blynk.virtualWrite(vFID, 0);
       Blynk.virtualWrite(vEID, 255);
       Blynk.virtualWrite(vUID, 0);
    }
    if (pFull[i] == -1){
       Blynk.virtualWrite(vFID, 0);
       Blynk.virtualWrite(vEID, 0);
       Blynk.virtualWrite(vUID, 255);
    }
  }
  
 }
 
  today = Time.weekday();
  day = today;
 
  Serial.println("Setup Complete");

}

/* This function loops forever --------------------------------------------*/
void loop()
{
    Blynk.run();
    
    updateTime();
    
    updateLog();
    
     // once a day - housekeeping - pills are Low
    if (day != today) {
        day = today;
        refillToday = 0; // clear the flag if box refilled yesterday
    }
    
    delay(5000);
    //Check if time for a pill
    if (pFull[today] != 0 && currentTimeInSecs >= alarmTime && refillToday == 0) {
         pillTime();
    }
    // update BLYNK status display
    Blynk.virtualWrite(15, ((alarmTime - currentTimeInSecs) /60));
   // check if button pressed
   if (buttonStatus ==1) {
       activate();
   }
    //check if reminder needed
    if (takePills == 1) {
        if (currentTimeInSecs >= (alarmTime + (reminderTime * 60 * reminderNum))) {
            pillReminder();
        }
        if (currentTimeInSecs >= (alarmTime + (careWarningTime * 60 * creminderNum))) {
            careWarning();
        }
    }
   
}

void pillsLow() {
    strcpy(boxName, deviceName);
    sprintf (message,"PILLMINDER: %s Pillbox is running low",boxName);
    clientSMS();
    careSMS();// do the SMS thing
    Blynk.virtualWrite(V8, 255);
}

void buttonPressed() {
    buttonStatus = 1;
}

void activate() {
     Serial.println("Button Pushed");
     if (takePills != 1) {
         failMusic();
     }
     if (takePills == 1) {
         //open the lid "today"
         for (uint16_t pulselen = SERVOMIN; pulselen < SERVOMAX; pulselen++) {
           pwm.setPWM(today, 0, pulselen);
           delay(lidSpeed);
         }
         wellDone();
     }
     buttonStatus = 0;
}

void pillReminder() {
     sprintf (message,"PILLMINDER: Reminder # %i. Take your pills now.",reminderNum);
     clientSMS();
     reminderNum = (reminderNum + 1);
     alarmMusic();
}
void careWarning() {
     int m = ((currentTimeInSecs - alarmTime) * 0.01667);
     strcpy(boxName, deviceName);
     sprintf (message,"PILLMINDER: %s pills are now %i minutes overdue.",boxName, m);
     careSMS();
     creminderNum = (creminderNum + 1);
     alarmMusic();
}
void pillTime() {
     takePills = 1;
     Serial.println("Alarm ON "); 
     digitalWrite(LEDday[today], HIGH);
     alarmMusic();
}

void wellDone() {
    digitalWrite(LEDday[1], LOW);
    digitalWrite(LEDday[2], LOW);
    digitalWrite(LEDday[3], LOW);
    digitalWrite(LEDday[4], LOW);
    digitalWrite(LEDday[5], LOW);
    digitalWrite(LEDday[6], LOW);
    digitalWrite(LEDday[7], LOW); // just to be sure all LEDs off
    pFull[today]= 0;
    vFID = (20 + today); //update app
    vEID = (30 + today);
    vUID = (40 + today);
    Blynk.virtualWrite(vFID, 0);
    Blynk.virtualWrite(vEID, 255);
    Blynk.virtualWrite(vUID, 0);
    int l = 100 + (today * 10);
    EEPROM.put(l,0); //save the state
    takePills = 0;
    reminderNum = 1;
    creminderNum = 1;
    tTaken = currentTimeInSecs;
    int takeDeltaMin = (tTaken-alarmTime)/60;
    sprintf (message,"PILLMINDER: %s pills taken %i mintutes after alarm.",deviceName, takeDeltaMin);
    clientSMS();
    careSMS();
    successMusic();
    pillCount();
}

void clientSMS () {
   sprintf(SMS, "Twilio-SMS/+1%i%i",clientAC,clientNumber);
   Serial.printlnf("SMS: %s", message );
   //Particle.publish(SMS,message,PRIVATE);   
}

void careSMS () {
   sprintf(SMS, "Twilio-SMS/+1%i%i",care1AC,care1Number);
   // Particle.publish(SMS,message,PRIVATE);  
   sprintf(SMS, "Twilio-SMS/+1%i%i",care2AC,care2Number);
   // Particle.publish(SMS,message,PRIVATE); 
   Serial.printlnf("SMS: %s", message );
}

void pillCount(){
   pillsRemaining = 0;
   i=0;
   while (i<7) {
     i++;
     if (pFull[i] > 0) {
        pillsRemaining = pillsRemaining + 1;
     }
   }
   if (pillsRemaining <= pillMin) {
     pillsLow();
   }
   else { 
     Blynk.virtualWrite(V8, 0);
   }
}

void successMusic() {
    tone(speaker,500,500);
    delay(500);
    tone(speaker,2000,500);
}

void failMusic() {
     tone(speaker,5000,200);
     delay(400);
     tone(speaker,5000,200);
     delay(400);
     tone(speaker,5000,200);

}

void alarmMusic() {
     if (audibleAlerts == 1) {
     tone(speaker,1200,200); 
     }
}

void updateTime() {
    if (millis() - lastSync > ONE_DAY_MILLIS) { //daily cloud time sync
      Particle.syncTime();
      lastSync = millis();
    }
    Time.zone(localTimeZone);
    second = Time.second();
    minute = Time.minute();
    hour = Time.hour();
    currentTimeInSecs = second + (minute*60) + (hour*60*60);
    today = Time.weekday();
}

void updateLog() {
    Serial.println();
    Serial.printlnf("Current Alive Seconds= %i", millis()/1000);
    Serial.printlnf("Name: %s ", deviceName);
    Serial.printlnf("Client Number: %i-%i", clientAC, clientNumber );
    Serial.printlnf("Care1 Number: %i-%i", care1AC, care1Number );
    Serial.printlnf("Care2 Number: %i-%i", care2AC, care2Number );
    Serial.printlnf("Local Time Zone = %i Day = %i ",localTimeZone, today);
    Serial.printlnf("Alarm Time Hour = %i Alarm Time Seconds = %i ",alarmTime/3600, alarmTime );
    Serial.printlnf("Current Hour = %i Current Time In Seconds = %i",Time.hour(), currentTimeInSecs);
    Serial.printlnf("Client Reminder Time =%i minutes Reminder Count =%i--Care Reminder Time =%i minutes - Reminder Count = %i",reminderTime, reminderNum, careWarningTime, creminderNum);
    Serial.printlnf("Take Pills= %i Pills Remaining = %i ",takePills, pillsRemaining);
    Serial.printlnf("Pill Bin Status: Sun= %i Mon= %i Tues= %i Wed= %i Thur= %i Fri= %i Sat= %i", pFull[1], pFull[2], pFull[3], pFull[4], pFull[5], pFull[6], pFull[7]);
}

Credits

Mark Lewis

Mark Lewis

3 projects • 2 followers

Comments