clasp-rules 4.1.0

Server-side rules engine for CLASP protocol
Documentation

clasp-rules

Server-side reactive automation engine for CLASP routers.

Features

  • Reactive Triggers - Fire on state change, threshold crossing, events, or intervals
  • Conditional Execution - Guard rules with comparisons against live state
  • Transform Pipeline - Scale, clamp, invert, or threshold values on the fly
  • Loop Prevention - Automatic origin tagging prevents rule feedback loops
  • Cooldowns - Per-rule minimum time between firings
  • JSON Rules - Define rules in JSON for runtime loading

Installation

[dependencies]
clasp-rules = "3.5"

Usage

Motion Sensor to Lights (OnChange)

use clasp_rules::{Rule, Trigger, RuleAction, RulesEngine};
use clasp_core::Value;
use std::time::Duration;

let rule = Rule {
    id: "motion-lights".to_string(),
    name: "Motion activates lights".to_string(),
    enabled: true,
    trigger: Trigger::OnChange {
        pattern: "/sensors/*/motion".to_string(),
    },
    conditions: vec![],
    actions: vec![RuleAction::Set {
        address: "/lights/hallway/brightness".to_string(),
        value: Value::Float(1.0),
    }],
    cooldown: Some(Duration::from_secs(5)),
};

let mut engine = RulesEngine::new();
engine.add_rule(rule)?;

Threshold Alert

use clasp_rules::{Trigger, Condition, CompareOp, RuleAction};
use clasp_core::{Value, SignalType};

let rule = Rule {
    id: "temp-alert".to_string(),
    name: "High temperature alert".to_string(),
    enabled: true,
    trigger: Trigger::OnThreshold {
        address: "/sensors/room1/temperature".to_string(),
        above: Some(30.0),
        below: None,
    },
    conditions: vec![Condition {
        address: "/system/alerts/enabled".to_string(),
        op: CompareOp::Eq,
        value: Value::Bool(true),
    }],
    actions: vec![RuleAction::Publish {
        address: "/alerts/temperature".to_string(),
        signal: SignalType::Event,
        value: Some(Value::String("High temperature in room 1".into())),
    }],
    cooldown: Some(Duration::from_secs(60)),
};

SetFromTrigger with Scale Transform

use clasp_rules::{RuleAction, Transform};

// Map a 0-1 slider to 0-255 DMX range
let rule = Rule {
    id: "slider-to-dmx".to_string(),
    name: "Scale slider to DMX".to_string(),
    enabled: true,
    trigger: Trigger::OnChange {
        pattern: "/controls/slider1".to_string(),
    },
    conditions: vec![],
    actions: vec![RuleAction::SetFromTrigger {
        address: "/dmx/1/channel/1".to_string(),
        transform: Transform::Scale {
            scale: 255.0,
            offset: 0.0,
        },
    }],
    cooldown: None,
};

Periodic Heartbeat (OnInterval)

let rule = Rule {
    id: "heartbeat".to_string(),
    name: "Periodic heartbeat".to_string(),
    enabled: true,
    trigger: Trigger::OnInterval { seconds: 10 },
    conditions: vec![],
    actions: vec![RuleAction::Publish {
        address: "/system/heartbeat".to_string(),
        signal: SignalType::Event,
        value: None,
    }],
    cooldown: None,
};

Evaluate Rules

let actions = engine.evaluate(
    "/sensors/room1/motion",    // address that changed
    &Value::Bool(true),         // new value
    SignalType::Param,          // signal type
    None,                       // origin (None = from client)
    |addr| {                    // state lookup function
        // Return current value for an address
        Some(Value::Bool(true))
    },
);

for action in actions {
    println!("Rule {} fires: {:?}", action.rule_id, action.action);
    // Execute action.action against the router
}

// For interval rules
let intervals = engine.interval_rules(); // Vec<(rule_id, seconds)>
let actions = engine.evaluate_interval("heartbeat", |addr| None);

JSON Rule Definition

Rules are fully serializable for runtime configuration:

{
  "id": "motion-lights",
  "name": "Motion activates lights",
  "enabled": true,
  "trigger": {
    "OnChange": {
      "pattern": "/sensors/*/motion"
    }
  },
  "conditions": [],
  "actions": [
    {
      "Set": {
        "address": "/lights/hallway/brightness",
        "value": 1.0
      }
    }
  ],
  "cooldown": {
    "secs": 5,
    "nanos": 0
  }
}

Configuration Reference

Rule

Field Type Default Description
id String required Unique rule identifier
name String required Human-readable name
enabled bool required Whether the rule is active
trigger Trigger required What triggers the rule
conditions Vec<Condition> [] Additional conditions (all must be true)
actions Vec<RuleAction> required Actions to execute when the rule fires
cooldown Option<Duration> None Minimum time between firings

Trigger Variants

Variant Fields Description
OnChange pattern: String Fires when a param matching the pattern changes
OnThreshold address: String, above: Option<f64>, below: Option<f64> Fires when a value crosses a threshold
OnEvent pattern: String Fires when an event matching the pattern is published
OnInterval seconds: u64 Fires periodically

Condition

Field Type Description
address String CLASP address to check
op CompareOp Eq, Ne, Gt, Gte, Lt, Lte
value Value Value to compare against

RuleAction Variants

Variant Fields Description
Set address, value Set a parameter to a fixed value
Publish address, signal, value? Publish an event
SetFromTrigger address, transform Copy trigger's value with optional transform
Delay milliseconds Delay before the next action

Transform Variants

Variant Fields Description
Identity -- Pass through unchanged
Scale scale, offset output = input * scale + offset
Clamp min, max Clamp to range [min, max]
Threshold value true if input > value, else false
Invert min, max output = max - (input - min)

Loop Prevention

Actions produced by rules carry an origin field ("rule:{id}" or "interval:{id}"). The engine skips evaluation when the origin starts with "rule:", preventing infinite feedback loops.

License

Licensed under either of Apache License, Version 2.0 or MIT license at your option.


Maintained by LumenCanvas