Project 2 – Intro to Physical Computing: Student Work fall 2019 https://courses.ideate.cmu.edu/60-223/f2019/work Intro to Physical Computing: Student Work Thu, 31 Oct 2019 06:46:38 +0000 en-US hourly 1 https://wordpress.org/?v=5.2.20 Programmable Workout Timer https://courses.ideate.cmu.edu/60-223/f2019/work/programmable-workout-timer/ Thu, 17 Oct 2019 13:16:51 +0000 https://courses.ideate.cmu.edu/60-223/f2019/work/?p=8626 Overview

The Programmable Workout Timer is a device that times a user inputed workout and alerts the user when rest and reps should start.

The user inputs workout information before the timing process begins.

During the workout, the device uses a countdown and lights to inform the user when to begin and end exercises.

A speaker alerts the user at the end of a rep or rest period.

The back of the device can be opened to change the battery and perform maintenance.

An example transition from rep in progress to rest.

Process Images and Review

The original state transition planning for the timer.

One of the biggest challenge of this project was coordinating the input and outputs of the device.  This task was particularly difficult, because the same component could interact with different variables at different times in the program.  For example, the keypad is used to enter the information for multiple variables, and the display screen will display a variety of different texts at different times.  Clearly, it was necessary to create some sort of organizational model to better coordinate these processes.

In order to achieve this task, I implemented a multi state system, where the functions of the device were dependent on the state.  Each mode would have a different input, output, and functionality.  The state choices were very natural, as each state could align with a different screen that was displayed to the user.  This decision ultimately made the programming process much less chaotic and more organized.

The code for the “Set Entry” state,  where the user enters the number of sets in their workout.

Another important design choice was made later in the process after testing my original prototype.  I found it frustrating that when I typed a wrong number, I could not delete it like on a normal phone or laptop keypad.  Therefore, I decided to turn the ‘B’ button into a backspace functionality using string manipulation.  While adding the ability to backspace, I also restricted the user from inputing non number values or blank inputs.  These simple changes to the keypad made the project much more user friendly and resistant to errors.

The original electronic prototype.  When testing this system, I realized not being able to delete inputs was frustrating.

Discussion

This project challenged me to do many tasks that I had never done before, and as a result I made many important realizations about larger scale electronics projects.  The largest of these realizations is the importance of planning the physical enclosure of a circuit.  I struggled to fit my circuit inside a reasonably portable box, because I assumed that if all components could fit then the circuit could fit.  However, I did not account for the fact that wires took up significant space and could limit the rotation of the components.  This was a large road block in developing my final product, as I had to redo the wiring for some components.  If I had planned better from the start, I could have saved a lot of time.  Another takeaway from the project is that I really enjoy the coding aspect of circuits involving Arduino’s.  This project is the first time I’ve written Arduino code of this length and magnitude, and naturally I had to learn along the way to overcome obstacles.   Learning more about the Arduino language (and C based languages in general) was a really fun and rewarding part of the process for me.  In particular, learning to manipulate strings and chars was a really useful task that I would like to use again for another project.

Overall, I am very happy with the outcome of this project.  It fulfills the task that I created it for, and I have used it multiple times when working out.  The casing has been durable enough to withstand travel in my backpack, and it is also portable enough to fit easily.  As far as the electronics go, the device effectively tracks and communicates the timing that I need it to do, and it has been a big improvement on using a conventional stopwatch in my workouts.

However, after using the device I’ve realized that there are a few additions that could be beneficial.  One drawback of the current system is that it assumes all rests in a set are the same length, but occasionally I will do a sprint workout where the rests in-between reps are different.  An additional mode which can program more complicated workout structures would be a useful.  Furthermore, I occasionally want to extend a rest period or cut it short, so an “add 30 seconds” or “start now” feature may be a good addition.  Lastly, when the track is wet I worry about damaging the electronics, so waterproofing the case could be beneficial.

When looking at written critiques, it is apparent there are other possible improvements  to consider.  One classmate commented,  “A good modification would be having a selection of preset workouts.”  I think this is a very interesting idea, but I do not think I will implement it.  Almost every week I slightly modify the workouts I am doing.  Therefore,  while I often do similar workouts, I rarely do identical workouts, so this change would not be very beneficial for me personally.  However, if I were to make this product for mass distribution, adding a preset workout feature could be more useful.  Another classmate suggested “making the indicators or sounds louder or bigger,” and I agree that this should be changed in the next iteration.  In the prototype, the speaker was much louder, but when using the Arduino Nano and battery power it became  fainter.  Using an amplifier or a louder speaker could certainly improve the quality of the project.

 

Technical Information

Schematic:

Code:

/*
 * Programmable Workout Timer
 * Justin Kiefel (jkiefel)
 * 
 * Description: This project allows a user to input workout data using a 
 * 4x3 keypad and tracks the time of the workout.  It gives alerts through 
 * a speaker, LED's, and an LCD display when a rep or rest is beginning, ending, 
 * or almost finished.  This project was designed specifically for sprint workouts,
 * but its functionality can be transferred to many other workouts.
 * 
 * Credit: 
 * Keypad and LCD Setup Code From - http://www.circuitbasics.com/how-to-set-up-a-keypad-on-an-arduino/
 * Pitch Code From - http://www.arduino.cc/en/Tutorial/Tone
 * Libraries Used - LiquidCrystal_I2C, Keypad
 * 
 * Summary: 
 * 
 * Inputs: 
 *  Arduino pin | input
 *  2-9           keypad
 *  A4            LCD SDA
 *  A5            LCD SCL
 *  
 * Outputs:
 *  Arduino pin | output
 *  10            speaker
 *  11            green LED
 *  12            blue LED
 *  13            red LED
 */

// keypad/lcd setup code from
// 

#include "pitches.h" 
#include <Wire.h>
#include<LiquidCrystal_I2C.h>
#include<Keypad.h>

// keypad setup
const byte ROWS = 4;
const byte COLS = 4;

const byte rowPins[ROWS] = {9, 8, 7, 6};
const byte colPins[COLS] = {5, 4, 3, 2};

const char hexaKeys[ROWS][COLS] = {
  {'1', '2', '3', 'A'},
  {'4', '5', '6', 'B'},
  {'7', '8', '9', 'C'},
  {'*', '0', '#', 'D'}
};

Keypad userKeypad = Keypad(makeKeymap(hexaKeys), rowPins, colPins, ROWS, COLS);

// lcd setup
LiquidCrystal_I2C lcd(0x27, 16, 2);  

// pin setup
const int SPEAKER_PIN = 10;
const int REP_PIN = 11;
const int SOON_PIN = 12;
const int REST_PIN = 13;

// speaker note data
const int repNoteDuration[] = {8,8};
const int repNoteMelody[]{NOTE_B3, NOTE_B5};

const int restNoteDuration[] = {8, 8};
const int restNoteMelody[]{NOTE_B5, NOTE_B3};

const int soonDuration[]{4};
const int soonMelody[]{NOTE_B3};

// initializing variables 
int state = 0; // current device state

String sets = ""; // user input number of sets
String reps = ""; // user input number of reps
String tpr = ""; // user input time per rep
String rpr = ""; // user input rest per rep
String rps = ""; // user input rest per set

int setsInt = 0; // number of sets
int repsInt = 0; // number of reps
int tprInt = 0;  // time per rep
int rprInt = 0;  // rest per rep
int rpsInt = 0;  // rest per set

int copyOfReps = 0; // used to remember number of reps
int startingCountdown = 4; // length of the starting countdown

int lcdTimer = 0; // the current integer to be displayed on the LCD screen 
int prevLcdTimer = 0; // the previous integer on the LCD screen

unsigned long timeMarker = 0; // used to track time at the start of a state

bool sound1Done = 0; // indicates the completion of the 'starting state' outputs
bool sound2Done = 0; // indicates the completion of the 'almost done' outputs

void setup() {
  lcd.backlight();
  lcd.init();
  lcd.home();
  
  pinMode(SPEAKER_PIN, OUTPUT);
  pinMode(REP_PIN, OUTPUT);
  pinMode(SOON_PIN, OUTPUT);
  pinMode(REST_PIN, OUTPUT);
}

void loop() {  
  char userInput = userKeypad.getKey(); // gets user input

  if (state == 0){ // opening screen 
    lcd.home();
    lcd.print("press * to start");
    if (userInput == '*'){
      state = 1;
      lcd.clear();
    }
  }

  else if (state == 1){ // set entry screen
    lcd.home();
    lcd.print("enter # of sets");
    lcd.setCursor(0,1); 
    
    if (isDigit(userInput)){ // typing functionality
      sets = sets+String(userInput);
    }
    else if (userInput == 'B'){ // backspace functionality
      sets = sets.substring(0,(sets.length()-1));
      lcd.clear();
      Serial.println(sets);
    }
    else if (userInput == '*' and sets != ""){ // next state functionality
      state = 2;
      lcd.clear();
    }
    lcd.print(sets);
  }

  else if (state == 2){ // rep entry screen
    lcd.home();
    lcd.print("enter # of reps");
    lcd.setCursor(0,1);
    
     if (isDigit(userInput)){ // typing functionality
      reps = reps+String(userInput);
    }
    else if (userInput == 'B'){ // backspace functionality
      reps = reps.substring(0,(reps.length()-1));
      lcd.clear();
    }
    else if (userInput == '*' and reps != ""){ // next state functionality
      state = 3;
      lcd.clear();
    }
    lcd.print(reps);
  }

  else if (state == 3){ // time per rep entry screen
    lcd.home();
    lcd.print("time per rep (s)?");
    lcd.setCursor(0,1);
    
     if (isDigit(userInput)){ // typing functionality
      tpr = tpr+String(userInput);
    }
    else if (userInput == 'B'){ // backspace functionality
      tpr = tpr.substring(0,(tpr.length()-1));
      lcd.clear();
    }
    else if (userInput == '*' and tpr != ""){ // next state functionality
      state = 4;
      lcd.clear();
    }
    lcd.print(tpr);
  }

  else if (state == 4){ // rest per rep entry screen
    lcd.home();
    lcd.print("rest per rep (s)");
    lcd.setCursor(0,1);
    
     if (isDigit(userInput)){ // typing functionality
      rpr = rpr+String(userInput);
    }
    else if (userInput == 'B'){ // backspace functionality
      rpr = rpr.substring(0,(rpr.length()-1));
      lcd.clear();
    }
    else if (userInput == '*' and rpr != ""){ // next state functionality
      state = 5;
      lcd.clear();
    }
    lcd.print(rpr);
  }

  else if (state == 5){ // rest per rep entry screen
    lcd.home();
    lcd.print("rest per set (s)");
    lcd.setCursor(0,1);
    
    if (isDigit(userInput)){ // typing functionality
      rps = rps+String(userInput);
    }
    else if (userInput == 'B'){ // backspace functionality
      rps = rps.substring(0,(rps.length()-1));
      lcd.clear();
    }
    else if (userInput == '*' and rps != ""){ //  next state functionality
      state = 6;
      lcd.clear();
    }
    lcd.print(rps);
  }

  else if (state == 6){ // countdown to start
    startingCountdown = startingCountdown - 1;
    
    lcd.home(); 
    lcd.print("starting in");
    lcd.setCursor(0,1);

    int noteDuration = 1000 / soonDuration[0];
    tone(SPEAKER_PIN, soonMelody[0], noteDuration);
    
    setsInt = sets.toInt() - 1; // to account for first set
    repsInt = reps.toInt() - 1; // to account for first rep 
    tprInt = tpr.toInt();
    rprInt = rpr.toInt(); 
    rpsInt = rps.toInt();
    copyOfReps = repsInt;

    delay(1000);
    lcd.print(startingCountdown);
    
    if (startingCountdown == 0){
      state = 7;
      startingCountdown = 4;
      lcd.clear();
    }
  }

  else if (state == 7){ // rep in progress
    if (not sound1Done){ // rep start light and sound
      digitalWrite(REP_PIN, HIGH);
      timeMarker = millis();
      sound1Done = 1;
      for (int i = 0 ; i < 2 ; i++){
        int noteDuration = 1000 / repNoteDuration[i];
        tone(SPEAKER_PIN, repNoteMelody[i], noteDuration);
        int pause = noteDuration * 1.25;
        delay(pause);
        noTone(10);
      }
    }
    // updates countdown
    lcdTimer = (tprInt - (millis() - timeMarker)/1000);
    
    if (prevLcdTimer != lcdTimer){
      lcd.clear();
      prevLcdTimer = lcdTimer;
    }
    
    lcd.home();
    lcd.print("rep in progress");
    lcd.setCursor(0,1);
    lcd.print(lcdTimer);

    if (((millis() - timeMarker)/1000 > (tprInt*.75)) and (not sound2Done)) { // almost done warning
      sound2Done = 1;
      digitalWrite(SOON_PIN, HIGH);
      int noteDuration = 1000 / soonDuration[0];
      tone(SPEAKER_PIN, soonMelody[0], noteDuration);
    }

    if ((millis() - timeMarker)/1000 > tprInt){ // next state functionality
      digitalWrite(REP_PIN, LOW);
      digitalWrite(SOON_PIN, LOW);
      sound1Done = 0;
      sound2Done = 0;
      lcd.clear();
      
      if (repsInt > 0){ // if more reps in set, then go to rep rest
        state = 8;
      }
      else if (setsInt > 0){
        repsInt = copyOfReps;
        state = 9;
      }
      else{
        state = 0;
        sets = "";
        reps = "";
        tpr = "";
        rpr = "";
        rps = "";
        setsInt = 0;
        repsInt = 0;
        tprInt = 0;
        rprInt = 0;
        rpsInt = 0;
        copyOfReps = 0;
      }
    }
  }

  else if (state == 8){ // rest between reps
    if(not sound1Done){ // rest start light and sound
      timeMarker = millis();
      digitalWrite(REST_PIN, HIGH);
      sound1Done = 1;
      for (int i = 0 ; i < 2 ; i++){
        Serial.print(i);
        int noteDuration = 1000 / restNoteDuration[i];
        tone(SPEAKER_PIN, restNoteMelody[i], noteDuration);
        int pause = noteDuration * 1.25;
        delay(pause);
        noTone(10);
      }
    }
    // updates countdown
    lcdTimer = (rprInt - (millis() - timeMarker)/1000);
    
    if (prevLcdTimer != lcdTimer){ 
      lcd.clear();
      prevLcdTimer = lcdTimer;
    }
    
    lcd.home();
    lcd.print("rest between reps");
    lcd.setCursor(0,1);
    lcd.print(lcdTimer);

    if (((millis() - timeMarker)/1000 > (rprInt*.75)) and (not sound2Done)) { // almost done warning
      sound2Done = 1;
      digitalWrite(SOON_PIN, HIGH);
      int noteDuration = 1000 / soonDuration[0];
      tone(SPEAKER_PIN, soonMelody[0], noteDuration);
    }
    
   if ((millis() - timeMarker)/1000 > rprInt){ // next state functionality
      digitalWrite(REST_PIN, LOW);
      digitalWrite(SOON_PIN, LOW);
      sound1Done = 0;
      sound2Done = 0;
      lcd.clear();
      repsInt = repsInt - 1;
      state = 7;
   }
  } 

  else if (state == 9){ // rest between sets
    if(not sound1Done){ // rest start light and sound
      timeMarker = millis();
      digitalWrite(REST_PIN, HIGH);
      sound1Done = 1;
      for (int i = 0 ; i < 2 ; i++){
        Serial.print(i);
        int noteDuration = 1000 / restNoteDuration[i];
        tone(SPEAKER_PIN, restNoteMelody[i], noteDuration);
        int pause = noteDuration * 1.25;
        delay(pause);
        noTone(10);
      }
    }
    // updates countdown
    lcdTimer = (rpsInt - (millis() - timeMarker)/1000);
    
    if (prevLcdTimer != lcdTimer){
      lcd.clear();
      prevLcdTimer = lcdTimer;
    }

    lcd.home();
    lcd.print("rest between sets");
    lcd.setCursor(0,1);
    lcd.print(lcdTimer);

    if (((millis() - timeMarker)/1000 > (rpsInt*.75)) and (not sound2Done)) { // almost done warning
      sound2Done = 1;
      digitalWrite(SOON_PIN, HIGH);
      int noteDuration = 1000 / soonDuration[0];
      tone(SPEAKER_PIN, soonMelody[0], noteDuration);
    }
    
   if ((millis() - timeMarker)/1000 > rpsInt){ // next state functionality
      digitalWrite(REST_PIN, LOW);
      digitalWrite(SOON_PIN, LOW);
      sound1Done = 0;
      sound2Done = 0;
      lcd.clear();
      setsInt = setsInt - 1;
      state = 7;
    }  
  }
}

 

]]>
Active Alarm https://courses.ideate.cmu.edu/60-223/f2019/work/active-alarm/ Thu, 17 Oct 2019 09:23:45 +0000 https://courses.ideate.cmu.edu/60-223/f2019/work/?p=8605

Overview

Summary

My project was an alarm clock that forces the user to get up and get active – the alarm will only stop ringing when the user goes to certain locations in their room in a specific, randomized order.

 

I noticed that I can only really wake up if I’m forced to get out of my bed and get moving. A lot of people also feel similarly, so it isn’t uncommon to place a phone alarm across the room to force one to get up and turn it off. But after that, it quite easy to slip back into bed and drift into sleep again. What if you had to go to multiple locations in your room, maybe in hard to reach nooks and crevices, in a specific order that is generated by the alarm? That way, you have to really get moving in order to truly turn it off. By that point, ideally, you won’t feel as strong of an urge to slip into bed.

A front view of the alarm

A close up of the LCD that will show the commands

A close up of the button and dial the user will use to set the alarm

A look inside at all the electronics

Demo of setting the time I want the alarm to go off

A demo of how the alarm looks when it is set off and prompts the user to scan specific tags

Process

How I decided to represent locations in my room – simple tags on cards

Getting a prototype “dial” and button to work

Getting the RFID scanner to work was the first hurdle I needed to pass. At first, I merely used female to male jumper cables to connect the scanner to the Arduino, which I expected to work perfectly. However, I was constantly getting error messages that no scanner was actually connected. Sometimes I got lucky and I would get a stray couple of bytes to show that something is hooked up before the same error messages would pop up again. I checked the wiring over and over again and replaced the scanner module multiple times. After a couple of hours, I was at my wit’s end and finally decided to solder the wires instead of just using jumper cables. After soldering, it worked perfectly. I still don’t know what the problem was, but if I didn’t decide to take the soldering approach, perhaps I would have opted to not use an RFID scanner at all. I’m really glad that it ended up working because the scanner ended up being a key part of this project’s appeal.

RFID lit up and working – This guy gave me a lot of trouble

Testing how I would mount the pieces onto the front, I ended up cutting out the wrong dimensions for the LCD, so I would have to redo this box

I didn’t wire my components with the understanding that I would later be putting in some kind of housing, I just wired it in the way that seemed the easiest and fastest at the moment. When it came to actually placing it in the box I cut out, I realized I would have to do a huge overhaul of the way I had everything organized, essentially tearing out all the wires and starting all over again. In future projects, I’ll keep in mind the final presentation while I design the hardware layout to save myself a lot of pain.

Getting everything into the box proved to be a big hassle, lots of wires came undone in the process. I pretty much had to rewire the entire project. Good learning experience.

Discussion 

Response

The LED strips look a little messy on the outside. Maybe put something on top of them to diffuse the light?

I totally agree. Currently, my LED strip is held on by its own stickiness, extra double-sided tape, and prayers. I tried to make it look as straight as possible, but it definitely could be a lot straighter and neater. My original hope was that I could use an LED strip that was wrapped in white/clear silicone,  so it would nicely diffuse the lights while maintaining brightness, but I didn’t know if we had this in the Physical Computing Lab. Of course, it is an alarm so we do want the lights to be a little jarring, so maybe diffusing may not necessarily be the way to go.  However, I think maybe it could be in a sort of housing so that the LED strip isn’t just nakedly sitting on the box.

 

If the tags are placed permanently in [a] different location, maybe you could put the RFID scanner on the opposite side of your interface so the buttons don’t interfere with you holding the box against the tags.

I also agree with this critique. I realized as I was putting all the wires and components into the box that maybe it would be hard to scan the tags from the front, given there are buttons and knobs. I tried it out by sticking a tag on the wall and trying to scan it, and it did work after I tilted the box a little bit. This is definitely a design flaw that I should have kept in mind. This is not something I actively thought about while building because I simulated the walls by just bringing the tags to the RFID scanner location.  Maybe it would have been better to put the RFID facing the back, which is flat and can be brought up to the wall.

Self-Critique

I’m pretty happy with how the project turned out. Visually, it looks very similar to my original drawings/concepts. However, I would probably not have a hole for the RFID sensor in a more refined project. I put it in this prototype because I thought that I would want to see the exact place of the sensor for debugging purposes, and I was unsure if it could scan through the wood. Now that I know it actually has quite a considerable range, I would not put a hole there and would merely etch a symbol to let the user know that this is the location to scan at. This would look a lot cleaner than the version I have now.  I also think that some of my code is a little hacky, for lack of a better word, and that its efficiency can be improved considerably.  If given more time, I would consider cleaning up my code so that maybe the project would run smoother.

What I Learned

There were points in the project that if I decided to fully pivot there, I would have gotten it to a functioning state much faster. An example is when I noticed that the RTC wasn’t showing the correct time, and in fact, would generate random times at bootup. For almost a week, this issue didn’t improve because the library I was using simply wasn’t capable of properly flashing the correct time to the RTC. It wasn’t until after I finally used another library did it fix itself. However, if I was just more willing to switch libraries earlier on, I could have fixed this and focused on other stuff. So advice to both past and future self: don’t be afraid to have to redo things to get something to function, sometimes you’re going to have to pivot from what you are currently using to a totally new tool, and you’re going to have to be okay with that.

Next Steps

My original vision for this project was that the RFID would just be one component of many. I’d also have to play with other sensors to turn off the alarm as well. For example, the alarm would need a photoresistor to read values above a certain threshold, either by bringing the alarm to the light or by binging my phone flashlight up to it. This would end up as almost like a “bop-it” alarm clock, as you have to complete certain commands in rapid succession to turn it off. I still think this is a fun idea and is probably the approach I would take if I decide to keep working on it. Also, currently, the only way to “cancel” an alarm a user has already set up is by ripping out the battery. I would definitely make a reset/off/cancel button in an improvement of this project.

I would also like to integrate this with a way to measure and display information sleep as well, perhaps with a mobile/web app. It would be great to record the number of hours slept, probably by adding another button to set when the user goes to sleep and then tracking until the alarm is turned off. I think displaying this information on a graph and running some analytics on it could be extremely useful to the user when deciding how to change their sleeping habits.

Schematic

Code

/* Active Alarm
 * Neeharika Vogety (nvogety)
 *
 * 
 * Summary: An alarm will ring, vibrate, and light up at a time that a user sets 
 * to wake up at. The only way to turn off the alarm is to scan RFID tags set around
 * the room in a specific, randomly-generated order. The idea is to get the user up 
 * and moving to turn off the alarm, rather than just hitting a button and going back to
 * sleep.
 * 
 *  Inputs: 
 *  
 *  Arduino Pin | Input
 *     5          LED Strip Pin      
 *     2          Button Pin
 *     9          RFID Pin
 *     A1         Potentiometer Pin
 *  
 *  Outputs:
 *  
 *  Arduino pin | Output
 *      7         Vibrating Motor Pin
 *      4         Piezo Buzzer Pin
 */


#include <Wire.h>
#include <Time.h>
#include <LiquidCrystal_I2C.h>
#include <RTClib.h>
#include <SPI.h>
#include <MFRC522.h>
#include <FastLED.h>

// Define important pins for RFID
#define RST_PIN         9          
#define SS_PIN          10 
#define TASKSLEN        5 // Define length of tasks array, used throughout code

// Define important constants for LED strip
#define LED_PIN     5
#define NUM_LEDS    45
#define BRIGHTNESS  200
#define LED_TYPE    WS2811
#define COLOR_ORDER GRB
CRGB leds[NUM_LEDS];

#define UPDATES_PER_SECOND 100

CRGBPalette16 currentPalette;
TBlendType    currentBlending;


typedef struct{
  char *tagName;
  char *tagUID;
} Tag;

// An array of Tag structs that links together
// the name of the tag and the ID of the tag.
// If this is changed, must change TASKLEN at
// the top!
Tag tasks[TASKSLEN] = {
  {"A", "437B172845C81"},
  {"B", "450B072845C80"},
  {"C", "42FB172845C81"},
  {"D", "461DF72845C80"},
  {"E", "49ED972845C80"},
};

int nums[TASKSLEN]; // Create nums array
int numOrder[TASKSLEN]; // Create num Order array
int nOindex = 0; // Index to traverse num Order

// Bools that we will use to execute certain parts of the code
bool startGeneration = false; 
bool readRFID = false;
bool setAlarm = true;
bool alarmSet = false;
// Hour and Minute, will set with RTC
int alarmHour;
int alarmMin;

LiquidCrystal_I2C screen(0x27, 16, 2); //Create Screen object
MFRC522 mfrc522(SS_PIN, RST_PIN);  // Create MFRC522 instance
DS1307 RTC; // Create RTC object

// Initialize constant i/o pins
const int BUTTONPIN = 2;
const int POTPIN = A1;
const int VIBRPIN = 7;
const int BUZZERPIN = 4;

void setup() {
  Serial.begin(9600);
  
  // Set up INPUTS AND OUTPUTS
  pinMode(BUTTONPIN, INPUT_PULLUP);
  pinMode(POTPIN, INPUT);
  pinMode(VIBRPIN, OUTPUT);
  pinMode(BUZZERPIN, OUTPUT);

  // Set up RFID
  SPI.begin();      // Init SPI bus
  mfrc522.PCD_Init();   // Init MFRC522
  delay(4); 
  mfrc522.PCD_DumpVersionToSerial(); // Make sure RFID is up and running

  // Start RTC
  RTC.begin();

  //Set up LED String
  FastLED.addLeds<LED_TYPE, LED_PIN, COLOR_ORDER>(leds, NUM_LEDS).setCorrection( TypicalLEDStrip );
  FastLED.setBrightness(  BRIGHTNESS );
  currentPalette = RainbowStripeColors_p;
  currentBlending = NOBLEND;
  clearLEDs();

  // Clear LCD screen and signal set up is over
  screen.init();
  screen.backlight();
  screen.home();
  screen.print("Booting up..");
  delay(1000);
  screen.clear();
}

void loop() {
  
  // If we still need to set an alarm time,
  //   display the current time and wait for button press
  //   to set the hour, then the minute of the alarm, and
  //   set the alarm
  if(setAlarm){

    // Display time until button is pressed
    while(digitalRead(BUTTONPIN) == LOW){
      DateTime now = RTC.now();
      screen.clear();
      screen.setCursor(0, 0);
      screen.print("Current Time:");
      screen.setCursor(0, 1);
      if(now.hour() <= 9){
        screen.print("0");
      }
      screen.print(now.hour());
      screen.print(":");
      if(now.minute() <= 9){
        screen.print("0");
      }
      screen.print(now.minute());
      screen.print(":");
      if(now.second() <= 9){
        screen.print("0");
      }
      screen.print(now.second());
      delay(1000);
    }
    delay(1000);

    // Allow user to set up hour of alarm until button is pressed again
    while(digitalRead(BUTTONPIN) == LOW){
      screen.clear();
      screen.setCursor(0, 0);
      screen.print("Set Alarm Hour:");
      // Set alarm hour with potentiometer
      alarmHour = map(analogRead(POTPIN), 0, 1023, 0, 24);
      screen.setCursor(0, 1);
      if(alarmHour <= 9){
         screen.print("0");
      }
      screen.print(alarmHour);
      delay(250);
    }
    delay(1000);

    // Allow user to set up minute of alarm until button is pressed again
    while(digitalRead(BUTTONPIN) == LOW){
      screen.clear();
      screen.setCursor(0, 0);
      screen.print("Set Alarm Minute:");
      // Set alarm minute with potentiometer
      alarmMin = map(analogRead(POTPIN), 0, 1023, 0, 60);
      screen.setCursor(0, 1);
      if(alarmMin <= 9){
         screen.print("0");
      }
      screen.print(alarmMin);
      delay(250);
    }
    delay(1000);
    
    // Show when alarm is set for
    screen.clear();
    screen.setCursor(0, 0);
    screen.print("Alarm set for:");
    screen.setCursor(0, 1);
    if(alarmHour <= 9){
      screen.print("0");
    }
    screen.print(alarmHour);
    screen.print(":");
    if(alarmMin <= 9){
      screen.print("0");
    }
    screen.print(alarmMin);
    setAlarm = false;
    alarmSet = true;
    
  }

  // Keep checking if current hour and minute is what we set for the alarm
  if(alarmSet){
    DateTime now = RTC.now();
    if(now.hour() == alarmHour && now.minute() == alarmMin){
      startGeneration = true;
      alarmSet = false;
      screen.clear();
      screen.print("ALARM STARTED");
      // Set off buzzer, will make a humming sound
      tone(BUZZERPIN, 100);
      vibrate();
      vibrate();
      vibrate();
      Serial.print("ALARM STARTED!!!!!");
    }
  }

  // If alarm went off, start to create the random list of Tags
  if(startGeneration){
    // Fill nums in with just numbers counting from 0 up
    for(int i = 0; i < TASKSLEN; i++){
      nums[i] = i;
    }

    // Form new numOrder array by grabbing different parts of numArray
    for(int currlen = TASKSLEN; currlen > 0; currlen--){
      // Seed differently everytime
      randomSeed(analogRead(A0));
      // Get random number, which will be the index for the number to grab from num
      int randi = random(currlen);
      numOrder[nOindex] = nums[randi];
      nOindex++;
      // "Remove" the number we just picked from num by shifting over the other numbers
      //  This will guaruntee we don't pick that number again
      for(int k = randi; k < currlen; k++){
        nums[k] = nums[k+1];
      }
    }

    // Loop through the tasks in the order that we generated
    for(int i = 0; i < TASKSLEN; i++){
      screen.clear();
      screen.print("GO TO ");
      screen.print(tasks[numOrder[i]].tagName);
      readRFID = true;
      while(readRFID){
        
        // Keeping sounding the alarm, even while user is still scanning tags
        soundAlarm();
        
        // Keep checking if a new tag has been shown
        if ( ! mfrc522.PICC_IsNewCardPresent()) {
          continue;
        }

        if ( ! mfrc522.PICC_ReadCardSerial()) {
          continue;
        }

         // Print the tag info scanned for debugging purposes
         Serial.print("CARD SCANNED!!!!!: ");
         String readtagID = "";
         // The MIFARE PICCs that we use have 4 byte UID
         for ( uint8_t i = 0; i < 7; i++) { 
           readtagID.concat(String(mfrc522.uid.uidByte[i], HEX)); // Adds the 4 bytes in a single String variable
         }
         readtagID.toUpperCase();
         mfrc522.PICC_DumpToSerial(&(mfrc522.uid));
         
         // If the tag scanned is the correct one in the order specified.
         //    move on to checking for the next tag
         if(readtagID == tasks[numOrder[i]].tagUID){
            screen.clear();
            if(i < TASKSLEN - 1){
            screen.print("Good! Next one..");
            vibrate();
            screen.clear();
            }
            readRFID = false;
         }
          
      }
    }

    // Show that user has finished
    screen.print("FINISHED");
    noTone(BUZZERPIN);
    vibrate();
    screen.clear();
    
    
    clearLEDs();
    startGeneration = false;
    setAlarm = true;
    loopCounter++;
  }
}

// What the user should see while alarm is going off,
//   start filling the LEDs with color
void soundAlarm(){
  static uint8_t startIndex = 0;
  startIndex = startIndex + 1; /* motion speed */
    
  FillLEDsFromPaletteColors( startIndex);
  FastLED.show();
  FastLED.delay(1000 / UPDATES_PER_SECOND);
}

// Fill LED strip based off of variables set at the beginning
void FillLEDsFromPaletteColors( uint8_t colorIndex)
{
    uint8_t brightness = 255;
    
    for( int i = 0; i < NUM_LEDS; i++) {
        leds[i] = ColorFromPalette( currentPalette, colorIndex, brightness, currentBlending);
        colorIndex += 3;
    }
}

// Clear all the LEDs
void clearLEDs(){
  for( int i = 0; i < NUM_LEDS; i++) {
        leds[i] = CRGB::Black;
  }
  FastLED.show();
}

// Pattern of vibration when alarm first goes off
void vibrate(){
  digitalWrite(VIBRPIN, HIGH);
  delay(1000);
  digitalWrite(VIBRPIN, LOW);
  delay(500);
}

 

]]>
Sleep Better Alarm Clock https://courses.ideate.cmu.edu/60-223/f2019/work/sleep-better-alarm-clock/ Thu, 17 Oct 2019 07:42:54 +0000 https://courses.ideate.cmu.edu/60-223/f2019/work/?p=8512 Overview

An alarm clock that reminds you to get ready for bed, and gives advice for how to improve your sleep.

The alarm clock after you turn off the morning alarm. It displays the date, time, and a message based on the amount of sleep you got that night.

The inside of the alarm clock

The light turns on to signify that the user should get ready for bed. It can be turned off with a button, that also tells the clock the user is going to bed.

Pressing the button again turns off the morning alarm and tells the clock the user has woken up. It then displays a message based on the amount of sleep it tracked for that night.

Process

One of the big decisions I made was not having a way to change the alarm times. I originally planned to have two buttons that when pressed, would allow you to change the alarms with a potentiometer. I quickly realized, however, that I wouldn’t have enough time to implement this and instead decided to just have the alarms hard coded.

Another decision I made was to use an lcd screen to display the messages for how to improve your sleep. I was debating whether to use an lcd screen or receipt printer, and decided to use the lcd screen since it was big enough to display the messages I had in mind and I knew how to use one so I knew it wouldn’t take much time to add.

The final breadboard version on my clock, with two lcd screens.

Highlights:

This was the first version of my alarm clock. It had a real time clock, LED, piezo buzzer, a button, and one lcd screen that displayed the time.

The final version, with everything soldered to a protoboard. The final version had a second lcd screen, another button, a barrel jack, a speaker instead of a buzzer, and used an arduino nano instead of an uno.

The laser cut box I made for the clock, before I but the electronics in. The original top had holes that were too small for the buttons, so I had to recut the top.

Discussion

One of the critiques I received was, “I think that it would be more effective if you could change the sleep interval w/o having to reprogram the entire device.” I agree that this is something that would greatly improve the clock, since when you need to wake up or go to bed changes over time. As I mentioned earlier, I originally planned to include a way to change the alarms but didn’t have enough time to implement it.

Another critique was, “It might be cool to have this track sleep statistics over the course of the week.” I also agree with this critique. I think having advice given based on a pattern of sleep rather than just one night of sleep would be more useful. 

I think it did satisfy my main goals, and I am happy with how it came out. All of the features work the way I intended them to. I did the best I could in the time given, but there is still a lot of room for improvement. I think that the box is a bit big and bulky. I had designed it that way to make sure everything fit inside it, but it is bigger than it needs to be.

I learned a lot from this project, like that I don’t know how to use interrupts. I ran into a problem where the alarms stopped working correctly all of a sudden, so I tried switching to a different library to see if there was a problem with the library I was using. However, I couldn’t figure out how to use the interrupt that the library relied on to set alarms. There are still a lot of things you can do with the arduino that I have yet to learn.

I really enjoyed being able to just make something on my own.  This is the first time I’ve made a device, so it’s really satisfying to have taken an idea and made it into a reality. Since I had come up with the project idea and it was something I could actually use, I was really motivated and excited to work on the project.

If I could have done anything differently, I would have spent more time researching libraries for the real time clock. I went the library used in the first tutorial I found, but there might have been better libraries out there that I could have worked better for my project that I just didn’t know about.

If I have time this semester, I would like to make another iteration. I would add a way to change the time and the alarms, probably with buttons to indicate you want to change an alarm and a potentiometer to change the time. I would also like to make a more complicated sleep tracker that gives advice after a week of tracking your sleep. I would also make the physical clock smaller, since it’s bulky and takes up a lot of room when it isn’t necessary for it to be so big.

Technical Information

Schematic

 

/*
 * Project 2
 * Karen Abruzzo (kabruzzo)
 * Time: two weeks
 *
 * Collaboration:
 * I used code from the example SetAlarms from the DS3231M library as a staring point.
 * https://github.com/SV-Zanshin/DS3231M
 * 
 * Summary: The code sets two alarms that can be turns off with a button. One alarm turns on 
 * an LED to remind you to go to bed, the other plays a sound to wake you up in the morning.
 * There is also a snooze button. The code also tracks your sleep and outputs the current date,
 * time, and advice onto lcd screens.
 * 
 * Inputs:
 * real time clock              | PIN A4, A5
 * button                       | PIN 2
 * button                       | PIN 5
 * 
 * Outputs:
 * lcd screen    | PIN A4, A5
 * lcd screen    | PIN A4, A5
 * speaker       | PIN 4
 * LED           | PIN 3
 */

#include <DS3231M.h>//library for the rtc
#include <Wire.h>//library for i2c communications on analog pins
#include <LiquidCrystal_I2C.h>//library for the lcd screens

//setup the screens
LiquidCrystal_I2C screen1(0x27, 16, 2);
LiquidCrystal_I2C screen2(0x20, 20, 4);
const uint32_t SERIAL_SPEED        = 115200;

//set up pin numbers
const int ALARM_BUT_PIN = 2; 
const int SNOOZE_BUT_PIN = 5;
const int LED_PIN = 3;
const int BUZ_PIN = 4;

//set up global variables
//time you go to bed
int SLEEP_HOUR;
int SLEEP_MIN;
int SLEEP_SEC;
//time you wake up
int WAKE_HOUR;
int WAKE_MIN;
int WAKE_SEC;
//# of snoozes
int SNOOZES = 0;
//amount of time you slept
int HOURS_SLEPT;
int MINS_SLEPT;
int SECS_SLEPT;

DS3231M_Class DS3231M;
const uint8_t  SPRINTF_BUFFER_SIZE =     32; //this is for displaying the time and date properly
char inputBuffer[SPRINTF_BUFFER_SIZE];
bool ALARM = true; //this variable keeps track of which alarm (day or night) is set to go off next. True means the night alarm is set up.
DateTime SETUP_TIME = DateTime(2019, 10, 10, 11, 23, 0);//current time
DateTime NIGHT_ALARM = DateTime(0, 0, 0, 23, 0, 0);//time you want to go to bed
DateTime NIGHT_ALARM2 = DateTime(0, 0, 0, NIGHT_ALARM.hour() - 1, 30, 0);//time nigh alarm will go off
DateTime DAY_ALARM = DateTime(0, 0, 0, 8, 20, 0);//time day alarm will go off

void setup() {
  // put your setup code here, to run once:

//setup pins
  pinMode(ALARM_BUT_PIN, INPUT);
  pinMode(LED_PIN, OUTPUT);
  pinMode(BUZ_PIN, OUTPUT);
  Serial.begin(SERIAL_SPEED);

  //setup lcd screens
  screen1.init();
  screen2.init();

  // turn on the backlight to start
  screen1.backlight();
  screen2.backlight();

  // set cursor to home position, i.e. the upper left corner
  screen1.home();
  while (!DS3231M.begin()) {
    Serial.println(F("Unable to find DS3231MM. Checking again in 3s."));
    delay(3000);
  }
  //DS3231M.adjust(SETUP_TIME); //uncomment this line to set the time on the rtc
  DS3231M.setAlarm(minutesHoursMatch, NIGHT_ALARM2); //start the alarm
  //display the date and time
  DateTime now = DS3231M.now();
  sprintf(inputBuffer, "%04d-%02d-%02d", now.year(), now.month(), now.day());
  screen1.clear();
  screen1.home();
  screen2.clear();
  screen2.home();
  screen1.print(inputBuffer);
  Serial.print(inputBuffer);
  Serial.print(" ");
  sprintf(inputBuffer, "%02d:%02d", now.hour(), now.minute());
  screen1.setCursor(0, 1);
  screen1.print(inputBuffer);
  Serial.println(inputBuffer);

}

void loop() {
  // put your main code here, to run repeatedly:
  DateTime sleepTime; //time you go to bed
  DateTime wakeTime; // time you wake up
  DateTime timeSlept; //how long you slept
  DateTime now = DS3231M.now();
  static uint8_t mins;
  if (mins != now.minute()) //every minute, change display so it displays current time
  { 
    sprintf(inputBuffer, "%04d-%02d-%02d", now.year(), now.month(), now.day());
    screen1.clear();
    screen1.home();
    screen1.print(inputBuffer);
    Serial.print(inputBuffer);
    Serial.print(" ");
    sprintf(inputBuffer, "%02d:%02d", now.hour(), now.minute());
    screen1.setCursor(0, 1);
    screen1.print(inputBuffer);
    Serial.println(inputBuffer);
    mins = now.minute();
  }
  if (digitalRead(ALARM_BUT_PIN)) //if the alarm off button is pressed
  {
    delay(200); //debounces button
    SNOOZES = 0; //reset snoozes
    if (ALARM) { //if the alarm that went off was the night alarm
      //turn off alarm, set alarm time to the morning alarm, turn off the LED,
      //set the sleep time and turn off backlight on second lcd screen
      DS3231M.clearAlarm(); 
      DS3231M.setAlarm(minutesHoursMatch, DAY_ALARM);
      ALARM = !ALARM;
      digitalWrite(LED_PIN, 0);
      sleepTime = DS3231M.now();
      SLEEP_HOUR = sleepTime.hour();
      SLEEP_MIN = sleepTime.minute();
      SLEEP_SEC = sleepTime.second();
      screen2.clear();
      screen2.noBacklight();

    }
    else { //if morning alarm just went off
      //turn off alarm, set alarm time to the night alarm, turn off the speaker,
      //set the wakeup time, calculate and display time slept, 
      //display advice depending on amount of time slept
      DS3231M.clearAlarm();
      DS3231M.setAlarm(minutesHoursMatch, NIGHT_ALARM2);
      sprintf(inputBuffer, "%02d:%02d", NIGHT_ALARM.hour(), NIGHT_ALARM.minute());
      ALARM = !ALARM;
      noTone(BUZ_PIN);
      wakeTime = DS3231M.now();
      WAKE_HOUR = wakeTime.hour();
      WAKE_MIN = wakeTime.minute();
      WAKE_SEC = wakeTime.second();

      HOURS_SLEPT = WAKE_HOUR - SLEEP_HOUR;
      MINS_SLEPT = WAKE_MIN - SLEEP_MIN;
      SECS_SLEPT = WAKE_SEC - SLEEP_SEC;

      //account for any negative numbers from the subtraction
      if (SECS_SLEPT < 0)
      {
        MINS_SLEPT -= 1;
        SECS_SLEPT += 60;
      }
      if (MINS_SLEPT < 0)
      {
        HOURS_SLEPT -= 1;
        MINS_SLEPT += 60;
      }
      if (HOURS_SLEPT < 0)
      {
        HOURS_SLEPT += 12;
      }
      Serial.println("time slept:");
      screen2.clear();
      screen2.backlight();
      screen2.home();
      screen2.print("time slept: ");
      sprintf(inputBuffer, "%02d:%02d:%02d", HOURS_SLEPT, MINS_SLEPT, SECS_SLEPT);
      Serial.println(inputBuffer);

      screen2.print(inputBuffer);
      if (HOURS_SLEPT < 8)
      {
        if (HOURS_SLEPT < 5)
        {
          screen2.setCursor(0, 1);
          screen2.print("Try going to bed");
          screen2.setCursor(0, 2);
          screen2.print("early today, you got");
          screen2.setCursor(0, 3);
          screen2.print("very little sleep.");
          Serial.println("Try going to bed early today, you got very little sleep.");
        }
        else
        {
          screen2.setCursor(0, 1);
          screen2.print("You got less than");
          screen2.setCursor(0, 2);
          screen2.print("8 hours, try to get");
          screen2.setCursor(0, 3);
          screen2.print("more sleep.");
          Serial.println("You slept less than 8 hours, try to get more sleep.");
        }
      }

      else if (SLEEP_HOUR != NIGHT_ALARM.hour())
      {
        screen2.setCursor(0, 1);
        screen2.print("You didn't go to bed");
        screen2.setCursor(0, 2);
        screen2.print("on time, try to stay");
        screen2.setCursor(0, 3);
        screen2.print("on schedule.");
        Serial.println("You didn't go to bed on time, try to stay on schedule.");
      }
      else
      {
        screen2.setCursor(0, 1);
        screen2.print("You got a good");
        screen2.setCursor(0, 2);
        screen2.print("amount of sleep,");
        screen2.setCursor(0, 3);
        screen2.print("good job!");
        Serial.println("You got a good amount of sleep, good job!");
      }
    }

  }
  if (digitalRead(SNOOZE_BUT_PIN)) //if snooze button is pressed
  {
    if (DS3231M.isAlarm()) //if an alarm is going off
    {
      if (SNOOZES < 3) //if snooze button has been hit less than 3 times
      {
        delay(200); //debounce button
        //turn off alarm for ten seconds
        DS3231M.clearAlarm(); 
        DS3231M.setAlarm(minutesHoursMatch, (0, 0, 0, now + TimeSpan(0, 0, 10, 0)));
        digitalWrite(LED_PIN, 0);
        noTone(BUZ_PIN);
        SNOOZES += 1;
      }
      else //if snooze button has been hit 3 or more times
      {
        if (ALARM)
        {
          screen2.clear();
          screen2.home();
          screen2.print("You need to go to");
          screen2.setCursor(0, 1);
          screen2.print("bed!");
        }
        else
        {
          screen2.clear();
          screen2.backlight();
          screen2.home();
          screen2.print("You need to go get");
          screen2.setCursor(0, 1);
          screen2.print("up!");

        }

      }


    }
  }
  if (DS3231M.isAlarm()) //if the alarm is going off
  {

    if (ALARM)
    {
      digitalWrite(LED_PIN, 1); //turn on led
    }
    else
    {
      tone(BUZ_PIN, 1000); //play a loud annoying sound
    }


  }
}

 

]]>
Contact Lens Reminder https://courses.ideate.cmu.edu/60-223/f2019/work/contact-lens-reminder/ Thu, 17 Oct 2019 05:23:19 +0000 https://courses.ideate.cmu.edu/60-223/f2019/work/?p=8554 Overview

This device effectively reminds you to put in your contacts lenses when you wake up.

Back View: One port to reprogram and another for power

Sample LEDs that blink to remind the user

Switch that detects presence of phone

Process Review

One decision point during the process was the choice between using a real time clock or simply another switch to determine when the reminder would be able to activate. If I were to use a switch that the user would have to turn on and off, the device would be more accurate in determining when the user goes to bed, but it’s manual and not automatic which runs the risk of the user forgetting to flip the switch. The real time clock allows the device to automatically know when the user should be asleep based on the time it is set, but the accuracy of the timing relies on the consistency of the user’s sleep schedule. In the end, I decided that the clock has enough accuracy to pinpoint when the user would go to bed, and it’s automatic quality would be more desired.

Here is an early prototype with the switch

This is part of the final build with the DS3231 module (Clock) attached to protoboard

Another decision point was how the phone button would be optimally implemented into the final build of the device without creating flaw in the design.  This was significant because if the button wasn’t efficient in detecting the phone, the whole device would be compromised. I luckily discovered that I could use the inter-structure of cardboard to have space for wires and a place for the button to show to be pressed. This allowed the button to be fully functional without any wires showing.

Very early build of phone button

One side of the fed through wire for phone button

Another side of wire fed through cardboard for phone button

Progress Pictures (cont.)

Backside of protoboard

Front side of protoboard

Discussion

Responses:

I think that the form of the box could be modified to fit the shape and size of the phone more. Is there a way to set the way that the LEDs blink on the fly? The rapid flashing seems like it would be a lot at 8AM.”

Now that I’m looking back on it, a holder for the phone would have been extremely useful and convenient. This would help prevent the phone from somehow falling off or moved enough where the button isn’t triggered anymore. The point of the presence of a phone triggering  the LEDs rather than simply at a set time was to increase accuracy in determining when the LEDs turn on. My thinking was that everyone goes everywhere with their phone, and we often have varying schedules, so your phone would act as a better key when you wake rather than time. The purpose of the clock was implemented to automatically “set” the device and to “unset”.  Also, the rapid flashing of the LEDs was intended to cause as much annoyance as possible to force the user to remember to put in their contact lenses.

The minimalistic interface does keep it simple for users, which is always a plus, especially if the users are tired, college students.”

The key characteristics I wanted to highlight in this device were simplicity and effectiveness, so I’m glad that this response and many others indicated the key characteristics. The purpose of implementing these key aspects was because I myself am a tired college student, and lets be honest, I probably wouldn’t need a device to remind me of self-care if I wasn’t a tired college student. That being said I wanted a device where I could roll out of bed and without using my brain, be able to remember to put my contact lenses in, and I think this device does that pretty well.

 

This device is simple, and it works. I’m satisfied in the aspect that it does everything I wanted it to do. However, after going through the process and gaining feedback, there are many things about my device that could be improved. The core design of the device is good, but each aspect that make up the core design can be improved to produce a much better overall device.  I learned how useful peer feedback is. As I went through the process, I had an idea of how to improve parts, but I couldn’t quite make the connection in my head. When I heard feedback from others, I was able to finalize exactly how to improve certain parts.  On my next project, I’ll be sure to reach out to more peers during the process of building the device to catch more flaws. The most difficult part of making the device was the design part. This was especially true on the placement of the phone button which I was lucky enough to find a creative spot to put it. I felt like that the design was where I put most of my time into, and in the end,  it was the area that could have used the most improvements.  I think next time I should make more frequent and less time consuming prototype design builds in order to eliminate more flaws. I don’t plan on building another finalization of this device, but I know how I would improve it if I did. First, I would design a holder to keep the phone in place when placed onto the button. Also, I would change the button placement to be under the middle of the phone rather than on one of its sides. Finally, some of the parameters should be modified in the software to more accurately determine when the device needs to be set. Right now, it only takes in account the hour, but it could also consider the minutes of the hour.

Schematic

 

Code

// Contact Lens Reminder
// Leland Mersky
// 
// This code runs a reminder system in the morning (or any desired
// time) that will blink LED's once you take your phone off it to
// get up and on with your day. It'll shut off at a set time when
// you're not expected to be at home and have no use for it.
//
// Digital
// Red LED Pin   2
// Green LED Pin 3
// Blue LED Pin  4
// Phone Button Pin 5

#include <DS3231.h>

DS3231  rtc(SDA, SCL);

const int RED_LED_PIN = 2;
const int GREEN_LED_PIN = 3;
const int BLUE_LED_PIN = 4;

const int PHONE_BUTTON_PIN = 5;

void setup() {
  pinMode(RED_LED_PIN, OUTPUT);
  pinMode(GREEN_LED_PIN, OUTPUT);
  pinMode(BLUE_LED_PIN, OUTPUT);
  pinMode(PHONE_BUTTON_PIN, INPUT);
  
  Serial.begin(9600);
  Serial.begin(115200);
  
  rtc.begin(); //starts the real time clock
  rtc.setDOW(TUESDAY); //set the day of the week
  rtc.setTime(0,20,00); //set the time in hours:minutes:seconds
  rtc.setDate(10,9,2019); //set the date months/days/year

   
}

void loop() {
  String mytime = rtc.getTimeStr(); //gets the time in a string
  mytime = mytime.substring(0,2); //only gets the hours part of string
  int hour = mytime.toInt(); //turns hours part into an int type
  int PhoneButtonState;
  PhoneButtonState = digitalRead(PHONE_BUTTON_PIN);
  if  (((hour<=7) and (PhoneButtonState == LOW))){
    //set hour to when you want it to be activated
    //this is set to turn on no later than 7:59 AM
    //because hours can be from 0 to 7
    digitalWrite(RED_LED_PIN,HIGH);
    delay(100);
    digitalWrite(RED_LED_PIN,LOW);
    digitalWrite(GREEN_LED_PIN,HIGH);
    delay(100);
    digitalWrite(GREEN_LED_PIN,LOW);
    digitalWrite(BLUE_LED_PIN,HIGH);
    delay(100);
    digitalWrite(BLUE_LED_PIN,LOW);
  }
}

 

]]>
RFID-based Habit Tracker https://courses.ideate.cmu.edu/60-223/f2019/work/rfid-based-habit-tracker/ Thu, 17 Oct 2019 05:00:50 +0000 https://courses.ideate.cmu.edu/60-223/f2019/work/?p=8394 Overview

This device helps the user develop habits by allowing the user to track the strength of habits via RFID tags. Each time the user performs an activity, they can scan the associated RFID tag/key card on the device as a tally of how many times they’ve performed the activity. When this happens a certain amount of time, the activity is considered a developed habit.

Front view of the device with two types of compatible RFID-embedded objects.

Usage Demo

Details

A fully “developed” habit will trigger the light ring embedded within the dial to display a rainbow.

Front view of device showing the size of the dial and scanning surface

The interface of the device is a cylindrical dial with an embedded button and LED ring, as well as a RFID reader underneath the flat surface.

Process

The first decision point  came from an aesthetic standpoint and occurred early on in the process. My original idea was using a potentiometer to adjust the strength of a habit, and have an LED strip near the RFID scanner to display the strength.

Original proof of concept with a potentiometer, linear Neopixel strip, and separate button.

However, I decided to move to a ring and encoder setup because the LED also needs to be responsive to dial adjustments. By using a ring, the motion of the rotation is coupled with the “motion” of the LED lights. Furthermore, using an encoder allows the user to turn the dial freely, but figuring out what the rotations are translated to is relegated to software.

Though this appeared to be a minor change, the encoder required major restructure of the main program loop. Originally, I wanted to check if a RFID tag is detected by the scanner every cycle. But the time this takes interferes with the encoder’s ability to detect rotation. This led me to introduce a “time out” system for setting a new tag’s strength. Pushing down the encoder while rotating will set the strength, and after releasing the encoder, the user has three seconds to flash a new tag. Otherwise, the nano goes back to waiting to read a tag.

Converting to Neopixel ring and rotary encoder with built-in button.

One feature I wanted to add was making time a factor in the calculation of how strong a strength is. However, I decided against implementing it. Because of the long-running nature of this device, I would need to incorporate timing in the form of an extra component to connect to the arduino. Furthermore, this information would need to be encoded onto the RFID tags. Though this would allow me to differentiate habits I infrequently follow from the ones that I do abide by daily, the amount of technical complications was too much for the time frame of this project.

Instead, after implementing the functions I initially planned on, I decided to focus on designing the appearance of the final product.

Process Highlights

Moving components to a protoboard to slim down the form factor.

Initial sketch of the enclosure

Modeled enclosure with built-in supports for the components.

Printed enclosure with placeholder acrylic dial.

Final enclosure with printed lid and dial.

Discussion

Responses

“A time interval could make it even better.”

The most common points raised during the critique was a lack of a time element. The “strength” of a habit should account for the frequency in which an activity is performed, but my device currently only tracks the amount of times it has been scanned. This is definitely a feature I would like to incorporate, including a way to set the target frequency (e.g. X times/ week). One concern for adding this feature is that it might require a more complicated interface. The consensus seems to be that my current press-and-turn interface is “simple and intuitive.” The simplicity may come from the fact that there is only one thing to control right now: the maximum number of times the tag is scanned to fill up the light ring. If there are two degrees of freedom involved, I would need to think of a way for one dial to set two parameters.

Another way I could maintain the simplicity of the interface is to set a constant number/duration for which a tag must be scanned. However, the dial will then only set the desired frequency, using this and the actual frequency of scanning to calculate “strength”.

“I wonder if there could be the opposite, where it punishes you for having bad habits.”

If this time feature is implemented, I could introduce a decay factor into my habit. This way, the strength of the habit could decrease if the scanning becomes too infrequent. Although this is not necessarily a real punishment, it does mean that I will be further from getting to see the rainbow lights spin around.

Another question raised during the critique was how I will remember to scan the tags related to an activity when I am not immediately next to my desk, which is where this device would be. An example I provided is putting a tag on my gym bag, so when I come home from the gym I will naturally scan it. However, not every activity may have a physical object associated with it, which would make remembering more difficult. The hope would then be that seeing this tracker on my desk can also serve as a reminder for me to perform tasks as well as remember to scan tags associated with them. In that sense, remembering to use this device is also a habit I would then need to develop.

Self Critique and Takeaways

Looking back, I think that this project was quite ambitious given that it was an individual project over the course of two weeks. I am happy with the way that my project turned out, but I also think I could have been more ambitious. I decided to focus on the physical design of my project over the software because I wanted to use this assignment as a way to explore that field. Because of this, I settled with a conceptually simple (but still challenging to implement) software side.

One of the potential issues on the software side I decided to overlook was the sequential nature of the LED animations. Because each animation routine was a separate function that used delay() for timing, no RFID tags will be scanned when an animation is being played. This also means that tags must be scanned one at a time, i.e. I would not be able to bulk scan a lot of tags (e.g. at the end of the say when I get home). However, an advantage of this is that I would be able to see the status of each tag I scan, which would can serve as a way for me to check in with myself about a habit and give enough time for all of them.

I wanted to learn how to design and 3D print for this project, and I was able to accomplish that goal. Translating something from my mind to the whiteboard, then to a modeling software, and finally materialized in physical space was a very exciting process for me, and I learned to consider a lot of different factors in my design, especially the space required to fit components. For example, the wiring for the neopixel needed to be routed through the dial to the protoboard, so I needed to make sure that there was enough clearance between the encoder stand and the protoboard stand to route these wires. While designing the internal components, I also had a general idea of how I wanted the external appearance would turn out, and this also helped the final design come together.

Nonetheless, there were also some issues with my 3D printed enclosure, which is understandable for my first time designing. The stands for my protoboard were too small, so I needed to sand down the edges of the board for it to fit. On the other hand, the stand for the encoder was too wide for the component to sit snugly in it. I used tape make sure it stayed in its place during operation. Moreover, I made the dial a bit too flimsy because the sides of it were too thin, and this made turning the dial feel less stable than anticipated. Another detail I overlooked was the gap between the dial and the body of the enclosure. The gap is inevitable, as there needs to be room for the dial to be pressed. However, this exposed the internals originally, and I improvised by wrapping the central LED ring support with white tape.

I learn a lot about my own process for designing, which involved quite a bit of improvising. After all, this is only the first iteration. I wanted to make sure that my internal components worked before I began to think too much about the final “look” of the product. I seem to allow the functionalities and components to dictate the form, instead of the other way around. I also designed the base before I designed the dial and top cover because I needed a tangible product to test fit the internals. Some of the considerations about the base only arose when the dial and lid needed to be designed, but it was too late. To summarize, I think I take on a very impatient but incremental approach to design, which caused some unfortunately silly mistakes.

What’s Next?

In the next iteration, I would address some of the issues with the external enclosure (i.e. make the dial sturdier, adjust the supports for components). Since this device would be sitting on my desk, I could afford to make it larger to fit in a real time clock module, which would allow me to implement the time-based features discussed above.

Another potential addition could be a way to report the development of a habit over time, like a line chart. This could be done via IoT method (most likely an AWS or Google Cloud micro service), so I could have a web dashboard that keeps track of my habit development. Another more analog way could be using a dot matrix printer. It could be a slow process where, after each day, a single line is printed, with each dot corresponding to a habit. There are a lot of directions this project could take, and I am excited to see what direction I end up choosing.

Technical Information

Schematic

Code

/*
   RFID Habit Tracker
   This is the code for the Arduino Nano Rev3 board within the habit tracker.
   Each time an RFID chip is scanned by the MFRC522 unit,
   a byte stored within the chip (buffer[0]) is incremented
   in its memory block (buffer) to represent the number of times a habit is reinforced.

   The maximum limit to increment up until is set by turning the encoder while
   pressing it down, and "flashing" a new tag with buffer[1] set as the
   max count.

   The status of a habit, when incremented, is displayed on the Neopixel ring with an animation.
   When turning the rotary encoder, the strength is also represented on the ring.
   When a tag has been scanned its "maximum" times, the ring fills up and rotates.

   The Adafruit Neopixel API was consulted to implement the LED animations.
   (https://learn.adafruit.com/adafruit-neopixel-uberguide/arduino-library-use)

   The Rotary encoder code is based on the example found here:
   (https://github.com/PaulStoffregen/Encoder)

   The MFRC522 scanner uses the library in the link below.
   (https://github.com/miguelbalboa/rfid)

   MFRC522 Wiring
   -------------------------------
               MFRC522     Arduino
               Reader/PCD  Nano v3
   Signal      Pin         Pin
   -------------------------------
   RST/Reset   RST         D9
   SPI SS      SDA(SS)     D10
   SPI MOSI    MOSI        D11
   SPI MISO    MISO        D12
   SPI SCK     SCK         D13

   Other connections
   -----------------------------------
   Device      Dev. Pin    Arduino Pin
   -----------------------------------
   Neopixel    Digital_in  D3
   Encoder     CLK         D6
   Encoder     DT          D5
   Encoder     SW          D4

*/

//#define ENCODER_DO_NOT_USE_INTERRUPTS
#include <SPI.h>
#include <MFRC522.h>
#include <Adafruit_NeoPixel.h>
#include <Encoder.h>

#ifdef __AVR__
#include <avr/power.h> // Required for 16 MHz Adafruit Trinket
#endif


const int RST_PIN     = 9;
const int SS_PIN      = 10;
const int LED_PIN     = 3;
const int BUTTON_PIN  = 4;

// milliseconds of no button press until we stop waiting to "flash"
const int TIMEOUT     = 3000;

//Max val the encoder can take on
const int MAX_ENCODER = 100;
//Max val that buffer[1] could be
const int MAX_SCANS = 30

                      const int LED_COUNT   = 16;
const int BRIGHTNESS  = 100;
const int FILLUP_SPEED = 10;

//Location of bytes in the RFID tag to write to
const byte BLK_ADDR      = 4;
const byte TRAILER_BLK   = 7;

float last = millis();
long pos = 0;
bool increment;

// Declare NeoPixel strip object:
Adafruit_NeoPixel strip(LED_COUNT, LED_PIN, NEO_GRB + NEO_KHZ800);

// Create MFRC522 instance.
MFRC522 mfrc522(SS_PIN, RST_PIN);
MFRC522::MIFARE_Key key;

Encoder myEnc(6, 5);

void dump_byte_array(byte *buffer, byte bufferSize);


void setup() {
  Serial.begin(115200); // Initialize serial communications with the PC
  pinMode(BUTTON_PIN, INPUT_PULLUP);

  strip.begin();           // INITIALIZE NeoPixel strip object (REQUIRED)
  strip.show();            // Turn OFF all pixels ASAP
  strip.setBrightness(BRIGHTNESS); // Set BRIGHTNESS to about 1/5 (max = 255)

  SPI.begin();        // Init SPI bus
  mfrc522.PCD_Init(); // Init MFRC522 card

  // Prepare the key (used both as key A and as key B)
  for (byte i = 0; i < 6; i++) {
    key.keyByte[i] = 0xFF;
  }
}

/*
   Main loop.
*/
void loop() {
  bool button = !digitalRead(BUTTON_PIN);
  bool flashed = false;
  if (button) {
    increment = false;
    last = millis();
    pos = min(max(0, myEnc.read()), MAX_ENCODER);
    myEnc.write(pos);
    display(pos, MAX_ENCODER);
    return;
  } else {
    // "flash" routine timed out, go back to waiting to
    // read and increment RFID tag
    if (millis() - last > TIMEOUT) {
      fadeout(2);
      increment = true;
      myEnc.write(0);
    }
  }

  // Reset the loop if no new card present on the sensor/reader.
  // We only check rfid tags when the button is not pressed
  // This prevents encoder lagging
  if ( ! mfrc522.PICC_IsNewCardPresent()) {
    return;
  }
  // Select one of the cards
  if ( ! mfrc522.PICC_ReadCardSerial())
    return;

  //reset encoder position
  myEnc.write(0);

  //declare buffer for data to be read into
  MFRC522::StatusCode status;
  byte buffer[18];
  byte size = sizeof(buffer);

  // Authenticate using key A,B
  status = (MFRC522::StatusCode) mfrc522.PCD_Authenticate(
             MFRC522::PICC_CMD_MF_AUTH_KEY_A, TRAILER_BLK, &key, &(mfrc522.uid));
  status = (MFRC522::StatusCode) mfrc522.PCD_Authenticate(
             MFRC522::PICC_CMD_MF_AUTH_KEY_B, TRAILER_BLK, &key, &(mfrc522.uid));
  if (status != MFRC522::STATUS_OK) {
    Serial.print(F("PCD_Authenticate() failed: "));
    Serial.println(mfrc522.GetStatusCodeName(status));
    return;
  }

  // Read to buffer
  mfrc522.MIFARE_Read(BLK_ADDR, buffer, &size);

  //Update buffer or reinitialize buffer
  if (increment) {
    buffer[0] = min(buffer[0] + 1, buffer[1]);
  } else {
    increment = true;
    flashed = true;
    buffer[0] = 0;
    buffer[1] = map(pos, 0, MAX_ENCODER, 0, MAX_SCANS);
  }

  // Write data to the block
  status = (MFRC522::StatusCode) mfrc522.MIFARE_Write(BLK_ADDR, buffer, 16);
  if (status != MFRC522::STATUS_OK) {
    Serial.print(F("MIFARE_Write() failed: "));
    Serial.println(mfrc522.GetStatusCodeName(status));
  }

  // Halt PICC
  mfrc522.PICC_HaltA();
  // Stop encryption on PCD
  mfrc522.PCD_StopCrypto1();

  // fill up ring and fade out to indicate newly flashed tag
  if (flashed) {
    for (int i = pos; i < MAX_ENCODER; i++) {
      display(i, MAX_ENCODER);
      delay(3);
    }
    fadeout(1);
  }

  //always display current strength animation
  fill_up_to(int(buffer[0]), int(buffer[1]));

  //Habit full strength, display lights
  if (buffer[0] == buffer[1])
  {
    rainbow_cycle();
  }
  last = millis();
}

/**
   Helper routine to dump a byte array as hex values to Serial.
*/
void dump_byte_array(byte *buffer, byte bufferSize) {
  for (byte i = 0; i < bufferSize; i++) {
    Serial.print(buffer[i] < 0x10 ? " 0" : " ");
    Serial.print(buffer[i], HEX);
  }
}

//display encoder value on ring with m being full
void display(int cur, int m) {
  int remapped = map(cur, 0, m, 0, BRIGHTNESS * LED_COUNT);

  int full_bright = remapped / BRIGHTNESS;
  int rem = (remapped) % BRIGHTNESS / 10 * 10;

  strip.clear();
  if (full_bright)
    strip.fill(strip.ColorHSV(49152, 255, BRIGHTNESS),
               LED_COUNT - full_bright, full_bright);
  strip.setPixelColor(LED_COUNT - 1 - full_bright,
                      strip.ColorHSV(49152, 255, rem));
  strip.show();
}

//fade out ring light at given speed
void fadeout(int speed) {
  for (int i = BRIGHTNESS; i >= 0; i -= speed) {
    strip.setBrightness(i);
    strip.show();
  }
  strip.clear();
  strip.setBrightness(BRIGHTNESS);
  strip.show();
}

//animate the ring filling up to an arc based on
//value (cur) and what would be a full circle (m)
void fill_up_to(int cur, int m) {
  int remapped = map(cur, 0, m, 0, BRIGHTNESS * LED_COUNT);

  int full_bright = remapped / BRIGHTNESS;
  int rem = (remapped) % BRIGHTNESS / 10 * 10;

  strip.clear();
  strip.show();
  for (uint16_t i = 0; i < full_bright; i++) {
    uint16_t hue = 0 - i * (65536 / LED_COUNT);
    for (int b = 0; b < 256; b += FILLUP_SPEED) {
      strip.setPixelColor(LED_COUNT - 1 - i,
                          strip.ColorHSV(hue, 255, b));
      delay(1);
      strip.show();
    }
  }

  uint16_t hue = 0 - full_bright * (65536 / LED_COUNT);
  strip.setPixelColor(LED_COUNT - 1 - full_bright,
                      strip.ColorHSV(hue, 255, rem));

  strip.show();
}

//rotate rainbow light around ring and fade out
void rainbow_cycle() {
  strip.clear();
  uint16_t prev = 0;
  for (uint16_t offset = 0;
       offset >= prev;
       offset += 65536 / LED_COUNT / 20) {
    
    prev = offset;
    for (uint16_t i = 0; i < LED_COUNT; i++) {
      uint16_t hue = offset - i * (65536 / LED_COUNT);
      strip.setPixelColor(LED_COUNT - 1 - i,
                          strip.ColorHSV(hue, 255, BRIGHTNESS));
    }
    delay(2);
    strip.show();
  }
  fadeout(1);
}

 

]]>
Physical Pomodoro Clock https://courses.ideate.cmu.edu/60-223/f2019/work/physical-pomodoro-clock/ Thu, 17 Oct 2019 02:37:46 +0000 https://courses.ideate.cmu.edu/60-223/f2019/work/?p=8316 Overview

The Physical Pomodoro Clock is a productivity tool disguised as a laptop stand that is designed to assist the user in staying focused over longer work periods by allowing the user to set a goal for how they would like to divide their productive time between working and taking healthy breaks as well as reinforcing achieving that goal through incentivizes for getting closer to the goal and reminders for the user when they stray from their goal.

Operation

Here’s a rundown of how the Physical Pomodoro Clock works.

It’s probably best to understand how the Pomodoro system itself works, which is a very simple concept. Break up the entire time you intend to work into 30 minute blocks. Work for the first 25 minutes, then take a break to stretch and move around for the next 5 minutes. Rinse and repeat for every 30 minute block.

The Physical Pomodoro follows a similar concept. While the clock is running, the user will either be in work or break mode. It’s up to the user to let the clock know when they’re doing what, and the clock will keep track of the proportion of time they spend working or on break. When a 30 minute cycle completes, the clock will compare the user’s work-to-break ratio, measured as a percentage, to the target ratio that the user can define as they please. The closer the user’s work-to-break ratio is to the target ratio, the more tokens they are awarded with. These tokens can then be spent for a random chance that a piece of candy is pushed out from the computer stand to award the user for their productivity and encourage them to keep at it.

Interacting with the Clock

Viewable on the LCD screen, the user can utilize two menus, the main pomodoro clock menu and a ‘popup’ menu for spending tokens, the virtual currency awarded for completing work cycles.

When interacting with the clock, there are two buttons and a knob.

  • The knob’s sole purpose is to set the target ratio.
  • The red button serves the following purposes
    • pressing it allows the user to start the clock from a paused state, resume work time from break, and spend tokens
    • holding it allows the user to access the token spending screen
  • The black button serves the following purposes
    • pressing it allows the user to start their break and exit from the token spending screen
    • holding it pauses the clock and resets the current cycle’s progress

Understanding the Clock Screen
The clock state

In the bottom right of the clock screen, there’s an indicator of whether the clock is running or not. If it is running, a “>” symbol will be shown and the clock time and progress indicators will be changing. If it is not running and thus in its paused state, a “||” symbol will appear. The clock as well as any progress on the current cycle will be set to 0 when it is paused.

Another useful indicator is the work vs. break indicator in the third column. If the current set mode is break, then there will be  a “B” in the top row of the third column. Otherwise the mode is work, so there is a “W” in the bottom row of the third column.

The ratio system

The clock menu displays a graphical representation of the user’s work-to-break ratio in the left-most column and the target ratio in the column to the right of it. The left side of each column shows work percentage while the right side shows the break percentage. The work time percentages are also shown numerically in the top row, where the first percentage, which has a checkered ‘target’ icon before it is the target ratio, while the user’s ratio is at the far right, which seems to be updating to a mid-20 percent at the moment the photo was taken, thus the furthest left column is around 1/4 of the way full on its left side, but there’s nothing on its right side since no break time has been taken in that cycle.  The target ratio at 94% is represented by a completely filled left half in the second column with not enough break time to be shown in the right half.

The ratio system has been scaled such that it has a minimum of 50% and a maximum of 95%.

Measures of overall progress

Note: The cycle and token values are stored on the device and persist even after powering the device off and on.

The remaining two unexplained parts are the numbers followed by “cy” and the coin symbol followed by a “x” and a number. The first set of numbers is an indicator of purely how many cycles have been completed, irregardless of ratios achieved or not achieved. The second number is the number of total tokens the user has available to spend on the token menu.

The token Menu

I’d say this menu is pretty self-explanatory. If you press the red button, it’ll deduct 10 tokens and do some probability math to decide whether it gives you a prize or not.

The Notification System

The clock can notify the user through the use of a vibrating motor which is mounted in the lower right corner of the above photo, as well as flashing an LED in the bottom left corner of this photo.

When the clock is turned on, put into play, or completes a cycle, it’ll do one short buzz.

When the clock wants to remind the user to take a break or get back to work because they’re too far from their target ratio, it repeatedly makes short buzzes until the user complies or the cycle completes.

When the user wins a prize from spending their tokens, a long buzz will sound from the clock to commemorate the moment.

Videos

Video of switching menus

Video of spending tokens for reward

Process and Review

This project began as one of several proposed ideas for something that could be useful in my life. The initial idea was a vague design based on a Pomodoro clock  with very minimal input, just 2 buttons and a potentiometer.

However, a mess of wires, electronic components, and boards aren’t exactly aesthetically appealing, so as the electronic circuit was nearly finished, I had to look for a suitable structure to mount the simple clock. As the goal of the project was to simply design something useful, I figured I’d mount the Pomodoro clock somewhere it would be most effective and give it a second purpose. A major reason I got a Pomodoro clock on my phone is how long I spend in front of a computer without getting up to move, which can be unhealthy. So I figured a computer stand could be a practical means to elevate my computer screen to a more comfortable level while being right where I need it most.

A picture of the WIP CAD design for the stand from the Fusion 360 software can be seen below.

Works great as a laptop stand, 1/4 in. plywood is pretty sturdy

In the later hours of working on this project, I had wavered on whether to keep or discard a reward system, where a virtual currency could be earned by following the Pomodoro clock closely. In the end, I decided I wanted to implement one and did a somewhat rushed job in adding a servo and some other parts to randomly reward the user with a candy when they spent the currency. Unfortunately, the candy I used did not fit very well through the hole it was designed to leave through, and the servo struggled to push the candy very far against friction. Perhaps if I had a different candy or designed the case to be larger to allow for more options, then it would’ve gone more smoothly.

 

A rainbow tangle of wires, normally kept hidden underneath the top panel of the laptop stand. The servo, candy, and, and part of a popsicle stick ramp can be seen on the right side, adjacent to the polycarbonate ‘window’.

Although much of the code ran smoothly, there were a few hitches. I had also uploaded a piece of code I had slightly modified but didn’t test thoroughly, thinking that there was no major affect. However, the notification system went off even at times it shouldn’t be during the demo, which made it rather difficult to demo the timer aspect due to the incessant buzzing from the vibration motor. But there was a very curious bug earlier in development when Japanese characters would appear, which was due to incorrect mapping to certain memory addresses of the LCD screen since the LCD screen came with Japanese and English characters by default that could be displayed on the screen. The intended goal was a few custom character slots that I had been writing the custom ratio indicator symbols to.

Discussion

Response

“It would be awesome if things are drilled onto the board instead of being taped.”

– I admit that the tape did not do as good a job as I hoped to hold things together. If I had more confidence in where things should be located, I may have gone ahead and glued it at least. Drilling wouldn’t be too bad for some components, but the breadboards I was using didn’t seem to be easily mounted in that manner, so I’d probably still avoid drilling.

“I wish we got a little more idea on how the timer itself worked.”

– And I wished I had given you a better idea about how it worked. That was just a flaw in my presentation skills after panicking from the notification system incorrectly going off while the timer was running. There was actually a lot of other functionality I wasn’t able to show, including a graphical representation of the ratios in the left two columns of the LCD, a break/work indicator column, and a play/plause indicator in the bottom right. But the basics are about the same as the classic Pomodoro clock. The ratio potentiometer adjusts a target goal to break out of the hard set 25-5 minute ratio, instead letting the user define the proportion of work they want to get done with each cycle. With the system of letting the user indicate whether they’re on break or working, my system gives the user more freedom in defining when they work and when they take a break, so the break doesn’t have to be at the end of the cycle. When the cycle completes, a productivity ratio closer to the self-chosen target ratio would award more tokens, which seem to be an aspect that received much praise, despite the system of reward delivery being close to non-functional.

“The circular acrylic window for the rectangular LCD display doesn’t make to much sense to me”

– I thought it would look cool to be able to see inside at the electronics underneath. Unfortunately, there’s not much in terms of LEDs or anything else to see, so it probably would’ve been better to stick to a square hole that would’ve made mounting it infinitely easier. So I certainly agree that the acrylic window was fairly unnecessary, though I think it does kind of look cool.

“Does it only hold one piece of candy at a time?” + “Try to use a smaller and lighter candy”

– Sadly yes, it holds only one piece of candy, since I was rather constrained by the space within the computer stand. If I was feeling more creative, I might play it off as a feature to encourage the user to get up and refill the candy each time instead of sit in front of the computer the whole time. If I were to do it again, especially if I wanted to hold more than one candy, I’d probably use a smaller and lighter candy that came in a shape that could easily roll, like a tic-tac, though I would have had to go buy the candy then.

“Nice popsicle stick” – Indeed.

Critique

After having completed the project, I can look back and say that there were at least two major problems. The first was creating a design that suffered too much from feature bloat. The design would’ve greatly benefited from retaining the simplicity of a basic Pomodoro clock to go with its simple controls. The whole reward system was an entertaining concept, but it appears to be detrimental to add unnecessary features that take away from the intentionally simple design. It probably would’ve been better to have used the second menu option for more in depth clock configurations.

The other problem is a general lack of planning for a number of issues. These included somewhat minor issues: a lack of space for the electronics to easily fit beneath the top board of the stand, not cutting certain openings a little smaller to account for the width of the laser, not laser cutting a slot for the small ‘tab’ in the potentiometer to fit into, as well as somewhat major ones: lack of a plan for mounting the devices and unclear aesthetic goals for the project. While the initial idea was feature-rich, it was lacking in implementation specifics.

Conclusion

This project has been a very valuable learning experience. The many issues that I didn’t anticipate this time around are something I can learn from and hopefully address when I can anticipate them in future projects and avoid making the same mistakes this time around. I also had to think outside the lasercut box for this project, opting for a laptop stand instead of a generic box. So at least, even if the Pomodoro clock doesn’t alway work, I can rely on it as a fine laptop stand. If I had another shot at it, I’d see if I could make it bigger with clear acrylic and set up a better candy delivery system, probably with a steeper ramp and a servo controlled valve that delivered smaller and rounder candies. The clear acrylic would let you could see the electronics working inside the stand as well as the candies moving around, which I think would be cool, plus the whole stand could pulse with the color of the internal LED when the user was being notified. I’d also see if I could fix the bug where the notification system goes off all the time when the intended behavior is to notify the user when they should end their break. At least it reminds the user to start their breaks and when a cycle ends, just like a classic Pomodoro clock.

Technical

Schematic

Code

/*
Physical Pomodoro Clock

Description: Code for taking input from two buttons and a potentiometer to control a pomodoro clock with a notification and reward functionality

Pin Mapping
Input
Pin |  Input
2      Button 1 (RED)
3      Button 2 (BLACK)
A0     Potentiometer
A5     Random Noise Pin

Output 
Pin | Output
5     Notification system (Vibration Motor + LED)
7     Servo Pin

//Referenced resources
   LCD Screen code contains snippets and references to code written by Robert Zacharias at Carnegie Mellon University, rzach@cmu.edu
   released by the author to the public domain, November 2018
//https://forum.arduino.cc/index.php?topic=418257.0
//https://forum.arduino.cc/index.php?topic=215062.0
//https://learn.robotgeek.com/28-robotgeek-getting-started-guides/61-robotgeek-i2c-lcd-library#customChar
//https://learn.robotgeek.com/getting-started/59-lcd-special-characters.html
*/




#include <Wire.h>
#include <LiquidCrystal_I2C.h>
#include <Servo.h>
#include <EEPROM.h>

/* Create an LCD display object called "screen" with I2C address 0x27
   which is 16 columns wide and 2 rows tall. You can use any name you'd like. */
LiquidCrystal_I2C screen(0x27, 16, 2);

Servo gate;

//Pins
const int BUTTON1_PIN = 2;
const int BUTTON2_PIN = 3;
const int RATIO_PIN = A0;
const int NOTIFIER_PIN = 5;
const int REWARD_PIN = 7;
const int RANDOM_PIN = A5; //Intentionally unconnected to be a source of random noise
//***


//***Utilities***

//Mode
bool isPomo = true;

//Notifier
bool canNotify = false;
unsigned long notifyTimer = 0;
unsigned long endTimer = 0;
bool flop = false;
int flopFreq = 0;
int duration = 0;

//Masks
const int B1P_MASK = B0001;
const int B1H_MASK = B0010;
const int B2P_MASK = B0100;
const int B2H_MASK = B1000;

//Time constants
const int delayRate = 100; //Refresh rate in ms for the whole system
const int renderRate = 800; //rate of rendering

//Max Pot Value
const int MAX_POT = 1023; //Represents the highest potentiometer output

//Buttons
//tracks how many delays
int counter = 0;
//The minimum time in ms necessary for a press to count as any press
const int minPressTime = 300;
//The minimum time in ms necessary for a hold to register
const int minHoldTime = 2000;

//int[] for button hold times
unsigned int button_Times[2];

//Reward Gate parameters
const int gateTime = 5000; //2 seconds to grab the candy if won 
const int openAngle = 5;
const int closedAngle = 90;


//***Pomodoro
unsigned long referenceTime = 0; //Whenever the clock is 'paused', this number is no longer useful and must be reset on resuming pomodoro. Thus a pause resets progress on that pomodoro.
//For higher accuracy on progress time
unsigned long lastTick = 0;
unsigned int elapsed = 0; //Once elapsed in seconds reaches the equivalent of 30 minutes, a cycle will be updated, and 'prizes' awarded. Then it will be reset
unsigned long progress = 0; //productive time in milliseconds
unsigned int cycles = 0;
const unsigned int CYCLE_CONSTANT = 60; //number of seconds in 30 minute pomodoro
//const byte pRatios[6] = {1, 2, 4, 5, 9, 14}; //productivity ratio numbers to 1
float ratio = 0.83; //number of productive seconds
//Ratio limits. Max break is 15 min, while min break is 2 min per 30 minute pomodoro
const float minRatio = 0.5;
const float maxRatio = 0.95;


//ratio and progress percentages respectively
byte rPercent = 0;
byte pPercent = 0;

bool isPaused = true;
bool isBreak = false;

//byte columns
byte left_c = B11000;
byte right_c = B00011;

//symbol like > to indicate clock is running #Not Used
byte playSign[] = {
  B10000,
  B11000,
  B11100,
  B11111,
  B11100,
  B11000,
  B10000,
  B00000
}; // (note the extra row of zeros at the bottom)

//symbol like || to indicate clock is paused
byte pauseSign[] = {
  B10001,
  B11011,
  B11011,
  B11011,
  B11011,
  B10001,
  B00000,
  B00000
}; // (note the extra row of zeros at the bottom)
//***

//Not Used
byte upSign[] = {
  B00000,
  B00100,
  B01110,
  B11111,
  B00000,
  B00000,
  B00000,
  B00000
}; // (note the extra row of zeros at the bottom)

//Not Used
byte downSign[] = {
  B00000,
  B00000,
  B00000,
  B11111,
  B01110,
  B00100,
  B00000,
  B00000
}; // (note the extra row of zeros at the bottom)

//Resembles a coin
byte rewardSign[] = {
  B00100,
  B01010,
  B11101,
  B11101,
  B11101,
  B01110,
  B00100,
  B00000
}; // (note the extra row of zeros at the bottom)

//Resembles a target
byte targetSign[] = {
  B00000,
  B00100,
  B01010,
  B10101,
  B01010,
  B00100,
  B00000,
  B00000
}; // (note the extra row of zeros at the bottom)

/*
  //This is adjustable and determined by code
  byte targetSign[] = {
  B00000,
  B00000,
  B00000,
  B00000,
  B00000,
  B00000,
  B00000,
  B00000
  }; // (note the extra row of zeros at the bottom)

  //This is determined by code and updated as progress changes
  byte progressSign[] = {
  B00000,
  B00000,
  B00000,
  B00000,
  B00000,
  B00000,
  B00000,
  B00000
  }; // (note the extra row of zeros at the bottom)
*/

//***Reward System
unsigned int tokens = 100; //Total 'currency'
unsigned int spinPrice = 10; //Price to attempt to draw a prize
bool trySpin = false; //Indicates whether an attempt to draw a prize is made
const int randomThreshold = 40; //Percent chance that prize is won 
//***


struct saveData {
  unsigned int savedCycles;
  unsigned int savedTokens;
};


void setup() {
  Serial.begin(9600);

  saveData lastSave;
  //Assumed that EEPROM will read and write from first address
  EEPROM.get(0,lastSave);
  Serial.println(lastSave.savedTokens);
  cycles = (lastSave.savedCycles!=65535)?lastSave.savedCycles:0;
  tokens = (lastSave.savedTokens!=65535)?lastSave.savedTokens:0;
  
  //Random Seed setup
  randomSeed(analogRead(RANDOM_PIN));

  //Setup Pins
  //Buttons
  pinMode(BUTTON1_PIN, INPUT);
  pinMode(BUTTON2_PIN, INPUT);
  //Potentiometer
  pinMode(RATIO_PIN, INPUT);
  //Audio-visual Notification
  pinMode(NOTIFIER_PIN, OUTPUT);
  gate.attach(REWARD_PIN);

  //turn gate to closed position initially
  gate.write(closedAngle);
  
  // initialize the screen (only need to do this once)
  screen.init();

  // turn on the backlight to start
  screen.backlight();

  //Loads symbols into memory
  screen.createChar(0, playSign);
  screen.createChar(1, pauseSign);
  //2 is reserved for both up and down signs and dynamically set later
  screen.createChar(3, rewardSign);
  screen.createChar(4, targetSign);
  // set cursor to home position, i.e. the upper left corner
  //screen.home();

  screen.clear();
  screen.home();
  //screen.print("b1 = ");
  //screen.setCursor(0, 1);
  //screen.print("b2 = ");
  digitalWrite(5, HIGH);
  delay(1000);
  digitalWrite(5, LOW);
  //tokens = 100;

}

void loop() {
  
  //screen.clear();
  delay(delayRate);

  //screen.setCursor(5, 0);
  //screen.print(digitalRead(2));

  //screen.setCursor(5, 1);
  //screen.print(digitalRead(3));
  //Serial.println(analogRead(RATIO_PIN));

  byte buttonData = buttonInput();
  //Button Data: ####
  //Last # (2^0): B1 Press
  //Left1# (2^1): B1 Hold
  //Left3# (2^2): B3 Press
  //Left4# (2^3): B4 Hold
  //A short hold has short hold logic
  //A long hold has long hold logic

  //if it isn't paused, process data from last loop
  if (!isPaused)pomodoro_tick();

  //Divides logic based on whether the mode is Pomodoro or not
  if (isPomo) {
    //updates various internal variables for this display mode
    pomodoro_logic();
    //Render to screen step
    if (counter > renderRate)renderPomo();

    //Button press logic
    //If the 1st button has been pressed and released, ***PLAY***
    if ((buttonData & B1P_MASK) != 0) { 
      //start/resume functionality
      //Start from paused
      if (isPaused) {
        isPaused = false;
        //Re-syncs the reference time
        referenceTime = millis();
        //syncs up the lasttick initially
        lastTick = millis();
        //enables notifies
        enableNotify();
      }

      //Resumes from break
      if (isBreak) isBreak = false;

    }
    //If the 1st button has been held, ***SHOP***
    if ((buttonData & B1H_MASK) != 0) { 
      //Switch menus
      isPomo = !isPomo;
      //Limits clear commands to when screen changes
      screen.clear();
    }

    //If the 2nd button has been pressed, Mark Break  ***BREAK***
    if ((buttonData & B2P_MASK) != 0) {
      isBreak = true;
    }
    //If the 2nd button has been held, Mark Pause/End ***PAUSE***
    if ((buttonData & B2H_MASK) != 0) {
      isPaused = true;
      //reset all current progress and time, this way there won't be any surprises when resyncing resets progress
      progress = 0;
      elapsed = 0;
      //Disable notifies
      disableNotify();
    }




  }
  else {
    //shop Logic, only relevant when in shop, so does not need to be in main loop body
    shopLogic();
    //Render Entertainment
    if (counter > renderRate)renderShop();

    //press button 1
    if ((buttonData & B1P_MASK) != 0) {
      //the spend your token sort of fun stuff
      trySpin = true;
    }
    //press button 2
    if ((buttonData & B2P_MASK) != 0) {
      //returns to main screen
      isPomo = !isPomo;
      //Limits clear commands to when screen changes
      screen.clear();
    }


  }



  //Ensures that counter always increments, so refresh doesn't get stuck at menu changes
  counter += delayRate;
}

//Relevant Logic for the Shop
void shopLogic() {
//Spin button pressed
 if (trySpin) {
    //Tokens are charged
    if (tokens >= spinPrice) {
      tokens -= spinPrice;
      generateReward();
      updateSave();
    }
    else {
      //show inssuficient funds
      //Serial.println("fail");
      screen.clear();
      screen.home();
      screen.print("Insufficient ");
      screen.write(3);
    }
    trySpin = false;
  }
  
}
void renderShop() {
  counter = 0;
  //screen.clear();
  screen.home();
  
  //Default Screen
  
  //Top Row
  screen.print("Token Spin | Own");

  //Bottom Row
  screen.setCursor(0, 1);
  screen.print("Use ");
  screen.write(3);
  screen.print("x" + (String)spinPrice);
  screen.setCursor(11,1);
  screen.print("|");
  screen.write(3);
  screen.print("x" + (String)tokens);

}
//to be implemented
void generateReward() 
{
  
  screen.clear();
  screen.home();
  int randNumb = random(100); //Generates a random number from 0 to 99 (100 possible numbers)
  if(randNumb<randomThreshold){ //If the number is less than the threshold, or percent chance to win, then the user wins a prize
    gate.write(openAngle); //Opens the prize gate
    screen.print("You Win a Prize!");
    digitalWrite(NOTIFIER_PIN, HIGH); //Some other indications of winning
    delay(gateTime); //Time to claim prize
    gate.write(closedAngle); //Closes the prize gate
    digitalWrite(NOTIFIER_PIN, LOW); //Silences the notification
  }
  else{
    screen.print("...Try again?"); //A somewhat encouraging message in case of loss
    delay(1000);
  }
  
}


//byte Cells[2][8] = {}; //fill rows 0-6 of 1D arrays
//byte Cell1[8] = {};
//byte cell2[8] = {};

//print double column (each cell has 7 rows)
void RatioPercent(int column, float r1, float r2, byte slot) {
  //These individual byte arrays may be converted to a 2D array
  //byte topCell[8]; //fill rows 0-6
  //byte lowCell[8]; //fill rows 0-6

  int divisions = 7;
  int cells = 2;

  //starts at the bottom of the cell, row 1
  for (int j = cells - 1; j >= 0; j--) {
    //j = cell index, therefore
    //j is inverse for left
    //j is direct for right
    byte temp[8] = {};
    for (int i = divisions - 1; i >= 0; i--) {
      //i begins at the bottom rows, at index 6

      //left: the relative number of rows that should be lit : Right (cells begin from the bottom, as do the row writing, thus thye have to be subtracted)
      if (r1 * divisions * cells > (cells - j - 1)*divisions + (divisions - i)) {
        //Cells[j][i] = Cells[j][i]|left_c;
        temp[i] = B11000;
      }
      //
      //At the bottom ,it would be all previous cells plus all rows of last cell (j*divisions) + i rows
      if (r2 * divisions * cells > (j)*divisions + i + 1) {
        //Cells[j][i] = Cells[j][i]|right_c;
        temp[i] = temp[i] | B00011;
      }

    }
    screen.createChar(slot + j, temp);
    //Print current cell
    screen.setCursor(column, j);
    screen.write(slot + j);
  }
}


//Renders the screen for the pomo mode
void renderPomo() {
  //Resets the counter for screen refresh
  counter = 0;

  //clear the screen
  //screen.clear();
  //Personal Progress/ratio (column 0, both rows)
  RatioPercent(0, pPercent / 100.0, (float)elapsed / CYCLE_CONSTANT - pPercent / 100.0, 5);

  //Target Progress/ratio (column 1, both rows)
  RatioPercent(1, rPercent / 100.0, 1 - rPercent / 100.0, 7);

  //Break or work (column 2, both rows)
  screen.setCursor(2, 0); //isBreak is true (top)
  screen.print((isBreak) ? ("B") : (" ")); //Prints B for break on top or empty for not break
  screen.setCursor(2, 1); //isBreak is false (bottom)
  screen.print((isBreak) ? (" ") : ("W")); //Prints B W for work on bottom or empty for on break
  //target percent (column 3-6, row 0)
  char char_buffer [6]; // a few bytes larger than your intended line
  sprintf (char_buffer, "%02d", rPercent);
  screen.setCursor(3, 0);
  screen.write (4); //target sign, index 3
  screen.print (char_buffer); // index 4,5
  screen.write (37); //percent symbol, index 6

  //# of cycles (column 4-7, row 1)
  screen.setCursor(4, 1);
  //screen.print(cycles);
  sprintf (char_buffer, "%02d", cycles); //number of cycles. May go into the 10s, but assumed that using this for 100 cycles or 3000 continuous minutes is unlikely
  screen.print(char_buffer);
  screen.setCursor(6, 1);
  screen.print("cy"); //for cycles

  //Prints time string (column 7-11, row 0)
  //char char_buffer2[6];
  sprintf (char_buffer, "%02d%s%02d", elapsed / 60, ":", elapsed % 60); // send data to the buffer
  screen.setCursor(7, 0);
  screen.print(char_buffer);

  //print reward string (column 9-13or14, row 1)
  screen.setCursor(9, 1);
  screen.write(3); //reward sign, index 8
  sprintf (char_buffer, "%03d", tokens);
  screen.print("x");
  screen.print(char_buffer);

  //indicator of current percent (c12-15, r0)
  //Writes percentage
  screen.setCursor(12, 0);
  if(pPercent<100){ //to avoid three digits pushing the line off the screen
    sprintf (char_buffer, "%3d", pPercent);
    screen.print (char_buffer); // index 4,5
  }
  screen.write (37); //percent symbol, index 6
  
  /*
  screen.setCursor(12, 0);
  //print indicator of more work or more rest
  //threshold is hard set as 5 currently, can be serialized as a const variable if necessary
  if (pPercent - rPercent > 5) {
    screen.createChar(2, downSign);
    screen.write(2);
  }
  else if (rPercent - pPercent > 5) {
    screen.createChar(2, upSign);
    screen.write(2);
  }
  else screen.print(" ");
  */
  
  //indicator of pause or play status
  screen.setCursor(15, 1);
  screen.write((isPaused) ? (1) : (62)); //if paused, print pause symbol, if playing, use > for play symbol (62).
}

//Pomodoro Logic to keep variables updated while clock functionality is in play
void pomodoro_tick() {
  //temp long
  //moves progress if not on break
  elapsed = (unsigned int)((unsigned long)(millis() - referenceTime) / (unsigned long)1000);
  if (!isBreak)progress += millis() - lastTick;
  lastTick = millis();
  
  //Loops pomodoro once time reaches limit
  if (elapsed > CYCLE_CONSTANT) {
    elapsed = 0;
    progress = 0;
    cycles += 1;
    calcReward();
    referenceTime = millis();
    updateSave();
  }

  //Reminder Logic
  //Operate on the NOTIFIER_PIN
  
  //Long LED and vibration for when cycle restarts
  if(elapsed == 0){ //an indicator that a cycle restart occurred
      
      startNotify(1000, 0);
  }
  
  //Short pulses when break should begin
  if((!isBreak)&&(pPercent-rPercent>5)){
    if(duration==0)startNotify(1000,1);
  }
  
  //Short pulses when work should resume
  if((isBreak)&&((int)((float)(elapsed*100)/CYCLE_CONSTANT-pPercent)-(100-rPercent)>5)){
    if(duration==0)startNotify(1000,1);
  }


  if(canNotify)writeNotify();
  

}

//Relevant logic that occurs in pomodoro screen mode but not related to its ticking (clock) functionality
void pomodoro_logic() {
  
  ratio = analogRead(RATIO_PIN)*(maxRatio - minRatio)/MAX_POT+minRatio; //Sets the ratio proportional to input potentiometer signal within the range between min and max ratio
  
  rPercent = (int)(ratio * 100);
  pPercent = ((int)(progress / (unsigned long)10) / (CYCLE_CONSTANT));
  
  
  
}

//Determines Reward for completing a pomodoro
void calcReward() {
  //Uses rPercent and pPercent to determine accuracy thresholds
  if(abs(pPercent-rPercent)<10)tokens += 10;
  else if(abs(pPercent-rPercent)<20)tokens+=8;
  else if(abs(pPercent-rPercent)<30)tokens+=6;
  else tokens+=4;
}

void startNotify(int totalTime, int flopfrq){
   notifyTimer = millis();
   endTimer = notifyTimer + totalTime;
   flop = false;
   flopFreq = flopfrq;
   duration = totalTime;
   digitalWrite(NOTIFIER_PIN, HIGH);
}

//Determines when and how to end the notify
void writeNotify(){
  Serial.println("Write notify");
  //Checks if there's a timer in progress, i.e. duration has been set.
  if(duration>0){
    //if the timer time has reached the flop time (the entire duration if 0 flopFreq)
    if(notifyTimer+(duration/(flopFreq+1))<=millis()){ 
        Serial.println("writing");     
        digitalWrite(NOTIFIER_PIN, flop?HIGH:LOW);
        flop = !flop;
        notifyTimer = millis();
        
    }
    //Deactivates timer if the time has been reached
    if(endTimer<=notifyTimer){
      duration = 0;
      digitalWrite(NOTIFIER_PIN, LOW);
    }
  }
}

//Allows notify system to go
void enableNotify(){
  canNotify = true;
}
//Prevents notify system from going off
void disableNotify(){
  digitalWrite(NOTIFIER_PIN, LOW);
  canNotify = false;
  duration = 0;
}
//Commits the persistent data to storage
void updateSave(){
  saveData newSave = {
    cycles,
    tokens
  };
  //Assumed same address
  EEPROM.put(0,newSave);
  
}

byte buttonInput() {
  //Register Button Hold Times
  //For that button, detect if it is held or released.
  //If the array elements are greater than 0, then that element has been held earlier.
  //Minimum time registered as any hold to avoid any flickering
  byte input = 0;

  //button 1 logic
  if (digitalRead(BUTTON1_PIN)) {
    //Determines if the signal duration has entered the hold range
    if (button_Times[0] >= minHoldTime)input = input | B1H_MASK;
    //While held, the button time will continue to increment
    button_Times[0] += delayRate;
  }
  else {
    //if the button hold duration is long enough to count as an intentional signal but not a hold, then it must be a press signal
    if (button_Times[0] >= minPressTime && button_Times[0] < minHoldTime)input = input | B1P_MASK;
    //Since the button has been 'released' or is currently not pressed, time is reset to 0.
    button_Times[0] = 0;
  }

  //Same logic as above but for button 2
  if (digitalRead(BUTTON2_PIN)) {
    //Determines if the signal duration has entered the hold range
    if (button_Times[1] >= minHoldTime)input = input | B2H_MASK;
    //While held, the button time will continue to increment
    button_Times[1] += delayRate;
  }
  else {
    //if the button hold duration is long enough to count as an intentional signal but not a hold, then it must be a press signal
    if (button_Times[1] >= minPressTime && button_Times[1] < minHoldTime)input = input | B2P_MASK;
    //Since the button has been 'released' or is currently not pressed, time is reset to 0.
    button_Times[1] = 0;
  }
  return input;
}

 

]]>
Watering Machine https://courses.ideate.cmu.edu/60-223/f2019/work/watering-machine/ Wed, 16 Oct 2019 23:52:53 +0000 https://courses.ideate.cmu.edu/60-223/f2019/work/?p=8320 OVERVIEW

This is a smart 4-plant watering machine that caters to the different need of each plant.

Front view

Front view

Back view

Back view

I own a couple unfortunate plants, two small ones and two big ones. They suffer from dehydration whenever I’m away from home. I decided to design a plant watering machine because I want them to stay alive.  Moreover, I want to design a watering machine that caters to the different need of every plant. The idea is that I am able to set a watering cycle such that the machine repetitively waters the plants with different amount of water every set amount of time. For example, I can configure the machine to water the 2 small plants with 1 portion of water and 2 big plants with 3 portions of water everyday.

The final product allows me to set the wait time between each watering cycle with 3 preset buttons (0~3 units of time). Also, for each individual plant, there are 3 preset buttons to set the watering amount (0~3 portions).

Labelled LED Interface

Labelled LED Interface:

plant 1 watering amount = 3 (15 seconds of watering)

plant 2 watering amount = 0

plant 3 watering amount = 2 (10 seconds of watering)

plant 4 watering amount = 1 (5 seconds of watering)

Time between cycle = 2 (20 seconds between each cycle)

In action

In action (disclaimer: the LED doesn’t jitter in reality. The frame rate aliasing makes it to look jittery in the video)

Valve close-up

Valve close-up

Valves

Valves

Tubing

Tubing

PROCESS IMAGES AND REVIEW

One of the hardest problems of this project was designing the valves. It was a mechanical problem and I had zero experience in designing mechanics. I tried to design the valve three times and the last time worked.

First valve design with crossed screws -- not pretty and not really functionalIn the first iteration, I built a valve with two crossing screws such that when the servo rotates, it “pinches” the tube to stop the water from flowing. However, this design was not good, because first of all, the tube was too hard to pinch, and second, the cross shape of the plastic base always trip on the tube and move the tube away.

Soft tube

Soft tube (ordered)

Hard tube

Hard tube (original)

For the second iteration, I ordered softer tubes that made it possible for the servo to stop water from flowing. In the above pictures, you can see the soft tube is much easier to bend.

Final valve design

In the third iteration, I remade the valves with round plastic base and wide apart screws. The crossing screws might cut the tube open. So I designed another version of the valve where the screws won’t pinch the tube, but rather stretch the tube. The soft tube is flexible enough to be stretched. The round plastic base also allows the tube to stay in place when the valve rotates.

Closed vs open valves

Closed vs open valves

This is how the valves look like when they are all open.

Prototype

Prototype

This is the prototype version of this watering machine. In the above image, you can see the first iteration of the valve design.

Considered another pump

I also considered using another pump, but this pump is WAY too powerful for watering purpose.

Tested many servos to find the ones that don't jitter when the pump is on

Tested many servos to find the ones that don’t jitter when the pump is on

Capacitor

Capacitor

One problem I encountered was that the servos jitter really hard when the pump was on. I tested all servos in physical computing lab and found 4 servos that don’t jitter. I also added a capacitor to smooth out the power supply.

Labelled LED Interface

Labelled LED Interface: plant 1 watering amount = 3 (15 seconds of watering) plant 2 watering amount = 0 plant 3 watering amount = 2 (10 seconds of watering) plant 4 watering amount = 1 (5 seconds of watering) Time between cycle = 2 (20 seconds between each cycle)

I also found it challenging to design the interface of the watering machine. I chose the Adafruit Trellis squishy 4×4 LED and button matrix simply because I love the feeling of pressing on them. However, with 16 buttons and LEDs it’s hard to tell the user how to use it and what the setting is. If I want to teach someone else to use the watering machine, I’d love it to be easy to explain and remember. The interface I came up with is the following. The top left button is a red button that execute the setting when it’s pressed. It blinks to confirm the setting. Column 2-4 allow the user to toggle the watering level from 0-3. The first column sets the wait time between each watering cycle from 0-3.

However, without the labels, it is extremely difficult to explain. Therefore, I labeled the buttons and asked someone who has never seen this project to try to interpret it. The only confusion with the labels is the time between cycle setting. The person wondered if it sets the watering frequency during a day or the amount of wait time between each cycle. Currently the time between cycle sets the amount of wait time between each cycle. One unit of time is 10 seconds. So in the above setting, there’s a 20-second wait between each cycle.

DISCUSSION

Response

“The interface might be confusing to some—no labels so it’s easy to get lost!”

Yeah I agree. It’d be great to use different colors to indicate the different functionality of each column. I could have also used a LED screen to show words and numbers. It was a personal decision to use the 4×4 LED and button matrix and I didn’t have enough variety of LED colors to color-code the functionality.

“I found the mechanism interesting, difficult to construct and very useful! Bravo Jeena!!”

Wahhoooo! It was pretty difficult to construct the valves. It took 3 iterations to finally get the valves to tighten properly. In the first iteration, I had two crossing screws close the valve by pinching the tube, but the tube was too hard to actually close up. The second iteration I bought softer tubes, which made it much easier. However, crossing screws were too weak to form a close seal. In the third iteration, I designed a slightly different valve mechanism, where the soft tubes are fixed on both end with zip ties and hot glue, and the valve simply stretch and bend the tube to form a seal.

Self critique

I mostly agree with the comments I received at the critique. I’m happy with how it turned out to be — a functioning watering machine that can water 4 different plants with a sufficient interface (for me). But I’d love it to be smaller, something that can be tucked away in my living room. Right now, it’s a giant open shelf of wires. It’s not pretty enough to occupy that much space. It’d be great to use a smaller clear acrylic box that encloses everything and allows me to see through.

What I learned

Mechanical problems are real.

I didn’t think the valves are hard to design at all. The idea is to water one plant at a time, so I close all valves but one to water only one plant. To switch between valves, I open another valve and close all other valves. I spent most time trying different ways to stop water from flowing while another valve is opened. In the prototype week, I didn’t figure out the mechanical problem. Next time, I should try to figure out the hardest part first.

Also, I gained a ton of soldering skills by soldering 32 LEDs in one go for the button interface, plus many wires later on. I learned how to be very careful and fast at the same time.

I drilled many holes to make the valves work. I drilled holes into the plastic pieces that come with the hobby servos first, then screw the screws in. It was not easy at all, given that the holes are all so tiny. Also, I drilled holes for the zip ties to secure the tubes onto the wooden shelf.

I learned how to pronounce “valve” correctly. It’s “vaaaalv” not “volve”. Ah.

Next steps

I will make a box with a clear acrylic door that can enclose the box.

TECHNICAL INFORMATION

Schematic

Schematic

Schematic

Shelf design

Shelf design

/*
 * Project 2
 * Jeena Yin (qyin)
 * It took two weeks
 *
 * Collaboration:
 * Referenced code in 
 * https://learn.adafruit.com/adafruit-trellis-diy-open-source-led-keypad/connecting
 * https://courses.ideate.cmu.edu/60-223/f2019/tutorials/code-bites#blink-without-blocking
 * 
 * Summary: The code below waters 4 plant with different watering 
 * amount according to the setting
 * 
 * Inputs:
 * Adafruit Trellis LED buttons | PIN A2
 * Valve 0 button               | PIN 4
 * Valve 1 button               | PIN 5
 * Valve 2 button               | PIN 7
 * Valve 3 button               | PIN 8
 * 
 * Outputs:
 * Valve 0 servo | PIN 6 (PWM)
 * Valve 1 servo | PIN 9 (PWM)
 * Valve 2 servo | PIN 10 (PWM)
 * Valve 3 servo | PIN 11 (PWM)
 * Pump motor    | PIN 3
 */

#include <Wire.h>
#include <Servo.h>
#include "Adafruit_Trellis.h"

#define NUMTRELLIS 1
#define numKeys (NUMTRELLIS * 16)
#define INTPIN A2

// Valve 0
const int VALVE0_PIN = 6;        // PWM
const int VALVE0_SWITCH_PIN = 4; // for testing

// Valve 1
const int VALVE1_PIN = 9;        // PWM
const int VALVE1_SWITCH_PIN = 5; // for testing

// Valve 2
const int VALVE2_PIN = 10;       // PWM
const int VALVE2_SWITCH_PIN = 7; // for 

// Valve 3
const int VALVE3_PIN = 11;       // PWM
const int VALVE3_SWITCH_PIN = 8; // for testing

// Pump
const int PUMP_PIN = 3;

const int VALVE_CLOSE_POS = 0;
const int VALVE_OPEN_POS = 140;

// 10 seconds as cycle length: if time set to 2 then water every 20 seconds
const int CYCLELENGTH = 10; 
// 5 seconds as unit for watering amount
const int WATERAMOUNTUNIT = 5; 

// Blink the red LED as feedback confirmation
const int blinkLEDdelay = 70;

// Array that stores the water amount setting
int waterAmount[] = {0, 0, 0, 0};
int waterFrequency = 0;
unsigned long microTimer = 0;
unsigned long macroTimer = 0;
unsigned long quarterMacroTimer = 0; // wait time between watering cycles in second
unsigned long quarterMicroTimer = 0; // wait time between each plant in second
int wateringPlantId = -1; // id of plant being watered

// only water if frequency > 0 and wateramount is > 0 for any plant
bool shouldWater = false; 
bool waitingForNextCycle = false;

Servo valve0;
Servo valve1;
Servo valve2;
Servo valve3;

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

Adafruit_Trellis matrix = Adafruit_Trellis();
Adafruit_TrellisSet trellis = Adafruit_TrellisSet(&matrix);

void setup() {
  // put your setup code here, to run once:
  pinMode(PUMP_PIN, OUTPUT);
  pinMode(VALVE0_SWITCH_PIN, INPUT_PULLUP);
  pinMode(VALVE1_SWITCH_PIN, INPUT_PULLUP);
  pinMode(VALVE2_SWITCH_PIN, INPUT_PULLUP);
  pinMode(VALVE3_SWITCH_PIN, INPUT_PULLUP);
  pinMode(INTPIN, INPUT);
  Serial.begin(9600);
  valve0.attach(VALVE0_PIN);
  valve1.attach(VALVE1_PIN);
  valve2.attach(VALVE2_PIN);
  valve3.attach(VALVE3_PIN);
  
  for(int i = 0; i < 4; i++){
    OpenValveForPlant(i);
  }
  OpenAllValves();
  
  digitalWrite(INTPIN, HIGH);
  trellis.begin(0x70);

  // turn on all LEDs
  for (uint8_t i=0; i<numKeys; i++) {
    trellis.setLED(i);
    trellis.writeDisplay();    
    delay(50);
  }
  // then turn them off
  for (uint8_t i=0; i<numKeys; i++) {
    trellis.clrLED(i);
    trellis.writeDisplay();    
    delay(50);
  }
  trellis.setLED(0); // light up the red button only
  trellis.writeDisplay();
}

void loop() {
  if (trellis.readSwitches()) {
    for (uint8_t i=0; i<numKeys; i++) {
      if (i == 0 && trellis.justReleased(i)) {
        BlinkLED(i);
      } 
      if (trellis.justPressed(i)) {
        ButtonPressed(i);
      }
    }
    // tell the trellis to set the LEDs we requested
    trellis.writeDisplay();
  }

  // Watering plants
  if(shouldWater) {
    if(millis()/1000 - microTimer >= quarterMicroTimer) {
      // switch to next plant
      if(wateringPlantId == 3) WaitForNextCycle(); // Cycle ends
      else{
        GetReadyForPlant(wateringPlantId + 1);
      }
    }
  }
  // Waiting for next watering cycle
  else if(waitingForNextCycle) {
    if(millis()/1000 - macroTimer >= quarterMacroTimer) {
      Execute();
      macroTimer = millis()/1000;
    }
  }
  // Only check test switches when it's not watering.
  else{
    CheckSwitches();
  }
  delay(30);
}

void ButtonPressed(int i) { 
  // The red button is pressed
  if(i == 0) {
    Execute();
    // if it was pressed, turn it on
    if (trellis.justPressed(i)) {
      trellis.setLED(i);
    }
    return;
  }
  
  int col = i % 4;
  int row = (int) (i / 4);

  // Set Time
  if(col == 0) { // Interface design silimar to a slider
    if (trellis.isLED(i)) {
      if(row == 3 || !trellis.isLED(i+4)) { // Frequency = 0
        SetFrequency(0);
        for(int k = 1; k <= 3; k++) {
          trellis.clrLED(k*4);
        }
      }
      else {                                // Adjust Frequency
        SetFrequency(row);
        for(int k = 1; k <= row; k++) {
          trellis.setLED(k*4);
        }
        for(int k = row+1; k <= 3; k++) {
          trellis.clrLED(k*4);
        }
      }
    }
    else {                                  // Adjust Frequency 
      SetFrequency(row);
      for(int k = 1; k <= row; k++) {
        trellis.setLED(k*4);
      }
      for(int k = row+1; k <= 3; k++) {
        trellis.clrLED(k*4);
      }
    }
  }
  // Set water level
  else { // Interface design silimar to a slider
    if (trellis.isLED(i)) {
      if(col == 3 || !trellis.isLED(i+1)) { // No water
        SetWater(row, 0);
        for(int k = 1; k <= 3; k++) {
          trellis.clrLED(k+row*4);
        }
      }
      else {                                // Adjust water level
        SetWater(row, col);
        for(int k = 1; k <= col; k++) {
          trellis.setLED(k+row*4);
        }
        for(int k = col+1; k <= 3; k++) {
          trellis.clrLED(k+row*4);
        }
      }
    }
    else {                                  // Adjust water level 
      SetWater(row, col);
      for(int k = 1; k <= col; k++) {
        trellis.setLED(k+row*4);
      }
      for(int k = col+1; k <= 3; k++) {
        trellis.clrLED(k+row*4);
      }
    }
  }
}

// Helper function that starts watering the plant right now
void Execute() {
  // Close all valves
  for(int i = 0; i < 4; i++) {
    CloseValve(i);
  }

  // only water if frequency > 0 and total wateramount is > 0
  shouldWater = (waterFrequency > 0 && WaterAmountNonZero());
  waitingForNextCycle = false;
  
  if(shouldWater) {
    Serial.println("Start watering");
    TurnOnPump();
    // Initialize global variables 
    GetReadyForPlant(0);
  }
  else {
    TurnOffPump();
    OpenAllValves();
    Serial.println("Stop watering");
  }
}

void GetReadyForPlant(int id) {
  Serial.print("Get ready for plant "); Serial.println(id);
  microTimer = millis()/1000;
  quarterMicroTimer = GetWaterTimeForPlant(id);
  Serial.print("Water amount(seconds): "); Serial.println(quarterMicroTimer);
  wateringPlantId = id;
  OpenValveForPlant(id);
}

void WaitForNextCycle() {
  Serial.println("Wait for next cycle... ");
  TurnOffPump();
  OpenAllValves();
  shouldWater = false;
  waitingForNextCycle = true;
  macroTimer = millis()/1000;
  quarterMacroTimer = waterFrequency * CYCLELENGTH;
  Serial.print("Wait time(seconds): "); Serial.println(quarterMacroTimer);
}

int GetWaterTimeForPlant(int id) {
  return waterAmount[id] * WATERAMOUNTUNIT;
}

// Change the global watering frequency per minute
void SetFrequency(int frequency) {
  Serial.print("Set time: "); Serial.println(frequency);
  waterFrequency = frequency;
}

// Set the array of watering amount
void SetWater(int plantId, int water) {
  Serial.print("Set water: "); Serial.print(plantId); Serial.print(" "); Serial.println(water);
  waterAmount[plantId] = water;
}

// Only close one valve
void CloseValve(int valve) {
  // Don't close if is already closed
  if(!valveStates[valve]) return;
  else valveStates[valve] = false;
  Serial.print("Close "); Serial.println(valve);
  switch(valve) {
    case 0:
      valve0.write(VALVE_CLOSE_POS);
      break;
    case 1:
      valve1.write(VALVE_CLOSE_POS);
      break;
    case 2:
      valve2.write(VALVE_CLOSE_POS);
      break;
    case 3:
      valve3.write(VALVE_CLOSE_POS);
      break;
    default:
      break;
  }
  delay(50);
}

void OpenValve(int valve) {
  // Don't open if is already open
  if(valveStates[valve]) return;
  else valveStates[valve] = true;
  Serial.print("Open "); Serial.println(valve);
  switch(valve) {
    case 0:
      valve0.write(VALVE_OPEN_POS);
      break;
    case 1:
      valve1.write(VALVE_OPEN_POS);
      break;
    case 2:
      valve2.write(VALVE_OPEN_POS);
      break;
    case 3:
      valve3.write(VALVE_OPEN_POS);
      break;
    default:
      break;
  }
  delay(50);
}

// Helper function returns true if any plant water amount is > 0
bool WaterAmountNonZero() {
  for(int i = 0; i < 4; i++) {
    if(waterAmount[i] > 0) return true;
  }
  return false;
}

void CheckSwitches() {
//  return;
  if(!digitalRead(VALVE0_SWITCH_PIN)) {
    OpenValveForPlant(0);
  }
  if(!digitalRead(VALVE1_SWITCH_PIN)) {
    OpenValveForPlant(1);
  }
  if(!digitalRead(VALVE2_SWITCH_PIN)) {
    OpenValveForPlant(2);
  }
  if(!digitalRead(VALVE3_SWITCH_PIN)) {
    OpenValveForPlant(3);
  }
}

// Open valve for only plant id, close all other valves
void OpenValveForPlant(int id) {
  for(int i = 0; i < 4; i++) {
    if(i == id) OpenValve(i);
    else CloseValve(i);
  }
  Serial.print("Opening valve for only plant "); Serial.println(id);
}

void TurnOnPump() {
  digitalWrite(PUMP_PIN, HIGH);
  Serial.println("Pump on");
}

void TurnOffPump() {
  digitalWrite(PUMP_PIN, LOW);
  Serial.println("Pump off");
}

// Blink the LED
void BlinkLED(int i) {
  trellis.clrLED(i);
  trellis.writeDisplay();
  delay(blinkLEDdelay);
  trellis.setLED(i);
  trellis.writeDisplay();
  delay(blinkLEDdelay);
  trellis.clrLED(i);
  trellis.writeDisplay();
  delay(blinkLEDdelay);
  trellis.setLED(i);
  trellis.writeDisplay();
  delay(blinkLEDdelay);
  trellis.clrLED(i);
  trellis.writeDisplay();
  delay(blinkLEDdelay);
  trellis.setLED(i);
  trellis.writeDisplay();
  delay(blinkLEDdelay);
}

void OpenAllValves() {
  Serial.println("Open all");
  for(int i = 0; i < 4; i++){
    OpenValve(i);
  }
}

 

]]>
Get-Up Reminder for Thrombosis Patients https://courses.ideate.cmu.edu/60-223/f2019/work/get-up-reminder-for-thrombosis-patients/ Wed, 16 Oct 2019 15:14:40 +0000 https://courses.ideate.cmu.edu/60-223/f2019/work/?p=8296 A device that reminds the wearer to stand up and move around every hour.

Overview

Turning on the device.

Sitting to standing position wearing the device.

An overview of the device.

Close up shot of the device; a white LED in a triangular form.

 

Close up shot of the battery pack; designed to hold a 9 volt battery.

The device being worn above the knee.

Process

Decision Points:

One decision point that occurred fairly early on was the decision to use a rechargeable 9 volt battery not contained within the device itself. In the initial ideation stages, I had planned to use two tiny button batteries in series. I wanted everything to fit inside the main body of the device, which I later realized was quite difficult to achieve. I then made the decision to switch to a rechargeable 9 volt battery with an external battery pack to allot more space, and to avoid burning through too many batteries over extended wear.

An early battery pack prototype. The 3D print was uneven due to printing multiple parts together once.

Another decision point (also related to the size of the device) was the decision to remake the top half of the device, due to it not being able to fit the electronic components. The original design had the acrylic circle inset into the triangular form. However, the height was too short to fit the electronics, so it was remade so that the circle sat on top. Even though the extruded circle gives the impression of a circle, which is not the intended interaction, it was more important to fit the electronic components.

Original top half of the device with the cutout as an inset.

Process Highlights:

Initial sketchbook drawing and planning.

Soldering the tiny circuit.

Applying Bondo and sanding the 3D prints.

Piecing together the case and the electronics.

Discussion

Responses:

It’s a shame that there aren’t smaller batteries to go with it, or it could be even smaller.”

I addressed this for the most part in the decision points of the process section. I definitely agree that it would be more elegant if the design did not include a battery pack and if the device itself was smaller. I think the main benefit of having a battery pack is that the battery can easily be changed when it dies. Since the main device is sealed shut, I would have had to integrate some sort of access point to change the batteries had they been within the device itself; which would have been difficult due to the lack of space and the cluttered electronic components inside.

Clearly a lot of thought was put into figuring out how the components all fit together functionally and aesthetically.”

I think the plan since the beginning was to make the circuit as small as possible, make the case as spacious as possible (without being obtrusive), and smashing the two together. Soldering all the components to a tiny piece of protoboard was definitely straining. I did have to make an adjustment to the top half of the case when the acrylic indent got in the way. I also ran into some problems when the connections broke because I shoved the circuit too hard. Overall, I think I got lucky that everything fit how I imagined for the most part.

———-

Overall, I am satisfied with the way the project turned out. In the ideation stages, I felt like I underestimated the difficulty of the coding and electronics aspect. I ended up having to spend a lot of time on the two, especially in getting them to synchronize. I feel like I accomplished a lot in terms of the technical aspects of the project because I had very little prior experience with electronics and physical computing in general. I think the aesthetics of the project are below my usual standards, but I am happy with the end result given the time restrictions. With purely design projects, I am able to dedicate 100% of the time to aesthetics and UI, while I had to allocate time to technical aspects in realizing this project. Although I wish I had the time to polish up its outer appearance, I am happy about the opportunity to combine technical and design skills.

Through all this, I delved into some things that I never thought I would. First, I learned not to underestimate the size of electronic components. On the bright side, I was able to learn how to program an ATTINY85. Another thing I learned was how to problem solve. Uniquely in terms of technical problems, I was never aware of how many useful resources can be found online through a simple Google search. I was able to find solutions to many of my hardware and coding problems online. If I were to go through this experience again, I would definitely lower my expectations for how fast things progress. With the type of work I normally do in design, I never really get stuck past the ideation phase; so I found myself getting really frustrated when the code was not working the way I intended, or the circuit was not wired up correctly. I expected everything to go smoothly so this caused me a lot of anxiety. I wish I would have anticipated this and mentally prepared myself for all the mishaps. On the topic of future work, I would be interested in building another iteration of this device. I would like to explore higher quality materials, such as silicone or leather straps. Even though I have already dedicated a lot of effort to the aesthetic qualities of the project, I would be interested in reworking it to the point where it can be useful to me as a showpiece. I would also consider adding a programmable element for the sitting time.

Schematic

Code

/* Get-Up Reminder for Thrombosis Patients
 * 
 * Description:
 * The code below takes the input from an accelerometer 
 * to detect whether or not the wearer is sitting or 
 * standing, and vibrates a motor and blinks an LED in 
 * a predetermined pattern to remind them to stand up 
 * if they have been sitting for too long.
 * 
 * Inputs: 
 *  ATTINY85 pin | input
 *  1             switch
 *  A2            accelerometer (z)
 *  
 * Outputs:
 *  ATTINY85 pin | output
 *  0             vibration motor
 *  3             LED
 *  
 *  Credits:
 *  Programming the ATTINY85: sparkfun tutorial by JIMBLOM
 *  https://learn.sparkfun.com/tutorials/tiny-avr-programmer-hookup-guide/programming-in-arduino
 *  Programming outputs for pancake vibration motor: youtube video by Electronic Clinic
 *  https://www.youtube.com/watch?v=y-Fgm4yYsqg
 *  Debouncing switch inputs: course page tutorial
 *  https://courses.ideate.cmu.edu/60-223/f2019/tutorials/debouncing
 */

const int BUTTON_PIN = 1;
const int MOTOR_PIN = 0;
const int Z_PIN = A2;
const int LED_PIN = 3;

int buttonState; // the current reading from the button input pin
int lastButtonState = LOW; // the previous reading from the button input pin
bool powerState = false; // is the device on or off

unsigned long lastDebounceTime = 0; // the last time the output pin was toggled
unsigned long debounceDelay = 50; // the debounce time; increase if the output flickers

unsigned long startTime = millis(); // the time at which the user began sitting
unsigned long timeElapsed = 0; // the amount of time the user has been sitting

void setup() {
  pinMode(BUTTON_PIN, INPUT);
  pinMode(Z_PIN, INPUT);
  pinMode(MOTOR_PIN, OUTPUT);
  pinMode(LED_PIN, OUTPUT);
}

void loop() {
  int reading = digitalRead(BUTTON_PIN); // the state of the button switch
  int zRead = analogRead(Z_PIN); // the reading from the z input pin of the accelerometer

  if (reading != lastButtonState) { // if the switch changed during single press instance
    lastDebounceTime = millis(); // reset debouncing timer
  }

  // whatever the reading is at, it's been there for longer than the debounce delay, 
  // so take it as the actual current state:
  if ((millis() - lastDebounceTime) > debounceDelay) { 

    if (reading != buttonState) { // if the button state has changed
      buttonState = reading;
      if (buttonState == HIGH) { // only toggle the on/off state if the new button state is HIGH
        powerState = !powerState;
        startTime = millis(); // reset the time the user began sitting to the current time
      }
    }
  }

  if (powerState) { // if the device is "on":
    digitalWrite(LED_PIN, HIGH); // turn on the LED
    if (zRead > 350) { // if the accelerometer indicates that the user is sitting
      timeElapsed = millis() - startTime; // start counting the amount of time the user has been sitting
    } else { // if the accelerometer indicates that the user is standing
      startTime = millis(); // reset the time the user began sitting to the current time
    }
    if (timeElapsed > 3600000) { // if the user has been sitting for more than 1 hour
    // if (timeElapsed > 10000) { // demo case: the user has been sitting for 10 seconds
      // vibrate and blink LED 3 times:
      analogWrite(MOTOR_PIN, 255);
      delay(500);
      analogWrite(MOTOR_PIN, 0);
      delay(500);
      analogWrite(MOTOR_PIN, 255);
      delay(500);
      analogWrite(MOTOR_PIN, 0);
      delay(500);
      analogWrite(MOTOR_PIN, 255);
      delay(500);
      analogWrite(MOTOR_PIN, 0);
      delay(500);
      startTime = millis(); // reset the time the user began sitting to the current time
    }
  } else { // if the device is "off"
    digitalWrite(LED_PIN, LOW); // turn the LED off
  }
  lastButtonState = reading; // save the reading as lastButtonState for the next loop
}

 

]]>
Water pilly https://courses.ideate.cmu.edu/60-223/f2019/work/water-pilly/ Tue, 15 Oct 2019 16:13:45 +0000 https://courses.ideate.cmu.edu/60-223/f2019/work/?p=8399 Water pilly is an assistive outdoor ornament for people suffering from allergic asthma caused by environmental fungi ‘Altenaria’. 

It constitutes a mechanical flower, opening and closing in response to the degree of humidity in the atmosphere, notifying the patients when they have to  take antihistamine and how much.

The project applied for me a creative platform to experiment with new things such as organic design, construction and engineering concepts for organic design, 3d printing, improvising with new electronics and libraries, as also combining them with components we had already learned in class such as ‘maintained’ switches, potentiometers as threshold, and LCD screens.

_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-

petals_construction details_digital model

petals_construction details_physical model

stepper motor_axis rotation_back and forth

axis_bidirectional rotation_strings wrapping_ pull & push petals

1) humidity sensor__humidity & temperature index, 2) ‘maintained switch’__on/off according to being/not being sick__ pill increase/decrease, 3) analog potentiometer__patient’s tolerance to humidity__increases/decreases according to sickness mode, 4) LED on/off__open/close, 5) A4988 chip__stepper motor

 humidity>tolerance_petals open_1 pill

 humidity>tolerance_sickness mode is on_petals open_2 pill (double dose)

fast speed_emergent distortion_petals overlap

total view

_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-

 

Soon I discovered that the most valuable lessons for me were the followings:

  • The exploration and learning process of new components (humidity sensor, stepper motor). I had to figure out how each of them functions, gets wired and which library is the most appropriate for the current use.
  • Another point of great importance, was learning how to deconstruct a difficult concept (flower’s operation) into simpler steps (read of humidity, read of switch, read of potentiometer, write on LCD screen etc.). This strategy was really helpful and allows to compose any concept out of the co-ordination among smaller units.
  • However, I found quite challenging to make all the different components co-ordinate with each other in order for the flower to open and close according to the original concept. After accomplishing that,
  • I also realized that the uneven length of the strings wrapping around, made it difficult to absolutely estimate how many turns the motor needed to rotate in order for the petals to close and reopen. Therefore, I experienced a difficulty in debugging the petal’s opening and closing activity.
  • Eventually, the project offered me the opportunity to learn how to examine emerged ‘bugs’ caused either by wrong wiring connections, ‘cooked’ components or ‘heavy workloads’. I also experienced when I have to integrate an unpredictable, emergent behavior into my concept. For instance, LCD screen seriously delayed the motor’s velocity. However, without this delay and due to the uncontrollable wrapping of the strings, the flower’s form was feasible to get distorted, even break. This ‘wrong behavior’ of the motor, offered a safety degree to the whole construction.
  • I found other pupils’ critic a very good method to understand how I could improve my device. I cite here the following examples:
    • The physical design of the pill mechanism is gorgeous, the other electronics a bit less so, it might be better to incorporate all the electronics somewhere into your case.The opening is a bit slow, but I really like watching it anyways.”

 I really liked the idea to perceive the electronics as an integral part of the flower’s physical design. That was a good motivation for me to start soldering the components on thin boards and attach them onto the parts of the final construction. I separated the components into two categories, those that should be directly exposed because they cause physical interaction (humidity sensor, switch) and those which needed more protection (Arduino, LED, LCD etc.). On these grounds, I placed them either on the upper levels of the construction or deeper in that.

    • The flower design is very appealing and original. Its function could be tweaked to be more reliable, and the device is rather big, but the idea is great.

I totally agree that the mechanism needs some slight modifications in order to become more reliable. I should reconsider the starting position (close or open) of the flower, the degree and the velocity of the rotation, as well as the length of the strings and their even wrapping. However, I disagree with the part about the device’s size. For the petals to be 3d printed properly, without breaking and with preserving harmonic proportions between length and thickness, I firmly believe that this is a sufficient size.

_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-

 Self critic:

During this project, I realized 1) my affinity for exploring new components, figuring out how to make them work and interact with each other, exchanging values. 2) I also experienced a confusion whenever a ‘bug’ appeared. That made me consider to create a better strategy for ‘debugging’. I should create a list of checks for inspecting bugs and excluding potential reasons for errors.

In general lines, I am quite satisfied with the project’s outcome. However, next time I will spend more time in organizing a detailed engineering strategy, to decrease the amount of possible ‘motion errors’ arising in kinetic devices like this. Furthermore, I will experiment more with the mechanical motion in situ in order to calibrate it better and adjust it to the given circumstances. Finally, in my next projects I will try to integrate the electronics into the physical model from the early beginning, either fully designating them or hiding them.

Next steps:

In the future, I would like to iterate the same concept, but instead of creating mechanical motion for opening and closing the flower’s petals, I will produce shape transformation using morphing materials. On the grounds, humidity will be turned into energy (heat) and energy into physical transformation (motion).  Finally, this time the design would constitute an indoor ornament, that wirelessly receives the humidity and temperature information from the outdoor environment.

 

 

 

 

 

 

 

 

 

_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-

 

schematic diagram_circuits

/*
  Project title: Water pilly
  Intro to Physical Computing @ Carnegie Mellon University

  Made by Maria Vlachostergiou

  Inputs:
  Arduino pin | Input
     2         Humidity sensor (DHT22)
     A0        Potentiometer
     13        Switch

  Outputs:
  Arduino pin | Output
     7         LED
     4         Motor step
     3         Motor direction

  Step1: We measure humidity degree, the state of potentiometer and switch.
  Step 2: According to four different conditions, the stepper motor rotates in two different directions, back and forth.
  Step 3: The LCD screen shows how many pills the patient has to take.
*/

// humidity sensor
#include "DHT.h"
#define DHTPIN 2
#define DHTTYPE DHT22
DHT dht(DHTPIN, DHTTYPE);
float humidity;
float temperature;
float fahrenheit;

// Stepper motor
#include <AccelStepper.h>
#define STEP_PIN 4
#define DIRECTION_PIN 3
AccelStepper flowerActivity(1, STEP_PIN, DIRECTION_PIN); // it conducts 200 steps per revolution
float turns = 1.1;
int initialPos = 200 * turns; // the motor starts at 0

// maintained button for illness
#define SWITCH_PIN 13
int switchMode;
int isSick = false;
int amountOfPills = 0;

// potentiometer that evaluates the tolerance toward humidity
/* when being sick, a lower degree of humidity might cause
  // more regular and intenser asthma episodes*/
#define POT_PIN A0
int tolerance;

// LED, it turns on when himidity is higher than patient's tolerance
#define LED_PIN 7

// I²C LCD, prints out the amount of pills the patient has to take
#include <Wire.h>
#include <LiquidCrystal_I2C.h>
LiquidCrystal_I2C myLCD(0x27, 20, 4);

/*********************************************************************************/

void setup() {
  // pinModes
  pinMode(SWITCH_PIN, INPUT);
  pinMode(POT_PIN, INPUT);
  pinMode(LED_PIN, OUTPUT);

  // initialize humidity sensor
  dht.begin();

  // Serial.begin(9600);

  // initialize stepper motor
  flowerActivity.setMaxSpeed(400);
  flowerActivity.setAcceleration(50);
  flowerActivity.moveTo(initialPos);

  //initialize LCD
  myLCD.init();

} // void setup

/********************************************************************************/

void loop() {
  // delay the reading of sensors
  //delay(500);

  // motor starts
  flowerActivity.run();

  // Read potentiometer and fix a tolerance
  int t = analogRead(POT_PIN);
  tolerance = map(t, 0, 1023, 0, 100);

  // Read humidity sensor, compute heatindex and serially print the necessary data
  humidity = dht.readHumidity();
  temperature = dht.readTemperature();
  fahrenheit = dht.readTemperature(true);
  // in case that the humidity sensor fails to read
  if (isnan(humidity) || isnan(temperature) || isnan(fahrenheit)) {
    Serial.println("Sorry, something is wrong! Failed to read from sensor!");
    return;
  }
  float hif = dht.computeHeatIndex(fahrenheit, humidity);
  float hic = dht.computeHeatIndex(temperature, humidity, false);

  Serial.print("Humidity is: ");
  Serial.print(humidity);
  Serial.print(" %,\t");
  Serial.print("Temperature is: ");
  Serial.print(temperature);
  Serial.print(" *C\t");
  //Serial.print(fahrenheit);
  //Serial.print(" *F\t");
  //Serial.print("Heat index: ");
  //Serial.print(hic);
  //Serial.print(" *C or ");
  //Serial.print(hif);
  //Serial.print(" *F,\t");

  // Read the switch
  switchMode = digitalRead(SWITCH_PIN);
  if (switchMode == 1) {
    isSick = true;
  } else {
    isSick = false;
  }

  // Compute and print the amount of Pills, open and close the flower, adjust the LCD
  Serial.print("Tolerance to humidity: ");
  Serial.print(tolerance);

  myLCD.home();
  myLCD.print(humidity);
  myLCD.setCursor(4, 0);
  myLCD.print("% Humidity!");
  myLCD.setCursor(0, 1);
  myLCD.print(tolerance);
  myLCD.setCursor(2, 1);
  myLCD.print("%: Your tolerance");

  if ((humidity < tolerance) && (isSick == false)) // condition 1
  {
    flowerActivity.moveTo(initialPos);
    amountOfPills = 0;
    digitalWrite(LED_PIN, LOW);

    myLCD.noBacklight();
    myLCD.setCursor(0, 3);
    myLCD.print("Not being sick");
  } // condition 1

  else if ((humidity < tolerance) && (isSick == true)) // condition 2
  {
    flowerActivity.moveTo(0);
    amountOfPills = 1;
    Serial.println("\tYou are sick. Take 1 pill!");
    digitalWrite(LED_PIN, LOW);

    myLCD.backlight();
    myLCD.setCursor(0, 2);
    myLCD.print("You are sick");
    myLCD.setCursor(0, 3);
    myLCD.print("Take 1 pill!");
  } // condition 2

  else if ((humidity >= tolerance) && (isSick == false)) // condition 3
  {
    flowerActivity.moveTo(0);
    amountOfPills = 1;
    Serial.println("\tHumidity is over your tolerance. Take 1 pill!");
    digitalWrite(LED_PIN, HIGH);

    myLCD.backlight();
    myLCD.setCursor(0, 2);
    myLCD.print("High humidity");
    myLCD.setCursor(0, 3);
    myLCD.print("Take 1 pill!");
  } // condition 3

  else if ((humidity >= tolerance) && (isSick == true)) // condition 4
  {
    flowerActivity.moveTo(0);
    amountOfPills = 2;
    Serial.println("\tBoth sick & Humidity over tolerance. Take 2 pills!");
    digitalWrite(LED_PIN, HIGH);

    myLCD.backlight();
    myLCD.setCursor(0, 2);
    myLCD.print("High humidity & sick");
    myLCD.setCursor(0, 3);
    myLCD.print("Take 2 pill!");
  } // condition 4

  if (flowerActivity.currentPosition() == initialPos)
  {
    Serial.println("\tFlower closed");
  } else {
    Serial.println("\tFlower opened");
  }

} // void loop

/********************************************************************************/

 

 

]]>