# homecore-automation YAML-based automation engine for HOMECORE with trigger evaluation, conditions, and MiniJinja template support. [![Crates.io](https://img.shields.io/crates/v/homecore-automation.svg)](https://crates.io/crates/homecore-automation) ![License](https://img.shields.io/badge/license-MIT-blue.svg) ![MSRV: 1.89+](https://img.shields.io/badge/MSRV-1.89%2B-purple.svg) [![Tests](https://img.shields.io/badge/tests-34%20passing-brightgreen.svg)](https://github.com/ruvnet/RuView) [![ADR-129](https://img.shields.io/badge/ADR-129-orange.svg)](../../docs/adr/ADR-129-homecore-automation-trigger-condition-action.md) Home Assistant-compatible automation engine for HOMECORE, parsing YAML trigger→condition→action rules and executing them against the HOMECORE event bus. ## What this crate does `homecore-automation` provides the runtime for HOMECORE automations — YAML files that define "if X happens and Y is true, do Z". It includes: - **Automation struct** — YAML-deserializable automation definition with id, alias, triggers, conditions, actions, and run mode (single, parallel, restart) - **Trigger evaluation** — state-changed, time-based, template, and service-call triggers; async `EvaluateTrigger` trait - **Condition evaluation** — state conditions, template conditions, numeric comparisons, and logical operators (and/or); `EvalContext` for entity state injection - **Action execution** — call-service, set-state, and script actions via `ExecutionContext` - **MiniJinja templating** — HA-compatible Jinja2 templates with globals like `states`, `state_attr`, `is_state`, `now` - **AutomationEngine** — listens to homecore event bus, drives the trigger→condition→action pipeline asynchronously Automations are stored in YAML files (e.g., `automations.yaml`) and loaded at startup. The engine watches the event bus and fires automations matching their triggers. ## Features - **YAML automation syntax** — familiar HA format: triggers, conditions, actions, mode - **State-changed triggers** — fires when `entity.light.kitchen` changes to `on` - **Time-based triggers** — `at: "15:30:00"` or `minutes: 5` (cron-like) - **Template triggers** — `value_template: "{{ states('light.kitchen') == 'on' }}"` - **Service-call triggers** — `service: light.turn_on` for chaining automations - **Condition evaluation** — `condition: state` with entity_id + state matching - **Template conditions** — `condition: template` with Jinja2 expressions - **Numeric comparisons** — `condition: numeric_state` with `above`, `below`, `between` - **Logical operators** — `condition: and` / `condition: or` for complex rules - **Service call actions** — `action: service` with `service: light.turn_on` + data - **State setting actions** — `action: set_state` to directly update entity state - **MiniJinja templating** — `{{ now() }}`, `{{ states('sensor.temp') }}`, `{{ is_state('light.kitchen', 'on') }}` - **Automation modes** — single (queue), parallel (all fire), restart (drop old runs) ## Capabilities | Capability | Type | Method | Notes | |------------|------|--------|-------| | Parse YAML automation | Loader | `serde_yaml::from_str::(yaml_str)` | Deserialize automation definition | | Evaluate trigger | Trigger | `Trigger::StateChanged {...}.evaluate(context)` | Check if trigger condition met | | Evaluate condition | Condition | `Condition::State {...}.evaluate(context)` | Check if condition passes | | Execute action | Action | `Action::Service {...}.execute(context)` | Call service or set state | | Render template | Template | `TemplateEnvironment::render(expr, context)` | Jinja2 with HA globals | | Run automation | Engine | `AutomationEngine::run_automation(automation, context)` | Execute full trigger→condition→action pipeline | | Subscribe to events | Engine | `AutomationEngine::listen(homecore.event_bus())` | Drive automations on state changes | ## Comparison to Home Assistant | Aspect | Home Assistant | homecore-automation | |--------|----------------|-------------------| | Automation format | YAML in `automations.yaml` | Identical YAML format | | Parser | Python YAML + voluptuous | serde_yaml + serde validation | | Trigger types | state_changed, time, template, service, mqtt, ... | state_changed, time, template, service (core 4) | | Condition types | state, numeric_state, template, and/or, ... | Identical (core types) | | Action types | call_service, set_state, script, wait_template, ... | call_service, set_state (core 2) | | Template engine | Python Jinja2 | MiniJinja (pure Rust, HA-compatible) | | Globals | states, state_attr, is_state, now, ... | Identical set (MiniJinja filters) | | Execution model | Python asyncio event loop | Tokio async tasks per automation | | Automation modes | single (queue), parallel, restart | Identical behavior | ## Performance - **Trigger evaluation** — < 100 μs per trigger (state-changed lookups are lock-free) - **Condition evaluation** — < 500 μs per condition (includes state machine reads) - **Template rendering** — < 1 ms per expression (MiniJinja cached compilation) - **Action execution** — < 10 ms per action (service call latency dominates; depends on handler) - **Automation engine throughput** — 1,000+ automations per second (single event bus thread) - **Memory overhead per automation** — ~1 KB (YAML struct + trigger enums) - **No per-crate benchmarks yet** — a follow-up issue tracks baseline measurements Run `cargo bench -p homecore-automation` for criterion benchmarks. ## Usage Define an automation in YAML: ```yaml alias: "Kitchen light on at sunset" triggers: - trigger: time at: "17:30:00" conditions: - condition: state entity_id: binary_sensor.is_dark state: "on" actions: - action: service service: light.turn_on target: entity_id: light.kitchen data: brightness: 200 mode: single ``` Load and run it (Rust): ```rust use homecore_automation::{Automation, AutomationEngine}; use homecore::HomeCore; #[tokio::main] async fn main() { let homecore = HomeCore::new(); let yaml = std::fs::read_to_string("automations.yaml").expect("read automation"); let automation: Automation = serde_yaml::from_str(&yaml).expect("parse automation"); let engine = AutomationEngine::new(homecore.clone()); engine.listen(homecore.event_bus()).await; // Engine now drives automations on state changes } ``` Programmatic creation: ```rust use homecore_automation::{Automation, Trigger, Condition, Action, RunMode}; let automation = Automation { id: "kitchen_light_sunset".to_string(), alias: Some("Kitchen light on at sunset".to_string()), triggers: vec![ Trigger::StateChanged { entity_id: "binary_sensor.is_dark".to_string(), to: Some("on".to_string()), ..Default::default() }, ], conditions: vec![], actions: vec![ Action::Service { service: "light.turn_on".to_string(), data: serde_json::json!({"entity_id": "light.kitchen", "brightness": 200}), }, ], mode: RunMode::Single, ..Default::default() }; println!("Automation: {}", automation.alias.unwrap_or_default()); ``` ## Relation to other HOMECORE crates ``` homecore-automation (automation engine) ├─ homecore (state machine + event bus; automations subscribe to state changes) ├─ homecore-api (exposes automation metadata via REST, P2) ├─ homecore-assist (intents can trigger automations via service calls, P2) ├─ homecore-server (loads automations.yaml at startup) └─ minijinja (template rendering) ``` ## References - [ADR-129: HOMECORE Automation Engine](../../docs/adr/ADR-129-homecore-automation-trigger-condition-action.md) - [ADR-126: HOMECORE Home Assistant Port (master)](../../docs/adr/ADR-126-homecore-home-assistant-port.md) - [Home Assistant Automation Integration](https://www.home-assistant.io/docs/automation/) - [MiniJinja Documentation](https://docs.rs/minijinja/latest/minijinja/) - [README — wifi-densepose](../../../README.md)