pokeys-lib 1.0.4

Pure Rust core library for PoKeys device control - USB/Network connectivity, I/O, PWM, encoders, SPI/I2C protocols
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
//! Pulse Engine v2 support for motion control

use crate::device::PoKeysDevice;
use crate::error::{PoKeysError, Result};
use crate::types::{PulseEngineAxisState, PulseEngineState};
use serde::{Deserialize, Serialize};

/// Motor driver step setting constants
pub mod step_setting {
    pub const FULL_STEP: u8 = 0; // 1/1
    pub const HALF_NON_CIRCULAR: u8 = 1; // 1/2 non-circular
    pub const HALF_STEP: u8 = 2; // 1/2
    pub const QUARTER_STEP: u8 = 3; // 1/4
    pub const EIGHTH_STEP: u8 = 4; // 1/8
    pub const SIXTEENTH_STEP: u8 = 5; // 1/16
}

/// Step resolution options for stepper motors
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum StepResolution {
    FullStep,
    HalfStep,
    QuarterStep,
    EighthStep,
    SixteenthStep,
}

impl StepResolution {
    pub fn to_u8(self) -> u8 {
        match self {
            StepResolution::FullStep => step_setting::FULL_STEP,
            StepResolution::HalfStep => step_setting::HALF_STEP,
            StepResolution::QuarterStep => step_setting::QUARTER_STEP,
            StepResolution::EighthStep => step_setting::EIGHTH_STEP,
            StepResolution::SixteenthStep => step_setting::SIXTEENTH_STEP,
        }
    }

    pub fn multiplier(self) -> f32 {
        match self {
            StepResolution::FullStep => 1.0,
            StepResolution::HalfStep => 0.5,
            StepResolution::QuarterStep => 0.25,
            StepResolution::EighthStep => 0.125,
            StepResolution::SixteenthStep => 0.0625,
        }
    }
    pub const THIRTY_SECOND_STEP: u8 = 6; // 1/32
    pub const STEP_128: u8 = 7; // 1/128
    pub const STEP_256: u8 = 8; // 1/256
}

/// Pulse engine power states (bit-mapped)
pub struct PulseEnginePowerState;

impl PulseEnginePowerState {
    // States with enabled power (bit-mapped)
    pub const PE_STOPPED: u8 = 1 << 0;
    pub const PE_STOP_LIMIT: u8 = 1 << 1;
    pub const PE_STOP_EMERGENCY: u8 = 1 << 2;

    // States with enabled charge pump (bit-mapped)
    pub const PE_STOPPED_CHARGE_PUMP: u8 = 1 << 4;
    pub const PE_STOP_LIMIT_CHARGE_PUMP: u8 = 1 << 5;
    pub const PE_STOP_EMERGENCY_CHARGE_PUMP: u8 = 1 << 6;

    // Common combinations
    pub const ALL_POWER_ENABLED: u8 =
        Self::PE_STOPPED | Self::PE_STOP_LIMIT | Self::PE_STOP_EMERGENCY;
    pub const ALL_CHARGE_PUMP_ENABLED: u8 = Self::PE_STOPPED_CHARGE_PUMP
        | Self::PE_STOP_LIMIT_CHARGE_PUMP
        | Self::PE_STOP_EMERGENCY_CHARGE_PUMP;
}

/// Pulse engine setup configuration
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PulseEngineConfig {
    pub enabled_axes: u8,
    pub charge_pump_enabled: u8,
    pub generator_type: u8,
    pub buffer_size: u8,
    pub emergency_switch_polarity: u8,
    pub power_states: u8,
}

impl PulseEngineConfig {
    /// Create builder for 3-channel internal generator
    pub fn three_channel_internal(axes: u8, swap_step_dir: bool) -> PulseEngineConfigBuilder {
        PulseEngineConfigBuilder {
            enabled_axes: axes,
            charge_pump_enabled: 0,
            generator_type: if swap_step_dir { 0x41 } else { 0x01 },
            buffer_size: 0,
            emergency_switch_polarity: 1,
            power_states: PulseEnginePowerState::ALL_POWER_ENABLED,
        }
    }

    /// Create configuration for 8-channel external generator
    pub fn eight_channel_external(axes: u8) -> Self {
        Self {
            enabled_axes: axes,
            charge_pump_enabled: 0,
            generator_type: 0, // 8ch external
            buffer_size: 0,    // default
            emergency_switch_polarity: 1,
            power_states: PulseEnginePowerState::ALL_POWER_ENABLED,
        }
    }
}

pub struct PulseEngineConfigBuilder {
    enabled_axes: u8,
    charge_pump_enabled: u8,
    generator_type: u8,
    buffer_size: u8,
    emergency_switch_polarity: u8,
    power_states: u8,
}

impl PulseEngineConfigBuilder {
    pub fn charge_pump_enabled(mut self, enabled: u8) -> Self {
        self.charge_pump_enabled = enabled;
        self
    }

    pub fn buffer_size(mut self, size: u8) -> Self {
        self.buffer_size = size;
        self
    }

    pub fn emergency_switch_polarity(mut self, polarity: u8) -> Self {
        self.emergency_switch_polarity = polarity;
        self
    }

    pub fn power_states(mut self, states: u8) -> Self {
        self.power_states = states;
        self
    }

    pub fn build(self) -> PulseEngineConfig {
        PulseEngineConfig {
            enabled_axes: self.enabled_axes,
            charge_pump_enabled: self.charge_pump_enabled,
            generator_type: self.generator_type,
            buffer_size: self.buffer_size,
            emergency_switch_polarity: self.emergency_switch_polarity,
            power_states: self.power_states,
        }
    }
}

/// Pulse Engine v2 information
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PulseEngineV2Info {
    pub nr_of_axes: u8,
    pub max_pulse_frequency: u8,
    pub buffer_depth: u8,
    pub slot_timing: u8,
}

/// Pulse Engine v2 main structure
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PulseEngineV2 {
    pub info: PulseEngineV2Info,
    pub axes_state: [u8; 8],
    pub axes_config: [u8; 8],
    pub axes_switch_config: [u8; 8],
    pub current_position: [i32; 8],
    pub position_setup: [i32; 8],
    pub reference_position_speed: [i32; 8],
    pub invert_axis_enable: [i8; 8],
    pub soft_limit_maximum: [i32; 8],
    pub soft_limit_minimum: [i32; 8],
    pub homing_speed: [u8; 8],
    pub homing_return_speed: [u8; 8],
    pub home_offsets: [i32; 8],
    pub homing_algorithm: [u8; 8],
    pub filter_limit_m_switch: [u8; 8],
    pub filter_limit_p_switch: [u8; 8],
    pub filter_home_switch: [u8; 8],
    pub probe_position: [i32; 8],
    pub probe_max_position: [i32; 8],
    pub max_speed: [f32; 8],
    pub max_acceleration: [f32; 8],
    pub max_deceleration: [f32; 8],
    pub mpg_jog_multiplier: [i32; 8],
    pub mpg_jog_encoder: [u8; 8],
    pub pin_home_switch: [u8; 8],
    pub pin_limit_m_switch: [u8; 8],
    pub pin_limit_p_switch: [u8; 8],
    pub axis_enable_output_pins: [u8; 8],
    pub home_back_off_distance: [u32; 8],
    pub mpg_jog_divider: [u16; 8],
    pub filter_probe_input: u8,
    pub axis_signal_options: [u8; 8],
    pub reference_velocity_pv: [f32; 8],
    pub pulse_engine_enabled: u8,
    pub pulse_generator_type: u8,
    pub charge_pump_enabled: u8,
    pub emergency_switch_polarity: u8,
    pub pulse_engine_activated: u8,
    pub limit_status_p: u8,
    pub limit_status_n: u8,
    pub home_status: u8,
    pub error_input_status: u8,
    pub misc_input_status: u8,
    pub limit_override: u8,
    pub limit_override_setup: u8,
    pub pulse_engine_state: u8,
    pub axis_enabled_mask: u8,
    pub emergency_input_pin: u8,
    pub sync_fast_outputs_axis_id: u8,
    pub sync_fast_outputs_mapping: [u8; 8],
    pub param1: u8,
    pub param2: u8,
    pub param3: u8,
    pub axis_enabled_states_mask: u8,
    pub pulse_engine_state_setup: u8,
    pub soft_limit_status: u8,
    pub external_relay_outputs: u8,
    pub external_oc_outputs: u8,
    pub pulse_engine_buffer_size: u8,
    pub motion_buffer_entries_accepted: u8,
    pub new_motion_buffer_entries: u8,
    pub homing_start_mask_setup: u8,
    pub probe_start_mask_setup: u8,
    pub probe_input: u8,
    pub probe_input_polarity: u8,
    pub probe_status: u8,
    pub motion_buffer: Vec<u8>,
    pub probe_speed: f32,
    pub debug_values: [u32; 16],
    pub backlash_width: [u16; 8],
    pub backlash_register: [i16; 8],
    pub backlash_acceleration: [u8; 8],
    pub backlash_compensation_enabled: u8,
    pub backlash_compensation_max_speed: u8,
    pub trigger_preparing: u8,
    pub trigger_prepared: u8,
    pub trigger_pending: u8,
    pub trigger_active: u8,
    pub spindle_speed_estimate: i32,
    pub spindle_position_error: i32,
    pub motor_step_setting: [u8; 4],
    pub motor_current_setting: [u8; 4],
    pub spindle_rpm: u32,
    pub spindle_index_counter: u32,
    pub dedicated_limit_n_inputs: u8,
    pub dedicated_limit_p_inputs: u8,
    pub dedicated_home_inputs: u8,
    pub trigger_ignored_axis_mask: u8,
    pub encoder_index_count: u32,
    pub encoder_ticks_per_rotation: u32,
    pub encoder_velocity: u32,
    pub internal_driver_step_config: [u8; 4],
    pub internal_driver_current_config: [u8; 4],
}

impl PulseEngineV2 {
    pub fn new() -> Self {
        Self {
            info: PulseEngineV2Info {
                nr_of_axes: 0,
                max_pulse_frequency: 0,
                buffer_depth: 0,
                slot_timing: 0,
            },
            axes_state: [0; 8],
            axes_config: [0; 8],
            axes_switch_config: [0; 8],
            current_position: [0; 8],
            position_setup: [0; 8],
            reference_position_speed: [0; 8],
            invert_axis_enable: [0; 8],
            soft_limit_maximum: [0; 8],
            soft_limit_minimum: [0; 8],
            homing_speed: [0; 8],
            homing_return_speed: [0; 8],
            home_offsets: [0; 8],
            homing_algorithm: [0; 8],
            filter_limit_m_switch: [0; 8],
            filter_limit_p_switch: [0; 8],
            filter_home_switch: [0; 8],
            probe_position: [0; 8],
            probe_max_position: [0; 8],
            max_speed: [0.0; 8],
            max_acceleration: [0.0; 8],
            max_deceleration: [0.0; 8],
            mpg_jog_multiplier: [0; 8],
            mpg_jog_encoder: [0; 8],
            pin_home_switch: [0; 8],
            pin_limit_m_switch: [0; 8],
            pin_limit_p_switch: [0; 8],
            axis_enable_output_pins: [0; 8],
            home_back_off_distance: [0; 8],
            mpg_jog_divider: [0; 8],
            filter_probe_input: 0,
            axis_signal_options: [0; 8],
            reference_velocity_pv: [0.0; 8],
            pulse_engine_enabled: 0,
            pulse_generator_type: 0,
            charge_pump_enabled: 0,
            emergency_switch_polarity: 0,
            pulse_engine_activated: 0,
            limit_status_p: 0,
            limit_status_n: 0,
            home_status: 0,
            error_input_status: 0,
            misc_input_status: 0,
            limit_override: 0,
            limit_override_setup: 0,
            pulse_engine_state: 0,
            axis_enabled_mask: 0,
            emergency_input_pin: 0,
            sync_fast_outputs_axis_id: 0,
            sync_fast_outputs_mapping: [0; 8],
            param1: 0,
            param2: 0,
            param3: 0,
            axis_enabled_states_mask: 0,
            pulse_engine_state_setup: 0,
            soft_limit_status: 0,
            external_relay_outputs: 0,
            external_oc_outputs: 0,
            pulse_engine_buffer_size: 0,
            motion_buffer_entries_accepted: 0,
            new_motion_buffer_entries: 0,
            homing_start_mask_setup: 0,
            probe_start_mask_setup: 0,
            probe_input: 0,
            probe_input_polarity: 0,
            probe_status: 0,
            motion_buffer: vec![0; 448],
            probe_speed: 0.0,
            debug_values: [0; 16],
            backlash_width: [0; 8],
            backlash_register: [0; 8],
            backlash_acceleration: [0; 8],
            backlash_compensation_enabled: 0,
            backlash_compensation_max_speed: 0,
            trigger_preparing: 0,
            trigger_prepared: 0,
            trigger_pending: 0,
            trigger_active: 0,
            spindle_speed_estimate: 0,
            spindle_position_error: 0,
            motor_step_setting: [0; 4],
            motor_current_setting: [0; 4],
            spindle_rpm: 0,
            spindle_index_counter: 0,
            dedicated_limit_n_inputs: 0,
            dedicated_limit_p_inputs: 0,
            dedicated_home_inputs: 0,
            trigger_ignored_axis_mask: 0,
            encoder_index_count: 0,
            encoder_ticks_per_rotation: 0,
            encoder_velocity: 0,
            internal_driver_step_config: [0; 4],
            internal_driver_current_config: [0; 4],
        }
    }

    pub fn is_enabled(&self) -> bool {
        self.pulse_engine_enabled != 0
    }

    pub fn is_activated(&self) -> bool {
        self.pulse_engine_activated != 0
    }

    pub fn get_state(&self) -> PulseEngineState {
        match self.pulse_engine_state {
            0 => PulseEngineState::Stopped,
            1 => PulseEngineState::Internal,
            2 => PulseEngineState::Buffer,
            3 => PulseEngineState::Running,
            10 => PulseEngineState::Jogging,
            11 => PulseEngineState::Stopping,
            20 => PulseEngineState::Home,
            21 => PulseEngineState::Homing,
            30 => PulseEngineState::ProbeComplete,
            31 => PulseEngineState::Probe,
            32 => PulseEngineState::ProbeError,
            40 => PulseEngineState::HybridProbeStopping,
            41 => PulseEngineState::HybridProbeComplete,
            100 => PulseEngineState::StopLimit,
            101 => PulseEngineState::StopEmergency,
            _ => PulseEngineState::Stopped,
        }
    }

    /// Get pulse generator type (bits 0-3)
    pub fn get_generator_type(&self) -> u8 {
        self.pulse_generator_type & 0x0F
    }

    /// Get pulse generator type description
    pub fn get_generator_type_description(&self) -> &'static str {
        match self.get_generator_type() {
            0 => "8ch external",
            1 => "3ch internal",
            2 => "8ch smart external",
            _ => "unknown",
        }
    }

    /// Check if step/dir signals are swapped (bit 6)
    pub fn is_step_dir_swapped(&self) -> bool {
        self.pulse_generator_type & 0x40 != 0
    }

    /// Check if external extended IO features are enabled (bit 7)
    pub fn is_extended_io_enabled(&self) -> bool {
        self.pulse_generator_type & 0x80 != 0
    }

    /// Get axis state for a specific axis
    pub fn get_axis_state(&self, axis: usize) -> PulseEngineAxisState {
        if axis >= 8 {
            return PulseEngineAxisState::Stopped;
        }

        match self.axes_state[axis] {
            0 => PulseEngineAxisState::Stopped,
            1 => PulseEngineAxisState::Ready,
            2 => PulseEngineAxisState::Running,
            3 => PulseEngineAxisState::Accelerating,
            4 => PulseEngineAxisState::Decelerating,
            8 => PulseEngineAxisState::HomingResetting,
            9 => PulseEngineAxisState::HomingBackingOff,
            10 => PulseEngineAxisState::Home,
            11 => PulseEngineAxisState::HomingStart,
            12 => PulseEngineAxisState::HomingSearch,
            13 => PulseEngineAxisState::HomingBack,
            14 => PulseEngineAxisState::Probed,
            15 => PulseEngineAxisState::ProbeStart,
            16 => PulseEngineAxisState::ProbeSearch,
            20 => PulseEngineAxisState::Error,
            30 => PulseEngineAxisState::Limit,
            _ => PulseEngineAxisState::Stopped,
        }
    }

    /// Check if axis has positive limit switch triggered
    pub fn is_axis_limit_positive(&self, axis: usize) -> bool {
        if axis >= 8 {
            return false;
        }
        self.limit_status_p & (1 << axis) != 0
    }

    /// Check if axis has negative limit switch triggered
    pub fn is_axis_limit_negative(&self, axis: usize) -> bool {
        if axis >= 8 {
            return false;
        }
        self.limit_status_n & (1 << axis) != 0
    }

    /// Check if axis is at home position
    pub fn is_axis_home(&self, axis: usize) -> bool {
        if axis >= 8 {
            return false;
        }
        self.home_status & (1 << axis) != 0
    }

    /// Check if axis has soft limit triggered
    pub fn is_axis_soft_limit(&self, axis: usize) -> bool {
        if axis >= 8 {
            return false;
        }
        self.soft_limit_status & (1 << axis) != 0
    }

    pub fn is_axis_enabled(&self, axis: usize) -> bool {
        if axis >= 8 {
            return false;
        }
        (self.axis_enabled_mask & (1 << axis)) != 0
    }

    pub fn is_axis_homed(&self, axis: usize) -> bool {
        if axis >= 8 {
            return false;
        }
        (self.home_status & (1 << axis)) != 0
    }

    pub fn is_limit_triggered(&self, axis: usize, positive: bool) -> bool {
        if axis >= 8 {
            return false;
        }

        if positive {
            (self.limit_status_p & (1 << axis)) != 0
        } else {
            (self.limit_status_n & (1 << axis)) != 0
        }
    }
}

impl Default for PulseEngineV2 {
    fn default() -> Self {
        Self::new()
    }
}

impl PoKeysDevice {
    /// Enable pulse engine
    pub fn enable_pulse_engine(&mut self, enable: bool) -> Result<()> {
        self.pulse_engine_v2.pulse_engine_enabled = if enable { 1 } else { 0 };
        self.send_request(0x80, if enable { 1 } else { 0 }, 0, 0, 0)?;
        Ok(())
    }

    /// Activate pulse engine
    pub fn activate_pulse_engine(&mut self, activate: bool) -> Result<()> {
        self.pulse_engine_v2.pulse_engine_activated = if activate { 1 } else { 0 };
        self.send_request(0x81, if activate { 1 } else { 0 }, 0, 0, 0)?;
        Ok(())
    }

    /// Setup pulse engine (0x85/0x01)
    /// Setup pulse engine (0x85/0x01) with axis enable mask
    pub fn setup_pulse_engine_with_axes(
        &mut self,
        config: &PulseEngineConfig,
        axis_enable_mask: u8,
    ) -> Result<()> {
        let mut request = vec![0u8; 56]; // Only data payload (protocol bytes 9-64)

        // Build request according to official PoKeysLib implementation
        request[0] = config.enabled_axes; // Protocol byte 9: Number of enabled axes
        request[1] = config.charge_pump_enabled; // Protocol byte 10: Safety charge pump
        request[2] = config.generator_type & 0x7F; // Protocol byte 11: Generator configuration
        request[3] = config.buffer_size; // Protocol byte 12: Motion buffer size
        request[4] = config.emergency_switch_polarity; // Protocol byte 13: Emergency switch polarity
        request[5] = axis_enable_mask; // Protocol byte 14: AxisEnabledStatesMask (like official PoKeysLib)

        // Protocol bytes 15-63 are reserved (already initialized to 0)

        self.send_request_with_data(0x85, 0x01, 0, 0, 0, &request)?;
        Ok(())
    }

    pub fn get_pulse_engine_status(&mut self) -> Result<()> {
        let response = self.send_request(0x85, 0x00, 0, 0, 0)?;

        if response.len() >= 64 {
            // Parse response according to specification
            self.pulse_engine_v2.soft_limit_status = response[3];
            self.pulse_engine_v2.axis_enabled_states_mask = response[4];
            self.pulse_engine_v2.axis_enabled_mask = response[4]; // Update the field used by is_axis_enabled
            self.pulse_engine_v2.limit_override = response[5];
            // Skip request ID (6) and checksum (7)
            self.pulse_engine_v2.info.nr_of_axes = response[8];
            self.pulse_engine_v2.pulse_engine_activated = response[9];
            self.pulse_engine_v2.pulse_engine_state = response[10];
            self.pulse_engine_v2.charge_pump_enabled = response[11];
            self.pulse_engine_v2.limit_status_p = response[12];
            self.pulse_engine_v2.limit_status_n = response[13];
            self.pulse_engine_v2.home_status = response[14];
            self.pulse_engine_v2.pulse_generator_type = response[15];

            // Parse axes status (bytes 16-23)
            for i in 0..8 {
                if 16 + i < response.len() {
                    self.pulse_engine_v2.axes_state[i] = response[16 + i];
                }
            }

            // Parse axes positions (bytes 24-55, 8x 32-bit LSB first)
            for i in 0..8 {
                let base = 24 + i * 4;
                if base + 3 < response.len() {
                    self.pulse_engine_v2.current_position[i] = i32::from_le_bytes([
                        response[base],
                        response[base + 1],
                        response[base + 2],
                        response[base + 3],
                    ]);
                }
            }

            // Parse remaining fields
            if response.len() >= 63 {
                self.pulse_engine_v2.info.max_pulse_frequency = response[57];
                self.pulse_engine_v2.info.buffer_depth = response[58];
                self.pulse_engine_v2.info.slot_timing = response[59];
                self.pulse_engine_v2.emergency_switch_polarity = response[60];
                self.pulse_engine_v2.error_input_status = response[61];
                self.pulse_engine_v2.misc_input_status = response[62];
            }
        }

        Ok(())
    }

    /// Set axis position (0x85/0x03)
    pub fn set_axis_position(&mut self, axis: usize, position: i32) -> Result<()> {
        if axis >= 8 {
            return Err(PoKeysError::Parameter("Axis index must be 0-7".to_string()));
        }

        let mut request = vec![0u8; 56]; // Only data payload (protocol bytes 9-64)

        // Protocol bytes 9-40: Axis positions (32-bit LSB first)
        // Each axis position is 4 bytes, so axis N starts at byte N*4
        let pos_bytes = position.to_le_bytes();
        let start_idx = axis * 4;
        request[start_idx..start_idx + 4].copy_from_slice(&pos_bytes);

        // Protocol byte 4: Axis position setup selection (bit-mapped)
        let axis_mask = 1u8 << axis;

        // Protocol bytes 41-63 are reserved (already initialized to 0)

        self.send_request_with_data(0x85, 0x03, axis_mask, 0, 0, &request)?;
        Ok(())
    }

    /// Set pulse engine state (0x85/0x02)
    pub fn set_pulse_engine_state(
        &mut self,
        state: u8,
        limit_override: u8,
        output_enable_mask: u8,
    ) -> Result<()> {
        let request = vec![0u8; 55]; // Reserved bytes 9-63
        self.send_request_with_data(
            0x85,
            0x02,
            state,
            limit_override,
            output_enable_mask,
            &request,
        )?;
        Ok(())
    }

    /// Reboot pulse engine (0x85/0x05)
    pub fn reboot_pulse_engine(&mut self) -> Result<()> {
        let request = vec![0u8; 55]; // Reserved bytes 9-63
        self.send_request_with_data(0x85, 0x05, 0, 0, 0, &request)?;
        Ok(())
    }

    /// Set axis configuration (0x85/0x11)
    pub fn set_axis_configuration(&mut self, axis: usize) -> Result<()> {
        if axis >= 8 {
            return Err(PoKeysError::Parameter("Axis index must be 0-7".to_string()));
        }

        let mut request = vec![0u8; 56]; // Data payload (protocol bytes 9-64)

        // Byte 9: Axis options - Enable axis with internal planner, soft limits, and masked enable
        let mut axis_options = 0x01; // aoENABLED
        axis_options |= 1 << 2; // aoINTERNAL_PLANNER - enables acceleration/deceleration control
        axis_options |= 1 << 3; // aoPOSITION_MODE - internal position control mode
        axis_options |= 1 << 6; // aoENABLED_MASKED - required for output enable mask control
        if self.pulse_engine_v2.soft_limit_minimum[axis] != 0
            || self.pulse_engine_v2.soft_limit_maximum[axis] != 0
        {
            axis_options |= 1 << 5; // aoSOFT_LIMIT_ENABLED
        }
        request[0] = axis_options;

        // Byte 10: Axis switch options
        request[1] = 0x00; // No switches

        // Bytes 11-13: Switch pins (0 for external)
        request[2] = 0; // Home switch pin
        request[3] = 0; // Limit- switch pin  
        request[4] = 0; // Limit+ switch pin

        // Bytes 14-15: Homing speeds
        request[5] = 50; // Homing speed (50% of max)
        request[6] = 10; // Homing return speed (10% of homing)

        // Byte 16: MPG jog encoder ID
        request[7] = 0;

        // Bytes 17-20: Maximum speed (32-bit float in timeslot units)
        // Convert from pulses/second to timeslot units for 25kHz internal pulse frequency
        // timeslot_units = pulses_per_second / 25000
        let speed_timeslots = self.pulse_engine_v2.max_speed[axis] / 25000.0;
        let speed_bytes = speed_timeslots.to_le_bytes();
        request[8..12].copy_from_slice(&speed_bytes);

        // Bytes 21-24: Maximum acceleration (32-bit float in timeslot units)
        let accel_timeslots = self.pulse_engine_v2.max_acceleration[axis] / 25000.0;
        let accel_bytes = accel_timeslots.to_le_bytes();
        request[12..16].copy_from_slice(&accel_bytes);

        // Bytes 25-28: Maximum deceleration (32-bit float in timeslot units)
        let decel_timeslots = self.pulse_engine_v2.max_deceleration[axis] / 25000.0;
        let decel_bytes = decel_timeslots.to_le_bytes();
        request[16..20].copy_from_slice(&decel_bytes);

        // Bytes 29-32: Soft-limit minimum position
        let min_bytes = self.pulse_engine_v2.soft_limit_minimum[axis].to_le_bytes();
        request[20..24].copy_from_slice(&min_bytes);

        // Bytes 33-36: Soft-limit maximum position
        let max_bytes = self.pulse_engine_v2.soft_limit_maximum[axis].to_le_bytes();
        request[24..28].copy_from_slice(&max_bytes);

        // Bytes 37-38: MPG jog multiplier
        request[28] = 1;
        request[29] = 0;

        // Byte 39: Axis enable output pin (0 for external)
        request[30] = 0;

        // Byte 40: Invert axis enable signal
        request[31] = 0;

        // Bytes 41-43: Filter settings
        request[32] = 0; // Limit- filter
        request[33] = 0; // Limit+ filter
        request[34] = 0; // Home filter

        // Byte 44: Home algorithm
        request[35] = 0x83; // Default algorithm

        // Bytes 46-49: Home back-off distance
        let backoff = 0i32;
        let backoff_bytes = backoff.to_le_bytes();
        request[37..41].copy_from_slice(&backoff_bytes);

        // Bytes 50-51: MPG encoder divider
        request[41] = 1;
        request[42] = 0;

        // Byte 52: Additional misc options
        request[43] = 0x00; // No inversion

        // Byte 53: Probe filter
        request[44] = 0;

        // Bytes 54-63: Reserved (already 0)

        // Send the complete configuration using send_request_with_data
        self.send_request_with_data(0x85, 0x11, axis as u8, 0, 0, &request)?;
        Ok(())
    }

    /// Get axis configuration (0x85/0x10)
    pub fn get_axis_configuration(&mut self, axis: usize) -> Result<()> {
        if axis >= 8 {
            return Err(PoKeysError::Parameter("Axis index must be 0-7".to_string()));
        }

        let response = self.send_request(0x85, 0x10, axis as u8, 0, 0)?;

        if response.len() >= 40 {
            // Debug: Read back the values we just set
            let speed_bytes = [response[16], response[17], response[18], response[19]];
            let accel_bytes = [response[20], response[21], response[22], response[23]];
            let decel_bytes = [response[24], response[25], response[26], response[27]];

            let readback_speed = f32::from_le_bytes(speed_bytes);
            let readback_accel = f32::from_le_bytes(accel_bytes);
            let readback_decel = f32::from_le_bytes(decel_bytes);

            println!(
                "DEBUG 0x85/0x10 READBACK: speed={:.6}, accel={:.6}, decel={:.6}",
                readback_speed, readback_accel, readback_decel
            );
            println!(
                "DEBUG ORIGINAL VALUES: speed={:.6}, accel={:.6}, decel={:.6}",
                self.pulse_engine_v2.max_speed[axis],
                self.pulse_engine_v2.max_acceleration[axis],
                self.pulse_engine_v2.max_deceleration[axis]
            );
        }

        // Parse response according to specification
        if response.len() >= 64 {
            // Byte 9: Axis options
            self.pulse_engine_v2.axes_config[axis] = response[8];

            // Byte 10: Axis switch options
            self.pulse_engine_v2.axes_switch_config[axis] = response[9];

            // Byte 14: Homing speed
            self.pulse_engine_v2.homing_speed[axis] = response[13];

            // Byte 15: Homing return speed
            self.pulse_engine_v2.homing_return_speed[axis] = response[14];

            // Byte 16: MPG jog encoder ID
            self.pulse_engine_v2.mpg_jog_encoder[axis] = response[15];

            // Bytes 17-20: Maximum speed (32-bit float)
            let speed_bytes = [response[16], response[17], response[18], response[19]];
            self.pulse_engine_v2.max_speed[axis] = f32::from_le_bytes(speed_bytes);

            // Bytes 21-24: Maximum acceleration (32-bit float)
            let accel_bytes = [response[20], response[21], response[22], response[23]];
            self.pulse_engine_v2.max_acceleration[axis] = f32::from_le_bytes(accel_bytes);

            // Bytes 25-28: Maximum deceleration (32-bit float)
            let decel_bytes = [response[24], response[25], response[26], response[27]];
            self.pulse_engine_v2.max_deceleration[axis] = f32::from_le_bytes(decel_bytes);

            // Bytes 29-32: Soft-limit minimum position (32-bit int)
            let min_pos_bytes = [response[28], response[29], response[30], response[31]];
            self.pulse_engine_v2.soft_limit_minimum[axis] = i32::from_le_bytes(min_pos_bytes);

            // Bytes 33-36: Soft-limit maximum position (32-bit int)
            let max_pos_bytes = [response[32], response[33], response[34], response[35]];
            self.pulse_engine_v2.soft_limit_maximum[axis] = i32::from_le_bytes(max_pos_bytes);
        }

        Ok(())
    }
    pub fn get_axis_position(&mut self, axis: usize) -> Result<i32> {
        if axis >= 8 {
            return Err(PoKeysError::Parameter("Invalid axis number".to_string()));
        }

        // Read pulse engine status
        self.read_pulse_engine_status()?;

        Ok(self.pulse_engine_v2.current_position[axis])
    }

    /// Move axis to position using 0x85/0x20 command in position control mode
    pub fn move_axis_to_position(&mut self, axis: usize, position: i32, _speed: f32) -> Result<()> {
        if axis >= 8 {
            return Err(PoKeysError::Parameter("Invalid axis number".to_string()));
        }

        // Get current positions first
        self.get_pulse_engine_status()?;

        // Set the reference position for the axis
        self.pulse_engine_v2.reference_position_speed[axis] = position;

        // Execute the move using 0x85/0x20 command
        // Now that axis is in position control mode, this should move to exact position
        let mut request = vec![0u8; 56]; // Data payload bytes 9-64

        // Send target position for moving axis, current positions for others
        for i in 0..8 {
            let pos = if i == axis {
                position // Target position for moving axis
            } else {
                self.pulse_engine_v2.current_position[i] // Current position for other axes
            };
            let pos_bytes = pos.to_le_bytes();
            request[i * 4..(i + 1) * 4].copy_from_slice(&pos_bytes);
        }

        self.send_request_with_data(0x85, 0x20, 0, 0, 0, &request)?;
        Ok(())
    }

    /// Start axis homing
    pub fn start_axis_homing(&mut self, axis_mask: u8) -> Result<()> {
        self.pulse_engine_v2.homing_start_mask_setup = axis_mask;
        self.send_request(0x85, axis_mask, 0, 0, 0)?;
        Ok(())
    }

    /// Configure axis homing
    pub fn configure_axis_homing(
        &mut self,
        axis: usize,
        home_pin: u8,
        homing_speed: u8,
        home_offset: i32,
    ) -> Result<()> {
        if axis >= 8 {
            return Err(PoKeysError::Parameter("Invalid axis number".to_string()));
        }

        self.pulse_engine_v2.pin_home_switch[axis] = home_pin;
        self.pulse_engine_v2.homing_speed[axis] = homing_speed;
        self.pulse_engine_v2.home_offsets[axis] = home_offset;

        self.send_request(0x86, axis as u8, home_pin, homing_speed, 0)?;
        Ok(())
    }

    /// Configure axis limits
    pub fn configure_axis_limits(
        &mut self,
        axis: usize,
        limit_n_pin: u8,
        limit_p_pin: u8,
        soft_limit_min: i32,
        soft_limit_max: i32,
    ) -> Result<()> {
        if axis >= 8 {
            return Err(PoKeysError::Parameter("Invalid axis number".to_string()));
        }

        self.pulse_engine_v2.pin_limit_m_switch[axis] = limit_n_pin;
        self.pulse_engine_v2.pin_limit_p_switch[axis] = limit_p_pin;
        self.pulse_engine_v2.soft_limit_minimum[axis] = soft_limit_min;
        self.pulse_engine_v2.soft_limit_maximum[axis] = soft_limit_max;

        self.send_request(0x87, axis as u8, limit_n_pin, limit_p_pin, 0)?;
        Ok(())
    }

    /// Emergency stop
    pub fn emergency_stop(&mut self) -> Result<()> {
        self.send_request(0x88, 0, 0, 0, 0)?;
        Ok(())
    }

    /// Read pulse engine status
    pub fn read_pulse_engine_status(&mut self) -> Result<()> {
        let response = self.send_request(0x89, 0, 0, 0, 0)?;

        // Parse pulse engine status from response
        if response.len() >= 64 {
            self.pulse_engine_v2.pulse_engine_state = response[8];
            self.pulse_engine_v2.axis_enabled_mask = response[9];
            self.pulse_engine_v2.limit_status_p = response[10];
            self.pulse_engine_v2.limit_status_n = response[11];
            self.pulse_engine_v2.home_status = response[12];

            // Parse axis positions
            for i in 0..8 {
                let pos_index = 16 + (i * 4);
                if pos_index + 3 < response.len() {
                    self.pulse_engine_v2.current_position[i] = i32::from_le_bytes([
                        response[pos_index],
                        response[pos_index + 1],
                        response[pos_index + 2],
                        response[pos_index + 3],
                    ]);
                }
            }
        }

        Ok(())
    }

    /// Get pulse engine state
    pub fn get_pulse_engine_state(&mut self) -> Result<PulseEngineState> {
        self.read_pulse_engine_status()?;
        Ok(self.pulse_engine_v2.get_state())
    }

    /// Get axis state
    pub fn get_axis_state(&mut self, axis: usize) -> Result<PulseEngineAxisState> {
        if axis >= 8 {
            return Err(PoKeysError::Parameter("Invalid axis number".to_string()));
        }

        self.read_pulse_engine_status()?;
        Ok(self.pulse_engine_v2.get_axis_state(axis))
    }

    /// Check if axis is homed
    pub fn is_axis_homed(&mut self, axis: usize) -> Result<bool> {
        if axis >= 8 {
            return Err(PoKeysError::Parameter("Invalid axis number".to_string()));
        }

        self.read_pulse_engine_status()?;
        Ok(self.pulse_engine_v2.is_axis_homed(axis))
    }

    /// Wait for axis to complete movement
    pub fn wait_for_axis(&mut self, axis: usize, timeout_ms: u32) -> Result<()> {
        if axis >= 8 {
            return Err(PoKeysError::Parameter("Invalid axis number".to_string()));
        }

        let start_time = std::time::Instant::now();
        let timeout = std::time::Duration::from_millis(timeout_ms as u64);

        loop {
            let state = self.get_axis_state(axis)?;

            match state {
                PulseEngineAxisState::Ready | PulseEngineAxisState::Stopped => break,
                PulseEngineAxisState::Error | PulseEngineAxisState::Limit => {
                    return Err(PoKeysError::Protocol(
                        "Axis error or limit reached".to_string(),
                    ));
                }
                _ => {
                    if start_time.elapsed() > timeout {
                        return Err(PoKeysError::Timeout);
                    }
                    std::thread::sleep(std::time::Duration::from_millis(10));
                }
            }
        }

        Ok(())
    }

    /// Set internal motor drivers configuration (0x85/0x19)
    pub fn set_motor_drivers_configuration(&mut self) -> Result<()> {
        let mut request = vec![0u8; 56]; // Data payload (protocol bytes 9-64)

        // Set motor driver settings for each axis (following official PoKeysLib)
        // device->request[8 + i*2] = pe->InternalDriverStepConfig[i];
        // device->request[9 + i*2] = pe->InternalDriverCurrentConfig[i];
        // But our request starts at protocol byte 9, so we need offset -1:
        // Axis 0: protocol bytes 8,9 -> request[-1,0] (invalid)
        // Axis 1: protocol bytes 10,11 -> request[1,2]
        // Axis 2: protocol bytes 12,13 -> request[3,4]
        // Axis 3: protocol bytes 14,15 -> request[5,6]

        for axis in 0..4 {
            let protocol_offset = 9 + axis * 2; // Changed from 8 to 9
            if protocol_offset >= 9 {
                // Only if within our request range (starts at byte 9)
                let request_offset = protocol_offset - 9; // Convert to request array index
                if request_offset + 1 < request.len() {
                    request[request_offset] = self.pulse_engine_v2.motor_step_setting[axis];
                    request[request_offset + 1] = self.pulse_engine_v2.motor_current_setting[axis];
                }
            }
        }

        println!(
            "DEBUG 0x85/0x19 REQUEST: Axis 2 -> protocol bytes 13,14 -> request[4,5] = step={}, current={}",
            request[4], request[5]
        );

        let response = self.send_request_with_data(0x85, 0x19, 0, 0, 0, &request)?;

        println!(
            "DEBUG 0x85/0x19 RESPONSE: protocol bytes 13,14 = step={}, current={}",
            response.get(13).unwrap_or(&255),
            response.get(14).unwrap_or(&255)
        );

        Ok(())
    }

    /// Create axis configuration builder
    pub fn configure_axis(&mut self, axis: usize) -> AxisConfigBuilder {
        AxisConfigBuilder::new(axis)
    }

    /// Set axis positions (0x85/0x03)
    pub fn set_axis_positions(&mut self, axis_mask: u8, positions: &[i32; 8]) -> Result<()> {
        let mut request = vec![0u8; 56]; // Data payload (protocol bytes 9-64)

        // Bytes 0-31: Axis positions (8x 32-bit integers, LSB first)
        for (i, &position) in positions.iter().enumerate() {
            let pos_bytes = position.to_le_bytes();
            let offset = i * 4;
            request[offset..offset + 4].copy_from_slice(&pos_bytes);
        }

        self.send_request_with_data(0x85, 0x03, axis_mask, 0, 0, &request)?;
        Ok(())
    }

    /// Move PV (Set reference position and speed) (0x85/0x25)
    pub fn move_pv(&mut self, axis_mask: u8, positions: &[i32; 8], velocity: u16) -> Result<()> {
        let mut request = vec![0u8; 56]; // Data payload (protocol bytes 9-64)

        // Bytes 0-31: Reference positions (8x 32-bit integers, LSB first)
        for (i, &position) in positions.iter().enumerate() {
            let pos_bytes = position.to_le_bytes();
            let offset = i * 4;
            request[offset..offset + 4].copy_from_slice(&pos_bytes);
        }

        // Bytes 32-47: Move velocity (16 bytes, but only first 2 used)
        let vel_bytes = velocity.to_le_bytes();
        request[32..34].copy_from_slice(&vel_bytes);

        self.send_request_with_data(0x85, 0x25, axis_mask, 0, 0, &request)?;
        Ok(())
    }

    /// Create motor driver configuration builder
    pub fn configure_motor_drivers(&mut self) -> MotorDriverConfigBuilder {
        MotorDriverConfigBuilder::new()
    }

    /// Get internal motor drivers configuration (0x85/0x18)
    pub fn get_motor_drivers_configuration(&mut self) -> Result<()> {
        let response = self.send_request(0x85, 0x18, 0, 0, 0)?;

        // Parse motor driver settings for each axis
        for axis in 0..4 {
            let byte_offset = 8 + (axis * 2); // Bytes 9-16 for axes 1-4
            if byte_offset + 1 < response.len() {
                let step_setting = response[byte_offset];
                let current_setting = response[byte_offset + 1];

                // Device returns 0-based values which match our 0x85/0x19 command format
                self.pulse_engine_v2.motor_step_setting[axis] = step_setting;
                self.pulse_engine_v2.motor_current_setting[axis] = current_setting;
            }
        }

        Ok(())
    }
}

/// Axis configuration builder
pub struct AxisConfigBuilder {
    axis: usize,
    max_speed: f32,
    max_acceleration: f32,
    max_deceleration: f32,
    soft_limit_min: i32,
    soft_limit_max: i32,
}

impl AxisConfigBuilder {
    pub fn new(axis: usize) -> Self {
        Self {
            axis,
            max_speed: 1000.0,
            max_acceleration: 100.0,
            max_deceleration: 100.0,
            soft_limit_min: 0,
            soft_limit_max: 0,
        }
    }

    pub fn max_speed(mut self, speed: f32) -> Self {
        self.max_speed = speed;
        self
    }

    pub fn max_acceleration(mut self, acceleration: f32) -> Self {
        self.max_acceleration = acceleration;
        self
    }

    pub fn max_deceleration(mut self, deceleration: f32) -> Self {
        self.max_deceleration = deceleration;
        self
    }

    pub fn soft_limit_min(mut self, min: i32) -> Self {
        self.soft_limit_min = min;
        self
    }

    pub fn soft_limit_max(mut self, max: i32) -> Self {
        self.soft_limit_max = max;
        self
    }

    pub fn build(self, device: &mut PoKeysDevice) -> Result<()> {
        if self.axis >= 8 {
            return Err(PoKeysError::Parameter("Axis index must be 0-7".to_string()));
        }

        // Store values directly as floats (no conversion needed)
        device.pulse_engine_v2.max_speed[self.axis] = self.max_speed;
        device.pulse_engine_v2.max_acceleration[self.axis] = self.max_acceleration;
        device.pulse_engine_v2.max_deceleration[self.axis] = self.max_deceleration;
        device.pulse_engine_v2.soft_limit_minimum[self.axis] = self.soft_limit_min;
        device.pulse_engine_v2.soft_limit_maximum[self.axis] = self.soft_limit_max;

        device.set_axis_configuration(self.axis)
    }
}

/// Motor driver configuration builder
pub struct MotorDriverConfigBuilder {
    step_settings: [Option<u8>; 4],
    current_settings: [Option<u8>; 4],
}

impl Default for MotorDriverConfigBuilder {
    fn default() -> Self {
        Self::new()
    }
}

impl MotorDriverConfigBuilder {
    pub fn new() -> Self {
        Self {
            step_settings: [None; 4],
            current_settings: [None; 4],
        }
    }

    pub fn axis_step_setting(mut self, axis: usize, step_setting: u8) -> Self {
        if axis < 4 {
            self.step_settings[axis] = Some(step_setting);
        }
        self
    }

    pub fn axis_current_setting(mut self, axis: usize, current_setting: u8) -> Self {
        if axis < 4 {
            self.current_settings[axis] = Some(current_setting);
        }
        self
    }

    pub fn build(self, device: &mut PoKeysDevice) -> Result<()> {
        // Only update the values that were explicitly set
        for axis in 0..4 {
            if let Some(step_setting) = self.step_settings[axis] {
                device.pulse_engine_v2.motor_step_setting[axis] = step_setting;
            }
            if let Some(current_setting) = self.current_settings[axis] {
                device.pulse_engine_v2.motor_current_setting[axis] = current_setting;
            }
        }
        device.set_motor_drivers_configuration()
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_pulse_engine_creation() {
        let pe = PulseEngineV2::new();
        assert!(!pe.is_enabled());
        assert!(!pe.is_activated());
        assert_eq!(pe.get_state(), PulseEngineState::Stopped);
    }

    #[test]
    fn test_axis_state_conversion() {
        let mut pe = PulseEngineV2::new();

        pe.axes_state[0] = 1;
        assert_eq!(pe.get_axis_state(0), PulseEngineAxisState::Ready);

        pe.axes_state[0] = 2;
        assert_eq!(pe.get_axis_state(0), PulseEngineAxisState::Running);

        pe.axes_state[0] = 20;
        assert_eq!(pe.get_axis_state(0), PulseEngineAxisState::Error);
    }

    #[test]
    fn test_axis_enable_mask() {
        let mut pe = PulseEngineV2::new();

        assert!(!pe.is_axis_enabled(0));

        pe.axis_enabled_mask = 0b00000001;
        assert!(pe.is_axis_enabled(0));
        assert!(!pe.is_axis_enabled(1));

        pe.axis_enabled_mask = 0b00000011;
        assert!(pe.is_axis_enabled(0));
        assert!(pe.is_axis_enabled(1));
        assert!(!pe.is_axis_enabled(2));
    }
}