frappy_psi.phytron: stop motor before restart

restarting the phytron motor without prior stop leads
to funny behaviour.

- send stop before restart
- stop motor when moving but status not busy
- restart when motor drives the wrong way

+ better status text when stopping

Change-Id: I82cd59297b3c79a354a4eeb5ba03fc65bedf755f
Reviewed-on: https://forge.frm2.tum.de/review/c/secop/frappy/+/31929
Tested-by: Jenkins Automated Tests <pedersen+jenkins@frm2.tum.de>
Reviewed-by: Markus Zolliker <markus.zolliker@psi.ch>
This commit is contained in:
zolliker 2023-08-09 15:03:12 +02:00
parent fcb5052565
commit 4fc6ef52da
2 changed files with 79 additions and 25 deletions

View File

@ -89,6 +89,9 @@ class HasStates:
elif newstate: elif newstate:
# restart case # restart case
if status is not None: if status is not None:
if sm.status[1] == status[1]:
status = sm.status
else:
status = sm.status[0], f'restarting ({status[1]})' status = sm.status[0], f'restarting ({status[1]})'
else: else:
# start case # start case

View File

@ -85,6 +85,9 @@ class Motor(HasOffset, HasStates, PersistentMixin, HasIO, Drivable):
_step_size = None # degree / step _step_size = None # degree / step
_blocking_error = None # None or a string indicating the reason of an error needing clear_errors _blocking_error = None # None or a string indicating the reason of an error needing clear_errors
_running = False # status indicates motor is running _running = False # status indicates motor is running
_prev_diff = 0 # for checking progress
_intermediate_target = 0
_stopped_at = 0
STATUS_MAP = { STATUS_MAP = {
'08': (IDLE, ''), '08': (IDLE, ''),
@ -174,27 +177,37 @@ class Motor(HasOffset, HasStates, PersistentMixin, HasIO, Drivable):
self.status = ERROR, 'clear_errors needed after ' + self._blocking_error self.status = ERROR, 'clear_errors needed after ' + self._blocking_error
raise HardwareError(self.status[1]) raise HardwareError(self.status[1])
self.saveParameters() self.saveParameters()
if self.backlash: self.start_machine(self.starting, target=value)
# drive first to target + backlash
# we do not optimize when already driving from the right side
self.set('A', self.sign * (value + self.backlash))
else:
self.set('A', self.sign * value)
self.start_machine(self.driving)
self._running = True
return value return value
def hw_stop(self):
self._stopped_at = time.time()
self.get('S')
def doPoll(self):
super().doPoll()
if self._running and not self.isBusy():
if time.time() > self._stopped_at + 5:
self.log.warning('stop motor not started by us')
self.hw_stop()
def read_status(self): def read_status(self):
sysstatus = self.communicate(f'{self.address:x}SE') sysstatus = self.communicate(f'{self.address:x}SE')
sysstatus = sysstatus[1:4] if self.axis == 'X' else sysstatus[5:8] sysstatus = sysstatus[1:4] if self.axis == 'X' else sysstatus[5:8]
self._running = sysstatus[0] != '1'
status = self.STATUS_MAP.get(sysstatus[1:]) or (ERROR, f'unknown error {sysstatus[1:]}') status = self.STATUS_MAP.get(sysstatus[1:]) or (ERROR, f'unknown error {sysstatus[1:]}')
self._running = sysstatus[0] != '1'
if status[0] == ERROR: if status[0] == ERROR:
self._blocking_error = status[1] self._blocking_error = status[1]
return status return status
return super().read_status() # status from state machine return super().read_status() # status from state machine
def check_moving(self): def check_moving(self):
"""checks while motor is moving
- in case motor is moving properly return Retry
- in case motor is moving in the wrong direction, return self.stopping_for_restart
- in case encoder does not follow steps, raise a blocking error
"""
prev_enc = self.encoder prev_enc = self.encoder
if self.encoder_mode != 'NO': if self.encoder_mode != 'NO':
enc = self.read_encoder() enc = self.read_encoder()
@ -202,32 +215,70 @@ class Motor(HasOffset, HasStates, PersistentMixin, HasIO, Drivable):
enc = self.value enc = self.value
if not self._running: # at target if not self._running: # at target
return False return False
diff = abs(self.value - self._intermediate_target)
if diff > self._prev_diff and diff > self.encoder_tolerance:
# when the motor was started while running, without stopping first
# it happend that the motor run (almost) endless beyond the target!
# add this is to catch similar errors
self.hw_stop()
self.log.warning('motor is moving the wrong way')
return self.stopping_for_restart
self._prev_diff = diff
if self.encoder_mode != 'CHECK': if self.encoder_mode != 'CHECK':
return True return Retry
e1, e2 = sorted((prev_enc, enc)) tol = self.encoder_tolerance + abs(enc - prev_enc)
if e1 - self.encoder_tolerance <= self.value <= e2 + self.encoder_tolerance: if abs(self.value - enc) <= tol:
return True return Retry
self.log.error('encoder lag: %g not within %g..%g', self.log.error('encoder lag: steps %g deviate by more than %g from encoder %g',
self.value, e1, e2) self.value, tol, enc)
self.get('S') # stop self.hw_stop()
self.saveParameters() self.saveParameters()
self._blocking_error = 'encoder lag error' self._blocking_error = 'encoder lag error'
raise HardwareError(self._blocking_error) raise HardwareError(self._blocking_error)
def start_motor(self, target):
if self.backlash and (target - self.value) * self.backlash > 0:
# drive first to target + backlash
target += self.backlash
self._prev_diff = abs(target - self.value)
self._intermediate_target = target
self.set('A', self.sign * target)
@status_code(BUSY)
def stopping_for_restart(self, sm):
self.read_status()
if self._running:
return Retry
self.start_motor(sm.target)
return self.driving
@status_code(BUSY)
def starting(self, sm):
self.read_status()
if self._running:
self.hw_stop()
return self.stopping_for_restart
self.start_motor(sm.target)
return self.driving
@status_code(BUSY) @status_code(BUSY)
def driving(self, sm): def driving(self, sm):
if self.check_moving(): self.read_status()
return Retry result = self.check_moving()
if self.backlash: if result:
# drive to real target return result # Retry or self.stopping_for_restart
self.set('A', self.sign * self.target) if self._intermediate_target != self.target:
self._intermediate_target = self.target
# drive to real target after intermediate target
self.start_motor(self.target)
return self.driving_to_final_position return self.driving_to_final_position
return self.finishing return self.finishing
@status_code(BUSY) @status_code(BUSY)
def driving_to_final_position(self, sm): def driving_to_final_position(self, sm):
if self.check_moving(): result = self.check_moving()
return Retry if result:
return result # Retry or self.stopping_for_restart
return self.finishing return self.finishing
@status_code(BUSY) @status_code(BUSY)
@ -257,7 +308,7 @@ class Motor(HasOffset, HasStates, PersistentMixin, HasIO, Drivable):
@Command @Command
def stop(self): def stop(self):
self.get('S') self.hw_stop()
self.start_machine(self.stopping, status=(BUSY, 'stopping')) self.start_machine(self.stopping, status=(BUSY, 'stopping'))
@Command @Command