Skip to content
all writing
17 min readhome-assistantclimateenergy

The air-con was reading the wrong room: climate control in Home Assistant

On the hottest days last summer my ducted air-con was satisfied at 23 °C while the room I cared about sat at 28 to 32. So I measured every sensor in the house against the others and rebuilt the control around that room, the tariff and the forecast. The sensors, the sums, and what the first 34 °C day showed.

Last summer, on the 40 °C days, my ducted air-con ran all afternoon and was satisfied. Its thermostat read 23 or 24 °C. The room I most wanted comfortable was at 28 to 32.

Nothing was broken. The unit measures the air coming back to it in a hallway, and a hallway is not the room with afternoon sun on its window and a hot roof overhead. It did exactly what it was told, about the wrong room.

This is how I fixed that in Home Assistant: what the sensors around the house said once I compared them, the rules the new controller follows, the sums behind every threshold, and how the first hot day went. I built the controller on 28 September, and the first 34 °C day followed straight after.

What it replaced

Through last summer and most of winter the air-con ran on its own thermostat, and it ran a lot: about 15 hours a day in summer and 16 in winter. On hot days it was on for 91 to 99% of the hours from noon to 19:00.

In July the household moved to a power plan that is free from 12:00 to 15:00 for the first 24 kWh a day, about 20c a kWh either side of that (10:00 to 12:00 and 15:00 to 16:00), about 35c overnight (midnight to 6:00) and about 59c at peak (6:00 to 10:00 and 16:00 to midnight). So a fixed rule went into the battery automation: air-con on at noon, off at three. Running the air-con in the free window made sense. The rule itself didn't work well:

  • It needed a setpoint picked in advance, against weather it couldn't see, and the guess was often wrong.
  • Once the window closed the house warmed up again, and the air-con came back on at peak anyway.

Priced at today's rates, last summer's cooling cost about $2.10 a day on 30 to 35 °C days, $3.30 on 35 to 40 °C days and $7.70 on days over 40. Winter heating ran about $3.00 a day on nights of 8 °C or colder, and heating at peak made up half of one winter bill. The home battery didn't rescue the evenings: on an ordinary night it is empty by about 22:00, and air-con use only empties it sooner.

The thermostat was in the wrong room

Before writing any logic I compared the air-con's reading with the sensors in the room that matters, using Home Assistant's hourly statistics from last summer and last winter.

SeasonThe room, against the unit's readingWhat it meant
Summer, cooling+0.4 °C on average, +2 to +6 °C on 40 °C daysThe unit stopped while the room was still hot
Winter, heating−1.0 °C on averageThe unit stopped while the room was still cold

On an average day the error is small. On the days that matter it is the whole problem, and no amount of clever logic fixes a sensor that is 6 °C out.

So the controller ignores the unit's thermostat. It reads the room, from the average of two sensors, so either can drop out without blinding it. Then it asks the unit for a degree past the point where it really wants to stop: a degree colder when cooling, a degree warmer when heating. The unit chases its hallway number, and the controller turns it off when the room gets there.

What every sensor in the house said

Once I started comparing, the sensors disagreed with each other, and each disagreement explained something.

Two thermometers in one room rarely agree. Pairs in the same room differ by 1 to 3 °C. In one room, one of the pair follows that room's window sensor: 26.93 °C against 26.94 at 19:00. It is probably sitting near the glass, measuring the window rather than the room. Two sensors, averaged and kept away from windows, beat trusting either one.

The windows are heaters. Ten sensors on windows and doors report temperature. Over the 52 days last summer that reached 33 °C or more, I compared each window with the middle of the house:

WindowWarmer than the house coreWhen
West-facing+17 °C16:30
East-facing+8 °C10:30
The others+2.5 to +6 °CMid-afternoon

Every window with a blind or curtain takes sun at some point in the day, so the rule is blunt: on a day forecast at 27 °C or more, every blind, curtain and shutter stays shut from sunrise. If the forecast misses a hot day, 27 °C outside for ten minutes with the sun up closes them anyway. It costs nothing to run, and it keeps out more heat than any setpoint change could.

The roof space is hottest at exactly the wrong time. The ducts run through the attic, and a sensor there read 41 to 42 °C from 12:00 to 15:00 on 32 to 36 °C days last summer, and 48 to 50 °C on days over 40. That is the free window. At 10:00 the attic is 5 to 8 °C cooler, and by 18:00 about 10 °C cooler. Every bit of cold air the free window makes crosses that roof first.

The air-con's own power reading has always been zero. The unit has energy sensors, and they have never reported anything else. The real figures come from current clamps on the air-con circuits at the switchboard, and both clamps turned out to be fitted backwards: live power read negative. Home Assistant flips the sign. The clamp on the second, smaller unit also reads about 1.6 times high, so it is scaled by 0.62, a factor worked out by regression against the whole-house meter. Every efficiency figure in this post rests on those corrections, so they came first.

Outside, two readings. The air-con's outdoor unit reports the temperature outside, and the Bureau of Meteorology's daily forecast says what the day will do. For planning, the forecast matters more.

Doors and windows. When a window or door in the air-con's zones is open, the unit is off and nothing runs.

The rules, and the sums behind them

The room has a comfort band: heat below 20 °C, cool above 23 °C. Every threshold is a slider on a dashboard, so tuning never means editing YAML.

The band flexes with the price of power. The controller picks one of four regimes from the tariff, the day's plan and whether the house is exporting more than 2 kW of solar:

RegimeWhenCool: start → stopHeat: start → stopUnit set to
Pre-coolFree window, hot day21.0 → 20.3Not used19
Pre-heatFree window, cold nightNot used22.0 → 22.724
CheapShoulder, an unplanned free window, or solar export23.0 → 22.320.0 → 20.721 cool, 22 heat
PeakPeak and overnight24.0 → 22.719.0 → 20.322 cool, 21 heat
  • At peak the room may drift a degree past the band before anything runs, and a run stops just 0.3 inside it: short, shallow runs.
  • In the free window it drives the room to the far edge of the band, so the evening starts with cool or warmth already stored.
  • The gap between start and stop keeps the compressor from flapping around one number.
  • The unit's setpoint is the stop point plus or minus that hallway degree, rounded and kept between 16 and 30.

The core of the controller, with the entity names changed:

yaml
# lo = heat below, hi = cool above, slack = how far the room may drift at peak
variables:
  t:      "{{ states('sensor.zone_a_temperature') | float }}"   # mean of two sensors
  lo:     "{{ states('input_number.heat_below') | float }}"     # 20
  hi:     "{{ states('input_number.cool_above') | float }}"     # 23
  slack:  "{{ states('input_number.peak_slack') | float }}"     # 1.0
  tariff: "{{ states('select.import_tariff') }}"    # free / shoulder / peak / offpeak
  plan:   "{{ states('input_select.free_window_plan') }}"   # hold / pre-cool / pre-heat
  cheap:  "{{ tariff in ['free', 'shoulder'] or is_state('binary_sensor.solar_surplus', 'on') }}"
  regime: >-
    {{ 'precool' if tariff == 'free' and plan == 'pre-cool'
       else 'preheat' if tariff == 'free' and plan == 'pre-heat'
       else 'cheap' if cheap else 'peak' }}
  cool_on:  "{{ {'precool': lo + 1.0, 'preheat': hi + slack + 1, 'cheap': hi, 'peak': hi + slack}[regime] }}"
  cool_off: "{{ {'precool': lo + 0.3, 'preheat': hi + slack, 'cheap': hi - 0.7, 'peak': hi - 0.3}[regime] }}"
  heat_on:  "{{ {'precool': lo - slack - 1, 'preheat': hi - 1.0, 'cheap': lo, 'peak': lo - slack}[regime] }}"
  heat_off: "{{ {'precool': lo - slack, 'preheat': hi - 0.3, 'cheap': lo + 0.7, 'peak': lo + 0.3}[regime] }}"
  # the unit's own sensor reads about a degree off the room: ask for a degree past the stop
  sp_cool: "{{ [16, [30, (cool_off - 1) | round(0) | int] | min] | max }}"
  sp_heat: "{{ [16, [30, (heat_off + 1) | round(0) | int] | min] | max }}"
  why: "zone a {{ t }}°C, band {{ lo }}–{{ hi }}, regime {{ regime }} (tariff {{ tariff }})"

Every decision writes its why line to the logbook, so "why is the air-con on?" always has an answer on the dashboard.

What to do with free power

At 11:50, ten minutes before the free window, the controller reads the Bureau of Meteorology forecast:

Today's high, 27 °C or moreTonight's low, 10 °C or lessPlan
YesNoPre-cool
NoYesPre-heat
YesYesHold: cool banked at noon would only mean more heating overnight
NoNoHold

No forecast means hold. One trap: tonight's low is tomorrow's minimum in a daily forecast, because today's entry drops its low after the morning. The automation is on the household stack page.

The guards

  • A 15-minute lockout follows every on, off or mode change, to protect the compressor.
  • A person wins. A change from the app, a dashboard, a button or voice pauses the controller for two hours. In Home Assistant a person's command carries a user and an automation's doesn't, which is how it tells them apart. An automation that fights a person gets switched off.
  • A ceiling fan goes first. Five minutes above the cool threshold starts the room's ceiling fan at 33%. Moving air costs far less than a compressor.

What I took out

The first version widened the band by 3 °C whenever no tracked phone was home. On the first hot morning that let the room climb from 22.0 to 25.3 °C by noon with nothing running, and someone can be home without a phone Home Assistant knows about. Phone presence is not occupancy, so away mode was gone by early afternoon. A holiday switch can cover long trips, its only real saving.

The first hot day: 34 °C

The forecast said 34, so the plan was pre-cool and the blinds stayed shut from sunrise.

Morning. With nothing running, the room warmed 0.3 °C an hour until 9:00, then 0.8 °C an hour until noon as it reached 27 to 32 °C outside. That is twice the rate my older data suggested, so every coasting estimate I had was optimistic.

Noon. The pre-cool, with the unit set to 19, drew about 3 kW. Last summer's hardest hourly average had been 1.8 kW, because the old setpoints sat close to the hallway reading and the unit throttled itself back. The room went from 25.3 to 22.3 °C in 71 minutes and reached 20.4 by 15:00. The pre-cool ran until 15:15 and used 7.4 kWh, all but 0.2 kWh of it free. The whole house used 12.1 of its 24 free kWh that day.

15:00 to 19:00, the rebound. The unit stopped with the room at 20.4. It rose 1.6 °C in the first 45 minutes, then 1.2, 0.7 and 0.3 °C an hour, and sat flat at 24.2 from 18:51 as a cool change came through. That first fast rise is the air warming back up to match the walls, floor and furniture, which three hours of cold air had barely touched.

20:00, one short run. At 24.2 the room was past the 24.0 peak threshold, so the controller ran the unit at a setpoint of 22: about 1 kW for 35 minutes, taking the room from 24.2 to 22.6 °C on 0.59 kWh, about 35c. It stopped on the room's own sensor. At that moment the unit's thermostat still read 24 against its setpoint of 22, so left to itself it would have kept going. That run was the day's only cooling at the peak rate.

The whole day. The ducted unit drew 1.1 kWh at paid rates, about 50c including its standby draw. Last summer, on its own thermostat, a day this hot cost about $2.10 at today's rates. One day is a sample of one, but it points the right way.

And a bug. At 15:00 three triggers fired in the same second. The first run turned the unit off. The other two still saw it cooling, because it hadn't reported the off yet, and sent a setpoint refresh that carried the mode. Four minutes later the compressor was back on at 1,084 W, straight through its own lockout. It cost about 4c, but it would have repeated on any day the unit was running at 15:00. Refreshes now send only the temperature and wait out the lockout, and whenever triggers can coincide I assume a device's reported state is a few seconds stale.

"Doesn't switching off just make it work harder later?"

That was my worry going in. For energy, mostly no. Heat leaks through walls and ceilings in proportion to the difference between inside and out. Hold the house at 20 °C on a 34 °C afternoon and 14 degrees of difference push heat in. Let it drift to 24 °C and the gap is 10, so about 30% less heat comes in each hour. A house left to warm gains heat more slowly, so coasting and then recovering uses no more energy than holding it cold the whole time.

text
heat gain per hour  ∝  outside − inside

held cold:        34 − 20 = 14
left to drift:    34 − 24 = 10      10 / 14 = 0.71, about 30% less

What is real is when the recovery happens: at the peak price, in the hottest hours, and each restart first pushes out duct air that has been sitting in a hot roof. So the lever is banking cool before 16:00, not running through peak.

Should the pre-cool run one more hour, through the 20c shoulder hour from 15:00 to 16:00? At 34 °C the sum says no. The last hour of the free window drew about 2 kW just to hold the room near 20.5 °C, so one more hour costs about 2 kWh, or 40c. The most it could save was the evening's 35c run, and the 1.6 °C rise in the first 45 minutes says cool banked that late drains into the structure within the hour anyway. I'll check again on the first 38 °C day.

How efficient is the air-con, really?

The two runs on the first hot day make a useful comparison, per degree of cooling:

Midday pull-downEvening run
Unit set to1922
Average draw2.86 kWabout 1 kW
Room25.3 → 22.3 °C in 71 min24.2 → 22.6 °C in 35 min
Energy3.38 kWh0.59 kWh
Per degree1.13 kWh0.37 kWh
Roof space / outside37 / 34 °C27.5 / 26 °C

The midday pull-down used three times the energy per degree. Not all of that is the machine: at 20:00 the roof space was more than 9 °C cooler and the air outside 8 °C cooler, so the evening run had less heat to fight and lost less through the ducts. But it fits how inverter air-cons behave. They are most efficient running slowly, and a setpoint of 19 drives the compressor flat out.

That gives the controller its shape. Pulling hard is fine when the power is free, because inefficient free power still costs nothing; the only limit is the 24 kWh daily cap, which the battery shares. At peak, runs are gentle and short. And cooling done before the house heats up goes furthest of all.

Since then: the first two weeks of October

Everything above is the controller as it stood on 30 September. Two weeks of use changed a few things, and the sums are worth adding. The pre-cool and shading threshold has also gone up from 27 to 29 °C.

Humidity counts now. Temperature alone isn't comfort. On the muggy days people in the house reached for the air-con's dry mode, and once, the controller switched it off again within a minute. Cooling decisions now use how the room feels: the thermometer plus a humidity penalty taken from the Bureau of Meteorology's "feels like" formula.

python
import math

def feels_like(t, rh, weight=0.5):
    """Room temperature plus a humidity penalty, in °C."""
    es = 6.105 * math.exp(17.27 * t / (237.7 + t))  # saturation vapour pressure, hPa
    penalty = weight * 0.33 * (rh - 50) / 100 * es  # BOM's humidity term, zeroed at 50% RH
    return t + min(2.0, max(0.0, penalty))          # never negative, capped at +2 °C

print(round(feels_like(22.25, 62), 1))  # 22.8
print(round(feels_like(23.05, 66), 1))  # 23.8
print(round(feels_like(24.0, 45), 1))   # 24.0: dry air never relaxes anything

The 0.33 °C per hPa is the humidity term in the Bureau's apparent temperature. Zeroing it at 50% keeps the band's meaning: at 50% humidity the room feels what the thermometer says. The weight is a half because the Bureau's formula is built for people outdoors and adds about 0.9 °C per 10% of humidity at 23 °C, while indoor comfort models add 0.2 to 0.25 °C. Heating ignores humidity.

A sensor that was reading the ceiling. A small split system guards an equipment room, and it steered on that room's hot-spot sensor. A week of five-minute data showed its lead over a second sensor in the room rose and fell with the attic: a correlation of 0.81, 3.0 °C ahead at night and 6.9 °C ahead at 13:00. Equipment heat is steady all day, so a gap that peaks with the sun is the roof. The split ran for about 60 hours straight against a reading it could never move. It steers on the room's air now: where a sensor is mounted matters more than which sensor it is.

The free window works on quiet days too. On a day with no plan, the free window used to idle. It now cools the room to the middle of the band, so the evening starts about 1.5 °C cooler for nothing.

Cooling starts with the morning sun. On a 30 °C day in October, morning solar had the battery near 60% by noon, leaving the free window little to fill: the day before, the house imported only 0.36 of its 24 free kWh and exported 2.7 kWh at about 2c. Meanwhile the room warmed 1 °C an hour from 10:00. So on pre-cool days the air-con now starts at about 10:00, once spare solar reaches 1.5 kW, at a gentle setpoint of 21. At 26 °C outside, 21 drew 1.05 kW and 19 drew 2.7 kW, so the gentle setting fits inside the morning surplus and the hard pull waits for the free window. It hasn't had a hot enough day to prove itself yet.

Doing this in your own house

You don't need new air-con hardware for most of it. In the order I'd do it:

  1. Put two cheap thermometers in the room you care about, away from windows, and log them against the air-con's own reading for a few weeks. If the gap is a degree or more on hot days, that is the first fix.
  2. Find your heaters. A sensor on each sunny window and one in the roof space tell you which glass to cover, and when.
  3. Have an electrician clamp the air-con circuit at the switchboard, and check on day one that live power reads positive. Don't trust the unit's own energy figures until they match the clamp.
  4. Control from the room, not the unit: a band, a peak slack, and a unit setpoint a degree past the stop point.
  5. Add the guards before the clever parts: a compressor lockout, off when a window opens, a two-hour back-off when a person takes over, and a logbook line for every decision.
  6. Let tomorrow's forecast decide what to do with cheap power, and shut the blinds on hot days.
  7. Put every number on a slider, so tuning is a dashboard job.

Here Home Assistant talks to the ducted unit through a small ESPHome board on its control wiring, so nothing depends on a vendor's cloud. An official integration or Wi-Fi module works with the same logic.

If your air-con runs on its own thermostat, there is a good chance it is measuring a hallway too. I can set the same up in your house or office as a small job, quoted before I start, at $90 an hour. More of the code is on the household stack page, and the first conversation is free: get in touch.