1use crate::device::PoKeysDevice;
4use crate::error::{PoKeysError, Result};
5use crate::types::{PulseEngineAxisState, PulseEngineState};
6use serde::{Deserialize, Serialize};
7
8pub mod step_setting {
10 pub const FULL_STEP: u8 = 0; pub const HALF_NON_CIRCULAR: u8 = 1; pub const HALF_STEP: u8 = 2; pub const QUARTER_STEP: u8 = 3; pub const EIGHTH_STEP: u8 = 4; pub const SIXTEENTH_STEP: u8 = 5; }
17
18#[derive(Debug, Clone, Copy, PartialEq)]
20pub enum StepResolution {
21 FullStep,
22 HalfStep,
23 QuarterStep,
24 EighthStep,
25 SixteenthStep,
26}
27
28impl StepResolution {
29 pub fn to_u8(self) -> u8 {
30 match self {
31 StepResolution::FullStep => step_setting::FULL_STEP,
32 StepResolution::HalfStep => step_setting::HALF_STEP,
33 StepResolution::QuarterStep => step_setting::QUARTER_STEP,
34 StepResolution::EighthStep => step_setting::EIGHTH_STEP,
35 StepResolution::SixteenthStep => step_setting::SIXTEENTH_STEP,
36 }
37 }
38
39 pub fn multiplier(self) -> f32 {
40 match self {
41 StepResolution::FullStep => 1.0,
42 StepResolution::HalfStep => 0.5,
43 StepResolution::QuarterStep => 0.25,
44 StepResolution::EighthStep => 0.125,
45 StepResolution::SixteenthStep => 0.0625,
46 }
47 }
48 pub const THIRTY_SECOND_STEP: u8 = 6; pub const STEP_128: u8 = 7; pub const STEP_256: u8 = 8; }
52
53pub struct PulseEnginePowerState;
55
56impl PulseEnginePowerState {
57 pub const PE_STOPPED: u8 = 1 << 0;
59 pub const PE_STOP_LIMIT: u8 = 1 << 1;
60 pub const PE_STOP_EMERGENCY: u8 = 1 << 2;
61
62 pub const PE_STOPPED_CHARGE_PUMP: u8 = 1 << 4;
64 pub const PE_STOP_LIMIT_CHARGE_PUMP: u8 = 1 << 5;
65 pub const PE_STOP_EMERGENCY_CHARGE_PUMP: u8 = 1 << 6;
66
67 pub const ALL_POWER_ENABLED: u8 =
69 Self::PE_STOPPED | Self::PE_STOP_LIMIT | Self::PE_STOP_EMERGENCY;
70 pub const ALL_CHARGE_PUMP_ENABLED: u8 = Self::PE_STOPPED_CHARGE_PUMP
71 | Self::PE_STOP_LIMIT_CHARGE_PUMP
72 | Self::PE_STOP_EMERGENCY_CHARGE_PUMP;
73}
74
75#[derive(Debug, Clone, Serialize, Deserialize)]
77pub struct PulseEngineConfig {
78 pub enabled_axes: u8,
79 pub charge_pump_enabled: u8,
80 pub generator_type: u8,
81 pub buffer_size: u8,
82 pub emergency_switch_polarity: u8,
83 pub power_states: u8,
84}
85
86impl PulseEngineConfig {
87 pub fn three_channel_internal(axes: u8, swap_step_dir: bool) -> PulseEngineConfigBuilder {
89 PulseEngineConfigBuilder {
90 enabled_axes: axes,
91 charge_pump_enabled: 0,
92 generator_type: if swap_step_dir { 0x41 } else { 0x01 },
93 buffer_size: 0,
94 emergency_switch_polarity: 1,
95 power_states: PulseEnginePowerState::ALL_POWER_ENABLED,
96 }
97 }
98
99 pub fn eight_channel_external(axes: u8) -> Self {
101 Self {
102 enabled_axes: axes,
103 charge_pump_enabled: 0,
104 generator_type: 0, buffer_size: 0, emergency_switch_polarity: 1,
107 power_states: PulseEnginePowerState::ALL_POWER_ENABLED,
108 }
109 }
110}
111
112pub struct PulseEngineConfigBuilder {
113 enabled_axes: u8,
114 charge_pump_enabled: u8,
115 generator_type: u8,
116 buffer_size: u8,
117 emergency_switch_polarity: u8,
118 power_states: u8,
119}
120
121impl PulseEngineConfigBuilder {
122 pub fn charge_pump_enabled(mut self, enabled: u8) -> Self {
123 self.charge_pump_enabled = enabled;
124 self
125 }
126
127 pub fn buffer_size(mut self, size: u8) -> Self {
128 self.buffer_size = size;
129 self
130 }
131
132 pub fn emergency_switch_polarity(mut self, polarity: u8) -> Self {
133 self.emergency_switch_polarity = polarity;
134 self
135 }
136
137 pub fn power_states(mut self, states: u8) -> Self {
138 self.power_states = states;
139 self
140 }
141
142 pub fn build(self) -> PulseEngineConfig {
143 PulseEngineConfig {
144 enabled_axes: self.enabled_axes,
145 charge_pump_enabled: self.charge_pump_enabled,
146 generator_type: self.generator_type,
147 buffer_size: self.buffer_size,
148 emergency_switch_polarity: self.emergency_switch_polarity,
149 power_states: self.power_states,
150 }
151 }
152}
153
154#[derive(Debug, Clone, Serialize, Deserialize)]
156pub struct PulseEngineV2Info {
157 pub nr_of_axes: u8,
158 pub max_pulse_frequency: u8,
159 pub buffer_depth: u8,
160 pub slot_timing: u8,
161}
162
163#[derive(Debug, Clone, Serialize, Deserialize)]
165pub struct PulseEngineV2 {
166 pub info: PulseEngineV2Info,
167 pub axes_state: [u8; 8],
168 pub axes_config: [u8; 8],
169 pub axes_switch_config: [u8; 8],
170 pub current_position: [i32; 8],
171 pub position_setup: [i32; 8],
172 pub reference_position_speed: [i32; 8],
173 pub invert_axis_enable: [i8; 8],
174 pub soft_limit_maximum: [i32; 8],
175 pub soft_limit_minimum: [i32; 8],
176 pub homing_speed: [u8; 8],
177 pub homing_return_speed: [u8; 8],
178 pub home_offsets: [i32; 8],
179 pub homing_algorithm: [u8; 8],
180 pub filter_limit_m_switch: [u8; 8],
181 pub filter_limit_p_switch: [u8; 8],
182 pub filter_home_switch: [u8; 8],
183 pub probe_position: [i32; 8],
184 pub probe_max_position: [i32; 8],
185 pub max_speed: [f32; 8],
186 pub max_acceleration: [f32; 8],
187 pub max_deceleration: [f32; 8],
188 pub mpg_jog_multiplier: [i32; 8],
189 pub mpg_jog_encoder: [u8; 8],
190 pub pin_home_switch: [u8; 8],
191 pub pin_limit_m_switch: [u8; 8],
192 pub pin_limit_p_switch: [u8; 8],
193 pub axis_enable_output_pins: [u8; 8],
194 pub home_back_off_distance: [u32; 8],
195 pub mpg_jog_divider: [u16; 8],
196 pub filter_probe_input: u8,
197 pub axis_signal_options: [u8; 8],
198 pub reference_velocity_pv: [f32; 8],
199 pub pulse_engine_enabled: u8,
200 pub pulse_generator_type: u8,
201 pub charge_pump_enabled: u8,
202 pub emergency_switch_polarity: u8,
203 pub pulse_engine_activated: u8,
204 pub limit_status_p: u8,
205 pub limit_status_n: u8,
206 pub home_status: u8,
207 pub error_input_status: u8,
208 pub misc_input_status: u8,
209 pub limit_override: u8,
210 pub limit_override_setup: u8,
211 pub pulse_engine_state: u8,
212 pub axis_enabled_mask: u8,
213 pub emergency_input_pin: u8,
214 pub sync_fast_outputs_axis_id: u8,
215 pub sync_fast_outputs_mapping: [u8; 8],
216 pub param1: u8,
217 pub param2: u8,
218 pub param3: u8,
219 pub axis_enabled_states_mask: u8,
220 pub pulse_engine_state_setup: u8,
221 pub soft_limit_status: u8,
222 pub external_relay_outputs: u8,
223 pub external_oc_outputs: u8,
224 pub pulse_engine_buffer_size: u8,
225 pub motion_buffer_entries_accepted: u8,
226 pub new_motion_buffer_entries: u8,
227 pub homing_start_mask_setup: u8,
228 pub probe_start_mask_setup: u8,
229 pub probe_input: u8,
230 pub probe_input_polarity: u8,
231 pub probe_status: u8,
232 pub motion_buffer: Vec<u8>,
233 pub probe_speed: f32,
234 pub debug_values: [u32; 16],
235 pub backlash_width: [u16; 8],
236 pub backlash_register: [i16; 8],
237 pub backlash_acceleration: [u8; 8],
238 pub backlash_compensation_enabled: u8,
239 pub backlash_compensation_max_speed: u8,
240 pub trigger_preparing: u8,
241 pub trigger_prepared: u8,
242 pub trigger_pending: u8,
243 pub trigger_active: u8,
244 pub spindle_speed_estimate: i32,
245 pub spindle_position_error: i32,
246 pub motor_step_setting: [u8; 4],
247 pub motor_current_setting: [u8; 4],
248 pub spindle_rpm: u32,
249 pub spindle_index_counter: u32,
250 pub dedicated_limit_n_inputs: u8,
251 pub dedicated_limit_p_inputs: u8,
252 pub dedicated_home_inputs: u8,
253 pub trigger_ignored_axis_mask: u8,
254 pub encoder_index_count: u32,
255 pub encoder_ticks_per_rotation: u32,
256 pub encoder_velocity: u32,
257 pub internal_driver_step_config: [u8; 4],
258 pub internal_driver_current_config: [u8; 4],
259}
260
261impl PulseEngineV2 {
262 pub fn new() -> Self {
263 Self {
264 info: PulseEngineV2Info {
265 nr_of_axes: 0,
266 max_pulse_frequency: 0,
267 buffer_depth: 0,
268 slot_timing: 0,
269 },
270 axes_state: [0; 8],
271 axes_config: [0; 8],
272 axes_switch_config: [0; 8],
273 current_position: [0; 8],
274 position_setup: [0; 8],
275 reference_position_speed: [0; 8],
276 invert_axis_enable: [0; 8],
277 soft_limit_maximum: [0; 8],
278 soft_limit_minimum: [0; 8],
279 homing_speed: [0; 8],
280 homing_return_speed: [0; 8],
281 home_offsets: [0; 8],
282 homing_algorithm: [0; 8],
283 filter_limit_m_switch: [0; 8],
284 filter_limit_p_switch: [0; 8],
285 filter_home_switch: [0; 8],
286 probe_position: [0; 8],
287 probe_max_position: [0; 8],
288 max_speed: [0.0; 8],
289 max_acceleration: [0.0; 8],
290 max_deceleration: [0.0; 8],
291 mpg_jog_multiplier: [0; 8],
292 mpg_jog_encoder: [0; 8],
293 pin_home_switch: [0; 8],
294 pin_limit_m_switch: [0; 8],
295 pin_limit_p_switch: [0; 8],
296 axis_enable_output_pins: [0; 8],
297 home_back_off_distance: [0; 8],
298 mpg_jog_divider: [0; 8],
299 filter_probe_input: 0,
300 axis_signal_options: [0; 8],
301 reference_velocity_pv: [0.0; 8],
302 pulse_engine_enabled: 0,
303 pulse_generator_type: 0,
304 charge_pump_enabled: 0,
305 emergency_switch_polarity: 0,
306 pulse_engine_activated: 0,
307 limit_status_p: 0,
308 limit_status_n: 0,
309 home_status: 0,
310 error_input_status: 0,
311 misc_input_status: 0,
312 limit_override: 0,
313 limit_override_setup: 0,
314 pulse_engine_state: 0,
315 axis_enabled_mask: 0,
316 emergency_input_pin: 0,
317 sync_fast_outputs_axis_id: 0,
318 sync_fast_outputs_mapping: [0; 8],
319 param1: 0,
320 param2: 0,
321 param3: 0,
322 axis_enabled_states_mask: 0,
323 pulse_engine_state_setup: 0,
324 soft_limit_status: 0,
325 external_relay_outputs: 0,
326 external_oc_outputs: 0,
327 pulse_engine_buffer_size: 0,
328 motion_buffer_entries_accepted: 0,
329 new_motion_buffer_entries: 0,
330 homing_start_mask_setup: 0,
331 probe_start_mask_setup: 0,
332 probe_input: 0,
333 probe_input_polarity: 0,
334 probe_status: 0,
335 motion_buffer: vec![0; 448],
336 probe_speed: 0.0,
337 debug_values: [0; 16],
338 backlash_width: [0; 8],
339 backlash_register: [0; 8],
340 backlash_acceleration: [0; 8],
341 backlash_compensation_enabled: 0,
342 backlash_compensation_max_speed: 0,
343 trigger_preparing: 0,
344 trigger_prepared: 0,
345 trigger_pending: 0,
346 trigger_active: 0,
347 spindle_speed_estimate: 0,
348 spindle_position_error: 0,
349 motor_step_setting: [0; 4],
350 motor_current_setting: [0; 4],
351 spindle_rpm: 0,
352 spindle_index_counter: 0,
353 dedicated_limit_n_inputs: 0,
354 dedicated_limit_p_inputs: 0,
355 dedicated_home_inputs: 0,
356 trigger_ignored_axis_mask: 0,
357 encoder_index_count: 0,
358 encoder_ticks_per_rotation: 0,
359 encoder_velocity: 0,
360 internal_driver_step_config: [0; 4],
361 internal_driver_current_config: [0; 4],
362 }
363 }
364
365 pub fn is_enabled(&self) -> bool {
366 self.pulse_engine_enabled != 0
367 }
368
369 pub fn is_activated(&self) -> bool {
370 self.pulse_engine_activated != 0
371 }
372
373 pub fn get_state(&self) -> PulseEngineState {
374 match self.pulse_engine_state {
375 0 => PulseEngineState::Stopped,
376 1 => PulseEngineState::Internal,
377 2 => PulseEngineState::Buffer,
378 3 => PulseEngineState::Running,
379 10 => PulseEngineState::Jogging,
380 11 => PulseEngineState::Stopping,
381 20 => PulseEngineState::Home,
382 21 => PulseEngineState::Homing,
383 30 => PulseEngineState::ProbeComplete,
384 31 => PulseEngineState::Probe,
385 32 => PulseEngineState::ProbeError,
386 40 => PulseEngineState::HybridProbeStopping,
387 41 => PulseEngineState::HybridProbeComplete,
388 100 => PulseEngineState::StopLimit,
389 101 => PulseEngineState::StopEmergency,
390 _ => PulseEngineState::Stopped,
391 }
392 }
393
394 pub fn get_generator_type(&self) -> u8 {
396 self.pulse_generator_type & 0x0F
397 }
398
399 pub fn get_generator_type_description(&self) -> &'static str {
401 match self.get_generator_type() {
402 0 => "8ch external",
403 1 => "3ch internal",
404 2 => "8ch smart external",
405 _ => "unknown",
406 }
407 }
408
409 pub fn is_step_dir_swapped(&self) -> bool {
411 self.pulse_generator_type & 0x40 != 0
412 }
413
414 pub fn is_extended_io_enabled(&self) -> bool {
416 self.pulse_generator_type & 0x80 != 0
417 }
418
419 pub fn get_axis_state(&self, axis: usize) -> PulseEngineAxisState {
421 if axis >= 8 {
422 return PulseEngineAxisState::Stopped;
423 }
424
425 match self.axes_state[axis] {
426 0 => PulseEngineAxisState::Stopped,
427 1 => PulseEngineAxisState::Ready,
428 2 => PulseEngineAxisState::Running,
429 3 => PulseEngineAxisState::Accelerating,
430 4 => PulseEngineAxisState::Decelerating,
431 8 => PulseEngineAxisState::HomingResetting,
432 9 => PulseEngineAxisState::HomingBackingOff,
433 10 => PulseEngineAxisState::Home,
434 11 => PulseEngineAxisState::HomingStart,
435 12 => PulseEngineAxisState::HomingSearch,
436 13 => PulseEngineAxisState::HomingBack,
437 14 => PulseEngineAxisState::Probed,
438 15 => PulseEngineAxisState::ProbeStart,
439 16 => PulseEngineAxisState::ProbeSearch,
440 20 => PulseEngineAxisState::Error,
441 30 => PulseEngineAxisState::Limit,
442 _ => PulseEngineAxisState::Stopped,
443 }
444 }
445
446 pub fn is_axis_limit_positive(&self, axis: usize) -> bool {
448 if axis >= 8 {
449 return false;
450 }
451 self.limit_status_p & (1 << axis) != 0
452 }
453
454 pub fn is_axis_limit_negative(&self, axis: usize) -> bool {
456 if axis >= 8 {
457 return false;
458 }
459 self.limit_status_n & (1 << axis) != 0
460 }
461
462 pub fn is_axis_home(&self, axis: usize) -> bool {
464 if axis >= 8 {
465 return false;
466 }
467 self.home_status & (1 << axis) != 0
468 }
469
470 pub fn is_axis_soft_limit(&self, axis: usize) -> bool {
472 if axis >= 8 {
473 return false;
474 }
475 self.soft_limit_status & (1 << axis) != 0
476 }
477
478 pub fn is_axis_enabled(&self, axis: usize) -> bool {
479 if axis >= 8 {
480 return false;
481 }
482 (self.axis_enabled_mask & (1 << axis)) != 0
483 }
484
485 pub fn is_axis_homed(&self, axis: usize) -> bool {
486 if axis >= 8 {
487 return false;
488 }
489 (self.home_status & (1 << axis)) != 0
490 }
491
492 pub fn is_limit_triggered(&self, axis: usize, positive: bool) -> bool {
493 if axis >= 8 {
494 return false;
495 }
496
497 if positive {
498 (self.limit_status_p & (1 << axis)) != 0
499 } else {
500 (self.limit_status_n & (1 << axis)) != 0
501 }
502 }
503}
504
505impl Default for PulseEngineV2 {
506 fn default() -> Self {
507 Self::new()
508 }
509}
510
511impl PoKeysDevice {
512 pub fn enable_pulse_engine(&mut self, enable: bool) -> Result<()> {
514 self.pulse_engine_v2.pulse_engine_enabled = if enable { 1 } else { 0 };
515 self.send_request(0x80, if enable { 1 } else { 0 }, 0, 0, 0)?;
516 Ok(())
517 }
518
519 pub fn activate_pulse_engine(&mut self, activate: bool) -> Result<()> {
521 self.pulse_engine_v2.pulse_engine_activated = if activate { 1 } else { 0 };
522 self.send_request(0x81, if activate { 1 } else { 0 }, 0, 0, 0)?;
523 Ok(())
524 }
525
526 pub fn setup_pulse_engine_with_axes(
529 &mut self,
530 config: &PulseEngineConfig,
531 axis_enable_mask: u8,
532 ) -> Result<()> {
533 let mut request = vec![0u8; 56]; request[0] = config.enabled_axes; request[1] = config.charge_pump_enabled; request[2] = config.generator_type & 0x7F; request[3] = config.buffer_size; request[4] = config.emergency_switch_polarity; request[5] = axis_enable_mask; self.send_request_with_data(0x85, 0x01, 0, 0, 0, &request)?;
546 Ok(())
547 }
548
549 pub fn get_pulse_engine_status(&mut self) -> Result<()> {
550 let response = self.send_request(0x85, 0x00, 0, 0, 0)?;
551
552 if response.len() >= 64 {
553 self.pulse_engine_v2.soft_limit_status = response[3];
555 self.pulse_engine_v2.axis_enabled_states_mask = response[4];
556 self.pulse_engine_v2.axis_enabled_mask = response[4]; self.pulse_engine_v2.limit_override = response[5];
558 self.pulse_engine_v2.info.nr_of_axes = response[8];
560 self.pulse_engine_v2.pulse_engine_activated = response[9];
561 self.pulse_engine_v2.pulse_engine_state = response[10];
562 self.pulse_engine_v2.charge_pump_enabled = response[11];
563 self.pulse_engine_v2.limit_status_p = response[12];
564 self.pulse_engine_v2.limit_status_n = response[13];
565 self.pulse_engine_v2.home_status = response[14];
566 self.pulse_engine_v2.pulse_generator_type = response[15];
567
568 for i in 0..8 {
570 if 16 + i < response.len() {
571 self.pulse_engine_v2.axes_state[i] = response[16 + i];
572 }
573 }
574
575 for i in 0..8 {
577 let base = 24 + i * 4;
578 if base + 3 < response.len() {
579 self.pulse_engine_v2.current_position[i] = i32::from_le_bytes([
580 response[base],
581 response[base + 1],
582 response[base + 2],
583 response[base + 3],
584 ]);
585 }
586 }
587
588 if response.len() >= 63 {
590 self.pulse_engine_v2.info.max_pulse_frequency = response[57];
591 self.pulse_engine_v2.info.buffer_depth = response[58];
592 self.pulse_engine_v2.info.slot_timing = response[59];
593 self.pulse_engine_v2.emergency_switch_polarity = response[60];
594 self.pulse_engine_v2.error_input_status = response[61];
595 self.pulse_engine_v2.misc_input_status = response[62];
596 }
597 }
598
599 Ok(())
600 }
601
602 pub fn set_axis_position(&mut self, axis: usize, position: i32) -> Result<()> {
604 if axis >= 8 {
605 return Err(PoKeysError::Parameter("Axis index must be 0-7".to_string()));
606 }
607
608 let mut request = vec![0u8; 56]; let pos_bytes = position.to_le_bytes();
613 let start_idx = axis * 4;
614 request[start_idx..start_idx + 4].copy_from_slice(&pos_bytes);
615
616 let axis_mask = 1u8 << axis;
618
619 self.send_request_with_data(0x85, 0x03, axis_mask, 0, 0, &request)?;
622 Ok(())
623 }
624
625 pub fn set_pulse_engine_state(
627 &mut self,
628 state: u8,
629 limit_override: u8,
630 output_enable_mask: u8,
631 ) -> Result<()> {
632 let request = vec![0u8; 55]; self.send_request_with_data(
634 0x85,
635 0x02,
636 state,
637 limit_override,
638 output_enable_mask,
639 &request,
640 )?;
641 Ok(())
642 }
643
644 pub fn reboot_pulse_engine(&mut self) -> Result<()> {
646 let request = vec![0u8; 55]; self.send_request_with_data(0x85, 0x05, 0, 0, 0, &request)?;
648 Ok(())
649 }
650
651 pub fn set_axis_configuration(&mut self, axis: usize) -> Result<()> {
653 if axis >= 8 {
654 return Err(PoKeysError::Parameter("Axis index must be 0-7".to_string()));
655 }
656
657 let mut request = vec![0u8; 56]; let mut axis_options = 0x01; axis_options |= 1 << 2; axis_options |= 1 << 3; axis_options |= 1 << 6; if self.pulse_engine_v2.soft_limit_minimum[axis] != 0
665 || self.pulse_engine_v2.soft_limit_maximum[axis] != 0
666 {
667 axis_options |= 1 << 5; }
669 request[0] = axis_options;
670
671 request[1] = 0x00; request[2] = 0; request[3] = 0; request[4] = 0; request[5] = 50; request[6] = 10; request[7] = 0;
685
686 let speed_timeslots = self.pulse_engine_v2.max_speed[axis] / 25000.0;
690 let speed_bytes = speed_timeslots.to_le_bytes();
691 request[8..12].copy_from_slice(&speed_bytes);
692
693 let accel_timeslots = self.pulse_engine_v2.max_acceleration[axis] / 25000.0;
695 let accel_bytes = accel_timeslots.to_le_bytes();
696 request[12..16].copy_from_slice(&accel_bytes);
697
698 let decel_timeslots = self.pulse_engine_v2.max_deceleration[axis] / 25000.0;
700 let decel_bytes = decel_timeslots.to_le_bytes();
701 request[16..20].copy_from_slice(&decel_bytes);
702
703 let min_bytes = self.pulse_engine_v2.soft_limit_minimum[axis].to_le_bytes();
705 request[20..24].copy_from_slice(&min_bytes);
706
707 let max_bytes = self.pulse_engine_v2.soft_limit_maximum[axis].to_le_bytes();
709 request[24..28].copy_from_slice(&max_bytes);
710
711 request[28] = 1;
713 request[29] = 0;
714
715 request[30] = 0;
717
718 request[31] = 0;
720
721 request[32] = 0; request[33] = 0; request[34] = 0; request[35] = 0x83; let backoff = 0i32;
731 let backoff_bytes = backoff.to_le_bytes();
732 request[37..41].copy_from_slice(&backoff_bytes);
733
734 request[41] = 1;
736 request[42] = 0;
737
738 request[43] = 0x00; request[44] = 0;
743
744 self.send_request_with_data(0x85, 0x11, axis as u8, 0, 0, &request)?;
748 Ok(())
749 }
750
751 pub fn get_axis_configuration(&mut self, axis: usize) -> Result<()> {
753 if axis >= 8 {
754 return Err(PoKeysError::Parameter("Axis index must be 0-7".to_string()));
755 }
756
757 let response = self.send_request(0x85, 0x10, axis as u8, 0, 0)?;
758
759 if response.len() >= 40 {
760 let speed_bytes = [response[16], response[17], response[18], response[19]];
762 let accel_bytes = [response[20], response[21], response[22], response[23]];
763 let decel_bytes = [response[24], response[25], response[26], response[27]];
764
765 let readback_speed = f32::from_le_bytes(speed_bytes);
766 let readback_accel = f32::from_le_bytes(accel_bytes);
767 let readback_decel = f32::from_le_bytes(decel_bytes);
768
769 println!(
770 "DEBUG 0x85/0x10 READBACK: speed={:.6}, accel={:.6}, decel={:.6}",
771 readback_speed, readback_accel, readback_decel
772 );
773 println!(
774 "DEBUG ORIGINAL VALUES: speed={:.6}, accel={:.6}, decel={:.6}",
775 self.pulse_engine_v2.max_speed[axis],
776 self.pulse_engine_v2.max_acceleration[axis],
777 self.pulse_engine_v2.max_deceleration[axis]
778 );
779 }
780
781 if response.len() >= 64 {
783 self.pulse_engine_v2.axes_config[axis] = response[8];
785
786 self.pulse_engine_v2.axes_switch_config[axis] = response[9];
788
789 self.pulse_engine_v2.homing_speed[axis] = response[13];
791
792 self.pulse_engine_v2.homing_return_speed[axis] = response[14];
794
795 self.pulse_engine_v2.mpg_jog_encoder[axis] = response[15];
797
798 let speed_bytes = [response[16], response[17], response[18], response[19]];
800 self.pulse_engine_v2.max_speed[axis] = f32::from_le_bytes(speed_bytes);
801
802 let accel_bytes = [response[20], response[21], response[22], response[23]];
804 self.pulse_engine_v2.max_acceleration[axis] = f32::from_le_bytes(accel_bytes);
805
806 let decel_bytes = [response[24], response[25], response[26], response[27]];
808 self.pulse_engine_v2.max_deceleration[axis] = f32::from_le_bytes(decel_bytes);
809
810 let min_pos_bytes = [response[28], response[29], response[30], response[31]];
812 self.pulse_engine_v2.soft_limit_minimum[axis] = i32::from_le_bytes(min_pos_bytes);
813
814 let max_pos_bytes = [response[32], response[33], response[34], response[35]];
816 self.pulse_engine_v2.soft_limit_maximum[axis] = i32::from_le_bytes(max_pos_bytes);
817 }
818
819 Ok(())
820 }
821 pub fn get_axis_position(&mut self, axis: usize) -> Result<i32> {
822 if axis >= 8 {
823 return Err(PoKeysError::Parameter("Invalid axis number".to_string()));
824 }
825
826 self.read_pulse_engine_status()?;
828
829 Ok(self.pulse_engine_v2.current_position[axis])
830 }
831
832 pub fn move_axis_to_position(&mut self, axis: usize, position: i32, _speed: f32) -> Result<()> {
834 if axis >= 8 {
835 return Err(PoKeysError::Parameter("Invalid axis number".to_string()));
836 }
837
838 self.get_pulse_engine_status()?;
840
841 self.pulse_engine_v2.reference_position_speed[axis] = position;
843
844 let mut request = vec![0u8; 56]; for i in 0..8 {
850 let pos = if i == axis {
851 position } else {
853 self.pulse_engine_v2.current_position[i] };
855 let pos_bytes = pos.to_le_bytes();
856 request[i * 4..(i + 1) * 4].copy_from_slice(&pos_bytes);
857 }
858
859 self.send_request_with_data(0x85, 0x20, 0, 0, 0, &request)?;
860 Ok(())
861 }
862
863 pub fn start_axis_homing(&mut self, axis_mask: u8) -> Result<()> {
865 self.pulse_engine_v2.homing_start_mask_setup = axis_mask;
866 self.send_request(0x85, axis_mask, 0, 0, 0)?;
867 Ok(())
868 }
869
870 pub fn configure_axis_homing(
872 &mut self,
873 axis: usize,
874 home_pin: u8,
875 homing_speed: u8,
876 home_offset: i32,
877 ) -> Result<()> {
878 if axis >= 8 {
879 return Err(PoKeysError::Parameter("Invalid axis number".to_string()));
880 }
881
882 self.pulse_engine_v2.pin_home_switch[axis] = home_pin;
883 self.pulse_engine_v2.homing_speed[axis] = homing_speed;
884 self.pulse_engine_v2.home_offsets[axis] = home_offset;
885
886 self.send_request(0x86, axis as u8, home_pin, homing_speed, 0)?;
887 Ok(())
888 }
889
890 pub fn configure_axis_limits(
892 &mut self,
893 axis: usize,
894 limit_n_pin: u8,
895 limit_p_pin: u8,
896 soft_limit_min: i32,
897 soft_limit_max: i32,
898 ) -> Result<()> {
899 if axis >= 8 {
900 return Err(PoKeysError::Parameter("Invalid axis number".to_string()));
901 }
902
903 self.pulse_engine_v2.pin_limit_m_switch[axis] = limit_n_pin;
904 self.pulse_engine_v2.pin_limit_p_switch[axis] = limit_p_pin;
905 self.pulse_engine_v2.soft_limit_minimum[axis] = soft_limit_min;
906 self.pulse_engine_v2.soft_limit_maximum[axis] = soft_limit_max;
907
908 self.send_request(0x87, axis as u8, limit_n_pin, limit_p_pin, 0)?;
909 Ok(())
910 }
911
912 pub fn emergency_stop(&mut self) -> Result<()> {
914 self.send_request(0x88, 0, 0, 0, 0)?;
915 Ok(())
916 }
917
918 pub fn read_pulse_engine_status(&mut self) -> Result<()> {
920 let response = self.send_request(0x89, 0, 0, 0, 0)?;
921
922 if response.len() >= 64 {
924 self.pulse_engine_v2.pulse_engine_state = response[8];
925 self.pulse_engine_v2.axis_enabled_mask = response[9];
926 self.pulse_engine_v2.limit_status_p = response[10];
927 self.pulse_engine_v2.limit_status_n = response[11];
928 self.pulse_engine_v2.home_status = response[12];
929
930 for i in 0..8 {
932 let pos_index = 16 + (i * 4);
933 if pos_index + 3 < response.len() {
934 self.pulse_engine_v2.current_position[i] = i32::from_le_bytes([
935 response[pos_index],
936 response[pos_index + 1],
937 response[pos_index + 2],
938 response[pos_index + 3],
939 ]);
940 }
941 }
942 }
943
944 Ok(())
945 }
946
947 pub fn get_pulse_engine_state(&mut self) -> Result<PulseEngineState> {
949 self.read_pulse_engine_status()?;
950 Ok(self.pulse_engine_v2.get_state())
951 }
952
953 pub fn get_axis_state(&mut self, axis: usize) -> Result<PulseEngineAxisState> {
955 if axis >= 8 {
956 return Err(PoKeysError::Parameter("Invalid axis number".to_string()));
957 }
958
959 self.read_pulse_engine_status()?;
960 Ok(self.pulse_engine_v2.get_axis_state(axis))
961 }
962
963 pub fn is_axis_homed(&mut self, axis: usize) -> Result<bool> {
965 if axis >= 8 {
966 return Err(PoKeysError::Parameter("Invalid axis number".to_string()));
967 }
968
969 self.read_pulse_engine_status()?;
970 Ok(self.pulse_engine_v2.is_axis_homed(axis))
971 }
972
973 pub fn wait_for_axis(&mut self, axis: usize, timeout_ms: u32) -> Result<()> {
975 if axis >= 8 {
976 return Err(PoKeysError::Parameter("Invalid axis number".to_string()));
977 }
978
979 let start_time = std::time::Instant::now();
980 let timeout = std::time::Duration::from_millis(timeout_ms as u64);
981
982 loop {
983 let state = self.get_axis_state(axis)?;
984
985 match state {
986 PulseEngineAxisState::Ready | PulseEngineAxisState::Stopped => break,
987 PulseEngineAxisState::Error | PulseEngineAxisState::Limit => {
988 return Err(PoKeysError::Protocol(
989 "Axis error or limit reached".to_string(),
990 ));
991 }
992 _ => {
993 if start_time.elapsed() > timeout {
994 return Err(PoKeysError::Timeout);
995 }
996 std::thread::sleep(std::time::Duration::from_millis(10));
997 }
998 }
999 }
1000
1001 Ok(())
1002 }
1003
1004 pub fn set_motor_drivers_configuration(&mut self) -> Result<()> {
1006 let mut request = vec![0u8; 56]; for axis in 0..4 {
1018 let protocol_offset = 9 + axis * 2; if protocol_offset >= 9 {
1020 let request_offset = protocol_offset - 9; if request_offset + 1 < request.len() {
1023 request[request_offset] = self.pulse_engine_v2.motor_step_setting[axis];
1024 request[request_offset + 1] = self.pulse_engine_v2.motor_current_setting[axis];
1025 }
1026 }
1027 }
1028
1029 println!(
1030 "DEBUG 0x85/0x19 REQUEST: Axis 2 -> protocol bytes 13,14 -> request[4,5] = step={}, current={}",
1031 request[4], request[5]
1032 );
1033
1034 let response = self.send_request_with_data(0x85, 0x19, 0, 0, 0, &request)?;
1035
1036 println!(
1037 "DEBUG 0x85/0x19 RESPONSE: protocol bytes 13,14 = step={}, current={}",
1038 response.get(13).unwrap_or(&255),
1039 response.get(14).unwrap_or(&255)
1040 );
1041
1042 Ok(())
1043 }
1044
1045 pub fn configure_axis(&mut self, axis: usize) -> AxisConfigBuilder {
1047 AxisConfigBuilder::new(axis)
1048 }
1049
1050 pub fn set_axis_positions(&mut self, axis_mask: u8, positions: &[i32; 8]) -> Result<()> {
1052 let mut request = vec![0u8; 56]; for (i, &position) in positions.iter().enumerate() {
1056 let pos_bytes = position.to_le_bytes();
1057 let offset = i * 4;
1058 request[offset..offset + 4].copy_from_slice(&pos_bytes);
1059 }
1060
1061 self.send_request_with_data(0x85, 0x03, axis_mask, 0, 0, &request)?;
1062 Ok(())
1063 }
1064
1065 pub fn move_pv(&mut self, axis_mask: u8, positions: &[i32; 8], velocity: u16) -> Result<()> {
1067 let mut request = vec![0u8; 56]; for (i, &position) in positions.iter().enumerate() {
1071 let pos_bytes = position.to_le_bytes();
1072 let offset = i * 4;
1073 request[offset..offset + 4].copy_from_slice(&pos_bytes);
1074 }
1075
1076 let vel_bytes = velocity.to_le_bytes();
1078 request[32..34].copy_from_slice(&vel_bytes);
1079
1080 self.send_request_with_data(0x85, 0x25, axis_mask, 0, 0, &request)?;
1081 Ok(())
1082 }
1083
1084 pub fn configure_motor_drivers(&mut self) -> MotorDriverConfigBuilder {
1086 MotorDriverConfigBuilder::new()
1087 }
1088
1089 pub fn get_motor_drivers_configuration(&mut self) -> Result<()> {
1091 let response = self.send_request(0x85, 0x18, 0, 0, 0)?;
1092
1093 for axis in 0..4 {
1095 let byte_offset = 8 + (axis * 2); if byte_offset + 1 < response.len() {
1097 let step_setting = response[byte_offset];
1098 let current_setting = response[byte_offset + 1];
1099
1100 self.pulse_engine_v2.motor_step_setting[axis] = step_setting;
1102 self.pulse_engine_v2.motor_current_setting[axis] = current_setting;
1103 }
1104 }
1105
1106 Ok(())
1107 }
1108}
1109
1110pub struct AxisConfigBuilder {
1112 axis: usize,
1113 max_speed: f32,
1114 max_acceleration: f32,
1115 max_deceleration: f32,
1116 soft_limit_min: i32,
1117 soft_limit_max: i32,
1118}
1119
1120impl AxisConfigBuilder {
1121 pub fn new(axis: usize) -> Self {
1122 Self {
1123 axis,
1124 max_speed: 1000.0,
1125 max_acceleration: 100.0,
1126 max_deceleration: 100.0,
1127 soft_limit_min: 0,
1128 soft_limit_max: 0,
1129 }
1130 }
1131
1132 pub fn max_speed(mut self, speed: f32) -> Self {
1133 self.max_speed = speed;
1134 self
1135 }
1136
1137 pub fn max_acceleration(mut self, acceleration: f32) -> Self {
1138 self.max_acceleration = acceleration;
1139 self
1140 }
1141
1142 pub fn max_deceleration(mut self, deceleration: f32) -> Self {
1143 self.max_deceleration = deceleration;
1144 self
1145 }
1146
1147 pub fn soft_limit_min(mut self, min: i32) -> Self {
1148 self.soft_limit_min = min;
1149 self
1150 }
1151
1152 pub fn soft_limit_max(mut self, max: i32) -> Self {
1153 self.soft_limit_max = max;
1154 self
1155 }
1156
1157 pub fn build(self, device: &mut PoKeysDevice) -> Result<()> {
1158 if self.axis >= 8 {
1159 return Err(PoKeysError::Parameter("Axis index must be 0-7".to_string()));
1160 }
1161
1162 device.pulse_engine_v2.max_speed[self.axis] = self.max_speed;
1164 device.pulse_engine_v2.max_acceleration[self.axis] = self.max_acceleration;
1165 device.pulse_engine_v2.max_deceleration[self.axis] = self.max_deceleration;
1166 device.pulse_engine_v2.soft_limit_minimum[self.axis] = self.soft_limit_min;
1167 device.pulse_engine_v2.soft_limit_maximum[self.axis] = self.soft_limit_max;
1168
1169 device.set_axis_configuration(self.axis)
1170 }
1171}
1172
1173pub struct MotorDriverConfigBuilder {
1175 step_settings: [Option<u8>; 4],
1176 current_settings: [Option<u8>; 4],
1177}
1178
1179impl Default for MotorDriverConfigBuilder {
1180 fn default() -> Self {
1181 Self::new()
1182 }
1183}
1184
1185impl MotorDriverConfigBuilder {
1186 pub fn new() -> Self {
1187 Self {
1188 step_settings: [None; 4],
1189 current_settings: [None; 4],
1190 }
1191 }
1192
1193 pub fn axis_step_setting(mut self, axis: usize, step_setting: u8) -> Self {
1194 if axis < 4 {
1195 self.step_settings[axis] = Some(step_setting);
1196 }
1197 self
1198 }
1199
1200 pub fn axis_current_setting(mut self, axis: usize, current_setting: u8) -> Self {
1201 if axis < 4 {
1202 self.current_settings[axis] = Some(current_setting);
1203 }
1204 self
1205 }
1206
1207 pub fn build(self, device: &mut PoKeysDevice) -> Result<()> {
1208 for axis in 0..4 {
1210 if let Some(step_setting) = self.step_settings[axis] {
1211 device.pulse_engine_v2.motor_step_setting[axis] = step_setting;
1212 }
1213 if let Some(current_setting) = self.current_settings[axis] {
1214 device.pulse_engine_v2.motor_current_setting[axis] = current_setting;
1215 }
1216 }
1217 device.set_motor_drivers_configuration()
1218 }
1219}
1220
1221#[cfg(test)]
1222mod tests {
1223 use super::*;
1224
1225 #[test]
1226 fn test_pulse_engine_creation() {
1227 let pe = PulseEngineV2::new();
1228 assert!(!pe.is_enabled());
1229 assert!(!pe.is_activated());
1230 assert_eq!(pe.get_state(), PulseEngineState::Stopped);
1231 }
1232
1233 #[test]
1234 fn test_axis_state_conversion() {
1235 let mut pe = PulseEngineV2::new();
1236
1237 pe.axes_state[0] = 1;
1238 assert_eq!(pe.get_axis_state(0), PulseEngineAxisState::Ready);
1239
1240 pe.axes_state[0] = 2;
1241 assert_eq!(pe.get_axis_state(0), PulseEngineAxisState::Running);
1242
1243 pe.axes_state[0] = 20;
1244 assert_eq!(pe.get_axis_state(0), PulseEngineAxisState::Error);
1245 }
1246
1247 #[test]
1248 fn test_axis_enable_mask() {
1249 let mut pe = PulseEngineV2::new();
1250
1251 assert!(!pe.is_axis_enabled(0));
1252
1253 pe.axis_enabled_mask = 0b00000001;
1254 assert!(pe.is_axis_enabled(0));
1255 assert!(!pe.is_axis_enabled(1));
1256
1257 pe.axis_enabled_mask = 0b00000011;
1258 assert!(pe.is_axis_enabled(0));
1259 assert!(pe.is_axis_enabled(1));
1260 assert!(!pe.is_axis_enabled(2));
1261 }
1262}