Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| ec1baac099 |
@@ -1,9 +0,0 @@
|
|||||||
# Do not edit this file!
|
|
||||||
# It is needed to track the repo template version, and editing may break things.
|
|
||||||
# This file will be overwritten by copier on template updates.
|
|
||||||
|
|
||||||
_commit: v1.2.8
|
|
||||||
_src_path: https://github.com/bec-project/plugin_copier_template.git
|
|
||||||
make_commit: false
|
|
||||||
project_name: debye_bec
|
|
||||||
widget_plugins_input: []
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
|
|
||||||
semantic-release changelog -D version_variable=$SCRIPT_DIR/../../semantic_release/__init__.py:__version__
|
|
||||||
semantic-release version -D version_variable=$SCRIPT_DIR/../../semantic_release/__init__.py:__version__
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
black --line-length=100 $(git diff --cached --name-only --diff-filter=ACM -- '*.py')
|
|
||||||
isort --line-length=100 --profile=black --multi-line=3 --trailing-comma $(git diff --cached --name-only --diff-filter=ACM -- '*.py')
|
|
||||||
git add $(git diff --cached --name-only --diff-filter=ACM -- '*.py')
|
|
||||||
@@ -1,102 +0,0 @@
|
|||||||
name: CI for debye_bec
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
pull_request:
|
|
||||||
workflow_dispatch:
|
|
||||||
inputs:
|
|
||||||
BEC_WIDGETS_BRANCH:
|
|
||||||
description: "Branch of BEC Widgets to install"
|
|
||||||
required: false
|
|
||||||
type: string
|
|
||||||
default: "main"
|
|
||||||
BEC_CORE_BRANCH:
|
|
||||||
description: "Branch of BEC Core to install"
|
|
||||||
required: false
|
|
||||||
type: string
|
|
||||||
default: "main"
|
|
||||||
OPHYD_DEVICES_BRANCH:
|
|
||||||
description: "Branch of Ophyd Devices to install"
|
|
||||||
required: false
|
|
||||||
type: string
|
|
||||||
default: "main"
|
|
||||||
BEC_PLUGIN_REPO_BRANCH:
|
|
||||||
description: "Branch of the BEC Plugin Repository to install"
|
|
||||||
required: false
|
|
||||||
type: string
|
|
||||||
default: "main"
|
|
||||||
PYTHON_VERSION:
|
|
||||||
description: "Python version to use"
|
|
||||||
required: false
|
|
||||||
type: string
|
|
||||||
default: "3.12"
|
|
||||||
|
|
||||||
permissions:
|
|
||||||
pull-requests: write
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
test:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
env:
|
|
||||||
QTWEBENGINE_DISABLE_SANDBOX: 1
|
|
||||||
QT_QPA_PLATFORM: "offscreen"
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: Setup Python
|
|
||||||
uses: actions/setup-python@v5
|
|
||||||
with:
|
|
||||||
python-version: "${{ inputs.PYTHON_VERSION || '3.12' }}"
|
|
||||||
|
|
||||||
- name: Checkout BEC Plugin Repository
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
with:
|
|
||||||
repository: bec/debye_bec
|
|
||||||
ref: "${{ inputs.BEC_PLUGIN_REPO_BRANCH || github.head_ref || github.sha }}"
|
|
||||||
path: ./debye_bec
|
|
||||||
|
|
||||||
- name: Lint for merge conflicts from template updates
|
|
||||||
shell: bash
|
|
||||||
# Find all Copier conflicts except this line
|
|
||||||
run: '! grep -r "<<<<<<< before updating" | grep -v "grep -r \"<<<<<<< before updating"'
|
|
||||||
|
|
||||||
- name: Checkout BEC Core
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
with:
|
|
||||||
repository: bec/bec
|
|
||||||
ref: "${{ inputs.BEC_CORE_BRANCH || 'main' }}"
|
|
||||||
path: ./bec
|
|
||||||
|
|
||||||
- name: Checkout Ophyd Devices
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
with:
|
|
||||||
repository: bec/ophyd_devices
|
|
||||||
ref: "${{ inputs.OPHYD_DEVICES_BRANCH || 'main' }}"
|
|
||||||
path: ./ophyd_devices
|
|
||||||
|
|
||||||
- name: Checkout BEC Widgets
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
with:
|
|
||||||
repository: bec/bec_widgets
|
|
||||||
ref: "${{ inputs.BEC_WIDGETS_BRANCH || 'main' }}"
|
|
||||||
path: ./bec_widgets
|
|
||||||
|
|
||||||
- name: Install dependencies
|
|
||||||
shell: bash
|
|
||||||
run: |
|
|
||||||
sudo apt-get update
|
|
||||||
sudo apt-get install -y libgl1 libegl1 x11-utils libxkbcommon-x11-0 libdbus-1-3 xvfb
|
|
||||||
sudo apt-get -y install libnss3 libxdamage1 libasound2t64 libatomic1 libxcursor1
|
|
||||||
|
|
||||||
- name: Install Python dependencies
|
|
||||||
shell: bash
|
|
||||||
run: |
|
|
||||||
pip install uv
|
|
||||||
uv pip install --system -e ./ophyd_devices
|
|
||||||
uv pip install --system -e ./bec/bec_lib[dev]
|
|
||||||
uv pip install --system -e ./bec/bec_ipython_client
|
|
||||||
uv pip install --system -e ./bec/bec_server[dev]
|
|
||||||
uv pip install --system -e ./bec_widgets[dev,pyside6]
|
|
||||||
uv pip install --system -e ./debye_bec
|
|
||||||
|
|
||||||
- name: Run Pytest with Coverage
|
|
||||||
id: coverage
|
|
||||||
run: pytest --random-order --cov=./debye_bec --cov-config=./debye_bec/pyproject.toml --cov-branch --cov-report=xml --no-cov-on-fail ./debye_bec/tests/ || test $? -eq 5
|
|
||||||
@@ -1,62 +0,0 @@
|
|||||||
name: Create template upgrade PR for debye_bec
|
|
||||||
on:
|
|
||||||
workflow_dispatch:
|
|
||||||
|
|
||||||
permissions:
|
|
||||||
pull-requests: write
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
create_update_branch_and_pr:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
permissions:
|
|
||||||
contents: write
|
|
||||||
pull-requests: write
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: Setup Python
|
|
||||||
uses: actions/setup-python@v5
|
|
||||||
with:
|
|
||||||
python-version: '3.12'
|
|
||||||
|
|
||||||
- name: Install tools
|
|
||||||
run: |
|
|
||||||
pip install copier PySide6
|
|
||||||
|
|
||||||
- name: Checkout
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
|
|
||||||
- name: Perform update
|
|
||||||
run: |
|
|
||||||
git config --global user.email "bec_ci_staging@psi.ch"
|
|
||||||
git config --global user.name "BEC automated CI"
|
|
||||||
|
|
||||||
branch="chore/update-template-$(python -m uuid)"
|
|
||||||
echo "switching to branch $branch"
|
|
||||||
git checkout -b $branch
|
|
||||||
|
|
||||||
echo "Running copier update..."
|
|
||||||
output="$(copier update --trust --defaults --conflict inline 2>&1)"
|
|
||||||
echo "$output"
|
|
||||||
msg="$(printf '%s\n' "$output" | head -n 1)"
|
|
||||||
|
|
||||||
if ! grep -q "make_commit: true" .copier-answers.yml ; then
|
|
||||||
echo "Autocommit not made, committing..."
|
|
||||||
git add -A
|
|
||||||
git commit -a -m "$msg"
|
|
||||||
fi
|
|
||||||
|
|
||||||
if diff-index --quiet HEAD ; then
|
|
||||||
echo "No changes detected"
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
git push -u origin $branch
|
|
||||||
curl -X POST "https://gitea.psi.ch/api/v1/repos/${{ gitea.repository }}/pulls" \
|
|
||||||
-H "Authorization: token ${{ secrets.CI_REPO_WRITE }}" \
|
|
||||||
-H "Content-Type: application/json" \
|
|
||||||
-d "{
|
|
||||||
\"title\": \"Template: $(echo $msg)\",
|
|
||||||
\"body\": \"This PR was created by Gitea Actions\",
|
|
||||||
\"head\": \"$(echo $branch)\",
|
|
||||||
\"base\": \"main\"
|
|
||||||
}"
|
|
||||||
@@ -8,9 +8,6 @@
|
|||||||
**/.pytest_cache
|
**/.pytest_cache
|
||||||
**/*.egg*
|
**/*.egg*
|
||||||
|
|
||||||
# recovery_config files
|
|
||||||
recovery_config_*
|
|
||||||
|
|
||||||
# file writer data
|
# file writer data
|
||||||
**.h5
|
**.h5
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,40 @@
|
|||||||
|
# This file is a template, and might need editing before it works on your project.
|
||||||
|
# Official language image. Look for the different tagged releases at:
|
||||||
|
# https://hub.docker.com/r/library/python/tags/
|
||||||
|
image: $CI_DEPENDENCY_PROXY_GROUP_IMAGE_PREFIX/python:3.10
|
||||||
|
|
||||||
|
#commands to run in the Docker container before starting each job.
|
||||||
|
before_script:
|
||||||
|
- pip install -e .[dev]
|
||||||
|
|
||||||
|
# different stages in the pipeline
|
||||||
|
stages:
|
||||||
|
- Formatter
|
||||||
|
- Test
|
||||||
|
- AdditionalTests
|
||||||
|
- Deploy
|
||||||
|
|
||||||
|
formatter:
|
||||||
|
stage: Formatter
|
||||||
|
script:
|
||||||
|
- pip install black
|
||||||
|
- black --check --diff --color --line-length=100 ./
|
||||||
|
|
||||||
|
pytest:
|
||||||
|
stage: Test
|
||||||
|
script:
|
||||||
|
- pytest -v --random-order ./tests
|
||||||
|
|
||||||
|
tests-3.11:
|
||||||
|
stage: AdditionalTests
|
||||||
|
image: $CI_DEPENDENCY_PROXY_GROUP_IMAGE_PREFIX/python:3.11
|
||||||
|
needs: ["pytest"]
|
||||||
|
script:
|
||||||
|
- pytest -v --random-order ./tests
|
||||||
|
allow_failure: true
|
||||||
|
|
||||||
|
tests-3.12:
|
||||||
|
extends: "tests-3.11"
|
||||||
|
stage: AdditionalTests
|
||||||
|
image: $CI_DEPENDENCY_PROXY_GROUP_IMAGE_PREFIX/python:3.12
|
||||||
|
allow_failure: true
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
|
|
||||||
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.
|
|
||||||
@@ -1,65 +1,3 @@
|
|||||||
# 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"
|
|
||||||
```
|
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
from .bec_client import *
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
from .plugins import *
|
||||||
@@ -0,0 +1,245 @@
|
|||||||
|
from bec_lib.devicemanager import Device
|
||||||
|
from bec_lib.scan_report import ScanReport
|
||||||
|
|
||||||
|
# pylint:disable=undefined-variable
|
||||||
|
# pylint: disable=too-many-arguments
|
||||||
|
|
||||||
|
|
||||||
|
def dscan(
|
||||||
|
motor1: Device, m1_from: float, m1_to: float, steps: int, exp_time: float, **kwargs
|
||||||
|
) -> ScanReport:
|
||||||
|
"""Relative line scan with one device.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
motor1 (Device): Device that should be scanned.
|
||||||
|
m1_from (float): Start position relative to the current position.
|
||||||
|
m1_to (float): End position relative to the current position.
|
||||||
|
steps (int): Number of steps.
|
||||||
|
exp_time (float): Exposure time.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
ScanReport: Status object.
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
>>> dscan(dev.motor1, -5, 5, 10, 0.1)
|
||||||
|
"""
|
||||||
|
return scans.line_scan(
|
||||||
|
motor1, m1_from, m1_to, steps=steps, exp_time=exp_time, relative=True, **kwargs
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def d2scan(
|
||||||
|
motor1: Device,
|
||||||
|
m1_from: float,
|
||||||
|
m1_to: float,
|
||||||
|
motor2: Device,
|
||||||
|
m2_from: float,
|
||||||
|
m2_to: float,
|
||||||
|
steps: int,
|
||||||
|
exp_time: float,
|
||||||
|
**kwargs
|
||||||
|
) -> ScanReport:
|
||||||
|
"""Relative line scan with two devices.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
motor1 (Device): First device that should be scanned.
|
||||||
|
m1_from (float): Start position of the first device relative to its current position.
|
||||||
|
m1_to (float): End position of the first device relative to its current position.
|
||||||
|
motor2 (Device): Second device that should be scanned.
|
||||||
|
m2_from (float): Start position of the second device relative to its current position.
|
||||||
|
m2_to (float): End position of the second device relative to its current position.
|
||||||
|
steps (int): Number of steps.
|
||||||
|
exp_time (float): Exposure time
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
ScanReport: Status object.
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
>>> d2scan(dev.motor1, -5, 5, dev.motor2, -8, 8, 10, 0.1)
|
||||||
|
"""
|
||||||
|
return scans.line_scan(
|
||||||
|
motor1,
|
||||||
|
m1_from,
|
||||||
|
m1_to,
|
||||||
|
motor2,
|
||||||
|
m2_from,
|
||||||
|
m2_to,
|
||||||
|
steps=steps,
|
||||||
|
exp_time=exp_time,
|
||||||
|
relative=True,
|
||||||
|
**kwargs
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def ascan(motor1, m1_from, m1_to, steps, exp_time, **kwargs):
|
||||||
|
"""Absolute line scan with one device.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
motor1 (Device): Device that should be scanned.
|
||||||
|
m1_from (float): Start position.
|
||||||
|
m1_to (float): End position.
|
||||||
|
steps (int): Number of steps.
|
||||||
|
exp_time (float): Exposure time.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
ScanReport: Status object.
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
>>> ascan(dev.motor1, -5, 5, 10, 0.1)
|
||||||
|
"""
|
||||||
|
return scans.line_scan(
|
||||||
|
motor1, m1_from, m1_to, steps=steps, exp_time=exp_time, relative=False, **kwargs
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def a2scan(motor1, m1_from, m1_to, motor2, m2_from, m2_to, steps, exp_time, **kwargs):
|
||||||
|
"""Absolute line scan with two devices.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
motor1 (Device): First device that should be scanned.
|
||||||
|
m1_from (float): Start position of the first device.
|
||||||
|
m1_to (float): End position of the first device.
|
||||||
|
motor2 (Device): Second device that should be scanned.
|
||||||
|
m2_from (float): Start position of the second device.
|
||||||
|
m2_to (float): End position of the second device.
|
||||||
|
steps (int): Number of steps.
|
||||||
|
exp_time (float): Exposure time
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
ScanReport: Status object.
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
>>> a2scan(dev.motor1, -5, 5, dev.motor2, -8, 8, 10, 0.1)
|
||||||
|
"""
|
||||||
|
return scans.line_scan(
|
||||||
|
motor1,
|
||||||
|
m1_from,
|
||||||
|
m1_to,
|
||||||
|
motor2,
|
||||||
|
m2_from,
|
||||||
|
m2_to,
|
||||||
|
steps=steps,
|
||||||
|
exp_time=exp_time,
|
||||||
|
relative=False,
|
||||||
|
**kwargs
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def dmesh(motor1, m1_from, m1_to, m1_steps, motor2, m2_from, m2_to, m2_steps, exp_time, **kwargs):
|
||||||
|
"""Relative mesh scan (grid scan) with two devices.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
motor1 (Device): First device that should be scanned.
|
||||||
|
m1_from (float): Start position of the first device relative to its current position.
|
||||||
|
m1_to (float): End position of the first device relative to its current position.
|
||||||
|
m1_steps (int): Number of steps for motor1.
|
||||||
|
motor2 (Device): Second device that should be scanned.
|
||||||
|
m2_from (float): Start position of the second device relative to its current position.
|
||||||
|
m2_to (float): End position of the second device relative to its current position.
|
||||||
|
m2_steps (int): Number of steps for motor2.
|
||||||
|
exp_time (float): Exposure time
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
ScanReport: Status object.
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
>>> dmesh(dev.motor1, -5, 5, 10, dev.motor2, -8, 8, 10, 0.1)
|
||||||
|
"""
|
||||||
|
return scans.grid_scan(
|
||||||
|
motor1,
|
||||||
|
m1_from,
|
||||||
|
m1_to,
|
||||||
|
m1_steps,
|
||||||
|
motor2,
|
||||||
|
m2_from,
|
||||||
|
m2_to,
|
||||||
|
m2_steps,
|
||||||
|
exp_time=exp_time,
|
||||||
|
relative=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def amesh(motor1, m1_from, m1_to, m1_steps, motor2, m2_from, m2_to, m2_steps, exp_time, **kwargs):
|
||||||
|
"""Absolute mesh scan (grid scan) with two devices.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
motor1 (Device): First device that should be scanned.
|
||||||
|
m1_from (float): Start position of the first device.
|
||||||
|
m1_to (float): End position of the first device.
|
||||||
|
m1_steps (int): Number of steps for motor1.
|
||||||
|
motor2 (Device): Second device that should be scanned.
|
||||||
|
m2_from (float): Start position of the second device.
|
||||||
|
m2_to (float): End position of the second device.
|
||||||
|
m2_steps (int): Number of steps for motor2.
|
||||||
|
exp_time (float): Exposure time
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
ScanReport: Status object.
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
>>> amesh(dev.motor1, -5, 5, 10, dev.motor2, -8, 8, 10, 0.1)
|
||||||
|
"""
|
||||||
|
return scans.grid_scan(
|
||||||
|
motor1,
|
||||||
|
m1_from,
|
||||||
|
m1_to,
|
||||||
|
m1_steps,
|
||||||
|
motor2,
|
||||||
|
m2_from,
|
||||||
|
m2_to,
|
||||||
|
m2_steps,
|
||||||
|
exp_time=exp_time,
|
||||||
|
relative=False,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def umv(*args) -> ScanReport:
|
||||||
|
"""Updated absolute move (i.e. blocking) for one or more devices.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
ScanReport: Status object.
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
>>> umv(dev.samx, 1)
|
||||||
|
>>> umv(dev.samx, 1, dev.samy, 2)
|
||||||
|
"""
|
||||||
|
return scans.umv(*args, relative=False)
|
||||||
|
|
||||||
|
|
||||||
|
def umvr(*args) -> ScanReport:
|
||||||
|
"""Updated relative move (i.e. blocking) for one or more devices.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
ScanReport: Status object.
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
>>> umvr(dev.samx, 1)
|
||||||
|
>>> umvr(dev.samx, 1, dev.samy, 2)
|
||||||
|
"""
|
||||||
|
return scans.umv(*args, relative=True)
|
||||||
|
|
||||||
|
|
||||||
|
def mv(*args) -> ScanReport:
|
||||||
|
"""Absolute move for one or more devices.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
ScanReport: Status object.
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
>>> mv(dev.samx, 1)
|
||||||
|
>>> mv(dev.samx, 1, dev.samy, 2)
|
||||||
|
"""
|
||||||
|
return scans.mv(*args, relative=False)
|
||||||
|
|
||||||
|
|
||||||
|
def mvr(*args) -> ScanReport:
|
||||||
|
"""Relative move for one or more devices.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
ScanReport: Status object.
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
>>> mvr(dev.samx, 1)
|
||||||
|
>>> mvr(dev.samx, 1, dev.samy, 2)
|
||||||
|
"""
|
||||||
|
return scans.mv(*args, relative=True)
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
"""
|
||||||
|
Post startup script for the BEC client. This script is executed after the
|
||||||
|
IPython shell is started. It is used to load the beamline specific
|
||||||
|
information and to setup the prompts.
|
||||||
|
|
||||||
|
The script is executed in the global namespace of the IPython shell. This
|
||||||
|
means that all variables defined here are available in the shell.
|
||||||
|
|
||||||
|
If needed, bec command-line arguments can be parsed here. For example, to
|
||||||
|
parse the --session argument, add the following lines to the script:
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
parser = argparse.ArgumentParser()
|
||||||
|
parser.add_argument("--session", help="Session name", type=str, default="my_default_session")
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
if args.session == "my_session":
|
||||||
|
print("Loading my_session session")
|
||||||
|
from bec_plugins.bec_client.plugins.my_session import *
|
||||||
|
else:
|
||||||
|
print("Loading default session")
|
||||||
|
from bec_plugins.bec_client.plugins.default_session import *
|
||||||
|
"""
|
||||||
|
|
||||||
|
# pylint: disable=invalid-name, unused-import, import-error, undefined-variable, unused-variable, unused-argument, no-name-in-module
|
||||||
|
import argparse
|
||||||
|
|
||||||
|
from bec_lib import bec_logger
|
||||||
|
|
||||||
|
logger = bec_logger.logger
|
||||||
|
|
||||||
|
logger.info("Using the Debye startup script.")
|
||||||
|
|
||||||
|
parser = argparse.ArgumentParser()
|
||||||
|
parser.add_argument("--session", help="Session name", type=str, default="Debye")
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
# SETUP BEAMLINE INFO
|
||||||
|
from bec_client.plugins.SLS.sls_info import OperatorInfo, SLSInfo
|
||||||
|
|
||||||
|
bec._beamline_mixin._bl_info_register(SLSInfo)
|
||||||
|
bec._beamline_mixin._bl_info_register(OperatorInfo)
|
||||||
|
|
||||||
|
# SETUP PROMPTS
|
||||||
|
bec._ip.prompts.username = "Debye"
|
||||||
|
bec._ip.prompts.status = 1
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
"""
|
||||||
|
Pre-startup script for BEC client. This script is executed before the BEC client
|
||||||
|
is started. It can be used to set up the BEC client configuration. The script is
|
||||||
|
executed in the global namespace of the BEC client. This means that all
|
||||||
|
variables defined here are available in the BEC client.
|
||||||
|
|
||||||
|
To set up the BEC client configuration, use the ServiceConfig class. For example,
|
||||||
|
to set the configuration file path, add the following lines to the script:
|
||||||
|
|
||||||
|
import pathlib
|
||||||
|
from bec_lib import ServiceConfig
|
||||||
|
|
||||||
|
current_path = pathlib.Path(__file__).parent.resolve()
|
||||||
|
CONFIG_PATH = f"{current_path}/<path_to_my_config_file.yaml>"
|
||||||
|
|
||||||
|
config = ServiceConfig(CONFIG_PATH)
|
||||||
|
|
||||||
|
If this startup script defined a ServiceConfig object, the BEC client will use
|
||||||
|
it to configure itself. Otherwise, the BEC client will use the default config.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# example:
|
||||||
|
# current_path = pathlib.Path(__file__).parent.resolve()
|
||||||
|
# CONFIG_PATH = f"{current_path}/../../../bec_config.yaml"
|
||||||
|
# config = ServiceConfig(CONFIG_PATH)
|
||||||
+6
-8
@@ -7,12 +7,10 @@ import traceback
|
|||||||
|
|
||||||
import h5py
|
import h5py
|
||||||
import numpy as np
|
import numpy as np
|
||||||
from bec_lib import bec_logger, messages
|
from bec_lib import MessageEndpoints, RedisConnector, ServiceConfig, bec_logger, messages
|
||||||
from bec_lib.bec_service import BECService
|
from bec_lib.bec_service import BECService
|
||||||
from bec_lib.endpoints import MessageEndpoints
|
from bec_lib.file_utils import FileWriterMixin
|
||||||
from bec_lib.file_utils import FileWriter
|
from bec_lib.redis_connector import MessageObject
|
||||||
from bec_lib.redis_connector import MessageObject, RedisConnector
|
|
||||||
from bec_lib.service_config import ServiceConfig
|
|
||||||
|
|
||||||
logger = bec_logger.logger
|
logger = bec_logger.logger
|
||||||
|
|
||||||
@@ -28,8 +26,8 @@ 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.config.get("file_writer")
|
config = self._service_config.service_config.get("file_writer")
|
||||||
self.writer_mixin = FileWriter(service_config=config)
|
self.writer_mixin = FileWriterMixin(config)
|
||||||
self._scan_status_consumer = None
|
self._scan_status_consumer = None
|
||||||
self._ni_data_consumer = None
|
self._ni_data_consumer = None
|
||||||
self._ni_data_event = None
|
self._ni_data_event = None
|
||||||
@@ -161,7 +159,7 @@ class NIDAQWriterService(BECService):
|
|||||||
signals = {}
|
signals = {}
|
||||||
|
|
||||||
for key in msgs[0].content["signals"]:
|
for key in msgs[0].content["signals"]:
|
||||||
signals[key] = np.concatenate([msg.content["signals"][key]["value"] for msg in msgs])
|
signals[key] = np.concatenate([msg.content["signals"][key] for msg in msgs])
|
||||||
|
|
||||||
# write data to queue
|
# write data to queue
|
||||||
self.queue.put(signals)
|
self.queue.put(signals)
|
||||||
+13
-5
@@ -1,6 +1,4 @@
|
|||||||
from bec_lib import messages
|
from bec_lib import MessageEndpoints, RedisConnector, messages
|
||||||
from bec_lib.endpoints import MessageEndpoints
|
|
||||||
from bec_lib.redis_connector import RedisConnector
|
|
||||||
|
|
||||||
|
|
||||||
def send_scan_status(scan_number, status):
|
def send_scan_status(scan_number, status):
|
||||||
@@ -26,9 +24,19 @@ if __name__ == "__main__":
|
|||||||
parser = argparse.ArgumentParser(description="Scan status helper")
|
parser = argparse.ArgumentParser(description="Scan status helper")
|
||||||
command = parser.add_subparsers(dest="command")
|
command = parser.add_subparsers(dest="command")
|
||||||
start = command.add_parser("start", help="Start a new scan")
|
start = command.add_parser("start", help="Start a new scan")
|
||||||
start.add_argument("--scan_number", type=int, required=True, help="Scan number")
|
start.add_argument(
|
||||||
|
"--scan_number",
|
||||||
|
type=int,
|
||||||
|
required=True,
|
||||||
|
help="Scan number",
|
||||||
|
)
|
||||||
stop = command.add_parser("stop", help="Stop the scan")
|
stop = command.add_parser("stop", help="Stop the scan")
|
||||||
stop.add_argument("--scan_number", type=int, required=True, help="Scan number")
|
stop.add_argument(
|
||||||
|
"--scan_number",
|
||||||
|
type=int,
|
||||||
|
required=True,
|
||||||
|
help="Scan number",
|
||||||
|
)
|
||||||
|
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
send_scan_status(args.scan_number, args.command)
|
send_scan_status(args.scan_number, args.command)
|
||||||
+8
-3
@@ -2,8 +2,7 @@ import threading
|
|||||||
import time
|
import time
|
||||||
|
|
||||||
import numpy as np
|
import numpy as np
|
||||||
from bec_lib import messages
|
from bec_lib import MessageEndpoints, RedisConnector, ServiceConfig, bec_logger, messages
|
||||||
from bec_lib.redis_connector import RedisConnector
|
|
||||||
|
|
||||||
|
|
||||||
class NIDAQSim(threading.Thread):
|
class NIDAQSim(threading.Thread):
|
||||||
@@ -14,7 +13,10 @@ class NIDAQSim(threading.Thread):
|
|||||||
index = 0
|
index = 0
|
||||||
producer = RedisConnector(["localhost:6379"]).producer()
|
producer = RedisConnector(["localhost:6379"]).producer()
|
||||||
signal = np.asarray(range(index, index + 600000))
|
signal = np.asarray(range(index, index + 600000))
|
||||||
signals = {"signal1": signal, "signal2": signal}
|
signals = {
|
||||||
|
"signal1": signal,
|
||||||
|
"signal2": signal,
|
||||||
|
}
|
||||||
|
|
||||||
msg = messages.DeviceMessage(signals=signals)
|
msg = messages.DeviceMessage(signals=signals)
|
||||||
msg = msg.dumps()
|
msg = msg.dumps()
|
||||||
@@ -38,6 +40,9 @@ class NIDAQSim(threading.Thread):
|
|||||||
|
|
||||||
time.sleep(0.5)
|
time.sleep(0.5)
|
||||||
print(f"Elapsed time: {time.time() - start}")
|
print(f"Elapsed time: {time.time() - start}")
|
||||||
|
print(f"Total time: {time.time() - total_time}")
|
||||||
|
print(f"FPS: {index / (time.time() - total_time)}")
|
||||||
|
print(f"Signal size: {signal.nbytes/1e6*2} MB")
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
@@ -1,14 +1,15 @@
|
|||||||
import argparse
|
import argparse
|
||||||
import threading
|
import threading
|
||||||
|
|
||||||
from bec_lib import bec_logger
|
from bec_lib import RedisConnector, ServiceConfig, bec_logger
|
||||||
from bec_lib.redis_connector import RedisConnector
|
from NIDAQ_writer import NIDAQWriterService
|
||||||
from bec_lib.service_config import ServiceConfig
|
|
||||||
|
|
||||||
from debye_bec.services.NIDAQ_writer import NIDAQWriterService
|
|
||||||
|
|
||||||
parser = argparse.ArgumentParser(formatter_class=argparse.ArgumentDefaultsHelpFormatter)
|
parser = argparse.ArgumentParser(formatter_class=argparse.ArgumentDefaultsHelpFormatter)
|
||||||
parser.add_argument("--config", default="", help="path to the config file")
|
parser.add_argument(
|
||||||
|
"--config",
|
||||||
|
default="",
|
||||||
|
help="path to the config file",
|
||||||
|
)
|
||||||
clargs = parser.parse_args()
|
clargs = parser.parse_args()
|
||||||
config_path = clargs.config
|
config_path = clargs.config
|
||||||
|
|
||||||
@@ -16,7 +17,10 @@ config = ServiceConfig(config_path)
|
|||||||
bec_logger.level = bec_logger.LOGLEVEL.INFO
|
bec_logger.level = bec_logger.LOGLEVEL.INFO
|
||||||
logger = bec_logger.logger
|
logger = bec_logger.logger
|
||||||
|
|
||||||
bec_server = NIDAQWriterService(config=config, connector_cls=RedisConnector)
|
bec_server = NIDAQWriterService(
|
||||||
|
config=config,
|
||||||
|
connector_cls=RedisConnector,
|
||||||
|
)
|
||||||
try:
|
try:
|
||||||
event = threading.Event()
|
event = threading.Event()
|
||||||
# pylint: disable=E1102
|
# pylint: disable=E1102
|
||||||
@@ -1 +0,0 @@
|
|||||||
# Add anything you don't want to check in to git, e.g. very large files
|
|
||||||
@@ -1,82 +0,0 @@
|
|||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
import builtins
|
|
||||||
from typing import TYPE_CHECKING
|
|
||||||
|
|
||||||
from bec_lib import bec_logger
|
|
||||||
from debye_bec.devices.absorber import STATUS as ABS_STATUS
|
|
||||||
|
|
||||||
logger = bec_logger.logger
|
|
||||||
# import builtins to avoid linter errors
|
|
||||||
dev = builtins.__dict__.get("dev")
|
|
||||||
|
|
||||||
class MoveToLabelError(Exception):
|
|
||||||
"""Exception for the MoveToLabel function"""
|
|
||||||
|
|
||||||
def move_to_label():
|
|
||||||
"""
|
|
||||||
Function to move several motors to a specific position defined in the label dict.
|
|
||||||
"""
|
|
||||||
|
|
||||||
label = get_device_conditions(label="digitalTwin")
|
|
||||||
|
|
||||||
# Get absorber status and close if open
|
|
||||||
logger.info("Check Frontend Absorber Status")
|
|
||||||
abs_was_open = dev.abs.status.get() == ABS_STATUS.OPEN
|
|
||||||
if abs_was_open:
|
|
||||||
logger.info(" Close Frontend Absorber")
|
|
||||||
status = dev.abs.close()
|
|
||||||
status.wait()
|
|
||||||
|
|
||||||
# Move Frontend Slits
|
|
||||||
logger.info("Move Frontend Slits into position")
|
|
||||||
devices = ["sldi_centerx", "sldi_centery", "sldi_gapx", "sldi_gapy"]
|
|
||||||
matches = {key: label[key] for key in devices if key in label}
|
|
||||||
statuses = []
|
|
||||||
for device in matches.values():
|
|
||||||
statuses.append(device['device'].move(device['value']))
|
|
||||||
for status in statuses:
|
|
||||||
status.wait(timeout=30)
|
|
||||||
|
|
||||||
# Move Collimating mirror
|
|
||||||
logger.info("Move Collimating Mirror into position")
|
|
||||||
if "cm_rotx" in label: # pitch
|
|
||||||
logger.info(" Move pitch into position")
|
|
||||||
surveyed_movement(
|
|
||||||
axis=label['cm_rotx'],
|
|
||||||
surveyed_axes= [
|
|
||||||
{'device': dev.cm_rotz, 'abs_tol': 0.1},
|
|
||||||
]
|
|
||||||
)
|
|
||||||
|
|
||||||
# Restore absorber position
|
|
||||||
logger.info("Restore Frontend Absorber Status")
|
|
||||||
if abs_was_open:
|
|
||||||
status = dev.abs.open()
|
|
||||||
status.wait()
|
|
||||||
|
|
||||||
|
|
||||||
def surveyed_movement(axis, surveyed_axes):
|
|
||||||
"""
|
|
||||||
Moves an axis while surverying a set of axes.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
axis (DeviceCondition): Device condition
|
|
||||||
surveyed_axes (list): List of dicts (same format as DeviceCondition)
|
|
||||||
|
|
||||||
Raises:
|
|
||||||
If during movement of axis, one of the surveyed axes moves out of tolerance.
|
|
||||||
"""
|
|
||||||
|
|
||||||
for surv_ax in surveyed_axes:
|
|
||||||
surv_ax['old_value'] = surv_ax['device'].read()
|
|
||||||
status = axis['device'].move(axis['value'])
|
|
||||||
while status.status == 'RUNNING':
|
|
||||||
for surv_ax in surveyed_axes:
|
|
||||||
if abs(surv_ax['device'].read() - surv_ax['old_value']) > surv_ax['abs_tol']:
|
|
||||||
axis['device'].stop()
|
|
||||||
raise MoveToLabelError(
|
|
||||||
f"During movement of {axis['device'].name}, {surv_ax['device'].name} " +
|
|
||||||
f"started to move unexpectedly (old pos: {surv_ax['old_value']}, " +
|
|
||||||
f"current pos: {surv_ax['device'].read()})"
|
|
||||||
)
|
|
||||||
@@ -1,36 +0,0 @@
|
|||||||
"""
|
|
||||||
Post startup script for the BEC client. This script is executed after the
|
|
||||||
IPython shell is started. It is used to load the beamline specific
|
|
||||||
information and to setup the prompts.
|
|
||||||
|
|
||||||
The script is executed in the global namespace of the IPython shell. This
|
|
||||||
means that all variables defined here are available in the shell.
|
|
||||||
|
|
||||||
While command-line arguments have to be set in the pre-startup script, the
|
|
||||||
post-startup script can be used to load beamline specific information and
|
|
||||||
to setup the prompts.
|
|
||||||
|
|
||||||
from bec_lib.logger import bec_logger
|
|
||||||
|
|
||||||
logger = bec_logger.logger
|
|
||||||
|
|
||||||
# pylint: disable=import-error
|
|
||||||
_args = _main_dict["args"]
|
|
||||||
|
|
||||||
_session_name = "cSAXS"
|
|
||||||
if _args.session.lower() == "lamni":
|
|
||||||
from csaxs_bec.bec_ipython_client.plugins.cSAXS import *
|
|
||||||
from csaxs_bec.bec_ipython_client.plugins.LamNI import *
|
|
||||||
|
|
||||||
_session_name = "LamNI"
|
|
||||||
lamni = LamNI(bec)
|
|
||||||
logger.success("LamNI session loaded.")
|
|
||||||
|
|
||||||
elif _args.session.lower() == "csaxs":
|
|
||||||
print("Loading cSAXS session")
|
|
||||||
from csaxs_bec.bec_ipython_client.plugins.cSAXS import *
|
|
||||||
|
|
||||||
logger.success("cSAXS session loaded.")
|
|
||||||
"""
|
|
||||||
|
|
||||||
# pylint: disable=invalid-name, unused-import, import-error, undefined-variable, unused-variable, unused-argument, no-name-in-module
|
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
"""
|
|
||||||
Pre-startup script for BEC client. This script is executed before the BEC client
|
|
||||||
is started. It can be used to add additional command line arguments.
|
|
||||||
"""
|
|
||||||
|
|
||||||
import os
|
|
||||||
|
|
||||||
from bec_lib.service_config import ServiceConfig
|
|
||||||
|
|
||||||
import debye_bec
|
|
||||||
|
|
||||||
|
|
||||||
def extend_command_line_args(parser):
|
|
||||||
"""
|
|
||||||
Extend the command line arguments of the BEC client.
|
|
||||||
"""
|
|
||||||
|
|
||||||
# parser.add_argument("--session", help="Session name", type=str, default="cSAXS")
|
|
||||||
|
|
||||||
return parser
|
|
||||||
|
|
||||||
|
|
||||||
def get_config() -> ServiceConfig:
|
|
||||||
"""
|
|
||||||
Create and return the ServiceConfig for the plugin repository
|
|
||||||
"""
|
|
||||||
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})
|
|
||||||
@@ -1,41 +0,0 @@
|
|||||||
# This file was automatically generated by generate_cli.py
|
|
||||||
# type: ignore
|
|
||||||
|
|
||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
from bec_lib.logger import bec_logger
|
|
||||||
|
|
||||||
from bec_widgets.cli.rpc.rpc_base import RPCBase, rpc_call, rpc_timeout
|
|
||||||
|
|
||||||
logger = bec_logger.logger
|
|
||||||
|
|
||||||
# pylint: skip-file
|
|
||||||
|
|
||||||
|
|
||||||
_Widgets = {
|
|
||||||
"DigitalTwin": "DigitalTwin",
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class DigitalTwin(RPCBase):
|
|
||||||
"""Main widget of Digital Twin"""
|
|
||||||
|
|
||||||
_IMPORT_MODULE = "debye_bec.bec_widgets.widgets.digital_twin.digital_twin"
|
|
||||||
|
|
||||||
@rpc_call
|
|
||||||
def remove(self):
|
|
||||||
"""
|
|
||||||
Cleanup the BECConnector
|
|
||||||
"""
|
|
||||||
|
|
||||||
@rpc_call
|
|
||||||
def attach(self):
|
|
||||||
"""
|
|
||||||
None
|
|
||||||
"""
|
|
||||||
|
|
||||||
@rpc_call
|
|
||||||
def detach(self):
|
|
||||||
"""
|
|
||||||
Detach the widget from its parent dock widget (if widget is in the dock), making it a floating widget.
|
|
||||||
"""
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
# This file was automatically generated by generate_cli.py
|
|
||||||
# type: ignore
|
|
||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
# pylint: skip-file
|
|
||||||
|
|
||||||
designer_plugins = {
|
|
||||||
"DigitalTwin": ("debye_bec.bec_widgets.widgets.digital_twin.digital_twin", "DigitalTwin"),
|
|
||||||
}
|
|
||||||
|
|
||||||
widget_icons = {
|
|
||||||
"DigitalTwin": "lightbulb",
|
|
||||||
}
|
|
||||||
@@ -1,242 +0,0 @@
|
|||||||
import os
|
|
||||||
import numpy as np
|
|
||||||
from bec_lib import bec_logger
|
|
||||||
|
|
||||||
os.environ["USE_XRT"] = "False"
|
|
||||||
import debye_bec.bec_widgets.widgets.x01da_parameters as bl
|
|
||||||
|
|
||||||
logger = bec_logger.logger
|
|
||||||
|
|
||||||
def calc_positions(cfg):
|
|
||||||
|
|
||||||
pos = {}
|
|
||||||
|
|
||||||
## FE slits
|
|
||||||
trxr = -np.arctan(cfg['h_acc'])*bl.feSlits.center1[1]
|
|
||||||
trxw = (np.arctan(cfg['h_acc'])*bl.feSlits.center1[1])/bl.feSlits.center1[1]*bl.feSlits.center2[1]
|
|
||||||
tryb = -np.arctan(cfg['v_acc'])*bl.feSlits.center1[1]
|
|
||||||
tryt = (np.arctan(cfg['v_acc'])*bl.feSlits.center1[1])/bl.feSlits.center1[1]*bl.feSlits.center2[1]
|
|
||||||
|
|
||||||
# trxw_proj = trxw/bl.feSlits.center2[1]*bl.feSlits.center1[1]
|
|
||||||
# tryt_proj = tryt/bl.feSlits.center2[1]*bl.feSlits.center1[1]
|
|
||||||
|
|
||||||
# xcen = (trxr + trxw) / 2
|
|
||||||
# ycen = (tryb + tryt) / 2
|
|
||||||
xgap = trxw - trxr
|
|
||||||
ygap = tryt - tryb
|
|
||||||
|
|
||||||
pos['sldi_gapx'] = {'value': xgap}
|
|
||||||
pos['sldi_gapy'] = {'value': ygap}
|
|
||||||
|
|
||||||
## Collimating Mirror
|
|
||||||
obj_dist = bl.cm.center[1] # object distance
|
|
||||||
beam_vs = 2 * obj_dist * np.tan(cfg['v_acc']) # vertical size of beam after CM
|
|
||||||
|
|
||||||
# TRX
|
|
||||||
try:
|
|
||||||
index = bl.cm.surface.index(cfg['cm_stripe'])
|
|
||||||
except:
|
|
||||||
raise ValueError(f"Requested stripe {cfg['cm_stripe']} not found in parameters!")
|
|
||||||
cm_trx = -(bl.cm.limOptX[0][index] + bl.cm.limOptX[1][index]) / 2
|
|
||||||
pos['cm_trx'] = {'value': cm_trx}
|
|
||||||
|
|
||||||
# TRY
|
|
||||||
height = obj_dist * np.tan(cfg['v_acc'])**2 * 1 / np.tan(cfg['cm_pitch'])
|
|
||||||
pos['cm_try'] = {'value': height}
|
|
||||||
|
|
||||||
# Pitch
|
|
||||||
pos['cm_rotx'] = {'value': -cfg["cm_pitch"]*1e3} # invert and convert to mrad (same as EGU of rotx axis)
|
|
||||||
|
|
||||||
# Bending Radius
|
|
||||||
radius = 2. * obj_dist / np.sin(cfg['cm_pitch']) # Elements of modern X-ray Physics, page 108 ff.
|
|
||||||
pos['cm_bnd_radius'] = {'value': radius * 1e-6} # Convert to km
|
|
||||||
|
|
||||||
## Monochromator
|
|
||||||
# Bragg Angle
|
|
||||||
# if cfg['mo1_mode'] == 'Monochromatic':
|
|
||||||
# # Add 2x CM pitch to the bragg angle
|
|
||||||
# bragg = ((2 * cfg['cm_pitch']) + cfg['mo1_bragg']) / np.pi * 180
|
|
||||||
# elif cfg['mo1_mode'] == 'Pinkbeam':
|
|
||||||
# # Align xtal surfaces parallel to beam
|
|
||||||
# bragg = (2 * cfg['cm_pitch']) / np.pi * 180
|
|
||||||
# else:
|
|
||||||
# raise Exception('Monochromator mode not supported')
|
|
||||||
if cfg['mo1_mode'] == 'Monochromatic':
|
|
||||||
# Add 2x CM pitch to the bragg angle
|
|
||||||
bragg = cfg['mo1_bragg']
|
|
||||||
elif cfg['mo1_mode'] == 'Pinkbeam':
|
|
||||||
# Align xtal surfaces parallel to beam
|
|
||||||
bragg = 0
|
|
||||||
else:
|
|
||||||
raise Exception('Monochromator mode not supported')
|
|
||||||
pos['mo1_bragg_angle'] = {'value': bragg/np.pi*180} # Bragg angle in deg
|
|
||||||
|
|
||||||
# TRY, Height
|
|
||||||
l = bl.mo1.xtalGap[0]/np.sin(cfg['mo1_bragg'])
|
|
||||||
yhor = l*np.cos(2.*(cfg['mo1_bragg']+cfg['cm_pitch']))
|
|
||||||
yver = yhor*np.tan(2.*cfg['cm_pitch'])
|
|
||||||
|
|
||||||
if cfg['mo1_mode'] == 'Monochromatic':
|
|
||||||
beamOffsetCCM = l*np.sin(2.*(cfg['mo1_bragg']+cfg['cm_pitch']))-yver # Resultat ist korrekt!
|
|
||||||
elif cfg['mo1_mode'] == 'Pinkbeam':
|
|
||||||
beamOffsetCCM = 0
|
|
||||||
else:
|
|
||||||
raise Exception('Monochromator mode not supported')
|
|
||||||
|
|
||||||
def csc(a):
|
|
||||||
return 1/np.sin(a)
|
|
||||||
|
|
||||||
def cot(a):
|
|
||||||
return 1/np.tan(a)
|
|
||||||
|
|
||||||
# calculate height of center of first crystal surface
|
|
||||||
f = bl.mo1.rotOffset # rotation offset, mm
|
|
||||||
# logger.info(f'f = {f}')
|
|
||||||
d = bl.mo1.heightOffset # xtal height offset, mm
|
|
||||||
# logger.info(f'd = {d}')
|
|
||||||
c = d*csc(cfg['mo1_bragg'])-f*cot(cfg['mo1_bragg'])
|
|
||||||
# logger.info(f'c = {c}')
|
|
||||||
|
|
||||||
# Calculate height of center of rotation
|
|
||||||
b = np.sqrt(d**2*csc(cfg['mo1_bragg'])**2-2*d*f*cot(cfg['mo1_bragg'])*csc(cfg['mo1_bragg'])+f**2*cot(cfg['mo1_bragg'])**2+f**2)
|
|
||||||
# logger.info(f'b = {b}')
|
|
||||||
h = np.cos(np.pi/2-np.arctan(f/c)-cfg['mo1_bragg']-2*cfg['cm_pitch'])*b
|
|
||||||
# logger.info(f'h = {h}')
|
|
||||||
h2 = ((bl.mo1.center[1] - bl.cm.center[1])-np.sqrt(b**2-h**2))*np.tan(2*cfg['cm_pitch'])
|
|
||||||
# logger.info(f'mo1 = {bl.mo1.center[1]}')
|
|
||||||
# logger.info(f'cm = {bl.cm.center[1]}')
|
|
||||||
# logger.info(f'pitch = {cfg["cm_pitch"]}')
|
|
||||||
# logger.info(f'h2 = {h2}')
|
|
||||||
#TODO Mono height not exactly the same as in raytracing
|
|
||||||
heightCCM1real = h + h2 # per design, the height should not change if the pitch of the CM is not changed!
|
|
||||||
# heightCCM1real = heightCCM1real - 30 # Zero position of stage is at 1430 mm from ground.
|
|
||||||
if cfg['mo1_mode'] == 'Monochromatic':
|
|
||||||
pass
|
|
||||||
elif cfg['mo1_mode'] == 'Pinkbeam':
|
|
||||||
heightCCM1real = heightCCM1real - 13 # Move down to let beam pass between both crystal without touching copper cooler
|
|
||||||
else:
|
|
||||||
raise Exception('Monochromator mode not supported')
|
|
||||||
pos['mo1_try'] = {'value': heightCCM1real}
|
|
||||||
|
|
||||||
# TRX, Crystal selection
|
|
||||||
if cfg['mo1_mode'] == 'Monochromatic':
|
|
||||||
try:
|
|
||||||
xtal = cfg['mo1_xtal'].translate(str.maketrans('', '', '()')) # Remove brackets from xtal name to conform with parameters
|
|
||||||
index = bl.mo1.xtal.index(xtal)
|
|
||||||
except:
|
|
||||||
raise ValueError(f"Requested xtal {xtal} not found in parameters!")
|
|
||||||
pos['mo1_trx'] = {'value': bl.mo1.xtalOffsetX[index]}
|
|
||||||
else:
|
|
||||||
pos['mo1_trx'] = {'value': 0}
|
|
||||||
|
|
||||||
|
|
||||||
#TODO move to mono, calc for beam Z-movement between crystal surfaces
|
|
||||||
diag = bl.mo1.xtalGap[0] / np.sin(cfg['mo1_bragg']) # Calculations for Mono
|
|
||||||
dz = diag * np.cos(2 * (cfg['cm_pitch'] + cfg['mo1_bragg']))
|
|
||||||
|
|
||||||
## Slits 1
|
|
||||||
d = bl.opSlits1.center[1] - bl.cm.center[1] - dz
|
|
||||||
sl1_beam_height = d * np.tan(2 * cfg['cm_pitch']) + beamOffsetCCM
|
|
||||||
pos['sl1_centery'] = {'value': sl1_beam_height}
|
|
||||||
pos['sl1_gapy'] = {'value': beam_vs + 1} # Add 0.5 mm space on both sides of the beam
|
|
||||||
|
|
||||||
## Beam Monitor 1
|
|
||||||
d = bl.opBM1.center[1] - bl.cm.center[1] - dz
|
|
||||||
# logger.info(f'distance: {d}')
|
|
||||||
# logger.info(f'cm pitch: {cfg["cm_pitch"]}')
|
|
||||||
# logger.info(f'mono offset: {beamOffsetCCM}')
|
|
||||||
bm1_beam_height = d * np.tan(2 * cfg['cm_pitch']) + beamOffsetCCM
|
|
||||||
pos['bm1_try'] = {'value': bm1_beam_height}
|
|
||||||
|
|
||||||
## Focusing Mirror
|
|
||||||
p = bl.fm.center[1]
|
|
||||||
q = cfg['smpl'] - bl.fm.center[1]
|
|
||||||
f = (p*q)/(p+q) # focal length
|
|
||||||
|
|
||||||
# Bender radius
|
|
||||||
if cfg['fm_qy'] is None:
|
|
||||||
radius = 2 * q / np.sin(cfg['fm_rotx']) # ideal bending radius for focused beam
|
|
||||||
else:
|
|
||||||
radius = 2 * cfg['fm_qy'] / np.sin(cfg['fm_rotx']) # ideal bending radius for unfocused beam
|
|
||||||
pos['fm_bnd_radius'] = {'value': radius * 1e-6} # Convert to km
|
|
||||||
|
|
||||||
# Pitch
|
|
||||||
d = bl.fm.center[1] - bl.cm.center[1] - dz
|
|
||||||
fm_rotx = 2 * cfg['cm_pitch'] - cfg['fm_rotx'] # calculate pitch in absolute values (according to horizontal plane)
|
|
||||||
pos['fm_rotx'] = {'value': -fm_rotx * 1e3} # invert and convert to mrad (same as EGU of rotx axis)
|
|
||||||
|
|
||||||
if cfg['fm_stripe'] in ('Rh (toroid)', 'Pt (toroid)'):
|
|
||||||
|
|
||||||
# TRY
|
|
||||||
if cfg['fm_stripe'] in 'Rh (toroid)':
|
|
||||||
r = bl.fm.r[0]
|
|
||||||
h_cyl = bl.fm.hToroid[0]
|
|
||||||
else: # PT toroid
|
|
||||||
r = bl.fm.r[1]
|
|
||||||
h_cyl = bl.fm.hToroid[1]
|
|
||||||
widthBeam = 2 * bl.fm.center[1] * np.tan(cfg['h_acc'] * 1e-3)
|
|
||||||
alpha = np.arccos(1 - widthBeam**2 / (2 * r**2))
|
|
||||||
h = r - (r * np.cos(alpha / 2))
|
|
||||||
fm_beam_height = (d * np.tan(2 * cfg['cm_pitch']) + beamOffsetCCM) * cfg['fm_gain_height']
|
|
||||||
fm_height = (d * np.tan(2 * cfg['cm_pitch']) + beamOffsetCCM - h_cyl + h / 2) * cfg['fm_gain_height']
|
|
||||||
pos['fm_try'] = {'value': fm_height}
|
|
||||||
|
|
||||||
# TRX
|
|
||||||
if cfg['fm_stripe'] in 'Rh (toroid)':
|
|
||||||
x_cyl = - bl.fm.xToroid[0]
|
|
||||||
else:
|
|
||||||
x_cyl = - bl.fm.xToroid[1]
|
|
||||||
pos['fm_trx'] = {'value': x_cyl}
|
|
||||||
|
|
||||||
elif cfg['fm_stripe'] in ('Rh (flat)', 'Pt (flat)'):
|
|
||||||
|
|
||||||
# TRY
|
|
||||||
fm_height = (d * np.tan(2 * cfg['cm_pitch']) + beamOffsetCCM) * cfg['fm_gain_height']
|
|
||||||
fm_beam_height = fm_height
|
|
||||||
pos['fm_try'] = {'value': fm_height}
|
|
||||||
|
|
||||||
# TRX
|
|
||||||
if cfg['fm_stripe'] in 'Rh (flat)':
|
|
||||||
x_flat = - bl.fm.xFlat[0]
|
|
||||||
else:
|
|
||||||
x_flat = - bl.fm.xFlat[1]
|
|
||||||
pos['fm_trx'] = {'value': x_flat}
|
|
||||||
|
|
||||||
else:
|
|
||||||
raise Exception('FM Stripe selection not valid')
|
|
||||||
|
|
||||||
pos['fm_roty'] = {'value': 0}
|
|
||||||
pos['fm_rotz'] = {'value': 0}
|
|
||||||
|
|
||||||
## Slits 2
|
|
||||||
d = bl.opSlits2.center[1] - bl.fm.center[1]
|
|
||||||
sl2_beam_height = fm_beam_height - d * np.tan(-(2 * cfg['cm_pitch'] - 2 * cfg['fm_rotx']))
|
|
||||||
pos['sl2_centery'] = {'value': sl2_beam_height}
|
|
||||||
pos['sl2_gapy'] = {'value': beam_vs + 1} # Add 0.5 mm space on both sides of the beam
|
|
||||||
|
|
||||||
## Beam Monitor 2
|
|
||||||
d = bl.opBM2.center[1] - bl.fm.center[1]
|
|
||||||
bm2_beam_height = fm_beam_height - d * np.tan(-(2 * cfg['cm_pitch'] - 2 * cfg['fm_rotx']))
|
|
||||||
pos['bm2_try'] = {'value': bm2_beam_height}
|
|
||||||
|
|
||||||
## Optical Table
|
|
||||||
|
|
||||||
# TRY
|
|
||||||
d = bl.ehWindow.center[1] - bl.fm.center[1]
|
|
||||||
ot_height = fm_beam_height - d * np.tan(-(2 * cfg['cm_pitch'] - 2 * cfg['fm_rotx']))
|
|
||||||
# logger.info(fm_height)
|
|
||||||
# logger.info(d * np.tan((2 * cfg['cm_pitch'] - 2 * cfg['fm_rotx'])))
|
|
||||||
pos['ot_try'] = {'value': ot_height}
|
|
||||||
|
|
||||||
# Pitch
|
|
||||||
ot_pitch = - (2 * cfg['cm_pitch'] - 2 * cfg['fm_rotx'])
|
|
||||||
pos['ot_rotx'] = {'value': ot_pitch * 1e3}
|
|
||||||
|
|
||||||
# TRZ ES1
|
|
||||||
ot_es1_trz = cfg['smpl']
|
|
||||||
pos['ot_es1_trz'] = {'value': ot_es1_trz}
|
|
||||||
|
|
||||||
# ES0 exit window
|
|
||||||
pos['es0wi_try'] = {'value': 5} # At 5mm, the middle of the window is 500 mm from the table (neutral position)
|
|
||||||
|
|
||||||
return pos
|
|
||||||
@@ -1,42 +0,0 @@
|
|||||||
import numpy as np
|
|
||||||
import debye_bec.bec_widgets.widgets.x01da_parameters as bl
|
|
||||||
|
|
||||||
def calc_sideview(cfg):
|
|
||||||
|
|
||||||
# Calculate height of beam after CM
|
|
||||||
height = 2 * bl.cm.center[1] * np.tan(cfg['v_acc'])
|
|
||||||
|
|
||||||
# beam height (Y=height, Z=along beam)
|
|
||||||
beam = {}
|
|
||||||
beam['x'] = []
|
|
||||||
beam['y'] = []
|
|
||||||
beam['x'].append(0) # Source
|
|
||||||
beam['y'].append(bl.sourceHeight)
|
|
||||||
beam['x'].append(bl.cm.center[1]) # CM
|
|
||||||
beam['y'].append(bl.sourceHeight)
|
|
||||||
if cfg['mo1_mode'] in 'Monochromatic':
|
|
||||||
diag = bl.mo1.xtalGap[0]/np.sin(cfg['mo1_bragg']) # Calculations for Mono
|
|
||||||
dy = diag*np.sin(2*(cfg['cm_pitch']+cfg['mo1_bragg']))
|
|
||||||
dz = diag*np.cos(2*(cfg['cm_pitch']+cfg['mo1_bragg']))
|
|
||||||
beam['x'].append(bl.mo1.center[1]-dz/2) # Mono 1.1
|
|
||||||
beam['y'].append(bl.sourceHeight+np.tan(2*cfg['cm_pitch'])*(bl.mo1.center[1]-dz/2-bl.cm.center[1]))
|
|
||||||
beam['x'].append(bl.mo1.center[1]+dz/2) # Mono 1.2
|
|
||||||
beam['y'].append(bl.sourceHeight+np.tan(2*cfg['cm_pitch'])*(bl.mo1.center[1]-dz/2-bl.cm.center[1])+dy)
|
|
||||||
beam['x'].append(bl.fm.center[1]) # FM
|
|
||||||
beam['y'].append(bl.sourceHeight+np.tan(2*cfg['cm_pitch'])*(bl.fm.center[1]-bl.cm.center[1]-dz)+dy)
|
|
||||||
beam['x'].append(cfg['smpl']) # Experiment
|
|
||||||
beam['y'].append(bl.sourceHeight+np.tan(2*cfg['cm_pitch'])*(bl.fm.center[1]-bl.cm.center[1]-dz)+dy+np.tan(2*(cfg['cm_pitch']-cfg['fm_rotx']))*(cfg['smpl']-bl.fm.center[1]))
|
|
||||||
elif cfg['mo1_mode'] == 'Pinkbeam':
|
|
||||||
beam['x'].append(bl.fm.center[1]) # FM
|
|
||||||
beam['y'].append(bl.sourceHeight+np.tan(2*cfg['cm_pitch'])*(bl.fm.center[1]-bl.cm.center[1]))
|
|
||||||
beam['x'].append(cfg['smpl']) # Experiment
|
|
||||||
beam['y'].append(bl.sourceHeight+np.tan(2*cfg['cm_pitch'])*(bl.fm.center[1]-bl.cm.center[1])+np.tan(2*(cfg['cm_pitch']-cfg['fm_rotx']))*(cfg['smpl']-bl.fm.center[1]))
|
|
||||||
|
|
||||||
dy_fm_ex = beam['y'][-1] - beam['y'][-2]
|
|
||||||
dz_fm_ex = beam['x'][-1] - beam['x'][-2]
|
|
||||||
dz_fm_win = bl.ehWindow.center[1] - beam['x'][-2]
|
|
||||||
h_at_win = beam['y'][-2] + dy_fm_ex / dz_fm_ex * dz_fm_win
|
|
||||||
|
|
||||||
beam['heightWindow'] = h_at_win
|
|
||||||
|
|
||||||
return beam
|
|
||||||
@@ -1,131 +0,0 @@
|
|||||||
import os
|
|
||||||
import re
|
|
||||||
import numpy as np
|
|
||||||
from bec_lib import bec_logger
|
|
||||||
|
|
||||||
logger = bec_logger.logger
|
|
||||||
|
|
||||||
os.environ["USE_XRT"] = "False"
|
|
||||||
import debye_bec.bec_widgets.widgets.x01da_parameters as bl
|
|
||||||
|
|
||||||
def calc_surfaces(cfg):
|
|
||||||
|
|
||||||
out = {
|
|
||||||
'cm': {'x': [], 'y': []},
|
|
||||||
'mo1_1': {'x': [], 'y': []},
|
|
||||||
'mo1_2': {'x': [], 'y': []},
|
|
||||||
'fm': {'x': [], 'y': []},
|
|
||||||
}
|
|
||||||
|
|
||||||
# Collimating mirror
|
|
||||||
l = 2 * bl.cm.center[1] * np.tan(cfg['v_acc'])/np.sin(cfg['cm_pitch'])
|
|
||||||
|
|
||||||
w1 = 2 * (bl.cm.center[1]-l/2) * np.tan(cfg['h_acc'])
|
|
||||||
w2 = 2 * (bl.cm.center[1]+l/2) * np.tan(cfg['h_acc'])
|
|
||||||
|
|
||||||
index = bl.cm.surface.index(cfg['cm_stripe'])
|
|
||||||
cen = (bl.cm.limOptX[0][index] + bl.cm.limOptX[1][index]) / 2
|
|
||||||
|
|
||||||
if cfg['cm_trx'] is not None:
|
|
||||||
cen = cfg['cm_trx']
|
|
||||||
|
|
||||||
out['cm']['x'] = [cen-w1/2, cen-w2/2, cen+w2/2, cen+w1/2]
|
|
||||||
out['cm']['y'] = [-l/2, l/2, l/2, -l/2]
|
|
||||||
|
|
||||||
|
|
||||||
# Monochromator
|
|
||||||
# calculate height of center of first crystal surface
|
|
||||||
c = bl.mo1.heightOffset*1/np.sin(cfg['mo1_bragg'])-bl.mo1.rotOffset*1/np.tan(cfg['mo1_bragg'])
|
|
||||||
e = bl.mo1.xtalGap[0]/np.tan(cfg['mo1_bragg'])-c
|
|
||||||
|
|
||||||
xtal = cfg['mo1_xtal'].translate(str.maketrans('', '', '()')) # Remove brackets from xtal name to conform with parameters
|
|
||||||
index = bl.mo1.xtal.index(xtal)
|
|
||||||
|
|
||||||
xtalPos = bl.mo1.xtalOffsetX[index]
|
|
||||||
xtalLength1 = bl.mo1.xtalLength1[index]
|
|
||||||
xtalLength2 = bl.mo1.xtalLength2[index]
|
|
||||||
|
|
||||||
widthBeam = 2 * bl.mo1.center[1] * np.tan(cfg['h_acc'])
|
|
||||||
|
|
||||||
heightBeam = 2 * bl.cm.center[1] * np.tan(cfg['v_acc'])
|
|
||||||
w = heightBeam / np.sin(cfg['mo1_bragg'])
|
|
||||||
|
|
||||||
if cfg['mo1_mode'] in 'Monochromatic':
|
|
||||||
out['mo1_1']['x'] = [xtalPos-widthBeam/2, xtalPos+widthBeam/2, xtalPos+widthBeam/2, xtalPos-widthBeam/2]
|
|
||||||
out['mo1_1']['y'] = [xtalLength1/2-c-w/2, xtalLength1/2-c-w/2, xtalLength1/2-c+w/2, xtalLength1/2-c+w/2]
|
|
||||||
out['mo1_2']['x'] = [xtalPos-widthBeam/2, xtalPos+widthBeam/2, xtalPos+widthBeam/2, xtalPos-widthBeam/2]
|
|
||||||
out['mo1_2']['y'] = [-xtalLength2/2+e-w/2, -xtalLength2/2+e-w/2, -xtalLength2/2+e+w/2, -xtalLength2/2+e+w/2]
|
|
||||||
else: # Pinkbeam
|
|
||||||
out['mo1_1']['x'] = []
|
|
||||||
out['mo1_1']['y'] = []
|
|
||||||
out['mo1_2']['x'] = []
|
|
||||||
out['mo1_2']['y'] = []
|
|
||||||
|
|
||||||
# Focusing mirror
|
|
||||||
if cfg['fm_stripe'] in ('Rh (toroid)', 'Pt (toroid)'):
|
|
||||||
surface = bl.fm.surfaceToroid
|
|
||||||
stripe = re.sub(r'\s*\(.*?\)', '', cfg['fm_stripe']).strip()
|
|
||||||
index = surface.index(stripe)
|
|
||||||
off = (bl.fm.limOptXToroid[0][index] + bl.fm.limOptXToroid[1][index]) / 2
|
|
||||||
r = bl.fm.r[index]
|
|
||||||
else:
|
|
||||||
surface = bl.fm.surfaceFlat
|
|
||||||
stripe = re.sub(r'\s*\(.*?\)', '', cfg['fm_stripe']).strip()
|
|
||||||
index = surface.index(stripe)
|
|
||||||
off = (bl.fm.limOptXFlat[0][index] + bl.fm.limOptXFlat[1][index]) / 2
|
|
||||||
r = bl.fm.r[index]
|
|
||||||
if cfg['fm_trx'] is not None:
|
|
||||||
off = cfg['fm_trx']
|
|
||||||
|
|
||||||
widthBeam = 2 * bl.fm.center[1] * np.tan(cfg['h_acc'])
|
|
||||||
|
|
||||||
if cfg['fm_stripe'] in ('Rh (toroid)', 'Pt (toroid)'):
|
|
||||||
|
|
||||||
l = heightBeam/np.sin(cfg['fm_rotx'])
|
|
||||||
alpha = np.arccos(1-widthBeam**2/(2*r**2))
|
|
||||||
h = r-(r*np.cos(alpha/2))
|
|
||||||
z = h/np.tan(cfg['fm_rotx'])
|
|
||||||
|
|
||||||
x = [off-widthBeam/2, off-widthBeam/2]
|
|
||||||
y = [l/2-z/2, -l/2-z/2]
|
|
||||||
|
|
||||||
# logger.info(f'stripe: {cfg["fm_stripe"]}')
|
|
||||||
# logger.info(f'fm_rotx: {cfg["fm_rotx"]}')
|
|
||||||
# logger.info(f'h: {h}')
|
|
||||||
# logger.info(f'z: {z}')
|
|
||||||
# logger.info(f'r: {r}')
|
|
||||||
|
|
||||||
res = 20
|
|
||||||
xElipse = np.linspace(0, np.pi, res)
|
|
||||||
yElipse = np.linspace(0, np.pi, res)
|
|
||||||
xElipse = [-widthBeam/2*np.cos(i)+off for i in xElipse]
|
|
||||||
yElipse = [widthBeam*np.sin(i)*z/widthBeam-l/2-z/2 for i in yElipse]
|
|
||||||
|
|
||||||
x.extend(xElipse)
|
|
||||||
y.extend(yElipse)
|
|
||||||
|
|
||||||
x.extend([off+widthBeam/2, off+widthBeam/2])
|
|
||||||
y.extend([-l/2-z/2, l/2-z/2])
|
|
||||||
|
|
||||||
res = 50
|
|
||||||
xElipse = np.linspace(np.pi, 0, res)
|
|
||||||
yElipse = np.linspace(np.pi, 0, res)
|
|
||||||
xElipse = [-widthBeam/2*np.cos(i)+off for i in xElipse]
|
|
||||||
yElipse = [widthBeam*np.sin(i)*z/widthBeam+l/2-z/2 for i in yElipse]
|
|
||||||
|
|
||||||
x.extend(xElipse)
|
|
||||||
y.extend(yElipse)
|
|
||||||
|
|
||||||
out['fm']['x'] = x
|
|
||||||
out['fm']['y'] = y
|
|
||||||
|
|
||||||
else: # flat surface, no toroid
|
|
||||||
l = heightBeam/np.sin(cfg['fm_rotx'])
|
|
||||||
|
|
||||||
w1 = 2 * (bl.fm.center[1]-l/2) * np.tan(cfg['h_acc'])
|
|
||||||
w2 = 2 * (bl.fm.center[1]+l/2) * np.tan(cfg['h_acc'])
|
|
||||||
|
|
||||||
out['fm']['x'] = [off-w1/2, off+w1/2, off+w2/2, off-w2/2]
|
|
||||||
out['fm']['y'] = [-l/2, -l/2, l/2, l/2]
|
|
||||||
|
|
||||||
return out
|
|
||||||
@@ -1,206 +0,0 @@
|
|||||||
import re
|
|
||||||
import numpy as np
|
|
||||||
from scipy.interpolate import UnivariateSpline
|
|
||||||
from xrt.backends.raycing.physconsts import CHeVcm, AVOGADRO
|
|
||||||
from bec_lib import bec_logger
|
|
||||||
|
|
||||||
import debye_bec.bec_widgets.widgets.x01da_parameters as bl
|
|
||||||
|
|
||||||
logger = bec_logger.logger
|
|
||||||
|
|
||||||
def sldi_gap_to_acc(sldi_gapx, sldi_gapy):
|
|
||||||
d1 = bl.feSlits.center1[1]
|
|
||||||
d2 = bl.feSlits.center2[1]
|
|
||||||
h_acc = np.tan(sldi_gapx / (d2 + d1))
|
|
||||||
v_acc = np.tan(sldi_gapy / (d2 + d1))
|
|
||||||
|
|
||||||
# h_acc = np.tan(sldi_gapx / (2 * d1))
|
|
||||||
# v_acc = np.tan(sldi_gapy / (2 * d1))
|
|
||||||
return h_acc, v_acc
|
|
||||||
|
|
||||||
def cm_trx_to_stripe(cm_trx):
|
|
||||||
cm_stripe = None
|
|
||||||
for name, low, high in zip(bl.cm.surface, bl.cm.limOptX[0], bl.cm.limOptX[1]):
|
|
||||||
if low <= cm_trx <= high:
|
|
||||||
cm_stripe = name
|
|
||||||
return cm_stripe
|
|
||||||
|
|
||||||
def fm_trx_to_stripe(fm_trx):
|
|
||||||
fm_stripe = None
|
|
||||||
for name, low, high in zip(bl.fm.surfaceFlat, bl.fm.limOptXFlat[1], bl.fm.limOptXFlat[0]):
|
|
||||||
if low <= fm_trx <= high:
|
|
||||||
fm_stripe = name + ' (flat)'
|
|
||||||
for name, low, high in zip(bl.fm.surfaceToroid, bl.fm.limOptXToroid[1], bl.fm.limOptXToroid[0]):
|
|
||||||
if low <= fm_trx <= high:
|
|
||||||
fm_stripe = name + ' (toroid)'
|
|
||||||
return fm_stripe
|
|
||||||
|
|
||||||
def mo1_energy_resolution(xtal, energy):
|
|
||||||
index = bl.mo1.xtal.index(xtal)
|
|
||||||
crystal = bl.mo1.material1[index]
|
|
||||||
|
|
||||||
dtheta = np.linspace(-30, 90, 601)
|
|
||||||
theta = crystal.get_Bragg_angle(energy) + dtheta * 1e-6
|
|
||||||
refl = np.abs(crystal.get_amplitude(energy, np.sin(theta))[0])**2 # single crystal
|
|
||||||
|
|
||||||
refl2 = refl**2 # DCM with parallel crystals
|
|
||||||
|
|
||||||
# FWHM of the DCM curve
|
|
||||||
spline = UnivariateSpline(dtheta, refl2 - refl2.max()/2, s=0)
|
|
||||||
r1, r2 = spline.roots()
|
|
||||||
fwhm_rad = (r2 - r1) * 1e-6 # µrad → rad
|
|
||||||
|
|
||||||
# Energy resolution
|
|
||||||
theta_B = crystal.get_Bragg_angle(energy)
|
|
||||||
dE_over_E = fwhm_rad / np.tan(theta_B)
|
|
||||||
dE = dE_over_E * energy
|
|
||||||
|
|
||||||
# logger.info(f"DCM FWHM : {r2-r1:.2f} µrad")
|
|
||||||
# logger.info(f"ΔE/E : {dE_over_E:.2e}")
|
|
||||||
# logger.info(f"ΔE : {dE:.3f} eV at {E} eV")
|
|
||||||
|
|
||||||
return dE
|
|
||||||
|
|
||||||
def cm_reflectivity(cm_stripe, cm_pitch, energy):
|
|
||||||
index = bl.cm.surface.index(cm_stripe)
|
|
||||||
rs, rp = bl.cm.material[index].get_amplitude(
|
|
||||||
energy,
|
|
||||||
np.sin(cm_pitch)
|
|
||||||
)[0:2]
|
|
||||||
refl = abs(rs)**2
|
|
||||||
return refl
|
|
||||||
|
|
||||||
def fm_reflectivity(fm_stripe, fm_pitch, energy):
|
|
||||||
if fm_stripe in ('Rh (toroid)', 'Pt (toroid)'):
|
|
||||||
surface = bl.fm.surfaceToroid
|
|
||||||
material = bl.fm.materialToroid
|
|
||||||
stripe = re.sub(r'\s*\(.*?\)', '', fm_stripe).strip()
|
|
||||||
index = surface.index(stripe)
|
|
||||||
else:
|
|
||||||
surface = bl.fm.surfaceFlat
|
|
||||||
material = bl.fm.materialFlat
|
|
||||||
stripe = re.sub(r'\s*\(.*?\)', '', fm_stripe).strip()
|
|
||||||
index = surface.index(stripe)
|
|
||||||
rs, rp = material[index].get_amplitude(
|
|
||||||
energy,
|
|
||||||
np.sin(fm_pitch)
|
|
||||||
)[0:2]
|
|
||||||
refl = abs(rs)**2
|
|
||||||
return refl
|
|
||||||
|
|
||||||
def mo1_bragg_angle(mo_mode, d_spacing, energy, cm_pitch):
|
|
||||||
H = 6.62606957E-34
|
|
||||||
E = 1.602176634E-19
|
|
||||||
C = 299792458
|
|
||||||
wl = C * H / (E * energy)
|
|
||||||
val = wl / (2 * d_spacing * 1e-10)
|
|
||||||
bragg_angle = 0
|
|
||||||
if val > -1 and val < 1:
|
|
||||||
bragg_angle = np.asin(val)
|
|
||||||
if mo_mode in 'Monochromatic':
|
|
||||||
# Add 2x CM pitch to the bragg angle
|
|
||||||
bragg_angle_cor = ((2 * cm_pitch) + bragg_angle)
|
|
||||||
elif mo_mode in 'Pinkbeam':
|
|
||||||
# Align xtal surfaces parallel to beam
|
|
||||||
bragg_angle_cor = (2 * cm_pitch)
|
|
||||||
return bragg_angle, bragg_angle_cor
|
|
||||||
|
|
||||||
def fm_ideal_pitch(fm_focus, fm_stripe, smpl, sldi_hacc=None, sldi_vacc=None, fm_focx=None, fm_focy=None):
|
|
||||||
p = bl.fm.center[1] # posFM
|
|
||||||
q = smpl - bl.fm.center[1] # dist posFM to posEX
|
|
||||||
if fm_focus in 'Defocused':
|
|
||||||
a = 2 * np.tan(sldi_hacc) * bl.fm.center[1] # Beam width at focusing mirror
|
|
||||||
b = 2 * np.tan(sldi_vacc) * bl.cm.center[1] # Beam height at focusing mirror (collimated beam)
|
|
||||||
x = fm_focx
|
|
||||||
y = fm_focy
|
|
||||||
qx = q + x * p / a
|
|
||||||
qy = q + y * p / b
|
|
||||||
f = (p * qx) / (p + qx) # focal length
|
|
||||||
else: # Calculate for focused beam on sample in "manual" and "focused" mode
|
|
||||||
qy = None
|
|
||||||
f = (p * q) / (p + q) # focal length
|
|
||||||
pitch = 0
|
|
||||||
if 'Rh' in fm_stripe:
|
|
||||||
pitch = np.arcsin(bl.fm.r[0]/(2*f))# ideal pitch for FM
|
|
||||||
if 'Pt' in fm_stripe:
|
|
||||||
pitch = np.arcsin(bl.fm.r[1]/(2*f)) # ideal pitch for FM
|
|
||||||
return pitch, qy
|
|
||||||
|
|
||||||
def cm_critical_angle(cm_stripe, energy):
|
|
||||||
if cm_stripe in 'Si':
|
|
||||||
stripe = bl.stripeSi
|
|
||||||
elif cm_stripe in 'Pt':
|
|
||||||
stripe = bl.stripePt
|
|
||||||
elif cm_stripe in 'Rh':
|
|
||||||
stripe = bl.stripeRh
|
|
||||||
else:
|
|
||||||
raise Exception(f'Stripe {stripe} not found in beamline parameters!')
|
|
||||||
w = CHeVcm/100/energy # convert energy [eV] to wavelength [m]
|
|
||||||
# Calculate critical angle for mirror
|
|
||||||
f1 = stripe.elements[0].Z + np.real(stripe.elements[0].get_f1f2(energy))
|
|
||||||
numberDensity = stripe.rho*1e3*AVOGADRO/(stripe.elements[0].mass/1e3)
|
|
||||||
criticalAngle = np.sqrt(numberDensity*2.8179e-15*w**2*f1/np.pi)
|
|
||||||
return criticalAngle
|
|
||||||
|
|
||||||
|
|
||||||
def mirror_surface_geometries(mirror):
|
|
||||||
if mirror in "cm":
|
|
||||||
surface = bl.cm.surface
|
|
||||||
limOptX = bl.cm.limOptX
|
|
||||||
limOptY = bl.cm.limOptY
|
|
||||||
elif mirror in 'fm_toroid':
|
|
||||||
surface = bl.fm.surfaceToroid
|
|
||||||
limOptX = bl.fm.limOptXToroid
|
|
||||||
limOptY = bl.fm.limOptYToroid
|
|
||||||
elif mirror in 'fm_flat':
|
|
||||||
surface = bl.fm.surfaceFlat
|
|
||||||
limOptX = bl.fm.limOptXFlat
|
|
||||||
limOptY = bl.fm.limOptYFlat
|
|
||||||
else:
|
|
||||||
raise ValueError(f'Requested mirror {mirror} not available!')
|
|
||||||
geom = {}
|
|
||||||
for sf, lx, hx, ly, hy in zip(surface, limOptX[0], limOptX[1], limOptY[0], limOptY[1]):
|
|
||||||
geom[sf] = (lx, ly, hx-lx, hy-ly)
|
|
||||||
return geom
|
|
||||||
|
|
||||||
def mo_surface_geometries(mo, plane):
|
|
||||||
if mo in 'mo1':
|
|
||||||
xtal = bl.mo1.xtal
|
|
||||||
xtal_width = bl.mo1.xtalWidth
|
|
||||||
xtal_offset_x = bl.mo1.xtalOffsetX
|
|
||||||
if plane == 0:
|
|
||||||
xtal_length = bl.mo1.xtalLength1
|
|
||||||
else:
|
|
||||||
xtal_length = bl.mo1.xtalLength2
|
|
||||||
else:
|
|
||||||
raise ValueError(f'Requested mono {mo} not available!')
|
|
||||||
geom = {}
|
|
||||||
for sf, w, offx, length in zip(xtal, xtal_width, xtal_offset_x, xtal_length):
|
|
||||||
geom[sf] = (offx-w/2, -length/2, w, length)
|
|
||||||
return geom
|
|
||||||
|
|
||||||
def wall_geometries():
|
|
||||||
geom = []
|
|
||||||
for i, _ in enumerate(bl.walls.start):
|
|
||||||
geom.append([
|
|
||||||
bl.walls.start[i],
|
|
||||||
bl.walls.height[i][0],
|
|
||||||
bl.walls.end[i] - bl.walls.start[i],
|
|
||||||
bl.walls.height[i][1] - bl.walls.height[i][0],
|
|
||||||
])
|
|
||||||
return geom
|
|
||||||
|
|
||||||
def pipe_geometries():
|
|
||||||
pipes = []
|
|
||||||
for i, _ in enumerate(bl.vacuum_pipes.center):
|
|
||||||
top = bl.vacuum_pipes.center[i] + bl.vacuum_pipes.diameter[i]/2 + bl.sourceHeight
|
|
||||||
bottom = bl.vacuum_pipes.center[i] - bl.vacuum_pipes.diameter[i]/2 + bl.sourceHeight
|
|
||||||
pipes.append({
|
|
||||||
'x': np.array([bl.vacuum_pipes.start[i], bl.vacuum_pipes.end[i]]),
|
|
||||||
'y': np.array([top, top])
|
|
||||||
})
|
|
||||||
pipes.append({
|
|
||||||
'x': np.array([bl.vacuum_pipes.start[i], bl.vacuum_pipes.end[i]]),
|
|
||||||
'y': np.array([bottom, bottom])
|
|
||||||
})
|
|
||||||
return pipes
|
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -1 +0,0 @@
|
|||||||
{'files': ['digital_twin.py']}
|
|
||||||
@@ -1,57 +0,0 @@
|
|||||||
# Copyright (C) 2022 The Qt Company Ltd.
|
|
||||||
# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
|
|
||||||
|
|
||||||
from bec_widgets.utils.bec_designer import designer_material_icon
|
|
||||||
from qtpy.QtDesigner import QDesignerCustomWidgetInterface
|
|
||||||
from qtpy.QtWidgets import QWidget
|
|
||||||
|
|
||||||
from debye_bec.bec_widgets.widgets.digital_twin.digital_twin import DigitalTwin
|
|
||||||
|
|
||||||
DOM_XML = """
|
|
||||||
<ui language='c++'>
|
|
||||||
<widget class='DigitalTwin' name='digital_twin'>
|
|
||||||
</widget>
|
|
||||||
</ui>
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
class DigitalTwinPlugin(QDesignerCustomWidgetInterface): # pragma: no cover
|
|
||||||
def __init__(self):
|
|
||||||
super().__init__()
|
|
||||||
self._form_editor = None
|
|
||||||
|
|
||||||
def createWidget(self, parent):
|
|
||||||
if parent is None:
|
|
||||||
return QWidget()
|
|
||||||
t = DigitalTwin(parent)
|
|
||||||
return t
|
|
||||||
|
|
||||||
def domXml(self):
|
|
||||||
return DOM_XML
|
|
||||||
|
|
||||||
def group(self):
|
|
||||||
return ""
|
|
||||||
|
|
||||||
def icon(self):
|
|
||||||
return designer_material_icon(DigitalTwin.ICON_NAME)
|
|
||||||
|
|
||||||
def includeFile(self):
|
|
||||||
return "digital_twin"
|
|
||||||
|
|
||||||
def initialize(self, form_editor):
|
|
||||||
self._form_editor = form_editor
|
|
||||||
|
|
||||||
def isContainer(self):
|
|
||||||
return False
|
|
||||||
|
|
||||||
def isInitialized(self):
|
|
||||||
return self._form_editor is not None
|
|
||||||
|
|
||||||
def name(self):
|
|
||||||
return "DigitalTwin"
|
|
||||||
|
|
||||||
def toolTip(self):
|
|
||||||
return "DigitalTwin"
|
|
||||||
|
|
||||||
def whatsThis(self):
|
|
||||||
return self.toolTip()
|
|
||||||
@@ -1,511 +0,0 @@
|
|||||||
import time
|
|
||||||
import random
|
|
||||||
import threading
|
|
||||||
|
|
||||||
# import qtawesome as qta
|
|
||||||
from bec_qthemes import material_icon
|
|
||||||
from bec_widgets.utils.colors import get_accent_colors
|
|
||||||
from bec_lib import bec_logger
|
|
||||||
|
|
||||||
from debye_bec.devices.absorber import STATUS as ABS_STATUS
|
|
||||||
|
|
||||||
from qtpy.QtCore import Qt, QThread, Signal, QObject, Property, QPropertyAnimation
|
|
||||||
from qtpy.QtWidgets import (
|
|
||||||
QGroupBox, QHBoxLayout, QVBoxLayout, QLabel, QPushButton,
|
|
||||||
QDoubleSpinBox, QFrame, QWidget, QApplication
|
|
||||||
)
|
|
||||||
from qtpy.QtGui import QTransform
|
|
||||||
|
|
||||||
logger = bec_logger.logger
|
|
||||||
|
|
||||||
class Status:
|
|
||||||
IN_POSITION = "in_position" # green mdi.check-circle
|
|
||||||
NOT_IN_POSITION = "not_in_position" # orange mdi.close-circle
|
|
||||||
MOVING = "moving" # blue mdi.loading (spinning)
|
|
||||||
ERROR = "error" # red mdi.alert-circle
|
|
||||||
|
|
||||||
class StatusIcon(QWidget):
|
|
||||||
"""
|
|
||||||
Displays a status icon using bec_qthemes Material Design Icons.
|
|
||||||
Handles its own spin animation for the MOVING state via QPropertyAnimation.
|
|
||||||
"""
|
|
||||||
|
|
||||||
ICON_SIZE = 20
|
|
||||||
|
|
||||||
_ICON_MAP = {
|
|
||||||
Status.IN_POSITION: ("check_circle", "#27ae60"),
|
|
||||||
Status.NOT_IN_POSITION: ("cancel", "#e6d922"),
|
|
||||||
Status.ERROR: ("warning", "#e74c3c"),
|
|
||||||
Status.MOVING: ("cycle", "#2980b9"),
|
|
||||||
}
|
|
||||||
|
|
||||||
def __init__(self, parent=None):
|
|
||||||
super().__init__(parent=parent)
|
|
||||||
self._status = None
|
|
||||||
self._rotation = 0.0
|
|
||||||
|
|
||||||
self._label = QLabel(self)
|
|
||||||
self._label.setFixedSize(self.ICON_SIZE, self.ICON_SIZE)
|
|
||||||
self._label.setAlignment(Qt.AlignCenter)
|
|
||||||
self.setFixedSize(self.ICON_SIZE, self.ICON_SIZE)
|
|
||||||
|
|
||||||
self._spin_anim = QPropertyAnimation(self, b"rotation")
|
|
||||||
self._spin_anim.setStartValue(0)
|
|
||||||
self._spin_anim.setEndValue(360)
|
|
||||||
self._spin_anim.setDuration(1000)
|
|
||||||
self._spin_anim.setLoopCount(-1) # Loop indefinitely
|
|
||||||
|
|
||||||
self.set_status(Status.NOT_IN_POSITION)
|
|
||||||
|
|
||||||
def get_rotation(self):
|
|
||||||
return self._rotation
|
|
||||||
|
|
||||||
def set_rotation(self, angle):
|
|
||||||
self._rotation = angle
|
|
||||||
if self._current_pixmap_base is not None:
|
|
||||||
cx = self._current_pixmap_base.width() / 2
|
|
||||||
cy = self._current_pixmap_base.height() / 2
|
|
||||||
t = QTransform().translate(cx, cy).rotate(angle).translate(-cx, -cy)
|
|
||||||
self._label.setPixmap(self._current_pixmap_base.transformed(t, Qt.SmoothTransformation))
|
|
||||||
|
|
||||||
rotation = Property(float, get_rotation, set_rotation)
|
|
||||||
|
|
||||||
def set_status(self, status: str):
|
|
||||||
if status == self._status:
|
|
||||||
return
|
|
||||||
self._status = status
|
|
||||||
|
|
||||||
icon_name, color = self._ICON_MAP[status]
|
|
||||||
icon = material_icon(icon_name, size=(self.ICON_SIZE, self.ICON_SIZE), color=color, convert_to_pixmap=True)
|
|
||||||
self._current_pixmap_base = icon
|
|
||||||
|
|
||||||
if status == Status.MOVING:
|
|
||||||
self._spin_anim.start()
|
|
||||||
else:
|
|
||||||
self._spin_anim.stop()
|
|
||||||
self._label.setPixmap(icon)
|
|
||||||
|
|
||||||
class MotionWorker(QObject):
|
|
||||||
"""
|
|
||||||
Executes motion on the specified motor and includes some safety during
|
|
||||||
motion for certain motors.
|
|
||||||
"""
|
|
||||||
position_changed = Signal(float)
|
|
||||||
error = Signal(bool) # True = error
|
|
||||||
finished = Signal(bool) # True = reached target, False = stopped
|
|
||||||
|
|
||||||
def __init__(self, dev, motor, target_pos: float):
|
|
||||||
super().__init__()
|
|
||||||
self.dev = dev
|
|
||||||
self.motor = motor
|
|
||||||
self._target = target_pos
|
|
||||||
self._stop_flag = threading.Event()
|
|
||||||
|
|
||||||
def stop(self):
|
|
||||||
self._stop_flag.set()
|
|
||||||
|
|
||||||
# def run(self):
|
|
||||||
# logger.info(f'Would run motor {self.motor}')
|
|
||||||
# simulated_run_time = 3
|
|
||||||
# start = time.time()
|
|
||||||
# while (time.time() - start) < simulated_run_time:
|
|
||||||
# if self._stop_flag.is_set():
|
|
||||||
# break
|
|
||||||
# time.sleep(0.01)
|
|
||||||
|
|
||||||
# # self.motor.move(self._target, relative=False)
|
|
||||||
# # while self.motor.motor_is_moving.get():
|
|
||||||
# # if self._stop_flag.is_set():
|
|
||||||
# # self.motor.motor_stop()
|
|
||||||
# # self.position_changed.emit(self.motor.read[self.name]['value'])
|
|
||||||
# # time.sleep(0.1)
|
|
||||||
# self.finished.emit(True)
|
|
||||||
|
|
||||||
def run(self):
|
|
||||||
match self.motor:
|
|
||||||
case 'sldi_gapx' | 'sldi_gapy' | 'sldi_centerx' | 'sldi_centery':
|
|
||||||
self.motion()
|
|
||||||
case 'cm_trx':
|
|
||||||
self.motion(abs_closed=True, surveyed_axes=[
|
|
||||||
{'device': self.dev['cm_roty'], 'abs_tol': 0.05}
|
|
||||||
])
|
|
||||||
case 'cm_roty':
|
|
||||||
self.motion(abs_closed=True, surveyed_axes=[
|
|
||||||
{'device': self.dev['cm_trx'], 'abs_tol': 0.05}
|
|
||||||
])
|
|
||||||
case 'cm_try':
|
|
||||||
self.motion(abs_closed=True, surveyed_axes=[
|
|
||||||
{'device': self.dev['cm_rotx'], 'abs_tol': 0.05},
|
|
||||||
{'device': self.dev['cm_rotz'], 'abs_tol': 0.05},
|
|
||||||
])
|
|
||||||
case 'cm_rotx':
|
|
||||||
self.motion(abs_closed=True, surveyed_axes=[
|
|
||||||
{'device': self.dev['cm_try'], 'abs_tol': 0.05},
|
|
||||||
{'device': self.dev['cm_rotz'], 'abs_tol': 0.05},
|
|
||||||
])
|
|
||||||
case 'cm_rotz':
|
|
||||||
self.motion(abs_closed=True, surveyed_axes=[
|
|
||||||
{'device': self.dev['cm_try'], 'abs_tol': 0.05},
|
|
||||||
{'device': self.dev['cm_rotx'], 'abs_tol': 0.05},
|
|
||||||
])
|
|
||||||
case 'cm_bnd':
|
|
||||||
p1 = (1/(self.dev.cm_bnd_radius.read()['cm_bnd_radius']['value']*1e3) + 0.0284)/2e-6
|
|
||||||
p2 = (1/(self._target*1e3) + 0.0284)/2e-6
|
|
||||||
self._target = p2 - p1
|
|
||||||
self.motion(relative=True, rb=
|
|
||||||
{'device': self.dev['cm_bnd_radius']}
|
|
||||||
)
|
|
||||||
case 'mo1_try' | 'mo1_trx' | 'mo1_roty':
|
|
||||||
self.motion(abs_closed=True)
|
|
||||||
case 'mo1_bragg_angle':
|
|
||||||
self.motion()
|
|
||||||
case 'sl1_centery' | 'sl1_gapy' | 'bm1_try':
|
|
||||||
self.motion()
|
|
||||||
case 'fm_trx':
|
|
||||||
self.motion(abs_closed=True, surveyed_axes=[
|
|
||||||
{'device': self.dev['fm_roty'], 'abs_tol': 0.05}
|
|
||||||
])
|
|
||||||
case 'fm_roty':
|
|
||||||
self.motion(abs_closed=True, surveyed_axes=[
|
|
||||||
{'device': self.dev['fm_trx'], 'abs_tol': 0.05}
|
|
||||||
])
|
|
||||||
case 'fm_try':
|
|
||||||
self.motion(abs_closed=True, surveyed_axes=[
|
|
||||||
{'device': self.dev['fm_rotx'], 'abs_tol': 0.05},
|
|
||||||
{'device': self.dev['fm_rotz'], 'abs_tol': 0.05},
|
|
||||||
])
|
|
||||||
case 'fm_rotx':
|
|
||||||
self.motion(abs_closed=True, surveyed_axes=[
|
|
||||||
{'device': self.dev['fm_try'], 'abs_tol': 0.05},
|
|
||||||
{'device': self.dev['fm_rotz'], 'abs_tol': 0.05},
|
|
||||||
])
|
|
||||||
case 'fm_rotz':
|
|
||||||
self.motion(abs_closed=True, surveyed_axes=[
|
|
||||||
{'device': self.dev['fm_try'], 'abs_tol': 0.05},
|
|
||||||
{'device': self.dev['fm_rotx'], 'abs_tol': 0.05},
|
|
||||||
])
|
|
||||||
case 'fm_bnd':
|
|
||||||
p1 = (1/(self.dev.fm_bnd_radius.read()['fm_bnd_radius']['value']*1e3) + 4.28e-5)/1.84e-9
|
|
||||||
p2 = (1/(self._target*1e3) + 4.28e-5)/1.84e-9
|
|
||||||
self._target = p2 - p1
|
|
||||||
self.motion(relative=True, rb=
|
|
||||||
{'device': self.dev['fm_bnd_radius']}
|
|
||||||
)
|
|
||||||
case 'sl2_centery' | 'sl2_gapy' | 'bm2_try':
|
|
||||||
self.motion()
|
|
||||||
case 'ot_try' | 'ot_rotx' | 'ot_es1_trz':
|
|
||||||
self.motion()
|
|
||||||
case _:
|
|
||||||
logger.warning(f'Motor {self.motor} not integrated in digital twin!')
|
|
||||||
|
|
||||||
def motion(self, abs_closed=False, relative=False, rb=None, surveyed_axes = None):
|
|
||||||
"""
|
|
||||||
Moves an axis while surverying a set of axes (if set).
|
|
||||||
Example surveyed_axes:
|
|
||||||
[{'device': bec_device_object, 'abs_tol': 0.1},]
|
|
||||||
|
|
||||||
Args:
|
|
||||||
surveyed_axes (list): List of dictionaries of devices
|
|
||||||
"""
|
|
||||||
if abs_closed:
|
|
||||||
if self.dev.abs.status.get() == ABS_STATUS.OPEN:
|
|
||||||
status = self.dev.abs.close()
|
|
||||||
# TODO Set timeout to 0.001 and check if it actually raises (it should not start motion).
|
|
||||||
# Check of behavior of digital twin afterwards.
|
|
||||||
status.wait(timeout=5)
|
|
||||||
if surveyed_axes is not None:
|
|
||||||
for surv_ax in surveyed_axes:
|
|
||||||
surv_ax['name'] = surv_ax['device'].dotted_name
|
|
||||||
surv_ax['old_value'] = surv_ax['device'].read(cached=True)[surv_ax['name']]['value']
|
|
||||||
if rb is not None:
|
|
||||||
rb['name'] = rb['device'].dotted_name
|
|
||||||
self.dev[self.motor].move(self._target, relative=relative)
|
|
||||||
time.sleep(0.5)
|
|
||||||
while self.dev[self.motor].motor_is_moving.get():
|
|
||||||
if self._stop_flag.is_set():
|
|
||||||
self.dev[self.motor].stop()
|
|
||||||
self._stop_flag.clear()
|
|
||||||
if rb is not None:
|
|
||||||
self.position_changed.emit(rb['device'].read(cached=True)[rb['name']]['value'])
|
|
||||||
else:
|
|
||||||
self.position_changed.emit(self.dev[self.motor].read(cached=True)[self.motor]['value'])
|
|
||||||
if surveyed_axes is not None:
|
|
||||||
for surv_ax in surveyed_axes:
|
|
||||||
fb = surv_ax['device'].read(cached=True)[surv_ax['name']]['value']
|
|
||||||
if abs(fb - surv_ax['old_value']) > surv_ax['abs_tol']:
|
|
||||||
self.dev[self.motor].stop()
|
|
||||||
self.error.emit(1)
|
|
||||||
break
|
|
||||||
time.sleep(0.1)
|
|
||||||
self.finished.emit(True)
|
|
||||||
|
|
||||||
class MoveWidget(QWidget):
|
|
||||||
"""
|
|
||||||
One motor stage control group containing:
|
|
||||||
- Target label (target position)
|
|
||||||
- Feedback label (current position)
|
|
||||||
- Status icon (bec_qthemes)
|
|
||||||
- Start / Stop button
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, dev, motor, label: str = '', unit=None, decimals=3, deadband=0.0):
|
|
||||||
super().__init__()
|
|
||||||
self.fb = 0.0
|
|
||||||
self.target = 0
|
|
||||||
self.dev = dev
|
|
||||||
self.motor = motor
|
|
||||||
self.deadband = deadband
|
|
||||||
self.status = Status.IN_POSITION
|
|
||||||
self._thread: QThread | None = None
|
|
||||||
self._worker: MotionWorker | None = None
|
|
||||||
|
|
||||||
self.text_color = (0, 0, 0)
|
|
||||||
|
|
||||||
self.unit = unit
|
|
||||||
self.decimals = decimals
|
|
||||||
|
|
||||||
layout = QHBoxLayout(self)
|
|
||||||
layout.setContentsMargins(10, 0, 0, 0)
|
|
||||||
layout.setSpacing(0)
|
|
||||||
|
|
||||||
# Name
|
|
||||||
self.label = QLabel(label)
|
|
||||||
self.label.setFixedWidth(100)
|
|
||||||
self.label.setContentsMargins(0, 0, 10, 0)
|
|
||||||
self.label.setWordWrap(True)
|
|
||||||
layout.addWidget(self.label)
|
|
||||||
|
|
||||||
# Target
|
|
||||||
self.target_label = QLabel('-')
|
|
||||||
self.target_label.setFixedWidth(100)
|
|
||||||
layout.addWidget(self.target_label)
|
|
||||||
|
|
||||||
# Feedback
|
|
||||||
self.fb_label = QLabel('-')
|
|
||||||
self.fb_label.setFixedWidth(100)
|
|
||||||
layout.addWidget(self.fb_label)
|
|
||||||
|
|
||||||
# Status icon
|
|
||||||
self.status_icon = StatusIcon()
|
|
||||||
self.status_icon.setFixedWidth(30)
|
|
||||||
self.status_icon.setContentsMargins(0, 0, 10, 0)
|
|
||||||
layout.addWidget(self.status_icon)
|
|
||||||
|
|
||||||
# Start / Stop button
|
|
||||||
self.btn_action = QPushButton("Move")
|
|
||||||
self.btn_action.setFixedWidth(90)
|
|
||||||
self.btn_action.setFixedHeight(20)
|
|
||||||
self.btn_action.clicked.connect(self._on_button_clicked)
|
|
||||||
layout.addWidget(self.btn_action)
|
|
||||||
self.btn_mode = 'start'
|
|
||||||
|
|
||||||
self._apply_button_style("start")
|
|
||||||
|
|
||||||
self.apply_theme()
|
|
||||||
|
|
||||||
def apply_theme(self, theme=None):
|
|
||||||
if theme is None:
|
|
||||||
app = QApplication.instance()
|
|
||||||
theme = app.theme.theme # type: ignore
|
|
||||||
|
|
||||||
if theme == "light":
|
|
||||||
self.text_color = {'target': (79, 163, 224), 'fb': (240, 128, 60)}
|
|
||||||
else: # dark theme
|
|
||||||
self.text_color = {'target': (26, 111, 173), 'fb': (212, 83, 10)}
|
|
||||||
r, g, b = self.text_color['target']
|
|
||||||
self.target_label.setStyleSheet(f'QLabel {{color: rgb({r}, {g}, {b})}}')
|
|
||||||
r, g, b = self.text_color['fb']
|
|
||||||
self.fb_label.setStyleSheet(f'QLabel {{color: rgb({r}, {g}, {b})}}')
|
|
||||||
|
|
||||||
if self.btn_mode == 'start':
|
|
||||||
self.btn_action.setStyleSheet(
|
|
||||||
f"QPushButton {{background-color: {get_accent_colors().success.name()}; color: white;}}"
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
self.btn_action.setStyleSheet(
|
|
||||||
f"QPushButton {{background-color: {get_accent_colors().emergency.name()}; color: white;}}"
|
|
||||||
)
|
|
||||||
|
|
||||||
def set_target(self, target):
|
|
||||||
self.target = target
|
|
||||||
text = f'{target:.{int(self.decimals)}f}'
|
|
||||||
if self.unit is not None:
|
|
||||||
text = text + ' ' + self.unit
|
|
||||||
self.target_label.setText(text)
|
|
||||||
self._on_target_or_fb_changed()
|
|
||||||
|
|
||||||
def set_feedback(self, fb):
|
|
||||||
if self.status != Status.MOVING:
|
|
||||||
self.fb = fb
|
|
||||||
text = f'{fb:.{int(self.decimals)}f}'
|
|
||||||
if self.unit is not None:
|
|
||||||
text = text + ' ' + self.unit
|
|
||||||
self.fb_label.setText(text)
|
|
||||||
self._on_target_or_fb_changed()
|
|
||||||
|
|
||||||
def _apply_button_style(self, mode: str):
|
|
||||||
self.btn_mode = mode
|
|
||||||
if mode == "start":
|
|
||||||
self.btn_action.setText("Move")
|
|
||||||
self.btn_action.setStyleSheet(
|
|
||||||
f"QPushButton {{background-color: {get_accent_colors().success.name()}; color: white;}}"
|
|
||||||
)
|
|
||||||
else: # stop
|
|
||||||
self.btn_action.setText("Stop")
|
|
||||||
self.btn_action.setStyleSheet(
|
|
||||||
f"QPushButton {{background-color: {get_accent_colors().emergency.name()}; color: white;}}"
|
|
||||||
)
|
|
||||||
|
|
||||||
def _set_status(self, status: str):
|
|
||||||
self.status = status
|
|
||||||
self.status_icon.set_status(status)
|
|
||||||
|
|
||||||
def _on_target_or_fb_changed(self):
|
|
||||||
"""Re-evaluate in-position status whenever the target value changes."""
|
|
||||||
if self.status in (Status.ERROR, Status.MOVING):
|
|
||||||
return
|
|
||||||
if abs(self.fb - self.target) <= self.deadband:
|
|
||||||
self._set_status(Status.IN_POSITION)
|
|
||||||
else:
|
|
||||||
self._set_status(Status.NOT_IN_POSITION)
|
|
||||||
|
|
||||||
def _on_button_clicked(self):
|
|
||||||
if self._thread and self._thread.isRunning():
|
|
||||||
self._stop_motion()
|
|
||||||
else:
|
|
||||||
self._start_motion()
|
|
||||||
|
|
||||||
def _start_motion(self):
|
|
||||||
target = self.target
|
|
||||||
if abs(target - self.fb) <= self.deadband:
|
|
||||||
self._set_status(Status.IN_POSITION)
|
|
||||||
return
|
|
||||||
|
|
||||||
self._set_status(Status.MOVING)
|
|
||||||
self._apply_button_style("stop")
|
|
||||||
|
|
||||||
self._worker = MotionWorker(self.dev, self.motor, target)
|
|
||||||
self._thread = QThread()
|
|
||||||
self._worker.moveToThread(self._thread)
|
|
||||||
|
|
||||||
self._thread.started.connect(self._worker.run)
|
|
||||||
self._worker.position_changed.connect(self._on_position_changed)
|
|
||||||
self._worker.error.connect(self._on_error)
|
|
||||||
self._worker.error.connect(self._thread.quit)
|
|
||||||
self._worker.finished.connect(self._on_motion_finished)
|
|
||||||
self._worker.finished.connect(self._thread.quit)
|
|
||||||
self._thread.finished.connect(self._cleanup_thread)
|
|
||||||
|
|
||||||
self._thread.start()
|
|
||||||
|
|
||||||
def _on_error(self):
|
|
||||||
self._set_status(Status.ERROR)
|
|
||||||
self._apply_button_style("start")
|
|
||||||
|
|
||||||
def _stop_motion(self):
|
|
||||||
if self._worker:
|
|
||||||
self._worker.stop()
|
|
||||||
|
|
||||||
def _on_position_changed(self, pos: float):
|
|
||||||
self.fb = pos
|
|
||||||
text = f'{pos:.{int(self.decimals)}f}'
|
|
||||||
if self.unit is not None:
|
|
||||||
text = text + ' ' + self.unit
|
|
||||||
self.fb_label.setText(text)
|
|
||||||
|
|
||||||
def _on_motion_finished(self, reached: bool):
|
|
||||||
target = self.target
|
|
||||||
if self.status not in Status.ERROR:
|
|
||||||
if abs(self.fb - target) <= self.deadband:
|
|
||||||
self._set_status(Status.IN_POSITION)
|
|
||||||
else:
|
|
||||||
self._set_status(Status.NOT_IN_POSITION)
|
|
||||||
self._apply_button_style("start")
|
|
||||||
|
|
||||||
def _cleanup_thread(self):
|
|
||||||
if self._thread:
|
|
||||||
self._thread.deleteLater()
|
|
||||||
self._thread = None
|
|
||||||
if self._worker:
|
|
||||||
self._worker.deleteLater()
|
|
||||||
self._worker = None
|
|
||||||
|
|
||||||
def shutdown(self):
|
|
||||||
if self._worker:
|
|
||||||
self._worker.stop()
|
|
||||||
if self._thread:
|
|
||||||
self._thread.quit()
|
|
||||||
self._thread.wait(2000) # max 2 s grace period
|
|
||||||
|
|
||||||
class AbsorberWidget(QWidget):
|
|
||||||
"""
|
|
||||||
Control of the frontend absorber (only open)
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, absorber, label: str = 'Absorber'):
|
|
||||||
super().__init__()
|
|
||||||
self.absorber = absorber
|
|
||||||
self.fb = False
|
|
||||||
self.text_color = (0, 0, 0)
|
|
||||||
|
|
||||||
layout = QHBoxLayout(self)
|
|
||||||
layout.setContentsMargins(10, 0, 0, 0)
|
|
||||||
layout.setSpacing(0)
|
|
||||||
|
|
||||||
# Name
|
|
||||||
self.label = QLabel(label)
|
|
||||||
self.label.setFixedWidth(100)
|
|
||||||
self.label.setContentsMargins(0, 0, 10, 0)
|
|
||||||
self.label.setWordWrap(True)
|
|
||||||
layout.addWidget(self.label)
|
|
||||||
|
|
||||||
# Blank
|
|
||||||
self.blank_label = QLabel('')
|
|
||||||
self.blank_label.setFixedWidth(100)
|
|
||||||
layout.addWidget(self.blank_label)
|
|
||||||
|
|
||||||
# Feedback
|
|
||||||
self.fb_label = QLabel('-')
|
|
||||||
self.fb_label.setFixedWidth(100)
|
|
||||||
layout.addWidget(self.fb_label)
|
|
||||||
|
|
||||||
# Blank icon
|
|
||||||
self.blank_icon = QLabel('')
|
|
||||||
self.blank_icon.setFixedWidth(30)
|
|
||||||
self.blank_icon.setContentsMargins(0, 0, 10, 0)
|
|
||||||
layout.addWidget(self.blank_icon)
|
|
||||||
|
|
||||||
# Open
|
|
||||||
self.btn_action = QPushButton("Open")
|
|
||||||
self.btn_action.setFixedWidth(90)
|
|
||||||
self.btn_action.setFixedHeight(20)
|
|
||||||
self.btn_action.clicked.connect(self._on_button_clicked)
|
|
||||||
layout.addWidget(self.btn_action)
|
|
||||||
|
|
||||||
def set_feedback(self, fb: bool):
|
|
||||||
self.fb = fb
|
|
||||||
if fb:
|
|
||||||
self.fb_label.setText('Open')
|
|
||||||
self.fb_label.setStyleSheet(
|
|
||||||
f"QLabel {{color: {get_accent_colors().success.name()}}}"
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
self.fb_label.setText('Closed')
|
|
||||||
self.fb_label.setStyleSheet(
|
|
||||||
f"QLabel {{color: {get_accent_colors().emergency.name()}}}"
|
|
||||||
)
|
|
||||||
|
|
||||||
def enable_open(self, enable: bool = False):
|
|
||||||
if enable:
|
|
||||||
self.btn_action.setStyleSheet(
|
|
||||||
f"QPushButton {{background-color: {get_accent_colors().success.name()}; color: white;}}"
|
|
||||||
)
|
|
||||||
self.btn_action.setEnabled(True)
|
|
||||||
else: # disabled
|
|
||||||
self.btn_action.setStyleSheet(
|
|
||||||
"QPushButton {{background-color: rgb(120, 120, 120); color: white;}}"
|
|
||||||
)
|
|
||||||
self.btn_action.setDisabled(True)
|
|
||||||
|
|
||||||
def _on_button_clicked(self):
|
|
||||||
self.absorber.open()
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
def main(): # pragma: no cover
|
|
||||||
from qtpy import PYSIDE6
|
|
||||||
|
|
||||||
if not PYSIDE6:
|
|
||||||
print("PYSIDE6 is not available in the environment. Cannot patch designer.")
|
|
||||||
return
|
|
||||||
from PySide6.QtDesigner import QPyDesignerCustomWidgetCollection
|
|
||||||
|
|
||||||
from debye_bec.bec_widgets.widgets.digital_twin.digital_twin_plugin import DigitalTwinPlugin
|
|
||||||
|
|
||||||
QPyDesignerCustomWidgetCollection.addCustomWidget(DigitalTwinPlugin())
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__": # pragma: no cover
|
|
||||||
main()
|
|
||||||
@@ -1,272 +0,0 @@
|
|||||||
|
|
||||||
from functools import partial
|
|
||||||
# pylint: disable=E0611
|
|
||||||
from qtpy.QtWidgets import (
|
|
||||||
QWidget, QVBoxLayout, QHBoxLayout, QLabel, QLineEdit,
|
|
||||||
QPushButton, QGroupBox, QComboBox, QApplication, QDoubleSpinBox
|
|
||||||
)
|
|
||||||
from qtpy.QtGui import QFont
|
|
||||||
from qtpy.QtCore import Qt
|
|
||||||
|
|
||||||
from bec_widgets.utils.colors import get_accent_colors
|
|
||||||
|
|
||||||
class Group(QGroupBox):
|
|
||||||
def __init__(self, label, widgets):
|
|
||||||
super().__init__(label)
|
|
||||||
self.layout = QVBoxLayout(self) # type: ignore
|
|
||||||
for widget in widgets:
|
|
||||||
self.layout.addWidget(widget) # type: ignore
|
|
||||||
|
|
||||||
class NumberIndicator(QWidget):
|
|
||||||
def __init__(self, label='', unit=None, highlight=False, decimals=3):
|
|
||||||
super().__init__()
|
|
||||||
layout = QHBoxLayout(self)
|
|
||||||
layout.setContentsMargins(10, 0, 0, 0)
|
|
||||||
layout.setSpacing(0)
|
|
||||||
self.label = QLabel(label)
|
|
||||||
self.label.setFixedWidth(140)
|
|
||||||
self.label.setContentsMargins(0, 0, 10, 0)
|
|
||||||
self.label.setWordWrap(True)
|
|
||||||
layout.addWidget(self.label)
|
|
||||||
self.val = QLabel('-')
|
|
||||||
self.val.setAlignment(Qt.AlignTop) # type: ignore
|
|
||||||
# self.val.setFixedWidth(140)
|
|
||||||
layout.addWidget(self.val)
|
|
||||||
self.unit = unit
|
|
||||||
self.highlight = highlight
|
|
||||||
self.decimals = decimals
|
|
||||||
self.number = 0
|
|
||||||
if highlight:
|
|
||||||
font = QFont()
|
|
||||||
font.setBold(True)
|
|
||||||
font.setPointSize(14)
|
|
||||||
self.label.setFont(font)
|
|
||||||
self.val.setFont(font)
|
|
||||||
|
|
||||||
def value(self) -> float:
|
|
||||||
return self.number
|
|
||||||
|
|
||||||
def setLabel(self, label) -> None:
|
|
||||||
self.label.setText(label)
|
|
||||||
|
|
||||||
def setValue(self, number):
|
|
||||||
self.number = number
|
|
||||||
text = f'{number:.{int(self.decimals)}f}'
|
|
||||||
if self.unit is not None:
|
|
||||||
text = text + ' ' + self.unit
|
|
||||||
self.val.setText(text)
|
|
||||||
|
|
||||||
class InputNumberField(QWidget):
|
|
||||||
def __init__(self, identifier='', label='', unit=None, prefix=None, init=0.0, decimals=1, single_step=0.1, ll=-1e6, hl=1e6):
|
|
||||||
super().__init__()
|
|
||||||
layout = QHBoxLayout(self)
|
|
||||||
layout.setContentsMargins(10, 0, 0, 0)
|
|
||||||
layout.setSpacing(0)
|
|
||||||
self.identifier = identifier
|
|
||||||
self.label = QLabel(label)
|
|
||||||
self.label.setFixedWidth(140)
|
|
||||||
self.label.setContentsMargins(0, 0, 10, 0)
|
|
||||||
self.label.setWordWrap(True)
|
|
||||||
layout.addWidget(self.label)
|
|
||||||
self.val = QDoubleSpinBox()
|
|
||||||
self.val.setRange(ll, hl)
|
|
||||||
self.val.setDecimals(decimals)
|
|
||||||
self.val.setSingleStep(single_step)
|
|
||||||
self.val.setValue(init)
|
|
||||||
if unit is not None:
|
|
||||||
self.val.setSuffix(' ' + unit)
|
|
||||||
if prefix is not None:
|
|
||||||
self.val.setPrefix(prefix + ' ')
|
|
||||||
# self.val.setFixedWidth(140)
|
|
||||||
layout.addWidget(self.val)
|
|
||||||
|
|
||||||
def set_number(self, number):
|
|
||||||
self.val.setValue(number)
|
|
||||||
|
|
||||||
def has_focus(self) -> bool:
|
|
||||||
return self.val.hasFocus()
|
|
||||||
|
|
||||||
def value(self) -> float:
|
|
||||||
return self.val.value()
|
|
||||||
|
|
||||||
def value_changed_connect(self, func):
|
|
||||||
"""Connect a function to the Enter/Return key press."""
|
|
||||||
self.val.valueChanged.connect(
|
|
||||||
partial(func, identifier=self.identifier, value_obj=self.val, value=lambda: self.val.value())
|
|
||||||
)
|
|
||||||
|
|
||||||
class ComboBox(QWidget):
|
|
||||||
def __init__(self, identifier='', label='', enums=[]):
|
|
||||||
super().__init__()
|
|
||||||
layout = QHBoxLayout(self)
|
|
||||||
layout.setContentsMargins(10, 0, 0, 0)
|
|
||||||
layout.setSpacing(0)
|
|
||||||
self.identifier = identifier
|
|
||||||
self.label = QLabel(label)
|
|
||||||
self.label.setFixedWidth(140)
|
|
||||||
self.label.setContentsMargins(0, 0, 10, 0)
|
|
||||||
self.label.setWordWrap(True)
|
|
||||||
layout.addWidget(self.label)
|
|
||||||
self.value = QComboBox()
|
|
||||||
for entry in enums:
|
|
||||||
self.value.addItem(entry)
|
|
||||||
layout.addWidget(self.value)
|
|
||||||
|
|
||||||
def set_current_text(self, text):
|
|
||||||
self.value.setCurrentText(text)
|
|
||||||
|
|
||||||
def currentText(self) -> str:
|
|
||||||
return self.value.currentText()
|
|
||||||
|
|
||||||
def has_focus(self) -> bool:
|
|
||||||
return QApplication.focusWidget() is self.value.view()
|
|
||||||
|
|
||||||
def activated_connect(self, func):
|
|
||||||
"""Connect a function to the Enter/Return key press."""
|
|
||||||
self.value.activated.connect(
|
|
||||||
partial(func, identifier=self.identifier, value_obj=self.value, value=lambda: self.value.currentText())
|
|
||||||
)
|
|
||||||
|
|
||||||
def setDisabled(self, disable):
|
|
||||||
self.value.setDisabled(disable)
|
|
||||||
|
|
||||||
class Button(QWidget):
|
|
||||||
def __init__(self, label=None, label_button:str='', enabled=False):
|
|
||||||
super().__init__()
|
|
||||||
layout = QHBoxLayout(self)
|
|
||||||
layout.setContentsMargins(10, 0, 0, 0)
|
|
||||||
layout.setSpacing(0)
|
|
||||||
if label is not None:
|
|
||||||
self.label = QLabel(label)
|
|
||||||
self.label.setFixedWidth(140)
|
|
||||||
layout.addWidget(self.label)
|
|
||||||
self.button = QPushButton(label_button)
|
|
||||||
if label is not None:
|
|
||||||
self.button.setFixedWidth(160)
|
|
||||||
self.enable_button(enabled)
|
|
||||||
layout.addWidget(self.button)
|
|
||||||
|
|
||||||
def clicked_connect(self, func):
|
|
||||||
"""Connect a function to the button press."""
|
|
||||||
self.button.clicked.connect(func)
|
|
||||||
|
|
||||||
def enable_button(self, enable: bool = False):
|
|
||||||
if enable:
|
|
||||||
self.button.setStyleSheet(
|
|
||||||
f"QPushButton {{background-color: {get_accent_colors().default.name()}; color: white;}}"
|
|
||||||
)
|
|
||||||
self.button.setEnabled(True)
|
|
||||||
else: # disabled
|
|
||||||
self.button.setStyleSheet(
|
|
||||||
"QPushButton {{background-color: rgb(120, 120, 120); color: white;}}"
|
|
||||||
)
|
|
||||||
self.button.setDisabled(True)
|
|
||||||
|
|
||||||
def setText(self, text):
|
|
||||||
self.button.setText(text)
|
|
||||||
|
|
||||||
# class TextIndicator(QWidget):
|
|
||||||
# def __init__(self, label, unit=None, highlight=False):
|
|
||||||
# super().__init__()
|
|
||||||
# layout = QHBoxLayout(self)
|
|
||||||
# layout.setContentsMargins(10, 0, 0, 0)
|
|
||||||
# layout.setSpacing(0)
|
|
||||||
# self.label = QLabel(label)
|
|
||||||
# self.label.setFixedWidth(150)
|
|
||||||
# layout.addWidget(self.label)
|
|
||||||
# self.value = QLabel('-')
|
|
||||||
# self.value.setFixedWidth(160)
|
|
||||||
# layout.addWidget(self.value)
|
|
||||||
# self.unit = unit
|
|
||||||
# self.highlight = highlight
|
|
||||||
# if highlight:
|
|
||||||
# font = QFont()
|
|
||||||
# font.setBold(True)
|
|
||||||
# font.setPointSize(14)
|
|
||||||
# self.label.setFont(font)
|
|
||||||
# self.value.setFont(font)
|
|
||||||
|
|
||||||
# def set_text(self, text):
|
|
||||||
# if self.unit is not None:
|
|
||||||
# text = text + ' ' + self.unit
|
|
||||||
# self.value.setText(text)
|
|
||||||
|
|
||||||
# class Button(QWidget):
|
|
||||||
# def __init__(self, label, label_button):
|
|
||||||
# super().__init__()
|
|
||||||
# layout = QHBoxLayout(self)
|
|
||||||
# layout.setContentsMargins(10, 0, 0, 0)
|
|
||||||
# layout.setSpacing(0)
|
|
||||||
# self.label = QLabel(label)
|
|
||||||
# self.label.setFixedWidth(150)
|
|
||||||
# layout.addWidget(self.label)
|
|
||||||
# self.button = QPushButton(label_button)
|
|
||||||
# self.button.setStyleSheet("color: black; background-color: dodgerblue;")
|
|
||||||
# self.button.setFixedWidth(160)
|
|
||||||
# layout.addWidget(self.button)
|
|
||||||
|
|
||||||
# def set_on_press(self, func):
|
|
||||||
# """Connect a function to the button press."""
|
|
||||||
# self.button.clicked.connect(func)
|
|
||||||
|
|
||||||
# def enable_button(self):
|
|
||||||
# self.button.setEnabled(True)
|
|
||||||
# self.button.setStyleSheet("color: black; background-color: dodgerblue;")
|
|
||||||
|
|
||||||
# def disable_button(self):
|
|
||||||
# self.button.setEnabled(False)
|
|
||||||
# self.button.setStyleSheet("color: black; background-color: grey;")
|
|
||||||
|
|
||||||
# def set_button_text(self, text):
|
|
||||||
# self.button.setText(text)
|
|
||||||
|
|
||||||
# class LED(QWidget):
|
|
||||||
# def __init__(self, states, colors, label):
|
|
||||||
# super().__init__()
|
|
||||||
# self.states = states
|
|
||||||
# self.colors = colors
|
|
||||||
# layout = QHBoxLayout(self)
|
|
||||||
# layout.setContentsMargins(10, 0, 0, 0)
|
|
||||||
# layout.setSpacing(0)
|
|
||||||
# self.label = QLabel(label)
|
|
||||||
# self.label.setFixedWidth(150)
|
|
||||||
# layout.addWidget(self.label)
|
|
||||||
# self.led = QLabel()
|
|
||||||
# self.led.setFixedWidth(160)
|
|
||||||
# layout.addWidget(self.led)
|
|
||||||
|
|
||||||
# def apply_color(self, val):
|
|
||||||
# color = self.colors[self.states.index(val)]
|
|
||||||
# self.led.setStyleSheet(f"background-color: {color}; border: 1px solid black;")
|
|
||||||
|
|
||||||
# class InputTextField(QWidget):
|
|
||||||
# def __init__(self, topic, label):
|
|
||||||
# super().__init__()
|
|
||||||
# self.topic = topic
|
|
||||||
# layout = QHBoxLayout(self)
|
|
||||||
# layout.setContentsMargins(10, 0, 0, 0)
|
|
||||||
# layout.setSpacing(0)
|
|
||||||
# self.label = QLabel(label)
|
|
||||||
# self.label.setFixedWidth(140)
|
|
||||||
# self.label.setContentsMargins(0, 0, 10, 0)
|
|
||||||
# self.label.setWordWrap(True)
|
|
||||||
# layout.addWidget(self.label)
|
|
||||||
# self.val = QLineEdit()
|
|
||||||
# self.val.setPlaceholderText('0')
|
|
||||||
# # self.val.setFixedWidth(140)
|
|
||||||
# layout.addWidget(self.val)
|
|
||||||
|
|
||||||
# def set_text(self, text):
|
|
||||||
# self.val.setText(text)
|
|
||||||
|
|
||||||
# def has_focus(self) -> bool:
|
|
||||||
# return self.val.hasFocus()
|
|
||||||
|
|
||||||
# def text(self) -> str:
|
|
||||||
# return self.val.text()
|
|
||||||
|
|
||||||
# def set_on_return(self, func):
|
|
||||||
# """Connect a function to the Enter/Return key press."""
|
|
||||||
# self.val.returnPressed.connect(
|
|
||||||
# partial(func, self.val, self.topic, lambda: self.val.text())
|
|
||||||
# )
|
|
||||||
@@ -1,50 +0,0 @@
|
|||||||
cm_try:
|
|
||||||
offset: 0.15
|
|
||||||
|
|
||||||
mo1_trx:
|
|
||||||
modifier:
|
|
||||||
axis: mo1_trx
|
|
||||||
range: [[-30, -0.1], [0.1, 30]]
|
|
||||||
offset: [0, 2.21]
|
|
||||||
|
|
||||||
mo1_try:
|
|
||||||
modifier:
|
|
||||||
axis: mo1_trx
|
|
||||||
range: [[-30, -0.1], [0.1, 30]]
|
|
||||||
offset: [0, -1.6]
|
|
||||||
|
|
||||||
sl1_centery:
|
|
||||||
offset: -1.8
|
|
||||||
|
|
||||||
fm_trx:
|
|
||||||
modifier:
|
|
||||||
axis: fm_trx
|
|
||||||
range: [[-66, -31], [-24, 7], [11, 31], [38, 66]]
|
|
||||||
offset: [0, 0, 0, -0.16]
|
|
||||||
|
|
||||||
fm_try:
|
|
||||||
modifier:
|
|
||||||
axis: fm_trx
|
|
||||||
range: [[-66, -31], [-24, 7], [11, 31], [38, 66]]
|
|
||||||
offset: [0, 0, 0, -0.45]
|
|
||||||
|
|
||||||
fm_rotx:
|
|
||||||
modifier:
|
|
||||||
axis: fm_trx
|
|
||||||
range: [[-66, -31], [-24, 7], [11, 31], [38, 66]]
|
|
||||||
offset: [0, 0, 0, 0.063]
|
|
||||||
|
|
||||||
fm_roty:
|
|
||||||
modifier:
|
|
||||||
axis: fm_trx
|
|
||||||
range: [[-66, -31], [-24, 7], [11, 31], [38, 66]]
|
|
||||||
offset: [0, 0, 0, -0.04]
|
|
||||||
|
|
||||||
sl2_centery:
|
|
||||||
offset: 1.2
|
|
||||||
|
|
||||||
ot_try:
|
|
||||||
offset: 0
|
|
||||||
|
|
||||||
ot_rotx:
|
|
||||||
offset: 0
|
|
||||||
@@ -1,311 +0,0 @@
|
|||||||
"""
|
|
||||||
X01DA / Debye Beamline Parameters.
|
|
||||||
This file describes the parameter of each component of the Debye beamline
|
|
||||||
to be used for raytracing and geometrical calculations.
|
|
||||||
"""
|
|
||||||
|
|
||||||
import os
|
|
||||||
import numpy as np
|
|
||||||
from collections import namedtuple
|
|
||||||
|
|
||||||
import xrt.backends.raycing.materials as rm
|
|
||||||
|
|
||||||
# if os.environ.get("USE_XRT", "True").lower() in ("1", "true", "yes"):
|
|
||||||
# import xrt.backends.raycing.materials as rm # type: ignore
|
|
||||||
# else:
|
|
||||||
# class _DummyClass:
|
|
||||||
# def __init__(self, *args, **kwargs):
|
|
||||||
# pass
|
|
||||||
# class _DummyMaterials:
|
|
||||||
# Material = _DummyClass
|
|
||||||
# CrystalSi = _DummyClass
|
|
||||||
# rm = _DummyMaterials()
|
|
||||||
|
|
||||||
# XRT definitions
|
|
||||||
filterBeryl = rm.Material('Be', rho=1.85, kind='plate') # pyright: ignore[reportArgumentType]
|
|
||||||
filterDiamond = rm.Material('C', rho=3.52, kind='plate') # pyright: ignore[reportArgumentType]
|
|
||||||
filterGraphite = rm.Material('C', rho=2.266, kind='plate') # pyright: ignore[reportArgumentType]
|
|
||||||
|
|
||||||
stripeSi = rm.Material('Si', rho=2.33) # pyright: ignore[reportArgumentType]
|
|
||||||
stripePt = rm.Material('Pt', rho=21.45) # pyright: ignore[reportArgumentType]
|
|
||||||
stripeRh = rm.Material('Rh', rho=12.41) # pyright: ignore[reportArgumentType]
|
|
||||||
stripeCr = rm.Material('Cr', rho=7.14) # pyright: ignore[reportArgumentType]
|
|
||||||
stripePyrex = rm.Material('Si', rho=2.20) # Use Si as bare element and the density of SiO2 # pyright: ignore[reportArgumentType]
|
|
||||||
|
|
||||||
si111_1 = rm.CrystalSi(hkl=(1, 1, 1), tK=77) # first xtal surface
|
|
||||||
si311_1 = rm.CrystalSi(hkl=(3, 1, 1), tK=77) # first xtal surface
|
|
||||||
si333_1 = rm.CrystalSi(hkl=(3, 3, 3), tK=77) # first xtal surface
|
|
||||||
si511_1 = rm.CrystalSi(hkl=(5, 1, 1), tK=77) # first xtal surface
|
|
||||||
si111_2 = rm.CrystalSi(hkl=(1, 1, 1), tK=77) # second xtal surface
|
|
||||||
si311_2 = rm.CrystalSi(hkl=(3, 1, 1), tK=77) # second xtal surface
|
|
||||||
si333_2 = rm.CrystalSi(hkl=(3, 3, 3), tK=77) # second xtal surface
|
|
||||||
si511_2 = rm.CrystalSi(hkl=(5, 1, 1), tK=77) # second xtal surface
|
|
||||||
|
|
||||||
filterDiamond = rm.Material('C', rho=3.52, kind='plate') # pyright: ignore[reportArgumentType]
|
|
||||||
filterBe = rm.Material('Be', rho=1.85, kind='plate') # pyright: ignore[reportArgumentType]
|
|
||||||
filterSi3N4 = rm.Material(['Si', 'N'], quantities=[3, 4], rho=3.44, kind='plate') # pyright: ignore[reportArgumentType]
|
|
||||||
filterAl = rm.Material('Al', rho=2.69, kind='plate') # pyright: ignore[reportArgumentType]
|
|
||||||
filterGraphite = rm.Material('C', rho=2.266, kind='plate') # pyright: ignore[reportArgumentType]
|
|
||||||
|
|
||||||
# General parameters
|
|
||||||
sourceHeight = 0
|
|
||||||
|
|
||||||
#Synchrotron
|
|
||||||
synchrotron = namedtuple('synchrotron', ['eE', 'eI', 'eEspread',
|
|
||||||
'eEpsilonX', 'eEpsilonZ', 'betaX', 'betaZ'])
|
|
||||||
|
|
||||||
sls1 = synchrotron(
|
|
||||||
eE = 2.4,
|
|
||||||
eI = 0.4,
|
|
||||||
eEspread=0.878e-3,
|
|
||||||
eEpsilonX=5.63,
|
|
||||||
eEpsilonZ=0.007,
|
|
||||||
betaX=0.45,
|
|
||||||
betaZ=14.4,
|
|
||||||
)
|
|
||||||
|
|
||||||
sls2 = synchrotron(
|
|
||||||
eE=2.7,
|
|
||||||
eI=0.4,
|
|
||||||
eEspread=1.147e-3,
|
|
||||||
eEpsilonX=0.156,
|
|
||||||
eEpsilonZ=0.01,
|
|
||||||
betaX=0.18,
|
|
||||||
betaZ=4.6,
|
|
||||||
)
|
|
||||||
|
|
||||||
# Source
|
|
||||||
bendingMagnet = namedtuple('bendingMagnet', ['name', 'center', 'sync', 'B0'])
|
|
||||||
|
|
||||||
sls1_14t = bendingMagnet(
|
|
||||||
name='FE-BM-SLS1-1.4T',
|
|
||||||
center=(0, 0, 0),
|
|
||||||
sync=sls1,
|
|
||||||
B0=1.4,)
|
|
||||||
|
|
||||||
sls2_21t = bendingMagnet(
|
|
||||||
name='FE-BM-SLS2-2.1T',
|
|
||||||
center=(0, 0, 0),
|
|
||||||
sync=sls2,
|
|
||||||
B0=2.1,)
|
|
||||||
|
|
||||||
sls2_35t = bendingMagnet(
|
|
||||||
name='FE-BM-SLS2-3.5T',
|
|
||||||
center=(0, 0, 0),
|
|
||||||
sync=sls2,
|
|
||||||
B0=3.5,)
|
|
||||||
|
|
||||||
sls2_50t = bendingMagnet(
|
|
||||||
name='FE-BM-SLS2-5.0T',
|
|
||||||
center=(0, 0, 0),
|
|
||||||
sync=sls2,
|
|
||||||
B0=5.0,)
|
|
||||||
|
|
||||||
# FE slits
|
|
||||||
fe_slits = namedtuple('slits', ['name', 'center', 'center1', 'center2', 'maxDivH', 'maxDivV'])
|
|
||||||
|
|
||||||
feSlits = fe_slits(
|
|
||||||
name='FE-SLITS',
|
|
||||||
center=(0, 6117, sourceHeight),
|
|
||||||
center1=(0, 5045, sourceHeight),
|
|
||||||
center2=(0, 5289.5, sourceHeight),
|
|
||||||
maxDivH=1.8e-3,
|
|
||||||
maxDivV=0.8e-3,)
|
|
||||||
|
|
||||||
# FE Window
|
|
||||||
filt = namedtuple('filt', ['name', 'center', 'pitch', 'limPhysX', 'limPhysY', 'surface', 'material', 'thickness'])
|
|
||||||
|
|
||||||
feWindow = filt(
|
|
||||||
name='FE-WINDOW',
|
|
||||||
center=(0., 7020, sourceHeight),
|
|
||||||
pitch=np.pi/2,
|
|
||||||
limPhysX=(-6, 6),
|
|
||||||
limPhysY=(-3., 3.),
|
|
||||||
surface='None',
|
|
||||||
material=filterDiamond,
|
|
||||||
thickness=0.1,)
|
|
||||||
feWindow = feWindow._replace(surface=f'CVD Diamond window {feWindow.thickness*1e3:0.0f} $\\mu$m')
|
|
||||||
|
|
||||||
# Collimating mirror
|
|
||||||
collimatingMirror = namedtuple('collimatingMirror', ['name',
|
|
||||||
'center', 'surface', 'material', 'limPhysX', 'limPhysY',
|
|
||||||
'limOptX', 'limOptY', 'R', 'pitch', 'jack1', 'jack2', 'jack3',
|
|
||||||
'tx1', 'tx2'])
|
|
||||||
|
|
||||||
cm = collimatingMirror(
|
|
||||||
name='FE-CM',
|
|
||||||
center=[0, 6890, sourceHeight],
|
|
||||||
surface=('Si','Pt','Rh'),
|
|
||||||
material=(stripeSi, stripePt, stripeRh),
|
|
||||||
limPhysX=(-34, 34),
|
|
||||||
limPhysY=(-600, 600),
|
|
||||||
limOptX=((-21, -7, 14), (-11, 11, 23)),
|
|
||||||
limOptY=((-500, -500, -500), (500, 500, 500)),
|
|
||||||
R=[3e6, 15e6],
|
|
||||||
pitch=[-5.0e-3, -0.0e-3],
|
|
||||||
jack1=[0., 7210., 0.], #Tripod X, Y, Z (global)
|
|
||||||
jack2=[-210., 8310., 0.],
|
|
||||||
jack3=[210., 8310., 0.],
|
|
||||||
tx1=[0.0, -575.5], # X-Stage 1 [x, y] (local)
|
|
||||||
tx2=[0.0, 575],) # X-Stage 2
|
|
||||||
|
|
||||||
apertures = namedtuple('apertures', ['name', 'center', 'opening'])
|
|
||||||
|
|
||||||
fePS = apertures(
|
|
||||||
name='FE-PS',
|
|
||||||
center=[0, 8815, sourceHeight],
|
|
||||||
opening=[-20., 20., -20.+12.5, 20.+12.5]) # left, right, bottom, top
|
|
||||||
|
|
||||||
opWbBsBlock = apertures(
|
|
||||||
name='OP-WB-BS-BLOCK',
|
|
||||||
center=[0., 13860, sourceHeight],
|
|
||||||
opening=[-18., 18., 25, 85.5]) # left, right, bottom, top
|
|
||||||
# opening=[-18., 18., 42, 76], # X10DA
|
|
||||||
|
|
||||||
# Monochromator
|
|
||||||
monochromator = namedtuple('monochromator', ['name', 'center',
|
|
||||||
'xtal', 'material1', 'material2', 'xtalWidth', 'xtalOffsetX',
|
|
||||||
'xtalLength1', 'xtalLength2', 'xtalGap', 'rotOffset',
|
|
||||||
'heightOffset', 'braggLim', 'jack1', 'jack2', 'jack3', 'tx'])
|
|
||||||
|
|
||||||
mo1 = monochromator(
|
|
||||||
name='OP-MO1',
|
|
||||||
center=[0., 11750, sourceHeight],
|
|
||||||
xtal=('Si311','Si111'),
|
|
||||||
material1=(si311_1, si111_1),
|
|
||||||
material2=(si311_2, si111_2),
|
|
||||||
xtalWidth = (24, 24),
|
|
||||||
xtalOffsetX=(-21.2, 21.2),
|
|
||||||
xtalLength1 = (55, 55),
|
|
||||||
xtalLength2 = (105, 105),
|
|
||||||
xtalGap = (8, 8),
|
|
||||||
rotOffset = 6,
|
|
||||||
heightOffset = 8.5,
|
|
||||||
braggLim = [3.6, 33],
|
|
||||||
jack1=[0., 11350., 0.], #Tripod maybe not available!
|
|
||||||
jack2=[-400., 12350., 0.],
|
|
||||||
jack3=[400., 12350., 0.],
|
|
||||||
tx=0.0,) # X-Stage [x]
|
|
||||||
|
|
||||||
mo2 = monochromator(
|
|
||||||
name='OP-CCM2',
|
|
||||||
center=[0., 13250, sourceHeight],
|
|
||||||
xtal=('Si311','Si111'),
|
|
||||||
material1=(si311_1, si111_1),
|
|
||||||
material2=(si311_2, si111_2),
|
|
||||||
xtalWidth = (24, 24),
|
|
||||||
xtalOffsetX=(-21, 21),
|
|
||||||
xtalLength1 = (55, 55),
|
|
||||||
xtalLength2 = (105, 105),
|
|
||||||
xtalGap = (8, 8),
|
|
||||||
rotOffset = 6,
|
|
||||||
heightOffset = 8.5,
|
|
||||||
braggLim = [3.6, 33],
|
|
||||||
jack1=[0., 13350., 0.], #Tripod maybe not available!
|
|
||||||
jack2=[-400., 14350., 0.],
|
|
||||||
jack3=[400., 14350., 0.],
|
|
||||||
tx=0.0,) # X-Stage [x]
|
|
||||||
|
|
||||||
# OP Slits
|
|
||||||
op_slits = namedtuple('op_slits', ['name', 'center'])
|
|
||||||
|
|
||||||
opSlits1 = op_slits(
|
|
||||||
name='OP-SLITS 1',
|
|
||||||
center=(0, 14349.6, sourceHeight),
|
|
||||||
)
|
|
||||||
|
|
||||||
opSlits2 = op_slits(
|
|
||||||
name='OP-SLITS 2',
|
|
||||||
center=(0, 18134.8, sourceHeight),
|
|
||||||
)
|
|
||||||
|
|
||||||
# OP Beam Monitors
|
|
||||||
op_bm = namedtuple('op_bm', ['name', 'center'])
|
|
||||||
|
|
||||||
opBM1 = op_bm(
|
|
||||||
name='OP Beam Monitor 1',
|
|
||||||
center=(0, 14599.6, sourceHeight),
|
|
||||||
)
|
|
||||||
|
|
||||||
opBM2 = op_bm(
|
|
||||||
name='OP Beam Monitor 2',
|
|
||||||
center=(0, 18384.8, sourceHeight),
|
|
||||||
)
|
|
||||||
|
|
||||||
# Focusing mirror
|
|
||||||
focusingMirror = namedtuple('focusingMirror', ['name', 'center',
|
|
||||||
'surfaceToroid', 'materialToroid', 'surfaceFlat', 'materialFlat',
|
|
||||||
'limPhysXToroid', 'limPhysYToroid', 'limPhysXFlat', 'limPhysYFlat',
|
|
||||||
'limOptXToroid', 'limOptYToroid', 'limOptXFlat', 'limOptYFlat',
|
|
||||||
'R', 'pitch', 'r', 'xToroid', 'xFlat', 'hToroid', 'jack1', 'jack2', 'jack3',
|
|
||||||
'tx1', 'tx2'])
|
|
||||||
|
|
||||||
fm = focusingMirror(
|
|
||||||
name='OP-FM',
|
|
||||||
center=[0., 15670, sourceHeight], # nominal height 58 mm above ring, SLS1!
|
|
||||||
surfaceToroid=('Rh', 'Pt'),
|
|
||||||
materialToroid=(stripeRh, stripePt),
|
|
||||||
surfaceFlat=('Rh', 'Pt'),
|
|
||||||
materialFlat=(stripeRh, stripePt),
|
|
||||||
limPhysXToroid=(-79., 79.),
|
|
||||||
limPhysYToroid=(-575., 575.),
|
|
||||||
limPhysXFlat=(-79., 79.),
|
|
||||||
limPhysYFlat=(-575., 575.),
|
|
||||||
limOptXToroid=((-38, 66), (-66, 31)),
|
|
||||||
limOptYToroid=((-500., -500.), (500., 500.)),
|
|
||||||
limOptXFlat=((-11.45, 23.55), (-30.45, -6.45)),
|
|
||||||
limOptYFlat=((-500., -500.), (500., 500.)),
|
|
||||||
R=[3e6, 15e6],
|
|
||||||
pitch=[-5.0e-3, 0e-3],
|
|
||||||
r=[35.510, 24.986],
|
|
||||||
xToroid=[-52, 48.5], # offset in local x
|
|
||||||
xFlat = [-20.95, 8.55],
|
|
||||||
hToroid=[2.88, 7.15], # depth of the cylinder at x = xCylinder1 and x = xCylinder2.
|
|
||||||
jack1=[-130., 15535-538., 0.],
|
|
||||||
jack2=[130., 15535+538., 0.],
|
|
||||||
jack3=[0., 15535+538., 0.],
|
|
||||||
tx1=[0., -575.], # X-Stage 1 [x, y]
|
|
||||||
tx2=[0., 575.],) # X-Stage 2 [x, y]
|
|
||||||
|
|
||||||
# EH Window
|
|
||||||
ehWindow = filt(
|
|
||||||
name='EH-WINDOW',
|
|
||||||
center=(0., 19998.3, sourceHeight),
|
|
||||||
pitch=np.pi/2,
|
|
||||||
limPhysX=(-20., 20.),
|
|
||||||
limPhysY=(-4, 4),
|
|
||||||
surface='None',
|
|
||||||
material=filterSi3N4,
|
|
||||||
thickness=0.002,)
|
|
||||||
ehWindow = ehWindow._replace(surface=f'Beryllium window {ehWindow.thickness*1e3:0.0f} $\\mu$m')
|
|
||||||
|
|
||||||
# Sample
|
|
||||||
sample = namedtuple('sample', ['name', 'center'])
|
|
||||||
|
|
||||||
smpl = sample(
|
|
||||||
name='EH-SMPL',
|
|
||||||
center=[0, 23365, sourceHeight],)
|
|
||||||
|
|
||||||
smpl2 = sample(
|
|
||||||
name='EH-SMPL2',
|
|
||||||
center=[0, 27500, sourceHeight],)
|
|
||||||
|
|
||||||
# Vacuum pipes
|
|
||||||
# DN40CF ID = 35 mm oder 37 mm
|
|
||||||
# DN50CF ID = 47.5 mm
|
|
||||||
# DN63CF ID = 60.2 mm oder 66 mm
|
|
||||||
# DN100CF ID = 97.4 mm oder 104 mm
|
|
||||||
pipe = namedtuple('pipes', ['center', 'diameter', 'start', 'end'])
|
|
||||||
vacuum_pipes = pipe(
|
|
||||||
center= [27.5, (37.5+27.5)/2, 37.5, 62.5, 72.5],
|
|
||||||
diameter=[97.4, 97.4, 97.4, 97.4, 97.4],
|
|
||||||
start= [10952.88, 11750+250, mo2.center[1]+250, 14000, fm.center[1]],
|
|
||||||
end= [11750-250, mo2.center[1]-250, 14000, fm.center[1], ehWindow.center[1]],
|
|
||||||
)
|
|
||||||
|
|
||||||
Walls = namedtuple('walls', ['start', 'end', 'height'])
|
|
||||||
walls = Walls(
|
|
||||||
start= [13999.30],
|
|
||||||
end= [13999+75.5+30],
|
|
||||||
height= [[-20, 25]],
|
|
||||||
)
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
import os
|
|
||||||
|
|
||||||
|
|
||||||
def setup_epics_ca():
|
|
||||||
# os.environ["EPICS_CA_AUTO_ADDR_LIST"] = "NO"
|
|
||||||
# os.environ["EPICS_CA_ADDR_LIST"] = "129.129.122.255 sls-x12sa-cagw.psi.ch:5836"
|
|
||||||
os.environ["PYTHONIOENCODING"] = "latin1"
|
|
||||||
|
|
||||||
|
|
||||||
def run():
|
|
||||||
setup_epics_ca()
|
|
||||||
@@ -1,34 +0,0 @@
|
|||||||
|
|
||||||
###################################
|
|
||||||
## 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,449 +0,0 @@
|
|||||||
###################################
|
|
||||||
## Optical Table ##
|
|
||||||
###################################
|
|
||||||
|
|
||||||
ot_tryu:
|
|
||||||
readoutPriority: baseline
|
|
||||||
description: Optical Table Y-Translation Upstream
|
|
||||||
deviceClass: ophyd_devices.EpicsMotorEC
|
|
||||||
deviceConfig:
|
|
||||||
prefix: X01DA-ES-OT:TRYU
|
|
||||||
onFailure: retry
|
|
||||||
enabled: true
|
|
||||||
softwareTrigger: false
|
|
||||||
|
|
||||||
ot_tryd:
|
|
||||||
readoutPriority: baseline
|
|
||||||
description: Optical Table Y-Translation Downstream
|
|
||||||
deviceClass: ophyd_devices.EpicsMotorEC
|
|
||||||
deviceConfig:
|
|
||||||
prefix: X01DA-ES-OT:TRYD
|
|
||||||
onFailure: retry
|
|
||||||
enabled: true
|
|
||||||
softwareTrigger: false
|
|
||||||
|
|
||||||
ot_es1_trz:
|
|
||||||
readoutPriority: baseline
|
|
||||||
description: Optical Table ES1 Z-Translation
|
|
||||||
deviceClass: ophyd_devices.EpicsMotorEC
|
|
||||||
deviceConfig:
|
|
||||||
prefix: X01DA-ES1-OT:TRZ
|
|
||||||
onFailure: retry
|
|
||||||
enabled: true
|
|
||||||
softwareTrigger: false
|
|
||||||
|
|
||||||
ot_es2_trz:
|
|
||||||
readoutPriority: baseline
|
|
||||||
description: Optical Table ES2 Z-Translation
|
|
||||||
deviceClass: ophyd_devices.EpicsMotorEC
|
|
||||||
deviceConfig:
|
|
||||||
prefix: X01DA-ES2-OT:TRZ
|
|
||||||
onFailure: retry
|
|
||||||
enabled: true
|
|
||||||
softwareTrigger: false
|
|
||||||
|
|
||||||
ot_try:
|
|
||||||
readoutPriority: baseline
|
|
||||||
description: Optical Table Y-Translation
|
|
||||||
deviceClass: ophyd_devices.EpicsMotorEC
|
|
||||||
deviceConfig:
|
|
||||||
prefix: X01DA-ES-OT:TRY
|
|
||||||
onFailure: retry
|
|
||||||
enabled: true
|
|
||||||
softwareTrigger: false
|
|
||||||
|
|
||||||
ot_rotx:
|
|
||||||
readoutPriority: baseline
|
|
||||||
description: Optical Table Pitch
|
|
||||||
deviceClass: ophyd_devices.EpicsMotorEC
|
|
||||||
deviceConfig:
|
|
||||||
prefix: X01DA-ES-OT:ROTX
|
|
||||||
onFailure: retry
|
|
||||||
enabled: true
|
|
||||||
softwareTrigger: false
|
|
||||||
|
|
||||||
###################################
|
|
||||||
## Exit Window ##
|
|
||||||
###################################
|
|
||||||
|
|
||||||
es0wi_try:
|
|
||||||
readoutPriority: baseline
|
|
||||||
description: End Station 0 Exit Window Y-translation
|
|
||||||
deviceClass: ophyd_devices.EpicsMotorEC
|
|
||||||
deviceConfig:
|
|
||||||
prefix: X01DA-ES0-WI:TRY
|
|
||||||
onFailure: retry
|
|
||||||
enabled: true
|
|
||||||
softwareTrigger: false
|
|
||||||
|
|
||||||
###################################
|
|
||||||
## ES0 Filter ##
|
|
||||||
###################################
|
|
||||||
|
|
||||||
es0filter:
|
|
||||||
readoutPriority: baseline
|
|
||||||
description: ES0 filter station
|
|
||||||
deviceClass: debye_bec.devices.es0filter.ES0Filter
|
|
||||||
deviceConfig:
|
|
||||||
prefix: "X01DA-ES0-FI:"
|
|
||||||
onFailure: retry
|
|
||||||
enabled: true
|
|
||||||
softwareTrigger: false
|
|
||||||
|
|
||||||
###################################
|
|
||||||
## Slits ES0 ##
|
|
||||||
###################################
|
|
||||||
|
|
||||||
es0sl_trxr:
|
|
||||||
readoutPriority: baseline
|
|
||||||
description: End Station slits X-translation Ring-edge
|
|
||||||
deviceClass: ophyd_devices.EpicsMotorEC
|
|
||||||
deviceConfig:
|
|
||||||
prefix: X01DA-ES0-SL:TRXR
|
|
||||||
onFailure: retry
|
|
||||||
enabled: true
|
|
||||||
softwareTrigger: false
|
|
||||||
|
|
||||||
es0sl_trxw:
|
|
||||||
readoutPriority: baseline
|
|
||||||
description: End Station slits X-translation Wall-edge
|
|
||||||
deviceClass: ophyd_devices.EpicsMotorEC
|
|
||||||
deviceConfig:
|
|
||||||
prefix: X01DA-ES0-SL:TRXW
|
|
||||||
onFailure: retry
|
|
||||||
enabled: true
|
|
||||||
softwareTrigger: false
|
|
||||||
|
|
||||||
es0sl_tryb:
|
|
||||||
readoutPriority: baseline
|
|
||||||
description: End Station slits Y-translation Bottom-edge
|
|
||||||
deviceClass: ophyd_devices.EpicsMotorEC
|
|
||||||
deviceConfig:
|
|
||||||
prefix: X01DA-ES0-SL:TRYB
|
|
||||||
onFailure: retry
|
|
||||||
enabled: true
|
|
||||||
softwareTrigger: false
|
|
||||||
|
|
||||||
es0sl_tryt:
|
|
||||||
readoutPriority: baseline
|
|
||||||
description: End Station slits X-translation Top-edge
|
|
||||||
deviceClass: ophyd_devices.EpicsMotorEC
|
|
||||||
deviceConfig:
|
|
||||||
prefix: X01DA-ES0-SL:TRYT
|
|
||||||
onFailure: retry
|
|
||||||
enabled: true
|
|
||||||
softwareTrigger: false
|
|
||||||
|
|
||||||
es0sl_center:
|
|
||||||
readoutPriority: baseline
|
|
||||||
description: End Station slits X-center
|
|
||||||
deviceClass: ophyd_devices.EpicsMotorEC
|
|
||||||
deviceConfig:
|
|
||||||
prefix: X01DA-ES0-SL:CENTERX
|
|
||||||
onFailure: retry
|
|
||||||
enabled: true
|
|
||||||
softwareTrigger: false
|
|
||||||
|
|
||||||
es0sl_gapx:
|
|
||||||
readoutPriority: baseline
|
|
||||||
description: End Station slits X-gap
|
|
||||||
deviceClass: ophyd_devices.EpicsMotorEC
|
|
||||||
deviceConfig:
|
|
||||||
prefix: X01DA-ES0-SL:GAPX
|
|
||||||
onFailure: retry
|
|
||||||
enabled: true
|
|
||||||
softwareTrigger: false
|
|
||||||
|
|
||||||
es0sl_centery:
|
|
||||||
readoutPriority: baseline
|
|
||||||
description: End Station slits Y-center
|
|
||||||
deviceClass: ophyd_devices.EpicsMotorEC
|
|
||||||
deviceConfig:
|
|
||||||
prefix: X01DA-ES0-SL:CENTERY
|
|
||||||
onFailure: retry
|
|
||||||
enabled: true
|
|
||||||
softwareTrigger: false
|
|
||||||
|
|
||||||
es0sl_gapy:
|
|
||||||
readoutPriority: baseline
|
|
||||||
description: End Station slits Y-gap
|
|
||||||
deviceClass: ophyd_devices.EpicsMotorEC
|
|
||||||
deviceConfig:
|
|
||||||
prefix: X01DA-ES0-SL:GAPY
|
|
||||||
onFailure: retry
|
|
||||||
enabled: true
|
|
||||||
softwareTrigger: false
|
|
||||||
|
|
||||||
###################################
|
|
||||||
## Alignment Laser ##
|
|
||||||
###################################
|
|
||||||
|
|
||||||
es1_alignment_laser:
|
|
||||||
readoutPriority: baseline
|
|
||||||
description: ES1 alignment laser
|
|
||||||
deviceClass: ophyd.EpicsSignal
|
|
||||||
deviceConfig:
|
|
||||||
read_pv: "X01DA-ES1-LAS:Relay"
|
|
||||||
onFailure: retry
|
|
||||||
enabled: true
|
|
||||||
softwareTrigger: false
|
|
||||||
|
|
||||||
###################################
|
|
||||||
## Sample Manipulator ##
|
|
||||||
###################################
|
|
||||||
|
|
||||||
es1man_trx:
|
|
||||||
readoutPriority: baseline
|
|
||||||
description: End Station sample manipulator X-translation
|
|
||||||
deviceClass: ophyd_devices.EpicsMotorEC
|
|
||||||
deviceConfig:
|
|
||||||
prefix: X01DA-ES1-MAN1:TRX
|
|
||||||
onFailure: retry
|
|
||||||
enabled: true
|
|
||||||
softwareTrigger: false
|
|
||||||
|
|
||||||
es1man_try:
|
|
||||||
readoutPriority: baseline
|
|
||||||
description: End Station sample manipulator Y-translation
|
|
||||||
deviceClass: ophyd_devices.EpicsMotorEC
|
|
||||||
deviceConfig:
|
|
||||||
prefix: X01DA-ES1-MAN1:TRY
|
|
||||||
onFailure: retry
|
|
||||||
enabled: true
|
|
||||||
softwareTrigger: false
|
|
||||||
|
|
||||||
es1man_trz:
|
|
||||||
readoutPriority: baseline
|
|
||||||
description: End Station sample manipulator Z-translation
|
|
||||||
deviceClass: ophyd_devices.EpicsMotorEC
|
|
||||||
deviceConfig:
|
|
||||||
prefix: X01DA-ES1-MAN1:TRZ
|
|
||||||
onFailure: retry
|
|
||||||
enabled: true
|
|
||||||
softwareTrigger: false
|
|
||||||
|
|
||||||
es1man_roty:
|
|
||||||
readoutPriority: baseline
|
|
||||||
description: End Station sample manipulator Y-rotation
|
|
||||||
deviceClass: ophyd_devices.EpicsMotorEC
|
|
||||||
deviceConfig:
|
|
||||||
prefix: X01DA-ES1-MAN1:ROTY
|
|
||||||
onFailure: retry
|
|
||||||
enabled: true
|
|
||||||
softwareTrigger: false
|
|
||||||
|
|
||||||
###################################
|
|
||||||
## Segmented Arc ##
|
|
||||||
###################################
|
|
||||||
|
|
||||||
es1arc_roty:
|
|
||||||
readoutPriority: baseline
|
|
||||||
description: End Station segmented arc Y-rotation
|
|
||||||
deviceClass: ophyd_devices.EpicsMotorEC
|
|
||||||
deviceConfig:
|
|
||||||
prefix: X01DA-ES1-ARC:ROTY
|
|
||||||
onFailure: retry
|
|
||||||
enabled: true
|
|
||||||
softwareTrigger: false
|
|
||||||
|
|
||||||
es1det1_trx:
|
|
||||||
readoutPriority: baseline
|
|
||||||
description: End Station SDD 1 X-translation
|
|
||||||
deviceClass: ophyd_devices.EpicsMotorEC
|
|
||||||
deviceConfig:
|
|
||||||
prefix: X01DA-ES1-DET1:TRX
|
|
||||||
onFailure: retry
|
|
||||||
enabled: true
|
|
||||||
softwareTrigger: false
|
|
||||||
|
|
||||||
es1bm1_trx:
|
|
||||||
readoutPriority: baseline
|
|
||||||
description: End Station X-ray Eye X-translation
|
|
||||||
deviceClass: ophyd_devices.EpicsMotorEC
|
|
||||||
deviceConfig:
|
|
||||||
prefix: X01DA-ES1-BM1:TRX
|
|
||||||
onFailure: retry
|
|
||||||
enabled: true
|
|
||||||
softwareTrigger: false
|
|
||||||
|
|
||||||
es1det2_trx:
|
|
||||||
readoutPriority: baseline
|
|
||||||
description: End Station SDD 2 X-translation
|
|
||||||
deviceClass: ophyd_devices.EpicsMotorEC
|
|
||||||
deviceConfig:
|
|
||||||
prefix: X01DA-ES1-DET2:TRX
|
|
||||||
onFailure: retry
|
|
||||||
enabled: true
|
|
||||||
softwareTrigger: false
|
|
||||||
|
|
||||||
###################################
|
|
||||||
## IC1 + IC2 Manipulator ##
|
|
||||||
###################################
|
|
||||||
|
|
||||||
es2ma2_try:
|
|
||||||
readoutPriority: baseline
|
|
||||||
description: End Station ionization chamber 1+2 Y-translation
|
|
||||||
deviceClass: ophyd_devices.EpicsMotorEC
|
|
||||||
deviceConfig:
|
|
||||||
prefix: X01DA-ES2-MA2:TRY
|
|
||||||
onFailure: retry
|
|
||||||
enabled: true
|
|
||||||
softwareTrigger: false
|
|
||||||
|
|
||||||
es2ma2_trz:
|
|
||||||
readoutPriority: baseline
|
|
||||||
description: End Station ionization chamber 1+2 Z-translation
|
|
||||||
deviceClass: ophyd_devices.EpicsMotorEC
|
|
||||||
deviceConfig:
|
|
||||||
prefix: X01DA-ES2-MA2:TRZ
|
|
||||||
onFailure: retry
|
|
||||||
enabled: true
|
|
||||||
softwareTrigger: false
|
|
||||||
|
|
||||||
###################################
|
|
||||||
## XRD Detector Manipulator ##
|
|
||||||
###################################
|
|
||||||
|
|
||||||
es2ma3_try:
|
|
||||||
readoutPriority: baseline
|
|
||||||
description: End Station XRD detector Y-translation
|
|
||||||
deviceClass: ophyd_devices.EpicsMotorEC
|
|
||||||
deviceConfig:
|
|
||||||
prefix: X01DA-ES2-MA3:TRY
|
|
||||||
onFailure: retry
|
|
||||||
enabled: true
|
|
||||||
softwareTrigger: false
|
|
||||||
|
|
||||||
###################################
|
|
||||||
## Hutch Env. Sensors + Light ##
|
|
||||||
###################################
|
|
||||||
|
|
||||||
es_temperature1:
|
|
||||||
readoutPriority: baseline
|
|
||||||
description: ES temperature sensor 1
|
|
||||||
deviceClass: ophyd.EpicsSignalRO
|
|
||||||
deviceConfig:
|
|
||||||
read_pv: "X01DA-PC-I2C:_CH1:TEMP"
|
|
||||||
onFailure: retry
|
|
||||||
enabled: true
|
|
||||||
softwareTrigger: false
|
|
||||||
|
|
||||||
es_humidity1:
|
|
||||||
readoutPriority: baseline
|
|
||||||
description: ES humidity sensor 1
|
|
||||||
deviceClass: ophyd.EpicsSignalRO
|
|
||||||
deviceConfig:
|
|
||||||
read_pv: "X01DA-PC-I2C:_CH1:HUMIREL"
|
|
||||||
onFailure: retry
|
|
||||||
enabled: true
|
|
||||||
softwareTrigger: false
|
|
||||||
|
|
||||||
es_pressure1:
|
|
||||||
readoutPriority: baseline
|
|
||||||
description: ES ambient pressure sensor 1
|
|
||||||
deviceClass: ophyd.EpicsSignalRO
|
|
||||||
deviceConfig:
|
|
||||||
read_pv: "X01DA-PC-I2C:_CH1:PRES"
|
|
||||||
onFailure: retry
|
|
||||||
enabled: true
|
|
||||||
softwareTrigger: false
|
|
||||||
|
|
||||||
es_temperature2:
|
|
||||||
readoutPriority: baseline
|
|
||||||
description: ES temperature sensor 2
|
|
||||||
deviceClass: ophyd.EpicsSignalRO
|
|
||||||
deviceConfig:
|
|
||||||
read_pv: "X01DA-PC-I2C:_CH2:TEMP"
|
|
||||||
onFailure: retry
|
|
||||||
enabled: true
|
|
||||||
softwareTrigger: false
|
|
||||||
|
|
||||||
es_humidity2:
|
|
||||||
readoutPriority: baseline
|
|
||||||
description: ES humidity sensor 2
|
|
||||||
deviceClass: ophyd.EpicsSignalRO
|
|
||||||
deviceConfig:
|
|
||||||
read_pv: "X01DA-PC-I2C:_CH2:HUMIREL"
|
|
||||||
onFailure: retry
|
|
||||||
enabled: true
|
|
||||||
softwareTrigger: false
|
|
||||||
|
|
||||||
es_pressure2:
|
|
||||||
readoutPriority: baseline
|
|
||||||
description: ES ambient pressure sensor 2
|
|
||||||
deviceClass: ophyd.EpicsSignalRO
|
|
||||||
deviceConfig:
|
|
||||||
read_pv: "X01DA-PC-I2C:_CH2:PRES"
|
|
||||||
onFailure: retry
|
|
||||||
enabled: true
|
|
||||||
softwareTrigger: false
|
|
||||||
|
|
||||||
es_light_toggle:
|
|
||||||
readoutPriority: baseline
|
|
||||||
description: ES light toggle
|
|
||||||
deviceClass: ophyd.EpicsSignal
|
|
||||||
deviceConfig:
|
|
||||||
read_pv: "X01DA-EH-LIGHT:TOGGLE"
|
|
||||||
onFailure: retry
|
|
||||||
enabled: true
|
|
||||||
softwareTrigger: false
|
|
||||||
|
|
||||||
es_gas_sensor_o2:
|
|
||||||
readoutPriority: baseline
|
|
||||||
description: ES Gas Sensor O2
|
|
||||||
deviceClass: ophyd.EpicsSignalRO
|
|
||||||
deviceConfig:
|
|
||||||
read_pv: "X01DA-KIMESSA2:EH-O2"
|
|
||||||
onFailure: retry
|
|
||||||
enabled: true
|
|
||||||
softwareTrigger: false
|
|
||||||
|
|
||||||
es_gas_sensor_h2s:
|
|
||||||
readoutPriority: baseline
|
|
||||||
description: ES Gas Sensor H2S
|
|
||||||
deviceClass: ophyd.EpicsSignalRO
|
|
||||||
deviceConfig:
|
|
||||||
read_pv: "X01DA-KIMESSA2:EH-H2S"
|
|
||||||
onFailure: retry
|
|
||||||
enabled: true
|
|
||||||
softwareTrigger: false
|
|
||||||
|
|
||||||
es_gas_sensor_no2:
|
|
||||||
readoutPriority: baseline
|
|
||||||
description: ES Gas Sensor NO2
|
|
||||||
deviceClass: ophyd.EpicsSignalRO
|
|
||||||
deviceConfig:
|
|
||||||
read_pv: "X01DA-KIMESSA2:EH-NO2"
|
|
||||||
onFailure: retry
|
|
||||||
enabled: true
|
|
||||||
softwareTrigger: false
|
|
||||||
|
|
||||||
es_gas_sensor_co:
|
|
||||||
readoutPriority: baseline
|
|
||||||
description: ES Gas Sensor CO
|
|
||||||
deviceClass: ophyd.EpicsSignalRO
|
|
||||||
deviceConfig:
|
|
||||||
read_pv: "X01DA-KIMESSA2:EH-CO"
|
|
||||||
onFailure: retry
|
|
||||||
enabled: true
|
|
||||||
softwareTrigger: false
|
|
||||||
|
|
||||||
es_gas_sensor_h2:
|
|
||||||
readoutPriority: baseline
|
|
||||||
description: ES Gas Sensor H2
|
|
||||||
deviceClass: ophyd.EpicsSignalRO
|
|
||||||
deviceConfig:
|
|
||||||
read_pv: "X01DA-KIMESSA2:EH-H2"
|
|
||||||
onFailure: retry
|
|
||||||
enabled: true
|
|
||||||
softwareTrigger: false
|
|
||||||
|
|
||||||
es_gas_sensor_nh3:
|
|
||||||
readoutPriority: baseline
|
|
||||||
description: ES Gas Sensor NH3
|
|
||||||
deviceClass: ophyd.EpicsSignalRO
|
|
||||||
deviceConfig:
|
|
||||||
read_pv: "X01DA-KIMESSA2:EH-NH3"
|
|
||||||
onFailure: retry
|
|
||||||
enabled: true
|
|
||||||
softwareTrigger: false
|
|
||||||
@@ -1,243 +0,0 @@
|
|||||||
|
|
||||||
###################################
|
|
||||||
## Frontend Absorber ##
|
|
||||||
###################################
|
|
||||||
|
|
||||||
abs:
|
|
||||||
readoutPriority: baseline
|
|
||||||
description: Frontend Absorber
|
|
||||||
deviceClass: debye_bec.devices.absorber.Absorber
|
|
||||||
deviceConfig:
|
|
||||||
prefix: "X01DA-FE-ABS1:"
|
|
||||||
onFailure: retry
|
|
||||||
enabled: true
|
|
||||||
softwareTrigger: false
|
|
||||||
|
|
||||||
###################################
|
|
||||||
## Frontend Slits ##
|
|
||||||
###################################
|
|
||||||
|
|
||||||
sldi_trxr:
|
|
||||||
readoutPriority: baseline
|
|
||||||
description: Front-end slit diaphragm X-translation Ring-edge
|
|
||||||
deviceClass: ophyd_devices.EpicsMotorEC
|
|
||||||
deviceConfig:
|
|
||||||
prefix: X01DA-FE-SLDI:TRXR
|
|
||||||
onFailure: retry
|
|
||||||
enabled: true
|
|
||||||
softwareTrigger: false
|
|
||||||
|
|
||||||
sldi_trxw:
|
|
||||||
readoutPriority: baseline
|
|
||||||
description: Front-end slit diaphragm X-translation Wall-edge
|
|
||||||
deviceClass: ophyd_devices.EpicsMotorEC
|
|
||||||
deviceConfig:
|
|
||||||
prefix: X01DA-FE-SLDI:TRXW
|
|
||||||
onFailure: retry
|
|
||||||
enabled: true
|
|
||||||
softwareTrigger: false
|
|
||||||
|
|
||||||
sldi_tryb:
|
|
||||||
readoutPriority: baseline
|
|
||||||
description: Front-end slit diaphragm Y-translation Bottom-edge
|
|
||||||
deviceClass: ophyd_devices.EpicsMotorEC
|
|
||||||
deviceConfig:
|
|
||||||
prefix: X01DA-FE-SLDI:TRYB
|
|
||||||
onFailure: retry
|
|
||||||
enabled: true
|
|
||||||
softwareTrigger: false
|
|
||||||
|
|
||||||
sldi_tryt:
|
|
||||||
readoutPriority: baseline
|
|
||||||
description: Front-end slit diaphragm X-translation Top-edge
|
|
||||||
deviceClass: ophyd_devices.EpicsMotorEC
|
|
||||||
deviceConfig:
|
|
||||||
prefix: X01DA-FE-SLDI:TRYT
|
|
||||||
onFailure: retry
|
|
||||||
enabled: true
|
|
||||||
softwareTrigger: false
|
|
||||||
|
|
||||||
sldi_centerx:
|
|
||||||
readoutPriority: baseline
|
|
||||||
description: Front-end slit diaphragm X-center
|
|
||||||
deviceClass: ophyd_devices.EpicsMotorEC
|
|
||||||
deviceConfig:
|
|
||||||
prefix: X01DA-FE-SLDI:CENTERX
|
|
||||||
onFailure: retry
|
|
||||||
enabled: true
|
|
||||||
softwareTrigger: false
|
|
||||||
|
|
||||||
sldi_gapx:
|
|
||||||
readoutPriority: baseline
|
|
||||||
description: Front-end slit diaphragm X-gap
|
|
||||||
deviceClass: ophyd_devices.EpicsMotorEC
|
|
||||||
deviceConfig:
|
|
||||||
prefix: X01DA-FE-SLDI:GAPX
|
|
||||||
onFailure: retry
|
|
||||||
enabled: true
|
|
||||||
softwareTrigger: false
|
|
||||||
|
|
||||||
sldi_centery:
|
|
||||||
readoutPriority: baseline
|
|
||||||
description: Front-end slit diaphragm Y-center
|
|
||||||
deviceClass: ophyd_devices.EpicsMotorEC
|
|
||||||
deviceConfig:
|
|
||||||
prefix: X01DA-FE-SLDI:CENTERY
|
|
||||||
onFailure: retry
|
|
||||||
enabled: true
|
|
||||||
softwareTrigger: false
|
|
||||||
|
|
||||||
sldi_gapy:
|
|
||||||
readoutPriority: baseline
|
|
||||||
description: Front-end slit diaphragm Y-gap
|
|
||||||
deviceClass: ophyd_devices.EpicsMotorEC
|
|
||||||
deviceConfig:
|
|
||||||
prefix: X01DA-FE-SLDI:GAPY
|
|
||||||
onFailure: retry
|
|
||||||
enabled: true
|
|
||||||
softwareTrigger: false
|
|
||||||
|
|
||||||
###################################
|
|
||||||
## Collimating Mirror ##
|
|
||||||
###################################
|
|
||||||
|
|
||||||
cm_trxu:
|
|
||||||
readoutPriority: baseline
|
|
||||||
description: Collimating Mirror X-translation upstream
|
|
||||||
deviceClass: ophyd_devices.EpicsMotorEC
|
|
||||||
deviceConfig:
|
|
||||||
prefix: X01DA-FE-CM:TRXU
|
|
||||||
onFailure: retry
|
|
||||||
enabled: true
|
|
||||||
softwareTrigger: false
|
|
||||||
|
|
||||||
cm_trxd:
|
|
||||||
readoutPriority: baseline
|
|
||||||
description: Collimating Mirror X-translation downstream
|
|
||||||
deviceClass: ophyd_devices.EpicsMotorEC
|
|
||||||
deviceConfig:
|
|
||||||
prefix: X01DA-FE-CM:TRXD
|
|
||||||
onFailure: retry
|
|
||||||
enabled: true
|
|
||||||
softwareTrigger: false
|
|
||||||
|
|
||||||
cm_tryu:
|
|
||||||
readoutPriority: baseline
|
|
||||||
description: Collimating Mirror Y-translation upstream
|
|
||||||
deviceClass: ophyd_devices.EpicsMotorEC
|
|
||||||
deviceConfig:
|
|
||||||
prefix: X01DA-FE-CM:TRYU
|
|
||||||
onFailure: retry
|
|
||||||
enabled: true
|
|
||||||
softwareTrigger: false
|
|
||||||
|
|
||||||
cm_trydr:
|
|
||||||
readoutPriority: baseline
|
|
||||||
description: Collimating Mirror Y-translation downstream ring
|
|
||||||
deviceClass: ophyd_devices.EpicsMotorEC
|
|
||||||
deviceConfig:
|
|
||||||
prefix: X01DA-FE-CM:TRYDR
|
|
||||||
onFailure: retry
|
|
||||||
enabled: true
|
|
||||||
softwareTrigger: false
|
|
||||||
|
|
||||||
cm_trydw:
|
|
||||||
readoutPriority: baseline
|
|
||||||
description: Collimating Mirror Y-translation downstream wall
|
|
||||||
deviceClass: ophyd_devices.EpicsMotorEC
|
|
||||||
deviceConfig:
|
|
||||||
prefix: X01DA-FE-CM:TRYDW
|
|
||||||
onFailure: retry
|
|
||||||
enabled: true
|
|
||||||
softwareTrigger: false
|
|
||||||
|
|
||||||
cm_bnd:
|
|
||||||
readoutPriority: baseline
|
|
||||||
description: Collimating Mirror bender
|
|
||||||
deviceClass: ophyd_devices.EpicsMotorEC
|
|
||||||
deviceConfig:
|
|
||||||
prefix: X01DA-FE-CM:BND
|
|
||||||
onFailure: retry
|
|
||||||
enabled: true
|
|
||||||
softwareTrigger: false
|
|
||||||
|
|
||||||
cm_bnd_radius:
|
|
||||||
readoutPriority: baseline
|
|
||||||
description: Collimating Mirror Bending Radius
|
|
||||||
deviceClass: ophyd.EpicsSignalRO
|
|
||||||
deviceConfig:
|
|
||||||
read_pv: X01DA-CPCL-CM:BNDFORCE
|
|
||||||
onFailure: retry
|
|
||||||
readOnly: true
|
|
||||||
enabled: true
|
|
||||||
softwareTrigger: false
|
|
||||||
|
|
||||||
cm_rotx:
|
|
||||||
readoutPriority: baseline
|
|
||||||
description: Collimating Morror Pitch
|
|
||||||
deviceClass: ophyd_devices.EpicsMotorEC
|
|
||||||
deviceConfig:
|
|
||||||
prefix: X01DA-FE-CM:ROTX
|
|
||||||
onFailure: retry
|
|
||||||
enabled: true
|
|
||||||
softwareTrigger: false
|
|
||||||
|
|
||||||
cm_roty:
|
|
||||||
readoutPriority: baseline
|
|
||||||
description: Collimating Morror Yaw
|
|
||||||
deviceClass: ophyd_devices.EpicsMotorEC
|
|
||||||
deviceConfig:
|
|
||||||
prefix: X01DA-FE-CM:ROTY
|
|
||||||
onFailure: retry
|
|
||||||
enabled: true
|
|
||||||
softwareTrigger: false
|
|
||||||
|
|
||||||
cm_rotz:
|
|
||||||
readoutPriority: baseline
|
|
||||||
description: Collimating Morror Roll
|
|
||||||
deviceClass: ophyd_devices.EpicsMotorEC
|
|
||||||
deviceConfig:
|
|
||||||
prefix: X01DA-FE-CM:ROTZ
|
|
||||||
onFailure: retry
|
|
||||||
enabled: true
|
|
||||||
softwareTrigger: false
|
|
||||||
|
|
||||||
cm_trx:
|
|
||||||
readoutPriority: baseline
|
|
||||||
description: Collimating Morror Center Point X
|
|
||||||
deviceClass: ophyd_devices.EpicsMotorEC
|
|
||||||
deviceConfig:
|
|
||||||
prefix: X01DA-FE-CM:XTCP
|
|
||||||
onFailure: retry
|
|
||||||
enabled: true
|
|
||||||
softwareTrigger: false
|
|
||||||
|
|
||||||
cm_try:
|
|
||||||
readoutPriority: baseline
|
|
||||||
description: Collimating Morror Center Point Y
|
|
||||||
deviceClass: ophyd_devices.EpicsMotorEC
|
|
||||||
deviceConfig:
|
|
||||||
prefix: X01DA-FE-CM:YTCP
|
|
||||||
onFailure: retry
|
|
||||||
enabled: true
|
|
||||||
softwareTrigger: false
|
|
||||||
|
|
||||||
cm_ztcp:
|
|
||||||
readoutPriority: baseline
|
|
||||||
description: Collimating Morror Center Point Z
|
|
||||||
deviceClass: ophyd_devices.EpicsMotorEC
|
|
||||||
deviceConfig:
|
|
||||||
prefix: X01DA-FE-CM:ZTCP
|
|
||||||
onFailure: retry
|
|
||||||
enabled: true
|
|
||||||
softwareTrigger: false
|
|
||||||
|
|
||||||
cm_xstripe:
|
|
||||||
readoutPriority: baseline
|
|
||||||
description: Collimating Morror X Stripe
|
|
||||||
deviceClass: ophyd_devices.EpicsMotorEC
|
|
||||||
deviceConfig:
|
|
||||||
prefix: X01DA-FE-CM:XSTRIPE
|
|
||||||
onFailure: retry
|
|
||||||
enabled: true
|
|
||||||
softwareTrigger: false
|
|
||||||
@@ -1,34 +0,0 @@
|
|||||||
|
|
||||||
###################################
|
|
||||||
## Hutch Cameras ##
|
|
||||||
###################################
|
|
||||||
|
|
||||||
hutch_cam_1:
|
|
||||||
readoutPriority: baseline
|
|
||||||
description: Hutch Camera 1
|
|
||||||
deviceClass: debye_bec.devices.cameras.hutch_cam.HutchCam
|
|
||||||
deviceConfig:
|
|
||||||
prefix: "pcp085420"
|
|
||||||
onFailure: retry
|
|
||||||
enabled: true
|
|
||||||
softwareTrigger: false
|
|
||||||
|
|
||||||
hutch_cam_2:
|
|
||||||
readoutPriority: baseline
|
|
||||||
description: Hutch Camera 2
|
|
||||||
deviceClass: debye_bec.devices.cameras.hutch_cam.HutchCam
|
|
||||||
deviceConfig:
|
|
||||||
prefix: "pcp085436"
|
|
||||||
onFailure: retry
|
|
||||||
enabled: true
|
|
||||||
softwareTrigger: false
|
|
||||||
|
|
||||||
hutch_cam_3:
|
|
||||||
readoutPriority: baseline
|
|
||||||
description: Hutch Camera 3
|
|
||||||
deviceClass: debye_bec.devices.cameras.hutch_cam.HutchCam
|
|
||||||
deviceConfig:
|
|
||||||
prefix: "pcp085435"
|
|
||||||
onFailure: retry
|
|
||||||
enabled: true
|
|
||||||
softwareTrigger: false
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
|
|
||||||
###################################
|
|
||||||
## 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
|
|
||||||
@@ -1,411 +0,0 @@
|
|||||||
|
|
||||||
###################################
|
|
||||||
## Monochromator ##
|
|
||||||
###################################
|
|
||||||
|
|
||||||
mo1_try:
|
|
||||||
readoutPriority: baseline
|
|
||||||
description: Monochromator Y Translation
|
|
||||||
deviceClass: ophyd_devices.EpicsMotorEC
|
|
||||||
deviceConfig:
|
|
||||||
prefix: X01DA-OP-MO1:TRY
|
|
||||||
onFailure: retry
|
|
||||||
enabled: true
|
|
||||||
softwareTrigger: false
|
|
||||||
|
|
||||||
mo1_trx:
|
|
||||||
readoutPriority: baseline
|
|
||||||
description: Monochromator X Translation
|
|
||||||
deviceClass: ophyd_devices.EpicsMotorEC
|
|
||||||
deviceConfig:
|
|
||||||
prefix: X01DA-OP-MO1:TRX
|
|
||||||
onFailure: retry
|
|
||||||
enabled: true
|
|
||||||
softwareTrigger: false
|
|
||||||
|
|
||||||
mo1_roty:
|
|
||||||
readoutPriority: baseline
|
|
||||||
description: Monochromator Yaw
|
|
||||||
deviceClass: ophyd_devices.EpicsMotorEC
|
|
||||||
deviceConfig:
|
|
||||||
prefix: X01DA-OP-MO1:ROTY
|
|
||||||
onFailure: retry
|
|
||||||
enabled: true
|
|
||||||
softwareTrigger: false
|
|
||||||
|
|
||||||
###################################
|
|
||||||
## Optics Slits + Beam Monitor 1 ##
|
|
||||||
###################################
|
|
||||||
|
|
||||||
sl1_trxr:
|
|
||||||
readoutPriority: baseline
|
|
||||||
description: Optics slits 1 X-translation Ring-edge
|
|
||||||
deviceClass: ophyd_devices.EpicsMotorEC
|
|
||||||
deviceConfig:
|
|
||||||
prefix: X01DA-OP-SL1:TRXR
|
|
||||||
onFailure: retry
|
|
||||||
enabled: true
|
|
||||||
softwareTrigger: false
|
|
||||||
deviceTags:
|
|
||||||
- optics
|
|
||||||
- slits
|
|
||||||
|
|
||||||
sl1_trxw:
|
|
||||||
readoutPriority: baseline
|
|
||||||
description: Optics slits 1 X-translation Wall-edge
|
|
||||||
deviceClass: ophyd_devices.EpicsMotorEC
|
|
||||||
deviceConfig:
|
|
||||||
prefix: X01DA-OP-SL1:TRXW
|
|
||||||
onFailure: retry
|
|
||||||
enabled: true
|
|
||||||
softwareTrigger: false
|
|
||||||
deviceTags:
|
|
||||||
- optics
|
|
||||||
- slits
|
|
||||||
|
|
||||||
sl1_tryb:
|
|
||||||
readoutPriority: baseline
|
|
||||||
description: Optics slits 1 Y-translation Bottom-edge
|
|
||||||
deviceClass: ophyd_devices.EpicsMotorEC
|
|
||||||
deviceConfig:
|
|
||||||
prefix: X01DA-OP-SL1:TRYB
|
|
||||||
onFailure: retry
|
|
||||||
enabled: true
|
|
||||||
softwareTrigger: false
|
|
||||||
deviceTags:
|
|
||||||
- optics
|
|
||||||
- slits
|
|
||||||
|
|
||||||
sl1_tryt:
|
|
||||||
readoutPriority: baseline
|
|
||||||
description: Optics slits 1 X-translation Top-edge
|
|
||||||
deviceClass: ophyd_devices.EpicsMotorEC
|
|
||||||
deviceConfig:
|
|
||||||
prefix: X01DA-OP-SL1:TRYT
|
|
||||||
onFailure: retry
|
|
||||||
enabled: true
|
|
||||||
softwareTrigger: false
|
|
||||||
deviceTags:
|
|
||||||
- optics
|
|
||||||
- slits
|
|
||||||
|
|
||||||
bm1_try:
|
|
||||||
readoutPriority: baseline
|
|
||||||
description: Beam Monitor 1 Y-translation
|
|
||||||
deviceClass: ophyd_devices.EpicsMotorEC
|
|
||||||
deviceConfig:
|
|
||||||
prefix: X01DA-OP-BM1:TRY
|
|
||||||
onFailure: retry
|
|
||||||
enabled: true
|
|
||||||
softwareTrigger: false
|
|
||||||
deviceTags:
|
|
||||||
- optics
|
|
||||||
- slits
|
|
||||||
|
|
||||||
sl1_centerx:
|
|
||||||
readoutPriority: baseline
|
|
||||||
description: Optics slits 1 X-center
|
|
||||||
deviceClass: ophyd_devices.EpicsMotorEC
|
|
||||||
deviceConfig:
|
|
||||||
prefix: X01DA-OP-SL1:CENTERX
|
|
||||||
onFailure: retry
|
|
||||||
enabled: true
|
|
||||||
softwareTrigger: false
|
|
||||||
deviceTags:
|
|
||||||
- optics
|
|
||||||
- slits
|
|
||||||
|
|
||||||
sl1_gapx:
|
|
||||||
readoutPriority: baseline
|
|
||||||
description: Optics slits 1 X-gap
|
|
||||||
deviceClass: ophyd_devices.EpicsMotorEC
|
|
||||||
deviceConfig:
|
|
||||||
prefix: X01DA-OP-SL1:GAPX
|
|
||||||
onFailure: retry
|
|
||||||
enabled: true
|
|
||||||
softwareTrigger: false
|
|
||||||
deviceTags:
|
|
||||||
- optics
|
|
||||||
- slits
|
|
||||||
|
|
||||||
sl1_centery:
|
|
||||||
readoutPriority: baseline
|
|
||||||
description: Optics slits 1 Y-center
|
|
||||||
deviceClass: ophyd_devices.EpicsMotorEC
|
|
||||||
deviceConfig:
|
|
||||||
prefix: X01DA-OP-SL1:CENTERY
|
|
||||||
onFailure: retry
|
|
||||||
enabled: true
|
|
||||||
softwareTrigger: false
|
|
||||||
deviceTags:
|
|
||||||
- optics
|
|
||||||
- slits
|
|
||||||
|
|
||||||
sl1_gapy:
|
|
||||||
readoutPriority: baseline
|
|
||||||
description: Optics slits 1 Y-gap
|
|
||||||
deviceClass: ophyd_devices.EpicsMotorEC
|
|
||||||
deviceConfig:
|
|
||||||
prefix: X01DA-OP-SL1:GAPY
|
|
||||||
onFailure: retry
|
|
||||||
enabled: true
|
|
||||||
softwareTrigger: false
|
|
||||||
deviceTags:
|
|
||||||
- optics
|
|
||||||
- slits
|
|
||||||
|
|
||||||
###################################
|
|
||||||
## Focusing Mirror ##
|
|
||||||
###################################
|
|
||||||
|
|
||||||
fm_trxu:
|
|
||||||
readoutPriority: baseline
|
|
||||||
description: Focusing Mirror X-translation upstream
|
|
||||||
deviceClass: ophyd_devices.EpicsMotorEC
|
|
||||||
deviceConfig:
|
|
||||||
prefix: X01DA-OP-FM:TRXU
|
|
||||||
onFailure: retry
|
|
||||||
enabled: true
|
|
||||||
softwareTrigger: false
|
|
||||||
|
|
||||||
fm_trxd:
|
|
||||||
readoutPriority: baseline
|
|
||||||
description: Focusing Mirror X-translation downstream
|
|
||||||
deviceClass: ophyd_devices.EpicsMotorEC
|
|
||||||
deviceConfig:
|
|
||||||
prefix: X01DA-OP-FM:TRXD
|
|
||||||
onFailure: retry
|
|
||||||
enabled: true
|
|
||||||
softwareTrigger: false
|
|
||||||
|
|
||||||
fm_tryd:
|
|
||||||
readoutPriority: baseline
|
|
||||||
description: Focusing Mirror Y-translation downstream
|
|
||||||
deviceClass: ophyd_devices.EpicsMotorEC
|
|
||||||
deviceConfig:
|
|
||||||
prefix: X01DA-OP-FM:TRYD
|
|
||||||
onFailure: retry
|
|
||||||
enabled: true
|
|
||||||
softwareTrigger: false
|
|
||||||
|
|
||||||
fm_tryur:
|
|
||||||
readoutPriority: baseline
|
|
||||||
description: Focusing Mirror Y-translation upstream ring
|
|
||||||
deviceClass: ophyd_devices.EpicsMotorEC
|
|
||||||
deviceConfig:
|
|
||||||
prefix: X01DA-OP-FM:TRYUR
|
|
||||||
onFailure: retry
|
|
||||||
enabled: true
|
|
||||||
softwareTrigger: false
|
|
||||||
|
|
||||||
fm_tryuw:
|
|
||||||
readoutPriority: baseline
|
|
||||||
description: Focusing Mirror Y-translation upstream wall
|
|
||||||
deviceClass: ophyd_devices.EpicsMotorEC
|
|
||||||
deviceConfig:
|
|
||||||
prefix: X01DA-OP-FM:TRYUW
|
|
||||||
onFailure: retry
|
|
||||||
enabled: true
|
|
||||||
softwareTrigger: false
|
|
||||||
|
|
||||||
fm_bnd:
|
|
||||||
readoutPriority: baseline
|
|
||||||
description: Focusing Mirror bender
|
|
||||||
deviceClass: ophyd_devices.EpicsMotorEC
|
|
||||||
deviceConfig:
|
|
||||||
prefix: X01DA-OP-FM:BND
|
|
||||||
onFailure: retry
|
|
||||||
enabled: true
|
|
||||||
softwareTrigger: false
|
|
||||||
|
|
||||||
fm_bnd_radius:
|
|
||||||
readoutPriority: baseline
|
|
||||||
description: Focusing Mirror Bending Radius
|
|
||||||
deviceClass: ophyd.EpicsSignalRO
|
|
||||||
deviceConfig:
|
|
||||||
read_pv: X01DA-CPCL-FM:BNDFORCE
|
|
||||||
onFailure: retry
|
|
||||||
readOnly: true
|
|
||||||
enabled: true
|
|
||||||
softwareTrigger: false
|
|
||||||
|
|
||||||
fm_rotx:
|
|
||||||
readoutPriority: baseline
|
|
||||||
description: Focusing Morror Pitch
|
|
||||||
deviceClass: ophyd_devices.EpicsMotorEC
|
|
||||||
deviceConfig:
|
|
||||||
prefix: X01DA-OP-FM:ROTX
|
|
||||||
onFailure: retry
|
|
||||||
enabled: true
|
|
||||||
softwareTrigger: false
|
|
||||||
|
|
||||||
fm_roty:
|
|
||||||
readoutPriority: baseline
|
|
||||||
description: Focusing Morror Yaw
|
|
||||||
deviceClass: ophyd_devices.EpicsMotorEC
|
|
||||||
deviceConfig:
|
|
||||||
prefix: X01DA-OP-FM:ROTY
|
|
||||||
onFailure: retry
|
|
||||||
enabled: true
|
|
||||||
softwareTrigger: false
|
|
||||||
|
|
||||||
fm_rotz:
|
|
||||||
readoutPriority: baseline
|
|
||||||
description: Focusing Morror Roll
|
|
||||||
deviceClass: ophyd_devices.EpicsMotorEC
|
|
||||||
deviceConfig:
|
|
||||||
prefix: X01DA-OP-FM:ROTZ
|
|
||||||
onFailure: retry
|
|
||||||
enabled: true
|
|
||||||
softwareTrigger: false
|
|
||||||
|
|
||||||
fm_trx:
|
|
||||||
readoutPriority: baseline
|
|
||||||
description: Focusing Morror Center Point X
|
|
||||||
deviceClass: ophyd_devices.EpicsMotorEC
|
|
||||||
deviceConfig:
|
|
||||||
prefix: X01DA-OP-FM:XTCP
|
|
||||||
onFailure: retry
|
|
||||||
enabled: true
|
|
||||||
softwareTrigger: false
|
|
||||||
|
|
||||||
fm_try:
|
|
||||||
readoutPriority: baseline
|
|
||||||
description: Focusing Morror Center Point Y
|
|
||||||
deviceClass: ophyd_devices.EpicsMotorEC
|
|
||||||
deviceConfig:
|
|
||||||
prefix: X01DA-OP-FM:YTCP
|
|
||||||
onFailure: retry
|
|
||||||
enabled: true
|
|
||||||
softwareTrigger: false
|
|
||||||
|
|
||||||
fm_ztcp:
|
|
||||||
readoutPriority: baseline
|
|
||||||
description: Focusing Morror Center Point Z
|
|
||||||
deviceClass: ophyd_devices.EpicsMotorEC
|
|
||||||
deviceConfig:
|
|
||||||
prefix: X01DA-OP-FM:ZTCP
|
|
||||||
onFailure: retry
|
|
||||||
enabled: true
|
|
||||||
softwareTrigger: false
|
|
||||||
|
|
||||||
###################################
|
|
||||||
## Optics Slits + Beam Monitor 2 ##
|
|
||||||
###################################
|
|
||||||
|
|
||||||
sl2_trxr:
|
|
||||||
readoutPriority: baseline
|
|
||||||
description: Optics slits 2 X-translation Ring-edge
|
|
||||||
deviceClass: ophyd_devices.EpicsMotorEC
|
|
||||||
deviceConfig:
|
|
||||||
prefix: X01DA-OP-SL2:TRXR
|
|
||||||
onFailure: retry
|
|
||||||
enabled: true
|
|
||||||
softwareTrigger: false
|
|
||||||
deviceTags:
|
|
||||||
- optics
|
|
||||||
- slits
|
|
||||||
|
|
||||||
sl2_trxw:
|
|
||||||
readoutPriority: baseline
|
|
||||||
description: Optics slits 2 X-translation Wall-edge
|
|
||||||
deviceClass: ophyd_devices.EpicsMotorEC
|
|
||||||
deviceConfig:
|
|
||||||
prefix: X01DA-OP-SL2:TRXW
|
|
||||||
onFailure: retry
|
|
||||||
enabled: true
|
|
||||||
softwareTrigger: false
|
|
||||||
deviceTags:
|
|
||||||
- optics
|
|
||||||
- slits
|
|
||||||
|
|
||||||
sl2_tryb:
|
|
||||||
readoutPriority: baseline
|
|
||||||
description: Optics slits 2 Y-translation Bottom-edge
|
|
||||||
deviceClass: ophyd_devices.EpicsMotorEC
|
|
||||||
deviceConfig:
|
|
||||||
prefix: X01DA-OP-SL2:TRYB
|
|
||||||
onFailure: retry
|
|
||||||
enabled: true
|
|
||||||
softwareTrigger: false
|
|
||||||
deviceTags:
|
|
||||||
- optics
|
|
||||||
- slits
|
|
||||||
|
|
||||||
sl2_tryt:
|
|
||||||
readoutPriority: baseline
|
|
||||||
description: Optics slits 2 X-translation Top-edge
|
|
||||||
deviceClass: ophyd_devices.EpicsMotorEC
|
|
||||||
deviceConfig:
|
|
||||||
prefix: X01DA-OP-SL2:TRYT
|
|
||||||
onFailure: retry
|
|
||||||
enabled: true
|
|
||||||
softwareTrigger: false
|
|
||||||
deviceTags:
|
|
||||||
- optics
|
|
||||||
- slits
|
|
||||||
|
|
||||||
bm2_try:
|
|
||||||
readoutPriority: baseline
|
|
||||||
description: Beam Monitor 2 Y-translation
|
|
||||||
deviceClass: ophyd_devices.EpicsMotorEC
|
|
||||||
deviceConfig:
|
|
||||||
prefix: X01DA-OP-BM2:TRY
|
|
||||||
onFailure: retry
|
|
||||||
enabled: true
|
|
||||||
softwareTrigger: false
|
|
||||||
deviceTags:
|
|
||||||
- optics
|
|
||||||
- slits
|
|
||||||
|
|
||||||
sl2_centerx:
|
|
||||||
readoutPriority: baseline
|
|
||||||
description: Optics slits 2 X-center
|
|
||||||
deviceClass: ophyd_devices.EpicsMotorEC
|
|
||||||
deviceConfig:
|
|
||||||
prefix: X01DA-OP-SL2:CENTERX
|
|
||||||
onFailure: retry
|
|
||||||
enabled: true
|
|
||||||
softwareTrigger: false
|
|
||||||
deviceTags:
|
|
||||||
- optics
|
|
||||||
- slits
|
|
||||||
|
|
||||||
sl2_gapx:
|
|
||||||
readoutPriority: baseline
|
|
||||||
description: Optics slits 2 X-gap
|
|
||||||
deviceClass: ophyd_devices.EpicsMotorEC
|
|
||||||
deviceConfig:
|
|
||||||
prefix: X01DA-OP-SL2:GAPX
|
|
||||||
onFailure: retry
|
|
||||||
enabled: true
|
|
||||||
softwareTrigger: false
|
|
||||||
deviceTags:
|
|
||||||
- optics
|
|
||||||
- slits
|
|
||||||
|
|
||||||
sl2_centery:
|
|
||||||
readoutPriority: baseline
|
|
||||||
description: Optics slits 2 Y-center
|
|
||||||
deviceClass: ophyd_devices.EpicsMotorEC
|
|
||||||
deviceConfig:
|
|
||||||
prefix: X01DA-OP-SL2:CENTERY
|
|
||||||
onFailure: retry
|
|
||||||
enabled: true
|
|
||||||
softwareTrigger: false
|
|
||||||
deviceTags:
|
|
||||||
- optics
|
|
||||||
- slits
|
|
||||||
|
|
||||||
sl2_gapy:
|
|
||||||
readoutPriority: baseline
|
|
||||||
description: Optics slits 2 Y-gap
|
|
||||||
deviceClass: ophyd_devices.EpicsMotorEC
|
|
||||||
deviceConfig:
|
|
||||||
prefix: X01DA-OP-SL2:GAPY
|
|
||||||
onFailure: retry
|
|
||||||
enabled: true
|
|
||||||
softwareTrigger: false
|
|
||||||
deviceTags:
|
|
||||||
- optics
|
|
||||||
- slits
|
|
||||||
@@ -1,80 +0,0 @@
|
|||||||
|
|
||||||
###################################
|
|
||||||
## General ##
|
|
||||||
###################################
|
|
||||||
|
|
||||||
## SLS Machine
|
|
||||||
machine_config:
|
|
||||||
- !include ./x01da_machine.yaml
|
|
||||||
|
|
||||||
## Beam Monitors OP + EH
|
|
||||||
beam_monitors_config:
|
|
||||||
- !include ./x01da_beam_monitors.yaml
|
|
||||||
|
|
||||||
###################################
|
|
||||||
## Frontend ##
|
|
||||||
###################################
|
|
||||||
|
|
||||||
## Frontend
|
|
||||||
frontend_config:
|
|
||||||
- !include ./x01da_frontend.yaml
|
|
||||||
|
|
||||||
###################################
|
|
||||||
## Optics Hutch ##
|
|
||||||
###################################
|
|
||||||
|
|
||||||
## Bragg Monochromator
|
|
||||||
mo1_bragg:
|
|
||||||
readoutPriority: baseline
|
|
||||||
description: Positioner for the Monochromator
|
|
||||||
deviceClass: debye_bec.devices.mo1_bragg.mo1_bragg.Mo1Bragg
|
|
||||||
deviceConfig:
|
|
||||||
prefix: "X01DA-OP-MO1:BRAGG:"
|
|
||||||
onFailure: retry
|
|
||||||
enabled: true
|
|
||||||
softwareTrigger: false
|
|
||||||
mo1_bragg_angle:
|
|
||||||
readoutPriority: baseline
|
|
||||||
description: Positioner for the Monochromator
|
|
||||||
deviceClass: debye_bec.devices.mo1_bragg.mo1_bragg_angle.Mo1BraggAngle
|
|
||||||
deviceConfig:
|
|
||||||
prefix: "X01DA-OP-MO1:BRAGG:"
|
|
||||||
onFailure: retry
|
|
||||||
enabled: true
|
|
||||||
softwareTrigger: false
|
|
||||||
|
|
||||||
## Remaining optics hutch
|
|
||||||
optics_config:
|
|
||||||
- !include ./x01da_optics.yaml
|
|
||||||
|
|
||||||
###################################
|
|
||||||
## Experimental Hutch ##
|
|
||||||
###################################
|
|
||||||
|
|
||||||
# ## NIDAQ
|
|
||||||
nidaq:
|
|
||||||
readoutPriority: monitored
|
|
||||||
description: NIDAQ backend for data reading for debye scans
|
|
||||||
deviceClass: debye_bec.devices.nidaq.nidaq.Nidaq
|
|
||||||
deviceConfig:
|
|
||||||
prefix: "X01DA-PC-SCANSERVER:"
|
|
||||||
onFailure: retry
|
|
||||||
enabled: true
|
|
||||||
softwareTrigger: false
|
|
||||||
|
|
||||||
## XAS (ICx, SDD, ref foils)
|
|
||||||
xas_config:
|
|
||||||
- !include ./x01da_xas.yaml
|
|
||||||
|
|
||||||
## XRD (Pilatus, pinhole, beamstop)
|
|
||||||
#xrd_config:
|
|
||||||
# - !include ./x01da_xrd.yaml
|
|
||||||
|
|
||||||
# Commented out because too slow
|
|
||||||
## Hutch cameras
|
|
||||||
# hutch_cams:
|
|
||||||
# - !include ./x01da_hutch_cameras.yaml
|
|
||||||
|
|
||||||
## Remaining experimental hutch
|
|
||||||
es_config:
|
|
||||||
- !include ./x01da_experimental_hutch.yaml
|
|
||||||
@@ -1,83 +0,0 @@
|
|||||||
|
|
||||||
###################################
|
|
||||||
## Ionization Chambers ##
|
|
||||||
###################################
|
|
||||||
|
|
||||||
ic0:
|
|
||||||
readoutPriority: baseline
|
|
||||||
description: Ionization chamber 0
|
|
||||||
deviceClass: debye_bec.devices.ionization_chambers.ionization_chamber.IonizationChamber0
|
|
||||||
deviceConfig:
|
|
||||||
prefix: "X01DA-"
|
|
||||||
onFailure: retry
|
|
||||||
enabled: true
|
|
||||||
softwareTrigger: false
|
|
||||||
|
|
||||||
ic1:
|
|
||||||
readoutPriority: baseline
|
|
||||||
description: Ionization chamber 1
|
|
||||||
deviceClass: debye_bec.devices.ionization_chambers.ionization_chamber.IonizationChamber1
|
|
||||||
deviceConfig:
|
|
||||||
prefix: "X01DA-"
|
|
||||||
onFailure: retry
|
|
||||||
enabled: true
|
|
||||||
softwareTrigger: false
|
|
||||||
|
|
||||||
ic2:
|
|
||||||
readoutPriority: baseline
|
|
||||||
description: Ionization chamber 2
|
|
||||||
deviceClass: debye_bec.devices.ionization_chambers.ionization_chamber.IonizationChamber2
|
|
||||||
deviceConfig:
|
|
||||||
prefix: "X01DA-"
|
|
||||||
onFailure: retry
|
|
||||||
enabled: true
|
|
||||||
softwareTrigger: false
|
|
||||||
|
|
||||||
pips:
|
|
||||||
readoutPriority: baseline
|
|
||||||
description: Pips diode
|
|
||||||
deviceClass: debye_bec.devices.ionization_chambers.ionization_chamber.Pips
|
|
||||||
deviceConfig:
|
|
||||||
prefix: "X01DA-"
|
|
||||||
onFailure: retry
|
|
||||||
enabled: true
|
|
||||||
softwareTrigger: false
|
|
||||||
|
|
||||||
###################################
|
|
||||||
## Reference Foil Changer ##
|
|
||||||
###################################
|
|
||||||
|
|
||||||
reffoilchanger:
|
|
||||||
readoutPriority: baseline
|
|
||||||
description: ES2 reference foil changer
|
|
||||||
deviceClass: debye_bec.devices.reffoilchanger.Reffoilchanger
|
|
||||||
deviceConfig:
|
|
||||||
prefix: "X01DA-"
|
|
||||||
onFailure: retry
|
|
||||||
enabled: true
|
|
||||||
softwareTrigger: false
|
|
||||||
|
|
||||||
###################################
|
|
||||||
## SDD Sensors ##
|
|
||||||
###################################
|
|
||||||
|
|
||||||
sdd1_temperature:
|
|
||||||
readoutPriority: baseline
|
|
||||||
description: SDD1 temperature sensor
|
|
||||||
deviceClass: ophyd.EpicsSignalRO
|
|
||||||
deviceConfig:
|
|
||||||
read_pv: "X01DA-ES1-DET1:Temperature"
|
|
||||||
onFailure: retry
|
|
||||||
enabled: true
|
|
||||||
softwareTrigger: false
|
|
||||||
|
|
||||||
sdd1_humidity:
|
|
||||||
readoutPriority: baseline
|
|
||||||
description: SDD1 humidity sensor
|
|
||||||
deviceClass: ophyd.EpicsSignalRO
|
|
||||||
deviceConfig:
|
|
||||||
read_pv: "X01DA-ES1-DET1:Humidity"
|
|
||||||
kind: "config"
|
|
||||||
onFailure: retry
|
|
||||||
enabled: true
|
|
||||||
softwareTrigger: false
|
|
||||||
@@ -1,108 +0,0 @@
|
|||||||
|
|
||||||
###################################
|
|
||||||
## Pinhole ##
|
|
||||||
###################################
|
|
||||||
|
|
||||||
pin1_trx:
|
|
||||||
readoutPriority: baseline
|
|
||||||
description: Pinhole X-translation
|
|
||||||
deviceClass: ophyd_devices.EpicsMotorEC
|
|
||||||
deviceConfig:
|
|
||||||
prefix: X01DA-ES1-PIN1:TRX
|
|
||||||
onFailure: retry
|
|
||||||
enabled: true
|
|
||||||
softwareTrigger: false
|
|
||||||
tags: Endstation
|
|
||||||
|
|
||||||
pin1_try:
|
|
||||||
readoutPriority: baseline
|
|
||||||
description: Pinhole Y-translation
|
|
||||||
deviceClass: ophyd_devices.EpicsMotorEC
|
|
||||||
deviceConfig:
|
|
||||||
prefix: X01DA-ES1-PIN1:TRY
|
|
||||||
onFailure: retry
|
|
||||||
enabled: true
|
|
||||||
softwareTrigger: false
|
|
||||||
tags: Endstation
|
|
||||||
|
|
||||||
pin1_rotx:
|
|
||||||
readoutPriority: baseline
|
|
||||||
description: Pinhole X-rotation
|
|
||||||
deviceClass: ophyd_devices.EpicsMotorEC
|
|
||||||
deviceConfig:
|
|
||||||
prefix: X01DA-ES1-PIN1:ROTX
|
|
||||||
onFailure: retry
|
|
||||||
enabled: true
|
|
||||||
softwareTrigger: false
|
|
||||||
tags: Endstation
|
|
||||||
|
|
||||||
pin1_roty:
|
|
||||||
readoutPriority: baseline
|
|
||||||
description: Pinhole Y-rotation
|
|
||||||
deviceClass: ophyd_devices.EpicsMotorEC
|
|
||||||
deviceConfig:
|
|
||||||
prefix: X01DA-ES1-PIN1:ROTY
|
|
||||||
onFailure: retry
|
|
||||||
enabled: true
|
|
||||||
softwareTrigger: false
|
|
||||||
tags: Endstation
|
|
||||||
|
|
||||||
###################################
|
|
||||||
## Beam Stop ##
|
|
||||||
###################################
|
|
||||||
|
|
||||||
es2bs_trx:
|
|
||||||
readoutPriority: baseline
|
|
||||||
description: End Station beamstop X-translation
|
|
||||||
deviceClass: ophyd_devices.EpicsMotorEC
|
|
||||||
deviceConfig:
|
|
||||||
prefix: X01DA-ES2-BS:TRX
|
|
||||||
onFailure: retry
|
|
||||||
enabled: true
|
|
||||||
softwareTrigger: false
|
|
||||||
|
|
||||||
es2bs_try:
|
|
||||||
readoutPriority: baseline
|
|
||||||
description: End Station beamstop Y-translation
|
|
||||||
deviceClass: ophyd_devices.EpicsMotorEC
|
|
||||||
deviceConfig:
|
|
||||||
prefix: X01DA-ES2-BS:TRY
|
|
||||||
onFailure: retry
|
|
||||||
enabled: true
|
|
||||||
softwareTrigger: false
|
|
||||||
|
|
||||||
###################################
|
|
||||||
## Pilatus ##
|
|
||||||
###################################
|
|
||||||
|
|
||||||
pilatus_curtain:
|
|
||||||
readoutPriority: baseline
|
|
||||||
description: Pilatus Curtain
|
|
||||||
deviceClass: debye_bec.devices.pilatus_curtain.PilatusCurtain
|
|
||||||
deviceConfig:
|
|
||||||
prefix: "X01DA-ES2-DET3:TRY-"
|
|
||||||
onFailure: retry
|
|
||||||
enabled: true
|
|
||||||
softwareTrigger: false
|
|
||||||
|
|
||||||
pilatus:
|
|
||||||
readoutPriority: baseline
|
|
||||||
description: Pilatus
|
|
||||||
deviceClass: debye_bec.devices.pilatus.pilatus.Pilatus
|
|
||||||
deviceTags:
|
|
||||||
- detector
|
|
||||||
deviceConfig:
|
|
||||||
prefix: "X01DA-ES2-PIL:"
|
|
||||||
onFailure: retry
|
|
||||||
enabled: true
|
|
||||||
softwareTrigger: true
|
|
||||||
|
|
||||||
pilatus_smpl:
|
|
||||||
readoutPriority: baseline
|
|
||||||
description: Sample to pilatus distance
|
|
||||||
deviceClass: ophyd.EpicsSignalRO
|
|
||||||
deviceConfig:
|
|
||||||
read_pv: "X01DA-ES2-DET:SMPLDIST"
|
|
||||||
onFailure: retry
|
|
||||||
enabled: true
|
|
||||||
softwareTrigger: false
|
|
||||||
@@ -1,72 +0,0 @@
|
|||||||
"""Frontend Absorber"""
|
|
||||||
|
|
||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
import enum
|
|
||||||
from typing import TYPE_CHECKING
|
|
||||||
|
|
||||||
from ophyd import Component as Cpt
|
|
||||||
from ophyd import EpicsSignal, EpicsSignalRO
|
|
||||||
from ophyd_devices import CompareStatus, DeviceStatus
|
|
||||||
from ophyd_devices.interfaces.base_classes.psi_device_base import PSIDeviceBase
|
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
|
||||||
from bec_lib.devicemanager import ScanInfo
|
|
||||||
|
|
||||||
class AbsorberError(Exception):
|
|
||||||
"""Absorber specific exception"""
|
|
||||||
|
|
||||||
class STATUS(int, enum.Enum):
|
|
||||||
"""Absorber States"""
|
|
||||||
|
|
||||||
MOVING_CLOSE = 0
|
|
||||||
OPEN = 1
|
|
||||||
MOVING_OPEN = 2
|
|
||||||
CLOSED = 3
|
|
||||||
NOT_ENABLED = 4
|
|
||||||
TIMEOUT_CLOSE = 5
|
|
||||||
TIMEOUT_OPEN = 6
|
|
||||||
CLOSE_LS_LOST = 7
|
|
||||||
OPEN_LS_LOST = 8
|
|
||||||
CLOSE_LS_NOT_FREE = 9
|
|
||||||
OPEN_LS_NOT_FREE = 10
|
|
||||||
ERROR_LS = 11
|
|
||||||
TO_CONNECT = 12
|
|
||||||
MAN_OPEN = 13
|
|
||||||
UNDEFINED = 14
|
|
||||||
|
|
||||||
class Absorber(PSIDeviceBase):
|
|
||||||
"""Class for the Frontend Absorber"""
|
|
||||||
|
|
||||||
USER_ACCESS = ["open", "close"]
|
|
||||||
|
|
||||||
request = Cpt(EpicsSignal, suffix="REQUEST", kind="config", doc="Open/Close Absorber")
|
|
||||||
status = Cpt(EpicsSignalRO, suffix="STATUS", kind="normal", doc="Absorber Status")
|
|
||||||
status_string = Cpt(EpicsSignalRO, suffix="STATUS", kind="normal", string=True, doc="Absorber Status")
|
|
||||||
|
|
||||||
def __init__(self, *, name: str, prefix: str = "", scan_info: ScanInfo | None = None, **kwargs):
|
|
||||||
super().__init__(name=name, prefix=prefix, scan_info=scan_info, **kwargs)
|
|
||||||
|
|
||||||
self.timeout_for_move = 10
|
|
||||||
# Wait for connection on all components, ensure IOC is connected
|
|
||||||
self.wait_for_connection(all_signals=True, timeout=5)
|
|
||||||
|
|
||||||
def open(self) -> DeviceStatus | None:
|
|
||||||
"""Open the Absorber"""
|
|
||||||
if self.status.get() == STATUS.CLOSED:
|
|
||||||
self.request.put(1)
|
|
||||||
status_open = CompareStatus(self.status, STATUS.OPEN, timeout=self.timeout_for_move)
|
|
||||||
status = status_open
|
|
||||||
return status
|
|
||||||
else:
|
|
||||||
return None
|
|
||||||
|
|
||||||
def close(self) -> DeviceStatus | None:
|
|
||||||
"""Close the Absorber"""
|
|
||||||
if self.status.get() == STATUS.OPEN:
|
|
||||||
self.request.put(1)
|
|
||||||
status_close = CompareStatus(self.status, STATUS.CLOSED, timeout=self.timeout_for_move)
|
|
||||||
status = status_close
|
|
||||||
return status
|
|
||||||
else:
|
|
||||||
return None
|
|
||||||
@@ -1,46 +0,0 @@
|
|||||||
"""Basler camera class for Debye BEC."""
|
|
||||||
|
|
||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
from typing import TYPE_CHECKING
|
|
||||||
|
|
||||||
from ophyd import ADBase, EpicsSignalRO
|
|
||||||
from ophyd import ADComponent as ADCpt
|
|
||||||
from ophyd import 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."""
|
|
||||||
|
|
||||||
cam_detector_state_string = Cpt(EpicsSignalRO, suffix="cam1:DetectorState_RBV", string=True)
|
|
||||||
|
|
||||||
_default_configuration_attrs = [
|
|
||||||
'cam1.acquire_time',
|
|
||||||
'cam1.detector_state',
|
|
||||||
'cam_detector_state_string',
|
|
||||||
'cam1.gain',
|
|
||||||
'cam1.model',
|
|
||||||
]
|
|
||||||
|
|
||||||
cam1 = ADCpt(AravisDetectorCam, "cam1:")
|
|
||||||
image1 = ADCpt(ImagePlugin_V35, "image1:")
|
|
||||||
|
|
||||||
|
|
||||||
class BaslerCam(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.",
|
|
||||||
)
|
|
||||||
@@ -1,138 +0,0 @@
|
|||||||
"""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."""
|
|
||||||
@@ -1,79 +0,0 @@
|
|||||||
"""EH Hutch Cameras"""
|
|
||||||
|
|
||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
import cv2
|
|
||||||
import threading
|
|
||||||
from typing import TYPE_CHECKING
|
|
||||||
|
|
||||||
from bec_lib.logger import bec_logger
|
|
||||||
from bec_lib.file_utils import get_full_path
|
|
||||||
from ophyd_devices.interfaces.base_classes.psi_device_base import PSIDeviceBase
|
|
||||||
from ophyd_devices import DeviceStatus
|
|
||||||
|
|
||||||
if TYPE_CHECKING: # pragma: no cover
|
|
||||||
from bec_lib.devicemanager import ScanInfo
|
|
||||||
from bec_lib.messages import ScanStatusMessage
|
|
||||||
|
|
||||||
logger = bec_logger.logger
|
|
||||||
|
|
||||||
CAM_USERNAME = "camera_user"
|
|
||||||
CAM_PASSWORD = "camera_user1"
|
|
||||||
CAM_PORT = 554
|
|
||||||
|
|
||||||
class HutchCam(PSIDeviceBase):
|
|
||||||
"""Class for the Hutch Cameras"""
|
|
||||||
|
|
||||||
# image = Cpt(Signal, name='image', kind='config')
|
|
||||||
|
|
||||||
def __init__(self, *, name: str, prefix: str = "", scan_info: ScanInfo | None = None, **kwargs):
|
|
||||||
super().__init__(name=name, scan_info=scan_info, **kwargs)
|
|
||||||
|
|
||||||
self.hostname = prefix
|
|
||||||
self.status = None
|
|
||||||
|
|
||||||
# pylint: disable=E1101
|
|
||||||
def on_connected(self) -> None:
|
|
||||||
"""
|
|
||||||
Called after the device is connected and its signals are connected.
|
|
||||||
Default values for signals should be set here.
|
|
||||||
"""
|
|
||||||
rtsp_url = f"rtsp://{CAM_USERNAME}:{CAM_PASSWORD}@{self.hostname}.psi.ch:{CAM_PORT}/rtpstream/config1"
|
|
||||||
cap = cv2.VideoCapture(f"{rtsp_url}?tcp")
|
|
||||||
if not cap.isOpened():
|
|
||||||
logger.error(self, "Connection Failed", "Could not connect to the camera stream.")
|
|
||||||
return
|
|
||||||
cap.release()
|
|
||||||
|
|
||||||
def on_stage(self) -> DeviceStatus:
|
|
||||||
"""Called while staging the device."""
|
|
||||||
|
|
||||||
scan_msg: ScanStatusMessage = self.scan_info.msg
|
|
||||||
file_path = get_full_path(scan_msg, name='hutch_cam_' + self.hostname).removesuffix('h5')
|
|
||||||
|
|
||||||
self.status = DeviceStatus(self)
|
|
||||||
|
|
||||||
thread = threading.Thread(target=self._save_picture, args=(file_path, self.status), daemon=True)
|
|
||||||
thread.start()
|
|
||||||
|
|
||||||
return self.status
|
|
||||||
|
|
||||||
def _save_picture(self, file_path, status):
|
|
||||||
try:
|
|
||||||
logger.info(f'Capture from camera {self.hostname}')
|
|
||||||
rtsp_url = f"rtsp://{CAM_USERNAME}:{CAM_PASSWORD}@{self.hostname}.psi.ch:{CAM_PORT}/rtpstream/config1"
|
|
||||||
cap = cv2.VideoCapture(f"{rtsp_url}?tcp")
|
|
||||||
if not cap.isOpened():
|
|
||||||
logger.error("Connection Failed", "Could not connect to the camera stream.")
|
|
||||||
return
|
|
||||||
logger.info(f'Connection to camera {self.hostname} established')
|
|
||||||
ret, frame = cap.readAsync()
|
|
||||||
cap.release()
|
|
||||||
if not ret:
|
|
||||||
logger.error("Capture Failed", "Failed to capture image from camera.")
|
|
||||||
return
|
|
||||||
cv2.imwrite(file_path + 'png', frame)
|
|
||||||
status.set_finished()
|
|
||||||
logger.info(f'Capture from camera {self.hostname} done')
|
|
||||||
except Exception as e:
|
|
||||||
status.set_exception(e)
|
|
||||||
@@ -1,49 +0,0 @@
|
|||||||
"""Prosilica camera class for integration of beam_monitor 1/2 cameras."""
|
|
||||||
|
|
||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
from typing import TYPE_CHECKING
|
|
||||||
|
|
||||||
from ophyd import ADBase, EpicsSignalRO
|
|
||||||
from ophyd import ADComponent as ADCpt
|
|
||||||
from ophyd import Component as Cpt
|
|
||||||
from ophyd_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."""
|
|
||||||
|
|
||||||
cam_detector_state_string = Cpt(EpicsSignalRO, suffix="cam1:DetectorState_RBV", string=True)
|
|
||||||
|
|
||||||
_default_configuration_attrs = [
|
|
||||||
'cam1.acquire_time',
|
|
||||||
'cam1.detector_state',
|
|
||||||
'cam_detector_state_string',
|
|
||||||
'cam1.gain',
|
|
||||||
'cam1.model',
|
|
||||||
]
|
|
||||||
|
|
||||||
cam1 = ADCpt(ProsilicaDetectorCam, "cam1:")
|
|
||||||
image1 = ADCpt(ImagePlugin_V35, "image1:")
|
|
||||||
|
|
||||||
|
|
||||||
class ProsilicaCam(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.",
|
|
||||||
)
|
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
// This file was autogenerated. Do not edit it manually.
|
|
||||||
## Device List
|
|
||||||
### debye_bec
|
|
||||||
| Device | Documentation | Module |
|
|
||||||
| :----- | :------------- | :------ |
|
|
||||||
| BaslerCam | Basler camera class at Debye. IOC prefix: X01DA-ES-XRAYEYE: | [debye_bec.devices.cameras.basler_cam](https://gitlab.psi.ch/bec/debye_bec/-/blob/main/debye_bec/devices/cameras/basler_cam.py) |
|
|
||||||
| BaslerCamBase | BaslerCam Base class. | [debye_bec.devices.cameras.basler_cam](https://gitlab.psi.ch/bec/debye_bec/-/blob/main/debye_bec/devices/cameras/basler_cam.py) |
|
|
||||||
| DebyeBaseCamera | Base class for Debye cameras. | [debye_bec.devices.cameras.debye_base_cam](https://gitlab.psi.ch/bec/debye_bec/-/blob/main/debye_bec/devices/cameras/debye_base_cam.py) |
|
|
||||||
| ES0Filter | Class for the ES0 filter station X01DA-ES0-FI: | [debye_bec.devices.es0filter](https://gitlab.psi.ch/bec/debye_bec/-/blob/main/debye_bec/devices/es0filter.py) |
|
|
||||||
| GasMixSetup | Class for the ES2 Pilatus Curtain | [debye_bec.devices.pilatus_curtain](https://gitlab.psi.ch/bec/debye_bec/-/blob/main/debye_bec/devices/pilatus_curtain.py) |
|
|
||||||
| GasMixSetupControl | GasMixSetup Control for Inonization Chamber 0 | [debye_bec.devices.ionization_chambers.ionization_chamber](https://gitlab.psi.ch/bec/debye_bec/-/blob/main/debye_bec/devices/ionization_chambers/ionization_chamber.py) |
|
|
||||||
| 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) |
|
|
||||||
@@ -1,54 +0,0 @@
|
|||||||
"""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")
|
|
||||||
@@ -1,434 +0,0 @@
|
|||||||
"""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_devices import CompareStatus, DeviceStatus, SubscriptionStatus, 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="omitted", doc="Gas 1 requirement")
|
|
||||||
conc1_req = Cpt(
|
|
||||||
EpicsSignalWithRBV, suffix="Conc1Req", kind="omitted", doc="Concentration 1 requirement"
|
|
||||||
)
|
|
||||||
gas2_req = Cpt(EpicsSignalWithRBV, suffix="Gas2Req", kind="omitted", doc="Gas 2 requirement")
|
|
||||||
conc2_req = Cpt(
|
|
||||||
EpicsSignalWithRBV, suffix="Conc2Req", kind="omitted", doc="Concentration 2 requirement"
|
|
||||||
)
|
|
||||||
press_req = Cpt(
|
|
||||||
EpicsSignalWithRBV, suffix="PressReq", kind="omitted", doc="Pressure requirement"
|
|
||||||
)
|
|
||||||
fill = Cpt(EpicsSignal, suffix="Fill", kind="config", doc="Fill the chamber")
|
|
||||||
status = Cpt(EpicsSignalRO, suffix="Status", kind="config", doc="Status")
|
|
||||||
gas1 = Cpt(EpicsSignalRO, suffix="Gas1", kind="config", doc="Gas 1")
|
|
||||||
gas1_string = Cpt(EpicsSignalRO, suffix="Gas1", kind="config", doc="Gas 1", string=True)
|
|
||||||
conc1 = Cpt(EpicsSignalRO, suffix="Conc1", kind="config", doc="Concentration 1")
|
|
||||||
gas2 = Cpt(EpicsSignalRO, suffix="Gas2", kind="config", doc="Gas 2")
|
|
||||||
gas2_string = Cpt(EpicsSignalRO, suffix="Gas2", kind="config", doc="Gas 2", string=True)
|
|
||||||
conc2 = Cpt(EpicsSignalRO, suffix="Conc2", kind="config", doc="Concentration 2")
|
|
||||||
press = Cpt(EpicsSignalRO, suffix="PressTransm", kind="config", doc="Current Pressure")
|
|
||||||
|
|
||||||
|
|
||||||
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}"},
|
|
||||||
),
|
|
||||||
"cOnOff_string": (
|
|
||||||
EpicsSignal,
|
|
||||||
(f"ES:AMP5004.cOnOff{num}"),
|
|
||||||
{"kind": "config", "doc": f"Enable ch{num} -> IC{num-1}", "string": True},
|
|
||||||
),
|
|
||||||
"cGain_ENUM_string": (
|
|
||||||
EpicsSignalWithRBV,
|
|
||||||
(f"ES:AMP5004:cGain{num}_ENUM"),
|
|
||||||
{"kind": "config", "doc": f"Gain of ch{num} -> IC{num-1}", "string": True},
|
|
||||||
),
|
|
||||||
"cFilter_ENUM_string": (
|
|
||||||
EpicsSignalWithRBV,
|
|
||||||
(f"ES:AMP5004:cFilter{num}_ENUM"),
|
|
||||||
{"kind": "config", "doc": f"Filter of ch{num} -> IC{num-1}", "string": True},
|
|
||||||
),
|
|
||||||
}
|
|
||||||
amp = Dcpt(amp_signals)
|
|
||||||
gmes = Cpt(GasMixSetupControl, suffix=f"ES-GMES:IC{num-1}")
|
|
||||||
gmes_status_msg = Cpt(EpicsSignalRO, suffix="ES-GMES:StatusMsg0", kind="config", doc="Status")
|
|
||||||
hv = Cpt(HighVoltageSuppliesControl, suffix=f"ES1-IC{num-1}:")
|
|
||||||
hv_en_signals = {
|
|
||||||
"ext_ena": (
|
|
||||||
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"]) -> 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"]
|
|
||||||
) -> 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}"},
|
|
||||||
),
|
|
||||||
"cOnOff_string": (
|
|
||||||
EpicsSignal,
|
|
||||||
(f"ES:AMP5004.cOnOff{num}"),
|
|
||||||
{"kind": "config", "doc": f"Enable ch{num} -> IC{num-1}", "string": True},
|
|
||||||
),
|
|
||||||
"cGain_ENUM_string": (
|
|
||||||
EpicsSignalWithRBV,
|
|
||||||
(f"ES:AMP5004:cGain{num}_ENUM"),
|
|
||||||
{"kind": "config", "doc": f"Gain of ch{num} -> IC{num-1}", "string": True},
|
|
||||||
),
|
|
||||||
"cFilter_ENUM_string": (
|
|
||||||
EpicsSignalWithRBV,
|
|
||||||
(f"ES:AMP5004:cFilter{num}_ENUM"),
|
|
||||||
{"kind": "config", "doc": f"Filter of ch{num} -> IC{num-1}", "string": True},
|
|
||||||
),
|
|
||||||
}
|
|
||||||
amp = Dcpt(amp_signals)
|
|
||||||
gmes = Cpt(GasMixSetupControl, suffix=f"ES-GMES:IC{num-1}")
|
|
||||||
gmes_status_msg = Cpt(EpicsSignalRO, suffix="ES-GMES:StatusMsg0", kind="config", doc="Status")
|
|
||||||
hv = Cpt(HighVoltageSuppliesControl, suffix=f"ES2-IC{num-1}:")
|
|
||||||
hv_en_signals = {
|
|
||||||
"ext_ena": (
|
|
||||||
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}"},
|
|
||||||
),
|
|
||||||
"cOnOff_string": (
|
|
||||||
EpicsSignal,
|
|
||||||
(f"ES:AMP5004.cOnOff{num}"),
|
|
||||||
{"kind": "config", "doc": f"Enable ch{num} -> IC{num-1}", "string": True},
|
|
||||||
),
|
|
||||||
"cGain_ENUM_string": (
|
|
||||||
EpicsSignalWithRBV,
|
|
||||||
(f"ES:AMP5004:cGain{num}_ENUM"),
|
|
||||||
{"kind": "config", "doc": f"Gain of ch{num} -> IC{num-1}", "string": True},
|
|
||||||
),
|
|
||||||
"cFilter_ENUM_string": (
|
|
||||||
EpicsSignalWithRBV,
|
|
||||||
(f"ES:AMP5004:cFilter{num}_ENUM"),
|
|
||||||
{"kind": "config", "doc": f"Filter of ch{num} -> IC{num-1}", "string": True},
|
|
||||||
),
|
|
||||||
}
|
|
||||||
amp = Dcpt(amp_signals)
|
|
||||||
gmes = Cpt(GasMixSetupControl, suffix=f"ES-GMES:IC{num-1}")
|
|
||||||
gmes_status_msg = Cpt(EpicsSignalRO, suffix="ES-GMES:StatusMsg0", kind="config", doc="Status")
|
|
||||||
hv = Cpt(HighVoltageSuppliesControl, suffix=f"ES2-IC{num-1}:")
|
|
||||||
hv_en_signals = {
|
|
||||||
"ext_ena": (
|
|
||||||
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 Pips(IonizationChamber0):
|
|
||||||
"""Pips, prefix should be 'X01DA-'."""
|
|
||||||
|
|
||||||
USER_ACCESS = ["set_gain", "set_filter"]
|
|
||||||
|
|
||||||
num = 4
|
|
||||||
amp_signals = {
|
|
||||||
"cOnOff": (
|
|
||||||
EpicsSignal,
|
|
||||||
(f"ES:AMP5004.cOnOff{num}"),
|
|
||||||
{"kind": "config", "doc": f"Enable ch{num} -> IC{num-1}"},
|
|
||||||
),
|
|
||||||
"cGain_ENUM": (
|
|
||||||
EpicsSignalWithRBV,
|
|
||||||
(f"ES:AMP5004:cGain{num}_ENUM"),
|
|
||||||
{"kind": "config", "doc": f"Gain of ch{num} -> IC{num-1}"},
|
|
||||||
),
|
|
||||||
"cFilter_ENUM": (
|
|
||||||
EpicsSignalWithRBV,
|
|
||||||
(f"ES:AMP5004:cFilter{num}_ENUM"),
|
|
||||||
{"kind": "config", "doc": f"Filter of ch{num} -> IC{num-1}"},
|
|
||||||
),
|
|
||||||
"cOnOff_string": (
|
|
||||||
EpicsSignal,
|
|
||||||
(f"ES:AMP5004.cOnOff{num}"),
|
|
||||||
{"kind": "config", "doc": f"Enable ch{num} -> IC{num-1}", "string": True},
|
|
||||||
),
|
|
||||||
"cGain_ENUM_string": (
|
|
||||||
EpicsSignalWithRBV,
|
|
||||||
(f"ES:AMP5004:cGain{num}_ENUM"),
|
|
||||||
{"kind": "config", "doc": f"Gain of ch{num} -> IC{num-1}", "string": True},
|
|
||||||
),
|
|
||||||
"cFilter_ENUM_string": (
|
|
||||||
EpicsSignalWithRBV,
|
|
||||||
(f"ES:AMP5004:cFilter{num}_ENUM"),
|
|
||||||
{"kind": "config", "doc": f"Filter of ch{num} -> IC{num-1}", "string": True},
|
|
||||||
),
|
|
||||||
}
|
|
||||||
amp = Dcpt(amp_signals)
|
|
||||||
gmes = None
|
|
||||||
gmes_status_msg = None
|
|
||||||
hv = None
|
|
||||||
hv_en_signals = None
|
|
||||||
hv_en = None
|
|
||||||
|
|
||||||
@typechecked
|
|
||||||
def set_hv(self, *_) -> None:
|
|
||||||
"""Not available for the PIPS"""
|
|
||||||
return None
|
|
||||||
|
|
||||||
@typechecked
|
|
||||||
def set_grid(self, *_) -> None:
|
|
||||||
"""Not available for the PIPS"""
|
|
||||||
return None
|
|
||||||
|
|
||||||
@typechecked
|
|
||||||
def fill(self, *_) -> None:
|
|
||||||
"""Not available for the PIPS"""
|
|
||||||
return None
|
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
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,479 +0,0 @@
|
|||||||
"""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 Literal
|
|
||||||
|
|
||||||
from bec_lib.devicemanager import ScanInfo
|
|
||||||
from bec_lib.logger import bec_logger
|
|
||||||
from ophyd import Component as Cpt
|
|
||||||
from ophyd import DeviceStatus, StatusBase
|
|
||||||
from ophyd.status import WaitTimeoutError
|
|
||||||
from ophyd_devices import CompareStatus, ProgressSignal, TransitionStatus
|
|
||||||
from ophyd_devices.interfaces.base_classes.psi_device_base import PSIDeviceBase
|
|
||||||
from 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,
|
|
||||||
failure_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)
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
"""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"
|
|
||||||
@@ -1,442 +0,0 @@
|
|||||||
"""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"""
|
|
||||||
|
|
||||||
bragg_off_si111 = Cpt(EpicsSignalWithRBV, suffix="bragg_off_si111", kind="config")
|
|
||||||
bragg_off_si311 = Cpt(EpicsSignalWithRBV, suffix="bragg_off_si311", kind="config")
|
|
||||||
phi_off_si111 = Cpt(EpicsSignalWithRBV, suffix="phi_off_si111", kind="config")
|
|
||||||
phi_off_si311 = Cpt(EpicsSignalWithRBV, suffix="phi_off_si311", kind="config")
|
|
||||||
azm_off_si111 = Cpt(EpicsSignalWithRBV, suffix="azm_off_si111", kind="config")
|
|
||||||
azm_off_si311 = Cpt(EpicsSignalWithRBV, suffix="azm_off_si311", kind="config")
|
|
||||||
miscut_si111 = Cpt(EpicsSignalWithRBV, suffix="miscut_si111", kind="config")
|
|
||||||
miscut_si311 = Cpt(EpicsSignalWithRBV, suffix="miscut_si311", kind="config")
|
|
||||||
xtal_enum = Cpt(EpicsSignalWithRBV, suffix="xtal_ENUM", kind="config")
|
|
||||||
d_spacing_si111 = Cpt(EpicsSignalWithRBV, suffix="d_spacing_si111", kind="config")
|
|
||||||
d_spacing_si311 = Cpt(EpicsSignalWithRBV, suffix="d_spacing_si311", kind="config")
|
|
||||||
set_offset = Cpt(EpicsSignal, suffix="set_offset", kind="config", put_complete=True)
|
|
||||||
current_d_spacing = Cpt(
|
|
||||||
EpicsSignalRO, suffix="current_d_spacing_RBV", kind="normal", auto_monitor=True
|
|
||||||
)
|
|
||||||
current_bragg_off = Cpt(
|
|
||||||
EpicsSignalRO, suffix="current_bragg_off_RBV", kind="normal", auto_monitor=True
|
|
||||||
)
|
|
||||||
current_phi_off = Cpt(
|
|
||||||
EpicsSignalRO, suffix="current_phi_off_RBV", kind="normal", auto_monitor=True
|
|
||||||
)
|
|
||||||
current_azm_off = Cpt(
|
|
||||||
EpicsSignalRO, suffix="current_azm_off_RBV", kind="normal", auto_monitor=True
|
|
||||||
)
|
|
||||||
current_miscut = Cpt(
|
|
||||||
EpicsSignalRO, suffix="current_miscut_RBV", kind="normal", auto_monitor=True
|
|
||||||
)
|
|
||||||
current_xtal = Cpt(
|
|
||||||
EpicsSignalRO, suffix="current_xtal_ENUM_RBV", kind="normal", auto_monitor=True
|
|
||||||
)
|
|
||||||
current_xtal_string = Cpt(
|
|
||||||
EpicsSignalRO, suffix="current_xtal_ENUM_RBV", kind="normal", auto_monitor=True, string=True
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class Mo1BraggScanSettings(Device):
|
|
||||||
"""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)
|
|
||||||
|
|
||||||
angle = Cpt(EpicsSignalRO, suffix="feedback_pos_angle_RBV", kind="normal", auto_monitor=True)
|
|
||||||
|
|
||||||
########## Move Command PVs ##########
|
|
||||||
|
|
||||||
move_abs = Cpt(EpicsSignal, suffix="move_abs", kind="config", put_complete=True)
|
|
||||||
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"],
|
|
||||||
bragg_off_si111: float = None,
|
|
||||||
bragg_off_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
|
|
||||||
bragg_off_si111 (float) : Offset for the 111 crystal
|
|
||||||
bragg_off_si311 (float) : Offset for the 311 crystal
|
|
||||||
d_spacing_si111 (float) : d-spacing for the 111 crystal
|
|
||||||
d_spacing_si311 (float) : d-spacing for the 311 crystal
|
|
||||||
"""
|
|
||||||
if bragg_off_si111 is not None:
|
|
||||||
self.crystal.bragg_off_si111.put(bragg_off_si111)
|
|
||||||
if bragg_off_si311 is not None:
|
|
||||||
self.crystal.bragg_off_si311.put(bragg_off_si311)
|
|
||||||
if d_spacing_si111 is not None:
|
|
||||||
self.crystal.d_spacing_si111.put(d_spacing_si111)
|
|
||||||
if d_spacing_si311 is not None:
|
|
||||||
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)
|
|
||||||
@@ -1,61 +0,0 @@
|
|||||||
"""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
|
|
||||||
@@ -1,93 +0,0 @@
|
|||||||
"""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
|
|
||||||
@@ -1,816 +0,0 @@
|
|||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
from typing import TYPE_CHECKING, Literal
|
|
||||||
|
|
||||||
from bec_lib.logger import bec_logger
|
|
||||||
from ophyd import Component as Cpt
|
|
||||||
from ophyd import Device, DeviceStatus, EpicsSignal, EpicsSignalRO, Kind, StatusBase
|
|
||||||
from ophyd.status import WaitTimeoutError
|
|
||||||
from ophyd_devices import CompareStatus, ProgressSignal, TransitionStatus
|
|
||||||
from ophyd_devices.interfaces.base_classes.psi_device_base import PSIDeviceBase
|
|
||||||
from ophyd_devices.sim.sim_signals import SetableSignal
|
|
||||||
|
|
||||||
from debye_bec.devices.nidaq.nidaq_enums import (
|
|
||||||
EncoderFactors,
|
|
||||||
NIDAQCompression,
|
|
||||||
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 ###
|
|
||||||
energy = Cpt(SetableSignal, value=0, kind=Kind.normal)
|
|
||||||
|
|
||||||
smpl_abs = Cpt(
|
|
||||||
SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream sample absorption"
|
|
||||||
)
|
|
||||||
smpl_fluo = Cpt(
|
|
||||||
SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream sample fluorescence"
|
|
||||||
)
|
|
||||||
ref_abs = Cpt(
|
|
||||||
SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream reference absorption"
|
|
||||||
)
|
|
||||||
cisum = Cpt(
|
|
||||||
SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream counter sum"
|
|
||||||
)
|
|
||||||
|
|
||||||
ai0_mean = Cpt(
|
|
||||||
SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream analog input 0, MEAN"
|
|
||||||
)
|
|
||||||
ai1_mean = Cpt(
|
|
||||||
SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream analog input 1, MEAN"
|
|
||||||
)
|
|
||||||
ai2_mean = Cpt(
|
|
||||||
SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream analog input 2, MEAN"
|
|
||||||
)
|
|
||||||
ai3_mean = Cpt(
|
|
||||||
SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream analog input 3, MEAN"
|
|
||||||
)
|
|
||||||
ai4_mean = Cpt(
|
|
||||||
SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream analog input 4, MEAN"
|
|
||||||
)
|
|
||||||
ai5_mean = Cpt(
|
|
||||||
SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream analog input 5, MEAN"
|
|
||||||
)
|
|
||||||
ai6_mean = Cpt(
|
|
||||||
SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream analog input 6, MEAN"
|
|
||||||
)
|
|
||||||
ai7_mean = Cpt(
|
|
||||||
SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream analog input 7, MEAN"
|
|
||||||
)
|
|
||||||
|
|
||||||
di0_max = Cpt(SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream digital input 0, MAX")
|
|
||||||
di1_max = Cpt(SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream digital input 1, MAX")
|
|
||||||
di2_max = Cpt(SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream digital input 2, MAX")
|
|
||||||
di3_max = Cpt(SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream digital input 3, MAX")
|
|
||||||
di4_max = Cpt(SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream digital input 4, MAX")
|
|
||||||
|
|
||||||
ci0_mean = Cpt(
|
|
||||||
SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream counter input 0, MEAN"
|
|
||||||
)
|
|
||||||
ci1_mean = Cpt(
|
|
||||||
SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream counter input 1, MEAN"
|
|
||||||
)
|
|
||||||
ci2_mean = Cpt(
|
|
||||||
SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream counter input 2, MEAN"
|
|
||||||
)
|
|
||||||
ci3_mean = Cpt(
|
|
||||||
SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream counter input 3, MEAN"
|
|
||||||
)
|
|
||||||
ci4_mean = Cpt(
|
|
||||||
SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream counter input 4, MEAN"
|
|
||||||
)
|
|
||||||
ci5_mean = Cpt(
|
|
||||||
SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream counter input 5, MEAN"
|
|
||||||
)
|
|
||||||
ci6_mean = Cpt(
|
|
||||||
SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream counter input 6, MEAN"
|
|
||||||
)
|
|
||||||
ci7_mean = Cpt(
|
|
||||||
SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream counter input 7, MEAN"
|
|
||||||
)
|
|
||||||
ci8_mean = Cpt(
|
|
||||||
SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream counter input 8, MEAN"
|
|
||||||
)
|
|
||||||
ci9_mean = Cpt(
|
|
||||||
SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream counter input 9, MEAN"
|
|
||||||
)
|
|
||||||
ci10_mean = Cpt(
|
|
||||||
SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream counter input 10, MEAN"
|
|
||||||
)
|
|
||||||
ci11_mean = Cpt(
|
|
||||||
SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream counter input 11, MEAN"
|
|
||||||
)
|
|
||||||
ci12_mean = Cpt(
|
|
||||||
SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream counter input 12, MEAN"
|
|
||||||
)
|
|
||||||
ci13_mean = Cpt(
|
|
||||||
SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream counter input 13, MEAN"
|
|
||||||
)
|
|
||||||
ci14_mean = Cpt(
|
|
||||||
SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream counter input 14, MEAN"
|
|
||||||
)
|
|
||||||
ci15_mean = Cpt(
|
|
||||||
SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream counter input 15, MEAN"
|
|
||||||
)
|
|
||||||
ci16_mean = Cpt(
|
|
||||||
SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream counter input 16, MEAN"
|
|
||||||
)
|
|
||||||
ci17_mean = Cpt(
|
|
||||||
SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream counter input 17, MEAN"
|
|
||||||
)
|
|
||||||
|
|
||||||
ai0 = Cpt(
|
|
||||||
EpicsSignalRO,
|
|
||||||
suffix="NIDAQ-AI0",
|
|
||||||
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,
|
|
||||||
)
|
|
||||||
ci8 = Cpt(
|
|
||||||
EpicsSignalRO,
|
|
||||||
suffix="NIDAQ-CI8",
|
|
||||||
kind=Kind.normal,
|
|
||||||
doc="EPICS counter input 8",
|
|
||||||
auto_monitor=True,
|
|
||||||
)
|
|
||||||
ci9 = Cpt(
|
|
||||||
EpicsSignalRO,
|
|
||||||
suffix="NIDAQ-CI9",
|
|
||||||
kind=Kind.normal,
|
|
||||||
doc="EPICS counter input 9",
|
|
||||||
auto_monitor=True,
|
|
||||||
)
|
|
||||||
ci10 = Cpt(
|
|
||||||
EpicsSignalRO,
|
|
||||||
suffix="NIDAQ-CI10",
|
|
||||||
kind=Kind.normal,
|
|
||||||
doc="EPICS counter input 0",
|
|
||||||
auto_monitor=True,
|
|
||||||
)
|
|
||||||
ci11 = Cpt(
|
|
||||||
EpicsSignalRO,
|
|
||||||
suffix="NIDAQ-CI11",
|
|
||||||
kind=Kind.normal,
|
|
||||||
doc="EPICS counter input 1",
|
|
||||||
auto_monitor=True,
|
|
||||||
)
|
|
||||||
ci12 = Cpt(
|
|
||||||
EpicsSignalRO,
|
|
||||||
suffix="NIDAQ-CI12",
|
|
||||||
kind=Kind.normal,
|
|
||||||
doc="EPICS counter input 2",
|
|
||||||
auto_monitor=True,
|
|
||||||
)
|
|
||||||
ci13 = Cpt(
|
|
||||||
EpicsSignalRO,
|
|
||||||
suffix="NIDAQ-CI13",
|
|
||||||
kind=Kind.normal,
|
|
||||||
doc="EPICS counter input 3",
|
|
||||||
auto_monitor=True,
|
|
||||||
)
|
|
||||||
ci14 = Cpt(
|
|
||||||
EpicsSignalRO,
|
|
||||||
suffix="NIDAQ-CI14",
|
|
||||||
kind=Kind.normal,
|
|
||||||
doc="EPICS counter input 4",
|
|
||||||
auto_monitor=True,
|
|
||||||
)
|
|
||||||
ci15 = Cpt(
|
|
||||||
EpicsSignalRO,
|
|
||||||
suffix="NIDAQ-CI15",
|
|
||||||
kind=Kind.normal,
|
|
||||||
doc="EPICS counter input 5",
|
|
||||||
auto_monitor=True,
|
|
||||||
)
|
|
||||||
ci16 = Cpt(
|
|
||||||
EpicsSignalRO,
|
|
||||||
suffix="NIDAQ-CI16",
|
|
||||||
kind=Kind.normal,
|
|
||||||
doc="EPICS counter input 6",
|
|
||||||
auto_monitor=True,
|
|
||||||
)
|
|
||||||
ci17 = Cpt(
|
|
||||||
EpicsSignalRO,
|
|
||||||
suffix="NIDAQ-CI17",
|
|
||||||
kind=Kind.normal,
|
|
||||||
doc="EPICS counter input 7",
|
|
||||||
auto_monitor=True,
|
|
||||||
)
|
|
||||||
|
|
||||||
di0 = Cpt(
|
|
||||||
EpicsSignalRO,
|
|
||||||
suffix="NIDAQ-DI0",
|
|
||||||
kind=Kind.normal,
|
|
||||||
doc="EPICS digital input 0",
|
|
||||||
auto_monitor=True,
|
|
||||||
)
|
|
||||||
di1 = Cpt(
|
|
||||||
EpicsSignalRO,
|
|
||||||
suffix="NIDAQ-DI1",
|
|
||||||
kind=Kind.normal,
|
|
||||||
doc="EPICS digital input 1",
|
|
||||||
auto_monitor=True,
|
|
||||||
)
|
|
||||||
di2 = Cpt(
|
|
||||||
EpicsSignalRO,
|
|
||||||
suffix="NIDAQ-DI2",
|
|
||||||
kind=Kind.normal,
|
|
||||||
doc="EPICS digital input 2",
|
|
||||||
auto_monitor=True,
|
|
||||||
)
|
|
||||||
di3 = Cpt(
|
|
||||||
EpicsSignalRO,
|
|
||||||
suffix="NIDAQ-DI3",
|
|
||||||
kind=Kind.normal,
|
|
||||||
doc="EPICS digital input 3",
|
|
||||||
auto_monitor=True,
|
|
||||||
)
|
|
||||||
di4 = Cpt(
|
|
||||||
EpicsSignalRO,
|
|
||||||
suffix="NIDAQ-DI4",
|
|
||||||
kind=Kind.normal,
|
|
||||||
doc="EPICS digital input 4",
|
|
||||||
auto_monitor=True,
|
|
||||||
)
|
|
||||||
|
|
||||||
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_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_std_dev = Cpt(
|
|
||||||
SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream counter input 0. STD"
|
|
||||||
)
|
|
||||||
ci1_std_dev = Cpt(
|
|
||||||
SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream counter input 1. STD"
|
|
||||||
)
|
|
||||||
ci2_std_dev = Cpt(
|
|
||||||
SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream counter input 2. STD"
|
|
||||||
)
|
|
||||||
ci3_std_dev = Cpt(
|
|
||||||
SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream counter input 3. STD"
|
|
||||||
)
|
|
||||||
ci4_std_dev = Cpt(
|
|
||||||
SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream counter input 4. STD"
|
|
||||||
)
|
|
||||||
ci5_std_dev = Cpt(
|
|
||||||
SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream counter input 5. STD"
|
|
||||||
)
|
|
||||||
ci6_std_dev = Cpt(
|
|
||||||
SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream counter input 6. STD"
|
|
||||||
)
|
|
||||||
ci7_std_dev = Cpt(
|
|
||||||
SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream counter input 7. STD"
|
|
||||||
)
|
|
||||||
ci8_std_dev = Cpt(
|
|
||||||
SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream counter input 8. STD"
|
|
||||||
)
|
|
||||||
ci9_std_dev = Cpt(
|
|
||||||
SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream counter input 9. STD"
|
|
||||||
)
|
|
||||||
ci10_std_dev = Cpt(
|
|
||||||
SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream counter input 10. STD"
|
|
||||||
)
|
|
||||||
ci11_std_dev = Cpt(
|
|
||||||
SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream counter input 11. STD"
|
|
||||||
)
|
|
||||||
ci12_std_dev = Cpt(
|
|
||||||
SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream counter input 12. STD"
|
|
||||||
)
|
|
||||||
ci13_std_dev = Cpt(
|
|
||||||
SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream counter input 13. STD"
|
|
||||||
)
|
|
||||||
ci14_std_dev = Cpt(
|
|
||||||
SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream counter input 14. STD"
|
|
||||||
)
|
|
||||||
ci15_std_dev = Cpt(
|
|
||||||
SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream counter input 15. STD"
|
|
||||||
)
|
|
||||||
ci16_std_dev = Cpt(
|
|
||||||
SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream counter input 16. STD"
|
|
||||||
)
|
|
||||||
ci17_std_dev = Cpt(
|
|
||||||
SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream counter input 17. STD"
|
|
||||||
)
|
|
||||||
|
|
||||||
xas_timestamp = Cpt(SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream XAS timestamp")
|
|
||||||
xrd_timestamp = Cpt(SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream XRD timestamp")
|
|
||||||
xrd_angle = Cpt(SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream XRD angle")
|
|
||||||
xrd_energy = Cpt(SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream XRD energy")
|
|
||||||
xrd_ai0_mean = Cpt(SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream XRD ai0 mean")
|
|
||||||
xrd_ai0_std_dev = Cpt(SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream XRD ai0 std dev")
|
|
||||||
|
|
||||||
enc = Cpt(SetableSignal, value=0, kind=Kind.normal)
|
|
||||||
rle = Cpt(SetableSignal, value=0, kind=Kind.normal)
|
|
||||||
|
|
||||||
### Control PVs ###
|
|
||||||
|
|
||||||
enable_compression = Cpt(EpicsSignal, suffix="NIDAQ-EnableRLE", kind=Kind.config, auto_monitor=True)
|
|
||||||
enable_dead_time_correction = Cpt(EpicsSignal, suffix="NIDAQ-EnableDTC", kind=Kind.config, auto_monitor=True)
|
|
||||||
kickoff_call = Cpt(EpicsSignal, suffix="NIDAQ-Kickoff", kind=Kind.config)
|
|
||||||
stage_call = Cpt(EpicsSignal, suffix="NIDAQ-Stage", kind=Kind.config)
|
|
||||||
state = Cpt(EpicsSignal, suffix="NIDAQ-FSMState", kind=Kind.config, auto_monitor=True)
|
|
||||||
server_status = Cpt(EpicsSignalRO, suffix="NIDAQ-ServerStatus", kind=Kind.config)
|
|
||||||
compression_ratio = Cpt(EpicsSignalRO, suffix="NIDAQ-CompressionRatio", kind=Kind.config)
|
|
||||||
scan_type = Cpt(EpicsSignal, suffix="NIDAQ-ScanType", kind=Kind.config)
|
|
||||||
scan_type_string = Cpt(EpicsSignal, suffix="NIDAQ-ScanType", kind=Kind.config, string=True)
|
|
||||||
sampling_rate = Cpt(EpicsSignal, suffix="NIDAQ-SamplingRateRequested", kind=Kind.config, auto_monitor=True)
|
|
||||||
sampling_rate_string = Cpt(EpicsSignal, suffix="NIDAQ-SamplingRateRequested", kind=Kind.config, string=True, auto_monitor=True)
|
|
||||||
scan_duration = Cpt(EpicsSignal, suffix="NIDAQ-SamplingDuration", kind=Kind.config)
|
|
||||||
readout_range = Cpt(EpicsSignal, suffix="NIDAQ-ReadoutRange", kind=Kind.config, auto_monitor=True)
|
|
||||||
readout_range_string = Cpt(EpicsSignal, suffix="NIDAQ-ReadoutRange", kind=Kind.config, string=True, auto_monitor=True)
|
|
||||||
encoder_factor = Cpt(EpicsSignal, suffix="NIDAQ-EncoderFactor", kind=Kind.config, auto_monitor=True)
|
|
||||||
encoder_factor_string = Cpt(EpicsSignal, suffix="NIDAQ-EncoderFactor", kind=Kind.config, string=True, auto_monitor=True)
|
|
||||||
stop_call = Cpt(EpicsSignal, suffix="NIDAQ-Stop", kind=Kind.config)
|
|
||||||
power = Cpt(EpicsSignal, suffix="NIDAQ-Power", kind=Kind.config)
|
|
||||||
heartbeat = Cpt(EpicsSignal, suffix="NIDAQ-Heartbeat", kind=Kind.config, auto_monitor=True)
|
|
||||||
time_left = Cpt(EpicsSignalRO, suffix="NIDAQ-TimeLeft", kind=Kind.config, auto_monitor=True)
|
|
||||||
|
|
||||||
ai_chans = Cpt(EpicsSignal, suffix="NIDAQ-AIChans", kind=Kind.config, auto_monitor=True)
|
|
||||||
ci_chans = Cpt(EpicsSignal, suffix="NIDAQ-CIChans", kind=Kind.config, auto_monitor=True)
|
|
||||||
di_chans = Cpt(EpicsSignal, suffix="NIDAQ-DIChans", kind=Kind.config, auto_monitor=True)
|
|
||||||
add_chans = Cpt(EpicsSignal, suffix="NIDAQ-AddChans", kind=Kind.config, auto_monitor=True)
|
|
||||||
|
|
||||||
smpl_abs_ln = Cpt(EpicsSignal, suffix="NIDAQ-smpl_abs_ln", kind=Kind.config, auto_monitor=True)
|
|
||||||
ref_abs_ln = Cpt(EpicsSignal, suffix="NIDAQ-ref_abs_ln", kind=Kind.config, auto_monitor=True)
|
|
||||||
|
|
||||||
smpl_abs_no = Cpt(EpicsSignal, suffix="NIDAQ-smpl_abs_no", kind=Kind.config, auto_monitor=True)
|
|
||||||
smpl_abs_no_string = Cpt(EpicsSignal, suffix="NIDAQ-smpl_abs_no", kind=Kind.config, string=True, auto_monitor=True)
|
|
||||||
|
|
||||||
smpl_abs_de = Cpt(EpicsSignal, suffix="NIDAQ-smpl_abs_de", kind=Kind.config, auto_monitor=True)
|
|
||||||
smpl_abs_de_string = Cpt(EpicsSignal, suffix="NIDAQ-smpl_abs_de", kind=Kind.config, string=True, auto_monitor=True)
|
|
||||||
|
|
||||||
smpl_fluo_no = Cpt(EpicsSignal, suffix="NIDAQ-smpl_fluo_no", kind=Kind.config, auto_monitor=True)
|
|
||||||
smpl_fluo_no_string = Cpt(EpicsSignal, suffix="NIDAQ-smpl_fluo_no", kind=Kind.config, string=True, auto_monitor=True)
|
|
||||||
|
|
||||||
smpl_fluo_de = Cpt(EpicsSignal, suffix="NIDAQ-smpl_fluo_de", kind=Kind.config, auto_monitor=True)
|
|
||||||
smpl_fluo_de_string = Cpt(EpicsSignal, suffix="NIDAQ-smpl_fluo_de", kind=Kind.config, string=True, auto_monitor=True)
|
|
||||||
|
|
||||||
ref_abs_no = Cpt(EpicsSignal, suffix="NIDAQ-ref_abs_no", kind=Kind.config, auto_monitor=True)
|
|
||||||
ref_abs_no_string = Cpt(EpicsSignal, suffix="NIDAQ-ref_abs_no", kind=Kind.config, string=True, auto_monitor=True)
|
|
||||||
|
|
||||||
ref_abs_de = Cpt(EpicsSignal, suffix="NIDAQ-ref_abs_de", kind=Kind.config, auto_monitor=True)
|
|
||||||
ref_abs_de_string = Cpt(EpicsSignal, suffix="NIDAQ-ref_abs_de", kind=Kind.config, string=True, auto_monitor=True)
|
|
||||||
|
|
||||||
|
|
||||||
class Nidaq(PSIDeviceBase, NidaqControl):
|
|
||||||
"""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 = 5 # 5s timeout for pv calls. editted due to timeout issues persisting
|
|
||||||
self.valid_scan_names = [
|
|
||||||
"xas_simple_scan",
|
|
||||||
"xas_simple_scan_with_xrd",
|
|
||||||
"xas_advanced_scan",
|
|
||||||
"xas_advanced_scan_with_xrd",
|
|
||||||
"nidaq_continuous_scan",
|
|
||||||
]
|
|
||||||
|
|
||||||
########################################
|
|
||||||
# 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)
|
|
||||||
# TODO 11.11.25/HS64
|
|
||||||
# Switched from set to put in the hope to get rid of the rare event where nidaq is stopped at the start of a scan
|
|
||||||
# Problems consistently persisting, testing changing back to set, unconvinced this is the actual cause 14.11.25/AHC
|
|
||||||
# self.stage_call.set(1).wait(timeout=self._timeout_wait_for_pv)
|
|
||||||
self.stage_call.put(1)
|
|
||||||
status.wait(timeout=self.timeout_wait_for_signal)
|
|
||||||
if self.scan_info.msg.scan_name != "nidaq_continuous_scan":
|
|
||||||
status = self.on_kickoff()
|
|
||||||
self.cancel_on_stop(status)
|
|
||||||
status.wait(timeout=self._timeout_wait_for_pv)
|
|
||||||
logger.info(f"Device {self.name} was staged: {NidaqState(self.state.get())}")
|
|
||||||
|
|
||||||
def on_kickoff(self) -> DeviceStatus | StatusBase:
|
|
||||||
"""Kickoff the Nidaq"""
|
|
||||||
status = self.kickoff_call.set(1)
|
|
||||||
self.cancel_on_stop(status)
|
|
||||||
return status
|
|
||||||
|
|
||||||
def on_unstage(self) -> DeviceStatus | StatusBase | None:
|
|
||||||
"""Called while unstaging the device. Check that the Nidaq goes into Standby"""
|
|
||||||
|
|
||||||
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)
|
|
||||||
@@ -1,60 +0,0 @@
|
|||||||
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
|
|
||||||
@@ -1,685 +0,0 @@
|
|||||||
"""Pilatus AD integration at Debye beamline."""
|
|
||||||
|
|
||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
import enum
|
|
||||||
import threading
|
|
||||||
import time
|
|
||||||
import traceback
|
|
||||||
from typing import TYPE_CHECKING, Tuple
|
|
||||||
|
|
||||||
import numpy as np
|
|
||||||
from bec_lib.file_utils import get_full_path
|
|
||||||
from bec_lib.logger import bec_logger
|
|
||||||
from ophyd import Component as Cpt
|
|
||||||
from ophyd import EpicsSignal, EpicsSignalRO, Kind
|
|
||||||
from ophyd.areadetector.cam import ADBase, PilatusDetectorCam
|
|
||||||
from ophyd.areadetector.plugins import HDF5Plugin_V22 as HDF5Plugin
|
|
||||||
from ophyd.areadetector.plugins import ImagePlugin_V22 as ImagePlugin
|
|
||||||
from ophyd.status import WaitTimeoutError
|
|
||||||
from ophyd_devices import AndStatus, CompareStatus, DeviceStatus, FileEventSignal, PreviewSignal
|
|
||||||
from ophyd_devices.interfaces.base_classes.psi_device_base import PSIDeviceBase
|
|
||||||
from pydantic import BaseModel, Field
|
|
||||||
|
|
||||||
if TYPE_CHECKING: # pragma: no cover
|
|
||||||
from bec_lib.devicemanager import ScanInfo
|
|
||||||
from bec_lib.messages import DevicePreviewMessage, ScanStatusMessage
|
|
||||||
from bec_server.device_server.device_server import DeviceManagerDS
|
|
||||||
|
|
||||||
PILATUS_READOUT_TIME = 0.1 # in s
|
|
||||||
# PILATUS_ACQUIRE_TIME = (
|
|
||||||
# 999999 # This time is the timeout of the detector in operation mode, so it needs to be large.
|
|
||||||
# )
|
|
||||||
|
|
||||||
# pylint: disable=redefined-outer-name
|
|
||||||
# pylint: disable=raise-missing-from
|
|
||||||
|
|
||||||
logger = bec_logger.logger
|
|
||||||
|
|
||||||
|
|
||||||
class DETECTORSTATE(int, enum.Enum):
|
|
||||||
"""Pilatus Detector States from CamServer"""
|
|
||||||
|
|
||||||
UNARMED = 0
|
|
||||||
ARMED = 1
|
|
||||||
|
|
||||||
|
|
||||||
class ACQUIREMODE(int, enum.Enum):
|
|
||||||
"""Pilatus Acquisition Modes"""
|
|
||||||
|
|
||||||
DONE = 0
|
|
||||||
ACQUIRING = 1
|
|
||||||
|
|
||||||
|
|
||||||
class FILEWRITEMODE(int, enum.Enum):
|
|
||||||
"""HDF5 Plugin FileWrite Mode"""
|
|
||||||
|
|
||||||
SINGLE = 0
|
|
||||||
CAPTURE = 1
|
|
||||||
STREAM = 2
|
|
||||||
|
|
||||||
|
|
||||||
class COMPRESSIONALGORITHM(int, enum.Enum):
|
|
||||||
"""HDF5 Plugin Compression Algorithm"""
|
|
||||||
|
|
||||||
NONE = 0
|
|
||||||
NBIT = 1 # Don't use that..
|
|
||||||
SZIP = 2
|
|
||||||
ZLIB = 3
|
|
||||||
|
|
||||||
|
|
||||||
class TRIGGERMODE(int, enum.Enum):
|
|
||||||
"""Pilatus Trigger Modes"""
|
|
||||||
|
|
||||||
INTERNAL = 0
|
|
||||||
EXT_ENABLE = 1
|
|
||||||
EXT_TRIGGER = 2
|
|
||||||
MULT_TRIGGER = 3
|
|
||||||
ALIGNMENT = 4
|
|
||||||
|
|
||||||
|
|
||||||
class MONOTRIGGERSOURCE(int, enum.Enum):
|
|
||||||
""" "Mono XRD trigger source"""
|
|
||||||
|
|
||||||
EPICS = 0
|
|
||||||
INPOS = 1
|
|
||||||
|
|
||||||
|
|
||||||
class MONOTRIGGERMODE(int, enum.Enum):
|
|
||||||
""" "Mono XRD trigger mode"""
|
|
||||||
|
|
||||||
PULSE = 0
|
|
||||||
CONDITION = 1
|
|
||||||
|
|
||||||
def description(self) -> str:
|
|
||||||
"""Return a description of the trigger mode."""
|
|
||||||
descriptions = {
|
|
||||||
TRIGGERMODE.INTERNAL: "Internal trigger mode, images are acquired on internal trigger.",
|
|
||||||
TRIGGERMODE.EXT_ENABLE: "External Enable trigger mode; check manual as details are currently unknown",
|
|
||||||
TRIGGERMODE.EXT_TRIGGER: "External Trigger mode, images are acquired on external trigger signal. All images on single trigger.",
|
|
||||||
TRIGGERMODE.MULT_TRIGGER: "Multiple External Trigger mode, images are acquired on multiple external trigger signals. One image per trigger.",
|
|
||||||
TRIGGERMODE.ALIGNMENT: "Alignment mode, used for beam alignment.",
|
|
||||||
}
|
|
||||||
return descriptions.get(self, "Unknown")
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return self.description()
|
|
||||||
|
|
||||||
|
|
||||||
class ScanParameter(BaseModel):
|
|
||||||
"""Dataclass to store the scan parameters for the Pilatus.
|
|
||||||
This needs to be in sync with the kwargs of the XRD related scans from Debye, to
|
|
||||||
ensure that the scan parameters are correctly set. Any changes in the scan kwargs,
|
|
||||||
i.e. renaming or adding new parameters, need to be represented here as well."""
|
|
||||||
|
|
||||||
scan_time: float | None = Field(None, description="Scan time for a half oscillation")
|
|
||||||
scan_duration: float | None = Field(None, description="Duration of the scan")
|
|
||||||
break_enable_low: bool | None = Field(
|
|
||||||
None, description="Break enabled for low, should be PV trig_ena_lo_enum"
|
|
||||||
) # trig_enable_low: bool = None
|
|
||||||
break_enable_high: bool | None = Field(
|
|
||||||
None, description="Break enabled for high, should be PV trig_ena_hi_enum"
|
|
||||||
) # trig_enable_high: bool = None
|
|
||||||
break_time_low: float | None = Field(None, description="Break time low energy/angle")
|
|
||||||
break_time_high: float | None = Field(None, description="Break time high energy/angle")
|
|
||||||
cycle_low: int | None = Field(None, description="Cycle for low energy/angle")
|
|
||||||
cycle_high: int | None = Field(None, description="Cycle for high energy/angle")
|
|
||||||
exp_time: float | None = Field(None, description="XRD trigger period")
|
|
||||||
n_of_trigger: int | None = Field(None, description="Amount of XRD triggers")
|
|
||||||
start: float | None = Field(None, description="Start value for energy/angle")
|
|
||||||
stop: float | None = Field(None, description="Stop value for energy/angle")
|
|
||||||
model_config: dict = {"validate_assignment": True}
|
|
||||||
|
|
||||||
|
|
||||||
class Pilatus(PSIDeviceBase, ADBase):
|
|
||||||
"""
|
|
||||||
Pilatus Base integration for Debye.
|
|
||||||
Prefix of the detector is 'X01DA-ES2-PIL:'
|
|
||||||
|
|
||||||
Args:
|
|
||||||
prefix (str) : Prefix for the IOC
|
|
||||||
name (str) : Name of the detector
|
|
||||||
scan_info (ScanInfo | None) : ScanInfo object passed through the device by the device_manager
|
|
||||||
device_manager (DeviceManager | None) : DeviceManager object passed through the device by the device_manager
|
|
||||||
"""
|
|
||||||
|
|
||||||
# USER_ACCESS = ["start_live_mode", "stop_live_mode"]
|
|
||||||
|
|
||||||
cam_gain_menu_string = Cpt(EpicsSignalRO, suffix='cam1:GainMenu', string=True)
|
|
||||||
|
|
||||||
_default_configuration_attrs = [
|
|
||||||
'cam.threshold_energy',
|
|
||||||
'cam.threshold_auto_apply',
|
|
||||||
'cam.gain_menu',
|
|
||||||
'cam_gain_menu_string',
|
|
||||||
'cam.pixel_cut_off',
|
|
||||||
'cam.acquire_time',
|
|
||||||
'cam.num_exposures',
|
|
||||||
'cam.model',
|
|
||||||
]
|
|
||||||
|
|
||||||
cam = Cpt(PilatusDetectorCam, "cam1:")
|
|
||||||
hdf = Cpt(HDF5Plugin, "HDF1:")
|
|
||||||
image1 = Cpt(ImagePlugin, "image1:")
|
|
||||||
filter_number = Cpt(
|
|
||||||
EpicsSignal, "cam1:FileNumber", kind=Kind.omitted, doc="File number for ramdisk"
|
|
||||||
)
|
|
||||||
trigger_shot = Cpt(
|
|
||||||
EpicsSignal,
|
|
||||||
read_pv="X01DA-OP-MO1:BRAGG:xrd_trig_req",
|
|
||||||
write_pv="X01DA-OP-MO1:BRAGG:xrd_trig_req",
|
|
||||||
add_prefix=("a",),
|
|
||||||
kind=Kind.omitted,
|
|
||||||
doc="Trigger PV from MO1 Bragg",
|
|
||||||
)
|
|
||||||
trigger_source = Cpt(
|
|
||||||
EpicsSignal,
|
|
||||||
read_pv="X01DA-OP-MO1:BRAGG:xrd_trig_src_ENUM_RBV",
|
|
||||||
write_pv="X01DA-OP-MO1:BRAGG:xrd_trig_src_ENUM",
|
|
||||||
add_prefix=("a",),
|
|
||||||
kind=Kind.omitted,
|
|
||||||
doc="Trigger Source; PV, 0 : EPICS, 1 : INPOS",
|
|
||||||
)
|
|
||||||
trigger_mode = Cpt(
|
|
||||||
EpicsSignal,
|
|
||||||
read_pv="X01DA-OP-MO1:BRAGG:xrd_trig_mode_ENUM_RBV",
|
|
||||||
write_pv="X01DA-OP-MO1:BRAGG:xrd_trig_mode_ENUM",
|
|
||||||
add_prefix=("a",),
|
|
||||||
kind=Kind.omitted,
|
|
||||||
doc="Trigger Mode; 0 : PULSE, 1 : CONDITION",
|
|
||||||
)
|
|
||||||
trigger_pulse_length = Cpt(
|
|
||||||
EpicsSignal,
|
|
||||||
read_pv="X01DA-OP-MO1:BRAGG:xrd_trig_len_RBV",
|
|
||||||
write_pv="X01DA-OP-MO1:BRAGG:xrd_trig_len",
|
|
||||||
add_prefix=("a",),
|
|
||||||
kind=Kind.omitted,
|
|
||||||
doc="Trigger Period in seconds",
|
|
||||||
)
|
|
||||||
trigger_period = Cpt(
|
|
||||||
EpicsSignal,
|
|
||||||
read_pv="X01DA-OP-MO1:BRAGG:xrd_trig_period_RBV",
|
|
||||||
write_pv="X01DA-OP-MO1:BRAGG:xrd_trig_period",
|
|
||||||
add_prefix=("a",),
|
|
||||||
kind=Kind.omitted,
|
|
||||||
doc="Trigger Pulse Length in seconds",
|
|
||||||
)
|
|
||||||
trigger_n_of = Cpt(
|
|
||||||
EpicsSignal,
|
|
||||||
read_pv="X01DA-OP-MO1:BRAGG:xrd_n_of_trig_RBV",
|
|
||||||
write_pv="X01DA-OP-MO1:BRAGG:xrd_n_of_trig",
|
|
||||||
add_prefix=("a",),
|
|
||||||
kind=Kind.omitted,
|
|
||||||
doc="Number of trigger to generate for each request",
|
|
||||||
)
|
|
||||||
preview = Cpt(
|
|
||||||
PreviewSignal,
|
|
||||||
name="preview",
|
|
||||||
ndim=2,
|
|
||||||
num_rotation_90=3,
|
|
||||||
doc="Preview signal for the Pilatus Detector",
|
|
||||||
)
|
|
||||||
file_event = Cpt(FileEventSignal, name="file_event")
|
|
||||||
|
|
||||||
def __init__(
|
|
||||||
self,
|
|
||||||
*,
|
|
||||||
name: str,
|
|
||||||
prefix: str = "",
|
|
||||||
scan_info: ScanInfo | None = None,
|
|
||||||
device_manager: DeviceManagerDS | None = None,
|
|
||||||
**kwargs,
|
|
||||||
):
|
|
||||||
super().__init__(
|
|
||||||
name=name, prefix=prefix, scan_info=scan_info, device_manager=device_manager, **kwargs
|
|
||||||
)
|
|
||||||
self.scan_parameter = ScanParameter()
|
|
||||||
self.device_manager = device_manager
|
|
||||||
self._readout_time = PILATUS_READOUT_TIME
|
|
||||||
self._full_path = ""
|
|
||||||
self._poll_thread = threading.Thread(
|
|
||||||
target=self._poll_array_data, daemon=True, name=f"{self.name}_poll_thread"
|
|
||||||
)
|
|
||||||
self._poll_thread_kill_event = threading.Event()
|
|
||||||
self._poll_rate = 1 # Poll rate in Hz
|
|
||||||
self.xas_xrd_scan_names = ["xas_simple_scan_with_xrd", "xas_advanced_scan_with_xrd"]
|
|
||||||
self.n_images = None
|
|
||||||
# self._live_mode_thread = threading.Thread(
|
|
||||||
# target=self._live_mode_loop, daemon=True, name=f"{self.name}_live_mode_thread"
|
|
||||||
# )
|
|
||||||
# self._live_mode_kill_event = threading.Event()
|
|
||||||
# self._live_mode_run_event = threading.Event()
|
|
||||||
# self._live_mode_stopped_event = threading.Event()
|
|
||||||
# self._live_mode_stopped_event.set() # Initial state is stopped
|
|
||||||
|
|
||||||
########################################
|
|
||||||
# Custom Beamline Methods #
|
|
||||||
########################################
|
|
||||||
|
|
||||||
def _poll_array_data(self):
|
|
||||||
"""Poll the array data for preview updates."""
|
|
||||||
while not self._poll_thread_kill_event.wait(1 / self._poll_rate):
|
|
||||||
try:
|
|
||||||
# logger.info(f"Running poll loop for {self.name}..")
|
|
||||||
value = self.image1.array_data.get()
|
|
||||||
if value is None:
|
|
||||||
continue
|
|
||||||
width = self.image1.array_size.width.get()
|
|
||||||
height = self.image1.array_size.height.get()
|
|
||||||
# Geometry correction for the image
|
|
||||||
data = np.reshape(value, (height, width))
|
|
||||||
last_image: DevicePreviewMessage = self.preview.get()
|
|
||||||
# logger.info(f"Preview image for {self.name} has shape {data.shape}")
|
|
||||||
if last_image is not None:
|
|
||||||
if np.array_equal(data, last_image.data):
|
|
||||||
# No update if image is the same, ~2.5ms on 2400x2400 image (6M)
|
|
||||||
logger.debug(
|
|
||||||
f"Pilatus preview image for {self.name} is the same as last one, not updating."
|
|
||||||
)
|
|
||||||
continue
|
|
||||||
|
|
||||||
logger.debug(f"Setting preview data for {self.name}")
|
|
||||||
self.preview.put(data)
|
|
||||||
except Exception: # pylint: disable=broad-except
|
|
||||||
content = traceback.format_exc()
|
|
||||||
logger.error(
|
|
||||||
f"Error while polling array data for preview of {self.name}: {content}"
|
|
||||||
)
|
|
||||||
|
|
||||||
# def start_live_mode(self, exp_time: float, n_images_max: int = 50000):
|
|
||||||
# """
|
|
||||||
# Start live mode with given exposure time.
|
|
||||||
|
|
||||||
# Args:
|
|
||||||
# exp_time (float) : Exposure time in seconds
|
|
||||||
# n_images_max (int): Maximum number of images to capture during live mode.
|
|
||||||
# Default is 5000. Only reset if needed.
|
|
||||||
# """
|
|
||||||
# if (
|
|
||||||
# self.cam.acquire.get() != ACQUIREMODE.DONE.value
|
|
||||||
# or self.hdf.capture.get() != ACQUIREMODE.DONE.value
|
|
||||||
# ):
|
|
||||||
# logger.warning(f"Can't start live mode, acquisition running on detector {self.name}.")
|
|
||||||
# return
|
|
||||||
# if self._live_mode_run_event.is_set():
|
|
||||||
# logger.warning(f"Live mode is already running on detector {self.name}.")
|
|
||||||
# return
|
|
||||||
|
|
||||||
# # Set relevant PVs
|
|
||||||
# self.cam.array_counter.set(0).wait(5) # Reset array counter
|
|
||||||
# self.cam.num_images.set(n_images_max).wait(5)
|
|
||||||
# logger.info(
|
|
||||||
# f"Setting exposure time to {exp_time} s for live mode on {self.name} with {n_images_max} images."
|
|
||||||
# )
|
|
||||||
# self.cam.acquire_time.set(exp_time - self._readout_time).wait(5)
|
|
||||||
# self.cam.acquire_period.set(exp_time).wait(5)
|
|
||||||
|
|
||||||
# status = CompareStatus(self.cam.acquire, ACQUIREMODE.DONE.value)
|
|
||||||
# # It should suffice to make sure that self.hdf.capture is not set..
|
|
||||||
# self.cam.acquire.put(1) # Start measurement
|
|
||||||
# try:
|
|
||||||
# status.wait(10)
|
|
||||||
# except WaitTimeoutError:
|
|
||||||
# content = traceback.format_exc()
|
|
||||||
# raise RuntimeError(
|
|
||||||
# f"Live Mode on detector {self.name} did not stop: {content} after 10s."
|
|
||||||
# )
|
|
||||||
# self._live_mode_run_event.set()
|
|
||||||
|
|
||||||
# def _live_mode_loop(self, exp_time: float):
|
|
||||||
# while not self._live_mode_kill_event.is_set():
|
|
||||||
# self._live_mode_run_event.wait()
|
|
||||||
# self._live_mode_stopped_event.clear() # Clear stopped event
|
|
||||||
# time.sleep(self._readout_time) # make sure to wait for the readout_time
|
|
||||||
# n_images = self.cam.array_counter.get()
|
|
||||||
# status = CompareStatus(self.cam.array_counter, n_images + 1)
|
|
||||||
# self.trigger_shot.put(1)
|
|
||||||
# try:
|
|
||||||
# status.wait(60)
|
|
||||||
# except WaitTimeoutError:
|
|
||||||
# logger.warning(
|
|
||||||
# f"Live mode timeout exceeded for {self.name}. Continuing in live_mode_loop"
|
|
||||||
# )
|
|
||||||
# if self._live_mode_run_event.is_set():
|
|
||||||
# self._live_mode_stopped_event.set() # Set stopped event to indicate that live mode loop is stopped
|
|
||||||
|
|
||||||
# def stop_live_mode(self):
|
|
||||||
# """Stop live mode."""
|
|
||||||
# if self._live_mode_stopped_event.is_set():
|
|
||||||
# return
|
|
||||||
# status = CompareStatus(self.cam.acquire, ACQUIREMODE.DONE.value)
|
|
||||||
# self.cam.acquire.put(0)
|
|
||||||
# self._live_mode_run_event.clear()
|
|
||||||
# if not self._live_mode_stopped_event.wait(10): # Wait until live mode loop is stopped
|
|
||||||
# logger.warning(f"Live mode did not stop in time for {self.name}.")
|
|
||||||
# try:
|
|
||||||
# status.wait(10)
|
|
||||||
# except WaitTimeoutError:
|
|
||||||
# content = traceback.format_exc()
|
|
||||||
# raise RuntimeError(
|
|
||||||
# f"Live Mode on detector {self.name} did not stop: {content} after 10s."
|
|
||||||
# )
|
|
||||||
|
|
||||||
def check_detector_stop_running_acquisition(self) -> AndStatus:
|
|
||||||
"""Check if the detector is still running an acquisition."""
|
|
||||||
status_acquire = CompareStatus(self.cam.acquire, ACQUIREMODE.DONE.value)
|
|
||||||
status_writing = CompareStatus(self.hdf.capture, ACQUIREMODE.DONE.value)
|
|
||||||
status_cam_server = CompareStatus(self.cam.armed, DETECTORSTATE.UNARMED.value)
|
|
||||||
status = status_acquire & status_writing & status_cam_server
|
|
||||||
return status
|
|
||||||
|
|
||||||
def _calculate_trigger(self, scan_msg: ScanStatusMessage) -> Tuple[float, float]:
|
|
||||||
self._update_scan_parameter()
|
|
||||||
total_osc = 0
|
|
||||||
calc_duration = 0
|
|
||||||
total_trig_lo = 0
|
|
||||||
total_trig_hi = 0
|
|
||||||
# Switching high/low is intended as angle is inverse to energy and settings in BEC are always in energy
|
|
||||||
loc_break_enable_low = self.scan_parameter.break_enable_high
|
|
||||||
loc_break_time_low = self.scan_parameter.break_time_high
|
|
||||||
loc_cycle_low = self.scan_parameter.cycle_high
|
|
||||||
loc_break_enable_high = self.scan_parameter.break_enable_low
|
|
||||||
loc_break_time_high = self.scan_parameter.break_time_low
|
|
||||||
loc_cycle_high = self.scan_parameter.cycle_low
|
|
||||||
|
|
||||||
if not loc_break_enable_low:
|
|
||||||
loc_break_time_low = 0
|
|
||||||
loc_cycle_low = 1
|
|
||||||
if not loc_break_enable_high:
|
|
||||||
loc_break_time_high = 0
|
|
||||||
loc_cycle_high = 1
|
|
||||||
|
|
||||||
total_osc = self.scan_parameter.scan_duration / (
|
|
||||||
self.scan_parameter.scan_time +
|
|
||||||
loc_break_time_low / (2 * loc_cycle_low) +
|
|
||||||
loc_break_time_high / (2 * loc_cycle_high)
|
|
||||||
)
|
|
||||||
total_osc = np.ceil(total_osc)
|
|
||||||
total_osc = total_osc + total_osc % 2 # round up to the next even number
|
|
||||||
|
|
||||||
if loc_break_enable_low:
|
|
||||||
total_trig_lo = np.floor(total_osc / (2 * loc_cycle_low))
|
|
||||||
if loc_break_enable_high:
|
|
||||||
total_trig_hi = np.floor(total_osc / (2 * loc_cycle_high))
|
|
||||||
calc_duration = total_osc * self.scan_parameter.scan_time + total_trig_lo * loc_break_time_low + total_trig_hi * loc_break_time_high
|
|
||||||
|
|
||||||
if calc_duration < self.scan_parameter.scan_duration:
|
|
||||||
# Due to inaccuracy in formula, this can happen, we then need to manually add two oscillations and recalculate the triggers
|
|
||||||
total_osc = total_osc + 2
|
|
||||||
if loc_break_enable_low:
|
|
||||||
total_trig_lo = np.floor(total_osc / (2 * loc_cycle_low))
|
|
||||||
if loc_break_enable_high:
|
|
||||||
total_trig_hi = np.floor(total_osc / (2 * loc_cycle_high))
|
|
||||||
calc_duration = total_osc * self.scan_parameter.scan_time + total_trig_lo * loc_break_time_low + total_trig_hi * loc_break_time_high
|
|
||||||
|
|
||||||
return total_trig_lo, total_trig_hi
|
|
||||||
|
|
||||||
########################################
|
|
||||||
# Beamline Specific Implementations #
|
|
||||||
########################################
|
|
||||||
|
|
||||||
def on_init(self) -> None:
|
|
||||||
"""
|
|
||||||
Called when the device is initialized.
|
|
||||||
|
|
||||||
No signals are connected at this point. If you like to
|
|
||||||
set default values on signals, please use on_connected instead.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def on_connected(self) -> None:
|
|
||||||
"""
|
|
||||||
Called after the device is connected and its signals are connected.
|
|
||||||
Default values for signals should be set here.
|
|
||||||
"""
|
|
||||||
|
|
||||||
status_cam = CompareStatus(self.cam.acquire, ACQUIREMODE.DONE.value)
|
|
||||||
status_hdf = CompareStatus(self.hdf.capture, ACQUIREMODE.DONE.value)
|
|
||||||
try:
|
|
||||||
status_cam.wait(timeout=5)
|
|
||||||
status_hdf.wait(timeout=5)
|
|
||||||
except WaitTimeoutError:
|
|
||||||
logger.warning(
|
|
||||||
f"Camera device {self.name} was running an acquisition. Stopping acquisition."
|
|
||||||
)
|
|
||||||
self.cam.acquire.put(0)
|
|
||||||
self.hdf.capture.put(0)
|
|
||||||
|
|
||||||
self.cam.trigger_mode.set(TRIGGERMODE.MULT_TRIGGER.value).wait(5)
|
|
||||||
self.cam.image_file_tmot.set(60).wait(5)
|
|
||||||
self.hdf.file_write_mode.set(FILEWRITEMODE.STREAM.value).wait(5)
|
|
||||||
self.hdf.file_template.set("%s%s").wait(5)
|
|
||||||
self.hdf.auto_save.set(1).wait(5)
|
|
||||||
self.hdf.lazy_open.set(1).wait(5)
|
|
||||||
self.hdf.compression.set(COMPRESSIONALGORITHM.NONE.value).wait(5) # To test which to use
|
|
||||||
# Start polling thread...
|
|
||||||
self._poll_thread.start()
|
|
||||||
# Start live mode thread...
|
|
||||||
# self._live_mode_thread.start()
|
|
||||||
|
|
||||||
def on_stage(self) -> DeviceStatus | None:
|
|
||||||
"""
|
|
||||||
Called while staging the device.
|
|
||||||
|
|
||||||
Information about the upcoming scan can be accessed from the scan_info
|
|
||||||
(self.scan_info.msg) object.
|
|
||||||
"""
|
|
||||||
# self.stop_live_mode() # Make sure that live mode is stopped if scan runs
|
|
||||||
|
|
||||||
# If user has activated alignment mode on qt panel, switch back to multitrigger and stop acquisition
|
|
||||||
if self.cam.trigger_mode.get() != TRIGGERMODE.MULT_TRIGGER.value:
|
|
||||||
self.cam.trigger_mode.set(TRIGGERMODE.MULT_TRIGGER.value).wait(5)
|
|
||||||
if self.cam.acquire.get() == ACQUIREMODE.ACQUIRING.value:
|
|
||||||
self.cam.acquire.put(0)
|
|
||||||
status_cam = CompareStatus(self.cam.acquire, ACQUIREMODE.DONE.value)
|
|
||||||
status_cam.wait(timeout=5)
|
|
||||||
|
|
||||||
scan_msg: ScanStatusMessage = self.scan_info.msg
|
|
||||||
if scan_msg.scan_name in self.xas_xrd_scan_names:
|
|
||||||
self._update_scan_parameter()
|
|
||||||
# Compute number of triggers
|
|
||||||
total_trig_lo, total_trig_hi = self._calculate_trigger(scan_msg)
|
|
||||||
# Set the number of images, we may also set this to a higher values if preferred and stop the acquisition
|
|
||||||
# TODO This logic is prone to errors, as we rely on the scans to nicely resolve to n_images. We should
|
|
||||||
# use here instead a way of settings the n_images independently of the scan parameters to avoid running out of sync
|
|
||||||
# with the complete method. Ideally we comput them in the scan itself.. This is much safer IMO!
|
|
||||||
self.n_images = (total_trig_lo + total_trig_hi) * self.scan_parameter.n_of_trigger
|
|
||||||
exp_time = self.scan_parameter.exp_time
|
|
||||||
self.trigger_source.set(MONOTRIGGERSOURCE.INPOS).wait(5)
|
|
||||||
self.trigger_n_of.set(self.scan_parameter.n_of_trigger).wait(5)
|
|
||||||
|
|
||||||
elif scan_msg.scan_type == "step":
|
|
||||||
self.n_images = scan_msg.num_points * scan_msg.scan_parameters.get(
|
|
||||||
"frames_per_trigger", 1
|
|
||||||
)
|
|
||||||
exp_time = scan_msg.scan_parameters.get("exp_time")
|
|
||||||
self.trigger_source.set(MONOTRIGGERSOURCE.EPICS).wait(5)
|
|
||||||
self.trigger_n_of.set(1).wait(5) # BEC will trigger each acquisition
|
|
||||||
else:
|
|
||||||
# TODO how to deal with fly scans?
|
|
||||||
return None
|
|
||||||
# Common settings
|
|
||||||
self.trigger_mode.set(MONOTRIGGERMODE.PULSE).wait(5)
|
|
||||||
self.trigger_period.set(exp_time).wait(5)
|
|
||||||
self.trigger_pulse_length.set(0.005).wait(
|
|
||||||
5
|
|
||||||
) # Pulse length of 5 ms enough for Pilatus and NIDAQ
|
|
||||||
|
|
||||||
if exp_time - self._readout_time <= 0:
|
|
||||||
raise ValueError(
|
|
||||||
(
|
|
||||||
f"Exposure time {exp_time} is too short ",
|
|
||||||
f"for Pilatus with readout_time {self._readout_time}.",
|
|
||||||
)
|
|
||||||
)
|
|
||||||
detector_exp_time = exp_time - self._readout_time
|
|
||||||
self._full_path = get_full_path(scan_msg, name="pilatus")
|
|
||||||
file_path = "/".join(self._full_path.split("/")[:-1])
|
|
||||||
file_name = self._full_path.split("/")[-1]
|
|
||||||
# Prepare detector and backend
|
|
||||||
self.cam.array_callbacks.set(1).wait(5) # Enable array callbacks
|
|
||||||
self.hdf.enable.set(1).wait(5) # Enable HDF5 plugin
|
|
||||||
# Camera settings
|
|
||||||
self.cam.num_exposures.set(1).wait(5)
|
|
||||||
self.cam.num_images.set(self.n_images).wait(5)
|
|
||||||
self.cam.acquire_time.set(detector_exp_time).wait(5) # let's try this
|
|
||||||
self.cam.acquire_period.set(exp_time).wait(5)
|
|
||||||
self.filter_number.set(0).wait(5)
|
|
||||||
# HDF5 settings
|
|
||||||
logger.debug(
|
|
||||||
f"Setting HDF5 file path to {file_path} and file name to {file_name}. full_path is {self._full_path}"
|
|
||||||
)
|
|
||||||
self.hdf.file_path.set(file_path).wait(5)
|
|
||||||
self.hdf.file_name.set(file_name).wait(5)
|
|
||||||
self.hdf.num_capture.set(self.n_images).wait(5)
|
|
||||||
self.cam.array_counter.set(0).wait(5) # Reset array counter
|
|
||||||
self.file_event.put(
|
|
||||||
file_path=self._full_path,
|
|
||||||
done=False,
|
|
||||||
successful=False,
|
|
||||||
hinted_h5_entries={"data": "/entry/data/data"},
|
|
||||||
)
|
|
||||||
|
|
||||||
def on_unstage(self) -> None:
|
|
||||||
"""Called while unstaging the device."""
|
|
||||||
|
|
||||||
def on_pre_scan(self) -> DeviceStatus | None:
|
|
||||||
"""Called right before the scan starts on all devices automatically."""
|
|
||||||
scan_msg: ScanStatusMessage = self.scan_info.msg
|
|
||||||
if (
|
|
||||||
scan_msg.scan_name in self.xas_xrd_scan_names or scan_msg.scan_type == "step"
|
|
||||||
): # TODO how to deal with fly scans?
|
|
||||||
status_hdf = CompareStatus(self.hdf.capture, ACQUIREMODE.ACQUIRING.value)
|
|
||||||
status_cam = CompareStatus(self.cam.acquire, ACQUIREMODE.ACQUIRING.value)
|
|
||||||
status_cam_server = CompareStatus(self.cam.armed, DETECTORSTATE.ARMED.value)
|
|
||||||
status = status_hdf & status_cam & status_cam_server
|
|
||||||
self.cam.acquire.put(1)
|
|
||||||
self.hdf.capture.put(1)
|
|
||||||
self.cancel_on_stop(status)
|
|
||||||
return status
|
|
||||||
else:
|
|
||||||
return None
|
|
||||||
|
|
||||||
def on_trigger(self) -> DeviceStatus | None:
|
|
||||||
"""Called when the device is triggered."""
|
|
||||||
scan_msg: ScanStatusMessage = self.scan_info.msg
|
|
||||||
if not scan_msg.scan_type == "step":
|
|
||||||
return None
|
|
||||||
start_time = time.time()
|
|
||||||
img_counter = self.hdf.num_captured.get()
|
|
||||||
logger.debug(f"Triggering image with num_captured {img_counter}")
|
|
||||||
status = CompareStatus(self.hdf.num_captured, img_counter + 1)
|
|
||||||
logger.debug(f"Triggering took image {time.time() - start_time:.3f} seconds")
|
|
||||||
self.trigger_shot.put(1)
|
|
||||||
self.cancel_on_stop(status)
|
|
||||||
return status
|
|
||||||
|
|
||||||
def _complete_callback(self, status: DeviceStatus):
|
|
||||||
"""Callback for when the device completes a scan."""
|
|
||||||
scan_msg: ScanStatusMessage = self.scan_info.msg
|
|
||||||
if (
|
|
||||||
scan_msg.scan_name in self.xas_xrd_scan_names or scan_msg.scan_type == "step"
|
|
||||||
): # TODO how to deal with fly scans?
|
|
||||||
if status.success:
|
|
||||||
self.file_event.put(
|
|
||||||
file_path=self._full_path,
|
|
||||||
done=True,
|
|
||||||
successful=True,
|
|
||||||
hinted_h5_entries={"data": "/entry/data/data"},
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
self.file_event.put(
|
|
||||||
file_path=self._full_path,
|
|
||||||
done=True,
|
|
||||||
successful=False,
|
|
||||||
hinted_h5_entries={"data": "/entry/data/data"},
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
return None
|
|
||||||
|
|
||||||
def on_complete(self) -> DeviceStatus | None:
|
|
||||||
"""Called to inquire if a device has completed a scans."""
|
|
||||||
scan_msg: ScanStatusMessage = self.scan_info.msg
|
|
||||||
if (
|
|
||||||
scan_msg.scan_name in self.xas_xrd_scan_names or scan_msg.scan_type == "step"
|
|
||||||
): # TODO how to deal with fly scans?
|
|
||||||
status_hdf = CompareStatus(self.hdf.capture, ACQUIREMODE.DONE.value)
|
|
||||||
status_cam = CompareStatus(self.cam.acquire, ACQUIREMODE.DONE.value)
|
|
||||||
status_cam_server = CompareStatus(self.cam.armed, DETECTORSTATE.UNARMED.value)
|
|
||||||
if self.scan_info.msg.scan_name in self.xas_xrd_scan_names:
|
|
||||||
# For long scans, it can be that the mono will execute one cycle more,
|
|
||||||
# meaning a few more XRD triggers will be sent
|
|
||||||
status_img_written = CompareStatus(
|
|
||||||
self.hdf.num_captured, self.n_images, operation_success=">="
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
status_img_written = CompareStatus(self.hdf.num_captured, self.n_images)
|
|
||||||
status_img_written = CompareStatus(self.hdf.num_captured, self.n_images)
|
|
||||||
status = status_hdf & status_cam & status_img_written & status_cam_server
|
|
||||||
status.add_callback(self._complete_callback) # Callback that writing was successful
|
|
||||||
self.cancel_on_stop(status)
|
|
||||||
return status
|
|
||||||
else:
|
|
||||||
return None
|
|
||||||
|
|
||||||
def on_kickoff(self) -> None:
|
|
||||||
"""Called to kickoff a device for a fly scan. Has to be called explicitly."""
|
|
||||||
|
|
||||||
def on_stop(self) -> None:
|
|
||||||
"""Called when the device is stopped."""
|
|
||||||
self.cam.acquire.put(0)
|
|
||||||
self.hdf.capture.put(0)
|
|
||||||
|
|
||||||
def on_destroy(self) -> None:
|
|
||||||
"""Called when the device is destroyed. Cleanup resources here."""
|
|
||||||
self._poll_thread_kill_event.set()
|
|
||||||
# TODO do we need to clean the poll thread ourselves?
|
|
||||||
self.on_stop()
|
|
||||||
|
|
||||||
def _update_scan_parameter(self):
|
|
||||||
"""Get the scan_info parameters for the scan."""
|
|
||||||
for key, value in self.scan_info.msg.request_inputs["inputs"].items():
|
|
||||||
if hasattr(self.scan_parameter, key):
|
|
||||||
setattr(self.scan_parameter, key, value)
|
|
||||||
for key, value in self.scan_info.msg.request_inputs["kwargs"].items():
|
|
||||||
if hasattr(self.scan_parameter, key):
|
|
||||||
setattr(self.scan_parameter, key, value)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
try:
|
|
||||||
pilatus = Pilatus(name="pilatus", prefix="X01DA-ES2-PIL:")
|
|
||||||
logger.info("Calling wait for connection")
|
|
||||||
# pilatus.wait_for_connection(all_signals=True, timeout=20)
|
|
||||||
logger.info("Connecting to pilatus...")
|
|
||||||
pilatus.on_connected()
|
|
||||||
for exp_time, scan_number, n_pnts in zip([0.5, 1.0, 2.0], [1, 2, 3], [30, 20, 10]):
|
|
||||||
logger.info("Sleeping for 5s")
|
|
||||||
time.sleep(5)
|
|
||||||
pilatus.scan_info.msg.num_points = n_pnts
|
|
||||||
pilatus.scan_info.msg.scan_parameters["exp_time"] = exp_time
|
|
||||||
pilatus.scan_info.msg.scan_parameters["frames_per_trigger"] = 1
|
|
||||||
pilatus.scan_info.msg.info["file_components"] = (
|
|
||||||
f"/sls/x01da/data/p22481/raw/data/S00000-00999/S{scan_number:05d}/S{scan_number:05d}",
|
|
||||||
"h5",
|
|
||||||
)
|
|
||||||
pilatus.on_stage()
|
|
||||||
logger.info("Stage done")
|
|
||||||
pilatus.on_pre_scan().wait(timeout=5)
|
|
||||||
logger.info("Pre-scan done")
|
|
||||||
for ii in range(pilatus.scan_info.msg.num_points):
|
|
||||||
# if ii == 0:
|
|
||||||
# time.sleep(1)
|
|
||||||
logger.info(f"Triggering image {ii+1}/{pilatus.scan_info.msg.num_points}")
|
|
||||||
pilatus.on_trigger().wait()
|
|
||||||
p = pilatus.preview.get()
|
|
||||||
if p is not None:
|
|
||||||
p: DevicePreviewMessage
|
|
||||||
logger.warning(
|
|
||||||
f"Preview shape: {p.data.shape}, max: {np.max(p.data)}, min: {np.min(p.data)}, mean: {np.mean(p.data)}"
|
|
||||||
)
|
|
||||||
pilatus.on_complete().wait(timeout=5)
|
|
||||||
logger.info("Complete done")
|
|
||||||
pilatus.on_unstage()
|
|
||||||
logger.info("Unstage done")
|
|
||||||
finally:
|
|
||||||
pilatus.on_destroy()
|
|
||||||
@@ -1,100 +0,0 @@
|
|||||||
"""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
|
|
||||||
|
|
||||||
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_success="!=")
|
|
||||||
status = 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_success="!=")
|
|
||||||
status = status_close & status_error
|
|
||||||
return status
|
|
||||||
else:
|
|
||||||
return None
|
|
||||||
@@ -1,200 +0,0 @@
|
|||||||
"""ES2 Reference Foil Changer"""
|
|
||||||
|
|
||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
import enum
|
|
||||||
from typing import TYPE_CHECKING
|
|
||||||
|
|
||||||
from ophyd import Component as Cpt
|
|
||||||
from ophyd import EpicsSignal, EpicsSignalRO, EpicsSignalWithRBV
|
|
||||||
from ophyd.status import DeviceStatus
|
|
||||||
from ophyd_devices.interfaces.base_classes.psi_device_base import PSIDeviceBase
|
|
||||||
from ophyd_devices.utils.errors import DeviceStopError
|
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
|
||||||
from bec_lib.devicemanager import ScanInfo
|
|
||||||
|
|
||||||
|
|
||||||
class Status(int, enum.Enum):
|
|
||||||
"""Enum class for the status field"""
|
|
||||||
|
|
||||||
BOOT = 0
|
|
||||||
RETRACTED = 1
|
|
||||||
INSERTED = 2
|
|
||||||
MOVING = 3
|
|
||||||
ERROR = 4
|
|
||||||
|
|
||||||
|
|
||||||
class OpMode(int, enum.Enum):
|
|
||||||
"""Enum class for the Operating Mode field"""
|
|
||||||
|
|
||||||
USERMODE = 0
|
|
||||||
MAINTENANCEMODE = 1
|
|
||||||
DIAGNOSTICMODE = 2
|
|
||||||
ERRORMODE = 3
|
|
||||||
|
|
||||||
|
|
||||||
class Reffoilchanger(PSIDeviceBase):
|
|
||||||
"""Class for the ES2 Reference Foil Changer"""
|
|
||||||
|
|
||||||
USER_ACCESS = ["insert"]
|
|
||||||
|
|
||||||
inserted = Cpt(
|
|
||||||
EpicsSignalRO, suffix="ES2-REF:TRY-FilterInserted", kind="config", doc="Inserted indicator"
|
|
||||||
)
|
|
||||||
retracted = Cpt(
|
|
||||||
EpicsSignalRO,
|
|
||||||
suffix="ES2-REF:TRY-FilterRetracted",
|
|
||||||
kind="config",
|
|
||||||
doc="Retracted indicator",
|
|
||||||
)
|
|
||||||
moving = Cpt(EpicsSignalRO, suffix="ES2-REF:ROTY.MOVN", kind="config", doc="Moving indicator")
|
|
||||||
status = Cpt(
|
|
||||||
EpicsSignal, suffix="ES2-REF:SELN-FilterState-ENUM_RBV", kind="config", doc="Status"
|
|
||||||
)
|
|
||||||
status_string = Cpt(
|
|
||||||
EpicsSignal, suffix="ES2-REF:SELN-FilterState-ENUM_RBV", kind="config", doc="Status", string=True
|
|
||||||
)
|
|
||||||
op_mode = Cpt(
|
|
||||||
EpicsSignalWithRBV, suffix="ES2-REF:SELN-OpMode-ENUM", kind="config", doc="Status"
|
|
||||||
)
|
|
||||||
op_mode_string = Cpt(
|
|
||||||
EpicsSignalWithRBV, suffix="ES2-REF:SELN-OpMode-ENUM", kind="config", doc="Status", string=True
|
|
||||||
)
|
|
||||||
ref_set = Cpt(EpicsSignal, suffix="ES2-REF:SELN-SET", kind="config", doc="Requested reference")
|
|
||||||
ref_rb = Cpt(
|
|
||||||
EpicsSignalRO, suffix="ES2-REF:SELN-RB", kind="config", doc="Currently set reference"
|
|
||||||
)
|
|
||||||
|
|
||||||
foil01 = Cpt(EpicsSignalRO, suffix="ES-REFFOIL:FOIL01.DESC", kind="config", doc="Foil 01")
|
|
||||||
foil02 = Cpt(EpicsSignalRO, suffix="ES-REFFOIL:FOIL02.DESC", kind="config", doc="Foil 02")
|
|
||||||
foil03 = Cpt(EpicsSignalRO, suffix="ES-REFFOIL:FOIL03.DESC", kind="config", doc="Foil 03")
|
|
||||||
foil04 = Cpt(EpicsSignalRO, suffix="ES-REFFOIL:FOIL04.DESC", kind="config", doc="Foil 04")
|
|
||||||
foil05 = Cpt(EpicsSignalRO, suffix="ES-REFFOIL:FOIL05.DESC", kind="config", doc="Foil 05")
|
|
||||||
foil06 = Cpt(EpicsSignalRO, suffix="ES-REFFOIL:FOIL06.DESC", kind="config", doc="Foil 06")
|
|
||||||
foil07 = Cpt(EpicsSignalRO, suffix="ES-REFFOIL:FOIL07.DESC", kind="config", doc="Foil 07")
|
|
||||||
foil08 = Cpt(EpicsSignalRO, suffix="ES-REFFOIL:FOIL08.DESC", kind="config", doc="Foil 08")
|
|
||||||
foil09 = Cpt(EpicsSignalRO, suffix="ES-REFFOIL:FOIL09.DESC", kind="config", doc="Foil 09")
|
|
||||||
foil10 = Cpt(EpicsSignalRO, suffix="ES-REFFOIL:FOIL10.DESC", kind="config", doc="Foil 10")
|
|
||||||
foil11 = Cpt(EpicsSignalRO, suffix="ES-REFFOIL:FOIL11.DESC", kind="config", doc="Foil 11")
|
|
||||||
foil12 = Cpt(EpicsSignalRO, suffix="ES-REFFOIL:FOIL12.DESC", kind="config", doc="Foil 12")
|
|
||||||
foil13 = Cpt(EpicsSignalRO, suffix="ES-REFFOIL:FOIL13.DESC", kind="config", doc="Foil 13")
|
|
||||||
foil14 = Cpt(EpicsSignalRO, suffix="ES-REFFOIL:FOIL14.DESC", kind="config", doc="Foil 14")
|
|
||||||
foil15 = Cpt(EpicsSignalRO, suffix="ES-REFFOIL:FOIL15.DESC", kind="config", doc="Foil 15")
|
|
||||||
foil16 = Cpt(EpicsSignalRO, suffix="ES-REFFOIL:FOIL16.DESC", kind="config", doc="Foil 16")
|
|
||||||
foil17 = Cpt(EpicsSignalRO, suffix="ES-REFFOIL:FOIL17.DESC", kind="config", doc="Foil 17")
|
|
||||||
foil18 = Cpt(EpicsSignalRO, suffix="ES-REFFOIL:FOIL18.DESC", kind="config", doc="Foil 18")
|
|
||||||
foil19 = Cpt(EpicsSignalRO, suffix="ES-REFFOIL:FOIL19.DESC", kind="config", doc="Foil 19")
|
|
||||||
foil20 = Cpt(EpicsSignalRO, suffix="ES-REFFOIL:FOIL20.DESC", kind="config", doc="Foil 20")
|
|
||||||
foil21 = Cpt(EpicsSignalRO, suffix="ES-REFFOIL:FOIL21.DESC", kind="config", doc="Foil 21")
|
|
||||||
foil22 = Cpt(EpicsSignalRO, suffix="ES-REFFOIL:FOIL22.DESC", kind="config", doc="Foil 22")
|
|
||||||
foil23 = Cpt(EpicsSignalRO, suffix="ES-REFFOIL:FOIL23.DESC", kind="config", doc="Foil 23")
|
|
||||||
foil24 = Cpt(EpicsSignalRO, suffix="ES-REFFOIL:FOIL24.DESC", kind="config", doc="Foil 24")
|
|
||||||
foil25 = Cpt(EpicsSignalRO, suffix="ES-REFFOIL:FOIL25.DESC", kind="config", doc="Foil 25")
|
|
||||||
foil26 = Cpt(EpicsSignalRO, suffix="ES-REFFOIL:FOIL26.DESC", kind="config", doc="Foil 26")
|
|
||||||
foil27 = Cpt(EpicsSignalRO, suffix="ES-REFFOIL:FOIL27.DESC", kind="config", doc="Foil 27")
|
|
||||||
foil28 = Cpt(EpicsSignalRO, suffix="ES-REFFOIL:FOIL28.DESC", kind="config", doc="Foil 28")
|
|
||||||
foil29 = Cpt(EpicsSignalRO, suffix="ES-REFFOIL:FOIL29.DESC", kind="config", doc="Foil 29")
|
|
||||||
foil30 = Cpt(EpicsSignalRO, suffix="ES-REFFOIL:FOIL30.DESC", kind="config", doc="Foil 30")
|
|
||||||
foil31 = Cpt(EpicsSignalRO, suffix="ES-REFFOIL:FOIL31.DESC", kind="config", doc="Foil 31")
|
|
||||||
foil32 = Cpt(EpicsSignalRO, suffix="ES-REFFOIL:FOIL32.DESC", kind="config", doc="Foil 32")
|
|
||||||
foil33 = Cpt(EpicsSignalRO, suffix="ES-REFFOIL:FOIL33.DESC", kind="config", doc="Foil 33")
|
|
||||||
foil34 = Cpt(EpicsSignalRO, suffix="ES-REFFOIL:FOIL34.DESC", kind="config", doc="Foil 34")
|
|
||||||
foil35 = Cpt(EpicsSignalRO, suffix="ES-REFFOIL:FOIL35.DESC", kind="config", doc="Foil 35")
|
|
||||||
foil36 = Cpt(EpicsSignalRO, suffix="ES-REFFOIL:FOIL36.DESC", kind="config", doc="Foil 36")
|
|
||||||
foil37 = Cpt(EpicsSignalRO, suffix="ES-REFFOIL:FOIL37.DESC", kind="config", doc="Foil 37")
|
|
||||||
foil38 = Cpt(EpicsSignalRO, suffix="ES-REFFOIL:FOIL38.DESC", kind="config", doc="Foil 38")
|
|
||||||
|
|
||||||
def __init__(self, *, name: str, prefix: str = "", scan_info: ScanInfo | None = None, **kwargs):
|
|
||||||
super().__init__(name=name, prefix=prefix, scan_info=scan_info, **kwargs)
|
|
||||||
|
|
||||||
self.foils = [
|
|
||||||
self.foil01,
|
|
||||||
self.foil02,
|
|
||||||
self.foil03,
|
|
||||||
self.foil04,
|
|
||||||
self.foil05,
|
|
||||||
self.foil06,
|
|
||||||
self.foil07,
|
|
||||||
self.foil08,
|
|
||||||
self.foil09,
|
|
||||||
self.foil10,
|
|
||||||
self.foil11,
|
|
||||||
self.foil12,
|
|
||||||
self.foil13,
|
|
||||||
self.foil14,
|
|
||||||
self.foil15,
|
|
||||||
self.foil16,
|
|
||||||
self.foil17,
|
|
||||||
self.foil18,
|
|
||||||
self.foil19,
|
|
||||||
self.foil20,
|
|
||||||
self.foil21,
|
|
||||||
self.foil22,
|
|
||||||
self.foil23,
|
|
||||||
self.foil24,
|
|
||||||
self.foil25,
|
|
||||||
self.foil26,
|
|
||||||
self.foil27,
|
|
||||||
self.foil28,
|
|
||||||
self.foil29,
|
|
||||||
self.foil30,
|
|
||||||
self.foil31,
|
|
||||||
self.foil32,
|
|
||||||
self.foil33,
|
|
||||||
self.foil34,
|
|
||||||
self.foil35,
|
|
||||||
self.foil36,
|
|
||||||
self.foil37,
|
|
||||||
self.foil38,
|
|
||||||
]
|
|
||||||
|
|
||||||
def insert(self, ref: str, wait: bool = False) -> DeviceStatus:
|
|
||||||
"""Insert a reference
|
|
||||||
|
|
||||||
Args:
|
|
||||||
ref (str) : Desired reference foil name, e.g. Fe or Pt
|
|
||||||
wait (bool): If you like to wait for the filling to finish. Default False.
|
|
||||||
"""
|
|
||||||
|
|
||||||
filter_number = -1
|
|
||||||
for i, foil in enumerate(self.foils):
|
|
||||||
if foil.get() == ref:
|
|
||||||
filter_number = i + 1
|
|
||||||
break
|
|
||||||
if filter_number == -1:
|
|
||||||
raise ValueError(f"Requested foil ({ref}) is not in list of available foils")
|
|
||||||
|
|
||||||
if self.op_mode.get() == OpMode.USERMODE:
|
|
||||||
self.ref_set.put(filter_number)
|
|
||||||
|
|
||||||
def wait_for_status():
|
|
||||||
return (
|
|
||||||
(self.status.get() == Status.RETRACTED)
|
|
||||||
or (self.status.get() == Status.MOVING)
|
|
||||||
or (
|
|
||||||
self.ref_rb.get() < (filter_number + 0.2)
|
|
||||||
and self.ref_rb.get() > (filter_number - 0.2)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
timeout = 3
|
|
||||||
if not self.wait_for_condition(wait_for_status, timeout=timeout, check_stopped=True):
|
|
||||||
raise TimeoutError(
|
|
||||||
f"Reference foil changer did not retract the current foil within {timeout}s"
|
|
||||||
)
|
|
||||||
|
|
||||||
def wait_for_change_finished():
|
|
||||||
return self.status.get() == Status.INSERTED and self.op_mode == OpMode.USERMODE
|
|
||||||
|
|
||||||
# Wait until new reference foil is inserted
|
|
||||||
status = self.task_handler.submit_task(
|
|
||||||
task=self.wait_for_condition, task_args=(wait_for_change_finished, 5, True)
|
|
||||||
)
|
|
||||||
if wait:
|
|
||||||
status.wait()
|
|
||||||
return status
|
|
||||||
else:
|
|
||||||
raise DeviceStopError(
|
|
||||||
f"Reference foil changer must be in User Mode but is in {self.op_mode.get(as_string=True)}"
|
|
||||||
)
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
def patch_dual_pvs(device):
|
|
||||||
device.wait_for_connection(all_signals=True)
|
|
||||||
for walk in device.walk_signals():
|
|
||||||
if not hasattr(walk.item, "_read_pv"):
|
|
||||||
continue
|
|
||||||
if not hasattr(walk.item, "_write_pv"):
|
|
||||||
continue
|
|
||||||
if walk.item._read_pv.pvname.endswith("_RBV"):
|
|
||||||
walk.item._read_pv = walk.item._write_pv
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
from .debye_nexus_structure import DebyeNexusStructure
|
|
||||||
@@ -1,353 +0,0 @@
|
|||||||
from bec_server.file_writer.default_writer import DefaultFormat
|
|
||||||
|
|
||||||
import debye_bec.bec_widgets.widgets.x01da_parameters as bl
|
|
||||||
|
|
||||||
class DebyeNexusStructure(DefaultFormat):
|
|
||||||
"""Nexus Structure for Debye"""
|
|
||||||
|
|
||||||
def format(self) -> None:
|
|
||||||
"""Specify the file format for the file writer."""
|
|
||||||
|
|
||||||
entry = self.storage.create_group(name="entry")
|
|
||||||
entry.attrs["NX_class"] = "NXentry"
|
|
||||||
instrument = entry.create_group(name="instrument")
|
|
||||||
instrument.attrs["NX_class"] = "NXinstrument"
|
|
||||||
|
|
||||||
##################
|
|
||||||
## source specific information
|
|
||||||
###################
|
|
||||||
|
|
||||||
source = instrument.create_group(name="source")
|
|
||||||
source.attrs["NX_class"] = "NXsource"
|
|
||||||
|
|
||||||
beamline_name = source.create_dataset(name="beamline_name", data="Debye")
|
|
||||||
beamline_name.attrs["NX_class"] = "NX_CHAR"
|
|
||||||
|
|
||||||
facility_name = source.create_dataset(name="facility_name", data="Swiss Light Source")
|
|
||||||
facility_name.attrs["NX_class"] = "NX_CHAR"
|
|
||||||
|
|
||||||
probe = source.create_dataset(name="probe", data="X-ray")
|
|
||||||
probe.attrs["NX_class"] = "NX_CHAR"
|
|
||||||
|
|
||||||
if "curr" in self.device_manager.devices:
|
|
||||||
ring_current = source.create_soft_link(
|
|
||||||
name="ring_current",
|
|
||||||
target="/entry/collection/devices/curr/curr/value",
|
|
||||||
)
|
|
||||||
ring_current.attrs["NX_class"] = "NX_FLOAT"
|
|
||||||
ring_current.attrs["units"] = "mA"
|
|
||||||
|
|
||||||
###################
|
|
||||||
## mo1_bragg specific information
|
|
||||||
###################
|
|
||||||
|
|
||||||
## Logic if device exist
|
|
||||||
if "mo1_bragg" in self.device_manager.devices:
|
|
||||||
|
|
||||||
monochromator = instrument.create_group(name="monochromator")
|
|
||||||
monochromator.attrs["NX_class"] = "NXmonochromator"
|
|
||||||
crystal = monochromator.create_group(name="crystal")
|
|
||||||
crystal.attrs["NX_class"] = "NXcrystal"
|
|
||||||
|
|
||||||
# Create a dataset
|
|
||||||
chemical_formular = crystal.create_dataset(name="chemical_formular", data="Si")
|
|
||||||
chemical_formular.attrs["NX_class"] = "NX_CHAR"
|
|
||||||
|
|
||||||
reflection = crystal.create_soft_link(
|
|
||||||
name="reflection",
|
|
||||||
target="/entry/collection/devices/mo1_bragg/mo1_bragg_crystal_current_xtal_string/value",
|
|
||||||
)
|
|
||||||
reflection.attrs["NX_class"] = "NX_CHAR"
|
|
||||||
|
|
||||||
# Create a softlink
|
|
||||||
d_spacing = crystal.create_soft_link(
|
|
||||||
name="d_spacing",
|
|
||||||
target="/entry/collection/devices/mo1_bragg/mo1_bragg_crystal_current_d_spacing/value",
|
|
||||||
)
|
|
||||||
d_spacing.attrs["NX_class"] = "NX_FLOAT"
|
|
||||||
d_spacing.attrs["units"] = "angstrom"
|
|
||||||
|
|
||||||
bragg_offset = crystal.create_soft_link(
|
|
||||||
name="bragg_offset",
|
|
||||||
target="/entry/collection/devices/mo1_bragg/mo1_bragg_crystal_current_bragg_off/value",
|
|
||||||
)
|
|
||||||
bragg_offset.attrs["NX_class"] = "NX_FLOAT"
|
|
||||||
bragg_offset.attrs["units"] = "degree"
|
|
||||||
|
|
||||||
phi_offset = crystal.create_soft_link(
|
|
||||||
name="phi_offset",
|
|
||||||
target="/entry/collection/devices/mo1_bragg/mo1_bragg_crystal_current_phi_off/value",
|
|
||||||
)
|
|
||||||
phi_offset.attrs["NX_class"] = "NX_FLOAT"
|
|
||||||
phi_offset.attrs["units"] = "degree"
|
|
||||||
|
|
||||||
## Logic if device exist
|
|
||||||
if "mo1_roty" in self.device_manager.devices:
|
|
||||||
|
|
||||||
# Create a softlink
|
|
||||||
azimuthal_angle = crystal.create_soft_link(
|
|
||||||
name="azimuthal_angle",
|
|
||||||
target="/entry/collection/devices/mo1_roty/mo1_roty/value",
|
|
||||||
)
|
|
||||||
azimuthal_angle.attrs["NX_class"] = "NX_FLOAT"
|
|
||||||
azimuthal_angle.attrs["units"] = "degree"
|
|
||||||
|
|
||||||
azm_offset = crystal.create_soft_link(
|
|
||||||
name="azm_offset",
|
|
||||||
target="/entry/collection/devices/mo1_bragg/mo1_bragg_crystal_current_azm_off/value",
|
|
||||||
)
|
|
||||||
azm_offset.attrs["NX_class"] = "NX_FLOAT"
|
|
||||||
azm_offset.attrs["units"] = "degree"
|
|
||||||
|
|
||||||
miscut = crystal.create_soft_link(
|
|
||||||
name="miscut",
|
|
||||||
target="/entry/collection/devices/mo1_bragg/mo1_bragg_crystal_current_miscut/value",
|
|
||||||
)
|
|
||||||
miscut.attrs["NX_class"] = "NX_FLOAT"
|
|
||||||
miscut.attrs["units"] = "degree"
|
|
||||||
|
|
||||||
###################
|
|
||||||
### cm mirror specific information
|
|
||||||
####################
|
|
||||||
|
|
||||||
collimating_mirror = instrument.create_group(name="collimating_mirror")
|
|
||||||
collimating_mirror.attrs["NX_class"] = "NXmirror"
|
|
||||||
|
|
||||||
cm_substrate_material = collimating_mirror.create_dataset(
|
|
||||||
name="substrate_material", data="Si"
|
|
||||||
)
|
|
||||||
cm_substrate_material.attrs["NX_class"] = "NX_CHAR"
|
|
||||||
|
|
||||||
#previous error due to space in name field
|
|
||||||
|
|
||||||
if "cm_bnd_radius" in self.device_manager.devices:
|
|
||||||
cm_bending_radius = collimating_mirror.create_soft_link(
|
|
||||||
name="sagittal_radius",
|
|
||||||
target="/entry/collection/devices/cm_bnd_radius/cm_bnd_radius/value",
|
|
||||||
)
|
|
||||||
cm_bending_radius.attrs["NX_class"] = "NX_FLOAT"
|
|
||||||
cm_bending_radius.attrs["units"] = "km"
|
|
||||||
|
|
||||||
if "cm_rotx" in self.device_manager.devices:
|
|
||||||
cm_incidence_angle = collimating_mirror.create_soft_link(
|
|
||||||
name="incidence_angle", target="/entry/collection/devices/cm_rotx/cm_rotx/value"
|
|
||||||
)
|
|
||||||
cm_incidence_angle.attrs["NX_class"] = "NX_FLOAT"
|
|
||||||
cm_incidence_angle.attrs["units"] = "mrad"
|
|
||||||
|
|
||||||
if "cm_roty" in self.device_manager.devices:
|
|
||||||
cm_yaw_angle = collimating_mirror.create_soft_link(
|
|
||||||
name="yaw_angle", target="/entry/collection/devices/cm_roty/cm_roty/value"
|
|
||||||
)
|
|
||||||
cm_yaw_angle.attrs["NX_class"] = "NX_FLOAT"
|
|
||||||
cm_yaw_angle.attrs["units"] = "mrad"
|
|
||||||
|
|
||||||
if "cm_rotz" in self.device_manager.devices:
|
|
||||||
cm_roll_angle = collimating_mirror.create_soft_link(
|
|
||||||
name="roll_angle", target="/entry/collection/devices/cm_rotz/cm_rotz/value"
|
|
||||||
)
|
|
||||||
cm_roll_angle.attrs["NX_class"] = "NX_FLOAT"
|
|
||||||
cm_roll_angle.attrs["units"] = "mrad"
|
|
||||||
|
|
||||||
if 'cm_trx' in self.device_manager.devices:
|
|
||||||
cm_trx = - self.device_manager.devices.cm_trx.read(cached=True).get('cm_trx').get('value')
|
|
||||||
stripe = 'Unknown'
|
|
||||||
for name, low, high in zip(bl.cm.surface, bl.cm.limOptX[0], bl.cm.limOptX[1]):
|
|
||||||
if low <= cm_trx <= high:
|
|
||||||
stripe = name
|
|
||||||
cm_stripe = collimating_mirror.create_dataset(
|
|
||||||
name="stripe", data=stripe
|
|
||||||
)
|
|
||||||
cm_stripe.attrs["NX_class"] = "NX_CHAR"
|
|
||||||
|
|
||||||
###################
|
|
||||||
### fm mirror specific information
|
|
||||||
####################
|
|
||||||
|
|
||||||
focusing_mirror = instrument.create_group(name="focusing_mirror")
|
|
||||||
focusing_mirror.attrs["NX_class"] = "NXmirror"
|
|
||||||
|
|
||||||
fm_substrate_material = focusing_mirror.create_dataset(
|
|
||||||
name="substrate_material", data="Si"
|
|
||||||
)
|
|
||||||
fm_substrate_material.attrs["NX_class"] = "NX_CHAR"
|
|
||||||
|
|
||||||
if "fm_bnd_radius" in self.device_manager.devices:
|
|
||||||
fm_bending_radius = focusing_mirror.create_soft_link(
|
|
||||||
name="sagittal_radius",
|
|
||||||
target="/entry/collection/devices/fm_bnd_radius/fm_bnd_radius/value",
|
|
||||||
)
|
|
||||||
fm_bending_radius.attrs["NX_class"] = "NX_FLOAT"
|
|
||||||
fm_bending_radius.attrs["units"] = "km"
|
|
||||||
|
|
||||||
if "fm_rotx" in self.device_manager.devices:
|
|
||||||
fm_incidence_angle = focusing_mirror.create_soft_link(
|
|
||||||
name="incidence_angle", target="/entry/collection/devices/fm_rotx/fm_rotx/value"
|
|
||||||
)
|
|
||||||
fm_incidence_angle.attrs["NX_class"] = "NX_FLOAT"
|
|
||||||
fm_incidence_angle.attrs["units"] = "mrad"
|
|
||||||
|
|
||||||
if "fm_roty" in self.device_manager.devices:
|
|
||||||
fm_yaw_angle = focusing_mirror.create_soft_link(
|
|
||||||
name="yaw_angle", target="/entry/collection/devices/fm_roty/fm_roty/value"
|
|
||||||
)
|
|
||||||
fm_yaw_angle.attrs["NX_class"] = "NX_FLOAT"
|
|
||||||
fm_yaw_angle.attrs["units"] = "mrad"
|
|
||||||
|
|
||||||
if "fm_rotz" in self.device_manager.devices:
|
|
||||||
fm_roll_angle = focusing_mirror.create_soft_link(
|
|
||||||
name="roll_angle", target="/entry/collection/devices/fm_rotz/fm_rotz/value"
|
|
||||||
)
|
|
||||||
fm_roll_angle.attrs["NX_class"] = "NX_FLOAT"
|
|
||||||
fm_roll_angle.attrs["units"] = "mrad"
|
|
||||||
|
|
||||||
if 'fm_trx' in self.device_manager.devices:
|
|
||||||
fm_trx = - self.device_manager.devices.fm_trx.read(cached=True).get('fm_trx').get('value')
|
|
||||||
stripe = 'Unknown'
|
|
||||||
for name, low, high in zip(bl.fm.surfaceFlat, bl.fm.limOptXFlat[1], bl.fm.limOptXFlat[0]):
|
|
||||||
if low <= fm_trx <= high:
|
|
||||||
stripe = name + ' (flat)'
|
|
||||||
for name, low, high in zip(bl.fm.surfaceToroid, bl.fm.limOptXToroid[1], bl.fm.limOptXToroid[0]):
|
|
||||||
if low <= fm_trx <= high:
|
|
||||||
stripe = name + ' (toroid)'
|
|
||||||
fm_stripe = focusing_mirror.create_dataset(
|
|
||||||
name="stripe", data=stripe
|
|
||||||
)
|
|
||||||
fm_stripe.attrs["NX_class"] = "NX_CHAR"
|
|
||||||
|
|
||||||
###################
|
|
||||||
## nidaq specific information
|
|
||||||
###################
|
|
||||||
|
|
||||||
## Logic if device exist
|
|
||||||
if "nidaq" in self.device_manager.devices:
|
|
||||||
|
|
||||||
#ai_chans_bits = self.device_manager.devices.nidaq.ai_chans.read(cached=True).get("nidaq_ai_chans").get("value")
|
|
||||||
ai_chans_bits = self.configuration.get("nidaq", {}).get("nidaq_ai_chans", {}).get("value")
|
|
||||||
ci_chans_bits = self.configuration.get("nidaq", {}).get("nidaq_ci_chans", {}).get("value")
|
|
||||||
#add_chans_bits = self.device_manager.devices.nidaq.add_chans.read(cached=True).get("nidaq_add_chans").get("value")
|
|
||||||
add_chans_bits = self.configuration.get("nidaq", {}).get("nidaq_add_chans", {}).get("value")
|
|
||||||
|
|
||||||
measurement_mode = entry.create_group(name="mode")
|
|
||||||
measurement_mode.attrs["NX_class"] = "NX_CHAR"
|
|
||||||
|
|
||||||
if (int(ci_chans_bits) & 0x7F) != 0:
|
|
||||||
# Create a dataset
|
|
||||||
rayspec_sdd_active = measurement_mode.create_group(name="Multi_Element_Partial_Fluorescence_Yield")
|
|
||||||
me_sdd = rayspec_sdd_active.create_dataset(name="Detector", data="Rayspec 7 element Silicon Drift Detector")
|
|
||||||
me_sdd.attrs["NX_class"] = "NX_CHAR"
|
|
||||||
|
|
||||||
if (int(ci_chans_bits) & (1<<8)) != 0:
|
|
||||||
# Create a dataset
|
|
||||||
ketek_sdd_active = measurement_mode.create_group(name="Single_Element_Partial_Fluorescence_Yield")
|
|
||||||
se_sdd = ketek_sdd_active.create_dataset(name="Detector", data="Ketex mini single element Silicon Drift Detector")
|
|
||||||
se_sdd.attrs["NX_class"] = "NX_CHAR"
|
|
||||||
|
|
||||||
if ((int(ai_chans_bits) & (1<<6)) != 0):
|
|
||||||
# Create a dataset
|
|
||||||
pips_active = measurement_mode.create_group(name="Total_Flourescence_Yield")
|
|
||||||
tfy = pips_active.create_dataset(name="Detector", data="Mirion Technologies Partially Depeleted PIPS Detector")
|
|
||||||
tfy.attrs["NX_class"] = "NX_CHAR"
|
|
||||||
|
|
||||||
if ((int(ai_chans_bits) & (1<<0)) != 0) & ((int(ai_chans_bits) & (1<<2)) != 0):
|
|
||||||
# Create a dataset
|
|
||||||
ai0ai2_active = measurement_mode.create_group(name="Sample_Transmission")
|
|
||||||
sam_trans = ai0ai2_active.create_dataset(name="Detector", data="Ionitec 15 cm gas filled Ionisation Chambers")
|
|
||||||
sam_trans.attrs["NX_class"] = "NX_CHAR"
|
|
||||||
|
|
||||||
if ((int(ai_chans_bits) & (1<<2)) != 0) & ((int(ai_chans_bits) & (1<<4)) != 0):
|
|
||||||
# Create a dataset
|
|
||||||
ai2ai4_active = measurement_mode.create_group(name="Reference_Transmission")
|
|
||||||
ref_trans = ai2ai4_active.create_dataset(name="Detector", data="Ionitec 15 cm gas filled Ionisation Chambers")
|
|
||||||
ref_trans.attrs["NX_class"] = "NX_CHAR"
|
|
||||||
|
|
||||||
main_data = entry.create_group(name="data")
|
|
||||||
main_data.attrs["NX_class"] = "NXdata"
|
|
||||||
|
|
||||||
##################
|
|
||||||
## energy, test whether the signal exists. how to check from config?
|
|
||||||
###################
|
|
||||||
|
|
||||||
energy = main_data.create_group(name="energy")
|
|
||||||
energy.attrs["NX_class"] = "NXdata"
|
|
||||||
energy.attrs["units"] = "eV"
|
|
||||||
|
|
||||||
main_data.create_soft_link(name="energy", target="/entry/collection/readout_groups/async/nidaq/nidaq_energy/value")
|
|
||||||
|
|
||||||
##################
|
|
||||||
## i0
|
|
||||||
###################
|
|
||||||
|
|
||||||
if (int(ai_chans_bits) & (1<<0)) !=0:
|
|
||||||
i0 = main_data.create_group(name="i0")
|
|
||||||
i0.attrs["NX_class"] = "NXdata"
|
|
||||||
i0.attrs["units"] = "V"
|
|
||||||
|
|
||||||
main_data.create_soft_link(name="i0", target="/entry/collection/readout_groups/async/nidaq/nidaq_ai0_mean/value")
|
|
||||||
|
|
||||||
##################
|
|
||||||
## i1
|
|
||||||
###################
|
|
||||||
|
|
||||||
if (int(ai_chans_bits) & (1<<2)) !=0:
|
|
||||||
i1 = main_data.create_group(name="i1")
|
|
||||||
i1.attrs["NX_class"] = "NXdata"
|
|
||||||
i1.attrs["units"] = "V"
|
|
||||||
|
|
||||||
main_data.create_soft_link(name="i1", target="/entry/collection/readout_groups/async/nidaq/nidaq_ai2_mean/value")
|
|
||||||
|
|
||||||
##################
|
|
||||||
## i2
|
|
||||||
###################
|
|
||||||
|
|
||||||
if (int(ai_chans_bits) & (1<<4)) !=0:
|
|
||||||
i2 = main_data.create_group(name="i2")
|
|
||||||
i2.attrs["NX_class"] = "NXdata"
|
|
||||||
i2.attrs["units"] = "V"
|
|
||||||
|
|
||||||
main_data.create_soft_link(name="i2", target="/entry/collection/readout_groups/async/nidaq/nidaq_ai4_mean/value")
|
|
||||||
|
|
||||||
##################
|
|
||||||
## ci sum
|
|
||||||
###################
|
|
||||||
|
|
||||||
if int(ci_chans_bits) > 0:
|
|
||||||
ci_sum = main_data.create_group(name="Fluorescence_Sum")
|
|
||||||
ci_sum.attrs["NX_class"] = "NXdata"
|
|
||||||
ci_sum.attrs["units"] = "counts"
|
|
||||||
|
|
||||||
main_data.create_soft_link(name="Fluorescence_Sum", target="/entry/collection/readout_groups/async/nidaq/nidaq_cisum/value")
|
|
||||||
|
|
||||||
##################
|
|
||||||
## mu sample, test whether the signal exists. how to check from config?
|
|
||||||
###################
|
|
||||||
|
|
||||||
if (int(add_chans_bits) & (1<<0)) !=0:
|
|
||||||
mu_sample = main_data.create_group(name="mu_sample")
|
|
||||||
mu_sample.attrs["NX_class"] = "NXdata"
|
|
||||||
|
|
||||||
main_data.create_soft_link(name="mu_sample", target="/entry/collection/readout_groups/async/nidaq/nidaq_smpl_abs/value")
|
|
||||||
|
|
||||||
##################
|
|
||||||
## fluo sample, test whether the signal exists. how to check from config?
|
|
||||||
###################
|
|
||||||
|
|
||||||
if (int(add_chans_bits) & (1<<1)) !=0:
|
|
||||||
mu_sample = main_data.create_group(name="fluo_sample")
|
|
||||||
mu_sample.attrs["NX_class"] = "NXdata"
|
|
||||||
|
|
||||||
main_data.create_soft_link(name="fluo_sample", target="/entry/collection/readout_groups/async/nidaq/nidaq_smpl_fluo/value")
|
|
||||||
|
|
||||||
##################
|
|
||||||
## mu reference, test whether the signal exists. how to check from config?
|
|
||||||
###################
|
|
||||||
|
|
||||||
if (int(add_chans_bits) & (1<<2)) !=0:
|
|
||||||
mu_reference = main_data.create_group(name="mu_reference")
|
|
||||||
mu_reference.attrs["NX_class"] = "NXdata"
|
|
||||||
|
|
||||||
main_data.create_soft_link(name="mu_reference", target="/entry/collection/readout_groups/async/nidaq/nidaq_ref_abs/value")
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
# 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
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
from .mono_bragg_scans import (
|
|
||||||
XASAdvancedScan,
|
|
||||||
XASAdvancedScanWithXRD,
|
|
||||||
XASSimpleScan,
|
|
||||||
XASSimpleScanWithXRD,
|
|
||||||
)
|
|
||||||
from .nidaq_cont_scan import NIDAQContinuousScan
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
# 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
|
|
||||||
@@ -1,34 +0,0 @@
|
|||||||
# # 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.
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
from bec_lib.metadata_schema import BasicScanMetadata
|
|
||||||
|
|
||||||
|
|
||||||
#
|
|
||||||
#
|
|
||||||
class xas_simple_scan_schema(BasicScanMetadata):
|
|
||||||
Edge: str
|
|
||||||
Element: str
|
|
||||||
@@ -1,310 +0,0 @@
|
|||||||
"""This module contains the scan classes for the mono bragg motor of the Debye beamline."""
|
|
||||||
|
|
||||||
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 XASSimpleScan(AsyncFlyScanBase):
|
|
||||||
"""Class for the XAS simple scan"""
|
|
||||||
|
|
||||||
scan_name = "xas_simple_scan"
|
|
||||||
scan_type = "fly"
|
|
||||||
scan_report_hint = "device_progress"
|
|
||||||
required_kwargs = []
|
|
||||||
use_scan_progress_report = False
|
|
||||||
pre_move = False
|
|
||||||
gui_config = {
|
|
||||||
"Movement Parameters": ["start", "stop"],
|
|
||||||
"Scan Parameters": ["scan_time", "scan_duration"],
|
|
||||||
}
|
|
||||||
|
|
||||||
def __init__(
|
|
||||||
self,
|
|
||||||
start: float,
|
|
||||||
stop: float,
|
|
||||||
scan_time: float,
|
|
||||||
scan_duration: float,
|
|
||||||
motor: DeviceBase = "mo1_bragg",
|
|
||||||
**kwargs,
|
|
||||||
):
|
|
||||||
"""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 is the duration of the scan. If scan duration is set to 0, the
|
|
||||||
scan will run infinitely.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
start (float): Start energy for the scan.
|
|
||||||
stop (float): Stop energy for the scan.
|
|
||||||
scan_time (float): Time for one scan cycle.
|
|
||||||
scan_duration (float): Duration of the scan.
|
|
||||||
motor (DeviceBase, optional): Motor device to be used for the scan.
|
|
||||||
Defaults to "mo1_bragg".
|
|
||||||
Examples:
|
|
||||||
>>> scans.xas_simple_scan(start=8000, stop=9000, scan_time=1, scan_duration=10)
|
|
||||||
"""
|
|
||||||
super().__init__(**kwargs)
|
|
||||||
self.motor = motor
|
|
||||||
self.start = start
|
|
||||||
self.stop = stop
|
|
||||||
self.scan_time = scan_time
|
|
||||||
self.scan_duration = scan_duration
|
|
||||||
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):
|
|
||||||
"""Prepare the positions for the scan.
|
|
||||||
|
|
||||||
Use here only start and end energy defining the range for the scan.
|
|
||||||
"""
|
|
||||||
self.positions = np.array([self.start, self.stop], dtype=float)
|
|
||||||
self.num_pos = None
|
|
||||||
yield None
|
|
||||||
|
|
||||||
def pre_scan(self):
|
|
||||||
"""Pre Scan action."""
|
|
||||||
|
|
||||||
self._check_limits()
|
|
||||||
# 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.motor]})
|
|
||||||
|
|
||||||
def scan_core(self):
|
|
||||||
"""Run the scan core.
|
|
||||||
Kickoff the oscillation on the Bragg motor and wait for the completion of the motion.
|
|
||||||
"""
|
|
||||||
# Start the oscillation on the Bragg motor.
|
|
||||||
yield from self.stubs.kickoff(device=self.motor)
|
|
||||||
complete_status = yield from self.stubs.complete(device=self.motor, 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
|
|
||||||
|
|
||||||
|
|
||||||
class XASSimpleScanWithXRD(XASSimpleScan):
|
|
||||||
"""Class for the XAS simple scan with XRD"""
|
|
||||||
|
|
||||||
scan_name = "xas_simple_scan_with_xrd"
|
|
||||||
gui_config = {
|
|
||||||
"Movement Parameters": ["start", "stop"],
|
|
||||||
"Scan Parameters": ["scan_time", "scan_duration"],
|
|
||||||
"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,
|
|
||||||
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_simple_scan_with_xrd is an oscillation motion on the mono motor
|
|
||||||
with XRD triggering at low and high energy ranges.
|
|
||||||
If scan duration is set to 0, the scan will run infinitely.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
start (float): Start energy for the scan.
|
|
||||||
stop (float): Stop energy for the scan.
|
|
||||||
scan_time (float): Time for one oscillation .
|
|
||||||
scan_duration (float): Total duration of the scan.
|
|
||||||
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_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__(
|
|
||||||
start=start,
|
|
||||||
stop=stop,
|
|
||||||
scan_time=scan_time,
|
|
||||||
scan_duration=scan_duration,
|
|
||||||
motor=motor,
|
|
||||||
**kwargs,
|
|
||||||
)
|
|
||||||
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
|
|
||||||
|
|
||||||
|
|
||||||
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
|
|
||||||
@@ -1,84 +0,0 @@
|
|||||||
"""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
|
|
||||||
@@ -1,33 +0,0 @@
|
|||||||
"""
|
|
||||||
SCAN PLUGINS
|
|
||||||
|
|
||||||
All new scans should be derived from ScanBase. ScanBase provides various methods that can be customized and overriden
|
|
||||||
but they are executed in a specific order:
|
|
||||||
|
|
||||||
- self.initialize # initialize the class if needed
|
|
||||||
- self.read_scan_motors # used to retrieve the start position (and the relative position shift if needed)
|
|
||||||
- self.prepare_positions # prepare the positions for the scan. The preparation is split into multiple sub fuctions:
|
|
||||||
- self._calculate_positions # calculate the positions
|
|
||||||
- self._set_positions_offset # apply the previously retrieved scan position shift (if needed)
|
|
||||||
- self._check_limits # tests to ensure the limits won't be reached
|
|
||||||
- self.open_scan # send an open_scan message including the scan name, the number of points and the scan motor names
|
|
||||||
- self.stage # stage all devices for the upcoming acquisiton
|
|
||||||
- self.run_baseline_readings # read all devices to get a baseline for the upcoming scan
|
|
||||||
- self.pre_scan # perform additional actions before the scan starts
|
|
||||||
- self.scan_core # run a loop over all position
|
|
||||||
- self._at_each_point(ind, pos) # called at each position with the current index and the target positions as arguments
|
|
||||||
- self.finalize # clean up the scan, e.g. move back to the start position; wait everything to finish
|
|
||||||
- self.unstage # unstage all devices that have been staged before
|
|
||||||
- self.cleanup # send a close scan message and perform additional cleanups if needed
|
|
||||||
"""
|
|
||||||
|
|
||||||
# import time
|
|
||||||
|
|
||||||
# import numpy as np
|
|
||||||
|
|
||||||
# from bec_lib import bec_logger, messages
|
|
||||||
# from bec_lib.endpoints import MessageEndpoints
|
|
||||||
# from bec_server.scan_server.errors import ScanAbortion
|
|
||||||
# from bec_server.scan_server.scans import FlyScanBase, RequestBase, ScanArgType, ScanBase
|
|
||||||
|
|
||||||
# logger = bec_logger.logger
|
|
||||||
@@ -1,85 +0,0 @@
|
|||||||
[build-system]
|
|
||||||
requires = ["hatchling"]
|
|
||||||
build-backend = "hatchling.build"
|
|
||||||
|
|
||||||
[project]
|
|
||||||
name = "debye_bec"
|
|
||||||
version = "0.0.0"
|
|
||||||
description = "A plugin repository for BEC"
|
|
||||||
requires-python = ">=3.11"
|
|
||||||
classifiers = [
|
|
||||||
"Development Status :: 3 - Alpha",
|
|
||||||
"Programming Language :: Python :: 3",
|
|
||||||
"Topic :: Scientific/Engineering",
|
|
||||||
]
|
|
||||||
dependencies = [
|
|
||||||
"numpy",
|
|
||||||
"scipy",
|
|
||||||
"bec_lib",
|
|
||||||
"h5py",
|
|
||||||
"ophyd_devices",
|
|
||||||
"opencv-python==4.11.0.86",
|
|
||||||
"xrt",
|
|
||||||
]
|
|
||||||
|
|
||||||
[project.optional-dependencies]
|
|
||||||
dev = [
|
|
||||||
"black",
|
|
||||||
"copier",
|
|
||||||
"isort",
|
|
||||||
"coverage",
|
|
||||||
"pylint",
|
|
||||||
"pytest",
|
|
||||||
"pytest-random-order",
|
|
||||||
"bec_server",
|
|
||||||
]
|
|
||||||
|
|
||||||
[project.entry-points."bec"]
|
|
||||||
plugin_bec = "debye_bec"
|
|
||||||
|
|
||||||
[project.entry-points."bec.deployment.device_server"]
|
|
||||||
plugin_ds_startup = "debye_bec.deployments.device_server.startup:run"
|
|
||||||
|
|
||||||
[project.entry-points."bec.file_writer"]
|
|
||||||
plugin_file_writer = "debye_bec.file_writer"
|
|
||||||
|
|
||||||
[project.entry-points."bec.scans"]
|
|
||||||
plugin_scans = "debye_bec.scans"
|
|
||||||
|
|
||||||
[project.entry-points."bec.scans.metadata_schema"]
|
|
||||||
plugin_metadata_schema = "debye_bec.scans.metadata_schema"
|
|
||||||
|
|
||||||
[project.entry-points."bec.ipython_client_startup"]
|
|
||||||
plugin_ipython_client_pre = "debye_bec.bec_ipython_client.startup.pre_startup"
|
|
||||||
plugin_ipython_client_post = "debye_bec.bec_ipython_client.startup"
|
|
||||||
|
|
||||||
[project.entry-points."bec.widgets.auto_updates"]
|
|
||||||
plugin_widgets_update = "debye_bec.bec_widgets.auto_updates"
|
|
||||||
|
|
||||||
[project.entry-points."bec.widgets.user_widgets"]
|
|
||||||
plugin_widgets = "debye_bec.bec_widgets.widgets"
|
|
||||||
|
|
||||||
[tool.hatch.build.targets.wheel]
|
|
||||||
include = ["*"]
|
|
||||||
|
|
||||||
[tool.isort]
|
|
||||||
profile = "black"
|
|
||||||
line_length = 100
|
|
||||||
multi_line_output = 3
|
|
||||||
include_trailing_comma = true
|
|
||||||
|
|
||||||
[tool.black]
|
|
||||||
line-length = 100
|
|
||||||
skip-magic-trailing-comma = true
|
|
||||||
|
|
||||||
[tool.pylint.basic]
|
|
||||||
# Good variable names regexes, separated by a comma. If names match any regex,
|
|
||||||
# they will always be accepted
|
|
||||||
good-names-rgxs = [
|
|
||||||
".*scanID.*",
|
|
||||||
".*RID.*",
|
|
||||||
".*pointID.*",
|
|
||||||
".*ID.*",
|
|
||||||
".*_2D.*",
|
|
||||||
".*_1D.*",
|
|
||||||
]
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user