Medication Reminder with ESP-32S and Lego Bricks

Saahil Barai
8 min readFeb 2, 2021

Taking your medicine as prescribed is important for treating conditions, overall long-term health, and well-being. To help with prescription adherence, I wanted to make a cost effective medication reminder.

For this project I will be using the materials below:

  • NodeMCU ESP-32S Microcontroller
  • Bread Board
  • Bread Board Jumper wires (Any breadboard compatible wires can be used)
  • Passive Buzzer Module
  • Standard Button
  • LED
  • Resistors of 220 Ohm, 100 Ohm, and 10K Ohm
  • Legos (Optional)

Wiring Diagram

Port Definitions — Orange = P13, Violet = P5, and Pink = P16

There are a couple basic functionalities I wanted my medicine reminder station to have: time-based notifications, an alarm sound, and a convenient way to store the medicine and register its consumption. There are many things you can do on top of this basic design. The ESP has the capability to send emails, so you could potentially email the end user a weekly report of their consumption and corresponding times. Additionally, although my use case only requires for one medicine container, you can make a weekly system with a bottle and progress LED for each day of the week. For this tutorial, let’s continue with the basic functionalities.

In order to send a reminder notification every morning, the ESP will need a way to access the current time. So let’s begin by setting up the infrastructure to print out the current time using the Arduino IDE and the ESP. In order to do this, we will be using the Network Time Protocol. The Network Time Protocol allows for a majority of our devices to synchronize their clocks over the internet. The ESP-32S is an inexpensive chip that has an onboard Wi-Fi module, making it perfect for this usage. We will start by importing the Wi-Fi and time libraries.

#include <WiFi.h>
#include "time.h"

We will also need to define some pins, our network name and password prior to getting started. The pins correspond to the different components we will be using throughout this tutorial: a buzzer, button and LED.

const char* ssid = "ssid";
const char* password = "password";
const int buttonPin = 16;
const int ledPin = 13;
const int buzzerPin = 5;
char timeMin[3];
char timeHour[3];
char timeDay[3];

To obtain the current time from the server, we will first need to establish a Wi-Fi connection from our ESP. I will split the Wi-Fi tasks into a setup/connect method and a disconnect method. The WiFi.begin() method in the setup method initializes the Wi-Fi library’s network settings and provides the current status. Following this, I have some code to indicate the connection status. Once the connection is made, the configTime method in the setup block will be used to connect to our NTP server of choice. The configTime function accepts 3 main parameters: an integer representing the time zone offset, the daylight savings offset in seconds, and the NTP server we are going to use. There are a range of NTP servers we can use, google has an NTP server located at “time.google.com” and “pool.ntp.org” is a public usage time sever. For this project I used the pool network server, but any NTP server can be used. Lastly, we can disconnect the Wi-Fi, as the ESP will now keep track of time using an internal counter.

void setup(){
Serial.begin(115200);
setupConnection();
configTime(gmtOffset_sec, daylightOffset_sec, ntpServer); stopConnection();
}
void setupConnection(){
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println("Wi-Fi connection success.");
}
void stopConnection(){
WiFi.disconnect(true);
WiFi.mode(WIFI_OFF);
}

We can now check if our code is obtaining the appropriate values from the NTP server by creating a method that prints and saves out our relevant parameters.

void getTimeInfo(){
struct tm timeinfo;
try {
getLocalTime(&timeinfo)
}
catch() {
Serial.println("Failed to obtain time");
return;
}
strftime(timeHour,3, "%H", &timeinfo);
strftime(timeDay,3, "%d", &timeinfo);
strftime(timeMin,3, "%M", &timeinfo);
Serial.println(timeHour);
Serial.println(timeMin);
Serial.println(timeDay);
}

By printing out timeHour, timeDay and timeMin to the serial monitor we can ensure that the offset we provided is correct and that the ESP is receiving the time. Using the current time we can trigger events at specific times. In this case, I would like to sound a buzzer reminding the user to take their medicine. To produce different tones or sounds I used a passive buzzer. A passive buzzer is a low-cost component that uses AC signals to make a sound. Modifying the AC signal produces the tone of the sound. By oscillating between different tones we can create an alarm-like sound. To do this on an ESP-32S we will repurpose the LEDC library. LEDC is primarily designed to control the intensity of LEDs, although it can also be used to generate pulse-width modulation signals. Here, we add the methods ledcSetup to initialize a channel, frequency, resolution for our buzzer and ledcAttachPin to assign our buzzer pin number. For further information on the ledc methods the espressif github page is a great resource.

void setup(){
Serial.begin(115200);
ledcSetup(0,1E5,12);
ledcAttachPin(buzzerPin,0);
setupConnection(); configTime(gmtOffset_sec, daylightOffset_sec, ntpServer); stopConnection();
}

To create an alarm-like sound we will periodically alternate between playing a tone and no tone. For this we will create a method that writes to channel 0 with a frequency of 800. You can try out different frequencies and even write a method that plays a song. When I got the buzzer producing sound, one of the first things I did was play the star wars theme.

void soundAlarm(){
ledcWriteTone(0,800);
delay(1000);
uint8_t octave = 1;
ledcWrite(0, LOW);
delay(1000);
}

At this point, we have our time and sound pieces in place and have verified that they are working. We can use these components to build out the remaining functionalities. To set up the system to trigger events and notify the user we will use a flag based system. This flag based system will be written out in our loop function. We begin each loop iteration by obtaining the updated time information fields. These updated fields can be used to initiate a notification in the msgChecker method. In this case, my notification is entirely dependent on whether it is 8:00 AM and that the alarm has not yet been triggered. Once msgChecker sets the send flag to its respective boolean value based on these dependencies we either loop back on a false or go into the notify block on a true. Once our ESP gets the time of 8:00 AM the flag will be set to true and we will then enter a notify loop that runs for approximately 60 seconds. (I run the soundAlarm method which takes near two seconds, 30 times for a total of 60 seconds)

void loop(){
delay(1000);
getTimeInfo();
msgChecker();
if(sendMsgFlag)
{
while(alarmTimer < 30)
{
soundAlarm();
alarmTimer++;
}
sendMsgFlag = false;
alarmTimer = 0;
sentAck = true;
}
}
void msgChecker(){
if(atoi(timeHour) == 8 and atoi(timeMin) == 0 and !sentAck)
{
sendMsgFlag = true;
}
}

On top of letting the alarm ring for a certain amount of time, I want to give the user control to shut the alarm off using a button. This button will rest underneath the medicine container providing an ergonomic way to take the medicine and shut off the alarm swiftly. I also want to take into account that some users will not strictly take their medicine when reminded, but possibly a couple minutes before. To provide acknowledgment to the user, we will use a LED that lights up when the user presses the button. This signals to them that even though the alarm has not yet sounded, the system has recognized that the user has taken their medicine and as a result the system will bypass the alarm. In order to accomplish this we will modify our existing loop function above.

void loop(){
delay(1000);
getTimeInfo();
checkNewDay();
msgChecker();
buttonState = digitalRead(buttonPin);
if(buttonState == HIGH)
{
bypassNotif = true;
digitalWrite(ledPin, HIGH);
delay(1000);
digitalWrite(ledPin, LOW);
}
else
{
bypassNotif = false;
}


if(sendMsgFlag and !bypassNotif)
{
while(alarmTimer < 30)
{
soundAlarm();
alarmTimer++;
buttonState = digitalRead(buttonPin);
if(buttonState == HIGH)
{
digitalWrite(ledPin, HIGH);
delay(1000);
digitalWrite(ledPin, LOW);
break;
}

}
sendMsgFlag = false;
alarmTimer = 0;
sentAck = true;
}
}
void checkNewDay(){
if(atoi(timeHour) == 23 and atoi(timeMin) == 59)
{
bypassNotif = false;
}
}

To read the button state we use the digitalRead function with the corresponding pin number. To account for a user taking their medicine early, we check if the button is ever pressed from within the loop. If it is, we set our bypassNotif flag to true as we no longer want to ring the alarm and then we blink the LED to convey this change to the user. We want to reset this bypassNotif flag each time we enter a new day as if we do not data from a previous day may prevent the alarm from sounding. To do this we use the checkNewDay function to reset this flag for each new day. Next, we want to give the user the power to shut of the alarm once they’ve taken their medicine. To do this we check if the button has been pressed within the notify loop in a very similar manner. Once the alarm has sounded a press of the button can stop the alarm and trigger the LED blink for confirmation. No press will let the alarm play out for around a minute.

It is also possible to make this process more efficient. Sending the ESP into a deep sleep mode after medicine has been taken for the day can help conserve power. Additionally, one can set a second alarm to ring at a specified interval after the first alarm, if the first alarm is not acknowledged with a button press. The great thing about this setup is that it is adaptable to many scenarios and preferences.

These are the basic building blocks for a fully functional medicine reminder. We have time-based notifications and an alarm sound. We’re only missing one thing: a convenient way to store the medicine and register its consumption. This brings me to my favorite part of the project. To clean up all the wiring and make a nice holder for the medicine bottle, I built a casing using Legos. While a casing can be made from just about anything, I had some leftover Legos and decided it would be a fun way to finish off this project. Here’s how it turned out!

Lego housing for breadboard, wiring, and medicine bottle. The medicine bottle has a button underneath it, attached to the breadboard. The window shows the LED indicator.

I hope that you can use or build upon this tutorial, to make a medicine reminder of your own. You can even modify this code to create something entirely new!

--

--