use super::axis_view::AxisView;
use super::cia402::Cia402State;
fn status_base(state: Cia402State) -> u16 {
match state {
Cia402State::NotReadyToSwitchOn => 0x0000,
Cia402State::SwitchOnDisabled => 0x0040,
Cia402State::ReadyToSwitchOn => 0x0021,
Cia402State::SwitchedOn => 0x0023,
Cia402State::OperationEnabled => 0x0027,
Cia402State::QuickStopActive => 0x0007,
Cia402State::FaultReactionActive => 0x000F,
Cia402State::Fault => 0x0008,
Cia402State::Unknown => 0x0000,
}
}
#[derive(Debug, Clone)]
pub struct SimDrive {
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,
modes_display: i8,
state: Cia402State,
active_target: i32,
moving: bool,
setpoint_ack: bool,
prev_new_setpoint: bool,
halt_blocks_setpoint_ack: bool,
}
impl Default for SimDrive {
fn default() -> Self {
Self {
control_word: 0,
target_position: 0,
profile_velocity: 0,
profile_acceleration: 0,
profile_deceleration: 0,
modes_of_operation: 0,
status_word: status_base(Cia402State::SwitchOnDisabled) | (1 << 9),
position_actual: 0,
velocity_actual: 0,
modes_display: 0,
state: Cia402State::SwitchOnDisabled,
active_target: 0,
moving: false,
setpoint_ack: false,
prev_new_setpoint: false,
halt_blocks_setpoint_ack: false,
}
}
}
impl SimDrive {
pub fn new() -> Self {
Self::default()
}
pub fn set_halt_blocks_setpoint_ack(&mut self, v: bool) {
self.halt_blocks_setpoint_ack = v;
}
pub fn step(&mut self, dt: f64) {
self.step_state_machine();
let cw = self.control_word;
let bit = |n: u8| cw & (1 << n) != 0;
let new_setpoint = bit(4);
let halt = bit(8);
let enabled = self.state == Cia402State::OperationEnabled;
let pp_mode = self.modes_of_operation == 1;
if enabled && pp_mode {
if new_setpoint && !self.prev_new_setpoint {
if !(self.halt_blocks_setpoint_ack && halt) {
self.active_target = self.target_position;
self.setpoint_ack = true;
self.moving = true;
}
}
if !new_setpoint {
self.setpoint_ack = false;
}
} else {
self.setpoint_ack = false;
self.moving = false;
}
self.prev_new_setpoint = new_setpoint;
if enabled && pp_mode && !halt && self.moving {
let remaining = self.active_target - self.position_actual;
if remaining == 0 {
self.velocity_actual = 0;
self.moving = false;
} else {
let step = ((self.profile_velocity as f64 * dt).round() as i32).max(1);
if step >= remaining.abs() {
self.position_actual = self.active_target;
self.velocity_actual = 0;
self.moving = false;
} else {
let dir = remaining.signum();
self.position_actual += dir * step;
self.velocity_actual = dir * self.profile_velocity as i32;
}
}
} else {
self.velocity_actual = 0;
if halt {
self.moving = false;
}
}
let mut sw = status_base(self.state) | (1 << 9); if self.setpoint_ack {
sw |= 1 << 12; }
if !self.moving && self.position_actual == self.active_target {
sw |= 1 << 10; }
self.status_word = sw;
self.modes_display = self.modes_of_operation;
}
fn step_state_machine(&mut self) {
let cw = self.control_word;
let switch_on = cw & (1 << 0) != 0;
let enable_voltage = cw & (1 << 1) != 0;
let quick_stop = cw & (1 << 2) != 0;
let enable_op = cw & (1 << 3) != 0;
use Cia402State::*;
if !enable_voltage || !quick_stop {
self.state = SwitchOnDisabled;
return;
}
self.state = if switch_on {
if enable_op { OperationEnabled } else { SwitchedOn }
} else {
ReadyToSwitchOn
};
}
}
impl AxisView for SimDrive {
fn control_word(&self) -> u16 {
self.control_word
}
fn set_control_word(&mut self, word: u16) {
self.control_word = 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_display
}
fn status_word(&self) -> u16 {
self.status_word
}
fn position_actual(&self) -> i32 {
self.position_actual
}
fn velocity_actual(&self) -> i32 {
self.velocity_actual
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::motion::cia402::{Cia402Control, Cia402Status, RawControlWord, RawStatusWord};
fn enable(sim: &mut SimDrive) {
let mut cw = RawControlWord(0);
cw.cmd_enable_operation(); sim.set_control_word(cw.raw());
sim.set_modes_of_operation(1); for _ in 0..3 {
sim.step(0.01);
}
}
#[test]
fn reaches_operation_enabled() {
let mut sim = SimDrive::new();
assert_eq!(RawStatusWord(sim.status_word()).state(), Cia402State::SwitchOnDisabled);
enable(&mut sim);
assert_eq!(RawStatusWord(sim.status_word()).state(), Cia402State::OperationEnabled);
}
#[test]
fn disable_voltage_returns_to_switch_on_disabled() {
let mut sim = SimDrive::new();
enable(&mut sim);
sim.set_control_word(0x0000); sim.step(0.01);
assert_eq!(RawStatusWord(sim.status_word()).state(), Cia402State::SwitchOnDisabled);
}
#[test]
fn pp_move_runs_handshake_and_reaches_target() {
let mut sim = SimDrive::new();
enable(&mut sim);
sim.set_target_position(1000);
sim.set_profile_velocity(10_000);
let mut cw = RawControlWord(sim.control_word());
cw.set_bit(4, true);
sim.set_control_word(cw.raw());
sim.step(0.01);
let sw = RawStatusWord(sim.status_word());
assert!(sw.raw() & (1 << 12) != 0, "setpoint acknowledge asserted on rising edge");
assert!(sim.velocity_actual() > 0, "moving toward target");
cw.set_bit(4, false);
sim.set_control_word(cw.raw());
sim.step(0.01);
assert!(RawStatusWord(sim.status_word()).raw() & (1 << 12) == 0, "ack cleared");
for _ in 0..20 {
sim.step(0.01);
}
assert_eq!(sim.position_actual(), 1000);
assert_eq!(sim.velocity_actual(), 0);
assert!(RawStatusWord(sim.status_word()).raw() & (1 << 10) != 0, "target reached");
}
#[test]
fn halt_brings_velocity_to_zero() {
let mut sim = SimDrive::new();
enable(&mut sim);
sim.set_target_position(100_000);
sim.set_profile_velocity(10_000);
let mut cw = RawControlWord(sim.control_word());
cw.set_bit(4, true);
sim.set_control_word(cw.raw());
sim.step(0.01);
assert!(sim.velocity_actual() > 0);
cw.set_bit(8, true);
sim.set_control_word(cw.raw());
sim.step(0.01);
assert_eq!(sim.velocity_actual(), 0, "halt stops motion");
}
#[test]
fn quirk_emulation_blocks_ack_while_halted() {
let mut sim = SimDrive::new();
sim.set_halt_blocks_setpoint_ack(true);
enable(&mut sim);
let mut cw = RawControlWord(sim.control_word());
cw.set_bit(8, true); cw.set_bit(4, true); sim.set_control_word(cw.raw());
sim.step(0.01);
assert!(RawStatusWord(sim.status_word()).raw() & (1 << 12) == 0,
"quirk drive must NOT ack a setpoint while halted");
let mut ideal = SimDrive::new();
enable(&mut ideal);
ideal.set_control_word(cw.raw());
ideal.step(0.01);
assert!(RawStatusWord(ideal.status_word()).raw() & (1 << 12) != 0,
"ideal drive acks even while halted");
}
}