Compare commits

...

87 Commits

Author SHA1 Message Date
x01da 3e80b0fd8d Automatic generated files for digital twin
CI for debye_bec / test (pull_request) Successful in 1m5s
CI for debye_bec / test (push) Successful in 1m7s
2026-05-18 09:03:26 +02:00
x01da 823142b296 Add config checker 2026-05-18 09:03:26 +02:00
appel_c 5d6d0535af test: remove scan_motors to fix tests
CI for debye_bec / test (pull_request) Successful in 1m4s
CI for debye_bec / test (push) Successful in 1m7s
2026-05-18 08:36:30 +02:00
x01da 7797ce1980 fix mo1_bragg test
CI for debye_bec / test (push) Failing after 1m4s
CI for debye_bec / test (pull_request) Failing after 1m5s
2026-05-18 07:38:46 +02:00
x01da 7b1ea281a3 Changed from EpicsMotor to EpicsMotorEC
CI for debye_bec / test (push) Failing after 1m9s
CI for debye_bec / test (pull_request) Failing after 1m10s
2026-05-18 06:57:11 +02:00
x01da a75320ccbc refactoring, bugfix theme move buttons
CI for debye_bec / test (push) Failing after 1m4s
CI for debye_bec / test (pull_request) Failing after 1m7s
2026-05-13 16:17:12 +02:00
x01da c04d829fc6 wip: digital twin
CI for debye_bec / test (push) Failing after 1m4s
CI for debye_bec / test (pull_request) Failing after 1m6s
2026-05-13 13:29:09 +02:00
x01da 5d862e1d5b Adding additional signals for nidaq and mono
CI for debye_bec / test (pull_request) Failing after 1m1s
CI for debye_bec / test (push) Failing after 1m8s
2026-05-13 08:57:56 +02:00
x01da 3e959e6c5d wip: digital twin
CI for debye_bec / test (push) Failing after 1m4s
CI for debye_bec / test (pull_request) Failing after 1m9s
2026-05-11 10:16:42 +02:00
x01da fe43dafac8 wip: digital twin
CI for debye_bec / test (push) Failing after 1m1s
CI for debye_bec / test (pull_request) Failing after 1m0s
2026-05-07 15:49:13 +02:00
x01da 8493b60468 wip: digital twin
CI for debye_bec / test (push) Failing after 1m0s
CI for debye_bec / test (pull_request) Failing after 1m2s
2026-05-07 14:52:54 +02:00
x01da 0365d6eac7 refactoring
CI for debye_bec / test (pull_request) Failing after 1m4s
CI for debye_bec / test (push) Failing after 1m5s
2026-05-07 07:32:43 +02:00
x01da 6da7e665b3 wip: digital twin
CI for debye_bec / test (push) Failing after 1m0s
CI for debye_bec / test (pull_request) Failing after 1m1s
2026-05-06 16:12:25 +02:00
x01da b0a7d6905c wip: digital twin
CI for debye_bec / test (push) Failing after 1m0s
CI for debye_bec / test (pull_request) Failing after 1m3s
2026-05-06 14:53:06 +02:00
x01da acc5e320cf wip: digital twin
CI for debye_bec / test (push) Failing after 1m4s
CI for debye_bec / test (pull_request) Failing after 1m0s
2026-05-06 12:58:45 +02:00
x01da 3d2485aea7 wip: digital twin
CI for debye_bec / test (pull_request) Failing after 1m3s
CI for debye_bec / test (push) Failing after 1m35s
2026-05-05 15:32:01 +02:00
x01da 131d7f7f3e Updated nexus structure
CI for debye_bec / test (push) Failing after 1m9s
CI for debye_bec / test (pull_request) Failing after 1m35s
2026-05-05 13:38:56 +02:00
x01da 16bd819a9f wip: digital twin
CI for debye_bec / test (pull_request) Failing after 1m3s
CI for debye_bec / test (push) Failing after 1m5s
2026-05-04 12:46:50 +02:00
x01da b14f2c0fe3 wip: digital twin
CI for debye_bec / test (push) Failing after 1m11s
CI for debye_bec / test (pull_request) Failing after 1m9s
2026-05-04 06:52:48 +02:00
x01da 09799554ba signal name correction 2026-05-04 06:52:30 +02:00
x01da 3d756469e3 Change of kind for angle signal 2026-05-04 06:52:01 +02:00
x01da 4ca59c57be wip: digital twin
CI for debye_bec / test (push) Failing after 1m1s
CI for debye_bec / test (pull_request) Failing after 1m4s
2026-04-30 15:46:05 +02:00
x01da 576c59f5e5 Added angle signal for digital twin 2026-04-30 15:45:28 +02:00
x01da ce3f231276 wip: digital twin
CI for debye_bec / test (pull_request) Failing after 1m4s
CI for debye_bec / test (push) Failing after 1m6s
2026-04-30 14:47:16 +02:00
x01da 274bb9154c wip: digital twin
CI for debye_bec / test (push) Failing after 1m5s
CI for debye_bec / test (pull_request) Failing after 1m8s
2026-04-30 08:11:33 +02:00
x01da 282756288f Adding xrt library for digital twin 2026-04-30 08:11:09 +02:00
x01da 101954476c wip: digital twin
CI for debye_bec / test (pull_request) Failing after 1m1s
CI for debye_bec / test (push) Failing after 1m6s
2026-04-29 16:36:52 +02:00
x01da 339adab06c wip: digital twin widget
CI for debye_bec / test (push) Failing after 1m5s
CI for debye_bec / test (pull_request) Failing after 1m2s
2026-04-29 14:22:10 +02:00
x01da 588152871c wip: move components to label 2026-04-29 14:21:46 +02:00
x01da f3fbdbf5f2 update of config and nexus structure 2026-04-29 14:18:48 +02:00
x01da 7fb68d67de Adding bender radius signal
CI for debye_bec / test (pull_request) Failing after 1m4s
CI for debye_bec / test (push) Failing after 1m5s
2026-04-28 15:16:59 +02:00
x01da 3132658396 Signal name change (consistency) 2026-04-28 15:16:40 +02:00
x01da 6e149a6a73 Adding string representation of status 2026-04-28 15:16:03 +02:00
x01da 204e2827eb Adding signals of additional nidaq signals 2026-04-28 15:14:48 +02:00
x01da adf3a8ab11 Renaming of offset signals
CI for debye_bec / test (push) Failing after 1m4s
CI for debye_bec / test (pull_request) Failing after 59s
2026-04-28 11:21:47 +02:00
x01da 6a2d813506 Corrected ot_rotx name 2026-04-28 10:10:13 +02:00
x01da 4103b3153a feat: Added frontend absorber
CI for debye_bec / test (push) Failing after 1m3s
CI for debye_bec / test (pull_request) Failing after 2m16s
2026-04-27 15:20:02 +02:00
x01da c428bb5a87 Change of order of nidaq signals
CI for debye_bec / test (push) Successful in 1m8s
CI for debye_bec / test (pull_request) Successful in 1m9s
2026-04-02 14:22:29 +02:00
x01da 632d554245 add additional signals to nidaq
CI for debye_bec / test (push) Successful in 1m3s
CI for debye_bec / test (pull_request) Successful in 1m6s
2026-03-25 09:48:25 +01:00
x01da efd8842540 do not close pilatus curtain after measurements
CI for debye_bec / test (push) Successful in 1m1s
CI for debye_bec / test (pull_request) Successful in 1m3s
2026-03-02 13:28:24 +01:00
x01da fd1626fbcd nidaq improvement on_stage 2026-03-02 13:27:51 +01:00
x01da 062df3171b add additional signals for xrd tigger information 2026-03-02 13:27:51 +01:00
x01da 37a268fe7b add additional CI channels for NIDAQ. Add enable PV for dead time correction 2026-03-02 13:27:51 +01:00
x01da e9e7d84e60 reworked function to get rid of (potentionally infinite) loop. 2026-03-02 13:27:51 +01:00
x01da 1c0c9ad53e add opencv dependency for hutch cameras 2026-03-02 13:27:51 +01:00
x01da faeb991b75 create hutch camera class 2026-03-02 13:27:51 +01:00
x01da 7377613213 add config signals 2026-03-02 13:27:51 +01:00
x01da d8383d3b73 add string representation of signals. add Pips class/device 2026-03-02 13:27:51 +01:00
x01da c3bfab2056 add string representation of signals 2026-03-02 13:27:51 +01:00
x01da 0261c601ff uncomment Pilatus-Sample distance 2026-03-02 13:27:51 +01:00
x01da d3dc130f11 uncomment ionization chamber and add pips diode 2026-03-02 13:27:51 +01:00
x01da ed1e5a027f add hutch cameras to config 2026-03-02 13:27:51 +01:00
x01da df2961ce8e add hutch cameras config 2026-03-02 13:27:51 +01:00
x01da e179fc1a07 add gas sensors to config 2026-03-02 13:27:51 +01:00
perl_d 60d1dfc5af Update repo with template version v1.2.8
CI for debye_bec / test (pull_request) Successful in 1m4s
CI for debye_bec / test (push) Successful in 1m2s
2026-02-27 15:49:26 +01:00
perl_d 3e2e37908b Update repo with template version v1.2.7
CI for debye_bec / test (push) Failing after 0s
CI for debye_bec / test (pull_request) Failing after 0s
2026-02-27 12:11:40 +01:00
appel_c 804a731181 test(pilatus): Fix on_complete callback for pilatus
CI for debye_bec / test (pull_request) Successful in 1m0s
CI for debye_bec / test (push) Successful in 1m4s
2025-12-05 14:18:46 +01:00
appel_c 99f6192f37 refactor: deprecate duplicate Status implementation.
CI for debye_bec / test (push) Failing after 1m2s
CI for debye_bec / test (pull_request) Failing after 1m9s
2025-11-30 22:29:23 +01:00
appel_c 0a8272685d fix(status): cleanup and remove of old status usage 2025-11-30 22:28:34 +01:00
appel_c c6ed27966c fix(status): fix compare and transition status occurences
CI for debye_bec / test (push) Failing after 1m9s
CI for debye_bec / test (pull_request) Failing after 3m33s
2025-11-26 13:46:53 +01:00
appel_c 6a8f6c7988 fix: remove enums from typehints
CI for debye_bec / test (pull_request) Failing after 2s
CI for debye_bec / test (push) Successful in 1m16s
2025-09-18 07:17:48 +02:00
appel_c 6bfc8999f7 refactor: fix set_exception for AndStatusWithList 2025-09-18 07:14:39 +02:00
appel_c c70088e7bc tests: fix test cases after refactoring
CI for debye_bec / test (pull_request) Successful in 1m7s
CI for debye_bec / test (push) Successful in 1m5s
2025-09-17 07:44:31 +02:00
appel_c 190eae2c3f fix: formatting 2025-09-17 07:44:31 +02:00
appel_c 1d6caa2291 refactor(pilatus-curtain): cleanup pilatus curtain 2025-09-17 07:44:31 +02:00
appel_c 9b739c852d fix(mo1-bragg-devices): revert signal name change, motor_is_moving 2025-09-17 07:44:31 +02:00
appel_c 9d9a2e9681 refactor(pilatus): Cleanupt PIlatus integration 2025-09-17 07:44:31 +02:00
x01da ed759da14f bugfix for XAS only scans 2025-09-17 07:44:31 +02:00
x01da 158175f545 add baseline signals (not workling yet) 2025-09-17 07:44:31 +02:00
x01da ed9148ed96 bugfix 2025-09-17 07:44:31 +02:00
x01da 0a83b59af8 refacotring 2025-09-17 07:44:31 +02:00
x01da a67394a9a2 refactoring 2025-09-17 07:44:31 +02:00
x01da a6f0d01558 updated/added configs 2025-09-17 07:44:31 +02:00
gac-x01da 217a14d03d configure mono trigger signal 2025-09-17 07:44:31 +02:00
gac-x01da 4424f83b8b working example of combined xas_xrd scan 2025-09-17 07:44:31 +02:00
gac-x01da 626b0dc8a0 added xrd energy signal 2025-09-17 07:44:31 +02:00
gac-x01da 1f7fdb89d7 add on_stage for xas_xrd scans 2025-09-17 07:44:31 +02:00
gac-x01da ee748d56c4 added timestamp signals for nidaq 2025-09-17 07:44:31 +02:00
gac-x01da 02e6462ea1 updated trigger signals of mono 2025-09-17 07:44:31 +02:00
appel_c 2633c8be0a refactor(pilatus): add live mode to pilatus 2025-09-17 07:44:31 +02:00
appel_c 09c3e395de refactor(pilatus): update config, add live mode 2025-09-17 07:44:31 +02:00
appel_c 8e5bdd230d test(pilatus): add tests for the pilatus. on_stage & on_connected tests fail due to AD baseclass callbacks 2025-09-17 07:44:31 +02:00
gac-x01da dd0fe31cb7 feat(pilatus): Initial commit of Pilatus integration 2025-09-17 07:44:31 +02:00
appel_c 442c421d05 fix: update repo to copier template v1-2-2
CI for debye_bec / test (pull_request) Successful in 59s
CI for debye_bec / test (push) Successful in 58s
2025-09-11 18:17:22 +02:00
appel_c 85042a7f45 feat: update repo with copier template for gitea migration 2025-09-11 15:43:15 +02:00
appel_c 6a1992f605 fix(test-mo1-bragg): fix test for mo1_bragg mov succeeds. 2025-07-31 15:55:08 +02:00
appel_c aaf4084517 fix: rename .service_config to .config 2025-07-31 15:54:54 +02:00
59 changed files with 7142 additions and 2163 deletions
+1 -1
View File
@@ -2,7 +2,7 @@
# 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.0.0
_commit: v1.2.8
_src_path: https://github.com/bec-project/plugin_copier_template.git
make_commit: false
project_name: debye_bec
+102
View File
@@ -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
+62
View File
@@ -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\"
}"
-7
View File
@@ -1,7 +0,0 @@
include:
- file: /templates/plugin-repo-template.yml
inputs:
name: debye_bec
target: debye_bec
branch: $CHILD_PIPELINE_BRANCH
project: bec/awi_utils
@@ -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()})"
)
@@ -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})
+41
View File
@@ -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()
+272
View File
@@ -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
@@ -1,3 +1,8 @@
###################################
## SLS Machine ##
###################################
curr:
readoutPriority: baseline
description: SLS ring current
@@ -1,227 +0,0 @@
## 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
deviceTags:
- optics
- slits
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
deviceTags:
- optics
- slits
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
deviceTags:
- optics
- slits
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
deviceTags:
- optics
- slits
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
deviceTags:
- optics
- slits
## 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
deviceTags:
- optics
- slits
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
deviceTags:
- optics
- slits
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
deviceTags:
- optics
- slits
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
deviceTags:
- optics
- slits
## 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
deviceTags:
- optics
- slits
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
deviceTags:
- optics
- slits
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
deviceTags:
- optics
- slits
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
deviceTags:
- optics
- slits
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
deviceTags:
- optics
- slits
## 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
deviceTags:
- optics
- slits
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
deviceTags:
- optics
- slits
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
deviceTags:
- optics
- slits
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
deviceTags:
- optics
- slits
+411
View File
@@ -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,636 +0,0 @@
optic_slit_config:
- !include ./x01da_optic_slits.yaml
machine_config:
- !include ./x01da_machine.yaml
## 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
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
# 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:TRX
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
# 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
## Pinhole alignment stages -- Physical Positioners
pin1_trx:
readoutPriority: baseline
description: Pinhole X-translation
deviceClass: ophyd.EpicsMotor
deviceConfig:
prefix: X01DA-ES1-PIN1:TRX
onFailure: retry
enabled: true
softwareTrigger: false
tags: Endstation
pin1_try:
readoutPriority: baseline
description: Pinhole Y-translation
deviceClass: ophyd.EpicsMotor
deviceConfig:
prefix: X01DA-ES1-PIN1:TRY
onFailure: retry
enabled: true
softwareTrigger: false
tags: Endstation
pin1_rotx:
readoutPriority: baseline
description: Pinhole X-rotation
deviceClass: ophyd.EpicsMotor
deviceConfig:
prefix: X01DA-ES1-PIN1:ROTX
onFailure: retry
enabled: true
softwareTrigger: false
tags: Endstation
pin1_roty:
readoutPriority: baseline
description: Pinhole Y-rotation
deviceClass: ophyd.EpicsMotor
deviceConfig:
prefix: X01DA-ES1-PIN1:ROTY
onFailure: retry
enabled: true
softwareTrigger: false
tags: Endstation
+83
View File
@@ -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
+108
View File
@@ -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
+72
View File
@@ -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
+11 -1
View File
@@ -4,7 +4,7 @@ from __future__ import annotations
from typing import TYPE_CHECKING
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
@@ -20,6 +20,16 @@ if TYPE_CHECKING: # pragma: no cover
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:")
+79
View File
@@ -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)
+11 -1
View File
@@ -4,7 +4,7 @@ from __future__ import annotations
from typing import TYPE_CHECKING
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
@@ -20,6 +20,16 @@ if TYPE_CHECKING: # pragma: no cover
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:")
@@ -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
+51 -36
View File
@@ -9,16 +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, WaitTimeoutError
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
@@ -56,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")
@@ -140,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
@@ -155,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
@@ -176,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
@@ -193,12 +200,14 @@ 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
@@ -213,7 +222,7 @@ class Mo1Bragg(PSIDeviceBase, Mo1BraggPositioner):
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
# Wait for params to be checked from controller
status.wait(self.timeout_for_pvwait)
return None
@@ -271,7 +280,7 @@ class Mo1Bragg(PSIDeviceBase, Mo1BraggPositioner):
self.scan_control.scan_status,
transitions=[ScanControlScanStatus.READY, ScanControlScanStatus.RUNNING],
strict=True,
raise_states=[ScanControlScanStatus.PARAMETER_WRONG],
failure_states=[ScanControlScanStatus.PARAMETER_WRONG],
)
self.cancel_on_stop(status)
start_func(1)
@@ -311,7 +320,7 @@ class Mo1Bragg(PSIDeviceBase, Mo1BraggPositioner):
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)
@@ -342,7 +351,7 @@ class Mo1Bragg(PSIDeviceBase, Mo1BraggPositioner):
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
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":
@@ -391,22 +400,24 @@ class Mo1Bragg(PSIDeviceBase, Mo1BraggPositioner):
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
"""
status_list = []
@@ -417,10 +428,10 @@ class Mo1Bragg(PSIDeviceBase, Mo1BraggPositioner):
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(exp_time_high))
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(exp_time_low))
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))
@@ -429,10 +440,15 @@ class Mo1Bragg(PSIDeviceBase, Mo1BraggPositioner):
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.
@@ -453,7 +469,6 @@ class Mo1Bragg(PSIDeviceBase, Mo1BraggPositioner):
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."""
for key, value in self.scan_info.msg.request_inputs["inputs"].items():
@@ -76,8 +76,14 @@ 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")
@@ -85,13 +91,21 @@ class Mo1BraggCrystal(Device):
current_d_spacing = Cpt(
EpicsSignalRO, suffix="current_d_spacing_RBV", kind="normal", auto_monitor=True
)
current_offset = Cpt(
EpicsSignalRO, suffix="current_offset_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
)
@@ -139,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")
@@ -232,6 +254,8 @@ 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 = Cpt(EpicsSignalRO, suffix="feedback_pos_angle_RBV", kind="normal", auto_monitor=True)
########## Move Command PVs ##########
move_abs = Cpt(EpicsSignal, suffix="move_abs", kind="config", put_complete=True)
@@ -384,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:
@@ -393,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:
+249 -69
View File
@@ -1,12 +1,11 @@
from __future__ import annotations
import time
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 SubscriptionStatus, WaitTimeoutError
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
@@ -34,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",
@@ -147,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,
@@ -201,32 +371,6 @@ class NidaqControl(Device):
)
### 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"
)
@@ -252,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"
)
@@ -301,38 +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)
energy = 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_factor = Cpt(EpicsSignal, suffix="NIDAQ-EncoderFactor", 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-CIChans", 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):
@@ -352,7 +528,7 @@ class Nidaq(PSIDeviceBase, NidaqControl):
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",
@@ -551,7 +727,11 @@ class Nidaq(PSIDeviceBase, NidaqControl):
# Stage call to IOC
status = CompareStatus(self.state, NidaqState.STAGE)
self.cancel_on_stop(status)
self.stage_call.set(1).wait(timeout=self._timeout_wait_for_pv)
# 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()
+685
View File
@@ -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()
+69 -44
View File
@@ -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
+6
View File
@@ -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"
+324 -96
View File
@@ -1,5 +1,6 @@
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"""
@@ -12,102 +13,6 @@ class DebyeNexusStructure(DefaultFormat):
instrument = entry.create_group(name="instrument")
instrument.attrs["NX_class"] = "NXinstrument"
###################
## 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"
# 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"
offset = crystal.create_soft_link(
name="offset",
target="/entry/collection/devices/mo1_bragg/mo1_bragg_crystal_current_offset/value",
)
offset.attrs["NX_class"] = "NX_FLOAT"
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"
##################
## 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"
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"
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_yaw_angle = collimating_mirror.create_soft_link(
name="incident angle", target="/entry/collection/devices/cm_roty/cm_roty/value"
)
cm_yaw_angle.attrs["NX_class"] = "NX_FLOAT"
##################
## 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"
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_incidence_angle = focusing_mirror.create_soft_link(
name="incidence angle",
target="/entry/collection/devices/fm_incidence_angle/fm_incidence_angle/value",
)
fm_incidence_angle.attrs["NX_class"] = "NX_FLOAT"
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_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"
##################
## source specific information
###################
@@ -123,3 +28,326 @@ class DebyeNexusStructure(DefaultFormat):
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")
+6
View File
@@ -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
View File
+42 -40
View File
@@ -108,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__(
@@ -118,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,
):
@@ -138,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".
@@ -162,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):
@@ -233,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__(
@@ -245,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,
):
@@ -270,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".
@@ -298,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
@@ -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
+10 -2
View File
@@ -6,13 +6,21 @@ build-backend = "hatchling.build"
name = "debye_bec"
version = "0.0.0"
description = "A plugin repository for BEC"
requires-python = ">=3.10"
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 = [
View File
View File
View File
View File
+22 -17
View File
@@ -52,7 +52,7 @@ def test_init(mock_bragg):
dev = mock_bragg
assert dev.name == "bragg"
assert dev.prefix == "X01DA-OP-MO1:BRAGG:"
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"
@@ -74,15 +74,16 @@ def test_egu(mock_bragg):
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):
@@ -105,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
@@ -131,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
@@ -142,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):
+283
View File
@@ -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()
+13 -16
View File
@@ -66,7 +66,6 @@ def test_xas_simple_scan(scan_assembler, ScanStubStatusMock):
device=None,
action="open_scan",
parameter={
"scan_motors": [],
"readout_priority": {
"monitored": [],
"baseline": [],
@@ -141,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,
@@ -167,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": [],
@@ -262,7 +261,6 @@ def test_xas_advanced_scan(scan_assembler, ScanStubStatusMock):
device=None,
action="open_scan",
parameter={
"scan_motors": [],
"readout_priority": {
"monitored": [],
"baseline": [],
@@ -339,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")
@@ -365,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": [],
@@ -60,7 +60,6 @@ def test_xas_simple_scan(scan_assembler, ScanStubStatusMock):
device=None,
action="open_scan",
parameter={
"scan_motors": [],
"readout_priority": {
"monitored": [],
"baseline": [],