Compare commits
127 Commits
Debye_AD
...
fix_status
| Author | SHA1 | Date | |
|---|---|---|---|
| c70088e7bc | |||
| 190eae2c3f | |||
| 1d6caa2291 | |||
| 9b739c852d | |||
| 9d9a2e9681 | |||
|
|
ed759da14f | ||
|
|
158175f545 | ||
|
|
ed9148ed96 | ||
|
|
0a83b59af8 | ||
|
|
a67394a9a2 | ||
|
|
a6f0d01558 | ||
|
|
217a14d03d | ||
|
|
4424f83b8b | ||
|
|
626b0dc8a0 | ||
|
|
1f7fdb89d7 | ||
|
|
ee748d56c4 | ||
|
|
02e6462ea1 | ||
| 2633c8be0a | |||
| 09c3e395de | |||
| 8e5bdd230d | |||
|
|
dd0fe31cb7 | ||
| 442c421d05 | |||
| 85042a7f45 | |||
| 6a1992f605 | |||
| aaf4084517 | |||
|
|
34dbc1839d | ||
|
|
db6a9a502f | ||
|
|
e6586ceab2 | ||
| ed6d64c7f9 | |||
| 43e8aea6c8 | |||
| abf432f2a9 | |||
|
|
b3672cf5f5 | ||
|
|
fa434794c3 | ||
|
|
9be74da098 | ||
|
|
29913cea61 | ||
|
|
881bc9e7a3 | ||
| e941647750 | |||
| 827557b667 | |||
|
|
a9fd62d249 | ||
| 27ff5697af | |||
| 07d05f9490 | |||
|
|
39adeb72de | ||
|
|
bc666dc807 | ||
|
|
89cc27a8da | ||
|
|
718a001a8a | ||
|
|
f038679d76 | ||
|
|
a1433efbf8 | ||
|
|
79ead32e79 | ||
|
|
c934aa8e9a | ||
| 0d87e958d0 | |||
| e4556ad90e | |||
|
|
da89f9287c | ||
| 665c290a90 | |||
|
|
10b0608d31 | ||
| ca2cf40d6a | |||
| 415c601d2a | |||
|
|
0c1f41cd7c | ||
|
|
0cdad97d00 | ||
| b3f63f4f76 | |||
| 87ea95e975 | |||
| 4d9a062b8c | |||
|
|
c782324065 | ||
|
|
5bb0df2ddf | ||
|
|
20759e7ff1 | ||
| b03b90a86a | |||
| 74e0b01b02 | |||
|
|
7b7a24b6c8 | ||
|
|
31ff28236b | ||
|
|
002a3323a0 | ||
|
|
24d81bb180 | ||
|
|
32e24cd92a | ||
| 03e3b1c605 | |||
| 6ab1a2941c | |||
| b8a050c424 | |||
|
|
a8e7325f0f | ||
|
|
ae50cbdfd1 | ||
|
|
c164414631 | ||
|
|
c074240444 | ||
|
|
759636cf2c | ||
|
|
510073d2f2 | ||
|
|
17b671dd4b | ||
|
|
fe3e8b6291 | ||
|
|
b7f72f8e44 | ||
|
|
8cad42920b | ||
|
|
c43ca4aaa8 | ||
| ce046f55f6 | |||
|
|
849b2d2bdb | ||
| d3ab7316ac | |||
|
|
01a17cbe3a | ||
|
|
edcf00a55c | ||
|
|
81bca16f67 | ||
| cddc231d53 | |||
|
|
6999837d6b | ||
|
|
7c5bb1e963 | ||
|
|
2a0b1d7453 | ||
|
|
d58553f9e7 | ||
| fae7b93805 | |||
|
|
7f07f4a3dd | ||
|
|
87ab69b335 | ||
|
|
3e36274f55 | ||
|
|
fe6040cd91 | ||
|
|
6eabb4cb3a | ||
|
|
79e5e158c1 | ||
|
|
dc3e0685d8 | ||
|
|
8e1d0b8536 | ||
| 1a193a39ca | |||
| 2ad35be182 | |||
|
|
e49fc6af41 | ||
|
|
23dd8c11e0 | ||
|
|
6d43a08bd5 | ||
|
|
9b9db93677 | ||
|
|
985a4228de | ||
| a55f8bf705 | |||
| d50519f3d3 | |||
| 7061aaf450 | |||
| 42ca7ed9a4 | |||
|
|
b85b8e6a2f | ||
|
|
ab70895fe6 | ||
|
|
cc51aefb57 | ||
|
|
676c3ba97e | ||
|
|
0fbc700f1b | ||
|
|
0062da5a6b | ||
|
|
31dd582648 | ||
|
|
38bee8c5c7 | ||
|
|
5b46464ef9 | ||
|
|
6d9f48c8dd | ||
| 58f6510d86 |
9
.copier-answers.yml
Normal file
9
.copier-answers.yml
Normal 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.2
|
||||||
|
_src_path: https://github.com/bec-project/plugin_copier_template.git
|
||||||
|
make_commit: false
|
||||||
|
project_name: debye_bec
|
||||||
|
widget_plugins_input: []
|
||||||
97
.gitea/workflows/ci.yml
Normal file
97
.gitea/workflows/ci.yml
Normal file
@@ -0,0 +1,97 @@
|
|||||||
|
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.11"
|
||||||
|
|
||||||
|
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.11' }}"
|
||||||
|
|
||||||
|
- 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: 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: 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
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
include:
|
include:
|
||||||
- project: bec/awi_utils
|
- file: /templates/plugin-repo-template.yml
|
||||||
file: /templates/plugin-repo-template.yml
|
inputs:
|
||||||
inputs:
|
name: debye_bec
|
||||||
name: "debye"
|
target: debye_bec
|
||||||
target: "debye_bec"
|
branch: $CHILD_PIPELINE_BRANCH
|
||||||
branch: $CHILD_PIPELINE_BRANCH
|
project: bec/awi_utils
|
||||||
|
|||||||
29
LICENSE
Normal file
29
LICENSE
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
|
||||||
|
BSD 3-Clause License
|
||||||
|
|
||||||
|
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:
|
||||||
|
|
||||||
|
1. Redistributions of source code must retain the above copyright notice, this
|
||||||
|
list of conditions and the following disclaimer.
|
||||||
|
|
||||||
|
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
this list of conditions and the following disclaimer in the documentation
|
||||||
|
and/or other materials provided with the distribution.
|
||||||
|
|
||||||
|
3. Neither the name of the copyright holder nor the names of its
|
||||||
|
contributors may be used to endorse or promote products derived from
|
||||||
|
this software without specific prior written permission.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
|
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||||
|
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||||
|
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.
|
||||||
64
README.md
64
README.md
@@ -1,3 +1,65 @@
|
|||||||
# Debye BEC
|
# Debye BEC
|
||||||
|
|
||||||
Debye-specific plugins and configs for BEC
|
Debye-specific plugins and configs for BEC
|
||||||
|
|
||||||
|
## How to
|
||||||
|
|
||||||
|
### Visual studio code
|
||||||
|
To open
|
||||||
|
```
|
||||||
|
ssh x01da-bec-001
|
||||||
|
cd /data/test/x01da-test-bec/bec_deployment
|
||||||
|
code
|
||||||
|
```
|
||||||
|
To run tests directly in vs code terminal
|
||||||
|
```
|
||||||
|
. /data/test/x01da-test-bec/bec_deployment/bec_venv/bin/activate
|
||||||
|
cd /data/test/x01da-test-bec/bec_deployment/debye_bec
|
||||||
|
pytest -vv ./tests
|
||||||
|
```
|
||||||
|
### Git
|
||||||
|
```
|
||||||
|
git pull
|
||||||
|
git push origin feat/add_advanced_scan_modes
|
||||||
|
git status
|
||||||
|
```
|
||||||
|
If git claims to not know the author identity
|
||||||
|
```
|
||||||
|
git config --global user.email "you@example.com"
|
||||||
|
git config --global user.name "gac-x01da"
|
||||||
|
```
|
||||||
|
|
||||||
|
### BEC Server
|
||||||
|
```
|
||||||
|
ssh x01da-bec-001
|
||||||
|
cd /data/test/x01da-test-bec/bec_deployment
|
||||||
|
. /data/test/x01da-test-bec/bec_deployment/bec_venv/bin/activate
|
||||||
|
|
||||||
|
bec-server start
|
||||||
|
bec-server restart
|
||||||
|
bec-server stop
|
||||||
|
bec-server attach
|
||||||
|
```
|
||||||
|
|
||||||
|
To restart individual server modules:
|
||||||
|
- ctrl-c + ctrl-c to stop for example scan server or device server module
|
||||||
|
- restart server module(s)
|
||||||
|
|
||||||
|
### BEC Client
|
||||||
|
```
|
||||||
|
ssh x01da-bec-001
|
||||||
|
cd /data/test/x01da-test-bec/bec_deployment
|
||||||
|
bec
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Useful commands in bec
|
||||||
|
Update Session with specific config:
|
||||||
|
```
|
||||||
|
bec.config.update_session_with_file("debye_bec/debye_bec/device_configs/x01da_test_config.yaml")
|
||||||
|
```
|
||||||
|
|
||||||
|
Define folder and sample name for written files:
|
||||||
|
```
|
||||||
|
bec.system_config.file_directory="test"
|
||||||
|
bec.system_config.file_suffix ="sampleA"
|
||||||
|
```
|
||||||
1
bin/.gitignore
vendored
Normal file
1
bin/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
# Add anything you don't want to check in to git, e.g. very large files
|
||||||
@@ -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
|
post-startup script can be used to load beamline specific information and
|
||||||
to setup the prompts.
|
to setup the prompts.
|
||||||
|
|
||||||
from bec_lib import bec_logger
|
from bec_lib.logger import bec_logger
|
||||||
|
|
||||||
logger = bec_logger.logger
|
logger = bec_logger.logger
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,14 @@
|
|||||||
"""
|
"""
|
||||||
Pre-startup script for BEC client. This script is executed before the BEC client
|
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
|
from bec_lib.service_config import ServiceConfig
|
||||||
|
|
||||||
|
import debye_bec
|
||||||
|
|
||||||
|
|
||||||
def extend_command_line_args(parser):
|
def extend_command_line_args(parser):
|
||||||
"""
|
"""
|
||||||
@@ -18,6 +22,11 @@ def extend_command_line_args(parser):
|
|||||||
|
|
||||||
def get_config() -> ServiceConfig:
|
def get_config() -> ServiceConfig:
|
||||||
"""
|
"""
|
||||||
Create and return the service configuration.
|
Create and return the ServiceConfig for the plugin repository
|
||||||
"""
|
"""
|
||||||
return ServiceConfig(redis={"host": "x01da-bec-001", "port": 6379})
|
deployment_path = os.path.dirname(os.path.dirname(os.path.dirname(debye_bec.__file__)))
|
||||||
|
files = os.listdir(deployment_path)
|
||||||
|
if "bec_config.yaml" in files:
|
||||||
|
return ServiceConfig(config_path=os.path.join(deployment_path, "bec_config.yaml"))
|
||||||
|
else:
|
||||||
|
return ServiceConfig(redis={"host": "localhost", "port": 6379})
|
||||||
|
|||||||
0
debye_bec/bec_widgets/auto_updates/__init__.py
Normal file
0
debye_bec/bec_widgets/auto_updates/__init__.py
Normal file
0
debye_bec/bec_widgets/widgets/__init__.py
Normal file
0
debye_bec/bec_widgets/widgets/__init__.py
Normal file
34
debye_bec/device_configs/x01da_beam_monitors.yaml
Normal file
34
debye_bec/device_configs/x01da_beam_monitors.yaml
Normal 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
|
||||||
@@ -1,692 +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.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
|
|
||||||
|
|
||||||
###############################################
|
|
||||||
## 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
|
|
||||||
|
|
||||||
|
|
||||||
#########################################
|
|
||||||
## 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
|
|
||||||
389
debye_bec/device_configs/x01da_experimental_hutch.yaml
Normal file
389
debye_bec/device_configs/x01da_experimental_hutch.yaml
Normal file
@@ -0,0 +1,389 @@
|
|||||||
|
###################################
|
||||||
|
## 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
|
||||||
218
debye_bec/device_configs/x01da_frontend.yaml
Normal file
218
debye_bec/device_configs/x01da_frontend.yaml
Normal 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
|
||||||
18
debye_bec/device_configs/x01da_machine.yaml
Normal file
18
debye_bec/device_configs/x01da_machine.yaml
Normal 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
|
||||||
395
debye_bec/device_configs/x01da_optics.yaml
Normal file
395
debye_bec/device_configs/x01da_optics.yaml
Normal 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
|
||||||
75
debye_bec/device_configs/x01da_standard_config.yaml
Normal file
75
debye_bec/device_configs/x01da_standard_config.yaml
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
|
||||||
|
###################################
|
||||||
|
## 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
|
||||||
|
|
||||||
|
## Remaining experimental hutch
|
||||||
|
es_config:
|
||||||
|
- !include ./x01da_experimental_hutch.yaml
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
## Bragg Monochromator
|
|
||||||
mo1_bragg:
|
|
||||||
readoutPriority: baseline
|
|
||||||
description: Positioner for the Monochromator
|
|
||||||
deviceClass: debye_bec.devices.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
|
|
||||||
73
debye_bec/device_configs/x01da_xas.yaml
Normal file
73
debye_bec/device_configs/x01da_xas.yaml
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
|
||||||
|
###################################
|
||||||
|
## 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
|
||||||
|
|
||||||
|
###################################
|
||||||
|
## Reference Foil Changer ##
|
||||||
|
###################################
|
||||||
|
|
||||||
|
reffoilchanger:
|
||||||
|
readoutPriority: baseline
|
||||||
|
description: ES2 reference foil changer
|
||||||
|
deviceClass: debye_bec.devices.reffoilchanger.Reffoilchanger
|
||||||
|
deviceConfig:
|
||||||
|
prefix: "X01DA-"
|
||||||
|
onFailure: retry
|
||||||
|
enabled: true
|
||||||
|
softwareTrigger: false
|
||||||
|
|
||||||
|
###################################
|
||||||
|
## SDD Sensors ##
|
||||||
|
###################################
|
||||||
|
|
||||||
|
sdd1_temperature:
|
||||||
|
readoutPriority: baseline
|
||||||
|
description: SDD1 temperature sensor
|
||||||
|
deviceClass: ophyd.EpicsSignalRO
|
||||||
|
deviceConfig:
|
||||||
|
read_pv: "X01DA-ES1-DET1:Temperature"
|
||||||
|
onFailure: retry
|
||||||
|
enabled: true
|
||||||
|
softwareTrigger: false
|
||||||
|
|
||||||
|
sdd1_humidity:
|
||||||
|
readoutPriority: baseline
|
||||||
|
description: SDD1 humidity sensor
|
||||||
|
deviceClass: ophyd.EpicsSignalRO
|
||||||
|
deviceConfig:
|
||||||
|
read_pv: "X01DA-ES1-DET1:Humidity"
|
||||||
|
kind: "config"
|
||||||
|
onFailure: retry
|
||||||
|
enabled: true
|
||||||
|
softwareTrigger: false
|
||||||
108
debye_bec/device_configs/x01da_xrd.yaml
Normal file
108
debye_bec/device_configs/x01da_xrd.yaml
Normal 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: async
|
||||||
|
description: Pilatus
|
||||||
|
deviceClass: debye_bec.devices.pilatus.pilatus.Pilatus
|
||||||
|
deviceTags:
|
||||||
|
- detector
|
||||||
|
deviceConfig:
|
||||||
|
prefix: "X01DA-ES2-PIL:"
|
||||||
|
onFailure: retry
|
||||||
|
enabled: true
|
||||||
|
softwareTrigger: true
|
||||||
|
|
||||||
|
# sampl_pil:
|
||||||
|
# readoutPriority: baseline
|
||||||
|
# description: Sample to pilatus distance
|
||||||
|
# deviceClass: ophyd.EpicsSignalRO
|
||||||
|
# deviceConfig:
|
||||||
|
# read_pv: "X01DA-SAMPL-PIL"
|
||||||
|
# onFailure: retry
|
||||||
|
# enabled: true
|
||||||
|
# softwareTrigger: false
|
||||||
0
debye_bec/devices/cameras/__init__.py
Normal file
0
debye_bec/devices/cameras/__init__.py
Normal file
36
debye_bec/devices/cameras/basler_cam.py
Normal file
36
debye_bec/devices/cameras/basler_cam.py
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
"""Basler camera class for Debye BEC."""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
|
from ophyd import ADBase
|
||||||
|
from ophyd import ADComponent as ADCpt
|
||||||
|
from ophyd import Component as Cpt
|
||||||
|
from ophyd_devices import PreviewSignal
|
||||||
|
from ophyd_devices.devices.areadetector.cam import AravisDetectorCam
|
||||||
|
from ophyd_devices.devices.areadetector.plugins import ImagePlugin_V35
|
||||||
|
|
||||||
|
from 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."""
|
||||||
|
|
||||||
|
cam1 = ADCpt(AravisDetectorCam, "cam1:")
|
||||||
|
image1 = ADCpt(ImagePlugin_V35, "image1:")
|
||||||
|
|
||||||
|
|
||||||
|
class BaslerCam(DebyeBaseCamera, BaslerCamBase):
|
||||||
|
"""Basler camera class at Debye. IOC prefix: X01DA-ES-XRAYEYE:"""
|
||||||
|
|
||||||
|
preview = Cpt(
|
||||||
|
PreviewSignal,
|
||||||
|
name="preview",
|
||||||
|
ndim=2,
|
||||||
|
num_rotation_90=3,
|
||||||
|
doc="Preview signal for the camera.",
|
||||||
|
)
|
||||||
138
debye_bec/devices/cameras/debye_base_cam.py
Normal file
138
debye_bec/devices/cameras/debye_base_cam.py
Normal 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."""
|
||||||
39
debye_bec/devices/cameras/prosilica_cam.py
Normal file
39
debye_bec/devices/cameras/prosilica_cam.py
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
"""Prosilica camera class for integration of beam_monitor 1/2 cameras."""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
|
from ophyd import ADBase
|
||||||
|
from ophyd import ADComponent as ADCpt
|
||||||
|
from ophyd import Component as Cpt
|
||||||
|
from ophyd_devices import PreviewSignal
|
||||||
|
from ophyd_devices.devices.areadetector.cam import ProsilicaDetectorCam
|
||||||
|
from ophyd_devices.devices.areadetector.plugins import ImagePlugin_V35
|
||||||
|
|
||||||
|
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."""
|
||||||
|
|
||||||
|
cam1 = ADCpt(ProsilicaDetectorCam, "cam1:")
|
||||||
|
image1 = ADCpt(ImagePlugin_V35, "image1:")
|
||||||
|
|
||||||
|
|
||||||
|
class ProsilicaCam(DebyeBaseCamera, ProsilicaCamBase):
|
||||||
|
"""
|
||||||
|
Prosilica camera class, for integration of beam_monitor 1/2 cameras.
|
||||||
|
Prefixes are: X01DA-OP-GIGE02: and X01DA-OP-GIGE01:
|
||||||
|
"""
|
||||||
|
|
||||||
|
preview = Cpt(
|
||||||
|
PreviewSignal,
|
||||||
|
name="preview",
|
||||||
|
ndim=2,
|
||||||
|
num_rotation_90=3,
|
||||||
|
doc="Preview signal for the camera.",
|
||||||
|
)
|
||||||
@@ -3,9 +3,28 @@
|
|||||||
### debye_bec
|
### debye_bec
|
||||||
| Device | Documentation | Module |
|
| Device | Documentation | Module |
|
||||||
| :----- | :------------- | :------ |
|
| :----- | :------------- | :------ |
|
||||||
| 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) |
|
| 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) |
|
||||||
| 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) |
|
| 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) |
|
||||||
| 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) |
|
| 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) |
|
||||||
| 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) |
|
| 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) |
|
||||||
| 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) |
|
| 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) |
|
||||||
| 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) |
|
| 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) |
|
||||||
|
|||||||
54
debye_bec/devices/es0filter.py
Normal file
54
debye_bec/devices/es0filter.py
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
"""ES0 Filter Station"""
|
||||||
|
|
||||||
|
from typing import Literal
|
||||||
|
|
||||||
|
from ophyd import Component as Cpt
|
||||||
|
from ophyd import Device, EpicsSignal, Kind
|
||||||
|
from ophyd_devices.utils import bec_utils
|
||||||
|
from typeguard import typechecked
|
||||||
|
|
||||||
|
|
||||||
|
class EpicsSignalWithRBVBit(EpicsSignal):
|
||||||
|
|
||||||
|
def __init__(self, prefix, *, bit: int, **kwargs):
|
||||||
|
super().__init__(prefix, **kwargs)
|
||||||
|
self.bit = bit
|
||||||
|
|
||||||
|
@typechecked
|
||||||
|
def put(self, value: Literal[0, 1], **kwargs):
|
||||||
|
bit_value = super().get()
|
||||||
|
# convert to int
|
||||||
|
bit_value = int(bit_value)
|
||||||
|
if value == 1:
|
||||||
|
new_value = bit_value | (1 << self.bit)
|
||||||
|
else:
|
||||||
|
new_value = bit_value & ~(1 << self.bit)
|
||||||
|
super().put(new_value, **kwargs)
|
||||||
|
|
||||||
|
def get(self, **kwargs) -> Literal[0, 1]:
|
||||||
|
bit_value = super().get()
|
||||||
|
# convert to int
|
||||||
|
bit_value = int(bit_value)
|
||||||
|
if (bit_value & (1 << self.bit)) == 0:
|
||||||
|
return 0
|
||||||
|
return 1
|
||||||
|
|
||||||
|
|
||||||
|
class ES0Filter(Device):
|
||||||
|
"""Class for the ES0 filter station X01DA-ES0-FI:"""
|
||||||
|
|
||||||
|
Mo400 = Cpt(EpicsSignalWithRBVBit, suffix="BIO", bit=1, kind="config", doc="Mo400 filter")
|
||||||
|
Mo300 = Cpt(EpicsSignalWithRBVBit, suffix="BIO", bit=2, kind="config", doc="Mo300 filter")
|
||||||
|
Mo200 = Cpt(EpicsSignalWithRBVBit, suffix="BIO", bit=3, kind="config", doc="Mo200 filter")
|
||||||
|
Zn500 = Cpt(EpicsSignalWithRBVBit, suffix="BIO", bit=4, kind="config", doc="Zn500 filter")
|
||||||
|
Zn250 = Cpt(EpicsSignalWithRBVBit, suffix="BIO", bit=5, kind="config", doc="Zn250 filter")
|
||||||
|
Zn125 = Cpt(EpicsSignalWithRBVBit, suffix="BIO", bit=6, kind="config", doc="Zn125 filter")
|
||||||
|
Zn50 = Cpt(EpicsSignalWithRBVBit, suffix="BIO", bit=7, kind="config", doc="Zn50 filter")
|
||||||
|
Zn25 = Cpt(EpicsSignalWithRBVBit, suffix="BIO", bit=8, kind="config", doc="Zn25 filter")
|
||||||
|
Al500 = Cpt(EpicsSignalWithRBVBit, suffix="BIO", bit=9, kind="config", doc="Al500 filter")
|
||||||
|
Al320 = Cpt(EpicsSignalWithRBVBit, suffix="BIO", bit=10, kind="config", doc="Al320 filter")
|
||||||
|
Al200 = Cpt(EpicsSignalWithRBVBit, suffix="BIO", bit=11, kind="config", doc="Al200 filter")
|
||||||
|
Al100 = Cpt(EpicsSignalWithRBVBit, suffix="BIO", bit=12, kind="config", doc="Al100 filter")
|
||||||
|
Al50 = Cpt(EpicsSignalWithRBVBit, suffix="BIO", bit=13, kind="config", doc="Al50 filter")
|
||||||
|
Al20 = Cpt(EpicsSignalWithRBVBit, suffix="BIO", bit=14, kind="config", doc="Al20 filter")
|
||||||
|
Al10 = Cpt(EpicsSignalWithRBVBit, suffix="BIO", bit=15, kind="config", doc="Al10 filter")
|
||||||
0
debye_bec/devices/ionization_chambers/__init__.py
Normal file
0
debye_bec/devices/ionization_chambers/__init__.py
Normal file
331
debye_bec/devices/ionization_chambers/ionization_chamber.py
Normal file
331
debye_bec/devices/ionization_chambers/ionization_chamber.py
Normal file
@@ -0,0 +1,331 @@
|
|||||||
|
"""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
|
||||||
|
from ophyd.status import DeviceStatus, SubscriptionStatus
|
||||||
|
from ophyd_devices import CompareStatus, TransitionStatus
|
||||||
|
from ophyd_devices.interfaces.base_classes.psi_device_base import PSIDeviceBase
|
||||||
|
from typeguard import typechecked
|
||||||
|
|
||||||
|
from debye_bec.devices.ionization_chambers.ionization_chamber_enums import (
|
||||||
|
AmplifierEnable,
|
||||||
|
AmplifierFilter,
|
||||||
|
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"""
|
||||||
|
|
||||||
|
def __init__(self, prefix, **kwargs):
|
||||||
|
super().__init__(prefix + "-RB", write_pv=prefix + "Set", **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
class GasMixSetupControl(Device):
|
||||||
|
"""GasMixSetup Control for Inonization Chamber 0"""
|
||||||
|
|
||||||
|
gas1_req = Cpt(EpicsSignalWithRBV, suffix="Gas1Req", kind="config", doc="Gas 1 requirement")
|
||||||
|
conc1_req = Cpt(
|
||||||
|
EpicsSignalWithRBV, suffix="Conc1Req", kind="config", doc="Concentration 1 requirement"
|
||||||
|
)
|
||||||
|
gas2_req = Cpt(EpicsSignalWithRBV, suffix="Gas2Req", kind="config", doc="Gas 2 requirement")
|
||||||
|
conc2_req = Cpt(
|
||||||
|
EpicsSignalWithRBV, suffix="Conc2Req", kind="config", doc="Concentration 2 requirement"
|
||||||
|
)
|
||||||
|
press_req = Cpt(
|
||||||
|
EpicsSignalWithRBV, suffix="PressReq", kind="config", 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")
|
||||||
|
conc1 = Cpt(EpicsSignalRO, suffix="Conc1", kind="config", doc="Concentration 1")
|
||||||
|
gas2 = Cpt(EpicsSignalRO, suffix="Gas2", kind="config", doc="Gas 2")
|
||||||
|
conc2 = Cpt(EpicsSignalRO, suffix="Conc2", kind="config", doc="Concentration 2")
|
||||||
|
press = Cpt(EpicsSignalRO, suffix="PressTransm", kind="config", doc="Current Pressure")
|
||||||
|
|
||||||
|
|
||||||
|
class HighVoltageSuppliesControl(Device):
|
||||||
|
"""HighVoltage Supplies Control for Ionization Chamber 0"""
|
||||||
|
|
||||||
|
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": (
|
||||||
|
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}"},
|
||||||
|
),
|
||||||
|
}
|
||||||
|
amp = Dcpt(amp_signals)
|
||||||
|
gmes = Cpt(GasMixSetupControl, suffix=f"ES-GMES:IC{num-1}")
|
||||||
|
gmes_status = Cpt(EpicsSignalRO, suffix="ES-GMES:StatusMsg0", kind="config", doc="Status")
|
||||||
|
hv = Cpt(HighVoltageSuppliesControl, suffix=f"ES1-IC{num-1}:")
|
||||||
|
hv_en_signals = {
|
||||||
|
"ext_ena": (
|
||||||
|
EpicsSignalRO,
|
||||||
|
"ES1-IC0:HV-Ext-Ena",
|
||||||
|
{"kind": "config", "doc": "External 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, prefix: str = "", scan_info: ScanInfo | None = None, **kwargs):
|
||||||
|
self.timeout_for_pvwait = 2.5
|
||||||
|
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:
|
||||||
|
"""Configure the gain setting of the specified channel
|
||||||
|
|
||||||
|
Args:
|
||||||
|
gain (Literal['1e6', '1e7', '5e7', '1e8', '1e9']) : Desired gain
|
||||||
|
"""
|
||||||
|
|
||||||
|
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)
|
||||||
|
status.wait(self.timeout_for_pvwait)
|
||||||
|
|
||||||
|
match gain:
|
||||||
|
case "1e6":
|
||||||
|
self.amp.cGain_ENUM.put(AmplifierGain.G1E6)
|
||||||
|
case "1e7":
|
||||||
|
self.amp.cGain_ENUM.put(AmplifierGain.G1E7)
|
||||||
|
case "5e7":
|
||||||
|
self.amp.cGain_ENUM.put(AmplifierGain.G5E7)
|
||||||
|
case "1e8":
|
||||||
|
self.amp.cGain_ENUM.put(AmplifierGain.G1E8)
|
||||||
|
case "1e9":
|
||||||
|
self.amp.cGain_ENUM.put(AmplifierGain.G1E9)
|
||||||
|
|
||||||
|
def set_filter(
|
||||||
|
self,
|
||||||
|
value: (
|
||||||
|
Literal["1us", "3us", "10us", "30us", "100us", "300us", "1ms", "3ms"] | AmplifierFilter
|
||||||
|
),
|
||||||
|
) -> None:
|
||||||
|
"""Configure the filter setting of the specified channel
|
||||||
|
|
||||||
|
Args:
|
||||||
|
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)
|
||||||
|
status.wait(self.timeout_for_pvwait)
|
||||||
|
|
||||||
|
match value:
|
||||||
|
case "1us":
|
||||||
|
self.amp.cFilter_ENUM.put(AmplifierFilter.F1US)
|
||||||
|
case "3us":
|
||||||
|
self.amp.cFilter_ENUM.put(AmplifierFilter.F3US)
|
||||||
|
case "10us":
|
||||||
|
self.amp.cFilter_ENUM.put(AmplifierFilter.F10US)
|
||||||
|
case "30us":
|
||||||
|
self.amp.cFilter_ENUM.put(AmplifierFilter.F30US)
|
||||||
|
case "100us":
|
||||||
|
self.amp.cFilter_ENUM.put(AmplifierFilter.F100US)
|
||||||
|
case "300us":
|
||||||
|
self.amp.cFilter_ENUM.put(AmplifierFilter.F300US)
|
||||||
|
case "1ms":
|
||||||
|
self.amp.cFilter_ENUM.put(AmplifierFilter.F1MS)
|
||||||
|
case "3ms":
|
||||||
|
self.amp.cFilter_ENUM.put(AmplifierFilter.F3MS)
|
||||||
|
|
||||||
|
@typechecked
|
||||||
|
def set_hv(self, hv: float) -> None:
|
||||||
|
"""Configure the high voltage settings , this will
|
||||||
|
enable the high voltage (if external enable is active)!
|
||||||
|
|
||||||
|
Args:
|
||||||
|
hv (float) : Desired voltage for the 'HV' terminal. Voltage has to be between 0...3000
|
||||||
|
"""
|
||||||
|
|
||||||
|
if not 0 <= hv <= 3000:
|
||||||
|
raise ValueError(f"specified HV {hv} not within range [0 .. 3000]")
|
||||||
|
if not np.isclose(np.abs(hv - self.hv.grid_v.get()), 0, atol=3):
|
||||||
|
raise ValueError(f"Grid {self.hv.grid_v.get()} must not be higher than HV {hv}!")
|
||||||
|
|
||||||
|
if not self.hv_en.ena.get() == 1:
|
||||||
|
status = CompareStatus(self.hv_en.ena, 1)
|
||||||
|
self.cancel_on_stop(status)
|
||||||
|
self.hv_en.ena.put(1)
|
||||||
|
status.wait(self.timeout_for_pvwait)
|
||||||
|
|
||||||
|
# Set current fixed to 3 mA (max)
|
||||||
|
self.hv.hv_i.put(3)
|
||||||
|
self.hv.hv_v.put(hv)
|
||||||
|
|
||||||
|
@typechecked
|
||||||
|
def set_grid(self, grid: float) -> None:
|
||||||
|
"""Configure the high voltage settings , this will
|
||||||
|
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
|
||||||
|
"""
|
||||||
|
|
||||||
|
if not 0 <= grid <= 3000:
|
||||||
|
raise ValueError(f"specified Grid {grid} not within range [0 .. 3000]")
|
||||||
|
if not np.isclose(np.abs(grid - self.hv.hv_v.get()), 0, atol=3):
|
||||||
|
raise ValueError(f"Grid {grid} must not be higher than HV {self.hv.hv_v.get()}!")
|
||||||
|
|
||||||
|
if not self.hv_en.ena.get() == 1:
|
||||||
|
status = CompareStatus(self.hv_en.ena, 1)
|
||||||
|
self.cancel_on_stop(status)
|
||||||
|
self.hv_en.ena.put(1)
|
||||||
|
status.wait(self.timeout_for_pvwait)
|
||||||
|
|
||||||
|
# Set current fixed to 3 mA (max)
|
||||||
|
self.hv.grid_i.put(3)
|
||||||
|
self.hv.grid_v.put(grid)
|
||||||
|
|
||||||
|
@typechecked
|
||||||
|
def fill(
|
||||||
|
self,
|
||||||
|
gas1: Literal["He", "N2", "Ar", "Kr"],
|
||||||
|
conc1: float,
|
||||||
|
gas2: Literal["He", "N2", "Ar", "Kr"],
|
||||||
|
conc2: float,
|
||||||
|
pressure: float,
|
||||||
|
*,
|
||||||
|
wait: bool = False,
|
||||||
|
) -> DeviceStatus | None:
|
||||||
|
"""Fill an ionization chamber with the specified gas mixture.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
gas1 (Literal['He', 'N2', 'Ar', 'Kr']) : Gas 1 requirement,
|
||||||
|
conc1 (float) : Concentration 1 requirement in %,
|
||||||
|
gas2 (Literal['He', 'N2', 'Ar', 'Kr']) : Gas 2 requirement,
|
||||||
|
conc2 (float) : Concentration 2 requirement in %,
|
||||||
|
pressure (float) : Required pressure in bar abs,
|
||||||
|
wait (bool): If you like to wait for the filling to finish.
|
||||||
|
"""
|
||||||
|
|
||||||
|
if not 0 <= conc1 <= 100:
|
||||||
|
raise ValueError(f"Concentration 1 {conc1} out of range [0 .. 100 %]")
|
||||||
|
if not 0 <= conc2 <= 100:
|
||||||
|
raise ValueError(f"Concentration 2 {conc2} out of range [0 .. 100 %]")
|
||||||
|
if not np.isclose((conc1 + conc2), 100, atol=0.1):
|
||||||
|
raise ValueError(f"Conc1 {conc1} and conc2 {conc2} must sum to 100 +- 0.1")
|
||||||
|
if not 0 <= pressure <= 3:
|
||||||
|
raise ValueError(f"Pressure {pressure} out of range [0 .. 3 bar abs]")
|
||||||
|
|
||||||
|
self.gmes.gas1_req.set(gas1).wait(timeout=3)
|
||||||
|
self.gmes.conc1_req.set(conc1).wait(timeout=3)
|
||||||
|
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)
|
||||||
|
if wait:
|
||||||
|
status.wait(timeout=360)
|
||||||
|
else:
|
||||||
|
return status
|
||||||
|
|
||||||
|
|
||||||
|
class IonizationChamber1(IonizationChamber0):
|
||||||
|
"""Ionization Chamber 1, prefix should be 'X01DA-'."""
|
||||||
|
|
||||||
|
num = 2
|
||||||
|
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}"},
|
||||||
|
),
|
||||||
|
}
|
||||||
|
amp = Dcpt(amp_signals)
|
||||||
|
gmes = Cpt(GasMixSetupControl, suffix=f"ES-GMES:IC{num-1}")
|
||||||
|
gmes_status = Cpt(EpicsSignalRO, suffix="ES-GMES:StatusMsg0", kind="config", doc="Status")
|
||||||
|
hv = Cpt(HighVoltageSuppliesControl, suffix=f"ES2-IC{num-1}:")
|
||||||
|
hv_en_signals = {
|
||||||
|
"ext_ena": (
|
||||||
|
EpicsSignalRO,
|
||||||
|
"ES2-IC12:HV-Ext-Ena",
|
||||||
|
{"kind": "config", "doc": "External 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(IonizationChamber0):
|
||||||
|
"""Ionization Chamber 2, prefix should be 'X01DA-'."""
|
||||||
|
|
||||||
|
num = 3
|
||||||
|
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}"},
|
||||||
|
),
|
||||||
|
}
|
||||||
|
amp = Dcpt(amp_signals)
|
||||||
|
gmes = Cpt(GasMixSetupControl, suffix=f"ES-GMES:IC{num-1}")
|
||||||
|
gmes_status = Cpt(EpicsSignalRO, suffix="ES-GMES:StatusMsg0", kind="config", doc="Status")
|
||||||
|
hv = Cpt(HighVoltageSuppliesControl, suffix=f"ES2-IC{num-1}:")
|
||||||
|
hv_en_signals = {
|
||||||
|
"ext_ena": (
|
||||||
|
EpicsSignalRO,
|
||||||
|
"ES2-IC12:HV-Ext-Ena",
|
||||||
|
{"kind": "config", "doc": "External enable signal of HV"},
|
||||||
|
),
|
||||||
|
"ena": (EpicsSignal, "ES2-IC12:HV-Ena", {"kind": "config", "doc": "Enable signal of HV"}),
|
||||||
|
}
|
||||||
|
hv_en = Dcpt(hv_en_signals)
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
import enum
|
||||||
|
|
||||||
|
|
||||||
|
class AmplifierEnable(int, enum.Enum):
|
||||||
|
"""Enum class for the enable signal of the channel"""
|
||||||
|
|
||||||
|
OFF = 0
|
||||||
|
STARTUP = 1
|
||||||
|
ON = 2
|
||||||
|
|
||||||
|
|
||||||
|
class AmplifierGain(int, enum.Enum):
|
||||||
|
"""Enum class for the gain of the channel"""
|
||||||
|
|
||||||
|
G1E6 = 0
|
||||||
|
G1E7 = 1
|
||||||
|
G5E7 = 2
|
||||||
|
G1E8 = 3
|
||||||
|
G1E9 = 4
|
||||||
|
|
||||||
|
|
||||||
|
class AmplifierFilter(int, enum.Enum):
|
||||||
|
"""Enum class for the filter of the channel"""
|
||||||
|
|
||||||
|
F1US = 0
|
||||||
|
F3US = 1
|
||||||
|
F10US = 2
|
||||||
|
F30US = 3
|
||||||
|
F100US = 4
|
||||||
|
F300US = 5
|
||||||
|
F1MS = 6
|
||||||
|
F3MS = 7
|
||||||
@@ -1,891 +0,0 @@
|
|||||||
""" Module for the Mo1 Bragg positioner of the Debye beamline.
|
|
||||||
The soft IOC is reachable via the EPICS prefix X01DA-OP-MO1:BRAGG: and connected
|
|
||||||
to a motor controller via web sockets. The Mo1 Bragg positioner is not only a
|
|
||||||
positioner, but also a scan controller to setup XAS and XRD scans. A few scan modes
|
|
||||||
are programmed in the controller, e.g. simple and advanced XAS scans + XRD triggering mode.
|
|
||||||
|
|
||||||
Note: For some of the Epics PVs, in particular action buttons, the suffix .PROC and
|
|
||||||
put_complete=True is used to ensure that the action is executed completely. This is believed
|
|
||||||
to allow for a more stable execution of the action."""
|
|
||||||
|
|
||||||
import enum
|
|
||||||
import threading
|
|
||||||
import time
|
|
||||||
import traceback
|
|
||||||
from dataclasses import dataclass
|
|
||||||
from typing import Literal
|
|
||||||
|
|
||||||
from bec_lib.logger import bec_logger
|
|
||||||
from ophyd import Component as Cpt
|
|
||||||
from ophyd import (
|
|
||||||
Device,
|
|
||||||
DeviceStatus,
|
|
||||||
EpicsSignal,
|
|
||||||
EpicsSignalRO,
|
|
||||||
EpicsSignalWithRBV,
|
|
||||||
Kind,
|
|
||||||
PositionerBase,
|
|
||||||
Signal,
|
|
||||||
Staged,
|
|
||||||
)
|
|
||||||
from ophyd.utils import LimitError
|
|
||||||
from ophyd_devices.utils import bec_scaninfo_mixin, bec_utils
|
|
||||||
from ophyd_devices.utils.errors import DeviceStopError, DeviceTimeoutError
|
|
||||||
|
|
||||||
logger = bec_logger.logger
|
|
||||||
|
|
||||||
|
|
||||||
class ScanControlScanStatus(int, enum.Enum):
|
|
||||||
"""Enum class for the scan status of the Bragg positioner"""
|
|
||||||
|
|
||||||
PARAMETER_WRONG = 0
|
|
||||||
VALIDATION_PENDING = 1
|
|
||||||
READY = 2
|
|
||||||
RUNNING = 3
|
|
||||||
|
|
||||||
|
|
||||||
class ScanControlLoadMessage(int, enum.Enum):
|
|
||||||
"""Enum for validating messages for load message of the Bragg positioner"""
|
|
||||||
|
|
||||||
PENDING = 0
|
|
||||||
STARTED = 1
|
|
||||||
SUCCESS = 2
|
|
||||||
ERR_XRD_MEAS_LEN_LOW = 3
|
|
||||||
ERR_XRD_N_TRIGGERS_LOW = 4
|
|
||||||
ERR_XRD_TRIGS_EVERY_N_LOW = 5
|
|
||||||
ERR_XRD_MEAS_LEN_HI = 6
|
|
||||||
ERR_XRD_N_TRIGGERS_HI = 7
|
|
||||||
ERR_XRD_TRIGS_EVERY_N_HI = 8
|
|
||||||
ERR_SCAN_HI_ANGLE_LIMIT = 9
|
|
||||||
ERR_SCAN_LOW_ANGLE_LIMITS = 10
|
|
||||||
ERR_SCAN_TIME = 11
|
|
||||||
ERR_SCAN_VEL_TOO_HI = 12
|
|
||||||
ERR_SCAN_ANGLE_OUT_OF_LIM = 13
|
|
||||||
ERR_SCAN_HIGH_VEL_LAR_42 = 14
|
|
||||||
ERR_SCAN_MODE_INVALID = 15
|
|
||||||
|
|
||||||
|
|
||||||
class Mo1BraggError(Exception):
|
|
||||||
"""Mo1Bragg specific exception"""
|
|
||||||
|
|
||||||
|
|
||||||
class MoveType(str, enum.Enum):
|
|
||||||
"""Enum class to switch between move types energy and angle for the Bragg positioner"""
|
|
||||||
|
|
||||||
ENERGY = "energy"
|
|
||||||
ANGLE = "angle"
|
|
||||||
|
|
||||||
|
|
||||||
class ScanControlMode(int, enum.Enum):
|
|
||||||
"""Enum class for the scan control mode of the Bragg positioner"""
|
|
||||||
|
|
||||||
SIMPLE = 0
|
|
||||||
ADVANCED = 1
|
|
||||||
|
|
||||||
|
|
||||||
class MoveTypeSignal(Signal):
|
|
||||||
"""Custom Signal to set the move type of the Bragg positioner"""
|
|
||||||
|
|
||||||
# pylint: disable=arguments-differ
|
|
||||||
def set(self, value: str | MoveType) -> None:
|
|
||||||
"""Returns currently active move method
|
|
||||||
|
|
||||||
Args:
|
|
||||||
value (str | MoveType) : Can be either 'energy' or 'angle'
|
|
||||||
"""
|
|
||||||
|
|
||||||
value = MoveType(value.lower())
|
|
||||||
self._readback = value.value
|
|
||||||
|
|
||||||
|
|
||||||
class Mo1BraggStatus(Device):
|
|
||||||
"""Mo1 Bragg PVs for status monitoring"""
|
|
||||||
|
|
||||||
error_status = Cpt(EpicsSignalRO, suffix="error_status_RBV", kind="config", auto_monitor=True)
|
|
||||||
brake_enabled = Cpt(EpicsSignalRO, suffix="brake_enabled_RBV", kind="config", auto_monitor=True)
|
|
||||||
mot_commutated = Cpt(
|
|
||||||
EpicsSignalRO, suffix="mot_commutated_RBV", kind="config", auto_monitor=True
|
|
||||||
)
|
|
||||||
axis_enabled = Cpt(EpicsSignalRO, suffix="axis_enabled_RBV", kind="config", auto_monitor=True)
|
|
||||||
enc_initialized = Cpt(
|
|
||||||
EpicsSignalRO, suffix="enc_initialized_RBV", kind="config", auto_monitor=True
|
|
||||||
)
|
|
||||||
heartbeat = Cpt(EpicsSignalRO, suffix="heartbeat_RBV", kind="config", auto_monitor=True)
|
|
||||||
|
|
||||||
|
|
||||||
class Mo1BraggEncoder(Device):
|
|
||||||
"""Mo1 Bragg PVs to communicate with the encoder"""
|
|
||||||
|
|
||||||
enc_reinit = Cpt(EpicsSignal, suffix="enc_reinit.PROC", kind="config")
|
|
||||||
enc_reinit_done = Cpt(EpicsSignalRO, suffix="enc_reinit_done_RBV", kind="config")
|
|
||||||
|
|
||||||
|
|
||||||
class Mo1BraggCrystal(Device):
|
|
||||||
"""Mo1 Bragg PVs to set the crystal parameters"""
|
|
||||||
|
|
||||||
offset_si111 = Cpt(EpicsSignalWithRBV, suffix="offset_si111", kind="config")
|
|
||||||
offset_si311 = Cpt(EpicsSignalWithRBV, suffix="offset_si311", kind="config")
|
|
||||||
xtal_enum = Cpt(EpicsSignalWithRBV, suffix="xtal_ENUM", kind="config")
|
|
||||||
d_spacing_si111 = Cpt(EpicsSignalWithRBV, suffix="d_spacing_si111", kind="config")
|
|
||||||
d_spacing_si311 = Cpt(EpicsSignalWithRBV, suffix="d_spacing_si311", kind="config")
|
|
||||||
set_offset = Cpt(EpicsSignal, suffix="set_offset.PROC", kind="config", put_complete=True)
|
|
||||||
current_xtal = Cpt(
|
|
||||||
EpicsSignalRO, suffix="current_xtal_ENUM_RBV", kind="normal", auto_monitor=True
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class Mo1BraggScanSettings(Device):
|
|
||||||
"""Mo1 Bragg PVs to set the scan setttings"""
|
|
||||||
|
|
||||||
# XRD settings
|
|
||||||
xrd_select_ref_enum = Cpt(EpicsSignalWithRBV, suffix="xrd_select_ref_ENUM", kind="config")
|
|
||||||
xrd_enable_hi_enum = Cpt(EpicsSignalWithRBV, suffix="xrd_enable_hi_ENUM", kind="config")
|
|
||||||
|
|
||||||
xrd_time_hi = Cpt(EpicsSignalWithRBV, suffix="xrd_time_hi", kind="config")
|
|
||||||
xrd_n_trigger_hi = Cpt(EpicsSignalWithRBV, suffix="xrd_n_trigger_hi", kind="config")
|
|
||||||
xrd_every_n_hi = Cpt(EpicsSignalWithRBV, suffix="xrd_every_n_hi", kind="config")
|
|
||||||
|
|
||||||
xrd_enable_lo_enum = Cpt(EpicsSignalWithRBV, suffix="xrd_enable_lo_ENUM", kind="config")
|
|
||||||
xrd_time_lo = Cpt(EpicsSignalWithRBV, suffix="xrd_time_lo", kind="config")
|
|
||||||
xrd_n_trigger_lo = Cpt(EpicsSignalWithRBV, suffix="xrd_n_trigger_lo", kind="config")
|
|
||||||
xrd_every_n_lo = Cpt(EpicsSignalWithRBV, suffix="xrd_every_n_lo", kind="config")
|
|
||||||
|
|
||||||
# XAS simple scan settings
|
|
||||||
s_scan_angle_hi = Cpt(EpicsSignalWithRBV, suffix="s_scan_angle_hi", kind="config")
|
|
||||||
s_scan_angle_lo = Cpt(EpicsSignalWithRBV, suffix="s_scan_angle_lo", kind="config")
|
|
||||||
s_scan_energy_lo = Cpt(
|
|
||||||
EpicsSignalWithRBV, suffix="s_scan_energy_lo", kind="config", auto_monitor=True
|
|
||||||
)
|
|
||||||
s_scan_energy_hi = Cpt(
|
|
||||||
EpicsSignalWithRBV, suffix="s_scan_energy_hi", kind="config", auto_monitor=True
|
|
||||||
)
|
|
||||||
s_scan_scantime = Cpt(
|
|
||||||
EpicsSignalWithRBV, suffix="s_scan_scantime", kind="config", auto_monitor=True
|
|
||||||
)
|
|
||||||
|
|
||||||
# XAS advanced scan settings
|
|
||||||
a_scan_pos = Cpt(EpicsSignalWithRBV, suffix="a_scan_pos", kind="config", auto_monitor=True)
|
|
||||||
a_scan_vel = Cpt(EpicsSignalWithRBV, suffix="a_scan_vel", kind="config", auto_monitor=True)
|
|
||||||
a_scan_time = Cpt(EpicsSignalWithRBV, suffix="a_scan_time", kind="config", auto_monitor=True)
|
|
||||||
|
|
||||||
|
|
||||||
class Mo1BraggScanControl(Device):
|
|
||||||
"""Mo1 Bragg PVs to control the scan after setting the parameters."""
|
|
||||||
|
|
||||||
scan_mode_enum = Cpt(EpicsSignalWithRBV, suffix="scan_mode_ENUM", kind="config")
|
|
||||||
scan_duration = Cpt(
|
|
||||||
EpicsSignalWithRBV, suffix="scan_duration", kind="config", auto_monitor=True
|
|
||||||
)
|
|
||||||
scan_load = Cpt(EpicsSignal, suffix="scan_load.PROC", kind="config", put_complete=True)
|
|
||||||
scan_msg = Cpt(EpicsSignalRO, suffix="scan_msg_ENUM_RBV", kind="config", auto_monitor=True)
|
|
||||||
scan_start_infinite = Cpt(
|
|
||||||
EpicsSignal, suffix="scan_start_infinite.PROC", kind="config", put_complete=True
|
|
||||||
)
|
|
||||||
scan_start_timer = Cpt(
|
|
||||||
EpicsSignal, suffix="scan_start_timer.PROC", kind="config", put_complete=True
|
|
||||||
)
|
|
||||||
scan_stop = Cpt(EpicsSignal, suffix="scan_stop.PROC", kind="config", put_complete=True)
|
|
||||||
scan_status = Cpt(
|
|
||||||
EpicsSignalRO, suffix="scan_status_ENUM_RBV", kind="config", auto_monitor=True
|
|
||||||
)
|
|
||||||
scan_time_left = Cpt(
|
|
||||||
EpicsSignalRO, suffix="scan_time_left_RBV", kind="config", auto_monitor=True
|
|
||||||
)
|
|
||||||
scan_done = Cpt(EpicsSignalRO, suffix="scan_done_RBV", kind="config", auto_monitor=True)
|
|
||||||
scan_val_reset = Cpt(
|
|
||||||
EpicsSignal, suffix="scan_val_reset.PROC", kind="config", put_complete=True
|
|
||||||
)
|
|
||||||
scan_progress = Cpt(EpicsSignalRO, suffix="scan_progress_RBV", kind="config", auto_monitor=True)
|
|
||||||
scan_spectra_done = Cpt(
|
|
||||||
EpicsSignalRO, suffix="scan_n_osc_RBV", kind="config", auto_monitor=True
|
|
||||||
)
|
|
||||||
scan_spectra_left = Cpt(
|
|
||||||
EpicsSignalRO, suffix="scan_n_osc_left_RBV", kind="config", auto_monitor=True
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
|
||||||
class ScanParameter:
|
|
||||||
"""Dataclass to store the scan parameters for the Mo1 Bragg positioner.
|
|
||||||
This needs to be in sync with the kwargs of the MO1 Bragg 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
|
|
||||||
scan_duration: float = None
|
|
||||||
xrd_enable_low: bool = None
|
|
||||||
xrd_enable_high: bool = None
|
|
||||||
num_trigger_low: int = None
|
|
||||||
num_trigger_high: int = None
|
|
||||||
exp_time_low: float = None
|
|
||||||
exp_time_high: float = None
|
|
||||||
cycle_low: int = None
|
|
||||||
cycle_high: int = None
|
|
||||||
start: float = None
|
|
||||||
stop: float = None
|
|
||||||
|
|
||||||
|
|
||||||
class Mo1Bragg(Device, PositionerBase):
|
|
||||||
"""Class for the Mo1 Bragg positioner of the Debye beamline.
|
|
||||||
The prefix to connect to the soft IOC is X01DA-OP-MO1:BRAGG: which is connected to
|
|
||||||
the NI motor controller via web sockets.
|
|
||||||
"""
|
|
||||||
|
|
||||||
USER_ACCESS = ["set_xtal", "stop_scan"]
|
|
||||||
|
|
||||||
crystal = Cpt(Mo1BraggCrystal, "")
|
|
||||||
encoder = Cpt(Mo1BraggEncoder, "")
|
|
||||||
scan_settings = Cpt(Mo1BraggScanSettings, "")
|
|
||||||
scan_control = Cpt(Mo1BraggScanControl, "")
|
|
||||||
status = Cpt(Mo1BraggStatus, "")
|
|
||||||
|
|
||||||
# signal to indicate the move type 'energy' or 'angle'
|
|
||||||
move_type = Cpt(MoveTypeSignal, value=MoveType.ENERGY, kind="config")
|
|
||||||
|
|
||||||
# Energy PVs
|
|
||||||
readback = Cpt(
|
|
||||||
EpicsSignalRO, suffix="feedback_pos_energy_RBV", kind="hinted", auto_monitor=True
|
|
||||||
)
|
|
||||||
setpoint = Cpt(
|
|
||||||
EpicsSignalWithRBV, suffix="set_abs_pos_energy", kind="normal", auto_monitor=True
|
|
||||||
)
|
|
||||||
motor_is_moving = Cpt(
|
|
||||||
EpicsSignalRO, suffix="move_abs_done_RBV", kind="normal", auto_monitor=True
|
|
||||||
)
|
|
||||||
low_lim = Cpt(EpicsSignalRO, suffix="lo_lim_pos_energy_RBV", kind="config", auto_monitor=True)
|
|
||||||
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 makd angle motion a pseudo motor
|
|
||||||
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
|
|
||||||
)
|
|
||||||
|
|
||||||
# Execute motion
|
|
||||||
move_abs = Cpt(EpicsSignal, suffix="move_abs.PROC", kind="config", put_complete=True)
|
|
||||||
move_stop = Cpt(EpicsSignal, suffix="move_stop.PROC", kind="config", put_complete=True)
|
|
||||||
|
|
||||||
SUB_READBACK = "readback"
|
|
||||||
_default_sub = SUB_READBACK
|
|
||||||
SUB_PROGRESS = "progress"
|
|
||||||
|
|
||||||
def __init__(
|
|
||||||
self, prefix="", *, name: str, kind: Kind = None, device_manager=None, parent=None, **kwargs
|
|
||||||
):
|
|
||||||
"""Initialize the Mo1 Bragg positioner.
|
|
||||||
|
|
||||||
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._stopped = False
|
|
||||||
self.device_manager = device_manager
|
|
||||||
self._move_thread = None
|
|
||||||
self.service_cfg = None
|
|
||||||
self.scaninfo = None
|
|
||||||
# Init scan parameters
|
|
||||||
self.scan_parameter = ScanParameter()
|
|
||||||
|
|
||||||
self.timeout_for_pvwait = 2.5
|
|
||||||
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()
|
|
||||||
|
|
||||||
self.connector = self.device_manager.connector
|
|
||||||
self._update_scaninfo()
|
|
||||||
self._on_init()
|
|
||||||
|
|
||||||
def _on_init(self):
|
|
||||||
"""Action to be executed on initialization of the device"""
|
|
||||||
self.scan_control.scan_progress.subscribe(self._progress_update, run=False)
|
|
||||||
|
|
||||||
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
|
|
||||||
"""
|
|
||||||
max_value = 100
|
|
||||||
self._run_subs(
|
|
||||||
sub_type=self.SUB_PROGRESS,
|
|
||||||
value=value,
|
|
||||||
max_value=max_value,
|
|
||||||
done=bool(max_value == value),
|
|
||||||
)
|
|
||||||
|
|
||||||
def _update_scaninfo(self) -> None:
|
|
||||||
"""Connect to the ScanInfo mixin"""
|
|
||||||
self.scaninfo = bec_scaninfo_mixin.BecScaninfoMixin(self.device_manager)
|
|
||||||
self.scaninfo.load_scan_metadata()
|
|
||||||
|
|
||||||
@property
|
|
||||||
def stopped(self) -> bool:
|
|
||||||
"""Return the stopped flag. If True, the motion is stopped."""
|
|
||||||
return self._stopped
|
|
||||||
|
|
||||||
def stop(self, *, success=False) -> None:
|
|
||||||
"""Stop any motion on the positioner
|
|
||||||
|
|
||||||
Args:
|
|
||||||
success (bool) : Flag to indicate if the motion was successful
|
|
||||||
"""
|
|
||||||
self.move_stop.put(1)
|
|
||||||
self._stopped = True
|
|
||||||
if self._move_thread is not None:
|
|
||||||
self._move_thread.join()
|
|
||||||
self._move_thread = None
|
|
||||||
super().stop(success=success)
|
|
||||||
|
|
||||||
def stop_scan(self) -> None:
|
|
||||||
"""Stop the currently running scan gracefully, this finishes the running oscillation."""
|
|
||||||
self.scan_control.scan_stop.put(1)
|
|
||||||
|
|
||||||
# -------------- Positioner specific methods -----------------#
|
|
||||||
@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())
|
|
||||||
|
|
||||||
@property
|
|
||||||
def low_limit(self) -> float:
|
|
||||||
"""Return low limit of axis"""
|
|
||||||
return self.limits[0]
|
|
||||||
|
|
||||||
@property
|
|
||||||
def high_limit(self) -> float:
|
|
||||||
"""Return high limit of axis"""
|
|
||||||
return self.limits[1]
|
|
||||||
|
|
||||||
@property
|
|
||||||
def egu(self) -> str:
|
|
||||||
"""Return the engineering units of the positioner"""
|
|
||||||
if self.move_type.get() == MoveType.ENERGY:
|
|
||||||
return "eV"
|
|
||||||
return "deg"
|
|
||||||
|
|
||||||
@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()
|
|
||||||
|
|
||||||
# pylint: disable=arguments-differ
|
|
||||||
def check_value(self, value: float) -> None:
|
|
||||||
"""Method to check if a value is within limits of the positioner. Called by PositionerBase.move()
|
|
||||||
|
|
||||||
Args:
|
|
||||||
value (float) : value to move axis to.
|
|
||||||
"""
|
|
||||||
low_limit, high_limit = self.limits
|
|
||||||
|
|
||||||
if low_limit < high_limit and not low_limit <= value <= high_limit:
|
|
||||||
raise LimitError(f"position={value} not within limits {self.limits}")
|
|
||||||
|
|
||||||
def _move_and_finish(
|
|
||||||
self,
|
|
||||||
target_pos: float,
|
|
||||||
move_cpt: Cpt,
|
|
||||||
read_cpt: Cpt,
|
|
||||||
status: DeviceStatus,
|
|
||||||
update_frequency: float = 0.1,
|
|
||||||
) -> None:
|
|
||||||
"""Method to be called in the move thread to move the Bragg positioner to the target position.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
target_pos (float) : target position for the motion
|
|
||||||
move_cpt (Cpt) : component to set the target position on the IOC,
|
|
||||||
either setpoint or setpoint_abs_angle depending on the move type
|
|
||||||
read_cpt (Cpt) : component to read the current position of the motion, readback or feedback_pos_angle
|
|
||||||
status (DeviceStatus) : status object to set the status of the motion
|
|
||||||
update_frequency (float): Optional, frequency to update the current position of the motion, defaults to 0.1s
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
# Set the target position on IOC
|
|
||||||
move_cpt.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)
|
|
||||||
while self.motor_is_moving.get() == 0:
|
|
||||||
if self.stopped:
|
|
||||||
raise DeviceStopError(f"Device {self.name} was stopped")
|
|
||||||
time.sleep(update_frequency)
|
|
||||||
# pylint: disable=protected-access
|
|
||||||
status.set_finished()
|
|
||||||
# pylint: disable=broad-except
|
|
||||||
except Exception as exc:
|
|
||||||
content = traceback.format_exc()
|
|
||||||
logger.error(f"Error in move thread of device {self.name}: {content}")
|
|
||||||
status.set_exception(exc=exc)
|
|
||||||
|
|
||||||
def move(self, value: float, move_type: str | MoveType = None, **kwargs) -> DeviceStatus:
|
|
||||||
"""Move the Bragg positioner to the specified value, allows to switch between move types angle and energy.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
value (float) : target value for the motion
|
|
||||||
move_type (str | MoveType) : Optional, specify the type of move, either 'energy' or 'angle'
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
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
|
|
||||||
read_cpt = self.readback if move_type == MoveType.ENERGY else self.feedback_pos_angle
|
|
||||||
|
|
||||||
self.check_value(value)
|
|
||||||
status = DeviceStatus(device=self)
|
|
||||||
|
|
||||||
self._move_thread = threading.Thread(
|
|
||||||
target=self._move_and_finish, args=(value, move_cpt, read_cpt, status, 0.1)
|
|
||||||
)
|
|
||||||
self._move_thread.start()
|
|
||||||
return status
|
|
||||||
|
|
||||||
# -------------- End of Positioner specific methods -----------------#
|
|
||||||
|
|
||||||
# -------------- MO1 Bragg specific methods -----------------#
|
|
||||||
|
|
||||||
def set_xtal(
|
|
||||||
self,
|
|
||||||
xtal_enum: Literal["111", "311"],
|
|
||||||
offset_si111: float = None,
|
|
||||||
offset_si311: float = None,
|
|
||||||
d_spacing_si111: float = None,
|
|
||||||
d_spacing_si311: float = None,
|
|
||||||
) -> None:
|
|
||||||
"""Method to set the crystal parameters of the Bragg positioner
|
|
||||||
|
|
||||||
Args:
|
|
||||||
xtal_enum (Literal["111", "311"]) : Enum to set the crystal orientation
|
|
||||||
offset_si111 (float) : Offset for the 111 crystal
|
|
||||||
offset_si311 (float) : Offset for the 311 crystal
|
|
||||||
d_spacing_si111 (float) : d-spacing for the 111 crystal
|
|
||||||
d_spacing_si311 (float) : d-spacing for the 311 crystal
|
|
||||||
"""
|
|
||||||
if offset_si111 is not None:
|
|
||||||
self.crystal.offset_si111.put(offset_si111)
|
|
||||||
if offset_si311 is not None:
|
|
||||||
self.crystal.offset_si311.put(offset_si311)
|
|
||||||
if d_spacing_si111 is not None:
|
|
||||||
self.crystal.d_spacing_si111.put(d_spacing_si111)
|
|
||||||
if d_spacing_si311 is not None:
|
|
||||||
self.crystal.d_spacing_si311.put(d_spacing_si311)
|
|
||||||
if xtal_enum == "111":
|
|
||||||
crystal_set = 0
|
|
||||||
elif xtal_enum == "311":
|
|
||||||
crystal_set = 1
|
|
||||||
else:
|
|
||||||
raise ValueError(
|
|
||||||
f"Invalid argument for xtal_enum : {xtal_enum}, choose from '111' or '311'"
|
|
||||||
)
|
|
||||||
self.crystal.xtal_enum.put(crystal_set)
|
|
||||||
self.crystal.set_offset.put(1)
|
|
||||||
|
|
||||||
def set_xas_settings(self, low: float, high: float, scan_time: float) -> None:
|
|
||||||
"""Set XAS parameters for upcoming scan.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
low (float): Low energy/angle value of the scan
|
|
||||||
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 set_xrd_settings(
|
|
||||||
self,
|
|
||||||
enable_low: bool,
|
|
||||||
enable_high: bool,
|
|
||||||
num_trigger_low: int,
|
|
||||||
num_trigger_high: int,
|
|
||||||
exp_time_low: int,
|
|
||||||
exp_time_high: int,
|
|
||||||
cycle_low: int,
|
|
||||||
cycle_high: int,
|
|
||||||
) -> None:
|
|
||||||
"""Set XRD settings for the upcoming scan.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
enable_low (bool): Enable XRD for low energy/angle
|
|
||||||
enable_high (bool): Enable XRD 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
|
|
||||||
cycle_low (int): Cycle for low energy/angle
|
|
||||||
cycle_high (int): Cycle for high energy/angle
|
|
||||||
"""
|
|
||||||
self.scan_settings.xrd_enable_hi_enum.put(int(enable_high))
|
|
||||||
self.scan_settings.xrd_enable_lo_enum.put(int(enable_low))
|
|
||||||
self.scan_settings.xrd_n_trigger_hi.put(num_trigger_high)
|
|
||||||
self.scan_settings.xrd_n_trigger_lo.put(num_trigger_low)
|
|
||||||
self.scan_settings.xrd_time_hi.put(exp_time_high)
|
|
||||||
self.scan_settings.xrd_time_lo.put(exp_time_low)
|
|
||||||
self.scan_settings.xrd_every_n_hi.put(cycle_high)
|
|
||||||
self.scan_settings.xrd_every_n_lo.put(cycle_low)
|
|
||||||
|
|
||||||
def set_scan_control_settings(self, mode: ScanControlMode, scan_duration: float) -> None:
|
|
||||||
"""Set the scan control settings for the upcoming scan.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
mode (ScanControlMode): Mode for the scan, either simple or advanced
|
|
||||||
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)
|
|
||||||
|
|
||||||
def _update_scan_parameter(self):
|
|
||||||
"""Get the scaninfo parameters for the scan."""
|
|
||||||
for key, value in self.scaninfo.scan_msg.content["info"]["kwargs"].items():
|
|
||||||
if hasattr(self.scan_parameter, key):
|
|
||||||
setattr(self.scan_parameter, key, value)
|
|
||||||
|
|
||||||
# -------------- End MO1 Bragg specific methods -----------------#
|
|
||||||
|
|
||||||
# -------------- Flyer Interface methods -----------------#
|
|
||||||
|
|
||||||
def kickoff(self):
|
|
||||||
"""Kickoff the device, called from BEC."""
|
|
||||||
scan_duration = self.scan_control.scan_duration.get()
|
|
||||||
# TODO implement better logic for infinite scans, at least bring it up with Debye
|
|
||||||
start_func = (
|
|
||||||
self.scan_control.scan_start_infinite.put
|
|
||||||
if scan_duration < 0.1
|
|
||||||
else self.scan_control.scan_start_timer.put
|
|
||||||
)
|
|
||||||
start_func(1)
|
|
||||||
status = self.wait_with_status(
|
|
||||||
signal_conditions=[(self.scan_control.scan_status.get, ScanControlScanStatus.RUNNING)],
|
|
||||||
timeout=self.timeout_for_pvwait,
|
|
||||||
check_stopped=True,
|
|
||||||
)
|
|
||||||
return status
|
|
||||||
|
|
||||||
def stage(self) -> list[object]:
|
|
||||||
"""
|
|
||||||
Stage the device in preparation for a scan.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
list(object): list of objects that were staged
|
|
||||||
"""
|
|
||||||
if self._staged != Staged.no:
|
|
||||||
return super().stage()
|
|
||||||
self._stopped = False
|
|
||||||
self.scaninfo.load_scan_metadata()
|
|
||||||
self.on_stage()
|
|
||||||
return super().stage()
|
|
||||||
|
|
||||||
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)
|
|
||||||
|
|
||||||
if not self.wait_for_signals(
|
|
||||||
signal_conditions=[(self.scan_control.scan_msg.get, target_state)],
|
|
||||||
timeout=self.timeout_for_pvwait,
|
|
||||||
check_stopped=True,
|
|
||||||
):
|
|
||||||
raise TimeoutError(
|
|
||||||
f"Timeout after {self.timeout_for_pvwait} while waiting for scan status,"
|
|
||||||
f" current state: {ScanControlScanStatus(self.scan_control.scan_msg.get())}"
|
|
||||||
)
|
|
||||||
|
|
||||||
def on_stage(self) -> None:
|
|
||||||
"""Actions to be executed when the device is staged."""
|
|
||||||
if not self.scaninfo.scan_type == "fly":
|
|
||||||
return
|
|
||||||
self._check_scan_msg(ScanControlLoadMessage.PENDING)
|
|
||||||
|
|
||||||
scan_name = self.scaninfo.scan_msg.content["info"].get("scan_name", "")
|
|
||||||
self._update_scan_parameter()
|
|
||||||
if scan_name == "xas_simple_scan":
|
|
||||||
self.set_xas_settings(
|
|
||||||
low=self.scan_parameter.start,
|
|
||||||
high=self.scan_parameter.stop,
|
|
||||||
scan_time=self.scan_parameter.scan_time,
|
|
||||||
)
|
|
||||||
self.set_xrd_settings(
|
|
||||||
enable_low=False,
|
|
||||||
enable_high=False,
|
|
||||||
num_trigger_low=0,
|
|
||||||
num_trigger_high=0,
|
|
||||||
exp_time_low=0,
|
|
||||||
exp_time_high=0,
|
|
||||||
cycle_low=0,
|
|
||||||
cycle_high=0,
|
|
||||||
)
|
|
||||||
self.set_scan_control_settings(
|
|
||||||
mode=ScanControlMode.SIMPLE, scan_duration=self.scan_parameter.scan_duration
|
|
||||||
)
|
|
||||||
elif scan_name == "xas_simple_scan_with_xrd":
|
|
||||||
self.set_xas_settings(
|
|
||||||
low=self.scan_parameter.start,
|
|
||||||
high=self.scan_parameter.stop,
|
|
||||||
scan_time=self.scan_parameter.scan_time,
|
|
||||||
)
|
|
||||||
self.set_xrd_settings(
|
|
||||||
enable_low=self.scan_parameter.xrd_enable_low,
|
|
||||||
enable_high=self.scan_parameter.xrd_enable_high,
|
|
||||||
num_trigger_low=self.scan_parameter.num_trigger_low,
|
|
||||||
num_trigger_high=self.scan_parameter.num_trigger_high,
|
|
||||||
exp_time_low=self.scan_parameter.exp_time_low,
|
|
||||||
exp_time_high=self.scan_parameter.exp_time_high,
|
|
||||||
cycle_low=self.scan_parameter.cycle_low,
|
|
||||||
cycle_high=self.scan_parameter.cycle_high,
|
|
||||||
)
|
|
||||||
self.set_scan_control_settings(
|
|
||||||
mode=ScanControlMode.SIMPLE, scan_duration=self.scan_parameter.scan_duration
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
raise Mo1BraggError(
|
|
||||||
f"Scan mode {scan_name} not implemented for scan_type={self.scaninfo.scan_type} on device {self.name}"
|
|
||||||
)
|
|
||||||
# Load the scan parameters to the controller
|
|
||||||
self.scan_control.scan_load.put(1)
|
|
||||||
# Wait for params to be checked from controller
|
|
||||||
if not self.wait_for_signals(
|
|
||||||
signal_conditions=[(self.scan_control.scan_msg.get, ScanControlLoadMessage.SUCCESS)],
|
|
||||||
timeout=self.timeout_for_pvwait,
|
|
||||||
check_stopped=True,
|
|
||||||
):
|
|
||||||
raise TimeoutError(
|
|
||||||
f"Scan parameter validation run into timeout after {self.timeout_for_pvwait} with {ScanControlLoadMessage(self.scan_control.scan_msg.get())}"
|
|
||||||
)
|
|
||||||
|
|
||||||
def complete(self) -> DeviceStatus:
|
|
||||||
"""Complete the acquisition.
|
|
||||||
|
|
||||||
The method returns a DeviceStatus object that resolves to set_finished or set_exception once the acquisition is completed.
|
|
||||||
"""
|
|
||||||
status = self.on_complete()
|
|
||||||
if isinstance(status, DeviceStatus):
|
|
||||||
return status
|
|
||||||
status = DeviceStatus(self)
|
|
||||||
status.set_finished()
|
|
||||||
return status
|
|
||||||
|
|
||||||
def on_complete(self) -> DeviceStatus:
|
|
||||||
"""Specify actions to be performed for the completion of the acquisition."""
|
|
||||||
status = self.wait_with_status(
|
|
||||||
signal_conditions=[(self.scan_control.scan_done.get, 1)],
|
|
||||||
timeout=None,
|
|
||||||
check_stopped=True,
|
|
||||||
)
|
|
||||||
return status
|
|
||||||
|
|
||||||
def unstage(self) -> list[object]:
|
|
||||||
"""
|
|
||||||
Unstage device after a scan. It has to be possible to call this multiple times.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
list(object): list of objects that were unstaged
|
|
||||||
"""
|
|
||||||
self.check_scan_id()
|
|
||||||
self._stopped = False
|
|
||||||
self.on_unstage()
|
|
||||||
return super().unstage()
|
|
||||||
|
|
||||||
def on_unstage(self) -> None:
|
|
||||||
"""Actions to be executed when the device is unstaged.
|
|
||||||
The checks here ensure that the controller resets the Scan_msg to PENDING state."""
|
|
||||||
if self.wait_for_signals(
|
|
||||||
signal_conditions=[(self.scan_control.scan_msg.get, ScanControlLoadMessage.PENDING)],
|
|
||||||
timeout=self.timeout_for_pvwait,
|
|
||||||
check_stopped=False,
|
|
||||||
):
|
|
||||||
return
|
|
||||||
|
|
||||||
self.scan_control.scan_val_reset.put(1)
|
|
||||||
if not self.wait_for_signals(
|
|
||||||
signal_conditions=[(self.scan_control.scan_msg.get, ScanControlLoadMessage.PENDING)],
|
|
||||||
timeout=self.timeout_for_pvwait,
|
|
||||||
check_stopped=False,
|
|
||||||
):
|
|
||||||
raise TimeoutError(
|
|
||||||
f"Timeout after {self.timeout_for_pvwait} while waiting for scan validation"
|
|
||||||
)
|
|
||||||
|
|
||||||
# -------------- End Flyer Interface methods -----------------#
|
|
||||||
|
|
||||||
# -------------- Utility methods -----------------#
|
|
||||||
|
|
||||||
def check_scan_id(self) -> None:
|
|
||||||
"""Checks if scan_id has changed and set stopped flagged to True if it has."""
|
|
||||||
old_scan_id = self.scaninfo.scan_id
|
|
||||||
self.scaninfo.load_scan_metadata()
|
|
||||||
if self.scaninfo.scan_id != old_scan_id:
|
|
||||||
self._stopped = True
|
|
||||||
|
|
||||||
def wait_for_signals(
|
|
||||||
self,
|
|
||||||
signal_conditions: list[tuple],
|
|
||||||
timeout: float | None = None,
|
|
||||||
check_stopped: bool = False,
|
|
||||||
interval: float = 0.05,
|
|
||||||
all_signals: bool = False,
|
|
||||||
) -> bool:
|
|
||||||
"""Wrapper around a list of conditions that allows waiting for them to become True.
|
|
||||||
For EPICs PVs, an example usage is pasted at the bottom.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
signal_conditions (list[tuple]): tuple of executable calls for conditions (get_current_state, condition) to check
|
|
||||||
timeout (float): timeout in seconds
|
|
||||||
check_stopped (bool): True if stopped flag should be checked. The function relies on the self.stopped property to be set
|
|
||||||
interval (float): interval in seconds
|
|
||||||
all_signals (bool): True if all signals should be True, False if any signal should be True
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
bool: True if all signals are in the desired state, False if timeout is reached
|
|
||||||
|
|
||||||
>>> Example usage for EPICS PVs:
|
|
||||||
>>> self.wait_for_signals(signal_conditions=[(self.acquiring.get, False)], timeout=5, interval=0.05, check_stopped=True, all_signals=True)
|
|
||||||
"""
|
|
||||||
|
|
||||||
timer = 0
|
|
||||||
while True:
|
|
||||||
checks = [
|
|
||||||
get_current_state() == condition
|
|
||||||
for get_current_state, condition in signal_conditions
|
|
||||||
]
|
|
||||||
if check_stopped is True and self.stopped is True:
|
|
||||||
return False
|
|
||||||
if (all_signals and all(checks)) or (not all_signals and any(checks)):
|
|
||||||
return True
|
|
||||||
if timeout and timer > timeout:
|
|
||||||
return False
|
|
||||||
time.sleep(interval)
|
|
||||||
timer += interval
|
|
||||||
|
|
||||||
def wait_with_status(
|
|
||||||
self,
|
|
||||||
signal_conditions: list[tuple],
|
|
||||||
timeout: float | None = None,
|
|
||||||
check_stopped: bool = False,
|
|
||||||
interval: float = 0.05,
|
|
||||||
all_signals: bool = False,
|
|
||||||
exception_on_timeout: Exception = None,
|
|
||||||
) -> DeviceStatus:
|
|
||||||
"""Wrapper around wait_for_signals to be started in thread and attach a DeviceStatus object.
|
|
||||||
This allows BEC to perform actinos in parallel and not be blocked by method calls on a device.
|
|
||||||
Typically used for on_trigger, on_complete methods or also the kickoff.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
signal_conditions (list[tuple]): tuple of executable calls for conditions (get_current_state, condition) to check
|
|
||||||
timeout (float): timeout in seconds
|
|
||||||
check_stopped (bool): True if stopped flag should be checked
|
|
||||||
interval (float): interval in seconds
|
|
||||||
all_signals (bool): True if all signals should be True, False if any signal should be True
|
|
||||||
exception_on_timeout (Exception): Exception to raise on timeout
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
DeviceStatus: DeviceStatus object that resolves either to set_finished or set_exception
|
|
||||||
"""
|
|
||||||
if exception_on_timeout is None:
|
|
||||||
exception_on_timeout = DeviceTimeoutError(
|
|
||||||
f"Timeout error for {self.name} while waiting for signals {signal_conditions}"
|
|
||||||
)
|
|
||||||
|
|
||||||
status = DeviceStatus(device=self)
|
|
||||||
|
|
||||||
def wait_for_signals_wrapper(
|
|
||||||
status: DeviceStatus,
|
|
||||||
signal_conditions: list[tuple],
|
|
||||||
timeout: float,
|
|
||||||
check_stopped: bool,
|
|
||||||
interval: float,
|
|
||||||
all_signals: bool,
|
|
||||||
exception_on_timeout: Exception = None,
|
|
||||||
):
|
|
||||||
"""Convenient wrapper around wait_for_signals to set status based on the result.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
status (DeviceStatus): DeviceStatus object to be set
|
|
||||||
signal_conditions (list[tuple]): tuple of executable calls for conditions (get_current_state, condition) to check
|
|
||||||
timeout (float): timeout in seconds
|
|
||||||
check_stopped (bool): True if stopped flag should be checked
|
|
||||||
interval (float): interval in seconds
|
|
||||||
all_signals (bool): True if all signals should be True, False if any signal should be True
|
|
||||||
exception_on_timeout (Exception): Exception to raise on timeout
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
result = self.wait_for_signals(
|
|
||||||
signal_conditions, timeout, check_stopped, interval, all_signals
|
|
||||||
)
|
|
||||||
if result is True:
|
|
||||||
# pylint: disable=protected-access
|
|
||||||
status.set_finished()
|
|
||||||
else:
|
|
||||||
if self.stopped:
|
|
||||||
# INFO This will execute a callback to the parent device.stop() method
|
|
||||||
status.set_exception(exc=DeviceStopError(f"{self.name} was stopped"))
|
|
||||||
else:
|
|
||||||
# INFO This will execute a callback to the parent device.stop() method
|
|
||||||
status.set_exception(exc=exception_on_timeout)
|
|
||||||
# pylint: disable=broad-except
|
|
||||||
except Exception as exc:
|
|
||||||
content = traceback.format_exc()
|
|
||||||
logger.warning(f"Error in wait_for_signals in {self.name}; Traceback: {content}")
|
|
||||||
# INFO This will execute a callback to the parent device.stop() method
|
|
||||||
status.set_exception(exc=exc)
|
|
||||||
|
|
||||||
thread = threading.Thread(
|
|
||||||
target=wait_for_signals_wrapper,
|
|
||||||
args=(
|
|
||||||
status,
|
|
||||||
signal_conditions,
|
|
||||||
timeout,
|
|
||||||
check_stopped,
|
|
||||||
interval,
|
|
||||||
all_signals,
|
|
||||||
exception_on_timeout,
|
|
||||||
),
|
|
||||||
daemon=True,
|
|
||||||
)
|
|
||||||
thread.start()
|
|
||||||
return status
|
|
||||||
0
debye_bec/devices/mo1_bragg/__init__.py
Normal file
0
debye_bec/devices/mo1_bragg/__init__.py
Normal file
480
debye_bec/devices/mo1_bragg/mo1_bragg.py
Normal file
480
debye_bec/devices/mo1_bragg/mo1_bragg.py
Normal file
@@ -0,0 +1,480 @@
|
|||||||
|
"""Module for the Mo1 Bragg positioner of the Debye beamline.
|
||||||
|
The softIOC is reachable via the EPICS prefix X01DA-OP-MO1:BRAGG: and connected
|
||||||
|
to a motor controller via web sockets. The Mo1 Bragg positioner is not only a
|
||||||
|
positioner, but also a scan controller to setup XAS and XRD scans. A few scan modes
|
||||||
|
are programmed in the controller, e.g. simple and advanced XAS scans + XRD triggering mode.
|
||||||
|
|
||||||
|
Note: For some of the Epics PVs, in particular action buttons, the put_complete=True is
|
||||||
|
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 bec_lib.devicemanager import ScanInfo
|
||||||
|
from bec_lib.logger import bec_logger
|
||||||
|
from ophyd import Component as Cpt
|
||||||
|
from ophyd import DeviceStatus, Signal, StatusBase
|
||||||
|
from ophyd.status import SubscriptionStatus, WaitTimeoutError
|
||||||
|
from ophyd_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
|
||||||
|
|
||||||
|
from debye_bec.devices.mo1_bragg.mo1_bragg_devices import Mo1BraggPositioner
|
||||||
|
|
||||||
|
# pylint: disable=unused-import
|
||||||
|
from debye_bec.devices.mo1_bragg.mo1_bragg_enums import (
|
||||||
|
MoveType,
|
||||||
|
ScanControlLoadMessage,
|
||||||
|
ScanControlMode,
|
||||||
|
ScanControlScanStatus,
|
||||||
|
TriggerControlMode,
|
||||||
|
TriggerControlSource,
|
||||||
|
)
|
||||||
|
from debye_bec.devices.mo1_bragg.mo1_bragg_utils import compute_spline
|
||||||
|
|
||||||
|
# Initialise logger
|
||||||
|
logger = bec_logger.logger
|
||||||
|
|
||||||
|
########### Exceptions ###########
|
||||||
|
|
||||||
|
|
||||||
|
class Mo1BraggError(Exception):
|
||||||
|
"""Exception for the Mo1 Bragg positioner"""
|
||||||
|
|
||||||
|
|
||||||
|
########## Scan Parameter Model ##########
|
||||||
|
|
||||||
|
|
||||||
|
class ScanParameter(BaseModel):
|
||||||
|
"""Dataclass to store the scan parameters for the Mo1 Bragg positioner.
|
||||||
|
This needs to be in sync with the kwargs of the MO1 Bragg 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")
|
||||||
|
p_kink: float | None = Field(None, description="P Kink")
|
||||||
|
e_kink: float | None = Field(None, description="Energy Kink")
|
||||||
|
model_config: dict = {"validate_assignment": True}
|
||||||
|
|
||||||
|
|
||||||
|
########### Mo1 Bragg Motor Class ###########
|
||||||
|
|
||||||
|
|
||||||
|
class Mo1Bragg(PSIDeviceBase, Mo1BraggPositioner):
|
||||||
|
"""Mo1 Bragg motor for the Debye beamline.
|
||||||
|
|
||||||
|
The prefix to connect to the soft IOC is X01DA-OP-MO1:BRAGG:
|
||||||
|
"""
|
||||||
|
|
||||||
|
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
|
||||||
|
"""
|
||||||
|
Initialize the PSI Device Base class.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
name (str) : Name of the device
|
||||||
|
scan_info (ScanInfo): The scan info to use.
|
||||||
|
"""
|
||||||
|
super().__init__(name=name, scan_info=scan_info, prefix=prefix, **kwargs)
|
||||||
|
self.scan_parameter = ScanParameter()
|
||||||
|
self.timeout_for_pvwait = 7.5
|
||||||
|
|
||||||
|
########################################
|
||||||
|
# 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.scan_control.scan_progress.subscribe(self._progress_update, run=False)
|
||||||
|
|
||||||
|
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.
|
||||||
|
"""
|
||||||
|
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()
|
||||||
|
if scan_name == "xas_simple_scan":
|
||||||
|
self.set_xas_settings(
|
||||||
|
low=self.scan_parameter.start,
|
||||||
|
high=self.scan_parameter.stop,
|
||||||
|
scan_time=self.scan_parameter.scan_time,
|
||||||
|
)
|
||||||
|
self.set_trig_settings(
|
||||||
|
enable_low=False,
|
||||||
|
enable_high=False,
|
||||||
|
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
|
||||||
|
)
|
||||||
|
elif scan_name == "xas_simple_scan_with_xrd":
|
||||||
|
self.set_xas_settings(
|
||||||
|
low=self.scan_parameter.start,
|
||||||
|
high=self.scan_parameter.stop,
|
||||||
|
scan_time=self.scan_parameter.scan_time,
|
||||||
|
)
|
||||||
|
self.set_trig_settings(
|
||||||
|
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
|
||||||
|
)
|
||||||
|
elif scan_name == "xas_advanced_scan":
|
||||||
|
self.set_advanced_xas_settings(
|
||||||
|
low=self.scan_parameter.start,
|
||||||
|
high=self.scan_parameter.stop,
|
||||||
|
scan_time=self.scan_parameter.scan_time,
|
||||||
|
p_kink=self.scan_parameter.p_kink,
|
||||||
|
e_kink=self.scan_parameter.e_kink,
|
||||||
|
)
|
||||||
|
self.set_trig_settings(
|
||||||
|
enable_low=False,
|
||||||
|
enable_high=False,
|
||||||
|
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
|
||||||
|
)
|
||||||
|
elif scan_name == "xas_advanced_scan_with_xrd":
|
||||||
|
self.set_advanced_xas_settings(
|
||||||
|
low=self.scan_parameter.start,
|
||||||
|
high=self.scan_parameter.stop,
|
||||||
|
scan_time=self.scan_parameter.scan_time,
|
||||||
|
p_kink=self.scan_parameter.p_kink,
|
||||||
|
e_kink=self.scan_parameter.e_kink,
|
||||||
|
)
|
||||||
|
self.set_trig_settings(
|
||||||
|
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.wait(self.timeout_for_pvwait)
|
||||||
|
return None
|
||||||
|
|
||||||
|
def on_unstage(self) -> DeviceStatus | StatusBase | None:
|
||||||
|
"""Called while unstaging the device."""
|
||||||
|
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:
|
||||||
|
status.wait(2)
|
||||||
|
return None
|
||||||
|
except WaitTimeoutError:
|
||||||
|
logger.warning(
|
||||||
|
f"Timeout in on_unstage of {self.name} after {self.timeout_for_pvwait}s, current scan_control_message : {self.scan_control.scan_msg.get()}"
|
||||||
|
)
|
||||||
|
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:
|
||||||
|
"""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."""
|
||||||
|
status = CompareStatus(self.scan_control.scan_done, 1)
|
||||||
|
self.cancel_on_stop(status)
|
||||||
|
return status
|
||||||
|
|
||||||
|
def on_kickoff(self) -> DeviceStatus | StatusBase | None:
|
||||||
|
"""Called to kickoff a device for a fly scan. Has to be called explicitly."""
|
||||||
|
scan_duration = self.scan_control.scan_duration.get()
|
||||||
|
# TODO implement better logic for infinite scans, at least bring it up with Debye
|
||||||
|
start_func = (
|
||||||
|
self.scan_control.scan_start_infinite.put
|
||||||
|
if scan_duration < 0.1
|
||||||
|
else self.scan_control.scan_start_timer.put
|
||||||
|
)
|
||||||
|
status = TransitionStatus(
|
||||||
|
self.scan_control.scan_status,
|
||||||
|
transitions=[ScanControlScanStatus.READY, ScanControlScanStatus.RUNNING],
|
||||||
|
strict=True,
|
||||||
|
raise_states=[ScanControlScanStatus.PARAMETER_WRONG],
|
||||||
|
)
|
||||||
|
self.cancel_on_stop(status)
|
||||||
|
start_func(1)
|
||||||
|
return status
|
||||||
|
|
||||||
|
def on_stop(self) -> None:
|
||||||
|
"""Called when the device is stopped."""
|
||||||
|
self.stopped = True # Needs to be set to stop motion
|
||||||
|
|
||||||
|
######### Utility Methods #########
|
||||||
|
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
|
||||||
|
"""
|
||||||
|
max_value = 100
|
||||||
|
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.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
low (float): Low energy/angle value of the scan
|
||||||
|
high (float): High energy/angle value of the scan
|
||||||
|
scan_time (float): Time for a half oscillation
|
||||||
|
"""
|
||||||
|
|
||||||
|
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(
|
||||||
|
self, mode: Literal["AngleToEnergy", "EnergyToAngle"], inp: float
|
||||||
|
) -> float:
|
||||||
|
"""Calculate energy to angle or vice versa
|
||||||
|
|
||||||
|
Args:
|
||||||
|
mode (Literal["AngleToEnergy", "EnergyToAngle"]): Mode of calculation
|
||||||
|
input (float): Either angle or energy
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
output (float): Converted angle or energy
|
||||||
|
"""
|
||||||
|
self.calculator.calc_reset.put(0)
|
||||||
|
self.calculator.calc_reset.put(1)
|
||||||
|
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)
|
||||||
|
|
||||||
|
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":
|
||||||
|
return self.calculator.calc_angle.get()
|
||||||
|
|
||||||
|
def set_advanced_xas_settings(
|
||||||
|
self, low: float, high: float, scan_time: float, p_kink: float, e_kink: float
|
||||||
|
) -> None:
|
||||||
|
"""Set Advanced XAS parameters for upcoming scan.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
low (float): Low angle value of the scan in eV
|
||||||
|
high (float): High angle value of the scan in eV
|
||||||
|
scan_time (float): Time for a half oscillation in s
|
||||||
|
p_kink (float): Position of kink in %
|
||||||
|
e_kink (float): Energy of kink in eV
|
||||||
|
"""
|
||||||
|
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,
|
||||||
|
high_deg=high_deg,
|
||||||
|
p_kink=p_kink,
|
||||||
|
e_kink_deg=e_kink_deg,
|
||||||
|
scan_time=scan_time,
|
||||||
|
)
|
||||||
|
|
||||||
|
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,
|
||||||
|
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
|
||||||
|
break_time_low (float): Exposure time for low energy/angle
|
||||||
|
break_time_high (float): Exposure time for high energy/angle
|
||||||
|
cycle_low (int): Cycle for low energy/angle
|
||||||
|
cycle_high (int): Cycle for high energy/angle
|
||||||
|
exp_time (float): Length of 1 trigger period in seconds
|
||||||
|
n_of_trigger (int): Amount of triggers to be fired during brake
|
||||||
|
"""
|
||||||
|
|
||||||
|
status_list = []
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
mode (ScanControlMode): Mode for the scan, either simple or advanced
|
||||||
|
scan_duration (float): Duration of the scan
|
||||||
|
"""
|
||||||
|
val = ScanControlMode(mode).value
|
||||||
|
|
||||||
|
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."""
|
||||||
|
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)
|
||||||
20
debye_bec/devices/mo1_bragg/mo1_bragg_angle.py
Normal file
20
debye_bec/devices/mo1_bragg/mo1_bragg_angle.py
Normal 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"
|
||||||
426
debye_bec/devices/mo1_bragg/mo1_bragg_devices.py
Normal file
426
debye_bec/devices/mo1_bragg/mo1_bragg_devices.py
Normal file
@@ -0,0 +1,426 @@
|
|||||||
|
"""Module for the Mo1 Bragg positioner"""
|
||||||
|
|
||||||
|
import threading
|
||||||
|
import time
|
||||||
|
import traceback
|
||||||
|
from typing import Literal
|
||||||
|
|
||||||
|
from bec_lib.logger import bec_logger
|
||||||
|
from ophyd import Component as Cpt
|
||||||
|
from ophyd import (
|
||||||
|
Device,
|
||||||
|
DeviceStatus,
|
||||||
|
EpicsSignal,
|
||||||
|
EpicsSignalRO,
|
||||||
|
EpicsSignalWithRBV,
|
||||||
|
PositionerBase,
|
||||||
|
Signal,
|
||||||
|
)
|
||||||
|
from ophyd.utils import LimitError
|
||||||
|
|
||||||
|
from debye_bec.devices.mo1_bragg.mo1_bragg_enums import MoveType
|
||||||
|
|
||||||
|
# Initialise logger
|
||||||
|
logger = bec_logger.logger
|
||||||
|
|
||||||
|
############# Exceptions #############
|
||||||
|
|
||||||
|
|
||||||
|
class Mo1BraggStoppedError(Exception):
|
||||||
|
"""Exception to raise when the Bragg positioner is stopped."""
|
||||||
|
|
||||||
|
|
||||||
|
############# Signal classes #############
|
||||||
|
|
||||||
|
|
||||||
|
class MoveTypeSignal(Signal):
|
||||||
|
"""Custom Signal to set the move type of the Bragg positioner"""
|
||||||
|
|
||||||
|
# pylint: disable=arguments-differ
|
||||||
|
def set(self, value: str | MoveType) -> None:
|
||||||
|
"""Returns currently active move method
|
||||||
|
|
||||||
|
Args:
|
||||||
|
value (str | MoveType) : Can be either 'energy' or 'angle'
|
||||||
|
"""
|
||||||
|
|
||||||
|
value = MoveType(value.lower())
|
||||||
|
self._readback = value.value
|
||||||
|
|
||||||
|
|
||||||
|
############# Utility devices to separate the namespace #############
|
||||||
|
|
||||||
|
|
||||||
|
class Mo1BraggStatus(Device):
|
||||||
|
"""Mo1 Bragg PVs for status monitoring"""
|
||||||
|
|
||||||
|
error_status = Cpt(EpicsSignalRO, suffix="error_status_RBV", kind="config", auto_monitor=True)
|
||||||
|
brake_enabled = Cpt(EpicsSignalRO, suffix="brake_enabled_RBV", kind="config", auto_monitor=True)
|
||||||
|
mot_commutated = Cpt(
|
||||||
|
EpicsSignalRO, suffix="mot_commutated_RBV", kind="config", auto_monitor=True
|
||||||
|
)
|
||||||
|
axis_enabled = Cpt(EpicsSignalRO, suffix="axis_enabled_RBV", kind="config", auto_monitor=True)
|
||||||
|
enc_initialized = Cpt(
|
||||||
|
EpicsSignalRO, suffix="enc_initialized_RBV", kind="config", auto_monitor=True
|
||||||
|
)
|
||||||
|
heartbeat = Cpt(EpicsSignalRO, suffix="heartbeat_RBV", kind="config", auto_monitor=True)
|
||||||
|
|
||||||
|
|
||||||
|
class Mo1BraggEncoder(Device):
|
||||||
|
"""Mo1 Bragg PVs to communicate with the encoder"""
|
||||||
|
|
||||||
|
enc_reinit = Cpt(EpicsSignal, suffix="enc_reinit", kind="config")
|
||||||
|
enc_reinit_done = Cpt(EpicsSignalRO, suffix="enc_reinit_done_RBV", kind="config")
|
||||||
|
|
||||||
|
|
||||||
|
class Mo1BraggCrystal(Device):
|
||||||
|
"""Mo1 Bragg PVs to set the crystal parameters"""
|
||||||
|
|
||||||
|
offset_si111 = Cpt(EpicsSignalWithRBV, suffix="offset_si111", kind="config")
|
||||||
|
offset_si311 = Cpt(EpicsSignalWithRBV, suffix="offset_si311", kind="config")
|
||||||
|
xtal_enum = Cpt(EpicsSignalWithRBV, suffix="xtal_ENUM", kind="config")
|
||||||
|
d_spacing_si111 = Cpt(EpicsSignalWithRBV, suffix="d_spacing_si111", kind="config")
|
||||||
|
d_spacing_si311 = Cpt(EpicsSignalWithRBV, suffix="d_spacing_si311", kind="config")
|
||||||
|
set_offset = Cpt(EpicsSignal, suffix="set_offset", kind="config", put_complete=True)
|
||||||
|
current_d_spacing = Cpt(
|
||||||
|
EpicsSignalRO, suffix="current_d_spacing_RBV", kind="normal", auto_monitor=True
|
||||||
|
)
|
||||||
|
current_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"""
|
||||||
|
|
||||||
|
# TRIG settings
|
||||||
|
trig_select_ref_enum = Cpt(EpicsSignalWithRBV, suffix="trig_select_ref_ENUM", kind="config")
|
||||||
|
|
||||||
|
trig_ena_hi_enum = Cpt(EpicsSignalWithRBV, suffix="trig_ena_hi_ENUM", kind="config")
|
||||||
|
trig_time_hi = Cpt(EpicsSignalWithRBV, suffix="trig_time_hi", kind="config")
|
||||||
|
trig_every_n_hi = Cpt(EpicsSignalWithRBV, suffix="trig_every_n_hi", kind="config")
|
||||||
|
|
||||||
|
trig_ena_lo_enum = Cpt(EpicsSignalWithRBV, suffix="trig_ena_lo_ENUM", kind="config")
|
||||||
|
trig_time_lo = Cpt(EpicsSignalWithRBV, suffix="trig_time_lo", kind="config")
|
||||||
|
trig_every_n_lo = Cpt(EpicsSignalWithRBV, suffix="trig_every_n_lo", kind="config")
|
||||||
|
|
||||||
|
# XAS simple scan settings
|
||||||
|
s_scan_angle_hi = Cpt(EpicsSignalWithRBV, suffix="s_scan_angle_hi", kind="config")
|
||||||
|
s_scan_angle_lo = Cpt(EpicsSignalWithRBV, suffix="s_scan_angle_lo", kind="config")
|
||||||
|
s_scan_energy_lo = Cpt(
|
||||||
|
EpicsSignalWithRBV, suffix="s_scan_energy_lo", kind="config", auto_monitor=True
|
||||||
|
)
|
||||||
|
s_scan_energy_hi = Cpt(
|
||||||
|
EpicsSignalWithRBV, suffix="s_scan_energy_hi", kind="config", auto_monitor=True
|
||||||
|
)
|
||||||
|
s_scan_scantime = Cpt(
|
||||||
|
EpicsSignalWithRBV, suffix="s_scan_scantime", kind="config", auto_monitor=True
|
||||||
|
)
|
||||||
|
|
||||||
|
# XAS advanced scan settings
|
||||||
|
a_scan_pos = Cpt(EpicsSignalWithRBV, suffix="a_scan_pos", kind="config", auto_monitor=True)
|
||||||
|
a_scan_vel = Cpt(EpicsSignalWithRBV, suffix="a_scan_vel", kind="config", auto_monitor=True)
|
||||||
|
a_scan_time = Cpt(EpicsSignalWithRBV, suffix="a_scan_time", kind="config", auto_monitor=True)
|
||||||
|
|
||||||
|
|
||||||
|
class Mo1TriggerSettings(Device):
|
||||||
|
"""Mo1 Trigger settings"""
|
||||||
|
|
||||||
|
settle_time = Cpt(EpicsSignalWithRBV, suffix="settle_time", kind="config")
|
||||||
|
max_dev = Cpt(EpicsSignalWithRBV, suffix="max_dev", kind="config")
|
||||||
|
|
||||||
|
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")
|
||||||
|
|
||||||
|
|
||||||
|
class Mo1BraggCalculator(Device):
|
||||||
|
"""Mo1 Bragg PVs to convert angle to energy or vice-versa."""
|
||||||
|
|
||||||
|
calc_reset = Cpt(EpicsSignal, suffix="calc_reset", kind="config", put_complete=True)
|
||||||
|
calc_done = Cpt(EpicsSignalRO, suffix="calc_done_RBV", kind="config")
|
||||||
|
calc_energy = Cpt(EpicsSignalWithRBV, suffix="calc_energy", kind="config")
|
||||||
|
calc_angle = Cpt(EpicsSignalWithRBV, suffix="calc_angle", kind="config")
|
||||||
|
|
||||||
|
|
||||||
|
class Mo1BraggScanControl(Device):
|
||||||
|
"""Mo1 Bragg PVs to control the scan after setting the parameters."""
|
||||||
|
|
||||||
|
scan_mode_enum = Cpt(EpicsSignalWithRBV, suffix="scan_mode_ENUM", kind="config")
|
||||||
|
scan_duration = Cpt(
|
||||||
|
EpicsSignalWithRBV, suffix="scan_duration", kind="config", auto_monitor=True
|
||||||
|
)
|
||||||
|
scan_load = Cpt(EpicsSignal, suffix="scan_load", kind="config", put_complete=True)
|
||||||
|
scan_msg = Cpt(EpicsSignalRO, suffix="scan_msg_ENUM_RBV", kind="config", auto_monitor=True)
|
||||||
|
scan_start_infinite = Cpt(
|
||||||
|
EpicsSignal, suffix="scan_start_infinite", kind="config", put_complete=True
|
||||||
|
)
|
||||||
|
scan_start_timer = Cpt(EpicsSignal, suffix="scan_start_timer", kind="config", put_complete=True)
|
||||||
|
scan_stop = Cpt(EpicsSignal, suffix="scan_stop", kind="config", put_complete=True)
|
||||||
|
scan_status = Cpt(
|
||||||
|
EpicsSignalRO, suffix="scan_status_ENUM_RBV", kind="config", auto_monitor=True
|
||||||
|
)
|
||||||
|
scan_time_left = Cpt(
|
||||||
|
EpicsSignalRO, suffix="scan_time_left_RBV", kind="config", auto_monitor=True
|
||||||
|
)
|
||||||
|
scan_done = Cpt(EpicsSignalRO, suffix="scan_done_RBV", kind="config", auto_monitor=True)
|
||||||
|
scan_val_reset = Cpt(EpicsSignal, suffix="scan_val_reset", kind="config", put_complete=True)
|
||||||
|
scan_progress = Cpt(EpicsSignalRO, suffix="scan_progress_RBV", kind="config", auto_monitor=True)
|
||||||
|
scan_spectra_done = Cpt(
|
||||||
|
EpicsSignalRO, suffix="scan_n_osc_RBV", kind="config", auto_monitor=True
|
||||||
|
)
|
||||||
|
scan_spectra_left = Cpt(
|
||||||
|
EpicsSignalRO, suffix="scan_n_osc_left_RBV", kind="config", auto_monitor=True
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class Mo1BraggPositioner(Device, PositionerBase):
|
||||||
|
"""
|
||||||
|
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_xtal"]
|
||||||
|
|
||||||
|
####### Sub-components ########
|
||||||
|
# Namespace is cleaner and easier to maintain
|
||||||
|
crystal = Cpt(Mo1BraggCrystal, "")
|
||||||
|
encoder = Cpt(Mo1BraggEncoder, "")
|
||||||
|
scan_settings = Cpt(Mo1BraggScanSettings, "")
|
||||||
|
trigger_settings = Cpt(Mo1TriggerSettings, "")
|
||||||
|
calculator = Cpt(Mo1BraggCalculator, "")
|
||||||
|
scan_control = Cpt(Mo1BraggScanControl, "")
|
||||||
|
status = Cpt(Mo1BraggStatus, "")
|
||||||
|
|
||||||
|
############# Energy PVs #############
|
||||||
|
|
||||||
|
readback = Cpt(
|
||||||
|
EpicsSignalRO, suffix="feedback_pos_energy_RBV", kind="hinted", auto_monitor=True
|
||||||
|
)
|
||||||
|
setpoint = Cpt(
|
||||||
|
EpicsSignalWithRBV, suffix="set_abs_pos_energy", kind="normal", auto_monitor=True
|
||||||
|
)
|
||||||
|
motor_is_moving = Cpt(
|
||||||
|
EpicsSignalRO, suffix="move_abs_done_RBV", kind="normal", auto_monitor=True
|
||||||
|
)
|
||||||
|
low_lim = Cpt(EpicsSignalRO, suffix="lo_lim_pos_energy_RBV", kind="config", auto_monitor=True)
|
||||||
|
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)
|
||||||
|
|
||||||
|
########## Move Command PVs ##########
|
||||||
|
|
||||||
|
move_abs = Cpt(EpicsSignal, suffix="move_abs", kind="config", put_complete=True)
|
||||||
|
move_stop = Cpt(EpicsSignal, suffix="move_stop", kind="config", put_complete=True)
|
||||||
|
|
||||||
|
SUB_READBACK = "readback"
|
||||||
|
_default_sub = SUB_READBACK
|
||||||
|
SUB_PROGRESS = "progress"
|
||||||
|
|
||||||
|
def __init__(self, prefix="", *, name: str, **kwargs):
|
||||||
|
"""Initialize the Mo1 Bragg positioner.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
prefix (str): EPICS prefix for the device
|
||||||
|
name (str): Name of the device
|
||||||
|
kwargs: Additional keyword arguments
|
||||||
|
"""
|
||||||
|
super().__init__(prefix, name=name, **kwargs)
|
||||||
|
self._move_thread = None
|
||||||
|
self._stopped = False
|
||||||
|
self.readback.name = self.name
|
||||||
|
|
||||||
|
def stop(self, *, success=False) -> None:
|
||||||
|
"""Stop any motion on the positioner
|
||||||
|
|
||||||
|
Args:
|
||||||
|
success (bool) : Flag to indicate if the motion was successful
|
||||||
|
"""
|
||||||
|
self.move_stop.put(1)
|
||||||
|
self._stopped = True
|
||||||
|
super().stop(success=success)
|
||||||
|
|
||||||
|
def stop_scan(self) -> None:
|
||||||
|
"""Stop the currently running scan gracefully, this finishes the running oscillation."""
|
||||||
|
self.scan_control.scan_stop.put(1)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def stopped(self) -> bool:
|
||||||
|
"""Return the status of the positioner"""
|
||||||
|
return self._stopped
|
||||||
|
|
||||||
|
######### Positioner specific methods #########
|
||||||
|
|
||||||
|
@property
|
||||||
|
def limits(self) -> tuple:
|
||||||
|
"""Return limits of the Bragg positioner"""
|
||||||
|
return (self.low_lim.get(), self.high_lim.get())
|
||||||
|
|
||||||
|
@property
|
||||||
|
def low_limit(self) -> float:
|
||||||
|
"""Return low limit of axis"""
|
||||||
|
return self.limits[0]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def high_limit(self) -> float:
|
||||||
|
"""Return high limit of axis"""
|
||||||
|
return self.limits[1]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def egu(self) -> str:
|
||||||
|
"""Return the engineering units of the positioner"""
|
||||||
|
return "eV"
|
||||||
|
|
||||||
|
@property
|
||||||
|
def position(self) -> float:
|
||||||
|
"""Return the current position of Mo1Bragg, considering the move type"""
|
||||||
|
return self.readback.get()
|
||||||
|
|
||||||
|
# pylint: disable=arguments-differ
|
||||||
|
def check_value(self, value: float) -> None:
|
||||||
|
"""Method to check if a value is within limits of the positioner.
|
||||||
|
Called by PositionerBase.move()
|
||||||
|
|
||||||
|
Args:
|
||||||
|
value (float) : value to move axis to.
|
||||||
|
"""
|
||||||
|
low_limit, high_limit = self.limits
|
||||||
|
|
||||||
|
if low_limit < high_limit and not low_limit <= value <= high_limit:
|
||||||
|
raise LimitError(f"position={value} not within limits {self.limits}")
|
||||||
|
|
||||||
|
def _move_and_finish(
|
||||||
|
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
|
||||||
|
to the target position.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
target_pos (float) : target position for the motion
|
||||||
|
move_cpt (Cpt) : component to set the target position on the IOC,
|
||||||
|
either setpoint or setpoint_abs_angle depending
|
||||||
|
on the move type
|
||||||
|
read_cpt (Cpt) : component to read the current position of the motion,
|
||||||
|
readback or feedback_pos_angle
|
||||||
|
status (DeviceStatus) : status object to set the status of the motion
|
||||||
|
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
|
||||||
|
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")
|
||||||
|
time.sleep(update_frequency)
|
||||||
|
# pylint: disable=protected-access
|
||||||
|
status.set_finished()
|
||||||
|
# pylint: disable=broad-except
|
||||||
|
except Exception as exc:
|
||||||
|
content = traceback.format_exc()
|
||||||
|
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, **kwargs) -> DeviceStatus:
|
||||||
|
"""
|
||||||
|
Move the Bragg positioner to the specified value, allows to
|
||||||
|
switch between move types angle and energy.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
value (float) : target value for the motion
|
||||||
|
move_type (str | MoveType) : Optional, specify the type of move,
|
||||||
|
either 'energy' or 'angle'
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
DeviceStatus : status object to track the motion
|
||||||
|
"""
|
||||||
|
self._stopped = False
|
||||||
|
|
||||||
|
self.check_value(value)
|
||||||
|
status = DeviceStatus(device=self)
|
||||||
|
|
||||||
|
self._move_thread = threading.Thread(
|
||||||
|
target=self._move_and_finish, args=(value, status, 0.1)
|
||||||
|
)
|
||||||
|
self._move_thread.start()
|
||||||
|
return status
|
||||||
|
|
||||||
|
# -------------- End of Positioner specific methods -----------------#
|
||||||
|
|
||||||
|
# -------------- MO1 Bragg specific methods -----------------#
|
||||||
|
|
||||||
|
def set_xtal(
|
||||||
|
self,
|
||||||
|
xtal_enum: Literal["111", "311"],
|
||||||
|
offset_si111: float = None,
|
||||||
|
offset_si311: float = None,
|
||||||
|
d_spacing_si111: float = None,
|
||||||
|
d_spacing_si311: float = None,
|
||||||
|
) -> None:
|
||||||
|
"""Method to set the crystal parameters of the Bragg positioner
|
||||||
|
|
||||||
|
Args:
|
||||||
|
xtal_enum (Literal["111", "311"]) : Enum to set the crystal orientation
|
||||||
|
offset_si111 (float) : Offset for the 111 crystal
|
||||||
|
offset_si311 (float) : Offset for the 311 crystal
|
||||||
|
d_spacing_si111 (float) : d-spacing for the 111 crystal
|
||||||
|
d_spacing_si311 (float) : d-spacing for the 311 crystal
|
||||||
|
"""
|
||||||
|
if offset_si111 is not None:
|
||||||
|
self.crystal.offset_si111.put(offset_si111)
|
||||||
|
if offset_si311 is not None:
|
||||||
|
self.crystal.offset_si311.put(offset_si311)
|
||||||
|
if d_spacing_si111 is not None:
|
||||||
|
self.crystal.d_spacing_si111.put(d_spacing_si111)
|
||||||
|
if d_spacing_si311 is not None:
|
||||||
|
self.crystal.d_spacing_si311.put(d_spacing_si311)
|
||||||
|
if xtal_enum == "111":
|
||||||
|
crystal_set = 0
|
||||||
|
elif xtal_enum == "311":
|
||||||
|
crystal_set = 1
|
||||||
|
else:
|
||||||
|
raise ValueError(
|
||||||
|
f"Invalid argument for xtal_enum : {xtal_enum}, choose from '111' or '311'"
|
||||||
|
)
|
||||||
|
self.crystal.xtal_enum.put(crystal_set)
|
||||||
|
self.crystal.set_offset.put(1)
|
||||||
61
debye_bec/devices/mo1_bragg/mo1_bragg_enums.py
Normal file
61
debye_bec/devices/mo1_bragg/mo1_bragg_enums.py
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
"""Enums for the Bragg positioner and trigger generator"""
|
||||||
|
|
||||||
|
import enum
|
||||||
|
|
||||||
|
|
||||||
|
class TriggerControlSource(int, enum.Enum):
|
||||||
|
"""Enum class for the trigger control source of the trigger generator"""
|
||||||
|
|
||||||
|
EPICS = 0
|
||||||
|
INPOS = 1
|
||||||
|
|
||||||
|
|
||||||
|
class TriggerControlMode(int, enum.Enum):
|
||||||
|
"""Enum class for the trigger control mode of the trigger generator"""
|
||||||
|
|
||||||
|
PULSE = 0
|
||||||
|
CONDITION = 1
|
||||||
|
|
||||||
|
|
||||||
|
class ScanControlScanStatus(int, enum.Enum):
|
||||||
|
"""Enum class for the scan status of the Bragg positioner"""
|
||||||
|
|
||||||
|
PARAMETER_WRONG = 0
|
||||||
|
VALIDATION_PENDING = 1
|
||||||
|
READY = 2
|
||||||
|
RUNNING = 3
|
||||||
|
|
||||||
|
|
||||||
|
class ScanControlLoadMessage(int, enum.Enum):
|
||||||
|
"""Enum for validating messages for load message of the Bragg positioner"""
|
||||||
|
|
||||||
|
PENDING = 0
|
||||||
|
STARTED = 1
|
||||||
|
SUCCESS = 2
|
||||||
|
ERR_TRIG_MEAS_LEN_LOW = 3
|
||||||
|
ERR_TRIG_N_TRIGGERS_LOW = 4
|
||||||
|
ERR_TRIG_TRIGS_EVERY_N_LOW = 5
|
||||||
|
ERR_TRIG_MEAS_LEN_HI = 6
|
||||||
|
ERR_TRIG_N_TRIGGERS_HI = 7
|
||||||
|
ERR_TRIG_TRIGS_EVERY_N_HI = 8
|
||||||
|
ERR_SCAN_HI_ANGLE_LIMIT = 9
|
||||||
|
ERR_SCAN_LOW_ANGLE_LIMITS = 10
|
||||||
|
ERR_SCAN_TIME = 11
|
||||||
|
ERR_SCAN_VEL_TOO_HI = 12
|
||||||
|
ERR_SCAN_ANGLE_OUT_OF_LIM = 13
|
||||||
|
ERR_SCAN_HIGH_VEL_LAR_42 = 14
|
||||||
|
ERR_SCAN_MODE_INVALID = 15
|
||||||
|
|
||||||
|
|
||||||
|
class MoveType(str, enum.Enum):
|
||||||
|
"""Enum class to switch between move types energy and angle for the Bragg positioner"""
|
||||||
|
|
||||||
|
ENERGY = "energy"
|
||||||
|
ANGLE = "angle"
|
||||||
|
|
||||||
|
|
||||||
|
class ScanControlMode(int, enum.Enum):
|
||||||
|
"""Enum class for the scan control mode of the Bragg positioner"""
|
||||||
|
|
||||||
|
SIMPLE = 0
|
||||||
|
ADVANCED = 1
|
||||||
93
debye_bec/devices/mo1_bragg/mo1_bragg_utils.py
Normal file
93
debye_bec/devices/mo1_bragg/mo1_bragg_utils.py
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
"""Module for additional utils of the Mo1 Bragg Positioner"""
|
||||||
|
|
||||||
|
import numpy as np
|
||||||
|
from scipy.interpolate import BSpline
|
||||||
|
|
||||||
|
################ Define Constants ############
|
||||||
|
SAFETY_FACTOR = 0.025 # safety factor to limit acceleration -> NEVER SET TO ZERO !
|
||||||
|
N_SAMPLES = 41 # number of samples to generate -> Always choose uneven number,
|
||||||
|
# otherwise peak value will not be included
|
||||||
|
DEGREE_SPLINE = 3 # DEGREE_SPLINE of spline, 3 works good
|
||||||
|
TIME_COMPENSATE_SPLINE = 0.0062 # time to be compensated each spline in s
|
||||||
|
POSITION_COMPONSATION = 0.02 # angle to add at both limits, must be same values
|
||||||
|
# as used on ACS controller for simple scans
|
||||||
|
|
||||||
|
|
||||||
|
class Mo1UtilsSplineError(Exception):
|
||||||
|
"""Exception for spline computation"""
|
||||||
|
|
||||||
|
|
||||||
|
def compute_spline(
|
||||||
|
low_deg: float, high_deg: float, p_kink: float, e_kink_deg: float, scan_time: float
|
||||||
|
) -> tuple[float, float, float]:
|
||||||
|
"""Spline computation for the advanced scan mode
|
||||||
|
|
||||||
|
Args:
|
||||||
|
low_deg (float): Low angle value of the scan in deg
|
||||||
|
high_deg (float): High angle value of the scan in deg
|
||||||
|
scan_time (float): Time for a half oscillation in s
|
||||||
|
p_kink (float): Position of kink in %
|
||||||
|
e_kink_deg (float): Position of kink in degree
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
tuple[float,float,float] : Position, Velocity and delta T arrays for the spline
|
||||||
|
"""
|
||||||
|
|
||||||
|
# increase motion range slightly so that xas trigger signals will occur at defined energy limits
|
||||||
|
low_deg = low_deg - POSITION_COMPONSATION
|
||||||
|
high_deg = high_deg + POSITION_COMPONSATION
|
||||||
|
|
||||||
|
if not (0 <= p_kink <= 100):
|
||||||
|
raise Mo1UtilsSplineError(
|
||||||
|
"Kink position not within range of [0..100%]" + f"for p_kink: {p_kink}"
|
||||||
|
)
|
||||||
|
|
||||||
|
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"
|
||||||
|
+ f"high_deg {high_deg}."
|
||||||
|
)
|
||||||
|
|
||||||
|
tc1 = SAFETY_FACTOR / scan_time * TIME_COMPENSATE_SPLINE
|
||||||
|
t_kink = (scan_time - TIME_COMPENSATE_SPLINE - 2 * (SAFETY_FACTOR - tc1)) * p_kink / 100 + (
|
||||||
|
SAFETY_FACTOR - tc1
|
||||||
|
)
|
||||||
|
|
||||||
|
t_input = [
|
||||||
|
0,
|
||||||
|
SAFETY_FACTOR - tc1,
|
||||||
|
t_kink,
|
||||||
|
scan_time - TIME_COMPENSATE_SPLINE - SAFETY_FACTOR + tc1,
|
||||||
|
scan_time - TIME_COMPENSATE_SPLINE,
|
||||||
|
]
|
||||||
|
p_input = [0, 0, e_kink_deg - low_deg, high_deg - low_deg, high_deg - low_deg]
|
||||||
|
|
||||||
|
cv = np.stack((t_input, p_input)).T # spline coefficients
|
||||||
|
max_param = len(cv) - DEGREE_SPLINE
|
||||||
|
kv = np.clip(np.arange(len(cv) + DEGREE_SPLINE + 1) - DEGREE_SPLINE, 0, max_param) # knots
|
||||||
|
spl = BSpline(kv, cv, DEGREE_SPLINE) # get spline function
|
||||||
|
p = spl(np.linspace(0, max_param, N_SAMPLES))
|
||||||
|
v = spl(np.linspace(0, max_param, N_SAMPLES), 1)
|
||||||
|
a = spl(np.linspace(0, max_param, N_SAMPLES), 2)
|
||||||
|
j = spl(np.linspace(0, max_param, N_SAMPLES), 3)
|
||||||
|
|
||||||
|
tim, pos = p.T
|
||||||
|
pos = pos + low_deg
|
||||||
|
vel = v[:, 1] / v[:, 0]
|
||||||
|
|
||||||
|
acc = []
|
||||||
|
for item in a:
|
||||||
|
acc.append(0) if item[1] == 0 else acc.append(item[1] / item[0])
|
||||||
|
jerk = []
|
||||||
|
for item in j:
|
||||||
|
jerk.append(0) if item[1] == 0 else jerk.append(item[1] / item[0])
|
||||||
|
|
||||||
|
dt = np.zeros(len(tim))
|
||||||
|
for i in np.arange(len(tim)):
|
||||||
|
if i == 0:
|
||||||
|
dt[i] = 0
|
||||||
|
else:
|
||||||
|
dt[i] = 1000 * (tim[i] - tim[i - 1])
|
||||||
|
|
||||||
|
return pos, vel, dt
|
||||||
0
debye_bec/devices/nidaq/__init__.py
Normal file
0
debye_bec/devices/nidaq/__init__.py
Normal file
642
debye_bec/devices/nidaq/nidaq.py
Normal file
642
debye_bec/devices/nidaq/nidaq.py
Normal file
@@ -0,0 +1,642 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import time
|
||||||
|
from typing import TYPE_CHECKING, Literal, cast
|
||||||
|
|
||||||
|
from bec_lib.logger import bec_logger
|
||||||
|
from ophyd import Component as Cpt
|
||||||
|
from ophyd import Device, DeviceStatus, EpicsSignal, EpicsSignalRO, Kind, StatusBase
|
||||||
|
from ophyd.status import SubscriptionStatus, WaitTimeoutError
|
||||||
|
from ophyd_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,
|
||||||
|
NidaqState,
|
||||||
|
ReadoutRange,
|
||||||
|
ScanRates,
|
||||||
|
ScanType,
|
||||||
|
)
|
||||||
|
|
||||||
|
if TYPE_CHECKING: # pragma: no cover
|
||||||
|
from bec_lib.devicemanager import ScanInfo
|
||||||
|
|
||||||
|
logger = bec_logger.logger
|
||||||
|
|
||||||
|
|
||||||
|
class NidaqError(Exception):
|
||||||
|
"""Nidaq specific error"""
|
||||||
|
|
||||||
|
|
||||||
|
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,
|
||||||
|
)
|
||||||
|
|
||||||
|
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,
|
||||||
|
)
|
||||||
|
|
||||||
|
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,
|
||||||
|
)
|
||||||
|
|
||||||
|
### Readback for BEC emitter ###
|
||||||
|
|
||||||
|
ai0_mean = Cpt(
|
||||||
|
SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream analog input 0, MEAN"
|
||||||
|
)
|
||||||
|
ai1_mean = Cpt(
|
||||||
|
SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream analog input 1, MEAN"
|
||||||
|
)
|
||||||
|
ai2_mean = Cpt(
|
||||||
|
SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream analog input 2, MEAN"
|
||||||
|
)
|
||||||
|
ai3_mean = Cpt(
|
||||||
|
SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream analog input 3, MEAN"
|
||||||
|
)
|
||||||
|
ai4_mean = Cpt(
|
||||||
|
SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream analog input 4, MEAN"
|
||||||
|
)
|
||||||
|
ai5_mean = Cpt(
|
||||||
|
SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream analog input 5, MEAN"
|
||||||
|
)
|
||||||
|
ai6_mean = Cpt(
|
||||||
|
SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream analog input 6, MEAN"
|
||||||
|
)
|
||||||
|
ai7_mean = Cpt(
|
||||||
|
SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream analog input 7, MEAN"
|
||||||
|
)
|
||||||
|
|
||||||
|
ai0_std_dev = Cpt(
|
||||||
|
SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream analog input 0, STD"
|
||||||
|
)
|
||||||
|
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_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"
|
||||||
|
)
|
||||||
|
|
||||||
|
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_energy = Cpt(SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream XRD energy")
|
||||||
|
|
||||||
|
di0_max = Cpt(SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream digital input 0, MAX")
|
||||||
|
di1_max = Cpt(SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream digital input 1, MAX")
|
||||||
|
di2_max = Cpt(SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream digital input 2, MAX")
|
||||||
|
di3_max = Cpt(SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream digital input 3, MAX")
|
||||||
|
di4_max = Cpt(SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream digital input 4, MAX")
|
||||||
|
|
||||||
|
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)
|
||||||
|
kickoff_call = Cpt(EpicsSignal, suffix="NIDAQ-Kickoff", kind=Kind.config)
|
||||||
|
stage_call = Cpt(EpicsSignal, suffix="NIDAQ-Stage", kind=Kind.config)
|
||||||
|
state = Cpt(EpicsSignal, suffix="NIDAQ-FSMState", kind=Kind.config, auto_monitor=True)
|
||||||
|
server_status = Cpt(EpicsSignalRO, suffix="NIDAQ-ServerStatus", kind=Kind.config)
|
||||||
|
compression_ratio = Cpt(EpicsSignalRO, suffix="NIDAQ-CompressionRatio", kind=Kind.config)
|
||||||
|
scan_type = Cpt(EpicsSignal, suffix="NIDAQ-ScanType", kind=Kind.config)
|
||||||
|
sampling_rate = Cpt(EpicsSignal, suffix="NIDAQ-SamplingRateRequested", kind=Kind.config)
|
||||||
|
scan_duration = Cpt(EpicsSignal, suffix="NIDAQ-SamplingDuration", kind=Kind.config)
|
||||||
|
readout_range = Cpt(EpicsSignal, suffix="NIDAQ-ReadoutRange", kind=Kind.config)
|
||||||
|
encoder_factor = Cpt(EpicsSignal, suffix="NIDAQ-EncoderFactor", kind=Kind.config)
|
||||||
|
stop_call = Cpt(EpicsSignal, suffix="NIDAQ-Stop", kind=Kind.config)
|
||||||
|
power = Cpt(EpicsSignal, suffix="NIDAQ-Power", kind=Kind.config)
|
||||||
|
heartbeat = Cpt(EpicsSignal, suffix="NIDAQ-Heartbeat", kind=Kind.config, auto_monitor=True)
|
||||||
|
time_left = Cpt(EpicsSignalRO, suffix="NIDAQ-TimeLeft", kind=Kind.config, auto_monitor=True)
|
||||||
|
|
||||||
|
ai_chans = Cpt(EpicsSignal, suffix="NIDAQ-AIChans", kind=Kind.config)
|
||||||
|
ci_chans = Cpt(EpicsSignal, suffix="NIDAQ-CIChans", kind=Kind.config)
|
||||||
|
di_chans = Cpt(EpicsSignal, suffix="NIDAQ-DIChans", kind=Kind.config)
|
||||||
|
|
||||||
|
|
||||||
|
class Nidaq(PSIDeviceBase, NidaqControl):
|
||||||
|
"""NIDAQ ophyd wrapper around the NIDAQ backend currently running at x01da-cons-05
|
||||||
|
|
||||||
|
Args:
|
||||||
|
prefix (str) : Prefix to the NIDAQ soft ioc, currently X01DA-PC-SCANSERVER:
|
||||||
|
name (str) : Name of the device
|
||||||
|
scan_info (ScanInfo) : ScanInfo object passed by BEC's devicemanager.
|
||||||
|
"""
|
||||||
|
|
||||||
|
progress_signal = Cpt(ProgressSignal, name="progress_signal")
|
||||||
|
|
||||||
|
USER_ACCESS = ["set_config"]
|
||||||
|
|
||||||
|
def __init__(self, prefix: str = "", *, name: str, scan_info: ScanInfo = None, **kwargs):
|
||||||
|
super().__init__(name=name, prefix=prefix, scan_info=scan_info, **kwargs)
|
||||||
|
self.scan_info: ScanInfo
|
||||||
|
self.timeout_wait_for_signal = 5 # put 5s firsts
|
||||||
|
self._timeout_wait_for_pv = 3 # 3s timeout for pv calls
|
||||||
|
self.valid_scan_names = [
|
||||||
|
"xas_simple_scan",
|
||||||
|
"xas_simple_scan_with_xrd",
|
||||||
|
"xas_advanced_scan",
|
||||||
|
"xas_advanced_scan_with_xrd",
|
||||||
|
"nidaq_continuous_scan",
|
||||||
|
]
|
||||||
|
|
||||||
|
########################################
|
||||||
|
# Beamline Methods #
|
||||||
|
########################################
|
||||||
|
|
||||||
|
def _check_if_scan_name_is_valid(self) -> bool:
|
||||||
|
"""Check if the scan is within the list of scans for which the backend is working"""
|
||||||
|
scan_name = self.scan_info.msg.scan_name
|
||||||
|
if scan_name in self.valid_scan_names:
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
def set_config(
|
||||||
|
self,
|
||||||
|
sampling_rate: Literal[
|
||||||
|
100000, 500000, 1000000, 2000000, 4000000, 5000000, 10000000, 14286000
|
||||||
|
],
|
||||||
|
ai: list,
|
||||||
|
ci: list,
|
||||||
|
di: list,
|
||||||
|
scan_type: Literal["continuous", "triggered"] = "triggered",
|
||||||
|
scan_duration: float = 0,
|
||||||
|
readout_range: Literal[1, 2, 5, 10] = 10,
|
||||||
|
encoder_type: Literal["X_1", "X_2", "X_4"] = "X_4",
|
||||||
|
enable_compression: bool = True,
|
||||||
|
) -> None:
|
||||||
|
"""Method to configure the NIDAQ
|
||||||
|
|
||||||
|
Args:
|
||||||
|
sampling_rate(Literal[100000, 500000, 1000000, 2000000, 4000000, 5000000,
|
||||||
|
10000000, 14286000]): Sampling rate in Hz
|
||||||
|
ai(list): List of analog input channel numbers to add, i.e. [0, 1, 2] for
|
||||||
|
input 0, 1 and 2
|
||||||
|
ci(list): List of counter input channel numbers to add, i.e. [0, 1, 2] for
|
||||||
|
input 0, 1 and 2
|
||||||
|
di(list): List of digital input channel numbers to add, i.e. [0, 1, 2] for
|
||||||
|
input 0, 1 and 2
|
||||||
|
scan_type(Literal['continuous', 'triggered']): Triggered to use with monochromator,
|
||||||
|
otherwise continuous, default 'triggered'
|
||||||
|
scan_duration(float): Scan duration in seconds, use 0 for infinite scan, default 0
|
||||||
|
readout_range(Literal[1, 2, 5, 10]): Readout range in +- Volts, default +-10V
|
||||||
|
encoder_type(Literal['X_1', 'X_2', 'X_4']): Encoder readout type, default 'X_4'
|
||||||
|
enable_compression(bool): Enable or disable compression of data, default True
|
||||||
|
|
||||||
|
"""
|
||||||
|
if sampling_rate == 100000:
|
||||||
|
self.sampling_rate.put(ScanRates.HUNDRED_KHZ)
|
||||||
|
elif sampling_rate == 500000:
|
||||||
|
self.sampling_rate.put(ScanRates.FIVE_HUNDRED_KHZ)
|
||||||
|
elif sampling_rate == 1000000:
|
||||||
|
self.sampling_rate.put(ScanRates.ONE_MHZ)
|
||||||
|
elif sampling_rate == 2000000:
|
||||||
|
self.sampling_rate.put(ScanRates.TWO_MHZ)
|
||||||
|
elif sampling_rate == 4000000:
|
||||||
|
self.sampling_rate.put(ScanRates.FOUR_MHZ)
|
||||||
|
elif sampling_rate == 5000000:
|
||||||
|
self.sampling_rate.put(ScanRates.FIVE_MHZ)
|
||||||
|
elif sampling_rate == 10000000:
|
||||||
|
self.sampling_rate.put(ScanRates.TEN_MHZ)
|
||||||
|
elif sampling_rate == 14286000:
|
||||||
|
self.sampling_rate.put(ScanRates.FOURTEEN_THREE_MHZ)
|
||||||
|
|
||||||
|
ai_chans = 0
|
||||||
|
if isinstance(ai, list):
|
||||||
|
for ch in ai:
|
||||||
|
if isinstance(ch, int):
|
||||||
|
if ch >= 0 and ch <= 7:
|
||||||
|
ai_chans = ai_chans | (1 << ch)
|
||||||
|
self.ai_chans.put(ai_chans)
|
||||||
|
|
||||||
|
ci_chans = 0
|
||||||
|
if isinstance(ci, list):
|
||||||
|
for ch in ci:
|
||||||
|
if isinstance(ch, int):
|
||||||
|
if ch >= 0 and ch <= 7:
|
||||||
|
ci_chans = ci_chans | (1 << ch)
|
||||||
|
self.ci_chans.put(ci_chans)
|
||||||
|
|
||||||
|
di_chans = 0
|
||||||
|
if isinstance(di, list):
|
||||||
|
for ch in di:
|
||||||
|
if isinstance(ch, int):
|
||||||
|
if ch >= 0 and ch <= 4:
|
||||||
|
di_chans = di_chans | (1 << ch)
|
||||||
|
self.di_chans.put(di_chans)
|
||||||
|
|
||||||
|
if scan_type in "continuous":
|
||||||
|
self.scan_type.put(ScanType.CONTINUOUS)
|
||||||
|
elif scan_type in "triggered":
|
||||||
|
self.scan_type.put(ScanType.TRIGGERED)
|
||||||
|
|
||||||
|
if scan_duration >= 0:
|
||||||
|
self.scan_duration.put(scan_duration)
|
||||||
|
|
||||||
|
if readout_range == 1:
|
||||||
|
self.readout_range.put(ReadoutRange.ONE_V)
|
||||||
|
elif readout_range == 2:
|
||||||
|
self.readout_range.put(ReadoutRange.TWO_V)
|
||||||
|
elif readout_range == 5:
|
||||||
|
self.readout_range.put(ReadoutRange.FIVE_V)
|
||||||
|
elif readout_range == 10:
|
||||||
|
self.readout_range.put(ReadoutRange.TEN_V)
|
||||||
|
|
||||||
|
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)
|
||||||
|
elif enable_compression is False:
|
||||||
|
self.enable_compression.put(NIDAQCompression.OFF)
|
||||||
|
|
||||||
|
########################################
|
||||||
|
# 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 = 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:
|
||||||
|
"""
|
||||||
|
Called while staging the device.
|
||||||
|
|
||||||
|
Information about the upcoming scan can be accessed from the scan_info (self.scan_info.msg) object.
|
||||||
|
If the upcoming scan is not in the list of valid scans, return immediately.
|
||||||
|
"""
|
||||||
|
if not self._check_if_scan_name_is_valid():
|
||||||
|
return None
|
||||||
|
|
||||||
|
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 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.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)
|
||||||
|
self.stage_call.set(1).wait(timeout=self._timeout_wait_for_pv)
|
||||||
|
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"""
|
||||||
|
|
||||||
|
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:
|
||||||
|
"""
|
||||||
|
Called right before the scan starts on all devices automatically.
|
||||||
|
|
||||||
|
Here we ensure that the NIDAQ master task is running
|
||||||
|
before the motor starts its oscillation. This is needed for being properly homed.
|
||||||
|
The NIDAQ should go into Acquiring mode.
|
||||||
|
"""
|
||||||
|
if not self._check_if_scan_name_is_valid():
|
||||||
|
return None
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
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())}"
|
||||||
|
)
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
For the NIDAQ we use this method to stop the backend since it
|
||||||
|
would not stop by itself in its current implementation since the number of points are not predefined.
|
||||||
|
"""
|
||||||
|
if not self._check_if_scan_name_is_valid():
|
||||||
|
return None
|
||||||
|
|
||||||
|
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 _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.put(1)
|
||||||
60
debye_bec/devices/nidaq/nidaq_enums.py
Normal file
60
debye_bec/devices/nidaq/nidaq_enums.py
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
import enum
|
||||||
|
|
||||||
|
|
||||||
|
class NIDAQCompression(str, enum.Enum):
|
||||||
|
"""Options for Compression"""
|
||||||
|
|
||||||
|
OFF = 0
|
||||||
|
ON = 1
|
||||||
|
|
||||||
|
|
||||||
|
class ScanType(int, enum.Enum):
|
||||||
|
"""Triggering options of the backend"""
|
||||||
|
|
||||||
|
TRIGGERED = 0
|
||||||
|
CONTINUOUS = 1
|
||||||
|
|
||||||
|
|
||||||
|
class NidaqState(int, enum.Enum):
|
||||||
|
"""Possible States of the NIDAQ backend"""
|
||||||
|
|
||||||
|
DISABLED = 0
|
||||||
|
STANDBY = 1
|
||||||
|
STAGE = 2
|
||||||
|
KICKOFF = 3
|
||||||
|
ACQUIRE = 4
|
||||||
|
UNSTAGE = 5
|
||||||
|
|
||||||
|
|
||||||
|
class ScanRates(int, enum.Enum):
|
||||||
|
"""Sampling Rate options for the backend, in kHZ and MHz"""
|
||||||
|
|
||||||
|
HUNDRED_KHZ = 0
|
||||||
|
FIVE_HUNDRED_KHZ = 1
|
||||||
|
ONE_MHZ = 2
|
||||||
|
TWO_MHZ = 3
|
||||||
|
FOUR_MHZ = 4
|
||||||
|
FIVE_MHZ = 5
|
||||||
|
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 EncoderFactors(int, enum.Enum):
|
||||||
|
"""Encoder Factors"""
|
||||||
|
|
||||||
|
X1_16 = 0
|
||||||
|
X1_8 = 1
|
||||||
|
X1_4 = 2
|
||||||
|
X1_2 = 3
|
||||||
|
X1 = 4
|
||||||
|
X2 = 5
|
||||||
|
X4 = 6
|
||||||
0
debye_bec/devices/pilatus/__init__.py
Normal file
0
debye_bec/devices/pilatus/__init__.py
Normal file
701
debye_bec/devices/pilatus/pilatus.py
Normal file
701
debye_bec/devices/pilatus/pilatus.py
Normal file
@@ -0,0 +1,701 @@
|
|||||||
|
"""Pilatus AD integration at Debye beamline."""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import enum
|
||||||
|
import threading
|
||||||
|
import time
|
||||||
|
import traceback
|
||||||
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
|
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, 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 CompareStatus, DeviceStatus, FileEventSignal, PreviewSignal
|
||||||
|
from ophyd_devices.interfaces.base_classes.psi_device_base import PSIDeviceBase
|
||||||
|
from pydantic import BaseModel, Field
|
||||||
|
|
||||||
|
from debye_bec.devices.pilatus.utils import AndStatusWithList
|
||||||
|
|
||||||
|
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 = 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=0, # Check this
|
||||||
|
doc="Preview signal for the Pilatus Detector",
|
||||||
|
)
|
||||||
|
file_event = Cpt(FileEventSignal, name="file_event")
|
||||||
|
|
||||||
|
@property
|
||||||
|
def baseline_signals(self):
|
||||||
|
"""Define baseline signals"""
|
||||||
|
return [
|
||||||
|
self.cam.acquire_time,
|
||||||
|
self.cam.num_exposures,
|
||||||
|
self.cam.threshold_energy,
|
||||||
|
self.cam.gain_menu,
|
||||||
|
self.cam.pixel_cut_off,
|
||||||
|
]
|
||||||
|
|
||||||
|
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) -> AndStatusWithList:
|
||||||
|
"""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 = AndStatusWithList(
|
||||||
|
device=self, status_list=[status_acquire, status_writing, status_cam_server]
|
||||||
|
)
|
||||||
|
return status
|
||||||
|
|
||||||
|
def _calculate_trigger(self, scan_msg: ScanStatusMessage):
|
||||||
|
self._update_scan_parameter()
|
||||||
|
total_osc = 0
|
||||||
|
total_trig_lo = 0
|
||||||
|
total_trig_hi = 0
|
||||||
|
calc_duration = 0
|
||||||
|
n_trig_lo = 1
|
||||||
|
n_trig_hi = 1
|
||||||
|
init_lo = 1
|
||||||
|
init_hi = 1
|
||||||
|
lo_done = 0
|
||||||
|
hi_done = 0
|
||||||
|
if not self.scan_parameter.break_enable_low:
|
||||||
|
lo_done = 1
|
||||||
|
if not self.scan_parameter.break_enable_high:
|
||||||
|
hi_done = 1
|
||||||
|
start_time = time.time()
|
||||||
|
while True:
|
||||||
|
# TODO, we should not use infinite loops, for now let's add the escape Timeout of 20s, but should eventually be reviewed.
|
||||||
|
if time.time() - start_time > 20:
|
||||||
|
raise RuntimeError(
|
||||||
|
f"Calculating the number of triggers for scan {scan_msg.scan_name} took more than 20 seconds, aborting."
|
||||||
|
)
|
||||||
|
total_osc = total_osc + 2
|
||||||
|
calc_duration = calc_duration + 2 * self.scan_parameter.scan_time
|
||||||
|
|
||||||
|
if self.scan_parameter.break_enable_low and n_trig_lo >= self.scan_parameter.cycle_low:
|
||||||
|
n_trig_lo = 1
|
||||||
|
calc_duration = calc_duration + self.scan_parameter.break_time_low
|
||||||
|
if init_lo:
|
||||||
|
lo_done = 1
|
||||||
|
init_lo = 0
|
||||||
|
else:
|
||||||
|
n_trig_lo += 1
|
||||||
|
|
||||||
|
if (
|
||||||
|
self.scan_parameter.break_enable_high
|
||||||
|
and n_trig_hi >= self.scan_parameter.cycle_high
|
||||||
|
):
|
||||||
|
n_trig_hi = 1
|
||||||
|
calc_duration = calc_duration + self.scan_parameter.break_time_high
|
||||||
|
if init_hi:
|
||||||
|
hi_done = 1
|
||||||
|
init_hi = 0
|
||||||
|
else:
|
||||||
|
n_trig_hi += 1
|
||||||
|
|
||||||
|
if lo_done and hi_done:
|
||||||
|
n = np.floor(self.scan_parameter.scan_duration / calc_duration)
|
||||||
|
total_osc = total_osc * n
|
||||||
|
if self.scan_parameter.break_enable_low:
|
||||||
|
total_trig_lo = n + 1
|
||||||
|
if self.scan_parameter.break_enable_high:
|
||||||
|
total_trig_hi = n + 1
|
||||||
|
calc_duration = calc_duration * n
|
||||||
|
lo_done = 0
|
||||||
|
hi_done = 0
|
||||||
|
|
||||||
|
if calc_duration >= self.scan_parameter.scan_duration:
|
||||||
|
break
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
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 = AndStatusWithList(
|
||||||
|
device=self, status_list=[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:
|
||||||
|
status.device.file_event.put(
|
||||||
|
file_path=status.device._full_path, # pylint: disable:protected-access
|
||||||
|
done=True,
|
||||||
|
successful=True,
|
||||||
|
hinted_h5_entries={"data": "/entry/data/data"},
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
status.device.file_event.put(
|
||||||
|
file_path=status.device._full_path, # pylint: disable:protected-access
|
||||||
|
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=">="
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
status_img_written = CompareStatus(self.hdf.num_captured, self.n_images)
|
||||||
|
status_img_written = CompareStatus(self.hdf.num_captured, self.n_images)
|
||||||
|
status = AndStatusWithList(
|
||||||
|
device=self,
|
||||||
|
status_list=[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()
|
||||||
238
debye_bec/devices/pilatus/utils.py
Normal file
238
debye_bec/devices/pilatus/utils.py
Normal file
@@ -0,0 +1,238 @@
|
|||||||
|
"""Temporary utility module for Status Object implementations."""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
|
from ophyd import Device, DeviceStatus, StatusBase
|
||||||
|
|
||||||
|
|
||||||
|
class AndStatusWithList(DeviceStatus):
|
||||||
|
"""
|
||||||
|
Custom implementation of the AndStatus that combines the
|
||||||
|
option to add multiple statuses as a list, and in addition
|
||||||
|
allows for adding the Device as an object to access its
|
||||||
|
methods.
|
||||||
|
|
||||||
|
Args"""
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
device: Device,
|
||||||
|
status_list: StatusBase | DeviceStatus | list[StatusBase | DeviceStatus],
|
||||||
|
**kwargs,
|
||||||
|
):
|
||||||
|
self.all_statuses = status_list if isinstance(status_list, list) else [status_list]
|
||||||
|
super().__init__(device=device, **kwargs)
|
||||||
|
self._trace_attributes["all"] = [st._trace_attributes for st in self.all_statuses]
|
||||||
|
|
||||||
|
def inner(status):
|
||||||
|
with self._lock:
|
||||||
|
if self._externally_initiated_completion:
|
||||||
|
return
|
||||||
|
if self.done: # Return if status is already done.. It must be resolved already
|
||||||
|
return
|
||||||
|
|
||||||
|
for st in self.all_statuses:
|
||||||
|
with st._lock:
|
||||||
|
if st.done and not st.success:
|
||||||
|
self.set_exception(st.exception()) # st._exception
|
||||||
|
return
|
||||||
|
|
||||||
|
if all(st.done for st in self.all_statuses) and all(
|
||||||
|
st.success for st in self.all_statuses
|
||||||
|
):
|
||||||
|
self.set_finished()
|
||||||
|
|
||||||
|
for st in self.all_statuses:
|
||||||
|
with st._lock:
|
||||||
|
st.add_callback(inner)
|
||||||
|
|
||||||
|
# TODO improve __repr__ and __str__
|
||||||
|
def __repr__(self):
|
||||||
|
return "<AndStatusWithList({self.all_statuses!r})>".format(self=self)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return "<AndStatusWithList(done={self.done}, success={self.success})>".format(self=self)
|
||||||
|
|
||||||
|
def __contains__(self, status: StatusBase | DeviceStatus) -> bool:
|
||||||
|
for child in self.all_statuses:
|
||||||
|
if child == status:
|
||||||
|
return True
|
||||||
|
if isinstance(child, AndStatusWithList):
|
||||||
|
if status in child:
|
||||||
|
return True
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
|
# TODO Check if this actually works....
|
||||||
|
def set_exception(self, exc):
|
||||||
|
super().set_exception(exc)
|
||||||
|
# Propagate the exception to all sub-statuses that are not done yet.
|
||||||
|
with self._lock:
|
||||||
|
for st in self.all_statuses:
|
||||||
|
with st._lock:
|
||||||
|
if not st.done:
|
||||||
|
st.set_exception(exc)
|
||||||
|
|
||||||
|
def _run_callbacks(self):
|
||||||
|
"""
|
||||||
|
Set the Event and run the callbacks.
|
||||||
|
"""
|
||||||
|
if self.timeout is None:
|
||||||
|
timeout = None
|
||||||
|
else:
|
||||||
|
timeout = self.timeout + self.settle_time
|
||||||
|
if not self._settled_event.wait(timeout):
|
||||||
|
self.log.warning("%r has timed out", self)
|
||||||
|
with self._externally_initiated_completion_lock:
|
||||||
|
if self._exception is None:
|
||||||
|
exc = TimeoutError(
|
||||||
|
f"AndStatus from device {self.device.name} failed to complete in specified timeout of {self.timeout + self.settle_time}."
|
||||||
|
)
|
||||||
|
self._exception = exc
|
||||||
|
# Mark this as "settled".
|
||||||
|
try:
|
||||||
|
self._settled()
|
||||||
|
except Exception:
|
||||||
|
self.log.exception("%r encountered error during _settled()", self)
|
||||||
|
with self._lock:
|
||||||
|
self._event.set()
|
||||||
|
if self._exception is not None:
|
||||||
|
try:
|
||||||
|
self._handle_failure()
|
||||||
|
except Exception:
|
||||||
|
self.log.exception("%r encountered an error during _handle_failure()", self)
|
||||||
|
for cb in self._callbacks:
|
||||||
|
try:
|
||||||
|
cb(self)
|
||||||
|
except Exception:
|
||||||
|
self.log.exception(
|
||||||
|
"An error was raised on a background thread while "
|
||||||
|
"running the callback %r(%r).",
|
||||||
|
cb,
|
||||||
|
self,
|
||||||
|
)
|
||||||
|
self._callbacks.clear()
|
||||||
|
|
||||||
|
|
||||||
|
class AndStatus(StatusBase):
|
||||||
|
"""Custom AndStatus for TimePix detector."""
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
left: StatusBase | DeviceStatus | list[StatusBase | DeviceStatus] | None,
|
||||||
|
name: str | Device | None = None,
|
||||||
|
right: StatusBase | DeviceStatus | list[StatusBase | DeviceStatus] | None = None,
|
||||||
|
**kwargs,
|
||||||
|
):
|
||||||
|
self.left = left if isinstance(left, list) else [left]
|
||||||
|
if right is not None:
|
||||||
|
self.right = right if isinstance(right, list) else [right]
|
||||||
|
else:
|
||||||
|
self.right = []
|
||||||
|
self.all_statuses = self.left + self.right
|
||||||
|
if name is None:
|
||||||
|
name = "unname_status"
|
||||||
|
elif isinstance(name, Device):
|
||||||
|
name = name.name
|
||||||
|
else:
|
||||||
|
name = name
|
||||||
|
self.name = name
|
||||||
|
super().__init__(**kwargs)
|
||||||
|
self._trace_attributes["left"] = [st._trace_attributes for st in self.left]
|
||||||
|
self._trace_attributes["right"] = [st._trace_attributes for st in self.right]
|
||||||
|
|
||||||
|
def inner(status):
|
||||||
|
with self._lock:
|
||||||
|
if self._externally_initiated_completion:
|
||||||
|
return
|
||||||
|
if self.done: # Return if status is already done.. It must be resolved already
|
||||||
|
return
|
||||||
|
|
||||||
|
for st in self.all_statuses:
|
||||||
|
with st._lock:
|
||||||
|
if st.done and not st.success:
|
||||||
|
self.set_exception(st.exception()) # st._exception
|
||||||
|
return
|
||||||
|
|
||||||
|
if all(st.done for st in self.all_statuses) and all(
|
||||||
|
st.success for st in self.all_statuses
|
||||||
|
):
|
||||||
|
self.set_finished()
|
||||||
|
|
||||||
|
for st in self.all_statuses:
|
||||||
|
with st._lock:
|
||||||
|
st.add_callback(inner)
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return "({self.left!r} & {self.right!r})".format(self=self)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return "{0}(done={1.done}, " "success={1.success})" "".format(self.__class__.__name__, self)
|
||||||
|
|
||||||
|
def __contains__(self, status: StatusBase) -> bool:
|
||||||
|
for child in [self.left, self.right]:
|
||||||
|
if child == status:
|
||||||
|
return True
|
||||||
|
if isinstance(child, AndStatus):
|
||||||
|
if status in child:
|
||||||
|
return True
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
|
def _run_callbacks(self):
|
||||||
|
"""
|
||||||
|
Set the Event and run the callbacks.
|
||||||
|
"""
|
||||||
|
if self.timeout is None:
|
||||||
|
timeout = None
|
||||||
|
else:
|
||||||
|
timeout = self.timeout + self.settle_time
|
||||||
|
if not self._settled_event.wait(timeout):
|
||||||
|
# We have timed out. It's possible that set_finished() has already
|
||||||
|
# been called but we got here before the settle_time timer expired.
|
||||||
|
# And it's possible that in this space be between the above
|
||||||
|
# statement timing out grabbing the lock just below,
|
||||||
|
# set_exception(exc) has been called. Both of these possibilties
|
||||||
|
# are accounted for.
|
||||||
|
self.log.warning("%r has timed out", self)
|
||||||
|
with self._externally_initiated_completion_lock:
|
||||||
|
# Set the exception and mark the Status as done, unless
|
||||||
|
# set_exception(exc) was called externally before we grabbed
|
||||||
|
# the lock.
|
||||||
|
if self._exception is None:
|
||||||
|
exc = TimeoutError(
|
||||||
|
f"Status with name {self.name} failed to complete in specified timeout of {self.timeout + self.settle_time}."
|
||||||
|
)
|
||||||
|
self._exception = exc
|
||||||
|
# Mark this as "settled".
|
||||||
|
try:
|
||||||
|
self._settled()
|
||||||
|
except Exception:
|
||||||
|
# No alternative but to log this. We can't supersede set_exception,
|
||||||
|
# and we have to continue and run the callbacks.
|
||||||
|
self.log.exception("%r encountered error during _settled()", self)
|
||||||
|
# Now we know whether or not we have succeed or failed, either by
|
||||||
|
# timeout above or by set_exception(exc), so we can set the Event that
|
||||||
|
# will mark this Status as done.
|
||||||
|
with self._lock:
|
||||||
|
self._event.set()
|
||||||
|
if self._exception is not None:
|
||||||
|
try:
|
||||||
|
self._handle_failure()
|
||||||
|
except Exception:
|
||||||
|
self.log.exception("%r encountered an error during _handle_failure()", self)
|
||||||
|
# The callbacks have access to self, from which they can distinguish
|
||||||
|
# success or failure.
|
||||||
|
for cb in self._callbacks:
|
||||||
|
try:
|
||||||
|
cb(self)
|
||||||
|
except Exception:
|
||||||
|
self.log.exception(
|
||||||
|
"An error was raised on a background thread while "
|
||||||
|
"running the callback %r(%r).",
|
||||||
|
cb,
|
||||||
|
self,
|
||||||
|
)
|
||||||
|
self._callbacks.clear()
|
||||||
102
debye_bec/devices/pilatus_curtain.py
Normal file
102
debye_bec/devices/pilatus_curtain.py
Normal file
@@ -0,0 +1,102 @@
|
|||||||
|
"""ES2 Pilatus Curtain"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import enum
|
||||||
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
|
from ophyd import Component as Cpt
|
||||||
|
from ophyd import EpicsSignal, EpicsSignalRO
|
||||||
|
from ophyd_devices import CompareStatus, DeviceStatus
|
||||||
|
from ophyd_devices.interfaces.base_classes.psi_device_base import PSIDeviceBase
|
||||||
|
|
||||||
|
from debye_bec.devices.pilatus.utils import AndStatusWithList
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from bec_lib.devicemanager import ScanInfo
|
||||||
|
|
||||||
|
|
||||||
|
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, *, 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
|
||||||
|
# Wait for connection on all components, ensure IOC is connected
|
||||||
|
self.wait_for_connection(all_signals=True, timeout=5)
|
||||||
|
|
||||||
|
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!")
|
||||||
|
|
||||||
|
def on_stage(self) -> DeviceStatus | None:
|
||||||
|
"""Called while staging the device."""
|
||||||
|
return self.open()
|
||||||
|
|
||||||
|
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="!=")
|
||||||
|
status = AndStatusWithList(device=self, status_list=[status_open, status_error])
|
||||||
|
return status
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
|
||||||
|
def close(self) -> DeviceStatus | None:
|
||||||
|
"""Close the cover"""
|
||||||
|
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="!=")
|
||||||
|
status = AndStatusWithList(device=self, status_list=[status_close, status_error])
|
||||||
|
return status
|
||||||
|
else:
|
||||||
|
return None
|
||||||
194
debye_bec/devices/reffoilchanger.py
Normal file
194
debye_bec/devices/reffoilchanger.py
Normal file
@@ -0,0 +1,194 @@
|
|||||||
|
"""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"
|
||||||
|
)
|
||||||
|
op_mode = Cpt(
|
||||||
|
EpicsSignalWithRBV, suffix="ES2-REF:SELN-OpMode-ENUM", kind="config", doc="Status"
|
||||||
|
)
|
||||||
|
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)}"
|
||||||
|
)
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
from .debye_nexus_structure import DebyeNexusStructure
|
||||||
|
|||||||
125
debye_bec/file_writer/debye_nexus_structure.py
Normal file
125
debye_bec/file_writer/debye_nexus_structure.py
Normal 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"
|
||||||
6
debye_bec/macros/README.md
Normal file
6
debye_bec/macros/README.md
Normal 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
|
||||||
0
debye_bec/macros/__init__.py
Normal file
0
debye_bec/macros/__init__.py
Normal file
@@ -1 +1,7 @@
|
|||||||
from .mono_bragg_scans import XASSimpleScan, XASSimpleScanWithXRD
|
from .mono_bragg_scans import (
|
||||||
|
XASAdvancedScan,
|
||||||
|
XASAdvancedScanWithXRD,
|
||||||
|
XASSimpleScan,
|
||||||
|
XASSimpleScanWithXRD,
|
||||||
|
)
|
||||||
|
from .nidaq_cont_scan import NIDAQContinuousScan
|
||||||
|
|||||||
0
debye_bec/scans/metadata_schema/__init__.py
Normal file
0
debye_bec/scans/metadata_schema/__init__.py
Normal file
12
debye_bec/scans/metadata_schema/metadata_schema_registry.py
Normal file
12
debye_bec/scans/metadata_schema/metadata_schema_registry.py
Normal 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
|
||||||
34
debye_bec/scans/metadata_schema/metadata_schema_template.py
Normal file
34
debye_bec/scans/metadata_schema/metadata_schema_template.py
Normal 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.
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
from bec_lib.metadata_schema import BasicScanMetadata
|
||||||
|
|
||||||
|
|
||||||
|
#
|
||||||
|
#
|
||||||
|
class xas_simple_scan_schema(BasicScanMetadata):
|
||||||
|
Edge: str
|
||||||
|
Element: str
|
||||||
@@ -1,13 +1,18 @@
|
|||||||
""" This module contains the scan classes for the mono bragg motor of the Debye beamline."""
|
"""This module contains the scan classes for the mono bragg motor of the Debye beamline."""
|
||||||
|
|
||||||
import time
|
import time
|
||||||
|
from typing import Literal
|
||||||
|
|
||||||
import numpy as np
|
import numpy as np
|
||||||
from bec_lib.device import DeviceBase
|
from bec_lib.device import DeviceBase
|
||||||
|
from bec_lib.logger import bec_logger
|
||||||
from bec_server.scan_server.scans import AsyncFlyScanBase
|
from bec_server.scan_server.scans import AsyncFlyScanBase
|
||||||
|
|
||||||
|
logger = bec_logger.logger
|
||||||
|
|
||||||
|
|
||||||
class XASSimpleScan(AsyncFlyScanBase):
|
class XASSimpleScan(AsyncFlyScanBase):
|
||||||
|
"""Class for the XAS simple scan"""
|
||||||
|
|
||||||
scan_name = "xas_simple_scan"
|
scan_name = "xas_simple_scan"
|
||||||
scan_type = "fly"
|
scan_type = "fly"
|
||||||
@@ -30,15 +35,17 @@ class XASSimpleScan(AsyncFlyScanBase):
|
|||||||
**kwargs,
|
**kwargs,
|
||||||
):
|
):
|
||||||
"""The xas_simple_scan is used to start a simple oscillating scan on the mono bragg motor.
|
"""The xas_simple_scan is used to start a simple oscillating scan on the mono bragg motor.
|
||||||
Start and Stop define the energy range for the scan, scan_time is the time for one scan cycle and scan_duration
|
Start and Stop define the energy range for the scan, scan_time is the time for one scan
|
||||||
is the duration of the scan. If scan duration is set to 0, the scan will run infinitely.
|
cycle and scan_duration is the duration of the scan. If scan duration is set to 0, the
|
||||||
|
scan will run infinitely.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
start (float): Start energy for the scan.
|
start (float): Start energy for the scan.
|
||||||
stop (float): Stop energy for the scan.
|
stop (float): Stop energy for the scan.
|
||||||
scan_time (float): Time for one scan cycle.
|
scan_time (float): Time for one scan cycle.
|
||||||
scan_duration (float): Duration of the scan.
|
scan_duration (float): Duration of the scan.
|
||||||
motor (DeviceBase, optional): Motor device to be used for the scan. Defaults to "mo1_bragg".
|
motor (DeviceBase, optional): Motor device to be used for the scan.
|
||||||
|
Defaults to "mo1_bragg".
|
||||||
Examples:
|
Examples:
|
||||||
>>> scans.xas_simple_scan(start=8000, stop=9000, scan_time=1, scan_duration=10)
|
>>> scans.xas_simple_scan(start=8000, stop=9000, scan_time=1, scan_duration=10)
|
||||||
"""
|
"""
|
||||||
@@ -50,6 +57,11 @@ class XASSimpleScan(AsyncFlyScanBase):
|
|||||||
self.scan_duration = scan_duration
|
self.scan_duration = scan_duration
|
||||||
self.primary_readout_cycle = 1
|
self.primary_readout_cycle = 1
|
||||||
|
|
||||||
|
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):
|
def prepare_positions(self):
|
||||||
"""Prepare the positions for the scan.
|
"""Prepare the positions for the scan.
|
||||||
|
|
||||||
@@ -60,14 +72,11 @@ class XASSimpleScan(AsyncFlyScanBase):
|
|||||||
yield None
|
yield None
|
||||||
|
|
||||||
def pre_scan(self):
|
def pre_scan(self):
|
||||||
"""Pre Scan action. Ensure the motor movetype is set to energy, then check limits for start/end energy.
|
"""Pre Scan action."""
|
||||||
#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")
|
|
||||||
|
|
||||||
self._check_limits()
|
self._check_limits()
|
||||||
# Ensure parent class pre_scan actions to be called.
|
# Ensure parent class pre_scan actions to be called.
|
||||||
super().pre_scan()
|
yield from super().pre_scan()
|
||||||
|
|
||||||
def scan_report_instructions(self):
|
def scan_report_instructions(self):
|
||||||
"""
|
"""
|
||||||
@@ -81,33 +90,27 @@ class XASSimpleScan(AsyncFlyScanBase):
|
|||||||
"""
|
"""
|
||||||
# Start the oscillation on the Bragg motor.
|
# Start the oscillation on the Bragg motor.
|
||||||
yield from self.stubs.kickoff(device=self.motor)
|
yield from self.stubs.kickoff(device=self.motor)
|
||||||
yield from self.stubs.complete(device=self.motor)
|
complete_status = yield from self.stubs.complete(device=self.motor, wait=False)
|
||||||
|
|
||||||
# Get the target DIID (instruction number) for the stubs.complete call
|
while not complete_status.done:
|
||||||
target_diid = self.DIID - 1
|
|
||||||
|
|
||||||
while True:
|
|
||||||
# Readout monitored devices
|
# Readout monitored devices
|
||||||
yield from self.stubs.read_and_wait(group="primary", wait_group="readout_primary")
|
yield from self.stubs.read(group="monitored", point_id=self.point_id)
|
||||||
# Check if complete call on Mo1 Bragg has been finished
|
|
||||||
status = self.stubs.get_req_status(
|
|
||||||
device=self.motor, RID=self.metadata["RID"], DIID=target_diid
|
|
||||||
)
|
|
||||||
if status:
|
|
||||||
break
|
|
||||||
time.sleep(self.primary_readout_cycle)
|
time.sleep(self.primary_readout_cycle)
|
||||||
self.point_id += 1
|
self.point_id += 1
|
||||||
|
|
||||||
self.num_pos = self.point_id + 1
|
self.num_pos = self.point_id
|
||||||
|
|
||||||
|
|
||||||
class XASSimpleScanWithXRD(XASSimpleScan):
|
class XASSimpleScanWithXRD(XASSimpleScan):
|
||||||
|
"""Class for the XAS simple scan with XRD"""
|
||||||
|
|
||||||
scan_name = "xas_simple_scan_with_xrd"
|
scan_name = "xas_simple_scan_with_xrd"
|
||||||
gui_config = {
|
gui_config = {
|
||||||
"Movement Parameters": ["start", "stop"],
|
"Movement Parameters": ["start", "stop"],
|
||||||
"Scan Parameters": ["scan_time", "scan_duration"],
|
"Scan Parameters": ["scan_time", "scan_duration"],
|
||||||
"Low Energy Range": ["xrd_enable_low", "num_trigger_low", "exp_time_low", "cycle_low"],
|
"Low Energy Break": ["break_enable_low", "break_time_low", "cycle_low"],
|
||||||
"High Energy Range": ["xrd_enable_high", "num_trigger_high", "exp_time_high", "cycle_high"],
|
"High Energy Break": ["break_enable_high", "break_time_high", "cycle_high"],
|
||||||
|
"XRD Triggers": ["exp_time", "n_of_trigger"],
|
||||||
}
|
}
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
@@ -116,14 +119,14 @@ class XASSimpleScanWithXRD(XASSimpleScan):
|
|||||||
stop: float,
|
stop: float,
|
||||||
scan_time: float,
|
scan_time: float,
|
||||||
scan_duration: float,
|
scan_duration: float,
|
||||||
xrd_enable_low: bool,
|
break_enable_low: bool,
|
||||||
num_trigger_low: int,
|
break_time_low: float,
|
||||||
exp_time_low: float,
|
|
||||||
cycle_low: int,
|
cycle_low: int,
|
||||||
xrd_enable_high: bool,
|
break_enable_high: bool,
|
||||||
num_trigger_high: int,
|
break_time_high: float,
|
||||||
exp_time_high: float,
|
|
||||||
cycle_high: float,
|
cycle_high: float,
|
||||||
|
exp_time: float,
|
||||||
|
n_of_trigger: int,
|
||||||
motor: DeviceBase = "mo1_bragg",
|
motor: DeviceBase = "mo1_bragg",
|
||||||
**kwargs,
|
**kwargs,
|
||||||
):
|
):
|
||||||
@@ -136,19 +139,22 @@ class XASSimpleScanWithXRD(XASSimpleScan):
|
|||||||
stop (float): Stop energy for the scan.
|
stop (float): Stop energy for the scan.
|
||||||
scan_time (float): Time for one oscillation .
|
scan_time (float): Time for one oscillation .
|
||||||
scan_duration (float): Total duration of the scan.
|
scan_duration (float): Total duration of the scan.
|
||||||
xrd_enable_low (bool): Enable XRD triggering for the low energy range.
|
break_enable_low (bool): Enable breaks for the low energy range.
|
||||||
num_trigger_low (int): Number of triggers for the low energy range.
|
break_time_low (float): Break time for the low energy range.
|
||||||
exp_time_low (float): Exposure time for the low energy range.
|
cycle_low (int): Specify how often the triggers should be considered,
|
||||||
cycle_low (int): Specify how often the triggers should be considered, every nth cycle for low
|
every nth cycle for low
|
||||||
xrd_enable_high (bool): Enable XRD triggering for the high energy range.
|
break_enable_high (bool): Enable breaks for the high energy range.
|
||||||
num_trigger_high (int): Number of triggers for the high energy range.
|
break_time_high (float): Break time for the high energy range.
|
||||||
exp_time_high (float): Exposure time for the high energy range.
|
cycle_high (int): Specify how often the triggers should be considered,
|
||||||
cycle_high (int): Specify how often the triggers should be considered, every nth cycle for high
|
every nth cycle for high
|
||||||
motor (DeviceBase, optional): Motor device to be used for the scan. Defaults to "mo1_bragg".
|
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".
|
||||||
|
|
||||||
Examples:
|
Examples:
|
||||||
>>> scans.xas_simple_scan_with_xrd(start=8000, stop=9000, scan_time=1, scan_duration=10, xrd_enable_low=True, num_trigger_low=5, cycle_low=2, exp_time_low=100, xrd_enable_high=False, num_trigger_high=3, cycle_high=1, exp_time_high=1000)
|
>>> scans.xas_simple_scan_with_xrd(start=8000, stop=9000, scan_time=1, scan_duration=10, xrd_enable_low=True, num_trigger_low=5, cycle_low=2, exp_time_low=100, xrd_enable_high=False, num_trigger_high=3, cycle_high=1, exp_time_high=1000)
|
||||||
"""
|
"""
|
||||||
super().__init__(
|
super().__init__(
|
||||||
start=start,
|
start=start,
|
||||||
stop=stop,
|
stop=stop,
|
||||||
@@ -157,11 +163,148 @@ class XASSimpleScanWithXRD(XASSimpleScan):
|
|||||||
motor=motor,
|
motor=motor,
|
||||||
**kwargs,
|
**kwargs,
|
||||||
)
|
)
|
||||||
self.xrd_enable_low = xrd_enable_low
|
self.break_enable_low = break_enable_low
|
||||||
self.num_trigger_low = num_trigger_low
|
self.break_time_low = break_time_low
|
||||||
self.exp_time_low = exp_time_low
|
|
||||||
self.cycle_low = cycle_low
|
self.cycle_low = cycle_low
|
||||||
self.xrd_enable_high = xrd_enable_high
|
self.break_enable_high = break_enable_high
|
||||||
self.num_trigger_high = num_trigger_high
|
self.break_time_high = break_time_high
|
||||||
self.exp_time_high = exp_time_high
|
|
||||||
self.cycle_high = cycle_high
|
self.cycle_high = cycle_high
|
||||||
|
self.exp_time = exp_time
|
||||||
|
self.n_of_trigger = n_of_trigger
|
||||||
|
|
||||||
|
|
||||||
|
class XASAdvancedScan(XASSimpleScan):
|
||||||
|
"""Class for the XAS advanced scan"""
|
||||||
|
|
||||||
|
scan_name = "xas_advanced_scan"
|
||||||
|
gui_config = {
|
||||||
|
"Movement Parameters": ["start", "stop"],
|
||||||
|
"Scan Parameters": ["scan_time", "scan_duration"],
|
||||||
|
"Spline Parameters": ["p_kink", "e_kink"],
|
||||||
|
}
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
start: float,
|
||||||
|
stop: float,
|
||||||
|
scan_time: float,
|
||||||
|
scan_duration: float,
|
||||||
|
p_kink: float,
|
||||||
|
e_kink: float,
|
||||||
|
motor: DeviceBase = "mo1_bragg",
|
||||||
|
**kwargs,
|
||||||
|
):
|
||||||
|
"""The xas_advanced_scan is an oscillation motion on the mono motor.
|
||||||
|
Start and Stop define the energy range for the scan, scan_time is the
|
||||||
|
time for one scan cycle and scan_duration is the duration of the scan.
|
||||||
|
If scan duration is set to 0, the scan will run infinitely.
|
||||||
|
p_kink and e_kink add a kink to the motion profile to slow down in the
|
||||||
|
exafs region of the scan.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
start (float): Start angle for the scan.
|
||||||
|
stop (float): Stop angle for the scan.
|
||||||
|
scan_time (float): Time for one oscillation .
|
||||||
|
scan_duration (float): Total duration of the scan.
|
||||||
|
p_kink (float): Position of the kink.
|
||||||
|
e_kink (float): Energy of the kink.
|
||||||
|
motor (DeviceBase, optional): Motor device to be used for the scan.
|
||||||
|
Defaults to "mo1_bragg".
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
>>> scans.xas_advanced_scan(start=10000, stop=12000, scan_time=0.5, scan_duration=10, p_kink=50, e_kink=10500)
|
||||||
|
"""
|
||||||
|
super().__init__(
|
||||||
|
start=start,
|
||||||
|
stop=stop,
|
||||||
|
scan_time=scan_time,
|
||||||
|
scan_duration=scan_duration,
|
||||||
|
motor=motor,
|
||||||
|
**kwargs,
|
||||||
|
)
|
||||||
|
self.p_kink = p_kink
|
||||||
|
self.e_kink = e_kink
|
||||||
|
|
||||||
|
|
||||||
|
class XASAdvancedScanWithXRD(XASAdvancedScan):
|
||||||
|
"""Class for the XAS advanced scan with XRD"""
|
||||||
|
|
||||||
|
scan_name = "xas_advanced_scan_with_xrd"
|
||||||
|
gui_config = {
|
||||||
|
"Movement Parameters": ["start", "stop"],
|
||||||
|
"Scan Parameters": ["scan_time", "scan_duration"],
|
||||||
|
"Spline Parameters": ["p_kink", "e_kink"],
|
||||||
|
"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__(
|
||||||
|
self,
|
||||||
|
start: float,
|
||||||
|
stop: float,
|
||||||
|
scan_time: float,
|
||||||
|
scan_duration: float,
|
||||||
|
p_kink: float,
|
||||||
|
e_kink: float,
|
||||||
|
break_enable_low: bool,
|
||||||
|
break_time_low: float,
|
||||||
|
cycle_low: int,
|
||||||
|
break_enable_high: bool,
|
||||||
|
break_time_high: float,
|
||||||
|
cycle_high: float,
|
||||||
|
exp_time: float,
|
||||||
|
n_of_trigger: int,
|
||||||
|
motor: DeviceBase = "mo1_bragg",
|
||||||
|
**kwargs,
|
||||||
|
):
|
||||||
|
"""The xas_advanced_scan is an oscillation motion on the mono motor
|
||||||
|
with XRD triggering at low and high energy ranges.
|
||||||
|
Start and Stop define the energy range for the scan, scan_time is the time for
|
||||||
|
one scan cycle and scan_duration is the duration of the scan. If scan duration
|
||||||
|
is set to 0, the scan will run infinitely. p_kink and e_kink add a kink to the
|
||||||
|
motion profile to slow down in the exafs region of the scan.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
start (float): Start angle for the scan.
|
||||||
|
stop (float): Stop angle for the scan.
|
||||||
|
scan_time (float): Time for one oscillation .
|
||||||
|
scan_duration (float): Total duration of the scan.
|
||||||
|
p_kink (float): Position of kink.
|
||||||
|
e_kink (float): Energy of the kink.
|
||||||
|
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
|
||||||
|
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".
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
>>> scans.xas_advanced_scan_with_xrd(start=10000, stop=12000, scan_time=0.5, scan_duration=10, p_kink=50, e_kink=10500, xrd_enable_low=True, num_trigger_low=5, cycle_low=2, exp_time_low=100, xrd_enable_high=False, num_trigger_high=3, cycle_high=1, exp_time_high=1000)
|
||||||
|
"""
|
||||||
|
super().__init__(
|
||||||
|
start=start,
|
||||||
|
stop=stop,
|
||||||
|
scan_time=scan_time,
|
||||||
|
scan_duration=scan_duration,
|
||||||
|
p_kink=p_kink,
|
||||||
|
e_kink=e_kink,
|
||||||
|
motor=motor,
|
||||||
|
**kwargs,
|
||||||
|
)
|
||||||
|
self.p_kink = p_kink
|
||||||
|
self.e_kink = e_kink
|
||||||
|
self.break_enable_low = break_enable_low
|
||||||
|
self.break_time_low = break_time_low
|
||||||
|
self.cycle_low = cycle_low
|
||||||
|
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
|
||||||
|
|||||||
84
debye_bec/scans/nidaq_cont_scan.py
Normal file
84
debye_bec/scans/nidaq_cont_scan.py
Normal 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
|
||||||
@@ -28,7 +28,7 @@ class NIDAQWriterService(BECService):
|
|||||||
def __init__(self, config: ServiceConfig, connector_cls: RedisConnector) -> None:
|
def __init__(self, config: ServiceConfig, connector_cls: RedisConnector) -> None:
|
||||||
super().__init__(config=config, connector_cls=connector_cls, unique_service=True)
|
super().__init__(config=config, connector_cls=connector_cls, unique_service=True)
|
||||||
self.queue = queue.Queue()
|
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.writer_mixin = FileWriter(service_config=config)
|
||||||
self._scan_status_consumer = None
|
self._scan_status_consumer = None
|
||||||
self._ni_data_consumer = None
|
self._ni_data_consumer = None
|
||||||
|
|||||||
@@ -5,24 +5,25 @@ build-backend = "hatchling.build"
|
|||||||
[project]
|
[project]
|
||||||
name = "debye_bec"
|
name = "debye_bec"
|
||||||
version = "0.0.0"
|
version = "0.0.0"
|
||||||
description = "Custom device implementations based on the ophyd hardware abstraction layer"
|
description = "A plugin repository for BEC"
|
||||||
requires-python = ">=3.10"
|
requires-python = ">=3.10"
|
||||||
classifiers = [
|
classifiers = [
|
||||||
"Development Status :: 3 - Alpha",
|
"Development Status :: 3 - Alpha",
|
||||||
"Programming Language :: Python :: 3",
|
"Programming Language :: Python :: 3",
|
||||||
"Topic :: Scientific/Engineering",
|
"Topic :: Scientific/Engineering",
|
||||||
]
|
]
|
||||||
dependencies = ["numpy", "bec_lib", "h5py", "ophyd_devices"]
|
dependencies = ["numpy", "scipy", "bec_lib", "h5py", "ophyd_devices"]
|
||||||
|
|
||||||
[project.optional-dependencies]
|
[project.optional-dependencies]
|
||||||
dev = [
|
dev = [
|
||||||
"bec_server",
|
|
||||||
"black",
|
"black",
|
||||||
|
"copier",
|
||||||
"isort",
|
"isort",
|
||||||
"coverage",
|
"coverage",
|
||||||
"pylint",
|
"pylint",
|
||||||
"pytest",
|
"pytest",
|
||||||
"pytest-random-order",
|
"pytest-random-order",
|
||||||
|
"bec_server",
|
||||||
]
|
]
|
||||||
|
|
||||||
[project.entry-points."bec"]
|
[project.entry-points."bec"]
|
||||||
@@ -37,12 +38,18 @@ plugin_file_writer = "debye_bec.file_writer"
|
|||||||
[project.entry-points."bec.scans"]
|
[project.entry-points."bec.scans"]
|
||||||
plugin_scans = "debye_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"]
|
[project.entry-points."bec.ipython_client_startup"]
|
||||||
plugin_ipython_client_pre = "debye_bec.bec_ipython_client.startup.pre_startup"
|
plugin_ipython_client_pre = "debye_bec.bec_ipython_client.startup.pre_startup"
|
||||||
plugin_ipython_client_post = "debye_bec.bec_ipython_client.startup"
|
plugin_ipython_client_post = "debye_bec.bec_ipython_client.startup"
|
||||||
|
|
||||||
[project.entry-points."bec.widgets"]
|
[project.entry-points."bec.widgets.auto_updates"]
|
||||||
plugin_widgets = "debye_bec.bec_widgets"
|
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]
|
[tool.hatch.build.targets.wheel]
|
||||||
include = ["*"]
|
include = ["*"]
|
||||||
|
|||||||
0
test_commit.yml
Normal file
0
test_commit.yml
Normal file
0
test_commit_2.yml
Normal file
0
test_commit_2.yml
Normal file
0
test_commit_3.yml
Normal file
0
test_commit_3.yml
Normal file
0
test_commit_4
Normal file
0
test_commit_4
Normal file
@@ -1,31 +1,34 @@
|
|||||||
# Getting Started with Testing using pytest
|
# Getting Started with Testing using pytest
|
||||||
|
|
||||||
BEC is using the [pytest](https://docs.pytest.org/en/8.0.x/) framework.
|
BEC is using the [pytest](https://docs.pytest.org/en/latest/) framework.
|
||||||
It can be install via
|
It can be installed via
|
||||||
``` bash
|
|
||||||
|
```bash
|
||||||
pip install pytest
|
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.
|
We note that pytest is part of the optional-dependencies `[dev]` of the plugin package.
|
||||||
|
|
||||||
## Introduction
|
## Introduction
|
||||||
|
|
||||||
Tests in this package should be stored in the `tests` directory.
|
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>`.
|
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
|
pytest -v --random-order ./tests
|
||||||
```
|
```
|
||||||
|
|
||||||
Note, the python environment needs to be active.
|
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 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
|
## 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.
|
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).
|
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).
|
In addition, we also recommend to take a look at the [How-to guides from pytest](https://docs.pytest.org/en/8.0.x/how-to/index.html).
|
||||||
|
|
||||||
|
|||||||
@@ -1,31 +1,34 @@
|
|||||||
# Getting Started with Testing using pytest
|
# Getting Started with Testing using pytest
|
||||||
|
|
||||||
BEC is using the [pytest](https://docs.pytest.org/en/8.0.x/) framework.
|
BEC is using the [pytest](https://docs.pytest.org/en/latest/) framework.
|
||||||
It can be install via
|
It can be installed via
|
||||||
``` bash
|
|
||||||
|
```bash
|
||||||
pip install pytest
|
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.
|
We note that pytest is part of the optional-dependencies `[dev]` of the plugin package.
|
||||||
|
|
||||||
## Introduction
|
## Introduction
|
||||||
|
|
||||||
Tests in this package should be stored in the `tests` directory.
|
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>`.
|
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
|
pytest -v --random-order ./tests
|
||||||
```
|
```
|
||||||
|
|
||||||
Note, the python environment needs to be active.
|
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 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
|
## 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.
|
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).
|
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).
|
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).
|
||||||
|
|
||||||
|
|||||||
34
tests/tests_dap_services/README.md
Normal file
34
tests/tests_dap_services/README.md
Normal 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).
|
||||||
@@ -1,31 +1,34 @@
|
|||||||
# Getting Started with Testing using pytest
|
# Getting Started with Testing using pytest
|
||||||
|
|
||||||
BEC is using the [pytest](https://docs.pytest.org/en/8.0.x/) framework.
|
BEC is using the [pytest](https://docs.pytest.org/en/latest/) framework.
|
||||||
It can be install via
|
It can be installed via
|
||||||
``` bash
|
|
||||||
|
```bash
|
||||||
pip install pytest
|
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.
|
We note that pytest is part of the optional-dependencies `[dev]` of the plugin package.
|
||||||
|
|
||||||
## Introduction
|
## Introduction
|
||||||
|
|
||||||
Tests in this package should be stored in the `tests` directory.
|
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>`.
|
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
|
pytest -v --random-order ./tests
|
||||||
```
|
```
|
||||||
|
|
||||||
Note, the python environment needs to be active.
|
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 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
|
## 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.
|
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).
|
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).
|
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).
|
||||||
|
|
||||||
|
|||||||
70
tests/tests_devices/test_cameras.py
Normal file
70
tests/tests_devices/test_cameras.py
Normal 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
|
||||||
86
tests/tests_devices/test_debye_base_cam.py
Normal file
86
tests/tests_devices/test_debye_base_cam.py
Normal 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
|
||||||
@@ -16,14 +16,14 @@ from ophyd.utils import LimitError
|
|||||||
from ophyd_devices.tests.utils import MockPV
|
from ophyd_devices.tests.utils import MockPV
|
||||||
|
|
||||||
# from bec_server.device_server.tests.utils import DMMock
|
# from bec_server.device_server.tests.utils import DMMock
|
||||||
from debye_bec.devices.mo1_bragg import (
|
from debye_bec.devices.mo1_bragg.mo1_bragg import (
|
||||||
Mo1Bragg,
|
Mo1Bragg,
|
||||||
Mo1BraggError,
|
Mo1BraggError,
|
||||||
MoveType,
|
|
||||||
ScanControlLoadMessage,
|
ScanControlLoadMessage,
|
||||||
ScanControlMode,
|
ScanControlMode,
|
||||||
ScanControlScanStatus,
|
ScanControlScanStatus,
|
||||||
)
|
)
|
||||||
|
from debye_bec.devices.mo1_bragg.mo1_bragg_devices import MoveType
|
||||||
|
|
||||||
# TODO move this function to ophyd_devices, it is duplicated in csaxs_bec and needed for other pluging repositories
|
# 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
|
from debye_bec.devices.test_utils.utils import patch_dual_pvs
|
||||||
@@ -40,10 +40,7 @@ def scan_worker_mock(scan_server_mock):
|
|||||||
def mock_bragg():
|
def mock_bragg():
|
||||||
name = "bragg"
|
name = "bragg"
|
||||||
prefix = "X01DA-OP-MO1:BRAGG:"
|
prefix = "X01DA-OP-MO1:BRAGG:"
|
||||||
with (
|
with mock.patch.object(ophyd, "cl") as mock_cl:
|
||||||
mock.patch.object(ophyd, "cl") as mock_cl,
|
|
||||||
mock.patch("debye_bec.devices.mo1_bragg.Mo1Bragg", "_on_init"),
|
|
||||||
):
|
|
||||||
mock_cl.get_pv = MockPV
|
mock_cl.get_pv = MockPV
|
||||||
mock_cl.thread_class = threading.Thread
|
mock_cl.thread_class = threading.Thread
|
||||||
dev = Mo1Bragg(name=name, prefix=prefix)
|
dev = Mo1Bragg(name=name, prefix=prefix)
|
||||||
@@ -55,52 +52,38 @@ def test_init(mock_bragg):
|
|||||||
dev = mock_bragg
|
dev = mock_bragg
|
||||||
assert dev.name == "bragg"
|
assert dev.name == "bragg"
|
||||||
assert dev.prefix == "X01DA-OP-MO1:BRAGG:"
|
assert dev.prefix == "X01DA-OP-MO1:BRAGG:"
|
||||||
assert dev.move_type.get() == MoveType.ENERGY
|
|
||||||
assert dev.crystal.offset_si111._read_pvname == "X01DA-OP-MO1:BRAGG:offset_si111_RBV"
|
assert dev.crystal.offset_si111._read_pvname == "X01DA-OP-MO1:BRAGG:offset_si111_RBV"
|
||||||
assert dev.move_abs._read_pvname == "X01DA-OP-MO1:BRAGG:move_abs.PROC"
|
assert dev.move_abs._read_pvname == "X01DA-OP-MO1:BRAGG:move_abs"
|
||||||
|
|
||||||
|
|
||||||
def test_check_value(mock_bragg):
|
def test_check_value(mock_bragg):
|
||||||
dev = mock_bragg
|
dev = mock_bragg
|
||||||
dev.low_lim._read_pv.mock_data = 0
|
dev.low_lim._read_pv.mock_data = 0
|
||||||
dev.high_lim._read_pv.mock_data = 1
|
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
|
# nothing happens
|
||||||
dev.check_value(0.5)
|
dev.check_value(0.5)
|
||||||
with pytest.raises(LimitError):
|
with pytest.raises(LimitError):
|
||||||
dev.check_value(15)
|
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):
|
def test_egu(mock_bragg):
|
||||||
dev = mock_bragg
|
dev = mock_bragg
|
||||||
assert dev.egu == "eV"
|
assert dev.egu == "eV"
|
||||||
dev.move_type.set(MoveType.ANGLE)
|
|
||||||
assert dev.egu == "deg"
|
|
||||||
|
|
||||||
|
|
||||||
def test_move_succeeds(mock_bragg):
|
def test_move_succeeds(mock_bragg):
|
||||||
dev = mock_bragg
|
dev = mock_bragg
|
||||||
dev.move_abs._read_pv.mock_data = 0
|
dev.move_abs._read_pv.mock_data = 0
|
||||||
# Move succeeds
|
dev.motor_is_moving._read_pv.mock_data = 0
|
||||||
with mock.patch.object(dev.motor_is_moving._read_pv, "mock_data", side_effect=[0, 1]):
|
|
||||||
status = dev.move(0.5)
|
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
|
assert status.done is False
|
||||||
time.sleep(1)
|
dev.motor_is_moving._read_pv.mock_data = 1
|
||||||
assert status.done is True
|
status.wait(timeout=3) # Callback should within that time
|
||||||
assert status.success is True
|
assert status.done is True
|
||||||
assert dev.setpoint.get() == 0.5
|
assert status.success is True
|
||||||
assert dev.move_abs.get() == 1
|
assert dev.setpoint.get() == 0.5
|
||||||
|
assert dev.move_abs.get() == 1
|
||||||
|
|
||||||
|
|
||||||
def test_stop_move(mock_bragg):
|
def test_stop_move(mock_bragg):
|
||||||
@@ -138,38 +121,32 @@ def test_set_xtal(mock_bragg):
|
|||||||
|
|
||||||
def test_set_xas_settings(mock_bragg):
|
def test_set_xas_settings(mock_bragg):
|
||||||
dev = mock_bragg
|
dev = mock_bragg
|
||||||
dev.move_type.set(MoveType.ENERGY)
|
|
||||||
dev.set_xas_settings(low=0.5, high=1, scan_time=0.1)
|
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_lo.get() == 0.5
|
||||||
assert dev.scan_settings.s_scan_energy_hi.get() == 1
|
assert dev.scan_settings.s_scan_energy_hi.get() == 1
|
||||||
assert dev.scan_settings.s_scan_scantime.get() == 0.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_xrd_settings(mock_bragg):
|
def test_set_trig_settings(mock_bragg):
|
||||||
dev = mock_bragg
|
dev = mock_bragg
|
||||||
dev.set_xrd_settings(
|
dev.set_trig_settings(
|
||||||
enable_low=True,
|
enable_low=True,
|
||||||
enable_high=False,
|
enable_high=False,
|
||||||
num_trigger_low=1,
|
break_time_high=0.1,
|
||||||
num_trigger_high=7,
|
break_time_low=0.01,
|
||||||
exp_time_low=1,
|
|
||||||
exp_time_high=3,
|
|
||||||
cycle_low=1,
|
cycle_low=1,
|
||||||
cycle_high=5,
|
cycle_high=3,
|
||||||
|
exp_time=0.5,
|
||||||
|
n_of_trigger=7,
|
||||||
)
|
)
|
||||||
assert dev.scan_settings.xrd_enable_lo_enum.get() == True
|
assert dev.scan_settings.trig_ena_lo_enum.get() == True
|
||||||
assert dev.scan_settings.xrd_enable_hi_enum.get() == False
|
assert dev.scan_settings.trig_ena_hi_enum.get() == False
|
||||||
assert dev.scan_settings.xrd_n_trigger_lo.get() == 1
|
assert dev.scan_settings.trig_every_n_lo.get() == 1
|
||||||
assert dev.scan_settings.xrd_n_trigger_hi.get() == 7
|
assert dev.scan_settings.trig_every_n_hi.get() == 3
|
||||||
assert dev.scan_settings.xrd_time_lo.get() == 1
|
assert dev.scan_settings.trig_time_lo.get() == 0.01
|
||||||
assert dev.scan_settings.xrd_time_hi.get() == 3
|
assert dev.scan_settings.trig_time_hi.get() == 0.1
|
||||||
assert dev.scan_settings.xrd_every_n_lo.get() == 1
|
assert dev.trigger_settings.xrd_trig_period.get() == 0.5
|
||||||
assert dev.scan_settings.xrd_every_n_hi.get() == 5
|
assert dev.trigger_settings.xrd_n_of_trig.get() == 7
|
||||||
|
|
||||||
|
|
||||||
def test_set_control_settings(mock_bragg):
|
def test_set_control_settings(mock_bragg):
|
||||||
@@ -187,6 +164,25 @@ def test_update_scan_parameters(mock_bragg):
|
|||||||
msg = ScanStatusMessage(
|
msg = ScanStatusMessage(
|
||||||
scan_id="my_scan_id",
|
scan_id="my_scan_id",
|
||||||
status="closed",
|
status="closed",
|
||||||
|
request_inputs={
|
||||||
|
"inputs": {},
|
||||||
|
"kwargs": {
|
||||||
|
"start": 0,
|
||||||
|
"stop": 5,
|
||||||
|
"scan_time": 1,
|
||||||
|
"scan_duration": 10,
|
||||||
|
"xrd_enable_low": True,
|
||||||
|
"xrd_enable_high": False,
|
||||||
|
"num_trigger_low": 1,
|
||||||
|
"num_trigger_high": 7,
|
||||||
|
"exp_time_low": 1,
|
||||||
|
"exp_time_high": 3,
|
||||||
|
"cycle_low": 1,
|
||||||
|
"cycle_high": 5,
|
||||||
|
"p_kink": 50,
|
||||||
|
"e_kink": 8000,
|
||||||
|
},
|
||||||
|
},
|
||||||
info={
|
info={
|
||||||
"kwargs": {
|
"kwargs": {
|
||||||
"start": 0,
|
"start": 0,
|
||||||
@@ -201,18 +197,20 @@ def test_update_scan_parameters(mock_bragg):
|
|||||||
"exp_time_high": 3,
|
"exp_time_high": 3,
|
||||||
"cycle_low": 1,
|
"cycle_low": 1,
|
||||||
"cycle_high": 5,
|
"cycle_high": 5,
|
||||||
|
"p_kink": 50,
|
||||||
|
"e_kink": 8000,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
metadata={},
|
metadata={},
|
||||||
)
|
)
|
||||||
mock_bragg.scaninfo.scan_msg = msg
|
mock_bragg.scan_info.msg = msg
|
||||||
for field in fields(dev.scan_parameter):
|
scan_param = dev.scan_parameter.model_dump()
|
||||||
assert getattr(dev.scan_parameter, field.name) == None
|
for _, v in scan_param.items():
|
||||||
|
assert v == None
|
||||||
dev._update_scan_parameter()
|
dev._update_scan_parameter()
|
||||||
for field in fields(dev.scan_parameter):
|
scan_param = dev.scan_parameter.model_dump()
|
||||||
assert getattr(dev.scan_parameter, field.name) == msg.content["info"]["kwargs"].get(
|
for k, v in scan_param.items():
|
||||||
field.name, None
|
assert v == msg.content["request_inputs"]["kwargs"].get(k, None)
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def test_kickoff_scan(mock_bragg):
|
def test_kickoff_scan(mock_bragg):
|
||||||
@@ -223,9 +221,14 @@ def test_kickoff_scan(mock_bragg):
|
|||||||
dev.scan_control.scan_start_infinite._read_pv.mock_data = 0
|
dev.scan_control.scan_start_infinite._read_pv.mock_data = 0
|
||||||
status = dev.kickoff()
|
status = dev.kickoff()
|
||||||
assert status.done is False
|
assert status.done is False
|
||||||
dev.scan_control.scan_status._read_pv.mock_data = ScanControlScanStatus.RUNNING
|
# TODO MockPV does not support callbacks yet, so we need to improve here #16
|
||||||
time.sleep(0.2)
|
# dev.scan_control.scan_status._read_pv.mock_data = ScanControlScanStatus.RUNNING
|
||||||
assert status.done is True
|
# 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
|
assert dev.scan_control.scan_start_timer.get() == 1
|
||||||
|
|
||||||
dev.scan_control.scan_duration._read_pv.mock_data = 0
|
dev.scan_control.scan_duration._read_pv.mock_data = 0
|
||||||
@@ -237,200 +240,324 @@ def test_kickoff_scan(mock_bragg):
|
|||||||
assert dev.scan_control.scan_start_infinite.get() == 1
|
assert dev.scan_control.scan_start_infinite.get() == 1
|
||||||
|
|
||||||
|
|
||||||
def test_complete(mock_bragg):
|
# FIXME #22 once mock_pv supports callbacks, high priority!
|
||||||
dev = mock_bragg
|
# def test_complete(mock_bragg):
|
||||||
dev.scan_control.scan_done._read_pv.mock_data = 0
|
# dev = mock_bragg
|
||||||
# Normal case
|
# dev.scan_control.scan_done._read_pv.mock_data = 0
|
||||||
status = dev.complete()
|
# # Normal case
|
||||||
assert status.done is False
|
# status = dev.complete()
|
||||||
assert status.success is False
|
# assert status.done is False
|
||||||
dev.scan_control.scan_done._read_pv.mock_data = 1
|
# assert status.success is False
|
||||||
time.sleep(0.2)
|
# dev.scan_control.scan_done._read_pv.mock_data = 1
|
||||||
assert status.done is True
|
# status.wait()
|
||||||
assert status.success is True
|
# # time.sleep(0.2)
|
||||||
|
# assert status.done is True
|
||||||
|
# assert status.success is True
|
||||||
|
|
||||||
# Stop called case
|
# # Stop called case
|
||||||
dev.scan_control.scan_done._read_pv.mock_data = 0
|
# dev.scan_control.scan_done._read_pv.mock_data = 0
|
||||||
status = dev.complete()
|
# status = dev.complete()
|
||||||
assert status.done is False
|
# assert status.done is False
|
||||||
assert status.success is False
|
# assert status.success is False
|
||||||
dev.stop()
|
# dev.stop()
|
||||||
time.sleep(0.2)
|
# time.sleep(0.2)
|
||||||
assert status.done is True
|
# assert status.done is True
|
||||||
assert status.success is False
|
# assert status.success is False
|
||||||
|
|
||||||
|
|
||||||
def test_unstage(mock_bragg):
|
# FIXME #22 once mock_pv supports callbacks, high priority!
|
||||||
mock_bragg.timeout_for_pvwait = 0.5
|
# def test_unstage(mock_bragg):
|
||||||
mock_bragg.scan_control.scan_val_reset._read_pv.mock_data = 0
|
# mock_bragg.timeout_for_pvwait = 0.5
|
||||||
mock_bragg.scan_control.scan_msg._read_pv.mock_data = ScanControlLoadMessage.PENDING
|
# 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:
|
# with mock.patch.object(mock_bragg.scan_control.scan_val_reset, "put") as mock_put:
|
||||||
mock_bragg.unstage()
|
# status = mock_bragg.unstage()
|
||||||
assert mock_put.call_count == 0
|
# assert mock_put.call_count == 0
|
||||||
mock_bragg.scan_control.scan_msg._read_pv.mock_data = ScanControlLoadMessage.SUCCESS
|
# mock_bragg.scan_control.scan_msg._read_pv.mock_data = ScanControlLoadMessage.SUCCESS
|
||||||
with pytest.raises(TimeoutError):
|
# with pytest.raises(TimeoutError):
|
||||||
mock_bragg.unstage()
|
# mock_bragg.unstage()
|
||||||
assert mock_put.call_count == 1
|
# assert mock_put.call_count == 1
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
# TODO reimplement the test for stage method
|
||||||
"msg",
|
# @pytest.mark.parametrize(
|
||||||
[
|
# "msg",
|
||||||
ScanQueueMessage(
|
# [
|
||||||
scan_type="monitor_scan",
|
# ScanQueueMessage(
|
||||||
parameter={
|
# scan_type="monitor_scan",
|
||||||
"args": {},
|
# parameter={
|
||||||
"kwargs": {"device": "mo1_bragg", "start": 0, "stop": 10, "relative": True},
|
# "args": {},
|
||||||
"num_points": 100,
|
# "kwargs": {
|
||||||
},
|
# "device": "mo1_bragg",
|
||||||
queue="primary",
|
# "start": 0,
|
||||||
metadata={"RID": "test1234"},
|
# "stop": 10,
|
||||||
),
|
# "relative": True,
|
||||||
ScanQueueMessage(
|
# "system_config": {"file_suffix": None, "file_directory": None},
|
||||||
scan_type="xas_simple_scan",
|
# },
|
||||||
parameter={
|
# "num_points": 100,
|
||||||
"args": {},
|
# },
|
||||||
"kwargs": {
|
# queue="primary",
|
||||||
"motor": "mo1_bragg",
|
# metadata={"RID": "test1234"},
|
||||||
"start": 0,
|
# ),
|
||||||
"stop": 10,
|
# ScanQueueMessage(
|
||||||
"scan_time": 1,
|
# scan_type="xas_simple_scan",
|
||||||
"scan_duration": 10,
|
# parameter={
|
||||||
},
|
# "args": {},
|
||||||
"num_points": 100,
|
# "kwargs": {
|
||||||
},
|
# "motor": "mo1_bragg",
|
||||||
queue="primary",
|
# "start": 0,
|
||||||
metadata={"RID": "test1234"},
|
# "stop": 10,
|
||||||
),
|
# "scan_time": 1,
|
||||||
ScanQueueMessage(
|
# "scan_duration": 10,
|
||||||
scan_type="xas_simple_scan_with_xrd",
|
# "system_config": {"file_suffix": None, "file_directory": None},
|
||||||
parameter={
|
# },
|
||||||
"args": {},
|
# "num_points": 100,
|
||||||
"kwargs": {
|
# },
|
||||||
"motor": "mo1_bragg",
|
# queue="primary",
|
||||||
"start": 0,
|
# metadata={"RID": "test1234"},
|
||||||
"stop": 10,
|
# ),
|
||||||
"scan_time": 1,
|
# ScanQueueMessage(
|
||||||
"scan_duration": 10,
|
# scan_type="xas_simple_scan_with_xrd",
|
||||||
"xrd_enable_low": True,
|
# parameter={
|
||||||
"xrd_enable_high": False,
|
# "args": {},
|
||||||
"num_trigger_low": 1,
|
# "kwargs": {
|
||||||
"num_trigger_high": 7,
|
# "motor": "mo1_bragg",
|
||||||
"exp_time_low": 1,
|
# "start": 0,
|
||||||
"exp_time_high": 3,
|
# "stop": 10,
|
||||||
"cycle_low": 1,
|
# "scan_time": 1,
|
||||||
"cycle_high": 5,
|
# "scan_duration": 10,
|
||||||
},
|
# "xrd_enable_low": True,
|
||||||
"num_points": 10,
|
# "xrd_enable_high": False,
|
||||||
},
|
# "num_trigger_low": 1,
|
||||||
queue="primary",
|
# "num_trigger_high": 7,
|
||||||
metadata={"RID": "test1234"},
|
# "exp_time_low": 1,
|
||||||
),
|
# "exp_time_high": 3,
|
||||||
],
|
# "cycle_low": 1,
|
||||||
)
|
# "cycle_high": 5,
|
||||||
def test_stage(mock_bragg, scan_worker_mock, msg):
|
# "system_config": {"file_suffix": None, "file_directory": None},
|
||||||
"""This test is important to check that the stage method of the device is working correctly.
|
# },
|
||||||
Changing the kwargs names in the scans is tightly linked to the logic on the device, thus
|
# "num_points": 10,
|
||||||
it is important to check that the stage method is working correctly for the current implementation.
|
# },
|
||||||
|
# queue="primary",
|
||||||
|
# metadata={"RID": "test1234"},
|
||||||
|
# ),
|
||||||
|
# ScanQueueMessage(
|
||||||
|
# scan_type="xas_advanced_scan",
|
||||||
|
# parameter={
|
||||||
|
# "args": {},
|
||||||
|
# "kwargs": {
|
||||||
|
# "motor": "mo1_bragg",
|
||||||
|
# "start": 8000,
|
||||||
|
# "stop": 9000,
|
||||||
|
# "scan_time": 1,
|
||||||
|
# "scan_duration": 10,
|
||||||
|
# "p_kink": 50,
|
||||||
|
# "e_kink": 8500,
|
||||||
|
# "system_config": {"file_suffix": None, "file_directory": None},
|
||||||
|
# },
|
||||||
|
# "num_points": 100,
|
||||||
|
# },
|
||||||
|
# queue="primary",
|
||||||
|
# metadata={"RID": "test1234"},
|
||||||
|
# ),
|
||||||
|
# ScanQueueMessage(
|
||||||
|
# scan_type="xas_advanced_scan_with_xrd",
|
||||||
|
# parameter={
|
||||||
|
# "args": {},
|
||||||
|
# "kwargs": {
|
||||||
|
# "motor": "mo1_bragg",
|
||||||
|
# "start": 8000,
|
||||||
|
# "stop": 9000,
|
||||||
|
# "scan_time": 1,
|
||||||
|
# "scan_duration": 10,
|
||||||
|
# "p_kink": 50,
|
||||||
|
# "e_kink": 8500,
|
||||||
|
# "xrd_enable_low": True,
|
||||||
|
# "xrd_enable_high": False,
|
||||||
|
# "num_trigger_low": 1,
|
||||||
|
# "num_trigger_high": 7,
|
||||||
|
# "exp_time_low": 1,
|
||||||
|
# "exp_time_high": 3,
|
||||||
|
# "cycle_low": 1,
|
||||||
|
# "cycle_high": 5,
|
||||||
|
# "system_config": {"file_suffix": None, "file_directory": None},
|
||||||
|
# },
|
||||||
|
# "num_points": 10,
|
||||||
|
# },
|
||||||
|
# queue="primary",
|
||||||
|
# metadata={"RID": "test1234"},
|
||||||
|
# ),
|
||||||
|
# ],
|
||||||
|
# )
|
||||||
|
# def test_stage(mock_bragg, scan_worker_mock, msg):
|
||||||
|
# """This test is important to check that the stage method of the device is working correctly.
|
||||||
|
# Changing the kwargs names in the scans is tightly linked to the logic on the device, thus
|
||||||
|
# it is important to check that the stage method is working correctly for the current implementation.
|
||||||
|
|
||||||
Therefor, this test creates a scaninfo message using the scan.open_scan() method to always check
|
# Therefor, this test creates a scaninfo message using the scan.open_scan() method to always check
|
||||||
agains the currently implemented scans vs. the logic on the device"""
|
# agains the currently implemented scans vs. the logic on the device"""
|
||||||
# Create a scaninfo message using scans the ScanQueueMessages above, 3 cases of fly scan; for the general case the procedure is not defined yet
|
# # Create a scaninfo message using scans the ScanQueueMessages above, 3 cases of fly scan; for the general case the procedure is not defined yet
|
||||||
worker = scan_worker_mock
|
# worker = scan_worker_mock
|
||||||
scan_server = worker.parent
|
# scan_server = worker.parent
|
||||||
rb = RequestBlock(msg, assembler=ScanAssembler(parent=scan_server))
|
# rb = RequestBlock(msg, assembler=ScanAssembler(parent=scan_server))
|
||||||
with mock.patch.object(worker, "current_instruction_queue_item"):
|
# with mock.patch.object(worker, "current_instruction_queue_item"):
|
||||||
worker.scan_motors = []
|
# worker.scan_motors = []
|
||||||
worker.readout_priority = {
|
# worker.readout_priority = {
|
||||||
"monitored": [],
|
# "monitored": [],
|
||||||
"baseline": [],
|
# "baseline": [],
|
||||||
"async": [],
|
# "async": [],
|
||||||
"continuous": [],
|
# "continuous": [],
|
||||||
"on_request": [],
|
# "on_request": [],
|
||||||
}
|
# }
|
||||||
open_scan_msg = list(rb.scan.open_scan())[0]
|
# open_scan_msg = list(rb.scan.open_scan())[0]
|
||||||
worker._initialize_scan_info(rb, open_scan_msg, msg.content["parameter"].get("num_points"))
|
# worker._initialize_scan_info(
|
||||||
scan_status_msg = ScanStatusMessage(
|
# rb, open_scan_msg, msg.content["parameter"].get("num_points", 1)
|
||||||
scan_id="test1234", status="closed", info=worker.current_scan_info, metadata={}
|
# )
|
||||||
)
|
# # TODO find a better solution to this...
|
||||||
mock_bragg.scaninfo.scan_msg = scan_status_msg
|
# scan_status_msg = ScanStatusMessage(
|
||||||
|
# scan_id=worker.current_scan_id,
|
||||||
|
# status="open",
|
||||||
|
# scan_name=worker.current_scan_info.get("scan_name"),
|
||||||
|
# scan_number=worker.current_scan_info.get("scan_number"),
|
||||||
|
# session_id=worker.current_scan_info.get("session_id"),
|
||||||
|
# dataset_number=worker.current_scan_info.get("dataset_number"),
|
||||||
|
# num_points=worker.current_scan_info.get("num_points"),
|
||||||
|
# scan_type=worker.current_scan_info.get("scan_type"),
|
||||||
|
# scan_report_devices=worker.current_scan_info.get("scan_report_devices"),
|
||||||
|
# user_metadata=worker.current_scan_info.get("user_metadata"),
|
||||||
|
# readout_priority=worker.current_scan_info.get("readout_priority"),
|
||||||
|
# scan_parameters=worker.current_scan_info.get("scan_parameters"),
|
||||||
|
# request_inputs=worker.current_scan_info.get("request_inputs"),
|
||||||
|
# info=worker.current_scan_info,
|
||||||
|
# )
|
||||||
|
# mock_bragg.scan_info.msg = scan_status_msg
|
||||||
|
|
||||||
# Ensure that ScanControlLoadMessage is set to SUCCESS
|
# # Ensure that ScanControlLoadMessage is set to SUCCESS
|
||||||
mock_bragg.scan_control.scan_msg._read_pv.mock_data = ScanControlLoadMessage.SUCCESS
|
# mock_bragg.scan_control.scan_msg._read_pv.mock_data = ScanControlLoadMessage.SUCCESS
|
||||||
with (
|
# 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, "_check_scan_msg") as mock_check_scan_msg,
|
# mock.patch.object(mock_bragg, "on_unstage"),
|
||||||
mock.patch.object(mock_bragg, "on_unstage"),
|
# ):
|
||||||
):
|
# scan_name = scan_status_msg.content["info"].get("scan_name", "")
|
||||||
scan_name = scan_status_msg.content["info"].get("scan_name", "")
|
# # Chek the not implemented fly scan first, should raise Mo1BraggError
|
||||||
# Chek the not implemented fly scan first, should raise Mo1BraggError
|
# if scan_name not in [
|
||||||
if scan_name not in ["xas_simple_scan", "xas_simple_scan_with_xrd"]:
|
# "xas_simple_scan",
|
||||||
with pytest.raises(Mo1BraggError):
|
# "xas_simple_scan_with_xrd",
|
||||||
mock_bragg.stage()
|
# "xas_advanced_scan",
|
||||||
assert mock_check_scan_msg.call_count == 1
|
# "xas_advanced_scan_with_xrd",
|
||||||
assert mock_load_scan_metadata.call_count == 1
|
# ]:
|
||||||
else:
|
# with pytest.raises(Mo1BraggError):
|
||||||
with (
|
# mock_bragg.stage()
|
||||||
mock.patch.object(mock_bragg, "set_xas_settings") as mock_xas_settings,
|
# assert mock_check_scan_msg.call_count == 1
|
||||||
mock.patch.object(mock_bragg, "set_xrd_settings") as mock_xrd_settings,
|
# else:
|
||||||
mock.patch.object(
|
# with (
|
||||||
mock_bragg, "set_scan_control_settings"
|
# mock.patch.object(mock_bragg, "set_xas_settings") as mock_xas_settings,
|
||||||
) as mock_set_scan_control_settings,
|
# mock.patch.object(
|
||||||
):
|
# mock_bragg, "set_advanced_xas_settings"
|
||||||
# Check xas_simple_scan
|
# ) as mock_advanced_xas_settings,
|
||||||
if scan_name == "xas_simple_scan":
|
# mock.patch.object(mock_bragg, "set_trig_settings") as mock_trig_settings,
|
||||||
mock_bragg.stage()
|
# mock.patch.object(
|
||||||
assert mock_xas_settings.call_args == mock.call(
|
# mock_bragg, "set_scan_control_settings"
|
||||||
low=scan_status_msg.content["info"]["kwargs"]["start"],
|
# ) as mock_set_scan_control_settings,
|
||||||
high=scan_status_msg.content["info"]["kwargs"]["stop"],
|
# ):
|
||||||
scan_time=scan_status_msg.content["info"]["kwargs"]["scan_time"],
|
# # Check xas_simple_scan
|
||||||
)
|
# if scan_name == "xas_simple_scan":
|
||||||
assert mock_xrd_settings.call_args == mock.call(
|
# mock_bragg.stage()
|
||||||
enable_low=False,
|
# assert mock_xas_settings.call_args == mock.call(
|
||||||
enable_high=False,
|
# low=scan_status_msg.content["info"]["kwargs"]["start"],
|
||||||
num_trigger_low=0,
|
# high=scan_status_msg.content["info"]["kwargs"]["stop"],
|
||||||
num_trigger_high=0,
|
# scan_time=scan_status_msg.content["info"]["kwargs"]["scan_time"],
|
||||||
exp_time_low=0,
|
# )
|
||||||
exp_time_high=0,
|
# assert mock_trig_settings.call_args == mock.call(
|
||||||
cycle_low=0,
|
# enable_low=False,
|
||||||
cycle_high=0,
|
# enable_high=False,
|
||||||
)
|
# exp_time_low=0,
|
||||||
assert mock_set_scan_control_settings.call_args == mock.call(
|
# exp_time_high=0,
|
||||||
mode=ScanControlMode.SIMPLE,
|
# cycle_low=0,
|
||||||
scan_duration=scan_status_msg.content["info"]["kwargs"][
|
# cycle_high=0,
|
||||||
"scan_duration"
|
# )
|
||||||
],
|
# assert mock_set_scan_control_settings.call_args == mock.call(
|
||||||
)
|
# mode=ScanControlMode.SIMPLE,
|
||||||
# Check xas_simple_scan_with_xrd
|
# scan_duration=scan_status_msg.content["info"]["kwargs"][
|
||||||
elif scan_name == "xas_simple_scan_with_xrd":
|
# "scan_duration"
|
||||||
mock_bragg.stage()
|
# ],
|
||||||
assert mock_xas_settings.call_args == mock.call(
|
# )
|
||||||
low=scan_status_msg.content["info"]["kwargs"]["start"],
|
# # Check xas_simple_scan_with_xrd
|
||||||
high=scan_status_msg.content["info"]["kwargs"]["stop"],
|
# elif scan_name == "xas_simple_scan_with_xrd":
|
||||||
scan_time=scan_status_msg.content["info"]["kwargs"]["scan_time"],
|
# mock_bragg.stage()
|
||||||
)
|
# assert mock_xas_settings.call_args == mock.call(
|
||||||
assert mock_xrd_settings.call_args == mock.call(
|
# low=scan_status_msg.content["info"]["kwargs"]["start"],
|
||||||
enable_low=scan_status_msg.content["info"]["kwargs"]["xrd_enable_low"],
|
# high=scan_status_msg.content["info"]["kwargs"]["stop"],
|
||||||
enable_high=scan_status_msg.content["info"]["kwargs"][
|
# scan_time=scan_status_msg.content["info"]["kwargs"]["scan_time"],
|
||||||
"xrd_enable_high"
|
# )
|
||||||
],
|
# assert mock_trig_settings.call_args == mock.call(
|
||||||
num_trigger_low=scan_status_msg.content["info"]["kwargs"][
|
# enable_low=scan_status_msg.content["info"]["kwargs"]["xrd_enable_low"],
|
||||||
"num_trigger_low"
|
# enable_high=scan_status_msg.content["info"]["kwargs"][
|
||||||
],
|
# "xrd_enable_high"
|
||||||
num_trigger_high=scan_status_msg.content["info"]["kwargs"][
|
# ],
|
||||||
"num_trigger_high"
|
# exp_time_low=scan_status_msg.content["info"]["kwargs"]["exp_time_low"],
|
||||||
],
|
# exp_time_high=scan_status_msg.content["info"]["kwargs"][
|
||||||
exp_time_low=scan_status_msg.content["info"]["kwargs"]["exp_time_low"],
|
# "exp_time_high"
|
||||||
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"],
|
||||||
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,
|
||||||
assert mock_set_scan_control_settings.call_args == mock.call(
|
# scan_duration=scan_status_msg.content["info"]["kwargs"][
|
||||||
mode=ScanControlMode.SIMPLE,
|
# "scan_duration"
|
||||||
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"
|
||||||
|
# ],
|
||||||
|
# )
|
||||||
|
|||||||
88
tests/tests_devices/test_mo1_bragg_angle.py
Normal file
88
tests/tests_devices/test_mo1_bragg_angle.py
Normal 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
|
||||||
152
tests/tests_devices/test_mo1_bragg_utils.py
Normal file
152
tests/tests_devices/test_mo1_bragg_utils.py
Normal file
@@ -0,0 +1,152 @@
|
|||||||
|
# pylint: skip-file
|
||||||
|
import numpy as np
|
||||||
|
|
||||||
|
import debye_bec.devices.mo1_bragg.mo1_bragg_utils as utils
|
||||||
|
|
||||||
|
|
||||||
|
def test_compute_spline():
|
||||||
|
p, v, dt = utils.compute_spline(
|
||||||
|
low_deg=10, high_deg=12, p_kink=50, e_kink_deg=11, scan_time=0.5
|
||||||
|
)
|
||||||
|
|
||||||
|
rtol = 1e-6
|
||||||
|
atol = 1e-3
|
||||||
|
p_desired = [
|
||||||
|
9.98,
|
||||||
|
9.98376125,
|
||||||
|
9.99479,
|
||||||
|
10.01270375,
|
||||||
|
10.03712,
|
||||||
|
10.06765625,
|
||||||
|
10.10393,
|
||||||
|
10.14555875,
|
||||||
|
10.19216,
|
||||||
|
10.24335125,
|
||||||
|
10.29875,
|
||||||
|
10.35797375,
|
||||||
|
10.42064,
|
||||||
|
10.48636625,
|
||||||
|
10.55477,
|
||||||
|
10.62546875,
|
||||||
|
10.69808,
|
||||||
|
10.77222125,
|
||||||
|
10.84751,
|
||||||
|
10.92356375,
|
||||||
|
11.0,
|
||||||
|
11.07643625,
|
||||||
|
11.15249,
|
||||||
|
11.22777875,
|
||||||
|
11.30192,
|
||||||
|
11.37453125,
|
||||||
|
11.44523,
|
||||||
|
11.51363375,
|
||||||
|
11.57936,
|
||||||
|
11.64202625,
|
||||||
|
11.70125,
|
||||||
|
11.75664875,
|
||||||
|
11.80784,
|
||||||
|
11.85444125,
|
||||||
|
11.89607,
|
||||||
|
11.93234375,
|
||||||
|
11.96288,
|
||||||
|
11.98729625,
|
||||||
|
12.00521,
|
||||||
|
12.01623875,
|
||||||
|
12.02,
|
||||||
|
]
|
||||||
|
v_desired = [
|
||||||
|
0.0,
|
||||||
|
1.50156441,
|
||||||
|
2.35715667,
|
||||||
|
2.90783907,
|
||||||
|
3.29035796,
|
||||||
|
3.57019636,
|
||||||
|
3.78263174,
|
||||||
|
3.9483388,
|
||||||
|
4.08022441,
|
||||||
|
4.18675043,
|
||||||
|
4.27368333,
|
||||||
|
4.34507577,
|
||||||
|
4.40384627,
|
||||||
|
4.45213618,
|
||||||
|
4.49153736,
|
||||||
|
4.52324148,
|
||||||
|
4.54814006,
|
||||||
|
4.5668924,
|
||||||
|
4.57997194,
|
||||||
|
4.58769736,
|
||||||
|
4.59025246,
|
||||||
|
4.58769736,
|
||||||
|
4.57997194,
|
||||||
|
4.5668924,
|
||||||
|
4.54814006,
|
||||||
|
4.52324148,
|
||||||
|
4.49153736,
|
||||||
|
4.45213618,
|
||||||
|
4.40384627,
|
||||||
|
4.34507577,
|
||||||
|
4.27368333,
|
||||||
|
4.18675043,
|
||||||
|
4.08022441,
|
||||||
|
3.9483388,
|
||||||
|
3.78263174,
|
||||||
|
3.57019636,
|
||||||
|
3.29035796,
|
||||||
|
2.90783907,
|
||||||
|
2.35715667,
|
||||||
|
1.50156441,
|
||||||
|
0.0,
|
||||||
|
]
|
||||||
|
dt_desired = [
|
||||||
|
0.0,
|
||||||
|
4.34081063,
|
||||||
|
5.57222438,
|
||||||
|
6.73882688,
|
||||||
|
7.84061813,
|
||||||
|
8.87759812,
|
||||||
|
9.84976688,
|
||||||
|
10.75712437,
|
||||||
|
11.59967063,
|
||||||
|
12.37740563,
|
||||||
|
13.09032937,
|
||||||
|
13.73844188,
|
||||||
|
14.32174313,
|
||||||
|
14.84023312,
|
||||||
|
15.29391188,
|
||||||
|
15.68277937,
|
||||||
|
16.00683562,
|
||||||
|
16.26608063,
|
||||||
|
16.46051438,
|
||||||
|
16.59013687,
|
||||||
|
16.65494813,
|
||||||
|
16.65494813,
|
||||||
|
16.59013687,
|
||||||
|
16.46051438,
|
||||||
|
16.26608063,
|
||||||
|
16.00683562,
|
||||||
|
15.68277938,
|
||||||
|
15.29391188,
|
||||||
|
14.84023312,
|
||||||
|
14.32174313,
|
||||||
|
13.73844187,
|
||||||
|
13.09032938,
|
||||||
|
12.37740562,
|
||||||
|
11.59967063,
|
||||||
|
10.75712437,
|
||||||
|
9.84976687,
|
||||||
|
8.87759813,
|
||||||
|
7.84061812,
|
||||||
|
6.73882688,
|
||||||
|
5.57222437,
|
||||||
|
4.34081063,
|
||||||
|
]
|
||||||
|
|
||||||
|
np.testing.assert_allclose(p, p_desired, rtol, atol)
|
||||||
|
np.testing.assert_allclose(v, v_desired, rtol, atol)
|
||||||
|
np.testing.assert_allclose(dt, dt_desired, rtol, atol)
|
||||||
|
|
||||||
|
assert utils.SAFETY_FACTOR == 0.025
|
||||||
|
assert utils.N_SAMPLES == 41
|
||||||
|
assert utils.DEGREE_SPLINE == 3
|
||||||
|
assert utils.TIME_COMPENSATE_SPLINE == 0.0062
|
||||||
|
assert utils.POSITION_COMPONSATION == 0.02
|
||||||
166
tests/tests_devices/test_nidaq.py
Normal file
166
tests/tests_devices/test_nidaq.py
Normal 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
|
||||||
285
tests/tests_devices/test_pilatus.py
Normal file
285
tests/tests_devices/test_pilatus.py
Normal file
@@ -0,0 +1,285 @@
|
|||||||
|
# pylint: skip-file
|
||||||
|
import os
|
||||||
|
import threading
|
||||||
|
from typing import TYPE_CHECKING, Generator
|
||||||
|
from unittest import mock
|
||||||
|
|
||||||
|
import numpy as np
|
||||||
|
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):
|
||||||
|
"""Test the on_complete logic of the Pilatus detector."""
|
||||||
|
|
||||||
|
if pilatus.scan_info.msg.scan_name.startswith("xas"):
|
||||||
|
# TODO add test cases for xas scans
|
||||||
|
# status = pilatus.complete()
|
||||||
|
# assert status.done is True
|
||||||
|
# assert status.success is True
|
||||||
|
return
|
||||||
|
# Check in addition that the file event is set properly, once with if it works, and once if not (i.e. when cancelled)
|
||||||
|
for success in [True, False]:
|
||||||
|
if success is True:
|
||||||
|
pilatus.file_event.put(file_path="", done=False, successful=False)
|
||||||
|
pilatus._full_path = "file-path-for-success"
|
||||||
|
else:
|
||||||
|
pilatus.file_event.put(file_path="", done=False, successful=True)
|
||||||
|
pilatus._full_path = "file-path-for-failure"
|
||||||
|
# Set values for relevant PVs
|
||||||
|
pilatus.cam.acquire._read_pv.mock_data = ACQUIREMODE.ACQUIRING.value
|
||||||
|
pilatus.hdf.capture._read_pv.mock_data = ACQUIREMODE.ACQUIRING.value
|
||||||
|
pilatus.cam.armed._read_pv.mock_data = DETECTORSTATE.ARMED.value
|
||||||
|
num_images = pilatus.scan_info.msg.num_points * pilatus.scan_info.msg.scan_parameters.get(
|
||||||
|
"frames_per_trigger", 1
|
||||||
|
)
|
||||||
|
pilatus.hdf.num_captured._read_pv.mock_data = num_images - 1
|
||||||
|
# Call on complete
|
||||||
|
pilatus.n_images = num_images
|
||||||
|
status = pilatus.complete()
|
||||||
|
# Should not be finished
|
||||||
|
assert status.done is False
|
||||||
|
pilatus.cam.acquire.put(ACQUIREMODE.DONE.value)
|
||||||
|
pilatus.hdf.capture.put(ACQUIREMODE.DONE.value)
|
||||||
|
pilatus.cam.armed.put(DETECTORSTATE.UNARMED.value)
|
||||||
|
assert status.done is False
|
||||||
|
if success is True:
|
||||||
|
pilatus.hdf.num_captured._read_pv.mock_data = num_images
|
||||||
|
# Now it should resolve
|
||||||
|
status.wait(timeout=5)
|
||||||
|
assert status.done is True
|
||||||
|
assert status.success is True
|
||||||
|
file_msg: FileMessage = pilatus.file_event.get()
|
||||||
|
assert file_msg.file_path == "file-path-for-success"
|
||||||
|
assert file_msg.done is True
|
||||||
|
assert file_msg.successful is True
|
||||||
|
else:
|
||||||
|
with pytest.raises(DeviceStoppedError):
|
||||||
|
pilatus.stop()
|
||||||
|
status.wait(timeout=5)
|
||||||
|
assert status.done is True
|
||||||
|
assert status.success is False
|
||||||
|
file_msg: FileMessage = pilatus.file_event.get()
|
||||||
|
assert file_msg.file_path == "file-path-for-failure"
|
||||||
|
assert file_msg.done is True
|
||||||
|
assert file_msg.successful is False
|
||||||
|
|
||||||
|
|
||||||
|
# TODO, figure out how to properly test this..
|
||||||
|
# def test_pilatus_on_stage(pilatus):
|
||||||
|
# """Test the on_stage logic of the Pilatus detector."""
|
||||||
|
# # Make sure that no additional logic from stage signals of underlying components is triggered
|
||||||
|
# pilatus.stage_sigs = {}
|
||||||
|
# pilatus.cam.stage_sigs = {}
|
||||||
|
# pilatus.hdf.stage_sigs = {}
|
||||||
|
# if pilatus.scan_info.msg.scan_name.startswith("xas"):
|
||||||
|
# pilatus.on_stage()
|
||||||
|
# return
|
||||||
|
# exp_time = pilatus.scan_info.msg.scan_parameters.get("exp_time", 0.1)
|
||||||
|
# n_images = pilatus.scan_info.msg.num_points * pilatus.scan_info.msg.scan_parameters.get(
|
||||||
|
# "frames_per_trigger", 1
|
||||||
|
# )
|
||||||
|
# if exp_time <= 0.1:
|
||||||
|
# with pytest.raises(ValueError):
|
||||||
|
# pilatus.on_stage()
|
||||||
|
# return
|
||||||
|
# pilatus.filter_number.put(10)
|
||||||
|
# pilatus.cam.array_counter.put(1)
|
||||||
|
# file_components = pilatus.scan_info.msg.info.get("file_components", ("", ""))[0]
|
||||||
|
# base_path = file_components[0].rsplit("/", 1)[0]
|
||||||
|
# file_name = file_components[0].rsplit("/", 1)[1] + "_pilatus.h5"
|
||||||
|
# file_path = os.path.join(base_path, file_name)
|
||||||
|
# pilatus.on_stage()
|
||||||
|
# assert pilatus.cam.array_callbacks.get() == 0
|
||||||
|
# assert pilatus.hdf.enable.get() == 1
|
||||||
|
# assert pilatus.cam.num_exposures.get() == 1
|
||||||
|
# assert pilatus.cam.num_images.get() == n_images
|
||||||
|
# assert pilatus.cam.acquire_time.get() == exp_time - pilatus._readout_time
|
||||||
|
# assert pilatus.cam.acquire_period.get() == exp_time
|
||||||
|
# assert pilatus.filter_number.get() == 0
|
||||||
|
# assert pilatus.hdf.file_path.get() == base_path
|
||||||
|
# assert pilatus.hdf.file_name.get() == file_name
|
||||||
|
# assert pilatus.hdf.num_capture.get() == n_images
|
||||||
|
# assert pilatus.cam.array_counter.get() == 0
|
||||||
|
# file_msg: FileMessage = pilatus.file_event.get()
|
||||||
|
# assert file_msg.file_path == file_path
|
||||||
|
# assert file_msg.done is False
|
||||||
|
# assert file_msg.successful is False
|
||||||
|
|
||||||
|
|
||||||
|
def test_pilatus_on_stage_raises_low_exp_time(pilatus):
|
||||||
|
"""Test that on_stage raises a ValueError if the exposure time is too low."""
|
||||||
|
pilatus.scan_info.msg.scan_parameters["exp_time"] = 0.09
|
||||||
|
scan_msg = pilatus.scan_info.msg
|
||||||
|
if scan_msg.scan_type != "step" and scan_msg.scan_name not in pilatus.xas_xrd_scan_names:
|
||||||
|
return
|
||||||
|
with pytest.raises(ValueError):
|
||||||
|
pilatus.on_stage()
|
||||||
@@ -1,31 +1,34 @@
|
|||||||
# Getting Started with Testing using pytest
|
# Getting Started with Testing using pytest
|
||||||
|
|
||||||
BEC is using the [pytest](https://docs.pytest.org/en/8.0.x/) framework.
|
BEC is using the [pytest](https://docs.pytest.org/en/latest/) framework.
|
||||||
It can be install via
|
It can be installed via
|
||||||
``` bash
|
|
||||||
|
```bash
|
||||||
pip install pytest
|
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.
|
We note that pytest is part of the optional-dependencies `[dev]` of the plugin package.
|
||||||
|
|
||||||
## Introduction
|
## Introduction
|
||||||
|
|
||||||
Tests in this package should be stored in the `tests` directory.
|
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>`.
|
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
|
pytest -v --random-order ./tests
|
||||||
```
|
```
|
||||||
|
|
||||||
Note, the python environment needs to be active.
|
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 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
|
## 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.
|
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).
|
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).
|
In addition, we also recommend to take a look at the [How-to guides from pytest](https://docs.pytest.org/en/8.0.x/how-to/index.html).
|
||||||
|
|
||||||
|
|||||||
@@ -1,31 +1,34 @@
|
|||||||
# Getting Started with Testing using pytest
|
# Getting Started with Testing using pytest
|
||||||
|
|
||||||
BEC is using the [pytest](https://docs.pytest.org/en/8.0.x/) framework.
|
BEC is using the [pytest](https://docs.pytest.org/en/latest/) framework.
|
||||||
It can be install via
|
It can be installed via
|
||||||
``` bash
|
|
||||||
|
```bash
|
||||||
pip install pytest
|
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.
|
We note that pytest is part of the optional-dependencies `[dev]` of the plugin package.
|
||||||
|
|
||||||
## Introduction
|
## Introduction
|
||||||
|
|
||||||
Tests in this package should be stored in the `tests` directory.
|
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>`.
|
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
|
pytest -v --random-order ./tests
|
||||||
```
|
```
|
||||||
|
|
||||||
Note, the python environment needs to be active.
|
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 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
|
## 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.
|
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).
|
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).
|
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).
|
||||||
|
|
||||||
|
|||||||
36
tests/tests_scans/conftest.py
Normal file
36
tests/tests_scans/conftest.py
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
# pylint: skip-file
|
||||||
|
from functools import partial
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
from bec_server.device_server.tests.utils import DeviceMockType, DMMock
|
||||||
|
from bec_server.scan_server.tests.fixtures import (
|
||||||
|
ScanStubStatusMock,
|
||||||
|
connector_mock,
|
||||||
|
instruction_handler_mock,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def device_manager_mock():
|
||||||
|
device_manager = DMMock()
|
||||||
|
device_manager.add_device(
|
||||||
|
"mo1_bragg", dev_type=DeviceMockType.POSITIONER, readout_priority="monitored"
|
||||||
|
)
|
||||||
|
device_manager.add_device("samx")
|
||||||
|
device_manager.add_device(
|
||||||
|
"eiger", dev_type=DeviceMockType.SIGNAL, readout_priority="monitored", software_trigger=True
|
||||||
|
)
|
||||||
|
device_manager.add_device("bpm4i", dev_type=DeviceMockType.SIGNAL, readout_priority="monitored")
|
||||||
|
yield device_manager
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def scan_assembler(instruction_handler_mock, device_manager_mock):
|
||||||
|
def _assemble_scan(scan_class, *args, **kwargs):
|
||||||
|
return scan_class(*args, **kwargs)
|
||||||
|
|
||||||
|
return partial(
|
||||||
|
_assemble_scan,
|
||||||
|
instruction_handler=instruction_handler_mock,
|
||||||
|
device_manager=device_manager_mock,
|
||||||
|
)
|
||||||
@@ -4,300 +4,430 @@ from unittest import mock
|
|||||||
from bec_lib.messages import DeviceInstructionMessage
|
from bec_lib.messages import DeviceInstructionMessage
|
||||||
from bec_server.device_server.tests.utils import DMMock
|
from bec_server.device_server.tests.utils import DMMock
|
||||||
|
|
||||||
from debye_bec.scans import XASSimpleScan, XASSimpleScanWithXRD
|
from debye_bec.scans import (
|
||||||
|
XASAdvancedScan,
|
||||||
|
XASAdvancedScanWithXRD,
|
||||||
|
XASSimpleScan,
|
||||||
|
XASSimpleScanWithXRD,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def test_xas_simple_scan():
|
def get_instructions(request, ScanStubStatusMock):
|
||||||
# create a fake device manager that we can use to add devices
|
|
||||||
device_manager = DMMock()
|
|
||||||
device_manager.add_device("mo1_bragg")
|
|
||||||
|
|
||||||
request = XASSimpleScan(
|
|
||||||
start=0, stop=5, scan_time=1, scan_duration=10, device_manager=device_manager
|
|
||||||
)
|
|
||||||
request.metadata["RID"] = "my_test_request_id"
|
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 (
|
with (
|
||||||
mock.patch.object(request.stubs, "get_req_status", side_effect=[False, True]),
|
mock.patch.object(request.stubs, "complete", side_effect=fake_complete),
|
||||||
mock.patch.object(request.stubs, "_get_from_rpc", return_value=True),
|
mock.patch.object(request.stubs, "_get_result_from_status", return_value=None),
|
||||||
):
|
):
|
||||||
reference_commands = list(request.run())
|
reference_commands = list(request.run())
|
||||||
|
|
||||||
for cmd in reference_commands:
|
for cmd in reference_commands:
|
||||||
if not cmd:
|
if not cmd or isinstance(cmd, str):
|
||||||
continue
|
continue
|
||||||
if "RID" in cmd.metadata:
|
if "RID" in cmd.metadata:
|
||||||
cmd.metadata["RID"] = "my_test_request_id"
|
cmd.metadata["RID"] = "my_test_request_id"
|
||||||
if "rpc_id" in cmd.parameter:
|
if "rpc_id" in cmd.parameter:
|
||||||
cmd.parameter["rpc_id"] = "my_test_rpc_id"
|
cmd.parameter["rpc_id"] = "my_test_rpc_id"
|
||||||
|
cmd.metadata.pop("device_instr_id", None)
|
||||||
|
|
||||||
assert reference_commands == [
|
return reference_commands
|
||||||
None,
|
|
||||||
None,
|
|
||||||
DeviceInstructionMessage(
|
def test_xas_simple_scan(scan_assembler, ScanStubStatusMock):
|
||||||
metadata={"readout_priority": "monitored", "DIID": 0, "RID": "my_test_request_id"},
|
|
||||||
device=None,
|
request = scan_assembler(XASSimpleScan, start=0, stop=5, scan_time=1, scan_duration=10)
|
||||||
action="scan_report_instruction",
|
request.device_manager.add_device("nidaq")
|
||||||
parameter={"device_progress": ["mo1_bragg"]},
|
reference_commands = get_instructions(request, ScanStubStatusMock)
|
||||||
),
|
|
||||||
DeviceInstructionMessage(
|
assert reference_commands == [
|
||||||
metadata={"readout_priority": "monitored", "DIID": 1, "RID": "my_test_request_id"},
|
None,
|
||||||
device=None,
|
None,
|
||||||
action="open_scan",
|
DeviceInstructionMessage(
|
||||||
parameter={
|
metadata={"readout_priority": "monitored", "RID": "my_test_request_id"},
|
||||||
"scan_motors": [],
|
device=None,
|
||||||
"readout_priority": {
|
action="scan_report_instruction",
|
||||||
"monitored": [],
|
parameter={"device_progress": ["mo1_bragg"]},
|
||||||
"baseline": [],
|
),
|
||||||
"on_request": [],
|
DeviceInstructionMessage(
|
||||||
"async": [],
|
metadata={"readout_priority": "monitored", "RID": "my_test_request_id"},
|
||||||
},
|
device=None,
|
||||||
"num_points": None,
|
action="open_scan",
|
||||||
"positions": [0.0, 5.0],
|
parameter={
|
||||||
"scan_name": "xas_simple_scan",
|
"scan_motors": [],
|
||||||
"scan_type": "fly",
|
"readout_priority": {
|
||||||
|
"monitored": [],
|
||||||
|
"baseline": [],
|
||||||
|
"on_request": [],
|
||||||
|
"async": ["nidaq"],
|
||||||
},
|
},
|
||||||
),
|
"num_points": None,
|
||||||
DeviceInstructionMessage(
|
"positions": [0.0, 5.0],
|
||||||
metadata={"readout_priority": "monitored", "DIID": 2, "RID": "my_test_request_id"},
|
"scan_name": "xas_simple_scan",
|
||||||
device=None,
|
"scan_type": "fly",
|
||||||
action="stage",
|
},
|
||||||
parameter={},
|
),
|
||||||
),
|
DeviceInstructionMessage(metadata={}, device="nidaq", action="stage", parameter={}),
|
||||||
DeviceInstructionMessage(
|
DeviceInstructionMessage(
|
||||||
metadata={"readout_priority": "baseline", "DIID": 3, "RID": "my_test_request_id"},
|
metadata={},
|
||||||
device=None,
|
device=["bpm4i", "eiger", "mo1_bragg", "samx"],
|
||||||
action="baseline_reading",
|
action="stage",
|
||||||
parameter={},
|
parameter={},
|
||||||
),
|
),
|
||||||
DeviceInstructionMessage(
|
DeviceInstructionMessage(
|
||||||
metadata={
|
metadata={"readout_priority": "baseline", "RID": "my_test_request_id"},
|
||||||
"readout_priority": "monitored",
|
device=["samx"],
|
||||||
"DIID": 4,
|
action="read",
|
||||||
"RID": "my_test_request_id",
|
parameter={},
|
||||||
"response": True,
|
),
|
||||||
},
|
DeviceInstructionMessage(
|
||||||
device="mo1_bragg",
|
metadata={"readout_priority": "monitored", "RID": "my_test_request_id"},
|
||||||
action="rpc",
|
device=["bpm4i", "eiger", "mo1_bragg", "nidaq", "samx"],
|
||||||
parameter={
|
action="pre_scan",
|
||||||
"device": "mo1_bragg",
|
parameter={},
|
||||||
"func": "move_type.set",
|
),
|
||||||
"rpc_id": "my_test_rpc_id",
|
DeviceInstructionMessage(
|
||||||
"args": ("energy",),
|
metadata={"readout_priority": "monitored", "RID": "my_test_request_id"},
|
||||||
"kwargs": {},
|
device="mo1_bragg",
|
||||||
},
|
action="kickoff",
|
||||||
),
|
parameter={"configure": {}},
|
||||||
DeviceInstructionMessage(
|
),
|
||||||
metadata={"readout_priority": "monitored", "DIID": 5, "RID": "my_test_request_id"},
|
"fake_complete",
|
||||||
device="mo1_bragg",
|
DeviceInstructionMessage(
|
||||||
action="kickoff",
|
metadata={"readout_priority": "monitored", "RID": "my_test_request_id", "point_id": 0},
|
||||||
parameter={"configure": {}, "wait_group": "kickoff"},
|
device=["bpm4i", "eiger", "mo1_bragg"],
|
||||||
),
|
action="read",
|
||||||
DeviceInstructionMessage(
|
parameter={"group": "monitored"},
|
||||||
metadata={"readout_priority": "monitored", "DIID": 6, "RID": "my_test_request_id"},
|
),
|
||||||
device="mo1_bragg",
|
DeviceInstructionMessage(
|
||||||
action="complete",
|
metadata={"readout_priority": "monitored", "RID": "my_test_request_id", "point_id": 1},
|
||||||
parameter={},
|
device=["bpm4i", "eiger", "mo1_bragg"],
|
||||||
),
|
action="read",
|
||||||
DeviceInstructionMessage(
|
parameter={"group": "monitored"},
|
||||||
metadata={"readout_priority": "monitored", "DIID": 7, "RID": "my_test_request_id"},
|
),
|
||||||
device=None,
|
"fake_complete",
|
||||||
action="read",
|
DeviceInstructionMessage(
|
||||||
parameter={"group": "primary", "wait_group": "readout_primary"},
|
metadata={},
|
||||||
),
|
device=["bpm4i", "eiger", "mo1_bragg", "nidaq", "samx"],
|
||||||
DeviceInstructionMessage(
|
action="unstage",
|
||||||
metadata={"readout_priority": "monitored", "DIID": 8, "RID": "my_test_request_id"},
|
parameter={},
|
||||||
device=None,
|
),
|
||||||
action="wait",
|
DeviceInstructionMessage(
|
||||||
parameter={"type": "read", "group": "primary", "wait_group": "readout_primary"},
|
metadata={"readout_priority": "monitored", "RID": "my_test_request_id"},
|
||||||
),
|
device=None,
|
||||||
DeviceInstructionMessage(
|
action="close_scan",
|
||||||
metadata={"readout_priority": "monitored", "DIID": 9, "RID": "my_test_request_id"},
|
parameter={},
|
||||||
device=None,
|
),
|
||||||
action="read",
|
]
|
||||||
parameter={"group": "primary", "wait_group": "readout_primary"},
|
|
||||||
),
|
|
||||||
DeviceInstructionMessage(
|
|
||||||
metadata={"readout_priority": "monitored", "DIID": 10, "RID": "my_test_request_id"},
|
|
||||||
device=None,
|
|
||||||
action="wait",
|
|
||||||
parameter={"type": "read", "group": "primary", "wait_group": "readout_primary"},
|
|
||||||
),
|
|
||||||
DeviceInstructionMessage(
|
|
||||||
metadata={"readout_priority": "monitored", "DIID": 11, "RID": "my_test_request_id"},
|
|
||||||
device=None,
|
|
||||||
action="wait",
|
|
||||||
parameter={"type": "read", "group": "primary", "wait_group": "readout_primary"},
|
|
||||||
),
|
|
||||||
DeviceInstructionMessage(
|
|
||||||
metadata={"readout_priority": "monitored", "DIID": 12, "RID": "my_test_request_id"},
|
|
||||||
device=None,
|
|
||||||
action="complete",
|
|
||||||
parameter={},
|
|
||||||
),
|
|
||||||
DeviceInstructionMessage(
|
|
||||||
metadata={"readout_priority": "monitored", "DIID": 13, "RID": "my_test_request_id"},
|
|
||||||
device=None,
|
|
||||||
action="unstage",
|
|
||||||
parameter={},
|
|
||||||
),
|
|
||||||
DeviceInstructionMessage(
|
|
||||||
metadata={"readout_priority": "monitored", "DIID": 14, "RID": "my_test_request_id"},
|
|
||||||
device=None,
|
|
||||||
action="close_scan",
|
|
||||||
parameter={},
|
|
||||||
),
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
def test_xas_simple_scan_with_xrd():
|
def test_xas_simple_scan_with_xrd(scan_assembler, ScanStubStatusMock):
|
||||||
# create a fake device manager that we can use to add devices
|
|
||||||
device_manager = DMMock()
|
|
||||||
device_manager.add_device("mo1_bragg")
|
|
||||||
|
|
||||||
request = XASSimpleScanWithXRD(
|
request = scan_assembler(
|
||||||
|
XASSimpleScanWithXRD,
|
||||||
start=0,
|
start=0,
|
||||||
stop=5,
|
stop=5,
|
||||||
scan_time=1,
|
scan_time=1,
|
||||||
scan_duration=10,
|
scan_duration=10,
|
||||||
xrd_enable_low=True,
|
break_enable_low=True,
|
||||||
num_trigger_low=1,
|
break_time_low=1,
|
||||||
exp_time_low=1,
|
|
||||||
cycle_low=1,
|
cycle_low=1,
|
||||||
xrd_enable_high=True,
|
break_enable_high=True,
|
||||||
num_trigger_high=2,
|
break_time_high=2,
|
||||||
exp_time_high=3,
|
exp_time=1,
|
||||||
|
n_of_trigger=1,
|
||||||
cycle_high=4,
|
cycle_high=4,
|
||||||
device_manager=device_manager,
|
|
||||||
)
|
)
|
||||||
request.metadata["RID"] = "my_test_request_id"
|
request.device_manager.add_device("nidaq")
|
||||||
with (
|
reference_commands = get_instructions(request, ScanStubStatusMock)
|
||||||
mock.patch.object(request.stubs, "get_req_status", side_effect=[False, True]),
|
# TODO #64 based on creating this ScanStatusMessage, we should test the logic of stage/kickoff/complete/unstage in Pilatus and mo1Bragg
|
||||||
mock.patch.object(request.stubs, "_get_from_rpc", return_value=True),
|
|
||||||
):
|
|
||||||
reference_commands = list(request.run())
|
|
||||||
|
|
||||||
for cmd in reference_commands:
|
assert reference_commands == [
|
||||||
if not cmd:
|
None,
|
||||||
continue
|
None,
|
||||||
if "RID" in cmd.metadata:
|
DeviceInstructionMessage(
|
||||||
cmd.metadata["RID"] = "my_test_request_id"
|
metadata={"readout_priority": "monitored", "RID": "my_test_request_id"},
|
||||||
if "rpc_id" in cmd.parameter:
|
device=None,
|
||||||
cmd.parameter["rpc_id"] = "my_test_rpc_id"
|
action="scan_report_instruction",
|
||||||
|
parameter={"device_progress": ["mo1_bragg"]},
|
||||||
|
),
|
||||||
|
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": None,
|
||||||
|
"positions": [0.0, 5.0],
|
||||||
|
"scan_name": "xas_simple_scan_with_xrd",
|
||||||
|
"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="mo1_bragg",
|
||||||
|
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={},
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
||||||
assert reference_commands == [
|
|
||||||
None,
|
def test_xas_advanced_scan(scan_assembler, ScanStubStatusMock):
|
||||||
None,
|
|
||||||
DeviceInstructionMessage(
|
request = scan_assembler(
|
||||||
metadata={"readout_priority": "monitored", "DIID": 0, "RID": "my_test_request_id"},
|
XASAdvancedScan,
|
||||||
device=None,
|
start=8000,
|
||||||
action="scan_report_instruction",
|
stop=9000,
|
||||||
parameter={"device_progress": ["mo1_bragg"]},
|
scan_time=1,
|
||||||
),
|
scan_duration=10,
|
||||||
DeviceInstructionMessage(
|
p_kink=50,
|
||||||
metadata={"readout_priority": "monitored", "DIID": 1, "RID": "my_test_request_id"},
|
e_kink=8500,
|
||||||
device=None,
|
)
|
||||||
action="open_scan",
|
request.device_manager.add_device("nidaq")
|
||||||
parameter={
|
reference_commands = get_instructions(request, ScanStubStatusMock)
|
||||||
"scan_motors": [],
|
|
||||||
"readout_priority": {
|
assert reference_commands == [
|
||||||
"monitored": [],
|
None,
|
||||||
"baseline": [],
|
None,
|
||||||
"on_request": [],
|
DeviceInstructionMessage(
|
||||||
"async": [],
|
metadata={"readout_priority": "monitored", "RID": "my_test_request_id"},
|
||||||
},
|
device=None,
|
||||||
"num_points": None,
|
action="scan_report_instruction",
|
||||||
"positions": [0.0, 5.0],
|
parameter={"device_progress": ["mo1_bragg"]},
|
||||||
"scan_name": "xas_simple_scan_with_xrd",
|
),
|
||||||
"scan_type": "fly",
|
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": None,
|
||||||
DeviceInstructionMessage(
|
"positions": [8000.0, 9000.0],
|
||||||
metadata={"readout_priority": "monitored", "DIID": 2, "RID": "my_test_request_id"},
|
"scan_name": "xas_advanced_scan",
|
||||||
device=None,
|
"scan_type": "fly",
|
||||||
action="stage",
|
},
|
||||||
parameter={},
|
),
|
||||||
),
|
DeviceInstructionMessage(metadata={}, device="nidaq", action="stage", parameter={}),
|
||||||
DeviceInstructionMessage(
|
DeviceInstructionMessage(
|
||||||
metadata={"readout_priority": "baseline", "DIID": 3, "RID": "my_test_request_id"},
|
metadata={},
|
||||||
device=None,
|
device=["bpm4i", "eiger", "mo1_bragg", "samx"],
|
||||||
action="baseline_reading",
|
action="stage",
|
||||||
parameter={},
|
parameter={},
|
||||||
),
|
),
|
||||||
DeviceInstructionMessage(
|
DeviceInstructionMessage(
|
||||||
metadata={
|
metadata={"readout_priority": "baseline", "RID": "my_test_request_id"},
|
||||||
"readout_priority": "monitored",
|
device=["samx"],
|
||||||
"DIID": 4,
|
action="read",
|
||||||
"RID": "my_test_request_id",
|
parameter={},
|
||||||
"response": True,
|
),
|
||||||
|
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="mo1_bragg",
|
||||||
|
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={},
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def test_xas_advanced_scan_with_xrd(scan_assembler, ScanStubStatusMock):
|
||||||
|
|
||||||
|
request = scan_assembler(
|
||||||
|
XASAdvancedScanWithXRD,
|
||||||
|
start=8000,
|
||||||
|
stop=9000,
|
||||||
|
scan_time=1,
|
||||||
|
scan_duration=10,
|
||||||
|
p_kink=50,
|
||||||
|
e_kink=8500,
|
||||||
|
break_enable_low=True,
|
||||||
|
break_time_low=1,
|
||||||
|
cycle_low=1,
|
||||||
|
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 == [
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
DeviceInstructionMessage(
|
||||||
|
metadata={"readout_priority": "monitored", "RID": "my_test_request_id"},
|
||||||
|
device=None,
|
||||||
|
action="scan_report_instruction",
|
||||||
|
parameter={"device_progress": ["mo1_bragg"]},
|
||||||
|
),
|
||||||
|
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"],
|
||||||
},
|
},
|
||||||
device="mo1_bragg",
|
"num_points": None,
|
||||||
action="rpc",
|
"positions": [8000.0, 9000.0],
|
||||||
parameter={
|
"scan_name": "xas_advanced_scan_with_xrd",
|
||||||
"device": "mo1_bragg",
|
"scan_type": "fly",
|
||||||
"func": "move_type.set",
|
},
|
||||||
"rpc_id": "my_test_rpc_id",
|
),
|
||||||
"args": ("energy",),
|
DeviceInstructionMessage(metadata={}, device="nidaq", action="stage", parameter={}),
|
||||||
"kwargs": {},
|
DeviceInstructionMessage(
|
||||||
},
|
metadata={},
|
||||||
),
|
device=["bpm4i", "eiger", "mo1_bragg", "samx"],
|
||||||
DeviceInstructionMessage(
|
action="stage",
|
||||||
metadata={"readout_priority": "monitored", "DIID": 5, "RID": "my_test_request_id"},
|
parameter={},
|
||||||
device="mo1_bragg",
|
),
|
||||||
action="kickoff",
|
DeviceInstructionMessage(
|
||||||
parameter={"configure": {}, "wait_group": "kickoff"},
|
metadata={"readout_priority": "baseline", "RID": "my_test_request_id"},
|
||||||
),
|
device=["samx"],
|
||||||
DeviceInstructionMessage(
|
action="read",
|
||||||
metadata={"readout_priority": "monitored", "DIID": 6, "RID": "my_test_request_id"},
|
parameter={},
|
||||||
device="mo1_bragg",
|
),
|
||||||
action="complete",
|
DeviceInstructionMessage(
|
||||||
parameter={},
|
metadata={"readout_priority": "monitored", "RID": "my_test_request_id"},
|
||||||
),
|
device=["bpm4i", "eiger", "mo1_bragg", "nidaq", "samx"],
|
||||||
DeviceInstructionMessage(
|
action="pre_scan",
|
||||||
metadata={"readout_priority": "monitored", "DIID": 7, "RID": "my_test_request_id"},
|
parameter={},
|
||||||
device=None,
|
),
|
||||||
action="read",
|
DeviceInstructionMessage(
|
||||||
parameter={"group": "primary", "wait_group": "readout_primary"},
|
metadata={"readout_priority": "monitored", "RID": "my_test_request_id"},
|
||||||
),
|
device="mo1_bragg",
|
||||||
DeviceInstructionMessage(
|
action="kickoff",
|
||||||
metadata={"readout_priority": "monitored", "DIID": 8, "RID": "my_test_request_id"},
|
parameter={"configure": {}},
|
||||||
device=None,
|
),
|
||||||
action="wait",
|
"fake_complete",
|
||||||
parameter={"type": "read", "group": "primary", "wait_group": "readout_primary"},
|
DeviceInstructionMessage(
|
||||||
),
|
metadata={"readout_priority": "monitored", "RID": "my_test_request_id", "point_id": 0},
|
||||||
DeviceInstructionMessage(
|
device=["bpm4i", "eiger", "mo1_bragg"],
|
||||||
metadata={"readout_priority": "monitored", "DIID": 9, "RID": "my_test_request_id"},
|
action="read",
|
||||||
device=None,
|
parameter={"group": "monitored"},
|
||||||
action="read",
|
),
|
||||||
parameter={"group": "primary", "wait_group": "readout_primary"},
|
DeviceInstructionMessage(
|
||||||
),
|
metadata={"readout_priority": "monitored", "RID": "my_test_request_id", "point_id": 1},
|
||||||
DeviceInstructionMessage(
|
device=["bpm4i", "eiger", "mo1_bragg"],
|
||||||
metadata={"readout_priority": "monitored", "DIID": 10, "RID": "my_test_request_id"},
|
action="read",
|
||||||
device=None,
|
parameter={"group": "monitored"},
|
||||||
action="wait",
|
),
|
||||||
parameter={"type": "read", "group": "primary", "wait_group": "readout_primary"},
|
"fake_complete",
|
||||||
),
|
DeviceInstructionMessage(
|
||||||
DeviceInstructionMessage(
|
metadata={},
|
||||||
metadata={"readout_priority": "monitored", "DIID": 11, "RID": "my_test_request_id"},
|
device=["bpm4i", "eiger", "mo1_bragg", "nidaq", "samx"],
|
||||||
device=None,
|
action="unstage",
|
||||||
action="wait",
|
parameter={},
|
||||||
parameter={"type": "read", "group": "primary", "wait_group": "readout_primary"},
|
),
|
||||||
),
|
DeviceInstructionMessage(
|
||||||
DeviceInstructionMessage(
|
metadata={"readout_priority": "monitored", "RID": "my_test_request_id"},
|
||||||
metadata={"readout_priority": "monitored", "DIID": 12, "RID": "my_test_request_id"},
|
device=None,
|
||||||
device=None,
|
action="close_scan",
|
||||||
action="complete",
|
parameter={},
|
||||||
parameter={},
|
),
|
||||||
),
|
]
|
||||||
DeviceInstructionMessage(
|
|
||||||
metadata={"readout_priority": "monitored", "DIID": 13, "RID": "my_test_request_id"},
|
|
||||||
device=None,
|
|
||||||
action="unstage",
|
|
||||||
parameter={},
|
|
||||||
),
|
|
||||||
DeviceInstructionMessage(
|
|
||||||
metadata={"readout_priority": "monitored", "DIID": 14, "RID": "my_test_request_id"},
|
|
||||||
device=None,
|
|
||||||
action="close_scan",
|
|
||||||
parameter={},
|
|
||||||
),
|
|
||||||
]
|
|
||||||
|
|||||||
127
tests/tests_scans/test_nidaq_continous_scan.py
Normal file
127
tests/tests_scans/test_nidaq_continous_scan.py
Normal 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={},
|
||||||
|
),
|
||||||
|
]
|
||||||
Reference in New Issue
Block a user