Shahariar
Published © CC BY-SA

Proximity Sensor Controlled Smart Water Faucet

Save water, save energy and monitor water consumption with a smart faucet.

IntermediateFull instructions provided20 hours3,319

Things used in this project

Hardware components

Proximity Sensor- Pyroelectric Infrared Sensor Module
KEMET Electronics Corporation Proximity Sensor- Pyroelectric Infrared Sensor Module
×1
EK-TM4C123GXL TM4C Tiva LaunchPad
Texas Instruments EK-TM4C123GXL TM4C Tiva LaunchPad
×1
Argon
Particle Argon
×1
Waveshare 1.54 Inch B/W E-Paper Display
×1
Dual coil Latch Relay
×1
Seeed Studio Laser Red 5
×1
Seeed Studio Phototransistor
×1
Water Flow Sensor Hall Effect based
×1
Solenoid Valve for Water Flow Control
×1
STMicroelectronics STP75NF75 n-Ch MOSFET
×1
onsemi 2n2222
×3
Optocoupler, Transistor Output
Optocoupler, Transistor Output
×1
Linear Regulator with Adjustable Output
Linear Regulator with Adjustable Output
×1
1N4007 – High Voltage, High Current Rated Diode
1N4007 – High Voltage, High Current Rated Diode
×2
onsemi MUR1560 Fast Recovery Diode
×1
Capacitor 100 µF
Capacitor 100 µF
×1
Capacitor 47 µF
Capacitor 47 µF
×1
Capacitor 22 µF
Capacitor 22 µF
×1
Resistor, 10 ohm
Resistor, 10 ohm
×1
Resistor 1k ohm
Resistor 1k ohm
×3
Through Hole Resistor, 6.8 kohm
Through Hole Resistor, 6.8 kohm
×1
Resistor 10k ohm
Resistor 10k ohm
×1
Resistor 100k ohm
Resistor 100k ohm
×1
Resistor 220 ohm
Resistor 220 ohm
×1
Battery, 3.7 V
Battery, 3.7 V
×1
60W PCIe 12V 5A Power Supply
Digilent 60W PCIe 12V 5A Power Supply
×1
Hose Clamp
×6
Plastic Faucet
×1
Plastic Hose
×1

Software apps and online services

Energia
Texas Instruments Energia
Particle Build Web IDE
Particle Build Web IDE
Blynk
Blynk

Hand tools and fabrication machines

Soldering iron (generic)
Soldering iron (generic)
Hot glue gun (generic)
Hot glue gun (generic)
Cable Tie 2.5 mm

Story

Read more

Custom parts and enclosures

epaper image1

epaper image2

Schematics

Schematic

v 1.4

Code

main

C/C++
half done
///////////// Header Files ////////////////
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <inttypes.h>
#include <sysctl.h>
#include <eeprom.h>

#include <SPI.h>
#include "epd1in54.h"
#include "epdif.h"
#include "epdpaint.h"
#include "imagedata.h"

// following constant is derived from flow sensor curve //
#define WATER_FLOW_CONSTANT 2.06 // ml of water/sec per Hz
#define AUTO_OFF_WATER 10 // turn off water after 10 minutes
#define NO_CONNECTION 15 // turn off device if not connected 
// ... to blynk cloud or particle iot even after 15 minutes

int COLORED  = 0; // White in Black on epaper
int UNCOLORED = 1;  // Black in White on epaper
unsigned char image[8120];  // buffer for epaper


uint32_t romdata_written[2] ; // var array to write eeprom
uint32_t romdata_read[2] ; // var array to read eeprom

volatile unsigned long frequency = 0;  // ISR variable for frequency
volatile unsigned long last_micro = 0; // ISR variable for time diff

unsigned long previousMillis = 0;   // duration measurement variable
unsigned long time_correction = 0;  // duration correction variable
float water_this_session;           // water usage calc variable (in ml)
uint32_t net_water_consumed;        // net water usage upto 4.2 Million ltr
/////////// Enum ////////////////////////
////// width must be multiple of 8///////
Paint paint(image, 0, 0);  // epaper enum
Epd epd;                   // epaper enum

/////////////////////////////////////////////////
////////////// I/O pin mapping //////////////////
////////// See Schematic for details ////////////
/////////////////////////////////////////////////

//// I/O pins used interfacing paper display ////
// see inside epdif.c , epdif.h files and also //
// check for tiva tm4c123 energia SPI0 pinmap  //

// #define RST_PIN   9  // PA6 (GPIO)
// #define DC_PIN    10 // PA7 (GPIO)
// #define BUSY_PIN  19 // PB2 (GPIO)
// #define MSI_SPI0  8  // PA5 (SPI0)
// #define MIS_SPI0  13 // PA4 (SPI0)
// #define CSL_SPI0  12 // PA3 (SPI0)
// #define CLK_SPI0  11 // PA2 (SPI0)
// *** note : MISO (PA4) has no actual physical wiring
//  between tm4c123 and epaper, it's part of that SPI port
//////////////////////////////////////////////////////////
// Serial connection between tm4c123 and particle argon //
// It's H/W Serial4 of tm4c123 and H/W Serial1 of Argon //
// #define RX_UART4  37 // PC4
// #define TX_UART4  36 // PC5
//////////////////////////////////////////////////////////
//// Input for sensing water flow   ////
#define FLOW_METER 31       // PF4
//// Output for valve control       ////
#define POWERUP_VALVE 40    // PF2
//// Particle Argon Connected+Ready ////
#define BLYNK_CONNECTED 35  // PC6
//// Relay unlatch control          ////
#define TRIP_RELAY 34       // PC7
//// Output for laser drive         ////
#define POWERUP_LASER 33    // PD6
//// Input for Photodiode           ////
#define SENSE_BEAM    A0    // PE3  
//// Tiva on board RGB LED control  ////
#define LED_BLU 40           // PF2 
#define LED_RED 30           // PF1 
#define LED_GRN 39           // PF3 
////////////////////////////////////////
////////////////////////////////////////

///////////////////////////////////////////////
/// ISR to measure pulses from flow sensor ////
///////////////////////////////////////////////

void PortFIntHandler()
{
  // clear interrupt
  uint32_t flag = 0;
  flag = GPIOIntStatus(GPIO_PORTE_BASE, true);
  GPIOIntClear(GPIO_PORTF_BASE, GPIO_INT_PIN_4);

  // using delay and micros is not recommended in ISR
  // but meh, it's ok for this project !
  digitalWrite(LED_GRN, HIGH); delay(1);
  digitalWrite(LED_GRN, LOW);

  // measuring frequency // f=1000000/t (t in uSeconds)
  frequency = 1000000.0 / (micros() - last_micro);
  last_micro = micros();
  // time_passed =  last_micro- first_micro ;
}

//////////////////////////////////////////////////
//////////////////////////////////////////////////
//////////////////////////////////////////////////


void setup()
{
  // initialize I/O pins //
  pinMode(LED_RED, OUTPUT);
  pinMode(LED_BLU, OUTPUT);
  pinMode(LED_GRN, OUTPUT);
  pinMode(TRIP_RELAY, OUTPUT);
  pinMode(POWERUP_LASER, OUTPUT);
  pinMode(BLYNK_CONNECTED, INPUT);



  // set PF4 as input pullup //
  pinMode(FLOW_METER, INPUT_PULLUP); // <---- PORT F pin 4, PF4
  // configure for falling edge interrupt
  GPIOIntTypeSet(GPIO_PORTF_BASE, GPIO_PIN_4, GPIO_FALLING_EDGE);
  // associate with ISR function
  GPIOIntRegister(GPIO_PORTF_BASE, PortFIntHandler);
  // enable interrupt to sense water flow pulses
  //GPIOIntEnable(GPIO_PORTF_BASE, GPIO_INT_PIN_4);

  // wrire I/O pins init states //
  digitalWrite(TRIP_RELAY, LOW);
  digitalWrite(LED_RED, LOW);
  digitalWrite(LED_BLU, LOW);
  digitalWrite(POWERUP_LASER, HIGH);


  // Initialize EEPROM for storing water metering data
  SysCtlPeripheralEnable(SYSCTL_PERIPH_EEPROM0);
  while (!SysCtlPeripheralReady(SYSCTL_PERIPH_EEPROM0))
  {}
  EEPROMInit();

  // enable valve here to activate water flow
  // time just before water flow started
  // first_micro = micros();

  // enable interrupt to sense water flow pulses
  //GPIOIntEnable(GPIO_PORTF_BASE, GPIO_INT_PIN_4);

}

/////////////////////////////////////////////////
//////////// end of Void Setup/init /////////////
/////////////////////////////////////////////////



void loop()
{
  // turn on solenoid valve //
  digitalWrite(POWERUP_VALVE, HIGH);

  // start measurement of water flow sensor pulse//
  // enable interrupt to sense water flow pulses

  GPIOIntEnable(GPIO_PORTF_BASE, GPIO_INT_PIN_4);

  // time before epaper loading //
  time_correction = millis();

  // show instruction on epaper how to stop water //
  // clear and init e paper //
  flush_epd();
  init_epd();
  // show instruction after/during use //
  epd.SetFrameMemory(Image_Faucet_Off);
  epd.DisplayFrame();

  // time required for epaper update //
  time_correction = millis() - time_correction; // in ms
  time_correction = time_correction / 1000;     // in seconds

  // accounting for water measurement correction //
  water_this_session = time_correction * frequency * WATER_FLOW_CONSTANT;

  // do water metering during the session //
  // until laser beam interrupted by user & //
  // sensed by the connected photodiode to ADC //
  do
  {
    // do nothing during this to reduce measurement error //
    unsigned long currentMillis = millis();
    if (currentMillis - previousMillis >= 1000)
    {
      previousMillis = currentMillis;
      // measure water in milli ltr, check flow rate every second and add used amount
      water_this_session = water_this_session + (frequency * WATER_FLOW_CONSTANT) ;
    }
    // auto off water after 10 min if user forgets to turn off water
    if (currentMillis > AUTO_OFF_WATER * 60 * 1000)
      break;
  }
  while (analogRead(SENSE_BEAM) > 2000);

  // turn off valve and stop measurement //
  digitalWrite(POWERUP_VALVE, LOW);
  GPIOIntDisable(GPIO_PORTF_BASE, GPIO_INT_PIN_4);
  frequency = 0;

  // turn off laser //
  digitalWrite(POWERUP_LASER, LOW);

  /*******************************************
    // update eeprom with water metering info //
  *******************************************/

  // red led glowing, eeprom in action ! don't power cycle //
  digitalWrite(LED_RED, HIGH);
  EEPROMRead(romdata_read, 0x00, sizeof(romdata_read));
  // update data before writing to eeprom, (first 32 byte area)
  romdata_written[0] =  romdata_read[0] + water_this_session;
  // write/save data to eeprom to 0x00 address with max size 64 bytes (32+32)
  EEPROMProgram(romdata_written, 0x00, sizeof(romdata_written));
  // Read eeprom 0x00 location data for new value
  EEPROMRead(romdata_read, 0x00, sizeof(romdata_read));
  // eeprom operation done, turn off red led
  delay(100);
  digitalWrite(LED_RED, LOW);
  net_water_consumed = romdata_read[0];


  // clear and init e paper //
  flush_epd();
  init_epd();

  // show user water consumption data on epaper display //
  // this session usage displayed in mili liter //
  // and net usage displayed in liter to avoid truncation error //

  char VAL[9]; // buffer for int to char array conversion

  paint.SetWidth(200);
  paint.SetHeight(200);
  paint.SetRotate(ROTATE_180);
  paint.Clear(UNCOLORED);


  paint.DrawStringAt(10, 2,  "Water used " , &Font24, COLORED);
  paint.DrawStringAt(10, 26, "during this" , &Font24, COLORED);
  paint.DrawStringAt(10, 50, " session:- " , &Font24, COLORED);
  paint.DrawStringAt(10, 74, "         ml" , &Font24, COLORED);

  paint.DrawStringAt(10, 75, "___________", &Font24, COLORED);
  paint.DrawStringAt(10, 106, "Total water", &Font24, COLORED);
  paint.DrawStringAt(10, 130, "consumed at", &Font24, COLORED);
  paint.DrawStringAt(10, 154, "this faucet:", &Font24, COLORED);
  paint.DrawStringAt(10, 178, "       Ltrs", &Font24, COLORED);

  paint.DrawStringAt(30, 74, itoa(water_this_session, VAL, 10), &Font24, COLORED);
  paint.DrawStringAt(30, 178, itoa((net_water_consumed / 1000), VAL, 10), &Font24, COLORED);

  epd.SetFrameMemory(paint.GetImage(), 0, 0, paint.GetWidth(), paint.GetHeight());
  epd.DisplayFrame();
  delay(5000);


  // Wait until connected to Blynk cloud through Particle Argon
  while (digitalRead(BLYNK_CONNECTED) == 0)
  {
    // exit after 15 min if connectivity to cloud fails
    if (millis() > NO_CONNECTION * 60 * 1000)
      break;
  }

  digitalWrite(LED_GRN, HIGH);
  // start h/w serial port 4 to transfer data
  Serial4.begin(9600);
  delay(20);

  // Send water consumption data this session and net
  Serial4.print(water_this_session);
  Serial4.print("\t"); delay(5);

  // Send net water consumption data of all time
  Serial4.print(net_water_consumed);
  Serial4.print("\t"); delay(5);

  // indicate success by blinking green LED

  digitalWrite(LED_GRN, LOW);
  delay(200);
  digitalWrite(LED_GRN, HIGH);
  delay(200);
  digitalWrite(LED_GRN, LOW);

  // put a message on display about it
  flush_epd();
  init_epd();
  paint.SetWidth(200);
  paint.SetHeight(200);
  paint.SetRotate(ROTATE_180);
  paint.Clear(UNCOLORED);


  paint.DrawStringAt(10, 2,  "Water Usage" , &Font24, COLORED);
  paint.DrawStringAt(10, 26, "data stored" , &Font24, COLORED);
  paint.DrawStringAt(10, 50, " on EEPROM " , &Font24, COLORED);
  paint.DrawStringAt(10, 74, " Successful" , &Font24, COLORED);

  paint.DrawStringAt(10, 75,  "___________", &Font24, COLORED);
  paint.DrawStringAt(10, 106, "Data upload", &Font24, COLORED);
  paint.DrawStringAt(10, 130, "to IoTCloud", &Font24, COLORED);
  paint.DrawStringAt(10, 154, " has also  ", &Font24, COLORED);
  paint.DrawStringAt(10, 178, " completed ", &Font24, COLORED);
  epd.SetFrameMemory(paint.GetImage(), 0, 0, paint.GetWidth(), paint.GetHeight());
  epd.DisplayFrame();
  delay(4000);
  // put final instruction image
  // show instruction on epaper disp before use
  flush_epd();
  init_epd();
  epd.SetFrameMemory(Image_Faucet_On);
  epd.DisplayFrame();
  delay(200);

  // turn on red LED & disconnect Power with latch relay
  digitalWrite(LED_RED, HIGH);
  digitalWrite(TRIP_RELAY, HIGH);
  delay(300);
  digitalWrite(TRIP_RELAY, LOW);

  // system should be powered off by now

  while (1)
  {
    /* unreachable code under normal operation
       if LED blinks, then something went wrong
       with wiring or circuit or code  */
    digitalWrite(LED_RED, HIGH);
    delay(100);
    digitalWrite(LED_RED, LOW);
    delay(100);
  }
}

///////////////////////////////////////////////
////////////////main loop ends here ///////////
///////////////////////////////////////////////


///////////////////////////////////////////////
///////////// Function body ///////////////////
///////////////////////////////////////////////

// clean up dark spots on epaper display //

void flush_epd (void)
{
  if (epd.Init(lut_full_update) != 0)
  {
    return;
  }
  epd.ClearFrameMemory(0xFF);
  epd.DisplayFrame();
  epd.ClearFrameMemory(0xFF);
  epd.DisplayFrame();

}

// initialize full e paper display //

void init_epd(void)
{
  ///// Init Full Display Update ///////////
  if (epd.Init(lut_full_update) != 0)
  {
    return;
  }
  ///// Init Partial Display Update ////////
  if (epd.Init(lut_partial_update) != 0)
  {
    return;
  }
  ////////// Clears Up Full Disp ///////////
  // bit set = white, bit reset = black
  epd.ClearFrameMemory(0xFF);
  epd.DisplayFrame();
  epd.ClearFrameMemory(0xFF);
  epd.DisplayFrame();

}

Full Code

C/C++
Download, Unzip and Run with Energia IDE 1.8.7 or later
No preview (download only).

Particle code

C/C++
#include <blynk.h>

#define BLUE_LED D7
int value = 0;
int value2 = 0;
char value_buffer[12];
bool send_once = 1;


// auth token send from blynk
//char auth[] = "your auth token sent by Blynk to your email";
// periodic timer for blynk
BlynkTimer t1;




void setup() 
{
    pinMode (BLUE_LED, OUTPUT);
    digitalWrite (BLUE_LED, LOW);
    Serial1.begin(9600);
    Blynk.begin(auth);
    t1.setInterval(300L, checkSerial_sendData); 
}

void checkSerial_sendData()
{
    // check if it's connected and ready to push data
    if (Particle.connected() && Blynk.connected())
    {
       digitalWrite (BLUE_LED, HIGH);  
       // delay(50)
       // serial available
       if (Serial1.available() && send_once == 1)
       {
       delay(25);
       strcpy(value_buffer,Serial1.readStringUntil('\t'));
       value  = atoi(value_buffer) ;
       strcpy(value_buffer,Serial1.readStringUntil('\t'));
       value2 = atoi(value_buffer) ; 
       
       float wcts = value/1000;  // convert to liters
       float nwc  = value2/1000; // convert to liters
      // send data with this PUSH selected on App
       Blynk.virtualWrite(V5, wcts);  // this session
       Blynk.virtualWrite(V3, nwc); // total
       send_once = 0;
       }
    }
}



void loop() 
{
      Blynk.run();
      t1.run();
}

Credits

Shahariar

Shahariar

71 projects • 262 followers
"What Kills a 'Great life' is a 'Good Life', which is Living a Life Inside While Loop"

Comments