From b8d4a0292b8737b8cf313a1e79a2f7bef8783bfe Mon Sep 17 00:00:00 2001 From: Henrik Lemke Date: Thu, 27 Dec 2018 21:40:56 +0100 Subject: [PATCH 1/9] startup and more --- eco/bernina/bernina.py | 28 +++++++++++++++++++++++++--- eco/bernina/config.py | 4 ++-- eco/ecocnf.py | 14 ++++++++++++++ eco/utilities/config.py | 16 +++++++++++++--- startup.py | 4 ++-- 5 files changed, 56 insertions(+), 10 deletions(-) diff --git a/eco/bernina/bernina.py b/eco/bernina/bernina.py index 14287dc..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 +import logging -itglobals = globals() +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 02d5c04..e967488 100755 --- a/eco/bernina/config.py +++ b/eco/bernina/config.py @@ -16,12 +16,12 @@ from ..utilities.config import ( Alias, init_device, initFromConfigList, - ExperimentConfiguration, + Configuration, ) _eco_lazy_init = False -config = ExperimentConfiguration( +config = Configuration( "/sf/bernina/config/exp/bernina_config_eco.json", name="bernina_config" ) diff --git a/eco/ecocnf.py b/eco/ecocnf.py index 62a1dca..53054a6 100644 --- a/eco/ecocnf.py +++ b/eco/ecocnf.py @@ -1,3 +1,8 @@ +import logging +import json +from pathlib import Path +from .eco.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/utilities/config.py b/eco/utilities/config.py index a33f519..aeb016e 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: @@ -93,7 +103,7 @@ def initFromConfigList(config_list, lazy=False): 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.""" @@ -144,7 +154,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): 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__": From 0805bf80512e39edbbbbd453e926865c114ac696 Mon Sep 17 00:00:00 2001 From: Henrik Lemke Date: Thu, 21 Feb 2019 18:42:09 +0100 Subject: [PATCH 2/9] test --- Readme.md | 6 +++--- eco/bernina/config.py | 5 +++++ eco/utilities/config.py | 9 +++++++++ 3 files changed, 17 insertions(+), 3 deletions(-) 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/bernina/config.py b/eco/bernina/config.py index 4364199..e74b40e 100755 --- a/eco/bernina/config.py +++ b/eco/bernina/config.py @@ -297,6 +297,11 @@ components = [ }, ] +try: + components.append(config.components) +except: + print('Could not append components from config.') + components_old = { "SARFE10-OPSH044": { diff --git a/eco/utilities/config.py b/eco/utilities/config.py index aeb016e..b7b319b 100644 --- a/eco/utilities/config.py +++ b/eco/utilities/config.py @@ -170,3 +170,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) \ No newline at end of file From a56e2f74d619601b511baaebfc5d6116e6216a03 Mon Sep 17 00:00:00 2001 From: Henrik Lemke Date: Mon, 18 Mar 2019 16:05:11 +0100 Subject: [PATCH 3/9] ipython config tab completion instead dummy class --- eco/utilities/config.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/eco/utilities/config.py b/eco/utilities/config.py index b7b319b..4866cad 100644 --- a/eco/utilities/config.py +++ b/eco/utilities/config.py @@ -120,11 +120,11 @@ class Configuration: 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): @@ -143,6 +143,9 @@ class Configuration: 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) @@ -178,4 +181,4 @@ def append_to_path(*args): def prepend_to_path(*args): for targ in args: - sys.path.insert(0,targ) \ No newline at end of file + sys.path.insert(0,targ) From 35c8bfa3b5c3e62bce94f0d86e90d4b23d485c08 Mon Sep 17 00:00:00 2001 From: lemke_h Date: Tue, 30 Apr 2019 10:40:39 +0200 Subject: [PATCH 4/9] started on cta implementation --- eco/ecocnf.py | 2 +- eco/timing/cta_lib.py | 491 ------------------------------------- eco/timing/event_timing.py | 20 ++ 3 files changed, 21 insertions(+), 492 deletions(-) delete mode 100644 eco/timing/cta_lib.py diff --git a/eco/ecocnf.py b/eco/ecocnf.py index 53054a6..46de8d5 100644 --- a/eco/ecocnf.py +++ b/eco/ecocnf.py @@ -1,7 +1,7 @@ import logging import json from pathlib import Path -from .eco.utilities.config import Configuration +from .utilities.config import Configuration startup_lazy = False 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..6aa5aaf 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", @@ -225,3 +227,21 @@ class EventReceiver: def __init__(self, pvname, name=None): self.name = name self.pvname = pvname + + +class CTA_sequencer: + def __init__(self, Id, name=None): + self._cta = CtaLib(Id) + self.sequence_local = [] + + def get_sequence(self): + pass + + def append_line(self,code,pulse_delay): + self.sequence_local.append((code,pulse_delay)) + + def start(self): + pass + + + From 6302c1d49e9d8c69587d8dd8e1db9bf857915200 Mon Sep 17 00:00:00 2001 From: lemke_h Date: Tue, 30 Apr 2019 15:53:19 +0200 Subject: [PATCH 5/9] started on cta implementation --- eco/timing/event_timing.py | 35 ++++++++++++++++++++++++++++++++--- 1 file changed, 32 insertions(+), 3 deletions(-) diff --git a/eco/timing/event_timing.py b/eco/timing/event_timing.py index 6aa5aaf..11b9733 100644 --- a/eco/timing/event_timing.py +++ b/eco/timing/event_timing.py @@ -230,18 +230,47 @@ class EventReceiver: class CTA_sequencer: - def __init__(self, Id, name=None): + 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_sequence(self): - pass + + self.synced = True + + 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 append_line(self,code,pulse_delay): self.sequence_local.append((code,pulse_delay)) + 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': self._cta.RepetitionMode.NTIMES(ntim), 'n': N}) def start(self): - pass + s._cta.start() + + def stop(self): + s._cta.stop() From 9a2e5b75de73f896371a52e23e397fead7b7353e Mon Sep 17 00:00:00 2001 From: lemke_h Date: Wed, 1 May 2019 14:21:17 +0200 Subject: [PATCH 6/9] fixed config bernina to new configuratoin object --- eco/bernina/config.py | 35 +++++++++++++++++++++++++---------- eco/ecocnf.py | 2 +- eco/utilities/config.py | 6 +++++- 3 files changed, 31 insertions(+), 12 deletions(-) diff --git a/eco/bernina/config.py b/eco/bernina/config.py index ba23752..076b9e5 100755 --- a/eco/bernina/config.py +++ b/eco/bernina/config.py @@ -22,7 +22,7 @@ from ..utilities.config import ( _eco_lazy_init = False config = Configuration( - "/sf/bernina/config/exp/bernina_config_eco.json", name="bernina_config" + "/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": [], @@ -323,15 +330,15 @@ components = [ "kwargs": { "instrument": "bernina", "api_address": "http://sf-daq-bernina:10000", - "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", @@ -344,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, @@ -357,10 +364,18 @@ 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": {} + }, ] try: - components.append(config.components) + components.extend(config['components']) + print('Did append additional components!') except: print('Could not append components from config.') diff --git a/eco/ecocnf.py b/eco/ecocnf.py index 46de8d5..0114645 100644 --- a/eco/ecocnf.py +++ b/eco/ecocnf.py @@ -14,7 +14,7 @@ scopes = [ -settings = Configuration(Path.home() / '.ecorc', name='eco_startup_settings') +# settings = Configuration(Path.home() / '.ecorc', name='eco_startup_settings') diff --git a/eco/utilities/config.py b/eco/utilities/config.py index 4866cad..2523264 100644 --- a/eco/utilities/config.py +++ b/eco/utilities/config.py @@ -93,12 +93,16 @@ 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 From 78b4d2e45e5c207f6eeb2109dcf34a4daf539fd0 Mon Sep 17 00:00:00 2001 From: lemke_h Date: Wed, 1 May 2019 14:43:25 +0200 Subject: [PATCH 7/9] fixed dia, update to bernins-op and fix in latest pedestal determination --- eco/acquisition/dia.py | 28 +++++++++++++++++----------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/eco/acquisition/dia.py b/eco/acquisition/dia.py index b6290c5..379331e 100644 --- a/eco/acquisition/dia.py +++ b/eco/acquisition/dia.py @@ -47,12 +47,13 @@ class DIAClient: print("ERROR: please configure the instrument parameter in DIAClient") self.update_config() self.active_clients = list(self.get_active_clients()['clients_enabled'].keys()) + self.jf_channels = list(x for x in self.active_clients if x != 'bsread') def update_config(self,): - try: - self.get_last_pedestal() - except: - print('Did not find a previous config file') + #try: + self.get_last_pedestal() + #except: + # print('Did not find a pedestal file in %s'%(self.pedestal_directory)) self.writer_config.update({ "output_file": "/sf/%s/data/p%d/raw/test_data" % (self.instrument, self.pgroup), @@ -160,12 +161,13 @@ class DIAClient: run as jungfrau_utils_run ) - directory = "/sf/%s/data/p%d/raw/JF_pedestal/" % (self.instrument, self.pgroup) + directory = "/sf/%s/data/p%d/raw/JF_pedestals/" % (self.instrument, self.pgroup) res_dir = directory.replace("/raw/", "/res/") if not os.path.exists(res_dir): print("Directory %s not existing, creating it" % res_dir) os.makedirs(res_dir) + os.chmod(res_dir, 0o775) filename = "pedestal_%s" % datetime.now().strftime("%Y%m%d_%H%M") period = 0.04 jungfrau_utils_run( @@ -191,6 +193,8 @@ class DIAClient: os.system('\;'.join(commandstr)) def get_last_pedestal(self): + self.active_clients = list(self.get_active_clients()['clients_enabled'].keys()) + self.jf_channels = list(x for x in self.active_clients if x != 'bsread') p = Path(self.pedestal_directory) allpedestals = [(datetime.strptime(f.stem.split('pedestal_')[1].split('.')[0],"%Y%m%d_%H%M"),f) for f in p.glob('*.h5')] completepedestals = [] @@ -201,14 +205,16 @@ class DIAClient: tpedset[1].append([tf for tt,tf in allpedestals if (tt==pedtime and channel in tf.as_posix())][0]) if len(tpedset[1])==len(self.jf_channels): completepedestals.append(tpedset) + else: + print('Number of pedestal files %4f not number of JFs %4f'%(len(tpedset[1]), len(self.jf_channels))) + return except: pass - # print(completepedestals) - 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() - + if len(completepedestals)>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() + def start(self): self.client.start() print("start acquisition") From c082d5c545c2c59add07afc9c3bb96c13dfa3882 Mon Sep 17 00:00:00 2001 From: lemke_h Date: Wed, 1 May 2019 15:01:17 +0200 Subject: [PATCH 8/9] moved Pv adjustable to adjustable --- eco/devices_general/adjustable.py | 189 +++++++++++------------------ eco/endstations/bernina_cameras.py | 2 +- 2 files changed, 71 insertions(+), 120 deletions(-) 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/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 From 90c1e7e0fb2fb36867be023153aeb0121f9249b4 Mon Sep 17 00:00:00 2001 From: lemke_h Date: Fri, 3 May 2019 09:35:58 +0200 Subject: [PATCH 9/9] added timing master and cta to bernina --- eco/bernina/config.py | 7 +++ eco/endstations/bernina_diffractometers.py | 1 - eco/timing/event_timing.py | 69 +++++++++++++++++++--- eco/xoptics/offsetMirrors.py | 1 - 4 files changed, 67 insertions(+), 11 deletions(-) diff --git a/eco/bernina/config.py b/eco/bernina/config.py index 076b9e5..d7966c0 100755 --- a/eco/bernina/config.py +++ b/eco/bernina/config.py @@ -371,6 +371,13 @@ components = [ "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: 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/event_timing.py b/eco/timing/event_timing.py index 11b9733..80232c9 100644 --- a/eco/timing/event_timing.py +++ b/eco/timing/event_timing.py @@ -99,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): @@ -141,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) @@ -232,15 +258,19 @@ class EventReceiver: class CTA_sequencer: def __init__(self, Id, name=None, master_frequency=100): self._cta = CtaLib(Id) - self.sequence_local = [] + self.sequence_local = {} self.synced = False self._master_frequency = master_frequency + - - def get_sequence(self): - + 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: @@ -257,20 +287,41 @@ class CTA_sequencer: mode = 1 self._cta.set_start_config(config={'mode':mode, 'modulo':modulo, 'offset':offset}) - def append_line(self,code,pulse_delay): - self.sequence_local.append((code,pulse_delay)) + 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': self._cta.RepetitionMode.NTIMES(ntim), 'n': N}) + 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): - s._cta.start() + self._cta.start() def stop(self): - s._cta.stop() + self._cta.stop() 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