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
"""
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
:param statefunc: the initial state to be called
:param fast_poll: flag to indicate that polling has to switched to fast
: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
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
"""
sm = self._state_machine
sm.status = self.get_status(statefunc, BUSY)
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)
self.read_status()
if fast_poll:
@ -212,24 +214,23 @@ class HasStates:
self.setFastPoll(True)
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
:param stopped_status: status to be set after stopping
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
given in the sopped_status argument.
given in the stopped_status argument.
An already running cleanup sequence is not executed again.
"""
sm = self._state_machine
if sm.is_active:
sm.idle_status = stopped_status
sm.stop()
sm.status = self.get_status(sm.statefunc, sm.status[0])[0], 'stopping'
self.read_status()
self.pollInfo.trigger(True) # trigger poller
self.start_machine(self.stopping, restart_text='stopping')
@Command
def stop(self):

View File

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