What does “Message malformed: required key not provided @ data[‘action’][0][‘choose’][1][‘sequence’]” mean in English?
Without a line number, how on earth do I find the error in a 731 line blueprint with lots of choose and sequence statements?
### ------------------------------------------------------------------------------------
###
### HEATING X, Release 2
### --------------------
###
### Release 1 (Feb 2023) had the following features
### Controls one or more thermostats from a calendar
### Allows temporary manual override
### Optionally turns off thermostat if a door or window is opened,
### Optionally turns off thermostat if the room is unoccupied for a while.
###
### Release 2 (Feb 2024) adds the following features
### Away mode. Changes the heating of all rooms to the 'away temperature' setting when on, reverts to the usual schedules etc when off.
### Background temperature can be specified to use when there is no active calendar event or room is unoccupied
### TRV battery life saved by transmitting to TRV only when there is an actual change
###
### 06-Feb-23 | Andy Symons | Release 1. See feature list above
### 26-Jan-24 | Andy Symons | Release 2. See feature list above
###
### ------------------------------------------------------------------------------------
blueprint:
name: "Heating X2" # temporary name for testing
description: Controls one or more thermostats from a calendar, allows temporary manual override, and optionally turns off thermostat if a door or window is opened, or if the area is unoccupied for a while.
domain: automation
### ----------------------------------------------------------------------------
### INPUTS
### ----------------------------------------------------------------------------
input:
thermostat_controls:
name: DEVICE ENTITY - Thermostat control
description: One or more thermostat entities that are to be controlled by this automation
selector:
entity:
filter:
domain: climate
multiple: true
thermostat_set_temperature_sensors:
name: DEVICE ENTITY - Thermostat set temperature
description: The (template) sensors that read the set temperature from the specified thermostats
selector:
entity:
filter:
domain: sensor
multiple: true
door_or_window_open_sensors:
name: DEVICE ENTITY - Door or window open sensors
description: Zero or more sensors that detect whether a door or window is open
selector:
entity:
filter:
domain: binary_sensor
device_class: opening
multiple: true
default: []
area_occupancy_sensors:
name: DEVICE ENTITY - area occupancy sensors
description: Zero or more sensors that detect whether there is anyone in the area
selector:
entity:
filter:
domain: binary_sensor
device_class: occupancy
multiple: true
default: []
away_switch:
name: DEVİCE ENTITY - Away switch
description: A switch, input boolean, or binary sensor that changes all rooms to 'away' mode
selector:
entity:
area_calendar:
name: CALENDAR ENTITY - area calendar
description: The calendar dedicated to scheduling events for this area
selector:
entity:
filter:
domain: calendar
manual_temperature:
name: HELPER - Temperature set manually
description: The global variable (helper) to hold the required temperature
selector:
entity:
filter:
domain: input_number
set_temperature:
name: HELPER - Current set temperature
description: The global variable (helper) to hold the required temperature
selector:
entity:
filter:
domain: input_number
required_temperature:
name: HELPER - Required temperature (next set temperature)
description: The global variable (helper) to hold the required temperature
selector:
entity:
filter:
domain: input_number
setting_reason:
name: HELPER - Setting reason
description: The global variable (helper) into which the automation writes the reason for the current setting (for use on a dashboard)
selector:
entity:
filter:
domain: input_text
door_or_window_open_timer:
name: HELPER - Door or window open timer
description: The global variable (helper) to hold the timer for the period since a door or window was opened
selector:
entity:
filter:
domain: timer
unoccupancy_timer:
name: HELPER - Unoccupancy timer
description: The global variable (helper) to hold the timer for the period since the area was last unoccupied
selector:
entity:
filter:
domain: timer
warmup_timer:
name: HELPER - Warmup timer
description: The global variable (helper) to hold the timer for the event warmup period
selector:
entity:
filter:
domain: timer
manual_override_timer:
name: HELPER - Manual override timer
description: The global variable (helper) to hold the timer for a manual intervention
selector:
entity:
filter:
domain: timer
echoblock_timer:
name: HELPER - Echoblock timer
description: The timer for use inside the automation to disinguish genuine manual changes of the set temperature from those set by the automation
selector:
entity:
filter:
domain: timer
minimum_thermostat_temperature:
name: PARAMETER - Minimum thermostat temperature
description: The minimum temperature that can be set on the thermostat
selector:
number:
min: 0
max: 100
default: 5
maximum_thermostat_temperature:
name: PARAMETER - Maximum thermostat temperature
description: The maximum temperature that can be set on the thermostat
selector:
number:
min: 0
max: 100
default: 95
frost_setting:
name: PARAMETER - Frost setting
description: The temperature to be used when the heating is turned off
selector:
number:
min: 0
max: 100
default: 5
away_temperature:
name: PARAMETER - Away temperature
description: The temperature to be used when away mode is in operation
selector:
number:
min: 0
max: 100
default: 6
background_temperature:
name: PARAMETER - Frost setting
description: The temperature to be used when there is no calendar event or the room is unoccupied
selector:
number:
min: 0
max: 100
default: 7
warmup_period:
name: PARAMETER - Warmup period
description: The period of time from the start of a new event for which area unoccupancy will be ignored
selector:
time:
default: "02:00:00"
manual_override_period:
name: PARAMETER - Manual override period
description: The time period for which a manual intervention will override the schedule
selector:
time:
default: "02:00:00"
door_or_window_open_period:
name: PARAMETER - Door or window open period
description: The time period for which a door or window may be open before the heating is turned off
selector:
time:
default: "0:03:00"
unoccupancy_period:
name: PARAMETER - Unoccupancy period
description: The time period for which the area may be unoccupied before the heating is turned off
selector:
time:
default: "01:00:00"
mode: queued # use all triggers but avoid conflicting states
## ----------------------------------------------------------------------------
## LOCAL VARİABLES
## needed to capture global variable values for use in templates
## ----------------------------------------------------------------------------
variables:
local_thermostat_set_temperature_sensors: !input thermostat_set_temperature_sensors
local_door_or_window_open_sensors: !input door_or_window_open_sensors
local_required_temperature: !input required_temperature
local_set_temperature: !input set_temperature
local_manual_override_timer: !input manual_override_timer
local_maximum_thermostat_temperature: !input maximum_thermostat_temperature
local_minimum_thermostat_temperature: !input minimum_thermostat_temperature
local_area_calendar: !input area_calendar
local_area_occupancy_sensors: !input area_occupancy_sensors
## ----------------------------------------------------------------------------
## TRIGGERS
## ----------------------------------------------------------------------------
trigger:
# 1. Calendar state to on (active event)
- platform: state
entity_id: !input area_calendar
to: "on"
id: calendar_state_to_on
# 2. Calendar state to off, unknown, or unavailable (no active event)
- platform: state
entity_id: !input area_calendar
from: "on"
id: calendar_state_to_off
# 3. Calendar event start
- platform: calendar
event: start
offset: "0:0:0"
entity_id: !input area_calendar
id: calendar_event_start
# 4. Calendar event end
- platform: calendar
event: end
offset: "0:0:0"
entity_id: !input area_calendar
id: calendar_event_end
# 5. Change in any one of the thermostat set temperatures
- platform: state
entity_id: !input thermostat_set_temperature_sensors
for:
seconds: 5
id: set_temperature_change
# 6. End of manual override (timer idle, paused, unknown, or unavailable)
- platform: state
entity_id: !input manual_override_timer
from: active
id: manual_override_end
# 7. area becomes unoccupied: sensor available and off (clear)
- platform: state
entity_id: !input area_occupancy_sensors
to: "off"
for:
seconds: 10
id: area_unoccupied
# 8. area becomes occupied: sensor 'detected' (on), 'unknown', or 'unavailable'
- platform: state
entity_id: !input area_occupancy_sensors
from: "off"
for:
seconds: 10
id: area_occupied
# 9. A door or window is opened
- platform: state
entity_id: !input door_or_window_open_sensors
to: "on"
for:
seconds: 10
id: door_or_window_opened
# 10. A doors or windows is closed, or becomes 'unknown' or 'unavailable'
- platform: state
entity_id: !input door_or_window_open_sensors
from: "on"
for:
seconds: 10
id: doors_and_windows_closed
# 11. Door or window open timer finished (time to turn off the heating)
- platform: state
entity_id: !input door_or_window_open_timer
from: active
id: door_or_window_open_timer_end
# 12. Unoccupancy timer finished (time to turn off the heating)
- platform: state
entity_id: !input unoccupancy_timer
from: active
id: unoccupancy_timer_end
# 13. Away switch change
- platform: state
entity_id: !input away_switch
# 14. end of warmup period
- platform: state
entity_id: !input warmup_timer
from: active
id: warmup_timer_end
## ----------------------------------------------------------------------------
## ACTIONS STEP 1 -- RESPOND TO TRIGGERS
## ----------------------------------------------------------------------------
action:
# Each choice responds to a specific trigger (where a response is needed))
- choose:
#
# a. Manual override start
#
- conditions:
- condition: trigger
id: set_temperature_change
#ignore if it is just an echo from a setting from this automation
- condition: state
entity_id: !input echoblock_timer
state: idle
#ignore if same value as before (e.g. triggered by template reload)
- condition: template
value_template: "{{ not states(local_set_temperature) == states(local_required_temperature) }}"
sequence:
- service: timer.start
data:
duration: !input manual_override_period
target:
entity_id: !input manual_override_timer
#
# b. Manual override end
#
- conditions:
- condition: trigger
id: manual_override_end
#
# c. Door or window opened - start timer
#
- conditions:
- condition: trigger
id: door_or_window_opened
# Do not restart timer if already running
- condition: not
conditions:
- condition: state
entity_id: !input door_or_window_open_timer
state: active
sequence:
- service: timer.start
data:
duration: !input door_or_window_open_period
target:
entity_id: !input door_or_window_open_timer
#
# d. Doors and windows closed - restart and pause timer
#
- conditions:
- condition: trigger
id: doors_and_windows_closed
sequence:
- service: timer.start
data:
duration: !input door_or_window_open_period
target:
entity_id: !input door_or_window_open_timer
- service: timer.pause # Keeps it in the active state
target:
entity_id: !input door_or_window_open_timer
#
# e. Unoccupancy - start timer
#
- conditions:
- condition: trigger
id: area_unoccupied
# Do not restart timer if already running
- condition: not
conditions:
- condition: state
entity_id: !input unoccupancy_timer
state: active
sequence:
- service: timer.start
data:
duration: !input unoccupancy_period
target:
entity_id: !input unoccupancy_timer
#
# f. area occupied - restart and pause timer
#
- conditions:
- condition: trigger
id: area_occupied
sequence:
- service: timer.start
data:
duration: !input unoccupancy_period
target:
entity_id: !input unoccupancy_timer
- service: timer.pause # Keeps it in the active state
target:
entity_id: !input unoccupancy_timer
## ----------------------------------------------------------------------------
## ACTIONS STEP 2 -- DETERMINE THE REQUIRED TEMPERATURE ACCORDNG TO THE STATE
## ----------------------------------------------------------------------------
## Each choice sets the required temperature and the reason text, for later use
## The states are tested in order of their priority over other states
- choose:
# 1. If 'away' switch is on
- conditions:
- condition: state
entity_id: !input away_switch
state: "on"
sequence:
- service: input_number.set_value
data:
value: !input away_temperature
target:
entity_id: !input required_temperature
- service: input_text.set_value
data:
value: "Set to away temperature because away mode is turned on"
target:
entity_id: !input setting_reason
# 2. If a door or window is open
- conditions:
- condition: state
entity_id: !input door_or_window_open_timer
state: idle
- condition: template
value_template: "{{ local_door_or_window_open_sensors | select ('is_state', 'on') | list | count > 0 }}"
sequence:
- service: input_number.set_value
data:
value: !input frost_setting
target:
entity_id: !input required_temperature
- service: input_text.set_value
data:
value: "Turned off because a door or window is open"
target:
entity_id: !input setting_reason
# 2. If the area has been unoccupied for the set period, and we are not in a warmup period ***
- conditions:
- condition: state
entity_id: !input unoccupancy_timer
state: idle
- condition: state
entity_id: !input warmup_timer
state: idle
- condition: template
value_template: "{{ local_area_occupancy_sensors | reject ('is_state', [ 'unknown', 'unavailable' ] ) | select ('is_state', 'on') | list | count > 0 }}"
sequence:
- service: input_number.set_value
data:
value: !input background_temperature
target:
entity_id: !input required_temperature
- service: input_text.set_value
data:
value: "Set to background temperature because the area is unoccupied"
target:
entity_id: !input setting_reason
# 3. If there is a manual override in operation
- conditions:
- condition: state
entity_id: !input manual_override_timer
state: active
sequence:
- service: input_number.set_value
data:
value: "{{ states(local_thermostat_set_temperature) }}"
target:
entity_id: !input required_temperature
- service: input_text.set_value
data:
value: "Set manually to {{ states(local_required_temperature) }}. Time left {{ ( as_timestamp(state_attr(local_manual_override_timer,'finishes_at'))-as_timestamp(now()) ) | timestamp_custom('%H:%M', False, 0) }}"
target:
entity_id: !input setting_reason
# 4. If there is an active calendar event and a valid temperature
- choose:
# 4.1 If there is no temperature field
- conditions:
- condition: template
value_template: >-
{{ not state_attr(local_area_calendar, 'description').split('#') | count >= 3 }}
sequence:
- service: input_number.set_value
data:
value: !input background_temperature
target:
entity_id: !input required_temperature
- service: input_text.set_value
data:
value: >-
{{ "Set to background temperature because the calendar event '" + state_attr(local_area_calendar, 'message') + "' does not specify a temperature." }}
target:
entity_id: !input setting_reason
# 4.2 If there is a temperature field that is not a number
- conditions:
- condition: template
value_template: >-
{{ not state_attr(local_area_calendar, 'description').split('#')[1] | is_number }}
sequence:
- service: input_number.set_value
data:
value: !input background_temperature
target:
entity_id: !input required_temperature
- service: input_text.set_value
data:
value: >-
{{ "Set to background temperature because the calendar event '" + state_attr(local_area_calendar, 'message') + "' does not specify a valid number for the temperature." }}
target:
entity_id: !input setting_reason
# 4.3 If there is a temperature that is too low
- conditions:
- condition: template
value_template: >-
{{ not state_attr(local_area_calendar, 'description').split('#')[1] | float(0)) >= local_minimum_thermostat_temperature }}
sequence:
- service: input_number.set_value
data:
value: !input background_temperature
target:
entity_id: !input required_temperature
- service: input_text.set_value
data:
value: >-
{{ "Set to background temperature because the calendar event '" + state_attr(local_area_calendar, 'message') + "' specifies a temperature below the minimum." }}
target:
entity_id: !input setting_reason
# 4.4 If there is a temperature that is too high
- conditions:
- condition: template
value_template: >-
{{ not ( state_attr(local_area_calendar, 'description').split('#')[1] | float(0)) <= local_maximum_thermostat_temperature }}
sequence:
- service: input_number.set_value
data:
value: !input background_temperature
target:
entity_id: !input required_temperature
- service: input_text.set_value
data:
value: >-
{{ "Set to background temperature because the calendar event '" + state_attr(local_area_calendar, 'message') + "' specifies a temperature above the maximum." }}
target:
entity_id: !input setting_reason
# 4.5 Else, the normal case -- there is an active calendar event with a valid temperature
default:
sequence:
- service: input_number.set_value
data:
value: "{{ state_attr(local_area_calendar, 'description').split('#')[1] | float(0) }}"
target:
entity_id: !input required_temperature
- service: input_text.set_value
data:
value: >-
{{ "Set to " + states(local_required_temperature) + " by calendar event '" + state_attr(local_area_calendar, 'message') + "' until " + ( as_timestamp(state_attr(local_area_calendar, 'end_time')) ) | timestamp_custom('%a %d %b %Y at %H:%M') + "." }}
target:
entity_id: !input setting_reason
# 5. If there is no active calendar event
- conditions:
- condition: state
entity_id: !input area_calendar
state: "off"
sequence:
- service: input_number.set_value
data:
value: !input background_temperature
target:
entity_id: !input required_temperature
- service: input_text.set_value
data:
value: >-
{{ "Set to background temperature because nothing is scheduled."}}
{% if state_attr(local_area_calendar, 'message') %}
{{ "The next event is '" + state_attr(local_area_calendar, 'message') + "' " + ( as_timestamp(state_attr(local_area_calendar, 'start_time')) ) | timestamp_custom('on %a %d %b %Y at %H:%M', false)}}
{% else %}
{{ "There are no future events." }}
{% endif %}
target:
entity_id: !input setting_reason
#6. If the calendar state becomes unknown
- conditions:
- condition: state
entity_id: !input area_calendar
state: unknown
sequence:
- service: input_number.set_value
data:
value: !input background_temperature
target:
entity_id: !input required_temperature
- service: input_text.set_value
data:
value: Set to background temperature because the calendar state is unknown
target:
entity_id: !input setting_reason
#7. If the calendar becomes unavailable
- conditions:
- condition: state
entity_id: !input area_calendar
state: unavailable
sequence:
- service: input_number.set_value
data:
value: !input background_temperature
target:
entity_id: !input required_temperature
- service: input_text.set_value
data:
value: Set to background temperature because the calendar is unavailable
target:
entity_id: !input setting_reason
# Default - should never happen!
default:
- service: input_number.set_value
data:
value: !input frost_setting
target:
entity_id: !input required_temperature
- service: input_text.set_value
data:
value: Turned off by because room state is unknown
target:
entity_id: !input setting_reason
## ----------------------------------------------------------------------------
## ACTIONS STEP 4 -- COMMUNICATE WITH THE TRV(s)
## ----------------------------------------------------------------------------
# Check not already set
- if:
- condition: template
value_template: >-
{{ states(local_required_temperature) <> states(local_set_temperature) }}
then:
# Send the command
- service: climate.set_temperature
data:
temperature: local_required_temperature
target:
entity_id: !input thermostat_controls
# Start the echoblock timer
- service: timer.start
data:
duration:
seconds: 10
target:
entity_id: !input echoblock_timer
# Wait 10 seconds for command to be received and reflected back
- delay: "00:00:10"
# Check TRV(s) responded
# Need a loop to test each individually?
- if:
- condition: template
value_template: >-
{{ states(local_required_temperature) | float(0) <> states(local_set_temperature) | float(0) }}
then:
- service: input_text.set_value
data:
value: "Turned off because a door or window is open"
target:
entity_id: !input setting_reason
- service: notify.notify
data:
title: "Heating X error"
message: "{{'TRV' states(local_thermostat_set_temperature_sensors) 'is not responding'}}"
You know it’s going to be in the actions section of the blueprint ( [‘action’] ) and it’s referencing “choose” (which is an action)
under the “action:” section it will be the first action performed. lists in HA are 0 indexed so index 0 ( [0] ) will be the first action in the list.
so that narrows it down to this (the first “choose:”):
- choose:
#
# a. Manual override start
#
- conditions:
- condition: trigger
id: set_temperature_change
#ignore if it is just an echo from a setting from this automation
- condition: state
entity_id: !input echoblock_timer
state: idle
#ignore if same value as before (e.g. triggered by template reload)
- condition: template
value_template: "{{ not states(local_set_temperature) == states(local_required_temperature) }}"
sequence:
- service: timer.start
data:
duration: !input manual_override_period
target:
entity_id: !input manual_override_timer
#
# b. Manual override end
#
- conditions:
- condition: trigger
id: manual_override_end
#
# c. Door or window opened - start timer
#
- conditions:
- condition: trigger
id: door_or_window_opened
# Do not restart timer if already running
- condition: not
conditions:
- condition: state
entity_id: !input door_or_window_open_timer
state: active
sequence:
- service: timer.start
data:
duration: !input door_or_window_open_period
target:
entity_id: !input door_or_window_open_timer
#
# d. Doors and windows closed - restart and pause timer
#
- conditions:
- condition: trigger
id: doors_and_windows_closed
sequence:
- service: timer.start
data:
duration: !input door_or_window_open_period
target:
entity_id: !input door_or_window_open_timer
- service: timer.pause # Keeps it in the active state
target:
entity_id: !input door_or_window_open_timer
#
# e. Unoccupancy - start timer
#
- conditions:
- condition: trigger
id: area_unoccupied
# Do not restart timer if already running
- condition: not
conditions:
- condition: state
entity_id: !input unoccupancy_timer
state: active
sequence:
- service: timer.start
data:
duration: !input unoccupancy_period
target:
entity_id: !input unoccupancy_timer
#
# f. area occupied - restart and pause timer
#
- conditions:
- condition: trigger
id: area_occupied
sequence:
- service: timer.start
data:
duration: !input unoccupancy_period
target:
entity_id: !input unoccupancy_timer
- service: timer.pause # Keeps it in the active state
target:
entity_id: !input unoccupancy_timer
then it’s saying that the choose action in that action step has an error that is missing the key called “sequence:” from the second condition in the list since it is referencing index 1 ( [1] ).
so index 1 of the first “choose:” is this:
- conditions:
- condition: trigger
id: manual_override_end
#
# c. Door or window opened - start timer
#
- conditions:
notice that there is no “sequence:” key between the first “- conditions:” section there (which is the second condition in the first choose action) and the next “- conditions:” section.
every choose conditions section section has to have an associated sequence section. yours is missing that one.
But even then the HA templates are only a subset of the full jinja2 templates so not all of the commands are available in HA templates (but most are). And HA has added a few more that aren’t in the standard jinja2 library as well.
So you should always start with the standard HA templating docs first as that will get you the closest to what is used. Then if you need additional info then go to the Jinja2 templating docs. Then use the Python docs.
Finally if all else fails you can come back here and beg ask for help.