Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Epics, Cothread and other changes #94

Merged
merged 18 commits into from
Jan 21, 2019
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions pytac/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,9 @@
# Default argument flag.
DEFAULT = 'default'

from . import data_source, element, epics, exceptions, lattice, load_csv, units, utils # noqa: E402
from . import data_source, element, exceptions, lattice, load_csv, units, utils # noqa: E402
"""Error 402 is suppressed as we cannot import these modules at the top of the
file as the strings above must be set first or the imports will fail.
"""
__all__ = ["data_source", "element", "epics", "exceptions", "lattice",
"load_csv", "units", "utils"]
__all__ = ["data_source", "element", "exceptions", "lattice", "load_csv",
"units", "utils"]
67 changes: 60 additions & 7 deletions pytac/cothread_cs.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@


class CothreadControlSystem(ControlSystem):
""" The EPICS control system.
"""A control system using cothread to communicate with EPICS. N.B. this is
the default control system.

It is used to communicate over channel access with the hardware
in the ring.
Expand All @@ -13,8 +14,8 @@ class CothreadControlSystem(ControlSystem):
def __init__(self):
pass

def get(self, pv):
""" Get the value of a given PV.
def get_single(self, pv):
"""Get the value of a given PV.

Args:
pv (string): The process variable given as a string. It can be a
Expand All @@ -23,13 +24,65 @@ def get(self, pv):
Returns:
float: Represents the current value of the given PV.
"""
return caget(pv)
try:
T-Nicholls marked this conversation as resolved.
Show resolved Hide resolved
return float(caget(pv, timeout=1.0, throw=False))
except TypeError:
print('cannot connect to {}'.format(pv))
return None

def put(self, pv, value):
""" Set the value for a given.
def get_multiple(self, pvs):
T-Nicholls marked this conversation as resolved.
Show resolved Hide resolved
"""Get the value for given PVs.

Args:
pvs (list): A list of process variables, given as a strings. They
can be a readback or setpoint PVs.

Returns:
list: of floats, representing the current values of the PVs.

Raises:
ValueError: if the PVs are not passed in as a list.
"""
if not isinstance(pvs, list):
raise ValueError('Please enter PVs as a list.')
results = caget(pvs, timeout=1.0, throw=False)
for i in range(len(results)):
try:
results[i] = float(results[i])
except TypeError:
print('cannot connect to {}'.format(pvs[i]))
results[i] = None
return results

def set_single(self, pv, value):
T-Nicholls marked this conversation as resolved.
Show resolved Hide resolved
"""Set the value of a given PV.

Args:
pv (string): The PV to set the value of. It must be a setpoint PV.
value (Number): The value to set the PV to.
"""
caput(pv, value)
try:
caput(pv, value, timeout=1.0, throw=True)
except Exception:
print('cannot connect to {}'.format(pv))

def set_multiple(self, pvs, values):
"""Set the values for given PVs.

Args:
pvs (list): A list of PVs to set the values of. It must be a
setpoint PV.
values (list): A list of the numbers to set no the PVs.

Raises:
ValueError: if the PVs or values are not passed in as a list, or if
the lists of values and PVs are diffent lengths.
"""
if not isinstance(pvs, list) or not isinstance(values, list):
T-Nicholls marked this conversation as resolved.
Show resolved Hide resolved
raise ValueError('Please enter PVs and values as a list.')
elif len(pvs) != len(values):
raise ValueError('Please enter the same number of values as PVs.')
try:
caput(pvs, values, timeout=1.0, throw=True)
except Exception:
print('cannot connect to one or more PV(s).')
51 changes: 42 additions & 9 deletions pytac/cs.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,26 +2,59 @@


class ControlSystem(object):
""" Abstract base class representing a control system.
"""Abstract base class representing a control system.

A specialised implementation of this class would be used to communicate over
channel access with the hardware in the ring.

**Methods:**
"""
def get(self, pv):
""" Get the value of the given PV.
def get_single(self, pv):
"""Get the value of a given PV.

Args:
pv (string): The PV to get the value of.
pv (string): The process variable given as a string. It can be a
readback or a setpoint PV.

Returns:
Number: The numeric value of the PV.
float: Represents the current value of the given PV.
"""
raise NotImplementedError()

def put(self, pv, value):
""" Put the value of a given PV.
def get_multiple(self, pvs):
"""Get the value for given PVs.

Args:
pv (string): The PV to put the value for.
value (Number): The value to be set.
pvs (list): A list of process variables, given as a strings. They
can be a readback or setpoint PVs.

Returns:
list: of floats, representing the current values of the PVs.

Raises:
ValueError: if the PVs are not passed in as a list.
"""
raise NotImplementedError()

def set_single(self, pv, value):
"""Set the value of a given PV.

Args:
pv (string): The PV to set the value of. It must be a setpoint PV.
value (Number): The value to set the PV to.
"""
raise NotImplementedError()

def set_multiple(self, pvs, values):
"""Set the values for given PVs.

Args:
pvs (list): A list of PVs to set the values of. It must be a
setpoint PV.
values (list): A list of the numbers to set no the PVs.

Raises:
ValueError: if the PVs or values are not passed in as a list, or if
the lists of values and PVs are diffent lengths.
"""
raise NotImplementedError()
152 changes: 152 additions & 0 deletions pytac/device.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
DLS is a sextupole magnet that contains also horizontal and vertical corrector
magnets and a skew quadrupole.
"""
import pytac
from pytac.exceptions import HandleException


class Device(object):
Expand Down Expand Up @@ -78,3 +80,153 @@ def get_value(self, handle=None):
?: the value of the PV.
"""
return self.value


class EpicsDevice(Device):
"""An EPICS-aware device.

Contains a control system, readback and setpoint PVs. A readback
or setpoint PV is required when creating a device otherwise a
DeviceException is raised. The device is enabled by default.

**Attributes:**

Attributes:
name (str): The prefix of EPICS PVs for this device.
rb_pv (str): The EPICS readback PV.
sp_pv (str): The EPICS setpoint PV.

.. Private Attributes:
_cs (ControlSystem): The control system object used to get and set
the value of a PV.
_enabled (bool-like): Whether the device is enabled. May be a
PvEnabler object.
"""
def __init__(self, name, cs, enabled=True, rb_pv=None, sp_pv=None):
"""
Args:
name (str): The prefix of EPICS PV for this device.
cs (ControlSystem): The control system object used to get and set
the value of a PV.
enabled (bool-like): Whether the device is enabled. May be a
PvEnabler object.
rb_pv (str): The EPICS readback PV.
sp_pv (str): The EPICS setpoint PV.

**Methods:**
"""
self.name = name
self._cs = cs
self.rb_pv = rb_pv
self.sp_pv = sp_pv
self._enabled = enabled

def is_enabled(self):
"""Whether the device is enabled.

Returns:
bool: whether the device is enabled.
"""
return bool(self._enabled)

def set_value(self, value):
"""Set the device value.

Args:
value (float): The value to set.

Raises:
HandleException: if no setpoint PV exists.
"""
if self.sp_pv is None:
raise HandleException("Device {0} has no setpoint PV."
.format(self.name))
self._cs.set_single(self.sp_pv, value)

def get_value(self, handle):
"""Read the value of a readback or setpoint PV.

Args:
handle (str): pytac.SP or pytac.RB.

Returns:
float: The value of the PV.

Raises:
HandleException: if the requested PV doesn't exist.
"""
if handle == pytac.RB and self.rb_pv:
return self._cs.get_single(self.rb_pv)
elif handle == pytac.SP and self.sp_pv:
return self._cs.get_single(self.sp_pv)

raise HandleException("Device {0} has no {1} PV."
.format(self.name, handle))

def get_pv_name(self, handle):
"""Get the PV name for the specified handle.

Args:
handle (str): The readback or setpoint handle to be returned.

Returns:
str: A readback or setpoint PV.

Raises:
HandleException: if the PV doesn't exist.
"""
if handle == pytac.RB and self.rb_pv:
return self.rb_pv
elif handle == pytac.SP and self.sp_pv:
return self.sp_pv

raise HandleException("Device {0} has no {1} PV."
.format(self.name, handle))


class PvEnabler(object):
"""A PvEnabler class to check whether a device is enabled.

The class will behave like True if the PV value equals enabled_value,
and False otherwise.

.. Private Attributes:
_pv (str): The PV name.
_enabled_value (str): The value for PV for which the device should
be considered enabled.
_cs (ControlSystem): The control system object.
"""
def __init__(self, pv, enabled_value, cs):
"""
Args:
pv (str): The PV name.
enabled_value (str): The value for PV for which the device should
be considered enabled.
cs (ControlSystem): The control system object.

**Methods:**
"""
self._pv = pv
self._enabled_value = str(int(float(enabled_value)))
self._cs = cs

def __nonzero__(self):
"""Used to override the 'if object' clause.

Support for Python 2.7.

Returns:
bool: True if the device should be considered enabled.
"""
pv_value = self._cs.get_single(self._pv)
return self._enabled_value == str(int(float(pv_value)))

def __bool__(self):
"""Used to override the 'if object' clause.

Support for Python 3.x.

Returns:
bool: True if the device should be considered enabled.
"""
return self.__nonzero__()
29 changes: 29 additions & 0 deletions pytac/element.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
"""Module containing the element class."""
import pytac
from pytac.exceptions import DeviceException
from pytac.data_source import DataSourceManager


Expand Down Expand Up @@ -181,3 +182,31 @@ def set_value(self, field, value, handle=pytac.SP, units=pytac.DEFAULT,
"""
self._data_source_manager.set_value(field, value, handle, units,
data_source)


class EpicsElement(Element):
"""EPICS-aware element.

Adds get_pv_name() method.

**Methods:**
"""
def get_pv_name(self, field, handle):
"""Get PV name for the specified field and handle.

Args:
field (str): The requested field.
handle (str): pytac.RB or pytac.SP.

Returns:
str: The readback or setpoint PV for the specified field.

Raises:
DeviceException: if there is no device for this field.
"""
try:
return (self._data_source_manager._data_sources[pytac.LIVE]
.get_device(field).get_pv_name(handle))
except KeyError:
raise DeviceException('{} has no device for field {}'
.format(self, field))
Loading