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.
Overall Photo
Detail Images
Functioning Video
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.
-
- 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.
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.
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
One of the challenges that comes with the design process I discovered as I worked up until the deadline is finding a stopping point. This project taught me that the design process truly never ends as designs can always be innovated upon. The final critique opened the floor for new ideas and suggestions to improve the Hydro Task. One suggestion I received during the critique was “Just tinker some more with the design/prototype. Perhaps a sleeve/sack that encases the whole bottle? Should give you enough real estate for all of the necessary components instead of the wrap-around.” I had not rethought the layout of Hydro Task’s design since the prototype. From my initial sketches, I imagined the Hydro Task to be a device that wraps all the way around the water bottle with an adjustable band. It would be interesting to experiment with the layout and see if I could make the Hydro Task smaller and more compact. The current layout was created specifically for the bottle it is demonstrated on but in the future an improved design can allow it to be compatible with bottles of other sizes. I also agree with the feedback that adding a sleeve would have given the product a more finished look. Adding a sleeve was my original intention but due to other unforeseen challenges I did not have time. I attempted to duct tape a sleeve with fabric and cut out holes for the buttons and screens but it looked worse. Another challenge with my make-shift sleeve is it was disturbing the wiring, causing some wires to detach. In the future I would like to sew a sleeve that can be attached with velcro so it can be removed in case some wires get messed up. The ergonomics were not my priority and as a result they fell short of my original expectations. My attempts to improve the design was an after thought because the product’s function and features took priority. However, I think with some experimentation of the layout and adding a well designed sleeve the overall Hydro Task can be improved immensely.
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.
The Hydro Task is just the beginning as there is so much potential for improving this product. Another piece of feedback I received was to add an on and off switch for the battery pack. I think a switch would improve the functionality of the device and make it more user friendly. When I attached the wires from the battery pack to the Teensy the connection was fairly loose and caused some complications with using the device, especially as I went to move the water bottle. Adding a switch and soldering these connections would have addressed this problem as well. In addition to a switch, I would also like to add a feature that allows the user to set their own water drinking goal. Another idea to make the Hydro Task more personalized would be having the user enter in information about themselves, such as height and weight, and based on that information the device determines their water drinking goal. This feature would make the Hydro Task more personalized to the user’s needs. Overall, feedback and critique inspired more ideas and innovation for the Hydro Task.
Technical
-
Block Diagram
-
Electrical Schematic
-
Code
/* Hydro Task 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> #include <Adafruit_GFX.h> #include <Adafruit_SSD1306.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) Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET); 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 value_x = analogRead(A9); int value_y = analogRead(A8); int value_z = analogRead(A7); 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 value_x2 = analogRead(A9); int value_y2 = analogRead(A8); 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 */ if (digitalRead(3) == LOW) { //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. */ if (digitalRead(4) == HIGH){ 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 if (digitalRead(4) == LOW){ 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(); }