ExMaude. IoT
(ExMaude v0.3.0)
View Source
IoT rule conflict detection using Maude formal verification.
This module provides an Elixir API for detecting conflicts in IoT automation rules using Maude's formal verification capabilities. It implements the four conflict types identified in the AutoIoT paper (arxiv.org/abs/2411.10665):
Conflict Types
State Conflict - Two rules target the same device property with incompatible values. Example: motion sensor turns light on while time-based rule turns it off.
Environment Conflict - Two rules produce opposing environmental effects. Example: one rule opens a window to cool, another closes it to reduce noise.
State Cascade - A rule's output triggers another rule, creating unexpected chains. Example: door open → light on → play sound → light off creates oscillation.
State-Environment Cascade - Combined state and environment effects cascade through multiple rules. Example: AC on → window closes → CO2 rises → window opens → conflicts with AC.
Usage
# Define rules
rules = [
%{
id: "motion-light",
thing_id: "light-1",
trigger: {:prop_eq, "motion", true},
actions: [{:set_prop, "light-1", "state", "on"}],
priority: 1
},
%{
id: "night-light",
thing_id: "light-1",
trigger: {:prop_gt, "time", 2300},
actions: [{:set_prop, "light-1", "state", "off"}],
priority: 1
}
]
# Detect conflicts (the bundled iot-rules.maude module is loaded
# automatically on first call)
{:ok, conflicts} = ExMaude.IoT.detect_conflicts(rules)
# => [%{type: :state_conflict, rule1: "motion-light", rule2: "night-light", ...}]Telemetry
This module emits the following telemetry events:
[:ex_maude, :iot, :detect_conflicts, :start]- Emitted when detection begins[:ex_maude, :iot, :detect_conflicts, :stop]- Emitted when detection completes
Measurements include :duration in native time units, :rule_count, and
:conflict_count. Metadata includes :result (:ok or :error) and
:template (:iot_rules).
See ExMaude.Telemetry for full event documentation and integration examples.
Summary
Types
A predicate over a world state: a device property holding a value, or an environment key holding a value.
Options for the state-space verification functions.
Functions
Detects cascade conflicts (both state and state-environment).
Detects all conflicts in a set of IoT rules.
Detects only environment conflicts in a set of rules.
Detects only state conflicts in a set of rules.
Validates a rule structure without sending it to Maude.
Validates a list of rules.
Bounded liveness check: looks for a deadlock that prevents rules from
reaching goal_state.
Bounded safety check: proves no execution of rules reaches a world matching
bad_state.
Types
@type conflict() :: %{ type: conflict_type(), rule1: String.t(), rule2: String.t(), reason: String.t() }
@type conflict_type() ::
:state_conflict | :env_conflict | :state_cascade | :state_env_cascade
@type rule() :: %{ :id => String.t(), :thing_id => thing_id(), :trigger => trigger(), :actions => [action()], optional(:priority) => non_neg_integer() }
@type state_pred() :: {:thing_state, thing_id(), String.t(), term()} | {:env_state, String.t(), term()}
A predicate over a world state: a device property holding a value, or an environment key holding a value.
@type thing_id() :: String.t()
@type trigger() :: {:prop_eq, String.t(), term()} | {:prop_gt, String.t(), number()} | {:prop_lt, String.t(), number()} | {:prop_gte, String.t(), number()} | {:prop_lte, String.t(), number()} | {:env_eq, String.t(), term()} | {:env_gt, String.t(), number()} | {:env_lt, String.t(), number()} | {:always} | {:and, trigger(), trigger()} | {:or, trigger(), trigger()} | {:not, trigger()}
@type world_opts() :: [ initial_state: [state_pred()], max_depth: pos_integer(), timeout: timeout() ]
Options for the state-space verification functions.
:initial_state- bindings present before any rule fires (default[]):max_depth- bound on search depth (default50); unbounded searches that hit the bound return{:ok, :unverified}rather than blocking:timeout- per-search timeout in ms (default30_000)
Functions
Detects cascade conflicts (both state and state-environment).
Cascade conflicts occur when one rule's output triggers another rule.
Detects all conflicts in a set of IoT rules.
Analyzes the given rules for all four conflict types using Maude formal verification. Returns a list of detected conflicts, or an empty list if no conflicts are found.
Examples
rules = [
%{id: "r1", thing_id: "light-1", trigger: {:prop_eq, "motion", true},
actions: [{:set_prop, "light-1", "state", "on"}], priority: 1},
%{id: "r2", thing_id: "light-1", trigger: {:prop_gt, "time", 2300},
actions: [{:set_prop, "light-1", "state", "off"}], priority: 1}
]
{:ok, conflicts} = ExMaude.IoT.detect_conflicts(rules)
[%{type: :state_conflict, rule1: "r1", rule2: "r2", reason: _}] = conflictsOptions
:timeout- Maximum time in milliseconds (default: 10000):conflict_types- List of conflict types to check (default: all)
Detects only environment conflicts in a set of rules.
Environment conflicts occur when two rules produce opposing environmental effects.
Detects only state conflicts in a set of rules.
State conflicts occur when two rules target the same device property with incompatible values.
Examples
{:ok, conflicts} = ExMaude.IoT.detect_state_conflicts(rules)
Validates a rule structure without sending it to Maude.
Returns :ok if the rule is valid, or {:error, errors} with a list
of validation error messages.
Examples
:ok = ExMaude.IoT.validate_rule(%{
id: "my-rule",
thing_id: "device-1",
trigger: {:prop_eq, "state", true},
actions: [{:set_prop, "device-1", "power", "on"}]
})
{:error, ["missing required field: id"]} = ExMaude.IoT.validate_rule(%{})
Validates a list of rules.
Returns :ok if all rules are valid, or {:error, errors} with a map
of rule IDs to their validation errors.
@spec verify_liveness([rule()], state_pred(), world_opts()) :: {:ok, :live} | {:ok, :unverified} | {:error, :deadlock_possible | ExMaude.Error.t() | term()}
Bounded liveness check: looks for a deadlock that prevents rules from
reaching goal_state.
Searches for a terminal world (=>!, no rule can fire) in which goal_state
does not hold. Because the search only inspects terminal states, this detects
deadlocks (the system stops short of the goal); it does not detect livelocks
(infinite progress that never reaches the goal), which need full LTL model
checking.
Returns:
{:ok, :live}- every reachable terminal world satisfiesgoal_state{:error, :deadlock_possible}- a reachable terminal world misses the goal{:ok, :unverified}- Maude unavailable or timed out — undecided{:error, %ExMaude.Error{}}- the verification itself failed (invalid rule encoding, missing module, ...)
Examples
ExMaude.IoT.verify_liveness(rules, {:thing_state, "door", "state", "notified"})
#=> {:ok, :live}
@spec verify_safety([rule()], state_pred() | [state_pred()], world_opts()) :: {:ok, :safe} | {:ok, :unverified} | {:error, {:counterexample, [map()]} | ExMaude.Error.t() | term()}
Bounded safety check: proves no execution of rules reaches a world matching
bad_state.
Explores the rule-firing transition system (the IOT-EXEC Maude module) from
the initial state with =>* reachability search, looking for a reachable
world whose state contains bad_state.
Returns:
{:ok, :safe}- no bad world reachable withinmax_depth{:error, {:counterexample, solutions}}- a reachable bad world (thesolutionscarry the matching state/substitution){:ok, :unverified}- Maude unavailable or timed out — unable to decide (absence of proof, not evidence of safety){:error, %ExMaude.Error{}}- the verification itself failed (e.g. a rule that doesn't encode to valid Maude, or a missing module); surfaced as an error rather than:unverifiedbecause it indicates a bug in the input, not an inconclusive search
bad_state is a state_pred or a list of them (a list means "all present in
the same reachable world").
Examples
# Rule drives a door into an error state when motion is detected.
rules = [%{id: "r1", thing_id: "door", trigger: {:prop_eq, "motion", true},
actions: [{:set_prop, "door", "state", "error"}], priority: 1}]
ExMaude.IoT.verify_safety(rules, {:thing_state, "door", "state", "error"},
initial_state: [{:thing_state, "door", "motion", true}])
#=> {:error, {:counterexample, [_ | _]}}