Skip to content

Rules Engine

window.lcards.core.rulesManager — Evaluates conditions and hot-patches card styles at runtime.


Overview

RulesEngine extends BaseService. It maintains a compiled index of rules, tracks which entities each rule depends on, and re-evaluates dirty rules on every HASS state push. Matched rules produce patches — plain style objects that are merged onto target overlays without those overlays needing to know the rule exists.


Key Classes

ClassFileRole
RulesEnginecore/rules/RulesEngine.jsEvaluation loop, dependency index, patch distribution
compileConditionscore/rules/compileConditions.jsCompiles rule when DSL to optimised predicate functions
RuleTraceBuffercore/rules/RuleTraceBuffer.jsRing buffer of evaluation events for the Debug Panel

Compilation

When RulesEngine is constructed it immediately compiles every rule's when block into an optimised predicate tree via compileRule() in compileConditions.js. The result is stored in this.compiledRules (a Map<ruleId, { tree, deps }>):

  • tree — a recursive node structure (all, any, not, leaf comparisons, javascript, jinja2) that evalCompiled() walks during evaluation.
  • deps — sets of entity IDs, performance-metric keys, and feature flags that the rule references, used to build the dependency index.

Compilation happens once per RulesEngine instance (at construction). If rules are replaced, a new RulesEngine instance is created rather than mutating the existing one. Compilation issues (e.g. a plain string condition without [[[ ]]] or {{ }}) are logged as warnings and the affected rule falls back to an always-true node.

javascript
// compileConditions.js shape
{ tree: {
    type: 'all',           // 'all' | 'any' | 'not' | 'leaf' | 'javascript' | 'jinja2' | 'always'
    nodes: [ ... ]
  },
  deps: {
    entities: Set<string>,
    perf:     Set<string>,
    flags:    Set<string>
  }
}

Evaluation Flow

HASS state push
    → mark affected rules dirty  (dependency index O(1) lookup)
    → evaluate dirty rules only  (evalCompiled() walks compiled tree)
    → for each matched rule: compute patches
    → distribute patches to registered overlay listeners
    → overlays call requestUpdate()

Rule Schema

yaml
rules:
  - id: high_temp
    priority: 10             # higher number = evaluated first
    stop: false
    enabled: true
    when:
      entity: sensor.cpu_temp
      above: 75
    apply:
      overlays:
        tag:temp_widget:     # tag selector — all overlays tagged 'temp_widget'
          style:
            color: var(--lcars-red)
      animations:
        - tag: temp_widget
          preset: pulse
          loop: true

  - id: motion_active
    when:
      entity: binary_sensor.front_door
      state: "on"
    apply:
      overlays:
        front-door-button:   # direct overlay ID
          style:
            color: var(--lcars-orange)

Condition Operators

OperatorValue typeMeaning
statestringExact state match (alias for equals)
equalsstring/numberExact equality
not_equalsstring/numberInequality
abovenumbernumeric_state > value (strictly greater)
belownumbernumeric_state < value (strictly less)
inarrayState is one of the listed values
not_inarrayState is not in the listed values
regexstringState matches regular expression
attributestringAttribute name — pair with comparison operator
allcondition[]AND — all nested conditions must match
anycondition[]OR — at least one nested condition must match
notconditionNegate a nested condition
conditionstringJS [[[code]]] or Jinja2 {{template}} — truthy return
jinja2stringExplicit Jinja2 template
javascriptstringExplicit JavaScript expression
time_between"HH:MM-HH:MM"True when current time is in range
weekday_instring[]True when today is one of monsun
sun_elevation{ above?, below? }True when sun elevation matches
perf_metric{ key, above?, below? }Internal performance metric comparison
random_chancenumber 0–1True with given probability each evaluation

Targeting

The keys of apply.overlays are selectors. Each maps to a patch fragment:

Selector formMatches
overlay-idExact overlay ID (direct match)
tag:tagnameAll overlays with tag tagname
type:typenameAll overlays of type typename
pattern:regexAll overlays whose ID matches the regex
allEvery registered overlay
excludeArray of IDs to exclude from bulk selectors

Multiple selectors in one rule are merged; later selectors' style keys override earlier ones for the same overlay.


Card Integration

javascript
// Register overlay (in _handleFirstUpdate)
this._registerOverlayForRules({ id: `btn-${this._cardGuid}`, type: 'button' });

// Receive patches
_onRulePatchesChanged(patches) {
  this._resolveStyle();   // merge config.style + patches, then requestUpdate()
}

Priority & Stop Processing

Higher priority number wins when multiple rules target the same overlay property (default 0). Rules are evaluated in descending priority order.

Set stop: true on a rule to prevent lower-priority rules from running at all once this rule matches.


Public API

Property / MethodReturnsDescription
rulesRule[]All registered rules in evaluation order
rulesByIdMap<id, Rule>Rules keyed by ID for fast lookup
getAllRules()Rule[]Array of all registered rule objects
getTrace()ObjectDetailed evaluation trace with per-overlay match history
getRecentMatches(windowMs)Match[]All rule matches within the last N milliseconds
getRuleTrace(ruleId, limit?)Object[]Per-rule match history with optional result count limit

Console Access

javascript
window.lcards.debug.singleton('rulesManager')
// → { type: 'RulesEngine', rulesCount: 3, dirtyRules: 0, evalCounts: {...}, trace: {...} }
javascript
const rm = window.lcards.core.rulesManager

rm.rules                            // full rules array
rm.rulesById                        // Map<id, rule>
rm.getTrace()                       // detailed evaluation trace
rm.getRecentMatches(30000)          // matches in the last 30s
rm.getRuleTrace('my-rule-id', 20)   // history for one rule
rm.getAllRules()                     // array of all registered rules

See Also