跳至內容

Script Editor

2026年6月2日
Script Editor
渥屋科技股份有限公司, 系統管理者
Creating Scripts with the Visual Editor

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...

網誌: Help center
分享這個貼文

閱讀下一篇
Scene Editor