use strum_macros::FromRepr;
use std::time::{Duration, Instant};
use serde_json::json;
use crate::fb::StateMachine;
use super::axis_view::AxisView;
use crate::command_client::CommandClient;
use crate::ethercat::{SdoClient, SdoResult};
#[derive(Clone, Copy, Debug, Default)]
pub struct RawControlWord(pub u16);
#[derive(Clone, Copy, Debug, Default)]
pub struct RawStatusWord(pub u16);
pub trait Cia402Control {
fn raw(&self) -> u16;
fn raw_mut(&mut self) -> &mut u16;
fn set_switch_on(&mut self, v: bool) {
self.set_bit(0, v);
}
fn set_enable_voltage(&mut self, v: bool) {
self.set_bit(1, v);
}
fn set_quick_stop(&mut self, v: bool) {
self.set_bit(2, v);
}
fn set_enable_operation(&mut self, v: bool) {
self.set_bit(3, v);
}
fn set_fault_reset(&mut self, v: bool) {
self.set_bit(7, v);
}
fn cmd_shutdown(&mut self) {
let w = self.raw_mut();
*w = (*w & !0x008F) | 0x0006; }
fn cmd_switch_on(&mut self) {
let w = self.raw_mut();
*w = (*w & !0x008F) | 0x0007; }
fn cmd_enable_operation(&mut self) {
let w = self.raw_mut();
*w = (*w & !0x008F) | 0x000F; }
fn cmd_disable_operation(&mut self) {
let w = self.raw_mut();
*w = (*w & !0x008F) | 0x0007; }
fn cmd_disable_voltage(&mut self) {
let w = self.raw_mut();
*w &= !0x0082; }
fn cmd_quick_stop(&mut self) {
let w = self.raw_mut();
*w = (*w & !0x0086) | 0x0002; }
fn cmd_fault_reset(&mut self) {
let w = self.raw_mut();
*w |= 0x0080; }
fn cmd_clear_fault_reset(&mut self) {
self.set_bit(7, false);
}
fn set_bit(&mut self, bit: u8, v: bool) {
let w = self.raw_mut();
if v {
*w |= 1 << bit;
} else {
*w &= !(1 << bit);
}
}
}
pub trait Cia402Status {
fn raw(&self) -> u16;
fn ready_to_switch_on(&self) -> bool {
self.raw() & (1 << 0) != 0
}
fn switched_on(&self) -> bool {
self.raw() & (1 << 1) != 0
}
fn operation_enabled(&self) -> bool {
self.raw() & (1 << 2) != 0
}
fn fault(&self) -> bool {
self.raw() & (1 << 3) != 0
}
fn voltage_enabled(&self) -> bool {
self.raw() & (1 << 4) != 0
}
fn quick_stop_active(&self) -> bool {
self.raw() & (1 << 5) != 0
}
fn switch_on_disabled(&self) -> bool {
self.raw() & (1 << 6) != 0
}
fn warning(&self) -> bool {
self.raw() & (1 << 7) != 0
}
fn remote(&self) -> bool {
self.raw() & (1 << 9) != 0
}
fn target_reached(&self) -> bool {
self.raw() & (1 << 10) != 0
}
fn state(&self) -> Cia402State {
let w = self.raw();
if w & 0x006F == 0x0027 { return Cia402State::OperationEnabled; }
if w & 0x006F == 0x0023 { return Cia402State::SwitchedOn; }
if w & 0x006F == 0x0021 { return Cia402State::ReadyToSwitchOn; }
if w & 0x006F == 0x0007 { return Cia402State::QuickStopActive; }
if w & 0x004F == 0x000F { return Cia402State::FaultReactionActive; }
if w & 0x004F == 0x0008 { return Cia402State::Fault; }
if w & 0x004F == 0x0040 { return Cia402State::SwitchOnDisabled; }
if w & 0x004F == 0x0000 { return Cia402State::NotReadyToSwitchOn; }
Cia402State::Unknown
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Cia402State {
NotReadyToSwitchOn,
SwitchOnDisabled,
ReadyToSwitchOn,
SwitchedOn,
OperationEnabled,
QuickStopActive,
FaultReactionActive,
Fault,
Unknown,
}
impl std::fmt::Display for Cia402State {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::NotReadyToSwitchOn => write!(f, "Not Ready to Switch On"),
Self::SwitchOnDisabled => write!(f, "Switch On Disabled"),
Self::ReadyToSwitchOn => write!(f, "Ready to Switch On"),
Self::SwitchedOn => write!(f, "Switched On"),
Self::OperationEnabled => write!(f, "Operation Enabled"),
Self::QuickStopActive => write!(f, "Quick Stop Active"),
Self::FaultReactionActive => write!(f, "Fault Reaction Active"),
Self::Fault => write!(f, "Fault"),
Self::Unknown => write!(f, "Unknown"),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(i8)]
pub enum ModesOfOperation {
ProfilePosition = 1,
ProfileVelocity = 3,
Homing = 6,
InterpolatedPosition = 7,
CyclicSynchronousPosition = 8,
CyclicSynchronousVelocity = 9,
CyclicSynchronousTorque = 10,
}
impl ModesOfOperation {
pub fn from_i8(v: i8) -> Option<Self> {
match v {
1 => Some(Self::ProfilePosition),
3 => Some(Self::ProfileVelocity),
6 => Some(Self::Homing),
7 => Some(Self::InterpolatedPosition),
8 => Some(Self::CyclicSynchronousPosition),
9 => Some(Self::CyclicSynchronousVelocity),
10 => Some(Self::CyclicSynchronousTorque),
_ => None,
}
}
pub fn as_i8(self) -> i8 {
self as i8
}
}
impl std::fmt::Display for ModesOfOperation {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::ProfilePosition => write!(f, "Profile Position (PP)"),
Self::ProfileVelocity => write!(f, "Profile Velocity (PV)"),
Self::Homing => write!(f, "Homing"),
Self::InterpolatedPosition => write!(f, "Interpolated Position (IP)"),
Self::CyclicSynchronousPosition => write!(f, "Cyclic Synchronous Position (CSP)"),
Self::CyclicSynchronousVelocity => write!(f, "Cyclic Synchronous Velocity (CSV)"),
Self::CyclicSynchronousTorque => write!(f, "Cyclic Synchronous Torque (CST)"),
}
}
}
impl Cia402Control for RawControlWord {
fn raw(&self) -> u16 {
self.0
}
fn raw_mut(&mut self) -> &mut u16 {
&mut self.0
}
}
impl Cia402Status for RawStatusWord {
fn raw(&self) -> u16 {
self.0
}
}
pub trait PpControl: Cia402Control {
fn set_new_set_point(&mut self, v: bool) {
self.set_bit(4, v);
}
fn set_change_set_immediately(&mut self, v: bool) {
self.set_bit(5, v);
}
fn set_relative(&mut self, v: bool) {
self.set_bit(6, v);
}
fn set_halt(&mut self, v: bool) {
self.set_bit(8, v);
}
}
pub trait PpStatus: Cia402Status {
fn pp_target_reached(&self) -> bool {
self.raw() & (1 << 10) != 0
}
fn internal_limit(&self) -> bool {
self.raw() & (1 << 11) != 0
}
fn set_point_acknowledge(&self) -> bool {
self.raw() & (1 << 12) != 0
}
fn following_error(&self) -> bool {
self.raw() & (1 << 13) != 0
}
}
pub trait PvControl: Cia402Control {
fn set_halt(&mut self, v: bool) {
self.set_bit(8, v);
}
}
pub trait PvStatus: Cia402Status {
fn pv_target_reached(&self) -> bool {
self.raw() & (1 << 10) != 0
}
fn pv_internal_limit(&self) -> bool {
self.raw() & (1 << 11) != 0
}
fn speed_is_zero(&self) -> bool {
self.raw() & (1 << 12) != 0
}
fn max_slippage_error(&self) -> bool {
self.raw() & (1 << 13) != 0
}
}
pub trait HomingControl: Cia402Control {
fn set_homing_start(&mut self, v: bool) {
self.set_bit(4, v);
}
fn set_halt(&mut self, v: bool) {
self.set_bit(8, v);
}
}
pub trait HomingStatus: Cia402Status {
fn homing_target_reached(&self) -> bool {
self.raw() & (1 << 10) != 0
}
fn homing_attained(&self) -> bool {
self.raw() & (1 << 12) != 0
}
fn homing_error(&self) -> bool {
self.raw() & (1 << 13) != 0
}
}
impl PpControl for RawControlWord {}
impl PpStatus for RawStatusWord {}
impl PvControl for RawControlWord {}
impl PvStatus for RawStatusWord {}
impl HomingControl for RawControlWord {}
impl HomingStatus for RawStatusWord {}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_state_decoding() {
assert_eq!(RawStatusWord(0x0000).state(), Cia402State::NotReadyToSwitchOn);
assert_eq!(RawStatusWord(0x0040).state(), Cia402State::SwitchOnDisabled);
assert_eq!(RawStatusWord(0x0021).state(), Cia402State::ReadyToSwitchOn);
assert_eq!(RawStatusWord(0x0023).state(), Cia402State::SwitchedOn);
assert_eq!(RawStatusWord(0x0027).state(), Cia402State::OperationEnabled);
assert_eq!(RawStatusWord(0x0007).state(), Cia402State::QuickStopActive);
assert_eq!(RawStatusWord(0x000F).state(), Cia402State::FaultReactionActive);
assert_eq!(RawStatusWord(0x0008).state(), Cia402State::Fault);
}
#[test]
fn test_state_decoding_bit5_dont_care() {
assert_eq!(RawStatusWord(0x0020).state(), Cia402State::NotReadyToSwitchOn);
assert_eq!(RawStatusWord(0x0060).state(), Cia402State::SwitchOnDisabled);
assert_eq!(RawStatusWord(0x002F).state(), Cia402State::FaultReactionActive);
assert_eq!(RawStatusWord(0x0028).state(), Cia402State::Fault);
}
#[test]
fn test_state_decoding_ignores_high_bits() {
assert_eq!(RawStatusWord(0xFF27).state(), Cia402State::OperationEnabled);
assert_eq!(RawStatusWord(0x8040).state(), Cia402State::SwitchOnDisabled);
}
#[test]
fn test_cmd_shutdown() {
let mut cw = RawControlWord(0xFF00);
cw.cmd_shutdown();
assert_eq!(cw.0, 0xFF06);
}
#[test]
fn test_cmd_enable_operation() {
let mut cw = RawControlWord(0x0000);
cw.cmd_enable_operation();
assert_eq!(cw.0, 0x000F);
}
#[test]
fn test_cmd_fault_reset() {
let mut cw = RawControlWord(0x0000);
cw.cmd_fault_reset();
assert!(cw.0 & 0x0080 != 0); }
#[test]
fn test_modes_of_operation_roundtrip() {
for mode in [
ModesOfOperation::ProfilePosition,
ModesOfOperation::ProfileVelocity,
ModesOfOperation::Homing,
] {
assert_eq!(ModesOfOperation::from_i8(mode.as_i8()), Some(mode));
}
assert_eq!(ModesOfOperation::from_i8(99), None);
}
}
#[repr(i32)]
#[derive(Copy, Clone, Debug, FromRepr)]
enum ModeOfOperationStates {
Reset = 0,
WriteModeOp,
WaitWriteModeOp,
ReadModeOp,
WaitReadModeOp
}
#[derive(Clone, Debug)]
pub struct FbSetModeOfOperation {
state : StateMachine,
target_mode : i8,
tid : u32,
retry_count : u16
}
impl FbSetModeOfOperation {
pub fn new() -> Self {
Self {
state : StateMachine::new(),
target_mode : 0,
tid : 0,
retry_count : 0
}
}
pub fn reset(&mut self) {
self.state.error_code = 0;
self.state.error_message.clear();
self.state.index = ModeOfOperationStates::Reset as i32;
}
pub fn start(&mut self, target_mode : i8) {
self.target_mode = target_mode;
self.retry_count = 0;
self.state.error_code = 0;
self.state.error_message.clear();
self.state.index = ModeOfOperationStates::WriteModeOp as i32;
}
pub fn is_busy(&self) -> bool {
return self.state.index > ModeOfOperationStates::Reset as i32;
}
pub fn is_error(&self) -> bool {
return self.state.is_error();
}
pub fn error_code(&self) -> i32 {
return self.state.error_code;
}
pub fn error_message(&self) -> String {
return self.state.error_message.clone();
}
pub fn tick(&mut self, client: &mut CommandClient, sdo: &mut SdoClient) {
match ModeOfOperationStates::from_repr(self.state.index) {
Some(ModeOfOperationStates::Reset) => {
},
Some(ModeOfOperationStates::WriteModeOp) => {
self.tid = sdo.write(
client, 0x6060, 0, json!(self.target_mode),
);
self.state.index = ModeOfOperationStates::WaitWriteModeOp as i32;
self.state.timeout_preset = Duration::from_secs(7);
log::info!("FbSetModeOfOperation: Waiting write complete target_mode {}", self.target_mode);
},
Some(ModeOfOperationStates::WaitWriteModeOp) => {
match sdo.result(client, self.tid, Duration::from_secs(5)) {
SdoResult::Ok(_) => {
log::info!("FbSetModeOfOperation: write complete.");
self.state.index = ModeOfOperationStates::ReadModeOp as i32;
self.state.timer_preset = Duration::from_millis(100);
}
SdoResult::Pending => {
if self.state.timed_out() {
self.state.error_code = ModeOfOperationStates::WaitWriteModeOp as i32 * 10;
self.state.error_message = "Timeout waiting for SDO write to complete".to_string();
self.state.index = ModeOfOperationStates::Reset as i32;
}
}
SdoResult::Err(e) => {
self.state.error_code = ModeOfOperationStates::WaitWriteModeOp as i32 * 10 + 1;
self.state.error_message = format!("SDO write error {}", e);
self.state.index = ModeOfOperationStates::Reset as i32;
}
SdoResult::Timeout => {
self.state.error_code = ModeOfOperationStates::WaitWriteModeOp as i32 * 10 + 2;
self.state.error_message = "SDO write resulted in timeout".to_string();
self.state.index = ModeOfOperationStates::Reset as i32;
}
}
},
Some(ModeOfOperationStates::ReadModeOp) => {
if self.state.timer_done() {
log::info!("FbSetModeOfOperation: Starting read to validate operation mode.");
self.tid = sdo.read(
client, 0x6061, 0
);
self.state.index = ModeOfOperationStates::WaitReadModeOp as i32;
self.state.timeout_preset = Duration::from_secs(7);
}
},
Some(ModeOfOperationStates::WaitReadModeOp) => {
match sdo.result(client, self.tid, Duration::from_secs(5)) {
SdoResult::Ok(val) => {
log::info!("FbSetModeOfOperation: read complete.");
if let Some(val_num) = val["value"].as_i64() {
if val_num as i8 == self.target_mode {
log::info!("Successfully changed motor mode of operation to {}", self.target_mode);
self.state.index = ModeOfOperationStates::Reset as i32;
}
else {
self.retry_count += 1;
if self.retry_count > 3 {
self.state.error_code = ModeOfOperationStates::WaitReadModeOp as i32 * 10 + 3;
self.state.error_message = format!("Drive did not transition to operation mode {} from {}",
self.target_mode, val_num as i8
);
self.state.index = ModeOfOperationStates::Reset as i32;
}
else {
log::info!("FbSetModeOfOperation: Operation mode does not match. Retrying...");
self.state.timer_preset = Duration::from_millis(50);
self.state.index = ModeOfOperationStates::ReadModeOp as i32;
}
}
}
else {
self.state.error_code = ModeOfOperationStates::WaitReadModeOp as i32 * 10 + 4;
self.state.error_message = format!("Invalid value received from drive SDO read 0x6061:01 [{}]", val );
self.state.index = ModeOfOperationStates::Reset as i32;
}
}
SdoResult::Pending => {
if self.state.timed_out() {
self.state.error_code = ModeOfOperationStates::WaitReadModeOp as i32 * 10;
self.state.error_message = "Timeout waiting for SDO write to complete".to_string();
self.state.index = ModeOfOperationStates::Reset as i32;
}
}
SdoResult::Err(e) => {
self.state.error_code = ModeOfOperationStates::WaitReadModeOp as i32 * 10 + 1;
self.state.error_message = format!("SDO write error {}", e);
self.state.index = ModeOfOperationStates::Reset as i32;
}
SdoResult::Timeout => {
self.state.error_code = ModeOfOperationStates::WaitReadModeOp as i32 * 10 + 2;
self.state.error_message = "SDO write resulted in timeout".to_string();
self.state.index = ModeOfOperationStates::Reset as i32;
}
}
},
None => {
self.state.index = ModeOfOperationStates::Reset as i32;
}
}
self.state.call();
}
}