The Water Watcher

Monitoring the pilot light on my water heater.

Similar projects worth following
This project consists of two parts (or three parts if you count the MQTT server through which they communicate, or four parts if you count the wifi router, or, hey, just stop counting).

One part is a sensor that monitors the status of the pilot light and main burner in my water heater. The other part is small LED matrix that graphically displays that status. The monitor broadcasts the status over MQTT every 30 seconds, and the display is updated based on the status messages.

My home's water is heated by storage tank water heater fueled by natural gas. Even though the water heater is fairly new, it uses a pilot light to light the main burner when it needs to heat the water. The advantage of that is that the operation is completely free of electricity. It's all driven by water pressure and some miraculous combination of thermal and mechanical stuff.

One of the disadvantages of using a pilot light is that it is always burning a little fuel, 24/7/365. It's amazing to me that systems like that are so widespread, but there we are. (My gas range, oven, and furnace do not have pilot lights. They use some kind of electrically-powered lighter to ignite the main burners when needed.)

At my house, there is another weird disadvantage: a few times a year, the pilot light goes out. I discussed that with a "water heater guy", and he immediately told me that he noticed on his way up my driveway that the exhaust outlet was not tall enough above the roof. According to him, the wind can hug the roof and sometimes blow back down the exhaust stack and blow out the pilot light. Is he right? I don't know, but I do know that something puts out the pilot light.

The pilot light only goes out 3-4 times a year. When it does happen, which is almost always noticed first thing in the morning, after I've waited in a stupor for an incredibly long time for the shower water to get hot, I trudge from the upstairs bathroom down into the basement. Then I do a kind of acrobatic headstand while using all three of my hands to go through the pilot light re-lighting procedure.

Sure, sure, I should talk to some more water heater pros and get this thing resolved for good. Someday. In the meantime, I've cooked up this remote monitoring system so I can at least know at a glance that the pilot light is out and the water is cold. My family's admiration for my level of technical geniosity more than makes up for the occasional tepid shower.

Even though this project is essentially complete, I'm going to give most of the details in the Project Log section since that's the best way to describe the several discrete pieces of information. So, start at the bottom and read upwards.

To make things immediately exciting, here is a short video of the Water Watcher in action:

The things you see in the video are the same things you see in the still images in this project's gallery, though the video gives a better look at the intentional flickering effect. The main picture in the gallery is the Water Watcher on duty in the master bathroom.


This is the esphome config for the device that reports on the water heater flame status.

x-yaml - 5.04 kB - 01/11/2021 at 01:22



This is the esphome config file for the monitor/display device.

x-yaml - 12.67 kB - 01/11/2021 at 01:22


  • The display animations

    WJCarpenter4 hours ago 0 comments

    Like just about every other artistic effort in my life, things looked a lot better in my mind than in reality. There is only so much you can do with a 5x5 display, even if it is RGB. As every civilized person knows, an RGB LED is just 3 LEDs in the same housing. With the M4 Atom Matrix, you can sometimes see the individual components of whatever RGB color you've selected. From a distance, it's OK, but up close it's a bit, uh, discrete. In the end, I had to simplify my grand designs so that the displays were more symbolically meaningful and less literally artistic. I didn't have enough pixels for good pointillist artwork. (I had one graphic that was intended to show a match being lit into a flame by an unseen force, but it turned out that I had to explain what it was to my test audience. After I explained it, the reaction was something like, "yeah, I guess it is".)

    Itt also occurred to me that I wasn't sure how I was going to orient the M5 Atom Matrix device in its final position. Even the otherwise reasonable "lots of flames shooting up" would look a bit wrong if the tops of the flames went sideways or down. In the end, I decided to use designs that were horizontally and vertically symmetrical, and I achieved some animations using built-in lighting effects provided by ESPhome's FastLED component.

    Here are the animations that I finally used. To see the actual animation effects, you should watch the video I provided earlier. These still photos are just for easy reference.

    The Water Watcher has one additional flame status state called "unknown" that the Water Bug doesn't send. The "unknown" status, as its name implies, is used when the Water Watcher doesn't know the flame status. That happens after it boots up but before it has received its first MQTT message. I just wanted something visually distinctive, so I used the ESPhome "addressable rainbow" effect.

    The "pilot_on" animation is displayed almost all of the time. There are only a few minutes a day when something else is displayed. I could have chosen to just have a blank display, but I wanted to have some indication that the Water Watch was working and not dead. I settled for flickering dots (using the ESPhome "addressable flicker" effect) in each corner of the 5x5 matrix to imply "a little bit of flame" to suggest the pilot light.

    Analogous to the "pilot_on" animation, the "full_flame" animation is meant to suggest "a lot of flame". It uses the same RGB orange color and ESPhome "addressable flicker" effect, but in this case all of the LEDs are involved. (I get to see this display when I step out of the shower.)

    I chose to have the Water Watcher give s distinctive display for the "dazzled" state. It's just these five RGB orange pixels in the center of the display, again animated with the ESPhome "addressable flicker" effect. I don't have any illusion that this suggests to anybody else what's going on during the "dazzled" state, but it makes me nerdly happy to be able to see it as a distinct state.

    Finally, the reason we're all here: the "pilot_off" state. The animation for this is a lot of blinking and flashing of RGB blue. I wanted it to be very distracting and unavoidable. Even though I told the household members what it means, I expect the normal reaction to seeing it will be to ask me what the heck it is; mission accomplished!

    Again, I recommend you want the short video to see the actual animations. The flickering effect for the flames is fairly effective (and it took some experiments with parameter tuning to get it to be so).

  • Flame on!

    WJCarpenter5 hours ago 0 comments

    I experimented with several different things, both on the Water Bug sensor board and the Water Watcher display board. I ultimately decided that the Water Bug sensor board would send a simple text message to indicate status of the water heater flame. As I mentioned earlier, the lux values read by the TSL2591 aren't calibrated, so it's not especially meaningful to push those around for consumption. They do happen to go to my Home Assistant server, and I also send the lux values over MQTT to be displayed in the debug log output of the Water Watcher. The latter is for my own convenience so that I didn't have to monitor both devices to see what was going on during troubleshooting.

    The Water Bug reads the lux value from the sensor every 30 seconds. Based on the experimental observations I mentioned earlier, it reduces that to "pilot_on", "full_flame", "pilot_off", or the special case of "dazzled". I keep track of the last time the Water Bug saw "full_flame". If it sees "pilot_off" within a short time after that, it sends "dazzled" instead of "pilot_off". The message recipient can decide what it wants to do about "dazzled" messages.

    ESPhome provides a mechanism for knowing the wall clock time, either from the Home Assistant server or from an NTP server. It makes it available in a convenient object form that measures seconds since the Unix epoch. I use the wall clock time as part of the display for my various sensor boards that have a built-in OLED display. The Water Bug doesn't have a display, and it doesn't otherwise need to know what time it is. The ESP32 provides a high-precision timer for elapsed time since boot-up, and ESPhome makes that available in its "Uptime" pseudo-sensor. It's granularity is in seconds, but that's fine for this particular use, and it let me avoid the need to deal with the ESP32's timer in native code.

    Once it has decided which piece of text to use for the flame state, the Water Bug sends that as a message on a particular MQTT topic. It then sends the raw lux value with the prefix "lux: " on the same MQTT topic. The only thing the Water Bug has to remember from earlier iterations of the 30 second loop is the last time it saw "full_flame".

    The Water Watcher listens on that MQTT topic and reacts to the messages. It logs every message it sees to the debug log, but it only actually reacts to flame status messages that it recognizes. And, in fact, it only reacts to recognized messages if they are different from the last recognized message that it saw. The reason for that is to avoid some visually distracting repainting of the LED matrix when there was no actual status change. The reaction takes the form of some kind of display on the LED matrix. Those are animated and keep running until a different reaction is triggered.

    The animations in the display and the rapid succession of the flame status and lux messages arriving over MQTT led to some kind of timing problem. I found that the listen-and-react model was not completely stable. I hypothesize that MQTT message strings were changing out from under my code that was trying to react to them. Since I don't know the internals of ESPhome's event looping all that well, I changed my code that listened to the MQTT topic so that it immediately copied the string and pushed it onto a FIFO queue as quickly as possible. The reaction code then read from that FIFO queue and triggered the display update when appropriate. That at least decoupled the timing and got rid of the instability.

  • The birth of the Water Watcher

    WJCarpenter5 hours ago 0 comments

    Periodically checking the sensor graphs on the Home Assistant web interface, or even on the mobile app, is not that great a way to be notified that the pilot light had gone out. It's more of an after-the-fact thing.. I started thinking about more appropriate mechanisms.

    I wanted something that I could leave in some fairly visible location, something that could give an indication that would catch someone's attention, and something that would be meaningful to other family members. I considered a lot of possibilities, but I happened to come across these M5 Atom Matrix devices from M5Stack:

    These are pretty amazing for a device that costs less than US$10. It's size is 24 x 24 x 14 mm (approximately 1in x 1in x 1/2 in) and houses a 5x5 RGB LED matrix. Inside is an ESP32. It's powered through a USB-C connector and exposes connectors for several GPIO pins. There is a reset button on one side. The top face (with the LEDs looking out) is actually an enormous button that you can read through a GPIO. And, of course, it's got the usual goodies you expect from an ESP32 device.

    Once I started looking at this device, I stopped thinking I could make something better. Sure, it might be way overkill for the job I am giving it, but did I mention it costs under 10 bucks? Others had already worked out how to use it with ESPhome (for example,, so I felt pretty confident about getting it to work. All that was left for me was to figure out how it should listen for status updates and what it should display for each possible state.

  • Measuring the light

    WJCarpentera day ago 0 comments

    The TSL2591 gives a read-out of light intensity in lux. For those numbers to be numerically accurate, the device should be calibrated under lab conditions. Luckily, I wasn't as interested in the accuracy and precision of the values as I was in distinguishing among "none", "a little bit", and "quite a bit". I still refer to the values as lux values, but I always remember that they are probably not accurate.

    I "installed" the sensor with its face against the little glass window. I still have plenty of room to observe the pilot light during the re-lighting procedure. Although I only stuck it in place with some tape, the location is behind a metal plate and some insulation that only gets removed a couple of times a year when I have to re-light the pilot light. It seems happy where it is. As you can see, "QC 12", that's my motto. The angle is intentional to get the I2C wires to the notched gap. The ESP8266 board is to the left, just out of the frame.

    After doing some experiments and observing the actual numerical outputs, I was happy to find a pleasant consistency.

    • With the pilot light turned off (and the cover back in place), the output was 0.
    • With the pilot light on, the reading is somewhere between 50 and 100; generally in the 70s.
    • With the main burner ignited, the reading shoots up to 50-60,000.
    • There was an unexpected effect when the main burner shuts off. The sensor drops down to a reading of 0 for a couple of minutes, then goes up to a reading of 3-400 for a minute or so, then settles down to the expected value of 50-100 for the pilot light. I compensated for that in software.

    After getting this far, I would periodically check the graphs of the values in Home Assistant. I was kind of surprised at how seldom the main burner comes on. It's usually only when someone has a shower or we use a fair amount of hot water for something else. It seems the water heater keeps things toasty with passive insulation most of the time. That's nice.

  • Seeing the light

    WJCarpentera day ago 0 comments

    I thought detecting the flame status of the water heater would be pretty simple, but it turned out to have some unexpected characteristics. The burner element and pilot light are enclosed at the bottom of the water heater. There is a small glass window, perhaps 2 inches by 2 inches, through which you can observe the pilot light and the main burner. The purpose of the window is so that you can see if you are successful when lighting the pilot light..

    It's kind of interesting how the pilot light and gas flow work. There is no electricity involved, so everything relies on mechanical and thermal interactions. If the pilot light goes out, the gas is completely shut off, including the gas supply to the pilot light itself. To light the pilot light, you hold down a switch which allows a tiny bit of gas to flow to the pilot light fixture. You then push a plunger switch, which I assume is a piezoelectric spark generator. It doesn't always light the pilot light on the first try. Even if it does, you have to continue holding the first switch for several seconds, or the pilot light will be extinguished. I believe that the pilot light itself warms up some small thermocouple enough to open up the gas supply for the pilot light. When the pilot light goes out, that thermocouple goes cold and cuts off the gas supply to the pilot light. Holding down the first switch bypasses the thermocouple mechanism. The reason to keep holding the first switch down after lighting the pilot light is to give the thermocouple a chance to get heated back up.

    Once the pilot light is lit and stabilized, you can turn up the main thermostat. That triggers the main burner to fire, being lit by the pilot light after the gas valve opens. It makes a very satisfying "whoosh!" sound when the main burner ignites.

    All of that is very easy to see when you look with your naked eye through that little window.

    The first thing I tried was using a common cadmium sulfate (CdS) ambient light sensor. Those cost only a few cents each. They work by providing a variable resistance more or less proportional to the intensity of light striking the sensor. I had a couple of those on hand, including one that was on a convenient break-out board. Unfortunately, nothing I did with the pilot light or the main burner made any difference to any of those CdS sensors I tried. For a while, I thought it was just a problem of getting the active face of the sensor pointed in the right direction. After a while, I concluded that they were just being stubborn and didn't want to tell me about the flames I was interested in.

    The next thing I tried was a GY-302, which is a breakout board containing a BH1750 illumination sensor. It provides a digital value over I2C, which is very convenient. Alas, it also did not care a whit about differences in the gas flame.

    I spent quite a bit of time studying spec sheets for both light sensors and for natural gas flames. I couldn't see why neither of those sensors reacted at all, but I did see that the most intense part of the natural gas flame is in a frequency range that is not a sweet spot for those sensors. I can't blame the sensors since their job is to detect sunlight or artificial light that is somewhere between white and yellow. You can see a little yellow in a natural gas flame, but most of it is blue.

    Armed with this half-baked understanding of the light that I wanted to detect, I went in search of a capable sensor. At last I found it: the TSL2591. They are widely available on breakout boards with I2C interfaces from the usual places. I imagine they are all more or less the same, but the exact board that I got was this one:

    To be honest, I hadn't completely convinced myself that this would do the trick based on my wavelength research, but I took a chance on it. I liked the fact that it advertised a very high dynamic range (600 million to 1). It also contains two distinct light sensors for different (overlapped) frequency ranges. You can use the...

    Read more »

  • Software platform: ESPhome

    WJCarpenter2 days ago 0 comments

    For some other projects, including the sensors already scattered around my house, I was already using ESPhome. The strength of ESPhome is that it makes it very, very easy to deploy standard sensors and a cornucopia of other devices. It provides the glue logic and mundane code to run the devices, and it also provides wifi connectivity, over-the-air updates, connectivity to Home Assistant, connectivity to an MQTT broker, and many other niceties. Most "programming" for ESPhome is done with YAML configuration, though there are escape mechanisms for doing things in C++ if and when you need them.

    You can find copies of my ESPhome configuration in the "Files" section of this project.

    At the time I deployed the Water Bug and planned to have it monitor the pilot light, I wasn't sure what I was going to do to receive the pilot light status messages at the other end. I thought I might have Home Assistant trigger some kind of notification (and I still might in the future). But, since ESPhome allows devices to both send and receive MQTT messages, and since I already had an MQTT broker on my network for other reasons, I decided to have the Water Bug simply broadcast the pilot light status over an MQTT topic every 30 seconds. That makes it independent of having Home Assistant, though Home Assistant is one of the clients that receives those messages.

  • The birth of the Water Bug

    WJCarpenter3 days ago 0 comments

    I have been thinking about how to get alerts for the extinguished pilot light only a slightly shorter amount of time than I have been aware of the problem. But what's the best approach? I know some old-timers would immediately starting thinking about doing it with a couple of 555 timers and a handful of paper clips, but I am not that guy.

    I already have some sensors sprinkled around my house. Most of them are simple ESP8266 boards with an attached OLED display and a BME280 temperature/pressure/humidity sensor. The attached OLED display shows those vitals when you are looking at the device, but I also relay the data to a Home Assistant server. 

    It seemed pretty natural to use something quite similar for the water heater problem. In that case, I didn't bother with the attached OLED display, mostly because I had some ESP8266 boards on hand that didn't happen to have one. Even if I didn't already have a few, these boards can be bought for only a few dollars; cheap enough for hobbyist use. I wired it up with the BME280 so that I could monitor the most boring, unchanging environment in my house (my basement).

    I called that device the Water Bug and stuck it onto the side of my water heater while I contemplated how to do the rest of the project. Here it is:

    I used one of those small 170-point breadboards, cut down the middle lengthwise, to mount the ESP8266.because most of those boards are not breadboard friendly. The nice thing about all of those small breadboards that I have seen is that the back has a paper layer that you can peel off so you can stick the breadboard to something. In this case, it's stuck to the side of my water heater. Dupont jumper wires provide the I2C connection to the BME280 (not visible in this picture).

    My plans for this sensor include:

View all 7 project logs

Enjoy this project?



Similar Projects

Does this project spark your interest?

Become a member to follow this project and never miss any updates