With many thanks to @CentralCommand, @DarkWarden, @Didgeridrew, and @EdwardTFN I now have blueprint code with correct syntax – no error messages or silent rejections. Most of it is working too but there are a couple of puzzling cases where the trace says it is going down the right path but the output is nevertheless wrong! That puzzles me. Can anyone see what is happening?
I am testing by setting up an instance of the blueprint connected to a sample radiator controller, presence detector and closure detector on my desk. I am exposing all the helpers (used as persistent global variables) in a dashboard alongside a thermostat card. Note in particular the ‘radiator_setting_reason’ helper, with which the automation tells the user why the thermostat has the setting it has. It also tells me what path the automation followed in action step 2. The problem is that the trace tells me a different story. Puzzling.
Working:
The automation correctly reports that the heating is turned off when the room is unoccupied or a door or window is opened. It reverts back to ‘turned off because nothing is scheduled’ when the room is occupied again and the doors and windows are closed. I tested with all combinations of opening and occupancy. So far so good.
Two cases do not work
a) when the temperature is set manually
b) when the temperature is set by calendar event
In both cases ‘Step 1’ executes correctly. For the manual case, the temperature is captured and the timer is started. At a calendar event start, the event name (aka summary), start time, end time, description and the temperature set in the description are all captured correctly.
The problem is in step 2: According to the trace, the automation is following the correct path to set the manual/event temperature on the radiator thermostat, and report ‘Set manually’ or ‘Set according to a calendar event’. However, the thermostat is being set to 5 (regarded as ‘off’) and the dashboard is showing ‘Turned off because nothing is scheduled’.
The six branches are all the same in structure and essentially the same as my model automation created using the UI, on which I based the blueprint.
Is there a blindingly obvious error that I am overlooking, or something very subtle?? How can I debug if tracing and the actual output do not tally?
Screenshots
-
Scheduled event - trace
(Edit: sorry, wrong screenshot included) -
Schedule event - result
-
Manual override - trace
-
Manual override - result
Code
### ----------------------------------------------------------------------------
### ANDY'S RADIATOR CONTROLLER
###
### Andy Symons 2-Feb-23
###
### Controls radiator temperature from a calendar,
### allows temporary manual override,
### and turns off radiator if a door or window is opened,
### or if the room is unoccupied for a while
### ----------------------------------------------------------------------------
blueprint:
name: Andys Radiator Controller
description: Controls radiator temperature from a calendar, allows temporary manual override, and turns off radiator if a door or window is opened or if the room is unoccupied for a while
domain: automation
### ----------------------------------------------------------------------------
### INPUTS
### ----------------------------------------------------------------------------
input:
radiator_thermostat:
name: Radiator thermostat
description: The entity that controls the radiator TRV
selector:
entity:
domain: climate
radiator_set_temperature_sensor:
name: Radiator thermostat set temperature
description: A (template) sensor that reads the set temperature from the radiator thermostat
selector:
entity:
domain: sensor
radiator_calendar:
name: Radiator calendar
description: The calendar dedicated to scheduling events for this radiator
selector:
entity:
domain: calendar
door_or_window_opening_sensor:
name: Door or window opening sensor
description: The sensor [group] that detects whether any external door or window is open
selector:
entity:
domain: binary_sensor
device_class: opening
door_or_window_open_recognition_time:
name: Door or window open recognition time
description: The time for which a door or window can be open before the heating switches off
selector:
time:
default: "00:03:00"
door_or_window_closed_recognition_time:
name: Door or window closed recognition time
description: The time for which a door or window has to be closed before the fact is used
selector:
time:
default: "00:01:00"
room_occupancy_sensor:
name: Room occupancy sensor
description: The sensor [group] that detects whether there is anyone in the room
selector:
entity:
domain: binary_sensor
device_class: occupancy
room_occupancy_recognition_time:
name: Room occupancy recognition time
description: The time for which the room has to be unoccupied before the fact is used
selector:
time:
default: "00:02:00"
room_unoccupancy_recognition_time:
name: Room unoccupancy recognition time
description: The time for which the room can be unoccupied before the heating switches off (except suring the warm-up period)
selector:
duration:
default: "01:00:00"
warmup_timer:
name: Warmup timer
description: The global variable (helper) to hold the timer for the event warmup period
selector:
entity:
domain: timer
warmup_period:
name: Warmup period
description: The period of time from the start of a new event for which room unoccupancy will be ignored
selector:
time:
default: "02:00:00"
event_name:
name: Event name
description: The global variable (helper) to hold the name (aka Summary) of the current event (if any)
selector:
entity:
domain: input_text
event_description:
name: Event description
description: The global variable (helper) to hold the description of the current event (if any)
selector:
entity:
domain: input_text
event_start:
name: Event start
description: The global variable (helper) to hold the start date and time of the current event (if any)
selector:
entity:
domain: input_datetime
event_end:
name: Event end
description: The global variable (helper) to hold the end date and time of the current event (if any)
selector:
entity:
domain: input_datetime
event_end_offset_time:
name: Event end offset time
description: The time for which acting on an event end is deferred in order to detect a contiguous event
selector:
time:
default: "00:05:00"
event_temperature:
name: Event temperature
description: The global variable (helper) to hold the temperature specified in the current event (if any)
selector:
entity:
domain: input_number
manual_temperature:
name: Manual temperature
description: The global variable (helper) to hold the temperature specified by manual control of the device or the dashboard
selector:
entity:
domain: input_number
manual_mode_recognition_period:
name: Manual mode recognition time
description: The time period for which a manual setting has to be stable before it is taken as a new manual setting
selector:
time:
default: "00:00:10"
manual_override_timer:
name: Manual override timer
description: The global variable (helper) to hold the timer for a manual intervention
selector:
entity:
domain: timer
manual_override_period:
name: Manual override period
description: The time period for which a manual intervention will override the schedule
selector:
time:
default: "02:00:00"
setting_reason:
name: 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:
domain: input_text
echoblock_timer:
name: 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:
domain: timer
echoblock_period:
name: Echoblock period
description: The time period for which am echo may be received from the automation setting the thermostat
selector:
time:
default: "00:00:05"
mode: single
## ----------------------------------------------------------------------------
## LOCAL VARİABLES
## needed to capture global varable values for use in templates
## ----------------------------------------------------------------------------
variables:
local_event_name: !input event_name
local_event_description: !input event_description
local_radiator_set_temperature: !input radiator_set_temperature_sensor
## ----------------------------------------------------------------------------
## TRIGGERS
## ----------------------------------------------------------------------------
trigger:
# 1. Calendar event start
- platform: calendar
event: start
entity_id: !input radiator_calendar
id: event_start
# 2. Calendar event end
- platform: calendar
event: end
offset: !input event_end_offset_time
entity_id: !input radiator_calendar
id: event_end
# 3. Calendar state to on
# (Similar to event start but can be around 30 seconds later)
- platform: state
entity_id: !input radiator_calendar
from: "off"
to: "on"
id: calendar_state_to_on
# 4. Calendar state from on to off
# (Similar to event end but can be around 30 seconds later)
- platform: state
entity_id: !input radiator_calendar
from: "on"
to: "off"
id: calendar_state_to_off
# 5. Change in the thermostat set temperature
# (could be manual override but could also be an echo at this stage)
- platform: state
entity_id: !input radiator_set_temperature_sensor
for: !input manual_mode_recognition_period
id: set_temperature_change
# 6. End of manual override
- platform: state
entity_id: !input manual_override_timer
from: active
to: idle
id: manual_override_end
# 7. Room becomes unoccupied
- platform: state
entity_id: !input room_occupancy_sensor
from: "on"
to: "off"
for: !input room_unoccupancy_recognition_time
id: room_unoccupied
# 8. Room becomes occupied
- platform: state
entity_id: !input room_occupancy_sensor
from: "off"
to: "on"
for: !input room_occupancy_recognition_time
id: room_occupied
# 9. A door or window is opened
- platform: state
entity_id: !input door_or_window_opening_sensor
from: "off"
to: "on"
for: !input door_or_window_open_recognition_time
id: door_or_window_opened
# 10. All doors and windows closed
- platform: state
entity_id: !input door_or_window_opening_sensor
from: "on"
to: "off"
for: !input door_or_window_closed_recognition_time
id: doors_and_windows_closed
## ----------------------------------------------------------------------------
## ACTIONS STEP 1 -- SET THE STATE VARİABLES
## ----------------------------------------------------------------------------
action:
- choose:
#
# a. Calendar event start
#
- conditions:
- condition: trigger
id: event_start
sequence:
# i. capture the event name, start, end and description
- service: input_text.set_value
data:
value: "{{ trigger.calendar_event.summary }}"
target:
entity_id: !input event_name
- service: input_datetime.set_datetime
data:
datetime: "{{ trigger.calendar_event.start }}"
target:
entity_id: !input event_start
- service: input_datetime.set_datetime
data:
datetime: "{{ trigger.calendar_event.end }}"
target:
entity_id: !input event_end
- service: input_text.set_value
data:
value: "{{ trigger.calendar_event.description }}"
target:
entity_id: !input event_description
# ii. Extract the temperature from the description (enclosed in hashes) and round to one decimal place
- service: input_number.set_value
data:
value: >-
{% set temperature_text = states(local_event_description).split('#')[1] %}
{{ '%0.1f' | format(float(temperature_text)) }}
target:
entity_id: !input event_temperature
# iii. start the warmup period timer
- service: timer.start
data:
duration: !input warmup_period
target:
entity_id: !input warmup_timer
#
# b. Calendar event end
#
- conditions:
- condition: trigger
id: event_end
# Only act if the ending event is the current one; otherwise a consecutive event has already started
- condition: template
value_template: >-
{{ trigger.calendar_event.summary == states(local_event_name) }}
sequence:
# i. Clear the event name
- service: input_text.set_value
data:
value: (none)
target:
entity_id: !input event_name
# ii. Clear the event description
- service: input_text.set_value
data:
value: (none)
target:
entity_id: !input event_description
# iii. Clear the event temperature
- service: input_number.set_value
data:
value: 5
target:
entity_id: !input event_temperature
# iv. Clear the warmup timer
- service: timer.cancel
data: {}
target:
entity_id: !input warmup_timer
#
# c. 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
sequence:
- service: timer.start
data:
duration: !input manual_override_period
target:
entity_id: !input manual_override_timer
- service: input_number.set_value
data:
value: "{{ states(local_radiator_set_temperature) }}"
target:
entity_id: !input manual_temperature
#
# d. Manual override end
#
- conditions:
- condition: trigger
id: manual_override_end
sequence:
- service: input_number.set_value
data:
value: 5
target:
entity_id: !input manual_temperature
## ----------------------------------------------------------------------------
## ACTIONS STEP 2 -- SET THE TEMPERATURE ON THE THERMOSTAT AND RECORD WHY
## ----------------------------------------------------------------------------
- choose:
# 1. If a door or window has been open for the set time, turn the heating off
- conditions:
- condition: state
state: "on"
for: !input door_or_window_open_recognition_time
entity_id: !input door_or_window_opening_sensor
sequence:
- service: climate.set_temperature
data:
temperature: 5
target:
entity_id: !input radiator_thermostat
- 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 room has been unoccupied for the set time, turn the heating off
- conditions:
- condition: state
entity_id: !input room_occupancy_sensor
state: "off"
for: !input room_unoccupancy_recognition_time
# Do not act on unoccupancy during the warmup period
- condition: state
entity_id: !input warmup_timer
state: idle
sequence:
- service: climate.set_temperature
data:
temperature: 5
target:
entity_id: !input radiator_thermostat
- service: input_text.set_value
data:
value: Turned off because the room 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: climate.set_temperature
data:
temperature: !input manual_temperature
target:
entity_id: !input radiator_thermostat
- service: input_text.set_value
data:
value: Set manually
target:
entity_id: !input setting_reason
# 4. If there is an active calendar event
- conditions:
- condition: state
entity_id: !input radiator_calendar
state: "on"
sequence:
- service: climate.set_temperature
data:
temperature: !input event_temperature
target:
entity_id: !input radiator_thermostat
- service: input_text.set_value
data:
value: Set according to a calendar event
target:
entity_id: !input setting_reason
# 5. If there is no active calendar event
- conditions:
- condition: state
entity_id: !input radiator_calendar
state: "off"
sequence:
- service: climate.set_temperature
data:
temperature: 5
target:
entity_id: !input radiator_thermostat
- service: input_text.set_value
data:
value: Turned off because nothing is scheduled
target:
entity_id: !input setting_reason
# Default should never happen!
default:
- service: climate.set_temperature
data:
temperature: 5
target:
entity_id: !input radiator_thermostat
- service: input_text.set_value
data:
value: Turned off by default
target:
entity_id: !input setting_reason
# Start the echo-block timer
- service: timer.start
data:
duration: !input echoblock_period
target:
entity_id: !input echoblock_timer