pub struct Sequence { /* private fields */ }Expand description
One sequence inside a SequenceFile.
A sequence owns two of the four variable scopes:
locals, storage private to one call of this sequence.parameters, values the caller supplies.
The other two live elsewhere: file globals on the sequence file, and station globals on the engine.
Implementations§
Source§impl Sequence
impl Sequence
Sourcepub fn name(&self) -> Result<String, Error>
pub fn name(&self) -> Result<String, Error>
The sequence’s name (Sequence.Name).
§Errors
Error if the COM call fails or returns an unexpected type.
Examples found in repository?
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}More examples
131fn apply_templates(
132 sequence_file: &SequenceFile,
133 step: &PropertyObject,
134 sequence: &PropertyObject,
135 variable: &PropertyObject,
136) -> Result<(), Error> {
137 let main_sequence = sequence_file.get_sequence_by_name(MAIN_SEQUENCE)?;
138
139 // Inserting returns the copy on the Step interface, so it can be renamed
140 // and re-identified straight away. Without a new step ID the copy would go
141 // on claiming the prototype's identity.
142 let inserted_step = main_sequence.insert_step_from_template(step, 0, StepGroup::Main)?;
143 inserted_step.set_name("Step From Template")?;
144 inserted_step.create_new_unique_step_id()?;
145 println!(" step: {}", inserted_step.name()?);
146
147 let inserted_sequence = sequence_file.insert_sequence_from_template(sequence)?;
148 inserted_sequence.create_new_unique_step_ids()?;
149 println!(
150 " sequence: {} ({} step(s))",
151 inserted_sequence.name()?,
152 inserted_sequence.get_num_steps(StepGroup::Main)?
153 );
154
155 // A variable is the easy case: Locals is a property tree, so a clone drops
156 // straight in under the template's own name.
157 let locals = main_sequence.locals()?;
158 locals.set_property_object(
159 &variable.name()?,
160 PropertyOptions::INSERT_IF_MISSING.bits(),
161 &variable.clone_property("", PropertyOptions::NONE.bits())?,
162 )?;
163 println!(
164 " variable: {} = {:?}",
165 variable.name()?,
166 locals.get_val_string(&variable.name()?, PropertyOptions::NONE.bits())?
167 );
168
169 // A file that does not believe it changed will not write anything.
170 sequence_file
171 .as_property_object_file()?
172 .inc_change_count()?;
173 Ok(())
174}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}Sourcepub fn set_name(&self, name: &str) -> Result<(), Error>
pub fn set_name(&self, name: &str) -> Result<(), Error>
Examples found in repository?
More examples
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}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}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}Sourcepub fn get_num_steps(&self, group: StepGroup) -> Result<i32, Error>
pub fn get_num_steps(&self, group: StepGroup) -> Result<i32, Error>
How many steps a group holds (Sequence.GetNumSteps).
§Errors
Error if the COM call fails or returns an unexpected type.
Examples found in repository?
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}More examples
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}
86
87/// Waits for the run, pumping the thread's messages and draining the engine's.
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}
108
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}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}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}62fn add_numeric_limit_test(
63 engine: &Engine,
64 sequence: &Sequence,
65 name: &str,
66 data_source: &str,
67 low: f64,
68 high: f64,
69) -> Result<(), Error> {
70 let step = engine.new_step(AdapterKeyName::NoneAdapter.as_str(), "NumericLimitTest")?;
71 step.set_name(name)?;
72 step.set_record_result(true)?;
73
74 let properties = step.as_property_object()?;
75 properties.set_val_string("DataSource", insert_if_missing(), data_source)?;
76 properties.set_val_number("Limits.Low", insert_if_missing(), low)?;
77 properties.set_val_number("Limits.High", insert_if_missing(), high)?;
78
79 sequence.insert_step(
80 &step,
81 sequence.get_num_steps(StepGroup::Main)?,
82 StepGroup::Main,
83 )
84}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}Sourcepub fn get_step(&self, index: i32, group: StepGroup) -> Result<Step, Error>
pub fn get_step(&self, index: i32, group: StepGroup) -> Result<Step, Error>
A step by position within a group (Sequence.GetStep).
§Errors
Error if the index is out of range or the COM call fails.
Examples found in repository?
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}More examples
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}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}Sourcepub fn insert_step(
&self,
step: &Step,
index: i32,
group: StepGroup,
) -> Result<(), Error>
pub fn insert_step( &self, step: &Step, index: i32, group: StepGroup, ) -> Result<(), Error>
Places a step in a group at a position (Sequence.InsertStep).
§Errors
Error if the index is out of range, the step is not a live object,
or the COM call fails.
Examples found in repository?
More examples
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}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}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}62fn add_numeric_limit_test(
63 engine: &Engine,
64 sequence: &Sequence,
65 name: &str,
66 data_source: &str,
67 low: f64,
68 high: f64,
69) -> Result<(), Error> {
70 let step = engine.new_step(AdapterKeyName::NoneAdapter.as_str(), "NumericLimitTest")?;
71 step.set_name(name)?;
72 step.set_record_result(true)?;
73
74 let properties = step.as_property_object()?;
75 properties.set_val_string("DataSource", insert_if_missing(), data_source)?;
76 properties.set_val_number("Limits.Low", insert_if_missing(), low)?;
77 properties.set_val_number("Limits.High", insert_if_missing(), high)?;
78
79 sequence.insert_step(
80 &step,
81 sequence.get_num_steps(StepGroup::Main)?,
82 StepGroup::Main,
83 )
84}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}Sourcepub fn insert_step_from_template(
&self,
template: &PropertyObject,
index: i32,
group: StepGroup,
) -> Result<Step, Error>
pub fn insert_step_from_template( &self, template: &PropertyObject, index: i32, group: StepGroup, ) -> Result<Step, Error>
Inserts a copy of a template step and returns the copy
(PropertyObject.Clone + Sequence.InsertStep).
Composed rather than a COM member of its own, because the raw sequence
has a trap in it. Clone lives on PropertyObject, so a copied step
arrives on the PropertyObject interface, and the Step interface
shares none of its dispatch identifiers. Reading Name off such a copy
would dispatch a step identifier against a property-object interface and
take the process down rather than fail. Inserting first and reading the
step back from the sequence is the route that stays on the right
interface, so this returns the inserted step and never exposes the
intermediate.
The template itself is untouched and can be inserted any number of
times. Each copy still carries the template’s step ID until
Step::create_new_unique_step_id
is called on it.
§Errors
Error if the template is not a live object, the index is out of
range, or a COM call fails.
Examples found in repository?
131fn apply_templates(
132 sequence_file: &SequenceFile,
133 step: &PropertyObject,
134 sequence: &PropertyObject,
135 variable: &PropertyObject,
136) -> Result<(), Error> {
137 let main_sequence = sequence_file.get_sequence_by_name(MAIN_SEQUENCE)?;
138
139 // Inserting returns the copy on the Step interface, so it can be renamed
140 // and re-identified straight away. Without a new step ID the copy would go
141 // on claiming the prototype's identity.
142 let inserted_step = main_sequence.insert_step_from_template(step, 0, StepGroup::Main)?;
143 inserted_step.set_name("Step From Template")?;
144 inserted_step.create_new_unique_step_id()?;
145 println!(" step: {}", inserted_step.name()?);
146
147 let inserted_sequence = sequence_file.insert_sequence_from_template(sequence)?;
148 inserted_sequence.create_new_unique_step_ids()?;
149 println!(
150 " sequence: {} ({} step(s))",
151 inserted_sequence.name()?,
152 inserted_sequence.get_num_steps(StepGroup::Main)?
153 );
154
155 // A variable is the easy case: Locals is a property tree, so a clone drops
156 // straight in under the template's own name.
157 let locals = main_sequence.locals()?;
158 locals.set_property_object(
159 &variable.name()?,
160 PropertyOptions::INSERT_IF_MISSING.bits(),
161 &variable.clone_property("", PropertyOptions::NONE.bits())?,
162 )?;
163 println!(
164 " variable: {} = {:?}",
165 variable.name()?,
166 locals.get_val_string(&variable.name()?, PropertyOptions::NONE.bits())?
167 );
168
169 // A file that does not believe it changed will not write anything.
170 sequence_file
171 .as_property_object_file()?
172 .inc_change_count()?;
173 Ok(())
174}More examples
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}Sourcepub fn as_property_object(&self) -> Result<PropertyObject, Error>
pub fn as_property_object(&self) -> Result<PropertyObject, Error>
Sourcepub fn create_new_unique_step_ids(&self) -> Result<(), Error>
pub fn create_new_unique_step_ids(&self) -> Result<(), Error>
Re-identifies every step in the sequence
(Sequence.CreateNewUniqueStepIds).
The bulk form of
Step::create_new_unique_step_id,
and what a sequence cloned from a template needs: every step in the copy
arrives holding the identity of its counterpart in the original.
§Errors
Error if the COM call fails.
Examples found in repository?
131fn apply_templates(
132 sequence_file: &SequenceFile,
133 step: &PropertyObject,
134 sequence: &PropertyObject,
135 variable: &PropertyObject,
136) -> Result<(), Error> {
137 let main_sequence = sequence_file.get_sequence_by_name(MAIN_SEQUENCE)?;
138
139 // Inserting returns the copy on the Step interface, so it can be renamed
140 // and re-identified straight away. Without a new step ID the copy would go
141 // on claiming the prototype's identity.
142 let inserted_step = main_sequence.insert_step_from_template(step, 0, StepGroup::Main)?;
143 inserted_step.set_name("Step From Template")?;
144 inserted_step.create_new_unique_step_id()?;
145 println!(" step: {}", inserted_step.name()?);
146
147 let inserted_sequence = sequence_file.insert_sequence_from_template(sequence)?;
148 inserted_sequence.create_new_unique_step_ids()?;
149 println!(
150 " sequence: {} ({} step(s))",
151 inserted_sequence.name()?,
152 inserted_sequence.get_num_steps(StepGroup::Main)?
153 );
154
155 // A variable is the easy case: Locals is a property tree, so a clone drops
156 // straight in under the template's own name.
157 let locals = main_sequence.locals()?;
158 locals.set_property_object(
159 &variable.name()?,
160 PropertyOptions::INSERT_IF_MISSING.bits(),
161 &variable.clone_property("", PropertyOptions::NONE.bits())?,
162 )?;
163 println!(
164 " variable: {} = {:?}",
165 variable.name()?,
166 locals.get_val_string(&variable.name()?, PropertyOptions::NONE.bits())?
167 );
168
169 // A file that does not believe it changed will not write anything.
170 sequence_file
171 .as_property_object_file()?
172 .inc_change_count()?;
173 Ok(())
174}Sourcepub fn locals(&self) -> Result<PropertyObject, Error>
pub fn locals(&self) -> Result<PropertyObject, Error>
The sequence’s local variables (Sequence.Locals).
Locals are per call: each invocation of the sequence gets its own copy, so what this returns at edit time is the definition rather than any running instance’s values.
§Errors
Error if the COM call fails or returns an unexpected type.
Examples found in repository?
131fn apply_templates(
132 sequence_file: &SequenceFile,
133 step: &PropertyObject,
134 sequence: &PropertyObject,
135 variable: &PropertyObject,
136) -> Result<(), Error> {
137 let main_sequence = sequence_file.get_sequence_by_name(MAIN_SEQUENCE)?;
138
139 // Inserting returns the copy on the Step interface, so it can be renamed
140 // and re-identified straight away. Without a new step ID the copy would go
141 // on claiming the prototype's identity.
142 let inserted_step = main_sequence.insert_step_from_template(step, 0, StepGroup::Main)?;
143 inserted_step.set_name("Step From Template")?;
144 inserted_step.create_new_unique_step_id()?;
145 println!(" step: {}", inserted_step.name()?);
146
147 let inserted_sequence = sequence_file.insert_sequence_from_template(sequence)?;
148 inserted_sequence.create_new_unique_step_ids()?;
149 println!(
150 " sequence: {} ({} step(s))",
151 inserted_sequence.name()?,
152 inserted_sequence.get_num_steps(StepGroup::Main)?
153 );
154
155 // A variable is the easy case: Locals is a property tree, so a clone drops
156 // straight in under the template's own name.
157 let locals = main_sequence.locals()?;
158 locals.set_property_object(
159 &variable.name()?,
160 PropertyOptions::INSERT_IF_MISSING.bits(),
161 &variable.clone_property("", PropertyOptions::NONE.bits())?,
162 )?;
163 println!(
164 " variable: {} = {:?}",
165 variable.name()?,
166 locals.get_val_string(&variable.name()?, PropertyOptions::NONE.bits())?
167 );
168
169 // A file that does not believe it changed will not write anything.
170 sequence_file
171 .as_property_object_file()?
172 .inc_change_count()?;
173 Ok(())
174}More examples
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(¶meters, "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}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}Sourcepub fn parameters(&self) -> Result<PropertyObject, Error>
pub fn parameters(&self) -> Result<PropertyObject, Error>
The sequence’s parameters (Sequence.Parameters).
§Errors
Error if the COM call fails or returns an unexpected type.
Examples found in repository?
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}
84
85/// Waits for a run to finish, pumping and draining as it goes.
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}
106
107/// Starts a run at the named sequence and prints what it recorded.
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}
139
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
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(¶meters, "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}