Purpose: An attachable light to a water bottle that reminds you to drink water

Ring light (neo pixel) is attached to a Velcro belt and lights up four colors: green, yellow, red, and blue

 

Video of the water bottle initially starting in green state, for the video purpose I changed the time limit to alert you to drink water from 15min to 10 seconds. Then the ring led blinks yellow until a user picks it up and then a blue animation starts until a user sets the bottle back down, then the color changes back to green because you’ve taken a drink of water. 

  ***near the end of the video I said “if you go longer than 10 seconds without drinking water red light flashes”, I meant longer than 20 seconds (twice the amount of time for yellow light) before the red light flashes*** 

Process Images and Review

Throughout the process, one significant choice I made was to use a neo pixel ring instead of soldering a bunch of LEDs in a circle to a protoboard and then using a saw to cut the corners of the protoboard. I got this idea from looking at all the parts in my kit, and seeing a circle ring of LEDs which wasn’t a part in the Arduino kit, but something I could probably learn more about and test it in a reasonable amount of time. This decision definitely saved me a lot of pain and time, and allowed me to learn how to use a neo pixel which was easier than it sounds. To attach it to the belt I initially bent paper clips to make two different slots, painted them black, and hot glued it to the back of the ring. Since the paper clips I was using are made of metal it caused some noise disturbance and potentially unwanted connections which caused to me to end up attaching the back of the ring to the belt with cut up Command Strips which also worked.

Left photo is a rough idea of how I would’ve implemented the ring LED without the neo pixel, and the left is me using the neo pixel and playing around with the different colors and intensities I could program it to.

These are process images for the paper clip attachment I designed for the neo pixel. First I took a paper clip, bent it to the right dimensions, and painted it black. Next I hot glued both clips to either ends of the neo pixel and waited for the glue to cool. This allowed me to slip the neo pixel on and off the Velcro belt which is attached on the water bottle

After deciding the paper clip attachments weren’t the best idea, I took a command strip and cut a piece of it to attach to the water bottle itself and the back of the neo pixel. Then I attached both ends and the neo pixel stuck in place

Another significant choice I made was to hard code all the water capacity angles into my code. This meant I had to gather some data on how far I would have to tilt my water bottle to get it at 90% capacity, 80% capacity, 70% capacity, etc within the dimensions of my water bottle. I found that the bounds between each of the water capacities (especially around 50%-60%) were very similar and often overlapped, which was a bit of a surprise but something I could manage in my code. It took around an hour to collect this data and two minutes to “hard code” so it wasn’t too bad.

The left picture is my “hard code” function that finds capacity level and to the right is the data I gathered

The last significant choice I made was to add a blue light animation that would begin once the water bottle was picked up and end once the bottle was back on a flat surface. I speak more about where this decision came from and the ultimate outcome of this idea in my discussion section of documentation.

Video of blue light animation

Video of water bottle moving with blue light animation playing 

 

Discussion

Before the project was finalized, I had the opportunity to come into class and show my prototype to my classmates to get some constructive feedback on the model. Feedback from classmate (A) was “you know what you could add? An animation with the neo pixel ring that starts as your lifting the bottle and drinking out of it.” Classmate (B) stated “you could de-solder the accelerometer and have it flush with water bottle to make the design cleaner.” I was motivated by both responses and added a function to my code which begins an animation with blue lights once a user picks up the bottle and ends when a bottle is put down on a flat surface. I attempted implementing the second response, but I found it extremely hard to de-solder the accelerometer while taking out pins because the solder cooled as soon as I melted it and there wasn’t much free room between the pins to move my soldering iron around which ultimately resulted in me abandoning this idea.

Abandoning the latter idea wasn’t all too bad, I just hid the “bulky” accelerometer by placing it to the opposite of the neo pixel ring on the belt strap. Overall, this managed to do the trick but one aspect of the project I was still unsatisfied with was how visible the wires were in my project. I couldn’t think of a way around this because the object was made to move. In another iteration of the project I probably would’ve taken the time to learn more about the mini-Arduino since my electrical schematic only involved the Accelerometer, and neo pixel ring (3 analog pins, 1 digital pin, power, and ground). The slimness of the mini-Arduino would’ve allowed me to store the wires in a nice box that I could attach onto the belt strap.

Aside from the visible wires I’m happy that I got to learn how to use a neo pixel ring, how to write functions in Arduino, and got to a semi-functioning project. It’s buggy due to my code, I found that coding if the water bottle was picked up and the angle was constantly changing was harder than I initially thought. In my code, I implemented a function to sense if the angle was changing and it always evaluated to true even when the bottle was on a flat surface for some time. I used this function to control the blue light animation in order to demonstrate that the user was drinking water, and since the function always evaluated to true, the animation was always playing. This was definitely not intentional and I got hung up on trying to fix it with no avail. On the next iteration, finding out how to fix this would probably be the priority because the animation was one feature I really liked about the project and would like to keep it for the future.

 

Technical information

/*  Title: HYDRATION REMINDER 
 *  Owner: Amelia Lopez
 *  60223, project 2
 *  
 *  Description: Code that alerts a user when to drink water 
 *  through the color of a neo pixel (inported library on line #)
 *  green corresponds to a healthy level of water intake every 15 min
 *  yellow reminds a user to drink water based on water capacity for the past 15 min
 *  red alerts a user they haven't been drinking water for more than 30 minutes.
 *  Water capacity is measured through the accelerometer which measures angle. 
 *  The steeper the angle, the less water is in the water bottle. 
 * 
 *  Borrowed heavily from: 
 *  https://how2electronics.com/arduino-tilt-angle-distance-meter/
 *  in order to find angle from accelerometer
 * 
  Pin mapping:

   Arduino pin | type   | description
   ------------|--------|-------------
   A1            input     X-pin Accelerometer
   A2            input     Y-pin Accelerometer
   A3            input     Z-pin Accelerometer
   ~11           output    neo pixel ring (LED)
  (digital PWM~)

*/
#include <PololuLedStrip.h>

// Create an ledStrip object and specify the pin it will use.
PololuLedStrip<11> ledStrip;

// Create a buffer for holding the colors (3 bytes per color).
#define LED_COUNT 12
rgb_color colors[LED_COUNT];
//YELLOW= 25, 25, 0
//GREEN= 0, 20, 0
//RED = 35, 0, 0
//blue = 0, 14, 34

//DECLARING ARDUINO PINS
const int ACCE_X = A1;
const int ACCE_Y = A2;
const int ACCE_Z = A3;

const int RED_PIN = 11;
const int GREEN_PIN = 10;
const int BLUE_PIN = 9;

//declaring variables useful for angle measurement
#define ADC_ref 5 // ADC reference Voltage
#define zero_x 1.799
#define zero_y 1.799
#define zero_z 1.799
#define echoPin 8
#define trigPin 9
#define selectSwitch 1
#define sensitivity_x 0.4
#define sensitivity_y 0.4
#define sensitivity_z 0.4
unsigned int value_x;
unsigned int value_y;
unsigned int value_z;
float xv;
float yv;
float zv;
float currAngle;
float angleY;
float angleZ;
float min_angle;
String colorName;

//DECLARING INTS FOR LCD WAITING TIMES
const int WAIT_TIME = 1000;
unsigned long timeVariable = 0;

//DECLARING INTS FOR WATER LEVEL
const int WATER_WAIT_TIME = 15000;
const int TOLERANCE = 2;
const int angleFlat = 230;
int water_capacity = 100; // unit: %
int prev_capacity = 100; //unit: %
unsigned long waterTimeVariable = 0;
unsigned long yellowTimer = 0;
float lastMinAngle;
bool isDrinkining = false;
bool yellowLightState = LOW;

void setup() {
  analogReference(ADC_ref);

  //INPUT PINS
  pinMode(ACCE_X, INPUT);
  pinMode(ACCE_Y, INPUT);
  pinMode(ACCE_Z, INPUT);
}

void loop() {
  setGreen();
  //set color to yellow to remind user to drink
  if (millis() - waterTimeVariable > WATER_WAIT_TIME) {
    //SET COLOR TO BLINKING YELLOW UNTIL ANGLE CHANGES
    prev_capacity = calculateCapacity(prev_capacity, calculateAngle());
    currAngle = calculateAngle();
    while (angleFlat - TOLERANCE <= currAngle)//blink yellow; reminder to drink
    {
      setYellow();
      yellowTimer = millis();
      yellowLightState == HIGH;
      currAngle = calculateAngle();
      delay(500);
      setOff();
      delay(500); 
      
      if (millis() - yellowTimer >= 5000 and yellowLightState == HIGH) {
        yellowLightState = LOW;
        setOff();
        yellowTimer = millis();
      }
      currAngle = calculateAngle();
      if (millis() - yellowTimer >= 5000 and yellowLightState == LOW) {
        yellowLightState = HIGH;
        setYellow();
        yellowTimer = millis();
      }
      
      currAngle = calculateAngle();
      if (millis() - waterTimeVariable > 2 * WATER_WAIT_TIME) //set LED red; missed a drinking cycle
      {
        setRed();
      }
    }
    while (angleIsChanging()) {
      blueAnimation();
      water_capacity = calculateCapacity(prev_capacity, calculateAngle());
      angleIsChanging();
    }
    setGreen();
    
    //check that the new capacity is lower than the previous. If not, light continues to blink yellow
    while (water_capacity == prev_capacity) {
      setYellow();
      yellowTimer = millis();
      yellowLightState == HIGH;
      currAngle = calculateAngle();
      if (millis() - yellowTimer >= 500 and yellowLightState == HIGH) {
        yellowLightState = LOW;
        setOff();
        yellowTimer = millis();
      }
      water_capacity = calculateCapacity(prev_capacity, calculateAngle());
      if (millis() - yellowTimer >= 500 and yellowLightState == LOW) {
        yellowLightState = HIGH;
        setYellow();
        yellowTimer = millis();
      }
      water_capacity = calculateCapacity(prev_capacity, calculateAngle());
    }
    waterTimeVariable = millis();
    
  }

  //taken a sip
  setGreen();

  //while angle is changing, display the blue animation
  while (angleIsChanging()) {
    blueAnimation();
    water_capacity = calculateCapacity(prev_capacity, calculateAngle());
    angleIsChanging();
  }
  timeVariable = millis();
}


int calculateAngle() {
  //Taking in Accelerometer readings and converting it into an angle
  value_x = analogRead(ACCE_X);
  value_y = analogRead(ACCE_Y);
  value_z = analogRead(ACCE_Z);
  xv = (value_x / 1024.0 * ADC_ref - zero_x) / sensitivity_x;
  yv = (value_y / 1024.0 * ADC_ref - zero_y) / sensitivity_y;
  zv = (value_z / 1024.0 * ADC_ref - zero_z) / sensitivity_z;
  currAngle = atan2(-yv, -zv) * 57.2957795 + 180; //x angle
  angleY = atan2(-xv, -zv) * 57.2957795 + 180; //y angle
  angleZ = atan2(-yv, -xv) * 57.2957795 + 180; //z angle
  return currAngle;
}

int calculateCapacity(int prev_capacity, float min_angle) {
  if (247 < min_angle) return 100;
  if (225 < min_angle) return 90;
  if (208 < min_angle) return 80;
  if (204 < min_angle) return 70;
  if (203 < min_angle) return 60;
  if (200 < min_angle && prev_capacity == 60) return 50;
  if (200 < min_angle) return 40;
  if (198 < min_angle) return 30;
  if (196 < min_angle) return 20;
  if (193 < min_angle) return 10;
  return 0;
}

//set all pixels to yellow
void setYellow() {
  for (uint16_t i = 0; i < LED_COUNT; i++)
  {
    colors[i] = rgb_color(25, 25, 0); //yel
  }
  ledStrip.write(colors, LED_COUNT);
}

//set all pixels off
void setOff() {
  for (uint16_t i = 0; i < LED_COUNT; i++)
  {
    colors[i] = rgb_color(0, 0, 0); //yel
  }
  ledStrip.write(colors, LED_COUNT);
}

//set all pixels to red
void setRed() {
  for (uint16_t i = 0; i < LED_COUNT; i++)
  {
    colors[i] = rgb_color(35, 0, 0);
  }
  ledStrip.write(colors, LED_COUNT);
}

//set all pixels to green
void setGreen() {
  for (uint16_t i = 0; i < LED_COUNT; i++)
  {
    colors[i] = rgb_color(0, 20, 0);
  }
  ledStrip.write(colors, LED_COUNT);
}

//cascade of blue lights turning on
void animationUp() {
  const int startingPoint = 4;
  for (int i = 0; i < (LED_COUNT / 2) + 1; i++)
  {
    colors[(startingPoint - i) % LED_COUNT] = rgb_color(0, 14, 34);
    colors[startingPoint + i] = rgb_color(0, 14, 34);
    if ((startingPoint - i) % LED_COUNT == -1) {
      colors[LED_COUNT - 1] = rgb_color(0, 14, 34);
    }
    ledStrip.write(colors, LED_COUNT);
    delay(150);
  }
}

//cascade of blue lights turning off
void animationDown() {
  const int startingPoint = 4;
  for (int i = LED_COUNT / 2; i >= 0; i--)
  {
    colors[(startingPoint - i) % LED_COUNT] = rgb_color(0, 0, 0);
    colors[startingPoint + i] = rgb_color(0, 0, 0);
    if ((startingPoint - i) % LED_COUNT == -1) {
      colors[LED_COUNT - 1] = rgb_color(0, 0, 0);
    }
    ledStrip.write(colors, LED_COUNT);
    delay(150);
  }
}

//determine if angle is changing 
bool angleIsChanging() {
  return angleFlat - TOLERANCE > calculateAngle();
  
}

//starts the blue animation 
void blueAnimation() {
  animationUp();
  delay(100); //maybe 50ms instead
  animationDown();
  delay(100);
}

//blinks yellow light 
void blinkYellow(){
  setYellow();
  delay(200);
  setOff();
  delay(200); 
}