rusty_tip/
types.rs

1use serde::{Deserialize, Serialize};
2
3use crate::error::NanonisError;
4use std::time::Duration;
5
6#[derive(Debug, Clone)]
7pub enum NanonisValue {
8    U16(u16),
9    I16(i16),
10    U32(u32),
11    I32(i32),
12    F32(f32),
13    F64(f64),
14    String(String),
15    ArrayU16(Vec<u16>),
16    ArrayI16(Vec<i16>),
17    ArrayU32(Vec<u32>),
18    ArrayI32(Vec<i32>),
19    ArrayF32(Vec<f32>),
20    ArrayF64(Vec<f64>),
21    ArrayString(Vec<String>),
22    Array2DF32(Vec<Vec<f32>>),
23}
24
25// Conversion traits for NanonisValue
26impl From<f32> for NanonisValue {
27    fn from(value: f32) -> Self {
28        NanonisValue::F32(value)
29    }
30}
31
32impl From<f64> for NanonisValue {
33    fn from(value: f64) -> Self {
34        NanonisValue::F64(value)
35    }
36}
37
38impl From<u16> for NanonisValue {
39    fn from(value: u16) -> Self {
40        NanonisValue::U16(value)
41    }
42}
43
44impl From<u32> for NanonisValue {
45    fn from(value: u32) -> Self {
46        NanonisValue::U32(value)
47    }
48}
49
50impl From<i16> for NanonisValue {
51    fn from(value: i16) -> Self {
52        NanonisValue::I16(value)
53    }
54}
55
56impl From<i32> for NanonisValue {
57    fn from(value: i32) -> Self {
58        NanonisValue::I32(value)
59    }
60}
61
62impl From<String> for NanonisValue {
63    fn from(value: String) -> Self {
64        NanonisValue::String(value)
65    }
66}
67
68impl From<Vec<f32>> for NanonisValue {
69    fn from(value: Vec<f32>) -> Self {
70        NanonisValue::ArrayF32(value)
71    }
72}
73
74impl From<Vec<String>> for NanonisValue {
75    fn from(value: Vec<String>) -> Self {
76        NanonisValue::ArrayString(value)
77    }
78}
79
80impl From<Vec<i32>> for NanonisValue {
81    fn from(value: Vec<i32>) -> Self {
82        NanonisValue::ArrayI32(value)
83    }
84}
85
86impl TryFrom<NanonisValue> for f32 {
87    type Error = NanonisError;
88
89    fn try_from(value: NanonisValue) -> Result<Self, Self::Error> {
90        match value {
91            NanonisValue::F32(v) => Ok(v),
92            _ => Err(NanonisError::Type(format!("Expected f32, got {value:?}"))),
93        }
94    }
95}
96
97impl TryFrom<NanonisValue> for f64 {
98    type Error = NanonisError;
99
100    fn try_from(value: NanonisValue) -> Result<Self, Self::Error> {
101        match value {
102            NanonisValue::F64(v) => Ok(v),
103            _ => Err(NanonisError::Type(format!("Expected f64, got {value:?}"))),
104        }
105    }
106}
107
108impl TryFrom<NanonisValue> for u16 {
109    type Error = NanonisError;
110
111    fn try_from(value: NanonisValue) -> Result<Self, Self::Error> {
112        match value {
113            NanonisValue::U16(v) => Ok(v),
114            _ => Err(NanonisError::Type(format!("Expected u16, got {value:?}"))),
115        }
116    }
117}
118
119impl TryFrom<NanonisValue> for u32 {
120    type Error = NanonisError;
121
122    fn try_from(value: NanonisValue) -> Result<Self, Self::Error> {
123        match value {
124            NanonisValue::U32(v) => Ok(v),
125            _ => Err(NanonisError::Type(format!("Expected u32, got {value:?}"))),
126        }
127    }
128}
129
130impl TryFrom<NanonisValue> for i16 {
131    type Error = NanonisError;
132
133    fn try_from(value: NanonisValue) -> Result<Self, Self::Error> {
134        match value {
135            NanonisValue::I16(v) => Ok(v),
136            _ => Err(NanonisError::Type(format!("Expected i16, got {value:?}"))),
137        }
138    }
139}
140
141impl TryFrom<NanonisValue> for i32 {
142    type Error = NanonisError;
143
144    fn try_from(value: NanonisValue) -> Result<Self, Self::Error> {
145        match value {
146            NanonisValue::I32(v) => Ok(v),
147            _ => Err(NanonisError::Type(format!("Expected i32, got {value:?}"))),
148        }
149    }
150}
151
152impl TryFrom<NanonisValue> for Vec<f32> {
153    type Error = NanonisError;
154
155    fn try_from(value: NanonisValue) -> Result<Self, Self::Error> {
156        match value {
157            NanonisValue::ArrayF32(v) => Ok(v),
158            _ => Err(NanonisError::Type(format!(
159                "Expected Vec<f32>, got {value:?}"
160            ))),
161        }
162    }
163}
164
165impl TryFrom<NanonisValue> for Vec<String> {
166    type Error = NanonisError;
167
168    fn try_from(value: NanonisValue) -> Result<Self, Self::Error> {
169        match value {
170            NanonisValue::ArrayString(v) => Ok(v),
171            _ => Err(NanonisError::Type(format!(
172                "Expected Vec<String>, got {value:?}"
173            ))),
174        }
175    }
176}
177
178impl TryFrom<NanonisValue> for Vec<i32> {
179    type Error = NanonisError;
180
181    fn try_from(value: NanonisValue) -> Result<Self, Self::Error> {
182        match value {
183            NanonisValue::ArrayI32(v) => Ok(v),
184            _ => Err(NanonisError::Type(format!(
185                "Expected Vec<i32>, got {value:?}"
186            ))),
187        }
188    }
189}
190
191// Convenience methods (keeping these for backwards compatibility)
192impl NanonisValue {
193    /// Extract f32 value with type checking
194    pub fn as_f32(&self) -> Result<f32, NanonisError> {
195        match self {
196            NanonisValue::F32(v) => Ok(*v),
197            _ => Err(NanonisError::Type(format!("Expected f32, got {self:?}"))),
198        }
199    }
200
201    /// Extract f64 value with type checking
202    pub fn as_f64(&self) -> Result<f64, NanonisError> {
203        match self {
204            NanonisValue::F64(v) => Ok(*v),
205            _ => Err(NanonisError::Type(format!("Expected f64, got {self:?}"))),
206        }
207    }
208
209    /// Extract u16 value with type checking
210    pub fn as_u16(&self) -> Result<u16, NanonisError> {
211        match self {
212            NanonisValue::U16(v) => Ok(*v),
213            _ => Err(NanonisError::Type(format!("Expected u16, got {self:?}"))),
214        }
215    }
216
217    /// Extract u32 value with type checking
218    pub fn as_u32(&self) -> Result<u32, NanonisError> {
219        match self {
220            NanonisValue::U32(v) => Ok(*v),
221            _ => Err(NanonisError::Type(format!("Expected u32, got {self:?}"))),
222        }
223    }
224
225    /// Extract i16 value with type checking
226    pub fn as_i16(&self) -> Result<i16, NanonisError> {
227        match self {
228            NanonisValue::I16(v) => Ok(*v),
229            _ => Err(NanonisError::Type(format!("Expected i16, got {self:?}"))),
230        }
231    }
232
233    /// Extract i32 value with type checking
234    pub fn as_i32(&self) -> Result<i32, NanonisError> {
235        match self {
236            NanonisValue::I32(v) => Ok(*v),
237            _ => Err(NanonisError::Type(format!("Expected i32, got {self:?}"))),
238        }
239    }
240
241    /// Extract string array with type checking
242    pub fn as_string_array(&self) -> Result<&[String], NanonisError> {
243        match self {
244            NanonisValue::ArrayString(arr) => Ok(arr),
245            _ => Err(NanonisError::Type(format!(
246                "Expected string array, got {self:?}"
247            ))),
248        }
249    }
250
251    /// Extract f32 array with type checking
252    pub fn as_f32_array(&self) -> Result<&[f32], NanonisError> {
253        match self {
254            NanonisValue::ArrayF32(arr) => Ok(arr),
255            _ => Err(NanonisError::Type(format!(
256                "Expected f32 array, got {self:?}"
257            ))),
258        }
259    }
260
261    /// Extract f64 array with type checking
262    pub fn as_f64_array(&self) -> Result<&[f64], NanonisError> {
263        match self {
264            NanonisValue::ArrayF64(arr) => Ok(arr),
265            _ => Err(NanonisError::Type(format!(
266                "Expected f64 array, got {self:?}"
267            ))),
268        }
269    }
270
271    /// Extract i32 array with type checking
272    pub fn as_i32_array(&self) -> Result<&[i32], NanonisError> {
273        match self {
274            NanonisValue::ArrayI32(arr) => Ok(arr),
275            _ => Err(NanonisError::Type(format!(
276                "Expected i32 array, got {self:?}"
277            ))),
278        }
279    }
280
281    /// Extract string value with type checking
282    pub fn as_string(&self) -> Result<&str, NanonisError> {
283        match self {
284            NanonisValue::String(s) => Ok(s),
285            _ => Err(NanonisError::Type(format!("Expected string, got {self:?}"))),
286        }
287    }
288
289    /// Extract 2D f32 array with type checking
290    pub fn as_f32_2d_array(&self) -> Result<&Vec<Vec<f32>>, NanonisError> {
291        match self {
292            NanonisValue::Array2DF32(arr) => Ok(arr),
293            _ => Err(NanonisError::Type(format!(
294                "Expected 2D f32 array, got {self:?}"
295            ))),
296        }
297    }
298}
299
300/// Signal and Channel Types
301#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
302pub struct SignalIndex(pub i32);
303
304impl SignalIndex {
305    pub fn new(index: i32) -> Result<Self, crate::error::NanonisError> {
306        if (0..=127).contains(&index) {
307            Ok(SignalIndex(index))
308        } else {
309            Err(crate::error::NanonisError::InvalidCommand(format!(
310                "Signal index must be 0-127, got {}",
311                index
312            )))
313        }
314    }
315}
316
317impl From<SignalIndex> for i32 {
318    fn from(signal: SignalIndex) -> Self {
319        signal.0
320    }
321}
322
323impl From<i32> for SignalIndex {
324    fn from(index: i32) -> Self {
325        SignalIndex(index)
326    }
327}
328
329impl From<usize> for SignalIndex {
330    fn from(index: usize) -> Self {
331        SignalIndex(index as i32)
332    }
333}
334
335#[derive(Debug, Clone, Copy, PartialEq, Eq)]
336pub struct ChannelIndex(pub i32);
337
338impl ChannelIndex {
339    pub fn new(index: i32) -> Result<Self, crate::error::NanonisError> {
340        if (0..=23).contains(&index) {
341            Ok(ChannelIndex(index))
342        } else {
343            Err(crate::error::NanonisError::InvalidCommand(format!(
344                "Channel index must be 0-23, got {}",
345                index
346            )))
347        }
348    }
349}
350
351impl From<ChannelIndex> for i32 {
352    fn from(channel: ChannelIndex) -> Self {
353        channel.0
354    }
355}
356
357impl From<i32> for ChannelIndex {
358    fn from(index: i32) -> Self {
359        ChannelIndex(index)
360    }
361}
362
363impl From<usize> for ChannelIndex {
364    fn from(index: usize) -> Self {
365        ChannelIndex(index as i32)
366    }
367}
368
369#[derive(Debug, Clone, Copy, PartialEq, Eq)]
370pub struct OscilloscopeIndex(pub i32);
371
372impl From<OscilloscopeIndex> for i32 {
373    fn from(osci: OscilloscopeIndex) -> Self {
374        osci.0
375    }
376}
377
378impl From<i32> for OscilloscopeIndex {
379    fn from(index: i32) -> Self {
380        OscilloscopeIndex(index)
381    }
382}
383
384impl From<usize> for OscilloscopeIndex {
385    fn from(index: usize) -> Self {
386        OscilloscopeIndex(index as i32)
387    }
388}
389
390/// Motor Control Types
391#[derive(Debug, Clone, Copy, PartialEq, Eq)]
392pub enum MotorDirection {
393    XPlus = 0,
394    XMinus = 1,
395    YPlus = 2,
396    YMinus = 3,
397    ZPlus = 4,
398    ZMinus = 5,
399}
400
401impl From<MotorDirection> for u32 {
402    fn from(direction: MotorDirection) -> Self {
403        direction as u32
404    }
405}
406
407impl TryFrom<u32> for MotorDirection {
408    type Error = crate::error::NanonisError;
409
410    fn try_from(value: u32) -> Result<Self, Self::Error> {
411        match value {
412            0 => Ok(MotorDirection::XPlus),
413            1 => Ok(MotorDirection::XMinus),
414            2 => Ok(MotorDirection::YPlus),
415            3 => Ok(MotorDirection::YMinus),
416            4 => Ok(MotorDirection::ZPlus),
417            5 => Ok(MotorDirection::ZMinus),
418            _ => Err(crate::error::NanonisError::InvalidCommand(format!(
419                "Invalid motor direction: {}",
420                value
421            ))),
422        }
423    }
424}
425
426#[derive(Debug, Clone, Copy, PartialEq, Eq)]
427pub enum MotorGroup {
428    Group1 = 0,
429    Group2 = 1,
430    Group3 = 2,
431    Group4 = 3,
432    Group5 = 4,
433    Group6 = 5,
434}
435
436impl From<MotorGroup> for u32 {
437    fn from(group: MotorGroup) -> Self {
438        group as u32
439    }
440}
441
442impl TryFrom<u32> for MotorGroup {
443    type Error = crate::error::NanonisError;
444
445    fn try_from(value: u32) -> Result<Self, Self::Error> {
446        match value {
447            0 => Ok(MotorGroup::Group1),
448            1 => Ok(MotorGroup::Group2),
449            2 => Ok(MotorGroup::Group3),
450            3 => Ok(MotorGroup::Group4),
451            4 => Ok(MotorGroup::Group5),
452            5 => Ok(MotorGroup::Group6),
453            _ => Err(crate::error::NanonisError::InvalidCommand(format!(
454                "Invalid motor group: {}",
455                value
456            ))),
457        }
458    }
459}
460
461#[derive(Debug, Clone, Copy)]
462pub struct StepCount(pub u16);
463
464impl From<StepCount> for u16 {
465    fn from(steps: StepCount) -> Self {
466        steps.0
467    }
468}
469
470impl From<u16> for StepCount {
471    fn from(steps: u16) -> Self {
472        StepCount(steps)
473    }
474}
475
476impl From<u32> for StepCount {
477    fn from(steps: u32) -> Self {
478        StepCount(steps as u16)
479    }
480}
481
482#[derive(Debug, Clone, Copy)]
483pub struct Frequency(pub f32);
484
485impl Frequency {
486    pub fn hz(value: f32) -> Self {
487        Self(value)
488    }
489}
490
491impl From<Frequency> for f32 {
492    fn from(freq: Frequency) -> Self {
493        freq.0
494    }
495}
496
497impl From<f32> for Frequency {
498    fn from(freq: f32) -> Self {
499        Frequency(freq)
500    }
501}
502
503impl From<f64> for Frequency {
504    fn from(freq: f64) -> Self {
505        Frequency(freq as f32)
506    }
507}
508
509#[derive(Debug, Clone, Copy)]
510pub struct Amplitude(pub f32);
511
512impl Amplitude {
513    pub fn volts(value: f32) -> Self {
514        Self(value)
515    }
516}
517
518impl From<Amplitude> for f32 {
519    fn from(amp: Amplitude) -> Self {
520        amp.0
521    }
522}
523
524impl From<f32> for Amplitude {
525    fn from(amp: f32) -> Self {
526        Amplitude(amp)
527    }
528}
529
530impl From<f64> for Amplitude {
531    fn from(amp: f64) -> Self {
532        Amplitude(amp as f32)
533    }
534}
535
536#[derive(Debug, Clone, Copy, PartialEq, Eq)]
537pub enum MotorAxis {
538    All = 0,
539    X = 1,
540    Y = 2,
541    Z = 3,
542}
543
544impl From<MotorAxis> for u16 {
545    fn from(axis: MotorAxis) -> Self {
546        axis as u16
547    }
548}
549
550// From implementations for common integer types for convenience
551impl From<u16> for MotorAxis {
552    fn from(value: u16) -> Self {
553        match value {
554            0 => MotorAxis::All,
555            1 => MotorAxis::X,
556            2 => MotorAxis::Y,
557            3 => MotorAxis::Z,
558            _ => MotorAxis::All, // Default fallback
559        }
560    }
561}
562
563impl From<i32> for MotorAxis {
564    fn from(value: i32) -> Self {
565        MotorAxis::from(value as u16)
566    }
567}
568
569/// Position Extensions
570#[derive(Debug, Clone, Copy)]
571pub struct Position3D {
572    pub x: f64,
573    pub y: f64,
574    pub z: f64,
575}
576
577impl Position3D {
578    pub fn new(x: f64, y: f64, z: f64) -> Self {
579        Self { x, y, z }
580    }
581
582    pub fn meters(x: f64, y: f64, z: f64) -> Self {
583        Self::new(x, y, z)
584    }
585}
586
587#[derive(Debug, Clone, Copy, PartialEq, Eq)]
588pub enum MovementMode {
589    Relative = 0,
590    Absolute = 1,
591}
592
593impl From<MovementMode> for u32 {
594    fn from(mode: MovementMode) -> Self {
595        mode as u32
596    }
597}
598
599impl TryFrom<u32> for MovementMode {
600    type Error = crate::error::NanonisError;
601
602    fn try_from(value: u32) -> Result<Self, Self::Error> {
603        match value {
604            0 => Ok(MovementMode::Relative),
605            1 => Ok(MovementMode::Absolute),
606            _ => Err(crate::error::NanonisError::InvalidCommand(format!(
607                "Invalid movement mode: {}",
608                value
609            ))),
610        }
611    }
612}
613
614/// Trigger and Timing Types
615#[derive(Debug, Clone, Copy, PartialEq, Eq)]
616pub enum TriggerMode {
617    Immediate = 0,
618    Level = 1,
619    Digital = 2,
620}
621
622impl From<TriggerMode> for u16 {
623    fn from(mode: TriggerMode) -> Self {
624        mode as u16
625    }
626}
627
628// From implementations for common integer types for convenience
629impl From<u16> for TriggerMode {
630    fn from(value: u16) -> Self {
631        match value {
632            0 => TriggerMode::Immediate,
633            1 => TriggerMode::Level,
634            2 => TriggerMode::Digital,
635            _ => TriggerMode::Immediate, // Default fallback
636        }
637    }
638}
639
640impl From<i32> for TriggerMode {
641    fn from(value: i32) -> Self {
642        TriggerMode::from(value as u16)
643    }
644}
645
646#[derive(Debug, Clone, Copy, PartialEq, Eq)]
647pub enum TriggerSlope {
648    Falling = 0,
649    Rising = 1,
650}
651
652impl From<TriggerSlope> for u16 {
653    fn from(slope: TriggerSlope) -> Self {
654        slope as u16
655    }
656}
657
658impl TryFrom<u16> for TriggerSlope {
659    type Error = crate::error::NanonisError;
660
661    fn try_from(value: u16) -> Result<Self, Self::Error> {
662        match value {
663            0 => Ok(TriggerSlope::Falling),
664            1 => Ok(TriggerSlope::Rising),
665            _ => Err(crate::error::NanonisError::InvalidCommand(format!(
666                "Invalid trigger slope: {}",
667                value
668            ))),
669        }
670    }
671}
672
673#[derive(Debug, Clone, Copy)]
674pub struct TriggerLevel(pub f64);
675
676impl From<TriggerLevel> for f64 {
677    fn from(level: TriggerLevel) -> Self {
678        level.0
679    }
680}
681
682impl From<f64> for TriggerLevel {
683    fn from(level: f64) -> Self {
684        TriggerLevel(level)
685    }
686}
687
688impl From<f32> for TriggerLevel {
689    fn from(level: f32) -> Self {
690        TriggerLevel(level as f64)
691    }
692}
693
694#[derive(Debug, Clone, Copy)]
695pub struct SampleCount(pub i32);
696
697impl SampleCount {
698    pub fn new(count: i32) -> Self {
699        Self(count)
700    }
701}
702
703impl From<SampleCount> for i32 {
704    fn from(samples: SampleCount) -> Self {
705        samples.0
706    }
707}
708
709impl From<i32> for SampleCount {
710    fn from(count: i32) -> Self {
711        SampleCount(count)
712    }
713}
714
715impl From<u32> for SampleCount {
716    fn from(count: u32) -> Self {
717        SampleCount(count as i32)
718    }
719}
720
721impl From<usize> for SampleCount {
722    fn from(count: usize) -> Self {
723        SampleCount(count as i32)
724    }
725}
726
727/// Scan Types
728#[derive(Debug, Clone, Copy)]
729pub struct ScanFrame {
730    pub center: Position,
731    pub width_m: f32,
732    pub height_m: f32,
733    pub angle_deg: f32,
734}
735
736impl ScanFrame {
737    pub fn new(center: Position, width_m: f32, height_m: f32, angle_deg: f32) -> Self {
738        Self {
739            center,
740            width_m,
741            height_m,
742            angle_deg,
743        }
744    }
745}
746
747#[derive(Debug, Clone, Copy, PartialEq, Eq)]
748pub enum ScanAction {
749    Start = 0,
750    Stop = 1,
751    Pause = 2,
752    Resume = 3,
753    Freeze = 4,
754    Unfreeze = 5,
755    GoToCenter = 6,
756}
757
758impl From<ScanAction> for u16 {
759    fn from(action: ScanAction) -> Self {
760        action as u16
761    }
762}
763
764impl TryFrom<u16> for ScanAction {
765    type Error = crate::error::NanonisError;
766
767    fn try_from(value: u16) -> Result<Self, Self::Error> {
768        match value {
769            0 => Ok(ScanAction::Start),
770            1 => Ok(ScanAction::Stop),
771            2 => Ok(ScanAction::Pause),
772            3 => Ok(ScanAction::Resume),
773            4 => Ok(ScanAction::Freeze),
774            5 => Ok(ScanAction::Unfreeze),
775            6 => Ok(ScanAction::GoToCenter),
776            _ => Err(crate::error::NanonisError::InvalidCommand(format!(
777                "Invalid scan action: {}",
778                value
779            ))),
780        }
781    }
782}
783
784#[derive(Debug, Clone, Copy, PartialEq, Eq)]
785pub enum ScanDirection {
786    Down = 0,
787    Up = 1,
788}
789
790impl From<ScanDirection> for u32 {
791    fn from(direction: ScanDirection) -> Self {
792        direction as u32
793    }
794}
795
796impl TryFrom<u32> for ScanDirection {
797    type Error = crate::error::NanonisError;
798
799    fn try_from(value: u32) -> Result<Self, Self::Error> {
800        match value {
801            0 => Ok(ScanDirection::Down),
802            1 => Ok(ScanDirection::Up),
803            _ => Err(crate::error::NanonisError::InvalidCommand(format!(
804                "Invalid scan direction: {}",
805                value
806            ))),
807        }
808    }
809}
810
811// Interface Types
812/// Universal Z-controller hold states for SPM operations
813#[derive(Debug, Clone, Copy, PartialEq, Eq)]
814pub enum ZControllerHold {
815    /// Don't modify Z-controller state
816    NoChange = 0,
817    /// Hold Z-controller during operation
818    Hold = 1,
819    /// Release/disable Z-controller during operation
820    Release = 2,
821}
822
823impl From<ZControllerHold> for u16 {
824    fn from(hold: ZControllerHold) -> Self {
825        hold as u16
826    }
827}
828
829/// Universal SPM pulse modes - concepts that apply to any SPM system
830#[derive(Debug, Clone, Copy, PartialEq, Eq)]
831pub enum PulseMode {
832    /// Keep current bias voltage unchanged
833    Keep = 0,
834    /// Add voltage to current bias (relative)
835    Relative = 1,
836    /// Set bias to absolute voltage value
837    Absolute = 2,
838}
839
840impl From<PulseMode> for u16 {
841    fn from(mode: PulseMode) -> Self {
842        mode as u16
843    }
844}
845
846/// position 2d used in
847#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
848pub struct Position {
849    pub x: f64,
850    pub y: f64,
851}
852
853impl Position {
854    pub fn new(x: f64, y: f64) -> Self {
855        Self { x, y }
856    }
857}
858
859/// Session metadata - static information written once per monitoring session
860#[derive(Debug, Clone, Serialize, Deserialize)]
861pub struct SessionMetadata {
862    pub session_id: String,
863    pub signal_names: Vec<String>,   // All signal names
864    pub active_indices: Vec<usize>,  // Which signals are being monitored
865    pub primary_signal_index: usize, // Index of the primary signal
866    pub session_start: f64,          // Session start timestamp
867}
868
869// ==================== Action System Types ====================
870
871/// Motor movement specification
872#[derive(Debug, Clone)]
873pub struct MotorMovement {
874    pub direction: MotorDirection,
875    pub steps: StepCount,
876    pub group: MotorGroup,
877}
878
879impl MotorMovement {
880    pub fn new(direction: MotorDirection, steps: StepCount, group: MotorGroup) -> Self {
881        Self {
882            direction,
883            steps,
884            group,
885        }
886    }
887}
888
889/// Oscilloscope trigger mode for Osci1T and Osci2T
890#[derive(Debug, Clone, Copy, PartialEq, Eq)]
891pub enum OsciTriggerMode {
892    Immediate = 0,
893    Level = 1,
894    Auto = 2,
895}
896
897impl From<OsciTriggerMode> for u16 {
898    fn from(mode: OsciTriggerMode) -> Self {
899        mode as u16
900    }
901}
902
903impl TryFrom<u16> for OsciTriggerMode {
904    type Error = NanonisError;
905
906    fn try_from(value: u16) -> Result<Self, Self::Error> {
907        match value {
908            0 => Ok(OsciTriggerMode::Immediate),
909            1 => Ok(OsciTriggerMode::Level),
910            2 => Ok(OsciTriggerMode::Auto),
911            _ => Err(NanonisError::InvalidCommand(format!(
912                "Invalid oscilloscope trigger mode: {}",
913                value
914            ))),
915        }
916    }
917}
918
919/// Oversampling index for Osci2T
920#[derive(Debug, Clone, Copy, PartialEq, Eq)]
921pub enum OversamplingIndex {
922    Samples50 = 0,
923    Samples20 = 1,
924    Samples10 = 2,
925    Samples5 = 3,
926    Samples2 = 4,
927    Samples1 = 5,
928}
929
930impl From<OversamplingIndex> for u16 {
931    fn from(index: OversamplingIndex) -> Self {
932        index as u16
933    }
934}
935
936impl TryFrom<u16> for OversamplingIndex {
937    type Error = NanonisError;
938
939    fn try_from(value: u16) -> Result<Self, Self::Error> {
940        match value {
941            0 => Ok(OversamplingIndex::Samples50),
942            1 => Ok(OversamplingIndex::Samples20),
943            2 => Ok(OversamplingIndex::Samples10),
944            3 => Ok(OversamplingIndex::Samples5),
945            4 => Ok(OversamplingIndex::Samples2),
946            5 => Ok(OversamplingIndex::Samples1),
947            _ => Err(NanonisError::InvalidCommand(format!(
948                "Invalid oversampling index: {}",
949                value
950            ))),
951        }
952    }
953}
954
955/// Timebase index for oscilloscope operations
956#[derive(Debug, Clone, Copy, PartialEq, Eq)]
957pub struct TimebaseIndex(pub i32);
958
959impl From<TimebaseIndex> for i32 {
960    fn from(index: TimebaseIndex) -> Self {
961        index.0
962    }
963}
964
965impl From<TimebaseIndex> for u16 {
966    fn from(index: TimebaseIndex) -> Self {
967        index.0 as u16
968    }
969}
970
971impl From<i32> for TimebaseIndex {
972    fn from(value: i32) -> Self {
973        TimebaseIndex(value)
974    }
975}
976
977impl From<u16> for TimebaseIndex {
978    fn from(value: u16) -> Self {
979        TimebaseIndex(value as i32)
980    }
981}
982
983/// Data acquisition mode for oscilloscope operations
984///
985/// Note: `DataToGet::Stable` is only supported by the ActionDriver layer,
986/// which implements sophisticated stability detection logic. The SPM interface
987/// layer (spm_impl.rs) will return an error if `Stable` is used directly.
988#[derive(Debug, Clone, Copy, PartialEq, Eq)]
989pub enum DataToGet {
990    Current,
991    NextTrigger,
992    Wait2Triggers,
993    /// Stability detection mode - only supported by ActionDriver
994    ///
995    /// Parameters:
996    /// - `readings`: Number of stable readings required
997    /// - `timeout`: Maximum time to wait for stability
998    Stable {
999        readings: u32,
1000        timeout: Duration,
1001    },
1002}
1003
1004/// TCP Logger status enumeration
1005/// Represents the different states of the TCP Logger module in Nanonis
1006#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1007pub enum TCPLogStatus {
1008    Disconnected = 0,
1009    Idle = 1,
1010    Start = 2,
1011    Stop = 3,
1012    Running = 4,
1013    TCPConnect = 5,
1014    TCPDisconnect = 6,
1015    BufferOverflow = 7,
1016}
1017
1018impl From<TCPLogStatus> for i32 {
1019    fn from(status: TCPLogStatus) -> Self {
1020        status as i32
1021    }
1022}
1023
1024impl TryFrom<i32> for TCPLogStatus {
1025    type Error = NanonisError;
1026
1027    fn try_from(value: i32) -> Result<Self, Self::Error> {
1028        match value {
1029            0 => Ok(TCPLogStatus::Disconnected),
1030            1 => Ok(TCPLogStatus::Idle),
1031            2 => Ok(TCPLogStatus::Start),
1032            3 => Ok(TCPLogStatus::Stop),
1033            4 => Ok(TCPLogStatus::Running),
1034            5 => Ok(TCPLogStatus::TCPConnect),
1035            6 => Ok(TCPLogStatus::TCPDisconnect),
1036            7 => Ok(TCPLogStatus::BufferOverflow),
1037            _ => Err(NanonisError::InvalidCommand(format!(
1038                "Invalid TCP Logger status: {}",
1039                value
1040            ))),
1041        }
1042    }
1043}
1044
1045impl std::fmt::Display for TCPLogStatus {
1046    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1047        let status_str = match self {
1048            TCPLogStatus::Disconnected => "Disconnected",
1049            TCPLogStatus::Idle => "Idle",
1050            TCPLogStatus::Start => "Start",
1051            TCPLogStatus::Stop => "Stop",
1052            TCPLogStatus::Running => "Running",
1053            TCPLogStatus::TCPConnect => "TCP Connect",
1054            TCPLogStatus::TCPDisconnect => "TCP Disconnect",
1055            TCPLogStatus::BufferOverflow => "Buffer Overflow",
1056        };
1057        write!(f, "{}", status_str)
1058    }
1059}
1060
1061/// Trigger configuration for oscilloscope operations
1062#[derive(Debug, Clone, Copy)]
1063pub struct TriggerConfig {
1064    pub mode: OsciTriggerMode,
1065    pub slope: TriggerSlope,
1066    pub level: f64,
1067    pub hysteresis: f64,
1068}
1069
1070impl TriggerConfig {
1071    pub fn new(mode: OsciTriggerMode, slope: TriggerSlope, level: f64, hysteresis: f64) -> Self {
1072        Self {
1073            mode,
1074            slope,
1075            level,
1076            hysteresis,
1077        }
1078    }
1079
1080    pub fn immediate() -> Self {
1081        Self {
1082            mode: OsciTriggerMode::Immediate,
1083            slope: TriggerSlope::Rising,
1084            level: 0.0,
1085            hysteresis: 0.0,
1086        }
1087    }
1088
1089    pub fn level_trigger(level: f64, slope: TriggerSlope) -> Self {
1090        Self {
1091            mode: OsciTriggerMode::Level,
1092            slope,
1093            level,
1094            hysteresis: 0.1,
1095        }
1096    }
1097
1098    pub fn auto_trigger() -> Self {
1099        Self {
1100            mode: OsciTriggerMode::Auto,
1101            slope: TriggerSlope::Rising,
1102            level: 0.0,
1103            hysteresis: 0.1,
1104        }
1105    }
1106}
1107
1108/// Statistical analysis of signal data
1109#[derive(Debug, Clone)]
1110pub struct SignalStats {
1111    pub mean: f64,
1112    pub std_dev: f64,
1113    pub relative_std: f64,
1114    pub window_size: usize,
1115    /// Method used to determine stability: "relative", "absolute", or "both"
1116    pub stability_method: String,
1117}
1118
1119/// Oscilloscope data structure containing timing and measurement information
1120#[derive(Debug, Clone)]
1121pub struct OsciData {
1122    pub t0: f64,
1123    pub dt: f64,
1124    pub size: i32,
1125    pub data: Vec<f64>,
1126    pub signal_stats: Option<SignalStats>,
1127    /// Indicates if this data represents a stable reading (for ReadOsciStable actions)
1128    pub is_stable: bool,
1129    /// Fallback single value when stable oscilloscope data couldn't be obtained
1130    pub fallback_value: Option<f64>,
1131}
1132
1133impl OsciData {
1134    pub fn new(t0: f64, dt: f64, size: i32, data: Vec<f64>) -> Self {
1135        Self {
1136            t0,
1137            dt,
1138            size,
1139            data,
1140            signal_stats: None,
1141            is_stable: true, // Default to stable for regular osci readings
1142            fallback_value: None,
1143        }
1144    }
1145
1146    pub fn new_with_stats(t0: f64, dt: f64, size: i32, data: Vec<f64>, stats: SignalStats) -> Self {
1147        Self {
1148            t0,
1149            dt,
1150            size,
1151            data,
1152            signal_stats: Some(stats),
1153            is_stable: true, // Default to stable for regular osci readings
1154            fallback_value: None,
1155        }
1156    }
1157
1158    /// Create OsciData for stable readings
1159    pub fn new_stable(t0: f64, dt: f64, size: i32, data: Vec<f64>) -> Self {
1160        Self {
1161            t0,
1162            dt,
1163            size,
1164            data,
1165            signal_stats: None,
1166            is_stable: true,
1167            fallback_value: None,
1168        }
1169    }
1170
1171    /// Create OsciData for unstable readings with fallback value
1172    pub fn new_unstable_with_fallback(t0: f64, dt: f64, size: i32, data: Vec<f64>, fallback: f64) -> Self {
1173        Self {
1174            t0,
1175            dt,
1176            size,
1177            data,
1178            signal_stats: None,
1179            is_stable: false,
1180            fallback_value: Some(fallback),
1181        }
1182    }
1183
1184    /// Get just the measurement values
1185    pub fn values(&self) -> &[f64] {
1186        &self.data
1187    }
1188
1189    /// Get time series as (time, value) pairs
1190    pub fn time_series(&self) -> Vec<(f64, f64)> {
1191        self.data
1192            .iter()
1193            .enumerate()
1194            .map(|(i, &value)| (self.t0 + i as f64 * self.dt, value))
1195            .collect()
1196    }
1197
1198    /// Get signal statistics if available
1199    pub fn stats(&self) -> Option<&SignalStats> {
1200        self.signal_stats.as_ref()
1201    }
1202
1203    /// Check if this data includes stability analysis
1204    pub fn is_stable(&self) -> bool {
1205        self.signal_stats.is_some()
1206    }
1207
1208    pub fn duration(&self) -> f64 {
1209        (self.size - 1) as f64 * self.dt
1210    }
1211
1212    pub fn sample_rate(&self) -> f64 {
1213        if self.dt > 0.0 {
1214            1.0 / self.dt
1215        } else {
1216            0.0
1217        }
1218    }
1219
1220    pub fn time_points(&self) -> Vec<f64> {
1221        (0..self.size)
1222            .map(|i| self.t0 + i as f64 * self.dt)
1223            .collect()
1224    }
1225}
1226
1227/// TCP Logger data stream frame from Nanonis TCP Logger.
1228#[derive(Debug, Clone)]
1229pub struct TCPLoggerData {
1230    /// Number of channels (32-bit integer)
1231    pub num_channels: u32,
1232    /// Oversampling rate (32-bit float)
1233    pub oversampling: f32,
1234    /// Frame counter (64-bit integer)
1235    pub counter: u64,
1236    /// Logger state (16-bit unsigned integer)
1237    pub state: TCPLogStatus,
1238    /// Signal data (num_channels × 32-bit floats)
1239    pub data: Vec<f32>,
1240}
1241
1242/// Result of an auto-approach operation
1243#[derive(Debug, Clone, PartialEq, Eq)]
1244pub enum AutoApproachResult {
1245    /// Auto-approach completed successfully
1246    Success,
1247    /// Auto-approach timed out before completion
1248    Timeout,
1249    /// Auto-approach failed (e.g., hardware error, abnormal termination)
1250    Failed(String),
1251    /// Auto-approach was already running when attempted to start
1252    AlreadyRunning,
1253    /// Auto-approach was cancelled/stopped externally
1254    Cancelled,
1255}
1256
1257impl AutoApproachResult {
1258    /// Check if the result represents a successful operation
1259    pub fn is_success(&self) -> bool {
1260        matches!(self, AutoApproachResult::Success)
1261    }
1262
1263    /// Check if the result represents a failure
1264    pub fn is_failure(&self) -> bool {
1265        matches!(
1266            self,
1267            AutoApproachResult::Failed(_) | AutoApproachResult::Timeout
1268        )
1269    }
1270
1271    /// Get error description if this is a failure
1272    pub fn error_message(&self) -> Option<&str> {
1273        match self {
1274            AutoApproachResult::Failed(msg) => Some(msg),
1275            AutoApproachResult::Timeout => Some("Auto-approach timed out"),
1276            AutoApproachResult::AlreadyRunning => Some("Auto-approach already running"),
1277            AutoApproachResult::Cancelled => Some("Auto-approach was cancelled"),
1278            AutoApproachResult::Success => None,
1279        }
1280    }
1281}
1282
1283/// Status information for auto-approach operations
1284#[derive(Debug, Clone, PartialEq, Eq)]
1285pub enum AutoApproachStatus {
1286    /// Auto-approach is not running
1287    Idle,
1288    /// Auto-approach is currently running
1289    Running,
1290    /// Auto-approach status is unknown (e.g., communication error)
1291    Unknown,
1292}