uniax: added force control as seperate module
Change-Id: I8b14fc90f5885834e342f6bc5e5dd0ea711b27ea
This commit is contained in:
41
cfg/sim_uniax.cfg
Normal file
41
cfg/sim_uniax.cfg
Normal file
@ -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"
|
@ -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)
|
||||
|
42
secop_psi/simdpm.py
Normal file
42
secop_psi/simdpm.py
Normal file
@ -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 <markus.zolliker@psi.ch>
|
||||
#
|
||||
# *****************************************************************************
|
||||
"""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())
|
129
secop_psi/uniax.py
Normal file
129
secop_psi/uniax.py
Normal file
@ -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 <markus.zolliker@psi.ch>
|
||||
#
|
||||
# *****************************************************************************
|
||||
"""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'
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
Reference in New Issue
Block a user