Introduction: Deathwatch -- Wearable Life Expectancy Timer Using Adafruit Circuit Playground + OLED

Hello! You're going to die someday! But that's okay, so am I :)



I think a near constant awareness of one's own mortality is an inspiring, motivating, (soul-crushing), and ultimately perspective-focusing endeavor that everybody should undertake. That's why I made a wearable to help me!

In short, it's a wrist-worn Adafruit Circuit Playground that displays a randomly generated (but statistically informed) number of days I have left to live. Fun!

Basic premise is: it operates on code that randomly generates these days based on an actuarial table I found that gave me the probability I would die in various age blocks. So every now and then it'll change -- one day it'll say I have 23,454 days left, another only 19. And it's all on a randomized timer as well, so I never know when it'll change, allowing it to catch me off guard and hopefully make me shift perspective in that moment and internalize that life expectancy.


I was inspired by a quote I found in Alan Watt's "The Book",

"The sole means now for the saving of the beings of the planet Earth would be to implant again into their presences a new organ … of such properties that every one of these unfortunates during the process of existence should constantly sense and be cognizant of the inevitability of his own death as well as the death of everyone upon whom his eyes or attention rests. Only such a sensation and such a cognizance can now destroy the egoism completely crystallized in them..." -- G.I. Gurdjieff

and thought it would be cool to make this "organ" into a device I could wear.

In this instructable, I'll show you how I went about this, starting with getting my materials, figuring out approximately what my life expectancy is, coding the sketch in the Arduino IDE, and finally sewing the components onto my bracer. It's my first instructable (long time listener, first time caller...) so please let me know what I need to flesh out and edit so others can hopefully use this to make their own!

Step 1: Tools & Materials

First off, let me just say -- I love Adafruit. I really really love Adafruit. That's all.


Tech Components

  • Adafruit Circuit Playground (https://www.adafruit.com/product/3000)
  • 3.7V 2000mAh LiPo battery (https://www.adafruit.com/product/2011)
  • 0.96" 12C OLED 128x64 Monochrome LCD (https://www.amazon.com/gp/product/B00O2KDQBE/ref=o...)
  • LiPo USB charger (https://www.adafruit.com/product/1304)

Technically there are a lot of other boards that would work fine with this project, but I just love Adafruit's wearable microcontroller line (Flora, Gemma, etc.) and I was really excited to try out the Circuit Playground since it had so many other features/sensors packed onto it (for only 20 bucks!!). So I recommend that one, especially since if you want the LED features with another board, you'll have to buy and install a bunch of individual neopixels (or a ring).

Same goes for the screen, there are tons of others that will work, I just liked this one because an OLED screen is a bit more battery friendly, and I didn't need it to have a bunch of crazy colors or anything. Maybe a bit bigger would have been nice. Keep in mind there are a ton of changes you'll have to make with the code if you use a different screen.

And same goes for the battery. I like LiPo batteries because they're rechargeable and pack a pretty good punch. 3.3V was perfect for the Circuit Playground, and the 2000mAh seemed like a decent charge for something I'd want to wear all day (I've worn it for a few hours so far, I'll update this if it turns out to not last as long as I had hoped), but feel free to adjust if your power needs differ. I also liked the size of this one for something that would fit well in the bracer pocket without being too bulky.

Wearable Components

  • Bandana (or whatever material you want to mount the components onto)
  • Yonic to Phallic Jumper Wires** (https://www.adafruit.com/product/1953)
  • Velcro (https://www.amazon.com/gp/product/B000YZ8UF8/ref=o...

You can technically do this whole project with conductive thread instead of the jumper wires, but here was my breakdown of Pros/Cons for which to use:

  1. Jumper wires are a little more protected since they have the insulation, whereas if someone were to touch the conductive thread, they could potentially cause a short (Agh!)
  2. Jumper wires are more fragile, since they're not that thick and stick out of the board (I'm low-key terrified that if I bump into a wall or something they'll all snap off...)
  3. Jumper wires look f**king badass sticking out of your board, and give it that cool cyperpunk aesthetic!
  4. Conductive thread means more sewing (which I did not particularly want to do)
  5. Mostly I just wasn't exactly sure how I was going to connect the weird header pins of the OLED if i used conductive thread...

**(Female to Male) My friend thought it would be cool to start saying Yonic to Phallic instead of Female to Male, so I'm trying it out.

Tools

  • Computer with the Arduino IDE installed
  • Soldering Iron
  • This thing that holds your board while you're soldering (can't remember the name of it)
  • Prototyping Materials (bread board, alligator clips, jumpers)
  • USB cable
  • Sewing Kit (needle, thread, safety pins)
  • Scissors
  • Pliers to help pull the needle through the thick velcro

Step 2: Code

Overview

So the basic run-down of how this works is that the main loop of the timer goes through a few central functions:

  1. watch() -- displays the days on the screen.
  2. oracle() -- the main meat, 99% of the time this is running, basically just waiting to trigger a new day prediction (you can totally take this out if you want another system of triggering a new day count, i just wanted this so it would display at random times without me triggering it -- to catch me off-guard)
  3. prophecy() -- the function that runs on start and whenever oracle triggers it -- flashes the LEDs, displays "You're going to die", and then uses lifeGen() to return a new day prediction
  4. lifeGen() -- uses a random number generator informed by the statistical data I found on my life expectancy to spit out a new estimated number of days I have left.

Then, depending on what the estimation is, you might need a way to celebrate/distract, so I included a "party mode" activated via switch on the Circuit Playground that turns the NeoPixels into a sound-reactive lightshow. The basic idea being -- only 3 days left to live? Go dance at a club!


My original idea was just to do the life expectancy timer, then once I figured out all the cool stuff the Circuit Playground could do, I decided to add the "Party Mode". The meat of it, however, is the timer, so if you decide you don't want the party mode, all you have to do is delete the bits taken from the vu_meter example. (There's actually a lot of functionality that the party mode makes difficult, I feel like if I remake this, I'll do something different and keep it out.)

Libraries

I'm assuming you already know enough about the Arduino environment to have it installed and upload sketches, but if not, there are a ton of great instructables out there to help you. Same goes for installing Adafruit libraries, but if you need a refresher, check this out (https://learn.adafruit.com/adafruit-all-about-ardu...)

Did I mention I love Adafruit?

I love them so much that I frankensteined three of their example sketches together to make the skeleton for this project :) all code projects are just standing on the shoulders of giants after all...

The three libraries I used in my code are as follows, with the corresponding example sketch I cannibalized:

  • Circuit Playground Library (vu_meter example)
    • for the sound-reactive LED "Party Mode"
  • SSD1306 Library (128x64 I2C example)
    • handles all the display functions
  • NeoPixel Library (buttoncycler example)
    • just for the rainbow flash effect in the prophecy() function
    • probably could have just used the Circuit Playground Library for this, but I already had this function in one of my previous projects, so it was less work to just copy/paste it into this.

Life Table

So since I wanted this to be more or less individually tailored to me, I used an Actuarial (Life) Table that took my basic demographics and gave me the probabilities that I would die in various age blocks for the rest of my life. The website is very cool, check it out! (https://flowingdata.com/2015/09/23/years-you-have-...)

Code

The code is also available here -- a star would be lovely :)
(https://github.com/DeadAugust/deathwatch/blob/mast...)

*Not sure why the Instructable Editor broke it up into all these chunks, sorry about that -- let me know if copy/pasting it took out anything essential, I gave it a couple proofreads and think I got everything...

/* "MY DAYS ARE NUMBERED" 
Deathwatch sketch for Adafruit Circuit Playground and 128x64 OLED screen

Frankensteined from several lovely Adafruit tutorials by August Luhrs [deadaugust]

Version 1.0 Jul. 1 2017

based on:

vu_meter example from Circuit Playground library
SSD1306 128x64 12C example from SSD1306 library
buttoncycler example from NeoPixel library

life expectancy based on stats for 24 y.o male:
<a href="https://flowingdata.com/2015/09/23/years-you-have-left-to-live-probably/">https://flowingdata.com/2015/09/23/years-you-have...</a>
Adafruit invests time and resources providing open source code, 
please support Adafruit and open-source hardware by purchasing 
products from Adafruit! (seriously, they're the best y'all)
Written by Adafruit Industries.  Distributed under the BSD license.
This paragraph must be included in any redistribution.
*/
<adafruit_neopixel.h><adafruit_circuitplayground.h><math.h><spi.h><wire.h><adafruit_gfx.h><adafruit_ssd1306.h><p>#include <Adafruit_NeoPixel.h>  //i know this is kind of redundant, but i didn't want to mess with the neopixel effect i got from buttoncycler</p><p><adafruit_neopixel.h>#include <</adafruit_neopixel.h>Adafruit_CircuitPlayground.h></p><p><adafruit_neopixel.h><adafruit_circuitplayground.h>#include <math.h)<math.h>
#include <SPI.h><spi.h>
#include <Wire.h><wire.h>
#include <</wire.h></spi.h></math.h></adafruit_circuitplayground.h></adafruit_neopixel.h>Adafruit_GFX.h></p>#include <</adafruit_ssd1306.h></adafruit_gfx.h></wire.h></spi.h></math.h></adafruit_circuitplayground.h></adafruit_neopixel.h>Adafruit_SSD1306.h>  //had to uncomment #define "SSD1306_128_64" and out "#define ...32"
//from SSD1306
#define OLED_RESET 4
Adafruit_SSD1306 display(OLED_RESET);
//from neopixel
#define PIXEL_COUNT 10
#define PIXEL_PIN  17
Adafruit_NeoPixel strip = Adafruit_NeoPixel(PIXEL_COUNT, PIXEL_PIN, NEO_GRB + NEO_KHZ800);
//from vumeter
#define NUMFLAKES 10      
#define XPOS 0
#define YPOS 1
#define DELTAY 2
#define MIC_PIN         A4  // Microphone is attached to this analog pin (A4 for circuit playground)
#define SAMPLE_WINDOW   10  // Sample window for average level
#define PEAK_HANG       24  // Time of pause before peak dot falls
#define PEAK_FALL        4  // Rate of falling peak dot
#define INPUT_FLOOR     10  // Lower range of analogRead input
#define INPUT_CEILING  888  // Max range of analogRead input, the lower the value the more sensitive (1023 = max)
                            // i changed mine to 888, for louder music, and also b/c good number
#define LOGO16_GLCD_HEIGHT 16 
#define LOGO16_GLCD_WIDTH  16 
static const unsigned char PROGMEM logo16_glcd_bmp[] =
{ B00000000, B11000000,
  B00000001, B11000000,
  B00000001, B11000000,
  B00000011, B11100000,
  B11110011, B11100000,
  B11111110, B11111000,
  B01111110, B11111111,
  B00110011, B10011111,
  B00011111, B11111100,
  B00001101, B01110000,
  B00011011, B10100000,
  B00111111, B11100000,
  B00111111, B11110000,
  B01111100, B11110000,
  B01110000, B01110000,
  B00000000, B00110000 };
#if (SSD1306_LCDHEIGHT != 64)
#error("Height incorrect, please fix Adafruit_SSD1306.h!"); //if you're getting this, check line 22
#endif
byte peak = 16;        // Peak level of column; used for falling dots
unsigned int sample;
byte dotCount = 0;     //Frame counter for peak dot
byte dotHangCount = 0; //Frame counter for holding peak dot
float fscale(float originalMin, float originalMax, float newBegin, float newEnd, float inputValue, float curve);
bool slideSwitch;
int days;             //number of days left to live
int firstWatch = 1;   //for the single run of watch() 
long rando;           //random number for oracle()
//had to put all the functions before setup because kept getting "not declared in scope" error
/*SSD1306 functions
.
.
.
.
. */
//draws the cool circle thing
void testdrawcircle(void) {
  for (int16_t i=0; i<display.height(); i+="2)" {=""  ="" display.drawcircle(display.width()="" 2,="" display.height()="" i,="" white);="" display.display();="" delay(1);="" }="" }<="" p=""></display.height();>
//watch display function
void watch(){ 
  if (firstWatch == 1){ //so it only loops once
    // draw cool circle thing
    testdrawcircle();
    display.display();
    delay(2000);
    display.clearDisplay();
    
    // days display
    display.setTextSize(1);
    display.setTextColor(WHITE);
    display.setCursor(33,0);
    display.println("You have");
    display.setTextSize(3);
    display.println(days);
    display.setTextSize(2);
    display.println("days left to live");
    display.display(); 
    display.clearDisplay();
    //ends loop
    firstWatch = 0;
  }
}
    
    
/* my functions! yay
.
.
.
*/
void oracle(){ //runs RNG until new prophecy (this is the "timer")
  if (rando != 333) { //lucky number ;)
    rando = random(8888888);  //adjust this based on desired spacing of event
                            //1000 was less than a minute, 100k was a few minutes
    Serial.println(rando);
  }
  else {
    prophecy(); //spits out new day
  }
}
void prophecy(){ //reveals new life expectancy
  display.setTextSize(1);
  display.setTextColor(WHITE);
  display.setCursor(0,34);
  display.println("YOU'RE GOING TO DIE");
  display.display();
  theaterChaseRainbow(55);
  lifeGen();
  display.clearDisplay();
  firstWatch = 1; //reset?
  rando = 0;
}
void lifeGen(){ //life expectancy generator, probability i'll die in various age blocks
  int years = random(100);  //also, f**k leap years, every year has 365 days
  Serial.println(years);
  if (years == 0) {  //1% probability I'll die in the next 9 years
    days = random(3286); //3,285 days in the next 9 years
    Serial.println(days);
  }
  else if (years == 1 || years == 2){ //2% chance I'll die in 10-19 years
    days = random(3286, 6936);
    Serial.println(days);
  }
  else if (years > 2 && years < 7){ //4% chance I'll die in 20-29 years
    days = random(6936, 10586);
    Serial.println(days);
  }
  else if (years > 6 && years < 15){ //8% chance I'll die in 30-39 years
    days = random(10586, 14236);
    Serial.println(days);
  }
  else if (years > 14 && years < 29){ //15% chance I'll die in 40-49 years
    days = random(14236, 17886);
    Serial.println(days);
  }
  else { //69-70% chance I'll die between age 50-88 (arbitrary end year, actual life expect is 81.5 years)
    days = random(17886, 32121);
    Serial.println(days);
  }
}
/*neopixel function
.
.
*/
//Theatre-style crawling lights with rainbow effect
void theaterChaseRainbow(uint8_t wait) {
  for (int j= 210; j < 248; j++) {     // cycles through blue --> green
    for (int q=0; q < 3; q++) {
      for (int i=0; i < strip.numPixels(); i=i+3) {
        strip.setPixelColor(i+q, Wheel( (i+j) % 255));    //turn every third pixel on
      }
      strip.show();
      delay(wait);
      for (int i=0; i < strip.numPixels(); i=i+3) {
        strip.setPixelColor(i+q, 0);        //turn every third pixel off
      }
    }
  }
}
/*vumeter functions
.
.
.
.
. */
//Used to draw a line between two points of a given color
void drawLine(uint8_t from, uint8_t to, uint32_t c) {
  uint8_t fromTemp;
  if (from > to) {
    fromTemp = from;
    from = to;
    to = fromTemp;
  }
  for(int i=from; i<=to; i++){
    CircuitPlayground.strip.setPixelColor(i, c);
  }
}
float fscale( float originalMin, float originalMax, float newBegin, float
newEnd, float inputValue, float curve){
  float OriginalRange = 0;
  float NewRange = 0;
  float zeroRefCurVal = 0;
  float normalizedCurVal = 0;
  float rangedValue = 0;
  boolean invFlag = 0;
  // condition curve parameter
  // limit range
  if (curve > 10) curve = 10;
  if (curve < -10) curve = -10;
  curve = (curve * -.1) ; // - invert and scale - this seems more intuitive - postive numbers give more weight to high end on output 
  curve = pow(10, curve); // convert linear scale into lograthimic exponent for other pow function
  /*
   Serial.println(curve * 100, DEC);   // multply by 100 to preserve resolution  
   Serial.println(); 
   */
  // Check for out of range inputValues
  if (inputValue < originalMin) {
    inputValue = originalMin;
  }
  if (inputValue > originalMax) {
    inputValue = originalMax;
  }
  // Zero Refference the values
  OriginalRange = originalMax - originalMin;
  if (newEnd > newBegin){ 
    NewRange = newEnd - newBegin;
  }
  else
  {
    NewRange = newBegin - newEnd; 
    invFlag = 1;
  }
  zeroRefCurVal = inputValue - originalMin;
  normalizedCurVal  =  zeroRefCurVal / OriginalRange;   // normalize to 0 - 1 float
  // Check for originalMin > originalMax  - the math for all other cases i.e. negative numbers seems to work out fine 
  if (originalMin > originalMax ) {
    return 0;
  }
  if (invFlag == 0){
    rangedValue =  (pow(normalizedCurVal, curve) * NewRange) + newBegin;
  }
  else     // invert the ranges
  {   
    rangedValue =  newBegin - (pow(normalizedCurVal, curve) * NewRange); 
  }
  return rangedValue;
}
// Input a value 0 to 255 to get a color value.
// The colours are a transition r - g - b - back to r.
uint32_t Wheel(byte WheelPos) {
  if(WheelPos < 85) {
    return CircuitPlayground.strip.Color(WheelPos * 3, 255 - WheelPos * 3, 0);
  } 
  else if(WheelPos < 170) {
    WheelPos -= 85;
    return CircuitPlayground.strip.Color(255 - WheelPos * 3, 0, WheelPos * 3);
  } 
  else {
    WheelPos -= 170;
    return CircuitPlayground.strip.Color(0, WheelPos * 3, 255 - WheelPos * 3);
  }
}
void setup() {
  CircuitPlayground.begin();
  Serial.begin(9600); //just to check the RNG day thing (i think)
  display.begin(SSD1306_SWITCHCAPVCC, 0x3C);  // initialize with the I2C addr 0x3D (for the 128x64)
                  // had to change to 0X3C -- **IMPORTANT**
  display.clearDisplay();
  randomSeed(analogRead(0)); //initializes psuedo-random number generator with fairly random input
  prophecy(); //new prophecy every time it starts
}
void loop() {
  slideSwitch = CircuitPlayground.slideSwitch();
 
  if (CircuitPlayground.slideSwitch()){ //if switch is to left (+): PARTY MODE!!!!1!!!11
    // CircuitPlayground.redLED(HIGH);   // just use for debugging
    
    //vu_meter code
    int numPixels = CircuitPlayground.strip.numPixels();
    unsigned long startMillis= millis();  // Start of sample window
    float peakToPeak = 0;   // peak-to-peak level
    unsigned int signalMax = 0;
    unsigned int signalMin = 1023;
    unsigned int c, y;
    // collect data for length of sample window (in mS)
    while (millis() - startMillis < SAMPLE_WINDOW)
    {
      sample = analogRead(MIC_PIN);
      if (sample < 1024)  // toss out spurious readings
      {
        if (sample > signalMax)
        {
          signalMax = sample;  // save just the max levels
        }
        else if (sample < signalMin)
        {
          signalMin = sample;  // save just the min levels
        }
      }
    }
    peakToPeak = signalMax - signalMin;  // max - min = peak-peak amplitude
   
    // Serial.println(peakToPeak);
  
    //Fill the strip with rainbow gradient
    for (int i=0;i<=numPixels-1;i++){
      CircuitPlayground.strip.setPixelColor(i,Wheel(map(i,0,numPixels-1,30,150)));
    }
  
    //Scale the input logarithmically instead of linearly
    c = fscale(INPUT_FLOOR, INPUT_CEILING, numPixels, 0, peakToPeak, 2);
  
    // Turn off pixels that are below volume threshold.
    if(c < peak) {
      peak = c;        // Keep dot on top
      dotHangCount = 0;    // make the dot hang before falling
    }
    if (c <= numPixels) { // Fill partial column with off pixels
      drawLine(numPixels, numPixels-c, CircuitPlayground.strip.Color(0, 0, 0));
    }
  
    // Set the peak dot to match the rainbow gradient
    y = numPixels - peak;
    CircuitPlayground.strip.setPixelColor(y-1,Wheel(map(y,0,numPixels-1,30,150)));
    CircuitPlayground.strip.show();
  
    // Frame based peak dot animation
    if(dotHangCount > PEAK_HANG) { //Peak pause length
      if(++dotCount >= PEAK_FALL) { //Fall rate 
        peak++;
        dotCount = 0;
      }
    } 
    else {
      dotHangCount++; 
    }
  }
  else {    //if switch is to right (-): Oracle Mode
    // CircuitPlayground.redLED(LOW); //just use for debugging
    CircuitPlayground.clearPixels(); //keep this or else party mode will keep neopixels lit
    strip.begin(); //redundant?
    watch(); //runs the display animation once then displays days
    oracle(); //runs until new days
  }
}

Step 3: Installing the Components (Making It Wearable)

Woo! If you've gotten this far, the worst is over. Unless you suck at sewing, like me.

So the basic steps I took were (with constant test-wears inbetween steps...)

  1. Measure all components and test the fabric on desired body placement
    • I wanted the fabric to be thick enough to give the board a comfortable berth, but not so wide that it took up too much space on my forearm -- for me that's roughly 3.5"x11" (though the fabric measured 14"x11" when I cut it, since I folded it twice to allow for a pocket.
  2. Cut the fabric
  3. Sew fabric together
    • I sewed the 14"x11" piece together by folding it in half so that each side was a 7"x11" rectangle of the "good" side of the material
    • I sewed two edges of the "bad" (dull) side together, and then flipped it inside out so the seam wouldn't be visible (see picture 1 in this section)
  4. Safety Pin the fabric to your desired placement, then mark where the various components should go
    • keeping safety pins on everything really helps hold stuff together while you're working
    • I used some chalk to mark the center spot the board and OLED would go on to for reference.
  5. Sew the OLED on
    • So when I did this, I ran into a slight hiccup... The OLED header pins come out at a 90 degree (see pic 2), not exactly ideal for application on a round wrist... luckily I had already figured I would install it at the "corner" of my wrist, and the weird angle fit okay on that rounded part of my wrist. I probably could have bent the headers, but I didn't want to mess it up and break them.
  6. Solder the Wires to the board
    • I then took the jumpers off the OLED and used my board-holder thingy to clamp down on the board while I soldered each jumper on.
  7. Sew the Board on
    • Same procedure as the OLED installation, safety pins really help.
    • Then, once it was all on, I fed the jumpers back under the board and hooked them back up to the OLED (don't try to do this before you sew, or else you'll constantly be moving around wires and it'll be annoying)
  8. Sew the Velco on
    • Make sure you're constantly checking it on your body to make sure everything is where you want it to be. Then for this step, wrap it and mark the spots that over lap -- this is where you want to put the velcro.
    • This part is a little tougher to sew, so feel free to bring out those pliers to pull the needle through the thicker bits.
  9. Sew the wires down
    • Lastly, I put one small loop in (there's a better name for this that escapes me) to hold the middle of the jumpers down. Just wanted to keep them relatively secure while also allowing the wires to stick out a bit for looks.
  10. Optional: Sew a pocket for the battery
    • For now the battery fits well if I slide it in in-between the fabric halves under the board, and the pressure of the tight bracer against my wrist generally keeps it from falling out. However I might end up sewing a little pocket to slide the battery into.

Step 4: You're Finished! (heh)

Hooray! You're acutely aware of your own mortality! Use this information to stop making stupid ego decisions such as, but not limited to:

  • Being mean to people
  • Holding grudges
  • Not Saying "I Love You" at the end of conversations with loved ones
  • Wasting your life in a career you hate
  • Putting off things for later
  • Letting fear dominate your actions

But really, PSA-time:
Communicate with your loved ones about your and their wishes regarding their remains, including getting wills written, and Advance Directives filled out:
http://www.aarp.org/home-family/caregiving/free-pr...
and be kind to one another, everybody is going to die, and chances are it might be sooner than "a long time from now".

I hope this instructable was coherent! Please let me know if there's anything I should flesh out.

Thanks for reading this far :)

P.S. if you try and bring this through airport security, and they take it out for inspection since all they see in the X-Ray screen is a sketchy circuit board with a bunch of wires sticking out -- don't fumble and accidentally say it's just a "death device".

Yours in life and death,

DeadAugust