1use crate::{
2 types::{
3 DataToGet, MovementMode, OsciData, Position, Position3D, ScanAction, SignalIndex,
4 TriggerConfig,
5 },
6 MotorDirection, TipShaperConfig,
7};
8use std::time::Duration;
9
10#[derive(Debug, Clone)]
13pub enum Action {
14 ReadSignal {
16 signal: SignalIndex,
17 wait_for_newest: bool,
18 },
19
20 ReadSignals {
22 signals: Vec<SignalIndex>,
23 wait_for_newest: bool,
24 },
25
26 ReadSignalNames,
28
29 ReadBias,
31
32 SetBias { voltage: f32 },
34
35 ReadOsci {
37 signal: SignalIndex,
38 trigger: Option<TriggerConfig>,
39 data_to_get: DataToGet,
40 is_stable: Option<fn(&[f64]) -> bool>,
41 },
42
43 ReadPiezoPosition { wait_for_newest_data: bool },
45
46 SetPiezoPosition {
48 position: Position,
49 wait_until_finished: bool,
50 },
51
52 MovePiezoRelative { delta: Position },
54
55 MoveMotor {
58 direction: MotorDirection,
59 steps: u16,
60 },
61
62 MoveMotorClosedLoop {
64 target: Position3D,
65 mode: MovementMode,
66 },
67
68 StopMotor,
70
71 AutoApproach {
74 wait_until_finished: bool,
75 timeout: Duration,
76 },
77
78 Withdraw {
80 wait_until_finished: bool,
81 timeout: Duration,
82 },
83
84 SetZSetpoint { setpoint: f32 },
86
87 ScanControl { action: ScanAction },
90
91 ReadScanStatus,
93
94 BiasPulse {
97 wait_until_done: bool,
98 pulse_width: Duration,
99 bias_value_v: f32,
100 z_controller_hold: u16,
101 pulse_mode: u16,
102 },
103
104 TipShaper {
106 config: TipShaperConfig,
107 wait_until_finished: bool,
108 timeout: Duration,
109 },
110
111 PulseRetract {
113 pulse_width: Duration,
114 pulse_height_v: f32,
115 },
116
117 Wait { duration: Duration },
119
120 Store { key: String, action: Box<Action> },
123
124 Retrieve { key: String },
126}
127
128#[derive(Debug, Clone)]
130pub enum ActionResult {
131 Value(f64),
133
134 Values(Vec<f64>),
136
137 Text(Vec<String>),
139
140 Status(bool),
142
143 Position(Position),
145
146 OsciData(OsciData),
148
149 Success,
151
152 None,
154}
155
156impl ActionResult {
157 pub fn as_f64(&self) -> Option<f64> {
159 match self {
160 ActionResult::Value(v) => Some(*v),
161 ActionResult::Values(values) => {
162 if values.len() == 1 {
163 Some(values[0])
164 } else {
165 None
166 }
167 }
168 _ => None,
169 }
170 }
171
172 pub fn as_bool(&self) -> Option<bool> {
174 match self {
175 ActionResult::Status(b) => Some(*b),
176 _ => None,
177 }
178 }
179
180 pub fn as_position(&self) -> Option<Position> {
182 match self {
183 ActionResult::Position(pos) => Some(*pos),
184 _ => None,
185 }
186 }
187
188 pub fn as_osci_data(&self) -> Option<&OsciData> {
190 match self {
191 ActionResult::OsciData(data) => Some(data),
192 _ => None,
193 }
194 }
195
196 pub fn expect_osci_data(self, action: &Action) -> OsciData {
201 match (action, self) {
202 (Action::ReadOsci { .. }, ActionResult::OsciData(data)) => data,
203 (action, result) => panic!(
204 "Expected OsciData from action {:?}, got {:?}",
205 action, result
206 ),
207 }
208 }
209
210 pub fn expect_signal_value(self, action: &Action) -> f64 {
212 match (action, self) {
213 (Action::ReadSignal { .. }, ActionResult::Value(v)) => v,
214 (Action::ReadSignal { .. }, ActionResult::Values(mut vs)) if vs.len() == 1 => {
215 vs.pop().unwrap()
216 }
217 (Action::ReadBias, ActionResult::Value(v)) => v,
218 (action, result) => panic!(
219 "Expected signal value from action {:?}, got {:?}",
220 action, result
221 ),
222 }
223 }
224
225 pub fn expect_values(self, action: &Action) -> Vec<f64> {
227 match (action, self) {
228 (Action::ReadSignals { .. }, ActionResult::Values(values)) => values,
229 (Action::ReadSignal { .. }, ActionResult::Value(v)) => vec![v],
230 (action, result) => {
231 panic!("Expected values from action {:?}, got {:?}", action, result)
232 }
233 }
234 }
235
236 pub fn expect_position(self, action: &Action) -> Position {
238 match (action, self) {
239 (Action::ReadPiezoPosition { .. }, ActionResult::Position(pos)) => pos,
240 (action, result) => panic!(
241 "Expected position from action {:?}, got {:?}",
242 action, result
243 ),
244 }
245 }
246
247 pub fn expect_bias_voltage(self, action: &Action) -> f32 {
249 match (action, self) {
250 (Action::ReadBias, ActionResult::Value(v)) => v as f32,
251 (action, result) => panic!(
252 "Expected bias voltage from action {:?}, got {:?}",
253 action, result
254 ),
255 }
256 }
257
258 pub fn expect_signal_names(self, action: &Action) -> Vec<String> {
260 match (action, self) {
261 (Action::ReadSignalNames, ActionResult::Text(names)) => names,
262 (action, result) => panic!(
263 "Expected signal names from action {:?}, got {:?}",
264 action, result
265 ),
266 }
267 }
268
269 pub fn expect_status(self, action: &Action) -> bool {
271 match (action, self) {
272 (Action::ReadScanStatus, ActionResult::Status(status)) => status,
273 (action, result) => {
274 panic!("Expected status from action {:?}, got {:?}", action, result)
275 }
276 }
277 }
278
279 pub fn try_into_osci_data(self, action: &Action) -> Result<OsciData, String> {
283 match (action, self) {
284 (Action::ReadOsci { .. }, ActionResult::OsciData(data)) => Ok(data),
285 (action, result) => Err(format!(
286 "Expected OsciData from action {:?}, got {:?}",
287 action, result
288 )),
289 }
290 }
291
292 pub fn try_into_signal_value(self, action: &Action) -> Result<f64, String> {
294 match (action, self) {
295 (Action::ReadSignal { .. }, ActionResult::Value(v)) => Ok(v),
296 (Action::ReadSignal { .. }, ActionResult::Values(mut vs)) if vs.len() == 1 => {
297 Ok(vs.pop().unwrap())
298 }
299 (Action::ReadBias, ActionResult::Value(v)) => Ok(v),
300 (action, result) => Err(format!(
301 "Expected signal value from action {:?}, got {:?}",
302 action, result
303 )),
304 }
305 }
306
307 pub fn try_into_position(self, action: &Action) -> Result<Position, String> {
309 match (action, self) {
310 (Action::ReadPiezoPosition { .. }, ActionResult::Position(pos)) => Ok(pos),
311 (action, result) => Err(format!(
312 "Expected position from action {:?}, got {:?}",
313 action, result
314 )),
315 }
316 }
317
318 pub fn try_into_status(self, action: &Action) -> Result<bool, String> {
320 match (action, self) {
321 (Action::ReadScanStatus, ActionResult::Status(status)) => Ok(status),
322 (action, result) => Err(format!(
323 "Expected status from action {:?}, got {:?}",
324 action, result
325 )),
326 }
327 }
328}
329
330pub trait ExpectFromAction<T> {
334 fn expect_from_action(self, action: &Action) -> T;
335}
336
337impl ExpectFromAction<OsciData> for ActionResult {
338 fn expect_from_action(self, action: &Action) -> OsciData {
339 self.expect_osci_data(action)
340 }
341}
342
343impl ExpectFromAction<f64> for ActionResult {
344 fn expect_from_action(self, action: &Action) -> f64 {
345 self.expect_signal_value(action)
346 }
347}
348
349impl ExpectFromAction<Vec<f64>> for ActionResult {
350 fn expect_from_action(self, action: &Action) -> Vec<f64> {
351 self.expect_values(action)
352 }
353}
354
355impl ExpectFromAction<Position> for ActionResult {
356 fn expect_from_action(self, action: &Action) -> Position {
357 self.expect_position(action)
358 }
359}
360
361impl ExpectFromAction<f32> for ActionResult {
362 fn expect_from_action(self, action: &Action) -> f32 {
363 self.expect_bias_voltage(action)
364 }
365}
366
367impl ExpectFromAction<Vec<String>> for ActionResult {
368 fn expect_from_action(self, action: &Action) -> Vec<String> {
369 self.expect_signal_names(action)
370 }
371}
372
373impl ExpectFromAction<bool> for ActionResult {
374 fn expect_from_action(self, action: &Action) -> bool {
375 self.expect_status(action)
376 }
377}
378
379impl Action {
382 pub fn is_positioning_action(&self) -> bool {
384 matches!(
385 self,
386 Action::SetPiezoPosition { .. }
387 | Action::MovePiezoRelative { .. }
388 | Action::MoveMotor { .. }
389 | Action::MoveMotorClosedLoop { .. }
390 )
391 }
392
393 pub fn is_read_action(&self) -> bool {
395 matches!(
396 self,
397 Action::ReadSignal { .. }
398 | Action::ReadSignals { .. }
399 | Action::ReadSignalNames
400 | Action::ReadBias
401 | Action::ReadPiezoPosition { .. }
402 | Action::ReadScanStatus
403 | Action::Retrieve { .. }
404 )
405 }
406
407 pub fn is_control_action(&self) -> bool {
409 matches!(
410 self,
411 Action::AutoApproach { .. }
412 | Action::Withdraw { .. }
413 | Action::ScanControl { .. }
414 | Action::StopMotor
415 )
416 }
417
418 pub fn modifies_bias(&self) -> bool {
420 matches!(self, Action::SetBias { .. } | Action::BiasPulse { .. })
421 }
422
423 pub fn involves_motor(&self) -> bool {
425 matches!(
426 self,
427 Action::MoveMotor { .. } | Action::MoveMotorClosedLoop { .. } | Action::StopMotor
428 )
429 }
430
431 pub fn involves_piezo(&self) -> bool {
433 matches!(
434 self,
435 Action::SetPiezoPosition { .. }
436 | Action::MovePiezoRelative { .. }
437 | Action::ReadPiezoPosition { .. }
438 )
439 }
440
441 pub fn description(&self) -> String {
443 match self {
444 Action::ReadSignal { signal, .. } => {
445 format!("Read signal {}", signal.0)
446 }
447 Action::ReadSignals { signals, .. } => {
448 let indices: Vec<i32> = signals.iter().map(|s| s.0).collect();
449 format!("Read signals: {:?}", indices)
450 }
451 Action::SetBias { voltage } => {
452 format!("Set bias to {:.3}V", voltage)
453 }
454 Action::SetPiezoPosition { position, .. } => {
455 format!(
456 "Set piezo position to ({:.3e}, {:.3e})",
457 position.x, position.y
458 )
459 }
460 Action::MoveMotor { direction, steps } => {
461 format!("Move motor {:?} {} steps", direction, steps)
462 }
463 Action::AutoApproach {
464 wait_until_finished,
465 timeout,
466 } => format!(
467 "Auto approach blocking: {wait_until_finished}, timeout: {:?}",
468 timeout
469 ),
470 Action::Withdraw { timeout, .. } => {
471 format!("Withdraw tip (timeout: {}ms)", timeout.as_micros())
472 }
473 Action::SetZSetpoint { setpoint } => {
474 format!("Set Z setpoint: {:.3e}", setpoint)
475 }
476 Action::Wait { duration } => {
477 format!("Wait {:.1}s", duration.as_secs_f64())
478 }
479 Action::BiasPulse {
480 wait_until_done: _,
481 pulse_width,
482 bias_value_v,
483 z_controller_hold: _,
484 pulse_mode: _,
485 } => {
486 format!("Bias pulse {:.3}V for {:?}ms", bias_value_v, pulse_width)
487 }
488 Action::TipShaper {
489 config,
490 wait_until_finished,
491 timeout,
492 } => {
493 format!(
494 "Tip shaper: bias {:.1}V, lift {:.0}nm, times {:.1?}s/{:.1?}s (wait: {}, timeout: {:?}ms)",
495 config.bias_v,
496 config.tip_lift_m * 1e9,
497 config.lift_time_1.as_secs_f32(),
498 config.lift_time_2.as_secs_f32(),
499 wait_until_finished,
500 timeout
501 )
502 }
503 Action::PulseRetract {
504 pulse_width,
505 pulse_height_v,
506 } => {
507 format!(
508 "Pulse retract {:.1}V for {:.0?}ms",
509 pulse_height_v, pulse_width
510 )
511 }
512 Action::ReadOsci {
513 signal,
514 trigger,
515 data_to_get,
516 is_stable,
517 } => {
518 let trigger_desc = match trigger {
519 Some(config) => format!("trigger: {:?}", config.mode),
520 None => "no trigger config".to_string(),
521 };
522 let stability_desc = match is_stable {
523 Some(_) => " with custom stability",
524 None => "",
525 };
526 format!(
527 "Read oscilloscope signal {} with {} (mode: {:?}){}",
528 signal.0, trigger_desc, data_to_get, stability_desc
529 )
530 }
531 _ => format!("{:?}", self),
532 }
533 }
534}
535
536#[cfg(test)]
537mod tests {
538 use super::*;
539
540 #[test]
541 fn test_action_result_extraction() {
542 let bias_result = ActionResult::Value(2.5);
543 assert_eq!(bias_result.as_f64(), Some(2.5));
544
545 let position_result = ActionResult::Position(Position { x: 1e-9, y: 2e-9 });
546 assert_eq!(
547 position_result.as_position(),
548 Some(Position { x: 1e-9, y: 2e-9 })
549 );
550 }
551}
552
553#[derive(Debug, Clone)]
555pub struct ActionChain {
556 actions: Vec<Action>,
557 name: Option<String>,
558}
559
560impl ActionChain {
561 pub fn new(actions: Vec<Action>) -> Self {
563 Self {
564 actions,
565 name: None,
566 }
567 }
568
569 pub fn from_actions(actions: impl IntoIterator<Item = Action>) -> Self {
571 Self::new(actions.into_iter().collect())
572 }
573
574 pub fn named(actions: Vec<Action>, name: impl Into<String>) -> Self {
576 Self {
577 actions,
578 name: Some(name.into()),
579 }
580 }
581
582 pub fn empty() -> Self {
584 Self::new(vec![])
585 }
586
587 pub fn actions(&self) -> &[Action] {
591 &self.actions
592 }
593
594 pub fn actions_mut(&mut self) -> &mut Vec<Action> {
596 &mut self.actions
597 }
598
599 pub fn push(&mut self, action: Action) {
601 self.actions.push(action);
602 }
603
604 pub fn extend(&mut self, actions: impl IntoIterator<Item = Action>) {
606 self.actions.extend(actions);
607 }
608
609 pub fn insert(&mut self, index: usize, action: Action) {
611 self.actions.insert(index, action);
612 }
613
614 pub fn remove(&mut self, index: usize) -> Action {
616 self.actions.remove(index)
617 }
618
619 pub fn pop(&mut self) -> Option<Action> {
621 self.actions.pop()
622 }
623
624 pub fn clear(&mut self) {
626 self.actions.clear();
627 }
628
629 pub fn chain_with(mut self, other: ActionChain) -> Self {
631 self.actions.extend(other.actions);
632 self
633 }
634
635 pub fn iter(&self) -> std::slice::Iter<'_, Action> {
637 self.actions.iter()
638 }
639
640 pub fn iter_mut(&mut self) -> std::slice::IterMut<'_, Action> {
642 self.actions.iter_mut()
643 }
644
645 pub fn name(&self) -> Option<&str> {
649 self.name.as_deref()
650 }
651
652 pub fn set_name(&mut self, name: impl Into<String>) {
654 self.name = Some(name.into());
655 }
656
657 pub fn len(&self) -> usize {
659 self.actions.len()
660 }
661
662 pub fn is_empty(&self) -> bool {
664 self.actions.is_empty()
665 }
666
667 pub fn positioning_actions(&self) -> Vec<&Action> {
671 self.actions
672 .iter()
673 .filter(|a| a.is_positioning_action())
674 .collect()
675 }
676
677 pub fn read_actions(&self) -> Vec<&Action> {
678 self.actions.iter().filter(|a| a.is_read_action()).collect()
679 }
680
681 pub fn control_actions(&self) -> Vec<&Action> {
682 self.actions
683 .iter()
684 .filter(|a| a.is_control_action())
685 .collect()
686 }
687
688 pub fn involves_motor(&self) -> bool {
690 self.actions.iter().any(|a| a.involves_motor())
691 }
692
693 pub fn involves_piezo(&self) -> bool {
695 self.actions.iter().any(|a| a.involves_piezo())
696 }
697
698 pub fn modifies_bias(&self) -> bool {
700 self.actions.iter().any(|a| a.modifies_bias())
701 }
702
703 pub fn summary(&self) -> String {
705 if let Some(name) = &self.name {
706 format!("{} ({} actions)", name, self.len())
707 } else {
708 format!("Action chain with {} actions", self.len())
709 }
710 }
711
712 pub fn analysis(&self) -> ChainAnalysis {
714 ChainAnalysis {
715 total_actions: self.len(),
716 positioning_actions: self.positioning_actions().len(),
717 read_actions: self.read_actions().len(),
718 control_actions: self.control_actions().len(),
719 involves_motor: self.involves_motor(),
720 involves_piezo: self.involves_piezo(),
721 modifies_bias: self.modifies_bias(),
722 }
723 }
724}
725
726#[derive(Debug, Clone)]
728pub struct ChainAnalysis {
729 pub total_actions: usize,
730 pub positioning_actions: usize,
731 pub read_actions: usize,
732 pub control_actions: usize,
733 pub involves_motor: bool,
734 pub involves_piezo: bool,
735 pub modifies_bias: bool,
736}
737
738impl IntoIterator for ActionChain {
741 type Item = Action;
742 type IntoIter = std::vec::IntoIter<Action>;
743
744 fn into_iter(self) -> Self::IntoIter {
745 self.actions.into_iter()
746 }
747}
748
749impl<'a> IntoIterator for &'a ActionChain {
750 type Item = &'a Action;
751 type IntoIter = std::slice::Iter<'a, Action>;
752
753 fn into_iter(self) -> Self::IntoIter {
754 self.actions.iter()
755 }
756}
757
758impl FromIterator<Action> for ActionChain {
759 fn from_iter<T: IntoIterator<Item = Action>>(iter: T) -> Self {
760 Self::from_actions(iter)
761 }
762}
763
764impl From<Vec<Action>> for ActionChain {
765 fn from(actions: Vec<Action>) -> Self {
766 Self::new(actions)
767 }
768}
769
770impl ActionChain {
773 pub fn system_status_check() -> Self {
775 ActionChain::named(
776 vec![
777 Action::ReadSignalNames,
778 Action::ReadBias,
779 Action::ReadPiezoPosition {
780 wait_for_newest_data: true,
781 },
782 ],
783 "System status check",
784 )
785 }
786
787 pub fn safe_tip_approach() -> Self {
789 ActionChain::named(
790 vec![
791 Action::ReadPiezoPosition {
792 wait_for_newest_data: true,
793 },
794 Action::AutoApproach {
795 wait_until_finished: true,
796 timeout: Duration::from_secs(300),
797 },
798 Action::Wait {
799 duration: Duration::from_millis(500),
800 },
801 Action::ReadSignal {
802 signal: SignalIndex(24),
803 wait_for_newest: true,
804 }, Action::ReadSignal {
806 signal: SignalIndex(0),
807 wait_for_newest: true,
808 }, ],
810 "Safe tip approach",
811 )
812 }
813
814 pub fn move_and_approach(target: Position) -> Self {
816 ActionChain::named(
817 vec![
818 Action::SetPiezoPosition {
819 position: target,
820 wait_until_finished: true,
821 },
822 Action::Wait {
823 duration: Duration::from_millis(100),
824 },
825 Action::AutoApproach {
826 wait_until_finished: true,
827 timeout: Duration::from_secs(300),
828 },
829 Action::ReadSignal {
830 signal: SignalIndex(24),
831 wait_for_newest: true,
832 },
833 ],
834 format!("Move to ({:.1e}, {:.1e}) and approach", target.x, target.y),
835 )
836 }
837
838 pub fn bias_pulse_sequence(voltage: f32, duration_ms: u32) -> Self {
840 ActionChain::named(
841 vec![
842 Action::ReadBias,
843 Action::SetBias { voltage },
844 Action::Wait {
845 duration: Duration::from_millis(50),
846 },
847 Action::Wait {
848 duration: Duration::from_millis(duration_ms as u64),
849 },
850 Action::SetBias { voltage: 0.0 },
851 ],
852 format!("Bias pulse {:.3}V for {}ms", voltage, duration_ms),
853 )
854 }
855
856 pub fn position_survey(positions: Vec<Position>) -> Self {
858 let position_count = positions.len(); let mut actions = Vec::new();
860
861 for pos in positions {
862 actions.extend([
863 Action::SetPiezoPosition {
864 position: pos,
865 wait_until_finished: true,
866 },
867 Action::Wait {
868 duration: Duration::from_millis(100),
869 },
870 Action::AutoApproach {
871 wait_until_finished: true,
872 timeout: Duration::from_secs(300),
873 },
874 Action::ReadSignal {
875 signal: SignalIndex(24),
876 wait_for_newest: true,
877 }, Action::ReadSignal {
879 signal: SignalIndex(0),
880 wait_for_newest: true,
881 }, Action::Withdraw {
883 wait_until_finished: true,
884 timeout: Duration::from_secs(5),
885 },
886 ]);
887 }
888
889 ActionChain::named(
890 actions,
891 format!("Position survey ({} points)", position_count),
892 )
893 }
894
895 pub fn tip_recovery_sequence() -> Self {
897 ActionChain::named(
898 vec![
899 Action::Withdraw {
900 wait_until_finished: true,
901 timeout: Duration::from_secs(5),
902 },
903 Action::MovePiezoRelative {
904 delta: Position { x: 3e-9, y: 3e-9 },
905 },
906 Action::Wait {
907 duration: Duration::from_millis(200),
908 },
909 Action::AutoApproach {
910 wait_until_finished: true,
911 timeout: Duration::from_secs(300),
912 },
913 Action::ReadSignal {
914 signal: SignalIndex(24),
915 wait_for_newest: true,
916 },
917 ],
918 "Tip recovery sequence",
919 )
920 }
921}
922
923#[cfg(test)]
924mod chain_tests {
925 use super::*;
926 use crate::types::MotorDirection;
927
928 #[test]
929 fn test_vec_foundation() {
930 let mut chain = ActionChain::new(vec![Action::ReadBias, Action::SetBias { voltage: 1.0 }]);
932
933 assert_eq!(chain.len(), 2);
934
935 chain.push(Action::AutoApproach {
937 wait_until_finished: true,
938 timeout: Duration::from_secs(300),
939 });
940 assert_eq!(chain.len(), 3);
941
942 let action = chain.pop().unwrap();
943 assert!(matches!(
944 action,
945 Action::AutoApproach {
946 wait_until_finished: true,
947 timeout: _
948 }
949 ));
950 assert_eq!(chain.len(), 2);
951
952 chain.extend([
954 Action::Wait {
955 duration: Duration::from_millis(100),
956 },
957 Action::ReadBias,
958 ]);
959 assert_eq!(chain.len(), 4);
960 }
961
962 #[test]
963 fn test_simple_construction() {
964 let chain = ActionChain::named(
965 vec![
966 Action::ReadBias,
967 Action::SetBias { voltage: 1.0 },
968 Action::Wait {
969 duration: Duration::from_millis(100),
970 },
971 Action::AutoApproach {
972 wait_until_finished: true,
973 timeout: Duration::from_secs(300),
974 },
975 ],
976 "Test chain",
977 );
978
979 assert_eq!(chain.name(), Some("Test chain"));
980 assert_eq!(chain.len(), 4);
981
982 let analysis = chain.analysis();
983 assert_eq!(analysis.total_actions, 4);
984 assert_eq!(analysis.read_actions, 1);
985 assert_eq!(analysis.control_actions, 1);
986 assert!(analysis.modifies_bias);
987 }
988
989 #[test]
990 fn test_programmatic_generation() {
991 let mut chain = ActionChain::empty();
993
994 for _ in 0..3 {
995 chain.push(Action::MoveMotor {
996 direction: MotorDirection::XPlus,
997 steps: 10,
998 });
999 chain.push(Action::Wait {
1000 duration: Duration::from_millis(100),
1001 });
1002 }
1003
1004 assert_eq!(chain.len(), 6);
1005 assert!(chain.involves_motor());
1006
1007 let actions: Vec<Action> = (0..5).map(|_| Action::ReadBias).collect();
1009
1010 let iter_chain: ActionChain = actions.into_iter().collect();
1011 assert_eq!(iter_chain.len(), 5);
1012 }
1013
1014 #[test]
1015 fn test_pre_built_patterns() {
1016 let status_check = ActionChain::system_status_check();
1017 assert!(status_check.name().is_some());
1018 assert!(!status_check.is_empty());
1019
1020 let approach = ActionChain::safe_tip_approach();
1021 assert!(!approach.control_actions().is_empty());
1022
1023 let positions = vec![Position { x: 1e-9, y: 1e-9 }, Position { x: 2e-9, y: 2e-9 }];
1024 let survey = ActionChain::position_survey(positions);
1025 assert_eq!(survey.len(), 12); }
1027
1028 #[test]
1029 fn test_chain_analysis() {
1030 let chain = ActionChain::new(vec![
1031 Action::MoveMotor {
1032 direction: MotorDirection::XPlus,
1033 steps: 100,
1034 },
1035 Action::SetPiezoPosition {
1036 position: Position { x: 1e-9, y: 1e-9 },
1037 wait_until_finished: true,
1038 },
1039 Action::ReadBias,
1040 Action::AutoApproach {
1041 wait_until_finished: true,
1042 timeout: Duration::from_secs(1),
1043 },
1044 Action::SetBias { voltage: 1.5 },
1045 ]);
1046
1047 let analysis = chain.analysis();
1048 assert_eq!(analysis.total_actions, 5);
1049 assert_eq!(analysis.positioning_actions, 2);
1050 assert_eq!(analysis.read_actions, 1);
1051 assert_eq!(analysis.control_actions, 1);
1052 assert!(analysis.involves_motor);
1053 assert!(analysis.involves_piezo);
1054 assert!(analysis.modifies_bias);
1055 }
1056
1057 #[test]
1058 fn test_iteration() {
1059 let chain = ActionChain::new(vec![
1060 Action::ReadBias,
1061 Action::AutoApproach {
1062 wait_until_finished: true,
1063 timeout: Duration::from_secs(1),
1064 },
1065 Action::Wait {
1066 duration: Duration::from_millis(100),
1067 },
1068 ]);
1069
1070 let mut count = 0;
1072 for _ in &chain {
1073 count += 1;
1074 }
1076 assert_eq!(count, 3);
1077
1078 let actions: Vec<Action> = chain.into_iter().collect();
1080 assert_eq!(actions.len(), 3);
1081 }
1082
1083 #[test]
1084 fn test_from_vec_action() {
1085 let actions = vec![
1087 Action::ReadBias,
1088 Action::SetBias { voltage: 1.5 },
1089 Action::AutoApproach {
1090 wait_until_finished: true,
1091 timeout: Duration::from_secs(1),
1092 },
1093 ];
1094
1095 let chain: ActionChain = actions.into();
1096 assert_eq!(chain.len(), 3);
1097 assert!(chain.name().is_none());
1098
1099 let vec_actions = vec![
1101 Action::ReadBias,
1102 Action::Wait {
1103 duration: Duration::from_millis(50),
1104 },
1105 ];
1106
1107 fn accepts_into_action_chain(_chain: impl Into<ActionChain>) {
1109 }
1111
1112 accepts_into_action_chain(vec_actions);
1113 }
1114}