My automation code below is generating a rather unhelpful cryptic error and no clue as to where , what line or why. I was always taught at college to make errors meaningful and certainly not like “Error 1234” - as per the lunar lander in 60s - which is meaningless without the decipher book !
So with that in mind, what on earth might be causing the error do we think?
Error:
2025-03-15 21:30:00.222 ERROR (MainThread) [homeassistant.components.automation.heating_pid_controller] Heating PID Controller: Error executing script. Error rendering template for variables at pos 1: TypeError: can't multiply sequence by non-int of type 'float'
2025-03-15 21:30:00.224 ERROR (MainThread) [homeassistant.components.automation.heating_pid_controller] Error while executing automation automation.heating_pid_controller: TypeError: can't multiply sequence by non-int of type 'float
Code: (package file pump_pid.yaml)
pump_pid:
# Input Select for Current Temperature Sensor
input_select:
current_temperature_sensor:
name: Current Temperature Sensor
options:
- sensor.meter_plus_da53_lounge
- sensor.meter_plus_88d5_kitchen
# Add more sensors as needed
initial: sensor.meter_plus_da53_lounge
# Input Number for Target Temperature and PID Gains
input_number:
pid_target_temperature:
name: PID Target Temperature
min: 10 # Minimum temperature allowed
max: 30 # Maximum temperature allowed
step: 0.1 # Step increment/decrement
initial: 20.5 # Default target temperature
mode: slider # Slider input for easier adjustment
pid_high_temperature_threshold:
name: PID High Temperature Threshold
min: 0
max: 50
step: 0.1
initial: 25
pid_low_temperature_threshold:
name: PID Low Temperature Threshold
min: 0
max: 50
step: 0.1
initial: 15
pid_proportional_gain:
name: PID Proportional Gain
min: 0
max: 10
step: 0.1
initial: 1.0
pid_integral_gain:
name: PID Integral Gain
min: 0
max: 10
step: 0.1
initial: 1.0
pid_derivative_gain:
name: PID Derivative Gain
min: 0
max: 10
step: 0.1
initial: 1.0
pid_previous_error:
name: PID Previous Error
min: -100
max: 100
step: 0.1
initial: 0
pid_integral:
name: PID Integral
min: -100
max: 100
step: 0.1
initial: 0
pid_current_offset:
name: PID Current Offset
min: -10
max: 10
step: 0.5
initial: 0
# Automation for PID Control
automation:
- alias: Heating PID Initialize
trigger:
platform: homeassistant
event: start
action:
- service: input_number.set_value
target:
entity_id: input_number.pid_previous_error
data:
value: 0
- service: input_number.set_value
target:
entity_id: input_number.pid_integral
data:
value: 0
- service: input_number.set_value
target:
entity_id: input_number.pid_current_offset
data:
value: 0
- alias: Heating PID on off
triggers:
- trigger: numeric_state
entity_id:
- sensor.meter_plus_da53_lounge
above: input_number.pid_high_temperature_threshold
id: pid-above-setpoint-day
enabled: true
- trigger: numeric_state
entity_id:
- sensor.meter_plus_da53_lounge
below: input_number.pid_low_temperature_threshold
id: pid-below-setpoint-day
enabled: true
conditions: []
actions:
- alias: PID daytime heating off
continue_on_error: true
if:
- condition: trigger
id:
- pid-above-setpoint-day
then:
- action: input_boolean.turn_off
data: {}
target:
entity_id: input_boolean.heating_demand
- alias: PID daytime heating on
continue_on_error: true
if:
- condition: trigger
id:
- pid-below-setpoint-day
then:
- action: input_boolean.turn_on
data: {}
target:
entity_id: input_boolean.heating_demand
- alias: Heating PID Controller
trigger:
- platform: time_pattern
minutes: '/10' # every 10 minutes
- platform: homeassistant
event: start
action:
- variables:
pid_target_temp: "{{ states('input_number.pid_target_temperature') | float }}" # Get target temperature from input_number
pid_current_sensor: "{{ states('sensor.meter_plus_da53_lounge') | float }}"
pid_current_temp: "{{ pid_current_sensor | float }}"
pid_high_threshold: "{{ states('input_number.pid_high_temperature_threshold') | float }}"
pid_low_threshold: "{{ states('input_number.pid_low_temperature_threshold') | float }}"
pid_previous_error: "{{ states('input_number.pid_previous_error') | float }}"
pid_integral_orig: "{{ states('input_number.pid_integral') | float }}"
pid_dt: 10.0 # time difference in minutes for actual sensor update period
pid_proportional_gain: "{{ states('input_number.pid_proportional_gain') | float }}"
pid_integral_gain: "{{ states('input_number.pid_integral_gain') | float }}"
pid_derivative_gain: "{{ states('input_number.pid_derivative_gain') | float }}"
# Calculate the error
pid_error: "{{ (pid_target_temp - pid_current_temp) | float }}"
# Adjust the integral to reduce overshooting
pid_integral: >
{% if pid_error | abs < 0.5 %}
{{ pid_integral_orig | float}} # Reduce the integral if close to target
{% else %}
{{ (pid_integral_orig + pid_error) | float * pid_dt }} # Regular accumulation
{% endif %}
pid_derivative: "{{ (pid_error - pid_previous_error) | float / pid_dt }}"
# Calculate PID output (scaled to range of -10 to +10)
pid_output: >
{{ (
(pid_proportional_gain | float * pid_error | float) +
(pid_integral_gain | float * pid_integral | float) +
(pid_derivative_gain | float * pid_derivative | float)
) | float * (10.0 / pid_target_temp | float) | float }}
# Limit PID output changes to avoid rapid spikes
pid_new_offset_raw: "{{ states('input_number.pid_current_offset') | float + pid_output }}"
pid_current_offset: "{{ states('input_number.pid_current_offset') | float }}"
pid_new_offset: >
{% if (pid_new_offset_raw - pid_current_offset) > 1 %}
{{ pid_current_offset + 1 }} # Limit increase
{% elif (pid_new_offset_raw - pid_current_offset) < -1 %}
{{ pid_current_offset - 1 }} # Limit decrease
{% else %}
{{ pid_new_offset_raw }} # Allow normal adjustment
{% endif %}
# Self-tuning logic
pid_adjustment_factor: >
{% if pid_error | abs > 1 %}
0.1 # Increase gains if error is large
{% elif pid_error | abs < 0.5 %}
-0.05 # Decrease gains if close to target
{% else %}
0 # No change
{% endif %}
pid_new_proportional_gain: "{{ (pid_proportional_gain + pid_adjustment_factor) | max(0) | min(10) }}"
pid_new_integral_gain: "{{ (pid_integral_gain + pid_adjustment_factor) | max(0) | min(10) }}"
pid_new_derivative_gain: "{{ (pid_derivative_gain + pid_adjustment_factor) | max(0) | min(10) }}"
# Log all important values
- service: logger.log
data:
message: >
Target Temp: {{ pid_target_temp }} |
Current Temp: {{ pid_current_temp }} |
Error: {{ pid_error }} |
PID Output: {{ pid_output }} |
Proportional Gain: {{ pid_proportional_gain }} |
Integral Gain: {{ pid_integral_gain }} |
Derivative Gain: {{ pid_derivative_gain }} |
Updated Proportional Gain: {{ pid_new_proportional_gain }} |
Updated Integral Gain: {{ pid_new_integral_gain }} |
Updated Derivative Gain: {{ pid_new_derivative_gain }} |
Current Offset: {{ pid_current_offset }} |
New Offset: {{ pid_new_offset }} |
Integral: {{ pid_integral }}
# Only update the temperature offset if it has changed
- choose:
- conditions:
- condition: template
value_template: "{{ pid_new_offset != pid_current_offset }}"
sequence:
- service: input_number.set_value
target:
entity_id: input_number.pid_current_offset
data:
value: "{{ pid_new_offset }}"
- action: climate.set_temperature
metadata: {}
data:
temperature: >-
{{ pid_new_offset }}
target:
entity_id: climate.heating_leaving_water_offset
- service: input_number.set_value
target:
entity_id: input_number.pid_previous_error
data:
value: "{{ pid_error }}"
- service: input_number.set_value
target:
entity_id: input_number.pid_integral
data:
value: "{{ pid_integral }}"
# Update PID gains for self-tuning
- service: input_number.set_value
target:
entity_id: input_number.pid_proportional_gain
data:
value: "{{ pid_new_proportional_gain }}"
- service: input_number.set_value
target:
entity_id: input_number.pid_integral_gain
data:
value: "{{ pid_new_integral_gain }}"
- service: input_number.set_value
target:
entity_id: input_number.pid_derivative_gain
data:
value: "{{ pid_new_derivative_gain }}"