frappy/secop_psi/dpm.py
Markus Zolliker 09056ea87e set unit together with datatype
Change-Id: I412471880e71792160ee56a79375dc62dd4aaafc
2021-10-06 09:07:09 +02:00

188 lines
6.0 KiB
Python

# -*- 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>
#
# *****************************************************************************
"""transducer DPM3 read out"""
from secop.core import Drivable, Parameter, FloatRange, BoolType, StringIO,\
HasIodev, IntRange, Done, Attached, Command
class DPM3IO(StringIO):
end_of_line = '\r'
timeout = 3
identification = [('*1R135', '01')]
def hex2float(hexvalue, digits):
value = int(hexvalue, 16)
if value >= 0x800000:
value -= 0x1000000
return value / (10 ** digits)
def float2hex(value, digits):
intvalue = int(round(value * 10 ** digits,0))
if intvalue < 0:
intvalue += 0x1000000
return '%06X' % intvalue
class DPM3(HasIodev, Drivable):
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}
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)
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
def query(self, adr, value=None):
if value is not None:
if adr == self.SCALE:
absval = abs(value)
for nibble, mag in self.MAGNITUDE.items():
if 10000 <= round(value * mag, 0) <= 99999:
break
else:
# not 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'
mag = self.MAGNITUDE[nibble]
hex_val = nibble + '%05X' % int(round(value * mag, 0))
if hex_val[1:] == '00000':
raise ValueError('scale factor can not be 0', value)
else:
hex_val = float2hex(value, self.digits)
cmd = '*1F3%02X%s\r' % (adr, hex_val)
else:
cmd = ""
cmd = cmd + '*1G3%02X' % adr
hexvalue = self._iodev.communicate(cmd)
if adr == self.SCALE:
mag = self.MAGNITUDE[hexvalue[0:1]]
value = int(hexvalue[1:], 16)
return value/mag
else:
return hex2float(hexvalue, self.digits)
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
self.write_scale_factor(self.scale_factor)
self.write_offset(self.offset)
return Done
def read_digits(self):
back_value = self._iodev.communicate('*1G135')
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'
def read_offset(self):
reply = self.query(self.OFFSET)
return reply
def write_offset(self, value):
return self.query(self.OFFSET, value)
def read_scale_factor(self):
reply = self.query(self.SCALE)
return float(reply) / 10 ** self.digits
def write_scale_factor(self, value):
reply = self.query(self.SCALE, value * 10 ** self.digits)
return float(reply) / 10 ** self.digits