Project tutorial
Arduino-Powered Water Bottle

Arduino-Powered Water Bottle © CC BY

A water bottle that can keep track of your water intake and reminds you to drink enough water.

  • 8,572 views
  • 5 comments
  • 34 respects

Components and supplies

Necessary tools and machines

09507 01
Soldering iron (generic)
Hy gluegun
Hot glue gun (generic)

Apps and online services

About this project

Introduction:

Drinking enough water is very important for our health. Drinking more water can lead to clearer skin, better overall health, improved productivity and brain function, increased energy levels, and even weight loss.

In our busy lives, it is really hard to remember to drink enough water. And most of the time we forget to drink enough water whether we are in the home, office or on-the-go. Around 75% of Americans are chronically dehydrated all the time.

So, in order to build a healthy water drinking habit, it’s important to track your water intake every day.

To track my water intake I made my water bottle smart using Arduino. It can:

1. Track my daily water intake

2. Track my weekly average water intake

3. Remind me to take water

4. Track last intake time

5. Run more than one month in a single charge.

Making RTC Module

To log or track water intake information, it requires current time and date information to be stored along with the accumulated data. A Real-Time Clock (RTC) chip such as DS1307 with a suitable back-up battery can be used to supply the required information. The programming process of the RTC chip (in software) is also very simple and is supported in the majority of programming environments.

Here is the design of a compact RTC module, based on the popular RTC IC DS1307, for your everyday microcontroller projects. The DS1307 serial real-time clock (RTC) is a low power, full binary coded decimal (BCD) clock/calendar plus 56 bytes of NV SRAM. Address and data are transferred serially through an I2C, bidirectional bus. The 24-hour/12-hour format clock/calendar provides seconds, minutes, hours, day, date, month, and year information, including corrections for leap year.

Let's make the real time clock module according to the attached schematic. DS1307 RTC requires an external crystal to operate correctly. Two pull-up resistors are required for SCL & SDA pin of the IC. The value of the resistor may be around 2k to 10k. I used 4.7k. During soldering I tried to keep the module as small as possible because space is limited for my circuit. You can use DS3231 instead of DS1307 and the IC has an internal crystal oscillator. So, you need not to add any external crystal. You may also buy a ready-made tiny RTC module if you don't like to make it own. As the complete system will be operate from battery, for that reason I connected VBAT pin of the RTC to ground without using coin cell.

Important Pins of DS1307:

  • 5V Pin: When this pin is high, then the ds1307 sends the data, and when it is low, it runs on the backup button cell.
  • GND: This is the ground pin for the module. Both the ground of the battery and the power supply are tied together.
  • SCL: It is the I2C clock pin - which communicates with the microcontroller. Should connect with Arduino SCL pin.
  • SDA: It is the I2C data pin - which communicates with the microcontroller. Should connect with Arduino SDA pin.
  • VBAT: Battery input for any standard 3V lithium cell or other energy source. Must be grounded if not used.

Making Seven Segment Display Board

For the display module here I used a self-contained, compact common-cathode module containing four 7-segment LED numeric displays good for displaying numerical data with some simple character.

Each segment in the display module is multiplexed, meaning it shares the same anode connection points. And each of the four digits in the module have their own common cathode connection point. This allows each digit to be turned on or off independently. Also, this multiplexing technique turns the massive amount of microcontroller pins necessary to control a display into just eleven or twelve (in place of thirty-two)!

The LED segments of the display require current-limiting resistors when powered from a 5 V logic pin. The value of the resistor is typically between 330 and 470 ohms for 5 V. For Li-ion battery operation it may be 220 ohms. And, driver transistors are recommended to provide additional driving current to the LED segments, because each pin of a microcontroller can source or sink near 40 mA of current only. When all (seven) segments of the display turned on at once (the numeral 8), the current demand will exceed this 40 mA limit. The image shown below indicates the basic wiring diagram of the current limiting resistors and driver transistors.

This 4 -digit 7-segment display section is wired around four common-cathode 7-segment LED displays, and four 2N2222 NPN transistors. The 1K resistors are used for base current limiting, and the 220R resistors limits the operating current of the LED display segments.

In the Arduino board, digital outputs from D10 to D17 are used to drive segments (a to g & dp), and digital outputs D6 to D9 are used for the digits (D0-D3) of the 4×7 LED display.

A button switch is added with the display module to access different options. It will also used to wake up the Arduino from sleep mode using external hardware interrupt. so, button is connected to Arduino digital pin #2 (INT0).

Connecting Display Board with Arduino Mini Pro

According to the schematic provided before solder all the segments pin to the Arduino mini pin. Then solder 4 common pin to the collector pin of the transistor. Transistor's bases are connected to Arduino pin. After connecting the display connect the button switch with the Arduino digital pin 2 (INT 0). Be sure you connected button to Arduino pin 2 because we will implement external hardware interrupt to wake up Arduino from sleep mode using this button.

Connecting RTC Module and Buzzer

Now, connect the RTC module you made earlier to the Arduino board. SDA pin of the module must be connected to SDA (A4) pin of the Arduino pin and SCL pin of the module must be connected to SCL (A5) pin of the Arduino. Then connect the buzzer to the Arduino board.

Connections:

RTC ------> Arduino Mini Pro

  • SDA ------> A4
  • SCL ------> A5
  • Buzzer ------> D3

Attaching Ultrasonic Sensor with Bottle Cap

Make a hole into the bottle cap as shown in the image. Bring out four wires of the ultrasonic sensor through the hole and fixed the ultrasonic sensor at the middle of the bottle cap. Sensor must be attached in the inner side of the bottle cap and it should be place at the center position.

Connections:

Ultrasonic Sensor ------> Arduino Mini Pro

  • VCC ------> VCC
  • GND ------> GND
  • Trigger ------> D4
  • Echo ------> D5

Uploading Program & Testing

After completing all the connection it is the right time for uploading program to Arduino board and testing the functionality. You will be required several library for the program to work correctly. For button interfacing you need to add OneButton library. As the device will operate from battery, power consumption is an important issue. I implemented sleep mode to my Arduino sketch. Watchdog timer and external hardware interrupt was used to wake up the Arduino from sleep mode. Arduino takes sensor reading for three times, average it and calculate the necessary information and display information for 10 seconds and then go to the sleep mode for 1 minutes. It wake up any time on button pressed connected to INT0 (digital pin 2). So, if you want to see the information about your water intake just press the option button any time. It will wake up for you. Without any button press it will take reading every 1 minutes. For implementing sleep mode I used Arduino Low-Power library. For reading time from DS1307 RTC module I used DS1307RTC library. So, for compiling the attached program add all the library to your environment.

I will explain here only the calculation algorithm in brief. See the attached fill for complete sketch. Calculation is done from the average of five sensor readings to make it accurate enough. Daily reading is reset at 24 hours and prepared for new reading for new day.

Average water intake is prepared from the average of previous seven day's daily intake. You 2 hours passed without intake of any water it will alert the user with double beep in every 15 minutes and stops for next 2 hours after taking in water.

Download the attached code and upload it to your Arduino Mini Pro. Connect all the module and power it up to test either all components is working correctly or not. If the display shows some result, then congratulations! You've already done the hard things.

Fixing Arduino with Bottle Cap

Using hot glue attache Arduino mini to the upper side of the bottle cap. Keep in mind that you have to fix all the component into the bottle cap. Add enough glue so that it tightly attached to the bottle cap.

Attaching Battery and Charger Module

Now, fix the Li-ion battery in the top of the Arduino. Be careful about shorting the battery with any open pin. Then connect the charger module with the cap. Keep the USB port easy accessible from the outer side so that it can be connect easily to the charger.

Making All the Circuits Waterproof

After connecting all the components and module it it the right time to make our device completely water proof. It is very important because it is water bottle and any time water can be drop to the circuit and may damaged the circuit. To make it completely waterproof add enough glue in every outer part of the circuits without the USB port. You can make the cap nice using glue to make it round at original cap.

Don't sink the bottle cap in the water.

Enjoy It

Code

Arduino SketchArduino
// Date and time functions using a DS1307 RTC connected via I2C and Wire lib
#include <Time.h>
#include <TimeLib.h>
#include <Wire.h>
#include <DS1307RTC.h>
#include "LowPower.h"
#include "OneButton.h"

OneButton button(2, true);

const byte interruptPin = 2;
volatile int state = 0;

const int trigPin = 4;
const int echoPin = 5;

int piezoPin = 3;

const int digit[4] = {9,6,7,8};
int digit_value[4];
int digit_value1[4];

int button_press_count = 1;
const int segment[8] = {16,10,12,14,15,17,11,13};
const byte number[10][8] = {{1,1,1,1,1,1,0,0}, //0
                            {0,1,1,0,0,0,0,0}, //1
                            {1,1,0,1,1,0,1,0}, //2
                            {1,1,1,1,0,0,1,0}, //3
                            {0,1,1,0,0,1,1,0}, //4
                            {1,0,1,1,0,1,1,0}, //5
                            {1,0,1,1,1,1,1,0}, //6
                            {1,1,1,0,0,0,0,0}, //7
                            {1,1,1,1,1,1,1,0}, //8
                            {1,1,1,1,0,1,1,0}}; //9

const byte d[8] = {0,1,1,1,1,0,1,1};
const byte a[8] = {1,1,1,0,1,1,1,1};
const byte r[8] = {0,0,0,0,1,0,1,1};
const byte t[8] = {0,0,0,1,1,1,1,1};

int seconds, minutes, hours;

int water_in_ounch[15];
int water_intake_ounch[15];
int water_intake_days[7];
int water_intake_times = 0;
int previous_water_amount = 0;
int total_water_intake_today = 0;
int average_intake_last_week = 0;
int inatke_day = 1;
float average_water_level = 0; //store average of multiple reading
int water_amount_in_ounce = 0; //store calculated amount of water

int idle_time = 0;
int intake_day = 1;
int previous_value = 0;

void setup() {
  Serial.begin(9600);
  pinMode(interruptPin, INPUT_PULLUP);
  // put your setup code here, to run once:
  for(int i=6; i<=17; i++)
      pinMode(i, OUTPUT);
  pinMode(trigPin, OUTPUT);
  pinMode(echoPin, INPUT);

  button.attachClick(pressed);
  button.attachDoubleClick(doubleclick);
  button.attachLongPressStart(longPressStart);
  button.attachDuringLongPress(longPress);
}


long previous_state = millis();
int count = 1;
int daily_intake = 0;
int weekly_intake = 0;
long sleep_time = millis();

void loop() {
  read_time();
  button.tick(); // keep watching the push buttons:
  calculation();
  daily_intake = total_water_intake_in_day();
  weekly_intake = average_water_intake_last_week();
  if(button_press_count == 1){
    display_d();
    display_number(daily_intake);
  }
  else if(button_press_count == 2){
    display_a();
    display_number(weekly_intake);
  }
  else if(button_press_count == 3){
    display_r();
    display_number(water_amount_in_ounce);
  }
  else if(button_press_count == 4){
    display_first_2(hours);
    display_last_2(minutes);
  }
  if(idle_time>=120){
    alert();
    alert();
    }
  if((millis() - sleep_time) >= 15000){
    display_off();
    attachInterrupt(digitalPinToInterrupt(interruptPin), blank, FALLING);
    LowPower.powerDown(SLEEP_8S, ADC_OFF, BOD_OFF);
    LowPower.powerDown(SLEEP_8S, ADC_OFF, BOD_OFF);
    LowPower.powerDown(SLEEP_8S, ADC_OFF, BOD_OFF);
    LowPower.powerDown(SLEEP_8S, ADC_OFF, BOD_OFF);
    LowPower.powerDown(SLEEP_8S, ADC_OFF, BOD_OFF);
    LowPower.powerDown(SLEEP_8S, ADC_OFF, BOD_OFF);
    detachInterrupt(digitalPinToInterrupt(interruptPin));
    sleep_time = millis();
    }  
}


void display_digit(int digit){
  for(int i=0; i<8; i++){
    digitalWrite(segment[i], number[digit][i]);
    }
  }


void display_number(int number){
  int i=0;
  while(number>0){
    digit_value[2-i] = number%10;
    number = number/10;
    i++;
    }
  digitalWrite(digit[1], HIGH);
  digitalWrite(digit[2], LOW);
  digitalWrite(digit[3], LOW);
  display_digit(digit_value[0]);
  delay(5);

  digitalWrite(digit[1], LOW);
  digitalWrite(digit[2], HIGH);
  digitalWrite(digit[3], LOW);
  display_digit(digit_value[1]);
  delay(5);

  digitalWrite(digit[1], LOW);
  digitalWrite(digit[2], LOW);
  digitalWrite(digit[3], HIGH);
  display_digit(digit_value[2]);
  delay(5);
  digitalWrite(digit[3], LOW);
  digit_value[0] = 0;
  digit_value[1] = 0;
  digit_value[2] = 0;
  }


void display_first_2(int number){
  digitalWrite(digit[2], LOW);
  digitalWrite(digit[3], LOW);
  int i=0;
  while(number>0){
    digit_value[1-i] = number%10;
    number = number/10;
    i++;
    }
  digitalWrite(digit[0], HIGH);
  digitalWrite(digit[1], LOW);
  display_digit(digit_value[0]);
  delay(3);

  digitalWrite(digit[0], LOW);
  digitalWrite(digit[1], HIGH);
  display_digit(digit_value[1]);
  delay(3);  
  }


void display_last_2(int number){
  digitalWrite(digit[0], LOW);
  digitalWrite(digit[1], LOW);
  int i=0;
  while(number>0){
    digit_value1[1-i] = number%10;
    number = number/10;
    i++;
    }
  digitalWrite(digit[2], HIGH);
  digitalWrite(digit[3], LOW);
  display_digit(digit_value1[0]);
  delay(3);
  digitalWrite(digit[2], LOW);
  digitalWrite(digit[3], HIGH);
  display_digit(digit_value1[1]);
  delay(3);
  }


void display_d(){
  digitalWrite(digit[0], HIGH);
  for(int i=0; i<8; i++){
    digitalWrite(segment[i], d[i]);
    }
  delay(5);
  digitalWrite(digit[0], LOW);
  }


void display_a(){
  digitalWrite(digit[0], HIGH);
  for(int i=0; i<8; i++){
    digitalWrite(segment[i], a[i]);
    }
  delay(5);
  digitalWrite(digit[0], LOW);
  }

  
void display_r(){
  digitalWrite(digit[0], HIGH);
  for(int i=0; i<8; i++){
    digitalWrite(segment[i], r[i]);
    }
  delay(5);
  digitalWrite(digit[0], LOW);
  }

  
void display_t(){
  digitalWrite(digit[0], HIGH);
  for(int i=0; i<8; i++){
    digitalWrite(segment[i], t[i]);
    }
  delay(5);
  digitalWrite(digit[0], LOW);
  }

void display_off(){
  digitalWrite(digit[0], LOW);
  digitalWrite(digit[1], LOW);
  digitalWrite(digit[2], LOW);
  digitalWrite(digit[3], LOW);
  for(int i=0; i<8; i++){
    digitalWrite(segment[i], LOW);
    }
  delay(5);
  }

void read_time() {
 tmElements_t tm;

 if (RTC.read(tm)) {
 seconds = tm.Second;
 minutes = tm.Minute;
 hours = tm.Hour;
  } 
}


int distance_in_cm(){
  long duration, cm;
  // The sensor is triggered by a HIGH pulse of 10 or more microseconds.
  // Give a short LOW pulse beforehand to ensure a clean HIGH pulse:
  
  digitalWrite(trigPin, LOW);
  delayMicroseconds(2);
  digitalWrite(trigPin, HIGH);
  delayMicroseconds(10);
  digitalWrite(trigPin, LOW);

  // Read the signal from the sensor: a HIGH pulse whose
  // duration is the time (in microseconds) from the sending
  // of the ping to the reception of its echo off of an object.
  
  duration = pulseIn(echoPin, HIGH);

  // convert the time into a distance
  cm = microsecondsToCentimeters(duration); 
  return cm;
  }

long microsecondsToCentimeters(long microseconds)
  {
  // The speed of sound is 340 m/s or 29 microseconds per centimeter.
  // The ping travels out and back, so to find the distance of the
  // object we take half of the distance travelled.
  return microseconds / 29 / 2;
  }

void alert(){
  tone(piezoPin, 2000, 50);
  tone(piezoPin, 2000, 200);
  //delay(10);
  }


void blank() {
  //tone(piezoPin, 2000, 100);
  //state++;
}

// This function will be called when the button1 was pressed 1 time (and no 2. button press followed).
void pressed() {
  //Serial.println("Button 1 click.");
  button_press_count++;
  alert();
  if(button_press_count == 5){
    button_press_count = 1;
    }
} // click


// This function will be called when the button1 was pressed 2 times in a short timeframe.
void doubleclick() {
  Serial.println("Button 1 doubleclick.");
} // doubleclick


// This function will be called once, when the button1 is pressed for a long time.
void longPressStart() {
  Serial.println("Button 1 longPress start");
} // longPressStart


// This function will be called often, while the button1 is pressed for a long time.
void longPress() {
  Serial.println("Button 1 longPress...");
  water_intake_ounch[water_intake_times - 1] = 0; //ignore last value
} // longPress

void calculation(){
  float water_level = 0;// store level in every step
  int read_value = 0; //read sensor reading in cm

  for(int i=0; i<5; i++){ //take five reading
      read_value = distance_in_cm();
      if(read_value>16 || read_value<3){// unstable reading
          return; //return to calling function because reading is unstable
        }
      else if(read_value<=16 && read_value>=3){//valid value      
          water_level = water_level + read_value;
        }
      delay(10);
   }
  
  average_water_level = 17 - water_level/5; //find average from five reading, 17 = botole height
  water_amount_in_ounce = int(average_water_level*1.87);//16 cm water level = 30 ounch
  if(water_intake_times==0){
     previous_water_amount = water_amount_in_ounce;
     water_intake_times = 1;
    }
  if((water_amount_in_ounce < previous_water_amount-1) && (hours < 24)){//some water is consumed
    water_intake_ounch[water_intake_times - 1] = previous_water_amount - water_amount_in_ounce;
    water_intake_times++;
    previous_water_amount = water_amount_in_ounce;
    idle_time = 0;
    }
  else if(water_amount_in_ounce > previous_water_amount){ //water is refilled
    //water refil here
    previous_water_amount = water_amount_in_ounce;
    }
  else if(water_amount_in_ounce == previous_water_amount){ //no water consumed or reafill
    idle_time+=1;
    }

  if(hours==23 && minutes==59){ // a day is over and all values start from zero for new day
    for(int i=0; i<15; i++){
      water_intake_ounch[i] = 0;
      }
    water_intake_times = 0;
    intake_day++;
    if(intake_day==8){
      intake_day = 1;
      }
    }
}

int total_water_intake_in_day(){//calculate total water intake in a day
  total_water_intake_today = 0;
  for(int i=0; i<water_intake_times; i++){
    total_water_intake_today = total_water_intake_today + water_intake_ounch[i];
    } 
    water_intake_days[intake_day] = total_water_intake_today;
    return total_water_intake_today;
  }

int average_water_intake_last_week(){//calculate average water intake last week
  for(int i=1; i<=intake_day; i++){
    average_intake_last_week = average_intake_last_week + water_intake_days[i-1];
    }
  average_intake_last_week = average_intake_last_week/intake_day;
  return average_intake_last_week;
  }

Schematics

Schematic for RTC
Schematic zffkcjiw66
Schematic for Display
Display module uhget6vbpt

Comments

Similar projects you might like

Doggo Water Bowl Refill Monitor/Indicator - Part 1

Project tutorial by Heathen_Hacks-v2

  • 681 views
  • 2 comments
  • 3 respects

Doggo Water Bowl Refill Monitor/Indicator - Part 2

Project tutorial by Heathen_Hacks-v2

  • 1,571 views
  • 0 comments
  • 3 respects

The Magnetic Field and RGB Tester

Project tutorial by Kutluhan Aktar

  • 3,530 views
  • 0 comments
  • 14 respects

Arduino Control AC Water Heater temperature

Project tutorial by Mohannad Rawashdeh

  • 9,040 views
  • 0 comments
  • 6 respects

'Roger Bot' the Pet Rover

Project showcase by hannu_hell

  • 1,960 views
  • 0 comments
  • 12 respects

Otto DIY+ Arduino Bluetooth Robot Easy to 3D Print

Project tutorial by Team Otto builders

  • 48,199 views
  • 117 comments
  • 162 respects
Add projectSign up / Login