Live build

Solar hot tub

A Canadian Spa Toronto — 1,100 litres — heated by five 405 W panels, Victron SmartSolar chargers, a Smart Shunt on the 24 V lithium bank, and a 150 L drum with a copper coil inside. The spa's own controls sit at 32°C. A Raspberry Pi will not run the elements unless Heat enable is closed, the Smart Shunt is live, the drum is under 85°C, and there is surplus — or leftover PV to dump on a full battery. It will not pump into the tub past the target set on the Pi (default 40°C). The numbers below are live. The story of the build is underneath.

Read-only: no controls, no GPIO, no LAN details. Live solar plant · Jump to how it works · Kit and cost · Control logic.

Since 20 August 2026

Solar generated

Everything the panels made, worth £0.00 at 26.11p/kWh.

Used for heat

Into the 600 W drum elements. That would have been £0.00 on a typical UK electricity bill.

Public kWh totals only start on 20 August 2026 — the hardware was already running, so this is not lifetime generation from day one. Listed spend is £1400.34, of which £900.34 is already in the plant — eight Oxford panels are bought, not fitted. Cabling and other oddments are still extra. At that unit rate the panels have made so far. Kit and cost.

Right now

Offline

No live signal

Awaiting first report from the tub.

Tub target 40.0°C — the pump stops there. Default is 40°C; set it on the Pi Live tab.

Last report never

Heat

Mode

Surplus staging

Available for heat

Battery (shunt)

Elements

Heaters stage from Smart Shunt surplus. Dump uses leftover PV when the battery is full. Below ~650 W a 600 W element would discharge the bank, so surplus stays off — unless dump is on and leftover PV would otherwise be clipped. Control logic.

Last night

Overnight standing loss

Overnight standing loss appears after an evening and morning tank reading (21:00–07:00 London) with the heaters off.

The keg lives in a garden shed, not in the house. It is wrapped, then boxed in sealed 100 mm Celotex on all six sides so overnight loss is standing heat through that jacket — not a bare drum in the wind.

Lost °C by night

Degrees the keg cooled 21:00–07:00. Hotter evenings should lose more — that is the jacket working against a bigger tank-to-air gap.

Overnight loss appears after an evening and morning tank reading.

Loss vs evening tank temperature

Hotter store should lose more overnight — that is the jacket against a bigger tank-to-air gap. Pink nights had heaters and pump off.

Need a couple of overnight losses before a pattern shows. Pink is standing loss (heaters and pump off).

Bank and efficiency

Bank watch

100 Ah class, two 12 V packs in series. Equivalent full cycles from shunt current (discharge Ah ÷ 100). Victron SoC is the same coulomb counter — it cannot say if the cells have faded.

SoC this week
Time ≥ 95%
Lowest bank
Heater sag R

Apparent resistance is rest voltage minus voltage under the 600 W elements, divided by shunt current — cells, BMS, and cables together. A slow rise over months is the tell. Floor is 25.2 V; time under it this week: .

State of charge

Last 7 days from Instant Readout. LFP is flat in the middle — watch the floor and time spent full, not the slope.

SoC appears after the Smart Shunt has reported for a minute.

Amp-hours through the bank

Charged vs discharged each London day. The two should be close over a week if dump is eating leftover PV instead of sitting clipped.

Daily Ah appears after the next midnight roundup (and a re-fold of the last 7 days).

Where the energy went

Last complete day. These are budget checks, not lab efficiencies — leftover drum heat, overnight loss, and weather all move the ratios.

Drum capture

Tank rise vs element kWh

Coil

Tub gain vs drum drop while pumping

Array vs sky

Yield vs horizontal GHI × array

Heat from PV

Element kWh / panel yield

Clipped PV

SoC ≥ 95% with sun and heaters off

Flow while pumping

Hall meter median

Temperatures

No probes reporting yet.

Relays

No relays reporting yet.

Last 7 days

Download CSV

The Pi reports about every 30 seconds. Charts keep 5-minute averages for the last day and 15-minute averages before that. The CSV is the raw samples — times, temps, PV, voltage, and when the pump and heaters were on — for later analysis. Hover or tap a chart to read the value at that time.

PV power

Solar chart fills in after the next few Instant Readout reports.

Battery voltage

Voltage chart fills in after the Smart Shunt or chargers have reported for a minute.

State of charge

SoC fills in after the Smart Shunt has reported for a minute.

Temperature

Chart fills in after a few status reports (about a minute).

Transfer and heat

Transfer is the copper-coil pump. Heaters can run at the same time, so this chart tracks them separately from the headline status.

Transfer chart fills in after the next few status reports.

Charts: Lightweight Charts.

Daily archive

Download CSV

After midnight London time, yesterday is folded into one Upstash point — energy, what blocked the loop, and Open-Meteo weather. That is about 365 points a year. The CSV is the analysis file.

Daily PV yield

Daily yield appears after the first midnight roundup.

Daily energy

PV is Victron yield. Heaters are 600 W × on-time. Transfer is tub heat gained while the pump ran (1,100 L × ΔT).

Energy totals appear after the first midnight roundup.

Drum gain and loss

Daytime gain is morning (~07:00) to the day's peak. After peak is cooling/transfer down to ~21:00. Overnight is standing loss 21:00–07:00 into that morning (positive = heat left the insulated keg in the shed). 150 L × ΔT.

Drum gain and overnight loss appear after the next midnight roundup.

What that would cost on a UK bill

The spa would normally heat from the wall. Here the 600 W drum elements run from the solar battery instead. Drum heaters is that electricity priced as if you had bought it from the grid (26.11p/kWh). All solar is everything the panels made that day, same price — including kWh that only charged the battery.

Costs appear after a day with heater or PV totals.

Running total

Running total appears after the first complete day.

Daily sunlight

Global horizontal irradiance from Open-Meteo. Compare with PV yield to see weather vs the controller.

Weather appears after the midnight roundup fetches Open-Meteo.

Daily temperatures

Daily temperatures appear after the first midnight roundup.

Daily hours

Daily hours appear after the first midnight roundup.

The build

How the solar hot tub works

This is a solar heat-exchanger on a Canadian Spa Toronto, not a plug-in spa heater. The shell holds about 1,100 litres. Five 405 W panels charge a 24 V lithium bank through four Victron SmartSolar chargers — a 100/30, a 100/50, and two 150/45s. Eight more panels from Oxford are bought and not on the roof yet. A Smart Shunt is the official bank meter. That bank feeds two 600 W elements in a 150 L drum. A coiled copper pipe inside the drum is the heat exchanger: when the drum is hotter than the tub, a transfer pump pushes tub water through the coil. A Raspberry Pi in the garden decides when that is safe.

The idea

The old meter had a simple rule: heat when the battery sat high, stop before it sagged. The Pi still keeps a 26.2 / 25.2 V floor, but it stages the elements from Smart Shunt surplus — watts charging the bank — and dumps leftover PV into the drum when the battery is already full. Two physical switches are permits, not thermostats. Opening Heat enable stops the elements only. Transfer enable is a separate permit for the pump, so leftover tank heat can still move after heating stops.

The live numbers at the top of this page are pushed from that Pi about every 30 seconds. This site cannot reach the garden. There are no controls here on purpose.

The tub

The spa is a Canadian Spa Toronto. It holds about 1,100 litres. The tub's own Canadian Spa controls are set to 32°C — that is the soak target on the topside panel. The Pi does not talk to that panel. Solar heat is a separate loop: a 150 L drum, a copper coil, a transfer pump, and a hard 40°C cap on the tub so a stuck permit cannot cook the soak. That cap stops the pump, not the elements — the drum is allowed to keep storing heat up to 85°C.

The heat store

The keg is not in the house. It sits in a garden shed. The drum is wrapped, then enclosed in a sealed 100 mm Celotex box on all six sides so the overnight drop is standing loss through that jacket — not a bare tank in the wind. Last night on this page is that number: 21:00–07:00 London, degrees and kWh out of 150 L, and how fast it fell. Hotter evenings should lose more; a bigger gap to shed air should cool faster. Nights where the heaters or the pump ran are called out so they do not look like insulation failure.

The bank

Two Renogy 12 V 100 Ah packs in series, so the shunt sees a 24 V 100 Ah class battery. Instant Readout gives voltage, current, and SoC. That SoC is coulomb counting against the 100 Ah setting — it will not tell you if the cells have lost capacity. The useful tells on this page are equivalent cycles (discharge Ah ÷ 100), time spent at or above 95%, how close the bank gets to the 25.2 V heat floor, and voltage sag when a 600 W element turns on. Sag includes the BMS and the cables. A slow rise in that milliohm number is what to watch, not a single day.

Drum capture is tank rise versus element kWh. Coil is tub gain versus drum drop while the pump ran. Array vs sky is yield against horizontal GHI times the array size — the roof is not horizontal, so it is a weather check, not a datasheet performance ratio. Clipped PV is hours at ≥95% SoC with the array still making power and the heaters off.

What is in the box

Tub
Canadian Spa Toronto
About 1,100 litres. Topside controls set to 32°C. The Pi does not drive that panel.
Brain
Raspberry Pi Zero WH
Python service, SH1106 OLED, LAN dashboard. ARMv6, ~512 MB.
Solar
Five 405 W panels on the roof
Three from Facebook Marketplace (£170) and two Maxeon SunPower Performance 6 all-black from Bimble Solar (£165.40 including delivery). Two on a Victron SmartSolar 100/30, three on a 100/50. Eight more panels from Oxford (£500 for the lot) are bought and not installed.
Chargers
Victron SmartSolar 100/30, 100/50, 2×150/45
100/30 and 100/50 paid £97.95 and £134.12. Two 150/45s are fitted but currently off, as is the 100/30. Instant Readout over Bluetooth — no VE.Direct cable.
Battery
24 V 100 Ah LiFePO4
Two Renogy 12 V 100 Ah Core packs in series, from Facebook Marketplace, £269.10 the pair. BMS stays in circuit. A Victron Smart Shunt is the official bank voltage.
Bank meter
Victron Smart Shunt
Instant Readout over Bluetooth. Heat stages from this current and SoC when it is live. Chargers are for PV and yield, not surplus.
Heat store
150 L stainless keg + copper coil
Amazon keg, £50.98. Two 600 W 24 V elements heat the drum water. Tub water is pumped through a coiled copper pipe inside. The keg sits in a garden shed: wrapped, then a sealed 100 mm Celotex box on all six sides. The drum can keep heating to 85°C after the tub has hit 40°C.
Transfer
Heat-transfer pump + Hall flow meter
Pushes tub water through the copper coil when the drum is hotter. Stops at 40°C in the tub. sourcingmap SEN-HZ21WI G1/2" meter (£12.79) is the dry-run interlock on BCM 5.
Senses
Three DS18B20 probes
Hot tub, heat tank, ambient air. Victron Instant Readout over Bluetooth from the chargers, plus a Smart Shunt for bank voltage, current, and SoC.
Switching
Industrial 24 V marine relays
They switch the heavy heater voltage. Pi GPIO only drives 24 V optocoupler modules; those then pull in the marine relay coils.
Permits
Heat enable + Transfer enable
Dry-contact switches. Heat enable is the elements; Transfer enable is the pump. Open means that loop is off.

What the kit cost

These are the pieces I actually paid for and wrote down. Two Renogy 12 V 100 Ah Core LiFePO4 batteries were £269.10 the pair on Facebook Marketplace. Eight more panels from Oxford (£500) are in that total and not on the roof yet. Cabling, lugs, the copper coil, the pump, the Pi, and the rest of the garden clutter are still missing — they add up, but they were not the headline spend. String fuses are in the pile; I did not write that amount down.

PieceWherePaid

Two Renogy 12 V 100 Ah Core LiFePO4 batteries

Series for a 24 V 100 Ah bank. £269.10 for the pair, Facebook Marketplace.

Facebook Marketplace£269.10

Three 405 W solar panels

Facebook Marketplace. On the roof now — part of the live 2,025 W array.

Facebook Marketplace£170.00

Two 405 W Maxeon SunPower Performance 6 panels

All-black, MCS approved, end-of-line stock from Bimble Solar. £35 of the total was delivery. On the roof now.

Bimble Solar£165.40

Eight solar panels (not installed)

Facebook Marketplace, Oxford. £500 for eight. Still on the ground — not on the roof, not on a charger, not in Instant Readout.

Facebook Marketplace (Oxford)£500.00

Victron SmartSolar MPPT 100/30

Two of the five roof panels. 100 V, 30 A, 12/24 V. Currently powered off.

Amazon£97.95

Victron SmartSolar MPPT 100/50

Three of the five roof panels. 100 V, 50 A, 12/24 V. Currently the live charger.

Amazon£134.12

Two Victron SmartSolar MPPT 150/45

Fitted and in the Instant Readout map. Currently powered off, waiting for strings.

Not recordedNot recorded

Victron Smart Shunt

Official 24 V bank voltage, current, and state of charge. Heat uses this reading when Instant Readout is live.

Not recordedNot recorded

150 L stainless keg

Heat store in a garden shed. Wrapped, then sealed 100 mm Celotex on all six sides. Two 600 W 24 V elements and a copper coil sit inside.

Amazon£50.98

sourcingmap SEN-HZ21WI Hall flow meter

G1/2", DC 5 V, 1–30 L/min. Transfer-loop dry-run interlock on the Pi (BCM 5). £12.79.

Bought£12.79

PV string fuses

Fuses for the panel strings. Bought; the amount was not written down with this update.

Not recordedNot recorded
Listed above£1400.34

Five 405 W panels is 2,025 W on the roof. Eight more from Oxford (£500) are bought and not fitted. Four Victron SmartSolar chargers sit in the map: the 100/30 takes two panels, the 100/50 takes three, and two 150/45s are fitted but currently off, as is the 100/30. The 100/50 is the live charger. A Victron Smart Shunt is the bank voltage. The 150 L keg is the heat store, not the spa. Recorded spend is £1400.34, of which £900.34 is already in the plant. Payback against the live kit will take a year of the running totals at the top of this page — the extra chargers, the shunt, string fuses, and oddments are still extra.

When the heaters run

Both elements are about 50 A at 24 V, so they are staged. Industrial 24 V marine relays switch that heating current — not the Pi, and not the small optocoupler modules on its GPIO. One element is the base load. The second only joins when there is enough surplus. Staging uses the Smart Shunt: charging watts into the bank, plus whatever the elements are already drawing. That sum is “available for heat.” A stale or missing shunt keeps the heaters off — the chargers cannot stand in for surplus. Clouds get a 20 s hold-on and a 45 s hold-off so the relays do not chatter.

A 600 W element cannot absorb 300 W of solar on its own. Below about 650 W of surplus both heaters stay off, otherwise they would discharge the bank. That is the right call while the battery still has room. Once the bank is full the MPPTs would otherwise throttle and throw that 300 W away. Dump mode turns heater 1 on for leftover PV (about 80 W or more) so the drum takes it. The rest of the 600 W comes from the battery until SoC drops out of dump, the sun fades, voltage hits 25.2 V, or the drum hits 85°C. Better a short dip from a full pack than clipping the array. Heater 2 still needs about 1,250 W. Solar-only never dumps.

  • Off

    Battery under 25.2 V (until it recovers to 26.2 V), Heat enable open, heat mode off, drum at 85°C, no live Smart Shunt, or no drum temp.

  • Surplus — one heater

    Heat enable closed, shunt live, voltage in the 26.2 / 25.2 V band, drum under 85°C, and about 650 W available (charging watts plus any element already on). The tub can already be at 40°C.

  • Surplus — both heaters

    Same as one heater, and about 1,250 W available. Heater 2 waits 1.5 s. The second 600 W should come from surplus, not only the battery.

  • Dump leftover PV

    Normal from 95% SoC, holiday from 90%. If the array is still making about 80 W, heater 1 turns on even when that is under 650 W — otherwise a full battery clips the panels. The 600 W element takes the rest from the bank until SoC, sun, 25.2 V, or 85°C stops it. Solar only never dumps.

Heat enable must be closed and the drum must be under 85°C. The tub sitting at 40°C does not stop the elements — they heat the drum, not the spa shell. A missing drum probe fails safe to off. Relays chatter is avoided with hysteresis: heat resumes below 82°C in the drum. Heater 2 waits about 1.5 seconds after Heater 1 so both 50 A loads do not slam on together. 26.2 / 25.2 V is still a hard floor, not the staging rule.

When the pump runs

Transfer is a separate loop from the elements. The pump pushes tub water through the copper coil in the drum. Heaters can already be off — leftover drum heat should still move. Opening Heat enable does not stop the pump. If the tub hits 40°C the pump stops, but the elements can keep charging the drum. The pump also stays off if the tub is under 30°C, the tank is under 60°C, or within 2°C of ambient: that looks like the heating return hose (or the tub probe) is in air, not water, and pumping then would run dry.

  • On

    Transfer enable closed, tank at least 60°C (stays on down to 50°C), tank at least 2°C hotter than the tub, tub between 30°C and 40°C, and the tub not within 2°C of ambient. Heaters may already be off.

  • Off

    Transfer enable open, tank under 60°C (below 50°C once running), delta under 1°C, tub at 40°C, tub under 30°C, tub within 2°C of ambient, or a missing tub/tank probe. A restart or loop error also drops the pump.

Fail-safe

Everything starts off. A restart or a loop error drops the pump and both heaters. A missing drum probe drops the heaters. A missing tub probe drops the pump only — the drum can still heat. A dead Instant Readout on the Smart Shunt drops the heaters — leftover drum heat can still transfer. Voltage from the chargers is not enough to stage heat. The 40°C tub cap stops the pump, not the elements; the drum may keep rising to 85°C. The pump also pauses if the tub is under 30°C, the tank is under 60°C, or sitting within 2°C of ambient (hose-out / dry-run guard). The LiFePO4 BMS stays in the circuit; the Pi's 26.2 V / 25.2 V window is a hard floor, not a battery manager.

GPIO is 3.3 V only. That side only pulls 24 V optocoupler inputs. The coils of the industrial marine relays are 24 V from a separate supply with a shared ground; those contacts switch the heater voltage. The OLED and temperature probes take 3.3 V from the Pi, never 5 V. A 1.3" SH1106 screen on the Pi shows one big reading at a time — temps, PV, volts, and why something is blocked.

The software

The runtime is a Raspberry Pi Zero WH. It is ARMv6 with about half a gig of RAM, so there is no Docker and no IDE running on it. I edit on a Windows PC and copy Python onto the Pi. A systemd service called httm runs a 1 Hz control loop: read probes and Victron Instant Readout, decide, drive three automatic relays, update the OLED, then push a sanitised snapshot here.

Instant Readout is Bluetooth listen-only — no VE.Direct cable. The LAN dashboard is for wiring and thresholds. This public page is read-only: temperatures, solar, the Smart Shunt, relay state, and the two enable switches. No GPIO map, no charger keys, no controls.

Control logic

About once a second the Pi reads the probes and the Victron Instant Readout, decides, then drives three automatic relays: pump, heater 1, heater 2. Relays 4–6 stay unused. The two dry-contact switches are permits, not thermostats. Thresholds live in config on the Pi; these are the numbers this build uses.

CheckThis build
LoopAbout 1 Hz. Relays start off and drop off on restart, crash, or a loop error.
Heat enablePermit for the two 600 W elements only. Open means heaters off. Does not stop the pump.
Transfer enablePermit for the heat-transfer pump. Independent of the heaters.
Battery voltageHard floor only: heaters on at 26.2 V, off under 25.2 V. Staging is surplus watts from the Smart Shunt, not this voltage.
Surplus bandsAvailable = shunt charging watts + heaters already on. Under ~650 W both off; ~650–1,250 W heater 1; above ~1,250 W both. Hold-on 20 s, hold-off 45 s.
Dump leftover PVNormal from 95% SoC, holiday from 90%. Heater 1 on if leftover PV is about 80 W or more, even under 650 W, so a full battery does not clip the array. Solar only never dumps.
Heat modeLive-tab choice, no restart: solar only (surplus, no dump), normal (surplus + dump above 95%), holiday (dump from 90%), or heat off (elements forced off).
Second heaterJoins above ~1,250 W available, after a 1.5 s delay. Dump does not force heater 2 on leftover PV.
Heat store150 L drum with a coiled copper pipe inside, in a garden shed: wrapped, then sealed 100 mm Celotex on all six sides. Elements off at 85°C, resume below 82°C. Pump can still run while the drum is at the cap.
Hot tubPump off at 40°C, resume below 39°C. Elements keep heating the drum. Pump also off under 30°C (resume at 31°C). Separate from the spa topside set to 32°C.
Hose-outIf the tub reading is within 2°C of ambient, the pump pauses (resume when the gap is 3°C). Treats a return hose or probe in air as a dry-run risk. Heaters can stay on. Missing ambient skips this check.
Transfer deltaPump on when tank − tub ≥ 2°C, off when the delta falls under 1°C. The tank must also be 60°C to start (hysteresis: stop below 50°C) so the coil is not circulating lukewarm store water.
Missing sensorsNo drum reading: heaters off. No tub reading: pump off, drum can still heat. No live Smart Shunt: heaters off, pump still allowed.

Available for heat is the Smart Shunt charging watts plus any heaters already on. Dump (normal above 95% SoC, holiday from 90%) also counts leftover PV, so a full battery with 300 W of sun still turns heater 1 on. A stale or missing shunt keeps the heaters off; the pump can still run. Heater 2 waits about 1.5 seconds after heater 1 so both 50 A loads do not slam on together. The Pi never switches the heater current itself: GPIO is 3.3 V into 24 V optocoupler modules, and industrial marine relays take the heating voltage.

If you copy this

This is a personal garden build, written up so the rules are visible — not a product, a kit, or a certified spa or heating control. The numbers above are what this tub uses. They are not a claim that the same window is safe on your battery, tank, or wiring.

Hot water, lithium storage, and high-current 24 V DC can injure people and start fires. I am not an electrician, this is not advice, and there is no warranty. If you reuse any of the ideas, the design, the wiring, and whatever happens next are yours. I am not responsible for injury, damage, or a copy that behaves differently from what you read here.

Where it is

This is on the real Canadian Spa Toronto now — panels, chargers, battery, 150 L drum, copper coil, transfer pump, and the two 600 W elements. The Pi in the garden is running the loop. This page is the public window.