MUSE (Massey University Smart Home) Waterflow detector
This software is designed to control the Arduino microcontroller on the water turbulence waterflow detector.
The idea is very simple – when water is flowing through a constriction (i.e. a tap), there’s turbulence and this makes a noise. Unlike most other noises in a home, this one is (for most uses of a tap) constant for more than a second.
An electret microphone is attached near a tap and the sound amplifed and applied to a threshold detector which is fed to one of the external interrupt inputs of the Arduino. The software detects when the noise begins and starts timing. If the noise doesn’t fall below the threshold for a definable period (say 1.5 seconds), then it sets the “waterflow detected” output.
This is susceptible to other constant noises in the room (e.g. a blender) but this can be ameliorated by some accoustic insulation on the microphone (bluetack maybe?).
For the intended environment, the presence of loud and constant noises is unlikely to be a problem.
This software is release under the terms of the GNU Public General Licence http://www.gnu.org/licenses/gpl.html.
I’ll upload the PC board and schematic shortly.
Giovanni Moretti
g.moretti@massey.ac.nz
/*
Waterflow Detector for Massey University Smart Homes Research Project
Accoustic Waterflow Detector Control Software
Copyright (C) 2011 Giovanni S. Moretti
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <avr/sleep.h>
#include <avr/wdt.h>
#include <avr/power.h>
// Inputs
int noiseDetectPin = 2; // D2 - Int0 - Pin 4
int noiseLossPin = 3; // D3 - Int1 - Pin 5
// Outputs
int waterflowOutput = 4; // D4 - Pin 6 - Hi to turn on 2N7000
int waterflowLED = 5; // D5 - Pin 11 - Low to turn LED on
int batteryCheck = 0; // ADC0 - pin 23 - To ADC to check battery
int arduinoLED = 13;
//==============================================================================
#define NOISE_DETECTED 1
#define NOISE_LOSS 2
#define TIMEOUT 3
#define OUTPUT_ACTIVE 4
#define PIN_GONEBACKHIGH 5
#define UNKNOWNINTERRUPT 6
#define ATTEMPTTOPULLFROMEMPTYQUEUE 7
//==============================================================================
// Which Interrupt is currently active - Waiting for HIGH or LOW?
volatile int activeInterrupt;
#define WAITINGFORHIGH 1
#define WAITINGFORLOW 2
#define NONE 0
#define OFF LOW
#define ON HIGH
volatile int watchdogTickCount;
#define WATCHDOGPERIOD WDTO_30MS
#define WATCHDOGTICKTHRESHOLD 60
//==============================================================================
// Debug LEDs on Arduino development board
void arduinoLEDOn()
{
digitalWrite(arduinoLED, ON);
}
void arduinoLEDOff()
{
digitalWrite(arduinoLED, OFF);
}
//==============================================================================
// Functions to enable/disable both the waterflowOutput which
//==============================================================================
void waterflowOutputActive() // Signal Waterflow detected
{
digitalWrite(waterflowOutput, HIGH); // Hi to turn 2N7000 on for Monnit Sensor
digitalWrite(waterflowLED, LOW); // Low to turn onboard diagnostic LED on
}
void waterflowOutputReset() // Waterflow stopped
{
digitalWrite(waterflowOutput, LOW); // Hi to turn 2N7000 on for Monnit Sensor
digitalWrite(waterflowLED, HIGH); // Low to turn onboard diagnostic LED on
}
//==============================================================================
// Noise Detection Interrupt - Int0
// Signal that we've heard something and start a two second timer
void noiseDetectionInterruptHandler()
{
detachInterrupt(0); // To stop repeated Interrupts
watchdogTimerEnableWithInterrupts(WATCHDOGPERIOD); // Start Timer
watchdogTickCount = 0;
queueEvent(NOISE_DETECTED); // Flag to be checked after wakeup
}
//==============================================================================
// Watchdog Interrupt Handler - Goes off every ~30mS if the NoiseInput
// is still active, increment a counter if the noise is still present
// and the counter gets to 1.5 to 2 seconds, signal WaterflowDetected,
// otherwise just disable timer
// ==============================================================================
ISR(WDT_vect) {
queueEvent(TIMEOUT);
if (digitalRead(noiseDetectPin) == LOW) // Still Noise
{
if (watchdogTickCount < WATCHDOGTICKTHRESHOLD)
watchdogTickCount++;
else // Must be >= to Threshold
{
waterflowOutputActive();
queueEvent(OUTPUT_ACTIVE);
}
// Start Watchdog timer for next sample of Noise Deteched Pin
watchdogTimerEnableWithInterrupts(WATCHDOGPERIOD);
}
else // Noise Stopped - Pin is HIGH, disable output & reconnect interrupt handler
{
wdt_disable();
queueEvent(NOISE_LOSS);
waterflowOutputReset();
watchdogTickCount = -1;
attachInterrupt(0, noiseDetectionInterruptHandler, LOW);
}
}
//==============================================================================
// Circular Queue: queue events under interrupt, and remove from the mainline
#define QUEUESIZE 25
volatile unsigned int hd, tl, count, tick = 0;
volatile unsigned int queue [QUEUESIZE];
void initEventQueue()
{
hd = tl = count = tick = 0;
}
void queueEvent(int event)
{
return;
if (count >= QUEUESIZE) return; // Queue is full, ignore event
queue[hd++] = (event << 11) | watchdogTickCount; // put item in queue
if (hd == QUEUESIZE) hd == 0; // point hd at next free slot
count++;
tick++;
}
int dequeueEvent()
{ int event;
noInterrupts();
if (queueSize() == 0)
{
interrupts();
return(ATTEMPTTOPULLFROMEMPTYQUEUE); // shouldn't ever happen
}
event = queue[tl++];
if (tl == QUEUESIZE) tl == 0;
count--;
interrupts();
return(event);
}
int queueSize()
{
noInterrupts();
int size = count;
interrupts();
return(size);
}
//=============================================================================
// Blink(n) - show a definable number of blinks for debugging.
//=============================================================================
void blink(int n)
{
for (int i = 1; i<= n; i++)
{
arduinoLEDOn(); delay(400); arduinoLEDOff(); delay(250);
}
delay(1000);
}
//=============================================================================
// snooze - put the CPU to sleep, return when awoken
//=============================================================================
void snooze(int sleepState)
{
delay(3); // Let any serial output finish
set_sleep_mode(sleepState); // sleep mode is set here
sleep_enable(); // enables the sleep bit in the MCUCR register
// so sleep is possible. just a safety pin
sleep_mode(); // GO TO SLEEP!
sleep_disable(); // WOKEN UP
}
//****************************************************************
// Watchdog Timer Code from the Nightingale
// 0=16ms, 1=32ms,2=64ms,3=128ms,4=250ms,5=500ms
// 6=1 sec,7=2 sec, 8=4 sec, 9= 8sec
void setup_watchdog(int ii) {
byte bb;
int ww;
if (ii > 9 ) ii=9;
bb=ii & 7;
if (ii > 7) bb|= (1<<5);
bb|= (1<<WDCE);
ww=bb;
MCUSR &= ~(1<<WDRF);
// start timed sequence
WDTCSR |= (1<<WDCE) ;// | (1<<WDE);
// set new watchdog timeout value
WDTCSR = bb | (1 << WDIE);
}
void watchdogInterruptEnable() // Enable Watchdog Interrupts
{
WDTCSR |= (1 << WDIE);
}
void watchdogInterruptDisable() // Disable Watchdog Interrupts
{
WDTCSR &= ~(1 << WDIE);
}
void watchdogTimerEnableWithInterrupts(int period)
{
// wdt_enable(period);
setup_watchdog(period);
// watchdogInterruptEnable();
}
//==============================================================================
// Setup() - executed ONCE
//==============================================================================
void setup() {
/*
// Thanks to IONITO - http://www.arduino.cc/cgi-bin/yabb2/YaBB.pl?num=1291420085
// Prevent interrupts during this phase
// noInterrupts();
// Temporarily turn off watchdog and kill watchdog if running
wdt_reset();
MCUSR = 0;
WDTCSR |= _BV(WDCE) | _BV(WDE);
WDTCSR = 0;
// Turn OFF Analog parts
ACSR = ACSR & 0b11110111 ; // clearing ACIE prevent analog interupts happening during next command
ACSR = ACSR | 0b10000000 ; // set ACD bit powers off analog comparator
ADCSRA = ADCSRA & 0b01111111; // clearing ADEN turns off analog digital converter
ADMUX &= B00111111; // Comparator uses AREF/GND and not internally generated references
power_adc_disable(); // redundant code if you check the above lines
power_spi_disable();
power_twi_disable();
power_usart0_disable();
// Disabling timers:
TCCR1B = 0b00000000; // I need timer0 and timer; only timer1 is disabled
// Put all I/O pins into input mode and internal pull-up
for (int i=0; i<=13;i++)
{
pinMode(i, INPUT);
digitalWrite(i, HIGH);
}
*/
//========================= End of snipped code ==================================
// power_usart0_disable();
// Define the Input Pins
pinMode(noiseDetectPin, INPUT); // Single Pin Active Low ==> Noise Detected
digitalWrite(noiseDetectPin, HIGH); // turn on pullup resistor
// Define the Output Pins
pinMode(waterflowOutput, OUTPUT);
pinMode(waterflowLED, OUTPUT);
pinMode(arduinoLED, OUTPUT); // Onboard LED on Arduino Uno
// Blink All the LEDs as a startup/debug signal
for (int i=1; i<= 5; i++) {
arduinoLEDOn(); waterflowOutputActive(); delay(300);
arduinoLEDOff(); waterflowOutputReset(); delay(300);
}
Serial.begin(115200);
Serial.println("Giovanni's Waterflow Monitor");
// All set up, now start mainline
waterflowOutputReset(); // All outputs OFF
initEventQueue();
//wait for a noise to occur: Int0 - Pin 4 will go low
attachInterrupt(0, noiseDetectionInterruptHandler, LOW);
}
//=============================================================================
// The eternally looping mainline
//=============================================================================
void loop() {
while (int currentCount = queueSize() > 0)
{
Serial.print ("QueueSize = "); Serial.print(currentCount);
unsigned int wakeupReason = dequeueEvent();
unsigned int thisTick = wakeupReason & 2047;
unsigned int event = wakeupReason >> 11;
if (event == NOISE_LOSS) Serial.println(" - Noise Loss: Timer stopped & Output Reset.");
else
if (event == NOISE_DETECTED) Serial.println(" - Noise Detected: timer started ...");
else
if (event == TIMEOUT)
{
Serial.print(" - Timeout");
Serial.print (" - WD Tick: "); Serial.println(thisTick);
}
else
if (event == OUTPUT_ACTIVE) Serial.println(" - Waterflow Output Active");
else
if (event == ATTEMPTTOPULLFROMEMPTYQUEUE)
Serial.println(" - attempt to pull from empty queue");
else
{
Serial.print(" - UnknownEvent Event "); Serial.println(event);
}
}
//Serial.println("Sleeping ...\n");
// delay(400);
snooze(SLEEP_MODE_PWR_DOWN);
//Serial.println("WAKE UP");
}