Baetz residence · Lafayette CA · hot-water repipe

Zoned Hot Water Recirc

Sheet P-1 · Rev A
2026-09-25

Hot water gets repiped in PEX under the house. A supply manifold at the water heaters sends four new loops out to room manifolds (master toilet wall, WIC 03, WIC 02, pantry), plus the existing laundry/powder loop, and each loop comes back to the heaters through its own motorized valve. Home Assistant runs the pump only for the zone someone is about to use, such as when a shower light turns on, and stops once that zone's return pipe is hot. This replaces the current pump that circulates through the whole house.

Why the old loops failed

2 loops · 1" PEX · bare in the ceiling · long branches

The old system was two big loops of 1" PEX covering the whole house. They ran through the ceiling with no insulation, with long branches down to the rooms. Each complaint has a physical cause:

  • It took forever to heat up. 1" PEX holds 0.030 gal/ft, more than three times as much as 1/2" (0.009). A 250 ft loop holds about 7.5 gal. Without the pump, a shower drawing ~1.2 gpm of hot has to flush most of that first, which takes several minutes.
  • Some showers never got hot. Two causes stack up.
    • Long branches are dead legs. The pump only moves water around the loop, never down a branch. So even with the loop hot, the branch to a far shower holds cooled water that has to be flushed first.
    • The loop cools along its length. With no balancing valves, the shorter loop steals most of the pump's flow. A bare 250 ft 1" loop loses ~6,250 Btu/h; at 0.5 gpm the water drops about 25°F by the far end (ΔT = loss ÷ 500 × gpm). 125°F out means roughly 100°F at the last branch, before the branch cools it further.
  • Running the pump wasted a lot. Heat loss depends on pipe surface area and temperature, not on use. Two big loops kept hot all day lose heat whether anyone is running water or not.
  • It heated the house. The pipe was bare and in the ceiling, so its heat went straight into the rooms below. 500 ft of bare 1" pipe at 120°F gives off about 12,500 Btu/h, about 3.7 kW. That's a large space heater running in the ceiling, and in summer the AC has to pump that heat back out. The new loops run under the floor in the crawl space with 1" wall insulation. California's recirculation rules (RA4.4.7) also don't allow recirculation piping run up into the attic and back down.
OldNew
Loops2, whole house4 new + 1 kept, one room each
Pipe1" PEX, bare, in the ceiling3/4" master, 1/2" others, 1" wall insulation
Water per loop~7.5 gal0.8–2.2 gal
Uncirculated pipelong branchesmanifold → fixture only, inside the wall
Circulateswhole house, for hoursone loop, ~1 min, on demand
Flow balanceshort loop steals flowone valve open at a time, nothing to balance
Heat into the house~12,500 Btu/h into the rooms while running~0 between uses; crawl space, insulated

Old-loop figures assume ~250 ft per loop, from the house footprint. Set the real length in the Energy section if you know it. Heat loss uses the Taco chart for 1" PEX-a (25 Btu/h·ft bare at 120°F).

How it works

schematic · not to scale

Four loops from one manifold. Heaters → mixing valve → a supply manifold beside the heaters, under the rear deck. Each outlet starts a loop: four new ones, plus a fifth outlet that picks up the existing laundry/powder loop. The loop runs out under the house (3/4" to the master bath for the tub and multiple heads, 1/2" to the single baths and kitchen) to the existing room manifold in the wall, which becomes the middle of the loop. Its outlets keep feeding that room's fixtures as they do today. The loop enters at one end of the manifold, and a new connection at the other end carries it back to the heaters.

The valve is on the far end. Each loop's back leg lands on a return manifold next to the supply manifold. Per loop, in flow order: isolation valve, spring check, temperature sensor on a copper stub, normally-closed motorized valve. That valve is your "turn off the far end" switch. Closed, the loop still delivers hot water to every fixture (it acts like a normal dead-end line), it just can't circulate. Open, and with the pump running, hot water flows around that one loop. After the valves the four loops merge into one ECM pump and a check valve, then feed the cold inlet of both tanks.

Why the valve lives at the heater end. The far end of every loop physically comes home to the heaters, so all four valves, sensors, the pump and the controller sit on one board in one place. Nothing electrical goes in the bathrooms except a button.

Demand, not a timer. A trigger opens that zone's valve. Triggers are a button (the code-compliant one), a bathroom light, or presence. After 5 s the pump starts. The ESP32 stops it when the zone's return hits 102°F or rises 10°F, or after 5 min, whichever comes first. These are the California Title 24 demand-control limits (RA4.4.9). They live in firmware, so a Home Assistant outage can't leave the pump running. The rest of the day nothing moves, so the pipes don't bleed heat into the crawl space.

Safe when it fails. No valve ever sits between the heater and a faucet. A dead controller, valve, or pump means you wait for hot water like a house without recirc. Check valves stop cold water from backfeeding a loop through single-handle faucets.

Thermal battery. Your heaters already go to 150°F on solar surplus. The tank stores that heat. Running the pump through all loops mostly heats the crawl space, so the new design keeps "open all valves + pump" as an optional mode rather than the default. See Controls.

Layout

drag anything · saved in this browser
The floor plan is the 2023 permit set from the brain. Positions follow your notes: heaters in the top-right corner under the rear main deck, room manifolds beside the master toilet, in WIC 03, in WIC 02 and on the pantry's east wall. South is the top (rear-deck) edge. Laundry and powder stay on their existing loop. It gets the 5th outlet on the supply manifold and the 5th valve lane on the return manifold, and no new pipe is bought for it.
loop out, 3/4" master / 1/2" others (supply manifold → room manifold) 1/2" twig to fixture (by zone) loop back to heaters, 1/2" or 3/4" existing loop, kept (laundry + powder) manifold fixture (dashed orange ring = line over 10 ft from its room manifold)

Pipe run estimate

Runs are routed square to the joists with 2–3 ft of drop at each end. A loop's back leg over 80 ft becomes 3/4". Purge is the water in the whole loop (out + back) that the pump pushes before the return sensor sees hot, at ~1–1.2 GPM (1/2" back) or ~2 GPM (3/4"). Out-leg drop is the pressure lost between the heaters and the room manifold at that loop's peak hot draw (master 5 gpm for tub fill + multiple heads, others 2.5 gpm for a 1.8 gpm shower plus a 1.2 gpm lav, both under California flow limits). Under ~10 psi is fine on typical 55–70 psi street pressure; orange means consider 3/4". Parts quantities add 15% waste.

Parts list

researched 2026-09-25 · quantities follow the layout
How the picks were chosen. Four research passes priced each part at pro suppliers (SupplyHouse, PexUniverse, Apex, Midwest Supply, Home Depot) and against Amazon and AliExpress. California law (AB1953) requires anything touching drinking water to be lead-free with a listing mark (NSF 61/372, cUPC, CSA), and loops at up to 140°F need CL5-rated PEX. Most marketplace plumbing parts show no listing, so an inspector can't accept them. Where a marketplace item is genuine and certified, it's marked OK. Several picks are Amazon listings of the real brand. Electronics that never touch water (buttons, probes, relay board) are fine from Amazon or AliExpress. Before buying: measure static water pressure (sets 1 vs 2 expansion tanks), check the existing loop's pipe size (sets the manifold variant), confirm each wall manifold's connection type, and ask U.S. Solid for the valve's NSF certificate. AliExpress prices are from search results; its pages blocked direct checks.

Tick items as you buy them (saved in this browser). opt rows are optional and left out of the core total.

Install

in order
Permit first. A repipe needs a permit in Lafayette (Contra Costa building). For an alteration, California's prescriptive path allows only demand recirculation with manual on/off control (RA4.4.9). That is why each zone gets a push button. Lights and presence are extras layered on top. The plan set needs a wiring diagram for the pump and controls (3C-REN). Tell the inspector: "manual demand recirc, 102°F / 10°F-rise cutoff, 5-min max, check valve in loop."
  1. Lock the layout

    Drag the manifolds, heaters and fixtures on the plan above to match reality. Calibrate the scale against a known dimension (e.g. the 21' master bedroom). Any fixture line with an orange ring runs over 10 ft from its room manifold. Export the JSON. Any loop back leg over 80 ft upsizes to 3/4" automatically.

  2. Heater room

    • Check the two tanks are piped reverse-return (first in, last out) so they share load. Re-pipe while you're in there if not.
    • Install the expansion tank on the common cold header, precharged to house pressure.
    • Install the mixing valve on the combined hot outlet, feeding the supply manifold. Heaters can reach 150°F; the mixer sends ~125°F to the house.
    • Add the return tee into the cold header downstream of the expansion tank, upstream of the split to both heaters. Also run a throttled branch to the mixer's cold port, per Caleffi, to avoid temperature creep. Never use the tank drain as the return inlet (A.O. Smith).
  3. Mount the supply and return manifolds

    On a board beside the heaters under the deck: the mixing valve feeds the 1" supply manifold, with an isolation valve on each of the four 3/4" outlets. Mount the return manifold right next to it so each loop's out and back ends sit side by side. Label the outlets Z1–Z5. Z5 reconnects the existing laundry/powder loop: cut it in at the heaters, supply end to outlet 5 and return end to lane 5.

  4. Run each loop out

    From each supply outlet, run PEX-A out to its room manifold (3/4" to the master, 1/2" to the others): master toilet wall, WIC 03, WIC 02, pantry east wall. Hang it every 32" or closer, leave expansion offsets on long straight runs, and keep PEX 6" or more from any flue. At the room, come up through the floor to the existing wall manifold and connect to its inlet, with an isolation valve below the floor.

  5. Open the far end of each room manifold

    The loop has to flow through the manifold, so it needs a way out at the end opposite the inlet. On most manifolds that end is capped or plugged: swap the cap for an adapter to PEX. If the manifold is closed at that end (e.g. a cast one-piece body), tee the return off the last fixture outlet instead. Check each manifold's size and connection type (copper sweat, PEX crimp or ProPEX) before buying adapters. The existing fixture lines stay as they are.

  6. Bring each loop home

    From that new far-end connection, run the back leg down through the floor and home to the return manifold (1/2", or 3/4" if over 80 ft). Label both ends with the loop number and color.

  7. Build the return manifold

    Per loop, in flow order:

    • isolation ball valve
    • spring check
    • 6" copper stub with the DS18B20 clamped on (stainless clamp, thermal paste)
    • 3-wire motorized valve (the far-end valve)

    Merge into the header, then: pump (arrow toward heater), main check, isolation valve, cold-header tee.

  8. Pressure test before insulating

    Typically 100 psi (or working pressure) for 15 min with no drop. Confirm the number with the inspector. Get the rough inspection signed off now.

  9. Insulate

    1" wall on the loop-out and back legs, the manifolds and the board, including elbows and tees. Fixture lines get at least 1/2". Buy copper-size ID (PEX OD = CTS). Use elastomeric instead of fiberglass near vents or anywhere rodents nest. Leave the sensor stubs wrapped separately so you can reach them.

  10. Wire the controller

    • 24 VDC supply. Each valve on one SPDT relay: COM → +24 V, NO → red (open), NC → blue (close), yellow → 0 V. Relay drop = valve drives closed.
    • Pump on a relay-switched 120 V outlet. Retire the Tuya plug.
    • DS18B20s daisy-chained on one 3-wire 1-Wire bus, 4.7 kΩ pull-up, under ~10 m. Keep low-voltage wire away from 120 V.
  11. Commission

    • Flash ESPHome and read sensor addresses from the log. Warm each copper stub by hand to map it.
    • Click each valve and confirm 3–5 s travel and that it closes when the relay drops.
    • Purge air: turn on "Recirc all zones" with a faucet open in each zone, 2–3 min.
    • Trigger each zone from cold and time it. The far zone should finish in about 2 min. If not, raise the Taco speed or upsize that return.
    • Fault tests: unplug a probe mid-cycle (pump must stop, log shows nanF). Close a zone's hand valve (5-min cap must fire). Confirm the pump never runs with all valves closed.
    • Crossover test: pump off, run a single-handle faucet in each zone, and watch that return. If it goes cold, that check valve leaks.
    • Track kWh for 2–4 weeks against the baseline below.

Controls

ESPHome + Home Assistant

Split of duties:

  • ESP32 (ESPHome) owns the hardware and the safety rules: 102°F / 10°F rise / 5 min, pump interlock, probe-fault stop. It exposes four "Demand Zn" buttons (one per loop) plus a "Recirc all zones" switch.
  • Home Assistant decides when: buttons, lights, presence. It only ever presses a demand button, so an HA crash can't leave anything running.

Your existing entities map straight in (I guessed Birdie's bath is Bath 03; swap Z2/Z3 if not):

ZoneTriggers
Z1 Master bathlight.main_bathroom_lights
Z2 Bath 03 (WIC 03)light.birdie_bathroom_lights
light.birdies_bathroom_vanity
Z3 Bath 02 (WIC 02)light.guest_bathroom_shower_lights
light.guest_bathroom_lights
Z4 Kitchen (pantry)kitchen presence (new), 06:00–21:00
Z5 Laundry + powder (existing loop)powder light (new); laundry exempt
Thermal battery. The "PW = 100% → pump" automation today circulates hot water through the whole loop. The tanks at 150°F are the actual battery. Circulating mostly moves that heat into the crawl space. Keep the automation raising the setpoint and drop the pump step. If you want the pump anyway, it calls "Recirc all zones", which the firmware caps at 15 min. That mode goes past the Title 24 run limits, so leave it disabled for inspection.

ESPHome: recirc-manifold.yaml

esphome:
  name: recirc-manifold
esp32:
  board: esp32dev
  framework: { type: esp-idf }
logger:
api:
ota:
  - platform: esphome
wifi:
  ssid: !secret wifi_ssid
  password: !secret wifi_password

one_wire:
  - platform: gpio
    pin: GPIO32              # 4.7k pull-up to 3V3

sensor:   # addresses come from the boot log; warm each pipe by hand to map them
  - { platform: dallas_temp, address: 0x0000000000000001, id: t_z1, name: "Return Z1 Master bath",    update_interval: 2s }
  - { platform: dallas_temp, address: 0x0000000000000002, id: t_z2, name: "Return Z2 Bath 03",    update_interval: 2s }
  - { platform: dallas_temp, address: 0x0000000000000003, id: t_z3, name: "Return Z3 Bath 02", update_interval: 2s }
  - { platform: dallas_temp, address: 0x0000000000000004, id: t_z4, name: "Return Z4 Kitchen",        update_interval: 2s }
  - { platform: dallas_temp, address: 0x0000000000000006, id: t_z5, name: "Return Z5 Laundry+powder (existing)", update_interval: 2s }
  - { platform: dallas_temp, address: 0x0000000000000005, id: t_hot, name: "Heater outlet",           update_interval: 10s }

switch:   # internal: only firmware drives the hardware
  - { platform: gpio, pin: GPIO2,  id: v1,   internal: true, restore_mode: ALWAYS_OFF }
  - { platform: gpio, pin: GPIO15, id: v2,   internal: true, restore_mode: ALWAYS_OFF }
  - { platform: gpio, pin: GPIO5,  id: v3,   internal: true, restore_mode: ALWAYS_OFF }
  - { platform: gpio, pin: GPIO4,  id: v4,   internal: true, restore_mode: ALWAYS_OFF }
  - { platform: gpio, pin: GPIO16, id: v5,   internal: true, restore_mode: ALWAYS_OFF }   # map pins to your board's relays
  - { platform: gpio, pin: GPIO14, id: pump, internal: true, restore_mode: ALWAYS_OFF }
  - platform: template        # thermal-battery / purge mode: all valves, 15 min cap
    name: "Recirc all zones"
    id: all_mode
    optimistic: true
    restore_mode: ALWAYS_OFF

globals:
  - { id: req,    type: int, initial_value: '0' }   # request bitmask from HA
  - { id: active, type: int, initial_value: '0' }   # zones circulating now

button:   # HA presses these
  - { platform: template, name: "Demand Z1", on_press: { lambda: 'id(req) |= 1;' } }
  - { platform: template, name: "Demand Z2", on_press: { lambda: 'id(req) |= 2;' } }
  - { platform: template, name: "Demand Z3", on_press: { lambda: 'id(req) |= 4;' } }
  - { platform: template, name: "Demand Z4", on_press: { lambda: 'id(req) |= 8;' } }
  - { platform: template, name: "Demand Z5", on_press: { lambda: 'id(req) |= 16;' } }

binary_sensor:
  - platform: template
    name: "Recirc pump running"
    lambda: 'return id(pump).state;'

interval:
  - interval: 1s
    then:
      - lambda: |-
          // Title 24 RA4.4.9/10 limits enforced here, independent of HA.
          const float MAX_F = 102.0f, RISE_F = 10.0f;
          const uint32_t MAX_MS = 300000, ALL_MS = 900000, VALVE_TRAVEL_MS = 5000;
          // ponytail: consts, not HA number entities. Upgrade: template `number` if tuning gets frequent.
          const int N = 5;
          static uint32_t start[N] = {0}, all_start = 0;
          static float t0[N];
          switch_::Switch *v[N] = {id(v1), id(v2), id(v3), id(v4), id(v5)};
          sensor::Sensor  *t[N] = {id(t_z1), id(t_z2), id(t_z3), id(t_z4), id(t_z5)};
          uint32_t now = millis();
          bool ready = false;

          if (id(all_mode).state) {                     // thermal battery: all open, time-capped
            if (all_start == 0) { all_start = now; for (auto s : v) s->turn_on(); }
            if (now - all_start > ALL_MS) id(all_mode).turn_off();
            else ready = (now - all_start > VALVE_TRAVEL_MS);
          } else if (all_start != 0) {
            all_start = 0; for (int i = 0; i < N; i++) if (!(id(active) & (1<<i))) v[i]->turn_off();
          }

          for (int i = 0; i < N; i++) {
            float f = t[i]->state * 1.8f + 32.0f;          // NaN if probe fault
            int bit = 1 << i;
            if (id(req) & bit) {
              id(req) &= ~bit;
              if (!(id(active) & bit) && !isnan(f) && f < MAX_F) {
                id(active) |= bit; start[i] = now; t0[i] = f; v[i]->turn_on();
                ESP_LOGI("recirc", "Z%d start %.1fF", i+1, f);
              }
            }
            if (id(active) & bit) {
              bool done = isnan(f) || f >= MAX_F || f >= t0[i] + RISE_F || (now - start[i]) > MAX_MS;
              if (done) {
                id(active) &= ~bit; if (!id(all_mode).state) v[i]->turn_off();
                ESP_LOGI("recirc", "Z%d stop %.1fF after %us", i+1, f, (now-start[i])/1000);
              } else if (now - start[i] > VALVE_TRAVEL_MS) {
                ready = true;                               // valve fully open
              }
            }
          }
          // Interlock: pump only runs with at least one valve open. Never dead-head.
          if (ready && !id(pump).state) id(pump).turn_on();
          if (!ready && id(pump).state) id(pump).turn_off();

HA automation, one per zone

alias: Recirc Z1 master bath
mode: single
triggers:
  - trigger: state            # code-compliant manual control
    entity_id: event.master_bath_recirc_button
  - trigger: state            # replaces "Hot Water Pump - On with Main Shower Light"
    entity_id: light.main_bathroom_lights
    to: "on"
conditions:
  - condition: numeric_state  # loop still warm -> skip
    entity_id: sensor.recirc_manifold_return_z1_master_bath
    below: 95
actions:
  - action: button.press
    target: { entity_id: button.recirc_manifold_demand_z1 }
  - delay: "00:15:00"         # debounce: mode single swallows repeats

# Z2 Bath 03: light.birdie_bathroom_lights
# Z3 Bath 02: light.guest_bathroom_shower_lights, light.guest_bathroom_lights
# Z4 Kitchen: kitchen presence 06:00-21:00
# Z5 Laundry+powder (existing loop): powder light; laundry is exempt, trigger optional

Rewire the existing automations

# Edit automation.hot_water_pump_thermal_battery_pw_100_heater_150degf:
actions:
  - action: switch.turn_on
    target: { entity_id: switch.recirc_manifold_recirc_all_zones }   # firmware caps it at 15 min
# then disable automation.hot_water_pump_on_with_main_shower_light and
# unplug/retire switch.hot_water_pump_socket_1 (the Tuya plug).

Runnable check for the firmware: during commissioning, unplug one probe mid-cycle. The log must show Zx stop nanF and the pump relay must drop.

Energy

estimate from your layout

The biggest win is going from 24/7 circulation to demand. Title 24's own performance model charges continuous recirc at 9.8× the distribution energy of a non-recirc system; manual demand is 1.75×. Insulation is the second biggest win. Numbers below use your layout lengths and the Taco heat-loss chart (120°F water, 68°F air; a cold crawl space loses more). Adjust the inputs.

COP defaults to 3.0 on the assumption your A.O. Smith units are heat-pump water heaters. Set 1.0 if they're resistance. All three figures are estimates. NREL modelling found demand recirc roughly energy-neutral against a compact, insulated system with no recirc. The real gain here is against the current 24/7 loop, plus 30–36% less water down the drain while waiting.

Simpler option

if you want fewer moving parts

Thermostatic balancing valves, no controller

Put a Caleffi ThermoSetter 116 (~$236) or Watts CircuitSolver on each return and trigger one demand pump. Each valve throttles its own loop once it's hot. No motorized valves, no ESP32, no firmware.

Why it's not the pick: every trigger heats all four loops, about 4× the heat per event (~30 therms/yr equivalent extra at 5 events/day). The four valves also cost ~$950, against ~$150 for four motorized valves plus sensors. Neither valve fully closes: the Caleffi bottoms out at Cv 0.23 and CircuitSolver bypasses 0.2 GPM.

Off-the-shelf single-zone demand

Grundfos ALPHA HWR-D has a native push-button demand mode (102°F / 5 min). Metlund D'MAND is a turnkey demand pump. Either is a fine one-box answer if you ever give up on zoning. Both treat the house as one loop, which is what you're trying to get away from.

Not an option: Viega ManaBloc home-run manifolds. Viega says they're not for use with recirculation loops.

Sources