diff --git a/cfg/sim_uniax.cfg b/cfg/sim_uniax.cfg new file mode 100644 index 0000000..27ee782 --- /dev/null +++ b/cfg/sim_uniax.cfg @@ -0,0 +1,41 @@ +[NODE] +id = uniax_sim.psi.ch +description = [sim] uniaxial pressure device + +[INTERFACE] +uri=tcp://5000 + +[drv] +class = secop.simulation.SimDrivable +extra_params = speed +description = simulated motor +value.default = 0 +speed.readonly = False +speed.default = 10 +interval = 0.11 + +[transducer] +class = secop_psi.simdpm.DPM3 +description = simulated force +motor = drv + +[force] +class = secop_psi.uniax.Uniax +description = uniax driver +motor = drv +transducer = transducer + +[res] +class = secop.simulation.SimReadable +description = raw temperature sensor on the stick +extra_params = jitter +jitter.default = 1 +value.default = 99 +value.datatype = {"type":"double", "unit":"Ohm"} + +[T] +class=secop_psi.softcal.Sensor +description=temperature sensor, soft calibration +rawsensor=res +calib = X132254.340 +value.unit = "K" diff --git a/secop_psi/dpm.py b/secop_psi/dpm.py index d1b34b5..f00e97d 100644 --- a/secop_psi/dpm.py +++ b/secop_psi/dpm.py @@ -21,8 +21,10 @@ # ***************************************************************************** """transducer DPM3 read out""" -from secop.core import Drivable, Parameter, FloatRange, BoolType, StringIO,\ - HasIodev, IntRange, Done, Attached, Command +import time + +from secop.core import Readable, Parameter, FloatRange, StringIO,\ + HasIodev, IntRange, Done class DPM3IO(StringIO): @@ -45,30 +47,22 @@ def float2hex(value, digits): return '%06X' % intvalue -class DPM3(HasIodev, Drivable): +class DPM3(HasIodev, Readable): OFFSET = 0x8f SCALE = 0x8c - MAGNITUDE = {'1': 1, '2': 10, '3': 100, '4': 1e3, '5': 1e4, '6': 1e5, - '9':-1, 'A':-10, 'B':-100, 'C':-1e3, 'D':-1e4, 'E':-1e5} + MAGNITUDE = {'1': 1, '2': 10, '3': 100, '4': 1e3, '5': 1e4, '6': 1e5, + '9': -1, 'A': -10, 'B': -100, 'C': -1e3, 'D': -1e4, 'E': -1e5} iodevClass = DPM3IO - motor = Attached() - digits = Parameter('number of digits for value', IntRange(0, 5), initwrite=True, readonly=False) value = Parameter(datatype=FloatRange(unit='N')) - target = Parameter(datatype=FloatRange(unit='N')) - step = Parameter('maximum motor step', FloatRange(unit='deg'), default=5, readonly=False) - + digits = Parameter('number of digits for value', IntRange(0, 5), initwrite=True, readonly=False) + # Note: we have to treat the units properly. + # We got an output of 150 for 10N. The maximal force we want to deal with is 100N, + # thus a maximal output of 1500. 10=150/f offset = Parameter('', FloatRange(-1e5, 1e5), readonly=False, poll=True) - - #Note: we have tro treat the units properly. - """ We got an output of 150 for 10N. The maximal force we want to deal with is 100N, - thus a maximl output of 1500. 10=150/f - """ - scale_factor = Parameter('', FloatRange(-1e5, 1e5, unit='input_units/N'), readonly=False, poll=True) - _target = None - fast_pollfactor = 0.01 + scale_factor = Parameter('', FloatRange(-1e5, 1e5, unit='input_units/N'), readonly=False, poll=True) def query(self, adr, value=None): if value is not None: @@ -78,11 +72,11 @@ class DPM3(HasIodev, Drivable): if 10000 <= round(value * mag, 0) <= 99999: break else: - # not suitable range found + # no suitable range found if absval >= 99999.5: # overrange raise ValueError('%s is out of range' % value) # underrange: take lowest - nibble = '9' if value < 0 else '1' + nibble = '9' if value < 0 else '1' mag = self.MAGNITUDE[nibble] hex_val = nibble + '%05X' % int(round(value * mag, 0)) if hex_val[1:] == '00000': @@ -103,9 +97,9 @@ class DPM3(HasIodev, Drivable): def write_digits(self, value): # value defines the number of digits - back_value=self._iodev.communicate('*1F135%02X\r*1G135' % (value + 1)) - self.digits = int(back_value,16) - 1 - # realculate proper scale and offset + back_value = self._iodev.communicate('*1F135%02X\r*1G135' % (value + 1)) + self.digits = int(back_value, 16) - 1 + # recalculate proper scale and offset self.write_scale_factor(self.scale_factor) self.write_offset(self.offset) return Done @@ -115,44 +109,7 @@ class DPM3(HasIodev, Drivable): return int(back_value,16) - 1 def read_value(self): - value = float(self._iodev.communicate('*1B1')) - mot = self._motor - if self._target is None: - if mot.isBusy(): - self.status = self.Status.IDLE, 'stopping' - else: - self.status = self.Status.IDLE, '' - else: - if self._direction * (self._target - value) > 0: - if self._mot_target != mot.target: - self.stop() - self.status = self.Status.IDLE, 'motor was stopped' - elif not mot.isBusy(): - step = self.step * self._direction - mot_target = mot.value + step - self._mot_target = mot.write_target(mot_target) - else: - self.stop() - self.status = self.Status.IDLE, 'target reached' - return value - - def write_target(self, target): - self._target = target - if target - self.value > 0: - self._direction = 1 - else: - self._direction = -1 - self.status = self.Status.BUSY, 'moving motor' - if self._motor.status[0] == self.Status.ERROR: - self._motor.reset() - self._mot_target = self._motor.target - return target - - @Command() - def stop(self): - self._target = None - self._motor.stop() - self.status = self.Status.IDLE, 'stopped' + return float(self._iodev.communicate('*1B1')) def read_offset(self): reply = self.query(self.OFFSET) diff --git a/secop_psi/simdpm.py b/secop_psi/simdpm.py new file mode 100644 index 0000000..00ff28b --- /dev/null +++ b/secop_psi/simdpm.py @@ -0,0 +1,42 @@ +# -*- coding: utf-8 -*- +# ***************************************************************************** +# +# This program is free software; you can redistribute it and/or modify it under +# the terms of the GNU General Public License as published by the Free Software +# Foundation; either version 2 of the License, or (at your option) any later +# version. +# +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more +# details. +# +# You should have received a copy of the GNU General Public License along with +# this program; if not, write to the Free Software Foundation, Inc., +# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# Module authors: +# M. Zolliker +# +# ***************************************************************************** +"""simulated transducer DPM3 read out""" + +import random +from secop.core import Readable, Parameter, FloatRange, Attached +from secop.lib import clamp + + +class DPM3(Readable): + motor = Attached() + jitter = Parameter('simulated jitter', FloatRange(unit='N'), default=1, readonly=False) + hysteresis = Parameter('simulated hysteresis', FloatRange(unit='deg'), default=100, readonly=False) + friction = Parameter('friction', FloatRange(unit='N/deg'), default=1, readonly=False) + slope = Parameter('slope', FloatRange(unit='N/deg'), default=10, readonly=False) + + _pos = 0 # effective piston position, main hysteresis taken into account + + def read_value(self): + mot = self._motor + self._pos = clamp(self._pos, mot.value - self.hysteresis * 0.5, mot.value + self.hysteresis * 0.5) + return clamp(0, 1, mot.value - self._pos) * self.friction \ + + self._pos * self.slope + self.jitter * (random.random() - random.random()) diff --git a/secop_psi/uniax.py b/secop_psi/uniax.py new file mode 100644 index 0000000..2a98f3c --- /dev/null +++ b/secop_psi/uniax.py @@ -0,0 +1,129 @@ +# -*- coding: utf-8 -*- +# ***************************************************************************** +# +# This program is free software; you can redistribute it and/or modify it under +# the terms of the GNU General Public License as published by the Free Software +# Foundation; either version 2 of the License, or (at your option) any later +# version. +# +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more +# details. +# +# You should have received a copy of the GNU General Public License along with +# this program; if not, write to the Free Software Foundation, Inc., +# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# Module authors: +# M. Zolliker +# +# ***************************************************************************** +"""use transducer and motor to adjust force""" + +import time +from secop.core import Drivable, Parameter, FloatRange, Done, Attached, Command + + +class Uniax(Drivable): + motor = Attached() + transducer = Attached() + step = Parameter('maximum motor step', FloatRange(unit='deg'), default=5, readonly=False) + fine_step = Parameter('motor step for fine adjustment', FloatRange(unit='deg'), default=1, readonly=False) + tolerance = Parameter('force tolerance', FloatRange(), readonly=False, default=1) + filter_interval = Parameter('filter time', FloatRange(0, 60, unit='s'), readonly=False, default=1) + span = Parameter('difference between max and min', FloatRange(), needscfg=False) + pollinterval = 0.1 + fast_pollfactor = 1 + + _target = None + _mot_target = None + _direction = 0 + _last_era = 0 + _cnt = 0 + _sum = 0 + _min = None + _max = None + _last_min = None + _last_max = None + + def read_value(self): + force = self._transducer + mot = self._motor + era = int(time.time() / self.filter_interval) + newera = era != self._last_era + value = force.read_value() + if newera: + self._last_era = era + if self._min is None: + self._min = self._max = self._last_max = self._last_min = value + if self._cnt > 0: + self.value = self._sum / self._cnt + self._sum = self._cnt = 0 + self.span = self._max - self._min + self._last_min = self._min + self._last_max = self._max + self._min = self._max = value + self._min = min(self._min, value) + self._max = max(self._max, value) + self._sum += value + self._cnt += 1 + high_value = max(self._max, self._last_max) + low_value = min(self._min, self._last_min) + if self._target is None: + if mot.isBusy(): + self.status = self.Status.IDLE, 'stopping' + else: + self.status = self.Status.IDLE, '' + return Done + step = 0 + if self._direction > 0: + if high_value < self._target: + step = self.step + else: + if low_value > self._target: + step = -self.step + if not step: + if self._direction * (self._target - value) < self.tolerance: + self.stop() + self.status = self.Status.IDLE, 'target reached' + return Done + if newera: + step = self.fine_step * self._direction + if step and not mot.isBusy(): + mot_target = mot.value + step + self._mot_target = mot.write_target(mot_target) + return Done + + def write_target(self, target): + self._target = target + if target - self.value > 0: + self._direction = 1 + else: + self._direction = -1 + if self._motor.status[0] == self.Status.ERROR: + self._motor.reset() + self.status = self.Status.BUSY, 'changed target' + self._mot_target = self._motor.target + self.read_value() + return target + + @Command() + def stop(self): + self._target = None + self._motor.stop() + self.status = self.Status.IDLE, 'stopped' + + + + + + + + + + + + + +