A Modern Thermostat with a Vintage Look
Everyone loves a Nixie tube. The warm orange glow makes any information seem like it’s coming from a bygone electronic era. In this article, Joe shares his journey designing a thermostat for his home using Nixie tubes, an ESP32, and a Raspberry Pi.
This is the story of the creation and evolution of the NixieStat thermostat, which, as the name implies, has a Nixie tube display. The NixieStat was designed for tinkering. The coding is in C++ and quite extensible. Since it is network-ready, it can use both local and remote sensors and can be integrated into Home-Assistant open-source home automation. It has a standard HVAC interface [1], though only the heating functionality was tested because I only have heat in my house. I hope to figure out a way to test additional functionality in a more “real-world” way in the near future.
Nixie tubes are vintage electron tubes filled with a neon/argon gas mixture. They display digits or symbols surrounded by a characteristic orange light when high voltage is applied. The IN-17 Nixie tubes I used in this project are shown mounted to the project board in Figure 1. (See Editor’s Note for further explanation.)
IN THE BEGINNING
The story began about 10 years ago, when I first moved into my new old house. The house has long, squat, hot water radiators and a natural gas water boiler controlled by one of those old round Honeywell thermostats with a mercury switch in it. This worked, but there was a temperature swing ≥10°F in the house during the winter. Between that and my incurable love for Nixie tubes, the NixieStat was born.
That year I went to Poland for Christmas with my wife (at that time my fiancée). Before the trip, I wired my house to get an idea of just what temperatures I was dealing with. I set up Dallas Semiconductor DS1820 1-Wire temperature sensors on the radiator, outside, and in both the living room and the dining room where the thermostat was located. My fiancée said that the house looked like it had spiders!
When we arrived in Poland, she turned the furnace on (which also has hot water radiators) and set the water temperature for the boiler to what I assumed was a temperature she deemed comfy for the current weather. In my ignorance, I thought she controlled the house temperature this way. Ignorance is so blissful! This gave me the idea that if I put the temperature swing on the radiators I could have a nice steady temperature in the room with the thermostat.
I learned later that in Europe, many homes have thermostatic valves on each radiator, controlling the temperature in each room. This works well at keeping temperatures steady in the rooms, but alas, without either the pretty Nixie display to see the temperature, or the fun of rolling my own. In fact, they are pretty much set to one’s comfort without a clue to the actual temperature in the rooms. In other words, Europeans have achieved bliss in that aspect.
THE HARDWARE JOURNEY
My first rendition of the thermostat used an ATMega1284 microcontroller. It has lots of memory, relative to the ATMega328, for the code, and lots of IO ports for the display, buttons, and relays. I also had four IO ports on which to hang one or more DS18S20 sensors, to get the temperature at the thermostat, the radiator temperature and the outside temperature, as well as any other locations I wanted to monitor and to which I could get a pair of wires. My code was written (and still is) in C++ and worked pretty well.
The thermostat comprised two PCBs with through-hole components, both of which I designed and laid out using the gEDA electronic design tools. (Sadly these tools are no longer in development or supported.) One PCB was for the CPU, the solid-state relays, the 160V power supply using a Maxim 1771 regulator, and a DS1307 real-time clock chip. The second PCB had the actual Nixie display and its transistor drivers.
This first version was nice, though it lacked much functionality for changing the temperature or logging the data. Also, someone in the household found that the wires in the basement were handy for hanging laundry. There were a couple of suspects, but I won’t name names. These reasons spurred me to build the second version of the thermostat—one with remote temperature sensors that did not rely on wires being threaded around my house.
For the second rendition, I chose the Espressif ESP32 family of processors. I wanted to keep the footprint of the first boards so that I could use the original display board. The new processor allowed me to access remote sensors over the network. Also, at 100mm length, it was still in the 10 boards for $5.00 category at PCBWay.
Version 2 of the NixieStat was a learning tool on many fronts. I was forced to switch to KiCad for designing the boards. I also moved to using surface-mount device (SMD) components where I could. I rolled my own ESP32 boards in the hope of getting enough IO ports, but alas, I still needed to use I2C port expanders to get the extra ports for driving the display. I also moved the 160V high-voltage circuit to the display board where it belonged.
My biggest take-aways from this version were first, SMD soldering is not as easy as it looks, especially for chips the very small footprints. Second the code for multiplexing the display using the I2C port expanders was not for the faint of heart. Between these two issues, though the NixieStat performed its thermostatic duties well, it did not reliably display the temperatures. Being pragmatic, I started to look for solutions to this problem that didn’t entail hours and hours of troubleshooting.
This led to the third edition. I ran across the Banana BPI-Leaf-S3 MCU and ESP32-S3-Devkit. Both of these had plenty of pins and the same pin layout. In fact, they had all the pins my project needed, with a few to spare. With this change I was able to ditch the I2C port expanders. I also removed a few more SMD components that I had trouble with and replaced them with through-hole components.
THE SOFTWARE JOURNEY
The software journey was as adventurous as the hardware journey. Working in the world of ATMega AVR processors taught me a lot about tiny stacks, the Harvard Architecture, and the incredible number of things you can do with 16K or less of RAM. I would have never imagined one could write any substantial C++ code on such tiny devices. It’s like I was back programming in the 1980s, except that I didn’t have to write the code in assembly language. The const keyword makes it all possible by keeping constant data out of RAM until needed, with only the small cost of a dance to shuffle data between flash and RAM.
Moving to the ESP32 platform allowed me to stretch my arms! It provided a full RTOS [2], RAM out the wazoo, and a built in Wi-Fi network. Life was sweet!
My first point of order was to migrate the configuration menus and have the entries use more generic values. I created a Variant class for the configuration variables that keeps the data as strings and has helper member functions for converting the strings into what I needed.
Next, I added the code for networking and handling the Mosquitto message bus. I then created a JSON interface to allow simple messages to be used to pass temperature and configuration data to and from the NixieStat. The serial port is still available, and required, for the initial configuration of the network and Mosquitto bus broker information.
Provisions were made in case the Wi-Fi network failed and no sensor data was being received over the message bus. In those circumstances, the code will fall back to being a dumb thermostat, just like the old Honeywell, using the DSB1820 temperature sensor and a simple algorithm.
I used generic off-the-shelf ESP32 boards for remote sensors. I started with the NixieStat code and removed all functionality related to the thermostat. The code now had a minimal set of menus for configuration, and the code to query MAXIM DS18B20 temperature sensors for their current temperature—which then gets packaged into a JSON message and posted to the appropriate topic on the message bus. These sensors worked well, though they had some drawbacks, such as not being battery-operated, and it was painful to update the code. Sadly, the menu system was somewhat gaseous and did not leave enough RAM to allow for over-the-wire updates. On the plus side, these have not required any maintenance, since I installed them, and I still use them in my house today.
The third iteration of the hardware required a lot of little changes to the code—enough to start an overall clean-up and refactoring to make amends for my sloppy coding habits. Changes such as those can take a lifetime, and I know there are still plenty of skeletons still hiding in the closets.
One of the bigger changes I made was to make the Menu system even more C++ by creating a virtual base class for the menu items, then building custom classes for each menu type (int string, bool, and so on), which have specific code to handle data conversion and presentation. I can’t say it is any less gaseous than before, but the code is much easier to read and maintain.
My next big change was expanding on my original JSON messages to facilitate integrating NixieStat into Home Assistant for monitoring and configuration. The other changes were IO port-related, as I moved from the port expanders and back again.
EXTERNAL SUPPORT SCRIPTS
Once I began using the ESP32 and the message bus, I needed a Raspberry Pi for support. Using the RaspAP module, I created a separate Wi-Fi access point for connecting all the sensors and NixieStat. This ensured that the network would continue working in spite of any disruptions to my home network. I also installed the Mosquitto message bus broker and a few support programs and scripts onto the Raspberry Pi.
The first support program listens for ping messages that were sent from each device for some sort of discovery. Each message had the name of the device and the time its clock is set to. A pong response is sent back with the real time if the device’s clock was too far off from reality. This program is written in C++ using the Qt SDK. As for discovery, it was somewhat handy to me, as the user, for seeing which devices were alive—but not for much else.
The other programs that run on the Raspberry Pi include a Python script that listens for Bluetooth BLE messages from the Govee temperature sensors. These are now my go-to remote sensors. They are small, reasonably inexpensive, battery operated and simple to set up.
The script converts the info from the Govee sensor into a properly formatted JSON message, which is then sent to the message bus just like any other temperature message. A second script listens for messages on the temperature topic and sends them out on individual topics with names formatted as <devc_name>/temperature. This makes them easier to be captured and displayed by Home Assistant. The last script simply listens for temperatures from the radiator sensor and provides a running average of the temperature over time.
For the curious, I chose Perl where I could, because it is lightweight and great for handling text data. To aid in this, it has a nice module for interacting with the message bus. This resulted in very simple scripts. Unfortunately, Perl does not have an existing Bluetooth BLE module, so I had to resort to Python to receive messages from the Govee sensors.
TEMPERATURE Control
I use a Proportional–Integral–Derivative (PID) controller to achieve a near-constant room temperature. The PID I used was derived from code I found on GitHub from Brett Beauregard [3]. I made a few changes to the parameter passing, but the functionality and algorithm remained unchanged.
Not counting the complexity of the PID, the equation was pretty straightforward:
tradiator = pid(troom) – toutside + C
where pid is the pid function, tradiator is the target radiator temperature, troom is the current room temperature, toutside is the outside temperature, and C is a constant, which can be configured. The target temperature for the room and any tunings for the PID have already been set and considered constant. This takes into account the room temperature and the outside temperature to come up with a desired radiator temperature, to which I then add a hysteresis.
The room temperature is determined by taking the lowest temperature from a list of sensors. This list can have from one to eight devices listed. In my house I have three sensors, one each for the three main downstairs rooms. Having multiple sensors ensures that the rest of the house will not be chilled when company is gathered in a single room. Granted, that room may get a little warm.
COMMUNICATION, CONFIGURATION, AND CONTROL
Version 1 of the NixieStat had only a serial port for configuration and data logging. This was somewhat painful, to say the least.
Migrating to the ESP32 enabled network access as well as more resources, such as RAM and flash and IO ports The serial port is still needed for the initial setup of the Wi-Fi and MQTT broker parameters. Those and all the settings can be accessed through a comprehensive menu system. Once the NixieStat is connected to the network and MQTT broker, all the settings can be accessed by sending JSON messages sent to various topics (which I will describe later).
In all, there are about 116 settings that can be configured through the message bus. The values for these are sent out continuously, one at a time. It takes about a minute to send them all out. These can be monitored over the message bus by subscribing to the topics (take note of the wildcard):
<mqtt_ident>/configuration/+
where <mqtt_ident> is the name given to the NixieStat during configuration.
The messages sent to each of the respectively named topics have the following format:
{“device”: <device_name>, “menu”: “<menu_name>”, “<parm_name>”: “<parm_value>”, “name”: “<descriptive name>”}
The important values are <parm_name> and <parm_value>. The other values are helpful for knowing what the parameters are, if you create Home Assistant screens.
Configuration values can be changed by sending a message to the <mqtt_itent>/configure topic. This message has the format:
{“topic”: <reply_topic>, “cmd”: “<parm_name>=<parm_value>”}
If the value was successfully changed, its new value will be sent to the appropriately named topic as described. Otherwise an error message will be sent to the <reply_topic>, indicating the error in the format, value, or name of the parameter.
TESTING, TESTING, TESTING…
The significant changes made to the menu system and how parameters are accessed justified creating a simulator to run the thermostat through its paces. I wrote a simple Perl script to simulate the temperatures of the house, radiator and the outside. The script listened for status messages from the thermostat to determine if the furnace was turned on or off. When on, the simulator would raise both the simulated radiator temperature and the simulated room temperature. When the status message indicated the furnace was turned off, the simulator would lower the simulated radiator and room temperatures.
— ADVERTISMENT—
—Advertise Here—
The model was far from accurate. Fortunately, it was close enough, and uncovered a few bugs, which gave me confidence that the thermostat was working correctly. Once again, at least to me, this proved that you can never do enough testing and that simulators are great tools for doing the testing!
PERFORMANCE
Overall, the NixieStat has performed as well as I hoped. The display is behaving well and always turns on correctly when it is supposed to. As for controlling the temperature of the house, it is performing like a champ. So far, this winter has had some very cold weather, along with some warmer periods. In all, the room temperature never strayed too far from the set temperature.
The screenshots in the article are from Home Assistant and show the NixieStat’s performance. Figure 2 shows the outside, radiator, and house temperatures. The house temperature is actually the temperature of the coldest of the three rooms being tracked. You can see both the hysteresis of the radiator and how the radiator temperature rises and falls inversely to the outside temperature.
Figure 3 shows the house’s reaction to life in the house. You can see that in the late afternoon, the oven was on and heating up two of the rooms but not the living room, which was the controlling temperature at that time and most of the other times. The quick spike of cold was when I opened the front door to check the mail.
Figure 4 shows effect the oven and open door had on the operation of the furnace. The radiator temperatures dropped, in part, from the warmth of the day, but also from the heat of the oven. When the door was opened, however, you can see where the furnace was turned on briefly to compensate.
Figure 5 is a good representation of performance when the outside temperature takes a radical drop. You can see the radiator warm up as the outside temperature drops. All the while, the rooms keep a nice, constant temperature.
CIRCUIT DESCRIPTION
The NixieStat is comprised of the CPU board, the display board, and a small keypad. The CPU board has the processor, real-time clock, IO drivers for controlling the Nixie cathodes, relays, and the main power supply.
Relays, Furnace/HVAC Interface, and Power Supply— Figure 6 is the schematic of the relays, the furnace/HVAC interface, and the power supply. The interface is pretty close to the standard six-wire interface that includes 24V AC lines.
In more modern systems, the R line is switched to the pins for the desired operations—Heat W, Cool Y, Fan G, and Valve O.
For those who have older systems, there is a section of PCB jumpers that can be flipped to allow the Heat and the Cool to be controlled by the pins W and Y for heat and G and O for cooling. These are set up for simple make-or-break connections to turn on and off the heat and cooling.
The power supply is built to handle the 24V AC and converts it to 24V DC and 5V DC. The 24V DC is available for the display board, and the 5V DC is fed to the ESP32, which, in turn, outputs the 3.3V DC used in the rest of the circuit.
CPU, RTC, and Connectivity—Figure 7 shows the schematic of the CPU, RTC, and connectivity. The real-time clock is provided by a PT7C4339 chip with a 32.788kHz crystal and the option to use a battery or supercap to retain time during power outages. I personally use a 7µf 5V supercap for this.
Also shown are most of the connections to the display board, the HVAC interface, the keypad connector, the on-board temperature sensor, and expansion ports, which allow adding different relays.
There are three female connectors. J3 and J4 are for the display board, and J8 is for a future relay expansion board. J3 has the digit-select lines, high-voltage enable, and the voltage at the common collector (VCC). The high-voltage enable signal, HV-EN, is inverted, and a high level disables the display. The digit control lines DIG1 through DIG7 control the digit to be enabled. VCC is the raw voltage from the bridge rectifier for the input voltage. J4 has signals for choosing the number displayed for the selected digit. J8 allows alternative HVAC/furnace control. The signals on it are directly connected to the processor to drive relays or whatever.
The cathode drivers are not shown. Each consists of a single MPSA42 NPN transistor that can handle the high voltage. Used as open-collector drivers, they pull the cathode of the Nixie tube to ground light it up.
Display Circuitry—The circuitry for the display is all on a second board shown in Figure 8. The board consists of four Nixie tubes, 10 neon bulbs, and a 160V high-voltage (HV) supply. The neon bulbs appear to the software as just another Nixie tube. The display is multiplexed with the tubes (or lamps) lit up sequentially. The anodes for selecting which Nixie tube of the neon lamps are driven directly by the signals DIG1 through DIG7. Breaking out the IO to the display like this allows flexibility to provide other options, such as seven-segment LED displays, with just a little work.
Each anode driver is constructed with an NPN/PNP transistor pair for driving the tube as shown in Figure 9. When the base of the Q1 is driven high, it turns on, which drops the voltage on the base of Q2 to turn it on. With the transistor turned on, the high voltage is applied to the anode of the tube. It is assumed that only one digit is turned on at a time. The power supply is designed around the common Maxim 1771 step-up switching controller. This makes a simple boost switching supply with a minimal number of parts.
Keypad Circuit—The keypad is a simple board with a single chip and a few passives. It uses the AT42Q2120 key pad controller from Atmel. It has four touch pads and communicates to the Esp32 using the
I2C bus.
LOOKING BACK…AND TO THE FUTURE
My journey with building the NixieStat was quite the adventure. While a lot of this project drew knowledge from my past experiences at both work and home, it still stretched me in areas such as board layout and network configuration. As for working surface mount devices, it opened my eyes to a new world! I especially like how you can have all the components for a project in just one little compartmentalized box. Now I just need to learn how to solder the parts correctly
NixieStat also opened my eyes to the family of ESP32 processors that are quite powerful for their size and price. With built-in wireless functionality, the FreeRTOS kernel and all the libraries really helped me focus on solving the problem without too much worry about the infrastructure needed.
I may make some design changes in future versions of NixieStat. First, I might redesign the display board. I have some ideas, but none are set in concrete. Second, I would also like to make an LED display for it. Third, someday I will rewrite the support script that currently reads ping messages in the Perl programming language (instead of C++), or remove it entirely. In any case, I want to modify the way NixieStat handles the temperature messages with old timestamps. This will help increase its robustness when failures occur. We will see what the future holds for these ideas!
— ADVERTISMENT—
—Advertise Here—
Editor’s Note: NIXIE TUBES
The following description of Nixie tubes is condensed from an article, “Nixie Tube Propeller Clock,” by Peter Csaszar (Circuit Cellar Issue 199, February 2007). It is the only article about a project using these tubes in the entire 38-year history of our magazine! The name “Nixie” is an abbreviation for “Numeric Indicator eXperimental No. 1” (NIX1), a term developed in the mid-1950s by Burroughs Corp. for marketing its new cold-cathode numerical display technology.
The Nixie tube is one of the most ancient electronic display technologies, predating LEDs, LCDs, and other contemporary displays. The tube is a cold cathode discharge tube with a shape resembling a vacuum tube, though its cathodes are not heated.
In principle, Nixie tubes are the same as the neon bulbs typically found in the illuminated switches on surge protectors. The ones Joe used in this project are shown assembled into the project board in Figure 1.
The glass enclosure contains neon gas at a low pressure. When a sufficiently high voltage is connected between the cathode (negative) and the anode (positive), the neon gas ionizes in a rather complex process. As a result, neon ions and electrons fly to the cathode and anode, respectively. The anode quietly swallows the electrons. However, the recombination of neon ions and electrons covers the cathode in a beautiful, ethereal orange glow.
This phenomenon is used in the Nixie tube to display characters (typically numeric). Multiple cathodes etched to the shape of each supported character are stacked on top of each other, standing behind the anode mesh that resembles miniature chicken wire. Energizing one of the cathodes and the anode produces a glow in the shape of the selected character. This delicate and fragile technology has been extinct from manufacturing for decades. Yet, the sparkling glass case and glowing neon has kept a group of Nixie enthusiasts loyal ever since.
Source: P. Csaszar, “Nixie Tube Propeller Clock,” Circuit Cellar, Issue 199, February 2007.
REFERENCES
[1] https://blog.supplyhouse.com/understanding-thermostat-and-low-voltage-wiring-for-every-home/
[2] https://freertos.org/
[3] https://github.com/br3ttb/Arduino-PID-Library
RESOURCES
Banana Pi | banana-pi.org
Dallas Semiconductor (Analog Devices) | analog.com
Espressif | espressif.com
FreeRTOS | freertos.org
Govee | us.govee.com
KiCad | kicad.org
Maxim (Analog Devices) | analog.com
Microchip Technology | microchip.com
Raspberry Pi | raspberrypi.com
PUBLISHED IN CIRCUIT CELLAR MAGAZINE • MAY 2026 #430 – Get a PDF of the issue
Sponsor this ArticleJoe Croft is a retired Software Engineer. He started his career without a college degree as a production technician in the telecom industry, with Reliance Comm/Tec. Slowly, through building text fixtures and play, he learned how to code—first in assembly, then C and C++ — which led him to become a software engineer. After 30 years, he switched to the world of Audio at Shure, Inc., where he spent the last decade of his career. His home projects have included the NixieNeon clock, the NixieStar clock, and other smaller projects, described on his website, nixieneon.com










