Plan Conditions

Triggers, events, and conditional logic

Plan Conditions

Reference for triggers, events, and conditional mechanisms that control Plan behavior and activation.

Pro Tip

Use debouncing and hysteresis for sensor triggers to avoid oscillation. A robot that rapidly switches between "obstacle detected" and "clear" states is harder to work with than one that uses a deadband - for example, trigger at 25cm but don't clear until 35cm.


Condition types

Condition Types


Sensor triggers

Threshold triggers

Activate when sensor crosses threshold:

// Activate when distance drops below 30cm
trigger on sensors.distance.front < 30 {
    activate("ObstacleStop")
}

// Deactivate when safe
trigger on sensors.distance.front > 50 {
    deactivate("ObstacleStop")
}

Range triggers

Activate when sensor is within range:

trigger on sensors.distance.front in 20..40 {
    slow_down()
}

Multi-sensor triggers

Combine multiple sensor conditions:

trigger on (sensors.distance.front < 30 &&
            sensors.distance.left < 30) {
    // Trapped in corner
    backup_and_turn()
}

Sensor change triggers

Activate on value change:

// Trigger on any change
trigger on change(sensors.line.center) {
    log("Line detection changed")
}

// Trigger when value increases
trigger on rising(sensors.light) {
    log("Light increasing")
}

// Trigger when value decreases
trigger on falling(battery.level) {
    if battery.level < 20 {
        activate("ReturnToBase")
    }
}

Time triggers

Delay trigger

Execute after delay:

trigger after 5000 {
    // Runs 5 seconds after Plan starts
    log("Warmup complete")
}

Interval trigger

Execute repeatedly:

trigger every 1000 {
    // Runs every second
    log("Status check")
    check_sensors()
}

Time-of-day trigger

Execute at specific times:

trigger at time.hour == 22 {
    // Run at 10 PM
    activate("NightMode")
}

trigger at time.hour == 6 {
    // Run at 6 AM
    deactivate("NightMode")
    activate("DayMode")
}

Schedule trigger

Cron-style scheduling:

// Every hour
trigger schedule "0 * * * *" {
    upload_status()
}

// Every day at midnight
trigger schedule "0 0 * * *" {
    reset_daily_stats()
}

// Weekdays at 9 AM
trigger schedule "0 9 * * 1-5" {
    start_work_routine()
}

State triggers

Plan state triggers

React to Plan state changes:

trigger on plan_activated("EmergencyStop") {
    // Stop all other activities
    deactivate_group("normal_ops")
}

trigger on plan_deactivated("EmergencyStop") {
    // Resume normal operations
    activate_group("normal_ops")
}

Variable state triggers

React to variable changes:

var mode = "idle"

trigger on mode == "active" {
    log("Entering active mode")
    start_motors()
}

trigger on mode == "idle" {
    log("Entering idle mode")
    stop_motors()
}

System state triggers

React to system conditions:

trigger on battery.low {
    activate("LowBatteryMode")
}

trigger on battery.charging {
    log("Now charging")
}

trigger on connection_lost("RemoteControl") {
    activate("AutonomousMode")
}

trigger on connection_restored("RemoteControl") {
    deactivate("AutonomousMode")
}

Location triggers

Position triggers

React to location:

trigger on position.x > 100 {
    log("Crossed X=100 boundary")
}

trigger on distance_to(home_position) < 10 {
    log("Arrived home")
}

Zone triggers

React to entering/leaving zones:

var danger_zone = {x: 50, y: 50, radius: 20}

trigger on entering_zone(danger_zone) {
    log("Warning: Entering danger zone")
    slow_down()
}

trigger on leaving_zone(danger_zone) {
    log("Left danger zone")
    resume_speed()
}

Geofence triggers

var boundary = [
    {x: 0, y: 0},
    {x: 100, y: 0},
    {x: 100, y: 100},
    {x: 0, y: 100}
]

trigger on outside_boundary(boundary) {
    log("Outside operating area!")
    navigate_inside()
}

Event handlers

Message events

Handle incoming messages:

on_message("command") { data ->
    log("Received command: " + data.action)
    execute_command(data)
}

on_message("emergency") { _ ->
    emergency_stop()
}

Filtered message events

Handle specific message content:

on_message("command") where data.action == "stop" {
    stop()
}

on_message("command") where data.action == "go" {
    drive(50, 50)
}

Broadcast events

Handle broadcast messages:

on_broadcast("status_request") { sender ->
    send_to(sender, "status_response", get_status())
}

Hardware events

Sensor events

// When specific sensor triggers
on_sensor("bump.front") { triggered ->
    if triggered {
        backup()
    }
}

// When sensor error occurs
on_sensor_error("distance.front") { error ->
    log_error("Front sensor error: " + error.message)
    use_backup_sensor()
}

Hardware connection events

on_hardware_connected() {
    log("Hardware connected")
    calibrate_sensors()
}

on_hardware_disconnected() {
    log_error("Hardware lost!")
    activate("SafeMode")
}

Button events

on_button_press("panic") {
    emergency_stop()
}

on_button_release("hold") {
    resume()
}

Conditional expressions

Comparison operators

Operator Description Example
== Equal state == "active"
!= Not equal state != "error"
< Less than speed < 50
> Greater than speed > 50
<= Less or equal battery <= 20
>= Greater or equal battery >= 80

Logical operators

Operator Description Example
&& AND a > 0 && b > 0
|| OR error || warning
! NOT !active

Range checks

// Value in range
if value in 10..50 {
    // value is between 10 and 50
}

// Value not in range
if value not in 10..50 {
    // value is outside 10-50
}

Type checks

if typeof(value) == "number" {
    process_number(value)
}

if typeof(value) == "string" {
    process_string(value)
}

Null checks

// Check for null
if value == null {
    value = default_value
}

// Null coalescing
var safe_value = value ?? default_value

Pattern matching

Match expression

var result = match state {
    "idle" -> 0
    "moving" -> 1
    "paused" -> 2
    _ -> -1  // Default case
}

Match with guards

match sensor_reading {
    x where x < 0 -> log("Invalid reading")
    x where x < 30 -> activate("TooClose")
    x where x < 60 -> log("Caution range")
    _ -> log("Safe distance")
}

Match on type

match incoming_data {
    {type: "command", action: a} -> execute(a)
    {type: "status"} -> send_status()
    [head, ...tail] -> process_list(head, tail)
    _ -> log("Unknown data format")
}

Wait conditions

wait_for

Block until condition is true:

// Wait for sensor
wait_for(sensors.distance.front < 30)

// Wait with timeout
wait_for(sensors.distance.front < 30, 5000)  // 5 second timeout

wait_for_any

Wait for any condition:

var which = wait_for_any([
    () -> sensors.distance.front < 30,
    () -> sensors.bump.front,
    () -> battery.low
])

match which {
    0 -> handle_distance()
    1 -> handle_bump()
    2 -> handle_battery()
}

wait_for_all

Wait for all conditions:

wait_for_all([
    () -> sensors.calibrated,
    () -> motors.ready,
    () -> gps.fix
])
log("All systems ready")

Debouncing and filtering

Debounce

Prevent rapid triggering:

// Require condition to be stable for 500ms
trigger on debounce(sensors.bump.front, 500) {
    // Only triggers if bump stays true for 500ms
    handle_bump()
}

Rate limiting

Limit trigger frequency:

// Max once per second
trigger on rate_limit(sensors.distance.front < 30, 1000) {
    log("Obstacle detected")
}

Smoothing

Filter noisy sensors:

// Moving average of last 10 readings
var smooth_distance = smooth(sensors.distance.front, 10)

trigger on smooth_distance < 30 {
    // Only trigger on stable readings
    stop()
}

Compound conditions

All conditions

var all_clear = all([
    sensors.distance.front > 50,
    sensors.distance.left > 30,
    sensors.distance.right > 30
])

if all_clear {
    drive_forward()
}

Any condition

var obstacle = any([
    sensors.distance.front < 30,
    sensors.bump.front,
    sensors.bump.left,
    sensors.bump.right
])

if obstacle {
    stop()
}

None condition

var safe = none([
    sensors.bump.front,
    sensors.bump.left,
    sensors.bump.right,
    battery.low
])

if safe {
    continue_operation()
}

Conditional plan activation

Auto-activate plans

// Define auto-activation rules
auto_activate("ObstacleStop") when sensors.distance.front < 20
auto_activate("LowBattery") when battery.level < 15
auto_activate("NightMode") when time.hour >= 22 || time.hour < 6

// With priority
auto_activate("Emergency", {priority: 100}) when sensors.bump.front

Conditional plan groups

// Define conditional groups
condition_group "autonomous_conditions" {
    sensors.distance.front > 30,
    !sensors.bump.front,
    battery.level > 10
}

// Check group
if check_conditions("autonomous_conditions") {
    activate("AutonomousPlan")
} else {
    activate("SafeMode")
}

Best practices

Ordered conditions

Check most important conditions first:

loop {
    // Safety first
    if sensors.bump.front || sensors.distance.front < 10 {
        emergency_stop()
        continue
    }

    // Battery second
    if battery.low {
        activate("ReturnToBase")
        continue
    }

    // Normal operation
    do_normal_work()

    delay(50)
}

Hysteresis

Prevent oscillation:

var THRESHOLD_LOW = 25
var THRESHOLD_HIGH = 35
var is_close = false

loop {
    var dist = sensors.distance.front

    // Different thresholds for on/off
    if !is_close && dist < THRESHOLD_LOW {
        is_close = true
        slow_down()
    } else if is_close && dist > THRESHOLD_HIGH {
        is_close = false
        resume_speed()
    }

    delay(50)
}

Timeout handling

Always handle timeouts:

try {
    var result = wait_for(condition, 5000)
    process_result(result)
} catch error {
    if error.code == "TIMEOUT" {
        log_warn("Condition not met in time")
        handle_timeout()
    } else {
        throw error
    }
}

In this section
Topics
reference plans conditions triggers events
See also