merge to dev

This commit is contained in:
2019-05-03 11:21:54 +02:00
13 changed files with 287 additions and 641 deletions
+3 -3
View File
@@ -8,7 +8,7 @@
eco is a python based control environment for experiments, developed and used at SwissFEL, PSI.
It is supposed to be used as
- library of experimental devices for higher level python applications or GUIs
- interactive command line interface from e.g. ipython/jupyter shell or notebook
- interactive command line interface from e.g. ipython/jupyter shell or notebook.
Eco follows an object oriented approach to represent devices which can be passed around as a compatibility layer in python, This should facilitate to combine devices in general control and acquisition routines as well as to develop experimental routines which take advantage of the constantly growing landscape of scientific python libraries.
Examples for such object representation will follow in the documantation, for object-oriented programing in python also checkout online documentation like this [short introduction]{https://realpython.com/python3-object-oriented-programming/}.
@@ -16,7 +16,7 @@ Examples for such object representation will follow in the documantation, for ob
## eco Elements
Eco consists in general terms of
1. conventions and examples for the behavior of general objects that allow to use them for different purposes.
2. library modules for broadly used devices using protocols like epics.
2. library modules for broadly used devices using protocols (_e.g._ epics).
3. library modules for more specific, facility-dependent devices or logical assemblies of devices.
4. scopes of specific configurations of devices and scope-specific code, usable e.g. in interactive mode.
@@ -44,5 +44,5 @@ At top level should be found:
The eco package is available on [anaconda.org](https://anaconda.org/paulscherrerinstitute/eco) and can be installed as follows:
```bash
conda install -c paulscherrerinstitute bsread
conda install -c paulscherrerinstitute eco
```
+1 -1
View File
@@ -211,7 +211,7 @@ class DIAClient:
except:
pass
if len(completepedestals)>0:
f = max(*completepedestals)[1][0]
f = max(completepedestals)[1][0]
#dtim,f = max((datetime.strptime(f.stem.split('pedestal_')[1].split('.')[0],"%Y%m%d_%H%M"),f) for f in p.glob('*.h5'))
self.pede_file = (f.parent / Path(f.stem.split('.')[0])).as_posix()
+26 -4
View File
@@ -1,16 +1,18 @@
from ..utilities.config import initFromConfigList
from epics import PV
from .config import components, config
from .. import ecocnf
from ..aliases import NamespaceCollection
from ..devices_general.pv_adjustable import PvRecord
itglobals = globals()
import logging
from .config import components, config
_namespace = globals()
_scope_name = 'bernina'
alias_namespaces = NamespaceCollection()
for key, value in initFromConfigList(components, lazy=ecocnf.startup_lazy).items():
globals()[key] = value
_namespace[key] = value
if not ecocnf.startup_lazy:
try:
@@ -21,3 +23,23 @@ for key, value in initFromConfigList(components, lazy=ecocnf.startup_lazy).items
except:
pass
alias_namespaces.bernina.store()
def initDevice(name):
if name=='all':
logging.info(f'initializing all components from {_scope_name}.')
name = list(components.keys())
if not name in components.keys():
raise KeyError(f'Could not find {name} in configuration!')
if type(name) is list:
for tname in name:
initDevice(tname)
else:
initFromConfigList
+38 -12
View File
@@ -16,13 +16,13 @@ from ..utilities.config import (
Alias,
init_device,
initFromConfigList,
ExperimentConfiguration,
Configuration,
)
_eco_lazy_init = False
config = ExperimentConfiguration(
"/sf/bernina/config/exp/bernina_config_eco.json", name="bernina_config"
config = Configuration(
"/sf/bernina/config/eco/bernina_config_eco.json", name="bernina_config"
)
components = [
@@ -32,6 +32,13 @@ components = [
# 'args' : ['all','the','requires','args'],
# 'kwargs': {}
# }
{
"type": "eco.utilities.config:append_to_path",
"args": config["path_exp"],
"name": "path_exp",
"kwargs": {},
"lazy": False
},
{
"name": "elog",
"type": "eco.utilities.elog:Elog",
@@ -230,7 +237,7 @@ components = [
"z_und": 142,
"desc": "General purpose station",
"type": "eco.endstations.bernina_diffractometers:GPS",
"kwargs": {"Id": "SARES22-GPS", "configuration": config.gps_config},
"kwargs": {"Id": "SARES22-GPS", "configuration": config['gps_config']},
},
{
"args": [],
@@ -322,17 +329,16 @@ components = [
"type": "eco.acquisition.dia:DIAClient",
"kwargs": {
"instrument": "bernina",
#"api_address": "http://sf-daq-3:10000",
"api_address": config["daq_address"],
"pgroup": config.pgroup,
'pedestal_directory':config.jf_pedestal_directory,
"gain_path": config.jf_gain_path,
"config_default": config.daq_dia_config,
'jf_channels':config.jf_channels,
"pgroup": config['pgroup'],
'pedestal_directory':config['jf_pedestal_directory'],
"gain_path": config['jf_gain_path'],
"config_default": config['daq_dia_config'],
'jf_channels':config['jf_channels'],
},
},
{
"args": [config.checker_PV, config.checker_thresholds, config.checker_fractionInThreshold], #'SARFE10-PBPG050:HAMP-INTENSITY-CAL',[60,700],.7],
"args": [config['checker_PV'], config['checker_thresholds'], config['checker_fractionInThreshold']], #'SARFE10-PBPG050:HAMP-INTENSITY-CAL',[60,700],.7],
"name": "checker",
"desc": "checker functions for data acquisition",
"type": "eco.acquisition.checkers:CheckerCA",
@@ -345,7 +351,7 @@ components = [
"type": "eco.acquisition.scan:Scans",
"kwargs": {
"data_base_dir": "scan_data",
"scan_info_dir": f"/sf/bernina/data/{config.pgroup}/res/scan_info",
"scan_info_dir": f"/sf/bernina/data/{config['pgroup']}/res/scan_info",
"default_counters": [Component("daq")],
"checker": Component("checker"),
"scan_directories": True,
@@ -358,8 +364,28 @@ components = [
"type": "eco.timing.lasertiming:Lxt",
"kwargs": {}
},
{
"args": ['SAR-CCTA-ESB'],
"name": "seq",
"desc": "sequencer timing application (CTA)",
"type": "eco.timing.event_timing:CTA_sequencer",
"kwargs": {}
},
{
"args": ['SIN-TIMAST-TMA'],
"name": "event_master",
"desc": "SwissFEL timing master information",
"type": "eco.timing.event_timing:MasterEventSystem",
"kwargs": {}
},
]
try:
components.extend(config['components'])
print('Did append additional components!')
except:
print('Could not append components from config.')
#### OLD STUFF TO BE TRANSFERRED OR DELETED ####
+70 -119
View File
@@ -2,6 +2,7 @@ import subprocess
from threading import Thread
from epics import PV
from .utilities import Changer
from ..aliases import Alias
def _keywordChecker(kw_key_list_tups):
@@ -9,139 +10,89 @@ def _keywordChecker(kw_key_list_tups):
assert tkey in tlist, "Keyword %s should be one of %s" % (tkw, tlist)
class ValueRdback:
def __init__(self, pv_value, pv_readback, name=None, elog=None):
self.Id = pv_value
self._PV_value = PV(pv_value)
self._PV_readback = PV(pv_readback)
self._elog = elog
class PvRecord:
def __init__(
self,
pvsetname,
pvreadbackname = None,
accuracy = None,
name=None,
elog=None,
):
# alias_fields={"setpv": pvsetname, "readback": pvreadbackname},
# ):
self.Id = pvsetname
self.name = name
self.alias = Alias(name)
# for an, af in alias_fields.items():
# self.alias.append(
# Alias(an, channel=".".join([pvname, af]), channeltype="CA")
# )
self._pv = PV(self.Id)
self._currentChange = None
self.accuracy = accuracy
# Conventional methods and properties for all Adjustable objects
def changeTo(self, value, hold=False, check=True):
""" Adjustable convention"""
def changer(value):
self._status = self._motor.move(value, ignore_limits=(not check), wait=True)
self._status_message = _status_messages[self._status]
if not self._status == 0:
print(self._status_message)
# changer = lambda value: self._motor.move(\
# value, ignore_limits=(not check),
# wait=True)
return Changer(
target=value,
parent=self,
changer=changer,
hold=hold,
stopper=self._motor.stop,
)
def stop(self):
""" Adjustable convention"""
try:
self._currentChange.stop()
except:
self._motor.stop()
pass
def get_current_value(self):
""" Adjustable convention"""
return self._PV_readback.get()
def set_current_value(self, value):
""" Adjustable convention"""
print("not implemented: depends on a defined offset")
def get_precision(self):
""" Adjustable convention"""
if isinstance(self._precision, PV):
return self._precision.get()
if pvreadbackname is None:
self._pvreadback = PV(self.Id)
else:
return self._precision
self._pvreadback = PV(pvreadbackname)
def set_precision(self, value):
""" Adjustable convention"""
if isinstance(self._precision, PV):
self._precision.put(value)
else:
self._precision = value
precision = property(get_precision, set_precision)
def set_speed(self):
""" Adjustable convention"""
pass
def get_speed(self):
""" Adjustable convention"""
pass
def set_speedMax(self):
""" Adjustable convention"""
pass
def get_current_value(self, readback=True,):
if readback:
currval = self._pvreadback.get()
if not readback:
currval = self._pv.get()
return currval
def get_moveDone(self):
""" Adjustable convention"""
""" 0: moving 1: move done"""
return PV(str(self.Id + ".DMOV")).value
movedone = 1
if self.accuracy is not None:
if( np.abs(self.get_current_value(readback=False)-self.get_current_value(readback=True)) > self.accuracy ):
movedone = 0
return movedone
def set_limits(self, values, posType="user", relative_to_present=False):
def move(self,value):
self._pv.put(value)
time.sleep(0.1)
while( self.get_moveDone() == 0 ):
time.sleep(0.1)
def changeTo(self, value, hold=False):
""" Adjustable convention"""
_keywordChecker([("posType", posType, _posTypes)])
ll_name, hl_name = "LLM", "HLM"
if posType is "dial":
ll_name, hl_name = "DLLM", "DHLM"
if relative_to_present:
v = self.get_current_value(posType=posType)
values = [v - values[0], v - values[1]]
self._motor.put(ll_name, values[0])
self._motor.put(hl_name, values[1])
def get_limits(self, posType="user"):
""" Adjustable convention"""
_keywordChecker([("posType", posType, _posTypes)])
ll_name, hl_name = "LLM", "HLM"
if posType is "dial":
ll_name, hl_name = "DLLM", "DHLM"
return self._motor.get(ll_name), self._motor.get(hl_name)
changer = lambda value: self.move(\
value)
return Changer(
target=value,
parent=self,
changer=changer,
hold=hold,
stopper=None)
# return string with motor value as variable representation
def __str__(self):
return "Motor is at %s" % self.wm()
# spec-inspired convenience methods
def mv(self,value):
self._currentChange = self.changeTo(value)
def wm(self,*args,**kwargs):
return self.get_current_value(*args,**kwargs)
def mvr(self,value,*args,**kwargs):
if(self.get_moveDone == 1):
startvalue = self.get_current_value(readback=True,*args,**kwargs)
else:
startvalue = self.get_current_value(readback=False,*args,**kwargs)
self._currentChange = self.changeTo(value+startvalue,*args,**kwargs)
def wait(self):
self._currentChange.wait()
def __repr__(self):
return self.__str__()
return "%s is at: %s"%(self.Id,self.get_current_value())
def __call__(self, value):
self._currentChange = self.changeTo(value)
class ChangerOld:
def __init__(self, target=None, parent=None, mover=None, hold=True, stopper=None):
self.target = target
self._mover = mover
self._stopper = stopper
self._thread = Thread(target=self._mover, args=(target,))
if not hold:
self._thread.start()
def wait(self):
self._thread.join()
def start(self):
self._thread.start()
def status(self):
if self._thread.ident is None:
return "waiting"
else:
if self._isAlive:
return "changing"
else:
return "done"
def stop(self):
self._stopper()
+14
View File
@@ -1,3 +1,8 @@
import logging
import json
from pathlib import Path
from .utilities.config import Configuration
startup_lazy = False
scopes = [
@@ -5,3 +10,12 @@ scopes = [
{"name": "Bernina", "facility": "SwissFEL", "module": "bernina"},
{"name": "SwissMX", "facility": "SwissFEL", "module": "swissmx"},
]
# settings = Configuration(Path.home() / '.ecorc', name='eco_startup_settings')
+1 -1
View File
@@ -1,6 +1,6 @@
from ..devices_general.motors import MotorRecord
from ..devices_general.detectors import CameraCA, CameraBS
from ..devices_general.pv_adjustable import PvRecord
from ..devices_general.adjustable import PvRecord
from ..aliases import Alias
# from ..devices_general.epics_wrappers import EnumSelector
@@ -2,7 +2,6 @@ import sys
sys.path.append("..")
from ..devices_general.motors import MotorRecord
from ..devices_general.adjustable import ValueRdback
from epics import PV
from ..aliases import Alias
-491
View File
@@ -1,491 +0,0 @@
from epics import PV
import numpy
import logging
import time
import threading
"""
The complex triggering application (CTA) is an application which allows the
user to inject a user defined sequence of events in a subtree of the timing
tree.
The CTA runs on an IOC. There are two ways to program and control it.
The first is a GUI and the second is this python library.
"""
class CtaLib:
def __init__(self, device, sequence=0, log_level="warning"):
"""
Constructor
Arguments
device: device name (e.g. SAR-CCTA-ESA)
sequence: sequence number (default = 0)
log_level: critical, error, warning, info, debug
"""
self.event_code_range_base = 200
self.num_of_event_codes = 20
# setup logging
numeric_level = getattr(logging, log_level.upper(), None)
if not isinstance(numeric_level, int):
raise ValueError("Invalid log_level: %s" % log_level)
logging.basicConfig(
level=numeric_level, format="%(asctime)s | %(levelname)8s | %(message)s"
)
logging.info("__init__() is running (device=" + device + ")")
# create threading event
self.event = threading.Event()
# create connection housekeeper
self.num_connected = 0
# create attributes for callback support
self._status_callbacks = list()
self._series_callbacks = list()
# create pv objects
self.pvs = dict()
self.num_of_pvs = 26
pv_name = device + ":SerMaxLen"
self.pvs["SerMaxLen"] = PV(
pv_name, connection_callback=self.connection_callback
)
pv_name = device + ":seq" + str(sequence) + "Ctrl-Length-I"
self.pvs["Ctrl-Length-I"] = PV(
pv_name, connection_callback=self.connection_callback
)
pv_name = device + ":seq" + str(sequence) + "Ctrl-Cycles-I"
self.pvs["Ctrl-Cycles-I"] = PV(
pv_name, connection_callback=self.connection_callback
)
pv_name = device + ":seq" + str(sequence) + "Ctrl-Start-I"
self.pvs["Ctrl-Start-I"] = PV(
pv_name, connection_callback=self.connection_callback
)
pv_name = device + ":seq" + str(sequence) + "Ctrl-Stop-I"
self.pvs["Ctrl-Stop-I"] = PV(
pv_name, connection_callback=self.connection_callback
)
pv_name = device + ":seq" + str(sequence) + "Ctrl-IsRunning-O"
self.pvs["Ctrl-IsRunning-O"] = PV(
pv_name,
callback=self._status_callback,
connection_callback=self.connection_callback,
)
self.pvs["Data-I"] = list()
for i in range(0, self.num_of_event_codes):
pv_name = device + ":seq" + str(sequence) + "Ser" + str(i) + "-Data-I"
self.pvs["Data-I"].append(
PV(
pv_name,
callback=self._series_callback,
connection_callback=self.connection_callback,
)
)
# wait for the connections to be established
rv = self.event.wait(timeout=5.0)
if not rv:
raise RuntimeError("Some PV(s) is/are not connected")
time.sleep(1) # NOTE02
# logging
for i in range(0, self.num_of_event_codes):
logging.debug("NORD of " + str(i) + ":" + str(self.pvs["Data-I"][i]))
logging.info("__init__() is done")
def __del__(self):
"""
Deconstructor
"""
logging.info("__del__() is running")
del self.pvs
logging.info("__del__() is done")
def download(self, seq):
"""
Download a sequence to the IOC
Arguments
seq: The sequence to be downloaded to the IOC.
seq is a dictionary where each key value pair represents a series.
A series is a list of 0's and 1's which defines, if the corresponding event code
is send in the corresponding machine pulse.
The key is an integer and represents the event code.
The value is the series.
If a certain event code is not send in the sequence, it may or may not
not be present in the dictionary.
Example:
seq = {200: [1, 0], 201: [1, 1]}
=>
machine pulse x: event code 200 is send
machine pulse x + 1: event code 200 and 201 are send
"""
logging.info("download() is running")
# check the sequence
self.check_sequence(seq)
# fill empty series
seq = self.fill_empty_series(seq)
# logging
logging.debug("download() downloads: " + str(seq))
# check connections
rv = self.event.wait(timeout=5.0)
if not rv:
raise RuntimeError("Some PV(s) is/are not connected")
# downloading seq to pvs
for i in range(0, self.num_of_event_codes):
self.pvs["Data-I"][i].put(
numpy.array(seq[self.event_code_range_base + i]), wait=True
)
# set length
self.pvs["Ctrl-Length-I"].put(len(seq[self.event_code_range_base]), wait=True)
logging.info("download() is done")
def upload(self):
"""
Upload a sequence from the IOC
Return
seq: The sequence uploaded from the IOC.
Refer to the download method for a definition of seq.
"""
logging.info("upload() is running")
# check connections
rv = self.event.wait(timeout=5.0)
if not rv:
raise RuntimeError("Some PV(s) is/are not connected")
# upload
seq = {}
for i in range(0, self.num_of_event_codes):
logging.debug("NORD of " + str(i) + ":" + str(self.pvs["Data-I"][i]))
seq[self.event_code_range_base + i] = numpy.atleast_1d(
self.pvs["Data-I"][i].get()
).tolist()
# logging
logging.debug("upload() uploaded: " + str(seq))
# check the sequence
self.check_sequence(seq)
logging.info("upload() is done")
return seq
def start(self, repetitions):
"""
Start CTA
Arguments
repetitions: 0 = forever, x = x repetitions
"""
logging.info("start() is running (repetitions=" + str(repetitions) + ")")
# check connections
rv = self.event.wait(timeout=5.0)
if not rv:
raise RuntimeError("Some PV(s) is/are not connected")
# start
self.pvs["Ctrl-Cycles-I"].put(repetitions, wait=True)
self.pvs["Ctrl-Start-I"].put(1, wait=True)
time.sleep(3) # => NOTE01
logging.info("start() is done")
def stop(self):
"""
Stop CTA
"""
logging.info("stop() is running")
# check connections
rv = self.event.wait(timeout=5.0)
if not rv:
raise RuntimeError("Some PV(s) is/are not connected")
self.pvs["Ctrl-Stop-I"].put(1, wait=True)
# stop
time.sleep(3) # => NOTE01
logging.info("stop() is done")
def is_running(self):
"""
Check if CTA is running
Return
True if CTA is running, False otherwise
"""
logging.info("is_running() is running")
# check connections
rv = self.event.wait(timeout=5.0)
if not rv:
raise RuntimeError("Some PV(s) is/are not connected")
# get status
if self.pvs["Ctrl-IsRunning-O"].get() != 0:
rv = True
else:
rv = False
logging.info("is_running() is done")
return rv
def register_status_callback(self, callback):
"""
This function can be used to register a callback function which is
called if the status of the sequence controller changed.
The following arguments will be passed to the callback function:
value: 1 if sequence is running, 0 otherwise
Keep your callback function short.
Arguments
callback: Function to be called.
"""
self._status_callbacks.append(callback)
def register_series_callback(self, callback):
"""
This function can be used to register a callback function which is
called if a series of the sequence on the IOC has changed.
Refer to the download method for a definition of a sequence.
The following arguments will be passed to the callback function:
sequence: sequence containing the series which has changed
Keep your callback function short.
Arguments
callback: Function to be called
"""
self._series_callbacks.append(callback)
def check_sequence(self, seq):
"""
Check if a sequence is valid
Arguments
seq: The sequence to be checked.
A RunTimeError exception is thrown if the sequence is not valid.
Refer to the download method for a definition of seq.
"""
logging.info("check_sequence() is running")
# check that seq has correct types and at least one series
if type(seq) != type(dict()):
raise RuntimeError("seq arg is not a dictionary")
if len(list(seq)) == 0:
raise RuntimeError("dictionary seq is empty")
for key, series in seq.items():
if type(key) != type(int()):
raise RuntimeError(
"dictionary contains key value pair where key is" " not an int"
)
if type(series) != type(list()):
raise RuntimeError(
"dictionary contains key value pair where value is" " not a list"
)
for i in range(len(series)):
if series[i] != 0 and series[i] != 1:
raise RuntimeError(
"dictionary contains key value pair where value is"
" is a list with at least one element which is not 0 or 1"
)
# check that all series have same length
length = len(seq[list(seq)[0]])
for key, series in seq.items():
if len(seq[key]) != length:
raise RuntimeError(
"dictionary contains key value pair where at least"
" two values are lists with different length"
)
# check that series are not too long
length = len(seq[list(seq)[0]])
if length > self.pvs["SerMaxLen"].get():
raise RuntimeError(
"dictionary contains key value pair where the values "
"are lists with too many elements"
)
logging.info("check_sequence() is done")
def fill_empty_series(self, seq):
"""
Fill the sequence such that all events are described
Arguments
seq: A sequence where some events might not be defined.
Return
seq: The sequence with all events defined.
Refer to the download method for a definition of seq.
"""
logging.info("fill_empty_series() is running")
length = len(seq[list(seq)[0]])
for i in range(self.event_code_range_base, 220):
if i not in seq:
seq[i] = [0] * length
logging.info("fill_empty_series() is done")
return seq
def print(self, seq):
"""
Print the sequence to std output
Arguments
seq: The sequence to be printed.
Refer to the download method for a definition of seq.
"""
logging.info("print() is running")
logging.debug("print() prints: " + str(seq))
# check the sequence
self.check_sequence(seq)
length = len(seq[list(seq)[0]])
print(" | <---------------- event -------------->")
print(" | 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2")
print(" | 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1")
print("pulse | 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9")
print("-----------------------------------------------")
for i in range(length):
print(repr(i).rjust(5), "|", end="")
for key, series in seq.items():
print(repr(series[i]).rjust(2), end="")
print()
logging.info("print() is done")
def _status_callback(self, value=None, **kw):
"""
Callback function which is called when the status PV changes the value.
It is used to call user callback functions which registered for
for this event.
"""
logging.info("_status_callback() is running (value=" + str(value) + ")")
logging.info("calling status callbacks next")
for cb in self._status_callbacks:
cb(value)
logging.info("calling status callbacks done")
def _series_callback(self, pvname=None, value=None, **kw):
"""
Callback function which is called when one of the PVs holding a series of
the sequence changes the value. It is used to call user callback functions which
registered for this event.
"""
logging.info(
"_series_callback() is running (pv=" + pvname + ", value=" + str(value)
)
event_number = self.event_code_range_base
# determine event number from pvname
for idx, pv in enumerate(self.pvs["Data-I"]):
if pv.pvname == pvname:
event_number = self.event_code_range_base + idx
break
# create sequence
seq = {}
seq[event_number] = numpy.atleast_1d(value).tolist()
# call callbacks
logging.info("calling sequence callbacks next")
for cb in self._series_callbacks:
cb(seq)
logging.info("calling sequence callbacks done")
logging.info("_series_callback() is done")
def connection_callback(self, pvname=None, conn=None, **kw):
"""
Callback function used internally to do connection status housekeeping
Arguments
pvname: name of PV for which the callbak is called
conn: status of the connection
"""
logging.info(
"connection_callback() is running (pvname="
+ pvname
+ ", conn="
+ repr(conn)
+ ", thread_id="
+ str(threading.get_ident())
+ ")"
)
# do connection housekeeping
if conn:
self.num_connected += 1
else:
self.num_connected -= 1
logging.debug("num_connected=" + str(self.num_connected))
# signal to other thread
if self.num_connected == self.num_of_pvs:
self.event.set()
else:
self.event.clear()
logging.info("connection_callback() is done")
# NOTE 01
# This sleep is needed for unknown reasons.
# If it is not there, we get the following error if the lib object
# goes out of scope after start():
# FATAL: exception not rethrown
# CA client library tcp receive thread terminating due to a non-standard C++ exception
# Aborted
#
# NOTE 02
# This sleep is needed for the initial ca communication to be completed.
# If it is not there and the upload is called right after object creation,
# the number of elements in the PV has not arrived in python for all PVs.
# This leads to a fail of check_sequence().
+100
View File
@@ -2,6 +2,8 @@ from epics import PV
from ..aliases import Alias
from ..utilities.lazy_proxy import Proxy
from cta_lib import CtaLib
# EVR output mapping
evr_mapping = {
0: "Pulser 0",
@@ -97,8 +99,32 @@ eventcodes = [
47,
48,
49,
50
]
event_code_delays_fix = {
200: 100,
201: 107,
202: 114,
203: 121,
204: 128,
205: 135,
206: 142,
207: 149,
208: 156,
209: 163,
210: 170,
211: 177,
212: 184,
213: 191,
214: 198,
215: 205,
216: 212,
217: 219,
218: 226,
219: 233}
tim_tick = 7e-9
class MasterEventSystem:
def __init__(self, pvname, name=None):
@@ -139,6 +165,8 @@ class MasterEventSystem:
return Id
def get_evtcode_delay(self, evtcode, **kwargs):
if evtcode in event_code_delays_fix.keys():
return event_code_delays_fix[evtcode]*tim_tick
Id = self._get_evtcode_Id(evtcode)
return self._get_Id_delay(Id, **kwargs)
@@ -225,3 +253,75 @@ class EventReceiver:
def __init__(self, pvname, name=None):
self.name = name
self.pvname = pvname
class CTA_sequencer:
def __init__(self, Id, name=None, master_frequency=100):
self._cta = CtaLib(Id)
self.sequence_local = {}
self.synced = False
self._master_frequency = master_frequency
def get_active_sequence(self):
self.sequence_local = self._cta.download()
self.length = self._cta.get_length()
self.synced = True
def upload_local_sequence(self):
self._cta.upload(self.sequence_local)
def get_start_config(self,set_params=True):
cfg = self._cta.get_start_config()
if set_params:
self._start_immediately = cfg['mode']
self.start_divisor = cfg['divisor']
self.start_offset = cfg['offset']
else:
return cfg
def set_start_config(self,divisor, offset):
if divisor==1 and offset ==0:
mode = 0
else:
mode = 1
self._cta.set_start_config(config={'mode':mode, 'modulo':modulo, 'offset':offset})
def reset_local_sequence(self):
self.sequence_local = {}
self.length = 0
self.synced = False
def append_singlecode(self,code,pulse_delay):
if self.length == 0:
self.length = 1
if not code in self.sequence_local.keys():
self.sequence_local[code] = self.length * [0]
self.length += pulse_delay
for tc in self.sequence_local.keys():
self.sequence_local[tc].extend(pulse_delay*[0])
self.sequence_local[code][self.length-1] = 1
self.synced = False
def set_repetitions(self,n_rep):
"""Set the number of sequence repetitions, 0 is infinite repetitions"""
ntim = int(n_rep>0)
self._cta.set_repetition_config(config={'mode': ntim, 'n': n_rep})
def get_repetitions(self):
"""Get the number of sequence repetitions, 0 is infinite repetitions"""
repc = self._cta.get_repetition_config()
if repc['mode']==0:
return 0
else:
return repc['n']
def start(self):
self._cta.start()
def stop(self):
self._cta.stop()
+32 -6
View File
@@ -51,6 +51,17 @@ def init_device(type_string, name, args=[], kwargs={}, verbose=True, lazy=True):
raise expt
def get_dependencies(inp):
outp = []
if isinstance(inp, dict):
inp = inp.values()
for ta in inp:
if isinstance(ta, Component):
outp.append(ta.name)
elif isinstance(ta, dict) or isinstance(ta, list):
outp.append(get_dependencies(ta))
return outp
def replaceComponent(inp, dict_all):
if isinstance(inp, list):
outp = []
@@ -74,7 +85,6 @@ def replaceComponent(inp, dict_all):
return inp
return outp
def initFromConfigList(config_list, lazy=False):
op = {}
for td in config_list:
@@ -83,17 +93,21 @@ def initFromConfigList(config_list, lazy=False):
# tkwk: op[tkwv.name] if isinstance(tkwv, Component) else tkwv
# for tkwk, tkwv in td["kwargs"].items()
# }
try:
tlazy = td['lazy']
except:
tlazy = lazy
op[td["name"]] = init_device(
td["type"],
td["name"],
replaceComponent(td["args"], op),
replaceComponent(td["kwargs"], op),
lazy=lazy,
lazy=tlazy,
)
return op
class ExperimentConfiguration:
class Configuration:
"""Configuration collector object collecting important settings for arbitrary use,
linking to one or few standard config files in the file system. Sould also be used
for config file writing."""
@@ -110,11 +124,11 @@ class ExperimentConfiguration:
assert (
type(self._config) is dict
), f"Problem reading {self.configFile} json file, seems not to be a valid dictionary structure!"
self.__dict__.update(self._config)
# self.__dict__.update(self._config)
def __setitem__(self, key, item):
self._config[key] = item
self.__dict__.update(self._config)
# self.__dict__.update(self._config)
self.saveConfigFile()
def __getitem__(self, key):
@@ -133,6 +147,9 @@ class ExperimentConfiguration:
return
writeConfig(filename, self._config)
def _ipython_key_completions_(self):
return list(self._config.keys())
def __repr__(self):
return json.dumps(self._config,indent=4)
@@ -144,7 +161,7 @@ def loadConfig(fina):
def writeConfig(fina, obj):
with open(fina, "w") as f:
json.dump(obj, f)
json.dump(obj, f,indent=4)
def parseChannelListFile(fina):
@@ -160,3 +177,12 @@ def parseChannelListFile(fina):
if not d[0] == "#":
out.append(d.strip())
return out
def append_to_path(*args):
for targ in args:
sys.path.append(targ)
def prepend_to_path(*args):
for targ in args:
sys.path.insert(0,targ)
-1
View File
@@ -2,7 +2,6 @@ import sys
sys.path.append("..")
from ..devices_general.motors import MotorRecord
from ..devices_general.adjustable import ValueRdback
from epics import PV
from ..aliases import Alias
+2 -2
View File
@@ -78,9 +78,9 @@ def main():
shell.enable_matplotlib()
shell()
else:
from IPython import embed
from IPython import start_ipython
embed()
start_ipython()
if __name__ == "__main__":