Compare commits
63 Commits
test_new_r
...
fix_mono_n
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2fd783ad54 | ||
|
|
34dbc1839d | ||
|
|
db6a9a502f | ||
|
|
e6586ceab2 | ||
| ed6d64c7f9 | |||
| 43e8aea6c8 | |||
| abf432f2a9 | |||
|
|
b3672cf5f5 | ||
|
|
fa434794c3 | ||
|
|
9be74da098 | ||
|
|
29913cea61 | ||
|
|
881bc9e7a3 | ||
| e941647750 | |||
| 827557b667 | |||
|
|
a9fd62d249 | ||
| 27ff5697af | |||
| 07d05f9490 | |||
|
|
39adeb72de | ||
|
|
bc666dc807 | ||
|
|
89cc27a8da | ||
|
|
718a001a8a | ||
|
|
f038679d76 | ||
|
|
a1433efbf8 | ||
|
|
79ead32e79 | ||
|
|
c934aa8e9a | ||
| 0d87e958d0 | |||
| e4556ad90e | |||
|
|
da89f9287c | ||
| 665c290a90 | |||
|
|
10b0608d31 | ||
| ca2cf40d6a | |||
| 415c601d2a | |||
|
|
0c1f41cd7c | ||
|
|
0cdad97d00 | ||
| b3f63f4f76 | |||
| 87ea95e975 | |||
| 4d9a062b8c | |||
|
|
c782324065 | ||
|
|
5bb0df2ddf | ||
|
|
20759e7ff1 | ||
| b03b90a86a | |||
| 74e0b01b02 | |||
|
|
7b7a24b6c8 | ||
|
|
31ff28236b | ||
|
|
002a3323a0 | ||
|
|
24d81bb180 | ||
|
|
32e24cd92a | ||
| 03e3b1c605 | |||
| 6ab1a2941c | |||
| b8a050c424 | |||
|
|
a8e7325f0f | ||
|
|
ae50cbdfd1 | ||
|
|
c164414631 | ||
|
|
c074240444 | ||
|
|
759636cf2c | ||
|
|
510073d2f2 | ||
|
|
17b671dd4b | ||
|
|
fe3e8b6291 | ||
|
|
b7f72f8e44 | ||
|
|
8cad42920b | ||
|
|
c43ca4aaa8 | ||
| ce046f55f6 | |||
|
|
849b2d2bdb |
9
.copier-answers.yml
Normal file
9
.copier-answers.yml
Normal file
@@ -0,0 +1,9 @@
|
||||
# Do not edit this file!
|
||||
# It is needed to track the repo template version, and editing may break things.
|
||||
# This file will be overwritten by copier on template updates.
|
||||
|
||||
_commit: v1.0.0
|
||||
_src_path: https://github.com/bec-project/plugin_copier_template.git
|
||||
make_commit: false
|
||||
project_name: debye_bec
|
||||
widget_plugins_input: []
|
||||
@@ -1,7 +1,7 @@
|
||||
include:
|
||||
- project: bec/awi_utils
|
||||
file: /templates/plugin-repo-template.yml
|
||||
inputs:
|
||||
name: "debye"
|
||||
target: "debye_bec"
|
||||
branch: $CHILD_PIPELINE_BRANCH
|
||||
- file: /templates/plugin-repo-template.yml
|
||||
inputs:
|
||||
name: debye_bec
|
||||
target: debye_bec
|
||||
branch: $CHILD_PIPELINE_BRANCH
|
||||
project: bec/awi_utils
|
||||
|
||||
5
LICENSE
5
LICENSE
@@ -1,6 +1,7 @@
|
||||
|
||||
BSD 3-Clause License
|
||||
|
||||
Copyright (c) 2024, Paul Scherrer Institute
|
||||
Copyright (c) 2025, Paul Scherrer Institute
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
@@ -25,4 +26,4 @@ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
1
bin/.gitignore
vendored
Normal file
1
bin/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
# Add anything you don't want to check in to git, e.g. very large files
|
||||
@@ -10,7 +10,7 @@ While command-line arguments have to be set in the pre-startup script, the
|
||||
post-startup script can be used to load beamline specific information and
|
||||
to setup the prompts.
|
||||
|
||||
from bec_lib import bec_logger
|
||||
from bec_lib.logger import bec_logger
|
||||
|
||||
logger = bec_logger.logger
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
"""
|
||||
Pre-startup script for BEC client. This script is executed before the BEC client
|
||||
is started. It can be used to add additional command line arguments.
|
||||
is started. It can be used to add additional command line arguments.
|
||||
"""
|
||||
|
||||
from bec_lib.service_config import ServiceConfig
|
||||
|
||||
0
debye_bec/bec_widgets/auto_updates/__init__.py
Normal file
0
debye_bec/bec_widgets/auto_updates/__init__.py
Normal file
0
debye_bec/bec_widgets/widgets/__init__.py
Normal file
0
debye_bec/bec_widgets/widgets/__init__.py
Normal file
@@ -210,7 +210,7 @@ cm_xstripe:
|
||||
mo1_bragg:
|
||||
readoutPriority: baseline
|
||||
description: Positioner for the Monochromator
|
||||
deviceClass: debye_bec.devices.mo1_bragg.mo1.bragg.Mo1Bragg
|
||||
deviceClass: debye_bec.devices.mo1_bragg.mo1_bragg.Mo1Bragg
|
||||
deviceConfig:
|
||||
prefix: "X01DA-OP-MO1:BRAGG:"
|
||||
onFailure: retry
|
||||
|
||||
13
debye_bec/device_configs/x01da_machine.yaml
Normal file
13
debye_bec/device_configs/x01da_machine.yaml
Normal file
@@ -0,0 +1,13 @@
|
||||
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
|
||||
227
debye_bec/device_configs/x01da_optic_slits.yaml
Normal file
227
debye_bec/device_configs/x01da_optic_slits.yaml
Normal file
@@ -0,0 +1,227 @@
|
||||
## Optics Slits 1 -- Physical positioners
|
||||
|
||||
sl1_trxr:
|
||||
readoutPriority: baseline
|
||||
description: Optics slits 1 X-translation Ring-edge
|
||||
deviceClass: ophyd.EpicsMotor
|
||||
deviceConfig:
|
||||
prefix: X01DA-OP-SL1:TRXR
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
deviceTags:
|
||||
- optics
|
||||
- slits
|
||||
sl1_trxw:
|
||||
readoutPriority: baseline
|
||||
description: Optics slits 1 X-translation Wall-edge
|
||||
deviceClass: ophyd.EpicsMotor
|
||||
deviceConfig:
|
||||
prefix: X01DA-OP-SL1:TRXW
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
deviceTags:
|
||||
- optics
|
||||
- slits
|
||||
sl1_tryb:
|
||||
readoutPriority: baseline
|
||||
description: Optics slits 1 Y-translation Bottom-edge
|
||||
deviceClass: ophyd.EpicsMotor
|
||||
deviceConfig:
|
||||
prefix: X01DA-OP-SL1:TRYB
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
deviceTags:
|
||||
- optics
|
||||
- slits
|
||||
sl1_tryt:
|
||||
readoutPriority: baseline
|
||||
description: Optics slits 1 X-translation Top-edge
|
||||
deviceClass: ophyd.EpicsMotor
|
||||
deviceConfig:
|
||||
prefix: X01DA-OP-SL1:TRYT
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
deviceTags:
|
||||
- optics
|
||||
- slits
|
||||
bm1_try:
|
||||
readoutPriority: baseline
|
||||
description: Beam Monitor 1 Y-translation
|
||||
deviceClass: ophyd.EpicsMotor
|
||||
deviceConfig:
|
||||
prefix: X01DA-OP-BM1:TRY
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
deviceTags:
|
||||
- optics
|
||||
- slits
|
||||
|
||||
## Optics Slits 1 -- Virtual positioners
|
||||
|
||||
sl1_centerx:
|
||||
readoutPriority: baseline
|
||||
description: Optics slits 1 X-center
|
||||
deviceClass: ophyd.EpicsMotor
|
||||
deviceConfig:
|
||||
prefix: X01DA-OP-SL1:CENTERX
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
deviceTags:
|
||||
- optics
|
||||
- slits
|
||||
sl1_gapx:
|
||||
readoutPriority: baseline
|
||||
description: Optics slits 1 X-gap
|
||||
deviceClass: ophyd.EpicsMotor
|
||||
deviceConfig:
|
||||
prefix: X01DA-OP-SL1:GAPX
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
deviceTags:
|
||||
- optics
|
||||
- slits
|
||||
sl1_centery:
|
||||
readoutPriority: baseline
|
||||
description: Optics slits 1 Y-center
|
||||
deviceClass: ophyd.EpicsMotor
|
||||
deviceConfig:
|
||||
prefix: X01DA-OP-SL1:CENTERY
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
deviceTags:
|
||||
- optics
|
||||
- slits
|
||||
sl1_gapy:
|
||||
readoutPriority: baseline
|
||||
description: Optics slits 1 Y-gap
|
||||
deviceClass: ophyd.EpicsMotor
|
||||
deviceConfig:
|
||||
prefix: X01DA-OP-SL1:GAPY
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
deviceTags:
|
||||
- optics
|
||||
- slits
|
||||
|
||||
## Optics Slits 2 -- Physical positioners
|
||||
|
||||
sl2_trxr:
|
||||
readoutPriority: baseline
|
||||
description: Optics slits 2 X-translation Ring-edge
|
||||
deviceClass: ophyd.EpicsMotor
|
||||
deviceConfig:
|
||||
prefix: X01DA-OP-SL2:TRXR
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
deviceTags:
|
||||
- optics
|
||||
- slits
|
||||
sl2_trxw:
|
||||
readoutPriority: baseline
|
||||
description: Optics slits 2 X-translation Wall-edge
|
||||
deviceClass: ophyd.EpicsMotor
|
||||
deviceConfig:
|
||||
prefix: X01DA-OP-SL2:TRXW
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
deviceTags:
|
||||
- optics
|
||||
- slits
|
||||
sl2_tryb:
|
||||
readoutPriority: baseline
|
||||
description: Optics slits 2 Y-translation Bottom-edge
|
||||
deviceClass: ophyd.EpicsMotor
|
||||
deviceConfig:
|
||||
prefix: X01DA-OP-SL2:TRYB
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
deviceTags:
|
||||
- optics
|
||||
- slits
|
||||
sl2_tryt:
|
||||
readoutPriority: baseline
|
||||
description: Optics slits 2 X-translation Top-edge
|
||||
deviceClass: ophyd.EpicsMotor
|
||||
deviceConfig:
|
||||
prefix: X01DA-OP-SL2:TRYT
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
deviceTags:
|
||||
- optics
|
||||
- slits
|
||||
bm2_try:
|
||||
readoutPriority: baseline
|
||||
description: Beam Monitor 2 Y-translation
|
||||
deviceClass: ophyd.EpicsMotor
|
||||
deviceConfig:
|
||||
prefix: X01DA-OP-BM2:TRY
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
deviceTags:
|
||||
- optics
|
||||
- slits
|
||||
|
||||
## Optics Slits 2 -- Virtual positioners
|
||||
|
||||
sl2_centerx:
|
||||
readoutPriority: baseline
|
||||
description: Optics slits 2 X-center
|
||||
deviceClass: ophyd.EpicsMotor
|
||||
deviceConfig:
|
||||
prefix: X01DA-OP-SL2:CENTERX
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
deviceTags:
|
||||
- optics
|
||||
- slits
|
||||
sl2_gapx:
|
||||
readoutPriority: baseline
|
||||
description: Optics slits 2 X-gap
|
||||
deviceClass: ophyd.EpicsMotor
|
||||
deviceConfig:
|
||||
prefix: X01DA-OP-SL2:GAPX
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
deviceTags:
|
||||
- optics
|
||||
- slits
|
||||
sl2_centery:
|
||||
readoutPriority: baseline
|
||||
description: Optics slits 2 Y-center
|
||||
deviceClass: ophyd.EpicsMotor
|
||||
deviceConfig:
|
||||
prefix: X01DA-OP-SL2:CENTERY
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
deviceTags:
|
||||
- optics
|
||||
- slits
|
||||
sl2_gapy:
|
||||
readoutPriority: baseline
|
||||
description: Optics slits 2 Y-gap
|
||||
deviceClass: ophyd.EpicsMotor
|
||||
deviceConfig:
|
||||
prefix: X01DA-OP-SL2:GAPY
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
deviceTags:
|
||||
- optics
|
||||
- slits
|
||||
@@ -1,3 +1,208 @@
|
||||
optic_slit_config:
|
||||
- !include ./x01da_optic_slits.yaml
|
||||
machine_config:
|
||||
- !include ./x01da_machine.yaml
|
||||
## Slit Diaphragm -- Physical positioners
|
||||
sldi_trxr:
|
||||
readoutPriority: baseline
|
||||
description: Front-end slit diaphragm X-translation Ring-edge
|
||||
deviceClass: ophyd.EpicsMotor
|
||||
deviceConfig:
|
||||
prefix: X01DA-FE-SLDI:TRXR
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
sldi_trxw:
|
||||
readoutPriority: baseline
|
||||
description: Front-end slit diaphragm X-translation Wall-edge
|
||||
deviceClass: ophyd.EpicsMotor
|
||||
deviceConfig:
|
||||
prefix: X01DA-FE-SLDI:TRXW
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
sldi_tryb:
|
||||
readoutPriority: baseline
|
||||
description: Front-end slit diaphragm Y-translation Bottom-edge
|
||||
deviceClass: ophyd.EpicsMotor
|
||||
deviceConfig:
|
||||
prefix: X01DA-FE-SLDI:TRYB
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
sldi_tryt:
|
||||
readoutPriority: baseline
|
||||
description: Front-end slit diaphragm X-translation Top-edge
|
||||
deviceClass: ophyd.EpicsMotor
|
||||
deviceConfig:
|
||||
prefix: X01DA-FE-SLDI:TRYT
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
|
||||
## Slit Diaphragm -- Virtual positioners
|
||||
|
||||
sldi_centerx:
|
||||
readoutPriority: baseline
|
||||
description: Front-end slit diaphragm X-center
|
||||
deviceClass: ophyd.EpicsMotor
|
||||
deviceConfig:
|
||||
prefix: X01DA-FE-SLDI:CENTERX
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
sldi_gapx:
|
||||
readoutPriority: baseline
|
||||
description: Front-end slit diaphragm X-gap
|
||||
deviceClass: ophyd.EpicsMotor
|
||||
deviceConfig:
|
||||
prefix: X01DA-FE-SLDI:GAPX
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
sldi_centery:
|
||||
readoutPriority: baseline
|
||||
description: Front-end slit diaphragm Y-center
|
||||
deviceClass: ophyd.EpicsMotor
|
||||
deviceConfig:
|
||||
prefix: X01DA-FE-SLDI:CENTERY
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
sldi_gapy:
|
||||
readoutPriority: baseline
|
||||
description: Front-end slit diaphragm Y-gap
|
||||
deviceClass: ophyd.EpicsMotor
|
||||
deviceConfig:
|
||||
prefix: X01DA-FE-SLDI:GAPY
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
|
||||
|
||||
## Collimating Mirror -- Physical Positioners
|
||||
|
||||
cm_trxu:
|
||||
readoutPriority: baseline
|
||||
description: Collimating Mirror X-translation upstream
|
||||
deviceClass: ophyd.EpicsMotor
|
||||
deviceConfig:
|
||||
prefix: X01DA-FE-CM:TRXU
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
cm_trxd:
|
||||
readoutPriority: baseline
|
||||
description: Collimating Mirror X-translation downstream
|
||||
deviceClass: ophyd.EpicsMotor
|
||||
deviceConfig:
|
||||
prefix: X01DA-FE-CM:TRXD
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
cm_tryu:
|
||||
readoutPriority: baseline
|
||||
description: Collimating Mirror Y-translation upstream
|
||||
deviceClass: ophyd.EpicsMotor
|
||||
deviceConfig:
|
||||
prefix: X01DA-FE-CM:TRYU
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
cm_trydr:
|
||||
readoutPriority: baseline
|
||||
description: Collimating Mirror Y-translation downstream ring
|
||||
deviceClass: ophyd.EpicsMotor
|
||||
deviceConfig:
|
||||
prefix: X01DA-FE-CM:TRYDR
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
cm_trydw:
|
||||
readoutPriority: baseline
|
||||
description: Collimating Mirror Y-translation downstream wall
|
||||
deviceClass: ophyd.EpicsMotor
|
||||
deviceConfig:
|
||||
prefix: X01DA-FE-CM:TRYDW
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
cm_bnd:
|
||||
readoutPriority: baseline
|
||||
description: Collimating Mirror bender
|
||||
deviceClass: ophyd.EpicsMotor
|
||||
deviceConfig:
|
||||
prefix: X01DA-FE-CM:BND
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
|
||||
## Collimating Mirror -- Virtual Positioners
|
||||
|
||||
cm_rotx:
|
||||
readoutPriority: baseline
|
||||
description: Collimating Morror Pitch
|
||||
deviceClass: ophyd.EpicsMotor
|
||||
deviceConfig:
|
||||
prefix: X01DA-FE-CM:ROTX
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
cm_roty:
|
||||
readoutPriority: baseline
|
||||
description: Collimating Morror Yaw
|
||||
deviceClass: ophyd.EpicsMotor
|
||||
deviceConfig:
|
||||
prefix: X01DA-FE-CM:ROTY
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
cm_rotz:
|
||||
readoutPriority: baseline
|
||||
description: Collimating Morror Roll
|
||||
deviceClass: ophyd.EpicsMotor
|
||||
deviceConfig:
|
||||
prefix: X01DA-FE-CM:ROTZ
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
cm_trx:
|
||||
readoutPriority: baseline
|
||||
description: Collimating Morror Center Point X
|
||||
deviceClass: ophyd.EpicsMotor
|
||||
deviceConfig:
|
||||
prefix: X01DA-FE-CM:XTCP
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
cm_try:
|
||||
readoutPriority: baseline
|
||||
description: Collimating Morror Center Point Y
|
||||
deviceClass: ophyd.EpicsMotor
|
||||
deviceConfig:
|
||||
prefix: X01DA-FE-CM:YTCP
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
cm_ztcp:
|
||||
readoutPriority: baseline
|
||||
description: Collimating Morror Center Point Z
|
||||
deviceClass: ophyd.EpicsMotor
|
||||
deviceConfig:
|
||||
prefix: X01DA-FE-CM:ZTCP
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
cm_xstripe:
|
||||
readoutPriority: baseline
|
||||
description: Collimating Morror X Stripe
|
||||
deviceClass: ophyd.EpicsMotor
|
||||
deviceConfig:
|
||||
prefix: X01DA-FE-CM:XSTRIPE
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
|
||||
## Bragg Monochromator
|
||||
mo1_bragg:
|
||||
readoutPriority: baseline
|
||||
@@ -8,12 +213,12 @@ mo1_bragg:
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
dummy_pv:
|
||||
readoutPriority: monitored
|
||||
description: Heartbeat of Bragg
|
||||
deviceClass: ophyd.EpicsSignalRO
|
||||
mo1_bragg_angle:
|
||||
readoutPriority: baseline
|
||||
description: Positioner for the Monochromator
|
||||
deviceClass: debye_bec.devices.mo1_bragg.mo1_bragg_angle.Mo1BraggAngle
|
||||
deviceConfig:
|
||||
read_pv: "X01DA-OP-MO1:BRAGG:heartbeat_RBV"
|
||||
prefix: "X01DA-OP-MO1:BRAGG:"
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
@@ -29,35 +234,179 @@ nidaq:
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
|
||||
## Monochromator -- Physical Positioners
|
||||
|
||||
mo_try:
|
||||
readoutPriority: baseline
|
||||
description: Monochromator Y Translation
|
||||
deviceClass: ophyd.EpicsMotor
|
||||
deviceConfig:
|
||||
prefix: X01DA-OP-MO1:TRY
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
mo_trx:
|
||||
readoutPriority: baseline
|
||||
description: Monochromator X Translation
|
||||
deviceClass: ophyd.EpicsMotor
|
||||
deviceConfig:
|
||||
prefix: X01DA-OP-MO1:TRX
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
mo_roty:
|
||||
readoutPriority: baseline
|
||||
description: Monochromator Yaw
|
||||
deviceClass: ophyd.EpicsMotor
|
||||
deviceConfig:
|
||||
prefix: X01DA-OP-MO1:ROTY
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
|
||||
## Focusing Mirror -- Physical Positioners
|
||||
|
||||
fm_trxu:
|
||||
readoutPriority: baseline
|
||||
description: Focusing Mirror X-translation upstream
|
||||
deviceClass: ophyd.EpicsMotor
|
||||
deviceConfig:
|
||||
prefix: X01DA-OP-FM:TRXU
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
fm_trxd:
|
||||
readoutPriority: baseline
|
||||
description: Focusing Mirror X-translation downstream
|
||||
deviceClass: ophyd.EpicsMotor
|
||||
deviceConfig:
|
||||
prefix: X01DA-OP-FM:TRXD
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
fm_tryd:
|
||||
readoutPriority: baseline
|
||||
description: Focusing Mirror Y-translation downstream
|
||||
deviceClass: ophyd.EpicsMotor
|
||||
deviceConfig:
|
||||
prefix: X01DA-OP-FM:TRYD
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
fm_tryur:
|
||||
readoutPriority: baseline
|
||||
description: Focusing Mirror Y-translation upstream ring
|
||||
deviceClass: ophyd.EpicsMotor
|
||||
deviceConfig:
|
||||
prefix: X01DA-OP-FM:TRYUR
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
fm_tryuw:
|
||||
readoutPriority: baseline
|
||||
description: Focusing Mirror Y-translation upstream wall
|
||||
deviceClass: ophyd.EpicsMotor
|
||||
deviceConfig:
|
||||
prefix: X01DA-OP-FM:TRYUW
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
fm_bnd:
|
||||
readoutPriority: baseline
|
||||
description: Focusing Mirror bender
|
||||
deviceClass: ophyd.EpicsMotor
|
||||
deviceConfig:
|
||||
prefix: X01DA-OP-FM:BND
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
|
||||
## Focusing Mirror -- Virtual Positioners
|
||||
|
||||
fm_rotx:
|
||||
readoutPriority: baseline
|
||||
description: Focusing Morror Pitch
|
||||
deviceClass: ophyd.EpicsMotor
|
||||
deviceConfig:
|
||||
prefix: X01DA-OP-FM:ROTX
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
fm_roty:
|
||||
readoutPriority: baseline
|
||||
description: Focusing Morror Yaw
|
||||
deviceClass: ophyd.EpicsMotor
|
||||
deviceConfig:
|
||||
prefix: X01DA-OP-FM:ROTY
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
fm_rotz:
|
||||
readoutPriority: baseline
|
||||
description: Focusing Morror Roll
|
||||
deviceClass: ophyd.EpicsMotor
|
||||
deviceConfig:
|
||||
prefix: X01DA-OP-FM:ROTZ
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
fm_xctp:
|
||||
readoutPriority: baseline
|
||||
description: Focusing Morror Center Point X
|
||||
deviceClass: ophyd.EpicsMotor
|
||||
deviceConfig:
|
||||
prefix: X01DA-OP-FM:XTCP
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
fm_ytcp:
|
||||
readoutPriority: baseline
|
||||
description: Focusing Morror Center Point Y
|
||||
deviceClass: ophyd.EpicsMotor
|
||||
deviceConfig:
|
||||
prefix: X01DA-OP-FM:YTCP
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
fm_ztcp:
|
||||
readoutPriority: baseline
|
||||
description: Focusing Morror Center Point Z
|
||||
deviceClass: ophyd.EpicsMotor
|
||||
deviceConfig:
|
||||
prefix: X01DA-OP-FM:ZTCP
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
|
||||
# Ionization Chambers
|
||||
|
||||
# ic0:
|
||||
# readoutPriority: baseline
|
||||
# description: Ionization chamber 0
|
||||
# deviceClass: debye_bec.devices.ionization_chambers.ionization_chamber.IonizationChamber0
|
||||
# deviceConfig:
|
||||
# prefix: "X01DA-"
|
||||
# onFailure: retry
|
||||
# enabled: true
|
||||
# softwareTrigger: false
|
||||
# ic1:
|
||||
# readoutPriority: baseline
|
||||
# description: Ionization chamber 1
|
||||
# deviceClass: debye_bec.devices.ionization_chambers.ionization_chamber.IonizationChamber1
|
||||
# deviceConfig:
|
||||
# prefix: "X01DA-"
|
||||
# onFailure: retry
|
||||
# enabled: true
|
||||
# softwareTrigger: false
|
||||
# ic2:
|
||||
# readoutPriority: baseline
|
||||
# description: Ionization chamber 2
|
||||
# deviceClass: debye_bec.devices.ionization_chambers.ionization_chamber.IonizationChamber2
|
||||
# deviceConfig:
|
||||
# prefix: "X01DA-"
|
||||
# onFailure: retry
|
||||
# enabled: true
|
||||
# softwareTrigger: false
|
||||
ic0:
|
||||
readoutPriority: baseline
|
||||
description: Ionization chamber 0
|
||||
deviceClass: debye_bec.devices.ionization_chambers.ionization_chamber.IonizationChamber0
|
||||
deviceConfig:
|
||||
prefix: "X01DA-"
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
ic1:
|
||||
readoutPriority: baseline
|
||||
description: Ionization chamber 1
|
||||
deviceClass: debye_bec.devices.ionization_chambers.ionization_chamber.IonizationChamber1
|
||||
deviceConfig:
|
||||
prefix: "X01DA-"
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
ic2:
|
||||
readoutPriority: baseline
|
||||
description: Ionization chamber 2
|
||||
deviceClass: debye_bec.devices.ionization_chambers.ionization_chamber.IonizationChamber2
|
||||
deviceConfig:
|
||||
prefix: "X01DA-"
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
|
||||
# ES0 Filter
|
||||
|
||||
@@ -71,6 +420,18 @@ es0filter:
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
|
||||
# Reference foil changer
|
||||
|
||||
reffoilchanger:
|
||||
readoutPriority: baseline
|
||||
description: ES2 reference foil changer
|
||||
deviceClass: debye_bec.devices.reffoilchanger.Reffoilchanger
|
||||
deviceConfig:
|
||||
prefix: "X01DA-"
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
|
||||
# Beam Monitors
|
||||
|
||||
# beam_monitor_1:
|
||||
@@ -93,15 +454,15 @@ es0filter:
|
||||
# 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
|
||||
xray_eye:
|
||||
readoutPriority: async
|
||||
description: X-ray eye
|
||||
deviceClass: debye_bec.devices.cameras.basler_cam.BaslerCam
|
||||
deviceConfig:
|
||||
prefix: "X01DA-ES-XRAYEYE:"
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
|
||||
# Pilatus Curtain
|
||||
# pilatus_curtain:
|
||||
@@ -227,4 +588,49 @@ es1_alignment_laser:
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
|
||||
|
||||
## Pinhole alignment stages -- Physical Positioners
|
||||
|
||||
pin1_trx:
|
||||
readoutPriority: baseline
|
||||
description: Pinhole X-translation
|
||||
deviceClass: ophyd.EpicsMotor
|
||||
deviceConfig:
|
||||
prefix: X01DA-ES1-PIN1:TRX
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
tags: Endstation
|
||||
|
||||
pin1_try:
|
||||
readoutPriority: baseline
|
||||
description: Pinhole Y-translation
|
||||
deviceClass: ophyd.EpicsMotor
|
||||
deviceConfig:
|
||||
prefix: X01DA-ES1-PIN1:TRY
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
tags: Endstation
|
||||
|
||||
pin1_rotx:
|
||||
readoutPriority: baseline
|
||||
description: Pinhole X-rotation
|
||||
deviceClass: ophyd.EpicsMotor
|
||||
deviceConfig:
|
||||
prefix: X01DA-ES1-PIN1:ROTX
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
tags: Endstation
|
||||
|
||||
pin1_roty:
|
||||
readoutPriority: baseline
|
||||
description: Pinhole Y-rotation
|
||||
deviceClass: ophyd.EpicsMotor
|
||||
deviceConfig:
|
||||
prefix: X01DA-ES1-PIN1:ROTY
|
||||
onFailure: retry
|
||||
enabled: true
|
||||
softwareTrigger: false
|
||||
tags: Endstation
|
||||
@@ -1,29 +1,36 @@
|
||||
# from ophyd_devices.sim.sim_signals import SetableSignal
|
||||
# from ophyd_devices.utils.psi_component import PSIComponent, SignalType
|
||||
import numpy as np
|
||||
"""Basler camera class for Debye BEC."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from ophyd import ADBase
|
||||
from ophyd import ADComponent as ADCpt
|
||||
from ophyd import Kind
|
||||
from ophyd import Component as Cpt
|
||||
from ophyd_devices import PreviewSignal
|
||||
from ophyd_devices.devices.areadetector.cam import AravisDetectorCam
|
||||
from ophyd_devices.devices.areadetector.plugins import ImagePlugin_V35
|
||||
from ophyd_devices.interfaces.base_classes.psi_device_base import PSIDeviceBase
|
||||
|
||||
from debye_bec.devices.cameras.debye_base_cam import DebyeBaseCamera
|
||||
|
||||
if TYPE_CHECKING: # pragma: no cover
|
||||
from bec_lib.devicemanager import ScanInfo
|
||||
|
||||
|
||||
class BaslerCamBase(ADBase):
|
||||
"""BaslerCam Base class."""
|
||||
|
||||
cam1 = ADCpt(AravisDetectorCam, "cam1:")
|
||||
image1 = ADCpt(ImagePlugin_V35, "image1:")
|
||||
|
||||
|
||||
class BaslerCam(PSIDeviceBase, BaslerCamBase):
|
||||
class BaslerCam(DebyeBaseCamera, BaslerCamBase):
|
||||
"""Basler camera class at Debye. IOC prefix: X01DA-ES-XRAYEYE:"""
|
||||
|
||||
# preview_2d = PSIComponent(SetableSignal, signal_type=SignalType.PREVIEW, ndim=2, kind=Kind.omitted)
|
||||
|
||||
def emit_to_bec(self, *args, obj=None, old_value=None, value=None, **kwargs):
|
||||
width = self.image1.array_size.width.get()
|
||||
height = self.image1.array_size.height.get()
|
||||
data = np.reshape(value, (height, width))
|
||||
# self.preview_2d.put(data)
|
||||
self._run_subs(sub_type=self.SUB_DEVICE_MONITOR_2D, value=data)
|
||||
|
||||
def on_connected(self):
|
||||
self.image1.array_data.subscribe(self.emit_to_bec, run=False)
|
||||
preview = Cpt(
|
||||
PreviewSignal,
|
||||
name="preview",
|
||||
ndim=2,
|
||||
num_rotation_90=3,
|
||||
doc="Preview signal for the camera.",
|
||||
)
|
||||
|
||||
138
debye_bec/devices/cameras/debye_base_cam.py
Normal file
138
debye_bec/devices/cameras/debye_base_cam.py
Normal file
@@ -0,0 +1,138 @@
|
||||
"""Base class for Camera integration at Debye."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import threading
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
import numpy as np
|
||||
from bec_lib.logger import bec_logger
|
||||
from ophyd import Component as Cpt
|
||||
from ophyd import DeviceStatus, StatusBase
|
||||
from ophyd_devices import PreviewSignal
|
||||
from ophyd_devices.interfaces.base_classes.psi_device_base import PSIDeviceBase
|
||||
from typeguard import typechecked
|
||||
|
||||
if TYPE_CHECKING: # pragma: no cover
|
||||
from bec_lib.devicemanager import ScanInfo
|
||||
from ophyd_devices.devices.areadetector.plugins import ImagePlugin_V35
|
||||
|
||||
|
||||
logger = bec_logger.logger
|
||||
|
||||
|
||||
class DebyeBaseCamera(PSIDeviceBase):
|
||||
"""Base class for Debye cameras."""
|
||||
|
||||
USER_ACCESS = ["live_mode"]
|
||||
preview = Cpt(
|
||||
PreviewSignal,
|
||||
name="preview",
|
||||
ndim=2,
|
||||
num_rotation_90=-1,
|
||||
doc="Preview signal for the camera.",
|
||||
)
|
||||
|
||||
def __init__(self, *, name: str, prefix: str = "", scan_info: ScanInfo | None = None, **kwargs):
|
||||
super().__init__(name=name, prefix=prefix, scan_info=scan_info, **kwargs)
|
||||
self.image1: "ImagePlugin_V35"
|
||||
self._update_frequency = 1 # Hz
|
||||
self._live_mode = False
|
||||
self._live_mode_event = None
|
||||
self._task_status = None
|
||||
|
||||
@property
|
||||
def live_mode(self) -> bool:
|
||||
"""Live mode status."""
|
||||
return self._live_mode
|
||||
|
||||
@typechecked
|
||||
@live_mode.setter
|
||||
def live_mode(self, value: bool) -> None:
|
||||
"""
|
||||
Set the live mode status.
|
||||
|
||||
Args:
|
||||
value (bool): True to enable live mode, False to disable.
|
||||
"""
|
||||
if value == self._live_mode:
|
||||
return
|
||||
self._live_mode = value
|
||||
if value:
|
||||
self._start_live_mode()
|
||||
else:
|
||||
self._stop_live_mode()
|
||||
|
||||
def _start_live_mode(self) -> None:
|
||||
"""Start live mode."""
|
||||
if self._live_mode_event is not None: # Kill task if it exists
|
||||
self._live_mode_event.set()
|
||||
self._live_mode_event = None
|
||||
if self._task_status is not None:
|
||||
self.task_handler.kill_task(task_status=self._task_status)
|
||||
self._task_status = None
|
||||
|
||||
self._live_mode_event = threading.Event()
|
||||
self._task_status = self.task_handler.submit_task(task=self.emit_to_bec)
|
||||
|
||||
def _stop_live_mode(self) -> None:
|
||||
"""Stop live mode."""
|
||||
if self._live_mode_event is not None:
|
||||
self._live_mode_event.set()
|
||||
self._live_mode_event = None
|
||||
|
||||
def emit_to_bec(self):
|
||||
"""Emit the image data to BEC. If _live_mode_event is set, stop the task."""
|
||||
while not self._live_mode_event.wait(1 / self._update_frequency):
|
||||
value = self.image1.array_data.get()
|
||||
if value is None:
|
||||
continue
|
||||
width = self.image1.array_size.width.get()
|
||||
height = self.image1.array_size.height.get()
|
||||
# Geometry correction for the image
|
||||
data = np.reshape(value, (height, width))
|
||||
self.preview.put(data)
|
||||
|
||||
########################################
|
||||
# Beamline Specific Implementations #
|
||||
########################################
|
||||
|
||||
def on_init(self) -> None:
|
||||
"""
|
||||
Called when the device is initialized.
|
||||
|
||||
No signals are connected at this point. If you like to
|
||||
set default values on signals, please use on_connected instead.
|
||||
"""
|
||||
|
||||
def on_connected(self) -> None:
|
||||
"""
|
||||
Called after the device is connected and its signals are connected.
|
||||
Default values for signals should be set here.
|
||||
"""
|
||||
self.live_mode = True
|
||||
|
||||
def on_stage(self) -> DeviceStatus | StatusBase | None:
|
||||
"""
|
||||
Called while staging the device.
|
||||
|
||||
Information about the upcoming scan can be accessed from the scan_info (self.scan_info.msg) object.
|
||||
"""
|
||||
|
||||
def on_unstage(self) -> DeviceStatus | StatusBase | None:
|
||||
"""Called while unstaging the device."""
|
||||
|
||||
def on_pre_scan(self) -> DeviceStatus | StatusBase | None:
|
||||
"""Called right before the scan starts on all devices automatically."""
|
||||
|
||||
def on_trigger(self) -> DeviceStatus | StatusBase | None:
|
||||
"""Called when the device is triggered."""
|
||||
|
||||
def on_complete(self) -> DeviceStatus | StatusBase | None:
|
||||
"""Called to inquire if a device has completed a scans."""
|
||||
|
||||
def on_kickoff(self) -> DeviceStatus | StatusBase | None:
|
||||
"""Called to kickoff a device for a fly scan. Has to be called explicitly."""
|
||||
|
||||
def on_stop(self) -> None:
|
||||
"""Called when the device is stopped."""
|
||||
@@ -1,25 +1,39 @@
|
||||
import numpy as np
|
||||
"""Prosilica camera class for integration of beam_monitor 1/2 cameras."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from ophyd import ADBase
|
||||
from ophyd import ADComponent as ADCpt
|
||||
from ophyd import Component as Cpt
|
||||
from ophyd import Device
|
||||
from ophyd_devices import PreviewSignal
|
||||
from ophyd_devices.devices.areadetector.cam import ProsilicaDetectorCam
|
||||
from ophyd_devices.devices.areadetector.plugins import ImagePlugin_V35
|
||||
from ophyd_devices.interfaces.base_classes.psi_device_base import PSIDeviceBase
|
||||
|
||||
from debye_bec.devices.cameras.debye_base_cam import DebyeBaseCamera
|
||||
|
||||
if TYPE_CHECKING: # pragma: no cover
|
||||
from bec_lib.devicemanager import ScanInfo
|
||||
|
||||
|
||||
class ProsilicaCamBase(ADBase):
|
||||
"""Base class for Prosilica cameras."""
|
||||
|
||||
cam1 = ADCpt(ProsilicaDetectorCam, "cam1:")
|
||||
image1 = ADCpt(ImagePlugin_V35, "image1:")
|
||||
|
||||
|
||||
class ProsilicaCam(PSIDeviceBase, ProsilicaCamBase):
|
||||
class ProsilicaCam(DebyeBaseCamera, ProsilicaCamBase):
|
||||
"""
|
||||
Prosilica camera class, for integration of beam_monitor 1/2 cameras.
|
||||
Prefixes are: X01DA-OP-GIGE02: and X01DA-OP-GIGE01:
|
||||
"""
|
||||
|
||||
def emit_to_bec(self, *args, obj=None, old_value=None, value=None, **kwargs):
|
||||
width = self.image1.array_size.width.get()
|
||||
height = self.image1.array_size.height.get()
|
||||
data = np.reshape(value, (height, width))
|
||||
self._run_subs(sub_type=self.SUB_DEVICE_MONITOR_2D, value=data)
|
||||
|
||||
def on_connected(self):
|
||||
self.image1.array_data.subscribe(self.emit_to_bec, run=False)
|
||||
preview = Cpt(
|
||||
PreviewSignal,
|
||||
name="preview",
|
||||
ndim=2,
|
||||
num_rotation_90=3,
|
||||
doc="Preview signal for the camera.",
|
||||
)
|
||||
|
||||
@@ -3,15 +3,28 @@
|
||||
### debye_bec
|
||||
| Device | Documentation | Module |
|
||||
| :----- | :------------- | :------ |
|
||||
| Amplifiers | Class for the ES HV power supplies | [debye_bec.devices.hv_supplies](https://gitlab.psi.ch/bec/debye_bec/-/blob/main/debye_bec/devices/hv_supplies.py) |
|
||||
| ES0Filter | Class for the ES0 filter station | [debye_bec.devices.es0filter](https://gitlab.psi.ch/bec/debye_bec/-/blob/main/debye_bec/devices/es0filter.py) |
|
||||
| BaslerCam | Basler camera class at Debye. IOC prefix: X01DA-ES-XRAYEYE: | [debye_bec.devices.cameras.basler_cam](https://gitlab.psi.ch/bec/debye_bec/-/blob/main/debye_bec/devices/cameras/basler_cam.py) |
|
||||
| BaslerCamBase | BaslerCam Base class. | [debye_bec.devices.cameras.basler_cam](https://gitlab.psi.ch/bec/debye_bec/-/blob/main/debye_bec/devices/cameras/basler_cam.py) |
|
||||
| DebyeBaseCamera | Base class for Debye cameras. | [debye_bec.devices.cameras.debye_base_cam](https://gitlab.psi.ch/bec/debye_bec/-/blob/main/debye_bec/devices/cameras/debye_base_cam.py) |
|
||||
| ES0Filter | Class for the ES0 filter station X01DA-ES0-FI: | [debye_bec.devices.es0filter](https://gitlab.psi.ch/bec/debye_bec/-/blob/main/debye_bec/devices/es0filter.py) |
|
||||
| GasMixSetup | Class for the ES2 Pilatus Curtain | [debye_bec.devices.pilatus_curtain](https://gitlab.psi.ch/bec/debye_bec/-/blob/main/debye_bec/devices/pilatus_curtain.py) |
|
||||
| Mo1Bragg | Class for the Mo1 Bragg positioner of the Debye beamline.<br> The prefix to connect to the soft IOC is X01DA-OP-MO1:BRAGG: which is connected to<br> the NI motor controller via web sockets.<br> | [debye_bec.devices.mo1_bragg](https://gitlab.psi.ch/bec/debye_bec/-/blob/main/debye_bec/devices/mo1_bragg.py) |
|
||||
| Mo1BraggCalculator | Mo1 Bragg PVs to convert angle to energy or vice-versa. | [debye_bec.devices.mo1_bragg](https://gitlab.psi.ch/bec/debye_bec/-/blob/main/debye_bec/devices/mo1_bragg.py) |
|
||||
| Mo1BraggCrystal | Mo1 Bragg PVs to set the crystal parameters | [debye_bec.devices.mo1_bragg](https://gitlab.psi.ch/bec/debye_bec/-/blob/main/debye_bec/devices/mo1_bragg.py) |
|
||||
| Mo1BraggEncoder | Mo1 Bragg PVs to communicate with the encoder | [debye_bec.devices.mo1_bragg](https://gitlab.psi.ch/bec/debye_bec/-/blob/main/debye_bec/devices/mo1_bragg.py) |
|
||||
| Mo1BraggScanControl | Mo1 Bragg PVs to control the scan after setting the parameters. | [debye_bec.devices.mo1_bragg](https://gitlab.psi.ch/bec/debye_bec/-/blob/main/debye_bec/devices/mo1_bragg.py) |
|
||||
| Mo1BraggScanSettings | Mo1 Bragg PVs to set the scan setttings | [debye_bec.devices.mo1_bragg](https://gitlab.psi.ch/bec/debye_bec/-/blob/main/debye_bec/devices/mo1_bragg.py) |
|
||||
| Mo1BraggStatus | Mo1 Bragg PVs for status monitoring | [debye_bec.devices.mo1_bragg](https://gitlab.psi.ch/bec/debye_bec/-/blob/main/debye_bec/devices/mo1_bragg.py) |
|
||||
| Mo1TriggerSettings | Mo1 Trigger settings | [debye_bec.devices.mo1_bragg](https://gitlab.psi.ch/bec/debye_bec/-/blob/main/debye_bec/devices/mo1_bragg.py) |
|
||||
| NIDAQ | NIDAQ ophyd wrapper around the NIDAQ backend currently running at x01da-cons-05<br> <br> Args:<br> prefix (str) : Prefix to the NIDAQ soft ioc, currently X01DA-PC-SCANSERVER:<br> name (str) : Name of the device<br> kind (Kind) : Ophyd Kind of the device<br> parent (Device) : Parent clas<br> device_manager : device manager as forwarded by BEC<br> | [debye_bec.devices.nidaq](https://gitlab.psi.ch/bec/debye_bec/-/blob/main/debye_bec/devices/nidaq.py) |
|
||||
| GasMixSetupControl | GasMixSetup Control for Inonization Chamber 0 | [debye_bec.devices.ionization_chambers.ionization_chamber](https://gitlab.psi.ch/bec/debye_bec/-/blob/main/debye_bec/devices/ionization_chambers/ionization_chamber.py) |
|
||||
| HighVoltageSuppliesControl | HighVoltage Supplies Control for Ionization Chamber 0 | [debye_bec.devices.ionization_chambers.ionization_chamber](https://gitlab.psi.ch/bec/debye_bec/-/blob/main/debye_bec/devices/ionization_chambers/ionization_chamber.py) |
|
||||
| IonizationChamber0 | Ionization Chamber 0, prefix should be 'X01DA-'. | [debye_bec.devices.ionization_chambers.ionization_chamber](https://gitlab.psi.ch/bec/debye_bec/-/blob/main/debye_bec/devices/ionization_chambers/ionization_chamber.py) |
|
||||
| IonizationChamber1 | Ionization Chamber 1, prefix should be 'X01DA-'. | [debye_bec.devices.ionization_chambers.ionization_chamber](https://gitlab.psi.ch/bec/debye_bec/-/blob/main/debye_bec/devices/ionization_chambers/ionization_chamber.py) |
|
||||
| IonizationChamber2 | Ionization Chamber 2, prefix should be 'X01DA-'. | [debye_bec.devices.ionization_chambers.ionization_chamber](https://gitlab.psi.ch/bec/debye_bec/-/blob/main/debye_bec/devices/ionization_chambers/ionization_chamber.py) |
|
||||
| Mo1Bragg | Mo1 Bragg motor for the Debye beamline.<br><br> The prefix to connect to the soft IOC is X01DA-OP-MO1:BRAGG:<br> | [debye_bec.devices.mo1_bragg.mo1_bragg](https://gitlab.psi.ch/bec/debye_bec/-/blob/main/debye_bec/devices/mo1_bragg/mo1_bragg.py) |
|
||||
| Mo1BraggAngle | Positioner implementation with readback angle of the MO1 Bragg positioner. | [debye_bec.devices.mo1_bragg.mo1_bragg_angle](https://gitlab.psi.ch/bec/debye_bec/-/blob/main/debye_bec/devices/mo1_bragg/mo1_bragg_angle.py) |
|
||||
| Mo1BraggCalculator | Mo1 Bragg PVs to convert angle to energy or vice-versa. | [debye_bec.devices.mo1_bragg.mo1_bragg_devices](https://gitlab.psi.ch/bec/debye_bec/-/blob/main/debye_bec/devices/mo1_bragg/mo1_bragg_devices.py) |
|
||||
| Mo1BraggCrystal | Mo1 Bragg PVs to set the crystal parameters | [debye_bec.devices.mo1_bragg.mo1_bragg_devices](https://gitlab.psi.ch/bec/debye_bec/-/blob/main/debye_bec/devices/mo1_bragg/mo1_bragg_devices.py) |
|
||||
| Mo1BraggEncoder | Mo1 Bragg PVs to communicate with the encoder | [debye_bec.devices.mo1_bragg.mo1_bragg_devices](https://gitlab.psi.ch/bec/debye_bec/-/blob/main/debye_bec/devices/mo1_bragg/mo1_bragg_devices.py) |
|
||||
| Mo1BraggPositioner | <br> Positioner implementation with readback energy of the MO1 Bragg positioner.<br><br> The prefix to connect to the soft IOC is X01DA-OP-MO1:BRAGG:<br> This soft IOC connects to the NI motor and its control loop.<br> | [debye_bec.devices.mo1_bragg.mo1_bragg_devices](https://gitlab.psi.ch/bec/debye_bec/-/blob/main/debye_bec/devices/mo1_bragg/mo1_bragg_devices.py) |
|
||||
| Mo1BraggScanControl | Mo1 Bragg PVs to control the scan after setting the parameters. | [debye_bec.devices.mo1_bragg.mo1_bragg_devices](https://gitlab.psi.ch/bec/debye_bec/-/blob/main/debye_bec/devices/mo1_bragg/mo1_bragg_devices.py) |
|
||||
| Mo1BraggScanSettings | Mo1 Bragg PVs to set the scan setttings | [debye_bec.devices.mo1_bragg.mo1_bragg_devices](https://gitlab.psi.ch/bec/debye_bec/-/blob/main/debye_bec/devices/mo1_bragg/mo1_bragg_devices.py) |
|
||||
| Mo1BraggStatus | Mo1 Bragg PVs for status monitoring | [debye_bec.devices.mo1_bragg.mo1_bragg_devices](https://gitlab.psi.ch/bec/debye_bec/-/blob/main/debye_bec/devices/mo1_bragg/mo1_bragg_devices.py) |
|
||||
| Mo1TriggerSettings | Mo1 Trigger settings | [debye_bec.devices.mo1_bragg.mo1_bragg_devices](https://gitlab.psi.ch/bec/debye_bec/-/blob/main/debye_bec/devices/mo1_bragg/mo1_bragg_devices.py) |
|
||||
| Nidaq | NIDAQ ophyd wrapper around the NIDAQ backend currently running at x01da-cons-05<br><br> Args:<br> prefix (str) : Prefix to the NIDAQ soft ioc, currently X01DA-PC-SCANSERVER:<br> name (str) : Name of the device<br> scan_info (ScanInfo) : ScanInfo object passed by BEC's devicemanager.<br> | [debye_bec.devices.nidaq.nidaq](https://gitlab.psi.ch/bec/debye_bec/-/blob/main/debye_bec/devices/nidaq/nidaq.py) |
|
||||
| NidaqControl | Nidaq control class with all PVs | [debye_bec.devices.nidaq.nidaq](https://gitlab.psi.ch/bec/debye_bec/-/blob/main/debye_bec/devices/nidaq/nidaq.py) |
|
||||
| ProsilicaCam | <br> Prosilica camera class, for integration of beam_monitor 1/2 cameras.<br> Prefixes are: X01DA-OP-GIGE02: and X01DA-OP-GIGE01:<br> | [debye_bec.devices.cameras.prosilica_cam](https://gitlab.psi.ch/bec/debye_bec/-/blob/main/debye_bec/devices/cameras/prosilica_cam.py) |
|
||||
| ProsilicaCamBase | Base class for Prosilica cameras. | [debye_bec.devices.cameras.prosilica_cam](https://gitlab.psi.ch/bec/debye_bec/-/blob/main/debye_bec/devices/cameras/prosilica_cam.py) |
|
||||
| Reffoilchanger | Class for the ES2 Reference Foil Changer | [debye_bec.devices.reffoilchanger](https://gitlab.psi.ch/bec/debye_bec/-/blob/main/debye_bec/devices/reffoilchanger.py) |
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
from typing import Literal
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING, Literal
|
||||
|
||||
import numpy as np
|
||||
from ophyd import Component as Cpt
|
||||
@@ -15,6 +17,9 @@ from debye_bec.devices.ionization_chambers.ionization_chamber_enums import (
|
||||
AmplifierGain,
|
||||
)
|
||||
|
||||
if TYPE_CHECKING: # pragma: no cover
|
||||
from bec_lib.devicemanager import ScanInfo
|
||||
|
||||
|
||||
class EpicsSignalSplit(EpicsSignal):
|
||||
"""Wrapper around EpicsSignal with different read and write pv"""
|
||||
@@ -44,21 +49,22 @@ class GasMixSetupControl(Device):
|
||||
gas2 = Cpt(EpicsSignalRO, suffix="Gas2", kind="config", doc="Gas 2")
|
||||
conc2 = Cpt(EpicsSignalRO, suffix="Conc2", kind="config", doc="Concentration 2")
|
||||
press = Cpt(EpicsSignalRO, suffix="PressTransm", kind="config", doc="Current Pressure")
|
||||
status_msg = Cpt(EpicsSignalRO, suffix="StatusMsg0", kind="config", doc="Status")
|
||||
|
||||
|
||||
class HighVoltageSuppliesControl(Device):
|
||||
"""HighVoltage Supplies Control for Ionization Chamber 0"""
|
||||
|
||||
hv_v = Cpt(EpicsSignalSplit, suffix="HV1-V", kind="config", doc="HV voltage")
|
||||
hv_i = Cpt(EpicsSignalSplit, suffix="HV1-I", kind="config", doc="HV current")
|
||||
grid_v = Cpt(EpicsSignalSplit, suffix="HV2-V", kind="config", doc="Grid voltage")
|
||||
grid_i = Cpt(EpicsSignalSplit, suffix="HV2-I", kind="config", doc="Grid current")
|
||||
hv_v = Cpt(EpicsSignalSplit, suffix="HV2-V", kind="config", doc="HV voltage")
|
||||
hv_i = Cpt(EpicsSignalSplit, suffix="HV2-I", kind="config", doc="HV current")
|
||||
grid_v = Cpt(EpicsSignalSplit, suffix="HV1-V", kind="config", doc="Grid voltage")
|
||||
grid_i = Cpt(EpicsSignalSplit, suffix="HV1-I", kind="config", doc="Grid current")
|
||||
|
||||
|
||||
class IonizationChamber0(PSIDeviceBase):
|
||||
"""Ionization Chamber 0, prefix should be 'X01DA-'."""
|
||||
|
||||
USER_ACCESS = ["set_gain", "set_filter", "set_hv", "set_grid", "fill"]
|
||||
|
||||
num = 1
|
||||
amp_signals = {
|
||||
"cOnOff": (
|
||||
@@ -79,6 +85,7 @@ class IonizationChamber0(PSIDeviceBase):
|
||||
}
|
||||
amp = Dcpt(amp_signals)
|
||||
gmes = Cpt(GasMixSetupControl, suffix=f"ES-GMES:IC{num-1}")
|
||||
gmes_status = Cpt(EpicsSignalRO, suffix="ES-GMES:StatusMsg0", kind="config", doc="Status")
|
||||
hv = Cpt(HighVoltageSuppliesControl, suffix=f"ES1-IC{num-1}:")
|
||||
hv_en_signals = {
|
||||
"ext_ena": (
|
||||
@@ -86,13 +93,13 @@ class IonizationChamber0(PSIDeviceBase):
|
||||
"ES1-IC0:HV-Ext-Ena",
|
||||
{"kind": "config", "doc": "External enable signal of HV"},
|
||||
),
|
||||
"ena": (EpicsSignalRO, "ES1-IC0:HV-Ena", {"kind": "config", "doc": "Enable signal of HV"}),
|
||||
"ena": (EpicsSignal, "ES1-IC0:HV-Ena", {"kind": "config", "doc": "Enable signal of HV"}),
|
||||
}
|
||||
hv_en = Dcpt(hv_en_signals)
|
||||
|
||||
def __init__(self, name: str, scan_info=None, **kwargs):
|
||||
def __init__(self, name: str, prefix: str = "", scan_info: ScanInfo | None = None, **kwargs):
|
||||
self.timeout_for_pvwait = 2.5
|
||||
super().__init__(name=name, scan_info=scan_info, **kwargs)
|
||||
super().__init__(name=name, prefix=prefix, scan_info=scan_info, **kwargs)
|
||||
|
||||
@typechecked
|
||||
def set_gain(self, gain: Literal["1e6", "1e7", "5e7", "1e8", "1e9"] | AmplifierGain) -> None:
|
||||
@@ -180,9 +187,9 @@ class IonizationChamber0(PSIDeviceBase):
|
||||
hv (float) : Desired voltage for the 'HV' terminal. Voltage has to be between 0...3000
|
||||
"""
|
||||
|
||||
if not 0 < hv < 3000:
|
||||
if not (0 <= hv <= 3000):
|
||||
raise ValueError(f"specified HV {hv} not within range [0 .. 3000]")
|
||||
if self.hv.grid_v.get() > hv:
|
||||
if not np.isclose(np.abs(hv - self.hv.grid_v.get()), 0, atol=3):
|
||||
raise ValueError(f"Grid {self.hv.grid_v.get()} must not be higher than HV {hv}!")
|
||||
|
||||
if not self.hv_en.ena.get() == 1:
|
||||
@@ -208,9 +215,9 @@ class IonizationChamber0(PSIDeviceBase):
|
||||
grid (float) : Desired voltage for the 'Grid' terminal, Grid Voltage has to be between 0...3000
|
||||
"""
|
||||
|
||||
if not 0 < grid < 3000:
|
||||
if not (0 <= grid <= 3000):
|
||||
raise ValueError(f"specified Grid {grid} not within range [0 .. 3000]")
|
||||
if grid > self.hv.hv_v.get():
|
||||
if not np.isclose(np.abs(grid - self.hv.hv_v.get()), 0, atol=3):
|
||||
raise ValueError(f"Grid {grid} must not be higher than HV {self.hv.hv_v.get()}!")
|
||||
|
||||
if not self.hv_en.ena.get() == 1:
|
||||
@@ -249,13 +256,13 @@ class IonizationChamber0(PSIDeviceBase):
|
||||
wait (bool): If you like to wait for the filling to finish.
|
||||
"""
|
||||
|
||||
if 100 < conc1 < 0:
|
||||
if not (0 <= conc1 <= 100):
|
||||
raise ValueError(f"Concentration 1 {conc1} out of range [0 .. 100 %]")
|
||||
if 100 < conc2 < 0:
|
||||
if not (0 <= conc2 <= 100):
|
||||
raise ValueError(f"Concentration 2 {conc2} out of range [0 .. 100 %]")
|
||||
if not np.isclose((conc1 + conc2), 100, atol=0.1):
|
||||
raise ValueError(f"Conc1 {conc1} and conc2 {conc2} must sum to 100 +- 0.1")
|
||||
if 3 < pressure < 0:
|
||||
if not (0 <= pressure <= 3):
|
||||
raise ValueError(f"Pressure {pressure} out of range [0 .. 3 bar abs]")
|
||||
|
||||
self.gmes.gas1_req.set(gas1).wait(timeout=3)
|
||||
@@ -271,7 +278,7 @@ class IonizationChamber0(PSIDeviceBase):
|
||||
timeout = 3
|
||||
if not self.wait_for_condition(wait_for_status, timeout=timeout, check_stopped=True):
|
||||
raise TimeoutError(
|
||||
f"Ionization chamber filling process did not start after {timeout}s. Last log message {self.gmes.status_msg.get()}"
|
||||
f"Ionization chamber filling process did not start after {timeout}s. Last log message {self.gmes_status.get()}"
|
||||
)
|
||||
|
||||
def wait_for_filling_finished():
|
||||
@@ -286,8 +293,8 @@ class IonizationChamber0(PSIDeviceBase):
|
||||
return status
|
||||
|
||||
|
||||
class IonizationChamber1(PSIDeviceBase):
|
||||
"""Ionization Chamber 0, prefix should be 'X01DA-'."""
|
||||
class IonizationChamber1(IonizationChamber0):
|
||||
"""Ionization Chamber 1, prefix should be 'X01DA-'."""
|
||||
|
||||
num = 2
|
||||
amp_signals = {
|
||||
@@ -309,6 +316,7 @@ class IonizationChamber1(PSIDeviceBase):
|
||||
}
|
||||
amp = Dcpt(amp_signals)
|
||||
gmes = Cpt(GasMixSetupControl, suffix=f"ES-GMES:IC{num-1}")
|
||||
gmes_status = Cpt(EpicsSignalRO, suffix="ES-GMES:StatusMsg0", kind="config", doc="Status")
|
||||
hv = Cpt(HighVoltageSuppliesControl, suffix=f"ES2-IC{num-1}:")
|
||||
hv_en_signals = {
|
||||
"ext_ena": (
|
||||
@@ -316,13 +324,13 @@ class IonizationChamber1(PSIDeviceBase):
|
||||
"ES2-IC12:HV-Ext-Ena",
|
||||
{"kind": "config", "doc": "External enable signal of HV"},
|
||||
),
|
||||
"ena": (EpicsSignalRO, "ES2-IC12:HV-Ena", {"kind": "config", "doc": "Enable signal of HV"}),
|
||||
"ena": (EpicsSignal, "ES2-IC12:HV-Ena", {"kind": "config", "doc": "Enable signal of HV"}),
|
||||
}
|
||||
hv_en = Dcpt(hv_en_signals)
|
||||
|
||||
|
||||
class IonizationChamber2(PSIDeviceBase):
|
||||
"""Ionization Chamber 0, prefix should be 'X01DA-'."""
|
||||
class IonizationChamber2(IonizationChamber0):
|
||||
"""Ionization Chamber 2, prefix should be 'X01DA-'."""
|
||||
|
||||
num = 3
|
||||
amp_signals = {
|
||||
@@ -344,6 +352,7 @@ class IonizationChamber2(PSIDeviceBase):
|
||||
}
|
||||
amp = Dcpt(amp_signals)
|
||||
gmes = Cpt(GasMixSetupControl, suffix=f"ES-GMES:IC{num-1}")
|
||||
gmes_status = Cpt(EpicsSignalRO, suffix="ES-GMES:StatusMsg0", kind="config", doc="Status")
|
||||
hv = Cpt(HighVoltageSuppliesControl, suffix=f"ES2-IC{num-1}:")
|
||||
hv_en_signals = {
|
||||
"ext_ena": (
|
||||
@@ -351,6 +360,6 @@ class IonizationChamber2(PSIDeviceBase):
|
||||
"ES2-IC12:HV-Ext-Ena",
|
||||
{"kind": "config", "doc": "External enable signal of HV"},
|
||||
),
|
||||
"ena": (EpicsSignalRO, "ES2-IC12:HV-Ena", {"kind": "config", "doc": "Enable signal of HV"}),
|
||||
"ena": (EpicsSignal, "ES2-IC12:HV-Ena", {"kind": "config", "doc": "Enable signal of HV"}),
|
||||
}
|
||||
hv_en = Dcpt(hv_en_signals)
|
||||
|
||||
@@ -14,7 +14,9 @@ from typing import Any, Literal
|
||||
from bec_lib.devicemanager import ScanInfo
|
||||
from bec_lib.logger import bec_logger
|
||||
from ophyd import Component as Cpt
|
||||
from ophyd import DeviceStatus, StatusBase
|
||||
from ophyd import DeviceStatus, Signal, StatusBase
|
||||
from ophyd.status import SubscriptionStatus, WaitTimeoutError
|
||||
from ophyd_devices import CompareStatus, ProgressSignal, TransitionStatus
|
||||
from ophyd_devices.interfaces.base_classes.psi_device_base import PSIDeviceBase
|
||||
from ophyd_devices.utils.errors import DeviceStopError
|
||||
from pydantic import BaseModel, Field
|
||||
@@ -80,7 +82,9 @@ class Mo1Bragg(PSIDeviceBase, Mo1BraggPositioner):
|
||||
The prefix to connect to the soft IOC is X01DA-OP-MO1:BRAGG:
|
||||
"""
|
||||
|
||||
USER_ACCESS = ["set_advanced_xas_settings"]
|
||||
progress_signal = Cpt(ProgressSignal, name="progress_signal")
|
||||
|
||||
USER_ACCESS = ["set_advanced_xas_settings", "set_xtal"]
|
||||
|
||||
def __init__(self, name: str, prefix: str = "", scan_info: ScanInfo | None = None, **kwargs): # type: ignore
|
||||
"""
|
||||
@@ -92,7 +96,7 @@ class Mo1Bragg(PSIDeviceBase, Mo1BraggPositioner):
|
||||
"""
|
||||
super().__init__(name=name, scan_info=scan_info, prefix=prefix, **kwargs)
|
||||
self.scan_parameter = ScanParameter()
|
||||
self.timeout_for_pvwait = 2.5
|
||||
self.timeout_for_pvwait = 7.5
|
||||
|
||||
########################################
|
||||
# Beamline Specific Implementations #
|
||||
@@ -119,7 +123,11 @@ class Mo1Bragg(PSIDeviceBase, Mo1BraggPositioner):
|
||||
|
||||
Information about the upcoming scan can be accessed from the scan_info (self.scan_info.msg) object.
|
||||
"""
|
||||
self._check_scan_msg(ScanControlLoadMessage.PENDING)
|
||||
if self.scan_control.scan_msg.get() != ScanControlLoadMessage.PENDING:
|
||||
status = CompareStatus(self.scan_control.scan_msg, ScanControlLoadMessage.PENDING)
|
||||
self.cancel_on_stop(status)
|
||||
self.scan_control.scan_val_reset.put(1)
|
||||
status.wait(timeout=self.timeout_for_pvwait)
|
||||
|
||||
scan_name = self.scan_info.msg.scan_name
|
||||
self._update_scan_parameter()
|
||||
@@ -197,42 +205,45 @@ class Mo1Bragg(PSIDeviceBase, Mo1BraggPositioner):
|
||||
)
|
||||
else:
|
||||
return
|
||||
# Setting scan duration seems to lag behind slightly in the backend, include small sleep
|
||||
logger.info(f"Sleeping for one second")
|
||||
time.sleep(1)
|
||||
logger.info(f"Device {self.name}, done sleeping")
|
||||
# Load the scan parameters to the controller
|
||||
status = CompareStatus(self.scan_control.scan_msg, ScanControlLoadMessage.SUCCESS)
|
||||
self.cancel_on_stop(status)
|
||||
self.scan_control.scan_load.put(1)
|
||||
# Wait for params to be checked from controller
|
||||
status = self.task_handler.submit_task(
|
||||
task=self.wait_for_signal,
|
||||
task_args=(self.scan_control.scan_msg, ScanControlLoadMessage.SUCCESS),
|
||||
)
|
||||
return status
|
||||
# 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."""
|
||||
|
||||
def unstage_procedure():
|
||||
if self.stopped is True:
|
||||
logger.warning(f"Resetting stopped in unstage for device {self.name}.")
|
||||
self._stopped = False
|
||||
if self.scan_control.scan_msg.get() in [
|
||||
ScanControlLoadMessage.STARTED,
|
||||
ScanControlLoadMessage.SUCCESS,
|
||||
]:
|
||||
status = CompareStatus(self.scan_control.scan_msg, ScanControlLoadMessage.PENDING)
|
||||
self.cancel_on_stop(status)
|
||||
try:
|
||||
self.wait_for_signal(
|
||||
self.scan_control.scan_msg,
|
||||
ScanControlLoadMessage.PENDING,
|
||||
timeout=self.timeout_for_pvwait / 2,
|
||||
)
|
||||
return
|
||||
except TimeoutError:
|
||||
status.wait(2)
|
||||
return None
|
||||
except WaitTimeoutError:
|
||||
logger.warning(
|
||||
f"Timeout after {self.timeout_for_pvwait} while waiting for scan validation"
|
||||
f"Timeout in on_unstage of {self.name} after {self.timeout_for_pvwait}s, current scan_control_message : {self.scan_control.scan_msg.get()}"
|
||||
)
|
||||
time.sleep(0.25)
|
||||
start_time = time.time()
|
||||
while time.time() - start_time < self.timeout_for_pvwait / 2:
|
||||
if not self.scan_control.scan_msg.get() == ScanControlLoadMessage.PENDING:
|
||||
break
|
||||
time.sleep(0.1)
|
||||
raise TimeoutError(
|
||||
f"Device {self.name} run into timeout after {self.timeout_for_pvwait} while waiting for scan validation"
|
||||
)
|
||||
|
||||
status = self.task_handler.submit_task(unstage_procedure)
|
||||
status.wait()
|
||||
status = CompareStatus(self.scan_control.scan_msg, ScanControlLoadMessage.PENDING)
|
||||
self.cancel_on_stop(status)
|
||||
self.scan_control.scan_val_reset.put(1)
|
||||
status.wait(timeout=self.timeout_for_pvwait)
|
||||
else:
|
||||
status = CompareStatus(self.scan_control.scan_msg, ScanControlLoadMessage.PENDING)
|
||||
self.cancel_on_stop(status)
|
||||
self.scan_control.scan_val_reset.put(1)
|
||||
status.wait(timeout=self.timeout_for_pvwait)
|
||||
return None
|
||||
|
||||
def on_pre_scan(self) -> DeviceStatus | StatusBase | None:
|
||||
@@ -243,20 +254,8 @@ class Mo1Bragg(PSIDeviceBase, Mo1BraggPositioner):
|
||||
|
||||
def on_complete(self) -> DeviceStatus | StatusBase | None:
|
||||
"""Called to inquire if a device has completed a scans."""
|
||||
|
||||
def wait_for_complete():
|
||||
"""Wait for the scan to complete. No timeout is set."""
|
||||
start_time = time.time()
|
||||
while True:
|
||||
if self.stopped is True:
|
||||
raise DeviceStopError(
|
||||
f"Device {self.name} was stopped while waiting for scan to complete"
|
||||
)
|
||||
if self.scan_control.scan_done.get() == 1:
|
||||
return
|
||||
time.sleep(0.1)
|
||||
|
||||
status = self.task_handler.submit_task(wait_for_complete)
|
||||
status = CompareStatus(self.scan_control.scan_done, 1)
|
||||
self.cancel_on_stop(status)
|
||||
return status
|
||||
|
||||
def on_kickoff(self) -> DeviceStatus | StatusBase | None:
|
||||
@@ -268,11 +267,14 @@ class Mo1Bragg(PSIDeviceBase, Mo1BraggPositioner):
|
||||
if scan_duration < 0.1
|
||||
else self.scan_control.scan_start_timer.put
|
||||
)
|
||||
start_func(1)
|
||||
status = self.task_handler.submit_task(
|
||||
task=self.wait_for_signal,
|
||||
task_args=(self.scan_control.scan_status, ScanControlScanStatus.RUNNING),
|
||||
status = TransitionStatus(
|
||||
self.scan_control.scan_status,
|
||||
transitions=[ScanControlScanStatus.READY, ScanControlScanStatus.RUNNING],
|
||||
strict=True,
|
||||
raise_states=[ScanControlScanStatus.PARAMETER_WRONG],
|
||||
)
|
||||
self.cancel_on_stop(status)
|
||||
start_func(1)
|
||||
return status
|
||||
|
||||
def on_stop(self) -> None:
|
||||
@@ -280,9 +282,6 @@ class Mo1Bragg(PSIDeviceBase, Mo1BraggPositioner):
|
||||
self.stopped = True # Needs to be set to stop motion
|
||||
|
||||
######### Utility Methods #########
|
||||
|
||||
# FIXME this should become the ProgressSignal
|
||||
# pylint: disable=unused-argument
|
||||
def _progress_update(self, value, **kwargs) -> None:
|
||||
"""Callback method to update the scan progress, runs a callback
|
||||
to SUB_PROGRESS subscribers, i.e. BEC.
|
||||
@@ -291,12 +290,7 @@ class Mo1Bragg(PSIDeviceBase, Mo1BraggPositioner):
|
||||
value (int) : current progress value
|
||||
"""
|
||||
max_value = 100
|
||||
self._run_subs(
|
||||
sub_type=self.SUB_PROGRESS,
|
||||
value=value,
|
||||
max_value=max_value,
|
||||
done=bool(max_value == value),
|
||||
)
|
||||
self.progress_signal.put(value=value, max_value=max_value, done=bool(max_value == value))
|
||||
|
||||
def set_xas_settings(self, low: float, high: float, scan_time: float) -> None:
|
||||
"""Set XAS parameters for upcoming scan.
|
||||
@@ -306,30 +300,20 @@ class Mo1Bragg(PSIDeviceBase, Mo1BraggPositioner):
|
||||
high (float): High energy/angle value of the scan
|
||||
scan_time (float): Time for a half oscillation
|
||||
"""
|
||||
move_type = self.move_type.get()
|
||||
if move_type == MoveType.ENERGY:
|
||||
self.scan_settings.s_scan_energy_lo.put(low)
|
||||
self.scan_settings.s_scan_energy_hi.put(high)
|
||||
else:
|
||||
self.scan_settings.s_scan_angle_lo.put(low)
|
||||
self.scan_settings.s_scan_angle_hi.put(high)
|
||||
self.scan_settings.s_scan_scantime.put(scan_time)
|
||||
|
||||
def wait_for_signal(self, signal: Cpt, value: Any, timeout: float | None = None) -> None:
|
||||
"""Wait for a signal to reach a certain value."""
|
||||
if timeout is None:
|
||||
timeout = self.timeout_for_pvwait
|
||||
start_time = time.time()
|
||||
while time.time() - start_time < timeout:
|
||||
if signal.get() == value:
|
||||
return None
|
||||
if self.stopped is True: # Should this check be optional or configurable?!
|
||||
raise DeviceStopError(f"Device {self.name} was stopped while waiting for signal")
|
||||
time.sleep(0.1)
|
||||
# If we end up here, the status did not resolve
|
||||
raise TimeoutError(
|
||||
f"Device {self.name} run into timeout after {timeout}s while waiting for scan to start"
|
||||
)
|
||||
status_list = []
|
||||
|
||||
status_list.append(self.scan_settings.s_scan_energy_lo.set(low))
|
||||
self.cancel_on_stop(status_list[-1])
|
||||
|
||||
status_list.append(self.scan_settings.s_scan_energy_hi.set(high))
|
||||
self.cancel_on_stop(status_list[-1])
|
||||
|
||||
status_list.append(self.scan_settings.s_scan_scantime.set(scan_time))
|
||||
self.cancel_on_stop(status_list[-1])
|
||||
|
||||
for s in status_list:
|
||||
s.wait(timeout=self.timeout_for_pvwait)
|
||||
|
||||
@typechecked
|
||||
def convert_angle_energy(
|
||||
@@ -346,15 +330,19 @@ class Mo1Bragg(PSIDeviceBase, Mo1BraggPositioner):
|
||||
"""
|
||||
self.calculator.calc_reset.put(0)
|
||||
self.calculator.calc_reset.put(1)
|
||||
self.wait_for_signal(self.calculator.calc_done, 0)
|
||||
status = CompareStatus(self.calculator.calc_done, 0)
|
||||
self.cancel_on_stop(status)
|
||||
status.wait(self.timeout_for_pvwait)
|
||||
|
||||
if mode == "AngleToEnergy":
|
||||
self.calculator.calc_angle.put(inp)
|
||||
elif mode == "EnergyToAngle":
|
||||
self.calculator.calc_energy.put(inp)
|
||||
|
||||
self.wait_for_signal(self.calculator.calc_done, 1)
|
||||
time.sleep(0.25) # Needed due to update frequency of softIOC
|
||||
status = CompareStatus(self.calculator.calc_done, 1)
|
||||
self.cancel_on_stop(status)
|
||||
status.wait(self.timeout_for_pvwait)
|
||||
time.sleep(0.15)
|
||||
if mode == "AngleToEnergy":
|
||||
return self.calculator.calc_energy.get()
|
||||
elif mode == "EnergyToAngle":
|
||||
@@ -372,18 +360,11 @@ class Mo1Bragg(PSIDeviceBase, Mo1BraggPositioner):
|
||||
p_kink (float): Position of kink in %
|
||||
e_kink (float): Energy of kink in eV
|
||||
"""
|
||||
# TODO Add fallback solution for automatic testing, otherwise test will fail
|
||||
# because no monochromator will calculate the angle
|
||||
# Unsure how to implement this
|
||||
|
||||
move_type = self.move_type.get()
|
||||
if move_type == MoveType.ENERGY:
|
||||
e_kink_deg = self.convert_angle_energy(mode="EnergyToAngle", inp=e_kink)
|
||||
# Angle and Energy are inverse proportional!
|
||||
high_deg = self.convert_angle_energy(mode="EnergyToAngle", inp=low)
|
||||
low_deg = self.convert_angle_energy(mode="EnergyToAngle", inp=high)
|
||||
else:
|
||||
raise Mo1BraggError("MoveType Angle not implemented for advanced scans, use Energy")
|
||||
e_kink_deg = self.convert_angle_energy(mode="EnergyToAngle", inp=e_kink)
|
||||
# Angle and Energy are inverse proportional!
|
||||
high_deg = self.convert_angle_energy(mode="EnergyToAngle", inp=low)
|
||||
low_deg = self.convert_angle_energy(mode="EnergyToAngle", inp=high)
|
||||
p_kink = 100 - p_kink
|
||||
|
||||
pos, vel, dt = compute_spline(
|
||||
low_deg=low_deg,
|
||||
@@ -393,9 +374,19 @@ class Mo1Bragg(PSIDeviceBase, Mo1BraggPositioner):
|
||||
scan_time=scan_time,
|
||||
)
|
||||
|
||||
self.scan_settings.a_scan_pos.set(pos)
|
||||
self.scan_settings.a_scan_vel.set(vel)
|
||||
self.scan_settings.a_scan_time.set(dt)
|
||||
status_list = []
|
||||
|
||||
status_list.append(self.scan_settings.a_scan_pos.set(pos))
|
||||
self.cancel_on_stop(status_list[-1])
|
||||
|
||||
status_list.append(self.scan_settings.a_scan_vel.set(vel))
|
||||
self.cancel_on_stop(status_list[-1])
|
||||
|
||||
status_list.append(self.scan_settings.a_scan_time.set(dt))
|
||||
self.cancel_on_stop(status_list[-1])
|
||||
|
||||
for s in status_list:
|
||||
s.wait(timeout=self.timeout_for_pvwait)
|
||||
|
||||
def set_trig_settings(
|
||||
self,
|
||||
@@ -418,12 +409,30 @@ class Mo1Bragg(PSIDeviceBase, Mo1BraggPositioner):
|
||||
cycle_low (int): Cycle for low energy/angle
|
||||
cycle_high (int): Cycle for high energy/angle
|
||||
"""
|
||||
self.scan_settings.trig_ena_hi_enum.put(int(enable_high))
|
||||
self.scan_settings.trig_ena_lo_enum.put(int(enable_low))
|
||||
self.scan_settings.trig_time_hi.put(exp_time_high)
|
||||
self.scan_settings.trig_time_lo.put(exp_time_low)
|
||||
self.scan_settings.trig_every_n_hi.put(cycle_high)
|
||||
self.scan_settings.trig_every_n_lo.put(cycle_low)
|
||||
|
||||
status_list = []
|
||||
|
||||
status_list.append(self.scan_settings.trig_ena_hi_enum.set(int(enable_high)))
|
||||
self.cancel_on_stop(status_list[-1])
|
||||
|
||||
status_list.append(self.scan_settings.trig_ena_lo_enum.set(int(enable_low)))
|
||||
self.cancel_on_stop(status_list[-1])
|
||||
|
||||
status_list.append(self.scan_settings.trig_time_hi.set(exp_time_high))
|
||||
self.cancel_on_stop(status_list[-1])
|
||||
|
||||
status_list.append(self.scan_settings.trig_time_lo.set(exp_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])
|
||||
|
||||
for s in status_list:
|
||||
s.wait(timeout=self.timeout_for_pvwait)
|
||||
|
||||
|
||||
def set_scan_control_settings(self, mode: ScanControlMode, scan_duration: float) -> None:
|
||||
"""Set the scan control settings for the upcoming scan.
|
||||
@@ -433,8 +442,18 @@ class Mo1Bragg(PSIDeviceBase, Mo1BraggPositioner):
|
||||
scan_duration (float): Duration of the scan
|
||||
"""
|
||||
val = ScanControlMode(mode).value
|
||||
self.scan_control.scan_mode_enum.put(val)
|
||||
self.scan_control.scan_duration.put(scan_duration)
|
||||
|
||||
status_list = []
|
||||
|
||||
status_list.append(self.scan_control.scan_mode_enum.set(val))
|
||||
self.cancel_on_stop(status_list[-1])
|
||||
|
||||
status_list.append(self.scan_control.scan_duration.set(scan_duration))
|
||||
self.cancel_on_stop(status_list[-1])
|
||||
|
||||
for s in status_list:
|
||||
s.wait(timeout=self.timeout_for_pvwait)
|
||||
|
||||
|
||||
def _update_scan_parameter(self):
|
||||
"""Get the scan_info parameters for the scan."""
|
||||
@@ -444,30 +463,3 @@ class Mo1Bragg(PSIDeviceBase, Mo1BraggPositioner):
|
||||
for key, value in self.scan_info.msg.request_inputs["kwargs"].items():
|
||||
if hasattr(self.scan_parameter, key):
|
||||
setattr(self.scan_parameter, key, value)
|
||||
|
||||
def _check_scan_msg(self, target_state: ScanControlLoadMessage) -> None:
|
||||
"""Check if the scan message is gettting available
|
||||
|
||||
Args:
|
||||
target_state (ScanControlLoadMessage): Target state to check for
|
||||
|
||||
Raises:
|
||||
TimeoutError: If the scan message is not available after the timeout
|
||||
"""
|
||||
state = self.scan_control.scan_msg.get()
|
||||
if state != target_state:
|
||||
logger.warning(
|
||||
f"Resetting scan validation in stage for state: {ScanControlLoadMessage(state)}, "
|
||||
f"retry .get() on scan_control: {ScanControlLoadMessage(self.scan_control.scan_msg.get())} and sleeping 1s"
|
||||
)
|
||||
self.scan_control.scan_val_reset.put(1)
|
||||
# Sleep to ensure the reset is done
|
||||
time.sleep(1)
|
||||
|
||||
try:
|
||||
self.wait_for_signal(self.scan_control.scan_msg, target_state)
|
||||
except TimeoutError as exc:
|
||||
raise TimeoutError(
|
||||
f"Timeout after {self.timeout_for_pvwait} while waiting for scan status,"
|
||||
f" current state: {ScanControlScanStatus(self.scan_control.scan_msg.get())}"
|
||||
) from exc
|
||||
|
||||
20
debye_bec/devices/mo1_bragg/mo1_bragg_angle.py
Normal file
20
debye_bec/devices/mo1_bragg/mo1_bragg_angle.py
Normal file
@@ -0,0 +1,20 @@
|
||||
"""Positioner implementation with readback angle of the MO1 Bragg positioner."""
|
||||
|
||||
from ophyd import Component as Cpt
|
||||
from ophyd import EpicsSignalRO, EpicsSignalWithRBV
|
||||
|
||||
from debye_bec.devices.mo1_bragg.mo1_bragg_devices import Mo1BraggPositioner
|
||||
|
||||
|
||||
class Mo1BraggAngle(Mo1BraggPositioner):
|
||||
"""Positioner implementation with readback angle of the MO1 Bragg positioner."""
|
||||
|
||||
readback = Cpt(EpicsSignalRO, suffix="feedback_pos_angle_RBV", kind="normal", auto_monitor=True)
|
||||
setpoint = Cpt(EpicsSignalWithRBV, suffix="set_abs_pos_angle", kind="normal", auto_monitor=True)
|
||||
low_lim = Cpt(EpicsSignalRO, suffix="lo_lim_pos_angle_RBV", kind="config", auto_monitor=True)
|
||||
high_lim = Cpt(EpicsSignalRO, suffix="hi_lim_pos_angle_RBV", kind="config", auto_monitor=True)
|
||||
|
||||
@property
|
||||
def egu(self) -> str:
|
||||
"""Return the engineering unit of the positioner."""
|
||||
return "deg"
|
||||
@@ -82,10 +82,20 @@ class Mo1BraggCrystal(Device):
|
||||
d_spacing_si111 = Cpt(EpicsSignalWithRBV, suffix="d_spacing_si111", kind="config")
|
||||
d_spacing_si311 = Cpt(EpicsSignalWithRBV, suffix="d_spacing_si311", kind="config")
|
||||
set_offset = Cpt(EpicsSignal, suffix="set_offset", kind="config", put_complete=True)
|
||||
current_d_spacing = Cpt(
|
||||
EpicsSignalRO, suffix="current_d_spacing_RBV", kind="normal", auto_monitor=True
|
||||
)
|
||||
current_offset = Cpt(
|
||||
EpicsSignalRO, suffix="current_offset_RBV", kind="normal", auto_monitor=True
|
||||
)
|
||||
current_xtal = Cpt(
|
||||
EpicsSignalRO, suffix="current_xtal_ENUM_RBV", kind="normal", auto_monitor=True
|
||||
)
|
||||
|
||||
current_xtal_string = Cpt(
|
||||
EpicsSignalRO, suffix="current_xtal_ENUM_RBV", kind="normal", auto_monitor=True, string=True
|
||||
)
|
||||
|
||||
|
||||
class Mo1BraggScanSettings(Device):
|
||||
"""Mo1 Bragg PVs to set the scan setttings"""
|
||||
@@ -189,13 +199,13 @@ class Mo1BraggScanControl(Device):
|
||||
|
||||
class Mo1BraggPositioner(Device, PositionerBase):
|
||||
"""
|
||||
Positioner implementation of the MO1 Bragg positioner.
|
||||
Positioner implementation with readback energy of the MO1 Bragg positioner.
|
||||
|
||||
The prefix to connect to the soft IOC is X01DA-OP-MO1:BRAGG:
|
||||
This soft IOC connects to the NI motor and its control loop.
|
||||
"""
|
||||
|
||||
USER_ACCESS = ["set_advanced_xas_settings"]
|
||||
USER_ACCESS = ["set_xtal"]
|
||||
|
||||
####### Sub-components ########
|
||||
# Namespace is cleaner and easier to maintain
|
||||
@@ -207,10 +217,6 @@ class Mo1BraggPositioner(Device, PositionerBase):
|
||||
scan_control = Cpt(Mo1BraggScanControl, "")
|
||||
status = Cpt(Mo1BraggStatus, "")
|
||||
|
||||
############# switch between energy and angle #############
|
||||
# TODO should be removed/replaced once decision about pseudo motor is made
|
||||
move_type = Cpt(MoveTypeSignal, value=MoveType.ENERGY, kind="config")
|
||||
|
||||
############# Energy PVs #############
|
||||
|
||||
readback = Cpt(
|
||||
@@ -226,22 +232,6 @@ class Mo1BraggPositioner(Device, PositionerBase):
|
||||
high_lim = Cpt(EpicsSignalRO, suffix="hi_lim_pos_energy_RBV", kind="config", auto_monitor=True)
|
||||
velocity = Cpt(EpicsSignalWithRBV, suffix="move_velocity", kind="config", auto_monitor=True)
|
||||
|
||||
########### Angle PVs #############
|
||||
|
||||
# TODO Pseudo motor for angle?
|
||||
feedback_pos_angle = Cpt(
|
||||
EpicsSignalRO, suffix="feedback_pos_angle_RBV", kind="normal", auto_monitor=True
|
||||
)
|
||||
setpoint_abs_angle = Cpt(
|
||||
EpicsSignalWithRBV, suffix="set_abs_pos_angle", kind="normal", auto_monitor=True
|
||||
)
|
||||
low_limit_angle = Cpt(
|
||||
EpicsSignalRO, suffix="lo_lim_pos_angle_RBV", kind="config", auto_monitor=True
|
||||
)
|
||||
high_limit_angle = Cpt(
|
||||
EpicsSignalRO, suffix="hi_lim_pos_angle_RBV", kind="config", auto_monitor=True
|
||||
)
|
||||
|
||||
########## Move Command PVs ##########
|
||||
|
||||
move_abs = Cpt(EpicsSignal, suffix="move_abs", kind="config", put_complete=True)
|
||||
@@ -271,9 +261,7 @@ class Mo1BraggPositioner(Device, PositionerBase):
|
||||
success (bool) : Flag to indicate if the motion was successful
|
||||
"""
|
||||
self.move_stop.put(1)
|
||||
if self._move_thread is not None:
|
||||
self._move_thread.join()
|
||||
self._move_thread = None
|
||||
self._stopped = True
|
||||
super().stop(success=success)
|
||||
|
||||
def stop_scan(self) -> None:
|
||||
@@ -290,9 +278,7 @@ class Mo1BraggPositioner(Device, PositionerBase):
|
||||
@property
|
||||
def limits(self) -> tuple:
|
||||
"""Return limits of the Bragg positioner"""
|
||||
if self.move_type.get() == MoveType.ENERGY:
|
||||
return (self.low_lim.get(), self.high_lim.get())
|
||||
return (self.low_limit_angle.get(), self.high_limit_angle.get())
|
||||
return (self.low_lim.get(), self.high_lim.get())
|
||||
|
||||
@property
|
||||
def low_limit(self) -> float:
|
||||
@@ -307,16 +293,12 @@ class Mo1BraggPositioner(Device, PositionerBase):
|
||||
@property
|
||||
def egu(self) -> str:
|
||||
"""Return the engineering units of the positioner"""
|
||||
if self.move_type.get() == MoveType.ENERGY:
|
||||
return "eV"
|
||||
return "deg"
|
||||
return "eV"
|
||||
|
||||
@property
|
||||
def position(self) -> float:
|
||||
"""Return the current position of Mo1Bragg, considering the move type"""
|
||||
move_type = self.move_type.get()
|
||||
move_cpt = self.readback if move_type == MoveType.ENERGY else self.feedback_pos_angle
|
||||
return move_cpt.get()
|
||||
return self.readback.get()
|
||||
|
||||
# pylint: disable=arguments-differ
|
||||
def check_value(self, value: float) -> None:
|
||||
@@ -332,7 +314,7 @@ class Mo1BraggPositioner(Device, PositionerBase):
|
||||
raise LimitError(f"position={value} not within limits {self.limits}")
|
||||
|
||||
def _move_and_finish(
|
||||
self, target_pos: float, move_cpt: Cpt, status: DeviceStatus, update_frequency: float = 0.1
|
||||
self, target_pos: float, status: DeviceStatus, update_frequency: float = 0.1
|
||||
) -> None:
|
||||
"""
|
||||
Method to be called in the move thread to move the Bragg positioner
|
||||
@@ -349,12 +331,14 @@ class Mo1BraggPositioner(Device, PositionerBase):
|
||||
update_frequency (float): Optional, frequency to update the current position of
|
||||
the motion, defaults to 0.1s
|
||||
"""
|
||||
motor_name = None
|
||||
try:
|
||||
# Set the target position on IOC
|
||||
move_cpt.put(target_pos)
|
||||
self.setpoint.put(target_pos)
|
||||
self.move_abs.put(1)
|
||||
# Currently sleep is needed due to delay in updates on PVs, maybe time can be reduced
|
||||
time.sleep(0.5)
|
||||
motor_name = self.name
|
||||
while self.motor_is_moving.get() == 0:
|
||||
if self.stopped:
|
||||
raise Mo1BraggStoppedError(f"Device {self.name} was stopped")
|
||||
@@ -364,10 +348,12 @@ class Mo1BraggPositioner(Device, PositionerBase):
|
||||
# pylint: disable=broad-except
|
||||
except Exception as exc:
|
||||
content = traceback.format_exc()
|
||||
logger.error(f"Error in move thread of device {self.name}: {content}")
|
||||
logger.error(
|
||||
f"Error in move thread of device {motor_name if motor_name else ''}: {content}"
|
||||
)
|
||||
status.set_exception(exc=exc)
|
||||
|
||||
def move(self, value: float, move_type: str | MoveType = None, **kwargs) -> DeviceStatus:
|
||||
def move(self, value: float, **kwargs) -> DeviceStatus:
|
||||
"""
|
||||
Move the Bragg positioner to the specified value, allows to
|
||||
switch between move types angle and energy.
|
||||
@@ -381,16 +367,12 @@ class Mo1BraggPositioner(Device, PositionerBase):
|
||||
DeviceStatus : status object to track the motion
|
||||
"""
|
||||
self._stopped = False
|
||||
if move_type is not None:
|
||||
self.move_type.put(move_type)
|
||||
move_type = self.move_type.get()
|
||||
move_cpt = self.setpoint if move_type == MoveType.ENERGY else self.setpoint_abs_angle
|
||||
|
||||
self.check_value(value)
|
||||
status = DeviceStatus(device=self)
|
||||
|
||||
self._move_thread = threading.Thread(
|
||||
target=self._move_and_finish, args=(value, move_cpt, status, 0.1)
|
||||
target=self._move_and_finish, args=(value, status, 0.1)
|
||||
)
|
||||
self._move_thread.start()
|
||||
return status
|
||||
|
||||
@@ -37,12 +37,12 @@ def compute_spline(
|
||||
low_deg = low_deg - POSITION_COMPONSATION
|
||||
high_deg = high_deg + POSITION_COMPONSATION
|
||||
|
||||
if p_kink < 0 or p_kink > 100:
|
||||
if not (0 <= p_kink <= 100):
|
||||
raise Mo1UtilsSplineError(
|
||||
"Kink position not within range of [0..100%]" + f"for p_kink: {p_kink}"
|
||||
)
|
||||
|
||||
if e_kink_deg < low_deg or e_kink_deg > high_deg:
|
||||
if not (low_deg < e_kink_deg < high_deg):
|
||||
raise Mo1UtilsSplineError(
|
||||
"Kink energy not within selected energy range of scan,"
|
||||
+ f"for e_kink_deg {e_kink_deg}, low_deg {low_deg} and"
|
||||
|
||||
@@ -1,18 +1,23 @@
|
||||
from __future__ import annotations
|
||||
from typing import Literal, TYPE_CHECKING, cast
|
||||
|
||||
from ophyd_devices.interfaces.base_classes.psi_device_base import PSIDeviceBase
|
||||
from ophyd import Device, Kind, DeviceStatus, Component as Cpt
|
||||
from ophyd import EpicsSignal, EpicsSignalRO, StatusBase
|
||||
from ophyd_devices.sim.sim_signals import SetableSignal
|
||||
import time
|
||||
from typing import TYPE_CHECKING, Literal, cast
|
||||
|
||||
from bec_lib.logger import bec_logger
|
||||
from ophyd import Component as Cpt
|
||||
from ophyd import Device, DeviceStatus, EpicsSignal, EpicsSignalRO, Kind, StatusBase
|
||||
from ophyd.status import SubscriptionStatus, WaitTimeoutError
|
||||
from ophyd_devices import CompareStatus, ProgressSignal, TransitionStatus
|
||||
from ophyd_devices.interfaces.base_classes.psi_device_base import PSIDeviceBase
|
||||
from ophyd_devices.sim.sim_signals import SetableSignal
|
||||
|
||||
from debye_bec.devices.nidaq.nidaq_enums import (
|
||||
EncoderFactors,
|
||||
NIDAQCompression,
|
||||
ScanType,
|
||||
NidaqState,
|
||||
ScanRates,
|
||||
ReadoutRange,
|
||||
EncoderTypes,
|
||||
ScanRates,
|
||||
ScanType,
|
||||
)
|
||||
|
||||
if TYPE_CHECKING: # pragma: no cover
|
||||
@@ -29,69 +34,273 @@ class NidaqControl(Device):
|
||||
"""Nidaq control class with all PVs"""
|
||||
|
||||
### Readback PVs for EpicsEmitter ###
|
||||
ai0 = Cpt(EpicsSignalRO, suffix="NIDAQ-AI0", kind=Kind.normal, doc="EPICS analog input 0",auto_monitor=True)
|
||||
ai1 = Cpt(EpicsSignalRO, suffix="NIDAQ-AI1", kind=Kind.normal, doc="EPICS analog input 1",auto_monitor=True)
|
||||
ai2 = Cpt(EpicsSignalRO, suffix="NIDAQ-AI2", kind=Kind.normal, doc="EPICS analog input 2",auto_monitor=True)
|
||||
ai3 = Cpt(EpicsSignalRO, suffix="NIDAQ-AI3", kind=Kind.normal, doc="EPICS analog input 3",auto_monitor=True)
|
||||
ai4 = Cpt(EpicsSignalRO, suffix="NIDAQ-AI4", kind=Kind.normal, doc="EPICS analog input 4",auto_monitor=True)
|
||||
ai5 = Cpt(EpicsSignalRO, suffix="NIDAQ-AI5", kind=Kind.normal, doc="EPICS analog input 5",auto_monitor=True)
|
||||
ai6 = Cpt(EpicsSignalRO, suffix="NIDAQ-AI6", kind=Kind.normal, doc="EPICS analog input 6",auto_monitor=True)
|
||||
ai7 = Cpt(EpicsSignalRO, suffix="NIDAQ-AI7", kind=Kind.normal, doc="EPICS analog input 7",auto_monitor=True)
|
||||
ai0 = Cpt(
|
||||
EpicsSignalRO,
|
||||
suffix="NIDAQ-AI0",
|
||||
kind=Kind.normal,
|
||||
doc="EPICS analog input 0",
|
||||
auto_monitor=True,
|
||||
)
|
||||
ai1 = Cpt(
|
||||
EpicsSignalRO,
|
||||
suffix="NIDAQ-AI1",
|
||||
kind=Kind.normal,
|
||||
doc="EPICS analog input 1",
|
||||
auto_monitor=True,
|
||||
)
|
||||
ai2 = Cpt(
|
||||
EpicsSignalRO,
|
||||
suffix="NIDAQ-AI2",
|
||||
kind=Kind.normal,
|
||||
doc="EPICS analog input 2",
|
||||
auto_monitor=True,
|
||||
)
|
||||
ai3 = Cpt(
|
||||
EpicsSignalRO,
|
||||
suffix="NIDAQ-AI3",
|
||||
kind=Kind.normal,
|
||||
doc="EPICS analog input 3",
|
||||
auto_monitor=True,
|
||||
)
|
||||
ai4 = Cpt(
|
||||
EpicsSignalRO,
|
||||
suffix="NIDAQ-AI4",
|
||||
kind=Kind.normal,
|
||||
doc="EPICS analog input 4",
|
||||
auto_monitor=True,
|
||||
)
|
||||
ai5 = Cpt(
|
||||
EpicsSignalRO,
|
||||
suffix="NIDAQ-AI5",
|
||||
kind=Kind.normal,
|
||||
doc="EPICS analog input 5",
|
||||
auto_monitor=True,
|
||||
)
|
||||
ai6 = Cpt(
|
||||
EpicsSignalRO,
|
||||
suffix="NIDAQ-AI6",
|
||||
kind=Kind.normal,
|
||||
doc="EPICS analog input 6",
|
||||
auto_monitor=True,
|
||||
)
|
||||
ai7 = Cpt(
|
||||
EpicsSignalRO,
|
||||
suffix="NIDAQ-AI7",
|
||||
kind=Kind.normal,
|
||||
doc="EPICS analog input 7",
|
||||
auto_monitor=True,
|
||||
)
|
||||
|
||||
ci0 = Cpt(EpicsSignalRO, suffix="NIDAQ-CI0", kind=Kind.normal, doc="EPICS counter input 0", auto_monitor=True)
|
||||
ci1 = Cpt(EpicsSignalRO, suffix="NIDAQ-CI1", kind=Kind.normal, doc="EPICS counter input 1", auto_monitor=True)
|
||||
ci2 = Cpt(EpicsSignalRO, suffix="NIDAQ-CI2", kind=Kind.normal, doc="EPICS counter input 2", auto_monitor=True)
|
||||
ci3 = Cpt(EpicsSignalRO, suffix="NIDAQ-CI3", kind=Kind.normal, doc="EPICS counter input 3", auto_monitor=True)
|
||||
ci4 = Cpt(EpicsSignalRO, suffix="NIDAQ-CI4", kind=Kind.normal, doc="EPICS counter input 4", auto_monitor=True)
|
||||
ci5 = Cpt(EpicsSignalRO, suffix="NIDAQ-CI5", kind=Kind.normal, doc="EPICS counter input 5", auto_monitor=True)
|
||||
ci6 = Cpt(EpicsSignalRO, suffix="NIDAQ-CI6", kind=Kind.normal, doc="EPICS counter input 6", auto_monitor=True)
|
||||
ci7 = Cpt(EpicsSignalRO, suffix="NIDAQ-CI7", kind=Kind.normal, doc="EPICS counter input 7", auto_monitor=True)
|
||||
ci0 = Cpt(
|
||||
EpicsSignalRO,
|
||||
suffix="NIDAQ-CI0",
|
||||
kind=Kind.normal,
|
||||
doc="EPICS counter input 0",
|
||||
auto_monitor=True,
|
||||
)
|
||||
ci1 = Cpt(
|
||||
EpicsSignalRO,
|
||||
suffix="NIDAQ-CI1",
|
||||
kind=Kind.normal,
|
||||
doc="EPICS counter input 1",
|
||||
auto_monitor=True,
|
||||
)
|
||||
ci2 = Cpt(
|
||||
EpicsSignalRO,
|
||||
suffix="NIDAQ-CI2",
|
||||
kind=Kind.normal,
|
||||
doc="EPICS counter input 2",
|
||||
auto_monitor=True,
|
||||
)
|
||||
ci3 = Cpt(
|
||||
EpicsSignalRO,
|
||||
suffix="NIDAQ-CI3",
|
||||
kind=Kind.normal,
|
||||
doc="EPICS counter input 3",
|
||||
auto_monitor=True,
|
||||
)
|
||||
ci4 = Cpt(
|
||||
EpicsSignalRO,
|
||||
suffix="NIDAQ-CI4",
|
||||
kind=Kind.normal,
|
||||
doc="EPICS counter input 4",
|
||||
auto_monitor=True,
|
||||
)
|
||||
ci5 = Cpt(
|
||||
EpicsSignalRO,
|
||||
suffix="NIDAQ-CI5",
|
||||
kind=Kind.normal,
|
||||
doc="EPICS counter input 5",
|
||||
auto_monitor=True,
|
||||
)
|
||||
ci6 = Cpt(
|
||||
EpicsSignalRO,
|
||||
suffix="NIDAQ-CI6",
|
||||
kind=Kind.normal,
|
||||
doc="EPICS counter input 6",
|
||||
auto_monitor=True,
|
||||
)
|
||||
ci7 = Cpt(
|
||||
EpicsSignalRO,
|
||||
suffix="NIDAQ-CI7",
|
||||
kind=Kind.normal,
|
||||
doc="EPICS counter input 7",
|
||||
auto_monitor=True,
|
||||
)
|
||||
|
||||
di0 = Cpt(EpicsSignalRO, suffix="NIDAQ-DI0", kind=Kind.normal, doc="EPICS digital input 0", auto_monitor=True)
|
||||
di1 = Cpt(EpicsSignalRO, suffix="NIDAQ-DI1", kind=Kind.normal, doc="EPICS digital input 1", auto_monitor=True)
|
||||
di2 = Cpt(EpicsSignalRO, suffix="NIDAQ-DI2", kind=Kind.normal, doc="EPICS digital input 2", auto_monitor=True)
|
||||
di3 = Cpt(EpicsSignalRO, suffix="NIDAQ-DI3", kind=Kind.normal, doc="EPICS digital input 3", auto_monitor=True)
|
||||
di4 = Cpt(EpicsSignalRO, suffix="NIDAQ-DI4", kind=Kind.normal, doc="EPICS digital input 4", auto_monitor=True)
|
||||
di0 = Cpt(
|
||||
EpicsSignalRO,
|
||||
suffix="NIDAQ-DI0",
|
||||
kind=Kind.normal,
|
||||
doc="EPICS digital input 0",
|
||||
auto_monitor=True,
|
||||
)
|
||||
di1 = Cpt(
|
||||
EpicsSignalRO,
|
||||
suffix="NIDAQ-DI1",
|
||||
kind=Kind.normal,
|
||||
doc="EPICS digital input 1",
|
||||
auto_monitor=True,
|
||||
)
|
||||
di2 = Cpt(
|
||||
EpicsSignalRO,
|
||||
suffix="NIDAQ-DI2",
|
||||
kind=Kind.normal,
|
||||
doc="EPICS digital input 2",
|
||||
auto_monitor=True,
|
||||
)
|
||||
di3 = Cpt(
|
||||
EpicsSignalRO,
|
||||
suffix="NIDAQ-DI3",
|
||||
kind=Kind.normal,
|
||||
doc="EPICS digital input 3",
|
||||
auto_monitor=True,
|
||||
)
|
||||
di4 = Cpt(
|
||||
EpicsSignalRO,
|
||||
suffix="NIDAQ-DI4",
|
||||
kind=Kind.normal,
|
||||
doc="EPICS digital input 4",
|
||||
auto_monitor=True,
|
||||
)
|
||||
|
||||
enc_epics = Cpt(
|
||||
EpicsSignalRO,
|
||||
suffix="NIDAQ-ENC",
|
||||
kind=Kind.normal,
|
||||
doc="EPICS Encoder reading",
|
||||
auto_monitor=True,
|
||||
)
|
||||
|
||||
energy_epics = Cpt(
|
||||
EpicsSignalRO,
|
||||
suffix="NIDAQ-ENERGY",
|
||||
kind=Kind.normal,
|
||||
doc="EPICS Energy reading",
|
||||
auto_monitor=True,
|
||||
)
|
||||
|
||||
enc_epics = Cpt(EpicsSignalRO, suffix="NIDAQ-ENC", kind=Kind.normal, doc="EPICS Encoder reading", auto_monitor=True)
|
||||
|
||||
### Readback for BEC emitter ###
|
||||
|
||||
ai0_mean = Cpt(SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream analog input 0, MEAN")
|
||||
ai1_mean = Cpt(SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream analog input 1, MEAN")
|
||||
ai2_mean = Cpt(SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream analog input 2, MEAN")
|
||||
ai3_mean = Cpt(SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream analog input 3, MEAN")
|
||||
ai4_mean = Cpt(SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream analog input 4, MEAN")
|
||||
ai5_mean = Cpt(SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream analog input 5, MEAN")
|
||||
ai6_mean = Cpt(SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream analog input 6, MEAN")
|
||||
ai7_mean = Cpt(SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream analog input 7, MEAN")
|
||||
ai0_mean = Cpt(
|
||||
SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream analog input 0, MEAN"
|
||||
)
|
||||
ai1_mean = Cpt(
|
||||
SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream analog input 1, MEAN"
|
||||
)
|
||||
ai2_mean = Cpt(
|
||||
SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream analog input 2, MEAN"
|
||||
)
|
||||
ai3_mean = Cpt(
|
||||
SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream analog input 3, MEAN"
|
||||
)
|
||||
ai4_mean = Cpt(
|
||||
SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream analog input 4, MEAN"
|
||||
)
|
||||
ai5_mean = Cpt(
|
||||
SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream analog input 5, MEAN"
|
||||
)
|
||||
ai6_mean = Cpt(
|
||||
SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream analog input 6, MEAN"
|
||||
)
|
||||
ai7_mean = Cpt(
|
||||
SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream analog input 7, MEAN"
|
||||
)
|
||||
|
||||
ai0_std_dev = Cpt(SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream analog input 0, STD")
|
||||
ai1_std_dev = Cpt(SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream analog input 1, STD")
|
||||
ai2_std_dev = Cpt(SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream analog input 2, STD")
|
||||
ai3_std_dev = Cpt(SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream analog input 3, STD")
|
||||
ai4_std_dev = Cpt(SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream analog input 4, STD")
|
||||
ai5_std_dev = Cpt(SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream analog input 5, STD")
|
||||
ai6_std_dev = Cpt(SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream analog input 6, STD")
|
||||
ai7_std_dev = Cpt(SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream analog input 7, STD")
|
||||
ai0_std_dev = Cpt(
|
||||
SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream analog input 0, STD"
|
||||
)
|
||||
ai1_std_dev = Cpt(
|
||||
SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream analog input 1, STD"
|
||||
)
|
||||
ai2_std_dev = Cpt(
|
||||
SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream analog input 2, STD"
|
||||
)
|
||||
ai3_std_dev = Cpt(
|
||||
SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream analog input 3, STD"
|
||||
)
|
||||
ai4_std_dev = Cpt(
|
||||
SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream analog input 4, STD"
|
||||
)
|
||||
ai5_std_dev = Cpt(
|
||||
SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream analog input 5, STD"
|
||||
)
|
||||
ai6_std_dev = Cpt(
|
||||
SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream analog input 6, STD"
|
||||
)
|
||||
ai7_std_dev = Cpt(
|
||||
SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream analog input 7, STD"
|
||||
)
|
||||
|
||||
ci0_mean = Cpt(SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream counter input 0, MEAN")
|
||||
ci1_mean = Cpt(SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream counter input 1, MEAN")
|
||||
ci2_mean = Cpt(SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream counter input 2, MEAN")
|
||||
ci3_mean = Cpt(SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream counter input 3, MEAN")
|
||||
ci4_mean = Cpt(SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream counter input 4, MEAN")
|
||||
ci5_mean = Cpt(SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream counter input 5, MEAN")
|
||||
ci6_mean = Cpt(SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream counter input 6, MEAN")
|
||||
ci7_mean = Cpt(SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream counter input 7, MEAN")
|
||||
ci0_mean = Cpt(
|
||||
SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream counter input 0, MEAN"
|
||||
)
|
||||
ci1_mean = Cpt(
|
||||
SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream counter input 1, MEAN"
|
||||
)
|
||||
ci2_mean = Cpt(
|
||||
SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream counter input 2, MEAN"
|
||||
)
|
||||
ci3_mean = Cpt(
|
||||
SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream counter input 3, MEAN"
|
||||
)
|
||||
ci4_mean = Cpt(
|
||||
SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream counter input 4, MEAN"
|
||||
)
|
||||
ci5_mean = Cpt(
|
||||
SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream counter input 5, MEAN"
|
||||
)
|
||||
ci6_mean = Cpt(
|
||||
SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream counter input 6, MEAN"
|
||||
)
|
||||
ci7_mean = Cpt(
|
||||
SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream counter input 7, MEAN"
|
||||
)
|
||||
|
||||
ci0_std_dev = Cpt(SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream counter input 0. STD")
|
||||
ci1_std_dev = Cpt(SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream counter input 1. STD")
|
||||
ci2_std_dev = Cpt(SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream counter input 2. STD")
|
||||
ci3_std_dev = Cpt(SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream counter input 3. STD")
|
||||
ci4_std_dev = Cpt(SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream counter input 4. STD")
|
||||
ci5_std_dev = Cpt(SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream counter input 5. STD")
|
||||
ci6_std_dev = Cpt(SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream counter input 6. STD")
|
||||
ci7_std_dev = Cpt(SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream counter input 7. STD")
|
||||
ci0_std_dev = Cpt(
|
||||
SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream counter input 0. STD"
|
||||
)
|
||||
ci1_std_dev = Cpt(
|
||||
SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream counter input 1. STD"
|
||||
)
|
||||
ci2_std_dev = Cpt(
|
||||
SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream counter input 2. STD"
|
||||
)
|
||||
ci3_std_dev = Cpt(
|
||||
SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream counter input 3. STD"
|
||||
)
|
||||
ci4_std_dev = Cpt(
|
||||
SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream counter input 4. STD"
|
||||
)
|
||||
ci5_std_dev = Cpt(
|
||||
SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream counter input 5. STD"
|
||||
)
|
||||
ci6_std_dev = Cpt(
|
||||
SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream counter input 6. STD"
|
||||
)
|
||||
ci7_std_dev = Cpt(
|
||||
SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream counter input 7. STD"
|
||||
)
|
||||
|
||||
di0_max = Cpt(SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream digital input 0, MAX")
|
||||
di1_max = Cpt(SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream digital input 1, MAX")
|
||||
@@ -100,6 +309,8 @@ class NidaqControl(Device):
|
||||
di4_max = Cpt(SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream digital input 4, MAX")
|
||||
|
||||
enc = Cpt(SetableSignal, value=0, kind=Kind.normal)
|
||||
energy = Cpt(SetableSignal, value=0, kind=Kind.normal)
|
||||
rle = Cpt(SetableSignal, value=0, kind=Kind.normal)
|
||||
|
||||
### Control PVs ###
|
||||
|
||||
@@ -113,11 +324,14 @@ class NidaqControl(Device):
|
||||
sampling_rate = Cpt(EpicsSignal, suffix="NIDAQ-SamplingRateRequested", kind=Kind.config)
|
||||
scan_duration = Cpt(EpicsSignal, suffix="NIDAQ-SamplingDuration", kind=Kind.config)
|
||||
readout_range = Cpt(EpicsSignal, suffix="NIDAQ-ReadoutRange", kind=Kind.config)
|
||||
encoder_type = Cpt(EpicsSignal, suffix="NIDAQ-EncoderType", kind=Kind.config)
|
||||
encoder_factor = Cpt(EpicsSignal, suffix="NIDAQ-EncoderFactor", kind=Kind.config)
|
||||
stop_call = Cpt(EpicsSignal, suffix="NIDAQ-Stop", kind=Kind.config)
|
||||
power = Cpt(EpicsSignal, suffix="NIDAQ-Power", kind=Kind.config)
|
||||
heartbeat = Cpt(EpicsSignal, suffix="NIDAQ-Heartbeat", kind=Kind.config, auto_monitor=True)
|
||||
time_left = Cpt(EpicsSignalRO, suffix="NIDAQ-TimeLeft", kind=Kind.config, auto_monitor=True)
|
||||
|
||||
ai_chans = Cpt(EpicsSignal, suffix="NIDAQ-AIChans", kind=Kind.config)
|
||||
ci_chans = Cpt(EpicsSignal, suffix="NIDAQ-CIChans6614", kind=Kind.config)
|
||||
ci_chans = Cpt(EpicsSignal, suffix="NIDAQ-CIChans", kind=Kind.config)
|
||||
di_chans = Cpt(EpicsSignal, suffix="NIDAQ-DIChans", kind=Kind.config)
|
||||
|
||||
|
||||
@@ -130,16 +344,21 @@ class Nidaq(PSIDeviceBase, NidaqControl):
|
||||
scan_info (ScanInfo) : ScanInfo object passed by BEC's devicemanager.
|
||||
"""
|
||||
|
||||
progress_signal = Cpt(ProgressSignal, name="progress_signal")
|
||||
|
||||
USER_ACCESS = ["set_config"]
|
||||
|
||||
def __init__(self, prefix: str = "", *, name: str, scan_info: ScanInfo = None, **kwargs):
|
||||
super().__init__(name=name, prefix=prefix, scan_info=scan_info, **kwargs)
|
||||
self.scan_info: ScanInfo
|
||||
self.timeout_wait_for_signal = 5 # put 5s firsts
|
||||
self._timeout_wait_for_pv = 3 # 3s timeout for pv calls
|
||||
self.valid_scan_names = [
|
||||
"xas_simple_scan",
|
||||
"xas_simple_scan_with_xrd",
|
||||
"xas_advanced_scan",
|
||||
"xas_advanced_scan_with_xrd",
|
||||
"nidaq_continuous_scan",
|
||||
]
|
||||
|
||||
########################################
|
||||
@@ -244,12 +463,20 @@ class Nidaq(PSIDeviceBase, NidaqControl):
|
||||
elif readout_range == 10:
|
||||
self.readout_range.put(ReadoutRange.TEN_V)
|
||||
|
||||
if encoder_type in "X_1":
|
||||
self.encoder_type.put(EncoderTypes.X_1)
|
||||
elif encoder_type in "X_2":
|
||||
self.encoder_type.put(EncoderTypes.X_2)
|
||||
elif encoder_type in "X_4":
|
||||
self.encoder_type.put(EncoderTypes.X_4)
|
||||
if encoder_type in "1/16":
|
||||
self.encoder_factor.put(EncoderFactors.X1_16)
|
||||
elif encoder_type in "1/8":
|
||||
self.encoder_factor.put(EncoderFactors.X1_8)
|
||||
elif encoder_type in "1/4":
|
||||
self.encoder_factor.put(EncoderFactors.X1_4)
|
||||
elif encoder_type in "1/2":
|
||||
self.encoder_factor.put(EncoderFactors.X1_2)
|
||||
elif encoder_type in "1":
|
||||
self.encoder_factor.put(EncoderFactors.X1)
|
||||
elif encoder_type in "2":
|
||||
self.encoder_factor.put(EncoderFactors.X2)
|
||||
elif encoder_type in "4":
|
||||
self.encoder_factor.put(EncoderFactors.X4)
|
||||
|
||||
if enable_compression is True:
|
||||
self.enable_compression.put(NIDAQCompression.ON)
|
||||
@@ -273,12 +500,23 @@ class Nidaq(PSIDeviceBase, NidaqControl):
|
||||
Called after the device is connected and its signals are connected.
|
||||
Default values for signals should be set here.
|
||||
"""
|
||||
if not self.wait_for_condition(
|
||||
condition= lambda: self.state.get() == NidaqState.STANDBY,
|
||||
timeout = self.timeout_wait_for_signal,
|
||||
check_stopped=True):
|
||||
raise NidaqError(f"Device {self.name} has not been reached in state STANDBY, current state {NidaqState(self.state.get())}")
|
||||
self.scan_duration.set(0).wait()
|
||||
status = TransitionStatus(self.heartbeat, transitions=[0, 1], strict=False)
|
||||
self.cancel_on_stop(status)
|
||||
try:
|
||||
status.wait(timeout=self.timeout_wait_for_signal) # Raises if timeout is reached
|
||||
except WaitTimeoutError:
|
||||
logger.warning(f"Device {self.name} was not alive, trying to put power on")
|
||||
status = TransitionStatus(self.heartbeat, transitions=[0, 1], strict=False)
|
||||
self.cancel_on_stop(status)
|
||||
self.power.put(1)
|
||||
|
||||
status.wait(timeout=self.timeout_wait_for_signal)
|
||||
|
||||
status = CompareStatus(self.state, NidaqState.STANDBY)
|
||||
self.cancel_on_stop(status)
|
||||
status.wait(timeout=self.timeout_wait_for_signal)
|
||||
self.scan_duration.set(0).wait(timeout=self._timeout_wait_for_pv)
|
||||
self.time_left.subscribe(self._progress_update, run=False)
|
||||
|
||||
def on_stage(self) -> DeviceStatus | StatusBase | None:
|
||||
"""
|
||||
@@ -290,37 +528,52 @@ class Nidaq(PSIDeviceBase, NidaqControl):
|
||||
if not self._check_if_scan_name_is_valid():
|
||||
return None
|
||||
|
||||
if not self.wait_for_condition(
|
||||
condition=lambda: self.state.get() == NidaqState.STANDBY, timeout=self.timeout_wait_for_signal, check_stopped=True
|
||||
):
|
||||
raise NidaqError(
|
||||
f"Device {self.name} has not been reached in state STANDBY, current state {NidaqState(self.state.get())}"
|
||||
)
|
||||
self.scan_type.set(ScanType.TRIGGERED).wait()
|
||||
self.scan_duration.set(0).wait()
|
||||
self.stage_call.set(1).wait()
|
||||
if self.state.get() != NidaqState.STANDBY:
|
||||
status = CompareStatus(self.state, NidaqState.STANDBY)
|
||||
self.cancel_on_stop(status)
|
||||
self.on_stop()
|
||||
status.wait(timeout=self.timeout_wait_for_signal)
|
||||
|
||||
if not self.wait_for_condition(
|
||||
condition=lambda: self.state.get() == NidaqState.STAGE, timeout=self.timeout_wait_for_signal, check_stopped=True
|
||||
):
|
||||
raise NidaqError(
|
||||
f"Device {self.name} has not been reached in state STAGE, current state {NidaqState(self.state.get())}"
|
||||
# If scan is not part of the valid_scan_names,
|
||||
if self.scan_info.msg.scan_name != "nidaq_continuous_scan":
|
||||
self.scan_type.set(ScanType.TRIGGERED).wait(timeout=self._timeout_wait_for_pv)
|
||||
self.scan_duration.set(0).wait(timeout=self._timeout_wait_for_pv)
|
||||
self.enable_compression.set(1).wait(timeout=self._timeout_wait_for_pv)
|
||||
else:
|
||||
self.scan_type.set(ScanType.CONTINUOUS).wait(timeout=self._timeout_wait_for_pv)
|
||||
self.scan_duration.set(self.scan_info.msg.scan_parameters["scan_duration"]).wait(
|
||||
timeout=self._timeout_wait_for_pv
|
||||
)
|
||||
self.kickoff_call.set(1).wait()
|
||||
self.enable_compression.set(self.scan_info.msg.scan_parameters["compression"]).wait(
|
||||
timeout=self._timeout_wait_for_pv
|
||||
)
|
||||
|
||||
# Stage call to IOC
|
||||
status = CompareStatus(self.state, NidaqState.STAGE)
|
||||
self.cancel_on_stop(status)
|
||||
self.stage_call.set(1).wait(timeout=self._timeout_wait_for_pv)
|
||||
status.wait(timeout=self.timeout_wait_for_signal)
|
||||
if self.scan_info.msg.scan_name != "nidaq_continuous_scan":
|
||||
status = self.on_kickoff()
|
||||
self.cancel_on_stop(status)
|
||||
status.wait(timeout=self._timeout_wait_for_pv)
|
||||
logger.info(f"Device {self.name} was staged: {NidaqState(self.state.get())}")
|
||||
|
||||
def on_kickoff(self) -> DeviceStatus | StatusBase:
|
||||
"""Kickoff the Nidaq"""
|
||||
status = self.kickoff_call.set(1)
|
||||
self.cancel_on_stop(status)
|
||||
return status
|
||||
|
||||
def on_unstage(self) -> DeviceStatus | StatusBase | None:
|
||||
"""Called while unstaging the device. Check that the Nidaq goes into Standby"""
|
||||
|
||||
def _get_state():
|
||||
return self.state.get() == NidaqState.STANDBY
|
||||
|
||||
if not self.wait_for_condition(
|
||||
condition=_get_state, timeout=self.timeout_wait_for_signal, check_stopped=False
|
||||
):
|
||||
raise NidaqError(
|
||||
f"Device {self.name} has not been reached in state STANDBY, current state {NidaqState(self.state.get())}"
|
||||
)
|
||||
status = CompareStatus(self.state, NidaqState.STANDBY)
|
||||
self.cancel_on_stop(status)
|
||||
status.wait(timeout=self.timeout_wait_for_signal)
|
||||
status = self.enable_compression.set(1)
|
||||
self.cancel_on_stop(status)
|
||||
status.wait(self._timeout_wait_for_pv)
|
||||
logger.info(f"Device {self.name} was unstaged: {NidaqState(self.state.get())}")
|
||||
|
||||
def on_pre_scan(self) -> DeviceStatus | StatusBase | None:
|
||||
@@ -334,15 +587,13 @@ class Nidaq(PSIDeviceBase, NidaqControl):
|
||||
if not self._check_if_scan_name_is_valid():
|
||||
return None
|
||||
|
||||
def _wait_for_state():
|
||||
return self.state.get() == NidaqState.KICKOFF
|
||||
if self.scan_info.msg.scan_name == "nidaq_continuous_scan":
|
||||
logger.info(f"Device {self.name} ready to be kicked off for nidaq_continuous_scan")
|
||||
return None
|
||||
|
||||
if not self.wait_for_condition(
|
||||
_wait_for_state, timeout=self.timeout_wait_for_signal, check_stopped=True
|
||||
):
|
||||
raise NidaqError(
|
||||
f"Device {self.name} failed to reach state KICKOFF during pre scan, current state {NidaqState(self.state.get())}"
|
||||
)
|
||||
status = CompareStatus(self.state, NidaqState.KICKOFF)
|
||||
self.cancel_on_stop(status)
|
||||
status.wait(timeout=self._timeout_wait_for_pv)
|
||||
logger.info(
|
||||
f"Device {self.name} ready to take data after pre_scan: {NidaqState(self.state.get())}"
|
||||
)
|
||||
@@ -359,19 +610,27 @@ class Nidaq(PSIDeviceBase, NidaqControl):
|
||||
"""
|
||||
if not self._check_if_scan_name_is_valid():
|
||||
return None
|
||||
self.on_stop()
|
||||
#TODO check if this wait can be removed. We are waiting in unstage anyways which will always be called afterwards
|
||||
# Wait for device to be stopped
|
||||
status = self.wait_for_condition(
|
||||
condition = lambda: self.state.get() == NidaqState.STANDBY,
|
||||
check_stopped=True,
|
||||
timeout=self.timeout_wait_for_signal,
|
||||
)
|
||||
|
||||
status = CompareStatus(self.state, NidaqState.STANDBY)
|
||||
self.cancel_on_stop(status)
|
||||
if self.scan_info.msg.scan_name != "nidaq_continuous_scan":
|
||||
self.on_stop()
|
||||
return status
|
||||
|
||||
def on_kickoff(self) -> DeviceStatus | StatusBase | None:
|
||||
"""Called to kickoff a device for a fly scan. Has to be called explicitly."""
|
||||
def _progress_update(self, value, **kwargs) -> None:
|
||||
"""Callback method to update the scan progress, runs a callback
|
||||
to SUB_PROGRESS subscribers, i.e. BEC.
|
||||
|
||||
Args:
|
||||
value (int) : current progress value
|
||||
"""
|
||||
scan_duration = self.scan_info.msg.scan_parameters.get("scan_duration", None)
|
||||
if not isinstance(scan_duration, (int, float)):
|
||||
return
|
||||
value = scan_duration - value
|
||||
max_value = scan_duration
|
||||
self.progress_signal.put(value=value, max_value=max_value, done=bool(max_value == value))
|
||||
|
||||
def on_stop(self) -> None:
|
||||
"""Called when the device is stopped."""
|
||||
self.stop_call.set(1).wait()
|
||||
self.stop_call.put(1)
|
||||
|
||||
@@ -2,17 +2,22 @@ import enum
|
||||
|
||||
|
||||
class NIDAQCompression(str, enum.Enum):
|
||||
""" Options for Compression"""
|
||||
"""Options for Compression"""
|
||||
|
||||
OFF = 0
|
||||
ON = 1
|
||||
|
||||
|
||||
class ScanType(int, enum.Enum):
|
||||
""" Triggering options of the backend"""
|
||||
"""Triggering options of the backend"""
|
||||
|
||||
TRIGGERED = 0
|
||||
CONTINUOUS = 1
|
||||
|
||||
|
||||
class NidaqState(int, enum.Enum):
|
||||
""" Possible States of the NIDAQ backend"""
|
||||
"""Possible States of the NIDAQ backend"""
|
||||
|
||||
DISABLED = 0
|
||||
STANDBY = 1
|
||||
STAGE = 2
|
||||
@@ -20,8 +25,10 @@ class NidaqState(int, enum.Enum):
|
||||
ACQUIRE = 4
|
||||
UNSTAGE = 5
|
||||
|
||||
|
||||
class ScanRates(int, enum.Enum):
|
||||
""" Sampling Rate options for the backend, in kHZ and MHz"""
|
||||
"""Sampling Rate options for the backend, in kHZ and MHz"""
|
||||
|
||||
HUNDRED_KHZ = 0
|
||||
FIVE_HUNDRED_KHZ = 1
|
||||
ONE_MHZ = 2
|
||||
@@ -31,15 +38,23 @@ class ScanRates(int, enum.Enum):
|
||||
TEN_MHZ = 6
|
||||
FOURTEEN_THREE_MHZ = 7
|
||||
|
||||
|
||||
class ReadoutRange(int, enum.Enum):
|
||||
"""ReadoutRange in +-V"""
|
||||
|
||||
ONE_V = 0
|
||||
TWO_V = 1
|
||||
FIVE_V = 2
|
||||
TEN_V = 3
|
||||
|
||||
class EncoderTypes(int, enum.Enum):
|
||||
""" Encoder Types"""
|
||||
X_1 = 0
|
||||
X_2 = 1
|
||||
X_4 = 2
|
||||
|
||||
class EncoderFactors(int, enum.Enum):
|
||||
"""Encoder Factors"""
|
||||
|
||||
X1_16 = 0
|
||||
X1_8 = 1
|
||||
X1_4 = 2
|
||||
X1_2 = 3
|
||||
X1 = 4
|
||||
X2 = 5
|
||||
X4 = 6
|
||||
|
||||
194
debye_bec/devices/reffoilchanger.py
Normal file
194
debye_bec/devices/reffoilchanger.py
Normal file
@@ -0,0 +1,194 @@
|
||||
"""ES2 Reference Foil Changer"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import enum
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from ophyd import Component as Cpt
|
||||
from ophyd import EpicsSignal, EpicsSignalRO, EpicsSignalWithRBV
|
||||
from ophyd.status import DeviceStatus
|
||||
from ophyd_devices.interfaces.base_classes.psi_device_base import PSIDeviceBase
|
||||
from ophyd_devices.utils.errors import DeviceStopError
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from bec_lib.devicemanager import ScanInfo
|
||||
|
||||
|
||||
class Status(int, enum.Enum):
|
||||
"""Enum class for the status field"""
|
||||
|
||||
BOOT = 0
|
||||
RETRACTED = 1
|
||||
INSERTED = 2
|
||||
MOVING = 3
|
||||
ERROR = 4
|
||||
|
||||
|
||||
class OpMode(int, enum.Enum):
|
||||
"""Enum class for the Operating Mode field"""
|
||||
|
||||
USERMODE = 0
|
||||
MAINTENANCEMODE = 1
|
||||
DIAGNOSTICMODE = 2
|
||||
ERRORMODE = 3
|
||||
|
||||
|
||||
class Reffoilchanger(PSIDeviceBase):
|
||||
"""Class for the ES2 Reference Foil Changer"""
|
||||
|
||||
USER_ACCESS = ["insert"]
|
||||
|
||||
inserted = Cpt(
|
||||
EpicsSignalRO, suffix="ES2-REF:TRY-FilterInserted", kind="config", doc="Inserted indicator"
|
||||
)
|
||||
retracted = Cpt(
|
||||
EpicsSignalRO,
|
||||
suffix="ES2-REF:TRY-FilterRetracted",
|
||||
kind="config",
|
||||
doc="Retracted indicator",
|
||||
)
|
||||
moving = Cpt(EpicsSignalRO, suffix="ES2-REF:ROTY.MOVN", kind="config", doc="Moving indicator")
|
||||
status = Cpt(
|
||||
EpicsSignal, suffix="ES2-REF:SELN-FilterState-ENUM_RBV", kind="config", doc="Status"
|
||||
)
|
||||
op_mode = Cpt(
|
||||
EpicsSignalWithRBV, suffix="ES2-REF:SELN-OpMode-ENUM", kind="config", doc="Status"
|
||||
)
|
||||
ref_set = Cpt(EpicsSignal, suffix="ES2-REF:SELN-SET", kind="config", doc="Requested reference")
|
||||
ref_rb = Cpt(
|
||||
EpicsSignalRO, suffix="ES2-REF:SELN-RB", kind="config", doc="Currently set reference"
|
||||
)
|
||||
|
||||
foil01 = Cpt(EpicsSignalRO, suffix="ES-REFFOIL:FOIL01.DESC", kind="config", doc="Foil 01")
|
||||
foil02 = Cpt(EpicsSignalRO, suffix="ES-REFFOIL:FOIL02.DESC", kind="config", doc="Foil 02")
|
||||
foil03 = Cpt(EpicsSignalRO, suffix="ES-REFFOIL:FOIL03.DESC", kind="config", doc="Foil 03")
|
||||
foil04 = Cpt(EpicsSignalRO, suffix="ES-REFFOIL:FOIL04.DESC", kind="config", doc="Foil 04")
|
||||
foil05 = Cpt(EpicsSignalRO, suffix="ES-REFFOIL:FOIL05.DESC", kind="config", doc="Foil 05")
|
||||
foil06 = Cpt(EpicsSignalRO, suffix="ES-REFFOIL:FOIL06.DESC", kind="config", doc="Foil 06")
|
||||
foil07 = Cpt(EpicsSignalRO, suffix="ES-REFFOIL:FOIL07.DESC", kind="config", doc="Foil 07")
|
||||
foil08 = Cpt(EpicsSignalRO, suffix="ES-REFFOIL:FOIL08.DESC", kind="config", doc="Foil 08")
|
||||
foil09 = Cpt(EpicsSignalRO, suffix="ES-REFFOIL:FOIL09.DESC", kind="config", doc="Foil 09")
|
||||
foil10 = Cpt(EpicsSignalRO, suffix="ES-REFFOIL:FOIL10.DESC", kind="config", doc="Foil 10")
|
||||
foil11 = Cpt(EpicsSignalRO, suffix="ES-REFFOIL:FOIL11.DESC", kind="config", doc="Foil 11")
|
||||
foil12 = Cpt(EpicsSignalRO, suffix="ES-REFFOIL:FOIL12.DESC", kind="config", doc="Foil 12")
|
||||
foil13 = Cpt(EpicsSignalRO, suffix="ES-REFFOIL:FOIL13.DESC", kind="config", doc="Foil 13")
|
||||
foil14 = Cpt(EpicsSignalRO, suffix="ES-REFFOIL:FOIL14.DESC", kind="config", doc="Foil 14")
|
||||
foil15 = Cpt(EpicsSignalRO, suffix="ES-REFFOIL:FOIL15.DESC", kind="config", doc="Foil 15")
|
||||
foil16 = Cpt(EpicsSignalRO, suffix="ES-REFFOIL:FOIL16.DESC", kind="config", doc="Foil 16")
|
||||
foil17 = Cpt(EpicsSignalRO, suffix="ES-REFFOIL:FOIL17.DESC", kind="config", doc="Foil 17")
|
||||
foil18 = Cpt(EpicsSignalRO, suffix="ES-REFFOIL:FOIL18.DESC", kind="config", doc="Foil 18")
|
||||
foil19 = Cpt(EpicsSignalRO, suffix="ES-REFFOIL:FOIL19.DESC", kind="config", doc="Foil 19")
|
||||
foil20 = Cpt(EpicsSignalRO, suffix="ES-REFFOIL:FOIL20.DESC", kind="config", doc="Foil 20")
|
||||
foil21 = Cpt(EpicsSignalRO, suffix="ES-REFFOIL:FOIL21.DESC", kind="config", doc="Foil 21")
|
||||
foil22 = Cpt(EpicsSignalRO, suffix="ES-REFFOIL:FOIL22.DESC", kind="config", doc="Foil 22")
|
||||
foil23 = Cpt(EpicsSignalRO, suffix="ES-REFFOIL:FOIL23.DESC", kind="config", doc="Foil 23")
|
||||
foil24 = Cpt(EpicsSignalRO, suffix="ES-REFFOIL:FOIL24.DESC", kind="config", doc="Foil 24")
|
||||
foil25 = Cpt(EpicsSignalRO, suffix="ES-REFFOIL:FOIL25.DESC", kind="config", doc="Foil 25")
|
||||
foil26 = Cpt(EpicsSignalRO, suffix="ES-REFFOIL:FOIL26.DESC", kind="config", doc="Foil 26")
|
||||
foil27 = Cpt(EpicsSignalRO, suffix="ES-REFFOIL:FOIL27.DESC", kind="config", doc="Foil 27")
|
||||
foil28 = Cpt(EpicsSignalRO, suffix="ES-REFFOIL:FOIL28.DESC", kind="config", doc="Foil 28")
|
||||
foil29 = Cpt(EpicsSignalRO, suffix="ES-REFFOIL:FOIL29.DESC", kind="config", doc="Foil 29")
|
||||
foil30 = Cpt(EpicsSignalRO, suffix="ES-REFFOIL:FOIL30.DESC", kind="config", doc="Foil 30")
|
||||
foil31 = Cpt(EpicsSignalRO, suffix="ES-REFFOIL:FOIL31.DESC", kind="config", doc="Foil 31")
|
||||
foil32 = Cpt(EpicsSignalRO, suffix="ES-REFFOIL:FOIL32.DESC", kind="config", doc="Foil 32")
|
||||
foil33 = Cpt(EpicsSignalRO, suffix="ES-REFFOIL:FOIL33.DESC", kind="config", doc="Foil 33")
|
||||
foil34 = Cpt(EpicsSignalRO, suffix="ES-REFFOIL:FOIL34.DESC", kind="config", doc="Foil 34")
|
||||
foil35 = Cpt(EpicsSignalRO, suffix="ES-REFFOIL:FOIL35.DESC", kind="config", doc="Foil 35")
|
||||
foil36 = Cpt(EpicsSignalRO, suffix="ES-REFFOIL:FOIL36.DESC", kind="config", doc="Foil 36")
|
||||
foil37 = Cpt(EpicsSignalRO, suffix="ES-REFFOIL:FOIL37.DESC", kind="config", doc="Foil 37")
|
||||
foil38 = Cpt(EpicsSignalRO, suffix="ES-REFFOIL:FOIL38.DESC", kind="config", doc="Foil 38")
|
||||
|
||||
def __init__(self, *, name: str, prefix: str = "", scan_info: ScanInfo | None = None, **kwargs):
|
||||
super().__init__(name=name, prefix=prefix, scan_info=scan_info, **kwargs)
|
||||
|
||||
self.foils = [
|
||||
self.foil01,
|
||||
self.foil02,
|
||||
self.foil03,
|
||||
self.foil04,
|
||||
self.foil05,
|
||||
self.foil06,
|
||||
self.foil07,
|
||||
self.foil08,
|
||||
self.foil09,
|
||||
self.foil10,
|
||||
self.foil11,
|
||||
self.foil12,
|
||||
self.foil13,
|
||||
self.foil14,
|
||||
self.foil15,
|
||||
self.foil16,
|
||||
self.foil17,
|
||||
self.foil18,
|
||||
self.foil19,
|
||||
self.foil20,
|
||||
self.foil21,
|
||||
self.foil22,
|
||||
self.foil23,
|
||||
self.foil24,
|
||||
self.foil25,
|
||||
self.foil26,
|
||||
self.foil27,
|
||||
self.foil28,
|
||||
self.foil29,
|
||||
self.foil30,
|
||||
self.foil31,
|
||||
self.foil32,
|
||||
self.foil33,
|
||||
self.foil34,
|
||||
self.foil35,
|
||||
self.foil36,
|
||||
self.foil37,
|
||||
self.foil38,
|
||||
]
|
||||
|
||||
def insert(self, ref: str, wait: bool = False) -> DeviceStatus:
|
||||
"""Insert a reference
|
||||
|
||||
Args:
|
||||
ref (str) : Desired reference foil name, e.g. Fe or Pt
|
||||
wait (bool): If you like to wait for the filling to finish. Default False.
|
||||
"""
|
||||
|
||||
filter_number = -1
|
||||
for i, foil in enumerate(self.foils):
|
||||
if foil.get() == ref:
|
||||
filter_number = i + 1
|
||||
break
|
||||
if filter_number == -1:
|
||||
raise ValueError(f"Requested foil ({ref}) is not in list of available foils")
|
||||
|
||||
if self.op_mode.get() == OpMode.USERMODE:
|
||||
self.ref_set.put(filter_number)
|
||||
|
||||
def wait_for_status():
|
||||
return (
|
||||
(self.status.get() == Status.RETRACTED)
|
||||
or (self.status.get() == Status.MOVING)
|
||||
or (
|
||||
self.ref_rb.get() < (filter_number + 0.2)
|
||||
and self.ref_rb.get() > (filter_number - 0.2)
|
||||
)
|
||||
)
|
||||
|
||||
timeout = 3
|
||||
if not self.wait_for_condition(wait_for_status, timeout=timeout, check_stopped=True):
|
||||
raise TimeoutError(
|
||||
f"Reference foil changer did not retract the current foil within {timeout}s"
|
||||
)
|
||||
|
||||
def wait_for_change_finished():
|
||||
return self.status.get() == Status.INSERTED and self.op_mode == OpMode.USERMODE
|
||||
|
||||
# Wait until new reference foil is inserted
|
||||
status = self.task_handler.submit_task(
|
||||
task=self.wait_for_condition, task_args=(wait_for_change_finished, 5, True)
|
||||
)
|
||||
if wait:
|
||||
status.wait()
|
||||
return status
|
||||
else:
|
||||
raise DeviceStopError(
|
||||
f"Reference foil changer must be in User Mode but is in {self.op_mode.get(as_string=True)}"
|
||||
)
|
||||
@@ -0,0 +1 @@
|
||||
from .debye_nexus_structure import DebyeNexusStructure
|
||||
|
||||
125
debye_bec/file_writer/debye_nexus_structure.py
Normal file
125
debye_bec/file_writer/debye_nexus_structure.py
Normal file
@@ -0,0 +1,125 @@
|
||||
from bec_server.file_writer.default_writer import DefaultFormat
|
||||
|
||||
|
||||
class DebyeNexusStructure(DefaultFormat):
|
||||
"""Nexus Structure for Debye"""
|
||||
|
||||
def format(self) -> None:
|
||||
"""Specify the file format for the file writer."""
|
||||
|
||||
entry = self.storage.create_group(name="entry")
|
||||
entry.attrs["NX_class"] = "NXentry"
|
||||
instrument = entry.create_group(name="instrument")
|
||||
instrument.attrs["NX_class"] = "NXinstrument"
|
||||
|
||||
###################
|
||||
## mo1_bragg specific information
|
||||
###################
|
||||
|
||||
# Logic if device exist
|
||||
if "mo1_bragg" in self.device_manager.devices:
|
||||
|
||||
monochromator = instrument.create_group(name="monochromator")
|
||||
monochromator.attrs["NX_class"] = "NXmonochromator"
|
||||
crystal = monochromator.create_group(name="crystal")
|
||||
crystal.attrs["NX_class"] = "NXcrystal"
|
||||
|
||||
# Create a dataset
|
||||
chemical_formular = crystal.create_dataset(name="chemical_formular", data="Si")
|
||||
chemical_formular.attrs["NX_class"] = "NX_CHAR"
|
||||
|
||||
# Create a softlink
|
||||
d_spacing = crystal.create_soft_link(
|
||||
name="d_spacing",
|
||||
target="/entry/collection/devices/mo1_bragg/mo1_bragg_crystal_current_d_spacing/value",
|
||||
)
|
||||
d_spacing.attrs["NX_class"] = "NX_FLOAT"
|
||||
|
||||
offset = crystal.create_soft_link(
|
||||
name="offset",
|
||||
target="/entry/collection/devices/mo1_bragg/mo1_bragg_crystal_current_offset/value",
|
||||
)
|
||||
offset.attrs["NX_class"] = "NX_FLOAT"
|
||||
|
||||
reflection = crystal.create_soft_link(
|
||||
name="reflection",
|
||||
target="/entry/collection/devices/mo1_bragg/mo1_bragg_crystal_current_xtal_string/value",
|
||||
)
|
||||
reflection.attrs["NX_class"] = "NX_CHAR"
|
||||
|
||||
##################
|
||||
## cm mirror specific information
|
||||
###################
|
||||
|
||||
collimating_mirror = instrument.create_group(name="collimating_mirror")
|
||||
collimating_mirror.attrs["NX_class"] = "NXmirror"
|
||||
|
||||
cm_substrate_material = collimating_mirror.create_dataset(
|
||||
name="substrate_material", data="Si"
|
||||
)
|
||||
cm_substrate_material.attrs["NX_class"] = "NX_CHAR"
|
||||
|
||||
cm_bending_radius = collimating_mirror.create_soft_link(
|
||||
name="sagittal radius",
|
||||
target="/entry/collection/devices/cm_bnd_radius/cm_bnd_radius/value",
|
||||
)
|
||||
cm_bending_radius.attrs["NX_class"] = "NX_FLOAT"
|
||||
cm_bending_radius.attrs["units"] = "km"
|
||||
|
||||
cm_incidence_angle = collimating_mirror.create_soft_link(
|
||||
name="incidence angle", target="/entry/collection/devices/cm_rotx/cm_rotx/value"
|
||||
)
|
||||
cm_incidence_angle.attrs["NX_class"] = "NX_FLOAT"
|
||||
|
||||
cm_yaw_angle = collimating_mirror.create_soft_link(
|
||||
name="incident angle", target="/entry/collection/devices/cm_roty/cm_roty/value"
|
||||
)
|
||||
cm_yaw_angle.attrs["NX_class"] = "NX_FLOAT"
|
||||
|
||||
##################
|
||||
## fm mirror specific information
|
||||
###################
|
||||
|
||||
focusing_mirror = instrument.create_group(name="focusing_mirror")
|
||||
focusing_mirror.attrs["NX_class"] = "NXmirror"
|
||||
|
||||
fm_substrate_material = focusing_mirror.create_dataset(name="substrate_material", data="Si")
|
||||
fm_substrate_material.attrs["NX_class"] = "NX_CHAR"
|
||||
|
||||
fm_bending_radius = focusing_mirror.create_soft_link(
|
||||
name="sagittal radius",
|
||||
target="/entry/collection/devices/fm_bnd_radius/fm_bnd_radius/value",
|
||||
)
|
||||
fm_bending_radius.attrs["NX_class"] = "NX_FLOAT"
|
||||
|
||||
fm_incidence_angle = focusing_mirror.create_soft_link(
|
||||
name="incidence angle",
|
||||
target="/entry/collection/devices/fm_incidence_angle/fm_incidence_angle/value",
|
||||
)
|
||||
fm_incidence_angle.attrs["NX_class"] = "NX_FLOAT"
|
||||
|
||||
fm_yaw_angle = focusing_mirror.create_soft_link(
|
||||
name="yaw angle", target="/entry/collection/devices/fm_roty/fm_roty/value"
|
||||
)
|
||||
fm_yaw_angle.attrs["NX_class"] = "NX_FLOAT"
|
||||
|
||||
fm_roll_angle = focusing_mirror.create_soft_link(
|
||||
name="roll angle", target="/entry/collection/devices/fm_rotz/fm_rotz/value"
|
||||
)
|
||||
fm_roll_angle.attrs["NX_class"] = "NX_FLOAT"
|
||||
|
||||
##################
|
||||
## source specific information
|
||||
###################
|
||||
|
||||
source = instrument.create_group(name="source")
|
||||
source.attrs["NX_class"] = "NXsource"
|
||||
|
||||
beamline_name = source.create_dataset(name="beamline_name", data="Debye")
|
||||
beamline_name.attrs["NX_class"] = "NX_CHAR"
|
||||
|
||||
facility_name = source.create_dataset(name="facility_name", data="Swiss Light Source")
|
||||
facility_name.attrs["NX_class"] = "NX_CHAR"
|
||||
|
||||
probe = source.create_dataset(name="probe", data="X-ray")
|
||||
probe.attrs["NX_class"] = "NX_CHAR"
|
||||
@@ -1 +1,7 @@
|
||||
from .mono_bragg_scans import XASSimpleScan, XASSimpleScanWithXRD, XASAdvancedScan, XASAdvancedScanWithXRD
|
||||
from .mono_bragg_scans import (
|
||||
XASAdvancedScan,
|
||||
XASAdvancedScanWithXRD,
|
||||
XASSimpleScan,
|
||||
XASSimpleScanWithXRD,
|
||||
)
|
||||
from .nidaq_cont_scan import NIDAQContinuousScan
|
||||
|
||||
0
debye_bec/scans/metadata_schema/__init__.py
Normal file
0
debye_bec/scans/metadata_schema/__init__.py
Normal file
12
debye_bec/scans/metadata_schema/metadata_schema_registry.py
Normal file
12
debye_bec/scans/metadata_schema/metadata_schema_registry.py
Normal file
@@ -0,0 +1,12 @@
|
||||
# from .metadata_schema_xas_simple_scan import xas_simple_scan_schema
|
||||
|
||||
METADATA_SCHEMA_REGISTRY = { # "xas_simple_scan": xas_simple_scan_schema
|
||||
# Add models which should be used to validate scan metadata here.
|
||||
# Make a model according to the template, and import it as above
|
||||
# Then associate it with a scan like so:
|
||||
# "example_scan": ExampleSchema
|
||||
}
|
||||
|
||||
# Define a default schema type which should be used as the fallback for everything:
|
||||
|
||||
DEFAULT_SCHEMA = None
|
||||
34
debye_bec/scans/metadata_schema/metadata_schema_template.py
Normal file
34
debye_bec/scans/metadata_schema/metadata_schema_template.py
Normal file
@@ -0,0 +1,34 @@
|
||||
# # By inheriting from BasicScanMetadata you can define a schema by which metadata
|
||||
# # supplied to a scan must be validated.
|
||||
# # This schema is a Pydantic model: https://docs.pydantic.dev/latest/concepts/models/
|
||||
# # but by default it will still allow you to add any arbitrary information to it.
|
||||
# # That is to say, when you run a scan with which such a model has been associated in the
|
||||
# # metadata_schema_registry, you can supply any python dictionary with strings as keys
|
||||
# # and built-in python types (strings, integers, floats) as values, and these will be
|
||||
# # added to the experiment metadata, but it *must* contain the keys and values of the
|
||||
# # types defined in the schema class.
|
||||
# #
|
||||
# #
|
||||
# # For example, say that you would like to enforce recording information about sample
|
||||
# # pretreatment, you could define the following:
|
||||
# #
|
||||
#
|
||||
# from bec_lib.metadata_schema import BasicScanMetadata
|
||||
#
|
||||
#
|
||||
# class ExampleSchema(BasicScanMetadata):
|
||||
# treatment_description: str
|
||||
# treatment_temperature_k: int
|
||||
#
|
||||
#
|
||||
# # If this was used according to the example in metadata_schema_registry.py,
|
||||
# # then when calling the scan, the user would need to write something like:
|
||||
# >>> scans.example_scan(
|
||||
# >>> motor,
|
||||
# >>> 1,
|
||||
# >>> 2,
|
||||
# >>> 3,
|
||||
# >>> metadata={"treatment_description": "oven overnight", "treatment_temperature_k": 575},
|
||||
# >>> )
|
||||
#
|
||||
# # And the additional metadata would be saved in the HDF5 file created for the scan.
|
||||
@@ -0,0 +1,8 @@
|
||||
from bec_lib.metadata_schema import BasicScanMetadata
|
||||
|
||||
|
||||
#
|
||||
#
|
||||
class xas_simple_scan_schema(BasicScanMetadata):
|
||||
Edge: str
|
||||
Element: str
|
||||
@@ -72,11 +72,7 @@ class XASSimpleScan(AsyncFlyScanBase):
|
||||
yield None
|
||||
|
||||
def pre_scan(self):
|
||||
"""Pre Scan action. Ensure the motor movetype is set to energy, then check
|
||||
limits for start/end energy.
|
||||
#TODO Remove once the motor movetype is removed and ANGLE motion is a pseudo motor.
|
||||
"""
|
||||
yield from self.stubs.send_rpc_and_wait(self.motor, "move_type.set", "energy")
|
||||
"""Pre Scan action."""
|
||||
|
||||
self._check_limits()
|
||||
# Ensure parent class pre_scan actions to be called.
|
||||
|
||||
84
debye_bec/scans/nidaq_cont_scan.py
Normal file
84
debye_bec/scans/nidaq_cont_scan.py
Normal file
@@ -0,0 +1,84 @@
|
||||
"""This module contains the scan class for the nidaq of the Debye beamline for use in continuous mode."""
|
||||
|
||||
import time
|
||||
from typing import Literal
|
||||
|
||||
import numpy as np
|
||||
from bec_lib.device import DeviceBase
|
||||
from bec_lib.logger import bec_logger
|
||||
from bec_server.scan_server.scans import AsyncFlyScanBase
|
||||
|
||||
logger = bec_logger.logger
|
||||
|
||||
|
||||
class NIDAQContinuousScan(AsyncFlyScanBase):
|
||||
"""Class for the nidaq continuous scan (without mono)"""
|
||||
|
||||
scan_name = "nidaq_continuous_scan"
|
||||
scan_type = "fly"
|
||||
scan_report_hint = "device_progress"
|
||||
required_kwargs = []
|
||||
use_scan_progress_report = False
|
||||
pre_move = False
|
||||
gui_config = {"Scan Parameters": ["scan_duration"], "Data Compression": ["compression"]}
|
||||
|
||||
def __init__(
|
||||
self, scan_duration: float, daq: DeviceBase = "nidaq", compression: bool = False, **kwargs
|
||||
):
|
||||
"""The NIDAQ continuous scan is used to measure with the NIDAQ without moving the
|
||||
monochromator or any other motor. The NIDAQ thus runs in continuous mode, with a
|
||||
set scan_duration.
|
||||
|
||||
Args:
|
||||
scan_duration (float): Duration of the scan.
|
||||
daq (DeviceBase, optional): DAQ device to be used for the scan.
|
||||
Defaults to "nidaq".
|
||||
Examples:
|
||||
>>> scans.nidaq_continuous_scan(scan_duration=10)
|
||||
"""
|
||||
super().__init__(**kwargs)
|
||||
self.scan_duration = scan_duration
|
||||
self.daq = daq
|
||||
self.start_time = 0
|
||||
self.primary_readout_cycle = 1
|
||||
self.scan_parameters["scan_duration"] = scan_duration
|
||||
self.scan_parameters["compression"] = compression
|
||||
|
||||
def update_readout_priority(self):
|
||||
"""Ensure that NIDAQ is not monitored for any quick EXAFS."""
|
||||
super().update_readout_priority()
|
||||
self.readout_priority["async"].append("nidaq")
|
||||
|
||||
def prepare_positions(self):
|
||||
"""Prepare the positions for the scan."""
|
||||
yield None
|
||||
|
||||
def pre_scan(self):
|
||||
"""Pre Scan action."""
|
||||
|
||||
self.start_time = time.time()
|
||||
# Ensure parent class pre_scan actions to be called.
|
||||
yield from super().pre_scan()
|
||||
|
||||
def scan_report_instructions(self):
|
||||
"""
|
||||
Return the instructions for the scan report.
|
||||
"""
|
||||
yield from self.stubs.scan_report_instruction({"device_progress": [self.daq]})
|
||||
|
||||
def scan_core(self):
|
||||
"""Run the scan core.
|
||||
Kickoff the acquisition of the NIDAQ wait for the completion of the scan.
|
||||
"""
|
||||
kickoff_status = yield from self.stubs.kickoff(device=self.daq)
|
||||
kickoff_status.wait(timeout=5) # wait for proper kickoff of device
|
||||
|
||||
complete_status = yield from self.stubs.complete(device=self.daq, wait=False)
|
||||
|
||||
while not complete_status.done:
|
||||
# Readout monitored devices
|
||||
yield from self.stubs.read(group="monitored", point_id=self.point_id)
|
||||
time.sleep(self.primary_readout_cycle)
|
||||
self.point_id += 1
|
||||
|
||||
self.num_pos = self.point_id
|
||||
@@ -5,7 +5,7 @@ build-backend = "hatchling.build"
|
||||
[project]
|
||||
name = "debye_bec"
|
||||
version = "0.0.0"
|
||||
description = "Custom device implementations based on the ophyd hardware abstraction layer"
|
||||
description = "A plugin repository for BEC"
|
||||
requires-python = ">=3.10"
|
||||
classifiers = [
|
||||
"Development Status :: 3 - Alpha",
|
||||
@@ -16,13 +16,14 @@ dependencies = ["numpy", "scipy", "bec_lib", "h5py", "ophyd_devices"]
|
||||
|
||||
[project.optional-dependencies]
|
||||
dev = [
|
||||
"bec_server",
|
||||
"black ~= 24.0",
|
||||
"black",
|
||||
"copier",
|
||||
"isort",
|
||||
"coverage",
|
||||
"pylint",
|
||||
"pytest",
|
||||
"pytest-random-order",
|
||||
"bec_server",
|
||||
]
|
||||
|
||||
[project.entry-points."bec"]
|
||||
@@ -37,12 +38,18 @@ plugin_file_writer = "debye_bec.file_writer"
|
||||
[project.entry-points."bec.scans"]
|
||||
plugin_scans = "debye_bec.scans"
|
||||
|
||||
[project.entry-points."bec.scans.metadata_schema"]
|
||||
plugin_metadata_schema = "debye_bec.scans.metadata_schema"
|
||||
|
||||
[project.entry-points."bec.ipython_client_startup"]
|
||||
plugin_ipython_client_pre = "debye_bec.bec_ipython_client.startup.pre_startup"
|
||||
plugin_ipython_client_post = "debye_bec.bec_ipython_client.startup"
|
||||
|
||||
[project.entry-points."bec.widgets"]
|
||||
plugin_widgets = "debye_bec.bec_widgets"
|
||||
[project.entry-points."bec.widgets.auto_updates"]
|
||||
plugin_widgets_update = "debye_bec.bec_widgets.auto_updates"
|
||||
|
||||
[project.entry-points."bec.widgets.user_widgets"]
|
||||
plugin_widgets = "debye_bec.bec_widgets.widgets"
|
||||
|
||||
[tool.hatch.build.targets.wheel]
|
||||
include = ["*"]
|
||||
|
||||
@@ -1,31 +1,34 @@
|
||||
# Getting Started with Testing using pytest
|
||||
|
||||
BEC is using the [pytest](https://docs.pytest.org/en/8.0.x/) framework.
|
||||
It can be install via
|
||||
``` bash
|
||||
BEC is using the [pytest](https://docs.pytest.org/en/latest/) framework.
|
||||
It can be installed via
|
||||
|
||||
```bash
|
||||
pip install pytest
|
||||
```
|
||||
in your *python environment*.
|
||||
|
||||
in your _python environment_.
|
||||
We note that pytest is part of the optional-dependencies `[dev]` of the plugin package.
|
||||
|
||||
## Introduction
|
||||
|
||||
Tests in this package should be stored in the `tests` directory.
|
||||
We suggest to sort tests of different submodules, i.e. `scans` or `devices` in the respective folder structure, and to folow a naming convention of `<test_module_name.py>`.
|
||||
It is mandatory for test files to begin with `test_` for pytest to discover them.
|
||||
|
||||
To run all tests, navigate to the directory of the plugin from the command line, and run the command
|
||||
To run all tests, navigate to the directory of the plugin from the command line, and run the command
|
||||
|
||||
``` bash
|
||||
```bash
|
||||
pytest -v --random-order ./tests
|
||||
```
|
||||
|
||||
Note, the python environment needs to be active.
|
||||
The additional arg `-v` allows pytest to run in verbose mode which provides more detailed information about the tests being run.
|
||||
The argument `--random-order` instructs pytest to run the tests in random order, which is the default in the CI pipelines.
|
||||
The argument `--random-order` instructs pytest to run the tests in random order, which is the default in the CI pipelines.
|
||||
|
||||
## Test examples
|
||||
|
||||
Writing tests can be quite specific for the given function.
|
||||
Writing tests can be quite specific for the given function.
|
||||
We recommend writing tests as isolated as possible, i.e. try to test single functions instead of full classes.
|
||||
A very useful class to enable isolated testing is [MagicMock](https://docs.python.org/3/library/unittest.mock.html).
|
||||
In addition, we also recommend to take a look at the [How-to guides from pytest](https://docs.pytest.org/en/8.0.x/how-to/index.html).
|
||||
|
||||
|
||||
@@ -1,31 +1,34 @@
|
||||
# Getting Started with Testing using pytest
|
||||
|
||||
BEC is using the [pytest](https://docs.pytest.org/en/8.0.x/) framework.
|
||||
It can be install via
|
||||
``` bash
|
||||
BEC is using the [pytest](https://docs.pytest.org/en/latest/) framework.
|
||||
It can be installed via
|
||||
|
||||
```bash
|
||||
pip install pytest
|
||||
```
|
||||
in your *python environment*.
|
||||
|
||||
in your _python environment_.
|
||||
We note that pytest is part of the optional-dependencies `[dev]` of the plugin package.
|
||||
|
||||
## Introduction
|
||||
|
||||
Tests in this package should be stored in the `tests` directory.
|
||||
We suggest to sort tests of different submodules, i.e. `scans` or `devices` in the respective folder structure, and to folow a naming convention of `<test_module_name.py>`.
|
||||
It is mandatory for test files to begin with `test_` for pytest to discover them.
|
||||
|
||||
To run all tests, navigate to the directory of the plugin from the command line, and run the command
|
||||
To run all tests, navigate to the directory of the plugin from the command line, and run the command
|
||||
|
||||
``` bash
|
||||
```bash
|
||||
pytest -v --random-order ./tests
|
||||
```
|
||||
|
||||
Note, the python environment needs to be active.
|
||||
The additional arg `-v` allows pytest to run in verbose mode which provides more detailed information about the tests being run.
|
||||
The argument `--random-order` instructs pytest to run the tests in random order, which is the default in the CI pipelines.
|
||||
The argument `--random-order` instructs pytest to run the tests in random order, which is the default in the CI pipelines.
|
||||
|
||||
## Test examples
|
||||
|
||||
Writing tests can be quite specific for the given function.
|
||||
Writing tests can be quite specific for the given function.
|
||||
We recommend writing tests as isolated as possible, i.e. try to test single functions instead of full classes.
|
||||
A very useful class to enable isolated testing is [MagicMock](https://docs.python.org/3/library/unittest.mock.html).
|
||||
In addition, we also recommend to take a look at the [How-to guides from pytest](https://docs.pytest.org/en/8.0.x/how-to/index.html).
|
||||
|
||||
|
||||
34
tests/tests_dap_services/README.md
Normal file
34
tests/tests_dap_services/README.md
Normal file
@@ -0,0 +1,34 @@
|
||||
# Getting Started with Testing using pytest
|
||||
|
||||
BEC is using the [pytest](https://docs.pytest.org/en/latest/) framework.
|
||||
It can be installed via
|
||||
|
||||
```bash
|
||||
pip install pytest
|
||||
```
|
||||
|
||||
in your _python environment_.
|
||||
We note that pytest is part of the optional-dependencies `[dev]` of the plugin package.
|
||||
|
||||
## Introduction
|
||||
|
||||
Tests in this package should be stored in the `tests` directory.
|
||||
We suggest to sort tests of different submodules, i.e. `scans` or `devices` in the respective folder structure, and to folow a naming convention of `<test_module_name.py>`.
|
||||
It is mandatory for test files to begin with `test_` for pytest to discover them.
|
||||
|
||||
To run all tests, navigate to the directory of the plugin from the command line, and run the command
|
||||
|
||||
```bash
|
||||
pytest -v --random-order ./tests
|
||||
```
|
||||
|
||||
Note, the python environment needs to be active.
|
||||
The additional arg `-v` allows pytest to run in verbose mode which provides more detailed information about the tests being run.
|
||||
The argument `--random-order` instructs pytest to run the tests in random order, which is the default in the CI pipelines.
|
||||
|
||||
## Test examples
|
||||
|
||||
Writing tests can be quite specific for the given function.
|
||||
We recommend writing tests as isolated as possible, i.e. try to test single functions instead of full classes.
|
||||
A very useful class to enable isolated testing is [MagicMock](https://docs.python.org/3/library/unittest.mock.html).
|
||||
In addition, we also recommend to take a look at the [How-to guides from pytest](https://docs.pytest.org/en/8.0.x/how-to/index.html).
|
||||
@@ -1,31 +1,34 @@
|
||||
# Getting Started with Testing using pytest
|
||||
|
||||
BEC is using the [pytest](https://docs.pytest.org/en/8.0.x/) framework.
|
||||
It can be install via
|
||||
``` bash
|
||||
BEC is using the [pytest](https://docs.pytest.org/en/latest/) framework.
|
||||
It can be installed via
|
||||
|
||||
```bash
|
||||
pip install pytest
|
||||
```
|
||||
in your *python environment*.
|
||||
|
||||
in your _python environment_.
|
||||
We note that pytest is part of the optional-dependencies `[dev]` of the plugin package.
|
||||
|
||||
## Introduction
|
||||
|
||||
Tests in this package should be stored in the `tests` directory.
|
||||
We suggest to sort tests of different submodules, i.e. `scans` or `devices` in the respective folder structure, and to folow a naming convention of `<test_module_name.py>`.
|
||||
It is mandatory for test files to begin with `test_` for pytest to discover them.
|
||||
|
||||
To run all tests, navigate to the directory of the plugin from the command line, and run the command
|
||||
To run all tests, navigate to the directory of the plugin from the command line, and run the command
|
||||
|
||||
``` bash
|
||||
```bash
|
||||
pytest -v --random-order ./tests
|
||||
```
|
||||
|
||||
Note, the python environment needs to be active.
|
||||
The additional arg `-v` allows pytest to run in verbose mode which provides more detailed information about the tests being run.
|
||||
The argument `--random-order` instructs pytest to run the tests in random order, which is the default in the CI pipelines.
|
||||
The argument `--random-order` instructs pytest to run the tests in random order, which is the default in the CI pipelines.
|
||||
|
||||
## Test examples
|
||||
|
||||
Writing tests can be quite specific for the given function.
|
||||
Writing tests can be quite specific for the given function.
|
||||
We recommend writing tests as isolated as possible, i.e. try to test single functions instead of full classes.
|
||||
A very useful class to enable isolated testing is [MagicMock](https://docs.python.org/3/library/unittest.mock.html).
|
||||
In addition, we also recommend to take a look at the [How-to guides from pytest](https://docs.pytest.org/en/8.0.x/how-to/index.html).
|
||||
|
||||
|
||||
70
tests/tests_devices/test_cameras.py
Normal file
70
tests/tests_devices/test_cameras.py
Normal file
@@ -0,0 +1,70 @@
|
||||
"""Module to test prosilica and Basler cam integrations."""
|
||||
|
||||
import threading
|
||||
from unittest import mock
|
||||
|
||||
import ophyd
|
||||
import pytest
|
||||
from ophyd_devices.devices.areadetector.cam import AravisDetectorCam, ProsilicaDetectorCam
|
||||
from ophyd_devices.devices.areadetector.plugins import ImagePlugin_V35
|
||||
from ophyd_devices.tests.utils import MockPV, patch_dual_pvs
|
||||
|
||||
from debye_bec.devices.cameras.basler_cam import BaslerCam
|
||||
from debye_bec.devices.cameras.prosilica_cam import ProsilicaCam
|
||||
|
||||
# pylint: disable=protected-access
|
||||
# pylint: disable=redefined-outer-name
|
||||
|
||||
|
||||
@pytest.fixture(scope="function")
|
||||
def mock_basler():
|
||||
"""Fixture to mock the camera device."""
|
||||
name = "cam"
|
||||
prefix = "test:"
|
||||
with mock.patch.object(ophyd, "cl") as mock_cl:
|
||||
mock_cl.get_pv = MockPV
|
||||
mock_cl.thread_class = threading.Thread
|
||||
dev = BaslerCam(name=name, prefix=prefix)
|
||||
patch_dual_pvs(dev)
|
||||
yield dev
|
||||
|
||||
|
||||
def test_basler_init(mock_basler):
|
||||
"""Test the initialization of the Basler camera device."""
|
||||
assert mock_basler.name == "cam"
|
||||
assert mock_basler.prefix == "test:"
|
||||
assert isinstance(mock_basler.cam1, AravisDetectorCam)
|
||||
assert isinstance(mock_basler.image1, ImagePlugin_V35)
|
||||
assert mock_basler._update_frequency == 1
|
||||
assert mock_basler._live_mode is False
|
||||
assert mock_basler._live_mode_event is None
|
||||
assert mock_basler._task_status is None
|
||||
assert mock_basler.preview.ndim == 2
|
||||
assert mock_basler.preview.num_rotation_90 == 3
|
||||
|
||||
|
||||
@pytest.fixture(scope="function")
|
||||
def mock_prosilica():
|
||||
"""Fixture to mock the camera device."""
|
||||
name = "cam"
|
||||
prefix = "test:"
|
||||
with mock.patch.object(ophyd, "cl") as mock_cl:
|
||||
mock_cl.get_pv = MockPV
|
||||
mock_cl.thread_class = threading.Thread
|
||||
dev = ProsilicaCam(name=name, prefix=prefix)
|
||||
patch_dual_pvs(dev)
|
||||
yield dev
|
||||
|
||||
|
||||
def test_prosilica_init(mock_prosilica):
|
||||
"""Test the initialization of the Prosilica camera device."""
|
||||
assert mock_prosilica.name == "cam"
|
||||
assert mock_prosilica.prefix == "test:"
|
||||
assert isinstance(mock_prosilica.cam1, ProsilicaDetectorCam)
|
||||
assert isinstance(mock_prosilica.image1, ImagePlugin_V35)
|
||||
assert mock_prosilica._update_frequency == 1
|
||||
assert mock_prosilica._live_mode is False
|
||||
assert mock_prosilica._live_mode_event is None
|
||||
assert mock_prosilica._task_status is None
|
||||
assert mock_prosilica.preview.ndim == 2
|
||||
assert mock_prosilica.preview.num_rotation_90 == 3
|
||||
86
tests/tests_devices/test_debye_base_cam.py
Normal file
86
tests/tests_devices/test_debye_base_cam.py
Normal file
@@ -0,0 +1,86 @@
|
||||
"""Module to test camera base integration class for Debye."""
|
||||
|
||||
import threading
|
||||
from unittest import mock
|
||||
|
||||
import ophyd
|
||||
import pytest
|
||||
from ophyd_devices.tests.utils import MockPV, patch_dual_pvs
|
||||
|
||||
from debye_bec.devices.cameras.debye_base_cam import DebyeBaseCamera
|
||||
|
||||
# pylint: disable=protected-access
|
||||
# pylint: disable=redefined-outer-name
|
||||
|
||||
|
||||
@pytest.fixture(scope="function")
|
||||
def mock_cam():
|
||||
"""Fixture to mock the camera device."""
|
||||
name = "cam"
|
||||
prefix = "test:"
|
||||
with mock.patch.object(ophyd, "cl") as mock_cl:
|
||||
mock_cl.get_pv = MockPV
|
||||
mock_cl.thread_class = threading.Thread
|
||||
dev = DebyeBaseCamera(name=name, prefix=prefix)
|
||||
patch_dual_pvs(dev)
|
||||
yield dev
|
||||
|
||||
|
||||
def test_init(mock_cam):
|
||||
"""Test the initialization of the camera device."""
|
||||
assert mock_cam.name == "cam"
|
||||
assert mock_cam.prefix == "test:"
|
||||
assert mock_cam._update_frequency == 1
|
||||
assert mock_cam._live_mode is False
|
||||
assert mock_cam._live_mode_event is None
|
||||
assert mock_cam._task_status is None
|
||||
assert mock_cam.preview.ndim == 2
|
||||
assert mock_cam.preview.num_rotation_90 == -1
|
||||
|
||||
|
||||
def test_start_live_mode(mock_cam):
|
||||
"""Test starting live mode."""
|
||||
|
||||
def mock_emit_to_bec(*args, **kwargs):
|
||||
"""Mock emit_to_bec method."""
|
||||
while not mock_cam._live_mode_event.wait(1 / mock_cam._update_frequency):
|
||||
pass
|
||||
|
||||
with mock.patch.object(mock_cam, "emit_to_bec", side_effect=mock_emit_to_bec):
|
||||
mock_cam._start_live_mode()
|
||||
assert mock_cam._live_mode_event is not None
|
||||
assert mock_cam._task_status is not None
|
||||
assert mock_cam._task_status.state == "running"
|
||||
mock_cam._live_mode_event.set()
|
||||
# Wait for the task to resolve
|
||||
mock_cam._task_status.wait(timeout=5)
|
||||
assert mock_cam._task_status.done is True
|
||||
|
||||
|
||||
def test_stop_live_mode(mock_cam):
|
||||
"""Test stopping live mode."""
|
||||
with mock.patch.object(mock_cam, "_live_mode_event") as mock_live_mode_event:
|
||||
mock_cam._stop_live_mode()
|
||||
assert mock_live_mode_event.set.called
|
||||
assert mock_cam._live_mode_event is None
|
||||
|
||||
|
||||
def test_live_mode_property(mock_cam):
|
||||
"""Test the live_mode property."""
|
||||
assert mock_cam.live_mode is False
|
||||
with mock.patch.object(mock_cam, "_start_live_mode") as mock_start_live_mode:
|
||||
with mock.patch.object(mock_cam, "_stop_live_mode") as mock_stop_live_mode:
|
||||
# Set to true
|
||||
mock_cam.live_mode = True
|
||||
assert mock_start_live_mode.called
|
||||
assert mock_cam._live_mode is True
|
||||
assert mock_start_live_mode.call_count == 1
|
||||
# Second call should call _start_live_mode
|
||||
mock_cam.live_mode = True
|
||||
assert mock_start_live_mode.call_count == 1
|
||||
|
||||
# Set to false
|
||||
mock_cam.live_mode = False
|
||||
assert mock_stop_live_mode.called
|
||||
assert mock_cam._live_mode is False
|
||||
assert mock_stop_live_mode.call_count == 1
|
||||
@@ -52,7 +52,6 @@ def test_init(mock_bragg):
|
||||
dev = mock_bragg
|
||||
assert dev.name == "bragg"
|
||||
assert dev.prefix == "X01DA-OP-MO1:BRAGG:"
|
||||
assert dev.move_type.get() == MoveType.ENERGY
|
||||
assert dev.crystal.offset_si111._read_pvname == "X01DA-OP-MO1:BRAGG:offset_si111_RBV"
|
||||
assert dev.move_abs._read_pvname == "X01DA-OP-MO1:BRAGG:move_abs"
|
||||
|
||||
@@ -61,29 +60,15 @@ def test_check_value(mock_bragg):
|
||||
dev = mock_bragg
|
||||
dev.low_lim._read_pv.mock_data = 0
|
||||
dev.high_lim._read_pv.mock_data = 1
|
||||
dev.low_limit_angle._read_pv.mock_data = 10
|
||||
dev.high_limit_angle._read_pv.mock_data = 20
|
||||
# Check that limits are taken correctly from angle or energy
|
||||
# Energy first
|
||||
move_type = MoveType.ENERGY
|
||||
dev.move_type.set(move_type)
|
||||
# nothing happens
|
||||
dev.check_value(0.5)
|
||||
with pytest.raises(LimitError):
|
||||
dev.check_value(15)
|
||||
# Angle next
|
||||
move_type = MoveType.ANGLE
|
||||
dev.move_type.set(move_type)
|
||||
dev.check_value(15)
|
||||
with pytest.raises(LimitError):
|
||||
dev.check_value(0.5)
|
||||
|
||||
|
||||
def test_egu(mock_bragg):
|
||||
dev = mock_bragg
|
||||
assert dev.egu == "eV"
|
||||
dev.move_type.set(MoveType.ANGLE)
|
||||
assert dev.egu == "deg"
|
||||
|
||||
|
||||
def test_move_succeeds(mock_bragg):
|
||||
@@ -135,16 +120,10 @@ def test_set_xtal(mock_bragg):
|
||||
|
||||
def test_set_xas_settings(mock_bragg):
|
||||
dev = mock_bragg
|
||||
dev.move_type.set(MoveType.ENERGY)
|
||||
dev.set_xas_settings(low=0.5, high=1, scan_time=0.1)
|
||||
assert dev.scan_settings.s_scan_energy_lo.get() == 0.5
|
||||
assert dev.scan_settings.s_scan_energy_hi.get() == 1
|
||||
assert dev.scan_settings.s_scan_scantime.get() == 0.1
|
||||
dev.move_type.set(MoveType.ANGLE)
|
||||
dev.set_xas_settings(low=10, high=20, scan_time=1)
|
||||
assert dev.scan_settings.s_scan_angle_lo.get() == 10
|
||||
assert dev.scan_settings.s_scan_angle_hi.get() == 20
|
||||
assert dev.scan_settings.s_scan_scantime.get() == 1
|
||||
|
||||
|
||||
def test_set_trig_settings(mock_bragg):
|
||||
@@ -181,6 +160,7 @@ def test_update_scan_parameters(mock_bragg):
|
||||
scan_id="my_scan_id",
|
||||
status="closed",
|
||||
request_inputs={
|
||||
"inputs": {},
|
||||
"kwargs": {
|
||||
"start": 0,
|
||||
"stop": 5,
|
||||
@@ -196,7 +176,7 @@ def test_update_scan_parameters(mock_bragg):
|
||||
"cycle_high": 5,
|
||||
"p_kink": 50,
|
||||
"e_kink": 8000,
|
||||
}
|
||||
},
|
||||
},
|
||||
info={
|
||||
"kwargs": {
|
||||
@@ -236,9 +216,14 @@ def test_kickoff_scan(mock_bragg):
|
||||
dev.scan_control.scan_start_infinite._read_pv.mock_data = 0
|
||||
status = dev.kickoff()
|
||||
assert status.done is False
|
||||
dev.scan_control.scan_status._read_pv.mock_data = ScanControlScanStatus.RUNNING
|
||||
time.sleep(0.2)
|
||||
assert status.done is True
|
||||
# TODO MockPV does not support callbacks yet, so we need to improve here #16
|
||||
# dev.scan_control.scan_status._read_pv.mock_data = ScanControlScanStatus.RUNNING
|
||||
# dev.scan_control.scan_status._read_pv.
|
||||
# status.wait(timeout=3) # Callback should resolve now
|
||||
# assert status.done is True
|
||||
# # dev.scan_control.scan_status._read_pv.mock_data = ScanControlScanStatus.RUNNING
|
||||
# time.sleep(0.2)
|
||||
# assert status.done is True
|
||||
assert dev.scan_control.scan_start_timer.get() == 1
|
||||
|
||||
dev.scan_control.scan_duration._read_pv.mock_data = 0
|
||||
@@ -250,42 +235,44 @@ def test_kickoff_scan(mock_bragg):
|
||||
assert dev.scan_control.scan_start_infinite.get() == 1
|
||||
|
||||
|
||||
def test_complete(mock_bragg):
|
||||
dev = mock_bragg
|
||||
dev.scan_control.scan_done._read_pv.mock_data = 0
|
||||
# Normal case
|
||||
status = dev.complete()
|
||||
assert status.done is False
|
||||
assert status.success is False
|
||||
dev.scan_control.scan_done._read_pv.mock_data = 1
|
||||
status.wait()
|
||||
# time.sleep(0.2)
|
||||
assert status.done is True
|
||||
assert status.success is True
|
||||
# FIXME #22 once mock_pv supports callbacks, high priority!
|
||||
# def test_complete(mock_bragg):
|
||||
# dev = mock_bragg
|
||||
# dev.scan_control.scan_done._read_pv.mock_data = 0
|
||||
# # Normal case
|
||||
# status = dev.complete()
|
||||
# assert status.done is False
|
||||
# assert status.success is False
|
||||
# dev.scan_control.scan_done._read_pv.mock_data = 1
|
||||
# status.wait()
|
||||
# # time.sleep(0.2)
|
||||
# assert status.done is True
|
||||
# assert status.success is True
|
||||
|
||||
# Stop called case
|
||||
dev.scan_control.scan_done._read_pv.mock_data = 0
|
||||
status = dev.complete()
|
||||
assert status.done is False
|
||||
assert status.success is False
|
||||
dev.stop()
|
||||
time.sleep(0.2)
|
||||
assert status.done is True
|
||||
assert status.success is False
|
||||
# # Stop called case
|
||||
# dev.scan_control.scan_done._read_pv.mock_data = 0
|
||||
# status = dev.complete()
|
||||
# assert status.done is False
|
||||
# assert status.success is False
|
||||
# dev.stop()
|
||||
# time.sleep(0.2)
|
||||
# assert status.done is True
|
||||
# assert status.success is False
|
||||
|
||||
|
||||
def test_unstage(mock_bragg):
|
||||
mock_bragg.timeout_for_pvwait = 0.5
|
||||
mock_bragg.scan_control.scan_val_reset._read_pv.mock_data = 0
|
||||
mock_bragg.scan_control.scan_msg._read_pv.mock_data = ScanControlLoadMessage.PENDING
|
||||
# FIXME #22 once mock_pv supports callbacks, high priority!
|
||||
# def test_unstage(mock_bragg):
|
||||
# mock_bragg.timeout_for_pvwait = 0.5
|
||||
# mock_bragg.scan_control.scan_val_reset._read_pv.mock_data = 0
|
||||
# mock_bragg.scan_control.scan_msg._read_pv.mock_data = ScanControlLoadMessage.PENDING
|
||||
|
||||
with mock.patch.object(mock_bragg.scan_control.scan_val_reset, "put") as mock_put:
|
||||
status = mock_bragg.unstage()
|
||||
assert mock_put.call_count == 0
|
||||
mock_bragg.scan_control.scan_msg._read_pv.mock_data = ScanControlLoadMessage.SUCCESS
|
||||
with pytest.raises(TimeoutError):
|
||||
mock_bragg.unstage()
|
||||
assert mock_put.call_count == 1
|
||||
# with mock.patch.object(mock_bragg.scan_control.scan_val_reset, "put") as mock_put:
|
||||
# status = mock_bragg.unstage()
|
||||
# assert mock_put.call_count == 0
|
||||
# mock_bragg.scan_control.scan_msg._read_pv.mock_data = ScanControlLoadMessage.SUCCESS
|
||||
# with pytest.raises(TimeoutError):
|
||||
# mock_bragg.unstage()
|
||||
# assert mock_put.call_count == 1
|
||||
|
||||
|
||||
# TODO reimplement the test for stage method
|
||||
@@ -441,138 +428,6 @@ def test_unstage(mock_bragg):
|
||||
# )
|
||||
# mock_bragg.scan_info.msg = scan_status_msg
|
||||
|
||||
<<<<<<< Updated upstream
|
||||
# Ensure that ScanControlLoadMessage is set to SUCCESS
|
||||
mock_bragg.scan_control.scan_msg._read_pv.mock_data = ScanControlLoadMessage.SUCCESS
|
||||
with (
|
||||
mock.patch.object(mock_bragg.scaninfo, "load_scan_metadata") as mock_load_scan_metadata,
|
||||
mock.patch.object(mock_bragg, "_check_scan_msg") as mock_check_scan_msg,
|
||||
mock.patch.object(mock_bragg, "on_unstage"),
|
||||
):
|
||||
scan_name = scan_status_msg.content["info"].get("scan_name", "")
|
||||
# Chek the not implemented fly scan first, should raise Mo1BraggError
|
||||
if scan_name not in [
|
||||
"xas_simple_scan",
|
||||
"xas_simple_scan_with_xrd",
|
||||
"xas_advanced_scan",
|
||||
"xas_advanced_scan_with_xrd",
|
||||
]:
|
||||
with pytest.raises(Mo1BraggError):
|
||||
mock_bragg.stage()
|
||||
assert mock_check_scan_msg.call_count == 1
|
||||
assert mock_load_scan_metadata.call_count == 1
|
||||
else:
|
||||
with (
|
||||
mock.patch.object(mock_bragg, "set_xas_settings") as mock_xas_settings,
|
||||
mock.patch.object(
|
||||
mock_bragg, "set_advanced_xas_settings"
|
||||
) as mock_advanced_xas_settings,
|
||||
mock.patch.object(mock_bragg, "set_trig_settings") as mock_trig_settings,
|
||||
mock.patch.object(
|
||||
mock_bragg, "set_scan_control_settings"
|
||||
) as mock_set_scan_control_settings,
|
||||
):
|
||||
# Check xas_simple_scan
|
||||
if scan_name == "xas_simple_scan":
|
||||
mock_bragg.stage()
|
||||
assert mock_xas_settings.call_args == mock.call(
|
||||
low=scan_status_msg.content["info"]["kwargs"]["start"],
|
||||
high=scan_status_msg.content["info"]["kwargs"]["stop"],
|
||||
scan_time=scan_status_msg.content["info"]["kwargs"]["scan_time"],
|
||||
)
|
||||
assert mock_trig_settings.call_args == mock.call(
|
||||
enable_low=False,
|
||||
enable_high=False,
|
||||
exp_time_low=0,
|
||||
exp_time_high=0,
|
||||
cycle_low=0,
|
||||
cycle_high=0,
|
||||
)
|
||||
assert mock_set_scan_control_settings.call_args == mock.call(
|
||||
mode=ScanControlMode.SIMPLE,
|
||||
scan_duration=scan_status_msg.content["info"]["kwargs"][
|
||||
"scan_duration"
|
||||
],
|
||||
)
|
||||
# Check xas_simple_scan_with_xrd
|
||||
elif scan_name == "xas_simple_scan_with_xrd":
|
||||
mock_bragg.stage()
|
||||
assert mock_xas_settings.call_args == mock.call(
|
||||
low=scan_status_msg.content["info"]["kwargs"]["start"],
|
||||
high=scan_status_msg.content["info"]["kwargs"]["stop"],
|
||||
scan_time=scan_status_msg.content["info"]["kwargs"]["scan_time"],
|
||||
)
|
||||
assert mock_trig_settings.call_args == mock.call(
|
||||
enable_low=scan_status_msg.content["info"]["kwargs"]["xrd_enable_low"],
|
||||
enable_high=scan_status_msg.content["info"]["kwargs"][
|
||||
"xrd_enable_high"
|
||||
],
|
||||
exp_time_low=scan_status_msg.content["info"]["kwargs"]["exp_time_low"],
|
||||
exp_time_high=scan_status_msg.content["info"]["kwargs"][
|
||||
"exp_time_high"
|
||||
],
|
||||
cycle_low=scan_status_msg.content["info"]["kwargs"]["cycle_low"],
|
||||
cycle_high=scan_status_msg.content["info"]["kwargs"]["cycle_high"],
|
||||
)
|
||||
assert mock_set_scan_control_settings.call_args == mock.call(
|
||||
mode=ScanControlMode.SIMPLE,
|
||||
scan_duration=scan_status_msg.content["info"]["kwargs"][
|
||||
"scan_duration"
|
||||
],
|
||||
)
|
||||
# Check xas_advanced_scan
|
||||
elif scan_name == "xas_advanced_scan":
|
||||
mock_bragg.stage()
|
||||
assert mock_advanced_xas_settings.call_args == mock.call(
|
||||
low=scan_status_msg.content["info"]["kwargs"]["start"],
|
||||
high=scan_status_msg.content["info"]["kwargs"]["stop"],
|
||||
scan_time=scan_status_msg.content["info"]["kwargs"]["scan_time"],
|
||||
p_kink=scan_status_msg.content["info"]["kwargs"]["p_kink"],
|
||||
e_kink=scan_status_msg.content["info"]["kwargs"]["e_kink"],
|
||||
)
|
||||
assert mock_trig_settings.call_args == mock.call(
|
||||
enable_low=False,
|
||||
enable_high=False,
|
||||
exp_time_low=0,
|
||||
exp_time_high=0,
|
||||
cycle_low=0,
|
||||
cycle_high=0,
|
||||
)
|
||||
assert mock_set_scan_control_settings.call_args == mock.call(
|
||||
mode=ScanControlMode.ADVANCED,
|
||||
scan_duration=scan_status_msg.content["info"]["kwargs"][
|
||||
"scan_duration"
|
||||
],
|
||||
)
|
||||
# Check xas_advanced_scan_with_xrd
|
||||
elif scan_name == "xas_advanced_scan_with_xrd":
|
||||
mock_bragg.stage()
|
||||
assert mock_advanced_xas_settings.call_args == mock.call(
|
||||
low=scan_status_msg.content["info"]["kwargs"]["start"],
|
||||
high=scan_status_msg.content["info"]["kwargs"]["stop"],
|
||||
scan_time=scan_status_msg.content["info"]["kwargs"]["scan_time"],
|
||||
p_kink=scan_status_msg.content["info"]["kwargs"]["p_kink"],
|
||||
e_kink=scan_status_msg.content["info"]["kwargs"]["e_kink"],
|
||||
)
|
||||
assert mock_trig_settings.call_args == mock.call(
|
||||
enable_low=scan_status_msg.content["info"]["kwargs"]["xrd_enable_low"],
|
||||
enable_high=scan_status_msg.content["info"]["kwargs"][
|
||||
"xrd_enable_high"
|
||||
],
|
||||
exp_time_low=scan_status_msg.content["info"]["kwargs"]["exp_time_low"],
|
||||
exp_time_high=scan_status_msg.content["info"]["kwargs"][
|
||||
"exp_time_high"
|
||||
],
|
||||
cycle_low=scan_status_msg.content["info"]["kwargs"]["cycle_low"],
|
||||
cycle_high=scan_status_msg.content["info"]["kwargs"]["cycle_high"],
|
||||
)
|
||||
assert mock_set_scan_control_settings.call_args == mock.call(
|
||||
mode=ScanControlMode.ADVANCED,
|
||||
scan_duration=scan_status_msg.content["info"]["kwargs"][
|
||||
"scan_duration"
|
||||
],
|
||||
)
|
||||
=======
|
||||
# # Ensure that ScanControlLoadMessage is set to SUCCESS
|
||||
# mock_bragg.scan_control.scan_msg._read_pv.mock_data = ScanControlLoadMessage.SUCCESS
|
||||
# with (
|
||||
@@ -701,4 +556,3 @@ def test_unstage(mock_bragg):
|
||||
# "scan_duration"
|
||||
# ],
|
||||
# )
|
||||
>>>>>>> Stashed changes
|
||||
|
||||
88
tests/tests_devices/test_mo1_bragg_angle.py
Normal file
88
tests/tests_devices/test_mo1_bragg_angle.py
Normal file
@@ -0,0 +1,88 @@
|
||||
"""Tests for the Mo1BraggAngle class."""
|
||||
|
||||
import threading
|
||||
from unittest import mock
|
||||
|
||||
import ophyd
|
||||
import pytest
|
||||
from ophyd_devices.tests.utils import MockPV, patch_dual_pvs
|
||||
|
||||
from debye_bec.devices.mo1_bragg.mo1_bragg_angle import Mo1BraggAngle
|
||||
from debye_bec.devices.mo1_bragg.mo1_bragg_devices import Mo1BraggStoppedError
|
||||
|
||||
# pylint: disable=protected-access
|
||||
|
||||
|
||||
@pytest.fixture(scope="function")
|
||||
def mock_bragg() -> Mo1BraggAngle:
|
||||
"""Fixture for the Mo1BraggAngle device."""
|
||||
name = "bragg"
|
||||
prefix = "X01DA-OP-MO1:BRAGG:"
|
||||
with mock.patch.object(ophyd, "cl") as mock_cl:
|
||||
mock_cl.get_pv = MockPV
|
||||
mock_cl.thread_class = threading.Thread
|
||||
dev = Mo1BraggAngle(name=name, prefix=prefix)
|
||||
patch_dual_pvs(dev)
|
||||
yield dev
|
||||
|
||||
|
||||
def test_mo1_bragg_angle_init(mock_bragg):
|
||||
"""Test the initialization of the Mo1BraggAngle device."""
|
||||
assert mock_bragg.name == "bragg"
|
||||
assert mock_bragg.prefix == "X01DA-OP-MO1:BRAGG:"
|
||||
assert isinstance(mock_bragg.readback, ophyd.EpicsSignalRO)
|
||||
assert isinstance(mock_bragg.setpoint, ophyd.EpicsSignalWithRBV)
|
||||
assert isinstance(mock_bragg.low_lim, ophyd.EpicsSignalRO)
|
||||
assert isinstance(mock_bragg.high_lim, ophyd.EpicsSignalRO)
|
||||
|
||||
|
||||
def test_mo1_bragg_angle_egu(mock_bragg):
|
||||
"""Test the engineering unit of the Mo1BraggAngle device."""
|
||||
assert mock_bragg.egu == "deg"
|
||||
|
||||
|
||||
def test_mo1_bragg_angle_limits(mock_bragg):
|
||||
"""Test the limits of the Mo1BraggAngle device."""
|
||||
mock_bragg.low_lim._read_pv.mock_data = -10
|
||||
mock_bragg.high_lim._read_pv.mock_data = 10
|
||||
|
||||
assert mock_bragg.limits == (-10, 10)
|
||||
|
||||
|
||||
def test_mo1_bragg_angle_move(mock_bragg):
|
||||
"""Test the move method of the Mo1BraggAngle device."""
|
||||
mock_bragg.setpoint.put(0)
|
||||
mock_bragg.readback._read_pv.mock_data = 0 # EpicsSignalRO
|
||||
|
||||
# Change PV for motor is moving before starting the move
|
||||
mock_bragg.motor_is_moving._read_pv.mock_data = 0 # EpicsSignalRO
|
||||
status = mock_bragg.move(5)
|
||||
assert status.done is False
|
||||
|
||||
# Check setpoint is set correctly
|
||||
assert mock_bragg.setpoint.get() == 5
|
||||
|
||||
# Update the motor is moving PV to simulate that the move is done
|
||||
mock_bragg.motor_is_moving._read_pv.mock_data = 1
|
||||
assert mock_bragg.motor_is_moving.get() == 1
|
||||
|
||||
status.wait(timeout=5) # If the status does not resolve after 5 seconds, something is wrong
|
||||
assert status.done is True
|
||||
|
||||
|
||||
def test_mo1_bragg_angle_stop(mock_bragg):
|
||||
"""Test the stop method of the Mo1BraggAngle device."""
|
||||
assert mock_bragg.stopped is False
|
||||
|
||||
mock_bragg.stop()
|
||||
assert mock_bragg.stopped is True
|
||||
|
||||
status = mock_bragg.move(5)
|
||||
assert status.done is False
|
||||
|
||||
# stopped should be resetted
|
||||
assert mock_bragg.stopped is False
|
||||
|
||||
with pytest.raises(Mo1BraggStoppedError):
|
||||
mock_bragg.stop()
|
||||
status.wait(timeout=5) # This should raise before due to stop() call
|
||||
166
tests/tests_devices/test_nidaq.py
Normal file
166
tests/tests_devices/test_nidaq.py
Normal file
@@ -0,0 +1,166 @@
|
||||
# pylint: skip-file
|
||||
import threading
|
||||
from typing import Generator
|
||||
from unittest import mock
|
||||
|
||||
import ophyd
|
||||
import pytest
|
||||
from bec_server.scan_server.scan_worker import ScanWorker
|
||||
from ophyd.status import WaitTimeoutError
|
||||
from ophyd_devices.interfaces.base_classes.psi_device_base import DeviceStoppedError
|
||||
from ophyd_devices.tests.utils import MockPV
|
||||
|
||||
# from bec_server.device_server.tests.utils import DMMock
|
||||
from debye_bec.devices.nidaq.nidaq import Nidaq, NidaqError
|
||||
|
||||
# TODO move this function to ophyd_devices, it is duplicated in csaxs_bec and needed for other pluging repositories
|
||||
from debye_bec.devices.test_utils.utils import patch_dual_pvs
|
||||
|
||||
|
||||
@pytest.fixture(scope="function")
|
||||
def scan_worker_mock(scan_server_mock):
|
||||
"""Scan worker fixture, utility to generate scan_info for a given scan name."""
|
||||
scan_server_mock.device_manager.connector = mock.MagicMock()
|
||||
scan_worker = ScanWorker(parent=scan_server_mock)
|
||||
yield scan_worker
|
||||
|
||||
|
||||
@pytest.fixture(scope="function")
|
||||
def mock_nidaq() -> Generator[Nidaq, None, None]:
|
||||
"""Fixture for the Nidaq device."""
|
||||
name = "nidaq"
|
||||
prefix = "nidaq:prefix_test:"
|
||||
with mock.patch.object(ophyd, "cl") as mock_cl:
|
||||
mock_cl.get_pv = MockPV
|
||||
mock_cl.thread_class = threading.Thread
|
||||
dev = Nidaq(name=name, prefix=prefix)
|
||||
patch_dual_pvs(dev)
|
||||
yield dev
|
||||
|
||||
|
||||
def test_init(mock_nidaq):
|
||||
"""Test the initialization of the Nidaq device."""
|
||||
dev = mock_nidaq
|
||||
assert dev.name == "nidaq"
|
||||
assert dev.prefix == "nidaq:prefix_test:"
|
||||
assert dev.valid_scan_names == [
|
||||
"xas_simple_scan",
|
||||
"xas_simple_scan_with_xrd",
|
||||
"xas_advanced_scan",
|
||||
"xas_advanced_scan_with_xrd",
|
||||
"nidaq_continuous_scan",
|
||||
]
|
||||
|
||||
|
||||
def test_check_if_scan_name_is_valid(mock_nidaq):
|
||||
"""Test the check_if_scan_name_is_valid method."""
|
||||
dev = mock_nidaq
|
||||
dev.scan_info.msg.scan_name = "xas_simple_scan"
|
||||
assert dev._check_if_scan_name_is_valid()
|
||||
dev.scan_info.msg.scan_name = "invalid_scan_name"
|
||||
assert not dev._check_if_scan_name_is_valid()
|
||||
|
||||
|
||||
def test_set_config(mock_nidaq):
|
||||
dev = mock_nidaq
|
||||
# TODO #21 Add test logic for set_config, issue created #
|
||||
|
||||
|
||||
def test_on_connected(mock_nidaq):
|
||||
"""Test the on_connected method of the Nidaq device."""
|
||||
dev = mock_nidaq
|
||||
dev.power.put(0)
|
||||
dev.heartbeat._read_pv.mock_data = 0
|
||||
# First scenario, raise timeout error
|
||||
|
||||
# This will raise a WaitTimeoutError error as we currently do not support callbacks in the MockPV
|
||||
dev.timeout_wait_for_signal = 0.1
|
||||
# To check that it raised, we check that dev.power PV is set to 1
|
||||
# Set state PV to 0, 1 is expected value
|
||||
dev.state._read_pv.mock_data = 0
|
||||
with pytest.raises(WaitTimeoutError):
|
||||
dev.on_connected()
|
||||
assert dev.power.get() == 1
|
||||
# TODO, once the MOCKPv supports callbacks, we can test the rest of the logic issue #22
|
||||
|
||||
|
||||
# def test_on_stage(mock_nidaq):
|
||||
# dev = mock_nidaq
|
||||
# #TODO Add once MockPV supports callbacks #22
|
||||
|
||||
|
||||
def test_on_kickoff(mock_nidaq):
|
||||
"""Test the on_kickoff method of the Nidaq device."""
|
||||
dev = mock_nidaq
|
||||
dev.kickoff_call.put(0)
|
||||
dev.kickoff()
|
||||
assert dev.kickoff_call.get() == 1
|
||||
|
||||
|
||||
def test_on_unstage(mock_nidaq):
|
||||
"""Test the on_unstage method of the Nidaq device."""
|
||||
dev = mock_nidaq
|
||||
dev.state._read_pv.mock_data = 0 # Set state to 0, 1 is Standby
|
||||
dev._timeout_wait_for_pv = 0.1 # Set a short timeout for testing
|
||||
dev.enable_compression._read_pv.mock_data = 0 # Compression enabled
|
||||
with pytest.raises(WaitTimeoutError):
|
||||
dev.on_unstage()
|
||||
dev.state._read_pv.mock_data = 1
|
||||
# FIXME #22 add callback mechanism to MockPV to test the rest of the logic
|
||||
# dev.on_unstage()
|
||||
# assert dev.enable_compression.get() == 1
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
["scan_name", "raise_error", "nidaq_state"],
|
||||
[
|
||||
("line_scan", False, 0),
|
||||
("xas_simple_scan", False, 3),
|
||||
("xas_simple_scan", True, 0),
|
||||
("nidaq_continuous_scan", False, 0),
|
||||
],
|
||||
)
|
||||
def test_on_pre_scan(mock_nidaq, scan_name, raise_error, nidaq_state):
|
||||
"""Test the on_pre_scan method of the Nidaq device."""
|
||||
dev = mock_nidaq
|
||||
dev.state.put(nidaq_state)
|
||||
dev.scan_info.msg.scan_name = scan_name
|
||||
dev._timeout_wait_for_pv = 0.1 # Set a short timeout for testing
|
||||
if not raise_error:
|
||||
dev.pre_scan()
|
||||
else:
|
||||
with pytest.raises(WaitTimeoutError):
|
||||
dev.pre_scan()
|
||||
|
||||
|
||||
def test_on_complete(mock_nidaq):
|
||||
"""Test the on_complete method of the Nidaq device."""
|
||||
dev = mock_nidaq
|
||||
# Check for nidaq_continuous_scan
|
||||
dev.scan_info.msg.scan_name = "nidaq_continuous_scan"
|
||||
dev.state.put(0) # Set state to DISABLED
|
||||
status = dev.complete()
|
||||
assert status.done is False
|
||||
dev.state.put(1)
|
||||
# Should resolve now
|
||||
status.wait(timeout=5) # Wait for the status to complete
|
||||
assert status.done is True
|
||||
|
||||
# Check for XAS simple scan
|
||||
dev.scan_info.msg.scan_name = "xas_simple_scan"
|
||||
dev.state.put(0) # Set state to ACQUIRE
|
||||
dev.stop_call.put(0)
|
||||
dev._timeout_wait_for_pv = 5
|
||||
status = dev.on_complete()
|
||||
assert status.done is False
|
||||
assert dev.stop_call.get() == 1 # Should have called stop
|
||||
dev.state.put(1) # Set state to STANDBY
|
||||
# Should resolve now
|
||||
status.wait(timeout=5) # Wait for the status to complete
|
||||
assert status.done is True
|
||||
|
||||
# Test that it resolves if device is stopped
|
||||
dev.state.put(0) # Set state to DISABLED
|
||||
dev.stop()
|
||||
status.wait(timeout=5)
|
||||
assert status.done is True
|
||||
@@ -1,31 +1,34 @@
|
||||
# Getting Started with Testing using pytest
|
||||
|
||||
BEC is using the [pytest](https://docs.pytest.org/en/8.0.x/) framework.
|
||||
It can be install via
|
||||
``` bash
|
||||
BEC is using the [pytest](https://docs.pytest.org/en/latest/) framework.
|
||||
It can be installed via
|
||||
|
||||
```bash
|
||||
pip install pytest
|
||||
```
|
||||
in your *python environment*.
|
||||
|
||||
in your _python environment_.
|
||||
We note that pytest is part of the optional-dependencies `[dev]` of the plugin package.
|
||||
|
||||
## Introduction
|
||||
|
||||
Tests in this package should be stored in the `tests` directory.
|
||||
We suggest to sort tests of different submodules, i.e. `scans` or `devices` in the respective folder structure, and to folow a naming convention of `<test_module_name.py>`.
|
||||
It is mandatory for test files to begin with `test_` for pytest to discover them.
|
||||
|
||||
To run all tests, navigate to the directory of the plugin from the command line, and run the command
|
||||
To run all tests, navigate to the directory of the plugin from the command line, and run the command
|
||||
|
||||
``` bash
|
||||
```bash
|
||||
pytest -v --random-order ./tests
|
||||
```
|
||||
|
||||
Note, the python environment needs to be active.
|
||||
The additional arg `-v` allows pytest to run in verbose mode which provides more detailed information about the tests being run.
|
||||
The argument `--random-order` instructs pytest to run the tests in random order, which is the default in the CI pipelines.
|
||||
The argument `--random-order` instructs pytest to run the tests in random order, which is the default in the CI pipelines.
|
||||
|
||||
## Test examples
|
||||
|
||||
Writing tests can be quite specific for the given function.
|
||||
Writing tests can be quite specific for the given function.
|
||||
We recommend writing tests as isolated as possible, i.e. try to test single functions instead of full classes.
|
||||
A very useful class to enable isolated testing is [MagicMock](https://docs.python.org/3/library/unittest.mock.html).
|
||||
In addition, we also recommend to take a look at the [How-to guides from pytest](https://docs.pytest.org/en/8.0.x/how-to/index.html).
|
||||
|
||||
|
||||
@@ -1,31 +1,34 @@
|
||||
# Getting Started with Testing using pytest
|
||||
|
||||
BEC is using the [pytest](https://docs.pytest.org/en/8.0.x/) framework.
|
||||
It can be install via
|
||||
``` bash
|
||||
BEC is using the [pytest](https://docs.pytest.org/en/latest/) framework.
|
||||
It can be installed via
|
||||
|
||||
```bash
|
||||
pip install pytest
|
||||
```
|
||||
in your *python environment*.
|
||||
|
||||
in your _python environment_.
|
||||
We note that pytest is part of the optional-dependencies `[dev]` of the plugin package.
|
||||
|
||||
## Introduction
|
||||
|
||||
Tests in this package should be stored in the `tests` directory.
|
||||
We suggest to sort tests of different submodules, i.e. `scans` or `devices` in the respective folder structure, and to folow a naming convention of `<test_module_name.py>`.
|
||||
It is mandatory for test files to begin with `test_` for pytest to discover them.
|
||||
|
||||
To run all tests, navigate to the directory of the plugin from the command line, and run the command
|
||||
To run all tests, navigate to the directory of the plugin from the command line, and run the command
|
||||
|
||||
``` bash
|
||||
```bash
|
||||
pytest -v --random-order ./tests
|
||||
```
|
||||
|
||||
Note, the python environment needs to be active.
|
||||
The additional arg `-v` allows pytest to run in verbose mode which provides more detailed information about the tests being run.
|
||||
The argument `--random-order` instructs pytest to run the tests in random order, which is the default in the CI pipelines.
|
||||
The argument `--random-order` instructs pytest to run the tests in random order, which is the default in the CI pipelines.
|
||||
|
||||
## Test examples
|
||||
|
||||
Writing tests can be quite specific for the given function.
|
||||
Writing tests can be quite specific for the given function.
|
||||
We recommend writing tests as isolated as possible, i.e. try to test single functions instead of full classes.
|
||||
A very useful class to enable isolated testing is [MagicMock](https://docs.python.org/3/library/unittest.mock.html).
|
||||
In addition, we also recommend to take a look at the [How-to guides from pytest](https://docs.pytest.org/en/8.0.x/how-to/index.html).
|
||||
|
||||
|
||||
@@ -49,6 +49,7 @@ def get_instructions(request, ScanStubStatusMock):
|
||||
def test_xas_simple_scan(scan_assembler, ScanStubStatusMock):
|
||||
|
||||
request = scan_assembler(XASSimpleScan, start=0, stop=5, scan_time=1, scan_duration=10)
|
||||
request.device_manager.add_device("nidaq")
|
||||
reference_commands = get_instructions(request, ScanStubStatusMock)
|
||||
|
||||
assert reference_commands == [
|
||||
@@ -70,7 +71,7 @@ def test_xas_simple_scan(scan_assembler, ScanStubStatusMock):
|
||||
"monitored": [],
|
||||
"baseline": [],
|
||||
"on_request": [],
|
||||
"async": [],
|
||||
"async": ["nidaq"],
|
||||
},
|
||||
"num_points": None,
|
||||
"positions": [0.0, 5.0],
|
||||
@@ -78,6 +79,7 @@ def test_xas_simple_scan(scan_assembler, ScanStubStatusMock):
|
||||
"scan_type": "fly",
|
||||
},
|
||||
),
|
||||
DeviceInstructionMessage(metadata={}, device="nidaq", action="stage", parameter={}),
|
||||
DeviceInstructionMessage(
|
||||
metadata={},
|
||||
device=["bpm4i", "eiger", "mo1_bragg", "samx"],
|
||||
@@ -92,19 +94,7 @@ def test_xas_simple_scan(scan_assembler, ScanStubStatusMock):
|
||||
),
|
||||
DeviceInstructionMessage(
|
||||
metadata={"readout_priority": "monitored", "RID": "my_test_request_id"},
|
||||
device="mo1_bragg",
|
||||
action="rpc",
|
||||
parameter={
|
||||
"device": "mo1_bragg",
|
||||
"func": "move_type.set",
|
||||
"rpc_id": "my_test_rpc_id",
|
||||
"args": ("energy",),
|
||||
"kwargs": {},
|
||||
},
|
||||
),
|
||||
DeviceInstructionMessage(
|
||||
metadata={"readout_priority": "monitored", "RID": "my_test_request_id"},
|
||||
device=["bpm4i", "eiger", "mo1_bragg", "samx"],
|
||||
device=["bpm4i", "eiger", "mo1_bragg", "nidaq", "samx"],
|
||||
action="pre_scan",
|
||||
parameter={},
|
||||
),
|
||||
@@ -130,7 +120,7 @@ def test_xas_simple_scan(scan_assembler, ScanStubStatusMock):
|
||||
"fake_complete",
|
||||
DeviceInstructionMessage(
|
||||
metadata={},
|
||||
device=["bpm4i", "eiger", "mo1_bragg", "samx"],
|
||||
device=["bpm4i", "eiger", "mo1_bragg", "nidaq", "samx"],
|
||||
action="unstage",
|
||||
parameter={},
|
||||
),
|
||||
@@ -160,6 +150,7 @@ def test_xas_simple_scan_with_xrd(scan_assembler, ScanStubStatusMock):
|
||||
exp_time_high=3,
|
||||
cycle_high=4,
|
||||
)
|
||||
request.device_manager.add_device("nidaq")
|
||||
reference_commands = get_instructions(request, ScanStubStatusMock)
|
||||
|
||||
assert reference_commands == [
|
||||
@@ -181,7 +172,7 @@ def test_xas_simple_scan_with_xrd(scan_assembler, ScanStubStatusMock):
|
||||
"monitored": [],
|
||||
"baseline": [],
|
||||
"on_request": [],
|
||||
"async": [],
|
||||
"async": ["nidaq"],
|
||||
},
|
||||
"num_points": None,
|
||||
"positions": [0.0, 5.0],
|
||||
@@ -189,6 +180,7 @@ def test_xas_simple_scan_with_xrd(scan_assembler, ScanStubStatusMock):
|
||||
"scan_type": "fly",
|
||||
},
|
||||
),
|
||||
DeviceInstructionMessage(metadata={}, device="nidaq", action="stage", parameter={}),
|
||||
DeviceInstructionMessage(
|
||||
metadata={},
|
||||
device=["bpm4i", "eiger", "mo1_bragg", "samx"],
|
||||
@@ -203,19 +195,7 @@ def test_xas_simple_scan_with_xrd(scan_assembler, ScanStubStatusMock):
|
||||
),
|
||||
DeviceInstructionMessage(
|
||||
metadata={"readout_priority": "monitored", "RID": "my_test_request_id"},
|
||||
device="mo1_bragg",
|
||||
action="rpc",
|
||||
parameter={
|
||||
"device": "mo1_bragg",
|
||||
"func": "move_type.set",
|
||||
"rpc_id": "my_test_rpc_id",
|
||||
"args": ("energy",),
|
||||
"kwargs": {},
|
||||
},
|
||||
),
|
||||
DeviceInstructionMessage(
|
||||
metadata={"readout_priority": "monitored", "RID": "my_test_request_id"},
|
||||
device=["bpm4i", "eiger", "mo1_bragg", "samx"],
|
||||
device=["bpm4i", "eiger", "mo1_bragg", "nidaq", "samx"],
|
||||
action="pre_scan",
|
||||
parameter={},
|
||||
),
|
||||
@@ -241,7 +221,7 @@ def test_xas_simple_scan_with_xrd(scan_assembler, ScanStubStatusMock):
|
||||
"fake_complete",
|
||||
DeviceInstructionMessage(
|
||||
metadata={},
|
||||
device=["bpm4i", "eiger", "mo1_bragg", "samx"],
|
||||
device=["bpm4i", "eiger", "mo1_bragg", "nidaq", "samx"],
|
||||
action="unstage",
|
||||
parameter={},
|
||||
),
|
||||
@@ -265,6 +245,7 @@ def test_xas_advanced_scan(scan_assembler, ScanStubStatusMock):
|
||||
p_kink=50,
|
||||
e_kink=8500,
|
||||
)
|
||||
request.device_manager.add_device("nidaq")
|
||||
reference_commands = get_instructions(request, ScanStubStatusMock)
|
||||
|
||||
assert reference_commands == [
|
||||
@@ -286,7 +267,7 @@ def test_xas_advanced_scan(scan_assembler, ScanStubStatusMock):
|
||||
"monitored": [],
|
||||
"baseline": [],
|
||||
"on_request": [],
|
||||
"async": [],
|
||||
"async": ["nidaq"],
|
||||
},
|
||||
"num_points": None,
|
||||
"positions": [8000.0, 9000.0],
|
||||
@@ -294,6 +275,7 @@ def test_xas_advanced_scan(scan_assembler, ScanStubStatusMock):
|
||||
"scan_type": "fly",
|
||||
},
|
||||
),
|
||||
DeviceInstructionMessage(metadata={}, device="nidaq", action="stage", parameter={}),
|
||||
DeviceInstructionMessage(
|
||||
metadata={},
|
||||
device=["bpm4i", "eiger", "mo1_bragg", "samx"],
|
||||
@@ -308,19 +290,7 @@ def test_xas_advanced_scan(scan_assembler, ScanStubStatusMock):
|
||||
),
|
||||
DeviceInstructionMessage(
|
||||
metadata={"readout_priority": "monitored", "RID": "my_test_request_id"},
|
||||
device="mo1_bragg",
|
||||
action="rpc",
|
||||
parameter={
|
||||
"device": "mo1_bragg",
|
||||
"func": "move_type.set",
|
||||
"rpc_id": "my_test_rpc_id",
|
||||
"args": ("energy",),
|
||||
"kwargs": {},
|
||||
},
|
||||
),
|
||||
DeviceInstructionMessage(
|
||||
metadata={"readout_priority": "monitored", "RID": "my_test_request_id"},
|
||||
device=["bpm4i", "eiger", "mo1_bragg", "samx"],
|
||||
device=["bpm4i", "eiger", "mo1_bragg", "nidaq", "samx"],
|
||||
action="pre_scan",
|
||||
parameter={},
|
||||
),
|
||||
@@ -346,7 +316,7 @@ def test_xas_advanced_scan(scan_assembler, ScanStubStatusMock):
|
||||
"fake_complete",
|
||||
DeviceInstructionMessage(
|
||||
metadata={},
|
||||
device=["bpm4i", "eiger", "mo1_bragg", "samx"],
|
||||
device=["bpm4i", "eiger", "mo1_bragg", "nidaq", "samx"],
|
||||
action="unstage",
|
||||
parameter={},
|
||||
),
|
||||
@@ -378,6 +348,7 @@ def test_xas_advanced_scan_with_xrd(scan_assembler, ScanStubStatusMock):
|
||||
exp_time_high=3,
|
||||
cycle_high=4,
|
||||
)
|
||||
request.device_manager.add_device("nidaq")
|
||||
reference_commands = get_instructions(request, ScanStubStatusMock)
|
||||
|
||||
assert reference_commands == [
|
||||
@@ -399,7 +370,7 @@ def test_xas_advanced_scan_with_xrd(scan_assembler, ScanStubStatusMock):
|
||||
"monitored": [],
|
||||
"baseline": [],
|
||||
"on_request": [],
|
||||
"async": [],
|
||||
"async": ["nidaq"],
|
||||
},
|
||||
"num_points": None,
|
||||
"positions": [8000.0, 9000.0],
|
||||
@@ -407,6 +378,7 @@ def test_xas_advanced_scan_with_xrd(scan_assembler, ScanStubStatusMock):
|
||||
"scan_type": "fly",
|
||||
},
|
||||
),
|
||||
DeviceInstructionMessage(metadata={}, device="nidaq", action="stage", parameter={}),
|
||||
DeviceInstructionMessage(
|
||||
metadata={},
|
||||
device=["bpm4i", "eiger", "mo1_bragg", "samx"],
|
||||
@@ -421,19 +393,7 @@ def test_xas_advanced_scan_with_xrd(scan_assembler, ScanStubStatusMock):
|
||||
),
|
||||
DeviceInstructionMessage(
|
||||
metadata={"readout_priority": "monitored", "RID": "my_test_request_id"},
|
||||
device="mo1_bragg",
|
||||
action="rpc",
|
||||
parameter={
|
||||
"device": "mo1_bragg",
|
||||
"func": "move_type.set",
|
||||
"rpc_id": "my_test_rpc_id",
|
||||
"args": ("energy",),
|
||||
"kwargs": {},
|
||||
},
|
||||
),
|
||||
DeviceInstructionMessage(
|
||||
metadata={"readout_priority": "monitored", "RID": "my_test_request_id"},
|
||||
device=["bpm4i", "eiger", "mo1_bragg", "samx"],
|
||||
device=["bpm4i", "eiger", "mo1_bragg", "nidaq", "samx"],
|
||||
action="pre_scan",
|
||||
parameter={},
|
||||
),
|
||||
@@ -459,7 +419,7 @@ def test_xas_advanced_scan_with_xrd(scan_assembler, ScanStubStatusMock):
|
||||
"fake_complete",
|
||||
DeviceInstructionMessage(
|
||||
metadata={},
|
||||
device=["bpm4i", "eiger", "mo1_bragg", "samx"],
|
||||
device=["bpm4i", "eiger", "mo1_bragg", "nidaq", "samx"],
|
||||
action="unstage",
|
||||
parameter={},
|
||||
),
|
||||
|
||||
127
tests/tests_scans/test_nidaq_continous_scan.py
Normal file
127
tests/tests_scans/test_nidaq_continous_scan.py
Normal file
@@ -0,0 +1,127 @@
|
||||
# pylint: skip-file
|
||||
from unittest import mock
|
||||
|
||||
from bec_lib.messages import DeviceInstructionMessage
|
||||
from bec_server.device_server.tests.utils import DMMock
|
||||
|
||||
from debye_bec.scans import NIDAQContinuousScan
|
||||
|
||||
|
||||
def get_instructions(request, ScanStubStatusMock):
|
||||
request.metadata["RID"] = "my_test_request_id"
|
||||
|
||||
def fake_done():
|
||||
"""
|
||||
Fake done function for ScanStubStatusMock. Upon each call, it returns the next value from the generator.
|
||||
This is used to simulate the completion of the scan.
|
||||
"""
|
||||
yield False
|
||||
yield False
|
||||
yield True
|
||||
|
||||
def fake_complete(*args, **kwargs):
|
||||
yield "fake_complete"
|
||||
return ScanStubStatusMock(done_func=fake_done)
|
||||
|
||||
with (
|
||||
mock.patch.object(request.stubs, "complete", side_effect=fake_complete),
|
||||
mock.patch.object(request.stubs, "_get_result_from_status", return_value=None),
|
||||
):
|
||||
reference_commands = list(request.run())
|
||||
|
||||
for cmd in reference_commands:
|
||||
if not cmd or isinstance(cmd, str):
|
||||
continue
|
||||
if "RID" in cmd.metadata:
|
||||
cmd.metadata["RID"] = "my_test_request_id"
|
||||
if "rpc_id" in cmd.parameter:
|
||||
cmd.parameter["rpc_id"] = "my_test_rpc_id"
|
||||
cmd.metadata.pop("device_instr_id", None)
|
||||
|
||||
return reference_commands
|
||||
|
||||
|
||||
def test_xas_simple_scan(scan_assembler, ScanStubStatusMock):
|
||||
|
||||
request = scan_assembler(NIDAQContinuousScan, scan_duration=10)
|
||||
request.device_manager.add_device("nidaq")
|
||||
reference_commands = get_instructions(request, ScanStubStatusMock)
|
||||
assert reference_commands == [
|
||||
None,
|
||||
None,
|
||||
DeviceInstructionMessage(
|
||||
metadata={"readout_priority": "monitored", "RID": "my_test_request_id"},
|
||||
device=None,
|
||||
action="scan_report_instruction",
|
||||
parameter={"device_progress": ["nidaq"]},
|
||||
),
|
||||
DeviceInstructionMessage(
|
||||
metadata={"readout_priority": "monitored", "RID": "my_test_request_id"},
|
||||
device=None,
|
||||
action="open_scan",
|
||||
parameter={
|
||||
"scan_motors": [],
|
||||
"readout_priority": {
|
||||
"monitored": [],
|
||||
"baseline": [],
|
||||
"on_request": [],
|
||||
"async": ["nidaq"],
|
||||
},
|
||||
"num_points": 0,
|
||||
"positions": [],
|
||||
"scan_name": "nidaq_continuous_scan",
|
||||
"scan_type": "fly",
|
||||
},
|
||||
),
|
||||
DeviceInstructionMessage(metadata={}, device="nidaq", action="stage", parameter={}),
|
||||
DeviceInstructionMessage(
|
||||
metadata={},
|
||||
device=["bpm4i", "eiger", "mo1_bragg", "samx"],
|
||||
action="stage",
|
||||
parameter={},
|
||||
),
|
||||
DeviceInstructionMessage(
|
||||
metadata={"readout_priority": "baseline", "RID": "my_test_request_id"},
|
||||
device=["samx"],
|
||||
action="read",
|
||||
parameter={},
|
||||
),
|
||||
DeviceInstructionMessage(
|
||||
metadata={"readout_priority": "monitored", "RID": "my_test_request_id"},
|
||||
device=["bpm4i", "eiger", "mo1_bragg", "nidaq", "samx"],
|
||||
action="pre_scan",
|
||||
parameter={},
|
||||
),
|
||||
DeviceInstructionMessage(
|
||||
metadata={"readout_priority": "monitored", "RID": "my_test_request_id"},
|
||||
device="nidaq",
|
||||
action="kickoff",
|
||||
parameter={"configure": {}},
|
||||
),
|
||||
"fake_complete",
|
||||
DeviceInstructionMessage(
|
||||
metadata={"readout_priority": "monitored", "RID": "my_test_request_id", "point_id": 0},
|
||||
device=["bpm4i", "eiger", "mo1_bragg"],
|
||||
action="read",
|
||||
parameter={"group": "monitored"},
|
||||
),
|
||||
DeviceInstructionMessage(
|
||||
metadata={"readout_priority": "monitored", "RID": "my_test_request_id", "point_id": 1},
|
||||
device=["bpm4i", "eiger", "mo1_bragg"],
|
||||
action="read",
|
||||
parameter={"group": "monitored"},
|
||||
),
|
||||
"fake_complete",
|
||||
DeviceInstructionMessage(
|
||||
metadata={},
|
||||
device=["bpm4i", "eiger", "mo1_bragg", "nidaq", "samx"],
|
||||
action="unstage",
|
||||
parameter={},
|
||||
),
|
||||
DeviceInstructionMessage(
|
||||
metadata={"readout_priority": "monitored", "RID": "my_test_request_id"},
|
||||
device=None,
|
||||
action="close_scan",
|
||||
parameter={},
|
||||
),
|
||||
]
|
||||
Reference in New Issue
Block a user