pub struct SequenceFile { /* private fields */ }Expand description
A sequence file held open by the engine.
Obtained from crate::Engine::get_sequence_file_ex. Dropping this value
releases the wrapper’s own reference, but the engine keeps the file in its
cache until it is released explicitly, see
crate::Engine::release_sequence_file_ex.
Implementations§
Source§impl SequenceFile
impl SequenceFile
Sourcepub fn num_sequences(&self) -> Result<i32, Error>
pub fn num_sequences(&self) -> Result<i32, Error>
How many sequences the file contains (NumSequences).
§Errors
Error if the COM call fails or returns an unexpected type.
Examples found in repository?
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}Sourcepub fn insert_sequence(&self, sequence: &Sequence) -> Result<(), Error>
pub fn insert_sequence(&self, sequence: &Sequence) -> Result<(), Error>
Adds a sequence to the file (SequenceFile.InsertSequence).
§Errors
Error if the sequence is not a live object or the COM call fails.
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}More examples
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 insert_sequence_from_template(
&self,
template: &PropertyObject,
) -> Result<Sequence, Error>
pub fn insert_sequence_from_template( &self, template: &PropertyObject, ) -> Result<Sequence, Error>
Inserts a copy of a template sequence and returns the copy
(PropertyObject.Clone + SequenceFile.InsertSequence).
The sequence-level counterpart of
Sequence::insert_step_from_template,
and composed for the same reason: a clone arrives on the
PropertyObject interface, which shares no dispatch identifiers with
Sequence. The copy is looked up by name after insertion so the caller
only ever holds the right interface.
The copy keeps the template’s name, so inserting the same template twice
without renaming the first copy puts two sequences of one name in the
file. Every step in the copy also still carries the template’s step ID, /// see
Sequence::create_new_unique_step_ids.
§Errors
Error if the template is not a live object, has no name, 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}Sourcepub fn as_property_object_file(&self) -> Result<PropertyObjectFile, Error>
pub fn as_property_object_file(&self) -> Result<PropertyObjectFile, Error>
The file seen as a property-object file (AsPropertyObjectFile).
This is the route to the file’s registered types.
§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
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 file_globals_default_values(&self) -> Result<PropertyObject, Error>
pub fn file_globals_default_values(&self) -> Result<PropertyObject, Error>
The file’s edit-time global variables (FileGlobalsDefaultValues).
These are the defaults stored in the file, which is what an editor shows and what this API can change. A running execution works on its own run-time copy instead, and edits made there do not travel back here, /// reach that copy through the execution, not through this method.
§Errors
Error if the COM call fails or returns an unexpected type.
Examples found in repository?
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}Sourcepub fn get_sequence_by_name(&self, name: &str) -> Result<Sequence, Error>
pub fn get_sequence_by_name(&self, name: &str) -> Result<Sequence, Error>
Looks a sequence up by name (GetSequenceByName).
§Errors
Error if no such sequence exists or the COM call fails.
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
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}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}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}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}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}Sourcepub fn save(&self, path: &str) -> Result<(), Error>
pub fn save(&self, path: &str) -> Result<(), Error>
Saves the file, optionally to a new path (Save).
An empty path saves in place.
§Errors
Error if the COM call fails.
Examples found in repository?
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}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}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}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}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}