apsbits 1.0.0rc2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- apsbits/__init__.py +18 -0
- apsbits/_version.py +21 -0
- apsbits/core/__init__.py +11 -0
- apsbits/core/best_effort_init.py +38 -0
- apsbits/core/catalog_init.py +29 -0
- apsbits/core/run_engine_init.py +71 -0
- apsbits/demo_instrument/README.md +1 -0
- apsbits/demo_instrument/__init__.py +3 -0
- apsbits/demo_instrument/callbacks/__init__.py +1 -0
- apsbits/demo_instrument/callbacks/nexus_data_file_writer.py +60 -0
- apsbits/demo_instrument/callbacks/spec_data_file_writer.py +93 -0
- apsbits/demo_instrument/configs/__init__.py +1 -0
- apsbits/demo_instrument/configs/devices.yml +52 -0
- apsbits/demo_instrument/configs/devices_aps_only.yml +6 -0
- apsbits/demo_instrument/configs/iconfig.yml +81 -0
- apsbits/demo_instrument/configs/logging.yml +41 -0
- apsbits/demo_instrument/devices/__init__.py +1 -0
- apsbits/demo_instrument/plans/__init__.py +8 -0
- apsbits/demo_instrument/plans/dm_plans.py +111 -0
- apsbits/demo_instrument/plans/sim_plans.py +69 -0
- apsbits/demo_instrument/startup.py +63 -0
- apsbits/tests/__init__.py +1 -0
- apsbits/tests/conftest.py +32 -0
- apsbits/tests/test_device_factories.py +44 -0
- apsbits/tests/test_general.py +83 -0
- apsbits/tests/test_stored_dict.py +139 -0
- apsbits/utils/__init__.py +1 -0
- apsbits/utils/aps_functions.py +67 -0
- apsbits/utils/config_loaders.py +61 -0
- apsbits/utils/context_aware.py +187 -0
- apsbits/utils/controls_setup.py +113 -0
- apsbits/utils/create_new_instrument.py +144 -0
- apsbits/utils/helper_functions.py +113 -0
- apsbits/utils/logging_setup.py +211 -0
- apsbits/utils/make_devices_yaml.py +127 -0
- apsbits/utils/metadata.py +101 -0
- apsbits/utils/sim_creator.py +199 -0
- apsbits/utils/stored_dict.py +192 -0
- apsbits-1.0.0rc2.dist-info/LICENSE +48 -0
- apsbits-1.0.0rc2.dist-info/METADATA +349 -0
- apsbits-1.0.0rc2.dist-info/RECORD +44 -0
- apsbits-1.0.0rc2.dist-info/WHEEL +5 -0
- apsbits-1.0.0rc2.dist-info/entry_points.txt +2 -0
- apsbits-1.0.0rc2.dist-info/top_level.txt +1 -0
apsbits/__init__.py
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# -*- coding: iso-8859-1 -*-
|
|
2
|
+
|
|
3
|
+
"""Model Bluesky Data Acquisition Instrument."""
|
|
4
|
+
|
|
5
|
+
from apsbits.utils.logging_setup import configure_logging
|
|
6
|
+
|
|
7
|
+
configure_logging()
|
|
8
|
+
|
|
9
|
+
__package__ = "apsbits"
|
|
10
|
+
try:
|
|
11
|
+
from setuptools_scm import get_version
|
|
12
|
+
|
|
13
|
+
__version__ = get_version(root="..", relative_to=__file__)
|
|
14
|
+
del get_version
|
|
15
|
+
except (LookupError, ModuleNotFoundError):
|
|
16
|
+
from importlib.metadata import version
|
|
17
|
+
|
|
18
|
+
__version__ = version(__package__)
|
apsbits/_version.py
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# file generated by setuptools-scm
|
|
2
|
+
# don't change, don't track in version control
|
|
3
|
+
|
|
4
|
+
__all__ = ["__version__", "__version_tuple__", "version", "version_tuple"]
|
|
5
|
+
|
|
6
|
+
TYPE_CHECKING = False
|
|
7
|
+
if TYPE_CHECKING:
|
|
8
|
+
from typing import Tuple
|
|
9
|
+
from typing import Union
|
|
10
|
+
|
|
11
|
+
VERSION_TUPLE = Tuple[Union[int, str], ...]
|
|
12
|
+
else:
|
|
13
|
+
VERSION_TUPLE = object
|
|
14
|
+
|
|
15
|
+
version: str
|
|
16
|
+
__version__: str
|
|
17
|
+
__version_tuple__: VERSION_TUPLE
|
|
18
|
+
version_tuple: VERSION_TUPLE
|
|
19
|
+
|
|
20
|
+
__version__ = version = '1.0.0rc2'
|
|
21
|
+
__version_tuple__ = version_tuple = (1, 0, 0, 'rc2')
|
apsbits/core/__init__.py
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Utility support to start bluesky sessions.
|
|
3
|
+
|
|
4
|
+
Also contains setup code that MUST run before other code in this directory.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from apsbits.utils.helper_functions import debug_python
|
|
8
|
+
from apsbits.utils.helper_functions import mpl_setup
|
|
9
|
+
|
|
10
|
+
debug_python()
|
|
11
|
+
mpl_setup()
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"""
|
|
2
|
+
BestEffortCallback: simple real-time visualizations, provides ``bec``.
|
|
3
|
+
======================================================================
|
|
4
|
+
|
|
5
|
+
.. autosummary::
|
|
6
|
+
~bec
|
|
7
|
+
~peaks
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
import logging
|
|
11
|
+
|
|
12
|
+
from bluesky.callbacks.best_effort import BestEffortCallback
|
|
13
|
+
|
|
14
|
+
from apsbits.utils.config_loaders import iconfig
|
|
15
|
+
from apsbits.utils.helper_functions import running_in_queueserver
|
|
16
|
+
|
|
17
|
+
logger = logging.getLogger(__name__)
|
|
18
|
+
logger.bsdev(__file__)
|
|
19
|
+
|
|
20
|
+
bec = BestEffortCallback()
|
|
21
|
+
"""BestEffortCallback object, creates live tables and plots."""
|
|
22
|
+
|
|
23
|
+
bec_config = iconfig.get("BEC", {})
|
|
24
|
+
|
|
25
|
+
if not bec_config.get("BASELINE", True):
|
|
26
|
+
bec.disable_baseline()
|
|
27
|
+
|
|
28
|
+
if not bec_config.get("HEADING", True):
|
|
29
|
+
bec.disable_heading()
|
|
30
|
+
|
|
31
|
+
if not bec_config.get("PLOTS", True) or running_in_queueserver():
|
|
32
|
+
bec.disable_plots()
|
|
33
|
+
|
|
34
|
+
if not bec_config.get("TABLE", True):
|
|
35
|
+
bec.disable_table()
|
|
36
|
+
|
|
37
|
+
peaks = bec.peaks
|
|
38
|
+
"""Dictionary with statistical analysis of LivePlots."""
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Databroker catalog, provides ``cat``
|
|
3
|
+
====================================
|
|
4
|
+
|
|
5
|
+
.. autosummary::
|
|
6
|
+
~cat
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import logging
|
|
10
|
+
|
|
11
|
+
import databroker
|
|
12
|
+
|
|
13
|
+
from apsbits.utils.config_loaders import iconfig
|
|
14
|
+
|
|
15
|
+
logger = logging.getLogger(__name__)
|
|
16
|
+
logger.bsdev(__file__)
|
|
17
|
+
|
|
18
|
+
TEMPORARY_CATALOG_NAME = "temp"
|
|
19
|
+
|
|
20
|
+
catalog_name = iconfig.get("DATABROKER_CATALOG", TEMPORARY_CATALOG_NAME)
|
|
21
|
+
try:
|
|
22
|
+
_cat = databroker.catalog[catalog_name].v2
|
|
23
|
+
except KeyError:
|
|
24
|
+
_cat = databroker.temp().v2
|
|
25
|
+
|
|
26
|
+
cat = _cat
|
|
27
|
+
"""Databroker catalog object, receives new data from ``RE``."""
|
|
28
|
+
|
|
29
|
+
logger.info("Databroker catalog: %s", cat.name)
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Setup the Bluesky RunEngine, provides ``RE`` and ``sd``.
|
|
3
|
+
========================================================
|
|
4
|
+
|
|
5
|
+
.. autosummary::
|
|
6
|
+
~RE
|
|
7
|
+
~sd
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
import logging
|
|
11
|
+
|
|
12
|
+
import bluesky
|
|
13
|
+
from bluesky.utils import ProgressBarManager
|
|
14
|
+
|
|
15
|
+
from apsbits.core.best_effort_init import bec
|
|
16
|
+
from apsbits.core.catalog_init import cat
|
|
17
|
+
from apsbits.utils.config_loaders import iconfig
|
|
18
|
+
from apsbits.utils.controls_setup import connect_scan_id_pv
|
|
19
|
+
from apsbits.utils.controls_setup import set_control_layer
|
|
20
|
+
from apsbits.utils.controls_setup import set_timeouts
|
|
21
|
+
from apsbits.utils.metadata import MD_PATH
|
|
22
|
+
from apsbits.utils.stored_dict import StoredDict
|
|
23
|
+
|
|
24
|
+
logger = logging.getLogger(__name__)
|
|
25
|
+
logger.bsdev(__file__)
|
|
26
|
+
|
|
27
|
+
re_config = iconfig.get("RUN_ENGINE", {})
|
|
28
|
+
|
|
29
|
+
RE = bluesky.RunEngine()
|
|
30
|
+
"""The bluesky RunEngine object."""
|
|
31
|
+
|
|
32
|
+
# Save/restore RE.md dictionary, in this precise order.
|
|
33
|
+
if MD_PATH is not None:
|
|
34
|
+
handler_name = re_config.get("MD_STORAGE_HANDLER", "StoredDict")
|
|
35
|
+
logger.debug(
|
|
36
|
+
"Select %r to store 'RE.md' dictionary in %s.",
|
|
37
|
+
handler_name,
|
|
38
|
+
MD_PATH,
|
|
39
|
+
)
|
|
40
|
+
try:
|
|
41
|
+
if handler_name == "PersistentDict":
|
|
42
|
+
RE.md = bluesky.utils.PersistentDict(MD_PATH)
|
|
43
|
+
else:
|
|
44
|
+
RE.md = StoredDict(MD_PATH)
|
|
45
|
+
except Exception as error:
|
|
46
|
+
print(
|
|
47
|
+
"\n"
|
|
48
|
+
f"Could not create {handler_name} for RE metadata. Continuing"
|
|
49
|
+
f" without saving metadata to disk. {error=}\n"
|
|
50
|
+
)
|
|
51
|
+
logger.warning("%s('%s') error:%s", handler_name, MD_PATH, error)
|
|
52
|
+
|
|
53
|
+
# RE.md.update(re_metadata(cat)) # programmatic metadata
|
|
54
|
+
# RE.md.update(re_config.get("DEFAULT_METADATA", {}))
|
|
55
|
+
|
|
56
|
+
sd = bluesky.SupplementalData()
|
|
57
|
+
"""Baselines & monitors for ``RE``."""
|
|
58
|
+
|
|
59
|
+
RE.subscribe(cat.v1.insert)
|
|
60
|
+
RE.subscribe(bec)
|
|
61
|
+
RE.preprocessors.append(sd)
|
|
62
|
+
|
|
63
|
+
set_control_layer()
|
|
64
|
+
set_timeouts() # MUST happen before ANY EpicsSignalBase (or subclass) is created.
|
|
65
|
+
|
|
66
|
+
connect_scan_id_pv(RE) # if configured
|
|
67
|
+
|
|
68
|
+
if re_config.get("USE_PROGRESS_BAR", True):
|
|
69
|
+
# Add a progress bar.
|
|
70
|
+
pbar_manager = ProgressBarManager()
|
|
71
|
+
RE.waiting_hook = pbar_manager
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
## Demo Instrument
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""RunEngine callbacks, mostly."""
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
"""
|
|
2
|
+
|
|
3
|
+
NeXus Writer
|
|
4
|
+
============
|
|
5
|
+
|
|
6
|
+
Write scan(s) to a NeXus/HDF5 file.
|
|
7
|
+
|
|
8
|
+
.. autosummary::
|
|
9
|
+
:nosignatures:
|
|
10
|
+
|
|
11
|
+
~MyNXWriter
|
|
12
|
+
~nxwriter
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
import logging
|
|
16
|
+
|
|
17
|
+
from apsbits.core.run_engine_init import RE
|
|
18
|
+
from apsbits.utils.aps_functions import host_on_aps_subnet
|
|
19
|
+
from apsbits.utils.config_loaders import iconfig
|
|
20
|
+
|
|
21
|
+
logger = logging.getLogger(__name__)
|
|
22
|
+
logger.bsdev(__file__)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
if host_on_aps_subnet():
|
|
26
|
+
from apstools.callbacks import NXWriterAPS as NXWriter
|
|
27
|
+
else:
|
|
28
|
+
from apstools.callbacks import NXWriter
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class MyNXWriter(NXWriter):
|
|
32
|
+
"""Patch to get sample title from metadata, if available."""
|
|
33
|
+
|
|
34
|
+
def get_sample_title(self):
|
|
35
|
+
"""
|
|
36
|
+
Get the title from the metadata or modify the default.
|
|
37
|
+
|
|
38
|
+
default title: S{scan_id}-{plan_name}-{short_uid}
|
|
39
|
+
"""
|
|
40
|
+
try:
|
|
41
|
+
title = self.metadata["title"]
|
|
42
|
+
except KeyError:
|
|
43
|
+
# title = super().get_sample_title() # the default title
|
|
44
|
+
title = f"S{self.scan_id:05d}-{self.plan_name}-{self.uid[:7]}"
|
|
45
|
+
return title
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
nxwriter = MyNXWriter() # create the callback instance
|
|
49
|
+
"""The NeXus file writer object."""
|
|
50
|
+
|
|
51
|
+
if iconfig.get("NEXUS_DATA_FILES", {}).get("ENABLE", False):
|
|
52
|
+
RE.subscribe(nxwriter.receiver) # write data to NeXus files
|
|
53
|
+
|
|
54
|
+
nxwriter.file_extension = iconfig.get("NEXUS_DATA_FILES", {}).get(
|
|
55
|
+
"FILE_EXTENSION", "hdf"
|
|
56
|
+
)
|
|
57
|
+
print("\n\n\n")
|
|
58
|
+
print(nxwriter.file_extension)
|
|
59
|
+
warn_missing = iconfig.get("NEXUS_DATA_FILES", {}).get("WARN_MISSING", False)
|
|
60
|
+
nxwriter.warn_on_missing_content = warn_missing
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
"""
|
|
2
|
+
custom callbacks
|
|
3
|
+
================
|
|
4
|
+
|
|
5
|
+
.. autosummary::
|
|
6
|
+
:nosignatures:
|
|
7
|
+
|
|
8
|
+
~newSpecFile
|
|
9
|
+
~spec_comment
|
|
10
|
+
~specwriter
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
import datetime
|
|
14
|
+
import logging
|
|
15
|
+
import pathlib
|
|
16
|
+
|
|
17
|
+
import apstools.callbacks
|
|
18
|
+
import apstools.utils
|
|
19
|
+
|
|
20
|
+
from apsbits.core.run_engine_init import RE
|
|
21
|
+
from apsbits.utils.config_loaders import iconfig
|
|
22
|
+
|
|
23
|
+
logger = logging.getLogger(__name__)
|
|
24
|
+
logger.bsdev(__file__)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
file_extension = iconfig.get("SPEC_DATA_FILES", {}).get("FILE_EXTENSION", "dat")
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def spec_comment(comment, doc=None):
|
|
31
|
+
"""Make it easy for user to add comments to the data file."""
|
|
32
|
+
apstools.callbacks.spec_comment(comment, doc, specwriter)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def newSpecFile(title, scan_id=None, RE=None):
|
|
36
|
+
"""
|
|
37
|
+
User choice of the SPEC file name.
|
|
38
|
+
|
|
39
|
+
Cleans up title, prepends month and day and appends file extension.
|
|
40
|
+
If ``RE`` is passed, then resets ``RE.md["scan_id"] = scan_id``.
|
|
41
|
+
|
|
42
|
+
If the SPEC file already exists, then ``scan_id`` is ignored and
|
|
43
|
+
``RE.md["scan_id"]`` is set to the last scan number in the file.
|
|
44
|
+
"""
|
|
45
|
+
kwargs = {}
|
|
46
|
+
if RE is not None:
|
|
47
|
+
kwargs["RE"] = RE
|
|
48
|
+
|
|
49
|
+
mmdd = str(datetime.datetime.now()).split()[0][5:].replace("-", "_")
|
|
50
|
+
clean = apstools.utils.cleanupText(title)
|
|
51
|
+
fname = pathlib.Path(f"{mmdd}_{clean}.{file_extension}")
|
|
52
|
+
if fname.exists():
|
|
53
|
+
logger.warning(f">>> file already exists: {fname} <<<")
|
|
54
|
+
handled = "appended"
|
|
55
|
+
else:
|
|
56
|
+
kwargs["scan_id"] = scan_id or 1
|
|
57
|
+
handled = "created"
|
|
58
|
+
|
|
59
|
+
specwriter.newfile(fname, **kwargs)
|
|
60
|
+
|
|
61
|
+
logger.info(f"SPEC file name : {specwriter.spec_filename}")
|
|
62
|
+
logger.info(f"File will be {handled} at end of next bluesky scan.")
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
# write scans to SPEC data file
|
|
66
|
+
try:
|
|
67
|
+
# apstools >=1.6.21
|
|
68
|
+
_specwriter = apstools.callbacks.SpecWriterCallback2()
|
|
69
|
+
except AttributeError:
|
|
70
|
+
# apstools <1.6.21
|
|
71
|
+
_specwriter = apstools.callbacks.SpecWriterCallback()
|
|
72
|
+
|
|
73
|
+
specwriter = _specwriter
|
|
74
|
+
"""The SPEC file writer object."""
|
|
75
|
+
|
|
76
|
+
# make the SPEC file in current working directory (assumes is writable)
|
|
77
|
+
specwriter.newfile(specwriter.spec_filename)
|
|
78
|
+
|
|
79
|
+
if iconfig.get("SPEC_DATA_FILES", {}).get("ENABLE", False):
|
|
80
|
+
RE.subscribe(specwriter.receiver) # write data to SPEC files
|
|
81
|
+
logger.info("SPEC data file: %s", specwriter.spec_filename.resolve())
|
|
82
|
+
|
|
83
|
+
try:
|
|
84
|
+
# feature new in apstools 1.6.14
|
|
85
|
+
from apstools.plans import label_stream_wrapper
|
|
86
|
+
|
|
87
|
+
def motor_start_preprocessor(plan):
|
|
88
|
+
"""Record motor positions at start of each run."""
|
|
89
|
+
return label_stream_wrapper(plan, "motor", when="start")
|
|
90
|
+
|
|
91
|
+
RE.preprocessors.append(motor_start_preprocessor)
|
|
92
|
+
except Exception:
|
|
93
|
+
logger.warning("Could load support to log motors positions.")
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Configs required to set up user package"""
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# Guarneri-style device YAML configuration
|
|
2
|
+
|
|
3
|
+
apsbits.utils.sim_creator.predefined_device:
|
|
4
|
+
- {creator: ophyd.sim.motor, name: sim_motor}
|
|
5
|
+
- {creator: ophyd.sim.noisy_det, name: sim_det}
|
|
6
|
+
|
|
7
|
+
apstools.devices.SimulatedApsPssShutterWithStatus:
|
|
8
|
+
- name: shutter
|
|
9
|
+
labels: ["shutters"]
|
|
10
|
+
|
|
11
|
+
# ophyd.Signal:
|
|
12
|
+
# - name: test
|
|
13
|
+
# value: 50.7
|
|
14
|
+
# - name: t2
|
|
15
|
+
# value: 2
|
|
16
|
+
|
|
17
|
+
# apstools.synApps.Optics2Slit2D_HV:
|
|
18
|
+
# - name: slit1
|
|
19
|
+
# prefix: ioc:Slit1
|
|
20
|
+
# labels: ["slits"]
|
|
21
|
+
|
|
22
|
+
# hkl.SimulatedE4CV:
|
|
23
|
+
# - name: sim4c
|
|
24
|
+
# prefix: ""
|
|
25
|
+
# labels: ["diffractometer"]
|
|
26
|
+
|
|
27
|
+
# ophyd.scaler.ScalerCH:
|
|
28
|
+
# - name: scaler1
|
|
29
|
+
# prefix: vme:scaler1
|
|
30
|
+
# labels: ["scalers", "detectors"]
|
|
31
|
+
|
|
32
|
+
# ophyd.EpicsMotor:
|
|
33
|
+
# - {name: m1, prefix: gp:m1, labels: ["motor"]}
|
|
34
|
+
# - {name: m2, prefix: gp:m2, labels: ["motor"]}
|
|
35
|
+
# - {name: m3, prefix: gp:m3, labels: ["motor"]}
|
|
36
|
+
# - {name: m4, prefix: gp:m4, labels: ["motor"]}
|
|
37
|
+
|
|
38
|
+
# apstools.devices.ad_creator:
|
|
39
|
+
# - name: adsimdet
|
|
40
|
+
# prefix: "ad:"
|
|
41
|
+
# labels: ["area_detector", "detectors"]
|
|
42
|
+
# plugins:
|
|
43
|
+
# - cam:
|
|
44
|
+
# class: apstools.devices.SimDetectorCam_V34
|
|
45
|
+
# - image
|
|
46
|
+
# - pva
|
|
47
|
+
# - hdf1:
|
|
48
|
+
# class: apstools.devices.AD_EpicsFileNameHDF5Plugin
|
|
49
|
+
# read_path_template: "/path/to/bluesky/tmp/"
|
|
50
|
+
# write_path_template: "/path/to/ioc/tmp/"
|
|
51
|
+
# - roi1
|
|
52
|
+
# - stats1
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
# Configuration for the Bluesky instrument package.
|
|
2
|
+
|
|
3
|
+
# identify the version of this iconfig.yml file
|
|
4
|
+
ICONFIG_VERSION: 2.0.0
|
|
5
|
+
|
|
6
|
+
# Add additional configuration for use with your instrument.
|
|
7
|
+
|
|
8
|
+
### The short name for the databroker catalog.
|
|
9
|
+
DATABROKER_CATALOG: &databroker_catalog temp
|
|
10
|
+
|
|
11
|
+
### RunEngine configuration
|
|
12
|
+
RUN_ENGINE:
|
|
13
|
+
DEFAULT_METADATA:
|
|
14
|
+
beamline_id: demo_instrument
|
|
15
|
+
instrument_name: Most Glorious Scientific Instrument
|
|
16
|
+
proposal_id: commissioning
|
|
17
|
+
databroker_catalog: *databroker_catalog
|
|
18
|
+
|
|
19
|
+
### EPICS PV to use for the `scan_id`.
|
|
20
|
+
### Default: `RE.md["scan_id"]` (not using an EPICS PV)
|
|
21
|
+
# SCAN_ID_PV: "IOC:bluesky_scan_id"
|
|
22
|
+
|
|
23
|
+
### Where to "autosave" the RE.md dictionary.
|
|
24
|
+
### Defaults:
|
|
25
|
+
MD_STORAGE_HANDLER: StoredDict
|
|
26
|
+
MD_PATH: .re_md_dict.yml
|
|
27
|
+
|
|
28
|
+
### The progress bar is nice to see,
|
|
29
|
+
### except when it clutters the output in Jupyter notebooks.
|
|
30
|
+
### Default: False
|
|
31
|
+
USE_PROGRESS_BAR: false
|
|
32
|
+
|
|
33
|
+
# Command-line tools, such as %wa, %ct, ...
|
|
34
|
+
USE_BLUESKY_MAGICS: true
|
|
35
|
+
|
|
36
|
+
### Best Effort Callback Configurations
|
|
37
|
+
### Defaults: all true
|
|
38
|
+
### except no plots in queueserver
|
|
39
|
+
BEC:
|
|
40
|
+
BASELINE: true
|
|
41
|
+
HEADING: true
|
|
42
|
+
PLOTS: false
|
|
43
|
+
TABLE: true
|
|
44
|
+
|
|
45
|
+
### Support for known output file formats.
|
|
46
|
+
### Uncomment to use. If undefined, will not write that type of file.
|
|
47
|
+
### Each callback should apply its configuration from here.
|
|
48
|
+
NEXUS_DATA_FILES:
|
|
49
|
+
ENABLE: false
|
|
50
|
+
FILE_EXTENSION: hdf
|
|
51
|
+
WARN_MISSING_CONTENT: true
|
|
52
|
+
|
|
53
|
+
SPEC_DATA_FILES:
|
|
54
|
+
ENABLE: true
|
|
55
|
+
FILE_EXTENSION: dat
|
|
56
|
+
|
|
57
|
+
### APS Data Management
|
|
58
|
+
### Use bash shell, deactivate all conda environments, source this file:
|
|
59
|
+
DM_SETUP_FILE: "/home/dm/etc/dm.setup.sh"
|
|
60
|
+
|
|
61
|
+
### Local OPHYD Device Control Yaml
|
|
62
|
+
DEVICES_FILE: devices.yml
|
|
63
|
+
APS_DEVICES_FILE: devices_aps_only.yml
|
|
64
|
+
|
|
65
|
+
# ----------------------------------
|
|
66
|
+
|
|
67
|
+
OPHYD:
|
|
68
|
+
### Control layer for ophyd to communicate with EPICS.
|
|
69
|
+
### Default: PyEpics
|
|
70
|
+
### Choices: "PyEpics" or "caproto" # caproto is not yet supported
|
|
71
|
+
CONTROL_LAYER: PyEpics
|
|
72
|
+
|
|
73
|
+
### default timeouts (seconds)
|
|
74
|
+
TIMEOUTS:
|
|
75
|
+
PV_READ: &TIMEOUT 5
|
|
76
|
+
PV_WRITE: *TIMEOUT
|
|
77
|
+
PV_CONNECTION: *TIMEOUT
|
|
78
|
+
|
|
79
|
+
# Control detail of exception traces in IPython (console and notebook).
|
|
80
|
+
# Options are: Minimal, Plain, Verbose
|
|
81
|
+
XMODE_DEBUG_LEVEL: Plain
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# Bluesky Session Logging Configuration
|
|
2
|
+
|
|
3
|
+
console_logs:
|
|
4
|
+
date_format: "%a-%H:%M:%S"
|
|
5
|
+
log_format: "%(levelname)-.1s %(asctime)s.%(msecs)03d: %(message)s"
|
|
6
|
+
level: info
|
|
7
|
+
root_level: bsdev
|
|
8
|
+
|
|
9
|
+
file_logs:
|
|
10
|
+
date_format: "%Y-%m-%d %H:%M:%S"
|
|
11
|
+
log_directory: .logs
|
|
12
|
+
log_filename_base: logging.log
|
|
13
|
+
log_format: "|\
|
|
14
|
+
%(asctime)s.%(msecs)03d|\
|
|
15
|
+
%(levelname)s|\
|
|
16
|
+
%(process)d|\
|
|
17
|
+
%(name)s|\
|
|
18
|
+
%(module)s|\
|
|
19
|
+
%(lineno)d|\
|
|
20
|
+
%(threadName)s| - \
|
|
21
|
+
%(message)s"
|
|
22
|
+
maxBytes: 1_000_000
|
|
23
|
+
backupCount: 9
|
|
24
|
+
level: info
|
|
25
|
+
rotate_on_startup: true
|
|
26
|
+
|
|
27
|
+
ipython_logs:
|
|
28
|
+
log_directory: .logs
|
|
29
|
+
log_filename_base: ipython_log.py
|
|
30
|
+
log_mode: rotate
|
|
31
|
+
options: -o -t
|
|
32
|
+
|
|
33
|
+
modules:
|
|
34
|
+
apstools: warning
|
|
35
|
+
bluesky-queueserver: warning
|
|
36
|
+
bluesky: warning
|
|
37
|
+
bluesky.RE: warning
|
|
38
|
+
caproto: warning
|
|
39
|
+
databroker: warning
|
|
40
|
+
instrument: bsdev
|
|
41
|
+
ophyd: warning
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Ophyd-style devices."""
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
"""Bluesky plans."""
|
|
2
|
+
|
|
3
|
+
from .dm_plans import dm_kickoff_workflow # noqa: F401
|
|
4
|
+
from .dm_plans import dm_list_processing_jobs # noqa: F401
|
|
5
|
+
from .dm_plans import dm_submit_workflow_job # noqa: F401
|
|
6
|
+
from .sim_plans import sim_count_plan # noqa: F401
|
|
7
|
+
from .sim_plans import sim_print_plan # noqa: F401
|
|
8
|
+
from .sim_plans import sim_rel_scan_plan # noqa: F401
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Plans in support of APS Data Management
|
|
3
|
+
=======================================
|
|
4
|
+
|
|
5
|
+
.. autosummary::
|
|
6
|
+
|
|
7
|
+
~dm_kickoff_workflow
|
|
8
|
+
~dm_list_processing_jobs
|
|
9
|
+
~dm_submit_workflow_job
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
import logging
|
|
13
|
+
|
|
14
|
+
from apstools.devices import DM_WorkflowConnector
|
|
15
|
+
from apstools.utils import dm_api_proc
|
|
16
|
+
from apstools.utils import share_bluesky_metadata_with_dm
|
|
17
|
+
from bluesky import plan_stubs as bps
|
|
18
|
+
|
|
19
|
+
logger = logging.getLogger(__name__)
|
|
20
|
+
logger.bsdev(__file__)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def dm_kickoff_workflow(run, argsDict, timeout=None, wait=False):
|
|
24
|
+
"""
|
|
25
|
+
Start a DM workflow for this bluesky run and share run's metadata with DM.
|
|
26
|
+
|
|
27
|
+
PARAMETERS:
|
|
28
|
+
|
|
29
|
+
run (*obj*): Bluesky run object (such as 'run = cat[uid]').
|
|
30
|
+
|
|
31
|
+
argsDict (*dict*): Dictionary of parameters needed by 'workflowName'.
|
|
32
|
+
At minimum, most workflows expect these keys: 'filePath' and
|
|
33
|
+
'experimentName'. Consult the workflow for the expected
|
|
34
|
+
content of 'argsDict'.
|
|
35
|
+
|
|
36
|
+
timeout (*number*): When should bluesky stop reporting on this
|
|
37
|
+
DM workflow job (if it has not ended). Units are seconds.
|
|
38
|
+
Default is forever.
|
|
39
|
+
|
|
40
|
+
wait (*bool*): Should this plan stub wait for the job to end?
|
|
41
|
+
Default is 'False'.
|
|
42
|
+
"""
|
|
43
|
+
dm_workflow = DM_WorkflowConnector(name="dm_workflow")
|
|
44
|
+
|
|
45
|
+
if timeout is None:
|
|
46
|
+
# Disable periodic reports, use a long time (s).
|
|
47
|
+
timeout = 999_999_999_999
|
|
48
|
+
|
|
49
|
+
yield from bps.mv(dm_workflow.concise_reporting, True)
|
|
50
|
+
yield from bps.mv(dm_workflow.reporting_period, timeout)
|
|
51
|
+
|
|
52
|
+
workflow_name = argsDict.pop["workflowName"]
|
|
53
|
+
yield from dm_workflow.run_as_plan(
|
|
54
|
+
workflow=workflow_name,
|
|
55
|
+
wait=wait,
|
|
56
|
+
timeout=timeout,
|
|
57
|
+
**argsDict,
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
# Upload bluesky run metadata to APS DM.
|
|
61
|
+
share_bluesky_metadata_with_dm(argsDict["experimentName"], workflow_name, run)
|
|
62
|
+
|
|
63
|
+
# Users requested the DM workflow job ID be printed to the console.
|
|
64
|
+
dm_workflow._update_processing_data()
|
|
65
|
+
job_id = dm_workflow.job_id.get()
|
|
66
|
+
job_stage = dm_workflow.stage_id.get()
|
|
67
|
+
job_status = dm_workflow.status.get()
|
|
68
|
+
print(f"DM workflow id: {job_id!r} status: {job_status} stage: {job_stage}")
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def dm_list_processing_jobs(exclude=None):
|
|
72
|
+
"""
|
|
73
|
+
Show all the DM jobs with status not excluded.
|
|
74
|
+
|
|
75
|
+
Excluded status (default): 'done', 'failed'
|
|
76
|
+
"""
|
|
77
|
+
yield from bps.null() # make this a plan stub
|
|
78
|
+
api = dm_api_proc()
|
|
79
|
+
if exclude is None:
|
|
80
|
+
exclude = ("done", "failed")
|
|
81
|
+
|
|
82
|
+
for j in api.listProcessingJobs():
|
|
83
|
+
if j["status"] not in exclude:
|
|
84
|
+
print(
|
|
85
|
+
f"id={j['id']!r}"
|
|
86
|
+
f" submitted={j.get('submissionTimestamp')}"
|
|
87
|
+
f" status={j['status']!r}"
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def dm_submit_workflow_job(workflowName, argsDict):
|
|
92
|
+
"""
|
|
93
|
+
Low-level plan stub to submit a job to a DM workflow.
|
|
94
|
+
|
|
95
|
+
It is recommended to use dm_kickoff_workflow() instead.
|
|
96
|
+
This plan does not share run metadata with DM.
|
|
97
|
+
|
|
98
|
+
PARAMETERS:
|
|
99
|
+
|
|
100
|
+
workflowName (*str*): Name of the DM workflow to be run.
|
|
101
|
+
|
|
102
|
+
argsDict (*dict*): Dictionary of parameters needed by 'workflowName'.
|
|
103
|
+
At minimum, most workflows expect these keys: 'filePath' and
|
|
104
|
+
'experimentName'. Consult the workflow for the expected
|
|
105
|
+
content of 'argsDict'.
|
|
106
|
+
"""
|
|
107
|
+
yield from bps.null() # make this a plan stub
|
|
108
|
+
api = dm_api_proc()
|
|
109
|
+
|
|
110
|
+
job = api.startProcessingJob(api.username, workflowName, argsDict)
|
|
111
|
+
print(f"workflow={workflowName!r} id={job['id']!r}")
|