Ask just about anyone you know, and you will hear disaster stories of hot water tanks blowing and causing major damage. I recently had my tank blow and thankfully I had a water leak detection system installed. I needed a project for the Pi-SPi modules, here is a Hot Water Tank Leak Detector and Temperature Monitoring Project.
This system detects water leaks, measures temperatures for the Cold Water Line and Hot Water Lines, drives an audible alarm, and has a test button. Sample code in C using the Geany Compiler also provided.
Parts:
- RPi (I'm using the new RPi 3)
- Pi-SPi-8AI 8 channel analog input module
- Pi-SPi-8KO 8 channel Relay module
- Test switch SPST
- Qty 2 10K Thermistors (Cantherm 3380 Beta with 3 foot leads)
- Water Leak Sensor (2 stainless steel screws and nuts on a piece of plastic)
- 24 VDC Audible Buzzer (Mode Electronics)
- 24 VDC Power Supply (required to drive the relays and buzzer)
Basic Operation:
- Detect Water Leaks and drive the buzzer and activate K1 Relay
- Test Pushbutton drive buzzer and activate K1 Relay
- Measure the temperature for the Cold and Hot Water Lines (The code uses the Steinhart-Hart equations)
- Active the K2 Relay if the Hot water Line gets within 10% of the Cold Water Line temperature
The key to this circuit (other than the RPi of course ;), is the versatile analog input circuit of the Pi-SPi-8AI. There are 8 channels using the MCP3208, each channel can be configured as a mA input, a Voltage Input, a Thermistor input, a SPST Switch input, and I modified one channel for a water leak sensor input.
Basic Circuit:
4-20 mA and VDC Input Circuit
The mA input is converted to a voltage across Rload. 20mA x 150 Ohms = 3.0 VDC
For DC Voltge inputs, the Rload resistor is removed. For voltages greater than 3.3 VDC, C2 can be replaced with a resistor creating a voltage divider between R2 and the R where C2 is.
Thermistor Input Circuit
Switch Input
The SPST switch shorts out the input, the ADC sees either full AD counts or close to 0 AD counts
Water Leak Detection Circuit:
Project Setup:
Pi-SPi-8AI
- Channels 1 thru 4 are not used
- Channel 5 Water Leak Detection Circuit
- Channel 6 Cold Water Line Temperature Sensor
- Channel 7 Hot Water Line Temperature Sensor
- Channel 8 Test Switch Input
P-SPi-8KO
- K1 Audible Driver
- K2 Temperature Alarm
- K3 Output - Water Leak Circuit Voltage
- K4 thru K8 are not used
Here is the test setup before installation:
Here is a screen shot before installation:
How the Water Leak Detector Works:
The K3 output is turned on to provide a 3.3 VDC output to one of the water leak sensor terminals. After a small delay, the Channel 5 is read by the MCP3208. If there are AD counts higher than the threshold setting, then there is a water leak.
This circuit acts as a water conductivity detector, by adjusting the settings, turn on times, etc., this concept can be used for water solids content measuring, soil humidity content, etc.
The reason for toggling the power on the sense circuit is to prevent electrolysis and corrosion on the sensing elements.
Installation:
Here is my brand new water hot water tank!
A close up of the water leak sensor:
And the temperature sensor taped to the hot water line (top to the left) and cold water line (top to the right).
A screenshot of the Pi system in operation:
This is a very basic setup. The next steps are to add email alerts, a nice GUI interface, more detection points, maybe a water shut-off valve, etc.
Detailed specs on the Pi-SPi's can be found here:
Finally, here is the sample code used in the above project:
/*
* pispi_hwt.c
*
* Copyright 2016
*
* 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 2 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, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301, USA.
*
*
*/
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <stdlib.h>
#include <math.h>
#include <wiringPi.h>
#include <wiringPiSPI.h>
// Define Pins
#define PIN_EN_AD 7
#define PIN_EN_K 8
#define OFF 0
#define ON 1
#define WATER_LEAK_THRESHOLD 1000
#define K1 0x01
#define K2 0x02
#define K3 0x04
// Steinhar-Hart Equation Defines for 10K Thermistor Beta 3380 Temp Sensor
#define R_LOAD 10000.0
#define R_ROOM_TEMP 10000.0 // for 25 Deg C
#define T_BETA 3380
#define T_AD_COUNTS 4095 // MCP3208 is 12 bit ADC
#define ROOM_TEMP_NOM 25.0
// Prototypes
void Initialize_Pi_Hardware(void);
void Update_Switch(void);
void Update_Water_Leak(void);
void Update_Temperatures(void);
void Update_Relays(unsigned char status);
void Update_Analog(unsigned char channel);
// Variables
unsigned AN_AD[8];
float Temp_Hot_Line, Temp_Cold_Line;
int Switch;
unsigned char Relay_Status;
unsigned char Water_Leak;
// main
int main(void) {
wiringPiSetupGpio();
Initialize_Pi_Hardware();
digitalWrite(PIN_EN_K, HIGH);
digitalWrite(PIN_EN_AD, HIGH);
Relay_Status = 0;
Switch = OFF;
Water_Leak = OFF;
while(1)
{
printf("Water Leak = %s \n", Water_Leak ? "ON" : "OFF");
printf("Test Switch = %s \n", Switch ? "ON" : "OFF");
printf("Cold Water Line = %0.1f Deg C \n", Temp_Cold_Line);
printf("Hot Water Line = %0.1f Deg C \n", Temp_Hot_Line);
printf("\n");
Update_Switch();
Update_Water_Leak();
Update_Relays(Relay_Status);
Update_Temperatures();
delay(500);
}
return 0;
}
void Update_Switch(void) {
Update_Analog(7);
if(AN_AD[7] > 2048) {
Switch = OFF;
}
else {
Switch = ON;
}
}
void Update_Water_Leak(void) {
Relay_Status |= K3; // Turn ON Water Sense Circuit
Update_Relays(Relay_Status);
delay(200);
Update_Analog(4); // Read Water Sense Circuit AD Counts
if(AN_AD[4] > WATER_LEAK_THRESHOLD) {
Water_Leak = ON;
}
else {
Water_Leak = OFF;
}
Relay_Status &= ~ K3; // Turn OFF Water Sense Circuit
Update_Relays(Relay_Status);
}
void Update_Temperatures(void) {
float sample;
unsigned int average;
int i;
// Calculate Temperature for Cold Water Line
average = 0;
for(i=0; i<5; i++) { // take 5 reading and average
Update_Analog(5); // to smooth out the reading
average += AN_AD[5];
}
average = average / 5;
sample = ((float)T_AD_COUNTS / average) - 1;
sample = (float)R_LOAD / sample;
sample = sample / (float)R_ROOM_TEMP;
sample = log(sample);
sample /= (float)T_BETA;
sample += 1.0 / ((float)ROOM_TEMP_NOM +275.15);
sample = 1.0 / sample;
sample -= 273.15;
Temp_Cold_Line = sample;
// Calculate Temperature for Hot Water Line
average = 0;
for(i=0; i<5; i++) { // take 5 reading and average
Update_Analog(6); // to smooth out the reading
average += AN_AD[6];
}
average = average / 5;
sample = ((float)T_AD_COUNTS / average) - 1;
sample = (float)R_LOAD / sample;
sample = sample / (float)R_ROOM_TEMP;
sample = log(sample);
sample /= (float)T_BETA;
sample += 1.0 / ((float)ROOM_TEMP_NOM +275.15);
sample = 1.0 / sample;
sample -= 273.15;
Temp_Hot_Line = sample;
// Set Alarm if Hot Water < Cold Water + 10% * Cold Water
if(Temp_Hot_Line < (Temp_Cold_Line + (Temp_Cold_Line * .1))) {
Relay_Status |= K2;
}
else {
Relay_Status &= ~K2;
}
}
void Update_Analog(unsigned char channel) {
unsigned int adc, input;
unsigned char buf[3];
wiringPiSPISetup(1, 100000);
input = 0x0600 | (channel << 6);
buf[0] = (input >> 8) & 0xff;
buf[1] = input & 0xff;
buf[2] = 0;
digitalWrite(PIN_EN_AD, LOW);
wiringPiSPIDataRW(1,buf,3);
adc = ((buf[1] & 0x0f ) << 8) | buf[2];
digitalWrite(PIN_EN_AD, HIGH);
AN_AD[channel] = adc;
}
void Update_Relays(unsigned char status) {
unsigned char buf[2];
if(Switch == ON || Water_Leak == ON) {
status |= K1;
}
else {
if(Switch == OFF && Water_Leak == OFF) {
status &= ~K1;
}
}
buf[0] = status;
wiringPiSPISetup(0, 100000);
wiringPiSPIDataRW(0,buf,1);
digitalWrite(PIN_EN_K, LOW);
delay(1);
digitalWrite(PIN_EN_K, HIGH);
}
void Initialize_Pi_Hardware(void) {
// SPI CS Enable Lines
pinMode(PIN_EN_AD, OUTPUT);
pinMode(PIN_EN_K, OUTPUT);
}