diff --git a/frappy/states.py b/frappy/states.py index eb4b5bb..c2489e7 100644 --- a/frappy/states.py +++ b/frappy/states.py @@ -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): diff --git a/frappy_psi/phytron.py b/frappy_psi/phytron.py index ea0b08f..4f4d8ba 100644 --- a/frappy_psi/phytron.py +++ b/frappy_psi/phytron.py @@ -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