Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,7 @@ fun haPrimaryAction(domain: String): String? = when (domain) {
"light", "switch", "fan", "siren" -> "toggle"
"button", "input_button" -> "press"
"scene", "script" -> "turn_on"
"automation" -> "trigger"
else -> null
}

Expand Down Expand Up @@ -188,6 +189,7 @@ fun haFullActions(domain: String): List<HaAction> = when (domain) {
"button", "input_button" -> listOf(HaAction("press", "Press"))
"scene" -> listOf(HaAction("turn_on", "Activate"))
"script" -> listOf(HaAction("turn_on", "Run"))
"automation" -> listOf(HaAction("trigger", "Trigger"))
else -> emptyList()
}

Expand Down
13 changes: 13 additions & 0 deletions apps/desktop-flutter/lib/ui/ha_overlay/ha_actions.dart
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
// lock -> lock / unlock
// button, input_button -> press
// scene, script -> turn_on
// automation -> trigger
//
// Anything else (binary_sensor, sensor, an unknown domain from a newer HA)
// yields NO actions, so the card stays read-only exactly as it is today.
Expand Down Expand Up @@ -128,6 +129,13 @@ const List<HaControlAction> _kScriptActions = [
HaControlAction(action: 'turn_on', label: 'Run', icon: Icons.play_arrow),
];

/// automation — HA's service is `automation.trigger`, which fires the
/// automation's actions immediately (skipping its trigger conditions). "Trigger"
/// is what it reads as on the badge.
const List<HaControlAction> _kAutomationActions = [
HaControlAction(action: 'trigger', label: 'Trigger', icon: Icons.bolt),
];

/// The actions the server will accept for an entity in [domain]; empty for
/// every domain that has no control path (the card then renders read-only).
List<HaControlAction> haActionsForDomain(String domain) {
Expand All @@ -148,6 +156,8 @@ List<HaControlAction> haActionsForDomain(String domain) {
return _kSceneActions;
case 'script':
return _kScriptActions;
case 'automation':
return _kAutomationActions;
default:
return const [];
}
Expand Down Expand Up @@ -184,6 +194,7 @@ List<HaControlAction> haAllActionsForDomain(String domain) {
/// light / switch / fan / siren -> toggle
/// button / input_button -> press
/// scene / script -> turn_on (activate / run)
/// automation -> trigger
///
/// Unknown/read-only domains (binary_sensor, sensor, a newer HA domain) return
/// null: no guessed actuation, the badge stays read-only.
Expand All @@ -201,6 +212,8 @@ HaControlAction? haPrimaryAction(String domain) {
return _kSceneActions.first;
case 'script':
return _kScriptActions.first;
case 'automation':
return _kAutomationActions.first;
default:
return null;
}
Expand Down
3 changes: 3 additions & 0 deletions apps/ios/Crumb/Features/HomeAssistant/HomeAssistant.swift
Original file line number Diff line number Diff line change
Expand Up @@ -394,6 +394,8 @@ extension HA {
return [HAAction("turn_on", "Activate", "film")]
case "script":
return [HAAction("turn_on", "Run", "play.fill")]
case "automation":
return [HAAction("trigger", "Trigger", "bolt.fill")]
default:
return []
}
Expand Down Expand Up @@ -448,6 +450,7 @@ extension HA {
case "light", "switch", "fan", "siren": return "toggle"
case "button", "input_button": return "press"
case "scene", "script": return "turn_on"
case "automation": return "trigger"
default: return nil
}
}
Expand Down
9 changes: 5 additions & 4 deletions docs-site/docs/integrations/home-assistant.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,9 @@ What works today:
- **Live entity badges pinned on the video**, with an icon, shape, color, size,
and opacity you choose per entity.
- **Control from the badge.** Entities linked with the **Control** role, lights,
switches, fans, sirens, covers, locks, buttons, scenes, and scripts, can be
operated right from the badge: tap a simple device to fire its action, or open
switches, fans, sirens, covers, locks, buttons, scenes, scripts, and
automations, can be operated right from the badge: tap a simple device to fire
its action (an automation triggers, a scene activates, a script runs), or open
a small action card for a device like a cover or lock that has more than one
meaningful action.
- **A Home Assistant hub** in the console: connection settings, every linked
Expand Down Expand Up @@ -93,8 +94,8 @@ picker scoped to the domains that role accepts:
detail card can show, for example, `72°F` instead of a bare number.
- The **Control** picker searches every domain Crumb can actuate: `light`,
`switch`, `fan`, `siren`, `cover`, `lock`, `button`, `input_button`, `scene`,
and `script`, grouped by domain. If you are looking for a switch or a light,
this is the button; they are not in the sensor pickers.
`script`, and `automation`, grouped by domain. If you are looking for a switch
or a light, this is the button; they are not in the sensor pickers.

Every picker has a search box, and an entity already linked under that role
shows as "(linked)" instead of being hidden. Nothing is saved until you hit
Expand Down
9 changes: 6 additions & 3 deletions services/api/src/ha.rs
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,7 @@ const HA_ACTION_ALLOWLIST: &[(&str, &[HaActionSpec])] = &[
("input_button", &[spec("press")]),
("scene", &[spec("turn_on")]),
("script", &[spec("turn_on")]),
("automation", &[spec("trigger")]),
];

/// The kind of numeric value an action carries. Kept as an enum (not a bare
Expand Down Expand Up @@ -410,7 +411,8 @@ struct EntitiesQuery {
/// A single HA domain (e.g. `binary_sensor`, `sensor`, `light`, `cover`), or
/// one of the role aliases: `controls` (every actuatable domain from
/// `HA_ACTION_ALLOWLIST`: light, switch, fan, siren, cover, lock, button,
/// `input_button`, scene, script) or `sensors` (numeric `sensor`). Omitted ⇒
/// `input_button`, scene, script, automation) or `sensors` (numeric
/// `sensor`). Omitted ⇒
/// the union of all of the above (motion binary sensors + numeric sensors +
/// every controllable domain).
domain: Option<String>,
Expand Down Expand Up @@ -1013,8 +1015,8 @@ async fn get_entities(
let s = effective_settings(&state).await?;
let domains: Vec<&str> = match q.domain.as_deref() {
// Actuator role: every domain the action endpoint can drive (light,
// switch, fan, siren, cover, lock, button, input_button, scene, script),
// derived from the allowlist so the two can never diverge.
// switch, fan, siren, cover, lock, button, input_button, scene, script,
// automation), derived from the allowlist so the two can never diverge.
Some("controls") => control_domains(),
// Numeric-sensor role (temperature/humidity/... display links).
Some("sensors") => vec!["sensor"],
Expand Down Expand Up @@ -1934,6 +1936,7 @@ mod tests {
("input_button", &["press"]),
("scene", &["turn_on"]),
("script", &["turn_on"]),
("automation", &["trigger"]),
];
for (domain, actions) in allowed {
for action in *actions {
Expand Down
Loading