Heat Pump Optimizer
What?
Following up on rentry.co/heatshit, you may know that heat pumps perform the worst when they have to defrost a lot. This Home Assistant python script creates a simple automation that uses sensors and weather forecast to optimize the time of heating over a 24-hour period, such that it avoids heating in low efficiency periods.
How?
Heat pumps perform the worst at 0-5C in high humidity conditions. The script checks hourly weather forecast, then picks out good hours (that are not in the danger zone) and bad hours (that are in the danger zone) and calculates a planned heating offset distribution based on the amount of good/bad hours. Then applies an appropriate coefficient to the present state. Heating more in good efficiency hours and not heating so much in bad efficiency hours will make your COP more better numbers.
Ok, how do?
You will need:
- Home assistant
- Heat pump connected to HASS with a Climate entity (i.e. SmartThinQ LGE Sensors for LG)
it is assumed that you have weather compensation with offset adjustment, the script as-is is NOT designed to directly control flow temperature, but rather just provide offsets (although it wouldn't be hard to rework it for flow temp control)
- Weather Forecast integration (met.no is default for the code, others may work, but likely would need formatting changes) (obviously you have to configure the forecast correctly to your exact location, otherwise the numbers will be out of whack)
- Python Scripts enabled in HASS
- (optional) logging enabled in HASS
Configuration.yaml
Add the following:
Note that you can have any other levels/logs set if you want/need, only that at least "logger:" should be present
Create variables
- Go to Settings > Devices > Helpers
- Click Create helper
- Select number
- Enter name: hvac_temp_lean
- Pick a cool icon with sunglasses
- Set minimum value and maximum value according to your heat pump's adjustment settings (this value influences overall heat input, replacing the original curve adjustment function on the heat pump controller) - for LG, this would be -5 to +5
- Hit create
- Create another number helper with a cool icon
- Enter name: hvac_target_indoor
- Set minimum and maximum value according to realistic expected indoor temps you might possibly want, maybe 18 to 25 or so (this value influences target temperature, the script will try to correct target heating temperature if the indoor temperature starts falling too far out of whack)
- Hit create
Create files
Create a folder called python_scripts (note this is SCRIPTS, not SCRIPT, but the Configuration.yaml entry is SCRIPT), in the SAME DIRECTORY as your Configuration.yaml file (usually homeassistant/)
Note: use File Editor addon if you do not have it
Create a new file called "set_heat_pump.py"
Copy the code into the file
Code
We do not write code in current year, so I had my AI girlfriend generate this.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 | climate_id = 'climate.heatpump'
weather_id = 'weather.home'
indoor_id = 'sensor.indoor'
outdoor_id = 'sensor.outdoor'
humidity_id = 'sensor.humidity_outdoor'
lean_id = 'input_number.hvac_temp_lean'
target_indoor_id = 'input_number.hvac_target_indoor'
base = 17.0
min_d = -2.0
max_d = 5.0
base_magnitude = 2.0
k_p = 1.0 # Tweak this if the response to indoor error feels off
# =====================================================
# Heat Pump Optimizer v0.1
# Hacked by Ai-chama (Grok)
# Don't even think about messing with this, baka!
# It's not like I wrote it for your sake or anything...
# =====================================================
now = dt_util.now()
log_msg = f"Heat Pump Optimizer run at {now}:\n"
# Get indoor error with fallback
try:
indoor = float(hass.states.get(indoor_id).state)
target = float(hass.states.get(target_indoor_id).state)
error = target - indoor
log_msg += f"Indoor temp: {indoor}, Target: {target}, Error: {error}\n"
except:
error = 0.0
log_msg += "Indoor sensor fallback: Error set to 0.0\n"
# Get lean
try:
lean = float(hass.states.get(lean_id).state)
log_msg += f"Lean: {lean}\n"
except:
lean = 0.0
log_msg += "Lean fallback: 0.0\n"
# Get current outdoor temp and humidity with fallback to weather entity attributes
try:
outdoor_temp = float(hass.states.get(outdoor_id).state)
log_msg += f"Outdoor temp from sensor: {outdoor_temp}\n"
except:
outdoor_temp = None
try:
hum = float(hass.states.get(humidity_id).state)
log_msg += f"Humidity from sensor: {hum}\n"
except:
hum = None
weather_attr = hass.states.get(weather_id).attributes
if outdoor_temp is None:
outdoor_temp = weather_attr.get('temperature')
log_msg += f"Outdoor temp fallback to weather: {outdoor_temp}\n"
if hum is None:
hum = weather_attr.get('humidity')
log_msg += f"Humidity fallback to weather: {hum}\n"
# Determine current efficiency
if outdoor_temp is None or hum is None:
is_efficient = False # Safe fallback, assume inefficient
log_msg += "Missing data: Assuming inefficient\n"
else:
is_efficient = (outdoor_temp >= 5 or outdoor_temp <= 0 or (0 < outdoor_temp < 5 and hum <= 60))
log_msg += f"Current efficient: {is_efficient} (Temp: {outdoor_temp}, Hum: {hum})\n"
# Get hourly forecasts via service
try:
response = hass.services.call(
'weather',
'get_forecasts',
{'entity_id': [weather_id], 'type': 'hourly'},
blocking=True,
return_response=True
)
if weather_id in response:
forecasts = response[weather_id]['forecast'] # Adjusted based on HA structure
log_msg += f"Got {len(forecasts)} forecasts\n"
else:
forecasts = []
log_msg += "No forecasts in response\n"
except:
forecasts = []
log_msg += "Forecast service failed, using empty list\n"
# Count good/bad hours, including current
g = 1 if is_efficient else 0
b = 0 if is_efficient else 1
forecast_count_added = 0
for f in forecasts:
try:
dt_str = f['datetime']
if forecast_count_added < 1: # Log first one for debug
log_msg += f"First forecast dt_str: {dt_str}\n"
dt = dt_util.parse_datetime(dt_str)
if forecast_count_added < 1:
log_msg += f"Parsed dt: {dt}\n"
time_diff_sec = dt.timestamp() - now.timestamp()
if time_diff_sec <= 0 or time_diff_sec > 24 * 3600:
continue
f_temp = f.get('temperature')
f_hum = f.get('humidity')
if f_temp is None or f_hum is None:
log_msg += f"Skipped forecast at {dt_str}: Missing temp or hum\n"
continue
f_eff = (f_temp >= 5 or f_temp <= 0 or (0 < f_temp < 5 and f_hum <= 60))
if f_eff:
g += 1
else:
b += 1
forecast_count_added += 1
except Exception as e:
log_msg += f"Skipped a forecast due to error: {str(e)}\n"
continue
log_msg += f"Good hours: {g}, Bad hours: {b}\n"
log_msg += f"Added {forecast_count_added} forecasts to count\n"
# Calculate deltas
neg_mag = base_magnitude - lean
neg_mag = max(0, min(2, neg_mag))
n = -neg_mag
log_msg += f"Negative magnitude: {neg_mag}, n: {n}\n"
if g == 0:
d = lean + k_p * error
log_msg += "No good hours: d = lean + k_p * error\n"
else:
p = lean + base_magnitude * (b / g)
log_msg += f"p: {p}\n"
if is_efficient:
d = p
else:
d = n
d += k_p * error
log_msg += f"Delta d: {d} (before clip)\n"
# Clip d
d = max(min_d, min(max_d, d))
log_msg += f"Clipped d: {d}\n"
# Set thermostat temp
temp = base + d
log_msg += f"Setting temp to: {temp}\n"
hass.services.call('climate', 'set_temperature', {'entity_id': climate_id, 'temperature': temp})
logger.info(log_msg)
|
Adjust code
Obviously replace all the entity_id's in the first 5 lines with ones that you have in your own system. You can leave temperature sensors blank, the script will fall back on weather forecast if you don't have any sensors.
Next,
base - Due to an error in the HASS LG integration, the displayed base temperature in Auto (weather comp mode) at 0C offset is 17C in the HASS integration. The base value represents this 17C offset from 0. If your heat pump Climate entity presents 0 as default 0, enter a 0.
min_d - Due to another error in the HASS LG integration, the controller is only allowed to go to 15C at lowest, even though the actual LG Controller device allows +/- 5C offset for the weather comp curve, hence the min_d is -2 (the difference between 17 and 15). If your Climate entity works normally, use -5 or whatever offset the controller lets you have
max_d - same as min_d but the other way around, can leave it at 5
base_magnitude - influences the base "force" the script uses, meaning it switches 2C hotter or 2C colder, depending on conditions
k_p - integral for temperature correction from indoor error calculation. Make it higher (1.5 or 2) if the heating responds too slowly to indoor temp errors, decrease it (maybe to 0.5) if it responds too much.
You could break out base magnitude and k_p to HASS numbers if you want to play with them actively without having to open file editor, but defaults might well be good enough.
Set up automation
- Go to Settings > Automations & scenes
- Create automation > Create new automation
- In the top right corner click the three dots ..., select Edit in YAML
- Copypaste the code
- Hit save, change name or add description if you want
- Go back to the main Automations window, open Heat Pump Optimizer
- Click the three dots ... on the right side of the last row, "Then do: Perform action...", select Run action, it shouldn't throw any errors if you did everything right
The time pattern trigger makes this run every 30 minutes. If you remove the / at minutes, it will run every hour (at the 30th minute).
Final bad touches
- Check Core logs for the log output - whenever you do a test run of the action (step 7 above), it should give you a nice log output on what the script decided to do.
- Add Card Entities to a dashboard of your choice for the Heat Pump Optimizer automation (so you can turn it on and off easily) and the two number variables (ideally sliders)
- tweak your weather comp curve in the heat pump's own controller as needed to ensure good neutral heating at 0C offset
Why not just control flow temp? Why control the weather comp offset?
Having weather compensation enabled at all is a last resort failsafe. Even if it's set to -5 offset, it's going to do some amount of heating, so, for example, if your HASS crashes or the script glitches out somehow, the worst that can happen is that you maybe lose a bit of interior heat, as opposed to having zero heating for X hours, until you notice the fault. In a worst-case scenario, you could have 10-15C daytime temp, the script failing at this level with maybe 25C flow temp set, and then outdoor temp crashing down to -5C, and the heat pump still at 25C flow temp would be doing almost nothing. Meanwhile the -5 offset weather comp curve would maybe at least target 30C flow temp at -5C outdoor.