1use strum_macros::FromRepr;
17use std::time::{Duration, Instant};
18
19use serde_json::json;
20
21use crate::fb::StateMachine;
22use super::axis_view::AxisView;
23use crate::command_client::CommandClient;
24use crate::ethercat::{SdoClient, SdoResult};
25
26
27#[derive(Clone, Copy, Debug, Default)]
29pub struct RawControlWord(pub u16);
30
31#[derive(Clone, Copy, Debug, Default)]
33pub struct RawStatusWord(pub u16);
34
35pub trait Cia402Control {
45 fn raw(&self) -> u16;
47 fn raw_mut(&mut self) -> &mut u16;
49
50 fn set_switch_on(&mut self, v: bool) {
54 self.set_bit(0, v);
55 }
56 fn set_enable_voltage(&mut self, v: bool) {
58 self.set_bit(1, v);
59 }
60 fn set_quick_stop(&mut self, v: bool) {
62 self.set_bit(2, v);
63 }
64 fn set_enable_operation(&mut self, v: bool) {
66 self.set_bit(3, v);
67 }
68 fn set_fault_reset(&mut self, v: bool) {
70 self.set_bit(7, v);
71 }
72
73 fn cmd_shutdown(&mut self) {
83 let w = self.raw_mut();
84 *w = (*w & !0x008F) | 0x0006; }
86
87 fn cmd_switch_on(&mut self) {
90 let w = self.raw_mut();
91 *w = (*w & !0x008F) | 0x0007; }
93
94 fn cmd_enable_operation(&mut self) {
97 let w = self.raw_mut();
98 *w = (*w & !0x008F) | 0x000F; }
100
101 fn cmd_disable_operation(&mut self) {
104 let w = self.raw_mut();
105 *w = (*w & !0x008F) | 0x0007; }
107
108 fn cmd_disable_voltage(&mut self) {
111 let w = self.raw_mut();
112 *w &= !0x0082; }
114
115 fn cmd_quick_stop(&mut self) {
118 let w = self.raw_mut();
119 *w = (*w & !0x0086) | 0x0002; }
121
122 fn cmd_fault_reset(&mut self) {
125 let w = self.raw_mut();
126 *w |= 0x0080; }
128
129
130 fn cmd_clear_fault_reset(&mut self) {
132 self.set_bit(7, false);
133 }
134
135 fn set_bit(&mut self, bit: u8, v: bool) {
137 let w = self.raw_mut();
138 if v {
139 *w |= 1 << bit;
140 } else {
141 *w &= !(1 << bit);
142 }
143 }
144}
145
146pub trait Cia402Status {
154 fn raw(&self) -> u16;
156
157 fn ready_to_switch_on(&self) -> bool {
159 self.raw() & (1 << 0) != 0
160 }
161 fn switched_on(&self) -> bool {
163 self.raw() & (1 << 1) != 0
164 }
165 fn operation_enabled(&self) -> bool {
167 self.raw() & (1 << 2) != 0
168 }
169 fn fault(&self) -> bool {
171 self.raw() & (1 << 3) != 0
172 }
173 fn voltage_enabled(&self) -> bool {
175 self.raw() & (1 << 4) != 0
176 }
177 fn quick_stop_active(&self) -> bool {
179 self.raw() & (1 << 5) != 0
180 }
181 fn switch_on_disabled(&self) -> bool {
183 self.raw() & (1 << 6) != 0
184 }
185 fn warning(&self) -> bool {
187 self.raw() & (1 << 7) != 0
188 }
189 fn remote(&self) -> bool {
191 self.raw() & (1 << 9) != 0
192 }
193 fn target_reached(&self) -> bool {
195 self.raw() & (1 << 10) != 0
196 }
197
198 fn state(&self) -> Cia402State {
205 let w = self.raw();
206 if w & 0x006F == 0x0027 { return Cia402State::OperationEnabled; }
208 if w & 0x006F == 0x0023 { return Cia402State::SwitchedOn; }
209 if w & 0x006F == 0x0021 { return Cia402State::ReadyToSwitchOn; }
210 if w & 0x006F == 0x0007 { return Cia402State::QuickStopActive; }
211 if w & 0x004F == 0x000F { return Cia402State::FaultReactionActive; }
213 if w & 0x004F == 0x0008 { return Cia402State::Fault; }
214 if w & 0x004F == 0x0040 { return Cia402State::SwitchOnDisabled; }
215 if w & 0x004F == 0x0000 { return Cia402State::NotReadyToSwitchOn; }
216 Cia402State::Unknown
217 }
218}
219
220#[derive(Debug, Clone, Copy, PartialEq, Eq)]
222pub enum Cia402State {
223 NotReadyToSwitchOn,
225 SwitchOnDisabled,
227 ReadyToSwitchOn,
229 SwitchedOn,
231 OperationEnabled,
233 QuickStopActive,
235 FaultReactionActive,
237 Fault,
239 Unknown,
241}
242
243impl std::fmt::Display for Cia402State {
244 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
245 match self {
246 Self::NotReadyToSwitchOn => write!(f, "Not Ready to Switch On"),
247 Self::SwitchOnDisabled => write!(f, "Switch On Disabled"),
248 Self::ReadyToSwitchOn => write!(f, "Ready to Switch On"),
249 Self::SwitchedOn => write!(f, "Switched On"),
250 Self::OperationEnabled => write!(f, "Operation Enabled"),
251 Self::QuickStopActive => write!(f, "Quick Stop Active"),
252 Self::FaultReactionActive => write!(f, "Fault Reaction Active"),
253 Self::Fault => write!(f, "Fault"),
254 Self::Unknown => write!(f, "Unknown"),
255 }
256 }
257}
258
259#[derive(Debug, Clone, Copy, PartialEq, Eq)]
265#[repr(i8)]
266pub enum ModesOfOperation {
267 ProfilePosition = 1,
269 ProfileVelocity = 3,
271 Homing = 6,
273 InterpolatedPosition = 7,
275 CyclicSynchronousPosition = 8,
277 CyclicSynchronousVelocity = 9,
279 CyclicSynchronousTorque = 10,
281}
282
283impl ModesOfOperation {
284 pub fn from_i8(v: i8) -> Option<Self> {
286 match v {
287 1 => Some(Self::ProfilePosition),
288 3 => Some(Self::ProfileVelocity),
289 6 => Some(Self::Homing),
290 7 => Some(Self::InterpolatedPosition),
291 8 => Some(Self::CyclicSynchronousPosition),
292 9 => Some(Self::CyclicSynchronousVelocity),
293 10 => Some(Self::CyclicSynchronousTorque),
294 _ => None,
295 }
296 }
297
298 pub fn as_i8(self) -> i8 {
300 self as i8
301 }
302}
303
304impl std::fmt::Display for ModesOfOperation {
305 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
306 match self {
307 Self::ProfilePosition => write!(f, "Profile Position (PP)"),
308 Self::ProfileVelocity => write!(f, "Profile Velocity (PV)"),
309 Self::Homing => write!(f, "Homing"),
310 Self::InterpolatedPosition => write!(f, "Interpolated Position (IP)"),
311 Self::CyclicSynchronousPosition => write!(f, "Cyclic Synchronous Position (CSP)"),
312 Self::CyclicSynchronousVelocity => write!(f, "Cyclic Synchronous Velocity (CSV)"),
313 Self::CyclicSynchronousTorque => write!(f, "Cyclic Synchronous Torque (CST)"),
314 }
315 }
316}
317
318impl Cia402Control for RawControlWord {
320 fn raw(&self) -> u16 {
321 self.0
322 }
323 fn raw_mut(&mut self) -> &mut u16 {
324 &mut self.0
325 }
326}
327impl Cia402Status for RawStatusWord {
328 fn raw(&self) -> u16 {
329 self.0
330 }
331}
332
333pub trait PpControl: Cia402Control {
339 fn set_new_set_point(&mut self, v: bool) {
341 self.set_bit(4, v);
342 }
343 fn set_change_set_immediately(&mut self, v: bool) {
345 self.set_bit(5, v);
346 }
347 fn set_relative(&mut self, v: bool) {
349 self.set_bit(6, v);
350 }
351 fn set_halt(&mut self, v: bool) {
353 self.set_bit(8, v);
354 }
355}
356
357pub trait PpStatus: Cia402Status {
359 fn pp_target_reached(&self) -> bool {
361 self.raw() & (1 << 10) != 0
362 }
363 fn internal_limit(&self) -> bool {
365 self.raw() & (1 << 11) != 0
366 }
367 fn set_point_acknowledge(&self) -> bool {
369 self.raw() & (1 << 12) != 0
370 }
371 fn following_error(&self) -> bool {
373 self.raw() & (1 << 13) != 0
374 }
375}
376
377pub trait PvControl: Cia402Control {
383 fn set_halt(&mut self, v: bool) {
385 self.set_bit(8, v);
386 }
387}
388
389pub trait PvStatus: Cia402Status {
391 fn pv_target_reached(&self) -> bool {
393 self.raw() & (1 << 10) != 0
394 }
395 fn pv_internal_limit(&self) -> bool {
397 self.raw() & (1 << 11) != 0
398 }
399 fn speed_is_zero(&self) -> bool {
401 self.raw() & (1 << 12) != 0
402 }
403 fn max_slippage_error(&self) -> bool {
405 self.raw() & (1 << 13) != 0
406 }
407}
408
409pub trait HomingControl: Cia402Control {
415 fn set_homing_start(&mut self, v: bool) {
417 self.set_bit(4, v);
418 }
419 fn set_halt(&mut self, v: bool) {
421 self.set_bit(8, v);
422 }
423}
424
425pub trait HomingStatus: Cia402Status {
427 fn homing_target_reached(&self) -> bool {
429 self.raw() & (1 << 10) != 0
430 }
431 fn homing_attained(&self) -> bool {
433 self.raw() & (1 << 12) != 0
434 }
435 fn homing_error(&self) -> bool {
437 self.raw() & (1 << 13) != 0
438 }
439}
440
441impl PpControl for RawControlWord {}
443impl PpStatus for RawStatusWord {}
444impl PvControl for RawControlWord {}
445impl PvStatus for RawStatusWord {}
446impl HomingControl for RawControlWord {}
447impl HomingStatus for RawStatusWord {}
448
449#[cfg(test)]
454mod tests {
455 use super::*;
456
457 #[test]
458 fn test_state_decoding() {
459 assert_eq!(RawStatusWord(0x0000).state(), Cia402State::NotReadyToSwitchOn);
461 assert_eq!(RawStatusWord(0x0040).state(), Cia402State::SwitchOnDisabled);
462 assert_eq!(RawStatusWord(0x0021).state(), Cia402State::ReadyToSwitchOn);
463 assert_eq!(RawStatusWord(0x0023).state(), Cia402State::SwitchedOn);
464 assert_eq!(RawStatusWord(0x0027).state(), Cia402State::OperationEnabled);
465 assert_eq!(RawStatusWord(0x0007).state(), Cia402State::QuickStopActive);
466 assert_eq!(RawStatusWord(0x000F).state(), Cia402State::FaultReactionActive);
467 assert_eq!(RawStatusWord(0x0008).state(), Cia402State::Fault);
468 }
469
470 #[test]
471 fn test_state_decoding_bit5_dont_care() {
472 assert_eq!(RawStatusWord(0x0020).state(), Cia402State::NotReadyToSwitchOn);
477 assert_eq!(RawStatusWord(0x0060).state(), Cia402State::SwitchOnDisabled);
479 assert_eq!(RawStatusWord(0x002F).state(), Cia402State::FaultReactionActive);
481 assert_eq!(RawStatusWord(0x0028).state(), Cia402State::Fault);
483 }
484
485 #[test]
486 fn test_state_decoding_ignores_high_bits() {
487 assert_eq!(RawStatusWord(0xFF27).state(), Cia402State::OperationEnabled);
489 assert_eq!(RawStatusWord(0x8040).state(), Cia402State::SwitchOnDisabled);
490 }
491
492 #[test]
493 fn test_cmd_shutdown() {
494 let mut cw = RawControlWord(0xFF00);
495 cw.cmd_shutdown();
496 assert_eq!(cw.0, 0xFF06);
498 }
499
500 #[test]
501 fn test_cmd_enable_operation() {
502 let mut cw = RawControlWord(0x0000);
503 cw.cmd_enable_operation();
504 assert_eq!(cw.0, 0x000F);
505 }
506
507 #[test]
508 fn test_cmd_fault_reset() {
509 let mut cw = RawControlWord(0x0000);
510 cw.cmd_fault_reset();
511 assert!(cw.0 & 0x0080 != 0); }
513
514 #[test]
515 fn test_modes_of_operation_roundtrip() {
516 for mode in [
517 ModesOfOperation::ProfilePosition,
518 ModesOfOperation::ProfileVelocity,
519 ModesOfOperation::Homing,
520 ] {
521 assert_eq!(ModesOfOperation::from_i8(mode.as_i8()), Some(mode));
522 }
523 assert_eq!(ModesOfOperation::from_i8(99), None);
524 }
525}
526
527
528
529#[repr(i32)]
530#[derive(Copy, Clone, Debug, FromRepr)]
531enum ModeOfOperationStates {
532 Reset = 0,
533 WriteModeOp,
534 WaitWriteModeOp,
535 ReadModeOp,
536 WaitReadModeOp
537}
538
539#[derive(Clone, Debug)]
543pub struct FbSetModeOfOperation {
544 state : StateMachine,
546 target_mode : i8,
548 tid : u32,
549 retry_count : u16
550}
551
552impl FbSetModeOfOperation {
553 pub fn new() -> Self {
555 Self {
556 state : StateMachine::new(),
557 target_mode : 0,
558 tid : 0,
559 retry_count : 0
560 }
561 }
562
563 pub fn reset(&mut self) {
564 self.state.error_code = 0;
565 self.state.error_message.clear();
566 self.state.index = ModeOfOperationStates::Reset as i32;
567 }
568
569 pub fn start(&mut self, target_mode : i8) {
570 self.target_mode = target_mode;
571 self.retry_count = 0;
572 self.state.error_code = 0;
573 self.state.error_message.clear();
574 self.state.index = ModeOfOperationStates::WriteModeOp as i32;
575 }
576
577 pub fn is_busy(&self) -> bool {
578 return self.state.index > ModeOfOperationStates::Reset as i32;
579 }
580
581 pub fn is_error(&self) -> bool {
582 return self.state.is_error();
583 }
584
585 pub fn error_code(&self) -> i32 {
586 return self.state.error_code;
587 }
588
589 pub fn error_message(&self) -> String {
590 return self.state.error_message.clone();
591 }
592
593 pub fn tick(&mut self, client: &mut CommandClient, sdo: &mut SdoClient) {
594 match ModeOfOperationStates::from_repr(self.state.index) {
595 Some(ModeOfOperationStates::Reset) => {
596 },
598 Some(ModeOfOperationStates::WriteModeOp) => {
599 self.tid = sdo.write(
602 client, 0x6060, 0, json!(self.target_mode),
603 );
604 self.state.index = ModeOfOperationStates::WaitWriteModeOp as i32;
605 self.state.timeout_preset = Duration::from_secs(7);
606 log::info!("FbSetModeOfOperation: Waiting write complete target_mode {}", self.target_mode);
607 },
608 Some(ModeOfOperationStates::WaitWriteModeOp) => {
609 match sdo.result(client, self.tid, Duration::from_secs(5)) {
611 SdoResult::Ok(_) => {
612 log::info!("FbSetModeOfOperation: write complete.");
613 self.state.index = ModeOfOperationStates::ReadModeOp as i32;
614 self.state.timer_preset = Duration::from_millis(100);
615 }
616 SdoResult::Pending => {
617 if self.state.timed_out() {
618 self.state.error_code = ModeOfOperationStates::WaitWriteModeOp as i32 * 10;
619 self.state.error_message = "Timeout waiting for SDO write to complete".to_string();
620 self.state.index = ModeOfOperationStates::Reset as i32;
621 }
622 }
623 SdoResult::Err(e) => {
624 self.state.error_code = ModeOfOperationStates::WaitWriteModeOp as i32 * 10 + 1;
625 self.state.error_message = format!("SDO write error {}", e);
626 self.state.index = ModeOfOperationStates::Reset as i32;
627 }
628 SdoResult::Timeout => {
629 self.state.error_code = ModeOfOperationStates::WaitWriteModeOp as i32 * 10 + 2;
630 self.state.error_message = "SDO write resulted in timeout".to_string();
631 self.state.index = ModeOfOperationStates::Reset as i32;
632 }
633 }
634 },
635 Some(ModeOfOperationStates::ReadModeOp) => {
636 if self.state.timer_done() {
637 log::info!("FbSetModeOfOperation: Starting read to validate operation mode.");
640 self.tid = sdo.read(
641 client, 0x6061, 0
642 );
643 self.state.index = ModeOfOperationStates::WaitReadModeOp as i32;
644 self.state.timeout_preset = Duration::from_secs(7);
645 }
646 },
647 Some(ModeOfOperationStates::WaitReadModeOp) => {
648 match sdo.result(client, self.tid, Duration::from_secs(5)) {
650 SdoResult::Ok(val) => {
651 log::info!("FbSetModeOfOperation: read complete.");
652 if let Some(val_num) = val["value"].as_i64() {
653 if val_num as i8 == self.target_mode {
654 log::info!("Successfully changed motor mode of operation to {}", self.target_mode);
655 self.state.index = ModeOfOperationStates::Reset as i32;
656 }
657 else {
658 self.retry_count += 1;
659 if self.retry_count > 3 {
660 self.state.error_code = ModeOfOperationStates::WaitReadModeOp as i32 * 10 + 3;
661 self.state.error_message = format!("Drive did not transition to operation mode {} from {}",
662 self.target_mode, val_num as i8
663 );
664 self.state.index = ModeOfOperationStates::Reset as i32;
665 }
666 else {
667 log::info!("FbSetModeOfOperation: Operation mode does not match. Retrying...");
668 self.state.timer_preset = Duration::from_millis(50);
669 self.state.index = ModeOfOperationStates::ReadModeOp as i32;
670 }
671 }
672 }
673 else {
674 self.state.error_code = ModeOfOperationStates::WaitReadModeOp as i32 * 10 + 4;
675 self.state.error_message = format!("Invalid value received from drive SDO read 0x6061:01 [{}]", val );
676 self.state.index = ModeOfOperationStates::Reset as i32;
677 }
678 }
679 SdoResult::Pending => {
680 if self.state.timed_out() {
681 self.state.error_code = ModeOfOperationStates::WaitReadModeOp as i32 * 10;
682 self.state.error_message = "Timeout waiting for SDO write to complete".to_string();
683 self.state.index = ModeOfOperationStates::Reset as i32;
684 }
685 }
686 SdoResult::Err(e) => {
687 self.state.error_code = ModeOfOperationStates::WaitReadModeOp as i32 * 10 + 1;
688 self.state.error_message = format!("SDO write error {}", e);
689 self.state.index = ModeOfOperationStates::Reset as i32;
690 }
691 SdoResult::Timeout => {
692 self.state.error_code = ModeOfOperationStates::WaitReadModeOp as i32 * 10 + 2;
693 self.state.error_message = "SDO write resulted in timeout".to_string();
694 self.state.index = ModeOfOperationStates::Reset as i32;
695 }
696 }
697 },
698 None => {
699 self.state.index = ModeOfOperationStates::Reset as i32;
700 }
701 }
702
703
704 self.state.call();
706 }
707
708
709}
710
711