hvac: cooling has thresholds of its own, and the stove heats first
Watching it decide — which is what `commands: false` is for — caught two things a type check never would. It wanted to cool the house to 21 degrees in August. The comfort band is what *heating* aims at; the reference cooled above 26 and at night above 24.5, and this port had collapsed the two into one number. A compressor running every summer afternoon to reach a heating setpoint is the most expensive kind of correct-looking bug. And it was willing to heat while the pellet stove was doing the same job. The reference only ever let the heat pump heat when the stove reported itself faulty, which is the right way round: the stove is what heats this house and the pump is what covers for it. Both tables now have a check behind them, along with the boilers' — the three places in the house where a wrong threshold is a bill rather than a mistake. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -148,8 +148,10 @@ def process(
|
||||
home=True,
|
||||
sleeping=False,
|
||||
oven_on=False,
|
||||
oven_faulty=False,
|
||||
hot=29.0,
|
||||
high=26.0,
|
||||
night_high=24.5,
|
||||
cool_soc=60.0,
|
||||
cool_soc_away=90.0,
|
||||
fan_soc=45.0,
|
||||
@@ -160,7 +162,11 @@ def process(
|
||||
if not enabled or watch:
|
||||
return {"command": OFF, "why": "off: " + ("disabled" if not enabled else "power")}
|
||||
|
||||
# Cooling has thresholds of its own, well above the comfort band. The band
|
||||
# is what *heating* aims at; cooling down to it in August would run the
|
||||
# compressor all day for nothing. The reference drew the same line.
|
||||
t_hot, t_high = hot + lift, high + lift
|
||||
t_night = night_high + lift
|
||||
|
||||
def ask(mode, temp=None, fan="auto", why=""):
|
||||
return {
|
||||
@@ -177,6 +183,8 @@ def process(
|
||||
# Nobody in: only ever to stop the house cooking, and only on the sun.
|
||||
if top > t_hot and soc > cool_soc_away:
|
||||
return ask("cool", t_hot, why="empty house is too hot")
|
||||
if indoor < t_min and oven_faulty and soc > dry_soc:
|
||||
return ask("heat", t_min + 3, why="empty house is freezing")
|
||||
if humidity is not None and humidity > 60 and soc > dry_soc:
|
||||
return ask("dry", t_min + 3, why="empty house is damp")
|
||||
return {"command": OFF, "why": "nobody in"}
|
||||
@@ -185,7 +193,7 @@ def process(
|
||||
# Warm air at the ceiling and cold feet: move it rather than make more.
|
||||
return ask("fan", fan="high" if oven_on else "low", why="stratified")
|
||||
|
||||
limit = t_max if not sleeping else t_max + 1.5
|
||||
limit = t_night if sleeping else t_high
|
||||
if top > limit:
|
||||
if soc > (cool_soc if not sleeping else cool_soc_away):
|
||||
return ask("cool", t_hot, why="too warm")
|
||||
@@ -198,7 +206,12 @@ def process(
|
||||
return ask("dry", t_min + 3, fan="low", why="damp")
|
||||
|
||||
if indoor < t_min and soc > dry_soc:
|
||||
return ask("heat", t_min + 3, why="too cold")
|
||||
# The stove is what heats this house; the heat pump is the fallback for
|
||||
# when it cannot. Both running is the expensive one winning an argument
|
||||
# with the cheap one.
|
||||
if not oven_faulty:
|
||||
return {"command": OFF, "why": "cold, but that is the stove's job"}
|
||||
return ask("heat", t_min + 3, why="too cold and the stove is out")
|
||||
|
||||
return {"command": OFF, "why": "within the band"}
|
||||
'''
|
||||
@@ -370,6 +383,12 @@ def hvac(h: dict[str, Any], commands: bool) -> Flow:
|
||||
"dtype": "bool",
|
||||
"trigger": False,
|
||||
},
|
||||
{
|
||||
"name": "oven.faulty",
|
||||
"port": "oven_faulty",
|
||||
"dtype": "bool",
|
||||
"trigger": False,
|
||||
},
|
||||
{"name": "enabled", "dtype": "bool", "trigger": False},
|
||||
],
|
||||
"provides": [
|
||||
|
||||
Reference in New Issue
Block a user