Compare commits
125 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 3e80b0fd8d | |||
| 823142b296 | |||
| 5d6d0535af | |||
| 7797ce1980 | |||
| 7b1ea281a3 | |||
| a75320ccbc | |||
| c04d829fc6 | |||
| 5d862e1d5b | |||
| 3e959e6c5d | |||
| fe43dafac8 | |||
| 8493b60468 | |||
| 0365d6eac7 | |||
| 6da7e665b3 | |||
| b0a7d6905c | |||
| acc5e320cf | |||
| 3d2485aea7 | |||
| 131d7f7f3e | |||
| 16bd819a9f | |||
| b14f2c0fe3 | |||
| 09799554ba | |||
| 3d756469e3 | |||
| 4ca59c57be | |||
| 576c59f5e5 | |||
| ce3f231276 | |||
| 274bb9154c | |||
| 282756288f | |||
| 101954476c | |||
| 339adab06c | |||
| 588152871c | |||
| f3fbdbf5f2 | |||
| 7fb68d67de | |||
| 3132658396 | |||
| 6e149a6a73 | |||
| 204e2827eb | |||
| adf3a8ab11 | |||
| 6a2d813506 | |||
| 4103b3153a | |||
| c428bb5a87 | |||
| 632d554245 | |||
| efd8842540 | |||
| fd1626fbcd | |||
| 062df3171b | |||
| 37a268fe7b | |||
| e9e7d84e60 | |||
| 1c0c9ad53e | |||
| faeb991b75 | |||
| 7377613213 | |||
| d8383d3b73 | |||
| c3bfab2056 | |||
| 0261c601ff | |||
| d3dc130f11 | |||
| ed1e5a027f | |||
| df2961ce8e | |||
| e179fc1a07 | |||
| 60d1dfc5af | |||
| 3e2e37908b | |||
| 804a731181 | |||
| 99f6192f37 | |||
| 0a8272685d | |||
| c6ed27966c | |||
| 6a8f6c7988 | |||
| 6bfc8999f7 | |||
| c70088e7bc | |||
| 190eae2c3f | |||
| 1d6caa2291 | |||
| 9b739c852d | |||
| 9d9a2e9681 | |||
| ed759da14f | |||
| 158175f545 | |||
| ed9148ed96 | |||
| 0a83b59af8 | |||
| a67394a9a2 | |||
| a6f0d01558 | |||
| 217a14d03d | |||
| 4424f83b8b | |||
| 626b0dc8a0 | |||
| 1f7fdb89d7 | |||
| ee748d56c4 | |||
| 02e6462ea1 | |||
| 2633c8be0a | |||
| 09c3e395de | |||
| 8e5bdd230d | |||
| dd0fe31cb7 | |||
| 442c421d05 | |||
| 85042a7f45 | |||
| 6a1992f605 | |||
| aaf4084517 | |||
| 34dbc1839d | |||
| db6a9a502f | |||
| e6586ceab2 | |||
| ed6d64c7f9 | |||
| 43e8aea6c8 | |||
| abf432f2a9 | |||
| b3672cf5f5 | |||
| fa434794c3 | |||
| 9be74da098 | |||
| 29913cea61 | |||
| 881bc9e7a3 | |||
| e941647750 | |||
| 827557b667 | |||
| a9fd62d249 | |||
| 27ff5697af | |||
| 07d05f9490 | |||
| 39adeb72de | |||
| bc666dc807 | |||
| 89cc27a8da | |||
| 718a001a8a | |||
| f038679d76 | |||
| a1433efbf8 | |||
| 79ead32e79 | |||
| c934aa8e9a | |||
| 0d87e958d0 | |||
| e4556ad90e | |||
| da89f9287c | |||
| 665c290a90 | |||
| 10b0608d31 | |||
| ca2cf40d6a | |||
| 415c601d2a | |||
| 0c1f41cd7c | |||
| 0cdad97d00 | |||
| b3f63f4f76 | |||
| 87ea95e975 | |||
| 4d9a062b8c | |||
| c782324065 | |||
| 5bb0df2ddf |
@@ -0,0 +1,9 @@
|
||||
# Do not edit this file!
|
||||
# It is needed to track the repo template version, and editing may break things.
|
||||
# This file will be overwritten by copier on template updates.
|
||||
|
||||
_commit: v1.2.8
|
||||
_src_path: https://github.com/bec-project/plugin_copier_template.git
|
||||
make_commit: false
|
||||
project_name: debye_bec
|
||||
widget_plugins_input: []
|
||||
@@ -0,0 +1,102 @@
|
||||
name: CI for debye_bec
|
||||
on:
|
||||
push:
|
||||
pull_request:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
BEC_WIDGETS_BRANCH:
|
||||
description: "Branch of BEC Widgets to install"
|
||||
required: false
|
||||
type: string
|
||||
default: "main"
|
||||
BEC_CORE_BRANCH:
|
||||
description: "Branch of BEC Core to install"
|
||||
required: false
|
||||
type: string
|
||||
default: "main"
|
||||
OPHYD_DEVICES_BRANCH:
|
||||
description: "Branch of Ophyd Devices to install"
|
||||
required: false
|
||||
type: string
|
||||
default: "main"
|
||||
BEC_PLUGIN_REPO_BRANCH:
|
||||
description: "Branch of the BEC Plugin Repository to install"
|
||||
required: false
|
||||
type: string
|
||||
default: "main"
|
||||
PYTHON_VERSION:
|
||||
description: "Python version to use"
|
||||
required: false
|
||||
type: string
|
||||
default: "3.12"
|
||||
|
||||
permissions:
|
||||
pull-requests: write
|
||||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
QTWEBENGINE_DISABLE_SANDBOX: 1
|
||||
QT_QPA_PLATFORM: "offscreen"
|
||||
|
||||
steps:
|
||||
- name: Setup Python
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: "${{ inputs.PYTHON_VERSION || '3.12' }}"
|
||||
|
||||
- name: Checkout BEC Plugin Repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
repository: bec/debye_bec
|
||||
ref: "${{ inputs.BEC_PLUGIN_REPO_BRANCH || github.head_ref || github.sha }}"
|
||||
path: ./debye_bec
|
||||
|
||||
- name: Lint for merge conflicts from template updates
|
||||
shell: bash
|
||||
# Find all Copier conflicts except this line
|
||||
run: '! grep -r "<<<<<<< before updating" | grep -v "grep -r \"<<<<<<< before updating"'
|
||||
|
||||
- name: Checkout BEC Core
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
repository: bec/bec
|
||||
ref: "${{ inputs.BEC_CORE_BRANCH || 'main' }}"
|
||||
path: ./bec
|
||||
|
||||
- name: Checkout Ophyd Devices
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
repository: bec/ophyd_devices
|
||||
ref: "${{ inputs.OPHYD_DEVICES_BRANCH || 'main' }}"
|
||||
path: ./ophyd_devices
|
||||
|
||||
- name: Checkout BEC Widgets
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
repository: bec/bec_widgets
|
||||
ref: "${{ inputs.BEC_WIDGETS_BRANCH || 'main' }}"
|
||||
path: ./bec_widgets
|
||||
|
||||
- name: Install dependencies
|
||||
shell: bash
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y libgl1 libegl1 x11-utils libxkbcommon-x11-0 libdbus-1-3 xvfb
|
||||
sudo apt-get -y install libnss3 libxdamage1 libasound2t64 libatomic1 libxcursor1
|
||||
|
||||
- name: Install Python dependencies
|
||||
shell: bash
|
||||
run: |
|
||||
pip install uv
|
||||
uv pip install --system -e ./ophyd_devices
|
||||
uv pip install --system -e ./bec/bec_lib[dev]
|
||||
uv pip install --system -e ./bec/bec_ipython_client
|
||||
uv pip install --system -e ./bec/bec_server[dev]
|
||||
uv pip install --system -e ./bec_widgets[dev,pyside6]
|
||||
uv pip install --system -e ./debye_bec
|
||||
|
||||
- name: Run Pytest with Coverage
|
||||
id: coverage
|
||||
run: pytest --random-order --cov=./debye_bec --cov-config=./debye_bec/pyproject.toml --cov-branch --cov-report=xml --no-cov-on-fail ./debye_bec/tests/ || test $? -eq 5
|
||||
@@ -0,0 +1,62 @@
|
||||
name: Create template upgrade PR for debye_bec
|
||||
on:
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
pull-requests: write
|
||||
|
||||
jobs:
|
||||
create_update_branch_and_pr:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: write
|
||||
pull-requests: write
|
||||
|
||||
steps:
|
||||
- name: Setup Python
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: '3.12'
|
||||
|
||||
- name: Install tools
|
||||
run: |
|
||||
pip install copier PySide6
|
||||
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Perform update
|
||||
run: |
|
||||
git config --global user.email "bec_ci_staging@psi.ch"
|
||||
git config --global user.name "BEC automated CI"
|
||||
|
||||
branch="chore/update-template-$(python -m uuid)"
|
||||
echo "switching to branch $branch"
|
||||
git checkout -b $branch
|
||||
|
||||
echo "Running copier update..."
|
||||
output="$(copier update --trust --defaults --conflict inline 2>&1)"
|
||||
echo "$output"
|
||||
msg="$(printf '%s\n' "$output" | head -n 1)"
|
||||
|
||||
if ! grep -q "make_commit: true" .copier-answers.yml ; then
|
||||
echo "Autocommit not made, committing..."
|
||||
git add -A
|
||||
git commit -a -m "$msg"
|
||||
fi
|
||||
|
||||
if diff-index --quiet HEAD ; then
|
||||
echo "No changes detected"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
git push -u origin $branch
|
||||
curl -X POST "https://gitea.psi.ch/api/v1/repos/${{ gitea.repository }}/pulls" \
|
||||
-H "Authorization: token ${{ secrets.CI_REPO_WRITE }}" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "{
|
||||
\"title\": \"Template: $(echo $msg)\",
|
||||
\"body\": \"This PR was created by Gitea Actions\",
|
||||
\"head\": \"$(echo $branch)\",
|
||||
\"base\": \"main\"
|
||||
}"
|
||||
@@ -1,7 +0,0 @@
|
||||
include:
|
||||
- project: bec/awi_utils
|
||||
file: /templates/plugin-repo-template.yml
|
||||
inputs:
|
||||
name: "debye"
|
||||
target: "debye_bec"
|
||||
branch: $CHILD_PIPELINE_BRANCH
|
||||
@@ -1,6 +1,7 @@
|
||||
|
||||
BSD 3-Clause License
|
||||
|
||||
Copyright (c) 2024, Paul Scherrer Institute
|
||||
Copyright (c) 2025, Paul Scherrer Institute
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
@@ -25,4 +26,4 @@ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
@@ -0,0 +1 @@
|
||||
# Add anything you don't want to check in to git, e.g. very large files
|
||||
@@ -0,0 +1,82 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import builtins
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from bec_lib import bec_logger
|
||||
from debye_bec.devices.absorber import STATUS as ABS_STATUS
|
||||
|
||||
logger = bec_logger.logger
|
||||
# import builtins to avoid linter errors
|
||||
dev = builtins.__dict__.get("dev")
|
||||
|
||||
class MoveToLabelError(Exception):
|
||||
"""Exception for the MoveToLabel function"""
|
||||
|
||||
def move_to_label():
|
||||
"""
|
||||
Function to move several motors to a specific position defined in the label dict.
|
||||
"""
|
||||
|
||||
label = get_device_conditions(label="digitalTwin")
|
||||
|
||||
# Get absorber status and close if open
|
||||
logger.info("Check Frontend Absorber Status")
|
||||
abs_was_open = dev.abs.status.get() == ABS_STATUS.OPEN
|
||||
if abs_was_open:
|
||||
logger.info(" Close Frontend Absorber")
|
||||
status = dev.abs.close()
|
||||
status.wait()
|
||||
|
||||
# Move Frontend Slits
|
||||
logger.info("Move Frontend Slits into position")
|
||||
devices = ["sldi_centerx", "sldi_centery", "sldi_gapx", "sldi_gapy"]
|
||||
matches = {key: label[key] for key in devices if key in label}
|
||||
statuses = []
|
||||
for device in matches.values():
|
||||
statuses.append(device['device'].move(device['value']))
|
||||
for status in statuses:
|
||||
status.wait(timeout=30)
|
||||
|
||||
# Move Collimating mirror
|
||||
logger.info("Move Collimating Mirror into position")
|
||||
if "cm_rotx" in label: # pitch
|
||||
logger.info(" Move pitch into position")
|
||||
surveyed_movement(
|
||||
axis=label['cm_rotx'],
|
||||
surveyed_axes= [
|
||||
{'device': dev.cm_rotz, 'abs_tol': 0.1},
|
||||
]
|
||||
)
|
||||
|
||||
# Restore absorber position
|
||||
logger.info("Restore Frontend Absorber Status")
|
||||
if abs_was_open:
|
||||
status = dev.abs.open()
|
||||
status.wait()
|
||||
|
||||
|
||||
def surveyed_movement(axis, surveyed_axes):
|
||||
"""
|
||||
Moves an axis while surverying a set of axes.
|
||||
|
||||
Args:
|
||||
axis (DeviceCondition): Device condition
|
||||
surveyed_axes (list): List of dicts (same format as DeviceCondition)
|
||||
|
||||
Raises:
|
||||
If during movement of axis, one of the surveyed axes moves out of tolerance.
|
||||
"""
|
||||
|
||||
for surv_ax in surveyed_axes:
|
||||
surv_ax['old_value'] = surv_ax['device'].read()
|
||||
status = axis['device'].move(axis['value'])
|
||||
while status.status == 'RUNNING':
|
||||
for surv_ax in surveyed_axes:
|
||||
if abs(surv_ax['device'].read() - surv_ax['old_value']) > surv_ax['abs_tol']:
|
||||
axis['device'].stop()
|
||||
raise MoveToLabelError(
|
||||
f"During movement of {axis['device'].name}, {surv_ax['device'].name} " +
|
||||
f"started to move unexpectedly (old pos: {surv_ax['old_value']}, " +
|
||||
f"current pos: {surv_ax['device'].read()})"
|
||||
)
|
||||
@@ -10,7 +10,7 @@ While command-line arguments have to be set in the pre-startup script, the
|
||||
post-startup script can be used to load beamline specific information and
|
||||
to setup the prompts.
|
||||
|
||||
from bec_lib import bec_logger
|
||||
from bec_lib.logger import bec_logger
|
||||
|
||||
logger = bec_logger.logger
|
||||
|
||||
|
||||
@@ -3,8 +3,12 @@ Pre-startup script for BEC client. This script is executed before the BEC client
|
||||
is started. It can be used to add additional command line arguments.
|
||||
"""
|
||||
|
||||
import os
|
||||
|
||||
from bec_lib.service_config import ServiceConfig
|
||||
|
||||
import debye_bec
|
||||
|
||||
|
||||
def extend_command_line_args(parser):
|
||||
"""
|
||||
@@ -18,6 +22,11 @@ def extend_command_line_args(parser):
|
||||
|
||||
def get_config() -> ServiceConfig:
|
||||
"""
|
||||
Create and return the service configuration.
|
||||
Create and return the ServiceConfig for the plugin repository
|
||||
"""
|
||||
return ServiceConfig(redis={"host": "x01da-bec-001", "port": 6379})
|
||||
deployment_path = os.path.dirname(os.path.dirname(os.path.dirname(debye_bec.__file__)))
|
||||
files = os.listdir(deployment_path)
|
||||
if "bec_config.yaml" in files:
|
||||
return ServiceConfig(config_path=os.path.join(deployment_path, "bec_config.yaml"))
|
||||
else:
|
||||
return ServiceConfig(redis={"host": "localhost", "port": 6379})
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
# This file was automatically generated by generate_cli.py
|
||||
# type: ignore
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from bec_lib.logger import bec_logger
|
||||
|
||||
from bec_widgets.cli.rpc.rpc_base import RPCBase, rpc_call, rpc_timeout
|
||||
|
||||
logger = bec_logger.logger
|
||||
|
||||
# pylint: skip-file
|
||||
|
||||
|
||||
_Widgets = {
|
||||
"DigitalTwin": "DigitalTwin",
|
||||
}
|
||||
|
||||
|
||||
class DigitalTwin(RPCBase):
|
||||
"""Main widget of Digital Twin"""
|
||||
|
||||
_IMPORT_MODULE = "debye_bec.bec_widgets.widgets.digital_twin.digital_twin"
|
||||
|
||||
@rpc_call
|
||||
def remove(self):
|
||||
"""
|
||||
Cleanup the BECConnector
|
||||
"""
|
||||
|
||||
@rpc_call
|
||||
def attach(self):
|
||||
"""
|
||||
None
|
||||
"""
|
||||
|
||||
@rpc_call
|
||||
def detach(self):
|
||||
"""
|
||||
Detach the widget from its parent dock widget (if widget is in the dock), making it a floating widget.
|
||||
"""
|
||||
@@ -0,0 +1,13 @@
|
||||
# This file was automatically generated by generate_cli.py
|
||||
# type: ignore
|
||||
from __future__ import annotations
|
||||
|
||||
# pylint: skip-file
|
||||
|
||||
designer_plugins = {
|
||||
"DigitalTwin": ("debye_bec.bec_widgets.widgets.digital_twin.digital_twin", "DigitalTwin"),
|
||||
}
|
||||
|
||||
widget_icons = {
|
||||
"DigitalTwin": "lightbulb",
|
||||
}
|
||||
@@ -0,0 +1,242 @@
|
||||
import os
|
||||
import numpy as np
|
||||
from bec_lib import bec_logger
|
||||
|
||||
os.environ["USE_XRT"] = "False"
|
||||
import debye_bec.bec_widgets.widgets.x01da_parameters as bl
|
||||
|
||||
logger = bec_logger.logger
|
||||
|
||||
def calc_positions(cfg):
|
||||
|
||||
pos = {}
|
||||
|
||||
## FE slits
|
||||
trxr = -np.arctan(cfg['h_acc'])*bl.feSlits.center1[1]
|
||||
trxw = (np.arctan(cfg['h_acc'])*bl.feSlits.center1[1])/bl.feSlits.center1[1]*bl.feSlits.center2[1]
|
||||
tryb = -np.arctan(cfg['v_acc'])*bl.feSlits.center1[1]
|
||||
tryt = (np.arctan(cfg['v_acc'])*bl.feSlits.center1[1])/bl.feSlits.center1[1]*bl.feSlits.center2[1]
|
||||
|
||||
# trxw_proj = trxw/bl.feSlits.center2[1]*bl.feSlits.center1[1]
|
||||
# tryt_proj = tryt/bl.feSlits.center2[1]*bl.feSlits.center1[1]
|
||||
|
||||
# xcen = (trxr + trxw) / 2
|
||||
# ycen = (tryb + tryt) / 2
|
||||
xgap = trxw - trxr
|
||||
ygap = tryt - tryb
|
||||
|
||||
pos['sldi_gapx'] = {'value': xgap}
|
||||
pos['sldi_gapy'] = {'value': ygap}
|
||||
|
||||
## Collimating Mirror
|
||||
obj_dist = bl.cm.center[1] # object distance
|
||||
beam_vs = 2 * obj_dist * np.tan(cfg['v_acc']) # vertical size of beam after CM
|
||||
|
||||
# TRX
|
||||
try:
|
||||
index = bl.cm.surface.index(cfg['cm_stripe'])
|
||||
except:
|
||||
raise ValueError(f"Requested stripe {cfg['cm_stripe']} not found in parameters!")
|
||||
cm_trx = -(bl.cm.limOptX[0][index] + bl.cm.limOptX[1][index]) / 2
|
||||
pos['cm_trx'] = {'value': cm_trx}
|
||||
|
||||
# TRY
|
||||
height = obj_dist * np.tan(cfg['v_acc'])**2 * 1 / np.tan(cfg['cm_pitch'])
|
||||
pos['cm_try'] = {'value': height}
|
||||
|
||||
# Pitch
|
||||
pos['cm_rotx'] = {'value': -cfg["cm_pitch"]*1e3} # invert and convert to mrad (same as EGU of rotx axis)
|
||||
|
||||
# Bending Radius
|
||||
radius = 2. * obj_dist / np.sin(cfg['cm_pitch']) # Elements of modern X-ray Physics, page 108 ff.
|
||||
pos['cm_bnd_radius'] = {'value': radius * 1e-6} # Convert to km
|
||||
|
||||
## Monochromator
|
||||
# Bragg Angle
|
||||
# if cfg['mo1_mode'] == 'Monochromatic':
|
||||
# # Add 2x CM pitch to the bragg angle
|
||||
# bragg = ((2 * cfg['cm_pitch']) + cfg['mo1_bragg']) / np.pi * 180
|
||||
# elif cfg['mo1_mode'] == 'Pinkbeam':
|
||||
# # Align xtal surfaces parallel to beam
|
||||
# bragg = (2 * cfg['cm_pitch']) / np.pi * 180
|
||||
# else:
|
||||
# raise Exception('Monochromator mode not supported')
|
||||
if cfg['mo1_mode'] == 'Monochromatic':
|
||||
# Add 2x CM pitch to the bragg angle
|
||||
bragg = cfg['mo1_bragg']
|
||||
elif cfg['mo1_mode'] == 'Pinkbeam':
|
||||
# Align xtal surfaces parallel to beam
|
||||
bragg = 0
|
||||
else:
|
||||
raise Exception('Monochromator mode not supported')
|
||||
pos['mo1_bragg_angle'] = {'value': bragg/np.pi*180} # Bragg angle in deg
|
||||
|
||||
# TRY, Height
|
||||
l = bl.mo1.xtalGap[0]/np.sin(cfg['mo1_bragg'])
|
||||
yhor = l*np.cos(2.*(cfg['mo1_bragg']+cfg['cm_pitch']))
|
||||
yver = yhor*np.tan(2.*cfg['cm_pitch'])
|
||||
|
||||
if cfg['mo1_mode'] == 'Monochromatic':
|
||||
beamOffsetCCM = l*np.sin(2.*(cfg['mo1_bragg']+cfg['cm_pitch']))-yver # Resultat ist korrekt!
|
||||
elif cfg['mo1_mode'] == 'Pinkbeam':
|
||||
beamOffsetCCM = 0
|
||||
else:
|
||||
raise Exception('Monochromator mode not supported')
|
||||
|
||||
def csc(a):
|
||||
return 1/np.sin(a)
|
||||
|
||||
def cot(a):
|
||||
return 1/np.tan(a)
|
||||
|
||||
# calculate height of center of first crystal surface
|
||||
f = bl.mo1.rotOffset # rotation offset, mm
|
||||
# logger.info(f'f = {f}')
|
||||
d = bl.mo1.heightOffset # xtal height offset, mm
|
||||
# logger.info(f'd = {d}')
|
||||
c = d*csc(cfg['mo1_bragg'])-f*cot(cfg['mo1_bragg'])
|
||||
# logger.info(f'c = {c}')
|
||||
|
||||
# Calculate height of center of rotation
|
||||
b = np.sqrt(d**2*csc(cfg['mo1_bragg'])**2-2*d*f*cot(cfg['mo1_bragg'])*csc(cfg['mo1_bragg'])+f**2*cot(cfg['mo1_bragg'])**2+f**2)
|
||||
# logger.info(f'b = {b}')
|
||||
h = np.cos(np.pi/2-np.arctan(f/c)-cfg['mo1_bragg']-2*cfg['cm_pitch'])*b
|
||||
# logger.info(f'h = {h}')
|
||||
h2 = ((bl.mo1.center[1] - bl.cm.center[1])-np.sqrt(b**2-h**2))*np.tan(2*cfg['cm_pitch'])
|
||||
# logger.info(f'mo1 = {bl.mo1.center[1]}')
|
||||
# logger.info(f'cm = {bl.cm.center[1]}')
|
||||
# logger.info(f'pitch = {cfg["cm_pitch"]}')
|
||||
# logger.info(f'h2 = {h2}')
|
||||
#TODO Mono height not exactly the same as in raytracing
|
||||
heightCCM1real = h + h2 # per design, the height should not change if the pitch of the CM is not changed!
|
||||
# heightCCM1real = heightCCM1real - 30 # Zero position of stage is at 1430 mm from ground.
|
||||
if cfg['mo1_mode'] == 'Monochromatic':
|
||||
pass
|
||||
elif cfg['mo1_mode'] == 'Pinkbeam':
|
||||
heightCCM1real = heightCCM1real - 13 # Move down to let beam pass between both crystal without touching copper cooler
|
||||
else:
|
||||
raise Exception('Monochromator mode not supported')
|
||||
pos['mo1_try'] = {'value': heightCCM1real}
|
||||
|
||||
# TRX, Crystal selection
|
||||
if cfg['mo1_mode'] == 'Monochromatic':
|
||||
try:
|
||||
xtal = cfg['mo1_xtal'].translate(str.maketrans('', '', '()')) # Remove brackets from xtal name to conform with parameters
|
||||
index = bl.mo1.xtal.index(xtal)
|
||||
except:
|
||||
raise ValueError(f"Requested xtal {xtal} not found in parameters!")
|
||||
pos['mo1_trx'] = {'value': bl.mo1.xtalOffsetX[index]}
|
||||
else:
|
||||
pos['mo1_trx'] = {'value': 0}
|
||||
|
||||
|
||||
#TODO move to mono, calc for beam Z-movement between crystal surfaces
|
||||
diag = bl.mo1.xtalGap[0] / np.sin(cfg['mo1_bragg']) # Calculations for Mono
|
||||
dz = diag * np.cos(2 * (cfg['cm_pitch'] + cfg['mo1_bragg']))
|
||||
|
||||
## Slits 1
|
||||
d = bl.opSlits1.center[1] - bl.cm.center[1] - dz
|
||||
sl1_beam_height = d * np.tan(2 * cfg['cm_pitch']) + beamOffsetCCM
|
||||
pos['sl1_centery'] = {'value': sl1_beam_height}
|
||||
pos['sl1_gapy'] = {'value': beam_vs + 1} # Add 0.5 mm space on both sides of the beam
|
||||
|
||||
## Beam Monitor 1
|
||||
d = bl.opBM1.center[1] - bl.cm.center[1] - dz
|
||||
# logger.info(f'distance: {d}')
|
||||
# logger.info(f'cm pitch: {cfg["cm_pitch"]}')
|
||||
# logger.info(f'mono offset: {beamOffsetCCM}')
|
||||
bm1_beam_height = d * np.tan(2 * cfg['cm_pitch']) + beamOffsetCCM
|
||||
pos['bm1_try'] = {'value': bm1_beam_height}
|
||||
|
||||
## Focusing Mirror
|
||||
p = bl.fm.center[1]
|
||||
q = cfg['smpl'] - bl.fm.center[1]
|
||||
f = (p*q)/(p+q) # focal length
|
||||
|
||||
# Bender radius
|
||||
if cfg['fm_qy'] is None:
|
||||
radius = 2 * q / np.sin(cfg['fm_rotx']) # ideal bending radius for focused beam
|
||||
else:
|
||||
radius = 2 * cfg['fm_qy'] / np.sin(cfg['fm_rotx']) # ideal bending radius for unfocused beam
|
||||
pos['fm_bnd_radius'] = {'value': radius * 1e-6} # Convert to km
|
||||
|
||||
# Pitch
|
||||
d = bl.fm.center[1] - bl.cm.center[1] - dz
|
||||
fm_rotx = 2 * cfg['cm_pitch'] - cfg['fm_rotx'] # calculate pitch in absolute values (according to horizontal plane)
|
||||
pos['fm_rotx'] = {'value': -fm_rotx * 1e3} # invert and convert to mrad (same as EGU of rotx axis)
|
||||
|
||||
if cfg['fm_stripe'] in ('Rh (toroid)', 'Pt (toroid)'):
|
||||
|
||||
# TRY
|
||||
if cfg['fm_stripe'] in 'Rh (toroid)':
|
||||
r = bl.fm.r[0]
|
||||
h_cyl = bl.fm.hToroid[0]
|
||||
else: # PT toroid
|
||||
r = bl.fm.r[1]
|
||||
h_cyl = bl.fm.hToroid[1]
|
||||
widthBeam = 2 * bl.fm.center[1] * np.tan(cfg['h_acc'] * 1e-3)
|
||||
alpha = np.arccos(1 - widthBeam**2 / (2 * r**2))
|
||||
h = r - (r * np.cos(alpha / 2))
|
||||
fm_beam_height = (d * np.tan(2 * cfg['cm_pitch']) + beamOffsetCCM) * cfg['fm_gain_height']
|
||||
fm_height = (d * np.tan(2 * cfg['cm_pitch']) + beamOffsetCCM - h_cyl + h / 2) * cfg['fm_gain_height']
|
||||
pos['fm_try'] = {'value': fm_height}
|
||||
|
||||
# TRX
|
||||
if cfg['fm_stripe'] in 'Rh (toroid)':
|
||||
x_cyl = - bl.fm.xToroid[0]
|
||||
else:
|
||||
x_cyl = - bl.fm.xToroid[1]
|
||||
pos['fm_trx'] = {'value': x_cyl}
|
||||
|
||||
elif cfg['fm_stripe'] in ('Rh (flat)', 'Pt (flat)'):
|
||||
|
||||
# TRY
|
||||
fm_height = (d * np.tan(2 * cfg['cm_pitch']) + beamOffsetCCM) * cfg['fm_gain_height']
|
||||
fm_beam_height = fm_height
|
||||
pos['fm_try'] = {'value': fm_height}
|
||||
|
||||
# TRX
|
||||
if cfg['fm_stripe'] in 'Rh (flat)':
|
||||
x_flat = - bl.fm.xFlat[0]
|
||||
else:
|
||||
x_flat = - bl.fm.xFlat[1]
|
||||
pos['fm_trx'] = {'value': x_flat}
|
||||
|
||||
else:
|
||||
raise Exception('FM Stripe selection not valid')
|
||||
|
||||
pos['fm_roty'] = {'value': 0}
|
||||
pos['fm_rotz'] = {'value': 0}
|
||||
|
||||
## Slits 2
|
||||
d = bl.opSlits2.center[1] - bl.fm.center[1]
|
||||
sl2_beam_height = fm_beam_height - d * np.tan(-(2 * cfg['cm_pitch'] - 2 * cfg['fm_rotx']))
|
||||
pos['sl2_centery'] = {'value': sl2_beam_height}
|
||||
pos['sl2_gapy'] = {'value': beam_vs + 1} # Add 0.5 mm space on both sides of the beam
|
||||
|
||||
## Beam Monitor 2
|
||||
d = bl.opBM2.center[1] - bl.fm.center[1]
|
||||
bm2_beam_height = fm_beam_height - d * np.tan(-(2 * cfg['cm_pitch'] - 2 * cfg['fm_rotx']))
|
||||
pos['bm2_try'] = {'value': bm2_beam_height}
|
||||
|
||||
## Optical Table
|
||||
|
||||
# TRY
|
||||
d = bl.ehWindow.center[1] - bl.fm.center[1]
|
||||
ot_height = fm_beam_height - d * np.tan(-(2 * cfg['cm_pitch'] - 2 * cfg['fm_rotx']))
|
||||
# logger.info(fm_height)
|
||||
# logger.info(d * np.tan((2 * cfg['cm_pitch'] - 2 * cfg['fm_rotx'])))
|
||||
pos['ot_try'] = {'value': ot_height}
|
||||
|
||||
# Pitch
|
||||
ot_pitch = - (2 * cfg['cm_pitch'] - 2 * cfg['fm_rotx'])
|
||||
pos['ot_rotx'] = {'value': ot_pitch * 1e3}
|
||||
|
||||
# TRZ ES1
|
||||
ot_es1_trz = cfg['smpl']
|
||||
pos['ot_es1_trz'] = {'value': ot_es1_trz}
|
||||
|
||||
# ES0 exit window
|
||||
pos['es0wi_try'] = {'value': 5} # At 5mm, the middle of the window is 500 mm from the table (neutral position)
|
||||
|
||||
return pos
|
||||
@@ -0,0 +1,42 @@
|
||||
import numpy as np
|
||||
import debye_bec.bec_widgets.widgets.x01da_parameters as bl
|
||||
|
||||
def calc_sideview(cfg):
|
||||
|
||||
# Calculate height of beam after CM
|
||||
height = 2 * bl.cm.center[1] * np.tan(cfg['v_acc'])
|
||||
|
||||
# beam height (Y=height, Z=along beam)
|
||||
beam = {}
|
||||
beam['x'] = []
|
||||
beam['y'] = []
|
||||
beam['x'].append(0) # Source
|
||||
beam['y'].append(bl.sourceHeight)
|
||||
beam['x'].append(bl.cm.center[1]) # CM
|
||||
beam['y'].append(bl.sourceHeight)
|
||||
if cfg['mo1_mode'] in 'Monochromatic':
|
||||
diag = bl.mo1.xtalGap[0]/np.sin(cfg['mo1_bragg']) # Calculations for Mono
|
||||
dy = diag*np.sin(2*(cfg['cm_pitch']+cfg['mo1_bragg']))
|
||||
dz = diag*np.cos(2*(cfg['cm_pitch']+cfg['mo1_bragg']))
|
||||
beam['x'].append(bl.mo1.center[1]-dz/2) # Mono 1.1
|
||||
beam['y'].append(bl.sourceHeight+np.tan(2*cfg['cm_pitch'])*(bl.mo1.center[1]-dz/2-bl.cm.center[1]))
|
||||
beam['x'].append(bl.mo1.center[1]+dz/2) # Mono 1.2
|
||||
beam['y'].append(bl.sourceHeight+np.tan(2*cfg['cm_pitch'])*(bl.mo1.center[1]-dz/2-bl.cm.center[1])+dy)
|
||||
beam['x'].append(bl.fm.center[1]) # FM
|
||||
beam['y'].append(bl.sourceHeight+np.tan(2*cfg['cm_pitch'])*(bl.fm.center[1]-bl.cm.center[1]-dz)+dy)
|
||||
beam['x'].append(cfg['smpl']) # Experiment
|
||||
beam['y'].append(bl.sourceHeight+np.tan(2*cfg['cm_pitch'])*(bl.fm.center[1]-bl.cm.center[1]-dz)+dy+np.tan(2*(cfg['cm_pitch']-cfg['fm_rotx']))*(cfg['smpl']-bl.fm.center[1]))
|
||||
elif cfg['mo1_mode'] == 'Pinkbeam':
|
||||
beam['x'].append(bl.fm.center[1]) # FM
|
||||
beam['y'].append(bl.sourceHeight+np.tan(2*cfg['cm_pitch'])*(bl.fm.center[1]-bl.cm.center[1]))
|
||||
beam['x'].append(cfg['smpl']) # Experiment
|
||||
beam['y'].append(bl.sourceHeight+np.tan(2*cfg['cm_pitch'])*(bl.fm.center[1]-bl.cm.center[1])+np.tan(2*(cfg['cm_pitch']-cfg['fm_rotx']))*(cfg['smpl']-bl.fm.center[1]))
|
||||
|
||||
dy_fm_ex = beam['y'][-1] - beam['y'][-2]
|
||||
dz_fm_ex = beam['x'][-1] - beam['x'][-2]
|
||||
dz_fm_win = bl.ehWindow.center[1] - beam['x'][-2]
|
||||
h_at_win = beam['y'][-2] + dy_fm_ex / dz_fm_ex * dz_fm_win
|
||||
|
||||
beam['heightWindow'] = h_at_win
|
||||
|
||||
return beam
|
||||
@@ -0,0 +1,131 @@
|
||||
import os
|
||||
import re
|
||||
import numpy as np
|
||||
from bec_lib import bec_logger
|
||||
|
||||
logger = bec_logger.logger
|
||||
|
||||
os.environ["USE_XRT"] = "False"
|
||||
import debye_bec.bec_widgets.widgets.x01da_parameters as bl
|
||||
|
||||
def calc_surfaces(cfg):
|
||||
|
||||
out = {
|
||||
'cm': {'x': [], 'y': []},
|
||||
'mo1_1': {'x': [], 'y': []},
|
||||
'mo1_2': {'x': [], 'y': []},
|
||||
'fm': {'x': [], 'y': []},
|
||||
}
|
||||
|
||||
# Collimating mirror
|
||||
l = 2 * bl.cm.center[1] * np.tan(cfg['v_acc'])/np.sin(cfg['cm_pitch'])
|
||||
|
||||
w1 = 2 * (bl.cm.center[1]-l/2) * np.tan(cfg['h_acc'])
|
||||
w2 = 2 * (bl.cm.center[1]+l/2) * np.tan(cfg['h_acc'])
|
||||
|
||||
index = bl.cm.surface.index(cfg['cm_stripe'])
|
||||
cen = (bl.cm.limOptX[0][index] + bl.cm.limOptX[1][index]) / 2
|
||||
|
||||
if cfg['cm_trx'] is not None:
|
||||
cen = cfg['cm_trx']
|
||||
|
||||
out['cm']['x'] = [cen-w1/2, cen-w2/2, cen+w2/2, cen+w1/2]
|
||||
out['cm']['y'] = [-l/2, l/2, l/2, -l/2]
|
||||
|
||||
|
||||
# Monochromator
|
||||
# calculate height of center of first crystal surface
|
||||
c = bl.mo1.heightOffset*1/np.sin(cfg['mo1_bragg'])-bl.mo1.rotOffset*1/np.tan(cfg['mo1_bragg'])
|
||||
e = bl.mo1.xtalGap[0]/np.tan(cfg['mo1_bragg'])-c
|
||||
|
||||
xtal = cfg['mo1_xtal'].translate(str.maketrans('', '', '()')) # Remove brackets from xtal name to conform with parameters
|
||||
index = bl.mo1.xtal.index(xtal)
|
||||
|
||||
xtalPos = bl.mo1.xtalOffsetX[index]
|
||||
xtalLength1 = bl.mo1.xtalLength1[index]
|
||||
xtalLength2 = bl.mo1.xtalLength2[index]
|
||||
|
||||
widthBeam = 2 * bl.mo1.center[1] * np.tan(cfg['h_acc'])
|
||||
|
||||
heightBeam = 2 * bl.cm.center[1] * np.tan(cfg['v_acc'])
|
||||
w = heightBeam / np.sin(cfg['mo1_bragg'])
|
||||
|
||||
if cfg['mo1_mode'] in 'Monochromatic':
|
||||
out['mo1_1']['x'] = [xtalPos-widthBeam/2, xtalPos+widthBeam/2, xtalPos+widthBeam/2, xtalPos-widthBeam/2]
|
||||
out['mo1_1']['y'] = [xtalLength1/2-c-w/2, xtalLength1/2-c-w/2, xtalLength1/2-c+w/2, xtalLength1/2-c+w/2]
|
||||
out['mo1_2']['x'] = [xtalPos-widthBeam/2, xtalPos+widthBeam/2, xtalPos+widthBeam/2, xtalPos-widthBeam/2]
|
||||
out['mo1_2']['y'] = [-xtalLength2/2+e-w/2, -xtalLength2/2+e-w/2, -xtalLength2/2+e+w/2, -xtalLength2/2+e+w/2]
|
||||
else: # Pinkbeam
|
||||
out['mo1_1']['x'] = []
|
||||
out['mo1_1']['y'] = []
|
||||
out['mo1_2']['x'] = []
|
||||
out['mo1_2']['y'] = []
|
||||
|
||||
# Focusing mirror
|
||||
if cfg['fm_stripe'] in ('Rh (toroid)', 'Pt (toroid)'):
|
||||
surface = bl.fm.surfaceToroid
|
||||
stripe = re.sub(r'\s*\(.*?\)', '', cfg['fm_stripe']).strip()
|
||||
index = surface.index(stripe)
|
||||
off = (bl.fm.limOptXToroid[0][index] + bl.fm.limOptXToroid[1][index]) / 2
|
||||
r = bl.fm.r[index]
|
||||
else:
|
||||
surface = bl.fm.surfaceFlat
|
||||
stripe = re.sub(r'\s*\(.*?\)', '', cfg['fm_stripe']).strip()
|
||||
index = surface.index(stripe)
|
||||
off = (bl.fm.limOptXFlat[0][index] + bl.fm.limOptXFlat[1][index]) / 2
|
||||
r = bl.fm.r[index]
|
||||
if cfg['fm_trx'] is not None:
|
||||
off = cfg['fm_trx']
|
||||
|
||||
widthBeam = 2 * bl.fm.center[1] * np.tan(cfg['h_acc'])
|
||||
|
||||
if cfg['fm_stripe'] in ('Rh (toroid)', 'Pt (toroid)'):
|
||||
|
||||
l = heightBeam/np.sin(cfg['fm_rotx'])
|
||||
alpha = np.arccos(1-widthBeam**2/(2*r**2))
|
||||
h = r-(r*np.cos(alpha/2))
|
||||
z = h/np.tan(cfg['fm_rotx'])
|
||||
|
||||
x = [off-widthBeam/2, off-widthBeam/2]
|
||||
y = [l/2-z/2, -l/2-z/2]
|
||||
|
||||
# logger.info(f'stripe: {cfg["fm_stripe"]}')
|
||||
# logger.info(f'fm_rotx: {cfg["fm_rotx"]}')
|
||||
# logger.info(f'h: {h}')
|
||||
# logger.info(f'z: {z}')
|
||||
# logger.info(f'r: {r}')
|
||||
|
||||
res = 20
|
||||
xElipse = np.linspace(0, np.pi, res)
|
||||
yElipse = np.linspace(0, np.pi, res)
|
||||
xElipse = [-widthBeam/2*np.cos(i)+off for i in xElipse]
|
||||
yElipse = [widthBeam*np.sin(i)*z/widthBeam-l/2-z/2 for i in yElipse]
|
||||
|
||||
x.extend(xElipse)
|
||||
y.extend(yElipse)
|
||||
|
||||
x.extend([off+widthBeam/2, off+widthBeam/2])
|
||||
y.extend([-l/2-z/2, l/2-z/2])
|
||||
|
||||
res = 50
|
||||
xElipse = np.linspace(np.pi, 0, res)
|
||||
yElipse = np.linspace(np.pi, 0, res)
|
||||
xElipse = [-widthBeam/2*np.cos(i)+off for i in xElipse]
|
||||
yElipse = [widthBeam*np.sin(i)*z/widthBeam+l/2-z/2 for i in yElipse]
|
||||
|
||||
x.extend(xElipse)
|
||||
y.extend(yElipse)
|
||||
|
||||
out['fm']['x'] = x
|
||||
out['fm']['y'] = y
|
||||
|
||||
else: # flat surface, no toroid
|
||||
l = heightBeam/np.sin(cfg['fm_rotx'])
|
||||
|
||||
w1 = 2 * (bl.fm.center[1]-l/2) * np.tan(cfg['h_acc'])
|
||||
w2 = 2 * (bl.fm.center[1]+l/2) * np.tan(cfg['h_acc'])
|
||||
|
||||
out['fm']['x'] = [off-w1/2, off+w1/2, off+w2/2, off-w2/2]
|
||||
out['fm']['y'] = [-l/2, -l/2, l/2, l/2]
|
||||
|
||||
return out
|
||||
@@ -0,0 +1,206 @@
|
||||
import re
|
||||
import numpy as np
|
||||
from scipy.interpolate import UnivariateSpline
|
||||
from xrt.backends.raycing.physconsts import CHeVcm, AVOGADRO
|
||||
from bec_lib import bec_logger
|
||||
|
||||
import debye_bec.bec_widgets.widgets.x01da_parameters as bl
|
||||
|
||||
logger = bec_logger.logger
|
||||
|
||||
def sldi_gap_to_acc(sldi_gapx, sldi_gapy):
|
||||
d1 = bl.feSlits.center1[1]
|
||||
d2 = bl.feSlits.center2[1]
|
||||
h_acc = np.tan(sldi_gapx / (d2 + d1))
|
||||
v_acc = np.tan(sldi_gapy / (d2 + d1))
|
||||
|
||||
# h_acc = np.tan(sldi_gapx / (2 * d1))
|
||||
# v_acc = np.tan(sldi_gapy / (2 * d1))
|
||||
return h_acc, v_acc
|
||||
|
||||
def cm_trx_to_stripe(cm_trx):
|
||||
cm_stripe = None
|
||||
for name, low, high in zip(bl.cm.surface, bl.cm.limOptX[0], bl.cm.limOptX[1]):
|
||||
if low <= cm_trx <= high:
|
||||
cm_stripe = name
|
||||
return cm_stripe
|
||||
|
||||
def fm_trx_to_stripe(fm_trx):
|
||||
fm_stripe = None
|
||||
for name, low, high in zip(bl.fm.surfaceFlat, bl.fm.limOptXFlat[1], bl.fm.limOptXFlat[0]):
|
||||
if low <= fm_trx <= high:
|
||||
fm_stripe = name + ' (flat)'
|
||||
for name, low, high in zip(bl.fm.surfaceToroid, bl.fm.limOptXToroid[1], bl.fm.limOptXToroid[0]):
|
||||
if low <= fm_trx <= high:
|
||||
fm_stripe = name + ' (toroid)'
|
||||
return fm_stripe
|
||||
|
||||
def mo1_energy_resolution(xtal, energy):
|
||||
index = bl.mo1.xtal.index(xtal)
|
||||
crystal = bl.mo1.material1[index]
|
||||
|
||||
dtheta = np.linspace(-30, 90, 601)
|
||||
theta = crystal.get_Bragg_angle(energy) + dtheta * 1e-6
|
||||
refl = np.abs(crystal.get_amplitude(energy, np.sin(theta))[0])**2 # single crystal
|
||||
|
||||
refl2 = refl**2 # DCM with parallel crystals
|
||||
|
||||
# FWHM of the DCM curve
|
||||
spline = UnivariateSpline(dtheta, refl2 - refl2.max()/2, s=0)
|
||||
r1, r2 = spline.roots()
|
||||
fwhm_rad = (r2 - r1) * 1e-6 # µrad → rad
|
||||
|
||||
# Energy resolution
|
||||
theta_B = crystal.get_Bragg_angle(energy)
|
||||
dE_over_E = fwhm_rad / np.tan(theta_B)
|
||||
dE = dE_over_E * energy
|
||||
|
||||
# logger.info(f"DCM FWHM : {r2-r1:.2f} µrad")
|
||||
# logger.info(f"ΔE/E : {dE_over_E:.2e}")
|
||||
# logger.info(f"ΔE : {dE:.3f} eV at {E} eV")
|
||||
|
||||
return dE
|
||||
|
||||
def cm_reflectivity(cm_stripe, cm_pitch, energy):
|
||||
index = bl.cm.surface.index(cm_stripe)
|
||||
rs, rp = bl.cm.material[index].get_amplitude(
|
||||
energy,
|
||||
np.sin(cm_pitch)
|
||||
)[0:2]
|
||||
refl = abs(rs)**2
|
||||
return refl
|
||||
|
||||
def fm_reflectivity(fm_stripe, fm_pitch, energy):
|
||||
if fm_stripe in ('Rh (toroid)', 'Pt (toroid)'):
|
||||
surface = bl.fm.surfaceToroid
|
||||
material = bl.fm.materialToroid
|
||||
stripe = re.sub(r'\s*\(.*?\)', '', fm_stripe).strip()
|
||||
index = surface.index(stripe)
|
||||
else:
|
||||
surface = bl.fm.surfaceFlat
|
||||
material = bl.fm.materialFlat
|
||||
stripe = re.sub(r'\s*\(.*?\)', '', fm_stripe).strip()
|
||||
index = surface.index(stripe)
|
||||
rs, rp = material[index].get_amplitude(
|
||||
energy,
|
||||
np.sin(fm_pitch)
|
||||
)[0:2]
|
||||
refl = abs(rs)**2
|
||||
return refl
|
||||
|
||||
def mo1_bragg_angle(mo_mode, d_spacing, energy, cm_pitch):
|
||||
H = 6.62606957E-34
|
||||
E = 1.602176634E-19
|
||||
C = 299792458
|
||||
wl = C * H / (E * energy)
|
||||
val = wl / (2 * d_spacing * 1e-10)
|
||||
bragg_angle = 0
|
||||
if val > -1 and val < 1:
|
||||
bragg_angle = np.asin(val)
|
||||
if mo_mode in 'Monochromatic':
|
||||
# Add 2x CM pitch to the bragg angle
|
||||
bragg_angle_cor = ((2 * cm_pitch) + bragg_angle)
|
||||
elif mo_mode in 'Pinkbeam':
|
||||
# Align xtal surfaces parallel to beam
|
||||
bragg_angle_cor = (2 * cm_pitch)
|
||||
return bragg_angle, bragg_angle_cor
|
||||
|
||||
def fm_ideal_pitch(fm_focus, fm_stripe, smpl, sldi_hacc=None, sldi_vacc=None, fm_focx=None, fm_focy=None):
|
||||
p = bl.fm.center[1] # posFM
|
||||
q = smpl - bl.fm.center[1] # dist posFM to posEX
|
||||
if fm_focus in 'Defocused':
|
||||
a = 2 * np.tan(sldi_hacc) * bl.fm.center[1] # Beam width at focusing mirror
|
||||
b = 2 * np.tan(sldi_vacc) * bl.cm.center[1] # Beam height at focusing mirror (collimated beam)
|
||||
x = fm_focx
|
||||
y = fm_focy
|
||||
qx = q + x * p / a
|
||||
qy = q + y * p / b
|
||||
f = (p * qx) / (p + qx) # focal length
|
||||
else: # Calculate for focused beam on sample in "manual" and "focused" mode
|
||||
qy = None
|
||||
f = (p * q) / (p + q) # focal length
|
||||
pitch = 0
|
||||
if 'Rh' in fm_stripe:
|
||||
pitch = np.arcsin(bl.fm.r[0]/(2*f))# ideal pitch for FM
|
||||
if 'Pt' in fm_stripe:
|
||||
pitch = np.arcsin(bl.fm.r[1]/(2*f)) # ideal pitch for FM
|
||||
return pitch, qy
|
||||
|
||||
def cm_critical_angle(cm_stripe, energy):
|
||||
if cm_stripe in 'Si':
|
||||
stripe = bl.stripeSi
|
||||
elif cm_stripe in 'Pt':
|
||||
stripe = bl.stripePt
|
||||
elif cm_stripe in 'Rh':
|
||||
stripe = bl.stripeRh
|
||||
else:
|
||||
raise Exception(f'Stripe {stripe} not found in beamline parameters!')
|
||||
w = CHeVcm/100/energy # convert energy [eV] to wavelength [m]
|
||||
# Calculate critical angle for mirror
|
||||
f1 = stripe.elements[0].Z + np.real(stripe.elements[0].get_f1f2(energy))
|
||||
numberDensity = stripe.rho*1e3*AVOGADRO/(stripe.elements[0].mass/1e3)
|
||||
criticalAngle = np.sqrt(numberDensity*2.8179e-15*w**2*f1/np.pi)
|
||||
return criticalAngle
|
||||
|
||||
|
||||
def mirror_surface_geometries(mirror):
|
||||
if mirror in "cm":
|
||||
surface = bl.cm.surface
|
||||
limOptX = bl.cm.limOptX
|
||||
limOptY = bl.cm.limOptY
|
||||
elif mirror in 'fm_toroid':
|
||||
surface = bl.fm.surfaceToroid
|
||||
limOptX = bl.fm.limOptXToroid
|
||||
limOptY = bl.fm.limOptYToroid
|
||||
elif mirror in 'fm_flat':
|
||||
surface = bl.fm.surfaceFlat
|
||||
limOptX = bl.fm.limOptXFlat
|
||||
limOptY = bl.fm.limOptYFlat
|
||||
else:
|
||||
raise ValueError(f'Requested mirror {mirror} not available!')
|
||||
geom = {}
|
||||
for sf, lx, hx, ly, hy in zip(surface, limOptX[0], limOptX[1], limOptY[0], limOptY[1]):
|
||||
geom[sf] = (lx, ly, hx-lx, hy-ly)
|
||||
return geom
|
||||
|
||||
def mo_surface_geometries(mo, plane):
|
||||
if mo in 'mo1':
|
||||
xtal = bl.mo1.xtal
|
||||
xtal_width = bl.mo1.xtalWidth
|
||||
xtal_offset_x = bl.mo1.xtalOffsetX
|
||||
if plane == 0:
|
||||
xtal_length = bl.mo1.xtalLength1
|
||||
else:
|
||||
xtal_length = bl.mo1.xtalLength2
|
||||
else:
|
||||
raise ValueError(f'Requested mono {mo} not available!')
|
||||
geom = {}
|
||||
for sf, w, offx, length in zip(xtal, xtal_width, xtal_offset_x, xtal_length):
|
||||
geom[sf] = (offx-w/2, -length/2, w, length)
|
||||
return geom
|
||||
|
||||
def wall_geometries():
|
||||
geom = []
|
||||
for i, _ in enumerate(bl.walls.start):
|
||||
geom.append([
|
||||
bl.walls.start[i],
|
||||
bl.walls.height[i][0],
|
||||
bl.walls.end[i] - bl.walls.start[i],
|
||||
bl.walls.height[i][1] - bl.walls.height[i][0],
|
||||
])
|
||||
return geom
|
||||
|
||||
def pipe_geometries():
|
||||
pipes = []
|
||||
for i, _ in enumerate(bl.vacuum_pipes.center):
|
||||
top = bl.vacuum_pipes.center[i] + bl.vacuum_pipes.diameter[i]/2 + bl.sourceHeight
|
||||
bottom = bl.vacuum_pipes.center[i] - bl.vacuum_pipes.diameter[i]/2 + bl.sourceHeight
|
||||
pipes.append({
|
||||
'x': np.array([bl.vacuum_pipes.start[i], bl.vacuum_pipes.end[i]]),
|
||||
'y': np.array([top, top])
|
||||
})
|
||||
pipes.append({
|
||||
'x': np.array([bl.vacuum_pipes.start[i], bl.vacuum_pipes.end[i]]),
|
||||
'y': np.array([bottom, bottom])
|
||||
})
|
||||
return pipes
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1 @@
|
||||
{'files': ['digital_twin.py']}
|
||||
@@ -0,0 +1,57 @@
|
||||
# Copyright (C) 2022 The Qt Company Ltd.
|
||||
# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
|
||||
|
||||
from bec_widgets.utils.bec_designer import designer_material_icon
|
||||
from qtpy.QtDesigner import QDesignerCustomWidgetInterface
|
||||
from qtpy.QtWidgets import QWidget
|
||||
|
||||
from debye_bec.bec_widgets.widgets.digital_twin.digital_twin import DigitalTwin
|
||||
|
||||
DOM_XML = """
|
||||
<ui language='c++'>
|
||||
<widget class='DigitalTwin' name='digital_twin'>
|
||||
</widget>
|
||||
</ui>
|
||||
"""
|
||||
|
||||
|
||||
class DigitalTwinPlugin(QDesignerCustomWidgetInterface): # pragma: no cover
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self._form_editor = None
|
||||
|
||||
def createWidget(self, parent):
|
||||
if parent is None:
|
||||
return QWidget()
|
||||
t = DigitalTwin(parent)
|
||||
return t
|
||||
|
||||
def domXml(self):
|
||||
return DOM_XML
|
||||
|
||||
def group(self):
|
||||
return ""
|
||||
|
||||
def icon(self):
|
||||
return designer_material_icon(DigitalTwin.ICON_NAME)
|
||||
|
||||
def includeFile(self):
|
||||
return "digital_twin"
|
||||
|
||||
def initialize(self, form_editor):
|
||||
self._form_editor = form_editor
|
||||
|
||||
def isContainer(self):
|
||||
return False
|
||||
|
||||
def isInitialized(self):
|
||||
return self._form_editor is not None
|
||||
|
||||
def name(self):
|
||||
return "DigitalTwin"
|
||||
|
||||
def toolTip(self):
|
||||
return "DigitalTwin"
|
||||
|
||||
def whatsThis(self):
|
||||
return self.toolTip()
|
||||
@@ -0,0 +1,511 @@
|
||||
import time
|
||||
import random
|
||||
import threading
|
||||
|
||||
# import qtawesome as qta
|
||||
from bec_qthemes import material_icon
|
||||
from bec_widgets.utils.colors import get_accent_colors
|
||||
from bec_lib import bec_logger
|
||||
|
||||
from debye_bec.devices.absorber import STATUS as ABS_STATUS
|
||||
|
||||
from qtpy.QtCore import Qt, QThread, Signal, QObject, Property, QPropertyAnimation
|
||||
from qtpy.QtWidgets import (
|
||||
QGroupBox, QHBoxLayout, QVBoxLayout, QLabel, QPushButton,
|
||||
QDoubleSpinBox, QFrame, QWidget, QApplication
|
||||
)
|
||||
from qtpy.QtGui import QTransform
|
||||
|
||||
logger = bec_logger.logger
|
||||
|
||||
class Status:
|
||||
IN_POSITION = "in_position" # green mdi.check-circle
|
||||
NOT_IN_POSITION = "not_in_position" # orange mdi.close-circle
|
||||
MOVING = "moving" # blue mdi.loading (spinning)
|
||||
ERROR = "error" # red mdi.alert-circle
|
||||
|
||||
class StatusIcon(QWidget):
|
||||
"""
|
||||
Displays a status icon using bec_qthemes Material Design Icons.
|
||||
Handles its own spin animation for the MOVING state via QPropertyAnimation.
|
||||
"""
|
||||
|
||||
ICON_SIZE = 20
|
||||
|
||||
_ICON_MAP = {
|
||||
Status.IN_POSITION: ("check_circle", "#27ae60"),
|
||||
Status.NOT_IN_POSITION: ("cancel", "#e6d922"),
|
||||
Status.ERROR: ("warning", "#e74c3c"),
|
||||
Status.MOVING: ("cycle", "#2980b9"),
|
||||
}
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super().__init__(parent=parent)
|
||||
self._status = None
|
||||
self._rotation = 0.0
|
||||
|
||||
self._label = QLabel(self)
|
||||
self._label.setFixedSize(self.ICON_SIZE, self.ICON_SIZE)
|
||||
self._label.setAlignment(Qt.AlignCenter)
|
||||
self.setFixedSize(self.ICON_SIZE, self.ICON_SIZE)
|
||||
|
||||
self._spin_anim = QPropertyAnimation(self, b"rotation")
|
||||
self._spin_anim.setStartValue(0)
|
||||
self._spin_anim.setEndValue(360)
|
||||
self._spin_anim.setDuration(1000)
|
||||
self._spin_anim.setLoopCount(-1) # Loop indefinitely
|
||||
|
||||
self.set_status(Status.NOT_IN_POSITION)
|
||||
|
||||
def get_rotation(self):
|
||||
return self._rotation
|
||||
|
||||
def set_rotation(self, angle):
|
||||
self._rotation = angle
|
||||
if self._current_pixmap_base is not None:
|
||||
cx = self._current_pixmap_base.width() / 2
|
||||
cy = self._current_pixmap_base.height() / 2
|
||||
t = QTransform().translate(cx, cy).rotate(angle).translate(-cx, -cy)
|
||||
self._label.setPixmap(self._current_pixmap_base.transformed(t, Qt.SmoothTransformation))
|
||||
|
||||
rotation = Property(float, get_rotation, set_rotation)
|
||||
|
||||
def set_status(self, status: str):
|
||||
if status == self._status:
|
||||
return
|
||||
self._status = status
|
||||
|
||||
icon_name, color = self._ICON_MAP[status]
|
||||
icon = material_icon(icon_name, size=(self.ICON_SIZE, self.ICON_SIZE), color=color, convert_to_pixmap=True)
|
||||
self._current_pixmap_base = icon
|
||||
|
||||
if status == Status.MOVING:
|
||||
self._spin_anim.start()
|
||||
else:
|
||||
self._spin_anim.stop()
|
||||
self._label.setPixmap(icon)
|
||||
|
||||
class MotionWorker(QObject):
|
||||
"""
|
||||
Executes motion on the specified motor and includes some safety during
|
||||
motion for certain motors.
|
||||
"""
|
||||
position_changed = Signal(float)
|
||||
error = Signal(bool) # True = error
|
||||
finished = Signal(bool) # True = reached target, False = stopped
|
||||
|
||||
def __init__(self, dev, motor, target_pos: float):
|
||||
super().__init__()
|
||||
self.dev = dev
|
||||
self.motor = motor
|
||||
self._target = target_pos
|
||||
self._stop_flag = threading.Event()
|
||||
|
||||
def stop(self):
|
||||
self._stop_flag.set()
|
||||
|
||||
# def run(self):
|
||||
# logger.info(f'Would run motor {self.motor}')
|
||||
# simulated_run_time = 3
|
||||
# start = time.time()
|
||||
# while (time.time() - start) < simulated_run_time:
|
||||
# if self._stop_flag.is_set():
|
||||
# break
|
||||
# time.sleep(0.01)
|
||||
|
||||
# # self.motor.move(self._target, relative=False)
|
||||
# # while self.motor.motor_is_moving.get():
|
||||
# # if self._stop_flag.is_set():
|
||||
# # self.motor.motor_stop()
|
||||
# # self.position_changed.emit(self.motor.read[self.name]['value'])
|
||||
# # time.sleep(0.1)
|
||||
# self.finished.emit(True)
|
||||
|
||||
def run(self):
|
||||
match self.motor:
|
||||
case 'sldi_gapx' | 'sldi_gapy' | 'sldi_centerx' | 'sldi_centery':
|
||||
self.motion()
|
||||
case 'cm_trx':
|
||||
self.motion(abs_closed=True, surveyed_axes=[
|
||||
{'device': self.dev['cm_roty'], 'abs_tol': 0.05}
|
||||
])
|
||||
case 'cm_roty':
|
||||
self.motion(abs_closed=True, surveyed_axes=[
|
||||
{'device': self.dev['cm_trx'], 'abs_tol': 0.05}
|
||||
])
|
||||
case 'cm_try':
|
||||
self.motion(abs_closed=True, surveyed_axes=[
|
||||
{'device': self.dev['cm_rotx'], 'abs_tol': 0.05},
|
||||
{'device': self.dev['cm_rotz'], 'abs_tol': 0.05},
|
||||
])
|
||||
case 'cm_rotx':
|
||||
self.motion(abs_closed=True, surveyed_axes=[
|
||||
{'device': self.dev['cm_try'], 'abs_tol': 0.05},
|
||||
{'device': self.dev['cm_rotz'], 'abs_tol': 0.05},
|
||||
])
|
||||
case 'cm_rotz':
|
||||
self.motion(abs_closed=True, surveyed_axes=[
|
||||
{'device': self.dev['cm_try'], 'abs_tol': 0.05},
|
||||
{'device': self.dev['cm_rotx'], 'abs_tol': 0.05},
|
||||
])
|
||||
case 'cm_bnd':
|
||||
p1 = (1/(self.dev.cm_bnd_radius.read()['cm_bnd_radius']['value']*1e3) + 0.0284)/2e-6
|
||||
p2 = (1/(self._target*1e3) + 0.0284)/2e-6
|
||||
self._target = p2 - p1
|
||||
self.motion(relative=True, rb=
|
||||
{'device': self.dev['cm_bnd_radius']}
|
||||
)
|
||||
case 'mo1_try' | 'mo1_trx' | 'mo1_roty':
|
||||
self.motion(abs_closed=True)
|
||||
case 'mo1_bragg_angle':
|
||||
self.motion()
|
||||
case 'sl1_centery' | 'sl1_gapy' | 'bm1_try':
|
||||
self.motion()
|
||||
case 'fm_trx':
|
||||
self.motion(abs_closed=True, surveyed_axes=[
|
||||
{'device': self.dev['fm_roty'], 'abs_tol': 0.05}
|
||||
])
|
||||
case 'fm_roty':
|
||||
self.motion(abs_closed=True, surveyed_axes=[
|
||||
{'device': self.dev['fm_trx'], 'abs_tol': 0.05}
|
||||
])
|
||||
case 'fm_try':
|
||||
self.motion(abs_closed=True, surveyed_axes=[
|
||||
{'device': self.dev['fm_rotx'], 'abs_tol': 0.05},
|
||||
{'device': self.dev['fm_rotz'], 'abs_tol': 0.05},
|
||||
])
|
||||
case 'fm_rotx':
|
||||
self.motion(abs_closed=True, surveyed_axes=[
|
||||
{'device': self.dev['fm_try'], 'abs_tol': 0.05},
|
||||
{'device': self.dev['fm_rotz'], 'abs_tol': 0.05},
|
||||
])
|
||||
case 'fm_rotz':
|
||||
self.motion(abs_closed=True, surveyed_axes=[
|
||||
{'device': self.dev['fm_try'], 'abs_tol': 0.05},
|
||||
{'device': self.dev['fm_rotx'], 'abs_tol': 0.05},
|
||||
])
|
||||
case 'fm_bnd':
|
||||
p1 = (1/(self.dev.fm_bnd_radius.read()['fm_bnd_radius']['value']*1e3) + 4.28e-5)/1.84e-9
|
||||
p2 = (1/(self._target*1e3) + 4.28e-5)/1.84e-9
|
||||
self._target = p2 - p1
|
||||
self.motion(relative=True, rb=
|
||||
{'device': self.dev['fm_bnd_radius']}
|
||||
)
|
||||
case 'sl2_centery' | 'sl2_gapy' | 'bm2_try':
|
||||
self.motion()
|
||||
case 'ot_try' | 'ot_rotx' | 'ot_es1_trz':
|
||||
self.motion()
|
||||
case _:
|
||||
logger.warning(f'Motor {self.motor} not integrated in digital twin!')
|
||||
|
||||
def motion(self, abs_closed=False, relative=False, rb=None, surveyed_axes = None):
|
||||
"""
|
||||
Moves an axis while surverying a set of axes (if set).
|
||||
Example surveyed_axes:
|
||||
[{'device': bec_device_object, 'abs_tol': 0.1},]
|
||||
|
||||
Args:
|
||||
surveyed_axes (list): List of dictionaries of devices
|
||||
"""
|
||||
if abs_closed:
|
||||
if self.dev.abs.status.get() == ABS_STATUS.OPEN:
|
||||
status = self.dev.abs.close()
|
||||
# TODO Set timeout to 0.001 and check if it actually raises (it should not start motion).
|
||||
# Check of behavior of digital twin afterwards.
|
||||
status.wait(timeout=5)
|
||||
if surveyed_axes is not None:
|
||||
for surv_ax in surveyed_axes:
|
||||
surv_ax['name'] = surv_ax['device'].dotted_name
|
||||
surv_ax['old_value'] = surv_ax['device'].read(cached=True)[surv_ax['name']]['value']
|
||||
if rb is not None:
|
||||
rb['name'] = rb['device'].dotted_name
|
||||
self.dev[self.motor].move(self._target, relative=relative)
|
||||
time.sleep(0.5)
|
||||
while self.dev[self.motor].motor_is_moving.get():
|
||||
if self._stop_flag.is_set():
|
||||
self.dev[self.motor].stop()
|
||||
self._stop_flag.clear()
|
||||
if rb is not None:
|
||||
self.position_changed.emit(rb['device'].read(cached=True)[rb['name']]['value'])
|
||||
else:
|
||||
self.position_changed.emit(self.dev[self.motor].read(cached=True)[self.motor]['value'])
|
||||
if surveyed_axes is not None:
|
||||
for surv_ax in surveyed_axes:
|
||||
fb = surv_ax['device'].read(cached=True)[surv_ax['name']]['value']
|
||||
if abs(fb - surv_ax['old_value']) > surv_ax['abs_tol']:
|
||||
self.dev[self.motor].stop()
|
||||
self.error.emit(1)
|
||||
break
|
||||
time.sleep(0.1)
|
||||
self.finished.emit(True)
|
||||
|
||||
class MoveWidget(QWidget):
|
||||
"""
|
||||
One motor stage control group containing:
|
||||
- Target label (target position)
|
||||
- Feedback label (current position)
|
||||
- Status icon (bec_qthemes)
|
||||
- Start / Stop button
|
||||
"""
|
||||
|
||||
def __init__(self, dev, motor, label: str = '', unit=None, decimals=3, deadband=0.0):
|
||||
super().__init__()
|
||||
self.fb = 0.0
|
||||
self.target = 0
|
||||
self.dev = dev
|
||||
self.motor = motor
|
||||
self.deadband = deadband
|
||||
self.status = Status.IN_POSITION
|
||||
self._thread: QThread | None = None
|
||||
self._worker: MotionWorker | None = None
|
||||
|
||||
self.text_color = (0, 0, 0)
|
||||
|
||||
self.unit = unit
|
||||
self.decimals = decimals
|
||||
|
||||
layout = QHBoxLayout(self)
|
||||
layout.setContentsMargins(10, 0, 0, 0)
|
||||
layout.setSpacing(0)
|
||||
|
||||
# Name
|
||||
self.label = QLabel(label)
|
||||
self.label.setFixedWidth(100)
|
||||
self.label.setContentsMargins(0, 0, 10, 0)
|
||||
self.label.setWordWrap(True)
|
||||
layout.addWidget(self.label)
|
||||
|
||||
# Target
|
||||
self.target_label = QLabel('-')
|
||||
self.target_label.setFixedWidth(100)
|
||||
layout.addWidget(self.target_label)
|
||||
|
||||
# Feedback
|
||||
self.fb_label = QLabel('-')
|
||||
self.fb_label.setFixedWidth(100)
|
||||
layout.addWidget(self.fb_label)
|
||||
|
||||
# Status icon
|
||||
self.status_icon = StatusIcon()
|
||||
self.status_icon.setFixedWidth(30)
|
||||
self.status_icon.setContentsMargins(0, 0, 10, 0)
|
||||
layout.addWidget(self.status_icon)
|
||||
|
||||
# Start / Stop button
|
||||
self.btn_action = QPushButton("Move")
|
||||
self.btn_action.setFixedWidth(90)
|
||||
self.btn_action.setFixedHeight(20)
|
||||
self.btn_action.clicked.connect(self._on_button_clicked)
|
||||
layout.addWidget(self.btn_action)
|
||||
self.btn_mode = 'start'
|
||||
|
||||
self._apply_button_style("start")
|
||||
|
||||
self.apply_theme()
|
||||
|
||||
def apply_theme(self, theme=None):
|
||||
if theme is None:
|
||||
app = QApplication.instance()
|
||||
theme = app.theme.theme # type: ignore
|
||||
|
||||
if theme == "light":
|
||||
self.text_color = {'target': (79, 163, 224), 'fb': (240, 128, 60)}
|
||||
else: # dark theme
|
||||
self.text_color = {'target': (26, 111, 173), 'fb': (212, 83, 10)}
|
||||
r, g, b = self.text_color['target']
|
||||
self.target_label.setStyleSheet(f'QLabel {{color: rgb({r}, {g}, {b})}}')
|
||||
r, g, b = self.text_color['fb']
|
||||
self.fb_label.setStyleSheet(f'QLabel {{color: rgb({r}, {g}, {b})}}')
|
||||
|
||||
if self.btn_mode == 'start':
|
||||
self.btn_action.setStyleSheet(
|
||||
f"QPushButton {{background-color: {get_accent_colors().success.name()}; color: white;}}"
|
||||
)
|
||||
else:
|
||||
self.btn_action.setStyleSheet(
|
||||
f"QPushButton {{background-color: {get_accent_colors().emergency.name()}; color: white;}}"
|
||||
)
|
||||
|
||||
def set_target(self, target):
|
||||
self.target = target
|
||||
text = f'{target:.{int(self.decimals)}f}'
|
||||
if self.unit is not None:
|
||||
text = text + ' ' + self.unit
|
||||
self.target_label.setText(text)
|
||||
self._on_target_or_fb_changed()
|
||||
|
||||
def set_feedback(self, fb):
|
||||
if self.status != Status.MOVING:
|
||||
self.fb = fb
|
||||
text = f'{fb:.{int(self.decimals)}f}'
|
||||
if self.unit is not None:
|
||||
text = text + ' ' + self.unit
|
||||
self.fb_label.setText(text)
|
||||
self._on_target_or_fb_changed()
|
||||
|
||||
def _apply_button_style(self, mode: str):
|
||||
self.btn_mode = mode
|
||||
if mode == "start":
|
||||
self.btn_action.setText("Move")
|
||||
self.btn_action.setStyleSheet(
|
||||
f"QPushButton {{background-color: {get_accent_colors().success.name()}; color: white;}}"
|
||||
)
|
||||
else: # stop
|
||||
self.btn_action.setText("Stop")
|
||||
self.btn_action.setStyleSheet(
|
||||
f"QPushButton {{background-color: {get_accent_colors().emergency.name()}; color: white;}}"
|
||||
)
|
||||
|
||||
def _set_status(self, status: str):
|
||||
self.status = status
|
||||
self.status_icon.set_status(status)
|
||||
|
||||
def _on_target_or_fb_changed(self):
|
||||
"""Re-evaluate in-position status whenever the target value changes."""
|
||||
if self.status in (Status.ERROR, Status.MOVING):
|
||||
return
|
||||
if abs(self.fb - self.target) <= self.deadband:
|
||||
self._set_status(Status.IN_POSITION)
|
||||
else:
|
||||
self._set_status(Status.NOT_IN_POSITION)
|
||||
|
||||
def _on_button_clicked(self):
|
||||
if self._thread and self._thread.isRunning():
|
||||
self._stop_motion()
|
||||
else:
|
||||
self._start_motion()
|
||||
|
||||
def _start_motion(self):
|
||||
target = self.target
|
||||
if abs(target - self.fb) <= self.deadband:
|
||||
self._set_status(Status.IN_POSITION)
|
||||
return
|
||||
|
||||
self._set_status(Status.MOVING)
|
||||
self._apply_button_style("stop")
|
||||
|
||||
self._worker = MotionWorker(self.dev, self.motor, target)
|
||||
self._thread = QThread()
|
||||
self._worker.moveToThread(self._thread)
|
||||
|
||||
self._thread.started.connect(self._worker.run)
|
||||
self._worker.position_changed.connect(self._on_position_changed)
|
||||
self._worker.error.connect(self._on_error)
|
||||
self._worker.error.connect(self._thread.quit)
|
||||
self._worker.finished.connect(self._on_motion_finished)
|
||||
self._worker.finished.connect(self._thread.quit)
|
||||
self._thread.finished.connect(self._cleanup_thread)
|
||||
|
||||
self._thread.start()
|
||||
|
||||
def _on_error(self):
|
||||
self._set_status(Status.ERROR)
|
||||
self._apply_button_style("start")
|
||||
|
||||
def _stop_motion(self):
|
||||
if self._worker:
|
||||
self._worker.stop()
|
||||
|
||||
def _on_position_changed(self, pos: float):
|
||||
self.fb = pos
|
||||
text = f'{pos:.{int(self.decimals)}f}'
|
||||
if self.unit is not None:
|
||||
text = text + ' ' + self.unit
|
||||
self.fb_label.setText(text)
|
||||
|
||||
def _on_motion_finished(self, reached: bool):
|
||||
target = self.target
|
||||
if self.status not in Status.ERROR:
|
||||
if abs(self.fb - target) <= self.deadband:
|
||||
self._set_status(Status.IN_POSITION)
|
||||
else:
|
||||
self._set_status(Status.NOT_IN_POSITION)
|
||||
self._apply_button_style("start")
|
||||
|
||||
def _cleanup_thread(self):
|
||||
if self._thread:
|
||||
self._thread.deleteLater()
|
||||
self._thread = None
|
||||
if self._worker:
|
||||
self._worker.deleteLater()
|
||||
self._worker = None
|
||||
|
||||
def shutdown(self):
|
||||
if self._worker:
|
||||
self._worker.stop()
|
||||
if self._thread:
|
||||
self._thread.quit()
|
||||
self._thread.wait(2000) # max 2 s grace period
|
||||
|
||||
class AbsorberWidget(QWidget):
|
||||
"""
|
||||
Control of the frontend absorber (only open)
|
||||
"""
|
||||
|
||||
def __init__(self, absorber, label: str = 'Absorber'):
|
||||
super().__init__()
|
||||
self.absorber = absorber
|
||||
self.fb = False
|
||||
self.text_color = (0, 0, 0)
|
||||
|
||||
layout = QHBoxLayout(self)
|
||||
layout.setContentsMargins(10, 0, 0, 0)
|
||||
layout.setSpacing(0)
|
||||
|
||||
# Name
|
||||
self.label = QLabel(label)
|
||||
self.label.setFixedWidth(100)
|
||||
self.label.setContentsMargins(0, 0, 10, 0)
|
||||
self.label.setWordWrap(True)
|
||||
layout.addWidget(self.label)
|
||||
|
||||
# Blank
|
||||
self.blank_label = QLabel('')
|
||||
self.blank_label.setFixedWidth(100)
|
||||
layout.addWidget(self.blank_label)
|
||||
|
||||
# Feedback
|
||||
self.fb_label = QLabel('-')
|
||||
self.fb_label.setFixedWidth(100)
|
||||
layout.addWidget(self.fb_label)
|
||||
|
||||
# Blank icon
|
||||
self.blank_icon = QLabel('')
|
||||
self.blank_icon.setFixedWidth(30)
|
||||
self.blank_icon.setContentsMargins(0, 0, 10, 0)
|
||||
layout.addWidget(self.blank_icon)
|
||||
|
||||
# Open
|
||||
self.btn_action = QPushButton("Open")
|
||||
self.btn_action.setFixedWidth(90)
|
||||
self.btn_action.setFixedHeight(20)
|
||||
self.btn_action.clicked.connect(self._on_button_clicked)
|
||||
layout.addWidget(self.btn_action)
|
||||
|
||||
def set_feedback(self, fb: bool):
|
||||
self.fb = fb
|
||||
if fb:
|
||||
self.fb_label.setText('Open')
|
||||
self.fb_label.setStyleSheet(
|
||||
f"QLabel {{color: {get_accent_colors().success.name()}}}"
|
||||
)
|
||||
else:
|
||||
self.fb_label.setText('Closed')
|
||||
self.fb_label.setStyleSheet(
|
||||
f"QLabel {{color: {get_accent_colors().emergency.name()}}}"
|
||||
)
|
||||
|
||||
def enable_open(self, enable: bool = False):
|
||||
if enable:
|
||||
self.btn_action.setStyleSheet(
|
||||
f"QPushButton {{background-color: {get_accent_colors().success.name()}; color: white;}}"
|
||||
)
|
||||
self.btn_action.setEnabled(True)
|
||||
else: # disabled
|
||||
self.btn_action.setStyleSheet(
|
||||
"QPushButton {{background-color: rgb(120, 120, 120); color: white;}}"
|
||||
)
|
||||
self.btn_action.setDisabled(True)
|
||||
|
||||
def _on_button_clicked(self):
|
||||
self.absorber.open()
|
||||
@@ -0,0 +1,15 @@
|
||||
def main(): # pragma: no cover
|
||||
from qtpy import PYSIDE6
|
||||
|
||||
if not PYSIDE6:
|
||||
print("PYSIDE6 is not available in the environment. Cannot patch designer.")
|
||||
return
|
||||
from PySide6.QtDesigner import QPyDesignerCustomWidgetCollection
|
||||
|
||||
from debye_bec.bec_widgets.widgets.digital_twin.digital_twin_plugin import DigitalTwinPlugin
|
||||
|
||||
QPyDesignerCustomWidgetCollection.addCustomWidget(DigitalTwinPlugin())
|
||||
|
||||
|
||||
if __name__ == "__main__": # pragma: no cover
|
||||
main()
|
||||
@@ -0,0 +1,272 @@
|
||||
|
||||
from functools import partial
|
||||
# pylint: disable=E0611
|
||||
from qtpy.QtWidgets import (
|
||||
QWidget, QVBoxLayout, QHBoxLayout, QLabel, QLineEdit,
|
||||
QPushButton, QGroupBox, QComboBox, QApplication, QDoubleSpinBox
|
||||
)
|
||||
from qtpy.QtGui import QFont
|
||||
from qtpy.QtCore import Qt
|
||||
|
||||
from bec_widgets.utils.colors import get_accent_colors
|
||||
|
||||
class Group(QGroupBox):
|
||||
def __init__(self, label, widgets):
|
||||
super().__init__(label)
|
||||
self.layout = QVBoxLayout(self) # type: ignore
|
||||
for widget in widgets:
|
||||
self.layout.addWidget(widget) # type: ignore
|
||||
|
||||
class NumberIndicator(QWidget):
|
||||
def __init__(self, label='', unit=None, highlight=False, decimals=3):
|
||||
super().__init__()
|
||||
layout = QHBoxLayout(self)
|
||||
layout.setContentsMargins(10, 0, 0, 0)
|
||||
layout.setSpacing(0)
|
||||
self.label = QLabel(label)
|
||||
self.label.setFixedWidth(140)
|
||||
self.label.setContentsMargins(0, 0, 10, 0)
|
||||
self.label.setWordWrap(True)
|
||||
layout.addWidget(self.label)
|
||||
self.val = QLabel('-')
|
||||
self.val.setAlignment(Qt.AlignTop) # type: ignore
|
||||
# self.val.setFixedWidth(140)
|
||||
layout.addWidget(self.val)
|
||||
self.unit = unit
|
||||
self.highlight = highlight
|
||||
self.decimals = decimals
|
||||
self.number = 0
|
||||
if highlight:
|
||||
font = QFont()
|
||||
font.setBold(True)
|
||||
font.setPointSize(14)
|
||||
self.label.setFont(font)
|
||||
self.val.setFont(font)
|
||||
|
||||
def value(self) -> float:
|
||||
return self.number
|
||||
|
||||
def setLabel(self, label) -> None:
|
||||
self.label.setText(label)
|
||||
|
||||
def setValue(self, number):
|
||||
self.number = number
|
||||
text = f'{number:.{int(self.decimals)}f}'
|
||||
if self.unit is not None:
|
||||
text = text + ' ' + self.unit
|
||||
self.val.setText(text)
|
||||
|
||||
class InputNumberField(QWidget):
|
||||
def __init__(self, identifier='', label='', unit=None, prefix=None, init=0.0, decimals=1, single_step=0.1, ll=-1e6, hl=1e6):
|
||||
super().__init__()
|
||||
layout = QHBoxLayout(self)
|
||||
layout.setContentsMargins(10, 0, 0, 0)
|
||||
layout.setSpacing(0)
|
||||
self.identifier = identifier
|
||||
self.label = QLabel(label)
|
||||
self.label.setFixedWidth(140)
|
||||
self.label.setContentsMargins(0, 0, 10, 0)
|
||||
self.label.setWordWrap(True)
|
||||
layout.addWidget(self.label)
|
||||
self.val = QDoubleSpinBox()
|
||||
self.val.setRange(ll, hl)
|
||||
self.val.setDecimals(decimals)
|
||||
self.val.setSingleStep(single_step)
|
||||
self.val.setValue(init)
|
||||
if unit is not None:
|
||||
self.val.setSuffix(' ' + unit)
|
||||
if prefix is not None:
|
||||
self.val.setPrefix(prefix + ' ')
|
||||
# self.val.setFixedWidth(140)
|
||||
layout.addWidget(self.val)
|
||||
|
||||
def set_number(self, number):
|
||||
self.val.setValue(number)
|
||||
|
||||
def has_focus(self) -> bool:
|
||||
return self.val.hasFocus()
|
||||
|
||||
def value(self) -> float:
|
||||
return self.val.value()
|
||||
|
||||
def value_changed_connect(self, func):
|
||||
"""Connect a function to the Enter/Return key press."""
|
||||
self.val.valueChanged.connect(
|
||||
partial(func, identifier=self.identifier, value_obj=self.val, value=lambda: self.val.value())
|
||||
)
|
||||
|
||||
class ComboBox(QWidget):
|
||||
def __init__(self, identifier='', label='', enums=[]):
|
||||
super().__init__()
|
||||
layout = QHBoxLayout(self)
|
||||
layout.setContentsMargins(10, 0, 0, 0)
|
||||
layout.setSpacing(0)
|
||||
self.identifier = identifier
|
||||
self.label = QLabel(label)
|
||||
self.label.setFixedWidth(140)
|
||||
self.label.setContentsMargins(0, 0, 10, 0)
|
||||
self.label.setWordWrap(True)
|
||||
layout.addWidget(self.label)
|
||||
self.value = QComboBox()
|
||||
for entry in enums:
|
||||
self.value.addItem(entry)
|
||||
layout.addWidget(self.value)
|
||||
|
||||
def set_current_text(self, text):
|
||||
self.value.setCurrentText(text)
|
||||
|
||||
def currentText(self) -> str:
|
||||
return self.value.currentText()
|
||||
|
||||
def has_focus(self) -> bool:
|
||||
return QApplication.focusWidget() is self.value.view()
|
||||
|
||||
def activated_connect(self, func):
|
||||
"""Connect a function to the Enter/Return key press."""
|
||||
self.value.activated.connect(
|
||||
partial(func, identifier=self.identifier, value_obj=self.value, value=lambda: self.value.currentText())
|
||||
)
|
||||
|
||||
def setDisabled(self, disable):
|
||||
self.value.setDisabled(disable)
|
||||
|
||||
class Button(QWidget):
|
||||
def __init__(self, label=None, label_button:str='', enabled=False):
|
||||
super().__init__()
|
||||
layout = QHBoxLayout(self)
|
||||
layout.setContentsMargins(10, 0, 0, 0)
|
||||
layout.setSpacing(0)
|
||||
if label is not None:
|
||||
self.label = QLabel(label)
|
||||
self.label.setFixedWidth(140)
|
||||
layout.addWidget(self.label)
|
||||
self.button = QPushButton(label_button)
|
||||
if label is not None:
|
||||
self.button.setFixedWidth(160)
|
||||
self.enable_button(enabled)
|
||||
layout.addWidget(self.button)
|
||||
|
||||
def clicked_connect(self, func):
|
||||
"""Connect a function to the button press."""
|
||||
self.button.clicked.connect(func)
|
||||
|
||||
def enable_button(self, enable: bool = False):
|
||||
if enable:
|
||||
self.button.setStyleSheet(
|
||||
f"QPushButton {{background-color: {get_accent_colors().default.name()}; color: white;}}"
|
||||
)
|
||||
self.button.setEnabled(True)
|
||||
else: # disabled
|
||||
self.button.setStyleSheet(
|
||||
"QPushButton {{background-color: rgb(120, 120, 120); color: white;}}"
|
||||
)
|
||||
self.button.setDisabled(True)
|
||||
|
||||
def setText(self, text):
|
||||
self.button.setText(text)
|
||||
|
||||
# class TextIndicator(QWidget):
|
||||
# def __init__(self, label, unit=None, highlight=False):
|
||||
# super().__init__()
|
||||
# layout = QHBoxLayout(self)
|
||||
# layout.setContentsMargins(10, 0, 0, 0)
|
||||
# layout.setSpacing(0)
|
||||
# self.label = QLabel(label)
|
||||
# self.label.setFixedWidth(150)
|
||||
# layout.addWidget(self.label)
|
||||
# self.value = QLabel('-')
|
||||
# self.value.setFixedWidth(160)
|
||||
# layout.addWidget(self.value)
|
||||
# self.unit = unit
|
||||
# self.highlight = highlight
|
||||
# if highlight:
|
||||
# font = QFont()
|
||||
# font.setBold(True)
|
||||
# font.setPointSize(14)
|
||||
# self.label.setFont(font)
|
||||
# self.value.setFont(font)
|
||||
|
||||
# def set_text(self, text):
|
||||
# if self.unit is not None:
|
||||
# text = text + ' ' + self.unit
|
||||
# self.value.setText(text)
|
||||
|
||||
# class Button(QWidget):
|
||||
# def __init__(self, label, label_button):
|
||||
# super().__init__()
|
||||
# layout = QHBoxLayout(self)
|
||||
# layout.setContentsMargins(10, 0, 0, 0)
|
||||
# layout.setSpacing(0)
|
||||
# self.label = QLabel(label)
|
||||
# self.label.setFixedWidth(150)
|
||||
# layout.addWidget(self.label)
|
||||
# self.button = QPushButton(label_button)
|
||||
# self.button.setStyleSheet("color: black; background-color: dodgerblue;")
|
||||
# self.button.setFixedWidth(160)
|
||||
# layout.addWidget(self.button)
|
||||
|
||||
# def set_on_press(self, func):
|
||||
# """Connect a function to the button press."""
|
||||
# self.button.clicked.connect(func)
|
||||
|
||||
# def enable_button(self):
|
||||
# self.button.setEnabled(True)
|
||||
# self.button.setStyleSheet("color: black; background-color: dodgerblue;")
|
||||
|
||||
# def disable_button(self):
|
||||
# self.button.setEnabled(False)
|
||||
# self.button.setStyleSheet("color: black; background-color: grey;")
|
||||
|
||||
# def set_button_text(self, text):
|
||||
# self.button.setText(text)
|
||||
|
||||
# class LED(QWidget):
|
||||
# def __init__(self, states, colors, label):
|
||||
# super().__init__()
|
||||
# self.states = states
|
||||
# self.colors = colors
|
||||
# layout = QHBoxLayout(self)
|
||||
# layout.setContentsMargins(10, 0, 0, 0)
|
||||
# layout.setSpacing(0)
|
||||
# self.label = QLabel(label)
|
||||
# self.label.setFixedWidth(150)
|
||||
# layout.addWidget(self.label)
|
||||
# self.led = QLabel()
|
||||
# self.led.setFixedWidth(160)
|
||||
# layout.addWidget(self.led)
|
||||
|
||||
# def apply_color(self, val):
|
||||
# color = self.colors[self.states.index(val)]
|
||||
# self.led.setStyleSheet(f"background-color: {color}; border: 1px solid black;")
|
||||
|
||||
# class InputTextField(QWidget):
|
||||
# def __init__(self, topic, label):
|
||||
# super().__init__()
|
||||
# self.topic = topic
|
||||
# layout = QHBoxLayout(self)
|
||||
# layout.setContentsMargins(10, 0, 0, 0)
|
||||
# layout.setSpacing(0)
|
||||
# self.label = QLabel(label)
|
||||
# self.label.setFixedWidth(140)
|
||||
# self.label.setContentsMargins(0, 0, 10, 0)
|
||||
# self.label.setWordWrap(True)
|
||||
# layout.addWidget(self.label)
|
||||
# self.val = QLineEdit()
|
||||
# self.val.setPlaceholderText('0')
|
||||
# # self.val.setFixedWidth(140)
|
||||
# layout.addWidget(self.val)
|
||||
|
||||
# def set_text(self, text):
|
||||
# self.val.setText(text)
|
||||
|
||||
# def has_focus(self) -> bool:
|
||||
# return self.val.hasFocus()
|
||||
|
||||
# def text(self) -> str:
|
||||
# return self.val.text()
|
||||
|
||||
# def set_on_return(self, func):
|
||||
# """Connect a function to the Enter/Return key press."""
|
||||
# self.val.returnPressed.connect(
|
||||
# partial(func, self.val, self.topic, lambda: self.val.text())
|
||||
# )
|
||||
@@ -0,0 +1,50 @@
|
||||
cm_try:
|
||||
offset: 0.15
|
||||
|
||||
mo1_trx:
|
||||
modifier:
|
||||
axis: mo1_trx
|
||||
range: [[-30, -0.1], [0.1, 30]]
|
||||
offset: [0, 2.21]
|
||||
|
||||
mo1_try:
|
||||
modifier:
|
||||
axis: mo1_trx
|
||||
range: [[-30, -0.1], [0.1, 30]]
|
||||
offset: [0, -1.6]
|
||||
|
||||
sl1_centery:
|
||||
offset: -1.8
|
||||
|
||||
fm_trx:
|
||||
modifier:
|
||||
axis: fm_trx
|
||||
range: [[-66, -31], [-24, 7], [11, 31], [38, 66]]
|
||||
offset: [0, 0, 0, -0.16]
|
||||
|
||||
fm_try:
|
||||
modifier:
|
||||
axis: fm_trx
|
||||
range: [[-66, -31], [-24, 7], [11, 31], [38, 66]]
|
||||
offset: [0, 0, 0, -0.45]
|
||||
|
||||
fm_rotx:
|
||||
modifier:
|
||||
axis: fm_trx
|
||||
range: [[-66, -31], [-24, 7], [11, 31], [38, 66]]
|
||||
offset: [0, 0, 0, 0.063]
|
||||
|
||||
fm_roty:
|
||||
modifier:
|
||||
axis: fm_trx
|
||||
range: [[-66, -31], [-24, 7], [11, 31], [38, 66]]
|
||||
offset: [0, 0, 0, -0.04]
|
||||
|
||||
sl2_centery:
|
||||
offset: 1.2
|
||||
|
||||
ot_try:
|
||||
offset: 0
|
||||
|
||||
ot_rotx:
|
||||
offset: 0
|
||||
@@ -0,0 +1,311 @@
|
||||
"""
|
||||
X01DA / Debye Beamline Parameters.
|
||||
This file describes the parameter of each component of the Debye beamline
|
||||
to be used for raytracing and geometrical calculations.
|
||||
"""
|
||||
|
||||
import os
|
||||
import numpy as np
|
||||
from collections import namedtuple
|
||||
|
||||
import xrt.backends.raycing.materials as rm
|
||||
|
||||
# if os.environ.get("USE_XRT", "True").lower() in ("1", "true", "yes"):
|
||||
# import xrt.backends.raycing.materials as rm # type: ignore
|
||||
# else:
|
||||
# class _DummyClass:
|
||||
# def __init__(self, *args, **kwargs):
|
||||
# pass
|
||||
# class _DummyMaterials:
|
||||
# Material = _DummyClass
|
||||
# CrystalSi = _DummyClass
|
||||
# rm = _DummyMaterials()
|
||||
|
||||
# XRT definitions
|
||||
filterBeryl = rm.Material('Be', rho=1.85, kind='plate') # pyright: ignore[reportArgumentType]
|
||||
filterDiamond = rm.Material('C', rho=3.52, kind='plate') # pyright: ignore[reportArgumentType]
|
||||
filterGraphite = rm.Material('C', rho=2.266, kind='plate') # pyright: ignore[reportArgumentType]
|
||||
|
||||
stripeSi = rm.Material('Si', rho=2.33) # pyright: ignore[reportArgumentType]
|
||||
stripePt = rm.Material('Pt', rho=21.45) # pyright: ignore[reportArgumentType]
|
||||
stripeRh = rm.Material('Rh', rho=12.41) # pyright: ignore[reportArgumentType]
|
||||
stripeCr = rm.Material('Cr', rho=7.14) # pyright: ignore[reportArgumentType]
|
||||
stripePyrex = rm.Material('Si', rho=2.20) # Use Si as bare element and the density of SiO2 # pyright: ignore[reportArgumentType]
|
||||
|
||||
si111_1 = rm.CrystalSi(hkl=(1, 1, 1), tK=77) # first xtal surface
|
||||
si311_1 = rm.CrystalSi(hkl=(3, 1, 1), tK=77) # first xtal surface
|
||||
si333_1 = rm.CrystalSi(hkl=(3, 3, 3), tK=77) # first xtal surface
|
||||
si511_1 = rm.CrystalSi(hkl=(5, 1, 1), tK=77) # first xtal surface
|
||||
si111_2 = rm.CrystalSi(hkl=(1, 1, 1), tK=77) # second xtal surface
|
||||
si311_2 = rm.CrystalSi(hkl=(3, 1, 1), tK=77) # second xtal surface
|
||||
si333_2 = rm.CrystalSi(hkl=(3, 3, 3), tK=77) # second xtal surface
|
||||
si511_2 = rm.CrystalSi(hkl=(5, 1, 1), tK=77) # second xtal surface
|
||||
|
||||
filterDiamond = rm.Material('C', rho=3.52, kind='plate') # pyright: ignore[reportArgumentType]
|
||||
filterBe = rm.Material('Be', rho=1.85, kind='plate') # pyright: ignore[reportArgumentType]
|
||||
filterSi3N4 = rm.Material(['Si', 'N'], quantities=[3, 4], rho=3.44, kind='plate') # pyright: ignore[reportArgumentType]
|
||||
filterAl = rm.Material('Al', rho=2.69, kind='plate') # pyright: ignore[reportArgumentType]
|
||||
filterGraphite = rm.Material('C', rho=2.266, kind='plate') # pyright: ignore[reportArgumentType]
|
||||
|
||||
# General parameters
|
||||
sourceHeight = 0
|
||||
|
||||
#Synchrotron
|
||||
synchrotron = namedtuple('synchrotron', ['eE', 'eI', 'eEspread',
|
||||
'eEpsilonX', 'eEpsilonZ', 'betaX', 'betaZ'])
|
||||
|
||||
sls1 = synchrotron(
|
||||
eE = 2.4,
|
||||
eI = 0.4,
|
||||
eEspread=0.878e-3,
|
||||
eEpsilonX=5.63,
|
||||
eEpsilonZ=0.007,
|
||||
betaX=0.45,
|
||||
betaZ=14.4,
|
||||
)
|
||||
|
||||
sls2 = synchrotron(
|
||||
eE=2.7,
|
||||
eI=0.4,
|
||||
eEspread=1.147e-3,
|
||||
eEpsilonX=0.156,
|
||||
eEpsilonZ=0.01,
|
||||
betaX=0.18,
|
||||
betaZ=4.6,
|
||||
)
|
||||
|
||||
# Source
|
||||
bendingMagnet = namedtuple('bendingMagnet', ['name', 'center', 'sync', 'B0'])
|
||||
|
||||
sls1_14t = bendingMagnet(
|
||||
name='FE-BM-SLS1-1.4T',
|
||||
center=(0, 0, 0),
|
||||
sync=sls1,
|
||||
B0=1.4,)
|
||||
|
||||
sls2_21t = bendingMagnet(
|
||||
name='FE-BM-SLS2-2.1T',
|
||||
center=(0, 0, 0),
|
||||
sync=sls2,
|
||||
B0=2.1,)
|
||||
|
||||
sls2_35t = bendingMagnet(
|
||||
name='FE-BM-SLS2-3.5T',
|
||||
center=(0, 0, 0),
|
||||
sync=sls2,
|
||||
B0=3.5,)
|
||||
|
||||
sls2_50t = bendingMagnet(
|
||||
name='FE-BM-SLS2-5.0T',
|
||||
center=(0, 0, 0),
|
||||
sync=sls2,
|
||||
B0=5.0,)
|
||||
|
||||
# FE slits
|
||||
fe_slits = namedtuple('slits', ['name', 'center', 'center1', 'center2', 'maxDivH', 'maxDivV'])
|
||||
|
||||
feSlits = fe_slits(
|
||||
name='FE-SLITS',
|
||||
center=(0, 6117, sourceHeight),
|
||||
center1=(0, 5045, sourceHeight),
|
||||
center2=(0, 5289.5, sourceHeight),
|
||||
maxDivH=1.8e-3,
|
||||
maxDivV=0.8e-3,)
|
||||
|
||||
# FE Window
|
||||
filt = namedtuple('filt', ['name', 'center', 'pitch', 'limPhysX', 'limPhysY', 'surface', 'material', 'thickness'])
|
||||
|
||||
feWindow = filt(
|
||||
name='FE-WINDOW',
|
||||
center=(0., 7020, sourceHeight),
|
||||
pitch=np.pi/2,
|
||||
limPhysX=(-6, 6),
|
||||
limPhysY=(-3., 3.),
|
||||
surface='None',
|
||||
material=filterDiamond,
|
||||
thickness=0.1,)
|
||||
feWindow = feWindow._replace(surface=f'CVD Diamond window {feWindow.thickness*1e3:0.0f} $\\mu$m')
|
||||
|
||||
# Collimating mirror
|
||||
collimatingMirror = namedtuple('collimatingMirror', ['name',
|
||||
'center', 'surface', 'material', 'limPhysX', 'limPhysY',
|
||||
'limOptX', 'limOptY', 'R', 'pitch', 'jack1', 'jack2', 'jack3',
|
||||
'tx1', 'tx2'])
|
||||
|
||||
cm = collimatingMirror(
|
||||
name='FE-CM',
|
||||
center=[0, 6890, sourceHeight],
|
||||
surface=('Si','Pt','Rh'),
|
||||
material=(stripeSi, stripePt, stripeRh),
|
||||
limPhysX=(-34, 34),
|
||||
limPhysY=(-600, 600),
|
||||
limOptX=((-21, -7, 14), (-11, 11, 23)),
|
||||
limOptY=((-500, -500, -500), (500, 500, 500)),
|
||||
R=[3e6, 15e6],
|
||||
pitch=[-5.0e-3, -0.0e-3],
|
||||
jack1=[0., 7210., 0.], #Tripod X, Y, Z (global)
|
||||
jack2=[-210., 8310., 0.],
|
||||
jack3=[210., 8310., 0.],
|
||||
tx1=[0.0, -575.5], # X-Stage 1 [x, y] (local)
|
||||
tx2=[0.0, 575],) # X-Stage 2
|
||||
|
||||
apertures = namedtuple('apertures', ['name', 'center', 'opening'])
|
||||
|
||||
fePS = apertures(
|
||||
name='FE-PS',
|
||||
center=[0, 8815, sourceHeight],
|
||||
opening=[-20., 20., -20.+12.5, 20.+12.5]) # left, right, bottom, top
|
||||
|
||||
opWbBsBlock = apertures(
|
||||
name='OP-WB-BS-BLOCK',
|
||||
center=[0., 13860, sourceHeight],
|
||||
opening=[-18., 18., 25, 85.5]) # left, right, bottom, top
|
||||
# opening=[-18., 18., 42, 76], # X10DA
|
||||
|
||||
# Monochromator
|
||||
monochromator = namedtuple('monochromator', ['name', 'center',
|
||||
'xtal', 'material1', 'material2', 'xtalWidth', 'xtalOffsetX',
|
||||
'xtalLength1', 'xtalLength2', 'xtalGap', 'rotOffset',
|
||||
'heightOffset', 'braggLim', 'jack1', 'jack2', 'jack3', 'tx'])
|
||||
|
||||
mo1 = monochromator(
|
||||
name='OP-MO1',
|
||||
center=[0., 11750, sourceHeight],
|
||||
xtal=('Si311','Si111'),
|
||||
material1=(si311_1, si111_1),
|
||||
material2=(si311_2, si111_2),
|
||||
xtalWidth = (24, 24),
|
||||
xtalOffsetX=(-21.2, 21.2),
|
||||
xtalLength1 = (55, 55),
|
||||
xtalLength2 = (105, 105),
|
||||
xtalGap = (8, 8),
|
||||
rotOffset = 6,
|
||||
heightOffset = 8.5,
|
||||
braggLim = [3.6, 33],
|
||||
jack1=[0., 11350., 0.], #Tripod maybe not available!
|
||||
jack2=[-400., 12350., 0.],
|
||||
jack3=[400., 12350., 0.],
|
||||
tx=0.0,) # X-Stage [x]
|
||||
|
||||
mo2 = monochromator(
|
||||
name='OP-CCM2',
|
||||
center=[0., 13250, sourceHeight],
|
||||
xtal=('Si311','Si111'),
|
||||
material1=(si311_1, si111_1),
|
||||
material2=(si311_2, si111_2),
|
||||
xtalWidth = (24, 24),
|
||||
xtalOffsetX=(-21, 21),
|
||||
xtalLength1 = (55, 55),
|
||||
xtalLength2 = (105, 105),
|
||||
xtalGap = (8, 8),
|
||||
rotOffset = 6,
|
||||
heightOffset = 8.5,
|
||||
braggLim = [3.6, 33],
|
||||
jack1=[0., 13350., 0.], #Tripod maybe not available!
|
||||
jack2=[-400., 14350., 0.],
|
||||
jack3=[400., 14350., 0.],
|
||||
tx=0.0,) # X-Stage [x]
|
||||
|
||||
# OP Slits
|
||||
op_slits = namedtuple('op_slits', ['name', 'center'])
|
||||
|
||||
opSlits1 = op_slits(
|
||||
name='OP-SLITS 1',
|
||||
center=(0, 14349.6, sourceHeight),
|
||||
)
|
||||
|
||||
opSlits2 = op_slits(
|
||||
name='OP-SLITS 2',
|
||||
center=(0, 18134.8, sourceHeight),
|
||||
)
|
||||
|
||||
# OP Beam Monitors
|
||||
op_bm = namedtuple('op_bm', ['name', 'center'])
|
||||
|
||||
opBM1 = op_bm(
|
||||
name='OP Beam Monitor 1',
|
||||
center=(0, 14599.6, sourceHeight),
|
||||
)
|
||||
|
||||
opBM2 = op_bm(
|
||||
name='OP Beam Monitor 2',
|
||||
center=(0, 18384.8, sourceHeight),
|
||||
)
|
||||
|
||||
# Focusing mirror
|
||||
focusingMirror = namedtuple('focusingMirror', ['name', 'center',
|
||||
'surfaceToroid', 'materialToroid', 'surfaceFlat', 'materialFlat',
|
||||
'limPhysXToroid', 'limPhysYToroid', 'limPhysXFlat', 'limPhysYFlat',
|
||||
'limOptXToroid', 'limOptYToroid', 'limOptXFlat', 'limOptYFlat',
|
||||
'R', 'pitch', 'r', 'xToroid', 'xFlat', 'hToroid', 'jack1', 'jack2', 'jack3',
|
||||
'tx1', 'tx2'])
|
||||
|
||||
fm = focusingMirror(
|
||||
name='OP-FM',
|
||||
center=[0., 15670, sourceHeight], # nominal height 58 mm above ring, SLS1!
|
||||
surfaceToroid=('Rh', 'Pt'),
|
||||
materialToroid=(stripeRh, stripePt),
|
||||
surfaceFlat=('Rh', 'Pt'),
|
||||
materialFlat=(stripeRh, stripePt),
|
||||
limPhysXToroid=(-79., 79.),
|
||||
limPhysYToroid=(-575., 575.),
|
||||
limPhysXFlat=(-79., 79.),
|
||||
limPhysYFlat=(-575., 575.),
|
||||
limOptXToroid=((-38, 66), (-66, 31)),
|
||||
limOptYToroid=((-500., -500.), (500., 500.)),
|
||||
limOptXFlat=((-11.45, 23.55), (-30.45, -6.45)),
|
||||
limOptYFlat=((-500., -500.), (500., 500.)),
|
||||
R=[3e6, 15e6],
|
||||
pitch=[-5.0e-3, 0e-3],
|
||||
r=[35.510, 24.986],
|
||||
xToroid=[-52, 48.5], # offset in local x
|
||||
xFlat = [-20.95, 8.55],
|
||||
hToroid=[2.88, 7.15], # depth of the cylinder at x = xCylinder1 and x = xCylinder2.
|
||||
jack1=[-130., 15535-538., 0.],
|
||||
jack2=[130., 15535+538., 0.],
|
||||
jack3=[0., 15535+538., 0.],
|
||||
tx1=[0., -575.], # X-Stage 1 [x, y]
|
||||
tx2=[0., 575.],) # X-Stage 2 [x, y]
|
||||
|
||||
# EH Window
|
||||
ehWindow = filt(
|
||||
name='EH-WINDOW',
|
||||
center=(0., 19998.3, sourceHeight),
|
||||
pitch=np.pi/2,
|
||||
limPhysX=(-20., 20.),
|
||||
limPhysY=(-4, 4),
|
||||
surface='None',
|
||||
material=filterSi3N4,
|
||||
thickness=0.002,)
|
||||
ehWindow = ehWindow._replace(surface=f'Beryllium window {ehWindow.thickness*1e3:0.0f} $\\mu$m')
|
||||
|
||||
# Sample
|
||||
sample = namedtuple('sample', ['name', 'center'])
|
||||
|
||||
smpl = sample(
|
||||
name='EH-SMPL',
|
||||
center=[0, 23365, sourceHeight],)
|
||||
|
||||
smpl2 = sample(
|
||||
name='EH-SMPL2',
|
||||
center=[0, 27500, sourceHeight],)
|
||||
|
||||
# Vacuum pipes
|
||||
# DN40CF ID = 35 mm oder 37 mm
|
||||
# DN50CF ID = 47.5 mm
|
||||
# DN63CF ID = 60.2 mm oder 66 mm
|
||||
# DN100CF ID = 97.4 mm oder 104 mm
|
||||
pipe = namedtuple('pipes', ['center', 'diameter', 'start', 'end'])
|
||||
vacuum_pipes = pipe(
|
||||
center= [27.5, (37.5+27.5)/2, 37.5, 62.5, 72.5],
|
||||
diameter=[97.4, 97.4, 97.4, 97.4, 97.4],
|
||||
start= [10952.88, 11750+250, mo2.center[1]+250, 14000, fm.center[1]],
|
||||
end= [11750-250, mo2.center[1]-250, 14000, fm.center[1], ehWindow.center[1]],
|
||||
)
|
||||
|
||||
Walls = namedtuple('walls', ['start', 'end', 'height'])
|
||||
walls = Walls(
|
||||
start= [13999.30],
|
||||
end= [13999+75.5+30],
|
||||
height= [[-20, 25]],
|
||||
)
|
||||
@@ -0,0 +1,34 @@
|
||||
|
||||
###################################
|
||||
## Beam Monitors ##
|
||||
###################################
|
||||
|
||||
beam_monitor_1:
|
||||
readoutPriority: async
|
||||
description: Beam monitor 1
|
||||
deviceClass: debye_bec.devices.cameras.prosilica_cam.ProsilicaCam
|
||||
deviceConfig:
|
||||
prefix: "X01DA-OP-GIGE01:"
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
|
||||
beam_monitor_2:
|
||||
readoutPriority: async
|
||||
description: Beam monitor 2
|
||||
deviceClass: debye_bec.devices.cameras.prosilica_cam.ProsilicaCam
|
||||
deviceConfig:
|
||||
prefix: "X01DA-OP-GIGE02:"
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
|
||||
xray_eye:
|
||||
readoutPriority: async
|
||||
description: X-ray eye
|
||||
deviceClass: debye_bec.devices.cameras.basler_cam.BaslerCam
|
||||
deviceConfig:
|
||||
prefix: "X01DA-ES-XRAYEYE:"
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
@@ -1,875 +0,0 @@
|
||||
###################
|
||||
#### FRONT END ####
|
||||
###################
|
||||
|
||||
## Slit Diaphragm -- Physical positioners
|
||||
sldi_trxr:
|
||||
readoutPriority: baseline
|
||||
description: Front-end slit diaphragm X-translation Ring-edge
|
||||
deviceClass: ophyd.EpicsMotor
|
||||
deviceConfig:
|
||||
prefix: X01DA-FE-SLDI:TRXR
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
sldi_trxw:
|
||||
readoutPriority: baseline
|
||||
description: Front-end slit diaphragm X-translation Wall-edge
|
||||
deviceClass: ophyd.EpicsMotor
|
||||
deviceConfig:
|
||||
prefix: X01DA-FE-SLDI:TRXW
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
sldi_tryb:
|
||||
readoutPriority: baseline
|
||||
description: Front-end slit diaphragm Y-translation Bottom-edge
|
||||
deviceClass: ophyd.EpicsMotor
|
||||
deviceConfig:
|
||||
prefix: X01DA-FE-SLDI:TRYB
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
sldi_tryt:
|
||||
readoutPriority: baseline
|
||||
description: Front-end slit diaphragm X-translation Top-edge
|
||||
deviceClass: ophyd.EpicsMotor
|
||||
deviceConfig:
|
||||
prefix: X01DA-FE-SLDI:TRYT
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
|
||||
## Slit Diaphragm -- Virtual positioners
|
||||
|
||||
sldi_centerx:
|
||||
readoutPriority: baseline
|
||||
description: Front-end slit diaphragm X-center
|
||||
deviceClass: ophyd.EpicsMotor
|
||||
deviceConfig:
|
||||
prefix: X01DA-FE-SLDI:CENTERX
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
sldi_gapx:
|
||||
readoutPriority: baseline
|
||||
description: Front-end slit diaphragm X-gap
|
||||
deviceClass: ophyd.EpicsMotor
|
||||
deviceConfig:
|
||||
prefix: X01DA-FE-SLDI:GAPX
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
sldi_centery:
|
||||
readoutPriority: baseline
|
||||
description: Front-end slit diaphragm Y-center
|
||||
deviceClass: ophyd.EpicsMotor
|
||||
deviceConfig:
|
||||
prefix: X01DA-FE-SLDI:CENTERY
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
sldi_gapy:
|
||||
readoutPriority: baseline
|
||||
description: Front-end slit diaphragm Y-gap
|
||||
deviceClass: ophyd.EpicsMotor
|
||||
deviceConfig:
|
||||
prefix: X01DA-FE-SLDI:GAPY
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
|
||||
## Collimating Mirror -- Physical Positioners
|
||||
|
||||
cm_trxu:
|
||||
readoutPriority: baseline
|
||||
description: Collimating Mirror X-translation upstream
|
||||
deviceClass: ophyd.EpicsMotor
|
||||
deviceConfig:
|
||||
prefix: X01DA-FE-CM:TRXU
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
cm_trxd:
|
||||
readoutPriority: baseline
|
||||
description: Collimating Mirror X-translation downstream
|
||||
deviceClass: ophyd.EpicsMotor
|
||||
deviceConfig:
|
||||
prefix: X01DA-FE-CM:TRXD
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
cm_tryu:
|
||||
readoutPriority: baseline
|
||||
description: Collimating Mirror Y-translation upstream
|
||||
deviceClass: ophyd.EpicsMotor
|
||||
deviceConfig:
|
||||
prefix: X01DA-FE-CM:TRYU
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
cm_trydr:
|
||||
readoutPriority: baseline
|
||||
description: Collimating Mirror Y-translation downstream ring
|
||||
deviceClass: ophyd.EpicsMotor
|
||||
deviceConfig:
|
||||
prefix: X01DA-FE-CM:TRYDR
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
cm_trydw:
|
||||
readoutPriority: baseline
|
||||
description: Collimating Mirror Y-translation downstream wall
|
||||
deviceClass: ophyd.EpicsMotor
|
||||
deviceConfig:
|
||||
prefix: X01DA-FE-CM:TRYDW
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
cm_bnd:
|
||||
readoutPriority: baseline
|
||||
description: Collimating Mirror bender
|
||||
deviceClass: ophyd.EpicsMotor
|
||||
deviceConfig:
|
||||
prefix: X01DA-FE-CM:BND
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
|
||||
## Collimating Mirror -- Virtual Positioners
|
||||
|
||||
cm_rotx:
|
||||
readoutPriority: baseline
|
||||
description: Collimating Morror Pitch
|
||||
deviceClass: ophyd.EpicsMotor
|
||||
deviceConfig:
|
||||
prefix: X01DA-FE-CM:ROTX
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
cm_roty:
|
||||
readoutPriority: baseline
|
||||
description: Collimating Morror Yaw
|
||||
deviceClass: ophyd.EpicsMotor
|
||||
deviceConfig:
|
||||
prefix: X01DA-FE-CM:ROTY
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
cm_rotz:
|
||||
readoutPriority: baseline
|
||||
description: Collimating Morror Roll
|
||||
deviceClass: ophyd.EpicsMotor
|
||||
deviceConfig:
|
||||
prefix: X01DA-FE-CM:ROTZ
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
cm_xctp:
|
||||
readoutPriority: baseline
|
||||
description: Collimating Morror Center Point X
|
||||
deviceClass: ophyd.EpicsMotor
|
||||
deviceConfig:
|
||||
prefix: X01DA-FE-CM:XTCP
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
cm_ytcp:
|
||||
readoutPriority: baseline
|
||||
description: Collimating Morror Center Point Y
|
||||
deviceClass: ophyd.EpicsMotor
|
||||
deviceConfig:
|
||||
prefix: X01DA-FE-CM:YTCP
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
cm_ztcp:
|
||||
readoutPriority: baseline
|
||||
description: Collimating Morror Center Point Z
|
||||
deviceClass: ophyd.EpicsMotor
|
||||
deviceConfig:
|
||||
prefix: X01DA-FE-CM:ZTCP
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
cm_xstripe:
|
||||
readoutPriority: baseline
|
||||
description: Collimating Morror X Stripe
|
||||
deviceClass: ophyd.EpicsMotor
|
||||
deviceConfig:
|
||||
prefix: X01DA-FE-CM:XSTRIPE
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
|
||||
###################
|
||||
###### OPTICS #####
|
||||
###################
|
||||
|
||||
## Bragg Monochromator
|
||||
mo1_bragg:
|
||||
readoutPriority: baseline
|
||||
description: Positioner for the Monochromator
|
||||
deviceClass: debye_bec.devices.mo1_bragg.mo1_bragg.Mo1Bragg
|
||||
deviceConfig:
|
||||
prefix: "X01DA-OP-MO1:BRAGG:"
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
|
||||
## Monochromator -- Physical Positioners
|
||||
|
||||
mo_try:
|
||||
readoutPriority: baseline
|
||||
description: Monochromator Y Translation
|
||||
deviceClass: ophyd.EpicsMotor
|
||||
deviceConfig:
|
||||
prefix: X01DA-OP-MO1:TRY
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
mo_trx:
|
||||
readoutPriority: baseline
|
||||
description: Monochromator X Translation
|
||||
deviceClass: ophyd.EpicsMotor
|
||||
deviceConfig:
|
||||
prefix: X01DA-OP-MO1:TRY
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
mo_roty:
|
||||
readoutPriority: baseline
|
||||
description: Monochromator Yaw
|
||||
deviceClass: ophyd.EpicsMotor
|
||||
deviceConfig:
|
||||
prefix: X01DA-OP-MO1:ROTY
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
|
||||
## Focusing Mirror -- Physical Positioners
|
||||
|
||||
fm_trxu:
|
||||
readoutPriority: baseline
|
||||
description: Focusing Mirror X-translation upstream
|
||||
deviceClass: ophyd.EpicsMotor
|
||||
deviceConfig:
|
||||
prefix: X01DA-OP-FM:TRXU
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
fm_trxd:
|
||||
readoutPriority: baseline
|
||||
description: Focusing Mirror X-translation downstream
|
||||
deviceClass: ophyd.EpicsMotor
|
||||
deviceConfig:
|
||||
prefix: X01DA-OP-FM:TRXD
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
fm_tryd:
|
||||
readoutPriority: baseline
|
||||
description: Focusing Mirror Y-translation downstream
|
||||
deviceClass: ophyd.EpicsMotor
|
||||
deviceConfig:
|
||||
prefix: X01DA-OP-FM:TRYD
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
fm_tryur:
|
||||
readoutPriority: baseline
|
||||
description: Focusing Mirror Y-translation upstream ring
|
||||
deviceClass: ophyd.EpicsMotor
|
||||
deviceConfig:
|
||||
prefix: X01DA-OP-FM:TRYUR
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
fm_tryuw:
|
||||
readoutPriority: baseline
|
||||
description: Focusing Mirror Y-translation upstream wall
|
||||
deviceClass: ophyd.EpicsMotor
|
||||
deviceConfig:
|
||||
prefix: X01DA-OP-FM:TRYUW
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
fm_bnd:
|
||||
readoutPriority: baseline
|
||||
description: Focusing Mirror bender
|
||||
deviceClass: ophyd.EpicsMotor
|
||||
deviceConfig:
|
||||
prefix: X01DA-OP-FM:BND
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
|
||||
## Focusing Mirror -- Virtual Positioners
|
||||
|
||||
fm_rotx:
|
||||
readoutPriority: baseline
|
||||
description: Focusing Morror Pitch
|
||||
deviceClass: ophyd.EpicsMotor
|
||||
deviceConfig:
|
||||
prefix: X01DA-OP-FM:ROTX
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
fm_roty:
|
||||
readoutPriority: baseline
|
||||
description: Focusing Morror Yaw
|
||||
deviceClass: ophyd.EpicsMotor
|
||||
deviceConfig:
|
||||
prefix: X01DA-OP-FM:ROTY
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
fm_rotz:
|
||||
readoutPriority: baseline
|
||||
description: Focusing Morror Roll
|
||||
deviceClass: ophyd.EpicsMotor
|
||||
deviceConfig:
|
||||
prefix: X01DA-OP-FM:ROTZ
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
fm_xctp:
|
||||
readoutPriority: baseline
|
||||
description: Focusing Morror Center Point X
|
||||
deviceClass: ophyd.EpicsMotor
|
||||
deviceConfig:
|
||||
prefix: X01DA-OP-FM:XTCP
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
fm_ytcp:
|
||||
readoutPriority: baseline
|
||||
description: Focusing Morror Center Point Y
|
||||
deviceClass: ophyd.EpicsMotor
|
||||
deviceConfig:
|
||||
prefix: X01DA-OP-FM:YTCP
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
fm_ztcp:
|
||||
readoutPriority: baseline
|
||||
description: Focusing Morror Center Point Z
|
||||
deviceClass: ophyd.EpicsMotor
|
||||
deviceConfig:
|
||||
prefix: X01DA-OP-FM:ZTCP
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
# fm_xstripe:
|
||||
# readoutPriority: baseline
|
||||
# description: Focusing Morror X Stripe
|
||||
# deviceClass: ophyd.EpicsMotor
|
||||
# deviceConfig:
|
||||
# prefix: X01DA-OP-FM:XSTRIPE
|
||||
# onFailure: retry
|
||||
# enabled: true
|
||||
# softwareTrigger: false
|
||||
|
||||
## Optics Slits 1 -- Physical positioners
|
||||
|
||||
sl1_trxr:
|
||||
readoutPriority: baseline
|
||||
description: Optics slits 1 X-translation Ring-edge
|
||||
deviceClass: ophyd.EpicsMotor
|
||||
deviceConfig:
|
||||
prefix: X01DA-OP-SL1:TRXR
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
sl1_trxw:
|
||||
readoutPriority: baseline
|
||||
description: Optics slits 1 X-translation Wall-edge
|
||||
deviceClass: ophyd.EpicsMotor
|
||||
deviceConfig:
|
||||
prefix: X01DA-OP-SL1:TRXW
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
sl1_tryb:
|
||||
readoutPriority: baseline
|
||||
description: Optics slits 1 Y-translation Bottom-edge
|
||||
deviceClass: ophyd.EpicsMotor
|
||||
deviceConfig:
|
||||
prefix: X01DA-OP-SL1:TRYB
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
sl1_tryt:
|
||||
readoutPriority: baseline
|
||||
description: Optics slits 1 X-translation Top-edge
|
||||
deviceClass: ophyd.EpicsMotor
|
||||
deviceConfig:
|
||||
prefix: X01DA-OP-SL1:TRYT
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
bm1_try:
|
||||
readoutPriority: baseline
|
||||
description: Beam Monitor 1 Y-translation
|
||||
deviceClass: ophyd.EpicsMotor
|
||||
deviceConfig:
|
||||
prefix: X01DA-OP-BM1:TRY
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
|
||||
## Optics Slits 1 -- Virtual positioners
|
||||
|
||||
sl1_centerx:
|
||||
readoutPriority: baseline
|
||||
description: Optics slits 1 X-center
|
||||
deviceClass: ophyd.EpicsMotor
|
||||
deviceConfig:
|
||||
prefix: X01DA-OP-SL1:CENTERX
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
sl1_gapx:
|
||||
readoutPriority: baseline
|
||||
description: Optics slits 1 X-gap
|
||||
deviceClass: ophyd.EpicsMotor
|
||||
deviceConfig:
|
||||
prefix: X01DA-OP-SL1:GAPX
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
sl1_centery:
|
||||
readoutPriority: baseline
|
||||
description: Optics slits 1 Y-center
|
||||
deviceClass: ophyd.EpicsMotor
|
||||
deviceConfig:
|
||||
prefix: X01DA-OP-SL1:CENTERY
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
sl1_gapy:
|
||||
readoutPriority: baseline
|
||||
description: Optics slits 1 Y-gap
|
||||
deviceClass: ophyd.EpicsMotor
|
||||
deviceConfig:
|
||||
prefix: X01DA-OP-SL1:GAPY
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
|
||||
## Optics Slits 2 -- Physical positioners
|
||||
|
||||
sl2_trxr:
|
||||
readoutPriority: baseline
|
||||
description: Optics slits 2 X-translation Ring-edge
|
||||
deviceClass: ophyd.EpicsMotor
|
||||
deviceConfig:
|
||||
prefix: X01DA-OP-SL2:TRXR
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
sl2_trxw:
|
||||
readoutPriority: baseline
|
||||
description: Optics slits 2 X-translation Wall-edge
|
||||
deviceClass: ophyd.EpicsMotor
|
||||
deviceConfig:
|
||||
prefix: X01DA-OP-SL2:TRXW
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
sl2_tryb:
|
||||
readoutPriority: baseline
|
||||
description: Optics slits 2 Y-translation Bottom-edge
|
||||
deviceClass: ophyd.EpicsMotor
|
||||
deviceConfig:
|
||||
prefix: X01DA-OP-SL2:TRYB
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
sl2_tryt:
|
||||
readoutPriority: baseline
|
||||
description: Optics slits 2 X-translation Top-edge
|
||||
deviceClass: ophyd.EpicsMotor
|
||||
deviceConfig:
|
||||
prefix: X01DA-OP-SL2:TRYT
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
bm2_try:
|
||||
readoutPriority: baseline
|
||||
description: Beam Monitor 2 Y-translation
|
||||
deviceClass: ophyd.EpicsMotor
|
||||
deviceConfig:
|
||||
prefix: X01DA-OP-BM2:TRY
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
|
||||
## Optics Slits 2 -- Virtual positioners
|
||||
|
||||
sl2_centerx:
|
||||
readoutPriority: baseline
|
||||
description: Optics slits 2 X-center
|
||||
deviceClass: ophyd.EpicsMotor
|
||||
deviceConfig:
|
||||
prefix: X01DA-OP-SL2:CENTERX
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
sl2_gapx:
|
||||
readoutPriority: baseline
|
||||
description: Optics slits 2 X-gap
|
||||
deviceClass: ophyd.EpicsMotor
|
||||
deviceConfig:
|
||||
prefix: X01DA-OP-SL2:GAPX
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
sl2_centery:
|
||||
readoutPriority: baseline
|
||||
description: Optics slits 2 Y-center
|
||||
deviceClass: ophyd.EpicsMotor
|
||||
deviceConfig:
|
||||
prefix: X01DA-OP-SL2:CENTERY
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
sl2_gapy:
|
||||
readoutPriority: baseline
|
||||
description: Optics slits 2 Y-gap
|
||||
deviceClass: ophyd.EpicsMotor
|
||||
deviceConfig:
|
||||
prefix: X01DA-OP-SL2:GAPY
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
|
||||
###############################
|
||||
###### EXPERIMENTAL HUTCH #####
|
||||
###############################
|
||||
|
||||
###########################################
|
||||
## Optical Table -- Physical Positioners ##
|
||||
###########################################
|
||||
|
||||
ot_tryu:
|
||||
readoutPriority: baseline
|
||||
description: Optical Table Y-Translation Upstream
|
||||
deviceClass: ophyd.EpicsMotor
|
||||
deviceConfig:
|
||||
prefix: X01DA-ES-OT:TRYU
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
ot_tryd:
|
||||
readoutPriority: baseline
|
||||
description: Optical Table Y-Translation Downstream
|
||||
deviceClass: ophyd.EpicsMotor
|
||||
deviceConfig:
|
||||
prefix: X01DA-ES-OT:TRYD
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
|
||||
############################################
|
||||
## Optical Table -- Virtual Positioners ###
|
||||
############################################
|
||||
|
||||
ot_try:
|
||||
readoutPriority: baseline
|
||||
description: Optical Table Y-Translation
|
||||
deviceClass: ophyd.EpicsMotor
|
||||
deviceConfig:
|
||||
prefix: X01DA-ES-OT:TRY
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
ot_pitch:
|
||||
readoutPriority: baseline
|
||||
description: Optical Table Pitch
|
||||
deviceClass: ophyd.EpicsMotor
|
||||
deviceConfig:
|
||||
prefix: X01DA-ES-OT:ROTX
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
|
||||
#########################################
|
||||
## Exit Window -- Physical Positioners ##
|
||||
#########################################
|
||||
|
||||
es0wi_try:
|
||||
readoutPriority: baseline
|
||||
description: End Station 0 Exit Window Y-translation
|
||||
deviceClass: ophyd.EpicsMotor
|
||||
deviceConfig:
|
||||
prefix: X01DA-ES0-WI:TRY
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
|
||||
###############################################
|
||||
## End Station Slits -- Physical Positioners ##
|
||||
###############################################
|
||||
|
||||
es0sl_trxr:
|
||||
readoutPriority: baseline
|
||||
description: End Station slits X-translation Ring-edge
|
||||
deviceClass: ophyd.EpicsMotor
|
||||
deviceConfig:
|
||||
prefix: X01DA-ES0-SL:TRXR
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
es0sl_trxw:
|
||||
readoutPriority: baseline
|
||||
description: End Station slits X-translation Wall-edge
|
||||
deviceClass: ophyd.EpicsMotor
|
||||
deviceConfig:
|
||||
prefix: X01DA-ES0-SL:TRXW
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
es0sl_tryb:
|
||||
readoutPriority: baseline
|
||||
description: End Station slits Y-translation Bottom-edge
|
||||
deviceClass: ophyd.EpicsMotor
|
||||
deviceConfig:
|
||||
prefix: X01DA-ES0-SL:TRYB
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
es0sl_tryt:
|
||||
readoutPriority: baseline
|
||||
description: End Station slits X-translation Top-edge
|
||||
deviceClass: ophyd.EpicsMotor
|
||||
deviceConfig:
|
||||
prefix: X01DA-ES0-SL:TRYT
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
|
||||
##############################################
|
||||
## End Station Slits -- Virtual positioners ##
|
||||
##############################################
|
||||
|
||||
es0sl_center:
|
||||
readoutPriority: baseline
|
||||
description: End Station slits X-center
|
||||
deviceClass: ophyd.EpicsMotor
|
||||
deviceConfig:
|
||||
prefix: X01DA-ES0-SL:CENTERX
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
es0sl_gapx:
|
||||
readoutPriority: baseline
|
||||
description: End Station slits X-gap
|
||||
deviceClass: ophyd.EpicsMotor
|
||||
deviceConfig:
|
||||
prefix: X01DA-ES0-SL:GAPX
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
es0sl_centery:
|
||||
readoutPriority: baseline
|
||||
description: End Station slits Y-center
|
||||
deviceClass: ophyd.EpicsMotor
|
||||
deviceConfig:
|
||||
prefix: X01DA-ES0-SL:CENTERY
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
es0sl_gapy:
|
||||
readoutPriority: baseline
|
||||
description: End Station slits Y-gap
|
||||
deviceClass: ophyd.EpicsMotor
|
||||
deviceConfig:
|
||||
prefix: X01DA-ES0-SL:GAPY
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
|
||||
#########################################################
|
||||
## Pinhole and alignment laser -- Physical Positioners ##
|
||||
#########################################################
|
||||
|
||||
es1pin_try:
|
||||
readoutPriority: baseline
|
||||
description: End Station pinhole and alignment laser Y-translation
|
||||
deviceClass: ophyd.EpicsMotor
|
||||
deviceConfig:
|
||||
prefix: X01DA-ES1-PIN1:TRY
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
es1pin_trx:
|
||||
readoutPriority: baseline
|
||||
description: End Station pinhole and alignment laser X-translation
|
||||
deviceClass: ophyd.EpicsMotor
|
||||
deviceConfig:
|
||||
prefix: X01DA-ES1-PIN1:TRX
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
es1pin_rotx:
|
||||
readoutPriority: baseline
|
||||
description: End Station pinhole and alignment laser X-rotation
|
||||
deviceClass: ophyd.EpicsMotor
|
||||
deviceConfig:
|
||||
prefix: X01DA-ES1-PIN1:ROTX
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
es1pin_roty:
|
||||
readoutPriority: baseline
|
||||
description: End Station pinhole and alignment laser Y-rotation
|
||||
deviceClass: ophyd.EpicsMotor
|
||||
deviceConfig:
|
||||
prefix: X01DA-ES1-PIN1:ROTY
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
|
||||
|
||||
################################################
|
||||
## Sample Manipulator -- Physical Positioners ##
|
||||
################################################
|
||||
|
||||
es1man_trx:
|
||||
readoutPriority: baseline
|
||||
description: End Station sample manipulator X-translation
|
||||
deviceClass: ophyd.EpicsMotor
|
||||
deviceConfig:
|
||||
prefix: X01DA-ES1-MAN1:TRX
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
es1man_try:
|
||||
readoutPriority: baseline
|
||||
description: End Station sample manipulator Y-translation
|
||||
deviceClass: ophyd.EpicsMotor
|
||||
deviceConfig:
|
||||
prefix: X01DA-ES1-MAN1:TRY
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
es1man_trz:
|
||||
readoutPriority: baseline
|
||||
description: End Station sample manipulator Z-translation
|
||||
deviceClass: ophyd.EpicsMotor
|
||||
deviceConfig:
|
||||
prefix: X01DA-ES1-MAN1:TRZ
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
es1man_roty:
|
||||
readoutPriority: baseline
|
||||
description: End Station sample manipulator Y-rotation
|
||||
deviceClass: ophyd.EpicsMotor
|
||||
deviceConfig:
|
||||
prefix: X01DA-ES1-MAN1:ROTY
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
|
||||
############################################
|
||||
## Segemented Arc -- Physical Positioners ##
|
||||
############################################
|
||||
|
||||
es1arc_roty:
|
||||
readoutPriority: baseline
|
||||
description: End Station segmented arc Y-rotation
|
||||
deviceClass: ophyd.EpicsMotor
|
||||
deviceConfig:
|
||||
prefix: X01DA-ES1-ARC:ROTY
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
es1det1_trx:
|
||||
readoutPriority: baseline
|
||||
description: End Station SDD 1 X-translation
|
||||
deviceClass: ophyd.EpicsMotor
|
||||
deviceConfig:
|
||||
prefix: X01DA-ES1-DET1:TRX
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
es1bm1_trx:
|
||||
readoutPriority: baseline
|
||||
description: End Station X-ray Eye X-translation
|
||||
deviceClass: ophyd.EpicsMotor
|
||||
deviceConfig:
|
||||
prefix: X01DA-ES1-BM1:TRX
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
es1det2_trx:
|
||||
readoutPriority: baseline
|
||||
description: End Station SDD 2 X-translation
|
||||
deviceClass: ophyd.EpicsMotor
|
||||
deviceConfig:
|
||||
prefix: X01DA-ES1-DET2:TRX
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
|
||||
#######################################
|
||||
## Beam Stop -- Physical Positioners ##
|
||||
#######################################
|
||||
|
||||
es2bs_trx:
|
||||
readoutPriority: baseline
|
||||
description: End Station beamstop X-translation
|
||||
deviceClass: ophyd.EpicsMotor
|
||||
deviceConfig:
|
||||
prefix: X01DA-ES2-BS:TRX
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
es2bs_try:
|
||||
readoutPriority: baseline
|
||||
description: End Station beamstop Y-translation
|
||||
deviceClass: ophyd.EpicsMotor
|
||||
deviceConfig:
|
||||
prefix: X01DA-ES2-BS:TRY
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
|
||||
##############################################
|
||||
## IC12 Manipulator -- Physical Positioners ##
|
||||
##############################################
|
||||
|
||||
es2ma2_try:
|
||||
readoutPriority: baseline
|
||||
description: End Station ionization chamber 1+2 Y-translation
|
||||
deviceClass: ophyd.EpicsMotor
|
||||
deviceConfig:
|
||||
prefix: X01DA-ES2-MA2:TRY
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
es2ma2_trz:
|
||||
readoutPriority: baseline
|
||||
description: End Station ionization chamber 1+2 Z-translation
|
||||
deviceClass: ophyd.EpicsMotor
|
||||
deviceConfig:
|
||||
prefix: X01DA-ES2-MA2:TRZ
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
|
||||
#######################################################
|
||||
## XRD Detector Manipulator -- Physical Positioners ##
|
||||
#######################################################
|
||||
|
||||
es2ma3_try:
|
||||
readoutPriority: baseline
|
||||
description: End Station XRD detector Y-translation
|
||||
deviceClass: ophyd.EpicsMotor
|
||||
deviceConfig:
|
||||
prefix: X01DA-ES2-MA3:TRY
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
@@ -0,0 +1,449 @@
|
||||
###################################
|
||||
## Optical Table ##
|
||||
###################################
|
||||
|
||||
ot_tryu:
|
||||
readoutPriority: baseline
|
||||
description: Optical Table Y-Translation Upstream
|
||||
deviceClass: ophyd_devices.EpicsMotorEC
|
||||
deviceConfig:
|
||||
prefix: X01DA-ES-OT:TRYU
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
|
||||
ot_tryd:
|
||||
readoutPriority: baseline
|
||||
description: Optical Table Y-Translation Downstream
|
||||
deviceClass: ophyd_devices.EpicsMotorEC
|
||||
deviceConfig:
|
||||
prefix: X01DA-ES-OT:TRYD
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
|
||||
ot_es1_trz:
|
||||
readoutPriority: baseline
|
||||
description: Optical Table ES1 Z-Translation
|
||||
deviceClass: ophyd_devices.EpicsMotorEC
|
||||
deviceConfig:
|
||||
prefix: X01DA-ES1-OT:TRZ
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
|
||||
ot_es2_trz:
|
||||
readoutPriority: baseline
|
||||
description: Optical Table ES2 Z-Translation
|
||||
deviceClass: ophyd_devices.EpicsMotorEC
|
||||
deviceConfig:
|
||||
prefix: X01DA-ES2-OT:TRZ
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
|
||||
ot_try:
|
||||
readoutPriority: baseline
|
||||
description: Optical Table Y-Translation
|
||||
deviceClass: ophyd_devices.EpicsMotorEC
|
||||
deviceConfig:
|
||||
prefix: X01DA-ES-OT:TRY
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
|
||||
ot_rotx:
|
||||
readoutPriority: baseline
|
||||
description: Optical Table Pitch
|
||||
deviceClass: ophyd_devices.EpicsMotorEC
|
||||
deviceConfig:
|
||||
prefix: X01DA-ES-OT:ROTX
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
|
||||
###################################
|
||||
## Exit Window ##
|
||||
###################################
|
||||
|
||||
es0wi_try:
|
||||
readoutPriority: baseline
|
||||
description: End Station 0 Exit Window Y-translation
|
||||
deviceClass: ophyd_devices.EpicsMotorEC
|
||||
deviceConfig:
|
||||
prefix: X01DA-ES0-WI:TRY
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
|
||||
###################################
|
||||
## ES0 Filter ##
|
||||
###################################
|
||||
|
||||
es0filter:
|
||||
readoutPriority: baseline
|
||||
description: ES0 filter station
|
||||
deviceClass: debye_bec.devices.es0filter.ES0Filter
|
||||
deviceConfig:
|
||||
prefix: "X01DA-ES0-FI:"
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
|
||||
###################################
|
||||
## Slits ES0 ##
|
||||
###################################
|
||||
|
||||
es0sl_trxr:
|
||||
readoutPriority: baseline
|
||||
description: End Station slits X-translation Ring-edge
|
||||
deviceClass: ophyd_devices.EpicsMotorEC
|
||||
deviceConfig:
|
||||
prefix: X01DA-ES0-SL:TRXR
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
|
||||
es0sl_trxw:
|
||||
readoutPriority: baseline
|
||||
description: End Station slits X-translation Wall-edge
|
||||
deviceClass: ophyd_devices.EpicsMotorEC
|
||||
deviceConfig:
|
||||
prefix: X01DA-ES0-SL:TRXW
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
|
||||
es0sl_tryb:
|
||||
readoutPriority: baseline
|
||||
description: End Station slits Y-translation Bottom-edge
|
||||
deviceClass: ophyd_devices.EpicsMotorEC
|
||||
deviceConfig:
|
||||
prefix: X01DA-ES0-SL:TRYB
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
|
||||
es0sl_tryt:
|
||||
readoutPriority: baseline
|
||||
description: End Station slits X-translation Top-edge
|
||||
deviceClass: ophyd_devices.EpicsMotorEC
|
||||
deviceConfig:
|
||||
prefix: X01DA-ES0-SL:TRYT
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
|
||||
es0sl_center:
|
||||
readoutPriority: baseline
|
||||
description: End Station slits X-center
|
||||
deviceClass: ophyd_devices.EpicsMotorEC
|
||||
deviceConfig:
|
||||
prefix: X01DA-ES0-SL:CENTERX
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
|
||||
es0sl_gapx:
|
||||
readoutPriority: baseline
|
||||
description: End Station slits X-gap
|
||||
deviceClass: ophyd_devices.EpicsMotorEC
|
||||
deviceConfig:
|
||||
prefix: X01DA-ES0-SL:GAPX
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
|
||||
es0sl_centery:
|
||||
readoutPriority: baseline
|
||||
description: End Station slits Y-center
|
||||
deviceClass: ophyd_devices.EpicsMotorEC
|
||||
deviceConfig:
|
||||
prefix: X01DA-ES0-SL:CENTERY
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
|
||||
es0sl_gapy:
|
||||
readoutPriority: baseline
|
||||
description: End Station slits Y-gap
|
||||
deviceClass: ophyd_devices.EpicsMotorEC
|
||||
deviceConfig:
|
||||
prefix: X01DA-ES0-SL:GAPY
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
|
||||
###################################
|
||||
## Alignment Laser ##
|
||||
###################################
|
||||
|
||||
es1_alignment_laser:
|
||||
readoutPriority: baseline
|
||||
description: ES1 alignment laser
|
||||
deviceClass: ophyd.EpicsSignal
|
||||
deviceConfig:
|
||||
read_pv: "X01DA-ES1-LAS:Relay"
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
|
||||
###################################
|
||||
## Sample Manipulator ##
|
||||
###################################
|
||||
|
||||
es1man_trx:
|
||||
readoutPriority: baseline
|
||||
description: End Station sample manipulator X-translation
|
||||
deviceClass: ophyd_devices.EpicsMotorEC
|
||||
deviceConfig:
|
||||
prefix: X01DA-ES1-MAN1:TRX
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
|
||||
es1man_try:
|
||||
readoutPriority: baseline
|
||||
description: End Station sample manipulator Y-translation
|
||||
deviceClass: ophyd_devices.EpicsMotorEC
|
||||
deviceConfig:
|
||||
prefix: X01DA-ES1-MAN1:TRY
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
|
||||
es1man_trz:
|
||||
readoutPriority: baseline
|
||||
description: End Station sample manipulator Z-translation
|
||||
deviceClass: ophyd_devices.EpicsMotorEC
|
||||
deviceConfig:
|
||||
prefix: X01DA-ES1-MAN1:TRZ
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
|
||||
es1man_roty:
|
||||
readoutPriority: baseline
|
||||
description: End Station sample manipulator Y-rotation
|
||||
deviceClass: ophyd_devices.EpicsMotorEC
|
||||
deviceConfig:
|
||||
prefix: X01DA-ES1-MAN1:ROTY
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
|
||||
###################################
|
||||
## Segmented Arc ##
|
||||
###################################
|
||||
|
||||
es1arc_roty:
|
||||
readoutPriority: baseline
|
||||
description: End Station segmented arc Y-rotation
|
||||
deviceClass: ophyd_devices.EpicsMotorEC
|
||||
deviceConfig:
|
||||
prefix: X01DA-ES1-ARC:ROTY
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
|
||||
es1det1_trx:
|
||||
readoutPriority: baseline
|
||||
description: End Station SDD 1 X-translation
|
||||
deviceClass: ophyd_devices.EpicsMotorEC
|
||||
deviceConfig:
|
||||
prefix: X01DA-ES1-DET1:TRX
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
|
||||
es1bm1_trx:
|
||||
readoutPriority: baseline
|
||||
description: End Station X-ray Eye X-translation
|
||||
deviceClass: ophyd_devices.EpicsMotorEC
|
||||
deviceConfig:
|
||||
prefix: X01DA-ES1-BM1:TRX
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
|
||||
es1det2_trx:
|
||||
readoutPriority: baseline
|
||||
description: End Station SDD 2 X-translation
|
||||
deviceClass: ophyd_devices.EpicsMotorEC
|
||||
deviceConfig:
|
||||
prefix: X01DA-ES1-DET2:TRX
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
|
||||
###################################
|
||||
## IC1 + IC2 Manipulator ##
|
||||
###################################
|
||||
|
||||
es2ma2_try:
|
||||
readoutPriority: baseline
|
||||
description: End Station ionization chamber 1+2 Y-translation
|
||||
deviceClass: ophyd_devices.EpicsMotorEC
|
||||
deviceConfig:
|
||||
prefix: X01DA-ES2-MA2:TRY
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
|
||||
es2ma2_trz:
|
||||
readoutPriority: baseline
|
||||
description: End Station ionization chamber 1+2 Z-translation
|
||||
deviceClass: ophyd_devices.EpicsMotorEC
|
||||
deviceConfig:
|
||||
prefix: X01DA-ES2-MA2:TRZ
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
|
||||
###################################
|
||||
## XRD Detector Manipulator ##
|
||||
###################################
|
||||
|
||||
es2ma3_try:
|
||||
readoutPriority: baseline
|
||||
description: End Station XRD detector Y-translation
|
||||
deviceClass: ophyd_devices.EpicsMotorEC
|
||||
deviceConfig:
|
||||
prefix: X01DA-ES2-MA3:TRY
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
|
||||
###################################
|
||||
## Hutch Env. Sensors + Light ##
|
||||
###################################
|
||||
|
||||
es_temperature1:
|
||||
readoutPriority: baseline
|
||||
description: ES temperature sensor 1
|
||||
deviceClass: ophyd.EpicsSignalRO
|
||||
deviceConfig:
|
||||
read_pv: "X01DA-PC-I2C:_CH1:TEMP"
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
|
||||
es_humidity1:
|
||||
readoutPriority: baseline
|
||||
description: ES humidity sensor 1
|
||||
deviceClass: ophyd.EpicsSignalRO
|
||||
deviceConfig:
|
||||
read_pv: "X01DA-PC-I2C:_CH1:HUMIREL"
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
|
||||
es_pressure1:
|
||||
readoutPriority: baseline
|
||||
description: ES ambient pressure sensor 1
|
||||
deviceClass: ophyd.EpicsSignalRO
|
||||
deviceConfig:
|
||||
read_pv: "X01DA-PC-I2C:_CH1:PRES"
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
|
||||
es_temperature2:
|
||||
readoutPriority: baseline
|
||||
description: ES temperature sensor 2
|
||||
deviceClass: ophyd.EpicsSignalRO
|
||||
deviceConfig:
|
||||
read_pv: "X01DA-PC-I2C:_CH2:TEMP"
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
|
||||
es_humidity2:
|
||||
readoutPriority: baseline
|
||||
description: ES humidity sensor 2
|
||||
deviceClass: ophyd.EpicsSignalRO
|
||||
deviceConfig:
|
||||
read_pv: "X01DA-PC-I2C:_CH2:HUMIREL"
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
|
||||
es_pressure2:
|
||||
readoutPriority: baseline
|
||||
description: ES ambient pressure sensor 2
|
||||
deviceClass: ophyd.EpicsSignalRO
|
||||
deviceConfig:
|
||||
read_pv: "X01DA-PC-I2C:_CH2:PRES"
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
|
||||
es_light_toggle:
|
||||
readoutPriority: baseline
|
||||
description: ES light toggle
|
||||
deviceClass: ophyd.EpicsSignal
|
||||
deviceConfig:
|
||||
read_pv: "X01DA-EH-LIGHT:TOGGLE"
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
|
||||
es_gas_sensor_o2:
|
||||
readoutPriority: baseline
|
||||
description: ES Gas Sensor O2
|
||||
deviceClass: ophyd.EpicsSignalRO
|
||||
deviceConfig:
|
||||
read_pv: "X01DA-KIMESSA2:EH-O2"
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
|
||||
es_gas_sensor_h2s:
|
||||
readoutPriority: baseline
|
||||
description: ES Gas Sensor H2S
|
||||
deviceClass: ophyd.EpicsSignalRO
|
||||
deviceConfig:
|
||||
read_pv: "X01DA-KIMESSA2:EH-H2S"
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
|
||||
es_gas_sensor_no2:
|
||||
readoutPriority: baseline
|
||||
description: ES Gas Sensor NO2
|
||||
deviceClass: ophyd.EpicsSignalRO
|
||||
deviceConfig:
|
||||
read_pv: "X01DA-KIMESSA2:EH-NO2"
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
|
||||
es_gas_sensor_co:
|
||||
readoutPriority: baseline
|
||||
description: ES Gas Sensor CO
|
||||
deviceClass: ophyd.EpicsSignalRO
|
||||
deviceConfig:
|
||||
read_pv: "X01DA-KIMESSA2:EH-CO"
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
|
||||
es_gas_sensor_h2:
|
||||
readoutPriority: baseline
|
||||
description: ES Gas Sensor H2
|
||||
deviceClass: ophyd.EpicsSignalRO
|
||||
deviceConfig:
|
||||
read_pv: "X01DA-KIMESSA2:EH-H2"
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
|
||||
es_gas_sensor_nh3:
|
||||
readoutPriority: baseline
|
||||
description: ES Gas Sensor NH3
|
||||
deviceClass: ophyd.EpicsSignalRO
|
||||
deviceConfig:
|
||||
read_pv: "X01DA-KIMESSA2:EH-NH3"
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
@@ -0,0 +1,243 @@
|
||||
|
||||
###################################
|
||||
## Frontend Absorber ##
|
||||
###################################
|
||||
|
||||
abs:
|
||||
readoutPriority: baseline
|
||||
description: Frontend Absorber
|
||||
deviceClass: debye_bec.devices.absorber.Absorber
|
||||
deviceConfig:
|
||||
prefix: "X01DA-FE-ABS1:"
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
|
||||
###################################
|
||||
## Frontend Slits ##
|
||||
###################################
|
||||
|
||||
sldi_trxr:
|
||||
readoutPriority: baseline
|
||||
description: Front-end slit diaphragm X-translation Ring-edge
|
||||
deviceClass: ophyd_devices.EpicsMotorEC
|
||||
deviceConfig:
|
||||
prefix: X01DA-FE-SLDI:TRXR
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
|
||||
sldi_trxw:
|
||||
readoutPriority: baseline
|
||||
description: Front-end slit diaphragm X-translation Wall-edge
|
||||
deviceClass: ophyd_devices.EpicsMotorEC
|
||||
deviceConfig:
|
||||
prefix: X01DA-FE-SLDI:TRXW
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
|
||||
sldi_tryb:
|
||||
readoutPriority: baseline
|
||||
description: Front-end slit diaphragm Y-translation Bottom-edge
|
||||
deviceClass: ophyd_devices.EpicsMotorEC
|
||||
deviceConfig:
|
||||
prefix: X01DA-FE-SLDI:TRYB
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
|
||||
sldi_tryt:
|
||||
readoutPriority: baseline
|
||||
description: Front-end slit diaphragm X-translation Top-edge
|
||||
deviceClass: ophyd_devices.EpicsMotorEC
|
||||
deviceConfig:
|
||||
prefix: X01DA-FE-SLDI:TRYT
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
|
||||
sldi_centerx:
|
||||
readoutPriority: baseline
|
||||
description: Front-end slit diaphragm X-center
|
||||
deviceClass: ophyd_devices.EpicsMotorEC
|
||||
deviceConfig:
|
||||
prefix: X01DA-FE-SLDI:CENTERX
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
|
||||
sldi_gapx:
|
||||
readoutPriority: baseline
|
||||
description: Front-end slit diaphragm X-gap
|
||||
deviceClass: ophyd_devices.EpicsMotorEC
|
||||
deviceConfig:
|
||||
prefix: X01DA-FE-SLDI:GAPX
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
|
||||
sldi_centery:
|
||||
readoutPriority: baseline
|
||||
description: Front-end slit diaphragm Y-center
|
||||
deviceClass: ophyd_devices.EpicsMotorEC
|
||||
deviceConfig:
|
||||
prefix: X01DA-FE-SLDI:CENTERY
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
|
||||
sldi_gapy:
|
||||
readoutPriority: baseline
|
||||
description: Front-end slit diaphragm Y-gap
|
||||
deviceClass: ophyd_devices.EpicsMotorEC
|
||||
deviceConfig:
|
||||
prefix: X01DA-FE-SLDI:GAPY
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
|
||||
###################################
|
||||
## Collimating Mirror ##
|
||||
###################################
|
||||
|
||||
cm_trxu:
|
||||
readoutPriority: baseline
|
||||
description: Collimating Mirror X-translation upstream
|
||||
deviceClass: ophyd_devices.EpicsMotorEC
|
||||
deviceConfig:
|
||||
prefix: X01DA-FE-CM:TRXU
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
|
||||
cm_trxd:
|
||||
readoutPriority: baseline
|
||||
description: Collimating Mirror X-translation downstream
|
||||
deviceClass: ophyd_devices.EpicsMotorEC
|
||||
deviceConfig:
|
||||
prefix: X01DA-FE-CM:TRXD
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
|
||||
cm_tryu:
|
||||
readoutPriority: baseline
|
||||
description: Collimating Mirror Y-translation upstream
|
||||
deviceClass: ophyd_devices.EpicsMotorEC
|
||||
deviceConfig:
|
||||
prefix: X01DA-FE-CM:TRYU
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
|
||||
cm_trydr:
|
||||
readoutPriority: baseline
|
||||
description: Collimating Mirror Y-translation downstream ring
|
||||
deviceClass: ophyd_devices.EpicsMotorEC
|
||||
deviceConfig:
|
||||
prefix: X01DA-FE-CM:TRYDR
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
|
||||
cm_trydw:
|
||||
readoutPriority: baseline
|
||||
description: Collimating Mirror Y-translation downstream wall
|
||||
deviceClass: ophyd_devices.EpicsMotorEC
|
||||
deviceConfig:
|
||||
prefix: X01DA-FE-CM:TRYDW
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
|
||||
cm_bnd:
|
||||
readoutPriority: baseline
|
||||
description: Collimating Mirror bender
|
||||
deviceClass: ophyd_devices.EpicsMotorEC
|
||||
deviceConfig:
|
||||
prefix: X01DA-FE-CM:BND
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
|
||||
cm_bnd_radius:
|
||||
readoutPriority: baseline
|
||||
description: Collimating Mirror Bending Radius
|
||||
deviceClass: ophyd.EpicsSignalRO
|
||||
deviceConfig:
|
||||
read_pv: X01DA-CPCL-CM:BNDFORCE
|
||||
onFailure: retry
|
||||
readOnly: true
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
|
||||
cm_rotx:
|
||||
readoutPriority: baseline
|
||||
description: Collimating Morror Pitch
|
||||
deviceClass: ophyd_devices.EpicsMotorEC
|
||||
deviceConfig:
|
||||
prefix: X01DA-FE-CM:ROTX
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
|
||||
cm_roty:
|
||||
readoutPriority: baseline
|
||||
description: Collimating Morror Yaw
|
||||
deviceClass: ophyd_devices.EpicsMotorEC
|
||||
deviceConfig:
|
||||
prefix: X01DA-FE-CM:ROTY
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
|
||||
cm_rotz:
|
||||
readoutPriority: baseline
|
||||
description: Collimating Morror Roll
|
||||
deviceClass: ophyd_devices.EpicsMotorEC
|
||||
deviceConfig:
|
||||
prefix: X01DA-FE-CM:ROTZ
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
|
||||
cm_trx:
|
||||
readoutPriority: baseline
|
||||
description: Collimating Morror Center Point X
|
||||
deviceClass: ophyd_devices.EpicsMotorEC
|
||||
deviceConfig:
|
||||
prefix: X01DA-FE-CM:XTCP
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
|
||||
cm_try:
|
||||
readoutPriority: baseline
|
||||
description: Collimating Morror Center Point Y
|
||||
deviceClass: ophyd_devices.EpicsMotorEC
|
||||
deviceConfig:
|
||||
prefix: X01DA-FE-CM:YTCP
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
|
||||
cm_ztcp:
|
||||
readoutPriority: baseline
|
||||
description: Collimating Morror Center Point Z
|
||||
deviceClass: ophyd_devices.EpicsMotorEC
|
||||
deviceConfig:
|
||||
prefix: X01DA-FE-CM:ZTCP
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
|
||||
cm_xstripe:
|
||||
readoutPriority: baseline
|
||||
description: Collimating Morror X Stripe
|
||||
deviceClass: ophyd_devices.EpicsMotorEC
|
||||
deviceConfig:
|
||||
prefix: X01DA-FE-CM:XSTRIPE
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
@@ -0,0 +1,34 @@
|
||||
|
||||
###################################
|
||||
## Hutch Cameras ##
|
||||
###################################
|
||||
|
||||
hutch_cam_1:
|
||||
readoutPriority: baseline
|
||||
description: Hutch Camera 1
|
||||
deviceClass: debye_bec.devices.cameras.hutch_cam.HutchCam
|
||||
deviceConfig:
|
||||
prefix: "pcp085420"
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
|
||||
hutch_cam_2:
|
||||
readoutPriority: baseline
|
||||
description: Hutch Camera 2
|
||||
deviceClass: debye_bec.devices.cameras.hutch_cam.HutchCam
|
||||
deviceConfig:
|
||||
prefix: "pcp085436"
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
|
||||
hutch_cam_3:
|
||||
readoutPriority: baseline
|
||||
description: Hutch Camera 3
|
||||
deviceClass: debye_bec.devices.cameras.hutch_cam.HutchCam
|
||||
deviceConfig:
|
||||
prefix: "pcp085435"
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
@@ -0,0 +1,18 @@
|
||||
|
||||
###################################
|
||||
## SLS Machine ##
|
||||
###################################
|
||||
|
||||
curr:
|
||||
readoutPriority: baseline
|
||||
description: SLS ring current
|
||||
deviceClass: ophyd.EpicsSignalRO
|
||||
deviceConfig:
|
||||
auto_monitor: true
|
||||
read_pv: AGEBD-DBPM3CURR:CURRENT-AVG
|
||||
deviceTags:
|
||||
- machine
|
||||
onFailure: buffer
|
||||
enabled: true
|
||||
readOnly: true
|
||||
softwareTrigger: false
|
||||
@@ -0,0 +1,411 @@
|
||||
|
||||
###################################
|
||||
## Monochromator ##
|
||||
###################################
|
||||
|
||||
mo1_try:
|
||||
readoutPriority: baseline
|
||||
description: Monochromator Y Translation
|
||||
deviceClass: ophyd_devices.EpicsMotorEC
|
||||
deviceConfig:
|
||||
prefix: X01DA-OP-MO1:TRY
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
|
||||
mo1_trx:
|
||||
readoutPriority: baseline
|
||||
description: Monochromator X Translation
|
||||
deviceClass: ophyd_devices.EpicsMotorEC
|
||||
deviceConfig:
|
||||
prefix: X01DA-OP-MO1:TRX
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
|
||||
mo1_roty:
|
||||
readoutPriority: baseline
|
||||
description: Monochromator Yaw
|
||||
deviceClass: ophyd_devices.EpicsMotorEC
|
||||
deviceConfig:
|
||||
prefix: X01DA-OP-MO1:ROTY
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
|
||||
###################################
|
||||
## Optics Slits + Beam Monitor 1 ##
|
||||
###################################
|
||||
|
||||
sl1_trxr:
|
||||
readoutPriority: baseline
|
||||
description: Optics slits 1 X-translation Ring-edge
|
||||
deviceClass: ophyd_devices.EpicsMotorEC
|
||||
deviceConfig:
|
||||
prefix: X01DA-OP-SL1:TRXR
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
deviceTags:
|
||||
- optics
|
||||
- slits
|
||||
|
||||
sl1_trxw:
|
||||
readoutPriority: baseline
|
||||
description: Optics slits 1 X-translation Wall-edge
|
||||
deviceClass: ophyd_devices.EpicsMotorEC
|
||||
deviceConfig:
|
||||
prefix: X01DA-OP-SL1:TRXW
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
deviceTags:
|
||||
- optics
|
||||
- slits
|
||||
|
||||
sl1_tryb:
|
||||
readoutPriority: baseline
|
||||
description: Optics slits 1 Y-translation Bottom-edge
|
||||
deviceClass: ophyd_devices.EpicsMotorEC
|
||||
deviceConfig:
|
||||
prefix: X01DA-OP-SL1:TRYB
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
deviceTags:
|
||||
- optics
|
||||
- slits
|
||||
|
||||
sl1_tryt:
|
||||
readoutPriority: baseline
|
||||
description: Optics slits 1 X-translation Top-edge
|
||||
deviceClass: ophyd_devices.EpicsMotorEC
|
||||
deviceConfig:
|
||||
prefix: X01DA-OP-SL1:TRYT
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
deviceTags:
|
||||
- optics
|
||||
- slits
|
||||
|
||||
bm1_try:
|
||||
readoutPriority: baseline
|
||||
description: Beam Monitor 1 Y-translation
|
||||
deviceClass: ophyd_devices.EpicsMotorEC
|
||||
deviceConfig:
|
||||
prefix: X01DA-OP-BM1:TRY
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
deviceTags:
|
||||
- optics
|
||||
- slits
|
||||
|
||||
sl1_centerx:
|
||||
readoutPriority: baseline
|
||||
description: Optics slits 1 X-center
|
||||
deviceClass: ophyd_devices.EpicsMotorEC
|
||||
deviceConfig:
|
||||
prefix: X01DA-OP-SL1:CENTERX
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
deviceTags:
|
||||
- optics
|
||||
- slits
|
||||
|
||||
sl1_gapx:
|
||||
readoutPriority: baseline
|
||||
description: Optics slits 1 X-gap
|
||||
deviceClass: ophyd_devices.EpicsMotorEC
|
||||
deviceConfig:
|
||||
prefix: X01DA-OP-SL1:GAPX
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
deviceTags:
|
||||
- optics
|
||||
- slits
|
||||
|
||||
sl1_centery:
|
||||
readoutPriority: baseline
|
||||
description: Optics slits 1 Y-center
|
||||
deviceClass: ophyd_devices.EpicsMotorEC
|
||||
deviceConfig:
|
||||
prefix: X01DA-OP-SL1:CENTERY
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
deviceTags:
|
||||
- optics
|
||||
- slits
|
||||
|
||||
sl1_gapy:
|
||||
readoutPriority: baseline
|
||||
description: Optics slits 1 Y-gap
|
||||
deviceClass: ophyd_devices.EpicsMotorEC
|
||||
deviceConfig:
|
||||
prefix: X01DA-OP-SL1:GAPY
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
deviceTags:
|
||||
- optics
|
||||
- slits
|
||||
|
||||
###################################
|
||||
## Focusing Mirror ##
|
||||
###################################
|
||||
|
||||
fm_trxu:
|
||||
readoutPriority: baseline
|
||||
description: Focusing Mirror X-translation upstream
|
||||
deviceClass: ophyd_devices.EpicsMotorEC
|
||||
deviceConfig:
|
||||
prefix: X01DA-OP-FM:TRXU
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
|
||||
fm_trxd:
|
||||
readoutPriority: baseline
|
||||
description: Focusing Mirror X-translation downstream
|
||||
deviceClass: ophyd_devices.EpicsMotorEC
|
||||
deviceConfig:
|
||||
prefix: X01DA-OP-FM:TRXD
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
|
||||
fm_tryd:
|
||||
readoutPriority: baseline
|
||||
description: Focusing Mirror Y-translation downstream
|
||||
deviceClass: ophyd_devices.EpicsMotorEC
|
||||
deviceConfig:
|
||||
prefix: X01DA-OP-FM:TRYD
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
|
||||
fm_tryur:
|
||||
readoutPriority: baseline
|
||||
description: Focusing Mirror Y-translation upstream ring
|
||||
deviceClass: ophyd_devices.EpicsMotorEC
|
||||
deviceConfig:
|
||||
prefix: X01DA-OP-FM:TRYUR
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
|
||||
fm_tryuw:
|
||||
readoutPriority: baseline
|
||||
description: Focusing Mirror Y-translation upstream wall
|
||||
deviceClass: ophyd_devices.EpicsMotorEC
|
||||
deviceConfig:
|
||||
prefix: X01DA-OP-FM:TRYUW
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
|
||||
fm_bnd:
|
||||
readoutPriority: baseline
|
||||
description: Focusing Mirror bender
|
||||
deviceClass: ophyd_devices.EpicsMotorEC
|
||||
deviceConfig:
|
||||
prefix: X01DA-OP-FM:BND
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
|
||||
fm_bnd_radius:
|
||||
readoutPriority: baseline
|
||||
description: Focusing Mirror Bending Radius
|
||||
deviceClass: ophyd.EpicsSignalRO
|
||||
deviceConfig:
|
||||
read_pv: X01DA-CPCL-FM:BNDFORCE
|
||||
onFailure: retry
|
||||
readOnly: true
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
|
||||
fm_rotx:
|
||||
readoutPriority: baseline
|
||||
description: Focusing Morror Pitch
|
||||
deviceClass: ophyd_devices.EpicsMotorEC
|
||||
deviceConfig:
|
||||
prefix: X01DA-OP-FM:ROTX
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
|
||||
fm_roty:
|
||||
readoutPriority: baseline
|
||||
description: Focusing Morror Yaw
|
||||
deviceClass: ophyd_devices.EpicsMotorEC
|
||||
deviceConfig:
|
||||
prefix: X01DA-OP-FM:ROTY
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
|
||||
fm_rotz:
|
||||
readoutPriority: baseline
|
||||
description: Focusing Morror Roll
|
||||
deviceClass: ophyd_devices.EpicsMotorEC
|
||||
deviceConfig:
|
||||
prefix: X01DA-OP-FM:ROTZ
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
|
||||
fm_trx:
|
||||
readoutPriority: baseline
|
||||
description: Focusing Morror Center Point X
|
||||
deviceClass: ophyd_devices.EpicsMotorEC
|
||||
deviceConfig:
|
||||
prefix: X01DA-OP-FM:XTCP
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
|
||||
fm_try:
|
||||
readoutPriority: baseline
|
||||
description: Focusing Morror Center Point Y
|
||||
deviceClass: ophyd_devices.EpicsMotorEC
|
||||
deviceConfig:
|
||||
prefix: X01DA-OP-FM:YTCP
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
|
||||
fm_ztcp:
|
||||
readoutPriority: baseline
|
||||
description: Focusing Morror Center Point Z
|
||||
deviceClass: ophyd_devices.EpicsMotorEC
|
||||
deviceConfig:
|
||||
prefix: X01DA-OP-FM:ZTCP
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
|
||||
###################################
|
||||
## Optics Slits + Beam Monitor 2 ##
|
||||
###################################
|
||||
|
||||
sl2_trxr:
|
||||
readoutPriority: baseline
|
||||
description: Optics slits 2 X-translation Ring-edge
|
||||
deviceClass: ophyd_devices.EpicsMotorEC
|
||||
deviceConfig:
|
||||
prefix: X01DA-OP-SL2:TRXR
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
deviceTags:
|
||||
- optics
|
||||
- slits
|
||||
|
||||
sl2_trxw:
|
||||
readoutPriority: baseline
|
||||
description: Optics slits 2 X-translation Wall-edge
|
||||
deviceClass: ophyd_devices.EpicsMotorEC
|
||||
deviceConfig:
|
||||
prefix: X01DA-OP-SL2:TRXW
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
deviceTags:
|
||||
- optics
|
||||
- slits
|
||||
|
||||
sl2_tryb:
|
||||
readoutPriority: baseline
|
||||
description: Optics slits 2 Y-translation Bottom-edge
|
||||
deviceClass: ophyd_devices.EpicsMotorEC
|
||||
deviceConfig:
|
||||
prefix: X01DA-OP-SL2:TRYB
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
deviceTags:
|
||||
- optics
|
||||
- slits
|
||||
|
||||
sl2_tryt:
|
||||
readoutPriority: baseline
|
||||
description: Optics slits 2 X-translation Top-edge
|
||||
deviceClass: ophyd_devices.EpicsMotorEC
|
||||
deviceConfig:
|
||||
prefix: X01DA-OP-SL2:TRYT
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
deviceTags:
|
||||
- optics
|
||||
- slits
|
||||
|
||||
bm2_try:
|
||||
readoutPriority: baseline
|
||||
description: Beam Monitor 2 Y-translation
|
||||
deviceClass: ophyd_devices.EpicsMotorEC
|
||||
deviceConfig:
|
||||
prefix: X01DA-OP-BM2:TRY
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
deviceTags:
|
||||
- optics
|
||||
- slits
|
||||
|
||||
sl2_centerx:
|
||||
readoutPriority: baseline
|
||||
description: Optics slits 2 X-center
|
||||
deviceClass: ophyd_devices.EpicsMotorEC
|
||||
deviceConfig:
|
||||
prefix: X01DA-OP-SL2:CENTERX
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
deviceTags:
|
||||
- optics
|
||||
- slits
|
||||
|
||||
sl2_gapx:
|
||||
readoutPriority: baseline
|
||||
description: Optics slits 2 X-gap
|
||||
deviceClass: ophyd_devices.EpicsMotorEC
|
||||
deviceConfig:
|
||||
prefix: X01DA-OP-SL2:GAPX
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
deviceTags:
|
||||
- optics
|
||||
- slits
|
||||
|
||||
sl2_centery:
|
||||
readoutPriority: baseline
|
||||
description: Optics slits 2 Y-center
|
||||
deviceClass: ophyd_devices.EpicsMotorEC
|
||||
deviceConfig:
|
||||
prefix: X01DA-OP-SL2:CENTERY
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
deviceTags:
|
||||
- optics
|
||||
- slits
|
||||
|
||||
sl2_gapy:
|
||||
readoutPriority: baseline
|
||||
description: Optics slits 2 Y-gap
|
||||
deviceClass: ophyd_devices.EpicsMotorEC
|
||||
deviceConfig:
|
||||
prefix: X01DA-OP-SL2:GAPY
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
deviceTags:
|
||||
- optics
|
||||
- slits
|
||||
@@ -0,0 +1,80 @@
|
||||
|
||||
###################################
|
||||
## General ##
|
||||
###################################
|
||||
|
||||
## SLS Machine
|
||||
machine_config:
|
||||
- !include ./x01da_machine.yaml
|
||||
|
||||
## Beam Monitors OP + EH
|
||||
beam_monitors_config:
|
||||
- !include ./x01da_beam_monitors.yaml
|
||||
|
||||
###################################
|
||||
## Frontend ##
|
||||
###################################
|
||||
|
||||
## Frontend
|
||||
frontend_config:
|
||||
- !include ./x01da_frontend.yaml
|
||||
|
||||
###################################
|
||||
## Optics Hutch ##
|
||||
###################################
|
||||
|
||||
## Bragg Monochromator
|
||||
mo1_bragg:
|
||||
readoutPriority: baseline
|
||||
description: Positioner for the Monochromator
|
||||
deviceClass: debye_bec.devices.mo1_bragg.mo1_bragg.Mo1Bragg
|
||||
deviceConfig:
|
||||
prefix: "X01DA-OP-MO1:BRAGG:"
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
mo1_bragg_angle:
|
||||
readoutPriority: baseline
|
||||
description: Positioner for the Monochromator
|
||||
deviceClass: debye_bec.devices.mo1_bragg.mo1_bragg_angle.Mo1BraggAngle
|
||||
deviceConfig:
|
||||
prefix: "X01DA-OP-MO1:BRAGG:"
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
|
||||
## Remaining optics hutch
|
||||
optics_config:
|
||||
- !include ./x01da_optics.yaml
|
||||
|
||||
###################################
|
||||
## Experimental Hutch ##
|
||||
###################################
|
||||
|
||||
# ## NIDAQ
|
||||
nidaq:
|
||||
readoutPriority: monitored
|
||||
description: NIDAQ backend for data reading for debye scans
|
||||
deviceClass: debye_bec.devices.nidaq.nidaq.Nidaq
|
||||
deviceConfig:
|
||||
prefix: "X01DA-PC-SCANSERVER:"
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
|
||||
## XAS (ICx, SDD, ref foils)
|
||||
xas_config:
|
||||
- !include ./x01da_xas.yaml
|
||||
|
||||
## XRD (Pilatus, pinhole, beamstop)
|
||||
#xrd_config:
|
||||
# - !include ./x01da_xrd.yaml
|
||||
|
||||
# Commented out because too slow
|
||||
## Hutch cameras
|
||||
# hutch_cams:
|
||||
# - !include ./x01da_hutch_cameras.yaml
|
||||
|
||||
## Remaining experimental hutch
|
||||
es_config:
|
||||
- !include ./x01da_experimental_hutch.yaml
|
||||
@@ -1,474 +0,0 @@
|
||||
|
||||
## Slit Diaphragm -- Physical positioners
|
||||
sldi_trxr:
|
||||
readoutPriority: baseline
|
||||
description: Front-end slit diaphragm X-translation Ring-edge
|
||||
deviceClass: ophyd.EpicsMotor
|
||||
deviceConfig:
|
||||
prefix: X01DA-FE-SLDI:TRXR
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
sldi_trxw:
|
||||
readoutPriority: baseline
|
||||
description: Front-end slit diaphragm X-translation Wall-edge
|
||||
deviceClass: ophyd.EpicsMotor
|
||||
deviceConfig:
|
||||
prefix: X01DA-FE-SLDI:TRXW
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
sldi_tryb:
|
||||
readoutPriority: baseline
|
||||
description: Front-end slit diaphragm Y-translation Bottom-edge
|
||||
deviceClass: ophyd.EpicsMotor
|
||||
deviceConfig:
|
||||
prefix: X01DA-FE-SLDI:TRYB
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
sldi_tryt:
|
||||
readoutPriority: baseline
|
||||
description: Front-end slit diaphragm X-translation Top-edge
|
||||
deviceClass: ophyd.EpicsMotor
|
||||
deviceConfig:
|
||||
prefix: X01DA-FE-SLDI:TRYT
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
|
||||
## Slit Diaphragm -- Virtual positioners
|
||||
|
||||
sldi_centerx:
|
||||
readoutPriority: baseline
|
||||
description: Front-end slit diaphragm X-center
|
||||
deviceClass: ophyd.EpicsMotor
|
||||
deviceConfig:
|
||||
prefix: X01DA-FE-SLDI:CENTERX
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
sldi_gapx:
|
||||
readoutPriority: baseline
|
||||
description: Front-end slit diaphragm X-gap
|
||||
deviceClass: ophyd.EpicsMotor
|
||||
deviceConfig:
|
||||
prefix: X01DA-FE-SLDI:GAPX
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
sldi_centery:
|
||||
readoutPriority: baseline
|
||||
description: Front-end slit diaphragm Y-center
|
||||
deviceClass: ophyd.EpicsMotor
|
||||
deviceConfig:
|
||||
prefix: X01DA-FE-SLDI:CENTERY
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
sldi_gapy:
|
||||
readoutPriority: baseline
|
||||
description: Front-end slit diaphragm Y-gap
|
||||
deviceClass: ophyd.EpicsMotor
|
||||
deviceConfig:
|
||||
prefix: X01DA-FE-SLDI:GAPY
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
|
||||
|
||||
## Collimating Mirror -- Physical Positioners
|
||||
|
||||
cm_trxu:
|
||||
readoutPriority: baseline
|
||||
description: Collimating Mirror X-translation upstream
|
||||
deviceClass: ophyd.EpicsMotor
|
||||
deviceConfig:
|
||||
prefix: X01DA-FE-CM:TRXU
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
cm_trxd:
|
||||
readoutPriority: baseline
|
||||
description: Collimating Mirror X-translation downstream
|
||||
deviceClass: ophyd.EpicsMotor
|
||||
deviceConfig:
|
||||
prefix: X01DA-FE-CM:TRXD
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
cm_tryu:
|
||||
readoutPriority: baseline
|
||||
description: Collimating Mirror Y-translation upstream
|
||||
deviceClass: ophyd.EpicsMotor
|
||||
deviceConfig:
|
||||
prefix: X01DA-FE-CM:TRYU
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
cm_trydr:
|
||||
readoutPriority: baseline
|
||||
description: Collimating Mirror Y-translation downstream ring
|
||||
deviceClass: ophyd.EpicsMotor
|
||||
deviceConfig:
|
||||
prefix: X01DA-FE-CM:TRYDR
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
cm_trydw:
|
||||
readoutPriority: baseline
|
||||
description: Collimating Mirror Y-translation downstream wall
|
||||
deviceClass: ophyd.EpicsMotor
|
||||
deviceConfig:
|
||||
prefix: X01DA-FE-CM:TRYDW
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
cm_bnd:
|
||||
readoutPriority: baseline
|
||||
description: Collimating Mirror bender
|
||||
deviceClass: ophyd.EpicsMotor
|
||||
deviceConfig:
|
||||
prefix: X01DA-FE-CM:BND
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
|
||||
## Collimating Mirror -- Virtual Positioners
|
||||
|
||||
cm_rotx:
|
||||
readoutPriority: baseline
|
||||
description: Collimating Morror Pitch
|
||||
deviceClass: ophyd.EpicsMotor
|
||||
deviceConfig:
|
||||
prefix: X01DA-FE-CM:ROTX
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
cm_roty:
|
||||
readoutPriority: baseline
|
||||
description: Collimating Morror Yaw
|
||||
deviceClass: ophyd.EpicsMotor
|
||||
deviceConfig:
|
||||
prefix: X01DA-FE-CM:ROTY
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
cm_rotz:
|
||||
readoutPriority: baseline
|
||||
description: Collimating Morror Roll
|
||||
deviceClass: ophyd.EpicsMotor
|
||||
deviceConfig:
|
||||
prefix: X01DA-FE-CM:ROTZ
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
cm_trx:
|
||||
readoutPriority: baseline
|
||||
description: Collimating Morror Center Point X
|
||||
deviceClass: ophyd.EpicsMotor
|
||||
deviceConfig:
|
||||
prefix: X01DA-FE-CM:XTCP
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
cm_try:
|
||||
readoutPriority: baseline
|
||||
description: Collimating Morror Center Point Y
|
||||
deviceClass: ophyd.EpicsMotor
|
||||
deviceConfig:
|
||||
prefix: X01DA-FE-CM:YTCP
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
cm_ztcp:
|
||||
readoutPriority: baseline
|
||||
description: Collimating Morror Center Point Z
|
||||
deviceClass: ophyd.EpicsMotor
|
||||
deviceConfig:
|
||||
prefix: X01DA-FE-CM:ZTCP
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
cm_xstripe:
|
||||
readoutPriority: baseline
|
||||
description: Collimating Morror X Stripe
|
||||
deviceClass: ophyd.EpicsMotor
|
||||
deviceConfig:
|
||||
prefix: X01DA-FE-CM:XSTRIPE
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
|
||||
## Bragg Monochromator
|
||||
mo1_bragg:
|
||||
readoutPriority: baseline
|
||||
description: Positioner for the Monochromator
|
||||
deviceClass: debye_bec.devices.mo1_bragg.mo1_bragg.Mo1Bragg
|
||||
deviceConfig:
|
||||
prefix: "X01DA-OP-MO1:BRAGG:"
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
dummy_pv:
|
||||
readoutPriority: monitored
|
||||
description: Heartbeat of Bragg
|
||||
deviceClass: ophyd.EpicsSignalRO
|
||||
deviceConfig:
|
||||
read_pv: "X01DA-OP-MO1:BRAGG:heartbeat_RBV"
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
|
||||
# NIDAQ
|
||||
nidaq:
|
||||
readoutPriority: monitored
|
||||
description: NIDAQ backend for data reading for debye scans
|
||||
deviceClass: debye_bec.devices.nidaq.nidaq.Nidaq
|
||||
deviceConfig:
|
||||
prefix: "X01DA-PC-SCANSERVER:"
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
|
||||
## Monochromator -- Physical Positioners
|
||||
|
||||
mo_try:
|
||||
readoutPriority: baseline
|
||||
description: Monochromator Y Translation
|
||||
deviceClass: ophyd.EpicsMotor
|
||||
deviceConfig:
|
||||
prefix: X01DA-OP-MO1:TRY
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
mo_trx:
|
||||
readoutPriority: baseline
|
||||
description: Monochromator X Translation
|
||||
deviceClass: ophyd.EpicsMotor
|
||||
deviceConfig:
|
||||
prefix: X01DA-OP-MO1:TRY
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
mo_roty:
|
||||
readoutPriority: baseline
|
||||
description: Monochromator Yaw
|
||||
deviceClass: ophyd.EpicsMotor
|
||||
deviceConfig:
|
||||
prefix: X01DA-OP-MO1:ROTY
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
|
||||
# Ionization Chambers
|
||||
|
||||
ic0:
|
||||
readoutPriority: baseline
|
||||
description: Ionization chamber 0
|
||||
deviceClass: debye_bec.devices.ionization_chambers.ionization_chamber.IonizationChamber0
|
||||
deviceConfig:
|
||||
prefix: "X01DA-"
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
ic1:
|
||||
readoutPriority: baseline
|
||||
description: Ionization chamber 1
|
||||
deviceClass: debye_bec.devices.ionization_chambers.ionization_chamber.IonizationChamber1
|
||||
deviceConfig:
|
||||
prefix: "X01DA-"
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
ic2:
|
||||
readoutPriority: baseline
|
||||
description: Ionization chamber 2
|
||||
deviceClass: debye_bec.devices.ionization_chambers.ionization_chamber.IonizationChamber2
|
||||
deviceConfig:
|
||||
prefix: "X01DA-"
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
|
||||
# ES0 Filter
|
||||
|
||||
es0filter:
|
||||
readoutPriority: baseline
|
||||
description: ES0 filter station
|
||||
deviceClass: debye_bec.devices.es0filter.ES0Filter
|
||||
deviceConfig:
|
||||
prefix: "X01DA-ES0-FI:"
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
|
||||
# Reference foil changer
|
||||
|
||||
reffoilchanger:
|
||||
readoutPriority: baseline
|
||||
description: ES2 reference foil changer
|
||||
deviceClass: debye_bec.devices.reffoilchanger.Reffoilchanger
|
||||
deviceConfig:
|
||||
prefix: "X01DA-"
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
|
||||
# Beam Monitors
|
||||
|
||||
# beam_monitor_1:
|
||||
# readoutPriority: async
|
||||
# description: Beam monitor 1
|
||||
# deviceClass: debye_bec.devices.cameras.prosilica_cam.ProsilicaCam
|
||||
# deviceConfig:
|
||||
# prefix: "X01DA-OP-GIGE01:"
|
||||
# onFailure: retry
|
||||
# enabled: true
|
||||
# softwareTrigger: false
|
||||
|
||||
# beam_monitor_2:
|
||||
# readoutPriority: async
|
||||
# description: Beam monitor 2
|
||||
# deviceClass: debye_bec.devices.cameras.prosilica_cam.ProsilicaCam
|
||||
# deviceConfig:
|
||||
# prefix: "X01DA-OP-GIGE02:"
|
||||
# onFailure: retry
|
||||
# enabled: true
|
||||
# softwareTrigger: false
|
||||
|
||||
# xray_eye:
|
||||
# readoutPriority: async
|
||||
# description: X-ray eye
|
||||
# deviceClass: debye_bec.devices.cameras.basler_cam.BaslerCam
|
||||
# deviceConfig:
|
||||
# prefix: "X01DA-ES-XRAYEYE:"
|
||||
# onFailure: retry
|
||||
# enabled: true
|
||||
# softwareTrigger: false
|
||||
|
||||
# Pilatus Curtain
|
||||
# pilatus_curtain:
|
||||
# readoutPriority: baseline
|
||||
# description: Pilatus Curtain
|
||||
# deviceClass: debye_bec.devices.pilatus_curtain.PilatusCurtain
|
||||
# deviceConfig:
|
||||
# prefix: "X01DA-ES2-DET3:TRY-"
|
||||
# onFailure: retry
|
||||
# enabled: true
|
||||
# softwareTrigger: false
|
||||
|
||||
|
||||
################################
|
||||
## ES Hutch Sensors and Light ##
|
||||
################################
|
||||
|
||||
es_temperature1:
|
||||
readoutPriority: baseline
|
||||
description: ES temperature sensor 1
|
||||
deviceClass: ophyd.EpicsSignalRO
|
||||
deviceConfig:
|
||||
read_pv: "X01DA-PC-I2C:_CH1:TEMP"
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
|
||||
es_humidity1:
|
||||
readoutPriority: baseline
|
||||
description: ES humidity sensor 1
|
||||
deviceClass: ophyd.EpicsSignalRO
|
||||
deviceConfig:
|
||||
read_pv: "X01DA-PC-I2C:_CH1:HUMIREL"
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
|
||||
es_pressure1:
|
||||
readoutPriority: baseline
|
||||
description: ES ambient pressure sensor 1
|
||||
deviceClass: ophyd.EpicsSignalRO
|
||||
deviceConfig:
|
||||
read_pv: "X01DA-PC-I2C:_CH1:PRES"
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
|
||||
es_temperature2:
|
||||
readoutPriority: baseline
|
||||
description: ES temperature sensor 2
|
||||
deviceClass: ophyd.EpicsSignalRO
|
||||
deviceConfig:
|
||||
read_pv: "X01DA-PC-I2C:_CH2:TEMP"
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
|
||||
es_humidity2:
|
||||
readoutPriority: baseline
|
||||
description: ES humidity sensor 2
|
||||
deviceClass: ophyd.EpicsSignalRO
|
||||
deviceConfig:
|
||||
read_pv: "X01DA-PC-I2C:_CH2:HUMIREL"
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
|
||||
es_pressure2:
|
||||
readoutPriority: baseline
|
||||
description: ES ambient pressure sensor 2
|
||||
deviceClass: ophyd.EpicsSignalRO
|
||||
deviceConfig:
|
||||
read_pv: "X01DA-PC-I2C:_CH2:PRES"
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
|
||||
es_light_toggle:
|
||||
readoutPriority: baseline
|
||||
description: ES light toggle
|
||||
deviceClass: ophyd.EpicsSignal
|
||||
deviceConfig:
|
||||
read_pv: "X01DA-EH-LIGHT:TOGGLE"
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
|
||||
#################
|
||||
## SDD sensors ##
|
||||
#################
|
||||
|
||||
sdd1_temperature:
|
||||
readoutPriority: baseline
|
||||
description: SDD1 temperature sensor
|
||||
deviceClass: ophyd.EpicsSignalRO
|
||||
deviceConfig:
|
||||
read_pv: "X01DA-ES1-DET1:Temperature"
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
|
||||
sdd1_humidity:
|
||||
readoutPriority: baseline
|
||||
description: SDD1 humidity sensor
|
||||
deviceClass: ophyd.EpicsSignalRO
|
||||
deviceConfig:
|
||||
read_pv: "X01DA-ES1-DET1:Humidity"
|
||||
kind: "config"
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
|
||||
#####################
|
||||
## Alignment Laser ##
|
||||
#####################
|
||||
|
||||
es1_alignment_laser:
|
||||
readoutPriority: baseline
|
||||
description: ES1 alignment laser
|
||||
deviceClass: ophyd.EpicsSignal
|
||||
deviceConfig:
|
||||
read_pv: "X01DA-ES1-LAS:Relay"
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
|
||||
###################################
|
||||
## Ionization Chambers ##
|
||||
###################################
|
||||
|
||||
ic0:
|
||||
readoutPriority: baseline
|
||||
description: Ionization chamber 0
|
||||
deviceClass: debye_bec.devices.ionization_chambers.ionization_chamber.IonizationChamber0
|
||||
deviceConfig:
|
||||
prefix: "X01DA-"
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
|
||||
ic1:
|
||||
readoutPriority: baseline
|
||||
description: Ionization chamber 1
|
||||
deviceClass: debye_bec.devices.ionization_chambers.ionization_chamber.IonizationChamber1
|
||||
deviceConfig:
|
||||
prefix: "X01DA-"
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
|
||||
ic2:
|
||||
readoutPriority: baseline
|
||||
description: Ionization chamber 2
|
||||
deviceClass: debye_bec.devices.ionization_chambers.ionization_chamber.IonizationChamber2
|
||||
deviceConfig:
|
||||
prefix: "X01DA-"
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
|
||||
pips:
|
||||
readoutPriority: baseline
|
||||
description: Pips diode
|
||||
deviceClass: debye_bec.devices.ionization_chambers.ionization_chamber.Pips
|
||||
deviceConfig:
|
||||
prefix: "X01DA-"
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
|
||||
###################################
|
||||
## Reference Foil Changer ##
|
||||
###################################
|
||||
|
||||
reffoilchanger:
|
||||
readoutPriority: baseline
|
||||
description: ES2 reference foil changer
|
||||
deviceClass: debye_bec.devices.reffoilchanger.Reffoilchanger
|
||||
deviceConfig:
|
||||
prefix: "X01DA-"
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
|
||||
###################################
|
||||
## SDD Sensors ##
|
||||
###################################
|
||||
|
||||
sdd1_temperature:
|
||||
readoutPriority: baseline
|
||||
description: SDD1 temperature sensor
|
||||
deviceClass: ophyd.EpicsSignalRO
|
||||
deviceConfig:
|
||||
read_pv: "X01DA-ES1-DET1:Temperature"
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
|
||||
sdd1_humidity:
|
||||
readoutPriority: baseline
|
||||
description: SDD1 humidity sensor
|
||||
deviceClass: ophyd.EpicsSignalRO
|
||||
deviceConfig:
|
||||
read_pv: "X01DA-ES1-DET1:Humidity"
|
||||
kind: "config"
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
@@ -0,0 +1,108 @@
|
||||
|
||||
###################################
|
||||
## Pinhole ##
|
||||
###################################
|
||||
|
||||
pin1_trx:
|
||||
readoutPriority: baseline
|
||||
description: Pinhole X-translation
|
||||
deviceClass: ophyd_devices.EpicsMotorEC
|
||||
deviceConfig:
|
||||
prefix: X01DA-ES1-PIN1:TRX
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
tags: Endstation
|
||||
|
||||
pin1_try:
|
||||
readoutPriority: baseline
|
||||
description: Pinhole Y-translation
|
||||
deviceClass: ophyd_devices.EpicsMotorEC
|
||||
deviceConfig:
|
||||
prefix: X01DA-ES1-PIN1:TRY
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
tags: Endstation
|
||||
|
||||
pin1_rotx:
|
||||
readoutPriority: baseline
|
||||
description: Pinhole X-rotation
|
||||
deviceClass: ophyd_devices.EpicsMotorEC
|
||||
deviceConfig:
|
||||
prefix: X01DA-ES1-PIN1:ROTX
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
tags: Endstation
|
||||
|
||||
pin1_roty:
|
||||
readoutPriority: baseline
|
||||
description: Pinhole Y-rotation
|
||||
deviceClass: ophyd_devices.EpicsMotorEC
|
||||
deviceConfig:
|
||||
prefix: X01DA-ES1-PIN1:ROTY
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
tags: Endstation
|
||||
|
||||
###################################
|
||||
## Beam Stop ##
|
||||
###################################
|
||||
|
||||
es2bs_trx:
|
||||
readoutPriority: baseline
|
||||
description: End Station beamstop X-translation
|
||||
deviceClass: ophyd_devices.EpicsMotorEC
|
||||
deviceConfig:
|
||||
prefix: X01DA-ES2-BS:TRX
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
|
||||
es2bs_try:
|
||||
readoutPriority: baseline
|
||||
description: End Station beamstop Y-translation
|
||||
deviceClass: ophyd_devices.EpicsMotorEC
|
||||
deviceConfig:
|
||||
prefix: X01DA-ES2-BS:TRY
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
|
||||
###################################
|
||||
## Pilatus ##
|
||||
###################################
|
||||
|
||||
pilatus_curtain:
|
||||
readoutPriority: baseline
|
||||
description: Pilatus Curtain
|
||||
deviceClass: debye_bec.devices.pilatus_curtain.PilatusCurtain
|
||||
deviceConfig:
|
||||
prefix: "X01DA-ES2-DET3:TRY-"
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
|
||||
pilatus:
|
||||
readoutPriority: baseline
|
||||
description: Pilatus
|
||||
deviceClass: debye_bec.devices.pilatus.pilatus.Pilatus
|
||||
deviceTags:
|
||||
- detector
|
||||
deviceConfig:
|
||||
prefix: "X01DA-ES2-PIL:"
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: true
|
||||
|
||||
pilatus_smpl:
|
||||
readoutPriority: baseline
|
||||
description: Sample to pilatus distance
|
||||
deviceClass: ophyd.EpicsSignalRO
|
||||
deviceConfig:
|
||||
read_pv: "X01DA-ES2-DET:SMPLDIST"
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
@@ -0,0 +1,72 @@
|
||||
"""Frontend Absorber"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import enum
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from ophyd import Component as Cpt
|
||||
from ophyd import EpicsSignal, EpicsSignalRO
|
||||
from ophyd_devices import CompareStatus, DeviceStatus
|
||||
from ophyd_devices.interfaces.base_classes.psi_device_base import PSIDeviceBase
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from bec_lib.devicemanager import ScanInfo
|
||||
|
||||
class AbsorberError(Exception):
|
||||
"""Absorber specific exception"""
|
||||
|
||||
class STATUS(int, enum.Enum):
|
||||
"""Absorber States"""
|
||||
|
||||
MOVING_CLOSE = 0
|
||||
OPEN = 1
|
||||
MOVING_OPEN = 2
|
||||
CLOSED = 3
|
||||
NOT_ENABLED = 4
|
||||
TIMEOUT_CLOSE = 5
|
||||
TIMEOUT_OPEN = 6
|
||||
CLOSE_LS_LOST = 7
|
||||
OPEN_LS_LOST = 8
|
||||
CLOSE_LS_NOT_FREE = 9
|
||||
OPEN_LS_NOT_FREE = 10
|
||||
ERROR_LS = 11
|
||||
TO_CONNECT = 12
|
||||
MAN_OPEN = 13
|
||||
UNDEFINED = 14
|
||||
|
||||
class Absorber(PSIDeviceBase):
|
||||
"""Class for the Frontend Absorber"""
|
||||
|
||||
USER_ACCESS = ["open", "close"]
|
||||
|
||||
request = Cpt(EpicsSignal, suffix="REQUEST", kind="config", doc="Open/Close Absorber")
|
||||
status = Cpt(EpicsSignalRO, suffix="STATUS", kind="normal", doc="Absorber Status")
|
||||
status_string = Cpt(EpicsSignalRO, suffix="STATUS", kind="normal", string=True, doc="Absorber Status")
|
||||
|
||||
def __init__(self, *, name: str, prefix: str = "", scan_info: ScanInfo | None = None, **kwargs):
|
||||
super().__init__(name=name, prefix=prefix, scan_info=scan_info, **kwargs)
|
||||
|
||||
self.timeout_for_move = 10
|
||||
# Wait for connection on all components, ensure IOC is connected
|
||||
self.wait_for_connection(all_signals=True, timeout=5)
|
||||
|
||||
def open(self) -> DeviceStatus | None:
|
||||
"""Open the Absorber"""
|
||||
if self.status.get() == STATUS.CLOSED:
|
||||
self.request.put(1)
|
||||
status_open = CompareStatus(self.status, STATUS.OPEN, timeout=self.timeout_for_move)
|
||||
status = status_open
|
||||
return status
|
||||
else:
|
||||
return None
|
||||
|
||||
def close(self) -> DeviceStatus | None:
|
||||
"""Close the Absorber"""
|
||||
if self.status.get() == STATUS.OPEN:
|
||||
self.request.put(1)
|
||||
status_close = CompareStatus(self.status, STATUS.CLOSED, timeout=self.timeout_for_move)
|
||||
status = status_close
|
||||
return status
|
||||
else:
|
||||
return None
|
||||
@@ -1,43 +1,46 @@
|
||||
"""Basler camera class for Debye BEC."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import time
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
import numpy as np
|
||||
from ophyd import ADBase
|
||||
from ophyd import ADBase, EpicsSignalRO
|
||||
from ophyd import ADComponent as ADCpt
|
||||
from ophyd import Component as Cpt
|
||||
from ophyd_devices import PreviewSignal
|
||||
from ophyd_devices.devices.areadetector.cam import AravisDetectorCam
|
||||
from ophyd_devices.devices.areadetector.plugins import ImagePlugin_V35
|
||||
from ophyd_devices.interfaces.base_classes.psi_device_base import PSIDeviceBase
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from debye_bec.devices.cameras.debye_base_cam import DebyeBaseCamera
|
||||
|
||||
if TYPE_CHECKING: # pragma: no cover
|
||||
from bec_lib.devicemanager import ScanInfo
|
||||
|
||||
|
||||
class BaslerCamBase(ADBase):
|
||||
"""BaslerCam Base class."""
|
||||
|
||||
cam_detector_state_string = Cpt(EpicsSignalRO, suffix="cam1:DetectorState_RBV", string=True)
|
||||
|
||||
_default_configuration_attrs = [
|
||||
'cam1.acquire_time',
|
||||
'cam1.detector_state',
|
||||
'cam_detector_state_string',
|
||||
'cam1.gain',
|
||||
'cam1.model',
|
||||
]
|
||||
|
||||
cam1 = ADCpt(AravisDetectorCam, "cam1:")
|
||||
image1 = ADCpt(ImagePlugin_V35, "image1:")
|
||||
|
||||
|
||||
class BaslerCam(PSIDeviceBase, BaslerCamBase):
|
||||
class BaslerCam(DebyeBaseCamera, BaslerCamBase):
|
||||
"""Basler camera class at Debye. IOC prefix: X01DA-ES-XRAYEYE:"""
|
||||
|
||||
# preview_2d = PSIComponent(SetableSignal, signal_type=SignalType.PREVIEW, ndim=2, kind=Kind.omitted)
|
||||
|
||||
def __init__(self, *, name: str, prefix: str = "", scan_info: ScanInfo | None = None, **kwargs):
|
||||
super().__init__(name=name, prefix=prefix, scan_info=scan_info, **kwargs)
|
||||
self.last_emit = time.time()
|
||||
self.update_frequency = 5 # Hz
|
||||
|
||||
def emit_to_bec(self, *args, obj=None, old_value=None, value=None, **kwargs):
|
||||
if (time.time() - self.last_emit) < (1 / self.update_frequency):
|
||||
return # Check logic
|
||||
width = self.image1.array_size.width.get()
|
||||
height = self.image1.array_size.height.get()
|
||||
data = np.rot90(np.reshape(value, (height, width)), k=-1, axes=(0, 1))
|
||||
|
||||
# self.preview_2d.put(data)
|
||||
self._run_subs(sub_type=self.SUB_DEVICE_MONITOR_2D, value=data)
|
||||
self.last_emit = time.time()
|
||||
|
||||
def on_connected(self):
|
||||
self.image1.array_data.subscribe(self.emit_to_bec, run=False)
|
||||
preview = Cpt(
|
||||
PreviewSignal,
|
||||
name="preview",
|
||||
ndim=2,
|
||||
num_rotation_90=3,
|
||||
doc="Preview signal for the camera.",
|
||||
)
|
||||
|
||||
@@ -0,0 +1,138 @@
|
||||
"""Base class for Camera integration at Debye."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import threading
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
import numpy as np
|
||||
from bec_lib.logger import bec_logger
|
||||
from ophyd import Component as Cpt
|
||||
from ophyd import DeviceStatus, StatusBase
|
||||
from ophyd_devices import PreviewSignal
|
||||
from ophyd_devices.interfaces.base_classes.psi_device_base import PSIDeviceBase
|
||||
from typeguard import typechecked
|
||||
|
||||
if TYPE_CHECKING: # pragma: no cover
|
||||
from bec_lib.devicemanager import ScanInfo
|
||||
from ophyd_devices.devices.areadetector.plugins import ImagePlugin_V35
|
||||
|
||||
|
||||
logger = bec_logger.logger
|
||||
|
||||
|
||||
class DebyeBaseCamera(PSIDeviceBase):
|
||||
"""Base class for Debye cameras."""
|
||||
|
||||
USER_ACCESS = ["live_mode"]
|
||||
preview = Cpt(
|
||||
PreviewSignal,
|
||||
name="preview",
|
||||
ndim=2,
|
||||
num_rotation_90=-1,
|
||||
doc="Preview signal for the camera.",
|
||||
)
|
||||
|
||||
def __init__(self, *, name: str, prefix: str = "", scan_info: ScanInfo | None = None, **kwargs):
|
||||
super().__init__(name=name, prefix=prefix, scan_info=scan_info, **kwargs)
|
||||
self.image1: "ImagePlugin_V35"
|
||||
self._update_frequency = 1 # Hz
|
||||
self._live_mode = False
|
||||
self._live_mode_event = None
|
||||
self._task_status = None
|
||||
|
||||
@property
|
||||
def live_mode(self) -> bool:
|
||||
"""Live mode status."""
|
||||
return self._live_mode
|
||||
|
||||
@typechecked
|
||||
@live_mode.setter
|
||||
def live_mode(self, value: bool) -> None:
|
||||
"""
|
||||
Set the live mode status.
|
||||
|
||||
Args:
|
||||
value (bool): True to enable live mode, False to disable.
|
||||
"""
|
||||
if value == self._live_mode:
|
||||
return
|
||||
self._live_mode = value
|
||||
if value:
|
||||
self._start_live_mode()
|
||||
else:
|
||||
self._stop_live_mode()
|
||||
|
||||
def _start_live_mode(self) -> None:
|
||||
"""Start live mode."""
|
||||
if self._live_mode_event is not None: # Kill task if it exists
|
||||
self._live_mode_event.set()
|
||||
self._live_mode_event = None
|
||||
if self._task_status is not None:
|
||||
self.task_handler.kill_task(task_status=self._task_status)
|
||||
self._task_status = None
|
||||
|
||||
self._live_mode_event = threading.Event()
|
||||
self._task_status = self.task_handler.submit_task(task=self.emit_to_bec)
|
||||
|
||||
def _stop_live_mode(self) -> None:
|
||||
"""Stop live mode."""
|
||||
if self._live_mode_event is not None:
|
||||
self._live_mode_event.set()
|
||||
self._live_mode_event = None
|
||||
|
||||
def emit_to_bec(self):
|
||||
"""Emit the image data to BEC. If _live_mode_event is set, stop the task."""
|
||||
while not self._live_mode_event.wait(1 / self._update_frequency):
|
||||
value = self.image1.array_data.get()
|
||||
if value is None:
|
||||
continue
|
||||
width = self.image1.array_size.width.get()
|
||||
height = self.image1.array_size.height.get()
|
||||
# Geometry correction for the image
|
||||
data = np.reshape(value, (height, width))
|
||||
self.preview.put(data)
|
||||
|
||||
########################################
|
||||
# Beamline Specific Implementations #
|
||||
########################################
|
||||
|
||||
def on_init(self) -> None:
|
||||
"""
|
||||
Called when the device is initialized.
|
||||
|
||||
No signals are connected at this point. If you like to
|
||||
set default values on signals, please use on_connected instead.
|
||||
"""
|
||||
|
||||
def on_connected(self) -> None:
|
||||
"""
|
||||
Called after the device is connected and its signals are connected.
|
||||
Default values for signals should be set here.
|
||||
"""
|
||||
self.live_mode = True
|
||||
|
||||
def on_stage(self) -> DeviceStatus | StatusBase | None:
|
||||
"""
|
||||
Called while staging the device.
|
||||
|
||||
Information about the upcoming scan can be accessed from the scan_info (self.scan_info.msg) object.
|
||||
"""
|
||||
|
||||
def on_unstage(self) -> DeviceStatus | StatusBase | None:
|
||||
"""Called while unstaging the device."""
|
||||
|
||||
def on_pre_scan(self) -> DeviceStatus | StatusBase | None:
|
||||
"""Called right before the scan starts on all devices automatically."""
|
||||
|
||||
def on_trigger(self) -> DeviceStatus | StatusBase | None:
|
||||
"""Called when the device is triggered."""
|
||||
|
||||
def on_complete(self) -> DeviceStatus | StatusBase | None:
|
||||
"""Called to inquire if a device has completed a scans."""
|
||||
|
||||
def on_kickoff(self) -> DeviceStatus | StatusBase | None:
|
||||
"""Called to kickoff a device for a fly scan. Has to be called explicitly."""
|
||||
|
||||
def on_stop(self) -> None:
|
||||
"""Called when the device is stopped."""
|
||||
@@ -0,0 +1,79 @@
|
||||
"""EH Hutch Cameras"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import cv2
|
||||
import threading
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from bec_lib.logger import bec_logger
|
||||
from bec_lib.file_utils import get_full_path
|
||||
from ophyd_devices.interfaces.base_classes.psi_device_base import PSIDeviceBase
|
||||
from ophyd_devices import DeviceStatus
|
||||
|
||||
if TYPE_CHECKING: # pragma: no cover
|
||||
from bec_lib.devicemanager import ScanInfo
|
||||
from bec_lib.messages import ScanStatusMessage
|
||||
|
||||
logger = bec_logger.logger
|
||||
|
||||
CAM_USERNAME = "camera_user"
|
||||
CAM_PASSWORD = "camera_user1"
|
||||
CAM_PORT = 554
|
||||
|
||||
class HutchCam(PSIDeviceBase):
|
||||
"""Class for the Hutch Cameras"""
|
||||
|
||||
# image = Cpt(Signal, name='image', kind='config')
|
||||
|
||||
def __init__(self, *, name: str, prefix: str = "", scan_info: ScanInfo | None = None, **kwargs):
|
||||
super().__init__(name=name, scan_info=scan_info, **kwargs)
|
||||
|
||||
self.hostname = prefix
|
||||
self.status = None
|
||||
|
||||
# pylint: disable=E1101
|
||||
def on_connected(self) -> None:
|
||||
"""
|
||||
Called after the device is connected and its signals are connected.
|
||||
Default values for signals should be set here.
|
||||
"""
|
||||
rtsp_url = f"rtsp://{CAM_USERNAME}:{CAM_PASSWORD}@{self.hostname}.psi.ch:{CAM_PORT}/rtpstream/config1"
|
||||
cap = cv2.VideoCapture(f"{rtsp_url}?tcp")
|
||||
if not cap.isOpened():
|
||||
logger.error(self, "Connection Failed", "Could not connect to the camera stream.")
|
||||
return
|
||||
cap.release()
|
||||
|
||||
def on_stage(self) -> DeviceStatus:
|
||||
"""Called while staging the device."""
|
||||
|
||||
scan_msg: ScanStatusMessage = self.scan_info.msg
|
||||
file_path = get_full_path(scan_msg, name='hutch_cam_' + self.hostname).removesuffix('h5')
|
||||
|
||||
self.status = DeviceStatus(self)
|
||||
|
||||
thread = threading.Thread(target=self._save_picture, args=(file_path, self.status), daemon=True)
|
||||
thread.start()
|
||||
|
||||
return self.status
|
||||
|
||||
def _save_picture(self, file_path, status):
|
||||
try:
|
||||
logger.info(f'Capture from camera {self.hostname}')
|
||||
rtsp_url = f"rtsp://{CAM_USERNAME}:{CAM_PASSWORD}@{self.hostname}.psi.ch:{CAM_PORT}/rtpstream/config1"
|
||||
cap = cv2.VideoCapture(f"{rtsp_url}?tcp")
|
||||
if not cap.isOpened():
|
||||
logger.error("Connection Failed", "Could not connect to the camera stream.")
|
||||
return
|
||||
logger.info(f'Connection to camera {self.hostname} established')
|
||||
ret, frame = cap.readAsync()
|
||||
cap.release()
|
||||
if not ret:
|
||||
logger.error("Capture Failed", "Failed to capture image from camera.")
|
||||
return
|
||||
cv2.imwrite(file_path + 'png', frame)
|
||||
status.set_finished()
|
||||
logger.info(f'Capture from camera {self.hostname} done')
|
||||
except Exception as e:
|
||||
status.set_exception(e)
|
||||
@@ -1,41 +1,49 @@
|
||||
"""Prosilica camera class for integration of beam_monitor 1/2 cameras."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import time
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
import numpy as np
|
||||
from ophyd import ADBase
|
||||
from ophyd import ADBase, EpicsSignalRO
|
||||
from ophyd import ADComponent as ADCpt
|
||||
from ophyd import Component as Cpt
|
||||
from ophyd import Device
|
||||
from ophyd_devices import PreviewSignal
|
||||
from ophyd_devices.devices.areadetector.cam import ProsilicaDetectorCam
|
||||
from ophyd_devices.devices.areadetector.plugins import ImagePlugin_V35
|
||||
from ophyd_devices.interfaces.base_classes.psi_device_base import PSIDeviceBase
|
||||
|
||||
from debye_bec.devices.cameras.debye_base_cam import DebyeBaseCamera
|
||||
|
||||
if TYPE_CHECKING: # pragma: no cover
|
||||
from bec_lib.devicemanager import ScanInfo
|
||||
|
||||
|
||||
class ProsilicaCamBase(ADBase):
|
||||
"""Base class for Prosilica cameras."""
|
||||
|
||||
cam_detector_state_string = Cpt(EpicsSignalRO, suffix="cam1:DetectorState_RBV", string=True)
|
||||
|
||||
_default_configuration_attrs = [
|
||||
'cam1.acquire_time',
|
||||
'cam1.detector_state',
|
||||
'cam_detector_state_string',
|
||||
'cam1.gain',
|
||||
'cam1.model',
|
||||
]
|
||||
|
||||
cam1 = ADCpt(ProsilicaDetectorCam, "cam1:")
|
||||
image1 = ADCpt(ImagePlugin_V35, "image1:")
|
||||
|
||||
|
||||
class ProsilicaCam(PSIDeviceBase, ProsilicaCamBase):
|
||||
class ProsilicaCam(DebyeBaseCamera, ProsilicaCamBase):
|
||||
"""
|
||||
Prosilica camera class, for integration of beam_monitor 1/2 cameras.
|
||||
Prefixes are: X01DA-OP-GIGE02: and X01DA-OP-GIGE01:
|
||||
"""
|
||||
|
||||
def __init__(self, *, name: str, prefix: str = "", scan_info: ScanInfo | None = None, **kwargs):
|
||||
super().__init__(name=name, prefix=prefix, scan_info=scan_info, **kwargs)
|
||||
self.last_emit = time.time()
|
||||
self.update_frequency = 5 # Hz
|
||||
|
||||
def emit_to_bec(self, *args, obj=None, old_value=None, value=None, **kwargs):
|
||||
if (time.time() - self.last_emit) < (1 / self.update_frequency):
|
||||
return # Check logic
|
||||
width = self.image1.array_size.width.get()
|
||||
height = self.image1.array_size.height.get()
|
||||
data = np.rot90(np.reshape(value, (height, width)), k=-1, axes=(0, 1))
|
||||
self._run_subs(sub_type=self.SUB_DEVICE_MONITOR_2D, value=data)
|
||||
self.last_emit = time.time()
|
||||
|
||||
def on_connected(self):
|
||||
self.image1.array_data.subscribe(self.emit_to_bec, run=False)
|
||||
preview = Cpt(
|
||||
PreviewSignal,
|
||||
name="preview",
|
||||
ndim=2,
|
||||
num_rotation_90=3,
|
||||
doc="Preview signal for the camera.",
|
||||
)
|
||||
|
||||
@@ -3,8 +3,9 @@
|
||||
### debye_bec
|
||||
| Device | Documentation | Module |
|
||||
| :----- | :------------- | :------ |
|
||||
| BaslerCam | | [debye_bec.devices.cameras.basler_cam](https://gitlab.psi.ch/bec/debye_bec/-/blob/main/debye_bec/devices/cameras/basler_cam.py) |
|
||||
| BaslerCamBase | | [debye_bec.devices.cameras.basler_cam](https://gitlab.psi.ch/bec/debye_bec/-/blob/main/debye_bec/devices/cameras/basler_cam.py) |
|
||||
| BaslerCam | Basler camera class at Debye. IOC prefix: X01DA-ES-XRAYEYE: | [debye_bec.devices.cameras.basler_cam](https://gitlab.psi.ch/bec/debye_bec/-/blob/main/debye_bec/devices/cameras/basler_cam.py) |
|
||||
| BaslerCamBase | BaslerCam Base class. | [debye_bec.devices.cameras.basler_cam](https://gitlab.psi.ch/bec/debye_bec/-/blob/main/debye_bec/devices/cameras/basler_cam.py) |
|
||||
| DebyeBaseCamera | Base class for Debye cameras. | [debye_bec.devices.cameras.debye_base_cam](https://gitlab.psi.ch/bec/debye_bec/-/blob/main/debye_bec/devices/cameras/debye_base_cam.py) |
|
||||
| ES0Filter | Class for the ES0 filter station X01DA-ES0-FI: | [debye_bec.devices.es0filter](https://gitlab.psi.ch/bec/debye_bec/-/blob/main/debye_bec/devices/es0filter.py) |
|
||||
| GasMixSetup | Class for the ES2 Pilatus Curtain | [debye_bec.devices.pilatus_curtain](https://gitlab.psi.ch/bec/debye_bec/-/blob/main/debye_bec/devices/pilatus_curtain.py) |
|
||||
| GasMixSetupControl | GasMixSetup Control for Inonization Chamber 0 | [debye_bec.devices.ionization_chambers.ionization_chamber](https://gitlab.psi.ch/bec/debye_bec/-/blob/main/debye_bec/devices/ionization_chambers/ionization_chamber.py) |
|
||||
@@ -13,16 +14,17 @@
|
||||
| IonizationChamber1 | Ionization Chamber 1, prefix should be 'X01DA-'. | [debye_bec.devices.ionization_chambers.ionization_chamber](https://gitlab.psi.ch/bec/debye_bec/-/blob/main/debye_bec/devices/ionization_chambers/ionization_chamber.py) |
|
||||
| IonizationChamber2 | Ionization Chamber 2, prefix should be 'X01DA-'. | [debye_bec.devices.ionization_chambers.ionization_chamber](https://gitlab.psi.ch/bec/debye_bec/-/blob/main/debye_bec/devices/ionization_chambers/ionization_chamber.py) |
|
||||
| Mo1Bragg | Mo1 Bragg motor for the Debye beamline.<br><br> The prefix to connect to the soft IOC is X01DA-OP-MO1:BRAGG:<br> | [debye_bec.devices.mo1_bragg.mo1_bragg](https://gitlab.psi.ch/bec/debye_bec/-/blob/main/debye_bec/devices/mo1_bragg/mo1_bragg.py) |
|
||||
| Mo1BraggAngle | Positioner implementation with readback angle of the MO1 Bragg positioner. | [debye_bec.devices.mo1_bragg.mo1_bragg_angle](https://gitlab.psi.ch/bec/debye_bec/-/blob/main/debye_bec/devices/mo1_bragg/mo1_bragg_angle.py) |
|
||||
| Mo1BraggCalculator | Mo1 Bragg PVs to convert angle to energy or vice-versa. | [debye_bec.devices.mo1_bragg.mo1_bragg_devices](https://gitlab.psi.ch/bec/debye_bec/-/blob/main/debye_bec/devices/mo1_bragg/mo1_bragg_devices.py) |
|
||||
| Mo1BraggCrystal | Mo1 Bragg PVs to set the crystal parameters | [debye_bec.devices.mo1_bragg.mo1_bragg_devices](https://gitlab.psi.ch/bec/debye_bec/-/blob/main/debye_bec/devices/mo1_bragg/mo1_bragg_devices.py) |
|
||||
| Mo1BraggEncoder | Mo1 Bragg PVs to communicate with the encoder | [debye_bec.devices.mo1_bragg.mo1_bragg_devices](https://gitlab.psi.ch/bec/debye_bec/-/blob/main/debye_bec/devices/mo1_bragg/mo1_bragg_devices.py) |
|
||||
| Mo1BraggPositioner | <br> Positioner implementation of the MO1 Bragg positioner.<br><br> The prefix to connect to the soft IOC is X01DA-OP-MO1:BRAGG:<br> This soft IOC connects to the NI motor and its control loop.<br> | [debye_bec.devices.mo1_bragg.mo1_bragg_devices](https://gitlab.psi.ch/bec/debye_bec/-/blob/main/debye_bec/devices/mo1_bragg/mo1_bragg_devices.py) |
|
||||
| Mo1BraggPositioner | <br> Positioner implementation with readback energy of the MO1 Bragg positioner.<br><br> The prefix to connect to the soft IOC is X01DA-OP-MO1:BRAGG:<br> This soft IOC connects to the NI motor and its control loop.<br> | [debye_bec.devices.mo1_bragg.mo1_bragg_devices](https://gitlab.psi.ch/bec/debye_bec/-/blob/main/debye_bec/devices/mo1_bragg/mo1_bragg_devices.py) |
|
||||
| Mo1BraggScanControl | Mo1 Bragg PVs to control the scan after setting the parameters. | [debye_bec.devices.mo1_bragg.mo1_bragg_devices](https://gitlab.psi.ch/bec/debye_bec/-/blob/main/debye_bec/devices/mo1_bragg/mo1_bragg_devices.py) |
|
||||
| Mo1BraggScanSettings | Mo1 Bragg PVs to set the scan setttings | [debye_bec.devices.mo1_bragg.mo1_bragg_devices](https://gitlab.psi.ch/bec/debye_bec/-/blob/main/debye_bec/devices/mo1_bragg/mo1_bragg_devices.py) |
|
||||
| Mo1BraggStatus | Mo1 Bragg PVs for status monitoring | [debye_bec.devices.mo1_bragg.mo1_bragg_devices](https://gitlab.psi.ch/bec/debye_bec/-/blob/main/debye_bec/devices/mo1_bragg/mo1_bragg_devices.py) |
|
||||
| Mo1TriggerSettings | Mo1 Trigger settings | [debye_bec.devices.mo1_bragg.mo1_bragg_devices](https://gitlab.psi.ch/bec/debye_bec/-/blob/main/debye_bec/devices/mo1_bragg/mo1_bragg_devices.py) |
|
||||
| Nidaq | NIDAQ ophyd wrapper around the NIDAQ backend currently running at x01da-cons-05<br><br> Args:<br> prefix (str) : Prefix to the NIDAQ soft ioc, currently X01DA-PC-SCANSERVER:<br> name (str) : Name of the device<br> scan_info (ScanInfo) : ScanInfo object passed by BEC's devicemanager.<br> | [debye_bec.devices.nidaq.nidaq](https://gitlab.psi.ch/bec/debye_bec/-/blob/main/debye_bec/devices/nidaq/nidaq.py) |
|
||||
| NidaqControl | Nidaq control class with all PVs | [debye_bec.devices.nidaq.nidaq](https://gitlab.psi.ch/bec/debye_bec/-/blob/main/debye_bec/devices/nidaq/nidaq.py) |
|
||||
| ProsilicaCam | | [debye_bec.devices.cameras.prosilica_cam](https://gitlab.psi.ch/bec/debye_bec/-/blob/main/debye_bec/devices/cameras/prosilica_cam.py) |
|
||||
| ProsilicaCamBase | | [debye_bec.devices.cameras.prosilica_cam](https://gitlab.psi.ch/bec/debye_bec/-/blob/main/debye_bec/devices/cameras/prosilica_cam.py) |
|
||||
| ProsilicaCam | <br> Prosilica camera class, for integration of beam_monitor 1/2 cameras.<br> Prefixes are: X01DA-OP-GIGE02: and X01DA-OP-GIGE01:<br> | [debye_bec.devices.cameras.prosilica_cam](https://gitlab.psi.ch/bec/debye_bec/-/blob/main/debye_bec/devices/cameras/prosilica_cam.py) |
|
||||
| ProsilicaCamBase | Base class for Prosilica cameras. | [debye_bec.devices.cameras.prosilica_cam](https://gitlab.psi.ch/bec/debye_bec/-/blob/main/debye_bec/devices/cameras/prosilica_cam.py) |
|
||||
| Reffoilchanger | Class for the ES2 Reference Foil Changer | [debye_bec.devices.reffoilchanger](https://gitlab.psi.ch/bec/debye_bec/-/blob/main/debye_bec/devices/reffoilchanger.py) |
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
"""Ionization chamber device class"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING, Literal
|
||||
@@ -6,8 +8,8 @@ import numpy as np
|
||||
from ophyd import Component as Cpt
|
||||
from ophyd import Device
|
||||
from ophyd import DynamicDeviceComponent as Dcpt
|
||||
from ophyd import EpicsSignal, EpicsSignalRO, EpicsSignalWithRBV, Kind
|
||||
from ophyd.status import DeviceStatus, SubscriptionStatus
|
||||
from ophyd import EpicsSignal, EpicsSignalRO, EpicsSignalWithRBV
|
||||
from ophyd_devices import CompareStatus, DeviceStatus, SubscriptionStatus, TransitionStatus
|
||||
from ophyd_devices.interfaces.base_classes.psi_device_base import PSIDeviceBase
|
||||
from typeguard import typechecked
|
||||
|
||||
@@ -31,22 +33,24 @@ class EpicsSignalSplit(EpicsSignal):
|
||||
class GasMixSetupControl(Device):
|
||||
"""GasMixSetup Control for Inonization Chamber 0"""
|
||||
|
||||
gas1_req = Cpt(EpicsSignalWithRBV, suffix="Gas1Req", kind="config", doc="Gas 1 requirement")
|
||||
gas1_req = Cpt(EpicsSignalWithRBV, suffix="Gas1Req", kind="omitted", doc="Gas 1 requirement")
|
||||
conc1_req = Cpt(
|
||||
EpicsSignalWithRBV, suffix="Conc1Req", kind="config", doc="Concentration 1 requirement"
|
||||
EpicsSignalWithRBV, suffix="Conc1Req", kind="omitted", doc="Concentration 1 requirement"
|
||||
)
|
||||
gas2_req = Cpt(EpicsSignalWithRBV, suffix="Gas2Req", kind="config", doc="Gas 2 requirement")
|
||||
gas2_req = Cpt(EpicsSignalWithRBV, suffix="Gas2Req", kind="omitted", doc="Gas 2 requirement")
|
||||
conc2_req = Cpt(
|
||||
EpicsSignalWithRBV, suffix="Conc2Req", kind="config", doc="Concentration 2 requirement"
|
||||
EpicsSignalWithRBV, suffix="Conc2Req", kind="omitted", doc="Concentration 2 requirement"
|
||||
)
|
||||
press_req = Cpt(
|
||||
EpicsSignalWithRBV, suffix="PressReq", kind="config", doc="Pressure requirement"
|
||||
EpicsSignalWithRBV, suffix="PressReq", kind="omitted", doc="Pressure requirement"
|
||||
)
|
||||
fill = Cpt(EpicsSignal, suffix="Fill", kind="config", doc="Fill the chamber")
|
||||
status = Cpt(EpicsSignalRO, suffix="Status", kind="config", doc="Status")
|
||||
gas1 = Cpt(EpicsSignalRO, suffix="Gas1", kind="config", doc="Gas 1")
|
||||
gas1_string = Cpt(EpicsSignalRO, suffix="Gas1", kind="config", doc="Gas 1", string=True)
|
||||
conc1 = Cpt(EpicsSignalRO, suffix="Conc1", kind="config", doc="Concentration 1")
|
||||
gas2 = Cpt(EpicsSignalRO, suffix="Gas2", kind="config", doc="Gas 2")
|
||||
gas2_string = Cpt(EpicsSignalRO, suffix="Gas2", kind="config", doc="Gas 2", string=True)
|
||||
conc2 = Cpt(EpicsSignalRO, suffix="Conc2", kind="config", doc="Concentration 2")
|
||||
press = Cpt(EpicsSignalRO, suffix="PressTransm", kind="config", doc="Current Pressure")
|
||||
|
||||
@@ -82,10 +86,25 @@ class IonizationChamber0(PSIDeviceBase):
|
||||
(f"ES:AMP5004:cFilter{num}_ENUM"),
|
||||
{"kind": "config", "doc": f"Filter of ch{num} -> IC{num-1}"},
|
||||
),
|
||||
"cOnOff_string": (
|
||||
EpicsSignal,
|
||||
(f"ES:AMP5004.cOnOff{num}"),
|
||||
{"kind": "config", "doc": f"Enable ch{num} -> IC{num-1}", "string": True},
|
||||
),
|
||||
"cGain_ENUM_string": (
|
||||
EpicsSignalWithRBV,
|
||||
(f"ES:AMP5004:cGain{num}_ENUM"),
|
||||
{"kind": "config", "doc": f"Gain of ch{num} -> IC{num-1}", "string": True},
|
||||
),
|
||||
"cFilter_ENUM_string": (
|
||||
EpicsSignalWithRBV,
|
||||
(f"ES:AMP5004:cFilter{num}_ENUM"),
|
||||
{"kind": "config", "doc": f"Filter of ch{num} -> IC{num-1}", "string": True},
|
||||
),
|
||||
}
|
||||
amp = Dcpt(amp_signals)
|
||||
gmes = Cpt(GasMixSetupControl, suffix=f"ES-GMES:IC{num-1}")
|
||||
gmes_status = Cpt(EpicsSignalRO, suffix="ES-GMES:StatusMsg0", kind="config", doc="Status")
|
||||
gmes_status_msg = Cpt(EpicsSignalRO, suffix="ES-GMES:StatusMsg0", kind="config", doc="Status")
|
||||
hv = Cpt(HighVoltageSuppliesControl, suffix=f"ES1-IC{num-1}:")
|
||||
hv_en_signals = {
|
||||
"ext_ena": (
|
||||
@@ -102,7 +121,7 @@ class IonizationChamber0(PSIDeviceBase):
|
||||
super().__init__(name=name, prefix=prefix, scan_info=scan_info, **kwargs)
|
||||
|
||||
@typechecked
|
||||
def set_gain(self, gain: Literal["1e6", "1e7", "5e7", "1e8", "1e9"] | AmplifierGain) -> None:
|
||||
def set_gain(self, gain: Literal["1e6", "1e7", "5e7", "1e8", "1e9"]) -> None:
|
||||
"""Configure the gain setting of the specified channel
|
||||
|
||||
Args:
|
||||
@@ -110,18 +129,10 @@ class IonizationChamber0(PSIDeviceBase):
|
||||
"""
|
||||
|
||||
if self.amp.cOnOff.get() == AmplifierEnable.OFF:
|
||||
status = CompareStatus(self.amp.cOnOff, AmplifierEnable.ON)
|
||||
self.cancel_on_stop(status)
|
||||
self.amp.cOnOff.put(AmplifierEnable.ON)
|
||||
|
||||
# Wait until channel is switched on
|
||||
def _wait_enabled():
|
||||
return self.amp.cOnOff.get() == AmplifierEnable.ON
|
||||
|
||||
if not self.wait_for_condition(
|
||||
_wait_enabled, check_stopped=True, timeout=self.timeout_for_pvwait
|
||||
):
|
||||
raise TimeoutError(
|
||||
f"Enabling channel run into timeout after {self.timeout_for_pvwait} seconds"
|
||||
)
|
||||
status.wait(self.timeout_for_pvwait)
|
||||
|
||||
match gain:
|
||||
case "1e6":
|
||||
@@ -136,29 +147,18 @@ class IonizationChamber0(PSIDeviceBase):
|
||||
self.amp.cGain_ENUM.put(AmplifierGain.G1E9)
|
||||
|
||||
def set_filter(
|
||||
self,
|
||||
value: (
|
||||
Literal["1us", "3us", "10us", "30us", "100us", "300us", "1ms", "3ms"] | AmplifierFilter
|
||||
),
|
||||
self, value: Literal["1us", "3us", "10us", "30us", "100us", "300us", "1ms", "3ms"]
|
||||
) -> None:
|
||||
"""Configure the filter setting of the specified channel
|
||||
|
||||
Args:
|
||||
value (Literal['1us', '3us', '10us', '30us', '100us', '300us', '1ms', '3ms']) : Desired filter
|
||||
value (Literal['1us','3us','10us','30us','100us','300us','1ms','3ms']) :Desired filter
|
||||
"""
|
||||
if self.amp.cOnOff.get() == AmplifierEnable.OFF:
|
||||
status = CompareStatus(self.amp.cOnOff, AmplifierEnable.ON)
|
||||
self.cancel_on_stop(status)
|
||||
self.amp.cOnOff.put(AmplifierEnable.ON)
|
||||
|
||||
# Wait until channel is switched on
|
||||
def _wait_enabled():
|
||||
return self.amp.cOnOff.get() == AmplifierEnable.ON
|
||||
|
||||
if not self.wait_for_condition(
|
||||
_wait_enabled, check_stopped=True, timeout=self.timeout_for_pvwait
|
||||
):
|
||||
raise TimeoutError(
|
||||
f"Enabling channel run into timeout after {self.timeout_for_pvwait} seconds"
|
||||
)
|
||||
status.wait(self.timeout_for_pvwait)
|
||||
|
||||
match value:
|
||||
case "1us":
|
||||
@@ -187,20 +187,16 @@ class IonizationChamber0(PSIDeviceBase):
|
||||
hv (float) : Desired voltage for the 'HV' terminal. Voltage has to be between 0...3000
|
||||
"""
|
||||
|
||||
if not (0 <= hv <= 3000):
|
||||
if not 0 <= hv <= 3000:
|
||||
raise ValueError(f"specified HV {hv} not within range [0 .. 3000]")
|
||||
if not np.isclose(np.abs(hv - self.hv.grid_v.get()), 0, atol=3):
|
||||
raise ValueError(f"Grid {self.hv.grid_v.get()} must not be higher than HV {hv}!")
|
||||
|
||||
if not self.hv_en.ena.get() == 1:
|
||||
|
||||
def check_ch_ena(*, old_value, value, **kwargs):
|
||||
return value == 1
|
||||
|
||||
status = SubscriptionStatus(device=self.hv_en.ena, callback=check_ch_ena)
|
||||
status = CompareStatus(self.hv_en.ena, 1)
|
||||
self.cancel_on_stop(status)
|
||||
self.hv_en.ena.put(1)
|
||||
# Wait after setting ena to 1
|
||||
status.wait(timeout=2)
|
||||
status.wait(self.timeout_for_pvwait)
|
||||
|
||||
# Set current fixed to 3 mA (max)
|
||||
self.hv.hv_i.put(3)
|
||||
@@ -212,23 +208,20 @@ class IonizationChamber0(PSIDeviceBase):
|
||||
enable the high voltage (if external enable is active)!
|
||||
|
||||
Args:
|
||||
grid (float) : Desired voltage for the 'Grid' terminal, Grid Voltage has to be between 0...3000
|
||||
grid (float) : Desired voltage for the 'Grid' terminal,
|
||||
Grid Voltage has to be between 0...3000
|
||||
"""
|
||||
|
||||
if not (0 <= grid <= 3000):
|
||||
if not 0 <= grid <= 3000:
|
||||
raise ValueError(f"specified Grid {grid} not within range [0 .. 3000]")
|
||||
if not np.isclose(np.abs(grid - self.hv.hv_v.get()), 0, atol=3):
|
||||
raise ValueError(f"Grid {grid} must not be higher than HV {self.hv.hv_v.get()}!")
|
||||
|
||||
if not self.hv_en.ena.get() == 1:
|
||||
|
||||
def check_ch_ena(*, old_value, value, **kwargs):
|
||||
return value == 1
|
||||
|
||||
status = SubscriptionStatus(device=self.hv_en.ena, callback=check_ch_ena)
|
||||
status = CompareStatus(self.hv_en.ena, 1)
|
||||
self.cancel_on_stop(status)
|
||||
self.hv_en.ena.put(1)
|
||||
# Wait after setting ena to 1
|
||||
status.wait(timeout=2)
|
||||
status.wait(self.timeout_for_pvwait)
|
||||
|
||||
# Set current fixed to 3 mA (max)
|
||||
self.hv.grid_i.put(3)
|
||||
@@ -244,7 +237,7 @@ class IonizationChamber0(PSIDeviceBase):
|
||||
pressure: float,
|
||||
*,
|
||||
wait: bool = False,
|
||||
) -> DeviceStatus:
|
||||
) -> DeviceStatus | None:
|
||||
"""Fill an ionization chamber with the specified gas mixture.
|
||||
|
||||
Args:
|
||||
@@ -256,13 +249,13 @@ class IonizationChamber0(PSIDeviceBase):
|
||||
wait (bool): If you like to wait for the filling to finish.
|
||||
"""
|
||||
|
||||
if not (0 <= conc1 <= 100):
|
||||
if not 0 <= conc1 <= 100:
|
||||
raise ValueError(f"Concentration 1 {conc1} out of range [0 .. 100 %]")
|
||||
if not (0 <= conc2 <= 100):
|
||||
if not 0 <= conc2 <= 100:
|
||||
raise ValueError(f"Concentration 2 {conc2} out of range [0 .. 100 %]")
|
||||
if not np.isclose((conc1 + conc2), 100, atol=0.1):
|
||||
raise ValueError(f"Conc1 {conc1} and conc2 {conc2} must sum to 100 +- 0.1")
|
||||
if not (0 <= pressure <= 3):
|
||||
if not 0 <= pressure <= 3:
|
||||
raise ValueError(f"Pressure {pressure} out of range [0 .. 3 bar abs]")
|
||||
|
||||
self.gmes.gas1_req.set(gas1).wait(timeout=3)
|
||||
@@ -270,27 +263,13 @@ class IonizationChamber0(PSIDeviceBase):
|
||||
self.gmes.gas2_req.set(gas2).wait(timeout=3)
|
||||
self.gmes.conc2_req.set(conc2).wait(timeout=3)
|
||||
|
||||
status = TransitionStatus(self.gmes.status.get(), [0, 1])
|
||||
self.cancel_on_stop(status)
|
||||
self.gmes.fill.put(1)
|
||||
|
||||
def wait_for_status():
|
||||
return self.gmes.status.get() == 0
|
||||
|
||||
timeout = 3
|
||||
if not self.wait_for_condition(wait_for_status, timeout=timeout, check_stopped=True):
|
||||
raise TimeoutError(
|
||||
f"Ionization chamber filling process did not start after {timeout}s. Last log message {self.gmes_status.get()}"
|
||||
)
|
||||
|
||||
def wait_for_filling_finished():
|
||||
return self.gmes.status.get() == 1
|
||||
|
||||
# Wait until ionization chamber is filled successfully
|
||||
status = self.task_handler.submit_task(
|
||||
task=self.wait_for_condition, task_args=(wait_for_filling_finished, 360, True)
|
||||
)
|
||||
if wait:
|
||||
status.wait()
|
||||
return status
|
||||
status.wait(timeout=360)
|
||||
else:
|
||||
return status
|
||||
|
||||
|
||||
class IonizationChamber1(IonizationChamber0):
|
||||
@@ -313,10 +292,25 @@ class IonizationChamber1(IonizationChamber0):
|
||||
(f"ES:AMP5004:cFilter{num}_ENUM"),
|
||||
{"kind": "config", "doc": f"Filter of ch{num} -> IC{num-1}"},
|
||||
),
|
||||
"cOnOff_string": (
|
||||
EpicsSignal,
|
||||
(f"ES:AMP5004.cOnOff{num}"),
|
||||
{"kind": "config", "doc": f"Enable ch{num} -> IC{num-1}", "string": True},
|
||||
),
|
||||
"cGain_ENUM_string": (
|
||||
EpicsSignalWithRBV,
|
||||
(f"ES:AMP5004:cGain{num}_ENUM"),
|
||||
{"kind": "config", "doc": f"Gain of ch{num} -> IC{num-1}", "string": True},
|
||||
),
|
||||
"cFilter_ENUM_string": (
|
||||
EpicsSignalWithRBV,
|
||||
(f"ES:AMP5004:cFilter{num}_ENUM"),
|
||||
{"kind": "config", "doc": f"Filter of ch{num} -> IC{num-1}", "string": True},
|
||||
),
|
||||
}
|
||||
amp = Dcpt(amp_signals)
|
||||
gmes = Cpt(GasMixSetupControl, suffix=f"ES-GMES:IC{num-1}")
|
||||
gmes_status = Cpt(EpicsSignalRO, suffix="ES-GMES:StatusMsg0", kind="config", doc="Status")
|
||||
gmes_status_msg = Cpt(EpicsSignalRO, suffix="ES-GMES:StatusMsg0", kind="config", doc="Status")
|
||||
hv = Cpt(HighVoltageSuppliesControl, suffix=f"ES2-IC{num-1}:")
|
||||
hv_en_signals = {
|
||||
"ext_ena": (
|
||||
@@ -349,10 +343,25 @@ class IonizationChamber2(IonizationChamber0):
|
||||
(f"ES:AMP5004:cFilter{num}_ENUM"),
|
||||
{"kind": "config", "doc": f"Filter of ch{num} -> IC{num-1}"},
|
||||
),
|
||||
"cOnOff_string": (
|
||||
EpicsSignal,
|
||||
(f"ES:AMP5004.cOnOff{num}"),
|
||||
{"kind": "config", "doc": f"Enable ch{num} -> IC{num-1}", "string": True},
|
||||
),
|
||||
"cGain_ENUM_string": (
|
||||
EpicsSignalWithRBV,
|
||||
(f"ES:AMP5004:cGain{num}_ENUM"),
|
||||
{"kind": "config", "doc": f"Gain of ch{num} -> IC{num-1}", "string": True},
|
||||
),
|
||||
"cFilter_ENUM_string": (
|
||||
EpicsSignalWithRBV,
|
||||
(f"ES:AMP5004:cFilter{num}_ENUM"),
|
||||
{"kind": "config", "doc": f"Filter of ch{num} -> IC{num-1}", "string": True},
|
||||
),
|
||||
}
|
||||
amp = Dcpt(amp_signals)
|
||||
gmes = Cpt(GasMixSetupControl, suffix=f"ES-GMES:IC{num-1}")
|
||||
gmes_status = Cpt(EpicsSignalRO, suffix="ES-GMES:StatusMsg0", kind="config", doc="Status")
|
||||
gmes_status_msg = Cpt(EpicsSignalRO, suffix="ES-GMES:StatusMsg0", kind="config", doc="Status")
|
||||
hv = Cpt(HighVoltageSuppliesControl, suffix=f"ES2-IC{num-1}:")
|
||||
hv_en_signals = {
|
||||
"ext_ena": (
|
||||
@@ -363,3 +372,63 @@ class IonizationChamber2(IonizationChamber0):
|
||||
"ena": (EpicsSignal, "ES2-IC12:HV-Ena", {"kind": "config", "doc": "Enable signal of HV"}),
|
||||
}
|
||||
hv_en = Dcpt(hv_en_signals)
|
||||
|
||||
class Pips(IonizationChamber0):
|
||||
"""Pips, prefix should be 'X01DA-'."""
|
||||
|
||||
USER_ACCESS = ["set_gain", "set_filter"]
|
||||
|
||||
num = 4
|
||||
amp_signals = {
|
||||
"cOnOff": (
|
||||
EpicsSignal,
|
||||
(f"ES:AMP5004.cOnOff{num}"),
|
||||
{"kind": "config", "doc": f"Enable ch{num} -> IC{num-1}"},
|
||||
),
|
||||
"cGain_ENUM": (
|
||||
EpicsSignalWithRBV,
|
||||
(f"ES:AMP5004:cGain{num}_ENUM"),
|
||||
{"kind": "config", "doc": f"Gain of ch{num} -> IC{num-1}"},
|
||||
),
|
||||
"cFilter_ENUM": (
|
||||
EpicsSignalWithRBV,
|
||||
(f"ES:AMP5004:cFilter{num}_ENUM"),
|
||||
{"kind": "config", "doc": f"Filter of ch{num} -> IC{num-1}"},
|
||||
),
|
||||
"cOnOff_string": (
|
||||
EpicsSignal,
|
||||
(f"ES:AMP5004.cOnOff{num}"),
|
||||
{"kind": "config", "doc": f"Enable ch{num} -> IC{num-1}", "string": True},
|
||||
),
|
||||
"cGain_ENUM_string": (
|
||||
EpicsSignalWithRBV,
|
||||
(f"ES:AMP5004:cGain{num}_ENUM"),
|
||||
{"kind": "config", "doc": f"Gain of ch{num} -> IC{num-1}", "string": True},
|
||||
),
|
||||
"cFilter_ENUM_string": (
|
||||
EpicsSignalWithRBV,
|
||||
(f"ES:AMP5004:cFilter{num}_ENUM"),
|
||||
{"kind": "config", "doc": f"Filter of ch{num} -> IC{num-1}", "string": True},
|
||||
),
|
||||
}
|
||||
amp = Dcpt(amp_signals)
|
||||
gmes = None
|
||||
gmes_status_msg = None
|
||||
hv = None
|
||||
hv_en_signals = None
|
||||
hv_en = None
|
||||
|
||||
@typechecked
|
||||
def set_hv(self, *_) -> None:
|
||||
"""Not available for the PIPS"""
|
||||
return None
|
||||
|
||||
@typechecked
|
||||
def set_grid(self, *_) -> None:
|
||||
"""Not available for the PIPS"""
|
||||
return None
|
||||
|
||||
@typechecked
|
||||
def fill(self, *_) -> None:
|
||||
"""Not available for the PIPS"""
|
||||
return None
|
||||
|
||||
@@ -9,15 +9,15 @@ used to ensure that the action is executed completely. This is believed
|
||||
to allow for a more stable execution of the action."""
|
||||
|
||||
import time
|
||||
from typing import Any, Literal
|
||||
from typing import Literal
|
||||
|
||||
from bec_lib.devicemanager import ScanInfo
|
||||
from bec_lib.logger import bec_logger
|
||||
from ophyd import Component as Cpt
|
||||
from ophyd import DeviceStatus, Signal, StatusBase
|
||||
from ophyd.status import SubscriptionStatus
|
||||
from ophyd import DeviceStatus, StatusBase
|
||||
from ophyd.status import WaitTimeoutError
|
||||
from ophyd_devices import CompareStatus, ProgressSignal, TransitionStatus
|
||||
from ophyd_devices.interfaces.base_classes.psi_device_base import PSIDeviceBase
|
||||
from ophyd_devices.utils.errors import DeviceStopError
|
||||
from pydantic import BaseModel, Field
|
||||
from typeguard import typechecked
|
||||
|
||||
@@ -55,16 +55,18 @@ class ScanParameter(BaseModel):
|
||||
|
||||
scan_time: float | None = Field(None, description="Scan time for a half oscillation")
|
||||
scan_duration: float | None = Field(None, description="Duration of the scan")
|
||||
xrd_enable_low: bool | None = Field(
|
||||
None, description="XRD enabled for low, should be PV trig_ena_lo_enum"
|
||||
break_enable_low: bool | None = Field(
|
||||
None, description="Break enabled for low, should be PV trig_ena_lo_enum"
|
||||
) # trig_enable_low: bool = None
|
||||
xrd_enable_high: bool | None = Field(
|
||||
None, description="XRD enabled for high, should be PV trig_ena_hi_enum"
|
||||
break_enable_high: bool | None = Field(
|
||||
None, description="Break enabled for high, should be PV trig_ena_hi_enum"
|
||||
) # trig_enable_high: bool = None
|
||||
exp_time_low: float | None = Field(None, description="Exposure time low energy/angle")
|
||||
exp_time_high: float | None = Field(None, description="Exposure time high energy/angle")
|
||||
break_time_low: float | None = Field(None, description="Break time low energy/angle")
|
||||
break_time_high: float | None = Field(None, description="Break time high energy/angle")
|
||||
cycle_low: int | None = Field(None, description="Cycle for low energy/angle")
|
||||
cycle_high: int | None = Field(None, description="Cycle for high energy/angle")
|
||||
exp_time: float | None = Field(None, description="XRD trigger period")
|
||||
n_of_trigger: int | None = Field(None, description="Amount of XRD triggers")
|
||||
start: float | None = Field(None, description="Start value for energy/angle")
|
||||
stop: float | None = Field(None, description="Stop value for energy/angle")
|
||||
p_kink: float | None = Field(None, description="P Kink")
|
||||
@@ -81,7 +83,9 @@ class Mo1Bragg(PSIDeviceBase, Mo1BraggPositioner):
|
||||
The prefix to connect to the soft IOC is X01DA-OP-MO1:BRAGG:
|
||||
"""
|
||||
|
||||
USER_ACCESS = ["set_advanced_xas_settings"]
|
||||
progress_signal = Cpt(ProgressSignal, name="progress_signal")
|
||||
|
||||
USER_ACCESS = ["set_advanced_xas_settings", "set_xtal"]
|
||||
|
||||
def __init__(self, name: str, prefix: str = "", scan_info: ScanInfo | None = None, **kwargs): # type: ignore
|
||||
"""
|
||||
@@ -93,7 +97,7 @@ class Mo1Bragg(PSIDeviceBase, Mo1BraggPositioner):
|
||||
"""
|
||||
super().__init__(name=name, scan_info=scan_info, prefix=prefix, **kwargs)
|
||||
self.scan_parameter = ScanParameter()
|
||||
self.timeout_for_pvwait = 2.5
|
||||
self.timeout_for_pvwait = 7.5
|
||||
|
||||
########################################
|
||||
# Beamline Specific Implementations #
|
||||
@@ -120,7 +124,11 @@ class Mo1Bragg(PSIDeviceBase, Mo1BraggPositioner):
|
||||
|
||||
Information about the upcoming scan can be accessed from the scan_info (self.scan_info.msg) object.
|
||||
"""
|
||||
self._check_scan_msg(ScanControlLoadMessage.PENDING)
|
||||
if self.scan_control.scan_msg.get() != ScanControlLoadMessage.PENDING:
|
||||
status = CompareStatus(self.scan_control.scan_msg, ScanControlLoadMessage.PENDING)
|
||||
self.cancel_on_stop(status)
|
||||
self.scan_control.scan_val_reset.put(1)
|
||||
status.wait(timeout=self.timeout_for_pvwait)
|
||||
|
||||
scan_name = self.scan_info.msg.scan_name
|
||||
self._update_scan_parameter()
|
||||
@@ -133,10 +141,12 @@ class Mo1Bragg(PSIDeviceBase, Mo1BraggPositioner):
|
||||
self.set_trig_settings(
|
||||
enable_low=False,
|
||||
enable_high=False,
|
||||
exp_time_low=0,
|
||||
exp_time_high=0,
|
||||
break_time_low=0,
|
||||
break_time_high=0,
|
||||
cycle_low=0,
|
||||
cycle_high=0,
|
||||
exp_time=0,
|
||||
n_of_trigger=0,
|
||||
)
|
||||
self.set_scan_control_settings(
|
||||
mode=ScanControlMode.SIMPLE, scan_duration=self.scan_parameter.scan_duration
|
||||
@@ -148,12 +158,14 @@ class Mo1Bragg(PSIDeviceBase, Mo1BraggPositioner):
|
||||
scan_time=self.scan_parameter.scan_time,
|
||||
)
|
||||
self.set_trig_settings(
|
||||
enable_low=self.scan_parameter.xrd_enable_low, # enable_low=self.scan_parameter.trig_enable_low,
|
||||
enable_high=self.scan_parameter.xrd_enable_high, # enable_high=self.scan_parameter.trig_enable_high,
|
||||
exp_time_low=self.scan_parameter.exp_time_low,
|
||||
exp_time_high=self.scan_parameter.exp_time_high,
|
||||
enable_low=self.scan_parameter.break_enable_low,
|
||||
enable_high=self.scan_parameter.break_enable_high,
|
||||
break_time_low=self.scan_parameter.break_time_low,
|
||||
break_time_high=self.scan_parameter.break_time_high,
|
||||
cycle_low=self.scan_parameter.cycle_low,
|
||||
cycle_high=self.scan_parameter.cycle_high,
|
||||
exp_time=self.scan_parameter.exp_time,
|
||||
n_of_trigger=self.scan_parameter.n_of_trigger,
|
||||
)
|
||||
self.set_scan_control_settings(
|
||||
mode=ScanControlMode.SIMPLE, scan_duration=self.scan_parameter.scan_duration
|
||||
@@ -169,10 +181,12 @@ class Mo1Bragg(PSIDeviceBase, Mo1BraggPositioner):
|
||||
self.set_trig_settings(
|
||||
enable_low=False,
|
||||
enable_high=False,
|
||||
exp_time_low=0,
|
||||
exp_time_high=0,
|
||||
break_time_low=0,
|
||||
break_time_high=0,
|
||||
cycle_low=0,
|
||||
cycle_high=0,
|
||||
exp_time=0,
|
||||
n_of_trigger=0,
|
||||
)
|
||||
self.set_scan_control_settings(
|
||||
mode=ScanControlMode.ADVANCED, scan_duration=self.scan_parameter.scan_duration
|
||||
@@ -186,26 +200,30 @@ class Mo1Bragg(PSIDeviceBase, Mo1BraggPositioner):
|
||||
e_kink=self.scan_parameter.e_kink,
|
||||
)
|
||||
self.set_trig_settings(
|
||||
enable_low=self.scan_parameter.xrd_enable_low, # enable_low=self.scan_parameter.trig_enable_low,
|
||||
enable_high=self.scan_parameter.xrd_enable_high, # enable_high=self.scan_parameter.trig_enable_high,
|
||||
exp_time_low=self.scan_parameter.exp_time_low,
|
||||
exp_time_high=self.scan_parameter.exp_time_high,
|
||||
enable_low=self.scan_parameter.break_enable_low,
|
||||
enable_high=self.scan_parameter.break_enable_high,
|
||||
break_time_low=self.scan_parameter.break_time_low,
|
||||
break_time_high=self.scan_parameter.break_time_high,
|
||||
cycle_low=self.scan_parameter.cycle_low,
|
||||
cycle_high=self.scan_parameter.cycle_high,
|
||||
exp_time=self.scan_parameter.exp_time,
|
||||
n_of_trigger=self.scan_parameter.n_of_trigger,
|
||||
)
|
||||
self.set_scan_control_settings(
|
||||
mode=ScanControlMode.ADVANCED, scan_duration=self.scan_parameter.scan_duration
|
||||
)
|
||||
else:
|
||||
return
|
||||
# Setting scan duration seems to lag behind slightly in the backend, include small sleep
|
||||
logger.info(f"Sleeping for one second")
|
||||
time.sleep(1)
|
||||
logger.info(f"Device {self.name}, done sleeping")
|
||||
# Load the scan parameters to the controller
|
||||
status = CompareStatus(self.scan_control.scan_msg, ScanControlLoadMessage.SUCCESS)
|
||||
self.cancel_on_stop(status)
|
||||
self.scan_control.scan_load.put(1)
|
||||
# Wait for params to be checked from controller
|
||||
self.wait_for_signal(
|
||||
self.scan_control.scan_msg,
|
||||
ScanControlLoadMessage.SUCCESS,
|
||||
timeout=self.timeout_for_pvwait,
|
||||
)
|
||||
status.wait(self.timeout_for_pvwait)
|
||||
return None
|
||||
|
||||
def on_unstage(self) -> DeviceStatus | StatusBase | None:
|
||||
@@ -213,32 +231,28 @@ class Mo1Bragg(PSIDeviceBase, Mo1BraggPositioner):
|
||||
if self.stopped is True:
|
||||
logger.warning(f"Resetting stopped in unstage for device {self.name}.")
|
||||
self._stopped = False
|
||||
current_state = self.scan_control.scan_msg.get()
|
||||
# Case 1, message is already ScanControlLoadMessage.PENDING
|
||||
if current_state == ScanControlLoadMessage.PENDING:
|
||||
return None
|
||||
# Case 2, probably called after scan, backend should resolve on its own. Timeout to wait
|
||||
if current_state in [ScanControlLoadMessage.STARTED, ScanControlLoadMessage.SUCCESS]:
|
||||
if self.scan_control.scan_msg.get() in [
|
||||
ScanControlLoadMessage.STARTED,
|
||||
ScanControlLoadMessage.SUCCESS,
|
||||
]:
|
||||
status = CompareStatus(self.scan_control.scan_msg, ScanControlLoadMessage.PENDING)
|
||||
self.cancel_on_stop(status)
|
||||
try:
|
||||
self.wait_for_signal(
|
||||
self.scan_control.scan_msg,
|
||||
ScanControlLoadMessage.PENDING,
|
||||
timeout=self.timeout_for_pvwait,
|
||||
)
|
||||
return
|
||||
except TimeoutError:
|
||||
status.wait(2)
|
||||
return None
|
||||
except WaitTimeoutError:
|
||||
logger.warning(
|
||||
f"Timeout in on_unstage of {self.name} after {self.timeout_for_pvwait}s, current scan_control_message : {self.scan_control.scan_msg.get()}"
|
||||
)
|
||||
|
||||
def callback(*, old_value, value, **kwargs):
|
||||
if value == ScanControlLoadMessage.PENDING:
|
||||
return True
|
||||
return False
|
||||
|
||||
status = SubscriptionStatus(self.scan_control.scan_msg, callback=callback)
|
||||
self.scan_control.scan_val_reset.put(1)
|
||||
status.wait(timeout=self.timeout_for_pvwait)
|
||||
status = CompareStatus(self.scan_control.scan_msg, ScanControlLoadMessage.PENDING)
|
||||
self.cancel_on_stop(status)
|
||||
self.scan_control.scan_val_reset.put(1)
|
||||
status.wait(timeout=self.timeout_for_pvwait)
|
||||
else:
|
||||
status = CompareStatus(self.scan_control.scan_msg, ScanControlLoadMessage.PENDING)
|
||||
self.cancel_on_stop(status)
|
||||
self.scan_control.scan_val_reset.put(1)
|
||||
status.wait(timeout=self.timeout_for_pvwait)
|
||||
return None
|
||||
|
||||
def on_pre_scan(self) -> DeviceStatus | StatusBase | None:
|
||||
@@ -249,20 +263,8 @@ class Mo1Bragg(PSIDeviceBase, Mo1BraggPositioner):
|
||||
|
||||
def on_complete(self) -> DeviceStatus | StatusBase | None:
|
||||
"""Called to inquire if a device has completed a scans."""
|
||||
|
||||
def wait_for_complete():
|
||||
"""Wait for the scan to complete. No timeout is set."""
|
||||
start_time = time.time()
|
||||
while True:
|
||||
if self.stopped is True:
|
||||
raise DeviceStopError(
|
||||
f"Device {self.name} was stopped while waiting for scan to complete"
|
||||
)
|
||||
if self.scan_control.scan_done.get() == 1:
|
||||
return
|
||||
time.sleep(0.1)
|
||||
|
||||
status = self.task_handler.submit_task(wait_for_complete)
|
||||
status = CompareStatus(self.scan_control.scan_done, 1)
|
||||
self.cancel_on_stop(status)
|
||||
return status
|
||||
|
||||
def on_kickoff(self) -> DeviceStatus | StatusBase | None:
|
||||
@@ -274,13 +276,13 @@ class Mo1Bragg(PSIDeviceBase, Mo1BraggPositioner):
|
||||
if scan_duration < 0.1
|
||||
else self.scan_control.scan_start_timer.put
|
||||
)
|
||||
|
||||
def callback(*, old_value, value, **kwargs):
|
||||
if old_value == ScanControlScanStatus.READY and value == ScanControlScanStatus.RUNNING:
|
||||
return True
|
||||
return False
|
||||
|
||||
status = SubscriptionStatus(self.scan_control.scan_status, callback=callback)
|
||||
status = TransitionStatus(
|
||||
self.scan_control.scan_status,
|
||||
transitions=[ScanControlScanStatus.READY, ScanControlScanStatus.RUNNING],
|
||||
strict=True,
|
||||
failure_states=[ScanControlScanStatus.PARAMETER_WRONG],
|
||||
)
|
||||
self.cancel_on_stop(status)
|
||||
start_func(1)
|
||||
return status
|
||||
|
||||
@@ -289,9 +291,6 @@ class Mo1Bragg(PSIDeviceBase, Mo1BraggPositioner):
|
||||
self.stopped = True # Needs to be set to stop motion
|
||||
|
||||
######### Utility Methods #########
|
||||
|
||||
# FIXME this should become the ProgressSignal
|
||||
# pylint: disable=unused-argument
|
||||
def _progress_update(self, value, **kwargs) -> None:
|
||||
"""Callback method to update the scan progress, runs a callback
|
||||
to SUB_PROGRESS subscribers, i.e. BEC.
|
||||
@@ -300,12 +299,7 @@ class Mo1Bragg(PSIDeviceBase, Mo1BraggPositioner):
|
||||
value (int) : current progress value
|
||||
"""
|
||||
max_value = 100
|
||||
self._run_subs(
|
||||
sub_type=self.SUB_PROGRESS,
|
||||
value=value,
|
||||
max_value=max_value,
|
||||
done=bool(max_value == value),
|
||||
)
|
||||
self.progress_signal.put(value=value, max_value=max_value, done=bool(max_value == value))
|
||||
|
||||
def set_xas_settings(self, low: float, high: float, scan_time: float) -> None:
|
||||
"""Set XAS parameters for upcoming scan.
|
||||
@@ -315,30 +309,20 @@ class Mo1Bragg(PSIDeviceBase, Mo1BraggPositioner):
|
||||
high (float): High energy/angle value of the scan
|
||||
scan_time (float): Time for a half oscillation
|
||||
"""
|
||||
move_type = self.move_type.get()
|
||||
if move_type == MoveType.ENERGY:
|
||||
self.scan_settings.s_scan_energy_lo.put(low)
|
||||
self.scan_settings.s_scan_energy_hi.put(high)
|
||||
else:
|
||||
self.scan_settings.s_scan_angle_lo.put(low)
|
||||
self.scan_settings.s_scan_angle_hi.put(high)
|
||||
self.scan_settings.s_scan_scantime.put(scan_time)
|
||||
|
||||
def wait_for_signal(self, signal: Cpt, value: Any, timeout: float | None = None) -> None:
|
||||
"""Wait for a signal to reach a certain value."""
|
||||
if timeout is None:
|
||||
timeout = self.timeout_for_pvwait
|
||||
start_time = time.time()
|
||||
while time.time() - start_time < timeout:
|
||||
if signal.get() == value:
|
||||
return None
|
||||
if self.stopped is True: # Should this check be optional or configurable?!
|
||||
raise DeviceStopError(f"Device {self.name} was stopped while waiting for signal")
|
||||
time.sleep(0.1)
|
||||
# If we end up here, the status did not resolve
|
||||
raise TimeoutError(
|
||||
f"Device {self.name} run into timeout after {timeout}s for signal {signal.name} with value {signal.get()}, expected {value}"
|
||||
)
|
||||
status_list = []
|
||||
|
||||
status_list.append(self.scan_settings.s_scan_energy_lo.set(low))
|
||||
self.cancel_on_stop(status_list[-1])
|
||||
|
||||
status_list.append(self.scan_settings.s_scan_energy_hi.set(high))
|
||||
self.cancel_on_stop(status_list[-1])
|
||||
|
||||
status_list.append(self.scan_settings.s_scan_scantime.set(scan_time))
|
||||
self.cancel_on_stop(status_list[-1])
|
||||
|
||||
for s in status_list:
|
||||
s.wait(timeout=self.timeout_for_pvwait)
|
||||
|
||||
@typechecked
|
||||
def convert_angle_energy(
|
||||
@@ -355,15 +339,19 @@ class Mo1Bragg(PSIDeviceBase, Mo1BraggPositioner):
|
||||
"""
|
||||
self.calculator.calc_reset.put(0)
|
||||
self.calculator.calc_reset.put(1)
|
||||
self.wait_for_signal(self.calculator.calc_done, 0)
|
||||
status = CompareStatus(self.calculator.calc_done, 0)
|
||||
self.cancel_on_stop(status)
|
||||
status.wait(self.timeout_for_pvwait)
|
||||
|
||||
if mode == "AngleToEnergy":
|
||||
self.calculator.calc_angle.put(inp)
|
||||
elif mode == "EnergyToAngle":
|
||||
self.calculator.calc_energy.put(inp)
|
||||
|
||||
self.wait_for_signal(self.calculator.calc_done, 1)
|
||||
time.sleep(0.25) # Needed due to update frequency of softIOC
|
||||
status = CompareStatus(self.calculator.calc_done, 1)
|
||||
self.cancel_on_stop(status)
|
||||
status.wait(self.timeout_for_pvwait)
|
||||
time.sleep(0.25) # TODO needed still? Needed due to update frequency of softIOC
|
||||
if mode == "AngleToEnergy":
|
||||
return self.calculator.calc_energy.get()
|
||||
elif mode == "EnergyToAngle":
|
||||
@@ -381,18 +369,10 @@ class Mo1Bragg(PSIDeviceBase, Mo1BraggPositioner):
|
||||
p_kink (float): Position of kink in %
|
||||
e_kink (float): Energy of kink in eV
|
||||
"""
|
||||
# TODO Add fallback solution for automatic testing, otherwise test will fail
|
||||
# because no monochromator will calculate the angle
|
||||
# Unsure how to implement this
|
||||
|
||||
move_type = self.move_type.get()
|
||||
if move_type == MoveType.ENERGY:
|
||||
e_kink_deg = self.convert_angle_energy(mode="EnergyToAngle", inp=e_kink)
|
||||
# Angle and Energy are inverse proportional!
|
||||
high_deg = self.convert_angle_energy(mode="EnergyToAngle", inp=low)
|
||||
low_deg = self.convert_angle_energy(mode="EnergyToAngle", inp=high)
|
||||
else:
|
||||
raise Mo1BraggError("MoveType Angle not implemented for advanced scans, use Energy")
|
||||
e_kink_deg = self.convert_angle_energy(mode="EnergyToAngle", inp=e_kink)
|
||||
# Angle and Energy are inverse proportional!
|
||||
high_deg = self.convert_angle_energy(mode="EnergyToAngle", inp=low)
|
||||
low_deg = self.convert_angle_energy(mode="EnergyToAngle", inp=high)
|
||||
|
||||
pos, vel, dt = compute_spline(
|
||||
low_deg=low_deg,
|
||||
@@ -402,37 +382,72 @@ class Mo1Bragg(PSIDeviceBase, Mo1BraggPositioner):
|
||||
scan_time=scan_time,
|
||||
)
|
||||
|
||||
self.scan_settings.a_scan_pos.set(pos)
|
||||
self.scan_settings.a_scan_vel.set(vel)
|
||||
self.scan_settings.a_scan_time.set(dt)
|
||||
status_list = []
|
||||
|
||||
status_list.append(self.scan_settings.a_scan_pos.set(pos))
|
||||
self.cancel_on_stop(status_list[-1])
|
||||
|
||||
status_list.append(self.scan_settings.a_scan_vel.set(vel))
|
||||
self.cancel_on_stop(status_list[-1])
|
||||
|
||||
status_list.append(self.scan_settings.a_scan_time.set(dt))
|
||||
self.cancel_on_stop(status_list[-1])
|
||||
|
||||
for s in status_list:
|
||||
s.wait(timeout=self.timeout_for_pvwait)
|
||||
|
||||
def set_trig_settings(
|
||||
self,
|
||||
enable_low: bool,
|
||||
enable_high: bool,
|
||||
exp_time_low: int,
|
||||
exp_time_high: int,
|
||||
break_time_low: float,
|
||||
break_time_high: float,
|
||||
cycle_low: int,
|
||||
cycle_high: int,
|
||||
exp_time: float,
|
||||
n_of_trigger: int,
|
||||
) -> None:
|
||||
"""Set TRIG settings for the upcoming scan.
|
||||
|
||||
Args:
|
||||
enable_low (bool): Enable TRIG for low energy/angle
|
||||
enable_high (bool): Enable TRIG for high energy/angle
|
||||
num_trigger_low (int): Number of triggers for low energy/angle
|
||||
num_trigger_high (int): Number of triggers for high energy/angle
|
||||
exp_time_low (int): Exposure time for low energy/angle
|
||||
exp_time_high (int): Exposure time for high energy/angle
|
||||
break_time_low (float): Exposure time for low energy/angle
|
||||
break_time_high (float): Exposure time for high energy/angle
|
||||
cycle_low (int): Cycle for low energy/angle
|
||||
cycle_high (int): Cycle for high energy/angle
|
||||
exp_time (float): Length of 1 trigger period in seconds
|
||||
n_of_trigger (int): Amount of triggers to be fired during brake
|
||||
"""
|
||||
self.scan_settings.trig_ena_hi_enum.put(int(enable_high))
|
||||
self.scan_settings.trig_ena_lo_enum.put(int(enable_low))
|
||||
self.scan_settings.trig_time_hi.put(exp_time_high)
|
||||
self.scan_settings.trig_time_lo.put(exp_time_low)
|
||||
self.scan_settings.trig_every_n_hi.put(cycle_high)
|
||||
self.scan_settings.trig_every_n_lo.put(cycle_low)
|
||||
|
||||
status_list = []
|
||||
|
||||
status_list.append(self.scan_settings.trig_ena_hi_enum.set(int(enable_high)))
|
||||
self.cancel_on_stop(status_list[-1])
|
||||
|
||||
status_list.append(self.scan_settings.trig_ena_lo_enum.set(int(enable_low)))
|
||||
self.cancel_on_stop(status_list[-1])
|
||||
|
||||
status_list.append(self.scan_settings.trig_time_hi.set(break_time_high))
|
||||
self.cancel_on_stop(status_list[-1])
|
||||
|
||||
status_list.append(self.scan_settings.trig_time_lo.set(break_time_low))
|
||||
self.cancel_on_stop(status_list[-1])
|
||||
|
||||
status_list.append(self.scan_settings.trig_every_n_hi.set(cycle_high))
|
||||
self.cancel_on_stop(status_list[-1])
|
||||
|
||||
status_list.append(self.scan_settings.trig_every_n_lo.set(cycle_low))
|
||||
self.cancel_on_stop(status_list[-1])
|
||||
|
||||
status_list.append(self.trigger_settings.xrd_trig_period.set(exp_time))
|
||||
self.cancel_on_stop(status_list[-1])
|
||||
|
||||
status_list.append(self.trigger_settings.xrd_n_of_trig.set(n_of_trigger))
|
||||
self.cancel_on_stop(status_list[-1])
|
||||
|
||||
for s in status_list:
|
||||
s.wait(timeout=self.timeout_for_pvwait)
|
||||
|
||||
def set_scan_control_settings(self, mode: ScanControlMode, scan_duration: float) -> None:
|
||||
"""Set the scan control settings for the upcoming scan.
|
||||
@@ -442,8 +457,17 @@ class Mo1Bragg(PSIDeviceBase, Mo1BraggPositioner):
|
||||
scan_duration (float): Duration of the scan
|
||||
"""
|
||||
val = ScanControlMode(mode).value
|
||||
self.scan_control.scan_mode_enum.put(val)
|
||||
self.scan_control.scan_duration.put(scan_duration)
|
||||
|
||||
status_list = []
|
||||
|
||||
status_list.append(self.scan_control.scan_mode_enum.set(val))
|
||||
self.cancel_on_stop(status_list[-1])
|
||||
|
||||
status_list.append(self.scan_control.scan_duration.set(scan_duration))
|
||||
self.cancel_on_stop(status_list[-1])
|
||||
|
||||
for s in status_list:
|
||||
s.wait(timeout=self.timeout_for_pvwait)
|
||||
|
||||
def _update_scan_parameter(self):
|
||||
"""Get the scan_info parameters for the scan."""
|
||||
@@ -453,39 +477,3 @@ class Mo1Bragg(PSIDeviceBase, Mo1BraggPositioner):
|
||||
for key, value in self.scan_info.msg.request_inputs["kwargs"].items():
|
||||
if hasattr(self.scan_parameter, key):
|
||||
setattr(self.scan_parameter, key, value)
|
||||
|
||||
def _check_scan_msg(self, target_state: ScanControlLoadMessage) -> None:
|
||||
"""Check if the scan message is gettting available
|
||||
|
||||
Args:
|
||||
target_state (ScanControlLoadMessage): Target state to check for
|
||||
|
||||
Raises:
|
||||
TimeoutError: If the scan message is not available after the timeout
|
||||
"""
|
||||
try:
|
||||
self.wait_for_signal(self.scan_control.scan_msg, target_state, timeout=1)
|
||||
except TimeoutError as exc:
|
||||
logger.warning(
|
||||
f"Resetting scan validation in stage for state: {ScanControlLoadMessage(self.scan_control.scan_msg.get())}, "
|
||||
f"retry .get() on scan_control: {ScanControlLoadMessage(self.scan_control.scan_msg.get())} and sleeping 1s"
|
||||
)
|
||||
current_scan_msg = self.scan_control.scan_msg.get()
|
||||
|
||||
def callback(*, old_value, value, **kwargs):
|
||||
if old_value == current_scan_msg and value == target_state:
|
||||
return True
|
||||
return False
|
||||
|
||||
status = SubscriptionStatus(self.scan_control.scan_msg, callback=callback)
|
||||
self.scan_control.scan_val_reset.put(1)
|
||||
|
||||
status.wait(timeout=self.timeout_for_pvwait)
|
||||
|
||||
# try:
|
||||
# self.wait_for_signal(self.scan_control.scan_msg, target_state, timeout=4)
|
||||
# except TimeoutError as exc:
|
||||
# raise TimeoutError(
|
||||
# f"Timeout after {self.timeout_for_pvwait} while waiting for scan status,"
|
||||
# f" current state: {ScanControlScanStatus(self.scan_control.scan_msg.get())}"
|
||||
# ) from exc
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
"""Positioner implementation with readback angle of the MO1 Bragg positioner."""
|
||||
|
||||
from ophyd import Component as Cpt
|
||||
from ophyd import EpicsSignalRO, EpicsSignalWithRBV
|
||||
|
||||
from debye_bec.devices.mo1_bragg.mo1_bragg_devices import Mo1BraggPositioner
|
||||
|
||||
|
||||
class Mo1BraggAngle(Mo1BraggPositioner):
|
||||
"""Positioner implementation with readback angle of the MO1 Bragg positioner."""
|
||||
|
||||
readback = Cpt(EpicsSignalRO, suffix="feedback_pos_angle_RBV", kind="normal", auto_monitor=True)
|
||||
setpoint = Cpt(EpicsSignalWithRBV, suffix="set_abs_pos_angle", kind="normal", auto_monitor=True)
|
||||
low_lim = Cpt(EpicsSignalRO, suffix="lo_lim_pos_angle_RBV", kind="config", auto_monitor=True)
|
||||
high_lim = Cpt(EpicsSignalRO, suffix="hi_lim_pos_angle_RBV", kind="config", auto_monitor=True)
|
||||
|
||||
@property
|
||||
def egu(self) -> str:
|
||||
"""Return the engineering unit of the positioner."""
|
||||
return "deg"
|
||||
@@ -76,15 +76,39 @@ class Mo1BraggEncoder(Device):
|
||||
class Mo1BraggCrystal(Device):
|
||||
"""Mo1 Bragg PVs to set the crystal parameters"""
|
||||
|
||||
offset_si111 = Cpt(EpicsSignalWithRBV, suffix="offset_si111", kind="config")
|
||||
offset_si311 = Cpt(EpicsSignalWithRBV, suffix="offset_si311", kind="config")
|
||||
bragg_off_si111 = Cpt(EpicsSignalWithRBV, suffix="bragg_off_si111", kind="config")
|
||||
bragg_off_si311 = Cpt(EpicsSignalWithRBV, suffix="bragg_off_si311", kind="config")
|
||||
phi_off_si111 = Cpt(EpicsSignalWithRBV, suffix="phi_off_si111", kind="config")
|
||||
phi_off_si311 = Cpt(EpicsSignalWithRBV, suffix="phi_off_si311", kind="config")
|
||||
azm_off_si111 = Cpt(EpicsSignalWithRBV, suffix="azm_off_si111", kind="config")
|
||||
azm_off_si311 = Cpt(EpicsSignalWithRBV, suffix="azm_off_si311", kind="config")
|
||||
miscut_si111 = Cpt(EpicsSignalWithRBV, suffix="miscut_si111", kind="config")
|
||||
miscut_si311 = Cpt(EpicsSignalWithRBV, suffix="miscut_si311", kind="config")
|
||||
xtal_enum = Cpt(EpicsSignalWithRBV, suffix="xtal_ENUM", kind="config")
|
||||
d_spacing_si111 = Cpt(EpicsSignalWithRBV, suffix="d_spacing_si111", kind="config")
|
||||
d_spacing_si311 = Cpt(EpicsSignalWithRBV, suffix="d_spacing_si311", kind="config")
|
||||
set_offset = Cpt(EpicsSignal, suffix="set_offset", kind="config", put_complete=True)
|
||||
current_d_spacing = Cpt(
|
||||
EpicsSignalRO, suffix="current_d_spacing_RBV", kind="normal", auto_monitor=True
|
||||
)
|
||||
current_bragg_off = Cpt(
|
||||
EpicsSignalRO, suffix="current_bragg_off_RBV", kind="normal", auto_monitor=True
|
||||
)
|
||||
current_phi_off = Cpt(
|
||||
EpicsSignalRO, suffix="current_phi_off_RBV", kind="normal", auto_monitor=True
|
||||
)
|
||||
current_azm_off = Cpt(
|
||||
EpicsSignalRO, suffix="current_azm_off_RBV", kind="normal", auto_monitor=True
|
||||
)
|
||||
current_miscut = Cpt(
|
||||
EpicsSignalRO, suffix="current_miscut_RBV", kind="normal", auto_monitor=True
|
||||
)
|
||||
current_xtal = Cpt(
|
||||
EpicsSignalRO, suffix="current_xtal_ENUM_RBV", kind="normal", auto_monitor=True
|
||||
)
|
||||
current_xtal_string = Cpt(
|
||||
EpicsSignalRO, suffix="current_xtal_ENUM_RBV", kind="normal", auto_monitor=True, string=True
|
||||
)
|
||||
|
||||
|
||||
class Mo1BraggScanSettings(Device):
|
||||
@@ -129,21 +153,29 @@ class Mo1TriggerSettings(Device):
|
||||
xrd_trig_src_enum = Cpt(EpicsSignalWithRBV, suffix="xrd_trig_src_ENUM", kind="config")
|
||||
xrd_trig_mode_enum = Cpt(EpicsSignalWithRBV, suffix="xrd_trig_mode_ENUM", kind="config")
|
||||
xrd_trig_len = Cpt(EpicsSignalWithRBV, suffix="xrd_trig_len", kind="config")
|
||||
xrd_trig_period = Cpt(EpicsSignalWithRBV, suffix="xrd_trig_period", kind="config")
|
||||
xrd_n_of_trig = Cpt(EpicsSignalWithRBV, suffix="xrd_n_of_trig", kind="config")
|
||||
xrd_trig_req = Cpt(EpicsSignal, suffix="xrd_trig_req", kind="config")
|
||||
|
||||
falcon_trig_src_enum = Cpt(EpicsSignalWithRBV, suffix="falcon_trig_src_ENUM", kind="config")
|
||||
falcon_trig_mode_enum = Cpt(EpicsSignalWithRBV, suffix="falcon_trig_mode_ENUM", kind="config")
|
||||
falcon_trig_len = Cpt(EpicsSignalWithRBV, suffix="falcon_trig_len", kind="config")
|
||||
falcon_trig_period = Cpt(EpicsSignalWithRBV, suffix="falcon_trig_period", kind="config")
|
||||
falcon_n_of_trig = Cpt(EpicsSignalWithRBV, suffix="falcon_n_of_trig", kind="config")
|
||||
falcon_trig_req = Cpt(EpicsSignal, suffix="falcon_trig_req", kind="config")
|
||||
|
||||
univ1_trig_src_enum = Cpt(EpicsSignalWithRBV, suffix="univ1_trig_src_ENUM", kind="config")
|
||||
univ1_trig_mode_enum = Cpt(EpicsSignalWithRBV, suffix="univ1_trig_mode_ENUM", kind="config")
|
||||
univ1_trig_len = Cpt(EpicsSignalWithRBV, suffix="univ1_trig_len", kind="config")
|
||||
univ1_trig_period = Cpt(EpicsSignalWithRBV, suffix="univ1_trig_period", kind="config")
|
||||
univ1_n_of_trig = Cpt(EpicsSignalWithRBV, suffix="univ1_n_of_trig", kind="config")
|
||||
univ1_trig_req = Cpt(EpicsSignal, suffix="univ1_trig_req", kind="config")
|
||||
|
||||
univ2_trig_src_enum = Cpt(EpicsSignalWithRBV, suffix="univ2_trig_src_ENUM", kind="config")
|
||||
univ2_trig_mode_enum = Cpt(EpicsSignalWithRBV, suffix="univ2_trig_mode_ENUM", kind="config")
|
||||
univ2_trig_len = Cpt(EpicsSignalWithRBV, suffix="univ2_trig_len", kind="config")
|
||||
univ2_trig_period = Cpt(EpicsSignalWithRBV, suffix="univ2_trig_period", kind="config")
|
||||
univ2_n_of_trig = Cpt(EpicsSignalWithRBV, suffix="univ2_n_of_trig", kind="config")
|
||||
univ2_trig_req = Cpt(EpicsSignal, suffix="univ2_trig_req", kind="config")
|
||||
|
||||
|
||||
@@ -189,13 +221,13 @@ class Mo1BraggScanControl(Device):
|
||||
|
||||
class Mo1BraggPositioner(Device, PositionerBase):
|
||||
"""
|
||||
Positioner implementation of the MO1 Bragg positioner.
|
||||
Positioner implementation with readback energy of the MO1 Bragg positioner.
|
||||
|
||||
The prefix to connect to the soft IOC is X01DA-OP-MO1:BRAGG:
|
||||
This soft IOC connects to the NI motor and its control loop.
|
||||
"""
|
||||
|
||||
USER_ACCESS = ["set_advanced_xas_settings"]
|
||||
USER_ACCESS = ["set_xtal"]
|
||||
|
||||
####### Sub-components ########
|
||||
# Namespace is cleaner and easier to maintain
|
||||
@@ -207,10 +239,6 @@ class Mo1BraggPositioner(Device, PositionerBase):
|
||||
scan_control = Cpt(Mo1BraggScanControl, "")
|
||||
status = Cpt(Mo1BraggStatus, "")
|
||||
|
||||
############# switch between energy and angle #############
|
||||
# TODO should be removed/replaced once decision about pseudo motor is made
|
||||
move_type = Cpt(MoveTypeSignal, value=MoveType.ENERGY, kind="config")
|
||||
|
||||
############# Energy PVs #############
|
||||
|
||||
readback = Cpt(
|
||||
@@ -226,21 +254,7 @@ class Mo1BraggPositioner(Device, PositionerBase):
|
||||
high_lim = Cpt(EpicsSignalRO, suffix="hi_lim_pos_energy_RBV", kind="config", auto_monitor=True)
|
||||
velocity = Cpt(EpicsSignalWithRBV, suffix="move_velocity", kind="config", auto_monitor=True)
|
||||
|
||||
########### Angle PVs #############
|
||||
|
||||
# TODO Pseudo motor for angle?
|
||||
feedback_pos_angle = Cpt(
|
||||
EpicsSignalRO, suffix="feedback_pos_angle_RBV", kind="normal", auto_monitor=True
|
||||
)
|
||||
setpoint_abs_angle = Cpt(
|
||||
EpicsSignalWithRBV, suffix="set_abs_pos_angle", kind="normal", auto_monitor=True
|
||||
)
|
||||
low_limit_angle = Cpt(
|
||||
EpicsSignalRO, suffix="lo_lim_pos_angle_RBV", kind="config", auto_monitor=True
|
||||
)
|
||||
high_limit_angle = Cpt(
|
||||
EpicsSignalRO, suffix="hi_lim_pos_angle_RBV", kind="config", auto_monitor=True
|
||||
)
|
||||
angle = Cpt(EpicsSignalRO, suffix="feedback_pos_angle_RBV", kind="normal", auto_monitor=True)
|
||||
|
||||
########## Move Command PVs ##########
|
||||
|
||||
@@ -271,9 +285,7 @@ class Mo1BraggPositioner(Device, PositionerBase):
|
||||
success (bool) : Flag to indicate if the motion was successful
|
||||
"""
|
||||
self.move_stop.put(1)
|
||||
if self._move_thread is not None:
|
||||
self._move_thread.join()
|
||||
self._move_thread = None
|
||||
self._stopped = True
|
||||
super().stop(success=success)
|
||||
|
||||
def stop_scan(self) -> None:
|
||||
@@ -290,9 +302,7 @@ class Mo1BraggPositioner(Device, PositionerBase):
|
||||
@property
|
||||
def limits(self) -> tuple:
|
||||
"""Return limits of the Bragg positioner"""
|
||||
if self.move_type.get() == MoveType.ENERGY:
|
||||
return (self.low_lim.get(), self.high_lim.get())
|
||||
return (self.low_limit_angle.get(), self.high_limit_angle.get())
|
||||
return (self.low_lim.get(), self.high_lim.get())
|
||||
|
||||
@property
|
||||
def low_limit(self) -> float:
|
||||
@@ -307,16 +317,12 @@ class Mo1BraggPositioner(Device, PositionerBase):
|
||||
@property
|
||||
def egu(self) -> str:
|
||||
"""Return the engineering units of the positioner"""
|
||||
if self.move_type.get() == MoveType.ENERGY:
|
||||
return "eV"
|
||||
return "deg"
|
||||
return "eV"
|
||||
|
||||
@property
|
||||
def position(self) -> float:
|
||||
"""Return the current position of Mo1Bragg, considering the move type"""
|
||||
move_type = self.move_type.get()
|
||||
move_cpt = self.readback if move_type == MoveType.ENERGY else self.feedback_pos_angle
|
||||
return move_cpt.get()
|
||||
return self.readback.get()
|
||||
|
||||
# pylint: disable=arguments-differ
|
||||
def check_value(self, value: float) -> None:
|
||||
@@ -332,7 +338,7 @@ class Mo1BraggPositioner(Device, PositionerBase):
|
||||
raise LimitError(f"position={value} not within limits {self.limits}")
|
||||
|
||||
def _move_and_finish(
|
||||
self, target_pos: float, move_cpt: Cpt, status: DeviceStatus, update_frequency: float = 0.1
|
||||
self, target_pos: float, status: DeviceStatus, update_frequency: float = 0.1
|
||||
) -> None:
|
||||
"""
|
||||
Method to be called in the move thread to move the Bragg positioner
|
||||
@@ -349,12 +355,14 @@ class Mo1BraggPositioner(Device, PositionerBase):
|
||||
update_frequency (float): Optional, frequency to update the current position of
|
||||
the motion, defaults to 0.1s
|
||||
"""
|
||||
motor_name = None
|
||||
try:
|
||||
# Set the target position on IOC
|
||||
move_cpt.put(target_pos)
|
||||
self.setpoint.put(target_pos)
|
||||
self.move_abs.put(1)
|
||||
# Currently sleep is needed due to delay in updates on PVs, maybe time can be reduced
|
||||
time.sleep(0.5)
|
||||
motor_name = self.name
|
||||
while self.motor_is_moving.get() == 0:
|
||||
if self.stopped:
|
||||
raise Mo1BraggStoppedError(f"Device {self.name} was stopped")
|
||||
@@ -364,10 +372,12 @@ class Mo1BraggPositioner(Device, PositionerBase):
|
||||
# pylint: disable=broad-except
|
||||
except Exception as exc:
|
||||
content = traceback.format_exc()
|
||||
logger.error(f"Error in move thread of device {self.name}: {content}")
|
||||
logger.error(
|
||||
f"Error in move thread of device {motor_name if motor_name else ''}: {content}"
|
||||
)
|
||||
status.set_exception(exc=exc)
|
||||
|
||||
def move(self, value: float, move_type: str | MoveType = None, **kwargs) -> DeviceStatus:
|
||||
def move(self, value: float, **kwargs) -> DeviceStatus:
|
||||
"""
|
||||
Move the Bragg positioner to the specified value, allows to
|
||||
switch between move types angle and energy.
|
||||
@@ -381,16 +391,12 @@ class Mo1BraggPositioner(Device, PositionerBase):
|
||||
DeviceStatus : status object to track the motion
|
||||
"""
|
||||
self._stopped = False
|
||||
if move_type is not None:
|
||||
self.move_type.put(move_type)
|
||||
move_type = self.move_type.get()
|
||||
move_cpt = self.setpoint if move_type == MoveType.ENERGY else self.setpoint_abs_angle
|
||||
|
||||
self.check_value(value)
|
||||
status = DeviceStatus(device=self)
|
||||
|
||||
self._move_thread = threading.Thread(
|
||||
target=self._move_and_finish, args=(value, move_cpt, status, 0.1)
|
||||
target=self._move_and_finish, args=(value, status, 0.1)
|
||||
)
|
||||
self._move_thread.start()
|
||||
return status
|
||||
@@ -402,8 +408,8 @@ class Mo1BraggPositioner(Device, PositionerBase):
|
||||
def set_xtal(
|
||||
self,
|
||||
xtal_enum: Literal["111", "311"],
|
||||
offset_si111: float = None,
|
||||
offset_si311: float = None,
|
||||
bragg_off_si111: float = None,
|
||||
bragg_off_si311: float = None,
|
||||
d_spacing_si111: float = None,
|
||||
d_spacing_si311: float = None,
|
||||
) -> None:
|
||||
@@ -411,15 +417,15 @@ class Mo1BraggPositioner(Device, PositionerBase):
|
||||
|
||||
Args:
|
||||
xtal_enum (Literal["111", "311"]) : Enum to set the crystal orientation
|
||||
offset_si111 (float) : Offset for the 111 crystal
|
||||
offset_si311 (float) : Offset for the 311 crystal
|
||||
bragg_off_si111 (float) : Offset for the 111 crystal
|
||||
bragg_off_si311 (float) : Offset for the 311 crystal
|
||||
d_spacing_si111 (float) : d-spacing for the 111 crystal
|
||||
d_spacing_si311 (float) : d-spacing for the 311 crystal
|
||||
"""
|
||||
if offset_si111 is not None:
|
||||
self.crystal.offset_si111.put(offset_si111)
|
||||
if offset_si311 is not None:
|
||||
self.crystal.offset_si311.put(offset_si311)
|
||||
if bragg_off_si111 is not None:
|
||||
self.crystal.bragg_off_si111.put(bragg_off_si111)
|
||||
if bragg_off_si311 is not None:
|
||||
self.crystal.bragg_off_si311.put(bragg_off_si311)
|
||||
if d_spacing_si111 is not None:
|
||||
self.crystal.d_spacing_si111.put(d_spacing_si111)
|
||||
if d_spacing_si311 is not None:
|
||||
|
||||
+359
-126
@@ -1,15 +1,17 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING, Literal, cast
|
||||
from typing import TYPE_CHECKING, Literal
|
||||
|
||||
from bec_lib.logger import bec_logger
|
||||
from ophyd import Component as Cpt
|
||||
from ophyd import Device, DeviceStatus, EpicsSignal, EpicsSignalRO, Kind, StatusBase
|
||||
from ophyd.status import WaitTimeoutError
|
||||
from ophyd_devices import CompareStatus, ProgressSignal, TransitionStatus
|
||||
from ophyd_devices.interfaces.base_classes.psi_device_base import PSIDeviceBase
|
||||
from ophyd_devices.sim.sim_signals import SetableSignal
|
||||
|
||||
from debye_bec.devices.nidaq.nidaq_enums import (
|
||||
EncoderTypes,
|
||||
EncoderFactors,
|
||||
NIDAQCompression,
|
||||
NidaqState,
|
||||
ReadoutRange,
|
||||
@@ -31,6 +33,107 @@ class NidaqControl(Device):
|
||||
"""Nidaq control class with all PVs"""
|
||||
|
||||
### Readback PVs for EpicsEmitter ###
|
||||
energy = Cpt(SetableSignal, value=0, kind=Kind.normal)
|
||||
|
||||
smpl_abs = Cpt(
|
||||
SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream sample absorption"
|
||||
)
|
||||
smpl_fluo = Cpt(
|
||||
SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream sample fluorescence"
|
||||
)
|
||||
ref_abs = Cpt(
|
||||
SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream reference absorption"
|
||||
)
|
||||
cisum = Cpt(
|
||||
SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream counter sum"
|
||||
)
|
||||
|
||||
ai0_mean = Cpt(
|
||||
SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream analog input 0, MEAN"
|
||||
)
|
||||
ai1_mean = Cpt(
|
||||
SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream analog input 1, MEAN"
|
||||
)
|
||||
ai2_mean = Cpt(
|
||||
SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream analog input 2, MEAN"
|
||||
)
|
||||
ai3_mean = Cpt(
|
||||
SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream analog input 3, MEAN"
|
||||
)
|
||||
ai4_mean = Cpt(
|
||||
SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream analog input 4, MEAN"
|
||||
)
|
||||
ai5_mean = Cpt(
|
||||
SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream analog input 5, MEAN"
|
||||
)
|
||||
ai6_mean = Cpt(
|
||||
SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream analog input 6, MEAN"
|
||||
)
|
||||
ai7_mean = Cpt(
|
||||
SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream analog input 7, MEAN"
|
||||
)
|
||||
|
||||
di0_max = Cpt(SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream digital input 0, MAX")
|
||||
di1_max = Cpt(SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream digital input 1, MAX")
|
||||
di2_max = Cpt(SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream digital input 2, MAX")
|
||||
di3_max = Cpt(SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream digital input 3, MAX")
|
||||
di4_max = Cpt(SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream digital input 4, MAX")
|
||||
|
||||
ci0_mean = Cpt(
|
||||
SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream counter input 0, MEAN"
|
||||
)
|
||||
ci1_mean = Cpt(
|
||||
SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream counter input 1, MEAN"
|
||||
)
|
||||
ci2_mean = Cpt(
|
||||
SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream counter input 2, MEAN"
|
||||
)
|
||||
ci3_mean = Cpt(
|
||||
SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream counter input 3, MEAN"
|
||||
)
|
||||
ci4_mean = Cpt(
|
||||
SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream counter input 4, MEAN"
|
||||
)
|
||||
ci5_mean = Cpt(
|
||||
SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream counter input 5, MEAN"
|
||||
)
|
||||
ci6_mean = Cpt(
|
||||
SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream counter input 6, MEAN"
|
||||
)
|
||||
ci7_mean = Cpt(
|
||||
SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream counter input 7, MEAN"
|
||||
)
|
||||
ci8_mean = Cpt(
|
||||
SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream counter input 8, MEAN"
|
||||
)
|
||||
ci9_mean = Cpt(
|
||||
SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream counter input 9, MEAN"
|
||||
)
|
||||
ci10_mean = Cpt(
|
||||
SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream counter input 10, MEAN"
|
||||
)
|
||||
ci11_mean = Cpt(
|
||||
SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream counter input 11, MEAN"
|
||||
)
|
||||
ci12_mean = Cpt(
|
||||
SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream counter input 12, MEAN"
|
||||
)
|
||||
ci13_mean = Cpt(
|
||||
SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream counter input 13, MEAN"
|
||||
)
|
||||
ci14_mean = Cpt(
|
||||
SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream counter input 14, MEAN"
|
||||
)
|
||||
ci15_mean = Cpt(
|
||||
SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream counter input 15, MEAN"
|
||||
)
|
||||
ci16_mean = Cpt(
|
||||
SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream counter input 16, MEAN"
|
||||
)
|
||||
ci17_mean = Cpt(
|
||||
SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream counter input 17, MEAN"
|
||||
)
|
||||
|
||||
ai0 = Cpt(
|
||||
EpicsSignalRO,
|
||||
suffix="NIDAQ-AI0",
|
||||
@@ -144,6 +247,76 @@ class NidaqControl(Device):
|
||||
doc="EPICS counter input 7",
|
||||
auto_monitor=True,
|
||||
)
|
||||
ci8 = Cpt(
|
||||
EpicsSignalRO,
|
||||
suffix="NIDAQ-CI8",
|
||||
kind=Kind.normal,
|
||||
doc="EPICS counter input 8",
|
||||
auto_monitor=True,
|
||||
)
|
||||
ci9 = Cpt(
|
||||
EpicsSignalRO,
|
||||
suffix="NIDAQ-CI9",
|
||||
kind=Kind.normal,
|
||||
doc="EPICS counter input 9",
|
||||
auto_monitor=True,
|
||||
)
|
||||
ci10 = Cpt(
|
||||
EpicsSignalRO,
|
||||
suffix="NIDAQ-CI10",
|
||||
kind=Kind.normal,
|
||||
doc="EPICS counter input 0",
|
||||
auto_monitor=True,
|
||||
)
|
||||
ci11 = Cpt(
|
||||
EpicsSignalRO,
|
||||
suffix="NIDAQ-CI11",
|
||||
kind=Kind.normal,
|
||||
doc="EPICS counter input 1",
|
||||
auto_monitor=True,
|
||||
)
|
||||
ci12 = Cpt(
|
||||
EpicsSignalRO,
|
||||
suffix="NIDAQ-CI12",
|
||||
kind=Kind.normal,
|
||||
doc="EPICS counter input 2",
|
||||
auto_monitor=True,
|
||||
)
|
||||
ci13 = Cpt(
|
||||
EpicsSignalRO,
|
||||
suffix="NIDAQ-CI13",
|
||||
kind=Kind.normal,
|
||||
doc="EPICS counter input 3",
|
||||
auto_monitor=True,
|
||||
)
|
||||
ci14 = Cpt(
|
||||
EpicsSignalRO,
|
||||
suffix="NIDAQ-CI14",
|
||||
kind=Kind.normal,
|
||||
doc="EPICS counter input 4",
|
||||
auto_monitor=True,
|
||||
)
|
||||
ci15 = Cpt(
|
||||
EpicsSignalRO,
|
||||
suffix="NIDAQ-CI15",
|
||||
kind=Kind.normal,
|
||||
doc="EPICS counter input 5",
|
||||
auto_monitor=True,
|
||||
)
|
||||
ci16 = Cpt(
|
||||
EpicsSignalRO,
|
||||
suffix="NIDAQ-CI16",
|
||||
kind=Kind.normal,
|
||||
doc="EPICS counter input 6",
|
||||
auto_monitor=True,
|
||||
)
|
||||
ci17 = Cpt(
|
||||
EpicsSignalRO,
|
||||
suffix="NIDAQ-CI17",
|
||||
kind=Kind.normal,
|
||||
doc="EPICS counter input 7",
|
||||
auto_monitor=True,
|
||||
)
|
||||
|
||||
di0 = Cpt(
|
||||
EpicsSignalRO,
|
||||
@@ -189,33 +362,15 @@ class NidaqControl(Device):
|
||||
auto_monitor=True,
|
||||
)
|
||||
|
||||
energy_epics = Cpt(
|
||||
EpicsSignalRO,
|
||||
suffix="NIDAQ-ENERGY",
|
||||
kind=Kind.normal,
|
||||
doc="EPICS Energy reading",
|
||||
auto_monitor=True,
|
||||
)
|
||||
|
||||
### Readback for BEC emitter ###
|
||||
|
||||
ai0_mean = Cpt(
|
||||
SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream analog input 0, MEAN"
|
||||
)
|
||||
ai1_mean = Cpt(
|
||||
SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream analog input 1, MEAN"
|
||||
)
|
||||
ai2_mean = Cpt(
|
||||
SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream analog input 2, MEAN"
|
||||
)
|
||||
ai3_mean = Cpt(
|
||||
SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream analog input 3, MEAN"
|
||||
)
|
||||
ai4_mean = Cpt(
|
||||
SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream analog input 4, MEAN"
|
||||
)
|
||||
ai5_mean = Cpt(
|
||||
SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream analog input 5, MEAN"
|
||||
)
|
||||
ai6_mean = Cpt(
|
||||
SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream analog input 6, MEAN"
|
||||
)
|
||||
ai7_mean = Cpt(
|
||||
SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream analog input 7, MEAN"
|
||||
)
|
||||
|
||||
ai0_std_dev = Cpt(
|
||||
SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream analog input 0, STD"
|
||||
)
|
||||
@@ -241,31 +396,6 @@ class NidaqControl(Device):
|
||||
SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream analog input 7, STD"
|
||||
)
|
||||
|
||||
ci0_mean = Cpt(
|
||||
SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream counter input 0, MEAN"
|
||||
)
|
||||
ci1_mean = Cpt(
|
||||
SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream counter input 1, MEAN"
|
||||
)
|
||||
ci2_mean = Cpt(
|
||||
SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream counter input 2, MEAN"
|
||||
)
|
||||
ci3_mean = Cpt(
|
||||
SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream counter input 3, MEAN"
|
||||
)
|
||||
ci4_mean = Cpt(
|
||||
SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream counter input 4, MEAN"
|
||||
)
|
||||
ci5_mean = Cpt(
|
||||
SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream counter input 5, MEAN"
|
||||
)
|
||||
ci6_mean = Cpt(
|
||||
SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream counter input 6, MEAN"
|
||||
)
|
||||
ci7_mean = Cpt(
|
||||
SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream counter input 7, MEAN"
|
||||
)
|
||||
|
||||
ci0_std_dev = Cpt(
|
||||
SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream counter input 0. STD"
|
||||
)
|
||||
@@ -290,33 +420,95 @@ class NidaqControl(Device):
|
||||
ci7_std_dev = Cpt(
|
||||
SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream counter input 7. STD"
|
||||
)
|
||||
ci8_std_dev = Cpt(
|
||||
SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream counter input 8. STD"
|
||||
)
|
||||
ci9_std_dev = Cpt(
|
||||
SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream counter input 9. STD"
|
||||
)
|
||||
ci10_std_dev = Cpt(
|
||||
SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream counter input 10. STD"
|
||||
)
|
||||
ci11_std_dev = Cpt(
|
||||
SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream counter input 11. STD"
|
||||
)
|
||||
ci12_std_dev = Cpt(
|
||||
SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream counter input 12. STD"
|
||||
)
|
||||
ci13_std_dev = Cpt(
|
||||
SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream counter input 13. STD"
|
||||
)
|
||||
ci14_std_dev = Cpt(
|
||||
SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream counter input 14. STD"
|
||||
)
|
||||
ci15_std_dev = Cpt(
|
||||
SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream counter input 15. STD"
|
||||
)
|
||||
ci16_std_dev = Cpt(
|
||||
SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream counter input 16. STD"
|
||||
)
|
||||
ci17_std_dev = Cpt(
|
||||
SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream counter input 17. STD"
|
||||
)
|
||||
|
||||
di0_max = Cpt(SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream digital input 0, MAX")
|
||||
di1_max = Cpt(SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream digital input 1, MAX")
|
||||
di2_max = Cpt(SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream digital input 2, MAX")
|
||||
di3_max = Cpt(SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream digital input 3, MAX")
|
||||
di4_max = Cpt(SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream digital input 4, MAX")
|
||||
xas_timestamp = Cpt(SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream XAS timestamp")
|
||||
xrd_timestamp = Cpt(SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream XRD timestamp")
|
||||
xrd_angle = Cpt(SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream XRD angle")
|
||||
xrd_energy = Cpt(SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream XRD energy")
|
||||
xrd_ai0_mean = Cpt(SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream XRD ai0 mean")
|
||||
xrd_ai0_std_dev = Cpt(SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream XRD ai0 std dev")
|
||||
|
||||
enc = Cpt(SetableSignal, value=0, kind=Kind.normal)
|
||||
rle = Cpt(SetableSignal, value=0, kind=Kind.normal)
|
||||
|
||||
### Control PVs ###
|
||||
|
||||
enable_compression = Cpt(EpicsSignal, suffix="NIDAQ-EnableRLE", kind=Kind.config)
|
||||
enable_compression = Cpt(EpicsSignal, suffix="NIDAQ-EnableRLE", kind=Kind.config, auto_monitor=True)
|
||||
enable_dead_time_correction = Cpt(EpicsSignal, suffix="NIDAQ-EnableDTC", kind=Kind.config, auto_monitor=True)
|
||||
kickoff_call = Cpt(EpicsSignal, suffix="NIDAQ-Kickoff", kind=Kind.config)
|
||||
stage_call = Cpt(EpicsSignal, suffix="NIDAQ-Stage", kind=Kind.config)
|
||||
state = Cpt(EpicsSignal, suffix="NIDAQ-FSMState", kind=Kind.config, auto_monitor=True)
|
||||
server_status = Cpt(EpicsSignalRO, suffix="NIDAQ-ServerStatus", kind=Kind.config)
|
||||
compression_ratio = Cpt(EpicsSignalRO, suffix="NIDAQ-CompressionRatio", kind=Kind.config)
|
||||
scan_type = Cpt(EpicsSignal, suffix="NIDAQ-ScanType", kind=Kind.config)
|
||||
sampling_rate = Cpt(EpicsSignal, suffix="NIDAQ-SamplingRateRequested", kind=Kind.config)
|
||||
scan_type_string = Cpt(EpicsSignal, suffix="NIDAQ-ScanType", kind=Kind.config, string=True)
|
||||
sampling_rate = Cpt(EpicsSignal, suffix="NIDAQ-SamplingRateRequested", kind=Kind.config, auto_monitor=True)
|
||||
sampling_rate_string = Cpt(EpicsSignal, suffix="NIDAQ-SamplingRateRequested", kind=Kind.config, string=True, auto_monitor=True)
|
||||
scan_duration = Cpt(EpicsSignal, suffix="NIDAQ-SamplingDuration", kind=Kind.config)
|
||||
readout_range = Cpt(EpicsSignal, suffix="NIDAQ-ReadoutRange", kind=Kind.config)
|
||||
encoder_type = Cpt(EpicsSignal, suffix="NIDAQ-EncoderType", kind=Kind.config)
|
||||
readout_range = Cpt(EpicsSignal, suffix="NIDAQ-ReadoutRange", kind=Kind.config, auto_monitor=True)
|
||||
readout_range_string = Cpt(EpicsSignal, suffix="NIDAQ-ReadoutRange", kind=Kind.config, string=True, auto_monitor=True)
|
||||
encoder_factor = Cpt(EpicsSignal, suffix="NIDAQ-EncoderFactor", kind=Kind.config, auto_monitor=True)
|
||||
encoder_factor_string = Cpt(EpicsSignal, suffix="NIDAQ-EncoderFactor", kind=Kind.config, string=True, auto_monitor=True)
|
||||
stop_call = Cpt(EpicsSignal, suffix="NIDAQ-Stop", kind=Kind.config)
|
||||
power = Cpt(EpicsSignal, suffix="NIDAQ-Power", kind=Kind.config)
|
||||
heartbeat = Cpt(EpicsSignal, suffix="NIDAQ-Heartbeat", kind=Kind.config, auto_monitor=True)
|
||||
time_left = Cpt(EpicsSignalRO, suffix="NIDAQ-TimeLeft", kind=Kind.config, auto_monitor=True)
|
||||
|
||||
ai_chans = Cpt(EpicsSignal, suffix="NIDAQ-AIChans", kind=Kind.config)
|
||||
ci_chans = Cpt(EpicsSignal, suffix="NIDAQ-CIChans6614", kind=Kind.config)
|
||||
di_chans = Cpt(EpicsSignal, suffix="NIDAQ-DIChans", kind=Kind.config)
|
||||
ai_chans = Cpt(EpicsSignal, suffix="NIDAQ-AIChans", kind=Kind.config, auto_monitor=True)
|
||||
ci_chans = Cpt(EpicsSignal, suffix="NIDAQ-CIChans", kind=Kind.config, auto_monitor=True)
|
||||
di_chans = Cpt(EpicsSignal, suffix="NIDAQ-DIChans", kind=Kind.config, auto_monitor=True)
|
||||
add_chans = Cpt(EpicsSignal, suffix="NIDAQ-AddChans", kind=Kind.config, auto_monitor=True)
|
||||
|
||||
smpl_abs_ln = Cpt(EpicsSignal, suffix="NIDAQ-smpl_abs_ln", kind=Kind.config, auto_monitor=True)
|
||||
ref_abs_ln = Cpt(EpicsSignal, suffix="NIDAQ-ref_abs_ln", kind=Kind.config, auto_monitor=True)
|
||||
|
||||
smpl_abs_no = Cpt(EpicsSignal, suffix="NIDAQ-smpl_abs_no", kind=Kind.config, auto_monitor=True)
|
||||
smpl_abs_no_string = Cpt(EpicsSignal, suffix="NIDAQ-smpl_abs_no", kind=Kind.config, string=True, auto_monitor=True)
|
||||
|
||||
smpl_abs_de = Cpt(EpicsSignal, suffix="NIDAQ-smpl_abs_de", kind=Kind.config, auto_monitor=True)
|
||||
smpl_abs_de_string = Cpt(EpicsSignal, suffix="NIDAQ-smpl_abs_de", kind=Kind.config, string=True, auto_monitor=True)
|
||||
|
||||
smpl_fluo_no = Cpt(EpicsSignal, suffix="NIDAQ-smpl_fluo_no", kind=Kind.config, auto_monitor=True)
|
||||
smpl_fluo_no_string = Cpt(EpicsSignal, suffix="NIDAQ-smpl_fluo_no", kind=Kind.config, string=True, auto_monitor=True)
|
||||
|
||||
smpl_fluo_de = Cpt(EpicsSignal, suffix="NIDAQ-smpl_fluo_de", kind=Kind.config, auto_monitor=True)
|
||||
smpl_fluo_de_string = Cpt(EpicsSignal, suffix="NIDAQ-smpl_fluo_de", kind=Kind.config, string=True, auto_monitor=True)
|
||||
|
||||
ref_abs_no = Cpt(EpicsSignal, suffix="NIDAQ-ref_abs_no", kind=Kind.config, auto_monitor=True)
|
||||
ref_abs_no_string = Cpt(EpicsSignal, suffix="NIDAQ-ref_abs_no", kind=Kind.config, string=True, auto_monitor=True)
|
||||
|
||||
ref_abs_de = Cpt(EpicsSignal, suffix="NIDAQ-ref_abs_de", kind=Kind.config, auto_monitor=True)
|
||||
ref_abs_de_string = Cpt(EpicsSignal, suffix="NIDAQ-ref_abs_de", kind=Kind.config, string=True, auto_monitor=True)
|
||||
|
||||
|
||||
class Nidaq(PSIDeviceBase, NidaqControl):
|
||||
@@ -328,17 +520,21 @@ class Nidaq(PSIDeviceBase, NidaqControl):
|
||||
scan_info (ScanInfo) : ScanInfo object passed by BEC's devicemanager.
|
||||
"""
|
||||
|
||||
progress_signal = Cpt(ProgressSignal, name="progress_signal")
|
||||
|
||||
USER_ACCESS = ["set_config"]
|
||||
|
||||
def __init__(self, prefix: str = "", *, name: str, scan_info: ScanInfo = None, **kwargs):
|
||||
super().__init__(name=name, prefix=prefix, scan_info=scan_info, **kwargs)
|
||||
self.scan_info: ScanInfo
|
||||
self.timeout_wait_for_signal = 5 # put 5s firsts
|
||||
self._timeout_wait_for_pv = 3 # 3s timeout for pv calls
|
||||
self._timeout_wait_for_pv = 5 # 5s timeout for pv calls. editted due to timeout issues persisting
|
||||
self.valid_scan_names = [
|
||||
"xas_simple_scan",
|
||||
"xas_simple_scan_with_xrd",
|
||||
"xas_advanced_scan",
|
||||
"xas_advanced_scan_with_xrd",
|
||||
"nidaq_continuous_scan",
|
||||
]
|
||||
|
||||
########################################
|
||||
@@ -443,12 +639,20 @@ class Nidaq(PSIDeviceBase, NidaqControl):
|
||||
elif readout_range == 10:
|
||||
self.readout_range.put(ReadoutRange.TEN_V)
|
||||
|
||||
if encoder_type in "X_1":
|
||||
self.encoder_type.put(EncoderTypes.X_1)
|
||||
elif encoder_type in "X_2":
|
||||
self.encoder_type.put(EncoderTypes.X_2)
|
||||
elif encoder_type in "X_4":
|
||||
self.encoder_type.put(EncoderTypes.X_4)
|
||||
if encoder_type in "1/16":
|
||||
self.encoder_factor.put(EncoderFactors.X1_16)
|
||||
elif encoder_type in "1/8":
|
||||
self.encoder_factor.put(EncoderFactors.X1_8)
|
||||
elif encoder_type in "1/4":
|
||||
self.encoder_factor.put(EncoderFactors.X1_4)
|
||||
elif encoder_type in "1/2":
|
||||
self.encoder_factor.put(EncoderFactors.X1_2)
|
||||
elif encoder_type in "1":
|
||||
self.encoder_factor.put(EncoderFactors.X1)
|
||||
elif encoder_type in "2":
|
||||
self.encoder_factor.put(EncoderFactors.X2)
|
||||
elif encoder_type in "4":
|
||||
self.encoder_factor.put(EncoderFactors.X4)
|
||||
|
||||
if enable_compression is True:
|
||||
self.enable_compression.put(NIDAQCompression.ON)
|
||||
@@ -472,15 +676,23 @@ class Nidaq(PSIDeviceBase, NidaqControl):
|
||||
Called after the device is connected and its signals are connected.
|
||||
Default values for signals should be set here.
|
||||
"""
|
||||
if not self.wait_for_condition(
|
||||
condition=lambda: self.state.get() == NidaqState.STANDBY,
|
||||
timeout=self.timeout_wait_for_signal,
|
||||
check_stopped=True,
|
||||
):
|
||||
raise NidaqError(
|
||||
f"Device {self.name} has not been reached in state STANDBY, current state {NidaqState(self.state.get())}"
|
||||
)
|
||||
status = TransitionStatus(self.heartbeat, transitions=[0, 1], strict=False)
|
||||
self.cancel_on_stop(status)
|
||||
try:
|
||||
status.wait(timeout=self.timeout_wait_for_signal) # Raises if timeout is reached
|
||||
except WaitTimeoutError:
|
||||
logger.warning(f"Device {self.name} was not alive, trying to put power on")
|
||||
status = TransitionStatus(self.heartbeat, transitions=[0, 1], strict=False)
|
||||
self.cancel_on_stop(status)
|
||||
self.power.put(1)
|
||||
|
||||
status.wait(timeout=self.timeout_wait_for_signal)
|
||||
|
||||
status = CompareStatus(self.state, NidaqState.STANDBY)
|
||||
self.cancel_on_stop(status)
|
||||
status.wait(timeout=self.timeout_wait_for_signal)
|
||||
self.scan_duration.set(0).wait(timeout=self._timeout_wait_for_pv)
|
||||
self.time_left.subscribe(self._progress_update, run=False)
|
||||
|
||||
def on_stage(self) -> DeviceStatus | StatusBase | None:
|
||||
"""
|
||||
@@ -492,41 +704,56 @@ class Nidaq(PSIDeviceBase, NidaqControl):
|
||||
if not self._check_if_scan_name_is_valid():
|
||||
return None
|
||||
|
||||
if not self.wait_for_condition(
|
||||
condition=lambda: self.state.get() == NidaqState.STANDBY,
|
||||
timeout=self.timeout_wait_for_signal,
|
||||
check_stopped=True,
|
||||
):
|
||||
raise NidaqError(
|
||||
f"Device {self.name} has not been reached in state STANDBY, current state {NidaqState(self.state.get())}"
|
||||
)
|
||||
self.scan_type.set(ScanType.TRIGGERED).wait(timeout=self._timeout_wait_for_pv)
|
||||
self.scan_duration.set(0).wait(timeout=self._timeout_wait_for_pv)
|
||||
self.stage_call.set(1).wait(timeout=self._timeout_wait_for_pv)
|
||||
if self.state.get() != NidaqState.STANDBY:
|
||||
status = CompareStatus(self.state, NidaqState.STANDBY)
|
||||
self.cancel_on_stop(status)
|
||||
self.on_stop()
|
||||
status.wait(timeout=self.timeout_wait_for_signal)
|
||||
|
||||
if not self.wait_for_condition(
|
||||
condition=lambda: self.state.get() == NidaqState.STAGE,
|
||||
timeout=self.timeout_wait_for_signal,
|
||||
check_stopped=True,
|
||||
):
|
||||
raise NidaqError(
|
||||
f"Device {self.name} has not been reached in state STAGE, current state {NidaqState(self.state.get())}"
|
||||
# If scan is not part of the valid_scan_names,
|
||||
if self.scan_info.msg.scan_name != "nidaq_continuous_scan":
|
||||
self.scan_type.set(ScanType.TRIGGERED).wait(timeout=self._timeout_wait_for_pv)
|
||||
self.scan_duration.set(0).wait(timeout=self._timeout_wait_for_pv)
|
||||
self.enable_compression.set(1).wait(timeout=self._timeout_wait_for_pv)
|
||||
else:
|
||||
self.scan_type.set(ScanType.CONTINUOUS).wait(timeout=self._timeout_wait_for_pv)
|
||||
self.scan_duration.set(self.scan_info.msg.scan_parameters["scan_duration"]).wait(
|
||||
timeout=self._timeout_wait_for_pv
|
||||
)
|
||||
self.kickoff_call.set(1).wait(timeout=self._timeout_wait_for_pv)
|
||||
self.enable_compression.set(self.scan_info.msg.scan_parameters["compression"]).wait(
|
||||
timeout=self._timeout_wait_for_pv
|
||||
)
|
||||
|
||||
# Stage call to IOC
|
||||
status = CompareStatus(self.state, NidaqState.STAGE)
|
||||
self.cancel_on_stop(status)
|
||||
# TODO 11.11.25/HS64
|
||||
# Switched from set to put in the hope to get rid of the rare event where nidaq is stopped at the start of a scan
|
||||
# Problems consistently persisting, testing changing back to set, unconvinced this is the actual cause 14.11.25/AHC
|
||||
# self.stage_call.set(1).wait(timeout=self._timeout_wait_for_pv)
|
||||
self.stage_call.put(1)
|
||||
status.wait(timeout=self.timeout_wait_for_signal)
|
||||
if self.scan_info.msg.scan_name != "nidaq_continuous_scan":
|
||||
status = self.on_kickoff()
|
||||
self.cancel_on_stop(status)
|
||||
status.wait(timeout=self._timeout_wait_for_pv)
|
||||
logger.info(f"Device {self.name} was staged: {NidaqState(self.state.get())}")
|
||||
|
||||
def on_kickoff(self) -> DeviceStatus | StatusBase:
|
||||
"""Kickoff the Nidaq"""
|
||||
status = self.kickoff_call.set(1)
|
||||
self.cancel_on_stop(status)
|
||||
return status
|
||||
|
||||
def on_unstage(self) -> DeviceStatus | StatusBase | None:
|
||||
"""Called while unstaging the device. Check that the Nidaq goes into Standby"""
|
||||
|
||||
def _get_state():
|
||||
return self.state.get() == NidaqState.STANDBY
|
||||
|
||||
if not self.wait_for_condition(
|
||||
condition=_get_state, timeout=self.timeout_wait_for_signal, check_stopped=False
|
||||
):
|
||||
raise NidaqError(
|
||||
f"Device {self.name} has not been reached in state STANDBY, current state {NidaqState(self.state.get())}"
|
||||
)
|
||||
status = CompareStatus(self.state, NidaqState.STANDBY)
|
||||
self.cancel_on_stop(status)
|
||||
status.wait(timeout=self.timeout_wait_for_signal)
|
||||
status = self.enable_compression.set(1)
|
||||
self.cancel_on_stop(status)
|
||||
status.wait(self._timeout_wait_for_pv)
|
||||
logger.info(f"Device {self.name} was unstaged: {NidaqState(self.state.get())}")
|
||||
|
||||
def on_pre_scan(self) -> DeviceStatus | StatusBase | None:
|
||||
@@ -540,15 +767,13 @@ class Nidaq(PSIDeviceBase, NidaqControl):
|
||||
if not self._check_if_scan_name_is_valid():
|
||||
return None
|
||||
|
||||
def _wait_for_state():
|
||||
return self.state.get() == NidaqState.KICKOFF
|
||||
if self.scan_info.msg.scan_name == "nidaq_continuous_scan":
|
||||
logger.info(f"Device {self.name} ready to be kicked off for nidaq_continuous_scan")
|
||||
return None
|
||||
|
||||
if not self.wait_for_condition(
|
||||
_wait_for_state, timeout=self.timeout_wait_for_signal, check_stopped=True
|
||||
):
|
||||
raise NidaqError(
|
||||
f"Device {self.name} failed to reach state KICKOFF during pre scan, current state {NidaqState(self.state.get())}"
|
||||
)
|
||||
status = CompareStatus(self.state, NidaqState.KICKOFF)
|
||||
self.cancel_on_stop(status)
|
||||
status.wait(timeout=self._timeout_wait_for_pv)
|
||||
logger.info(
|
||||
f"Device {self.name} ready to take data after pre_scan: {NidaqState(self.state.get())}"
|
||||
)
|
||||
@@ -565,18 +790,26 @@ class Nidaq(PSIDeviceBase, NidaqControl):
|
||||
"""
|
||||
if not self._check_if_scan_name_is_valid():
|
||||
return None
|
||||
self.on_stop()
|
||||
# TODO check if this wait can be removed. We are waiting in unstage anyways which will always be called afterwards
|
||||
# Wait for device to be stopped
|
||||
status = self.wait_for_condition(
|
||||
condition=lambda: self.state.get() == NidaqState.STANDBY,
|
||||
check_stopped=True,
|
||||
timeout=self.timeout_wait_for_signal,
|
||||
)
|
||||
|
||||
status = CompareStatus(self.state, NidaqState.STANDBY)
|
||||
self.cancel_on_stop(status)
|
||||
if self.scan_info.msg.scan_name != "nidaq_continuous_scan":
|
||||
self.on_stop()
|
||||
return status
|
||||
|
||||
def on_kickoff(self) -> DeviceStatus | StatusBase | None:
|
||||
"""Called to kickoff a device for a fly scan. Has to be called explicitly."""
|
||||
def _progress_update(self, value, **kwargs) -> None:
|
||||
"""Callback method to update the scan progress, runs a callback
|
||||
to SUB_PROGRESS subscribers, i.e. BEC.
|
||||
|
||||
Args:
|
||||
value (int) : current progress value
|
||||
"""
|
||||
scan_duration = self.scan_info.msg.scan_parameters.get("scan_duration", None)
|
||||
if not isinstance(scan_duration, (int, float)):
|
||||
return
|
||||
value = scan_duration - value
|
||||
max_value = scan_duration
|
||||
self.progress_signal.put(value=value, max_value=max_value, done=bool(max_value == value))
|
||||
|
||||
def on_stop(self) -> None:
|
||||
"""Called when the device is stopped."""
|
||||
|
||||
@@ -48,9 +48,13 @@ class ReadoutRange(int, enum.Enum):
|
||||
TEN_V = 3
|
||||
|
||||
|
||||
class EncoderTypes(int, enum.Enum):
|
||||
"""Encoder Types"""
|
||||
class EncoderFactors(int, enum.Enum):
|
||||
"""Encoder Factors"""
|
||||
|
||||
X_1 = 0
|
||||
X_2 = 1
|
||||
X_4 = 2
|
||||
X1_16 = 0
|
||||
X1_8 = 1
|
||||
X1_4 = 2
|
||||
X1_2 = 3
|
||||
X1 = 4
|
||||
X2 = 5
|
||||
X4 = 6
|
||||
|
||||
@@ -0,0 +1,685 @@
|
||||
"""Pilatus AD integration at Debye beamline."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import enum
|
||||
import threading
|
||||
import time
|
||||
import traceback
|
||||
from typing import TYPE_CHECKING, Tuple
|
||||
|
||||
import numpy as np
|
||||
from bec_lib.file_utils import get_full_path
|
||||
from bec_lib.logger import bec_logger
|
||||
from ophyd import Component as Cpt
|
||||
from ophyd import EpicsSignal, EpicsSignalRO, Kind
|
||||
from ophyd.areadetector.cam import ADBase, PilatusDetectorCam
|
||||
from ophyd.areadetector.plugins import HDF5Plugin_V22 as HDF5Plugin
|
||||
from ophyd.areadetector.plugins import ImagePlugin_V22 as ImagePlugin
|
||||
from ophyd.status import WaitTimeoutError
|
||||
from ophyd_devices import AndStatus, CompareStatus, DeviceStatus, FileEventSignal, PreviewSignal
|
||||
from ophyd_devices.interfaces.base_classes.psi_device_base import PSIDeviceBase
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
if TYPE_CHECKING: # pragma: no cover
|
||||
from bec_lib.devicemanager import ScanInfo
|
||||
from bec_lib.messages import DevicePreviewMessage, ScanStatusMessage
|
||||
from bec_server.device_server.device_server import DeviceManagerDS
|
||||
|
||||
PILATUS_READOUT_TIME = 0.1 # in s
|
||||
# PILATUS_ACQUIRE_TIME = (
|
||||
# 999999 # This time is the timeout of the detector in operation mode, so it needs to be large.
|
||||
# )
|
||||
|
||||
# pylint: disable=redefined-outer-name
|
||||
# pylint: disable=raise-missing-from
|
||||
|
||||
logger = bec_logger.logger
|
||||
|
||||
|
||||
class DETECTORSTATE(int, enum.Enum):
|
||||
"""Pilatus Detector States from CamServer"""
|
||||
|
||||
UNARMED = 0
|
||||
ARMED = 1
|
||||
|
||||
|
||||
class ACQUIREMODE(int, enum.Enum):
|
||||
"""Pilatus Acquisition Modes"""
|
||||
|
||||
DONE = 0
|
||||
ACQUIRING = 1
|
||||
|
||||
|
||||
class FILEWRITEMODE(int, enum.Enum):
|
||||
"""HDF5 Plugin FileWrite Mode"""
|
||||
|
||||
SINGLE = 0
|
||||
CAPTURE = 1
|
||||
STREAM = 2
|
||||
|
||||
|
||||
class COMPRESSIONALGORITHM(int, enum.Enum):
|
||||
"""HDF5 Plugin Compression Algorithm"""
|
||||
|
||||
NONE = 0
|
||||
NBIT = 1 # Don't use that..
|
||||
SZIP = 2
|
||||
ZLIB = 3
|
||||
|
||||
|
||||
class TRIGGERMODE(int, enum.Enum):
|
||||
"""Pilatus Trigger Modes"""
|
||||
|
||||
INTERNAL = 0
|
||||
EXT_ENABLE = 1
|
||||
EXT_TRIGGER = 2
|
||||
MULT_TRIGGER = 3
|
||||
ALIGNMENT = 4
|
||||
|
||||
|
||||
class MONOTRIGGERSOURCE(int, enum.Enum):
|
||||
""" "Mono XRD trigger source"""
|
||||
|
||||
EPICS = 0
|
||||
INPOS = 1
|
||||
|
||||
|
||||
class MONOTRIGGERMODE(int, enum.Enum):
|
||||
""" "Mono XRD trigger mode"""
|
||||
|
||||
PULSE = 0
|
||||
CONDITION = 1
|
||||
|
||||
def description(self) -> str:
|
||||
"""Return a description of the trigger mode."""
|
||||
descriptions = {
|
||||
TRIGGERMODE.INTERNAL: "Internal trigger mode, images are acquired on internal trigger.",
|
||||
TRIGGERMODE.EXT_ENABLE: "External Enable trigger mode; check manual as details are currently unknown",
|
||||
TRIGGERMODE.EXT_TRIGGER: "External Trigger mode, images are acquired on external trigger signal. All images on single trigger.",
|
||||
TRIGGERMODE.MULT_TRIGGER: "Multiple External Trigger mode, images are acquired on multiple external trigger signals. One image per trigger.",
|
||||
TRIGGERMODE.ALIGNMENT: "Alignment mode, used for beam alignment.",
|
||||
}
|
||||
return descriptions.get(self, "Unknown")
|
||||
|
||||
def __str__(self):
|
||||
return self.description()
|
||||
|
||||
|
||||
class ScanParameter(BaseModel):
|
||||
"""Dataclass to store the scan parameters for the Pilatus.
|
||||
This needs to be in sync with the kwargs of the XRD related scans from Debye, to
|
||||
ensure that the scan parameters are correctly set. Any changes in the scan kwargs,
|
||||
i.e. renaming or adding new parameters, need to be represented here as well."""
|
||||
|
||||
scan_time: float | None = Field(None, description="Scan time for a half oscillation")
|
||||
scan_duration: float | None = Field(None, description="Duration of the scan")
|
||||
break_enable_low: bool | None = Field(
|
||||
None, description="Break enabled for low, should be PV trig_ena_lo_enum"
|
||||
) # trig_enable_low: bool = None
|
||||
break_enable_high: bool | None = Field(
|
||||
None, description="Break enabled for high, should be PV trig_ena_hi_enum"
|
||||
) # trig_enable_high: bool = None
|
||||
break_time_low: float | None = Field(None, description="Break time low energy/angle")
|
||||
break_time_high: float | None = Field(None, description="Break time high energy/angle")
|
||||
cycle_low: int | None = Field(None, description="Cycle for low energy/angle")
|
||||
cycle_high: int | None = Field(None, description="Cycle for high energy/angle")
|
||||
exp_time: float | None = Field(None, description="XRD trigger period")
|
||||
n_of_trigger: int | None = Field(None, description="Amount of XRD triggers")
|
||||
start: float | None = Field(None, description="Start value for energy/angle")
|
||||
stop: float | None = Field(None, description="Stop value for energy/angle")
|
||||
model_config: dict = {"validate_assignment": True}
|
||||
|
||||
|
||||
class Pilatus(PSIDeviceBase, ADBase):
|
||||
"""
|
||||
Pilatus Base integration for Debye.
|
||||
Prefix of the detector is 'X01DA-ES2-PIL:'
|
||||
|
||||
Args:
|
||||
prefix (str) : Prefix for the IOC
|
||||
name (str) : Name of the detector
|
||||
scan_info (ScanInfo | None) : ScanInfo object passed through the device by the device_manager
|
||||
device_manager (DeviceManager | None) : DeviceManager object passed through the device by the device_manager
|
||||
"""
|
||||
|
||||
# USER_ACCESS = ["start_live_mode", "stop_live_mode"]
|
||||
|
||||
cam_gain_menu_string = Cpt(EpicsSignalRO, suffix='cam1:GainMenu', string=True)
|
||||
|
||||
_default_configuration_attrs = [
|
||||
'cam.threshold_energy',
|
||||
'cam.threshold_auto_apply',
|
||||
'cam.gain_menu',
|
||||
'cam_gain_menu_string',
|
||||
'cam.pixel_cut_off',
|
||||
'cam.acquire_time',
|
||||
'cam.num_exposures',
|
||||
'cam.model',
|
||||
]
|
||||
|
||||
cam = Cpt(PilatusDetectorCam, "cam1:")
|
||||
hdf = Cpt(HDF5Plugin, "HDF1:")
|
||||
image1 = Cpt(ImagePlugin, "image1:")
|
||||
filter_number = Cpt(
|
||||
EpicsSignal, "cam1:FileNumber", kind=Kind.omitted, doc="File number for ramdisk"
|
||||
)
|
||||
trigger_shot = Cpt(
|
||||
EpicsSignal,
|
||||
read_pv="X01DA-OP-MO1:BRAGG:xrd_trig_req",
|
||||
write_pv="X01DA-OP-MO1:BRAGG:xrd_trig_req",
|
||||
add_prefix=("a",),
|
||||
kind=Kind.omitted,
|
||||
doc="Trigger PV from MO1 Bragg",
|
||||
)
|
||||
trigger_source = Cpt(
|
||||
EpicsSignal,
|
||||
read_pv="X01DA-OP-MO1:BRAGG:xrd_trig_src_ENUM_RBV",
|
||||
write_pv="X01DA-OP-MO1:BRAGG:xrd_trig_src_ENUM",
|
||||
add_prefix=("a",),
|
||||
kind=Kind.omitted,
|
||||
doc="Trigger Source; PV, 0 : EPICS, 1 : INPOS",
|
||||
)
|
||||
trigger_mode = Cpt(
|
||||
EpicsSignal,
|
||||
read_pv="X01DA-OP-MO1:BRAGG:xrd_trig_mode_ENUM_RBV",
|
||||
write_pv="X01DA-OP-MO1:BRAGG:xrd_trig_mode_ENUM",
|
||||
add_prefix=("a",),
|
||||
kind=Kind.omitted,
|
||||
doc="Trigger Mode; 0 : PULSE, 1 : CONDITION",
|
||||
)
|
||||
trigger_pulse_length = Cpt(
|
||||
EpicsSignal,
|
||||
read_pv="X01DA-OP-MO1:BRAGG:xrd_trig_len_RBV",
|
||||
write_pv="X01DA-OP-MO1:BRAGG:xrd_trig_len",
|
||||
add_prefix=("a",),
|
||||
kind=Kind.omitted,
|
||||
doc="Trigger Period in seconds",
|
||||
)
|
||||
trigger_period = Cpt(
|
||||
EpicsSignal,
|
||||
read_pv="X01DA-OP-MO1:BRAGG:xrd_trig_period_RBV",
|
||||
write_pv="X01DA-OP-MO1:BRAGG:xrd_trig_period",
|
||||
add_prefix=("a",),
|
||||
kind=Kind.omitted,
|
||||
doc="Trigger Pulse Length in seconds",
|
||||
)
|
||||
trigger_n_of = Cpt(
|
||||
EpicsSignal,
|
||||
read_pv="X01DA-OP-MO1:BRAGG:xrd_n_of_trig_RBV",
|
||||
write_pv="X01DA-OP-MO1:BRAGG:xrd_n_of_trig",
|
||||
add_prefix=("a",),
|
||||
kind=Kind.omitted,
|
||||
doc="Number of trigger to generate for each request",
|
||||
)
|
||||
preview = Cpt(
|
||||
PreviewSignal,
|
||||
name="preview",
|
||||
ndim=2,
|
||||
num_rotation_90=3,
|
||||
doc="Preview signal for the Pilatus Detector",
|
||||
)
|
||||
file_event = Cpt(FileEventSignal, name="file_event")
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
name: str,
|
||||
prefix: str = "",
|
||||
scan_info: ScanInfo | None = None,
|
||||
device_manager: DeviceManagerDS | None = None,
|
||||
**kwargs,
|
||||
):
|
||||
super().__init__(
|
||||
name=name, prefix=prefix, scan_info=scan_info, device_manager=device_manager, **kwargs
|
||||
)
|
||||
self.scan_parameter = ScanParameter()
|
||||
self.device_manager = device_manager
|
||||
self._readout_time = PILATUS_READOUT_TIME
|
||||
self._full_path = ""
|
||||
self._poll_thread = threading.Thread(
|
||||
target=self._poll_array_data, daemon=True, name=f"{self.name}_poll_thread"
|
||||
)
|
||||
self._poll_thread_kill_event = threading.Event()
|
||||
self._poll_rate = 1 # Poll rate in Hz
|
||||
self.xas_xrd_scan_names = ["xas_simple_scan_with_xrd", "xas_advanced_scan_with_xrd"]
|
||||
self.n_images = None
|
||||
# self._live_mode_thread = threading.Thread(
|
||||
# target=self._live_mode_loop, daemon=True, name=f"{self.name}_live_mode_thread"
|
||||
# )
|
||||
# self._live_mode_kill_event = threading.Event()
|
||||
# self._live_mode_run_event = threading.Event()
|
||||
# self._live_mode_stopped_event = threading.Event()
|
||||
# self._live_mode_stopped_event.set() # Initial state is stopped
|
||||
|
||||
########################################
|
||||
# Custom Beamline Methods #
|
||||
########################################
|
||||
|
||||
def _poll_array_data(self):
|
||||
"""Poll the array data for preview updates."""
|
||||
while not self._poll_thread_kill_event.wait(1 / self._poll_rate):
|
||||
try:
|
||||
# logger.info(f"Running poll loop for {self.name}..")
|
||||
value = self.image1.array_data.get()
|
||||
if value is None:
|
||||
continue
|
||||
width = self.image1.array_size.width.get()
|
||||
height = self.image1.array_size.height.get()
|
||||
# Geometry correction for the image
|
||||
data = np.reshape(value, (height, width))
|
||||
last_image: DevicePreviewMessage = self.preview.get()
|
||||
# logger.info(f"Preview image for {self.name} has shape {data.shape}")
|
||||
if last_image is not None:
|
||||
if np.array_equal(data, last_image.data):
|
||||
# No update if image is the same, ~2.5ms on 2400x2400 image (6M)
|
||||
logger.debug(
|
||||
f"Pilatus preview image for {self.name} is the same as last one, not updating."
|
||||
)
|
||||
continue
|
||||
|
||||
logger.debug(f"Setting preview data for {self.name}")
|
||||
self.preview.put(data)
|
||||
except Exception: # pylint: disable=broad-except
|
||||
content = traceback.format_exc()
|
||||
logger.error(
|
||||
f"Error while polling array data for preview of {self.name}: {content}"
|
||||
)
|
||||
|
||||
# def start_live_mode(self, exp_time: float, n_images_max: int = 50000):
|
||||
# """
|
||||
# Start live mode with given exposure time.
|
||||
|
||||
# Args:
|
||||
# exp_time (float) : Exposure time in seconds
|
||||
# n_images_max (int): Maximum number of images to capture during live mode.
|
||||
# Default is 5000. Only reset if needed.
|
||||
# """
|
||||
# if (
|
||||
# self.cam.acquire.get() != ACQUIREMODE.DONE.value
|
||||
# or self.hdf.capture.get() != ACQUIREMODE.DONE.value
|
||||
# ):
|
||||
# logger.warning(f"Can't start live mode, acquisition running on detector {self.name}.")
|
||||
# return
|
||||
# if self._live_mode_run_event.is_set():
|
||||
# logger.warning(f"Live mode is already running on detector {self.name}.")
|
||||
# return
|
||||
|
||||
# # Set relevant PVs
|
||||
# self.cam.array_counter.set(0).wait(5) # Reset array counter
|
||||
# self.cam.num_images.set(n_images_max).wait(5)
|
||||
# logger.info(
|
||||
# f"Setting exposure time to {exp_time} s for live mode on {self.name} with {n_images_max} images."
|
||||
# )
|
||||
# self.cam.acquire_time.set(exp_time - self._readout_time).wait(5)
|
||||
# self.cam.acquire_period.set(exp_time).wait(5)
|
||||
|
||||
# status = CompareStatus(self.cam.acquire, ACQUIREMODE.DONE.value)
|
||||
# # It should suffice to make sure that self.hdf.capture is not set..
|
||||
# self.cam.acquire.put(1) # Start measurement
|
||||
# try:
|
||||
# status.wait(10)
|
||||
# except WaitTimeoutError:
|
||||
# content = traceback.format_exc()
|
||||
# raise RuntimeError(
|
||||
# f"Live Mode on detector {self.name} did not stop: {content} after 10s."
|
||||
# )
|
||||
# self._live_mode_run_event.set()
|
||||
|
||||
# def _live_mode_loop(self, exp_time: float):
|
||||
# while not self._live_mode_kill_event.is_set():
|
||||
# self._live_mode_run_event.wait()
|
||||
# self._live_mode_stopped_event.clear() # Clear stopped event
|
||||
# time.sleep(self._readout_time) # make sure to wait for the readout_time
|
||||
# n_images = self.cam.array_counter.get()
|
||||
# status = CompareStatus(self.cam.array_counter, n_images + 1)
|
||||
# self.trigger_shot.put(1)
|
||||
# try:
|
||||
# status.wait(60)
|
||||
# except WaitTimeoutError:
|
||||
# logger.warning(
|
||||
# f"Live mode timeout exceeded for {self.name}. Continuing in live_mode_loop"
|
||||
# )
|
||||
# if self._live_mode_run_event.is_set():
|
||||
# self._live_mode_stopped_event.set() # Set stopped event to indicate that live mode loop is stopped
|
||||
|
||||
# def stop_live_mode(self):
|
||||
# """Stop live mode."""
|
||||
# if self._live_mode_stopped_event.is_set():
|
||||
# return
|
||||
# status = CompareStatus(self.cam.acquire, ACQUIREMODE.DONE.value)
|
||||
# self.cam.acquire.put(0)
|
||||
# self._live_mode_run_event.clear()
|
||||
# if not self._live_mode_stopped_event.wait(10): # Wait until live mode loop is stopped
|
||||
# logger.warning(f"Live mode did not stop in time for {self.name}.")
|
||||
# try:
|
||||
# status.wait(10)
|
||||
# except WaitTimeoutError:
|
||||
# content = traceback.format_exc()
|
||||
# raise RuntimeError(
|
||||
# f"Live Mode on detector {self.name} did not stop: {content} after 10s."
|
||||
# )
|
||||
|
||||
def check_detector_stop_running_acquisition(self) -> AndStatus:
|
||||
"""Check if the detector is still running an acquisition."""
|
||||
status_acquire = CompareStatus(self.cam.acquire, ACQUIREMODE.DONE.value)
|
||||
status_writing = CompareStatus(self.hdf.capture, ACQUIREMODE.DONE.value)
|
||||
status_cam_server = CompareStatus(self.cam.armed, DETECTORSTATE.UNARMED.value)
|
||||
status = status_acquire & status_writing & status_cam_server
|
||||
return status
|
||||
|
||||
def _calculate_trigger(self, scan_msg: ScanStatusMessage) -> Tuple[float, float]:
|
||||
self._update_scan_parameter()
|
||||
total_osc = 0
|
||||
calc_duration = 0
|
||||
total_trig_lo = 0
|
||||
total_trig_hi = 0
|
||||
# Switching high/low is intended as angle is inverse to energy and settings in BEC are always in energy
|
||||
loc_break_enable_low = self.scan_parameter.break_enable_high
|
||||
loc_break_time_low = self.scan_parameter.break_time_high
|
||||
loc_cycle_low = self.scan_parameter.cycle_high
|
||||
loc_break_enable_high = self.scan_parameter.break_enable_low
|
||||
loc_break_time_high = self.scan_parameter.break_time_low
|
||||
loc_cycle_high = self.scan_parameter.cycle_low
|
||||
|
||||
if not loc_break_enable_low:
|
||||
loc_break_time_low = 0
|
||||
loc_cycle_low = 1
|
||||
if not loc_break_enable_high:
|
||||
loc_break_time_high = 0
|
||||
loc_cycle_high = 1
|
||||
|
||||
total_osc = self.scan_parameter.scan_duration / (
|
||||
self.scan_parameter.scan_time +
|
||||
loc_break_time_low / (2 * loc_cycle_low) +
|
||||
loc_break_time_high / (2 * loc_cycle_high)
|
||||
)
|
||||
total_osc = np.ceil(total_osc)
|
||||
total_osc = total_osc + total_osc % 2 # round up to the next even number
|
||||
|
||||
if loc_break_enable_low:
|
||||
total_trig_lo = np.floor(total_osc / (2 * loc_cycle_low))
|
||||
if loc_break_enable_high:
|
||||
total_trig_hi = np.floor(total_osc / (2 * loc_cycle_high))
|
||||
calc_duration = total_osc * self.scan_parameter.scan_time + total_trig_lo * loc_break_time_low + total_trig_hi * loc_break_time_high
|
||||
|
||||
if calc_duration < self.scan_parameter.scan_duration:
|
||||
# Due to inaccuracy in formula, this can happen, we then need to manually add two oscillations and recalculate the triggers
|
||||
total_osc = total_osc + 2
|
||||
if loc_break_enable_low:
|
||||
total_trig_lo = np.floor(total_osc / (2 * loc_cycle_low))
|
||||
if loc_break_enable_high:
|
||||
total_trig_hi = np.floor(total_osc / (2 * loc_cycle_high))
|
||||
calc_duration = total_osc * self.scan_parameter.scan_time + total_trig_lo * loc_break_time_low + total_trig_hi * loc_break_time_high
|
||||
|
||||
return total_trig_lo, total_trig_hi
|
||||
|
||||
########################################
|
||||
# Beamline Specific Implementations #
|
||||
########################################
|
||||
|
||||
def on_init(self) -> None:
|
||||
"""
|
||||
Called when the device is initialized.
|
||||
|
||||
No signals are connected at this point. If you like to
|
||||
set default values on signals, please use on_connected instead.
|
||||
"""
|
||||
|
||||
def on_connected(self) -> None:
|
||||
"""
|
||||
Called after the device is connected and its signals are connected.
|
||||
Default values for signals should be set here.
|
||||
"""
|
||||
|
||||
status_cam = CompareStatus(self.cam.acquire, ACQUIREMODE.DONE.value)
|
||||
status_hdf = CompareStatus(self.hdf.capture, ACQUIREMODE.DONE.value)
|
||||
try:
|
||||
status_cam.wait(timeout=5)
|
||||
status_hdf.wait(timeout=5)
|
||||
except WaitTimeoutError:
|
||||
logger.warning(
|
||||
f"Camera device {self.name} was running an acquisition. Stopping acquisition."
|
||||
)
|
||||
self.cam.acquire.put(0)
|
||||
self.hdf.capture.put(0)
|
||||
|
||||
self.cam.trigger_mode.set(TRIGGERMODE.MULT_TRIGGER.value).wait(5)
|
||||
self.cam.image_file_tmot.set(60).wait(5)
|
||||
self.hdf.file_write_mode.set(FILEWRITEMODE.STREAM.value).wait(5)
|
||||
self.hdf.file_template.set("%s%s").wait(5)
|
||||
self.hdf.auto_save.set(1).wait(5)
|
||||
self.hdf.lazy_open.set(1).wait(5)
|
||||
self.hdf.compression.set(COMPRESSIONALGORITHM.NONE.value).wait(5) # To test which to use
|
||||
# Start polling thread...
|
||||
self._poll_thread.start()
|
||||
# Start live mode thread...
|
||||
# self._live_mode_thread.start()
|
||||
|
||||
def on_stage(self) -> DeviceStatus | None:
|
||||
"""
|
||||
Called while staging the device.
|
||||
|
||||
Information about the upcoming scan can be accessed from the scan_info
|
||||
(self.scan_info.msg) object.
|
||||
"""
|
||||
# self.stop_live_mode() # Make sure that live mode is stopped if scan runs
|
||||
|
||||
# If user has activated alignment mode on qt panel, switch back to multitrigger and stop acquisition
|
||||
if self.cam.trigger_mode.get() != TRIGGERMODE.MULT_TRIGGER.value:
|
||||
self.cam.trigger_mode.set(TRIGGERMODE.MULT_TRIGGER.value).wait(5)
|
||||
if self.cam.acquire.get() == ACQUIREMODE.ACQUIRING.value:
|
||||
self.cam.acquire.put(0)
|
||||
status_cam = CompareStatus(self.cam.acquire, ACQUIREMODE.DONE.value)
|
||||
status_cam.wait(timeout=5)
|
||||
|
||||
scan_msg: ScanStatusMessage = self.scan_info.msg
|
||||
if scan_msg.scan_name in self.xas_xrd_scan_names:
|
||||
self._update_scan_parameter()
|
||||
# Compute number of triggers
|
||||
total_trig_lo, total_trig_hi = self._calculate_trigger(scan_msg)
|
||||
# Set the number of images, we may also set this to a higher values if preferred and stop the acquisition
|
||||
# TODO This logic is prone to errors, as we rely on the scans to nicely resolve to n_images. We should
|
||||
# use here instead a way of settings the n_images independently of the scan parameters to avoid running out of sync
|
||||
# with the complete method. Ideally we comput them in the scan itself.. This is much safer IMO!
|
||||
self.n_images = (total_trig_lo + total_trig_hi) * self.scan_parameter.n_of_trigger
|
||||
exp_time = self.scan_parameter.exp_time
|
||||
self.trigger_source.set(MONOTRIGGERSOURCE.INPOS).wait(5)
|
||||
self.trigger_n_of.set(self.scan_parameter.n_of_trigger).wait(5)
|
||||
|
||||
elif scan_msg.scan_type == "step":
|
||||
self.n_images = scan_msg.num_points * scan_msg.scan_parameters.get(
|
||||
"frames_per_trigger", 1
|
||||
)
|
||||
exp_time = scan_msg.scan_parameters.get("exp_time")
|
||||
self.trigger_source.set(MONOTRIGGERSOURCE.EPICS).wait(5)
|
||||
self.trigger_n_of.set(1).wait(5) # BEC will trigger each acquisition
|
||||
else:
|
||||
# TODO how to deal with fly scans?
|
||||
return None
|
||||
# Common settings
|
||||
self.trigger_mode.set(MONOTRIGGERMODE.PULSE).wait(5)
|
||||
self.trigger_period.set(exp_time).wait(5)
|
||||
self.trigger_pulse_length.set(0.005).wait(
|
||||
5
|
||||
) # Pulse length of 5 ms enough for Pilatus and NIDAQ
|
||||
|
||||
if exp_time - self._readout_time <= 0:
|
||||
raise ValueError(
|
||||
(
|
||||
f"Exposure time {exp_time} is too short ",
|
||||
f"for Pilatus with readout_time {self._readout_time}.",
|
||||
)
|
||||
)
|
||||
detector_exp_time = exp_time - self._readout_time
|
||||
self._full_path = get_full_path(scan_msg, name="pilatus")
|
||||
file_path = "/".join(self._full_path.split("/")[:-1])
|
||||
file_name = self._full_path.split("/")[-1]
|
||||
# Prepare detector and backend
|
||||
self.cam.array_callbacks.set(1).wait(5) # Enable array callbacks
|
||||
self.hdf.enable.set(1).wait(5) # Enable HDF5 plugin
|
||||
# Camera settings
|
||||
self.cam.num_exposures.set(1).wait(5)
|
||||
self.cam.num_images.set(self.n_images).wait(5)
|
||||
self.cam.acquire_time.set(detector_exp_time).wait(5) # let's try this
|
||||
self.cam.acquire_period.set(exp_time).wait(5)
|
||||
self.filter_number.set(0).wait(5)
|
||||
# HDF5 settings
|
||||
logger.debug(
|
||||
f"Setting HDF5 file path to {file_path} and file name to {file_name}. full_path is {self._full_path}"
|
||||
)
|
||||
self.hdf.file_path.set(file_path).wait(5)
|
||||
self.hdf.file_name.set(file_name).wait(5)
|
||||
self.hdf.num_capture.set(self.n_images).wait(5)
|
||||
self.cam.array_counter.set(0).wait(5) # Reset array counter
|
||||
self.file_event.put(
|
||||
file_path=self._full_path,
|
||||
done=False,
|
||||
successful=False,
|
||||
hinted_h5_entries={"data": "/entry/data/data"},
|
||||
)
|
||||
|
||||
def on_unstage(self) -> None:
|
||||
"""Called while unstaging the device."""
|
||||
|
||||
def on_pre_scan(self) -> DeviceStatus | None:
|
||||
"""Called right before the scan starts on all devices automatically."""
|
||||
scan_msg: ScanStatusMessage = self.scan_info.msg
|
||||
if (
|
||||
scan_msg.scan_name in self.xas_xrd_scan_names or scan_msg.scan_type == "step"
|
||||
): # TODO how to deal with fly scans?
|
||||
status_hdf = CompareStatus(self.hdf.capture, ACQUIREMODE.ACQUIRING.value)
|
||||
status_cam = CompareStatus(self.cam.acquire, ACQUIREMODE.ACQUIRING.value)
|
||||
status_cam_server = CompareStatus(self.cam.armed, DETECTORSTATE.ARMED.value)
|
||||
status = status_hdf & status_cam & status_cam_server
|
||||
self.cam.acquire.put(1)
|
||||
self.hdf.capture.put(1)
|
||||
self.cancel_on_stop(status)
|
||||
return status
|
||||
else:
|
||||
return None
|
||||
|
||||
def on_trigger(self) -> DeviceStatus | None:
|
||||
"""Called when the device is triggered."""
|
||||
scan_msg: ScanStatusMessage = self.scan_info.msg
|
||||
if not scan_msg.scan_type == "step":
|
||||
return None
|
||||
start_time = time.time()
|
||||
img_counter = self.hdf.num_captured.get()
|
||||
logger.debug(f"Triggering image with num_captured {img_counter}")
|
||||
status = CompareStatus(self.hdf.num_captured, img_counter + 1)
|
||||
logger.debug(f"Triggering took image {time.time() - start_time:.3f} seconds")
|
||||
self.trigger_shot.put(1)
|
||||
self.cancel_on_stop(status)
|
||||
return status
|
||||
|
||||
def _complete_callback(self, status: DeviceStatus):
|
||||
"""Callback for when the device completes a scan."""
|
||||
scan_msg: ScanStatusMessage = self.scan_info.msg
|
||||
if (
|
||||
scan_msg.scan_name in self.xas_xrd_scan_names or scan_msg.scan_type == "step"
|
||||
): # TODO how to deal with fly scans?
|
||||
if status.success:
|
||||
self.file_event.put(
|
||||
file_path=self._full_path,
|
||||
done=True,
|
||||
successful=True,
|
||||
hinted_h5_entries={"data": "/entry/data/data"},
|
||||
)
|
||||
else:
|
||||
self.file_event.put(
|
||||
file_path=self._full_path,
|
||||
done=True,
|
||||
successful=False,
|
||||
hinted_h5_entries={"data": "/entry/data/data"},
|
||||
)
|
||||
else:
|
||||
return None
|
||||
|
||||
def on_complete(self) -> DeviceStatus | None:
|
||||
"""Called to inquire if a device has completed a scans."""
|
||||
scan_msg: ScanStatusMessage = self.scan_info.msg
|
||||
if (
|
||||
scan_msg.scan_name in self.xas_xrd_scan_names or scan_msg.scan_type == "step"
|
||||
): # TODO how to deal with fly scans?
|
||||
status_hdf = CompareStatus(self.hdf.capture, ACQUIREMODE.DONE.value)
|
||||
status_cam = CompareStatus(self.cam.acquire, ACQUIREMODE.DONE.value)
|
||||
status_cam_server = CompareStatus(self.cam.armed, DETECTORSTATE.UNARMED.value)
|
||||
if self.scan_info.msg.scan_name in self.xas_xrd_scan_names:
|
||||
# For long scans, it can be that the mono will execute one cycle more,
|
||||
# meaning a few more XRD triggers will be sent
|
||||
status_img_written = CompareStatus(
|
||||
self.hdf.num_captured, self.n_images, operation_success=">="
|
||||
)
|
||||
else:
|
||||
status_img_written = CompareStatus(self.hdf.num_captured, self.n_images)
|
||||
status_img_written = CompareStatus(self.hdf.num_captured, self.n_images)
|
||||
status = status_hdf & status_cam & status_img_written & status_cam_server
|
||||
status.add_callback(self._complete_callback) # Callback that writing was successful
|
||||
self.cancel_on_stop(status)
|
||||
return status
|
||||
else:
|
||||
return None
|
||||
|
||||
def on_kickoff(self) -> None:
|
||||
"""Called to kickoff a device for a fly scan. Has to be called explicitly."""
|
||||
|
||||
def on_stop(self) -> None:
|
||||
"""Called when the device is stopped."""
|
||||
self.cam.acquire.put(0)
|
||||
self.hdf.capture.put(0)
|
||||
|
||||
def on_destroy(self) -> None:
|
||||
"""Called when the device is destroyed. Cleanup resources here."""
|
||||
self._poll_thread_kill_event.set()
|
||||
# TODO do we need to clean the poll thread ourselves?
|
||||
self.on_stop()
|
||||
|
||||
def _update_scan_parameter(self):
|
||||
"""Get the scan_info parameters for the scan."""
|
||||
for key, value in self.scan_info.msg.request_inputs["inputs"].items():
|
||||
if hasattr(self.scan_parameter, key):
|
||||
setattr(self.scan_parameter, key, value)
|
||||
for key, value in self.scan_info.msg.request_inputs["kwargs"].items():
|
||||
if hasattr(self.scan_parameter, key):
|
||||
setattr(self.scan_parameter, key, value)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
try:
|
||||
pilatus = Pilatus(name="pilatus", prefix="X01DA-ES2-PIL:")
|
||||
logger.info("Calling wait for connection")
|
||||
# pilatus.wait_for_connection(all_signals=True, timeout=20)
|
||||
logger.info("Connecting to pilatus...")
|
||||
pilatus.on_connected()
|
||||
for exp_time, scan_number, n_pnts in zip([0.5, 1.0, 2.0], [1, 2, 3], [30, 20, 10]):
|
||||
logger.info("Sleeping for 5s")
|
||||
time.sleep(5)
|
||||
pilatus.scan_info.msg.num_points = n_pnts
|
||||
pilatus.scan_info.msg.scan_parameters["exp_time"] = exp_time
|
||||
pilatus.scan_info.msg.scan_parameters["frames_per_trigger"] = 1
|
||||
pilatus.scan_info.msg.info["file_components"] = (
|
||||
f"/sls/x01da/data/p22481/raw/data/S00000-00999/S{scan_number:05d}/S{scan_number:05d}",
|
||||
"h5",
|
||||
)
|
||||
pilatus.on_stage()
|
||||
logger.info("Stage done")
|
||||
pilatus.on_pre_scan().wait(timeout=5)
|
||||
logger.info("Pre-scan done")
|
||||
for ii in range(pilatus.scan_info.msg.num_points):
|
||||
# if ii == 0:
|
||||
# time.sleep(1)
|
||||
logger.info(f"Triggering image {ii+1}/{pilatus.scan_info.msg.num_points}")
|
||||
pilatus.on_trigger().wait()
|
||||
p = pilatus.preview.get()
|
||||
if p is not None:
|
||||
p: DevicePreviewMessage
|
||||
logger.warning(
|
||||
f"Preview shape: {p.data.shape}, max: {np.max(p.data)}, min: {np.min(p.data)}, mean: {np.mean(p.data)}"
|
||||
)
|
||||
pilatus.on_complete().wait(timeout=5)
|
||||
logger.info("Complete done")
|
||||
pilatus.on_unstage()
|
||||
logger.info("Unstage done")
|
||||
finally:
|
||||
pilatus.on_destroy()
|
||||
@@ -1,75 +1,100 @@
|
||||
"""ES2 Pilatus Curtain"""
|
||||
|
||||
import time
|
||||
from __future__ import annotations
|
||||
|
||||
import enum
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from ophyd import Component as Cpt
|
||||
from ophyd import Device, EpicsSignal, EpicsSignalRO, Kind
|
||||
from ophyd_devices.utils import bec_utils
|
||||
from ophyd import EpicsSignal, EpicsSignalRO
|
||||
from ophyd_devices import CompareStatus, DeviceStatus
|
||||
from ophyd_devices.interfaces.base_classes.psi_device_base import PSIDeviceBase
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from bec_lib.devicemanager import ScanInfo
|
||||
|
||||
|
||||
class GasMixSetup(Device):
|
||||
class PilatusCurtainError(Exception):
|
||||
"""PilatusCurtain specific exception"""
|
||||
|
||||
|
||||
class COVER(int, enum.Enum):
|
||||
"""Pilatus Curtain States"""
|
||||
|
||||
# TODO What are the proper states here? - Probably enums for the states are better.
|
||||
OPEN = 0
|
||||
CLOSED = 0
|
||||
ERROR = 1
|
||||
|
||||
|
||||
class PilatusCurtain(PSIDeviceBase):
|
||||
"""Class for the ES2 Pilatus Curtain"""
|
||||
|
||||
USER_ACCESS = ["open", "close"]
|
||||
|
||||
open_cover = Cpt(EpicsSignal, suffix="OpenCover", kind="config", doc="Open Cover")
|
||||
|
||||
close_cover = Cpt(EpicsSignal, suffix="CloseCover", kind="config", doc="Close Cover")
|
||||
|
||||
cover_is_closed = Cpt(
|
||||
EpicsSignalRO, suffix="CoverIsClosed", kind="config", doc="Cover is closed"
|
||||
)
|
||||
|
||||
cover_is_open = Cpt(EpicsSignalRO, suffix="CoverIsOpen", kind="config", doc="Cover is open")
|
||||
|
||||
cover_is_moving = Cpt(
|
||||
EpicsSignalRO, suffix="CoverIsMoving", kind="config", doc="Cover is moving"
|
||||
)
|
||||
|
||||
cover_error = Cpt(EpicsSignalRO, suffix="CoverError", kind="config", doc="Cover error")
|
||||
|
||||
def __init__(
|
||||
self, prefix="", *, name: str, kind: Kind = None, device_manager=None, parent=None, **kwargs
|
||||
):
|
||||
"""Initialize the Pilatus Curtain.
|
||||
|
||||
Args:
|
||||
prefix (str): EPICS prefix for the device
|
||||
name (str): Name of the device
|
||||
kind (Kind): Kind of the device
|
||||
device_manager (DeviceManager): Device manager instance
|
||||
parent (Device): Parent device
|
||||
kwargs: Additional keyword arguments
|
||||
"""
|
||||
super().__init__(prefix, name=name, kind=kind, parent=parent, **kwargs)
|
||||
self.device_manager = device_manager
|
||||
self.service_cfg = None
|
||||
def __init__(self, *, name: str, prefix: str = "", scan_info: ScanInfo | None = None, **kwargs):
|
||||
super().__init__(name=name, prefix=prefix, scan_info=scan_info, **kwargs)
|
||||
|
||||
self.timeout_for_pvwait = 30
|
||||
self.readback.name = self.name
|
||||
# Wait for connection on all components, ensure IOC is connected
|
||||
self.wait_for_connection(all_signals=True, timeout=5)
|
||||
|
||||
if device_manager:
|
||||
self.device_manager = device_manager
|
||||
else:
|
||||
self.device_manager = bec_utils.DMMock()
|
||||
def on_connected(self) -> None:
|
||||
"""
|
||||
Called after the device is connected and its signals are connected.
|
||||
Default values for signals should be set here.
|
||||
"""
|
||||
if self.cover_error.get() == COVER.ERROR:
|
||||
raise PilatusCurtainError("Pilatus Curtain is in an error state!")
|
||||
|
||||
self.connector = self.device_manager.connector
|
||||
def on_stage(self) -> DeviceStatus | None:
|
||||
"""Called while staging the device."""
|
||||
return self.open()
|
||||
|
||||
def open(self) -> None:
|
||||
def on_unstage(self) -> DeviceStatus | None:
|
||||
"""Called while unstaging the device."""
|
||||
# return self.close()
|
||||
|
||||
def on_stop(self) -> DeviceStatus | None:
|
||||
"""Called when the device is stopped."""
|
||||
# return self.close()
|
||||
|
||||
def open(self) -> DeviceStatus | None:
|
||||
"""Open the cover"""
|
||||
if self.cover_is_closed.get() == COVER.CLOSED:
|
||||
self.open_cover.put(1)
|
||||
# TODO timeout ok?
|
||||
status_open = CompareStatus(self.cover_is_open, COVER.OPEN, timeout=5)
|
||||
status_error = CompareStatus(self.cover_error, COVER.ERROR, operation_success="!=")
|
||||
status = status_open & status_error
|
||||
return status
|
||||
else:
|
||||
return None
|
||||
|
||||
self.open_cover.put(1)
|
||||
|
||||
while not self.cover_is_open.get():
|
||||
time.sleep(0.1)
|
||||
if self.cover_error.get():
|
||||
raise TimeoutError("Curtain did not open successfully and is now in an error state")
|
||||
|
||||
def close(self) -> None:
|
||||
def close(self) -> DeviceStatus | None:
|
||||
"""Close the cover"""
|
||||
|
||||
self.close_cover.put(1)
|
||||
|
||||
while not self.cover_is_closed.get():
|
||||
time.sleep(0.1)
|
||||
if self.cover_error.get():
|
||||
raise TimeoutError(
|
||||
"Curtain did not close successfully and is now in an error state"
|
||||
)
|
||||
if self.cover_is_open.get() == COVER.OPEN:
|
||||
self.close_cover.put(1)
|
||||
# TODO timeout ok?
|
||||
status_close = CompareStatus(self.cover_is_closed, COVER.CLOSED, timeout=5)
|
||||
status_error = CompareStatus(self.cover_error, COVER.ERROR, operation_success="!=")
|
||||
status = status_close & status_error
|
||||
return status
|
||||
else:
|
||||
return None
|
||||
|
||||
@@ -52,9 +52,15 @@ class Reffoilchanger(PSIDeviceBase):
|
||||
status = Cpt(
|
||||
EpicsSignal, suffix="ES2-REF:SELN-FilterState-ENUM_RBV", kind="config", doc="Status"
|
||||
)
|
||||
status_string = Cpt(
|
||||
EpicsSignal, suffix="ES2-REF:SELN-FilterState-ENUM_RBV", kind="config", doc="Status", string=True
|
||||
)
|
||||
op_mode = Cpt(
|
||||
EpicsSignalWithRBV, suffix="ES2-REF:SELN-OpMode-ENUM", kind="config", doc="Status"
|
||||
)
|
||||
op_mode_string = Cpt(
|
||||
EpicsSignalWithRBV, suffix="ES2-REF:SELN-OpMode-ENUM", kind="config", doc="Status", string=True
|
||||
)
|
||||
ref_set = Cpt(EpicsSignal, suffix="ES2-REF:SELN-SET", kind="config", doc="Requested reference")
|
||||
ref_rb = Cpt(
|
||||
EpicsSignalRO, suffix="ES2-REF:SELN-RB", kind="config", doc="Currently set reference"
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
from .debye_nexus_structure import DebyeNexusStructure
|
||||
|
||||
@@ -0,0 +1,353 @@
|
||||
from bec_server.file_writer.default_writer import DefaultFormat
|
||||
|
||||
import debye_bec.bec_widgets.widgets.x01da_parameters as bl
|
||||
|
||||
class DebyeNexusStructure(DefaultFormat):
|
||||
"""Nexus Structure for Debye"""
|
||||
|
||||
def format(self) -> None:
|
||||
"""Specify the file format for the file writer."""
|
||||
|
||||
entry = self.storage.create_group(name="entry")
|
||||
entry.attrs["NX_class"] = "NXentry"
|
||||
instrument = entry.create_group(name="instrument")
|
||||
instrument.attrs["NX_class"] = "NXinstrument"
|
||||
|
||||
##################
|
||||
## source specific information
|
||||
###################
|
||||
|
||||
source = instrument.create_group(name="source")
|
||||
source.attrs["NX_class"] = "NXsource"
|
||||
|
||||
beamline_name = source.create_dataset(name="beamline_name", data="Debye")
|
||||
beamline_name.attrs["NX_class"] = "NX_CHAR"
|
||||
|
||||
facility_name = source.create_dataset(name="facility_name", data="Swiss Light Source")
|
||||
facility_name.attrs["NX_class"] = "NX_CHAR"
|
||||
|
||||
probe = source.create_dataset(name="probe", data="X-ray")
|
||||
probe.attrs["NX_class"] = "NX_CHAR"
|
||||
|
||||
if "curr" in self.device_manager.devices:
|
||||
ring_current = source.create_soft_link(
|
||||
name="ring_current",
|
||||
target="/entry/collection/devices/curr/curr/value",
|
||||
)
|
||||
ring_current.attrs["NX_class"] = "NX_FLOAT"
|
||||
ring_current.attrs["units"] = "mA"
|
||||
|
||||
###################
|
||||
## mo1_bragg specific information
|
||||
###################
|
||||
|
||||
## Logic if device exist
|
||||
if "mo1_bragg" in self.device_manager.devices:
|
||||
|
||||
monochromator = instrument.create_group(name="monochromator")
|
||||
monochromator.attrs["NX_class"] = "NXmonochromator"
|
||||
crystal = monochromator.create_group(name="crystal")
|
||||
crystal.attrs["NX_class"] = "NXcrystal"
|
||||
|
||||
# Create a dataset
|
||||
chemical_formular = crystal.create_dataset(name="chemical_formular", data="Si")
|
||||
chemical_formular.attrs["NX_class"] = "NX_CHAR"
|
||||
|
||||
reflection = crystal.create_soft_link(
|
||||
name="reflection",
|
||||
target="/entry/collection/devices/mo1_bragg/mo1_bragg_crystal_current_xtal_string/value",
|
||||
)
|
||||
reflection.attrs["NX_class"] = "NX_CHAR"
|
||||
|
||||
# Create a softlink
|
||||
d_spacing = crystal.create_soft_link(
|
||||
name="d_spacing",
|
||||
target="/entry/collection/devices/mo1_bragg/mo1_bragg_crystal_current_d_spacing/value",
|
||||
)
|
||||
d_spacing.attrs["NX_class"] = "NX_FLOAT"
|
||||
d_spacing.attrs["units"] = "angstrom"
|
||||
|
||||
bragg_offset = crystal.create_soft_link(
|
||||
name="bragg_offset",
|
||||
target="/entry/collection/devices/mo1_bragg/mo1_bragg_crystal_current_bragg_off/value",
|
||||
)
|
||||
bragg_offset.attrs["NX_class"] = "NX_FLOAT"
|
||||
bragg_offset.attrs["units"] = "degree"
|
||||
|
||||
phi_offset = crystal.create_soft_link(
|
||||
name="phi_offset",
|
||||
target="/entry/collection/devices/mo1_bragg/mo1_bragg_crystal_current_phi_off/value",
|
||||
)
|
||||
phi_offset.attrs["NX_class"] = "NX_FLOAT"
|
||||
phi_offset.attrs["units"] = "degree"
|
||||
|
||||
## Logic if device exist
|
||||
if "mo1_roty" in self.device_manager.devices:
|
||||
|
||||
# Create a softlink
|
||||
azimuthal_angle = crystal.create_soft_link(
|
||||
name="azimuthal_angle",
|
||||
target="/entry/collection/devices/mo1_roty/mo1_roty/value",
|
||||
)
|
||||
azimuthal_angle.attrs["NX_class"] = "NX_FLOAT"
|
||||
azimuthal_angle.attrs["units"] = "degree"
|
||||
|
||||
azm_offset = crystal.create_soft_link(
|
||||
name="azm_offset",
|
||||
target="/entry/collection/devices/mo1_bragg/mo1_bragg_crystal_current_azm_off/value",
|
||||
)
|
||||
azm_offset.attrs["NX_class"] = "NX_FLOAT"
|
||||
azm_offset.attrs["units"] = "degree"
|
||||
|
||||
miscut = crystal.create_soft_link(
|
||||
name="miscut",
|
||||
target="/entry/collection/devices/mo1_bragg/mo1_bragg_crystal_current_miscut/value",
|
||||
)
|
||||
miscut.attrs["NX_class"] = "NX_FLOAT"
|
||||
miscut.attrs["units"] = "degree"
|
||||
|
||||
###################
|
||||
### cm mirror specific information
|
||||
####################
|
||||
|
||||
collimating_mirror = instrument.create_group(name="collimating_mirror")
|
||||
collimating_mirror.attrs["NX_class"] = "NXmirror"
|
||||
|
||||
cm_substrate_material = collimating_mirror.create_dataset(
|
||||
name="substrate_material", data="Si"
|
||||
)
|
||||
cm_substrate_material.attrs["NX_class"] = "NX_CHAR"
|
||||
|
||||
#previous error due to space in name field
|
||||
|
||||
if "cm_bnd_radius" in self.device_manager.devices:
|
||||
cm_bending_radius = collimating_mirror.create_soft_link(
|
||||
name="sagittal_radius",
|
||||
target="/entry/collection/devices/cm_bnd_radius/cm_bnd_radius/value",
|
||||
)
|
||||
cm_bending_radius.attrs["NX_class"] = "NX_FLOAT"
|
||||
cm_bending_radius.attrs["units"] = "km"
|
||||
|
||||
if "cm_rotx" in self.device_manager.devices:
|
||||
cm_incidence_angle = collimating_mirror.create_soft_link(
|
||||
name="incidence_angle", target="/entry/collection/devices/cm_rotx/cm_rotx/value"
|
||||
)
|
||||
cm_incidence_angle.attrs["NX_class"] = "NX_FLOAT"
|
||||
cm_incidence_angle.attrs["units"] = "mrad"
|
||||
|
||||
if "cm_roty" in self.device_manager.devices:
|
||||
cm_yaw_angle = collimating_mirror.create_soft_link(
|
||||
name="yaw_angle", target="/entry/collection/devices/cm_roty/cm_roty/value"
|
||||
)
|
||||
cm_yaw_angle.attrs["NX_class"] = "NX_FLOAT"
|
||||
cm_yaw_angle.attrs["units"] = "mrad"
|
||||
|
||||
if "cm_rotz" in self.device_manager.devices:
|
||||
cm_roll_angle = collimating_mirror.create_soft_link(
|
||||
name="roll_angle", target="/entry/collection/devices/cm_rotz/cm_rotz/value"
|
||||
)
|
||||
cm_roll_angle.attrs["NX_class"] = "NX_FLOAT"
|
||||
cm_roll_angle.attrs["units"] = "mrad"
|
||||
|
||||
if 'cm_trx' in self.device_manager.devices:
|
||||
cm_trx = - self.device_manager.devices.cm_trx.read(cached=True).get('cm_trx').get('value')
|
||||
stripe = 'Unknown'
|
||||
for name, low, high in zip(bl.cm.surface, bl.cm.limOptX[0], bl.cm.limOptX[1]):
|
||||
if low <= cm_trx <= high:
|
||||
stripe = name
|
||||
cm_stripe = collimating_mirror.create_dataset(
|
||||
name="stripe", data=stripe
|
||||
)
|
||||
cm_stripe.attrs["NX_class"] = "NX_CHAR"
|
||||
|
||||
###################
|
||||
### fm mirror specific information
|
||||
####################
|
||||
|
||||
focusing_mirror = instrument.create_group(name="focusing_mirror")
|
||||
focusing_mirror.attrs["NX_class"] = "NXmirror"
|
||||
|
||||
fm_substrate_material = focusing_mirror.create_dataset(
|
||||
name="substrate_material", data="Si"
|
||||
)
|
||||
fm_substrate_material.attrs["NX_class"] = "NX_CHAR"
|
||||
|
||||
if "fm_bnd_radius" in self.device_manager.devices:
|
||||
fm_bending_radius = focusing_mirror.create_soft_link(
|
||||
name="sagittal_radius",
|
||||
target="/entry/collection/devices/fm_bnd_radius/fm_bnd_radius/value",
|
||||
)
|
||||
fm_bending_radius.attrs["NX_class"] = "NX_FLOAT"
|
||||
fm_bending_radius.attrs["units"] = "km"
|
||||
|
||||
if "fm_rotx" in self.device_manager.devices:
|
||||
fm_incidence_angle = focusing_mirror.create_soft_link(
|
||||
name="incidence_angle", target="/entry/collection/devices/fm_rotx/fm_rotx/value"
|
||||
)
|
||||
fm_incidence_angle.attrs["NX_class"] = "NX_FLOAT"
|
||||
fm_incidence_angle.attrs["units"] = "mrad"
|
||||
|
||||
if "fm_roty" in self.device_manager.devices:
|
||||
fm_yaw_angle = focusing_mirror.create_soft_link(
|
||||
name="yaw_angle", target="/entry/collection/devices/fm_roty/fm_roty/value"
|
||||
)
|
||||
fm_yaw_angle.attrs["NX_class"] = "NX_FLOAT"
|
||||
fm_yaw_angle.attrs["units"] = "mrad"
|
||||
|
||||
if "fm_rotz" in self.device_manager.devices:
|
||||
fm_roll_angle = focusing_mirror.create_soft_link(
|
||||
name="roll_angle", target="/entry/collection/devices/fm_rotz/fm_rotz/value"
|
||||
)
|
||||
fm_roll_angle.attrs["NX_class"] = "NX_FLOAT"
|
||||
fm_roll_angle.attrs["units"] = "mrad"
|
||||
|
||||
if 'fm_trx' in self.device_manager.devices:
|
||||
fm_trx = - self.device_manager.devices.fm_trx.read(cached=True).get('fm_trx').get('value')
|
||||
stripe = 'Unknown'
|
||||
for name, low, high in zip(bl.fm.surfaceFlat, bl.fm.limOptXFlat[1], bl.fm.limOptXFlat[0]):
|
||||
if low <= fm_trx <= high:
|
||||
stripe = name + ' (flat)'
|
||||
for name, low, high in zip(bl.fm.surfaceToroid, bl.fm.limOptXToroid[1], bl.fm.limOptXToroid[0]):
|
||||
if low <= fm_trx <= high:
|
||||
stripe = name + ' (toroid)'
|
||||
fm_stripe = focusing_mirror.create_dataset(
|
||||
name="stripe", data=stripe
|
||||
)
|
||||
fm_stripe.attrs["NX_class"] = "NX_CHAR"
|
||||
|
||||
###################
|
||||
## nidaq specific information
|
||||
###################
|
||||
|
||||
## Logic if device exist
|
||||
if "nidaq" in self.device_manager.devices:
|
||||
|
||||
#ai_chans_bits = self.device_manager.devices.nidaq.ai_chans.read(cached=True).get("nidaq_ai_chans").get("value")
|
||||
ai_chans_bits = self.configuration.get("nidaq", {}).get("nidaq_ai_chans", {}).get("value")
|
||||
ci_chans_bits = self.configuration.get("nidaq", {}).get("nidaq_ci_chans", {}).get("value")
|
||||
#add_chans_bits = self.device_manager.devices.nidaq.add_chans.read(cached=True).get("nidaq_add_chans").get("value")
|
||||
add_chans_bits = self.configuration.get("nidaq", {}).get("nidaq_add_chans", {}).get("value")
|
||||
|
||||
measurement_mode = entry.create_group(name="mode")
|
||||
measurement_mode.attrs["NX_class"] = "NX_CHAR"
|
||||
|
||||
if (int(ci_chans_bits) & 0x7F) != 0:
|
||||
# Create a dataset
|
||||
rayspec_sdd_active = measurement_mode.create_group(name="Multi_Element_Partial_Fluorescence_Yield")
|
||||
me_sdd = rayspec_sdd_active.create_dataset(name="Detector", data="Rayspec 7 element Silicon Drift Detector")
|
||||
me_sdd.attrs["NX_class"] = "NX_CHAR"
|
||||
|
||||
if (int(ci_chans_bits) & (1<<8)) != 0:
|
||||
# Create a dataset
|
||||
ketek_sdd_active = measurement_mode.create_group(name="Single_Element_Partial_Fluorescence_Yield")
|
||||
se_sdd = ketek_sdd_active.create_dataset(name="Detector", data="Ketex mini single element Silicon Drift Detector")
|
||||
se_sdd.attrs["NX_class"] = "NX_CHAR"
|
||||
|
||||
if ((int(ai_chans_bits) & (1<<6)) != 0):
|
||||
# Create a dataset
|
||||
pips_active = measurement_mode.create_group(name="Total_Flourescence_Yield")
|
||||
tfy = pips_active.create_dataset(name="Detector", data="Mirion Technologies Partially Depeleted PIPS Detector")
|
||||
tfy.attrs["NX_class"] = "NX_CHAR"
|
||||
|
||||
if ((int(ai_chans_bits) & (1<<0)) != 0) & ((int(ai_chans_bits) & (1<<2)) != 0):
|
||||
# Create a dataset
|
||||
ai0ai2_active = measurement_mode.create_group(name="Sample_Transmission")
|
||||
sam_trans = ai0ai2_active.create_dataset(name="Detector", data="Ionitec 15 cm gas filled Ionisation Chambers")
|
||||
sam_trans.attrs["NX_class"] = "NX_CHAR"
|
||||
|
||||
if ((int(ai_chans_bits) & (1<<2)) != 0) & ((int(ai_chans_bits) & (1<<4)) != 0):
|
||||
# Create a dataset
|
||||
ai2ai4_active = measurement_mode.create_group(name="Reference_Transmission")
|
||||
ref_trans = ai2ai4_active.create_dataset(name="Detector", data="Ionitec 15 cm gas filled Ionisation Chambers")
|
||||
ref_trans.attrs["NX_class"] = "NX_CHAR"
|
||||
|
||||
main_data = entry.create_group(name="data")
|
||||
main_data.attrs["NX_class"] = "NXdata"
|
||||
|
||||
##################
|
||||
## energy, test whether the signal exists. how to check from config?
|
||||
###################
|
||||
|
||||
energy = main_data.create_group(name="energy")
|
||||
energy.attrs["NX_class"] = "NXdata"
|
||||
energy.attrs["units"] = "eV"
|
||||
|
||||
main_data.create_soft_link(name="energy", target="/entry/collection/readout_groups/async/nidaq/nidaq_energy/value")
|
||||
|
||||
##################
|
||||
## i0
|
||||
###################
|
||||
|
||||
if (int(ai_chans_bits) & (1<<0)) !=0:
|
||||
i0 = main_data.create_group(name="i0")
|
||||
i0.attrs["NX_class"] = "NXdata"
|
||||
i0.attrs["units"] = "V"
|
||||
|
||||
main_data.create_soft_link(name="i0", target="/entry/collection/readout_groups/async/nidaq/nidaq_ai0_mean/value")
|
||||
|
||||
##################
|
||||
## i1
|
||||
###################
|
||||
|
||||
if (int(ai_chans_bits) & (1<<2)) !=0:
|
||||
i1 = main_data.create_group(name="i1")
|
||||
i1.attrs["NX_class"] = "NXdata"
|
||||
i1.attrs["units"] = "V"
|
||||
|
||||
main_data.create_soft_link(name="i1", target="/entry/collection/readout_groups/async/nidaq/nidaq_ai2_mean/value")
|
||||
|
||||
##################
|
||||
## i2
|
||||
###################
|
||||
|
||||
if (int(ai_chans_bits) & (1<<4)) !=0:
|
||||
i2 = main_data.create_group(name="i2")
|
||||
i2.attrs["NX_class"] = "NXdata"
|
||||
i2.attrs["units"] = "V"
|
||||
|
||||
main_data.create_soft_link(name="i2", target="/entry/collection/readout_groups/async/nidaq/nidaq_ai4_mean/value")
|
||||
|
||||
##################
|
||||
## ci sum
|
||||
###################
|
||||
|
||||
if int(ci_chans_bits) > 0:
|
||||
ci_sum = main_data.create_group(name="Fluorescence_Sum")
|
||||
ci_sum.attrs["NX_class"] = "NXdata"
|
||||
ci_sum.attrs["units"] = "counts"
|
||||
|
||||
main_data.create_soft_link(name="Fluorescence_Sum", target="/entry/collection/readout_groups/async/nidaq/nidaq_cisum/value")
|
||||
|
||||
##################
|
||||
## mu sample, test whether the signal exists. how to check from config?
|
||||
###################
|
||||
|
||||
if (int(add_chans_bits) & (1<<0)) !=0:
|
||||
mu_sample = main_data.create_group(name="mu_sample")
|
||||
mu_sample.attrs["NX_class"] = "NXdata"
|
||||
|
||||
main_data.create_soft_link(name="mu_sample", target="/entry/collection/readout_groups/async/nidaq/nidaq_smpl_abs/value")
|
||||
|
||||
##################
|
||||
## fluo sample, test whether the signal exists. how to check from config?
|
||||
###################
|
||||
|
||||
if (int(add_chans_bits) & (1<<1)) !=0:
|
||||
mu_sample = main_data.create_group(name="fluo_sample")
|
||||
mu_sample.attrs["NX_class"] = "NXdata"
|
||||
|
||||
main_data.create_soft_link(name="fluo_sample", target="/entry/collection/readout_groups/async/nidaq/nidaq_smpl_fluo/value")
|
||||
|
||||
##################
|
||||
## mu reference, test whether the signal exists. how to check from config?
|
||||
###################
|
||||
|
||||
if (int(add_chans_bits) & (1<<2)) !=0:
|
||||
mu_reference = main_data.create_group(name="mu_reference")
|
||||
mu_reference.attrs["NX_class"] = "NXdata"
|
||||
|
||||
main_data.create_soft_link(name="mu_reference", target="/entry/collection/readout_groups/async/nidaq/nidaq_ref_abs/value")
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
# Macros
|
||||
|
||||
This directory is intended to store macros which will be loaded automatically when starting BEC.
|
||||
Macros are small functions to make repetitive tasks easier. Functions defined in python files in this directory will be accessible from the BEC console.
|
||||
Please do not put any code outside of function definitions here. If you wish for code to be automatically run when starting BEC, see the startup script at debye_bec/bec_ipython_client/startup/post_startup.py
|
||||
For a guide on writing macros, please see: https://bec.readthedocs.io/en/latest/user/command_line_interface.html#how-to-write-a-macro
|
||||
@@ -4,3 +4,4 @@ from .mono_bragg_scans import (
|
||||
XASSimpleScan,
|
||||
XASSimpleScanWithXRD,
|
||||
)
|
||||
from .nidaq_cont_scan import NIDAQContinuousScan
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
# from .metadata_schema_xas_simple_scan import xas_simple_scan_schema
|
||||
|
||||
METADATA_SCHEMA_REGISTRY = { # "xas_simple_scan": xas_simple_scan_schema
|
||||
# Add models which should be used to validate scan metadata here.
|
||||
# Make a model according to the template, and import it as above
|
||||
# Then associate it with a scan like so:
|
||||
# "example_scan": ExampleSchema
|
||||
}
|
||||
|
||||
# Define a default schema type which should be used as the fallback for everything:
|
||||
|
||||
DEFAULT_SCHEMA = None
|
||||
@@ -0,0 +1,34 @@
|
||||
# # By inheriting from BasicScanMetadata you can define a schema by which metadata
|
||||
# # supplied to a scan must be validated.
|
||||
# # This schema is a Pydantic model: https://docs.pydantic.dev/latest/concepts/models/
|
||||
# # but by default it will still allow you to add any arbitrary information to it.
|
||||
# # That is to say, when you run a scan with which such a model has been associated in the
|
||||
# # metadata_schema_registry, you can supply any python dictionary with strings as keys
|
||||
# # and built-in python types (strings, integers, floats) as values, and these will be
|
||||
# # added to the experiment metadata, but it *must* contain the keys and values of the
|
||||
# # types defined in the schema class.
|
||||
# #
|
||||
# #
|
||||
# # For example, say that you would like to enforce recording information about sample
|
||||
# # pretreatment, you could define the following:
|
||||
# #
|
||||
#
|
||||
# from bec_lib.metadata_schema import BasicScanMetadata
|
||||
#
|
||||
#
|
||||
# class ExampleSchema(BasicScanMetadata):
|
||||
# treatment_description: str
|
||||
# treatment_temperature_k: int
|
||||
#
|
||||
#
|
||||
# # If this was used according to the example in metadata_schema_registry.py,
|
||||
# # then when calling the scan, the user would need to write something like:
|
||||
# >>> scans.example_scan(
|
||||
# >>> motor,
|
||||
# >>> 1,
|
||||
# >>> 2,
|
||||
# >>> 3,
|
||||
# >>> metadata={"treatment_description": "oven overnight", "treatment_temperature_k": 575},
|
||||
# >>> )
|
||||
#
|
||||
# # And the additional metadata would be saved in the HDF5 file created for the scan.
|
||||
@@ -0,0 +1,8 @@
|
||||
from bec_lib.metadata_schema import BasicScanMetadata
|
||||
|
||||
|
||||
#
|
||||
#
|
||||
class xas_simple_scan_schema(BasicScanMetadata):
|
||||
Edge: str
|
||||
Element: str
|
||||
@@ -72,11 +72,7 @@ class XASSimpleScan(AsyncFlyScanBase):
|
||||
yield None
|
||||
|
||||
def pre_scan(self):
|
||||
"""Pre Scan action. Ensure the motor movetype is set to energy, then check
|
||||
limits for start/end energy.
|
||||
#TODO Remove once the motor movetype is removed and ANGLE motion is a pseudo motor.
|
||||
"""
|
||||
yield from self.stubs.send_rpc_and_wait(self.motor, "move_type.set", "energy")
|
||||
"""Pre Scan action."""
|
||||
|
||||
self._check_limits()
|
||||
# Ensure parent class pre_scan actions to be called.
|
||||
@@ -112,8 +108,9 @@ class XASSimpleScanWithXRD(XASSimpleScan):
|
||||
gui_config = {
|
||||
"Movement Parameters": ["start", "stop"],
|
||||
"Scan Parameters": ["scan_time", "scan_duration"],
|
||||
"Low Energy Range": ["xrd_enable_low", "num_trigger_low", "exp_time_low", "cycle_low"],
|
||||
"High Energy Range": ["xrd_enable_high", "num_trigger_high", "exp_time_high", "cycle_high"],
|
||||
"Low Energy Break": ["break_enable_low", "break_time_low", "cycle_low"],
|
||||
"High Energy Break": ["break_enable_high", "break_time_high", "cycle_high"],
|
||||
"XRD Triggers": ["exp_time", "n_of_trigger"],
|
||||
}
|
||||
|
||||
def __init__(
|
||||
@@ -122,14 +119,14 @@ class XASSimpleScanWithXRD(XASSimpleScan):
|
||||
stop: float,
|
||||
scan_time: float,
|
||||
scan_duration: float,
|
||||
xrd_enable_low: bool,
|
||||
num_trigger_low: int,
|
||||
exp_time_low: float,
|
||||
break_enable_low: bool,
|
||||
break_time_low: float,
|
||||
cycle_low: int,
|
||||
xrd_enable_high: bool,
|
||||
num_trigger_high: int,
|
||||
exp_time_high: float,
|
||||
break_enable_high: bool,
|
||||
break_time_high: float,
|
||||
cycle_high: float,
|
||||
exp_time: float,
|
||||
n_of_trigger: int,
|
||||
motor: DeviceBase = "mo1_bragg",
|
||||
**kwargs,
|
||||
):
|
||||
@@ -142,16 +139,16 @@ class XASSimpleScanWithXRD(XASSimpleScan):
|
||||
stop (float): Stop energy for the scan.
|
||||
scan_time (float): Time for one oscillation .
|
||||
scan_duration (float): Total duration of the scan.
|
||||
xrd_enable_low (bool): Enable XRD triggering for the low energy range.
|
||||
num_trigger_low (int): Number of triggers for the low energy range.
|
||||
exp_time_low (float): Exposure time for the low energy range.
|
||||
break_enable_low (bool): Enable breaks for the low energy range.
|
||||
break_time_low (float): Break time for the low energy range.
|
||||
cycle_low (int): Specify how often the triggers should be considered,
|
||||
every nth cycle for low
|
||||
xrd_enable_high (bool): Enable XRD triggering for the high energy range.
|
||||
num_trigger_high (int): Number of triggers for the high energy range.
|
||||
exp_time_high (float): Exposure time for the high energy range.
|
||||
break_enable_high (bool): Enable breaks for the high energy range.
|
||||
break_time_high (float): Break time for the high energy range.
|
||||
cycle_high (int): Specify how often the triggers should be considered,
|
||||
every nth cycle for high
|
||||
exp_time (float): Length of 1 trigger period in seconds
|
||||
n_of_trigger (int): Amount of triggers to be fired during break
|
||||
motor (DeviceBase, optional): Motor device to be used for the scan.
|
||||
Defaults to "mo1_bragg".
|
||||
|
||||
@@ -166,14 +163,14 @@ class XASSimpleScanWithXRD(XASSimpleScan):
|
||||
motor=motor,
|
||||
**kwargs,
|
||||
)
|
||||
self.xrd_enable_low = xrd_enable_low
|
||||
self.num_trigger_low = num_trigger_low
|
||||
self.exp_time_low = exp_time_low
|
||||
self.break_enable_low = break_enable_low
|
||||
self.break_time_low = break_time_low
|
||||
self.cycle_low = cycle_low
|
||||
self.xrd_enable_high = xrd_enable_high
|
||||
self.num_trigger_high = num_trigger_high
|
||||
self.exp_time_high = exp_time_high
|
||||
self.break_enable_high = break_enable_high
|
||||
self.break_time_high = break_time_high
|
||||
self.cycle_high = cycle_high
|
||||
self.exp_time = exp_time
|
||||
self.n_of_trigger = n_of_trigger
|
||||
|
||||
|
||||
class XASAdvancedScan(XASSimpleScan):
|
||||
@@ -237,8 +234,9 @@ class XASAdvancedScanWithXRD(XASAdvancedScan):
|
||||
"Movement Parameters": ["start", "stop"],
|
||||
"Scan Parameters": ["scan_time", "scan_duration"],
|
||||
"Spline Parameters": ["p_kink", "e_kink"],
|
||||
"Low Energy Range": ["xrd_enable_low", "num_trigger_low", "exp_time_low", "cycle_low"],
|
||||
"High Energy Range": ["xrd_enable_high", "num_trigger_high", "exp_time_high", "cycle_high"],
|
||||
"Low Energy Break": ["break_enable_low", "break_time_low", "cycle_low"],
|
||||
"High Energy Break": ["break_enable_high", "break_time_high", "cycle_high"],
|
||||
"XRD Triggers": ["exp_time", "n_of_trigger"],
|
||||
}
|
||||
|
||||
def __init__(
|
||||
@@ -249,14 +247,14 @@ class XASAdvancedScanWithXRD(XASAdvancedScan):
|
||||
scan_duration: float,
|
||||
p_kink: float,
|
||||
e_kink: float,
|
||||
xrd_enable_low: bool,
|
||||
num_trigger_low: int,
|
||||
exp_time_low: float,
|
||||
break_enable_low: bool,
|
||||
break_time_low: float,
|
||||
cycle_low: int,
|
||||
xrd_enable_high: bool,
|
||||
num_trigger_high: int,
|
||||
exp_time_high: float,
|
||||
break_enable_high: bool,
|
||||
break_time_high: float,
|
||||
cycle_high: float,
|
||||
exp_time: float,
|
||||
n_of_trigger: int,
|
||||
motor: DeviceBase = "mo1_bragg",
|
||||
**kwargs,
|
||||
):
|
||||
@@ -274,16 +272,16 @@ class XASAdvancedScanWithXRD(XASAdvancedScan):
|
||||
scan_duration (float): Total duration of the scan.
|
||||
p_kink (float): Position of kink.
|
||||
e_kink (float): Energy of the kink.
|
||||
xrd_enable_low (bool): Enable XRD triggering for the low energy range.
|
||||
num_trigger_low (int): Number of triggers for the low energy range.
|
||||
exp_time_low (float): Exposure time for the low energy range.
|
||||
break_enable_low (bool): Enable breaks for the low energy range.
|
||||
break_time_low (float): Break time for the low energy range.
|
||||
cycle_low (int): Specify how often the triggers should be considered,
|
||||
every nth cycle for low
|
||||
xrd_enable_high (bool): Enable XRD triggering for the high energy range.
|
||||
num_trigger_high (int): Number of triggers for the high energy range.
|
||||
exp_time_high (float): Exposure time for the high energy range.
|
||||
break_enable_high (bool): Enable breaks for the high energy range.
|
||||
break_time_high (float): Break time for the high energy range.
|
||||
cycle_high (int): Specify how often the triggers should be considered,
|
||||
every nth cycle for high
|
||||
exp_time (float): Length of 1 trigger period in seconds
|
||||
n_of_trigger (int): Amount of triggers to be fired during break
|
||||
motor (DeviceBase, optional): Motor device to be used for the scan.
|
||||
Defaults to "mo1_bragg".
|
||||
|
||||
@@ -302,11 +300,11 @@ class XASAdvancedScanWithXRD(XASAdvancedScan):
|
||||
)
|
||||
self.p_kink = p_kink
|
||||
self.e_kink = e_kink
|
||||
self.xrd_enable_low = xrd_enable_low
|
||||
self.num_trigger_low = num_trigger_low
|
||||
self.exp_time_low = exp_time_low
|
||||
self.break_enable_low = break_enable_low
|
||||
self.break_time_low = break_time_low
|
||||
self.cycle_low = cycle_low
|
||||
self.xrd_enable_high = xrd_enable_high
|
||||
self.num_trigger_high = num_trigger_high
|
||||
self.exp_time_high = exp_time_high
|
||||
self.break_enable_high = break_enable_high
|
||||
self.break_time_high = break_time_high
|
||||
self.cycle_high = cycle_high
|
||||
self.exp_time = exp_time
|
||||
self.n_of_trigger = n_of_trigger
|
||||
|
||||
@@ -0,0 +1,84 @@
|
||||
"""This module contains the scan class for the nidaq of the Debye beamline for use in continuous mode."""
|
||||
|
||||
import time
|
||||
from typing import Literal
|
||||
|
||||
import numpy as np
|
||||
from bec_lib.device import DeviceBase
|
||||
from bec_lib.logger import bec_logger
|
||||
from bec_server.scan_server.scans import AsyncFlyScanBase
|
||||
|
||||
logger = bec_logger.logger
|
||||
|
||||
|
||||
class NIDAQContinuousScan(AsyncFlyScanBase):
|
||||
"""Class for the nidaq continuous scan (without mono)"""
|
||||
|
||||
scan_name = "nidaq_continuous_scan"
|
||||
scan_type = "fly"
|
||||
scan_report_hint = "device_progress"
|
||||
required_kwargs = []
|
||||
use_scan_progress_report = False
|
||||
pre_move = False
|
||||
gui_config = {"Scan Parameters": ["scan_duration"], "Data Compression": ["compression"]}
|
||||
|
||||
def __init__(
|
||||
self, scan_duration: float, daq: DeviceBase = "nidaq", compression: bool = False, **kwargs
|
||||
):
|
||||
"""The NIDAQ continuous scan is used to measure with the NIDAQ without moving the
|
||||
monochromator or any other motor. The NIDAQ thus runs in continuous mode, with a
|
||||
set scan_duration.
|
||||
|
||||
Args:
|
||||
scan_duration (float): Duration of the scan.
|
||||
daq (DeviceBase, optional): DAQ device to be used for the scan.
|
||||
Defaults to "nidaq".
|
||||
Examples:
|
||||
>>> scans.nidaq_continuous_scan(scan_duration=10)
|
||||
"""
|
||||
super().__init__(**kwargs)
|
||||
self.scan_duration = scan_duration
|
||||
self.daq = daq
|
||||
self.start_time = 0
|
||||
self.primary_readout_cycle = 1
|
||||
self.scan_parameters["scan_duration"] = scan_duration
|
||||
self.scan_parameters["compression"] = compression
|
||||
|
||||
def update_readout_priority(self):
|
||||
"""Ensure that NIDAQ is not monitored for any quick EXAFS."""
|
||||
super().update_readout_priority()
|
||||
self.readout_priority["async"].append("nidaq")
|
||||
|
||||
def prepare_positions(self):
|
||||
"""Prepare the positions for the scan."""
|
||||
yield None
|
||||
|
||||
def pre_scan(self):
|
||||
"""Pre Scan action."""
|
||||
|
||||
self.start_time = time.time()
|
||||
# Ensure parent class pre_scan actions to be called.
|
||||
yield from super().pre_scan()
|
||||
|
||||
def scan_report_instructions(self):
|
||||
"""
|
||||
Return the instructions for the scan report.
|
||||
"""
|
||||
yield from self.stubs.scan_report_instruction({"device_progress": [self.daq]})
|
||||
|
||||
def scan_core(self):
|
||||
"""Run the scan core.
|
||||
Kickoff the acquisition of the NIDAQ wait for the completion of the scan.
|
||||
"""
|
||||
kickoff_status = yield from self.stubs.kickoff(device=self.daq)
|
||||
kickoff_status.wait(timeout=5) # wait for proper kickoff of device
|
||||
|
||||
complete_status = yield from self.stubs.complete(device=self.daq, wait=False)
|
||||
|
||||
while not complete_status.done:
|
||||
# Readout monitored devices
|
||||
yield from self.stubs.read(group="monitored", point_id=self.point_id)
|
||||
time.sleep(self.primary_readout_cycle)
|
||||
self.point_id += 1
|
||||
|
||||
self.num_pos = self.point_id
|
||||
@@ -28,7 +28,7 @@ class NIDAQWriterService(BECService):
|
||||
def __init__(self, config: ServiceConfig, connector_cls: RedisConnector) -> None:
|
||||
super().__init__(config=config, connector_cls=connector_cls, unique_service=True)
|
||||
self.queue = queue.Queue()
|
||||
config = self._service_config.service_config.get("file_writer")
|
||||
config = self._service_config.config.get("file_writer")
|
||||
self.writer_mixin = FileWriter(service_config=config)
|
||||
self._scan_status_consumer = None
|
||||
self._ni_data_consumer = None
|
||||
|
||||
+22
-7
@@ -5,24 +5,33 @@ build-backend = "hatchling.build"
|
||||
[project]
|
||||
name = "debye_bec"
|
||||
version = "0.0.0"
|
||||
description = "Custom device implementations based on the ophyd hardware abstraction layer"
|
||||
requires-python = ">=3.10"
|
||||
description = "A plugin repository for BEC"
|
||||
requires-python = ">=3.11"
|
||||
classifiers = [
|
||||
"Development Status :: 3 - Alpha",
|
||||
"Programming Language :: Python :: 3",
|
||||
"Topic :: Scientific/Engineering",
|
||||
]
|
||||
dependencies = ["numpy", "scipy", "bec_lib", "h5py", "ophyd_devices"]
|
||||
dependencies = [
|
||||
"numpy",
|
||||
"scipy",
|
||||
"bec_lib",
|
||||
"h5py",
|
||||
"ophyd_devices",
|
||||
"opencv-python==4.11.0.86",
|
||||
"xrt",
|
||||
]
|
||||
|
||||
[project.optional-dependencies]
|
||||
dev = [
|
||||
"bec_server",
|
||||
"black ~= 25.0",
|
||||
"black",
|
||||
"copier",
|
||||
"isort",
|
||||
"coverage",
|
||||
"pylint",
|
||||
"pytest",
|
||||
"pytest-random-order",
|
||||
"bec_server",
|
||||
]
|
||||
|
||||
[project.entry-points."bec"]
|
||||
@@ -37,12 +46,18 @@ plugin_file_writer = "debye_bec.file_writer"
|
||||
[project.entry-points."bec.scans"]
|
||||
plugin_scans = "debye_bec.scans"
|
||||
|
||||
[project.entry-points."bec.scans.metadata_schema"]
|
||||
plugin_metadata_schema = "debye_bec.scans.metadata_schema"
|
||||
|
||||
[project.entry-points."bec.ipython_client_startup"]
|
||||
plugin_ipython_client_pre = "debye_bec.bec_ipython_client.startup.pre_startup"
|
||||
plugin_ipython_client_post = "debye_bec.bec_ipython_client.startup"
|
||||
|
||||
[project.entry-points."bec.widgets"]
|
||||
plugin_widgets = "debye_bec.bec_widgets"
|
||||
[project.entry-points."bec.widgets.auto_updates"]
|
||||
plugin_widgets_update = "debye_bec.bec_widgets.auto_updates"
|
||||
|
||||
[project.entry-points."bec.widgets.user_widgets"]
|
||||
plugin_widgets = "debye_bec.bec_widgets.widgets"
|
||||
|
||||
[tool.hatch.build.targets.wheel]
|
||||
include = ["*"]
|
||||
|
||||
@@ -1,31 +1,34 @@
|
||||
# Getting Started with Testing using pytest
|
||||
|
||||
BEC is using the [pytest](https://docs.pytest.org/en/8.0.x/) framework.
|
||||
It can be install via
|
||||
``` bash
|
||||
BEC is using the [pytest](https://docs.pytest.org/en/latest/) framework.
|
||||
It can be installed via
|
||||
|
||||
```bash
|
||||
pip install pytest
|
||||
```
|
||||
in your *python environment*.
|
||||
|
||||
in your _python environment_.
|
||||
We note that pytest is part of the optional-dependencies `[dev]` of the plugin package.
|
||||
|
||||
## Introduction
|
||||
|
||||
Tests in this package should be stored in the `tests` directory.
|
||||
We suggest to sort tests of different submodules, i.e. `scans` or `devices` in the respective folder structure, and to folow a naming convention of `<test_module_name.py>`.
|
||||
It is mandatory for test files to begin with `test_` for pytest to discover them.
|
||||
|
||||
To run all tests, navigate to the directory of the plugin from the command line, and run the command
|
||||
To run all tests, navigate to the directory of the plugin from the command line, and run the command
|
||||
|
||||
``` bash
|
||||
```bash
|
||||
pytest -v --random-order ./tests
|
||||
```
|
||||
|
||||
Note, the python environment needs to be active.
|
||||
The additional arg `-v` allows pytest to run in verbose mode which provides more detailed information about the tests being run.
|
||||
The argument `--random-order` instructs pytest to run the tests in random order, which is the default in the CI pipelines.
|
||||
The argument `--random-order` instructs pytest to run the tests in random order, which is the default in the CI pipelines.
|
||||
|
||||
## Test examples
|
||||
|
||||
Writing tests can be quite specific for the given function.
|
||||
Writing tests can be quite specific for the given function.
|
||||
We recommend writing tests as isolated as possible, i.e. try to test single functions instead of full classes.
|
||||
A very useful class to enable isolated testing is [MagicMock](https://docs.python.org/3/library/unittest.mock.html).
|
||||
In addition, we also recommend to take a look at the [How-to guides from pytest](https://docs.pytest.org/en/8.0.x/how-to/index.html).
|
||||
|
||||
|
||||
@@ -1,31 +1,34 @@
|
||||
# Getting Started with Testing using pytest
|
||||
|
||||
BEC is using the [pytest](https://docs.pytest.org/en/8.0.x/) framework.
|
||||
It can be install via
|
||||
``` bash
|
||||
BEC is using the [pytest](https://docs.pytest.org/en/latest/) framework.
|
||||
It can be installed via
|
||||
|
||||
```bash
|
||||
pip install pytest
|
||||
```
|
||||
in your *python environment*.
|
||||
|
||||
in your _python environment_.
|
||||
We note that pytest is part of the optional-dependencies `[dev]` of the plugin package.
|
||||
|
||||
## Introduction
|
||||
|
||||
Tests in this package should be stored in the `tests` directory.
|
||||
We suggest to sort tests of different submodules, i.e. `scans` or `devices` in the respective folder structure, and to folow a naming convention of `<test_module_name.py>`.
|
||||
It is mandatory for test files to begin with `test_` for pytest to discover them.
|
||||
|
||||
To run all tests, navigate to the directory of the plugin from the command line, and run the command
|
||||
To run all tests, navigate to the directory of the plugin from the command line, and run the command
|
||||
|
||||
``` bash
|
||||
```bash
|
||||
pytest -v --random-order ./tests
|
||||
```
|
||||
|
||||
Note, the python environment needs to be active.
|
||||
The additional arg `-v` allows pytest to run in verbose mode which provides more detailed information about the tests being run.
|
||||
The argument `--random-order` instructs pytest to run the tests in random order, which is the default in the CI pipelines.
|
||||
The argument `--random-order` instructs pytest to run the tests in random order, which is the default in the CI pipelines.
|
||||
|
||||
## Test examples
|
||||
|
||||
Writing tests can be quite specific for the given function.
|
||||
Writing tests can be quite specific for the given function.
|
||||
We recommend writing tests as isolated as possible, i.e. try to test single functions instead of full classes.
|
||||
A very useful class to enable isolated testing is [MagicMock](https://docs.python.org/3/library/unittest.mock.html).
|
||||
In addition, we also recommend to take a look at the [How-to guides from pytest](https://docs.pytest.org/en/8.0.x/how-to/index.html).
|
||||
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
# Getting Started with Testing using pytest
|
||||
|
||||
BEC is using the [pytest](https://docs.pytest.org/en/latest/) framework.
|
||||
It can be installed via
|
||||
|
||||
```bash
|
||||
pip install pytest
|
||||
```
|
||||
|
||||
in your _python environment_.
|
||||
We note that pytest is part of the optional-dependencies `[dev]` of the plugin package.
|
||||
|
||||
## Introduction
|
||||
|
||||
Tests in this package should be stored in the `tests` directory.
|
||||
We suggest to sort tests of different submodules, i.e. `scans` or `devices` in the respective folder structure, and to folow a naming convention of `<test_module_name.py>`.
|
||||
It is mandatory for test files to begin with `test_` for pytest to discover them.
|
||||
|
||||
To run all tests, navigate to the directory of the plugin from the command line, and run the command
|
||||
|
||||
```bash
|
||||
pytest -v --random-order ./tests
|
||||
```
|
||||
|
||||
Note, the python environment needs to be active.
|
||||
The additional arg `-v` allows pytest to run in verbose mode which provides more detailed information about the tests being run.
|
||||
The argument `--random-order` instructs pytest to run the tests in random order, which is the default in the CI pipelines.
|
||||
|
||||
## Test examples
|
||||
|
||||
Writing tests can be quite specific for the given function.
|
||||
We recommend writing tests as isolated as possible, i.e. try to test single functions instead of full classes.
|
||||
A very useful class to enable isolated testing is [MagicMock](https://docs.python.org/3/library/unittest.mock.html).
|
||||
In addition, we also recommend to take a look at the [How-to guides from pytest](https://docs.pytest.org/en/8.0.x/how-to/index.html).
|
||||
@@ -1,31 +1,34 @@
|
||||
# Getting Started with Testing using pytest
|
||||
|
||||
BEC is using the [pytest](https://docs.pytest.org/en/8.0.x/) framework.
|
||||
It can be install via
|
||||
``` bash
|
||||
BEC is using the [pytest](https://docs.pytest.org/en/latest/) framework.
|
||||
It can be installed via
|
||||
|
||||
```bash
|
||||
pip install pytest
|
||||
```
|
||||
in your *python environment*.
|
||||
|
||||
in your _python environment_.
|
||||
We note that pytest is part of the optional-dependencies `[dev]` of the plugin package.
|
||||
|
||||
## Introduction
|
||||
|
||||
Tests in this package should be stored in the `tests` directory.
|
||||
We suggest to sort tests of different submodules, i.e. `scans` or `devices` in the respective folder structure, and to folow a naming convention of `<test_module_name.py>`.
|
||||
It is mandatory for test files to begin with `test_` for pytest to discover them.
|
||||
|
||||
To run all tests, navigate to the directory of the plugin from the command line, and run the command
|
||||
To run all tests, navigate to the directory of the plugin from the command line, and run the command
|
||||
|
||||
``` bash
|
||||
```bash
|
||||
pytest -v --random-order ./tests
|
||||
```
|
||||
|
||||
Note, the python environment needs to be active.
|
||||
The additional arg `-v` allows pytest to run in verbose mode which provides more detailed information about the tests being run.
|
||||
The argument `--random-order` instructs pytest to run the tests in random order, which is the default in the CI pipelines.
|
||||
The argument `--random-order` instructs pytest to run the tests in random order, which is the default in the CI pipelines.
|
||||
|
||||
## Test examples
|
||||
|
||||
Writing tests can be quite specific for the given function.
|
||||
Writing tests can be quite specific for the given function.
|
||||
We recommend writing tests as isolated as possible, i.e. try to test single functions instead of full classes.
|
||||
A very useful class to enable isolated testing is [MagicMock](https://docs.python.org/3/library/unittest.mock.html).
|
||||
In addition, we also recommend to take a look at the [How-to guides from pytest](https://docs.pytest.org/en/8.0.x/how-to/index.html).
|
||||
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
"""Module to test prosilica and Basler cam integrations."""
|
||||
|
||||
import threading
|
||||
from unittest import mock
|
||||
|
||||
import ophyd
|
||||
import pytest
|
||||
from ophyd_devices.devices.areadetector.cam import AravisDetectorCam, ProsilicaDetectorCam
|
||||
from ophyd_devices.devices.areadetector.plugins import ImagePlugin_V35
|
||||
from ophyd_devices.tests.utils import MockPV, patch_dual_pvs
|
||||
|
||||
from debye_bec.devices.cameras.basler_cam import BaslerCam
|
||||
from debye_bec.devices.cameras.prosilica_cam import ProsilicaCam
|
||||
|
||||
# pylint: disable=protected-access
|
||||
# pylint: disable=redefined-outer-name
|
||||
|
||||
|
||||
@pytest.fixture(scope="function")
|
||||
def mock_basler():
|
||||
"""Fixture to mock the camera device."""
|
||||
name = "cam"
|
||||
prefix = "test:"
|
||||
with mock.patch.object(ophyd, "cl") as mock_cl:
|
||||
mock_cl.get_pv = MockPV
|
||||
mock_cl.thread_class = threading.Thread
|
||||
dev = BaslerCam(name=name, prefix=prefix)
|
||||
patch_dual_pvs(dev)
|
||||
yield dev
|
||||
|
||||
|
||||
def test_basler_init(mock_basler):
|
||||
"""Test the initialization of the Basler camera device."""
|
||||
assert mock_basler.name == "cam"
|
||||
assert mock_basler.prefix == "test:"
|
||||
assert isinstance(mock_basler.cam1, AravisDetectorCam)
|
||||
assert isinstance(mock_basler.image1, ImagePlugin_V35)
|
||||
assert mock_basler._update_frequency == 1
|
||||
assert mock_basler._live_mode is False
|
||||
assert mock_basler._live_mode_event is None
|
||||
assert mock_basler._task_status is None
|
||||
assert mock_basler.preview.ndim == 2
|
||||
assert mock_basler.preview.num_rotation_90 == 3
|
||||
|
||||
|
||||
@pytest.fixture(scope="function")
|
||||
def mock_prosilica():
|
||||
"""Fixture to mock the camera device."""
|
||||
name = "cam"
|
||||
prefix = "test:"
|
||||
with mock.patch.object(ophyd, "cl") as mock_cl:
|
||||
mock_cl.get_pv = MockPV
|
||||
mock_cl.thread_class = threading.Thread
|
||||
dev = ProsilicaCam(name=name, prefix=prefix)
|
||||
patch_dual_pvs(dev)
|
||||
yield dev
|
||||
|
||||
|
||||
def test_prosilica_init(mock_prosilica):
|
||||
"""Test the initialization of the Prosilica camera device."""
|
||||
assert mock_prosilica.name == "cam"
|
||||
assert mock_prosilica.prefix == "test:"
|
||||
assert isinstance(mock_prosilica.cam1, ProsilicaDetectorCam)
|
||||
assert isinstance(mock_prosilica.image1, ImagePlugin_V35)
|
||||
assert mock_prosilica._update_frequency == 1
|
||||
assert mock_prosilica._live_mode is False
|
||||
assert mock_prosilica._live_mode_event is None
|
||||
assert mock_prosilica._task_status is None
|
||||
assert mock_prosilica.preview.ndim == 2
|
||||
assert mock_prosilica.preview.num_rotation_90 == 3
|
||||
@@ -0,0 +1,86 @@
|
||||
"""Module to test camera base integration class for Debye."""
|
||||
|
||||
import threading
|
||||
from unittest import mock
|
||||
|
||||
import ophyd
|
||||
import pytest
|
||||
from ophyd_devices.tests.utils import MockPV, patch_dual_pvs
|
||||
|
||||
from debye_bec.devices.cameras.debye_base_cam import DebyeBaseCamera
|
||||
|
||||
# pylint: disable=protected-access
|
||||
# pylint: disable=redefined-outer-name
|
||||
|
||||
|
||||
@pytest.fixture(scope="function")
|
||||
def mock_cam():
|
||||
"""Fixture to mock the camera device."""
|
||||
name = "cam"
|
||||
prefix = "test:"
|
||||
with mock.patch.object(ophyd, "cl") as mock_cl:
|
||||
mock_cl.get_pv = MockPV
|
||||
mock_cl.thread_class = threading.Thread
|
||||
dev = DebyeBaseCamera(name=name, prefix=prefix)
|
||||
patch_dual_pvs(dev)
|
||||
yield dev
|
||||
|
||||
|
||||
def test_init(mock_cam):
|
||||
"""Test the initialization of the camera device."""
|
||||
assert mock_cam.name == "cam"
|
||||
assert mock_cam.prefix == "test:"
|
||||
assert mock_cam._update_frequency == 1
|
||||
assert mock_cam._live_mode is False
|
||||
assert mock_cam._live_mode_event is None
|
||||
assert mock_cam._task_status is None
|
||||
assert mock_cam.preview.ndim == 2
|
||||
assert mock_cam.preview.num_rotation_90 == -1
|
||||
|
||||
|
||||
def test_start_live_mode(mock_cam):
|
||||
"""Test starting live mode."""
|
||||
|
||||
def mock_emit_to_bec(*args, **kwargs):
|
||||
"""Mock emit_to_bec method."""
|
||||
while not mock_cam._live_mode_event.wait(1 / mock_cam._update_frequency):
|
||||
pass
|
||||
|
||||
with mock.patch.object(mock_cam, "emit_to_bec", side_effect=mock_emit_to_bec):
|
||||
mock_cam._start_live_mode()
|
||||
assert mock_cam._live_mode_event is not None
|
||||
assert mock_cam._task_status is not None
|
||||
assert mock_cam._task_status.state == "running"
|
||||
mock_cam._live_mode_event.set()
|
||||
# Wait for the task to resolve
|
||||
mock_cam._task_status.wait(timeout=5)
|
||||
assert mock_cam._task_status.done is True
|
||||
|
||||
|
||||
def test_stop_live_mode(mock_cam):
|
||||
"""Test stopping live mode."""
|
||||
with mock.patch.object(mock_cam, "_live_mode_event") as mock_live_mode_event:
|
||||
mock_cam._stop_live_mode()
|
||||
assert mock_live_mode_event.set.called
|
||||
assert mock_cam._live_mode_event is None
|
||||
|
||||
|
||||
def test_live_mode_property(mock_cam):
|
||||
"""Test the live_mode property."""
|
||||
assert mock_cam.live_mode is False
|
||||
with mock.patch.object(mock_cam, "_start_live_mode") as mock_start_live_mode:
|
||||
with mock.patch.object(mock_cam, "_stop_live_mode") as mock_stop_live_mode:
|
||||
# Set to true
|
||||
mock_cam.live_mode = True
|
||||
assert mock_start_live_mode.called
|
||||
assert mock_cam._live_mode is True
|
||||
assert mock_start_live_mode.call_count == 1
|
||||
# Second call should call _start_live_mode
|
||||
mock_cam.live_mode = True
|
||||
assert mock_start_live_mode.call_count == 1
|
||||
|
||||
# Set to false
|
||||
mock_cam.live_mode = False
|
||||
assert mock_stop_live_mode.called
|
||||
assert mock_cam._live_mode is False
|
||||
assert mock_stop_live_mode.call_count == 1
|
||||
@@ -52,8 +52,7 @@ def test_init(mock_bragg):
|
||||
dev = mock_bragg
|
||||
assert dev.name == "bragg"
|
||||
assert dev.prefix == "X01DA-OP-MO1:BRAGG:"
|
||||
assert dev.move_type.get() == MoveType.ENERGY
|
||||
assert dev.crystal.offset_si111._read_pvname == "X01DA-OP-MO1:BRAGG:offset_si111_RBV"
|
||||
assert dev.crystal.bragg_off_si111._read_pvname == "X01DA-OP-MO1:BRAGG:bragg_off_si111_RBV"
|
||||
assert dev.move_abs._read_pvname == "X01DA-OP-MO1:BRAGG:move_abs"
|
||||
|
||||
|
||||
@@ -61,43 +60,30 @@ def test_check_value(mock_bragg):
|
||||
dev = mock_bragg
|
||||
dev.low_lim._read_pv.mock_data = 0
|
||||
dev.high_lim._read_pv.mock_data = 1
|
||||
dev.low_limit_angle._read_pv.mock_data = 10
|
||||
dev.high_limit_angle._read_pv.mock_data = 20
|
||||
# Check that limits are taken correctly from angle or energy
|
||||
# Energy first
|
||||
move_type = MoveType.ENERGY
|
||||
dev.move_type.set(move_type)
|
||||
# nothing happens
|
||||
dev.check_value(0.5)
|
||||
with pytest.raises(LimitError):
|
||||
dev.check_value(15)
|
||||
# Angle next
|
||||
move_type = MoveType.ANGLE
|
||||
dev.move_type.set(move_type)
|
||||
dev.check_value(15)
|
||||
with pytest.raises(LimitError):
|
||||
dev.check_value(0.5)
|
||||
|
||||
|
||||
def test_egu(mock_bragg):
|
||||
dev = mock_bragg
|
||||
assert dev.egu == "eV"
|
||||
dev.move_type.set(MoveType.ANGLE)
|
||||
assert dev.egu == "deg"
|
||||
|
||||
|
||||
def test_move_succeeds(mock_bragg):
|
||||
dev = mock_bragg
|
||||
dev.move_abs._read_pv.mock_data = 0
|
||||
# Move succeeds
|
||||
with mock.patch.object(dev.motor_is_moving._read_pv, "mock_data", side_effect=[0, 1]):
|
||||
status = dev.move(0.5)
|
||||
# Sleep needed to allow thread to resolive in _move_and_finish, i.e. and the 0.25s sleep inside the function
|
||||
time.sleep(1)
|
||||
assert status.done is True
|
||||
assert status.success is True
|
||||
assert dev.setpoint.get() == 0.5
|
||||
assert dev.move_abs.get() == 1
|
||||
dev.motor_is_moving._read_pv.mock_data = 0
|
||||
|
||||
status = dev.move(0.5)
|
||||
assert status.done is False
|
||||
dev.motor_is_moving._read_pv.mock_data = 1
|
||||
status.wait(timeout=3) # Callback should within that time
|
||||
assert status.done is True
|
||||
assert status.success is True
|
||||
assert dev.setpoint.get() == 0.5
|
||||
assert dev.move_abs.get() == 1
|
||||
|
||||
|
||||
def test_stop_move(mock_bragg):
|
||||
@@ -120,14 +106,14 @@ def test_set_xtal(mock_bragg):
|
||||
dev = mock_bragg
|
||||
dev.set_xtal("111")
|
||||
# Default values for mock
|
||||
assert dev.crystal.offset_si111.get() == 0
|
||||
assert dev.crystal.offset_si311.get() == 0
|
||||
assert dev.crystal.bragg_off_si111.get() == 0
|
||||
assert dev.crystal.bragg_off_si311.get() == 0
|
||||
assert dev.crystal.d_spacing_si111.get() == 0
|
||||
assert dev.crystal.d_spacing_si311.get() == 0
|
||||
assert dev.crystal.xtal_enum.get() == 0
|
||||
dev.set_xtal("311", offset_si111=1, offset_si311=2, d_spacing_si111=3, d_spacing_si311=4)
|
||||
assert dev.crystal.offset_si111.get() == 1
|
||||
assert dev.crystal.offset_si311.get() == 2
|
||||
dev.set_xtal("311", bragg_off_si111=1, bragg_off_si311=2, d_spacing_si111=3, d_spacing_si311=4)
|
||||
assert dev.crystal.bragg_off_si111.get() == 1
|
||||
assert dev.crystal.bragg_off_si311.get() == 2
|
||||
assert dev.crystal.d_spacing_si111.get() == 3
|
||||
assert dev.crystal.d_spacing_si311.get() == 4
|
||||
assert dev.crystal.xtal_enum.get() == 1
|
||||
@@ -135,16 +121,10 @@ def test_set_xtal(mock_bragg):
|
||||
|
||||
def test_set_xas_settings(mock_bragg):
|
||||
dev = mock_bragg
|
||||
dev.move_type.set(MoveType.ENERGY)
|
||||
dev.set_xas_settings(low=0.5, high=1, scan_time=0.1)
|
||||
assert dev.scan_settings.s_scan_energy_lo.get() == 0.5
|
||||
assert dev.scan_settings.s_scan_energy_hi.get() == 1
|
||||
assert dev.scan_settings.s_scan_scantime.get() == 0.1
|
||||
dev.move_type.set(MoveType.ANGLE)
|
||||
dev.set_xas_settings(low=10, high=20, scan_time=1)
|
||||
assert dev.scan_settings.s_scan_angle_lo.get() == 10
|
||||
assert dev.scan_settings.s_scan_angle_hi.get() == 20
|
||||
assert dev.scan_settings.s_scan_scantime.get() == 1
|
||||
|
||||
|
||||
def test_set_trig_settings(mock_bragg):
|
||||
@@ -152,10 +132,12 @@ def test_set_trig_settings(mock_bragg):
|
||||
dev.set_trig_settings(
|
||||
enable_low=True,
|
||||
enable_high=False,
|
||||
exp_time_high=0.1,
|
||||
exp_time_low=0.01,
|
||||
break_time_high=0.1,
|
||||
break_time_low=0.01,
|
||||
cycle_low=1,
|
||||
cycle_high=3,
|
||||
exp_time=0.5,
|
||||
n_of_trigger=7,
|
||||
)
|
||||
assert dev.scan_settings.trig_ena_lo_enum.get() == True
|
||||
assert dev.scan_settings.trig_ena_hi_enum.get() == False
|
||||
@@ -163,6 +145,8 @@ def test_set_trig_settings(mock_bragg):
|
||||
assert dev.scan_settings.trig_every_n_hi.get() == 3
|
||||
assert dev.scan_settings.trig_time_lo.get() == 0.01
|
||||
assert dev.scan_settings.trig_time_hi.get() == 0.1
|
||||
assert dev.trigger_settings.xrd_trig_period.get() == 0.5
|
||||
assert dev.trigger_settings.xrd_n_of_trig.get() == 7
|
||||
|
||||
|
||||
def test_set_control_settings(mock_bragg):
|
||||
@@ -256,42 +240,44 @@ def test_kickoff_scan(mock_bragg):
|
||||
assert dev.scan_control.scan_start_infinite.get() == 1
|
||||
|
||||
|
||||
def test_complete(mock_bragg):
|
||||
dev = mock_bragg
|
||||
dev.scan_control.scan_done._read_pv.mock_data = 0
|
||||
# Normal case
|
||||
status = dev.complete()
|
||||
assert status.done is False
|
||||
assert status.success is False
|
||||
dev.scan_control.scan_done._read_pv.mock_data = 1
|
||||
status.wait()
|
||||
# time.sleep(0.2)
|
||||
assert status.done is True
|
||||
assert status.success is True
|
||||
# FIXME #22 once mock_pv supports callbacks, high priority!
|
||||
# def test_complete(mock_bragg):
|
||||
# dev = mock_bragg
|
||||
# dev.scan_control.scan_done._read_pv.mock_data = 0
|
||||
# # Normal case
|
||||
# status = dev.complete()
|
||||
# assert status.done is False
|
||||
# assert status.success is False
|
||||
# dev.scan_control.scan_done._read_pv.mock_data = 1
|
||||
# status.wait()
|
||||
# # time.sleep(0.2)
|
||||
# assert status.done is True
|
||||
# assert status.success is True
|
||||
|
||||
# Stop called case
|
||||
dev.scan_control.scan_done._read_pv.mock_data = 0
|
||||
status = dev.complete()
|
||||
assert status.done is False
|
||||
assert status.success is False
|
||||
dev.stop()
|
||||
time.sleep(0.2)
|
||||
assert status.done is True
|
||||
assert status.success is False
|
||||
# # Stop called case
|
||||
# dev.scan_control.scan_done._read_pv.mock_data = 0
|
||||
# status = dev.complete()
|
||||
# assert status.done is False
|
||||
# assert status.success is False
|
||||
# dev.stop()
|
||||
# time.sleep(0.2)
|
||||
# assert status.done is True
|
||||
# assert status.success is False
|
||||
|
||||
|
||||
def test_unstage(mock_bragg):
|
||||
mock_bragg.timeout_for_pvwait = 0.5
|
||||
mock_bragg.scan_control.scan_val_reset._read_pv.mock_data = 0
|
||||
mock_bragg.scan_control.scan_msg._read_pv.mock_data = ScanControlLoadMessage.PENDING
|
||||
# FIXME #22 once mock_pv supports callbacks, high priority!
|
||||
# def test_unstage(mock_bragg):
|
||||
# mock_bragg.timeout_for_pvwait = 0.5
|
||||
# mock_bragg.scan_control.scan_val_reset._read_pv.mock_data = 0
|
||||
# mock_bragg.scan_control.scan_msg._read_pv.mock_data = ScanControlLoadMessage.PENDING
|
||||
|
||||
with mock.patch.object(mock_bragg.scan_control.scan_val_reset, "put") as mock_put:
|
||||
status = mock_bragg.unstage()
|
||||
assert mock_put.call_count == 0
|
||||
mock_bragg.scan_control.scan_msg._read_pv.mock_data = ScanControlLoadMessage.SUCCESS
|
||||
with pytest.raises(TimeoutError):
|
||||
mock_bragg.unstage()
|
||||
assert mock_put.call_count == 1
|
||||
# with mock.patch.object(mock_bragg.scan_control.scan_val_reset, "put") as mock_put:
|
||||
# status = mock_bragg.unstage()
|
||||
# assert mock_put.call_count == 0
|
||||
# mock_bragg.scan_control.scan_msg._read_pv.mock_data = ScanControlLoadMessage.SUCCESS
|
||||
# with pytest.raises(TimeoutError):
|
||||
# mock_bragg.unstage()
|
||||
# assert mock_put.call_count == 1
|
||||
|
||||
|
||||
# TODO reimplement the test for stage method
|
||||
|
||||
@@ -0,0 +1,88 @@
|
||||
"""Tests for the Mo1BraggAngle class."""
|
||||
|
||||
import threading
|
||||
from unittest import mock
|
||||
|
||||
import ophyd
|
||||
import pytest
|
||||
from ophyd_devices.tests.utils import MockPV, patch_dual_pvs
|
||||
|
||||
from debye_bec.devices.mo1_bragg.mo1_bragg_angle import Mo1BraggAngle
|
||||
from debye_bec.devices.mo1_bragg.mo1_bragg_devices import Mo1BraggStoppedError
|
||||
|
||||
# pylint: disable=protected-access
|
||||
|
||||
|
||||
@pytest.fixture(scope="function")
|
||||
def mock_bragg() -> Mo1BraggAngle:
|
||||
"""Fixture for the Mo1BraggAngle device."""
|
||||
name = "bragg"
|
||||
prefix = "X01DA-OP-MO1:BRAGG:"
|
||||
with mock.patch.object(ophyd, "cl") as mock_cl:
|
||||
mock_cl.get_pv = MockPV
|
||||
mock_cl.thread_class = threading.Thread
|
||||
dev = Mo1BraggAngle(name=name, prefix=prefix)
|
||||
patch_dual_pvs(dev)
|
||||
yield dev
|
||||
|
||||
|
||||
def test_mo1_bragg_angle_init(mock_bragg):
|
||||
"""Test the initialization of the Mo1BraggAngle device."""
|
||||
assert mock_bragg.name == "bragg"
|
||||
assert mock_bragg.prefix == "X01DA-OP-MO1:BRAGG:"
|
||||
assert isinstance(mock_bragg.readback, ophyd.EpicsSignalRO)
|
||||
assert isinstance(mock_bragg.setpoint, ophyd.EpicsSignalWithRBV)
|
||||
assert isinstance(mock_bragg.low_lim, ophyd.EpicsSignalRO)
|
||||
assert isinstance(mock_bragg.high_lim, ophyd.EpicsSignalRO)
|
||||
|
||||
|
||||
def test_mo1_bragg_angle_egu(mock_bragg):
|
||||
"""Test the engineering unit of the Mo1BraggAngle device."""
|
||||
assert mock_bragg.egu == "deg"
|
||||
|
||||
|
||||
def test_mo1_bragg_angle_limits(mock_bragg):
|
||||
"""Test the limits of the Mo1BraggAngle device."""
|
||||
mock_bragg.low_lim._read_pv.mock_data = -10
|
||||
mock_bragg.high_lim._read_pv.mock_data = 10
|
||||
|
||||
assert mock_bragg.limits == (-10, 10)
|
||||
|
||||
|
||||
def test_mo1_bragg_angle_move(mock_bragg):
|
||||
"""Test the move method of the Mo1BraggAngle device."""
|
||||
mock_bragg.setpoint.put(0)
|
||||
mock_bragg.readback._read_pv.mock_data = 0 # EpicsSignalRO
|
||||
|
||||
# Change PV for motor is moving before starting the move
|
||||
mock_bragg.motor_is_moving._read_pv.mock_data = 0 # EpicsSignalRO
|
||||
status = mock_bragg.move(5)
|
||||
assert status.done is False
|
||||
|
||||
# Check setpoint is set correctly
|
||||
assert mock_bragg.setpoint.get() == 5
|
||||
|
||||
# Update the motor is moving PV to simulate that the move is done
|
||||
mock_bragg.motor_is_moving._read_pv.mock_data = 1
|
||||
assert mock_bragg.motor_is_moving.get() == 1
|
||||
|
||||
status.wait(timeout=5) # If the status does not resolve after 5 seconds, something is wrong
|
||||
assert status.done is True
|
||||
|
||||
|
||||
def test_mo1_bragg_angle_stop(mock_bragg):
|
||||
"""Test the stop method of the Mo1BraggAngle device."""
|
||||
assert mock_bragg.stopped is False
|
||||
|
||||
mock_bragg.stop()
|
||||
assert mock_bragg.stopped is True
|
||||
|
||||
status = mock_bragg.move(5)
|
||||
assert status.done is False
|
||||
|
||||
# stopped should be resetted
|
||||
assert mock_bragg.stopped is False
|
||||
|
||||
with pytest.raises(Mo1BraggStoppedError):
|
||||
mock_bragg.stop()
|
||||
status.wait(timeout=5) # This should raise before due to stop() call
|
||||
@@ -0,0 +1,166 @@
|
||||
# pylint: skip-file
|
||||
import threading
|
||||
from typing import Generator
|
||||
from unittest import mock
|
||||
|
||||
import ophyd
|
||||
import pytest
|
||||
from bec_server.scan_server.scan_worker import ScanWorker
|
||||
from ophyd.status import WaitTimeoutError
|
||||
from ophyd_devices.interfaces.base_classes.psi_device_base import DeviceStoppedError
|
||||
from ophyd_devices.tests.utils import MockPV
|
||||
|
||||
# from bec_server.device_server.tests.utils import DMMock
|
||||
from debye_bec.devices.nidaq.nidaq import Nidaq, NidaqError
|
||||
|
||||
# TODO move this function to ophyd_devices, it is duplicated in csaxs_bec and needed for other pluging repositories
|
||||
from debye_bec.devices.test_utils.utils import patch_dual_pvs
|
||||
|
||||
|
||||
@pytest.fixture(scope="function")
|
||||
def scan_worker_mock(scan_server_mock):
|
||||
"""Scan worker fixture, utility to generate scan_info for a given scan name."""
|
||||
scan_server_mock.device_manager.connector = mock.MagicMock()
|
||||
scan_worker = ScanWorker(parent=scan_server_mock)
|
||||
yield scan_worker
|
||||
|
||||
|
||||
@pytest.fixture(scope="function")
|
||||
def mock_nidaq() -> Generator[Nidaq, None, None]:
|
||||
"""Fixture for the Nidaq device."""
|
||||
name = "nidaq"
|
||||
prefix = "nidaq:prefix_test:"
|
||||
with mock.patch.object(ophyd, "cl") as mock_cl:
|
||||
mock_cl.get_pv = MockPV
|
||||
mock_cl.thread_class = threading.Thread
|
||||
dev = Nidaq(name=name, prefix=prefix)
|
||||
patch_dual_pvs(dev)
|
||||
yield dev
|
||||
|
||||
|
||||
def test_init(mock_nidaq):
|
||||
"""Test the initialization of the Nidaq device."""
|
||||
dev = mock_nidaq
|
||||
assert dev.name == "nidaq"
|
||||
assert dev.prefix == "nidaq:prefix_test:"
|
||||
assert dev.valid_scan_names == [
|
||||
"xas_simple_scan",
|
||||
"xas_simple_scan_with_xrd",
|
||||
"xas_advanced_scan",
|
||||
"xas_advanced_scan_with_xrd",
|
||||
"nidaq_continuous_scan",
|
||||
]
|
||||
|
||||
|
||||
def test_check_if_scan_name_is_valid(mock_nidaq):
|
||||
"""Test the check_if_scan_name_is_valid method."""
|
||||
dev = mock_nidaq
|
||||
dev.scan_info.msg.scan_name = "xas_simple_scan"
|
||||
assert dev._check_if_scan_name_is_valid()
|
||||
dev.scan_info.msg.scan_name = "invalid_scan_name"
|
||||
assert not dev._check_if_scan_name_is_valid()
|
||||
|
||||
|
||||
def test_set_config(mock_nidaq):
|
||||
dev = mock_nidaq
|
||||
# TODO #21 Add test logic for set_config, issue created #
|
||||
|
||||
|
||||
def test_on_connected(mock_nidaq):
|
||||
"""Test the on_connected method of the Nidaq device."""
|
||||
dev = mock_nidaq
|
||||
dev.power.put(0)
|
||||
dev.heartbeat._read_pv.mock_data = 0
|
||||
# First scenario, raise timeout error
|
||||
|
||||
# This will raise a WaitTimeoutError error as we currently do not support callbacks in the MockPV
|
||||
dev.timeout_wait_for_signal = 0.1
|
||||
# To check that it raised, we check that dev.power PV is set to 1
|
||||
# Set state PV to 0, 1 is expected value
|
||||
dev.state._read_pv.mock_data = 0
|
||||
with pytest.raises(WaitTimeoutError):
|
||||
dev.on_connected()
|
||||
assert dev.power.get() == 1
|
||||
# TODO, once the MOCKPv supports callbacks, we can test the rest of the logic issue #22
|
||||
|
||||
|
||||
# def test_on_stage(mock_nidaq):
|
||||
# dev = mock_nidaq
|
||||
# #TODO Add once MockPV supports callbacks #22
|
||||
|
||||
|
||||
def test_on_kickoff(mock_nidaq):
|
||||
"""Test the on_kickoff method of the Nidaq device."""
|
||||
dev = mock_nidaq
|
||||
dev.kickoff_call.put(0)
|
||||
dev.kickoff()
|
||||
assert dev.kickoff_call.get() == 1
|
||||
|
||||
|
||||
def test_on_unstage(mock_nidaq):
|
||||
"""Test the on_unstage method of the Nidaq device."""
|
||||
dev = mock_nidaq
|
||||
dev.state._read_pv.mock_data = 0 # Set state to 0, 1 is Standby
|
||||
dev._timeout_wait_for_pv = 0.1 # Set a short timeout for testing
|
||||
dev.enable_compression._read_pv.mock_data = 0 # Compression enabled
|
||||
with pytest.raises(WaitTimeoutError):
|
||||
dev.on_unstage()
|
||||
dev.state._read_pv.mock_data = 1
|
||||
# FIXME #22 add callback mechanism to MockPV to test the rest of the logic
|
||||
# dev.on_unstage()
|
||||
# assert dev.enable_compression.get() == 1
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
["scan_name", "raise_error", "nidaq_state"],
|
||||
[
|
||||
("line_scan", False, 0),
|
||||
("xas_simple_scan", False, 3),
|
||||
("xas_simple_scan", True, 0),
|
||||
("nidaq_continuous_scan", False, 0),
|
||||
],
|
||||
)
|
||||
def test_on_pre_scan(mock_nidaq, scan_name, raise_error, nidaq_state):
|
||||
"""Test the on_pre_scan method of the Nidaq device."""
|
||||
dev = mock_nidaq
|
||||
dev.state.put(nidaq_state)
|
||||
dev.scan_info.msg.scan_name = scan_name
|
||||
dev._timeout_wait_for_pv = 0.1 # Set a short timeout for testing
|
||||
if not raise_error:
|
||||
dev.pre_scan()
|
||||
else:
|
||||
with pytest.raises(WaitTimeoutError):
|
||||
dev.pre_scan()
|
||||
|
||||
|
||||
def test_on_complete(mock_nidaq):
|
||||
"""Test the on_complete method of the Nidaq device."""
|
||||
dev = mock_nidaq
|
||||
# Check for nidaq_continuous_scan
|
||||
dev.scan_info.msg.scan_name = "nidaq_continuous_scan"
|
||||
dev.state.put(0) # Set state to DISABLED
|
||||
status = dev.complete()
|
||||
assert status.done is False
|
||||
dev.state.put(1)
|
||||
# Should resolve now
|
||||
status.wait(timeout=5) # Wait for the status to complete
|
||||
assert status.done is True
|
||||
|
||||
# Check for XAS simple scan
|
||||
dev.scan_info.msg.scan_name = "xas_simple_scan"
|
||||
dev.state.put(0) # Set state to ACQUIRE
|
||||
dev.stop_call.put(0)
|
||||
dev._timeout_wait_for_pv = 5
|
||||
status = dev.on_complete()
|
||||
assert status.done is False
|
||||
assert dev.stop_call.get() == 1 # Should have called stop
|
||||
dev.state.put(1) # Set state to STANDBY
|
||||
# Should resolve now
|
||||
status.wait(timeout=5) # Wait for the status to complete
|
||||
assert status.done is True
|
||||
|
||||
# Test that it resolves if device is stopped
|
||||
dev.state.put(0) # Set state to DISABLED
|
||||
dev.stop()
|
||||
status.wait(timeout=5)
|
||||
assert status.done is True
|
||||
@@ -0,0 +1,283 @@
|
||||
# pylint: skip-file
|
||||
import threading
|
||||
from typing import TYPE_CHECKING, Generator
|
||||
from unittest import mock
|
||||
|
||||
import ophyd
|
||||
import pytest
|
||||
from bec_lib.messages import ScanStatusMessage
|
||||
from bec_server.scan_server.scan_worker import ScanWorker
|
||||
from ophyd_devices import CompareStatus, DeviceStatus
|
||||
from ophyd_devices.interfaces.base_classes.psi_device_base import DeviceStoppedError
|
||||
from ophyd_devices.tests.utils import MockPV, patch_dual_pvs
|
||||
from ophyd_devices.utils.psi_device_base_utils import TaskStatus
|
||||
|
||||
from debye_bec.devices.pilatus.pilatus import (
|
||||
ACQUIREMODE,
|
||||
COMPRESSIONALGORITHM,
|
||||
DETECTORSTATE,
|
||||
FILEWRITEMODE,
|
||||
TRIGGERMODE,
|
||||
Pilatus,
|
||||
)
|
||||
|
||||
if TYPE_CHECKING: # pragma no cover
|
||||
from bec_lib.messages import FileMessage
|
||||
|
||||
# @pytest.fixture(scope="function")
|
||||
# def scan_worker_mock(scan_server_mock):
|
||||
# scan_server_mock.device_manager.connector = mock.MagicMock()
|
||||
# scan_worker = ScanWorker(parent=scan_server_mock)
|
||||
# yield scan_worker
|
||||
|
||||
|
||||
@pytest.fixture(
|
||||
scope="function",
|
||||
params=[
|
||||
(0.1, 1, 1, "line_scan", "step"),
|
||||
(0.2, 2, 2, "time_scan", "step"),
|
||||
(0.5, 5, 5, "xas_advanced_scan", "fly"),
|
||||
],
|
||||
)
|
||||
def mock_scan_info(request, tmpdir):
|
||||
exp_time, frames_per_trigger, num_points, scan_name, scan_type = request.param
|
||||
scan_info = ScanStatusMessage(
|
||||
scan_id="test_id",
|
||||
status="open",
|
||||
scan_type=scan_type,
|
||||
scan_number=1,
|
||||
scan_parameters={
|
||||
"exp_time": exp_time,
|
||||
"frames_per_trigger": frames_per_trigger,
|
||||
"system_config": {},
|
||||
},
|
||||
info={"file_components": (f"{tmpdir}/data/S00000/S000001", "h5")},
|
||||
num_points=num_points,
|
||||
scan_name=scan_name,
|
||||
)
|
||||
yield scan_info
|
||||
|
||||
|
||||
@pytest.fixture(scope="function")
|
||||
def pilatus(mock_scan_info) -> Generator[Pilatus, None, None]:
|
||||
name = "pilatus"
|
||||
prefix = "X01DA-OP-MO1:PILATUS:"
|
||||
with mock.patch.object(ophyd, "cl") as mock_cl:
|
||||
mock_cl.get_pv = MockPV
|
||||
mock_cl.thread_class = threading.Thread
|
||||
dev = Pilatus(name=name, prefix=prefix)
|
||||
patch_dual_pvs(dev)
|
||||
# dev.image1 = mock.MagicMock()
|
||||
# with mock.patch.object(dev, "image1"):
|
||||
with mock.patch.object(dev, "task_handler"):
|
||||
dev.scan_info.msg = mock_scan_info
|
||||
try:
|
||||
yield dev
|
||||
finally:
|
||||
try:
|
||||
dev.on_destroy()
|
||||
except ophyd.utils.DestroyedError:
|
||||
pass
|
||||
|
||||
|
||||
# TODO figure out how to test as set calls on the PV below seem to break it..
|
||||
# def test_pilatus_on_connected(pilatus):
|
||||
# """Test the on_connected logic of the Pilatus detector."""
|
||||
# pilatus.cam.acquire._read_pv.mock_data = ACQUIREMODE.DONE.value
|
||||
# pilatus.hdf.capture._read_pv.mock_data = ACQUIREMODE.DONE.value
|
||||
# pilatus.on_connected()
|
||||
# assert pilatus.cam.trigger_mode.get() == TRIGGERMODE.MULT_TRIGGER
|
||||
# assert pilatus.hdf.file_write_mode.get() == FILEWRITEMODE.STREAM
|
||||
# assert pilatus.hdf.file_template.get() == "%s%s"
|
||||
# assert pilatus.hdf.auto_save.get() == 1
|
||||
# assert pilatus.hdf.lazy_open.get() == 1
|
||||
# assert pilatus.hdf.compression.get() == COMPRESSIONALGORITHM.NONE
|
||||
|
||||
|
||||
def test_pilatus_on_stop(pilatus):
|
||||
"""Test the on_stop logic of the Pilatus detector."""
|
||||
pilatus.cam.acquire._read_pv.mock_data = ACQUIREMODE.ACQUIRING.value
|
||||
pilatus.hdf.capture._read_pv.mock_data = ACQUIREMODE.ACQUIRING.value
|
||||
pilatus.on_stop()
|
||||
assert pilatus.cam.acquire.get() == ACQUIREMODE.DONE
|
||||
assert pilatus.hdf.capture.get() == ACQUIREMODE.DONE
|
||||
|
||||
|
||||
def test_pilatus_on_destroy(pilatus):
|
||||
"""Test the on_destroy logic of the Pilatus detector."""
|
||||
with mock.patch.object(pilatus, "on_stop") as mock_on_stop:
|
||||
pilatus.destroy()
|
||||
assert mock_on_stop.call_count == 1
|
||||
assert pilatus._poll_thread_kill_event.is_set()
|
||||
|
||||
|
||||
def test_pilatus_on_failure_callback(pilatus):
|
||||
"""Test the on_failure_callback logic of the Pilatus detector."""
|
||||
|
||||
with mock.patch.object(pilatus, "on_stop") as mock_on_stop:
|
||||
status = DeviceStatus(pilatus)
|
||||
status.set_finished() # Does not trigger 'stop'
|
||||
assert mock_on_stop.call_count == 0
|
||||
status = DeviceStatus(pilatus)
|
||||
status.set_exception(RuntimeError("Test error")) # triggers 'stop'
|
||||
assert mock_on_stop.call_count == 1
|
||||
|
||||
|
||||
def test_pilatus_on_pre_scan(pilatus):
|
||||
"""Test the on_pre_scan logic of the Pilatus detector."""
|
||||
scan_msg = pilatus.scan_info.msg
|
||||
if scan_msg.scan_type != "step" and scan_msg.scan_name not in pilatus.xas_xrd_scan_names:
|
||||
assert pilatus.on_pre_scan() is None
|
||||
return
|
||||
pilatus.cam.acquire._read_pv.mock_data = ACQUIREMODE.DONE.value
|
||||
pilatus.hdf.capture._read_pv.mock_data = ACQUIREMODE.DONE.value
|
||||
pilatus.cam.armed._read_pv.mock_data = DETECTORSTATE.UNARMED.value
|
||||
status = pilatus.on_pre_scan()
|
||||
assert status.done is False
|
||||
pilatus.cam.armed.put(DETECTORSTATE.ARMED.value)
|
||||
status.wait(timeout=5)
|
||||
assert status.done is True
|
||||
assert status.success is True
|
||||
|
||||
|
||||
def test_pilatus_on_trigger(pilatus):
|
||||
"""test on trigger logic of the Pilatus detector."""
|
||||
scan_msg = pilatus.scan_info.msg
|
||||
if scan_msg.scan_type != "step" and scan_msg.scan_name not in pilatus.xas_xrd_scan_names:
|
||||
status = pilatus.trigger()
|
||||
assert status.done is True
|
||||
assert status.success is True
|
||||
return None
|
||||
pilatus.hdf.num_captured._read_pv.mock_data = 0
|
||||
pilatus.trigger_shot.put(0)
|
||||
status = pilatus.trigger()
|
||||
assert status.done is False
|
||||
assert pilatus.trigger_shot.get() == 1
|
||||
pilatus.hdf.num_captured._read_pv.mock_data = 1
|
||||
status.wait(timeout=5)
|
||||
assert status.done is True
|
||||
assert status.success is True
|
||||
|
||||
|
||||
def test_pilatus_on_trigger_cancel_on_stop(pilatus):
|
||||
"""Test that the status of the trigger is cancelled if stop is called"""
|
||||
if pilatus.scan_info.msg.scan_name.startswith("xas"):
|
||||
status = pilatus.trigger()
|
||||
assert status.done is True
|
||||
assert status.success is True
|
||||
return
|
||||
pilatus.hdf.num_captured._read_pv.mock_data = 0
|
||||
pilatus.trigger_shot.put(0)
|
||||
status = pilatus.trigger()
|
||||
assert status.done is False
|
||||
with pytest.raises(DeviceStoppedError):
|
||||
pilatus.stop()
|
||||
status.wait(timeout=5)
|
||||
|
||||
|
||||
def test_pilatus_on_complete(pilatus: Pilatus):
|
||||
"""Test the on_complete logic of the Pilatus detector."""
|
||||
|
||||
if pilatus.scan_info.msg.scan_name.startswith("xas"):
|
||||
# TODO add test cases for xas scans
|
||||
# status = pilatus.complete()
|
||||
# assert status.done is True
|
||||
# assert status.success is True
|
||||
return
|
||||
# Check in addition that the file event is set properly, once with if it works, and once if not (i.e. when cancelled)
|
||||
for success in [True, False]:
|
||||
if success is True:
|
||||
pilatus.file_event.put(file_path="", done=False, successful=False)
|
||||
pilatus._full_path = "file-path-for-success"
|
||||
else:
|
||||
pilatus.file_event.put(file_path="", done=False, successful=True)
|
||||
pilatus._full_path = "file-path-for-failure"
|
||||
# Set values for relevant PVs
|
||||
pilatus.cam.acquire._read_pv.mock_data = ACQUIREMODE.ACQUIRING.value
|
||||
pilatus.hdf.capture._read_pv.mock_data = ACQUIREMODE.ACQUIRING.value
|
||||
pilatus.cam.armed._read_pv.mock_data = DETECTORSTATE.ARMED.value
|
||||
num_images = pilatus.scan_info.msg.num_points * pilatus.scan_info.msg.scan_parameters.get(
|
||||
"frames_per_trigger", 1
|
||||
)
|
||||
pilatus.hdf.num_captured._read_pv.mock_data = num_images - 1
|
||||
# Call on complete
|
||||
pilatus.n_images = num_images
|
||||
status = pilatus.complete()
|
||||
# Should not be finished
|
||||
assert status.done is False
|
||||
pilatus.cam.acquire.put(ACQUIREMODE.DONE.value)
|
||||
pilatus.hdf.capture.put(ACQUIREMODE.DONE.value)
|
||||
pilatus.cam.armed.put(DETECTORSTATE.UNARMED.value)
|
||||
assert status.done is False
|
||||
if success is True:
|
||||
pilatus.hdf.num_captured._read_pv.mock_data = num_images
|
||||
# Now it should resolve
|
||||
status.wait(timeout=5)
|
||||
assert status.done is True
|
||||
assert status.success is True
|
||||
file_msg: FileMessage = pilatus.file_event.get()
|
||||
assert file_msg.file_path == "file-path-for-success"
|
||||
assert file_msg.done is True
|
||||
assert file_msg.successful is True
|
||||
else:
|
||||
with pytest.raises(DeviceStoppedError):
|
||||
pilatus.stop()
|
||||
status.wait(timeout=5)
|
||||
assert status.done is True
|
||||
assert status.success is False
|
||||
file_msg: FileMessage = pilatus.file_event.get()
|
||||
assert file_msg.file_path == "file-path-for-failure"
|
||||
assert file_msg.done is True
|
||||
assert file_msg.successful is False
|
||||
|
||||
|
||||
# TODO, figure out how to properly test this..
|
||||
# def test_pilatus_on_stage(pilatus):
|
||||
# """Test the on_stage logic of the Pilatus detector."""
|
||||
# # Make sure that no additional logic from stage signals of underlying components is triggered
|
||||
# pilatus.stage_sigs = {}
|
||||
# pilatus.cam.stage_sigs = {}
|
||||
# pilatus.hdf.stage_sigs = {}
|
||||
# if pilatus.scan_info.msg.scan_name.startswith("xas"):
|
||||
# pilatus.on_stage()
|
||||
# return
|
||||
# exp_time = pilatus.scan_info.msg.scan_parameters.get("exp_time", 0.1)
|
||||
# n_images = pilatus.scan_info.msg.num_points * pilatus.scan_info.msg.scan_parameters.get(
|
||||
# "frames_per_trigger", 1
|
||||
# )
|
||||
# if exp_time <= 0.1:
|
||||
# with pytest.raises(ValueError):
|
||||
# pilatus.on_stage()
|
||||
# return
|
||||
# pilatus.filter_number.put(10)
|
||||
# pilatus.cam.array_counter.put(1)
|
||||
# file_components = pilatus.scan_info.msg.info.get("file_components", ("", ""))[0]
|
||||
# base_path = file_components[0].rsplit("/", 1)[0]
|
||||
# file_name = file_components[0].rsplit("/", 1)[1] + "_pilatus.h5"
|
||||
# file_path = os.path.join(base_path, file_name)
|
||||
# pilatus.on_stage()
|
||||
# assert pilatus.cam.array_callbacks.get() == 0
|
||||
# assert pilatus.hdf.enable.get() == 1
|
||||
# assert pilatus.cam.num_exposures.get() == 1
|
||||
# assert pilatus.cam.num_images.get() == n_images
|
||||
# assert pilatus.cam.acquire_time.get() == exp_time - pilatus._readout_time
|
||||
# assert pilatus.cam.acquire_period.get() == exp_time
|
||||
# assert pilatus.filter_number.get() == 0
|
||||
# assert pilatus.hdf.file_path.get() == base_path
|
||||
# assert pilatus.hdf.file_name.get() == file_name
|
||||
# assert pilatus.hdf.num_capture.get() == n_images
|
||||
# assert pilatus.cam.array_counter.get() == 0
|
||||
# file_msg: FileMessage = pilatus.file_event.get()
|
||||
# assert file_msg.file_path == file_path
|
||||
# assert file_msg.done is False
|
||||
# assert file_msg.successful is False
|
||||
|
||||
|
||||
def test_pilatus_on_stage_raises_low_exp_time(pilatus):
|
||||
"""Test that on_stage raises a ValueError if the exposure time is too low."""
|
||||
pilatus.scan_info.msg.scan_parameters["exp_time"] = 0.09
|
||||
scan_msg = pilatus.scan_info.msg
|
||||
if scan_msg.scan_type != "step" and scan_msg.scan_name not in pilatus.xas_xrd_scan_names:
|
||||
return
|
||||
with pytest.raises(ValueError):
|
||||
pilatus.on_stage()
|
||||
@@ -1,31 +1,34 @@
|
||||
# Getting Started with Testing using pytest
|
||||
|
||||
BEC is using the [pytest](https://docs.pytest.org/en/8.0.x/) framework.
|
||||
It can be install via
|
||||
``` bash
|
||||
BEC is using the [pytest](https://docs.pytest.org/en/latest/) framework.
|
||||
It can be installed via
|
||||
|
||||
```bash
|
||||
pip install pytest
|
||||
```
|
||||
in your *python environment*.
|
||||
|
||||
in your _python environment_.
|
||||
We note that pytest is part of the optional-dependencies `[dev]` of the plugin package.
|
||||
|
||||
## Introduction
|
||||
|
||||
Tests in this package should be stored in the `tests` directory.
|
||||
We suggest to sort tests of different submodules, i.e. `scans` or `devices` in the respective folder structure, and to folow a naming convention of `<test_module_name.py>`.
|
||||
It is mandatory for test files to begin with `test_` for pytest to discover them.
|
||||
|
||||
To run all tests, navigate to the directory of the plugin from the command line, and run the command
|
||||
To run all tests, navigate to the directory of the plugin from the command line, and run the command
|
||||
|
||||
``` bash
|
||||
```bash
|
||||
pytest -v --random-order ./tests
|
||||
```
|
||||
|
||||
Note, the python environment needs to be active.
|
||||
The additional arg `-v` allows pytest to run in verbose mode which provides more detailed information about the tests being run.
|
||||
The argument `--random-order` instructs pytest to run the tests in random order, which is the default in the CI pipelines.
|
||||
The argument `--random-order` instructs pytest to run the tests in random order, which is the default in the CI pipelines.
|
||||
|
||||
## Test examples
|
||||
|
||||
Writing tests can be quite specific for the given function.
|
||||
Writing tests can be quite specific for the given function.
|
||||
We recommend writing tests as isolated as possible, i.e. try to test single functions instead of full classes.
|
||||
A very useful class to enable isolated testing is [MagicMock](https://docs.python.org/3/library/unittest.mock.html).
|
||||
In addition, we also recommend to take a look at the [How-to guides from pytest](https://docs.pytest.org/en/8.0.x/how-to/index.html).
|
||||
|
||||
|
||||
@@ -1,31 +1,34 @@
|
||||
# Getting Started with Testing using pytest
|
||||
|
||||
BEC is using the [pytest](https://docs.pytest.org/en/8.0.x/) framework.
|
||||
It can be install via
|
||||
``` bash
|
||||
BEC is using the [pytest](https://docs.pytest.org/en/latest/) framework.
|
||||
It can be installed via
|
||||
|
||||
```bash
|
||||
pip install pytest
|
||||
```
|
||||
in your *python environment*.
|
||||
|
||||
in your _python environment_.
|
||||
We note that pytest is part of the optional-dependencies `[dev]` of the plugin package.
|
||||
|
||||
## Introduction
|
||||
|
||||
Tests in this package should be stored in the `tests` directory.
|
||||
We suggest to sort tests of different submodules, i.e. `scans` or `devices` in the respective folder structure, and to folow a naming convention of `<test_module_name.py>`.
|
||||
It is mandatory for test files to begin with `test_` for pytest to discover them.
|
||||
|
||||
To run all tests, navigate to the directory of the plugin from the command line, and run the command
|
||||
To run all tests, navigate to the directory of the plugin from the command line, and run the command
|
||||
|
||||
``` bash
|
||||
```bash
|
||||
pytest -v --random-order ./tests
|
||||
```
|
||||
|
||||
Note, the python environment needs to be active.
|
||||
The additional arg `-v` allows pytest to run in verbose mode which provides more detailed information about the tests being run.
|
||||
The argument `--random-order` instructs pytest to run the tests in random order, which is the default in the CI pipelines.
|
||||
The argument `--random-order` instructs pytest to run the tests in random order, which is the default in the CI pipelines.
|
||||
|
||||
## Test examples
|
||||
|
||||
Writing tests can be quite specific for the given function.
|
||||
Writing tests can be quite specific for the given function.
|
||||
We recommend writing tests as isolated as possible, i.e. try to test single functions instead of full classes.
|
||||
A very useful class to enable isolated testing is [MagicMock](https://docs.python.org/3/library/unittest.mock.html).
|
||||
In addition, we also recommend to take a look at the [How-to guides from pytest](https://docs.pytest.org/en/8.0.x/how-to/index.html).
|
||||
|
||||
|
||||
@@ -66,7 +66,6 @@ def test_xas_simple_scan(scan_assembler, ScanStubStatusMock):
|
||||
device=None,
|
||||
action="open_scan",
|
||||
parameter={
|
||||
"scan_motors": [],
|
||||
"readout_priority": {
|
||||
"monitored": [],
|
||||
"baseline": [],
|
||||
@@ -92,18 +91,6 @@ def test_xas_simple_scan(scan_assembler, ScanStubStatusMock):
|
||||
action="read",
|
||||
parameter={},
|
||||
),
|
||||
DeviceInstructionMessage(
|
||||
metadata={"readout_priority": "monitored", "RID": "my_test_request_id"},
|
||||
device="mo1_bragg",
|
||||
action="rpc",
|
||||
parameter={
|
||||
"device": "mo1_bragg",
|
||||
"func": "move_type.set",
|
||||
"rpc_id": "my_test_rpc_id",
|
||||
"args": ("energy",),
|
||||
"kwargs": {},
|
||||
},
|
||||
),
|
||||
DeviceInstructionMessage(
|
||||
metadata={"readout_priority": "monitored", "RID": "my_test_request_id"},
|
||||
device=["bpm4i", "eiger", "mo1_bragg", "nidaq", "samx"],
|
||||
@@ -153,17 +140,18 @@ def test_xas_simple_scan_with_xrd(scan_assembler, ScanStubStatusMock):
|
||||
stop=5,
|
||||
scan_time=1,
|
||||
scan_duration=10,
|
||||
xrd_enable_low=True,
|
||||
num_trigger_low=1,
|
||||
exp_time_low=1,
|
||||
break_enable_low=True,
|
||||
break_time_low=1,
|
||||
cycle_low=1,
|
||||
xrd_enable_high=True,
|
||||
num_trigger_high=2,
|
||||
exp_time_high=3,
|
||||
break_enable_high=True,
|
||||
break_time_high=2,
|
||||
exp_time=1,
|
||||
n_of_trigger=1,
|
||||
cycle_high=4,
|
||||
)
|
||||
request.device_manager.add_device("nidaq")
|
||||
reference_commands = get_instructions(request, ScanStubStatusMock)
|
||||
# TODO #64 based on creating this ScanStatusMessage, we should test the logic of stage/kickoff/complete/unstage in Pilatus and mo1Bragg
|
||||
|
||||
assert reference_commands == [
|
||||
None,
|
||||
@@ -179,7 +167,6 @@ def test_xas_simple_scan_with_xrd(scan_assembler, ScanStubStatusMock):
|
||||
device=None,
|
||||
action="open_scan",
|
||||
parameter={
|
||||
"scan_motors": [],
|
||||
"readout_priority": {
|
||||
"monitored": [],
|
||||
"baseline": [],
|
||||
@@ -205,18 +192,6 @@ def test_xas_simple_scan_with_xrd(scan_assembler, ScanStubStatusMock):
|
||||
action="read",
|
||||
parameter={},
|
||||
),
|
||||
DeviceInstructionMessage(
|
||||
metadata={"readout_priority": "monitored", "RID": "my_test_request_id"},
|
||||
device="mo1_bragg",
|
||||
action="rpc",
|
||||
parameter={
|
||||
"device": "mo1_bragg",
|
||||
"func": "move_type.set",
|
||||
"rpc_id": "my_test_rpc_id",
|
||||
"args": ("energy",),
|
||||
"kwargs": {},
|
||||
},
|
||||
),
|
||||
DeviceInstructionMessage(
|
||||
metadata={"readout_priority": "monitored", "RID": "my_test_request_id"},
|
||||
device=["bpm4i", "eiger", "mo1_bragg", "nidaq", "samx"],
|
||||
@@ -286,7 +261,6 @@ def test_xas_advanced_scan(scan_assembler, ScanStubStatusMock):
|
||||
device=None,
|
||||
action="open_scan",
|
||||
parameter={
|
||||
"scan_motors": [],
|
||||
"readout_priority": {
|
||||
"monitored": [],
|
||||
"baseline": [],
|
||||
@@ -312,18 +286,6 @@ def test_xas_advanced_scan(scan_assembler, ScanStubStatusMock):
|
||||
action="read",
|
||||
parameter={},
|
||||
),
|
||||
DeviceInstructionMessage(
|
||||
metadata={"readout_priority": "monitored", "RID": "my_test_request_id"},
|
||||
device="mo1_bragg",
|
||||
action="rpc",
|
||||
parameter={
|
||||
"device": "mo1_bragg",
|
||||
"func": "move_type.set",
|
||||
"rpc_id": "my_test_rpc_id",
|
||||
"args": ("energy",),
|
||||
"kwargs": {},
|
||||
},
|
||||
),
|
||||
DeviceInstructionMessage(
|
||||
metadata={"readout_priority": "monitored", "RID": "my_test_request_id"},
|
||||
device=["bpm4i", "eiger", "mo1_bragg", "nidaq", "samx"],
|
||||
@@ -375,13 +337,13 @@ def test_xas_advanced_scan_with_xrd(scan_assembler, ScanStubStatusMock):
|
||||
scan_duration=10,
|
||||
p_kink=50,
|
||||
e_kink=8500,
|
||||
xrd_enable_low=True,
|
||||
num_trigger_low=1,
|
||||
exp_time_low=1,
|
||||
break_enable_low=True,
|
||||
break_time_low=1,
|
||||
cycle_low=1,
|
||||
xrd_enable_high=True,
|
||||
num_trigger_high=2,
|
||||
exp_time_high=3,
|
||||
break_enable_high=True,
|
||||
break_time_high=2,
|
||||
exp_time=1,
|
||||
n_of_trigger=1,
|
||||
cycle_high=4,
|
||||
)
|
||||
request.device_manager.add_device("nidaq")
|
||||
@@ -401,7 +363,6 @@ def test_xas_advanced_scan_with_xrd(scan_assembler, ScanStubStatusMock):
|
||||
device=None,
|
||||
action="open_scan",
|
||||
parameter={
|
||||
"scan_motors": [],
|
||||
"readout_priority": {
|
||||
"monitored": [],
|
||||
"baseline": [],
|
||||
@@ -427,18 +388,6 @@ def test_xas_advanced_scan_with_xrd(scan_assembler, ScanStubStatusMock):
|
||||
action="read",
|
||||
parameter={},
|
||||
),
|
||||
DeviceInstructionMessage(
|
||||
metadata={"readout_priority": "monitored", "RID": "my_test_request_id"},
|
||||
device="mo1_bragg",
|
||||
action="rpc",
|
||||
parameter={
|
||||
"device": "mo1_bragg",
|
||||
"func": "move_type.set",
|
||||
"rpc_id": "my_test_rpc_id",
|
||||
"args": ("energy",),
|
||||
"kwargs": {},
|
||||
},
|
||||
),
|
||||
DeviceInstructionMessage(
|
||||
metadata={"readout_priority": "monitored", "RID": "my_test_request_id"},
|
||||
device=["bpm4i", "eiger", "mo1_bragg", "nidaq", "samx"],
|
||||
|
||||
@@ -0,0 +1,126 @@
|
||||
# pylint: skip-file
|
||||
from unittest import mock
|
||||
|
||||
from bec_lib.messages import DeviceInstructionMessage
|
||||
from bec_server.device_server.tests.utils import DMMock
|
||||
|
||||
from debye_bec.scans import NIDAQContinuousScan
|
||||
|
||||
|
||||
def get_instructions(request, ScanStubStatusMock):
|
||||
request.metadata["RID"] = "my_test_request_id"
|
||||
|
||||
def fake_done():
|
||||
"""
|
||||
Fake done function for ScanStubStatusMock. Upon each call, it returns the next value from the generator.
|
||||
This is used to simulate the completion of the scan.
|
||||
"""
|
||||
yield False
|
||||
yield False
|
||||
yield True
|
||||
|
||||
def fake_complete(*args, **kwargs):
|
||||
yield "fake_complete"
|
||||
return ScanStubStatusMock(done_func=fake_done)
|
||||
|
||||
with (
|
||||
mock.patch.object(request.stubs, "complete", side_effect=fake_complete),
|
||||
mock.patch.object(request.stubs, "_get_result_from_status", return_value=None),
|
||||
):
|
||||
reference_commands = list(request.run())
|
||||
|
||||
for cmd in reference_commands:
|
||||
if not cmd or isinstance(cmd, str):
|
||||
continue
|
||||
if "RID" in cmd.metadata:
|
||||
cmd.metadata["RID"] = "my_test_request_id"
|
||||
if "rpc_id" in cmd.parameter:
|
||||
cmd.parameter["rpc_id"] = "my_test_rpc_id"
|
||||
cmd.metadata.pop("device_instr_id", None)
|
||||
|
||||
return reference_commands
|
||||
|
||||
|
||||
def test_xas_simple_scan(scan_assembler, ScanStubStatusMock):
|
||||
|
||||
request = scan_assembler(NIDAQContinuousScan, scan_duration=10)
|
||||
request.device_manager.add_device("nidaq")
|
||||
reference_commands = get_instructions(request, ScanStubStatusMock)
|
||||
assert reference_commands == [
|
||||
None,
|
||||
None,
|
||||
DeviceInstructionMessage(
|
||||
metadata={"readout_priority": "monitored", "RID": "my_test_request_id"},
|
||||
device=None,
|
||||
action="scan_report_instruction",
|
||||
parameter={"device_progress": ["nidaq"]},
|
||||
),
|
||||
DeviceInstructionMessage(
|
||||
metadata={"readout_priority": "monitored", "RID": "my_test_request_id"},
|
||||
device=None,
|
||||
action="open_scan",
|
||||
parameter={
|
||||
"readout_priority": {
|
||||
"monitored": [],
|
||||
"baseline": [],
|
||||
"on_request": [],
|
||||
"async": ["nidaq"],
|
||||
},
|
||||
"num_points": 0,
|
||||
"positions": [],
|
||||
"scan_name": "nidaq_continuous_scan",
|
||||
"scan_type": "fly",
|
||||
},
|
||||
),
|
||||
DeviceInstructionMessage(metadata={}, device="nidaq", action="stage", parameter={}),
|
||||
DeviceInstructionMessage(
|
||||
metadata={},
|
||||
device=["bpm4i", "eiger", "mo1_bragg", "samx"],
|
||||
action="stage",
|
||||
parameter={},
|
||||
),
|
||||
DeviceInstructionMessage(
|
||||
metadata={"readout_priority": "baseline", "RID": "my_test_request_id"},
|
||||
device=["samx"],
|
||||
action="read",
|
||||
parameter={},
|
||||
),
|
||||
DeviceInstructionMessage(
|
||||
metadata={"readout_priority": "monitored", "RID": "my_test_request_id"},
|
||||
device=["bpm4i", "eiger", "mo1_bragg", "nidaq", "samx"],
|
||||
action="pre_scan",
|
||||
parameter={},
|
||||
),
|
||||
DeviceInstructionMessage(
|
||||
metadata={"readout_priority": "monitored", "RID": "my_test_request_id"},
|
||||
device="nidaq",
|
||||
action="kickoff",
|
||||
parameter={"configure": {}},
|
||||
),
|
||||
"fake_complete",
|
||||
DeviceInstructionMessage(
|
||||
metadata={"readout_priority": "monitored", "RID": "my_test_request_id", "point_id": 0},
|
||||
device=["bpm4i", "eiger", "mo1_bragg"],
|
||||
action="read",
|
||||
parameter={"group": "monitored"},
|
||||
),
|
||||
DeviceInstructionMessage(
|
||||
metadata={"readout_priority": "monitored", "RID": "my_test_request_id", "point_id": 1},
|
||||
device=["bpm4i", "eiger", "mo1_bragg"],
|
||||
action="read",
|
||||
parameter={"group": "monitored"},
|
||||
),
|
||||
"fake_complete",
|
||||
DeviceInstructionMessage(
|
||||
metadata={},
|
||||
device=["bpm4i", "eiger", "mo1_bragg", "nidaq", "samx"],
|
||||
action="unstage",
|
||||
parameter={},
|
||||
),
|
||||
DeviceInstructionMessage(
|
||||
metadata={"readout_priority": "monitored", "RID": "my_test_request_id"},
|
||||
device=None,
|
||||
action="close_scan",
|
||||
parameter={},
|
||||
),
|
||||
]
|
||||
Reference in New Issue
Block a user