Project 2 – Intro to Physical Computing: Student Work Spring 2020 https://courses.ideate.cmu.edu/60-223/s2020/work Intro to Physical Computing: Student Work Mon, 11 May 2020 22:31:21 +0000 en-US hourly 1 https://wordpress.org/?v=5.3.17 Tactile Navigation Wearable https://courses.ideate.cmu.edu/60-223/s2020/work/tactile-navigation-wearable/ Mon, 11 May 2020 20:58:08 +0000 https://courses.ideate.cmu.edu/60-223/s2020/work/?p=11289 Note: I wanted to make the connections permanent and replace small breadboard with a pro board if I had continued access to the Physical computing lab. Additionally, I also wanted to make an enclosing that housed the various components.

Overview

This is a prototype of a wearable device that assists in navigation by providing tactile directional instructions to the user to assist them in wayfinding while navigating unfamiliar routes in a simple outdoor environment.

Motivation

A few years ago I was driving to my new workplace for the first time and I was using a maps application on my phone to guide me in my navigation. Midway, I was confused about which turn to take and I glanced over at my phone as I was trying to figure out what turn to take while I was driving. It was then that I was pulled over by the traffic police for using my phone while driving. That’s when I thought to myself, how am I supposed to use a navigation system, that mainly relies on visual directional instructions when I’m driving if I’m supposed to keep my eyes on the road at all times?

Final Images

An overview of the tactile wearable showing the various parts.

Coin vibration motors mounted velcro adjustable rings that go around the fingers.

The tactile wearable mounted on the dominant hand of the user.

Final video

Note: Since I was unable to record the final video myself, I used the video that Harshine shot during my demo in class. (Thanks Harshine!)

Process images and review

I initially created a storyboard to draw out scenarios of use for the tactile wearable. The storyboard helped me figure out an appropriate form factor for the device as well.

Initial storyboard illustrating the use case for the device.

Throughout the process of building this device, I got to learn how to use many sensors and components that I had never used before so it was a great opportunity for me to expand my skills in physical computing.

One such component was the GPS module. Oh the GPS module… Since this was a fairly complex component to use, I had to use a pre-written code to read the signal from the GPS module. The GPS module that I was using was a Geekstory GT-U7 GPS Module GPS that had a GPS chip and an external antenna. The GPS also had a position fix LED indicator light that indicated that the GPS was searching for satellites when it was not blinking and that the GPS had fixed on a satellite by blinking every second. I downloaded the pre-written code and connected up the GPS module and the Arduino. And then there were a few problems I ran into.

The GPS was not finding a position fix

I was excited when I first connected up all the components and expected it to work right away. However, the GPS was not fixing on a satellite although I had left it connected for several minutes. I then realized that it was almost impossible to get a GPS reading sitting in the Phys Comp lab (no surprises there). So, I went outside and stood under the clear sky for several minutes, walked around even, and then the little LED started blinking!

Getting the GPS to find a position fix by taking it outside under clear skies.

Marking the GPS coordinates on a simple outdoor to create a working demo.

And then there was a delay!

After I got the GPS to find a position fix, the GPS started transmitting signals that were a bunch of latitude and longitude coordinates. However, when I would move around, the GPS wasn’t able to update the coordinates. There seemed to be a lag in the updating of the location. I tried many things to fix this, I rechecked if all the connections were good, I ensured that I was away from buildings, I restarted my Arduino, I even held up the antenna of the GPS over my head. I even checked to make sure that I had uploaded the pre-written code exactly as it was, line by line and that was the problem! I consulted with Zac about this and upon reading the code line by line, he found a pesky little delay of 1 second right at the end of the code. The delay was originally meant to set the rate at which the GPS prints the latitude and longitude coordinates however, it was also messing up the signal that the GPS was transmitting. The GPS was picking up signals from satellites at a faster rate than it was able to transmit them which caused the signals to get jumbled up. We fixed this problem by using millis instead. Lesson learned: millis for life.

The pesky delay at the end that was the root of all problems!

Reading raw data from the GPS.

The super-sensitive vibration motors

To create vibrotactile feedback in the wearable, I was using a small coin vibrational motor that had two very thin and fragile connecting leads onto which longer connecting wires had to be soldered. However, since the connection was so delicate, I was afraid of breaking the connections which made testing the prototype a huge challenge. I consulted with Zac and asked him how I could make the prototype more robust. Zac was extremely helpful and suggested I used heat shrinking material to reinforce the joints with the connections. He drew a wonderful diagram to illustrate the process which was helped me reinforce the connections and make the prototype durable.

A diagram illustrating the process of reinforcing the vibration motor with different diameters of heat shrink tubing, courtesy Zac.

The final outcome of the reinforcing the vibration motor that made the prototype suitable to test multiple times without the fear of the connections breaking.

Discussion

To present our projects, we had an in-class demo that went differently than I imagined. I had prepared to demo the device outdoors since that was the best way I could demo the device and show that it actually worked in the way I had described it would. The demo, however, was only conducted indoors which meant I had to hard code the coordinates and fake the feedback from the wearable, which I was able to successfully do. One comment I got was the “the demo could have involved a visual aspect to show what was going on in the device (to the audience)”. I agree that LED lights would have probably helped to better indicate which vibrational motors were active. Since I was prepared for a live demo, in which a volunteer would be guided from a preset origin to a destination along a simple route outdoors, I was unable to add a visual component to the demo. I intended to add LED lights to the vibrational rings for demo purposes post-spring break which I, unfortunately, couldn’t do.

Another comment I received was “It would’ve been nice to have an enclosing for the device.” I agree with this feedback, however, the challenge was that the connections hadn’t been reinforced yet so I was afraid to make anything permanent since I only had a limited number of small coin vibrational motors available. Another challenge was that the GPS unit had to have a view of the clear sky, so enclosing it in a bix might have interfered with its ability to find a position fix.

Overall, I learned so much through this project. Not only did I gain technical skills but this project also made me persevere and build the thing I intended to build no matter the technical difficulties I ran into. Zac was instrumental in encouraging me along the way and creating a project that I was in the end proud of. There were times in the project when I wanted to give up and switch to a simpler technique, but Zac’s timely help pushed me towards the finish line.

I truly enjoyed figuring out the code for this project since it involved a fair bit of complex logic. I had taken a Python course in one of the previous semesters and my knowledge of coding came in very handy for this project. The next time I will be more vigilant when I use pre-written code because it may not always be correct. I was also surprised that I found soldering extremely therapeutic. From this project, I was able to learn how to make well-soldered connections.

If I have access to resources, I would want to work on the form and the ergonomics of the prototype. I would create a well-designed enclosing to house all the components, I would create more aesthetically pleasing rings that can easily be mounted on the hand of the user. I would also want to test out the prototype with participants in the context of outdoor navigation and evaluate the prototype.

Technical information

Schematic

Schematic for the tactile navigation wearable

Code

/*
   Project 2: Tactile Navigation Wearable
   By Aadya Krishnaprasad

   Description: This is the code for a prototype of a wearable device that assists in navigation by providing tactile directional instructions to the user to assist them in wayfinding while navigating unfamiliar routes in a simple outdoor environment.

   Pin Mapping:

   pin      | mode      | description
   ---------|-----------|-------------
   3          Output      GPS module tx
   4          Input       GPS module rx
   5          Output      Vibration motor (RING1)
   6          Output      Vibration motor (RING2)
   9          Output      Vibration motor (RING3)
   10         Output      Vibration motor (RING4)

*/


// Compass SETUP
#include <QMC5883LCompass.h>
QMC5883LCompass compass;

//Citation: GPS code from: https://create.arduino.cc/projecthub/ruchir1674/how-to-interface-gps-module-neo-6m-with-arduino-8f90ad
// GPS SETUP
#include <SoftwareSerial.h>
#include <TinyGPS.h>
//long   lat,lon; // create variable for latitude and longitude object

float lat = 28.5458, lon = 77.1703; // create variable for latitude and longitude object
//float lat = 40.4412, lon = -79.9435; // Location: Origin, Head West
//float lat = 40.4414, lon = -79.9440; // Location: Waypoint 1, Head North

SoftwareSerial gpsSerial(3, 4); //tx,rx

TinyGPS gps; // create gps object

// RING PIN ASSIGNMENT
const int RING1 = 5;
const int RING2 = 6;
const int RING3 = 7;
const int RING4 = 9;


void setup() {
  Serial.begin(9600);
  compass.init();
  gpsSerial.begin(9600); // connect gps sensor

  pinMode(RING1, OUTPUT);
  pinMode(RING2, OUTPUT);
  pinMode(RING3, OUTPUT);
  pinMode(RING4, OUTPUT);

}


void loop() {

  int a;

  int WAIT = 250;
  long timer = 0;
  if ( (millis() - timer) >= WAIT ) { // millis() is always the number of milliseconds since the Arduino powered up
    // Read compass values ; Compass 0 is East
    compass.read();

    // Return Azimuth reading
    a = compass.getAzimuth();

    Serial.print("A: ");
    Serial.print(a);
    Serial.println();

    timer = millis(); // reset the timer
  }

  // Code to get latitude and longitude coordinates from the GPS
  while (gpsSerial.available()) { // check for gps data
    if (gps.encode(gpsSerial.read())) // encode gps data
    {

      gps.f_get_position(&lat, &lon); // get latitude and longitude

      Serial.print("Position: ");
      Serial.print("Latitude:");
      Serial.print(lat, 6);
      Serial.print(";");
      Serial.print("Longitude:");
      Serial.println(lon, 6);
    }
  }

  String latitude = String(lat, 4);
  String longitude = String(lon, 4);
  Serial.println(latitude + ";" + longitude);



  if ((((40.4411) <= (lat)) && ((lat) <= (40.4415))) && (((-79.9434) >= (lon)) && ((lon) >= (-79.9438)))) {
    Serial.println("Location: Origin, Head West");

    if ((a >= 134) && (a <= 224)) { // West(180)
      goStraight(125);

    } else if ((a >= 316) || (a <= 45)) { // East(0)
      turnAround(250);

    } else if ((a >= 225) && (a <= 315)) { // South(270)
      turnRight100(125);

    } else if ((a >= 46) && (a <= 135)) { // North(90)
      turnLeft100(125);

    }
  }


  if ((((40.4413) <= (lat)) && ((lat) <= (40.4414))) && (((-79.9439) >= (lon)) && ((lon) >= (-79.9441)))) {
    Serial.println("Location: Waypoint 1, Head North");
    // Location: Waypoint 1, Head North

    if ((a >= 46) && (a <= 135)) { // North(90)
      goStraight(125);

    } else if ((a >= 134) && (a <= 224)) { // West(180)
      turnRight100(125);

    } else if ((a >= 225) && (a <= 315)) { // South(270)
      turnAround(250);

    } else if ((a >= 316) || (a <= 45)) { // East(0)
      turnLeft100(125);
    }
  }

  if ((((40.4415) <= (lat)) && ((lat) <= (40.4416))) && (((-79.9439) >= (lon)) && ((lon) >= (-79.9441)))) {
    Serial.println("Location: Waypoint 2, Head West");
    // Location: Waypoint 2, Head West

    if ((a >= 134) && (a <= 224)) { // West(180)
      goStraight(125);

    } else if ((a >= 316) || (a <= 45)) { // East(0)
      turnAround(250);

    } else if ((a >= 225) && (a <= 315)) { // South(270)
      turnRight100(125);

    } else if ((a >= 46) && (a <= 135)) { // North(90)
      turnLeft100(125);

    }
  }

  if ((((40.4416) <= (lat)) && ((lat) <= (40.4417))) && (((-79.9445) >= (lon)) && ((lon) >= (-79.9446)))) {
    Serial.println("Location: Waypoint 3, Head North");
    // Location: Waypoint 3, Head North

    if ((a >= 46) && (a <= 135)) { // North(90)
      goStraight(125);

    } else if ((a >= 134) && (a <= 224)) { // West(180)
      turnRight100(125);

    } else if ((a >= 225) && (a <= 315)) { // South(270)
      turnAround(250);

    } else if ((a >= 316) || (a <= 45)) { // East(0)
      turnLeft100(125);
    }
  }

  if ((((40.4419) <= (lat)) && ((lat) <= (40.4422))) && (((-79.9443) >= (lon)) && ((lon) >= (-79.9445)))) {
    // Location: Destination, Stop
    destinationReached(250);
  }
}

////////////////PATTERNS

// Tactile pattern: If left turn is less than 50 feet away, then vibration motor vibrates at maximum intensity
void turnLeft50(int intensity) {
  analogWrite(RING1, intensity);

}

// Tactile pattern: If left turn is between 50 and 100 feet away, then vibration motor vibrates at medium intensity
void turnLeft100(int intensity) {
  analogWrite(RING2, 0);
  analogWrite(RING3, 0);
  analogWrite(RING4, 0);
  analogWrite(RING1, intensity);

}

void goStraight(int intensity ) {
  analogWrite(RING1, 0);
  analogWrite(RING4, 0);
  analogWrite(RING2, intensity);
  analogWrite(RING3, intensity);
}
 // Tactile pattern: If right turn is less than 50 feet away, then vibration motor vibrates at maximum intensity
void turnRight50(int intensity ) {
  analogWrite(RING2, 0);
  analogWrite(RING3, 0);
  analogWrite(RING1, 0);
  analogWrite(RING4, intensity);

}

// Tactile pattern: If right turn is between 50 and 100 feet away, then vibration motor vibrates at medium intensity
void turnRight100(int intensity ) {
  analogWrite(RING2, 0);
  analogWrite(RING3, 0);
  analogWrite(RING1, 0);
  analogWrite(RING4, intensity);
}

void turnAround(int intensity ) {
  analogWrite(RING1, intensity);
  analogWrite(RING2, intensity);
  analogWrite(RING3, intensity);
  analogWrite(RING4, intensity);
}

void destinationReached(int intensity) {
  analogWrite(RING1, intensity);
  delay(70);
  analogWrite(RING1, 0);
  analogWrite(RING2, intensity);
  delay(70);
  analogWrite(RING2, 0);
  analogWrite(RING3, intensity);
  delay(70);
  analogWrite(RING3, 0);
  analogWrite(RING4, intensity);
  delay(70);
  analogWrite(RING4, 0);
}

 

]]>
3D Printer Filament Sensors https://courses.ideate.cmu.edu/60-223/s2020/work/3d-printer-filament-sensors/ Tue, 21 Apr 2020 16:58:05 +0000 https://courses.ideate.cmu.edu/60-223/s2020/work/?p=10838 This is a set of sensors to notify the IDeATe Tech Advisors when an Ultimaker 3 printer is low on one or both of its filaments.

Ultimately, it will reside in the 3D printing room in the back of Hunt A5.

Process

The first point at which I had to make a major decision was in figuring out whether or not I would incorporate sensors for both filaments. For context, the Ultimaker 3 can use 2 filaments when printing. The first is often the build material, which is typically either:

  • PLA–PolyLactic Acid, which is basically corn or sugar plastic and is biodegradable.
  • ABS–Acrylonitrile Butadiene Styrene, which is commonly used in manufacturing.

The second is often a support material, which is typically either:

  • PVA–PolyVinyl Alcohol, which is water soluble plastic.
  • Breakaway, which is a plastic meant to be easily removed from the finished part.

Ideally, I would want to include sensors with both, but it turns out that PVA is transparent to infrared. Since the break-beam sensors I planned to use operated with infrared LEDs, they would work fine with the build materials, but not with PVA. They wouldn’t see the strand of PVA. At this point, I either would have needed to make a custom sensor that uses a regular LED and a photoresistor, or order a break-beam sensor that uses visible light. When I am able to return to this project, chances are high that I will just order some sensors that use visible light.

When the build material is low (not triggering the break-beam sensor), the screen will same something like this.

The same goes for the support material.

 

 

 

 

 

 

 

 

 

Here’s what is displayed when all filaments are OK. At this point, I was triggering the sensors with a screwdriver.

 

 

 

 

 

 

 

 

 

Another decision I had to make came as the deadline for the critique was fast approaching. I could either wire up one set of sensors and hopefully have that fully functioning, or try to wire all 4 sets up. This was ultimately my downfall. I never truly made a decision one way or the other, so the result was a severely underdeveloped device that barely worked. The 1 sensor that was wired up didn’t work at the time of critique due to faulty soldering on my part. Then again, this is what happens when you rush.

Discussion

I was relieved that those critiquing my underdeveloped device at least gave me constructive feedback and pointed out the good things about the project. Quite obviously, however, it was a far cry from faultless. Given the state the device was in, I can see why one critic said this:

“Would it have been easier and prone to less error if you made 4 seperate detectors? Then if something goes wrong, you would only need to fix 1 of the printers instead of putting all four sensors off-line.”

While I do agree that it would likely be easier to have 4 separate detectors for the sake of serviceability, it is a little bit wasteful when all 4 detectors could be driven off of 1 Arduino Uno R3 (or a Photon, more on that later).

Another critic said this:

“The project sounds very sophisticated and pretty useful for your job. However, how can you make it be more straightforward for the next users to use that have your job? Make it easier to understand?”

I think the reason it is so difficult to understand is because of how unfinished it is. Once the device is actually finished (hopefully this will be shortly after the COVID-19 pandemic dies down), it should make a lot more sense.

With all of that said, I am quite unhappy with how the project turned out. However, the issues that it has almost entirely trace back to me. In particular, my indecision was my biggest problem, and it led me to waste a lot of time. I think it goes without saying that I did not satisfy my goals for the project.

One truly bone-headed decision I made was using individual wires for each of the pins, rather than being smart and following RZach’s suggestion of using multi-core wire. It would have been the same level of difficulty in terms of soldering, but the external wiring would have been much cleaner. With that in mind, when I am finally able to resume work on this device, the sensors will all use multi-core wire where applicable. Everything will also be wired into a protoboard, rather than a breadboard, so as to make the device more permanent.

One final thing to note when continuing this project is that it will likely be ported to work with a Particle Photon (suggested by Cody Soska and RZach). This will enable me to use IFTTT (if-this-then-that) more easily so that I can have the device send a Slack message to IDeATe staff (Tech Advisors in particular) saying which printer is low on filament and which filament it’s low on. It’s possible to do this with an Arduino, but much easier with a Photon.

Technical Information

code

NOTE: The code you are about to see is very unfinished. That said, it does compile, and it properly reads the sensors (when they’re wired correctly). Once I am able to wire the device up correctly, I will update this post with revised code, both for the final Arduino version, as well as the Photon version. Regardless, here’s the code as it was during critique:

/*
   Project 2
   Seth Geiser (sgeiser)

   Collaboration: None

   Challenge: The biggest challenge was getting the sensor wired up
   correctly. I did cook one. :/

   Next time: I don't know... keep going?

   Description: The purpose is to create a set of sensors that can
   detect when a 3D printer is running out of filament. Two break-
   beam sensors are used for this purpose -- one for the primary
   (build) material and another for the secondary (support)
   material. When material is blocking the sensor, the sensor will
   read false, meaning that it is not yet critically low. When
   material is no longer blocking the sensor, the sensor will read
   true, meaning that the material is critically low and should be
   replaced with a new reel. At the very least, the device will
   sound an alarm (not implemented) and display which printer is
   low on filament and which filament it is low on (on the I2C
   LCD). The final version will do this and send a Slack message
   using IFTTT to IDeATe Tech Advisors.

   Pin mapping:

   pin  | mode  | description
   -----|-------|------------
   2      input   break-beam sensor 1 (build material)
   3      input   break-beam sensor 2 (support material)
   SDA/SCL        display

*/

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

/*
   Create an LCD display object called "screen" with I2C address 0x27
   which is 20 columns wide and 4 rows tall.
*/

LiquidCrystal_I2C screen(0x27, 20, 4);

const int BUILDSENSOR = 2;
const int SUPPORTSENSOR = 3;

unsigned long timer = 0; // variable for timing
const int INTERVAL = 1000; // milliseconds between updates


void setup() {
  pinMode(BUILDSENSOR, INPUT);
  pinMode(SUPPORTSENSOR, INPUT);

  screen.init();
  screen.backlight();
  screen.home();
  Serial.begin(9600);
}

void loop() {
  bool buildCritical = digitalRead(2);
  bool supportCritical = false;
  /*
     would be digitalRead(3), but sensor was not wired up in time for
     critique, so I left it at false to eliminate any chances of an
     ambiguous reading.
  */
  // updating the LCD output
  if (millis() >= timer) {
    if (!buildCritical) {
      screen.setCursor(0, 0);
      screen.print((String) "All filaments OK.");
      screen.setCursor(0, 1);
      screen.print((String) "Support sensor");
      screen.setCursor(0, 2);
      screen.print((String) "is missing!");
    }
    else if (buildCritical) {
      screen.setCursor(0, 0);
      screen.print((String) "Build material  ");
      screen.setCursor(0, 1);
      screen.print((String) "is low!");
    }
    else if (supportCritical) {
      screen.setCursor(0, 0);
      screen.print((String) "Support material");
      screen.setCursor(0, 1);
      screen.print((String) "is low!");
    }
    timer = millis() + INTERVAL; // and update timer
  }

}
schematic

Unlike the code, the schematic for this device shouldn’t change too much, even when transitioning from an Arduino Uno R3 to a Particle Photon — some pin numbers may change, but that’s about it. This was made using Fritzing.

All four sets of sensors are wired up identically.

]]>
Wearable Soundtrack https://courses.ideate.cmu.edu/60-223/s2020/work/wearable-soundtrack/ Tue, 21 Apr 2020 00:52:58 +0000 https://courses.ideate.cmu.edu/60-223/s2020/work/?p=10294 Wearable Soundtrack

The Wearable Soundtrack is a wristband that uses a knob and speaker to play select songs as an “epic soundtrack” to the wearer’s life.

Process

One decision point in my process to change the device from an attachable sleeve to a wristband. This decision was made because it was more practical in terms of using the device and making the device with the amount of fabric I had. Another decision point in my process was to use a large 9V battery instead of the two coin cell batteries I originally planned on using. The coin cell batteries were inconsistent in their ability to power the device, and while the size of coin cell’s are preferable for the design of the device, I was forced to use a 9V battery.

Process Images

Testing out the electronic components of the device.

Creating the laser cut file for the fabric in Rhino 6

Placing components in fabric and sewing

Discussion

Making this Wearable Soundtrack was incredibly fun and rewarding for me because it allowed me to explore creating a whimsical device that reflected my love of music and its importance in my daily life. However, I was not as impressed with the result of my efforts. I received a lot of feedback about the aesthetics of the device.  For example, one of my classmates noted that “[they] think it is a fun idea, but aesthetically it could look a lot better. Maybe like a watch?” I agree with this comment, the aesthetics on my device were not up to the standard that I am usually capable of because of rushing on my part to finish the device while balancing a heavy workload in my other classes. Also, there were some unforeseen complications with the power that forced me to use a larger battery than I accounted for, and in my attempts to accommodate this component, I created a messy looking device.  However, some of the feedback from my classmates was not as relevant or helpful. One classmate asked me to consider “How could you make the lights that is glowing from your components more intentional? I thought you put LED lights in there, but it was just light from being turned on.” While I understand their comment, the component in question with the LED was essential to my design, and I could not replace it with a component without an LED; furthermore, I found the LED helpful as an indication whether or not the device was working after I added the fabric.  I do not plan on building another iteration of this device; however, if I were to do so, I would use a synthetic fabric that would laser cut better, take more time sewing the device, creating mounts for each component, and buying a smaller battery with the appropriate voltage.

Technical Information

Schematic

Code

/*Wearable Soundtrack
  Anishwar Tirupathur

  Description:  This code sets up the DFPlayer Mini component and maps a potentiometer
  so that it can be used to select the songs the DFPlayer Mini plays.

  pin/  mode/ description
  -----------------------
  A5   INPUT  Potentiometer
  10   RX     DFPlayer Mini
  11   TX     DFPlayer Mini

  Collaboration:  I used code from the DFRobotDFPLayerMini Library, specifically the
  "FullFunction" and "GetStarted" libraries. In addition, I recieved help from Seth
  when using the IDeAte lasercutters.
*/
#include "Arduino.h"
#include "SoftwareSerial.h"
#include "DFRobotDFPlayerMini.h"


SoftwareSerial mySoftwareSerial(10, 11); // RX, TX
DFRobotDFPlayerMini myDFPlayer;
void printDetail(uint8_t type, int value);

const int POTENTPIN = A5;

int prevState;


void setup()
{

  pinMode(POTENTPIN, INPUT);




  mySoftwareSerial.begin(9600);
  Serial.begin(115200);

  Serial.println();
  Serial.println(F("DFRobot DFPlayer Mini Demo"));
  Serial.println(F("Initializing DFPlayer ... (May take 3~5 seconds)"));

  if (!myDFPlayer.begin(mySoftwareSerial)) {  //Use softwareSerial to communicate with mp3.
    Serial.println(F("Unable to begin:"));
    Serial.println(F("1.Please recheck the connection!"));
    Serial.println(F("2.Please insert the SD card!"));
    while (true) {
      delay(0); // Code to compatible with ESP8266 watch dog.
    }
  }
  Serial.println(F("DFPlayer Mini online."));

  myDFPlayer.volume(20);  //Set volume value. From 0 to 30

}

void loop() {
  // put your main code here, to run repeatedly:

  int potVal = map(analogRead(POTENTPIN), 0, 1023, 1, 35);
  //int buttonState = digitalRead(BUTTONPIN);

  prevState = potVal;
  if (prevState == potVal) {
    if (analogRead(POTENTPIN) % 36 == 0) {
      //do nothing
    } else {
      myDFPlayer.play(potVal);

      Serial.println((String)"Playing song # " + potVal);
      delay(250);
      while ((potVal - map(analogRead(POTENTPIN), 0, 1023, 1, 35)) == 0)
      {

        delay(1);
      }
    }
    prevState = potVal;
  }
}

Notes

– I could not include a gif/ video, featured image, or careful well-shot images of my device due to the complications of remote learning

]]>
Sleep Music Player https://courses.ideate.cmu.edu/60-223/s2020/work/sleep-music-player/ Sat, 18 Apr 2020 18:02:46 +0000 https://courses.ideate.cmu.edu/60-223/s2020/work/?p=10815

Overview – A music player that detects heart rate when user is ready to sleep and turns the volume of the music down when they fall asleep.

Music player in its natural habitat

A tiny speaker on each side of the product

hand-hold size, easy for user to place their thumb on

anatomy of the music player

 

 

 

 

 

 

 

Sleep Music Player from Meijie Hu on Vimeo.

Process

the first major turning point was how to accurately get the pulse. Since the pulse sensor does not always give out accurate output unless user place their finger in a very specific way, I chose to address the problem by designing a divit that guides people to press their thumb onto the pulse sensor in specific place and direction, which led to the result of the pulse sensor more accurate than before (still pretty terrible but much better). And through code I tried to eliminate all the abnormal values.

the second turning point was that as I tried to connect all the functional pieces together the data of pulse sensor got weird. After consulting with Zach we figured out that sharing the same power source between pulse sensor and the DFPlayer might lead to music player interferes the signals the pulse sensor tries to detect. Therefore, we solved the problem by using two seperate battery packs.

ideation sketch

writing pulse sensor code

testing ergonomic placement of the sensor

using a second battery pack for the speaker

making the form

test out speaker

 

Response

“Interesting project, i would suggest maybe making something more huggable. Maybe making it out of fur or something. I like the intention of the project.”

This would be an interesting next step. I guess the project would be even more interesting if it is a pillow. Free your hand and more intimate.

The location of the sensor could be moved, or it is a another separate system (wireless if possible) that could help detect even if the user is not cuddling it. “

This is definitely something that I have been struggling with since you cannot ask users to keep their hand on the speaker forever. Increase the surface area of detection (maybe turning it into a finger cap or something) might help.

 

Self-critique

I really hope the project worked during presentation:( I think it has a lot of potential, given that I got every part to function and spent so much effort on the form, the final result could definitely be better. I definitely under-estimate the delicacy of pulse sensor and wires, which gave me a lot of troubles in the final assembly since I need to stuck everything inside of the tiny tube. Another hard hard lesson to learn in this project is the importance of documentation. During the process I missed out some documentation of some critical steps, which gave me not enough materials to prove my thing is working when it malfunctioned during class.

 

What I learnt

I really enjoy brainstorming the form and the process of making it, and the coding part is also relatively simple. The hardest part was assembling and handling the wiring. The process of optimizing the wiring of the machine really drove me crazy because one misplacement of the wiring can make the whole thing stop working and I had to spend hours to spot the wrong wiring out.

 

Next Step

Like what I mentioned above, turning it into a pillow would be fun, and probably less pain in fixing the wiring since there would be more room. Upgrade the speaker and maybe giving some visual feedbacks such as dimming the light might add more fun to the product.

Schematics

 

Code

/*TITILE: 
 * Sleeping Music Player
 * 
 * meijieh
 * 
 * DESCRIPTION:
 * The code first check whether the current time reaches the alarm time, and
 * play songs when it is alarm time. It also calculates the BPM using the data 
 * sent from the pulse sensor,and as BPM value drops when user falls asleep.
 * 
 * PIN MAPPING
 * 
 * A0       pulse sensor
 * 11       RX for DFplayer
 * 10       TX for DFplayer mini
 * SCL      I2C LCD screen+ Real-time clock
 * SDA      I2C LCD screen+ Real-time clock
 * 
 * CREDIT:
 * 
 * DFplayer mini - https://wiki.dfrobot.com/DFPlayer_Mini_SKU_DFR0299
 * RTC - RTClib library 
 * BPM - https://www.xtronical.com/basics/heart-beat-sensor-ecg-display/
 */



#define UpperThreshold 800
#define LowerThreshold 600

#include "Arduino.h"
#include "SoftwareSerial.h"
#include "DFRobotDFPlayerMini.h"
#include <Wire.h>
#include <RTClib.h>
#include <LiquidCrystal_I2C.h>

//LCD screen
LiquidCrystal_I2C screen(0x27, 16, 2);

//DFplayer
SoftwareSerial mySoftwareSerial(10, 11); // RX, TX
DFRobotDFPlayerMini myDFPlayer;
void printDetail(uint8_t type, int value);
bool isPlaying = false;
int volume = 30;

unsigned long taskTimer = 0;
int interval = 1000;

//detect pulse
const int PULSEPIN = A0;
bool BPMTiming = false;
bool BeatComplete = false;
long LastTime = 0;
int BPM = 0;
int BPMs[10];
int BPMIndex = 0;


//running average
const int numReadings = 2;      // the index of the current reading
int readings[numReadings];      // the readings from the analog input
int readIndex = 0;
int dif = 0;

//alarm
RTC_DS3231 rtc;
char t[32];
char alarmT[32];
int alarmH = 0;
int alarmM = 0;




void setup() {
  mySoftwareSerial.begin(9600);
  Serial.begin(9600);
  Wire.begin();
  //set time
  rtc.begin();

  //rtc.adjust(DateTime(2020,3,3,3,27,0));
  pinMode(BUT1PIN, INPUT);
  pinMode(BUT2PIN, INPUT);
  pinMode(PULSEPIN, INPUT);

  //LCD screen
  screen.init();
  screen.backlight();
  delay(1000);
  screen.clear();
  screen.home();
  screen.setCursor(0, 1);
  screen.print("ALARM:");
  screen.setCursor(10, 0);
  screen.print("BPM:");

  //DFPlayer
  Serial.println();
  Serial.println(F("DFRobot DFPlayer Mini Demo"));
  Serial.println(F("Initializing DFPlayer ... (May take 3~5 seconds)"));

  if (!myDFPlayer.begin(mySoftwareSerial)) {  //Use softwareSerial to communicate with mp3.
    Serial.println(F("Unable to begin:"));
    Serial.println(F("1.Please recheck the connection!"));
    Serial.println(F("2.Please insert the SD card!"));
    while (true);
  }

  for (int i = 0; i < 10; i++)
  {
    BPMs[i] = random(40, 90);
    Serial.println(BPMs[i]);
  }

}


void loop()
{
  //read time
  DateTime now = rtc.now();
  sprintf(t, "%02d:%02d", now.hour(), now.minute());
  screen.setCursor(0, 0);
  screen.print(t);
  
  //set alarm
  screen.setCursor(6, 1);
  alarmH = 17;
  alarmM = 36;
  sprintf(alarmT, "%02d:%02d", alarmH, alarmM);
  screen.print(alarmT);

  //play music
  if (rtc.now().hour() == alarmH && rtc.now().minute() == alarmM && !isPlaying) {
    Serial.println(F("DFPlayer Mini online."));
    myDFPlayer.volume(volume);  //Set volume value. From 0 to 30
    myDFPlayer.play(1);  //Play the first mp3
    isPlaying = true;
  }
  if (myDFPlayer.available()) {
    printDetail(myDFPlayer.readType(), myDFPlayer.read()); //Print the detail message from DFPlayer to handle different errors and states.
  }

  // calc bpm
  long value = analogRead(A0);
  //Serial.println(value);
  if (value > UpperThreshold)
  {
    if (BeatComplete)
    {
      BPM = millis() - LastTime;
      BPM = int(60 / (float(BPM) / 1000));
      BPMTiming = false;
      BeatComplete = false;
    }
    if (BPMTiming == false)
    {
      LastTime = millis();
      BPMTiming = true;
    }
  }
  if ((value < LowerThreshold) & (BPMTiming)) {
    BeatComplete = true;
  }

  //show time
  if (millis() - taskTimer >= interval) {
    taskTimer = millis();

    // get running average of BPM
    readings[readIndex] = BPM;
    readIndex = readIndex + 1;
    if (readIndex >= numReadings) {
      readIndex = 0;
    }
    if (BPM > 100 || BPM < 40) {
      Serial.print(BPM);
      Serial.println("invalid data1");
      screen.setCursor(14, 0);
      screen.print(BPMs[random(0,10)]);
    }
    else {
      for (int i = 0; i < numReadings - 1; i++) {
        dif = readings[i + 1] - readings[i];
        if (dif == 0 || dif > 10) {
          Serial.println((String) dif + "invalid data2");
          screen.setCursor(14, 0);
          screen.print(BPMs[random(0,10)]);
        }
        else {
          Serial.println(BPM);
          screen.setCursor(14, 0);
          screen.print(BPM);
          BPMs[BPMIndex] = BPM;
          BPMIndex = BPMIndex + 1;
          if (BPMIndex >= 10) {
            readIndex = 0;
          }
        }
      }
      volume = map(BPM, 50, 100, 0, 30);
      myDFPlayer.volume(volume);
    }
  }
}




  void printDetail(uint8_t type, int value) {
    switch (type) {
      case TimeOut:
        Serial.println(F("Time Out!"));
        break;
      case WrongStack:
        Serial.println(F("Stack Wrong!"));
        break;
      case DFPlayerCardInserted:
        Serial.println(F("Card Inserted!"));
        break;
      case DFPlayerCardRemoved:
        Serial.println(F("Card Removed!"));
        break;
      case DFPlayerCardOnline:
        Serial.println(F("Card Online!"));
        break;
      case DFPlayerPlayFinished:
        Serial.print(F("Number:"));
        Serial.print(value);
        Serial.println(F(" Play Finished!"));
        isPlaying = false;
        break;
      case DFPlayerError:
        Serial.print(F("DFPlayerError:"));
        switch (value) {
          case Busy:
            Serial.println(F("Card not found"));
            break;
          case Sleeping:
            Serial.println(F("Sleeping"));
            break;
          case SerialWrongStack:
            Serial.println(F("Get Wrong Stack"));
            break;
          case CheckSumNotMatch:
            Serial.println(F("Check Sum Not Match"));
            break;
          case FileIndexOut:
            Serial.println(F("File Index Out of Bound"));
            break;
          case FileMismatch:
            Serial.println(F("Cannot Find File"));
            break;
          case Advertise:
            Serial.println(F("In Advertise"));
            break;
          default:
            break;
        }
        break;
      default:
        break;
    }
  }

 

 

]]>
Smart Happy Plant Chamber https://courses.ideate.cmu.edu/60-223/s2020/work/smart-happy-plant-chamber/ Tue, 31 Mar 2020 10:55:40 +0000 https://courses.ideate.cmu.edu/60-223/s2020/work/?p=10372 [High quality pictures were not available, but I used all pictures I have. For the documentation, I used all pictures I have, and either reused pictures throughout the document depending on needs or recreated the model digitally.]

Overview

Smart Happy Plant Chamber is an Arduino project designed to assist growing plants by implementing automatic watering and lighting systems.

A digitally recreated picture of the completed model of chamber

The overview of a final chamber. The top has two holes each for a soil moisture sensor and motor hoses

The front (Holes for an LCD screen and three switches – each labeled)

 

Process images and review

I had two important decision points in my project each from the software and the physical structure.

  • Software

In the beginning, I was naive about designing the details of software so the machine was designed to water plants every two days and turn on the light 24 hours even without setting the default plant for the project. However, I realize not only making the physical result and making it work, but also those software choices matter in the end, so I did some research on plant growths.

I set it as house garden plants so that the user can use it for a wide range of plants. I found out, with the sensor I used a SparkFun moisture sensor, the soil moisture level should be around 50%, which is done at the household by watering the plants every 3-4 days. With these in consideration, I designed the automatic watering system to water the plant every 3 days; however, If the soil moisture level is below 50% of the water threshold point anytime during 3 days, the system will water the plant until it gets to the point, and restart the 3 days.

Moreover for the lighting, I had problems with the amount and the duration of lighting. I thought the more light the better, and white light only would be enough. However, plants also have biorhythm, so they need some time to “sleep” and to be “awake” just like us. Thus, I implemented a real-time clock into the software, so the lighting system turns on during the daytime, specifically from 7 A.M., so if the natural light is below the threshold, then the LEDs would turn on, and after 7 P.M., the lighting system turns off completely. On top of that, plants need different frequency ranges of light, from red to blue to white, which I could improve by software, fortunately. Thus, I changed my software for WS2801 LED strip to have those RGB values alternatively for each LED unit such as RGBW RGBW RGBW. I learned that conducting a project on real life is not always about technical issues, but I need to be significantly knowledgeable about the area that I am developing the technologies, which in this case was basic biology on plant growths.

  • Physical Structure

Regarding the physical structure of the project, I needed to break down the structure into the bottom and top because they serve different purposes. The bottom part mainly stores most elements such as circuits, Arduino Uno, motor. For aesthetic purposes, everything that does not need to be outside for functional reasons was stored inside the bottom. The top part is where users put the actual plant. Thus it needed a flat floor for the plant, and also a proper structure for LEDs, a light sensor, water pipes, and soil moisture sensor.

For the bottom part, I was planning to make it as a cuboid, and have an LCD screen and switches on one side. However, I realized if those elements are on a side of the cuboid that is perpendicular to the floor, it will not be user friendly. Thus, I changed the bottom part to be a trapezoid column, so that the front side can be tilted to 45 degrees and users can easily use it.

For the top part, I planned to have a cuboid frame like the initial image below, but I realize first, it is not good for plants to spray water on its leaves, and second, it’s unnecessarily complicated to design. Therefore, I changed it as having two columns that will have LEDs inside and a light sensor on a top, and directly put water pipes and a moisture sensor in a plant vase.

The original ideation drawing (It has a cuboid frame that carries watering and lighting systems, and does not have a bottom part that can store the rest of the model such as circuits)

The final model of chamber (The bottom is of trapezoid column with the tilted front. The top has two columns on each side for LED strips and a photoresistor)

The planar figure of the bottom box (a screenshot of dxf file used for laser cutting)

 

Discussion

In retrospect, the most exciting part of this project was ideation in the beginning when I brainstormed which problem I would want to address. I had multiple ideas of machines that can assist my life, but in the end, I narrowed it down to a few by thinking which problems are relatable to others just as to myself. One of the project ideas that I narrowed down in this way was “Ramen Cooker”. I personally cannot cook instant ramen, so I was planning to make a ramen cooker Arduino project in the beginning. I realized this is not a common problem people have, so it might not be relatable to many other people. Some people might even ask questions like “why would you need a ‘machine’ to cook ramen?” I think this thought process helped me decide which project to work on, and I ended up making a smart chamber that grows plants automatically. I got many positive comments about the project idea, especially on how it can be useful to people who cannot grow plants well like me such as “it’s a good idea to help people who struggle with growing plants” and “the idea is very useful for people who are not good at raising plants”. I think my effort in trying to find a problem I have but also what other people can accept and relate to have worked in this kind of project where I need to perform and introduce my project to others, which is the case of most projects. Therefore, for future projects, I would want to use the same thought processes in the ideation process.

However, one significant part I missed during the planning was time management. I thought, if I make the physical outcomes of the project with appropriate design, circuits, and codes that would be the end of this project, and if I want to check it, then I can just run it. It may be true for some other types of projects; however, unlike other projects, the ultimate functionality check for this project would be growing plants at least for a week. Just as one of the comments I got, “ [I] am not sure about the functionality because we didn’t test it with a plant…”, I could not see if the plants actually grow well with the machine in the end. From this trial and error, I have learned that whenever I start any project, I need to study the environment of a project and then decide how to evaluate the results in the end, and include that into the schedule. Moreover, I had an issue with overall time management as well. Finding necessary parts and wiring them into Arduino circuits took a few days more than I expected, so I had a delay in my whole project schedule; thus, in the end, I could not finish combining everything, which I regretted the most. This was also an issue of setting a precise timeline not too tight but with the considerations of some buffers.

There were also some changes that I needed to make due to the time restraint. Originally, I did plan to have multiple modes for different types of plants because they all require different water cycles and light intensity. If I get the opportunity to repeat but improve this project in the future, I am going to plan from the start to the end in the beginning of the project including a few days to revise the semi-final outcomes and also to evaluate the result in the way I wanted to use the machine which is growing a plant with it. Moreover, I would like to add different modes for different types of plants that people often have in their houses such as herbs, succulents, etc. I am going to add a button in the front panel that users can change the mode and set the plant they have, and will add some codes according to the changes.

 

Technical information

A hand-drawn schematic for the chamber

/*
 * Project2: Smart Happy Plant Chamber
 * Jeongyun Lee(jeongyul)
 * 
 * Description: Following is a code for Smart Happy Plant Chamber. 
 * For the lighting system, during the daytime, it turns on the LED depending on the amount of light determined by the photoresistor 
 * value. During the night time, it always turns off the LED. However, when the LED manual switch is on, 
 * no matter of the light value and the real time, it turns on the LED.
 * For the watering system, it evaluate whether the plant needs more water from the soil moisture sensor value,
 * and if the soil is dry, it turns on the water pump and vice versa. However, when the water manual switch is on, 
 * no matter of the soil mositure value, it turns on the water pump.
 * 
 * Collaboration: N/A
 * Reference: I used a part of "Adafruit Neopixel" Library "simple" example codes to initiate LED strip 
 * 
 * Pin mapping:
 * 
 * pin    / mode    / description
 * -------/---------/------------
 * 9        input     water switch
 * 10       input     LED switch
 * A0       input     photoresistor
 * 4        output    water pump 
 */

#include <Wire.h>
#include <LiquidCrystal_I2C.h>
#include "RTClib.h"
#include <Adafruit_NeoPixel.h>
#ifdef __AVR__
#include <avr/power.h>
#endif

LiquidCrystal_I2C screen(0x27, 16, 2);

const int WATERSWITCHPIN2 = 9; //water manual button
const int LIGHTSWITCHPIN3 = 10; //light manual button
const int WATERPIN = 4; //water pump pin
const int LEDPIN = 6; //led strip pin
const int MOISTUREPIN1 = A1; //moisture sensor analaog pin
const int LIGHTSENSORPIN = A0; //light sensor pin
const int NUMPIXELS = 24;

const int moistThres = 511; //below 511 = dry, above 511 = enough water
const int lightThres1 = 50; //below lightThres1: very deem
const int lightThres2 = 100; //below lightThres2: mildly deem

//current status for Automatic/Manual, for LED, and for water pump
String curStat = "AUTO";
String LEDstat = "ON ";
String WATERstat = "ON ";


RTC_DS3231 rtc;

Adafruit_NeoPixel pixels(NUMPIXELS, LEDPIN, NEO_GRB + NEO_KHZ800);

void setup() {
  pinMode(WATERSWITCHPIN2, INPUT);
  pinMode(LIGHTSWITCHPIN3, INPUT);
  pinMode(LIGHTSENSORPIN, INPUT);

  pinMode(WATERPIN, OUTPUT);
  digitalWrite(WATERPIN, LOW);

  screen.init();
  screen.backlight();
  screen.home();
  screen.print("tree happy!");
  delay(7000);
  screen.clear();

//some codes from basic real time clock library 
#if defined(__AVR_ATtiny85__) && (F_CPU == 16000000)
  clock_prescale_set(clock_div_1);
#endif
#ifndef ESP8266
  while (!Serial); // for Leonardo/Micro/Zero
#endif
  pixels.begin();
  Serial.begin(9600);

//the error cases when the real time clock does not work
  if (! rtc.begin()) {
    Serial.println("Couldn't find RTC");
    while (1);
  }

  if (rtc.lostPower()) {
    Serial.println("RTC lost power, lets set the time!");
    // If the RTC have lost power it will sets the RTC to the date & time this sketch was compiled in the following line
    rtc.adjust(DateTime(F(__DATE__), F(__TIME__)));
    // This line sets the RTC with an explicit date & time, for example to set
    // January 21, 2014 at 3am you would call:
    // rtc.adjust(DateTime(2014, 1, 21, 3, 0, 0));
  }
} 

//A helper function for turning on a side of LED strip
void led1() {
  pixels.setPixelColor(0, pixels.Color(0, 0, 255));
  pixels.setPixelColor(1, pixels.Color(0, 255, 0));
  pixels.setPixelColor(2, pixels.Color(255, 0, 0));
  pixels.setPixelColor(3, pixels.Color(255, 255, 255));
  
  pixels.setPixelColor(4, pixels.Color(0, 0, 255));
  pixels.setPixelColor(5, pixels.Color(0, 255, 0));
  pixels.setPixelColor(6, pixels.Color(255, 0, 0));
  pixels.setPixelColor(7, pixels.Color(255, 255, 255));
  
  pixels.setPixelColor(8, pixels.Color(0, 0, 255));
  pixels.setPixelColor(9, pixels.Color(0, 255, 0));
  pixels.setPixelColor(10, pixels.Color(255, 0, 0));
  pixels.setPixelColor(11, pixels.Color(255, 255, 255));

  pixels.show();
}

//A helper function for turning on the other side of LED strip
void led2 () {
  pixels.setPixelColor(12, pixels.Color(0, 0, 255));
  pixels.setPixelColor(13, pixels.Color(0, 255, 0));
  pixels.setPixelColor(14, pixels.Color(255, 0, 0));
  pixels.setPixelColor(15, pixels.Color(255, 255, 255));

  pixels.setPixelColor(16, pixels.Color(0, 0, 255));
  pixels.setPixelColor(17, pixels.Color(0, 255, 0));
  pixels.setPixelColor(18, pixels.Color(255, 0, 0));
  pixels.setPixelColor(19, pixels.Color(255, 255, 255));

  pixels.setPixelColor(20, pixels.Color(0, 0, 255));
  pixels.setPixelColor(21, pixels.Color(0, 255, 0));
  pixels.setPixelColor(22, pixels.Color(255, 0, 0));
  pixels.setPixelColor(23, pixels.Color(255, 255, 255));

  pixels.show();
}

//A helpter function for turning on/off the water pump
void water() {
  delay(10);
  int moistVal = analogRead(MOISTUREPIN1);
  //when there is enough mositure in soil: water pump off
  if (moistVal >= moistThres) {
      WATERstat = "OFF";
      digitalWrite(WATERPIN, LOW);
  }
  //when there isn't enough mositure in soil: water pump on
  else {
      WATERstat = "ON ";
      digitalWrite(WATERPIN, HIGH);
      delay(5000);
  }
}

//A helper function for turning on/off the LEDs
void light(int hour) {
  int lightVal = analogRead(LIGHTSENSORPIN);
  
  //Daytime
  if (hour >= 7 && hour <= 16) {
    if (lightVal < lightThres1) {
      //when the light is very deem
      LEDstat = "ON ";
      led1();
    }
    else if (lightVal >= lightThres1 && lightVal < lightThres2) {
      //when the light is mildly deem
      LEDstat = "ON ";
      led1();
      led2();
    }
    else {
      //when the light is enough
      LEDstat = "OFF";
      pixels.clear();
    }
  }
  
  //Night time
  else {
    //at night, the light is always off
    digitalWrite(LEDPIN, LOW);
    LEDstat = "OFF";
  }
}

void loop() {
  DateTime now = rtc.now();
  
  //manual mode buttons
  int waterState = digitalRead(WATERSWITCHPIN2); //water manual on/off
  int lightState = digitalRead(LIGHTSWITCHPIN3); //water manual on/off
  
  //Setting modes
  //Water: manual, LED: manual
  if (waterState == HIGH && lightState == HIGH)
  {
    curStat = "MANU";
    digitalWrite(WATERPIN, HIGH);
    digitalWrite(LEDPIN, HIGH);
    LEDstat = "ON ";
    led1();
    led2();
  }
  //Water: manual, LED: auto
  else if (waterState == HIGH && lightState == LOW)
  {
      curStat = "MANU";
      digitalWrite(WATERPIN, HIGH);
      light(now.hour());
  }
  //Water: auto, LED: manual
  else if (waterState == LOW && lightState == HIGH)
  {
    curStat = "MANU";
    water();
    digitalWrite(LEDPIN, HIGH);
    led1();
    led2();
    LEDstat = "ON ";
    //delay(500);
  }
  //Water: auto, LED: auto
  else
  {
    curStat = "AUTO";
    water();
    light(now.hour());
  }

  //LCD screen print 
  screen.clear();
  screen.setCursor(1, 0);
  screen.print("Now: ");
  screen.setCursor(6, 0);
  screen.print(now.hour());
  screen.setCursor(8, 0);
  screen.print(":");
  screen.setCursor(9, 0);
  screen.print(now.minute());
  screen.setCursor(12, 0);
  screen.print(curStat); 
  screen.setCursor(1, 1);
  screen.print("L: ");
  screen.setCursor(4, 1);
  screen.print(LEDstat);
  screen.setCursor(7, 1);
  screen.print("  W: ");
  screen.setCursor(12, 1);
  screen.print(WATERstat);
  Serial.print(analogRead(LIGHTSENSORPIN));
}
]]>
Data Physicalization of Studio Hours https://courses.ideate.cmu.edu/60-223/s2020/work/data-physicalization-of-studio-hours/ Mon, 30 Mar 2020 12:57:07 +0000 https://courses.ideate.cmu.edu/60-223/s2020/work/?p=10310

Final prototype.

This project aims to track the number of hours I am in my studio, as the 24 rods on the top floor fall to the bottom floor for every hour that I am sitting in my studio chair. 

[high-quality images unavailable. Prototype does not work, so video is also unavailable.]

Sketches: How do you move 24 different parts with as little components as possible?

After consulting with Zach about my ideas in the initial check-in phase, I decided to go forth with making a physicalized data of my hours spent in my design studio. I sketched out numerous ideas to understand how to represent 24 hours with as little components as possible to reduce the number of “moving parts.”

Idea 1: Servo moves panel in linear motion at an increment every hour. If in studio, panel falls. If out of studio, panel stays on top floor. Problem: how do I differentiate between the two phases?

Consideration of how to differentiate between on-off phase  for idea 1.

Idea 2: Ribbon wrapped around two servos, servos rotate every hour I am in studio, as ribbon reveals the number of hours I have been there. A glorified timer.

Iteration of Idea 2.

Another iteration of idea 2.

Redefining parameters. Distance sensor, 24 hours and on-off state had to be used.

Final idea sketch

To explain how the project works: Top level contains 24 rods. Middle panel rotates every hour, with a small hole that opens and closes depending on whether I was in studio at that hour. If in studio, the rod falls to the bottom floor through the hole. If not, the rod remains on the top floor, as the hole never opens up.

Refined sketch. Figured out how to hold top panel in place

Decision 1:

A “flat” hole closer that can never open up, as the panel blocks the wooden piece from swiping down.

Hole closer: the anguish of making a flat surface that also doesn’t interfere with falling rods

The hole closer was critical to my design. Its essential function is to open up the hole when the ultrasonic ranger senses that I am in the seat, in order to let the rod drop onto the bottom floor.

I learned through trial and error the following rules to maintain the following rules in making the hole closer work.

    • The wooden piece had to be perfectly flat when in the “closed” phase, such that the rods don’t get stuck at any bumps when the middle plane rotates.
    • The wooden piece could not too big to interfere with a falling rod when in the “open” phase,  or the rod will get stuck onto the wooden piece before falling to the bottom floor.

The problem was that I could not figure out how to fulfill both rules when positioning the servo in the right place. When the servo was located to the right of the hole, the wooden piece would be perfectly flat in the “closed” phase, but the rod would get stuck on the piece in the “open” phase. 

Hole closer that closes flatly, but interferes with a falling rod in “open” state.

To solve this problem, when the servo was located towards the front of the hole (closer to the center), both rules would be satisfied, but the hole would not even open up in the first place due to the big size of the wooden piece. 

Rotation is blocked due to position of the servo

I tried using  soft pipe cleaners, rather than a hard wooden plane to solve the issue. The softness of the furs would reduce the “bumps” in either phase, even if not positioned entirely correctly. This did not work, however, as the pipe cleaner was too malleable, letting the rod go through as shown below:

Rod goes through the pipe cleaner attempt, causing the whole system to halt.

In the end, nothing really worked to solve this problem. Another fabrication method would have been better to open and close the hole, but as I had already cut out the  piece on laser cutter, I could not find another way around it. 

Decision 2:

Continuous servo motor → stepper motor for flatter surface and greater precision

A continuous servo motor was initially used to rotate the middle plane, but there were two problems:

    • One, I couldn’t achieve the desired angles for each stop, as the servo motor can only be controlled by the speed at which it moves.
    • Two, the servo motor was too flimsy, that it didn’t hold the plane at a perpendicular angle. That messed with the precision of the hole alignment from panel to panel. 

Servo motor unable to maintain a flat surface, making the rods difficult to pass through the holes

I then opted for using the stepper motor, which gave me a much greater precision. I blew up three A4988 chips in the process, however. The first time, I had circuits that flipped the ground and 5v. I couldn’t figure out the problems for the second and third times though. I lowered the voltage to 10 volts, rather than the maximum 12 volts for the stepper motor, just in case I don’t blow up another board.

A4988 chip used to interface with the stepper motor. I ended up blowing up three of these.

Stepper motor caused the plane to rotate uncontrollably due to the high vibration.

Rotation problem fixed by bolting the plane down to the motor.

Stepper motor gave me greater control over hole alignment. I figured out on code how to rotate the plane by 360/24 degrees. The hole alignment now worked from plane-to-plane.

Other moments in process

Rod falls through the hole well. One of few successful tries.

Initial, initial prototype. Tested out string gear for Idea 1.

Tested out how rods would fall if middle plane was moved.

Realization: Even the slightest tilt (as shown) can cause rods to fall out of place. The top plane had to be revolted to fix this error.

Final prototype. Panels are held together perpendicularly through thick wooden poles

Response

I was quite frustrated leading up to the final critique day, as I was staying up for three whole nights in the physical computing lab trying to make the project work. I had spent so long in the earlier phase trying to design how to represent 24 different data points using as few components as possible, that when I landed on a sketch that worked in theory, I was determined to follow through with my plan. 

What I should have realized early on is that my prototype plan had way too many mechanical problems. I thought I had found a simple solution to my seemingly unsolvable problem, but it turned out that even the “simple” solution had way too many parts to control. 

For one, it required a significant amount of precision in making sure each of the 24 rods to fall through the holes in the desired manner. Not only that, making the hole-closer mechanism was excruciatingly difficult, but I could not turn back on the design late into the prototype phase, as I had already laser cutted the piece for the specific purpose I had designed for. As a result, my piece never ended up working for the final crit day.

I got some feedback from my peers during the class critique to tackle those problems. Lots of advice were given to help with the problem of the rod getting stuck between the wooden planes, as well as making sure they fall into the right holes in the bottom layer.

“Nice concept, where you are able to visualize how long you have worked plays with your guilt. For the wood stick falling issue, you could use a tube that will lead the wood stick to certain place, so other factors like slight movement of wind doesn’t make the stick be stuck in the hole. Also place a little tray or holder to catch the wood sticks. “

  • The tube idea was something I had not thought of before. Definitely worth a try if I could have another go at this project. I wonder if the tubes become another obstacle that the rods hit against though, however, making the rods get stuck at an angle within the hole before exiting through the next hole. I find this to be very likely based on my experience with the project, although using a heavier rod, such as one made of steel or plastic, may help solve this issue. 

“The project aesthetic is really nice. Everything looks precisely measured which makes it look really good! Maybe try to have a function that wiggles around until the sticks fall”

  • I actually did try making the stepper motor “wiggle” at a stop, to help with the stick falling. I spent hours trying to figure out the right code for it and ended up not working. I could not figure out why in the given time span, but the motor would rotate around by over 50 degrees, rather than moving 5 degrees back and forth from the original spot as desired.

Looking back on the process now, I realize that I should have been less ambitious with my approach, even if it made sense in “theory” and in my sketches. Rather than simulating my project in my brain that clearly defied all rules of physics, I should have changed my project plan soon as I found myself working on a minor problem for 8 straight hours. 

I thought that my project was very simple due to the little amount of effort it takes on code, but it relied too much on physical fabrication for my idea to work well. One of the biggest problems was that I had no way of knowing how the rods (the moving part that has no direct ties to any Arduino component) would move. I’m thinking that maybe I should have taken the time to build the gear mechanism on Fusion360, where two gears can control the x and y direction of a servo, so that I have full control over any moving part that requires precision. From that mechanism on, I could have built a more manageable project by, say, just having the servo push 24 panels like a domino, where each panel has no dependency on the entire system. (Some rods currently get stuck between panels, stopping the middle plane from rotating to the next hole.)

Also, I could have been more open to a digital representation of time (ex: using LED bulbs) once I realized that controlling the rods became too difficult, and focused more of my attention on controlling the distance sensor on code as well as covering up all the electronics. The problem seems to be that I made no compromises to make my project more manageable, resulting in 3 all-nighters and a failed project at best. 

I’m not sure if going forward with my current model is the best idea. I was pretty distraught for most of my Spring break thinking of how unsuccessful my project was, so I am mentally exhausted from just looking at my project now. The idea of keeping track of my hours at studio through data physicalization is promising, but perhaps not through 24 different rods. I might give it another go when I regain more confidence in physical computing through other, more plausible projects.

Schematic

Code submission

/* Data Physicalization of Studio Hours
 * Elizabeth Han (yoonjuh)
 * 
 * Description:
 * This code achieves two functions in the overall project. First, it changes 
 * the degrees of a servo depending on the distance sensor reading in order to
 * close the hole in the middle panel. Second, it rotates the middle plane by 
 * 360/24 degrees every hour (currently set to 3 seconds for testing purposes) using the
 * stepper motor.
 * 
 * Pin mapping:
 * pin   / mode   / description
 * ------/--------/---------------------------
 * 12     input    ultrasonic sensor trigger
 * 11     input    ultrasonic sensor echo
 * 10     output   servo
 * 7      output   stepper motor steps
 * 8      output   stepper motor direction
 * 
 * References:
 * Stepper motor: https://courses.ideate.cmu.edu/60-223/s2020/tutorials/stepper
 * TaskTimer: https://courses.ideate.cmu.edu/60-223/s2020/tutorials/code-bites#blink-without-blocking
 */

#include <NewPing.h>
#include <Servo.h>
#include <AccelStepper.h>
#include <Wire.h>

#define TRIGGER_PIN  12  // Arduino pin tied to trigger pin on the ultrasonic sensor.
#define ECHO_PIN     11  // Arduino pin tied to echo pin on the ultrasonic sensor.
#define MAX_DISTANCE 400 // Maximum distance we want to ping for (in centimeters). Maximum sensor distance is rated at 400-500cm.

NewPing sonar(TRIGGER_PIN, ECHO_PIN, MAX_DISTANCE); // NewPing setup of pins and maximum distance.

//set up servo
Servo SwingServo;
const int SERVOPIN2 = 10;

//set up step motor
const int STEP_PIN = 7;
const int DIR_PIN = 8;
AccelStepper myMotor(1, STEP_PIN, DIR_PIN);

int steps = 0;
int pos = 1;

//set up timers
unsigned long taskTimer = 0;
unsigned long taskTimer2 = 0;
unsigned long taskTimer3 = 0;
const int INTERVAL = 4000; //check sitting as frequently as possible
const int SWINGINT = 4000;
const int MOTORINT = 3000; //1 hour
const int WIGGLEINT = 500;
int counter = 0;
int prevCounter = -1;

bool sitting = false;
int plus = 0;
int minus = 0;

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

  SwingServo.attach(SERVOPIN2);
  myMotor.setMaxSpeed(6000); // measured in steps per second
  myMotor.setAcceleration(10000); // measured in steps per second squared
}



void loop() {
  // distance sensor --> hole closer 
  //determines if person is sitting or not in seat, opens up hole
  
  int distance = sonar.ping_cm();

  if (millis() - taskTimer >= INTERVAL) {
    if (distance <= 10) {
      SwingServo.write(0);
      sitting = true;
    }
    else {
      SwingServo.write(90);
      sitting = false;
    }
    taskTimer = millis();
  }

  //swing loop
  //wiggles board to make sure rod goes down when in sitting state

    if (millis() - taskTimer2 >= SWINGINT) {
      if (sitting == true and prevCounter != counter) {
        Serial.println("swing");
  
        plus = steps + 1;
        myMotor.moveTo(plus);
        myMotor.run();
        delay(1000);
        Serial.print("swing1 current position: ");
        Serial.println(myMotor.currentPosition());
  
        minus = steps - 1;
        myMotor.moveTo(minus);
        myMotor.run();
        delay(1000);
  
        plus = steps + 1;
        myMotor.moveTo(plus);
        myMotor.run();
        delay(1000);
        Serial.print("swing1 current position: ");
        Serial.println(myMotor.currentPosition());
  
        minus = steps - 1;
        myMotor.moveTo(minus);
        myMotor.run();
        delay(1000);
  
        Serial.print("swing2 current position: ");
        Serial.println(myMotor.currentPosition());
      }
      prevCounter = counter;
      taskTimer2 = millis();
    }
  

  //stepper motor loop
  //turns every 360/24 degrees

  int degreesPerHour = 200 / 24; //200 steps in motor, therefore 200/24

  if (millis() - taskTimer3 >= MOTORINT and sitting == true) {
    counter += 1;

    if (counter == 24) {
      pos *= -1;
      counter = 0;

    }

    steps += degreesPerHour * pos;
    myMotor.moveTo(steps);

    taskTimer3 = millis();

    Serial.print("counter: ");
    Serial.println(counter);
    Serial.print("current position: ");
    Serial.println(steps);

  }

  myMotor.run(); 

}

 

]]>
Carpal Tunnel Syndrome Stretch Coach https://courses.ideate.cmu.edu/60-223/s2020/work/carpal-tunnel-syndrome-stretch-coach/ Sun, 22 Mar 2020 09:44:56 +0000 https://courses.ideate.cmu.edu/60-223/s2020/work/?p=9932 Overview

This is a desktop stretch coach to help relieve my wrists’ symptoms of Carpal Tunnel Syndrome.

Video

The card types alternate every 5 seconds: stretch card, and lifestyle reminder card. While I continue a stretch or take a break before the next stretch, the lifestyle reminder card will appear, helping me remember the good habits I should have for my wrists. The top card is held up by a white bar right under the ceiling of the box, so that it is stopped and does not collapse forward.

Overall VIEW

Device on, displaying the first stretch cards of the routine. The display counts from 1 to 5 every time a card flips.

Details

5-second timer display and switch.

Connection of the cards to the mounted stepper motor. The cards are loosely held through each hole of the wooden circle with pins, and the circle is tightly connected to the motor. The motor turns the circle forward. 

Device with the back board slid open. Back board is detachable for if the cards need replacement or if there is an internal problem.

Usage

Switch turned off when device is not in use. The whole device shuts down when the switch is turned off.

Process Images and Review

Throughout the project, there were some main redirection stages that I took to make the project more feasible or effective.

 

One part was determining how to display the 5-second timer. Initially, I thought about using an LCD screen, because that was the part that I was most familiar with. Then, I realized that it may not be necessary to use an LCD screen if I am just displaying 5 numbers, so I though about using 5 LEDs, that would visualize the numbers by turning on and off. Finally, I used a 7-segment display because this was a more concise and clearer way.

Initial plan: LCD screen.

Next plan: using 5 LEDs.

Final plan: using a 7-segment display.

 

Extending the 7-segment display connection out of the breadboard to put on the front plane of the box.

Secondly, another important part of the process was figuring out how to create the motor and cards move correctly. When I first brainstormed, I had my wooden circles much bigger and the rectangular prism quite big. After prototyping with chipboard and 3-D modelling, and knowing that I will be using 8 cards of specific sizes, I changed the dimensions. I could therefore avoid the whole structure becoming too big, or the movement becoming unnecessarily big, which would require the mechanism to be structurally very sturdy.

Furthermore, in the beginning, I wanted to have a dowel loosely holding the left side wooden circle, and the dowel tightly fitting into a plane that would also tightly fit inside the box. However, I just attached the dowel directly to the inside of the box.

Initial sketch

Larger-dimensions central wooden structure, and dowel fixed into a plane in the initial prototype.

Central wooden structure with refined dimensions.

Assembly.

Lastly, a software challenge was when I had to utilize the non-blocking routine method for the timer instead of using a delay, so that it wouldn’t be a hard-coded fix, but a more precise system.

Initial attempt using delay

Discussion

Through this project, I could have an enjoyable experience of making my first individual physical computing project , and it gave me an opportunity to have thoughts and explorations that were meaningful to myself as a product designer. However, I was not entirely satisfied with the final physical product. When I chose this topic and brainstormed, I did not foresee some critical challenges that would take a relatively long time to resolve carefully. I think that some of the solutions that were not the best were well-pointed out during the in-class critique.

Some comments about my product was that it would be good “to make the current features more precisely,” or consider the user experience more because “it currently lacks a little bit of utility.” Although this device is for myself, I think some aspects are definitely not coherent enough. For example, the lifestyle reminders do not entirely make sense. It could have been clearer to just have stretch cards going in a loop, and perhaps incorporating the reminders as a different feature. Also, it could be better if I can make a setting in which I would go back to the first stretch of the loop, as opposed to just starting at whichever stretch the motor is showing whenever I turn the switch on.

A positive comment from one of my peers was that “the design of the cards moving is really fun and visually engaging.” Another feedback was that the design is “very neat.” I appreciate these because I also think I had a relatively successful result in getting the mechanism to work, and crafting the overall product. The mechanical part to get the movement working was not challenging for me. Nevertheless, I could have done a better job on the physical aspects if I had some more time to focus on them, after I had the software problems resolved. For instance, I ended up having much more wires than I had expected, so I had to cram in all the parts into the box, which I had laser-cut already in specific dimensions. Also, I can see some inner roughly-made parts from the outside through the window. If I were to make another iteration, I would definitely make the casing more carefully, and perhaps make the cards replaceable and more sturdily connected to the motor.

For me, taking initial attempts at software solutions was difficult for both the timer display part and the stepper motor part. I was able to learn many things as I asked for help from others, and started off with base code references from similar Arduino problems. As I understood other examples and pieces of advice, I could make changes of my own or write code that I could eventually get to work after some trial and error.

Technical Information

Schematic

Code
/* Carpal Tunnel Syndrome Stretch Coach
 * Youie Cho (minyounc)
 * 
 * Description:
 * The code makes a card-flipping mechanism that flips various stretching guide
 * cards that help improve the symptoms of Carpal Tunnel Syndrome.The 7-segment
 * display helps the user to keep track of time for each stretch.
 * 
 * Pin mapping:
 * pin   / mode   / description
 * ------/--------/---------------------------
 * 2-9     output   7-segment display segments
 * 12      output   motor steps
 * 13      output   motor direction
 * 
 * References:
 * AccelStepper Library by Mike McCauley
 * Split flap display mechanism :https://imgur.com/a/0VAMZ
 * Blink without blocking coding: 60-223 course page
 * Stepper motor wiring and coding: 60-223 course page
 * 7-segment display timer wiring and coding:
 * https://create.arduino.cc/projecthub/arduino_uno_cool/create-a-7-segment-display-254ebe
 */
 
#include <AccelStepper.h> // Set up library for motor movement

const int STEP_PIN = 12; // A4988 "STEP" pin wired to pin 12
const int DIR_PIN = 13; // A4988 "DIRECTION" pin wired to pin 13

long second =  1000; // variable to represent 1000 milliseconds = 1 second
int pos = 0; // variable to store motor position
unsigned long timer = 0;// variable to keep track of passing time
const int wait = 5000; // number of milliseconds to wait between motor movements

AccelStepper myMotor(1, STEP_PIN, DIR_PIN); // create an AccelStepper motor object

bool dp = false; // boolean definition for timer (7-segment display)

// Setup for timer numbers 0 to 5
void disp(int num = -1, boolean dp = 0)
{
  digitalWrite(9, !dp);
  if (num == 0)
  {
    digitalWrite(2, 0);
    digitalWrite(3, 0);
    digitalWrite(4, 0);
    digitalWrite(5, 0);
    digitalWrite(6, 0);
    digitalWrite(7, 0);
    digitalWrite(8, 1);
  } else if (num == 1) {
    digitalWrite(2, 1);
    digitalWrite(3, 0);
    digitalWrite(4, 0);
    digitalWrite(5, 1);
    digitalWrite(6, 1);
    digitalWrite(7, 1);
    digitalWrite(8, 1);
  } else if (num == 2) {
    digitalWrite(2, 0);
    digitalWrite(3, 0);
    digitalWrite(4, 1);
    digitalWrite(5, 0);
    digitalWrite(6, 0);
    digitalWrite(7, 1);
    digitalWrite(8, 0);
  } else if (num == 3) {
    digitalWrite(2, 0);
    digitalWrite(3, 0);
    digitalWrite(4, 0);
    digitalWrite(5, 0);
    digitalWrite(6, 1);
    digitalWrite(7, 1);
    digitalWrite(8, 0);
  } else if (num == 4) {
    digitalWrite(2, 1);
    digitalWrite(3, 0);
    digitalWrite(4, 0);
    digitalWrite(5, 1);
    digitalWrite(6, 1);
    digitalWrite(7, 0);
    digitalWrite(8, 0);
  } else if (num == 5) {
    digitalWrite(2, 0);
    digitalWrite(3, 1);
    digitalWrite(4, 0);
    digitalWrite(5, 0);
    digitalWrite(6, 1);
    digitalWrite(7, 0);
    digitalWrite(8, 0);
  } else {
    digitalWrite(2, 1);
    digitalWrite(3, 1);
    digitalWrite(4, 1);
    digitalWrite(5, 1);
    digitalWrite(6, 1);
    digitalWrite(7, 1);
    digitalWrite(8, 1);
  }
}

void setup() {
  // Pin setup for timer
  pinMode(2, OUTPUT);
  pinMode(3, OUTPUT);
  pinMode(4, OUTPUT);
  pinMode(5, OUTPUT);
  pinMode(6, OUTPUT);
  pinMode(7, OUTPUT);
  pinMode(8, OUTPUT);
  pinMode(9, OUTPUT);
  
  Serial.begin(9600);// Begin serial monitor to monitor motor position (in steps)

  // myMotor settings to control motor behavior
  myMotor.setAcceleration(100000); // motor does not accelerate
  myMotor.setMaxSpeed(100); // maximum speed, measured in steps per second
  
  // Motor movement setting as 25/200 steps each time, so that it would go through
  // 8 positions to complete a revolution
  myMotor.moveTo(pos);
  pos = pos - 25;
}

void loop() {
  // Move the motor 25 steps every second
  if ((millis() - timer) > wait){
    Serial.println(pos);
    myMotor.moveTo(pos);
    pos = pos - 25;
    timer = millis();
  }

  // Call timer system function
  displayDigits();

  myMotor.run();
}


// Function to create a timer system
void displayDigits() {
  // Define the sec variable using millis
  int sec = millis() / second;
  
  myMotor.run();
  disp(sec % 5 + 1, 0); // When it reaches 5 seconds, it goes back to 1 second
  printDigits(sec);
  Serial.println();
}


// Function setting up the decimal parameter for second values
void printDigits (byte digits) {
  Serial.print(digits, DEC);
}

 

]]>
Sleep Time Reminder https://courses.ideate.cmu.edu/60-223/s2020/work/sleep-time-reminder/ Fri, 20 Mar 2020 00:43:25 +0000 https://courses.ideate.cmu.edu/60-223/s2020/work/?p=10153 Overview

At two preset time, the alarm robot wanders around randomly and repeats a melody  until it is placed next to the RFID  cards in bathroom or on bed.

 

 

This is the behavior of the robot when it is reminding the user to take a shower or go to bed. A USB power bank is used to provide power as I found out 4 AAA batteries do not provide enough current to support two motors and two speakers playing melody at the same time. However, the batteries do provide enough current when the speakers only vibrate constantly.

This shows a closer look at the working device. When the appropriate RFID card is presented, the device stops moving and playing melody.

The top view of the device.

The elevation view of the device. The RFID reader sticks out so that it can get closer to the RFID card when it is reading.

 

The front view of the device. The parts are arranged in a way to mimic the “face” of a robot, with the speakers being the “eyes” and the batteries case being the “nose”.

Process Image and Review

Motivation and Original Design

I have the problem of procrastinating when it comes to going to sleep. When I am busy during the week, I tend to stay up late working. When I am not as busy, I still go to bed late by watching all kinds of contends. Moreover, I tend to take shower right before I go to bed, which makes the bed time even latter.  I tried to set alarm on my phone to remind myself the time for shower and sleep, but it does not work well as I usually just stop the alarm.  Therefore, I wanted to build an assistive device that really grabs my attention and bring me to physical places at appropriate time.

The original design from the first ideating.

The original design is a small square car that carries two speakers, an electromagnetic sensor, a servo motor, and two continuous servo motors. Magnets will be placed in bathroom and on bed, and the electromagnetic sensor is there to detect the change of EM field, which indicates the physical locations.

The original design of the supporting wheel.

The supporting wheel is added to maintain the balance of the body of the device as well as to help the device turn left or right driven by a servo motor. The supporting wheel is free to rotate like the wheel of a chair.

Prototyping

During prototyping, I changed the continuous servo motors to DC motors because they supply much more power. I also realized that it is very difficult to attach a wheel to the servo motor, and it is much easier to use a  ball roller transfer as a supporting wheel. I was lucky enough to find a ball roller transfer that has the height similar to the radius of a toy tire, which the base of the device can sit on.

Ball roller transfer.

Then I went on to build the circuit on a breadboard. The wiring part is not bad because there are resources on the course website or online that are helpful.

The circuit of the engineering prototype.

However, I had some trouble writing codes for parts that I was not familiar with. For example, I had to learn how to use the library for the RFID reader MFRC522  to write functions that check whether the unique “ID” of the RFID card is the one of the card in the bathroom or on the bed. However, I had the most trouble figuring out how to make the motors and speakers perform two different sequences of actions spontaneously. Then I realized I can solve it by using the blink-without-block tactic. But instead of modifying boolean variables, I have to make a “time map” and assign tasks to the parts in each time interval. However, this is not the best approach when the sequence of actions and hence the time map become long. I will introduce another approach in latter section.

In the function be_annoying, the timer diff_motor and the motor_timemap checks determine which motion to perform.

Redesigning and Building

Because the circuit of the prototype is very messy and clumpy, I decided to solder the wires and components onto a circuit board so that they can fit onto a small robot car. It turned out to be a very time-consuming process but it makes the circuit clear, strong, and secure.

The wires and parts are all soldered onto the circuit board. To make more room from the board, I have cut the legs of the transistor on top.

I then redesign the body of the robot to fit the dimension of the circuit board and the parts.  The front part of the vehicle is not covered up so that the speakers and the RFID reader to extend out for better performance. The circuit is covered by the “shields” that insert into the groves on the side boards.  The idea of the “shields” are inspired by the design of the back of some Lamborghini.

Refined design of the body of the robot.

The shield above the back window.                                                                  https://www.motor1.com/news/78593/after-50-years-the-lovely-lamborghini-miura-is-back/

The sketch of the components. Notice that the grooves on the curved side of the right components are for the shields.

Unfortunately, the “shields” could not be used because the height of the Arduino pro mini is taller than expected due to the soldering on the circuit board. In other words, the soldering tips at the back of the circuit board raise the components up, which get into the way of the shields. Unfortunately, because I was not able to use the laser cutter again before the final critique, I had to abandon the idea and decided to make a pair of wings instead. As a result, the device has a raw look without enclosure.

Finally, the device was built by gluing the parts together and mounting the ball roller transfer onto the base with screws and nuts.

Playing a Song

Because the device was too annoying when the speakers only vibrate constantly to make loud noises, I decided to play a song instead. With two speakers, I can play two melodies at the same time.

 

The lists of notes and the lists of beats. Each note corresponds to a specific frequency for the tone() function.

With a list of notes and a list of beats, the speaker can go through the melody using the blink-without-block technique. However, because there are substantial notes and beats,  I could not use “if” anymore as it gets too tedious. As a result, a loop was written to generate the time-map by looping through the list of beat and add the previous beats up.

The loop that generate the time map for speaker 1.

Finally, a global variable “counter” determines which note to play and what beat it has.

The code that tells the speaker what note to play and how long to play it.

Discussion

Feedback

One of the feedback suggested to build “a cover” to hide the device’s components while another suggested putting “a shell” and bunch of screws” around the battery .  I really like these feedback because I will have a less chance to hack the system when I am not aware of the circuit and the batteries are difficult to remove. Instead of removing the batteries, now the only option to make the machine stop is to to the designated physical place to get the RFID card. As a result, the device can better serve the original purpose of making me take shower or go to sleep.

There are also feedback about “adding a flash light” and adding an interface for “adjustable time for the alarm”. With a flashlight, the robot car can grab more attention, especially at night. And with an interface to adjust the time, the device can be more user-friendly and convenient to use. Therefore, I think those are great feedback and potential improvement to the device.

Self Critique  and What I have learnt

I am pleased about the function that the device can perform. All the parts, including the RFID reader and the motors, work better than I expected. As a result, the device can perfectly perform its intended job. However, I am not satisfied about the design of the final product. As mentioned before, the robot looks rather raw without cover and the wires of the parts still look kind of messy.

One of the biggest lesson I learnt is that I have to make a clear schedule before starting the project. In this project, I only have a rough schedule my mind and as I spent more and more time on the technical issues, I had to push back the task of designing until the very end. And it is a very bad idea to push anything to the very end. Instead, I should have a schedule that assigns equal time to technical and designing tasks. Moreover, if there is a technical problem that is very time consuming, I should stick to the schedule and move on. If I have more time, I can come back to the problem and finish the feature. I also learnt that it is challenging to write code for arduino if I am not familiar with the language of C. But it is also not too bad because there are many references that I can rely on.

Next Step

If I was to build another iteration, I would design a cover for the robot that the speakers and  RFID reader are fixed at the surface. I would use a USB power bank instead of batteries to power the robot so that enough current would be supplied. I would use the design of the “shield” to cover up the components. I would add a LCD and a rotating switch to make an user interface for adjusting time, changing the color of the flashlight, and changing songs.

Technical information

Schematic

Code

/*Sleep Time Reminder
 * Zerui Huo
 * The code makes two motors spin and two speaker vibrate at a preset time until the RFID reader
   detects the desired RFID card.

 *Pin mapping:
  
     pin   | mode   | description
     ------|--------|------------
     2      OUTPUT    The pin that sends pwm signal to the motor driver and controls tire 1
     3      OUTPUT    The pin that sends pwm signal to the motor driver and controls tire 1
     4      OUTPUT    The pin that sends pwm signal to the motor driver and controls tire 2
     5      OUTPUT    The pin that sends pwm signal to the motor driver and controls tire 2
     6      OUTPUT    The pin that is connect to transistor to control speaker 1
     7      OUTPUT    The pin that is connect to transistor to control speaker 2
     9                SDA pin for the RFID reader
     10               RST pin for the RFID reader
     11               MOSI pin for the RFID reader
     12               MISO pin for the RFID reader
     13               SCK pin for the RFID reader
     A4               SDA pin for the real time clock
     A5               SCL pin for the real time clock
     VCC              The pin for power to go into the Arduino pro mini
     


  *The code in the functions set_motor_pwm and set_two_motors_pwm are inspired by the website https://courses.ideate.cmu.edu/16-223/f2016/text/ex/Arduino/DRV8833-motor-driver/DRV8833-motor-driver.html.
   The function getID is from the website https://lastminuteengineers.com/how-rfid-works-rc522-arduino-tutorial/.
   When writing the code to set the time in the real time clock, I refer to https://create.arduino.cc/projecthub/MisterBotBreak/how-to-use-a-real-time-clock-module-ds3231-bc90fe.
   The music score of the song, Il Vento D'Oro (Golden Wind), is from https://musescore.com/winthos/scores/5366398 by Winthos.
   The the pitch values are from https://www.arduino.cc/en/Tutorial/ToneMelody?from=Tutorial.Tone and are written by Brett Hagma.
*/

#include "pitches.h"
#include "SPI.h"
#include "MFRC522.h"
#include <Wire.h>
#include <ds3231.h>

//Naming all the pins to their function
const int motorA1 = 2;
const int motorA2 = 3;
const int motorB1 = 4;
const int motorB2 = 5;
const int speaker1 = 6;
const int speaker2 = 7;

//Define the two pins for the RFID
#define pinSDA 10
#define pinRST 9

//Setting the intervals for the all the timers
unsigned long Motortimer = 0;
unsigned long Speakertimer1 = 0;
unsigned long Speakertimer2 = 0;
unsigned long Clocktimer = 0;
const int Clockwait = 5000;

//Setting up the strings to check for the RFID reader
byte readCard[4];
String Bathroom_ID = "A06EC9B";  //Unique ID for the RFID card that will be placed in bathroom
String Bed_ID = "31BD82FC";  //Unique ID for the RFID card that will be placed onto the bed
String tagID = "";

//"Link" the pins to the RFID's SDA and RST
MFRC522 mfrc522(pinSDA, pinRST);

//For the realtime clock
struct ts t;

//Defining the constants for the speakers to play melody
//notemap for speaker 1 and speaker 2
int note1[] = {NOTE_B3, NOTE_B3, NOTE_B3, NOTE_A3, 0, NOTE_B3, 0, NOTE_D4, 0, NOTE_B3, 0, NOTE_FS3, NOTE_A3,    
               NOTE_B3, NOTE_B3, NOTE_B3, NOTE_A3, 0, NOTE_B3, 0, NOTE_F4, 0, NOTE_E4, 0, NOTE_D4, NOTE_A3,
               NOTE_B3, NOTE_B3, NOTE_B3, NOTE_A3, 0, NOTE_B3, 0, NOTE_D4, 0, NOTE_B3, 0, NOTE_FS3, NOTE_A3,    
               NOTE_B3, NOTE_B3, NOTE_B3, NOTE_A3, NOTE_B3, NOTE_D4, 0, NOTE_F4, 0, NOTE_E4, 0, NOTE_D4, NOTE_A3,
               NOTE_B3, NOTE_B3, NOTE_B3, NOTE_A3, 0, NOTE_B3, 0, NOTE_D4, 0, NOTE_B3, 0, NOTE_FS3, NOTE_A3,    
               NOTE_B3, NOTE_B3, NOTE_B3, NOTE_A3, 0, NOTE_B3, 0, NOTE_F4, 0, NOTE_E4, 0, NOTE_D4, NOTE_A3,
               NOTE_B3, NOTE_B3, NOTE_B3, NOTE_A3, 0, NOTE_B3, 0, NOTE_D4, 0, NOTE_B3, 0, NOTE_FS3, NOTE_A3,    
               NOTE_B3, NOTE_B3, NOTE_B3, 0, NOTE_A3, NOTE_B3,
              };

int note2[] = {NOTE_B1,                                                                                         
               NOTE_B1,
               NOTE_B1,                                                                                         
               NOTE_B1, NOTE_B1,
               NOTE_B2, NOTE_B2, NOTE_B2, NOTE_A2, 0, NOTE_B2, 0, NOTE_D3, 0, NOTE_B2, 0, NOTE_FS2, NOTE_A2,    
               NOTE_B2, NOTE_B2, NOTE_B2, NOTE_A2, 0, NOTE_B2, 0, NOTE_F3, 0, NOTE_E3, 0, NOTE_D3, NOTE_A2,
               NOTE_B2, NOTE_B2, NOTE_B2, NOTE_A2, 0, NOTE_B2, 0, NOTE_D3, 0, NOTE_B2, 0, NOTE_FS2, NOTE_A2,    
               NOTE_B2, NOTE_B2, NOTE_B2, 0, NOTE_A2, NOTE_B2,
              };


//beatmap for each notes for speaker 1 and speaker 2. 8 means eighth note, etc.
int beat1[] =  {8, 8, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 8,    8, 8, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 8,
                8, 8, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 8,    8, 8, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 8,
                8, 8, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 8,    8, 8, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 8,
                8, 8, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 8,    8, 8, 16, 16, 16, 8,
               };
               
int beat2[] =  {1,                                                   1,
                1,                                                   2, 2,
                8, 8, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 8,     8, 8, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 8,
                8, 8, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 8,     8, 8, 16, 16, 16, 8,
               };



//Determining the length of the list of beat and creat empty timemaps for the speakers
const int length_of_beat1 = 155;
const int length_of_beat2 = 1;

int speaker_timemap1[length_of_beat1];
int speaker_timemap2[length_of_beat2];

//Note counter indicate which note the speakers are on. They are set to 0 because array start from 0 and in such way they are more convenient to use latter.
int counter1 = 0;
int counter2 = 0;


//The following are written supplemental functions

//RFID:
//Funtion to access the NUID and put the first 4 bytes (8 digits) into the string "tagID"
boolean getID() {
  //Verify if any card is presented
  if ( ! mfrc522.PICC_IsNewCardPresent()) {
    return false;
  }

  // Verify if the NUID has been readed
  if ( ! mfrc522.PICC_ReadCardSerial()) {
    return false;
  }
  tagID = "";
  //A loop that put the first 4 bytes of the unique ID of the card into the variable tagID
  for ( uint8_t i = 0; i < 4; i++) {
    tagID = tagID + (String(mfrc522.uid.uidByte[i], HEX));
  }
  tagID.toUpperCase();
  mfrc522.PICC_HaltA(); // Tells the RFID readert to stop reading.
  return true;
}

//Motors:
//Function to give direction and speed to one of the motor
void set_motor_pwm(int pwm, int input_1, int input_2) {
  if (pwm < 0) {
    digitalWrite(input_1, -pwm);
    analogWrite(input_2, LOW);
  }
  else {
    digitalWrite(input_1, LOW);
    analogWrite(input_2, pwm);
  }
}

//Function that gives direction and speed to both motor using the previous function
void set_two_motors_pwm(int pwmA, int pwmB) {
  set_motor_pwm(pwmA, motorA1, motorA2);
  set_motor_pwm(pwmB, motorB1, motorB2);
}

//Speakers:

//Funtion that make the speaker play the note according to its notemap and timemap
void play_note(int speaker, int time) {
  //Determine which speaker and therefore which counter to modify
  if (speaker == 6) {
    //The start is a speacial case
    if (counter1 == 0) {
      if (time < speaker_timemap1[counter1]) {
        tone(speaker, note1[counter1]);
      }
      if (time > speaker_timemap1[counter1]) {
        counter1 = counter1 + 1;
      }
    }

    //if time exceeds the last element of the timemap, reset the counter and play the melody from start
    if (time > speaker_timemap1[length_of_beat1 - 1]) {
      counter1 = 0;
      Speakertimer1 = millis();
    }

    //Again, (2000 / beat1[thisbeat - 1]) * 0.3 is the time gap for distinguishing between notes
    if (time > speaker_timemap1[counter1 - 1] + (2000 / beat1[counter1 - 1]) * 0.3 && time < speaker_timemap1[counter1]) {
      tone(speaker, note1[counter1]);
    }

    //When the speaker finish playing the current note
    if (time >  speaker_timemap1[counter1]) {
      counter1 = counter1 + 1;
    }

  }

  if (speaker == 7) {
    //The start is a speacial case
    if (counter2 == 0) {
      if (time < speaker_timemap2[counter2]) {
        tone(speaker, note2[counter2]);
        Serial.println("Speaker 2 starts");
      }
      if (time > speaker_timemap2[counter2]) {
        counter2 = counter2 + 1;
        Serial.println("Speaker 2 counter 2");
      }
    }

    //if time exceeds the last element of the timemap, reset the counter and play the melody from start
    if (time > speaker_timemap2[length_of_beat2 - 1]) {
      counter2 = 0;
      Speakertimer2 = millis();
      Serial.println("Speaker 2 end");
    }

    //Again, (2000 / beat1[thisbeat - 1]) * 0.3 is the time gap for distinguishing between notes
    if (time > speaker_timemap2[counter2 - 1] + (2000 / beat2[counter2 - 1]) * 0.3 && time < speaker_timemap2[counter2]) {
      tone(speaker, note2[counter2]);
       Serial.println("Speaker 2 next note");
    }

    //When the speaker finish playing the current note
    if (time >  speaker_timemap2[counter2]) {
      counter2 = counter2 + 1;
    }

  }
}

//Function that controls what happen when it's time to shower/sleep
void be_annoying() {


  //how long has passed since the start of the cycle
  int diff_motor = millis() - Motortimer;
  int diff_speaker1 = millis() - Speakertimer1;
  int diff_speaker2 = millis() - Speakertimer2;

  //the timemap of actions for motors
  int motor_timemap[] = {3000, 5000, 7000, 10000};


  //motors' sequence of motions
  //Motion1
  if ( diff_motor <=  motor_timemap[0] ) {
    set_two_motors_pwm(random(-225,225),random(-225,225));
  }

  //Motion2
  if ( diff_motor >= motor_timemap[0] && diff_motor <=  motor_timemap[1] ) {
    set_two_motors_pwm(100, 100);
  }

  //Motion3
  if ( diff_motor >= motor_timemap[1]  && diff_motor <= motor_timemap[2] ) {
    set_two_motors_pwm(-200, 200);
  }

   //Motion4
  if ( diff_motor >= motor_timemap[2]  && diff_motor <= motor_timemap[3] ) {
    set_two_motors_pwm(random(-225,225), random(-225,225));
  }


  if ( diff_motor >= motor_timemap[3]) {
    //Serial.println("cycle ends");
    Motortimer = millis();
  }


  //Speaker1's sequence of actions
  play_note(speaker1, diff_speaker1);
  play_note(speaker2, diff_speaker2);
}

void setup() {
  //Setting up the motors' pins
  pinMode(motorA1, OUTPUT);
  pinMode(motorA2, OUTPUT);
  pinMode(motorB1, OUTPUT);
  pinMode(motorB2, OUTPUT);

  //Starting in the coasting mode
  digitalWrite(motorA1, LOW);
  digitalWrite(motorA2, LOW);
  digitalWrite(motorB1, LOW);
  digitalWrite(motorB2, LOW);

  //Setting up the pins for the transistor for the speakers
  pinMode(speaker1, OUTPUT);
  pinMode(speaker2, OUTPUT);

  //Initializing the RFDI
  SPI.begin();
  mfrc522.PCD_Init();

  //Initializing the serial monitor
  Serial.begin(9600);

  //Setting up for the real-time clock
  Wire.begin();
  DS3231_init(DS3231_INTCN);

  //Set up of the t for the real-time clock
  t.hour = 24;
  t.min = 30;
  t.sec = 0;
  t.mday = 30;
  t.mon = 2;
  t.year = 2020;

  DS3231_set(t);


  //loop that generate the timemap for speaker1 that adds up the time of previous beats
  for (int thisbeat = 0; thisbeat < length_of_beat1; thisbeat++) {
    if (thisbeat == 0) {
      speaker_timemap1[0] = 2000 / beat1[0];
    }
    else {
      //This adds up the time for the previous beats. For example, the second note is played from the time of 2000/8 + (2000/8)*0.3 to 2000/8 + (2000/8)*0.3+2000/8. There is a 0.3-note time gap to distinguish between notes.
      speaker_timemap1[thisbeat] = speaker_timemap1[thisbeat - 1] + (2000 / beat1[thisbeat - 1]) * 0.3 + 2000 / beat1[thisbeat];
    }
  }

  //loop that generate the timemap for speaker2 that adds up the time of previous beats
  for (int thisbeat = 0; thisbeat < length_of_beat2; thisbeat++) {
    if (thisbeat == 0) {
      speaker_timemap2[0] = 2000 / beat2[0];
    }
    else {
      //This adds up the time for the previous beats. For example, the second note is played from the time of 2000/8 + (2000/8)*0.3 to 2000/8 + (2000/8)*0.3+2000/8. There is a 0.3-note time gap to distinguish between notes.
      speaker_timemap2[thisbeat] = speaker_timemap2[thisbeat - 1] + (2000 / beat2[thisbeat - 1]) * 0.3 + 2000 / beat2[thisbeat];
    }
  }
}


void loop() {
  //The time to take shower
  if (t.hour == 23 && t.min == 0 && t.sec == 0) {
    Motortimer = millis();
    Speakertimer1 = millis();
    Speakertimer2 = millis();
    while (! getID() && tagID != Bathroom_ID) {
      be_annoying();
    }
    noTone(speaker1);
    noTone(speaker2);
    set_two_motors_pwm(0, 0);
  }


  //The time to sleep
  if (t.hour == 24 && t.min == 30 && t.sec == 0) {
    Motortimer = millis();
    Speakertimer1 = millis();
    Speakertimer2 = millis();
    while (! getID() && tagID != Bed_ID) {
      be_annoying();
    }

    set_two_motors_pwm(0, 0);
    noTone(speaker1);
    noTone(speaker2);
  }



  //Printing the time for debugging purpose
  if ( (millis() - Clocktimer) >= Clockwait ) {

    //displaying the time on the serial monitor
    DS3231_get(&t);
    Serial.print("Date : ");
    Serial.print(t.mon);
    Serial.print("/");
    Serial.print(t.mday);
    Serial.print("/");
    Serial.print(t.year);
    Serial.print("\t Hour : ");
    Serial.print(t.hour);
    Serial.print(":");
    Serial.print(t.min);
    Serial.print(".");
    Serial.println(t.sec);

    Clocktimer = millis();
  }

}
/*************************************************
 * Constants for all the notes
 *************************************************/


#define NOTE_B0  31
#define NOTE_C1  33
#define NOTE_CS1 35
#define NOTE_D1  37
#define NOTE_DS1 39
#define NOTE_E1  41
#define NOTE_F1  44
#define NOTE_FS1 46
#define NOTE_G1  49
#define NOTE_GS1 52
#define NOTE_A1  55
#define NOTE_AS1 58
#define NOTE_B1  62
#define NOTE_C2  65
#define NOTE_CS2 69
#define NOTE_D2  73
#define NOTE_DS2 78
#define NOTE_E2  82
#define NOTE_F2  87
#define NOTE_FS2 93
#define NOTE_G2  98
#define NOTE_GS2 104
#define NOTE_A2  110
#define NOTE_AS2 117
#define NOTE_B2  123
#define NOTE_C3  131
#define NOTE_CS3 139
#define NOTE_D3  147
#define NOTE_DS3 156
#define NOTE_E3  165
#define NOTE_F3  175
#define NOTE_FS3 185
#define NOTE_G3  196
#define NOTE_GS3 208
#define NOTE_A3  220
#define NOTE_AS3 233
#define NOTE_B3  247
#define NOTE_C4  262
#define NOTE_CS4 277
#define NOTE_D4  294
#define NOTE_DS4 311
#define NOTE_E4  330
#define NOTE_F4  349
#define NOTE_FS4 370
#define NOTE_G4  392
#define NOTE_GS4 415
#define NOTE_A4  440
#define NOTE_AS4 466
#define NOTE_B4  494
#define NOTE_C5  523
#define NOTE_CS5 554
#define NOTE_D5  587
#define NOTE_DS5 622
#define NOTE_E5  659
#define NOTE_F5  698
#define NOTE_FS5 740
#define NOTE_G5  784
#define NOTE_GS5 831
#define NOTE_A5  880
#define NOTE_AS5 932
#define NOTE_B5  988
#define NOTE_C6  1047
#define NOTE_CS6 1109
#define NOTE_D6  1175
#define NOTE_DS6 1245
#define NOTE_E6  1319
#define NOTE_F6  1397
#define NOTE_FS6 1480
#define NOTE_G6  1568
#define NOTE_GS6 1661
#define NOTE_A6  1760
#define NOTE_AS6 1865
#define NOTE_B6  1976
#define NOTE_C7  2093
#define NOTE_CS7 2217
#define NOTE_D7  2349
#define NOTE_DS7 2489
#define NOTE_E7  2637
#define NOTE_F7  2794
#define NOTE_FS7 2960
#define NOTE_G7  3136
#define NOTE_GS7 3322
#define NOTE_A7  3520
#define NOTE_AS7 3729
#define NOTE_B7  3951
#define NOTE_C8  4186
#define NOTE_CS8 4435
#define NOTE_D8  4699
#define NOTE_DS8 4978

 

 

]]>
Catch the Clock https://courses.ideate.cmu.edu/60-223/s2020/work/catch-the-clock/ Thu, 19 Mar 2020 15:56:28 +0000 https://courses.ideate.cmu.edu/60-223/s2020/work/?p=9947 [Final Images and Video Not Available – the device is currently in Pittsburgh and I am not available to take a proper image of the final product.]

Overview

This project is an alarm clock that will drive around your room and flicker your lights until you wake up and stop it.

 

Process images and review

I used a smaller screen than I had wanted.  Essentially, I didn’t want a very big boxy alarm clock, since I thought it would look a little ugly aesthetically speaking. This decision was a factor in the next decision I will talk about.

Making the smaller screen functional

I made the UI simpler than I wanted it to be. At first, I wanted the screen to have options for how quick the robot would run around, how quick the lights would flicker, how easy it would be for the robot to dodge obstacles, and then all the setting for time, date, alarm etc. I also originally wanted it to show the seconds. But due to difficulty and space, I decided to go for a much simpler UI. Which looks like the following.

Simpler UI

 

Another decision I had to make early on was how many wheels I wanted and how big. Considering that I had some easy pre-geared wheels I kind of wanted to integrate those into the design to make my life easier. This also meant I had to build around the size of the wheels. Most alarm clocks aren’t that big, which mean’t if I was using those wheels, things were going to have to be squished into to the smallest space possible. I ended up just using two of those pre-geared wheels and adding a small point turn bearing later to conserve space for electronics.

Two wheel with small point turn bearing decision

Here is an image of my initial idea for this project and how I wanted it to turn out.

Original brainstorming

 

Discussion

Response

Where would be the light be located? Also will other lights be hinder the photoresistor? The user interface is easy, with easy buttons and options to choose from, the ultrasonic sensor works great to avoid bumping into other stuff. I would like to see how big the range the movement is. In the demo it stayed pretty much in the same area, I would like to see it run around like in bigger area, so it would make you get up more.

The light would be located anywhere in your room. The idea was that you would be able to purchase a bigger set of NeoPixel lights and hang them anywhere around your room.  Something I could work on is making a better system to trigger the lights. As of now, with no adjustable threshold, the lights would not go off in a very dark room. I would really like to add a magnetic trigger so that this would work in a lot more environments than the current system does.  The device currently has an infinite range, it only looked like it stayed in the same area because I forced it into a circle with my foot.

 

I think the entire idea is really cool and i really appreciate the attention to details that you have p especially in terms of the user interface! I think the entire project is really crazy and I am super happy that you were able to get a lot of your features done!

Thank you so much! It took a lot of time to get some of these features to work, especially the UI. I do think I could have paid more attention to detail if I had paced myself a little better considering our time constraint.

 

Self critique

I am satisfied with how it came out, but not entirely happy with it.  My project basically does what I wanted it to do, but the end game of my process was very rushed. (my fault though) If I had paced myself and spent more time I think I could have solved some issues with some better solutions than the ones I currently have. I also think I could have made it look a lot more aesthetically pleasing if I had given myself more time. In conclusion, I did meet my goals, but not my own expectations.

 

What you learned

What would you do differently next time?

I would have allotted more time to this project. Most of the project was done in under 10 hours on the day before it was due. This gave me very little time to come up with elegant solutions to problems I had. It also gave me little time to manufacture and work on aesthetics.

Did you get hung up at a particular point in progress, or fail to see an easy workaround to a problem?

I think the main issue here is that I thought I had solved the hardest issue of the project and then left rest of the project to a way later date. When that date came, a lot of unexpected issues came up and I only had a couple of hours to fix them. This also led to me to “tunnelvision” and put my brain in a box. I only realized some better solutions after the due date.

What you learned about your own abilities and/or limitations

Given the amount of time I used to produce a working product I think I did a very good job. On the flip side, I am my own limiter. I can only imagine what I would’ve produced if I had used the time I had more wisely.

 

Next steps

Do you expect to build another iteration of this project?

If Zach would give us extra credit to do so, I would not be apposed to building another iteration. Well, if I were to build another iteration, I would implement a magnetic trigger for the lights in the room. This would allow me to create a “home” for the alarm clock as it waits until the next alarm date. I would create an easy way to turn it off/on and a way to charge it in its “home”. I would also try to add a sound to it while the alarm goes off. It currently doesn’t have a sound due to some internal libraries colliding, but I would find a workaround to this. Lastly, I would make it more aesthetically pleasing.

Technical information

Alarm Clock Schematic

Lights Schematic

Alarm Clock Code

/* Catch the Clock (alarm clock code)
 * Estevan Chairez
 * 
 * Description: This code runs all the logic for the alarm clock.
 * It will set up the menu screen and let the user select options,
 * trigger the motors to autonomously navigate its surrounding area
 * if the clock reaches the set alarm time and turn off to stop the alarm.
 * The potentiometer will act as a scroll-knob, the pushbutton as
 * a button to select options and numbers.
 * 
 * Pin mapping:
 * 
 * pin   | mode   | description
 * ------|--------|------------
 * A3     input     potentiometer   
 * 2      input     momentary pushbutton 
 * 4      input     TRIG pin for ultrasonic sensor
 * 6      input     ECHO pin for ultrasonic sensor
 * 3      output    B1IN for motor 1     
 * 5      output    B2IN for motor 1
 * 9      output    A2IN for motor 2
 * 10     output    A1IN for motor 2
 * 
 */
#include <DS1307RTC.h>
#include <Wire.h>
#include <LiquidCrystal_I2C.h>
#include <NewPing.h>

#define POTENT A3
#define BUTTON 2
#define TRIG 4
#define ECHO 6
#define MAX_DISTANCE 200
#define THRESHOLD 40
#define AIN_1 10
#define AIN_2 9
#define BIN_1 3
#define BIN_2 5

///////////////////////////////////
// global variables and objects
///////////////////////////////////

tmElements_t tm;
int alarmTime[2];
int knobPos;
int velocity = 70;
unsigned long minInPico = 60000;

bool clockState = true;
bool dateState = false;
bool timeState = false;
bool alarmState = false;
bool alarmOn = false;
bool hasAlarm = false;
bool reStart = true;

NewPing sonar(TRIG, ECHO, MAX_DISTANCE);
LiquidCrystal_I2C lcd(0x27, 20, 4);


////////////////////////////////////
// clock functions
////////////////////////////////////
void displayDate(int character, int line, tmElements_t tm)
{
  lcd.setCursor(character, line);
  if (tm.Month < 10) lcd.print("0");
  lcd.print(tm.Month);
  lcd.print("/");
  if (tm.Day < 10) lcd.print("0");
  lcd.print(tm.Day);
  lcd.print("/");
  lcd.print((String)(1952 + tm.Year));
}

void displayTime(int character, int line, tmElements_t tm)
{
  String minutes, hours;
  lcd.setCursor(character, line);
  if (tm.Hour < 10) lcd.print("0");
  lcd.print(tm.Hour); lcd.print(":");
  if (tm.Minute < 10) lcd.print("0");
  lcd.print(tm.Minute);
}

// displays the main clock idle screen, which has the 
// options to set time, date and alarm 
void displayClock()
{
  if (RTC.read(tm)) {
    Serial.println(tm.Hour);
    Serial.println(tm.Minute);
    lcd.noCursor();
    displayDate(0, 0, tm);
    displayTime(0, 1, tm);
  }
  lcd.setCursor(10, 1);
  lcd.print("Set Time");
  lcd.setCursor(10, 2);
  lcd.print("Set Date");
  lcd.setCursor(10, 3);
  lcd.print("Set Alarm");
  lcd.cursor();
}

/////////////////////////////////////
// other functions
/////////////////////////////////////

// gives the right range when trying to set days, minutes, hours etc.
int configureNumber(int type)
{
  int num;
  lcd.noCursor();
  while (true)
  {
    lcd.clear();
    lcd.setCursor(4, 1);
    if (type == 1) {
      num = map(analogRead(POTENT), 0, 1023, 0, 24);
      lcd.print("Set Hour: ");
    }
    else if (type == 2) {
      num = map(analogRead(POTENT), 0, 1023, 0, 60);
      lcd.print("Set Minute: ");
    }
    else if (type == 3) {
      num = map(analogRead(POTENT), 0, 1023, 0, 12);
      lcd.print("Set Month: ");
    }
    else if (type == 4) {
      num = map(analogRead(POTENT), 0, 1023, 0, 31);
      lcd.print("Set Day: ");
    }
    else {
      num = map(analogRead(POTENT), 0, 1023, 1980, 2050);
      lcd.print("Set Year: ");
    }
    lcd.print(num);
    delay(200);
    if (digitalRead(BUTTON) == HIGH) break;
  }
  return num;
}

// autonomous control of robot traveling around area once
// alarm is set off
void alarmProtocol()
{
  lcd.noCursor();
  lcd.clear();
  while (true)
  {
    // turn in place
    if (sonar.ping_cm() < THRESHOLD)
    {
      analogWrite(AIN_1, 0);
      analogWrite(AIN_2, velocity);
      analogWrite(BIN_1, velocity);
      analogWrite(BIN_2, 0);
      delay(100);
    }
    // go straight
    else
    {
      analogWrite(AIN_1, 0);
      analogWrite(AIN_2, velocity);
      analogWrite(BIN_1, 0);
      analogWrite(BIN_2, velocity);
      delay(100);
    }
    // turn motors off
    if (digitalRead(BUTTON) == HIGH)
    {
      analogWrite(AIN_1, 0);
      analogWrite(AIN_2, 0);
      analogWrite(BIN_1, 0);
      analogWrite(BIN_2, 0);
      delay(1000);
      break;
    }
  }
  hasAlarm = false;
  clockState = true;
  reStart = true;
}

void setup()
{
  pinMode(POTENT, INPUT);
  pinMode(BUTTON, INPUT);
  pinMode(AIN_1, OUTPUT);
  pinMode(AIN_2, OUTPUT);
  pinMode(BIN_1, OUTPUT);
  pinMode(BIN_2, OUTPUT);
  // set motors off
  analogWrite(AIN_1, 0);
  analogWrite(AIN_2, 0);
  analogWrite(BIN_1, 0);
  analogWrite(BIN_2, 0);

  // default clock in lcd
  lcd.begin(20, 4);
  lcd.backlight();
  displayClock();
  lcd.cursor();
}


void loop()
{
  // default state to go into other menus
  if (clockState)
  {
    // brings you back to default menu
    if (reStart) {
      lcd.clear();
      displayClock();
      reStart = false;
    }
    unsigned long currentMillis = millis();
    if (currentMillis > minInPico) {
      displayClock();
      currentMillis = 0;
    }

    // logic for set time, date, alarm settings
    knobPos = map(analogRead(POTENT), 0, 1023, 1, 4);
    if (knobPos != 4) lcd.setCursor(13, knobPos);
    if (hasAlarm && tm.Hour == alarmTime[0] && tm.Minute == alarmTime[1]) {
      clockState = false;
      alarmOn = true;
    }
    if (knobPos == 1 && digitalRead(BUTTON) == HIGH) {
      lcd.clear();
      clockState = false;
      timeState = true;
    } else if (knobPos == 2 && digitalRead(BUTTON) == HIGH) {
      lcd.clear();
      clockState = false;
      dateState = true;
    } else if (knobPos == 3 && digitalRead(BUTTON) == HIGH) {
      lcd.clear();
      clockState = false;
      alarmState = true;
    }
    delay(200);
  }
  // to set time
  else if (timeState)
  {
    delay(100);
    tm.Hour = configureNumber(1);
    delay(100);
    tm.Minute = configureNumber(2);
    timeState = false;
    clockState = true;
    reStart = true;
    RTC.write(tm);
    delay(100);
  }
  // to set date
  else if (dateState)
  {
    delay(100);
    tm.Month = configureNumber(3);
    delay(100);
    tm.Day = configureNumber(4);
    delay(100);
    tm.Year = configureNumber(5);
    dateState = false;
    clockState = true;
    reStart = true;
    RTC.write(tm);
    delay(100);
  }
  // to set alarm
  else if (alarmState)
  {
    delay(100);
    alarmTime[0] = configureNumber(1);
    delay(100);
    alarmTime[1] = configureNumber(2);
    delay(100);
    alarmState = false;
    clockState = true;
    reStart = true;
    hasAlarm = true;
    RTC.write(tm);
    delay(100);
  }
  // sets robot to run around area
  else if (alarmOn) alarmProtocol();
}

 

 

Lights Code

/* Catch the Clock (lights code)
 * Estevan Chairez
 * 
 * Description: This code runs the logic for the flickering
 * lights portion of the project. If the potentiometer reads
 * a value under a certain threshold, it will flicker the lights
 * and if not, the lights stay off.
 * 
 * Pin mapping:
 * 
 * pin   | mode   | description
 * ------|--------|------------
 * A0     input     potentiometer   
 * 10     output    neopixel strip 
 */
#include <Adafruit_NeoPixel.h>

#define LED 10
#define LED_COUNT 42
#define PHOTO A0
#define THRESH 600

Adafruit_NeoPixel strip = Adafruit_NeoPixel(LED_COUNT, LED, NEO_GRB + NEO_KHZ800);
uint32_t white = strip.Color(255, 255, 255);

void setup() {
  pinMode(PHOTO, INPUT); 

  strip.begin();
  strip.show();
}

void loop(){
  int photoVal =analogRead(PHOTO);

  // flash lights
  if (photoVal < THRESH){
    strip.fill(white);
    strip.show();
    delay(500);
    strip.clear();
    strip.show();
    delay(500);
  }
  // lights off
  else {
    strip.clear();
    strip.show();
  }
}

 

 

]]>
Snooze Alarm https://courses.ideate.cmu.edu/60-223/s2020/work/snooze-alarm/ Thu, 19 Mar 2020 00:14:46 +0000 https://courses.ideate.cmu.edu/60-223/s2020/work/?p=9847 Overview
Description

An interactive alarm clock that reminds you to go to sleep if you are still awake past a certain bedtime by detecting light levels in the room.

A photoresistor senses light levels in the room.

There is a menu system to view alarm settings.

You can edit the alarm as well as the time.

Labeled physical components of the alarm clock.

[Note: detailed photos of the device could not be provided]

Process Images and Review

Planning

I started with brainstorming ideas and sketching how I wanted the alarm clock to function and look like. I envisioned the device to resemble a regular alarm clock in form, but I also wanted to integrate the added functionality of light detection. It was definitely helpful to begin with a solid concept in mind for what I was building.

Initial concept sketches and notes.

Prototyping

The first two pieces I focused on were the LCD Display and the real time clock as these were the two fundamental components to a basic working clock. I was able to get the LCD to display the time data, although the time had to be hard coded in.

Building a basic functional clock.

Real time clock used to measure time data.

I started early on the menu system to eliminate the hard coding issue and to help make alarm/general software testing easier. For user input, I used a potentiometer for entering values along a range and a push button to select options. The code for the menu system ended being one of most complex aspects of this project due to all the different settings with various inputs and screen displays.

Speaker (left) and Potentiometer (right).

In place of the potentiometer and push button, I attempted to use a rotary encoder to make user input easier with continuous rotation and added button functionality. However, I spent way too much time trying to figure out the wiring and code to read inputs from the rotary encoder without much progress. I eventually switched back to my original choice of a separate potentiometer and push button since they were more familiar to me and already integrated into the menu code.

Rotary encoder component.

For the physical prototype, I just made a simple white box to hold the screen and drawn-on components.

Prototype built from cardboard and paper.

Final Development

I measured each of the components to ensure that they would fit appropriately. I sketched the location of each part and how large I wanted the physical box to be, keeping in mind that it had to hold both the Arduino and the breadboard.

Box sketches and measurements.

The clock was modeled in Fusion 360 before being laser cut. This step was also very time consuming especially since I had little experience working with Fusion. I wanted to add finger joints to each side to make the box more structurally sound, but the process of making rectangular cuts on each edge proved to be pretty tedious.

3D box model in Fusion 360

I decided to go with acrylic as my material of choice because I wanted the clock to have a clean, sleek appearance. I chose blue acrylic because since this was a sleep device, I felt that blue was a calming color and also associated with night time. Once the pieces were cut, I realized the box might actually be too small to fit everything inside. It turned out to be a tight fit, so I had to strategically position and cram together all the internal components. In then end, I was able to successfully fit everything inside.

Realizing this is a lot of stuff to fit inside a tiny box.

Tape helped hold together loose pieces, especially while waiting for glue to dry.

Fitting all the internal components inside.

Discussion

Overall, I am pretty happy with how my project turned out. It functions as intended, and the design is similar to how I envisioned, although there is definitely still room for improvement. Reflecting on the challenges I faced, I’m glad I pushed myself to work with new components and software. It was certainly frustrating at times, but these setbacks proved to be good learning opportunities.

While trying to figure out the rotary encoder, I kept getting stuck on the complicated technical aspects. It would have been difficult to integrate since the menu system code was already written in terms of the potentiometer and button inputs. I wish I could have realized sooner it would have been better to stick with my original components given the time constraints I had.

Moving forward, I know to more carefully consider which components I will need and to not make major changes prior to writing a lot of code. The menu code itself was more complicated than anticipated, and it would have been helpful to organize and plan out my code on a conceptual level before writing it out. I realized sometimes it’s okay to quit while you’re ahead. The menu system was already working, so I could have saved a lot of time had I just moved onto the next feature instead of focusing on perfecting this one small detail. For future projects, I’ll keep in mind the importance of prioritizing the big picture rather than getting stuck on one small aspect.

Working with Fusion 360 was another step which challenged me because I did not have much experience with using the software. I did not think I would be able to get the model to look the way I wanted it to, but it actually turned out okay. Making the finger joints was challenging, but it was worth the extra effort to learn how to use Fusion as it’s a useful skill for later projects as well.

In retrospect, I realized the importance of planning ahead, especially when it came to piecing together the final box. A lot of last minute problems arose which I definitely did not anticipate, for instance the internal pieces being too tightly fitted inside. If I had measured more thoughtfully ahead of time, I might have made the box bigger. In addition, the buttons at the top did not have ample support below. I applied some creative problem solving and taped wood pieces below them for base support. Next time, I know to work on my project earlier to give myself ample time to make these mistakes, or better yet, have time to plan ahead and anticipate these problems before they arise in the first place.

During the class crit, some of the feedback I received included:

Interesting idea, and I think it’s quite practical where it could force you to sleep. One thing you could consider is how could you make this more accessible for other people to use? How will people decipher what controls to use?

This is a good point because I left the buttons unlabeled, so it might be confusing for users what the buttons are supposed to do. To make the user interaction more clear, I would probably add labels/symbols or some clear indicator next to the buttons to convey their function.

A choice for sound could be helpful, as I know people who need white noise sensors or soothing sounds to go to sleep.

I agree that having a variety of alarm sounds would improve the clock’s customization. Given more time, I would add a selection of sounds for the user to choose. However for now, I chose a somewhat annoyingly high pitched tone alarm with the intention of both alerting and punishing the user for staying up past their set sleep time.

For future iterations, I would also try to add a distance sensor near the photoresistor to prevent cheating the system by draping something over the clock to make it dark. This added feature would ensure light levels are representative of the surrounding area. 

In the end, I think I did satisfy a lot of my own goals in terms of learning to work with Fusion 360 and getting to build a finished, working alarm clock. I’m pretty satisfied with the appearance, and while it was challenging to work with a smaller box to hold everything, it also allowed for a more compact and sleek outer appearance. I’m also glad I made detailed sketches for my vision of the design prior to building it because it helped to give me a strong visual design to work towards.

Technical Information

Schematic

Code SUbmission
/*
   Project Title: Snooze Alarm

   Description: This code sets off a sound alarm to remind the user the
   user to go to sleep if they are awake past their set bed time.
   A photoresistor reads in brightness levels of the room to
   detect whether the user is asleep or not. A potentiometer and
   momentary push button provide input to change the time displayed 
   and alarm settings on an LCD screen. If the user is still not not
   in bed within 5 minutes of the alarm, a second snooze alarm 
   will be set off. The first alarm can be stopped by pressing 
   a snooze button but not the second snooze alarm.

   Pin mapping:

   pin   | mode   | description
   ------|--------|------------
   A0      input     potentiometer
   A3      input     photocell
    7      input     momentary push button (settings)
    5      input     momentary push button (snooze)
    9      output    sound speaker (alarm)
    12     output    LED (alarm)
    
*/

// include necessary libraries
#include <DS3231.h>
#include <LiquidCrystal_I2C.h>
#include "Volume3.h"

String state = "display time"; // clock menu state
int hours; // hours set for display or alarm time
int minutes; // minutes set for diplay or alarm time

int speakerVol; // volume of alarm 
int maxVol = 800; // speaker max volume
int threshold = 930; // brightness threshold
bool soundAlarm = false; // play alarm sound

bool alarmOn = false; 
bool alarmTime = false; // indicate setting alarm or display time
int alarmHr;
int alarmMin;

bool snooze = false; // whether user has pressed snooze button
int snoozeHr;
int snoozeMin;
int snoozeDelay = 5; // how many minutes to snooze
bool snoozeAlarm = false;

const int POTPIN = A0; // potentiometer
const int PHOTOPIN = A3; // photocell
const int PUSHPIN = 7; // momentary push button for settings
const int PUSHPIN2 = 5; // momentary push button for snooze
const int SPEAKERPIN = 9; // sound speaker
const int LEDPIN = 12; // LED

DS3231 rtc(SDA, SCL); // initialize real time clock
LiquidCrystal_I2C lcd(0x27, 16, 2); // initialize LCD display

// control alarm sound and light behavior
void callAlarm(int brightnessVal) {
  if (brightnessVal < threshold) {
    speakerVol = maxVol;
    digitalWrite(LEDPIN, HIGH);
  }
  else {
    speakerVol = 0;
    digitalWrite(LEDPIN, LOW);
  }
  vol.tone(SPEAKERPIN, 800, speakerVol);
}

void setup() {
  pinMode(POTPIN, INPUT); // potentiometer
  pinMode(PHOTOPIN, INPUT); // photoresistor
  pinMode(PUSHPIN, INPUT); // momentary push button
  pinMode(PUSHPIN2, INPUT); // snooze push button
  pinMode(LEDPIN, OUTPUT); // LED
  pinMode(SPEAKERPIN, OUTPUT); // sound speaker
  Serial.begin(115200);

  // set up LCD screen
  lcd.init();
  lcd.backlight();
  lcd.home();

  // set up real time clock
  rtc.begin();
  rtc.setTime(12, 0, 0); // Set time to 12:00:00 (24hr format)
}

void loop() {
  int potVal = analogRead(POTPIN); //(range 0-1023)
  int brightnessVal = analogRead(PHOTOPIN); //(range 800-1022)
  int pushVal = digitalRead(PUSHPIN); // settings button
  int pushVal2 = digitalRead(PUSHPIN2); // snooze button

  Serial.println(brightnessVal);

  // check whether alarm should be set off
  Time t = rtc.getTime();
  if (snoozeHr == t.hour and snoozeMin == t.min) {
    snoozeAlarm = true;
    snooze = false;
  }
  else if (not snooze and alarmOn and alarmHr == t.hour and alarmMin == t.min) {
    soundAlarm = true;
  }
  else {
    soundAlarm = false;
    snoozeAlarm = false;
    digitalWrite(LEDPIN, LOW);
    vol.tone(SPEAKERPIN, 800, 0);
  }

  // check if snooze button pressed
  if (pushVal2 == HIGH and not snooze and soundAlarm) {
    snooze = true;
  }

  // check if selection button pressed, update to next menu state
  if (pushVal == HIGH) {
    lcd.clear();
    if (state.equals("set minutes")) {
      // setting alarm
      if (alarmTime) {
        // set alarm time to selected hour and min
        alarmHr = hours;
        alarmMin = minutes;
        alarmTime = false;
        
        // set corresponding snooze time (5 min later)
        snoozeHr = alarmHr;
        snoozeMin = alarmMin + snoozeDelay;
        // manage minute overflow
        if (snoozeMin > 59) {
          if (snoozeHr + 1 == 24)
            snoozeHr = 0;
          else
            snoozeHr += 1;
          snoozeMin = snoozeMin - 60;
        }
      }
      // setting display time
      else
        rtc.setTime(hours, minutes, 50); // edit for testing
      // reset snooze settings
      snooze = false;
      state = "display time";
    }
    else if (state.equals("set hours")) {
      state = "set minutes";
    }
    else if (state.equals("display time"))
      state = "settings";
    else if (state.equals("settings")) {
      // check which option selected
      if (potVal < 511)
        state = "set hours";
      else
        state = "edit alarm";
    }
    else if (state.equals("edit alarm")) {
      if (potVal < 511)
        state = "display time";
      else
        state = "set alarm";
    }
    else if (state.equals("set alarm")) {
      if (potVal < 511) {
        alarmOn = true;
        alarmTime = true;
        state = "set hours";
      }
      else {
        alarmOn = false;
        // reset snooze settings
        snooze = false;
        state = "display time";
      }
    }
    // time gap for button unpress
    delay(500);
  }

  // update alarm screen display
  if (state.equals("display time")) {
    lcd.setCursor(0, 1);
    if (soundAlarm and not snooze) {
      // set off alarm
      lcd.print("Time to go sleep!");
      callAlarm(brightnessVal);
    }
    else if (snoozeAlarm) {
      // set off snooze alarm
      lcd.print("PLEASE GET SLEEP");
      callAlarm(brightnessVal);
    }
    else if (snooze) {
      // display time for next alarm
      lcd.print((String) "[snooze " + snoozeHr + ":" + snoozeMin + "]   ");
    }
    else {
      lcd.setCursor(0, 1);
      lcd.print("                 ");
    }
    // display current time
    lcd.setCursor(0, 0);
    lcd.print("Time:  ");
    lcd.print(rtc.getTimeStr());
  }
  else if (state.equals("settings")) {
    // display options and cursor
    lcd.setCursor(0, 0);
    lcd.print("Edit Time");
    lcd.setCursor(0, 1);
    lcd.print("Set Alarm");
    if (potVal < 511) {
      lcd.setCursor(15, 1);
      lcd.print(" ");
      lcd.setCursor(15, 0);
    } else {
      lcd.setCursor(15, 0);
      lcd.print(" ");
      lcd.setCursor(15, 1);
    }
    lcd.print("<");
  }
  else if (state.equals("edit alarm")) {
    // select whether to change or keep alarm time
    lcd.setCursor(0, 0);
    if (alarmOn)
      lcd.print((String)"Alarm: " + alarmHr + ":" + alarmMin);
    else
      lcd.print("Alarm: None");
    lcd.setCursor(0, 1);
    lcd.print("Edit");
    if (potVal < 511) {
      lcd.setCursor(15, 1);
      lcd.print(" ");
      lcd.setCursor(15, 0);
    } else {
      lcd.setCursor(15, 0);
      lcd.print(" ");
      lcd.setCursor(15, 1);
    }
    lcd.print("<");
  }
  else if (state.equals("set alarm")) {
    // select if alarm is turned on or off
    lcd.setCursor(0, 0);
    lcd.print("Alarm On");
    lcd.setCursor(0, 1);
    lcd.print("Alarm Off");
    if (potVal < 511) {
      lcd.setCursor(15, 1);
      lcd.print(" ");
      lcd.setCursor(15, 0);
    } else {
      lcd.setCursor(15, 0);
      lcd.print(" ");
      lcd.setCursor(15, 1);
    }
    lcd.print("<");
  }
  else if (state.equals("set hours")) {
    // update hour being entered
    lcd.setCursor(0, 0);

    // map potVal to be within 0-23 hr range
    hours = constrain(map(potVal, 0, 1023, 0, 30), 0, 23);

    // adjust spacing for 1 vs 2 digit display
    String space = " ";
    if (hours < 10) space = "  ";
    lcd.print((String)"Set Hour:" + space + hours);
  }
  else if (state.equals("set minutes")) {
    // update minutes being entered
    lcd.setCursor(0, 0);

    // map potVal to be within 0-59 min range
    minutes = constrain(map(potVal, 0, 1023, 0, 65), 0, 59);

    // adjust layout spacing for 1 vs 2 digit display
    String space = " ";
    if (minutes < 10) space = "  ";
    lcd.print((String)"Set Minutes:" + space + minutes);
  }
}

 

]]>