ca: Low-level Channel Access module

The ca module provides a low-level wrapping of the EPICS Channel Access (CA) library, using ctypes. Most users of the epics module will not need to be concerned with most of the details here, and will instead use the simple procedural interface (epics.caget(), epics.caput() and so on), or use the epics.PV class to create and use epics PV objects.

General description, difference with C library

The ca module provides a fairly complete mapping of the C interface to the CA library while also providing a pleasant Python experience. It is expected that anyone using this module is somewhat familiar with Channel Access and knows where to consult the Channel Access Reference Documentation. Here, we focus on the differences with the C interface, and assume a general understanding of what the functions are meant to do.

Name Mangling

As a general rule, a CA function named ca_XXX in the C library will have the equivalent function called XXX in the ca module. This is because the intention is that one will import the ca module with

>>> from epics import ca

so that the Python function ca.XXX() will corresponds to the C function ca_XXX. That is, the CA library called its functions ca_XXX because C does not have namespaces. Python does have namespaces, and so they are used.

Similar name un-mangling also happens with the DBR prefixes for constants, held here in the dbr module. Thus, the C constant DBR_STRING becomes dbr.STRING in Python.

Supressing Messages from libCA

Added in version 3.5.7.

When using pyepics or any CA client, messages from libCA are sometimes seen to be written to a Terminal screen. These might be messages about like:

failed to start executable - "caRepeater"

or Virtual Circuit Disconnect messages if an Epics IOC goes down or offline. These messages are mostly informational and harmless. But they are also fairly annoying.

Starting with version 3.5.7, these messages are disabled by default. You can re-enable these by setting:

from epics import ca
ca.WITH_CA_MESSAGES = True

before initializing CA (say, creating any PVs or Channels).

You can also run the functions disable_ca_messages() or enable_ca_messages() at runtime. However, due to limitaions in Python ctypes modules, re-enabling messages once disabled does not fully work: The messages from CA will often including multiple values that should be formatted as with C’s printf function. But, when re-enabled, only the first formatting string will be received through the ctypes interface.

Setting ca.WITH_CA_MESSAGES before CA is initialized will correctly receive the full messages.

Other Changes and Omissions

Several function in the C version of the CA library are not implemented in the Python module. Most of these unimplemented functions are currently seen as unnecessary for Python, though some of these could be added without much trouble if needed. See Omissions for further details.

In addition, while the CA library supports several DBR types in C, not all of these are supported in Python. Only native types and their DBR_TIME and DBR_CTRL variants are supported here. The DBR_STS and DBR_GR variants are not, as they are subsets of the DBR_CTRL type, and space optimization is not something you’ll be striving for with Python. Several dbr_XXX functions are also not supported, as they appear to be needed only to be able to dynamically allocate memory, which is not necessary in Python.

Initialization, Finalization, and Life-cycle

The Channel Access library must be initialized before it can be used. There are 3 main reasons for this need:

1. CA requires a context model (preemptive callbacks or non-preemptive callbacks) to be specified before any actual calls can be made.

2. the ctypes interface requires that the shared library be loaded before it is used.

3. ctypes also requires that references to the library and callback functions be kept for the life-cycle of CA-using part of a program (or else they will be garbage collected).

As far as is possible, the ca module hides the details of the CA lifecyle from the user, so that it is not necessary to to worry about explicitly initializing a Channel Access session. Instead, the library is initialized as soon as it is needed, and intervention is really only required to change default settings. The ca module also handles finalizing the CA session, so that core-dumps and warning messages do not happen due to CA still being ‘alive’ as a program ends.

Because some users may wish to customize the initialization and finalization process, the detailed steps will be described here. These initialization and finalization tasks are handled in the following way:

  • The libca variable in the ca module holds a permanent, global reference to the CA shared object library (DLL).

  • the function initialize_libca() is called to initialize libca. This function takes no arguments, but does use the global Boolean PREEMPTIVE_CALLBACK (default value of True) to control whether preemptive callbacks are used.

  • the function finalize_libca() is used to finalize libca. Normally, this is function is registered to be called when a program ends with atexit.register(). Note that this only gets called on a graceful shutdown. If the program crashes (for a non-CA related reason, for example), this finalization may not be done, and connections to Epics Variables may not be closed completely on the Channel Access server.

epics.ca.PREEMPTIVE_CALLBACK

sets whether preemptive callbacks will be used. The default value is True. If you wish to run without preemptive callbacks this variable MUST be set before any other use of the CA library. With preemptive callbacks enabled, EPICS communication will not require client code to continually poll for changes. With preemptive callback disables, you will need to frequently poll epics with pend_io() and func:pend_event.

epics.ca.DEFAULT_CONNECTION_TIMEOUT

sets the default timeout value (in seconds) for connect_channel(). The default value is 2.0

epics.ca.AUTOMONITOR_MAXLENGTH

sets the default array length (ie, how many elements an array has) above which automatic conversion to numpy arrays and automatic monitoring for PV variables is suppressed. The default value is 65536. To be clear: waveforms with fewer elements than this value will be automatically monitored changes, and will be converted to numpy arrays (if numpy is installed). Larger waveforms will not be automatically monitored.

Working with waveform / array data and Strategies for working with large arrays for more details.

epics.ca.WITH_CA_MESSAGES

whether to enable message from CA (default is False, set to True to enable messages.

Supressing Messages from libCA for more details.

Using the CA module

Many general-purpose CA functions that deal with general communication and threading contexts are very close to the C library:

epics.ca.initialize_libca()

Initialize the Channel Access library.

This loads the shared object library (DLL) to establish Channel Access Connection. The value of PREEMPTIVE_CALLBACK sets the pre-emptive callback model.

This must be called prior to any actual use of the CA library, but

will be called automatically by the the withCA() decorator, so you should not need to call this directly from most real programs.

Returns:
libcaobject

ca library object, used for all subsequent ca calls

Notes

  1. This function must be called prior to any real CA calls.

  2. This function will disable messages from CA.

See the withCA decorator to ensure CA is initialized

epics.ca.finalize_libca()

shutdown channel access:

run clear_channel() for all chids in _cache, then calls flush_io() and poll() a few times.

Parameters:
maxtimefloat

maximimum time (in seconds) to wait for flush_io() and poll() to complete.

epics.ca.pend_io(timeout=1.0)

polls CA for i/o.

epics.ca.pend_event(timeout=1.e-5)

polls CA for events

epics.ca.poll(evt=1.e-5[, iot=1.0])

a convenience function which is equivalent to:: pend_event(evt) pend_io_(iot)

epics.ca.create_context()

Create a new context, using the value of PREEMPTIVE_CALLBACK to set the context type. Note that both context_create and create_context (which is more consistent with the Verb_Object of the rest of the CA library) are supported.

Parameters:
ctxint

0 – No preemptive callbacks, 1 – use use preemptive callbacks, None – use value of PREEMPTIVE_CALLBACK

epics.ca.destroy_context()

destroy current context

epics.ca.current_context()

return the current context

epics.ca.attach_context(context)

attach to the supplied context

epics.ca.detach_context()

detach context

epics.ca.use_initial_context()

Attaches to the context created when libca is initialized. Using this function is recommended when writing threaded programs that using CA.

See the advanced section in doc for further discussion.

epics.ca.client_status(context, level)

print (to stderr) information about Channel Access status, including status for each channel, and search and connection statistics.

epics.ca.version()

Print Channel Access version string. Currently, this should report ‘4.13’

epics.ca.message(status)

Print a message corresponding to a Channel Access status return value.

epics.ca.flush_io()

flush i/o

epics.ca.replace_printf_handler(writer=None)

replace the normal printf() output handler with the supplied writer function

Parameters:
writer: callable or None [default]

function to use for handling messages

Notes

  1. writer=None will suppress all CA messages.

  2. writer should have a signature of writer(*args), as sys.stderr.write and sys.stdout.write do.

  3. Due to limitations in ctypes, this will not work as well as expected. Once disabled, re-enabling ca_messages will receive only the first string argument, not the list of strings to be formatted.

epics.ca.disable_ca_messages()

disable messages rom CA: replace_printf_handler(None)

epics.ca.enable_ca_messages(writer='stderr')

enable messages from CA using the supplier writer

Parameters:
writer: callable, `stderr`, `stdout`, or `None`

function to use for handling messages

Notes

  1. writer=None will suppress all CA messages.

  2. writer should have a signature of writer(*args), as sys.stderr.write and sys.stdout.write do, though only the first value will actually be use.

  3. Due to limitations in ctypes, this will not work as well as expected. Once disabled, re-enabling ca_messages will receive only the first string argument, not the list of strings to be formatted.

Creating and Connecting to Channels

The basic channel object is the Channel ID or chid. With the CA library (and ca module), one creates and acts on the chid values. These are simply ctypes.c_long (C long integers) that hold the memory address of the C representation of the channel, but it is probably a good idea to treat these as object instances.

epics.ca.create_channel(pvname, connect=False, callback=None, auto_cb=True)

create a Channel for a given pvname

creates a channel, returning the Channel ID chid used by other functions to identify this channel.

Parameters:
pvnamestring

the name of the PV for which a channel should be created.

connectbool

whether to (try to) connect to PV as soon as possible.

auto_cbbool

whether to automatically use an internal connection callback.

callbackcallable or None

user-defined Python function to be called when the connection state change s.

Returns:
chidctypes.c_long

channel ID.

Notes

1. The user-defined connection callback function should be prepared to accept keyword arguments of

keyword

meaning

pvname

name of PV

chid

Channel ID

conn

whether channel is connected

2. If auto_cb is True, an internal connection callback is used so that you should not need to explicitly connect to a channel, unless you are having difficulty with dropped connections.

3. If the channel is already connected for the PV name, the callback will be called immediately.

epics.ca.connect_channel(chid, timeout=None, verbose=False)

connect to a channel, waiting up to timeout for a channel to connect. It returns the connection state, True or False.

This is usually not needed, as implicit connection will be done when needed in most cases.

Parameters:
chidctypes.c_long

Channel ID

timeoutfloat

maximum time to wait for connection.

verbosebool

whether to print out debugging information

Returns:
connection_statebool

that is, whether the Channel is connected

Notes

  1. If timeout is None, the value of DEFAULT_CONNECTION_TIMEOUT is used (defaults to 2.0 seconds).

2. Normally, channels will connect in milliseconds, and the connection callback will succeed on the first attempt.

3. For un-connected Channels (that are nevertheless queried), the ‘ts’ (timestamp of last connection attempt) and ‘failures’ (number of failed connection attempts) from the _cache will be used to prevent spending too much time waiting for a connection that may never happen.

Many other functions require a valid Channel ID, but not necessarily a connected Channel. These functions are essentially identical to the CA library versions, and include:

epics.ca.name(chid)

return PV name for channel name

epics.ca.host_name(chid)

return host name and port serving Channel

epics.ca.element_count(chid)

return number of elements in Channel’s data. 1 for most Channels, > 1 for waveform Channels

epics.ca.replace_access_rights_event(chid, callback=None)
epics.ca.read_access(chid)

return read access for a Channel: 1 for True, 0 for False.

epics.ca.write_access(chid)

return write access for a channel: 1 for True, 0 for False.

epics.ca.field_type(chid)

return the integer DBR field type.

See the ftype column from Table of DBR Types.

epics.ca.clear_channel(chid)

clear the channel

epics.ca.state(chid)

return state (that is, attachment state) for channel

A few additional pythonic functions have been added:

epics.ca.isConnected(chid)

return whether channel is connected: dbr.CS_CONN==state(chid)

This is True for a connected channel, False for an unconnected channel.

epics.ca.access(chid)

returns a string describing read/write access: one of no access, read-only, write-only, or read/write

epics.ca.promote_type(chid[, use_time=False[, use_ctrl=False]])

promotes the native field type of a chid to its TIME or CTRL variant. Returns the integer corresponding to the promoted field value.

See Table of DBR Types.

epics.ca._cache

The ca module keeps a global cache of Channels that holds connection status and a bit of internal information for all known PVs. This cache is not intended for general use.

epics.ca.show_cache(print_out=True)

print out a listing of PVs in the current session to standard output. Use the print_out=False option to be returned the listing instead of having it printed out.

epics.ca.clear_cache()

Clears global caches of Epics CA connections, and fully detaches from the CA context. This is important when doing multiprocessing (and is done internally by CAProcess), but can be useful to fully reset a Channel Access session.

Any class PV created prior this call without using get_pv() have to be disconnected (use PV.disconnect()) explicitly because disconnection clears subscriptions to events of Epics CA connection which are to be cleared here. No instance of the class PV created prior this call should be used after because underlaying Epics CA connections are cleared here. Failing to follow these rules may cause your application to experience a random SIGSEGV from inside Epics binaries.

Use register_clear_cache() to register a function which will be called before Epics CA connections are cleared.

This function is not thread safe.

Interacting with Connected Channels

Once a chid is created and connected there are several ways to communicating with it. These are primarily encapsulated in the functions get(), put(), and create_subscription(), with a few additional functions for retrieving specific information.

These functions are where this python module differs the most from the underlying CA library, and this is mostly due to the underlying CA function requiring the user to supply DBR TYPE and count as well as chid and allocated space for the data. In python none of these is needed, and keyword arguments can be used to specify such options.

epics.ca.get(chid, ftype=None, count=None, as_string=False, as_numpy=True, wait=True, timeout=None)

return the current value for a Channel. Note that there is not a separate form for array data.

Parameters:
chidctypes.c_long

Channel ID

ftypeint

field type to use (native type is default)

countint

maximum element count to return (full data returned by default)

as_stringbool

whether to return the string representation of the value. See notes below.

as_numpybool

whether to return the Numerical Python representation for array / waveform data.

waitbool

whether to wait for the data to be received, or return immediately.

timeoutfloat

maximum time to wait for data before returning None.

Returns:
dataobject

Normally, the value of the data. Will return None if the channel is not connected, wait=False was used, or the data transfer timed out.

Notes

  1. Returning None indicates an incomplete get

2. The as_string option is not as complete as the as_string argument for PV.get(). For Enum types, the name of the Enum state will be returned. For waveforms of type CHAR, the string representation will be returned. For other waveforms (with count > 1), a string like <array count=3, type=1> will be returned.

3. The as_numpy option will convert waveform data to be returned as a numpy array. This is only applied if numpy can be imported.

4. The wait option controls whether to wait for the data to be received over the network and actually return the value, or to return immediately after asking for it to be sent. If wait=False (that is, immediate return), the get operation is said to be incomplete. The data will be still be received (unless the channel is disconnected) eventually but stored internally, and can be read later with get_complete(). Using wait=False can be useful in some circumstances.

5. The timeout option sets the maximum time to wait for the data to be received over the network before returning None. Such a timeout could imply that the channel is disconnected or that the data size is larger or network slower than normal. In that case, the get operation is said to be incomplete, and the data may become available later with get_complete().

See Table of DBR Types for a listing of values of ftype,

See Strategies for working with large arrays for a discussion of strategies for how to best deal with very large arrays.

See Strategies for connecting to a large number of PVs for a discussion of when using wait=False can give a large performance boost.

See The wait and timeout options for get(), ca.get_complete() for further discussion of the wait and timeout options and the associated get_complete() function.

epics.ca.get_with_metadata(chid, ftype=None, count=None, as_string=False, as_numpy=True, wait=True, timeout=None)

Return the current value along with metadata for a Channel

Parameters:
chidctypes.c_long

Channel ID

ftypeint

field type to use (native type is default)

countint

maximum element count to return (full data returned by default)

as_stringbool

whether to return the string representation of the value. See notes.

as_numpybool

whether to return the Numerical Python representation for array / waveform data.

waitbool

whether to wait for the data to be received, or return immediately.

timeoutfloat

maximum time to wait for data before returning None.

Returns:
datadict or None

The dictionary of data, guaranteed to at least have the ‘value’ key. Depending on ftype, other keys may also be present:

{'precision', 'units', 'status', 'severity', 'enum_strs', 'status',
'severity', 'timestamp', 'posixseconds', 'nanoseconds',
'upper_disp_limit', 'lower_disp_limit', 'upper_alarm_limit',
'upper_warning_limit', 'lower_warning_limit','lower_alarm_limit',
'upper_ctrl_limit', 'lower_ctrl_limit'}

Returns None if the channel is not connected, wait=False was used, or the data transfer timed out.

See get() for additional usage notes.
epics.ca.get_complete(chid, ftype=None, count=None, as_string=False, as_numpy=True, timeout=None)

returns the current value for a Channel, completing an earlier incomplete get() that returned None, either because wait=False was used or because the data transfer did not complete before the timeout passed.

Parameters:
chidctypes.c_long

Channel ID

ftypeint

field type to use (native type is default)

countint

maximum element count to return (full data returned by default)

as_stringbool

whether to return the string representation of the value.

as_numpybool

whether to return the Numerical Python representation for array / waveform data.

timeoutfloat

maximum time to wait for data before returning None.

Returns:
dataobject

This function will return None if the previous get() actually completed, or if this data transfer also times out.

Notes

1. The default timeout is dependent on the element count:: default_timout = 1.0 + log10(count) (in seconds)

  1. Consult the doc for get() for more information.

See The wait and timeout options for get(), ca.get_complete() for further discussion.

epics.ca.get_complete_with_metadata(chid, ftype=None, count=None, as_string=False, as_numpy=True, timeout=None)

Returns the current value and associated metadata for a Channel

This completes an earlier incomplete get() that returned None, either because wait=False was used or because the data transfer did not complete before the timeout passed.

Parameters:
chidctypes.c_long

Channel ID

ftypeint

field type to use (native type is default)

countint

maximum element count to return (full data returned by default)

as_stringbool

whether to return the string representation of the value.

as_numpybool

whether to return the Numerical Python representation for array / waveform data.

timeoutfloat

maximum time to wait for data before returning None.

Returns:
datadict or None

This function will return None if the previous get() actually completed, or if this data transfer also times out.

See get_complete() for additional usage notes.
epics.ca.put(chid, value, wait=False, timeout=30, callback=None, callback_data=None, ftype=None)

sets the Channel to a value, with options to either wait (block) for the processing to complete, or to execute a supplied callback function when the process has completed.

Parameters:
chidctypes.c_long

Channel ID

waitbool

whether to wait for processing to complete (or time-out) before returning.

timeoutfloat

maximum time to wait for processing to complete before returning anyway.

callbackNone or callable

user-supplied function to run when processing has completed.

callback_dataobject

extra data to pass on to a user-supplied callback function.

ftypeNone or int (valid dbr type)

force field type to be a non-native form (None will use native form)

Returns:
statusint

1 for success, -1 on time-out

Notes

  1. Specifying a callback will override setting wait=True.

  2. A put-callback function will be called with keyword arguments

    pvname=pvname, data=callback_data

See User-supplied Callback functions for more on this put callback,

epics.ca.create_subscription(chid, use_time=False, use_ctrl=False, mask=None, callback=None)

create a subscription to changes. Sets up a user-supplied callback function to be called on any changes to the channel.

Parameters:
chidctypes.c_long

channel ID

use_timebool

whether to use the TIME variant for the PV type

use_ctrlbool

whether to use the CTRL variant for the PV type

ftypeinteger or None

ftype to use, overriding native type, use_time or use_ctrl if None, the native type is looked up, which requires a connected channel.

maskinteger or None

bitmask combination of dbr.DBE_ALARM, dbr.DBE_LOG, and dbr.DBE_VALUE, to control which changes result in a callback. If None, defaults to DEFAULT_SUBSCRIPTION_MASK.

callbackNone or callable

user-supplied callback function to be called on changes

timeoutNone or int

connection timeout used for unconnected channels.

Returns:
(callback_ref, user_arg_ref, event_id)

The returned tuple contains callback_ref an user_arg_ref which are references that should be kept for as long as the subscription lives (otherwise they may be garbage collected, causing no end of trouble). event_id is the id for the event (useful for clearing a subscription).

Notes

Keep the returned tuple in named variable!! if the return argument gets garbage collected, a coredump will occur.

If the channel is not connected, the ftype must be specified for a successful subscription.

See User-supplied Callback functions for more on writing the user-supplied callback,

Warning

event_id is the id for the event (useful for clearing a subscription). You must keep the returned tuple in active variables, either as a global variable or as data in an encompassing class. If you do not keep this data, the return value will be garbage collected, the C-level reference to the callback will disappear, and you will see coredumps.

On Linux, a message like:

python: Objects/funcobject.c:451: func_dealloc: Assertion 'g->gc.gc_refs != (-2)' failed.
Abort (core dumped)

is a hint that you have not kept this data.

epics.ca.DEFAULT_SUBSCRIPTION_MASK

This value is the default subscription type used when calling create_subscription() with mask=None. It is also used by default when creating a PV object with auto_monitor is set to True.

The initial default value is dbr.DBE_ALARM|dbr.DBE_VALUE (i.e. update on alarm changes or value changes which exceeds the monitor deadband.) The other possible flag in the bitmask is dbr.DBE_LOG for archive-deadband changes.

If this value is changed, it will change the default for all subsequent calls to create_subscription(), but it will not change any existing subscriptions.

epics.ca.clear_subscription(event_id)

cancel subscription given its event_id

Several other functions are provided:

epics.ca.get_timestamp(chid)

return the timestamp of a Channel – the time of last update.

epics.ca.get_severity(chid)

return the severity of a Channel.

epics.ca.get_precision(chid)

return the precision of a Channel. For Channels with native type other than FLOAT or DOUBLE, this will be 0

epics.ca.get_enum_strings(chid)

return list of names for ENUM states of a Channel. Returns None for non-ENUM Channels

epics.ca.get_ctrlvars(chid)

return the CTRL fields for a Channel.

Depending on the native type, the keys may include

status, severity, precision, units, enum_strs*, upper_disp_limit, lower_disp_limit, upper_alarm_limit*, lower_alarm_limit, upper_warning_limit*, lower_warning_limit, upper_ctrl_limit, lower_ctrl_limit

Notes

enum_strs will be a list of strings for the names of ENUM states.

See Table of Control Attributes

Table of Control Attributes

attribute

data types

status

severity

precision

0 for all but double, float

units

enum_strs

enum only

upper_disp_limit

lower_disp_limit

upper_alarm_limit

lower_alarm_limit

upper_warning_limit

lower_warning_limit

upper_ctrl_limit

lower_ctrl_limit

Note that enum_strs will be a tuple of strings for the names of ENUM states.

epics.ca.get_timevars(chid)

returns a dictionary of TIME fields for a Channel. This will contain keys of status, severity, and timestamp.

Synchronous Groups

Warning

Synchronous groups are simulated in pyepics, but are not recommended, and probably don’t really make sense for usage within pyepics and using asynchronous i/o anyway.

Synchronous Groups are can be used to ensure that a set of Channel Access calls all happen together, as if in a transaction. Synchronous Groups should be avoided in pyepics, and are not well tested. They probably make little sens in the context of asynchronous I/O. The documentation here is given for historical purposes.

The idea is to first create a synchronous group, then add a series of sg_put() and sg_get() which do not happen immediately, and finally block while all the channel access communication is done for the group as a unit. It is important to not issue pend_io() during the building of a synchronous group, as this will cause pending sg_put() and sg_get() to execute.

epics.ca.sg_create()

create synchronous group. Returns a group id, gid, which is used to identify this group and to be passed to all other synchronous group commands.

epics.ca.sg_delete(gid)

delete a synchronous group

epics.ca.sg_block(gid[, timeout=10.0])

block for a synchronous group to complete processing

epics.ca.sg_get(gid, chid[, ftype=None[, as_string=False[, as_numpy=True]]])

synchronous-group get of the current value for a Channel. same options as get()

This function will not immediately return the value, of course, but the address of the underlying data.

After the sg_block() has completed, you must use _unpack() to convert this data address to the actual value(s).

Examples

>>> chid = epics.ca.create_channel(PV_Name)
>>> epics.ca.connect_channel(chid1)
>>> sg = epics.ca.sg_create()
>>> data = epics.ca.sg_get(sg, chid)
>>> epics.ca.sg_block(sg)
>>> print(epics.ca._unpack(data, chid=chid))
epics.ca.sg_put(gid, chid, value)

perform a put within a synchronous group.

This put cannot wait for completion or for a a callback to complete.

epics.ca.sg_test(gid)

test whether a synchronous group has completed.

epics.ca.sg_reset(gid)

resets a synchronous group

Implementation details

The details given here should mostly be of interest to those looking at the implementation of the ca module, those interested in the internals, or those looking to translate lower-level C or Python code to this module.

DBR data types

Table of DBR Types

CA type

integer ftype

Python ctypes type

string

0

string

int

1

integer

short

1

integer

float

2

double

enum

3

integer

char

4

byte

long

5

integer

double

6

double

time_string

14

time_int

15

time_short

15

time_float

16

time_enum

17

time_char

18

time_long

19

time_double

20

ctrl_string

28

ctrl_int

29

ctrl_short

29

ctrl_float

30

ctrl_enum

31

ctrl_char

32

ctrl_long

33

ctrl_double

34

PySEVCHK and ChannelAccessExcepction: checking CA return codes

exception epics.ca.ChannelAccessException

This exception is raised when the ca module experiences unexpected behavior and must raise an exception

epics.ca.PySEVCHK(func_name, status[, expected=dbr.ECA_NORMAL])

This checks the return status returned from a libca.ca_*** and raises a ChannelAccessException if the value does not match the expected value (which is nornmally dbr.ECA_NORMAL.

The message from the exception will include the func_name (name of the Python function) and the CA message from message().

epics.ca.withSEVCHK(fcn)

decorator to raise a ChannelAccessException if the wrapped ca function does not return status = dbr.ECA_NORMAL. This handles the common case of running PySEVCHK() for a function whose return value is from a corresponding libca function and whose return value should be dbr.ECA_NORMAL.

Function Decorators

In addition to withSEVCHK(), several other decorator functions are used heavily inside of ca.py or are available for your convenience.

epics.ca.withCA(fcn)

decorator to ensure that libca and a context are created prior to function calls to the channel access library. This is intended for functions that need CA started to work, such as create_channel().

Note that CA functions that take a Channel ID (chid) as an argument are NOT wrapped by this: to get a chid, the library must have been initialized already.

epics.ca.withCHID(fcn)

decorator to ensure that first argument to a function is a Channel ID, chid. The test performed is very weak, as any ctypes long or python int will pass, but it is useful enough to catch most accidental errors before they would cause a crash of the CA library.

epics.ca.withConnectedCHID(fcn)

decorator to ensure that the first argument of a function is a fully connected Channel ID, chid. This test is (intended to be) robust, and will try to make sure a chid is actually connected before calling the decorated function.

epics.ca.withInitialContext(fcn)

decorator to ensure that the wrapped function uses the initial threading context created at initialization of CA

See Using Python Threads for further discussion.

Unpacking Data from Callbacks

Throughout the implementation, there are several places where data returned by the underlying CA library needs to be be converted to Python data. This is encapsulated in the _unpack() function. In general, you will not have to run this code, but there is one exception: when using sg_get(), the values returned will have to be unpacked with this function.

epics.ca._unpack(chid, data[, count=None[, ftype=None[, as_numpy=None]]])

unpacks raw data for a Channel ID chid returned by libca functions including ca_array_get_callback or subscription callback, and returns the corresponding Python data

Normally, users are not expected to need to access this function, but it will be necessary why using sg_get().

Parameters:
chidctypes.c_long or None

channel ID (if not None, used for determining count and ftype)

dataobject

raw data as returned by internal libca functions.

countinteger

number of elements to fetch (defaults to element count of chid or 1)

ftypeinteger

data type of channel (defaults to native type of chid)

as_numpybool

whether to convert to numpy array.

User-supplied Callback functions

User-supplied callback functions can be provided for put(), replace_access_rights_event(), and create_subscription(). Note that callbacks for PV objects are slightly different: see User-supplied Callback functions in the pv module for details.

When defining a callback function to be run either when a put() completes or on changes to the Channel, as set from create_subscription(), or when read/write permissions change from replace_access_rights_event(), it is important to know two things:

  1. how your function will be called.

  2. what is permissible to do inside your callback function.

Callbacks will be called with keyword arguments for put() and for create_subscription(). You should be prepared to have them passed to your function. Use **kw unless you are very sure of what will be sent. For the case of replace_access_rights_event(), only positional arguments will be passed.

For callbacks sent when a put() completes, your function will be passed these:

  • pvname : the name of the pv

  • data: the user-supplied callback_data (defaulting to None).

For subscription callbacks, your function will be called with keyword/value pairs that will include:

  • pvname: the name of the pv

  • value: the latest value

  • count: the number of data elements

  • ftype: the numerical CA type indicating the data type

  • status: the status of the PV (0 for OK)

  • chid: the integer address for the channel ID.

For access rights event callbacks, your function will be passed:

  • read_access: boolean indicating read access status

  • write_access: boolean indicating write access status

Depending on the data type, and whether the CTRL or TIME variant was used, the callback function may also include some of these as keyword arguments:

  • enum_strs: the list of enumeration strings

  • precision: number of decimal places of precision.

  • units: string for PV units

  • severity: PV severity

  • timestamp: timestamp from CA server.

  • posixseconds: integer seconds since POSIX epoch of timestamp from CA server.

  • nanoseconds: integer nanoseconds of timestamp from CA server.

Note that a the user-supplied callback will be run inside a CA function, and cannot reliably make any other CA calls. It is helpful to think “this all happens inside of a pend_event() call”, and in an epics thread that may or may not be the main thread of your program. It is advisable to keep the callback functions short and not resource-intensive. Consider strategies which use the callback only to record that a change has occurred and then act on that change later – perhaps in a separate thread, perhaps after pend_event() has completed.

Omissions

Several parts of the CA library are not implemented in the Python module. These are currently seen as unneeded (with notes where appropriate for alternatives), though they could be added on request.

epics.ca.ca_add_exception_event()

Not implemented: Python exceptions are raised where appropriate and can be used in user code.

epics.ca.ca_add_fd_registration()

Not implemented

epics.ca.ca_client_status()

Not implemented

epics.ca.ca_set_puser()

Not implemented : it is easy to pass user-defined data to callbacks as needed.

epics.ca.ca_puser()

Not implemented: it is easy to pass user-defined data to callbacks as needed.

epics.ca.ca_SEVCHK()

Not implemented: the Python function PySEVCHK() is approximately the same.

epics.ca.ca_signal()

Not implemented: the Python function PySEVCHK() is approximately the same.

epics.ca.ca_test_event()

Not implemented: this appears to be a function for debugging events. These are easy enough to simulate by directly calling Python callback functions.

epics.ca.ca_dump_dbr()

Not implemented

In addition, not all DBR types in the CA C library are supported.

Only native types and their DBR_TIME and DBR_CTRL variants are supported: DBR_STS and DBR_GR variants are not. Several dbr_XXX functions are also not supported, as they are needed only to dynamically allocate memory.

CAThread class

class epics.ca.CAThread(group=None[, target=None[, name=None[, args=()[, kwargs={}]]]])

create a CA-aware subclass of a standard Python threading.Thread. See the standard library documentation for further information on how to use Thread objects.

A CAThread simply runs use_initial_context() prior to running each target function, so that use_initial_context() does not have to be explicitly put inside the target function.

The See Using Python Threads for further discussion.

Examples

Here are some example sessions using the ca module.

Create, Connect, Get Value of Channel

Note here that several things have been simplified compare to using CA in C: initialization and creating a main-thread context are handled, and connection of channels is handled in the background:

from epics import ca
chid  = ca.create_channel('XXX:m1.VAL')
count = ca.element_count(chid)
ftype = ca.field_type(chid)
print("Channel ", chid, count, ftype)
value = ca.get()
print(value)

Put, waiting for completion

Here we set a PVs value, waiting for it to complete:

from epics import ca
chid  = ca.create_channel('XXX:m1.VAL')
ca.put(chid,  1.0, wait=True)

The put() method will wait to return until the processing is complete.

Define a callback to Subscribe to Changes

Here, we subscribe to changes for a PV, which is to say we define a callback function to be called whenever the PV value changes. In the case below, the function to be called will simply write the latest value out to standard output:

from epics import ca
import time
import sys

# define a callback function.  Note that this should
# expect certain keyword arguments, including 'pvname' and 'value'
def onChanges(pvname=None, value=None, **kw):
    fmt = 'New Value: %s  value=%s, kw=%s\n'
    sys.stdout.write(fmt % (pvname, str(value), repr(kw)))
    sys.stdout.flush()

# create the channel
mypv = 'XXX.VAL'
chid = ca.create_channel(mypv)

# subscribe to events giving a callback function
eventID = ca.create_subscription(chid, callback=onChanges)

# now we simply wait for changes
t0 = time.time()
while time.time()-t0 < 10.0:
    time.sleep(0.001)

It is vital that the return value from create_subscription() is kept in a variable so that it cannot be garbage collected. Failure to keep this value will cause trouble, including almost immediate segmentation faults (on Windows) or seemingly inexplicable crashes later (on linux).

Define a connection callback

Here, we define a connection callback – a function to be called when the connection status of the PV changes. Note that this will be called on initial connection:

import epics
import time

def onConnectionChange(pvname=None, conn=None, chid=None):
    print(f'ca connection status changed:  {pvname},  {conn}, {chid}')

# create channel, provide connection callback
motor1 = '13IDC:m1'
chid = epics.ca.create_channel(motor1, callback=onConnectionChange)

print('Now waiting, watching values and connection changes:')
t0 = time.time()
while time.time()-t0 < 30:
    time.sleep(0.001)

This will run the supplied callback soon after the channel has been created, when a successful connection has been made. Note that the callback should be prepared to accept keyword arguments of pvname, chid, and conn for the PV name, channel ID, and connection state (True or False).

Define an access rights change event callback

The following example demonstrates the addition of a function to be called when the access rights of a channel changes. Note this will be called immediately after successful installation:

import epics
import time

def on_access_rights_change(read_access, write_access):
    print(f'read access={read_access}, write access={write_access}')

# create a channel and attach the above function for access rights events
chid = epics.ca.create_channel('pv_name')
# a message should be printed immediately following this registration
epics.ca.replace_access_rights_event(chid, callback=on_access_rights_change)

# Affecting the channel's access rights, (for example, by enabling/disabling
# CA Security rules), should produce additional console messages
try:
    while True:
        time.sleep(0.25)
except KeyboardInterrupt:
    pass