Compare commits
21 Commits
ci/gitea_a
...
x07mb_test
Author | SHA1 | Date | |
---|---|---|---|
3b7b56caca | |||
a21f0eebfe | |||
9b63d4af18 | |||
c2d97144bc | |||
e72a00c13e | |||
76b30127d5 | |||
f89f9c4451 | |||
b36c19e224 | |||
68050cb8cf | |||
48f20434e6 | |||
47d5c315d1 | |||
68053222f8 | |||
e30625476d | |||
54435e799d | |||
1c2722e384 | |||
e1c2da776e | |||
e054b5e183 | |||
c3230acee4 | |||
5d106eaecd | |||
d24dc4c674 | |||
13dd35e7b3 |
@ -1,48 +0,0 @@
|
|||||||
name: CI Beamline
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
pull_request:
|
|
||||||
workflow_dispatch:
|
|
||||||
inputs:
|
|
||||||
BEC_WIDGETS_BRANCH:
|
|
||||||
description: "Branch of BEC Widgets to install"
|
|
||||||
required: false
|
|
||||||
type: string
|
|
||||||
BEC_CORE_BRANCH:
|
|
||||||
description: "Branch of BEC Core to install"
|
|
||||||
required: false
|
|
||||||
type: string
|
|
||||||
OPHYD_DEVICES_BRANCH:
|
|
||||||
description: "Branch of Ophyd Devices to install"
|
|
||||||
required: false
|
|
||||||
type: string
|
|
||||||
|
|
||||||
permissions:
|
|
||||||
pull-requests: write
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
test:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
defaults:
|
|
||||||
run:
|
|
||||||
shell: bash -el {0}
|
|
||||||
env:
|
|
||||||
QTWEBENGINE_DISABLE_SANDBOX: 1
|
|
||||||
QT_QPA_PLATFORM: "offscreen"
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: Checkout Shared Actions Repo
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
with:
|
|
||||||
repository: bec/bec_shared_actions
|
|
||||||
ref: main
|
|
||||||
|
|
||||||
- name: Install and test a BEC plugin repository
|
|
||||||
uses: https://gitea.psi.ch/bec/bec_shared_actions/.gitea/plugin_repo_tests@main
|
|
||||||
with:
|
|
||||||
BEC_CORE_BRANCH: ${{ inputs.BEC_CORE_BRANCH || 'main' }}
|
|
||||||
BEC_WIDGETS_BRANCH: ${{ inputs.BEC_WIDGETS_BRANCH || 'main' }}
|
|
||||||
OPHYD_DEVICES_BRANCH: ${{ inputs.OPHYD_DEVICES_BRANCH || 'main' }}
|
|
||||||
BEC_PLUGIN_REPO_URL: "https://gitea.psi.ch/bec/phoenix_bec.git"
|
|
||||||
BEC_PLUGIN_REPO_BRANCH: ${{ gitea.ref_name || 'main' }}
|
|
||||||
PYTHON_VERSION: "3.11"
|
|
1
.gitignore
vendored
1
.gitignore
vendored
@ -7,7 +7,6 @@
|
|||||||
**/.vscode
|
**/.vscode
|
||||||
**/.pytest_cache
|
**/.pytest_cache
|
||||||
**/*.egg*
|
**/*.egg*
|
||||||
*.gz
|
|
||||||
|
|
||||||
# recovery_config files
|
# recovery_config files
|
||||||
recovery_config_*
|
recovery_config_*
|
||||||
|
28
LICENSE
28
LICENSE
@ -1,28 +0,0 @@
|
|||||||
BSD 3-Clause License
|
|
||||||
|
|
||||||
Copyright (c) 2024, Paul Scherrer Institute
|
|
||||||
|
|
||||||
Redistribution and use in source and binary forms, with or without
|
|
||||||
modification, are permitted provided that the following conditions are met:
|
|
||||||
|
|
||||||
1. Redistributions of source code must retain the above copyright notice, this
|
|
||||||
list of conditions and the following disclaimer.
|
|
||||||
|
|
||||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
|
||||||
this list of conditions and the following disclaimer in the documentation
|
|
||||||
and/or other materials provided with the distribution.
|
|
||||||
|
|
||||||
3. Neither the name of the copyright holder nor the names of its
|
|
||||||
contributors may be used to endorse or promote products derived from
|
|
||||||
this software without specific prior written permission.
|
|
||||||
|
|
||||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
||||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
||||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
||||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
||||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
||||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
||||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
||||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
||||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
||||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
File diff suppressed because one or more lines are too long
Binary file not shown.
@ -0,0 +1,2 @@
|
|||||||
|
#to load plugins in this directory upon bec startup, make import in
|
||||||
|
# bec_iphyton_client/startup.py
|
@ -1,4 +1,7 @@
|
|||||||
"""
|
"""
|
||||||
|
FILE
|
||||||
|
phoenix_bec.bec_iphyton_client
|
||||||
|
|
||||||
Post startup script for the BEC client. This script is executed after the
|
Post startup script for the BEC client. This script is executed after the
|
||||||
IPython shell is started. It is used to load the beamline specific
|
IPython shell is started. It is used to load the beamline specific
|
||||||
information and to setup the prompts.
|
information and to setup the prompts.
|
||||||
@ -10,6 +13,11 @@ While command-line arguments have to be set in the pre-startup script, the
|
|||||||
post-startup script can be used to load beamline specific information and
|
post-startup script can be used to load beamline specific information and
|
||||||
to setup the prompts.
|
to setup the prompts.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#################################################################
|
||||||
|
# OLD CODE FROM CSAXS ONLY AS EXAMPLE NEXT LINES CAN BE IGNORED
|
||||||
|
################################################################
|
||||||
from bec_lib.logger import bec_logger
|
from bec_lib.logger import bec_logger
|
||||||
|
|
||||||
logger = bec_logger.logger
|
logger = bec_logger.logger
|
||||||
@ -24,28 +32,28 @@ to setup the prompts.
|
|||||||
|
|
||||||
_session_name = "LamNI"
|
_session_name = "LamNI"
|
||||||
lamni = LamNI(bec)
|
lamni = LamNI(bec)
|
||||||
logger.success("LamNI session loaded.")
|
logger.succ#ess("LamNI session loaded.")
|
||||||
|
|
||||||
elif _args.session.lower() == "csaxs":
|
elif _args.session.lower() == "csaxs":
|
||||||
print("Loading cSAXS session")
|
print("Loading cSAXS session")
|
||||||
from csaxs_bec.bec_ipython_client.plugins.cSAXS import *
|
from csaxs_bec.bec_ipython_client.plugins.cSAXS import *
|
||||||
|
|
||||||
logger.success("cSAXS session loaded.")
|
logger.success("cSAXS session loaded.")
|
||||||
|
#####################################################################
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# pylint: disable=invalid-name, unused-import, import-error, undefined-variable, unused-variable, unused-argument, no-name-in-module
|
|
||||||
import time as tt
|
import time as tt
|
||||||
import sys
|
import sys
|
||||||
import os
|
|
||||||
from IPython.core.magic import register_line_magic
|
from IPython.core.magic import register_line_magic
|
||||||
|
|
||||||
|
|
||||||
from bec_lib.logger import bec_logger
|
from bec_lib.logger import bec_logger
|
||||||
|
|
||||||
logger = bec_logger.logger
|
logger = bec_logger.logger
|
||||||
# logger = bec_logger.LOGLEVEL.TRACE
|
#logger = bec_logger.LOGLEVEL.TRACE
|
||||||
|
|
||||||
# pylint: disable=invald-name, unused-import, import-error, undefined-variable, unused-variable, unused-argument, no-name-in-module
|
#pylint: disable=invald-name, unused-import, import-error, undefined-variable, unused-variable, unused-argument, no-name-in-module
|
||||||
|
|
||||||
_session_name = "session_phoenix"
|
_session_name = "session_phoenix"
|
||||||
# SETUP PROMPTS
|
# SETUP PROMPTS
|
||||||
@ -53,120 +61,101 @@ bec._ip.prompts.username = "PHOENIX"
|
|||||||
bec._ip.prompts.status = 1
|
bec._ip.prompts.status = 1
|
||||||
|
|
||||||
# make sure that edited modules are reloaded when changed
|
# make sure that edited modules are reloaded when changed
|
||||||
|
print('phoenix_bec/bec_iphyon_client/startup/post_startup.py')
|
||||||
print("phoenix_bec/bec_iphyon_client/startup/post_startup.py")
|
print('... set autoreload of modules')
|
||||||
print("... set autoreload of modules")
|
bec._ip.run_line_magic("reload_ext","autoreload")
|
||||||
bec._ip.run_line_magic("reload_ext", "autoreload")
|
|
||||||
bec._ip.run_line_magic("autoreload", "2")
|
bec._ip.run_line_magic("autoreload", "2")
|
||||||
|
|
||||||
print("autoreload loaded ")
|
print('autoreload loaded ')
|
||||||
|
|
||||||
|
################################################################
|
||||||
|
#
|
||||||
|
# next lines are not ideal and likely need to be removed they are a temporary fix as
|
||||||
|
# we had trouble finding certain paths.
|
||||||
|
#
|
||||||
|
# we need to work in server_env, otherwise no server is found
|
||||||
|
# path to ophyd devices is only way to find default ophyd devices
|
||||||
|
# path to python packages not ideal but only way to add pyp5 and pandas packages
|
||||||
|
#
|
||||||
|
##############################################################
|
||||||
|
|
||||||
|
|
||||||
|
print('post_startup.py : set some paths as temp fix. This needs to be solved ')
|
||||||
|
|
||||||
|
ophyd_devices_path='/data/test/x07mb-test-bec/bec_deployment/ophyd_devices'
|
||||||
|
p_path='/data/test/x07mb-test-bec/bec_deployment/bec_server_venv/lib/python3.11/site-packages'
|
||||||
|
|
||||||
|
print('add ',ophyd_devices_path)
|
||||||
|
print('add ',p_path)
|
||||||
|
|
||||||
|
if ophyd_devices_path not in sys.path:
|
||||||
|
sys.path.insert(1, os.path.expandvars(ophyd_devices_path))
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if p_path not in sys.path:
|
||||||
|
sys.path.insert(1, os.path.expandvars(p_path))
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
#############################################################################
|
#############################################################################
|
||||||
#
|
#
|
||||||
# ... register BL specific magic commands
|
#... register BL specific magic commands
|
||||||
#
|
#
|
||||||
##############################################################################
|
##############################################################################
|
||||||
|
# not clear, error messages. Here we know what we do.
|
||||||
|
|
||||||
@register_line_magic
|
|
||||||
def ph_reload(line):
|
|
||||||
|
|
||||||
##########################################################################
|
|
||||||
#
|
#
|
||||||
# this reloads certain phoenix related script to the iphython shell.
|
########################################################################
|
||||||
# useful for debugging/development to be revised for production
|
|
||||||
# aim of this magic is to quickl reload BL related stuff for debugging
|
|
||||||
# Most likely there are better ways to do this (possibly bec.reload_user_script()
|
|
||||||
# but syntax not clear, error messages. Here we know what we do
|
|
||||||
#
|
|
||||||
###################################################################W#####
|
|
||||||
|
|
||||||
from phoenix_bec.scripts import phoenix as PH
|
from phoenix_bec.scripts import phoenix as PH
|
||||||
|
print('reload phoenix_bec.scripts.phoenix to iphyhton console')
|
||||||
print("reload phoenix_bec.scripts.phoenix to iphyhton console")
|
print('to update version server restart server ')
|
||||||
print("to update version server restart server ")
|
|
||||||
# need to use global statement here, as I like to reload into space on
|
# need to use global statement here, as I like to reload into space on
|
||||||
# iphyton consoel
|
# iphyton consoel
|
||||||
global PH, phoenix
|
global PH,phoenix
|
||||||
print("from phoenix_bec.scripts import phoenix as PH")
|
print('from phoenix_bec.scripts import phoenix as PH')
|
||||||
print("phoenix = PH.PhoenixBL()")
|
print('phoenix = PH.PhoenixBL()')
|
||||||
phoenix = PH.PhoenixBL()
|
phoenix = PH.PhoenixBL()
|
||||||
|
#ph_config=PH.PhoenixConfighelper()
|
||||||
|
#enddef
|
||||||
|
|
||||||
# ph_config=PH.PhoenixConfighelper()
|
print('##################################################################')
|
||||||
|
print('register magic')
|
||||||
|
print('...... %ph_load_xmap ... to reload xmap configuration')
|
||||||
# enddef
|
|
||||||
|
|
||||||
print("##################################################################")
|
|
||||||
print("register magic")
|
|
||||||
print("...... %ph_load_xmap ... to reload xmap configuration")
|
|
||||||
|
|
||||||
|
|
||||||
@register_line_magic
|
@register_line_magic
|
||||||
def ph_add_xmap(line):
|
def ph_load_xmap(line):
|
||||||
"""
|
|
||||||
magic for loading xmap
|
|
||||||
"""
|
|
||||||
t0 = tt.time()
|
|
||||||
phoenix.add_xmap()
|
|
||||||
print("elapsed time:", tt.time() - t0)
|
|
||||||
|
|
||||||
|
###
|
||||||
|
#magic for loading xmap
|
||||||
|
###
|
||||||
|
|
||||||
# enddef
|
t0=tt.time()
|
||||||
|
phoenix_server.add_xmap()
|
||||||
print("...... %ph_load_falcon ... to reload falcon configuration")
|
print('elapsed time:', tt.time()-t0)
|
||||||
|
#enddef
|
||||||
|
|
||||||
@register_line_magic
|
|
||||||
def ph_add_falcon(line):
|
|
||||||
"""
|
|
||||||
magic to add falcon to existing configuration
|
|
||||||
"""
|
|
||||||
t0 = tt.time()
|
|
||||||
phoenix.add_falcon()
|
|
||||||
print("elapsed time:", tt.time() - t0)
|
|
||||||
|
|
||||||
|
|
||||||
## enddef
|
|
||||||
|
|
||||||
|
|
||||||
|
print('...... %ph_load_falcon ... to reload falcon configuration')
|
||||||
@register_line_magic
|
@register_line_magic
|
||||||
def ph_load_falcon(line):
|
def ph_load_falcon(line):
|
||||||
"""
|
|
||||||
magic to load falcon as sole detector
|
|
||||||
"""
|
|
||||||
t0 = tt.time()
|
|
||||||
phoenix.load_falcon()
|
|
||||||
print("elapsed time:", tt.time() - t0)
|
|
||||||
|
|
||||||
|
# magic to load falcon
|
||||||
|
|
||||||
print("...... %ph_load_config ... to reload phoenix default configuration")
|
t0=tt.time()
|
||||||
|
phoenix_server.add_falcon()
|
||||||
|
print('elapsed time:', tt.time()-t0)
|
||||||
|
#enddef
|
||||||
|
|
||||||
|
print('...... %ph_load_config ... to reload phoenix default configuration')
|
||||||
@register_line_magic
|
@register_line_magic
|
||||||
def ph_create_base_config(line):
|
def ph_load_config(line):
|
||||||
"""
|
t0=tt.time()
|
||||||
load base configuration for PHOENIX beamline
|
phoenix_server.add_phoenix_config()
|
||||||
using phoenix.create_base_config()
|
print('elapsed time:', tt.time()-t0)
|
||||||
phoenix_bec/device_configs/phoenix_devices.yaml
|
#enddef
|
||||||
"""
|
|
||||||
t0 = tt.time()
|
|
||||||
phoenix.create_base_config()
|
|
||||||
print("elapsed time:", tt.time() - t0)
|
|
||||||
|
|
||||||
|
|
||||||
### enddef
|
|
||||||
|
|
||||||
|
|
||||||
@register_line_magic
|
|
||||||
def ph_restart_bec_server(line):
|
|
||||||
os.system("bec-server restart")
|
|
||||||
os.system(
|
|
||||||
'gnome-terminal --geometry 170X50 -- bash -c "source /data/test/x07mb-test-bec/bec_deployment/bec_venv/bin/activate ; bec-server attach; exec bash"'
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
|
##@register_line_magic
|
||||||
|
#def ph_post_startup(line):
|
||||||
|
# print('import phoenix_bec.bec_ipython_client.startup.post_startup does not work caused loop ')
|
||||||
# #import phoenix_bec.bec_ipython_client.startup.post_startup
|
# #import phoenix_bec.bec_ipython_client.startup.post_startup
|
||||||
# does not work seems to build a infinite stack...
|
# does not work seems to build a infinite stack...
|
||||||
|
|
||||||
@ -175,24 +164,19 @@ def ph_restart_bec_server(line):
|
|||||||
# init phoenix.py from server version as
|
# init phoenix.py from server version as
|
||||||
# .................. phoenix_server=PhoenixBL()
|
# .................. phoenix_server=PhoenixBL()
|
||||||
# and in ipython shell only as
|
# and in ipython shell only as
|
||||||
# .............. phoenix = Ph.Phoenix(BL()
|
# .............. phoenix = Ph.Pheonix(BL()
|
||||||
##
|
##
|
||||||
#####################################################################################
|
#####################################################################################
|
||||||
|
|
||||||
print("###############################################################")
|
print('###############################################################')
|
||||||
print("init phoenix_bec/scripts/phoenix.py in two different ways")
|
print('init phoenix_bec/scripts/phoenix.py in two different ways')
|
||||||
print(" 1) phoenix_server = PhoenixBL() ... takes code from server version ")
|
print(' 1) phoenix_server = PhoenixBL() ... takes code from server version ')
|
||||||
print("SERVR VERSION DOES NOT WORK ANYMORE ")
|
phoenix_server=PhoenixBL()
|
||||||
print("FOLDER SCRIPT SEEMS TO BE NON_STANDARD!!!!!!! ")
|
|
||||||
|
|
||||||
phoenix_server = PhoenixBL()
|
|
||||||
|
|
||||||
print(" 2) phoenix=PH.PhoenixBL() ... on inpython shell only! (for debugging)")
|
|
||||||
|
|
||||||
|
print(' 2) phoenix=PH.PhoenixBL() ... on inpython shell only! (for debugging)')
|
||||||
from phoenix_bec.scripts import phoenix as PH
|
from phoenix_bec.scripts import phoenix as PH
|
||||||
|
|
||||||
phoenix = PH.PhoenixBL()
|
phoenix = PH.PhoenixBL()
|
||||||
|
|
||||||
|
|
||||||
# from phoenix_bec.bec_ipython_client.plugins.phoenix import Phoenix
|
#from phoenix_bec.bec_ipython_client.plugins.phoenix import Phoenix
|
||||||
# from phoenix_bec.devices.falcon_phoenix_no_hdf5 import FalconHDF5Plugins
|
#from phoenix_bec.devices.falcon_phoenix_no_hdf5 import FalconHDF5Plugins
|
||||||
|
@ -1,182 +0,0 @@
|
|||||||
"""
|
|
||||||
FILE
|
|
||||||
phoenix_bec.bec_iphyton_client
|
|
||||||
|
|
||||||
Post startup script for the BEC client. This script is executed after the
|
|
||||||
IPython shell is started. It is used to load the beamline specific
|
|
||||||
information and to setup the prompts.
|
|
||||||
|
|
||||||
The script is executed in the global namespace of the IPython shell. This
|
|
||||||
means that all variables defined here are available in the shell.
|
|
||||||
|
|
||||||
While command-line arguments have to be set in the pre-startup script, the
|
|
||||||
post-startup script can be used to load beamline specific information and
|
|
||||||
to setup the prompts.
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#################################################################
|
|
||||||
# OLD CODE FROM CSAXS ONLY AS EXAMPLE NEXT LINES CAN BE IGNORED
|
|
||||||
################################################################
|
|
||||||
from bec_lib.logger import bec_logger
|
|
||||||
|
|
||||||
logger = bec_logger.logger
|
|
||||||
|
|
||||||
# pylint: disable=import-error
|
|
||||||
_args = _main_dict["args"]
|
|
||||||
|
|
||||||
_session_name = "cSAXS"
|
|
||||||
if _args.session.lower() == "lamni":
|
|
||||||
from csaxs_bec.bec_ipython_client.plugins.cSAXS import *
|
|
||||||
from csaxs_bec.bec_ipython_client.plugins.LamNI import *
|
|
||||||
|
|
||||||
_session_name = "LamNI"
|
|
||||||
lamni = LamNI(bec)
|
|
||||||
logger.succ#ess("LamNI session loaded.")
|
|
||||||
|
|
||||||
elif _args.session.lower() == "csaxs":
|
|
||||||
print("Loading cSAXS session")
|
|
||||||
from csaxs_bec.bec_ipython_client.plugins.cSAXS import *
|
|
||||||
|
|
||||||
logger.success("cSAXS session loaded.")
|
|
||||||
#####################################################################
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
import time as tt
|
|
||||||
import sys
|
|
||||||
from IPython.core.magic import register_line_magic
|
|
||||||
|
|
||||||
|
|
||||||
from bec_lib.logger import bec_logger
|
|
||||||
|
|
||||||
logger = bec_logger.logger
|
|
||||||
#logger = bec_logger.LOGLEVEL.TRACE
|
|
||||||
|
|
||||||
#pylint: disable=invald-name, unused-import, import-error, undefined-variable, unused-variable, unused-argument, no-name-in-module
|
|
||||||
|
|
||||||
_session_name = "session_phoenix"
|
|
||||||
# SETUP PROMPTS
|
|
||||||
bec._ip.prompts.username = "PHOENIX"
|
|
||||||
bec._ip.prompts.status = 1
|
|
||||||
|
|
||||||
# make sure that edited modules are reloaded when changed
|
|
||||||
print('phoenix_bec/bec_iphyon_client/startup/post_startup.py')
|
|
||||||
print('... set autoreload of modules')
|
|
||||||
bec._ip.run_line_magic("reload_ext","autoreload")
|
|
||||||
bec._ip.run_line_magic("autoreload", "2")
|
|
||||||
|
|
||||||
print('autoreload loaded ')
|
|
||||||
|
|
||||||
################################################################
|
|
||||||
#
|
|
||||||
# next lines are not ideal and likely need to be removed they are a temporary fix as
|
|
||||||
# we had trouble finding certain paths.
|
|
||||||
#
|
|
||||||
# we need to work in server_env, otherwise no server is found
|
|
||||||
# path to ophyd devices is only way to find default ophyd devices
|
|
||||||
# path to python packages not ideal but only way to add pyp5 and pandas packages
|
|
||||||
#
|
|
||||||
##############################################################
|
|
||||||
|
|
||||||
|
|
||||||
print('post_startup.py : set some paths as temp fix. This needs to be solved ')
|
|
||||||
|
|
||||||
ophyd_devices_path='/data/test/x07mb-test-bec/bec_deployment/ophyd_devices'
|
|
||||||
p_path='/data/test/x07mb-test-bec/bec_deployment/bec_server_venv/lib/python3.11/site-packages'
|
|
||||||
|
|
||||||
print('add ',ophyd_devices_path)
|
|
||||||
print('add ',p_path)
|
|
||||||
|
|
||||||
if ophyd_devices_path not in sys.path:
|
|
||||||
sys.path.insert(1, os.path.expandvars(ophyd_devices_path))
|
|
||||||
#endif
|
|
||||||
|
|
||||||
if p_path not in sys.path:
|
|
||||||
sys.path.insert(1, os.path.expandvars(p_path))
|
|
||||||
#endif
|
|
||||||
|
|
||||||
|
|
||||||
#############################################################################
|
|
||||||
#
|
|
||||||
#... register BL specific magic commands
|
|
||||||
#
|
|
||||||
##############################################################################
|
|
||||||
# not clear, error messages. Here we know what we do.
|
|
||||||
#
|
|
||||||
########################################################################
|
|
||||||
|
|
||||||
from phoenix_bec.scripts import phoenix as PH
|
|
||||||
print('reload phoenix_bec.scripts.phoenix to iphyhton console')
|
|
||||||
print('to update version server restart server ')
|
|
||||||
# need to use global statement here, as I like to reload into space on
|
|
||||||
# iphyton consoel
|
|
||||||
global PH,phoenix
|
|
||||||
print('from phoenix_bec.scripts import phoenix as PH')
|
|
||||||
print('phoenix = PH.PhoenixBL()')
|
|
||||||
phoenix = PH.PhoenixBL()
|
|
||||||
#ph_config=PH.PhoenixConfighelper()
|
|
||||||
#enddef
|
|
||||||
|
|
||||||
print('##################################################################')
|
|
||||||
print('register magic')
|
|
||||||
print('...... %ph_load_xmap ... to reload xmap configuration')
|
|
||||||
@register_line_magic
|
|
||||||
def ph_load_xmap(line):
|
|
||||||
|
|
||||||
###
|
|
||||||
#magic for loading xmap
|
|
||||||
###
|
|
||||||
|
|
||||||
t0=tt.time()
|
|
||||||
phoenix_server.add_xmap()
|
|
||||||
print('elapsed time:', tt.time()-t0)
|
|
||||||
#enddef
|
|
||||||
|
|
||||||
print('...... %ph_load_falcon ... to reload falcon configuration')
|
|
||||||
@register_line_magic
|
|
||||||
def ph_load_falcon(line):
|
|
||||||
|
|
||||||
# magic to load falcon
|
|
||||||
|
|
||||||
t0=tt.time()
|
|
||||||
phoenix_server.add_falcon()
|
|
||||||
print('elapsed time:', tt.time()-t0)
|
|
||||||
#enddef
|
|
||||||
|
|
||||||
print('...... %ph_load_config ... to reload phoenix default configuration')
|
|
||||||
@register_line_magic
|
|
||||||
def ph_load_config(line):
|
|
||||||
t0=tt.time()
|
|
||||||
phoenix_server.add_phoenix_config()
|
|
||||||
print('elapsed time:', tt.time()-t0)
|
|
||||||
#enddef
|
|
||||||
|
|
||||||
|
|
||||||
##@register_line_magic
|
|
||||||
#def ph_post_startup(line):
|
|
||||||
# print('import phoenix_bec.bec_ipython_client.startup.post_startup does not work caused loop ')
|
|
||||||
# #import phoenix_bec.bec_ipython_client.startup.post_startup
|
|
||||||
# does not work seems to build a infinite stack...
|
|
||||||
|
|
||||||
####################################################################################
|
|
||||||
#
|
|
||||||
# init phoenix.py from server version as
|
|
||||||
# .................. phoenix_server=PhoenixBL()
|
|
||||||
# and in ipython shell only as
|
|
||||||
# .............. phoenix = Ph.Pheonix(BL()
|
|
||||||
##
|
|
||||||
#####################################################################################
|
|
||||||
|
|
||||||
print('###############################################################')
|
|
||||||
print('init phoenix_bec/scripts/phoenix.py in two different ways')
|
|
||||||
print(' 1) phoenix_server = PhoenixBL() ... takes code from server version ')
|
|
||||||
phoenix_server=PhoenixBL()
|
|
||||||
|
|
||||||
print(' 2) phoenix=PH.PhoenixBL() ... on inpython shell only! (for debugging)')
|
|
||||||
from phoenix_bec.scripts import phoenix as PH
|
|
||||||
phoenix = PH.PhoenixBL()
|
|
||||||
|
|
||||||
|
|
||||||
#from phoenix_bec.bec_ipython_client.plugins.phoenix import Phoenix
|
|
||||||
#from phoenix_bec.devices.falcon_phoenix_no_hdf5 import FalconHDF5Plugins
|
|
@ -21,3 +21,6 @@ def extend_command_line_args(parser):
|
|||||||
# Create and return the service configuration.
|
# Create and return the service configuration.
|
||||||
# """
|
# """
|
||||||
# return ServiceConfig(redis={"host": "localhost", "port": 6379})
|
# return ServiceConfig(redis={"host": "localhost", "port": 6379})
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,26 +0,0 @@
|
|||||||
"""
|
|
||||||
Pre-startup script for BEC client. This script is executed before the BEC client
|
|
||||||
is started. It can be used to add additional command line arguments.
|
|
||||||
"""
|
|
||||||
|
|
||||||
from bec_lib.service_config import ServiceConfig
|
|
||||||
|
|
||||||
|
|
||||||
def extend_command_line_args(parser):
|
|
||||||
"""
|
|
||||||
Extend the command line arguments of the BEC client.
|
|
||||||
"""
|
|
||||||
|
|
||||||
# parser.add_argument("--session", help="Session name", type=str, default="cSAXS")
|
|
||||||
|
|
||||||
return parser
|
|
||||||
|
|
||||||
|
|
||||||
# def get_config() -> ServiceConfig:
|
|
||||||
# """
|
|
||||||
# Create and return the service configuration.
|
|
||||||
# """
|
|
||||||
# return ServiceConfig(redis={"host": "localhost", "port": 6379})
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -0,0 +1 @@
|
|||||||
|
|
||||||
|
@ -1,413 +0,0 @@
|
|||||||
|
|
||||||
PH_TTL:
|
|
||||||
description: PHOENIX TTL trigger
|
|
||||||
deviceClass: phoenix_bec.devices.phoenix_trigger.PhoenixTrigger
|
|
||||||
deviceConfig:
|
|
||||||
prefix: 'X07MB-OP2:'
|
|
||||||
deviceTags:
|
|
||||||
- phoenix
|
|
||||||
- TTL Trigger
|
|
||||||
- phoenix_devices.yaml
|
|
||||||
onFailure: buffer
|
|
||||||
enabled: true
|
|
||||||
readoutPriority: monitored
|
|
||||||
softwareTrigger: true
|
|
||||||
|
|
||||||
|
|
||||||
###################################################
|
|
||||||
#
|
|
||||||
# phoenix standard devices (motors)
|
|
||||||
#
|
|
||||||
#
|
|
||||||
####################################################:
|
|
||||||
|
|
||||||
PH_Dummy:
|
|
||||||
description: Class to test functionality of PSI detector. reads/writes into X07MB-PC-PSCAN.P-P0D0"
|
|
||||||
deviceClass: phoenix_bec.devices.dummy_devices.Dummy_PSIDetector
|
|
||||||
deviceConfig:
|
|
||||||
prefix: 'X07MB-PC-PSCAN:'
|
|
||||||
name: 'Dummy_Detector_PSI_Detector'
|
|
||||||
deviceTags:
|
|
||||||
- phoenix
|
|
||||||
- phoenix_devices.yamllass
|
|
||||||
- reads channel X07MB-PC-PSCAN.P-P0D0
|
|
||||||
- Dummy class to test PSI detector c from DAQ GUI
|
|
||||||
onFailure: buffer
|
|
||||||
enabled: true
|
|
||||||
readoutPriority: monitored
|
|
||||||
softwareTrigger: false
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
############################
|
|
||||||
#
|
|
||||||
# MOTORS ES1
|
|
||||||
#
|
|
||||||
############################
|
|
||||||
|
|
||||||
ScanX:
|
|
||||||
readoutPriority: baseline
|
|
||||||
description: 'Vertical sample position ES-MA1.ScanX'
|
|
||||||
deviceClass: ophyd.EpicsMotor
|
|
||||||
deviceConfig:
|
|
||||||
prefix: 'X07MB-ES-MA1:ScanX'
|
|
||||||
deviceTags:
|
|
||||||
- ES-MA1.ScanX
|
|
||||||
- phoenix_bec/device_configs/phoenix_devices.yaml
|
|
||||||
onFailure: retry
|
|
||||||
enabled: true
|
|
||||||
readOnly: false
|
|
||||||
|
|
||||||
ScanY:
|
|
||||||
readoutPriority: baseline
|
|
||||||
description: 'Horizontal sample position ES-MA1.ScanY'
|
|
||||||
deviceClass: ophyd.EpicsMotor
|
|
||||||
deviceConfig:
|
|
||||||
prefix: 'X07MB-ES-MA1:ScanY'
|
|
||||||
deviceTags:
|
|
||||||
- ES-MA1.ScanY
|
|
||||||
- phoenix_bec/device_configs/phoenix_devices.yaml
|
|
||||||
onFailure: retry
|
|
||||||
enabled: true
|
|
||||||
readOnly: false
|
|
||||||
softwareTrigger: false
|
|
||||||
#
|
|
||||||
#
|
|
||||||
# DIODES from ES1 ADC
|
|
||||||
#
|
|
||||||
#
|
|
||||||
|
|
||||||
SAI_07_MEAN:
|
|
||||||
readoutPriority: monitored
|
|
||||||
description: DIODE SAI07
|
|
||||||
deviceClass: ophyd.EpicsSignalRO
|
|
||||||
deviceConfig:
|
|
||||||
auto_monitor: true
|
|
||||||
read_pv: 'X07MB-OP2-SAI_07:MEAN'
|
|
||||||
deviceTags:
|
|
||||||
- PHOENIX
|
|
||||||
- phoenix_bec/device_configs/phoenix_devices.yaml
|
|
||||||
onFailure: buffer
|
|
||||||
enabled: true
|
|
||||||
readOnly: true
|
|
||||||
softwareTrigger: false
|
|
||||||
|
|
||||||
SAI_08_MEAN:
|
|
||||||
readoutPriority: monitored
|
|
||||||
description: DIODE
|
|
||||||
deviceClass: ophyd.EpicsSignalRO
|
|
||||||
deviceConfig:
|
|
||||||
auto_monitor: true
|
|
||||||
read_pv: 'X07MB-OP2-SAI_08:MEAN'
|
|
||||||
deviceTags:
|
|
||||||
- PHOENIX
|
|
||||||
- phoenix_bec/device_configs/phoenix_devices.yaml
|
|
||||||
onFailure: buffer
|
|
||||||
enabled: true
|
|
||||||
readOnly: true
|
|
||||||
softwareTrigger: false
|
|
||||||
|
|
||||||
#
|
|
||||||
# END OF STANDARD CONFIG
|
|
||||||
#
|
|
||||||
|
|
||||||
#
|
|
||||||
# falcon without hdf5
|
|
||||||
#
|
|
||||||
falcon_hdf5:
|
|
||||||
description: Falcon detector x-ray fluoresence with hdf5 plugin from device class phoenix_bec.devices. falcon_phoenix.FalconPhoenix
|
|
||||||
deviceClass: phoenix_bec.devices.falcon_phoenix.FalconPhoenix
|
|
||||||
deviceConfig:
|
|
||||||
prefix: 'X07MB-SITORO:'
|
|
||||||
deviceTags:
|
|
||||||
- phoenix
|
|
||||||
- falcon
|
|
||||||
- with hdf5
|
|
||||||
- phoenix_falcon.yaml
|
|
||||||
onFailure: buffer
|
|
||||||
enabled: true
|
|
||||||
readoutPriority: async
|
|
||||||
softwareTrigger: false
|
|
||||||
#
|
|
||||||
# END FALCON with HDF5
|
|
||||||
#
|
|
||||||
#
|
|
||||||
# falcon without hdf5
|
|
||||||
#
|
|
||||||
falcon_hdf5:
|
|
||||||
description: Falcon detector x-ray fluoresence with hdf5 plugin from device class phoenix_bec.devices. falcon_phoenix.FalconPhoenix
|
|
||||||
deviceClass: phoenix_bec.devices.falcon_phoenix.FalconPhoenix
|
|
||||||
deviceConfig:
|
|
||||||
prefix: 'X07MB-SITORO:'
|
|
||||||
deviceTags:
|
|
||||||
- phoenix
|
|
||||||
- falcon
|
|
||||||
- with hdf5
|
|
||||||
- phoenix_falcon.yaml
|
|
||||||
onFailure: buffer
|
|
||||||
enabled: true
|
|
||||||
readoutPriority: async
|
|
||||||
softwareTrigger: false
|
|
||||||
#
|
|
||||||
# END FALCON with HDF5
|
|
||||||
#
|
|
||||||
#
|
|
||||||
# falcon without hdf5
|
|
||||||
#
|
|
||||||
falcon_hdf5:
|
|
||||||
description: Falcon detector x-ray fluoresence with hdf5 plugin from device class phoenix_bec.devices. falcon_phoenix.FalconPhoenix
|
|
||||||
deviceClass: phoenix_bec.devices.falcon_phoenix.FalconPhoenix
|
|
||||||
deviceConfig:
|
|
||||||
prefix: 'X07MB-SITORO:'
|
|
||||||
deviceTags:
|
|
||||||
- phoenix
|
|
||||||
- falcon
|
|
||||||
- with hdf5
|
|
||||||
- phoenix_falcon.yaml
|
|
||||||
onFailure: buffer
|
|
||||||
enabled: true
|
|
||||||
readoutPriority: async
|
|
||||||
softwareTrigger: false
|
|
||||||
#
|
|
||||||
# END FALCON with HDF5
|
|
||||||
#
|
|
||||||
#
|
|
||||||
# falcon without hdf5
|
|
||||||
#
|
|
||||||
falcon_hdf5:
|
|
||||||
description: Falcon detector x-ray fluoresence with hdf5 plugin from device class phoenix_bec.devices. falcon_phoenix.FalconPhoenix
|
|
||||||
deviceClass: phoenix_bec.devices.falcon_phoenix.FalconPhoenix
|
|
||||||
deviceConfig:
|
|
||||||
prefix: 'X07MB-SITORO:'
|
|
||||||
deviceTags:
|
|
||||||
- phoenix
|
|
||||||
- falcon
|
|
||||||
- with hdf5
|
|
||||||
- phoenix_falcon.yaml
|
|
||||||
onFailure: buffer
|
|
||||||
enabled: true
|
|
||||||
readoutPriority: async
|
|
||||||
softwareTrigger: false
|
|
||||||
#
|
|
||||||
# END FALCON with HDF5
|
|
||||||
#
|
|
||||||
#
|
|
||||||
# falcon without hdf5
|
|
||||||
#
|
|
||||||
falcon_hdf5:
|
|
||||||
description: Falcon detector x-ray fluoresence with hdf5 plugin from device class phoenix_bec.devices. falcon_phoenix.FalconPhoenix
|
|
||||||
deviceClass: phoenix_bec.devices.falcon_phoenix.FalconPhoenix
|
|
||||||
deviceConfig:
|
|
||||||
prefix: 'X07MB-SITORO:'
|
|
||||||
deviceTags:
|
|
||||||
- phoenix
|
|
||||||
- falcon
|
|
||||||
- with hdf5
|
|
||||||
- phoenix_falcon.yaml
|
|
||||||
onFailure: buffer
|
|
||||||
enabled: true
|
|
||||||
readoutPriority: async
|
|
||||||
softwareTrigger: false
|
|
||||||
#
|
|
||||||
# END FALCON with HDF5
|
|
||||||
#
|
|
||||||
#
|
|
||||||
# falcon without hdf5
|
|
||||||
#
|
|
||||||
falcon_hdf5:
|
|
||||||
description: Falcon detector x-ray fluoresence with hdf5 plugin from device class phoenix_bec.devices. falcon_phoenix.FalconPhoenix
|
|
||||||
deviceClass: phoenix_bec.devices.falcon_phoenix.FalconPhoenix
|
|
||||||
deviceConfig:
|
|
||||||
prefix: 'X07MB-SITORO:'
|
|
||||||
deviceTags:
|
|
||||||
- phoenix
|
|
||||||
- falcon
|
|
||||||
- with hdf5
|
|
||||||
- phoenix_falcon.yaml
|
|
||||||
onFailure: buffer
|
|
||||||
enabled: true
|
|
||||||
readoutPriority: async
|
|
||||||
softwareTrigger: false
|
|
||||||
#
|
|
||||||
# END FALCON with HDF5
|
|
||||||
#
|
|
||||||
#
|
|
||||||
# falcon without hdf5
|
|
||||||
#
|
|
||||||
falcon_hdf5:
|
|
||||||
description: Falcon detector x-ray fluoresence with hdf5 plugin from device class phoenix_bec.devices. falcon_phoenix.FalconPhoenix
|
|
||||||
deviceClass: phoenix_bec.devices.falcon_phoenix.FalconPhoenix
|
|
||||||
deviceConfig:
|
|
||||||
prefix: 'X07MB-SITORO:'
|
|
||||||
deviceTags:
|
|
||||||
- phoenix
|
|
||||||
- falcon
|
|
||||||
- with hdf5
|
|
||||||
- phoenix_falcon.yaml
|
|
||||||
onFailure: buffer
|
|
||||||
enabled: true
|
|
||||||
readoutPriority: async
|
|
||||||
softwareTrigger: false
|
|
||||||
#
|
|
||||||
# END FALCON with HDF5
|
|
||||||
#
|
|
||||||
#
|
|
||||||
# falcon without hdf5
|
|
||||||
#
|
|
||||||
falcon_hdf5:
|
|
||||||
description: Falcon detector x-ray fluoresence with hdf5 plugin from device class phoenix_bec.devices. falcon_phoenix.FalconPhoenix
|
|
||||||
deviceClass: phoenix_bec.devices.falcon_phoenix.FalconPhoenix
|
|
||||||
deviceConfig:
|
|
||||||
prefix: 'X07MB-SITORO:'
|
|
||||||
deviceTags:
|
|
||||||
- phoenix
|
|
||||||
- falcon
|
|
||||||
- with hdf5
|
|
||||||
- phoenix_falcon.yaml
|
|
||||||
onFailure: buffer
|
|
||||||
enabled: true
|
|
||||||
readoutPriority: async
|
|
||||||
softwareTrigger: false
|
|
||||||
#
|
|
||||||
# END FALCON with HDF5
|
|
||||||
#
|
|
||||||
#
|
|
||||||
# falcon without hdf5
|
|
||||||
#
|
|
||||||
falcon_hdf5:
|
|
||||||
description: Falcon detector x-ray fluoresence with hdf5 plugin from device class phoenix_bec.devices. falcon_phoenix.FalconPhoenix
|
|
||||||
deviceClass: phoenix_bec.devices.falcon_phoenix.FalconPhoenix
|
|
||||||
deviceConfig:
|
|
||||||
prefix: 'X07MB-SITORO:'
|
|
||||||
deviceTags:
|
|
||||||
- phoenix
|
|
||||||
- falcon
|
|
||||||
- with hdf5
|
|
||||||
- phoenix_falcon.yaml
|
|
||||||
onFailure: buffer
|
|
||||||
enabled: true
|
|
||||||
readoutPriority: async
|
|
||||||
softwareTrigger: false
|
|
||||||
#
|
|
||||||
# END FALCON with HDF5
|
|
||||||
#
|
|
||||||
#
|
|
||||||
# falcon without hdf5
|
|
||||||
#
|
|
||||||
falcon_hdf5:
|
|
||||||
description: Falcon detector x-ray fluoresence with hdf5 plugin from device class phoenix_bec.devices. falcon_phoenix.FalconPhoenix
|
|
||||||
deviceClass: phoenix_bec.devices.falcon_phoenix.FalconPhoenix
|
|
||||||
deviceConfig:
|
|
||||||
prefix: 'X07MB-SITORO:'
|
|
||||||
deviceTags:
|
|
||||||
- phoenix
|
|
||||||
- falcon
|
|
||||||
- with hdf5
|
|
||||||
- phoenix_falcon.yaml
|
|
||||||
onFailure: buffer
|
|
||||||
enabled: true
|
|
||||||
readoutPriority: async
|
|
||||||
softwareTrigger: false
|
|
||||||
#
|
|
||||||
# END FALCON with HDF5
|
|
||||||
#
|
|
||||||
#
|
|
||||||
# falcon without hdf5
|
|
||||||
#
|
|
||||||
falcon_hdf5:
|
|
||||||
description: Falcon detector x-ray fluoresence with hdf5 plugin from device class phoenix_bec.devices. falcon_phoenix.FalconPhoenix
|
|
||||||
deviceClass: phoenix_bec.devices.falcon_phoenix.FalconPhoenix
|
|
||||||
deviceConfig:
|
|
||||||
prefix: 'X07MB-SITORO:'
|
|
||||||
deviceTags:
|
|
||||||
- phoenix
|
|
||||||
- falcon
|
|
||||||
- with hdf5
|
|
||||||
- phoenix_falcon.yaml
|
|
||||||
onFailure: buffer
|
|
||||||
enabled: true
|
|
||||||
readoutPriority: async
|
|
||||||
softwareTrigger: false
|
|
||||||
#
|
|
||||||
# END FALCON with HDF5
|
|
||||||
#
|
|
||||||
#
|
|
||||||
# falcon without hdf5
|
|
||||||
#
|
|
||||||
falcon_hdf5:
|
|
||||||
description: Falcon detector x-ray fluoresence with hdf5 plugin from device class phoenix_bec.devices. falcon_phoenix.FalconPhoenix
|
|
||||||
deviceClass: phoenix_bec.devices.falcon_phoenix.FalconPhoenix
|
|
||||||
deviceConfig:
|
|
||||||
prefix: 'X07MB-SITORO:'
|
|
||||||
deviceTags:
|
|
||||||
- phoenix
|
|
||||||
- falcon
|
|
||||||
- with hdf5
|
|
||||||
- phoenix_falcon.yaml
|
|
||||||
onFailure: buffer
|
|
||||||
enabled: true
|
|
||||||
readoutPriority: async
|
|
||||||
softwareTrigger: false
|
|
||||||
#
|
|
||||||
# END FALCON with HDF5
|
|
||||||
#
|
|
||||||
#
|
|
||||||
# falcon without hdf5
|
|
||||||
#
|
|
||||||
falcon_hdf5:
|
|
||||||
description: Falcon detector x-ray fluoresence with hdf5 plugin from device class phoenix_bec.devices. falcon_phoenix.FalconPhoenix
|
|
||||||
deviceClass: phoenix_bec.devices.falcon_phoenix.FalconPhoenix
|
|
||||||
deviceConfig:
|
|
||||||
prefix: 'X07MB-SITORO:'
|
|
||||||
deviceTags:
|
|
||||||
- phoenix
|
|
||||||
- falcon
|
|
||||||
- with hdf5
|
|
||||||
- phoenix_falcon.yaml
|
|
||||||
onFailure: buffer
|
|
||||||
enabled: true
|
|
||||||
readoutPriority: async
|
|
||||||
softwareTrigger: false
|
|
||||||
#
|
|
||||||
# END FALCON with HDF5
|
|
||||||
#
|
|
||||||
#
|
|
||||||
# falcon without hdf5
|
|
||||||
#
|
|
||||||
falcon_hdf5:
|
|
||||||
description: Falcon detector x-ray fluoresence with hdf5 plugin from device class phoenix_bec.devices. falcon_phoenix.FalconPhoenix
|
|
||||||
deviceClass: phoenix_bec.devices.falcon_phoenix.FalconPhoenix
|
|
||||||
deviceConfig:
|
|
||||||
prefix: 'X07MB-SITORO:'
|
|
||||||
deviceTags:
|
|
||||||
- phoenix
|
|
||||||
- falcon
|
|
||||||
- with hdf5
|
|
||||||
- phoenix_falcon.yaml
|
|
||||||
onFailure: buffer
|
|
||||||
enabled: true
|
|
||||||
readoutPriority: async
|
|
||||||
softwareTrigger: false
|
|
||||||
#
|
|
||||||
# END FALCON with HDF5
|
|
||||||
#
|
|
||||||
#
|
|
||||||
# falcon without hdf5
|
|
||||||
#
|
|
||||||
falcon_hdf5:
|
|
||||||
description: Falcon detector x-ray fluoresence with hdf5 plugin from device class phoenix_bec.devices. falcon_phoenix.FalconPhoenix
|
|
||||||
deviceClass: phoenix_bec.devices.falcon_phoenix.FalconPhoenix
|
|
||||||
deviceConfig:
|
|
||||||
prefix: 'X07MB-SITORO:'
|
|
||||||
deviceTags:
|
|
||||||
- phoenix
|
|
||||||
- falcon
|
|
||||||
- with hdf5
|
|
||||||
- phoenix_falcon.yaml
|
|
||||||
onFailure: buffer
|
|
||||||
enabled: true
|
|
||||||
readoutPriority: async
|
|
||||||
softwareTrigger: false
|
|
||||||
#
|
|
||||||
# END FALCON with HDF5
|
|
||||||
#
|
|
@ -1,71 +1,36 @@
|
|||||||
|
|
||||||
PH_TTL:
|
|
||||||
description: PHOENIX TTL trigger
|
|
||||||
deviceClass: phoenix_bec.devices.phoenix_trigger.PhoenixTrigger
|
|
||||||
deviceConfig:
|
|
||||||
prefix: 'X07MB-OP2:'
|
|
||||||
deviceTags:
|
|
||||||
- phoenix
|
|
||||||
- TTL Trigger
|
|
||||||
- phoenix_devices.yaml
|
|
||||||
onFailure: buffer
|
|
||||||
enabled: true
|
|
||||||
readoutPriority: monitored
|
|
||||||
softwareTrigger: true
|
|
||||||
|
|
||||||
|
|
||||||
###################################################
|
###################################################
|
||||||
#
|
#
|
||||||
# phoenix standard devices (motors)
|
# phoenix standard devices (motors)
|
||||||
#
|
#
|
||||||
#
|
#
|
||||||
####################################################:
|
#####################################################
|
||||||
|
|
||||||
PH_Dummy:
|
|
||||||
description: Class to test functionality of PSI detector. reads/writes into X07MB-PC-PSCAN.P-P0D0"
|
|
||||||
deviceClass: phoenix_bec.devices.dummy_devices.Dummy_PSIDetector
|
|
||||||
deviceConfig:
|
|
||||||
prefix: 'X07MB-PC-PSCAN:'
|
|
||||||
name: 'Dummy_Detector_PSI_Detector'
|
|
||||||
deviceTags:
|
|
||||||
- phoenix
|
|
||||||
- phoenix_devices.yamllass
|
|
||||||
- reads channel X07MB-PC-PSCAN.P-P0D0
|
|
||||||
- Dummy class to test PSI detector c from DAQ GUI
|
|
||||||
onFailure: buffer
|
|
||||||
enabled: true
|
|
||||||
readoutPriority: monitored
|
|
||||||
softwareTrigger: false
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
############################
|
|
||||||
#
|
#
|
||||||
# MOTORS ES1
|
# MOTORS ES1
|
||||||
#
|
#
|
||||||
############################
|
|
||||||
|
|
||||||
ScanX:
|
ScanX:
|
||||||
readoutPriority: baseline
|
readoutPriority: baseline
|
||||||
description: 'Vertical sample position ES-MA1.ScanX'
|
description: 'Horizontal sample position'
|
||||||
deviceClass: ophyd.EpicsMotor
|
deviceClass: ophyd.EpicsMotor
|
||||||
deviceConfig:
|
deviceConfig:
|
||||||
prefix: 'X07MB-ES-MA1:ScanX'
|
prefix: 'X07MB-ES-MA1:ScanX'
|
||||||
deviceTags:
|
deviceTags:
|
||||||
- ES-MA1.ScanX
|
- ES-MA1
|
||||||
- phoenix_bec/device_configs/phoenix_devices.yaml
|
- phoenix_bec/device_configs/phoenix_devices.yaml
|
||||||
onFailure: retry
|
onFailure: retry
|
||||||
enabled: true
|
enabled: true
|
||||||
readOnly: false
|
readOnly: false
|
||||||
|
softwareTrigger: false
|
||||||
|
|
||||||
ScanY:
|
ScanY:
|
||||||
readoutPriority: baseline
|
readoutPriority: baseline
|
||||||
description: 'Horizontal sample position ES-MA1.ScanY'
|
description: 'Horizontal sample position'
|
||||||
deviceClass: ophyd.EpicsMotor
|
deviceClass: ophyd.EpicsMotor
|
||||||
deviceConfig:
|
deviceConfig:
|
||||||
prefix: 'X07MB-ES-MA1:ScanY'
|
prefix: 'X07MB-ES-MA1:ScanY'
|
||||||
deviceTags:
|
deviceTags:
|
||||||
- ES-MA1.ScanY
|
- ES-MA1
|
||||||
- phoenix_bec/device_configs/phoenix_devices.yaml
|
- phoenix_bec/device_configs/phoenix_devices.yaml
|
||||||
onFailure: retry
|
onFailure: retry
|
||||||
enabled: true
|
enabled: true
|
||||||
@ -79,7 +44,7 @@ ScanY:
|
|||||||
|
|
||||||
SAI_07_MEAN:
|
SAI_07_MEAN:
|
||||||
readoutPriority: monitored
|
readoutPriority: monitored
|
||||||
description: DIODE SAI07
|
description: DIODE
|
||||||
deviceClass: ophyd.EpicsSignalRO
|
deviceClass: ophyd.EpicsSignalRO
|
||||||
deviceConfig:
|
deviceConfig:
|
||||||
auto_monitor: true
|
auto_monitor: true
|
||||||
@ -106,7 +71,3 @@ SAI_08_MEAN:
|
|||||||
enabled: true
|
enabled: true
|
||||||
readOnly: true
|
readOnly: true
|
||||||
softwareTrigger: false
|
softwareTrigger: false
|
||||||
|
|
||||||
#
|
|
||||||
# END OF STANDARD CONFIG
|
|
||||||
#
|
|
||||||
|
@ -1,21 +1,14 @@
|
|||||||
|
falcon_nohdf5:
|
||||||
#
|
description: Falcon detector x-ray fluoresence II
|
||||||
# falcon without hdf5
|
deviceClass: phoenix_bec.devices.falcon_phoenix_no_hdf5.FalconcSAXS
|
||||||
#
|
|
||||||
falcon_hdf5:
|
|
||||||
description: Falcon detector x-ray fluoresence with hdf5 plugin from device class phoenix_bec.devices. falcon_phoenix.FalconPhoenix
|
|
||||||
deviceClass: phoenix_bec.devices.falcon_phoenix.FalconPhoenix
|
|
||||||
deviceConfig:
|
deviceConfig:
|
||||||
prefix: 'X07MB-SITORO:'
|
prefix: 'X07MB-SITORO:'
|
||||||
deviceTags:
|
deviceTags:
|
||||||
- phoenix
|
- phoenix
|
||||||
- falcon
|
- falcon
|
||||||
- with hdf5
|
- no hdf5
|
||||||
- phoenix_falcon.yaml
|
- phoenix_devices.yaml
|
||||||
onFailure: buffer
|
onFailure: buffer
|
||||||
enabled: true
|
enabled: true
|
||||||
readoutPriority: async
|
readoutPriority: async
|
||||||
softwareTrigger: false
|
softwareTrigger: false
|
||||||
#
|
|
||||||
# END FALCON with HDF5
|
|
||||||
#
|
|
@ -1,17 +0,0 @@
|
|||||||
#
|
|
||||||
# falcon without hdf5
|
|
||||||
#
|
|
||||||
falcon_nohdf5:
|
|
||||||
description: Falcon detector x-ray fluoresence II
|
|
||||||
deviceClass: phoenix_bec.devices.falcon_phoenix_no_hdf5.FalconPhoenix
|
|
||||||
deviceConfig:
|
|
||||||
prefix: 'X07MB-SITORO:'
|
|
||||||
deviceTags:
|
|
||||||
- phoenix
|
|
||||||
- falcon
|
|
||||||
- no hdf5
|
|
||||||
- phoenix_devices.yaml
|
|
||||||
onFailure: buffer
|
|
||||||
enabled: true
|
|
||||||
readoutPriority: async
|
|
||||||
softwareTrigger: false
|
|
@ -1,10 +1,6 @@
|
|||||||
#
|
|
||||||
# Configuration XMAP without hdf5
|
|
||||||
#
|
|
||||||
|
|
||||||
xmap_nohdf5:
|
xmap_nohdf5:
|
||||||
description: XMAP detector x-ray fluoresence II
|
description: XMAP detector x-ray fluoresence II
|
||||||
deviceClass: phoenix_bec.devices.xmap_phoenix_no_hdf5.XMAPPhoenix
|
deviceClass: phoenix_bec.devices.xmap_phoenix_no_hdf5.XMAPphoenix
|
||||||
deviceConfig:
|
deviceConfig:
|
||||||
prefix: 'X07MB-XMAP:'
|
prefix: 'X07MB-XMAP:'
|
||||||
deviceTags:
|
deviceTags:
|
||||||
|
243
phoenix_bec/devices/Phoenix_trigger.py
Normal file
243
phoenix_bec/devices/Phoenix_trigger.py
Normal file
@ -0,0 +1,243 @@
|
|||||||
|
from ophyd import (
|
||||||
|
ADComponent as ADCpt,
|
||||||
|
Device,
|
||||||
|
DeviceStatus,
|
||||||
|
)
|
||||||
|
|
||||||
|
from ophyd import Component as Cpt
|
||||||
|
from ophyd import Device, EpicsSignal, EpicsSignalRO
|
||||||
|
|
||||||
|
from ophyd_devices.interfaces.base_classes.psi_detector_base import PSIDetectorBase, CustomDetectorMixin
|
||||||
|
|
||||||
|
from bec_lib import bec_logger, messages
|
||||||
|
from bec_lib.endpoints import MessageEndpoints
|
||||||
|
|
||||||
|
logger = bec_logger.logger
|
||||||
|
|
||||||
|
DETECTOR_TIMEOUT = 5
|
||||||
|
|
||||||
|
class PhoenixTriggerError(Exception):
|
||||||
|
"""Base class for exceptions in this module."""
|
||||||
|
|
||||||
|
|
||||||
|
class PhoenixTriggerTimeoutError(XMAPError):
|
||||||
|
"""Raised when the PhoenixTrigger does not respond in time."""
|
||||||
|
|
||||||
|
|
||||||
|
class PhoenixTriggerDetectorState(enum.IntEnum):
|
||||||
|
"""Detector states for XMAP detector"""
|
||||||
|
|
||||||
|
DONE = 0
|
||||||
|
ACQUIRING = 1
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class PhoenixTriggerSetup(CustomDetectorMixin):
|
||||||
|
"""
|
||||||
|
This defines the trigger setup.
|
||||||
|
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, *args, parent:Device = None, **kwargs):
|
||||||
|
super().__init__(*args, parent=parent, **kwargs)
|
||||||
|
self._counter = 0
|
||||||
|
|
||||||
|
def on_stage(self):
|
||||||
|
exposure_time = self.parent.scaninfo.exp_time
|
||||||
|
num_points = self.parent.scaninfo.num_points
|
||||||
|
|
||||||
|
# camera acquisition parameters
|
||||||
|
self.parent.cam.array_counter.put(0)
|
||||||
|
if self.parent.scaninfo.scan_type == 'step':
|
||||||
|
self.parent.cam.acquire_time.put(exposure_time)
|
||||||
|
self.parent.cam.num_images.put(1)
|
||||||
|
self.parent.cam.image_mode.put(0) # Single
|
||||||
|
self.parent.cam.trigger_mode.put(0) # auto
|
||||||
|
else:
|
||||||
|
# In flyscan, the exp_time is the time between two triggers,
|
||||||
|
# which minus 15% is used as the acquisition time.
|
||||||
|
self.parent.cam.acquire_time.put(exposure_time * 0.85)
|
||||||
|
self.parent.cam.num_images.put(num_points)
|
||||||
|
self.parent.cam.image_mode.put(1) # Multiple
|
||||||
|
self.parent.cam.trigger_mode.put(1) # trigger
|
||||||
|
self.parent.cam.acquire.put(1, wait=False) # arm
|
||||||
|
|
||||||
|
# file writer
|
||||||
|
self.parent.hdf.lazy_open.put(1)
|
||||||
|
self.parent.hdf.num_capture.put(num_points)
|
||||||
|
self.parent.hdf.file_write_mode.put(2) # Stream
|
||||||
|
self.parent.hdf.capture.put(1, wait=False)
|
||||||
|
self.parent.hdf.enable.put(1) # enable plugin
|
||||||
|
|
||||||
|
# roi statistics to collect signal and background in a timeseries
|
||||||
|
self.parent.roistat.enable.put(1)
|
||||||
|
self.parent.roistat.ts_num_points.put(num_points)
|
||||||
|
self.parent.roistat.ts_control.put(0, wait=False) # Erase/Start
|
||||||
|
|
||||||
|
logger.success('XXXX stage XXXX')
|
||||||
|
|
||||||
|
def on_trigger(self):
|
||||||
|
self.parent.cam.acquire.put(1, wait=False)
|
||||||
|
logger.success('XXXX trigger XXXX')
|
||||||
|
|
||||||
|
return self.wait_with_status(
|
||||||
|
[(self.parent.cam.acquire.get, 0)],
|
||||||
|
self.parent.scaninfo.exp_time + DETECTOR_TIMEOUT,
|
||||||
|
all_signals=True
|
||||||
|
)
|
||||||
|
|
||||||
|
def on_complete(self):
|
||||||
|
status = DeviceStatus(self.parent)
|
||||||
|
|
||||||
|
if self.parent.scaninfo.scan_type == 'step':
|
||||||
|
timeout = DETECTOR_TIMEOUT
|
||||||
|
else:
|
||||||
|
timeout = self.parent.scaninfo.exp_time * self.parent.scaninfo.num_points + DETECTOR_TIMEOUT
|
||||||
|
logger.success('XXXX %s XXXX' % self.parent.roistat.ts_acquiring.get())
|
||||||
|
success = self.wait_for_signals(
|
||||||
|
[
|
||||||
|
(self.parent.cam.acquire.get, 0),
|
||||||
|
(self.parent.hdf.capture.get, 0),
|
||||||
|
(self.parent.roistat.ts_acquiring.get, 'Done')
|
||||||
|
],
|
||||||
|
timeout,
|
||||||
|
check_stopped=True,
|
||||||
|
all_signals=True
|
||||||
|
)
|
||||||
|
|
||||||
|
# publish file location
|
||||||
|
self.parent.filepath.put(self.parent.hdf.full_file_name.get())
|
||||||
|
self.publish_file_location(done=True, successful=success)
|
||||||
|
|
||||||
|
# publish timeseries data
|
||||||
|
metadata = self.parent.scaninfo.scan_msg.metadata
|
||||||
|
metadata.update({"async_update": "append", "num_lines": self.parent.roistat.ts_current_point.get()})
|
||||||
|
msg = messages.DeviceMessage(
|
||||||
|
signals={
|
||||||
|
self.parent.roistat.roi1.name_.get(): {
|
||||||
|
'value': self.parent.roistat.roi1.ts_total.get(),
|
||||||
|
},
|
||||||
|
self.parent.roistat.roi2.name_.get(): {
|
||||||
|
'value': self.parent.roistat.roi2.ts_total.get(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
metadata=self.parent.scaninfo.scan_msg.metadata
|
||||||
|
)
|
||||||
|
self.parent.connector.xadd(
|
||||||
|
topic=MessageEndpoints.device_async_readback(
|
||||||
|
scan_id=self.parent.scaninfo.scan_id, device=self.parent.name
|
||||||
|
),
|
||||||
|
msg_dict={"data": msg},
|
||||||
|
expire=1800,
|
||||||
|
)
|
||||||
|
|
||||||
|
logger.success('XXXX complete %d XXXX' % success)
|
||||||
|
if success:
|
||||||
|
status.set_finished()
|
||||||
|
else:
|
||||||
|
status.set_exception(TimeoutError())
|
||||||
|
return status
|
||||||
|
|
||||||
|
def on_stop(self):
|
||||||
|
logger.success('XXXX stop XXXX')
|
||||||
|
self.parent.cam.acquire.put(0)
|
||||||
|
self.parent.hdf.capture.put(0)
|
||||||
|
self.parent.roistat.ts_control.put(2)
|
||||||
|
|
||||||
|
def on_unstage(self):
|
||||||
|
self.parent.cam.acquire.put(0)
|
||||||
|
self.parent.hdf.capture.put(0)
|
||||||
|
self.parent.roistat.ts_control.put(2)
|
||||||
|
logger.success('XXXX unstage XXXX')
|
||||||
|
|
||||||
|
|
||||||
|
class EigerROIStatPlugin(ROIStatPlugin):
|
||||||
|
roi1 = ADCpt(ROIStatNPlugin, '1:')
|
||||||
|
roi2 = ADCpt(ROIStatNPlugin, '2:')
|
||||||
|
|
||||||
|
class PhoenixTrigger(PSIDetectorBase):
|
||||||
|
|
||||||
|
"""
|
||||||
|
Parent class: PSIDetectorBase
|
||||||
|
|
||||||
|
class attributes:
|
||||||
|
custom_prepare_cls (XMAPSetup) : Custom detector setup class for cSAXS,
|
||||||
|
inherits from CustomDetectorMixin
|
||||||
|
in __init__ of PSIDetecor bases
|
||||||
|
class is initialized
|
||||||
|
self.custom_prepare = self.custom_prepare_cls(parent=self, **kwargs)
|
||||||
|
PSIDetectorBase.set_min_readout (float) : Minimum readout time for the detector
|
||||||
|
dxp (EpicsDXPXMAP) : DXP parameters for XMAP detector
|
||||||
|
mca (EpicsMCARecord) : MCA parameters for XMAP detector
|
||||||
|
hdf5 (XMAPHDF5Plugins) : HDF5 parameters for XMAP detector
|
||||||
|
MIN_READOUT (float) : Minimum readout time for the detector
|
||||||
|
|
||||||
|
|
||||||
|
The class PhoenixTrigger is the class to be called via yaml configuration file
|
||||||
|
the input arguments are defined by PSIDetectorBase,
|
||||||
|
and need to be given in the yaml configuration file.
|
||||||
|
To adress chanels such as 'X07MB-OP2:SMPL-DONE':
|
||||||
|
|
||||||
|
use prefix 'X07MB-OP2:' in the device definition in the yaml configuration file.
|
||||||
|
|
||||||
|
PSIDetectorBase(
|
||||||
|
prefix='',
|
||||||
|
*,Q
|
||||||
|
name,
|
||||||
|
kind=None,
|
||||||
|
parent=None,
|
||||||
|
device_manager=None,
|
||||||
|
**kwargs,
|
||||||
|
)
|
||||||
|
Docstring:
|
||||||
|
Abstract base class for SLS detectors
|
||||||
|
|
||||||
|
Class attributes:
|
||||||
|
custom_prepare_cls (object): class for custom prepare logic (BL specific)
|
||||||
|
|
||||||
|
Args:
|
||||||
|
prefix (str): EPICS PV prefix for component (optional)
|
||||||
|
name (str): name of the device, as will be reported via read()
|
||||||
|
kind (str): member of class 'ophydobj.Kind', defaults to Kind.normal
|
||||||
|
omitted -> readout ignored for read 'ophydobj.read()'
|
||||||
|
normal -> readout for read
|
||||||
|
config -> config parameter for 'ophydobj.read_configuration()'
|
||||||
|
hinted -> which attribute is readout for read
|
||||||
|
parent (object): instance of the parent device
|
||||||
|
device_manager (object): bec device manager
|
||||||
|
**kwargs: keyword arguments
|
||||||
|
File: /data/test/x07mb-test-bec/bec_deployment/ophyd_devices/ophyd_devices/interfaces/base_classes/psi_detector_base.py
|
||||||
|
Type: type
|
||||||
|
Subclasses: SimCamera, SimMonitorAsync
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
"""
|
||||||
|
#custom_prepare_cls = PhoenixTriggerSetup
|
||||||
|
|
||||||
|
#cam = ADCpt(SLSDetectorCam, 'cam1:')
|
||||||
|
|
||||||
|
|
||||||
|
#X07MB-OP2:START-CSMPL.. cont on / off
|
||||||
|
|
||||||
|
# X07MB-OP2:SMPL.. take single sample
|
||||||
|
#X07MB-OP2:INTR-COUNT.. counter run up
|
||||||
|
#X07MB-OP2:TOTAL-CYCLES .. cycles set
|
||||||
|
#X07MB-OP2:SMPL-DONE
|
||||||
|
|
||||||
|
QUESTION HOW does ADCpt kno the EPICS prefix??????
|
||||||
|
|
||||||
|
#image = ADCpt(ImagePlugin, 'image1:')
|
||||||
|
#roi1 = ADCpt(ROIPlugin, 'ROI1:')
|
||||||
|
#roi2 = ADCpt(ROIPlugin, 'ROI2:')
|
||||||
|
#stats1 = ADCpt(StatsPlugin, 'Stats1:')
|
||||||
|
#stats2 = ADCpt(StatsPlugin, 'Stats2:')
|
||||||
|
roistat = ADCpt(EigerROIStatPlugin, 'ROIStat1:')
|
||||||
|
#roistat1 = ADCpt(ROIStatNPlugin, 'ROIStat1:1:')
|
||||||
|
#roistat2 = ADCpt(ROIStatNPlugin, 'ROIStat1:2:')
|
||||||
|
hdf = ADCpt(HDF5Plugin, 'HDF1:')
|
||||||
|
)
|
@ -1 +1 @@
|
|||||||
from .phoenix_trigger import PhoenixTrigger
|
from .falcon_phoenix_no_hdf5 import FalconcSAXS
|
||||||
|
345
phoenix_bec/devices/delay_generator_csaxs.py
Normal file
345
phoenix_bec/devices/delay_generator_csaxs.py
Normal file
@ -0,0 +1,345 @@
|
|||||||
|
from bec_lib import bec_logger
|
||||||
|
from ophyd import Component
|
||||||
|
from ophyd_devices.interfaces.base_classes.psi_delay_generator_base import (
|
||||||
|
DDGCustomMixin,
|
||||||
|
PSIDelayGeneratorBase,
|
||||||
|
TriggerSource,
|
||||||
|
)
|
||||||
|
from ophyd_devices.utils import bec_utils
|
||||||
|
|
||||||
|
logger = bec_logger.logger
|
||||||
|
|
||||||
|
|
||||||
|
class DelayGeneratorError(Exception):
|
||||||
|
"""Exception raised for errors."""
|
||||||
|
|
||||||
|
|
||||||
|
class DDGSetup(DDGCustomMixin):
|
||||||
|
"""
|
||||||
|
Mixin class for DelayGenerator logic at cSAXS.
|
||||||
|
|
||||||
|
At cSAXS, multiple DDGs were operated at the same time. There different behaviour is
|
||||||
|
implemented in the ddg_config signals that are passed via the device config.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def initialize_default_parameter(self) -> None:
|
||||||
|
"""Method to initialize default parameters."""
|
||||||
|
for ii, channel in enumerate(self.parent.all_channels):
|
||||||
|
self.parent.set_channels("polarity", self.parent.polarity.get()[ii], [channel])
|
||||||
|
|
||||||
|
self.parent.set_channels("amplitude", self.parent.amplitude.get())
|
||||||
|
self.parent.set_channels("offset", self.parent.offset.get())
|
||||||
|
# Setup reference
|
||||||
|
self.parent.set_channels(
|
||||||
|
"reference", 0, [f"channel{pair}.ch1" for pair in self.parent.all_delay_pairs]
|
||||||
|
)
|
||||||
|
self.parent.set_channels(
|
||||||
|
"reference", 0, [f"channel{pair}.ch2" for pair in self.parent.all_delay_pairs]
|
||||||
|
)
|
||||||
|
self.parent.set_trigger(getattr(TriggerSource, self.parent.set_trigger_source.get()))
|
||||||
|
# Set threshold level for ext. pulses
|
||||||
|
self.parent.level.put(self.parent.thres_trig_level.get())
|
||||||
|
|
||||||
|
def prepare_ddg(self) -> None:
|
||||||
|
"""
|
||||||
|
Method to prepare scan logic of cSAXS
|
||||||
|
|
||||||
|
Two scantypes are supported: "step" and "fly":
|
||||||
|
- step: Scan is performed by stepping the motor and acquiring data at each step
|
||||||
|
- fly: Scan is performed by moving the motor with a constant velocity and acquiring data
|
||||||
|
|
||||||
|
Custom logic for different DDG behaviour during scans.
|
||||||
|
|
||||||
|
- set_high_on_exposure : If True, then TTL signal is high during
|
||||||
|
the full exposure time of the scan (all frames).
|
||||||
|
E.g. Keep shutter open for the full scan.
|
||||||
|
- fixed_ttl_width : fixed_ttl_width is a list of 5 values, one for each channel.
|
||||||
|
If the value is 0, then the width of the TTL pulse is determined,
|
||||||
|
no matter which parameters are passed from the scaninfo for exposure time
|
||||||
|
- set_trigger_source : Specifies the default trigger source for the DDG. For cSAXS, relevant ones
|
||||||
|
were: SINGLE_SHOT, EXT_RISING_EDGE
|
||||||
|
"""
|
||||||
|
self.parent.set_trigger(getattr(TriggerSource, self.parent.set_trigger_source.get()))
|
||||||
|
# scantype "step"
|
||||||
|
if self.parent.scaninfo.scan_type == "step":
|
||||||
|
# High on exposure means that the signal
|
||||||
|
if self.parent.set_high_on_exposure.get():
|
||||||
|
# caluculate parameters
|
||||||
|
num_burst_cycle = 1 + self.parent.additional_triggers.get()
|
||||||
|
|
||||||
|
exp_time = (
|
||||||
|
self.parent.delta_width.get()
|
||||||
|
+ self.parent.scaninfo.frames_per_trigger
|
||||||
|
* (self.parent.scaninfo.exp_time + self.parent.scaninfo.readout_time)
|
||||||
|
)
|
||||||
|
total_exposure = exp_time
|
||||||
|
delay_burst = self.parent.delay_burst.get()
|
||||||
|
|
||||||
|
# Set individual channel widths, if fixed_ttl_width and trigger_width are combined, this can be a common call too
|
||||||
|
if not self.parent.trigger_width.get():
|
||||||
|
self.parent.set_channels("width", exp_time)
|
||||||
|
else:
|
||||||
|
self.parent.set_channels("width", self.parent.trigger_width.get())
|
||||||
|
for value, channel in zip(
|
||||||
|
self.parent.fixed_ttl_width.get(), self.parent.all_channels
|
||||||
|
):
|
||||||
|
logger.debug(f"Trying to set DDG {channel} to {value}")
|
||||||
|
if value != 0:
|
||||||
|
self.parent.set_channels("width", value, channels=[channel])
|
||||||
|
else:
|
||||||
|
# caluculate parameters
|
||||||
|
exp_time = self.parent.delta_width.get() + self.parent.scaninfo.exp_time
|
||||||
|
total_exposure = exp_time + self.parent.scaninfo.readout_time
|
||||||
|
delay_burst = self.parent.delay_burst.get()
|
||||||
|
num_burst_cycle = (
|
||||||
|
self.parent.scaninfo.frames_per_trigger + self.parent.additional_triggers.get()
|
||||||
|
)
|
||||||
|
|
||||||
|
# Set individual channel widths, if fixed_ttl_width and trigger_width are combined, this can be a common call too
|
||||||
|
if not self.parent.trigger_width.get():
|
||||||
|
self.parent.set_channels("width", exp_time)
|
||||||
|
else:
|
||||||
|
self.parent.set_channels("width", self.parent.trigger_width.get())
|
||||||
|
# scantype "fly"
|
||||||
|
elif self.parent.scaninfo.scan_type == "fly":
|
||||||
|
if self.parent.set_high_on_exposure.get():
|
||||||
|
# caluculate parameters
|
||||||
|
exp_time = (
|
||||||
|
self.parent.delta_width.get()
|
||||||
|
+ self.parent.scaninfo.exp_time * self.parent.scaninfo.num_points
|
||||||
|
+ self.parent.scaninfo.readout_time * (self.parent.scaninfo.num_points - 1)
|
||||||
|
)
|
||||||
|
total_exposure = exp_time
|
||||||
|
delay_burst = self.parent.delay_burst.get()
|
||||||
|
num_burst_cycle = 1 + self.parent.additional_triggers.get()
|
||||||
|
|
||||||
|
# Set individual channel widths, if fixed_ttl_width and trigger_width are combined, this can be a common call too
|
||||||
|
if not self.parent.trigger_width.get():
|
||||||
|
self.parent.set_channels("width", exp_time)
|
||||||
|
else:
|
||||||
|
self.parent.set_channels("width", self.parent.trigger_width.get())
|
||||||
|
for value, channel in zip(
|
||||||
|
self.parent.fixed_ttl_width.get(), self.parent.all_channels
|
||||||
|
):
|
||||||
|
logger.debug(f"Trying to set DDG {channel} to {value}")
|
||||||
|
if value != 0:
|
||||||
|
self.parent.set_channels("width", value, channels=[channel])
|
||||||
|
else:
|
||||||
|
# caluculate parameters
|
||||||
|
exp_time = self.parent.delta_width.get() + self.parent.scaninfo.exp_time
|
||||||
|
total_exposure = exp_time + self.parent.scaninfo.readout_time
|
||||||
|
delay_burst = self.parent.delay_burst.get()
|
||||||
|
num_burst_cycle = (
|
||||||
|
self.parent.scaninfo.num_points + self.parent.additional_triggers.get()
|
||||||
|
)
|
||||||
|
|
||||||
|
# Set individual channel widths, if fixed_ttl_width and trigger_width are combined, this can be a common call too
|
||||||
|
if not self.parent.trigger_width.get():
|
||||||
|
self.parent.set_channels("width", exp_time)
|
||||||
|
else:
|
||||||
|
self.parent.set_channels("width", self.parent.trigger_width.get())
|
||||||
|
|
||||||
|
else:
|
||||||
|
raise Exception(f"Unknown scan type {self.parent.scaninfo.scan_type}")
|
||||||
|
# Set common DDG parameters
|
||||||
|
self.parent.burst_enable(num_burst_cycle, delay_burst, total_exposure, config="first")
|
||||||
|
self.parent.set_channels("delay", 0.0)
|
||||||
|
|
||||||
|
def on_trigger(self) -> None:
|
||||||
|
"""Method to be executed upon trigger"""
|
||||||
|
if self.parent.source.read()[self.parent.source.name]["value"] == TriggerSource.SINGLE_SHOT:
|
||||||
|
self.parent.trigger_shot.put(1)
|
||||||
|
|
||||||
|
def check_scan_id(self) -> None:
|
||||||
|
"""
|
||||||
|
Method to check if scan_id has changed.
|
||||||
|
|
||||||
|
If yes, then it changes parent.stopped to True, which will stop further actions.
|
||||||
|
"""
|
||||||
|
old_scan_id = self.parent.scaninfo.scan_id
|
||||||
|
self.parent.scaninfo.load_scan_metadata()
|
||||||
|
if self.parent.scaninfo.scan_id != old_scan_id:
|
||||||
|
self.parent.stopped = True
|
||||||
|
|
||||||
|
def finished(self) -> None:
|
||||||
|
"""Method checks if DDG finished acquisition"""
|
||||||
|
|
||||||
|
def on_pre_scan(self) -> None:
|
||||||
|
"""
|
||||||
|
Method called by pre_scan hook in parent class.
|
||||||
|
|
||||||
|
Executes trigger if premove_trigger is Trus.
|
||||||
|
"""
|
||||||
|
if self.parent.premove_trigger.get() is True:
|
||||||
|
self.parent.trigger_shot.put(1)
|
||||||
|
|
||||||
|
|
||||||
|
class DelayGeneratorcSAXS(PSIDelayGeneratorBase):
|
||||||
|
"""
|
||||||
|
DG645 delay generator at cSAXS (multiple can be in use depending on the setup)
|
||||||
|
|
||||||
|
Default values for setting up DDG.
|
||||||
|
Note: checks of set calues are not (only partially) included, check manual for details on possible settings.
|
||||||
|
https://www.thinksrs.com/downloads/pdfs/manuals/DG645m.pdf
|
||||||
|
|
||||||
|
- delay_burst : (float >=0) Delay between trigger and first pulse in burst mode
|
||||||
|
- delta_width : (float >= 0) Add width to fast shutter signal to make sure its open during acquisition
|
||||||
|
- additional_triggers : (int) add additional triggers to burst mode (mcs card needs +1 triggers per line)
|
||||||
|
- polarity : (list of 0/1) polarity for different channels
|
||||||
|
- amplitude : (float) amplitude voltage of TTLs
|
||||||
|
- offset : (float) offset for ampltitude
|
||||||
|
- thres_trig_level : (float) threshold of trigger amplitude
|
||||||
|
|
||||||
|
Custom signals for logic in different DDGs during scans (for custom_prepare.prepare_ddg):
|
||||||
|
|
||||||
|
- set_high_on_exposure : (bool): if True, then TTL signal should go high during the full acquisition time of a scan.
|
||||||
|
# TODO trigger_width and fixed_ttl could be combined into single list.
|
||||||
|
- fixed_ttl_width : (list of either 1 or 0), one for each channel.
|
||||||
|
- trigger_width : (float) if fixed_ttl_width is True, then the width of the TTL pulse is set to this value.
|
||||||
|
- set_trigger_source : (TriggerSource) specifies the default trigger source for the DDG.
|
||||||
|
- premove_trigger : (bool) if True, then a trigger should be executed before the scan starts (to be implemented in on_pre_scan).
|
||||||
|
- set_high_on_stage : (bool) if True, then TTL signal should go high already on stage.
|
||||||
|
"""
|
||||||
|
|
||||||
|
custom_prepare_cls = DDGSetup
|
||||||
|
|
||||||
|
delay_burst = Component(
|
||||||
|
bec_utils.ConfigSignal, name="delay_burst", kind="config", config_storage_name="ddg_config"
|
||||||
|
)
|
||||||
|
|
||||||
|
delta_width = Component(
|
||||||
|
bec_utils.ConfigSignal, name="delta_width", kind="config", config_storage_name="ddg_config"
|
||||||
|
)
|
||||||
|
|
||||||
|
additional_triggers = Component(
|
||||||
|
bec_utils.ConfigSignal,
|
||||||
|
name="additional_triggers",
|
||||||
|
kind="config",
|
||||||
|
config_storage_name="ddg_config",
|
||||||
|
)
|
||||||
|
|
||||||
|
polarity = Component(
|
||||||
|
bec_utils.ConfigSignal, name="polarity", kind="config", config_storage_name="ddg_config"
|
||||||
|
)
|
||||||
|
|
||||||
|
fixed_ttl_width = Component(
|
||||||
|
bec_utils.ConfigSignal,
|
||||||
|
name="fixed_ttl_width",
|
||||||
|
kind="config",
|
||||||
|
config_storage_name="ddg_config",
|
||||||
|
)
|
||||||
|
|
||||||
|
amplitude = Component(
|
||||||
|
bec_utils.ConfigSignal, name="amplitude", kind="config", config_storage_name="ddg_config"
|
||||||
|
)
|
||||||
|
|
||||||
|
offset = Component(
|
||||||
|
bec_utils.ConfigSignal, name="offset", kind="config", config_storage_name="ddg_config"
|
||||||
|
)
|
||||||
|
|
||||||
|
thres_trig_level = Component(
|
||||||
|
bec_utils.ConfigSignal,
|
||||||
|
name="thres_trig_level",
|
||||||
|
kind="config",
|
||||||
|
config_storage_name="ddg_config",
|
||||||
|
)
|
||||||
|
|
||||||
|
set_high_on_exposure = Component(
|
||||||
|
bec_utils.ConfigSignal,
|
||||||
|
name="set_high_on_exposure",
|
||||||
|
kind="config",
|
||||||
|
config_storage_name="ddg_config",
|
||||||
|
)
|
||||||
|
|
||||||
|
set_high_on_stage = Component(
|
||||||
|
bec_utils.ConfigSignal,
|
||||||
|
name="set_high_on_stage",
|
||||||
|
kind="config",
|
||||||
|
config_storage_name="ddg_config",
|
||||||
|
)
|
||||||
|
|
||||||
|
set_trigger_source = Component(
|
||||||
|
bec_utils.ConfigSignal,
|
||||||
|
name="set_trigger_source",
|
||||||
|
kind="config",
|
||||||
|
config_storage_name="ddg_config",
|
||||||
|
)
|
||||||
|
|
||||||
|
trigger_width = Component(
|
||||||
|
bec_utils.ConfigSignal,
|
||||||
|
name="trigger_width",
|
||||||
|
kind="config",
|
||||||
|
config_storage_name="ddg_config",
|
||||||
|
)
|
||||||
|
premove_trigger = Component(
|
||||||
|
bec_utils.ConfigSignal,
|
||||||
|
name="premove_trigger",
|
||||||
|
kind="config",
|
||||||
|
config_storage_name="ddg_config",
|
||||||
|
)
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
prefix="",
|
||||||
|
*,
|
||||||
|
name,
|
||||||
|
kind=None,
|
||||||
|
read_attrs=None,
|
||||||
|
configuration_attrs=None,
|
||||||
|
parent=None,
|
||||||
|
device_manager=None,
|
||||||
|
sim_mode=False,
|
||||||
|
ddg_config=None,
|
||||||
|
**kwargs,
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
Args:
|
||||||
|
prefix (str, optional): Prefix of the device. Defaults to "".
|
||||||
|
name (str): Name of the device.
|
||||||
|
kind (str, optional): Kind of the device. Defaults to None.
|
||||||
|
read_attrs (list, optional): List of attributes to read. Defaults to None.
|
||||||
|
configuration_attrs (list, optional): List of attributes to configure. Defaults to None.
|
||||||
|
parent (Device, optional): Parent device. Defaults to None.
|
||||||
|
device_manager (DeviceManagerBase, optional): DeviceManagerBase object. Defaults to None.
|
||||||
|
sim_mode (bool, optional): Simulation mode flag. Defaults to False.
|
||||||
|
ddg_config (dict, optional): Dictionary of ddg_config signals. Defaults to None.
|
||||||
|
|
||||||
|
"""
|
||||||
|
# Default values for ddg_config signals
|
||||||
|
self.ddg_config = {
|
||||||
|
# Setup default values
|
||||||
|
f"{name}_delay_burst": 0,
|
||||||
|
f"{name}_delta_width": 0,
|
||||||
|
f"{name}_additional_triggers": 0,
|
||||||
|
f"{name}_polarity": [1, 1, 1, 1, 1],
|
||||||
|
f"{name}_amplitude": 4.5,
|
||||||
|
f"{name}_offset": 0,
|
||||||
|
f"{name}_thres_trig_level": 2.5,
|
||||||
|
# Values for different behaviour during scans
|
||||||
|
f"{name}_fixed_ttl_width": [0, 0, 0, 0, 0],
|
||||||
|
f"{name}_trigger_width": None,
|
||||||
|
f"{name}_set_high_on_exposure": False,
|
||||||
|
f"{name}_set_high_on_stage": False,
|
||||||
|
f"{name}_set_trigger_source": "SINGLE_SHOT",
|
||||||
|
f"{name}_premove_trigger": False,
|
||||||
|
}
|
||||||
|
if ddg_config is not None:
|
||||||
|
# pylint: disable=expression-not-assigned
|
||||||
|
[self.ddg_config.update({f"{name}_{key}": value}) for key, value in ddg_config.items()]
|
||||||
|
super().__init__(
|
||||||
|
prefix=prefix,
|
||||||
|
name=name,
|
||||||
|
kind=kind,
|
||||||
|
read_attrs=read_attrs,
|
||||||
|
configuration_attrs=configuration_attrs,
|
||||||
|
parent=parent,
|
||||||
|
device_manager=device_manager,
|
||||||
|
sim_mode=sim_mode,
|
||||||
|
**kwargs,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
# Start delay generator in simulation mode.
|
||||||
|
# Note: To run, access to Epics must be available.
|
||||||
|
dgen = DelayGeneratorcSAXS("delaygen:DG1:", name="dgen", sim_mode=True)
|
@ -3,30 +3,3 @@
|
|||||||
### phoenix_bec
|
### phoenix_bec
|
||||||
| Device | Documentation | Module |
|
| Device | Documentation | Module |
|
||||||
| :----- | :------------- | :------ |
|
| :----- | :------------- | :------ |
|
||||||
| Dummy_PSIDetector | <br> Abstract base class for SLS detectors<br><br> Class attributes:<br> custom_prepare_cls (object): class for custom prepare logic (BL specific)<br><br> Args:<br> prefix (str): EPICS PV prefix for component (optional)<br> name (str): name of the device, as will be reported via read()<br> kind (str): member of class 'ophydobj.Kind', defaults to Kind.normal<br> omitted -> reado_PSIDetectorBase<br> | [phoenix_bec.devices.dummy_devices](https://gitlab.psi.ch/bec/phoenix_bec/-/blob/main/phoenix_bec/devices/dummy_devices.py) |
|
|
||||||
| EpicsDXPFalcon | <br> DXP parameters for Falcon detector<br><br> Base class to map EPICS PVs from DXP parameters to ophyd signals.<br> | [phoenix_bec.devices.falcon_phoenix_no_hdf5](https://gitlab.psi.ch/bec/phoenix_bec/-/blob/main/phoenix_bec/devices/falcon_phoenix_no_hdf5.py) |
|
|
||||||
| EpicsDXPXMAP | <br> DXP parameters for XMAP detector<br><br> Base class to map EPICS PVs from DXP parameters to ophyd signals.<br> | [phoenix_bec.devices.xmap_phoenix_no_hdf5](https://gitlab.psi.ch/bec/phoenix_bec/-/blob/main/phoenix_bec/devices/xmap_phoenix_no_hdf5.py) |
|
|
||||||
| EpicsMCARecordExtended | | [phoenix_bec.devices.falcon_phoenix](https://gitlab.psi.ch/bec/phoenix_bec/-/blob/main/phoenix_bec/devices/falcon_phoenix.py) |
|
|
||||||
| FalconcSAXS | <br> Falcon Sitoro detector for CSAXS<br><br> Parent class: PSIDetectorBase<br><br> class attributes:<br> custom_prepare_cls (FalconSetup) : Custom detector setup class for cSAXS,<br> inherits from CustomDetectorMixin<br> PSIDetectorBase.set_min_readout (float) : Minimum readout time for the detector<br> dxp (EpicsDXPFalcon) : DXP parameters for Falcon detector<br> mca (EpicsMCARecord) : MCA parameters for Falcon detector<br> hdf5 (FalconHDF5Plugins) : HDF5 parameters for Falcon detector<br> MIN_READOUT (float) : Minimum readout time for the detector<br> | [phoenix_bec.devices.falcon_csaxs_original](https://gitlab.psi.ch/bec/phoenix_bec/-/blob/main/phoenix_bec/devices/falcon_csaxs_original.py) |
|
|
||||||
| FalconHDF5Plugins | <br> HDF5 parameters for Falcon detector<br><br> Base class to map EPICS PVs from HDF5 Plugin to ophyd signals.<br> | [phoenix_bec.devices.falcon_phoenix_no_hdf5](https://gitlab.psi.ch/bec/phoenix_bec/-/blob/main/phoenix_bec/devices/falcon_phoenix_no_hdf5.py) |
|
|
||||||
| FalconPhoenix | <br> Falcon detector for phoenix<br> custom_prepare_cls (XMAPSetu<br> custom_prepare_cls (XMAPSetup) : Custom detector setup class for cSAXS,<br> inherits from CustomDetectorMixin<br> in __init__ of PSIDetecor base<br> PSIDetectorBase.set_min_readout (float) : Minimum readout time for the detector<br> dxp (EpicsDXPXMAP) : DXP parameters for XMAP detector<br> mca (EpicsMCARecord) : MCA parameters for XMAP detector<br> hdf5 (XMAPHDF5Plugins) : HDF5 parameters for XMAP detector<br> MIN_READOUT (float) : Minimum readout time for the detector<br> | [phoenix_bec.devices.falcon_phoenix_no_hdf5](https://gitlab.psi.ch/bec/phoenix_bec/-/blob/main/phoenix_bec/devices/falcon_phoenix_no_hdf5.py) |
|
|
||||||
| PhoenixTrigger | <br> Class for PHOENIX TTL hardware trigger: 'X07MB-OP2:'<br><br> This device is used to trigger communicate with an ADC card<br> that creates TTL signals to trigger cameras and detectors at Phoenix.<br><br> | [phoenix_bec.devices.phoenix_trigger](https://gitlab.psi.ch/bec/phoenix_bec/-/blob/main/phoenix_bec/devices/phoenix_trigger.py) |
|
|
||||||
| ROI | | [phoenix_bec.devices.sitoro](https://gitlab.psi.ch/bec/phoenix_bec/-/blob/main/phoenix_bec/devices/sitoro.py) |
|
|
||||||
| SitoroEpicsDXP | All high-level DXP parameters for each channel | [phoenix_bec.devices.sitoro](https://gitlab.psi.ch/bec/phoenix_bec/-/blob/main/phoenix_bec/devices/sitoro.py) |
|
|
||||||
| SitoroEpicsDXP_OLD | <br> DXP parameters for Sitoro detector<br><br> Base class to map EPICS PVs from DXP parameters to ophyd signals.<br> | [phoenix_bec.devices.sitoro_phoenix](https://gitlab.psi.ch/bec/phoenix_bec/-/blob/main/phoenix_bec/devices/sitoro_phoenix.py) |
|
|
||||||
| SitoroEpicsDXPBaseSystem | | [phoenix_bec.devices.sitoro](https://gitlab.psi.ch/bec/phoenix_bec/-/blob/main/phoenix_bec/devices/sitoro.py) |
|
|
||||||
| SitoroEpicsDXPLowLevel | | [phoenix_bec.devices.sitoro](https://gitlab.psi.ch/bec/phoenix_bec/-/blob/main/phoenix_bec/devices/sitoro.py) |
|
|
||||||
| SitoroEpicsDXPLowLevelParameter | | [phoenix_bec.devices.sitoro](https://gitlab.psi.ch/bec/phoenix_bec/-/blob/main/phoenix_bec/devices/sitoro.py) |
|
|
||||||
| SitoroEpicsDXPMapping | | [phoenix_bec.devices.sitoro](https://gitlab.psi.ch/bec/phoenix_bec/-/blob/main/phoenix_bec/devices/sitoro.py) |
|
|
||||||
| SitoroEpicsDXPMultiElementSystem | | [phoenix_bec.devices.sitoro](https://gitlab.psi.ch/bec/phoenix_bec/-/blob/main/phoenix_bec/devices/sitoro.py) |
|
|
||||||
| SitoroEpicsMCA | mca records with extras from mca.db | [phoenix_bec.devices.sitoro](https://gitlab.psi.ch/bec/phoenix_bec/-/blob/main/phoenix_bec/devices/sitoro.py) |
|
|
||||||
| SitoroEpicsMCACallback | Callback-related signals for MCA devices | [phoenix_bec.devices.sitoro](https://gitlab.psi.ch/bec/phoenix_bec/-/blob/main/phoenix_bec/devices/sitoro.py) |
|
|
||||||
| SitoroEpicsMCAReadNotify | mca record with extras from mcaReadNotify.db | [phoenix_bec.devices.sitoro](https://gitlab.psi.ch/bec/phoenix_bec/-/blob/main/phoenix_bec/devices/sitoro.py) |
|
|
||||||
| SitoroEpicsMCARecord | SynApps MCA Record interface | [phoenix_bec.devices.sitoro](https://gitlab.psi.ch/bec/phoenix_bec/-/blob/main/phoenix_bec/devices/sitoro.py) |
|
|
||||||
| SitoroEpicsMCARecordExtended_OLD | | [phoenix_bec.devices.sitoro_phoenix](https://gitlab.psi.ch/bec/phoenix_bec/-/blob/main/phoenix_bec/devices/sitoro_phoenix.py) |
|
|
||||||
| SitoroHDF5Plugins | <br> HDF5 parameters for Sitoro detector<br><br> Base class to map EPICS PVs from HDF5 Plugin to ophyd signals.<br> | [phoenix_bec.devices.sitoro_phoenix](https://gitlab.psi.ch/bec/phoenix_bec/-/blob/main/phoenix_bec/devices/sitoro_phoenix.py) |
|
|
||||||
| SitoroPhoenix | <br> Sitoro Sitoro detector for Phoenix<br><br><br> Parent class: PSIDetectorBase<br><br> class attributes:<br> custom_prepare_cls (SitoroSetup) : Custom detector setup class,<br> inherits from CustomDetectorMixin<br><br> PSIDetectorBase.set_min_readout (float) : Minimum readout time for the detector<br> dxp1, .. dxpi, .. , dxpN (SitoroEpicsDXP) : DXP parameters for Sitoro detector Nr i<br> mca1, .. mcai, .. , mcaN (SitoroEpicsMCARecord) : MCA parameters for Sitoro detector Nr i<br><br> hdf5 (SitoroHDF5Plugins) : HDF5 parameters for Sitoro detector<br> MIN_READOUT (float) : Minimum readout time for the detector<br><br><br><br> | [phoenix_bec.devices.sitoro_phoenix](https://gitlab.psi.ch/bec/phoenix_bec/-/blob/main/phoenix_bec/devices/sitoro_phoenix.py) |
|
|
||||||
| SitoroSoftDXPTrigger | Simple soft trigger for DXP devices<br><br> Parameters<br> ----------<br> count_signal : str, optional<br> Signal to set acquisition time (default: 'preset_real_time')<br> preset_mode : str, optional<br> Default preset mode for the stage signals (default: 'Real time')<br> mode_signal : str, optional<br> Preset mode signal attribute (default 'preset_mode')<br> stop_signal : str, optional<br> Stop signal attribute (default 'stop_all')<br> | [phoenix_bec.devices.sitoro](https://gitlab.psi.ch/bec/phoenix_bec/-/blob/main/phoenix_bec/devices/sitoro.py) |
|
|
||||||
| SitoroTest | | [phoenix_bec.devices.sitoro](https://gitlab.psi.ch/bec/phoenix_bec/-/blob/main/phoenix_bec/devices/sitoro.py) |
|
|
||||||
| XMAPHDF5Plugins | <br> HDF5 parameters for XMAP detector<br><br> Base class to map EPICS PVs from HDF5 Plugin to ophyd signals.<br> | [phoenix_bec.devices.xmap_phoenix_no_hdf5](https://gitlab.psi.ch/bec/phoenix_bec/-/blob/main/phoenix_bec/devices/xmap_phoenix_no_hdf5.py) |
|
|
||||||
| XMAPPhoenix | MCA<br> XMAP detector for phoenix<br> custom_prepare_cls (XMAPSetu<br> custom_prepare_cls (XMAPSetup) : Custom detector setup class for cSAXS,<br> inherits from CustomDetectorMixin<br> in __init__ of PSIDetecor base<br> PSIDetectorBase.set_min_readout (float) : Minimum readout time for the detector<br> dxp (EpicsDXPXMAP) : DXP parameters for XMAP detector<br> mca (EpicsMCARecord) : MCA parameters for XMAP detector<br> hdf5 (XMAPHDF5Plugins) : HDF5 parameters for XMAP detector<br> MIN_READOUT (float) : Minimum readout time for the detector<br> | [phoenix_bec.devices.xmap_phoenix_no_hdf5](https://gitlab.psi.ch/bec/phoenix_bec/-/blob/main/phoenix_bec/devices/xmap_phoenix_no_hdf5.py) |
|
|
||||||
|
@ -1,499 +0,0 @@
|
|||||||
"""
|
|
||||||
This is a copy of psi_detecto_base.py
|
|
||||||
with added print sigbnals to understand how it functions
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
import os
|
|
||||||
import threading
|
|
||||||
import time
|
|
||||||
import traceback
|
|
||||||
|
|
||||||
from bec_lib import messages
|
|
||||||
from bec_lib.endpoints import MessageEndpoints
|
|
||||||
from bec_lib.file_utils import FileWriter
|
|
||||||
from bec_lib.logger import bec_logger
|
|
||||||
from ophyd import Component
|
|
||||||
from ophyd import Component as Cpt
|
|
||||||
from ophyd import Device, DeviceStatus, EpicsSignal, EpicsSignalRO
|
|
||||||
from ophyd import FormattedComponent as FCpt
|
|
||||||
from ophyd import Kind
|
|
||||||
from ophyd.device import Staged
|
|
||||||
from ophyd_devices.interfaces.base_classes.psi_detector_base import (
|
|
||||||
CustomDetectorMixin,
|
|
||||||
PSIDetectorBase,
|
|
||||||
)
|
|
||||||
from ophyd_devices.sim.sim_signals import SetableSignal
|
|
||||||
from ophyd_devices.utils import bec_utils
|
|
||||||
from ophyd_devices.utils.bec_scaninfo_mixin import BecScaninfoMixin
|
|
||||||
from ophyd_devices.utils.errors import DeviceStopError, DeviceTimeoutError
|
|
||||||
|
|
||||||
from phoenix_bec.scripts.phoenix import PhoenixBL
|
|
||||||
|
|
||||||
logger = bec_logger.logger
|
|
||||||
|
|
||||||
|
|
||||||
# class LogTime():
|
|
||||||
|
|
||||||
# def __init__(self):
|
|
||||||
# self.t0=time.time()
|
|
||||||
|
|
||||||
# def p_s(self,x):
|
|
||||||
# now=time.time()
|
|
||||||
# #delta=now-self.t0
|
|
||||||
# m=str(now)+' sec '+x
|
|
||||||
# logger.success(m)
|
|
||||||
# #self.t0=now
|
|
||||||
# file=open('MyLogfile.txt','a')
|
|
||||||
# file.write(m+'\n')
|
|
||||||
# file.close
|
|
||||||
|
|
||||||
|
|
||||||
p_s = PhoenixBL.my_log
|
|
||||||
|
|
||||||
|
|
||||||
class DetectorInitError(Exception):
|
|
||||||
"""Raised when initiation of the device class fails,
|
|
||||||
due to missing device manager or not started in sim_mode."""
|
|
||||||
|
|
||||||
|
|
||||||
class SetupDummy(CustomDetectorMixin):
|
|
||||||
"""
|
|
||||||
Mixin class for custom detector logic
|
|
||||||
|
|
||||||
This class is used to implement BL specific logic for the detector.
|
|
||||||
It is used in the PSIDetectorBase class.
|
|
||||||
|
|
||||||
For the integration of a new detector, the following functions should
|
|
||||||
help with integrating functionality, but additional ones can be added.
|
|
||||||
|
|
||||||
Check PSIDetectorBase for the functions that are called during relevant function calls of
|
|
||||||
stage, unstage, trigger, stop and _init.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, *_args, parent: Device = None, **_kwargs) -> None:
|
|
||||||
self.parent = parent
|
|
||||||
|
|
||||||
def on_init(self) -> None:
|
|
||||||
""" """
|
|
||||||
|
|
||||||
def on_stage(self) -> None:
|
|
||||||
"""e is writing data on disk, this step should include publishing
|
|
||||||
a file_event and file_message to BEC to inform the system where the data is written to.
|
|
||||||
|
|
||||||
IMPORTANT:
|
|
||||||
It must be safe to assume that the device is ready for the scan
|
|
||||||
to start immediately once this function is finished.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def on_unstage(self) -> None:
|
|
||||||
"""
|
|
||||||
Specify actions to be executed during unstage.
|
|
||||||
|
|
||||||
This step should include checking if the acqusition was successful,
|
|
||||||
and publishing the file location and file event message,
|
|
||||||
with flagged done to BEC.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def on_stop(self) -> None:
|
|
||||||
"""
|
|
||||||
Specify actions to be executed during stop.
|
|
||||||
This must also set self.parent.stopped to True.
|
|
||||||
|
|
||||||
This step should include stopping the detector and backend service.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def on_trigger(self) -> None | DeviceStatus:
|
|
||||||
"""
|
|
||||||
Specify actions to be executed upon receiving trigger signal.
|
|
||||||
Return a DeviceStatus object or None
|
|
||||||
"""
|
|
||||||
|
|
||||||
def on_pre_scan(self) -> None:
|
|
||||||
"""
|
|
||||||
Specify actions to be executed right before a scan starts.
|
|
||||||
|
|
||||||
Only use if needed, and it is recommended to keep this function as short/fast as possible.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def on_complete(self) -> None | DeviceStatus:
|
|
||||||
"""
|
|
||||||
Specify actions to be executed when the scan is complete.
|
|
||||||
|
|
||||||
This can for instance be to check with the detector and backend if all data is written succsessfully.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def publish_file_location(self, done: bool, successful: bool, metadata: dict = None) -> None:
|
|
||||||
"""
|
|
||||||
Publish the filepath to REDIS.
|
|
||||||
|
|
||||||
We publish two events here:
|
|
||||||
- file_event: event for the filewriter
|
|
||||||
- public_file: event for any secondary service (e.g. radial integ code)
|
|
||||||
|
|
||||||
Args:
|
|
||||||
done (bool): True if scan is finished
|
|
||||||
successful (bool): True if scan was successful
|
|
||||||
metadata (dict): additional metadata to publish
|
|
||||||
"""
|
|
||||||
if metadata is None:
|
|
||||||
metadata = {}
|
|
||||||
|
|
||||||
msg = messages.FileMessage(
|
|
||||||
file_path=self.parent.filepath.get(),
|
|
||||||
done=done,
|
|
||||||
successful=successful,
|
|
||||||
metadata=metadata,
|
|
||||||
)
|
|
||||||
pipe = self.parent.connector.pipeline()
|
|
||||||
self.parent.connector.set_and_publish(
|
|
||||||
MessageEndpoints.public_file(self.parent.scaninfo.scan_id, self.parent.name),
|
|
||||||
msg,
|
|
||||||
pipe=pipe,
|
|
||||||
)
|
|
||||||
self.parent.connector.set_and_publish(
|
|
||||||
MessageEndpoints.file_event(self.parent.name), msg, pipe=pipe
|
|
||||||
)
|
|
||||||
pipe.execute()
|
|
||||||
|
|
||||||
def wait_for_signals(
|
|
||||||
self,
|
|
||||||
signal_conditions: list[tuple],
|
|
||||||
timeout: float,
|
|
||||||
check_stopped: bool = False,
|
|
||||||
interval: float = 0.05,
|
|
||||||
all_signals: bool = False,
|
|
||||||
) -> bool:
|
|
||||||
"""
|
|
||||||
Convenience wrapper to allow waiting for signals to reach a certain condition.
|
|
||||||
For EPICs PVs, an example usage is pasted at the bottom.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
signal_conditions (list[tuple]): tuple of executable calls for conditions (get_current_state, condition) to check
|
|
||||||
timeout (float): timeout in seconds
|
|
||||||
interval (float): interval in seconds
|
|
||||||
all_signals (bool): True if all signals should be True, False if any signal should be True
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
bool: True if all signals are in the desired state, False if timeout is reached
|
|
||||||
|
|
||||||
>>> Example usage for EPICS PVs:
|
|
||||||
>>> self.wait_for_signals(signal_conditions=[(self.acquiring.get, False)], timeout=5, interval=0.05, check_stopped=True, all_signals=True)
|
|
||||||
"""
|
|
||||||
|
|
||||||
timer = 0
|
|
||||||
while True:
|
|
||||||
checks = [
|
|
||||||
get_current_state() == condition
|
|
||||||
for get_current_state, condition in signal_conditions
|
|
||||||
]
|
|
||||||
if check_stopped is True and self.parent.stopped is True:
|
|
||||||
return False
|
|
||||||
if (all_signals and all(checks)) or (not all_signals and any(checks)):
|
|
||||||
return True
|
|
||||||
if timer > timeout:
|
|
||||||
return False
|
|
||||||
time.sleep(interval)
|
|
||||||
timer += interval
|
|
||||||
|
|
||||||
def wait_with_status(
|
|
||||||
self,
|
|
||||||
signal_conditions: list[tuple],
|
|
||||||
timeout: float,
|
|
||||||
check_stopped: bool = False,
|
|
||||||
interval: float = 0.05,
|
|
||||||
all_signals: bool = False,
|
|
||||||
exception_on_timeout: Exception = None,
|
|
||||||
) -> DeviceStatus:
|
|
||||||
"""Utility function to wait for signals in a thread.
|
|
||||||
Returns a DevicesStatus object that resolves either to set_finished or set_exception.
|
|
||||||
The DeviceStatus is attached to the parent device, i.e. the detector object inheriting from PSIDetectorBase.
|
|
||||||
|
|
||||||
Usage:
|
|
||||||
This function should be used to wait for signals to reach a certain condition, especially in the context of
|
|
||||||
on_trigger and on_complete. If it is not used, functions may block and slow down the performance of BEC.
|
|
||||||
It will return a DeviceStatus object that is to be returned from the function. Once the conditions are met,
|
|
||||||
the DeviceStatus will be set to set_finished in case of success or set_exception in case of a timeout or exception.
|
|
||||||
The exception can be specified with the exception_on_timeout argument. The default exception is a TimeoutError.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
signal_conditions (list[tuple]): tuple of executable calls for conditions (get_current_state, condition) to check
|
|
||||||
timeout (float): timeout in seconds
|
|
||||||
check_stopped (bool): T t_offset = 1724683600 # subtract some arbtrary offset from the time value
|
|
||||||
rue if stopped flag should be checked
|
|
||||||
interval (float): interval in seconds
|
|
||||||
all_signals (bool): True if all signals should be True, False if any signal should be True
|
|
||||||
exception_on_timeout (Exception): Exception to raise on timeout
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
DeviceStatus: DeviceStatus object that resolves either to set_finished or set_exception
|
|
||||||
"""
|
|
||||||
if exception_on_timeout is None:
|
|
||||||
exception_on_timeout = DeviceTimeoutError(
|
|
||||||
f"Timeout error for {self.parent.name} while waiting for signals {signal_conditions}"
|
|
||||||
)
|
|
||||||
|
|
||||||
status = DeviceStatus(self.parent)
|
|
||||||
|
|
||||||
# utility function to wrap the wait_for_signals function
|
|
||||||
def wait_for_signals_wrapper(
|
|
||||||
status: DeviceStatus,
|
|
||||||
signal_conditions: list[tuple],
|
|
||||||
timeout: float,
|
|
||||||
check_stopped: bool,
|
|
||||||
interval: float,
|
|
||||||
all_signals: bool,
|
|
||||||
exception_on_timeout: Exception,
|
|
||||||
):
|
|
||||||
"""Convenient wrapper around wait_for_signals to set status based on the result.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
status (DeviceStatus): DeviceStatus object to be set
|
|
||||||
signal_conditions (list[tuple]): tuple of executable calls for conditions (get_current_state, condition) to check
|
|
||||||
timeout (float): timeout in seconds
|
|
||||||
check_stopped (bool): True if stopped flag should be checked
|
|
||||||
interval (float): interval in seconds
|
|
||||||
all_signals (bool): True if all signals should be True, False if any signal should be True
|
|
||||||
exception_on_timeout (Exception): Exception to raise on timeout
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
result = self.wait_for_signals(
|
|
||||||
signal_conditions, timeout, check_stopped, interval, all_signals
|
|
||||||
)
|
|
||||||
if result:
|
|
||||||
status.set_finished()
|
|
||||||
else:
|
|
||||||
if self.parent.stopped:
|
|
||||||
# INFO This will execute a callback to the parent device.stop() method
|
|
||||||
status.set_exception(exc=DeviceStopError(f"{self.parent.name} was stopped"))
|
|
||||||
else:
|
|
||||||
# INFO This will execute a callback to the parent device.stop() method
|
|
||||||
status.set_exception(exc=exception_on_timeout)
|
|
||||||
# pylint: disable=broad-except
|
|
||||||
except Exception as exc:
|
|
||||||
content = traceback.format_exc()
|
|
||||||
logger.warning(
|
|
||||||
f"Error in wait_for_signals in {self.parent.name}; Traceback: {content}"
|
|
||||||
)
|
|
||||||
# INFO This will execute a callback to the parent device.stop() method
|
|
||||||
status.set_exception(exc=exc)
|
|
||||||
|
|
||||||
thread = threading.Thread(
|
|
||||||
target=wait_for_signals_wrapper,
|
|
||||||
args=(
|
|
||||||
status,
|
|
||||||
signal_conditions,
|
|
||||||
timeout,
|
|
||||||
check_stopped,
|
|
||||||
interval,
|
|
||||||
all_signals,
|
|
||||||
exception_on_timeout,
|
|
||||||
),
|
|
||||||
daemon=True,
|
|
||||||
)
|
|
||||||
thread.start()
|
|
||||||
return status
|
|
||||||
|
|
||||||
|
|
||||||
class Dummy_PSIDetector(PSIDetectorBase):
|
|
||||||
"""
|
|
||||||
Abstract base class for SLS detectors
|
|
||||||
|
|
||||||
Class attributes:
|
|
||||||
custom_prepare_cls (object): class for custom prepare logic (BL specific)
|
|
||||||
|
|
||||||
Args:
|
|
||||||
prefix (str): EPICS PV prefix for component (optional)
|
|
||||||
name (str): name of the device, as will be reported via read()
|
|
||||||
kind (str): member of class 'ophydobj.Kind', defaults to Kind.normal
|
|
||||||
omitted -> reado_PSIDetectorBase
|
|
||||||
"""
|
|
||||||
|
|
||||||
filepath = Component(SetableSignal, value="", kind=Kind.config)
|
|
||||||
|
|
||||||
custom_prepare_cls = SetupDummy
|
|
||||||
|
|
||||||
# prefix=X07MB-PC-PSCAN
|
|
||||||
|
|
||||||
D = Cpt(EpicsSignal, "P-P0D0") # cont on / off
|
|
||||||
|
|
||||||
def __init__(self, prefix="", *, name, kind=None, parent=None, device_manager=None, **kwargs):
|
|
||||||
|
|
||||||
self.p_s = PhoenixBL.my_log # must be before super!!!
|
|
||||||
|
|
||||||
self.p_s("Dummy_device Dummy_PSIDetector.__init__ ")
|
|
||||||
|
|
||||||
super().__init__(prefix=prefix, name=name, kind=kind, parent=parent, **kwargs)
|
|
||||||
|
|
||||||
self.stopped = False
|
|
||||||
self.name = name
|
|
||||||
self.service_cfg = None
|
|
||||||
self.scaninfo = None
|
|
||||||
self.filewriter = None
|
|
||||||
if not issubclass(self.custom_prepare_cls, CustomDetectorMixin):
|
|
||||||
raise DetectorInitError("Custom prepare class must be subclass of CustomDetectorMixin")
|
|
||||||
self.custom_prepare = self.custom_prepare_cls(parent=self, **kwargs)
|
|
||||||
|
|
||||||
if device_manager:
|
|
||||||
self._update_service_config()
|
|
||||||
self.device_manager = device_manager
|
|
||||||
else:
|
|
||||||
self.device_manager = bec_utils.DMMock()
|
|
||||||
base_path = kwargs["basepath"] if "basepath" in kwargs else "."
|
|
||||||
self.service_cfg = {"base_path": os.path.abspath(base_path)}
|
|
||||||
|
|
||||||
self.connector = self.device_manager.connector
|
|
||||||
self._update_scaninfo()
|
|
||||||
self._update_filewriter()
|
|
||||||
self._init()
|
|
||||||
# .. prepare my own log file
|
|
||||||
|
|
||||||
self.p_s("Dummy_device Dummy_PSIDetector.__init__ .. done ")
|
|
||||||
|
|
||||||
def _update_filewriter(self) -> None:
|
|
||||||
"""Update filewriter with service config"""
|
|
||||||
self.p_s("Dummy_device Dummy_PSIDetector._update_filewriter")
|
|
||||||
self.filewriter = FileWriter(service_config=self.service_cfg, connector=self.connector)
|
|
||||||
self.p_s("Dummy_device Dummy_PSIDetector._update_filewriter .. done ")
|
|
||||||
|
|
||||||
def _update_scaninfo(self) -> None:
|
|
||||||
"""Update scaninfo from BecScaninfoMixing
|
|
||||||
This depends on device manager and operation/sim_mode
|
|
||||||
"""
|
|
||||||
self.p_s("Dummy_device Dummy_PSIDetector._update_scaninfo")
|
|
||||||
|
|
||||||
self.scaninfo = BecScaninfoMixin(self.device_manager)
|
|
||||||
self.scaninfo.load_scan_metadata()
|
|
||||||
self.p_s("Dummy_device Dummy_PSIDetector._update_scaninfo .. done ")
|
|
||||||
|
|
||||||
def _update_service_config(self) -> None:
|
|
||||||
"""Update service config from BEC service config
|
|
||||||
|
|
||||||
If bec services are not running and SERVICE_CONFIG is NONE, we fall back to the current directory.
|
|
||||||
"""
|
|
||||||
# pylint: disable=import-outside-toplevel
|
|
||||||
|
|
||||||
from bec_lib.bec_service import SERVICE_CONFIG
|
|
||||||
|
|
||||||
self.p_s("Dummy_device Dummy_PSIDetector._update_service_config")
|
|
||||||
|
|
||||||
if SERVICE_CONFIG:
|
|
||||||
self.service_cfg = SERVICE_CONFIG.config["service_config"]["file_writer"]
|
|
||||||
return
|
|
||||||
self.service_cfg = {"base_path": os.path.abspath(".")}
|
|
||||||
self.p_s("Dummy_device Dummy_PSIDetector._update_service_config .. done")
|
|
||||||
|
|
||||||
def check_scan_id(self) -> None:
|
|
||||||
"""Checks if scan_id has changed and set stopped flagged to True if it has."""
|
|
||||||
self.p_s("Dummy_device Dummy_PSIDetector.check_scan_id")
|
|
||||||
|
|
||||||
old_scan_id = self.scaninfo.scan_id
|
|
||||||
self.scaninfo.load_scan_metadata()
|
|
||||||
if self.scaninfo.scan_id != old_scan_id:
|
|
||||||
self.stopped = True
|
|
||||||
self.p_s("Dummy_device Dummy_PSIDetector.check_scan_id .. done ")
|
|
||||||
|
|
||||||
def _init(self) -> None:
|
|
||||||
"""Initialize detector, filewriter and set default parameters"""
|
|
||||||
self.p_s("Dummy_device Dummy_PSIDetector._init")
|
|
||||||
|
|
||||||
self.custom_prepare.on_init()
|
|
||||||
self.p_s("Dummy_device Dummy_PSIDetector._init ... done ")
|
|
||||||
|
|
||||||
def stage(self) -> list[object]:
|
|
||||||
"""
|
|
||||||
Stage device in preparation for a scan.
|
|
||||||
First we check if the device is already staged. Stage is idempotent,
|
|
||||||
if staged twice it should raise (we let ophyd.Device handle the raise here).
|
|
||||||
We reset the stopped flag and get the scaninfo from BEC, before calling custom_prepare.on_stage.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
list(object): list of objects that were staged
|
|
||||||
|
|
||||||
"""
|
|
||||||
self.p_s("Dummy_device Dummy_PSIDetector.stage")
|
|
||||||
|
|
||||||
if self._staged != Staged.no:
|
|
||||||
return super().stage()
|
|
||||||
self.stopped = False
|
|
||||||
self.scaninfo.load_scan_metadata()
|
|
||||||
self.custom_prepare.on_stage()
|
|
||||||
self.p_s("Dummy_device Dummy_PSIDetector.stage done ")
|
|
||||||
|
|
||||||
return super().stage()
|
|
||||||
|
|
||||||
def pre_scan(self) -> None:
|
|
||||||
"""Pre-scan logic.
|
|
||||||
|
|
||||||
This function will be called from BEC directly before the scan core starts, and should only implement
|
|
||||||
time-critical actions. Therefore, it should also be kept as short/fast as possible.
|
|
||||||
I.e. Arming a detector in case there is a risk of timing out.
|
|
||||||
"""
|
|
||||||
self.p_s("Dummy_device Dummy_PSIDetector.pre_scan")
|
|
||||||
|
|
||||||
self.custom_prepare.on_pre_scan()
|
|
||||||
self.p_s("Dummy_device Dummy_PSIDetector.pre_scan .. done ")
|
|
||||||
|
|
||||||
def trigger(self) -> DeviceStatus:
|
|
||||||
"""Trigger the detector, called from BEC."""
|
|
||||||
|
|
||||||
# pylint: disable=assignment-from-no-return
|
|
||||||
self.p_s("Dummy_device Dummy_PSIDetector.trigger")
|
|
||||||
|
|
||||||
status = self.custom_prepare.on_trigger()
|
|
||||||
if isinstance(status, DeviceStatus):
|
|
||||||
return status
|
|
||||||
self.p_s("Dummy_device Dummy_PSIDetector.trigger.. done ")
|
|
||||||
|
|
||||||
return super().trigger()
|
|
||||||
|
|
||||||
def complete(self) -> None:
|
|
||||||
"""Complete the acquisition, called from BEC.
|
|
||||||
|
|
||||||
This function is called after the scan is complete, just before unstage.
|
|
||||||
We can check here with the data backend and detector if the acquisition successfully finished.
|
|
||||||
|
|
||||||
Actions are implemented in custom_prepare.on_complete since they are beamline specific.
|
|
||||||
"""
|
|
||||||
# pylint: disable=assignment-from-no-return
|
|
||||||
self.p_s("Dummy_device Dummy_PSIDetector.complete")
|
|
||||||
|
|
||||||
status = self.custom_prepare.on_complete()
|
|
||||||
if isinstance(status, DeviceStatus):
|
|
||||||
return status
|
|
||||||
status = DeviceStatus(self)
|
|
||||||
status.set_finished()
|
|
||||||
self.p_s("Dummy_device Dummy_PSIDetector.complete ... done ")
|
|
||||||
|
|
||||||
return status
|
|
||||||
|
|
||||||
def unstage(self) -> list[object]:
|
|
||||||
"""
|
|
||||||
Unstage device after a scan.
|
|
||||||
|
|
||||||
We first check if the scanID has changed, thus, the scan was unexpectedly interrupted but the device was not stopped.
|
|
||||||
If that is the case, the stopped flag is set to True, which will immediately unstage the device.
|
|
||||||
|
|
||||||
Custom_prepare.on_unstage is called to allow for BL specific logic to be executed.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
list(object): list of objects that were unstaged
|
|
||||||
"""
|
|
||||||
self.p_s("Dummy_device Dummy_PSIDetector.unstage")
|
|
||||||
self.check_scan_id()
|
|
||||||
self.custom_prepare.on_unstage()
|
|
||||||
self.stopped = False
|
|
||||||
self.p_s("Dummy_device Dummy_PSIDetector.unstage .. done")
|
|
||||||
|
|
||||||
return super().unstage()
|
|
||||||
|
|
||||||
def stop(self, *, success=False) -> None:
|
|
||||||
"""
|
|
||||||
Stop the scan, with camera and file writer
|
|
||||||
|
|
||||||
"""
|
|
||||||
self.p_s("Dummy_device Dummy_PSIDetector.stop")
|
|
||||||
self.custom_prepare.on_stop()
|
|
||||||
super().stop(success=success)
|
|
||||||
self.stopped = True
|
|
||||||
self.p_s("Dummy_device Dummy_PSIDetector.stop ... done")
|
|
@ -1,349 +0,0 @@
|
|||||||
import enum
|
|
||||||
import os
|
|
||||||
import threading
|
|
||||||
|
|
||||||
from bec_lib.logger import bec_logger
|
|
||||||
from ophyd import Component as Cpt
|
|
||||||
from ophyd import Device, EpicsSignal, EpicsSignalRO, EpicsSignalWithRBV
|
|
||||||
from ophyd.mca import EpicsMCARecord
|
|
||||||
from ophyd_devices.interfaces.base_classes.psi_detector_base import (
|
|
||||||
CustomDetectorMixin,
|
|
||||||
PSIDetectorBase,
|
|
||||||
)
|
|
||||||
|
|
||||||
logger = bec_logger.logger
|
|
||||||
|
|
||||||
|
|
||||||
class FalconError(Exception):
|
|
||||||
"""Base class for exceptions in this module."""
|
|
||||||
|
|
||||||
|
|
||||||
class FalconTimeoutError(FalconError):
|
|
||||||
"""Raised when the Falcon does not respond in time."""
|
|
||||||
|
|
||||||
|
|
||||||
class DetectorState(enum.IntEnum):
|
|
||||||
"""Detector states for Falcon detector"""
|
|
||||||
|
|
||||||
DONE = 0
|
|
||||||
ACQUIRING = 1
|
|
||||||
|
|
||||||
|
|
||||||
class TriggerSource(enum.IntEnum):
|
|
||||||
"""Trigger source for Falcon detector"""
|
|
||||||
|
|
||||||
USER = 0
|
|
||||||
GATE = 1
|
|
||||||
SYNC = 2
|
|
||||||
|
|
||||||
|
|
||||||
class MappingSource(enum.IntEnum):
|
|
||||||
"""Mapping source for Falcon detector"""
|
|
||||||
|
|
||||||
SPECTRUM = 0
|
|
||||||
MAPPING = 1
|
|
||||||
|
|
||||||
|
|
||||||
class EpicsDXPFalcon(Device):
|
|
||||||
"""
|
|
||||||
DXP parameters for Falcon detector
|
|
||||||
|
|
||||||
Base class to map EPICS PVs from DXP parameters to ophyd signals.
|
|
||||||
"""
|
|
||||||
|
|
||||||
elapsed_live_time = Cpt(EpicsSignal, "ElapsedLiveTime")
|
|
||||||
elapsed_real_time = Cpt(EpicsSignal, "ElapsedRealTime")
|
|
||||||
elapsed_trigger_live_time = Cpt(EpicsSignal, "ElapsedTriggerLiveTime")
|
|
||||||
|
|
||||||
# Energy Filter PVs
|
|
||||||
energy_threshold = Cpt(EpicsSignalWithRBV, "DetectionThreshold")
|
|
||||||
min_pulse_separation = Cpt(EpicsSignalWithRBV, "MinPulsePairSeparation")
|
|
||||||
detection_filter = Cpt(EpicsSignalWithRBV, "DetectionFilter", string=True)
|
|
||||||
scale_factor = Cpt(EpicsSignalWithRBV, "ScaleFactor")
|
|
||||||
risetime_optimisation = Cpt(EpicsSignalWithRBV, "RisetimeOptimization")
|
|
||||||
|
|
||||||
# Misc PVs
|
|
||||||
detector_polarity = Cpt(EpicsSignalWithRBV, "DetectorPolarity")
|
|
||||||
decay_time = Cpt(EpicsSignalWithRBV, "DecayTime")
|
|
||||||
|
|
||||||
current_pixel = Cpt(EpicsSignalRO, "CurrentPixel")
|
|
||||||
|
|
||||||
|
|
||||||
class FalconHDF5Plugins(Device):
|
|
||||||
"""
|
|
||||||
HDF5 parameters for Falcon detector
|
|
||||||
|
|
||||||
Base class to map EPICS PVs from HDF5 Plugin to ophyd signals.
|
|
||||||
"""
|
|
||||||
|
|
||||||
capture = Cpt(EpicsSignalWithRBV, "Capture")
|
|
||||||
enable = Cpt(EpicsSignalWithRBV, "EnableCallbacks", string=True, kind="config")
|
|
||||||
xml_file_name = Cpt(EpicsSignalWithRBV, "XMLFileName", string=True, kind="config")
|
|
||||||
lazy_open = Cpt(EpicsSignalWithRBV, "LazyOpen", string=True, doc="0='No' 1='Yes'")
|
|
||||||
temp_suffix = Cpt(EpicsSignalWithRBV, "TempSuffix", string=True)
|
|
||||||
file_path = Cpt(EpicsSignalWithRBV, "FilePath", string=True, kind="config")
|
|
||||||
file_name = Cpt(EpicsSignalWithRBV, "FileName", string=True, kind="config")
|
|
||||||
file_template = Cpt(EpicsSignalWithRBV, "FileTemplate", string=True, kind="config")
|
|
||||||
num_capture = Cpt(EpicsSignalWithRBV, "NumCapture", kind="config")
|
|
||||||
file_write_mode = Cpt(EpicsSignalWithRBV, "FileWriteMode", kind="config")
|
|
||||||
queue_size = Cpt(EpicsSignalWithRBV, "QueueSize", kind="config")
|
|
||||||
array_counter = Cpt(EpicsSignalWithRBV, "ArrayCounter", kind="config")
|
|
||||||
|
|
||||||
|
|
||||||
class FalconSetup(CustomDetectorMixin):
|
|
||||||
"""
|
|
||||||
Falcon setup class for cSAXS
|
|
||||||
|
|
||||||
Parent class: CustomDetectorMixin
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, *args, parent: Device = None, **kwargs) -> None:
|
|
||||||
super().__init__(*args, parent=parent, **kwargs)
|
|
||||||
self._lock = threading.RLock()
|
|
||||||
|
|
||||||
def on_init(self) -> None:
|
|
||||||
"""Initialize Falcon detector"""
|
|
||||||
self.initialize_default_parameter()
|
|
||||||
self.initialize_detector()
|
|
||||||
self.initialize_detector_backend()
|
|
||||||
|
|
||||||
def initialize_default_parameter(self) -> None:
|
|
||||||
"""
|
|
||||||
Set default parameters for Falcon
|
|
||||||
|
|
||||||
This will set:
|
|
||||||
- readout (float): readout time in seconds
|
|
||||||
- value_pixel_per_buffer (int): number of spectra in buffer of Falcon Sitoro
|
|
||||||
|
|
||||||
"""
|
|
||||||
self.parent.value_pixel_per_buffer = 20
|
|
||||||
self.update_readout_time()
|
|
||||||
|
|
||||||
def update_readout_time(self) -> None:
|
|
||||||
"""Set readout time for Eiger9M detector"""
|
|
||||||
readout_time = (
|
|
||||||
self.parent.scaninfo.readout_time
|
|
||||||
if hasattr(self.parent.scaninfo, "readout_time")
|
|
||||||
else self.parent.MIN_READOUT
|
|
||||||
)
|
|
||||||
self.parent.readout_time = max(readout_time, self.parent.MIN_READOUT)
|
|
||||||
|
|
||||||
def initialize_detector(self) -> None:
|
|
||||||
"""Initialize Falcon detector"""
|
|
||||||
self.stop_detector()
|
|
||||||
self.stop_detector_backend()
|
|
||||||
self.set_trigger(
|
|
||||||
mapping_mode=MappingSource.MAPPING, trigger_source=TriggerSource.GATE, ignore_gate=0
|
|
||||||
)
|
|
||||||
# 1 Realtime
|
|
||||||
self.parent.preset_mode.put(1)
|
|
||||||
# 0 Normal, 1 Inverted
|
|
||||||
self.parent.input_logic_polarity.put(0)
|
|
||||||
# 0 Manual 1 Auto
|
|
||||||
self.parent.auto_pixels_per_buffer.put(0)
|
|
||||||
# Sets the number of pixels/spectra in the buffer
|
|
||||||
self.parent.pixels_per_buffer.put(self.parent.value_pixel_per_buffer)
|
|
||||||
|
|
||||||
def initialize_detector_backend(self) -> None:
|
|
||||||
"""Initialize the detector backend for Falcon."""
|
|
||||||
self.parent.hdf5.enable.put(1)
|
|
||||||
# file location of h5 layout for cSAXS
|
|
||||||
self.parent.hdf5.xml_file_name.put("layout.xml")
|
|
||||||
# TODO Check if lazy open is needed and wanted!
|
|
||||||
self.parent.hdf5.lazy_open.put(1)
|
|
||||||
self.parent.hdf5.temp_suffix.put("")
|
|
||||||
# size of queue for number of spectra allowed in the buffer, if too small at high throughput, data is lost
|
|
||||||
self.parent.hdf5.queue_size.put(2000)
|
|
||||||
# Segmentation into Spectra within EPICS, 1 is activate, 0 is deactivate
|
|
||||||
self.parent.nd_array_mode.put(1)
|
|
||||||
|
|
||||||
def on_stage(self) -> None:
|
|
||||||
"""Prepare detector and backend for acquisition"""
|
|
||||||
self.prepare_detector()
|
|
||||||
self.prepare_data_backend()
|
|
||||||
self.publish_file_location(done=False, successful=False)
|
|
||||||
self.arm_acquisition()
|
|
||||||
|
|
||||||
def prepare_detector(self) -> None:
|
|
||||||
"""Prepare detector for acquisition"""
|
|
||||||
self.set_trigger(
|
|
||||||
mapping_mode=MappingSource.MAPPING, trigger_source=TriggerSource.GATE, ignore_gate=0
|
|
||||||
)
|
|
||||||
self.parent.preset_real.put(self.parent.scaninfo.exp_time)
|
|
||||||
self.parent.pixels_per_run.put(
|
|
||||||
int(self.parent.scaninfo.num_points * self.parent.scaninfo.frames_per_trigger)
|
|
||||||
)
|
|
||||||
|
|
||||||
def prepare_data_backend(self) -> None:
|
|
||||||
"""Prepare data backend for acquisition"""
|
|
||||||
self.parent.filepath.set(
|
|
||||||
self.parent.filewriter.compile_full_filename(f"{self.parent.name}.h5")
|
|
||||||
).wait()
|
|
||||||
file_path, file_name = os.path.split(self.parent.filepath.get())
|
|
||||||
self.parent.hdf5.file_path.put(file_path)
|
|
||||||
self.parent.hdf5.file_name.put(file_name)
|
|
||||||
self.parent.hdf5.file_template.put("%s%s")
|
|
||||||
self.parent.hdf5.num_capture.put(
|
|
||||||
int(self.parent.scaninfo.num_points * self.parent.scaninfo.frames_per_trigger)
|
|
||||||
)
|
|
||||||
self.parent.hdf5.file_write_mode.put(2)
|
|
||||||
# Reset spectrum counter in filewriter, used for indexing & identifying missing triggers
|
|
||||||
self.parent.hdf5.array_counter.put(0)
|
|
||||||
# Start file writing
|
|
||||||
self.parent.hdf5.capture.put(1)
|
|
||||||
|
|
||||||
def arm_acquisition(self) -> None:
|
|
||||||
"""Arm detector for acquisition"""
|
|
||||||
self.parent.start_all.put(1)
|
|
||||||
signal_conditions = [
|
|
||||||
(
|
|
||||||
lambda: self.parent.state.read()[self.parent.state.name]["value"],
|
|
||||||
DetectorState.ACQUIRING,
|
|
||||||
)
|
|
||||||
]
|
|
||||||
if not self.wait_for_signals(
|
|
||||||
signal_conditions=signal_conditions,
|
|
||||||
timeout=self.parent.TIMEOUT_FOR_SIGNALS,
|
|
||||||
check_stopped=True,
|
|
||||||
all_signals=False,
|
|
||||||
):
|
|
||||||
raise FalconTimeoutError(
|
|
||||||
f"Failed to arm the acquisition. Detector state {signal_conditions[0][0]}"
|
|
||||||
)
|
|
||||||
|
|
||||||
def on_unstage(self) -> None:
|
|
||||||
"""Unstage detector and backend"""
|
|
||||||
pass
|
|
||||||
|
|
||||||
def on_complete(self) -> None:
|
|
||||||
"""Complete detector and backend"""
|
|
||||||
self.finished(timeout=self.parent.TIMEOUT_FOR_SIGNALS)
|
|
||||||
self.publish_file_location(done=True, successful=True)
|
|
||||||
|
|
||||||
def on_stop(self) -> None:
|
|
||||||
"""Stop detector and backend"""
|
|
||||||
self.stop_detector()
|
|
||||||
self.stop_detector_backend()
|
|
||||||
|
|
||||||
def stop_detector(self) -> None:
|
|
||||||
"""Stops detector"""
|
|
||||||
|
|
||||||
self.parent.stop_all.put(1)
|
|
||||||
self.parent.erase_all.put(1)
|
|
||||||
|
|
||||||
signal_conditions = [
|
|
||||||
(lambda: self.parent.state.read()[self.parent.state.name]["value"], DetectorState.DONE)
|
|
||||||
]
|
|
||||||
|
|
||||||
if not self.wait_for_signals(
|
|
||||||
signal_conditions=signal_conditions,
|
|
||||||
timeout=self.parent.TIMEOUT_FOR_SIGNALS - self.parent.TIMEOUT_FOR_SIGNALS // 2,
|
|
||||||
all_signals=False,
|
|
||||||
):
|
|
||||||
# Retry stop detector and wait for remaining time
|
|
||||||
raise FalconTimeoutError(
|
|
||||||
f"Failed to stop detector, timeout with state {signal_conditions[0][0]}"
|
|
||||||
)
|
|
||||||
|
|
||||||
def stop_detector_backend(self) -> None:
|
|
||||||
"""Stop the detector backend"""
|
|
||||||
self.parent.hdf5.capture.put(0)
|
|
||||||
|
|
||||||
def finished(self, timeout: int = 5) -> None:
|
|
||||||
"""Check if scan finished succesfully"""
|
|
||||||
with self._lock:
|
|
||||||
total_frames = int(
|
|
||||||
self.parent.scaninfo.num_points * self.parent.scaninfo.frames_per_trigger
|
|
||||||
)
|
|
||||||
signal_conditions = [
|
|
||||||
(self.parent.dxp.current_pixel.get, total_frames),
|
|
||||||
(self.parent.hdf5.array_counter.get, total_frames),
|
|
||||||
]
|
|
||||||
if not self.wait_for_signals(
|
|
||||||
signal_conditions=signal_conditions,
|
|
||||||
timeout=timeout,
|
|
||||||
check_stopped=True,
|
|
||||||
all_signals=True,
|
|
||||||
):
|
|
||||||
logger.debug(
|
|
||||||
f"Falcon missed a trigger: received trigger {self.parent.dxp.current_pixel.get()},"
|
|
||||||
f" send data {self.parent.hdf5.array_counter.get()} from total_frames"
|
|
||||||
f" {total_frames}"
|
|
||||||
)
|
|
||||||
self.stop_detector()
|
|
||||||
self.stop_detector_backend()
|
|
||||||
|
|
||||||
def set_trigger(
|
|
||||||
self, mapping_mode: MappingSource, trigger_source: TriggerSource, ignore_gate: int = 0
|
|
||||||
) -> None:
|
|
||||||
"""
|
|
||||||
Set triggering mode for detector
|
|
||||||
|
|
||||||
Args:
|
|
||||||
mapping_mode (MappingSource): Mapping mode for the detector
|
|
||||||
trigger_source (TriggerSource): Trigger source for the detector, pixel_advance_signal
|
|
||||||
ignore_gate (int): Ignore gate from TTL signal; defaults to 0
|
|
||||||
|
|
||||||
"""
|
|
||||||
mapping = int(mapping_mode)
|
|
||||||
trigger = trigger_source
|
|
||||||
self.parent.collect_mode.put(mapping)
|
|
||||||
self.parent.pixel_advance_mode.put(trigger)
|
|
||||||
self.parent.ignore_gate.put(ignore_gate)
|
|
||||||
|
|
||||||
|
|
||||||
class FalconcSAXS(PSIDetectorBase):
|
|
||||||
"""
|
|
||||||
Falcon Sitoro detector for CSAXS
|
|
||||||
|
|
||||||
Parent class: PSIDetectorBase
|
|
||||||
|
|
||||||
class attributes:
|
|
||||||
custom_prepare_cls (FalconSetup) : Custom detector setup class for cSAXS,
|
|
||||||
inherits from CustomDetectorMixin
|
|
||||||
PSIDetectorBase.set_min_readout (float) : Minimum readout time for the detector
|
|
||||||
dxp (EpicsDXPFalcon) : DXP parameters for Falcon detector
|
|
||||||
mca (EpicsMCARecord) : MCA parameters for Falcon detector
|
|
||||||
hdf5 (FalconHDF5Plugins) : HDF5 parameters for Falcon detector
|
|
||||||
MIN_READOUT (float) : Minimum readout time for the detector
|
|
||||||
"""
|
|
||||||
|
|
||||||
# Specify which functions are revealed to the user in BEC client
|
|
||||||
USER_ACCESS = ["describe"]
|
|
||||||
|
|
||||||
# specify Setup class
|
|
||||||
custom_prepare_cls = FalconSetup
|
|
||||||
# specify minimum readout time for detector
|
|
||||||
MIN_READOUT = 3e-3
|
|
||||||
TIMEOUT_FOR_SIGNALS = 5
|
|
||||||
|
|
||||||
# specify class attributes
|
|
||||||
dxp = Cpt(EpicsDXPFalcon, "dxp1:")
|
|
||||||
mca = Cpt(EpicsMCARecord, "mca1")
|
|
||||||
hdf5 = Cpt(FalconHDF5Plugins, "HDF1:")
|
|
||||||
|
|
||||||
stop_all = Cpt(EpicsSignal, "StopAll")
|
|
||||||
erase_all = Cpt(EpicsSignal, "EraseAll")
|
|
||||||
start_all = Cpt(EpicsSignal, "StartAll")
|
|
||||||
state = Cpt(EpicsSignal, "Acquiring")
|
|
||||||
preset_mode = Cpt(EpicsSignal, "PresetMode") # 0 No preset 1 Real time 2 Events 3 Triggers
|
|
||||||
preset_real = Cpt(EpicsSignal, "PresetReal")
|
|
||||||
preset_events = Cpt(EpicsSignal, "PresetEvents")
|
|
||||||
preset_triggers = Cpt(EpicsSignal, "PresetTriggers")
|
|
||||||
triggers = Cpt(EpicsSignalRO, "MaxTriggers", lazy=True)
|
|
||||||
events = Cpt(EpicsSignalRO, "MaxEvents", lazy=True)
|
|
||||||
input_count_rate = Cpt(EpicsSignalRO, "MaxInputCountRate", lazy=True)
|
|
||||||
output_count_rate = Cpt(EpicsSignalRO, "MaxOutputCountRate", lazy=True)
|
|
||||||
collect_mode = Cpt(EpicsSignal, "CollectMode") # 0 MCA spectra, 1 MCA mapping
|
|
||||||
pixel_advance_mode = Cpt(EpicsSignal, "PixelAdvanceMode")
|
|
||||||
ignore_gate = Cpt(EpicsSignal, "IgnoreGate")
|
|
||||||
input_logic_polarity = Cpt(EpicsSignal, "InputLogicPolarity")
|
|
||||||
auto_pixels_per_buffer = Cpt(EpicsSignal, "AutoPixelsPerBuffer")
|
|
||||||
pixels_per_buffer = Cpt(EpicsSignal, "PixelsPerBuffer")
|
|
||||||
pixels_per_run = Cpt(EpicsSignal, "PixelsPerRun")
|
|
||||||
nd_array_mode = Cpt(EpicsSignal, "NDArrayMode")
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
falcon = FalconcSAXS(name="falcon", prefix="X12SA-SITORO:", sim_mode=True)
|
|
@ -1,468 +0,0 @@
|
|||||||
"""
|
|
||||||
Implementation for falcon at PHOENIX, derived from
|
|
||||||
implementation on csaxs (file falcon_csaxs.py)
|
|
||||||
|
|
||||||
17.10.2024 try to streamline implementation with mca record
|
|
||||||
|
|
||||||
|
|
||||||
Differences to implement
|
|
||||||
|
|
||||||
1) we consider EPICS initialization as standard implementaion,
|
|
||||||
so no reinitialization when bec device is initrialized ... DONE ...
|
|
||||||
|
|
||||||
2) in EpicsDXPFalcon(Device) add ICR and OCR for individual detectors
|
|
||||||
|
|
||||||
3) can we make this generic to make it suited for both falcon and XMAP ?
|
|
||||||
|
|
||||||
3) make easy switching between mca spectra an mca mapping
|
|
||||||
|
|
||||||
fix defiend relation bwetween variables and names used here for example DONE
|
|
||||||
|
|
||||||
aquiring is currently called 'state' --> should be renamed to aquiring
|
|
||||||
Currently state = Cpt(EpicsSignal, "Acquiring")
|
|
||||||
should be acquiring = Cpt(EpicsSignal, "Acquiring")
|
|
||||||
|
|
||||||
|
|
||||||
hdf5 = Cpt(FalconHDF5Plugins, "HDF1:")
|
|
||||||
|
|
||||||
def arm_aquisition
|
|
||||||
|
|
||||||
raise FalconTimeoutError(
|
|
||||||
f"Failed to arm the acquisition. Detector state {signal_conditions[0][0]}"
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
CHANGES LOG and
|
|
||||||
|
|
||||||
System as taken from cSAXS some times works for one element need about 7 second
|
|
||||||
There seem to be still serious timout issues
|
|
||||||
|
|
||||||
changes log
|
|
||||||
TIMEOUT_FOR_SIGNALs from 5 to 10
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
import enum
|
|
||||||
import threading
|
|
||||||
import time
|
|
||||||
|
|
||||||
from bec_lib.logger import bec_logger
|
|
||||||
|
|
||||||
from ophyd import Component as Cpt
|
|
||||||
from ophyd import Device, EpicsSignal, EpicsSignalRO, EpicsSignalWithRBV
|
|
||||||
|
|
||||||
# from ophyd.mca import EpicsMCARecord # old import
|
|
||||||
# now import ophyd.mca completely
|
|
||||||
|
|
||||||
import ophyd.mca as Mca
|
|
||||||
|
|
||||||
from ophyd_devices.interfaces.base_classes.psi_detector_base import (
|
|
||||||
CustomDetectorMixin,
|
|
||||||
PSIDetectorBase,
|
|
||||||
)
|
|
||||||
|
|
||||||
logger = bec_logger.logger
|
|
||||||
|
|
||||||
|
|
||||||
class FalconError(Exception):
|
|
||||||
"""Base class for exceptions in this module."""
|
|
||||||
|
|
||||||
|
|
||||||
class FalconTimeoutError(FalconError):
|
|
||||||
"""Raised when the Falcon does not respond in time."""
|
|
||||||
|
|
||||||
|
|
||||||
class DetectorState(enum.IntEnum):
|
|
||||||
"""Detector states for Falcon detector"""
|
|
||||||
|
|
||||||
DONE = 0
|
|
||||||
ACQUIRING = 1
|
|
||||||
|
|
||||||
|
|
||||||
class TriggerSource(enum.IntEnum):
|
|
||||||
"""Trigger source for Falcon detector"""
|
|
||||||
|
|
||||||
USER = 0
|
|
||||||
GATE = 1
|
|
||||||
SYNC = 2
|
|
||||||
|
|
||||||
|
|
||||||
class MappingSource(enum.IntEnum):
|
|
||||||
"""Mapping source for Falcon detector"""
|
|
||||||
|
|
||||||
SPECTRUM = 0
|
|
||||||
MAPPING = 1
|
|
||||||
|
|
||||||
|
|
||||||
class EpicsMCARecordExtended(Mca.EpicsMCARecord):
|
|
||||||
|
|
||||||
# add parameters for detector energy calibration
|
|
||||||
# which are missing in mca.py
|
|
||||||
|
|
||||||
calo = Cpt(EpicsSignal, ".CALO")
|
|
||||||
cals = Cpt(EpicsSignal, ".CALS")
|
|
||||||
calq = Cpt(EpicsSignal, ".CALQ")
|
|
||||||
tth = Cpt(EpicsSignal, ".TTH")
|
|
||||||
|
|
||||||
|
|
||||||
class EpicsDXPFalcon(Device):
|
|
||||||
"""
|
|
||||||
DXP parameters for Falcon detector
|
|
||||||
|
|
||||||
Base class to map EPICS PVs from DXP parameters to ophyd signals.
|
|
||||||
"""
|
|
||||||
|
|
||||||
elapsed_live_time = Cpt(EpicsSignal, "ElapsedLiveTime")
|
|
||||||
elapsed_real_time = Cpt(EpicsSignal, "ElapsedRealTime")
|
|
||||||
elapsed_trigger_live_time = Cpt(EpicsSignal, "ElapsedTriggerLiveTime")
|
|
||||||
|
|
||||||
# Energy Filter PVs
|
|
||||||
energy_threshold = Cpt(EpicsSignalWithRBV, "DetectionThreshold")
|
|
||||||
min_pulse_separation = Cpt(EpicsSignalWithRBV, "MinPulsePairSeparation")
|
|
||||||
detection_filter = Cpt(EpicsSignalWithRBV, "DetectionFilter", string=True)
|
|
||||||
scale_factor = Cpt(EpicsSignalWithRBV, "ScaleFactor")
|
|
||||||
risetime_optimisation = Cpt(EpicsSignalWithRBV, "RisetimeOptimization")
|
|
||||||
decay_time = Cpt(EpicsSignalWithRBV, "DecayTime")
|
|
||||||
|
|
||||||
current_pixel = Cpt(EpicsSignalRO, "CurrentPixel")
|
|
||||||
|
|
||||||
|
|
||||||
class FalconHDF5Plugins(Device):
|
|
||||||
"""
|
|
||||||
HDF5 parameters for Falcon detector
|
|
||||||
|
|
||||||
Base class to map EPICS PVs from HDF5 Plugin to ophyd signals.
|
|
||||||
"""
|
|
||||||
|
|
||||||
capture = Cpt(EpicsSignalWithRBV, "Capture")
|
|
||||||
enable = Cpt(EpicsSignalWithRBV, "EnableCallbacks", string=True, kind="config")
|
|
||||||
xml_file_name = Cpt(EpicsSignalWithRBV, "XMLFileName", string=True, kind="config")
|
|
||||||
lazy_open = Cpt(EpicsSignalWithRBV, "LazyOpen", string=True, doc="0='No' 1='Yes'")
|
|
||||||
temp_suffix = Cpt(EpicsSignalWithRBV, "TempSuffix", string=True)
|
|
||||||
file_path = Cpt(EpicsSignalWithRBV, "FilePath", string=True, kind="config")
|
|
||||||
file_name = Cpt(EpicsSignalWithRBV, "FileName", string=True, kind="config")
|
|
||||||
file_template = Cpt(EpicsSignalWithRBV, "FileTemplate", string=True, kind="config")
|
|
||||||
num_capture = Cpt(EpicsSignalWithRBV, "NumCapture", kind="config")
|
|
||||||
file_write_mode = Cpt(EpicsSignalWithRBV, "FileWriteMode", kind="config")
|
|
||||||
queue_size = Cpt(EpicsSignalWithRBV, "QueueSize", kind="config")
|
|
||||||
array_counter = Cpt(EpicsSignalWithRBV, "ArrayCounter", kind="config")
|
|
||||||
|
|
||||||
|
|
||||||
class FalconSetup(CustomDetectorMixin):
|
|
||||||
"""
|
|
||||||
Falcon setup class for Phoenix
|
|
||||||
|
|
||||||
Parent class: CustomDetectorMixin
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, *args, parent: Device = None, **kwargs) -> None:
|
|
||||||
super().__init__(*args, parent=parent, **kwargs)
|
|
||||||
self._lock = threading.RLock()
|
|
||||||
|
|
||||||
def on_init(self) -> None:
|
|
||||||
"""Initialize Falcon detector"""
|
|
||||||
self.initialize_default_parameter()
|
|
||||||
self.initialize_detector()
|
|
||||||
self.initialize_detector_backend()
|
|
||||||
|
|
||||||
def initialize_default_parameter(self) -> None:
|
|
||||||
"""
|
|
||||||
Set default parameters for Falcon
|
|
||||||
|
|
||||||
This will set:
|
|
||||||
- readout (float): readout time in seconds
|
|
||||||
- value_pixel_per_buffer (int): number of spectra in buffer of Falcon Sitoro
|
|
||||||
|
|
||||||
"""
|
|
||||||
# self.parent.value_pixel_per_buffer = 20
|
|
||||||
self.update_readout_time()
|
|
||||||
|
|
||||||
def update_readout_time(self) -> None:
|
|
||||||
"""Set readout time for Eiger9M detector"""
|
|
||||||
readout_time = (
|
|
||||||
self.parent.scaninfo.readout_time
|
|
||||||
if hasattr(self.parent.scaninfo, "readout_time")
|
|
||||||
else self.parent.MIN_READOUT
|
|
||||||
)
|
|
||||||
self.parent.readout_time = max(readout_time, self.parent.MIN_READOUT)
|
|
||||||
|
|
||||||
def initialize_detector(self) -> None:
|
|
||||||
"""Initialize Falcon detector"""
|
|
||||||
|
|
||||||
pass
|
|
||||||
|
|
||||||
"""
|
|
||||||
THIS IS THE OLD CSACS CODE. uncomment for now, as we consider EPICS as the boss
|
|
||||||
for initialization
|
|
||||||
|
|
||||||
self.stop_detector()
|
|
||||||
self.stop_detector_backend()
|
|
||||||
|
|
||||||
self.set_trigger(
|
|
||||||
mapping_mode=MappingSource.MAPPING, trigger_source=TriggerSource.GATE, ignore_gate=0
|
|
||||||
)
|
|
||||||
# 1 Realtime
|
|
||||||
self.parent.preset_mode.put(1)
|
|
||||||
# 0 Normal, 1 Inverted
|
|
||||||
self.parent.input_logic_polarity.put(0)
|
|
||||||
# 0 Manual 1 Auto
|
|
||||||
self.parent.auto_pixels_per_buffer.put(0)
|
|
||||||
# Sets the number of pixels/spectra in the buffer
|
|
||||||
self.parent.pixels_per_buffer.put(self.parent.value_pixel_per_buffer)
|
|
||||||
"""
|
|
||||||
|
|
||||||
def initialize_detector_backend(self) -> None:
|
|
||||||
"""Initialize the detector backend for Falcon."""
|
|
||||||
self.parent.hdf5.enable.put(1)
|
|
||||||
# file location of h5 layout for cSAXS
|
|
||||||
self.parent.hdf5.xml_file_name.put("layout.xml")
|
|
||||||
# TODO Check if lazy open is needed and wanted!
|
|
||||||
self.parent.hdf5.lazy_open.put(1)
|
|
||||||
self.parent.hdf5.temp_suffix.put("")
|
|
||||||
# size of queue for number of spectra allowed in the buffer, if too small at high throughput, data is lost
|
|
||||||
self.parent.hdf5.queue_size.put(2000)
|
|
||||||
# Segmentation into Spectra within EPICS, 1 is activate, 0 is deactivate
|
|
||||||
self.parent.nd_array_mode.put(1)
|
|
||||||
|
|
||||||
def on_stage(self) -> None:
|
|
||||||
"""Prepare detector and backend for acquisition"""
|
|
||||||
self.prepare_detector()
|
|
||||||
self.prepare_data_backend()
|
|
||||||
self.publish_file_location(done=False, successful=False)
|
|
||||||
self.arm_acquisition()
|
|
||||||
|
|
||||||
def prepare_detector(self) -> None:
|
|
||||||
"""Prepare detector for acquisition"""
|
|
||||||
self.set_trigger(
|
|
||||||
mapping_mode=MappingSource.MAPPING, trigger_source=TriggerSource.GATE, ignore_gate=0
|
|
||||||
)
|
|
||||||
self.parent.preset_real.put(self.parent.scaninfo.exp_time)
|
|
||||||
self.parent.pixels_per_run.put(
|
|
||||||
int(self.parent.scaninfo.num_points * self.parent.scaninfo.frames_per_trigger)
|
|
||||||
)
|
|
||||||
|
|
||||||
def prepare_data_backend(self) -> None:
|
|
||||||
"""Prepare data backend for acquisition"""
|
|
||||||
self.parent.filepath.set(
|
|
||||||
self.parent.filewriter.compile_full_filename(f"{self.parent.name}.h5")
|
|
||||||
).wait()
|
|
||||||
file_path, file_name = os.path.split(self.parent.filepath.get())
|
|
||||||
self.parent.hdf5.file_path.put(file_path)
|
|
||||||
self.parent.hdf5.file_name.put(file_name)
|
|
||||||
self.parent.hdf5.file_template.put("%s%s")
|
|
||||||
self.parent.hdf5.num_capture.put(
|
|
||||||
int(self.parent.scaninfo.num_points * self.parent.scaninfo.frames_per_trigger)
|
|
||||||
)
|
|
||||||
self.parent.hdf5.file_write_mode.put(2)
|
|
||||||
# Reset spectrum counter in filewriter, used for indexing & identifying missing triggers
|
|
||||||
self.parent.hdf5.array_counter.put(0)
|
|
||||||
# Start file writing
|
|
||||||
self.parent.hdf5.capture.put(1)
|
|
||||||
|
|
||||||
def arm_acquisition(self) -> None:
|
|
||||||
"""Arm detector for acquisition"""
|
|
||||||
self.parent.start_all.put(1)
|
|
||||||
signal_conditions = [
|
|
||||||
(
|
|
||||||
lambda: self.parent.acquiring.read()[self.parent.acquiring.name]["value"],
|
|
||||||
DetectorState.ACQUIRING,
|
|
||||||
)
|
|
||||||
]
|
|
||||||
if not self.wait_for_signals(
|
|
||||||
signal_conditions=signal_conditions,
|
|
||||||
timeout=self.parent.TIMEOUT_FOR_SIGNALS,
|
|
||||||
check_stopped=True,
|
|
||||||
all_signals=False,
|
|
||||||
):
|
|
||||||
raise FalconTimeoutError(
|
|
||||||
f"Failed to arm the acquisition. Detector state {signal_conditions[0][0]}"
|
|
||||||
)
|
|
||||||
|
|
||||||
def on_unstage(self) -> None:
|
|
||||||
"""Unstage detector and backend"""
|
|
||||||
pass
|
|
||||||
|
|
||||||
def on_complete(self) -> None:
|
|
||||||
"""Complete detector and backend"""
|
|
||||||
self.finished(timeout=self.parent.TIMEOUT_FOR_SIGNALS)
|
|
||||||
self.publish_file_location(done=True, successful=True)
|
|
||||||
|
|
||||||
def on_stop(self) -> None:
|
|
||||||
"""Stop detector and backend"""
|
|
||||||
self.stop_detector()
|
|
||||||
self.stop_detector_backend()
|
|
||||||
|
|
||||||
def stop_detector(self) -> None:
|
|
||||||
"""Stops detector"""
|
|
||||||
|
|
||||||
self.parent.stop_all.put(1)
|
|
||||||
time.sleep(0.5)
|
|
||||||
self.parent.erase_all.put(1)
|
|
||||||
time.sleep(0.5)
|
|
||||||
|
|
||||||
signal_conditions = [
|
|
||||||
(
|
|
||||||
lambda: self.parent.acquiring.read()[self.parent.acquiring.name]["value"],
|
|
||||||
DetectorState.DONE,
|
|
||||||
)
|
|
||||||
]
|
|
||||||
|
|
||||||
if not self.wait_for_signals(
|
|
||||||
signal_conditions=signal_conditions,
|
|
||||||
timeout=self.parent.TIMEOUT_FOR_SIGNALS - self.parent.TIMEOUT_FOR_SIGNALS // 2,
|
|
||||||
all_signals=False,
|
|
||||||
):
|
|
||||||
# Retry stop detector and wait for remaining time
|
|
||||||
raise FalconTimeoutError(
|
|
||||||
f"Failed to stop detector, timeout with state {signal_conditions[0][0]}"
|
|
||||||
)
|
|
||||||
|
|
||||||
def stop_detector_backend(self) -> None:
|
|
||||||
"""Stop the detector backend"""
|
|
||||||
self.parent.hdf5.capture.put(0)
|
|
||||||
|
|
||||||
def finished(self, timeout: int = 5) -> None:
|
|
||||||
"""Check if scan finished succesfully"""
|
|
||||||
with self._lock:
|
|
||||||
total_frames = int(
|
|
||||||
self.parent.scaninfo.num_points * self.parent.scaninfo.frames_per_trigger
|
|
||||||
)
|
|
||||||
signal_conditions = [
|
|
||||||
(self.parent.dxp.current_pixel.get, total_frames),
|
|
||||||
(self.parent.hdf5.array_counter.get, total_frames),
|
|
||||||
]
|
|
||||||
if not self.wait_for_signals(
|
|
||||||
signal_conditions=signal_conditions,
|
|
||||||
timeout=timeout,
|
|
||||||
check_stopped=True,
|
|
||||||
all_signals=True,
|
|
||||||
):
|
|
||||||
logger.debug(
|
|
||||||
f"Falcon missed a trigger: received trigger {self.parent.dxp.current_pixel.get()},"
|
|
||||||
f" send data {self.parent.hdf5.array_counter.get()} from total_frames"
|
|
||||||
f" {total_frames}"
|
|
||||||
)
|
|
||||||
self.stop_detector()
|
|
||||||
self.stop_detector_backend()
|
|
||||||
|
|
||||||
def set_trigger(
|
|
||||||
self, mapping_mode: MappingSource, trigger_source: TriggerSource, ignore_gate: int = 0
|
|
||||||
) -> None:
|
|
||||||
"""
|
|
||||||
Set triggering mode for detector
|
|
||||||
|
|
||||||
Args:
|
|
||||||
mapping_mode (MappingSource): Mapping mode for the detector
|
|
||||||
trigger_source (TriggerSource): Trigger source for the detector, pixel_advance_signal
|
|
||||||
ignore_gate (int): Ignore gate from TTL signal; defaults to 0
|
|
||||||
|
|
||||||
"""
|
|
||||||
mapping = int(mapping_mode)
|
|
||||||
trigger = trigger_source
|
|
||||||
self.parent.collect_mode.put(mapping)
|
|
||||||
self.parent.pixel_advance_mode.put(trigger)
|
|
||||||
self.parent.ignore_gate.put(ignore_gate)
|
|
||||||
|
|
||||||
|
|
||||||
class FalconPhoenix(PSIDetectorBase):
|
|
||||||
"""
|
|
||||||
Falcon Sitoro detector for Phoenix
|
|
||||||
|
|
||||||
|
|
||||||
Parent class: PSIDetectorBase
|
|
||||||
|
|
||||||
class attributes:
|
|
||||||
custom_prepare_cls (FalconSetup) : Custom detector setup class for cSAXS,
|
|
||||||
inherits from CustomDetectorMixin
|
|
||||||
PSIDetectorBase.set_min_readout (float) : Minimum readout time for the detector
|
|
||||||
dxp (EpicsDXPFalcon) : DXP parameters for Falcon detector
|
|
||||||
mca (EpicsMCARecord) : MCA parameters for Falcon detector
|
|
||||||
hdf5 (FalconHDF5Plugins) : HDF5 parameters for Falcon detector
|
|
||||||
MIN_READOUT (float) : Minimum readout time for the detector
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
# Specify which functions are revealed to the user in BEC client
|
|
||||||
USER_ACCESS = ["describe"]
|
|
||||||
|
|
||||||
# specify Setup class
|
|
||||||
custom_prepare_cls = FalconSetup
|
|
||||||
# specify minimum readout time for detector
|
|
||||||
MIN_READOUT = 3e-3
|
|
||||||
TIMEOUT_FOR_SIGNALS = 1
|
|
||||||
|
|
||||||
# specify class attributes
|
|
||||||
|
|
||||||
# Parameters for individual detector elements
|
|
||||||
# Note: need to wrote 'dxp: here, but not dxp'
|
|
||||||
|
|
||||||
# dxp1 = Cpt(Mca.EpicsDXP, "dxp1:")
|
|
||||||
dxp1 = Cpt(EpicsDXPFalcon, "dxp1:")
|
|
||||||
# dxp2 = Cpt(EpicsDXPFalcon, "dxp2:")
|
|
||||||
# dxp3 = Cpt(EpicsDXPFalcon, "dxp3:")
|
|
||||||
# dxp4 = Cpt(EpicsDXPFalcon, "dxp4:")
|
|
||||||
|
|
||||||
#
|
|
||||||
# THIS IS NOT WELL-DONE as it take out one part of mca.py from ophy.py
|
|
||||||
#
|
|
||||||
#
|
|
||||||
mca1 = Cpt(EpicsMCARecordExtended, "mca1")
|
|
||||||
# mca2 = Cpt(EpicsMCARecordExtended, "mca2")
|
|
||||||
# mca3 = Cpt(EpicsMCARecordExtended, "mca3")
|
|
||||||
# mca4 = Cpt(EpicsMCARecordExtended, "mca4")
|
|
||||||
|
|
||||||
# need to write 'mca1', but not 'mca1:'
|
|
||||||
# mca1 = Cpt(EpicsMCARecord, "mca1")
|
|
||||||
# mca2 = Cpt(EpicsMCARecord, "mca2")
|
|
||||||
# mca3 = Cpt(EpicsMCARecord, "mca3")
|
|
||||||
# mca4 = Cpt(EpicsMCARecord, "mca4")
|
|
||||||
|
|
||||||
# other general parameters
|
|
||||||
hdf5 = Cpt(FalconHDF5Plugins, "HDF1:")
|
|
||||||
|
|
||||||
stop_all = Cpt(EpicsSignal, "StopAll")
|
|
||||||
erase_all = Cpt(EpicsSignal, "EraseAll")
|
|
||||||
start_all = Cpt(EpicsSignal, "StartAll")
|
|
||||||
# state = Cpt(EpicsSignal, "Acquiring") # <-- This is from cSAX implementation
|
|
||||||
acquiring = Cpt(EpicsSignal, "Acquiring")
|
|
||||||
preset_mode = Cpt(EpicsSignal, "PresetMode") # 0 No preset 1 Real time 2 Events 3 Triggers
|
|
||||||
preset_real = Cpt(EpicsSignal, "PresetReal")
|
|
||||||
preset_events = Cpt(EpicsSignal, "PresetEvents")
|
|
||||||
preset_triggers = Cpt(EpicsSignal, "PresetTriggers")
|
|
||||||
|
|
||||||
# _________________ General Epic parameters
|
|
||||||
|
|
||||||
# changes Oct 2024
|
|
||||||
# triggers--> max_triggers,
|
|
||||||
# events-->max_events
|
|
||||||
# input_count_rate--> max_input_count_rate
|
|
||||||
# output_count_rate--> max_output_count_rate
|
|
||||||
|
|
||||||
max_triggers = Cpt(EpicsSignalRO, "MaxTriggers", lazy=True)
|
|
||||||
max_events = Cpt(EpicsSignalRO, "MaxEvents", lazy=True)
|
|
||||||
|
|
||||||
max_input_count_rate = Cpt(EpicsSignalRO, "MaxInputCountRate", lazy=True)
|
|
||||||
max_output_count_rate = Cpt(EpicsSignalRO, "MaxOutputCountRate", lazy=True)
|
|
||||||
|
|
||||||
collect_mode = Cpt(EpicsSignal, "CollectMode") # 0 MCA spectra, 1 MCA mapping
|
|
||||||
pixel_advance_mode = Cpt(EpicsSignal, "PixelAdvanceMode")
|
|
||||||
ignore_gate = Cpt(EpicsSignal, "IgnoreGate")
|
|
||||||
input_logic_polarity = Cpt(EpicsSignal, "InputLogicPolarity")
|
|
||||||
auto_pixels_per_buffer = Cpt(EpicsSignal, "AutoPixelsPerBuffer")
|
|
||||||
pixels_per_buffer = Cpt(EpicsSignal, "PixelsPerBuffer")
|
|
||||||
pixels_per_run = Cpt(EpicsSignal, "PixelsPerRun")
|
|
||||||
# print(pixel_per_run
|
|
||||||
# if "SITORO" in prefix:
|
|
||||||
nd_array_mode = Cpt(EpicsSignal, "NDArrayMode")
|
|
||||||
# endif
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
falcon = FalconPhoenix(name="falcon_hdf5", prefix="X07MB-SITORO:", sim_mode=True)
|
|
@ -1,6 +1,6 @@
|
|||||||
#
|
#
|
||||||
#
|
#
|
||||||
# changes version for PHOENIX WITHOUT HDF5 plugin to be revised/renamed...
|
# changes version for PHOENIX WITHOUT HDF5 plugin to be revised/renamed...
|
||||||
#
|
#
|
||||||
#
|
#
|
||||||
|
|
||||||
@ -82,7 +82,7 @@ class FalconHDF5Plugins(Device):
|
|||||||
Base class to map EPICS PVs from HDF5 Plugin to ophyd signals.
|
Base class to map EPICS PVs from HDF5 Plugin to ophyd signals.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
""" ----------------------------------------------------------------------------
|
""" ----------------------------------------------------------------------------
|
||||||
capture = Cpt(EpicsSignalWithRBV, "Capture")
|
capture = Cpt(EpicsSignalWithRBV, "Capture")
|
||||||
enable = Cpt(EpicsSignalWithRBV, "EnableCallbacks", string=True, kind="config")
|
enable = Cpt(EpicsSignalWithRBV, "EnableCallbacks", string=True, kind="config")
|
||||||
xml_file_name = Cpt(EpicsSignalWithRBV, "XMLFileName", string=True, kind="config")
|
xml_file_name = Cpt(EpicsSignalWithRBV, "XMLFileName", string=True, kind="config")
|
||||||
@ -97,7 +97,6 @@ class FalconHDF5Plugins(Device):
|
|||||||
array_counter = Cpt(EpicsSignalWithRBV, "ArrayCounter", kind="config")
|
array_counter = Cpt(EpicsSignalWithRBV, "ArrayCounter", kind="config")
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
class FalconSetup(CustomDetectorMixin):
|
class FalconSetup(CustomDetectorMixin):
|
||||||
"""
|
"""
|
||||||
Falcon setup class for cSAXS
|
Falcon setup class for cSAXS
|
||||||
@ -114,7 +113,7 @@ class FalconSetup(CustomDetectorMixin):
|
|||||||
"""Initialize Falcon detector"""
|
"""Initialize Falcon detector"""
|
||||||
self.initialize_default_parameter()
|
self.initialize_default_parameter()
|
||||||
self.initialize_detector()
|
self.initialize_detector()
|
||||||
# self.initialize_detector_backend()
|
self.initialize_detector_backend()
|
||||||
|
|
||||||
def initialize_default_parameter(self) -> None:
|
def initialize_default_parameter(self) -> None:
|
||||||
"""
|
"""
|
||||||
@ -140,12 +139,12 @@ class FalconSetup(CustomDetectorMixin):
|
|||||||
def initialize_detector(self) -> None:
|
def initialize_detector(self) -> None:
|
||||||
"""Initialize Falcon detector"""
|
"""Initialize Falcon detector"""
|
||||||
self.stop_detector()
|
self.stop_detector()
|
||||||
# self.stop_detector_backend()
|
self.stop_detector_backend()
|
||||||
self.set_trigger(
|
self.set_trigger(
|
||||||
mapping_mode=MappingSource.SPECTRUM, trigger_source=TriggerSource.GATE, ignore_gate=0
|
mapping_mode=MappingSource.MAPPING, trigger_source=TriggerSource.GATE, ignore_gate=0
|
||||||
)
|
)
|
||||||
# 1 Realtime
|
# 1 Realtime
|
||||||
self.parent.preset_mode.put(0)
|
self.parent.preset_mode.put(1)
|
||||||
# 0 Normal, 1 Inverted
|
# 0 Normal, 1 Inverted
|
||||||
self.parent.input_logic_polarity.put(0)
|
self.parent.input_logic_polarity.put(0)
|
||||||
# 0 Manual 1 Auto
|
# 0 Manual 1 Auto
|
||||||
@ -155,34 +154,30 @@ class FalconSetup(CustomDetectorMixin):
|
|||||||
|
|
||||||
def initialize_detector_backend(self) -> None:
|
def initialize_detector_backend(self) -> None:
|
||||||
"""Initialize the detector backend for Falcon."""
|
"""Initialize the detector backend for Falcon."""
|
||||||
w = 0
|
w=0
|
||||||
# ----------------------------------------------------------------------
|
#----------------------------------------------------------------------
|
||||||
# self.parent.hdf5.enable.put(1)
|
#self.parent.hdf5.enable.put(1)
|
||||||
# file location of h5 layout for cSAXS
|
# file location of h5 layout for cSAXS
|
||||||
# self.parent.hdf5.xml_file_name.put("layout.xml")
|
#self.parent.hdf5.xml_file_name.put("layout.xml")
|
||||||
# TODO Check if lazy open is needed and wanted!
|
# TODO Check if lazy open is needed and wanted!
|
||||||
# self.parent.hdf5.lazy_open.put(1)
|
#self.parent.hdf5.lazy_open.put(1)
|
||||||
# self.parent.hdf5.temp_suffix.put("")
|
#self.parent.hdf5.temp_suffix.put("")
|
||||||
# size of queue for number of spectra allowed in the buffer, if too small at high throughput, data is lost
|
# size of queue for number of spectra allowed in the buffer, if too small at high throughput, data is lost
|
||||||
# self.parent.hdf5.queue_size.put(2000)
|
#self.parent.hdf5.queue_size.put(2000)
|
||||||
# Segmentation into Spectra within EPICS, 1 is activate, 0 is deactivate
|
# Segmentation into Spectra within EPICS, 1 is activate, 0 is deactivate
|
||||||
# self.parent.nd_array_mode.put(1)
|
#self.parent.nd_array_mode.put(1)
|
||||||
|
|
||||||
def on_stage(self) -> None:
|
def on_stage(self) -> None:
|
||||||
"""Prepare detector and backend for acquisition"""
|
"""Prepare detector and backend for acquisition"""
|
||||||
self.prepare_detector()
|
self.prepare_detector()
|
||||||
# self.prepare_data_backend()
|
self.prepare_data_backend()
|
||||||
# self.publish_file_location(done=False, successful=False)
|
self.publish_file_location(done=False, successful=False)
|
||||||
# self.arm_acquisition()
|
|
||||||
|
|
||||||
def on_trigger(self) -> None:
|
|
||||||
"""Actions on pre_scan. This is performed AFTER stage, just before scan_core"""
|
|
||||||
self.arm_acquisition()
|
self.arm_acquisition()
|
||||||
|
|
||||||
def prepare_detector(self) -> None:
|
def prepare_detector(self) -> None:
|
||||||
"""Prepare detector for acquisition"""
|
"""Prepare detector for acquisition"""
|
||||||
self.set_trigger(
|
self.set_trigger(
|
||||||
mapping_mode=MappingSource.SPECTRUM, trigger_source=TriggerSource.GATE, ignore_gate=0
|
mapping_mode=MappingSource.MAPPING, trigger_source=TriggerSource.GATE, ignore_gate=0
|
||||||
)
|
)
|
||||||
self.parent.preset_real.put(self.parent.scaninfo.exp_time)
|
self.parent.preset_real.put(self.parent.scaninfo.exp_time)
|
||||||
self.parent.pixels_per_run.put(
|
self.parent.pixels_per_run.put(
|
||||||
@ -191,7 +186,7 @@ class FalconSetup(CustomDetectorMixin):
|
|||||||
|
|
||||||
def prepare_data_backend(self) -> None:
|
def prepare_data_backend(self) -> None:
|
||||||
"""Prepare data backend for acquisition"""
|
"""Prepare data backend for acquisition"""
|
||||||
w = 9
|
w=9
|
||||||
""" --------------------------------------------------------------
|
""" --------------------------------------------------------------
|
||||||
self.parent.filepath.set(
|
self.parent.filepath.set(
|
||||||
self.parent.filewriter.compile_full_filename(f"{self.parent.name}.h5")
|
self.parent.filewriter.compile_full_filename(f"{self.parent.name}.h5")
|
||||||
@ -235,31 +230,30 @@ class FalconSetup(CustomDetectorMixin):
|
|||||||
|
|
||||||
def on_complete(self) -> None:
|
def on_complete(self) -> None:
|
||||||
"""Complete detector and backend"""
|
"""Complete detector and backend"""
|
||||||
# ------------------------------------------------------------------
|
#------------------------------------------------------------------
|
||||||
# self.finished(timeout=self.parent.TIMEOUT_FOR_SIGNALS)
|
#self.finished(timeout=self.parent.TIMEOUT_FOR_SIGNALS)
|
||||||
# self.publish_file_location(done=True, successful=True)
|
#self.publish_file_location(done=True, successful=True)
|
||||||
w = 9
|
w=9
|
||||||
|
|
||||||
def on_stop(self) -> None:
|
def on_stop(self) -> None:
|
||||||
"""Stop detector and backend"""
|
"""Stop detector and backend"""
|
||||||
self.stop_detector()
|
self.stop_detector()
|
||||||
# self.stop_detector_backend()
|
#self.stop_detector_backend()
|
||||||
|
|
||||||
def stop_detector(self) -> None:
|
def stop_detector(self) -> None:
|
||||||
"""Stops detector"""
|
"""Stops detector"""
|
||||||
|
|
||||||
self.parent.stop_all.put(1)
|
self.parent.stop_all.put(1)
|
||||||
self.parent.erase_all.put(1)
|
self.parent.erase_all.put(1)
|
||||||
# -------------------------------------------------------------------
|
#-------------------------------------------------------------------
|
||||||
# signal_conditions = [
|
#signal_conditions = [
|
||||||
# (lambda: self.parent.state.read()[self.parent.state.name]["value"], DetectorState.DONE)
|
# (lambda: self.parent.state.read()[self.parent.state.name]["value"], DetectorState.DONE)
|
||||||
# ]
|
#]
|
||||||
|
|
||||||
# if not self.wait_for_signals(
|
#if not self.wait_for_signals(
|
||||||
# signal_conditions=signal_conditions,
|
# signal_conditions=signal_conditions,
|
||||||
# timeout=self.parent.TIMEOUT_FOR_SIGNALS - self.parent.TIMEOUT_FOR_SIGNALS // 2,
|
# timeout=self.parent.TIMEOUT_FOR_SIGNALS - self.parent.TIMEOUT_FOR_SIGNALS // 2,
|
||||||
# all_signals=False,
|
# all_signals=False,
|
||||||
# ):
|
#):
|
||||||
# # Retry stop detector and wait for remaining time
|
# # Retry stop detector and wait for remaining time
|
||||||
# raise FalconTimeoutError(
|
# raise FalconTimeoutError(
|
||||||
# f"Failed to stop detector, timeout with state {signal_conditions[0][0]}"
|
# f"Failed to stop detector, timeout with state {signal_conditions[0][0]}"
|
||||||
@ -267,8 +261,8 @@ class FalconSetup(CustomDetectorMixin):
|
|||||||
|
|
||||||
def stop_detector_backend(self) -> None:
|
def stop_detector_backend(self) -> None:
|
||||||
"""Stop the detector backend"""
|
"""Stop the detector backend"""
|
||||||
# self.parent.hdf5.capture.put(0)
|
#self.parent.hdf5.capture.put(0)
|
||||||
w = 0
|
w=0
|
||||||
|
|
||||||
def finished(self, timeout: int = 5) -> None:
|
def finished(self, timeout: int = 5) -> None:
|
||||||
"""Check if scan finished succesfully"""
|
"""Check if scan finished succesfully"""
|
||||||
@ -278,7 +272,7 @@ class FalconSetup(CustomDetectorMixin):
|
|||||||
)
|
)
|
||||||
signal_conditions = [
|
signal_conditions = [
|
||||||
(self.parent.dxp.current_pixel.get, total_frames),
|
(self.parent.dxp.current_pixel.get, total_frames),
|
||||||
# (self.parent.hdf5.array_counter.get, total_frames), ---------------------
|
# (self.parent.hdf5.array_counter.get, total_frames), ---------------------
|
||||||
]
|
]
|
||||||
if not self.wait_for_signals(
|
if not self.wait_for_signals(
|
||||||
signal_conditions=signal_conditions,
|
signal_conditions=signal_conditions,
|
||||||
@ -313,17 +307,19 @@ class FalconSetup(CustomDetectorMixin):
|
|||||||
self.parent.ignore_gate.put(ignore_gate)
|
self.parent.ignore_gate.put(ignore_gate)
|
||||||
|
|
||||||
|
|
||||||
class FalconPhoenix(PSIDetectorBase):
|
class FalconcSAXS(PSIDetectorBase):
|
||||||
"""
|
"""
|
||||||
Falcon detector for phoenix
|
Falcon Sitoro detector for CSAXS
|
||||||
custom_prepare_cls (XMAPSetu
|
|
||||||
custom_prepare_cls (XMAPSetup) : Custom detector setup class for cSAXS,
|
Parent class: PSIDetectorBase
|
||||||
|
|
||||||
|
class attributes:
|
||||||
|
custom_prepare_cls (FalconSetup) : Custom detector setup class for cSAXS,
|
||||||
inherits from CustomDetectorMixin
|
inherits from CustomDetectorMixin
|
||||||
in __init__ of PSIDetecor base
|
|
||||||
PSIDetectorBase.set_min_readout (float) : Minimum readout time for the detector
|
PSIDetectorBase.set_min_readout (float) : Minimum readout time for the detector
|
||||||
dxp (EpicsDXPXMAP) : DXP parameters for XMAP detector
|
dxp (EpicsDXPFalcon) : DXP parameters for Falcon detector
|
||||||
mca (EpicsMCARecord) : MCA parameters for XMAP detector
|
mca (EpicsMCARecord) : MCA parameters for Falcon detector
|
||||||
hdf5 (XMAPHDF5Plugins) : HDF5 parameters for XMAP detector
|
hdf5 (FalconHDF5Plugins) : HDF5 parameters for Falcon detector
|
||||||
MIN_READOUT (float) : Minimum readout time for the detector
|
MIN_READOUT (float) : Minimum readout time for the detector
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@ -364,4 +360,4 @@ class FalconPhoenix(PSIDetectorBase):
|
|||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
falcon = FalconPhoenix(name="falcon", prefix="X07MB-SITORO:", sim_mode=True)
|
falcon = FalconcSAXS(name="falcon", prefix="X12SA-SITORO:", sim_mode=True)
|
||||||
|
@ -1,244 +0,0 @@
|
|||||||
"""
|
|
||||||
Module for the PhoenixTrigger class to connect to the ADC card
|
|
||||||
that creates TTL signals to trigger cameras and detectors at Phoenix.
|
|
||||||
|
|
||||||
TO Do
|
|
||||||
|
|
||||||
-- allow for variable dwell times
|
|
||||||
-- add erase/Start for XMAP and FALCON
|
|
||||||
-- check values for time.sleep()
|
|
||||||
-- check in on_triggerthe status check for Falcon
|
|
||||||
-- rework togther with Xiaoquiang the functionality of involved EPICS channels
|
|
||||||
(Callbacks etc.) to minimize the need of sleeping times.
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
import enum
|
|
||||||
import time
|
|
||||||
|
|
||||||
import numpy as np
|
|
||||||
from bec_lib import bec_logger
|
|
||||||
from ophyd import Component as Cpt
|
|
||||||
from ophyd import DeviceStatus, EpicsSignal, EpicsSignalRO, Kind
|
|
||||||
from ophyd_devices.interfaces.base_classes.psi_detector_base import (
|
|
||||||
CustomDetectorMixin,
|
|
||||||
PSIDetectorBase,
|
|
||||||
)
|
|
||||||
|
|
||||||
logger = bec_logger.logger
|
|
||||||
|
|
||||||
DETECTOR_TIMEOUT = 5
|
|
||||||
|
|
||||||
|
|
||||||
class PhoenixTriggerError(Exception):
|
|
||||||
"""PhoenixTrigger specific error"""
|
|
||||||
|
|
||||||
|
|
||||||
class SAMPLING(int, enum.Enum):
|
|
||||||
"""Sampling Done PV
|
|
||||||
|
|
||||||
This class serves redabilty of missinx class and ensure correct setting of
|
|
||||||
certain conditions.
|
|
||||||
defiend preset values for certain states to be called in the
|
|
||||||
mixing class, where we for example check whether the sampling is done of not
|
|
||||||
by comparing with SAMPLING.DONE
|
|
||||||
xxx==SAMPLING.DONE
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
RUNNING = 0
|
|
||||||
DONE = 1
|
|
||||||
|
|
||||||
|
|
||||||
class PhoenixTriggerSetup(CustomDetectorMixin):
|
|
||||||
"""
|
|
||||||
Mixin Class to setup the PhoenixTrigger device
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
def on_stage(self) -> None:
|
|
||||||
"""
|
|
||||||
|
|
||||||
On stage actions which are executed upon staging the device
|
|
||||||
|
|
||||||
|
|
||||||
"""
|
|
||||||
if self.parent.scaninfo.scan_type == "step": # check whether we use step scanning
|
|
||||||
###############
|
|
||||||
# next lines ensure that TTL trigger is on single sampling mode (posible )
|
|
||||||
##############
|
|
||||||
self.parent.start_csmpl.set(0)
|
|
||||||
time.sleep(0.1)
|
|
||||||
self.parent.total_cycles.set(1)
|
|
||||||
self.parent.smpl.put(1)
|
|
||||||
time.sleep(0.5)
|
|
||||||
#####
|
|
||||||
# set sampling to dwell time of scan
|
|
||||||
######
|
|
||||||
self.parent.total_cycles.set(np.ceil(self.parent.scaninfo.exp_time * 5))
|
|
||||||
|
|
||||||
logger.info(f"Device {self.parent.name} was staged for step scan")
|
|
||||||
|
|
||||||
def on_unstage(self) -> None:
|
|
||||||
"""On unstage actions which are executed upon unstaging the device"""
|
|
||||||
|
|
||||||
self.on_stop()
|
|
||||||
|
|
||||||
def on_trigger(self) -> DeviceStatus:
|
|
||||||
"""On trigger actions which are executed upon triggering the device"""
|
|
||||||
|
|
||||||
# TODO Test the proper check for the falcon state
|
|
||||||
# Check first that falcon is set to acquiring
|
|
||||||
falcon = self.parent.device_manager.devices.get("falcon_nohdf5", None) # get device
|
|
||||||
timeout = 1
|
|
||||||
if falcon is not None:
|
|
||||||
# TODO Check that falcon.state.get() == 1 is the correct check.
|
|
||||||
# --> When is the falcon acquiring, this assumes 1?
|
|
||||||
# self.wait_for_signals is defined in PSI_detector_base.CustomDetectorMixin
|
|
||||||
#
|
|
||||||
|
|
||||||
if not self.wait_for_signals([(falcon.state.get, 1)], timeout=timeout):
|
|
||||||
raise PhoenixTriggerError(
|
|
||||||
f"Device {self.parent.name} is not ready to take trigger, timeout due to waiting for Falcon to get ready. Timeout after {timeout}s"
|
|
||||||
)
|
|
||||||
|
|
||||||
if self.parent.scaninfo.scan_type == "step":
|
|
||||||
time.sleep(0.2)
|
|
||||||
self.parent.smpl.put(1)
|
|
||||||
# Minimum of 1 cycle has to be waited. Cycle == 0.2s
|
|
||||||
|
|
||||||
time.sleep(0.2)
|
|
||||||
|
|
||||||
# Trigger function from ophyd.Device returns a DeviceStatus. This function
|
|
||||||
# starts a process that creates a DeviceStatus, and waits for the signal_conditions
|
|
||||||
# self.parent.smpl_done.get to change to the value SAMPLING.DONE
|
|
||||||
# Once this takes place, the DeviceStatus.done flag will be set to True.
|
|
||||||
# When BEC calls trigger() on the devices, this method will be called assuming that
|
|
||||||
# the devices config softwareTrigger=True is set.
|
|
||||||
# In ScanBase, the _at_each_point function calls
|
|
||||||
# self.stubs.wait(wait_type="trigger", group="trigger", wait_time=self.exp_time)
|
|
||||||
# which ensures that the DeviceStatus object resolves before continuing,
|
|
||||||
# i.e. DeviceStatus.done = True
|
|
||||||
|
|
||||||
status = self.wait_with_status(
|
|
||||||
signal_conditions=[(self.parent.smpl_done.get, SAMPLING.DONE)],
|
|
||||||
timeout=5 * self.parent.scaninfo.exp_time, # Check if timeout is appropriate
|
|
||||||
check_stopped=True,
|
|
||||||
)
|
|
||||||
|
|
||||||
# explanation of last line (self.parent.smpl_done.get, SAMPLINGDONE.DONE)
|
|
||||||
# creates a tuple which defines a
|
|
||||||
# condition, which is tested in self.wait_with_status, here it tests for :
|
|
||||||
# (self.parent.smpl_done.get() == SAMPLINGDONE.DONE),
|
|
||||||
# where SAMPLINGDONE.DONE =1, as set in code above
|
|
||||||
# As this is in mixing class (PhoenixtriggerSetup), parent.sample_done is defined in
|
|
||||||
# main class as smpl_done = Cpt(EpicsSignalRO, "SMPL-DONE", kind=Kind.config)
|
|
||||||
|
|
||||||
return status # should this be in if clause level or outside?
|
|
||||||
# endif
|
|
||||||
|
|
||||||
def on_stop(self) -> None:
|
|
||||||
"""
|
|
||||||
Actions to stop the Device
|
|
||||||
Here the device is switched back to continuous sampling.
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
self.parent.total_cycles.set(5)
|
|
||||||
|
|
||||||
self.parent.start_csmpl.set(1)
|
|
||||||
time.sleep(0.5)
|
|
||||||
self.parent.smpl.put(1)
|
|
||||||
time.sleep(0.5)
|
|
||||||
self.parent.smpl.put(1)
|
|
||||||
time.sleep(0.2)
|
|
||||||
if self.parent.smpl_done.get() == SAMPLING.RUNNING:
|
|
||||||
return
|
|
||||||
self.parent.smpl.put(1)
|
|
||||||
|
|
||||||
|
|
||||||
class PhoenixTrigger(PSIDetectorBase):
|
|
||||||
"""
|
|
||||||
Class for PHOENIX TTL hardware trigger: 'X07MB-OP2:'
|
|
||||||
|
|
||||||
This device is used to trigger communicate with an ADC card
|
|
||||||
that creates TTL signals to trigger cameras and detectors at Phoenix.
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
##################################################################
|
|
||||||
#
|
|
||||||
# The Variable USER_ACCESS contains an ascii list of functions which will be
|
|
||||||
# visible in dev.TTL. Here, this list is empty.
|
|
||||||
# note that components are alway public
|
|
||||||
#
|
|
||||||
##################################################################
|
|
||||||
|
|
||||||
USER_ACCESS = []
|
|
||||||
|
|
||||||
####################################################################
|
|
||||||
#
|
|
||||||
# # specify Setup class into variable custom_prepare_cls
|
|
||||||
#
|
|
||||||
####################################################################
|
|
||||||
|
|
||||||
custom_prepare_cls = PhoenixTriggerSetup
|
|
||||||
|
|
||||||
###################################################################
|
|
||||||
# in __init__ of PSIDetectorBase will the initialzed by
|
|
||||||
# self.custom_prepare = self.custom_prepare_cls(parent=self, **kwargs)
|
|
||||||
# making the instance of PSIDetectorBase availble to functions
|
|
||||||
# in PhoenixTriggerSetup.
|
|
||||||
#
|
|
||||||
# This inherits, among other things, the input parameters, such as
|
|
||||||
# the notable prefix, which is here 'X07MB-OP2:'
|
|
||||||
#
|
|
||||||
# The input of Component=Cpt is Cpt(deviceClass,suffix)
|
|
||||||
# if Cpt is used in a class, which has interited Device, here via:
|
|
||||||
#
|
|
||||||
# (Here PhoenixTrigger <-- PSIDetectorBase <- Device
|
|
||||||
#
|
|
||||||
# then Cpt will construct - magically- the
|
|
||||||
# Epics channel name = prefix+suffix,
|
|
||||||
#
|
|
||||||
# for example
|
|
||||||
# 'X07MB-OP2:' + 'START-CSMPL' -> 'X07MB-OP2:' + 'START-CSMPL'
|
|
||||||
#
|
|
||||||
# to construct names, for now we keep the convention to derive
|
|
||||||
# them from the EPICS NAMES (such as ) X07MB-OP2:SMPL-DONE --> smpl_done
|
|
||||||
#
|
|
||||||
# this mean access to channel using dev.PH_TTL.smpl_done.get()
|
|
||||||
#
|
|
||||||
#
|
|
||||||
###################################################################
|
|
||||||
|
|
||||||
start_csmpl = Cpt(
|
|
||||||
EpicsSignal, "START-CSMPL", kind=Kind.config, put_complete=True
|
|
||||||
) # cont on / off
|
|
||||||
intr_count = Cpt(
|
|
||||||
EpicsSignal, "INTR-COUNT", kind=Kind.config, put_complete=True
|
|
||||||
) # conter run up
|
|
||||||
total_cycles = Cpt(
|
|
||||||
EpicsSignal, "TOTAL-CYCLES", kind=Kind.config, put_complete=True
|
|
||||||
) # cycles set
|
|
||||||
smpl = Cpt(
|
|
||||||
EpicsSignal, "SMPL", kind=Kind.config, put_complete=True
|
|
||||||
) # start sampling --> aquire
|
|
||||||
smpl_done = Cpt(
|
|
||||||
EpicsSignalRO, "SMPL-DONE", kind=Kind.config
|
|
||||||
) # show trigger is done, consider using string=True
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
|
|
||||||
# Test the PhoenixTrigger class
|
|
||||||
|
|
||||||
trigger = PhoenixTrigger(name="trigger", prefix="X07MB-OP2:")
|
|
||||||
trigger.wait_for_connection(all_signals=True)
|
|
||||||
trigger.read()
|
|
||||||
trigger.read_configuration()
|
|
||||||
|
|
||||||
trigger.stage()
|
|
||||||
device_status = trigger.trigger()
|
|
||||||
device_status.wait()
|
|
||||||
trigger.unstage()
|
|
@ -1,431 +0,0 @@
|
|||||||
"""
|
|
||||||
Base implementation for Sitoro Falcon
|
|
||||||
|
|
||||||
|
|
||||||
This is based on ophyd.mca.py
|
|
||||||
All relevant classes are renames by putting Sitoro ahead of the class name
|
|
||||||
eg. EpicsMCARecord(Device): --> SitoroEpicsMCARecord(Device)
|
|
||||||
|
|
||||||
fundamentally on could use
|
|
||||||
class SitoroEpicsMCARecord(Device):
|
|
||||||
class SitoroEpicsMCA(SitoroEpicsMCARecord):
|
|
||||||
class SitoroEpicsMCAReadNotify(SitoroEpicsMCARecord):
|
|
||||||
class SitoroEpicsMCAReadNotify(SitoroEpicsMCARecord):
|
|
||||||
class SitoroEpicsMCACallback(Device):
|
|
||||||
|
|
||||||
|
|
||||||
class SitoroEpicsDXP(Device):
|
|
||||||
class SitoroEpicsDXPLowLevelParameter(Device):
|
|
||||||
class SitoroEpicsDXPLowLevel(Device):
|
|
||||||
class SitoroEpicsDXPMapping(Device):
|
|
||||||
class SitoroEpicsDXPBaseSystem(Device):
|
|
||||||
class SitoroEpicsDXPMultiElementSystem(SitoroEpicsDXPBaseSystem):
|
|
||||||
class SitoroSoftDXPTrigger(Device):
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
import logging
|
|
||||||
from collections import OrderedDict
|
|
||||||
|
|
||||||
|
|
||||||
from ophyd.areadetector import EpicsSignalWithRBV as SignalWithRBV
|
|
||||||
from ophyd.device import Component as Cpt
|
|
||||||
from ophyd.device import Device
|
|
||||||
from ophyd.device import DynamicDeviceComponent as DDC
|
|
||||||
from ophyd.device import Kind
|
|
||||||
from ophyd.signal import EpicsSignal, EpicsSignalRO, Signal
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
class ROI(Device): # must keep name
|
|
||||||
|
|
||||||
# 'name' is not an allowed attribute
|
|
||||||
label = Cpt(EpicsSignal, "NM", lazy=True)
|
|
||||||
count = Cpt(EpicsSignalRO, "", lazy=True)
|
|
||||||
net_count = Cpt(EpicsSignalRO, "N", lazy=True)
|
|
||||||
preset_count = Cpt(EpicsSignal, "P", lazy=True)
|
|
||||||
is_preset = Cpt(EpicsSignal, "IP", lazy=True)
|
|
||||||
bkgnd_chans = Cpt(EpicsSignal, "BG", lazy=True)
|
|
||||||
hi_chan = Cpt(EpicsSignal, "HI", lazy=True)
|
|
||||||
lo_chan = Cpt(EpicsSignal, "LO", lazy=True)
|
|
||||||
|
|
||||||
def __init__(
|
|
||||||
self, prefix, *, read_attrs=None, configuration_attrs=None, name=None, parent=None, **kwargs
|
|
||||||
):
|
|
||||||
|
|
||||||
super().__init__(
|
|
||||||
prefix,
|
|
||||||
read_attrs=read_attrs,
|
|
||||||
configuration_attrs=configuration_attrs,
|
|
||||||
name=name,
|
|
||||||
parent=parent,
|
|
||||||
**kwargs,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def add_rois(range_, **kwargs): # must keep name
|
|
||||||
"""Add one or more ROIs to an MCA instance
|
|
||||||
|
|
||||||
Parameters
|
|
||||||
----------
|
|
||||||
range_ : sequence of ints
|
|
||||||
Must be be in the set [0,31]
|
|
||||||
|
|
||||||
By default, an EpicsMCA is initialized with all 32 rois.
|
|
||||||
These provide the following Components as EpicsSignals (N=[0,31]):
|
|
||||||
EpicsMCA.rois.roiN.(label,count,net_count,preset_cnt, is_preset,
|
|
||||||
bkgnd_chans, hi_chan, lo_chan)
|
|
||||||
"""
|
|
||||||
defn = OrderedDict()
|
|
||||||
|
|
||||||
for roi in range_:
|
|
||||||
if not (0 <= roi < 32):
|
|
||||||
raise ValueError("roi must be in the set [0,31]")
|
|
||||||
|
|
||||||
attr = "roi{}".format(roi)
|
|
||||||
defn[attr] = (ROI, ".R{}".format(roi), kwargs)
|
|
||||||
|
|
||||||
return defn
|
|
||||||
|
|
||||||
|
|
||||||
class SitoroEpicsMCARecord(Device):
|
|
||||||
"""SynApps MCA Record interface"""
|
|
||||||
|
|
||||||
stop_signal = Cpt(EpicsSignal, ".STOP", kind="omitted")
|
|
||||||
preset_real_time = Cpt(EpicsSignal, ".PRTM", kind=Kind.config | Kind.normal)
|
|
||||||
preset_live_time = Cpt(EpicsSignal, ".PLTM", kind="omitted")
|
|
||||||
elapsed_real_time = Cpt(EpicsSignalRO, ".ERTM")
|
|
||||||
elapsed_live_time = Cpt(EpicsSignalRO, ".ELTM", kind="omitted")
|
|
||||||
|
|
||||||
spectrum = Cpt(EpicsSignalRO, ".VAL")
|
|
||||||
background = Cpt(EpicsSignalRO, ".BG", kind="omitted")
|
|
||||||
mode = Cpt(EpicsSignal, ".MODE", string=True, kind="omitted")
|
|
||||||
|
|
||||||
rois = DDC(add_rois(range(0, 32), kind="omitted"), kind="omitted")
|
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
|
||||||
|
|
||||||
super().__init__(*args, **kwargs)
|
|
||||||
|
|
||||||
# could arguably be made a configuration_attr instead...
|
|
||||||
self.stage_sigs["mode"] = "PHA"
|
|
||||||
|
|
||||||
def stop(self, *, success=False):
|
|
||||||
self.stop_signal.put(1)
|
|
||||||
|
|
||||||
|
|
||||||
class SitoroEpicsMCA(SitoroEpicsMCARecord):
|
|
||||||
"""mca records with extras from mca.db"""
|
|
||||||
|
|
||||||
start = Cpt(EpicsSignal, "Start", kind="omitted")
|
|
||||||
stop_signal = Cpt(EpicsSignal, "Stop", kind="omitted")
|
|
||||||
erase = Cpt(EpicsSignal, "Erase", kind="omitted")
|
|
||||||
erase_start = Cpt(EpicsSignal, "EraseStart", trigger_value=1, kind="omitted")
|
|
||||||
|
|
||||||
check_acquiring = Cpt(EpicsSignal, "CheckACQG", kind="omitted")
|
|
||||||
client_wait = Cpt(EpicsSignal, "ClientWait", kind="omitted")
|
|
||||||
enable_wait = Cpt(EpicsSignal, "EnableWait", kind="omitted")
|
|
||||||
force_read = Cpt(EpicsSignal, "Read", kind="omitted")
|
|
||||||
set_client_wait = Cpt(EpicsSignal, "SetClientWait", kind="omitted")
|
|
||||||
status = Cpt(EpicsSignal, "Status", kind="omitted")
|
|
||||||
when_acq_stops = Cpt(EpicsSignal, "WhenAcqStops", kind="omitted")
|
|
||||||
why1 = Cpt(EpicsSignal, "Why1", kind="omitted")
|
|
||||||
why2 = Cpt(EpicsSignal, "Why2", kind="omitted")
|
|
||||||
why3 = Cpt(EpicsSignal, "Why3", kind="omitted")
|
|
||||||
why4 = Cpt(EpicsSignal, "Why4", kind="omitted")
|
|
||||||
|
|
||||||
|
|
||||||
class SitoroEpicsMCAReadNotify(SitoroEpicsMCARecord):
|
|
||||||
"""mca record with extras from mcaReadNotify.db"""
|
|
||||||
|
|
||||||
start = Cpt(EpicsSignal, "Start", kind="omitted")
|
|
||||||
stop_signal = Cpt(EpicsSignal, "Stop", kind="omitted")
|
|
||||||
erase = Cpt(EpicsSignal, "Erase", kind="omitted")
|
|
||||||
erase_start = Cpt(EpicsSignal, "EraseStart", trigger_value=1, kind="omitted")
|
|
||||||
|
|
||||||
check_acquiring = Cpt(EpicsSignal, "CheckACQG", kind="omitted")
|
|
||||||
client_wait = Cpt(EpicsSignal, "ClientWait", kind="omitted")
|
|
||||||
enable_wait = Cpt(EpicsSignal, "EnableWait", kind="omitted")
|
|
||||||
force_read = Cpt(EpicsSignal, "Read", kind="omitted")
|
|
||||||
set_client_wait = Cpt(EpicsSignal, "SetClientWait", kind="omitted")
|
|
||||||
status = Cpt(EpicsSignal, "Status", kind="omitted")
|
|
||||||
|
|
||||||
|
|
||||||
class SitoroEpicsMCACallback(Device):
|
|
||||||
"""Callback-related signals for MCA devices"""
|
|
||||||
|
|
||||||
read_callback = Cpt(EpicsSignal, "ReadCallback")
|
|
||||||
read_data_once = Cpt(EpicsSignal, "ReadDataOnce")
|
|
||||||
read_status_once = Cpt(EpicsSignal, "ReadStatusOnce")
|
|
||||||
collect_data = Cpt(EpicsSignal, "CollectData")
|
|
||||||
|
|
||||||
|
|
||||||
class SitoroEpicsDXP(Device):
|
|
||||||
"""All high-level DXP parameters for each channel"""
|
|
||||||
|
|
||||||
preset_mode = Cpt(EpicsSignal, "PresetMode", string=True)
|
|
||||||
|
|
||||||
live_time_output = Cpt(SignalWithRBV, "LiveTimeOutput", string=True)
|
|
||||||
# elapsed_live_time = Cpt(EpicsSignal, "ElapsedLiveTime")
|
|
||||||
# elapsed_real_time = Cpt(EpicsSignal, "ElapsedRealTime")
|
|
||||||
# elapsed_trigger_live_time = Cpt(EpicsSignal, "ElapsedTriggerLiveTime")
|
|
||||||
|
|
||||||
nd_array_mode = Cpt(EpicsSignal, "NDArrayMode")
|
|
||||||
|
|
||||||
# Trigger Filter PVs
|
|
||||||
trigger_peaking_time = Cpt(SignalWithRBV, "TriggerPeakingTime")
|
|
||||||
trigger_threshold = Cpt(SignalWithRBV, "TriggerThreshold")
|
|
||||||
trigger_gap_time = Cpt(SignalWithRBV, "TriggerGapTime")
|
|
||||||
trigger_output = Cpt(SignalWithRBV, "TriggerOutput", string=True)
|
|
||||||
max_width = Cpt(SignalWithRBV, "MaxWidth")
|
|
||||||
|
|
||||||
# Energy Filter PVs
|
|
||||||
peaking_time = Cpt(SignalWithRBV, "PeakingTime")
|
|
||||||
energy_threshold = Cpt(SignalWithRBV, "EnergyThreshold")
|
|
||||||
gap_time = Cpt(SignalWithRBV, "GapTime")
|
|
||||||
|
|
||||||
# Baseline PVs
|
|
||||||
# baseline_cut_percent = Cpt(SignalWithRBV, "BaselineCutPercent")
|
|
||||||
# baseline_cut_enable = Cpt(SignalWithRBV, "BaselineCutEnable")
|
|
||||||
# baseline_filter_length = Cpt(SignalWithRBV, "BaselineFilterLength")
|
|
||||||
# baseline_threshold = Cpt(SignalWithRBV, "BaselineThreshold")
|
|
||||||
# baseline_energy_array = Cpt(EpicsSignal, "BaselineEnergyArray")
|
|
||||||
# baseline_histogram = Cpt(EpicsSignal, "BaselineHistogram")
|
|
||||||
# baseline_threshold = Cpt(SignalWithRBV, "BaselineThreshold")
|
|
||||||
|
|
||||||
# Misc PVs
|
|
||||||
preamp_gain = Cpt(SignalWithRBV, "PreampGain")
|
|
||||||
detector_polarity = Cpt(SignalWithRBV, "DetectorPolarity")
|
|
||||||
reset_delay = Cpt(SignalWithRBV, "ResetDelay")
|
|
||||||
decay_time = Cpt(SignalWithRBV, "DecayTime")
|
|
||||||
max_energy = Cpt(SignalWithRBV, "MaxEnergy")
|
|
||||||
adc_percent_rule = Cpt(SignalWithRBV, "ADCPercentRule")
|
|
||||||
max_width = Cpt(SignalWithRBV, "MaxWidth")
|
|
||||||
|
|
||||||
# read-only diagnostics
|
|
||||||
triggers = Cpt(EpicsSignalRO, "Triggers", lazy=True)
|
|
||||||
events = Cpt(EpicsSignalRO, "Events", lazy=True)
|
|
||||||
overflows = Cpt(EpicsSignalRO, "Overflows", lazy=True)
|
|
||||||
underflows = Cpt(EpicsSignalRO, "Underflows", lazy=True)
|
|
||||||
input_count_rate = Cpt(EpicsSignalRO, "InputCountRate", lazy=True)
|
|
||||||
output_count_rate = Cpt(EpicsSignalRO, "OutputCountRate", lazy=True)
|
|
||||||
|
|
||||||
mca_bin_width = Cpt(EpicsSignalRO, "MCABinWidth_RBV")
|
|
||||||
calibration_energy = Cpt(EpicsSignalRO, "CalibrationEnergy_RBV")
|
|
||||||
current_pixel = Cpt(EpicsSignal, "CurrentPixel")
|
|
||||||
dynamic_range = Cpt(EpicsSignalRO, "DynamicRange_RBV")
|
|
||||||
|
|
||||||
# Preset options
|
|
||||||
preset_events = Cpt(SignalWithRBV, "PresetEvents")
|
|
||||||
preset_mode = Cpt(SignalWithRBV, "PresetMode", string=True)
|
|
||||||
preset_triggers = Cpt(SignalWithRBV, "PresetTriggers")
|
|
||||||
|
|
||||||
# Trace options
|
|
||||||
trace_data = Cpt(EpicsSignal, "TraceData")
|
|
||||||
trace_mode = Cpt(SignalWithRBV, "TraceMode", string=True)
|
|
||||||
trace_time_array = Cpt(EpicsSignal, "TraceTimeArray")
|
|
||||||
trace_time = Cpt(SignalWithRBV, "TraceTime")
|
|
||||||
|
|
||||||
|
|
||||||
class SitoroEpicsDXPLowLevelParameter(Device):
|
|
||||||
param_name = Cpt(EpicsSignal, "Name")
|
|
||||||
value = Cpt(SignalWithRBV, "Val")
|
|
||||||
|
|
||||||
|
|
||||||
class SitoroEpicsDXPLowLevel(Device):
|
|
||||||
num_low_level_params = Cpt(EpicsSignal, "NumLLParams")
|
|
||||||
read_low_level_params = Cpt(EpicsSignal, "ReadLLParams")
|
|
||||||
|
|
||||||
parameter_prefix = "LL{}"
|
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
|
||||||
super().__init__(*args, **kwargs)
|
|
||||||
self._parameter_cache = {}
|
|
||||||
|
|
||||||
def get_low_level_parameter(self, index):
|
|
||||||
"""Get a DXP low level parameter
|
|
||||||
|
|
||||||
Parameters
|
|
||||||
----------
|
|
||||||
index : int
|
|
||||||
In the range of [0, 229]
|
|
||||||
|
|
||||||
Returns
|
|
||||||
-------
|
|
||||||
param : EpicsDXPLowLevelParameter
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
return self._parameter_cache[index]
|
|
||||||
except KeyError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
prefix = "{}{}".format(self.prefix, self.parameter_prefix)
|
|
||||||
name = "{}_param{}".format(self.name, index)
|
|
||||||
param = EpicsDXPLowLevelParameter(prefix, name=name)
|
|
||||||
self._parameter_cache[index] = param
|
|
||||||
return param
|
|
||||||
|
|
||||||
|
|
||||||
class SitoroEpicsDXPMapping(Device):
|
|
||||||
apply = Cpt(EpicsSignal, "Apply")
|
|
||||||
auto_apply = Cpt(SignalWithRBV, "AutoApply")
|
|
||||||
auto_pixels_per_buffer = Cpt(SignalWithRBV, "AutoPixelsPerBuffer")
|
|
||||||
buffer_size = Cpt(EpicsSignalRO, "BufferSize_RBV")
|
|
||||||
collect_mode = Cpt(SignalWithRBV, "CollectMode")
|
|
||||||
ignore_gate = Cpt(SignalWithRBV, "IgnoreGate")
|
|
||||||
input_logic_polarity = Cpt(SignalWithRBV, "InputLogicPolarity")
|
|
||||||
list_mode = Cpt(SignalWithRBV, "ListMode")
|
|
||||||
mbytes_read = Cpt(EpicsSignalRO, "MBytesRead_RBV")
|
|
||||||
next_pixel = Cpt(EpicsSignal, "NextPixel")
|
|
||||||
pixel_advance_mode = Cpt(SignalWithRBV, "PixelAdvanceMode")
|
|
||||||
pixels_per_buffer = Cpt(SignalWithRBV, "PixelsPerBuffer")
|
|
||||||
pixels_per_run = Cpt(SignalWithRBV, "PixelsPerRun")
|
|
||||||
read_rate = Cpt(EpicsSignalRO, "ReadRate_RBV")
|
|
||||||
sync_count = Cpt(SignalWithRBV, "SyncCount")
|
|
||||||
|
|
||||||
|
|
||||||
class SitoroEpicsDXPBaseSystem(Device):
|
|
||||||
channel_advance = Cpt(EpicsSignal, "ChannelAdvance")
|
|
||||||
client_wait = Cpt(EpicsSignal, "ClientWait")
|
|
||||||
dwell = Cpt(EpicsSignal, "Dwell")
|
|
||||||
max_scas = Cpt(EpicsSignal, "MaxSCAs")
|
|
||||||
num_scas = Cpt(SignalWithRBV, "NumSCAs")
|
|
||||||
poll_time = Cpt(SignalWithRBV, "PollTime")
|
|
||||||
prescale = Cpt(EpicsSignal, "Prescale")
|
|
||||||
save_system = Cpt(SignalWithRBV, "SaveSystem")
|
|
||||||
save_system_file = Cpt(EpicsSignal, "SaveSystemFile")
|
|
||||||
set_client_wait = Cpt(EpicsSignal, "SetClientWait")
|
|
||||||
|
|
||||||
|
|
||||||
class SitoroTest(Device):
|
|
||||||
preset_mode = Cpt(EpicsSignal, "PresetMode", string=True)
|
|
||||||
|
|
||||||
|
|
||||||
class SitoroEpicsDXPMultiElementSystem(SitoroEpicsDXPBaseSystem):
|
|
||||||
|
|
||||||
# Preset info
|
|
||||||
preset_mode = Cpt(EpicsSignal, "PresetMode", string=True)
|
|
||||||
preset_real_time = Cpt(EpicsSignal, "PresetReal")
|
|
||||||
preset_events = Cpt(EpicsSignal, "PresetEvents")
|
|
||||||
preset_triggers = Cpt(EpicsSignal, "PresetTriggers")
|
|
||||||
mca_refresh_period = Cpt(EpicsSignal, "MCARefreshPeriod")
|
|
||||||
|
|
||||||
# preset_live_time = Cpt(EpicsSignal, "PresetLive")
|
|
||||||
# Acquisition
|
|
||||||
erase_all = Cpt(EpicsSignal, "EraseAll")
|
|
||||||
erase_start = Cpt(EpicsSignal, "EraseStart", trigger_value=1)
|
|
||||||
start_all = Cpt(EpicsSignal, "StartAll")
|
|
||||||
stop_all = Cpt(EpicsSignal, "StopAll")
|
|
||||||
|
|
||||||
# Status
|
|
||||||
|
|
||||||
set_acquire_busy = Cpt(EpicsSignal, "SetAcquireBusy") # -- not working
|
|
||||||
acquire_busy = Cpt(EpicsSignal, "AcquireBusy") # -- not working
|
|
||||||
status_all = Cpt(EpicsSignal, "StatusAll") # -- not working
|
|
||||||
status_all_once = Cpt(EpicsSignal, "StatusAllOnce") # -- not working
|
|
||||||
acquiring = Cpt(EpicsSignalRO, "Acquiring") # -- not working
|
|
||||||
|
|
||||||
# Reading
|
|
||||||
# read_baseline_histograms = Cpt(EpicsSignal, "ReadBaselineHistograms")
|
|
||||||
read_all = Cpt(EpicsSignal, "ReadAll") # -- not working
|
|
||||||
read_all_once = Cpt(EpicsSignal, "ReadAllOnce") # -- not working
|
|
||||||
|
|
||||||
# As a debugging note, if snl_connected is not '1', your IOC is
|
|
||||||
# misconfigured:
|
|
||||||
snl_connected = Cpt(EpicsSignal, "SNL_Connected")
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
# Copying to individual elements
|
|
||||||
copy_adcp_ercent_rule = Cpt(EpicsSignal, "CopyADCPercentRule")
|
|
||||||
#copy_baseline_cut_enable = Cpt(EpicsSignal, "CopyBaselineCutEnable")
|
|
||||||
#copy_baseline_cut_percent = Cpt(EpicsSignal, "CopyBaselineCutPercent")
|
|
||||||
#copy_baseline_filter_length = Cpt(EpicsSignal, "CopyBaselineFilterLength")
|
|
||||||
#copy_baseline_threshold = Cpt(EpicsSignal, "CopyBaselineThreshold")
|
|
||||||
copy_decay_time = Cpt(EpicsSignal, "CopyDecayTime")
|
|
||||||
copy_detector_polarity = Cpt(EpicsSignal, "CopyDetectorPolarity")
|
|
||||||
copy_energy_threshold = Cpt(EpicsSignal, "CopyEnergyThreshold")
|
|
||||||
copy_gap_time = Cpt(EpicsSignal, "CopyGapTime")
|
|
||||||
copy_max_energy = Cpt(EpicsSignal, "CopyMaxEnergy")
|
|
||||||
copy_max_width = Cpt(EpicsSignal, "CopyMaxWidth")
|
|
||||||
copy_peaking_time = Cpt(EpicsSignal, "CopyPeakingTime")
|
|
||||||
copy_preamp_gain = Cpt(EpicsSignal, "CopyPreampGain")
|
|
||||||
copy_roic_hannel = Cpt(EpicsSignal, "CopyROIChannel")
|
|
||||||
copy_roie_nergy = Cpt(EpicsSignal, "CopyROIEnergy")
|
|
||||||
copy_roi_sca = Cpt(EpicsSignal, "CopyROI_SCA")
|
|
||||||
copy_reset_delay = Cpt(EpicsSignal, "CopyResetDelay")
|
|
||||||
copy_trigger_gap_time = Cpt(EpicsSignal, "CopyTriggerGapTime")
|
|
||||||
copy_trigger_peaking_time = Cpt(EpicsSignal, "CopyTriggerPeakingTime")
|
|
||||||
copy_trigger_threshold = Cpt(EpicsSignal, "CopyTriggerThreshold")
|
|
||||||
|
|
||||||
# do_* executes the process:
|
|
||||||
do_read_all = Cpt(EpicsSignal, "DoReadAll")
|
|
||||||
#do_read_baseline_histograms = Cpt(EpicsSignal, "DoReadBaselineHistograms")
|
|
||||||
do_read_traces = Cpt(EpicsSignal, "DoReadTraces")
|
|
||||||
do_status_all = Cpt(EpicsSignal, "DoStatusAll")
|
|
||||||
"""
|
|
||||||
|
|
||||||
# Time
|
|
||||||
# dead_time = Cpt(EpicsSignal, "DeadTime")
|
|
||||||
# elapsed_live = Cpt(EpicsSignal, "ElapsedLive")
|
|
||||||
# elapsed_real = Cpt(EpicsSignal, "ElapsedReal")
|
|
||||||
|
|
||||||
# low-level
|
|
||||||
# read_low_level_params = Cpt(EpicsSignal, "ReadLLParams")
|
|
||||||
|
|
||||||
# Traces
|
|
||||||
# read_traces = Cpt(EpicsSignal, "ReadTraces")
|
|
||||||
# trace_modes = Cpt(EpicsSignal, "TraceModes", string=True)
|
|
||||||
# trace_times = Cpt(EpicsSignal, "TraceTimes")
|
|
||||||
|
|
||||||
|
|
||||||
class SitoroSoftDXPTrigger(Device):
|
|
||||||
"""Simple soft trigger for DXP devices
|
|
||||||
|
|
||||||
Parameters
|
|
||||||
----------
|
|
||||||
count_signal : str, optional
|
|
||||||
Signal to set acquisition time (default: 'preset_real_time')
|
|
||||||
preset_mode : str, optional
|
|
||||||
Default preset mode for the stage signals (default: 'Real time')
|
|
||||||
mode_signal : str, optional
|
|
||||||
Preset mode signal attribute (default 'preset_mode')
|
|
||||||
stop_signal : str, optional
|
|
||||||
Stop signal attribute (default 'stop_all')
|
|
||||||
"""
|
|
||||||
|
|
||||||
count_time = Cpt(Signal, value=None, doc="bluesky count time")
|
|
||||||
|
|
||||||
def __init__(
|
|
||||||
self,
|
|
||||||
*args,
|
|
||||||
count_signal="preset_real_time",
|
|
||||||
stop_signal="stop_all",
|
|
||||||
mode_signal="preset_mode",
|
|
||||||
preset_mode="Real time",
|
|
||||||
**kwargs,
|
|
||||||
):
|
|
||||||
super().__init__(*args, **kwargs)
|
|
||||||
self._status = None
|
|
||||||
self._count_signal = getattr(self, count_signal)
|
|
||||||
|
|
||||||
stop_signal = getattr(self, stop_signal)
|
|
||||||
self.stage_sigs[stop_signal] = 1
|
|
||||||
|
|
||||||
mode_signal = getattr(self, mode_signal)
|
|
||||||
self.stage_sigs[mode_signal] = preset_mode
|
|
||||||
|
|
||||||
def stage(self):
|
|
||||||
if self.count_time.get() is None:
|
|
||||||
# remove count_time from the stage signals if count_time unset
|
|
||||||
try:
|
|
||||||
del self.stage_sigs[self._count_signal]
|
|
||||||
except KeyError:
|
|
||||||
pass
|
|
||||||
else:
|
|
||||||
self.stage_sigs[self._count_signal] = self.count_time.get()
|
|
||||||
|
|
||||||
super().stage()
|
|
@ -1,485 +0,0 @@
|
|||||||
"""
|
|
||||||
Implementation for falcon at PHOENIX, derived from
|
|
||||||
implementation on csaxs (file falcon_csaxs.py)
|
|
||||||
|
|
||||||
18.10.2024 further development of falcon_phoenix.y to phoenix to sitoro_phoenix.py
|
|
||||||
Now we use the definition of all EPICS channels for falcon as defined in the classes in sitoro.py
|
|
||||||
|
|
||||||
WIP......
|
|
||||||
|
|
||||||
17.10.2024 try to streamline implementation with mca record
|
|
||||||
|
|
||||||
|
|
||||||
Differences to implement
|
|
||||||
|
|
||||||
1) we consider EPICS initialization as standard implementaion,
|
|
||||||
so no reinitialization when bec device is initrialized ... DONE ...
|
|
||||||
|
|
||||||
2) in EpicsDXPFalcon(Device) add ICR and OCR for individual detectors
|
|
||||||
|
|
||||||
3) can we make this generic to make it suited for both falcon and XMAP ?
|
|
||||||
|
|
||||||
3) make easy switching between mca spectra an mca mapping
|
|
||||||
|
|
||||||
fix defiend relation bwetween variables and names used here for example DONE
|
|
||||||
|
|
||||||
aquiring is currently called 'state' --> should be renamed to aquiring
|
|
||||||
Currently state = Cpt(EpicsSignal, "Acquiring")
|
|
||||||
should be acquiring = Cpt(EpicsSignal, "Acquiring")
|
|
||||||
|
|
||||||
|
|
||||||
hdf5 = Cpt(FalconHDF5Plugins, "HDF1:")
|
|
||||||
|
|
||||||
def arm_aquisition
|
|
||||||
|
|
||||||
raise FalconTimeoutError(
|
|
||||||
f"Failed to arm the acquisition. Detector state {signal_conditions[0][0]}"
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
CHANGES LOG and
|
|
||||||
|
|
||||||
System as taken from cSAXS some times works for one element need about 7 second
|
|
||||||
There seem to be still serious timout issues
|
|
||||||
|
|
||||||
changes log
|
|
||||||
TIMEOUT_FOR_SIGNALs from 5 to 10
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
import enum
|
|
||||||
import threading
|
|
||||||
import time
|
|
||||||
|
|
||||||
from bec_lib.logger import bec_logger
|
|
||||||
|
|
||||||
from ophyd import Component as Cpt
|
|
||||||
from ophyd import Device, EpicsSignal, EpicsSignalRO, EpicsSignalWithRBV
|
|
||||||
|
|
||||||
# from ophyd.mca import EpicsMCARecord # old import
|
|
||||||
# now import ophyd.mca completely
|
|
||||||
|
|
||||||
# import ophyd.mca as Mca
|
|
||||||
|
|
||||||
from .sitoro import (
|
|
||||||
SitoroEpicsMCARecord,
|
|
||||||
SitoroEpicsMCA,
|
|
||||||
SitoroEpicsMCAReadNotify,
|
|
||||||
SitoroEpicsDXP,
|
|
||||||
SitoroEpicsDXPBaseSystem,
|
|
||||||
SitoroEpicsDXPMultiElementSystem,
|
|
||||||
SitoroTest,
|
|
||||||
)
|
|
||||||
|
|
||||||
from ophyd_devices.interfaces.base_classes.psi_detector_base import (
|
|
||||||
CustomDetectorMixin,
|
|
||||||
PSIDetectorBase,
|
|
||||||
)
|
|
||||||
|
|
||||||
logger = bec_logger.logger
|
|
||||||
|
|
||||||
|
|
||||||
class SitoroError(Exception):
|
|
||||||
"""Base class for exceptions in this module."""
|
|
||||||
|
|
||||||
|
|
||||||
class SitoroTimeoutError(SitoroError):
|
|
||||||
"""Raised when the Sitoro does not respond in time."""
|
|
||||||
|
|
||||||
|
|
||||||
class DetectorState(enum.IntEnum):
|
|
||||||
"""Detector states for Sitoro detector"""
|
|
||||||
|
|
||||||
DONE = 0
|
|
||||||
ACQUIRING = 1
|
|
||||||
|
|
||||||
|
|
||||||
class TriggerSource(enum.IntEnum):
|
|
||||||
"""Trigger source for Sitoro detector"""
|
|
||||||
|
|
||||||
USER = 0
|
|
||||||
GATE = 1
|
|
||||||
SYNC = 2
|
|
||||||
|
|
||||||
|
|
||||||
class MappingSource(enum.IntEnum):
|
|
||||||
"""Mapping source for Sitoro detector"""
|
|
||||||
|
|
||||||
SPECTRUM = 0
|
|
||||||
MAPPING = 1
|
|
||||||
|
|
||||||
|
|
||||||
class SitoroEpicsMCARecordExtended_OLD(SitoroEpicsMCARecord):
|
|
||||||
|
|
||||||
# add parameters for detector energy calibration
|
|
||||||
# which are missing in mca.py
|
|
||||||
|
|
||||||
calo = Cpt(EpicsSignal, ".CALO")
|
|
||||||
cals = Cpt(EpicsSignal, ".CALS")
|
|
||||||
calq = Cpt(EpicsSignal, ".CALQ")
|
|
||||||
tth = Cpt(EpicsSignal, ".TTH")
|
|
||||||
|
|
||||||
|
|
||||||
class SitoroEpicsDXP_OLD(Device):
|
|
||||||
"""
|
|
||||||
DXP parameters for Sitoro detector
|
|
||||||
|
|
||||||
Base class to map EPICS PVs from DXP parameters to ophyd signals.
|
|
||||||
"""
|
|
||||||
|
|
||||||
elapsed_live_time = Cpt(EpicsSignal, "ElapsedLiveTime")
|
|
||||||
elapsed_real_time = Cpt(EpicsSignal, "ElapsedRealTime")
|
|
||||||
elapsed_trigger_live_time = Cpt(EpicsSignal, "ElapsedTriggerLiveTime")
|
|
||||||
|
|
||||||
# Energy Filter PVs
|
|
||||||
energy_threshold = Cpt(EpicsSignalWithRBV, "DetectionThreshold")
|
|
||||||
min_pulse_separation = Cpt(EpicsSignalWithRBV, "MinPulsePairSeparation")
|
|
||||||
detection_filter = Cpt(EpicsSignalWithRBV, "DetectionFilter", string=True)
|
|
||||||
scale_factor = Cpt(EpicsSignalWithRBV, "ScaleFactor")
|
|
||||||
risetime_optimisation = Cpt(EpicsSignalWithRBV, "RisetimeOptimization")
|
|
||||||
decay_time = Cpt(EpicsSignalWithRBV, "DecayTime")
|
|
||||||
|
|
||||||
current_pixel = Cpt(EpicsSignalRO, "CurrentPixel")
|
|
||||||
|
|
||||||
|
|
||||||
class SitoroHDF5Plugins(Device):
|
|
||||||
"""
|
|
||||||
HDF5 parameters for Sitoro detector
|
|
||||||
|
|
||||||
Base class to map EPICS PVs from HDF5 Plugin to ophyd signals.
|
|
||||||
"""
|
|
||||||
|
|
||||||
capture = Cpt(EpicsSignalWithRBV, "Capture")
|
|
||||||
enable = Cpt(EpicsSignalWithRBV, "EnableCallbacks", string=True, kind="config")
|
|
||||||
xml_file_name = Cpt(EpicsSignalWithRBV, "XMLFileName", string=True, kind="config")
|
|
||||||
lazy_open = Cpt(EpicsSignalWithRBV, "LazyOpen", string=True, doc="0='No' 1='Yes'")
|
|
||||||
temp_suffix = Cpt(EpicsSignalWithRBV, "TempSuffix", string=True)
|
|
||||||
file_path = Cpt(EpicsSignalWithRBV, "FilePath", string=True, kind="config")
|
|
||||||
file_name = Cpt(EpicsSignalWithRBV, "FileName", string=True, kind="config")
|
|
||||||
file_template = Cpt(EpicsSignalWithRBV, "FileTemplate", string=True, kind="config")
|
|
||||||
num_capture = Cpt(EpicsSignalWithRBV, "NumCapture", kind="config")
|
|
||||||
file_write_mode = Cpt(EpicsSignalWithRBV, "FileWriteMode", kind="config")
|
|
||||||
queue_size = Cpt(EpicsSignalWithRBV, "QueueSize", kind="config")
|
|
||||||
array_counter = Cpt(EpicsSignalWithRBV, "ArrayCounter", kind="config")
|
|
||||||
|
|
||||||
|
|
||||||
class SitoroSetup(CustomDetectorMixin):
|
|
||||||
"""
|
|
||||||
Sitoro setup class for Phoenix
|
|
||||||
|
|
||||||
Parent class: CustomDetectorMixin
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, *args, parent: Device = None, **kwargs) -> None:
|
|
||||||
super().__init__(*args, parent=parent, **kwargs)
|
|
||||||
self._lock = threading.RLock()
|
|
||||||
|
|
||||||
def on_init(self) -> None:
|
|
||||||
"""Initialize Sitoro detector"""
|
|
||||||
self.initialize_default_parameter()
|
|
||||||
self.initialize_detector()
|
|
||||||
self.initialize_detector_backend()
|
|
||||||
|
|
||||||
def initialize_default_parameter(self) -> None:
|
|
||||||
"""
|
|
||||||
Set default parameters for Sitoro
|
|
||||||
|
|
||||||
This will set:
|
|
||||||
- readout (float): readout time in seconds
|
|
||||||
- value_pixel_per_buffer (int): number of spectra in buffer of Sitoro Sitoro
|
|
||||||
|
|
||||||
"""
|
|
||||||
# self.parent.value_pixel_per_buffer = 20
|
|
||||||
self.update_readout_time()
|
|
||||||
|
|
||||||
def update_readout_time(self) -> None:
|
|
||||||
"""Set readout time for Eiger9M detector"""
|
|
||||||
readout_time = (
|
|
||||||
self.parent.scaninfo.readout_time
|
|
||||||
if hasattr(self.parent.scaninfo, "readout_time")
|
|
||||||
else self.parent.MIN_READOUT
|
|
||||||
)
|
|
||||||
self.parent.readout_time = max(readout_time, self.parent.MIN_READOUT)
|
|
||||||
|
|
||||||
def initialize_detector(self) -> None:
|
|
||||||
"""Initialize Sitoro detector"""
|
|
||||||
|
|
||||||
pass
|
|
||||||
|
|
||||||
"""
|
|
||||||
THIS IS THE OLD CSACS CODE. uncomment for now, as we consider EPICS as the boss
|
|
||||||
for initialization
|
|
||||||
|
|
||||||
self.stop_detector()
|
|
||||||
self.stop_detector_backend()
|
|
||||||
|
|
||||||
self.set_trigger(
|
|
||||||
mapping_mode=MappingSource.MAPPING, trigger_source=TriggerSource.GATE, ignore_gate=0
|
|
||||||
)
|
|
||||||
# 1 Realtime
|
|
||||||
self.parent.preset_mode.put(1)
|
|
||||||
# 0 Normal, 1 Inverted
|
|
||||||
self.parent.input_logic_polarity.put(0)
|
|
||||||
# 0 Manual 1 Auto
|
|
||||||
self.parent.auto_pixels_per_buffer.put(0)
|
|
||||||
# Sets the number of pixels/spectra in the buffer
|
|
||||||
self.parent.pixels_per_buffer.put(self.parent.value_pixel_per_buffer)
|
|
||||||
"""
|
|
||||||
|
|
||||||
def initialize_detector_backend(self) -> None:
|
|
||||||
"""Initialize the detector backend for Sitoro."""
|
|
||||||
self.parent.hdf5.enable.put(1)
|
|
||||||
# file location of h5 layout for cSAXS
|
|
||||||
self.parent.hdf5.xml_file_name.put("layout.xml")
|
|
||||||
# TODO Check if lazy open is needed and wanted!
|
|
||||||
self.parent.hdf5.lazy_open.put(1)
|
|
||||||
self.parent.hdf5.temp_suffix.put("")
|
|
||||||
# size of queue for number of spectra allowed in the buffer, if too small at high throughput, data is lost
|
|
||||||
self.parent.hdf5.queue_size.put(2000)
|
|
||||||
# Segmentation into Spectra within EPICS, 1 is activate, 0 is deactivate
|
|
||||||
self.parent.nd_array_mode.put(1)
|
|
||||||
|
|
||||||
def on_stage(self) -> None:
|
|
||||||
"""Prepare detector and backend for acquisition"""
|
|
||||||
self.prepare_detector()
|
|
||||||
self.prepare_data_backend()
|
|
||||||
self.publish_file_location(done=False, successful=False)
|
|
||||||
self.arm_acquisition()
|
|
||||||
|
|
||||||
def prepare_detector(self) -> None:
|
|
||||||
"""Prepare detector for acquisition"""
|
|
||||||
self.set_trigger(
|
|
||||||
mapping_mode=MappingSource.MAPPING, trigger_source=TriggerSource.GATE, ignore_gate=0
|
|
||||||
)
|
|
||||||
self.parent.preset_real.put(self.parent.scaninfo.exp_time)
|
|
||||||
self.parent.pixels_per_run.put(
|
|
||||||
int(self.parent.scaninfo.num_points * self.parent.scaninfo.frames_per_trigger)
|
|
||||||
)
|
|
||||||
|
|
||||||
def prepare_data_backend(self) -> None:
|
|
||||||
"""Prepare data backend for acquisition"""
|
|
||||||
self.parent.filepath.set(
|
|
||||||
self.parent.filewriter.compile_full_filename(f"{self.parent.name}.h5")
|
|
||||||
).wait()
|
|
||||||
file_path, file_name = os.path.split(self.parent.filepath.get())
|
|
||||||
self.parent.hdf5.file_path.put(file_path)
|
|
||||||
self.parent.hdf5.file_name.put(file_name)
|
|
||||||
self.parent.hdf5.file_template.put("%s%s")
|
|
||||||
self.parent.hdf5.num_capture.put(
|
|
||||||
int(self.parent.scaninfo.num_points * self.parent.scaninfo.frames_per_trigger)
|
|
||||||
)
|
|
||||||
self.parent.hdf5.file_write_mode.put(2)
|
|
||||||
# Reset spectrum counter in filewriter, used for indexing & identifying missing triggers
|
|
||||||
self.parent.hdf5.array_counter.put(0)
|
|
||||||
# Start file writing
|
|
||||||
self.parent.hdf5.capture.put(1)
|
|
||||||
|
|
||||||
def arm_acquisition(self) -> None:
|
|
||||||
"""Arm detector for acquisition"""
|
|
||||||
self.parent.start_all.put(1)
|
|
||||||
signal_conditions = [
|
|
||||||
(
|
|
||||||
lambda: self.parent.acquiring.read()[self.parent.acquiring.name]["value"],
|
|
||||||
DetectorState.ACQUIRING,
|
|
||||||
)
|
|
||||||
]
|
|
||||||
if not self.wait_for_signals(
|
|
||||||
signal_conditions=signal_conditions,
|
|
||||||
timeout=self.parent.TIMEOUT_FOR_SIGNALS,
|
|
||||||
check_stopped=True,
|
|
||||||
all_signals=False,
|
|
||||||
):
|
|
||||||
raise SitoroTimeoutError(
|
|
||||||
f"Failed to arm the acquisition. Detector state {signal_conditions[0][0]}"
|
|
||||||
)
|
|
||||||
|
|
||||||
def on_unstage(self) -> None:
|
|
||||||
"""Unstage detector and backend"""
|
|
||||||
pass
|
|
||||||
|
|
||||||
def on_complete(self) -> None:
|
|
||||||
"""Complete detector and backend"""
|
|
||||||
self.finished(timeout=self.parent.TIMEOUT_FOR_SIGNALS)
|
|
||||||
self.publish_file_location(done=True, successful=True)
|
|
||||||
|
|
||||||
def on_stop(self) -> None:
|
|
||||||
"""Stop detector and backend"""
|
|
||||||
self.stop_detector()
|
|
||||||
self.stop_detector_backend()
|
|
||||||
|
|
||||||
def stop_detector(self) -> None:
|
|
||||||
"""Stops detector"""
|
|
||||||
|
|
||||||
self.parent.stop_all.put(1)
|
|
||||||
time.sleep(0.5)
|
|
||||||
self.parent.erase_all.put(1)
|
|
||||||
time.sleep(0.5)
|
|
||||||
|
|
||||||
signal_conditions = [
|
|
||||||
(
|
|
||||||
lambda: self.parent.acquiring.read()[self.parent.acquiring.name]["value"],
|
|
||||||
DetectorState.DONE,
|
|
||||||
)
|
|
||||||
]
|
|
||||||
|
|
||||||
if not self.wait_for_signals(
|
|
||||||
signal_conditions=signal_conditions,
|
|
||||||
timeout=self.parent.TIMEOUT_FOR_SIGNALS - self.parent.TIMEOUT_FOR_SIGNALS // 2,
|
|
||||||
all_signals=False,
|
|
||||||
):
|
|
||||||
# Retry stop detector and wait for remaining time
|
|
||||||
raise SitoroTimeoutError(
|
|
||||||
f"Failed to stop detector, timeout with state {signal_conditions[0][0]}"
|
|
||||||
)
|
|
||||||
|
|
||||||
def stop_detector_backend(self) -> None:
|
|
||||||
"""Stop the detector backend"""
|
|
||||||
self.parent.hdf5.capture.put(0)
|
|
||||||
|
|
||||||
def finished(self, timeout: int = 5) -> None:
|
|
||||||
"""Check if scan finished succesfully"""
|
|
||||||
with self._lock:
|
|
||||||
total_frames = int(
|
|
||||||
self.parent.scaninfo.num_points * self.parent.scaninfo.frames_per_trigger
|
|
||||||
)
|
|
||||||
signal_conditions = [
|
|
||||||
(self.parent.dxp.current_pixel.get, total_frames),
|
|
||||||
(self.parent.hdf5.array_counter.get, total_frames),
|
|
||||||
]
|
|
||||||
if not self.wait_for_signals(
|
|
||||||
signal_conditions=signal_conditions,
|
|
||||||
timeout=timeout,
|
|
||||||
check_stopped=True,
|
|
||||||
all_signals=True,
|
|
||||||
):
|
|
||||||
logger.debug(
|
|
||||||
f"Sitoro missed a trigger: received trigger {self.parent.dxp.current_pixel.get()},"
|
|
||||||
f" send data {self.parent.hdf5.array_counter.get()} from total_frames"
|
|
||||||
f" {total_frames}"
|
|
||||||
)
|
|
||||||
self.stop_detector()
|
|
||||||
self.stop_detector_backend()
|
|
||||||
|
|
||||||
def set_trigger(
|
|
||||||
self, mapping_mode: MappingSource, trigger_source: TriggerSource, ignore_gate: int = 0
|
|
||||||
) -> None:
|
|
||||||
"""
|
|
||||||
Set triggering mode for detector
|
|
||||||
|
|
||||||
Args:
|
|
||||||
mapping_mode (MappingSource): Mapping mode for the detector
|
|
||||||
trigger_source (TriggerSource): Trigger source for the detector, pixel_advance_signal
|
|
||||||
ignore_gate (int): Ignore gate from TTL signal; defaults to 0
|
|
||||||
|
|
||||||
"""
|
|
||||||
mapping = int(mapping_mode)
|
|
||||||
trigger = trigger_source
|
|
||||||
self.parent.collect_mode.put(mapping)
|
|
||||||
self.parent.pixel_advance_mode.put(trigger)
|
|
||||||
self.parent.ignore_gate.put(ignore_gate)
|
|
||||||
|
|
||||||
|
|
||||||
class SitoroPhoenix(PSIDetectorBase, SitoroEpicsDXPMultiElementSystem):
|
|
||||||
# class SitoroPhoenix(PSIDetectorBase):
|
|
||||||
"""
|
|
||||||
Sitoro Sitoro detector for Phoenix
|
|
||||||
|
|
||||||
|
|
||||||
Parent class: PSIDetectorBase
|
|
||||||
|
|
||||||
class attributes:
|
|
||||||
custom_prepare_cls (SitoroSetup) : Custom detector setup class,
|
|
||||||
inherits from CustomDetectorMixin
|
|
||||||
|
|
||||||
PSIDetectorBase.set_min_readout (float) : Minimum readout time for the detector
|
|
||||||
dxp1, .. dxpi, .. , dxpN (SitoroEpicsDXP) : DXP parameters for Sitoro detector Nr i
|
|
||||||
mca1, .. mcai, .. , mcaN (SitoroEpicsMCARecord) : MCA parameters for Sitoro detector Nr i
|
|
||||||
|
|
||||||
hdf5 (SitoroHDF5Plugins) : HDF5 parameters for Sitoro detector
|
|
||||||
MIN_READOUT (float) : Minimum readout time for the detector
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
# Specify which functions are revealed to the user in BEC client
|
|
||||||
USER_ACCESS = ["describe"]
|
|
||||||
|
|
||||||
# specify Setup class
|
|
||||||
custom_prepare_cls = SitoroSetup
|
|
||||||
# specify minimum readout time for detector
|
|
||||||
MIN_READOUT = 3e-3
|
|
||||||
TIMEOUT_FOR_SIGNALS = 1
|
|
||||||
|
|
||||||
# specify class attributes
|
|
||||||
|
|
||||||
# Parameters for individual detector elements
|
|
||||||
# Note: need to wrote 'dxp: here, but not dxp'
|
|
||||||
|
|
||||||
# dxp1 = Cpt(SitoroEpicsDXP, "dxp1:")
|
|
||||||
# dxp2 = Cpt(SitoroEpicsDXP, "dxp2:")
|
|
||||||
# dxp3 = Cpt(SitoroEpicsDXP, "dxp3:")
|
|
||||||
# dxp4 = Cpt(SitoroEpicsDXP, "dxp4:")
|
|
||||||
|
|
||||||
#
|
|
||||||
# THIS IS NOT WELL-DONE as it take out one part of mca.py from ophy.py
|
|
||||||
#
|
|
||||||
#
|
|
||||||
# mca1 = Cpt(SitoroEpicsMCARecordExtended, "mca1")
|
|
||||||
# mca2 = Cpt(SitoroEpicsMCARecordExtended, "mca2")
|
|
||||||
# mca3 = Cpt(SitoroEpicsMCARecordExtended, "mca3")
|
|
||||||
# mca4 = Cpt(SitoroEpicsMCARecordExtended, "mca4")
|
|
||||||
|
|
||||||
# need to write 'mca1', but not 'mca1:'
|
|
||||||
# mca1 = Cpt(EpicsMCARecord, "mca1")
|
|
||||||
# mca2 = Cpt(EpicsMCARecord, "mca2")
|
|
||||||
# mca3 = Cpt(EpicsMCARecord, "mca3")
|
|
||||||
# mca4 = Cpt(EpicsMCARecord, "mca4")
|
|
||||||
|
|
||||||
# other general parameters
|
|
||||||
hdf5 = Cpt(SitoroHDF5Plugins, "HDF1:")
|
|
||||||
|
|
||||||
# stop_all = Cpt(EpicsSignal, "StopAll")
|
|
||||||
# erase_all = Cpt(EpicsSignal, "EraseAll")
|
|
||||||
# start_all = Cpt(EpicsSignal, "StartAll")
|
|
||||||
# state = Cpt(EpicsSignal, "Acquiring") # <-- This is from cSAX implementation
|
|
||||||
# acquiring = Cpt(EpicsSignal, "Acquiring")
|
|
||||||
# preset_mode = Cpt(EpicsSignal, "PresetMode") # 0 No preset 1 Real time 2 Events 3 Triggers
|
|
||||||
# preset_real = Cpt(EpicsSignal, "PresetReal")
|
|
||||||
# preset_events = Cpt(EpicsSignal, "PresetEvents")
|
|
||||||
# preset_triggers = Cpt(EpicsSignal, "PresetTriggers")
|
|
||||||
|
|
||||||
# _________________ General Epic parameters
|
|
||||||
|
|
||||||
# changes Oct 2024
|
|
||||||
# triggers--> max_triggers,
|
|
||||||
# events-->max_events
|
|
||||||
# input_count_rate--> max_input_count_rate
|
|
||||||
# output_count_rate--> max_output_count_rate
|
|
||||||
|
|
||||||
# max_triggers = Cpt(EpicsSignalRO, "MaxTriggers", lazy=True)
|
|
||||||
# max_events = Cpt(EpicsSignalRO, "MaxEvents", lazy=True)
|
|
||||||
|
|
||||||
# max_input_count_rate = Cpt(EpicsSignalRO, "MaxInputCountRate", lazy=True)
|
|
||||||
# max_output_count_rate = Cpt(EpicsSignalRO, "MaxOutputCountRate", lazy=True)
|
|
||||||
|
|
||||||
# collect_mode = Cpt(EpicsSignal, "CollectMode") # 0 MCA spectra, 1 MCA mapping
|
|
||||||
# pixel_advance_mode = Cpt(EpicsSignal, "PixelAdvanceMode")
|
|
||||||
# ignore_gate = Cpt(EpicsSignal, "IgnoreGate")
|
|
||||||
# input_logic_polarity = Cpt(EpicsSignal, "InputLogicPolarity")
|
|
||||||
# auto_pixels_per_buffer = Cpt(EpicsSignal, "AutoPixelsPerBuffer")
|
|
||||||
# pixels_per_buffer = Cpt(EpicsSignal, "PixelsPerBuffer")
|
|
||||||
# pixels_per_run = Cpt(EpicsSignal, "PixelsPerRun")
|
|
||||||
# print(pixel_per_run
|
|
||||||
# if "SITORO" in prefix:
|
|
||||||
nd_array_mode = Cpt(EpicsSignal, "NDArrayMode")
|
|
||||||
# endif
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
sitoro = SitoroPhoenix(name="sitoro", prefix="X07MB-SITORO:", sim_mode=True)
|
|
@ -78,6 +78,7 @@ class EpicsDXPXMAP(Device):
|
|||||||
|
|
||||||
current_pixel = Cpt(EpicsSignalRO, "CurrentPixel")
|
current_pixel = Cpt(EpicsSignalRO, "CurrentPixel")
|
||||||
|
|
||||||
|
Q
|
||||||
class XMAPHDF5Plugins(Device):
|
class XMAPHDF5Plugins(Device):
|
||||||
"""
|
"""
|
||||||
HDF5 parameters for XMAP detector
|
HDF5 parameters for XMAP detector
|
||||||
@ -307,10 +308,13 @@ class XMAPSetup(CustomDetectorMixin):
|
|||||||
self.parent.ignore_gate.put(ignore_gate)
|
self.parent.ignore_gate.put(ignore_gate)
|
||||||
|
|
||||||
|
|
||||||
class XMAPPhoenix(PSIDetectorBase):
|
class XMAPphoenix(PSIDetectorBase):
|
||||||
"""MCA
|
"""MCA
|
||||||
XMAP detector for phoenix
|
XMAP detector for phoenix
|
||||||
custom_prepare_cls (XMAPSetu
|
|
||||||
|
Parent class: PSIDetectorBase
|
||||||
|
|
||||||
|
class attributes:
|
||||||
custom_prepare_cls (XMAPSetup) : Custom detector setup class for cSAXS,
|
custom_prepare_cls (XMAPSetup) : Custom detector setup class for cSAXS,
|
||||||
inherits from CustomDetectorMixin
|
inherits from CustomDetectorMixin
|
||||||
in __init__ of PSIDetecor base
|
in __init__ of PSIDetecor base
|
||||||
@ -334,9 +338,9 @@ class XMAPPhoenix(PSIDetectorBase):
|
|||||||
dxp = Cpt(EpicsDXPXMAP, "dxp1:")
|
dxp = Cpt(EpicsDXPXMAP, "dxp1:")
|
||||||
|
|
||||||
mca1 = Cpt(EpicsMCARecord, "mca1")
|
mca1 = Cpt(EpicsMCARecord, "mca1")
|
||||||
#mca2 = Cpt(EpicsMCARecord, "mca2")
|
mca2 = Cpt(EpicsMCARecord, "mca2")
|
||||||
#mca3 = Cpt(EpicsMCARecord, "mca3")
|
mca3 = Cpt(EpicsMCARecord, "mca3")
|
||||||
#mca4 = Cpt(EpicsMCARecord, "mca4")
|
mca4 = Cpt(EpicsMCARecord, "mca4")
|
||||||
print('load hdf5')
|
print('load hdf5')
|
||||||
#hdf5 = Cpt(XMAPHDF5Plugins, "HDF1:")
|
#hdf5 = Cpt(XMAPHDF5Plugins, "HDF1:")
|
||||||
|
|
||||||
@ -359,11 +363,5 @@ class XMAPPhoenix(PSIDetectorBase):
|
|||||||
auto_pixels_per_buffer = Cpt(EpicsSignal, "AutoPixelsPerBuffer")
|
auto_pixels_per_buffer = Cpt(EpicsSignal, "AutoPixelsPerBuffer")
|
||||||
pixels_per_buffer = Cpt(EpicsSignal, "PixelsPerBuffer")
|
pixels_per_buffer = Cpt(EpicsSignal, "PixelsPerBuffer")
|
||||||
pixels_per_run = Cpt(EpicsSignal, "PixelsPerRun")
|
pixels_per_run = Cpt(EpicsSignal, "PixelsPerRun")
|
||||||
|
|
||||||
|
|
||||||
#nd_array_mode = Cpt(EpicsSignal, "NDArrayMode")
|
#nd_array_mode = Cpt(EpicsSignal, "NDArrayMode")
|
||||||
print('DONE connecton chanels in XMAPphoenix')
|
print('DONE connecton chanels in XMAPphoenix')
|
||||||
|
|
||||||
|
|
||||||
def aaaa(self):
|
|
||||||
print('aaaa')
|
|
||||||
|
@ -1,17 +0,0 @@
|
|||||||
# against all rues, make sure ff and falcon are really
|
|
||||||
# creates newly
|
|
||||||
|
|
||||||
|
|
||||||
ff = 0
|
|
||||||
falcon = 0
|
|
||||||
|
|
||||||
from ophyd import Component as Cpt
|
|
||||||
import phoenix_bec.devices.falcon_phoenix as ff
|
|
||||||
|
|
||||||
falcon = ff.FalconPhoenix(name="falcon_hdf5", prefix="X07MB-SITORO:")
|
|
||||||
# xmap = ff.FalconPhoenix(name="falcon_hdf5", prefix="X07MB-XMAP:")
|
|
||||||
# make a 'get to read all epics channels
|
|
||||||
# there will be an error message, if device contains a channel whcih does not exist
|
|
||||||
w = falcon.get()
|
|
||||||
|
|
||||||
print(w)
|
|
@ -1,435 +0,0 @@
|
|||||||
FILE ophyd_devices/ophy_devices/devices/interfaces/base_classes
|
|
||||||
|
|
||||||
|
|
||||||
"""This module contains the base class for SLS detectors. We follow the approach to integrate
|
|
||||||
PSI detectors into the BEC system based on this base class. The base class is used to implement
|
|
||||||
certain methods that are expected by BEC, such as stage, unstage, trigger, stop, etc...
|
|
||||||
We use composition with a custom prepare class to implement BL specific logic for the detector.
|
|
||||||
The beamlines need to inherit from the Custoon_
|
|
||||||
import threading
|
|
||||||
import time
|
|
||||||
import traceback
|
|
||||||
|
|
||||||
from bec_lib import messages
|
|
||||||
from bec_lib.endpoints import MessageEndpoints
|
|
||||||
from bec_lib.file_utils import FileWriter
|
|
||||||
from bec_lib.logger import bec_logger
|
|
||||||
from ophyd import Component, Device, DeviceStatus, Kind
|
|
||||||
from ophyd.device import Staged
|
|
||||||
|
|
||||||
from ophyd_devices.sim.sim_signals import SetableSignal
|
|
||||||
from ophyd_devices.utils import bec_utils
|
|
||||||
from ophyd_devices.utils.bec_scaninfo_mixin import BecScaninfoMixin
|
|
||||||
from ophyd_devices.utils.errors import DeviceStopError, DeviceTimeoutError
|
|
||||||
|
|
||||||
logger = bec_logger.logger
|
|
||||||
|
|
||||||
|
|
||||||
class DetectorInitError(Exception):
|
|
||||||
"""Raised when initiation of the device class fails,
|
|
||||||
due to missing device manager or not started in sim_mode."""
|
|
||||||
|
|
||||||
|
|
||||||
class CustomDetectorMixin:
|
|
||||||
"""
|
|
||||||
Mixin class for custom detector logic
|
|
||||||
|
|
||||||
This class is used to implement BL specific logic for the detector.
|
|
||||||
It is used in the PSIDetectorBase class.
|
|
||||||
|
|
||||||
For the integration of a new detector, the following functions should
|
|
||||||
help with integrating functionality, but additional ones can be added.
|
|
||||||
|
|
||||||
Check PSIDetectorBase for the functions that are called during relevant function calls of
|
|
||||||
stage, unstage, trigger, stop and _init.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, *_args, parent: Device = None, **_kwargs) -> None:
|
|
||||||
self.parent = parent
|
|
||||||
|
|
||||||
def on_init(self) -> None:
|
|
||||||
"""
|
|
||||||
Init sequence for the detector
|
|
||||||
"""
|
|
||||||
|
|
||||||
def on_stage(self) -> None:
|
|
||||||
"""
|
|
||||||
Specify actions to be executed during stage in preparation for a scan.
|
|
||||||
self.parent.scaninfo already has all current parameters for the upcoming scan.
|
|
||||||
|
|
||||||
In case the backend service is writing data on disk, this step should include publishing
|
|
||||||
a file_event and file_message to BEC to inform the system where the data is written to.
|
|
||||||
|
|
||||||
IMPORTANT:
|
|
||||||
It must be safe to assume that the device is ready for the scan
|
|
||||||
to start immediately once this function is finished.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def on_unstage(self) -> None:
|
|
||||||
"""
|
|
||||||
Specify actions to be executed during unstage.
|
|
||||||
|
|
||||||
This step should include checking if the acqusition was successful,
|
|
||||||
and publishing the file location and file event message,
|
|
||||||
with flagged done to BEC.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def on_stop(self) -> None:
|
|
||||||
"""
|
|
||||||
Specify actions to be executed during stop.
|
|
||||||
This must also set self.parent.stopped to True.
|
|
||||||
|
|
||||||
This step should include stopping the detector and backend service.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def on_trigger(self) -> None | DeviceStatus:
|
|
||||||
"""
|
|
||||||
Specify actions to be executed upon receiving trigger signal.
|
|
||||||
Return a DeviceStatus object or None
|
|
||||||
"""
|
|
||||||
|
|
||||||
def on_pre_scan(self) -> None:
|
|
||||||
"""
|
|
||||||
Specify actions to be executed right before a scan starts.
|
|
||||||
|
|
||||||
Only use if needed, and it is recommended to keep this function as short/fast as possible.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def on_complete(self) -> None | DeviceStatus:
|
|
||||||
"""
|
|
||||||
Specify actions to be executed when the scan is complete.
|
|
||||||
|
|
||||||
This can for instance be to check with the detector and backend if all data is written succsessfully.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def publish_file_location(self, done: bool, successful: bool, metadata: dict = None) -> None:
|
|
||||||
"""
|
|
||||||
Publish the filepath to REDIS.
|
|
||||||
|
|
||||||
We publish two events here:
|
|
||||||
- file_event: event for the filewriter
|
|
||||||
- public_file: event for any secondary service (e.g. radial integ code)
|
|
||||||
|
|
||||||
Args:
|
|
||||||
done (bool): True if scan is finished
|
|
||||||
successful (bool): True if scan was successful
|
|
||||||
metadata (dict): additional metadata to publish
|
|
||||||
"""
|
|
||||||
if metadata is None:
|
|
||||||
metadata = {}
|
|
||||||
|
|
||||||
msg = messages.FileMessage(
|
|
||||||
file_path=self.parent.filepath.get(),
|
|
||||||
done=done,
|
|
||||||
successful=successful,
|
|
||||||
metadata=metadata,
|
|
||||||
)
|
|
||||||
pipe = self.parent.connector.pipeline()
|
|
||||||
self.parent.connector.set_and_publish(
|
|
||||||
MessageEndpoints.public_file(self.parent.scaninfo.scan_id, self.parent.name),
|
|
||||||
msg,
|
|
||||||
pipe=pipe,
|
|
||||||
)
|
|
||||||
self.parent.connector.set_and_publish(
|
|
||||||
MessageEndpoints.file_event(self.parent.name), msg, pipe=pipe
|
|
||||||
)
|
|
||||||
pipe.execute()
|
|
||||||
|
|
||||||
def wait_for_signals(
|
|
||||||
self,
|
|
||||||
signal_conditions: list[tuple],
|
|
||||||
timeout: float,
|
|
||||||
check_stopped: bool = False,
|
|
||||||
interval: float = 0.05,
|
|
||||||
all_signals: bool = False,
|
|
||||||
) -> bool:
|
|
||||||
"""
|
|
||||||
Convenience wrapper to allow waiting for signals to reach a certain condition.
|
|
||||||
For EPICs PVs, an example usage is pasted at the bottom.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
signal_conditions (list[tuple]): tuple of executable calls for conditions (get_current_state, condition) to check
|
|
||||||
timeout (float): timeout in seconds
|
|
||||||
interval (float): interval in seconds
|
|
||||||
all_signals (bool): True if all signals should be True, False if any signal should be True
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
bool: True if all signals are in the desired state, False if timeout is reached
|
|
||||||
|
|
||||||
>>> Example usage for EPICS PVs:
|
|
||||||
>>> self.wait_for_signals(signal_conditions=[(self.acquiring.get, False)], timeout=5, interval=0.05, check_stopped=True, all_signals=True)
|
|
||||||
"""
|
|
||||||
|
|
||||||
timer = 0
|
|
||||||
while True:
|
|
||||||
checks = [
|
|
||||||
get_current_state() == condition
|
|
||||||
for get_current_state, condition in signal_conditions
|
|
||||||
]
|
|
||||||
if check_stopped is True and self.parent.stopped is True:
|
|
||||||
return False
|
|
||||||
if (all_signals and all(checks)) or (not all_signals and any(checks)):
|
|
||||||
return True
|
|
||||||
if timer > timeout:
|
|
||||||
return False
|
|
||||||
time.sleep(interval)
|
|
||||||
timer += interval
|
|
||||||
|
|
||||||
def wait_with_status(
|
|
||||||
self,
|
|
||||||
signal_conditions: list[tuple],
|
|
||||||
timeout: float,
|
|
||||||
check_stopped: bool = False,
|
|
||||||
interval: float = 0.05,
|
|
||||||
all_signals: bool = False,
|
|
||||||
exception_on_timeout: Exception = None,
|
|
||||||
) -> DeviceStatus:
|
|
||||||
"""Utility function to wait for signals in a thread.
|
|
||||||
Returns a DevicesStatus object that resolves either to set_finished or set_exception.
|
|
||||||
The DeviceStatus is attached to the parent device, i.e. the detector object inheriting from PSIDetectorBase.
|
|
||||||
|
|
||||||
Usage:
|
|
||||||
This function should be used to wait for signals to reach a certain condition, especially in the context of
|
|
||||||
on_trigger and on_complete. If it is not used, functions may block and slow down the performance of BEC.
|
|
||||||
It will return a DeviceStatus object that is to be returned from the function. Once the conditions are met,
|
|
||||||
the DeviceStatus will be set to set_finished in case of success or set_exception in case of a timeout or exception.
|
|
||||||
The exception can be specified with the exception_on_timeout argument. The default exception is a TimeoutError.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
signal_conditions (list[tuple]): tuple of executable calls for conditions (get_current_state, condition) to check
|
|
||||||
timeout (float): timeout in seconds
|
|
||||||
check_stopped (bool): True if stopped flag should be checked
|
|
||||||
interval (float): interval in seconds
|
|
||||||
all_signals (bool): True if all signals should be True, False if any signal should be True
|
|
||||||
exception_on_timeout (Exception): Exception to raise on timeout
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
DeviceStatus: DeviceStatus object that resolves either to set_finished or set_exception
|
|
||||||
"""
|
|
||||||
if exception_on_timeout is None:
|
|
||||||
exception_on_timeout = DeviceTimeoutError(
|
|
||||||
f"Timeout error for {self.parent.name} while waiting for signals {signal_conditions}"
|
|
||||||
)
|
|
||||||
|
|
||||||
status = DeviceStatus(self.parent)
|
|
||||||
|
|
||||||
# utility function to wrap the wait_for_signals function
|
|
||||||
def wait_for_signals_wrapper(
|
|
||||||
status: DeviceStatus,
|
|
||||||
signal_conditions: list[tuple],
|
|
||||||
timeout: float,
|
|
||||||
check_stopped: bool,
|
|
||||||
interval: float,
|
|
||||||
all_signals: bool,
|
|
||||||
exception_on_timeout: Exception,
|
|
||||||
):
|
|
||||||
"""Convenient wrapper around wait_for_signals to set status based on the result.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
status (DeviceStatus): DeviceStatus object to be set
|
|
||||||
signal_conditions (list[tuple]): tuple of executable calls for conditions (get_current_state, condition) to check
|
|
||||||
timeout (float): timeout in seconds
|
|
||||||
check_stopped (bool): True if stopped flag should be checked
|
|
||||||
interval (float): interval in seconds
|
|
||||||
all_signals (bool): True if all signals should be True, False if any signal should be True
|
|
||||||
exception_on_timeout (Exception): Exception to raise on timeout
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
result = self.wait_for_signals(
|
|
||||||
signal_conditions, timeout, check_stopped, interval, all_signals
|
|
||||||
)
|
|
||||||
if result:
|
|
||||||
status.set_finished()
|
|
||||||
else:
|
|
||||||
if self.parent.stopped:
|
|
||||||
# INFO This will execute a callback to the parent device.stop() method
|
|
||||||
status.set_exception(exc=DeviceStopError(f"{self.parent.name} was stopped"))
|
|
||||||
else:
|
|
||||||
# INFO This will execute a callback to the parent device.stop() method
|
|
||||||
status.set_exception(exc=exception_on_timeout)
|
|
||||||
# pylint: disable=broad-except
|
|
||||||
except Exception as exc:
|
|
||||||
content = traceback.format_exc()
|
|
||||||
logger.warning(
|
|
||||||
f"Error in wait_for_signals in {self.parent.name}; Traceback: {content}"
|
|
||||||
)
|
|
||||||
# INFO This will execute a callback to the parent device.stop() method
|
|
||||||
status.set_exception(exc=exc)
|
|
||||||
|
|
||||||
thread = threading.Thread(
|
|
||||||
target=wait_for_signals_wrapper,
|
|
||||||
args=(
|
|
||||||
status,
|
|
||||||
signal_conditions,
|
|
||||||
timeout,
|
|
||||||
check_stopped,
|
|
||||||
interval,
|
|
||||||
all_signals,
|
|
||||||
exception_on_timeout,
|
|
||||||
),
|
|
||||||
daemon=True,
|
|
||||||
)
|
|
||||||
thread.start()
|
|
||||||
return status
|
|
||||||
|
|
||||||
|
|
||||||
class PSIDetectorBase(Device):
|
|
||||||
"""
|
|
||||||
Abstract base class for SLS detectors
|
|
||||||
|
|
||||||
Class attributes:
|
|
||||||
custom_prepare_cls (object): class for custom prepare logic (BL specific)
|
|
||||||
|
|
||||||
Args:
|
|
||||||
prefix (str): EPICS PV prefix for component (optional)
|
|
||||||
name (str): name of the device, as will be reported via read()
|
|
||||||
kind (str): member of class 'ophydobj.Kind', defaults to Kind.normal
|
|
||||||
omitted -> readout ignored for read 'ophydobj.read()'
|
|
||||||
normal -> readout for read
|
|
||||||
config -> config parameter for 'ophydobj.read_configuration()'
|
|
||||||
hinted -> which attribute is readout for read
|
|
||||||
parent (object): instance of the parent device
|
|
||||||
device_manager (object): bec device manager
|
|
||||||
**kwargs: keyword arguments
|
|
||||||
"""
|
|
||||||
|
|
||||||
filepath = Component(SetableSignal, value="", kind=Kind.config)
|
|
||||||
|
|
||||||
custom_prepare_cls = CustomDetectorMixin
|
|
||||||
|
|
||||||
def __init__(self, prefix="", *, name, kind=None, parent=None, device_manager=None, **kwargs):
|
|
||||||
super().__init__(prefix=prefix, name=name, kind=kind, parent=parent, **kwargs)
|
|
||||||
self.stopped = False
|
|
||||||
self.name = name
|
|
||||||
self.service_cfg = None
|
|
||||||
self.scaninfo = None
|
|
||||||
self.filewriter = None
|
|
||||||
|
|
||||||
if not issubclass(self.custom_prepare_cls, CustomDetectorMixin):
|
|
||||||
raise DetectorInitError("Custom prepare class must be subclass of CustomDetectorMixin")
|
|
||||||
self.custom_prepare = self.custom_prepare_cls(parent=self, **kwargs)
|
|
||||||
|
|
||||||
if device_manager:
|
|
||||||
self._update_service_config()
|
|
||||||
self.device_manager = device_manager
|
|
||||||
else:
|
|
||||||
self.device_manager = bec_utils.DMMock()
|
|
||||||
base_path = kwargs["basepath"] if "basepath" in kwargs else "."
|
|
||||||
self.service_cfg = {"base_path": os.path.abspath(base_path)}
|
|
||||||
|
|
||||||
self.connector = self.device_manager.connector
|
|
||||||
self._update_scaninfo()
|
|
||||||
self._update_filewriter()
|
|
||||||
self._init()
|
|
||||||
|
|
||||||
def _update_filewriter(self) -> None:
|
|
||||||
"""Update filewriter with service config"""
|
|
||||||
self.filewriter = FileWriter(service_config=self.service_cfg, connector=self.connector)
|
|
||||||
|
|
||||||
def _update_scaninfo(self) -> None:
|
|
||||||
"""Update scaninfo from BecScaninfoMixing
|
|
||||||
This depends on device manager and operation/sim_mode
|
|
||||||
"""
|
|
||||||
self.scaninfo = BecScaninfoMixin(self.device_manager)
|
|
||||||
self.scaninfo.load_scan_metadata()
|
|
||||||
|
|
||||||
def _update_service_config(self) -> None:
|
|
||||||
"""Update service config from BEC service config
|
|
||||||
|
|
||||||
If bec services are not running and SERVICE_CONFIG is NONE, we fall back to the current directory.
|
|
||||||
"""
|
|
||||||
# pylint: disable=import-outside-toplevel
|
|
||||||
from bec_lib.bec_service import SERVICE_CONFIG
|
|
||||||
|
|
||||||
if SERVICE_CONFIG:
|
|
||||||
self.service_cfg = SERVICE_CONFIG.config["service_config"]["file_writer"]
|
|
||||||
return
|
|
||||||
self.service_cfg = {"base_path": os.path.abspath(".")}
|
|
||||||
|
|
||||||
def check_scan_id(self) -> None:
|
|
||||||
"""Checks if scan_id has changed and set stopped flagged to True if it has."""
|
|
||||||
old_scan_id = self.scaninfo.scan_id
|
|
||||||
self.scaninfo.load_scan_metadata()
|
|
||||||
if self.scaninfo.scan_id != old_scan_id:
|
|
||||||
self.stopped = True
|
|
||||||
|
|
||||||
def _init(self) -> None:
|
|
||||||
"""Initialize detector, filewriter and set default parameters"""
|
|
||||||
self.custom_prepare.on_init()
|
|
||||||
|
|
||||||
def stage(self) -> list[object]:
|
|
||||||
"""
|
|
||||||
Stage device in preparation for a scan.
|
|
||||||
First we check if the device is already staged. Stage is idempotent,
|
|
||||||
if staged twice it should raise (we let ophyd.Device handle the raise here).
|
|
||||||
We reset the stopped flag and get the scaninfo from BEC, before calling custom_prepare.on_stage.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
list(object): list of objects that were staged
|
|
||||||
|
|
||||||
"""
|
|
||||||
if self._staged != Staged.no:
|
|
||||||
return super().stage()
|
|
||||||
self.stopped = False
|
|
||||||
self.scaninfo.load_scan_metadata()
|
|
||||||
self.custom_prepare.on_stage()
|
|
||||||
return super().stage()
|
|
||||||
|
|
||||||
def pre_scan(self) -> None:
|
|
||||||
"""Pre-scan logic.
|
|
||||||
|
|
||||||
This function will be called from BEC directly before the scan core starts, and should only implement
|
|
||||||
time-critical actions. Therefore, it should also be kept as short/fast as possible.
|
|
||||||
I.e. Arming a detector in case there is a risk of timing out.
|
|
||||||
"""
|
|
||||||
self.custom_prepare.on_pre_scan()
|
|
||||||
|
|
||||||
def trigger(self) -> DeviceStatus:
|
|
||||||
"""Trigger the detector, called from BEC."""
|
|
||||||
# pylint: disable=assignment-from-no-return
|
|
||||||
status = self.custom_prepare.on_trigger()
|
|
||||||
if isinstance(status, DeviceStatus):
|
|
||||||
return status
|
|
||||||
return super().trigger()
|
|
||||||
|
|
||||||
def complete(self) -> None:
|
|
||||||
"""Complete the acquisition, called from BEC.
|
|
||||||
|
|
||||||
This function is called after the scan is complete, just before unstage.
|
|
||||||
We can check here with the data backend and detector if the acquisition successfully finished.
|
|
||||||
|
|
||||||
Actions are implemented in custom_prepare.on_complete since they are beamline specific.
|
|
||||||
"""
|
|
||||||
# pylint: disable=assignment-from-no-return
|
|
||||||
status = self.custom_prepare.on_complete()
|
|
||||||
if isinstance(status, DeviceStatus):
|
|
||||||
return status
|
|
||||||
status = DeviceStatus(self)
|
|
||||||
status.set_finished()
|
|
||||||
return status
|
|
||||||
|
|
||||||
def unstage(self) -> list[object]:
|
|
||||||
"""
|
|
||||||
Unstage device after a scan.
|
|
||||||
|
|
||||||
We first check if the scanID has changed, thus, the scan was unexpectedly interrupted but the device was not stopped.
|
|
||||||
If that is the case, the stopped flag is set to True, which will immediately unstage the device.
|
|
||||||
|
|
||||||
Custom_prepare.on_unstage is called to allow for BL specific logic to be executed.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
list(object): list of objects that were unstaged
|
|
||||||
"""
|
|
||||||
self.check_scan_id()
|
|
||||||
self.custom_prepare.on_unstage()
|
|
||||||
self.stopped = False
|
|
||||||
return super().unstage()
|
|
||||||
|
|
||||||
def stop(self, *, success=False) -> None:
|
|
||||||
"""
|
|
||||||
Stop the scan, with camera and file writer
|
|
||||||
|
|
||||||
"""
|
|
||||||
self.custom_prepare.on_stop()
|
|
||||||
super().stop(success=success)
|
|
||||||
self.stopped = True
|
|
@ -1,512 +0,0 @@
|
|||||||
"""
|
|
||||||
This module contains the Scans class and related classes for defining and running scans in BEC
|
|
||||||
from the client side.
|
|
||||||
"""
|
|
||||||
|
|
||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
import builtins
|
|
||||||
import uuid
|
|
||||||
from collections.abc import Callable
|
|
||||||
from contextlib import ContextDecorator
|
|
||||||
from copy import deepcopy
|
|
||||||
from typing import TYPE_CHECKING, Dict, Literal
|
|
||||||
|
|
||||||
from toolz import partition
|
|
||||||
from typeguard import typecheck
|
|
||||||
from bec_lib import messages
|
|
||||||
from bec_lib.bec_errors import ScanAbortion
|
|
||||||
from bec_lib.client import SystemConfig
|
|
||||||
from bec_lib.device import DeviceBase
|
|
||||||
from bec_lib.endpoints import MessageEndpoints
|
|
||||||
from bec_lib.logger import bec_logger
|
|
||||||
from bec_lib.scan_report import ScanReport
|
|
||||||
from bec_lib.signature_serializer import dict_to_signature
|
|
||||||
from bec_lib.utils import scan_to_csv
|
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
|
||||||
from bec_lib.client import BECClient
|
|
||||||
from bec_lib.connector import ConsumerConnector
|
|
||||||
|
|
||||||
logger = bec_logger.logger
|
|
||||||
|
|
||||||
|
|
||||||
class ScanObject:
|
|
||||||
"""ScanObject is a class for scans"""
|
|
||||||
|
|
||||||
def __init__(self, scan_name: str, scan_info: dict, client: BECClient = None) -> None:
|
|
||||||
self.scan_name = scan_name
|
|
||||||
self.scan_info = scan_info
|
|
||||||
self.client = client
|
|
||||||
|
|
||||||
# run must be an anonymous function to allow for multiple doc strings
|
|
||||||
# pylint: disable=unnecessary-lambda
|
|
||||||
self.run = lambda *args, **kwargs: self._run(*args, **kwargs)
|
|
||||||
|
|
||||||
def _run(
|
|
||||||
self,
|
|
||||||
*args,
|
|
||||||
callback: Callable = None,
|
|
||||||
async_callback: Callable = None,
|
|
||||||
hide_report: bool = False,
|
|
||||||
metadata: dict = None,
|
|
||||||
monitored: list[str | DeviceBase] = None,
|
|
||||||
file_suffix: str = None,
|
|
||||||
file_directory: str = None,
|
|
||||||
**kwargs,
|
|
||||||
) -> ScanReport:
|
|
||||||
"""
|
|
||||||
Run the request with the given arguments.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
*args: Arguments for the scan
|
|
||||||
callback: Callback function
|
|
||||||
async_callback: Asynchronous callback function
|
|
||||||
hide_report: Hide the report
|
|
||||||
metadata: Metadata dictionary
|
|
||||||
monitored: List of monitored devices
|
|
||||||
**kwargs: Keyword arguments
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
ScanReport
|
|
||||||
"""
|
|
||||||
if self.client.alarm_handler.alarms_stack:
|
|
||||||
logger.info("The alarm stack is not empty but will be cleared now.")
|
|
||||||
self.client.clear_all_alarms()
|
|
||||||
scans = self.client.scans
|
|
||||||
|
|
||||||
# pylint: disable=protected-access
|
|
||||||
hide_report = hide_report or scans._hide_report
|
|
||||||
|
|
||||||
user_metadata = deepcopy(self.client.metadata)
|
|
||||||
|
|
||||||
sys_config = self.client.system_config.model_copy(deep=True)
|
|
||||||
if file_suffix:
|
|
||||||
sys_config.file_suffix = file_suffix
|
|
||||||
if file_directory:
|
|
||||||
sys_config.file_directory = file_directory
|
|
||||||
|
|
||||||
if "sample_name" not in user_metadata:
|
|
||||||
var = self.client.get_globa file_suffix: str = None,
|
|
||||||
l_var("sample_name")
|
|
||||||
if var is not None:
|
|
||||||
user_metadata["sample_name"] = var
|
|
||||||
|
|
||||||
if metadata is not None:
|
|
||||||
user_metadata.update(metadata)
|
|
||||||
|
|
||||||
if monitored is not None:
|
|
||||||
if not isinstance(monitored, list):
|
|
||||||
monitored = [monitored]
|
|
||||||
for mon_device in monitored:
|
|
||||||
if isinstance(mon_device, str):
|
|
||||||
mon_device = self.client.device_manager.devices.get(mon_device)
|
|
||||||
if not mon_device:
|
|
||||||
raise RuntimeError(
|
|
||||||
f"Specified monitored device {mon_device} does not exist in the current device configuration."
|
|
||||||
)
|
|
||||||
kwargs["monitored"] = monitored
|
|
||||||
|
|
||||||
sys_config = sys_config.model_dump()
|
|
||||||
# pylint: disable=protected-access
|
|
||||||
if scans._scan_group:
|
|
||||||
sys_config["queue_group"] = scans._scan_group
|
|
||||||
if scans._scan_def_id:
|
|
||||||
sys_config["scan_def_id"] = scans._scan_def_id
|
|
||||||
if scans._dataset_id_on_hold:
|
|
||||||
sys_config["dataset_id_on_hold"] = scans._dataset_id_on_hold
|
|
||||||
|
|
||||||
kwargs["user_metadata"] = user_metadata
|
|
||||||
kwargs["system_config"] = sys_config
|
|
||||||
|
|
||||||
request = Scans.prepare_scan_request(self.scan_name, self.scan_info, *args, **kwargs)
|
|
||||||
request_id = str(uuid.uuid4())
|
|
||||||
|
|
||||||
# pylint: disable=unsupported-assignment-operation
|
|
||||||
request.metadata["RID"] = request_id
|
|
||||||
|
|
||||||
self._send_scan_request(request)
|
|
||||||
|
|
||||||
report = ScanReport.from_request(request, client=self.client)
|
|
||||||
report.request.callbacks.register_many("scan_segment", callback, sync=True)
|
|
||||||
report.request.callbacks.register_many("scan_segment", async_callback, sync=False)
|
|
||||||
|
|
||||||
if scans._scan_export and scans._scan_export.scans is not None:
|
|
||||||
scans._scan_export.scans.append(report)
|
|
||||||
|
|
||||||
if not hide_report and self.client.live_updates:
|
|
||||||
self.client.live_updates.process_request(request, callback)
|
|
||||||
|
|
||||||
self.client.callbacks.poll()
|
|
||||||
|
|
||||||
return report
|
|
||||||
|
|
||||||
def _start_register(self, request: messages.ScanQueueMessage) -> ConsumerConnector:
|
|
||||||
"""Start a register for the given request"""
|
|
||||||
register = self.client.device_manager.connector.register(
|
|
||||||
[
|
|
||||||
MessageEndpoints.device_readback(dev)
|
|
||||||
for dev in request.content["parameter"]["args"].keys()
|
|
||||||
],
|
|
||||||
threaded=False,
|
|
||||||
cb=(lambda msg: msg),
|
|
||||||
)
|
|
||||||
return register
|
|
||||||
|
|
||||||
def _send_scan_request(self, request: messages.ScanQueueMessage) -> None:
|
|
||||||
"""Send a scan request to the scan server"""
|
|
||||||
self.client.device_manager.connector.send(MessageEndpoints.scan_queue_request(), request)
|
|
||||||
|
|
||||||
|
|
||||||
class Scans:
|
|
||||||
"""Scans is a class for available scans in BEC"""
|
|
||||||
|
|
||||||
def __init__(self, parent):
|
|
||||||
self.parent = parent
|
|
||||||
self._available_scans = {}
|
|
||||||
self._import_scans()
|
|
||||||
self._scan_group = None
|
|
||||||
self._scan_def_id = None
|
|
||||||
self._scan_group_ctx = ScanGroup(parent=self)
|
|
||||||
self._scan_def_ctx = ScanDef(parent=self)
|
|
||||||
self._hide_report = None
|
|
||||||
self._hide_report_ctx = HideReport(parent=self)
|
|
||||||
self._dataset_id_on_hold = None
|
|
||||||
self._dataset_id_on_hold_ctx = DatasetIdOnHold(parent=self)
|
|
||||||
self._scan_export = None
|
|
||||||
|
|
||||||
def _import_scans(self):
|
|
||||||
"""Import scans from the scan server"""
|
|
||||||
available_scans = self.parent.connector.get(MessageEndpoints.available_scans())
|
|
||||||
if available_scans is None:
|
|
||||||
logger.warning("No scans available. Are redis and the BEC server running?")
|
|
||||||
return
|
|
||||||
for scan_name, scan_info in available_scans.resource.items():
|
|
||||||
self._available_scans[scan_name] = ScanObject(scan_name, scan_info, client=self.parent)
|
|
||||||
setattr(self, scan_name, self._available_scans[scan_name].run)
|
|
||||||
setattr(getattr(self, scan_name), "__doc__", scan_info.get("doc"))
|
|
||||||
setattr(
|
|
||||||
getattr(self, scan_name),
|
|
||||||
"__signature__",
|
|
||||||
dict_to_signature(scan_info.get("signature")),
|
|
||||||
)
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def get_arg_type(in_type: str):
|
|
||||||
"""translate type string into python type"""
|
|
||||||
# pylint: disable=too-many-return-statements
|
|
||||||
if in_type == "float":
|
|
||||||
return (float, int)
|
|
||||||
if in_type == "int":
|
|
||||||
return int
|
|
||||||
if in_type == "list":
|
|
||||||
return list
|
|
||||||
if in_type == "boolean":
|
|
||||||
return bool
|
|
||||||
if in_type == "str":
|
|
||||||
return str
|
|
||||||
if in_type == "dict":
|
|
||||||
return dict
|
|
||||||
if in_type == "device":
|
|
||||||
return DeviceBase
|
|
||||||
raise TypeError(f"Unknown type {in_type}")
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def prepare_scan_request(
|
|
||||||
scan_name: str, scan_info: dict, *args, **kwargs
|
|
||||||
) -> messages.ScanQueueMessage:
|
|
||||||
"""Prepare scan request message with given scan arguments
|
|
||||||
|
|
||||||
Args:
|
|
||||||
scan_name (str): scan name (matching a scan name on the scan server)
|
|
||||||
scan_info (dict): dictionary describing the scan (e.g. doc string, required kwargs etc.)
|
|
||||||
|
|
||||||
Raises:
|
|
||||||
TypeError: Raised if not all required keyword arguments have been specified.
|
|
||||||
TypeError: Raised if the number of args do fit into the required bundling pattern.
|
|
||||||
TypeError: Raised if an argument is not of the required type as specified in scan_info.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
messages.ScanQueueMessage: scan request message
|
|
||||||
"""
|
|
||||||
arg_input = list(scan_info.get("arg_input", {}).values())
|
|
||||||
|
|
||||||
arg_bundle_size = scan_info.get("arg_bundle_size", {})
|
|
||||||
bundle_size = arg_bundle_size.get("bundle")
|
|
||||||
if len(arg_input) > 0:
|
|
||||||
if len(args) % len(arg_input) != 0:
|
|
||||||
raise TypeError(
|
|
||||||
f"{scan_info.get('doc')}\n {scan_name} takes multiples of"
|
|
||||||
f" {len(arg_input)} arguments ({len(args)} given)."
|
|
||||||
)
|
|
||||||
if not all(req_kwarg in kwargs for req_kwarg in scan_info.get("required_kwargs")):
|
|
||||||
raise TypeError(
|
|
||||||
f"{scan_info.get('doc')}\n Not all required keyword arguments have been"
|
|
||||||
f" specified. The required arguments are: {scan_info.get('required_kwargs')}"
|
|
||||||
)
|
|
||||||
# check that all specified devices in args are different objects
|
|
||||||
for arg in args:
|
|
||||||
if not isinstance(arg, DeviceBase):
|
|
||||||
continue
|
|
||||||
if args.count(arg) > 1:
|
|
||||||
raise TypeError(
|
|
||||||
f"{scan_info.get('doc')}\n All specified devices must be different"
|
|
||||||
f" objects."
|
|
||||||
)
|
|
||||||
|
|
||||||
# check that all arguments are of the correct type
|
|
||||||
for ii, arg in enumerate(args):
|
|
||||||
if not isinstance(arg, Scans.get_arg_type(arg_input[ii % len(arg_input)])):
|
|
||||||
raise TypeError(
|
|
||||||
f"{scan_info.get('doc')}\n Argument {ii} must be of type"
|
|
||||||
f" {arg_input[ii%len(arg_input)]}, not {type(arg).__name__}."
|
|
||||||
)
|
|
||||||
|
|
||||||
metadata = {}
|
|
||||||
metadata.update(kwargs["system_config"])
|
|
||||||
metadata["user_metadata"] = kwargs.pop("user_metadata", {})
|
|
||||||
|
|
||||||
params = {"args": Scans._parameter_bundler(args, bundle_size), "kwargs": kwargs}
|
|
||||||
# check the number of arg bundles against the number of required bundles
|
|
||||||
if bundle_size:
|
|
||||||
num_bundles = len(params["args"])
|
|
||||||
min_bundles = arg_bundle_size.get("min")
|
|
||||||
max_bundles = arg_bundle_size.get("max")
|
|
||||||
if min_bundles and num_bundles < min_bundles:
|
|
||||||
raise TypeError(
|
|
||||||
f"{scan_info.get('doc')}\n {scan_name} requires at least {min_bundles} bundles"
|
|
||||||
f" of arguments ({num_bundles} given)."
|
|
||||||
)
|
|
||||||
if max_bundles and num_bundles > max_bundles:
|
|
||||||
raise TypeError(
|
|
||||||
f"{scan_info.get('doc')}\n {scan_name} requires at most {max_bundles} bundles"
|
|
||||||
f" of arguments ({num_bundles} given)."
|
|
||||||
)
|
|
||||||
return messages.ScanQueueMessage(
|
|
||||||
scan_type=scan_name, parameter=params, queue="primary", metadata=metadata
|
|
||||||
)
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def _parameter_bundler(args, bundle_size):
|
|
||||||
"""
|
|
||||||
|
|
||||||
Args:
|
|
||||||
args:
|
|
||||||
bundle_size: number of parameters per bundle
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
|
|
||||||
"""
|
|
||||||
if not bundle_size:
|
|
||||||
return tuple(cmd.name if hasattr(cmd, "name") else cmd for cmd in args)
|
|
||||||
params = {}
|
|
||||||
for cmds in partition(bundle_size, args):
|
|
||||||
cmds_serialized = [cmd.name if hasattr(cmd, "name") else cmd for cmd in cmds]
|
|
||||||
params[cmds_serialized[0]] = cmds_serialized[1:]
|
|
||||||
return params
|
|
||||||
|
|
||||||
@property
|
|
||||||
def scan_group(self):
|
|
||||||
"""Context manager / decorator for defining scan groups"""
|
|
||||||
return self._scan_group_ctx
|
|
||||||
|
|
||||||
@property
|
|
||||||
def scan_def(self):
|
|
||||||
"""Context manager / decorator for defining new scans"""
|
|
||||||
return self._scan_def_ctx
|
|
||||||
|
|
||||||
@property
|
|
||||||
def hide_report(self):
|
|
||||||
"""Context manager / decorator for hiding the report"""
|
|
||||||
return self._hide_report_ctx
|
|
||||||
|
|
||||||
@property
|
|
||||||
def dataset_id_on_hold(self):
|
|
||||||
"""Context manager / decorator for setting the dataset id on hold"""
|
|
||||||
return self._dataset_id_on_hold_ctx
|
|
||||||
|
|
||||||
def scan_export(self, output_file: str):
|
|
||||||
"""Context manager / decorator for exporting scans"""
|
|
||||||
return ScanExport(output_file)
|
|
||||||
|
|
||||||
|
|
||||||
class ScanGroup(ContextDecorator):
|
|
||||||
"""ScanGroup is a ContextDecorator for defining a scan group"""
|
|
||||||
|
|
||||||
def __init__(self, parent: Scans = None) -> None:
|
|
||||||
super().__init__()
|
|
||||||
self.parent = parent
|
|
||||||
|
|
||||||
def __enter__(self):
|
|
||||||
group_id = str(uuid.uuid4())
|
|
||||||
self.parent._scan_group = group_id
|
|
||||||
return self
|
|
||||||
|
|
||||||
def __exit__(self, *exc):
|
|
||||||
self.parent.close_scan_group()
|
|
||||||
self.parent._scan_group = None
|
|
||||||
|
|
||||||
|
|
||||||
class ScanDef(ContextDecorator):
|
|
||||||
"""ScanDef is a ContextDecorator for defining a new scan"""
|
|
||||||
|
|
||||||
def __init__(self, parent: Scans = None) -> None:
|
|
||||||
super().__init__()
|
|
||||||
self.parent = parent
|
|
||||||
|
|
||||||
def __enter__(self):
|
|
||||||
if self.parent._scan_def_id is not None:
|
|
||||||
raise ScanAbortion("Nested scan definitions currently not supported.")
|
|
||||||
scan_def_id = str(uuid.uuid4())
|
|
||||||
self.parent._scan_def_id = scan_def_id
|
|
||||||
self.parent.open_scan_def()
|
|
||||||
return self
|
|
||||||
|
|
||||||
def __exit__(self, *exc):
|
|
||||||
if exc[0] is None:
|
|
||||||
self.parent.close_scan_def()
|
|
||||||
self.parent._scan_def_id = None
|
|
||||||
|
|
||||||
|
|
||||||
class HideReport(ContextDecorator):
|
|
||||||
"""HideReport is a ContextDecorator for hiding the report"""
|
|
||||||
|
|
||||||
def __init__(self, parent: Scans = None) -> None:
|
|
||||||
super().__init__()
|
|
||||||
self.parent = parent
|
|
||||||
|
|
||||||
def __enter__(self):
|
|
||||||
if self.parent._hide_report is None:
|
|
||||||
self.parent._hide_report = True
|
|
||||||
return self
|
|
||||||
|
|
||||||
def __exit__(self, *exc):
|
|
||||||
self.parent._hide_report = None
|
|
||||||
|
|
||||||
|
|
||||||
class DatasetIdOnHold(ContextDecorator):
|
|
||||||
"""DatasetIdOnHold is a ContextDecorator for setting the dataset id on hold"""
|
|
||||||
|
|
||||||
def __init__(self, parent: Scans = None) -> None:
|
|
||||||
super().__init__()
|
|
||||||
self.parent = parent
|
|
||||||
self._call_count = 0
|
|
||||||
|
|
||||||
def __enter__(self):
|
|
||||||
self._call_count += 1
|
|
||||||
if self.parent._dataset_id_on_hold is None:
|
|
||||||
self.parent._dataset_id_on_hold = True
|
|
||||||
return self
|
|
||||||
|
|
||||||
def __exit__(self, *exc):
|
|
||||||
self._call_count -= 1
|
|
||||||
if self._call_count:
|
|
||||||
return
|
|
||||||
self.parent._dataset_id_on_hold = None
|
|
||||||
queue = self.parent.parent.queue
|
|
||||||
queue.next_dataset_number += 1
|
|
||||||
|
|
||||||
|
|
||||||
class FileWriter:
|
|
||||||
@typechecked
|
|
||||||
def __init__(self, file_suffix: str = None, file_directory: str = None) -> None:
|
|
||||||
"""Context manager for updating metadata
|
|
||||||
|
|
||||||
Args:
|
|
||||||
fw_config (dict): Dictionary with metadata for the filewriter, can only have keys "file_suffix" and "file_directory"
|
|
||||||
"""
|
|
||||||
self.client = self._get_client()
|
|
||||||
self.system_config = self.client.system_config
|
|
||||||
self._orig_system_config = None
|
|
||||||
self._orig_metadata = None
|
|
||||||
self.file_suffix = file_suffix
|
|
||||||
self.file_directory = file_directory
|
|
||||||
|
|
||||||
def _get_client(self):
|
|
||||||
"""Get BEC client"""
|
|
||||||
return builtins.__dict__["bec"]
|
|
||||||
|
|
||||||
def __enter__(self):
|
|
||||||
"""Enter the context manager"""
|
|
||||||
self._orig_metadata = deepcopy(self.client.metadata)
|
|
||||||
self._orig_system_config = self.system_config.model_copy(deep=True)
|
|
||||||
self.system_config.file_suffix = self.file_suffix
|
|
||||||
self.system_config.file_directory = self.file_directory
|
|
||||||
return self
|
|
||||||
|
|
||||||
def __exit__(self, *exc):
|
|
||||||
"""Exit the context manager"""
|
|
||||||
self.client.metadata = self._orig_metadata
|
|
||||||
self.system_config.file_suffix = self._orig_system_config.file_suffix
|
|
||||||
self.system_config.file_directory = self._orig_system_config.file_directory
|
|
||||||
|
|
||||||
|
|
||||||
class Metadata:
|
|
||||||
@typechecked
|
|
||||||
def __init__(self, metadata: dict) -> None:
|
|
||||||
"""Context manager for updating metadata
|
|
||||||
|
|
||||||
Args:
|
|
||||||
metadata (dict): Metadata dictionary
|
|
||||||
"""
|
|
||||||
self.client = self._get_client()
|
|
||||||
self._metadata = metadata
|
|
||||||
self._orig_metadata = None
|
|
||||||
|
|
||||||
def _get_client(self):
|
|
||||||
"""Get BEC client"""
|
|
||||||
return builtins.__dict__["bec"]
|
|
||||||
|
|
||||||
def __enter__(self):
|
|
||||||
"""Enter the context manager"""
|
|
||||||
self._orig_metadata = deepcopy(self.client.metadata)
|
|
||||||
self.client.metadata.update(self._metadata)
|
|
||||||
return self
|
|
||||||
|
|
||||||
def __exit__(self, *exc):
|
|
||||||
"""Exit the context manager"""
|
|
||||||
self.client.metadata = self._orig_metadata
|
|
||||||
|
|
||||||
|
|
||||||
class ScanExport:
|
|
||||||
def __init__(self, output_file: str) -> None:
|
|
||||||
"""Context manager for exporting scans
|
|
||||||
|
|
||||||
Args:
|
|
||||||
output_file (str): Output file name
|
|
||||||
"""
|
|
||||||
self.output_file = output_file
|
|
||||||
self.client = None
|
|
||||||
self.scans = None
|
|
||||||
|
|
||||||
def _check_abort_on_ctrl_c(self):
|
|
||||||
"""Check if scan should be aborted on Ctrl-C"""
|
|
||||||
# pylint: disable=protected-access
|
|
||||||
if not self.client._service_config.abort_on_ctrl_c:
|
|
||||||
raise RuntimeError(
|
|
||||||
"ScanExport context manager can only be used if abort_on_ctrl_c is set to True"
|
|
||||||
)
|
|
||||||
|
|
||||||
def _get_client(self):
|
|
||||||
return builtins.__dict__["bec"]
|
|
||||||
|
|
||||||
def __enter__(self):
|
|
||||||
self.scans = []
|
|
||||||
self.client = self._get_client()
|
|
||||||
self.client.scans._scan_export = self
|
|
||||||
self._check_abort_on_ctrl_c()
|
|
||||||
return self
|
|
||||||
|
|
||||||
def _export_to_csv(self):
|
|
||||||
scan_to_csv(self.scans, self.output_file)
|
|
||||||
|
|
||||||
def __exit__(self, *exc):
|
|
||||||
try:
|
|
||||||
for scan in self.scans:
|
|
||||||
scan.wait()
|
|
||||||
finally:
|
|
||||||
try:
|
|
||||||
self._export_to_csv()
|
|
||||||
self.scans = None
|
|
||||||
except Exception as exc:
|
|
||||||
logger.warning(f"Could not export scans to csv file, due to exception {exc}")
|
|
@ -1,510 +0,0 @@
|
|||||||
import enum
|
|
||||||
import time
|
|
||||||
from typing import Any
|
|
||||||
|
|
||||||
from bec_lib import bec_logger
|
|
||||||
from ophyd import (
|
|
||||||
Component,
|
|
||||||
Device,
|
|
||||||
DeviceStatus,
|
|
||||||
EpicsSignal,
|
|
||||||
EpicsSignalRO,
|
|
||||||
Kind,
|
|
||||||
PVPositioner,
|
|
||||||
Signal,
|
|
||||||
)
|
|
||||||
from ophyd.device import Staged
|
|
||||||
from ophyd.pseudopos import (
|
|
||||||
PseudoPositioner,
|
|
||||||
PseudoSingle,
|
|
||||||
pseudo_position_argument,
|
|
||||||
real_position_argument,
|
|
||||||
)
|
|
||||||
|
|
||||||
from ophyd_devices.utils import bec_utils
|
|
||||||
from ophyd_devices.utils.bec_scaninfo_mixin import BecScaninfoMixin
|
|
||||||
|
|
||||||
logger = bec_logger.logger
|
|
||||||
|
|
||||||
|
|
||||||
class DelayGeneratorError(Exception):
|
|
||||||
"""Exception raised for errors."""
|
|
||||||
|
|
||||||
|
|
||||||
class DeviceInitError(DelayGeneratorError):
|
|
||||||
"""Error upon failed initialization, invoked by missing device manager or device not started in sim_mode."""
|
|
||||||
|
|
||||||
|
|
||||||
class DelayGeneratorNotOkay(DelayGeneratorError):
|
|
||||||
"""Error when DDG is not okay"""
|
|
||||||
|
|
||||||
|
|
||||||
class TriggerSource(enum.IntEnum):
|
|
||||||
"""
|
|
||||||
Class for trigger options of DG645
|
|
||||||
|
|
||||||
Used to set the trigger source of the DG645 by setting the value
|
|
||||||
e.g. source.put(TriggerSource.Internal)
|
|
||||||
Exp:
|
|
||||||
TriggerSource.Internal
|
|
||||||
"""
|
|
||||||
|
|
||||||
INTERNAL = 0
|
|
||||||
EXT_RISING_EDGE = 1
|
|
||||||
EXT_FALLING_EDGE = 2
|
|
||||||
SS_EXT_RISING_EDGE = 3
|
|
||||||
SS_EXT_FALLING_EDGE = 4
|
|
||||||
SINGLE_SHOT = 5
|
|
||||||
LINE = 6
|
|
||||||
|
|
||||||
|
|
||||||
class DelayStatic(Device):
|
|
||||||
"""
|
|
||||||
Static axis for the T0 output channel
|
|
||||||
|
|
||||||
It allows setting the logic levels, but the timing is fixed.
|
|
||||||
The signal is high after receiving the trigger until the end
|
|
||||||
of the holdoff period.
|
|
||||||
"""
|
|
||||||
|
|
||||||
# Other channel stuff
|
|
||||||
ttl_mode = Component(EpicsSignal, "OutputModeTtlSS.PROC", kind=Kind.config)
|
|
||||||
nim_mode = Component(EpicsSignal, "OutputModeNimSS.PROC", kind=Kind.config)
|
|
||||||
polarity = Component(
|
|
||||||
EpicsSignal,
|
|
||||||
"OutputPolarityBI",
|
|
||||||
write_pv="OutputPolarityBO",
|
|
||||||
name="polarity",
|
|
||||||
kind=Kind.config,
|
|
||||||
)
|
|
||||||
amplitude = Component(
|
|
||||||
EpicsSignal, "OutputAmpAI", write_pv="OutputAmpAO", name="amplitude", kind=Kind.config
|
|
||||||
)
|
|
||||||
offset = Component(
|
|
||||||
EpicsSignal, "OutputOffsetAI", write_pv="OutputOffsetAO", name="offset", kind=Kind.config
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class DummyPositioner(PVPositioner):
|
|
||||||
"""Dummy Positioner to set AO, AI and ReferenceMO."""
|
|
||||||
|
|
||||||
setpoint = Component(EpicsSignal, "DelayAO", put_complete=True, kind=Kind.config)
|
|
||||||
readback = Component(EpicsSignalRO, "DelayAI", kind=Kind.config)
|
|
||||||
done = Component(Signal, value=1)
|
|
||||||
reference = Component(EpicsSignal, "ReferenceMO", put_complete=True, kind=Kind.config)
|
|
||||||
|
|
||||||
|
|
||||||
class DelayPair(PseudoPositioner):
|
|
||||||
"""
|
|
||||||
Delay pair interface
|
|
||||||
|
|
||||||
Virtual motor interface to a pair of signals (on the frontpanel - AB/CD/EF/GH).
|
|
||||||
It offers a simple delay and pulse width interface.
|
|
||||||
"""
|
|
||||||
|
|
||||||
# The pseudo positioner axes
|
|
||||||
delay = Component(PseudoSingle, limits=(0, 2000.0), name="delay")
|
|
||||||
width = Component(PseudoSingle, limits=(0, 2000.0), name="pulsewidth")
|
|
||||||
ch1 = Component(DummyPositioner, name="ch1")
|
|
||||||
ch2 = Component(DummyPositioner, name="ch2")
|
|
||||||
io = Component(DelayStatic, name="io")
|
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
|
||||||
# Change suffix names before connecting (a bit of dynamic connections)
|
|
||||||
self.__class__.__dict__["ch1"].suffix = kwargs["channel"][0]
|
|
||||||
self.__class__.__dict__["ch2"].suffix = kwargs["channel"][1]
|
|
||||||
self.__class__.__dict__["io"].suffix = kwargs["channel"]
|
|
||||||
|
|
||||||
del kwargs["channel"]
|
|
||||||
# Call parent to start the connections
|
|
||||||
super().__init__(*args, **kwargs)
|
|
||||||
|
|
||||||
@pseudo_position_argument
|
|
||||||
def forward(self, pseudo_pos):
|
|
||||||
"""Run a forward (pseudo -> real) calculation"""
|
|
||||||
return self.RealPosition(ch1=pseudo_pos.delay, ch2=pseudo_pos.delay + pseudo_pos.width)
|
|
||||||
|
|
||||||
@real_position_argument
|
|
||||||
def inverse(self, real_pos):
|
|
||||||
"""Run an inverse (real -> pseudo) calculation"""
|
|
||||||
return self.PseudoPosition(delay=real_pos.ch1, width=real_pos.ch2 - real_pos.ch1)
|
|
||||||
|
|
||||||
|
|
||||||
class DDGCustomMixin:
|
|
||||||
"""
|
|
||||||
Mixin class for custom DelayGenerator logic within PSIDelayGeneratorBase.
|
|
||||||
|
|
||||||
This class provides a parent class for implementation of BL specific logic of the device.
|
|
||||||
It is also possible to pass implementing certain methods, e.g. finished or on_trigger,
|
|
||||||
based on the setup and desired operation mode at the beamline.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
parent (object): instance of PSIDelayGeneratorBase
|
|
||||||
**kwargs: keyword arguments
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, *_args, parent: Device = None, **_kwargs) -> None:
|
|
||||||
self.parent = parent
|
|
||||||
|
|
||||||
def initialize_default_parameter(self) -> None:
|
|
||||||
"""
|
|
||||||
Method to initialize default parameters for DDG.
|
|
||||||
|
|
||||||
Called upon initiating the base class.
|
|
||||||
It should be used to set the DDG default parameters.
|
|
||||||
These may include: amplitude, offsets, delays, etc.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def prepare_ddg(self) -> None:
|
|
||||||
"""
|
|
||||||
Method to prepare the DDG for the upcoming scan.
|
|
||||||
|
|
||||||
Called by the stage method of the base class.
|
|
||||||
It should be used to set the DDG parameters for the upcoming scan.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def on_trigger(self) -> None:
|
|
||||||
"""Method executed upon trigger call in parent class"""
|
|
||||||
|
|
||||||
def finished(self) -> None:
|
|
||||||
"""Method to check if DDG is finished with the scan"""
|
|
||||||
|
|
||||||
def on_pre_scan(self) -> None:
|
|
||||||
"""
|
|
||||||
Method executed upon pre_scan call in parent class.
|
|
||||||
|
|
||||||
Covenient to implement time sensitive actions to be executed right before start of the scan.
|
|
||||||
Example could be to open the shutter by triggering a pulse via pre_scan.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def check_scan_id(self) -> None:
|
|
||||||
"""Method to check if there is a new scan_id, called by stage."""
|
|
||||||
|
|
||||||
def is_ddg_okay(self, raise_on_error=False) -> None:
|
|
||||||
"""
|
|
||||||
Method to check if DDG is okay
|
|
||||||
|
|
||||||
It checks the status PV of the DDG and tries to clear the error if it is not okay.
|
|
||||||
It will rerun itself and raise DelayGeneratorNotOkay if DDG is still not okay.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
raise_on_error (bool, optional): raise exception if DDG is not okay. Defaults to False.
|
|
||||||
"""
|
|
||||||
status = self.parent.status.read()[self.parent.status.name]["value"]
|
|
||||||
if status != "STATUS OK" and not raise_on_error:
|
|
||||||
logger.warning(f"DDG returns {status}, trying to clear ERROR")
|
|
||||||
self.parent.clear_error()
|
|
||||||
time.sleep(1)
|
|
||||||
self.is_ddg_okay(raise_on_error=True)
|
|
||||||
elif status != "STATUS OK":
|
|
||||||
raise DelayGeneratorNotOkay(f"DDG failed to start with status: {status}")
|
|
||||||
|
|
||||||
|
|
||||||
class PSIDelayGeneratorBase(Device):
|
|
||||||
"""
|
|
||||||
Abstract base class for DelayGenerator DG645
|
|
||||||
|
|
||||||
This class implements a thin Ophyd wrapper around the Stanford Research DG645
|
|
||||||
digital delay generator.
|
|
||||||
|
|
||||||
The DG645 generates 8+1 signals: A, B, C, D, E, F, G, H and T0. Front panel outputs
|
|
||||||
T0, AB, CD, EF and GH are combinations of these signals. Back panel outputs are
|
|
||||||
directly routed signals. Signals are not independent.
|
|
||||||
|
|
||||||
Signal pairs, e.g. AB, CD, EF, GH, are implemented as DelayPair objects. They
|
|
||||||
have a TTL pulse width, delay and a reference signal to which they are being triggered.
|
|
||||||
In addition, the io layer allows setting amplitude, offset and polarity for each pair.
|
|
||||||
|
|
||||||
Detailed information can be found in the manual:
|
|
||||||
https://www.thinksrs.com/downloads/pdfs/manuals/DG645m.pdf
|
|
||||||
|
|
||||||
Class attributes:
|
|
||||||
custom_prepare_cls (object): class for custom prepare logic (BL specific)
|
|
||||||
|
|
||||||
Args:
|
|
||||||
prefix (str) : EPICS PV prefix for component (optional)
|
|
||||||
name (str) : name of the device, as will be reported via read()
|
|
||||||
kind (str) : member of class 'ophydobj.Kind', defaults to Kind.normal
|
|
||||||
omitted -> readout ignored for read 'ophydobj.read()'
|
|
||||||
normal -> readout for read
|
|
||||||
config -> config parameter for 'ophydobj.read_configuration()'
|
|
||||||
hinted -> which attribute is readout for read
|
|
||||||
read_attrs (list) : sequence of attribute names to read
|
|
||||||
configuration_attrs (list) : sequence of attribute names via config_parameters
|
|
||||||
parent (object) : instance of the parent device
|
|
||||||
device_manager (object) : bec device manager
|
|
||||||
sim_mode (bool) : simulation mode, if True, no device manager is required
|
|
||||||
**kwargs : keyword arguments
|
|
||||||
attributes : lazy_wait_for_connection : bool
|
|
||||||
"""
|
|
||||||
|
|
||||||
# Custom_prepare_cls
|
|
||||||
custom_prepare_cls = DDGCustomMixin
|
|
||||||
|
|
||||||
SUB_PROGRESS = "progress"
|
|
||||||
SUB_VALUE = "value"
|
|
||||||
_default_sub = SUB_VALUE
|
|
||||||
|
|
||||||
USER_ACCESS = ["set_channels", "_set_trigger", "burst_enable", "burst_disable", "reload_config"]
|
|
||||||
|
|
||||||
# Assign PVs from DDG645
|
|
||||||
trigger_burst_readout = Component(
|
|
||||||
EpicsSignal, "EventStatusLI.PROC", name="trigger_burst_readout"
|
|
||||||
)
|
|
||||||
burst_cycle_finished = Component(EpicsSignalRO, "EventStatusMBBID.B3", name="read_burst_state")
|
|
||||||
delay_finished = Component(EpicsSignalRO, "EventStatusMBBID.B2", name="delay_finished")
|
|
||||||
status = Component(EpicsSignalRO, "StatusSI", name="status")
|
|
||||||
clear_error = Component(EpicsSignal, "StatusClearBO", name="clear_error")
|
|
||||||
|
|
||||||
# Front Panel
|
|
||||||
channelT0 = Component(DelayStatic, "T0", name="T0")
|
|
||||||
channelAB = Component(DelayPair, "", name="AB", channel="AB")
|
|
||||||
channelCD = Component(DelayPair, "", name="CD", channel="CD")
|
|
||||||
channelEF = Component(DelayPair, "", name="EF", channel="EF")
|
|
||||||
channelGH = Component(DelayPair, "", name="GH", channel="GH")
|
|
||||||
|
|
||||||
holdoff = Component(
|
|
||||||
EpicsSignal,
|
|
||||||
"TriggerHoldoffAI",
|
|
||||||
write_pv="TriggerHoldoffAO",
|
|
||||||
name="trigger_holdoff",
|
|
||||||
kind=Kind.config,
|
|
||||||
)
|
|
||||||
inhibit = Component(
|
|
||||||
EpicsSignal,
|
|
||||||
"TriggerInhibitMI",
|
|
||||||
write_pv="TriggerInhibitMO",
|
|
||||||
name="trigger_inhibit",
|
|
||||||
kind=Kind.config,
|
|
||||||
)
|
|
||||||
source = Component(
|
|
||||||
EpicsSignal,
|
|
||||||
"TriggerSourceMI",
|
|
||||||
write_pv="TriggerSourceMO",
|
|
||||||
name="trigger_source",
|
|
||||||
kind=Kind.config,
|
|
||||||
)
|
|
||||||
level = Component(
|
|
||||||
EpicsSignal,
|
|
||||||
"TriggerLevelAI",
|
|
||||||
write_pv="TriggerLevelAO",
|
|
||||||
name="trigger_level",
|
|
||||||
kind=Kind.config,
|
|
||||||
)
|
|
||||||
rate = Component(
|
|
||||||
EpicsSignal,
|
|
||||||
"TriggerRateAI",
|
|
||||||
write_pv="TriggerRateAO",
|
|
||||||
name="trigger_rate",
|
|
||||||
kind=Kind.config,
|
|
||||||
)
|
|
||||||
trigger_shot = Component(EpicsSignal, "TriggerDelayBO", name="trigger_shot", kind="config")
|
|
||||||
burstMode = Component(
|
|
||||||
EpicsSignal, "BurstModeBI", write_pv="BurstModeBO", name="burstmode", kind=Kind.config
|
|
||||||
)
|
|
||||||
burstConfig = Component(
|
|
||||||
EpicsSignal, "BurstConfigBI", write_pv="BurstConfigBO", name="burstconfig", kind=Kind.config
|
|
||||||
)
|
|
||||||
burstCount = Component(
|
|
||||||
EpicsSignal, "BurstCountLI", write_pv="BurstCountLO", name="burstcount", kind=Kind.config
|
|
||||||
)
|
|
||||||
burstDelay = Component(
|
|
||||||
EpicsSignal, "BurstDelayAI", write_pv="BurstDelayAO", name="burstdelay", kind=Kind.config
|
|
||||||
)
|
|
||||||
burstPeriod = Component(
|
|
||||||
EpicsSignal, "BurstPeriodAI", write_pv="BurstPeriodAO", name="burstperiod", kind=Kind.config
|
|
||||||
)
|
|
||||||
|
|
||||||
def __init__(
|
|
||||||
self,
|
|
||||||
prefix="",
|
|
||||||
*,
|
|
||||||
name,
|
|
||||||
kind=None,
|
|
||||||
read_attrs=None,
|
|
||||||
configuration_attrs=None,
|
|
||||||
parent=None,
|
|
||||||
device_manager=None,
|
|
||||||
sim_mode=False,
|
|
||||||
**kwargs,
|
|
||||||
):
|
|
||||||
super().__init__(
|
|
||||||
prefix=prefix,
|
|
||||||
name=name,
|
|
||||||
kind=kind,
|
|
||||||
read_attrs=read_attrs,
|
|
||||||
configuration_attrs=configuration_attrs,
|
|
||||||
parent=parent,
|
|
||||||
**kwargs,
|
|
||||||
)
|
|
||||||
if device_manager is None and not sim_mode:
|
|
||||||
raise DeviceInitError(
|
|
||||||
f"No device manager for device: {name}, and not started sim_mode: {sim_mode}. Add"
|
|
||||||
" DeviceManager to initialization or init with sim_mode=True"
|
|
||||||
)
|
|
||||||
# Init variables
|
|
||||||
self.sim_mode = sim_mode
|
|
||||||
self.stopped = False
|
|
||||||
self.name = name
|
|
||||||
self.scaninfo = None
|
|
||||||
self.timeout = 5
|
|
||||||
self.all_channels = ["channelT0", "channelAB", "channelCD", "channelEF", "channelGH"]
|
|
||||||
self.all_delay_pairs = ["AB", "CD", "EF", "GH"]
|
|
||||||
self.wait_for_connection(all_signals=True)
|
|
||||||
|
|
||||||
# Init custom prepare class with BL specific logic
|
|
||||||
self.custom_prepare = self.custom_prepare_cls(parent=self, **kwargs)
|
|
||||||
if not sim_mode:
|
|
||||||
self.device_manager = device_manager
|
|
||||||
else:
|
|
||||||
self.device_manager = bec_utils.DMMock()
|
|
||||||
self.connector = self.device_manager.connector
|
|
||||||
self._update_scaninfo()
|
|
||||||
self._init()
|
|
||||||
|
|
||||||
def _update_scaninfo(self) -> None:
|
|
||||||
"""
|
|
||||||
Method to updated scaninfo from BEC.
|
|
||||||
|
|
||||||
In sim_mode, scaninfo output is mocked - see bec_scaninfo_mixin.py
|
|
||||||
"""
|
|
||||||
self.scaninfo = BecScaninfoMixin(self.device_manager, self.sim_mode)
|
|
||||||
self.scaninfo.load_scan_metadata()
|
|
||||||
|
|
||||||
def _init(self) -> None:
|
|
||||||
"""Method to initialize custom parameters of the DDG."""
|
|
||||||
self.custom_prepare.initialize_default_parameter()
|
|
||||||
self.custom_prepare.is_ddg_okay()
|
|
||||||
|
|
||||||
def set_channels(self, signal: str, value: Any, channels: list = None) -> None:
|
|
||||||
"""
|
|
||||||
Method to set signals on DelayPair and DelayStatic channels.
|
|
||||||
|
|
||||||
Signals can be set on the DelayPair and DelayStatic channels. The method checks
|
|
||||||
if the signal is available on the channel and sets it. It works for both, DelayPair
|
|
||||||
and Delay Static although signals are hosted in different layers.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
signal (str) : signal to set (width, delay, amplitude, offset, polarity)
|
|
||||||
value (Any) : value to set
|
|
||||||
channels (list, optional) : list of channels to set. Defaults to self.all_channels (T0,AB,CD,EF,GH)
|
|
||||||
"""
|
|
||||||
if not channels:
|
|
||||||
channels = self.all_channels
|
|
||||||
for chname in channels:
|
|
||||||
channel = getattr(self, chname, None)
|
|
||||||
if not channel:
|
|
||||||
continue
|
|
||||||
if signal in channel.component_names:
|
|
||||||
getattr(channel, signal).set(value)
|
|
||||||
continue
|
|
||||||
if "io" in channel.component_names and signal in channel.io.component_names:
|
|
||||||
getattr(channel.io, signal).set(value)
|
|
||||||
|
|
||||||
def set_trigger(self, trigger_source: TriggerSource) -> None:
|
|
||||||
"""Set trigger source on DDG - possible values defined in TriggerSource enum"""
|
|
||||||
value = int(trigger_source)
|
|
||||||
self.source.put(value)
|
|
||||||
|
|
||||||
def burst_enable(self, count, delay, period, config="all"):
|
|
||||||
"""Enable the burst mode"""
|
|
||||||
# Validate inputs
|
|
||||||
count = int(count)
|
|
||||||
assert count > 0, "Number of bursts must be positive"
|
|
||||||
assert delay >= 0, "Burst delay must be larger than 0"
|
|
||||||
assert period > 0, "Burst period must be positive"
|
|
||||||
assert config in ["all", "first"], "Supported burst configs are 'all' and 'first'"
|
|
||||||
|
|
||||||
self.burstMode.put(1)
|
|
||||||
self.burstCount.put(count)
|
|
||||||
self.burstDelay.put(delay)
|
|
||||||
self.burstPeriod.put(period)
|
|
||||||
|
|
||||||
if config == "all":
|
|
||||||
self.burstConfig.put(0)
|
|
||||||
elif config == "first":
|
|
||||||
self.burstConfig.put(1)
|
|
||||||
|
|
||||||
def burst_disable(self):
|
|
||||||
"""Disable burst mode"""
|
|
||||||
self.burstMode.put(0)
|
|
||||||
|
|
||||||
def stage(self) -> list[object]:
|
|
||||||
"""
|
|
||||||
Method to stage the device.
|
|
||||||
|
|
||||||
Called in preparation for a scan.
|
|
||||||
|
|
||||||
Internal Calls:
|
|
||||||
- scaninfo.load_scan_metadata : load scan metadata
|
|
||||||
- custom_prepare.prepare_ddg : prepare DDG for measurement
|
|
||||||
- is_ddg_okay : check if DDG is okay
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
list(object): list of objects that were staged
|
|
||||||
"""
|
|
||||||
if self._staged != Staged.no:
|
|
||||||
return super().stage()
|
|
||||||
self.stopped = False
|
|
||||||
self.scaninfo.load_scan_metadata()
|
|
||||||
self.custom_prepare.prepare_ddg()
|
|
||||||
self.custom_prepare.is_ddg_okay()
|
|
||||||
# At the moment needed bc signal might not be reliable, BEC too fast.
|
|
||||||
# Consider removing this overhead in future!
|
|
||||||
time.sleep(0.05)
|
|
||||||
return super().stage()
|
|
||||||
|
|
||||||
def trigger(self) -> DeviceStatus:
|
|
||||||
"""
|
|
||||||
Method to trigger the acquisition.
|
|
||||||
|
|
||||||
Internal Call:
|
|
||||||
- custom_prepare.on_trigger : execute BL specific action
|
|
||||||
"""
|
|
||||||
self.custom_prepare.on_trigger()
|
|
||||||
return super().trigger()
|
|
||||||
|
|
||||||
def pre_scan(self) -> None:
|
|
||||||
"""
|
|
||||||
Method pre_scan gets executed directly before the scan
|
|
||||||
|
|
||||||
Internal Call:
|
|
||||||
- custom_prepare.on_pre_scan : execute BL specific action
|
|
||||||
"""
|
|
||||||
self.custom_prepare.on_pre_scan()
|
|
||||||
|
|
||||||
def unstage(self) -> list[object]:
|
|
||||||
"""
|
|
||||||
Method unstage gets called at the end of a scan.
|
|
||||||
|
|
||||||
If scan (self.stopped is True) is stopped, returns directly.
|
|
||||||
Otherwise, checks if the DDG finished acquisition
|
|
||||||
|
|
||||||
Internal Calls:
|
|
||||||
- custom_prepare.check_scan_id : check if scan_id changed or detector stopped
|
|
||||||
- custom_prepare.finished : check if device finished acquisition (succesfully)
|
|
||||||
- is_ddg_okay : check if DDG is okay
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
list(object): list of objects that were unstaged
|
|
||||||
"""
|
|
||||||
self.custom_prepare.check_scan_id()
|
|
||||||
if self.stopped is True:
|
|
||||||
return super().unstage()
|
|
||||||
self.custom_prepare.finished()
|
|
||||||
self.custom_prepare.is_ddg_okay()
|
|
||||||
self.stopped = False
|
|
||||||
return super().unstage()
|
|
||||||
|
|
||||||
def stop(self, *, success=False) -> None:
|
|
||||||
"""
|
|
||||||
Method to stop the DDG
|
|
||||||
|
|
||||||
#TODO Check if the pulse generation can be interruppted
|
|
||||||
|
|
||||||
Internal Call:
|
|
||||||
- custom_prepare.is_ddg_okay : check if DDG is okay
|
|
||||||
"""
|
|
||||||
self.custom_prepare.is_ddg_okay()
|
|
||||||
super().stop(success=success)
|
|
||||||
self.stopped = True
|
|
@ -1,570 +0,0 @@
|
|||||||
"""
|
|
||||||
Scan stubs are commands that can be used to control devices during a scan. They typically yield device messages that are
|
|
||||||
consumed by the scan worker and potentially forwarded to the device server.
|
|
||||||
"""
|
|
||||||
|
|
||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
import threading
|
|
||||||
import time
|
|
||||||
import uuid
|
|
||||||
from collections.abc import Callable
|
|
||||||
from typing import Generator, Literal
|
|
||||||
|
|
||||||
import numpy as np
|
|
||||||
|
|
||||||
from bec_lib import messages
|
|
||||||
from bec_lib.connector import ConnectorBase
|
|
||||||
from bec_lib.device import Status
|
|
||||||
from bec_lib.endpoints import MessageEndpoints
|
|
||||||
from bec_lib.logger import bec_logger
|
|
||||||
|
|
||||||
from .errors import DeviceMessageError, ScanAbortion
|
|
||||||
|
|
||||||
logger = bec_logger.logger
|
|
||||||
|
|
||||||
|
|
||||||
class ScanStubs:
|
|
||||||
"""
|
|
||||||
Scan stubs are commands that can be used to control devices during a scan. They typically yield device messages that are
|
|
||||||
consumed by the scan worker and potentially forwarded to the device server.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(
|
|
||||||
self,
|
|
||||||
connector: ConnectorBase,
|
|
||||||
device_msg_callback: Callable = None,
|
|
||||||
shutdown_event: threading.Event = None,
|
|
||||||
) -> None:
|
|
||||||
self.connector = connector
|
|
||||||
self.device_msg_metadata = (
|
|
||||||
device_msg_callback if device_msg_callback is not None else lambda: {}
|
|
||||||
)
|
|
||||||
self.shutdown_event = shutdown_event
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def _exclude_nones(input_dict: dict):
|
|
||||||
for key in list(input_dict.keys()):
|
|
||||||
if input_dict[key] is None:
|
|
||||||
input_dict.pop(key)
|
|
||||||
|
|
||||||
def _device_msg(self, **kwargs):
|
|
||||||
""""""
|
|
||||||
msg = messages.DeviceInstructionMessage(**kwargs)
|
|
||||||
msg.metadata = {**self.device_msg_metadata(), **msg.metadata}
|
|
||||||
return msg
|
|
||||||
|
|
||||||
def send_rpc_and_wait(self, device: str, func_name: str, *args, **kwargs) -> any:
|
|
||||||
"""Perform an RPC (remote procedure call) on a device and wait for its return value.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
device (str): Name of the device
|
|
||||||
func_name (str): Function name. The function name will be appended to the device.
|
|
||||||
args (tuple): Arguments to pass on to the RPC function
|
|
||||||
kwargs (dict): Keyword arguments to pass on to the RPC function
|
|
||||||
|
|
||||||
Raises:
|
|
||||||
ScanAbortion: Raised if the RPC's success is False
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
any: Return value of the executed rpc function
|
|
||||||
|
|
||||||
Examples:
|
|
||||||
>>> send_rpc_and_wait("samx", "controller.my_custom_function")
|
|
||||||
"""
|
|
||||||
rpc_id = str(uuid.uuid4())
|
|
||||||
parameter = {
|
|
||||||
"device": device,
|
|
||||||
"func": func_name,
|
|
||||||
"rpc_id": rpc_id,
|
|
||||||
"args": args,
|
|
||||||
"kwargs": kwargs,
|
|
||||||
}
|
|
||||||
yield from self.rpc(
|
|
||||||
device=device, parameter=parameter, metadata={"response": True, "RID": rpc_id}
|
|
||||||
)
|
|
||||||
return self._get_from_rpc(rpc_id)
|
|
||||||
|
|
||||||
def _get_from_rpc(self, rpc_id) -> any:
|
|
||||||
"""
|
|
||||||
Get the return value from an RPC call.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
rpc_id (str): RPC ID
|
|
||||||
|
|
||||||
Raises:
|
|
||||||
ScanAbortion: Raised if the RPC's success flag is False
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
any: Return value of the RPC call
|
|
||||||
"""
|
|
||||||
|
|
||||||
while not self.shutdown_event.is_set():
|
|
||||||
msg = self.connector.get(MessageEndpoints.device_rpc(rpc_id))
|
|
||||||
if msg:
|
|
||||||
break
|
|
||||||
time.sleep(0.001)
|
|
||||||
if self.shutdown_event.is_set():
|
|
||||||
raise ScanAbortion("The scan was aborted.")
|
|
||||||
if not msg.content["success"]:
|
|
||||||
error = msg.content["out"]
|
|
||||||
if isinstance(error, dict) and {"error", "msg", "traceback"}.issubset(
|
|
||||||
set(error.keys())
|
|
||||||
):
|
|
||||||
error_msg = f"During an RPC, the following error occured:\n{error['error']}: {error['msg']}.\nTraceback: {error['traceback']}\n The scan will be aborted."
|
|
||||||
else:
|
|
||||||
error_msg = "During an RPC, an error occured"
|
|
||||||
raise ScanAbortion(error_msg)
|
|
||||||
|
|
||||||
logger.debug(msg.content.get("out"))
|
|
||||||
return_val = msg.content.get("return_val")
|
|
||||||
if not isinstance(return_val, dict):
|
|
||||||
return return_val
|
|
||||||
if return_val.get("type") == "status" and return_val.get("RID"):
|
|
||||||
return Status(self.connector, return_val.get("RID"))
|
|
||||||
return return_val
|
|
||||||
|
|
||||||
def set_with_response(
|
|
||||||
self, *, device: str, value: float, request_id: str = None, metadata=None
|
|
||||||
) -> Generator[None, None, None]:
|
|
||||||
"""Set a device to a specific value and return the request ID. Use :func:`request_is_completed` to later check if the request is completed.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
device (str): Device name.
|
|
||||||
value (float): Target value.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
Generator[None, None, None]: Generator that yields a device message.
|
|
||||||
|
|
||||||
see also: :func:`request_is_completed`
|
|
||||||
|
|
||||||
"""
|
|
||||||
request_id = str(uuid.uuid4()) if request_id is None else request_id
|
|
||||||
metadata = metadata if metadata is not None else {}
|
|
||||||
metadata.update({"response": True, "RID": request_id})
|
|
||||||
yield from self.set(device=device, value=value, wait_group="set", metadata=metadata)
|
|
||||||
return request_id
|
|
||||||
|
|
||||||
def request_is_completed(self, RID: str) -> bool:
|
|
||||||
"""Check if a request that was initiated with :func:`set_with_response` is completed.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
RID (str): Request ID.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
bool: True if the request is completed, False otherwise.
|
|
||||||
|
|
||||||
"""
|
|
||||||
msg = self.connector.lrange(MessageEndpoints.device_req_status_container(RID), 0, -1)
|
|
||||||
if not msg:
|
|
||||||
return False
|
|
||||||
return True
|
|
||||||
|
|
||||||
def set_and_wait(
|
|
||||||
self, *, device: list[str], positions: list | np.ndarray
|
|
||||||
) -> Generator[None, None, None]:
|
|
||||||
"""Set devices to a specific position and wait completion.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
device (list[str]): List of device names.
|
|
||||||
positions (list | np.ndarray): Target position.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
Generator[None, None, None]: Generator that yields a device message.
|
|
||||||
|
|
||||||
see also: :func:`set`, :func:`wait`, :func:`set_with_response`
|
|
||||||
|
|
||||||
"""
|
|
||||||
if not isinstance(positions, list) and not isinstance(positions, np.ndarray):
|
|
||||||
positions = [positions]
|
|
||||||
if len(positions) == 0:
|
|
||||||
return
|
|
||||||
for ind, val in enumerate(device):
|
|
||||||
yield from self.set(device=val, value=positions[ind], wait_group="scan_motor")
|
|
||||||
yield from self.wait(device=device, wait_type="move", wait_group="scan_motor")
|
|
||||||
|
|
||||||
def read_and_wait(
|
|
||||||
self, *, wait_group: str, device: list = None, group: str = None, point_id: int = None
|
|
||||||
) -> Generator[None, None, None]:
|
|
||||||
"""Trigger a reading and wait for completion.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
wait_group (str): wait group
|
|
||||||
device (list, optional): List of device names. Can be specified instead of group. Defaults to None.
|
|
||||||
group (str, optional): Group name of devices. Can be specified instead of device. Defaults to None.
|
|
||||||
point_id (int, optional): _description_. Defaults to None.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
Generator[None, None, None]: Generator that yields a device message.
|
|
||||||
|
|
||||||
"""
|
|
||||||
self._check_device_and_groups(device, group)
|
|
||||||
yield from self.read(device=device, group=group, wait_group=wait_group, point_id=point_id)
|
|
||||||
yield from self.wait(device=device, wait_type="read", group=group, wait_group=wait_group)
|
|
||||||
|
|
||||||
def open_scan(
|
|
||||||
self,
|
|
||||||
*,
|
|
||||||
scan_motors: list,
|
|
||||||
readout_priority: dict,
|
|
||||||
num_pos: int,
|
|
||||||
scan_name: str,
|
|
||||||
scan_type: Literal["step", "fly"],
|
|
||||||
positions=None,
|
|
||||||
metadata=None,
|
|
||||||
) -> Generator[None, None, None]:
|
|
||||||
"""Open a new scan.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
scan_motors (list): List of scan motors.
|
|
||||||
readout_priority (dict): Modification of the readout priority.
|
|
||||||
num_pos (int): Number of positions within the scope of this scan.
|
|
||||||
positions (list): List of positions for this scan.
|
|
||||||
scan_name (str): Scan name.
|
|
||||||
scan_type (str): Scan type (e.g. 'step' or 'fly')
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
Generator[None, None, None]: Generator that yields a device message.
|
|
||||||
|
|
||||||
"""
|
|
||||||
yield self._device_msg(
|
|
||||||
device=None,
|
|
||||||
action="open_scan",
|
|
||||||
parameter={
|
|
||||||
"scan_motors": scan_motors,
|
|
||||||
"readout_priority": readout_priority,
|
|
||||||
"num_points": num_pos,
|
|
||||||
"positions": positions,
|
|
||||||
"scan_name": scan_name,
|
|
||||||
"scan_type": scan_type,
|
|
||||||
},
|
|
||||||
metadata=metadata,
|
|
||||||
)
|
|
||||||
|
|
||||||
def kickoff(
|
|
||||||
self, *, device: str, parameter: dict = None, wait_group="kickoff", metadata=None
|
|
||||||
) -> Generator[None, None, None]:
|
|
||||||
"""Kickoff a fly scan device.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
device (str): Device name of flyer.
|
|
||||||
parameter (dict, optional): Additional parameters that should be forwarded to the device. Defaults to {}.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
Generator[None, None, None]: Generator that yields a device message.
|
|
||||||
"""
|
|
||||||
parameter = parameter if parameter is not None else {}
|
|
||||||
parameter = {"configure": parameter, "wait_group": wait_group}
|
|
||||||
yield self._device_msg(
|
|
||||||
device=device, action="kickoff", parameter=parameter, metadata=metadata
|
|
||||||
)
|
|
||||||
|
|
||||||
def complete(self, *, device: str, metadata=None) -> Generator[None, None, None]:
|
|
||||||
"""Complete a fly scan device.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
device (str): Device name of flyer.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
Generator[None, None, None]: Generator that yields a device message.
|
|
||||||
"""
|
|
||||||
yield self._device_msg(device=device, action="complete", parameter={}, metadata=metadata)
|
|
||||||
|
|
||||||
def get_req_status(self, device: str, RID: str, DIID: int) -> int:
|
|
||||||
"""Check if a device request status matches the given RID and DIID
|
|
||||||
|
|
||||||
Args:
|
|
||||||
device (str): device under inspection
|
|
||||||
RID (str): request ID
|
|
||||||
DIID (int): device instruction ID
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
int: 1 if the request status matches the RID and DIID, 0 otherwise
|
|
||||||
"""
|
|
||||||
msg = self.connector.get(MessageEndpoints.device_req_status(device))
|
|
||||||
if not msg:
|
|
||||||
return 0
|
|
||||||
matching_RID = msg.metadata.get("RID") == RID
|
|
||||||
matching_DIID = msg.metadata.get("DIID") == DIID
|
|
||||||
if matching_DIID and matching_RID:
|
|
||||||
return 1
|
|
||||||
return 0
|
|
||||||
|
|
||||||
def get_device_progress(self, device: str, RID: str) -> float | None:
|
|
||||||
"""Get reported device progress
|
|
||||||
|
|
||||||
Args:
|
|
||||||
device (str): Name of the device
|
|
||||||
RID (str): request ID
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
float: reported progress value
|
|
||||||
|
|
||||||
"""
|
|
||||||
msg = self.connector.get(MessageEndpoints.device_progress(device))
|
|
||||||
if not msg:
|
|
||||||
return None
|
|
||||||
matching_RID = msg.metadata.get("RID") == RID
|
|
||||||
if not matching_RID:
|
|
||||||
return None
|
|
||||||
if not isinstance(msg, messages.ProgressMessage):
|
|
||||||
raise DeviceMessageError(
|
|
||||||
f"Expected to receive a Progressmessage for device {device} but instead received {msg}."
|
|
||||||
)
|
|
||||||
return msg.content["value"]
|
|
||||||
|
|
||||||
def close_scan(self) -> Generator[None, None, None]:
|
|
||||||
"""
|
|
||||||
Close the scan.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
Generator[None, None, None]: Generator that yields a device message.
|
|
||||||
|
|
||||||
see also: :func:`open_scan`
|
|
||||||
"""
|
|
||||||
|
|
||||||
yield self._device_msg(device=None, action="close_scan", parameter={})
|
|
||||||
|
|
||||||
def stage(self) -> Generator[None, None, None]:
|
|
||||||
"""
|
|
||||||
Stage all devices
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
Generator[None, None, None]: Generator that yields a device message.
|
|
||||||
|
|
||||||
see also: :func:`unstage`
|
|
||||||
"""
|
|
||||||
yield self._device_msg(device=None, action="stage", parameter={})
|
|
||||||
|
|
||||||
def unstage(self) -> Generator[None, None, None]:
|
|
||||||
"""
|
|
||||||
Unstage all devices
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
Generator[None, None, None]: Generator that yields a device message.
|
|
||||||
|
|
||||||
see also: :func:`stage`
|
|
||||||
"""
|
|
||||||
yield self._device_msg(device=None, action="unstage", parameter={})
|
|
||||||
|
|
||||||
def pre_scan(self) -> Generator[None, None, None]:
|
|
||||||
"""
|
|
||||||
Trigger pre-scan actions on all devices. Typically, pre-scan actions are called directly before the scan core starts and
|
|
||||||
are used to perform time-critical actions.
|
|
||||||
The event will be sent to all devices that have a pre_scan method implemented.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
Generator[None, None, None]: Generator that yields a device message.
|
|
||||||
"""
|
|
||||||
yield self._device_msg(device=None, action="pre_scan", parameter={})
|
|
||||||
|
|
||||||
def baseline_reading(self) -> Generator[None, None, None]:
|
|
||||||
"""
|
|
||||||
Run the baseline readings. This will readout all devices that are marked with the readout_priority "baseline".
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
Generator[None, None, None]: Generator that yields a device message.
|
|
||||||
|
|
||||||
"""
|
|
||||||
yield self._device_msg(
|
|
||||||
device=None,
|
|
||||||
action="baseline_reading",
|
|
||||||
parameter={},
|
|
||||||
metadata={"readout_priority": "baseline"},
|
|
||||||
)
|
|
||||||
|
|
||||||
def wait(
|
|
||||||
self,
|
|
||||||
*,
|
|
||||||
wait_type: Literal["move", "read", "trigger"],
|
|
||||||
device: list[str] | str | None = None,
|
|
||||||
group: Literal["scan_motor", "primary", None] = None,
|
|
||||||
wait_group: str = None,
|
|
||||||
wait_time: float = None,
|
|
||||||
):
|
|
||||||
"""Wait for an event.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
wait_type (Literal["move", "read", "trigger"]): Type of wait event. Can be "move", "read" or "trigger".
|
|
||||||
device (list[str] | str, optional): List of device names. Defaults to None.
|
|
||||||
group (Literal["scan_motor", "primary", None]): Device group that can be used instead of device. Defaults to None.
|
|
||||||
wait_group (str, optional): Wait group for this event. Defaults to None.
|
|
||||||
wait_time (float, optional): Wait time (for wait_type="trigger"). Defaults to None.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
Generator[None, None, None]: Generator that yields a device message.
|
|
||||||
|
|
||||||
Example:
|
|
||||||
>>> yield from self.stubs.wait(wait_type="move", group="scan_motor", wait_group="scan_motor")
|
|
||||||
>>> yield from self.stubs.wait(wait_type="read", group="scan_motor", wait_group="my_readout_motors")
|
|
||||||
|
|
||||||
"""
|
|
||||||
self._check_device_and_groups(device, group)
|
|
||||||
parameter = {"type": wait_type, "time": wait_time, "group": group, "wait_group": wait_group}
|
|
||||||
self._exclude_nones(parameter)
|
|
||||||
yield self._device_msg(device=device, action="wait", parameter=parameter)
|
|
||||||
|
|
||||||
def read(
|
|
||||||
self,
|
|
||||||
*,
|
|
||||||
wait_group: str,
|
|
||||||
device: list[str] | str | None = None,
|
|
||||||
point_id: int | None = None,
|
|
||||||
group: Literal["scan_motor", "primary", None] = None,
|
|
||||||
) -> Generator[None, None, None]:
|
|
||||||
"""
|
|
||||||
Trigger a reading on a device or device group.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
wait_group (str): Wait group for this event. The specified wait group can later be used
|
|
||||||
to wait for the completion of this event. Please note that the wait group has to be
|
|
||||||
unique. within the scope of the read / wait event.
|
|
||||||
device (list, optional): Device name. Can be used instead of group. Defaults to None.
|
|
||||||
point_id (int, optional): point_id to assign this reading to point within the scan. Defaults to None.
|
|
||||||
group (Literal["scan_motor", "primary", None], optional): Device group. Can be used instead of device. Defaults to None.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
Generator[None, None, None]: Generator that yields a device message.
|
|
||||||
|
|
||||||
Example:
|
|
||||||
>>> yield from self.stubs.read(wait_group="readout_primary", group="primary", point_id=self.point_id)
|
|
||||||
>>> yield from self.stubs.read(wait_group="sample_stage", device="samx", point_id=self.point_id)
|
|
||||||
|
|
||||||
"""
|
|
||||||
self._check_device_and_groups(device, group)
|
|
||||||
parameter = {"group": group, "wait_group": wait_group}
|
|
||||||
metadata = {"point_id": point_id}
|
|
||||||
self._exclude_nones(parameter)
|
|
||||||
self._exclude_nones(metadata)
|
|
||||||
yield self._device_msg(device=device, action="read", parameter=parameter, metadata=metadata)
|
|
||||||
|
|
||||||
def publish_data_as_read(
|
|
||||||
self, *, device: str, data: dict, point_id: int
|
|
||||||
) -> Generator[None, None, None]:
|
|
||||||
"""
|
|
||||||
Publish the given data as a read event and assign it to the given point_id.
|
|
||||||
This method can be used to customize the assignment of data to a specific point within a scan.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
device (str): Device name.
|
|
||||||
data (dict): Data that should be published.
|
|
||||||
point_id (int): point_id that should be attached to this data.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
Generator[None, None, None]: Generator that yields a device message.
|
|
||||||
"""
|
|
||||||
metadata = {"point_id": point_id}
|
|
||||||
yield self._device_msg(
|
|
||||||
device=device,
|
|
||||||
action="publish_data_as_read",
|
|
||||||
parameter={"data": data},
|
|
||||||
metadata=metadata,
|
|
||||||
)
|
|
||||||
|
|
||||||
def trigger(self, *, group: str, point_id: int) -> Generator[None, None, None]:
|
|
||||||
"""Trigger a device group. Note that the trigger event is not blocking and does not wait for the completion of the trigger event.
|
|
||||||
To wait for the completion of the trigger event, use the :func:`wait` command, specifying the wait_type as "trigger".
|
|
||||||
|
|
||||||
Args:
|
|
||||||
group (str): Device group that should receive the trigger.
|
|
||||||
point_id (int): point_id that should be attached to this trigger event.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
Generator[None, None, None]: Generator that yields a device message.
|
|
||||||
|
|
||||||
see also: :func:`wait`
|
|
||||||
"""
|
|
||||||
yield self._device_msg(
|
|
||||||
device=None,
|
|
||||||
action="trigger",
|
|
||||||
parameter={"group": group},
|
|
||||||
metadata={"point_id": point_id},
|
|
||||||
)
|
|
||||||
|
|
||||||
def set(self, *, device: str, value: float, wait_group: str, metadata=None):
|
|
||||||
"""Set the device to a specific value. This is similar to the direct set command
|
|
||||||
in the command-line interface. The wait_group can be used to wait for the completion of this event.
|
|
||||||
For a set operation, this simply means that the device has acknowledged the set command and does not
|
|
||||||
necessarily mean that the device has reached the target value.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
device (str): Device name
|
|
||||||
value (float): Target value.
|
|
||||||
wait_group (str): wait group for this event.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
Generator[None, None, None]: Generator that yields a device message.
|
|
||||||
|
|
||||||
.. warning::
|
|
||||||
|
|
||||||
Do not use this command to kickoff a long running operation. Use :func:`kickoff` instead or, if the
|
|
||||||
device does not support the kickoff command, use :func:`set_with_response` instead.
|
|
||||||
|
|
||||||
see also: :func:`wait`, :func:`set_and_wait`, :func:`set_with_response`
|
|
||||||
|
|
||||||
"""
|
|
||||||
yield self._device_msg(
|
|
||||||
device=device,
|
|
||||||
action="set",
|
|
||||||
parameter={"value": value, "wait_group": wait_group},
|
|
||||||
metadata=metadata,
|
|
||||||
)
|
|
||||||
|
|
||||||
def open_scan_def(self) -> Generator[None, None, None]:
|
|
||||||
"""
|
|
||||||
Open a new scan definition
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
Generator[None, None, None]: Generator that yields a device message.
|
|
||||||
"""
|
|
||||||
yield self._device_msg(device=None, action="open_scan_def", parameter={})
|
|
||||||
|
|
||||||
def close_scan_def(self) -> Generator[None, None, None]:
|
|
||||||
"""
|
|
||||||
Close a scan definition
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
Generator[None, None, None]: Generator that yields a device message.
|
|
||||||
"""
|
|
||||||
yield self._device_msg(device=None, action="close_scan_def", parameter={})
|
|
||||||
|
|
||||||
def close_scan_group(self) -> Generator[None, None, None]:
|
|
||||||
"""
|
|
||||||
Close a scan group
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
Generator[None, None, None]: Generator that yields a device message.
|
|
||||||
"""
|
|
||||||
yield self._device_msg(device=None, action="close_scan_group", parameter={})
|
|
||||||
|
|
||||||
def rpc(self, *, device: str, parameter: dict, metadata=None) -> Generator[None, None, None]:
|
|
||||||
"""Perfrom an RPC (remote procedure call) on a device.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
device (str): Device name.
|
|
||||||
parameter (dict): parameters used for this rpc instructions.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
Generator[None, None, None]: Generator that yields a device message.
|
|
||||||
|
|
||||||
"""
|
|
||||||
yield self._device_msg(device=device, action="rpc", parameter=parameter, metadata=metadata)
|
|
||||||
|
|
||||||
def scan_report_instruction(self, instructions: dict) -> Generator[None, None, None]:
|
|
||||||
"""Scan report instructions
|
|
||||||
|
|
||||||
Args:
|
|
||||||
instructions (dict): Dict containing the scan report instructions
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
Generator[None, None, None]: Generator that yields a device message.
|
|
||||||
"""
|
|
||||||
yield self._device_msg(
|
|
||||||
device=None, action="scan_report_instruction", parameter=instructions
|
|
||||||
)
|
|
||||||
|
|
||||||
def _check_device_and_groups(self, device, group) -> None:
|
|
||||||
if device and group:
|
|
||||||
raise DeviceMessageError("Device and device group was specified. Pick one.")
|
|
||||||
if device is None and group is None:
|
|
||||||
raise DeviceMessageError("Either devices or device groups have to be specified.")
|
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -1,69 +1,28 @@
|
|||||||
from ophyd import Device, EpicsMotor, EpicsSignal, EpicsSignalRO
|
from ophyd import Device, EpicsMotor, EpicsSignal, EpicsSignalRO
|
||||||
from ophyd import Component as Cpt
|
from ophyd import Component as Cpt
|
||||||
|
|
||||||
# option I via direct acces to classes
|
#option I via direct acces to classes
|
||||||
|
|
||||||
|
ScanX = EpicsMotor(name='ScanX',prefix='X07MB-ES-MA1:ScanX')
|
||||||
|
ScanY = EpicsMotor(name='ScanY',prefix='X07MB-ES-MA1:ScanY')
|
||||||
|
DIODE = EpicsSignal(name='SI',read_pv='X07MB-OP2-SAI_07:MEAN')
|
||||||
|
SMPL = EpicsSignal(name='SMPL',read_pv='X07MB-OP2:SMPL')
|
||||||
|
CYCLES = EpicsSignal(name='SMPL',read_pv='X07MB-OP2:TOTAL-CYCLES',write_pv='X07MB-OP2:TOTAL-CYCLES')
|
||||||
|
|
||||||
|
|
||||||
def print_dic(clname, cl):
|
prefix='XXXX:'
|
||||||
print("")
|
y_cpt = Cpt(EpicsMotor, 'ScanX')
|
||||||
print("-------- ", clname)
|
|
||||||
for ii in cl.__dict__:
|
|
||||||
if "_" not in ii:
|
|
||||||
|
|
||||||
try:
|
|
||||||
print(ii, " ---- ", cl.__getattribute__(ii))
|
|
||||||
except:
|
|
||||||
print(ii)
|
|
||||||
|
|
||||||
|
|
||||||
ScanX = EpicsMotor(name="ScanX", prefix="X07MB-ES-MA1:ScanX")
|
|
||||||
ScanY = EpicsMotor(name="ScanY", prefix="X07MB-ES-MA1:ScanY")
|
|
||||||
DIODE = EpicsSignal(name="SI", read_pv="X07MB-OP2-SAI_07:MEAN")
|
|
||||||
SMPL = EpicsSignal(name="SMPL", read_pv="X07MB-OP2:SMPL")
|
|
||||||
CYCLES = EpicsSignal(
|
|
||||||
name="SMPL", read_pv="X07MB-OP2:TOTAL-CYCLES", write_pv="X07MB-OP2:TOTAL-CYCLES"
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
# prefix='XXXX:'
|
|
||||||
y_cpt = Cpt(EpicsMotor, "ScanX")
|
|
||||||
# Option 2 using component
|
# Option 2 using component
|
||||||
|
prefix='X07MB-ES-MA1:'
|
||||||
device_ins = Device("X07MB-ES-MA1:", name=("device_name"))
|
dd=Device('X07MB-ES-MA1:',name=('device'))
|
||||||
print(" initialzation of device_in=Device(X07MB-ES-MA1:,name=(device_name)")
|
y_cpt_prefix = Cpt(EpicsMotor,'ScanX',parent=Device)
|
||||||
print("device_ins.__init__")
|
|
||||||
print(device_ins.__init__)
|
|
||||||
print_dic("class Device", Device)
|
|
||||||
print_dic("instance of device device_ins", device_ins)
|
|
||||||
|
|
||||||
|
|
||||||
print(" ")
|
|
||||||
print("DEFINE class StageXY... prefix variable not defined ")
|
|
||||||
|
|
||||||
|
|
||||||
class StageXY(Device):
|
class StageXY(Device):
|
||||||
# Here the whole namespace and finctionality
|
|
||||||
# of Device(Blueskyinterface,Pphydobject) is inherited
|
|
||||||
# into class StageXY using Device
|
|
||||||
# device requires as input parameters the prefix
|
|
||||||
# in the initialization of Cpt there is some magic
|
|
||||||
# hard to understand, moist likely using calss methods..
|
|
||||||
#
|
|
||||||
|
|
||||||
x = Cpt(EpicsMotor, "ScanX")
|
x = Cpt(EpicsMotor, 'ScanX')
|
||||||
y = Cpt(EpicsMotor, "ScanY")
|
y = Cpt(EpicsMotor, 'ScanY')
|
||||||
|
|
||||||
|
|
||||||
# end class
|
|
||||||
|
|
||||||
|
|
||||||
print()
|
|
||||||
print("init xy_stage, use input parameter from Device and prefix is defined in call ")
|
|
||||||
xy_stage = StageXY("X07MB-ES-MA1:", name="stageXXX")
|
|
||||||
|
|
||||||
print_dic("class StageXY", StageXY)
|
|
||||||
print_dic("instance of StageXY", xy_stage)
|
|
||||||
|
|
||||||
|
xy_stage = StageXY('X07MB-ES-MA1:', name='stage')
|
||||||
|
|
||||||
#############################################
|
#############################################
|
||||||
# This is basic bluesky
|
# This is basic bluesky
|
||||||
@ -82,7 +41,6 @@ print_dic("instance of StageXY", xy_stage)
|
|||||||
#
|
#
|
||||||
#########################################################
|
#########################################################
|
||||||
|
|
||||||
print("xy_stage.x.prefix")
|
|
||||||
print(xy_stage.x.prefix)
|
print(xy_stage.x.prefix)
|
||||||
xy_stage.__dict__
|
xy_stage.__dict__
|
||||||
|
|
||||||
|
@ -1,120 +0,0 @@
|
|||||||
from ophyd import Device, EpicsMotor, EpicsSignal, EpicsSignalRO
|
|
||||||
from ophyd import Component as Cpt
|
|
||||||
|
|
||||||
|
|
||||||
from phoenix_bec.local_scripts.Examples.my_ophyd import Device,EpicsMotor, EpicsSignal, EpicsSignalRO
|
|
||||||
from phoenix_bec.local_scripts.Examples.my_ophyd import Component as Cpt
|
|
||||||
|
|
||||||
############################################
|
|
||||||
#
|
|
||||||
# KEEP my_ophyd zipped to avoid chaos local version does not run
|
|
||||||
# so this is rather useless
|
|
||||||
#
|
|
||||||
##########################################
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#option I via direct acces to classes
|
|
||||||
|
|
||||||
def print_dic(clname,cl):
|
|
||||||
print('')
|
|
||||||
print('-------- ',clname)
|
|
||||||
for ii in cl.__dict__:
|
|
||||||
if '_' not in ii:
|
|
||||||
|
|
||||||
try:
|
|
||||||
print(ii,' ---- ',cl.__getattribute__(ii))
|
|
||||||
except:
|
|
||||||
print(ii)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
ScanX = EpicsMotor(name='ScanX',prefix='X07MB-ES-MA1:ScanX')
|
|
||||||
ScanY = EpicsMotor(name='ScanY',prefix='X07MB-ES-MA1:ScanY')
|
|
||||||
DIODE = EpicsSignal(name='SI',read_pv='X07MB-OP2-SAI_07:MEAN')
|
|
||||||
SMPL = EpicsSignal(name='SMPL',read_pv='X07MB-OP2:SMPL')
|
|
||||||
CYCLES = EpicsSignal(name='SMPL',read_pv='X07MB-OP2:TOTAL-CYCLES',write_pv='X07MB-OP2:TOTAL-CYCLES')
|
|
||||||
|
|
||||||
|
|
||||||
#prefix='XXXX:'
|
|
||||||
y_cpt = Cpt(EpicsMotor, 'ScanX')
|
|
||||||
# Option 2 using component
|
|
||||||
|
|
||||||
device_ins=Device('X07MB-ES-MA1:',name=('device_name'))
|
|
||||||
print(' initialzation of device_in=Device(X07MB-ES-MA1:,name=(device_name)')
|
|
||||||
print('device_ins.__init__')
|
|
||||||
print(device_ins.__init__)
|
|
||||||
print_dic('class Device',Device)
|
|
||||||
print_dic('instance of device device_ins',device_ins)
|
|
||||||
|
|
||||||
|
|
||||||
print(' ')
|
|
||||||
print('DEFINE class StageXY... prefix variable not defined ')
|
|
||||||
|
|
||||||
EpicsMotor, 'ScanY'
|
|
||||||
"""
|
|
||||||
class MyCpt(typing.Generic[K]):
|
|
||||||
|
|
||||||
def __init__(
|
|
||||||
self,
|
|
||||||
cls: Type[K],
|
|
||||||
suffix: Optional[str] = None,
|
|
||||||
*,
|
|
||||||
lazy: Optional[bool] = None,
|
|
||||||
trigger_value: Optional[Any] = None,
|
|
||||||
add_prefix: Optional[Sequence[str]] = None,
|
|
||||||
doc: Optional[str] = None,
|
|
||||||
kind: Union[str, Kind] = Kind.normal,
|
|
||||||
**kwargs,
|
|
||||||
):
|
|
||||||
self.attr = None # attr is set later by the device when known
|
|
||||||
self.cls = cls
|
|
||||||
self.kwargs = kwargs
|
|
||||||
#self.lazy = lazy if lazy is not None else self.lazy_default
|
|
||||||
self.suffix = suffix
|
|
||||||
self.doc = doc
|
|
||||||
self.trigger_value = trigger_value # TODO discuss
|
|
||||||
self.kind = Kind[kind.lower()] if isinstance(kind, str) else Kind(kind)
|
|
||||||
if add_prefix is None:
|
|
||||||
add_prefix = ("suffix", "write_pv")
|
|
||||||
self.add_prefix = tuple(add_prefix)
|
|
||||||
self._subscriptions = collections.defaultdict(list)
|
|
||||||
print(' ')
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
in Device we have this class method
|
|
||||||
Class device(..):
|
|
||||||
....
|
|
||||||
@classmethod
|
|
||||||
def _initialize_device(cls):
|
|
||||||
....
|
|
||||||
|
|
||||||
|
|
||||||
class StageXY(Device):
|
|
||||||
# Here the whole namespace and finctionality
|
|
||||||
# of Device(Blueskyinterface,Pphydobject) is inherited
|
|
||||||
# into class StageXY
|
|
||||||
|
|
||||||
x = Cpt(EpicsMotor, 'ScanX')
|
|
||||||
y = Cpt(EpicsMotor, 'ScanY')
|
|
||||||
|
|
||||||
# end class
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
print()
|
|
||||||
print('init xy_stage, use input parameter from Device and prefix is defined in call ')
|
|
||||||
xy = StageXY('X07MB-ES-MA1:', name='xy_name')
|
|
||||||
|
|
||||||
print_dic('class StageXY',StageXY)
|
|
||||||
print_dic('instance of StageXY',xy)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#print('xy.x.prefix')
|
|
||||||
#print(xy.x.prefix)
|
|
||||||
xy.__dict__
|
|
@ -1,81 +1,81 @@
|
|||||||
#from unittest import mock
|
#from unittest import mock
|
||||||
import numpy as np
|
import numpy as np
|
||||||
#import pandas
|
#import pandas
|
||||||
#import pytest
|
#import pytest
|
||||||
#from bec_lib import messages
|
#from bec_lib import messages
|
||||||
#import device_server
|
#import device_server
|
||||||
#from ophyd import Component as Cpt
|
#from ophyd import Component as Cpt
|
||||||
from ophyd import Device, EpicsMotor, EpicsSignal, EpicsSignalRO
|
from ophyd import Device, EpicsMotor, EpicsSignal, EpicsSignalRO
|
||||||
#from ophyd import FormattedComponent as FCpt
|
#from ophyd import FormattedComponent as FCpt
|
||||||
#from ophyd import Kind, PVPositioner, Signal
|
#from ophyd import Kind, PVPositioner, Signal
|
||||||
#from ophyd.flyers import FlyerInterface
|
#from ophyd.flyers import FlyerInterface
|
||||||
#from ophyd.pv_positioner import PVPositionerComparator
|
#from ophyd.pv_positioner import PVPositionerComparator
|
||||||
#from ophyd.status import DeviceStatus, SubscriptionStatus
|
#from ophyd.status import DeviceStatus, SubscriptionStatus
|
||||||
|
|
||||||
import time as tt
|
import time as tt
|
||||||
|
|
||||||
#import ophyd
|
#import ophyd
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
#logger = bec_logger.logger
|
#logger = bec_logger.logger
|
||||||
# load simulation
|
# load simulation
|
||||||
|
|
||||||
#bec.config.load_demo_config()
|
#bec.config.load_demo_config()
|
||||||
|
|
||||||
bec.config.update_session_with_file("config/config_1.yaml")
|
bec.config.update_session_with_file("config/config_1.yaml")
|
||||||
|
|
||||||
os.system('mv *.yaml tmp')
|
os.system('mv *.yaml tmp')
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class PhoenixBL:
|
class PhoenixBL:
|
||||||
|
|
||||||
#define some epics channels
|
#define some epics channels
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
from ophyd import Device, EpicsMotor, EpicsSignal, EpicsSignalRO
|
from ophyd import Device, EpicsMotor, EpicsSignal, EpicsSignalRO
|
||||||
from ophyd import Component as Cpt
|
from ophyd import Component as Cpt
|
||||||
self.ScanX = EpicsMotor(name='ScanX',prefix='X07MB-ES-MA1:ScanX')
|
self.ScanX = EpicsMotor(name='ScanX',prefix='X07MB-ES-MA1:ScanX')
|
||||||
self.ScanY = EpicsMotor(name='ScanY',prefix='X07MB-ES-MA1:ScanY')
|
self.ScanY = EpicsMotor(name='ScanY',prefix='X07MB-ES-MA1:ScanY')
|
||||||
self.DIODE = EpicsSignal(name='SI',read_pv='X07MB-OP2-SAI_07:MEAN')
|
self.DIODE = EpicsSignal(name='SI',read_pv='X07MB-OP2-SAI_07:MEAN')
|
||||||
self.SIG = Cpt(EpicsSignal,name='we',read_pv="X07MB-OP2-SAI_07:MEAN")
|
self.SIG = Cpt(EpicsSignal,name='we',read_pv="X07MB-OP2-SAI_07:MEAN")
|
||||||
self.SMPL = EpicsSignal(name='SMPL',read_pv='X07MB-OP2:SMPL')
|
self.SMPL = EpicsSignal(name='SMPL',read_pv='X07MB-OP2:SMPL')
|
||||||
self.CYCLES = EpicsSignal(name='SMPL',read_pv='X07MB-OP2:TOTAL-CYCLES',write_pv='X07MB-OP2:TOTAL-CYCLES')
|
self.CYCLES = EpicsSignal(name='SMPL',read_pv='X07MB-OP2:TOTAL-CYCLES',write_pv='X07MB-OP2:TOTAL-CYCLES')
|
||||||
self.fielda =EpicsSignal(name='SMPL',read_pv='X07MB-SCAN:scan1.P1SP',write_pv='X07MB-SCAN:scan1.P1SP')
|
self.fielda =EpicsSignal(name='SMPL',read_pv='X07MB-SCAN:scan1.P1SP',write_pv='X07MB-SCAN:scan1.P1SP')
|
||||||
#end class
|
#end class
|
||||||
|
|
||||||
ph=PhoenixBL()
|
ph=PhoenixBL()
|
||||||
|
|
||||||
print('---------------------------------')
|
print('---------------------------------')
|
||||||
|
|
||||||
# scan will not diode
|
# scan will not diode
|
||||||
print(' SCAN DO NOT READ DIODE ')
|
print(' SCAN DO NOT READ DIODE ')
|
||||||
dev.PH_curr_conf.readout_priority='baseline' # do not read detector
|
dev.PH_curr_conf.readout_priority='baseline' # do not read detector
|
||||||
ti=tt.time_ns()
|
ti=tt.time_ns()
|
||||||
s1=scans.line_scan(dev.PH_ScanX_conf,0,0.002,steps=4,exp_time=.01,relative=False,delay=2)
|
s1=scans.line_scan(dev.PH_ScanX_conf,0,0.002,steps=4,exp_time=.01,relative=False,delay=2)
|
||||||
tf=tt.time_ns()
|
tf=tt.time_ns()
|
||||||
|
|
||||||
print('elapsed time',(tf-ti)/1e9)
|
print('elapsed time',(tf-ti)/1e9)
|
||||||
# scan will read diode
|
# scan will read diode
|
||||||
print(' SCAN READ DIODE ')
|
print(' SCAN READ DIODE ')
|
||||||
tt.sleep(2)
|
tt.sleep(2)
|
||||||
dev.PH_curr_conf.readout_priority='monitored' # read detector
|
dev.PH_curr_conf.readout_priority='monitored' # read detector
|
||||||
|
|
||||||
s2=scans.line_scan(dev.PH_ScanX_conf,0,0.002,steps=11,exp_time=.3,relative=False,delay=2)
|
s2=scans.line_scan(dev.PH_ScanX_conf,0,0.002,steps=11,exp_time=.3,relative=False,delay=2)
|
||||||
|
|
||||||
|
|
||||||
"""
|
"""
|
||||||
next lines do not work as pandas is not installed on test system
|
next lines do not work as pandas is not installed on test system
|
||||||
|
|
||||||
res1 = s1.scan.to_pandas()
|
res1 = s1.scan.to_pandas()
|
||||||
re1 = res1.to_numpy()
|
re1 = res1.to_numpy()
|
||||||
print('Scana')
|
print('Scana')
|
||||||
print(res1)
|
print(res1)
|
||||||
print('')
|
print('')
|
||||||
print('Scan2 at pandas ')
|
print('Scan2 at pandas ')
|
||||||
print(res2)
|
print(res2)
|
||||||
print('Scan2 as numpy ')
|
print('Scan2 as numpy ')
|
||||||
print(res2)
|
print(res2)
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@ -83,3 +83,4 @@ print(res2)
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1,62 +1,52 @@
|
|||||||
"""
|
"""
|
||||||
Scritpt to be developed as template for phoenic scritps
|
Scritpt to be developed as template for phoenic scritps
|
||||||
"""
|
"""
|
||||||
|
#from unittest import mock
|
||||||
# from unittest import mock
|
|
||||||
import numpy as np
|
import numpy as np
|
||||||
|
#import pandas
|
||||||
# import pandas
|
#import pytest
|
||||||
# import pytest
|
#from bec_lib import messages
|
||||||
# from bec_lib import messages
|
#import device_server
|
||||||
# import device_server
|
#from ophyd importPhoenixTemplate.pyitioner import PVPositionerComparator
|
||||||
# from ophyd importPhoenixTemplate.pyitioner import PVPositionerComparator
|
#from ophyd.status import DeviceStatus, SubscriptionStatus
|
||||||
# from ophyd.status import DeviceStatus, SubscriptionStatus
|
|
||||||
|
|
||||||
import time
|
import time
|
||||||
|
|
||||||
# import ophyd
|
#import ophyd
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
import importlib
|
import importlib
|
||||||
import ophyd
|
import ophyd
|
||||||
|
|
||||||
|
|
||||||
#
|
#logger = bec_logger.logger
|
||||||
phoenix.add_phoenix_config()
|
# load local configuration
|
||||||
# bec.config.update_session_with_file('./ConfigPHOENIX/device_config/phoenix_devices.yaml')
|
#bec.config.load_demo_config()
|
||||||
|
|
||||||
|
# .. define base path for directory with scripts
|
||||||
|
|
||||||
|
PhoenixBL=0
|
||||||
|
from ConfigPHOENIX.config.phoenix import PhoenixBL
|
||||||
|
#from ConfigPHOENIX.devices.falcon_csaxs import FalconSetup
|
||||||
|
# initialize general parameter
|
||||||
|
ph=PhoenixBL()
|
||||||
|
|
||||||
|
bec.config.update_session_with_file('./ConfigPHOENIX/device_config/phoenix_devices.yaml')
|
||||||
|
|
||||||
time.sleep(1)
|
time.sleep(1)
|
||||||
|
|
||||||
s1 = scans.line_scan(dev.ScanX, 0, 0.1, steps=4, exp_time=0.2, relative=False, delay=2)
|
|
||||||
|
|
||||||
s2 = scans.phoenix_line_scan(dev.ScanX, 0, 0.1, steps=4, exp_time=0.2, relative=False, delay=2)
|
|
||||||
|
|
||||||
res1 = s1.scan.to_pandas()
|
|
||||||
re1 = res1.to_numpy()
|
|
||||||
w1 = PH.PhGroup("Bec Linescan")
|
|
||||||
w1.linescan2group(s1)
|
|
||||||
|
|
||||||
print("res1")
|
|
||||||
print(res1)
|
|
||||||
print("as numpy")
|
|
||||||
print("re1")
|
|
||||||
|
|
||||||
|
|
||||||
res2 = s2.scan.to_pandas()
|
s1=scans.line_scan(dev.ScanX,0,0.002,steps=4,exp_time=1,relative=False,delay=2)
|
||||||
re2 = res2.to_numpy()
|
|
||||||
w2 = PH.PhGroup("PHOENIX Linescan")
|
s2=scans.phoenix_line_scan(dev.ScanX,0,0.002,steps=4,exp_time=.01,relative=False,delay=2)
|
||||||
w2.linescan2group(s2)
|
|
||||||
|
|
||||||
print("res2")
|
|
||||||
print(res2)
|
|
||||||
print("as numpy")
|
|
||||||
print("re2")
|
|
||||||
|
|
||||||
|
|
||||||
print(s1)
|
|
||||||
print("---------------------------------")
|
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
print('---------------------------------')
|
||||||
|
|
||||||
# scan will not diode
|
# scan will not diode
|
||||||
print(' SCAN DO NOT READ DIODE ')
|
print(' SCAN DO NOT READ DIODE ')
|
||||||
dev.PH_curr_conf.readout_priority='baseline' # do not read detector
|
dev.PH_curr_conf.readout_priority='baseline' # do not read detector
|
||||||
@ -69,8 +59,9 @@ print('elapsed time',(tf-ti)/1e9)
|
|||||||
print(' SCAN READ DIODE ')s is not installed on test system
|
print(' SCAN READ DIODE ')s is not installed on test system
|
||||||
ScanX_conf,0,0.002,steps=11,exp_time=.3,relative=False,delay=2)
|
ScanX_conf,0,0.002,steps=11,exp_time=.3,relative=False,delay=2)
|
||||||
|
|
||||||
|
"""
|
||||||
#next lines do not work as pandas is not installed on test system
|
"""
|
||||||
|
next lines do not work as pandas is not installed on test system
|
||||||
|
|
||||||
res1 = s1.scan.to_pandas()
|
res1 = s1.scan.to_pandas()
|
||||||
re1 = res1.to_numpy()
|
re1 = res1.to_numpy()
|
||||||
@ -81,5 +72,4 @@ print('Scan2 at pandas ')
|
|||||||
print(res2)
|
print(res2)
|
||||||
print('Scan2 as numpy ')
|
print('Scan2 as numpy ')
|
||||||
print(res2)
|
print(res2)
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
@ -1,9 +0,0 @@
|
|||||||
# purpose of directory
|
|
||||||
|
|
||||||
This diretory is for scripts, test etc. which are not loaded into the bec-server.
|
|
||||||
|
|
||||||
For now we keep it in the phoenix_bec structure, but for operation, such files should be located out side of the
|
|
||||||
bec_phoenix plugin.
|
|
||||||
|
|
||||||
|
|
||||||
TO avoid poading of these files, there should be no files called __init__.py anywhere in the directory tree
|
|
@ -1,8 +0,0 @@
|
|||||||
This diretory is for scripts, test etc. which are not loaded into the server.
|
|
||||||
|
|
||||||
Hence no directory should contain a file named
|
|
||||||
__init__.py
|
|
||||||
|
|
||||||
|
|
||||||
For now we keep it in the phoenix_bec structure, but for operation, such files should be located out side of the
|
|
||||||
bec_phoenix plugin.
|
|
@ -1,5 +1,2 @@
|
|||||||
print('test')
|
print('test')
|
||||||
base='/data/test/x07mb-test-bec/bec_deployment/phoenix_bec/phoenix_bec'
|
|
||||||
bec.config.update_session_with_file(base+'/device_configs/phoenix_falcon.yaml')
|
|
||||||
#bec.config.update_session_with_file(base+'/device_configs/phoenix_devices.yaml')
|
|
||||||
|
|
||||||
|
@ -1,3 +0,0 @@
|
|||||||
import phoenix_bec.scripts.phoenix as PH
|
|
||||||
w=PH.PhGroup('labelName')
|
|
||||||
w.linescan2group(s1)
|
|
@ -1 +1,2 @@
|
|||||||
from .phoenix_scans import PhoenixLineScan
|
from .phoenix_line_scan import PhoenixLineScan
|
||||||
|
|
||||||
|
@ -13,8 +13,8 @@ but they are executed in a specific order:
|
|||||||
- self._check_limits # tests to ensure the limits won't be reached
|
- self._check_limits # tests to ensure the limits won't be reached
|
||||||
- self.open_scan # send an open_scan message including the scan name, the number of points and the scan motor names
|
- self.open_scan # send an open_scan message including the scan name, the number of points and the scan motor names
|
||||||
- self.stage # stage all devices for the upcoming acquisiton
|
- self.stage # stage all devices for the upcoming acquisiton
|
||||||
- self.run_baseline_readings # read all devices to get a baseline for the upcoming scan
|
- self.run_baseline_readings # read all devices to get a baseline for the upcoming scan
|
||||||
- self.pre_scan # perform additional actions before the scan starts
|
- self.pre_scan # perform additional actions before the scan starts
|
||||||
- self.scan_core # run a loop over all position
|
- self.scan_core # run a loop over all position
|
||||||
- self._at_each_point(ind, pos) # called at each position with the current index and the target positions as arguments
|
- self._at_each_point(ind, pos) # called at each position with the current index and the target positions as arguments
|
||||||
- self.finalize # clean up the scan, e.g. move back to the start position; wait everything to finish
|
- self.finalize # clean up the scan, e.g. move back to the start position; wait everything to finish
|
||||||
@ -22,43 +22,12 @@ but they are executed in a specific order:
|
|||||||
- self.cleanup # send a close scan message and perform additional cleanups if needed
|
- self.cleanup # send a close scan message and perform additional cleanups if needed
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# imports in ScanBase
|
|
||||||
# from __future__ import annotations
|
|
||||||
|
|
||||||
# import ast
|
|
||||||
# import enum
|
|
||||||
# import threading
|
|
||||||
# import time
|
# import time
|
||||||
# import uuid
|
|
||||||
# from abc import ABC, abstractmethod
|
|
||||||
# from typing import Any, Literal
|
|
||||||
|
|
||||||
# import numpy as np
|
# import numpy as np
|
||||||
|
|
||||||
# from bec_lib.device import DeviceBase
|
|
||||||
# from bec_lib.devicemanager import DeviceManagerBase
|
|
||||||
# from bec_lib.endpoints import MessageEndpoints
|
# from bec_lib.endpoints import MessageEndpoints
|
||||||
# from bec_lib.logger import bec_logger
|
# from bec_lib.logger import bec_logger
|
||||||
|
|
||||||
# from .errors import LimitError, ScanAbortion
|
|
||||||
# from .path_optimization import PathOptimizerMixin
|
|
||||||
# from .scan_stubs import ScanStubs
|
|
||||||
# end imports in ScanBase
|
|
||||||
|
|
||||||
# import time
|
|
||||||
|
|
||||||
# import numpy as np
|
|
||||||
|
|
||||||
import time
|
|
||||||
|
|
||||||
import numpy as np
|
|
||||||
|
|
||||||
# from bec_lib.endpoints import MessageEndpoints
|
|
||||||
from bec_lib.logger import bec_logger
|
|
||||||
from bec_server.scan_server.scans import ScanArgType, ScanBase
|
|
||||||
|
|
||||||
from phoenix_bec.scripts.phoenix import PhoenixBL
|
|
||||||
|
|
||||||
# from bec_lib import messages
|
# from bec_lib import messages
|
||||||
# from bec_server.scan_server.errors import ScanAbortion
|
# from bec_server.scan_server.errors import ScanAbortion
|
||||||
# from bec_server.scan_server.scans import FlyScanBase, RequestBase, ScanArgType, ScanBase
|
# from bec_server.scan_server.scans import FlyScanBase, RequestBase, ScanArgType, ScanBase
|
||||||
@ -66,56 +35,15 @@ from phoenix_bec.scripts.phoenix import PhoenixBL
|
|||||||
# logger = bec_logger.logger
|
# logger = bec_logger.logger
|
||||||
|
|
||||||
|
|
||||||
|
from bec_server.scan_server.scans import ScanBase, ScanArgType
|
||||||
|
import numpy as np
|
||||||
|
import time
|
||||||
|
from bec_lib.logger import bec_logger
|
||||||
|
|
||||||
logger = bec_logger.logger
|
logger = bec_logger.logger
|
||||||
|
|
||||||
|
class PhoenixLineScan(ScanBase):
|
||||||
class LogTime:
|
scan_name = "phoenix_line_scanZZZ"
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
logger.success("init LogTime")
|
|
||||||
self.t0 = time.time()
|
|
||||||
|
|
||||||
def p_s(self, x):
|
|
||||||
now = time.time()
|
|
||||||
# delta=now-self.t0
|
|
||||||
m = str(now) + " sec " + x
|
|
||||||
logger.success(m)
|
|
||||||
# making the instance of PSID
|
|
||||||
# self.t0=now
|
|
||||||
file = open("MyLogfile.txt", "a")
|
|
||||||
file.write(m + "\n")
|
|
||||||
file.close
|
|
||||||
|
|
||||||
|
|
||||||
class PhoenixScanBaseTTL(ScanBase):
|
|
||||||
"""
|
|
||||||
Base scan cl p_s('init scrips.phoenix.scans.PhoenixLineScan')
|
|
||||||
"""
|
|
||||||
|
|
||||||
def scan_core(self):
|
|
||||||
"""perform the scan core procedure"""
|
|
||||||
self.p_s("PhoenixScanBaseTT.scan_core")
|
|
||||||
for ind, pos in self._get_position():
|
|
||||||
for self.burst_index in range(self.burst_at_each_point):
|
|
||||||
self.p_s("PhoenixScanBaseTT.scan_core in loop ")
|
|
||||||
|
|
||||||
yield from self._at_each_point(ind, pos)
|
|
||||||
self.burst_index = 0
|
|
||||||
|
|
||||||
def _at_each_point(self, ind=None, pos=None):
|
|
||||||
self.p_s("PhoenixScanBaseTT._at_each_point")
|
|
||||||
yield from self._move_scan_motors_and_wait(pos)
|
|
||||||
time.sleep(self.settling_time)
|
|
||||||
yield from self.stubs.trigger(min_wait=self.exp_time)
|
|
||||||
yield from self.stubs.read(group="monitored", point_id=self.point_id)
|
|
||||||
|
|
||||||
self.point_id += 1
|
|
||||||
self.p_s("done")
|
|
||||||
|
|
||||||
|
|
||||||
class PhoenixLineScan(PhoenixScanBaseTTL):
|
|
||||||
|
|
||||||
scan_name = "phoenix_line_scan"
|
|
||||||
required_kwargs = ["steps", "relative"]
|
required_kwargs = ["steps", "relative"]
|
||||||
arg_input = {
|
arg_input = {
|
||||||
"device": ScanArgType.DEVICE,
|
"device": ScanArgType.DEVICE,
|
||||||
@ -135,40 +63,53 @@ class PhoenixLineScan(PhoenixScanBaseTTL):
|
|||||||
steps: int = None,
|
steps: int = None,
|
||||||
relative: bool = False,
|
relative: bool = False,
|
||||||
burst_at_each_point: int = 1,
|
burst_at_each_point: int = 1,
|
||||||
setup_device: str = None,
|
setup_device:str= None,
|
||||||
**kwargs,
|
**kwargs,
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
A phoenix line scan for one or more motors.
|
A phoenix line scan for one or more motors.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
*args (Device, float, float): pairs of device / start position / end position
|
*args (Device, float, float): pairs of device / start position / end position
|
||||||
exp_time (float): exposure time in s. Default: 0
|
exp_time (float): exposure time in s. Default: 0
|
||||||
steps (int): number of steps. Default: 10
|
steps (int): number of steps. Default: 10
|
||||||
relative (bool): if True, the start and end positions are relative to the current position. Default: False
|
relative (bool): if True, the start and end positions are relative to the current position. Default: False
|
||||||
burst_a Specifies the level of type checking analysis to perform.
|
burst_a Specifies the level of type checking analysis to perform.
|
||||||
ans.line_scan(dev.motor1, -5, 5, dev.motor2, -5, 5, steps=10, exp_time=0.1, relative=True)
|
ans.line_scan(dev.motor1, -5, 5, dev.motor2, -5, 5, steps=10, exp_time=0.1, relative=True)
|
||||||
|
|
||||||
"""
|
"""
|
||||||
# from phoenix_bec.scripts.phoenix import PhoenixBL
|
|
||||||
self.p_s = PhoenixBL.my_log
|
|
||||||
|
|
||||||
self.p_s("init scripts.phoenix.scans.PhoenixLineScan")
|
|
||||||
|
|
||||||
super().__init__(
|
super().__init__(
|
||||||
exp_time=exp_time, relative=relative, burst_at_each_point=burst_at_each_point, **kwargs
|
exp_time=exp_time, relative=relative, burst_at_each_point=burst_at_each_point, **kwargs
|
||||||
)
|
)
|
||||||
self.steps = steps
|
self.steps = steps
|
||||||
self.setup_device = setup_device
|
self.setup_device = setup_device
|
||||||
|
print('INIT CLASS PhoenixLineScan')
|
||||||
time.sleep(1)
|
time.sleep(1)
|
||||||
self.p_s("done")
|
|
||||||
|
|
||||||
def _calculate_positions(self) -> None:
|
def _calculate_positions(self) -> None:
|
||||||
self.p_s("PhoenixLineScan._calculate_positions")
|
|
||||||
axis = []
|
axis = []
|
||||||
for _, val in self.caller_args.items():
|
for _, val in self.caller_args.items():
|
||||||
ax_pos = np.linspace(val[0], val[1], self.steps, dtype=float)
|
ax_pos = np.linspace(val[0], val[1], self.steps, dtype=float)
|
||||||
axis.append(ax_pos)
|
axis.append(ax_pos)
|
||||||
self.positions = np.array(list(zip(*axis)), dtype=float)
|
self.positions = np.array(list(zip(*axis)), dtype=float)
|
||||||
self.p_s("done")
|
|
||||||
|
def _at_each_point(self, ind=None, pos=None):
|
||||||
|
yield from self._move_scan_motors_and_wait(pos)
|
||||||
|
if ind > 0:
|
||||||
|
yield from self.stubs.wait(
|
||||||
|
wait_type="read", group="primary", wait_group="readout_primary"
|
||||||
|
)
|
||||||
|
time.sleep(self.settling_time)
|
||||||
|
if self.setup_device:
|
||||||
|
yield from self.stubs.send_rpc_and_wait(self.setup_device, "velocity.set", 1)
|
||||||
|
yield from self.stubs.trigger(group="trigger", point_id=self.point_id)
|
||||||
|
yield from self.stubs.wait(wait_type="trigger", group="trigger", wait_time=self.exp_time)
|
||||||
|
yield from self.stubs.read(
|
||||||
|
group="primary", wait_group="readout_primary", point_id=self.point_id
|
||||||
|
)
|
||||||
|
yield from self.stubs.wait(
|
||||||
|
wait_type="read", group="scan_motor", wait_group="readout_primary"
|
||||||
|
)
|
||||||
|
|
||||||
|
self.point_id += 1
|
@ -1,11 +0,0 @@
|
|||||||
## scripts
|
|
||||||
|
|
||||||
Directory for general phoenix specific python code
|
|
||||||
|
|
||||||
|
|
||||||
to autoload register in __init__.py
|
|
||||||
|
|
||||||
|
|
||||||
## FILES
|
|
||||||
|
|
||||||
phoenix.py Base file with general definitions
|
|
@ -1,262 +1,92 @@
|
|||||||
# from unittest import mock
|
#from unittest import mock
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
import time
|
import time as tt
|
||||||
|
|
||||||
import numpy as np
|
import numpy as np
|
||||||
|
#import pandas
|
||||||
# import pandas
|
#import pytest
|
||||||
# import pytest
|
#from bec_lib import messages
|
||||||
# from bec_lib import messages
|
#import device_server
|
||||||
# import device_server
|
#from ophyd import Component as Cpt
|
||||||
# from ophyd import Component as Cpt
|
|
||||||
from ophyd import Device, EpicsMotor, EpicsSignal, EpicsSignalRO
|
from ophyd import Device, EpicsMotor, EpicsSignal, EpicsSignalRO
|
||||||
|
#from ophyd import FormattedComponent as FCpt
|
||||||
# from ophyd import FormattedComponent as FCpt
|
#from ophyd import Kind, PVPositioner, Signal
|
||||||
# from ophyd import Kind, PVPositioner, Signal
|
#from ophyd.flyers import FlyerInterface
|
||||||
# from ophyd.flyers import FlyerInterface
|
#from ophyd.pv_positioner import PVPositionerComparator
|
||||||
# from ophyd.pv_positioner import PVPositionerComparator
|
#from ophyd.status import DeviceStatus, SubscriptionStatus
|
||||||
# from ophyd.status import DeviceStatus, SubscriptionStatus
|
|
||||||
|
|
||||||
from bec_lib.config_helper import ConfigHelper
|
from bec_lib.config_helper import ConfigHelper
|
||||||
from bec_lib.logger import bec_logger
|
from bec_lib.logger import bec_logger
|
||||||
|
|
||||||
logger = bec_logger.logger
|
logger = bec_logger.logger
|
||||||
|
|
||||||
|
|
||||||
# import ophyd
|
#import ophyd
|
||||||
# logger = bec_logger.logger
|
#logger = bec_logger.logger
|
||||||
# load simulation
|
# load simulation
|
||||||
# bec.config.load_demo_config()
|
#bec.config.load_demo_config()
|
||||||
|
|
||||||
# .. define base path for directory with scripts
|
# .. define base path for directory with scripts
|
||||||
|
|
||||||
|
class PhoenixBL():
|
||||||
class PhoenixBL:
|
|
||||||
"""
|
"""
|
||||||
#
|
#
|
||||||
# General class for PHOENIX beamline located in phoenix_bec/phoenic_bec/scripts
|
# General class for PHOENIX beamline located in phoenix_bec/phoenic_bec/scripts
|
||||||
#
|
#
|
||||||
"""
|
"""
|
||||||
|
|
||||||
t0 = time.time()
|
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
"""
|
"""
|
||||||
init PhoenixBL() in phoenix_bec/scripts
|
init PhoenixBL() in phoenix_bec/scripts
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
import os
|
import os
|
||||||
|
|
||||||
print("..... init PhoenixBL from phoenix_bec/scripts/phoenix.py")
|
print('..... init PhoenixBL from phoenix_bec/scripts/phoenix.py')
|
||||||
|
|
||||||
|
#from ophyd import Device, EpicsMotor, EpicsSignal, EpicsSignalRO
|
||||||
|
#from ophyd import Component as Cpt
|
||||||
|
#self.ScanX = EpicsMotor(name='ScanX',prefix='X07MB-ES-MA1:ScanX')
|
||||||
|
#self.ScanY = EpicsMotor(name='ScanY',prefix='X07MB-ES-MA1:ScanY')
|
||||||
|
#self.DIODE = EpicsSignal(name='SI',read_pv='X07MB-OP2-SAI_07:MEAN')
|
||||||
|
#self.SIG = Cpt(EpicsSignal,name='we',read_pv="X07MB-OP2-SAI_07:MEAN")
|
||||||
|
#self.SMPL = EpicsSignal(name='SMPL',read_pv='X07MB-OP2:SMPL')
|
||||||
|
#self.CYCLES = EpicsSignal(name='SMPL',read_pv='X07MB-OP2:TOTAL-CYCLES',write_pv='X07MB-OP2:TOTAL-CYCLES')
|
||||||
|
|
||||||
# load local configuration
|
# load local configuration
|
||||||
|
|
||||||
self.path_scripts_local = (
|
self.path_scripts_local = '/data/test/x07mb-test-bec/bec_deployment/phoenix_bec/phoenix_bec/local_scripts/'
|
||||||
"/data/test/x07mb-test-bec/bec_deployment/phoenix_bec/phoenix_bec/local_scripts/"
|
self.path_config_local = self.path_scripts_local + 'TEST_ConfigPhoenix/' # base dir for local configurations
|
||||||
)
|
self.path_devices_local = self.path_config_local + 'Local_device_config/' # local yamal file
|
||||||
|
self.file_devices_file_local = self.path_devices_local + 'phoenix_devices.yaml'
|
||||||
|
|
||||||
self.path_config_local = (
|
self.path_phoenix_bec ='/data/test/x07mb-test-bec/bec_deployment/phoenix_bec/'
|
||||||
self.path_scripts_local + "TEST_ConfigPhoenix/"
|
self.path_devices = self.path_phoenix_bec + 'phoenix_bec/device_configs/' # local yamal file
|
||||||
) # base dir for local configurations
|
self.file_devices_file = self.path_phoenix_bec + 'phoenix_bec/device_configs/phoenix_devices.yaml' # local yamal file
|
||||||
|
|
||||||
self.path_devices_local = (
|
|
||||||
self.path_config_local + "Local_device_config/"
|
|
||||||
) # local yamal file
|
|
||||||
|
|
||||||
self.file_devices_file_local = self.path_devices_local + "phoenix_devices.yaml"
|
|
||||||
|
|
||||||
self.path_phoenix_bec = "/data/test/x07mb-test-bec/bec_deployment/phoenix_bec/"
|
|
||||||
self.path_devices = self.path_phoenix_bec + "phoenix_bec/device_configs/"
|
|
||||||
# yamal file for default configuration
|
|
||||||
|
|
||||||
self.file_devices_file = (
|
|
||||||
self.path_phoenix_bec + "phoenix_bec/device_configs/phoenix_devices.yaml"
|
|
||||||
) # local yamal file
|
|
||||||
|
|
||||||
self.file_devices_tmp = (
|
|
||||||
self.path_phoenix_bec + "phoenix_bec/device_configs/current_devices_tmp.yaml"
|
|
||||||
) # tmp configuration file. Will be electronicall created and appended if needed
|
|
||||||
|
|
||||||
self.t0 = time.time()
|
|
||||||
|
|
||||||
def read_local_phoenix_config(self):
|
def read_local_phoenix_config(self):
|
||||||
print("read file ")
|
print('read file ')
|
||||||
print(self.file_phoenix_devices_file)
|
print(self.file_phoenix_devices_file)
|
||||||
bec.config.update_session_with_file(self.file_devices_file_local)
|
bec.config.update_session_with_file(self.file_devices_file_local)
|
||||||
|
|
||||||
def create_base_config(self):
|
|
||||||
# create a yaml file from standard configuration
|
|
||||||
os.system("cat " + self.file_devices_file + " > " + self.file_devices_tmp)
|
|
||||||
# os.system("ls -altr" + self.path_phoenix_bec + "phoenix_bec/devices")
|
|
||||||
bec.config.update_session_with_file(self.file_devices_tmp)
|
|
||||||
|
|
||||||
def add_phoenix_config(self):
|
def add_phoenix_config(self):
|
||||||
print("add_phoenix_config ")
|
print('add_phoenix_config ')
|
||||||
print("self.file_devices_file")
|
print('self.file_devices_file')
|
||||||
bec.config.update_session_with_file(self.tmp.file_devices_file)
|
bec.config.update_session_with_file(self.file_devices_file)
|
||||||
|
|
||||||
def add_xmap(self):
|
def add_xmap(self):
|
||||||
print("add xmap ")
|
print('add xmap ')
|
||||||
|
print(self.path_devices+'phoenix_xmap.yaml')
|
||||||
|
|
||||||
os.system("cat " + self.path_devices + "phoenix_xmap.yaml" + " >> " + self.file_devices_tmp)
|
bec.config.update_session_with_file(self.path_devices+'phoenix_xmap.yaml',timeout=100)
|
||||||
bec.config.update_session_with_file(self.file_devices_tmp)
|
|
||||||
|
|
||||||
def add_falcon(self):
|
def add_falcon(self):
|
||||||
print("add_falcon to existing configuration ")
|
print('add_xmap')
|
||||||
|
print(self.path_devices+'/phoenix_falcon.yaml')
|
||||||
os.system(
|
bec.config.wait_for_config_reply()
|
||||||
"cat " + self.path_devices + "phoenix_falcon.yaml" + " >> " + self.file_devices_tmp
|
bec.config.update_session_with_file(self.path_devices+'/phoenix_falcon.yaml')
|
||||||
)
|
|
||||||
bec.config.update_session_with_file(self.file_devices_tmp)
|
|
||||||
|
|
||||||
def load_falcon(self):
|
|
||||||
|
|
||||||
print("load_falcon")
|
|
||||||
bec.config.update_session_with_file(self.path_devices + "phoenix_falcon.yaml")
|
|
||||||
|
|
||||||
def show_phoenix_setup(self):
|
def show_phoenix_setup(self):
|
||||||
print(self.path_phoenix_bec)
|
print(self.path_phoenix_bec)
|
||||||
os.system("cat " + self.path_phoenix_bec + "phoenix_bec/scripts/Current_setup.txt")
|
os.system('cat '+self.path_phoenix_bec+'phoenix_bec/scripts/Current_setup.txt')
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def my_log(cls, x):
|
|
||||||
"""
|
|
||||||
class method allows to write a user defined log file
|
|
||||||
time is seconds relative to some point max 10 minutes ago
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
print(time.time())
|
|
||||||
now = time.time() - (86400 * (time.time() // 86400))
|
|
||||||
now = now - 3600.0 * (now // 3600.0)
|
|
||||||
now = now - 600.0 * (now // 600.0)
|
|
||||||
m = str(now) + " sec " + x
|
|
||||||
|
|
||||||
logger.success(m)
|
|
||||||
|
|
||||||
file = open("MyLogfile.txt", "a")
|
|
||||||
file.write(m + "\n")
|
|
||||||
file.close
|
|
||||||
|
|
||||||
|
|
||||||
class PhGroup:
|
|
||||||
"""
|
|
||||||
Class to create data groups
|
|
||||||
compatible with larch groups
|
|
||||||
|
|
||||||
initialize by
|
|
||||||
|
|
||||||
ww=PhGroup('YourLabel')
|
|
||||||
|
|
||||||
it creates a group
|
|
||||||
with default attributes
|
|
||||||
|
|
||||||
ww.label = 'YourLabel' --- for compatibility with larch groups
|
|
||||||
ww.description =YourLabel'
|
|
||||||
|
|
||||||
Further data can be added with new tags by
|
|
||||||
|
|
||||||
ww.newtag=67
|
|
||||||
(bec_venv) [gac-x07mb@x07mb-bec-001 phoenix_bec]$
|
|
||||||
|
|
||||||
ww.keys() -- list all keys
|
|
||||||
ww.linescan2group -- converts bec linescan data to group format
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, description):
|
|
||||||
|
|
||||||
setattr(self, "description", description)
|
|
||||||
# atribute 'label' for compatibility woith La groups...
|
|
||||||
setattr(self, "label", description)
|
|
||||||
# if type(NameTag)==list:
|
|
||||||
# for i in NameTag:
|
|
||||||
# setattr(self,i,None)
|
|
||||||
# #endfor
|
|
||||||
# else:
|
|
||||||
# setattr(self,NameTag,None)
|
|
||||||
# endif
|
|
||||||
|
|
||||||
def add(self, NameTag, content):
|
|
||||||
"""
|
|
||||||
Add tags to group...
|
|
||||||
|
|
||||||
Parameters
|
|
||||||
----------
|
|
||||||
NameTag : TYPE
|
|
||||||
DESCRIPTION.
|
|
||||||
content : TYPE
|
|
||||||
DESCRIPTION.
|
|
||||||
|
|
||||||
Returns
|
|
||||||
-------
|
|
||||||
None.
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
setattr(self, NameTag, content)
|
|
||||||
|
|
||||||
def keys(self):
|
|
||||||
"""
|
|
||||||
Method gets all atributes, which are not methods
|
|
||||||
and which do not start with __
|
|
||||||
|
|
||||||
|
|
||||||
Returns
|
|
||||||
-------
|
|
||||||
box : TYPE
|
|
||||||
DESCRIPTION.
|
|
||||||
|
|
||||||
"""
|
|
||||||
box = []
|
|
||||||
|
|
||||||
for i in self.__dir__():
|
|
||||||
if "__" not in i:
|
|
||||||
# print(i)
|
|
||||||
if str(type(self.__getattribute__(i))) != "<class 'method'>":
|
|
||||||
box.append(i)
|
|
||||||
# endif
|
|
||||||
# endfor
|
|
||||||
return box
|
|
||||||
|
|
||||||
def linescan2group(self, this_scan):
|
|
||||||
"""
|
|
||||||
|
|
||||||
method merges results of linescan into group and
|
|
||||||
creates for each data a numpy variable constructed as
|
|
||||||
|
|
||||||
group_name.{device_name}_{variable_name}_val (for value )
|
|
||||||
group_name.{device_name}_{variable_name}_ts (for timestamp )
|
|
||||||
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
print("keys")
|
|
||||||
print(this_scan.scan.data.keys())
|
|
||||||
for outer_key in this_scan.scan.data.keys():
|
|
||||||
print("outer_key", outer_key)
|
|
||||||
n_outer = len(this_scan.scan.data.keys())
|
|
||||||
for inner_key in this_scan.scan.data[outer_key].keys():
|
|
||||||
print("inner_key", inner_key)
|
|
||||||
# calculate nunber of points
|
|
||||||
n_inner = len(this_scan.scan.data[outer_key][inner_key].keys())
|
|
||||||
value = np.zeros(n_inner)
|
|
||||||
timestamp = np.zeros(n_inner)
|
|
||||||
for i in range(n_inner):
|
|
||||||
try:
|
|
||||||
value[i] = this_scan.scan.data[outer_key][inner_key][i]["value"]
|
|
||||||
except:
|
|
||||||
value = None
|
|
||||||
try:
|
|
||||||
timestamp[i] = this_scan.scan.data[outer_key][inner_key][i]["timestamp"]
|
|
||||||
except:
|
|
||||||
timestamp[i] = None
|
|
||||||
# endfor
|
|
||||||
self.add(inner_key + "_" + outer_key + "_val", value)
|
|
||||||
self.add(inner_key + "_" + outer_key + "_ts", timestamp)
|
|
||||||
# endfor
|
|
||||||
# endfor
|
|
||||||
# endfor
|
|
||||||
|
|
||||||
# enddef
|
|
||||||
|
@ -1,103 +0,0 @@
|
|||||||
import threading
|
|
||||||
import time
|
|
||||||
from unittest import mock
|
|
||||||
|
|
||||||
import numpy as np
|
|
||||||
import ophyd
|
|
||||||
import pytest
|
|
||||||
from bec_server.device_server.tests.utils import DMMock
|
|
||||||
from ophyd_devices.tests.utils import MockPV, patch_dual_pvs
|
|
||||||
|
|
||||||
from phoenix_bec.devices.phoenix_trigger import SAMPLING, PhoenixTrigger
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(scope="function")
|
|
||||||
def mock_trigger():
|
|
||||||
name = "phoenix_trigger"
|
|
||||||
prefix = "X07MB-OP2:"
|
|
||||||
dm = DMMock()
|
|
||||||
with mock.patch.object(dm, "connecto"):
|
|
||||||
with (
|
|
||||||
mock.patch(
|
|
||||||
"ophyd_devices.interfaces.base_classes.bec_device_base.FileWriter"
|
|
||||||
) as filemixin,
|
|
||||||
mock.patch(
|
|
||||||
"ophyd_devices.interfaces.base_classes.psi_detector_base.PSIDetectorBase._update_service_config"
|
|
||||||
) as mock_service_config,
|
|
||||||
):
|
|
||||||
with mock.patch.object(ophyd, "cl") as mock_cl:
|
|
||||||
mock_cl.get_pv = MockPV
|
|
||||||
mock_cl.thread_class = threading.Thread
|
|
||||||
with mock.patch.object(PhoenixTrigger, "_init"):
|
|
||||||
det = PhoenixTrigger(name=name, prefix=prefix, device_manager=dm)
|
|
||||||
patch_dual_pvs(det)
|
|
||||||
det.TIMEOUT_FOR_SIGNALS = 0.1
|
|
||||||
yield det
|
|
||||||
|
|
||||||
|
|
||||||
def test_phoenix_trigger_init(mock_trigger):
|
|
||||||
"""Test PhoenixTrigger init"""
|
|
||||||
assert mock_trigger.name == "phoenix_trigger"
|
|
||||||
assert mock_trigger.prefix == "X07MB-OP2:"
|
|
||||||
|
|
||||||
|
|
||||||
def test_phoenix_trigger_stage(mock_trigger):
|
|
||||||
"""Test PhoenixTrigger on_stage"""
|
|
||||||
with mock.patch.object(mock_trigger.scaninfo, "load_scan_metadata") as mock_load_scan_metadata:
|
|
||||||
mock_trigger.scaninfo.scan_type = "step"
|
|
||||||
mock_trigger.scaninfo.exp_time = exp_time = 1
|
|
||||||
mock_trigger.stage()
|
|
||||||
assert mock_load_scan_metadata.call_count == 1
|
|
||||||
assert mock_trigger.start_csmpl.get() == 0
|
|
||||||
assert mock_trigger.total_cycles.get() == np.ceil(exp_time * 5)
|
|
||||||
assert mock_trigger.smpl.get() == 1
|
|
||||||
|
|
||||||
|
|
||||||
def test_phoenix_trigger_unstage(mock_trigger):
|
|
||||||
"""Test PhoenixTrigger on_unstage"""
|
|
||||||
with mock.patch.object(mock_trigger.custom_prepare, "on_stop") as mock_on_stop:
|
|
||||||
mock_trigger.unstage()
|
|
||||||
assert mock_on_stop.call_count == 1
|
|
||||||
|
|
||||||
|
|
||||||
def test_phoenix_trigger_stop(mock_trigger):
|
|
||||||
"""Test PhoenixTrigger on_stop"""
|
|
||||||
with mock.patch.object(mock_trigger.smpl, "put") as mock_smpl_put:
|
|
||||||
mock_trigger.smpl_done._read_pv.mock_data = SAMPLING.RUNNING
|
|
||||||
mock_trigger.stop()
|
|
||||||
assert mock_trigger.stopped is True
|
|
||||||
# assert mock_trigger.total_cycles.get() == 5
|
|
||||||
# 5 cycles is too tight during development
|
|
||||||
assert mock_trigger.start_csmpl.get() == 1
|
|
||||||
assert mock_smpl_put.call_args_list == [mock.call(1), mock.call(1)]
|
|
||||||
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
uncomment this test, as device names etc will change
|
|
||||||
and as other devices will bee added
|
|
||||||
|
|
||||||
def test_phoenix_trigger_trigger(mock_trigger):
|
|
||||||
#Test PhoenixTrigger on_trigger
|
|
||||||
#
|
|
||||||
#irst test that the trigger timeouts due to readback from smpl_done not being done.
|
|
||||||
#Afterwards, check that status object resolved correctly if smpl_done is done.
|
|
||||||
#
|
|
||||||
exp_time = 0.05
|
|
||||||
mock_trigger.device_manager.add_device("falcon_nohdf5")
|
|
||||||
falcon_state = mock_trigger.device_manager.devices.falcon_nohdf5.state = mock.MagicMock()
|
|
||||||
falcon_state.get = mock.MagicMock(return_value=1)
|
|
||||||
mock_trigger.scaninfo.scan_type = "step"
|
|
||||||
mock_trigger.scaninfo.exp_time = exp_time
|
|
||||||
|
|
||||||
with mock.patch.object(
|
|
||||||
mock_trigger.custom_prepare, "wait_with_status", return_value=mock.MagicMock()
|
|
||||||
) as mock_wait_with_status:
|
|
||||||
status = mock_trigger.trigger()
|
|
||||||
assert mock_wait_with_status.call_count == 1
|
|
||||||
assert mock_wait_with_status.call_args[1]["signal_conditions"] == [
|
|
||||||
(mock_trigger.smpl_done.get, SAMPLING.DONE)
|
|
||||||
]
|
|
||||||
assert mock_wait_with_status.call_args[1]["timeout"] == 5 * exp_time
|
|
||||||
assert mock_wait_with_status.call_args[1]["check_stopped"] is True
|
|
||||||
"""
|
|
Reference in New Issue
Block a user