Sleep Tracker & Display

Project by Vincent Wolfe

Box, much longer lengthwise. Lot of wires coming out. Gives the impression of being unfinished but it could be a bit sleek. Box is mostly wooden, project is shot on white backdrop.

Front view.

What is this device?

I've been tracking my sleep for the past three-ish years, since about when I first noticed a problem with my circadian rhythm. The original sleep-tracking documents I kept either by hand or in a PhotoShop file. At the beginning of this school year I updated the process to Excel, and that's where I've been logging it since. The device constructed for this class is meant to be a way to enter this sleep data, store it on a file, and then display it.

Basic use:

24 buttons on the front can be used to enter data. Each button corresponds to the LED above it, which can display a state of black, white, or "no data". A switch on the side toggles between a 'write mode' where data can be entered, and a 'lock mode' where data cannot be changed and accidentally overwritten. A dial on the side can be used to scroll through dates, or pressed to save entered data to a file on a SD card. On the top, the date and year corresponding to the data on the LEDs is shown. The button to the left of the date steps back one day, the button on the right steps forward one day.

Front panel with the LEDs and buttons is flipped down. The Arduino and wires are only a little pushed inside the box-container.

Innards.

Close up on the LED and button display on the front. A time is above an LED is above a button. Cut into a glossy black acrylic.

LED display and buttons.

"Sleep Charts" and visual format:

I've been using the same format since I started tracking sleep. These grids are immediately intuitive and legible to me, so it's the basis for how I designed my device to display. A row is 24 squares across, 1 square equals 1 hour. The leftmost column represents Midnight-1AM, the rightmost represents 11PM-Midnight. A black square is an hour I was asleep for. A square that's white (or any color other than black) is an hour I was awake for. 

Digital charts from quarantine, first year of college, and working a job shift.

3 very tall grids with 24 collumns each. Black squares form a repeating diagonal in the first, but less so in the other two. Those two have occasional color-coded blocks, the first does not.
Photo of a sketchbook spread. The left page has a date and wake and sleep times for each day. The right page is a hand-drawn recreation of the 24 collumn grid format.

Example of hand written chart.


The same 24 collumn grid format, now in Excel. Wow, look, Excel equations. Every hour is color-coded to mean something now. At the bottom is a line graph representing the same data, but it is possibly indecipherable because there are too many lines.

Current chart in Excel with line graph.

Why have this thing or the charts though like really?

The benefits to having this data are probably stronger for me- with regards to having the sleep disorder- than for most people. For the hours I've slept any given day, time and duration vary heavily and generally unpredictably. I find the latest ~4 days of sleep are the ones that are relevant to a current day's state alertness. If I didn't have a log to reference, I would have to remember those days' times and durations in my head at all times. I feel like this is generally not a common issue- ex. if you've skipped a night's sleep, you'll expect the next day you'll sleep more than average. You probably rarely do this, and expect to sleep about 8 hours while the sun is set. If I skip a night's sleep- I might get 3 hours rest around noon the next day, later go unconscious for 16 hours, and then be confused why I'm so fatigued waking up off 5 hours sleep since I know the majority of yesterday was spent in bed.

On timescales of a week or longer, I can capture some trends in mood and stress, and can make a prediction on where the circadian rhythm will land me. I can usually expect my circadian rhythm to be around 25 hours, so I would expect to need to sleep an hour later than I did yesterday. Wrong!!! This would be without interference of class schedules, and various incentives to be diurnal vs. nocturnal. So, while sleep across 4 days may be erratic due to scheduled responsibilities, usually a week's worth of data is enough of a trend to guess at when I'm more likely to need to sleep.

With mood and stress- those are visible in regularity and amount of time slept. Days that aren't tracked are usually because I was sleeping too much or too fatigued to bother logging, and I can assume the mood was low. Less than 6 hours average for more than a week is the danger-zone for more severe sleep deprivation, and I can assume I was very stressed. When the sleep is in irregular little chunks, it can be assumed I'm just generally not having a good time and am having to prioritize some task over wellbeing. The opposite of that- when there's the most overlap between a series of days (ex. the diagonal slope is very visible, or there's an unbroken column) feel healthier and less concussive.  Other states of mind and the such are sometimes visible in the charts, but require memory to fill in what the sleep cycle represents- ex. repeating diagonals with no ability to hold sleep down over quarantine, ~36 hour circadian rhythm during a Winter Break. Weather I am relying on interpreting data or supplementing in in-head memory,  these charts help me remember the past couple years in finer detail and are a useful tool for daily health.

Process, and review of process:

Fun Fact: we've gotten to the point I've been on my feet all day and I accidentally spent too long writing the above section before I realized it isn't exactly relevant to any of the things on the rubric and here we are, and I said I'd have this done tommorow.

Cont with relavant content to the project: I remember I started with the buttons and the LEDs and wiring that up and it was going okay. Then I had 30+ wires plugged in and it was incredibly unwieldy and hard to manage. So then when I got around to trying to laser cut a box for it the files were angry. Then we hit Fall Break and I'd had the project going too long that it was best to just drop it half-way unfinished and try to give myself a reset instead of slowly dragging on.

Response to class crits:

The week leading up to the halfway crit I was very busy and barely had time to work on anything, then managed to twice not communicate I was asking for an extension, and when I was up to give the crit was too exhausted to really explain it well. I did not find the advice from this crit very helpful.

The final crit mostly says "nice fabrication" because that's really all I had done.

What I learned:

So apparently if I say "I'm too tired to do the coding or the harder parts for this, let's see if I can work on something repetitive and requiring less alertness"- because I am trying to avoid making too many mistakes on the harder task- I will then make the mistakes on the relatively easy task instead. This is equally annoying. Also it got very annoying to be trying to learn Python and C at the same time there is no such thing as a coding language they just scrawl runes on GitHub like our ancestors did with mud in caves and its all uninterpretable.

Self crit and next steps:

I feel like, at an emotional level, this will not be safe to touch unless I have a complete game plan, or can manage to wiggle out of something else and redo most-all the fabrication. Really hard to find a good order to work on it in. If nothing is fabricated or in-place in a box, then it's a horrible jumble of wires that is so horrible it becomes difficult to debug software. If it all goes in a box first, then it becomes difficult to add, fix, or change hardware. Also I'm mad at the fabrication part where the protoboards for the buttons and the LEDs do not line up unless either the buttons or the LEDs get cut into 24 free-floating bits and then glued in individually.

The best solution I can come up with is to make this version with zero care to beauty or really even practicality. Like just glue everything down to a piece of chipboard so it's not free-floating, but not encased either. Then get that programmed so it works. Then completely disassemble the whole thing and probably remake a lot of it.

I do not know why this couldn't've happened the first time, it's just really hard to juggle writing code, finding if I need to add a component (ex. didn't originally plan have a RTC in it), knowing when to move something from breadboard to protoboard, and knowing when to start mounting it to a final box.


Device at an earlier stage. No box, wires are worse than before, scattered breadboarding.

Guts.

Device at an earlier stage. No box, wires are worse than before, scattered breadboarding.

Viscera.

Device at the stage in photos at the top of the page, but still a bunch of wire mess.

Have you ever read Lovecraft?

Square red button and 4 digit 7-segment display slotted into black acrylic rectangle.

What the acrylic insert on the top of the box would look like.

WIN_20231009_21_28_41_Pro.mp4

This was the only recorded video of it. Also the earliest chronologically of all the documentation. Look at his sweet optimism.

Technical information.

Arduino Cthulu

Schematic.

#include <SD.h>

int chipSelect = 53;

File file;


const int switchPin = 49;

int switchState = 0;


#include "RTClib.h"

RTC_Millis rtc;


#include "HT16K33.h"  //4 digit displays

HT16K33 left(0x71);

HT16K33 right(0x70);

uint32_t counter = 0;



#include <Encoder.h>

Encoder myEnc(3, 2);

#define SW 8

unsigned long rotaryTime;

bool reset = false;

int rotaryDuration = 0;


#include <PololuLedStrip.h>

PololuLedStrip<13> ledStrip;

#define ledCount 24

rgb_color colors[ledCount];


char colorName[25] = "bbbbbbbbbbbbbbbbbbbbbbb"

                     "\0";




const int button0 = 9;

const int button1 = 10;

const int button2 = 11;

const int button3 = 7;

const int button4 = 6;

const int button5 = 5;

const int button6 = 44;

const int button7 = 42;

const int button8 = 40;

const int button9 = 45;

const int button10 = 43;

const int button11 = 41;

const int button12 = 36;

const int button13 = 34;

const int button14 = 32;

const int button15 = 37;

const int button16 = 35;

const int button17 = 33;

const int button18 = 28;

const int button19 = 26;

const int button20 = 24;

const int button21 = 29;

const int button22 = 27;

const int button23 = 25;


int last0;

int last1;

int last2;

int last3;

int last4;

int last5;

int last6;

int last7;

int last8;

int last9;

int last10;

int last11;

int last12;

int last13;

int last14;

int last15;

int last16;

int last17;

int last18;

int last19;

int last20;

int last21;

int last22;

int last23;


int now0;

int now1;

int now2;

int now3;

int now4;

int now5;

int now6;

int now7;

int now8;

int now9;

int now10;

int now11;

int now12;

int now13;

int now14;

int now15;

int now16;

int now17;

int now18;

int now19;

int now20;

int now21;

int now22;

int now23;


const int dayPlus = 12;  // day +-1 buttons

bool dayPlusState;

int currentDayPlusStateCLK;

int lastDayPlusStateCLK;

unsigned long lastDayPlusPress = 0;


const int dayMinus = 4;  // day +-1 buttons

bool dayMinusState;

int currentDayMinusStateCLK;

int lastDayMinusStateCLK;

unsigned long lastDayMinusPress = 0;


int dayPosition = 0;

int dayPlusCount = 0;

int maxDay = 760;


void setup() {

  Serial.begin(9600);

  Wire.begin();


  file = SD.open("file2.txt", FILE_WRITE);  // open "file.txt" to write data


  pinMode(switchPin, INPUT);

  pinMode(dayPlus, INPUT);

  pinMode(dayMinus, INPUT);


  left.begin();

  right.begin();

  Wire.setClock(100000);

  left.displayOn();

  right.displayOn();


  pinMode(chipSelect, OUTPUT);

  if (!SD.begin(chipSelect)) {

    Serial.println("cool squares");

    Serial.println("Could not initialize SD card.");

  } else {

    Serial.println("cool squares");

    Serial.println("yup there's an sd card");

  }

  pinMode(SW, INPUT_PULLUP);

  pinMode(button0, INPUT);

  pinMode(button1, INPUT);

  pinMode(button2, INPUT);


  for (int i = 0; i < ledCount; i++) {

    colorName[i] = 'b';

  }

#ifndef ESP8266

  while (!Serial)

    ;  // wait for serial port to connect. Needed for native USB

#endif

  rtc.begin(DateTime(F(__DATE__), F(__TIME__)));

  // January 21, 2014 at 3am you would call:

  // rtc.adjust(DateTime(2014, 1, 21, 3, 0, 0));

}


long oldPosition = -999;


void loop() {

  rgb_color w;

  w.red = 50;

  w.green = 50;

  w.blue = 50;


  rgb_color b;

  b.red = 0;

  b.green = 0;

  b.blue = 0;


  rgb_color g;

  g.red = 1;

  g.green = 1;

  g.blue = 1;


  long newPosition = constrain(myEnc.read(), 0, maxDay);  //760 should update to be [days after 9/20/20]


  if (newPosition != oldPosition) {  //rotary encoder position. Needs a timer or something to adjust how smooth it goes.

    oldPosition = newPosition;

    dayPosition = newPosition + dayPlusCount;  //this is the actual day count being read

    Serial.println(dayPosition);

  }


  int lastRotaryState = rotaryDuration;

  int currentRotaryState = digitalRead(SW);

  dayPlusState = digitalRead(dayPlus);

  dayMinusState = digitalRead(dayMinus);

  lastDayPlusStateCLK = currentDayPlusStateCLK;

  lastDayMinusStateCLK = currentDayMinusStateCLK;


  //DateTime now = rtc.now();                            //unused blah blah


  DateTime future(1600560000 + dayPosition * 86400L);  // 9/20/20 + days since


  if (dayPlusState == HIGH) {

    if (millis() - lastDayPlusPress > 50) {

      dayPlusCount += 1;

      dayPosition = constrain(newPosition + dayPlusCount, 0, maxDay);

      Serial.println(dayPosition);

    }

    lastDayPlusPress = millis();

  }

  if (dayMinusState == HIGH) {

    if (millis() - lastDayMinusPress > 50) {

      dayPlusCount -= 1;

      dayPosition = constrain(newPosition + dayPlusCount, 0, maxDay);

      Serial.println(dayPosition);

    }

    lastDayMinusPress = millis();

  }


  if (!currentRotaryState && !reset) {

    rotaryTime = millis();

    reset = true;

  }

  if (!currentRotaryState && (millis() - rotaryTime > 50)) {

    rotaryDuration = 1;

  } else {

    rotaryDuration = 0;

  }

  if (!currentRotaryState && (millis() - rotaryTime > 1500)) {  //1.5 sec long press. changeable.

    rotaryDuration = 2;

  } else if (rotaryDuration != 1) {

    rotaryDuration = 0;

  }

  if (currentRotaryState && reset) {

    reset = false;

  }


  if (lastRotaryState == 1 && currentRotaryState) {  //short press

    file = SD.open("file2.txt", FILE_WRITE);

    if (file) {

      if (future.month() < 10) {

        file.print('0');

      }

      file.print(future.month(), DEC);

      file.print('/');

      if (future.day() < 10) {

        file.print('0');

      }

      file.print(future.day(), DEC);

      file.print('/');

      file.print(future.year(), DEC);

      file.println(colorName);

      file.close();

    } else {

      Serial.println("Could not open file (writing).");

    }

    file = SD.open("file2.txt", FILE_READ);

    if (file) {

      Serial.println("--- Reading start ---");

      char character;

      while ((character = file.read()) != -1) {  // this while loop reads data stored in "file.txt" and prints it to serial monitor

        Serial.print(character);

      }

      file.close();

    } else {

      Serial.println("Could not open file (reading).");

    }

  }

  if (lastRotaryState == 2 && currentRotaryState) {  //long press

    for (int i = 0; i < ledCount; i++) {

      colorName[i] = 'g';

      colors[i] = g;

    }

  }


  switchState = digitalRead(switchPin);

  // if (switchState == HIGH) {

  //   Serial.println("1");

  // } else {

  //   Serial.println("0");

  // }





  last0 = now0;

  last1 = now1;

  last2 = now2;

  last3 = now3;

  last4 = now4;

  last5 = now5;

  last6 = now6;

  last7 = now7;

  last8 = now8;

  last9 = now9;

  last10 = now10;

  last11 = now11;

  last12 = now12;

  last13 = now13;

  last14 = now14;

  last15 = now15;

  last16 = now16;

  last17 = now17;

  last18 = now18;

  last19 = now19;

  last20 = now20;

  last21 = now21;

  last22 = now22;

  last23 = now23;


  now0 = digitalRead(button0);

  now1 = digitalRead(button1);

  now2 = digitalRead(button2);

  now3 = digitalRead(button3);

  now4 = digitalRead(button4);

  now5 = digitalRead(button5);

  now6 = digitalRead(button6);

  now7 = digitalRead(button7);

  now8 = digitalRead(button8);

  now9 = digitalRead(button9);

  now10 = digitalRead(button10);

  now11 = digitalRead(button11);

  now12 = digitalRead(button12);

  now13 = digitalRead(button13);

  now14 = digitalRead(button14);

  now15 = digitalRead(button15);

  now16 = digitalRead(button16);

  now17 = digitalRead(button17);

  now18 = digitalRead(button18);

  now19 = digitalRead(button19);

  now20 = digitalRead(button20);

  now21 = digitalRead(button21);

  now22 = digitalRead(button22);

  now23 = digitalRead(button23);


  if (last0 == HIGH && now0 == LOW) {

    if (colorName[0] == 'b') {

      colors[0] = w;

      colorName[0] = 'w';

    } else {

      colors[0] = b;

      colorName[0] = 'b';

    }

  }

  if (last1 == HIGH && now1 == LOW) {

    if (colorName[1] == 'b') {

      colors[1] = w;

      colorName[1] = 'w';

    } else {

      colors[1] = b;

      colorName[1] = 'b';

    }

  }

  if (last2 == HIGH && now2 == LOW) {

    if (colorName[2] == 'b') {

      colors[2] = w;

      colorName[2] = 'w';

    } else {

      colors[2] = b;

      colorName[2] = 'b';

    }

  }

  if (last3 == HIGH && now3 == LOW) {

    if (colorName[3] == 'b') {

      colors[3] = w;

      colorName[3] = 'w';

    } else {

      colors[3] = b;

      colorName[3] = 'b';

    }

  }

  if (last4 == HIGH && now4 == LOW) {

    if (colorName[4] == 'b') {

      colors[4] = w;

      colorName[4] = 'w';

    } else {

      colors[4] = b;

      colorName[4] = 'b';

    }

  }

  if (last5 == HIGH && now5 == LOW) {

    if (colorName[5] == 'b') {

      colors[5] = w;

      colorName[5] = 'w';

    } else {

      colors[5] = b;

      colorName[5] = 'b';

    }

  }

  if (last6 == HIGH && now6 == LOW) {

    if (colorName[6] == 'b') {

      colors[6] = w;

      colorName[6] = 'w';

    } else {

      colors[6] = b;

      colorName[6] = 'b';

    }

  }

  if (last7 == HIGH && now7 == LOW) {

    if (colorName[7] == 'b') {

      colors[7] = w;

      colorName[7] = 'w';

    } else {

      colors[7] = b;

      colorName[7] = 'b';

    }

  }

  if (last8 == HIGH && now8 == LOW) {

    if (colorName[8] == 'b') {

      colors[8] = w;

      colorName[8] = 'w';

    } else {

      colors[8] = b;

      colorName[8] = 'b';

    }

  }

  if (last9 == HIGH && now9 == LOW) {

    if (colorName[9] == 'b') {

      colors[9] = w;

      colorName[9] = 'w';

    } else {

      colors[9] = b;

      colorName[9] = 'b';

    }

  }

  if (last10 == HIGH && now10 == LOW) {

    if (colorName[10] == 'b') {

      colors[10] = w;

      colorName[10] = 'w';

    } else {

      colors[10] = b;

      colorName[10] = 'b';

    }

  }

  if (last11 == HIGH && now11 == LOW) {

    if (colorName[11] == 'b') {

      colors[11] = w;

      colorName[11] = 'w';

    } else {

      colors[11] = b;

      colorName[11] = 'b';

    }

  }

  if (last12 == HIGH && now12 == LOW) {

    if (colorName[12] == 'b') {

      colors[12] = w;

      colorName[12] = 'w';

    } else {

      colors[12] = b;

      colorName[12] = 'b';

    }

  }

  if (last13 == HIGH && now13 == LOW) {

    if (colorName[13] == 'b') {

      colors[13] = w;

      colorName[13] = 'w';

    } else {

      colors[13] = b;

      colorName[13] = 'b';

    }

  }

  if (last14 == HIGH && now14 == LOW) {

    if (colorName[14] == 'b') {

      colors[14] = w;

      colorName[14] = 'w';

    } else {

      colors[14] = b;

      colorName[14] = 'b';

    }

  }

  if (last15 == HIGH && now15 == LOW) {

    if (colorName[15] == 'b') {

      colors[15] = w;

      colorName[15] = 'w';

    } else {

      colors[15] = b;

      colorName[15] = 'b';

    }

  }

  if (last16 == HIGH && now16 == LOW) {

    if (colorName[16] == 'b') {

      colors[16] = w;

      colorName[16] = 'w';

    } else {

      colors[16] = b;

      colorName[16] = 'b';

    }

  }

  if (last17 == HIGH && now17 == LOW) {

    if (colorName[17] == 'b') {

      colors[17] = w;

      colorName[17] = 'w';

    } else {

      colors[17] = b;

      colorName[17] = 'b';

    }

  }

  if (last18 == HIGH && now18 == LOW) {

    if (colorName[18] == 'b') {

      colors[18] = w;

      colorName[18] = 'w';

    } else {

      colors[18] = b;

      colorName[18] = 'b';

    }

  }

  if (last19 == HIGH && now19 == LOW) {

    if (colorName[19] == 'b') {

      colors[19] = w;

      colorName[19] = 'w';

    } else {

      colors[19] = b;

      colorName[19] = 'b';

    }

  }

  if (last20 == HIGH && now20 == LOW) {

    if (colorName[20] == 'b') {

      colors[20] = w;

      colorName[20] = 'w';

    } else {

      colors[20] = b;

      colorName[20] = 'b';

    }

  }

  if (last21 == HIGH && now21 == LOW) {

    if (colorName[21] == 'b') {

      colors[21] = w;

      colorName[21] = 'w';

    } else {

      colors[21] = b;

      colorName[21] = 'b';

    }

  }

  if (last22 == HIGH && now22 == LOW) {

    if (colorName[22] == 'b') {

      colors[22] = w;

      colorName[22] = 'w';

    } else {

      colors[22] = b;

      colorName[22] = 'b';

    }

  }

  if (last23 == HIGH && now23 == LOW) {

    if (colorName[23] == 'b') {

      colors[23] = w;

      colorName[23] = 'w';

    } else {

      colors[23] = b;

      colorName[23] = 'b';

    }

  }



  ledStrip.write(colors, ledCount);


  delay(5);

}