Skip to main content

Actions

Actions define what happens when a rule fires. Actions run in sequence by default. Use Parallel to run a group concurrently.

Every action accepts an optional enabled field (default true). Set enabled = false to disable a specific action without removing it — useful for temporary debugging.

Action reference

SetDeviceState

Commands a device by publishing to homecore/devices/{device_id}/cmd. The plugin receives the command and applies it.

For authored rules, prefer device = "canonical.name" instead of device_id = "plugin_specific_id".

[[actions]]
type = "set_device_state"
device = "living_room.floor_lamp"
state = { on = true, brightness = 200 }

# Turn off
[[actions]]
type = "set_device_state"
device = "kitchen.coffee_plug"
state = { on = false }

# Multiple attributes
[[actions]]
type = "set_device_state"
device = "hallway.thermostat"
state = { mode = "heat", target_temp = 68 }

device_id still works and remains supported for backward compatibility. device is the preferred field because it can take:

  • a canonical device name such as living_room.floor_lamp
  • a unique display name such as Kitchen Speaker
  • a raw device ID if you need it

If a display name matches more than one device, HomeCore marks the rule invalid instead of guessing.

SetDeviceState is also the standard way to send command-style payloads to plugins. This is common for scenes, media players, and any device where the payload represents an action rather than a simple state assignment.

Media player examples

# Start playback
[[actions]]
type = "set_device_state"
device = "living_room.sonos"
state = { action = "play" }

# Pause playback
[[actions]]
type = "set_device_state"
device = "living_room.sonos"
state = { action = "pause" }

# Set volume
[[actions]]
type = "set_device_state"
device = "living_room.sonos"
state = { action = "set_volume", volume = 30 }

# Play a Sonos favorite by name
[[actions]]
type = "set_device_state"
device = "living_room.sonos"
state = { action = "play_favorite", favorite = "Dinner Jazz" }

# Use the generic media command shape
[[actions]]
type = "set_device_state"
device = "living_room.sonos"
state = { action = "play_media", media_type = "playlist", name = "Dinner" }

Why this matters

The rule references the HomeCore canonical device name, not the plugin's private HTTP API, not a speaker IP address, and not a raw Sonos URI. The plugin resolves the named favorite or playlist at execution time.

That gives you:

  • stable rule files when a speaker IP changes
  • one consistent automation pattern across plugins
  • room for plugin-specific capabilities without leaking transport details into rules

SetDeviceStatePerMode

Applies a different state depending on which mode is active. The first matching mode wins. Falls back to default_state if no mode matches.

[[actions]]
type = "set_device_state_per_mode"
device_id = "light_desk"

[[actions.modes]]
mode_name = "mode_night"
state = { on = true, brightness = 30, color_temp = 2700 }

[[actions.modes]]
mode_name = "mode_away"
state = { on = false }

[actions.default_state]
on = true
brightness = 180
color_temp = 4000

PublishMqtt

Publishes a raw MQTT message. Useful for non-device integrations or custom protocols.

[[actions]]
type = "publish_mqtt"
topic = "homecore/events/custom_alert"
payload = '{"message":"motion detected"}'
retain = false

CallService

Makes an outbound HTTP request.

[[actions]]
type = "call_service"
url = "http://sonos.local:5005/Kitchen/say/Dinner+is+ready"
method = "GET"
timeout_ms = 5000

# POST with JSON body
[[actions]]
type = "call_service"
url = "http://external-api.local/v1/lights"
method = "POST"
body = { id = "kitchen", brightness = 200 }

# With retries and response capture
[[actions]]
type = "call_service"
url = "http://api.openweathermap.org/data/2.5/weather?q=Washington,DC&appid=KEY"
method = "GET"
retries = 3 # retry on network errors and 5xx (not 4xx)
response_event = "weather_update" # fires CustomEvent with response body

Use CallService when you genuinely need an external HTTP request. Do not use it for normal device control when the target is already represented as a HomeCore device. For example, a Sonos speaker registered as sonos_living_room should usually be controlled with SetDeviceState, not by calling the plugin's HTTP endpoint directly.

Retry backoff: 500 ms → 1000 ms → 2000 ms → 4000 ms (on network errors and 5xx only).


FireEvent

Publishes a custom event to the internal event bus and MQTT. Any rule with Trigger::CustomEvent matching the event_type fires immediately (same process, no broker round-trip).

[[actions]]
type = "fire_event"
event_type = "morning_routine_started"
payload = { source = "bedroom_motion" }

The event appears in the WebSocket stream and event log.


Notify

Sends a notification via a configured channel.

[[actions]]
type = "notify"
channel = "telegram"
message = "Front door opened"
title = "Security Alert" # optional; defaults to "HomeCore Alert"

# Log to server output only (no external service)
[[actions]]
type = "notify"
channel = "log"
message = "Rule fired: {{rule.name}}"

# Send to ALL configured channels
[[actions]]
type = "notify"
channel = "all"
message = "Critical alert!"

A notification failure (channel misconfigured, network error) logs a warning but does NOT abort the rule action sequence.


Delay

Non-blocking pause between actions.

[[actions]]
type = "delay"
duration_secs = 5

# Cancellable delay (can be cancelled by cancel_delays action)
[[actions]]
type = "delay"
duration_secs = 300
cancelable = true
cancel_key = "motion_off_delay"

Parallel

Runs a group of actions concurrently. Waits for all to complete before continuing.

[[actions]]
type = "parallel"

[[actions.actions]]
type = "notify"
channel = "telegram"
message = "Alert!"

[[actions.actions]]
type = "notify"
channel = "pushover"
message = "Alert!"

[[actions.actions]]
type = "set_device_state"
device_id = "light_alarm"
state = { on = true, color = "red" }

RepeatUntil

Loops until a Rhai condition returns true. Checks condition after each iteration (do-while semantics).

[[actions]]
type = "repeat_until"
condition = 'device_state("light.office")["on"] == false'
max_iterations = 10
interval_ms = 2000

[[actions.actions]]
type = "notify"
channel = "log"
message = "Still on — checking again"

RepeatWhile

Loops while a Rhai condition is true. Checks condition before each iteration (while semantics).

[[actions]]
type = "repeat_while"
condition = 'device_state("light.office")["on"] == true'
max_iterations = 20
interval_ms = 5000

[[actions.actions]]
type = "set_device_state"
device_id = "light.office"
state = { brightness = 50 }

RepeatCount

Loops a fixed number of times.

[[actions]]
type = "repeat_count"
count = 3
delay_ms = 500

[[actions.actions]]
type = "notify"
channel = "pushover"
message = "Alert! ({{iteration}})"

Conditional

Branches on a Rhai expression.

[[actions]]
type = "conditional"
condition = 'device_state("mode_night")["on"] == true'

[[actions.then_actions]]
type = "set_device_state"
device_id = "light_porch"
state = { on = true, brightness = 20 }

[[actions.else_actions]]
type = "set_device_state"
device_id = "light_porch"
state = { on = true, brightness = 180 }

Supports full else-if chains with additional [[actions.elseif_branches]].


FadeDevice

Gradually interpolates numeric attributes (brightness, color_temp, etc.) to a target value over a duration.

[[actions]]
type = "fade_device"
device_id = "light_living_room"
duration_secs = 30
steps = 10 # optional; defaults to duration_secs (1 step/sec)

[actions.target]
brightness = 0
color_temp = 2700

Non-numeric attributes pass through unchanged.


CaptureDeviceState / RestoreDeviceState

Snapshots the current state of devices under a named key, and restores it later.

# Capture before a scene change
[[actions]]
type = "capture_device_state"
key = "pre_movie_scene"
device_ids = ["light_living_room", "light_kitchen", "light_hallway"]

# ... movie mode actions ...

# Restore when done
[[actions]]
type = "restore_device_state"
key = "pre_movie_scene"

Captured state persists across rule firings (in-memory, cleared on restart).


PingHost

ICMP ping a host. Runs then_actions on success, else_actions on failure.

[[actions]]
type = "ping_host"
host = "192.168.1.1"
count = 3
timeout_ms = 2000

[[actions.then_actions]]
type = "notify"
channel = "log"
message = "Router is reachable"

[[actions.else_actions]]
type = "notify"
channel = "telegram"
message = "Router is UNREACHABLE!"

# Optionally fire a custom event with result
response_event = "router_ping_result"
# → {host, reachable, rtt_ms}

SetVariable

Set a rule-local variable. Variables persist across firings (in-memory; cleared on restart). See Advanced: Rule-local variables for full usage.

[[actions]]
type = "set_variable"
name = "open_count"
op = "Add" # Set | Add | Subtract | Multiply | Divide | Toggle | Append | Clear
value = 1.0

SetHubVariable

Write a cross-rule hub variable. Fires a hub_variable_changed event.

[[actions]]
type = "set_hub_variable"
name = "door_open_count"
op = "Add" # Set | Add | Subtract | Multiply | Divide | Toggle | Append | Clear
value = 1

# Set a string
[[actions]]
type = "set_hub_variable"
name = "last_motion_room"
op = "Set"
value = "living_room"

# Toggle a boolean
[[actions]]
type = "set_hub_variable"
name = "alarm_armed"
op = "Toggle"

Read in conditions via HubVariableIs or in Rhai: hub_var("door_open_count").


SetPrivateBoolean

Set a boolean flag scoped to this rule only.

[[actions]]
type = "set_private_boolean"
name = "already_notified"
value = true

Read in conditions via PrivateBooleanIs.


StopRuleChain

Stops HomeCore from evaluating any lower-priority rules for the current event. Rules with the same or lower priority are skipped.

[[actions]]
type = "stop_rule_chain"

Typically placed on a high-priority rule that should be exclusive. See Advanced: StopRuleChain for full usage.


RunRuleActions

Invoke another rule's action sequence inline (without evaluating its trigger/conditions).

[[actions]]
type = "run_rule_actions"
rule_id = "550e8400-e29b-41d4-a716-446655440000"

Maximum recursion depth: 10. Useful for shared action sequences across multiple rules.


WaitForEvent

Suspend execution until a matching event arrives on the bus.

[[actions]]
type = "wait_for_event"
event_type = "device_state_changed"
device_id = "door_sensor_front" # optional filter
attribute = "open" # optional filter
value = false # optional filter
timeout_ms = 30000

WaitForExpression

Suspend execution until a Rhai expression returns true.

[[actions]]
type = "wait_for_expression"
expression = 'device_state("door_sensor_front")["open"] == false'
poll_ms = 1000
timeout_ms = 60000

LogMessage

Emit a log line at a specified level.

[[actions]]
type = "log_message"
level = "info" # trace | debug | info | warn | error
message = "Motion detected, turning on lights"

Comment

A no-op for documentation. The executor skips it silently.

[[actions]]
type = "comment"
text = "--- Begin motion-triggered sequence ---"

PauseRule / ResumeRule

Pause or resume a rule at runtime. Paused rules skip evaluation without being disabled.

# Pause another rule
[[actions]]
type = "pause_rule"
rule_id = "550e8400-e29b-41d4-a716-446655440000"

# Pause this rule itself (omit rule_id)
[[actions]]
type = "pause_rule"

# Resume
[[actions]]
type = "resume_rule"
rule_id = "550e8400-e29b-41d4-a716-446655440000"

Pause state is in-memory and clears on restart.


ExitRule

Stop executing this rule's remaining actions immediately. Does not affect other rules.

[[actions]]
type = "exit_rule"

CancelDelays

Cancel a specific named cancellable delay.

[[actions]]
type = "cancel_delays"
cancel_key = "motion_off_delay"

CancelRuleTimers

Cancel all cancellable delays for a rule.

[[actions]]
type = "cancel_rule_timers"
rule_id = "550e8400-e29b-41d4-a716-446655440000" # omit for current rule

DelayPerMode

Delay for a duration that depends on the active mode.

[[actions]]
type = "delay_per_mode"
default_secs = 0

[[actions.modes]]
mode_name = "mode_night"
duration_secs = 300 # 5 minutes at night

[[actions.modes]]
mode_name = "mode_away"
duration_secs = 0 # no delay when away (skip the delayed action)

SetMode

Set a named mode on or off.

[[actions]]
type = "set_mode"
name = "mode_away"
value = true

# Turn off a mode
[[actions]]
type = "set_mode"
name = "mode_vacation"
value = false

RunScript

Execute a Rhai script as an action. The script runs synchronously inside spawn_blocking and can collect side effects (device state changes, MQTT publishes, notifications) that are applied asynchronously after the script returns.

[[actions]]
type = "run_script"
script = '''
let temp = device_state("thermostat_main")["temperature"];
if temp > 80 {
set_device("fan_living_room", #{"on": true});
}
'''

ActivateScenePerMode

Activate a different scene depending on which mode is active.

[[actions]]
type = "activate_scene_per_mode"
default_scene_id = "scene_daytime_living_room"

[[actions.modes]]
mode_name = "mode_night"
scene_id = "scene_nighttime_living_room"

[[actions.modes]]
mode_name = "mode_away"
scene_id = "scene_away_living_room"

Action trace in fire history

Every action execution is recorded in the rule's fire history:

curl -s http://localhost:8080/api/v1/automations/RULE_ID/history \
-H "Authorization: Bearer $TOKEN" | jq '.[0].actions'

Each entry includes action_type, description, outcome.status (ok/error/skipped), and duration_ms.