ReferenceControlArcHow ToTest Sequences

Test Sequences

Build automated test sequences with stages, timing, and abort handling

Test sequences are ordered procedures that step through stages: pressurize, hold, fire, shutdown. Arc sequences handle this naturally with stages, transitions, and concurrent monitoring for abort conditions.

Basic Sequence

A minimal sequence with idle, active, and complete states:

sequence main {
    stage idle {
        // Wait for operator to start
        0 -> press_vlv_cmd
    }

    stage active {
        1 -> press_vlv_cmd
        ox_pt_1 > 500 => next
    }

    stage complete {
        0 -> press_vlv_cmd
    }
}

start_cmd => main

Wire start_cmd to a button in Console. When clicked, the sequence moves from idle to active, opens the valve, waits for pressure to reach 500, then closes the valve and stops.

The entry point start_cmd => main triggers the sequence when the channel receives a truthy value (non-zero). Create start_cmd as a u8 virtual channel in Synnax, then wire it to a button in your Console schematic.

Timed Stages

Use wait to add delays between stages:

sequence main {
    stage pressurize {
        1 -> press_vlv_cmd
        ox_pt_1 > 500 => next
    }

    stage hold {
        // Keep valve open, wait 30 seconds
        1 -> press_vlv_cmd
        wait{duration=30s} => next
    }

    stage depressurize {
        0 -> press_vlv_cmd
        ox_pt_1 < 50 => next
    }

    stage complete {
        0 -> press_vlv_cmd
    }
}

start_cmd => main

The sequence pressurizes to 500 psi, holds for 30 seconds, then depressurizes.

Abort Handling

Real test sequences need abort capability. List abort conditions first in each stage (line order determines priority):

sequence main {
    stage pressurize {
        // ABORT CONDITIONS FIRST
        ox_pt_1 > 700 => abort // over-pressure
        abort_btn => abort // operator abort
        // Normal operations
        1 -> press_vlv_cmd
        ox_pt_1 > 500 => next
    }

    stage hold {
        ox_pt_1 > 700 => abort
        abort_btn => abort
        1 -> press_vlv_cmd
        wait{duration=30s} => next
    }

    stage depressurize {
        abort_btn => abort
        0 -> press_vlv_cmd
        ox_pt_1 < 50 => next
    }

    stage complete {
        0 -> press_vlv_cmd
    }
}

sequence abort {
    stage safing {
        0 -> press_vlv_cmd
        0 -> fuel_vlv_cmd
        0 -> igniter_cmd
    }
}

start_cmd => main
emergency_stop => abort

The abort sequence closes all valves and disables actuators. Both the automated conditions and the emergency_stop channel can trigger it.

Always put abort conditions before normal operation flows in each stage. When multiple conditional transitions (=>) are truthy in the same cycle, the first one listed wins. Safety conditions should always have priority.

Conditional Progression

Advance based on multiple conditions being satisfied:

sequence main {
    stage verify {
        // Check all systems ready
        ox_pt_1 > 100 and ox_pt_1 < 200 and fuel_pt_1 > 100 => next
        // Timeout if conditions not met
        wait{duration=10s} => timeout
    }

    stage pressurize {
        ox_pt_1 > 700 => abort
        abort_btn => abort
        1 -> press_vlv_cmd
        ox_pt_1 > 500 => next
    }

    stage hold {
        // ... rest of sequence
    }

    stage timeout {
        // Handle timeout condition
        0 -> press_vlv_cmd
    }
}

The verify stage waits until both pressure readings are in range. If they don’t reach the required values within 10 seconds, the sequence moves to a timeout stage instead.

Inline Gates for Linear Procedures

When a test is mostly ordered writes and waits but has one step that needs to watch multiple conditions, an inline stage keeps the procedure readable without forcing every step into its own named stage:

sequence prime {
    // Straight-line setup
    0 -> vent_vlv_cmd
    1 -> press_vlv_cmd

    // Inline gate: success, over-pressure abort, and timeout all watched at once
    stage {
        tank_pressure > 700 => abort
        abort_btn => abort
        tank_pressure > 500 => next
        wait{30s} => timeout
    }

    // Procedure resumes
    0 -> press_vlv_cmd
}

sequence abort {
    stage safed {
        0 -> press_vlv_cmd
        1 -> vent_vlv_cmd
    }
}

sequence timeout {
    stage safed {
        0 -> press_vlv_cmd
        1 -> vent_vlv_cmd
    }
}

start_btn => prime
emergency_stop => abort

All four transitions inside the inline stage are armed in parallel. Line order breaks ties, so the over-pressure abort wins if it fires at the same instant as the success check. => next advances to the next item in prime; => abort and => timeout jump to those named sequences.

Use this pattern when the rest of the procedure is straight-line and only one step needs a multi-exit gate.

Rate-Limited Pressurization

Control the pressurization rate to avoid thermal shock or mechanical stress:

func rate_monitor{dt_ms f64, max_rate f64} (value f64) u8 {
    prev $= 0.0
    d := value - prev
    prev = value

    dt_s := dt_ms / 1000.0
    if dt_s <= 0 {
        return 0
    }

    rate := d / dt_s
    // Check magnitude
    if rate < 0 {
        rate = 0.0 - rate
    }

    if rate > max_rate {
        return 1
    }
    return 0
}

sequence main {
    stage pressurize {
        // Abort conditions
        ox_pt_1 > 700 => abort
        ox_pt_1 -> rate_monitor{dt_ms=50.0, max_rate=100.0} -> ox_rate_high
        ox_rate_high => abort
        abort_btn => abort
        // Pressurize slowly by pulsing the valve
        interval{period=100ms} -> press_valve_pulse{}
        ox_pt_1 > 500 => next
    }
    // ... rest of sequence
}

The rate monitor triggers an abort if pressure rises faster than 100 psi/second.

Complete Test Stand Sequence

A realistic rocket engine test sequence with all the patterns combined:

// Rate monitoring
func pressure_rate{dt_ms f64} (value f64) f64 {
    prev $= 0.0
    d := value - prev
    prev = value
    dt_s := dt_ms / 1000.0
    if dt_s <= 0 {
        return 0.0
    }
    return d / dt_s
}

sequence main {
    // Stage 1: System checkout
    stage checkout {
        ox_pt_1 > 50 => abort // tank should be empty
        fuel_pt_1 > 50 => abort
        abort_btn => abort
        // All systems nominal, proceed
        wait{duration=2s} => next
    }
    // Stage 2: Pressurize oxidizer
    stage press_ox {
        ox_pt_1 > 650 => abort
        ox_pt_1 -> pressure_rate{dt_ms=50.0} -> ox_rate
        ox_rate > 100 => abort
        abort_btn => abort
        1 -> ox_press_vlv_cmd
        ox_pt_1 > 500 => next
    }
    // Stage 3: Pressurize fuel
    stage press_fuel {
        ox_pt_1 > 650 => abort
        fuel_pt_1 > 450 => abort
        abort_btn => abort
        1 -> ox_press_vlv_cmd // maintain ox pressure
        1 -> fuel_press_vlv_cmd
        fuel_pt_1 > 350 => next
    }
    // Stage 4: Pre-fire hold
    stage hold {
        ox_pt_1 > 650 => abort
        fuel_pt_1 > 450 => abort
        ox_pt_1 < 400 => abort // pressure decay = leak
        fuel_pt_1 < 250 => abort
        abort_btn => abort
        1 -> ox_press_vlv_cmd
        1 -> fuel_press_vlv_cmd
        wait{duration=5s} => next
    }
    // Stage 5: Ignition
    stage ignite {
        ox_pt_1 > 650 => abort
        fuel_pt_1 > 450 => abort
        abort_btn => abort
        1 -> ox_press_vlv_cmd
        1 -> fuel_press_vlv_cmd
        1 -> igniter_cmd
        // Wait for combustion confirmation
        chamber_tc_1 > 500 => next
        // Ignition timeout
        wait{duration=3s} => ignition_fail
    }
    // Stage 6: Main run
    stage main_run {
        ox_pt_1 > 700 => abort
        fuel_pt_1 > 500 => abort
        chamber_tc_1 > 2000 => abort
        abort_btn => abort
        1 -> ox_press_vlv_cmd
        1 -> fuel_press_vlv_cmd
        1 -> ox_main_vlv_cmd
        1 -> fuel_main_vlv_cmd
        0 -> igniter_cmd
        wait{duration=10s} => next
    }
    // Stage 7: Shutdown
    stage shutdown {
        // Controlled shutdown sequence
        0 -> ox_main_vlv_cmd
        0 -> fuel_main_vlv_cmd
        wait{duration=1s} => next
    }
    // Stage 8: Depressurize
    stage depress {
        0 -> ox_press_vlv_cmd
        0 -> fuel_press_vlv_cmd
        1 -> ox_vent_vlv_cmd
        1 -> fuel_vent_vlv_cmd
        ox_pt_1 < 20 and fuel_pt_1 < 20 => next
    }
    // Stage 9: Complete
    stage complete {
        0 -> ox_vent_vlv_cmd
        0 -> fuel_vent_vlv_cmd
    }
    // Stage: Ignition failure
    stage ignition_fail {
        0 -> igniter_cmd
        1 => abort
    }
}

sequence abort {
    stage safing {
        // Close all valves immediately
        0 -> ox_press_vlv_cmd
        0 -> fuel_press_vlv_cmd
        0 -> ox_main_vlv_cmd
        0 -> fuel_main_vlv_cmd
        0 -> igniter_cmd
        // Open vents
        1 -> ox_vent_vlv_cmd
        1 -> fuel_vent_vlv_cmd
    }
}
// Entry points
start_cmd => main
emergency_stop => abort

Sequence Design Tips

List abort conditions first. Line order determines priority. Safety conditions should always win over normal operations.

Use multiple abort thresholds. A pressure of 600 psi might be a warning, but 700 psi triggers an immediate abort.

Add timeouts. Sequences that wait indefinitely for conditions can hang. Use wait to set maximum durations and transition to error stages.

Keep stages focused. Each stage should have one primary purpose. Split complex operations into multiple stages for clarity and easier debugging.

Monitor continuously. While waiting for one condition, continue checking abort conditions. All flows in a stage run concurrently.

Test abort paths. Simulate abort conditions during development to verify the system reaches a safe state. The abort sequence is the most important part of your automation.