frappy/secop_ess/epics.py
Enrico Faulhaber 574a66c65b rework EnumType to use better Enum's
unfortunately IntEnum can't be bent like we would need it (extensible).
So we had to write our own....

The members of the Enum still behave like ints, but also have
.name and .value attributes, should they be needed.

needed adoptions to correctly use (and test) the EnumType are included.

Change-Id: Ie019d2f449a244c4fab00554b6c6daaac8948b59
Reviewed-on: https://forge.frm2.tum.de/review/17843
Tested-by: JenkinsCodeReview <bjoern_pedersen@frm2.tum.de>
Reviewed-by: Enrico Faulhaber <enrico.faulhaber@frm2.tum.de>
2018-06-07 17:57:46 +02:00

234 lines
8.8 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:
# Enrico Faulhaber <enrico.faulhaber@frm2.tum.de>
# Erik Dahlbäck <erik.dahlback@esss.se>
#
# *****************************************************************************
from __future__ import absolute_import
from secop.datatypes import EnumType, FloatRange, StringType
from secop.modules import Readable, Drivable, Param
try:
from pvaccess import Channel # import EPIVSv4 functionallity, PV access
except ImportError:
class Channel(object):
def __init__(self, pv_name):
self.pv_name = pv_name
self.value = 0.0
def get(self):
return self
def getDouble(self):
return self.value
def put(self, value):
try:
self.value = value
self.value = float(value)
except (TypeError, ValueError):
pass
try:
from epics import PV
except ImportError:
class PV(object):
def __init__(self, pv_name):
self.pv_name = pv_name
self.value = 0.0
class EpicsReadable(Readable):
"""EpicsDrivable handles a Drivable interfacing to EPICS v4"""
# Commmon parameter for all EPICS devices
parameters = {
'value': Param('EPICS generic value',
datatype=FloatRange(),
default=300.0,),
'epics_version': Param("EPICS version used, v3 or v4",
datatype=EnumType(v3=3, v4=4),),
# 'private' parameters: not remotely accessible
'value_pv': Param('EPICS pv_name of value',
datatype=StringType(),
default="unset", export=False),
'status_pv': Param('EPICS pv_name of status',
datatype=StringType(),
default="unset", export=False),
}
# Generic read and write functions
def _read_pv(self, pv_name):
if self.epics_version == 'v4':
pv_channel = Channel(pv_name)
# TODO: cannot handle read of string (is there a .getText() or
# .getString() ?)
return_value = pv_channel.get().getDouble()
else: # Not EPICS v4
# TODO: fix this, it does not work
pv = PV(pv_name + ".VAL")
return_value = pv.value
return return_value
def _write_pv(self, pv_name, write_value):
#self.log.info('Write value = %s from EPICS PV = %s' %(write_value, pv_name))
# try to convert value to float
try:
write_value = float(write_value)
except (TypeError, ValueError):
# can not convert to float, force to string
write_value = str(write_value)
if self.epics_version == 'v4':
pv_channel = Channel(pv_name)
pv_channel.put(write_value)
else: # Not EPICS v4
pv = PV(pv_name + ".VAL")
pv.value = write_value
def read_value(self, maxage=0):
return self._read_pv(self.value_pv)
def read_status(self, maxage=0):
# XXX: comparison may need to be a little unsharp
# XXX: Hardware may have it's own idea about the status: how to obtain?
if self.status_pv != 'unset':
# XXX: how to map an unknown type+value to an valid status ???
return Drivable.Status.UNKNOWN, self._read_pv(self.status_pv)
# status_pv is unset
return (Drivable.Status.IDLE, 'no pv set')
class EpicsDrivable(Drivable):
"""EpicsDrivable handles a Drivable interfacing to EPICS v4"""
# Commmon parameter for all EPICS devices
parameters = {
'target': Param('EPICS generic target', datatype=FloatRange(),
default=300.0, readonly=False),
'value': Param('EPICS generic value', datatype=FloatRange(),
default=300.0,),
'epics_version': Param("EPICS version used, v3 or v4",
datatype=StringType(),),
# 'private' parameters: not remotely accessible
'target_pv': Param('EPICS pv_name of target', datatype=StringType(),
default="unset", export=False),
'value_pv': Param('EPICS pv_name of value', datatype=StringType(),
default="unset", export=False),
'status_pv': Param('EPICS pv_name of status', datatype=StringType(),
default="unset", export=False),
}
# Generic read and write functions
def _read_pv(self, pv_name):
if self.epics_version == 'v4':
pv_channel = Channel(pv_name)
# TODO: cannot handle read of string (is there a .getText() or
# .getString() ?)
return_value = pv_channel.get().getDouble()
else: # Not EPICS v4
# TODO: fix this, it does not work
pv = PV(pv_name + ".VAL")
return_value = pv.value
return return_value
def _write_pv(self, pv_name, write_value):
#self.log.info('Write value = %s from EPICS PV = %s' %(write_value, pv_name))
# try to convert value to float
try:
write_value = float(write_value)
except (TypeError, ValueError):
# can not convert to float, force to string
write_value = str(write_value)
if self.epics_version == 'v4':
pv_channel = Channel(pv_name)
pv_channel.put(write_value)
else: # Not EPICS v4
pv = PV(pv_name + ".VAL")
pv.value = write_value
def read_target(self, maxage=0):
return self._read_pv(self.target_pv)
def write_target(self, write_value):
self._write_pv(self.target_pv, write_value)
def read_value(self, maxage=0):
return self._read_pv(self.value_pv)
def read_status(self, maxage=0):
# XXX: comparison may need to be a little unsharp
# XXX: Hardware may have it's own idea about the status: how to obtain?
if self.status_pv != 'unset':
# XXX: how to map an unknown type+value to an valid status ???
return Drivable.Status.UNKNOWN, self._read_pv(self.status_pv)
# status_pv is unset, derive status from equality of value + target
if self.read_value() == self.read_target():
return (Drivable.Status.OK, '')
return (Drivable.Status.BUSY, 'Moving')
# """Temperature control loop"""
# should also derive from secop.core.temperaturecontroller, once its
# features are agreed upon
class EpicsTempCtrl(EpicsDrivable):
parameters = {
# TODO: restrict possible values with oneof datatype
'heaterrange': Param('Heater range', datatype=StringType(),
default='Off', readonly=False,),
'tolerance': Param('allowed deviation between value and target',
datatype=FloatRange(1e-6, 1e6), default=0.1,
readonly=False,),
# 'private' parameters: not remotely accessible
'heaterrange_pv': Param('EPICS pv_name of heater range',
datatype=StringType(), default="unset", export=False,),
}
def read_target(self, maxage=0):
return self._read_pv(self.target_pv)
def write_target(self, write_value):
# send target to HW
self._write_pv(self.target_pv, write_value)
# update our status
self.read_status()
def read_value(self, maxage=0):
return self._read_pv(self.value_pv)
def read_status(self, maxage=0):
# XXX: comparison may need to collect a history to detect oscillations
at_target = abs(self.read_value(maxage) - self.read_target(maxage)) \
<= self.tolerance
if at_target:
return (Drivable.Status.OK, 'at Target')
return (Drivable.Status.BUSY, 'Moving')
# TODO: add support for strings over epics pv
# def read_heaterrange(self, maxage=0):
# return self._read_pv(self.heaterrange_pv)
# TODO: add support for strings over epics pv
# def write_heaterrange(self, range_value):
# self._write_pv(self.heaterrange_pv, range_value)