Introduction
This Alexa-automated pill dispenser dispenses pills from a “pill box” when you prompt Alexa to dispense your pills for a certain day.
The idea for my project came to fruition on one random morning in the kitchen. As I was eating my breakfast, I watched my dad struggle to fish his daily pills out of the tiny weekly pill box he stored in the medicine cabinet. With a long-standing family history of heart problems, both my mom and dad have to take multiple pills every single day. I wanted to be able to relieve that small part of their already busy day to day lives by making a pill dispenser that could be activated via Amazon Alexa.
Materials
Required materials:
- Particle Photon
- Jumper wires
- 28BYJ-48 Stepper Motor
- Potentiometer
- Amazon Alexa (I have an Ec…
Introduction
This Alexa-automated pill dispenser dispenses pills from a “pill box” when you prompt Alexa to dispense your pills for a certain day.
The idea for my project came to fruition on one random morning in the kitchen. As I was eating my breakfast, I watched my dad struggle to fish his daily pills out of the tiny weekly pill box he stored in the medicine cabinet. With a long-standing family history of heart problems, both my mom and dad have to take multiple pills every single day. I wanted to be able to relieve that small part of their already busy day to day lives by making a pill dispenser that could be activated via Amazon Alexa.
Materials
Required materials:
- Particle Photon
- Jumper wires
- 28BYJ-48 Stepper Motor
- Potentiometer
- Amazon Alexa (I have an Echo show, but any kind works).
Connecting to Alexa
Before beginning the design process, I had to connect my Particle Photon to my Alexa such that she can call a cloud function on the IoT microcontroller. I did this by using Amazon’s Alexa Developer Console, which allowed me to create a custom Alexa skill that communicated with my Photon directly through the Particle Cloud.
**Step 1:**Configuring the Alexa Skill
In the Developer Console, I created a skill called “Motor Controller.” This skill would allow me to give commands to Alexa that would rotate the Stepper motor.
When creating the skill, the configurations I used were other for the type of experience (as it’s a unique skill), custom for the model (which means you start from scratch), and Alexa-hosted [node.js] for the hosting service (since I planned to use JavaScript for the program).
Each skill allows me to create intents, which are basically the commands that you physically say to Alexa.
For example:
- “rotate motor 90”
- “give me Monday pills”
Each phrase activates the specific intents that I created, which then runs code in the cloud based on your prompt.
These are the intents that I created for my Motor Controller skill.
Example prompts: “rotate motor 90”, “rotate motor 180”
This intent rotates the motor with a specified number of degrees.
examples prompts: “dispense Monday”, “dispense Wednesday pills”
This intent uses the RotateMotorIntent, but maps it such that when you prompt it with a certain day, it will rotate a certain amount of degrees.
Step 2:Coding theSkill
Every skill needs a “brain” so that it knows what to do when you speak. In short, the AWS Lambda (a server-less compute service developed by Amazon Web Services) is this brain, where the code written in Node.js (the JavaScript file that I selected as my hosting service) determines what to do with the prompts given from the intents.
How the Lambda works:
- Listens for the voice commands that you give it
I programmed an intent handler for each intent that I created (RotateMotor and DispenseDay)
Example:
Intent Handler for the RotateMotorIntent
- Processes the request from the intent
When Alexa recognizes one of the intents, such as “rotate motor 90, “ she inputs the value 90 for {StepCount}
- Contacts the Particle Cloud and calls the cloud function rotate on my Python with the parameter being determined by the previous steps
After determining what to do with the command I gave Alexa, it sends an HTTPS POST request to the Particle API to call my cloud function named “rotate” with the determined argument (ex: 90, 45, 360).
Code that sends the POST request to Particle Cloud
rotate function being published to the coud
the rotateHandler function
Programming the Photon
The****Code:
#include "Particle.h"#include <Stepper.h>SYSTEM_MODE(AUTOMATIC);SerialLogHandler logHandler(LOG_LEVEL_INFO);const int stepsPerRevolution = 2048; // 28BYJ-48#define IN1 D2#define IN2 D3#define IN3 D4#define IN4 D5#define POT A5Stepper myStepper(stepsPerRevolution, IN1, IN3, IN2, IN4);volatile bool busy = false;volatile long pendingSteps = 0; // queued by cloudint lastAngle = 0; // our current target angle [0..360)unsigned long potFreezeUntil = 0; // ignore pot until this time (ms) after cloud cmdconst unsigned long potFreezeMs = 1200;// simple low-pass filter for potfloat filteredPot = 0.0f; // 0..4095const float potAlpha = 0.15f; // smoothing (0=noise, 1=no smoothing)int rotateHandler(String arg);void runSteps(long steps);static inline int wrap360(int a);void setup() { myStepper.setSpeed(17); // keep modest to avoid stalls pinMode(POT, INPUT); // Start filteredPot near real value to avoid a big jump on boot int raw = analogRead(POT); filteredPot = (float)raw; lastAngle = map(raw, 0, 4095, 0, 360); Particle.function("rotate", rotateHandler); Serial.begin(9600); delay(500); Log.info("Ready — function 'rotate' (degrees) is online.");}void loop() { // 1) Service any queued cloud movement first if (!busy && pendingSteps != 0) { long s = pendingSteps; pendingSteps = 0; runSteps(s); // keep lastAngle in sync with actual movement, so the pot won't "snap back" long movedDeg = (long)((s * 360L) / stepsPerRevolution); lastAngle = wrap360(lastAngle + (int)movedDeg); // freeze the pot briefly after cloud moves potFreezeUntil = millis() + potFreezeMs; } // 2) Pot control (only when not busy and not frozen) if (!busy && (long)(millis() - potFreezeUntil) >= 0) { int raw = analogRead(POT); // 0..4095 int newAngle = map((int)raw, 0, 4095, 0, 360); int rot = newAngle - lastAngle; // choose shortest path if (rot > 180) rot -= 360; if (rot < -180) rot += 360; if (abs(rot) > 4) { // deadband so it doesn't chatter long steps = (long)stepsPerRevolution * rot / 360; runSteps(steps); lastAngle = wrap360(newAngle); } } delay(50);}// helper functionsvoid runSteps(long steps) { busy = true; if (steps != 0) { myStepper.step((int)steps); } busy = false;}static inline int wrap360(int a) { a %= 360; if (a < 0) a += 360; return a;}// cloud function// arg is a number that represents the degrees to be rotatedint rotateHandler(String arg) { arg.trim(); long degrees = arg.toInt(); // user enters DEGREES long steps = (long)stepsPerRevolution * degrees / 360; pendingSteps = steps; return (int)degrees;}
Step1:ConnectingToParticle****Cloud
Particle.function("rotate", rotateHandler);
This line of code registers a cloud function on the Particle cloud called rotate(). Any time either I or Alexa call the rotate() function on the cloud, the rotateHandler() function in my code runs with the inputted value.
Step2:SettinguptheStepperMotor
In my project, I used a 28BYJ-48 Stepper Motor connected with a ULN2003 driver board.
#define IN1 D2#define IN2 D3#define IN3 D4#define IN4 D5Stepper myStepper(stepsPerRevolution, IN1, IN3, IN2, IN4);
With these lines of code, I created a Stepper object that represented my motor and connected the input pins on the driver board(INS1, 2, 3, 4) with the pins on my Photon (D2, 3, 4, 5). [Important! Make sure you have the Stepper library imported into your Particle Web IDE in order to make sure that you have access to the Stepper class.]
Then in the setup() of the code, I set the speed of the motor to 17 revolutions per minute (RPM). You can choose any value around ~15-20, but I chose 17 since it best prevented the motor from stalling
myStepper.setSpeed(17);
Step3:HandlingAlexaCommands
The main function of my code is the function that Alexa is able to call:
int rotateHandler(String arg) { arg.trim(); long degrees = arg.toInt(); // user enters DEGREES long steps = (long)stepsPerRevolution * degrees / 360; pendingSteps = steps; return (int)degrees;}
Basically, the flow of code when ran through Alexa goes,
Prompt Alexa (ex: rotate motor 90) –> code in the Lambda runs with the RotateMotorIntentHandler –> Lambda connects to the Particle Cloud and calls rotateHandler() function on my Photon with the prompted value –> value is converted into steps (the unit for the motor’s rotations) and stored into pendingSteps –> pendingSteps is later used in the loop function of the code.
Step****4:HelperFunctions
In my code, I created two helper functions to help with the logic of the motor.
void runSteps(long steps) { busy = true; if (steps != 0) { myStepper.step((int)steps); } busy = false;}static inline int wrap360(int a) { a %= 360; if (a < 0) a += 360; return a;}
The runSteps function is what actually physically rotates the motor. The busy boolean ensures that the motor doesn’t execute a new command while it’s still rotating.
The wrap360 function keeps the angle “wrapped” between 0 and 360 degrees since the motor rotates within those boundaaries. I did this to make sure if a degree greater than 360 was inputted, it would loop back around.
Step5:LoopandPotentiometer****Control
Since I was using two methods to rotate the motor (Potentiometer as a “homing device” so that I could manually move it for troubleshooting purposes, and via Alexa commands), I wanted to make sure that they didn’t interfere with each other.
To handle cloud commands to actually rotate the motor, I wrote this:
// 1) Service any queued cloud movement first if (!busy && pendingSteps != 0) { long s = pendingSteps; pendingSteps = 0; runSteps(s); // keep lastAngle in sync with actual movement, so the pot won't "snap back" // steps -> degrees long movedDeg = (long)((s * 360L) / stepsPerRevolution); lastAngle = wrap360(lastAngle + (int)movedDeg); // freeze the pot briefly after cloud moves potFreezeUntil = millis() + potFreezeMs; }
Here, I’m now using the pendingSteps variable that was stored through the rotateHandler() function that Alexa called. The busy boolean ensures that the motor isn’t mid-rotation when it’s prompted to start a new one. The runSteps() function is called (a helper function that I created that actually does the rotating) with the amount of pending steps.
Then, I create potFreezeUntil in order to stop the potentiometer from rotating any steps it inputs until the motor is done rotating.
Now, to handle the potentiometer, I wrote this:
// 2) Pot control (only when not busy and not frozen) if (!busy && (long)(millis() - potFreezeUntil) >= 0) { int raw = analogRead(POT); // 0..4095 int newAngle = map((int)raw, 0, 4095, 0, 360); int rot = newAngle - lastAngle; // choose shortest path if (rot > 180) rot -= 360; if (rot < -180) rot += 360; if (abs(rot) > 4) { // deadband so it doesn't chatter long steps = (long)stepsPerRevolution * rot / 360; runSteps(steps); lastAngle = wrap360(newAngle); } }
The code waits for the freeze period to end, preventing the potentiometer from interfering with Alexa. It then reads the values of the potentiometer and maps it to a value between 0 and 360, representing degrees of rotation. It then compares that value to the last known angle in order to make sure the motor doesn’t wrap around instead of going the shortest rotation path–I ignored tiny changes in the potentiometer (4 deg) in order to prevent the motor from jittering from misread pot values. Then, the angle difference is converted into steps and calls the runSteps function.
Creating the Dispenser
My proposal for the project was this:
- The motor would be connected to a disk with a sector cut out, which would act as a hole for the pills to drop into
- The disk would sit underneath a carousel that acts as the pillbox, with dividers that create 8 45 degree sections for the pills to sit in.
- When the disk rotates, the pills rotate with the disk but are blocked by the dividers in the carousel (the carousel isn’t attached to the disk) and fall.
- There is a funnel underneath the disk that catches the pills and dispenses them onto a tray.
Here was the prototype that I designed for the project.
CreatingthePillBoxCarousel:
The carousel pill box
The first structural component that I decided to make was the carousel that would act as a “pill box.” To do this, I folded a long sheet of cardboard into a cylindrical shape that would act as the outer shell of the carousel by hot gluing the ends together. I then cut out 8 small identical rectangles of cardboard that would act as the dividers, and hot glued them to the sides of the carousel.
CreatingTheDisk:
The carousel and the disk
I then made the disk that would sit underneath the carousel by tracing out a circle with the same diameter as it onto a thin sheet of cardboard. I drew it into 8 45 degree sections that would line up with the dividers in the carousel, and cut one of them out in order to create the hole for the pills to drop into.
The carousel and disk aligning on top of each other
CreatingtheDispense****Tray:
Dispense Tray
Next, I cut out the tray for the pills to dispense into. I did this by first creating a “slide” for the pills to fall down under the funnel. This slide would then lead to a wider piece of flat cardboard for the pills to slide on to. The diagonal borders connected to the contraption are to prevent the pills from flying out to the sides when falling down the slides, ensuring that they fall into the designated tray.
Piecingitall****Together:
Image of the final product
I chose to place my project underneath the cabinet containing all of our medicine so that it would be easy to reload the pill box. I did this by attaching a tall, wide sheet of key board to the wall that would serve as the wall for the contraption, since my wall wasn’t level. First, I placed the dispense tray on the bottom. Then, I placed the motor with the desk onto a platform a few inches above the funnel so that the pills would fall into the funnel. I created a platform for the motor the sit on because I wasn’t able to connect the motor to the wall due to the large disk that sat on it’s shaft.
Closer view of the schematics
Finally, I glued the carousel just above the disk-motor contraption (~3 mm), ensuring that it wasn’t touching the disk to make sure that the disk would actually rotate.
and that’s it! Here is a final demonstration of the Pill Dispenser working