Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
6f93751
Update existing control systems to multiple/single syntax.
T-Nicholls Sep 20, 2018
405f6fe
Add new caproto control system.
T-Nicholls Sep 20, 2018
2bd5941
Suppress version response warning.
T-Nicholls Sep 20, 2018
8cfbcd5
Update epics related pytac classes.
T-Nicholls Sep 20, 2018
a2fcb98
Update get_single in PvEnabler to get_multiple.
T-Nicholls Sep 20, 2018
92c13e0
Remove unnecessary LatticeException and correct get_pv_names to name …
T-Nicholls Sep 20, 2018
5f6a5e1
Update all existing tests to work with new syntax.
T-Nicholls Sep 20, 2018
2955788
Correct pep8 style issues.
T-Nicholls Sep 21, 2018
e38297e
Minor control system corrections.
T-Nicholls Sep 21, 2018
3fc0e77
Add get_pv_name correctly raises exception tests.
T-Nicholls Sep 21, 2018
7ae2065
Partially update cothread tests and add caproto tests.
T-Nicholls Sep 21, 2018
f6e21a6
Chnged NoneType back to type(None), because python 3 doesn't have it.
T-Nicholls Sep 21, 2018
dc53c74
Improved the descriptions of all control systems.
T-Nicholls Sep 21, 2018
08f34e0
First basic theoretical implementation of a pyepics control system.
T-Nicholls Sep 21, 2018
d94bf07
Add timeout placeholders and complete set_multiple.
T-Nicholls Sep 21, 2018
c3c2db0
Set the timeout in pyepics to 1 second.
T-Nicholls Sep 24, 2018
915fa90
Complete get_multiple and fill timeout placeholders.
T-Nicholls Sep 25, 2018
88f1f3c
Add support for slow and non-existent PVs to cothread_cs.
T-Nicholls Sep 25, 2018
0666f29
Update tests for new caget syntax.
T-Nicholls Sep 25, 2018
902f51b
Ensure pyepics_cs always returns float.
T-Nicholls Sep 25, 2018
74d4754
Add pyepics_cs tests and update some existing tests.
T-Nicholls Sep 25, 2018
bc0f5a7
Update caproto_cs so it can handle non-existent PVs correctly.
T-Nicholls Sep 26, 2018
436a737
Add TimeoutException definition for python versions < 3.3.
T-Nicholls Sep 27, 2018
093f71e
Add non-existent PV testing for caproto get methods.
T-Nicholls Sep 27, 2018
37d9ff4
Fix result misordering issue for pyepics.get_multiple().
T-Nicholls Sep 27, 2018
947880a
Finish off and tidy all control systems and their tests.
T-Nicholls Sep 27, 2018
d76cd5f
Deleted epics.py, and redistributed its contents, to allow pyepics to…
T-Nicholls Sep 27, 2018
889ac1f
Separated out control system tests from load tests.
T-Nicholls Sep 27, 2018
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
1 change: 1 addition & 0 deletions Pipfile
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,4 @@ pytest-lazy-fixture = "*"
[packages]
numpy = "*"
scipy = "*"
pyepics = "*"
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"]
128 changes: 128 additions & 0 deletions pytac/caproto_cs.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
import sys
import logging
import time as t
from pytac.cs import ControlSystem
from caproto.threading.client import Context, Batch
if float(sys.version[:3]) < 3.3: # TimoutError only exists in python >= 3.3.
from pytac.exceptions import TimeoutError # Otherwise we define it manually


class CaprotoControlSystem(ControlSystem):
"""A control system using caproto to communicate with EPICS.

It is used to communicate over channel access with the hardware
in the ring.

**Methods:**
"""
def __init__(self):
"""Create a _results attribute so all methods can access it, and disable
logging to temporarily prevent caproto version response warnings
being incorrectly and excessively raised.
"""
self._results = []
self.time = 0
logging.disable(logging.WARNING) # Temporary.

def _append_result(self, response):
"""Append a result to the current list of results.

Args:
response (caproto class): The response from epics containing the
result data.
"""
self._results.append(response.data[0])

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
readback or a setpoint PV.

Returns:
float: Represents the current value of the given PV.
"""
pv_object = Context().get_pvs(pv)[0]
try:
return pv_object.read().data[0]
except TimeoutError: # Only exists in python 3.3+ though caproto requires 3.6+
print('cannot connect to {}'.format(pv))
return None

def get_multiple(self, pvs):
"""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.')
self._results = []
pv_objects = Context().get_pvs(*pvs)
with Batch() as b:
for pv_object in pv_objects:
try:
self.time = t.time()
while isinstance(pv_object.channel, type(None)):
if (t.time() - self.time) > 1.0:
raise TimeoutError
else:
pass # Wait until pv object is fully initialised.
b.read(pv_object, self._append_result)
except TimeoutError:
self._results.append(None)
print('cannot connect to {}'.format(pvs[pv_objects.index(pv_object)]))
while len(self._results) is not len(pvs):
pass # Wait for results to be returned.
return self._results

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.
"""
pv_object = Context().get_pvs(pv)[0]
try:
pv_object.write(value, wait=False)
except TimeoutError:
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):
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.')
pv_objects = Context().get_pvs(*pvs)
with Batch() as b:
for pv_object, value in zip(pv_objects, values):
try:
self.time = t.time()
while isinstance(pv_object.channel, type(None)):
if (t.time() - self.time) > 1.0:
raise TimeoutError
else:
pass # Wait until pv object is fully initialised.
b.write(pv_object, value)
except TimeoutError:
print('cannot connect to {}'.format(pvs[pv_objects.index(pv_object)]))
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:
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):
"""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):
"""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):
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()
Loading