use crate::motion::{
AxisView,
cia402::{
Cia402Control, Cia402Status, Cia402State, ModesOfOperation,
PpControl, PpStatus,
HomingControl, HomingStatus,
},
};
use crate::ethercat::teknic::types::{
TeknicPpControlWord, TeknicPpStatusWord,
TeknicPpControl, TeknicPpStatus,
TeknicHomingControlWord, TeknicHomingStatusWord,
};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum HomingProgress {
Idle,
InProgress,
Attained,
Complete,
Error,
}
impl std::fmt::Display for HomingProgress {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Idle => write!(f, "Idle"),
Self::InProgress => write!(f, "In Progress"),
Self::Attained => write!(f, "Attained"),
Self::Complete => write!(f, "Complete"),
Self::Error => write!(f, "Error"),
}
}
}
pub struct TeknicPpView<'a> {
pub control_word: &'a mut u16,
pub target_position: &'a mut i32,
pub profile_velocity: &'a mut u32,
pub profile_acceleration: &'a mut u32,
pub profile_deceleration: &'a mut u32,
pub modes_of_operation: &'a mut i8,
pub status_word: &'a u16,
pub position_actual: &'a i32,
pub velocity_actual: &'a i32,
pub torque_actual: &'a i16,
pub modes_of_operation_display: &'a i8,
}
macro_rules! impl_teknic_pp_methods {
($T:ty, deref: $($d:tt)*) => {
impl $T {
pub fn pp_control(&self) -> TeknicPpControlWord {
TeknicPpControlWord($($d)* self.control_word)
}
pub fn pp_status(&self) -> TeknicPpStatusWord {
TeknicPpStatusWord($($d)* self.status_word)
}
pub fn set_pp_control(&mut self, cw: TeknicPpControlWord) {
$($d)* self.control_word = cw.raw();
}
pub fn state(&self) -> Cia402State {
self.pp_status().state()
}
pub fn cmd_shutdown(&mut self) {
let mut cw = self.pp_control();
cw.cmd_shutdown();
self.set_pp_control(cw);
}
pub fn cmd_switch_on(&mut self) {
let mut cw = self.pp_control();
cw.cmd_switch_on();
self.set_pp_control(cw);
}
pub fn cmd_enable_operation(&mut self) {
let mut cw = self.pp_control();
cw.cmd_enable_operation();
self.set_pp_control(cw);
}
pub fn cmd_disable_operation(&mut self) {
let mut cw = self.pp_control();
cw.cmd_disable_operation();
self.set_pp_control(cw);
}
pub fn cmd_disable_voltage(&mut self) {
let mut cw = self.pp_control();
cw.cmd_disable_voltage();
self.set_pp_control(cw);
}
pub fn cmd_quick_stop(&mut self) {
let mut cw = self.pp_control();
cw.cmd_quick_stop();
self.set_pp_control(cw);
}
pub fn cmd_fault_reset(&mut self) {
let mut cw = self.pp_control();
cw.cmd_fault_reset();
self.set_pp_control(cw);
}
pub fn cmd_clear_fault_reset(&mut self) {
let mut cw = self.pp_control();
cw.cmd_clear_fault_reset();
self.set_pp_control(cw);
}
pub fn ensure_pp_mode(&mut self) {
$($d)* self.modes_of_operation = ModesOfOperation::ProfilePosition.as_i8();
}
pub fn set_target(&mut self, position: i32, velocity: u32, accel: u32, decel: u32) {
$($d)* self.target_position = position;
$($d)* self.profile_velocity = velocity;
$($d)* self.profile_acceleration = accel;
$($d)* self.profile_deceleration = decel;
}
pub fn trigger_move(&mut self) {
let mut cw = self.pp_control();
cw.set_new_set_point(true);
self.set_pp_control(cw);
}
pub fn ack_set_point(&mut self) {
let mut cw = self.pp_control();
cw.set_new_set_point(false);
self.set_pp_control(cw);
}
pub fn set_halt(&mut self, v: bool) {
let mut cw = self.pp_control();
cw.set_halt(v);
self.set_pp_control(cw);
}
pub fn set_relative(&mut self, v: bool) {
let mut cw = self.pp_control();
cw.set_relative(v);
self.set_pp_control(cw);
}
pub fn set_relative_to_actual(&mut self, v: bool) {
let mut cw = self.pp_control();
cw.set_relative_to_actual(v);
self.set_pp_control(cw);
}
pub fn target_reached(&self) -> bool {
self.pp_status().pp_target_reached()
}
pub fn set_point_acknowledged(&self) -> bool {
self.pp_status().set_point_acknowledge()
}
pub fn in_range(&self) -> bool {
self.pp_status().in_range()
}
pub fn has_homed(&self) -> bool {
self.pp_status().has_homed()
}
pub fn at_velocity(&self) -> bool {
self.pp_status().at_velocity()
}
pub fn following_error(&self) -> bool {
self.pp_status().following_error()
}
pub fn internal_limit(&self) -> bool {
self.pp_status().internal_limit()
}
pub fn warning(&self) -> bool {
self.pp_status().warning()
}
pub fn is_faulted(&self) -> bool {
matches!(self.state(), Cia402State::Fault | Cia402State::FaultReactionActive)
}
pub fn ensure_homing_mode(&mut self) {
$($d)* self.modes_of_operation = ModesOfOperation::Homing.as_i8();
}
pub fn trigger_homing(&mut self) {
let mut cw = TeknicHomingControlWord($($d)* self.control_word);
cw.set_homing_start(true);
$($d)* self.control_word = cw.raw();
}
pub fn clear_homing_start(&mut self) {
let mut cw = TeknicHomingControlWord($($d)* self.control_word);
cw.set_homing_start(false);
$($d)* self.control_word = cw.raw();
}
pub fn homing_progress(&self) -> HomingProgress {
let sw = TeknicHomingStatusWord($($d)* self.status_word);
let attained = sw.homing_attained();
let reached = sw.homing_target_reached();
let error = sw.homing_error();
if error {
HomingProgress::Error
} else if attained && reached {
HomingProgress::Complete
} else if attained {
HomingProgress::Attained
} else if reached {
HomingProgress::Idle
} else {
HomingProgress::InProgress
}
}
pub fn position(&self) -> i32 {
$($d)* self.position_actual
}
pub fn velocity(&self) -> i32 {
$($d)* self.velocity_actual
}
pub fn torque(&self) -> i16 {
$($d)* self.torque_actual
}
pub fn current_mode(&self) -> Option<ModesOfOperation> {
ModesOfOperation::from_i8($($d)* self.modes_of_operation_display)
}
}
};
}
impl_teknic_pp_methods!(TeknicPpView<'_>, deref: *);
impl AxisView for TeknicPpView<'_> {
fn control_word(&self) -> u16 { *self.control_word }
fn set_control_word(&mut self, word: u16) { *self.control_word = word; }
fn status_word(&self) -> u16 { *self.status_word }
fn set_target_position(&mut self, pos: i32) { *self.target_position = pos; }
fn set_profile_velocity(&mut self, vel: u32) { *self.profile_velocity = vel; }
fn set_profile_acceleration(&mut self, accel: u32) { *self.profile_acceleration = accel; }
fn set_profile_deceleration(&mut self, decel: u32) { *self.profile_deceleration = decel; }
fn set_modes_of_operation(&mut self, mode: i8) { *self.modes_of_operation = mode; }
fn modes_of_operation_display(&self) -> i8 { *self.modes_of_operation_display }
fn position_actual(&self) -> i32 { *self.position_actual }
fn velocity_actual(&self) -> i32 { *self.velocity_actual }
}
#[macro_export]
macro_rules! teknic_pp_view {
($gm:expr, $prefix:ident) => {
paste::paste! {
$crate::teknic::view::TeknicPpView {
control_word: &mut $gm.[<$prefix _control_word>],
target_position: &mut $gm.[<$prefix _target_position>],
profile_velocity: &mut $gm.[<$prefix _profile_velocity>],
profile_acceleration: &mut $gm.[<$prefix _profile_acceleration>],
profile_deceleration: &mut $gm.[<$prefix _profile_deceleration>],
modes_of_operation: &mut $gm.[<$prefix _modes_of_operation>],
status_word: & $gm.[<$prefix _status_word>],
position_actual: & $gm.[<$prefix _position_actual>],
velocity_actual: & $gm.[<$prefix _velocity_actual>],
torque_actual: & $gm.[<$prefix _torque_actual>],
modes_of_operation_display: & $gm.[<$prefix _modes_of_operation_display>],
}
}
};
}
#[cfg(test)]
mod tests {
use super::*;
#[derive(Default)]
struct TestPdo {
control_word: u16,
target_position: i32,
profile_velocity: u32,
profile_acceleration: u32,
profile_deceleration: u32,
modes_of_operation: i8,
status_word: u16,
position_actual: i32,
velocity_actual: i32,
torque_actual: i16,
modes_of_operation_display: i8,
}
impl TestPdo {
fn view(&mut self) -> TeknicPpView<'_> {
TeknicPpView {
control_word: &mut self.control_word,
target_position: &mut self.target_position,
profile_velocity: &mut self.profile_velocity,
profile_acceleration: &mut self.profile_acceleration,
profile_deceleration: &mut self.profile_deceleration,
modes_of_operation: &mut self.modes_of_operation,
status_word: &self.status_word,
position_actual: &self.position_actual,
velocity_actual: &self.velocity_actual,
torque_actual: &self.torque_actual,
modes_of_operation_display: &self.modes_of_operation_display,
}
}
}
#[test]
fn test_view_reads_state() {
let mut pdo = TestPdo { status_word: 0x0040, ..Default::default() };
let view = pdo.view();
assert_eq!(view.state(), Cia402State::SwitchOnDisabled);
}
#[test]
fn test_view_cmd_shutdown() {
let mut pdo = TestPdo { status_word: 0x0040, ..Default::default() };
let mut view = pdo.view();
view.cmd_shutdown();
assert_eq!(*view.control_word & 0x008F, 0x0006);
}
#[test]
fn test_view_cmd_enable_operation() {
let mut pdo = TestPdo::default();
let mut view = pdo.view();
view.cmd_enable_operation();
assert_eq!(*view.control_word & 0x008F, 0x000F);
}
#[test]
fn test_view_set_target_and_trigger() {
let mut pdo = TestPdo::default();
let mut view = pdo.view();
view.cmd_enable_operation();
view.set_target(50_000, 10_000, 2_000, 2_000);
view.trigger_move();
assert_eq!(*view.target_position, 50_000);
assert_eq!(*view.profile_velocity, 10_000);
assert_eq!(*view.profile_acceleration, 2_000);
assert_eq!(*view.profile_deceleration, 2_000);
assert!(*view.control_word & (1 << 4) != 0);
}
#[test]
fn test_view_ack_set_point() {
let mut pdo = TestPdo::default();
let mut view = pdo.view();
view.cmd_enable_operation();
view.trigger_move();
assert!(*view.control_word & (1 << 4) != 0);
view.ack_set_point();
assert!(*view.control_word & (1 << 4) == 0);
assert_eq!(*view.control_word & 0x000F, 0x000F);
}
#[test]
fn test_view_absolute_move() {
let mut pdo = TestPdo::default();
let mut view = pdo.view();
view.cmd_enable_operation();
view.set_relative(false);
view.set_target(100_000, 50_000, 10_000, 10_000);
view.trigger_move();
assert_eq!(*view.control_word & (1 << 6), 0);
assert!(*view.control_word & (1 << 4) != 0);
}
#[test]
fn test_view_relative_move() {
let mut pdo = TestPdo::default();
let mut view = pdo.view();
view.cmd_enable_operation();
view.set_relative(true);
view.set_target(5_000, 10_000, 2_000, 2_000);
view.trigger_move();
assert!(*view.control_word & (1 << 6) != 0);
}
#[test]
fn test_view_relative_to_actual() {
let mut pdo = TestPdo::default();
let mut view = pdo.view();
view.cmd_enable_operation();
view.set_relative(true);
view.set_relative_to_actual(true);
view.set_target(1_000, 5_000, 1_000, 1_000);
view.trigger_move();
assert!(*view.control_word & (1 << 6) != 0);
assert!(*view.control_word & (1 << 13) != 0);
}
#[test]
fn test_view_feedback() {
let mut pdo = TestPdo {
position_actual: 12345,
velocity_actual: -500,
torque_actual: 100,
modes_of_operation_display: 1,
..Default::default()
};
let view = pdo.view();
assert_eq!(view.position(), 12345);
assert_eq!(view.velocity(), -500);
assert_eq!(view.torque(), 100);
assert_eq!(view.current_mode(), Some(ModesOfOperation::ProfilePosition));
}
#[test]
fn test_view_teknic_status_bits() {
let mut pdo = TestPdo {
status_word: 0x8127,
..Default::default()
};
let view = pdo.view();
assert_eq!(view.state(), Cia402State::OperationEnabled);
assert!(view.has_homed());
assert!(view.in_range());
assert!(!view.at_velocity());
}
#[test]
fn test_view_ensure_pp_mode() {
let mut pdo = TestPdo::default();
let mut view = pdo.view();
view.ensure_pp_mode();
assert_eq!(*view.modes_of_operation, 1);
}
#[test]
fn test_view_following_error() {
let mut pdo = TestPdo {
status_word: 0x2027,
..Default::default()
};
let view = pdo.view();
assert_eq!(view.state(), Cia402State::OperationEnabled);
assert!(view.following_error());
assert!(!view.is_faulted());
}
#[test]
fn test_view_internal_limit() {
let mut pdo = TestPdo { status_word: 0x0827, ..Default::default() };
let view = pdo.view();
assert!(view.internal_limit());
}
#[test]
fn test_view_warning() {
let mut pdo = TestPdo { status_word: 0x00A7, ..Default::default() };
let view = pdo.view();
assert_eq!(view.state(), Cia402State::OperationEnabled);
assert!(view.warning());
}
#[test]
fn test_view_is_faulted() {
let mut pdo = TestPdo::default();
pdo.status_word = 0x0008; let view = pdo.view();
assert!(view.is_faulted());
assert_eq!(view.state(), Cia402State::Fault);
pdo.status_word = 0x000F; let view = pdo.view();
assert!(view.is_faulted());
pdo.status_word = 0x0027; let view = pdo.view();
assert!(!view.is_faulted());
}
#[test]
fn test_view_ensure_homing_mode() {
let mut pdo = TestPdo::default();
let mut view = pdo.view();
view.ensure_homing_mode();
assert_eq!(*view.modes_of_operation, 6);
}
#[test]
fn test_view_trigger_and_clear_homing() {
let mut pdo = TestPdo::default();
let mut view = pdo.view();
view.cmd_enable_operation();
assert_eq!(*view.control_word & (1 << 4), 0);
view.trigger_homing();
assert!(*view.control_word & (1 << 4) != 0);
assert_eq!(*view.control_word & 0x000F, 0x000F);
view.clear_homing_start();
assert_eq!(*view.control_word & (1 << 4), 0);
assert_eq!(*view.control_word & 0x000F, 0x000F);
}
#[test]
fn test_homing_progress_idle() {
let mut pdo = TestPdo {
status_word: 0x0427, ..Default::default()
};
let view = pdo.view();
assert_eq!(view.homing_progress(), HomingProgress::Idle);
}
#[test]
fn test_homing_progress_in_progress() {
let mut pdo = TestPdo {
status_word: 0x0027, ..Default::default()
};
let view = pdo.view();
assert_eq!(view.homing_progress(), HomingProgress::InProgress);
}
#[test]
fn test_homing_progress_attained() {
let mut pdo = TestPdo {
status_word: 0x1027,
..Default::default()
};
let view = pdo.view();
assert_eq!(view.homing_progress(), HomingProgress::Attained);
}
#[test]
fn test_homing_progress_complete() {
let mut pdo = TestPdo {
status_word: 0x1427,
..Default::default()
};
let view = pdo.view();
assert_eq!(view.homing_progress(), HomingProgress::Complete);
}
#[test]
fn test_homing_progress_error() {
let mut pdo = TestPdo {
status_word: 0x2027,
..Default::default()
};
let view = pdo.view();
assert_eq!(view.homing_progress(), HomingProgress::Error);
pdo.status_word = 0x3427;
let view = pdo.view();
assert_eq!(view.homing_progress(), HomingProgress::Error);
}
#[test]
fn test_homing_has_homed_persists_across_modes() {
let mut pdo = TestPdo {
status_word: 0x0127,
modes_of_operation_display: 1, ..Default::default()
};
let view = pdo.view();
assert!(view.has_homed());
assert_eq!(view.current_mode(), Some(ModesOfOperation::ProfilePosition));
}
#[test]
fn test_axis_view_impl() {
let mut pdo = TestPdo {
status_word: 0x0027,
position_actual: 5000,
velocity_actual: 1000,
modes_of_operation_display: 1,
..Default::default()
};
let mut view = pdo.view();
assert_eq!(AxisView::status_word(&view), 0x0027);
assert_eq!(AxisView::position_actual(&view), 5000);
assert_eq!(AxisView::velocity_actual(&view), 1000);
assert_eq!(AxisView::modes_of_operation_display(&view), 1);
AxisView::set_control_word(&mut view, 0x000F);
assert_eq!(*view.control_word, 0x000F);
AxisView::set_target_position(&mut view, 10_000);
assert_eq!(*view.target_position, 10_000);
}
}