Skip to main content

Engine

Struct Engine 

Source
pub struct Engine { /* private fields */ }
Expand description

The TestStand™ Engine.

Constructing an Engine creates the underlying COM object; dropping it releases it. It is the entry point of the API:

use rs_teststand::Engine;

let engine = Engine::new()?;
println!("TestStand {}", engine.version_string()?);

Implementations§

Source§

impl Engine

Source

pub fn new() -> Result<Self, Error>

Creates the engine (STA COM apartment plus the TestStand.Engine object).

§Errors

Error::Com if COM cannot be initialized or the engine class cannot be created (e.g. no TestStand™ installation is registered).

Examples found in repository?
examples/version_print.rs (line 20)
19fn main() -> Result<(), Error> {
20    let engine = Engine::new()?;
21
22    println!("version : {}", engine.version_string()?);
23    println!(
24        "numeric : {}.{}.{} build {}",
25        engine.major_version()?,
26        engine.minor_version()?,
27        engine.revision_version()?,
28        engine.build_version()?
29    );
30    println!("bitness : {}", if engine.is_64bit()? { 64 } else { 32 });
31    println!("install : {}", engine.teststand_directory()?);
32
33    Ok(())
34}
More examples
Hide additional examples
examples/workspace_create.rs (line 6)
5fn main() -> Result<(), rs_teststand::Error> {
6    let engine = Engine::new()?;
7
8    println!("Creating a new workspace file...");
9    let ws_file = engine.new_workspace_file()?;
10    let root_obj = ws_file.root_workspace_object()?;
11
12    println!("Root workspace object:");
13    println!("  Display Name: '{}'", root_obj.display_name()?);
14    println!("  Object Type: {}", root_obj.object_type()?);
15    println!("  Child Objects: {}", root_obj.num_contained_objects()?);
16
17    println!("\nCreating project and folder structure...");
18    let project = root_obj.new_folder("Project Alpha")?;
19    println!("  Created project folder: '{}'", project.display_name()?);
20
21    let seq_entry = project.new_file("MainTest.seq")?;
22    println!(
23        "  Created sequence file entry: '{}'",
24        seq_entry.display_name()?
25    );
26
27    println!(
28        "\nUpdated root child objects count: {}",
29        root_obj.num_contained_objects()?
30    );
31
32    Ok(())
33}
examples/execution_run_subsequence.rs (line 141)
140fn main() -> Result<(), Box<dyn std::error::Error>> {
141    let engine = Engine::new()?;
142    engine.set_ui_message_polling_enabled(true)?;
143
144    let sequence_file = build(&engine)?;
145    println!("File holds {} sequence(s).", sequence_file.num_sequences()?);
146
147    // The conventional entry point.
148    run(&engine, &sequence_file, "MainSequence")?;
149
150    // A subsequence run on its own has no caller, so nothing supplies its
151    // parameters, they keep whatever default the sequence carries. Setting
152    // that default is therefore how a direct run is given its input.
153    let diagnostics = sequence_file.get_sequence_by_name(SUBSEQUENCE)?;
154    diagnostics
155        .parameters()?
156        .set_val_string(PARAMETER, none(), "FIXTURE-07")?;
157    println!(
158        "\n{SUBSEQUENCE}.{PARAMETER} default is now {:?}",
159        diagnostics
160            .parameters()?
161            .get_val_string(PARAMETER, none())?
162    );
163
164    run(&engine, &sequence_file, SUBSEQUENCE)?;
165
166    engine.release_sequence_file_ex(sequence_file, none())?;
167    Ok(())
168}
examples/station_options_update.rs (line 6)
5fn main() -> Result<(), rs_teststand::Error> {
6    let engine = Engine::new()?;
7    let station_options = engine.station_options()?;
8
9    station_options.set_tracing_enabled(true)?;
10    station_options.set_disable_results(false)?;
11    station_options.set_breakpoints_enabled(true)?;
12    station_options.set_check_out_files_when_edited(false)?;
13    station_options.set_language("English")?;
14    station_options.set_always_goto_cleanup_on_failure(true)?;
15    station_options.set_show_hidden_properties(true)?;
16    station_options.set_prompt_to_find_files(false)?;
17    station_options.set_auto_login_system_user(true)?;
18    station_options.set_ui_message_delay(100)?;
19    station_options.set_ui_message_min_delay(10)?;
20    station_options.set_station_id("STATION_RUST_01")?;
21    station_options.set_use_station_model(true)?;
22    station_options.set_allow_other_models(false)?;
23    station_options.set_use_localized_decimal_point(false)?;
24    station_options.set_time_limit(0, 0, 60.0)?;
25    station_options.set_time_limit_enabled(0, 0, true)?;
26
27    engine.commit_globals_to_disk(false)?;
28    println!("Station options updated and committed to disk.");
29
30    Ok(())
31}
examples/execution_run_test_headless.rs (line 147)
146fn main() -> Result<(), Box<dyn std::error::Error>> {
147    let engine = Engine::new()?;
148    // Nothing reaches the queue until this is on, and without the queue there
149    // is no way to know the run ended.
150    engine.set_ui_message_polling_enabled(true)?;
151
152    let sequence_file = engine.new_sequence_file()?;
153    let main_sequence = sequence_file.get_sequence_by_name("MainSequence")?;
154    for (name, data_source, low, high) in TESTS {
155        add_numeric_limit_test(&engine, &main_sequence, name, data_source, low, high)?;
156    }
157
158    let execution = engine.new_execution(&sequence_file, "MainSequence", None, false, 0)?;
159    println!("Running {} headless...", execution.display_name()?);
160
161    if wait_for_end(&engine, RUN_DEADLINE)? {
162        println!("Finished with status: {}", execution.result_status()?);
163        report(&execution.result_object()?)?;
164    } else {
165        // Reported rather than ignored: a host that assumes success here would
166        // publish results from a run that never finished.
167        println!("The run did not finish within {RUN_DEADLINE:?}; terminating.");
168        execution.terminate()?;
169    }
170
171    engine.release_sequence_file_ex(sequence_file, none())?;
172    Ok(())
173}
examples/template_manage_complex.rs (line 177)
176fn main() -> Result<(), Box<dyn std::error::Error>> {
177    let engine = Engine::new()?;
178    describe_station_templates(&engine)?;
179
180    println!("\nBuilding an in-memory template group...");
181    let group = new_template_group(&engine)?;
182    append_template(&group, &step_template(&engine)?)?;
183    append_template(&group, &sequence_template(&engine)?)?;
184    append_template(&group, &variable_template(&engine)?)?;
185    println!("  {} template(s) stored", group.get_num_elements()?);
186
187    let step = require(find_template(&group, STEP_TEMPLATE)?, STEP_TEMPLATE)?;
188    let sequence = require(find_template(&group, SEQUENCE_TEMPLATE)?, SEQUENCE_TEMPLATE)?;
189    let variable = require(find_template(&group, VARIABLE_TEMPLATE)?, VARIABLE_TEMPLATE)?;
190
191    // Saving first, then reopening, is deliberate: templates are worth having
192    // because they are applied to files a program did not build in this run.
193    let path = std::env::temp_dir().join("rs_teststand_from_templates.seq");
194    let path = path.to_string_lossy().into_owned();
195    engine.new_sequence_file()?.save(&path)?;
196
197    println!("\nApplying templates to the saved file...");
198    let target = engine.get_sequence_file_ex(
199        &path,
200        GetSeqFileOptions::DO_NOT_RUN_LOAD_CALLBACK,
201        rs_teststand::ConflictHandler::Error,
202    )?;
203    apply_templates(&target, &step, &sequence, &variable)?;
204    target.save(&path)?;
205    println!("\nSaved to {path}");
206
207    engine.release_sequence_file_ex(target, PropertyOptions::NONE.bits())?;
208    Ok(())
209}
Source

pub fn startup_dialogs(&self) -> &[DialogInfo]

Dialogs that were closed while this engine was being created.

A non-empty list is worth logging: it is the only record that something asked a question and was answered by closing the window.

Empty means nothing owned by this process was found, which is not the same as no dialog having appeared. Detection cannot see another process’s windows, and whether the engine’s unreleased-files warning is raised in-process has not been established. Do not treat an empty list as proof that startup was clean.

Source

pub fn load_type_palette_files_ex( &self, handler: ConflictHandler, options: i32, ) -> Result<(), Error>

Loads the type palette files (Engine.LoadTypePaletteFilesEx).

Called during construction; exposed for a caller that reconfigures the palette list and needs to reload.

§Errors

Error if the COM call fails.

Source

pub fn load_type_palette_files(&self) -> Result<(), Error>

Loads the type palette files (Engine.LoadTypePaletteFiles).

The older form, without conflict handling. Kept because it is the member available on engines from TestStand 2016.

§Errors

Error if the COM call fails.

Source

pub fn unload_type_palette_files(&self) -> Result<(), Error>

Unloads the type palette files (Engine.UnloadTypePaletteFiles).

§Errors

Error if the COM call fails.

Source

pub fn major_version(&self) -> Result<i32, Error>

The engine’s major version number (Engine.MajorVersion): the two-digit major, so TestStand™ 2026 reports 26 and 2016 reports 16.

§Errors

Error if the COM call fails or returns an unexpected type.

Examples found in repository?
examples/version_print.rs (line 25)
19fn main() -> Result<(), Error> {
20    let engine = Engine::new()?;
21
22    println!("version : {}", engine.version_string()?);
23    println!(
24        "numeric : {}.{}.{} build {}",
25        engine.major_version()?,
26        engine.minor_version()?,
27        engine.revision_version()?,
28        engine.build_version()?
29    );
30    println!("bitness : {}", if engine.is_64bit()? { 64 } else { 32 });
31    println!("install : {}", engine.teststand_directory()?);
32
33    Ok(())
34}
Source

pub fn minor_version(&self) -> Result<i32, Error>

The engine’s minor version number (Engine.MinorVersion).

§Errors

Error if the COM call fails or returns an unexpected type.

Examples found in repository?
examples/version_print.rs (line 26)
19fn main() -> Result<(), Error> {
20    let engine = Engine::new()?;
21
22    println!("version : {}", engine.version_string()?);
23    println!(
24        "numeric : {}.{}.{} build {}",
25        engine.major_version()?,
26        engine.minor_version()?,
27        engine.revision_version()?,
28        engine.build_version()?
29    );
30    println!("bitness : {}", if engine.is_64bit()? { 64 } else { 32 });
31    println!("install : {}", engine.teststand_directory()?);
32
33    Ok(())
34}
Source

pub fn revision_version(&self) -> Result<i32, Error>

The engine’s revision version number (Engine.RevisionVersion).

§Errors

Error if the COM call fails or returns an unexpected type.

Examples found in repository?
examples/version_print.rs (line 27)
19fn main() -> Result<(), Error> {
20    let engine = Engine::new()?;
21
22    println!("version : {}", engine.version_string()?);
23    println!(
24        "numeric : {}.{}.{} build {}",
25        engine.major_version()?,
26        engine.minor_version()?,
27        engine.revision_version()?,
28        engine.build_version()?
29    );
30    println!("bitness : {}", if engine.is_64bit()? { 64 } else { 32 });
31    println!("install : {}", engine.teststand_directory()?);
32
33    Ok(())
34}
Source

pub fn build_version(&self) -> Result<i32, Error>

The engine’s build version number (Engine.BuildVersion).

§Errors

Error if the COM call fails or returns an unexpected type.

Examples found in repository?
examples/version_print.rs (line 28)
19fn main() -> Result<(), Error> {
20    let engine = Engine::new()?;
21
22    println!("version : {}", engine.version_string()?);
23    println!(
24        "numeric : {}.{}.{} build {}",
25        engine.major_version()?,
26        engine.minor_version()?,
27        engine.revision_version()?,
28        engine.build_version()?
29    );
30    println!("bitness : {}", if engine.is_64bit()? { 64 } else { 32 });
31    println!("install : {}", engine.teststand_directory()?);
32
33    Ok(())
34}
Source

pub fn version_string(&self) -> Result<String, Error>

The engine’s full version string (Engine.VersionString).

§Errors

Error if the COM call fails or returns an unexpected type.

Examples found in repository?
examples/version_print.rs (line 22)
19fn main() -> Result<(), Error> {
20    let engine = Engine::new()?;
21
22    println!("version : {}", engine.version_string()?);
23    println!(
24        "numeric : {}.{}.{} build {}",
25        engine.major_version()?,
26        engine.minor_version()?,
27        engine.revision_version()?,
28        engine.build_version()?
29    );
30    println!("bitness : {}", if engine.is_64bit()? { 64 } else { 32 });
31    println!("install : {}", engine.teststand_directory()?);
32
33    Ok(())
34}
Source

pub fn is_64bit(&self) -> Result<bool, Error>

Returns true if the TestStand™ engine is running as a 64-bit process (Engine.Is64Bit).

§Errors

Error if the COM call fails or returns an unexpected type.

Examples found in repository?
examples/version_print.rs (line 30)
19fn main() -> Result<(), Error> {
20    let engine = Engine::new()?;
21
22    println!("version : {}", engine.version_string()?);
23    println!(
24        "numeric : {}.{}.{} build {}",
25        engine.major_version()?,
26        engine.minor_version()?,
27        engine.revision_version()?,
28        engine.build_version()?
29    );
30    println!("bitness : {}", if engine.is_64bit()? { 64 } else { 32 });
31    println!("install : {}", engine.teststand_directory()?);
32
33    Ok(())
34}
Source

pub fn teststand_directory(&self) -> Result<String, Error>

The path to the TestStand™ root directory (Engine.TestStandDirectory).

§Errors

Error if the COM call fails or returns an unexpected type.

Examples found in repository?
examples/version_print.rs (line 31)
19fn main() -> Result<(), Error> {
20    let engine = Engine::new()?;
21
22    println!("version : {}", engine.version_string()?);
23    println!(
24        "numeric : {}.{}.{} build {}",
25        engine.major_version()?,
26        engine.minor_version()?,
27        engine.revision_version()?,
28        engine.build_version()?
29    );
30    println!("bitness : {}", if engine.is_64bit()? { 64 } else { 32 });
31    println!("install : {}", engine.teststand_directory()?);
32
33    Ok(())
34}
Source

pub fn bin_directory(&self) -> Result<String, Error>

The path to the TestStand™ Bin directory (Engine.BinDirectory).

§Errors

Error if the COM call fails or returns an unexpected type.

Examples found in repository?
examples/search_directory_manage.rs (line 60)
49fn main() -> Result<(), rs_teststand::Error> {
50    let engine = Engine::new()?;
51    let search_directories = engine.search_directories()?;
52
53    println!("Total search directories: {}", search_directories.count()?);
54    for (index, directory) in search_directories.iter()?.enumerate() {
55        print_entry(index, &directory?)?;
56    }
57
58    // Insert at the front, so the new entry is searched first.
59    println!("\nInserting a new explicit search directory...");
60    let new_path = engine.bin_directory()?;
61    search_directories.insert(&new_path, 0, true, "", false, false)?;
62    println!("Total after insert: {}", search_directories.count()?);
63
64    let inserted = search_directories.get(0)?;
65    println!(
66        "New [0] Path: '{}', Subdirs: {}",
67        inserted.path()?,
68        inserted.search_subdirectories()?
69    );
70
71    // A disabled entry stays in the list but is not searched.
72    println!("Disabling the new directory...");
73    inserted.set_disabled(true)?;
74    println!("New [0] Disabled: {}", inserted.disabled()?);
75
76    // Order matters: entries are searched in list order.
77    println!("Moving the new directory to index 1...");
78    search_directories.move_search_directory(0, 1)?;
79    println!(
80        "Directory at index 1 is now: '{}'",
81        search_directories.get(1)?.path()?
82    );
83
84    println!("Removing the added directory to clean up...");
85    search_directories.remove(1)?;
86    println!("Total after cleanup: {}", search_directories.count()?);
87
88    // The engine writes search directories out at shutdown anyway. Committing
89    // now makes the change visible to other processes immediately, and passing
90    // `false` keeps a save conflict from raising a dialog.
91    engine.commit_globals_to_disk(false)?;
92    println!("Committed search directories configuration to disk.");
93
94    Ok(())
95}
Source

pub fn config_directory(&self) -> Result<String, Error>

The path to the TestStand™ Cfg directory (Engine.ConfigDirectory).

§Errors

Error if the COM call fails or returns an unexpected type.

Source

pub fn station_options(&self) -> Result<StationOptions, Error>

Accesses the station’s configuration settings (Engine.StationOptions).

§Errors

Error if the COM call fails or returns an unexpected type.

Examples found in repository?
examples/station_options_update.rs (line 7)
5fn main() -> Result<(), rs_teststand::Error> {
6    let engine = Engine::new()?;
7    let station_options = engine.station_options()?;
8
9    station_options.set_tracing_enabled(true)?;
10    station_options.set_disable_results(false)?;
11    station_options.set_breakpoints_enabled(true)?;
12    station_options.set_check_out_files_when_edited(false)?;
13    station_options.set_language("English")?;
14    station_options.set_always_goto_cleanup_on_failure(true)?;
15    station_options.set_show_hidden_properties(true)?;
16    station_options.set_prompt_to_find_files(false)?;
17    station_options.set_auto_login_system_user(true)?;
18    station_options.set_ui_message_delay(100)?;
19    station_options.set_ui_message_min_delay(10)?;
20    station_options.set_station_id("STATION_RUST_01")?;
21    station_options.set_use_station_model(true)?;
22    station_options.set_allow_other_models(false)?;
23    station_options.set_use_localized_decimal_point(false)?;
24    station_options.set_time_limit(0, 0, 60.0)?;
25    station_options.set_time_limit_enabled(0, 0, true)?;
26
27    engine.commit_globals_to_disk(false)?;
28    println!("Station options updated and committed to disk.");
29
30    Ok(())
31}
Source

pub fn new_sequence_file(&self) -> Result<SequenceFile, Error>

Creates an empty sequence file (Engine.NewSequenceFile).

The file exists only in memory until it is saved.

§Errors

Error if the COM call fails or returns an unexpected type.

Examples found in repository?
examples/execution_run_subsequence.rs (line 60)
59fn build(engine: &Engine) -> Result<SequenceFile, Error> {
60    let sequence_file = engine.new_sequence_file()?;
61
62    let main_sequence = sequence_file.get_sequence_by_name("MainSequence")?;
63    for name in ["Initialize", "Run Test", "Shut Down"] {
64        add_action(engine, &main_sequence, name)?;
65    }
66
67    let diagnostics = engine.new_sequence()?;
68    diagnostics.set_name(SUBSEQUENCE)?;
69    // A parameter is an ordinary property in the sequence's Parameters scope.
70    diagnostics.parameters()?.new_sub_property(
71        PARAMETER,
72        PropValType::String,
73        false,
74        "",
75        insert_if_missing(),
76    )?;
77    for name in ["Check Power Rails", "Check Clock"] {
78        add_action(engine, &diagnostics, name)?;
79    }
80    sequence_file.insert_sequence(&diagnostics)?;
81
82    Ok(sequence_file)
83}
More examples
Hide additional examples
examples/execution_run_test_headless.rs (line 152)
146fn main() -> Result<(), Box<dyn std::error::Error>> {
147    let engine = Engine::new()?;
148    // Nothing reaches the queue until this is on, and without the queue there
149    // is no way to know the run ended.
150    engine.set_ui_message_polling_enabled(true)?;
151
152    let sequence_file = engine.new_sequence_file()?;
153    let main_sequence = sequence_file.get_sequence_by_name("MainSequence")?;
154    for (name, data_source, low, high) in TESTS {
155        add_numeric_limit_test(&engine, &main_sequence, name, data_source, low, high)?;
156    }
157
158    let execution = engine.new_execution(&sequence_file, "MainSequence", None, false, 0)?;
159    println!("Running {} headless...", execution.display_name()?);
160
161    if wait_for_end(&engine, RUN_DEADLINE)? {
162        println!("Finished with status: {}", execution.result_status()?);
163        report(&execution.result_object()?)?;
164    } else {
165        // Reported rather than ignored: a host that assumes success here would
166        // publish results from a run that never finished.
167        println!("The run did not finish within {RUN_DEADLINE:?}; terminating.");
168        execution.terminate()?;
169    }
170
171    engine.release_sequence_file_ex(sequence_file, none())?;
172    Ok(())
173}
examples/template_manage_complex.rs (line 195)
176fn main() -> Result<(), Box<dyn std::error::Error>> {
177    let engine = Engine::new()?;
178    describe_station_templates(&engine)?;
179
180    println!("\nBuilding an in-memory template group...");
181    let group = new_template_group(&engine)?;
182    append_template(&group, &step_template(&engine)?)?;
183    append_template(&group, &sequence_template(&engine)?)?;
184    append_template(&group, &variable_template(&engine)?)?;
185    println!("  {} template(s) stored", group.get_num_elements()?);
186
187    let step = require(find_template(&group, STEP_TEMPLATE)?, STEP_TEMPLATE)?;
188    let sequence = require(find_template(&group, SEQUENCE_TEMPLATE)?, SEQUENCE_TEMPLATE)?;
189    let variable = require(find_template(&group, VARIABLE_TEMPLATE)?, VARIABLE_TEMPLATE)?;
190
191    // Saving first, then reopening, is deliberate: templates are worth having
192    // because they are applied to files a program did not build in this run.
193    let path = std::env::temp_dir().join("rs_teststand_from_templates.seq");
194    let path = path.to_string_lossy().into_owned();
195    engine.new_sequence_file()?.save(&path)?;
196
197    println!("\nApplying templates to the saved file...");
198    let target = engine.get_sequence_file_ex(
199        &path,
200        GetSeqFileOptions::DO_NOT_RUN_LOAD_CALLBACK,
201        rs_teststand::ConflictHandler::Error,
202    )?;
203    apply_templates(&target, &step, &sequence, &variable)?;
204    target.save(&path)?;
205    println!("\nSaved to {path}");
206
207    engine.release_sequence_file_ex(target, PropertyOptions::NONE.bits())?;
208    Ok(())
209}
examples/step_insert_from_template.rs (line 77)
65fn main() -> Result<(), Box<dyn std::error::Error>> {
66    let engine = Engine::new()?;
67    describe_templates(&engine)?;
68
69    let prototype = build_prototype(&engine)?;
70    println!(
71        "Prototype: {} via {:?}, run mode {:?}",
72        prototype.name()?,
73        prototype.adapter_key_name()?,
74        prototype.run_mode()?
75    );
76
77    let sequence_file = engine.new_sequence_file()?;
78    let main_sequence = sequence_file.get_sequence_by_name("MainSequence")?;
79    let template = prototype.as_property_object()?;
80
81    for copy_number in 1..=COPIES {
82        let index = main_sequence.get_num_steps(StepGroup::Main)?;
83        let inserted =
84            main_sequence.insert_step_from_template(&template, index, StepGroup::Main)?;
85        inserted.set_name(&format!("Measure {copy_number} (from template)"))?;
86        // Each copy needs an identity of its own; the clone brought the
87        // prototype's.
88        inserted.create_new_unique_step_id()?;
89    }
90
91    println!(
92        "\nMainSequence now holds {} step(s):",
93        main_sequence.get_num_steps(StepGroup::Main)?
94    );
95    for index in 0..main_sequence.get_num_steps(StepGroup::Main)? {
96        let step = main_sequence.get_step(index, StepGroup::Main)?;
97        println!(
98            "  [{index}] {} -> {}",
99            step.name()?,
100            step.as_property_object()?
101                .get_val_string(VI_PATH, PropertyOptions::NONE.bits())?
102        );
103    }
104
105    // The prototype is untouched and can go on producing copies.
106    println!("\nPrototype still named: {}", prototype.name()?);
107
108    let path = std::env::temp_dir().join("rs_teststand_from_template.seq");
109    let path = path.to_string_lossy().into_owned();
110    sequence_file.save(&path)?;
111    println!("Saved to {path}");
112    Ok(())
113}
examples/sequence_build.rs (line 70)
68fn main() -> Result<(), Box<dyn std::error::Error>> {
69    let engine = Engine::new()?;
70    let sequence_file = engine.new_sequence_file()?;
71    let main_sequence = sequence_file.get_sequence_by_name("MainSequence")?;
72
73    // Steps are placed by group and index, so order is explicit.
74    main_sequence.insert_step(
75        &numeric_limit_test(
76            &engine,
77            "Temperature Check",
78            "Locals.TempSensorPresent == True",
79            15.0,
80            85.0,
81        )?,
82        0,
83        StepGroup::Main,
84    )?;
85    main_sequence.insert_step(
86        &numeric_limit_test(
87            &engine,
88            "Voltage Monitor",
89            "Locals.DUTPowered == True",
90            4.75,
91            5.25,
92        )?,
93        1,
94        StepGroup::Main,
95    )?;
96
97    // A subsequence, so a later example has something to call.
98    let subsequence = engine.new_sequence()?;
99    subsequence.set_name("CustomSubsequence")?;
100    sequence_file.insert_sequence(&subsequence)?;
101
102    let init_step = engine.new_step(NO_ADAPTER, "Action")?;
103    init_step.set_name("Initialize Hardware")?;
104    subsequence.insert_step(&init_step, 0, StepGroup::Main)?;
105
106    describe(&main_sequence)?;
107    println!();
108    describe(&subsequence)?;
109
110    // Cleanup runs even when Main fails, which is why it is worth showing.
111    println!(
112        "\nGroup sizes in MainSequence: setup={}, main={}, cleanup={}",
113        main_sequence.get_num_steps(StepGroup::Setup)?,
114        main_sequence.get_num_steps(StepGroup::Main)?,
115        main_sequence.get_num_steps(StepGroup::Cleanup)?
116    );
117
118    let path = std::env::temp_dir().join("rs_teststand_built_sequence.seq");
119    sequence_file.save(&path.to_string_lossy())?;
120    println!("\nSaved to {}", path.display());
121
122    engine.release_sequence_file_ex(sequence_file, NO_OPTIONS)?;
123    Ok(())
124}
examples/data_type_manage.rs (line 109)
107fn main() -> Result<(), Box<dyn std::error::Error>> {
108    let engine = Engine::new()?;
109    let sequence_file = engine.new_sequence_file()?;
110    let file = sequence_file.as_property_object_file()?;
111    let types = file.type_usage_list()?;
112
113    types.insert_type(
114        &build_multimeter_type(&engine)?,
115        types.num_types()?,
116        TypeCategory::CustomDataTypes,
117    )?;
118    let coupling = register_enum(
119        &engine,
120        &types,
121        "Coupling",
122        &[("AC", 0.0), ("DC", 1.0)],
123        true,
124    )?;
125
126    // A variable of the enum type, so the change below has an instance to update.
127    let main_sequence = sequence_file.get_sequence_by_name("MainSequence")?;
128    main_sequence.locals()?.new_sub_property(
129        "InputCoupling",
130        PropValType::NamedType,
131        false,
132        "Coupling",
133        INSERT_IF_MISSING,
134    )?;
135
136    println!(
137        "Registered custom data types ({} in file):",
138        types.num_types()?
139    );
140    print_enumerators(&coupling)?;
141
142    // Evolve it: add an enumerator and raise the version.
143    println!(
144        "\nCoupling version before update: {}",
145        coupling.type_version()?
146    );
147    coupling.update_enumerators(&enumerator_array(
148        &engine,
149        &[("AC", 0.0), ("DC", 1.0), ("GND", 2.0)],
150        true,
151    )?)?;
152
153    // Raising the lowest field signals a change the engine applies silently;
154    // raising a higher one marks it as deliberate.
155    let version = coupling.type_version()?;
156    let mut fields = version.split('.');
157    let major: u32 = fields.next().unwrap_or("0").parse().unwrap_or(0);
158    let minor: u32 = fields.next().unwrap_or("0").parse().unwrap_or(0);
159    coupling.set_type_version(&format!("{major}.{}.0.0", minor + 1))?;
160    println!(
161        "Coupling version after  update: {}",
162        coupling.type_version()?
163    );
164
165    println!("\nCoupling now defines (InputCoupling reflects this):");
166    print_enumerators(&coupling)?;
167
168    // Saving does nothing unless the file believes it changed.
169    file.inc_change_count()?;
170    let path = std::env::temp_dir().join("rs_teststand_with_custom_types.seq");
171    sequence_file.save(&path.to_string_lossy())?;
172    println!("\nSaved to {}", path.display());
173
174    engine.release_sequence_file_ex(sequence_file, NO_OPTIONS)?;
175    Ok(())
176}
Source

pub fn new_execution( &self, sequence_file: &SequenceFile, sequence_name: &str, process_model: Option<&SequenceFile>, break_at_first_step: bool, execution_type_mask: i32, ) -> Result<Execution, Error>

Starts a sequence running (Engine.NewExecution).

The execution begins immediately.

Pass None for process_model to run the sequence directly; supply one to run a process-model entry point instead. execution_type_mask is normally 0.

§Errors

Error if the sequence cannot be started or the COM call fails.

Examples found in repository?
examples/execution_run_subsequence.rs (line 109)
108fn run(engine: &Engine, sequence_file: &SequenceFile, entry_point: &str) -> Result<(), Error> {
109    let execution = engine.new_execution(sequence_file, entry_point, None, false, 0)?;
110    println!("\nRunning {entry_point}...");
111
112    if !wait_for_end(engine, RUN_DEADLINE)? {
113        println!("  did not finish within {RUN_DEADLINE:?}; terminating");
114        execution.terminate()?;
115        return Ok(());
116    }
117    println!("  status: {}", execution.result_status()?);
118
119    let results = execution.result_object()?;
120    if !results.exists("ResultList", none())? {
121        println!("  no results recorded");
122        return Ok(());
123    }
124    let result_list = results.get_property_object("ResultList", none())?;
125    for index in 0..result_list.get_num_elements()? {
126        let entry = result_list.get_property_object_by_offset(index, none())?;
127        println!(
128            "  {}: {}",
129            entry
130                .get_val_string("TS.StepName", none())
131                .unwrap_or_else(|_| "<unnamed>".to_owned()),
132            entry
133                .get_val_string("Status", none())
134                .unwrap_or_else(|_| "<no status>".to_owned())
135        );
136    }
137    Ok(())
138}
More examples
Hide additional examples
examples/execution_run_test_headless.rs (line 158)
146fn main() -> Result<(), Box<dyn std::error::Error>> {
147    let engine = Engine::new()?;
148    // Nothing reaches the queue until this is on, and without the queue there
149    // is no way to know the run ended.
150    engine.set_ui_message_polling_enabled(true)?;
151
152    let sequence_file = engine.new_sequence_file()?;
153    let main_sequence = sequence_file.get_sequence_by_name("MainSequence")?;
154    for (name, data_source, low, high) in TESTS {
155        add_numeric_limit_test(&engine, &main_sequence, name, data_source, low, high)?;
156    }
157
158    let execution = engine.new_execution(&sequence_file, "MainSequence", None, false, 0)?;
159    println!("Running {} headless...", execution.display_name()?);
160
161    if wait_for_end(&engine, RUN_DEADLINE)? {
162        println!("Finished with status: {}", execution.result_status()?);
163        report(&execution.result_object()?)?;
164    } else {
165        // Reported rather than ignored: a host that assumes success here would
166        // publish results from a run that never finished.
167        println!("The run did not finish within {RUN_DEADLINE:?}; terminating.");
168        execution.terminate()?;
169    }
170
171    engine.release_sequence_file_ex(sequence_file, none())?;
172    Ok(())
173}
examples/result_list_parse.rs (line 130)
109fn main() -> Result<(), Box<dyn std::error::Error>> {
110    let engine = Engine::new()?;
111    engine.set_ui_message_polling_enabled(true)?;
112
113    let sequence_file = engine.new_sequence_file()?;
114    let main_sequence = sequence_file.get_sequence_by_name("MainSequence")?;
115
116    add_pass_fail(&engine, &main_sequence, "Verify Power Rail", "True")?;
117    add_unrecorded_filler(&engine, &main_sequence, "Filler (not recorded)")?;
118    add_numeric(
119        &engine,
120        &main_sequence,
121        "Verify Current Draw",
122        "1.5",
123        1.0,
124        2.0,
125    )?;
126    add_pass_fail(&engine, &main_sequence, "Verify Ground Bond", "True")?;
127
128    let step_count = main_sequence.get_num_steps(StepGroup::Main)?;
129    println!("Running {step_count} steps...");
130    let execution = engine.new_execution(&sequence_file, "MainSequence", None, false, 0)?;
131
132    if !wait_for_end(&engine, RUN_DEADLINE)? {
133        println!("The run did not finish within {RUN_DEADLINE:?}; terminating.");
134        execution.terminate()?;
135        return Ok(());
136    }
137    println!("Finished with status: {}\n", execution.result_status()?);
138
139    let results = execution.result_list()?;
140    let parsed = results.parse()?;
141
142    println!("{} of {step_count} steps recorded a result:", parsed.len());
143    for (index, result) in parsed.iter().enumerate() {
144        let measurement = match &result.value {
145            Some(ResultValue::Number(number)) => format!("measured {number}"),
146            Some(ResultValue::Text(text)) => format!("measured {text:?}"),
147            None => "no measurement".to_owned(),
148        };
149        println!(
150            "  [{index}] {} ({}) -> {}, {measurement}",
151            result.name, result.step_type, result.status
152        );
153    }
154
155    // The step whose recording was switched off is absent, by design.
156    println!(
157        "\nThe unrecorded step left no entry, which is why {} < {step_count}.",
158        parsed.len()
159    );
160
161    // The parsed results are plain data, so they outlive the run and can be
162    // handed to anything: a report writer, a database, a serde format.
163    let failures: Vec<&str> = parsed
164        .iter()
165        .filter(|result| result.status == "Failed")
166        .map(|result| result.name.as_str())
167        .collect();
168    if failures.is_empty() {
169        println!("No failures.");
170    } else {
171        println!("Failed steps: {}", failures.join(", "));
172    }
173
174    engine.release_sequence_file_ex(sequence_file, none())?;
175    Ok(())
176}
examples/ui_messages_handle.rs (line 71)
40fn main() -> Result<(), Box<dyn std::error::Error>> {
41    let engine = Engine::new()?;
42
43    // Nothing reaches the queue until polling is switched on.
44    engine.set_ui_message_polling_enabled(true)?;
45
46    let sequence_file = engine.new_sequence_file()?;
47    let main_sequence = sequence_file.get_sequence_by_name("MainSequence")?;
48
49    // Posted through the engine and tagged with the execution. Synchronous, so
50    // the sequence waits until the host acknowledges it.
51    add_statement(
52        &engine,
53        &main_sequence,
54        "Report Stage",
55        &format!(
56            "RunState.Engine.PostUIMessage(RunState.Execution, RunState.Thread, {STAGE_MESSAGE}, \
57             1, \"stage: configuring instruments\", Nothing, True)"
58        ),
59    )?;
60    // Posted through the thread. Asynchronous, so the sequence carries on.
61    add_statement(
62        &engine,
63        &main_sequence,
64        "Report Progress",
65        &format!(
66            "RunState.Thread.PostUIMessageEx({PROGRESS_MESSAGE}, 50, \"progress: halfway\", \
67             Nothing, False)"
68        ),
69    )?;
70
71    let execution = engine.new_execution(&sequence_file, "MainSequence", None, false, 0)?;
72    println!(
73        "Execution {} started; polling for messages...",
74        execution.id()?
75    );
76
77    // The end is detected from the message stream, not by waiting on the
78    // execution: a wait call does not pump the queue, so a synchronous message
79    // would sit unacknowledged and both sides would stop.
80    let started = Instant::now();
81    let mut ended = false;
82    while started.elapsed() < Duration::from_secs(60) && !ended {
83        while !engine.is_ui_message_queue_empty()? {
84            let message = engine.get_ui_message()?;
85            let code = message.event()?;
86            if matches!(
87                UIMessageCode::from_bits(code),
88                Ok(UIMessageCode::EndExecution)
89            ) {
90                ended = true;
91            }
92            let origin = if UIMessageCode::is_user_message(code) {
93                "sequence".to_owned()
94            } else {
95                format!("engine {:?}", UIMessageCode::from_bits(code))
96            };
97            println!(
98                "  [{origin}] code={code} numeric={} string={:?} synchronous={}",
99                message.numeric_data()?,
100                message.string_data()?,
101                message.is_synchronous()?
102            );
103            // Required: releases a synchronous poster and asks for the next.
104            message.acknowledge()?;
105        }
106    }
107
108    println!("\nExecution ended: {ended}");
109    engine.release_sequence_file_ex(sequence_file, NO_OPTIONS)?;
110    Ok(())
111}
Source

pub fn post_ui_message( &self, execution: &Execution, thread: &Thread, event_code: i32, numeric_data: f64, string_data: &str, activex_data: Option<&PropertyObject>, synchronous: bool, ) -> Result<(), Error>

Posts a message on behalf of an execution (Engine.PostUIMessage).

The counterpart to Thread::post_ui_message_ex, for code that is not itself running inside the sequence and therefore has no current thread to post from. Because there is no implied context, the execution and thread the message belongs to are given explicitly.

activex_data carries structured data, read back by the host from UIMessage::activex_data. Pass None to leave the slot empty.

Pass synchronous = true in the ordinary case; see Thread::post_ui_message_ex for why the blocking form is the safe default.

§Errors

Error if the COM call fails, or if a wrapper has no COM identity.

Source

pub fn set_current_user(&self, user: Option<&User>) -> Result<(), Error>

Logs a user in, or logs the current one out (Engine.CurrentUser).

Some(user) makes that user current; None clears it, which the engine documents as logging out.

This does not check the password. Setting the property is the act of logging in, not an authentication step: a host that cares must call User::validate_password first and refuse on false. Written this way because the engine draws the same line, and hiding a check inside a setter would make it unclear which one a caller had actually performed.

A host built on the ActiveX UI controls should use their own login method instead, so the controls raise the event they expect; this is the headless path.

§Errors

Error if the COM call fails, or if user has no COM identity.

Source

pub fn terminate_all(&self) -> Result<(), Error>

Asks every execution to stop (Engine.TerminateAll).

Termination, not abort: cleanup groups still run, so hardware is left in a safe state. Like Execution::terminate it is a request, and returns before the runs have finished unwinding. A caller that needs them stopped must then wait for UIMessageCode::EndExecution.

§Errors

Error if the COM call fails.

Source

pub fn abort_all(&self) -> Result<(), Error>

Stops every execution without running cleanup (Engine.AbortAll).

The blunt counterpart to terminate_all. Cleanup groups do not run, so anything a sequence would have switched off stays on. Prefer terminating unless the point is to stop now.

§Errors

Error if the COM call fails.

Source

pub fn license_type(&self) -> Result<LicenseType, Error>

The license the engine is currently using (Engine.LicenseType).

Using, not holding. A freshly created engine has acquired nothing and reports LicenseType::NoLicense even on a fully licensed station; the answer only becomes meaningful after something acquires. Use require_license to ask whether the station can license this host.

Reads state, so it acquires nothing and raises no dialog.

§Errors

Error if the COM call fails, or Error::UnknownLicenseType if the engine reports a type this build does not name.

Source

pub fn require_license(&self) -> Result<HeldLicense<'_>, Error>

Acquires a license, or fails if the station cannot grant one.

The check a headless host should make before anything else, and the object it should keep alive while it runs.

Acquiring is what makes a license real. license_type reports the license the engine is using, and a freshly created engine is using none, measured on a station with a valid development system license, it reads NoLicense until something acquires. So reading before acquiring answers the wrong question, and this method acquires first.

The request is ApplicationLicense::Unspecified, which lets the engine grant whatever it has. Naming a kind can be refused even when the station is properly licensed: on a development system station, ApplicationLicense::OperatorInterface is turned down while unspecified succeeds. Ask for a specific kind through acquire_license only when the host genuinely requires that one.

The startup dialog is suppressed, so an unlicensed station returns an error rather than opening a window nobody will close.

Refusal is retried for a few seconds before it is believed. The licensing subsystem is not ready the instant the engine object exists: measured on a properly licensed station, acquiring immediately after construction is refused, while the same call half a second later succeeds. A host that trusted the first answer would report an unlicensed station to its operator and stop. So a refusal is retried until it stops changing, which costs an unlicensed station a few seconds once, at startup.

Success is the handle, not the type. HeldLicense::kind reports what the engine says it is using and can still read NoLicense after an unspecified request was granted, so treat it as information rather than as the verdict.

§Errors

Error::NoLicense if no license can be acquired, or Error if the COM call fails.

Source

pub fn get_license_description(&self) -> Result<String, Error>

A description of the current license (Engine.GetLicenseDescription).

Free text meant for a person, so log it rather than branch on it; use license_type for decisions.

§Errors

Error if the COM call fails or returns an unexpected type.

Source

pub fn application_license(&self) -> Result<ApplicationLicense, Error>

The license this application requested (Engine.ApplicationLicense).

§Errors

Error if the COM call fails, or Error::UnknownLicenseType if the engine reports a value this build does not name.

Source

pub fn acquire_license( &self, license: ApplicationLicense, options: AcquireLicenseOptions, ) -> Result<i32, Error>

Acquires a license and returns its handle (Engine.AcquireLicense).

Release it with release_license; the license is held until every handle for it is released.

Pass AcquireLicenseOptions::SUPPRESS_STARTUP_DIALOG on any station without a person at it. Without it, an engine that cannot acquire the license opens a window offering to evaluate, activate or buy, and waits. A headless host stops there until something kills it. With it, the same situation returns an error this method propagates.

Prefer ApplicationLicense::Unspecified, which lets the engine grant whatever it has. Naming a kind is a constraint, not a preference, and a smaller request is not a safer one: on a station licensed for a development system, OperatorInterface is refused while unspecified succeeds. Name a kind only when the host truly requires it.

Most callers want require_license instead, which acquires and hands back a guard that releases on drop.

§Errors

Error::NoLicense if the license was not granted, or Error if the COM call fails.

A handle of zero is treated as refusal. The reference says this member returns an error when it cannot acquire the license; measured against an unlicensed station it succeeds and hands back zero instead. A caller that trusted the documented behavior would carry on unlicensed, so the zero is turned into the error the caller was promised.

Source

pub fn release_license(&self, handle: i32) -> Result<(), Error>

Releases a license handle (Engine.ReleaseLicense).

§Errors

Error if the COM call fails.

Source

pub fn has_addon_license(&self, feature_name: &str) -> Result<bool, Error>

Whether the station licenses an add-on feature (Engine.HasAddonLicense).

§Errors

Error if the COM call fails or returns an unexpected type.

Source

pub fn unload_all_modules(&self) -> Result<(), Error>

Releases every code module the engine has loaded (Engine.UnloadAllModules).

Loading a sequence file loads its modules, and they stay loaded until that file is closed. That is what makes the second run fast, and also what holds a DLL open against the build that wants to replace it. Unloading here frees them all at once, without closing anything.

Call it between runs, not during one: a module in use by a live execution is not a candidate, and the next run reloads whatever it needs.

State inside a module does not survive. Anything a module kept in a static or a global is gone once it is unloaded, and the reload starts from nothing. A station whose modules carry state between steps that way should keep that state in the engine instead, or not call this.

§Errors

Error if the COM call fails.

Source

pub fn breakpoints_enabled(&self) -> Result<bool, Error>

Whether breakpoints stop an execution (Engine.BreakpointsEnabled).

The master switch. With it off, breakpoints stay set but nothing stops on them, which is how a station runs unattended without anyone having to strip a sequence file of the breakpoints someone left in it.

Distinct from the station option of the same name, which is the setting written to disk. This is the engine’s live state.

§Errors

Error if the COM call fails or returns an unexpected type.

Source

pub fn set_breakpoints_enabled(&self, enabled: bool) -> Result<(), Error>

Turns breakpoints on or off (Engine.BreakpointsEnabled).

§Errors

Error if the COM call fails.

Source

pub fn persist_breakpoints(&self) -> Result<bool, Error>

Whether breakpoints survive the file they are set in (Engine.PersistBreakpoints).

On, the engine remembers them across a close and reopen. A host that sets breakpoints on behalf of a remote panel usually wants this off, so that a debugging session leaves nothing behind on the station.

§Errors

Error if the COM call fails or returns an unexpected type.

Source

pub fn set_persist_breakpoints(&self, persist: bool) -> Result<(), Error>

Chooses whether breakpoints are remembered (Engine.PersistBreakpoints).

§Errors

Error if the COM call fails.

Source

pub fn do_dot_net_garbage_collection(&self) -> Result<(), Error>

Runs a .NET garbage collection now (Engine.DoDotNetGarbageCollection).

Only relevant to a station whose steps call .NET code. Collection is otherwise periodic, on the interval; this forces one, which is worth doing between runs on a long-lived host rather than during a measurement, since collection pauses the runtime.

§Errors

Error if the COM call fails.

Source

pub fn dot_net_garbage_collection_interval(&self) -> Result<i32, Error>

How often the engine collects .NET garbage, in milliseconds (Engine.DotNetGarbageCollectionInterval).

Zero or less means automatic collection is off. A host built on this crate will normally read -1, and that is correct rather than broken: the three-second default belongs to applications built on the UI control, and a headless host does not create one. Nothing collects on a timer unless this is set to a positive interval, so a long-lived host that runs .NET steps should either set one or call do_dot_net_garbage_collection between runs.

§Errors

Error if the COM call fails or returns an unexpected type.

Source

pub fn set_dot_net_garbage_collection_interval( &self, milliseconds: i32, ) -> Result<(), Error>

Sets the .NET collection interval, in milliseconds (Engine.DotNetGarbageCollectionInterval).

Zero or less switches automatic collection off.

§Errors

Error if the COM call fails.

Source

pub fn dot_net_clr_version(&self) -> Result<String, Error>

The .NET runtime version the engine loaded (Engine.DotNetCLRVersion).

Empty on a station where nothing has pulled the runtime in yet, so treat an empty string as “not loaded” rather than as an error.

§Errors

Error if the COM call fails or returns an unexpected type.

Source

pub fn users_file(&self) -> Result<UsersFile, Error>

The station’s user list, as a file (Engine.UsersFile).

The users the engine loaded at startup, and the only route to writing them back. new_user builds a user in memory; without saving through this file the station is unchanged once the process exits.

§Errors

Error if the COM call fails or returns an unexpected type.

Examples found in repository?
examples/users_manage.rs (line 96)
29fn main() -> Result<(), rs_teststand::Error> {
30    let engine = Engine::new()?;
31
32    // Passing no profile means the user inherits nothing.
33    let user = engine.new_user(None)?;
34    user.set_login_name("operator1")?;
35    user.set_full_name("Test Operator")?;
36    user.set_password("ts-secret")?;
37
38    println!("User: {} ({})", user.login_name()?, user.full_name()?);
39
40    // Check a credential without handling the stored value.
41    println!(
42        "  password 'ts-secret' valid: {}",
43        user.validate_password("ts-secret")?
44    );
45    println!(
46        "  password 'wrong' valid:     {}",
47        user.validate_password("wrong")?
48    );
49
50    println!("  privilege checks:");
51    report_privileges(
52        &user,
53        &[
54            UserPrivilege::Operate,
55            UserPrivilege::Execute,
56            UserPrivilege::Develop,
57            UserPrivilege::Debug,
58            UserPrivilege::EditUsers,
59        ],
60    )?;
61
62    // A privilege can also be named by its full path, which is how a leaf
63    // inside a category is reached.
64    println!(
65        "  Debug.RunSelectedTests:  {}",
66        user.has_privilege_named("Debug.RunSelectedTests")?
67    );
68
69    // The privilege tree is nested: categories hold the individual rights.
70    let privileges = user.privileges()?;
71    let count = privileges.get_num_sub_properties("")?;
72    println!("  privilege tree ({count} categories):");
73    for index in 0..count {
74        let name = privileges.get_nth_sub_property_name("", index, 0)?;
75        let members = privileges
76            .get_property_object(&name, 0)?
77            .get_num_sub_properties("")?;
78        println!("    {name:<12} {members} member(s)");
79    }
80
81    // The station itself, read-only.
82    println!("\nStation:");
83    match engine.current_user()? {
84        Some(current) => println!("  logged in as {}", current.login_name()?),
85        None => println!("  nobody logged in (usual when login is not required)"),
86    }
87    println!(
88        "  is 'operator1' a real account here? {}",
89        engine.user_name_exists("operator1")?
90    );
91
92    // Where accounts actually live. Everything above this point exists only in
93    // memory: `new_user` builds a User, it does not enrol one. A user becomes
94    // part of the station when it is inserted into this file's list and the
95    // file is written.
96    let users_file = engine.users_file()?;
97    let file = users_file.as_property_object_file()?;
98    println!(
99        "
100Users file:"
101    );
102    println!("  path            {}", file.path()?);
103    println!(
104        "  users           {}",
105        users_file.user_list()?.get_num_elements()?
106    );
107    println!(
108        "  groups          {}",
109        users_file.user_group_list()?.get_num_elements()?
110    );
111    println!(
112        "  profiles        {}",
113        users_file.user_profile_list()?.get_num_elements()?
114    );
115
116    // Deliberately not run: it would edit the station this example is reading.
117    // The two calls a host makes to enrol the user built above are
118    //
119    //     users_file.user_list()?.set_num_elements(count + 1, 0)?;   // then fill
120    //     file.save_file_if_modified(false)?;                        // then write
121    //
122    // `false` matters: with `true` the engine puts a save dialog on screen, and
123    // a host with no operator would wait for an answer that never comes.
124    println!(
125        "
126  (this example does not write; enrolling a user would alter the station)"
127    );
128
129    Ok(())
130}
Source

pub fn ui_message_polling_enabled(&self) -> Result<bool, Error>

Whether the host polls for messages (Engine.UIMessagePollingEnabled).

§Errors

Error if the COM call fails or returns an unexpected type.

Source

pub fn set_ui_message_polling_enabled(&self, enabled: bool) -> Result<(), Error>

Turns message polling on or off (Engine.UIMessagePollingEnabled).

Off by default. A headless host must turn it on before anything appears in the queue, without it the queue stays empty however much a sequence posts.

§Errors

Error if the COM call fails.

Examples found in repository?
examples/execution_run_subsequence.rs (line 142)
140fn main() -> Result<(), Box<dyn std::error::Error>> {
141    let engine = Engine::new()?;
142    engine.set_ui_message_polling_enabled(true)?;
143
144    let sequence_file = build(&engine)?;
145    println!("File holds {} sequence(s).", sequence_file.num_sequences()?);
146
147    // The conventional entry point.
148    run(&engine, &sequence_file, "MainSequence")?;
149
150    // A subsequence run on its own has no caller, so nothing supplies its
151    // parameters, they keep whatever default the sequence carries. Setting
152    // that default is therefore how a direct run is given its input.
153    let diagnostics = sequence_file.get_sequence_by_name(SUBSEQUENCE)?;
154    diagnostics
155        .parameters()?
156        .set_val_string(PARAMETER, none(), "FIXTURE-07")?;
157    println!(
158        "\n{SUBSEQUENCE}.{PARAMETER} default is now {:?}",
159        diagnostics
160            .parameters()?
161            .get_val_string(PARAMETER, none())?
162    );
163
164    run(&engine, &sequence_file, SUBSEQUENCE)?;
165
166    engine.release_sequence_file_ex(sequence_file, none())?;
167    Ok(())
168}
More examples
Hide additional examples
examples/execution_run_test_headless.rs (line 150)
146fn main() -> Result<(), Box<dyn std::error::Error>> {
147    let engine = Engine::new()?;
148    // Nothing reaches the queue until this is on, and without the queue there
149    // is no way to know the run ended.
150    engine.set_ui_message_polling_enabled(true)?;
151
152    let sequence_file = engine.new_sequence_file()?;
153    let main_sequence = sequence_file.get_sequence_by_name("MainSequence")?;
154    for (name, data_source, low, high) in TESTS {
155        add_numeric_limit_test(&engine, &main_sequence, name, data_source, low, high)?;
156    }
157
158    let execution = engine.new_execution(&sequence_file, "MainSequence", None, false, 0)?;
159    println!("Running {} headless...", execution.display_name()?);
160
161    if wait_for_end(&engine, RUN_DEADLINE)? {
162        println!("Finished with status: {}", execution.result_status()?);
163        report(&execution.result_object()?)?;
164    } else {
165        // Reported rather than ignored: a host that assumes success here would
166        // publish results from a run that never finished.
167        println!("The run did not finish within {RUN_DEADLINE:?}; terminating.");
168        execution.terminate()?;
169    }
170
171    engine.release_sequence_file_ex(sequence_file, none())?;
172    Ok(())
173}
examples/result_list_parse.rs (line 111)
109fn main() -> Result<(), Box<dyn std::error::Error>> {
110    let engine = Engine::new()?;
111    engine.set_ui_message_polling_enabled(true)?;
112
113    let sequence_file = engine.new_sequence_file()?;
114    let main_sequence = sequence_file.get_sequence_by_name("MainSequence")?;
115
116    add_pass_fail(&engine, &main_sequence, "Verify Power Rail", "True")?;
117    add_unrecorded_filler(&engine, &main_sequence, "Filler (not recorded)")?;
118    add_numeric(
119        &engine,
120        &main_sequence,
121        "Verify Current Draw",
122        "1.5",
123        1.0,
124        2.0,
125    )?;
126    add_pass_fail(&engine, &main_sequence, "Verify Ground Bond", "True")?;
127
128    let step_count = main_sequence.get_num_steps(StepGroup::Main)?;
129    println!("Running {step_count} steps...");
130    let execution = engine.new_execution(&sequence_file, "MainSequence", None, false, 0)?;
131
132    if !wait_for_end(&engine, RUN_DEADLINE)? {
133        println!("The run did not finish within {RUN_DEADLINE:?}; terminating.");
134        execution.terminate()?;
135        return Ok(());
136    }
137    println!("Finished with status: {}\n", execution.result_status()?);
138
139    let results = execution.result_list()?;
140    let parsed = results.parse()?;
141
142    println!("{} of {step_count} steps recorded a result:", parsed.len());
143    for (index, result) in parsed.iter().enumerate() {
144        let measurement = match &result.value {
145            Some(ResultValue::Number(number)) => format!("measured {number}"),
146            Some(ResultValue::Text(text)) => format!("measured {text:?}"),
147            None => "no measurement".to_owned(),
148        };
149        println!(
150            "  [{index}] {} ({}) -> {}, {measurement}",
151            result.name, result.step_type, result.status
152        );
153    }
154
155    // The step whose recording was switched off is absent, by design.
156    println!(
157        "\nThe unrecorded step left no entry, which is why {} < {step_count}.",
158        parsed.len()
159    );
160
161    // The parsed results are plain data, so they outlive the run and can be
162    // handed to anything: a report writer, a database, a serde format.
163    let failures: Vec<&str> = parsed
164        .iter()
165        .filter(|result| result.status == "Failed")
166        .map(|result| result.name.as_str())
167        .collect();
168    if failures.is_empty() {
169        println!("No failures.");
170    } else {
171        println!("Failed steps: {}", failures.join(", "));
172    }
173
174    engine.release_sequence_file_ex(sequence_file, none())?;
175    Ok(())
176}
examples/ui_messages_handle.rs (line 44)
40fn main() -> Result<(), Box<dyn std::error::Error>> {
41    let engine = Engine::new()?;
42
43    // Nothing reaches the queue until polling is switched on.
44    engine.set_ui_message_polling_enabled(true)?;
45
46    let sequence_file = engine.new_sequence_file()?;
47    let main_sequence = sequence_file.get_sequence_by_name("MainSequence")?;
48
49    // Posted through the engine and tagged with the execution. Synchronous, so
50    // the sequence waits until the host acknowledges it.
51    add_statement(
52        &engine,
53        &main_sequence,
54        "Report Stage",
55        &format!(
56            "RunState.Engine.PostUIMessage(RunState.Execution, RunState.Thread, {STAGE_MESSAGE}, \
57             1, \"stage: configuring instruments\", Nothing, True)"
58        ),
59    )?;
60    // Posted through the thread. Asynchronous, so the sequence carries on.
61    add_statement(
62        &engine,
63        &main_sequence,
64        "Report Progress",
65        &format!(
66            "RunState.Thread.PostUIMessageEx({PROGRESS_MESSAGE}, 50, \"progress: halfway\", \
67             Nothing, False)"
68        ),
69    )?;
70
71    let execution = engine.new_execution(&sequence_file, "MainSequence", None, false, 0)?;
72    println!(
73        "Execution {} started; polling for messages...",
74        execution.id()?
75    );
76
77    // The end is detected from the message stream, not by waiting on the
78    // execution: a wait call does not pump the queue, so a synchronous message
79    // would sit unacknowledged and both sides would stop.
80    let started = Instant::now();
81    let mut ended = false;
82    while started.elapsed() < Duration::from_secs(60) && !ended {
83        while !engine.is_ui_message_queue_empty()? {
84            let message = engine.get_ui_message()?;
85            let code = message.event()?;
86            if matches!(
87                UIMessageCode::from_bits(code),
88                Ok(UIMessageCode::EndExecution)
89            ) {
90                ended = true;
91            }
92            let origin = if UIMessageCode::is_user_message(code) {
93                "sequence".to_owned()
94            } else {
95                format!("engine {:?}", UIMessageCode::from_bits(code))
96            };
97            println!(
98                "  [{origin}] code={code} numeric={} string={:?} synchronous={}",
99                message.numeric_data()?,
100                message.string_data()?,
101                message.is_synchronous()?
102            );
103            // Required: releases a synchronous poster and asks for the next.
104            message.acknowledge()?;
105        }
106    }
107
108    println!("\nExecution ended: {ended}");
109    engine.release_sequence_file_ex(sequence_file, NO_OPTIONS)?;
110    Ok(())
111}
Source

pub fn is_ui_message_queue_empty(&self) -> Result<bool, Error>

Whether the message queue is empty (Engine.IsUIMessageQueueEmpty).

§Errors

Error if the COM call fails or returns an unexpected type.

Examples found in repository?
examples/execution_run_subsequence.rs (line 92)
86fn wait_for_end(engine: &Engine, deadline: Duration) -> Result<bool, Error> {
87    let started = Instant::now();
88    while started.elapsed() < deadline {
89        if pump_thread_messages() {
90            return Ok(false);
91        }
92        while !engine.is_ui_message_queue_empty()? {
93            let message = engine.get_ui_message()?;
94            let ended = matches!(
95                UIMessageCode::from_bits(message.event()?),
96                Ok(UIMessageCode::EndExecution)
97            );
98            message.acknowledge()?;
99            if ended {
100                return Ok(true);
101            }
102        }
103    }
104    Ok(false)
105}
More examples
Hide additional examples
examples/result_list_parse.rs (line 94)
88fn wait_for_end(engine: &Engine, deadline: Duration) -> Result<bool, Error> {
89    let started = Instant::now();
90    while started.elapsed() < deadline {
91        if pump_thread_messages() {
92            return Ok(false);
93        }
94        while !engine.is_ui_message_queue_empty()? {
95            let message = engine.get_ui_message()?;
96            let ended = matches!(
97                UIMessageCode::from_bits(message.event()?),
98                Ok(UIMessageCode::EndExecution)
99            );
100            message.acknowledge()?;
101            if ended {
102                return Ok(true);
103            }
104        }
105    }
106    Ok(false)
107}
examples/execution_run_test_headless.rs (line 96)
90fn wait_for_end(engine: &Engine, deadline: Duration) -> Result<bool, Error> {
91    let started = Instant::now();
92    while started.elapsed() < deadline {
93        if pump_thread_messages() {
94            return Ok(false);
95        }
96        while !engine.is_ui_message_queue_empty()? {
97            let message = engine.get_ui_message()?;
98            let ended = matches!(
99                UIMessageCode::from_bits(message.event()?),
100                Ok(UIMessageCode::EndExecution)
101            );
102            // Acknowledging is what releases a synchronous poster; skipping it
103            // stalls the sequence rather than merely losing a notification.
104            message.acknowledge()?;
105            if ended {
106                return Ok(true);
107            }
108        }
109    }
110    Ok(false)
111}
examples/ui_messages_handle.rs (line 83)
40fn main() -> Result<(), Box<dyn std::error::Error>> {
41    let engine = Engine::new()?;
42
43    // Nothing reaches the queue until polling is switched on.
44    engine.set_ui_message_polling_enabled(true)?;
45
46    let sequence_file = engine.new_sequence_file()?;
47    let main_sequence = sequence_file.get_sequence_by_name("MainSequence")?;
48
49    // Posted through the engine and tagged with the execution. Synchronous, so
50    // the sequence waits until the host acknowledges it.
51    add_statement(
52        &engine,
53        &main_sequence,
54        "Report Stage",
55        &format!(
56            "RunState.Engine.PostUIMessage(RunState.Execution, RunState.Thread, {STAGE_MESSAGE}, \
57             1, \"stage: configuring instruments\", Nothing, True)"
58        ),
59    )?;
60    // Posted through the thread. Asynchronous, so the sequence carries on.
61    add_statement(
62        &engine,
63        &main_sequence,
64        "Report Progress",
65        &format!(
66            "RunState.Thread.PostUIMessageEx({PROGRESS_MESSAGE}, 50, \"progress: halfway\", \
67             Nothing, False)"
68        ),
69    )?;
70
71    let execution = engine.new_execution(&sequence_file, "MainSequence", None, false, 0)?;
72    println!(
73        "Execution {} started; polling for messages...",
74        execution.id()?
75    );
76
77    // The end is detected from the message stream, not by waiting on the
78    // execution: a wait call does not pump the queue, so a synchronous message
79    // would sit unacknowledged and both sides would stop.
80    let started = Instant::now();
81    let mut ended = false;
82    while started.elapsed() < Duration::from_secs(60) && !ended {
83        while !engine.is_ui_message_queue_empty()? {
84            let message = engine.get_ui_message()?;
85            let code = message.event()?;
86            if matches!(
87                UIMessageCode::from_bits(code),
88                Ok(UIMessageCode::EndExecution)
89            ) {
90                ended = true;
91            }
92            let origin = if UIMessageCode::is_user_message(code) {
93                "sequence".to_owned()
94            } else {
95                format!("engine {:?}", UIMessageCode::from_bits(code))
96            };
97            println!(
98                "  [{origin}] code={code} numeric={} string={:?} synchronous={}",
99                message.numeric_data()?,
100                message.string_data()?,
101                message.is_synchronous()?
102            );
103            // Required: releases a synchronous poster and asks for the next.
104            message.acknowledge()?;
105        }
106    }
107
108    println!("\nExecution ended: {ended}");
109    engine.release_sequence_file_ex(sequence_file, NO_OPTIONS)?;
110    Ok(())
111}
Source

pub fn get_ui_message(&self) -> Result<UIMessage, Error>

Takes the next message from the queue (Engine.GetUIMessage).

Check is_ui_message_queue_empty first. The message must be acknowledged once handled, see UIMessage::acknowledge.

§Errors

Error if the COM call fails or returns an unexpected type.

Examples found in repository?
examples/execution_run_subsequence.rs (line 93)
86fn wait_for_end(engine: &Engine, deadline: Duration) -> Result<bool, Error> {
87    let started = Instant::now();
88    while started.elapsed() < deadline {
89        if pump_thread_messages() {
90            return Ok(false);
91        }
92        while !engine.is_ui_message_queue_empty()? {
93            let message = engine.get_ui_message()?;
94            let ended = matches!(
95                UIMessageCode::from_bits(message.event()?),
96                Ok(UIMessageCode::EndExecution)
97            );
98            message.acknowledge()?;
99            if ended {
100                return Ok(true);
101            }
102        }
103    }
104    Ok(false)
105}
More examples
Hide additional examples
examples/result_list_parse.rs (line 95)
88fn wait_for_end(engine: &Engine, deadline: Duration) -> Result<bool, Error> {
89    let started = Instant::now();
90    while started.elapsed() < deadline {
91        if pump_thread_messages() {
92            return Ok(false);
93        }
94        while !engine.is_ui_message_queue_empty()? {
95            let message = engine.get_ui_message()?;
96            let ended = matches!(
97                UIMessageCode::from_bits(message.event()?),
98                Ok(UIMessageCode::EndExecution)
99            );
100            message.acknowledge()?;
101            if ended {
102                return Ok(true);
103            }
104        }
105    }
106    Ok(false)
107}
examples/execution_run_test_headless.rs (line 97)
90fn wait_for_end(engine: &Engine, deadline: Duration) -> Result<bool, Error> {
91    let started = Instant::now();
92    while started.elapsed() < deadline {
93        if pump_thread_messages() {
94            return Ok(false);
95        }
96        while !engine.is_ui_message_queue_empty()? {
97            let message = engine.get_ui_message()?;
98            let ended = matches!(
99                UIMessageCode::from_bits(message.event()?),
100                Ok(UIMessageCode::EndExecution)
101            );
102            // Acknowledging is what releases a synchronous poster; skipping it
103            // stalls the sequence rather than merely losing a notification.
104            message.acknowledge()?;
105            if ended {
106                return Ok(true);
107            }
108        }
109    }
110    Ok(false)
111}
examples/ui_messages_handle.rs (line 84)
40fn main() -> Result<(), Box<dyn std::error::Error>> {
41    let engine = Engine::new()?;
42
43    // Nothing reaches the queue until polling is switched on.
44    engine.set_ui_message_polling_enabled(true)?;
45
46    let sequence_file = engine.new_sequence_file()?;
47    let main_sequence = sequence_file.get_sequence_by_name("MainSequence")?;
48
49    // Posted through the engine and tagged with the execution. Synchronous, so
50    // the sequence waits until the host acknowledges it.
51    add_statement(
52        &engine,
53        &main_sequence,
54        "Report Stage",
55        &format!(
56            "RunState.Engine.PostUIMessage(RunState.Execution, RunState.Thread, {STAGE_MESSAGE}, \
57             1, \"stage: configuring instruments\", Nothing, True)"
58        ),
59    )?;
60    // Posted through the thread. Asynchronous, so the sequence carries on.
61    add_statement(
62        &engine,
63        &main_sequence,
64        "Report Progress",
65        &format!(
66            "RunState.Thread.PostUIMessageEx({PROGRESS_MESSAGE}, 50, \"progress: halfway\", \
67             Nothing, False)"
68        ),
69    )?;
70
71    let execution = engine.new_execution(&sequence_file, "MainSequence", None, false, 0)?;
72    println!(
73        "Execution {} started; polling for messages...",
74        execution.id()?
75    );
76
77    // The end is detected from the message stream, not by waiting on the
78    // execution: a wait call does not pump the queue, so a synchronous message
79    // would sit unacknowledged and both sides would stop.
80    let started = Instant::now();
81    let mut ended = false;
82    while started.elapsed() < Duration::from_secs(60) && !ended {
83        while !engine.is_ui_message_queue_empty()? {
84            let message = engine.get_ui_message()?;
85            let code = message.event()?;
86            if matches!(
87                UIMessageCode::from_bits(code),
88                Ok(UIMessageCode::EndExecution)
89            ) {
90                ended = true;
91            }
92            let origin = if UIMessageCode::is_user_message(code) {
93                "sequence".to_owned()
94            } else {
95                format!("engine {:?}", UIMessageCode::from_bits(code))
96            };
97            println!(
98                "  [{origin}] code={code} numeric={} string={:?} synchronous={}",
99                message.numeric_data()?,
100                message.string_data()?,
101                message.is_synchronous()?
102            );
103            // Required: releases a synchronous poster and asks for the next.
104            message.acknowledge()?;
105        }
106    }
107
108    println!("\nExecution ended: {ended}");
109    engine.release_sequence_file_ex(sequence_file, NO_OPTIONS)?;
110    Ok(())
111}
Source

pub fn new_step( &self, adapter_key_name: &str, step_type_name: &str, ) -> Result<Step, Error>

Creates a step (Engine.NewStep).

adapter_key_name selects the code-module adapter, see AdapterKeyName. step_type_name names the step type, for example NumericLimitTest or Action.

An empty key does not mean “no code module”. It means the step type chooses, falling back to the station’s DefaultAdapter when the type designates none, so an empty key on an Action yields whatever adapter the station happens to default to. Pass AdapterKeyName::NoneAdapter to actually mean no code module.

The step is not part of any sequence until it is inserted.

§Errors

Error if the step type is unknown or the COM call fails.

Examples found in repository?
examples/template_manage_complex.rs (line 103)
102fn step_template(engine: &Engine) -> Result<PropertyObject, Error> {
103    let step = engine.new_step(NO_ADAPTER, "Statement")?;
104    step.set_name(STEP_TEMPLATE)?;
105    step.set_post_expression(r#"Locals.Result = "Hello from step template!""#)?;
106    step.as_property_object()
107}
108
109/// A whole sequence, one step included, as a single prototype.
110fn sequence_template(engine: &Engine) -> Result<PropertyObject, Error> {
111    let sequence = engine.new_sequence()?;
112    sequence.set_name(SEQUENCE_TEMPLATE)?;
113
114    let step = engine.new_step(NO_ADAPTER, "Statement")?;
115    step.set_name("Inside_Sequence_Template")?;
116    sequence.insert_step(&step, 0, StepGroup::Main)?;
117
118    sequence.as_property_object()
119}
More examples
Hide additional examples
examples/result_list_parse.rs (line 42)
36fn add_pass_fail(
37    engine: &Engine,
38    sequence: &Sequence,
39    name: &str,
40    source: &str,
41) -> Result<(), Error> {
42    let step = engine.new_step(AdapterKeyName::NoneAdapter.as_str(), "PassFailTest")?;
43    step.set_name(name)?;
44    step.as_property_object()?
45        .set_val_string("DataSource", insert_if_missing(), source)?;
46    sequence.insert_step(
47        &step,
48        sequence.get_num_steps(StepGroup::Main)?,
49        StepGroup::Main,
50    )
51}
52
53/// Adds a numeric limit test with a fixed measurement.
54fn add_numeric(
55    engine: &Engine,
56    sequence: &Sequence,
57    name: &str,
58    source: &str,
59    low: f64,
60    high: f64,
61) -> Result<(), Error> {
62    let step = engine.new_step(AdapterKeyName::NoneAdapter.as_str(), "NumericLimitTest")?;
63    step.set_name(name)?;
64    let properties = step.as_property_object()?;
65    properties.set_val_string("DataSource", insert_if_missing(), source)?;
66    properties.set_val_number("Limits.Low", insert_if_missing(), low)?;
67    properties.set_val_number("Limits.High", insert_if_missing(), high)?;
68    sequence.insert_step(
69        &step,
70        sequence.get_num_steps(StepGroup::Main)?,
71        StepGroup::Main,
72    )
73}
74
75/// Adds a statement step that records nothing, to show the gap it leaves.
76fn add_unrecorded_filler(engine: &Engine, sequence: &Sequence, name: &str) -> Result<(), Error> {
77    let step = engine.new_step(AdapterKeyName::NoneAdapter.as_str(), "Statement")?;
78    step.set_name(name)?;
79    step.set_result_recording_option(ResultRecordingOption::Disabled)?;
80    sequence.insert_step(
81        &step,
82        sequence.get_num_steps(StepGroup::Main)?,
83        StepGroup::Main,
84    )
85}
examples/ui_messages_handle.rs (line 28)
22fn add_statement(
23    engine: &Engine,
24    sequence: &Sequence,
25    name: &str,
26    expression: &str,
27) -> Result<(), rs_teststand::Error> {
28    let step = engine.new_step(NO_ADAPTER, "Statement")?;
29    step.set_name(name)?;
30    step.as_property_object()?
31        .set_val_string("TS.PostExpr", INSERT_IF_MISSING, expression)?;
32    sequence.insert_step(
33        &step,
34        sequence.get_num_steps(StepGroup::Main)?,
35        StepGroup::Main,
36    )?;
37    Ok(())
38}
examples/sequence_build.rs (line 35)
28fn numeric_limit_test(
29    engine: &Engine,
30    name: &str,
31    precondition: &str,
32    low: f64,
33    high: f64,
34) -> Result<Step, rs_teststand::Error> {
35    let step = engine.new_step(NO_ADAPTER, "NumericLimitTest")?;
36    step.set_name(name)?;
37    step.set_precondition(precondition)?;
38    step.set_record_result(true)?;
39
40    let properties = step.as_property_object()?;
41    properties.set_val_number("Limits.High", INSERT_IF_MISSING, high)?;
42    properties.set_val_number("Limits.Low", INSERT_IF_MISSING, low)?;
43    Ok(step)
44}
45
46fn describe(sequence: &Sequence) -> Result<(), rs_teststand::Error> {
47    let count = sequence.get_num_steps(StepGroup::Main)?;
48    println!("{} holds {count} step(s) in Main:", sequence.name()?);
49    for index in 0..count {
50        let step = sequence.get_step(index, StepGroup::Main)?;
51        let properties = step.as_property_object()?;
52        println!("  [{index}] {}", step.name()?);
53        if properties.exists("Limits.Low", NO_OPTIONS)? {
54            println!(
55                "      limits: low={}, high={}",
56                properties.get_val_number("Limits.Low", NO_OPTIONS)?,
57                properties.get_val_number("Limits.High", NO_OPTIONS)?
58            );
59        }
60        let precondition = step.precondition()?;
61        if !precondition.is_empty() {
62            println!("      runs when: {precondition}");
63        }
64    }
65    Ok(())
66}
67
68fn main() -> Result<(), Box<dyn std::error::Error>> {
69    let engine = Engine::new()?;
70    let sequence_file = engine.new_sequence_file()?;
71    let main_sequence = sequence_file.get_sequence_by_name("MainSequence")?;
72
73    // Steps are placed by group and index, so order is explicit.
74    main_sequence.insert_step(
75        &numeric_limit_test(
76            &engine,
77            "Temperature Check",
78            "Locals.TempSensorPresent == True",
79            15.0,
80            85.0,
81        )?,
82        0,
83        StepGroup::Main,
84    )?;
85    main_sequence.insert_step(
86        &numeric_limit_test(
87            &engine,
88            "Voltage Monitor",
89            "Locals.DUTPowered == True",
90            4.75,
91            5.25,
92        )?,
93        1,
94        StepGroup::Main,
95    )?;
96
97    // A subsequence, so a later example has something to call.
98    let subsequence = engine.new_sequence()?;
99    subsequence.set_name("CustomSubsequence")?;
100    sequence_file.insert_sequence(&subsequence)?;
101
102    let init_step = engine.new_step(NO_ADAPTER, "Action")?;
103    init_step.set_name("Initialize Hardware")?;
104    subsequence.insert_step(&init_step, 0, StepGroup::Main)?;
105
106    describe(&main_sequence)?;
107    println!();
108    describe(&subsequence)?;
109
110    // Cleanup runs even when Main fails, which is why it is worth showing.
111    println!(
112        "\nGroup sizes in MainSequence: setup={}, main={}, cleanup={}",
113        main_sequence.get_num_steps(StepGroup::Setup)?,
114        main_sequence.get_num_steps(StepGroup::Main)?,
115        main_sequence.get_num_steps(StepGroup::Cleanup)?
116    );
117
118    let path = std::env::temp_dir().join("rs_teststand_built_sequence.seq");
119    sequence_file.save(&path.to_string_lossy())?;
120    println!("\nSaved to {}", path.display());
121
122    engine.release_sequence_file_ex(sequence_file, NO_OPTIONS)?;
123    Ok(())
124}
examples/step_insert_from_template.rs (line 54)
51fn build_prototype(engine: &Engine) -> Result<Step, Error> {
52    // The maintained LabVIEW adapter. The standard-prototype key still exists
53    // but the documentation marks it obsolete in favour of this one.
54    let step = engine.new_step(AdapterKeyName::LabView.as_str(), "Action")?;
55    step.set_name("Measure (VI)")?;
56    step.set_run_mode(RunMode::Normal)?;
57    step.as_property_object()?.set_val_string(
58        VI_PATH,
59        PropertyOptions::INSERT_IF_MISSING.bits(),
60        r"example.lvlibp\measure.vi",
61    )?;
62    Ok(step)
63}
examples/execution_run_subsequence.rs (line 48)
44fn add_action(engine: &Engine, sequence: &Sequence, name: &str) -> Result<(), Error> {
45    // The None adapter is what actually means "no code module"; an empty key
46    // would let the step type pick, and a step on a real adapter fails at run
47    // time with "module has not yet been specified".
48    let step = engine.new_step(AdapterKeyName::NoneAdapter.as_str(), "Action")?;
49    step.set_name(name)?;
50    step.set_record_result(true)?;
51    sequence.insert_step(
52        &step,
53        sequence.get_num_steps(StepGroup::Main)?,
54        StepGroup::Main,
55    )
56}
Source

pub fn new_sequence(&self) -> Result<Sequence, Error>

Creates a sequence (Engine.NewSequence).

The sequence is not part of any file until it is inserted.

§Errors

Error if the COM call fails or returns an unexpected type.

Examples found in repository?
examples/template_manage_complex.rs (line 111)
110fn sequence_template(engine: &Engine) -> Result<PropertyObject, Error> {
111    let sequence = engine.new_sequence()?;
112    sequence.set_name(SEQUENCE_TEMPLATE)?;
113
114    let step = engine.new_step(NO_ADAPTER, "Statement")?;
115    step.set_name("Inside_Sequence_Template")?;
116    sequence.insert_step(&step, 0, StepGroup::Main)?;
117
118    sequence.as_property_object()
119}
More examples
Hide additional examples
examples/execution_run_subsequence.rs (line 67)
59fn build(engine: &Engine) -> Result<SequenceFile, Error> {
60    let sequence_file = engine.new_sequence_file()?;
61
62    let main_sequence = sequence_file.get_sequence_by_name("MainSequence")?;
63    for name in ["Initialize", "Run Test", "Shut Down"] {
64        add_action(engine, &main_sequence, name)?;
65    }
66
67    let diagnostics = engine.new_sequence()?;
68    diagnostics.set_name(SUBSEQUENCE)?;
69    // A parameter is an ordinary property in the sequence's Parameters scope.
70    diagnostics.parameters()?.new_sub_property(
71        PARAMETER,
72        PropValType::String,
73        false,
74        "",
75        insert_if_missing(),
76    )?;
77    for name in ["Check Power Rails", "Check Clock"] {
78        add_action(engine, &diagnostics, name)?;
79    }
80    sequence_file.insert_sequence(&diagnostics)?;
81
82    Ok(sequence_file)
83}
examples/sequence_build.rs (line 98)
68fn main() -> Result<(), Box<dyn std::error::Error>> {
69    let engine = Engine::new()?;
70    let sequence_file = engine.new_sequence_file()?;
71    let main_sequence = sequence_file.get_sequence_by_name("MainSequence")?;
72
73    // Steps are placed by group and index, so order is explicit.
74    main_sequence.insert_step(
75        &numeric_limit_test(
76            &engine,
77            "Temperature Check",
78            "Locals.TempSensorPresent == True",
79            15.0,
80            85.0,
81        )?,
82        0,
83        StepGroup::Main,
84    )?;
85    main_sequence.insert_step(
86        &numeric_limit_test(
87            &engine,
88            "Voltage Monitor",
89            "Locals.DUTPowered == True",
90            4.75,
91            5.25,
92        )?,
93        1,
94        StepGroup::Main,
95    )?;
96
97    // A subsequence, so a later example has something to call.
98    let subsequence = engine.new_sequence()?;
99    subsequence.set_name("CustomSubsequence")?;
100    sequence_file.insert_sequence(&subsequence)?;
101
102    let init_step = engine.new_step(NO_ADAPTER, "Action")?;
103    init_step.set_name("Initialize Hardware")?;
104    subsequence.insert_step(&init_step, 0, StepGroup::Main)?;
105
106    describe(&main_sequence)?;
107    println!();
108    describe(&subsequence)?;
109
110    // Cleanup runs even when Main fails, which is why it is worth showing.
111    println!(
112        "\nGroup sizes in MainSequence: setup={}, main={}, cleanup={}",
113        main_sequence.get_num_steps(StepGroup::Setup)?,
114        main_sequence.get_num_steps(StepGroup::Main)?,
115        main_sequence.get_num_steps(StepGroup::Cleanup)?
116    );
117
118    let path = std::env::temp_dir().join("rs_teststand_built_sequence.seq");
119    sequence_file.save(&path.to_string_lossy())?;
120    println!("\nSaved to {}", path.display());
121
122    engine.release_sequence_file_ex(sequence_file, NO_OPTIONS)?;
123    Ok(())
124}
examples/step_insert.rs (line 79)
73fn main() -> Result<(), Box<dyn std::error::Error>> {
74    let engine = Engine::new()?;
75
76    // A file to insert into. Built here so the example depends on nothing that
77    // has to exist on the station first.
78    let sequence_file = engine.new_sequence_file()?;
79    let subsequence = engine.new_sequence()?;
80    subsequence.set_name("CustomSubsequence")?;
81    let existing = engine.new_step(AdapterKeyName::NoneAdapter.as_str(), "Action")?;
82    existing.set_name(TARGET_STEP)?;
83    subsequence.insert_step(&existing, 0, StepGroup::Main)?;
84    sequence_file.insert_sequence(&subsequence)?;
85
86    // Where to insert: in front of the target, or at the end if it is absent.
87    let insert_at = if let Some(index) = index_of(&subsequence, TARGET_STEP, StepGroup::Main)? {
88        println!("Found {TARGET_STEP} at index {index}; inserting in front of it.");
89        index
90    } else {
91        let end = subsequence.get_num_steps(StepGroup::Main)?;
92        println!("{TARGET_STEP} not found; appending at index {end}.");
93        end
94    };
95
96    for (offset, (name, vi, project, mode)) in [
97        (
98            "Measure Voltage (VI)",
99            r"instruments.lvlibp\measure_voltage.vi",
100            "",
101            RunMode::Normal,
102        ),
103        (
104            "Measure Current (VI)",
105            r"instruments.lvlibp\measure_current.vi",
106            r"instruments.lvproj",
107            RunMode::Skip,
108        ),
109    ]
110    .into_iter()
111    .enumerate()
112    {
113        let step = vi_call_step(&engine, name, vi, project, mode)?;
114        let at = insert_at + i32::try_from(offset).unwrap_or(0);
115        subsequence.insert_step(&step, at, StepGroup::Main)?;
116    }
117
118    println!(
119        "\n{} now holds {} step(s):",
120        subsequence.name()?,
121        subsequence.get_num_steps(StepGroup::Main)?
122    );
123    for index in 0..subsequence.get_num_steps(StepGroup::Main)? {
124        let step = subsequence.get_step(index, StepGroup::Main)?;
125        let properties = step.as_property_object()?;
126        let vi = properties
127            .get_val_string(VI_PATH, none())
128            .unwrap_or_else(|_| "<no VI>".to_owned());
129        println!(
130            "  [{index}] {} - {:?}, run mode {:?}, records {:?}",
131            step.name()?,
132            step.adapter_key_name()?,
133            step.run_mode()?,
134            step.result_recording_option()?
135        );
136        if vi != "<no VI>" {
137            println!("        calls {vi}");
138        }
139    }
140
141    let path = std::env::temp_dir().join("rs_teststand_step_insert.seq");
142    let path = path.to_string_lossy().into_owned();
143    sequence_file.save(&path)?;
144    println!("\nSaved to {path}");
145    Ok(())
146}
Source

pub fn new_user(&self, profile: Option<&User>) -> Result<User, Error>

Creates a user account object (Engine.NewUser).

Pass an existing user as profile to inherit its privileges; the new user does not join any group the profile belongs to. Pass None for a user with no privileges.

The result exists only in memory, nothing is written to the station’s users file by creating one.

§Errors

Error if the COM call fails or returns an unexpected type.

Examples found in repository?
examples/users_manage.rs (line 33)
29fn main() -> Result<(), rs_teststand::Error> {
30    let engine = Engine::new()?;
31
32    // Passing no profile means the user inherits nothing.
33    let user = engine.new_user(None)?;
34    user.set_login_name("operator1")?;
35    user.set_full_name("Test Operator")?;
36    user.set_password("ts-secret")?;
37
38    println!("User: {} ({})", user.login_name()?, user.full_name()?);
39
40    // Check a credential without handling the stored value.
41    println!(
42        "  password 'ts-secret' valid: {}",
43        user.validate_password("ts-secret")?
44    );
45    println!(
46        "  password 'wrong' valid:     {}",
47        user.validate_password("wrong")?
48    );
49
50    println!("  privilege checks:");
51    report_privileges(
52        &user,
53        &[
54            UserPrivilege::Operate,
55            UserPrivilege::Execute,
56            UserPrivilege::Develop,
57            UserPrivilege::Debug,
58            UserPrivilege::EditUsers,
59        ],
60    )?;
61
62    // A privilege can also be named by its full path, which is how a leaf
63    // inside a category is reached.
64    println!(
65        "  Debug.RunSelectedTests:  {}",
66        user.has_privilege_named("Debug.RunSelectedTests")?
67    );
68
69    // The privilege tree is nested: categories hold the individual rights.
70    let privileges = user.privileges()?;
71    let count = privileges.get_num_sub_properties("")?;
72    println!("  privilege tree ({count} categories):");
73    for index in 0..count {
74        let name = privileges.get_nth_sub_property_name("", index, 0)?;
75        let members = privileges
76            .get_property_object(&name, 0)?
77            .get_num_sub_properties("")?;
78        println!("    {name:<12} {members} member(s)");
79    }
80
81    // The station itself, read-only.
82    println!("\nStation:");
83    match engine.current_user()? {
84        Some(current) => println!("  logged in as {}", current.login_name()?),
85        None => println!("  nobody logged in (usual when login is not required)"),
86    }
87    println!(
88        "  is 'operator1' a real account here? {}",
89        engine.user_name_exists("operator1")?
90    );
91
92    // Where accounts actually live. Everything above this point exists only in
93    // memory: `new_user` builds a User, it does not enrol one. A user becomes
94    // part of the station when it is inserted into this file's list and the
95    // file is written.
96    let users_file = engine.users_file()?;
97    let file = users_file.as_property_object_file()?;
98    println!(
99        "
100Users file:"
101    );
102    println!("  path            {}", file.path()?);
103    println!(
104        "  users           {}",
105        users_file.user_list()?.get_num_elements()?
106    );
107    println!(
108        "  groups          {}",
109        users_file.user_group_list()?.get_num_elements()?
110    );
111    println!(
112        "  profiles        {}",
113        users_file.user_profile_list()?.get_num_elements()?
114    );
115
116    // Deliberately not run: it would edit the station this example is reading.
117    // The two calls a host makes to enrol the user built above are
118    //
119    //     users_file.user_list()?.set_num_elements(count + 1, 0)?;   // then fill
120    //     file.save_file_if_modified(false)?;                        // then write
121    //
122    // `false` matters: with `true` the engine puts a save dialog on screen, and
123    // a host with no operator would wait for an answer that never comes.
124    println!(
125        "
126  (this example does not write; enrolling a user would alter the station)"
127    );
128
129    Ok(())
130}
Source

pub fn get_user(&self, login_name: &str) -> Result<Option<User>, Error>

Finds a user by login name (Engine.GetUser).

Returns None when no user has that name, rather than erroring.

§Errors

Error if the COM call fails or returns an unexpected type.

Source

pub fn user_name_exists(&self, login_name: &str) -> Result<bool, Error>

Whether a login name is already taken (Engine.UserNameExists).

§Errors

Error if the COM call fails or returns an unexpected type.

Examples found in repository?
examples/users_manage.rs (line 89)
29fn main() -> Result<(), rs_teststand::Error> {
30    let engine = Engine::new()?;
31
32    // Passing no profile means the user inherits nothing.
33    let user = engine.new_user(None)?;
34    user.set_login_name("operator1")?;
35    user.set_full_name("Test Operator")?;
36    user.set_password("ts-secret")?;
37
38    println!("User: {} ({})", user.login_name()?, user.full_name()?);
39
40    // Check a credential without handling the stored value.
41    println!(
42        "  password 'ts-secret' valid: {}",
43        user.validate_password("ts-secret")?
44    );
45    println!(
46        "  password 'wrong' valid:     {}",
47        user.validate_password("wrong")?
48    );
49
50    println!("  privilege checks:");
51    report_privileges(
52        &user,
53        &[
54            UserPrivilege::Operate,
55            UserPrivilege::Execute,
56            UserPrivilege::Develop,
57            UserPrivilege::Debug,
58            UserPrivilege::EditUsers,
59        ],
60    )?;
61
62    // A privilege can also be named by its full path, which is how a leaf
63    // inside a category is reached.
64    println!(
65        "  Debug.RunSelectedTests:  {}",
66        user.has_privilege_named("Debug.RunSelectedTests")?
67    );
68
69    // The privilege tree is nested: categories hold the individual rights.
70    let privileges = user.privileges()?;
71    let count = privileges.get_num_sub_properties("")?;
72    println!("  privilege tree ({count} categories):");
73    for index in 0..count {
74        let name = privileges.get_nth_sub_property_name("", index, 0)?;
75        let members = privileges
76            .get_property_object(&name, 0)?
77            .get_num_sub_properties("")?;
78        println!("    {name:<12} {members} member(s)");
79    }
80
81    // The station itself, read-only.
82    println!("\nStation:");
83    match engine.current_user()? {
84        Some(current) => println!("  logged in as {}", current.login_name()?),
85        None => println!("  nobody logged in (usual when login is not required)"),
86    }
87    println!(
88        "  is 'operator1' a real account here? {}",
89        engine.user_name_exists("operator1")?
90    );
91
92    // Where accounts actually live. Everything above this point exists only in
93    // memory: `new_user` builds a User, it does not enrol one. A user becomes
94    // part of the station when it is inserted into this file's list and the
95    // file is written.
96    let users_file = engine.users_file()?;
97    let file = users_file.as_property_object_file()?;
98    println!(
99        "
100Users file:"
101    );
102    println!("  path            {}", file.path()?);
103    println!(
104        "  users           {}",
105        users_file.user_list()?.get_num_elements()?
106    );
107    println!(
108        "  groups          {}",
109        users_file.user_group_list()?.get_num_elements()?
110    );
111    println!(
112        "  profiles        {}",
113        users_file.user_profile_list()?.get_num_elements()?
114    );
115
116    // Deliberately not run: it would edit the station this example is reading.
117    // The two calls a host makes to enrol the user built above are
118    //
119    //     users_file.user_list()?.set_num_elements(count + 1, 0)?;   // then fill
120    //     file.save_file_if_modified(false)?;                        // then write
121    //
122    // `false` matters: with `true` the engine puts a save dialog on screen, and
123    // a host with no operator would wait for an answer that never comes.
124    println!(
125        "
126  (this example does not write; enrolling a user would alter the station)"
127    );
128
129    Ok(())
130}
Source

pub fn current_user(&self) -> Result<Option<User>, Error>

The user currently logged in (Engine.CurrentUser).

Returns None when nobody is logged in, which is the normal state on a station that does not require a login.

§Errors

Error if the COM call fails or returns an unexpected type.

Examples found in repository?
examples/users_manage.rs (line 83)
29fn main() -> Result<(), rs_teststand::Error> {
30    let engine = Engine::new()?;
31
32    // Passing no profile means the user inherits nothing.
33    let user = engine.new_user(None)?;
34    user.set_login_name("operator1")?;
35    user.set_full_name("Test Operator")?;
36    user.set_password("ts-secret")?;
37
38    println!("User: {} ({})", user.login_name()?, user.full_name()?);
39
40    // Check a credential without handling the stored value.
41    println!(
42        "  password 'ts-secret' valid: {}",
43        user.validate_password("ts-secret")?
44    );
45    println!(
46        "  password 'wrong' valid:     {}",
47        user.validate_password("wrong")?
48    );
49
50    println!("  privilege checks:");
51    report_privileges(
52        &user,
53        &[
54            UserPrivilege::Operate,
55            UserPrivilege::Execute,
56            UserPrivilege::Develop,
57            UserPrivilege::Debug,
58            UserPrivilege::EditUsers,
59        ],
60    )?;
61
62    // A privilege can also be named by its full path, which is how a leaf
63    // inside a category is reached.
64    println!(
65        "  Debug.RunSelectedTests:  {}",
66        user.has_privilege_named("Debug.RunSelectedTests")?
67    );
68
69    // The privilege tree is nested: categories hold the individual rights.
70    let privileges = user.privileges()?;
71    let count = privileges.get_num_sub_properties("")?;
72    println!("  privilege tree ({count} categories):");
73    for index in 0..count {
74        let name = privileges.get_nth_sub_property_name("", index, 0)?;
75        let members = privileges
76            .get_property_object(&name, 0)?
77            .get_num_sub_properties("")?;
78        println!("    {name:<12} {members} member(s)");
79    }
80
81    // The station itself, read-only.
82    println!("\nStation:");
83    match engine.current_user()? {
84        Some(current) => println!("  logged in as {}", current.login_name()?),
85        None => println!("  nobody logged in (usual when login is not required)"),
86    }
87    println!(
88        "  is 'operator1' a real account here? {}",
89        engine.user_name_exists("operator1")?
90    );
91
92    // Where accounts actually live. Everything above this point exists only in
93    // memory: `new_user` builds a User, it does not enrol one. A user becomes
94    // part of the station when it is inserted into this file's list and the
95    // file is written.
96    let users_file = engine.users_file()?;
97    let file = users_file.as_property_object_file()?;
98    println!(
99        "
100Users file:"
101    );
102    println!("  path            {}", file.path()?);
103    println!(
104        "  users           {}",
105        users_file.user_list()?.get_num_elements()?
106    );
107    println!(
108        "  groups          {}",
109        users_file.user_group_list()?.get_num_elements()?
110    );
111    println!(
112        "  profiles        {}",
113        users_file.user_profile_list()?.get_num_elements()?
114    );
115
116    // Deliberately not run: it would edit the station this example is reading.
117    // The two calls a host makes to enrol the user built above are
118    //
119    //     users_file.user_list()?.set_num_elements(count + 1, 0)?;   // then fill
120    //     file.save_file_if_modified(false)?;                        // then write
121    //
122    // `false` matters: with `true` the engine puts a save dialog on screen, and
123    // a host with no operator would wait for an answer that never comes.
124    println!(
125        "
126  (this example does not write; enrolling a user would alter the station)"
127    );
128
129    Ok(())
130}
Source

pub fn current_user_has_privilege( &self, privilege: UserPrivilege, ) -> Result<bool, Error>

Whether the logged-in user holds a privilege (Engine.CurrentUserHasPrivilege).

§Errors

Error if the COM call fails or returns an unexpected type.

Source

pub fn new_property_object( &self, value_type: PropValType, as_array: bool, type_name: &str, options: i32, ) -> Result<PropertyObject, Error>

Creates a standalone PropertyObject (Engine.NewPropertyObject).

The object belongs to no sequence file or station; it is useful as the root of a tree you build in memory. Pass a type name only when value_type is NamedType.

§Errors

Error if the COM call fails or returns an unexpected type.

Examples found in repository?
examples/template_manage_complex.rs (lines 60-65)
59fn new_template_group(engine: &Engine) -> Result<PropertyObject, Error> {
60    engine.new_property_object(
61        PropValType::Container,
62        true,
63        "",
64        PropertyOptions::NONE.bits(),
65    )
66}
67
68/// Appends a detached copy of a prototype to the group.
69///
70/// The copy matters: the group must not share the object the caller keeps
71/// editing, or adding a second template would change the first.
72fn append_template(group: &PropertyObject, template: &PropertyObject) -> Result<(), Error> {
73    let index = group.get_num_elements()?;
74    group.set_num_elements(index + 1, PropertyOptions::NONE.bits())?;
75    group.set_property_object(
76        &format!("[{index}]"),
77        PropertyOptions::NONE.bits(),
78        &template.clone_property("", PropertyOptions::NONE.bits())?,
79    )
80}
81
82/// Finds a prototype in the group by name.
83fn find_template(group: &PropertyObject, name: &str) -> Result<Option<PropertyObject>, Error> {
84    for index in 0..group.get_num_elements()? {
85        let candidate = group.get_property_object_by_offset(index, PropertyOptions::NONE.bits())?;
86        if candidate.name()? == name {
87            return Ok(Some(candidate));
88        }
89    }
90    Ok(None)
91}
92
93/// Turns "not found" into an error naming the template that is missing.
94fn require(found: Option<PropertyObject>, name: &'static str) -> Result<PropertyObject, Error> {
95    found.ok_or(Error::UnexpectedType {
96        expected: name,
97        actual: "no template of that name in the group",
98    })
99}
100
101/// A configured Statement step, taken as a property tree so it can be cloned.
102fn step_template(engine: &Engine) -> Result<PropertyObject, Error> {
103    let step = engine.new_step(NO_ADAPTER, "Statement")?;
104    step.set_name(STEP_TEMPLATE)?;
105    step.set_post_expression(r#"Locals.Result = "Hello from step template!""#)?;
106    step.as_property_object()
107}
108
109/// A whole sequence, one step included, as a single prototype.
110fn sequence_template(engine: &Engine) -> Result<PropertyObject, Error> {
111    let sequence = engine.new_sequence()?;
112    sequence.set_name(SEQUENCE_TEMPLATE)?;
113
114    let step = engine.new_step(NO_ADAPTER, "Statement")?;
115    step.set_name("Inside_Sequence_Template")?;
116    sequence.insert_step(&step, 0, StepGroup::Main)?;
117
118    sequence.as_property_object()
119}
120
121/// A string variable carrying its default value.
122fn variable_template(engine: &Engine) -> Result<PropertyObject, Error> {
123    let variable =
124        engine.new_property_object(PropValType::String, false, "", PropertyOptions::NONE.bits())?;
125    variable.set_name(VARIABLE_TEMPLATE)?;
126    variable.set_val_string("", PropertyOptions::NONE.bits(), "Template Variable Value")?;
127    Ok(variable)
128}
More examples
Hide additional examples
examples/data_type_manage.rs (line 29)
28fn build_multimeter_type(engine: &Engine) -> Result<PropertyObject, rs_teststand::Error> {
29    let data_type = engine.new_property_object(PropValType::Container, false, "", NO_OPTIONS)?;
30    data_type.set_val_number("Resolution", INSERT_IF_MISSING, 6.5)?;
31    data_type.set_val_bool("AutoZero", INSERT_IF_MISSING, false)?;
32    data_type.set_val_string("Mode", INSERT_IF_MISSING, "Voltage")?;
33    data_type.set_val_number("Range", INSERT_IF_MISSING, 100.0)?;
34    data_type.set_name("DigitalMultimeter")?;
35    Ok(data_type)
36}
37
38/// Builds the array `UpdateEnumerators` expects.
39///
40/// One container per enumerator, each carrying `EnumeratorName` and
41/// `EnumeratorValue`. Strictness is an attribute of the array, not a member of
42/// it, a strict enumeration refuses values outside the declared set.
43fn enumerator_array(
44    engine: &Engine,
45    named_values: &[(&str, f64)],
46    strict: bool,
47) -> Result<PropertyObject, rs_teststand::Error> {
48    let array = engine.new_property_object(PropValType::Container, true, "", NO_OPTIONS)?;
49    array.set_num_elements(i32::try_from(named_values.len()).unwrap_or(0), NO_OPTIONS)?;
50    for (index, (name, value)) in named_values.iter().enumerate() {
51        let element =
52            array.get_property_object_by_offset(i32::try_from(index).unwrap_or(0), NO_OPTIONS)?;
53        element.set_val_string("EnumeratorName", INSERT_IF_MISSING, name)?;
54        element.set_val_number("EnumeratorValue", INSERT_IF_MISSING, *value)?;
55    }
56    array
57        .attributes()?
58        .set_val_bool(IS_STRICT_ATTRIBUTE, INSERT_IF_MISSING, strict)?;
59    Ok(array)
60}
61
62/// Registers an enumeration, then returns its **registered** definition.
63///
64/// The distinction matters: enumerators can only be set on the definition the
65/// file holds, not on the loose object that was inserted.
66fn register_enum(
67    engine: &Engine,
68    types: &TypeUsageList,
69    name: &str,
70    named_values: &[(&str, f64)],
71    strict: bool,
72) -> Result<PropertyObject, rs_teststand::Error> {
73    let enum_type = engine.new_property_object(PropValType::Enum, false, "", NO_OPTIONS)?;
74    enum_type.set_name(name)?;
75    types.insert_type(
76        &enum_type,
77        types.num_types()?,
78        TypeCategory::CustomDataTypes,
79    )?;
80
81    let definition = types.get_type_definition(types.get_type_index(name)?)?;
82    definition.update_enumerators(&enumerator_array(engine, named_values, strict)?)?;
83    Ok(definition)
84}
Source

pub fn close(self, timeout: Duration) -> Result<bool, Error>

Shuts the engine down and leaves this thread’s COM apartment.

For a host that owns the engine on a spawned thread. Such a thread really does detach when it ends, so the apartment it initialized has to be closed or the COM runtime is left believing a live thread still owns one. The process’s main thread does not need this: it is ending anyway.

Consuming self is what makes the ordering safe, the engine is released before the apartment closes, and no caller can hold a reference across the boundary.

§Errors

Error if a COM call during shutdown fails. The apartment is closed either way.

Source

pub fn shutdown(&self, timeout: Duration) -> Result<bool, Error>

Closes files, terminates executions, and waits for the engine to say it is done (Engine.ShutDown).

ShutDown is asynchronous. It returns as soon as the request is accepted, having only started terminating executions and closing files; the engine reports completion later by posting UIMessageCode::ShutDownComplete to its message queue. So a caller that simply calls it and drops the engine tears down COM underneath work that is still running.

This does the whole protocol: enables message polling, asks the engine to shut down, then pumps and drains until the engine confirms or timeout elapses.

Returns true when the engine confirmed. false means the timeout came first, or the engine posted ShutDownCanceled, which a sequence can cause, for instance by refusing to terminate. Either way the wait is bounded: an unattended host must not be able to hang here.

Shutting down twice is harmless; the second call simply finds nothing to do and returns once the engine answers.

§Errors

Error if a COM call fails.

Source

pub fn get_templates_file( &self, options: GetTemplatesFileOptions, ) -> Result<PropertyObjectFile, Error>

The station’s templates file (Engine.GetTemplatesFile).

Holds the variable, step and sequence prototypes the editor offers when inserting. It is a station-wide file, so it is empty until someone adds templates to it, an empty one is the normal state, not a failure.

A template is an ordinary PropertyObject, not a type of its own, so a program is free to keep its own prototypes in a container it builds itself rather than in this file.

§Errors

Error if the COM call fails or returns an unexpected type.

Examples found in repository?
examples/step_insert_from_template.rs (line 34)
33fn describe_templates(engine: &Engine) -> Result<(), Error> {
34    let templates_file = engine.get_templates_file(GetTemplatesFileOptions::LOAD_IF_NOT_LOADED)?;
35    let root = templates_file
36        .data()?
37        .get_property_object("Root", PropertyOptions::NONE.bits())?;
38    for index in 0..root.get_num_elements()? {
39        let category = root.get_property_object_by_offset(index, PropertyOptions::NONE.bits())?;
40        if category.name()? == "Steps" {
41            println!(
42                "Step templates defined on this station: {}",
43                category.get_num_elements()?
44            );
45        }
46    }
47    Ok(())
48}
More examples
Hide additional examples
examples/template_manage_complex.rs (line 41)
40fn describe_station_templates(engine: &Engine) -> Result<(), Error> {
41    let templates_file = engine.get_templates_file(GetTemplatesFileOptions::LOAD_IF_NOT_LOADED)?;
42    let root = templates_file
43        .data()?
44        .get_property_object("Root", PropertyOptions::NONE.bits())?;
45
46    println!("Station templates file: {}", templates_file.path()?);
47    for index in 0..root.get_num_elements()? {
48        let category = root.get_property_object_by_offset(index, PropertyOptions::NONE.bits())?;
49        println!(
50            "  {}: {} template(s)",
51            category.name()?,
52            category.get_num_elements()?
53        );
54    }
55    Ok(())
56}
Source

pub fn get_sequence_file_ex( &self, path: &str, options: GetSeqFileOptions, handler: ConflictHandler, ) -> Result<SequenceFile, Error>

Opens a sequence file, or returns the already-loaded one (Engine.GetSequenceFileEx).

The engine caches the file and counts load references, so every successful call must be paired with release_sequence_file_ex.

Both option arguments matter on an unattended host: crate::sequence::GetSeqFileOptions::DO_NOT_RUN_LOAD_CALLBACK suppresses a load callback that could raise a dialog, and crate::sequence::ConflictHandler::Error fails the load instead of prompting.

§Errors

Error if the file cannot be opened or the COM call fails.

Examples found in repository?
examples/variables_manage.rs (lines 88-92)
84fn open_sequence_file(engine: &Engine) -> Result<Option<SequenceFile>, rs_teststand::Error> {
85    let Some(path) = std::env::args().nth(1) else {
86        return Ok(None);
87    };
88    let file = engine.get_sequence_file_ex(
89        &path,
90        GetSeqFileOptions::DO_NOT_RUN_LOAD_CALLBACK,
91        ConflictHandler::Error,
92    )?;
93    Ok(Some(file))
94}
More examples
Hide additional examples
examples/template_manage_complex.rs (lines 198-202)
176fn main() -> Result<(), Box<dyn std::error::Error>> {
177    let engine = Engine::new()?;
178    describe_station_templates(&engine)?;
179
180    println!("\nBuilding an in-memory template group...");
181    let group = new_template_group(&engine)?;
182    append_template(&group, &step_template(&engine)?)?;
183    append_template(&group, &sequence_template(&engine)?)?;
184    append_template(&group, &variable_template(&engine)?)?;
185    println!("  {} template(s) stored", group.get_num_elements()?);
186
187    let step = require(find_template(&group, STEP_TEMPLATE)?, STEP_TEMPLATE)?;
188    let sequence = require(find_template(&group, SEQUENCE_TEMPLATE)?, SEQUENCE_TEMPLATE)?;
189    let variable = require(find_template(&group, VARIABLE_TEMPLATE)?, VARIABLE_TEMPLATE)?;
190
191    // Saving first, then reopening, is deliberate: templates are worth having
192    // because they are applied to files a program did not build in this run.
193    let path = std::env::temp_dir().join("rs_teststand_from_templates.seq");
194    let path = path.to_string_lossy().into_owned();
195    engine.new_sequence_file()?.save(&path)?;
196
197    println!("\nApplying templates to the saved file...");
198    let target = engine.get_sequence_file_ex(
199        &path,
200        GetSeqFileOptions::DO_NOT_RUN_LOAD_CALLBACK,
201        rs_teststand::ConflictHandler::Error,
202    )?;
203    apply_templates(&target, &step, &sequence, &variable)?;
204    target.save(&path)?;
205    println!("\nSaved to {path}");
206
207    engine.release_sequence_file_ex(target, PropertyOptions::NONE.bits())?;
208    Ok(())
209}
Source

pub fn release_sequence_file_ex( &self, sequence_file: SequenceFile, options: i32, ) -> Result<bool, Error>

Drops one load reference on a sequence file (Engine.ReleaseSequenceFileEx).

Returns true when that was the last reference and the engine has discarded the file. false means something else still holds it open, so the file stays loaded, which is why only the true case also releases the wrapper’s own COM reference.

§Errors

Error if the COM call fails.

Examples found in repository?
examples/execution_run_subsequence.rs (line 166)
140fn main() -> Result<(), Box<dyn std::error::Error>> {
141    let engine = Engine::new()?;
142    engine.set_ui_message_polling_enabled(true)?;
143
144    let sequence_file = build(&engine)?;
145    println!("File holds {} sequence(s).", sequence_file.num_sequences()?);
146
147    // The conventional entry point.
148    run(&engine, &sequence_file, "MainSequence")?;
149
150    // A subsequence run on its own has no caller, so nothing supplies its
151    // parameters, they keep whatever default the sequence carries. Setting
152    // that default is therefore how a direct run is given its input.
153    let diagnostics = sequence_file.get_sequence_by_name(SUBSEQUENCE)?;
154    diagnostics
155        .parameters()?
156        .set_val_string(PARAMETER, none(), "FIXTURE-07")?;
157    println!(
158        "\n{SUBSEQUENCE}.{PARAMETER} default is now {:?}",
159        diagnostics
160            .parameters()?
161            .get_val_string(PARAMETER, none())?
162    );
163
164    run(&engine, &sequence_file, SUBSEQUENCE)?;
165
166    engine.release_sequence_file_ex(sequence_file, none())?;
167    Ok(())
168}
More examples
Hide additional examples
examples/execution_run_test_headless.rs (line 171)
146fn main() -> Result<(), Box<dyn std::error::Error>> {
147    let engine = Engine::new()?;
148    // Nothing reaches the queue until this is on, and without the queue there
149    // is no way to know the run ended.
150    engine.set_ui_message_polling_enabled(true)?;
151
152    let sequence_file = engine.new_sequence_file()?;
153    let main_sequence = sequence_file.get_sequence_by_name("MainSequence")?;
154    for (name, data_source, low, high) in TESTS {
155        add_numeric_limit_test(&engine, &main_sequence, name, data_source, low, high)?;
156    }
157
158    let execution = engine.new_execution(&sequence_file, "MainSequence", None, false, 0)?;
159    println!("Running {} headless...", execution.display_name()?);
160
161    if wait_for_end(&engine, RUN_DEADLINE)? {
162        println!("Finished with status: {}", execution.result_status()?);
163        report(&execution.result_object()?)?;
164    } else {
165        // Reported rather than ignored: a host that assumes success here would
166        // publish results from a run that never finished.
167        println!("The run did not finish within {RUN_DEADLINE:?}; terminating.");
168        execution.terminate()?;
169    }
170
171    engine.release_sequence_file_ex(sequence_file, none())?;
172    Ok(())
173}
examples/template_manage_complex.rs (line 207)
176fn main() -> Result<(), Box<dyn std::error::Error>> {
177    let engine = Engine::new()?;
178    describe_station_templates(&engine)?;
179
180    println!("\nBuilding an in-memory template group...");
181    let group = new_template_group(&engine)?;
182    append_template(&group, &step_template(&engine)?)?;
183    append_template(&group, &sequence_template(&engine)?)?;
184    append_template(&group, &variable_template(&engine)?)?;
185    println!("  {} template(s) stored", group.get_num_elements()?);
186
187    let step = require(find_template(&group, STEP_TEMPLATE)?, STEP_TEMPLATE)?;
188    let sequence = require(find_template(&group, SEQUENCE_TEMPLATE)?, SEQUENCE_TEMPLATE)?;
189    let variable = require(find_template(&group, VARIABLE_TEMPLATE)?, VARIABLE_TEMPLATE)?;
190
191    // Saving first, then reopening, is deliberate: templates are worth having
192    // because they are applied to files a program did not build in this run.
193    let path = std::env::temp_dir().join("rs_teststand_from_templates.seq");
194    let path = path.to_string_lossy().into_owned();
195    engine.new_sequence_file()?.save(&path)?;
196
197    println!("\nApplying templates to the saved file...");
198    let target = engine.get_sequence_file_ex(
199        &path,
200        GetSeqFileOptions::DO_NOT_RUN_LOAD_CALLBACK,
201        rs_teststand::ConflictHandler::Error,
202    )?;
203    apply_templates(&target, &step, &sequence, &variable)?;
204    target.save(&path)?;
205    println!("\nSaved to {path}");
206
207    engine.release_sequence_file_ex(target, PropertyOptions::NONE.bits())?;
208    Ok(())
209}
examples/sequence_build.rs (line 122)
68fn main() -> Result<(), Box<dyn std::error::Error>> {
69    let engine = Engine::new()?;
70    let sequence_file = engine.new_sequence_file()?;
71    let main_sequence = sequence_file.get_sequence_by_name("MainSequence")?;
72
73    // Steps are placed by group and index, so order is explicit.
74    main_sequence.insert_step(
75        &numeric_limit_test(
76            &engine,
77            "Temperature Check",
78            "Locals.TempSensorPresent == True",
79            15.0,
80            85.0,
81        )?,
82        0,
83        StepGroup::Main,
84    )?;
85    main_sequence.insert_step(
86        &numeric_limit_test(
87            &engine,
88            "Voltage Monitor",
89            "Locals.DUTPowered == True",
90            4.75,
91            5.25,
92        )?,
93        1,
94        StepGroup::Main,
95    )?;
96
97    // A subsequence, so a later example has something to call.
98    let subsequence = engine.new_sequence()?;
99    subsequence.set_name("CustomSubsequence")?;
100    sequence_file.insert_sequence(&subsequence)?;
101
102    let init_step = engine.new_step(NO_ADAPTER, "Action")?;
103    init_step.set_name("Initialize Hardware")?;
104    subsequence.insert_step(&init_step, 0, StepGroup::Main)?;
105
106    describe(&main_sequence)?;
107    println!();
108    describe(&subsequence)?;
109
110    // Cleanup runs even when Main fails, which is why it is worth showing.
111    println!(
112        "\nGroup sizes in MainSequence: setup={}, main={}, cleanup={}",
113        main_sequence.get_num_steps(StepGroup::Setup)?,
114        main_sequence.get_num_steps(StepGroup::Main)?,
115        main_sequence.get_num_steps(StepGroup::Cleanup)?
116    );
117
118    let path = std::env::temp_dir().join("rs_teststand_built_sequence.seq");
119    sequence_file.save(&path.to_string_lossy())?;
120    println!("\nSaved to {}", path.display());
121
122    engine.release_sequence_file_ex(sequence_file, NO_OPTIONS)?;
123    Ok(())
124}
examples/variables_manage.rs (line 224)
186fn main() -> Result<(), rs_teststand::Error> {
187    let engine = Engine::new()?;
188
189    show_station_globals(&engine)?;
190
191    // The other three scopes need a sequence file.
192    if let Some(sequence_file) = open_sequence_file(&engine)? {
193        // File globals: shared by every sequence in this file. These are the
194        // defaults stored in the file; a running execution gets its own copy.
195        let file_globals = sequence_file.file_globals_default_values()?;
196        set_string(&file_globals, "BatchID", "BATCH-2026-Q2-001")?;
197        println!(
198            "FileGlobals.BatchID                   = '{}'",
199            file_globals.get_val_string("BatchID", 0)?
200        );
201
202        let main_sequence = sequence_file.get_sequence_by_name("MainSequence")?;
203
204        // Locals: private to one call of this sequence.
205        let locals = main_sequence.locals()?;
206        set_string(&locals, "OperatorName", "Alice")?;
207        println!(
208            "MainSequence.Locals.OperatorName      = '{}'",
209            locals.get_val_string("OperatorName", 0)?
210        );
211
212        // Parameters: supplied by whoever calls this sequence.
213        let parameters = main_sequence.parameters()?;
214        set_string(&parameters, "DUTSerial", "SN-000000")?;
215        println!(
216            "MainSequence.Parameters.DUTSerial     = '{}'",
217            parameters.get_val_string("DUTSerial", 0)?
218        );
219
220        println!("\nTemporary variable lifecycle (Locals.TempScratch):");
221        temporary_variable_lifecycle(&locals)?;
222
223        // Nothing is saved: the file is left exactly as it was found.
224        engine.release_sequence_file_ex(sequence_file, 0)?;
225    } else {
226        println!("\n(pass a .seq path to also demonstrate locals, parameters and file globals)");
227        println!("\nTemporary variable lifecycle (StationGlobals.TempScratch):");
228        temporary_variable_lifecycle(&engine.globals()?)?;
229    }
230
231    engine.commit_globals_to_disk(false)?;
232    println!("\nStation globals committed to disk.");
233    Ok(())
234}
examples/data_type_manage.rs (line 174)
107fn main() -> Result<(), Box<dyn std::error::Error>> {
108    let engine = Engine::new()?;
109    let sequence_file = engine.new_sequence_file()?;
110    let file = sequence_file.as_property_object_file()?;
111    let types = file.type_usage_list()?;
112
113    types.insert_type(
114        &build_multimeter_type(&engine)?,
115        types.num_types()?,
116        TypeCategory::CustomDataTypes,
117    )?;
118    let coupling = register_enum(
119        &engine,
120        &types,
121        "Coupling",
122        &[("AC", 0.0), ("DC", 1.0)],
123        true,
124    )?;
125
126    // A variable of the enum type, so the change below has an instance to update.
127    let main_sequence = sequence_file.get_sequence_by_name("MainSequence")?;
128    main_sequence.locals()?.new_sub_property(
129        "InputCoupling",
130        PropValType::NamedType,
131        false,
132        "Coupling",
133        INSERT_IF_MISSING,
134    )?;
135
136    println!(
137        "Registered custom data types ({} in file):",
138        types.num_types()?
139    );
140    print_enumerators(&coupling)?;
141
142    // Evolve it: add an enumerator and raise the version.
143    println!(
144        "\nCoupling version before update: {}",
145        coupling.type_version()?
146    );
147    coupling.update_enumerators(&enumerator_array(
148        &engine,
149        &[("AC", 0.0), ("DC", 1.0), ("GND", 2.0)],
150        true,
151    )?)?;
152
153    // Raising the lowest field signals a change the engine applies silently;
154    // raising a higher one marks it as deliberate.
155    let version = coupling.type_version()?;
156    let mut fields = version.split('.');
157    let major: u32 = fields.next().unwrap_or("0").parse().unwrap_or(0);
158    let minor: u32 = fields.next().unwrap_or("0").parse().unwrap_or(0);
159    coupling.set_type_version(&format!("{major}.{}.0.0", minor + 1))?;
160    println!(
161        "Coupling version after  update: {}",
162        coupling.type_version()?
163    );
164
165    println!("\nCoupling now defines (InputCoupling reflects this):");
166    print_enumerators(&coupling)?;
167
168    // Saving does nothing unless the file believes it changed.
169    file.inc_change_count()?;
170    let path = std::env::temp_dir().join("rs_teststand_with_custom_types.seq");
171    sequence_file.save(&path.to_string_lossy())?;
172    println!("\nSaved to {}", path.display());
173
174    engine.release_sequence_file_ex(sequence_file, NO_OPTIONS)?;
175    Ok(())
176}
Source

pub fn search_directories(&self) -> Result<SearchDirectories, Error>

Accesses the collection of search directories (Engine.SearchDirectories).

§Errors

Error if the COM call fails or returns an unexpected type.

Examples found in repository?
examples/search_directory_manage.rs (line 51)
49fn main() -> Result<(), rs_teststand::Error> {
50    let engine = Engine::new()?;
51    let search_directories = engine.search_directories()?;
52
53    println!("Total search directories: {}", search_directories.count()?);
54    for (index, directory) in search_directories.iter()?.enumerate() {
55        print_entry(index, &directory?)?;
56    }
57
58    // Insert at the front, so the new entry is searched first.
59    println!("\nInserting a new explicit search directory...");
60    let new_path = engine.bin_directory()?;
61    search_directories.insert(&new_path, 0, true, "", false, false)?;
62    println!("Total after insert: {}", search_directories.count()?);
63
64    let inserted = search_directories.get(0)?;
65    println!(
66        "New [0] Path: '{}', Subdirs: {}",
67        inserted.path()?,
68        inserted.search_subdirectories()?
69    );
70
71    // A disabled entry stays in the list but is not searched.
72    println!("Disabling the new directory...");
73    inserted.set_disabled(true)?;
74    println!("New [0] Disabled: {}", inserted.disabled()?);
75
76    // Order matters: entries are searched in list order.
77    println!("Moving the new directory to index 1...");
78    search_directories.move_search_directory(0, 1)?;
79    println!(
80        "Directory at index 1 is now: '{}'",
81        search_directories.get(1)?.path()?
82    );
83
84    println!("Removing the added directory to clean up...");
85    search_directories.remove(1)?;
86    println!("Total after cleanup: {}", search_directories.count()?);
87
88    // The engine writes search directories out at shutdown anyway. Committing
89    // now makes the change visible to other processes immediately, and passing
90    // `false` keeps a save conflict from raising a dialog.
91    engine.commit_globals_to_disk(false)?;
92    println!("Committed search directories configuration to disk.");
93
94    Ok(())
95}
Source

pub fn globals(&self) -> Result<PropertyObject, Error>

Accesses the station global variables container (Engine.Globals).

§Errors

Error if the COM call fails or returns an unexpected type.

Examples found in repository?
examples/variables_manage.rs (line 102)
100fn show_station_globals(engine: &Engine) -> Result<(), rs_teststand::Error> {
101    // Station globals: shared by every sequence file, and written to disk.
102    let station_globals = engine.globals()?;
103    if !station_globals.exists("StationInfo", 0)? {
104        station_globals.new_sub_property(
105            "StationInfo",
106            PropValType::Container,
107            false,
108            "",
109            INSERT_IF_MISSING,
110        )?;
111    }
112    let station_info = station_globals.get_property_object("StationInfo", 0)?;
113
114    // A container holds mixed types, so one station record can carry the name,
115    // a count and a flag without three separate globals.
116    set_string(&station_info, "StationName", "STATION_01")?;
117    station_info.set_val_number("CalibrationIntervalDays", INSERT_IF_MISSING, 90.0)?;
118    station_info.set_val_bool("FixtureInstalled", INSERT_IF_MISSING, true)?;
119
120    // A 64-bit count. `SetValNumber` stores a double, which starts losing whole
121    // numbers past 2^53, so anything that counts for the life of a station wants
122    // the integer path instead.
123    station_info.set_val_integer64("UnitsTestedTotal", INSERT_IF_MISSING, 9_007_199_254_740_993)?;
124
125    // A nested container, because station data is rarely one level deep.
126    if !station_info.exists("LastCalibration", 0)? {
127        station_info.new_sub_property(
128            "LastCalibration",
129            PropValType::Container,
130            false,
131            "",
132            INSERT_IF_MISSING,
133        )?;
134    }
135    let calibration = station_info.get_property_object("LastCalibration", 0)?;
136    set_string(&calibration, "Technician", "R. Alvarez")?;
137    set_string(&calibration, "Date", "2026-06-14")?;
138
139    println!("StationGlobals.StationInfo:");
140    println!(
141        "  StationName             = '{}'",
142        station_info.get_val_string("StationName", 0)?
143    );
144    println!(
145        "  CalibrationIntervalDays = {}",
146        station_info.get_val_number("CalibrationIntervalDays", 0)?
147    );
148    println!(
149        "  FixtureInstalled        = {}",
150        station_info.get_val_bool("FixtureInstalled", 0)?
151    );
152    println!(
153        "  UnitsTestedTotal        = {}",
154        station_info.get_val_integer64("UnitsTestedTotal", 0)?
155    );
156    println!(
157        "  LastCalibration.Technician = '{}' on {}",
158        calibration.get_val_string("Technician", 0)?,
159        calibration.get_val_string("Date", 0)?
160    );
161
162    // Walk the container rather than naming each field, which is what a host
163    // does when it does not know the shape in advance.
164    println!(
165        "  walked, {} field(s):",
166        station_info.get_num_sub_properties("")?
167    );
168    for index in 0..station_info.get_num_sub_properties("")? {
169        println!(
170            "    {}",
171            station_info.get_nth_sub_property_name("", index, 0)?
172        );
173    }
174
175    // Station globals live in memory until this is called. Without it the values
176    // above are gone when the engine goes away, which is the difference between
177    // a station global and a file global.
178    // `false` means do not prompt if another process changed the file first.
179    // An example must never raise a dialog, and neither must a headless host.
180    engine.commit_globals_to_disk(false)?;
181    println!("  committed to disk");
182
183    Ok(())
184}
185
186fn main() -> Result<(), rs_teststand::Error> {
187    let engine = Engine::new()?;
188
189    show_station_globals(&engine)?;
190
191    // The other three scopes need a sequence file.
192    if let Some(sequence_file) = open_sequence_file(&engine)? {
193        // File globals: shared by every sequence in this file. These are the
194        // defaults stored in the file; a running execution gets its own copy.
195        let file_globals = sequence_file.file_globals_default_values()?;
196        set_string(&file_globals, "BatchID", "BATCH-2026-Q2-001")?;
197        println!(
198            "FileGlobals.BatchID                   = '{}'",
199            file_globals.get_val_string("BatchID", 0)?
200        );
201
202        let main_sequence = sequence_file.get_sequence_by_name("MainSequence")?;
203
204        // Locals: private to one call of this sequence.
205        let locals = main_sequence.locals()?;
206        set_string(&locals, "OperatorName", "Alice")?;
207        println!(
208            "MainSequence.Locals.OperatorName      = '{}'",
209            locals.get_val_string("OperatorName", 0)?
210        );
211
212        // Parameters: supplied by whoever calls this sequence.
213        let parameters = main_sequence.parameters()?;
214        set_string(&parameters, "DUTSerial", "SN-000000")?;
215        println!(
216            "MainSequence.Parameters.DUTSerial     = '{}'",
217            parameters.get_val_string("DUTSerial", 0)?
218        );
219
220        println!("\nTemporary variable lifecycle (Locals.TempScratch):");
221        temporary_variable_lifecycle(&locals)?;
222
223        // Nothing is saved: the file is left exactly as it was found.
224        engine.release_sequence_file_ex(sequence_file, 0)?;
225    } else {
226        println!("\n(pass a .seq path to also demonstrate locals, parameters and file globals)");
227        println!("\nTemporary variable lifecycle (StationGlobals.TempScratch):");
228        temporary_variable_lifecycle(&engine.globals()?)?;
229    }
230
231    engine.commit_globals_to_disk(false)?;
232    println!("\nStation globals committed to disk.");
233    Ok(())
234}
Source

pub fn new_workspace_file(&self) -> Result<WorkspaceFile, Error>

Creates a new workspace file object (Engine.NewWorkspaceFile).

§Errors

Error if the COM call fails or returns an unexpected type.

Examples found in repository?
examples/workspace_create.rs (line 9)
5fn main() -> Result<(), rs_teststand::Error> {
6    let engine = Engine::new()?;
7
8    println!("Creating a new workspace file...");
9    let ws_file = engine.new_workspace_file()?;
10    let root_obj = ws_file.root_workspace_object()?;
11
12    println!("Root workspace object:");
13    println!("  Display Name: '{}'", root_obj.display_name()?);
14    println!("  Object Type: {}", root_obj.object_type()?);
15    println!("  Child Objects: {}", root_obj.num_contained_objects()?);
16
17    println!("\nCreating project and folder structure...");
18    let project = root_obj.new_folder("Project Alpha")?;
19    println!("  Created project folder: '{}'", project.display_name()?);
20
21    let seq_entry = project.new_file("MainTest.seq")?;
22    println!(
23        "  Created sequence file entry: '{}'",
24        seq_entry.display_name()?
25    );
26
27    println!(
28        "\nUpdated root child objects count: {}",
29        root_obj.num_contained_objects()?
30    );
31
32    Ok(())
33}
Source

pub fn open_workspace_file( &self, path: &str, read_only: bool, options: i32, ) -> Result<WorkspaceFile, Error>

Opens an existing workspace file (Engine.OpenWorkspaceFile).

§Errors

Error if the COM call fails or returns an unexpected type.

Source

pub fn commit_globals_to_disk( &self, prompt_on_save_conflicts: bool, ) -> Result<(), Error>

Flushes modified station globals and configuration to disk (Engine.CommitGlobalsToDisk).

§Errors

Error if the COM call fails.

Examples found in repository?
examples/station_options_update.rs (line 27)
5fn main() -> Result<(), rs_teststand::Error> {
6    let engine = Engine::new()?;
7    let station_options = engine.station_options()?;
8
9    station_options.set_tracing_enabled(true)?;
10    station_options.set_disable_results(false)?;
11    station_options.set_breakpoints_enabled(true)?;
12    station_options.set_check_out_files_when_edited(false)?;
13    station_options.set_language("English")?;
14    station_options.set_always_goto_cleanup_on_failure(true)?;
15    station_options.set_show_hidden_properties(true)?;
16    station_options.set_prompt_to_find_files(false)?;
17    station_options.set_auto_login_system_user(true)?;
18    station_options.set_ui_message_delay(100)?;
19    station_options.set_ui_message_min_delay(10)?;
20    station_options.set_station_id("STATION_RUST_01")?;
21    station_options.set_use_station_model(true)?;
22    station_options.set_allow_other_models(false)?;
23    station_options.set_use_localized_decimal_point(false)?;
24    station_options.set_time_limit(0, 0, 60.0)?;
25    station_options.set_time_limit_enabled(0, 0, true)?;
26
27    engine.commit_globals_to_disk(false)?;
28    println!("Station options updated and committed to disk.");
29
30    Ok(())
31}
More examples
Hide additional examples
examples/search_directory_manage.rs (line 91)
49fn main() -> Result<(), rs_teststand::Error> {
50    let engine = Engine::new()?;
51    let search_directories = engine.search_directories()?;
52
53    println!("Total search directories: {}", search_directories.count()?);
54    for (index, directory) in search_directories.iter()?.enumerate() {
55        print_entry(index, &directory?)?;
56    }
57
58    // Insert at the front, so the new entry is searched first.
59    println!("\nInserting a new explicit search directory...");
60    let new_path = engine.bin_directory()?;
61    search_directories.insert(&new_path, 0, true, "", false, false)?;
62    println!("Total after insert: {}", search_directories.count()?);
63
64    let inserted = search_directories.get(0)?;
65    println!(
66        "New [0] Path: '{}', Subdirs: {}",
67        inserted.path()?,
68        inserted.search_subdirectories()?
69    );
70
71    // A disabled entry stays in the list but is not searched.
72    println!("Disabling the new directory...");
73    inserted.set_disabled(true)?;
74    println!("New [0] Disabled: {}", inserted.disabled()?);
75
76    // Order matters: entries are searched in list order.
77    println!("Moving the new directory to index 1...");
78    search_directories.move_search_directory(0, 1)?;
79    println!(
80        "Directory at index 1 is now: '{}'",
81        search_directories.get(1)?.path()?
82    );
83
84    println!("Removing the added directory to clean up...");
85    search_directories.remove(1)?;
86    println!("Total after cleanup: {}", search_directories.count()?);
87
88    // The engine writes search directories out at shutdown anyway. Committing
89    // now makes the change visible to other processes immediately, and passing
90    // `false` keeps a save conflict from raising a dialog.
91    engine.commit_globals_to_disk(false)?;
92    println!("Committed search directories configuration to disk.");
93
94    Ok(())
95}
examples/variables_manage.rs (line 180)
100fn show_station_globals(engine: &Engine) -> Result<(), rs_teststand::Error> {
101    // Station globals: shared by every sequence file, and written to disk.
102    let station_globals = engine.globals()?;
103    if !station_globals.exists("StationInfo", 0)? {
104        station_globals.new_sub_property(
105            "StationInfo",
106            PropValType::Container,
107            false,
108            "",
109            INSERT_IF_MISSING,
110        )?;
111    }
112    let station_info = station_globals.get_property_object("StationInfo", 0)?;
113
114    // A container holds mixed types, so one station record can carry the name,
115    // a count and a flag without three separate globals.
116    set_string(&station_info, "StationName", "STATION_01")?;
117    station_info.set_val_number("CalibrationIntervalDays", INSERT_IF_MISSING, 90.0)?;
118    station_info.set_val_bool("FixtureInstalled", INSERT_IF_MISSING, true)?;
119
120    // A 64-bit count. `SetValNumber` stores a double, which starts losing whole
121    // numbers past 2^53, so anything that counts for the life of a station wants
122    // the integer path instead.
123    station_info.set_val_integer64("UnitsTestedTotal", INSERT_IF_MISSING, 9_007_199_254_740_993)?;
124
125    // A nested container, because station data is rarely one level deep.
126    if !station_info.exists("LastCalibration", 0)? {
127        station_info.new_sub_property(
128            "LastCalibration",
129            PropValType::Container,
130            false,
131            "",
132            INSERT_IF_MISSING,
133        )?;
134    }
135    let calibration = station_info.get_property_object("LastCalibration", 0)?;
136    set_string(&calibration, "Technician", "R. Alvarez")?;
137    set_string(&calibration, "Date", "2026-06-14")?;
138
139    println!("StationGlobals.StationInfo:");
140    println!(
141        "  StationName             = '{}'",
142        station_info.get_val_string("StationName", 0)?
143    );
144    println!(
145        "  CalibrationIntervalDays = {}",
146        station_info.get_val_number("CalibrationIntervalDays", 0)?
147    );
148    println!(
149        "  FixtureInstalled        = {}",
150        station_info.get_val_bool("FixtureInstalled", 0)?
151    );
152    println!(
153        "  UnitsTestedTotal        = {}",
154        station_info.get_val_integer64("UnitsTestedTotal", 0)?
155    );
156    println!(
157        "  LastCalibration.Technician = '{}' on {}",
158        calibration.get_val_string("Technician", 0)?,
159        calibration.get_val_string("Date", 0)?
160    );
161
162    // Walk the container rather than naming each field, which is what a host
163    // does when it does not know the shape in advance.
164    println!(
165        "  walked, {} field(s):",
166        station_info.get_num_sub_properties("")?
167    );
168    for index in 0..station_info.get_num_sub_properties("")? {
169        println!(
170            "    {}",
171            station_info.get_nth_sub_property_name("", index, 0)?
172        );
173    }
174
175    // Station globals live in memory until this is called. Without it the values
176    // above are gone when the engine goes away, which is the difference between
177    // a station global and a file global.
178    // `false` means do not prompt if another process changed the file first.
179    // An example must never raise a dialog, and neither must a headless host.
180    engine.commit_globals_to_disk(false)?;
181    println!("  committed to disk");
182
183    Ok(())
184}
185
186fn main() -> Result<(), rs_teststand::Error> {
187    let engine = Engine::new()?;
188
189    show_station_globals(&engine)?;
190
191    // The other three scopes need a sequence file.
192    if let Some(sequence_file) = open_sequence_file(&engine)? {
193        // File globals: shared by every sequence in this file. These are the
194        // defaults stored in the file; a running execution gets its own copy.
195        let file_globals = sequence_file.file_globals_default_values()?;
196        set_string(&file_globals, "BatchID", "BATCH-2026-Q2-001")?;
197        println!(
198            "FileGlobals.BatchID                   = '{}'",
199            file_globals.get_val_string("BatchID", 0)?
200        );
201
202        let main_sequence = sequence_file.get_sequence_by_name("MainSequence")?;
203
204        // Locals: private to one call of this sequence.
205        let locals = main_sequence.locals()?;
206        set_string(&locals, "OperatorName", "Alice")?;
207        println!(
208            "MainSequence.Locals.OperatorName      = '{}'",
209            locals.get_val_string("OperatorName", 0)?
210        );
211
212        // Parameters: supplied by whoever calls this sequence.
213        let parameters = main_sequence.parameters()?;
214        set_string(&parameters, "DUTSerial", "SN-000000")?;
215        println!(
216            "MainSequence.Parameters.DUTSerial     = '{}'",
217            parameters.get_val_string("DUTSerial", 0)?
218        );
219
220        println!("\nTemporary variable lifecycle (Locals.TempScratch):");
221        temporary_variable_lifecycle(&locals)?;
222
223        // Nothing is saved: the file is left exactly as it was found.
224        engine.release_sequence_file_ex(sequence_file, 0)?;
225    } else {
226        println!("\n(pass a .seq path to also demonstrate locals, parameters and file globals)");
227        println!("\nTemporary variable lifecycle (StationGlobals.TempScratch):");
228        temporary_variable_lifecycle(&engine.globals()?)?;
229    }
230
231    engine.commit_globals_to_disk(false)?;
232    println!("\nStation globals committed to disk.");
233    Ok(())
234}

Trait Implementations§

Source§

impl Debug for Engine

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more

Auto Trait Implementations§

§

impl !RefUnwindSafe for Engine

§

impl !Send for Engine

§

impl !Sync for Engine

§

impl !UnwindSafe for Engine

§

impl Freeze for Engine

§

impl Unpin for Engine

§

impl UnsafeUnpin for Engine

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.