Giving blood saves lives and it's something I've done regularly since I turned 16. Sadly I will sometimes forget to donate for several months. I built the Drop of Life as a way to gently remind me when I'm eligible to donate blood.
The project uses a Particle Photon Wi-Fi microcontroller to query the Red Cross API for my blood donation eligibility date. A grid of red LED forms a drop that fills up as the date gets closer. It gently glows when I'm able to donate. After a donation, the Red Cross takes care of updating my next eligible date in their API so the drop will empty completely automatically!
The LED matrix is an 8x16 LED shield by Adafruit. Driving those LEDs from a microcontroller would require a lot of pins so the shield uses a HT16K33 LED matrix driver. You give it an array of data, one byte per row in this case, over I2C and it takes care of lighting the appropriate LEDs. I followed the instructions from Adafruit to assemble the shield.
To connect the shield to the Particle Photon I used a piece of perforated circuit board and 12-pin headers. Only 4 wires are needed between the LED shield and the Photon: ground, 3.3V, I2C clock (SCL) and I2C data (SDA).
I wanted a case that would show the drop shape even with the LEDs off.
I designed a box in Fusion 360 to fit the LED matrix, shield and Photon. In the front, I cut a hole in the shape of a drop, lining up the outline with the position of the LEDs.
I 3D printed the main part of the box and back with usual parameters. I 3D printed a plate to fill the drop-shape hole exactly, using only perimeters to create a swirling effect.
Everything fit nicely inside with room for a 90-degree angle USB cable to provide power.
The program does the following:
- Load up my Red Cross username and password from permanent storage
- Interact with the Red Cross API using Particle webhooks
- Display the drop filled to the right level on the LED matrix
The full source code is available on GitHub.
StorageFor storage, I'm using the built-in EEPROM functionality to load and store strings on the Photon across reboots.
struct Storage {
uint16_t app;
uint8_t username[32];
uint8_t password[32];
} storage;
void setup() {
EEPROM.get(0, storage);
Particle.function("login", setCredentials);
}
int setCredentials(String arg) {
// parse arg, update storage.username and storage.password
EEPROM.put(0, storage);
return 0;
}
To set the password, I called the "login" function on my device from the Particle Desktop IDE.
The easiest way to interact with external APIs on a Particle device is using webhooks. This means publishing an event in the Photon firmware and letting the Particle cloud do the HTTPS request and JSON parsing.
To log in to the Red Cross, I publish an event with the username and password, and subscribe to the response.
String token = "";
Particle.subscribe(
"hook-response/red_cross/login",
setRedCrossToken,
MY_DEVICES
);
String data = String::format(
"{\"username\":\"%s\",\"password\":\"%s\"}",
storage.username,
storage.password
);
Particle.publish("red_cross/login", data, PRIVATE);
void setRedCrossToken(const char *event, const char *data) {
token = data;
}
In the Particle Console, I add a webhook to call the Red Cross login API endpoint in with the username and password in JSON format. The response JSON will contain the token key.
Since this is the only piece of data the firmware needs, I use a Response Template to extract that key and return it to the firmware.
To get the blood donation eligibility date, I publish a different event and subscribe to the response.
long eligibility = 0;
Particle.subscribe(
"hook-response/red_cross/eligibility",
setEligibility,
MY_DEVICES
);
String data = String::format("{\"token\":\"%s\"}", token.c_str());
Particle.publish("red_cross/eligibility", data, PRIVATE);
void setEligibility(const char *event, const char *data) {
// parse string into year, month, day
// ...
// convert calendar date into timestamp (seconds since 1970)
tm date;
memset(&date, 0, sizeof(tm));
date.tm_year = year - 1900;
date.tm_mon = month - 1;
date.tm_mday = day;
eligibility = mktime(&date);
}
In the Particle Console, I add a webhook to call the Red Cross user API endpoint. I add the token I got above as a header. The response JSON will contain a lot of data. For now, I’m only interested in the next blood donation eligibility date so I use a Response template of {{history.eligibility.blood}}
to get only this value in the firmware.
From this date and today’s date, I calculate the fraction of the 8 weeks in between donations that has passed. This value is used in the display code.
The eligibility date is refreshed once per hour.
DisplayI had previously worked with the HT16K33 LED matrix display chip, so I had written some code to talk to it. I published this code as the HT16K33-LED library.
To start the display, initialize the I2C connection.
#include "HT16K33-LED.h"
HT16K33 display;
display.begin();
To draw something fill an array with data and send it to the display.
I sketched the blood drop as an 8 by 16 image. To draw it, I created 2 constant arrays in memory and draw lines from the FULL array up until the desired level then draw lines from the EMPTY array. I also realized that the array elements sent to the HT16K33 of the display didn't line up with the rows of the display, so I had to use a custom row mapping.
const uint8_t DROP_FULL[ROWS] = {
0b00010000,
0b00010000,
0b00111000,
0b00111000,
0b01111100,
0b01111100,
0b01111110,
0b11111110,
0b11111111,
0b11111111,
0b11111111,
0b11111111,
0b11111111,
0b11111111,
0b01111110,
0b00111100,
};
const uint8_t DROP_EMPTY[ROWS] = {
0b00010000,
0b00010000,
0b00101000,
0b00101000,
0b01000100,
0b01000100,
0b01000010,
0b10000010,
0b10000001,
0b10000001,
0b10000001,
0b10000001,
0b10000001,
0b10000001,
0b01000010,
0b00111100,
};
const uint8_t LINE_TO_ROW[ROWS] = {
15,
13,
11,
9,
7,
5,
3,
1,
14,
12,
10,
8,
6,
4,
2,
0,
};
uint8_t lines[ROWS];
for (int i = 0; i < ROWS; i++) {
uint8_t row = LINE_TO_ROW[i];
lines[row] = (i < ROWS - level) ? DROP_EMPTY[i] : DROP_FULL[i];
}
display.writeDisplay(lines, 0, ROWS);
To be able to easily show the project, it does a demo at boot. It starts empty, then fills over 8 seconds.
After this demo, the level of the drop is 0 (empty) if the last donation was today, 8 at the mid-point in between donations and 16 if the next eligible donation is today or in the past.
Putting it togetherIt was fun working on this project. It shows the power of the Particle platform and helps nudge me to do something good.
Time to go donate!
Comments