Hydro Task is a device that tracks how much water the user drinks throughout the day with the purpose of encouraging the user to meet their hydration goals.

## Detail Images

Reset button that allows the user to reset the device once a day

Accelerometer used to detect the position of the water bottle

Battery pack used as a power source for the Hydro Task

The OLED display shows whether the device is on or off meaning whether the user is drinking or not. This feature is controlled by the user so they can indicate to the device to record how much water is being consumed. The OLED display also shows the user’s goal and how much water they’ve drank today.

## Processing Images

#### 1. Determining how to measure the volume of water the user drinks throughout the day

• Once I decided to use the accelerometer to determine the position of the water bottle, I had to decided how I would use the raw values read by accelerometer to determine the position. I also had to decide how to use the bottle’s position to determine how much water the user was drinking.

My original idea was to calculate the position angle using code I borrowed from online and use those values to determine the acceleration due to gravity at that angle. Using this information along with duration the user had the water bottle tilted, I would calculate the volume by using a volumetric flow rate equation. However, after receiving feedback from the professor I understood that volumetric flow rate would not give an accurate volume measurement. I also did not understand the code I was borrowing from online so I decided to derive the equations used to calculate the water bottle’s position myself.

• The video below demonstrates me experimenting with the different position values reported by the accelerometer using the equation I derived. I derived the equation using trigonometry. Although I could not get the position values to reflect the position angle by ranging from 0-180 degrees, the values reported still accurately reflected the object’s position.

This photo shows the experiment I conducted to determine how much water was displaced at each position the water bottle can be held at. I decided to use this experimentation method when I understood that the volumetric flow rate calculation would not work.

This is one of the tables I made during a trial I conducted of the position-volume experiment.

#### 2. Deciding to use a reset button instead of a real time clock to reset the device once a day

• My original intention was to use a RTC(real time clock) to reset the device everyday when the clock reached 12 at night. However, I ran into some challenges when programming the RTC and had to move forward with using a reset button instead.

This is the RTC I attempted to use

This is the code I borrowed from the website https://www.pjrc.com/teensy/td_libs_DS1307RTC.html in attempt to set up the RTC

This is what would happen every time I uncommented the code for the RTC. Whenever I tried to run the RTC code along with the rest of my code the device would not work. The OLED display would only show the dailyVol value(0 mL) and would not update when I tilted the device.

Since the RTC was presenting many challenges I decided to pivot and use a reset button instead. This video shows how the reset button works. When the user clicks the button the dailyVol is reset to 0. Also if the user drank more than 3000 mL of water than the streak count goes up by 1. When the reset button is read as HIGH, the string “reset:on” is displayed on the screen. Once the user resets the device they must click the button again. The device will only measure water when the reset button is read as LOW, this is indicated to the user when the screen displays the string “reset: off.”

## Discussion

The main challenge I faced was determining how to measure the volume of water the user is drinks throughout the day. I tinkered with different methods including a load cell sensor. The challenge with using a load cell was it would not work with the design I imagined for the Hydro Task since I wanted it to wrap around the bottle. Therefore, I decided to use an accelerometer after learning it could be used to detect the position angle of an object. I experimented with the accelerometer in order to understand how I could use its raw values to measure the amount of water being displaced. I decided to do an experiment where I measured how much water was poured out of the water bottle at each given position. During the final critique one person commented “I really like the way you experimented with a bunch of different angles to get the rate of flow of the water!” At first I was apprehensive about using an experimental method to determine the values because I was afraid it would compromise the device’s accuracy. However, in the end I think it was the correct direction to go in for this project. After the prototype, I redid the experiment with a graduated cylinder that gave more precise measurements. I also conducted multiple trials of the experiment to ensure accuracy and was satisfied with the results of the measuring method.

## Technical

• ### Code

```/*

Nicole Monaco

This code controls the inputs and outputs that come tohgether to make up the hydro task. This includes an accelerometer, used to understand the position of the water bottle, two buttons, that the user interacts with to control the device, and an OLED display, that allows the user to track their progress throughout the day. One button is controlled by the user to indicate to the device whether or not the user is drinking from the bottle. When the button value is read as HIGH the OLED display indicates that device is "on" and the device maps the position read by the acceloromter to how much water is being displaced. How much water that is displaced is then added to the value "dailyVol" which represents how much water the user has drank that day. Everytime "dailyVol" is updated, the OLED display also updates its screen with the new value. When the dailyVol is equal to the user's hydration goal, 3000 mL, the OLED display shows a congratulation message to celebrate that the user met their drinking goal. The second button is programmed to allow the user to reset the device once a day. When the second button is read as "HIGH", "reset: on" is displayed on the top right of the OLED display and dailyVol is reset to 0. If the dailyVol was greater than or equal to 3000 mL, the desired goal, then 1 is added to the user's streak count. The streak count updates every day the user meets their goal, but is reset to 0 if they do not meet their goal.

Pin mapping:
Arduino pin | role   | description
------------|--------|-------------
A9            input     accelorometer (x direction)
A8            input     accelorometer (y direction)
A7            input     accelorometer (z direction)
3             input     button (Pressed by the user when they are drinking water)
4             input     reset button (Pressed by the user once a day to reset the device

Sources:
-Used example code from the libraries <Adafruit_GFX.h> and <Adafruit_SSD1306.h> to set up the OLED display and for the animation function

*/
//libraries
#include <Wire.h>
#include <Arduino.h>
#include <SPI.h>
#include <Wire.h>

int dailyVol = 0; //dailyVol is a variable used to track the amount of water the user drinks throughout the day and is initially set to 0
int lastangle = 778; /*"lastangle" is a value used to represent the last value read by the accelorometer. The reason this value is stored
is because the readings from the accelorometer vary even if the object is standing still, so the last value read is recorded to dtermine if
the change is signigficant enough to indicate movement. The reason it is initially set to 778 is because that is the value read by the accelerometer
when it is standing up straight in a still position.
*/

int streak = 0; //Streak is a value that tracks how many days in a row the user met their hydration goal.
int Goal = 3000; //Goal is a value that represents how much water the user should aspire to drink in a day.
bool congratsmode = false; /*congratsmode is a boolean variable that ensures that congrats mode is only activated once a day. It is initally set the
false and then once the dailyVol is equal to 3000 it is set to true. When it is set to true congratsmode can no longer be activated so the congratulation
screen will only be displayed once. When the user clicks the reset button, congratsmode is reset to false so it can be activated again if the user meets their
goal.
*/

//unsigned long animationTimer = 0;

//Buttons
const int buttonPin = 3; //Button used to indicate whether or not the user is drinking water to ensure values aren't being recorded when the user isn't drinking
const int resetbutton = 4; //Button to reset the device once a day

//Oled Display setup code

#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64

// Declaration for an SSD1306 display connected to I2C (SDA, SCL pins)
#define OLED_RESET     4 // Reset pin # (or -1 if sharing Arduino reset pin)

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

//button setup
pinMode(buttonPin, INPUT);

//Oled Display setup
if (!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) { // Address 0x3D for 128x64
Serial.println(F("SSD1306 allocation failed"));
for (;;); // Don't proceed, loop forever
}
display.display();
delay(2000); // Pause for 2 seconds
display.clearDisplay();

//animation code for congrats mode setup
#define NUMFLAKES     10 // Number of snowflakes in the animation example
#define LOGO_HEIGHT   16
#define LOGO_WIDTH    16

static const unsigned char PROGMEM logo_bmp[] =
{ B00000000, B11000000,
B00000001, B11000000,
B00000001, B11000000,
B00000011, B11100000,
B11110011, B11100000,
B11111110, B11111000,
B01111110, B11111111,
B00110011, B10011111,
B00011111, B11111100,
B00001101, B01110000,
B00011011, B10100000,
B00111111, B11100000,
B00111111, B11110000,
B01111100, B11110000,
B01110000, B01110000,
B00000000, B00110000
};
//OLed Display Text Setup
display.setTextSize(1); // Draw 2X-scale text
display.setTextColor(SSD1306_WHITE);
display.setCursor(0, 55);
display.println("Streak:"); //Displays the string "Streak:" on the screen
display.display();
delay(100);
display.setTextSize(1); // Draw 2X-scale text
display.setTextColor(SSD1306_WHITE);
display.setCursor(45, 55);
display.println(String(streak)); //Displays the value stored in the variable streak on the screen
display.display();
delay(100);
display.setTextSize(2); // Draw 2X-scale text
display.setTextColor(SSD1306_WHITE);
display.setCursor(0, 15);
display.println(F("Goal:3000")); //Displays a string indicating the user's water drinking goal
display.display();
delay(100);
display.setTextSize(2); // Draw 2X-scale text
display.setTextColor(SSD1306_WHITE);
display.setCursor(0, 35);
display.println(F("Today:")); //Displays a string "Today:" that indicates how much water the user has drank today
display.display();
delay(100);
}
void loop() {

//Displays the value stored in the variable dailyVol and updates the screen everytime the value dailyVol is updated
display.setTextSize(2); // Draw 2X-scale text
display.setTextColor(SSD1306_WHITE);
display.setCursor(70, 35);
display.println(String(dailyVol));
display.display();
delay(100);

/*This look up table maps each position recorded by the accelorometer to how much value was displaced at that given value.
The values recorded from the accelorometer ranged from 788 to 636. When the code understands that the user is drinking water
it uses the position values read by the accelorometer to look up how much water was displaced.
*/
int array1[160][2] = {
{788, 0},
{787, 0},
{786, 0},
{785, 0},
{784, 0},
{783, 0},
{782, 0},
{781, 0},
{780, 0},
{779, 1},
{778, 1},
{777, 1},
{776, 2},
{775, 2},
{774, 2},
{773, 3},
{772, 3},
{771, 3},
{770, 3},
{769, 4},
{768, 4},
{767, 4},
{766, 4},
{765, 5},
{764, 5},
{763, 5},
{762, 5},
{761, 5},
{760, 5},
{759, 5},
{758, 5},
{757, 5},
{756, 5},
{755, 5},
{754, 7},
{753, 7},
{752, 7},
{751, 7},
{750, 8},
{749, 8},
{748, 8},
{747, 8},
{746, 8},
{745, 9},
{744, 9},
{743, 9},
{742, 9},
{741, 9},
{740, 9},
{739, 10},
{739, 11},
{738, 11},
{737, 11},
{736, 11},
{735, 13},
{734, 13},
{733, 13},
{732, 13},
{731, 13},
{730, 13},
{729, 13},
{728, 13},
{727, 14},
{726, 14},
{725, 14},
{724, 14},
{723, 14},
{722, 14},
{721, 14},
{720, 15},
{719, 15},
{718, 15},
{717, 15},
{716, 15},
{715, 16},
{714, 16},
{713, 16},
{712, 16},
{711, 16},
{710, 16},
{709, 16},
{708, 16},
{707, 17},
{706, 17},
{705, 17},
{704, 17},
{703, 17},
{702, 18},
{701, 19},
{700, 19},
{699, 19},
{698, 20},
{697, 20},
{696, 20},
{695, 20},
{694, 20},
{692, 20},
{691, 20},
{690, 19},
{689, 19},
{688, 15},
{687, 8},
{686, 4},
{685, 4},
{684, 4},
{683, 3},
{682, 3},
{681, 2},
{680, 2},
{679, 2},
{678, 2},
{677, 2},
{676, 2},
{675, 2},
{674, 2},
{673, 1},
{672, 1},
{671, 0},
{670, 0},
{669, 0},
{668, 0},
{667, 0},
{666, 0},
{665, 0},
{664, 0},
{663, 0},
{662, 0},
{661, 0},
{660, 0},
{659, 0},
{658, 0},
{657, 0},
{656, 0},
{655, 0},
{654, 0},
{653, 0},
{652, 0},
{651, 0},
{650, 0},
{649, 0},
{648, 0},
{647, 0},
{646, 0},
{645, 0},
{644, 0},
{643, 0},
{642, 0},
{641, 0},
{640, 0},
{639, 0},
{638, 0},
{637, 0},
{636, 0},
{635, 0},
{634, 0},
{633, 0},
};

//These pins record the values read by the accelorometer along its x, y, and z axes.
//To perform the calculations to determine the bottle's position only the x and y values are used.

int angle1 = sqrt(sq(value_y) + sq(value_x)); //Trig was used to determine the initial position of the waterbottle.

delay(500); /*A delay is used to assist the device in determining if the change in values read is significant enough to
determine if there was a change in water bottle's position.
*/

//the device records the same values again after 500 miliscond delay to calculate the water bottle's updates position.
int angle2 = sqrt(sq(value_y2) + sq(value_x2));

if (digitalRead(3) == HIGH){ //if the button is on then the device will record how much water is being displaced
//Updates display to show the string "on" to indicate that the device is recording if the user is drinking water
display.drawRect(0, 0, 30, 10, BLACK);
display.fillRect(0, 0, 30, 10, BLACK);
display.setTextColor(WHITE, BLACK);
display.setTextSize(1);
display.setTextColor(SSD1306_WHITE);
display.setCursor(0, 0);
display.println(F("on"));
display.display();
delay(100);
if(angle1 - angle2 > 5){ //if the change of value is greater than 5 then the water bottle must be changing its position and the values are not varying due to chance
int index1 = map(angle2, 788, 684, 0, 155); //maps the final position value, angle2, to an index so it can be used to search the lookup table, there are 155 rows in the table
int index2 = map(lastangle, 788, 684, 0, 155); //maps the postion stored in the value lastangle to and index
int i = 0; //variable i is set to 0
/*
if for some reason the position recorded is outside the range of the lookup table, this could potentially map the index as a negative value and cause erros in the code.
Therefore this conditional prevents that error from occuring.
*/
if(index2 < 0){
index2 = 0;
}
/*This for loop loops through all the positions from the last drinking position recorded to the new drinking position and adds each displaced value between them.
The prupose of the for loop is to total all of the water displaced since the last recorded value.
*/
for(i=index2; i < index1; i = i + 1) {
dailyVol = dailyVol + array1[i][1];
}
//Updates screen everytime the dailyVol is updated
display.drawRect(70, 35, 50, 30, BLACK);
display.fillRect(70, 35, 50, 30, BLACK);
display.setTextColor(WHITE, BLACK);
display.setTextSize(2); // Draw 2X-scale text
display.setTextColor(SSD1306_WHITE);
display.setCursor(70, 35);
display.println(String(dailyVol));
display.display();
delay(100);
lastangle = angle2; //sets the varaible lastangle to the new angle position recorded
}
}

/*
When the button value is read as LOW the display screen updates to read "off" in the top left corner
*/
//display for when the button is off(not being pressed)
display.drawRect(0, 0, 30, 10, BLACK);
display.fillRect(0, 0, 30, 10, BLACK);
display.setTextColor(WHITE, BLACK);
display.setTextSize(1);
display.setTextColor(SSD1306_WHITE);
display.setCursor(0, 0);
display.println(F("off"));
display.display();
delay(100);
}

static const unsigned char PROGMEM logo_bmp[] =
{ B00000000, B11000000,
B00000001, B11000000,
B00000001, B11000000,
B00000011, B11100000,
B11110011, B11100000,
B11111110, B11111000,
B01111110, B11111111,
B00110011, B10011111,
B00011111, B11111100,
B00001101, B01110000,
B00011011, B10100000,
B00111111, B11100000,
B00111111, B11110000,
B01111100, B11110000,
B01110000, B01110000,
B00000000, B00110000
};
/*
If the user reaches the hydration goal, if dailyVol is greater than or equal to 3000, then "congrats mode" is activated. "Congrats mode" refers to an animation
that is displayed on the screen to celebrate the user's acheievement.
*/
if (dailyVol >= Goal) {
if (congratsmode == false) { //Only goes into congrats mode once a day
testanimate(logo_bmp, LOGO_WIDTH, LOGO_HEIGHT); //testanaimate is a helper function
congratsmode =  true; //congratsmode is changed to true so the animation only runs once a day, congratsmode reset to false when the reset button is pressed
//resets the display
display.setTextSize(1); // Draw 2X-scale text
display.setTextColor(SSD1306_WHITE);
display.setCursor(0, 55);
display.println("Streak:");
display.display();
delay(100);
display.setTextSize(1); // Draw 2X-scale text
display.setTextColor(SSD1306_WHITE);
display.setCursor(45, 55);
display.println(String(streak));
display.display();
delay(100);
display.setTextSize(2); // Draw 2X-scale text
display.setTextColor(SSD1306_WHITE);
display.setCursor(0, 15);
display.println(F("Goal:3000"));
display.display();
delay(100);
display.setTextSize(2); // Draw 2X-scale text
display.setTextColor(SSD1306_WHITE);
display.setCursor(0, 35);
display.println(F("Today:"));
display.display();
delay(100);
display.setTextSize(2); // Draw 2X-scale text
display.setTextColor(SSD1306_WHITE);
display.setCursor(70, 35);
display.println(String(dailyVol));
display.display();
delay(100);
}
}
/*
When the reset button is pressed, read as HIGH, congratsmode is reset to false and dailyVol is reset to 0.
Also, if the dailyVol is greater than or equal to 3000 one is added to the streak.
*/
congratsmode = false; //resets so congrats mode can be activated again
//The screen is updated to indicate to the user that the reset button is on
display.drawRect(70, 0, 60, 10, BLACK);
display.fillRect(70, 0, 60, 10, BLACK);
display.setTextSize(1); // Draw 2X-scale text
display.setTextColor(SSD1306_WHITE);
display.setCursor(70, 0);
display.println("reset:on");
display.display();
delay(100);
//if the dailyVol is greater than or equal to 3000 and then 1 is added to the streak
if (dailyVol >= 3000){
streak = streak + 1;
display.drawRect(45, 55, 50, 10, BLACK);
display.fillRect(45, 55, 50, 10, BLACK);
display.setTextSize(1); // Draw 2X-scale text
display.setTextColor(SSD1306_WHITE);
display.setCursor(45, 55);
display.println(String(streak));
display.display();
delay(100);
}
//if the dailyVol is not greater than or equal to 3000 and then the streak is reset to zero
else {
streak = 0
display.drawRect(45, 55, 50, 10, BLACK);
display.fillRect(45, 55, 50, 10, BLACK);
display.setTextSize(1); // Draw 2X-scale text
display.setTextColor(SSD1306_WHITE);
display.setCursor(45, 55);
display.println(String(streak));
display.display();
delay(100);
}
//dailyVol is reset to zero and the screen is updated with the new value for dailyVol
dailyVol = 0;
display.drawRect(70, 35, 50, 30, BLACK);
display.fillRect(70, 35, 50, 30, BLACK);
display.setTextColor(WHITE, BLACK);
display.setTextSize(2); // Draw 2X-scale text
display.setTextColor(SSD1306_WHITE);
display.setCursor(70, 35);
display.println(String(dailyVol));
display.display();
delay(100);
}
//if the reset button is read as LOW the screen is updated to indicate that the reset button is off
display.drawRect(70, 0, 50, 10, BLACK);
display.fillRect(70, 0, 50, 10, BLACK);
display.setTextSize(1); // Draw 2X-scale text
display.setTextColor(SSD1306_WHITE);
display.setCursor(70, 0);
display.println("reset:off");
display.display();
delay(100);
}
}

//helper function that creates the animiation used in congratsmode

#define XPOS   0 // Indexes into the 'icons' array in function below
#define YPOS   1
#define DELTAY 2
//need to reset screen after animation
void testanimate(const uint8_t *bitmap, uint8_t w, uint8_t h) {
int8_t f, icons[NUMFLAKES][3];
// Initialize 'snowflake' positions
for (f = 0; f < NUMFLAKES; f++) {
icons[f][XPOS]   = random(1 - LOGO_WIDTH, display.width());
icons[f][YPOS]   = -LOGO_HEIGHT;
icons[f][DELTAY] = random(1, 6);
Serial.print(F("x: "));
Serial.print(icons[f][XPOS], DEC);
Serial.print(F(" y: "));
Serial.print(icons[f][YPOS], DEC);
Serial.print(F(" dy: "));
Serial.println(icons[f][DELTAY], DEC);
}
int i = 0;
//for loop statement allows the loop to run 60 times
for (i = 0; i < 60; i = i + 1) {
display.clearDisplay();
display.setTextSize(2); // Draw 2X-scale text
display.setTextColor(SSD1306_WHITE);
display.setCursor(15, 20);
display.println("Congrats"); //Displays the string "Congrats" on the screen in addition to the animiation of the snow flakes
display.display();
// Draw each snowflake:
for (f = 0; f < NUMFLAKES; f++) {
display.drawBitmap(icons[f][XPOS], icons[f][YPOS], bitmap, w, h, SSD1306_WHITE);
}
display.display();
delay(200);
// Then update coordinates of each flake...
for (f = 0; f < NUMFLAKES; f++) {
icons[f][YPOS] += icons[f][DELTAY];
// If snowflake is off the bottom of the screen...
if (icons[f][YPOS] >= display.height()) {
// Reinitialize to a random position, just off the top
icons[f][XPOS]   = random(1 - LOGO_WIDTH, display.width());
icons[f][YPOS]   = -LOGO_HEIGHT;
icons[f][DELTAY] = random(1, 6);
}
}
}
display.clearDisplay();
}```