Creating Scripts with the Visual Editor
WoowTech ships with a point-and-click editor for assembling scripts, so you don't have to hand-write YAML to get a working sequence. A script is simply an ordered run of steps that fires the instant you call it — think "raise the blinds, hold for a moment, then start the coffee machine." Anything that can call an action can launch a script: a dashboard button, the Assist voice helper, an automation, and so on.
Scripts behave almost exactly like automations. The one real difference is that automations start themselves whenever a trigger fires, while a script waits for you to invoke it.
Opening the editor
Reach the editor through Settings > Automations & scenes > Scripts, then choose Add script. The editor presents your script as a stack of building blocks you add, reorder, and configure visually. Whatever you build there is saved as YAML behind the scenes, and you can flip to the YAML view at any time using the three-dot menu in the top-right corner. Everything below shows both how a block behaves and the YAML it produces.
When a script is invoked as a step inside an automation, it also receives the trigger variable describing what set the automation off.
The underlying structure
Each script is a list of key/value maps describing actions. A script with a lone action can skip the surrounding list. Every action may carry an optional alias, which is the friendly label the editor shows on each block.
# What the editor writes for a two-step script
script:
morning_routine:
sequence:
- alias: "Raise the bedroom blinds"
action: cover.open_cover
target:
entity_id: cover.bedroom_blinds
- alias: "Confirm the blinds opened"
action: notify.notify
data:
message: "Bedroom blinds are open!"
Adding an action block
The most common block performs an action. The editor gives you dropdowns for the action, its target, and any extra data.
- alias: "Dim the den"
action: light.turn_on
target:
entity_id: group.den
data:
brightness: 90
The scene shortcut
Instead of picking scene.turn_on, the editor lets you choose a scene directly, which writes the shorthand form:
- scene: scene.dinner_party
Setting variables
A variables block defines or overrides values that later templated blocks can use. There's also a separate script-variables setting for values that live across the whole script.
- alias: "Set variables"
variables:
rooms:
- switch.attic_fan
- switch.cellar_fan
level: 60
- alias: "Run the fans"
action: fan.set_percentage
target:
entity_id: "{{ rooms }}"
data:
percentage: "{{ level }}"
Variable values can be templated:
- alias: "Compose a status line"
variables:
blind_status: "The skylight is {{ states('cover.skylight') }}."
- alias: "Send the status line"
action: notify.send_message
target:
entity_id: notify.my_device
data:
message: "{{ blind_status }}"
Variable scope
Assigning to a variable that already exists updates that one. Assigning to a fresh name creates it at the top-level (script-run) scope.
sequence:
# Default the tally to zero
- variables:
arrivals: 0
# Increment when Marco is home
- if:
- condition: state
entity_id: device_tracker.marco
state: "home"
then:
- variables:
arrivals: "{{ arrivals + 1 }}"
marco_home: true
- action: notify.notify
data:
message: "There are {{ arrivals }} people home" # "There are 1 people home"
# The change carries forward
- action: notify.notify
data:
message: "There are {{ arrivals }} people home {% if marco_home is defined %}(Marco included){% endif %}"
Inserting a condition
Drop a condition block into the sequence to stop everything below it when the condition isn't met. If it doesn't return true, the script halts at that point.
Note: A condition only stops its own sequence block. Inside a repeat block it ends just that iteration; inside a choose block it stops only that choose.
# Continue only if Marco is home
- alias: "Check Marco is home"
condition: state
entity_id: device_tracker.marco
state: "home"
Supply several conditions and the script proceeds only when ALL of them are true:
- alias: "Marco home AND it's warm out"
conditions:
- condition: state
entity_id: "device_tracker.marco"
state: "home"
- condition: numeric_state
entity_id: "sensor.outdoor_temperature"
above: 24
Adding a delay
A delay block suspends the script for a set time. The editor offers fields for days, hours, minutes, and seconds, all of which map to one of these forms:
# Bare number of seconds — wait 10 seconds
- alias: "Wait 10s"
delay: 10
# HH:MM — wait 3 hours
- delay: "03:00"
# HH:MM:SS — wait 45 seconds
- delay: "00:00:45"
# Named units; combine as needed, at least one required.
# Milliseconds mean *at least* that long, not exact.
- delay:
minutes: 4
Delays accept templates as well:
- delay: "{{ states('input_number.wait_minutes') | multiply(60) | int }}"
Wait blocks
Wait blocks pause until an entity reaches a templated state, or until a trigger fires.
Wait for a template
The template is evaluated; if true the script continues, otherwise it waits for it to become true. Re-evaluation happens when a referenced entity changes. Functions like now() won't trigger continuous re-checks — reference a minutely- or daily-updating Time and Date sensor if you need that.
- alias: "Wait for the TV to switch off"
wait_template: "{{ is_state('media_player.living_room_tv', 'off') }}"
Wait for a trigger
This block reuses any automation trigger and resumes once any of them fires. Earlier trigger variables, variables, and script variables all pass through.
- alias: "Wait for SENSOR_PING or door open 8s"
wait_for_trigger:
- trigger: event
event_type: SENSOR_PING
- trigger: state
entity_id: binary_sensor.back_door
to: "on"
for: 8
Timeouts
Either wait type can take a timeout, after which the script continues anyway. Timeout shares delay's syntax and accepts templates.
- wait_template: "{{ is_state('binary_sensor.foyer', 'on') }}"
timeout: "00:02:00"
Add continue_on_timeout: false to abort once the timeout passes instead:
- wait_for_trigger:
- trigger: event
event_type: ifttt_webhook_received
event_data:
action: device_online
timeout:
minutes: "{{ timeout_minutes }}"
continue_on_timeout: false
Without that line the script always proceeds, since continue_on_timeout defaults to true.
The wait result variable
After any wait finishes, a wait variable records the outcome:
| Variable | Meaning |
|---|---|
wait.completed |
true if the condition was met, else false |
wait.remaining |
Timeout left, or none if none was set |
wait.trigger |
Only after wait_for_trigger; which trigger fired, or none on timeout |
Branch on it, or chain waits under one overall timeout:
- wait_template: "{{ is_state('binary_sensor.shutter', 'on') }}"
timeout: 15
- if:
- "{{ not wait.completed }}"
then:
- action: script.shutter_stuck
else:
- action: script.turn_on
target:
entity_id:
- script.shutter_moved
- script.play_chime
Firing an event
The Fire Manual Event block emits an event. Events can launch an automation or signal another integration — here one writes to the Activity panel.
- alias: "Emit LOGBOOK_ENTRY event"
event: LOGBOOK_ENTRY
event_data:
name: Marco
message: arrived home
entity_id: device_tracker.marco
domain: light
Attach custom data with event_data, which accepts templates:
- event: TANK_LEVEL_READ
event_data:
name: tankEvent
payload: "{{ tankReading }}"
Emitting then catching a custom event
One automation raises event_relay_state_changed:
- alias: "Emit Event"
triggers:
- trigger: state
entity_id: switch.workshop
to: "on"
actions:
- event: event_relay_state_changed
event_data:
state: "on"
Another listens for it and reacts:
- alias: "Catch Event"
triggers:
- trigger: event
event_type: event_relay_state_changed
actions:
- action: notify.notify
data:
message: "workshop relay is now {{ trigger.event.data.state }}"
Repeat blocks
A repeat block reruns a set of actions, with full nesting. Three forms control the loop count.
Counted
Provide a count, optionally templated:
script:
pulse_lamp:
mode: restart
sequence:
- action: light.turn_on
target:
entity_id: "light.{{ lamp }}"
- alias: "Toggle 'cycles' times"
repeat:
count: "{{ cycles|int * 2 - 1 }}"
sequence:
- delay: 1
- action: light.toggle
target:
entity_id: "light.{{ lamp }}"
For each
Iterate over a list, with the current item as repeat.item:
repeat:
for_each:
- "patio"
- "shed"
- "loft"
sequence:
- action: switch.turn_off
target:
entity_id: "switch.{{ repeat.item }}_heater"
Items may be mappings too:
repeat:
for_each:
- locale: Spanish
text: Hola Mundo
- locale: Italian
text: Ciao Mondo
sequence:
- action: notify.tablet
data:
title: "Greeting in {{ repeat.item.locale }}"
message: "{{ repeat.item.text }}!"
While
Conditions checked before each pass; loops while they stay true:
script:
keep_running:
sequence:
- action: script.warm_up
- alias: "Loop WHILE true"
repeat:
while:
- condition: state
entity_id: input_boolean.keep_active
state: "on"
- condition: template
value_template: "{{ repeat.index <= 30 }}"
sequence:
- action: script.process_item
Until
Conditions checked after each pass, so it runs at least once and loops until they hold:
- alias: "Loop UNTIL the relay reports on"
repeat:
sequence:
- action: shell_command.engage_relay
- delay:
milliseconds: 250
until:
- condition: state
entity_id: binary_sensor.relay_feedback
state: "on"
Loop variable
Inside a repeat block, the repeat variable is available in sequence, while, and until:
| Field | Description |
|---|---|
first |
True on the first pass |
index |
Pass number: 1, 2, 3, … |
last |
True on the final pass (counted loops only) |
If-then block
Run a then sequence when the and-combined conditions pass, with an optional else:
script:
- if:
- alias: "If nobody's home"
condition: state
entity_id: zone.home
state: 0
then:
- alias: "Start the robot vacuum"
action: vacuum.start
target:
area_id: hallway
else:
- action: notify.notify
data:
message: "Skipping the vacuum — someone's home!"
Choose block
The choose block selects one of several sequences. Each sequence has its own condition list; the first whose conditions all pass runs. An optional default covers the case where none match — like an if/elif/else.
automation:
- triggers:
- trigger: state
entity_id: input_boolean.run_scene_sim
to: "on"
mode: restart
actions:
- choose:
# IF early
- conditions:
- condition: template
value_template: "{{ now().hour < 7 }}"
sequence:
- action: script.sim_dawn
# ELIF afternoon
- conditions:
- condition: template
value_template: "{{ now().hour < 16 }}"
sequence:
- action: light.turn_off
target:
entity_id: light.parlor
- action: script.sim_afternoon
# ELSE late
default:
- action: light.turn_off
target:
entity_id: light.study
- delay:
minutes: "{{ range(1, 11)|random }}"
- action: light.turn_off
target:
entity_id: all
The shorthand template condition form also works:
automation:
- triggers:
- trigger: state
entity_id: input_select.household_state
actions:
- choose:
- conditions: >
{{ trigger.to_state.state == 'Home' and
is_state('binary_sensor.all_locked', 'on') }}
sequence:
- action: script.come_home
data:
ok: true
- conditions: "{{ trigger.to_state.state == 'Away' }}"
sequence:
- action: script.leave_home
Grouping with sequence
A sequence block bundles actions so they run one after another, and lets you collapse related sets in the UI for tidiness. Combined with parallel, it can run several ordered groups at once.
automation:
- triggers:
- trigger: state
entity_id: binary_sensor.garage_motion
to: "on"
actions:
- alias: "Switch on devices"
sequence:
- action: light.turn_on
target:
entity_id: light.workbench
- action: siren.turn_on
target:
entity_id: siren.garage_alarm
- alias: "Send alerts"
sequence:
- action: notify.person1
data:
message: "Garage motion picked up!"
- action: notify.person2
data:
message: "Note — garage sensor went off..."
Running things in parallel
By default actions run one at a time. A parallel block starts them all together, useful when order doesn't matter and they're independent.
automation:
- triggers:
- trigger: state
entity_id: binary_sensor.garage_motion
to: "on"
actions:
- parallel:
- action: notify.person1
data:
message: "Fired simultaneously!"
- action: notify.person2
data:
message: "Fired simultaneously!"
You can nest an ordered group inside a parallel block:
script:
parallel_demo:
sequence:
- parallel:
- sequence:
- wait_for_trigger:
- trigger: state
entity_id: binary_sensor.garage_motion
to: "on"
- action: notify.person1
data:
message: "Waited for motion first"
- action: notify.person2
data:
message: "Sent right away, no waiting!"
Caution: Parallel runs help in some cases, but reach for them only when needed — sequential actions usually do the job. Caveats: there's no order guarantee on completion; if one branch errors the others keep going; and variables in separate branches can clash, so name them uniquely.
Stopping the sequence
A stop block ends the script early and can return a response. Its text is logged and appears in traces.
- stop: "End the rest of the sequence here"
Return data with response_variable, naming a variable holding a key/value mapping:
- stop: "End the rest of the sequence here"
response_variable: "my_response_variable"
Mark it as a failure with error:
- stop: "Something went sideways!"
error: true
Carrying on after an error
Normally an action error halts the whole sequence and marks the run as errored. When an error is expected and harmless, set continue_on_error: true on that action (it defaults to false).
- alias: "If this errors..."
continue_on_error: true
action: notify.unreliable_gateway
data:
message: "I may fail..."
- alias: "This still runs!"
action: persistent_notification.create
data:
title: "Hi!"
message: "Doing fine..."
This won't hide misconfiguration or errors WoowTech doesn't handle.
Turning off an action
Disable a block without deleting it by adding enabled: false:
script:
morning_routine:
sequence:
# Disabled — won't run, no message sent
- enabled: false
alias: "Announce the blinds"
action: notify.notify
data:
message: "Raising the blinds!"
# Enabled — runs normally
- alias: "Raise the blinds"
action: cover.open_cover
target:
entity_id: cover.bedroom_blinds
You can also gate a block on a template or blueprint input:
blueprint:
input:
enable_step:
name: Enable
selector:
boolean:
actions:
- delay: 0:50
enabled: !input enable_step
Replying to a conversation
The set conversation response block returns a custom reply when a conversation engine (like a voice assistant) launches the automation. It can be templated.
- variables:
code: "789"
- set_conversation_response: "{{ 'Verifying ' ~ code }}"
The reply reaches the engine once the automation ends; the latest one wins if set more than once. Clear it with None:
set_conversation_response: ~
If no conversation engine started the run, the response is ignored.
Creating Scripts with the Visual Editor
WoowTech ships with a point-and-click editor for assembling scripts, so you don't have to hand-write YAML to get a working sequence. A script is simply an ordered run of steps that fires the instant you call it — think "raise the blinds, hold for a moment, then start the coffee machine." Anything that can call an action can launch a script: a dashboard button, the Assist voice helper, an automation, and so on.
Scripts behave almost exactly like automations. The one real difference is that automations start themselves whenever a trigger fires, while a script waits for you to invoke it.
Opening the editor
Reach the editor through Settings > Automations & scenes > Scripts, then choose Add script. The editor presents your script as a stack of building blocks you add, reorder, and configure visually. Whatever you build there is saved as YAML behind the scenes, and you can flip to the YAML view at any time using the three-dot menu in the top-right corner. Everything below shows both how a block behaves and the YAML it produces.
When a script is invoked as a step inside an automation, it also receives the trigger variable describing what set the automation off.
The underlying structure
Each script is a list of key/value maps describing actions. A script with a lone action can skip the surrounding list. Every action may carry an optional alias, which is the friendly label the editor shows on each block.
# What the editor writes for a two-step script
script:
morning_routine:
sequence:
- alias: "Raise the bedroom blinds"
action: cover.open_cover
target:
entity_id: cover.bedroom_blinds
- alias: "Confirm the blinds opened"
action: notify.notify
data:
message: "Bedroom blinds are open!"
Adding an action block
The most common block performs an action. The editor gives you dropdowns for the action, its target, and any extra data.
- alias: "Dim the den"
action: light.turn_on
target:
entity_id: group.den
data:
brightness: 90
The scene shortcut
Instead of picking scene.turn_on, the editor lets you choose a scene directly, which writes the shorthand form:
- scene: scene.dinner_party
Setting variables
A variables block defines or overrides values that later templated blocks can use. There's also a separate script-variables setting for values that live across the whole script.
- alias: "Set variables"
variables:
rooms:
- switch.attic_fan
- switch.cellar_fan
level: 60
- alias: "Run the fans"
action: fan.set_percentage
target:
entity_id: "{{ rooms }}"
data:
percentage: "{{ level }}"
Variable values can be templated:
- alias: "Compose a status line"
variables:
blind_status: "The skylight is {{ states('cover.skylight') }}."
- alias: "Send the status line"
action: notify.send_message
target:
entity_id: notify.my_device
data:
message: "{{ blind_status }}"
Variable scope
Assigning to a variable that already exists updates that one. Assigning to a fresh name creates it at the top-level (script-run) scope.
sequence:
# Default the tally to zero
- variables:
arrivals: 0
# Increment when Marco is home
- if:
- condition: state
entity_id: device_tracker.marco
state: "home"
then:
- variables:
arrivals: "{{ arrivals + 1 }}"
marco_home: true
- action: notify.notify
data:
message: "There are {{ arrivals }} people home" # "There are 1 people home"
# The change carries forward
- action: notify.notify
data:
message: "There are {{ arrivals }} people home {% if marco_home is defined %}(Marco included){% endif %}"
Inserting a condition
Drop a condition block into the sequence to stop everything below it when the condition isn't met. If it doesn't return true, the script halts at that point.
Note: A condition only stops its own sequence block. Inside a repeat block it ends just that iteration; inside a choose block it stops only that choose.
# Continue only if Marco is home
- alias: "Check Marco is home"
condition: state
entity_id: device_tracker.marco
state: "home"
Supply several conditions and the script proceeds only when ALL of them are true:
- alias: "Marco home AND it's warm out"
conditions:
- condition: state
entity_id: "device_tracker.marco"
state: "home"
- condition: numeric_state
entity_id: "sensor.outdoor_temperature"
above: 24
Adding a delay
A delay block suspends the script for a set time. The editor offers fields for days, hours, minutes, and seconds, all of which map to one of these forms:
# Bare number of seconds — wait 10 seconds
- alias: "Wait 10s"
delay: 10
# HH:MM — wait 3 hours
- delay: "03:00"
# HH:MM:SS — wait 45 seconds
- delay: "00:00:45"
# Named units; combine as needed, at least one required.
# Milliseconds mean *at least* that long, not exact.
- delay:
minutes: 4
Delays accept templates as well:
- delay: "{{ states('input_number.wait_minutes') | multiply(60) | int }}"
Wait blocks
Wait blocks pause until an entity reaches a templated state, or until a trigger fires.
Wait for a template
The template is evaluated; if true the script continues, otherwise it waits for it to become true. Re-evaluation happens when a referenced entity changes. Functions like now() won't trigger continuous re-checks — reference a minutely- or daily-updating Time and Date sensor if you need that.
- alias: "Wait for the TV to switch off"
wait_template: "{{ is_state('media_player.living_room_tv', 'off') }}"
Wait for a trigger
This block reuses any automation trigger and resumes once any of them fires. Earlier trigger variables, variables, and script variables all pass through.
- alias: "Wait for SENSOR_PING or door open 8s"
wait_for_trigger:
- trigger: event
event_type: SENSOR_PING
- trigger: state
entity_id: binary_sensor.back_door
to: "on"
for: 8
Timeouts
Either wait type can take a timeout, after which the script continues anyway. Timeout shares delay's syntax and accepts templates.
- wait_template: "{{ is_state('binary_sensor.foyer', 'on') }}"
timeout: "00:02:00"
Add continue_on_timeout: false to abort once the timeout passes instead:
- wait_for_trigger:
- trigger: event
event_type: ifttt_webhook_received
event_data:
action: device_online
timeout:
minutes: "{{ timeout_minutes }}"
continue_on_timeout: false
Without that line the script always proceeds, since continue_on_timeout defaults to true.
The wait result variable
After any wait finishes, a wait variable records the outcome:
| Variable | Meaning |
|---|---|
wait.completed |
true if the condition was met, else false |
wait.remaining |
Timeout left, or none if none was set |
wait.trigger |
Only after wait_for_trigger; which trigger fired, or none on timeout |
Branch on it, or chain waits under one overall timeout:
- wait_template: "{{ is_state('binary_sensor.shutter', 'on') }}"
timeout: 15
- if:
- "{{ not wait.completed }}"
then:
- action: script.shutter_stuck
else:
- action: script.turn_on
target:
entity_id:
- script.shutter_moved
- script.play_chime
Firing an event
The Fire Manual Event block emits an event. Events can launch an automation or signal another integration — here one writes to the Activity panel.
- alias: "Emit LOGBOOK_ENTRY event"
event: LOGBOOK_ENTRY
event_data:
name: Marco
message: arrived home
entity_id: device_tracker.marco
domain: light
Attach custom data with event_data, which accepts templates:
- event: TANK_LEVEL_READ
event_data:
name: tankEvent
payload: "{{ tankReading }}"
Emitting then catching a custom event
One automation raises event_relay_state_changed:
- alias: "Emit Event"
triggers:
- trigger: state
entity_id: switch.workshop
to: "on"
actions:
- event: event_relay_state_changed
event_data:
state: "on"
Another listens for it and reacts:
- alias: "Catch Event"
triggers:
- trigger: event
event_type: event_relay_state_changed
actions:
- action: notify.notify
data:
message: "workshop relay is now {{ trigger.event.data.state }}"
Repeat blocks
A repeat block reruns a set of actions, with full nesting. Three forms control the loop count.
Counted
Provide a count, optionally templated:
script:
pulse_lamp:
mode: restart
sequence:
- action: light.turn_on
target:
entity_id: "light.{{ lamp }}"
- alias: "Toggle 'cycles' times"
repeat:
count: "{{ cycles|int * 2 - 1 }}"
sequence:
- delay: 1
- action: light.toggle
target:
entity_id: "light.{{ lamp }}"
For each
Iterate over a list, with the current item as repeat.item:
repeat:
for_each:
- "patio"
- "shed"
- "loft"
sequence:
- action: switch.turn_off
target:
entity_id: "switch.{{ repeat.item }}_heater"
Items may be mappings too:
repeat:
for_each:
- locale: Spanish
text: Hola Mundo
- locale: Italian
text: Ciao Mondo
sequence:
- action: notify.tablet
data:
title: "Greeting in {{ repeat.item.locale }}"
message: "{{ repeat.item.text }}!"
While
Conditions checked before each pass; loops while they stay true:
script:
keep_running:
sequence:
- action: script.warm_up
- alias: "Loop WHILE true"
repeat:
while:
- condition: state
entity_id: input_boolean.keep_active
state: "on"
- condition: template
value_template: "{{ repeat.index <= 30 }}"
sequence:
- action: script.process_item
Until
Conditions checked after each pass, so it runs at least once and loops until they hold:
- alias: "Loop UNTIL the relay reports on"
repeat:
sequence:
- action: shell_command.engage_relay
- delay:
milliseconds: 250
until:
- condition: state
entity_id: binary_sensor.relay_feedback
state: "on"
Loop variable
Inside a repeat block, the repeat variable is available in sequence, while, and until:
| Field | Description |
|---|---|
first |
True on the first pass |
index |
Pass number: 1, 2, 3, … |
last |
True on the final pass (counted loops only) |
If-then block
Run a then sequence when the and-combined conditions pass, with an optional else:
script:
- if:
- alias: "If nobody's home"
condition: state
entity_id: zone.home
state: 0
then:
- alias: "Start the robot vacuum"
action: vacuum.start
target:
area_id: hallway
else:
- action: notify.notify
data:
message: "Skipping the vacuum — someone's home!"
Choose block
The choose block selects one of several sequences. Each sequence has its own condition list; the first whose conditions all pass runs. An optional default covers the case where none match — like an if/elif/else.
automation:
- triggers:
- trigger: state
entity_id: input_boolean.run_scene_sim
to: "on"
mode: restart
actions:
- choose:
# IF early
- conditions:
- condition: template
value_template: "{{ now().hour < 7 }}"
sequence:
- action: script.sim_dawn
# ELIF afternoon
- conditions:
- condition: template
value_template: "{{ now().hour < 16 }}"
sequence:
- action: light.turn_off
target:
entity_id: light.parlor
- action: script.sim_afternoon
# ELSE late
default:
- action: light.turn_off
target:
entity_id: light.study
- delay:
minutes: "{{ range(1, 11)|random }}"
- action: light.turn_off
target:
entity_id: all
The shorthand template condition form also works:
automation:
- triggers:
- trigger: state
entity_id: input_select.household_state
actions:
- choose:
- conditions: >
{{ trigger.to_state.state == 'Home' and
is_state('binary_sensor.all_locked', 'on') }}
sequence:
- action: script.come_home
data:
ok: true
- conditions: "{{ trigger.to_state.state == 'Away' }}"
sequence:
- action: script.leave_home
Grouping with sequence
A sequence block bundles actions so they run one after another, and lets you collapse related sets in the UI for tidiness. Combined with parallel, it can run several ordered groups at once.
automation:
- triggers:
- trigger: state
entity_id: binary_sensor.garage_motion
to: "on"
actions:
- alias: "Switch on devices"
sequence:
- action: light.turn_on
target:
entity_id: light.workbench
- action: siren.turn_on
target:
entity_id: siren.garage_alarm
- alias: "Send alerts"
sequence:
- action: notify.person1
data:
message: "Garage motion picked up!"
- action: notify.person2
data:
message: "Note — garage sensor went off..."
Running things in parallel
By default actions run one at a time. A parallel block starts them all together, useful when order doesn't matter and they're independent.
automation:
- triggers:
- trigger: state
entity_id: binary_sensor.garage_motion
to: "on"
actions:
- parallel:
- action: notify.person1
data:
message: "Fired simultaneously!"
- action: notify.person2
data:
message: "Fired simultaneously!"
You can nest an ordered group inside a parallel block:
script:
parallel_demo:
sequence:
- parallel:
- sequence:
- wait_for_trigger:
- trigger: state
entity_id: binary_sensor.garage_motion
to: "on"
- action: notify.person1
data:
message: "Waited for motion first"
- action: notify.person2
data:
message: "Sent right away, no waiting!"
Caution: Parallel runs help in some cases, but reach for them only when needed — sequential actions usually do the job. Caveats: there's no order guarantee on completion; if one branch errors the others keep going; and variables in separate branches can clash, so name them uniquely.
Stopping the sequence
A stop block ends the script early and can return a response. Its text is logged and appears in traces.
- stop: "End the rest of the sequence here"
Return data with response_variable, naming a variable holding a key/value mapping:
- stop: "End the rest of the sequence here"
response_variable: "my_response_variable"
Mark it as a failure with error:
- stop: "Something went sideways!"
error: true
Carrying on after an error
Normally an action error halts the whole sequence and marks the run as errored. When an error is expected and harmless, set continue_on_error: true on that action (it defaults to false).
- alias: "If this errors..."
continue_on_error: true
action: notify.unreliable_gateway
data:
message: "I may fail..."
- alias: "This still runs!"
action: persistent_notification.create
data:
title: "Hi!"
message: "Doing fine..."
This won't hide misconfiguration or errors WoowTech doesn't handle.
Turning off an action
Disable a block without deleting it by adding enabled: false:
script:
morning_routine:
sequence:
# Disabled — won't run, no message sent
- enabled: false
alias: "Announce the blinds"
action: notify.notify
data:
message: "Raising the blinds!"
# Enabled — runs normally
- alias: "Raise the blinds"
action: cover.open_cover
target:
entity_id: cover.bedroom_blinds
You can also gate a block on a template or blueprint input:
blueprint:
input:
enable_step:
name: Enable
selector:
boolean:
actions:
- delay: 0:50
enabled: !input enable_step
Replying to a conversation
The set conversation response block returns a custom reply when a conversation engine (like a voice assistant) launches the automation. It can be templated.
- variables:
code: "789"
- set_conversation_response: "{{ 'Verifying ' ~ code }}"
The reply reaches the engine once the automation ends; the latest one wins if set more than once. Clear it with None:
set_conversation_response: ~
If no conversation engine started the run, the response is ignored.
Start writing here...