Introduction

For our final project, our team was paired with an older friend, Irv, to develop a “convenience machine” he could use to improve his life. Over the course of a month, we interviewed him on his daily activities, developed a prototype, and refined a final model that Irv could take home and use. By employing methods of Physical Computing, including programming, circuitry, and physical fabrication, we created an “interactive itinerary” device unique to Irv’s busy lifestyle.

What we built

The Interactive Itinerary is a digital reminder system (think Google Calendar) that feels like an analog device. The user can set alarms for tasks or events without having to type on a small and sensitive touchscreen. Instead, the user can write down the task on a piece of paper and set the alarm using built in knobs.
The interactive itinerary prototype has 3 key components: 1) Two knobs that set the task and alarm 2) the digital display that shows task, time, and set time 3) a vibrating motor that acts as an alarm. To use the interactive itinerary, the user writes down a task on a piece of paper with an empty list from 1 – 10. Using the task knob, the user can turn to the desired task number turn the alarm on. Then, they would set the time using the time knob. When the alarm starts to buzz, the user can turn off the alarm with the task knob.

Outer housing of itinerary is bound with elastic. The knobs and buttons are protected by the sides but still accessible without having to open the device.

Inside of itinerary displays an LCD screen, casing for electronics, and notepad.

 

“Task” and “time” knobs for programming the device, indicator LED, and sliding mechanism to access electronics.

The top side of the electronics housing reveals the on/off button, “snooze” button for offsetting an alarm, and charging port.

A small manual is mounted under the notepad for troubleshooting and quick reference.

Sliding mechanism allows access to RTC for battery changes, arduino reset button, and any other troubleshooting issues.

How it’s used:

On a normal day, Irv wakes up and unplugs the device from the charging port. On the notepad, he writes down his tasks for the day and the time they occur (i.e., medications, tennis meetings, cultural events). He can then use the knobs on the device to set reminders for the tasks he would like to be reminded of. Irv then closes the journal and takes it with him throughout the day. When a reminder goes off, the journal will buzz and flash, and the task will appear on screen. He can then press a button to turn off the alert, or push “snooze” to delay the reminder for another 15 minutes.

How we got here

Following the formative critique phase of this project, our team created a list of milestones we intended to hit for our final meeting with Irv. After showcasing our first prototype at the formative critique to Irv, we learned that a 15 minute snooze button would be a useful addition to the device. We learned that Irv preferred to set alarms in military time and would like an on/off switch on the device to conserve power.

We created a to do list with Irv to ensure that the final product suits his needs and ranked it based on importance.

The snooze button and on/off button were relatively easy to set up. The most important part about the snooze and on/off button is picking a button that has the right tactile feeling. After the formative critique, we went to the physical computing lab and pulled out many button options for Irv to test. He settled on a red momentary push button for the snooze button and a black latching push button for the on/off switch.

Irv tests different buttons for the new features on his interactive itinerary.

The next thing we wanted to add to the interactive itinerary is displaying the task “home page” when the alarm for that task goes off. This feature, which wasn’t in our first iteration, makes sense because when the alarm goes off, we would want to know which task alarm was going off. So, when the alarm goes off now, the task number that is ringing is also displayed on the LCD. Without this feature, the user would have to look at the written list of tasks and check each alarm time to see which task was going off.

 

In order to manage all our coding iterations and keep track of our goals, we wrote comments on our new versions to communicate what was working, what wasn’t, and what our next steps should be.

To make the device portable, we intended to make the device battery powered. We ordered a lithium ion battery and a chip so that Irv can charge the device at night. The chip ensures that the battery charges without overcharging the lithium ion battery. After setting up the battery, we soldered the on/off switch to the battery, which acts as an on/off switch for the device. SInce the lithium ion battery is quite large, our goal to make the device smaller was getting harder.

Once all the necessary components were bought and collected, we laid out the device components to determine the best way to stack the device to minimize the space. As shown in the picture below, the device would already be very cramped, even before soldering everything together. Since we were no longer considering bootloading our device onto a smaller device, we had to stick to the arduino uno. So at this point in the process, our goal to make the device “pocket-sized” was becoming less and less feasible.

In an ideal set up, the LCD screen would sit at the top of the device. The charging port is in the top left of the box. The snooze button and the on/off button would rest on the top, closer to the spine of the box.

 

While we knew what the general layout of our itinerary would be, we explored ways we could configure the electronic and tactile components to save space. We also thought of ways we could construct a sliding mechanism with lasercut material that would allow the electronics to be accessible without force.

While designing the prototype for the device casing, we considered several materials for the electronic housing. Acrylic, while sturdy and aesthetically pleasing, is prone to shattering. So if Irv decided to put this device in his back pocket, or applied too much pressure to it, it might crack under pressure, rendering the casing useless. Styrene is a plastic that is a bit more flexible but sturdy as well. Ultimately, styrene would not have been ideal to use as our electronic casing because we needed to make a case that had precise cuts and we would not be able to laser cut styrene. So, we decided that ⅛” birch plywood was the best material to use for the device. We also decided to include a sliding lid on the electronic casing so that if Irv needed to replace the RTC battery or reset the arduino to troubleshoot.

The sliding mechanism consisted of two pieces of plywood glued together that fit into the top facing of the case. To keep the lid on, a support bar was built under the top facing. That way, the lid can rest on the support bar.

In addition, the laser cutters at TechSpark burn more, cut slower, and are more volatile than the laser cutters in IDeATe. Luckily, the first print was just a prototype; so we planned on printing the final casing in IDeATe. The first prototype was also larger than we needed. There was a lot of room inside the casing. We were able to make the case thinner and narrower. While the notebook would still not be pocket-sized, it was still considerably smaller.

After considering different methods of fabrication for the outer casing, we decided traditional “bookbinding” techniques would best fit Irv’s aesthetic. Inspired by moleskine journals, we used flexible chipboard, faux leather, and elastic to create a housing that would be sturdy, comfortable, and stylish.

Conclusion

After the final crit, we added one last feature to our box because of a comment made to “make the alarm stronger” if it couldn’t be kept in his pocket. Since we were planning to make the box small, we didn’t account for possible issues if it was too large to fit into a pocket. Some of these issues would be would Irv notice the alarm going off if it wasn’t on his person. Our solution was to add a small LED next to the task and time knobs that would blink at the same rate that the buzzer would be going off. This would allow Irv to see the alarm going off as well as feel it. As he’s told us that his hearing isn’t the best, we thought we’d try to stimulate his other senses with the alarm.

Working with Irv was more than just an opportunity to gain insight on the life of an older person, but a beneficial experience in collaborating with a client. Not only did learning about Irv’s lifestyle allow us to empathize with his daily needs, it also taught us how to iterate and design under constraints that can assist virtually anyone. While the itinerary was developed for his particular lifestyle and activities, all of us can benefit from a method of keeping track of lists with time notifications. In principle, this project was a great introduction to universal design. By solving the problems of overlooked populations rather than the “average person,” we can innovate more effectively and inclusively.

Moreover, we believe that having a person to design for other than ourselves pushed us further to create something functional and intuitive. Since we realized Irv wouldn’t have the means to fix the RTC if it reset, for example, we decided late into the project to build in a time-setting function into the program. This mindset also prompted us to add in a sliding compartment to access the electronics, a “sleep” mode to save power, and a reference manual for troubleshooting. If we hadn’t had an actual client to work with, it would have been easier to let these considerations slide when you know you can can quickly reupload, solder or glue something back together. Knowing that Irv will be keeping this in his home and hopefully trying it out, we feel more confident knowing that we built in features to extend the project’s lifespan.

Code and Files

schematic

Code
/*  Reminder - Final Project
    Eliza Pratt
    Helen Yu
    Eric Mendez

***********************************************************
description:
  Use "Task" and "Time" button knobs to set alarms throughout the day. The LCD display shows
  the current task (1 to 9) and the set alarm time. If the task button is pushed, the alarm
  is turned on. There is a snooze button as well, so if you'd like to be snooze the alarm, the
  button adds 15 min to the on-going alarm. The display will reflect that the alarm was snoozed.
  

Pin mapping:
  - The task and time knobs have 3 input pins each and those are pins 2-7
  - The snooze button is 8
  - The buzzer pin is 9
  - The pin for the LED is 11
  - The SCA/SCL pins are used on both the LCD screen and RTC (real time clock)
  - If adding a Lipo battery and power regulating chip (see our schematic)
    - use a switch to interupt the connection from the chip to Vin on the Arduino

example code used:
  - Used examples from TimeLib, LiquidCrystal, DS1307RTC, and Encoder to help set up
  their usage in this project.

***********************************************************
*/
#include <Wire.h>
#include <TimeLib.h>
#include <LiquidCrystal_I2C.h>
#include <DS1307RTC.h>
#include <Encoder.h>

// set up lcd
LiquidCrystal_I2C screen(0x27, 16, 2);

// time elements
tmElements_t tm;

Encoder taskKnob(2, 4);
Encoder timeKnob(3, 6);

// task and time knobs / buttons
const int TASK_BUTTON = 5; // yellow wire
const int TIME_BUTTON = 7; // yellow wire
const int SNOOZE_BUTTON = 8; // white wire
const int MOTORPIN = 9;
const int LEDPIN = 11;

long oldTaskPosition  = -999;
long oldTimePosition  = -999;

unsigned long motorTimer = 0;
unsigned long screenLight = 0;

bool motorState = false;
bool oldTimeState = false; // stores button state
bool oldTaskState = false; // stores old button state (time)
bool oldSnoozeState = false; // stores old button state (snooze)

// task
int task_pos = 0; // dial position
int time_pos = 0; // time dial position
int currentBuzz = -1;
// stores task position that is currently buzzing (-1 = nothing)
int timeMax = 24; // limit for dial turn (changes for hours/min/am-pm)
int timeButtonCount = 0; // hours/minutes/ampm
int lcdHour = 0; // for printing hour on LCD screen
int lcdMinute = 0;
int lcdTask = 0;
int rtcHour = 0;
int rtcMinute = 0;

// for each task (10 tasks)
int alarmHour[] = {12, 12, 12, 12, 12, 12, 12, 12, 12, 12};
int alarmMinute[] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
int alarmOn[] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0};

bool snoozeOn[] = {false, false, false, false, false, false, false, false, false, false};


void setup() {
  pinMode(MOTORPIN, OUTPUT);
  pinMode (TASK_BUTTON, INPUT_PULLUP);
  pinMode (TIME_BUTTON, INPUT_PULLUP);
  pinMode (SNOOZE_BUTTON, INPUT_PULLUP);
  pinMode(LEDPIN, OUTPUT);
  Serial.begin (9600);

  setSyncProvider(RTC.get);   // the function to get the time from the RTC

  screen.init();
  screen.backlight();
}

void loop() {
  //------------------------------------
  // Snooze
  //------------------------------------
  int snoozeState = digitalRead(SNOOZE_BUTTON); // task: enabled or not

  if (!snoozeState && oldSnoozeState) {
    screenLight = millis();
    Serial.println("SNOOZE");
    
    if (currentBuzz > -1) {
      if (alarmMinute[currentBuzz] < 45) {
        alarmMinute[currentBuzz] += 15;
         
      } else { 
        // alarmMinute[currentBuzz] >= 45
        alarmHour[currentBuzz] += 1;
        alarmMinute[currentBuzz] += (currentBuzz - 45);
      }
      
      snoozeOn[currentBuzz] = true;
      currentBuzz = -1;
    }
  }
  
  oldSnoozeState = snoozeState; // button thing

  //------------------------------------
  // Buzz Alarm
  //------------------------------------

  // loops through tasks
  for (int i = 0; i < 10; i++) {
    // checking if alarm needs to be activated
    
    if (hour() == alarmHour[i] && minute() == alarmMinute[i] && alarmOn[i] == 1) {
      screen.backlight();
      buzzing(); // pulses motor
      Serial.println("ALARM ON");
      currentBuzz = i; // save task position of current alarm going off
    }
  }
  
  if (currentBuzz == -1) {
    analogWrite(MOTORPIN, 0);
    digitalWrite(LEDPIN, LOW);
  }

  taskFunction();
  taskButton();


  //---------------------------------------
  // Time knob
  //---------------------------------------

  if (!(timeButtonCount % 3 == 0)) {
    
    int timeKnobMax = (timeMax) * 4;
    long newTimePosition;

    // account for 4 times extra readings
    if (timeKnob.read() > timeKnobMax) timeKnob.write(0);
    else if (timeKnob.read() < 0) timeKnob.write(timeKnobMax);

    newTimePosition = timeKnob.read();

    //TIME IS CHANGED
    if (newTimePosition != oldTimePosition) {
      screenLight = millis();
      oldTimePosition = newTimePosition;
      time_pos = oldTimePosition / 4;
      Serial.println(time_pos);
    }
  } else {
    timeKnob.write(0);
  }

  //------------------------------------
  // Time button
  //------------------------------------

  //Read time button
  int timeState = digitalRead(TIME_BUTTON);

  if (!timeState && oldTimeState) {
    screenLight = millis();
    screen.clear();
    Serial.print("LCD hour: ");
    Serial.println(lcdHour);
    timeKnob.write(0); // reset time knob position back to 0

    // display correct task and alarm time corresponding for that task
    if (task_pos < 9) {
      if (timeButtonCount % 3 == 1) alarmHour[task_pos] = time_pos;
      else if (timeButtonCount % 3 == 2) alarmMinute[task_pos] = time_pos;
    }
    // RESET time mode
    else if (task_pos == 9) { 
      if (timeButtonCount % 3 == 1) rtcHour = time_pos;
      else if (timeButtonCount % 3 == 2) rtcMinute = time_pos;
    }

    Serial.print(alarmHour[task_pos]);
    Serial.print(" : ");
    Serial.print(alarmMinute[task_pos]);
    Serial.print(" : ");
    Serial.println(alarmOn[task_pos]);

    timeButtonCount++;
    Serial.println(timeButtonCount);
    time_pos = 0;
    delay(300);
  }
  oldTimeState = timeState;
  
  // BUZZING
  if (currentBuzz > -1) {
    //display current task
    lcdHour = alarmHour[currentBuzz];
    lcdMinute = alarmMinute[currentBuzz];
    taskKnob.write(currentBuzz);
    task_pos = currentBuzz;
  }
  // set real time clock
  else {
    if (timeButtonCount % 3 == 0) {
      if (task_pos == 9) {
        lcdHour = rtcHour;
        lcdMinute = rtcMinute;
        
      } else {
        lcdHour = alarmHour[task_pos]; // display set hour on LCD
        lcdMinute = alarmMinute[task_pos];
        
      }
    }
    else if (timeButtonCount % 3 == 1) { //setting hour
      timeMax = 24;
      lcdHour = time_pos; //displays changing hour on LCD
      
      if (task_pos == 9) {
        lcdMinute = rtcMinute;
        
      } else {
        lcdMinute = alarmMinute[task_pos];
        
      }
    }
    else if (timeButtonCount % 3 == 2) { //setting minute
      timeMax = 60;
      lcdMinute = time_pos;
      
      if (task_pos == 9) {
        lcdHour = rtcHour;
        
      } else {
        lcdHour = alarmHour[task_pos];
        
      }
    }
  }


  screen.home();
  if (task_pos == 9) {
    screen.print("Reset Time");
  } else {
    screen.print("Task ");
    screen.print(task_pos + 1);
    if (snoozeOn[task_pos]) screen.print("*");
  }

  screen.setCursor(11, 0);
  if (hour() < 10) screen.print("0"); // 0 offset if < 10
  screen.print(hour());
  screen.setCursor(13, 0);
  screen.print(":");
  if (minute() < 10) screen.print("0"); // 0 offset if < 10
  screen.print(minute());

  screen.setCursor(0, 1);
  if (lcdHour < 10) screen.print("0"); // 0 offset if < 10
  screen.print(lcdHour);

  screen.setCursor(2, 1);
  screen.print(":");
  if (lcdMinute < 10) screen.print("0"); // 0 offset if < 10
  screen.print(lcdMinute);

  screen.setCursor(6, 1);

  if (alarmOn[task_pos] == 1) { // set alarm on
    // alarm on
    screen.setCursor(13, 1);
    if (task_pos == 9) {
      screen.print("SET");
    } else {
      screen.print("ON");
    }
  } else if (alarmOn[task_pos] == 0) { // set alarm off
    // alarm off
    screen.setCursor(13, 1);
    if (task_pos == 9) {
      screen.print("SET");
    } else {
      screen.print("OFF");
    }
  }

  //After a minute of inactivity, turn off the LCD backlight to save power
  if (screenLight + 60000 <= millis()) screen.noBacklight();
  else screen.backlight();
}


//pulses motor
void buzzing() {
  if (motorTimer + 1000 <= millis()) {
    motorState = !motorState; //flip state
    motorTimer = millis(); // reset timer
  }
  if (motorState) {
    analogWrite(MOTORPIN, 200);
    digitalWrite(LEDPIN, HIGH);
  }
  else {
    analogWrite(MOTORPIN, 0);
    digitalWrite(LEDPIN, LOW);
  }
}


//TASK knob
void taskFunction() {
  //Zachs method for looping the knob
  if (taskKnob.read() > 36) taskKnob.write(0);
  else if (taskKnob.read() < 0) taskKnob.write(36);
  long newTaskPosition = taskKnob.read();

  //TASK IS CHANGED
  if (newTaskPosition != oldTaskPosition) {
    oldTaskPosition = newTaskPosition;
    screenLight = millis();

    // reset time position and button count when task is changed
    timeButtonCount = 0;
    time_pos = 0;
    screen.clear();

    //print task position and time set for that task
    Serial.print("old task: ");
    Serial.print(oldTaskPosition);
    Serial.print(" / task_pos: ");
    Serial.println(task_pos);
    Serial.print(" / printing: ");
    Serial.println(newTaskPosition / 4);
  }

  task_pos = newTaskPosition / 4;
}


/*------------------------------------
  Task button
    if task knob is on position 11 -
    DISPLAY "RESET TIME" instead of "task 11"
    use time button to set current time
    hit task button to set
    when pressed, rtc.setTime(hours, minutes, seconds)
------------------------------------*/
void taskButton() {
  int taskState = digitalRead(TASK_BUTTON); // task: enabled or not

  if (!taskState && oldTaskState) {
    screenLight = millis();

    // RESET time on task 10
    if (task_pos == 9) {
      // set hour:min:sec
      tm.Hour = rtcHour;
      tm.Minute = rtcMinute;
      tm.Second = 0;
      // Apr 22 2019
      tm.Month = 4;
      tm.Day = 22;
      tm.Year = CalendarYrToTm(2019);
      if (RTC.write(tm)) {
        setSyncProvider(RTC.get);
      }
    }
    else { // regular alarm function
      if (snoozeOn[task_pos]) snoozeOn[task_pos] = false;
      
      // ***change 0 and 1 -> off and on for printing
      // setting the alarm: if alarm is off, turn it on
      if (alarmOn[task_pos] == 0 && currentBuzz == -1) {
        alarmOn[task_pos] = 1;
      }
      
      //if the alarm is set as on (and not buzzing), turn it off
      else if (alarmOn[task_pos] == 1 && currentBuzz == -1) alarmOn[task_pos] = 0;

      //if alarm is buzzing, we know alarm is on, so we turn it off
      if (currentBuzz > -1) {
        screen.clear();
        alarmOn[currentBuzz] = 0; // disable alarm
        analogWrite(MOTORPIN, 0); // turn off buzzing alarm
        digitalWrite(LEDPIN, LOW);
        Serial.println("ALARM OFF");
        currentBuzz = -1;
        taskKnob.write(0);
      }

      Serial.print("alarm on: ");
      Serial.println(alarmOn[task_pos]);

      // Alarm on
      if (alarmOn[task_pos] == 1) {
        screen.clear();
        screen.setCursor(13, 1);
        
        if (task_pos == 9) {
          screen.print("SET");
          
        } else {
          screen.print("ON");
          
        }
      } else if (alarmOn[task_pos] == 0) {
        // alarm off
        screen.clear();
        screen.setCursor(13, 1);
        
        if (task_pos == 9) {
          screen.print("SET");
          
        } else {
          screen.print("OFF");
          
        }
      }
      delay(300);
    }
  }
  oldTaskState = taskState; // button thing
}