diff --git a/cfg/main/ma10.cfg b/cfg/main/ma10.cfg index 1be5da3..67494cb 100644 --- a/cfg/main/ma10.cfg +++ b/cfg/main/ma10.cfg @@ -67,5 +67,6 @@ uri = ma10-ts.psi.ch:3004 description = stick rotation, typically used for omega class = secop_psi.phytron.Motor io = om_io +sign = -1 encoder_mode = CHECK diff --git a/cfg/phytron.cfg b/cfg/phytron.cfg index ee75c8a..26e6e15 100644 --- a/cfg/phytron.cfg +++ b/cfg/phytron.cfg @@ -8,7 +8,7 @@ uri = tcp://5000 [drv_io] description = class = secop_psi.phytron.PhytronIO -uri = ma10-ts.psi.ch:3004 +uri = ma7-ts.psi.ch:3007 # uri = serial:///dev/tty.usbserial?baudrate=57600 # uri = serial:///dev/ttyUSB0?baudrate=9600 @@ -16,4 +16,5 @@ uri = ma10-ts.psi.ch:3004 description = a phytron motor class = secop_psi.phytron.Motor io = drv_io +abslimits = -180,360 encoder_mode = CHECK diff --git a/cfg/ppmssim.cfg b/cfg/ppmssim.cfg index 1bc9f6b..82cc65e 100644 --- a/cfg/ppmssim.cfg +++ b/cfg/ppmssim.cfg @@ -14,111 +14,111 @@ io = ppms class = secop_psi.ppms.Field target.min = -9 target.max = 9 -.description = magnetic field -.io = ppms +description = magnetic field +io = ppms [pos] class = secop_psi.ppms.Position -.description = sample rotator -.io = ppms +description = sample rotator +io = ppms [lev] class = secop_psi.ppms.Level -.description = helium level -.io = ppms +description = helium level +io = ppms [chamber] class = secop_psi.ppms.Chamber -.description = chamber state -.io = ppms +description = chamber state +io = ppms [r1] class = secop_psi.ppms.BridgeChannel -.description = resistivity channel 1 -.no = 1 +description = resistivity channel 1 +no = 1 value.unit = Ohm -.io = ppms +io = ppms [r2] class = secop_psi.ppms.BridgeChannel -.description = resistivity channel 2 -.no = 2 +description = resistivity channel 2 +no = 2 value.unit = Ohm -.io = ppms +io = ppms [r3] class = secop_psi.ppms.BridgeChannel -.description = resistivity channel 3 -.no = 3 +description = resistivity channel 3 +no = 3 value.unit = Ohm -.io = ppms +io = ppms [r4] class = secop_psi.ppms.BridgeChannel -.description = resistivity channel 4 -.no = 4 +description = resistivity channel 4 +no = 4 value.unit = Ohm -.io = ppms +io = ppms [i1] class = secop_psi.ppms.Channel -.description = current channel 1 -.no = 1 +description = current channel 1 +no = 1 value.unit = uA -.io = ppms +io = ppms [i2] class = secop_psi.ppms.Channel -.description = current channel 2 -.no = 2 +description = current channel 2 +no = 2 value.unit = uA -.io = ppms +io = ppms [i3] class = secop_psi.ppms.Channel -.description = current channel 3 -.no = 3 +description = current channel 3 +no = 3 value.unit = uA -.io = ppms +io = ppms [i4] class = secop_psi.ppms.Channel -.description = current channel 4 -.no = 4 +description = current channel 4 +no = 4 value.unit = uA -.io = ppms +io = ppms [v1] class = secop_psi.ppms.DriverChannel -.description = voltage channel 1 -.no = 1 +description = voltage channel 1 +no = 1 value.unit = V -.io = ppms +io = ppms [v2] class = secop_psi.ppms.DriverChannel -.description = voltage channel 2 -.no = 2 +description = voltage channel 2 +no = 2 value.unit = V -.io = ppms +io = ppms [tv] class = secop_psi.ppms.UserChannel -.description = VTI temperature +description = VTI temperature enabled = 1 value.unit = K -.io = ppms +io = ppms [ts] class = secop_psi.ppms.UserChannel -.description = sample temperature +description = sample temperature enabled = 1 value.unit = K -.io = ppms +io = ppms [ppms] class = secop_psi.ppms.Main -.description = the main and poller module -.class_id = QD.MULTIVU.PPMS.1 -.visibility = 3 +description = the main and poller module +class_id = QD.MULTIVU.PPMS.1 +visibility = 3 pollinterval = 2 diff --git a/cfg/stick/ma10stick.cfg b/cfg/stick/ma10stick.cfg index 948114e..0fa393e 100644 --- a/cfg/stick/ma10stick.cfg +++ b/cfg/stick/ma10stick.cfg @@ -10,7 +10,7 @@ service = stick [ts] class = secop_psi.sea.SeaReadable -iodev = sea_stick +io = sea_stick sea_object = tt json_file = ma10.config.json rel_paths = ts diff --git a/secop_psi/phytron.py b/secop_psi/phytron.py index 13cb70b..cc0e585 100644 --- a/secop_psi/phytron.py +++ b/secop_psi/phytron.py @@ -20,11 +20,13 @@ # # ***************************************************************************** -"""driver for pythron motors""" +"""driver for phytron motors""" from secop.core import Done, Command, EnumType, FloatRange, IntRange, \ - HasIO, Parameter, Property, Drivable, PersistentMixin, PersistentParam, StringIO, StringType -from secop.errors import CommunicationFailedError, HardwareError + HasIO, Parameter, Property, Drivable, PersistentMixin, PersistentParam, \ + StringIO, StringType, TupleOf +from secop.errors import CommunicationFailedError, HardwareError, BadValueError +from secop.lib import clamp class PhytronIO(StringIO): @@ -60,18 +62,29 @@ class Motor(PersistentMixin, HasIO, Drivable): 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) + offset = PersistentParam('', FloatRange(unit='deg'), readonly=False, default=0) + sign = PersistentParam('', IntRange(-1,1), readonly=False, default=1) encoder = Parameter('encoder reading', FloatRange(unit='deg')) sameside_offset = Parameter('offset when always approaching from the same side', FloatRange(unit='deg'), readonly=False, default=0) + abslimits = Parameter('abs limits (raw values)', default=(0, 0), + datatype=TupleOf(FloatRange(unit='deg'), FloatRange(unit='deg'))) + userlimits = PersistentParam('user limits', readonly=False, default=(0, 0), initwrite=True, + datatype=TupleOf(FloatRange(unit='deg'), FloatRange(unit='deg'))) ioClass = PhytronIO fast_poll = 0.1 _sameside_pending = False _mismatch_count = 0 + _rawlimits = None def earlyInit(self): super().earlyInit() + if self.abslimits == (0, 0): + self.abslimits = -9e99, 9e99 + if self.userlimits == (0, 0): + self._rawlimits = self.abslimits + self.read_userlimits() self.loadParameters() def get(self, cmd): @@ -86,7 +99,7 @@ class Motor(PersistentMixin, HasIO, Drivable): def read_value(self): prev_enc = self.encoder - pos = float(self.get('P20R')) + self.zero + pos = float(self.get('P20R')) * self.sign - self.offset if self.encoder_mode != 'NO': enc = self.read_encoder() else: @@ -111,7 +124,7 @@ class Motor(PersistentMixin, HasIO, Drivable): else: if self._sameside_pending: # drive to real target - self.set('A', self.target - self.zero) + self.set('A', self.sign * (self.target + self.offset)) self._sameside_pending = False return pos if (self.encoder_mode == 'CHECK' and @@ -131,7 +144,7 @@ class Motor(PersistentMixin, HasIO, Drivable): def read_encoder(self): if self.encoder_mode == 'NO': return self.value - return float(self.get('P22R')) + self.zero + return float(self.get('P22R')) * self.sign - self.offset def read_speed(self): return float(self.get('P14R')) / self.speed_factor @@ -150,22 +163,42 @@ class Motor(PersistentMixin, HasIO, Drivable): raise HardwareError('speed factor does not match') return float(self.set_get('P15S', int(value * self.speed_factor), 'P15R')) / self.speed_factor + def _check_limits(self, *values): + for name, (mn, mx) in ('user', self._rawlimits), ('abs', self.abslimits): + mn -= self.offset + mx -= self.offset + for v in values: + if not (mn <= v <= mx): + raise BadValueError('%s limits violation: %g <= %g <= %g' % (name, mn, v, mx)) + v += self.offset + def write_target(self, value): if self.status[0] == self.Status.ERROR: raise HardwareError('need reset') self.status = self.Status.BUSY, 'changed target' + self._check_limits(value, value + self.sameside_offset) if self.sameside_offset: # drive first to target + sameside_offset # we do not optimize when already driving from the right side self._sameside_pending = True - self.set('A', value - self.zero + self.sameside_offset) + self.set('A', self.sign * (value + self.offset + self.sameside_offset)) else: - self.set('A', value - self.zero) + self.set('A', self.sign * (value + self.offset)) self.setFastPoll(True, self.fast_poll) return value - def write_zero(self, value): - self.zero = value + def read_userlimits(self): + return self._rawlimits[0] - self.offset, self._rawlimits[1] - self.offset + + def write_userlimits(self, value): + self._rawlimits = [clamp(self.abslimits[0], v + self.offset, self.abslimits[1]) for v in value] + value = self.read_userlimits() + self.saveParameters() + return value + + def write_offset(self, value): + self.offset = value + self.read_userlimits() self.saveParameters() return Done @@ -177,16 +210,17 @@ class Motor(PersistentMixin, HasIO, Drivable): """reset error, set position to encoder""" self.read_value() if self.status[0] == self.Status.ERROR: - enc = self.encoder - self.zero - pos = self.value - self.zero + enc = self.encoder + self.offset + pos = self.value + self.offset if abs(enc - pos) > self.encoder_tolerance: if enc < 0: + # assume we have a rotation (not a linear motor) while enc < 0: - self.zero -= 360 + self.offset += 360 enc += 360 - self.set('P22S', enc) + self.set('P22S', enc * self.sign) self.saveParameters() - self.set('P20S', enc) # set pos to encoder + self.set('P20S', enc * self.sign) # set pos to encoder self.read_value() # self.status = self.Status.IDLE, '' diff --git a/secop_psi/sea.py b/secop_psi/sea.py index d15aace..156c013 100644 --- a/secop_psi/sea.py +++ b/secop_psi/sea.py @@ -163,7 +163,7 @@ class SeaClient(ProxyClient, Module): self._connect_thread = None mkthread(self._rxthread, started_callback) - def request(self, command): + def request(self, command, quiet=False): """send a request and wait for reply""" with self._write_lock: if not self.syncio or not self.syncio.connection: @@ -181,7 +181,8 @@ class SeaClient(ProxyClient, Module): print('connected to %s' % self.uri) self.syncio.flush_recv() # print('> %s' % command) - self.syncio.writeline(('fulltransact %s' % command).encode()) + ft = 'fulltransAct' if quiet else 'fulltransact' + self.syncio.writeline(('%s %s' % (ft, command)).encode()) result = None deadline = time.time() + 10 while time.time() < deadline: @@ -292,11 +293,11 @@ class SeaClient(ProxyClient, Module): return reply @Command(StringType(), result=StringType()) - def query(self, cmd): + def query(self, cmd, quiet=False): """a request checking for errors and accepting 0 or 1 line as result""" errors = [] reply = None - for line in self.request(cmd).split('\n'): + for line in self.request(cmd, quiet).split('\n'): if line.strip().startswith('ERROR:'): errors.append(line[6:].strip()) elif reply is None: @@ -408,6 +409,7 @@ class SeaModule(Module): if issubclass(cls, SeaWritable): if paramdesc.get('readonly', True): raise ConfigError('%s/%s is not writable' % (sea_object, paramdesc['path'])) + params.insert(0, paramdesc.copy()) # copy value paramdesc['key'] = 'target' paramdesc['readonly'] = False extra_module_set = () @@ -543,7 +545,7 @@ class SeaModule(Module): # print('override %s.read_%s' % (cls.__name__, key)) def rfunc(self, cmd='hval %s/%s' % (base, path)): - reply = self.io.query(cmd) + reply = self.io.query(cmd, True) try: reply = float(reply) except ValueError: