October 21, 2026 · 4 min read
A water heater, two pumps and a concrete slab
How a tankless water heater and Home Assistant heat my ground floor, and why the hard part is a slab that is always hours behind.
My ground floor is heated without a boiler. The heat comes from a Takagi tankless gas water heater, and it reaches a loop of tubing in a concrete slab through a heat exchanger. Two small pumps move the water, five probes watch its temperature, and Home Assistant decides when to run.
The hard part is the slab. It takes hours to warm up and keeps giving off heat for hours after the pumps stop, so a thermostat that only reacts to the room is always late: too cold in the morning, too warm by afternoon. Most of what follows is about working with that lag. Every screenshot is my actual Home Assistant panel, showing one day in October.
Two loops and an exchanger
The water is split into two loops that never mix:
- Heater loop. The heater pump pushes water through the Takagi and into one side of a heat exchanger.
- Floor loop. The floor pump moves water from the other side of the exchanger through the tubing in the slab and back.
The exchanger passes heat from one loop to the other. Five temperature probes on the pipes, wired to a small ESPHome board in the laundry room, read the supply and return of each loop plus the heater's hot-water outlet. A Shelly 2PM relay switches both pumps and measures how much power each one draws. The Takagi shows up on the same kind of power meter: it draws about 5 W idle and much more when it fires.

The drop across each loop tells me the floor is taking heat. During a run the heater loop leaves the Takagi around 115°F. The floor loop goes into the slab around 108°F and comes back around 91°F.

The thermostat
The thermostat is Versatile Thermostat, a Home Assistant integration. It runs a TPI (time-proportional) controller on a 30-minute cycle: each cycle it decides what fraction of the 30 minutes the pumps should run, from the gap between room and target and from the outdoor temperature. It skips any run shorter than 10 minutes, so the slab gets a real dose of heat or none, never short bursts.
There are four presets: Frost 55°F, Eco 70°F, Comfort 72°F and Boost 75°F.

Around the thermostat sit a few automations, each there because of the slab's lag:
- Overnight setback. Eco from 10 PM. At 4 AM Home Assistant reads the forecast. On a mild day it goes straight to Comfort, because stored heat plus a warm afternoon overshoots. On a cold day, if the room is under 71.5°F, it runs Boost until the room is rising, for at most 90 minutes, then Comfort.
- Scavenge. When a long run ends, the floor pump keeps going alone for up to six minutes to pull the heat left in the exchanger into the slab.
- Slab probe. A few times a night the floor pump runs alone for three minutes, and the return temperature stands in for the slab temperature. No sensor in the concrete needed.
- Pump exercise. If a pump has not run for a week, it runs for a minute at noon so it does not seize over the summer.
One day of heating
The charts below cover the 12 hours to 12:47 PM on October 21. The shaded bands are heat calls. The 4 AM recovery is the long block, and after it the thermostat settles into shorter runs.


Power shows the same story from the electrical side. The two pumps together draw about 145 W while they run. The Takagi's own draw jumps each time it fires.

Failsafes
Home Assistant runs the schedule, but the house should not go cold if Home Assistant stops. Every five minutes it sends the pump relay a heartbeat with the current room temperature. If the relay hears nothing for 30 minutes, a script on the relay takes over and holds 70°F on its own, using the last temperature it was given. With no temperature at all, it runs the pumps 10 minutes on and 30 off.
Four alerts cover the rest: the heater not heating during a run, abnormal pump power, a heat call that runs too long, and the probe board going offline. A guard also turns off any pump that has been running alone for 10 minutes, whatever started it.
What it costs
Heating gas is $384 this year through October 12, against $459 for the same period last year: 16% lower. Weather accounts for some of that difference; this post does not try to separate the two.
