1use crate::motion::{
2 AxisView,
3 cia402::{
4 Cia402Control, Cia402Status, Cia402State, ModesOfOperation,
5 PpControl, PpStatus,
6 HomingControl, HomingStatus,
7 },
8};
9use crate::ethercat::teknic::types::{
10 TeknicPpControlWord, TeknicPpStatusWord,
11 TeknicPpControl, TeknicPpStatus,
12 TeknicHomingControlWord, TeknicHomingStatusWord,
13};
14
15#[derive(Debug, Clone, Copy, PartialEq, Eq)]
29pub enum HomingProgress {
30 Idle,
32 InProgress,
34 Attained,
36 Complete,
38 Error,
40}
41
42impl std::fmt::Display for HomingProgress {
43 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
44 match self {
45 Self::Idle => write!(f, "Idle"),
46 Self::InProgress => write!(f, "In Progress"),
47 Self::Attained => write!(f, "Attained"),
48 Self::Complete => write!(f, "Complete"),
49 Self::Error => write!(f, "Error"),
50 }
51 }
52}
53
54pub struct TeknicPpView<'a> {
210 pub control_word: &'a mut u16,
212 pub target_position: &'a mut i32,
213 pub profile_velocity: &'a mut u32,
214 pub profile_acceleration: &'a mut u32,
215 pub profile_deceleration: &'a mut u32,
216 pub modes_of_operation: &'a mut i8,
217
218 pub status_word: &'a u16,
220 pub position_actual: &'a i32,
221 pub velocity_actual: &'a i32,
222 pub torque_actual: &'a i16,
223 pub modes_of_operation_display: &'a i8,
224}
225
226impl<'a> TeknicPpView<'a> {
227 pub fn pp_control(&self) -> TeknicPpControlWord {
231 TeknicPpControlWord(*self.control_word)
232 }
233
234 pub fn pp_status(&self) -> TeknicPpStatusWord {
236 TeknicPpStatusWord(*self.status_word)
237 }
238
239 pub fn set_pp_control(&mut self, cw: TeknicPpControlWord) {
241 *self.control_word = cw.raw();
242 }
243
244 pub fn state(&self) -> Cia402State {
248 self.pp_status().state()
249 }
250
251 pub fn cmd_shutdown(&mut self) {
253 let mut cw = self.pp_control();
254 cw.cmd_shutdown();
255 self.set_pp_control(cw);
256 }
257
258 pub fn cmd_switch_on(&mut self) {
260 let mut cw = self.pp_control();
261 cw.cmd_switch_on();
262 self.set_pp_control(cw);
263 }
264
265 pub fn cmd_enable_operation(&mut self) {
267 let mut cw = self.pp_control();
268 cw.cmd_enable_operation();
269 self.set_pp_control(cw);
270 }
271
272 pub fn cmd_disable_operation(&mut self) {
274 let mut cw = self.pp_control();
275 cw.cmd_disable_operation();
276 self.set_pp_control(cw);
277 }
278
279 pub fn cmd_disable_voltage(&mut self) {
281 let mut cw = self.pp_control();
282 cw.cmd_disable_voltage();
283 self.set_pp_control(cw);
284 }
285
286 pub fn cmd_quick_stop(&mut self) {
288 let mut cw = self.pp_control();
289 cw.cmd_quick_stop();
290 self.set_pp_control(cw);
291 }
292
293
294 pub fn cmd_fault_reset(&mut self) {
296 let mut cw = self.pp_control();
297 cw.cmd_fault_reset();
298 self.set_pp_control(cw);
299 }
300
301 pub fn cmd_clear_fault_reset(&mut self) {
303 let mut cw = self.pp_control();
304 cw.cmd_clear_fault_reset();
305 self.set_pp_control(cw);
306 }
307
308 pub fn ensure_pp_mode(&mut self) {
312 *self.modes_of_operation = ModesOfOperation::ProfilePosition.as_i8();
313 }
314
315 pub fn set_target(&mut self, position: i32, velocity: u32, accel: u32, decel: u32) {
317 *self.target_position = position;
318 *self.profile_velocity = velocity;
319 *self.profile_acceleration = accel;
320 *self.profile_deceleration = decel;
321 }
322
323 pub fn trigger_move(&mut self) {
325 let mut cw = self.pp_control();
326 cw.set_new_set_point(true);
327 self.set_pp_control(cw);
328 }
329
330 pub fn ack_set_point(&mut self) {
332 let mut cw = self.pp_control();
333 cw.set_new_set_point(false);
334 self.set_pp_control(cw);
335 }
336
337 pub fn set_halt(&mut self, v: bool) {
339 let mut cw = self.pp_control();
340 cw.set_halt(v);
341 self.set_pp_control(cw);
342 }
343
344 pub fn set_relative(&mut self, v: bool) {
347 let mut cw = self.pp_control();
348 cw.set_relative(v);
349 self.set_pp_control(cw);
350 }
351
352 pub fn set_relative_to_actual(&mut self, v: bool) {
356 let mut cw = self.pp_control();
357 cw.set_relative_to_actual(v);
358 self.set_pp_control(cw);
359 }
360
361 pub fn target_reached(&self) -> bool {
365 self.pp_status().pp_target_reached()
366 }
367
368 pub fn set_point_acknowledged(&self) -> bool {
370 self.pp_status().set_point_acknowledge()
371 }
372
373 pub fn in_range(&self) -> bool {
375 self.pp_status().in_range()
376 }
377
378 pub fn has_homed(&self) -> bool {
381 self.pp_status().has_homed()
382 }
383
384 pub fn at_velocity(&self) -> bool {
386 self.pp_status().at_velocity()
387 }
388
389 pub fn following_error(&self) -> bool {
394 self.pp_status().following_error()
395 }
396
397 pub fn internal_limit(&self) -> bool {
400 self.pp_status().internal_limit()
401 }
402
403 pub fn warning(&self) -> bool {
405 self.pp_status().warning()
406 }
407
408 pub fn is_faulted(&self) -> bool {
410 matches!(self.state(), Cia402State::Fault | Cia402State::FaultReactionActive)
411 }
412
413 pub fn ensure_homing_mode(&mut self) {
417 *self.modes_of_operation = ModesOfOperation::Homing.as_i8();
418 }
419
420 pub fn trigger_homing(&mut self) {
426 let mut cw = TeknicHomingControlWord(*self.control_word);
427 cw.set_homing_start(true);
428 *self.control_word = cw.raw();
429 }
430
431 pub fn clear_homing_start(&mut self) {
433 let mut cw = TeknicHomingControlWord(*self.control_word);
434 cw.set_homing_start(false);
435 *self.control_word = cw.raw();
436 }
437
438 pub fn homing_progress(&self) -> HomingProgress {
442 let sw = TeknicHomingStatusWord(*self.status_word);
443 let attained = sw.homing_attained();
444 let reached = sw.homing_target_reached();
445 let error = sw.homing_error();
446
447 if error {
448 HomingProgress::Error
449 } else if attained && reached {
450 HomingProgress::Complete
451 } else if attained {
452 HomingProgress::Attained
453 } else if reached {
454 HomingProgress::Idle
455 } else {
456 HomingProgress::InProgress
457 }
458 }
459
460 pub fn position(&self) -> i32 {
464 *self.position_actual
465 }
466
467 pub fn velocity(&self) -> i32 {
469 *self.velocity_actual
470 }
471
472 pub fn torque(&self) -> i16 {
474 *self.torque_actual
475 }
476
477 pub fn current_mode(&self) -> Option<ModesOfOperation> {
479 ModesOfOperation::from_i8(*self.modes_of_operation_display)
480 }
481}
482
483impl AxisView for TeknicPpView<'_> {
488 fn control_word(&self) -> u16 { *self.control_word }
489 fn set_control_word(&mut self, word: u16) { *self.control_word = word; }
490 fn status_word(&self) -> u16 { *self.status_word }
491 fn set_target_position(&mut self, pos: i32) { *self.target_position = pos; }
492 fn set_profile_velocity(&mut self, vel: u32) { *self.profile_velocity = vel; }
493 fn set_profile_acceleration(&mut self, accel: u32) { *self.profile_acceleration = accel; }
494 fn set_profile_deceleration(&mut self, decel: u32) { *self.profile_deceleration = decel; }
495 fn set_modes_of_operation(&mut self, mode: i8) { *self.modes_of_operation = mode; }
496 fn modes_of_operation_display(&self) -> i8 { *self.modes_of_operation_display }
497 fn position_actual(&self) -> i32 { *self.position_actual }
498 fn velocity_actual(&self) -> i32 { *self.velocity_actual }
499 }
501
502#[macro_export]
521macro_rules! teknic_pp_view {
522 ($gm:expr, $prefix:ident) => {
523 paste::paste! {
524 $crate::teknic::view::TeknicPpView {
525 control_word: &mut $gm.[<$prefix _control_word>],
526 target_position: &mut $gm.[<$prefix _target_position>],
527 profile_velocity: &mut $gm.[<$prefix _profile_velocity>],
528 profile_acceleration: &mut $gm.[<$prefix _profile_acceleration>],
529 profile_deceleration: &mut $gm.[<$prefix _profile_deceleration>],
530 modes_of_operation: &mut $gm.[<$prefix _modes_of_operation>],
531 status_word: & $gm.[<$prefix _status_word>],
532 position_actual: & $gm.[<$prefix _position_actual>],
533 velocity_actual: & $gm.[<$prefix _velocity_actual>],
534 torque_actual: & $gm.[<$prefix _torque_actual>],
535 modes_of_operation_display: & $gm.[<$prefix _modes_of_operation_display>],
536 }
537 }
538 };
539}
540
541#[cfg(test)]
546mod tests {
547 use super::*;
548
549 #[derive(Default)]
552 struct TestPdo {
553 control_word: u16,
554 target_position: i32,
555 profile_velocity: u32,
556 profile_acceleration: u32,
557 profile_deceleration: u32,
558 modes_of_operation: i8,
559 status_word: u16,
560 position_actual: i32,
561 velocity_actual: i32,
562 torque_actual: i16,
563 modes_of_operation_display: i8,
564 }
565
566 impl TestPdo {
567 fn view(&mut self) -> TeknicPpView<'_> {
568 TeknicPpView {
569 control_word: &mut self.control_word,
570 target_position: &mut self.target_position,
571 profile_velocity: &mut self.profile_velocity,
572 profile_acceleration: &mut self.profile_acceleration,
573 profile_deceleration: &mut self.profile_deceleration,
574 modes_of_operation: &mut self.modes_of_operation,
575 status_word: &self.status_word,
576 position_actual: &self.position_actual,
577 velocity_actual: &self.velocity_actual,
578 torque_actual: &self.torque_actual,
579 modes_of_operation_display: &self.modes_of_operation_display,
580 }
581 }
582 }
583
584 #[test]
587 fn test_view_reads_state() {
588 let mut pdo = TestPdo { status_word: 0x0040, ..Default::default() };
589 let view = pdo.view();
590 assert_eq!(view.state(), Cia402State::SwitchOnDisabled);
591 }
592
593 #[test]
594 fn test_view_cmd_shutdown() {
595 let mut pdo = TestPdo { status_word: 0x0040, ..Default::default() };
596 let mut view = pdo.view();
597 view.cmd_shutdown();
598 assert_eq!(*view.control_word & 0x008F, 0x0006);
599 }
600
601 #[test]
602 fn test_view_cmd_enable_operation() {
603 let mut pdo = TestPdo::default();
604 let mut view = pdo.view();
605 view.cmd_enable_operation();
606 assert_eq!(*view.control_word & 0x008F, 0x000F);
607 }
608
609 #[test]
612 fn test_view_set_target_and_trigger() {
613 let mut pdo = TestPdo::default();
614 let mut view = pdo.view();
615 view.cmd_enable_operation();
616 view.set_target(50_000, 10_000, 2_000, 2_000);
617 view.trigger_move();
618
619 assert_eq!(*view.target_position, 50_000);
620 assert_eq!(*view.profile_velocity, 10_000);
621 assert_eq!(*view.profile_acceleration, 2_000);
622 assert_eq!(*view.profile_deceleration, 2_000);
623 assert!(*view.control_word & (1 << 4) != 0);
624 }
625
626 #[test]
627 fn test_view_ack_set_point() {
628 let mut pdo = TestPdo::default();
629 let mut view = pdo.view();
630 view.cmd_enable_operation();
631 view.trigger_move();
632 assert!(*view.control_word & (1 << 4) != 0);
633
634 view.ack_set_point();
635 assert!(*view.control_word & (1 << 4) == 0);
636 assert_eq!(*view.control_word & 0x000F, 0x000F);
637 }
638
639 #[test]
640 fn test_view_absolute_move() {
641 let mut pdo = TestPdo::default();
642 let mut view = pdo.view();
643 view.cmd_enable_operation();
644 view.set_relative(false);
645 view.set_target(100_000, 50_000, 10_000, 10_000);
646 view.trigger_move();
647
648 assert_eq!(*view.control_word & (1 << 6), 0);
649 assert!(*view.control_word & (1 << 4) != 0);
650 }
651
652 #[test]
653 fn test_view_relative_move() {
654 let mut pdo = TestPdo::default();
655 let mut view = pdo.view();
656 view.cmd_enable_operation();
657 view.set_relative(true);
658 view.set_target(5_000, 10_000, 2_000, 2_000);
659 view.trigger_move();
660
661 assert!(*view.control_word & (1 << 6) != 0);
662 }
663
664 #[test]
665 fn test_view_relative_to_actual() {
666 let mut pdo = TestPdo::default();
667 let mut view = pdo.view();
668 view.cmd_enable_operation();
669 view.set_relative(true);
670 view.set_relative_to_actual(true);
671 view.set_target(1_000, 5_000, 1_000, 1_000);
672 view.trigger_move();
673
674 assert!(*view.control_word & (1 << 6) != 0);
675 assert!(*view.control_word & (1 << 13) != 0);
676 }
677
678 #[test]
681 fn test_view_feedback() {
682 let mut pdo = TestPdo {
683 position_actual: 12345,
684 velocity_actual: -500,
685 torque_actual: 100,
686 modes_of_operation_display: 1,
687 ..Default::default()
688 };
689 let view = pdo.view();
690
691 assert_eq!(view.position(), 12345);
692 assert_eq!(view.velocity(), -500);
693 assert_eq!(view.torque(), 100);
694 assert_eq!(view.current_mode(), Some(ModesOfOperation::ProfilePosition));
695 }
696
697 #[test]
698 fn test_view_teknic_status_bits() {
699 let mut pdo = TestPdo {
700 status_word: 0x8127,
702 ..Default::default()
703 };
704 let view = pdo.view();
705
706 assert_eq!(view.state(), Cia402State::OperationEnabled);
707 assert!(view.has_homed());
708 assert!(view.in_range());
709 assert!(!view.at_velocity());
710 }
711
712 #[test]
713 fn test_view_ensure_pp_mode() {
714 let mut pdo = TestPdo::default();
715 let mut view = pdo.view();
716 view.ensure_pp_mode();
717 assert_eq!(*view.modes_of_operation, 1);
718 }
719
720 #[test]
723 fn test_view_following_error() {
724 let mut pdo = TestPdo {
725 status_word: 0x2027,
727 ..Default::default()
728 };
729 let view = pdo.view();
730
731 assert_eq!(view.state(), Cia402State::OperationEnabled);
732 assert!(view.following_error());
733 assert!(!view.is_faulted());
734 }
735
736 #[test]
737 fn test_view_internal_limit() {
738 let mut pdo = TestPdo { status_word: 0x0827, ..Default::default() };
739 let view = pdo.view();
740 assert!(view.internal_limit());
741 }
742
743 #[test]
744 fn test_view_warning() {
745 let mut pdo = TestPdo { status_word: 0x00A7, ..Default::default() };
746 let view = pdo.view();
747 assert_eq!(view.state(), Cia402State::OperationEnabled);
748 assert!(view.warning());
749 }
750
751 #[test]
752 fn test_view_is_faulted() {
753 let mut pdo = TestPdo::default();
754
755 pdo.status_word = 0x0008; let view = pdo.view();
757 assert!(view.is_faulted());
758 assert_eq!(view.state(), Cia402State::Fault);
759
760 pdo.status_word = 0x000F; let view = pdo.view();
762 assert!(view.is_faulted());
763
764 pdo.status_word = 0x0027; let view = pdo.view();
766 assert!(!view.is_faulted());
767 }
768
769 #[test]
772 fn test_view_ensure_homing_mode() {
773 let mut pdo = TestPdo::default();
774 let mut view = pdo.view();
775 view.ensure_homing_mode();
776 assert_eq!(*view.modes_of_operation, 6);
777 }
778
779 #[test]
780 fn test_view_trigger_and_clear_homing() {
781 let mut pdo = TestPdo::default();
782 let mut view = pdo.view();
783 view.cmd_enable_operation();
784
785 assert_eq!(*view.control_word & (1 << 4), 0);
787
788 view.trigger_homing();
790 assert!(*view.control_word & (1 << 4) != 0);
791 assert_eq!(*view.control_word & 0x000F, 0x000F);
793
794 view.clear_homing_start();
796 assert_eq!(*view.control_word & (1 << 4), 0);
797 assert_eq!(*view.control_word & 0x000F, 0x000F);
798 }
799
800 #[test]
801 fn test_homing_progress_idle() {
802 let mut pdo = TestPdo {
803 status_word: 0x0427, ..Default::default()
806 };
807 let view = pdo.view();
808 assert_eq!(view.homing_progress(), HomingProgress::Idle);
809 }
810
811 #[test]
812 fn test_homing_progress_in_progress() {
813 let mut pdo = TestPdo {
814 status_word: 0x0027, ..Default::default()
817 };
818 let view = pdo.view();
819 assert_eq!(view.homing_progress(), HomingProgress::InProgress);
820 }
821
822 #[test]
823 fn test_homing_progress_attained() {
824 let mut pdo = TestPdo {
825 status_word: 0x1027,
827 ..Default::default()
828 };
829 let view = pdo.view();
830 assert_eq!(view.homing_progress(), HomingProgress::Attained);
831 }
832
833 #[test]
834 fn test_homing_progress_complete() {
835 let mut pdo = TestPdo {
836 status_word: 0x1427,
838 ..Default::default()
839 };
840 let view = pdo.view();
841 assert_eq!(view.homing_progress(), HomingProgress::Complete);
842 }
843
844 #[test]
845 fn test_homing_progress_error() {
846 let mut pdo = TestPdo {
847 status_word: 0x2027,
849 ..Default::default()
850 };
851 let view = pdo.view();
852 assert_eq!(view.homing_progress(), HomingProgress::Error);
853
854 pdo.status_word = 0x3427;
856 let view = pdo.view();
857 assert_eq!(view.homing_progress(), HomingProgress::Error);
858 }
859
860 #[test]
861 fn test_homing_has_homed_persists_across_modes() {
862 let mut pdo = TestPdo {
863 status_word: 0x0127,
865 modes_of_operation_display: 1, ..Default::default()
867 };
868 let view = pdo.view();
869
870 assert!(view.has_homed());
871 assert_eq!(view.current_mode(), Some(ModesOfOperation::ProfilePosition));
872 }
873
874 #[test]
877 fn test_axis_view_impl() {
878 let mut pdo = TestPdo {
879 status_word: 0x0027,
880 position_actual: 5000,
881 velocity_actual: 1000,
882 modes_of_operation_display: 1,
883 ..Default::default()
884 };
885 let mut view = pdo.view();
886
887 assert_eq!(AxisView::status_word(&view), 0x0027);
889 assert_eq!(AxisView::position_actual(&view), 5000);
890 assert_eq!(AxisView::velocity_actual(&view), 1000);
891 assert_eq!(AxisView::modes_of_operation_display(&view), 1);
892
893 AxisView::set_control_word(&mut view, 0x000F);
895 assert_eq!(*view.control_word, 0x000F);
896 AxisView::set_target_position(&mut view, 10_000);
897 assert_eq!(*view.target_position, 10_000);
898 }
899}