Project tutorial
Hot Water Heater Thermocouple Voltage Monitor

Hot Water Heater Thermocouple Voltage Monitor © GPL3+

Use Arduino and Raspberry Pi to get notified if a hot water heater pilot light goes out.

  • 1,246 views
  • 6 comments
  • 7 respects

Components and supplies

About this project

Hot Water Heater Pilot Light Monitoring System

Traditional gas hot water heaters maintain a pilot light 24x7. This pilot light heats a thermocouple which generates a small voltage (up to 30 millivolts). This voltage is used by the hot water heater thermostat to hold open a gas valve. If the pilot light blows out, the voltage stops and the gas valve is automatically closed.

In my hot water heater, the pilot light blows out occasionally. Since this only happens around 3 times per year, I don't want to replace the expensive hot water heater. I tried replacing the thermocouple, but this did not solve the problem.

My solution: monitor the voltage generated by the thermocouple. If it falls to zero, send texts and emails so I can relight the pilot light. Remember, there is no danger of gas leak here, because the gas valve will get closed whenever the thermocouple voltage drops to zero.

Overview

A Raspberry Pi Zero WH will be used to send emails & texts when the pilot light goes out. Because the Raspberry Pi does not have a way to measure voltage, an Arduino Nano is used to monitor the voltage from the thermocouple.

Arduinos have analog-to-digital converters (ADCs) that can read a voltage and convert it to a number between 0 and 1023. In this project, the Arduino reads the voltage from the thermocouple and communicates this value to the Raspberry Pi. The Raspberry Pi receives the voltage reading and interprets it. If the value is too low, we assume the pilot light has gone out and send notifications.

Step 1: Prepare your Raspberry Pi

Get the Raspberry Pi up and running on your network (you can check my other project for instructions for doing this).

Create a Python program that will receive voltage readings from the Arduino. The program will loop and send notifications when the voltage reading is too low. Source code is attached.

Step 2: Prepare your Arduino

I bought cheap Chinese Arduino Nanos, so it took some trial and error to get these working with my Windows 10 computer.

Load the Arduino with a sketch to measure the voltage on its input pin every 30 seconds. The Arduino can measure voltage between 0 and a reference voltage. One can select 1.1V or 5V as the reference voltage. Because the thermocouple only outputs at most 30 millivolts, we will use the 1.1V reference voltage for greater resolution. The Arduino ADC outputs 0 for 0V and 1023 for 1.1V. As mentioned, the thermocouple is outputting at most 30 millivolts, so the reading will be 28 or less.

Step 3: Connect the Arduino to the Voltage Source

The thermocouple sends voltage to the hot water heater thermostat via a coaxial wire. The outside of the wire is positive.

Cut the wire about 5 inches away from the hot water heater thermostat. Cut the outside of the coaxial wire back a little so that the middle wire is accessible. Do not destroy whatever insulation is painted on the middle wire. We don't want the middle wire short circuiting to the outside of the coaxial wire.

Connect the wire so that it supplies the positive voltage to the hot water heater thermostat and to the Arduino input pin. I used input pin A0. The negative side of the voltage source should be connected to the Arduino's ground, as well as the negative side of the hot water heater thermostat.

I used 1 foot long wires with alligator clips on the ends to make the connections.

Step 4: Connect the Arduino to the Raspberry Pi

Our sketch running on the Arduino will transmit the voltage number from the ADC to the Raspberry Pi. The Arduino will send the signal as output from its digital pin 9.

Arduinos send signals at +5V. The Raspberry Pi can only receive a 3V input. Therefore, between the Arduino and the Raspberry Pi, there must be a voltage divider. This reduces the voltage from 5V to about 2.8V.

A voltage divider is a system of resistors that reduce the input voltage.

The output voltage is determined by the the voltage divider relationship.

One cannot use a single resistor to reduce the voltage because when no current is flowing, the voltage on both sides of the resistor will be Vin (5V). Using a voltage divider will reduce the voltage even in the no-current situation.

The ground wires of everything must be connected. See the connection diagram.

Step 5: Arduino Transmits the Voltage Measurement

Every 30 seconds the Arduino sketch gets the voltage measurement from the ADC and transmits the number to the Raspberry Pi. I decided to use a primitive, single-wire signaling system from Arduino pin 9 to Raspberry Pi pin 16.

Step 6: Raspberry Pi Receives the Voltage Measurement

The Raspberry Pi uses a Python program with an infinite loop to listen for rising edges on pin 16. When a rising edge is detected, it is assumed that the Arduino has begun transmitting a number.

Signaling System
Transmit a two digit number (ex: "12"). Each digit is preceded by a positive pulse received on pin 16. Subsequent positive pulses separated by less than 20 milliseconds (ms) increment the value of this digit. A pause over 40ms indicates that this digit is complete, and the next digit begins. Another pause over 40ms indicates that the second digit is complete. The entire two-digit number is now complete.

In the Python code running on the Raspberry Pi, the number received from the Arduino is returned from the method that interprets the signals from the Arduino. Other Python code decides whether or not a notification needs to be sent. Then, the code returns to waiting on the next rising edge on pin 16.

Step 7: Send the Notification

If the voltage is too low, increment the low count. A low count is maintained to prevent false alarms. If the low count reaches 5, then the voltage has been low for 5 measurements. Each measurement is 30 seconds apart. Send the notification and reset the low count. Notifications are sent using smtplib and a Gmail account. A timestamp is stored in the Python code when the notification is sent. The next notification will not be sent for 6 hours.

To send text alerts, I used a feature that most cell providers provide: the ability email a text to their users. Ex: for Verizon: 5555555555@vtext.com sends a text to that number.

Gmail
I created a new Gmail account to use for sending the notifications. At first, I configured the security settings on this Gmail account to allow 'less secure' access, so the Python code could send emails. However, after a few months, Gmail may disable this less-secure access. I discovered that Gmail prefers users to use app passwords.

Setting up an app password
https://support.google.com/accounts/answer/185833

Step 8: Connect Everything

Connect the Arduino to the hot water heater thermocouple wire and to the Raspberry Pi via the voltage divider. (The top part of my voltage divider uses two resistors that total 550 ohms—because those are the resistors that I had on hand.) Power on the Arduino and it will begin measuring the voltage and sending signals to the Raspberry Pi every 30 seconds.

Step 9: Run the Python Program

  • Create a script that kicks off your Python program.
  • Edit the root crontab to automatically run your script at boot time. Edit the directory paths as appropriate for you Raspberry Pi and where you have the Python program saved.
  • To edit the crontab:
    sudo crontab -e
  • Add a line to run your 'kick-off' script when the Raspberry Pi is rebooted:
    @reboot sh /home/pi/hotwater/run_HotWaterNotifier.sh >> /home/pi/hotwater/logs/HotWaterNotifier.log 2>&1
  • To run manually (before you have the crontab setup)
    First execute the process (execute your run_HotWaterNotifier.sh script)
    Then type ctrl-Z to suspend your process
    Then type bg once or twice to send the process to the background
    Type disown, this lets the process keep running after you log off
  • To see if the process is still running
    ps aux | grep -i HotWater


Code

Arduino CodeArduino
This reads the voltage every 30 seconds and sends signals to the Raspberry Pi
/******************************************************************************
  HotWaterHeater

  Measures the voltage coming from the hot water heater's thermocouple.
  Uses signaling to send this voltage sensor measurement to the Raspberry Pi.
 ****************************************************************************/

//double ReferenceVoltage = 5.0; //unused reference voltage
double ReferenceVoltage = 1.1;
int OUTPUT_PIN = 9;
int INTRA_DIGIT_WAIT=20;
int BETWEEN_DIGIT_WAIT=50;

///////////////////////////////////////////////////////////////////////////////
// the setup function runs once when you press reset or power the board
///////////////////////////////////////////////////////////////////////////////
void setup() {
  // initialize digital pin LED_BUILTIN as an output.
  pinMode(LED_BUILTIN, OUTPUT);
  pinMode(OUTPUT_PIN, OUTPUT);    // sets the digital pin 9 as output

  analogReference(INTERNAL);
  Serial.begin(9600);     //  opens serial port, sets data rate to 9600 bps
}

///////////////////////////////////////////////////////////////////////////////
// The loop function runs over and over again forever.
// Measure the voltge on the input pin.
// Then send that reading to the Raspberry Pi
///////////////////////////////////////////////////////////////////////////////
void loop() {
  int pinInput;
  double voltage;

  pinInput = analogRead(A0); // Probe Input
  Serial.print("PinInputA0=");
  Serial.print(pinInput);

  //The Arduino ADC is a ten-bit converter, meaning that the output value will range from 0 to 1023
  voltage = (pinInput * ReferenceVoltage ) / 1023;
  Serial.print(", voltageA0=");
  Serial.println(voltage);

  //Note: a reading of 5 = 5.38 mV
  sendNumberSignal(pinInput, OUTPUT_PIN, INTRA_DIGIT_WAIT, BETWEEN_DIGIT_WAIT);

  //Execute the check once every 30 seconds
  delay(30000);
}

/******************************************************************************
 * Signal a number on a single wire.
 * 
 * For each digit send a series of pulses.
 * There is also an initial kick-off pulse.
 * Each pulse looks like: __/ 20ms \__20ms__
 * 20+20ms between rising edges within the same digit
 * 
 * Between digits: __/ 20ms \__20ms__ 50ms___
 * 20+20+50ms between digits
 *****************************************************************************/
void sendNumberSignal(int number, int pin, int intraDigitWait, int betweenDigitWait) {
  int tens = number/10;
  int ones = number % 10;

  Serial.print("Signaling: ");
  Serial.println(number);

  // debugging ////////
  //Serial.print("tens: ");
  //Serial.println(tens);
  //Serial.print("ones: ");
  //Serial.println(ones);
  //Serial.print("millis: ");
  //Serial.println(millis());

  // debugging ////////
  //Serial.println("send tens");
  //Serial.print("millis: ");
  //Serial.println(millis());

  //send the tens number
  sendPulse(pin, intraDigitWait);
  for (int i=0; i<tens; i++) {
    sendPulse(pin, intraDigitWait);
  }

  //Serial.println("before between delay");
  //Serial.print("millis: ");
  //Serial.println(millis());
  delay(betweenDigitWait);
  //Serial.println("after between delay");
  //Serial.print("millis: ");
  //Serial.println(millis());

  // debugging ////////
  //Serial.println("send ones");
  //Serial.print("millis: ");
  //Serial.println(millis());

  //send the ones number
  sendPulse(pin, intraDigitWait);
  for (int i=0; i<ones; i++) {
    sendPulse(pin, intraDigitWait);
  }
  delay(betweenDigitWait);
}

/******************************************************************************
 * Send a pulse with the required wait times holding the pin high and then low.
 *****************************************************************************/
void sendPulse(int pin, int intraDigitWait) {
  //Serial.print("pulse start ");
  //Serial.println(millis());

  digitalWrite(pin, HIGH);
  delay(intraDigitWait);  //keep the pin high for intraDigitWait milliseconds
  digitalWrite(pin, LOW);
  delay(intraDigitWait);  //keep the pin low for intraDigitWait milliseconds

  //Serial.print("pulse end   ");
  //Serial.println(millis());
}
Phython CodePython
This code receives signals from the Arduino. It interprets the signals into a voltage reading. It then decides if the voltage is too low. If the voltage is too low, it send emails and texts.
###############################################################################
# HotWaterNotifier.py
# Monitor the input pin and receive signals from an Arduino.
# Decode the signals into numeric voltage sensor measurements.
# When the received voltage reading is too low, this indicates that the
# pilot light is out. When this occurs for 5 measurements in a row, assume that
# the pilot light really is out. Send emails/texts and record the time.
# Only send an email/text every six hours.
#
# This was written for Python 2.7. Minor changes may be required for Python 3.
###############################################################################
import smtplib
import RPi.GPIO as GPIO
import os
import os.path
import time
import datetime
import string
import logging
import sys

#Input GPIO to receive signals from the Arduino
GPIO_Alert=16

# How many hours to wait between emails
emailWaitHours=6

# Low voltage sensor reading.
# Less than or equal to this is low voltage.
lowSensorReading=1

# Once this many low sensor readings is reached, send the alert
maxLowVoltageCount=5

# for GPIO numbering, choose BCM
GPIO.setmode(GPIO.BCM)
GPIO.setup(GPIO_Alert, GPIO.IN, pull_up_down=GPIO.PUD_DOWN)
lastEmailSentTime = datetime.datetime(2000,1,1,0,0,0,0) #declare global variable

###############################################################################
# Setup the logger.
###############################################################################
def setup_custom_logger(name):
    formatter = logging.Formatter(fmt='%(asctime)s %(levelname)-8s %(message)s',
                                  datefmt='%Y-%m-%d %H:%M:%S')
    handler = logging.FileHandler('HotWaterLog.txt', mode='a')
    handler.setFormatter(formatter)
    screen_handler = logging.StreamHandler(stream=sys.stdout)
    screen_handler.setFormatter(formatter)
    logger = logging.getLogger(name)
    logger.setLevel(logging.DEBUG)
    logger.addHandler(handler)
    logger.addHandler(screen_handler)
    return logger

###############################################################################
# Function to send emails/texts
###############################################################################
def send_email_alert():
    # allow writing to the global variable
    global lastEmailSentTime

    # check if enough time has passed since the last email
    nowTime = datetime.datetime.now()
    emailWaitDelta = datetime.timedelta(hours=emailWaitHours)
    limitTime = nowTime - emailWaitDelta

    #if enough time has passed, send the email.
    if lastEmailSentTime is None or limitTime > lastEmailSentTime:
        logger.info('Sending email alert...')
        HOST = "smtp.gmail.com"
        PORT = 587
        SUBJECT = "Hot Water Heater Alert"
        #This should be a list object for multiple addresses
        TO = ["XXXXXXXXXXXXXX@gmail.com", "5555555555@vtext.com"]
        #TO = ["XXXXXXXXXXXXXX@gmail.com"]
        FROM = "XXXXXXXXXXXXXX@gmail.com"
        PWD = "XXXXXXXXXXXXXX"
        text = "Low Voltage Measured on the Hot Water Heater"
        #The to field is joined into 1 string here.
        #This is what is displayed to the recipient on their email.
        BODY = string.join(("from: %s" %FROM, "to: %s" %", ".join(TO), "Subject: %s" %SUBJECT, "     ", text), "\r\n")

        try:
            s = smtplib.SMTP(HOST,PORT)
            s.set_debuglevel(1)
            s.ehlo()
            s.starttls()
            s.login(FROM, PWD)
            s.sendmail(FROM,TO,BODY)
            s.quit
        except Exception as e:
            logger.exception('Exception caught file sending email. Trying again in 6 hours')

        #set the time so that an email is not sent for 6 hours
        lastEmailSentTime = nowTime
    else:
        logger.info('Not sending email. Last email sent at: ' + lastEmailSentTime.strftime("%Y-%m-%d %H:%M:%S"))

###############################################################################
# Receive signals from the Arduino.
# A number is made up of two digits. (a tens place digit, and a ones place digit)
# The Arduino always transmits a number, consisting of 2 digits.
# The signals received are a series of high pulses on the input pin.
# The waitReceiveNumber() method counts the high pulses and the [count -1] is
# the value of the digit.
# Each digit is preceeded by 1 pulse.
#
# 70 ms max between signal edges within the same digit
#       rising edges separated by under 70ms are the same digit
#       if greater than 70ms, then move to the next digit
# 200 ms means the number is complete
###############################################################################
def waitReceiveNumber(GPIO_Alert):
    lastSignalTime = datetime.datetime(2000,1,1,0,0,0,0)
    isTens=True
    isFirstIteration=True
    tensValue=0
    onesValue=0
    receivedEdge=None

    #Less than 70ms between pulses: this is the same digit still being transmitted
    #   Increment the value of the current digit
    #More than 70ms: switch to the next digit
    singleDigitMilliseconds = datetime.timedelta(milliseconds=70)

    #If this timeout is reached, it's the end of the number
    wholeNumberWaitTime = 200

    # wait here until a rising edge is detected
    #logger.info('Waiting on GPIO pin: ' + str(GPIO_Alert))
    while True:
        #Arduino sends a pulse when you flash it, start the Raspberry Pi second.
        #The Arduino should boot faster in the event of a power outage.
        if isFirstIteration:
            receivedEdge = GPIO.wait_for_edge(GPIO_Alert, GPIO.RISING, timeout=-1)  #wait forever until a kick-off pulse
        else:
            receivedEdge = GPIO.wait_for_edge(GPIO_Alert, GPIO.RISING, timeout=wholeNumberWaitTime)  #wait for up to waitTime ms

        #calculate the timing metrics for this signal
        signalTime = datetime.datetime.now()
        signalInterval = signalTime - lastSignalTime
        lastSignalTime = signalTime
        #debugging: logger.info('signalInterval: ' + str(signalInterval.total_seconds() * 1000))

        #determine what digit to increment
        if (signalInterval < singleDigitMilliseconds) or isFirstIteration:
            isFirstIteration=False
            if isTens:
                tensValue+=1
            else: #isOnes
                onesValue+=1
        elif receivedEdge is not None:  #signalInterval >= singleNumberMilliseconds:
            if isTens: #shift to ones
                isTens = False
                onesValue+=1
            else: #isOnes
                  #can't shift to next digit, so the number is complete.
                  #This should not happen. Once the number is done,
                  #the wait should timeout and receivedEdge should be None.
                return ((tensValue -1)*10) + (onesValue -1)
        else: #timeout, so number is complete.
            return ((tensValue -1)*10) + (onesValue -1)

###############################################################################
# The main method
###############################################################################
def main():
    logger.info('Starting HotWaterNotifier')
    referenceVoltage = 1.1
    lowVoltageCount=0

    try:
        while True:
            #This will block until it receives signals from the Arduino.
            #It will only return once a completed number is received.
            sensorReading = waitReceiveNumber(GPIO_Alert)

            #calulate the voltage from the Arduino sensor reading
            voltage = (sensorReading * referenceVoltage ) / 1023;
            logger.info('sensorReading: ' + str(sensorReading) + ', voltage: ' + str(voltage))

            if sensorReading <= lowSensorReading:
                lowVoltageCount+=1  #increment
                if lowVoltageCount >= maxLowVoltageCount:
                    logger.info('Low voltage alert')
                    send_email_alert()
                    lowVoltageCount=0  #reset the counter because we sent an alert
            else:
                lowVoltageCount=0  #reset the counter because a good voltage was received

    except KeyboardInterrupt:
        logger.info('Keyboard interrupt received')
        GPIO.cleanup()       # clean up GPIO on CTRL+C exit

    GPIO.cleanup()           # clean up GPIO

###############################################################################
# The test email method
###############################################################################
def testEmail():
    logger.info('Starting HotWaterNotifier')
    referenceVoltage = 1.1
    lowVoltageCount=0

    try:
        send_email_alert()

    except KeyboardInterrupt:
        logger.info('Keyboard interrupt received')
        GPIO.cleanup()       # clean up GPIO on CTRL+C exit

    GPIO.cleanup()           # clean up GPIO

###############################################################################
# A Global Variable
###############################################################################
# Set up a log file
logger = setup_custom_logger('HotWaterNotifier')

###############################################################################
# Call the main method.
#
# Call testEmail() here instead if you want to test the email capability.
###############################################################################
if __name__== "__main__":
    main()
Script to Start the Python CodeSH
This is the script that crontab calls to start the Python code
#!/bin/bash
# run_HotWaterNotifier.sh
# Launch the notifier

cd /home/pi/hotwater
sudo python HotWaterNotifier.py

Comments

Similar projects you might like

Arduino Control AC Water Heater temperature

Project tutorial by Mohannad Rawashdeh

  • 16,526 views
  • 0 comments
  • 11 respects

Water Waste Monitor

Project tutorial by MD R. Islam

  • 2,383 views
  • 2 comments
  • 5 respects

Water Level Monitor

Project tutorial by NewMC

  • 15,787 views
  • 3 comments
  • 23 respects

Mobile Water Level Tracker

Project tutorial by Kutluhan Aktar

  • 3,478 views
  • 0 comments
  • 6 respects

Arduino-Powered Water Bottle

Project tutorial by Md. Khairul Alam

  • 10,127 views
  • 5 comments
  • 35 respects

Hot Water Solar Boiler Project

Project in progress by Pleemans

  • 7,432 views
  • 2 comments
  • 28 respects
Add projectSign up / Login