merge to dev
This commit is contained in:
@@ -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
|
||||
```
|
||||
|
||||
@@ -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
@@ -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
@@ -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 ####
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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,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
|
||||
|
||||
@@ -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().
|
||||
@@ -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
@@ -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)
|
||||
|
||||
@@ -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
@@ -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__":
|
||||
|
||||
Reference in New Issue
Block a user