use super::*;
impl MotorRecord {
pub fn check_completion(&mut self) -> ProcessEffects {
let mut effects = ProcessEffects::default();
if self.stat.mip.contains(MipFlags::DELAY_ACK) {
self.stat.mip.remove(MipFlags::DELAY_ACK);
self.evaluate_position_error_after_delay(&mut effects);
return effects;
}
let driver_done =
self.stat.msta.contains(MstaFlags::DONE) && !self.stat.msta.contains(MstaFlags::MOVING);
if !driver_done {
effects.suppress_forward_link = true;
return effects;
}
if self.stat.mip.contains(MipFlags::STOP) {
if let Some(new_target) = self.internal.pending_retarget.take() {
self.stat.mip = MipFlags::empty();
self.pos.dval = new_target;
self.pos.val = coordinate::dial_to_user(new_target, self.conv.dir, self.pos.off);
if let Ok(rval) = coordinate::dial_to_raw(new_target, self.conv.mres) {
self.pos.rval = rval;
}
self.plan_absolute_move(&mut effects);
return effects;
}
if self.stat.mip.intersects(MipFlags::HOMF | MipFlags::HOMR) {
let forward = self.stat.mip.contains(MipFlags::HOMF);
self.stat.mip.remove(MipFlags::STOP);
self.set_phase(MotionPhase::Homing);
let hw_forward = if self.conv.mres >= 0.0 {
forward
} else {
!forward
};
self.stat.cdir = if self.conv.mres >= 0.0 {
forward
} else {
!forward
};
effects.commands.push(MotorCommand::Home {
forward: hw_forward,
velocity: self.vel.hvel,
acceleration: self.vel.accl,
});
effects.request_poll = true;
effects.suppress_forward_link = true;
return effects;
}
if self.stat.mip.intersects(MipFlags::JOGF | MipFlags::JOGR) {
let forward = self.stat.mip.contains(MipFlags::JOGF);
self.stat.mip.remove(MipFlags::STOP);
self.set_phase(MotionPhase::Jog);
self.internal.jog_was_forward = forward;
effects.commands.push(MotorCommand::MoveVelocity {
direction: forward,
velocity: self.vel.jvel,
acceleration: self.vel.jar,
});
effects.request_poll = true;
effects.suppress_forward_link = true;
return effects;
}
self.sync_positions();
self.finalize_or_delay(&mut effects);
return effects;
}
match self.stat.phase {
MotionPhase::MainMove => {
if self.internal.backlash_pending {
self.start_backlash_final(&mut effects);
} else {
self.evaluate_position_error(&mut effects);
}
}
MotionPhase::BacklashFinal => {
self.evaluate_position_error(&mut effects);
}
MotionPhase::Retry => {
self.evaluate_position_error(&mut effects);
}
MotionPhase::Jog | MotionPhase::JogStopping => {
self.sync_positions();
if self.needs_jog_backlash() {
self.start_jog_backlash(&mut effects);
} else {
self.finalize_or_delay(&mut effects);
}
}
MotionPhase::JogBacklash => {
if self.internal.backlash_pending {
self.start_jog_backlash_final(&mut effects);
} else {
self.finalize_or_delay(&mut effects);
}
}
MotionPhase::Homing => {
self.stat.athm = true;
self.sync_positions();
self.finalize_or_delay(&mut effects);
}
MotionPhase::DelayWait => {
self.finalize_motion(&mut effects);
}
MotionPhase::Idle => {
}
}
effects
}
pub(crate) fn finalize_or_delay(&mut self, effects: &mut ProcessEffects) {
if self.timing.dly > 0.0 {
self.set_phase(MotionPhase::DelayWait);
self.stat.mip.insert(MipFlags::DELAY_REQ);
effects.schedule_delay = Some(std::time::Duration::from_secs_f64(self.timing.dly));
effects.suppress_forward_link = true;
} else {
self.finalize_motion(effects);
}
}
pub(crate) fn finalize_motion(&mut self, _effects: &mut ProcessEffects) {
self.set_phase(MotionPhase::Idle);
self.stat.mip = MipFlags::empty();
self.stat.dmov = true;
self.stat.movn = false;
self.suppress_flnk = false;
self.retry.rcnt = 0;
self.internal.backlash_pending = false;
self.internal.pending_retarget = None;
self.internal.lval = self.pos.val;
self.internal.ldvl = self.pos.dval;
self.internal.lrvl = self.pos.rval;
if self.ctrl.spmg == SpmgMode::Move {
self.ctrl.spmg = SpmgMode::Pause;
self.internal.lspg = SpmgMode::Pause;
}
}
pub(crate) fn set_phase(&mut self, new_phase: MotionPhase) {
tracing::debug!("phase transition: {:?} -> {:?}", self.stat.phase, new_phase);
self.stat.phase = new_phase;
}
fn evaluate_position_error_after_delay(&mut self, effects: &mut ProcessEffects) {
let diff = (self.pos.dval - self.pos.drbv).abs();
let same_polarity = (self.conv.dir == MotorDir::Pos) == (self.conv.mres >= 0.0);
let user_cdir = if same_polarity {
self.stat.cdir
} else {
!self.stat.cdir
};
let ls_blocks_retry = (self.limits.hls && user_cdir) || (self.limits.lls && !user_cdir);
if diff > self.retry.rdbd
&& self.retry.rcnt < self.retry.rtry
&& self.retry.rdbd > 0.0
&& !ls_blocks_retry
{
if self.retry.rmod == RetryMode::InPosition {
self.retry.rcnt += 1;
self.retry.miss = false;
self.stat.mip = MipFlags::RETRY;
self.finalize_or_delay(effects);
return;
}
self.retry.rcnt += 1;
self.retry.miss = false;
self.set_phase(MotionPhase::Retry);
self.stat.mip = MipFlags::RETRY;
let retry_target = self.compute_retry_target();
let frac = self.retry.frac;
if self.use_relative_moves() {
let rel_distance = (retry_target - self.pos.drbv) * frac;
effects.commands.push(MotorCommand::MoveRelative {
distance: rel_distance,
velocity: self.vel.velo,
acceleration: self.vel.accl,
});
} else {
let position = self.pos.dval + frac * (retry_target - self.pos.dval);
effects.commands.push(MotorCommand::MoveAbsolute {
position,
velocity: self.vel.velo,
acceleration: self.vel.accl,
});
}
effects.request_poll = true;
effects.suppress_forward_link = true;
} else {
if diff > self.retry.rdbd && self.retry.rdbd > 0.0 {
self.retry.miss = true;
}
self.finalize_motion(effects);
}
}
fn evaluate_position_error(&mut self, effects: &mut ProcessEffects) {
let diff = (self.pos.dval - self.pos.drbv).abs();
let same_polarity = (self.conv.dir == MotorDir::Pos) == (self.conv.mres >= 0.0);
let user_cdir = if same_polarity {
self.stat.cdir
} else {
!self.stat.cdir
};
let ls_blocks_retry = (self.limits.hls && user_cdir) || (self.limits.lls && !user_cdir);
if diff > self.retry.rdbd
&& self.retry.rcnt < self.retry.rtry
&& self.retry.rdbd > 0.0
&& !ls_blocks_retry
{
if self.retry.rmod == RetryMode::InPosition {
self.finalize_or_delay(effects);
return;
}
self.retry.rcnt += 1;
self.retry.miss = false;
self.set_phase(MotionPhase::Retry);
self.stat.mip = MipFlags::RETRY;
let retry_target = self.compute_retry_target();
let frac = self.retry.frac;
if self.use_relative_moves() {
let rel_distance = (retry_target - self.pos.drbv) * frac;
effects.commands.push(MotorCommand::MoveRelative {
distance: rel_distance,
velocity: self.vel.velo,
acceleration: self.vel.accl,
});
} else {
let position = self.pos.dval + frac * (retry_target - self.pos.dval);
effects.commands.push(MotorCommand::MoveAbsolute {
position,
velocity: self.vel.velo,
acceleration: self.vel.accl,
});
}
effects.request_poll = true;
effects.suppress_forward_link = true;
} else {
if diff > self.retry.rdbd && self.retry.rdbd > 0.0 {
self.retry.miss = true;
}
self.finalize_or_delay(effects);
}
}
fn compute_retry_target(&self) -> f64 {
match self.retry.rmod {
RetryMode::Default => {
self.pos.dval
}
RetryMode::Arithmetic => {
let relpos = self.pos.dval - self.pos.drbv;
let rtry = self.retry.rtry as f64;
let rcnt = self.retry.rcnt as f64;
let factor = if rtry > 0.0 {
(rtry - rcnt + 1.0) / rtry
} else {
1.0
};
self.pos.drbv + relpos * factor
}
RetryMode::Geometric => {
let relpos = self.pos.dval - self.pos.drbv;
let power = (self.retry.rcnt - 1).max(0) as u32;
let factor = 1.0 / (2.0_f64.powi(power as i32));
self.pos.drbv + relpos * factor
}
RetryMode::InPosition => {
self.pos.dval
}
}
}
pub(crate) fn needs_backlash_for_move(&self, dval: f64, drbv: f64) -> bool {
if self.retry.bdst == 0.0 {
return false;
}
if self.retry.bdst.abs() < self.conv.mres.abs() {
return false;
}
let move_direction = dval - drbv;
if move_direction == 0.0 {
return false;
}
(move_direction > 0.0) != (self.retry.bdst > 0.0)
}
pub(crate) fn compute_backlash_pretarget(dval: f64, bdst: f64) -> f64 {
dval - bdst
}
fn needs_jog_backlash(&self) -> bool {
self.retry.bdst != 0.0 && self.retry.bdst.abs() >= self.conv.mres.abs()
}
fn start_backlash_final(&mut self, effects: &mut ProcessEffects) {
self.internal.backlash_pending = false;
self.set_phase(MotionPhase::BacklashFinal);
self.stat.mip = MipFlags::MOVE_BL;
let frac = self.retry.frac;
if self.use_relative_moves() {
let rel_distance = (self.pos.dval - self.pos.drbv) * frac;
effects.commands.push(MotorCommand::MoveRelative {
distance: rel_distance,
velocity: self.vel.bvel,
acceleration: self.vel.bacc,
});
} else {
let pretarget = Self::compute_backlash_pretarget(self.pos.dval, self.retry.bdst);
let position = pretarget + frac * (self.pos.dval - pretarget);
effects.commands.push(MotorCommand::MoveAbsolute {
position,
velocity: self.vel.bvel,
acceleration: self.vel.bacc,
});
}
effects.request_poll = true;
effects.suppress_forward_link = true;
}
fn start_jog_backlash(&mut self, effects: &mut ProcessEffects) {
let pretarget = self.pos.dval - self.retry.bdst;
self.set_phase(MotionPhase::JogBacklash);
self.stat.mip = MipFlags::JOG_BL1;
self.internal.backlash_pending = true;
if self.use_relative_moves() {
effects.commands.push(MotorCommand::MoveRelative {
distance: pretarget - self.pos.drbv,
velocity: self.vel.velo,
acceleration: self.vel.accl,
});
} else {
effects.commands.push(MotorCommand::MoveAbsolute {
position: pretarget,
velocity: self.vel.velo,
acceleration: self.vel.accl,
});
}
effects.request_poll = true;
effects.suppress_forward_link = true;
}
fn start_jog_backlash_final(&mut self, effects: &mut ProcessEffects) {
let frac = self.retry.frac;
self.stat.mip = MipFlags::JOG_BL2;
self.internal.backlash_pending = false;
let pretarget = Self::compute_backlash_pretarget(self.pos.dval, self.retry.bdst);
if self.use_relative_moves() {
let rel_distance = (self.pos.dval - self.pos.drbv) * frac;
effects.commands.push(MotorCommand::MoveRelative {
distance: rel_distance,
velocity: self.vel.bvel,
acceleration: self.vel.bacc,
});
} else {
let position = pretarget + frac * (self.pos.dval - pretarget);
effects.commands.push(MotorCommand::MoveAbsolute {
position,
velocity: self.vel.bvel,
acceleration: self.vel.bacc,
});
}
effects.request_poll = true;
effects.suppress_forward_link = true;
}
}