From e9f687e6ddba6214d2cec72ecd0ba5df7473f517 Mon Sep 17 00:00:00 2001 From: l_samenv Date: Fri, 22 Apr 2022 16:25:14 +0200 Subject: [PATCH] update phytron driver + fixes in secop_psi/sea.py (iodev) --- cfg/phytron.cfg | 6 ++-- secop_psi/phytron.py | 66 ++++++++++++++++++++++++++++++-------------- secop_psi/sea.py | 16 +++++------ 3 files changed, 57 insertions(+), 31 deletions(-) diff --git a/cfg/phytron.cfg b/cfg/phytron.cfg index 45ae300..ee75c8a 100644 --- a/cfg/phytron.cfg +++ b/cfg/phytron.cfg @@ -5,15 +5,15 @@ description = phytron motor test [INTERFACE] uri = tcp://5000 -[drv_iodev] +[drv_io] description = class = secop_psi.phytron.PhytronIO -uri = ma15-ts.psi.ch:3005 +uri = ma10-ts.psi.ch:3004 # uri = serial:///dev/tty.usbserial?baudrate=57600 # uri = serial:///dev/ttyUSB0?baudrate=9600 [drv] description = a phytron motor class = secop_psi.phytron.Motor -iodev = drv_iodev +io = drv_io encoder_mode = CHECK diff --git a/secop_psi/phytron.py b/secop_psi/phytron.py index 3a020a7..13cb70b 100644 --- a/secop_psi/phytron.py +++ b/secop_psi/phytron.py @@ -23,50 +23,62 @@ """driver for pythron motors""" from secop.core import Done, Command, EnumType, FloatRange, IntRange, \ - HasIodev, Parameter, Property, Drivable, PersistentMixin, PersistentParam, StringIO, StringType + HasIO, Parameter, Property, Drivable, PersistentMixin, PersistentParam, StringIO, StringType from secop.errors import CommunicationFailedError, HardwareError class PhytronIO(StringIO): end_of_line = '\x03' # ETX + timeout = 0.2 identification = [('0IVR', 'MCC Minilog .*')] - def communicate(self, command): - head, _, reply = super().communicate('\x02' + command).partition('\x02') - if reply[0] != '\x06': # ACK - raise CommunicationFailedError('missing ACK %r' % reply) + def communicate(self, command, expect_response=True): + for ntry in range(5, 0, -1): + try: + head, _, reply = super().communicate('\x02' + command).partition('\x02') + if reply[0] == '\x06': # ACK + if len(reply) == 1 and expect_response: + raise CommunicationFailedError('empty response') + break + raise CommunicationFailedError('missing ACK %r' % reply) + except Exception as e: + if ntry == 1: + raise + self.log.warning('%s - retry', e) return reply[1:] -class Motor(PersistentMixin, HasIodev, Drivable): +class Motor(PersistentMixin, HasIO, Drivable): axis = Property('motor axis X or Y', StringType(), default='X') address = Property('address', IntRange(0, 15), default=0) speed_factor = Property('steps / degree', FloatRange(0, None), default=2000) encoder_mode = Parameter('how to treat the encoder', EnumType('encoder', NO=0, READ=1, CHECK=2), default=1, readonly=False) - value = Parameter('angle', FloatRange(unit='deg'), poll=True) + value = Parameter('angle', FloatRange(unit='deg')) target = Parameter('target angle', FloatRange(unit='deg'), readonly=False) - speed = Parameter('', FloatRange(0, 20, unit='deg/s'), readonly=False, poll=True) - accel = Parameter('', FloatRange(2, 250, unit='deg/s/s'), readonly=False, poll=True) + speed = Parameter('', FloatRange(0, 20, unit='deg/s'), readonly=False) + accel = Parameter('', FloatRange(2, 250, unit='deg/s/s'), readonly=False) encoder_tolerance = Parameter('', FloatRange(unit='deg'), readonly=False, default=0.01) zero = PersistentParam('', FloatRange(unit='deg'), readonly=False, default=0) - encoder = Parameter('encoder reading', FloatRange(unit='deg'), poll=True) + encoder = Parameter('encoder reading', FloatRange(unit='deg')) sameside_offset = Parameter('offset when always approaching from the same side', - FloatRange(unit='deg'), readonly=True, default=0) + FloatRange(unit='deg'), readonly=False, default=0) - iodevClass = PhytronIO - fast_pollfactor = 0.02 + ioClass = PhytronIO + fast_poll = 0.1 _sameside_pending = False + _mismatch_count = 0 def earlyInit(self): + super().earlyInit() self.loadParameters() def get(self, cmd): - return self._iodev.communicate('\x02%x%s%s' % (self.address, self.axis, cmd)) + return self.communicate('\x02%x%s%s' % (self.address, self.axis, cmd)) def set(self, cmd, value): - self._iodev.communicate('\x02%x%s%s%g' % (self.address, self.axis, cmd, value)) + self.communicate('\x02%x%s%s%g' % (self.address, self.axis, cmd, value), False) def set_get(self, cmd, value, query): self.set(cmd, value) @@ -79,6 +91,9 @@ class Motor(PersistentMixin, HasIodev, Drivable): enc = self.read_encoder() else: enc = pos + status = self.communicate('\x02%xSE' % self.address) + status = status[0:4] if self.axis == 'X' else status[4:8] + self.log.debug('run %s enc %s end %s', status[1], status[2], status[3]) status = self.get('=H') if status == 'N': if self.encoder_mode == 'CHECK': @@ -86,9 +101,11 @@ class Motor(PersistentMixin, HasIodev, Drivable): if e1 - self.encoder_tolerance <= pos <= e2 + self.encoder_tolerance: self.status = self.Status.BUSY, 'driving' else: - self.log.error('encoder lag: %g not within %g..%g' % (pos, e1, e2)) + self.log.error('encoder lag: %g not within %g..%g', + pos, e1, e2) self.get('S') # stop self.status = self.Status.ERROR, 'encoder lag error' + self.setFastPoll(False) else: self.status = self.Status.BUSY, 'driving' else: @@ -99,9 +116,16 @@ class Motor(PersistentMixin, HasIodev, Drivable): return pos if (self.encoder_mode == 'CHECK' and abs(enc - pos) > self.encoder_tolerance): - self.status = self.Status.ERROR, 'encoder does not match pos' + if self._mismatch_count < 2: + self._mismatch_count += 1 + else: + self.log.error('encoder mismatch: abs(%g - %g) < %g', + enc, pos, self.encoder_tolerance) + self.status = self.Status.ERROR, 'encoder does not match pos' else: + self._mismatch_count = 0 self.status = self.Status.IDLE, '' + self.setFastPoll(False) return pos def read_encoder(self): @@ -113,8 +137,9 @@ class Motor(PersistentMixin, HasIodev, Drivable): return float(self.get('P14R')) / self.speed_factor def write_speed(self, value): - if abs(float(self.get('P03R')) * self.speed_factor - 1) > 0.001: - raise HardwareError('speed factor does not match') + inv_factor = float(self.get('P03R')) + if abs(inv_factor * self.speed_factor - 1) > 0.001: + raise HardwareError('speed factor does not match %g' % (1.0 / inv_factor)) return float(self.set_get('P14S', int(value * self.speed_factor), 'P14R')) / self.speed_factor def read_accel(self): @@ -136,6 +161,7 @@ class Motor(PersistentMixin, HasIodev, Drivable): self.set('A', value - self.zero + self.sameside_offset) else: self.set('A', value - self.zero) + self.setFastPoll(True, self.fast_poll) return value def write_zero(self, value): @@ -167,4 +193,4 @@ class Motor(PersistentMixin, HasIodev, Drivable): # TODO: # '=E' electronics status # '=I+' / '=I-': limit switches -# use P37 to determine if restarted \ No newline at end of file +# use P37 to determine if restarted diff --git a/secop_psi/sea.py b/secop_psi/sea.py index 0bde526..bee8674 100644 --- a/secop_psi/sea.py +++ b/secop_psi/sea.py @@ -62,7 +62,7 @@ service = %(service)s CFG_MODULE = """ [%(module)s] class = secop_psi.sea.%(modcls)s -iodev = %(seaconn)s +io = %(seaconn)s sea_object = %(module)s """ @@ -367,7 +367,7 @@ def get_datatype(paramdesc): class SeaModule(Module): - iodev = Attached() + io = Attached() # pollerClass=None path2param = None @@ -380,7 +380,7 @@ class SeaModule(Module): else: extra_modules = {} srv.extra_sea_modules = extra_modules - json_file = cfgdict.pop('json_file', None) or SeaClient.default_json_file[cfgdict['iodev']] + json_file = cfgdict.pop('json_file', None) or SeaClient.default_json_file[cfgdict['io']] visibility_level = cfgdict.pop('visibility_level', 2) single_module = cfgdict.pop('single_module', None) @@ -518,7 +518,7 @@ class SeaModule(Module): # print('override %s.read_%s' % (cls.__name__, key)) def rfunc(self, cmd='hval %s/%s' % (base, path)): - reply = self._iodev.query(cmd) + reply = self.io.query(cmd) try: reply = float(reply) except ValueError: @@ -538,7 +538,7 @@ class SeaModule(Module): value = int(value) # TODO: check if more has to be done for valid tcl data (strings?) cmd = "%s %s" % (command, value) - self._iodev.query(cmd) + self.io.query(cmd) return Done attributes['write_' + key] = wfunc @@ -584,7 +584,7 @@ class SeaModule(Module): self.DISPATCHER.broadcast_event(make_update(self.name, pobj)) def initModule(self): - self._iodev.register_obj(self, self.sea_object) + self.io.register_obj(self, self.sea_object) super().initModule() @@ -623,7 +623,7 @@ class SeaDrivable(SeaModule, Drivable): # return self.target def write_target(self, value): - self._iodev.query('run %s %s' % (self.sea_object, value)) + self.io.query('run %s %s' % (self.sea_object, value)) #self.status = [self.Status.BUSY, 'driving'] return value @@ -656,4 +656,4 @@ class SeaDrivable(SeaModule, Drivable): - on stdsct drivables this will call the halt script - on EaseDriv this will set the stopped state """ - self._iodev.query('%s is_running 0' % self.sea_object) + self.io.query('%s is_running 0' % self.sea_object)