This commit is contained in:
2018-04-17 12:05:48 +02:00
parent 14edc0e745
commit 58a1260003
428 changed files with 41350 additions and 477 deletions
+988
View File
@@ -0,0 +1,988 @@
"""
CaChannel class having identical API as of caPython/CaChannel class,
based on PythonCA ( > 1.20.1beta2)
Author: Xiaoqiang Wang
Created: Sep. 22, 2008
Changes:
"""
# python 2 -> 3 compatible layer
import sys
if sys.hexversion >= 0x03000000:
long = int
import ca
ca.cs_never_search = 4
# retrieve numeric waveforms as numpy arrays, default No
USE_NUMPY = False
class CaChannelException(Exception):
def __init__(self, status):
self.status = str(status)
def __str__(self):
return self.status
class CaChannel:
"""CaChannel: A Python class with identical API as of caPython/CaChannel.
This class implements the methods to operate on channel access so that you can find
their C library counterparts ,
http://www.aps.anl.gov/epics/base/R3-14/12-docs/CAref.html#Function.
Therefore an understanding of C API helps much.
To get started easily, convenient methods are created for often used operations,
========== ======
Operation Method
========== ======
connect :meth:`searchw`
read :meth:`getw`
write :meth:`putw`
========== ======
They have shorter names and default arguments. It is recommended to start with these methods.
Study the other C alike methods when necessary.
>>> import CaChannel
>>> chan = CaChannel.CaChannel('catest')
>>> chan.searchw()
>>> chan.putw(12.3)
>>> chan.getw()
12.3
"""
ca_timeout = 3.0
dbr_d = {}
dbr_d[ca.DBR_SHORT] = int
dbr_d[ca.DBR_INT] = int
dbr_d[ca.DBR_LONG] = int
dbr_d[ca.DBR_FLOAT] = float
dbr_d[ca.DBR_DOUBLE]= float
dbr_d[ca.DBR_CHAR] = int
dbr_d[ca.DBR_STRING]= str
dbr_d[ca.DBR_ENUM] = int
def __init__(self, pvName=None):
self.pvname = pvName
self.__chid = None
self.__evid = None
self.__timeout = None
self._field_type = None
self._element_count = None
self._puser = None
self._conn_state = None
self._host_name = None
self._raccess = None
self._waccess = None
self._callbacks={}
def __del__(self):
try:
self.clear_event()
self.clear_channel()
self.flush_io()
except:
pass
def version(self):
return "CaChannel, version v28-03-12"
#
# Class helper methods
#
def setTimeout(self, timeout):
"""Set the timeout for this channel object. It overrides the class timeout.
:param float timeout: timeout in seconds
"""
if (timeout>=0 or timeout is None):
self.__timeout = timeout
else:
raise ValueError
def getTimeout(self):
"""Retrieve the timeout set for this channel object.
:return: timeout in seconds for this channel instance
"""
if self.__timeout is None:
timeout = CaChannel.ca_timeout
else:
timeout = self.__timeout
return timeout
#
# *************** Channel access medthod ***************
#
#
# Connection methods
# search_and_connect
# search
# clear_channel
def search_and_connect(self, pvName, callback, *user_args):
"""Attempt to establish a connection to a process variable.
:param str pvName: process variable name
:param callable callback: function called when connection completes and connection status changes later on.
:param user_args: user provided arguments that are passed to callback when it is invoked.
:raises CaChannelException: if error happens
The user arguments are returned to the user in a tuple in the callback function.
The order of the arguments is preserved.
Each Python callback function is required to have two arguments.
The first argument is a tuple containing the results of the action.
The second argument is a tuple containing any user arguments specified by ``user_args``.
If no arguments were specified then the tuple is empty.
.. note:: All remote operation requests such as the above are accumulated (buffered)
and not forwarded to the IOC until one of execution methods (:meth:`pend_io`, :meth:`poll`, :meth:`pend_event`, :meth:`flush_io`)
is called. This allows several requests to be efficiently sent over the network in one message.
>>> chan = CaChannel('catest')
>>> def connCB(epicsArgs, userArgs):
... chid = epicsArgs[0]
... connection_state = epicsArgs[1]
... if connection_state == ca.CA_OP_CONN_UP:
... print('%s is connected' % ca.name(chid))
>>> chan.search_and_connect(None, connCB, chan)
>>> status = chan.pend_event(2)
catest is connected
"""
if pvName is None:
pvName = self.pvname
else:
self.pvname = pvName
self._callbacks['connCB']=(callback, user_args)
try:
self.__chid = ca.search(pvName, self._conn_callback)
except ca.error:
msg = sys.exc_info()[1]
raise CaChannelException(msg)
def search(self, pvName=None):
"""Attempt to establish a connection to a process variable.
:param str pvName: process variable name
:raises CaChannelException: if error happens
.. note:: All remote operation requests such as the above are accumulated (buffered)
and not forwarded to the IOC until one of execution methods (:meth:`pend_io`, :meth:`poll`, :meth:`pend_event`, :meth:`flush_io`)
is called. This allows several requests to be efficiently sent over the network in one message.
>>> chan = CaChannel()
>>> chan.search('catest')
>>> status = chan.pend_io(1)
>>> chan.state()
2
"""
if pvName is None:
pvName = self.pvname
else:
self.pvname = pvName
try:
self.__chid = ca.search(pvName, None)
except ca.error:
msg = sys.exc_info()[1]
raise CaChannelException(msg)
def clear_channel(self):
"""Close a channel created by one of the search functions.
Clearing a channel does not cause its connection handler to be called.
Clearing a channel does remove any monitors registered for that channel.
If the channel is currently connected then resources are freed only some
time after this request is flushed out to the server.
.. note:: All remote operation requests such as the above are accumulated (buffered)
and not forwarded to the IOC until one of execution methods (:meth:`pend_io`, :meth:`poll`, :meth:`pend_event`, :meth:`flush_io`)
is called. This allows several requests to be efficiently sent over the network in one message.
"""
if(self.__chid is not None):
try:
ca.clear(self.__chid)
except ca.error:
msg = sys.exc_info()[1]
raise CaChannelException(msg)
#
# Write methods
# array_put
# array_put_callback
#
def _setup_put(self,value, req_type, count = None):
if count is None:
count = self.element_count()
else:
count = max(1, min(self.element_count(), count) )
if req_type == -1:
req_type = self.field_type()
# single numeric value
if (isinstance(value, int) or
isinstance(value, long) or
isinstance(value, float) or
isinstance(value, bool)):
pval = (CaChannel.dbr_d[req_type](value),)
# single string value
# if DBR_CHAR, split into chars
# otherwise convert to field type
elif isinstance(value, str):
if req_type == ca.DBR_CHAR:
if len(value) < count:
count = len(value)
pval = [ord(x) for x in value[:count]]
else:
pval = (CaChannel.dbr_d[req_type](value),)
# assumes other sequence type
else:
if len(value) < count:
count = len(value)
pval = [CaChannel.dbr_d[req_type](x) for x in value[:count]]
return pval
def array_put(self, value, req_type=None, count=None):
"""Write a value or array of values to a channel
:param value: data to be written. For multiple values use a list or tuple
:param req_type: database request type (``ca.DBR_XXXX``). Defaults to be the native data type.
:param int count: number of data values to write. Defaults to be the native count.
>>> chan = CaChannel('catest')
>>> chan.searchw()
>>> chan.array_put(123)
>>> chan.flush_io()
>>> chan.getw()
123.0
>>> chan = CaChannel('cabo')
>>> chan.searchw()
>>> chan.array_put('Busy', ca.DBR_STRING)
>>> chan.flush_io()
>>> chan.getw()
1
>>> chan = CaChannel('cawave')
>>> chan.searchw()
>>> chan.array_put([1,2,3])
>>> chan.flush_io()
>>> chan.getw()
[1.0, 2.0, 3.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]
>>> chan.getw(count=3, use_numpy=True)
array([ 1., 2., 3.])
>>> chan = CaChannel('cawavec')
>>> chan.searchw()
>>> chan.array_put('1234',count=3)
>>> chan.flush_io()
>>> chan.getw(count=4)
[49, 50, 51, 0]
"""
if req_type is None: req_type = -1
val = self._setup_put(value, req_type, count)
try:
ca.put(self.__chid, val, None, None, req_type)
except ca.error:
msg = sys.exc_info()[1]
raise CaChannelException(msg)
def array_put_callback(self, value, req_type, count, callback, *user_args):
"""Write a value or array of values to a channel and execute the user
supplied callback after the put has completed.
:param value: data to be written. For multiple values use a list or tuple.
:param req_type: database request type (``ca.DBR_XXXX``). Defaults to be the native data type.
:param int count: number of data values to write, Defaults to be the native count.
:param callable callback: function called when the write is completed.
:param user_args: user provided arguments that are passed to callback when it is invoked.
:raises CaChannelException: if error happens
Each Python callback function is required to have two arguments.
The first argument is a dictionary containing the results of the action.
======= ===== =======
field type comment
======= ===== =======
chid int channels id structure
type int database request type (ca.DBR_XXXX)
count int number of values to transfered
status int CA status return code (ca.ECA_XXXX)
======= ===== =======
The second argument is a tuple containing any user arguments specified by ``user_args``.
If no arguments were specified then the tuple is empty.
>>> def putCB(epicsArgs, userArgs):
... print('%s put completed' % ca.name(epicsArgs['chid']))
>>> chan = CaChannel('catest')
>>> chan.searchw()
>>> chan.array_put_callback(145, None, None, putCB)
>>> status = chan.pend_event(1)
catest put completed
>>> chan = CaChannel('cabo')
>>> chan.searchw()
>>> chan.array_put_callback('Busy', ca.DBR_STRING, None, putCB)
>>> status = chan.pend_event(1)
cabo put completed
>>> chan = CaChannel('cawave')
>>> chan.searchw()
>>> chan.array_put_callback([1,2,3], None, None, putCB)
>>> status = chan.pend_event(1)
cawave put completed
>>> chan = CaChannel('cawavec')
>>> chan.searchw()
>>> chan.array_put_callback('123', None, None, putCB)
>>> status = chan.pend_event(1)
cawavec put completed
"""
if req_type is None: req_type = 0
val = self._setup_put(value, req_type, count)
self._callbacks['putCB']=(callback, user_args)
try:
ca.put(self.__chid, val, None, self._put_callback, req_type)
except ca.error:
msg = sys.exc_info()[1]
raise CaChannelException(msg)
#
# Read methods
# getValue
# array_get
# array_get_callback
#
# Obtain read value after ECA_NORMAL is returned on an array_get().
def getValue(self):
"""Return the value(s) after array_get has completed"""
return self.val
# Simulate with a synchronous getw function call
def array_get(self, req_type=None, count=None, **keywords):
"""Read a value or array of values from a channel. The new value is
retrieved by a call to getValue method.
:param req_type: database request type (``ca.DBR_XXXX``). Defaults to be the native data type.
:param int count: number of data values to read, Defaults to be the native count.
:param keywords: optional arguments assigned by keywords
=========== =====
keyword value
=========== =====
use_numpy True if waveform should be returned as numpy array. Default :data:`CaChannel.USE_NUMPY`.
=========== =====
:raises CaChannelException: if error happens
.. note:: All remote operation requests such as the above are accumulated (buffered)
and not forwarded to the IOC until one of execution methods (``pend_io``, ``poll``, ``pend_event``, ``flush_io``)
is called. This allows several requests to be efficiently sent over the network in one message.
>>> chan = CaChannel('catest')
>>> chan.searchw()
>>> chan.putw(123)
>>> chan.array_get()
>>> chan.getValue()
123.0
"""
self.val = self.getw(req_type, count, **keywords)
def array_get_callback(self, req_type, count, callback, *user_args, **keywords):
"""Read a value or array of values from a channel and execute the user
supplied callback after the get has completed.
:param req_type: database request type (``ca.DBR_XXXX``). Defaults to be the native data type.
:param int count: number of data values to read, Defaults to be the native count.
:param callable callback: function called when the get is completed.
:param user_args: user provided arguments that are passed to callback when it is invoked.
:param keywords: optional arguments assigned by keywords
=========== =====
keyword value
=========== =====
use_numpy True if waveform should be returned as numpy array. Default :data:`CaChannel.USE_NUMPY`.
=========== =====
:raises CaChannelException: if error happens
Each Python callback function is required to have two arguments.
The first argument is a dictionary containing the results of the action.
+-----------------+---------------+------------------------------------+-------------------------+---------------+-------------+---------------+
| field | type | comment | request type |
| | | +----------+--------------+---------------+-------------+---------------+
| | | | DBR_XXXX | DBR_STS_XXXX | DBR_TIME_XXXX | DBR_GR_XXXX | DBR_CTRL_XXXX |
+=================+===============+====================================+==========+==============+===============+=============+===============+
| chid | int | channels id number | X | X | X | X | X |
+-----------------+---------------+------------------------------------+----------+--------------+---------------+-------------+---------------+
| type | int | database request type | X | X | X | X | X |
| | | (ca.DBR_XXXX) | | | | | |
+-----------------+---------------+------------------------------------+----------+--------------+---------------+-------------+---------------+
| count | int | number of values to transfered | X | X | X | X | X |
+-----------------+---------------+------------------------------------+----------+--------------+---------------+-------------+---------------+
| status | int | CA status return code | X | X | X | X | X |
| | | (ca.ECA_XXXX) | | | | | |
+-----------------+---------------+------------------------------------+----------+--------------+---------------+-------------+---------------+
| pv_value | | PV value | X | X | X | X | X |
+-----------------+---------------+------------------------------------+----------+--------------+---------------+-------------+---------------+
| pv_status | int | PV alarm status | | X | X | X | X |
+-----------------+---------------+------------------------------------+----------+--------------+---------------+-------------+---------------+
| pv_severity | int | PV alarm severity | | X | X | X | X |
+-----------------+---------------+------------------------------------+----------+--------------+---------------+-------------+---------------+
| pv_seconds | float | timestamp | | | X | | |
+-----------------+---------------+------------------------------------+----------+--------------+---------------+-------------+---------------+
| pv_nostrings | int | ENUM PV's number of states | | | | X | X |
+-----------------+---------------+------------------------------------+----------+--------------+---------------+-------------+---------------+
| pv_statestrings | string list | ENUM PV's states string | | | | X | X |
+-----------------+---------------+------------------------------------+----------+--------------+---------------+-------------+---------------+
| pv_units | string | units | | | | X | X |
+-----------------+---------------+------------------------------------+----------+--------------+---------------+-------------+---------------+
| pv_precision | int | precision | | | | X | X |
+-----------------+---------------+------------------------------------+----------+--------------+---------------+-------------+---------------+
| pv_updislim | float | upper display limit | | | | X | X |
+-----------------+---------------+------------------------------------+----------+--------------+---------------+-------------+---------------+
| pv_lodislim | float | lower display limit | | | | X | X |
+-----------------+---------------+------------------------------------+----------+--------------+---------------+-------------+---------------+
| pv_upalarmlim | float | upper alarm limit | | | | X | X |
+-----------------+---------------+------------------------------------+----------+--------------+---------------+-------------+---------------+
| pv_upwarnlim | float | upper warning limit | | | | X | X |
+-----------------+---------------+------------------------------------+----------+--------------+---------------+-------------+---------------+
| pv_loalarmlim | float | lower alarm limit | | | | X | X |
+-----------------+---------------+------------------------------------+----------+--------------+---------------+-------------+---------------+
| pv_lowarnlim | float | lower warning limit | | | | X | X |
+-----------------+---------------+------------------------------------+----------+--------------+---------------+-------------+---------------+
| pv_upctrllim | float | upper control limit | | | | | X |
+-----------------+---------------+------------------------------------+----------+--------------+---------------+-------------+---------------+
| pv_loctrllim | float | lower control limit | | | | | X |
+-----------------+---------------+------------------------------------+----------+--------------+---------------+-------------+---------------+
The second argument is a tuple containing any user arguments specified by ``user_args``.
If no arguments were specified then the tuple is empty.
.. note:: All remote operation requests such as the above are accumulated (buffered)
and not forwarded to the IOC until one of execution methods (``pend_io``, ``poll``, ``pend_event``, ``flush_io``)
is called. This allows several requests to be efficiently sent over the network in one message.
>>> def getCB(epicsArgs, userArgs):
... for item in sorted(epicsArgs.keys()):
... if item.startswith('pv_'):
... print('%s %s' % (item,epicsArgs[item]))
>>> chan = CaChannel('catest')
>>> chan.searchw()
>>> chan.putw(145)
>>> chan.array_get_callback(ca.DBR_CTRL_DOUBLE, 1, getCB)
>>> status = chan.pend_event(1)
pv_loalarmlim -20.0
pv_loctrllim 0.0
pv_lodislim 0.0
pv_lowarnlim -10.0
pv_precision 3
pv_severity 2
pv_status 3
pv_units mm
pv_upalarmlim 20.0
pv_upctrllim 0.0
pv_updislim 0.0
pv_upwarnlim 10.0
pv_value 145.0
>>> chan = CaChannel('cabo')
>>> chan.searchw()
>>> chan.putw(0)
>>> chan.array_get_callback(ca.DBR_CTRL_ENUM, 1, getCB)
>>> status = chan.pend_event(1)
pv_nostrings 2
pv_severity 0
pv_statestrings ('Done', 'Busy')
pv_status 0
pv_value 0
"""
if req_type is None: req_type = ca.dbf_type_to_DBR(self.field_type())
if count is None: count = self.element_count()
self._callbacks['getCB']=(callback, user_args)
try:
ca.get(self.__chid, self._get_callback, req_type, count, keywords.get('use_numpy', USE_NUMPY))
except ca.error:
msg = sys.exc_info()[1]
raise CaChannelException(msg)
#
# Monitor methods
# add_masked_array_event
# clear_event
#
# Creates a new event id and stores it on self.__evid. Only one event registered
# per CaChannel object. If an event is already registered the event is cleared
# before registering a new event.
def add_masked_array_event(self, req_type, count, mask, callback, *user_args, **keywords):
"""Specify a callback function to be executed whenever changes occur to a PV.
:param req_type: database request type (``ca.DBR_XXXX``). Defaults to be the native data type.
:param int count: number of data values to read, Defaults to be the native count.
:param mask: logical or of ``ca.DBE_VALUE``, ``ca.DBE_LOG``, ``ca.DBE_ALARM``.
Defaults to be ``ca.DBE_VALUE|ca.DBE_ALARM``.
:param callable callback: function called when the get is completed.
:param user_args: user provided arguments that are passed to callback when
it is invoked.
:param keywords: optional arguments assigned by keywords
=========== =====
keyword value
=========== =====
use_numpy True if waveform should be returned as numpy array. Default :data:`CaChannel.USE_NUMPY`.
=========== =====
:raises CaChannelException: if error happens
.. note:: All remote operation requests such as the above are accumulated (buffered)
and not forwarded to the IOC until one of execution methods (:meth:`pend_io`, :meth:`poll`, :meth:`pend_event`, :meth:`flush_io`)
is called. This allows several requests to be efficiently sent over the network in one message.
>>> def eventCB(epicsArgs, userArgs):
... print('pv_value %s' % epicsArgs['pv_value'])
... print('pv_status %d %s' % (epicsArgs['pv_status'], ca.alarmStatusString(epicsArgs['pv_status'])))
... print('pv_severity %d %s' % (epicsArgs['pv_severity'], ca.alarmSeverityString(epicsArgs['pv_severity'])))
>>> chan = CaChannel('cabo')
>>> chan.searchw()
>>> chan.putw(1)
>>> chan.add_masked_array_event(ca.DBR_STS_ENUM, None, None, eventCB)
>>> status = chan.pend_event(1)
pv_value 1
pv_status 7 STATE
pv_severity 1 MINOR
>>> chan.clear_event()
>>> chan.add_masked_array_event(ca.DBR_STS_STRING, None, None, eventCB)
>>> status = chan.pend_event(1)
pv_value Busy
pv_status 7 STATE
pv_severity 1 MINOR
>>> chan.clear_event()
"""
if req_type is None: req_type = ca.dbf_type_to_DBR(self.field_type())
if count is None: count = self.element_count()
if mask is None: mask = ca.DBE_VALUE|ca.DBE_ALARM
if self.__evid is not None:
self.clear_event()
self.flush_io()
self._callbacks['eventCB']=(callback, user_args)
try:
self.__evid = ca.monitor(self.__chid, self._event_callback, count, mask, req_type, keywords.get('use_numpy', USE_NUMPY))
except ca.error:
msg = sys.exc_info()[1]
raise CaChannelException(msg)
def clear_event(self):
"""Remove previously installed callback function.
.. note:: All remote operation requests such as the above are accumulated (buffered)
and not forwarded to the IOC until one of execution methods (:meth:`pend_io`, :meth:`poll`, :meth:`pend_event`, :meth:`flush_io`)
is called. This allows several requests to be efficiently sent over the network in one message.
"""
if self.__evid is not None:
try:
ca.clear_monitor(self.__evid)
self.__evid = None
except ca.error:
msg = sys.exc_info()[1]
raise CaChannelException(msg)
#
# Execute methods
# pend_io
# pend_event
# poll
# flush_io
#
def pend_io(self,timeout=None):
"""Flush the send buffer and wait until outstanding queries (``search``, ``array_get``) complete
or the specified timeout expires.
:param float timeout: seconds to wait
:raises CaChannelException: if timeout or other error happens
"""
if timeout is None:
timeout = self.getTimeout()
status = ca.pend_io(float(timeout))
if status != 0:
raise CaChannelException(ca.caError._caErrorMsg[status])
def pend_event(self,timeout=None):
"""Flush the send buffer and process background activity (connect/get/put/monitor callbacks) for ``timeout`` seconds.
It will not return before the specified timeout expires and all unfinished channel access labor has been processed.
:param float timeout: seconds to wait
"""
if timeout is None:
timeout = 0.1
status = ca.pend_event(timeout)
# status is always ECA_TIMEOUT
return status
def poll(self):
"""Flush the send buffer and execute any outstanding background activity.
.. note:: It is an alias to ``pend_event(1e-12)``.
"""
status = ca.poll()
# status is always ECA_TIMEOUT
return status
def flush_io(self):
"""Flush the send buffer and does not execute outstanding background activity."""
status = ca.flush()
if status != 0:
raise CaChannelException(ca.caError._caErrorMsg[status])
#
# Channel Access Macros
# field_type
# element_count
# name
# state
# host_name
# read_access
# write_access
#
def get_info(self):
try:
info=(self._field_type, self._element_count, self._puser,
self._conn_state, self._host_name, self._raccess,
self._waccess) = ca.ch_info(self.__chid)
except ca.error:
msg = sys.exc_info()[1]
raise CaChannelException(msg)
return info
def field_type(self):
"""Native type of the PV in the server (``ca.DBF_XXXX``).
>>> chan = CaChannel('catest')
>>> chan.searchw()
>>> ftype = chan.field_type()
>>> ftype
6
>>> ca.dbf_text(ftype)
'DBF_DOUBLE'
>>> ca.DBF_DOUBLE == ftype
True
"""
self.get_info()
return self._field_type
def element_count(self):
"""Maximum array element count of the PV in the server.
>>> chan = CaChannel('catest')
>>> chan.searchw()
>>> chan.element_count()
1
"""
self.get_info()
return self._element_count
def name(self):
"""Channel name specified when the channel was created.
>>> chan = CaChannel('catest')
>>> chan.searchw()
>>> chan.name()
'catest'
"""
return ca.name(self.__chid)
def state(self):
"""Current state of the CA connection.
================== =============
States Meaning
================== =============
ca.cs_never_conn PV not found
ca.cs_prev_conn PV was found but unavailable
ca.cs_conn PV was found and available
ca.cs_closed PV not closed
ca.cs_never_search PV not searched yet
================== =============
>>> chan = CaChannel('catest')
>>> chan.searchw()
>>> chan.state()
2
"""
if self.__chid is None:
return ca.cs_never_search
else:
self.get_info()
return self._conn_state
def host_name(self):
"""Host name that hosts the process variable."""
self.get_info()
return self._host_name
def read_access(self):
"""Access right to read the channel.
:return: True if the channel can be read, False otherwise.
"""
self.get_info()
return self._raccess
def write_access(self):
"""Access right to write the channel.
:return: True if the channel can be written, False otherwise.
"""
self.get_info()
return self._waccess
#
# Wait functions
#
# These functions wait for completion of the requested action.
def searchw(self, pvName=None):
"""Attempt to establish a connection to a process variable.
:param str pvName: process variable name
:raises CaChannelException: if timeout or error happens
.. note:: This method waits for connection to be established or fail with exception.
>>> chan = CaChannel('non-exist-channel')
>>> chan.searchw()
Traceback (most recent call last):
...
CaChannelException: User specified timeout on IO operation expired
"""
if pvName is None:
pvName = self.pvname
else:
self.pvname = pvName
self.__chid = ca.search(pvName, None)
timeout = self.getTimeout()
status = ca.pend_io(timeout)
if status != 0:
raise CaChannelException(ca.caError._caErrorMsg[status])
def putw(self, value, req_type=None):
"""Write a value or array of values to a channel
If the request type is omitted the data is written as the Python type corresponding to the native format.
Multi-element data is specified as a tuple or a list.
Internally the sequence is converted to a list before inserting the values into a C array.
Access using non-numerical types is restricted to the first element in the data field.
Mixing character types with numerical types writes bogus results but is not prohibited at this time.
DBF_ENUM fields can be written using DBR_ENUM and DBR_STRING types.
DBR_STRING writes of a field of type DBF_ENUM must be accompanied by a valid string out of the possible enumerated values.
:param value: data to be written. For multiple values use a list or tuple
:param req_type: database request type (``ca.DBR_XXXX``). Defaults to be the native data type.
:raises CaChannelException: if timeout or error happens
.. note:: This method does flush the request to the channel access server.
>>> chan = CaChannel('catest')
>>> chan.searchw()
>>> chan.putw(145)
>>> chan.getw()
145.0
>>> chan = CaChannel('cabo')
>>> chan.searchw()
>>> chan.putw('Busy', ca.DBR_STRING)
>>> chan.getw()
1
>>> chan.getw(ca.DBR_STRING)
'Busy'
>>> chan = CaChannel('cawave')
>>> chan.searchw()
>>> chan.putw([1,2,3])
>>> chan.getw(req_type=ca.DBR_LONG,count=4)
[1, 2, 3, 0]
>>> chan = CaChannel('cawavec')
>>> chan.searchw()
>>> chan.putw('123')
>>> chan.getw(count=4)
[49, 50, 51, 0]
>>> chan = CaChannel('cawaves')
>>> chan.searchw()
>>> chan.putw(['string 1','string 2'])
>>> chan.getw()
['string 1', 'string 2', '']
"""
if req_type is None: req_type = -1
val = self._setup_put(value, req_type)
try:
ca.put(self.__chid, val, None, None, req_type)
except ca.error:
msg = sys.exc_info()[1]
raise CaChannelException(msg)
self.flush_io()
def getw(self, req_type=None, count=None, **keywords):
"""Read the value from a channel.
:param req_type: database request type. Defaults to be the native data type.
:param int count: number of data values to read, Defaults to be the native count.
:param keywords: optional arguments assigned by keywords
=========== =====
keyword value
=========== =====
use_numpy True if waveform should be returned as numpy array. Default :data:`CaChannel.USE_NUMPY`.
=========== =====
:return: If req_type is plain request type, only the value is returned. Otherwise a dict returns
with information depending on the request type, same as the first argument passed to user's callback.
See :meth:`array_get_callback`.
:raises CaChannelException: if timeout error happens
If the request type is omitted the data is returned to the user as the Python type corresponding to the native format.
Multi-element data has all the elements returned as items in a list and must be accessed using a numerical type.
Access using non-numerical types is restricted to the first element in the data field.
DBF_ENUM fields can be read using DBR_ENUM and DBR_STRING types.
DBR_STRING reads of a field of type DBF_ENUM returns the string corresponding to the current enumerated value.
"""
updated = [False]
value = [0]
def update_value(args):
if args is None:
return
try:
value[0] = self._format_cb_args(args)
finally:
updated[0] = True
if req_type is None: req_type = ca.dbf_type_to_DBR(self.field_type())
if count is None: count = self.element_count()
try:
ca.get(self.__chid, update_value, req_type, count, keywords.get('use_numpy', USE_NUMPY))
except ca.error:
msg = sys.exc_info()[1]
raise CaChannelException(msg)
timeout = self.getTimeout()
self.flush_io()
n = timeout / 0.001
while n > 0 and not updated[0]:
ca.pend_event(0.001)
n-=1
if not updated[0]:
raise CaChannelException(ca.caError._caErrorMsg[10]) # ECA_TIMEOUT
if ca.dbr_type_is_plain(req_type):
return value[0]['pv_value']
else:
return value[0]
#
# Callback functions
#
# These functions hook user supplied callback functions to CA extension
def _conn_callback(self):
callback = self._callbacks.get('connCB')
if callback is None:
return
callbackFunc, userArgs = callback
if self.state() == 2: OP = 6
else: OP = 7
epicsArgs = (self.__chid, OP)
try:
callbackFunc(epicsArgs, userArgs)
except:
pass
def _put_callback(self, args):
callback = self._callbacks.get('putCB')
if callback is None:
return
callbackFunc, userArgs = callback
epicsArgs={}
epicsArgs['chid']=self.__chid
epicsArgs['type']=self.field_type()
epicsArgs['count']=self.element_count()
epicsArgs['status']=args[1]
try:
callbackFunc(epicsArgs, userArgs)
except:
pass
def _get_callback(self, args):
callback = self._callbacks.get('getCB')
if callback is None:
return
callbackFunc, userArgs = callback
epicsArgs = self._format_cb_args(args)
try:
callbackFunc(epicsArgs, userArgs)
except:
pass
def _event_callback(self, args):
callback = self._callbacks.get('eventCB')
if callback is None:
return
callbackFunc, userArgs = callback
epicsArgs = self._format_cb_args(args)
try:
callbackFunc(epicsArgs, userArgs)
except:
pass
def _format_cb_args(self, args):
epicsArgs={}
epicsArgs['chid'] = self.__chid
# dbr_type is not returned
# use dbf_type instead
epicsArgs['type'] = self.field_type()
epicsArgs['count'] = self.element_count()
# status flag is not returned,
# args[1] is alarm status
# assume ECA_NORMAL
epicsArgs['status'] = 1
if len(args)==2: # Error
epicsArgs['pv_value'] = args[0] # always None
epicsArgs['status'] = args[1]
if len(args)>=3: # DBR_Plain
epicsArgs['pv_value'] = args[0]
epicsArgs['pv_severity']= args[1]
epicsArgs['pv_status'] = args[2]
if len(args)==4: # DBR_TIME, 0.0 for others
epicsArgs['pv_seconds'] = args[3]
if len(args)==5:
if len(args[4])==2: # DBR_CTRL_ENUM
epicsArgs['pv_nostrings'] = args[4][0]
epicsArgs['pv_statestrings']= args[4][1]
if len(args[4])>=7: # DBR_GR
epicsArgs['pv_units'] = args[4][0]
epicsArgs['pv_updislim'] = args[4][1]
epicsArgs['pv_lodislim'] = args[4][2]
epicsArgs['pv_upalarmlim'] = args[4][3]
epicsArgs['pv_upwarnlim'] = args[4][4]
epicsArgs['pv_loalarmlim'] = args[4][5]
epicsArgs['pv_lowarnlim'] = args[4][6]
if len(args[4])==8: # DBR_GR_FLOAT or DBR_GR_DOUBLE
epicsArgs['pv_precision'] = args[4][7]
if len(args[4])>=9: # DBR_CTRL
epicsArgs['pv_upctrllim'] = args[4][7]
epicsArgs['pv_loctrllim'] = args[4][8]
if len(args[4])==10: # DBR_CTRL_FLOAT or DBR_CTRL_DOUBLE
epicsArgs['pv_precision'] = args[4][9]
return epicsArgs
if __name__ == "__main__":
import doctest
doctest.testmod()
View File
+212
View File
@@ -0,0 +1,212 @@
import sys
import traceback
import java.lang.System
import java.lang.Thread
import java.lang.InterruptedException
import gov.aps.jca.CAStatus
import gov.aps.jca.JCALibrary
import gov.aps.jca.configuration.DefaultConfiguration
import gov.aps.jca.dbr.DBRType
import gov.aps.jca.dbr.Severity
import gov.aps.jca.event.PutListener
import gov.aps.jca.event.GetListener
import gov.aps.jca.event.MonitorListener
import ch.psi.jcae.impl.JcaeProperties
version = "PShell wrapper"
release = "1.0.0"
revision = "1.0.0"
error = Exception
def dbf_type_is_valid(type):
return (type >= 0) and (type <= LAST_TYPE)
def dbr_type_is_valid(type):
return (type >= 0) and (type <= LAST_BUFFER_TYPE)
def dbr_type_is_plain(type):
return (type >= DBR_STRING) and (type <= DBR_DOUBLE)
def dbr_type_is_STS(type):
return (type >= DBR_STS_STRING) and (type <= DBR_STS_DOUBLE)
def dbr_type_is_TIME(type):
return (type >= DBR_TIME_STRING) and (type <= DBR_TIME_DOUBLE)
def dbr_type_is_GR(type):
return (type >= DBR_GR_STRING) and (type <= DBR_GR_DOUBLE)
def dbr_type_is_CTRL(type):
return (type >= DBR_CTRL_STRING) and (type <= DBR_CTRL_DOUBLE)
def dbr_type_is_STRING(type):
return (type >= 0) and (type <= LAST_BUFFER_TYPE) and (type%(LAST_TYPE+1) == DBR_STRING)
def dbr_type_is_SHORT(type):
return (type >= 0) and (type <= LAST_BUFFER_TYPE) and (type%(LAST_TYPE+1) == DBR_SHORT)
def dbr_type_is_FLOAT(type):
return (type >= 0) and (type <= LAST_BUFFER_TYPE) and (type%(LAST_TYPE+1) == DBR_FLOAT)
def dbr_type_is_ENUM(type):
return (type >= 0) and (type <= LAST_BUFFER_TYPE) and (type%(LAST_TYPE+1) == DBR_ENUM)
def dbr_type_is_CHAR(type):
return (type >= 0) and (type <= LAST_BUFFER_TYPE) and (type%(LAST_TYPE+1) == DBR_CHAR)
def dbr_type_is_LONG(type):
return (type >= 0) and (type <= LAST_BUFFER_TYPE) and (type%(LAST_TYPE+1) == DBR_LONG)
def dbr_type_is_DOUBLE(type):
return (type >= 0) and (type <= LAST_BUFFER_TYPE) and (type%(LAST_TYPE+1) == DBR_DOUBLE)
#I am assuming have JCAE, but it is possible to implement over JCA only, prividing the configuration
properties = ch.psi.jcae.impl.JcaeProperties.getInstance()
jca= gov.aps.jca.JCALibrary.getInstance()
context = None
configuration = gov.aps.jca.configuration.DefaultConfiguration("jython ca context")
configuration.setAttribute("class", gov.aps.jca.JCALibrary.CHANNEL_ACCESS_JAVA)
configuration.setAttribute("addr_list", properties.getAddressList())
configuration.setAttribute("auto_addr_list", str(properties.isAutoAddressList()));
if properties.getMaxArrayBytes() is not None:
configuration.setAttribute("max_array_bytes", properties.getMaxArrayBytes())
if properties.getServerPort() is not None:
configuration.setAttribute("server_port", properties.getServerPort())
def initialize():
global jca
global context
global configuration
if context is not None:
context.destroy()
context= jca.createContext(configuration)
initialize()
class PutListener(gov.aps.jca.event.PutListener):
def __init__(self, callback):
self.callback = callback
def putCompleted(self, put_ev):
if put_ev is None:
self.callback(None , None , None)
else:
count = put_ev.getCount()
status = put_ev.getStatus()
dbr_type = put_ev.getType()
self.callback([count, status, dbr_type]) #TODO: Check these: status must be second par
def formatCbArgs(status, dbr):
dbrType = dbr.getType()
if status <> gov.aps.jca.CAStatus.NORMAL:
cb_args=[None, status.getValue()]
else:
try:
val = dbr.getValue()
if val is not None:
val = val.tolist()
if len(val) == 1:
val = val[0]
if dbr.isSTS():
cb_args = [val, dbr.getSeverity().getValue(), status.getValue()]
else:
cb_args = [val, gov.aps.jca.dbr.Severity.NO_ALARM, status.getValue()]
if dbr.isTIME():
timestamp = dbr.getTimeStamp()
cb_args.append( timestamp.nsec() if (timestamp is not None) else 0.0)
else:
cb_args.append(0.0)
if dbr.isENUM():
cb_args.append([None, None]) #TODO
elif dbr.isGR():
gr=[dbr.getUnits(), dbr.getUpperDispLimit(), dbr.getLowerDispLimit(),
dbr.getUpperAlarmLimit(), dbr.getUpperWarningLimit(),
dbr.getLowerAlarmLimit(), dbr.getLowerWarningLimit()]
if (dbr.isCTRL()):
gr.append(dbr.getUpperCtrlLimit())
gr.append(dbr.getLowerCtrlLimit())
if (dbr.isPRECSION()):
gr.append(dbr.getPrecision())
cb_args.append(gr)
except:
traceback.print_exc(file=sys.stderr)
cb_args=[None, None]
return cb_args
class GetListener(gov.aps.jca.event.GetListener):
def __init__(self, callback):
self.callback = callback
def getCompleted(self, get_ev):
status = get_ev.getStatus()
dbr = get_ev.getDBR()
self.callback(formatCbArgs(status, dbr))
class MonitorListener(gov.aps.jca.event.MonitorListener):
def __init__(self, callback):
self.callback = callback
def monitorChanged(self, monitor_ev):
status = monitor_ev.getStatus()
dbr = monitor_ev.getDBR()
self.callback(formatCbArgs(status, dbr))
#print dbr.getValue()
def search(name, callback): #returns channel
ch= context.createChannel(name)
context.pendIO(1.0)
return ch
def name(channel):
return channel.getName()
def clear(channel):
channel.destroy()
flush()
def put(channel, val, not_used, put_callback, req_type):
type = gov.aps.jca.dbr.DBRType.forValue(req_type)
if put_callback is not None:
listener = PutListener(put_callback)
channel.put(val, listener)
else:
channel.put(val)
flush()
def get(channel, get_callback, req_type, count, *args):
listener = GetListener(get_callback)
type = gov.aps.jca.dbr.DBRType.forValue(req_type)
channel.get(type, count, listener)
flush()
def monitor(channel, event_callback, count, mask, req_type, *args):
listener = MonitorListener(event_callback)
type = gov.aps.jca.dbr.DBRType.forValue(req_type)
monitor = channel.addMonitor(type, count, mask, listener)
flush()
return monitor
def clear_monitor(event_id):
event_id.removeMonitorListener(event_id.getMonitorListener())
flush()
def ch_info(channel):
return (channel.getFieldType().getValue(), channel.getElementCount() ,
None, channel.getConnectionState().getValue(), channel.getHostName(),
channel.getReadAccess() , channel.getWriteAccess())
def pend_io(timeout):
context.pendIO(timeout)
_checkInterrupted()
return 0 #for OK
def pend_event(timeout):
context.pendEvent(timeout)
_checkInterrupted()
return 80 #C library always returns ECA_TIMEOUT
def poll():
return pend_event(1e-12)
def flush():
context.flushIO()
_checkInterrupted()
return 0
def _checkInterrupted():
java.lang.Thread.currentThread().sleep(0)
if java.lang.Thread.currentThread().isInterrupted():
raise java.lang.InterruptedException()
+88
View File
@@ -0,0 +1,88 @@
from _ca import *
from cadefs import *
import caError
def alarmSeverityString(sevr):
try:
return AlarmSeverity.Strings[sevr]
except:
return "Unkown Severity"
def alarmStatusString(status):
try:
return AlarmStatus.Strings[status]
except:
return "Unknown Alarm"
def message(status):
try:
return caError._caErrorMsg[caError.CA_EXTRACT_MSG_NO(status)]
except:
return str(status)
def dbf_type_is_valid(dbftype):
return dbftype >= 0 and dbftype <= LAST_TYPE
def dbr_type_is_valid(dbrtype):
return dbrtype >= 0 and dbrtype <= LAST_BUFFER_TYPE
def dbr_type_is_plain(dbrtype):
return (dbrtype >= DBR_STRING and dbrtype <= DBR_DOUBLE)
def dbr_type_is_STS(dbrtype):
return (dbrtype >= DBR_STS_STRING and dbrtype <= DBR_STS_DOUBLE)
def dbr_type_is_TIME(dbrtype):
return (dbrtype >= DBR_TIME_STRING and dbrtype <= DBR_TIME_DOUBLE)
def dbr_type_is_GR(dbrtype):
return (dbrtype >= DBR_GR_STRING and dbrtype <= DBR_GR_DOUBLE)
def dbr_type_is_CTRL(dbrtype):
return (dbrtype >= DBR_CTRL_STRING and dbrtype <= DBR_CTRL_DOUBLE)
def dbr_type_is_STRING(dbrtype):
return (dbrtype >= 0 and dbrtype <= LAST_BUFFER_TYPE and
dbrtype%(LAST_TYPE+1) == DBR_STRING)
def dbr_type_is_SHORT(dbrtype):
return (dbrtype >= 0 and dbrtype <= LAST_BUFFER_TYPE and
dbrtype%(LAST_TYPE+1) == DBR_SHORT)
def dbr_type_is_FLOAT(dbrtype):
return (dbrtype >= 0 and dbrtype <= LAST_BUFFER_TYPE and
dbrtype%(LAST_TYPE+1) == DBR_FLOAT)
def dbr_type_is_ENUM(dbrtype):
return (dbrtype >= 0 and dbrtype <= LAST_BUFFER_TYPE and
dbrtype%(LAST_TYPE+1) == DBR_ENUM)
def dbr_type_is_CHAR(dbrtype):
return (dbrtype >= 0 and dbrtype <= LAST_BUFFER_TYPE and
dbrtype%(LAST_TYPE+1) == DBR_CHAR)
def dbr_type_is_LONG(dbrtype):
return (dbrtype >= 0 and dbrtype <= LAST_BUFFER_TYPE and
dbrtype%(LAST_TYPE+1) == DBR_LONG)
def dbr_type_is_DOUBLE(dbrtype):
return (dbrtype >= 0 and dbrtype <= LAST_BUFFER_TYPE and
dbrtype%(LAST_TYPE+1) == DBR_DOUBLE)
def dbf_type_to_DBR(dbftype):
if dbftype>=0 and dbftype <= LAST_TYPE:
return dbftype
else:
return -1
def dbf_type_to_DBR_STS(dbftype):
if dbftype>=0 and dbftype <= LAST_TYPE:
return dbftype + LAST_TYPE+1
else:
return -1
def dbf_type_to_DBR_TIME(dbftype):
if dbftype>=0 and dbftype <= LAST_TYPE:
return dbftype + (LAST_TYPE+1)*2
else:
return -1
def dbf_type_to_DBR_GR(dbftype):
if dbftype>=0 and dbftype <= LAST_TYPE:
return dbftype + (LAST_TYPE+1)*3
else:
return -1
def dbf_type_to_DBR_CTRL(dbftype):
if dbftype>=0 and dbftype <= LAST_TYPE:
return dbftype + (LAST_TYPE+1)*4
else:
return -1
+545
View File
@@ -0,0 +1,545 @@
#!/usr/bin/env python
## @package ca: EPICS-CA interface module for Python.
"""CA modlue : EPICS-CA interface module for Python.
This module provide a version of EPICS-CA and Python interface.
It users C module _ca. _ca module basically maps C-API in EPICS ca library into python. Interface between ca.py and _ca module is subject for change. You should not depend on it. API in ca.py will be preserved in future releases as much as possible.
Author: Noboru Yamamoto, KEK, JAPAN. -2007.
$Revision: 1.1 $
"""
from __future__ import print_function
__version__ = "$Revision: 1.1 $"
# $Source: /cvs/G/EPICS/extensions/src/PythonCA/src/_ca_kek.py,v $
#
try:
import signal
except:
print("signal module is not avaialble")
import time,thread,gc,sys,atexit
from exceptions import ValueError
# autGIL is not compatible with Tkinter and wx. So code was removed
# force thread module to call PyEval_InitThread in it.
__foo_lock=thread.allocate_lock()
def __foo():
"""
test function foo
This function is used to ensure thread module is initialized before
loading _ca module.
"""
global __foo_lock
__foo_lock.release()
thread.exit_thread()
# See Python/Include/ceval.h
__foo_lock.acquire()
thread.start_new_thread(__foo,()) # __foo release lock
__foo_lock.acquire() # make sure threading is activated
import _ca
# version from _ca314.cpp
version=_ca.version
revision=_ca.release
# some constants for EPICS channel Access library
from cadefs import *
from caError import *
#export pend_xxx routines for global operation
pendio =_ca.pendio
pend_io=_ca.pendio
pend_event=_ca.pend_event
poll=_ca.poll
poll_event=_ca.poll
flush_io=_ca.flush
flush=_ca.flush
test_io=_ca.test_io # test_io retunrs 42 for IODONE , 43 for IOINPROGRESS
add_fd_registration=_ca.add_fd_registration
#Error Object
error=_ca.error
shutdown=_ca.__ca_task_exit
#private dictionary for Get/Put functions
__ca_dict={}
__ca_dict_lock=thread.allocate_lock()
_channel__debug=False
class channel:
"""
a channel object for EPICS Channel Access.
It does not have direct connection
to channel object in C-library for EPICS Channel Access.
for creation just supply channel name to connect
"""
dbr_types=(
DBR_NATIVE, # default type
DBR_STRING, DBR_CHAR, DBR_FLOAT,
DBR_SHORT, #/* same as DBR_INT */
DBR_ENUM, DBR_LONG, DBR_DOUBLE,
DBR_TIME_STRING, DBR_TIME_CHAR, DBR_TIME_FLOAT,
DBR_TIME_SHORT, #:/* same as DBR_TIME_INT */
DBR_TIME_ENUM, DBR_TIME_LONG, DBR_TIME_DOUBLE,
DBR_CTRL_CHAR, DBR_CTRL_LONG,
DBR_CTRL_ENUM, DBR_CTRL_DOUBLE
)
def __init__(self, name, cb=None,noflush=None):
if (not cb) : cb=self.update_info
if name == "":
raise ValueError(name)
self.name=name
self.field_type = None
self.element_count = None
self.puser = None
self.conn_state = -1
self.hostname = None
self.raccess = None
self.waccess = None
self.sevr=None
self.ts=None
self.status=None
self.evid=[]
self.autoEvid=None
self.__callbacks={}
self.cbstate=None
self.updated=False
self.val=None
self.chid=_ca.search(name,cb)
if not noflush:
self.flush()
def clear(self):
if self.chid:
self.clear_monitor()
self.flush()
_ca.clear(self.chid)
self.flush()
self.chid=None
def __del__(self):
self.clear()
def wait_conn(self, wait=20, dt=0.05):
n=0
self.pend_event(dt)
self.update_info()
self.poll()
while (not self.isConnected()):
self.update_info()
self.pend_event(dt)
n=n+1
if (n > wait ) :
raise ECA_BADCHID("%s %d"%(self.name,n))
return -1
def get(self,cb=None,Type=DBR_NATIVE, count=0, type=DBR_NATIVE, type_=DBR_NATIVE):
try:
if not self.isConnected():
raise ECA_BADCHID(self.name)
except:
raise ECA_BADCHID(self.name)
if (Type == DBR_NATIVE):
if not(type == DBR_NATIVE):
Type=type
elif not(type_ == DBR_NATIVE):
Type=type_
rType=max(Type,type,type_)
if rType not in self.dbr_types:
raise TypeError(rType)
if not cb: cb=self.update_val
self.cbstate=None
self.updated=False
try:
_ca.get(self.chid, cb, Type, count)
finally:
pass
def put(self,*val,**kw):
"""
channel.put(valu) will put scalar value to channel. You may need to call channel.flush()
"""
if( val == ()):
print("No value(s) to put")
else:
if kw.has_key('cb'):
cb=kw['cb']
else:
cb=None
#self.__lock.acquire()
try:
_ca.put(self.chid, val, self.val, cb, DBR_NATIVE)
finally:
#self.__lock.release()
pass
def put_and_notify(self,*val,**kw):
if kw.has_key('cb'):
cb=kw['cb']
else:
cb=None # ca_put_array_callback does not return value.
if( val == ()):
print("No value(s) to put")
else:
#self.__lock.acquire()
try:
_ca.put(self.chid,val,self.val,cb, DBR_NATIVE)
finally:
#self.__lock.release()
pass
def monitor(self,callback=None,count=0,evmask=(DBE_VALUE|DBE_ALARM)):
if(not callback):
raise PyCa_NoCallback
if (self.conn_state != 2):
#print self.name,self.get_info()
raise ECA_BADCHID(self.name)
self.update_info()
if (self.field_type == DBR_NATIVE):
#print self.name,self.get_info()
raise ECA_BADTYPE(self.name)
self.evid.append(_ca.monitor(self.chid,callback,count,evmask))
self.__callbacks[self.evid[-1]]=callback
return self.evid[-1]
def __clear_event(self,evid):
if(_channel__debug): print("clearing evid:",evid)
_ca.clear_monitor(evid)
del self.__callbacks[evid]
def clear_monitor(self,evid=None):
if(evid):
if ( evid in self.evid):
self.__clear_event(evid)
i=self.evid.index(evid)
del self.evid[i]
i=self.evid.index(evid)
del self.evid[i]
else:
for evid in self.evid:
self.__clear_event(evid)
self.evid=[]
def autoUpdate(self):
if self.autoEvid == None:
self.monitor(self.update_val)
self.autoEvid=self.evid[-1]
self.flush()
def clearAutoUpdate(self):
if self.autoEvid is not None:
self.clear_monitor(self.autoEvid)
self.autoEvid=None
self.flush()
def pendio(self,tmo=0.001):
v=_ca.pendio(float(tmo))
return v
def pend_io(self,tmo=0.001):
v=_ca.pendio(float(tmo))
return v
def pend_event(self,tmo=0.001):
v=_ca.pend_event(float(tmo))
return v
def poll(self):
_ca.poll()
def flush(self,wait=0.001):
v=_ca.flush(wait)
return v
def update_val(self,valstat=None):
if valstat ==None:
raise caError("No value")
#self.__lock.acquire()
try:
self.val=valstat[0]
self.sevr=valstat[1]
self.status=valstat[2]
self.cbstate=1
try:
self.ts=valstat[3]
except:
pass
try:
self.ctrl=valstat[4]
except:
pass
finally:
#self.__lock.release()
self.updated=True
pass
def clear_cbstate(self):
#self.__lock.acquire()
self.cbstate=None
#self.__lock.release()
def state(self):
self.get_info()
return (self.conn_state - ch_state.cs_conn)
def isNeverConnected(self):
self.get_info()
return (self.conn_state == ch_state.cs_never_conn)
def isConnected(self):
self.get_info()
return (self.conn_state == ch_state.cs_conn)
def isPreviouslyConnected(self):
self.get_info()
return (self.conn_state == ch_state.cs_prev_conn)
def isDisonnected(self):
self.get_info()
return (self.conn_state == ch_state.cs_prev_conn)
def isClosed(self):
self.get_info()
return (self.conn_state == ch_state.cs_closed)
def get_info(self):
"""
update channel status information. return channel staus as a tuple.
"""
#self.__lock.acquire()
try:
info=(self.field_type, self.element_count, self.puser,
self.conn_state, self.hostname, self.raccess,
self.waccess) = _ca.ch_info(self.chid)
finally:
#self.__lock.release()
pass
return info
def update_info(self):
"""
Just update channel status information. No return value.
"""
self.get_info()
def fileno(self):
"""returns socket number used to connect.Scoket id is shared by
channels which are connected to the same IOC.
It became obsolete in EPICS 3.14 version of Python-CA.
You need to use fd_register function. But you may not need it anyway in multi-thread environment.
"""
return _ca.fileno(self.chid)
# convenient functions
# you need to call Clear() function before stopping Python, otherwise it cause coredump. 2009/2/11 NY
def __Ch(name,tmo=0.01):
if (type(name) == type("")):
if (__ca_dict.has_key(name)):
ch=__ca_dict[name]
else:
try:
ch=channel(name)
ch.wait_conn()
except:
raise ECA_BADCHID(name)
tmo=20*tmo
__ca_dict_lock.acquire()
try:
__ca_dict[name]=ch
finally:
__ca_dict_lock.release()
if( ch.state() != 0):
ch.wait_conn(10)
return ch
else:
raise ECA_BADTYPE(name)
def Info(name = "",tmo=0.01):
"""
returns a tuple as channel information.
tuple format=(field_type, element_count, puser argument,
connection_status, hostname:port,
read access mode, write access mode)
"""
ch=__Ch(name,tmo=tmo)
return ch.get_info()
def ClearAll():
for name in __ca_dict.keys():
Clear(name)
# __ca_dict should be cleared before Stopping Python
atexit.register(ClearAll)
def Clear(name= ""):
if (type(name) == type("")):
__ca_dict_lock.acquire()
try:
if (__ca_dict.has_key(name)):
ch=__ca_dict[name]
del __ca_dict[name]
ch.clear()
del ch
else:
__ca_dict_lock.release()
raise ECA_BADTYPE(name)
finally:
__ca_dict_lock.release()
else:
raise ECA_BADTYPE(name)
def Get(name="",count=0,Type=DBR_NATIVE,tmo=0.01,maxtmo=3):
"""
Get value from a channel "name".
"""
ch=__Ch(name,tmo)
def CB(vals,ch=ch):
ch.update_val(vals)
ch.get(cb=CB,Type=Type,count=count)
ch.flush()
while not ch.updated:
time.sleep(tmo)
maxtmo -=tmo
if maxtmo <=0:
raise caError("No get response")
return ch.val
def Put_and_Notify(name,val=None,cb=None):
"""
Convenient function:Put_and_Notify
calls put_and_notify with callback.
If callback is None, then just put data to a channel.
"""
ch=__Ch(name,tmo=0.1)
ch.put_and_notify(val,cb=cb)
ch.flush()
return ch.val
# define synonym
Put=Put_and_Notify
def Put_and_Notify_Array(name,val,cb=None):
"""
put array test version : not tested with string arrays yet
2007.8.30 T. Matsumoto
"""
ch=__Ch(name,tmo=0.1)
apply(ch.put_and_notify,val,dict(cb=cb))
ch.flush()
return ch.val
# define synonym
Put_Array=Put_and_Notify_Array
def Monitor(name,cb=None,evmask=(DBE_VALUE|DBE_ALARM)):
ch=__Ch(name,tmo=0.1)
if not cb:
def myCB(val,ch=ch):
print(ch.name,":",val[0],val[1],val[2],TS2Ascii(val[3]))
else:
def myCB(val, ch=ch, cb=cb):
cb(ch,val)
ch.clear_monitor()
evid=ch.monitor(myCB,evmask=evmask)
ch.flush()
return evid
def ClearMonitor(name,evid=None):
ch=__Ch(name,tmo=0.1)
try:
ch.clear_monitor(evid)
return
except:
raise ECA_BADCHID(name)
#
def isIODone():
if _ca.test_io()== 42:
return 1
else:
return 0
#
# syncronus group class
# Author: N.Yamamoto
# Date: May 27.1999 (first version)
#
class SyncGroup:
def __init__(self):
self.gid=_ca.sg_create()
self.chs={}
def add(self, chs):
try:
for ch in chs:
if(not self.chs.has_key(ch)):
self.chs[ch]=0
except:
if(not self.chs.has_key(chs)):
self.chs[chs]=0
def test(self):
return _ca.sg_test(self.gid)
def reset(self):
return _ca.sg_reset(self.gid)
def wait(self,tmo=1.0):
return _ca.sg_block(self.gid,float(tmo))
def put(self,ch,*value,**kw):
if kw.has_key("Type"):
Type=kw["Type"]
else:
Type=DBR_NATIVE
if self.chs.has_key(ch):
self.chs[ch]=_ca.sg_put(self.gid, ch.chid,
self.chs[ch], value , Type)
def get(self,ch):
if self.chs.has_key(ch):
self.chs[ch]=_ca.sg_get(self.gid,
ch.chid, self.chs[ch])
else:
pass
def convert(self,ch):
if self.chs.has_key(ch):
val=_ca.ca_convert(ch.chid, self.chs[ch])
ch.update_val(val[0])
def GetAll(self,tmo=1.0):
for ch in self.chs.keys():
self.chs[ch]=_ca.sg_get(self.gid,
ch.chid, self.chs[ch])
st=_ca.sg_block(self.gid,tmo)
if st == 0:
for ch in self.chs.keys():
val=_ca.ca_convert(ch.chid,self.chs[ch])
ch.update_val(val[0])
else:
raise Exception("CA_SG time out")
# TimeStamp utilities
# time.gmtime(631152000.0)=(1990, 1, 1, 0, 0, 0, 0, 1, 0)
#
__EPICS_TS_EPOCH=631152000.0
def TS2Ascii(ts):
import math
tstr=time.ctime(ts+__EPICS_TS_EPOCH)
nsstr=".%03d"%(math.modf(ts + __EPICS_TS_EPOCH)[0]*1000)
return tstr[:-5]+nsstr+tstr[-5:]
def TS2time(ts):
return time.localtime(ts+__EPICS_TS_EPOCH)
def TS2UTC(ts):
return (ts+__EPICS_TS_EPOCH)
+49
View File
@@ -0,0 +1,49 @@
#!/usr/bin/env python
## @package ca: EPICS-CA interface module for Python.
"""CA modlue : EPICS-CA interface module for Python.
This module provide a version of EPICS-CA and Python interface.
It users C module _ca. _ca module basically maps C-API in EPICS ca library into python. Interface between ca.py and _ca module is subject for change. You should not depend on it. API in ca.py will be preserved in future releases as much as possible.
Author: Noboru Yamamoto, KEK, JAPAN. -2007.
$Revision: 1.4 $
"""
__version__ = "$Revision: 1.4 $"
# $Source: /cvs/G/EPICS/extensions/src/PythonCA/src/ca.py,v $
import time,gc,sys,atexit
if sys.hexversion >= 0x03000000:
import _thread as thread
else:
import thread
# autGIL is not compatible with Tkinter and wx. So code was removed
# force thread module to call PyEval_InitThread in it.
__foo_lock=thread.allocate_lock()
def __foo():
"""
test function foo
This function is used to ensure thread module is initialized before
loading _ca module.
"""
global __foo_lock
__foo_lock.release()
thread.exit_thread()
# See Python/Include/ceval.h
__foo_lock.acquire()
thread.start_new_thread(__foo,()) # __foo release lock
__foo_lock.acquire() # make sure threading is activated
import _ca
# version from _ca314.cpp
version=_ca.version
revision=_ca.release
# some constants for EPICS channel Access library
from cadefs import *
from caError import *
# for FNAL version you need to provide _ca_fnal.py and import every thin from them
from _ca_fnal import *
+461
View File
@@ -0,0 +1,461 @@
import sys
if sys.hexversion >= 0x03000000:
intern = sys.intern
from ca import _ca
class caError(_ca.error):
""" EPICS ca.py Errors"""
pass
__caErrorMsg=(
"Normal successful completion",
"Maximum simultaneous IOC connections exceeded",
"Unknown internet host",
"Unknown internet service",
"Unable to allocate a new socket",
"Unable to connect to internet host or service",
"Unable to allocate additional dynamic memory",
"Unknown IO channel",
"Record field specified inappropriate for channel specified",
"The requested data transfer is greater than available memory or EPICS_CA_MAX_ARRAY_BYTES",
"User specified timeout on IO operation expired",
"Sorry, that feature is planned but not supported at this time",
"The supplied string is unusually large",
"The request was ignored because the specified channel is disconnected",
"The data type specifed is invalid",
"Remote Channel not found",
"Unable to locate all user specified channels",
"Channel Access Internal Failure",
"The requested local DB operation failed",
"Channel read request failed",
"Channel write request failed",
"Channel subscription request failed",
"Invalid element count requested",
"Invalid string",
"Virtual circuit disconnect",
"Identical process variable names on multiple servers",
"Request inappropriate within subscription (monitor) update callback",
"Database value get for that channel failed during channel search",
"Unable to initialize without the vxWorks VX_FP_TASK task option set",
"Event queue overflow has prevented first pass event after event add",
"Bad event subscription (monitor) identifier",
"Remote channel has new network address",
"New or resumed network connection",
"Specified task isnt a member of a CA context",
"Attempt to use defunct CA feature failed",
"The supplied string is empty",
"Unable to spawn the CA repeater thread- auto reconnect will fail",
"No channel id match for search reply- search reply ignored",
"Reseting dead connection- will try to reconnect",
"Server (IOC) has fallen behind or is not responding- still waiting",
"No internet interface with broadcast available",
"Invalid event selection mask",
"IO operations have completed",
"IO operations are in progress",
"Invalid synchronous group identifier",
"Put callback timed out",
"Read access denied",
"Write access denied",
"Requested feature is no longer supported",
"Empty PV search address list",
"No reasonable data conversion between client and server types",
"Invalid channel identifier",
"Invalid function pointer",
"Thread is already attached to a client context",
"Not supported by attached service",
"User destroyed channel",
"Invalid channel priority",
"Preemptive callback not enabled - additional threads may not join context",
"Client's protocol revision does not support transfers exceeding 16k bytes",
"Virtual circuit connection sequence aborted",
"Virtual circuit unresponsive",
)
_caErrorMsg=map(intern,__caErrorMsg)
if sys.hexversion >= 0x03000000:
_caErrorMsg = list(_caErrorMsg)
ErrCode2Class={}
class PyCa_NoCallback(caError):
__doc__="Null callback routine"
CA_M_MSG_NO = 0x0000FFF8
CA_M_SEVERITY = 0x00000007
CA_M_LEVEL = 0x00000003
CA_M_SUCCESS = 0x00000001
CA_M_ERROR = 0x00000002
CA_M_SEVERE = 0x00000004
CA_S_MSG_NO= 0x0D
CA_S_SEVERITY=0x03
CA_V_MSG_NO= 0x03
CA_V_SEVERITY= 0x00
CA_V_SUCCESS= 0x00
def CA_EXTRACT_MSG_NO(code): return ( ( (code) & CA_M_MSG_NO ) >> CA_V_MSG_NO )
def CA_EXTRACT_SEVERITY(code): return ( ( (code) & CA_M_SEVERITY ) >> CA_V_SEVERITY)
def CA_EXTRACT_SUCCESS(code): ( ( (code) & CA_M_SUCCESS ) >> CA_V_SUCCESS )
class ECA_NORMAL(caError):
__doc__=_caErrorMsg[0]
__errcode__=1
ErrCode2Class[1]=ECA_NORMAL
class ECA_MAXIOC(caError):
__doc__=_caErrorMsg[1]
__errcode__=10
ErrCode2Class[10]=ECA_MAXIOC
class ECA_UKNHOST(caError):
__doc__=_caErrorMsg[2]
__errcode__=18
ErrCode2Class[18]=ECA_UKNHOST
class ECA_UKNSERV(caError):
__doc__=_caErrorMsg[3]
__errcode__=26
ErrCode2Class[26]=ECA_UKNSERV
class ECA_SOCK(caError):
__doc__=_caErrorMsg[4]
__errcode__=34
ErrCode2Class[34]=ECA_SOCK
class ECA_CONN(caError):
__doc__=_caErrorMsg[5]
__errcode__=40
ErrCode2Class[40]=ECA_CONN
class ECA_ALLOCMEM(caError):
__doc__=_caErrorMsg[6]
__errcode__=48
ErrCode2Class[48]=ECA_ALLOCMEM
class ECA_UKNCHAN(caError):
__doc__=_caErrorMsg[7]
__errcode__=56
ErrCode2Class[56]=ECA_UKNCHAN
class ECA_UKNFIELD(caError):
__doc__=_caErrorMsg[8]
__errcode__=64
ErrCode2Class[64]=ECA_UKNFIELD
class ECA_TOLARGE(caError):
__doc__=_caErrorMsg[9]
__errcode__=72
ErrCode2Class[72]=ECA_TOLARGE
class ECA_TIMEOUT(caError):
__doc__=_caErrorMsg[10]
__errcode__=80
ErrCode2Class[80]=ECA_TIMEOUT
class ECA_NOSUPPORT(caError):
__doc__=_caErrorMsg[11]
__errcode__=88
ErrCode2Class[88]=ECA_NOSUPPORT
class ECA_STRTOBIG(caError):
__doc__=_caErrorMsg[12]
__errcode__=96
ErrCode2Class[96]=ECA_STRTOBIG
class ECA_DISCONNCHID(caError):
__doc__=_caErrorMsg[13]
__errcode__=106
ErrCode2Class[106]=ECA_DISCONNCHID
class ECA_BADTYPE(caError):
__doc__=_caErrorMsg[14]
__errcode__=114
ErrCode2Class[114]=ECA_BADTYPE
class ECA_CHIDNOTFND(caError):
__doc__=_caErrorMsg[15]
__errcode__=123
ErrCode2Class[123]=ECA_CHIDNOTFND
class ECA_CHIDRETRY(caError):
__doc__=_caErrorMsg[16]
__errcode__=131
ErrCode2Class[131]=ECA_CHIDRETRY
class ECA_INTERNAL(caError):
__doc__=_caErrorMsg[17]
__errcode__=142
ErrCode2Class[142]=ECA_INTERNAL
class ECA_DBLCLFAIL(caError):
__doc__=_caErrorMsg[18]
__errcode__=144
ErrCode2Class[144]=ECA_DBLCLFAIL
class ECA_GETFAIL(caError):
__doc__=_caErrorMsg[19]
__errcode__=152
ErrCode2Class[152]=ECA_GETFAIL
class ECA_PUTFAIL(caError):
__doc__=_caErrorMsg[20]
__errcode__=160
ErrCode2Class[160]=ECA_PUTFAIL
class ECA_ADDFAIL(caError):
__doc__=_caErrorMsg[21]
__errcode__=168
ErrCode2Class[168]=ECA_ADDFAIL
class ECA_BADCOUNT(caError):
__doc__=_caErrorMsg[22]
__errcode__=176
ErrCode2Class[176]=ECA_BADCOUNT
class ECA_BADSTR(caError):
__doc__=_caErrorMsg[23]
__errcode__=186
ErrCode2Class[186]=ECA_BADSTR
class ECA_DISCONN(caError):
__doc__=_caErrorMsg[24]
__errcode__=192
ErrCode2Class[192]=ECA_DISCONN
class ECA_DBLCHNL(caError):
__doc__=_caErrorMsg[25]
__errcode__=200
ErrCode2Class[200]=ECA_DBLCHNL
class ECA_EVDISALLOW(caError):
__doc__=_caErrorMsg[26]
__errcode__=210
ErrCode2Class[210]=ECA_EVDISALLOW
class ECA_BUILDGET(caError):
__doc__=_caErrorMsg[27]
__errcode__=216
ErrCode2Class[216]=ECA_BUILDGET
class ECA_NEEDSFP(caError):
__doc__=_caErrorMsg[28]
__errcode__=224
ErrCode2Class[224]=ECA_NEEDSFP
class ECA_OVEVFAIL(caError):
__doc__=_caErrorMsg[29]
__errcode__=232
ErrCode2Class[232]=ECA_OVEVFAIL
class ECA_BADMONID(caError):
__doc__=_caErrorMsg[30]
__errcode__=242
ErrCode2Class[242]=ECA_BADMONID
class ECA_NEWADDR(caError):
__doc__=_caErrorMsg[31]
__errcode__=248
ErrCode2Class[248]=ECA_NEWADDR
class ECA_NEWCONN(caError):
__doc__=_caErrorMsg[32]
__errcode__=259
ErrCode2Class[259]=ECA_NEWCONN
class ECA_NOCACTX(caError):
__doc__=_caErrorMsg[33]
__errcode__=264
ErrCode2Class[264]=ECA_NOCACTX
class ECA_DEFUNCT(caError):
__doc__=_caErrorMsg[34]
__errcode__=278
ErrCode2Class[278]=ECA_DEFUNCT
class ECA_EMPTYSTR(caError):
__doc__=_caErrorMsg[35]
__errcode__=280
ErrCode2Class[280]=ECA_EMPTYSTR
class ECA_NOREPEATER(caError):
__doc__=_caErrorMsg[36]
__errcode__=288
ErrCode2Class[288]=ECA_NOREPEATER
class ECA_NOCHANMSG(caError):
__doc__=_caErrorMsg[37]
__errcode__=296
ErrCode2Class[296]=ECA_NOCHANMSG
class ECA_DLCKREST(caError):
__doc__=_caErrorMsg[38]
__errcode__=304
ErrCode2Class[304]=ECA_DLCKREST
class ECA_SERVBEHIND(caError):
__doc__=_caErrorMsg[39]
__errcode__=312
ErrCode2Class[312]=ECA_SERVBEHIND
class ECA_NOCAST(caError):
__doc__=_caErrorMsg[40]
__errcode__=320
ErrCode2Class[320]=ECA_NOCAST
class ECA_BADMASK(caError):
__doc__=_caErrorMsg[41]
__errcode__=330
ErrCode2Class[330]=ECA_BADMASK
class ECA_IODONE(caError):
__doc__=_caErrorMsg[42]
__errcode__=339
ErrCode2Class[339]=ECA_IODONE
class ECA_IOINPROGRESS(caError):
__doc__=_caErrorMsg[43]
__errcode__=347
ErrCode2Class[347]=ECA_IOINPROGRESS
class ECA_BADSYNCGRP(caError):
__doc__=_caErrorMsg[44]
__errcode__=354
ErrCode2Class[354]=ECA_BADSYNCGRP
class ECA_PUTCBINPROG(caError):
__doc__=_caErrorMsg[45]
__errcode__=362
ErrCode2Class[362]=ECA_PUTCBINPROG
class ECA_NORDACCESS(caError):
__doc__=_caErrorMsg[46]
__errcode__=368
ErrCode2Class[368]=ECA_NORDACCESS
class ECA_NOWTACCESS(caError):
__doc__=_caErrorMsg[47]
__errcode__=376
ErrCode2Class[376]=ECA_NOWTACCESS
class ECA_ANACHRONISM(caError):
__doc__=_caErrorMsg[48]
__errcode__=386
ErrCode2Class[386]=ECA_ANACHRONISM
class ECA_NOSEARCHADDR(caError):
__doc__=_caErrorMsg[49]
__errcode__=392
ErrCode2Class[392]=ECA_NOSEARCHADDR
class ECA_NOCONVERT(caError):
__doc__=_caErrorMsg[50]
__errcode__=400
ErrCode2Class[400]=ECA_NOCONVERT
class ECA_BADCHID(caError):
__doc__=_caErrorMsg[51]
__errcode__=410
ErrCode2Class[410]=ECA_BADCHID
class ECA_BADFUNCPTR(caError):
__doc__=_caErrorMsg[52]
__errcode__=418
ErrCode2Class[418]=ECA_BADFUNCPTR
class ECA_ISATTACHED(caError):
__doc__=_caErrorMsg[53]
__errcode__=424
ErrCode2Class[424]=ECA_ISATTACHED
class ECA_UNAVAILINSERV(caError):
__doc__=_caErrorMsg[54]
__errcode__=432
ErrCode2Class[432]=ECA_UNAVAILINSERV
class ECA_CHANDESTROY(caError):
__doc__=_caErrorMsg[55]
__errcode__=440
ErrCode2Class[440]=ECA_CHANDESTROY
class ECA_BADPRIORITY(caError):
__doc__=_caErrorMsg[56]
__errcode__=450
ErrCode2Class[450]=ECA_BADPRIORITY
class ECA_NOTTHREADED(caError):
__doc__=_caErrorMsg[57]
__errcode__=458
ErrCode2Class[458]=ECA_NOTTHREADED
class ECA_16KARRAYCLIENT(caError):
__doc__=_caErrorMsg[58]
__errcode__=464
ErrCode2Class[464]=ECA_16KARRAYCLIENT
class ECA_CONNSEQTMO(caError):
__doc__=_caErrorMsg[59]
__errcode__=472
ErrCode2Class[472]=ECA_CONNSEQTMO
class ECA_UNRESPTMO(caError):
__doc__=_caErrorMsg[60]
__errcode__=480
ErrCode2Class[480]=ECA_UNRESPTMO
+647
View File
@@ -0,0 +1,647 @@
# ca_util.py - a thin wrapper around CaChannel
# Tim Mooney 12/05/2008
#
# Modified by Xiaoqiang Wang to be Python 3 compatible.
"""ca_util.py is a wrapper around CaChannel that allows the caller to write,
e.g.,
caget("xxx:m1")
instead of having to write
m1 = CaChannel()
m1.searchw("xxx:m1")
m1.getw()
Also, ca_util defends against null PV names and some effects of short-term
CA disconnections, and it can verify that caput*() operations succeeded.
"""
version = "2.0"
import ca
import CaChannel
import time
import sys
# DBR types
# ca.DBR_STRING = 0
# ca.DBR_SHORT = 1
# ca.DBR_INT = 1
# ca.DBR_FLOAT = 2
# ca.DBR_ENUM = 3
# ca.DBR_CHAR = 4
# ca.DBR_LONG = 5
# ca.DBR_DOUBLE = 6
# If caller imported CaChannel using "from CaChannel import *", then the
# class CaChannel will have the same name as the module CaChannel, and
# we won't be able to see the module attribute, 'CaChannel.__file__'.
def getCaChannelFileName():
""" For internal ca_util use"""
return CaChannel.__file__
#######################################################################
# Human readable exception description
# try:
# x = x + 1
# except:
# print formatExceptionInfo()
import sys
import traceback
def formatExceptionInfo(maxTBlevel=5):
cla, exc, trbk = sys.exc_info()
excName = cla.__name__
try:
excArgs = exc.__dict__["args"]
except KeyError:
excArgs = "<no args>"
excTb = traceback.format_tb(trbk, maxTBlevel)
return (excName, excArgs, excTb)
#######################################################################
# channel-access connection states
ca_states = {}
# ...from cadef.h:
ca_states[ca.cs_never_conn] = "never connected"
ca_states[ca.cs_prev_conn] = "previously connected"
ca_states[ca.cs_conn] = "connected"
ca_states[ca.cs_closed] = "closed"
ca_states[ca.cs_never_search] = "never searched"
#######################################################################
# default settings for ca_util
defaultTimeout = None # 'None' means use CaChannel's timeout
defaultRetries = 3
readCheckTolerance = None # 'None" means don't check
def set_ca_util_defaults(timeout=None, retries=None, read_check_tolerance=None):
"""
usage: old = set_ca_util_defaults(timeout=None, retries=None,
read_check_tolerance=None)
alternate: set_ca_util_defaults(defaultsList), where defaultsList is like
the list returned by get_ca_util_defaults()
Setting an argument to the string "NONE" disables it.
Returns the list of previous default values:
[defaultTimeout, defaultRetries, readCheckTolerance]
"""
global defaultTimeout, defaultRetries, readCheckTolerance
old = [defaultTimeout, defaultRetries, readCheckTolerance]
if type(timeout) == type([]):
argList = timeout
timeout = argList[0]
retries = argList[1]
read_check_tolerance = argList[2]
if (timeout!=None) : defaultTimeout = timeout
if (retries!=None) : defaultRetries = retries
if (read_check_tolerance!=None) : readCheckTolerance = read_check_tolerance
return old
def get_ca_util_defaults():
"""
usage: myList = get_ca_util_defaults()
myList is set to [defaultTimeout, defaultRetries, readCheckTolerance]
"""
global defaultTimeout, defaultRetries, readCheckTolerance
return [defaultTimeout, defaultRetries, readCheckTolerance]
def set_ca_util_default_timeout(timeout=None):
"""
usage: old = set_ca_util_default_timeout(timeout=None)
If timeout == "NONE", then ca_util doesn't specify any timeout in
calls to underlying software.
Returns previous default timeout.
"""
global defaultTimeout
old = defaultTimeout
defaultTimeout = timeout
return old
def get_ca_util_default_timeout():
global defaultTimeout
return defaultTimeout
def set_ca_util_default_retries(retries=None):
"""
usage: old = set_ca_util_default_retries(retries=None)
If retries == "NONE", then ca_util doesn't do any retries.
Returns previous default retries.
"""
global defaultRetries
old = defaultRetries
defaultRetries = retries
return old
def get_ca_util_default_retries():
global defaultRetries
return defaultRetries
def set_ca_util_default_read_check_tolerance(read_check_tolerance=None):
"""
usage: old = set_ca_util_default_read_check_tolerance(read_check_tolerance=None)
If read_check_tolerance == "NONE", then ca_util doesn't compare the value
it reads to the value it wrote.
Returns previous default tolerance.
"""
global readCheckTolerance
old = readCheckTolerance
readCheckTolerance = read_check_tolerance
return old
def get_ca_util_default_read_check_tolerance():
global readCheckTolerance
return readCheckTolerance
#######################################################################
# The dictionary, cadict, will be used to associate PV names with the
# machinery required to talk to EPICS PV's. If no entry is found (the
# name hasn't been used yet in a ca call), then we create a new instance
# of CaChannel, connect it to the PV, and put it in the dictionary. We also
# include a flag some of the ca_util routines can use to check if a callback
# has occurred for this PV.
class cadictEntry:
def __init__(self, channel):
self.channel = channel
self.callbackReceived = 0 # reserved for use by caputw()
self.field_type = channel.field_type()
self.element_count = channel.element_count()
#self.host_name = channel.host_name()
cadict = {}
#######################################################################
ca_utilExceptionStrings = ["No name was provided.", "Readback disagrees with put value.",
"PV is not connected."]
EXCEPTION_NULL_NAME = 0
EXCEPTION_READBACK_DISAGREES = 1
EXCEPTION_NOT_CONNECTED = 2
class ca_utilException(Exception):
def __init__(self, *args):
Exception.__init__(self, *args)
self.errorNumber = args[0]
def __int__(self):
return int(self.errorNumber)
def __str__(self):
return ca_utilExceptionStrings[self.errorNumber]
#######################################################################
def convertToType(type, value):
if type == ca.DBR_STRING:
return str(value)
elif type == ca.DBR_SHORT or type == ca.DBR_INT or type == ca.DBR_LONG:
try:
n = int(value)
except:
n = 0
return n
elif type == ca.DBR_FLOAT or type == ca.DBR_DOUBLE:
try:
n = float(value)
except:
n = 0.0
return n
elif type == ca.DBR_ENUM:
return value
elif type == ca.DBR_CHAR:
return value
else:
return value
#######################################################################
def checkName(name, timeout=None, retries=None):
"""
usage: checkName("xxx:m1.VAL", timeout=None, retries=None)
Intended for internal use by ca_util functions.
"""
global cadict, defaultTimeout, defaultRetries
if not name:
raise ca_utilException(EXCEPTION_NULL_NAME)
if ((timeout == None) and (defaultTimeout != None)): timeout = defaultTimeout
if (timeout == "NONE"): timeout = None
if ((retries == None) and (defaultRetries != None)): retries = defaultRetries
if ((retries == None) or (retries == "NONE")): retries = 0
tries = 0
while (name not in cadict) and (tries <= retries):
# Make a new entry in the PV-name dictionary
try:
channel = CaChannel.CaChannel()
if (timeout != None): channel.setTimeout(timeout)
channel.searchw(name)
cadict[name] = cadictEntry(channel)
except CaChannel.CaChannelException:
status = sys.exc_info()[1]
del channel
tries += 1
if (name not in cadict):
print("ca_util.checkName: Can't connect to '%s'" % name)
raise CaChannel.CaChannelException(status)
#######################################################################
def castate(name=None, timeout=None, retries=None):
"""usage: val = castate("xxx:m1.VAL", timeout=None, retries=None)
Try to read a PV, to find out whether it's really connected, and
whether caller is permitted to read and write it, without allowing
any exceptions to be thrown at the caller.
"""
global cadict, defaultTimeout, defaultRetries
if not name: return "Null name has no state"
# The only reliable way to check the *current* state of a PV is to attempt to use it.
try:
val = caget(name, timeout=timeout, retries=retries)
except CaChannel.CaChannelException:
pass
try:
checkName(name, timeout=timeout)
except CaChannel.CaChannelException:
return "not connected"
except:
return "error"
try:
state = cadict[name].channel.state()
except CaChannel.CaChannelException:
return "not connected"
except:
return "error"
else:
try:
read_access = cadict[name].channel.read_access()
write_access = cadict[name].channel.write_access()
if state in ca_states:
s = ca_states[state]
else:
s = "unknown state"
if not read_access: s += ", noread"
if not write_access: s += ", nowrite"
return s
except:
return "error"
#######################################################################
def caget(name, timeout=None, retries=None, req_type=None, req_count=None):
"""usage: val = caget("xxx:m1.VAL", timeout=None, retries=None,
req_type=None, req_count=None)"""
global cadict, defaultTimeout, defaultRetries
if not name:
print("caget: no PV name supplied")
raise ca_utilException(EXCEPTION_NULL_NAME)
if ((timeout==None) and (defaultTimeout != None)): timeout = defaultTimeout
if (timeout == "NONE"): timeout = None
if ((retries==None) and (defaultRetries != None)): retries = defaultRetries
if ((retries == None) or (retries == "NONE")): retries = 0
retries = max(retries,0)
retry = retries + 1
success = 0
# CaChannel sometimes chokes when it tries to process a channel that has been disconnected.
# The simplest fix is to clear the channel and reconnect to the PV, which we can do cleanly
# by deleting our dict entry for the channel, and calling checkName() to make a new entry.
while ((not success) and (retry > 0)):
checked = 0
while ((not checked) and (retry > 0)):
retry -= 1
try:
checkName(name, timeout=timeout)
except CaChannel.CaChannelException:
if retry <= 0:
raise
else:
checked = 1
entry = cadict[name]
if (timeout != None): entry.channel.setTimeout(timeout)
if req_type == None:
req_type=entry.field_type
# kludge for broken DBR_CHAR
if req_type == ca.DBR_CHAR:
req_type = ca.DBR_INT
if req_count == None:
req_count = entry.element_count
req_count = max(0, min(req_count, entry.element_count))
try:
val = entry.channel.getw(req_type=req_type, count=req_count)
except CaChannel.CaChannelException:
status = sys.exc_info()[1]
#print "getw threw an exception (%s)" % status
if ((int(status) == ca.ECA_BADTYPE) or (int(status) == ca.ECA_DISCONN)):
# Delete dictionary entry. This clears the CA connection.
print("caget: Repairing CA connection to ", name)
del cadict[name]
retry += 1
if retry <= 0:
raise
else:
success = 1
return val
def isNumber(s):
try:
n = int(s)
except:
return False
return True
#######################################################################
def same(value, readback, native_readback, field_type, read_check_tolerance):
"""For internal use by ca_util"""
#print "ca_util.same(): field_type=%s" % field_type
#print "ca_util.same(): value='%s'; readback='%s', native_readback='%s'" % (str(value), str(readback), str(native_readback))
#print "ca_util.same(): type(value)=%s; type(readback)=%s, type(native_readback)=%s" % (type(value),
# type(readback), type(native_readback))
if field_type in [ca.DBR_FLOAT, ca.DBR_DOUBLE]:
return (abs(float(readback)-float(value)) < read_check_tolerance)
elif field_type in [ca.DBR_INT, ca.DBR_SHORT, ca.DBR_LONG]:
return (abs(int(readback)-int(value)) == 0)
elif field_type == ca.DBR_ENUM:
if str(value) == str(readback):
return True
if str(value) == str(native_readback):
return True
return False
else:
return (str(value) == str(readback))
#######################################################################
def caput(name, value, timeout=None, req_type=None, retries=None, read_check_tolerance=None):
"""
usage: caput("xxx:m1.VAL", new_value, timeout=None, req_type=None,
retries=None, read_check_tolerance=None)
Put a value, and optionally check that the value arrived safely.
If read_check_tolerance == None (or is not supplied) then the default
read-check tolerance is used. If read_check_tolerance == "NONE", then no
read check is done.
If read_check_tolerance != "NONE", then floating point numbers must be
closer than the tolerance, and other types must agree exactly.
Note that defaults for timeout, retries, and read_check_tolerance can be
set for all ca_util functions, using the command set_ca_util_defaults().
"""
_caput("caput", name, value, 0, timeout, req_type, retries, read_check_tolerance)
#######################################################################
def __ca_util_waitCB(epics_args, user_args):
"""Function for internal use by caputw()."""
#print "__ca_util_waitCB: %s done\n" % user_args[0]
cadict[user_args[0]].callbackReceived = 1
#######################################################################
def caputw(name, value, wait_timeout=None, timeout=None, req_type=None, retries=None,
read_check_tolerance=None):
"""
usage: caputw("xxx:m1.VAL", new_value, wait_timeout=None, timeout=None,
req_type=None, retries=None, read_check_tolerance=None)
Put a value, optionally check that the value arrived safely, and wait (no
longer than wait_timeout) for processing to complete. If
read_check_tolerance == None (or is not supplied) then the default
read-check tolerance is used. If read_check_tolerance == "NONE", then no
read check is done. If read_check_tolerance != "NONE", then floating point
numbers must be closer than the tolerance, and other types must agree
exactly. Note that defaults for timeout, retries, and read_check_tolerance
can be set for all ca_util functions, using the command
set_ca_util_defaults().
"""
_caput("caputw", name, value, wait_timeout, timeout, req_type, retries, read_check_tolerance)
#######################################################################
def _caput(function, name, value, wait_timeout=None, timeout=None, req_type=None, retries=None, read_check_tolerance=None):
global cadict, defaultTimeout, defaultRetries, readCheckTolerance
#print function
if not name:
print("%s: no PV name supplied" % function)
raise ca_utilException(EXCEPTION_NULL_NAME)
if ((timeout == None) and (defaultTimeout != None)): timeout = defaultTimeout
if ((retries == None) and (defaultRetries != None)): retries = defaultRetries
if ((retries == None) or (retries == "NONE")): retries = 0
if ((read_check_tolerance == None) and (readCheckTolerance != None)):
read_check_tolerance = readCheckTolerance
retries = max(retries,0)
retry = retries + 1
success = 0
checkName(name, timeout=timeout, retries=retries)
while ((not success) and (retry > 0)):
retry -= 1
entry = cadict[name]
state = castate(name, timeout)
#print "%s: state='%s'" % (function, state)
if (state != 'connected'):
print("%s: Repairing CA connection to '%s'" % (function, name))
del cadict[name]
retry += 1
else:
if req_type == None:
req_type=entry.field_type
if ((timeout != None) and (timeout != "NONE")): entry.channel.setTimeout(timeout)
entry.callbackReceived = 0 # in case we're doing caputw()
#value = convertToType(value, req_type)
try:
if function == "caput":
entry.channel.putw(value, req_type=req_type)
else: #caputw
retval = entry.channel.array_put_callback(value,req_type,entry.element_count,__ca_util_waitCB,name)
except CaChannel.CaChannelException:
status = sys.exc_info()[1]
print("put() threw an exception (%s)" % status)
if ((int(status) == ca.ECA_BADTYPE) or (int(status) == ca.ECA_DISCONN)):
# Delete dictionary entry. This clears the CA connection.
print("%s: Repairing CA connection to '%s'" % (function, name))
del cadict[name]
retry += 1
if retry <= 0:
raise
entry.callbackReceived = 1
return
else:
if ((read_check_tolerance == None) or (read_check_tolerance == "NONE")):
success = True
else:
if timeout:
ca.pend_io(timeout)
else:
ca.pend_io(1.0)
readback_success = False
count = 0
while ((not readback_success) and (count < retries+1)):
try:
readback = caget(name, req_type=req_type)
native_readback = caget(name)
readback_success = True
if same(value, readback, native_readback, entry.field_type, read_check_tolerance):
success = True
#print "%s: Success\n" % (function)
else:
print("%s: readback '%s' disagrees with the value '%s' we wrote." % (function, readback, value))
raise ca_utilException(EXCEPTION_READBACK_DISAGREES)
entry.callbackReceived = 1
except CaChannel.CaChannelException:
print("%s: exception during readback." % (function))
count += 1
if success and (function == "caputw"):
start_time = time.time()
timed_out = 0
while (not entry.callbackReceived) and (not timed_out):
#print "waiting for ", name
time.sleep(0.1)
#ca.pend_io(0.1)
ca.poll()
if (not wait_timeout):
timed_out = 0
else:
timed_out = ((time.time()-start_time) > wait_timeout)
if not entry.callbackReceived:
print("Execution not completed by wait_timeout (%d seconds)" % wait_timeout)
#######################################################################
def camonitor(name, function, user_args=None, timeout=None, retries=None):
"""
usage: camonitor("xxx:m1.VAL", python_function, user_args, timeout=None,
retries=None)
Don't forget to call ca.pend_event(<pend_time_in_seconds>) periodically.
"""
global defaultTimeout, defaultRetries
if not name:
print("camonitor: no PV name supplied")
raise ca_utilException(EXCEPTION_NULL_NAME)
if not function:
print("camonitor: no callback function supplied")
raise ca_utilException(EXCEPTION_NULL_NAME)
if not user_args: user_args = name
if ((timeout==None) and (defaultTimeout != None)): timeout = defaultTimeout
if ((retries==None) and (defaultRetries != None)): retries = defaultRetries
if ((retries == None) or (retries == "NONE")): retries = 0
retries = max(retries,0)
retry = retries + 1
success = 0
while ((not success) and (retry > 0)):
checked = 0
while ((not checked) and (retry > 0)):
retry -= 1
try:
checkName(name, timeout=timeout)
except CaChannel.CaChannelException:
if retry <= 0:
raise
else:
checked = 1
entry = cadict[name]
if ((timeout != None) and (timeout != "NONE")): entry.channel.setTimeout(timeout)
try:
entry.channel.add_masked_array_event(entry.field_type,entry.element_count,ca.DBE_VALUE, function, user_args)
except CaChannel.CaChannelException:
status = sys.exc_info()[1]
#print "add_masked_array_event threw an exception (%s)" % status
if ((int(status) == ca.ECA_BADTYPE) or (int(status) == ca.ECA_DISCONN)):
# Delete dictionary entry. This clears the CA connection.
print("camonitor: Repairing CA connection to %s" % name)
del cadict[name]
retry += 1
if retry <= 0:
raise
else:
success = 1
#######################################################################
def caunmonitor(name, timeout=None):
"""usage: caunmonitor("xxx:m1.VAL", timeout=None)"""
global defaultTimeout
if not name:
print("caunmonitor: no PV name supplied")
raise ca_utilException(EXCEPTION_NULL_NAME)
if ((timeout==None) and (defaultTimeout != None)): timeout = defaultTimeout
if name not in cadict:
print("ca_util has no connection to '%s'" % name)
raise ca_utilException(EXCEPTION_NOT_CONNECTED)
channel = cadict[name].channel
if ((timeout != None) and (timeout != "NONE")): channel.setTimeout(timeout)
try:
channel.clear_event()
except CaChannel.CaChannelException:
status = sys.exc_info()[1]
print("caunmonitor: CaChannel exception, status=%d (%s)" % (status, ca.message(status)))
return
#######################################################################
def test_monitor_function(epics_args, user_args):
"""Example callback routine for use with camonitor()."""
print('test_monitor_function:')
print("...epics_args: %s" % repr(epics_args))
print("...user_args: %s" % repr(user_args))
#-------------------------------------------------------------------------------------------
# miscellaneous functions that might be useful, but haven't been integrated into the package
#######################################################################
def endianUs():
"""
usage: endianUs()
Returns one of "Little Endian", "Big Endian", "Unknown Endian".
"""
from struct import pack
if pack('<h', 1) == pack('=h',1):
return "Little Endian"
elif pack('>h', 1) == pack('=h',1):
return "Big Endian"
else:
return "Unknown Endian"
#######################################################################
def printExceptionInfo(maxTBlevel=15):
"""Intended for internal use by ca_util functions."""
import sys, traceback
cla, exc, trbk = sys.exc_info()
excName = cla.__name__
try:
excArgs = exc.__dict__["args"]
except KeyError:
excArgs = "<no args>"
excTb = traceback.format_tb(trbk, maxTBlevel)
print("Unanticipated exception: %s %s\n" % (excName, excArgs))
if (len(excTb) > 0):
print("Traceback:")
for trace in excTb:
print(trace)
return
+189
View File
@@ -0,0 +1,189 @@
""" @package cadefs
contstants and enumerated conststant
This defines constants and classes useful to inteprete code returned from CA library.
"""
#
CA_OP_GET = 0
CA_OP_PUT = 1
CA_OP_CREATE_CHANNEL = 2
CA_OP_ADD_EVENT = 3
CA_OP_CLEAR_EVENT = 4
CA_OP_OTHER = 5
# used with connection callbacks
CA_OP_CONN_UP = 6
CA_OP_CONN_DOWN = 7
# imported from caeventmask.h
DBE_VALUE =(1<<0)
DBE_LOG =(1<<1)
DBE_ALARM =(1<<2)
DBE_PROPERTY=(1<<3)
# also chekc ECA_IODONE/ECA_IOINPROGRESS in caError.py
IODONE = 42
IOINPROGRESS = 43
#
DBF_NATIVE=-1
DBF_STRING=0
DBF_INT = 1
DBF_SHORT =1
DBF_FLOAT =2
DBF_ENUM =3
DBF_CHAR =4
DBF_LONG = 5
DBF_DOUBLE = 6
DBF_NO_ACCES = 7
LAST_TYPE = DBF_DOUBLE
def VALID_DB_FIELD(x):
return ((x >= 0) and (x <= LAST_TYPE))
def INVALID_DB_FIELD(x):
return ((x < 0) or (x > LAST_TYPE))
#/* data request buffer types */
DBR_NATIVE= DBF_NATIVE
DBR_STRING = DBF_STRING
DBR_INT = DBF_INT
DBR_SHORT = DBF_INT
DBR_FLOAT = DBF_FLOAT
DBR_ENUM = DBF_ENUM
DBR_CHAR = DBF_CHAR
DBR_LONG = DBF_LONG
DBR_DOUBLE = DBF_DOUBLE
DBR_STS_STRING = 7
DBR_STS_SHORT = 8
DBR_STS_INT = DBR_STS_SHORT
DBR_STS_FLOAT = 9
DBR_STS_ENUM = 10
DBR_STS_CHAR = 11
DBR_STS_LONG = 12
DBR_STS_DOUBLE = 13
DBR_TIME_STRING = 14
DBR_TIME_INT = 15
DBR_TIME_SHORT = 15
DBR_TIME_FLOAT = 16
DBR_TIME_ENUM = 17
DBR_TIME_CHAR = 18
DBR_TIME_LONG = 19
DBR_TIME_DOUBLE = 20
DBR_GR_STRING = 21
DBR_GR_SHORT = 22
DBR_GR_INT = DBR_GR_SHORT
DBR_GR_FLOAT = 23
DBR_GR_ENUM = 24
DBR_GR_CHAR = 25
DBR_GR_LONG = 26
DBR_GR_DOUBLE = 27
DBR_CTRL_STRING = 28
DBR_CTRL_SHORT = 29
DBR_CTRL_INT = DBR_CTRL_SHORT
DBR_CTRL_FLOAT = 30
DBR_CTRL_ENUM = 31
DBR_CTRL_CHAR = 32
DBR_CTRL_LONG = 33
DBR_CTRL_DOUBLE = 34
DBR_PUT_ACKT = DBR_CTRL_DOUBLE + 1
DBR_PUT_ACKS = DBR_PUT_ACKT + 1
DBR_STSACK_STRING = DBR_PUT_ACKS + 1
LAST_BUFFER_TYPE = DBR_STSACK_STRING
def VALID_DB_REQ(x):
return ((x >= 0) and (x <= LAST_BUFFER_TYPE))
def INVALID_DB_REQ(x):
return ((x < 0) or (x > LAST_BUFFER_TYPE))
class AlarmSeverity:
"""Alarm Severity class
AlarmSeverity class is provided to keep constants representing EPICS channel severity status.
It also keeps strings and colors for each severity states.
"""
NO_ALARM =0x0
MINOR_ALARM=0x1
MAJOR_ALARM=0x2
INVALID_ALARM=0x3
ALARM_NSEV=INVALID_ALARM+1
Strings=(
"NO_ALARM",
"MINOR",
"MAJOR",
"INVALID",
)
Colors=("green","yellow","red","grey")
class AlarmStatus:
"""!
AlarmStatus class provides constants returned by EPICS Channe Access library as channel status code.
It also gives you strings for corresponding channel status.
"""
NO_ALARM = 0
READ_ALARM = 1
WRITE_ALARM = 2
#/* ANALOG ALARMS */
HIHI_ALARM = 3
HIGH_ALARM = 4
LOLO_ALARM = 5
LOW_ALARM = 6
#/* BINARY ALARMS */
STATE_ALARM = 7
COS_ALARM = 8
#/* other alarms */
COMM_ALARM = 9
TIMEOUT_ALARM = 10
HW_LIMIT_ALARM = 11
CALC_ALARM = 12
SCAN_ALARM = 13
LINK_ALARM = 14
SOFT_ALARM = 15
BAD_SUB_ALARM = 16
UDF_ALARM = 17
DISABLE_ALARM = 18
SIMM_ALARM = 19
READ_ACCESS_ALARM = 20
WRITE_ACCESS_ALARM = 21
Strings=(
"NO_ALARM",
"READ",
"WRITE",
"HIHI",
"HIGH",
"LOLO",
"LOW",
"STATE",
"COS",
"COMM",
"TIMEOUT",
"HWLIMIT",
"CALC",
"SCAN",
"LINK",
"SOFT",
"BAD_SUB",
"UDF",
"DISABLE",
"SIMM",
"READ_ACCESS",
"WRITE_ACCESS",
)
# ch_state={cs_never_conn=0, cs_prev_conn, cs_conn, cs_closed}
cs_never_conn= 0
cs_prev_conn = 1
cs_conn = 2
cs_closed = 3
class ch_state:
"""
ch_state class provides constants representing channel connection status.
"""
cs_never_conn= 0
cs_prev_conn = 1
cs_conn = 2
cs_closed = 3
Strings=(
"channel never connected",
"channel previously connected",
"channel connected",
"channel already closed",
)
+357
View File
@@ -0,0 +1,357 @@
"""
This module provides support for the EPICS motor record.
Author: Mark Rivers
Created: Sept. 16, 2002
Modifications:
"""
import time
import epicsPV
class epicsMotor:
"""
This module provides a class library for the EPICS motor record.
It uses the epicsPV class, which is in turn a subclass of CaChannel.
Virtual attributes:
These attributes do not appear in the dictionary for this class, but
are implemented with the __getattr__ and __setattr__ methods. They
simply do getw() or putw(value) to the appropriate motor record fields.
All attributes can be both read and written unless otherwise noted.
Attribute Description Field
--------- ----------------------- -----
slew_speed Slew speed or velocity .VELO
base_speed Base or starting speed .VBAS
acceleration Acceleration time (sec) .ACCL
description Description of motor .DESC
resolution Resolution (units/step) .MRES
high_limit High soft limit (user) .HLM
low_limit Low soft limit (user) .LLM
dial_high_limit High soft limit (dial) .DHLM
dial_low_limit Low soft limit (dial) .DLLM
backlash Backlash distance .BDST
offset Offset from dial to user .OFF
done_moving 1=Done, 0=Moving, read-only .DMOV
Exceptions:
The check_limits() method raises an "epicsMotorException" if a soft limit
or hard limit is detected. The move() and wait() methods call
check_limits() before they return, unless they are called with the
ignore_limits=1 keyword set.
Example use:
from epicsMotor import *
m=epicsMotor('13BMD:m38')
m.move(10) # Move to position 10 in user coordinates
m.move(100, dial=1) # Move to position 100 in dial coordinates
m.move(1, step=1, relative=1) # Move 1 step relative to current position
m.wait() # Wait for motor to stop moving
m.wait(start=1) # Wait for motor to start moving
m.wait(start=1, stop=1) # Wait for motor to start, then to stop
m.stop() # Stop moving immediately
high = m.high_limit # Get the high soft limit in user coordinates
m.dial_high_limit = 100 # Set the high limit to 100 in dial coodinates
speed = m.slew_speed # Get the slew speed
m.acceleration = 0.1 # Set the acceleration to 0.1 seconds
p=m.get_position() # Get the desired motor position in user coordinates
p=m.get_position(dial=1) # Get the desired motor position in dial coordinates
p=m.get_position(readback=1) # Get the actual position in user coordinates
p=m.get_position(readback=1, step=1) Get the actual motor position in steps
p=m.set_position(100) # Set the current position to 100 in user coordinates
# Puts motor in Set mode, writes value, puts back in Use mode.
p=m.set_position(10000, step=1) # Set the current position to 10000 steps
"""
def __init__(self, name):
"""
Creates a new epicsMotor instance.
Inputs:
name:
The name of the EPICS motor record without any trailing period or field
name.
Example:
m=epicsMotor('13BMD:m38')
"""
self.pvs = {'val' : epicsPV.epicsPV(name+'.VAL', wait=0),
'dval': epicsPV.epicsPV(name+'.DVAL', wait=0),
'rval': epicsPV.epicsPV(name+'.RVAL', wait=0),
'rlv' : epicsPV.epicsPV(name+'.RLV', wait=0),
'rbv' : epicsPV.epicsPV(name+'.RBV', wait=0),
'drbv': epicsPV.epicsPV(name+'.DRBV', wait=0),
'rrbv': epicsPV.epicsPV(name+'.RRBV', wait=0),
'dmov': epicsPV.epicsPV(name+'.DMOV', wait=0),
'stop': epicsPV.epicsPV(name+'.STOP', wait=0),
'velo': epicsPV.epicsPV(name+'.VELO', wait=0),
'vbas': epicsPV.epicsPV(name+'.VBAS', wait=0),
'accl': epicsPV.epicsPV(name+'.ACCL', wait=0),
'desc': epicsPV.epicsPV(name+'.DESC', wait=0),
'mres': epicsPV.epicsPV(name+'.MRES', wait=0),
'hlm': epicsPV.epicsPV(name+'.HLM', wait=0),
'llm': epicsPV.epicsPV(name+'.LLM', wait=0),
'dhlm': epicsPV.epicsPV(name+'.DHLM', wait=0),
'dllm': epicsPV.epicsPV(name+'.DLLM', wait=0),
'bdst': epicsPV.epicsPV(name+'.BDST', wait=0),
'set': epicsPV.epicsPV(name+'.SET', wait=0),
'lvio': epicsPV.epicsPV(name+'.LVIO', wait=0),
'lls': epicsPV.epicsPV(name+'.LLS', wait=0),
'hls': epicsPV.epicsPV(name+'.HLS', wait=0),
'off': epicsPV.epicsPV(name+'.OFF', wait=0)
}
# Wait for all PVs to connect
self.pvs['val'].pend_io()
def move(self, value, relative=0, dial=0, step=0, ignore_limits=0):
"""
Moves a motor to an absolute position or relative to the current position
in user, dial or step coordinates.
Inputs:
value:
The absolute position or relative amount of the move
Keywords:
relative:
Set relative=1 to move relative to current position.
The default is an absolute move.
dial:
Set dial=1 if "value" is in dial coordinates.
The default is user coordinates.
step:
Set step=1 if "value" is in steps.
The default is user coordinates.
ignore_limits:
Set ignore_limits=1 to suppress raising exceptions
if the move results in a soft or hard limit violation.
Notes:
The "step" and "dial" keywords are mutually exclusive.
The "relative" keyword can be used in user, dial or step
coordinates.
Examples:
m=epicsMotor('13BMD:m38')
m.move(10) # Move to position 10 in user coordinates
m.move(100, dial=1) # Move to position 100 in dial coordinates
m.move(2, step=1, relative=1) # Move 2 steps
"""
if (dial != 0):
# Position in dial coordinates
if (relative != 0):
current = self.get_position(dial=1)
self.pvs['dval'].putw(current+value)
else:
self.pvs['dval'].putw(value)
elif (step != 0):
# Position in steps
if (relative != 0):
current = self.get_position(step=1)
self.pvs['rval'].putw(current + value)
else:
self.pvs['rval'].putw(value)
else:
# Position in user coordinates
if (relative != 0):
self.pvs['rlv'].putw(value)
else:
self.pvs['val'].putw(value)
# Check for limit violations
if (ignore_limits == 0): self.check_limits()
def check_limits(self):
limit = self.pvs['lvio'].getw()
if (limit!=0):
raise epicsMotorException('Soft limit violation')
limit = self.pvs['lls'].getw()
if (limit!=0):
raise epicsMotorException('Low hard limit violation')
limit = self.pvs['hls'].getw()
if (limit!=0):
raise epicsMotorException('High hard limit violation')
def stop(self):
"""
Immediately stops a motor from moving by writing 1 to the .STOP field.
Examples:
m=epicsMotor('13BMD:m38')
m.move(10) # Move to position 10 in user coordinates
m.stop() # Stop motor
"""
self.pvs['stop'].putw(1)
def get_position(self, dial=0, readback=0, step=0):
"""
Returns the target or readback motor position in user, dial or step
coordinates.
Keywords:
readback:
Set readback=1 to return the readback position in the
desired coordinate system. The default is to return the
target position of the motor.
dial:
Set dial=1 to return the position in dial coordinates.
The default is user coordinates.
step:
Set step=1 to return the position in steps.
The default is user coordinates.
Notes:
The "step" and "dial" keywords are mutually exclusive.
The "readback" keyword can be used in user, dial or step
coordinates.
Examples:
m=epicsMotor('13BMD:m38')
m.move(10) # Move to position 10 in user coordinates
p=m.get_position(dial=1) # Read the target position in
# dial coordinates
p=m.get_position(readback=1, step=1) # Read the actual position in
# steps
"""
if (dial != 0):
if (readback != 0):
return self.pvs['drbv'].getw()
else:
return self.pvs['dval'].getw()
elif (step != 0):
if (readback != 0):
return self.pvs['rrbv'].getw()
else:
return self.pvs['rval'].getw()
else:
if (readback != 0):
return self.pvs['rbv'].getw()
else:
return self.pvs['val'].getw()
def set_position(self, position, dial=0, step=0):
"""
Sets the motor position in user, dial or step coordinates.
Inputs:
position:
The new motor position
Keywords:
dial:
Set dial=1 to set the position in dial coordinates.
The default is user coordinates.
step:
Set step=1 to set the position in steps.
The default is user coordinates.
Notes:
The "step" and "dial" keywords are mutually exclusive.
Examples:
m=epicsMotor('13BMD:m38')
m.set_position(10, dial=1) # Set the motor position to 10 in
# dial coordinates
m.set_position(1000, step=1) # Set the motor position to 1000 steps
"""
# Put the motor in "SET" mode
self.pvs['set'].putw(1)
if (dial != 0):
self.pvs['dval'].putw(position)
elif (step != 0):
self.pvs['rval'].putw(position)
else:
self.pvs['val'].putw(position)
# Put the motor back in "Use" mode
self.pvs['set'].putw(0)
def wait(self, start=0, stop=0, poll=0.01, ignore_limits=0):
"""
Waits for the motor to start moving and/or stop moving.
Keywords:
start:
Set start=1 to wait for the motor to start moving.
stop:
Set stop=1 to wait for the motor to stop moving.
poll:
Set this keyword to the time to wait between reading the
.DMOV field of the record to see if the motor is moving.
The default is 0.01 seconds.
ignore_limits:
Set ignore_limits=1 to suppress raising an exception if a soft or
hard limit is detected
Notes:
If neither the "start" nor "stop" keywords are set then "stop"
is set to 1, so the routine waits for the motor to stop moving.
If only "start" is set to 1 then the routine only waits for the
motor to start moving.
If both "start" and "stop" are set to 1 then the routine first
waits for the motor to start moving, and then to stop moving.
Examples:
m=epicsMotor('13BMD:m38')
m.move(100) # Move to position 100
m.wait(start=1, stop=1) # Wait for the motor to start moving
# and then to stop moving
"""
if (start == 0) and (stop == 0): stop=1
if (start != 0):
while(1):
done = self.pvs['dmov'].getw()
if (done != 1): break
time.sleep(poll)
if (stop != 0):
while(1):
done = self.pvs['dmov'].getw()
if (done != 0): break
time.sleep(poll)
if (ignore_limits == 0): self.check_limits()
def __getattr__(self, attrname):
if (attrname == 'slew_speed'): return self.pvs['velo'].getw()
elif (attrname == 'base_speed'): return self.pvs['vbas'].getw()
elif (attrname == 'acceleration'): return self.pvs['accl'].getw()
elif (attrname == 'description'): return self.pvs['desc'].getw()
elif (attrname == 'resolution'): return self.pvs['mres'].getw()
elif (attrname == 'high_limit'): return self.pvs['hlm'].getw()
elif (attrname == 'low_limit'): return self.pvs['llm'].getw()
elif (attrname == 'dial_high_limit'): return self.pvs['dhlm'].getw()
elif (attrname == 'dial_low_limit'): return self.pvs['dllm'].getw()
elif (attrname == 'backlash'): return self.pvs['bdst'].getw()
elif (attrname == 'offset'): return self.pvs['off'].getw()
elif (attrname == 'done_moving'): return self.pvs['dmov'].getw()
else: raise AttributeError(attrname)
def __setattr__(self, attrname, value):
if (attrname == 'pvs'): self.__dict__[attrname]=value
elif (attrname == 'slew_speed'): self.pvs['velo'].putw(value)
elif (attrname == 'base_speed'): self.pvs['vbas'].putw(value)
elif (attrname == 'acceleration'): self.pvs['accl'].putw(value)
elif (attrname == 'description'): self.pvs['desc'].putw(value)
elif (attrname == 'resolution'): self.pvs['mres'].putw(value)
elif (attrname == 'high_limit'): self.pvs['hlm'].putw(value)
elif (attrname == 'low_limit'): self.pvs['llm'].putw(value)
elif (attrname == 'dial_high_limit'): self.pvs['dhlm'].putw(value)
elif (attrname == 'dial_low_limit'): self.pvs['dllm'].putw(value)
elif (attrname == 'backlash'): self.pvs['bdst'].putw(value)
elif (attrname == 'offset'): self.pvs['off'].putw(value)
else: raise AttributeError(attrname)
class epicsMotorException(Exception):
def __init__(self, args=None):
self.args=args
+273
View File
@@ -0,0 +1,273 @@
"""
This module defines the epicsPV class, which adds additional features to
Geoff Savage's CaChannel class.
Author: Mark Rivers
Created: Sept. 16, 2002.
Modifications:
"""
import CaChannel
class epicsPV(CaChannel.CaChannel):
"""
This class subclasses Geoff Savage's CaChannel class to add the following
features:
- If a PV name is given then the class constructor will do a searchw()
by default.
- setMonitor() sets a generic callback routine for value change events.
Subsequent getw(), getValue() or array_get() calls will return the
value from the most recent callback, and hence do not result in any
network activity or latency. This can greatly improve performance.
- checkMonitor() returns a flag to indicate if a callback has occured
since the last call to checkMonitor(), getw(), getValue() or
array_get(). It can be used to increase efficiency in polling
applications.
- getControl() reads the "control" and other information from an
EPICS PV without having to use callbacks.
In addition to the PV value, this will return the graphic, control and
alarm limits, etc.
- putWait() calls array_put_callback() and waits for the callback to
occur before it returns. This allows programs to use array_put_callback()
synchronously and without user-written callbacks.
Created: Mark Rivers, Sept. 16, 2002.
Modifications:
"""
def __init__(self, pvName=None, wait=1):
"""
Keywords:
pvName:
An optional name of an EPICS Process Variable.
wait:
If wait==1 and pvName != None then this constructor will do a
CaChannel.searchw() on the PV. If wait==0 and pvName != None then
this constructor will do a CaChannel.search() on the PV, and the user
must subsequently do a pend_io() on this or another epicsPV or CaChannel
object.
Procedure:
Invokes CaChannel.__init__() and then searchw() or search() as explained
above
"""
# Invoke the base class initialization
self.callBack = callBack()
CaChannel.CaChannel.__init__(self)
if (pvName != None):
if (wait):
self.searchw(pvName)
else:
self.search(pvName)
def setMonitor(self):
"""
Sets a generic callback routine for value change events.
Subsequent getw(), getValue() or array_get() calls will return the
value from the most recent callback, do not result in any network
latency. This can greatly improve efficiency.
"""
self.add_masked_array_event(None, None, CaChannel.ca.DBE_VALUE,
getCallback, self.callBack)
self.callBack.monitorState = 1
def clearMonitor(self):
"""
Cancels the effect of a previous call to setMonitor().
Calls CaChannel.clear_event().
Subsequent getw(), getValue() or array_get() calls will no longer
return the value from the most recent callback, but will actually result
in channel access calls.
"""
self.clear_event()
self.callBack.monitorState = 0
def checkMonitor(self):
"""
Returns 1 to indicate if a value callback has occured
since the last call to checkMonitor(), getw(), getValue() or
array_get(), indicating that a new value is available. Returns 0 if
no such callback has occurred.
It can be used to increase efficiency in polling applications.
"""
# This should be self.poll(), but that is generating errors
self.pend_event(.0001)
m = self.callBack.newMonitor
self.callBack.newMonitor = 0
return m
def getControl(self, req_type=None, count=None, wait=1, poll=.01):
"""
Provides a method to read the "control" and other information from an
EPICS PV without having to use callbacks.
It calls CaChannel.array_get_callback() with a database request type of
CaChannel.ca.dbf_type_to_DBR_CTRL(req_type).
In addition to the PV value, this will return the graphic, control and
alarm limits, etc.
Example:
>>> pv = epicsPV('13IDC:m1')
>>> pv.getControl()
>>> for field in dir(pv.callBack):
>>> print field, ':', getattr(pv.callBack, field)
chid : _bfffec34_chid_p
count : 1
monitorState : 0
newMonitor : 1
putComplete : 0
pv_loalarmlim : 0.0
pv_loctrllim : -22.0
pv_lodislim : -22.0
pv_lowarnlim : 0.0
pv_precision : 4
pv_riscpad0 : 256
pv_severity : 0
pv_status : 0
pv_units : mm
pv_upalarmlim : 0.0
pv_upctrllim : 28.0
pv_updislim : 28.0
pv_upwarnlim : 0.0
pv_value : -15.0
status : 1
type : 34
Note the fields such as pv_plocrtllim, the lower control limit, and
pv_precision, the display precision.
Keywords:
wait:
If this keyword is 1 (the default) then this routine waits for
the callback before returning. If this keyword is 0 then it is
the user's responsibility to wait or check for the callback
by calling checkMonitor().
poll:
The timeout for pend_event() calls, waiting for the callback
to occur. Shorter times reduce the latency at the price of CPU
cycles.
"""
if (req_type == None): req_type = self.field_type()
if (wait != 0): self.callBack.newMonitor = 0
self.array_get_callback(CaChannel.ca.dbf_type_to_DBR_CTRL(req_type),
count, getCallback, self.callBack)
if (wait != 0):
while(self.callBack.newMonitor == 0):
self.pend_event(poll)
def array_get(self, req_type=None, count=None):
"""
If setMonitor() has not been called then this function simply calls
CaChannel.array_get(). If setMonitor has been called then it calls
CaChannel.pend_event() with a very short timeout, and then returns the
PV value from the last callback.
"""
if (self.callBack.monitorState != 0):
# This should be self.poll(), but that is generating errors
self.pend_event(.0001)
if (self.callBack.monitorState == 2):
self.callBack.newMonitor = 0
return self.callBack.pv_value
else:
return CaChannel.CaChannel.array_get(self, req_type, count)
def getw(self, req_type=None, count=None):
"""
If setMonitor() has not been called then this function simply calls
CaChannel.getw(). If setMonitor has been called then it calls
CaChannel.pend_event() with a very short timeout, and then returns the
PV value from the last callback.
"""
if (self.callBack.monitorState != 0):
# This should be self.poll(), but that is generating errors
self.pend_event(.0001)
if (self.callBack.monitorState == 2):
self.callBack.newMonitor = 0
if (count == None):
return self.callBack.pv_value
else:
return self.callBack.pv_value[0:count]
else:
return CaChannel.CaChannel.getw(self, req_type, count)
def getValue(self):
"""
If setMonitor() has not been called then this function simply calls
CaChannel.getValue(). If setMonitor has been called then it calls
CaChannel.pend_event() with a very short timeout, and then returns the
PV value from the last callback.
"""
if (self.callBack.monitorState != 0):
# This should be self.poll(), but that is generating errors
self.pend_event(.0001)
if (self.callBack.monitorState == 2):
self.callBack.newMonitor = 0
return self.callBack.pv_value
else:
return CaChannel.CaChannel.getValue(self)
def putWait(self, value, req_type=None, count=None, poll=.01):
"""
Calls CaChannel.array_put_callback() and waits for the callback to
occur before it returns. This allows programs to use array_put_callback()
without having to handle asynchronous callbacks.
Keywords:
req_type:
See CaChannel.array_put_callback()
count:
See CaChannel.array_put_callback()
poll:
The timeout for pend_event() calls, waiting for the callback
to occur. Shorter times reduce the latency at the price of CPU
cycles.
"""
self.callBack.putComplete=0
self.array_put_callback(value, req_type, count, putCallBack, self.callBack)
while(self.callBack.putComplete == 0):
self.pend_event(poll)
class callBack:
"""
This class is used by the epicsPV class to handle callbacks. It is required
to avoid circular references to the epicsPV object itself when dealing with
callbacks, in order to allow the CaChannel destructor to be called.
Users will only be interested in the fields that are copied to this class in
the callback resulting from a call to epicsPV.getControl().
"""
def __init__(self):
self.newMonitor = 0
self.putComplete = 0
self.monitorState = 0
# monitorState:
# 0=not monitored
# 1=monitor requested, but no callback yet
# 2=monitor requested, callback has arrived
def putCallBack(epicsArgs, userArgs):
"""
This is the generic callback function used by the epicsPV.putWait() method.
It simply sets the callBack.putComplete flag to 1.
"""
userArgs[0].putComplete=1
def getCallback(epicsArgs, userArgs):
"""
This is the generic callback function enabled by the epicsPV.setMonitor() method.
It sets the callBack.monitorState flag to 2, indicating that a monitor has
been received. It copies all of the attributes in the epicsArgs dictionary
to the callBack attribute of the epicsPV object.
"""
for key in epicsArgs.keys():
setattr(userArgs[0], key, epicsArgs[key])
if (userArgs[0].monitorState == 1): userArgs[0].monitorState = 2
userArgs[0].newMonitor = 1