phytron next version

with adaption of HasStates

Change-Id: I167ac8031bc5f7120c30031e7cfcb7587b42b61d
This commit is contained in:
zolliker 2023-05-15 11:12:52 +02:00
parent df4a37085a
commit 050a2dc8dc
2 changed files with 38 additions and 32 deletions

View File

@ -183,12 +183,13 @@ class HasStates:
override for code to be executed after stopping override for code to be executed after stopping
""" """
def start_machine(self, statefunc, fast_poll=True, **kwds): def start_machine(self, statefunc, fast_poll=True, restart_text='restarting', **kwds):
"""start or restart the state machine """start or restart the state machine
:param statefunc: the initial state to be called :param statefunc: the initial state to be called
:param fast_poll: flag to indicate that polling has to switched to fast :param fast_poll: flag to indicate that polling has to switched to fast
:param cleanup: a cleanup function :param cleanup: a cleanup function
:param restart_text: status text when machine was already running
:param kwds: attributes to be added to the state machine on start :param kwds: attributes to be added to the state machine on start
If the state machine is already running, the following happens: If the state machine is already running, the following happens:
@ -202,9 +203,10 @@ class HasStates:
4) the state machine continues at the given statefunc 4) the state machine continues at the given statefunc
""" """
sm = self._state_machine sm = self._state_machine
sm.status = self.get_status(statefunc, BUSY)
if sm.statefunc: if sm.statefunc:
sm.status = sm.status[0], 'restarting' sm.status = sm.status[0], restart_text
else:
sm.status = self.get_status(statefunc, BUSY)
sm.start(statefunc, cleanup=kwds.pop('cleanup', self.on_cleanup), **kwds) sm.start(statefunc, cleanup=kwds.pop('cleanup', self.on_cleanup), **kwds)
self.read_status() self.read_status()
if fast_poll: if fast_poll:
@ -212,24 +214,23 @@ class HasStates:
self.setFastPoll(True) self.setFastPoll(True)
self.pollInfo.trigger(True) # trigger poller self.pollInfo.trigger(True) # trigger poller
def stop_machine(self, stopped_status=(IDLE, 'stopped')): def stopping(self, sm):
return self.final_status(IDLE, 'stopped')
def stop_machine(self):
"""stop the currently running machine """stop the currently running machine
:param stopped_status: status to be set after stopping :param stopped_status: status to be set after stopping
If the state machine is not running, nothing happens. If the state machine is not running, nothing happens.
Else the state machine is stoppen, the predefined cleanup Else the state machine is stopped, the predefined cleanup
sequence is executed and then the status is set to the value sequence is executed and then the status is set to the value
given in the sopped_status argument. given in the stopped_status argument.
An already running cleanup sequence is not executed again. An already running cleanup sequence is not executed again.
""" """
sm = self._state_machine sm = self._state_machine
if sm.is_active: if sm.is_active:
sm.idle_status = stopped_status self.start_machine(self.stopping, restart_text='stopping')
sm.stop()
sm.status = self.get_status(sm.statefunc, sm.status[0])[0], 'stopping'
self.read_status()
self.pollInfo.trigger(True) # trigger poller
@Command @Command
def stop(self): def stop(self):

View File

@ -77,11 +77,11 @@ class Motor(HasOffset, HasStates, PersistentMixin, HasIO, Drivable):
target_max = Limit() target_max = Limit()
alive_time = PersistentParam('alive time for detecting restarts', alive_time = PersistentParam('alive time for detecting restarts',
FloatRange(), default=0, export=False) FloatRange(), default=0, export=False)
sim_error = Parameter('run time error simulation', StringType(), readonly=False, default='')
ioClass = PhytronIO ioClass = PhytronIO
_step_size = None # degree / step _step_size = None # degree / step
_reset_needed = False _blocking_error = None # None or a string indicating the reason of an error needing reset
_running = False # status indicates motor is running
STATUS_MAP = { STATUS_MAP = {
'08': (IDLE, ''), '08': (IDLE, ''),
@ -119,7 +119,7 @@ class Motor(HasOffset, HasStates, PersistentMixin, HasIO, Drivable):
self.set('P37S', axisbit | active_axes) # activate axis self.set('P37S', axisbit | active_axes) # activate axis
if now < self.alive_time + 7 * 24 * 3600: # the device was running within last week if now < self.alive_time + 7 * 24 * 3600: # the device was running within last week
# inform the user about the loss of position by the need of doing reset_error # inform the user about the loss of position by the need of doing reset_error
self._reset_needed = True self._blocking_error = 'lost position'
else: # do reset silently else: # do reset silently
self.reset_error() self.reset_error()
self.alive_time = now self.alive_time = now
@ -167,9 +167,8 @@ class Motor(HasOffset, HasStates, PersistentMixin, HasIO, Drivable):
def write_target(self, value): def write_target(self, value):
self.read_alive_time() self.read_alive_time()
if self._reset_needed: if self._blocking_error:
if not self.status[1].startswith('reset needed'): self.status = ERROR, 'reset needed after ' + self._blocking_error
self.status = ERROR, 'reset needed after ' + self.status[1]
raise HardwareError(self.status[1]) raise HardwareError(self.status[1])
self.saveParameters() self.saveParameters()
if self.backlash: if self.backlash:
@ -180,17 +179,16 @@ class Motor(HasOffset, HasStates, PersistentMixin, HasIO, Drivable):
else: else:
self.set('A', self.sign * value) self.set('A', self.sign * value)
self.start_machine(self.driving) self.start_machine(self.driving)
self._running = True
return value return value
def read_status(self): def read_status(self):
if self.sim_error: sysstatus = self.communicate(f'{self.address:x}SE')
sysstatus = self.sim_error sysstatus = sysstatus[1:4] if self.axis == 'X' else sysstatus[5:8]
else: self._running = sysstatus[0] != '1'
sysstatus = self.communicate(f'{self.address:x}SE') status = self.STATUS_MAP.get(sysstatus[1:]) or (ERROR, f'unknown error {sysstatus[1:]}')
sysstatus = sysstatus[2:4] if self.axis == 'X' else sysstatus[6:8]
status = self.STATUS_MAP.get(sysstatus) or (ERROR, f'unknown error {sysstatus}')
if status[0] == ERROR: if status[0] == ERROR:
self._reset_needed = True 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
@ -200,8 +198,7 @@ class Motor(HasOffset, HasStates, PersistentMixin, HasIO, Drivable):
enc = self.read_encoder() enc = self.read_encoder()
else: else:
enc = self.value enc = self.value
status = self.get('=H') if not self._running: # at target
if status != 'N': # at target
return False return False
if self.encoder_mode != 'CHECK': if self.encoder_mode != 'CHECK':
return True return True
@ -212,8 +209,8 @@ class Motor(HasOffset, HasStates, PersistentMixin, HasIO, Drivable):
self.value, e1, e2) self.value, e1, e2)
self.get('S') # stop self.get('S') # stop
self.saveParameters() self.saveParameters()
self._reset_needed = True self._blocking_error = 'encoder lag error'
raise HardwareError('encoder lag error') raise HardwareError(self._blocking_error)
@status_code(BUSY) @status_code(BUSY)
def driving(self, sm): def driving(self, sm):
@ -243,21 +240,29 @@ class Motor(HasOffset, HasStates, PersistentMixin, HasIO, Drivable):
if sm.mismatch_count > 2: if sm.mismatch_count > 2:
self.log.error('encoder mismatch: abs(%g - %g) < %g', self.log.error('encoder mismatch: abs(%g - %g) < %g',
enc, pos, self.encoder_tolerance) enc, pos, self.encoder_tolerance)
self._reset_needed = True self._blocking_error = 'encoder does not match pos'
raise HardwareError('encoder does not match pos') raise HardwareError(self._blocking_error)
sm.mismatch_count += 1 sm.mismatch_count += 1
return Retry return Retry
self.saveParameters() self.saveParameters()
return self.final_status(IDLE, '') return self.final_status(IDLE, '')
@status_code(BUSY)
def stopping(self, sm):
if self._running:
return Retry
return self.final_status(IDLE, 'stopped')
@Command
def stop(self): def stop(self):
self.get('S') self.get('S')
self.start_machine(self.stopping, status=(BUSY, 'stopping0'))
@Command @Command
def reset_error(self): def reset_error(self):
"""Reset error, set position to encoder""" """Reset error, set position to encoder"""
self.read_value() self.read_value()
if self._reset_needed: if self._blocking_error:
newenc = enc = self.read_encoder() newenc = enc = self.read_encoder()
pos = self.value pos = self.value
if abs(enc - pos) > self.encoder_tolerance or self.encoder_mode == 'NO': if abs(enc - pos) > self.encoder_tolerance or self.encoder_mode == 'NO':
@ -278,7 +283,7 @@ class Motor(HasOffset, HasStates, PersistentMixin, HasIO, Drivable):
self.set('P20S', newenc * self.sign) # set pos to encoder self.set('P20S', newenc * self.sign) # set pos to encoder
self.read_value() self.read_value()
self.status = 'IDLE', 'after error reset' self.status = 'IDLE', 'after error reset'
self._reset_needed = False self._blocking_error = None
# TODO: # TODO:
# '=E' electronics status # '=E' electronics status