169 lines
7.9 KiB
Markdown
169 lines
7.9 KiB
Markdown
# homecore-automation
|
|
|
|
YAML-based automation engine for HOMECORE with trigger evaluation, conditions, and MiniJinja template support.
|
|
|
|
[](https://crates.io/crates/homecore-automation)
|
|

|
|

|
|
[](https://github.com/ruvnet/RuView)
|
|
[](../../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::<Automation>(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)
|