diff --git a/Readme.md b/Readme.md index 488e428..4f4935e 100755 --- a/Readme.md +++ b/Readme.md @@ -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 ``` diff --git a/eco/acquisition/dia.py b/eco/acquisition/dia.py index 93a2685..379331e 100644 --- a/eco/acquisition/dia.py +++ b/eco/acquisition/dia.py @@ -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() diff --git a/eco/bernina/bernina.py b/eco/bernina/bernina.py index 72fc595..da4c74a 100644 --- a/eco/bernina/bernina.py +++ b/eco/bernina/bernina.py @@ -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 + + + + + + + diff --git a/eco/bernina/config.py b/eco/bernina/config.py index 8ec5dd4..e27f3a3 100755 --- a/eco/bernina/config.py +++ b/eco/bernina/config.py @@ -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 #### diff --git a/eco/devices_general/adjustable.py b/eco/devices_general/adjustable.py index 8d4bdf0..abc4c0f 100644 --- a/eco/devices_general/adjustable.py +++ b/eco/devices_general/adjustable.py @@ -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() diff --git a/eco/ecocnf.py b/eco/ecocnf.py index 62a1dca..0114645 100644 --- a/eco/ecocnf.py +++ b/eco/ecocnf.py @@ -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') + + + + diff --git a/eco/endstations/bernina_cameras.py b/eco/endstations/bernina_cameras.py index 98e6749..ffa690a 100755 --- a/eco/endstations/bernina_cameras.py +++ b/eco/endstations/bernina_cameras.py @@ -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 diff --git a/eco/endstations/bernina_diffractometers.py b/eco/endstations/bernina_diffractometers.py index 9e76a02..adfb978 100644 --- a/eco/endstations/bernina_diffractometers.py +++ b/eco/endstations/bernina_diffractometers.py @@ -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 diff --git a/eco/timing/cta_lib.py b/eco/timing/cta_lib.py deleted file mode 100644 index f86d54e..0000000 --- a/eco/timing/cta_lib.py +++ /dev/null @@ -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(). diff --git a/eco/timing/event_timing.py b/eco/timing/event_timing.py index c770b37..80232c9 100644 --- a/eco/timing/event_timing.py +++ b/eco/timing/event_timing.py @@ -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() + + + diff --git a/eco/utilities/config.py b/eco/utilities/config.py index a33f519..2523264 100644 --- a/eco/utilities/config.py +++ b/eco/utilities/config.py @@ -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) diff --git a/eco/xoptics/offsetMirrors.py b/eco/xoptics/offsetMirrors.py index e04edc1..7e6d028 100755 --- a/eco/xoptics/offsetMirrors.py +++ b/eco/xoptics/offsetMirrors.py @@ -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 diff --git a/startup.py b/startup.py index f32d038..67dc13f 100644 --- a/startup.py +++ b/startup.py @@ -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__":