110 Commits

Author SHA1 Message Date
x01da
efd8842540 do not close pilatus curtain after measurements
All checks were successful
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
60d1dfc5af Update repo with template version v1.2.8
All checks were successful
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
3e2e37908b Update repo with template version v1.2.7
Some checks failed
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
804a731181 test(pilatus): Fix on_complete callback for pilatus
All checks were successful
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
99f6192f37 refactor: deprecate duplicate Status implementation.
Some checks failed
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
0a8272685d fix(status): cleanup and remove of old status usage 2025-11-30 22:28:34 +01:00
c6ed27966c fix(status): fix compare and transition status occurences
Some checks failed
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
6a8f6c7988 fix: remove enums from typehints
Some checks failed
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
6bfc8999f7 refactor: fix set_exception for AndStatusWithList 2025-09-18 07:14:39 +02:00
c70088e7bc tests: fix test cases after refactoring
All checks were successful
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
190eae2c3f fix: formatting 2025-09-17 07:44:31 +02:00
1d6caa2291 refactor(pilatus-curtain): cleanup pilatus curtain 2025-09-17 07:44:31 +02:00
9b739c852d fix(mo1-bragg-devices): revert signal name change, motor_is_moving 2025-09-17 07:44:31 +02:00
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
2633c8be0a refactor(pilatus): add live mode to pilatus 2025-09-17 07:44:31 +02:00
09c3e395de refactor(pilatus): update config, add live mode 2025-09-17 07:44:31 +02:00
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
442c421d05 fix: update repo to copier template v1-2-2
All checks were successful
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
85042a7f45 feat: update repo with copier template for gitea migration 2025-09-11 15:43:15 +02:00
6a1992f605 fix(test-mo1-bragg): fix test for mo1_bragg mov succeeds. 2025-07-31 15:55:08 +02:00
aaf4084517 fix: rename .service_config to .config 2025-07-31 15:54:54 +02:00
gac-x01da
34dbc1839d fix test 2025-06-25 16:05:20 +02:00
gac-x01da
db6a9a502f bugfix for when nidaq is stopped early 2025-06-25 15:56:39 +02:00
gac-x01da
e6586ceab2 Make sure that parameters are set before validating 2025-06-25 15:55:54 +02:00
ed6d64c7f9 fix(camera): fix num_rotation_90 for cameras 2025-06-23 16:31:34 +02:00
43e8aea6c8 tests: fix test for MR, mo1_bragg and nidaq tests skipped, check issue 22 2025-06-20 09:30:53 +02:00
abf432f2a9 refactor: fix formatting 2025-06-20 09:19:51 +02:00
gac-x01da
b3672cf5f5 refactor: add compare and transition status for nidaq and mo1_bragg 2025-06-20 09:16:27 +02:00
gac-x01da
fa434794c3 feat(metadata-schema): add metadata schema 2025-06-20 09:16:03 +02:00
gac-x01da
9be74da098 feat(debye-nexus-structure): add first nexus template for debye 2025-06-20 09:15:39 +02:00
gac-x01da
29913cea61 refactor(nidaq-enums): add additional enums for NIDAQ 2025-06-20 09:14:40 +02:00
gac-x01da
881bc9e7a3 refactor(mo1-bragg-device): add Pvs for mono 2025-06-20 09:14:10 +02:00
e941647750 refactor(progress-signal): ProgressSignal for mo1_bragg and nidaq 2025-06-18 14:32:31 +02:00
827557b667 refactor(debye-cam): add preview signal to camera integrations 2025-06-18 14:32:31 +02:00
gac-x01da
a9fd62d249 feat: add initial file structure 2025-06-18 14:29:51 +02:00
27ff5697af fix(device-configs): change to relative path for !include syntax 2025-06-18 14:23:19 +02:00
07d05f9490 fix(nidaq): fix proper handling return of DeviceStatus for complete method 2025-06-18 14:23:19 +02:00
gac-x01da
39adeb72de test: add test for nidaq continous scan 2025-06-18 14:23:19 +02:00
gac-x01da
bc666dc807 fix: double timeout for for wait for ScanControlMessage 2025-06-18 14:23:19 +02:00
gac-x01da
89cc27a8da feat: add nidaq_continuous_scan scan 2025-06-18 14:23:19 +02:00
gac-x01da
718a001a8a typo and adding pinhole motors 2025-06-18 14:23:19 +02:00
gac-x01da
f038679d76 refactor(nidaq): add energy pv from nidaq 2025-06-18 14:23:19 +02:00
gac-x01da
a1433efbf8 update of test_config to include focusing mirror 2025-06-18 14:23:19 +02:00
gac-x01da
79ead32e79 feat(nidaq): ensure nidaq is powered on during on_connected 2025-06-18 14:23:19 +02:00
gac-x01da
c934aa8e9a refactor: update configs with optic slit config, machine config 2025-06-18 14:23:19 +02:00
0d87e958d0 Update copier template source to github 2025-06-11 16:26:24 +02:00
e4556ad90e refactor: migrate debye_bec to copier template 2025-05-16 15:21:04 +02:00
ci_update_bot
da89f9287c docs: Update device list 2025-05-16 13:19:11 +00:00
665c290a90 test(test-camers): Add unit tests for camera integration of prosilica and basler cameras 2025-05-16 15:14:17 +02:00
gac-x01da
10b0608d31 fix: debug with devices, throttle live_view to 1Hz 2025-05-16 15:14:17 +02:00
ca2cf40d6a refactor: refactor basler and prosilica cameras 2025-05-16 15:14:17 +02:00
415c601d2a feat(debye-base-cam): introduce base class for cameras at debye 2025-05-16 15:14:17 +02:00
ci_update_bot
0c1f41cd7c docs: Update device list 2025-05-16 13:08:16 +00:00
gac-x01da
0cdad97d00 refactor: add mo1_bragg_angle to x01da_test_config 2025-05-16 15:02:28 +02:00
b3f63f4f76 tests(mo1-bragg-angle): add tests for mo1_bragg_angle 2025-05-09 16:26:00 +02:00
87ea95e975 fix: improve move method of mo1_bragg_devices 2025-05-09 16:25:41 +02:00
4d9a062b8c tests: fix tests and formatting 2025-05-09 16:00:07 +02:00
gac-x01da
c782324065 feat(mo1-bragg-angle): add Mo1BraggAngle positioner class; closes #14 2025-05-09 08:41:44 +02:00
gac-x01da
5bb0df2ddf refactor(mo1-bragg-positioner): remove move_type, cleanupt of angle pvs in mo1_bragg_positioner 2025-05-09 08:36:26 +02:00
ci_update_bot
20759e7ff1 docs: Update device list 2025-05-07 11:44:11 +00:00
b03b90a86a fix: fix range checks in Mo1Bragg and IonizationChamber 2025-05-07 13:38:43 +02:00
74e0b01b02 fix: temporary comment, issue created #16 2025-05-07 12:52:50 +02:00
gac-x01da
7b7a24b6c8 refactor: formatting 2025-05-07 12:40:13 +02:00
gac-x01da
31ff28236b fix: update config, remove cameras for the moment 2025-05-07 12:38:41 +02:00
gac-x01da
002a3323a0 fix(ion-chambers): fix ion chamber code at beamline 2025-05-07 12:38:04 +02:00
gac-x01da
24d81bb180 build: update black dependency to ~=25.0 2025-05-07 12:37:33 +02:00
gac-x01da
32e24cd92a fix: fix occasional crash of mo1_bragg for scan; closes #11 2025-05-07 11:15:21 +02:00
03e3b1c605 fix: fix imports in basler_cam 2025-05-06 16:26:27 +02:00
6ab1a2941c fix: fix typo in device config mo1_bragg 2025-05-06 11:19:18 +02:00
b8a050c424 tests: fix DeviceMessages for tests 2025-05-05 15:14:11 +02:00
gac-x01da
a8e7325f0f fix(mo1-bragg): fix error upon fresh start, not yet working. 2025-05-05 14:31:03 +02:00
gac-x01da
ae50cbdfd1 refactor(device-config): extend device config 2025-05-05 14:31:03 +02:00
gac-x01da
c164414631 fix(camera): add throttled update to cameras 2025-05-05 14:31:03 +02:00
gac-x01da
c074240444 tpying fix 2025-05-05 14:30:36 +02:00
gac-x01da
759636cf2c fix type reffoil changer 2025-05-05 14:30:36 +02:00
gac-x01da
510073d2f2 fix(reffoil-changer): add scaninfo to __init__ signature 2025-05-05 14:30:36 +02:00
gac-x01da
17b671dd4b Introduction of reference foil changer 2025-05-05 14:30:36 +02:00
gac-x01da
fe3e8b6291 fix: update devices in configs, to be checked 2025-05-05 14:30:36 +02:00
gac-x01da
b7f72f8e44 wip support BEC core scans 2025-05-05 14:28:55 +02:00
gac-x01da
8cad42920b Changed readout priority from async to baseline for new devices 2025-05-05 14:28:39 +02:00
gac-x01da
c43ca4aaa8 fix (mo1-bragg): fix code after test with hardware at the beamline 2025-05-05 14:28:26 +02:00
ce046f55f6 refactor (mo1-bragg): refactored Mo1 Bragg class with new base class PSIDeviceBase 2025-05-05 14:28:26 +02:00
gac-x01da
849b2d2bdb feat: add camera and power supply ophyd classes 2025-05-05 14:27:43 +02:00
68 changed files with 5157 additions and 2001 deletions

9
.copier-answers.yml Normal file
View File

@@ -0,0 +1,9 @@
# Do not edit this file!
# It is needed to track the repo template version, and editing may break things.
# This file will be overwritten by copier on template updates.
_commit: v1.2.8
_src_path: https://github.com/bec-project/plugin_copier_template.git
make_commit: false
project_name: debye_bec
widget_plugins_input: []

102
.gitea/workflows/ci.yml Normal file
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

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\"
}"

View File

@@ -1,7 +0,0 @@
include:
- project: bec/awi_utils
file: /templates/plugin-repo-template.yml
inputs:
name: "debye"
target: "debye_bec"
branch: $CHILD_PIPELINE_BRANCH

View File

@@ -1,6 +1,7 @@
BSD 3-Clause License
Copyright (c) 2024, Paul Scherrer Institute
Copyright (c) 2025, Paul Scherrer Institute
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
@@ -25,4 +26,4 @@ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

1
bin/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
# Add anything you don't want to check in to git, e.g. very large files

View File

@@ -10,7 +10,7 @@ While command-line arguments have to be set in the pre-startup script, the
post-startup script can be used to load beamline specific information and
to setup the prompts.
from bec_lib import bec_logger
from bec_lib.logger import bec_logger
logger = bec_logger.logger

View File

@@ -1,10 +1,14 @@
"""
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.
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})

View File

@@ -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

View File

@@ -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

View File

@@ -0,0 +1,449 @@
###################################
## Optical Table ##
###################################
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
ot_es1_trz:
readoutPriority: baseline
description: Optical Table ES1 Z-Translation
deviceClass: ophyd.EpicsMotor
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.EpicsMotor
deviceConfig:
prefix: X01DA-ES2-OT:TRZ
onFailure: retry
enabled: true
softwareTrigger: false
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 ##
###################################
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
###################################
## 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.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
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
###################################
## 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.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
###################################
## Segmented Arc ##
###################################
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
###################################
## IC1 + IC2 Manipulator ##
###################################
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 ##
###################################
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
###################################
## 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

View File

@@ -0,0 +1,218 @@
###################################
## Frontend Slits ##
###################################
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
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 ##
###################################
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
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

View File

@@ -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

View File

@@ -0,0 +1,18 @@
###################################
## SLS Machine ##
###################################
curr:
readoutPriority: baseline
description: SLS ring current
deviceClass: ophyd.EpicsSignalRO
deviceConfig:
auto_monitor: true
read_pv: AGEBD-DBPM3CURR:CURRENT-AVG
deviceTags:
- machine
onFailure: buffer
enabled: true
readOnly: true
softwareTrigger: false

View File

@@ -0,0 +1,395 @@
###################################
## Monochromator ##
###################################
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
###################################
## Optics Slits + Beam Monitor 1 ##
###################################
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
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
###################################
## Focusing Mirror ##
###################################
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
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
###################################
## Optics Slits + Beam Monitor 2 ##
###################################
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
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

View File

@@ -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: monitored
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

View File

@@ -1,230 +0,0 @@
## Bragg Monochromator
mo1_bragg:
readoutPriority: baseline
description: Positioner for the Monochromator
deviceClass: debye_bec.devices.mo1_bragg.mo1_bragg.Mo1Bragg
deviceConfig:
prefix: "X01DA-OP-MO1:BRAGG:"
onFailure: retry
enabled: true
softwareTrigger: false
dummy_pv:
readoutPriority: monitored
description: Heartbeat of Bragg
deviceClass: ophyd.EpicsSignalRO
deviceConfig:
read_pv: "X01DA-OP-MO1:BRAGG:heartbeat_RBV"
onFailure: retry
enabled: true
softwareTrigger: false
# NIDAQ
nidaq:
readoutPriority: monitored
description: NIDAQ backend for data reading for debye scans
deviceClass: debye_bec.devices.nidaq.nidaq.Nidaq
deviceConfig:
prefix: "X01DA-PC-SCANSERVER:"
onFailure: retry
enabled: true
softwareTrigger: false
# 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
# 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

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

View File

@@ -0,0 +1,108 @@
###################################
## Pinhole ##
###################################
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
###################################
## Beam Stop ##
###################################
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
###################################
## 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

View File

@@ -1,29 +1,46 @@
# from ophyd_devices.sim.sim_signals import SetableSignal
# from ophyd_devices.utils.psi_component import PSIComponent, SignalType
import numpy as np
from ophyd import ADBase
"""Basler camera class for Debye BEC."""
from __future__ import annotations
from typing import TYPE_CHECKING
from ophyd import ADBase, EpicsSignalRO
from ophyd import ADComponent as ADCpt
from ophyd import Kind
from ophyd import Component as Cpt
from ophyd_devices import PreviewSignal
from ophyd_devices.devices.areadetector.cam import AravisDetectorCam
from ophyd_devices.devices.areadetector.plugins import ImagePlugin_V35
from ophyd_devices.interfaces.base_classes.psi_device_base import PSIDeviceBase
from debye_bec.devices.cameras.debye_base_cam import DebyeBaseCamera
if TYPE_CHECKING: # pragma: no cover
from bec_lib.devicemanager import ScanInfo
class BaslerCamBase(ADBase):
"""BaslerCam Base class."""
cam_detector_state_string = Cpt(EpicsSignalRO, suffix="cam1:DetectorState_RBV", string=True)
_default_configuration_attrs = [
'cam1.acquire_time',
'cam1.detector_state',
'cam_detector_state_string',
'cam1.gain',
'cam1.model',
]
cam1 = ADCpt(AravisDetectorCam, "cam1:")
image1 = ADCpt(ImagePlugin_V35, "image1:")
class BaslerCam(PSIDeviceBase, BaslerCamBase):
class BaslerCam(DebyeBaseCamera, BaslerCamBase):
"""Basler camera class at Debye. IOC prefix: X01DA-ES-XRAYEYE:"""
# preview_2d = PSIComponent(SetableSignal, signal_type=SignalType.PREVIEW, ndim=2, kind=Kind.omitted)
def emit_to_bec(self, *args, obj=None, old_value=None, value=None, **kwargs):
width = self.image1.array_size.width.get()
height = self.image1.array_size.height.get()
data = np.reshape(value, (height, width))
# self.preview_2d.put(data)
self._run_subs(sub_type=self.SUB_DEVICE_MONITOR_2D, value=data)
def on_connected(self):
self.image1.array_data.subscribe(self.emit_to_bec, run=False)
preview = Cpt(
PreviewSignal,
name="preview",
ndim=2,
num_rotation_90=3,
doc="Preview signal for the camera.",
)

View File

@@ -0,0 +1,138 @@
"""Base class for Camera integration at Debye."""
from __future__ import annotations
import threading
from typing import TYPE_CHECKING
import numpy as np
from bec_lib.logger import bec_logger
from ophyd import Component as Cpt
from ophyd import DeviceStatus, StatusBase
from ophyd_devices import PreviewSignal
from ophyd_devices.interfaces.base_classes.psi_device_base import PSIDeviceBase
from typeguard import typechecked
if TYPE_CHECKING: # pragma: no cover
from bec_lib.devicemanager import ScanInfo
from ophyd_devices.devices.areadetector.plugins import ImagePlugin_V35
logger = bec_logger.logger
class DebyeBaseCamera(PSIDeviceBase):
"""Base class for Debye cameras."""
USER_ACCESS = ["live_mode"]
preview = Cpt(
PreviewSignal,
name="preview",
ndim=2,
num_rotation_90=-1,
doc="Preview signal for the camera.",
)
def __init__(self, *, name: str, prefix: str = "", scan_info: ScanInfo | None = None, **kwargs):
super().__init__(name=name, prefix=prefix, scan_info=scan_info, **kwargs)
self.image1: "ImagePlugin_V35"
self._update_frequency = 1 # Hz
self._live_mode = False
self._live_mode_event = None
self._task_status = None
@property
def live_mode(self) -> bool:
"""Live mode status."""
return self._live_mode
@typechecked
@live_mode.setter
def live_mode(self, value: bool) -> None:
"""
Set the live mode status.
Args:
value (bool): True to enable live mode, False to disable.
"""
if value == self._live_mode:
return
self._live_mode = value
if value:
self._start_live_mode()
else:
self._stop_live_mode()
def _start_live_mode(self) -> None:
"""Start live mode."""
if self._live_mode_event is not None: # Kill task if it exists
self._live_mode_event.set()
self._live_mode_event = None
if self._task_status is not None:
self.task_handler.kill_task(task_status=self._task_status)
self._task_status = None
self._live_mode_event = threading.Event()
self._task_status = self.task_handler.submit_task(task=self.emit_to_bec)
def _stop_live_mode(self) -> None:
"""Stop live mode."""
if self._live_mode_event is not None:
self._live_mode_event.set()
self._live_mode_event = None
def emit_to_bec(self):
"""Emit the image data to BEC. If _live_mode_event is set, stop the task."""
while not self._live_mode_event.wait(1 / self._update_frequency):
value = self.image1.array_data.get()
if value is None:
continue
width = self.image1.array_size.width.get()
height = self.image1.array_size.height.get()
# Geometry correction for the image
data = np.reshape(value, (height, width))
self.preview.put(data)
########################################
# Beamline Specific Implementations #
########################################
def on_init(self) -> None:
"""
Called when the device is initialized.
No signals are connected at this point. If you like to
set default values on signals, please use on_connected instead.
"""
def on_connected(self) -> None:
"""
Called after the device is connected and its signals are connected.
Default values for signals should be set here.
"""
self.live_mode = True
def on_stage(self) -> DeviceStatus | StatusBase | None:
"""
Called while staging the device.
Information about the upcoming scan can be accessed from the scan_info (self.scan_info.msg) object.
"""
def on_unstage(self) -> DeviceStatus | StatusBase | None:
"""Called while unstaging the device."""
def on_pre_scan(self) -> DeviceStatus | StatusBase | None:
"""Called right before the scan starts on all devices automatically."""
def on_trigger(self) -> DeviceStatus | StatusBase | None:
"""Called when the device is triggered."""
def on_complete(self) -> DeviceStatus | StatusBase | None:
"""Called to inquire if a device has completed a scans."""
def on_kickoff(self) -> DeviceStatus | StatusBase | None:
"""Called to kickoff a device for a fly scan. Has to be called explicitly."""
def on_stop(self) -> None:
"""Called when the device is stopped."""

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)

View File

@@ -1,25 +1,49 @@
import numpy as np
from ophyd import ADBase
"""Prosilica camera class for integration of beam_monitor 1/2 cameras."""
from __future__ import annotations
from typing import TYPE_CHECKING
from ophyd import ADBase, EpicsSignalRO
from ophyd import ADComponent as ADCpt
from ophyd import Component as Cpt
from ophyd import Device
from ophyd_devices import PreviewSignal
from ophyd_devices.devices.areadetector.cam import ProsilicaDetectorCam
from ophyd_devices.devices.areadetector.plugins import ImagePlugin_V35
from ophyd_devices.interfaces.base_classes.psi_device_base import PSIDeviceBase
from debye_bec.devices.cameras.debye_base_cam import DebyeBaseCamera
if TYPE_CHECKING: # pragma: no cover
from bec_lib.devicemanager import ScanInfo
class ProsilicaCamBase(ADBase):
"""Base class for Prosilica cameras."""
cam_detector_state_string = Cpt(EpicsSignalRO, suffix="cam1:DetectorState_RBV", string=True)
_default_configuration_attrs = [
'cam1.acquire_time',
'cam1.detector_state',
'cam_detector_state_string',
'cam1.gain',
'cam1.model',
]
cam1 = ADCpt(ProsilicaDetectorCam, "cam1:")
image1 = ADCpt(ImagePlugin_V35, "image1:")
class ProsilicaCam(PSIDeviceBase, ProsilicaCamBase):
class ProsilicaCam(DebyeBaseCamera, ProsilicaCamBase):
"""
Prosilica camera class, for integration of beam_monitor 1/2 cameras.
Prefixes are: X01DA-OP-GIGE02: and X01DA-OP-GIGE01:
"""
def emit_to_bec(self, *args, obj=None, old_value=None, value=None, **kwargs):
width = self.image1.array_size.width.get()
height = self.image1.array_size.height.get()
data = np.reshape(value, (height, width))
self._run_subs(sub_type=self.SUB_DEVICE_MONITOR_2D, value=data)
def on_connected(self):
self.image1.array_data.subscribe(self.emit_to_bec, run=False)
preview = Cpt(
PreviewSignal,
name="preview",
ndim=2,
num_rotation_90=3,
doc="Preview signal for the camera.",
)

View File

@@ -3,15 +3,28 @@
### debye_bec
| Device | Documentation | Module |
| :----- | :------------- | :------ |
| Amplifiers | Class for the ES HV power supplies | [debye_bec.devices.hv_supplies](https://gitlab.psi.ch/bec/debye_bec/-/blob/main/debye_bec/devices/hv_supplies.py) |
| ES0Filter | Class for the ES0 filter station | [debye_bec.devices.es0filter](https://gitlab.psi.ch/bec/debye_bec/-/blob/main/debye_bec/devices/es0filter.py) |
| BaslerCam | Basler camera class at Debye. IOC prefix: X01DA-ES-XRAYEYE: | [debye_bec.devices.cameras.basler_cam](https://gitlab.psi.ch/bec/debye_bec/-/blob/main/debye_bec/devices/cameras/basler_cam.py) |
| BaslerCamBase | BaslerCam Base class. | [debye_bec.devices.cameras.basler_cam](https://gitlab.psi.ch/bec/debye_bec/-/blob/main/debye_bec/devices/cameras/basler_cam.py) |
| DebyeBaseCamera | Base class for Debye cameras. | [debye_bec.devices.cameras.debye_base_cam](https://gitlab.psi.ch/bec/debye_bec/-/blob/main/debye_bec/devices/cameras/debye_base_cam.py) |
| ES0Filter | Class for the ES0 filter station X01DA-ES0-FI: | [debye_bec.devices.es0filter](https://gitlab.psi.ch/bec/debye_bec/-/blob/main/debye_bec/devices/es0filter.py) |
| GasMixSetup | Class for the ES2 Pilatus Curtain | [debye_bec.devices.pilatus_curtain](https://gitlab.psi.ch/bec/debye_bec/-/blob/main/debye_bec/devices/pilatus_curtain.py) |
| Mo1Bragg | Class for the Mo1 Bragg positioner of the Debye beamline.<br> The prefix to connect to the soft IOC is X01DA-OP-MO1:BRAGG: which is connected to<br> the NI motor controller via web sockets.<br> | [debye_bec.devices.mo1_bragg](https://gitlab.psi.ch/bec/debye_bec/-/blob/main/debye_bec/devices/mo1_bragg.py) |
| Mo1BraggCalculator | Mo1 Bragg PVs to convert angle to energy or vice-versa. | [debye_bec.devices.mo1_bragg](https://gitlab.psi.ch/bec/debye_bec/-/blob/main/debye_bec/devices/mo1_bragg.py) |
| Mo1BraggCrystal | Mo1 Bragg PVs to set the crystal parameters | [debye_bec.devices.mo1_bragg](https://gitlab.psi.ch/bec/debye_bec/-/blob/main/debye_bec/devices/mo1_bragg.py) |
| Mo1BraggEncoder | Mo1 Bragg PVs to communicate with the encoder | [debye_bec.devices.mo1_bragg](https://gitlab.psi.ch/bec/debye_bec/-/blob/main/debye_bec/devices/mo1_bragg.py) |
| Mo1BraggScanControl | Mo1 Bragg PVs to control the scan after setting the parameters. | [debye_bec.devices.mo1_bragg](https://gitlab.psi.ch/bec/debye_bec/-/blob/main/debye_bec/devices/mo1_bragg.py) |
| Mo1BraggScanSettings | Mo1 Bragg PVs to set the scan setttings | [debye_bec.devices.mo1_bragg](https://gitlab.psi.ch/bec/debye_bec/-/blob/main/debye_bec/devices/mo1_bragg.py) |
| Mo1BraggStatus | Mo1 Bragg PVs for status monitoring | [debye_bec.devices.mo1_bragg](https://gitlab.psi.ch/bec/debye_bec/-/blob/main/debye_bec/devices/mo1_bragg.py) |
| Mo1TriggerSettings | Mo1 Trigger settings | [debye_bec.devices.mo1_bragg](https://gitlab.psi.ch/bec/debye_bec/-/blob/main/debye_bec/devices/mo1_bragg.py) |
| NIDAQ | NIDAQ ophyd wrapper around the NIDAQ backend currently running at x01da-cons-05<br> <br> Args:<br> prefix (str) : Prefix to the NIDAQ soft ioc, currently X01DA-PC-SCANSERVER:<br> name (str) : Name of the device<br> kind (Kind) : Ophyd Kind of the device<br> parent (Device) : Parent clas<br> device_manager : device manager as forwarded by BEC<br> | [debye_bec.devices.nidaq](https://gitlab.psi.ch/bec/debye_bec/-/blob/main/debye_bec/devices/nidaq.py) |
| GasMixSetupControl | GasMixSetup Control for Inonization Chamber 0 | [debye_bec.devices.ionization_chambers.ionization_chamber](https://gitlab.psi.ch/bec/debye_bec/-/blob/main/debye_bec/devices/ionization_chambers/ionization_chamber.py) |
| HighVoltageSuppliesControl | HighVoltage Supplies Control for Ionization Chamber 0 | [debye_bec.devices.ionization_chambers.ionization_chamber](https://gitlab.psi.ch/bec/debye_bec/-/blob/main/debye_bec/devices/ionization_chambers/ionization_chamber.py) |
| IonizationChamber0 | Ionization Chamber 0, prefix should be 'X01DA-'. | [debye_bec.devices.ionization_chambers.ionization_chamber](https://gitlab.psi.ch/bec/debye_bec/-/blob/main/debye_bec/devices/ionization_chambers/ionization_chamber.py) |
| IonizationChamber1 | Ionization Chamber 1, prefix should be 'X01DA-'. | [debye_bec.devices.ionization_chambers.ionization_chamber](https://gitlab.psi.ch/bec/debye_bec/-/blob/main/debye_bec/devices/ionization_chambers/ionization_chamber.py) |
| IonizationChamber2 | Ionization Chamber 2, prefix should be 'X01DA-'. | [debye_bec.devices.ionization_chambers.ionization_chamber](https://gitlab.psi.ch/bec/debye_bec/-/blob/main/debye_bec/devices/ionization_chambers/ionization_chamber.py) |
| Mo1Bragg | Mo1 Bragg motor for the Debye beamline.<br><br> The prefix to connect to the soft IOC is X01DA-OP-MO1:BRAGG:<br> | [debye_bec.devices.mo1_bragg.mo1_bragg](https://gitlab.psi.ch/bec/debye_bec/-/blob/main/debye_bec/devices/mo1_bragg/mo1_bragg.py) |
| Mo1BraggAngle | Positioner implementation with readback angle of the MO1 Bragg positioner. | [debye_bec.devices.mo1_bragg.mo1_bragg_angle](https://gitlab.psi.ch/bec/debye_bec/-/blob/main/debye_bec/devices/mo1_bragg/mo1_bragg_angle.py) |
| Mo1BraggCalculator | Mo1 Bragg PVs to convert angle to energy or vice-versa. | [debye_bec.devices.mo1_bragg.mo1_bragg_devices](https://gitlab.psi.ch/bec/debye_bec/-/blob/main/debye_bec/devices/mo1_bragg/mo1_bragg_devices.py) |
| Mo1BraggCrystal | Mo1 Bragg PVs to set the crystal parameters | [debye_bec.devices.mo1_bragg.mo1_bragg_devices](https://gitlab.psi.ch/bec/debye_bec/-/blob/main/debye_bec/devices/mo1_bragg/mo1_bragg_devices.py) |
| Mo1BraggEncoder | Mo1 Bragg PVs to communicate with the encoder | [debye_bec.devices.mo1_bragg.mo1_bragg_devices](https://gitlab.psi.ch/bec/debye_bec/-/blob/main/debye_bec/devices/mo1_bragg/mo1_bragg_devices.py) |
| Mo1BraggPositioner | <br> Positioner implementation with readback energy of the MO1 Bragg positioner.<br><br> The prefix to connect to the soft IOC is X01DA-OP-MO1:BRAGG:<br> This soft IOC connects to the NI motor and its control loop.<br> | [debye_bec.devices.mo1_bragg.mo1_bragg_devices](https://gitlab.psi.ch/bec/debye_bec/-/blob/main/debye_bec/devices/mo1_bragg/mo1_bragg_devices.py) |
| Mo1BraggScanControl | Mo1 Bragg PVs to control the scan after setting the parameters. | [debye_bec.devices.mo1_bragg.mo1_bragg_devices](https://gitlab.psi.ch/bec/debye_bec/-/blob/main/debye_bec/devices/mo1_bragg/mo1_bragg_devices.py) |
| Mo1BraggScanSettings | Mo1 Bragg PVs to set the scan setttings | [debye_bec.devices.mo1_bragg.mo1_bragg_devices](https://gitlab.psi.ch/bec/debye_bec/-/blob/main/debye_bec/devices/mo1_bragg/mo1_bragg_devices.py) |
| Mo1BraggStatus | Mo1 Bragg PVs for status monitoring | [debye_bec.devices.mo1_bragg.mo1_bragg_devices](https://gitlab.psi.ch/bec/debye_bec/-/blob/main/debye_bec/devices/mo1_bragg/mo1_bragg_devices.py) |
| Mo1TriggerSettings | Mo1 Trigger settings | [debye_bec.devices.mo1_bragg.mo1_bragg_devices](https://gitlab.psi.ch/bec/debye_bec/-/blob/main/debye_bec/devices/mo1_bragg/mo1_bragg_devices.py) |
| Nidaq | NIDAQ ophyd wrapper around the NIDAQ backend currently running at x01da-cons-05<br><br> Args:<br> prefix (str) : Prefix to the NIDAQ soft ioc, currently X01DA-PC-SCANSERVER:<br> name (str) : Name of the device<br> scan_info (ScanInfo) : ScanInfo object passed by BEC's devicemanager.<br> | [debye_bec.devices.nidaq.nidaq](https://gitlab.psi.ch/bec/debye_bec/-/blob/main/debye_bec/devices/nidaq/nidaq.py) |
| NidaqControl | Nidaq control class with all PVs | [debye_bec.devices.nidaq.nidaq](https://gitlab.psi.ch/bec/debye_bec/-/blob/main/debye_bec/devices/nidaq/nidaq.py) |
| ProsilicaCam | <br> Prosilica camera class, for integration of beam_monitor 1/2 cameras.<br> Prefixes are: X01DA-OP-GIGE02: and X01DA-OP-GIGE01:<br> | [debye_bec.devices.cameras.prosilica_cam](https://gitlab.psi.ch/bec/debye_bec/-/blob/main/debye_bec/devices/cameras/prosilica_cam.py) |
| ProsilicaCamBase | Base class for Prosilica cameras. | [debye_bec.devices.cameras.prosilica_cam](https://gitlab.psi.ch/bec/debye_bec/-/blob/main/debye_bec/devices/cameras/prosilica_cam.py) |
| Reffoilchanger | Class for the ES2 Reference Foil Changer | [debye_bec.devices.reffoilchanger](https://gitlab.psi.ch/bec/debye_bec/-/blob/main/debye_bec/devices/reffoilchanger.py) |

View File

@@ -1,11 +1,15 @@
from typing import Literal
"""Ionization chamber device class"""
from __future__ import annotations
from typing import TYPE_CHECKING, Literal
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
@@ -15,6 +19,9 @@ from debye_bec.devices.ionization_chambers.ionization_chamber_enums import (
AmplifierGain,
)
if TYPE_CHECKING: # pragma: no cover
from bec_lib.devicemanager import ScanInfo
class EpicsSignalSplit(EpicsSignal):
"""Wrapper around EpicsSignal with different read and write pv"""
@@ -26,39 +33,42 @@ 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")
status_msg = Cpt(EpicsSignalRO, suffix="StatusMsg0", kind="config", doc="Status")
class HighVoltageSuppliesControl(Device):
"""HighVoltage Supplies Control for Ionization Chamber 0"""
hv_v = Cpt(EpicsSignalSplit, suffix="HV1-V", kind="config", doc="HV voltage")
hv_i = Cpt(EpicsSignalSplit, suffix="HV1-I", kind="config", doc="HV current")
grid_v = Cpt(EpicsSignalSplit, suffix="HV2-V", kind="config", doc="Grid voltage")
grid_i = Cpt(EpicsSignalSplit, suffix="HV2-I", kind="config", doc="Grid current")
hv_v = Cpt(EpicsSignalSplit, suffix="HV2-V", kind="config", doc="HV voltage")
hv_i = Cpt(EpicsSignalSplit, suffix="HV2-I", kind="config", doc="HV current")
grid_v = Cpt(EpicsSignalSplit, suffix="HV1-V", kind="config", doc="Grid voltage")
grid_i = Cpt(EpicsSignalSplit, suffix="HV1-I", kind="config", doc="Grid current")
class IonizationChamber0(PSIDeviceBase):
"""Ionization Chamber 0, prefix should be 'X01DA-'."""
USER_ACCESS = ["set_gain", "set_filter", "set_hv", "set_grid", "fill"]
num = 1
amp_signals = {
"cOnOff": (
@@ -76,9 +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_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": (
@@ -86,16 +112,16 @@ class IonizationChamber0(PSIDeviceBase):
"ES1-IC0:HV-Ext-Ena",
{"kind": "config", "doc": "External enable signal of HV"},
),
"ena": (EpicsSignalRO, "ES1-IC0:HV-Ena", {"kind": "config", "doc": "Enable signal of HV"}),
"ena": (EpicsSignal, "ES1-IC0:HV-Ena", {"kind": "config", "doc": "Enable signal of HV"}),
}
hv_en = Dcpt(hv_en_signals)
def __init__(self, name: str, scan_info=None, **kwargs):
def __init__(self, name: str, prefix: str = "", scan_info: ScanInfo | None = None, **kwargs):
self.timeout_for_pvwait = 2.5
super().__init__(name=name, scan_info=scan_info, **kwargs)
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:
@@ -103,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":
@@ -129,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":
@@ -180,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 self.hv.grid_v.get() > hv:
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)
@@ -205,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 grid > self.hv.hv_v.get():
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)
@@ -237,7 +237,7 @@ class IonizationChamber0(PSIDeviceBase):
pressure: float,
*,
wait: bool = False,
) -> DeviceStatus:
) -> DeviceStatus | None:
"""Fill an ionization chamber with the specified gas mixture.
Args:
@@ -249,13 +249,13 @@ class IonizationChamber0(PSIDeviceBase):
wait (bool): If you like to wait for the filling to finish.
"""
if 100 < conc1 < 0:
if not 0 <= conc1 <= 100:
raise ValueError(f"Concentration 1 {conc1} out of range [0 .. 100 %]")
if 100 < conc2 < 0:
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 3 < pressure < 0:
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)
@@ -263,31 +263,17 @@ 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_msg.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(PSIDeviceBase):
"""Ionization Chamber 0, prefix should be 'X01DA-'."""
class IonizationChamber1(IonizationChamber0):
"""Ionization Chamber 1, prefix should be 'X01DA-'."""
num = 2
amp_signals = {
@@ -306,9 +292,25 @@ class IonizationChamber1(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_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": (
@@ -316,13 +318,13 @@ class IonizationChamber1(PSIDeviceBase):
"ES2-IC12:HV-Ext-Ena",
{"kind": "config", "doc": "External enable signal of HV"},
),
"ena": (EpicsSignalRO, "ES2-IC12:HV-Ena", {"kind": "config", "doc": "Enable signal of HV"}),
"ena": (EpicsSignal, "ES2-IC12:HV-Ena", {"kind": "config", "doc": "Enable signal of HV"}),
}
hv_en = Dcpt(hv_en_signals)
class IonizationChamber2(PSIDeviceBase):
"""Ionization Chamber 0, prefix should be 'X01DA-'."""
class IonizationChamber2(IonizationChamber0):
"""Ionization Chamber 2, prefix should be 'X01DA-'."""
num = 3
amp_signals = {
@@ -341,9 +343,25 @@ class IonizationChamber2(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_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": (
@@ -351,6 +369,66 @@ class IonizationChamber2(PSIDeviceBase):
"ES2-IC12:HV-Ext-Ena",
{"kind": "config", "doc": "External enable signal of HV"},
),
"ena": (EpicsSignalRO, "ES2-IC12:HV-Ena", {"kind": "config", "doc": "Enable signal of HV"}),
"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

View File

@@ -9,14 +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, 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
@@ -54,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")
@@ -80,7 +83,9 @@ class Mo1Bragg(PSIDeviceBase, Mo1BraggPositioner):
The prefix to connect to the soft IOC is X01DA-OP-MO1:BRAGG:
"""
USER_ACCESS = ["set_advanced_xas_settings"]
progress_signal = Cpt(ProgressSignal, name="progress_signal")
USER_ACCESS = ["set_advanced_xas_settings", "set_xtal"]
def __init__(self, name: str, prefix: str = "", scan_info: ScanInfo | None = None, **kwargs): # type: ignore
"""
@@ -92,7 +97,7 @@ class Mo1Bragg(PSIDeviceBase, Mo1BraggPositioner):
"""
super().__init__(name=name, scan_info=scan_info, prefix=prefix, **kwargs)
self.scan_parameter = ScanParameter()
self.timeout_for_pvwait = 2.5
self.timeout_for_pvwait = 7.5
########################################
# Beamline Specific Implementations #
@@ -119,7 +124,11 @@ class Mo1Bragg(PSIDeviceBase, Mo1BraggPositioner):
Information about the upcoming scan can be accessed from the scan_info (self.scan_info.msg) object.
"""
self._check_scan_msg(ScanControlLoadMessage.PENDING)
if self.scan_control.scan_msg.get() != ScanControlLoadMessage.PENDING:
status = CompareStatus(self.scan_control.scan_msg, ScanControlLoadMessage.PENDING)
self.cancel_on_stop(status)
self.scan_control.scan_val_reset.put(1)
status.wait(timeout=self.timeout_for_pvwait)
scan_name = self.scan_info.msg.scan_name
self._update_scan_parameter()
@@ -132,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
@@ -147,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
@@ -168,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
@@ -185,54 +200,59 @@ class Mo1Bragg(PSIDeviceBase, Mo1BraggPositioner):
e_kink=self.scan_parameter.e_kink,
)
self.set_trig_settings(
enable_low=self.scan_parameter.xrd_enable_low, # enable_low=self.scan_parameter.trig_enable_low,
enable_high=self.scan_parameter.xrd_enable_high, # enable_high=self.scan_parameter.trig_enable_high,
exp_time_low=self.scan_parameter.exp_time_low,
exp_time_high=self.scan_parameter.exp_time_high,
enable_low=self.scan_parameter.break_enable_low,
enable_high=self.scan_parameter.break_enable_high,
break_time_low=self.scan_parameter.break_time_low,
break_time_high=self.scan_parameter.break_time_high,
cycle_low=self.scan_parameter.cycle_low,
cycle_high=self.scan_parameter.cycle_high,
exp_time=self.scan_parameter.exp_time,
n_of_trigger=self.scan_parameter.n_of_trigger,
)
self.set_scan_control_settings(
mode=ScanControlMode.ADVANCED, scan_duration=self.scan_parameter.scan_duration
)
else:
return
# Setting scan duration seems to lag behind slightly in the backend, include small sleep
logger.info(f"Sleeping for one second")
time.sleep(1)
logger.info(f"Device {self.name}, done sleeping")
# Load the scan parameters to the controller
status = CompareStatus(self.scan_control.scan_msg, ScanControlLoadMessage.SUCCESS)
self.cancel_on_stop(status)
self.scan_control.scan_load.put(1)
# Wait for params to be checked from controller
status = self.task_handler.submit_task(
task=self.wait_for_signal,
task_args=(self.scan_control.scan_msg, ScanControlLoadMessage.SUCCESS),
)
return status
status.wait(self.timeout_for_pvwait)
return None
def on_unstage(self) -> DeviceStatus | StatusBase | None:
"""Called while unstaging the device."""
def unstage_procedure():
if self.stopped is True:
logger.warning(f"Resetting stopped in unstage for device {self.name}.")
self._stopped = False
if self.scan_control.scan_msg.get() in [
ScanControlLoadMessage.STARTED,
ScanControlLoadMessage.SUCCESS,
]:
status = CompareStatus(self.scan_control.scan_msg, ScanControlLoadMessage.PENDING)
self.cancel_on_stop(status)
try:
self.wait_for_signal(
self.scan_control.scan_msg,
ScanControlLoadMessage.PENDING,
timeout=self.timeout_for_pvwait / 2,
)
return
except TimeoutError:
status.wait(2)
return None
except WaitTimeoutError:
logger.warning(
f"Timeout after {self.timeout_for_pvwait} while waiting for scan validation"
f"Timeout in on_unstage of {self.name} after {self.timeout_for_pvwait}s, current scan_control_message : {self.scan_control.scan_msg.get()}"
)
time.sleep(0.25)
start_time = time.time()
while time.time() - start_time < self.timeout_for_pvwait / 2:
if not self.scan_control.scan_msg.get() == ScanControlLoadMessage.PENDING:
break
time.sleep(0.1)
raise TimeoutError(
f"Device {self.name} run into timeout after {self.timeout_for_pvwait} while waiting for scan validation"
)
status = self.task_handler.submit_task(unstage_procedure)
status.wait()
status = CompareStatus(self.scan_control.scan_msg, ScanControlLoadMessage.PENDING)
self.cancel_on_stop(status)
self.scan_control.scan_val_reset.put(1)
status.wait(timeout=self.timeout_for_pvwait)
else:
status = CompareStatus(self.scan_control.scan_msg, ScanControlLoadMessage.PENDING)
self.cancel_on_stop(status)
self.scan_control.scan_val_reset.put(1)
status.wait(timeout=self.timeout_for_pvwait)
return None
def on_pre_scan(self) -> DeviceStatus | StatusBase | None:
@@ -243,20 +263,8 @@ class Mo1Bragg(PSIDeviceBase, Mo1BraggPositioner):
def on_complete(self) -> DeviceStatus | StatusBase | None:
"""Called to inquire if a device has completed a scans."""
def wait_for_complete():
"""Wait for the scan to complete. No timeout is set."""
start_time = time.time()
while True:
if self.stopped is True:
raise DeviceStopError(
f"Device {self.name} was stopped while waiting for scan to complete"
)
if self.scan_control.scan_done.get() == 1:
return
time.sleep(0.1)
status = self.task_handler.submit_task(wait_for_complete)
status = CompareStatus(self.scan_control.scan_done, 1)
self.cancel_on_stop(status)
return status
def on_kickoff(self) -> DeviceStatus | StatusBase | None:
@@ -268,11 +276,14 @@ class Mo1Bragg(PSIDeviceBase, Mo1BraggPositioner):
if scan_duration < 0.1
else self.scan_control.scan_start_timer.put
)
start_func(1)
status = self.task_handler.submit_task(
task=self.wait_for_signal,
task_args=(self.scan_control.scan_status, ScanControlScanStatus.RUNNING),
status = TransitionStatus(
self.scan_control.scan_status,
transitions=[ScanControlScanStatus.READY, ScanControlScanStatus.RUNNING],
strict=True,
failure_states=[ScanControlScanStatus.PARAMETER_WRONG],
)
self.cancel_on_stop(status)
start_func(1)
return status
def on_stop(self) -> None:
@@ -280,9 +291,6 @@ class Mo1Bragg(PSIDeviceBase, Mo1BraggPositioner):
self.stopped = True # Needs to be set to stop motion
######### Utility Methods #########
# FIXME this should become the ProgressSignal
# pylint: disable=unused-argument
def _progress_update(self, value, **kwargs) -> None:
"""Callback method to update the scan progress, runs a callback
to SUB_PROGRESS subscribers, i.e. BEC.
@@ -291,12 +299,7 @@ class Mo1Bragg(PSIDeviceBase, Mo1BraggPositioner):
value (int) : current progress value
"""
max_value = 100
self._run_subs(
sub_type=self.SUB_PROGRESS,
value=value,
max_value=max_value,
done=bool(max_value == value),
)
self.progress_signal.put(value=value, max_value=max_value, done=bool(max_value == value))
def set_xas_settings(self, low: float, high: float, scan_time: float) -> None:
"""Set XAS parameters for upcoming scan.
@@ -306,30 +309,20 @@ class Mo1Bragg(PSIDeviceBase, Mo1BraggPositioner):
high (float): High energy/angle value of the scan
scan_time (float): Time for a half oscillation
"""
move_type = self.move_type.get()
if move_type == MoveType.ENERGY:
self.scan_settings.s_scan_energy_lo.put(low)
self.scan_settings.s_scan_energy_hi.put(high)
else:
self.scan_settings.s_scan_angle_lo.put(low)
self.scan_settings.s_scan_angle_hi.put(high)
self.scan_settings.s_scan_scantime.put(scan_time)
def wait_for_signal(self, signal: Cpt, value: Any, timeout: float | None = None) -> None:
"""Wait for a signal to reach a certain value."""
if timeout is None:
timeout = self.timeout_for_pvwait
start_time = time.time()
while time.time() - start_time < timeout:
if signal.get() == value:
return None
if self.stopped is True: # Should this check be optional or configurable?!
raise DeviceStopError(f"Device {self.name} was stopped while waiting for signal")
time.sleep(0.1)
# If we end up here, the status did not resolve
raise TimeoutError(
f"Device {self.name} run into timeout after {timeout}s while waiting for scan to start"
)
status_list = []
status_list.append(self.scan_settings.s_scan_energy_lo.set(low))
self.cancel_on_stop(status_list[-1])
status_list.append(self.scan_settings.s_scan_energy_hi.set(high))
self.cancel_on_stop(status_list[-1])
status_list.append(self.scan_settings.s_scan_scantime.set(scan_time))
self.cancel_on_stop(status_list[-1])
for s in status_list:
s.wait(timeout=self.timeout_for_pvwait)
@typechecked
def convert_angle_energy(
@@ -346,15 +339,19 @@ class Mo1Bragg(PSIDeviceBase, Mo1BraggPositioner):
"""
self.calculator.calc_reset.put(0)
self.calculator.calc_reset.put(1)
self.wait_for_signal(self.calculator.calc_done, 0)
status = CompareStatus(self.calculator.calc_done, 0)
self.cancel_on_stop(status)
status.wait(self.timeout_for_pvwait)
if mode == "AngleToEnergy":
self.calculator.calc_angle.put(inp)
elif mode == "EnergyToAngle":
self.calculator.calc_energy.put(inp)
self.wait_for_signal(self.calculator.calc_done, 1)
time.sleep(0.25) # Needed due to update frequency of softIOC
status = CompareStatus(self.calculator.calc_done, 1)
self.cancel_on_stop(status)
status.wait(self.timeout_for_pvwait)
time.sleep(0.25) # TODO needed still? Needed due to update frequency of softIOC
if mode == "AngleToEnergy":
return self.calculator.calc_energy.get()
elif mode == "EnergyToAngle":
@@ -372,18 +369,10 @@ class Mo1Bragg(PSIDeviceBase, Mo1BraggPositioner):
p_kink (float): Position of kink in %
e_kink (float): Energy of kink in eV
"""
# TODO Add fallback solution for automatic testing, otherwise test will fail
# because no monochromator will calculate the angle
# Unsure how to implement this
move_type = self.move_type.get()
if move_type == MoveType.ENERGY:
e_kink_deg = self.convert_angle_energy(mode="EnergyToAngle", inp=e_kink)
# Angle and Energy are inverse proportional!
high_deg = self.convert_angle_energy(mode="EnergyToAngle", inp=low)
low_deg = self.convert_angle_energy(mode="EnergyToAngle", inp=high)
else:
raise Mo1BraggError("MoveType Angle not implemented for advanced scans, use Energy")
e_kink_deg = self.convert_angle_energy(mode="EnergyToAngle", inp=e_kink)
# Angle and Energy are inverse proportional!
high_deg = self.convert_angle_energy(mode="EnergyToAngle", inp=low)
low_deg = self.convert_angle_energy(mode="EnergyToAngle", inp=high)
pos, vel, dt = compute_spline(
low_deg=low_deg,
@@ -393,37 +382,72 @@ class Mo1Bragg(PSIDeviceBase, Mo1BraggPositioner):
scan_time=scan_time,
)
self.scan_settings.a_scan_pos.set(pos)
self.scan_settings.a_scan_vel.set(vel)
self.scan_settings.a_scan_time.set(dt)
status_list = []
status_list.append(self.scan_settings.a_scan_pos.set(pos))
self.cancel_on_stop(status_list[-1])
status_list.append(self.scan_settings.a_scan_vel.set(vel))
self.cancel_on_stop(status_list[-1])
status_list.append(self.scan_settings.a_scan_time.set(dt))
self.cancel_on_stop(status_list[-1])
for s in status_list:
s.wait(timeout=self.timeout_for_pvwait)
def set_trig_settings(
self,
enable_low: bool,
enable_high: bool,
exp_time_low: int,
exp_time_high: int,
break_time_low: float,
break_time_high: float,
cycle_low: int,
cycle_high: int,
exp_time: float,
n_of_trigger: int,
) -> None:
"""Set TRIG settings for the upcoming scan.
Args:
enable_low (bool): Enable TRIG for low energy/angle
enable_high (bool): Enable TRIG for high energy/angle
num_trigger_low (int): Number of triggers for low energy/angle
num_trigger_high (int): Number of triggers for high energy/angle
exp_time_low (int): Exposure time for low energy/angle
exp_time_high (int): Exposure time for high energy/angle
break_time_low (float): Exposure time for low energy/angle
break_time_high (float): Exposure time for high energy/angle
cycle_low (int): Cycle for low energy/angle
cycle_high (int): Cycle for high energy/angle
exp_time (float): Length of 1 trigger period in seconds
n_of_trigger (int): Amount of triggers to be fired during brake
"""
self.scan_settings.trig_ena_hi_enum.put(int(enable_high))
self.scan_settings.trig_ena_lo_enum.put(int(enable_low))
self.scan_settings.trig_time_hi.put(exp_time_high)
self.scan_settings.trig_time_lo.put(exp_time_low)
self.scan_settings.trig_every_n_hi.put(cycle_high)
self.scan_settings.trig_every_n_lo.put(cycle_low)
status_list = []
status_list.append(self.scan_settings.trig_ena_hi_enum.set(int(enable_high)))
self.cancel_on_stop(status_list[-1])
status_list.append(self.scan_settings.trig_ena_lo_enum.set(int(enable_low)))
self.cancel_on_stop(status_list[-1])
status_list.append(self.scan_settings.trig_time_hi.set(break_time_high))
self.cancel_on_stop(status_list[-1])
status_list.append(self.scan_settings.trig_time_lo.set(break_time_low))
self.cancel_on_stop(status_list[-1])
status_list.append(self.scan_settings.trig_every_n_hi.set(cycle_high))
self.cancel_on_stop(status_list[-1])
status_list.append(self.scan_settings.trig_every_n_lo.set(cycle_low))
self.cancel_on_stop(status_list[-1])
status_list.append(self.trigger_settings.xrd_trig_period.set(exp_time))
self.cancel_on_stop(status_list[-1])
status_list.append(self.trigger_settings.xrd_n_of_trig.set(n_of_trigger))
self.cancel_on_stop(status_list[-1])
for s in status_list:
s.wait(timeout=self.timeout_for_pvwait)
def set_scan_control_settings(self, mode: ScanControlMode, scan_duration: float) -> None:
"""Set the scan control settings for the upcoming scan.
@@ -433,8 +457,17 @@ class Mo1Bragg(PSIDeviceBase, Mo1BraggPositioner):
scan_duration (float): Duration of the scan
"""
val = ScanControlMode(mode).value
self.scan_control.scan_mode_enum.put(val)
self.scan_control.scan_duration.put(scan_duration)
status_list = []
status_list.append(self.scan_control.scan_mode_enum.set(val))
self.cancel_on_stop(status_list[-1])
status_list.append(self.scan_control.scan_duration.set(scan_duration))
self.cancel_on_stop(status_list[-1])
for s in status_list:
s.wait(timeout=self.timeout_for_pvwait)
def _update_scan_parameter(self):
"""Get the scan_info parameters for the scan."""
@@ -444,30 +477,3 @@ class Mo1Bragg(PSIDeviceBase, Mo1BraggPositioner):
for key, value in self.scan_info.msg.request_inputs["kwargs"].items():
if hasattr(self.scan_parameter, key):
setattr(self.scan_parameter, key, value)
def _check_scan_msg(self, target_state: ScanControlLoadMessage) -> None:
"""Check if the scan message is gettting available
Args:
target_state (ScanControlLoadMessage): Target state to check for
Raises:
TimeoutError: If the scan message is not available after the timeout
"""
state = self.scan_control.scan_msg.get()
if state != target_state:
logger.warning(
f"Resetting scan validation in stage for state: {ScanControlLoadMessage(state)}, "
f"retry .get() on scan_control: {ScanControlLoadMessage(self.scan_control.scan_msg.get())} and sleeping 1s"
)
self.scan_control.scan_val_reset.put(1)
# Sleep to ensure the reset is done
time.sleep(1)
try:
self.wait_for_signal(self.scan_control.scan_msg, target_state)
except TimeoutError as exc:
raise TimeoutError(
f"Timeout after {self.timeout_for_pvwait} while waiting for scan status,"
f" current state: {ScanControlScanStatus(self.scan_control.scan_msg.get())}"
) from exc

View File

@@ -0,0 +1,20 @@
"""Positioner implementation with readback angle of the MO1 Bragg positioner."""
from ophyd import Component as Cpt
from ophyd import EpicsSignalRO, EpicsSignalWithRBV
from debye_bec.devices.mo1_bragg.mo1_bragg_devices import Mo1BraggPositioner
class Mo1BraggAngle(Mo1BraggPositioner):
"""Positioner implementation with readback angle of the MO1 Bragg positioner."""
readback = Cpt(EpicsSignalRO, suffix="feedback_pos_angle_RBV", kind="normal", auto_monitor=True)
setpoint = Cpt(EpicsSignalWithRBV, suffix="set_abs_pos_angle", kind="normal", auto_monitor=True)
low_lim = Cpt(EpicsSignalRO, suffix="lo_lim_pos_angle_RBV", kind="config", auto_monitor=True)
high_lim = Cpt(EpicsSignalRO, suffix="hi_lim_pos_angle_RBV", kind="config", auto_monitor=True)
@property
def egu(self) -> str:
"""Return the engineering unit of the positioner."""
return "deg"

View File

@@ -82,10 +82,20 @@ class Mo1BraggCrystal(Device):
d_spacing_si111 = Cpt(EpicsSignalWithRBV, suffix="d_spacing_si111", kind="config")
d_spacing_si311 = Cpt(EpicsSignalWithRBV, suffix="d_spacing_si311", kind="config")
set_offset = Cpt(EpicsSignal, suffix="set_offset", kind="config", put_complete=True)
current_d_spacing = Cpt(
EpicsSignalRO, suffix="current_d_spacing_RBV", kind="normal", auto_monitor=True
)
current_offset = Cpt(
EpicsSignalRO, suffix="current_offset_RBV", kind="normal", auto_monitor=True
)
current_xtal = Cpt(
EpicsSignalRO, suffix="current_xtal_ENUM_RBV", kind="normal", auto_monitor=True
)
current_xtal_string = Cpt(
EpicsSignalRO, suffix="current_xtal_ENUM_RBV", kind="normal", auto_monitor=True, string=True
)
class Mo1BraggScanSettings(Device):
"""Mo1 Bragg PVs to set the scan setttings"""
@@ -129,21 +139,29 @@ class Mo1TriggerSettings(Device):
xrd_trig_src_enum = Cpt(EpicsSignalWithRBV, suffix="xrd_trig_src_ENUM", kind="config")
xrd_trig_mode_enum = Cpt(EpicsSignalWithRBV, suffix="xrd_trig_mode_ENUM", kind="config")
xrd_trig_len = Cpt(EpicsSignalWithRBV, suffix="xrd_trig_len", kind="config")
xrd_trig_period = Cpt(EpicsSignalWithRBV, suffix="xrd_trig_period", kind="config")
xrd_n_of_trig = Cpt(EpicsSignalWithRBV, suffix="xrd_n_of_trig", kind="config")
xrd_trig_req = Cpt(EpicsSignal, suffix="xrd_trig_req", kind="config")
falcon_trig_src_enum = Cpt(EpicsSignalWithRBV, suffix="falcon_trig_src_ENUM", kind="config")
falcon_trig_mode_enum = Cpt(EpicsSignalWithRBV, suffix="falcon_trig_mode_ENUM", kind="config")
falcon_trig_len = Cpt(EpicsSignalWithRBV, suffix="falcon_trig_len", kind="config")
falcon_trig_period = Cpt(EpicsSignalWithRBV, suffix="falcon_trig_period", kind="config")
falcon_n_of_trig = Cpt(EpicsSignalWithRBV, suffix="falcon_n_of_trig", kind="config")
falcon_trig_req = Cpt(EpicsSignal, suffix="falcon_trig_req", kind="config")
univ1_trig_src_enum = Cpt(EpicsSignalWithRBV, suffix="univ1_trig_src_ENUM", kind="config")
univ1_trig_mode_enum = Cpt(EpicsSignalWithRBV, suffix="univ1_trig_mode_ENUM", kind="config")
univ1_trig_len = Cpt(EpicsSignalWithRBV, suffix="univ1_trig_len", kind="config")
univ1_trig_period = Cpt(EpicsSignalWithRBV, suffix="univ1_trig_period", kind="config")
univ1_n_of_trig = Cpt(EpicsSignalWithRBV, suffix="univ1_n_of_trig", kind="config")
univ1_trig_req = Cpt(EpicsSignal, suffix="univ1_trig_req", kind="config")
univ2_trig_src_enum = Cpt(EpicsSignalWithRBV, suffix="univ2_trig_src_ENUM", kind="config")
univ2_trig_mode_enum = Cpt(EpicsSignalWithRBV, suffix="univ2_trig_mode_ENUM", kind="config")
univ2_trig_len = Cpt(EpicsSignalWithRBV, suffix="univ2_trig_len", kind="config")
univ2_trig_period = Cpt(EpicsSignalWithRBV, suffix="univ2_trig_period", kind="config")
univ2_n_of_trig = Cpt(EpicsSignalWithRBV, suffix="univ2_n_of_trig", kind="config")
univ2_trig_req = Cpt(EpicsSignal, suffix="univ2_trig_req", kind="config")
@@ -189,13 +207,13 @@ class Mo1BraggScanControl(Device):
class Mo1BraggPositioner(Device, PositionerBase):
"""
Positioner implementation of the MO1 Bragg positioner.
Positioner implementation with readback energy of the MO1 Bragg positioner.
The prefix to connect to the soft IOC is X01DA-OP-MO1:BRAGG:
This soft IOC connects to the NI motor and its control loop.
"""
USER_ACCESS = ["set_advanced_xas_settings"]
USER_ACCESS = ["set_xtal"]
####### Sub-components ########
# Namespace is cleaner and easier to maintain
@@ -207,10 +225,6 @@ class Mo1BraggPositioner(Device, PositionerBase):
scan_control = Cpt(Mo1BraggScanControl, "")
status = Cpt(Mo1BraggStatus, "")
############# switch between energy and angle #############
# TODO should be removed/replaced once decision about pseudo motor is made
move_type = Cpt(MoveTypeSignal, value=MoveType.ENERGY, kind="config")
############# Energy PVs #############
readback = Cpt(
@@ -226,22 +240,6 @@ class Mo1BraggPositioner(Device, PositionerBase):
high_lim = Cpt(EpicsSignalRO, suffix="hi_lim_pos_energy_RBV", kind="config", auto_monitor=True)
velocity = Cpt(EpicsSignalWithRBV, suffix="move_velocity", kind="config", auto_monitor=True)
########### Angle PVs #############
# TODO Pseudo motor for angle?
feedback_pos_angle = Cpt(
EpicsSignalRO, suffix="feedback_pos_angle_RBV", kind="normal", auto_monitor=True
)
setpoint_abs_angle = Cpt(
EpicsSignalWithRBV, suffix="set_abs_pos_angle", kind="normal", auto_monitor=True
)
low_limit_angle = Cpt(
EpicsSignalRO, suffix="lo_lim_pos_angle_RBV", kind="config", auto_monitor=True
)
high_limit_angle = Cpt(
EpicsSignalRO, suffix="hi_lim_pos_angle_RBV", kind="config", auto_monitor=True
)
########## Move Command PVs ##########
move_abs = Cpt(EpicsSignal, suffix="move_abs", kind="config", put_complete=True)
@@ -271,9 +269,7 @@ class Mo1BraggPositioner(Device, PositionerBase):
success (bool) : Flag to indicate if the motion was successful
"""
self.move_stop.put(1)
if self._move_thread is not None:
self._move_thread.join()
self._move_thread = None
self._stopped = True
super().stop(success=success)
def stop_scan(self) -> None:
@@ -290,9 +286,7 @@ class Mo1BraggPositioner(Device, PositionerBase):
@property
def limits(self) -> tuple:
"""Return limits of the Bragg positioner"""
if self.move_type.get() == MoveType.ENERGY:
return (self.low_lim.get(), self.high_lim.get())
return (self.low_limit_angle.get(), self.high_limit_angle.get())
return (self.low_lim.get(), self.high_lim.get())
@property
def low_limit(self) -> float:
@@ -307,16 +301,12 @@ class Mo1BraggPositioner(Device, PositionerBase):
@property
def egu(self) -> str:
"""Return the engineering units of the positioner"""
if self.move_type.get() == MoveType.ENERGY:
return "eV"
return "deg"
return "eV"
@property
def position(self) -> float:
"""Return the current position of Mo1Bragg, considering the move type"""
move_type = self.move_type.get()
move_cpt = self.readback if move_type == MoveType.ENERGY else self.feedback_pos_angle
return move_cpt.get()
return self.readback.get()
# pylint: disable=arguments-differ
def check_value(self, value: float) -> None:
@@ -332,7 +322,7 @@ class Mo1BraggPositioner(Device, PositionerBase):
raise LimitError(f"position={value} not within limits {self.limits}")
def _move_and_finish(
self, target_pos: float, move_cpt: Cpt, status: DeviceStatus, update_frequency: float = 0.1
self, target_pos: float, status: DeviceStatus, update_frequency: float = 0.1
) -> None:
"""
Method to be called in the move thread to move the Bragg positioner
@@ -349,12 +339,14 @@ class Mo1BraggPositioner(Device, PositionerBase):
update_frequency (float): Optional, frequency to update the current position of
the motion, defaults to 0.1s
"""
motor_name = None
try:
# Set the target position on IOC
move_cpt.put(target_pos)
self.setpoint.put(target_pos)
self.move_abs.put(1)
# Currently sleep is needed due to delay in updates on PVs, maybe time can be reduced
time.sleep(0.5)
motor_name = self.name
while self.motor_is_moving.get() == 0:
if self.stopped:
raise Mo1BraggStoppedError(f"Device {self.name} was stopped")
@@ -364,10 +356,12 @@ class Mo1BraggPositioner(Device, PositionerBase):
# pylint: disable=broad-except
except Exception as exc:
content = traceback.format_exc()
logger.error(f"Error in move thread of device {self.name}: {content}")
logger.error(
f"Error in move thread of device {motor_name if motor_name else ''}: {content}"
)
status.set_exception(exc=exc)
def move(self, value: float, move_type: str | MoveType = None, **kwargs) -> DeviceStatus:
def move(self, value: float, **kwargs) -> DeviceStatus:
"""
Move the Bragg positioner to the specified value, allows to
switch between move types angle and energy.
@@ -381,16 +375,12 @@ class Mo1BraggPositioner(Device, PositionerBase):
DeviceStatus : status object to track the motion
"""
self._stopped = False
if move_type is not None:
self.move_type.put(move_type)
move_type = self.move_type.get()
move_cpt = self.setpoint if move_type == MoveType.ENERGY else self.setpoint_abs_angle
self.check_value(value)
status = DeviceStatus(device=self)
self._move_thread = threading.Thread(
target=self._move_and_finish, args=(value, move_cpt, status, 0.1)
target=self._move_and_finish, args=(value, status, 0.1)
)
self._move_thread.start()
return status

View File

@@ -37,12 +37,12 @@ def compute_spline(
low_deg = low_deg - POSITION_COMPONSATION
high_deg = high_deg + POSITION_COMPONSATION
if p_kink < 0 or p_kink > 100:
if not (0 <= p_kink <= 100):
raise Mo1UtilsSplineError(
"Kink position not within range of [0..100%]" + f"for p_kink: {p_kink}"
)
if e_kink_deg < low_deg or e_kink_deg > high_deg:
if not (low_deg < e_kink_deg < high_deg):
raise Mo1UtilsSplineError(
"Kink energy not within selected energy range of scan,"
+ f"for e_kink_deg {e_kink_deg}, low_deg {low_deg} and"

View File

@@ -1,18 +1,22 @@
from __future__ import annotations
from typing import Literal, TYPE_CHECKING, cast
from ophyd_devices.interfaces.base_classes.psi_device_base import PSIDeviceBase
from ophyd import Device, Kind, DeviceStatus, Component as Cpt
from ophyd import EpicsSignal, EpicsSignalRO, StatusBase
from ophyd_devices.sim.sim_signals import SetableSignal
from typing import TYPE_CHECKING, Literal
from bec_lib.logger import bec_logger
from ophyd import Component as Cpt
from ophyd import Device, DeviceStatus, EpicsSignal, EpicsSignalRO, Kind, StatusBase
from ophyd.status import WaitTimeoutError
from ophyd_devices import CompareStatus, ProgressSignal, TransitionStatus
from ophyd_devices.interfaces.base_classes.psi_device_base import PSIDeviceBase
from ophyd_devices.sim.sim_signals import SetableSignal
from debye_bec.devices.nidaq.nidaq_enums import (
EncoderFactors,
NIDAQCompression,
ScanType,
NidaqState,
ScanRates,
ReadoutRange,
EncoderTypes,
ScanRates,
ScanType,
)
if TYPE_CHECKING: # pragma: no cover
@@ -29,69 +33,411 @@ class NidaqControl(Device):
"""Nidaq control class with all PVs"""
### Readback PVs for EpicsEmitter ###
ai0 = Cpt(EpicsSignalRO, suffix="NIDAQ-AI0", kind=Kind.normal, doc="EPICS analog input 0",auto_monitor=True)
ai1 = Cpt(EpicsSignalRO, suffix="NIDAQ-AI1", kind=Kind.normal, doc="EPICS analog input 1",auto_monitor=True)
ai2 = Cpt(EpicsSignalRO, suffix="NIDAQ-AI2", kind=Kind.normal, doc="EPICS analog input 2",auto_monitor=True)
ai3 = Cpt(EpicsSignalRO, suffix="NIDAQ-AI3", kind=Kind.normal, doc="EPICS analog input 3",auto_monitor=True)
ai4 = Cpt(EpicsSignalRO, suffix="NIDAQ-AI4", kind=Kind.normal, doc="EPICS analog input 4",auto_monitor=True)
ai5 = Cpt(EpicsSignalRO, suffix="NIDAQ-AI5", kind=Kind.normal, doc="EPICS analog input 5",auto_monitor=True)
ai6 = Cpt(EpicsSignalRO, suffix="NIDAQ-AI6", kind=Kind.normal, doc="EPICS analog input 6",auto_monitor=True)
ai7 = Cpt(EpicsSignalRO, suffix="NIDAQ-AI7", kind=Kind.normal, doc="EPICS analog input 7",auto_monitor=True)
ai0 = Cpt(
EpicsSignalRO,
suffix="NIDAQ-AI0",
kind=Kind.normal,
doc="EPICS analog input 0",
auto_monitor=True,
)
ai1 = Cpt(
EpicsSignalRO,
suffix="NIDAQ-AI1",
kind=Kind.normal,
doc="EPICS analog input 1",
auto_monitor=True,
)
ai2 = Cpt(
EpicsSignalRO,
suffix="NIDAQ-AI2",
kind=Kind.normal,
doc="EPICS analog input 2",
auto_monitor=True,
)
ai3 = Cpt(
EpicsSignalRO,
suffix="NIDAQ-AI3",
kind=Kind.normal,
doc="EPICS analog input 3",
auto_monitor=True,
)
ai4 = Cpt(
EpicsSignalRO,
suffix="NIDAQ-AI4",
kind=Kind.normal,
doc="EPICS analog input 4",
auto_monitor=True,
)
ai5 = Cpt(
EpicsSignalRO,
suffix="NIDAQ-AI5",
kind=Kind.normal,
doc="EPICS analog input 5",
auto_monitor=True,
)
ai6 = Cpt(
EpicsSignalRO,
suffix="NIDAQ-AI6",
kind=Kind.normal,
doc="EPICS analog input 6",
auto_monitor=True,
)
ai7 = Cpt(
EpicsSignalRO,
suffix="NIDAQ-AI7",
kind=Kind.normal,
doc="EPICS analog input 7",
auto_monitor=True,
)
ci0 = Cpt(EpicsSignalRO, suffix="NIDAQ-CI0", kind=Kind.normal, doc="EPICS counter input 0", auto_monitor=True)
ci1 = Cpt(EpicsSignalRO, suffix="NIDAQ-CI1", kind=Kind.normal, doc="EPICS counter input 1", auto_monitor=True)
ci2 = Cpt(EpicsSignalRO, suffix="NIDAQ-CI2", kind=Kind.normal, doc="EPICS counter input 2", auto_monitor=True)
ci3 = Cpt(EpicsSignalRO, suffix="NIDAQ-CI3", kind=Kind.normal, doc="EPICS counter input 3", auto_monitor=True)
ci4 = Cpt(EpicsSignalRO, suffix="NIDAQ-CI4", kind=Kind.normal, doc="EPICS counter input 4", auto_monitor=True)
ci5 = Cpt(EpicsSignalRO, suffix="NIDAQ-CI5", kind=Kind.normal, doc="EPICS counter input 5", auto_monitor=True)
ci6 = Cpt(EpicsSignalRO, suffix="NIDAQ-CI6", kind=Kind.normal, doc="EPICS counter input 6", auto_monitor=True)
ci7 = Cpt(EpicsSignalRO, suffix="NIDAQ-CI7", kind=Kind.normal, doc="EPICS counter input 7", auto_monitor=True)
ci0 = Cpt(
EpicsSignalRO,
suffix="NIDAQ-CI0",
kind=Kind.normal,
doc="EPICS counter input 0",
auto_monitor=True,
)
ci1 = Cpt(
EpicsSignalRO,
suffix="NIDAQ-CI1",
kind=Kind.normal,
doc="EPICS counter input 1",
auto_monitor=True,
)
ci2 = Cpt(
EpicsSignalRO,
suffix="NIDAQ-CI2",
kind=Kind.normal,
doc="EPICS counter input 2",
auto_monitor=True,
)
ci3 = Cpt(
EpicsSignalRO,
suffix="NIDAQ-CI3",
kind=Kind.normal,
doc="EPICS counter input 3",
auto_monitor=True,
)
ci4 = Cpt(
EpicsSignalRO,
suffix="NIDAQ-CI4",
kind=Kind.normal,
doc="EPICS counter input 4",
auto_monitor=True,
)
ci5 = Cpt(
EpicsSignalRO,
suffix="NIDAQ-CI5",
kind=Kind.normal,
doc="EPICS counter input 5",
auto_monitor=True,
)
ci6 = Cpt(
EpicsSignalRO,
suffix="NIDAQ-CI6",
kind=Kind.normal,
doc="EPICS counter input 6",
auto_monitor=True,
)
ci7 = Cpt(
EpicsSignalRO,
suffix="NIDAQ-CI7",
kind=Kind.normal,
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, suffix="NIDAQ-DI0", kind=Kind.normal, doc="EPICS digital input 0", auto_monitor=True)
di1 = Cpt(EpicsSignalRO, suffix="NIDAQ-DI1", kind=Kind.normal, doc="EPICS digital input 1", auto_monitor=True)
di2 = Cpt(EpicsSignalRO, suffix="NIDAQ-DI2", kind=Kind.normal, doc="EPICS digital input 2", auto_monitor=True)
di3 = Cpt(EpicsSignalRO, suffix="NIDAQ-DI3", kind=Kind.normal, doc="EPICS digital input 3", auto_monitor=True)
di4 = Cpt(EpicsSignalRO, suffix="NIDAQ-DI4", kind=Kind.normal, doc="EPICS digital input 4", auto_monitor=True)
di0 = Cpt(
EpicsSignalRO,
suffix="NIDAQ-DI0",
kind=Kind.normal,
doc="EPICS digital input 0",
auto_monitor=True,
)
di1 = Cpt(
EpicsSignalRO,
suffix="NIDAQ-DI1",
kind=Kind.normal,
doc="EPICS digital input 1",
auto_monitor=True,
)
di2 = Cpt(
EpicsSignalRO,
suffix="NIDAQ-DI2",
kind=Kind.normal,
doc="EPICS digital input 2",
auto_monitor=True,
)
di3 = Cpt(
EpicsSignalRO,
suffix="NIDAQ-DI3",
kind=Kind.normal,
doc="EPICS digital input 3",
auto_monitor=True,
)
di4 = Cpt(
EpicsSignalRO,
suffix="NIDAQ-DI4",
kind=Kind.normal,
doc="EPICS digital input 4",
auto_monitor=True,
)
enc_epics = Cpt(
EpicsSignalRO,
suffix="NIDAQ-ENC",
kind=Kind.normal,
doc="EPICS Encoder reading",
auto_monitor=True,
)
energy_epics = Cpt(
EpicsSignalRO,
suffix="NIDAQ-ENERGY",
kind=Kind.normal,
doc="EPICS Energy reading",
auto_monitor=True,
)
enc_epics = Cpt(EpicsSignalRO, suffix="NIDAQ-ENC", kind=Kind.normal, doc="EPICS Encoder reading", auto_monitor=True)
### Readback for BEC emitter ###
ai0_mean = Cpt(SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream analog input 0, MEAN")
ai1_mean = Cpt(SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream analog input 1, MEAN")
ai2_mean = Cpt(SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream analog input 2, MEAN")
ai3_mean = Cpt(SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream analog input 3, MEAN")
ai4_mean = Cpt(SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream analog input 4, MEAN")
ai5_mean = Cpt(SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream analog input 5, MEAN")
ai6_mean = Cpt(SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream analog input 6, MEAN")
ai7_mean = Cpt(SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream analog input 7, MEAN")
ai0_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")
ai1_std_dev = Cpt(SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream analog input 1, STD")
ai2_std_dev = Cpt(SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream analog input 2, STD")
ai3_std_dev = Cpt(SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream analog input 3, STD")
ai4_std_dev = Cpt(SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream analog input 4, STD")
ai5_std_dev = Cpt(SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream analog input 5, STD")
ai6_std_dev = Cpt(SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream analog input 6, STD")
ai7_std_dev = Cpt(SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream analog input 7, STD")
ai0_std_dev = Cpt(
SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream analog input 0, STD"
)
ai1_std_dev = Cpt(
SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream analog input 1, STD"
)
ai2_std_dev = Cpt(
SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream analog input 2, STD"
)
ai3_std_dev = Cpt(
SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream analog input 3, STD"
)
ai4_std_dev = Cpt(
SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream analog input 4, STD"
)
ai5_std_dev = Cpt(
SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream analog input 5, STD"
)
ai6_std_dev = Cpt(
SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream analog input 6, STD"
)
ai7_std_dev = Cpt(
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_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"
)
ci0_std_dev = Cpt(SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream counter input 0. STD")
ci1_std_dev = Cpt(SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream counter input 1. STD")
ci2_std_dev = Cpt(SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream counter input 2. STD")
ci3_std_dev = Cpt(SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream counter input 3. STD")
ci4_std_dev = Cpt(SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream counter input 4. STD")
ci5_std_dev = Cpt(SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream counter input 5. STD")
ci6_std_dev = Cpt(SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream counter input 6. STD")
ci7_std_dev = Cpt(SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream counter input 7. STD")
ci0_std_dev = Cpt(
SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream counter input 0. STD"
)
ci1_std_dev = Cpt(
SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream counter input 1. STD"
)
ci2_std_dev = Cpt(
SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream counter input 2. STD"
)
ci3_std_dev = Cpt(
SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream counter input 3. STD"
)
ci4_std_dev = Cpt(
SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream counter input 4. STD"
)
ci5_std_dev = Cpt(
SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream counter input 5. STD"
)
ci6_std_dev = Cpt(
SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream counter input 6. STD"
)
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"
)
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")
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")
@@ -100,24 +446,34 @@ class NidaqControl(Device):
di4_max = Cpt(SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream digital input 4, MAX")
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_dead_time_correction = Cpt(EpicsSignal, suffix="NIDAQ-EnableDTC", kind=Kind.config)
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)
scan_type_string = Cpt(EpicsSignal, suffix="NIDAQ-ScanType", kind=Kind.config, string=True)
sampling_rate = Cpt(EpicsSignal, suffix="NIDAQ-SamplingRateRequested", kind=Kind.config)
sampling_rate_string = Cpt(EpicsSignal, suffix="NIDAQ-SamplingRateRequested", kind=Kind.config, string=True)
scan_duration = Cpt(EpicsSignal, suffix="NIDAQ-SamplingDuration", kind=Kind.config)
readout_range = Cpt(EpicsSignal, suffix="NIDAQ-ReadoutRange", kind=Kind.config)
encoder_type = Cpt(EpicsSignal, suffix="NIDAQ-EncoderType", kind=Kind.config)
readout_range_string = Cpt(EpicsSignal, suffix="NIDAQ-ReadoutRange", kind=Kind.config, string=True)
encoder_factor = Cpt(EpicsSignal, suffix="NIDAQ-EncoderFactor", kind=Kind.config)
encoder_factor_string = Cpt(EpicsSignal, suffix="NIDAQ-EncoderFactor", kind=Kind.config, string=True)
stop_call = Cpt(EpicsSignal, suffix="NIDAQ-Stop", kind=Kind.config)
power = Cpt(EpicsSignal, suffix="NIDAQ-Power", kind=Kind.config)
heartbeat = Cpt(EpicsSignal, suffix="NIDAQ-Heartbeat", kind=Kind.config, auto_monitor=True)
time_left = Cpt(EpicsSignalRO, suffix="NIDAQ-TimeLeft", kind=Kind.config, auto_monitor=True)
ai_chans = Cpt(EpicsSignal, suffix="NIDAQ-AIChans", kind=Kind.config)
ci_chans = Cpt(EpicsSignal, suffix="NIDAQ-CIChans6614", kind=Kind.config)
ci_chans = Cpt(EpicsSignal, suffix="NIDAQ-CIChans", kind=Kind.config)
di_chans = Cpt(EpicsSignal, suffix="NIDAQ-DIChans", kind=Kind.config)
@@ -130,16 +486,21 @@ class Nidaq(PSIDeviceBase, NidaqControl):
scan_info (ScanInfo) : ScanInfo object passed by BEC's devicemanager.
"""
progress_signal = Cpt(ProgressSignal, name="progress_signal")
USER_ACCESS = ["set_config"]
def __init__(self, prefix: str = "", *, name: str, scan_info: ScanInfo = None, **kwargs):
super().__init__(name=name, prefix=prefix, scan_info=scan_info, **kwargs)
self.scan_info: ScanInfo
self.timeout_wait_for_signal = 5 # put 5s firsts
self._timeout_wait_for_pv = 5 # 5s timeout for pv calls. editted due to timeout issues persisting
self.valid_scan_names = [
"xas_simple_scan",
"xas_simple_scan_with_xrd",
"xas_advanced_scan",
"xas_advanced_scan_with_xrd",
"nidaq_continuous_scan",
]
########################################
@@ -244,12 +605,20 @@ class Nidaq(PSIDeviceBase, NidaqControl):
elif readout_range == 10:
self.readout_range.put(ReadoutRange.TEN_V)
if encoder_type in "X_1":
self.encoder_type.put(EncoderTypes.X_1)
elif encoder_type in "X_2":
self.encoder_type.put(EncoderTypes.X_2)
elif encoder_type in "X_4":
self.encoder_type.put(EncoderTypes.X_4)
if encoder_type in "1/16":
self.encoder_factor.put(EncoderFactors.X1_16)
elif encoder_type in "1/8":
self.encoder_factor.put(EncoderFactors.X1_8)
elif encoder_type in "1/4":
self.encoder_factor.put(EncoderFactors.X1_4)
elif encoder_type in "1/2":
self.encoder_factor.put(EncoderFactors.X1_2)
elif encoder_type in "1":
self.encoder_factor.put(EncoderFactors.X1)
elif encoder_type in "2":
self.encoder_factor.put(EncoderFactors.X2)
elif encoder_type in "4":
self.encoder_factor.put(EncoderFactors.X4)
if enable_compression is True:
self.enable_compression.put(NIDAQCompression.ON)
@@ -273,12 +642,23 @@ class Nidaq(PSIDeviceBase, NidaqControl):
Called after the device is connected and its signals are connected.
Default values for signals should be set here.
"""
if not self.wait_for_condition(
condition= lambda: self.state.get() == NidaqState.STANDBY,
timeout = self.timeout_wait_for_signal,
check_stopped=True):
raise NidaqError(f"Device {self.name} has not been reached in state STANDBY, current state {NidaqState(self.state.get())}")
self.scan_duration.set(0).wait()
status = TransitionStatus(self.heartbeat, transitions=[0, 1], strict=False)
self.cancel_on_stop(status)
try:
status.wait(timeout=self.timeout_wait_for_signal) # Raises if timeout is reached
except WaitTimeoutError:
logger.warning(f"Device {self.name} was not alive, trying to put power on")
status = TransitionStatus(self.heartbeat, transitions=[0, 1], strict=False)
self.cancel_on_stop(status)
self.power.put(1)
status.wait(timeout=self.timeout_wait_for_signal)
status = CompareStatus(self.state, NidaqState.STANDBY)
self.cancel_on_stop(status)
status.wait(timeout=self.timeout_wait_for_signal)
self.scan_duration.set(0).wait(timeout=self._timeout_wait_for_pv)
self.time_left.subscribe(self._progress_update, run=False)
def on_stage(self) -> DeviceStatus | StatusBase | None:
"""
@@ -290,37 +670,56 @@ class Nidaq(PSIDeviceBase, NidaqControl):
if not self._check_if_scan_name_is_valid():
return None
if not self.wait_for_condition(
condition=lambda: self.state.get() == NidaqState.STANDBY, timeout=self.timeout_wait_for_signal, check_stopped=True
):
raise NidaqError(
f"Device {self.name} has not been reached in state STANDBY, current state {NidaqState(self.state.get())}"
)
self.scan_type.set(ScanType.TRIGGERED).wait()
self.scan_duration.set(0).wait()
self.stage_call.set(1).wait()
if self.state.get() != NidaqState.STANDBY:
status = CompareStatus(self.state, NidaqState.STANDBY)
self.cancel_on_stop(status)
self.on_stop()
status.wait(timeout=self.timeout_wait_for_signal)
if not self.wait_for_condition(
condition=lambda: self.state.get() == NidaqState.STAGE, timeout=self.timeout_wait_for_signal, check_stopped=True
):
raise NidaqError(
f"Device {self.name} has not been reached in state STAGE, current state {NidaqState(self.state.get())}"
# If scan is not part of the valid_scan_names,
if self.scan_info.msg.scan_name != "nidaq_continuous_scan":
self.scan_type.set(ScanType.TRIGGERED).wait(timeout=self._timeout_wait_for_pv)
self.scan_duration.set(0).wait(timeout=self._timeout_wait_for_pv)
self.enable_compression.set(1).wait(timeout=self._timeout_wait_for_pv)
else:
self.scan_type.set(ScanType.CONTINUOUS).wait(timeout=self._timeout_wait_for_pv)
self.scan_duration.set(self.scan_info.msg.scan_parameters["scan_duration"]).wait(
timeout=self._timeout_wait_for_pv
)
self.kickoff_call.set(1).wait()
self.enable_compression.set(self.scan_info.msg.scan_parameters["compression"]).wait(
timeout=self._timeout_wait_for_pv
)
# Stage call to IOC
status = CompareStatus(self.state, NidaqState.STAGE)
self.cancel_on_stop(status)
# TODO 11.11.25/HS64
# Switched from set to put in the hope to get rid of the rare event where nidaq is stopped at the start of a scan
# Problems consistently persisting, testing changing back to set, unconvinced this is the actual cause 14.11.25/AHC
# self.stage_call.set(1).wait(timeout=self._timeout_wait_for_pv)
self.stage_call.put(1)
status.wait(timeout=self.timeout_wait_for_signal)
if self.scan_info.msg.scan_name != "nidaq_continuous_scan":
status = self.on_kickoff()
self.cancel_on_stop(status)
status.wait(timeout=self._timeout_wait_for_pv)
logger.info(f"Device {self.name} was staged: {NidaqState(self.state.get())}")
def on_kickoff(self) -> DeviceStatus | StatusBase:
"""Kickoff the Nidaq"""
status = self.kickoff_call.set(1)
self.cancel_on_stop(status)
return status
def on_unstage(self) -> DeviceStatus | StatusBase | None:
"""Called while unstaging the device. Check that the Nidaq goes into Standby"""
def _get_state():
return self.state.get() == NidaqState.STANDBY
if not self.wait_for_condition(
condition=_get_state, timeout=self.timeout_wait_for_signal, check_stopped=False
):
raise NidaqError(
f"Device {self.name} has not been reached in state STANDBY, current state {NidaqState(self.state.get())}"
)
status = CompareStatus(self.state, NidaqState.STANDBY)
self.cancel_on_stop(status)
status.wait(timeout=self.timeout_wait_for_signal)
status = self.enable_compression.set(1)
self.cancel_on_stop(status)
status.wait(self._timeout_wait_for_pv)
logger.info(f"Device {self.name} was unstaged: {NidaqState(self.state.get())}")
def on_pre_scan(self) -> DeviceStatus | StatusBase | None:
@@ -334,15 +733,13 @@ class Nidaq(PSIDeviceBase, NidaqControl):
if not self._check_if_scan_name_is_valid():
return None
def _wait_for_state():
return self.state.get() == NidaqState.KICKOFF
if self.scan_info.msg.scan_name == "nidaq_continuous_scan":
logger.info(f"Device {self.name} ready to be kicked off for nidaq_continuous_scan")
return None
if not self.wait_for_condition(
_wait_for_state, timeout=self.timeout_wait_for_signal, check_stopped=True
):
raise NidaqError(
f"Device {self.name} failed to reach state KICKOFF during pre scan, current state {NidaqState(self.state.get())}"
)
status = CompareStatus(self.state, NidaqState.KICKOFF)
self.cancel_on_stop(status)
status.wait(timeout=self._timeout_wait_for_pv)
logger.info(
f"Device {self.name} ready to take data after pre_scan: {NidaqState(self.state.get())}"
)
@@ -359,19 +756,27 @@ class Nidaq(PSIDeviceBase, NidaqControl):
"""
if not self._check_if_scan_name_is_valid():
return None
self.on_stop()
#TODO check if this wait can be removed. We are waiting in unstage anyways which will always be called afterwards
# Wait for device to be stopped
status = self.wait_for_condition(
condition = lambda: self.state.get() == NidaqState.STANDBY,
check_stopped=True,
timeout=self.timeout_wait_for_signal,
)
status = CompareStatus(self.state, NidaqState.STANDBY)
self.cancel_on_stop(status)
if self.scan_info.msg.scan_name != "nidaq_continuous_scan":
self.on_stop()
return status
def on_kickoff(self) -> DeviceStatus | StatusBase | None:
"""Called to kickoff a device for a fly scan. Has to be called explicitly."""
def _progress_update(self, value, **kwargs) -> None:
"""Callback method to update the scan progress, runs a callback
to SUB_PROGRESS subscribers, i.e. BEC.
Args:
value (int) : current progress value
"""
scan_duration = self.scan_info.msg.scan_parameters.get("scan_duration", None)
if not isinstance(scan_duration, (int, float)):
return
value = scan_duration - value
max_value = scan_duration
self.progress_signal.put(value=value, max_value=max_value, done=bool(max_value == value))
def on_stop(self) -> None:
"""Called when the device is stopped."""
self.stop_call.set(1).wait()
self.stop_call.put(1)

View File

@@ -2,17 +2,22 @@ import enum
class NIDAQCompression(str, enum.Enum):
""" Options for Compression"""
"""Options for Compression"""
OFF = 0
ON = 1
class ScanType(int, enum.Enum):
""" Triggering options of the backend"""
"""Triggering options of the backend"""
TRIGGERED = 0
CONTINUOUS = 1
class NidaqState(int, enum.Enum):
""" Possible States of the NIDAQ backend"""
"""Possible States of the NIDAQ backend"""
DISABLED = 0
STANDBY = 1
STAGE = 2
@@ -20,8 +25,10 @@ class NidaqState(int, enum.Enum):
ACQUIRE = 4
UNSTAGE = 5
class ScanRates(int, enum.Enum):
""" Sampling Rate options for the backend, in kHZ and MHz"""
"""Sampling Rate options for the backend, in kHZ and MHz"""
HUNDRED_KHZ = 0
FIVE_HUNDRED_KHZ = 1
ONE_MHZ = 2
@@ -31,15 +38,23 @@ class ScanRates(int, enum.Enum):
TEN_MHZ = 6
FOURTEEN_THREE_MHZ = 7
class ReadoutRange(int, enum.Enum):
"""ReadoutRange in +-V"""
ONE_V = 0
TWO_V = 1
FIVE_V = 2
TEN_V = 3
class EncoderTypes(int, enum.Enum):
""" Encoder Types"""
X_1 = 0
X_2 = 1
X_4 = 2
class EncoderFactors(int, enum.Enum):
"""Encoder Factors"""
X1_16 = 0
X1_8 = 1
X1_4 = 2
X1_2 = 3
X1 = 4
X2 = 5
X4 = 6

View File

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()

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

View File

@@ -0,0 +1,200 @@
"""ES2 Reference Foil Changer"""
from __future__ import annotations
import enum
from typing import TYPE_CHECKING
from ophyd import Component as Cpt
from ophyd import EpicsSignal, EpicsSignalRO, EpicsSignalWithRBV
from ophyd.status import DeviceStatus
from ophyd_devices.interfaces.base_classes.psi_device_base import PSIDeviceBase
from ophyd_devices.utils.errors import DeviceStopError
if TYPE_CHECKING:
from bec_lib.devicemanager import ScanInfo
class Status(int, enum.Enum):
"""Enum class for the status field"""
BOOT = 0
RETRACTED = 1
INSERTED = 2
MOVING = 3
ERROR = 4
class OpMode(int, enum.Enum):
"""Enum class for the Operating Mode field"""
USERMODE = 0
MAINTENANCEMODE = 1
DIAGNOSTICMODE = 2
ERRORMODE = 3
class Reffoilchanger(PSIDeviceBase):
"""Class for the ES2 Reference Foil Changer"""
USER_ACCESS = ["insert"]
inserted = Cpt(
EpicsSignalRO, suffix="ES2-REF:TRY-FilterInserted", kind="config", doc="Inserted indicator"
)
retracted = Cpt(
EpicsSignalRO,
suffix="ES2-REF:TRY-FilterRetracted",
kind="config",
doc="Retracted indicator",
)
moving = Cpt(EpicsSignalRO, suffix="ES2-REF:ROTY.MOVN", kind="config", doc="Moving indicator")
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"
)
foil01 = Cpt(EpicsSignalRO, suffix="ES-REFFOIL:FOIL01.DESC", kind="config", doc="Foil 01")
foil02 = Cpt(EpicsSignalRO, suffix="ES-REFFOIL:FOIL02.DESC", kind="config", doc="Foil 02")
foil03 = Cpt(EpicsSignalRO, suffix="ES-REFFOIL:FOIL03.DESC", kind="config", doc="Foil 03")
foil04 = Cpt(EpicsSignalRO, suffix="ES-REFFOIL:FOIL04.DESC", kind="config", doc="Foil 04")
foil05 = Cpt(EpicsSignalRO, suffix="ES-REFFOIL:FOIL05.DESC", kind="config", doc="Foil 05")
foil06 = Cpt(EpicsSignalRO, suffix="ES-REFFOIL:FOIL06.DESC", kind="config", doc="Foil 06")
foil07 = Cpt(EpicsSignalRO, suffix="ES-REFFOIL:FOIL07.DESC", kind="config", doc="Foil 07")
foil08 = Cpt(EpicsSignalRO, suffix="ES-REFFOIL:FOIL08.DESC", kind="config", doc="Foil 08")
foil09 = Cpt(EpicsSignalRO, suffix="ES-REFFOIL:FOIL09.DESC", kind="config", doc="Foil 09")
foil10 = Cpt(EpicsSignalRO, suffix="ES-REFFOIL:FOIL10.DESC", kind="config", doc="Foil 10")
foil11 = Cpt(EpicsSignalRO, suffix="ES-REFFOIL:FOIL11.DESC", kind="config", doc="Foil 11")
foil12 = Cpt(EpicsSignalRO, suffix="ES-REFFOIL:FOIL12.DESC", kind="config", doc="Foil 12")
foil13 = Cpt(EpicsSignalRO, suffix="ES-REFFOIL:FOIL13.DESC", kind="config", doc="Foil 13")
foil14 = Cpt(EpicsSignalRO, suffix="ES-REFFOIL:FOIL14.DESC", kind="config", doc="Foil 14")
foil15 = Cpt(EpicsSignalRO, suffix="ES-REFFOIL:FOIL15.DESC", kind="config", doc="Foil 15")
foil16 = Cpt(EpicsSignalRO, suffix="ES-REFFOIL:FOIL16.DESC", kind="config", doc="Foil 16")
foil17 = Cpt(EpicsSignalRO, suffix="ES-REFFOIL:FOIL17.DESC", kind="config", doc="Foil 17")
foil18 = Cpt(EpicsSignalRO, suffix="ES-REFFOIL:FOIL18.DESC", kind="config", doc="Foil 18")
foil19 = Cpt(EpicsSignalRO, suffix="ES-REFFOIL:FOIL19.DESC", kind="config", doc="Foil 19")
foil20 = Cpt(EpicsSignalRO, suffix="ES-REFFOIL:FOIL20.DESC", kind="config", doc="Foil 20")
foil21 = Cpt(EpicsSignalRO, suffix="ES-REFFOIL:FOIL21.DESC", kind="config", doc="Foil 21")
foil22 = Cpt(EpicsSignalRO, suffix="ES-REFFOIL:FOIL22.DESC", kind="config", doc="Foil 22")
foil23 = Cpt(EpicsSignalRO, suffix="ES-REFFOIL:FOIL23.DESC", kind="config", doc="Foil 23")
foil24 = Cpt(EpicsSignalRO, suffix="ES-REFFOIL:FOIL24.DESC", kind="config", doc="Foil 24")
foil25 = Cpt(EpicsSignalRO, suffix="ES-REFFOIL:FOIL25.DESC", kind="config", doc="Foil 25")
foil26 = Cpt(EpicsSignalRO, suffix="ES-REFFOIL:FOIL26.DESC", kind="config", doc="Foil 26")
foil27 = Cpt(EpicsSignalRO, suffix="ES-REFFOIL:FOIL27.DESC", kind="config", doc="Foil 27")
foil28 = Cpt(EpicsSignalRO, suffix="ES-REFFOIL:FOIL28.DESC", kind="config", doc="Foil 28")
foil29 = Cpt(EpicsSignalRO, suffix="ES-REFFOIL:FOIL29.DESC", kind="config", doc="Foil 29")
foil30 = Cpt(EpicsSignalRO, suffix="ES-REFFOIL:FOIL30.DESC", kind="config", doc="Foil 30")
foil31 = Cpt(EpicsSignalRO, suffix="ES-REFFOIL:FOIL31.DESC", kind="config", doc="Foil 31")
foil32 = Cpt(EpicsSignalRO, suffix="ES-REFFOIL:FOIL32.DESC", kind="config", doc="Foil 32")
foil33 = Cpt(EpicsSignalRO, suffix="ES-REFFOIL:FOIL33.DESC", kind="config", doc="Foil 33")
foil34 = Cpt(EpicsSignalRO, suffix="ES-REFFOIL:FOIL34.DESC", kind="config", doc="Foil 34")
foil35 = Cpt(EpicsSignalRO, suffix="ES-REFFOIL:FOIL35.DESC", kind="config", doc="Foil 35")
foil36 = Cpt(EpicsSignalRO, suffix="ES-REFFOIL:FOIL36.DESC", kind="config", doc="Foil 36")
foil37 = Cpt(EpicsSignalRO, suffix="ES-REFFOIL:FOIL37.DESC", kind="config", doc="Foil 37")
foil38 = Cpt(EpicsSignalRO, suffix="ES-REFFOIL:FOIL38.DESC", kind="config", doc="Foil 38")
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.foils = [
self.foil01,
self.foil02,
self.foil03,
self.foil04,
self.foil05,
self.foil06,
self.foil07,
self.foil08,
self.foil09,
self.foil10,
self.foil11,
self.foil12,
self.foil13,
self.foil14,
self.foil15,
self.foil16,
self.foil17,
self.foil18,
self.foil19,
self.foil20,
self.foil21,
self.foil22,
self.foil23,
self.foil24,
self.foil25,
self.foil26,
self.foil27,
self.foil28,
self.foil29,
self.foil30,
self.foil31,
self.foil32,
self.foil33,
self.foil34,
self.foil35,
self.foil36,
self.foil37,
self.foil38,
]
def insert(self, ref: str, wait: bool = False) -> DeviceStatus:
"""Insert a reference
Args:
ref (str) : Desired reference foil name, e.g. Fe or Pt
wait (bool): If you like to wait for the filling to finish. Default False.
"""
filter_number = -1
for i, foil in enumerate(self.foils):
if foil.get() == ref:
filter_number = i + 1
break
if filter_number == -1:
raise ValueError(f"Requested foil ({ref}) is not in list of available foils")
if self.op_mode.get() == OpMode.USERMODE:
self.ref_set.put(filter_number)
def wait_for_status():
return (
(self.status.get() == Status.RETRACTED)
or (self.status.get() == Status.MOVING)
or (
self.ref_rb.get() < (filter_number + 0.2)
and self.ref_rb.get() > (filter_number - 0.2)
)
)
timeout = 3
if not self.wait_for_condition(wait_for_status, timeout=timeout, check_stopped=True):
raise TimeoutError(
f"Reference foil changer did not retract the current foil within {timeout}s"
)
def wait_for_change_finished():
return self.status.get() == Status.INSERTED and self.op_mode == OpMode.USERMODE
# Wait until new reference foil is inserted
status = self.task_handler.submit_task(
task=self.wait_for_condition, task_args=(wait_for_change_finished, 5, True)
)
if wait:
status.wait()
return status
else:
raise DeviceStopError(
f"Reference foil changer must be in User Mode but is in {self.op_mode.get(as_string=True)}"
)

View File

@@ -0,0 +1 @@
from .debye_nexus_structure import DebyeNexusStructure

View File

@@ -0,0 +1,125 @@
from bec_server.file_writer.default_writer import DefaultFormat
class DebyeNexusStructure(DefaultFormat):
"""Nexus Structure for Debye"""
def format(self) -> None:
"""Specify the file format for the file writer."""
entry = self.storage.create_group(name="entry")
entry.attrs["NX_class"] = "NXentry"
instrument = entry.create_group(name="instrument")
instrument.attrs["NX_class"] = "NXinstrument"
###################
## 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
###################
source = instrument.create_group(name="source")
source.attrs["NX_class"] = "NXsource"
beamline_name = source.create_dataset(name="beamline_name", data="Debye")
beamline_name.attrs["NX_class"] = "NX_CHAR"
facility_name = source.create_dataset(name="facility_name", data="Swiss Light Source")
facility_name.attrs["NX_class"] = "NX_CHAR"
probe = source.create_dataset(name="probe", data="X-ray")
probe.attrs["NX_class"] = "NX_CHAR"

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

View File

@@ -1 +1,7 @@
from .mono_bragg_scans import XASSimpleScan, XASSimpleScanWithXRD, XASAdvancedScan, XASAdvancedScanWithXRD
from .mono_bragg_scans import (
XASAdvancedScan,
XASAdvancedScanWithXRD,
XASSimpleScan,
XASSimpleScanWithXRD,
)
from .nidaq_cont_scan import NIDAQContinuousScan

View File

@@ -0,0 +1,12 @@
# from .metadata_schema_xas_simple_scan import xas_simple_scan_schema
METADATA_SCHEMA_REGISTRY = { # "xas_simple_scan": xas_simple_scan_schema
# Add models which should be used to validate scan metadata here.
# Make a model according to the template, and import it as above
# Then associate it with a scan like so:
# "example_scan": ExampleSchema
}
# Define a default schema type which should be used as the fallback for everything:
DEFAULT_SCHEMA = None

View File

@@ -0,0 +1,34 @@
# # By inheriting from BasicScanMetadata you can define a schema by which metadata
# # supplied to a scan must be validated.
# # This schema is a Pydantic model: https://docs.pydantic.dev/latest/concepts/models/
# # but by default it will still allow you to add any arbitrary information to it.
# # That is to say, when you run a scan with which such a model has been associated in the
# # metadata_schema_registry, you can supply any python dictionary with strings as keys
# # and built-in python types (strings, integers, floats) as values, and these will be
# # added to the experiment metadata, but it *must* contain the keys and values of the
# # types defined in the schema class.
# #
# #
# # For example, say that you would like to enforce recording information about sample
# # pretreatment, you could define the following:
# #
#
# from bec_lib.metadata_schema import BasicScanMetadata
#
#
# class ExampleSchema(BasicScanMetadata):
# treatment_description: str
# treatment_temperature_k: int
#
#
# # If this was used according to the example in metadata_schema_registry.py,
# # then when calling the scan, the user would need to write something like:
# >>> scans.example_scan(
# >>> motor,
# >>> 1,
# >>> 2,
# >>> 3,
# >>> metadata={"treatment_description": "oven overnight", "treatment_temperature_k": 575},
# >>> )
#
# # And the additional metadata would be saved in the HDF5 file created for the scan.

View File

@@ -0,0 +1,8 @@
from bec_lib.metadata_schema import BasicScanMetadata
#
#
class xas_simple_scan_schema(BasicScanMetadata):
Edge: str
Element: str

View File

@@ -72,11 +72,7 @@ class XASSimpleScan(AsyncFlyScanBase):
yield None
def pre_scan(self):
"""Pre Scan action. Ensure the motor movetype is set to energy, then check
limits for start/end energy.
#TODO Remove once the motor movetype is removed and ANGLE motion is a pseudo motor.
"""
yield from self.stubs.send_rpc_and_wait(self.motor, "move_type.set", "energy")
"""Pre Scan action."""
self._check_limits()
# Ensure parent class pre_scan actions to be called.
@@ -112,8 +108,9 @@ class XASSimpleScanWithXRD(XASSimpleScan):
gui_config = {
"Movement Parameters": ["start", "stop"],
"Scan Parameters": ["scan_time", "scan_duration"],
"Low Energy Range": ["xrd_enable_low", "num_trigger_low", "exp_time_low", "cycle_low"],
"High Energy Range": ["xrd_enable_high", "num_trigger_high", "exp_time_high", "cycle_high"],
"Low Energy Break": ["break_enable_low", "break_time_low", "cycle_low"],
"High Energy Break": ["break_enable_high", "break_time_high", "cycle_high"],
"XRD Triggers": ["exp_time", "n_of_trigger"],
}
def __init__(
@@ -122,14 +119,14 @@ class XASSimpleScanWithXRD(XASSimpleScan):
stop: float,
scan_time: float,
scan_duration: float,
xrd_enable_low: bool,
num_trigger_low: int,
exp_time_low: float,
break_enable_low: bool,
break_time_low: float,
cycle_low: int,
xrd_enable_high: bool,
num_trigger_high: int,
exp_time_high: float,
break_enable_high: bool,
break_time_high: float,
cycle_high: float,
exp_time: float,
n_of_trigger: int,
motor: DeviceBase = "mo1_bragg",
**kwargs,
):
@@ -142,16 +139,16 @@ class XASSimpleScanWithXRD(XASSimpleScan):
stop (float): Stop energy for the scan.
scan_time (float): Time for one oscillation .
scan_duration (float): Total duration of the scan.
xrd_enable_low (bool): Enable XRD triggering for the low energy range.
num_trigger_low (int): Number of triggers for the low energy range.
exp_time_low (float): Exposure time for the low energy range.
break_enable_low (bool): Enable breaks for the low energy range.
break_time_low (float): Break time for the low energy range.
cycle_low (int): Specify how often the triggers should be considered,
every nth cycle for low
xrd_enable_high (bool): Enable XRD triggering for the high energy range.
num_trigger_high (int): Number of triggers for the high energy range.
exp_time_high (float): Exposure time for the high energy range.
break_enable_high (bool): Enable breaks for the high energy range.
break_time_high (float): Break time for the high energy range.
cycle_high (int): Specify how often the triggers should be considered,
every nth cycle for high
exp_time (float): Length of 1 trigger period in seconds
n_of_trigger (int): Amount of triggers to be fired during break
motor (DeviceBase, optional): Motor device to be used for the scan.
Defaults to "mo1_bragg".
@@ -166,14 +163,14 @@ class XASSimpleScanWithXRD(XASSimpleScan):
motor=motor,
**kwargs,
)
self.xrd_enable_low = xrd_enable_low
self.num_trigger_low = num_trigger_low
self.exp_time_low = exp_time_low
self.break_enable_low = break_enable_low
self.break_time_low = break_time_low
self.cycle_low = cycle_low
self.xrd_enable_high = xrd_enable_high
self.num_trigger_high = num_trigger_high
self.exp_time_high = exp_time_high
self.break_enable_high = break_enable_high
self.break_time_high = break_time_high
self.cycle_high = cycle_high
self.exp_time = exp_time
self.n_of_trigger = n_of_trigger
class XASAdvancedScan(XASSimpleScan):
@@ -237,8 +234,9 @@ class XASAdvancedScanWithXRD(XASAdvancedScan):
"Movement Parameters": ["start", "stop"],
"Scan Parameters": ["scan_time", "scan_duration"],
"Spline Parameters": ["p_kink", "e_kink"],
"Low Energy Range": ["xrd_enable_low", "num_trigger_low", "exp_time_low", "cycle_low"],
"High Energy Range": ["xrd_enable_high", "num_trigger_high", "exp_time_high", "cycle_high"],
"Low Energy Break": ["break_enable_low", "break_time_low", "cycle_low"],
"High Energy Break": ["break_enable_high", "break_time_high", "cycle_high"],
"XRD Triggers": ["exp_time", "n_of_trigger"],
}
def __init__(
@@ -249,14 +247,14 @@ class XASAdvancedScanWithXRD(XASAdvancedScan):
scan_duration: float,
p_kink: float,
e_kink: float,
xrd_enable_low: bool,
num_trigger_low: int,
exp_time_low: float,
break_enable_low: bool,
break_time_low: float,
cycle_low: int,
xrd_enable_high: bool,
num_trigger_high: int,
exp_time_high: float,
break_enable_high: bool,
break_time_high: float,
cycle_high: float,
exp_time: float,
n_of_trigger: int,
motor: DeviceBase = "mo1_bragg",
**kwargs,
):
@@ -274,16 +272,16 @@ class XASAdvancedScanWithXRD(XASAdvancedScan):
scan_duration (float): Total duration of the scan.
p_kink (float): Position of kink.
e_kink (float): Energy of the kink.
xrd_enable_low (bool): Enable XRD triggering for the low energy range.
num_trigger_low (int): Number of triggers for the low energy range.
exp_time_low (float): Exposure time for the low energy range.
break_enable_low (bool): Enable breaks for the low energy range.
break_time_low (float): Break time for the low energy range.
cycle_low (int): Specify how often the triggers should be considered,
every nth cycle for low
xrd_enable_high (bool): Enable XRD triggering for the high energy range.
num_trigger_high (int): Number of triggers for the high energy range.
exp_time_high (float): Exposure time for the high energy range.
break_enable_high (bool): Enable breaks for the high energy range.
break_time_high (float): Break time for the high energy range.
cycle_high (int): Specify how often the triggers should be considered,
every nth cycle for high
exp_time (float): Length of 1 trigger period in seconds
n_of_trigger (int): Amount of triggers to be fired during break
motor (DeviceBase, optional): Motor device to be used for the scan.
Defaults to "mo1_bragg".
@@ -302,11 +300,11 @@ class XASAdvancedScanWithXRD(XASAdvancedScan):
)
self.p_kink = p_kink
self.e_kink = e_kink
self.xrd_enable_low = xrd_enable_low
self.num_trigger_low = num_trigger_low
self.exp_time_low = exp_time_low
self.break_enable_low = break_enable_low
self.break_time_low = break_time_low
self.cycle_low = cycle_low
self.xrd_enable_high = xrd_enable_high
self.num_trigger_high = num_trigger_high
self.exp_time_high = exp_time_high
self.break_enable_high = break_enable_high
self.break_time_high = break_time_high
self.cycle_high = cycle_high
self.exp_time = exp_time
self.n_of_trigger = n_of_trigger

View File

@@ -0,0 +1,84 @@
"""This module contains the scan class for the nidaq of the Debye beamline for use in continuous mode."""
import time
from typing import Literal
import numpy as np
from bec_lib.device import DeviceBase
from bec_lib.logger import bec_logger
from bec_server.scan_server.scans import AsyncFlyScanBase
logger = bec_logger.logger
class NIDAQContinuousScan(AsyncFlyScanBase):
"""Class for the nidaq continuous scan (without mono)"""
scan_name = "nidaq_continuous_scan"
scan_type = "fly"
scan_report_hint = "device_progress"
required_kwargs = []
use_scan_progress_report = False
pre_move = False
gui_config = {"Scan Parameters": ["scan_duration"], "Data Compression": ["compression"]}
def __init__(
self, scan_duration: float, daq: DeviceBase = "nidaq", compression: bool = False, **kwargs
):
"""The NIDAQ continuous scan is used to measure with the NIDAQ without moving the
monochromator or any other motor. The NIDAQ thus runs in continuous mode, with a
set scan_duration.
Args:
scan_duration (float): Duration of the scan.
daq (DeviceBase, optional): DAQ device to be used for the scan.
Defaults to "nidaq".
Examples:
>>> scans.nidaq_continuous_scan(scan_duration=10)
"""
super().__init__(**kwargs)
self.scan_duration = scan_duration
self.daq = daq
self.start_time = 0
self.primary_readout_cycle = 1
self.scan_parameters["scan_duration"] = scan_duration
self.scan_parameters["compression"] = compression
def update_readout_priority(self):
"""Ensure that NIDAQ is not monitored for any quick EXAFS."""
super().update_readout_priority()
self.readout_priority["async"].append("nidaq")
def prepare_positions(self):
"""Prepare the positions for the scan."""
yield None
def pre_scan(self):
"""Pre Scan action."""
self.start_time = time.time()
# Ensure parent class pre_scan actions to be called.
yield from super().pre_scan()
def scan_report_instructions(self):
"""
Return the instructions for the scan report.
"""
yield from self.stubs.scan_report_instruction({"device_progress": [self.daq]})
def scan_core(self):
"""Run the scan core.
Kickoff the acquisition of the NIDAQ wait for the completion of the scan.
"""
kickoff_status = yield from self.stubs.kickoff(device=self.daq)
kickoff_status.wait(timeout=5) # wait for proper kickoff of device
complete_status = yield from self.stubs.complete(device=self.daq, wait=False)
while not complete_status.done:
# Readout monitored devices
yield from self.stubs.read(group="monitored", point_id=self.point_id)
time.sleep(self.primary_readout_cycle)
self.point_id += 1
self.num_pos = self.point_id

View File

@@ -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

View File

@@ -5,24 +5,25 @@ build-backend = "hatchling.build"
[project]
name = "debye_bec"
version = "0.0.0"
description = "Custom device implementations based on the ophyd hardware abstraction layer"
requires-python = ">=3.10"
description = "A plugin repository for BEC"
requires-python = ">=3.11"
classifiers = [
"Development Status :: 3 - Alpha",
"Programming Language :: Python :: 3",
"Topic :: Scientific/Engineering",
]
dependencies = ["numpy", "scipy", "bec_lib", "h5py", "ophyd_devices"]
dependencies = ["numpy", "scipy", "bec_lib", "h5py", "ophyd_devices", "opencv-python==4.11.0.86"]
[project.optional-dependencies]
dev = [
"bec_server",
"black ~= 24.0",
"black",
"copier",
"isort",
"coverage",
"pylint",
"pytest",
"pytest-random-order",
"bec_server",
]
[project.entry-points."bec"]
@@ -37,12 +38,18 @@ plugin_file_writer = "debye_bec.file_writer"
[project.entry-points."bec.scans"]
plugin_scans = "debye_bec.scans"
[project.entry-points."bec.scans.metadata_schema"]
plugin_metadata_schema = "debye_bec.scans.metadata_schema"
[project.entry-points."bec.ipython_client_startup"]
plugin_ipython_client_pre = "debye_bec.bec_ipython_client.startup.pre_startup"
plugin_ipython_client_post = "debye_bec.bec_ipython_client.startup"
[project.entry-points."bec.widgets"]
plugin_widgets = "debye_bec.bec_widgets"
[project.entry-points."bec.widgets.auto_updates"]
plugin_widgets_update = "debye_bec.bec_widgets.auto_updates"
[project.entry-points."bec.widgets.user_widgets"]
plugin_widgets = "debye_bec.bec_widgets.widgets"
[tool.hatch.build.targets.wheel]
include = ["*"]

0
test_commit.yml Normal file
View File

0
test_commit_2.yml Normal file
View File

0
test_commit_3.yml Normal file
View File

0
test_commit_4 Normal file
View File

View File

@@ -1,31 +1,34 @@
# Getting Started with Testing using pytest
BEC is using the [pytest](https://docs.pytest.org/en/8.0.x/) framework.
It can be install via
``` bash
BEC is using the [pytest](https://docs.pytest.org/en/latest/) framework.
It can be installed via
```bash
pip install pytest
```
in your *python environment*.
in your _python environment_.
We note that pytest is part of the optional-dependencies `[dev]` of the plugin package.
## Introduction
Tests in this package should be stored in the `tests` directory.
We suggest to sort tests of different submodules, i.e. `scans` or `devices` in the respective folder structure, and to folow a naming convention of `<test_module_name.py>`.
It is mandatory for test files to begin with `test_` for pytest to discover them.
To run all tests, navigate to the directory of the plugin from the command line, and run the command
To run all tests, navigate to the directory of the plugin from the command line, and run the command
``` bash
```bash
pytest -v --random-order ./tests
```
Note, the python environment needs to be active.
The additional arg `-v` allows pytest to run in verbose mode which provides more detailed information about the tests being run.
The argument `--random-order` instructs pytest to run the tests in random order, which is the default in the CI pipelines.
The argument `--random-order` instructs pytest to run the tests in random order, which is the default in the CI pipelines.
## Test examples
Writing tests can be quite specific for the given function.
Writing tests can be quite specific for the given function.
We recommend writing tests as isolated as possible, i.e. try to test single functions instead of full classes.
A very useful class to enable isolated testing is [MagicMock](https://docs.python.org/3/library/unittest.mock.html).
In addition, we also recommend to take a look at the [How-to guides from pytest](https://docs.pytest.org/en/8.0.x/how-to/index.html).

View File

@@ -1,31 +1,34 @@
# Getting Started with Testing using pytest
BEC is using the [pytest](https://docs.pytest.org/en/8.0.x/) framework.
It can be install via
``` bash
BEC is using the [pytest](https://docs.pytest.org/en/latest/) framework.
It can be installed via
```bash
pip install pytest
```
in your *python environment*.
in your _python environment_.
We note that pytest is part of the optional-dependencies `[dev]` of the plugin package.
## Introduction
Tests in this package should be stored in the `tests` directory.
We suggest to sort tests of different submodules, i.e. `scans` or `devices` in the respective folder structure, and to folow a naming convention of `<test_module_name.py>`.
It is mandatory for test files to begin with `test_` for pytest to discover them.
To run all tests, navigate to the directory of the plugin from the command line, and run the command
To run all tests, navigate to the directory of the plugin from the command line, and run the command
``` bash
```bash
pytest -v --random-order ./tests
```
Note, the python environment needs to be active.
The additional arg `-v` allows pytest to run in verbose mode which provides more detailed information about the tests being run.
The argument `--random-order` instructs pytest to run the tests in random order, which is the default in the CI pipelines.
The argument `--random-order` instructs pytest to run the tests in random order, which is the default in the CI pipelines.
## Test examples
Writing tests can be quite specific for the given function.
Writing tests can be quite specific for the given function.
We recommend writing tests as isolated as possible, i.e. try to test single functions instead of full classes.
A very useful class to enable isolated testing is [MagicMock](https://docs.python.org/3/library/unittest.mock.html).
In addition, we also recommend to take a look at the [How-to guides from pytest](https://docs.pytest.org/en/8.0.x/how-to/index.html).

View File

@@ -0,0 +1,34 @@
# Getting Started with Testing using pytest
BEC is using the [pytest](https://docs.pytest.org/en/latest/) framework.
It can be installed via
```bash
pip install pytest
```
in your _python environment_.
We note that pytest is part of the optional-dependencies `[dev]` of the plugin package.
## Introduction
Tests in this package should be stored in the `tests` directory.
We suggest to sort tests of different submodules, i.e. `scans` or `devices` in the respective folder structure, and to folow a naming convention of `<test_module_name.py>`.
It is mandatory for test files to begin with `test_` for pytest to discover them.
To run all tests, navigate to the directory of the plugin from the command line, and run the command
```bash
pytest -v --random-order ./tests
```
Note, the python environment needs to be active.
The additional arg `-v` allows pytest to run in verbose mode which provides more detailed information about the tests being run.
The argument `--random-order` instructs pytest to run the tests in random order, which is the default in the CI pipelines.
## Test examples
Writing tests can be quite specific for the given function.
We recommend writing tests as isolated as possible, i.e. try to test single functions instead of full classes.
A very useful class to enable isolated testing is [MagicMock](https://docs.python.org/3/library/unittest.mock.html).
In addition, we also recommend to take a look at the [How-to guides from pytest](https://docs.pytest.org/en/8.0.x/how-to/index.html).

View File

@@ -1,31 +1,34 @@
# Getting Started with Testing using pytest
BEC is using the [pytest](https://docs.pytest.org/en/8.0.x/) framework.
It can be install via
``` bash
BEC is using the [pytest](https://docs.pytest.org/en/latest/) framework.
It can be installed via
```bash
pip install pytest
```
in your *python environment*.
in your _python environment_.
We note that pytest is part of the optional-dependencies `[dev]` of the plugin package.
## Introduction
Tests in this package should be stored in the `tests` directory.
We suggest to sort tests of different submodules, i.e. `scans` or `devices` in the respective folder structure, and to folow a naming convention of `<test_module_name.py>`.
It is mandatory for test files to begin with `test_` for pytest to discover them.
To run all tests, navigate to the directory of the plugin from the command line, and run the command
To run all tests, navigate to the directory of the plugin from the command line, and run the command
``` bash
```bash
pytest -v --random-order ./tests
```
Note, the python environment needs to be active.
The additional arg `-v` allows pytest to run in verbose mode which provides more detailed information about the tests being run.
The argument `--random-order` instructs pytest to run the tests in random order, which is the default in the CI pipelines.
The argument `--random-order` instructs pytest to run the tests in random order, which is the default in the CI pipelines.
## Test examples
Writing tests can be quite specific for the given function.
Writing tests can be quite specific for the given function.
We recommend writing tests as isolated as possible, i.e. try to test single functions instead of full classes.
A very useful class to enable isolated testing is [MagicMock](https://docs.python.org/3/library/unittest.mock.html).
In addition, we also recommend to take a look at the [How-to guides from pytest](https://docs.pytest.org/en/8.0.x/how-to/index.html).

View File

@@ -0,0 +1,70 @@
"""Module to test prosilica and Basler cam integrations."""
import threading
from unittest import mock
import ophyd
import pytest
from ophyd_devices.devices.areadetector.cam import AravisDetectorCam, ProsilicaDetectorCam
from ophyd_devices.devices.areadetector.plugins import ImagePlugin_V35
from ophyd_devices.tests.utils import MockPV, patch_dual_pvs
from debye_bec.devices.cameras.basler_cam import BaslerCam
from debye_bec.devices.cameras.prosilica_cam import ProsilicaCam
# pylint: disable=protected-access
# pylint: disable=redefined-outer-name
@pytest.fixture(scope="function")
def mock_basler():
"""Fixture to mock the camera device."""
name = "cam"
prefix = "test:"
with mock.patch.object(ophyd, "cl") as mock_cl:
mock_cl.get_pv = MockPV
mock_cl.thread_class = threading.Thread
dev = BaslerCam(name=name, prefix=prefix)
patch_dual_pvs(dev)
yield dev
def test_basler_init(mock_basler):
"""Test the initialization of the Basler camera device."""
assert mock_basler.name == "cam"
assert mock_basler.prefix == "test:"
assert isinstance(mock_basler.cam1, AravisDetectorCam)
assert isinstance(mock_basler.image1, ImagePlugin_V35)
assert mock_basler._update_frequency == 1
assert mock_basler._live_mode is False
assert mock_basler._live_mode_event is None
assert mock_basler._task_status is None
assert mock_basler.preview.ndim == 2
assert mock_basler.preview.num_rotation_90 == 3
@pytest.fixture(scope="function")
def mock_prosilica():
"""Fixture to mock the camera device."""
name = "cam"
prefix = "test:"
with mock.patch.object(ophyd, "cl") as mock_cl:
mock_cl.get_pv = MockPV
mock_cl.thread_class = threading.Thread
dev = ProsilicaCam(name=name, prefix=prefix)
patch_dual_pvs(dev)
yield dev
def test_prosilica_init(mock_prosilica):
"""Test the initialization of the Prosilica camera device."""
assert mock_prosilica.name == "cam"
assert mock_prosilica.prefix == "test:"
assert isinstance(mock_prosilica.cam1, ProsilicaDetectorCam)
assert isinstance(mock_prosilica.image1, ImagePlugin_V35)
assert mock_prosilica._update_frequency == 1
assert mock_prosilica._live_mode is False
assert mock_prosilica._live_mode_event is None
assert mock_prosilica._task_status is None
assert mock_prosilica.preview.ndim == 2
assert mock_prosilica.preview.num_rotation_90 == 3

View File

@@ -0,0 +1,86 @@
"""Module to test camera base integration class for Debye."""
import threading
from unittest import mock
import ophyd
import pytest
from ophyd_devices.tests.utils import MockPV, patch_dual_pvs
from debye_bec.devices.cameras.debye_base_cam import DebyeBaseCamera
# pylint: disable=protected-access
# pylint: disable=redefined-outer-name
@pytest.fixture(scope="function")
def mock_cam():
"""Fixture to mock the camera device."""
name = "cam"
prefix = "test:"
with mock.patch.object(ophyd, "cl") as mock_cl:
mock_cl.get_pv = MockPV
mock_cl.thread_class = threading.Thread
dev = DebyeBaseCamera(name=name, prefix=prefix)
patch_dual_pvs(dev)
yield dev
def test_init(mock_cam):
"""Test the initialization of the camera device."""
assert mock_cam.name == "cam"
assert mock_cam.prefix == "test:"
assert mock_cam._update_frequency == 1
assert mock_cam._live_mode is False
assert mock_cam._live_mode_event is None
assert mock_cam._task_status is None
assert mock_cam.preview.ndim == 2
assert mock_cam.preview.num_rotation_90 == -1
def test_start_live_mode(mock_cam):
"""Test starting live mode."""
def mock_emit_to_bec(*args, **kwargs):
"""Mock emit_to_bec method."""
while not mock_cam._live_mode_event.wait(1 / mock_cam._update_frequency):
pass
with mock.patch.object(mock_cam, "emit_to_bec", side_effect=mock_emit_to_bec):
mock_cam._start_live_mode()
assert mock_cam._live_mode_event is not None
assert mock_cam._task_status is not None
assert mock_cam._task_status.state == "running"
mock_cam._live_mode_event.set()
# Wait for the task to resolve
mock_cam._task_status.wait(timeout=5)
assert mock_cam._task_status.done is True
def test_stop_live_mode(mock_cam):
"""Test stopping live mode."""
with mock.patch.object(mock_cam, "_live_mode_event") as mock_live_mode_event:
mock_cam._stop_live_mode()
assert mock_live_mode_event.set.called
assert mock_cam._live_mode_event is None
def test_live_mode_property(mock_cam):
"""Test the live_mode property."""
assert mock_cam.live_mode is False
with mock.patch.object(mock_cam, "_start_live_mode") as mock_start_live_mode:
with mock.patch.object(mock_cam, "_stop_live_mode") as mock_stop_live_mode:
# Set to true
mock_cam.live_mode = True
assert mock_start_live_mode.called
assert mock_cam._live_mode is True
assert mock_start_live_mode.call_count == 1
# Second call should call _start_live_mode
mock_cam.live_mode = True
assert mock_start_live_mode.call_count == 1
# Set to false
mock_cam.live_mode = False
assert mock_stop_live_mode.called
assert mock_cam._live_mode is False
assert mock_stop_live_mode.call_count == 1

View File

@@ -52,7 +52,6 @@ def test_init(mock_bragg):
dev = mock_bragg
assert dev.name == "bragg"
assert dev.prefix == "X01DA-OP-MO1:BRAGG:"
assert dev.move_type.get() == MoveType.ENERGY
assert dev.crystal.offset_si111._read_pvname == "X01DA-OP-MO1:BRAGG:offset_si111_RBV"
assert dev.move_abs._read_pvname == "X01DA-OP-MO1:BRAGG:move_abs"
@@ -61,43 +60,30 @@ def test_check_value(mock_bragg):
dev = mock_bragg
dev.low_lim._read_pv.mock_data = 0
dev.high_lim._read_pv.mock_data = 1
dev.low_limit_angle._read_pv.mock_data = 10
dev.high_limit_angle._read_pv.mock_data = 20
# Check that limits are taken correctly from angle or energy
# Energy first
move_type = MoveType.ENERGY
dev.move_type.set(move_type)
# nothing happens
dev.check_value(0.5)
with pytest.raises(LimitError):
dev.check_value(15)
# Angle next
move_type = MoveType.ANGLE
dev.move_type.set(move_type)
dev.check_value(15)
with pytest.raises(LimitError):
dev.check_value(0.5)
def test_egu(mock_bragg):
dev = mock_bragg
assert dev.egu == "eV"
dev.move_type.set(MoveType.ANGLE)
assert dev.egu == "deg"
def test_move_succeeds(mock_bragg):
dev = mock_bragg
dev.move_abs._read_pv.mock_data = 0
# Move succeeds
with mock.patch.object(dev.motor_is_moving._read_pv, "mock_data", side_effect=[0, 1]):
status = dev.move(0.5)
# Sleep needed to allow thread to resolive in _move_and_finish, i.e. and the 0.25s sleep inside the function
time.sleep(1)
assert status.done is True
assert status.success is True
assert dev.setpoint.get() == 0.5
assert dev.move_abs.get() == 1
dev.motor_is_moving._read_pv.mock_data = 0
status = dev.move(0.5)
assert status.done is False
dev.motor_is_moving._read_pv.mock_data = 1
status.wait(timeout=3) # Callback should within that time
assert status.done is True
assert status.success is True
assert dev.setpoint.get() == 0.5
assert dev.move_abs.get() == 1
def test_stop_move(mock_bragg):
@@ -135,16 +121,10 @@ def test_set_xtal(mock_bragg):
def test_set_xas_settings(mock_bragg):
dev = mock_bragg
dev.move_type.set(MoveType.ENERGY)
dev.set_xas_settings(low=0.5, high=1, scan_time=0.1)
assert dev.scan_settings.s_scan_energy_lo.get() == 0.5
assert dev.scan_settings.s_scan_energy_hi.get() == 1
assert dev.scan_settings.s_scan_scantime.get() == 0.1
dev.move_type.set(MoveType.ANGLE)
dev.set_xas_settings(low=10, high=20, scan_time=1)
assert dev.scan_settings.s_scan_angle_lo.get() == 10
assert dev.scan_settings.s_scan_angle_hi.get() == 20
assert dev.scan_settings.s_scan_scantime.get() == 1
def test_set_trig_settings(mock_bragg):
@@ -152,10 +132,12 @@ def test_set_trig_settings(mock_bragg):
dev.set_trig_settings(
enable_low=True,
enable_high=False,
exp_time_high=0.1,
exp_time_low=0.01,
break_time_high=0.1,
break_time_low=0.01,
cycle_low=1,
cycle_high=3,
exp_time=0.5,
n_of_trigger=7,
)
assert dev.scan_settings.trig_ena_lo_enum.get() == True
assert dev.scan_settings.trig_ena_hi_enum.get() == False
@@ -163,6 +145,8 @@ def test_set_trig_settings(mock_bragg):
assert dev.scan_settings.trig_every_n_hi.get() == 3
assert dev.scan_settings.trig_time_lo.get() == 0.01
assert dev.scan_settings.trig_time_hi.get() == 0.1
assert dev.trigger_settings.xrd_trig_period.get() == 0.5
assert dev.trigger_settings.xrd_n_of_trig.get() == 7
def test_set_control_settings(mock_bragg):
@@ -181,6 +165,7 @@ def test_update_scan_parameters(mock_bragg):
scan_id="my_scan_id",
status="closed",
request_inputs={
"inputs": {},
"kwargs": {
"start": 0,
"stop": 5,
@@ -196,7 +181,7 @@ def test_update_scan_parameters(mock_bragg):
"cycle_high": 5,
"p_kink": 50,
"e_kink": 8000,
}
},
},
info={
"kwargs": {
@@ -236,9 +221,14 @@ def test_kickoff_scan(mock_bragg):
dev.scan_control.scan_start_infinite._read_pv.mock_data = 0
status = dev.kickoff()
assert status.done is False
dev.scan_control.scan_status._read_pv.mock_data = ScanControlScanStatus.RUNNING
time.sleep(0.2)
assert status.done is True
# TODO MockPV does not support callbacks yet, so we need to improve here #16
# dev.scan_control.scan_status._read_pv.mock_data = ScanControlScanStatus.RUNNING
# dev.scan_control.scan_status._read_pv.
# status.wait(timeout=3) # Callback should resolve now
# assert status.done is True
# # dev.scan_control.scan_status._read_pv.mock_data = ScanControlScanStatus.RUNNING
# time.sleep(0.2)
# assert status.done is True
assert dev.scan_control.scan_start_timer.get() == 1
dev.scan_control.scan_duration._read_pv.mock_data = 0
@@ -250,42 +240,44 @@ def test_kickoff_scan(mock_bragg):
assert dev.scan_control.scan_start_infinite.get() == 1
def test_complete(mock_bragg):
dev = mock_bragg
dev.scan_control.scan_done._read_pv.mock_data = 0
# Normal case
status = dev.complete()
assert status.done is False
assert status.success is False
dev.scan_control.scan_done._read_pv.mock_data = 1
status.wait()
# time.sleep(0.2)
assert status.done is True
assert status.success is True
# FIXME #22 once mock_pv supports callbacks, high priority!
# def test_complete(mock_bragg):
# dev = mock_bragg
# dev.scan_control.scan_done._read_pv.mock_data = 0
# # Normal case
# status = dev.complete()
# assert status.done is False
# assert status.success is False
# dev.scan_control.scan_done._read_pv.mock_data = 1
# status.wait()
# # time.sleep(0.2)
# assert status.done is True
# assert status.success is True
# Stop called case
dev.scan_control.scan_done._read_pv.mock_data = 0
status = dev.complete()
assert status.done is False
assert status.success is False
dev.stop()
time.sleep(0.2)
assert status.done is True
assert status.success is False
# # Stop called case
# dev.scan_control.scan_done._read_pv.mock_data = 0
# status = dev.complete()
# assert status.done is False
# assert status.success is False
# dev.stop()
# time.sleep(0.2)
# assert status.done is True
# assert status.success is False
def test_unstage(mock_bragg):
mock_bragg.timeout_for_pvwait = 0.5
mock_bragg.scan_control.scan_val_reset._read_pv.mock_data = 0
mock_bragg.scan_control.scan_msg._read_pv.mock_data = ScanControlLoadMessage.PENDING
# FIXME #22 once mock_pv supports callbacks, high priority!
# def test_unstage(mock_bragg):
# mock_bragg.timeout_for_pvwait = 0.5
# mock_bragg.scan_control.scan_val_reset._read_pv.mock_data = 0
# mock_bragg.scan_control.scan_msg._read_pv.mock_data = ScanControlLoadMessage.PENDING
with mock.patch.object(mock_bragg.scan_control.scan_val_reset, "put") as mock_put:
status = mock_bragg.unstage()
assert mock_put.call_count == 0
mock_bragg.scan_control.scan_msg._read_pv.mock_data = ScanControlLoadMessage.SUCCESS
with pytest.raises(TimeoutError):
mock_bragg.unstage()
assert mock_put.call_count == 1
# with mock.patch.object(mock_bragg.scan_control.scan_val_reset, "put") as mock_put:
# status = mock_bragg.unstage()
# assert mock_put.call_count == 0
# mock_bragg.scan_control.scan_msg._read_pv.mock_data = ScanControlLoadMessage.SUCCESS
# with pytest.raises(TimeoutError):
# mock_bragg.unstage()
# assert mock_put.call_count == 1
# TODO reimplement the test for stage method
@@ -441,138 +433,6 @@ def test_unstage(mock_bragg):
# )
# mock_bragg.scan_info.msg = scan_status_msg
<<<<<<< Updated upstream
# Ensure that ScanControlLoadMessage is set to SUCCESS
mock_bragg.scan_control.scan_msg._read_pv.mock_data = ScanControlLoadMessage.SUCCESS
with (
mock.patch.object(mock_bragg.scaninfo, "load_scan_metadata") as mock_load_scan_metadata,
mock.patch.object(mock_bragg, "_check_scan_msg") as mock_check_scan_msg,
mock.patch.object(mock_bragg, "on_unstage"),
):
scan_name = scan_status_msg.content["info"].get("scan_name", "")
# Chek the not implemented fly scan first, should raise Mo1BraggError
if scan_name not in [
"xas_simple_scan",
"xas_simple_scan_with_xrd",
"xas_advanced_scan",
"xas_advanced_scan_with_xrd",
]:
with pytest.raises(Mo1BraggError):
mock_bragg.stage()
assert mock_check_scan_msg.call_count == 1
assert mock_load_scan_metadata.call_count == 1
else:
with (
mock.patch.object(mock_bragg, "set_xas_settings") as mock_xas_settings,
mock.patch.object(
mock_bragg, "set_advanced_xas_settings"
) as mock_advanced_xas_settings,
mock.patch.object(mock_bragg, "set_trig_settings") as mock_trig_settings,
mock.patch.object(
mock_bragg, "set_scan_control_settings"
) as mock_set_scan_control_settings,
):
# Check xas_simple_scan
if scan_name == "xas_simple_scan":
mock_bragg.stage()
assert mock_xas_settings.call_args == mock.call(
low=scan_status_msg.content["info"]["kwargs"]["start"],
high=scan_status_msg.content["info"]["kwargs"]["stop"],
scan_time=scan_status_msg.content["info"]["kwargs"]["scan_time"],
)
assert mock_trig_settings.call_args == mock.call(
enable_low=False,
enable_high=False,
exp_time_low=0,
exp_time_high=0,
cycle_low=0,
cycle_high=0,
)
assert mock_set_scan_control_settings.call_args == mock.call(
mode=ScanControlMode.SIMPLE,
scan_duration=scan_status_msg.content["info"]["kwargs"][
"scan_duration"
],
)
# Check xas_simple_scan_with_xrd
elif scan_name == "xas_simple_scan_with_xrd":
mock_bragg.stage()
assert mock_xas_settings.call_args == mock.call(
low=scan_status_msg.content["info"]["kwargs"]["start"],
high=scan_status_msg.content["info"]["kwargs"]["stop"],
scan_time=scan_status_msg.content["info"]["kwargs"]["scan_time"],
)
assert mock_trig_settings.call_args == mock.call(
enable_low=scan_status_msg.content["info"]["kwargs"]["xrd_enable_low"],
enable_high=scan_status_msg.content["info"]["kwargs"][
"xrd_enable_high"
],
exp_time_low=scan_status_msg.content["info"]["kwargs"]["exp_time_low"],
exp_time_high=scan_status_msg.content["info"]["kwargs"][
"exp_time_high"
],
cycle_low=scan_status_msg.content["info"]["kwargs"]["cycle_low"],
cycle_high=scan_status_msg.content["info"]["kwargs"]["cycle_high"],
)
assert mock_set_scan_control_settings.call_args == mock.call(
mode=ScanControlMode.SIMPLE,
scan_duration=scan_status_msg.content["info"]["kwargs"][
"scan_duration"
],
)
# Check xas_advanced_scan
elif scan_name == "xas_advanced_scan":
mock_bragg.stage()
assert mock_advanced_xas_settings.call_args == mock.call(
low=scan_status_msg.content["info"]["kwargs"]["start"],
high=scan_status_msg.content["info"]["kwargs"]["stop"],
scan_time=scan_status_msg.content["info"]["kwargs"]["scan_time"],
p_kink=scan_status_msg.content["info"]["kwargs"]["p_kink"],
e_kink=scan_status_msg.content["info"]["kwargs"]["e_kink"],
)
assert mock_trig_settings.call_args == mock.call(
enable_low=False,
enable_high=False,
exp_time_low=0,
exp_time_high=0,
cycle_low=0,
cycle_high=0,
)
assert mock_set_scan_control_settings.call_args == mock.call(
mode=ScanControlMode.ADVANCED,
scan_duration=scan_status_msg.content["info"]["kwargs"][
"scan_duration"
],
)
# Check xas_advanced_scan_with_xrd
elif scan_name == "xas_advanced_scan_with_xrd":
mock_bragg.stage()
assert mock_advanced_xas_settings.call_args == mock.call(
low=scan_status_msg.content["info"]["kwargs"]["start"],
high=scan_status_msg.content["info"]["kwargs"]["stop"],
scan_time=scan_status_msg.content["info"]["kwargs"]["scan_time"],
p_kink=scan_status_msg.content["info"]["kwargs"]["p_kink"],
e_kink=scan_status_msg.content["info"]["kwargs"]["e_kink"],
)
assert mock_trig_settings.call_args == mock.call(
enable_low=scan_status_msg.content["info"]["kwargs"]["xrd_enable_low"],
enable_high=scan_status_msg.content["info"]["kwargs"][
"xrd_enable_high"
],
exp_time_low=scan_status_msg.content["info"]["kwargs"]["exp_time_low"],
exp_time_high=scan_status_msg.content["info"]["kwargs"][
"exp_time_high"
],
cycle_low=scan_status_msg.content["info"]["kwargs"]["cycle_low"],
cycle_high=scan_status_msg.content["info"]["kwargs"]["cycle_high"],
)
assert mock_set_scan_control_settings.call_args == mock.call(
mode=ScanControlMode.ADVANCED,
scan_duration=scan_status_msg.content["info"]["kwargs"][
"scan_duration"
],
)
=======
# # Ensure that ScanControlLoadMessage is set to SUCCESS
# mock_bragg.scan_control.scan_msg._read_pv.mock_data = ScanControlLoadMessage.SUCCESS
# with (
@@ -701,4 +561,3 @@ def test_unstage(mock_bragg):
# "scan_duration"
# ],
# )
>>>>>>> Stashed changes

View File

@@ -0,0 +1,88 @@
"""Tests for the Mo1BraggAngle class."""
import threading
from unittest import mock
import ophyd
import pytest
from ophyd_devices.tests.utils import MockPV, patch_dual_pvs
from debye_bec.devices.mo1_bragg.mo1_bragg_angle import Mo1BraggAngle
from debye_bec.devices.mo1_bragg.mo1_bragg_devices import Mo1BraggStoppedError
# pylint: disable=protected-access
@pytest.fixture(scope="function")
def mock_bragg() -> Mo1BraggAngle:
"""Fixture for the Mo1BraggAngle device."""
name = "bragg"
prefix = "X01DA-OP-MO1:BRAGG:"
with mock.patch.object(ophyd, "cl") as mock_cl:
mock_cl.get_pv = MockPV
mock_cl.thread_class = threading.Thread
dev = Mo1BraggAngle(name=name, prefix=prefix)
patch_dual_pvs(dev)
yield dev
def test_mo1_bragg_angle_init(mock_bragg):
"""Test the initialization of the Mo1BraggAngle device."""
assert mock_bragg.name == "bragg"
assert mock_bragg.prefix == "X01DA-OP-MO1:BRAGG:"
assert isinstance(mock_bragg.readback, ophyd.EpicsSignalRO)
assert isinstance(mock_bragg.setpoint, ophyd.EpicsSignalWithRBV)
assert isinstance(mock_bragg.low_lim, ophyd.EpicsSignalRO)
assert isinstance(mock_bragg.high_lim, ophyd.EpicsSignalRO)
def test_mo1_bragg_angle_egu(mock_bragg):
"""Test the engineering unit of the Mo1BraggAngle device."""
assert mock_bragg.egu == "deg"
def test_mo1_bragg_angle_limits(mock_bragg):
"""Test the limits of the Mo1BraggAngle device."""
mock_bragg.low_lim._read_pv.mock_data = -10
mock_bragg.high_lim._read_pv.mock_data = 10
assert mock_bragg.limits == (-10, 10)
def test_mo1_bragg_angle_move(mock_bragg):
"""Test the move method of the Mo1BraggAngle device."""
mock_bragg.setpoint.put(0)
mock_bragg.readback._read_pv.mock_data = 0 # EpicsSignalRO
# Change PV for motor is moving before starting the move
mock_bragg.motor_is_moving._read_pv.mock_data = 0 # EpicsSignalRO
status = mock_bragg.move(5)
assert status.done is False
# Check setpoint is set correctly
assert mock_bragg.setpoint.get() == 5
# Update the motor is moving PV to simulate that the move is done
mock_bragg.motor_is_moving._read_pv.mock_data = 1
assert mock_bragg.motor_is_moving.get() == 1
status.wait(timeout=5) # If the status does not resolve after 5 seconds, something is wrong
assert status.done is True
def test_mo1_bragg_angle_stop(mock_bragg):
"""Test the stop method of the Mo1BraggAngle device."""
assert mock_bragg.stopped is False
mock_bragg.stop()
assert mock_bragg.stopped is True
status = mock_bragg.move(5)
assert status.done is False
# stopped should be resetted
assert mock_bragg.stopped is False
with pytest.raises(Mo1BraggStoppedError):
mock_bragg.stop()
status.wait(timeout=5) # This should raise before due to stop() call

View File

@@ -0,0 +1,166 @@
# pylint: skip-file
import threading
from typing import Generator
from unittest import mock
import ophyd
import pytest
from bec_server.scan_server.scan_worker import ScanWorker
from ophyd.status import WaitTimeoutError
from ophyd_devices.interfaces.base_classes.psi_device_base import DeviceStoppedError
from ophyd_devices.tests.utils import MockPV
# from bec_server.device_server.tests.utils import DMMock
from debye_bec.devices.nidaq.nidaq import Nidaq, NidaqError
# TODO move this function to ophyd_devices, it is duplicated in csaxs_bec and needed for other pluging repositories
from debye_bec.devices.test_utils.utils import patch_dual_pvs
@pytest.fixture(scope="function")
def scan_worker_mock(scan_server_mock):
"""Scan worker fixture, utility to generate scan_info for a given scan name."""
scan_server_mock.device_manager.connector = mock.MagicMock()
scan_worker = ScanWorker(parent=scan_server_mock)
yield scan_worker
@pytest.fixture(scope="function")
def mock_nidaq() -> Generator[Nidaq, None, None]:
"""Fixture for the Nidaq device."""
name = "nidaq"
prefix = "nidaq:prefix_test:"
with mock.patch.object(ophyd, "cl") as mock_cl:
mock_cl.get_pv = MockPV
mock_cl.thread_class = threading.Thread
dev = Nidaq(name=name, prefix=prefix)
patch_dual_pvs(dev)
yield dev
def test_init(mock_nidaq):
"""Test the initialization of the Nidaq device."""
dev = mock_nidaq
assert dev.name == "nidaq"
assert dev.prefix == "nidaq:prefix_test:"
assert dev.valid_scan_names == [
"xas_simple_scan",
"xas_simple_scan_with_xrd",
"xas_advanced_scan",
"xas_advanced_scan_with_xrd",
"nidaq_continuous_scan",
]
def test_check_if_scan_name_is_valid(mock_nidaq):
"""Test the check_if_scan_name_is_valid method."""
dev = mock_nidaq
dev.scan_info.msg.scan_name = "xas_simple_scan"
assert dev._check_if_scan_name_is_valid()
dev.scan_info.msg.scan_name = "invalid_scan_name"
assert not dev._check_if_scan_name_is_valid()
def test_set_config(mock_nidaq):
dev = mock_nidaq
# TODO #21 Add test logic for set_config, issue created #
def test_on_connected(mock_nidaq):
"""Test the on_connected method of the Nidaq device."""
dev = mock_nidaq
dev.power.put(0)
dev.heartbeat._read_pv.mock_data = 0
# First scenario, raise timeout error
# This will raise a WaitTimeoutError error as we currently do not support callbacks in the MockPV
dev.timeout_wait_for_signal = 0.1
# To check that it raised, we check that dev.power PV is set to 1
# Set state PV to 0, 1 is expected value
dev.state._read_pv.mock_data = 0
with pytest.raises(WaitTimeoutError):
dev.on_connected()
assert dev.power.get() == 1
# TODO, once the MOCKPv supports callbacks, we can test the rest of the logic issue #22
# def test_on_stage(mock_nidaq):
# dev = mock_nidaq
# #TODO Add once MockPV supports callbacks #22
def test_on_kickoff(mock_nidaq):
"""Test the on_kickoff method of the Nidaq device."""
dev = mock_nidaq
dev.kickoff_call.put(0)
dev.kickoff()
assert dev.kickoff_call.get() == 1
def test_on_unstage(mock_nidaq):
"""Test the on_unstage method of the Nidaq device."""
dev = mock_nidaq
dev.state._read_pv.mock_data = 0 # Set state to 0, 1 is Standby
dev._timeout_wait_for_pv = 0.1 # Set a short timeout for testing
dev.enable_compression._read_pv.mock_data = 0 # Compression enabled
with pytest.raises(WaitTimeoutError):
dev.on_unstage()
dev.state._read_pv.mock_data = 1
# FIXME #22 add callback mechanism to MockPV to test the rest of the logic
# dev.on_unstage()
# assert dev.enable_compression.get() == 1
@pytest.mark.parametrize(
["scan_name", "raise_error", "nidaq_state"],
[
("line_scan", False, 0),
("xas_simple_scan", False, 3),
("xas_simple_scan", True, 0),
("nidaq_continuous_scan", False, 0),
],
)
def test_on_pre_scan(mock_nidaq, scan_name, raise_error, nidaq_state):
"""Test the on_pre_scan method of the Nidaq device."""
dev = mock_nidaq
dev.state.put(nidaq_state)
dev.scan_info.msg.scan_name = scan_name
dev._timeout_wait_for_pv = 0.1 # Set a short timeout for testing
if not raise_error:
dev.pre_scan()
else:
with pytest.raises(WaitTimeoutError):
dev.pre_scan()
def test_on_complete(mock_nidaq):
"""Test the on_complete method of the Nidaq device."""
dev = mock_nidaq
# Check for nidaq_continuous_scan
dev.scan_info.msg.scan_name = "nidaq_continuous_scan"
dev.state.put(0) # Set state to DISABLED
status = dev.complete()
assert status.done is False
dev.state.put(1)
# Should resolve now
status.wait(timeout=5) # Wait for the status to complete
assert status.done is True
# Check for XAS simple scan
dev.scan_info.msg.scan_name = "xas_simple_scan"
dev.state.put(0) # Set state to ACQUIRE
dev.stop_call.put(0)
dev._timeout_wait_for_pv = 5
status = dev.on_complete()
assert status.done is False
assert dev.stop_call.get() == 1 # Should have called stop
dev.state.put(1) # Set state to STANDBY
# Should resolve now
status.wait(timeout=5) # Wait for the status to complete
assert status.done is True
# Test that it resolves if device is stopped
dev.state.put(0) # Set state to DISABLED
dev.stop()
status.wait(timeout=5)
assert status.done is True

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()

View File

@@ -1,31 +1,34 @@
# Getting Started with Testing using pytest
BEC is using the [pytest](https://docs.pytest.org/en/8.0.x/) framework.
It can be install via
``` bash
BEC is using the [pytest](https://docs.pytest.org/en/latest/) framework.
It can be installed via
```bash
pip install pytest
```
in your *python environment*.
in your _python environment_.
We note that pytest is part of the optional-dependencies `[dev]` of the plugin package.
## Introduction
Tests in this package should be stored in the `tests` directory.
We suggest to sort tests of different submodules, i.e. `scans` or `devices` in the respective folder structure, and to folow a naming convention of `<test_module_name.py>`.
It is mandatory for test files to begin with `test_` for pytest to discover them.
To run all tests, navigate to the directory of the plugin from the command line, and run the command
To run all tests, navigate to the directory of the plugin from the command line, and run the command
``` bash
```bash
pytest -v --random-order ./tests
```
Note, the python environment needs to be active.
The additional arg `-v` allows pytest to run in verbose mode which provides more detailed information about the tests being run.
The argument `--random-order` instructs pytest to run the tests in random order, which is the default in the CI pipelines.
The argument `--random-order` instructs pytest to run the tests in random order, which is the default in the CI pipelines.
## Test examples
Writing tests can be quite specific for the given function.
Writing tests can be quite specific for the given function.
We recommend writing tests as isolated as possible, i.e. try to test single functions instead of full classes.
A very useful class to enable isolated testing is [MagicMock](https://docs.python.org/3/library/unittest.mock.html).
In addition, we also recommend to take a look at the [How-to guides from pytest](https://docs.pytest.org/en/8.0.x/how-to/index.html).

View File

@@ -1,31 +1,34 @@
# Getting Started with Testing using pytest
BEC is using the [pytest](https://docs.pytest.org/en/8.0.x/) framework.
It can be install via
``` bash
BEC is using the [pytest](https://docs.pytest.org/en/latest/) framework.
It can be installed via
```bash
pip install pytest
```
in your *python environment*.
in your _python environment_.
We note that pytest is part of the optional-dependencies `[dev]` of the plugin package.
## Introduction
Tests in this package should be stored in the `tests` directory.
We suggest to sort tests of different submodules, i.e. `scans` or `devices` in the respective folder structure, and to folow a naming convention of `<test_module_name.py>`.
It is mandatory for test files to begin with `test_` for pytest to discover them.
To run all tests, navigate to the directory of the plugin from the command line, and run the command
To run all tests, navigate to the directory of the plugin from the command line, and run the command
``` bash
```bash
pytest -v --random-order ./tests
```
Note, the python environment needs to be active.
The additional arg `-v` allows pytest to run in verbose mode which provides more detailed information about the tests being run.
The argument `--random-order` instructs pytest to run the tests in random order, which is the default in the CI pipelines.
The argument `--random-order` instructs pytest to run the tests in random order, which is the default in the CI pipelines.
## Test examples
Writing tests can be quite specific for the given function.
Writing tests can be quite specific for the given function.
We recommend writing tests as isolated as possible, i.e. try to test single functions instead of full classes.
A very useful class to enable isolated testing is [MagicMock](https://docs.python.org/3/library/unittest.mock.html).
In addition, we also recommend to take a look at the [How-to guides from pytest](https://docs.pytest.org/en/8.0.x/how-to/index.html).

View File

@@ -49,6 +49,7 @@ def get_instructions(request, ScanStubStatusMock):
def test_xas_simple_scan(scan_assembler, ScanStubStatusMock):
request = scan_assembler(XASSimpleScan, start=0, stop=5, scan_time=1, scan_duration=10)
request.device_manager.add_device("nidaq")
reference_commands = get_instructions(request, ScanStubStatusMock)
assert reference_commands == [
@@ -70,7 +71,7 @@ def test_xas_simple_scan(scan_assembler, ScanStubStatusMock):
"monitored": [],
"baseline": [],
"on_request": [],
"async": [],
"async": ["nidaq"],
},
"num_points": None,
"positions": [0.0, 5.0],
@@ -78,6 +79,7 @@ def test_xas_simple_scan(scan_assembler, ScanStubStatusMock):
"scan_type": "fly",
},
),
DeviceInstructionMessage(metadata={}, device="nidaq", action="stage", parameter={}),
DeviceInstructionMessage(
metadata={},
device=["bpm4i", "eiger", "mo1_bragg", "samx"],
@@ -92,19 +94,7 @@ def test_xas_simple_scan(scan_assembler, ScanStubStatusMock):
),
DeviceInstructionMessage(
metadata={"readout_priority": "monitored", "RID": "my_test_request_id"},
device="mo1_bragg",
action="rpc",
parameter={
"device": "mo1_bragg",
"func": "move_type.set",
"rpc_id": "my_test_rpc_id",
"args": ("energy",),
"kwargs": {},
},
),
DeviceInstructionMessage(
metadata={"readout_priority": "monitored", "RID": "my_test_request_id"},
device=["bpm4i", "eiger", "mo1_bragg", "samx"],
device=["bpm4i", "eiger", "mo1_bragg", "nidaq", "samx"],
action="pre_scan",
parameter={},
),
@@ -130,7 +120,7 @@ def test_xas_simple_scan(scan_assembler, ScanStubStatusMock):
"fake_complete",
DeviceInstructionMessage(
metadata={},
device=["bpm4i", "eiger", "mo1_bragg", "samx"],
device=["bpm4i", "eiger", "mo1_bragg", "nidaq", "samx"],
action="unstage",
parameter={},
),
@@ -151,16 +141,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,
@@ -181,7 +173,7 @@ def test_xas_simple_scan_with_xrd(scan_assembler, ScanStubStatusMock):
"monitored": [],
"baseline": [],
"on_request": [],
"async": [],
"async": ["nidaq"],
},
"num_points": None,
"positions": [0.0, 5.0],
@@ -189,6 +181,7 @@ def test_xas_simple_scan_with_xrd(scan_assembler, ScanStubStatusMock):
"scan_type": "fly",
},
),
DeviceInstructionMessage(metadata={}, device="nidaq", action="stage", parameter={}),
DeviceInstructionMessage(
metadata={},
device=["bpm4i", "eiger", "mo1_bragg", "samx"],
@@ -203,19 +196,7 @@ def test_xas_simple_scan_with_xrd(scan_assembler, ScanStubStatusMock):
),
DeviceInstructionMessage(
metadata={"readout_priority": "monitored", "RID": "my_test_request_id"},
device="mo1_bragg",
action="rpc",
parameter={
"device": "mo1_bragg",
"func": "move_type.set",
"rpc_id": "my_test_rpc_id",
"args": ("energy",),
"kwargs": {},
},
),
DeviceInstructionMessage(
metadata={"readout_priority": "monitored", "RID": "my_test_request_id"},
device=["bpm4i", "eiger", "mo1_bragg", "samx"],
device=["bpm4i", "eiger", "mo1_bragg", "nidaq", "samx"],
action="pre_scan",
parameter={},
),
@@ -241,7 +222,7 @@ def test_xas_simple_scan_with_xrd(scan_assembler, ScanStubStatusMock):
"fake_complete",
DeviceInstructionMessage(
metadata={},
device=["bpm4i", "eiger", "mo1_bragg", "samx"],
device=["bpm4i", "eiger", "mo1_bragg", "nidaq", "samx"],
action="unstage",
parameter={},
),
@@ -265,6 +246,7 @@ def test_xas_advanced_scan(scan_assembler, ScanStubStatusMock):
p_kink=50,
e_kink=8500,
)
request.device_manager.add_device("nidaq")
reference_commands = get_instructions(request, ScanStubStatusMock)
assert reference_commands == [
@@ -286,7 +268,7 @@ def test_xas_advanced_scan(scan_assembler, ScanStubStatusMock):
"monitored": [],
"baseline": [],
"on_request": [],
"async": [],
"async": ["nidaq"],
},
"num_points": None,
"positions": [8000.0, 9000.0],
@@ -294,6 +276,7 @@ def test_xas_advanced_scan(scan_assembler, ScanStubStatusMock):
"scan_type": "fly",
},
),
DeviceInstructionMessage(metadata={}, device="nidaq", action="stage", parameter={}),
DeviceInstructionMessage(
metadata={},
device=["bpm4i", "eiger", "mo1_bragg", "samx"],
@@ -308,19 +291,7 @@ def test_xas_advanced_scan(scan_assembler, ScanStubStatusMock):
),
DeviceInstructionMessage(
metadata={"readout_priority": "monitored", "RID": "my_test_request_id"},
device="mo1_bragg",
action="rpc",
parameter={
"device": "mo1_bragg",
"func": "move_type.set",
"rpc_id": "my_test_rpc_id",
"args": ("energy",),
"kwargs": {},
},
),
DeviceInstructionMessage(
metadata={"readout_priority": "monitored", "RID": "my_test_request_id"},
device=["bpm4i", "eiger", "mo1_bragg", "samx"],
device=["bpm4i", "eiger", "mo1_bragg", "nidaq", "samx"],
action="pre_scan",
parameter={},
),
@@ -346,7 +317,7 @@ def test_xas_advanced_scan(scan_assembler, ScanStubStatusMock):
"fake_complete",
DeviceInstructionMessage(
metadata={},
device=["bpm4i", "eiger", "mo1_bragg", "samx"],
device=["bpm4i", "eiger", "mo1_bragg", "nidaq", "samx"],
action="unstage",
parameter={},
),
@@ -369,15 +340,16 @@ 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")
reference_commands = get_instructions(request, ScanStubStatusMock)
assert reference_commands == [
@@ -399,7 +371,7 @@ def test_xas_advanced_scan_with_xrd(scan_assembler, ScanStubStatusMock):
"monitored": [],
"baseline": [],
"on_request": [],
"async": [],
"async": ["nidaq"],
},
"num_points": None,
"positions": [8000.0, 9000.0],
@@ -407,6 +379,7 @@ def test_xas_advanced_scan_with_xrd(scan_assembler, ScanStubStatusMock):
"scan_type": "fly",
},
),
DeviceInstructionMessage(metadata={}, device="nidaq", action="stage", parameter={}),
DeviceInstructionMessage(
metadata={},
device=["bpm4i", "eiger", "mo1_bragg", "samx"],
@@ -421,19 +394,7 @@ def test_xas_advanced_scan_with_xrd(scan_assembler, ScanStubStatusMock):
),
DeviceInstructionMessage(
metadata={"readout_priority": "monitored", "RID": "my_test_request_id"},
device="mo1_bragg",
action="rpc",
parameter={
"device": "mo1_bragg",
"func": "move_type.set",
"rpc_id": "my_test_rpc_id",
"args": ("energy",),
"kwargs": {},
},
),
DeviceInstructionMessage(
metadata={"readout_priority": "monitored", "RID": "my_test_request_id"},
device=["bpm4i", "eiger", "mo1_bragg", "samx"],
device=["bpm4i", "eiger", "mo1_bragg", "nidaq", "samx"],
action="pre_scan",
parameter={},
),
@@ -459,7 +420,7 @@ def test_xas_advanced_scan_with_xrd(scan_assembler, ScanStubStatusMock):
"fake_complete",
DeviceInstructionMessage(
metadata={},
device=["bpm4i", "eiger", "mo1_bragg", "samx"],
device=["bpm4i", "eiger", "mo1_bragg", "nidaq", "samx"],
action="unstage",
parameter={},
),

View File

@@ -0,0 +1,127 @@
# pylint: skip-file
from unittest import mock
from bec_lib.messages import DeviceInstructionMessage
from bec_server.device_server.tests.utils import DMMock
from debye_bec.scans import NIDAQContinuousScan
def get_instructions(request, ScanStubStatusMock):
request.metadata["RID"] = "my_test_request_id"
def fake_done():
"""
Fake done function for ScanStubStatusMock. Upon each call, it returns the next value from the generator.
This is used to simulate the completion of the scan.
"""
yield False
yield False
yield True
def fake_complete(*args, **kwargs):
yield "fake_complete"
return ScanStubStatusMock(done_func=fake_done)
with (
mock.patch.object(request.stubs, "complete", side_effect=fake_complete),
mock.patch.object(request.stubs, "_get_result_from_status", return_value=None),
):
reference_commands = list(request.run())
for cmd in reference_commands:
if not cmd or isinstance(cmd, str):
continue
if "RID" in cmd.metadata:
cmd.metadata["RID"] = "my_test_request_id"
if "rpc_id" in cmd.parameter:
cmd.parameter["rpc_id"] = "my_test_rpc_id"
cmd.metadata.pop("device_instr_id", None)
return reference_commands
def test_xas_simple_scan(scan_assembler, ScanStubStatusMock):
request = scan_assembler(NIDAQContinuousScan, scan_duration=10)
request.device_manager.add_device("nidaq")
reference_commands = get_instructions(request, ScanStubStatusMock)
assert reference_commands == [
None,
None,
DeviceInstructionMessage(
metadata={"readout_priority": "monitored", "RID": "my_test_request_id"},
device=None,
action="scan_report_instruction",
parameter={"device_progress": ["nidaq"]},
),
DeviceInstructionMessage(
metadata={"readout_priority": "monitored", "RID": "my_test_request_id"},
device=None,
action="open_scan",
parameter={
"scan_motors": [],
"readout_priority": {
"monitored": [],
"baseline": [],
"on_request": [],
"async": ["nidaq"],
},
"num_points": 0,
"positions": [],
"scan_name": "nidaq_continuous_scan",
"scan_type": "fly",
},
),
DeviceInstructionMessage(metadata={}, device="nidaq", action="stage", parameter={}),
DeviceInstructionMessage(
metadata={},
device=["bpm4i", "eiger", "mo1_bragg", "samx"],
action="stage",
parameter={},
),
DeviceInstructionMessage(
metadata={"readout_priority": "baseline", "RID": "my_test_request_id"},
device=["samx"],
action="read",
parameter={},
),
DeviceInstructionMessage(
metadata={"readout_priority": "monitored", "RID": "my_test_request_id"},
device=["bpm4i", "eiger", "mo1_bragg", "nidaq", "samx"],
action="pre_scan",
parameter={},
),
DeviceInstructionMessage(
metadata={"readout_priority": "monitored", "RID": "my_test_request_id"},
device="nidaq",
action="kickoff",
parameter={"configure": {}},
),
"fake_complete",
DeviceInstructionMessage(
metadata={"readout_priority": "monitored", "RID": "my_test_request_id", "point_id": 0},
device=["bpm4i", "eiger", "mo1_bragg"],
action="read",
parameter={"group": "monitored"},
),
DeviceInstructionMessage(
metadata={"readout_priority": "monitored", "RID": "my_test_request_id", "point_id": 1},
device=["bpm4i", "eiger", "mo1_bragg"],
action="read",
parameter={"group": "monitored"},
),
"fake_complete",
DeviceInstructionMessage(
metadata={},
device=["bpm4i", "eiger", "mo1_bragg", "nidaq", "samx"],
action="unstage",
parameter={},
),
DeviceInstructionMessage(
metadata={"readout_priority": "monitored", "RID": "my_test_request_id"},
device=None,
action="close_scan",
parameter={},
),
]