Skip to main content

step_insert/
step_insert.rs

1//! Example: insert VI-call steps at a chosen point in an existing sequence.
2//!
3//! ```text
4//! cargo run --example step_insert
5//! ```
6//!
7//! Appending to the end of a group is easy. Inserting *in front of a particular
8//! step* is the case that comes up in practice, and it needs the target's index
9//! rather than its name, so this looks the step up, reads where it sits, and
10//! inserts there.
11//!
12//! The steps built here call `LabVIEW` VIs. Building one needs no `LabVIEW`
13//! installation: the adapter key names an adapter the engine knows, and the VI
14//! paths are just properties until something runs them.
15
16use rs_teststand::{
17    AdapterKeyName, Engine, Error, PropertyOptions, ResultRecordingOption, RunMode, Sequence, Step,
18    StepGroup,
19};
20
21/// Where a VI-call step keeps the path of the VI it calls.
22const VI_PATH: &str = "TS.SData.ViCall.VIPath";
23/// Where it keeps the owning project, when the VI belongs to one.
24const PROJECT_PATH: &str = "TS.SData.ViCall.ProjectPath";
25/// The step whose position the new steps are inserted in front of.
26const TARGET_STEP: &str = "Initialize Hardware";
27
28const fn none() -> i32 {
29    PropertyOptions::NONE.bits()
30}
31
32const fn insert_if_missing() -> i32 {
33    PropertyOptions::INSERT_IF_MISSING.bits()
34}
35
36/// Builds a configured VI-call step, ready to insert.
37fn vi_call_step(
38    engine: &Engine,
39    name: &str,
40    vi_path: &str,
41    project_path: &str,
42    run_mode: RunMode,
43) -> Result<Step, Error> {
44    let step = engine.new_step(AdapterKeyName::LabView.as_str(), "Action")?;
45    step.set_name(name)?;
46    step.set_run_mode(run_mode)?;
47    // Recorded explicitly rather than left to the default, so the step shows up
48    // in the result list a report is built from.
49    step.set_result_recording_option(ResultRecordingOption::Enabled)?;
50
51    let properties = step.as_property_object()?;
52    properties.set_val_string(VI_PATH, insert_if_missing(), vi_path)?;
53    if !project_path.is_empty() {
54        properties.set_val_string(PROJECT_PATH, insert_if_missing(), project_path)?;
55    }
56    Ok(step)
57}
58
59/// The index of a step within a group, by name.
60///
61/// `None` when no step of that name is in the group, which a caller should
62/// treat as "append at the end" rather than as a failure: a sequence is free
63/// not to contain the step someone expected.
64fn index_of(sequence: &Sequence, name: &str, group: StepGroup) -> Result<Option<i32>, Error> {
65    for index in 0..sequence.get_num_steps(group)? {
66        if sequence.get_step(index, group)?.name()? == name {
67            return Ok(Some(index));
68        }
69    }
70    Ok(None)
71}
72
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}