There are many very good medication reminders out there, but I wanted something a little different. Here is the list of requirements for my ideal medication reminder:
- it should be able to handle both rare and regular medications
- it should generate new reminders automatically every day without user involvement
- it should use multi-faceted notifications (audio, visual, push), to make them difficult to miss
- it should require confirmation from the user that the medication was taken
- it should be able to announce simple messages without waiting for confirmation
- it should require no expensive hardware (like a TFT screen)
- It should need minimal user input to function properly
- it should provide a HTTP endpoint for administration purposes, so that I could update/stop/refresh the headless RPI remotely
First of all you need to install the Jam Hat on your RPI. It's very easy, because the HAT is fully assembled. No soldering is required, just a Philips screwdriver.
I won't go into details on how to install an OS on your RPI, because there are a lot of great resources about that on the internet, for example, this or this. When you are asked to select an OS image, Raspberry Pi OS Lite (the small one without desktop) will do nicely.
At the end of this step you should have a fully functional, internet-connected RPI and a way to talk to it, either via a SSH session or using a USB keyboard and a HDMI monitor. Please connect the Mini External USB Stereo Speaker as well.
Step 2 - installing Python3 accessories & librariesIn the terminal issue the following commands one-by-one:
sudo apt update
sudo apt upgrade
You may need to reboot at this point.
sudo apt install python3-pip
sudo apt install python3-gpiozero
sudo apt install espeak
sudo pip3 install pyttsx3
sudo pip3 install flask
sudo pip3 install apscheduler
At the end of this step you should be able to listen to your RPI talking by issuing the following command:
espeak Hello --stdout | sudo aplay
Step 3 - creating a Slack workspaceSee this link for details on how to create a free Slack workspace. Slack is being used for pushing notifications to any mobile device that runs the Slack client.
Note: you can skip this section if you don't want Slack notifications.
After you create your workspace, you need to create an app. Go here and click on the "Create New App" button. In the next screen give it a name (say "Medication Reminders") and associate it with your workspace which should be displayed in the drop down menu. Press "Create App" to continue. In the next screen press the button "Incoming Webhooks" and then switch "Activate Incoming Webhooks" to ON. Finally, click on the "Add New Webhook to Workspace" button, select a channel to post to as an app from the drop-down menu and press "Allow". Take a note of the Slack Webhook URL which is shown on screen (you can copy it to your clipboard by pressing the button "Copy".)
At the end of this step you should have your own Slack Webhook URL that will be used in the next step.
Step 4 - installing the applicationDownload the attached source code file meds.py to your home directory.
If you want Slack notifications and completed the process in step 3, then load the file in an editor, search for SLACK_URL and replace its value with your Slack Webhook URL. Save the file, and then copy it to the root directory:
sudo cp meds.py /
If you don't want Slack notifications, just leave SLACK_URL as is ( having the value "???").
Step 5 - writing the medication instructionsThe medication instructions is a CSV (comma separated values) file with the following structure:
- date and time (if it's an one-off event) or only time (if it's a daily recurring reminder)
- medication name or message
- how many minutes the reminder should stay active if not dismissed by the user
- is it just a reminder? True means no confirmation is needed. False means it's a medication, hence user confirmation is required.
Time format is %H:%M, which means hour using a 24-hour clock (00 to 23), colon, minute (00 to 59). Here are a couple of examples:
- 00:05 is 5 minutes after midnight
- 12:05 is 12:05 pm
- 09:30 is 09:30 am
- 21:30 is 09:30 pm
Date format is %Y-%m-%d, which means year including the century, dash, month (01 to 12), dash, day of the month (01 to 31); for example 2021-02-01 is February 1st 2021.
The name of the file should be /meds.csv. You can create it by using a text editor like so:
sudo nano /meds.csv
Please find attached an example of how the contents of this file are structured.
Final step: adding a cron job to run meds.py after rebootIn terminal issue the following command:
sudo crontab -e
The first time you'll do that you'll be asked to choose an editor. My personal favourite is nano, but you can select any option you want. At the very end of the file add the following:
@reboot python3 /meds.py &
Save and exit. That should be all. When you reboot your RPI, the green LEDs should be lit, which indicates that the program found your instructions and is up and running.
A few words about the program...When executed, the program looks for the input file /meds.csv and generates reminders for its contents. If no reminders are generated, the 2 red LEDs will be on, indicating an error. If everything goes well, the 2 green LEDs will be on, indicating that it's working. If no LEDs are on, then the program has abnormally ended and you should connect to your RPI to fix the problem.
While running, every 60'' the program will check for active reminders and start notifying the user for each one of them. A typical round of notifications goes like this:
- buzzer plays a couple of notes
- both orange LEDs are lit
- voice says "Medication Time" or "Your attention, please"
- for each pending reminder we hear its message
- for each pending medication...
...voice says its name
... a push notification is sent to Slack (only the 1st time)
... voice says "Press the blue button to dismiss, or the red button to postpone"
... the program waits for up to 60'' for the user to press a button. During this time the two orange LEDs will be blinking. If the user presses the red button, the reminder is postponed for 30 minutes. If the user presses the blue button, the remider is acknowledged and dismissed. If no button is pressed, the process is aborted and all remaining pending medications are postponed for 5 minutes. - if the check is not aborted due to user inaction, the green LEDs are switched on to indicate a return to normal function.
If the RPI is left switched on over night, the program will automatically generate reminders for the following day at midnight. This way it can run unattended for as long as the medication instructions are valid.
If you want to use the HTTP endpoint, it's on port 6666 of every network interface on your RPI. Please find a Postman collection attached which demonstrates some of the available functionality. You can learn about Postman and its collections here.
Command-line parametersSyntax:
python3 meds.py [-s][-h][-i <input file>][-l <config file>][-t][-d]
... or ...
python3 meds.py [--silent]
[-help]
[--input-file=<input file>]
[--log-config=<config file>]
[--test-mode]
[--debug]
-s or --silent runs the program with Slack notifications and text-to-speech turned off
-h or --help shows the available command-line parameters
-i or --input-file directs the program to read an alternate medication list (i.e. other than the default /meds.csv)
-t or --test-mode causes the program to use the keyboard instead of the jam-hat
-d or --debug switches on logging at DEBUG level
-l or --log-config directs the program to configure logging from a specific configuration file.
You can use curl, Postman or something similar to access or change the reminder information remotely. Assuming you have opened a terminal session on the RPI itself, then you can type...
- curl http://127.0.0.1:6666/meds
...to get a list of all reminders currently in memory, e.g.
{
"LTE2NzkzMzYzNA==":"Medication No 1 @ 26/01/2021 09:30 flags=MI_DS",
"LTE3MzE5MjcwNDE=":"Medication No 2 @ 26/01/2021 20:00 flags=MA___",
"LTE3ODM1NjQ3MTA=":"Medication No 3 @ 26/01/2021 08:30 flags=MI_DS",
"LTE5MDgyODQwMjU=":"Calcium @ 26/01/2021 19:30 flags=RA___",
"MTE0MzY5MjQzNg==":"Magnesium @ 26/01/2021 09:00 flags=RI_DS"
}
The format of each reminder is
"<id>":"<Medication> @ <time> flags=<.....>"
where <id> is the unique ID of the reminder, <Medication> is the name of the medication, <time> is the date and time that the reminder is due and <flags> are some properties of the reminder:
M or R denotes Medication or Reminder
A or I means Active or Inactive (i.e. dismissed or past its end time)
P or _ denotes whether the reminder has been postponed at least once
D or _ denotes whether the reminder has been dismissed or not
S or _ denotes whether a notification has been sent to Slack. - curl http://127.0.0.1:6666/meds/pending
... to get a list of the pending reminders currently in memory, i.e. those about which you'll be notified during the next check - curl http://127.0.0.1:6666/meds/stop
... to stop the program (exit to the OS) - curl http://127.0.0.1:6666/shutdown
... to stop the program and shut the RPI down. - curl http://127.0.0.1:6666/meds/<id>
... to return a single reminder from the list in memory. Just replace <id> with the unique ID of the item you want to select. - curl -X DELETE http://127.0.0.1:6666/meds/<id>
... to remove the related reminder from the list in memory - curl -X PATCH http://127.0.0.1:6666/meds/<id>
... to toggle the "Dismissed" flag on the related reminder. It's handy when the user either dismissed a reminder by mistake and wants to make it active again or does not want to be notified about a specific medication for some reason. - curl -X POST http://127.0.0.1:6666/meds
... to add a new reminder to the list in memory. Its details must be passed as a form with the following fields:
. name (the name of the medication)
. start_at (either a time or a date and time using the same formats as the CSV file)
. stop_after (the number of minutes to keep the reminder active if not dismissed by the user)
. is_optional (true or false)
Here is an example:
curl -X POST -F "name=My new medication" -F "start_at=12:45" -F "stop_after=30" http://127.0.0.1:6666/meds - curl -X POST http://127.0.0.1:6666/meds/refresh
... to cause (a) all inactive items (i.e. those that were explicitly dismissed by the user or are past their end time) to be dropped from the reminder list in memory, and (b) the medication file to be read again, so that new reminders be generated and added to the list (if necessary.) A single optional parameter called target may be passed, to indicate the date for which the new entries are to be generated. Target has the same date format as the CSV file. If nothing is passed, today's date is assumed. In the following example the user requests the list to be refreshed with items for December 31, 2021:
curl -X POST -F "target=2021-12-31" http://127.0.0.1:6666/meds/refresh - curl http://127.0.0.1:6666/meds/version
... to get the program's version and license
You can replace 127.0.0.1 with your RPI's IP address and send commands to it from any other computer in your local network.
Comments