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:
parent
fcb5052565
commit
4fc6ef52da
@ -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
|
||||||
|
@ -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
|
||||||
|
Loading…
x
Reference in New Issue
Block a user