1use crate::{
2 types::{DataToGet, MotorDisplacement, OsciData, TipShape, TriggerConfig},
3 MotorDirection, MovementMode, Position, Position3D, ScanAction, Signal,
4 TipShaperConfig,
5};
6use nanonis_rs::signals::SignalIndex;
7use std::{collections::HashMap, time::Duration};
8
9#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
11pub enum TipCheckMethod {
12 SignalBounds { signal: Signal, bounds: (f32, f32) },
14 MultiSignalBounds { signals: Vec<(Signal, (f32, f32))> },
16}
17
18#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
20pub enum SignalStabilityMethod {
21 StandardDeviation { threshold: f32 },
23 RelativeStandardDeviation { threshold_percent: f32 },
25 MovingWindow {
27 window_size: usize,
28 max_variation: f32,
29 },
30 TrendAnalysis { max_slope: f32 },
32 Combined { max_std_dev: f32, max_slope: f32 },
35}
36
37impl Default for SignalStabilityMethod {
38 fn default() -> Self {
39 Self::RelativeStandardDeviation {
40 threshold_percent: 5.0,
41 } }
43}
44
45#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
47pub enum TipStabilityMethod {
48 ExtendedMonitoring {
50 signal: Signal,
51 duration: Duration,
52 sampling_interval: Duration,
53 stability_threshold: f32,
54 },
55 BiasSweepResponse {
62 signal: Signal,
64 bias_range: (f32, f32),
66 bias_steps: u16,
68 step_duration: Duration,
70 allowed_signal_change: f32,
73 },
74}
75
76#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
78pub struct BiasSweepConfig {
79 pub lower_limit: f32,
80 pub upper_limit: f32,
81 pub steps: u16,
82 pub period_ms: u16,
83 pub reset_bias_after: bool,
84 pub z_controller_behavior: u16, }
86
87#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
89pub struct BoundsCheckInfo {
90 pub bounds_used: Vec<(SignalIndex, (f32, f32))>,
91 pub violations: Vec<(SignalIndex, f32, (f32, f32))>, pub all_passed: bool,
93}
94
95#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
97pub struct StabilityResult {
98 pub is_stable: bool,
99 pub method_used: String,
100 pub measured_values: HashMap<Signal, Vec<f32>>, pub analysis_duration: Duration,
102 pub metrics: HashMap<String, f32>, pub potential_damage_detected: bool,
104 pub recommendations: Vec<String>,
105}
106
107#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
109pub struct TipState {
110 pub shape: TipShape, pub measured_signals: HashMap<SignalIndex, f32>, pub metadata: HashMap<String, String>,
113}
114
115#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
117pub struct TCPReaderStatus {
118 pub status: crate::types::TCPLogStatus,
119 pub channels: Vec<i32>,
120 pub oversampling: i32,
121}
122
123#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
125pub struct StableSignal {
126 pub stable_value: f32,
127 pub data_points_used: usize,
128 pub analysis_duration: Duration,
129 pub stability_metrics: HashMap<String, f32>,
130 pub raw_data: Vec<f32>,
131}
132
133#[derive(Debug, Clone)]
136pub enum Action {
137 ReadSignal {
139 signal: Signal,
140 wait_for_newest: bool,
141 },
142
143 ReadSignals {
145 signals: Vec<Signal>,
146 wait_for_newest: bool,
147 },
148
149 ReadSignalNames,
151
152 ReadBias,
154
155 SetBias { voltage: f32 },
157
158 ReadOsci {
160 signal: Signal,
161 trigger: Option<TriggerConfig>,
162 data_to_get: DataToGet,
163 is_stable: Option<fn(&[f64]) -> bool>,
164 },
165
166 ReadPiezoPosition { wait_for_newest_data: bool },
168
169 SetPiezoPosition {
171 position: Position,
172 wait_until_finished: bool,
173 },
174
175 MovePiezoRelative { delta: Position },
177
178 MoveMotorAxis {
181 direction: MotorDirection,
182 steps: u16,
183 blocking: bool,
184 },
185
186 MoveMotor3D {
188 displacement: MotorDisplacement,
189 blocking: bool,
190 },
191
192 MoveMotorClosedLoop {
194 target: Position3D,
195 mode: MovementMode,
196 },
197
198 StopMotor,
200
201 AutoApproach {
204 wait_until_finished: bool,
205 timeout: Duration,
206 center_freq_shift: bool,
208 },
209
210 Withdraw {
212 wait_until_finished: bool,
213 timeout: Duration,
214 },
215
216 SafeReposition { x_steps: i16, y_steps: i16 },
218
219 SetZSetpoint { setpoint: f32 },
221
222 ScanControl { action: ScanAction },
225
226 ReadScanStatus,
228
229 BiasPulse {
232 wait_until_done: bool,
233 pulse_width: Duration,
234 bias_value_v: f32,
235 z_controller_hold: u16,
236 pulse_mode: u16,
237 },
238
239 TipShaper {
241 config: TipShaperConfig,
242 wait_until_finished: bool,
243 timeout: Duration,
244 },
245
246 PulseRetract {
248 pulse_width: Duration,
249 pulse_height_v: f32,
250 },
251
252 Wait { duration: Duration },
254
255 Store { key: String, action: Box<Action> },
258
259 Retrieve { key: String },
261
262 StartTCPLogger,
265
266 StopTCPLogger,
268
269 GetTCPLoggerStatus,
271
272 ConfigureTCPLogger {
274 channels: Vec<i32>,
275 oversampling: i32,
276 },
277
278 CheckTipState { method: TipCheckMethod },
281
282 CheckTipStability {
285 method: TipStabilityMethod,
286 max_duration: Duration,
287 },
288
289 ReadStableSignal {
291 signal: Signal,
292 data_points: Option<usize>,
293 use_new_data: bool,
294 stability_method: SignalStabilityMethod,
295 timeout: Duration,
296 retry_count: Option<u32>,
297 },
298
299 ReachedTargedAmplitude,
301}
302
303#[derive(Debug, Clone)]
305pub enum ActionResult {
306 Value(f64),
308
309 Values(Vec<f64>),
311
312 Text(Vec<String>),
314
315 Status(bool),
317
318 Position(Position),
320
321 OsciData(OsciData),
323
324 Success,
326
327 TCPReaderStatus(TCPReaderStatus),
329
330 TipState(TipState),
332
333 StabilityResult(StabilityResult),
335
336 StableSignal(StableSignal),
338
339 None,
341}
342
343impl ActionResult {
344 pub fn as_f64(&self) -> Option<f64> {
346 match self {
347 ActionResult::Value(v) => Some(*v),
348 ActionResult::Values(values) => {
349 if values.len() == 1 {
350 Some(values[0])
351 } else {
352 None
353 }
354 }
355 _ => None,
356 }
357 }
358
359 pub fn as_bool(&self) -> Option<bool> {
361 match self {
362 ActionResult::Status(b) => Some(*b),
363 _ => None,
364 }
365 }
366
367 pub fn as_position(&self) -> Option<Position> {
369 match self {
370 ActionResult::Position(pos) => Some(*pos),
371 _ => None,
372 }
373 }
374
375 pub fn as_osci_data(&self) -> Option<&OsciData> {
377 match self {
378 ActionResult::OsciData(data) => Some(data),
379 _ => None,
380 }
381 }
382
383 pub fn as_tip_shape(&self) -> Option<TipShape> {
385 match self {
386 ActionResult::TipState(tip_state) => Some(tip_state.shape),
387 _ => None,
388 }
389 }
390
391 pub fn as_tip_state(&self) -> Option<&TipState> {
393 match self {
394 ActionResult::TipState(tip_state) => Some(tip_state),
395 _ => None,
396 }
397 }
398
399 pub fn as_stability_result(&self) -> Option<&StabilityResult> {
401 match self {
402 ActionResult::StabilityResult(result) => Some(result),
403 _ => None,
404 }
405 }
406
407 pub fn as_stable_signal_value(&self) -> Option<f32> {
409 match self {
410 ActionResult::StableSignal(stable) => Some(stable.stable_value),
411 _ => None,
412 }
413 }
414
415 pub fn as_stable_signal(&self) -> Option<&StableSignal> {
417 match self {
418 ActionResult::StableSignal(stable) => Some(stable),
419 _ => None,
420 }
421 }
422
423 pub fn expect_osci_data(self, action: &Action) -> OsciData {
428 match (action, self) {
429 (Action::ReadOsci { .. }, ActionResult::OsciData(data)) => data,
430 (action, result) => panic!(
431 "Expected OsciData from action {:?}, got {:?}",
432 action, result
433 ),
434 }
435 }
436
437 pub fn expect_signal_value(self, action: &Action) -> f64 {
439 match (action, self) {
440 (Action::ReadSignal { .. }, ActionResult::Value(v)) => v,
441 (Action::ReadSignal { .. }, ActionResult::Values(mut vs))
442 if vs.len() == 1 =>
443 {
444 vs.pop().unwrap()
445 }
446 (Action::ReadBias, ActionResult::Value(v)) => v,
447 (action, result) => panic!(
448 "Expected signal value from action {:?}, got {:?}",
449 action, result
450 ),
451 }
452 }
453
454 pub fn expect_values(self, action: &Action) -> Vec<f64> {
456 match (action, self) {
457 (Action::ReadSignals { .. }, ActionResult::Values(values)) => {
458 values
459 }
460 (Action::ReadSignal { .. }, ActionResult::Value(v)) => vec![v],
461 (action, result) => {
462 panic!(
463 "Expected values from action {:?}, got {:?}",
464 action, result
465 )
466 }
467 }
468 }
469
470 pub fn expect_position(self, action: &Action) -> Position {
472 match (action, self) {
473 (Action::ReadPiezoPosition { .. }, ActionResult::Position(pos)) => {
474 pos
475 }
476 (action, result) => panic!(
477 "Expected position from action {:?}, got {:?}",
478 action, result
479 ),
480 }
481 }
482
483 pub fn expect_bias_voltage(self, action: &Action) -> f32 {
485 match (action, self) {
486 (Action::ReadBias, ActionResult::Value(v)) => v as f32,
487 (action, result) => panic!(
488 "Expected bias voltage from action {:?}, got {:?}",
489 action, result
490 ),
491 }
492 }
493
494 pub fn expect_signal_names(self, action: &Action) -> Vec<String> {
496 match (action, self) {
497 (Action::ReadSignalNames, ActionResult::Text(names)) => names,
498 (action, result) => panic!(
499 "Expected signal names from action {:?}, got {:?}",
500 action, result
501 ),
502 }
503 }
504
505 pub fn expect_status(self, action: &Action) -> bool {
507 match (action, self) {
508 (Action::ReadScanStatus, ActionResult::Status(status)) => status,
509 (action, result) => {
510 panic!(
511 "Expected status from action {:?}, got {:?}",
512 action, result
513 )
514 }
515 }
516 }
517
518 pub fn expect_tip_shape(self, action: &Action) -> TipShape {
520 match (action, self) {
521 (
522 Action::CheckTipState { .. },
523 ActionResult::TipState(tip_state),
524 ) => tip_state.shape,
525 (action, result) => {
526 panic!(
527 "Expected tip state from action {:?}, got {:?}",
528 action, result
529 )
530 }
531 }
532 }
533
534 pub fn expect_tip_state(self, action: &Action) -> TipState {
536 match (action, self) {
537 (
538 Action::CheckTipState { .. },
539 ActionResult::TipState(tip_state),
540 ) => tip_state,
541 (action, result) => {
542 panic!(
543 "Expected tip state from action {:?}, got {:?}",
544 action, result
545 )
546 }
547 }
548 }
549
550 pub fn expect_stability_result(self, action: &Action) -> StabilityResult {
552 match (action, self) {
553 (
554 Action::CheckTipStability { .. },
555 ActionResult::StabilityResult(result),
556 ) => result,
557 (action, result) => {
558 panic!(
559 "Expected stability result from action {:?}, got {:?}",
560 action, result
561 )
562 }
563 }
564 }
565
566 pub fn expect_stable_signal_value(self, action: &Action) -> f32 {
568 match (action, self) {
569 (
570 Action::ReadStableSignal { .. },
571 ActionResult::StableSignal(stable),
572 ) => stable.stable_value,
573 (action, result) => {
574 panic!(
575 "Expected stable signal from action {:?}, got {:?}",
576 action, result
577 )
578 }
579 }
580 }
581
582 pub fn expect_stable_signal(self, action: &Action) -> StableSignal {
584 match (action, self) {
585 (
586 Action::ReadStableSignal { .. },
587 ActionResult::StableSignal(stable),
588 ) => stable,
589 (action, result) => {
590 panic!(
591 "Expected stable signal from action {:?}, got {:?}",
592 action, result
593 )
594 }
595 }
596 }
597
598 pub fn expect_tcp_reader_status(self, action: &Action) -> TCPReaderStatus {
600 match (action, self) {
601 (
602 Action::GetTCPLoggerStatus,
603 ActionResult::TCPReaderStatus(status),
604 ) => status,
605 (action, result) => {
606 panic!(
607 "Expected TCP reader status from action {:?}, got {:?}",
608 action, result
609 )
610 }
611 }
612 }
613
614 pub fn try_into_osci_data(
618 self,
619 action: &Action,
620 ) -> Result<OsciData, String> {
621 match (action, self) {
622 (Action::ReadOsci { .. }, ActionResult::OsciData(data)) => Ok(data),
623 (action, result) => Err(format!(
624 "Expected OsciData from action {:?}, got {:?}",
625 action, result
626 )),
627 }
628 }
629
630 pub fn try_into_signal_value(self, action: &Action) -> Result<f64, String> {
632 match (action, self) {
633 (Action::ReadSignal { .. }, ActionResult::Value(v)) => Ok(v),
634 (Action::ReadSignal { .. }, ActionResult::Values(mut vs))
635 if vs.len() == 1 =>
636 {
637 Ok(vs.pop().unwrap())
638 }
639 (Action::ReadBias, ActionResult::Value(v)) => Ok(v),
640 (action, result) => Err(format!(
641 "Expected signal value from action {:?}, got {:?}",
642 action, result
643 )),
644 }
645 }
646
647 pub fn try_into_position(
649 self,
650 action: &Action,
651 ) -> Result<Position, String> {
652 match (action, self) {
653 (Action::ReadPiezoPosition { .. }, ActionResult::Position(pos)) => {
654 Ok(pos)
655 }
656 (action, result) => Err(format!(
657 "Expected position from action {:?}, got {:?}",
658 action, result
659 )),
660 }
661 }
662
663 pub fn try_into_status(self, action: &Action) -> Result<bool, String> {
665 match (action, self) {
666 (Action::ReadScanStatus, ActionResult::Status(status)) => {
667 Ok(status)
668 }
669 (action, result) => Err(format!(
670 "Expected status from action {:?}, got {:?}",
671 action, result
672 )),
673 }
674 }
675
676 pub fn try_into_stability_result(
678 self,
679 action: &Action,
680 ) -> Result<StabilityResult, String> {
681 match (action, self) {
682 (
683 Action::CheckTipStability { .. },
684 ActionResult::StabilityResult(result),
685 ) => Ok(result),
686 (action, result) => Err(format!(
687 "Expected stability result from action {:?}, got {:?}",
688 action, result
689 )),
690 }
691 }
692
693 pub fn try_into_stable_signal_value(
695 self,
696 action: &Action,
697 ) -> Result<f32, String> {
698 match (action, self) {
699 (
700 Action::ReadStableSignal { .. },
701 ActionResult::StableSignal(stable),
702 ) => Ok(stable.stable_value),
703 (action, result) => Err(format!(
704 "Expected stable signal from action {:?}, got {:?}",
705 action, result
706 )),
707 }
708 }
709}
710
711pub trait ExpectFromAction<T> {
715 fn expect_from_action(self, action: &Action) -> T;
716}
717
718impl ExpectFromAction<OsciData> for ActionResult {
719 fn expect_from_action(self, action: &Action) -> OsciData {
720 self.expect_osci_data(action)
721 }
722}
723
724impl ExpectFromAction<f64> for ActionResult {
725 fn expect_from_action(self, action: &Action) -> f64 {
726 self.expect_signal_value(action)
727 }
728}
729
730impl ExpectFromAction<Vec<f64>> for ActionResult {
731 fn expect_from_action(self, action: &Action) -> Vec<f64> {
732 self.expect_values(action)
733 }
734}
735
736impl ExpectFromAction<Position> for ActionResult {
737 fn expect_from_action(self, action: &Action) -> Position {
738 self.expect_position(action)
739 }
740}
741
742impl ExpectFromAction<Vec<String>> for ActionResult {
743 fn expect_from_action(self, action: &Action) -> Vec<String> {
744 self.expect_signal_names(action)
745 }
746}
747
748impl ExpectFromAction<bool> for ActionResult {
749 fn expect_from_action(self, action: &Action) -> bool {
750 self.expect_status(action)
751 }
752}
753
754impl ExpectFromAction<StabilityResult> for ActionResult {
755 fn expect_from_action(self, action: &Action) -> StabilityResult {
756 self.expect_stability_result(action)
757 }
758}
759
760impl ExpectFromAction<f32> for ActionResult {
761 fn expect_from_action(self, action: &Action) -> f32 {
762 match action {
763 Action::ReadStableSignal { .. } => {
764 self.expect_stable_signal_value(action)
765 }
766 _ => self.expect_bias_voltage(action),
767 }
768 }
769}
770
771impl Action {
774 pub fn is_positioning_action(&self) -> bool {
776 matches!(
777 self,
778 Action::SetPiezoPosition { .. }
779 | Action::MovePiezoRelative { .. }
780 | Action::MoveMotorAxis { .. }
781 | Action::MoveMotor3D { .. }
782 | Action::MoveMotorClosedLoop { .. }
783 )
784 }
785
786 pub fn is_read_action(&self) -> bool {
788 matches!(
789 self,
790 Action::ReadSignal { .. }
791 | Action::ReadSignals { .. }
792 | Action::ReadSignalNames
793 | Action::ReadBias
794 | Action::ReadPiezoPosition { .. }
795 | Action::ReadScanStatus
796 | Action::Retrieve { .. }
797 )
798 }
799
800 pub fn is_control_action(&self) -> bool {
802 matches!(
803 self,
804 Action::AutoApproach { .. }
805 | Action::Withdraw { .. }
806 | Action::SafeReposition { .. }
807 | Action::ScanControl { .. }
808 | Action::StopMotor
809 )
810 }
811
812 pub fn modifies_bias(&self) -> bool {
814 matches!(self, Action::SetBias { .. } | Action::BiasPulse { .. })
815 }
816
817 pub fn involves_motor(&self) -> bool {
819 matches!(
820 self,
821 Action::MoveMotorAxis { .. }
822 | Action::MoveMotor3D { .. }
823 | Action::MoveMotorClosedLoop { .. }
824 | Action::SafeReposition { .. }
825 | Action::StopMotor
826 )
827 }
828
829 pub fn involves_piezo(&self) -> bool {
831 matches!(
832 self,
833 Action::SetPiezoPosition { .. }
834 | Action::MovePiezoRelative { .. }
835 | Action::ReadPiezoPosition { .. }
836 )
837 }
838
839 pub fn description(&self) -> String {
841 match self {
842 Action::ReadSignal { signal, .. } => {
843 format!("Read signal {}", signal.index)
844 }
845 Action::ReadSignals { signals, .. } => {
846 let indices: Vec<i32> =
847 signals.iter().map(|s| s.index as i32).collect();
848 format!("Read signals: {:?}", indices)
849 }
850 Action::SetBias { voltage } => {
851 format!("Set bias to {:.3}V", voltage)
852 }
853 Action::SetPiezoPosition { position, .. } => {
854 format!(
855 "Set piezo position to ({:.3e}, {:.3e})",
856 position.x, position.y
857 )
858 }
859 Action::MoveMotorAxis {
860 direction,
861 steps,
862 blocking,
863 } => {
864 format!("Move motor {direction:?} {steps} steps with blocking {blocking}")
865 }
866 Action::MoveMotor3D {
867 displacement,
868 blocking,
869 } => {
870 format!(
871 "Move motor 3D displacement ({}, {}, {}) with blocking {blocking}",
872 displacement.x, displacement.y, displacement.z
873 )
874 }
875 Action::AutoApproach {
876 wait_until_finished,
877 timeout,
878 center_freq_shift,
879 } => format!(
880 "Auto approach blocking: {wait_until_finished}, timeout: {:?}, center freq: {center_freq_shift}",
881 timeout
882 ),
883 Action::Withdraw { timeout, .. } => {
884 format!("Withdraw tip (timeout: {}ms)", timeout.as_micros())
885 }
886 Action::SafeReposition { x_steps, y_steps } => {
887 format!(
888 "Safe reposition: move ({}, {}) steps",
889 x_steps, y_steps
890 )
891 }
892 Action::SetZSetpoint { setpoint } => {
893 format!("Set Z setpoint: {:.3e}", setpoint)
894 }
895 Action::Wait { duration } => {
896 format!("Wait {:.1}s", duration.as_secs_f64())
897 }
898 Action::BiasPulse {
899 wait_until_done: _,
900 pulse_width,
901 bias_value_v,
902 z_controller_hold: _,
903 pulse_mode: _,
904 } => {
905 format!(
906 "Bias pulse {:.3}V for {:?}ms",
907 bias_value_v, pulse_width
908 )
909 }
910 Action::TipShaper {
911 config,
912 wait_until_finished,
913 timeout,
914 } => {
915 format!(
916 "Tip shaper: bias {:.1}V, lift {:.0}nm, times {:.1?}s/{:.1?}s (wait: {}, timeout: {:?}ms)",
917 config.bias_v,
918 config.tip_lift_m * 1e9,
919 config.lift_time_1.as_secs_f32(),
920 config.lift_time_2.as_secs_f32(),
921 wait_until_finished,
922 timeout
923 )
924 }
925 Action::PulseRetract {
926 pulse_width,
927 pulse_height_v,
928 } => {
929 format!(
930 "Pulse retract {:.1}V for {:.0?}ms",
931 pulse_height_v, pulse_width
932 )
933 }
934 Action::ReadOsci {
935 signal,
936 trigger,
937 data_to_get,
938 is_stable,
939 } => {
940 let trigger_desc = match trigger {
941 Some(config) => format!("trigger: {:?}", config.mode),
942 None => "no trigger config".to_string(),
943 };
944 let stability_desc = match is_stable {
945 Some(_) => " with custom stability",
946 None => "",
947 };
948 format!(
949 "Read oscilloscope signal {} with {} (mode: {:?}){}",
950 signal.index, trigger_desc, data_to_get, stability_desc
951 )
952 }
953 Action::CheckTipState { method } => match method {
954 TipCheckMethod::SignalBounds { signal, bounds } => {
955 format!(
956 "Check tip state: signal {} bounds ({:.3e}, {:.3e})",
957 signal.index, bounds.0, bounds.1
958 )
959 }
960 TipCheckMethod::MultiSignalBounds { signals } => {
961 format!("Check tip state: {} signal bounds", signals.len())
962 }
963 },
964 Action::CheckTipStability {
965 method,
966 max_duration,
967 } => {
968 let duration_desc =
969 format!("{:.1}s", max_duration.as_secs_f32());
970 match method {
971 TipStabilityMethod::ExtendedMonitoring {
972 signal,
973 duration,
974 ..
975 } => {
976 format!("Check tip stability: extended monitoring signal {} for {:.1}s (max: {})",
977 signal.index, duration.as_secs_f32(), duration_desc)
978 }
979 TipStabilityMethod::BiasSweepResponse {
980 signal,
981 bias_range,
982 bias_steps,
983 step_duration,
984 allowed_signal_change,
985 } => {
986 format!("Check tip stability: bias sweep signal {} from {:.2}V to {:.2}V ({} steps, {:.1}ms period, {:.1}% change allowed, max: {})",
987 signal.index, bias_range.0, bias_range.1, bias_steps, step_duration.as_millis(), allowed_signal_change * 100.0, duration_desc)
988 }
989 }
990 }
991 Action::ReadStableSignal {
992 signal,
993 data_points,
994 use_new_data,
995 stability_method,
996 timeout,
997 retry_count,
998 } => {
999 let points_desc = data_points
1000 .map_or("default".to_string(), |p| p.to_string());
1001 let data_desc = if *use_new_data {
1002 "new data"
1003 } else {
1004 "buffered data"
1005 };
1006 let method_desc = match stability_method {
1007 SignalStabilityMethod::StandardDeviation { threshold } => {
1008 format!("std dev {:.3e}", threshold)
1009 }
1010 SignalStabilityMethod::RelativeStandardDeviation {
1011 threshold_percent,
1012 } => {
1013 format!("rel std {:.1}%", threshold_percent)
1014 }
1015 SignalStabilityMethod::MovingWindow {
1016 window_size,
1017 max_variation,
1018 } => {
1019 format!(
1020 "window {}pts, max var {:.3e}",
1021 window_size, max_variation
1022 )
1023 }
1024 SignalStabilityMethod::TrendAnalysis { max_slope } => {
1025 format!("trend analysis, max slope {:.3e}", max_slope)
1026 }
1027 SignalStabilityMethod::Combined {
1028 max_std_dev,
1029 max_slope,
1030 } => {
1031 format!(
1032 "combined: std dev {:.3e}, slope {:.3e}",
1033 max_std_dev, max_slope
1034 )
1035 }
1036 };
1037 let retry_desc = retry_count
1038 .map_or("no retry".to_string(), |r| {
1039 format!("{} retries", r)
1040 });
1041 format!(
1042 "Get stable signal {} ({} points, {}, {}, timeout {:.1}s, {})",
1043 signal.index,
1044 points_desc,
1045 data_desc,
1046 method_desc,
1047 timeout.as_secs_f32(),
1048 retry_desc
1049 )
1050 }
1051 _ => format!("{:?}", self),
1052 }
1053 }
1054}
1055
1056#[cfg(test)]
1057mod tests {
1058 use super::*;
1059
1060 #[test]
1061 fn test_action_result_extraction() {
1062 let bias_result = ActionResult::Value(2.5);
1063 assert_eq!(bias_result.as_f64(), Some(2.5));
1064
1065 let position_result =
1066 ActionResult::Position(Position { x: 1e-9, y: 2e-9 });
1067 assert_eq!(
1068 position_result.as_position(),
1069 Some(Position { x: 1e-9, y: 2e-9 })
1070 );
1071 }
1072}
1073
1074#[derive(Debug, Clone)]
1076pub struct ActionChain {
1077 actions: Vec<Action>,
1078 name: Option<String>,
1079}
1080
1081impl ActionChain {
1082 pub fn new(actions: Vec<Action>) -> Self {
1084 Self {
1085 actions,
1086 name: None,
1087 }
1088 }
1089
1090 pub fn from_actions(actions: impl IntoIterator<Item = Action>) -> Self {
1092 Self::new(actions.into_iter().collect())
1093 }
1094
1095 pub fn named(actions: Vec<Action>, name: impl Into<String>) -> Self {
1097 Self {
1098 actions,
1099 name: Some(name.into()),
1100 }
1101 }
1102
1103 pub fn empty() -> Self {
1105 Self::new(vec![])
1106 }
1107
1108 pub fn actions(&self) -> &[Action] {
1112 &self.actions
1113 }
1114
1115 pub fn actions_mut(&mut self) -> &mut Vec<Action> {
1117 &mut self.actions
1118 }
1119
1120 pub fn push(&mut self, action: Action) {
1122 self.actions.push(action);
1123 }
1124
1125 pub fn extend(&mut self, actions: impl IntoIterator<Item = Action>) {
1127 self.actions.extend(actions);
1128 }
1129
1130 pub fn insert(&mut self, index: usize, action: Action) {
1132 self.actions.insert(index, action);
1133 }
1134
1135 pub fn remove(&mut self, index: usize) -> Action {
1137 self.actions.remove(index)
1138 }
1139
1140 pub fn pop(&mut self) -> Option<Action> {
1142 self.actions.pop()
1143 }
1144
1145 pub fn clear(&mut self) {
1147 self.actions.clear();
1148 }
1149
1150 pub fn chain_with(mut self, other: ActionChain) -> Self {
1152 self.actions.extend(other.actions);
1153 self
1154 }
1155
1156 pub fn iter(&self) -> std::slice::Iter<'_, Action> {
1158 self.actions.iter()
1159 }
1160
1161 pub fn iter_mut(&mut self) -> std::slice::IterMut<'_, Action> {
1163 self.actions.iter_mut()
1164 }
1165
1166 pub fn name(&self) -> Option<&str> {
1170 self.name.as_deref()
1171 }
1172
1173 pub fn set_name(&mut self, name: impl Into<String>) {
1175 self.name = Some(name.into());
1176 }
1177
1178 pub fn len(&self) -> usize {
1180 self.actions.len()
1181 }
1182
1183 pub fn is_empty(&self) -> bool {
1185 self.actions.is_empty()
1186 }
1187
1188 pub fn positioning_actions(&self) -> Vec<&Action> {
1192 self.actions
1193 .iter()
1194 .filter(|a| a.is_positioning_action())
1195 .collect()
1196 }
1197
1198 pub fn read_actions(&self) -> Vec<&Action> {
1199 self.actions.iter().filter(|a| a.is_read_action()).collect()
1200 }
1201
1202 pub fn control_actions(&self) -> Vec<&Action> {
1203 self.actions
1204 .iter()
1205 .filter(|a| a.is_control_action())
1206 .collect()
1207 }
1208
1209 pub fn involves_motor(&self) -> bool {
1211 self.actions.iter().any(|a| a.involves_motor())
1212 }
1213
1214 pub fn involves_piezo(&self) -> bool {
1216 self.actions.iter().any(|a| a.involves_piezo())
1217 }
1218
1219 pub fn modifies_bias(&self) -> bool {
1221 self.actions.iter().any(|a| a.modifies_bias())
1222 }
1223
1224 pub fn summary(&self) -> String {
1226 if let Some(name) = &self.name {
1227 format!("{} ({} actions)", name, self.len())
1228 } else {
1229 format!("Action chain with {} actions", self.len())
1230 }
1231 }
1232
1233 pub fn analysis(&self) -> ChainAnalysis {
1235 ChainAnalysis {
1236 total_actions: self.len(),
1237 positioning_actions: self.positioning_actions().len(),
1238 read_actions: self.read_actions().len(),
1239 control_actions: self.control_actions().len(),
1240 involves_motor: self.involves_motor(),
1241 involves_piezo: self.involves_piezo(),
1242 modifies_bias: self.modifies_bias(),
1243 }
1244 }
1245}
1246
1247#[derive(Debug, Clone)]
1249pub struct ChainAnalysis {
1250 pub total_actions: usize,
1251 pub positioning_actions: usize,
1252 pub read_actions: usize,
1253 pub control_actions: usize,
1254 pub involves_motor: bool,
1255 pub involves_piezo: bool,
1256 pub modifies_bias: bool,
1257}
1258
1259impl IntoIterator for ActionChain {
1262 type Item = Action;
1263 type IntoIter = std::vec::IntoIter<Action>;
1264
1265 fn into_iter(self) -> Self::IntoIter {
1266 self.actions.into_iter()
1267 }
1268}
1269
1270impl<'a> IntoIterator for &'a ActionChain {
1271 type Item = &'a Action;
1272 type IntoIter = std::slice::Iter<'a, Action>;
1273
1274 fn into_iter(self) -> Self::IntoIter {
1275 self.actions.iter()
1276 }
1277}
1278
1279impl FromIterator<Action> for ActionChain {
1280 fn from_iter<T: IntoIterator<Item = Action>>(iter: T) -> Self {
1281 Self::from_actions(iter)
1282 }
1283}
1284
1285impl From<Vec<Action>> for ActionChain {
1286 fn from(actions: Vec<Action>) -> Self {
1287 Self::new(actions)
1288 }
1289}
1290
1291impl ActionChain {
1294 pub fn system_status_check() -> Self {
1296 ActionChain::named(
1297 vec![
1298 Action::ReadSignalNames,
1299 Action::ReadBias,
1300 Action::ReadPiezoPosition {
1301 wait_for_newest_data: true,
1302 },
1303 ],
1304 "System status check",
1305 )
1306 }
1307
1308 pub fn safe_tip_approach() -> Self {
1310 ActionChain::named(
1311 vec![
1312 Action::ReadPiezoPosition {
1313 wait_for_newest_data: true,
1314 },
1315 Action::AutoApproach {
1316 wait_until_finished: true,
1317 timeout: Duration::from_secs(300),
1318 center_freq_shift: false,
1319 },
1320 Action::Wait {
1321 duration: Duration::from_millis(500),
1322 },
1323 Action::ReadSignal {
1324 signal: Signal::new("Bias".to_string(), 24, None).unwrap(),
1325 wait_for_newest: true,
1326 }, Action::ReadSignal {
1328 signal: Signal::new("Current".to_string(), 0, None)
1329 .unwrap(),
1330 wait_for_newest: true,
1331 }, ],
1333 "Safe tip approach",
1334 )
1335 }
1336
1337 pub fn move_and_approach(target: Position) -> Self {
1339 ActionChain::named(
1340 vec![
1341 Action::SetPiezoPosition {
1342 position: target,
1343 wait_until_finished: true,
1344 },
1345 Action::Wait {
1346 duration: Duration::from_millis(100),
1347 },
1348 Action::AutoApproach {
1349 wait_until_finished: true,
1350 timeout: Duration::from_secs(300),
1351 center_freq_shift: false,
1352 },
1353 Action::ReadSignal {
1354 signal: Signal::new("Bias".to_string(), 24, None).unwrap(),
1355 wait_for_newest: true,
1356 },
1357 ],
1358 format!(
1359 "Move to ({:.1e}, {:.1e}) and approach",
1360 target.x, target.y
1361 ),
1362 )
1363 }
1364
1365 pub fn bias_pulse_sequence(voltage: f32, duration_ms: u32) -> Self {
1367 ActionChain::named(
1368 vec![
1369 Action::ReadBias,
1370 Action::SetBias { voltage },
1371 Action::Wait {
1372 duration: Duration::from_millis(50),
1373 },
1374 Action::Wait {
1375 duration: Duration::from_millis(duration_ms as u64),
1376 },
1377 Action::SetBias { voltage: 0.0 },
1378 ],
1379 format!("Bias pulse {:.3}V for {}ms", voltage, duration_ms),
1380 )
1381 }
1382
1383 pub fn position_survey(positions: Vec<Position>) -> Self {
1385 let position_count = positions.len(); let mut actions = Vec::new();
1387
1388 for pos in positions {
1389 actions.extend([
1390 Action::SetPiezoPosition {
1391 position: pos,
1392 wait_until_finished: true,
1393 },
1394 Action::Wait {
1395 duration: Duration::from_millis(100),
1396 },
1397 Action::AutoApproach {
1398 wait_until_finished: true,
1399 timeout: Duration::from_secs(300),
1400 center_freq_shift: false,
1401 },
1402 Action::ReadSignal {
1403 signal: Signal::new("Bias".to_string(), 24, None).unwrap(),
1404 wait_for_newest: true,
1405 }, Action::ReadSignal {
1407 signal: Signal::new("Current".to_string(), 0, None)
1408 .unwrap(),
1409 wait_for_newest: true,
1410 }, Action::Withdraw {
1412 wait_until_finished: true,
1413 timeout: Duration::from_secs(5),
1414 },
1415 ]);
1416 }
1417
1418 ActionChain::named(
1419 actions,
1420 format!("Position survey ({} points)", position_count),
1421 )
1422 }
1423
1424 pub fn tip_recovery_sequence() -> Self {
1426 ActionChain::named(
1427 vec![
1428 Action::Withdraw {
1429 wait_until_finished: true,
1430 timeout: Duration::from_secs(5),
1431 },
1432 Action::MovePiezoRelative {
1433 delta: Position { x: 3e-9, y: 3e-9 },
1434 },
1435 Action::Wait {
1436 duration: Duration::from_millis(200),
1437 },
1438 Action::AutoApproach {
1439 wait_until_finished: true,
1440 timeout: Duration::from_secs(300),
1441 center_freq_shift: false,
1442 },
1443 Action::ReadSignal {
1444 signal: Signal::new("Bias".to_string(), 24, None).unwrap(),
1445 wait_for_newest: true,
1446 },
1447 ],
1448 "Tip recovery sequence",
1449 )
1450 }
1451}
1452
1453#[cfg(test)]
1454mod chain_tests {
1455 use super::*;
1456 use crate::types::MotorDirection;
1457
1458 #[test]
1459 fn test_vec_foundation() {
1460 let mut chain = ActionChain::new(vec![
1462 Action::ReadBias,
1463 Action::SetBias { voltage: 1.0 },
1464 ]);
1465
1466 assert_eq!(chain.len(), 2);
1467
1468 chain.push(Action::AutoApproach {
1470 wait_until_finished: true,
1471 timeout: Duration::from_secs(300),
1472 center_freq_shift: false,
1473 });
1474 assert_eq!(chain.len(), 3);
1475
1476 let action = chain.pop().unwrap();
1477 assert!(matches!(
1478 action,
1479 Action::AutoApproach {
1480 wait_until_finished: true,
1481 timeout: _,
1482 center_freq_shift: _,
1483 }
1484 ));
1485 assert_eq!(chain.len(), 2);
1486
1487 chain.extend([
1489 Action::Wait {
1490 duration: Duration::from_millis(100),
1491 },
1492 Action::ReadBias,
1493 ]);
1494 assert_eq!(chain.len(), 4);
1495 }
1496
1497 #[test]
1498 fn test_simple_construction() {
1499 let chain = ActionChain::named(
1500 vec![
1501 Action::ReadBias,
1502 Action::SetBias { voltage: 1.0 },
1503 Action::Wait {
1504 duration: Duration::from_millis(100),
1505 },
1506 Action::AutoApproach {
1507 wait_until_finished: true,
1508 timeout: Duration::from_secs(300),
1509 center_freq_shift: false,
1510 },
1511 ],
1512 "Test chain",
1513 );
1514
1515 assert_eq!(chain.name(), Some("Test chain"));
1516 assert_eq!(chain.len(), 4);
1517
1518 let analysis = chain.analysis();
1519 assert_eq!(analysis.total_actions, 4);
1520 assert_eq!(analysis.read_actions, 1);
1521 assert_eq!(analysis.control_actions, 1);
1522 assert!(analysis.modifies_bias);
1523 }
1524
1525 #[test]
1526 fn test_programmatic_generation() {
1527 let mut chain = ActionChain::empty();
1529
1530 for _ in 0..3 {
1531 chain.push(Action::MoveMotorAxis {
1532 direction: MotorDirection::XPlus,
1533 steps: 10,
1534 blocking: true,
1535 });
1536 chain.push(Action::Wait {
1537 duration: Duration::from_millis(100),
1538 });
1539 }
1540
1541 assert_eq!(chain.len(), 6);
1542 assert!(chain.involves_motor());
1543
1544 let actions: Vec<Action> = (0..5).map(|_| Action::ReadBias).collect();
1546
1547 let iter_chain: ActionChain = actions.into_iter().collect();
1548 assert_eq!(iter_chain.len(), 5);
1549 }
1550
1551 #[test]
1552 fn test_pre_built_patterns() {
1553 let status_check = ActionChain::system_status_check();
1554 assert!(status_check.name().is_some());
1555 assert!(!status_check.is_empty());
1556
1557 let approach = ActionChain::safe_tip_approach();
1558 assert!(!approach.control_actions().is_empty());
1559
1560 let positions =
1561 vec![Position { x: 1e-9, y: 1e-9 }, Position { x: 2e-9, y: 2e-9 }];
1562 let survey = ActionChain::position_survey(positions);
1563 assert_eq!(survey.len(), 12); }
1565
1566 #[test]
1567 fn test_chain_analysis() {
1568 let chain = ActionChain::new(vec![
1569 Action::MoveMotorAxis {
1570 direction: MotorDirection::XPlus,
1571 steps: 100,
1572 blocking: true,
1573 },
1574 Action::SetPiezoPosition {
1575 position: Position { x: 1e-9, y: 1e-9 },
1576 wait_until_finished: true,
1577 },
1578 Action::ReadBias,
1579 Action::AutoApproach {
1580 wait_until_finished: true,
1581 timeout: Duration::from_secs(1),
1582 center_freq_shift: false,
1583 },
1584 Action::SetBias { voltage: 1.5 },
1585 ]);
1586
1587 let analysis = chain.analysis();
1588 assert_eq!(analysis.total_actions, 5);
1589 assert_eq!(analysis.positioning_actions, 2);
1590 assert_eq!(analysis.read_actions, 1);
1591 assert_eq!(analysis.control_actions, 1);
1592 assert!(analysis.involves_motor);
1593 assert!(analysis.involves_piezo);
1594 assert!(analysis.modifies_bias);
1595 }
1596
1597 #[test]
1598 fn test_iteration() {
1599 let chain = ActionChain::new(vec![
1600 Action::ReadBias,
1601 Action::AutoApproach {
1602 wait_until_finished: true,
1603 timeout: Duration::from_secs(1),
1604 center_freq_shift: false,
1605 },
1606 Action::Wait {
1607 duration: Duration::from_millis(100),
1608 },
1609 ]);
1610
1611 let mut count = 0;
1613 for _ in &chain {
1614 count += 1;
1615 }
1617 assert_eq!(count, 3);
1618
1619 let actions: Vec<Action> = chain.into_iter().collect();
1621 assert_eq!(actions.len(), 3);
1622 }
1623
1624 #[test]
1625 fn test_from_vec_action() {
1626 let actions = vec![
1628 Action::ReadBias,
1629 Action::SetBias { voltage: 1.5 },
1630 Action::AutoApproach {
1631 wait_until_finished: true,
1632 timeout: Duration::from_secs(1),
1633 center_freq_shift: false,
1634 },
1635 ];
1636
1637 let chain: ActionChain = actions.into();
1638 assert_eq!(chain.len(), 3);
1639 assert!(chain.name().is_none());
1640
1641 let vec_actions = vec![
1643 Action::ReadBias,
1644 Action::Wait {
1645 duration: Duration::from_millis(50),
1646 },
1647 ];
1648
1649 fn accepts_into_action_chain(_chain: impl Into<ActionChain>) {
1651 }
1653
1654 accepts_into_action_chain(vec_actions);
1655 }
1656}
1657
1658#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
1662pub struct ActionLogEntry {
1663 pub action: String, pub result: ActionLogResult,
1667 pub start_time: chrono::DateTime<chrono::Utc>,
1669 pub duration_ms: u64,
1671 pub metadata: Option<std::collections::HashMap<String, String>>,
1673}
1674
1675#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
1678pub enum ActionLogResult {
1679 Value(f64),
1681 Values(Vec<f64>),
1683 Text(Vec<String>),
1685 Status(bool),
1687 Position { x: f64, y: f64 },
1689 OsciData {
1691 t0: f64,
1692 dt: f64,
1693 size: i32,
1694 data: Vec<f64>,
1695 signal_stats: Option<LoggableSignalStats>,
1696 is_stable: bool,
1697 fallback_value: Option<f64>,
1698 },
1699 ExperimentData {
1701 action_result: Box<ActionLogResult>,
1702 signal_frames: Vec<LoggableTimestampedSignalFrame>,
1703 tcp_config: LoggableTCPLoggerConfig,
1704 action_start_ms: u64, action_end_ms: u64,
1706 total_duration_ms: u64,
1707 },
1708 ChainExperimentData {
1710 action_results: Vec<ActionLogResult>,
1711 signal_frames: Vec<LoggableTimestampedSignalFrame>,
1712 tcp_config: LoggableTCPLoggerConfig,
1713 action_timings: Vec<(u64, u64)>, chain_start_ms: u64,
1715 chain_end_ms: u64,
1716 total_duration_ms: u64,
1717 },
1718 TCPLoggerStatus {
1720 status: String, channels: Vec<i32>,
1722 oversampling: i32,
1723 },
1724 TipState {
1726 shape: TipShape,
1727 measured_signals: std::collections::HashMap<u8, f32>, bounds_info: Option<std::collections::HashMap<String, String>>, },
1730 StableSignal {
1732 stable_value: f32,
1733 data_points_used: usize,
1734 analysis_duration_ms: u64,
1735 stability_metrics: std::collections::HashMap<String, f32>,
1736 raw_data: Vec<f32>, },
1738 Success,
1740 None,
1742 Error(String),
1744}
1745
1746#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
1748pub struct LoggableSignalStats {
1749 pub mean: f64,
1750 pub std_dev: f64,
1751 pub relative_std: f64,
1752 pub window_size: usize,
1753 pub stability_method: String,
1754}
1755
1756#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
1758pub struct LoggableTimestampedSignalFrame {
1759 pub signal_frame: LoggableSignalFrame,
1760 pub timestamp_ms: u64, pub relative_time_ms: u64, }
1763
1764#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
1766pub struct LoggableSignalFrame {
1767 pub counter: u64,
1768 pub data: Vec<f32>,
1769}
1770
1771#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
1773pub struct LoggableTCPLoggerConfig {
1774 pub stream_port: u16,
1775 pub channels: Vec<i32>,
1776 pub oversampling: i32,
1777 pub auto_start: bool,
1778 pub buffer_size: Option<usize>,
1779}
1780
1781impl ActionLogEntry {
1782 pub fn new(
1784 action: &Action,
1785 result: &ActionResult,
1786 start_time: chrono::DateTime<chrono::Utc>,
1787 duration: std::time::Duration,
1788 ) -> Self {
1789 Self {
1790 action: action.description(),
1791 result: ActionLogResult::from_action_result(result),
1792 start_time,
1793 duration_ms: duration.as_millis() as u64,
1794 metadata: None,
1795 }
1796 }
1797
1798 pub fn new_error(
1800 action: &Action,
1801 error: &crate::NanonisError,
1802 start_time: chrono::DateTime<chrono::Utc>,
1803 duration: std::time::Duration,
1804 ) -> Self {
1805 Self {
1806 action: action.description(),
1807 result: ActionLogResult::Error(error.to_string()),
1808 start_time,
1809 duration_ms: duration.as_millis() as u64,
1810 metadata: None,
1811 }
1812 }
1813
1814 pub fn with_metadata(
1816 mut self,
1817 key: impl Into<String>,
1818 value: impl Into<String>,
1819 ) -> Self {
1820 if self.metadata.is_none() {
1821 self.metadata = Some(std::collections::HashMap::new());
1822 }
1823 self.metadata
1824 .as_mut()
1825 .unwrap()
1826 .insert(key.into(), value.into());
1827 self
1828 }
1829}
1830
1831impl ActionLogResult {
1832 pub fn from_action_result(result: &ActionResult) -> Self {
1835 match result {
1836 ActionResult::Value(v) => ActionLogResult::Value(*v),
1837 ActionResult::Values(values) => {
1838 ActionLogResult::Values(values.clone())
1839 }
1840 ActionResult::Text(text) => ActionLogResult::Text(text.clone()),
1841 ActionResult::Status(status) => ActionLogResult::Status(*status),
1842 ActionResult::Position(pos) => {
1843 ActionLogResult::Position { x: pos.x, y: pos.y }
1844 }
1845 ActionResult::OsciData(osci_data) => ActionLogResult::OsciData {
1846 t0: osci_data.t0,
1847 dt: osci_data.dt,
1848 size: osci_data.size,
1849 data: osci_data.data.clone(),
1850 signal_stats: osci_data.signal_stats.as_ref().map(|stats| {
1851 LoggableSignalStats {
1852 mean: stats.mean,
1853 std_dev: stats.std_dev,
1854 relative_std: stats.relative_std,
1855 window_size: stats.window_size,
1856 stability_method: stats.stability_method.clone(),
1857 }
1858 }),
1859 is_stable: osci_data.is_stable,
1860 fallback_value: osci_data.fallback_value,
1861 },
1862 ActionResult::Success => ActionLogResult::Success,
1863 ActionResult::None => ActionLogResult::None,
1864 ActionResult::TCPReaderStatus(tcp_status) => {
1865 ActionLogResult::TCPLoggerStatus {
1866 status: format!("{:?}", tcp_status.status), channels: tcp_status.channels.clone(),
1868 oversampling: tcp_status.oversampling,
1869 }
1870 }
1871 ActionResult::TipState(tip_state) => {
1872 let measured_signals = tip_state
1874 .measured_signals
1875 .iter()
1876 .map(|(signal_idx, value)| (signal_idx.get(), *value))
1877 .collect();
1878
1879 let bounds_info = if !tip_state.metadata.is_empty() {
1881 Some(tip_state.metadata.clone())
1882 } else {
1883 None
1884 };
1885
1886 ActionLogResult::TipState {
1887 shape: tip_state.shape,
1888 measured_signals,
1889 bounds_info,
1890 }
1891 }
1892 ActionResult::StabilityResult(result) => {
1893 let tip_shape = if result.is_stable {
1895 TipShape::Stable
1896 } else {
1897 TipShape::Blunt
1898 };
1899
1900 let measured_signals = result
1902 .measured_values
1903 .iter()
1904 .flat_map(|(signal_idx, values)| {
1905 values.last().map(|&value| (signal_idx.index, value))
1907 })
1908 .collect();
1909
1910 let mut bounds_info = std::collections::HashMap::new();
1912 bounds_info.insert(
1913 "is_stable".to_string(),
1914 result.is_stable.to_string(),
1915 );
1916 bounds_info.insert(
1917 "method".to_string(),
1918 "stability_check".to_string(),
1919 );
1920
1921 ActionLogResult::TipState {
1922 shape: tip_shape,
1923 measured_signals,
1924 bounds_info: Some(bounds_info),
1925 }
1926 }
1927 ActionResult::StableSignal(stable) => {
1928 ActionLogResult::StableSignal {
1930 stable_value: stable.stable_value,
1931 data_points_used: stable.data_points_used,
1932 analysis_duration_ms: stable.analysis_duration.as_millis()
1933 as u64,
1934 stability_metrics: stable.stability_metrics.clone(),
1935 raw_data: stable.raw_data.clone(), }
1937 }
1938 }
1939 }
1940
1941 pub fn from_experiment_data(
1943 exp_data: &crate::types::ExperimentData,
1944 ) -> Self {
1945 let action_result =
1946 Box::new(Self::from_action_result(&exp_data.action_result));
1947
1948 let signal_frames: Vec<LoggableTimestampedSignalFrame> = exp_data
1949 .signal_frames
1950 .iter()
1951 .map(|frame| LoggableTimestampedSignalFrame {
1952 signal_frame: LoggableSignalFrame {
1953 counter: frame.signal_frame.counter,
1954 data: frame.signal_frame.data.clone(),
1955 },
1956 timestamp_ms: chrono::Utc::now().timestamp_millis() as u64, relative_time_ms: frame.relative_time.as_millis() as u64,
1958 })
1959 .collect();
1960
1961 let tcp_config = LoggableTCPLoggerConfig {
1962 stream_port: exp_data.tcp_config.stream_port,
1963 channels: exp_data.tcp_config.channels.clone(),
1964 oversampling: exp_data.tcp_config.oversampling,
1965 auto_start: exp_data.tcp_config.auto_start,
1966 buffer_size: exp_data.tcp_config.buffer_size,
1967 };
1968
1969 ActionLogResult::ExperimentData {
1970 action_result,
1971 signal_frames,
1972 tcp_config,
1973 action_start_ms: chrono::Utc::now().timestamp_millis() as u64, action_end_ms: chrono::Utc::now().timestamp_millis() as u64,
1975 total_duration_ms: exp_data.total_duration.as_millis() as u64,
1976 }
1977 }
1978
1979 pub fn from_chain_experiment_data(
1981 chain_data: &crate::types::ChainExperimentData,
1982 ) -> Self {
1983 let action_results: Vec<ActionLogResult> = chain_data
1984 .action_results
1985 .iter()
1986 .map(Self::from_action_result)
1987 .collect();
1988
1989 let signal_frames: Vec<LoggableTimestampedSignalFrame> = chain_data
1990 .signal_frames
1991 .iter()
1992 .map(|frame| LoggableTimestampedSignalFrame {
1993 signal_frame: LoggableSignalFrame {
1994 counter: frame.signal_frame.counter,
1995 data: frame.signal_frame.data.clone(),
1996 },
1997 timestamp_ms: chrono::Utc::now().timestamp_millis() as u64, relative_time_ms: frame.relative_time.as_millis() as u64,
1999 })
2000 .collect();
2001
2002 let tcp_config = LoggableTCPLoggerConfig {
2003 stream_port: chain_data.tcp_config.stream_port,
2004 channels: chain_data.tcp_config.channels.clone(),
2005 oversampling: chain_data.tcp_config.oversampling,
2006 auto_start: chain_data.tcp_config.auto_start,
2007 buffer_size: chain_data.tcp_config.buffer_size,
2008 };
2009
2010 let action_timings: Vec<(u64, u64)> = chain_data
2011 .action_timings
2012 .iter()
2013 .map(|(_, _)| {
2014 let now = chrono::Utc::now().timestamp_millis() as u64;
2015 (now, now) })
2017 .collect();
2018
2019 ActionLogResult::ChainExperimentData {
2020 action_results,
2021 signal_frames,
2022 tcp_config,
2023 action_timings,
2024 chain_start_ms: chrono::Utc::now().timestamp_millis() as u64, chain_end_ms: chrono::Utc::now().timestamp_millis() as u64,
2026 total_duration_ms: chain_data.total_duration.as_millis() as u64,
2027 }
2028 }
2029}