Compare commits
448 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 32f33f9f9c | |||
| 23edabcbcb | |||
| ff3899ef82 | |||
| ae85841a57 | |||
| bb857d6a74 | |||
| 11364bc067 | |||
| f8c133c7dd | |||
| ca809d5a8a | |||
| bc397711b8 | |||
| 48edc92261 | |||
| 7f32de4b8e | |||
| 7797fc42f4 | |||
| c982215c92 | |||
| 878e31594e | |||
| f0fbcabf55 | |||
| 2c456d3a9a | |||
| 545d4d80ff | |||
| eabfd0dbf1 | |||
| f8fdc20d3f | |||
| f537827bfe | |||
| cc1dedcbca | |||
| e6a0e037f4 | |||
| f49778ee5f | |||
| 9b85d2ca76 | |||
| 2f56fac108 | |||
| 35fc5df9df | |||
| 9ad3b1d61e | |||
| 06f09ef07e | |||
| bd272f227d | |||
| 40f523c294 | |||
| aa0b9f36a9 | |||
| 8271bc837d | |||
| 07e73cb472 | |||
| 9dce2eb353 | |||
| d54109e6de | |||
| 8ade6cf615 | |||
| fea6dd0fdb | |||
| fed6eb3ed1 | |||
| 0d11fea537 | |||
| 1c5f1815c2 | |||
| 830bc9222b | |||
| 7f398e4b8c | |||
| 66cf353a36 | |||
| 63c4a48317 | |||
| 8c892f3fe3 | |||
| feaa6688dc | |||
| e68bb84c1d | |||
| 87225bc18b | |||
| d2f9dd2b1d | |||
| d682107ba0 | |||
| b620a87f61 | |||
| 81994df4ec | |||
| b5406ca61f | |||
| 86fbd27e82 | |||
| 99a0876421 | |||
| 80cddac125 | |||
| 429fa93b42 | |||
| 3716c65fae | |||
| 7c5ae3a6f7 | |||
| 224ae6266e | |||
| 8a3143bb2f | |||
| daeb80dc39 | |||
| 670d7002c8 | |||
| 98a31cfec2 | |||
| 24a89f7aa2 | |||
| 1ef4b53e9f | |||
| 5ecc0ae388 | |||
| 8aa87381d4 | |||
| ab3f36369c | |||
| c63c215e0b | |||
| f61262fe30 | |||
| 2748af71f1 | |||
| 14bb8b477d | |||
| 0d3188e918 | |||
| 07df2011fb | |||
| 941b8f0b07 | |||
| 83a0b8150a | |||
| 4ac8d7f3e1 | |||
| 7edea7bba4 | |||
| 3be8c9cb0a | |||
| 4a77f52686 | |||
| cf569258f1 | |||
| 198f651bbf | |||
| 25c1080d6c | |||
| 7ccb05479a | |||
| 634e904a92 | |||
| 4d6c9edb7a | |||
| b619a83dda | |||
| 4815c71042 | |||
| 06980fa5b9 | |||
| 3af72a61f2 | |||
| 9dcc32aaa7 | |||
| a3672aa4db | |||
| 76514d59f9 | |||
| 10fc9f4c18 | |||
| 1534cfff9a | |||
| 5f8361cc1e | |||
| c37bd3b87d | |||
| 07b6287cd0 | |||
| 885fe6e142 | |||
| eba351aeb7 | |||
| 4521410936 | |||
| 07f6d9666e | |||
| a7df23a8ae | |||
| 22cd9d89be | |||
| b44b65a15c | |||
| 97f392ed17 | |||
| f3c85e6842 | |||
| 7436f8be5b | |||
| 8df2a7c1e4 | |||
| 9f58ddb768 | |||
| 195575c0cc | |||
| d1cacf8f5f | |||
| ec2d588c11 | |||
| c791359a56 | |||
| 0287da40da | |||
| caa5e5332e | |||
| bba65d0756 | |||
| 536329a595 | |||
| 5f0b3852a7 | |||
| e7682b4c6b | |||
| ee2137469c | |||
| 678a7de458 | |||
| 2bb347c6e5 | |||
| b464a7bfc1 | |||
| 255e204df4 | |||
| 2fc5fb6947 | |||
| cdac3b734e | |||
| f09f36f721 | |||
| ec565f6cbe | |||
| a471bc6a46 | |||
| 6e7e1e5848 | |||
| 6a5b59df11 | |||
| 12b8f33175 | |||
| adfbbe4e86 | |||
| e7820ccab5 | |||
| 6a8763f9e2 | |||
| 494b4a192b | |||
| 1faacd2bbf | |||
| bc364d0034 | |||
| 36af43f4ff | |||
| 5a5bd3a81d | |||
| 55f56dd716 | |||
| ef78c3e9c0 | |||
| 46d699b5e5 | |||
| 227d82b4ac | |||
| e59ca1c947 | |||
| d3c6dd24da | |||
| dd94966b3b | |||
| c7e0c0463e | |||
| 45258ca958 | |||
| b25fba8d3e | |||
| a2296adad7 | |||
| 8e94e218fc | |||
| 0220817f78 | |||
| 8c1b47cd7a | |||
| ff92ebe9a6 | |||
| 12da0f7d68 | |||
| 89ea522a6e | |||
| fb946294f0 | |||
| 2fa8b1ef2c | |||
| 47d358e067 | |||
| f11f0740d9 | |||
| 268965b702 | |||
| b3e70727b3 | |||
| 808a7ad570 | |||
| 7b1b85707d | |||
| 2d43461419 | |||
| d86a597467 | |||
| aa8c1f2353 | |||
| 53000a8c3e | |||
| 4e61339606 | |||
| 2477c0ae0c | |||
| 4a8cfdf834 | |||
| 87a24eabb2 | |||
| 0adfa32983 | |||
| f5663a700c | |||
| f36b68a00c | |||
| 954b5b9102 | |||
| 0e0a942eb4 | |||
| 3cd730e616 | |||
| 7c9eaca8ef | |||
| de756a81c4 | |||
| 5a39189bcd | |||
| d9c552fe11 | |||
| f8f5d04f79 | |||
| 57273891ec | |||
| dc4516dc34 | |||
| 5c24a8dd50 | |||
| 7a540dee7f | |||
| 02075c0d75 | |||
| b7bea0ba10 | |||
| a56253cbf1 | |||
| b0c7b240fc | |||
| eea72f45b4 | |||
| cae53d791a | |||
| bc8a22a556 | |||
| 1290e76ce2 | |||
| 2612204114 | |||
| 303a034e2b | |||
| 5f6d58e913 | |||
| b61fb2fbe6 | |||
| c4ca1f55d8 | |||
| a62d619ab4 | |||
| 106a93ba52 | |||
| e7bfbe3f8a | |||
| 60e3de2a29 | |||
| e381778bbb | |||
| 6ee2549634 | |||
| bb9c3f7772 | |||
| dba183d775 | |||
| 3b56841683 | |||
| b5cabd7533 | |||
| b00619e9e1 | |||
| e188009f4c | |||
| 3e86247dc6 | |||
| 35b0be080b | |||
| df0c1ff2b0 | |||
| b06d6b7a22 | |||
| 6359dc1241 | |||
| 1a0d9a2dc8 | |||
| c9a05fd6f9 | |||
| 3b19638db1 | |||
| 0d7f4f98a2 | |||
| 5535890d63 | |||
| fd7e02b6c1 | |||
| 3a956e979b | |||
| 36f5cf9287 | |||
| b29772ca27 | |||
| fbc3e37cef | |||
| ffeac3d253 | |||
| ad9753025b | |||
| 0ebf5c8863 | |||
| 584ad2146e | |||
| 91c3e63983 | |||
| 008edcb4ac | |||
| 780629ca11 | |||
| f2496916f1 | |||
| a6f2b9c101 | |||
| 10aae5cc3a | |||
| 45e659e14d | |||
| 3e1c21ef73 | |||
| 362328e060 | |||
| 09b3a66c56 | |||
| ebddfc1839 | |||
| 229fc1a632 | |||
| 61b69f6d1b | |||
| 359ea457cb | |||
| 44787676b6 | |||
| d408819dcd | |||
| a5f7137577 | |||
| 321fe85da5 | |||
| 46f2407eab | |||
| 746db73fc8 | |||
| 83ec86482b | |||
| 0e5f220850 | |||
| ad8a2cbf25 | |||
| 2e5dfce7aa | |||
| d3e9eded62 | |||
| b83fbe8598 | |||
| 06b73c5a87 | |||
| ac6774d4a4 | |||
| 4d8ab90b79 | |||
| ad10aa9a56 | |||
| ebf11f6e7a | |||
| b45e05031b | |||
| 37e9e75dd2 | |||
| 6efb6e38c7 | |||
| 4107949d9c | |||
| 3f0a8560f7 | |||
| 778e34ce8a | |||
| 5d83c86cd2 | |||
| b8cb68164d | |||
| 8030e1a279 | |||
| 0b9eb59db1 | |||
| 2931dacf18 | |||
| e4062daa96 | |||
| b159f272bb | |||
| 5bcefe5768 | |||
| a3d9e79f9d | |||
| 9bb3d31393 | |||
| d22cdeded7 | |||
| fbb2baf63b | |||
| 434c537011 | |||
| acb8b47a35 | |||
| 581a631d7b | |||
| 929c9f0072 | |||
| a5ec6c71b4 | |||
| 7df6c50504 | |||
| 89630cf9f8 | |||
| 213ba72da5 | |||
| f53bf5a8c3 | |||
| f72e965584 | |||
| 499f791e44 | |||
| 957dadcc83 | |||
| e404d1e0bf | |||
| c09f970b54 | |||
| 34b4ac4f58 | |||
| 7ce508118a | |||
| dba390e227 | |||
| 1b1b5a32bc | |||
| fb826d5863 | |||
| b851b76452 | |||
| 8f6dcd52e2 | |||
| 10d57fa106 | |||
| a9805a630f | |||
| e438dd736d | |||
| 0780421285 | |||
| 73b9afc05e | |||
| 7828ce7fe4 | |||
| 034be1f852 | |||
| f06b041baa | |||
| dc1a5c5afc | |||
| 28994476e5 | |||
| 1658503716 | |||
| 6b76880a22 | |||
| 6f8483a672 | |||
| 0417e21623 | |||
| d120169400 | |||
| 87e80bf2a5 | |||
| eb36cdb948 | |||
| 7bdd19185f | |||
| 16abb7a9e7 | |||
| ab2cffaa47 | |||
| 7309897d5d | |||
| 69ed54f7aa | |||
| 1479efcd59 | |||
| 2a80fa11b2 | |||
| 6c9cf4799c | |||
| 3f4ce1c2d4 | |||
| eb5408fb2d | |||
| 86dba672cd | |||
| ed63e9860d | |||
| 60cf87fca0 | |||
| 2156da1fc5 | |||
| c575f4e213 | |||
| 48a6047916 | |||
| ed9fd67504 | |||
| fa108f663a | |||
| c63f349e94 | |||
| 67e23a41f9 | |||
| c16ac726cf | |||
| 08fb028bd7 | |||
| f9bcfd0a24 | |||
| bf31e5c118 | |||
| d9f3b201dd | |||
| 6a16287bf4 | |||
| 222ea032ff | |||
| 8f99c1c9ca | |||
| d936524c8a | |||
| 0519848be7 | |||
| 152debee61 | |||
| c953774c07 | |||
| 5ae5df377f | |||
| 54ddb44003 | |||
| db4d04de07 | |||
| 2fc0ce1c8d | |||
| d7313a0380 | |||
| 727f4f338b | |||
| 071b119da7 | |||
| 89b3a3743d | |||
| 8dd2ae357b | |||
| 5cb941c775 | |||
| f78c661716 | |||
| 46b16b5f3e | |||
| 6e2aa3acc5 | |||
| c3d33118fc | |||
| 86968da6da | |||
| 6d195db2cb | |||
| 96eac615da | |||
| ef9c47d3d4 | |||
| 68595906d4 | |||
| a5c5f0b227 | |||
| 4e5d853930 | |||
| 0e089b6796 | |||
| af96e52685 | |||
| ca54d9d983 | |||
| 140eeed9a5 | |||
| a4d18879ab | |||
| 8d29532d57 | |||
| 915ba4e837 | |||
| a38f6641a9 | |||
| efe64ae362 | |||
| c9c77338a9 | |||
| 344966ed1b | |||
| c1c30c15a2 | |||
| ac717411af | |||
| ec6c29f496 | |||
| 2a83c49307 | |||
| cdc01437ff | |||
| 3cb525c090 | |||
| dad5883cb2 | |||
| 8c8cf3c0af | |||
| 7c649a49e3 | |||
| 0e58d93e0b | |||
| db2d083ab3 | |||
| 950bdc3381 | |||
| caee42e77d | |||
| 7d58422551 | |||
| 0622849cd7 | |||
| 83ee606628 | |||
| 52f4dc4b16 | |||
| f86494580b | |||
| d69f6fcd20 | |||
| fea189a551 | |||
| 3e41e67387 | |||
| 57c85922ca | |||
| 0f7d8d6f05 | |||
| 0756b28ac5 | |||
| 4009fd2126 | |||
| ab4f5bcbd4 | |||
| 5c9d57bc2b | |||
| 86bfaf16dc | |||
| 90c1e7e0fb | |||
| c082d5c545 | |||
| 78b4d2e45e | |||
| 9a2e5b75de | |||
| 691cbf8e86 | |||
| 6302c1d49e | |||
| 35c8bfa3b5 | |||
| e790ecc550 | |||
| a56e2f74d6 | |||
| f46cfc2e06 | |||
| 5bc60a0e3c | |||
| 0805bf8051 | |||
| 1bb61cc74a | |||
| 91a44729ca | |||
| 802fab812b | |||
| f27af35cff | |||
| 6797206dde | |||
| b8d4a0292b | |||
| 7ffde547e5 | |||
| d0e6ac1034 | |||
| 9ed1e522b6 | |||
| 2691e5feff | |||
| ddd749011c | |||
| 643170b6a5 | |||
| b7f101fff5 | |||
| ad6a4c02ed | |||
| 84ba4a0ff7 | |||
| ea92c1ae80 | |||
| 52699918e0 | |||
| 4b37b0687c | |||
| 4784acb462 | |||
| a08aff3707 | |||
| 49bb77a8a9 | |||
| 9e7d92b928 | |||
| 1888676f73 |
Vendored
+123
@@ -0,0 +1,123 @@
|
||||
# The default ``config.py``
|
||||
# flake8: noqa
|
||||
|
||||
|
||||
def set_prefs(prefs):
|
||||
"""This function is called before opening the project"""
|
||||
|
||||
# Specify which files and folders to ignore in the project.
|
||||
# Changes to ignored resources are not added to the history and
|
||||
# VCSs. Also they are not returned in `Project.get_files()`.
|
||||
# Note that ``?`` and ``*`` match all characters but slashes.
|
||||
# '*.pyc': matches 'test.pyc' and 'pkg/test.pyc'
|
||||
# 'mod*.pyc': matches 'test/mod1.pyc' but not 'mod/1.pyc'
|
||||
# '.svn': matches 'pkg/.svn' and all of its children
|
||||
# 'build/*.o': matches 'build/lib.o' but not 'build/sub/lib.o'
|
||||
# 'build//*.o': matches 'build/lib.o' and 'build/sub/lib.o'
|
||||
prefs["ignored_resources"] = [
|
||||
"*.pyc",
|
||||
"*~",
|
||||
".ropeproject",
|
||||
".hg",
|
||||
".svn",
|
||||
"_svn",
|
||||
".git",
|
||||
".tox",
|
||||
]
|
||||
|
||||
# Specifies which files should be considered python files. It is
|
||||
# useful when you have scripts inside your project. Only files
|
||||
# ending with ``.py`` are considered to be python files by
|
||||
# default.
|
||||
# prefs['python_files'] = ['*.py']
|
||||
|
||||
# Custom source folders: By default rope searches the project
|
||||
# for finding source folders (folders that should be searched
|
||||
# for finding modules). You can add paths to that list. Note
|
||||
# that rope guesses project source folders correctly most of the
|
||||
# time; use this if you have any problems.
|
||||
# The folders should be relative to project root and use '/' for
|
||||
# separating folders regardless of the platform rope is running on.
|
||||
# 'src/my_source_folder' for instance.
|
||||
# prefs.add('source_folders', 'src')
|
||||
|
||||
# You can extend python path for looking up modules
|
||||
# prefs.add('python_path', '~/python/')
|
||||
|
||||
# Should rope save object information or not.
|
||||
prefs["save_objectdb"] = True
|
||||
prefs["compress_objectdb"] = False
|
||||
|
||||
# If `True`, rope analyzes each module when it is being saved.
|
||||
prefs["automatic_soa"] = True
|
||||
# The depth of calls to follow in static object analysis
|
||||
prefs["soa_followed_calls"] = 0
|
||||
|
||||
# If `False` when running modules or unit tests "dynamic object
|
||||
# analysis" is turned off. This makes them much faster.
|
||||
prefs["perform_doa"] = True
|
||||
|
||||
# Rope can check the validity of its object DB when running.
|
||||
prefs["validate_objectdb"] = True
|
||||
|
||||
# How many undos to hold?
|
||||
prefs["max_history_items"] = 32
|
||||
|
||||
# Shows whether to save history across sessions.
|
||||
prefs["save_history"] = True
|
||||
prefs["compress_history"] = False
|
||||
|
||||
# Set the number spaces used for indenting. According to
|
||||
# :PEP:`8`, it is best to use 4 spaces. Since most of rope's
|
||||
# unit-tests use 4 spaces it is more reliable, too.
|
||||
prefs["indent_size"] = 4
|
||||
|
||||
# Builtin and c-extension modules that are allowed to be imported
|
||||
# and inspected by rope.
|
||||
prefs["extension_modules"] = []
|
||||
|
||||
# Add all standard c-extensions to extension_modules list.
|
||||
prefs["import_dynload_stdmods"] = True
|
||||
|
||||
# If `True` modules with syntax errors are considered to be empty.
|
||||
# The default value is `False`; When `False` syntax errors raise
|
||||
# `rope.base.exceptions.ModuleSyntaxError` exception.
|
||||
prefs["ignore_syntax_errors"] = False
|
||||
|
||||
# If `True`, rope ignores unresolvable imports. Otherwise, they
|
||||
# appear in the importing namespace.
|
||||
prefs["ignore_bad_imports"] = False
|
||||
|
||||
# If `True`, rope will insert new module imports as
|
||||
# `from <package> import <module>` by default.
|
||||
prefs["prefer_module_from_imports"] = False
|
||||
|
||||
# If `True`, rope will transform a comma list of imports into
|
||||
# multiple separate import statements when organizing
|
||||
# imports.
|
||||
prefs["split_imports"] = False
|
||||
|
||||
# If `True`, rope will remove all top-level import statements and
|
||||
# reinsert them at the top of the module when making changes.
|
||||
prefs["pull_imports_to_top"] = True
|
||||
|
||||
# If `True`, rope will sort imports alphabetically by module name instead
|
||||
# of alphabetically by import statement, with from imports after normal
|
||||
# imports.
|
||||
prefs["sort_imports_alphabetically"] = False
|
||||
|
||||
# Location of implementation of
|
||||
# rope.base.oi.type_hinting.interfaces.ITypeHintingFactory In general
|
||||
# case, you don't have to change this value, unless you're an rope expert.
|
||||
# Change this value to inject you own implementations of interfaces
|
||||
# listed in module rope.base.oi.type_hinting.providers.interfaces
|
||||
# For example, you can add you own providers for Django Models, or disable
|
||||
# the search type-hinting in a class hierarchy, etc.
|
||||
prefs[
|
||||
"type_hinting_factory"
|
||||
] = "rope.base.oi.type_hinting.factory.default_type_hinting_factory"
|
||||
|
||||
|
||||
def project_opened(project):
|
||||
"""This function is called after opening the project"""
|
||||
# Do whatever you like here!
|
||||
Vendored
BIN
Binary file not shown.
Vendored
+9
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"python.linting.pylintEnabled": true,
|
||||
"python.linting.enabled": true,
|
||||
"python.pythonPath": "/sf/bernina/applications/bm/envs/bernina38/bin/python",
|
||||
"[python]": {
|
||||
"editor.defaultFormatter": "ms-python.black-formatter"
|
||||
},
|
||||
"python.formatting.provider": "none"
|
||||
}
|
||||
@@ -5,17 +5,64 @@
|
||||
Experiment Control \__/\__/\___/
|
||||
|
||||
# Experiment Control
|
||||
Python based control environment for experiments, developed at SwissFEL.
|
||||
eco is a python based control environment for experiments, developed and used at SwissFEL, PSI.
|
||||
It is supposed to be used as
|
||||
- library of experimental devices for higher level python applications or GUIs
|
||||
- interactive command line interface from e.g. ipython/jupyter shell or notebook.
|
||||
|
||||
## Structure
|
||||
eco consists of mutiple python modules strucrured in main classes
|
||||
Eco follows an object oriented approach to represent devices which can be passed around as a compatibility layer in python, This should facilitate to combine devices in general control and acquisition routines as well as to develop experimental routines which take advantage of the constantly growing landscape of scientific python libraries.
|
||||
Examples for such object representation will follow in the documantation, for object-oriented programing in python also checkout online documentation like this [short introduction]{https://realpython.com/python3-object-oriented-programming/}.
|
||||
|
||||
## eco Elements
|
||||
Eco consists in general terms of
|
||||
1. conventions and examples for the behavior of general objects that allow to use them for different purposes.
|
||||
2. library modules for broadly used devices using protocols (_e.g._ epics).
|
||||
3. library modules for more specific, facility-dependent devices or logical assemblies of devices.
|
||||
4. scopes of specific configurations of devices and scope-specific code, usable e.g. in interactive mode.
|
||||
|
||||
## Package Structure
|
||||
eco consists of a hierachy of mutiple python modules.
|
||||
|
||||
At top level should be found:
|
||||
- utilities (basic and convention helpers)
|
||||
- basic devices
|
||||
- examples
|
||||
-- convention checkers
|
||||
-- utilities
|
||||
|
||||
- specific types of devices
|
||||
-- general definition of potentially recurring devices
|
||||
-- separated into different groups
|
||||
- configurations of multiple devices into instruments
|
||||
|
||||
|
||||
[Device representation.pdf](https://github.com/paulscherrerinstitute/eco/files/2453401/Device.representation.pdf)
|
||||
|
||||
# Installation
|
||||
|
||||
## Anaconda
|
||||
|
||||
The eco package is available on [anaconda.org](https://anaconda.org/paulscherrerinstitute/eco) and can be installed as follows:
|
||||
|
||||
```bash
|
||||
conda install -c paulscherrerinstitute eco
|
||||
```
|
||||
# HowTos
|
||||
|
||||
Please find in the following some general procedures when adding components in eco according to present conventions. This section can and should be dynamic, and may include outdated hint if not updated for longer.
|
||||
|
||||
## create new object ind eco
|
||||
|
||||
In order to help with naming, aliases, shell representation, new objects should be implemented as derived from `elements.Assembly` and call the parent init function with the name variable.
|
||||
```python
|
||||
from elements.assembly import Assembly
|
||||
class Myobject(Assembly):
|
||||
def __init__(self,name=None):
|
||||
super().__init__(name=name)
|
||||
```
|
||||
The `Assembly` object has different methods that help to assemble different other eco objects together.
|
||||
```python
|
||||
# in
|
||||
self._append(MySubObject,*args, **kwargs, name='mysubobjname', is_setting=True, is_display=True)
|
||||
```
|
||||
The `is_setting` flag requires that the appended object is an adjustable (can be set afterwards) or has adjustable settings in case it is itself an assembly.
|
||||
The `is_status` flag independently determines if the subobject should be used to describe the status of the new assembly, e.g. show up in its representation. In case the subobject is no adjustable itself but has adjustable settings that should be shown in the object status, please use `is_display='recursive'`.
|
||||
|
||||
Vendored
+3
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"python.formatting.provider": "black"
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
try:
|
||||
from eco.elements.protocols import Adjustable, Detector, MonitorableValueUpdate
|
||||
except:
|
||||
print("cannot import Prototypic protocol classes")
|
||||
|
||||
from eco.elements.assembly import Assembly
|
||||
|
||||
from eco import defaults
|
||||
|
||||
|
||||
+57
-14
@@ -1,22 +1,28 @@
|
||||
from bsread import Source
|
||||
from bsread.h5 import receive
|
||||
from bsread.h5 import receive, process_message_compact
|
||||
from bsread.avail import dispatcher
|
||||
import zmq
|
||||
import mflow
|
||||
import os
|
||||
import data_api as api
|
||||
import datetime
|
||||
from threading import Thread
|
||||
|
||||
from pathlib import Path
|
||||
from .utilities import Acquisition
|
||||
|
||||
|
||||
class BStools:
|
||||
def __init__(
|
||||
self, default_channel_list={"listname": []}, default_file_path="%s", elog=None
|
||||
self,
|
||||
default_channel_list={"listname": []},
|
||||
default_file_path="%s",
|
||||
elog=None,
|
||||
name=None,
|
||||
):
|
||||
self._default_file_path = default_file_path
|
||||
self._default_channel_list = default_channel_list
|
||||
self._elog = elog
|
||||
self.name = name
|
||||
|
||||
def avail(self, *args, **kwargs):
|
||||
return dispatcher.get_current_channels(*args, **kwargs)
|
||||
@@ -60,10 +66,9 @@ class BStools:
|
||||
N_pulses=None,
|
||||
default_path=True,
|
||||
queue_size=100,
|
||||
compact_format=False,
|
||||
):
|
||||
if default_path:
|
||||
fina = self._default_file_path % fina
|
||||
|
||||
N_pulses *= 1
|
||||
if os.path.isfile(fina):
|
||||
print("!!! File %s already exists, would you like to delete it?" % fina)
|
||||
if input("(y/n)") == "y":
|
||||
@@ -71,18 +76,42 @@ class BStools:
|
||||
os.remove(fina)
|
||||
else:
|
||||
return
|
||||
|
||||
path_as_path = Path(fina)
|
||||
|
||||
if not path_as_path.parent.exists():
|
||||
path_as_path.parent.mkdir()
|
||||
|
||||
if not channel_list:
|
||||
print(
|
||||
"No channels specified, using default list '%s' instead."
|
||||
% list(self._default_channel_list.keys())[0]
|
||||
)
|
||||
channel_list = self._default_channel_list[
|
||||
list(self._default_channel_list.keys())[0]
|
||||
]
|
||||
print("No channels specified, using all lists instead.")
|
||||
channel_list = []
|
||||
for tlist in self._default_channel_list.values():
|
||||
channel_list.extend(tlist)
|
||||
print(channel_list)
|
||||
if compact_format:
|
||||
message_processor = process_message_compact
|
||||
else:
|
||||
message_processor = None
|
||||
|
||||
source = dispatcher.request_stream(channel_list)
|
||||
mode = zmq.SUB
|
||||
receive(source, fina, queue_size=queue_size, mode=mode, n_messages=N_pulses)
|
||||
try:
|
||||
print(f"message proc is {message_processor}")
|
||||
receive(
|
||||
source,
|
||||
fina,
|
||||
queue_size=queue_size,
|
||||
mode=mode,
|
||||
n_messages=N_pulses,
|
||||
message_processor=message_processor,
|
||||
)
|
||||
except KeyboardInterrupt:
|
||||
# KeyboardInterrupt is thrown if the receiving is terminated via ctrl+c
|
||||
# As we don't want to see a stacktrace then catch this exception
|
||||
pass
|
||||
finally:
|
||||
print("Closing stream")
|
||||
dispatcher.remove_stream(source)
|
||||
|
||||
def db(
|
||||
self,
|
||||
@@ -134,6 +163,20 @@ class BStools:
|
||||
def acquire(self, file_name=None, Npulses=100):
|
||||
file_name += ".h5"
|
||||
|
||||
# Npulses += 100
|
||||
if self._default_file_path:
|
||||
file_name = self._default_file_path % file_name
|
||||
data_dir = Path(os.path.dirname(file_name))
|
||||
if not data_dir.exists():
|
||||
print(
|
||||
f"Path {data_dir.absolute().as_posix()} does not exist, will try to create it..."
|
||||
)
|
||||
data_dir.mkdir(parents=True)
|
||||
|
||||
print(f"Tried to create {data_dir.absolute().as_posix()}")
|
||||
data_dir.chmod(0o775)
|
||||
print(f"Tried to change permissions to 775")
|
||||
|
||||
def acquire():
|
||||
self.h5(fina=file_name, N_pulses=Npulses)
|
||||
|
||||
|
||||
@@ -0,0 +1,148 @@
|
||||
import time
|
||||
from epics import PV
|
||||
import numpy as np
|
||||
from ..elements.adjustable import AdjustableFS
|
||||
from ..epics.adjustable import AdjustablePv
|
||||
from ..epics.detector import DetectorPvDataStream
|
||||
from ..detector.detectors_psi import DetectorBsStream
|
||||
|
||||
from ..elements.assembly import Assembly
|
||||
|
||||
|
||||
class CheckerCA(Assembly):
|
||||
def __init__(
|
||||
self,
|
||||
pvname=None,
|
||||
thresholds=None,
|
||||
required_fraction=None,
|
||||
filepath_thresholds="/sf/bernina/code/gac-bernina/eco_cnf_bernina/configuration/checker_thresholds.json",
|
||||
filepath_fraction="/sf/bernina/code/gac-bernina/eco_cnf_bernina/configuration/checker_required_fraction.json",
|
||||
name=None,
|
||||
):
|
||||
super().__init__(name=name)
|
||||
self._append(DetectorPvDataStream, pvname, name="monitor")
|
||||
self._append(
|
||||
AdjustableFS,
|
||||
filepath_thresholds,
|
||||
default_value=sorted(thresholds),
|
||||
name="thresholds",
|
||||
)
|
||||
self._append(
|
||||
AdjustableFS,
|
||||
filepath_fraction,
|
||||
default_value=required_fraction,
|
||||
name="required_fraction",
|
||||
)
|
||||
|
||||
def check_now(self):
|
||||
cv = self.monitor.get_current_value()
|
||||
thresholds = self.thresholds()
|
||||
if cv > thresholds[0] and cv < thresholds[1]:
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
# def append_to_data(self, **kwargs):
|
||||
# self.data.append(kwargs["value"])
|
||||
|
||||
def clear_and_start_counting(self):
|
||||
self.monitor.accumulate_start()
|
||||
|
||||
# def stopcounting(self):
|
||||
# self.PV.clear_callbacks()
|
||||
|
||||
def stop_and_analyze(self):
|
||||
data = np.asarray(self.monitor.accumulate_stop())
|
||||
thresholds = self.thresholds()
|
||||
good = np.logical_and(data > thresholds[0], data < thresholds[1])
|
||||
fraction = good.sum() / len(good)
|
||||
isgood = fraction >= self.required_fraction()
|
||||
if not isgood:
|
||||
print(f"Checker: {fraction*100}% inside limits {self.thresholds()},")
|
||||
print(f" given limit was {self.required_fraction()*100}%.")
|
||||
return fraction >= self.required_fraction()
|
||||
|
||||
|
||||
class CheckerBS(Assembly):
|
||||
def __init__(
|
||||
self,
|
||||
bs_channel=None,
|
||||
thresholds=None,
|
||||
required_fraction=None,
|
||||
filepath_thresholds="/sf/bernina/code/gac-bernina/eco_cnf_bernina/configuration/checker_thresholds.json",
|
||||
filepath_fraction="/sf/bernina/code/gac-bernina/eco_cnf_bernina/configuration/checker_required_fraction.json",
|
||||
name=None,
|
||||
):
|
||||
super().__init__(name=name)
|
||||
self._append(DetectorBsStream, bs_channel, name="monitor")
|
||||
self._append(
|
||||
AdjustableFS,
|
||||
filepath_thresholds,
|
||||
default_value=sorted(thresholds),
|
||||
name="thresholds",
|
||||
)
|
||||
self._append(
|
||||
AdjustableFS,
|
||||
filepath_fraction,
|
||||
default_value=required_fraction,
|
||||
name="required_fraction",
|
||||
)
|
||||
|
||||
def check_now(self):
|
||||
cv = None
|
||||
while cv is None:
|
||||
cv = self.monitor.get_current_value()
|
||||
time.sleep(0.02)
|
||||
|
||||
thresholds = self.thresholds()
|
||||
if cv > thresholds[0] and cv < thresholds[1]:
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
# def append_to_data(self, **kwargs):
|
||||
# self.data.append(kwargs["value"])
|
||||
|
||||
def clear_and_start_counting(self):
|
||||
self.monitor.accumulate_start()
|
||||
|
||||
# def stopcounting(self):
|
||||
# self.PV.clear_callbacks()
|
||||
|
||||
def stop_and_analyze(self):
|
||||
try:
|
||||
data = np.asarray(self.monitor.accumulate_stop())
|
||||
thresholds = self.thresholds()
|
||||
good = np.logical_and(data > thresholds[0], data < thresholds[1])
|
||||
|
||||
fraction = np.nansum(good) / len(good)
|
||||
isgood = fraction >= self.required_fraction()
|
||||
|
||||
if not isgood:
|
||||
print(f"Checker: {fraction*100}% inside limits {self.thresholds()},")
|
||||
print(f" given limit was {self.required_fraction()*100}%.")
|
||||
except:
|
||||
return False
|
||||
return fraction >= self.required_fraction()
|
||||
|
||||
|
||||
# checker_obj = Checker_obj(checkerPV)
|
||||
|
||||
|
||||
# checker_ready = {}
|
||||
# checker_ready["checker_call"] = checker_function
|
||||
# checker_ready["args"] = [[60, 700]]
|
||||
# checker_ready["kwargs"] = {}
|
||||
# checker_ready["wait_time"] = 3
|
||||
|
||||
# checker_init = {}
|
||||
# checker_init["checker_call"] = checker_obj.clear_and_start_counting
|
||||
# checker_init["args"] = []
|
||||
# checker_init["kwargs"] = {}
|
||||
# checker_init["wait_time"] = None
|
||||
|
||||
# checker_end = {}
|
||||
# checker_end["checker_call"] = checker_obj.stop_and_analyze
|
||||
# checker_end["args"] = [[60, 700], .7]
|
||||
# checker_end["kwargs"] = {}
|
||||
# checker_end["wait_time"] = None
|
||||
@@ -0,0 +1,315 @@
|
||||
import time
|
||||
import weakref
|
||||
from eco.acquisition.utilities import Acquisition
|
||||
from eco.elements.protocols import Detector, MonitorableValueUpdate
|
||||
from collections import namedtuple
|
||||
from escape import ArrayTimestamps
|
||||
from matplotlib.animation import FuncAnimation
|
||||
import matplotlib.pyplot as plt
|
||||
from pathlib import Path
|
||||
from datetime import datetime
|
||||
from escape import DataSet
|
||||
|
||||
DEFAULT_STORAGE_DIR = Path("./")
|
||||
|
||||
StepTime = namedtuple("StepTime", "start stop")
|
||||
|
||||
|
||||
class CounterValue:
|
||||
def __init__(self, *detectors, name="value_counter"):
|
||||
self.detectors = []
|
||||
self.detector_values = []
|
||||
self.monitorables = []
|
||||
self.append_detectors(*detectors)
|
||||
self.callbacks_start_scan = [self.start_scan]
|
||||
self.callbacks_start_step = []
|
||||
self.callbacks_step_counting = []
|
||||
self.callbacks_end_step = [self.create_arrays, self.plot_arrays]
|
||||
|
||||
def stopani(scan, **kwargs):
|
||||
scan.animation.event_source.stop()
|
||||
|
||||
self.callbacks_end_scan = [
|
||||
self.create_arrays,
|
||||
self.stop_monitoring,
|
||||
self.clear_detectors,
|
||||
stopani,
|
||||
self.store_arrays,
|
||||
]
|
||||
self.name = name
|
||||
|
||||
def append_detectors(self, *detectors):
|
||||
for detector in detectors:
|
||||
if not isinstance(detector, MonitorableValueUpdate) and not isinstance(
|
||||
detector, Detector
|
||||
):
|
||||
raise TypeError(
|
||||
f"Expected Detector or MonitorableValueUpdate, got {type(detector)}"
|
||||
)
|
||||
|
||||
if (
|
||||
isinstance(detector, MonitorableValueUpdate)
|
||||
and detector not in self.monitorables
|
||||
):
|
||||
self.monitorables.append(detector)
|
||||
elif isinstance(detector, Detector) and detector not in self.detectors:
|
||||
self.detectors.append(detector)
|
||||
|
||||
# self.detectors = detectors
|
||||
|
||||
def start_scan(self, scan=None, detectors=[], **kwargs):
|
||||
self.append_detectors(*detectors)
|
||||
scan.detector_values = []
|
||||
scan.detector_names = self.get_detector_names()
|
||||
self.start_monitoring(scan=scan)
|
||||
scan.timestamp_intervals = []
|
||||
# scan.moniitorable_names = self.get_monitorable_names()
|
||||
|
||||
def start_monitoring(self, scan=None, **kwargs):
|
||||
monitors = [tm.set_current_value_callback() for tm in self.monitorables]
|
||||
for tm in monitors:
|
||||
tm.start()
|
||||
if scan is not None:
|
||||
scan.monitors = {
|
||||
tn: tm for (tn, tm) in zip(self.get_monitorable_names(), monitors)
|
||||
}
|
||||
else:
|
||||
self.monitors = monitors
|
||||
|
||||
def stop_monitoring(self, scan=None, **kwargs):
|
||||
if scan is not None:
|
||||
for tm in scan.monitors.values():
|
||||
tm.stop()
|
||||
del scan.monitors
|
||||
else:
|
||||
for tm in self.monitors:
|
||||
tm.stop()
|
||||
del self.monitors
|
||||
|
||||
def clear_detectors(self, scan, **kwargs):
|
||||
for det in self.detectors:
|
||||
tmpref = weakref.ref(det)
|
||||
del det
|
||||
if tmpref() is not None:
|
||||
print(
|
||||
f"Warning: Detector {tmpref().name} could not be deleted properly!"
|
||||
)
|
||||
|
||||
def get_monitorable_names(self):
|
||||
names = []
|
||||
for tm in self.monitorables:
|
||||
try:
|
||||
names.append(tm.alias.get_full_name())
|
||||
except:
|
||||
names.append(tm.name)
|
||||
return names
|
||||
|
||||
def get_detector_names(self):
|
||||
names = []
|
||||
for detector in self.detectors:
|
||||
try:
|
||||
names.append(detector.alias.get_full_name())
|
||||
except:
|
||||
names.append(detector.name)
|
||||
return names
|
||||
|
||||
def get_detector_values(self):
|
||||
detector_values = []
|
||||
|
||||
for detector in self.detectors:
|
||||
try:
|
||||
detector_values.append(detector.get_current_value())
|
||||
except Exception as e:
|
||||
print(f"Error getting value from {detector.name}: {e}")
|
||||
detector_values.append(None)
|
||||
return detector_values
|
||||
|
||||
def acquire(self, scan=None, collection_time=1.0, Npulses=None, **kwargs):
|
||||
if Npulses is not None:
|
||||
collection_time = Npulses
|
||||
t_start = time.time()
|
||||
acq_pars = {}
|
||||
|
||||
if scan:
|
||||
scan_wr = weakref.ref(scan)
|
||||
acq_pars = {
|
||||
"scan_info": {
|
||||
"scan_name": scan.description(),
|
||||
"scan_values": scan.values_current_step,
|
||||
"scan_readbacks": scan.readbacks_current_step,
|
||||
"name": [adj.name for adj in scan.adjustables],
|
||||
"expected_total_number_of_steps": scan.number_of_steps(),
|
||||
"scan_step_info": {
|
||||
"step_number": scan.next_step + 1,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
acquisition = Acquisition(
|
||||
acquire=None,
|
||||
acquisition_kwargs={"Npulses": Npulses},
|
||||
)
|
||||
|
||||
def acquire():
|
||||
t_tmp = time.time()
|
||||
det_val = self.get_detector_values()
|
||||
scan_wr().detector_values.append(det_val)
|
||||
time.sleep(collection_time - (time.time() - t_tmp))
|
||||
t_stop = time.time()
|
||||
scan_wr().timestamp_intervals.append(StepTime(t_tmp, t_stop))
|
||||
|
||||
acquisition.set_acquire_foo(acquire, hold=False)
|
||||
|
||||
return acquisition
|
||||
|
||||
def create_arrays(self, scan, **kwargs):
|
||||
scan.monitor_scan_arrays = {}
|
||||
for monname, mon in scan.monitors.items():
|
||||
scan.monitor_scan_arrays[monname] = ArrayTimestamps(
|
||||
data=mon.data["values"],
|
||||
timestamps=mon.data["timestamps"],
|
||||
timestamp_intervals=scan.timestamp_intervals,
|
||||
parameter=parameter_from_scan(scan),
|
||||
name=monname,
|
||||
)
|
||||
|
||||
def plot_arrays(self, scan, **kwargs):
|
||||
if not hasattr(scan, "animation"):
|
||||
|
||||
plt.close("CounterValue")
|
||||
|
||||
f, axs = plt.subplots(
|
||||
len(scan.monitor_scan_arrays), 1, sharex=True, num="CounterValue"
|
||||
)
|
||||
scan.fig = f
|
||||
if isinstance(axs, plt.Axes):
|
||||
axs = [axs]
|
||||
|
||||
def plotdat(n, *args):
|
||||
for ma, ax in zip(scan.monitor_scan_arrays.values(), axs):
|
||||
ax.cla()
|
||||
ma.scan.plot(axis=ax, fmt="o-")
|
||||
|
||||
scan.animation = FuncAnimation(
|
||||
fig=f, func=plotdat, cache_frame_data=False, interval=500
|
||||
)
|
||||
plt.show(block=False)
|
||||
else:
|
||||
scan.fig.tight_layout()
|
||||
scan.fig.canvas.draw()
|
||||
scan.fig.canvas.flush_events()
|
||||
|
||||
def store_arrays(
|
||||
self, scan, filename="auto", directory="auto", elog=None, **kwargs
|
||||
):
|
||||
|
||||
if directory == "auto":
|
||||
directory = DEFAULT_STORAGE_DIR
|
||||
if callable(directory):
|
||||
directory = directory()
|
||||
directory = Path(directory)
|
||||
if not directory.exists():
|
||||
try:
|
||||
directory.mkdir(parents=True)
|
||||
except:
|
||||
print(f"Warning: Could not create directory {directory.resolve()} !")
|
||||
|
||||
if filename == "auto":
|
||||
filename = datetime.now().strftime("%Y-%m-%d_%H:%M:%S") + ".esc.h5"
|
||||
|
||||
try:
|
||||
d = DataSet.create_with_new_result_file(
|
||||
Path(directory) / Path(filename), force_overwrite=False
|
||||
)
|
||||
names = []
|
||||
for k, v in scan.monitor_scan_arrays.items():
|
||||
names.append(k)
|
||||
d.append(v, name=k)
|
||||
v.store()
|
||||
d.results_file.close()
|
||||
scan.stored_filename = (
|
||||
(Path(directory) / Path(filename)).resolve().as_posix()
|
||||
)
|
||||
print(
|
||||
f"Stored filename {(Path(directory) / Path(filename)).resolve().as_posix()}"
|
||||
)
|
||||
|
||||
d = DataSet.load_from_result_file(Path(directory) / Path(filename))
|
||||
for name in names:
|
||||
scan.monitor_scan_arrays[name] = d.datasets[name]
|
||||
d.results_file.close()
|
||||
except:
|
||||
print("Could not create dataset file!")
|
||||
|
||||
files = []
|
||||
try:
|
||||
# import mpld3
|
||||
|
||||
plotfilename = Path(directory) / Path(
|
||||
Path(filename).stem.split(".")[0] + ".png"
|
||||
)
|
||||
scan.fig.savefig(
|
||||
plotfilename.as_posix(),
|
||||
)
|
||||
files.append(plotfilename)
|
||||
# print(plotfilename, plotfilename.as_posix())
|
||||
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
files.append(Path(directory) / Path(filename))
|
||||
|
||||
if elog:
|
||||
if elog == True:
|
||||
elog = None
|
||||
scan.status_to_elog(
|
||||
text=f"### Quick scan: {scan.description()}\nData stored in {filename}.",
|
||||
auto_title=False,
|
||||
elog=elog,
|
||||
files=files,
|
||||
)
|
||||
|
||||
# TODO
|
||||
def start(self):
|
||||
pass
|
||||
|
||||
def stop(self):
|
||||
pass
|
||||
|
||||
|
||||
def parameter_from_scan(scan):
|
||||
parameter = {
|
||||
parname: {"values": [tvs[n] for tvs in scan.scan_info["scan_values"]]}
|
||||
for n, parname in enumerate(
|
||||
scan.scan_info["scan_parameters"]["name"],
|
||||
)
|
||||
}
|
||||
return parameter
|
||||
|
||||
|
||||
# class Monitor:
|
||||
# def __init__(self, pvname, start_immediately=True):
|
||||
# self.data = {}
|
||||
# self.print = False
|
||||
# self.pv = PV(pvname)
|
||||
# self.cb_index = None
|
||||
# if start_immediately:
|
||||
# self.start_callback()
|
||||
|
||||
# def start_callback(self):
|
||||
# self.cb_index = self.pv.add_callback(self.append)
|
||||
|
||||
# def stop_callback(self):
|
||||
# self.pv.remove_callback(self.cb_index)
|
||||
|
||||
# def append(self, pvname=None, value=None, timestamp=None, **kwargs):
|
||||
# if not (pvname in self.data):
|
||||
# self.data[pvname] = []
|
||||
# ts_local = time()
|
||||
# self.data[pvname].append(
|
||||
# {"value": value, "timestamp": timestamp, "timestamp_local": ts_local}
|
||||
# )
|
||||
# if self.print:
|
||||
# print(
|
||||
# f"{pvname}: {value}; time: {timestamp}; time_local: {ts_local}; diff: {ts_local-timestamp}"
|
||||
# )
|
||||
@@ -0,0 +1,601 @@
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# likely never worked ...
|
||||
# def _wait_for_tasks(scan, **kwargs):
|
||||
# print("checking remaining tasks from previous scan ...")
|
||||
# for task in scan.remaining_tasks:
|
||||
# task.join()
|
||||
# print("... done.")
|
||||
|
||||
import json
|
||||
from pathlib import Path
|
||||
import shutil
|
||||
from threading import Thread
|
||||
|
||||
|
||||
class CounterChecker:
|
||||
def __init__(self):
|
||||
# self.
|
||||
if self.checker:
|
||||
first_check = time()
|
||||
checker_unhappy = False
|
||||
while not self.checker.check_now():
|
||||
print(
|
||||
colorama.Fore.RED
|
||||
+ f"Condition checker is not happy, waiting for OK conditions since {time()-first_check:5.1f} seconds."
|
||||
+ colorama.Fore.RESET,
|
||||
# end="\r",
|
||||
)
|
||||
sleep(self._checker_sleep_time)
|
||||
checker_unhappy = True
|
||||
if checker_unhappy:
|
||||
print(
|
||||
colorama.Fore.RED
|
||||
+ f"Condition checker was not happy and waiting for {time()-first_check:5.1f} seconds."
|
||||
+ colorama.Fore.RESET
|
||||
)
|
||||
self.checker.clear_and_start_counting()
|
||||
|
||||
class CounterStatusInitNamespaceToDAQ:
|
||||
def __init__(self, namespace=None, daq=None):
|
||||
self.namespace = namespace
|
||||
self.daq = daq
|
||||
self.callbacks_start_scan = []
|
||||
self.callbacks_end_scan = []
|
||||
self.callbacks_start_step = []
|
||||
self.callbacks_end_step = []
|
||||
|
||||
def append_start_status_to_scan(self,scan=None, append_status_info=True):
|
||||
if not append_status_info:
|
||||
return
|
||||
namespace_status = self.namespace.get_status(base=None)
|
||||
stat = {"status_run_start": namespace_status}
|
||||
scan.namespace_status = stat
|
||||
|
||||
def callback_start_step(self, scan=None, append_status_info=True):
|
||||
pass
|
||||
def callback_end_step(self, scan=None, append_status_info=True):
|
||||
pass
|
||||
def append_status_to_scan_and(self,
|
||||
scan, append_status_info=True, end_scan=True, **kwargs
|
||||
):
|
||||
if not append_status_info:
|
||||
return
|
||||
|
||||
if not len(scan.values_done)>0:
|
||||
return
|
||||
|
||||
namespace_status = self.namespace.get_status(base=None)
|
||||
scan.namespace_status["status_run_end"] = namespace_status
|
||||
if hasattr(scan, "daq_run_number"):
|
||||
runno = scan.daq_run_number
|
||||
else:
|
||||
runno = self.daq.get_last_run_number()
|
||||
|
||||
pgroup = self.daq.pgroup
|
||||
tmpdir = Path(f"/sf/bernina/data/{pgroup}/res/run_data/daq/run{runno:04d}/aux")
|
||||
tmpdir.mkdir(exist_ok=True, parents=True)
|
||||
try:
|
||||
tmpdir.chmod(0o775)
|
||||
except:
|
||||
pass
|
||||
|
||||
statusfile = tmpdir / Path("status.json")
|
||||
if not statusfile.exists():
|
||||
with open(statusfile, "w") as f:
|
||||
json.dump(scan.namespace_status, f, sort_keys=True, cls=NumpyEncoder, indent=4)
|
||||
else:
|
||||
with open(statusfile, "r+") as f:
|
||||
f.seek(0)
|
||||
json.dump(scan.namespace_status, f, sort_keys=True, cls=NumpyEncoder, indent=4)
|
||||
f.truncate()
|
||||
print("Wrote status with seek truncate!")
|
||||
if not statusfile.group() == statusfile.parent.group():
|
||||
shutil.chown(statusfile, group=statusfile.parent.group())
|
||||
|
||||
response = self.daq.append_aux(
|
||||
statusfile.resolve().as_posix(),
|
||||
pgroup=pgroup,
|
||||
run_number=runno,
|
||||
)
|
||||
print("####### transfer status #######")
|
||||
print(response.json())
|
||||
print("###############################")
|
||||
scan.scan_info["scan_parameters"]["status"] = "aux/status.json"
|
||||
|
||||
|
||||
|
||||
class CounterAliasesToDAQ:
|
||||
def __init__(self, namespace=None, daq=None):
|
||||
self.namespace = namespace
|
||||
self.daq = daq
|
||||
|
||||
def callback_start_scan(self, scan=None, append_status_info=True):
|
||||
pass
|
||||
|
||||
def callback_end_step(self, scan=None, append_status_info=True):
|
||||
pass
|
||||
|
||||
|
||||
def callback_start_step(self, scan, force=False, **kwargs):
|
||||
if force or (len(scan.values_done) == 1):
|
||||
namespace_aliases = self.namespace.alias.get_all()
|
||||
if hasattr(scan, "daq_run_number"):
|
||||
runno = scan.daq_run_number
|
||||
else:
|
||||
runno = self.daq.get_last_run_number()
|
||||
pgroup = self.daq.pgroup
|
||||
tmpdir = Path(f"/sf/bernina/data/{pgroup}/res/tmp/aliases_run{runno:04d}")
|
||||
tmpdir.mkdir(exist_ok=True, parents=True)
|
||||
try:
|
||||
tmpdir.chmod(0o775)
|
||||
except:
|
||||
pass
|
||||
aliasfile = tmpdir / Path("aliases.json")
|
||||
if not Path(aliasfile).exists():
|
||||
with open(aliasfile, "w") as f:
|
||||
json.dump(
|
||||
namespace_aliases, f, sort_keys=True, cls=NumpyEncoder, indent=4
|
||||
)
|
||||
else:
|
||||
with open(aliasfile, "r+") as f:
|
||||
f.seek(0)
|
||||
json.dump(
|
||||
namespace_aliases, f, sort_keys=True, cls=NumpyEncoder, indent=4
|
||||
)
|
||||
f.truncate()
|
||||
if not aliasfile.group() == aliasfile.parent.group():
|
||||
shutil.chown(aliasfile, group=aliasfile.parent.group())
|
||||
|
||||
scan.remaining_tasks.append(
|
||||
Thread(
|
||||
target=daq.append_aux,
|
||||
args=[aliasfile.resolve().as_posix()],
|
||||
kwargs=dict(pgroup=pgroup, run_number=runno),
|
||||
)
|
||||
)
|
||||
# DEBUG
|
||||
print(
|
||||
f"Sending scan_info_rel.json in {Path(aliasfile).parent.stem} to run number {runno}."
|
||||
)
|
||||
scan.remaining_tasks[-1].start()
|
||||
# response = daq.append_aux(
|
||||
# aliasfile.resolve().as_posix(),
|
||||
# pgroup=pgroup,
|
||||
# run_number=runno,
|
||||
# )
|
||||
print("####### transfer aliases started #######")
|
||||
# print(response.json())
|
||||
# print("################################")
|
||||
scan.scan_info["scan_parameters"]["aliases"] = "aux/aliases.json"
|
||||
|
||||
|
||||
def _message_end_scan(scan, **kwargs):
|
||||
print(f"Finished run {scan.run_number}.")
|
||||
if hasattr(scan, "daq_run_number"):
|
||||
runno_daq_saved = scan.daq_run_number
|
||||
print(f"daq_run_number is run {runno_daq_saved}.")
|
||||
|
||||
try:
|
||||
runno = daq.get_last_run_number()
|
||||
print(f"daq last run number is run {runno}.")
|
||||
except:
|
||||
pass
|
||||
|
||||
try:
|
||||
e = pyttsx3.init()
|
||||
e.say(f"Finished run {scan.run_number}.")
|
||||
e.runAndWait()
|
||||
e.stop()
|
||||
except:
|
||||
print("Audio output failed.")
|
||||
|
||||
|
||||
# def _copy_scan_info_to_raw(scan, daq=daq):
|
||||
# run_number = daq.get_last_run_number()
|
||||
# pgroup = daq.pgroup
|
||||
# print(f"Copying info file to run {run_number} to the raw directory of {pgroup}.")
|
||||
# response = daq.append_aux(
|
||||
# scan.scan_info_filename, pgroup=pgroup, run_number=run_number
|
||||
# )
|
||||
# print(f"Status: {response.json()['status']} Message: {response.json()['message']}")
|
||||
|
||||
|
||||
def _create_general_run_info(scan, daq=daq, **kwargs):
|
||||
with open(scan.scan_info_filename, "r") as f:
|
||||
si = json.load(f)
|
||||
|
||||
info = {}
|
||||
# general info, potentially automatically filled
|
||||
info["general"] = {}
|
||||
# individual data filled by daq/writers/user through api
|
||||
info["start"] = {}
|
||||
info["end"] = {}
|
||||
info["steps"] = []
|
||||
|
||||
|
||||
def _copy_scan_info_to_raw(scan, daq=daq, **kwargs):
|
||||
t_start = time.time()
|
||||
|
||||
scan.writeScanInfo()
|
||||
|
||||
# get data that should come later from api or similar.
|
||||
run_directory = list(
|
||||
Path(f"/sf/bernina/data/{daq.pgroup}/raw").glob(f"run{scan.run_number:04d}*")
|
||||
)[0].as_posix()
|
||||
# with open(scan.scan_info_filename, "r") as f:
|
||||
# si = json.load(f)
|
||||
|
||||
# Get scan info from scan
|
||||
si = scan.scan_info
|
||||
|
||||
# correct some data in there (relative paths for now)
|
||||
from os.path import relpath
|
||||
|
||||
newfiles = []
|
||||
for files in si["scan_files"]:
|
||||
newfiles.append([relpath(file, run_directory) for file in files])
|
||||
|
||||
si["scan_files"] = newfiles
|
||||
|
||||
# save temprary file and send then to raw
|
||||
if hasattr(scan, "daq_run_number"):
|
||||
runno = scan.daq_run_number
|
||||
else:
|
||||
runno = daq.get_last_run_number()
|
||||
pgroup = daq.pgroup
|
||||
tmpdir = Path(f"/sf/bernina/data/{pgroup}/res/run_data/daq/run{runno:04d}/aux")
|
||||
tmpdir.mkdir(exist_ok=True, parents=True)
|
||||
try:
|
||||
tmpdir.chmod(0o775)
|
||||
except:
|
||||
pass
|
||||
scaninfofile = tmpdir / Path("scan_info_rel.json")
|
||||
if not Path(scaninfofile).exists():
|
||||
with open(scaninfofile, "w") as f:
|
||||
json.dump(si, f, sort_keys=True, cls=NumpyEncoder, indent=4)
|
||||
else:
|
||||
with open(scaninfofile, "r+") as f:
|
||||
f.seek(0)
|
||||
json.dump(si, f, sort_keys=True, cls=NumpyEncoder, indent=4)
|
||||
f.truncate()
|
||||
if not scaninfofile.group() == scaninfofile.parent.group():
|
||||
shutil.chown(scaninfofile, group=scaninfofile.parent.group())
|
||||
# print(f"Copying info file to run {runno} to the raw directory of {pgroup}.")
|
||||
|
||||
scan.remaining_tasks.append(
|
||||
Thread(
|
||||
target=daq.append_aux,
|
||||
args=[scaninfofile.as_posix()],
|
||||
kwargs=dict(pgroup=pgroup, run_number=runno),
|
||||
)
|
||||
)
|
||||
# DEBUG
|
||||
print(
|
||||
f"Sending scan_info_rel.json in {Path(scaninfofile).parent.stem} to run number {runno}."
|
||||
)
|
||||
scan.remaining_tasks[-1].start()
|
||||
# response = daq.append_aux(scaninfofile.as_posix(), pgroup=pgroup, run_number=runno)
|
||||
# print(f"Status: {response.json()['status']} Message: {response.json()['message']}")
|
||||
# print(
|
||||
# f"--> creating and copying file took{time.time()-t_start} s, presently adding to deadtime."
|
||||
# )
|
||||
|
||||
|
||||
from eco.detector import Jungfrau
|
||||
|
||||
|
||||
def _copy_selected_JF_pedestals_to_raw(
|
||||
scan, daq=daq, copy_selected_JF_pedestals_to_raw=True, **kwargs
|
||||
):
|
||||
def copy_to_aux(daq, scan):
|
||||
if hasattr(scan, "daq_run_number"):
|
||||
runno = scan.daq_run_number
|
||||
else:
|
||||
runno = daq.get_last_run_number()
|
||||
|
||||
pgroup = daq.pgroup
|
||||
|
||||
for jf_id in daq.channels["channels_JF"]():
|
||||
jf = Jungfrau(jf_id, name="noname", pgroup_adj=config_bernina.pgroup)
|
||||
print(
|
||||
f"Copying {jf_id} pedestal to run {runno} in the raw directory of {pgroup}."
|
||||
)
|
||||
response = daq.append_aux(
|
||||
jf.get_present_pedestal_filename_in_run(intempdir=True),
|
||||
pgroup=pgroup,
|
||||
run_number=runno,
|
||||
)
|
||||
print(
|
||||
f"Status: {response.json()['status']} Message: {response.json()['message']}"
|
||||
)
|
||||
print(
|
||||
f"Copying {jf_id} gainmap to run {runno} in the raw directory of {pgroup}."
|
||||
)
|
||||
|
||||
response = daq.append_aux(
|
||||
jf.get_present_gain_filename_in_run(intempdir=True),
|
||||
pgroup=pgroup,
|
||||
run_number=runno,
|
||||
)
|
||||
print(
|
||||
f"Status: {response.json()['status']} Message: {response.json()['message']}"
|
||||
)
|
||||
|
||||
if copy_selected_JF_pedestals_to_raw:
|
||||
scan.remaining_tasks.append(Thread(target=copy_to_aux, args=[daq, scan]))
|
||||
scan.remaining_tasks[-1].start()
|
||||
|
||||
|
||||
def _increment_daq_run_number(scan, daq=daq, **kwargs):
|
||||
try:
|
||||
daq_last_run_number = daq.get_last_run_number()
|
||||
if int(scan.run_number) is int(daq_last_run_number) + 1:
|
||||
print("############ incremented ##########")
|
||||
daq_run_number = daq.get_next_run_number()
|
||||
else:
|
||||
daq_run_number = daq_last_run_number
|
||||
if int(scan.run_number) is not int(daq_run_number):
|
||||
print(
|
||||
f"Difference in run number between eco {int(scan.run_number)} and daq {int(daq_run_number)}: using run number {int(scan.run_number)}"
|
||||
)
|
||||
if int(scan.run_number) > int(daq_run_number):
|
||||
n = int(scan.run_number) - int(daq_run_number)
|
||||
print("Increasing daq run_number")
|
||||
for i in range(n):
|
||||
rn = daq.get_next_run_number()
|
||||
print(rn)
|
||||
scan.daq_run_number = rn
|
||||
else:
|
||||
scan.daq_run_number = daq_run_number
|
||||
|
||||
except Exception as e:
|
||||
print(e)
|
||||
|
||||
|
||||
class Monitor:
|
||||
def __init__(self, pvname, start_immediately=True):
|
||||
self.data = {}
|
||||
self.print = False
|
||||
self.pv = PV(pvname)
|
||||
self.cb_index = None
|
||||
if start_immediately:
|
||||
self.start_callback()
|
||||
|
||||
def start_callback(self):
|
||||
self.cb_index = self.pv.add_callback(self.append)
|
||||
|
||||
def stop_callback(self):
|
||||
self.pv.remove_callback(self.cb_index)
|
||||
|
||||
def append(self, pvname=None, value=None, timestamp=None, **kwargs):
|
||||
if not (pvname in self.data):
|
||||
self.data[pvname] = []
|
||||
ts_local = time.time()
|
||||
self.data[pvname].append(
|
||||
{"value": value, "timestamp": timestamp, "timestamp_local": ts_local}
|
||||
)
|
||||
if self.print:
|
||||
print(
|
||||
f"{pvname}: {value}; time: {timestamp}; time_local: {ts_local}; diff: {ts_local-timestamp}"
|
||||
)
|
||||
|
||||
|
||||
import traceback
|
||||
|
||||
|
||||
def append_scan_monitors(
|
||||
scan,
|
||||
daq=daq,
|
||||
custom_monitors={},
|
||||
**kwargs,
|
||||
):
|
||||
scan.monitors = {}
|
||||
for adj in scan.adjustables:
|
||||
try:
|
||||
tname = adj.alias.get_full_name()
|
||||
except Exception:
|
||||
tname = adj.name
|
||||
traceback.print_exc()
|
||||
try:
|
||||
scan.monitors[tname] = Monitor(adj.pvname)
|
||||
except Exception:
|
||||
print(f"Could not add CA monitor for {tname}")
|
||||
traceback.print_exc()
|
||||
try:
|
||||
rname = adj.readback.alias.get_full_name()
|
||||
except Exception:
|
||||
print("no readback configured")
|
||||
traceback.print_exc()
|
||||
try:
|
||||
scan.monitors[rname] = Monitor(adj.readback.pvname)
|
||||
except Exception:
|
||||
print(f"Could not add CA readback monitor for {tname}")
|
||||
traceback.print_exc()
|
||||
|
||||
for tname, tobj in custom_monitors.items():
|
||||
try:
|
||||
if type(tobj) is str:
|
||||
tmonpv = tobj
|
||||
scan.monitors[tname] = Monitor(tmonpv)
|
||||
print(f"Added custom monitor for {tname}")
|
||||
except Exception:
|
||||
print(f"Could not add custom monitor for {tname}")
|
||||
traceback.print_exc()
|
||||
try:
|
||||
tname = daq.pulse_id.alias.get_full_name()
|
||||
scan.monitors[tname] = Monitor(daq.pulse_id.pvname)
|
||||
except Exception:
|
||||
print(f"Could not add daq.pulse_id monitor")
|
||||
traceback.print_exc()
|
||||
|
||||
|
||||
def end_scan_monitors(scan, daq=daq, **kwargs):
|
||||
for tmon in scan.monitors:
|
||||
scan.monitors[tmon].stop_callback()
|
||||
|
||||
monitor_result = {tmon: scan.monitors[tmon].data for tmon in scan.monitors}
|
||||
|
||||
#######
|
||||
# get data that should come later from api or similar.
|
||||
run_directory = list(
|
||||
Path(f"/sf/bernina/data/{daq.pgroup}/raw").glob(f"run{scan.run_number:04d}*")
|
||||
)[0].as_posix()
|
||||
|
||||
# correct some data in there (relative paths for now)
|
||||
from os.path import relpath
|
||||
|
||||
# save temprary file and send then to raw
|
||||
if hasattr(scan, "daq_run_number"):
|
||||
runno = scan.daq_run_number
|
||||
else:
|
||||
runno = daq.get_last_run_number()
|
||||
pgroup = daq.pgroup
|
||||
tmpdir = Path(f"/sf/bernina/data/{pgroup}/res/tmp/info_run{runno:04d}")
|
||||
tmpdir.mkdir(exist_ok=True, parents=True)
|
||||
try:
|
||||
tmpdir.chmod(0o775)
|
||||
except:
|
||||
pass
|
||||
scanmonitorfile = tmpdir / Path("scan_monitor.pkl")
|
||||
if not Path(scanmonitorfile).exists():
|
||||
with open(scanmonitorfile, "wb") as f:
|
||||
pickle.dump(monitor_result, f)
|
||||
|
||||
print(f"Copying monitor file to run {runno} to the raw directory of {pgroup}.")
|
||||
response = daq.append_aux(
|
||||
scanmonitorfile.as_posix(), pgroup=pgroup, run_number=runno
|
||||
)
|
||||
print(f"Status: {response.json()['status']} Message: {response.json()['message']}")
|
||||
|
||||
# scan.monitors = None
|
||||
|
||||
|
||||
def _init_all(scan, append_status_info=True, **kwargs):
|
||||
if not append_status_info:
|
||||
return
|
||||
namespace.init_all(silent=False)
|
||||
|
||||
|
||||
callbacks_start_scan = []
|
||||
callbacks_start_scan.append(_init_all)
|
||||
callbacks_start_scan.append(_wait_for_tasks)
|
||||
callbacks_start_scan.append(_append_namesace_status_to_scan)
|
||||
callbacks_start_scan.append(_increment_daq_run_number)
|
||||
callbacks_start_scan.append(append_scan_monitors)
|
||||
callbacks_end_step = []
|
||||
callbacks_end_step.append(_copy_scan_info_to_raw)
|
||||
callbacks_end_step.append(_write_namespace_aliases_to_scan)
|
||||
callbacks_end_step.append(
|
||||
lambda scan, daq=daq, namespace=namespace, append_status_info=True, end_scan=True, **kwargs: _write_namespace_status_to_scan(
|
||||
scan,
|
||||
daq=daq,
|
||||
namespace=namespace,
|
||||
append_status_info=append_status_info,
|
||||
end_scan=False,
|
||||
**kwargs,
|
||||
)
|
||||
)
|
||||
callbacks_end_scan = []
|
||||
callbacks_end_scan.append(_write_namespace_status_to_scan)
|
||||
callbacks_end_scan.append(_copy_scan_info_to_raw)
|
||||
callbacks_end_scan.append(
|
||||
lambda scan, daq=daq, force=True, **kwargs: _write_namespace_aliases_to_scan(
|
||||
scan, daq=daq, force=force, **kwargs
|
||||
)
|
||||
)
|
||||
callbacks_end_scan.append(_copy_selected_JF_pedestals_to_raw)
|
||||
callbacks_end_scan.append(end_scan_monitors)
|
||||
callbacks_end_scan.append(_message_end_scan)
|
||||
|
||||
# >>>> Extract for run_table and elog
|
||||
|
||||
|
||||
# if self._run_table or self._elog:
|
||||
def _create_metadata_structure_start_scan(
|
||||
scan, run_table=run_table, elog=elog, append_status_info=True, **kwargs
|
||||
):
|
||||
runname = os.path.basename(scan.fina).split(".")[0]
|
||||
runno = int(runname.split("run")[1].split("_")[0])
|
||||
metadata = {
|
||||
"type": "scan",
|
||||
"name": runname.split("_", 1)[1],
|
||||
"scan_info_file": scan.scan_info_filename,
|
||||
}
|
||||
for n, adj in enumerate(scan.adjustables):
|
||||
nname = None
|
||||
nId = None
|
||||
if hasattr(adj, "Id"):
|
||||
nId = adj.Id
|
||||
if hasattr(adj, "name"):
|
||||
nname = adj.name
|
||||
|
||||
metadata.update(
|
||||
{
|
||||
f"scan_motor_{n}": nname,
|
||||
f"from_motor_{n}": scan.values_todo[0][n],
|
||||
f"to_motor_{n}": scan.values_todo[-1][n],
|
||||
f"id_motor_{n}": nId,
|
||||
}
|
||||
)
|
||||
if np.mean(np.diff(scan.pulses_per_step)) < 1:
|
||||
pulses_per_step = scan.pulses_per_step[0]
|
||||
else:
|
||||
pulses_per_step = scan.pulses_per_step
|
||||
metadata.update(
|
||||
{
|
||||
"steps": len(scan.values_todo),
|
||||
"pulses_per_step": pulses_per_step,
|
||||
"counters": [daq.name for daq in scan.counterCallers],
|
||||
}
|
||||
)
|
||||
|
||||
try:
|
||||
try:
|
||||
metadata.update({"scan_command": get_ipython().user_ns["In"][-1]})
|
||||
except:
|
||||
print("Count not retrieve ipython scan command!")
|
||||
|
||||
message_string = f"#### Run {runno}"
|
||||
if metadata["name"]:
|
||||
message_string += f': {metadata["name"]}\n'
|
||||
else:
|
||||
message_string += "\n"
|
||||
|
||||
if "scan_command" in metadata.keys():
|
||||
message_string += "`" + metadata["scan_command"] + "`\n"
|
||||
message_string += "`" + metadata["scan_info_file"] + "`\n"
|
||||
elog_ids = elog.post(
|
||||
message_string,
|
||||
Title=f'Run {runno}: {metadata["name"]}',
|
||||
text_encoding="markdown",
|
||||
)
|
||||
scan._elog_id = elog_ids[1]
|
||||
metadata.update({"elog_message_id": scan._elog_id})
|
||||
metadata.update(
|
||||
{"elog_post_link": scan._elog.elogs[1]._log._url + str(scan._elog_id)}
|
||||
)
|
||||
except:
|
||||
print("Elog posting failed with:")
|
||||
traceback.print_exc()
|
||||
if not append_status_info:
|
||||
return
|
||||
d = {}
|
||||
## use values from status for run_table
|
||||
try:
|
||||
d = scan.status["status_run_start"]["status"]
|
||||
except:
|
||||
print("Tranferring values from status to run_table did not work")
|
||||
t_start_rt = time.time()
|
||||
try:
|
||||
run_table.append_run(runno, metadata=metadata, d=d)
|
||||
except:
|
||||
print("WARNING: issue adding data to run table")
|
||||
print(f"RT appending: {time.time()-t_start_rt:.3f} s")
|
||||
|
||||
|
||||
# <<<< Extract for run table and elog
|
||||
callbacks_start_scan.append(_create_metadata_structure_start_scan)
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,25 @@
|
||||
import weakref
|
||||
from eco.acquisition.counters import CounterValue
|
||||
# from eco.acquisition import scan
|
||||
|
||||
# from lazy_object_proxy import Proxy as LazyProxy
|
||||
|
||||
|
||||
def scannable(Obj):
|
||||
@property
|
||||
def scans(self):
|
||||
from eco.acquisition import scan # moved import here to avoid circular import
|
||||
if hasattr(self, "_counter"):
|
||||
if not hasattr(self, "_old_counters"):
|
||||
self._old_counters = []
|
||||
self._old_counters.append(weakref.ref(self._counter))
|
||||
del self._counter
|
||||
self._counter = CounterValue(self, name=self.alias.get_full_name())
|
||||
if not hasattr(self, "_scans"):
|
||||
self._scans = scan.Scans(default_counters=[self._counter])
|
||||
else:
|
||||
self._scans.default_counters = [self._counter]
|
||||
return self._scans
|
||||
|
||||
Obj.scans = scans
|
||||
return Obj
|
||||
@@ -0,0 +1,372 @@
|
||||
from time import sleep
|
||||
from datetime import datetime
|
||||
from ..acquisition.utilities import Acquisition
|
||||
from detector_integration_api import DetectorIntegrationClient
|
||||
import os
|
||||
from pathlib import Path
|
||||
import logging
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class DIAClient:
|
||||
def __init__(
|
||||
self,
|
||||
name=None,
|
||||
instrument=None,
|
||||
pgroup=None,
|
||||
gain_path="",
|
||||
pedestal_filename="",
|
||||
pedestal_directory="",
|
||||
api_address="http://sf-daq-2:10000",
|
||||
jf_channels=[],
|
||||
n_frames_default=100,
|
||||
config_default=None,
|
||||
default_file_path=None,
|
||||
):
|
||||
if config_default:
|
||||
for cnf, cnfdict in config_default.items():
|
||||
self.__dict__[cnf + "_config"] = cnfdict
|
||||
else:
|
||||
self.writer_config = {}
|
||||
self.backend_config = {}
|
||||
self.detector_config = {}
|
||||
self.bsread_config = {}
|
||||
|
||||
self.name = name
|
||||
self._default_file_path = default_file_path
|
||||
self._api_address = api_address
|
||||
self.client = DetectorIntegrationClient(api_address)
|
||||
print("\nDetector Integration API on %s" % api_address)
|
||||
if pgroup:
|
||||
self.pgroup = int("".join([s for s in pgroup if s.isdigit()]))
|
||||
else:
|
||||
self.pgroup = None
|
||||
self.n_frames = n_frames_default
|
||||
self.jf_channels = jf_channels
|
||||
self.pede_file = pedestal_filename
|
||||
self.pedestal_directory = pedestal_directory
|
||||
self.gain_path = gain_path
|
||||
self.instrument = instrument
|
||||
if instrument is None:
|
||||
print("ERROR: please configure the instrument parameter in DIAClient")
|
||||
self.update_config()
|
||||
self.active_clients = list(self.get_active_clients()["clients_enabled"].keys())
|
||||
self.jf_channels = list(x for x in self.active_clients if x != "bsread")
|
||||
|
||||
def update_config(
|
||||
self,
|
||||
):
|
||||
# try:
|
||||
self.get_last_pedestal()
|
||||
# except:
|
||||
# print('Did not find a pedestal file in %s'%(self.pedestal_directory))
|
||||
self.writer_config.update(
|
||||
{
|
||||
"output_file": "/sf/%s/data/p%d/raw/test_data"
|
||||
% (self.instrument, self.pgroup),
|
||||
"user_id": self.pgroup,
|
||||
"n_frames": self.n_frames,
|
||||
"general/user": str(self.pgroup),
|
||||
"general/process": __name__,
|
||||
"general/created": str(datetime.now()),
|
||||
"general/instrument": self.instrument,
|
||||
}
|
||||
)
|
||||
|
||||
self.backend_config.update(
|
||||
{
|
||||
"n_frames": self.n_frames,
|
||||
"bit_depth": 16,
|
||||
"gain_corrections_filename": self.gain_path,
|
||||
# FIXME: HARDCODED!!!
|
||||
"is_HG0": False,
|
||||
}
|
||||
)
|
||||
|
||||
if self.pede_file != "":
|
||||
self.backend_config["gain_corrections_filename"] = self.gain_path
|
||||
self.backend_config["gain_corrections_dataset"] = "gains"
|
||||
self.backend_config["pede_corrections_filename"] = self.pede_file
|
||||
self.backend_config["pede_corrections_dataset"] = "gains"
|
||||
self.backend_config["pede_mask_dataset"] = "pixel_mask"
|
||||
self.backend_config["activate_corrections_preview"] = True
|
||||
else:
|
||||
self.backend_config["pede_corrections_dataset"] = "gains"
|
||||
self.backend_config["pede_mask_dataset"] = "pixel_mask"
|
||||
self.backend_config["gain_corrections_filename"] = ""
|
||||
self.backend_config["pede_corrections_filename"] = ""
|
||||
self.backend_config["activate_corrections_preview"] = False
|
||||
|
||||
self.detector_config.update(
|
||||
{
|
||||
"timing": "trigger",
|
||||
# FIXME: HARDCODED
|
||||
"exptime": 0.000_005,
|
||||
"cycles": self.n_frames,
|
||||
# "delay" : 0.001992,
|
||||
"frames": 1,
|
||||
"dr": 16,
|
||||
}
|
||||
)
|
||||
|
||||
self.bsread_config.update(
|
||||
{
|
||||
"output_file": "/sf/%s/data/p%d/raw/test_bsread"
|
||||
% (self.instrument, self.pgroup),
|
||||
"user_id": self.pgroup,
|
||||
"general/user": str(self.pgroup),
|
||||
"general/process": __name__,
|
||||
"general/created": str(datetime.now()),
|
||||
"general/instrument": self.instrument,
|
||||
}
|
||||
)
|
||||
|
||||
# self.default_channels_list = jungfrau_utils.load_default_channel_list()
|
||||
|
||||
def reset(self):
|
||||
self.client.reset()
|
||||
# pass
|
||||
|
||||
def get_status(self):
|
||||
return self.client.get_status()
|
||||
|
||||
def get_config(self):
|
||||
config = self.client.get_config()
|
||||
return config
|
||||
|
||||
def set_pgroup(self, pgroup):
|
||||
self.pgroup = pgroup
|
||||
self.update_config()
|
||||
|
||||
def set_bs_channels(
|
||||
self,
|
||||
):
|
||||
print(
|
||||
"Please update /sf/%s/config/com/channel_lists/default_channel_list and restart all services on the DAQ server"
|
||||
% self.instrument
|
||||
)
|
||||
|
||||
def set_config(self):
|
||||
self.reset()
|
||||
self.client.set_config(
|
||||
{
|
||||
"writer": self.writer_config,
|
||||
"backend": self.backend_config,
|
||||
"detector": self.detector_config,
|
||||
"bsread": self.bsread_config,
|
||||
}
|
||||
)
|
||||
|
||||
def check_still_running(self, time_interval=0.5):
|
||||
cfg = self.get_config()
|
||||
running = True
|
||||
while running:
|
||||
if not self.get_status()["status"][-7:] == "RUNNING":
|
||||
running = False
|
||||
break
|
||||
# elif not self.get_status()['status'][-20:]=='BSREAD_STILL_RUNNING':
|
||||
# running = False
|
||||
# break
|
||||
else:
|
||||
sleep(time_interval)
|
||||
|
||||
def take_pedestal(
|
||||
self,
|
||||
n_frames=1000,
|
||||
analyze=True,
|
||||
analyze_locally=False,
|
||||
n_bad_modules=0,
|
||||
freq=25,
|
||||
):
|
||||
from jungfrau_utils.scripts.jungfrau_run_pedestals import (
|
||||
run as jungfrau_utils_run,
|
||||
)
|
||||
|
||||
directory = "/sf/%s/data/p%d/raw/JF_pedestals/" % (self.instrument, self.pgroup)
|
||||
|
||||
res_dir = directory.replace("/raw/", "/res/")
|
||||
if not os.path.exists(res_dir):
|
||||
print("Directory %s not existing, creating it" % res_dir)
|
||||
os.makedirs(res_dir)
|
||||
os.chmod(res_dir, 0o775)
|
||||
filename = "pedestal_%s" % datetime.now().strftime("%Y%m%d_%H%M")
|
||||
period = 1 / freq
|
||||
jungfrau_utils_run(
|
||||
self._api_address,
|
||||
filename,
|
||||
directory,
|
||||
self.pgroup,
|
||||
period,
|
||||
self.detector_config["exptime"],
|
||||
n_frames,
|
||||
1,
|
||||
analyze_locally,
|
||||
n_bad_modules,
|
||||
self.instrument,
|
||||
)
|
||||
if analyze:
|
||||
pedestals_taken = Path(directory).glob(filename + "*")
|
||||
print(
|
||||
"Analysis of pedestal data is outsourced to batch farm, user credentials required."
|
||||
)
|
||||
user = input("enter user name for analysis on sf batch farm: ")
|
||||
commandstr = [
|
||||
f"ssh {user}@sf-cn-1 source /sf/{self.instrument}/bin/anaconda_env"
|
||||
]
|
||||
for ped in pedestals_taken:
|
||||
commandstr.append(
|
||||
f"sbatch jungfrau_create_pedestals --filename {ped.as_posix()} --directory {res_dir}"
|
||||
)
|
||||
os.system("\;".join(commandstr))
|
||||
|
||||
def get_last_pedestal(self):
|
||||
self.active_clients = list(self.get_active_clients()["clients_enabled"].keys())
|
||||
self.jf_channels = list(x for x in self.active_clients if x != "bsread")
|
||||
p = Path(self.pedestal_directory)
|
||||
allpedestals = [
|
||||
(
|
||||
datetime.strptime(
|
||||
f.stem.split("pedestal_")[1].split(".")[0], "%Y%m%d_%H%M"
|
||||
),
|
||||
f,
|
||||
)
|
||||
for f in p.glob("*.h5")
|
||||
]
|
||||
completepedestals = []
|
||||
for pedtime in set([tt for tt, tf in allpedestals]):
|
||||
tpedset = [pedtime, []]
|
||||
try:
|
||||
for channel in self.jf_channels:
|
||||
tpedset[1].append(
|
||||
[
|
||||
tf
|
||||
for tt, tf in allpedestals
|
||||
if (tt == pedtime and channel in tf.as_posix())
|
||||
][0]
|
||||
)
|
||||
if len(tpedset[1]) == len(self.jf_channels):
|
||||
completepedestals.append(tpedset)
|
||||
else:
|
||||
print(
|
||||
"Number of pedestal files %4f not number of JFs %4f"
|
||||
% (len(tpedset[1]), len(self.jf_channels))
|
||||
)
|
||||
return
|
||||
except:
|
||||
pass
|
||||
if len(completepedestals) > 0:
|
||||
f = max(completepedestals)[1][0]
|
||||
# dtim,f = max((datetime.strptime(f.stem.split('pedestal_')[1].split('.')[0],"%Y%m%d_%H%M"),f) for f in p.glob('*.h5'))
|
||||
self.pede_file = (f.parent / Path(f.stem.split(".")[0])).as_posix()
|
||||
|
||||
def start(self):
|
||||
self.client.start()
|
||||
print("start acquisition")
|
||||
pass
|
||||
|
||||
def stop(self):
|
||||
self.client.stop()
|
||||
print("stop acquisition")
|
||||
pass
|
||||
|
||||
def config_and_start_test(self):
|
||||
self.reset()
|
||||
self.set_config()
|
||||
self.start()
|
||||
pass
|
||||
|
||||
def wait_for_status(self, *args, **kwargs):
|
||||
return self.client.wait_for_status(*args, **kwargs)
|
||||
|
||||
def get_active_clients(self):
|
||||
return self.client.get_clients_enabled()
|
||||
|
||||
def acquire(self, file_name=None, Npulses=100, JF_factor=1, bsread_padding=0):
|
||||
"""
|
||||
JF_factor?
|
||||
bsread_padding?
|
||||
"""
|
||||
file_rootdir = "/sf/%s/data/p%d/raw/" % (self.instrument, self.pgroup)
|
||||
|
||||
if file_name is None:
|
||||
# FIXME /dev/null crashes the data taking (h5py can't close /dev/null and crashes)
|
||||
print("Not saving any data, as file_name is not set")
|
||||
# file_name_JF = file_rootdir + "DelMe"
|
||||
# file_name_bsread = file_rootdir + "DelMe"
|
||||
file_name_JF = "/dev/null"
|
||||
file_name_bsread = "/dev/null"
|
||||
else:
|
||||
# FIXME hardcoded
|
||||
file_name_JF = file_rootdir + file_name
|
||||
file_name_bsread = file_rootdir + file_name
|
||||
|
||||
if self.pgroup == 0:
|
||||
raise ValueError("Please use set_pgroup() to set a pgroup value.")
|
||||
|
||||
def acquire():
|
||||
self.n_frames = Npulses * JF_factor
|
||||
self.update_config()
|
||||
# self.detector_config.update({
|
||||
# 'cycles': n_frames})
|
||||
self.writer_config.update(
|
||||
{
|
||||
"output_file": file_name_JF,
|
||||
# 'n_messages': n_frames
|
||||
}
|
||||
)
|
||||
self.backend_config.update({"run_name": file_name_JF})
|
||||
# 'n_frames': n_frames})
|
||||
self.bsread_config.update(
|
||||
{
|
||||
"output_file": file_name_bsread,
|
||||
# 'Npulses': Npulses + bsread_padding
|
||||
}
|
||||
)
|
||||
|
||||
self.reset()
|
||||
self.set_config()
|
||||
# print(self.get_config())
|
||||
self.wait_for_status("IntegrationStatus.CONFIGURED")
|
||||
self.client.start()
|
||||
done = False
|
||||
|
||||
while not done:
|
||||
stat = self.get_status()
|
||||
if stat["status"] == "IntegrationStatus.FINISHED":
|
||||
done = True
|
||||
# if stat["status"] == "IntegrationStatus.BSREAD_STILL_RUNNING":
|
||||
# done = True
|
||||
# if stat["status"] == "IntegrationStatus.INITIALIZED":
|
||||
# done = True
|
||||
# if stat["status"] == "IntegrationStatus.DETECTOR_STOPPED":
|
||||
# done = True
|
||||
sleep(0.1)
|
||||
|
||||
self.client.stop()
|
||||
|
||||
outputfilenames = [
|
||||
f"{file_name_JF}.{tcli.upper()}.h5"
|
||||
for tcli in self.active_clients
|
||||
+ [
|
||||
"BSREAD.IMAGES"
|
||||
] # ['BSREAD.h5_SARES20-CAMS142-M4','BSREAD.h5_SARES20-CAMS142-M5'] # DIRTY HACK
|
||||
]
|
||||
|
||||
return Acquisition(
|
||||
acquire=acquire,
|
||||
acquisition_kwargs={"file_names": outputfilenames, "Npulses": Npulses},
|
||||
hold=False,
|
||||
)
|
||||
|
||||
def wait_done(self):
|
||||
self.check_running()
|
||||
self.check_still_running()
|
||||
|
||||
def reset_server(self, *args):
|
||||
if not args:
|
||||
args = ["all"]
|
||||
os.system(
|
||||
"ssh jf@sf-daq-3 -t -i ~/.ssh/daq3.key ./services.sh restart "
|
||||
+ " ".join(args)
|
||||
)
|
||||
@@ -0,0 +1,463 @@
|
||||
import numpy as np
|
||||
import h5py
|
||||
from epics import PV
|
||||
import os
|
||||
import datetime
|
||||
from threading import Thread
|
||||
from time import sleep
|
||||
from pathlib import Path
|
||||
from .utilities import Acquisition
|
||||
import time
|
||||
from ..elements.adjustable import AdjustableFS
|
||||
from escape import ArrayTimestamps
|
||||
|
||||
|
||||
class RunFilenameGenerator:
|
||||
def __init__(self, path, prefix="run", Ndigits=4, separator="_", suffix="json"):
|
||||
self.separator = separator
|
||||
self.prefix = prefix
|
||||
self.Ndigits = Ndigits
|
||||
self.path = Path(path)
|
||||
self.suffix = suffix
|
||||
|
||||
def get_existing_runnumbers(self):
|
||||
fl = self.path.glob(
|
||||
self.prefix + self.Ndigits * "[0-9]" + self.separator + "*." + self.suffix
|
||||
)
|
||||
fl = [tf for tf in fl if tf.is_file()]
|
||||
runnos = [
|
||||
int(tf.name.split(self.prefix)[1].split(self.separator)[0]) for tf in fl
|
||||
]
|
||||
return runnos
|
||||
|
||||
def get_run_info_file(self, runno):
|
||||
fl = self.path.glob(
|
||||
self.prefix
|
||||
+ f"{runno:0{self.Ndigits}d}"
|
||||
+ self.separator
|
||||
+ "*."
|
||||
+ self.suffix
|
||||
)
|
||||
fl = [tf for tf in fl if tf.is_file()]
|
||||
if len(fl) > 1:
|
||||
raise Exception(
|
||||
f"Found multiple files in {self.path} with run number {runno}"
|
||||
)
|
||||
return fl[0]
|
||||
|
||||
def get_nextrun_number(self):
|
||||
runnos = self.get_existing_runnumbers()
|
||||
if runnos:
|
||||
return max(runnos) + 1
|
||||
else:
|
||||
return 0
|
||||
|
||||
def get_nextrun_filename(self, name):
|
||||
runnos = self.get_existing_runnumbers()
|
||||
if runnos:
|
||||
runno = max(runnos) + 1
|
||||
else:
|
||||
runno = 0
|
||||
return (
|
||||
self.prefix
|
||||
+ "{{:0{:d}d}}".format(self.Ndigits).format(runno)
|
||||
+ self.separator
|
||||
+ name
|
||||
+ "."
|
||||
+ self.suffix
|
||||
)
|
||||
|
||||
|
||||
class EpicsDaq:
|
||||
def __init__(
|
||||
self,
|
||||
elog=None,
|
||||
name=None,
|
||||
pgroup=None,
|
||||
channel_list=None,
|
||||
default_filepath=None,
|
||||
):
|
||||
self.name = name
|
||||
self.pgroup = pgroup
|
||||
self.alternative_file_path = AdjustableFS(
|
||||
f"/sf/bernina/code/gac-bernina/eco_cnf_bernina/reference_values/{name}_alternative_file_path.json",
|
||||
default_value=False,
|
||||
name="alternative_file_path",
|
||||
)
|
||||
self._elog = elog
|
||||
self.data = []
|
||||
self.channels = {}
|
||||
self.pulse_id = PV("SLAAR11-LTIM01-EVR0:RX-PULSEID")
|
||||
self.channel_list = channel_list
|
||||
self.update_channels()
|
||||
self.callbacks_start_scan = [self.generate_run_number]
|
||||
self.callbacks_start_step = []
|
||||
self.callbacks_step_counting = []
|
||||
self.callbacks_end_step = [self.write_scan_info]
|
||||
self.callbacks_end_scan = [
|
||||
self.create_arrays,
|
||||
self.store_arrays,
|
||||
]
|
||||
|
||||
@property
|
||||
def _default_file_path(self):
|
||||
if self.alternative_file_path() == False:
|
||||
file_path = f"/sf/bernina/data/{self.pgroup()}/res/run_data/epics_daq/data/"
|
||||
else:
|
||||
file_path = self.alternative_file_path()
|
||||
return file_path
|
||||
|
||||
@_default_file_path.setter
|
||||
def _default_file_path(self, val):
|
||||
self.alternative_file_path(val)
|
||||
|
||||
def update_channels(self):
|
||||
channels = self.channel_list.get_current_value()
|
||||
for channel in channels:
|
||||
if not (channel in self.channels.keys()):
|
||||
self.channels[channel] = PV(channel, auto_monitor=True)
|
||||
|
||||
def generate_run_number(self, scan, **kwargs):
|
||||
os.glob(self._default_file_path)
|
||||
|
||||
def get_existing_runnumbers(self):
|
||||
fl = self.path.glob(
|
||||
self.prefix + self.Ndigits * "[0-9]" + self.separator + "*." + self.suffix
|
||||
)
|
||||
fl = [tf for tf in fl if tf.is_file()]
|
||||
runnos = [
|
||||
int(tf.name.split(self.prefix)[1].split(self.separator)[0]) for tf in fl
|
||||
]
|
||||
return runnos
|
||||
|
||||
channels = self.channel_list.get_current_value()
|
||||
for channel in channels:
|
||||
if not (channel in self.channels.keys()):
|
||||
self.channels[channel] = PV(channel, auto_monitor=True)
|
||||
|
||||
def write_scan_info(self, scan, **kwargs):
|
||||
if not Path(scan.scan_info_filename).exists():
|
||||
with open(scan.scan_info_filename, "w") as f:
|
||||
json.dump(scan.scan_info, f, sort_keys=True, cls=NumpyEncoder)
|
||||
else:
|
||||
with open(self.scan_info_filename, "r+") as f:
|
||||
f.seek(0)
|
||||
json.dump(self.scan_info, f, sort_keys=True, cls=NumpyEncoder)
|
||||
f.truncate()
|
||||
|
||||
def h5(self, fina=None, channel_list=None, Npulses=None, queue_size=100):
|
||||
if channel_list is None:
|
||||
channel_list = self.channel_list
|
||||
if not channel_list.get_current_value() == list(self.channels.keys()):
|
||||
self.update_channels()
|
||||
|
||||
if os.path.isfile(fina):
|
||||
print("!!! File %s already exists, would you like to delete it?" % fina)
|
||||
if input("(y/n)") == "y":
|
||||
print("Deleting %s ." % fina)
|
||||
os.remove(fina)
|
||||
else:
|
||||
return
|
||||
|
||||
data = self.get_data(channel_list=None, Npulses=None, queue_size=100)
|
||||
|
||||
f = h5py.File(name=fina, mode="w")
|
||||
for k in data.keys():
|
||||
dat = f.create_group(name=k)
|
||||
dat.create_dataset(name="data", data=data[k]["values"])
|
||||
dat.create_dataset(name="timestamps", data=data[k]["timestamps"])
|
||||
dat.create_dataset(
|
||||
name="pulse_id", data=np.arange(Npulses) + round(time.time() * 100)
|
||||
)
|
||||
return data
|
||||
|
||||
def get_data(self, channel_list=None, Npulses=None, queue_size=100, **kwargs):
|
||||
if channel_list is None:
|
||||
channel_list = self.channel_list
|
||||
if not channel_list.get_current_value() == list(self.channels.keys()):
|
||||
self.update_channels()
|
||||
|
||||
data = {}
|
||||
counters = {}
|
||||
channels = self.channels
|
||||
for k, channel in channels.items():
|
||||
channelval = channel.value
|
||||
if type(channelval) == np.ndarray:
|
||||
shape = (Npulses,) + channelval.shape
|
||||
dtype = channelval.dtype
|
||||
else:
|
||||
shape = (Npulses,)
|
||||
dtype = type(channelval)
|
||||
|
||||
data[k] = {
|
||||
"values": np.ndarray(
|
||||
shape,
|
||||
dtype=dtype,
|
||||
),
|
||||
"timestamps": np.ndarray(
|
||||
(Npulses,),
|
||||
dtype=float,
|
||||
),
|
||||
}
|
||||
|
||||
counters[k] = 0
|
||||
|
||||
def cb_getdata(ch=None, k="", *args, **kwargs):
|
||||
data[k]["values"][counters[k]] = kwargs["value"]
|
||||
data[k]["timestamps"][counters[k]] = kwargs["timestamp"]
|
||||
counters[k] = counters[k] + 1
|
||||
if counters[k] == Npulses:
|
||||
ch.clear_callbacks()
|
||||
|
||||
for k, channel in channels.items():
|
||||
channel.add_callback(callback=cb_getdata, ch=channel, k=k)
|
||||
while True:
|
||||
sleep(0.005)
|
||||
if np.mean(list(counters.values())) == Npulses:
|
||||
break
|
||||
|
||||
return data
|
||||
|
||||
# def acquire(self, Npulses=100, default_path=True, scan=None):
|
||||
# file_name = scan._description
|
||||
# file_name += ".h5"
|
||||
# if default_path:
|
||||
# file_name = self._default_file_path + file_name
|
||||
# data_dir = Path(os.path.dirname(file_name))
|
||||
|
||||
# if not data_dir.exists():
|
||||
# print(
|
||||
# f"Path {data_dir.absolute().as_posix()} does not exist, will try to create it..."
|
||||
# )
|
||||
# data_dir.mkdir(parents=True)
|
||||
# print(f"Tried to create {data_dir.absolute().as_posix()}")
|
||||
# data_dir.chmod(0o775)
|
||||
# print(f"Tried to change permissions to 775")
|
||||
#
|
||||
# def acquire():
|
||||
# self.h5(fina=file_name, Npulses=Npulses)
|
||||
|
||||
# return Acquisition(
|
||||
# acquire=acquire,
|
||||
# acquisition_kwargs={"file_names": [file_name], "Npulses": Npulses},
|
||||
# hold=False,
|
||||
# )
|
||||
|
||||
def acquire(self, scan=None, Npulses=None, **kwargs):
|
||||
acq_pars = {}
|
||||
|
||||
if scan:
|
||||
scan_wr = weakref.ref(scan)
|
||||
acq_pars = {
|
||||
"scan_info": {
|
||||
"scan_name": scan.description(),
|
||||
"scan_values": scan.values_current_step,
|
||||
"scan_readbacks": scan.readbacks_current_step,
|
||||
"name": [adj.name for adj in scan.adjustables],
|
||||
"expected_total_number_of_steps": scan.number_of_steps(),
|
||||
"scan_step_info": {
|
||||
"step_number": scan.next_step + 1,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
acquisition = Acquisition(
|
||||
acquire=None,
|
||||
acquisition_kwargs={"Npulses": Npulses},
|
||||
)
|
||||
|
||||
def acquire():
|
||||
t_tmp = time.time()
|
||||
data_step = self.get_data(Npulses)
|
||||
scan_wr().data.append(data_step)
|
||||
t_stop = time.time()
|
||||
scan_wr().timestamp_intervals.append(StepTime(t_tmp, t_stop))
|
||||
|
||||
acquisition.set_acquire_foo(acquire, hold=False)
|
||||
return acquisition
|
||||
|
||||
def create_arrays(self, scan, **kwargs):
|
||||
scan.monitor_scan_arrays = {}
|
||||
nsteps = len(scan.data)
|
||||
|
||||
for monname, mon in scan.monitors.items():
|
||||
scan.monitor_scan_arrays[monname] = ArrayTimestamps(
|
||||
data=mon.data["values"],
|
||||
timestamps=mon.data["timestamps"],
|
||||
timestamp_intervals=scan.timestamp_intervals,
|
||||
parameter=parameter_from_scan(scan),
|
||||
name=monname,
|
||||
)
|
||||
|
||||
def store_arrays(
|
||||
self, scan, filename="auto", directory="auto", elog=None, **kwargs
|
||||
):
|
||||
|
||||
if directory == "auto":
|
||||
directory = self._default_file_path
|
||||
if not directory.exists():
|
||||
try:
|
||||
directory.mkdir(parents=True)
|
||||
except:
|
||||
print(f"Warning: Could not create directory {directory.resolve()} !")
|
||||
|
||||
if filename == "auto":
|
||||
filename = datetime.now().strftime("%Y-%m-%d_%H:%M:%S") + ".esc.h5"
|
||||
|
||||
try:
|
||||
d = DataSet.create_with_new_result_file(
|
||||
Path(directory) / Path(filename), force_overwrite=False
|
||||
)
|
||||
names = []
|
||||
for k, v in scan.monitor_scan_arrays.items():
|
||||
names.append(k)
|
||||
d.append(v, name=k)
|
||||
v.store()
|
||||
d.results_file.close()
|
||||
scan.stored_filename = (
|
||||
(Path(directory) / Path(filename)).resolve().as_posix()
|
||||
)
|
||||
print(
|
||||
f"Stored filename {(Path(directory) / Path(filename)).resolve().as_posix()}"
|
||||
)
|
||||
|
||||
d = DataSet.load_from_result_file(Path(directory) / Path(filename))
|
||||
for name in names:
|
||||
scan.monitor_scan_arrays[name] = d.datasets[name]
|
||||
except:
|
||||
print("Could not create dataset file!")
|
||||
|
||||
files = []
|
||||
try:
|
||||
# import mpld3
|
||||
|
||||
plotfilename = Path(directory) / Path(
|
||||
Path(filename).stem.split(".")[0] + ".png"
|
||||
)
|
||||
scan.fig.savefig(
|
||||
plotfilename.as_posix(),
|
||||
)
|
||||
files.append(plotfilename)
|
||||
# print(plotfilename, plotfilename.as_posix())
|
||||
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
files.append(Path(directory) / Path(filename))
|
||||
|
||||
if elog:
|
||||
if elog == True:
|
||||
elog = None
|
||||
scan.status_to_elog(
|
||||
text=f"### Quick scan: {scan.description()}\nData stored in {filename}.",
|
||||
auto_title=False,
|
||||
elog=elog,
|
||||
files=files,
|
||||
)
|
||||
|
||||
def wait_done(self):
|
||||
self.check_running()
|
||||
self.check_still_running()
|
||||
|
||||
|
||||
class Epicstools:
|
||||
def __init__(
|
||||
self,
|
||||
default_channel_list={"listname": []},
|
||||
default_file_path="%s",
|
||||
elog=None,
|
||||
name=None,
|
||||
channel_list=None,
|
||||
):
|
||||
self.name = name
|
||||
self._default_file_path = default_file_path
|
||||
self._default_channel_list = default_channel_list
|
||||
self._elog = elog
|
||||
self.channels = []
|
||||
self.pulse_id = PV("SLAAR11-LTIM01-EVR0:RX-PULSEID")
|
||||
|
||||
if not channel_list:
|
||||
|
||||
print("No channels specified, using all lists instead.")
|
||||
channel_list = []
|
||||
for tlist in self._default_channel_list.values():
|
||||
channel_list.extend(tlist)
|
||||
else:
|
||||
self.channel_list = channel_list
|
||||
for channel in self.channel_list:
|
||||
self.channels.append(PV(channel, auto_monitor=True))
|
||||
|
||||
def h5(self, fina=None, channel_list=None, Npulses=None, queue_size=100):
|
||||
channel_list = self.channel_list
|
||||
|
||||
if os.path.isfile(fina):
|
||||
print("!!! File %s already exists, would you like to delete it?" % fina)
|
||||
if input("(y/n)") == "y":
|
||||
print("Deleting %s ." % fina)
|
||||
os.remove(fina)
|
||||
else:
|
||||
return
|
||||
|
||||
data = []
|
||||
counters = []
|
||||
channels = self.channels
|
||||
for channel in channels:
|
||||
channelval = channel.value
|
||||
if type(channelval) == np.ndarray:
|
||||
shape = (Npulses,) + channelval.shape
|
||||
dtype = channelval.dtype
|
||||
else:
|
||||
shape = (Npulses,)
|
||||
dtype = type(channelval)
|
||||
data.append(np.ndarray(shape, dtype=dtype))
|
||||
counters.append(0)
|
||||
|
||||
def cb_getdata(ch=None, m=0, *args, **kwargs):
|
||||
data[m][counters[m]] = kwargs["value"]
|
||||
counters[m] = counters[m] + 1
|
||||
if counters[m] == Npulses:
|
||||
ch.clear_callbacks()
|
||||
|
||||
for m, channel in enumerate(channels):
|
||||
channel.add_callback(callback=cb_getdata, ch=channel, m=m)
|
||||
while True:
|
||||
sleep(0.005)
|
||||
if np.mean(counters) == Npulses:
|
||||
break
|
||||
|
||||
f = h5py.File(name=fina, mode="w")
|
||||
for n, channel in enumerate(channel_list):
|
||||
dat = f.create_group(name=channel)
|
||||
dat.create_dataset(name="data", data=data[n])
|
||||
dat.create_dataset(
|
||||
name="pulse_id", data=np.arange(Npulses) + round(time.time() * 100)
|
||||
)
|
||||
return data
|
||||
|
||||
def acquire(self, file_name=None, Npulses=100, default_path=True):
|
||||
file_name += ".h5"
|
||||
if default_path:
|
||||
file_name = self._default_file_path + file_name
|
||||
data_dir = Path(os.path.dirname(file_name))
|
||||
|
||||
if not data_dir.exists():
|
||||
print(
|
||||
f"Path {data_dir.absolute().as_posix()} does not exist, will try to create it..."
|
||||
)
|
||||
data_dir.mkdir(parents=True)
|
||||
print(f"Tried to create {data_dir.absolute().as_posix()}")
|
||||
data_dir.chmod(0o775)
|
||||
print(f"Tried to change permissions to 775")
|
||||
|
||||
def acquire():
|
||||
self.h5(fina=file_name, Npulses=Npulses)
|
||||
|
||||
return Acquisition(
|
||||
acquire=acquire,
|
||||
acquisition_kwargs={"file_names": [file_name], "Npulses": Npulses},
|
||||
hold=False,
|
||||
)
|
||||
|
||||
def wait_done(self):
|
||||
self.check_running()
|
||||
self.check_still_running()
|
||||
@@ -1,112 +0,0 @@
|
||||
import numpy as np
|
||||
import h5py
|
||||
from epics import PV
|
||||
import os
|
||||
import data_api as api
|
||||
import datetime
|
||||
from threading import Thread
|
||||
from time import sleep
|
||||
|
||||
from .utilities import Acquisition
|
||||
|
||||
|
||||
class Ioxostools:
|
||||
def __init__(
|
||||
self,
|
||||
default_channel_list={"listname": []},
|
||||
default_file_path="%s",
|
||||
elog=None,
|
||||
sleeptime=0.0305,
|
||||
channel_list=None,
|
||||
):
|
||||
self.sleeptime = sleeptime
|
||||
self._default_file_path = default_file_path
|
||||
self._default_channel_list = default_channel_list
|
||||
self._elog = elog
|
||||
self.channels = []
|
||||
if not channel_list:
|
||||
print(
|
||||
"No channels specified, using default list '%s' instead."
|
||||
% list(self._default_channel_list.keys())[0]
|
||||
)
|
||||
self.channel_list = self._default_channel_list[
|
||||
list(self._default_channel_list.keys())[0]
|
||||
]
|
||||
else:
|
||||
self.channel_list = channel_list
|
||||
for channel in self.channel_list:
|
||||
self.channels.append(PV(channel))
|
||||
|
||||
def h5(
|
||||
self,
|
||||
fina=None,
|
||||
channel_list=None,
|
||||
N_pulses=None,
|
||||
default_path=True,
|
||||
queue_size=100,
|
||||
):
|
||||
channel_list = self.channel_list
|
||||
if default_path:
|
||||
fina = self._default_file_path % fina
|
||||
|
||||
if os.path.isfile(fina):
|
||||
print("!!! File %s already exists, would you like to delete it?" % fina)
|
||||
if input("(y/n)") == "y":
|
||||
print("Deleting %s ." % fina)
|
||||
os.remove(fina)
|
||||
else:
|
||||
return
|
||||
|
||||
data = []
|
||||
counters = []
|
||||
channels = self.channels
|
||||
|
||||
for channel in channels:
|
||||
channelval = channel.value
|
||||
if type(channelval) == np.ndarray:
|
||||
shape = (N_pulses,) + channelval.shape
|
||||
dtype = channelval.dtype
|
||||
else:
|
||||
shape = (N_pulses,)
|
||||
dtype = type(channelval)
|
||||
data.append(np.ndarray(shape, dtype=dtype))
|
||||
counters.append(0)
|
||||
|
||||
def cb_getdata(ch=None, m=0, *args, **kwargs):
|
||||
data[m][counters[m]] = kwargs["value"]
|
||||
counters[m] = counters[m] + 1
|
||||
if counters[m] == N_pulses - 1:
|
||||
ch.clear_callbacks()
|
||||
|
||||
for (m, channel) in enumerate(channels):
|
||||
channel.add_callback(callback=cb_getdata, ch=channel, m=m)
|
||||
while True:
|
||||
sleep(0.01)
|
||||
if np.mean(counters) == N_pulses - 1:
|
||||
break
|
||||
|
||||
# for n in range(N_pulses):
|
||||
# channelvals = []
|
||||
|
||||
# sleep(self.sleeptime)
|
||||
|
||||
f = h5py.File(name=fina, mode="w")
|
||||
for (n, channel) in enumerate(channel_list):
|
||||
f.create_dataset(name=channel, data=data[n])
|
||||
return data
|
||||
|
||||
def acquire(self, file_name=None, Npulses=100):
|
||||
file_name += ".h5"
|
||||
|
||||
def acquire():
|
||||
self.h5(fina=file_name, N_pulses=Npulses)
|
||||
|
||||
return Acquisition(
|
||||
acquire=acquire,
|
||||
acquisition_kwargs={"file_names": [file_name], "Npulses": Npulses},
|
||||
hold=False,
|
||||
)
|
||||
|
||||
def wait_done(self):
|
||||
self.check_running()
|
||||
self.check_still_running()
|
||||
+1161
-148
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,256 @@
|
||||
from numbers import Number
|
||||
import os
|
||||
from pathlib import Path
|
||||
from escape.swissfel import load_dataset_from_scan
|
||||
|
||||
# from eco.elements.assembly import Assembly
|
||||
import json
|
||||
|
||||
from pandas import DataFrame
|
||||
|
||||
|
||||
class RunData:
|
||||
def __init__(
|
||||
self,
|
||||
pgroup_adj,
|
||||
path_search="/sf/bernina/data/{pgroup:s}/raw",
|
||||
load_kwargs={},
|
||||
name="",
|
||||
):
|
||||
# super().__init__(name=name)
|
||||
# self._append(pgroup_adj, name="pgroup")
|
||||
self.pgroup = pgroup_adj
|
||||
self.path_search = path_search
|
||||
self.load_kwargs = load_kwargs
|
||||
self.loaded_runs = {}
|
||||
|
||||
def get_available_run_numbers(self):
|
||||
pgroup = self.pgroup.get_current_value()
|
||||
p = Path(self.path_search.format(pgroup=pgroup))
|
||||
runs = []
|
||||
for tp in p.iterdir():
|
||||
if not tp.is_dir():
|
||||
continue
|
||||
if tp.name[:3] == "run":
|
||||
numstring = tp.name.split("run")[1]
|
||||
if numstring.isdecimal():
|
||||
runs.append(int(numstring))
|
||||
runs.sort()
|
||||
return runs
|
||||
|
||||
def load_run(self, run_number, **kwargs):
|
||||
if run_number < 0:
|
||||
run_number = self.get_available_run_numbers()[run_number]
|
||||
print(f"Loading run number {run_number}")
|
||||
tkwargs = self.load_kwargs.copy()
|
||||
tkwargs.update(kwargs)
|
||||
|
||||
tks = {}
|
||||
for tk, tv in tkwargs.items():
|
||||
if type(tv) is str:
|
||||
tv = tv.format(pgroup=self.pgroup.get_current_value())
|
||||
tks[tk] = tv
|
||||
|
||||
trun = load_dataset_from_scan(
|
||||
pgroup=self.pgroup.get_current_value(), run_numbers=[run_number], **tks
|
||||
)
|
||||
|
||||
###
|
||||
# self.adjust_group()
|
||||
|
||||
self.loaded_runs[run_number] = {"dataset": trun}
|
||||
self.__setattr__(f"run{run_number:04d}", trun)
|
||||
return trun
|
||||
|
||||
# def __dir__(self):
|
||||
# l = [
|
||||
# "get_available_run_numbers",
|
||||
# "get_run",
|
||||
# "load_kwargs",
|
||||
# "load_run",
|
||||
# "loaded_runs",
|
||||
# "path_search",
|
||||
# "pgroup",
|
||||
# ]
|
||||
# # l = dir(self)
|
||||
# l += [f"run{runno:04d}" for runno in self.get_available_run_numbers()]
|
||||
# return l
|
||||
|
||||
# def __getattribute__(self, name):
|
||||
# if name in [f"run{runno:04d}" for runno in self.get_available_run_numbers()]:
|
||||
# return self.get_run(int(name.split("run")[1]))
|
||||
# else:
|
||||
# return getattr(self, name)
|
||||
|
||||
def adjust_group(self, subdir_type="scratch/.escape_parse_result"):
|
||||
os.system(
|
||||
"chgrp -R "
|
||||
+ self.pgroup.get_current_value()[1:]
|
||||
+ f" /sf/bernina/data/{self.pgroup.get_current_value()}/{subdir_type}"
|
||||
)
|
||||
|
||||
def get_run(self, run_number, **kwargs):
|
||||
if run_number < 0:
|
||||
run_number = self.get_available_run_numbers()[run_number]
|
||||
print(f"Finding run number {run_number}")
|
||||
if run_number in self.loaded_runs.keys():
|
||||
return self.loaded_runs[run_number]["dataset"]
|
||||
else:
|
||||
return self.load_run(run_number, **kwargs)
|
||||
|
||||
def __getitem__(self, run_number):
|
||||
return self.get_run(run_number)
|
||||
|
||||
def __repr__(self):
|
||||
s = "<%s.%s object at %s>" % (
|
||||
self.__class__.__module__,
|
||||
self.__class__.__name__,
|
||||
hex(id(self)),
|
||||
)
|
||||
runnos = self.get_available_run_numbers()
|
||||
s += "\n"
|
||||
s += f"{len(runnos)} available from {min(runnos)} to {max(runnos)}."
|
||||
return s
|
||||
|
||||
|
||||
STATUS_DATA = {}
|
||||
|
||||
|
||||
class StatusData:
|
||||
def __init__(
|
||||
self,
|
||||
pgroup_adj,
|
||||
path_search="/sf/bernina/data/{pgroup:s}/raw",
|
||||
status_search="aux/status.json",
|
||||
load_kwargs={},
|
||||
name="",
|
||||
):
|
||||
# super().__init__(name=name)
|
||||
# self._append(pgroup_adj, name="pgroup")
|
||||
self.pgroup = pgroup_adj
|
||||
self.path_search = path_search
|
||||
self.status_search = status_search
|
||||
self.load_kwargs = load_kwargs
|
||||
self.loaded_statii = {}
|
||||
STATUS_DATA[self.pgroup.get_current_value()] = self
|
||||
|
||||
def get_available_run_numbers(self):
|
||||
pgroup = self.pgroup.get_current_value()
|
||||
p = Path(self.path_search.format(pgroup=pgroup))
|
||||
runs = []
|
||||
for tp in p.iterdir():
|
||||
if not tp.is_dir():
|
||||
continue
|
||||
if tp.name[:3] == "run":
|
||||
numstring = tp.name.split("run")[1]
|
||||
if numstring.isdecimal():
|
||||
if (tp / Path(self.status_search)).exists():
|
||||
runs.append(int(numstring))
|
||||
runs.sort()
|
||||
return runs
|
||||
|
||||
def get_run_status(self, run_number, **kwargs):
|
||||
if run_number < 0:
|
||||
run_number = self.get_available_run_numbers()[run_number]
|
||||
print(f"Finding run number {run_number}")
|
||||
if run_number in self.loaded_statii.keys():
|
||||
return self.loaded_statii[run_number]
|
||||
else:
|
||||
return self.load_run_status(run_number, **kwargs)
|
||||
|
||||
def load_run_status(self, run_number, **kwargs):
|
||||
if run_number < 0:
|
||||
run_number = self.get_available_run_numbers()[run_number]
|
||||
print(f"Loading run number {run_number}")
|
||||
tkwargs = self.load_kwargs
|
||||
tkwargs.update(kwargs)
|
||||
|
||||
tks = {}
|
||||
for tk, tv in tkwargs.items():
|
||||
if type(tv) is str:
|
||||
tv = tv.format(pgroup=self.pgroup.get_current_value())
|
||||
tks[tk] = tv
|
||||
pgroup = self.pgroup.get_current_value()
|
||||
with open(
|
||||
Path(self.path_search.format(pgroup=pgroup))
|
||||
/ Path(f"run{run_number:04d}/aux/status.json"),
|
||||
"r",
|
||||
) as fh:
|
||||
r = json.load(fh)
|
||||
self.loaded_statii[run_number] = r
|
||||
return r
|
||||
|
||||
|
||||
def run_status_convenience(Obj):
|
||||
# if not hasattr(Obj, "alias"):
|
||||
# return Obj
|
||||
|
||||
def run_status(
|
||||
self,
|
||||
run_number=None,
|
||||
par_type="status",
|
||||
force_reload=False,
|
||||
pgroup="auto",
|
||||
status_type="status_run_start",
|
||||
as_dataframe=False,
|
||||
):
|
||||
if pgroup == "auto":
|
||||
pgroup = list(STATUS_DATA.keys())[0]
|
||||
|
||||
if run_number is None:
|
||||
return STATUS_DATA[pgroup].get_available_run_numbers()
|
||||
if isinstance(run_number, Number):
|
||||
run_number = [run_number]
|
||||
|
||||
nam = self.alias.get_full_name()
|
||||
stat = {}
|
||||
for runno in run_number:
|
||||
if runno < 0:
|
||||
runno = STATUS_DATA[pgroup].get_available_run_numbers()[runno]
|
||||
if force_reload:
|
||||
dic = STATUS_DATA[pgroup].load_run_status(runno)[status_type][par_type]
|
||||
else:
|
||||
dic = STATUS_DATA[pgroup].get_run_status(runno)[status_type][par_type]
|
||||
dic = {
|
||||
"".join(k.split(nam + ".")[1:]): v
|
||||
for k, v in dic.items()
|
||||
if k.startswith(nam)
|
||||
}
|
||||
stat[runno] = dic
|
||||
|
||||
if as_dataframe:
|
||||
return DataFrame.from_dict(stat)
|
||||
|
||||
return stat
|
||||
|
||||
Obj.run_status = run_status
|
||||
|
||||
def apply_run_settings(
|
||||
self,
|
||||
run_number=None,
|
||||
par_type="settings",
|
||||
force_reload=False,
|
||||
pgroup="auto",
|
||||
status_type="status_run_start",
|
||||
as_dataframe=False,
|
||||
**kwargs,
|
||||
):
|
||||
stat = self.run_status(
|
||||
run_number=run_number,
|
||||
par_type=par_type,
|
||||
force_reload=force_reload,
|
||||
pgroup=pgroup,
|
||||
status_type=status_type,
|
||||
as_dataframe=as_dataframe,
|
||||
)
|
||||
run_number = list(stat.keys())
|
||||
if not len(run_number) == 1:
|
||||
raise Exception("Cannot apply mutiple settings")
|
||||
run_number = run_number[0]
|
||||
stat = stat[run_number]
|
||||
|
||||
self.memory.recall(input_obj=dict(settings=stat), **kwargs)
|
||||
|
||||
Obj.apply_run_settings = apply_run_settings
|
||||
|
||||
return Obj
|
||||
@@ -0,0 +1,95 @@
|
||||
from .scan import StepScan
|
||||
import numpy as np
|
||||
from numpy.random import RandomState
|
||||
|
||||
|
||||
# class Scan:
|
||||
# def __init__(
|
||||
# self,
|
||||
# adjustables,
|
||||
# values,
|
||||
# counterCallers,
|
||||
# fina,
|
||||
# Npulses=100,
|
||||
# basepath="",
|
||||
# scan_info_dir="",
|
||||
# checker=None,
|
||||
# scan_directories=False,
|
||||
# callbackStartStep=None,
|
||||
# checker_sleep_time=0.2,
|
||||
# return_at_end="question",
|
||||
# run_table=None,
|
||||
# elog=None,
|
||||
# ):
|
||||
|
||||
|
||||
class ScanND:
|
||||
def __init__(
|
||||
self,
|
||||
adjustables,
|
||||
arrays,
|
||||
counters,
|
||||
fina,
|
||||
Npulses=100,
|
||||
basepath="",
|
||||
scan_info_dir="",
|
||||
checker=None,
|
||||
scan_directories=False,
|
||||
cb_start_scan=None,
|
||||
cb_start_step=None,
|
||||
cb_end_step=None,
|
||||
cb_end_scan=None,
|
||||
checker_sleep_time=0.2,
|
||||
return_at_end="question",
|
||||
run_table=None,
|
||||
elog=None,
|
||||
):
|
||||
scan_array = []
|
||||
scan_adjustables = []
|
||||
for n_dim, (adj_tdim, arr_tdim) in enumerate(zip(adjustables, arrays)):
|
||||
# check if the dimension is a multi adjustable thing
|
||||
try:
|
||||
iter(adj_tdim)
|
||||
if not len(adj_tdim) == len(arr_tdim):
|
||||
raise Exception(
|
||||
f"arrays and adjusbles in dimension {n_dim}don't match"
|
||||
)
|
||||
if len(set([len(sarr) for sarr in arr_tdim])) > 1:
|
||||
raise Exception(
|
||||
f"subarrays in dimension {n_dim} do not have equal length!"
|
||||
)
|
||||
scan_array.append(tuple(arr_tdim))
|
||||
scan_adjustables.append(tuple(adj_tdim))
|
||||
except TypeError:
|
||||
scan_array.append(tuple([arr_tdim]))
|
||||
scan_adjustables.append(tuple([adj_tdim]))
|
||||
self.scan_adjustables = scan_adjustables
|
||||
self.scan_array = scan_array
|
||||
self.scan_dimension = n_dim + 1
|
||||
|
||||
@property
|
||||
def steps_total(self):
|
||||
return np.prod([len(ta[0]) for ta in self.scan_array])
|
||||
|
||||
@property
|
||||
def shape(self):
|
||||
return tuple([len(ta[0]) for ta in self.scan_array])
|
||||
|
||||
def create_stepping_order(self, order="C"):
|
||||
return [
|
||||
tuple(te)
|
||||
for te in np.vstack(
|
||||
np.unravel_index(np.arange(self.steps_total), self.shape, order=order)
|
||||
).T
|
||||
]
|
||||
|
||||
def create_random_selection(
|
||||
self,
|
||||
N_elements=None,
|
||||
scan_percentage=None,
|
||||
random_type=equal,
|
||||
sort_dimensions=False,
|
||||
):
|
||||
|
||||
rs = RandomState(seed=0)
|
||||
rs.choice(a, 5, p=np.exp(-a) / sum(np.exp(-a)), replace=False)
|
||||
@@ -1,20 +1,35 @@
|
||||
from threading import Thread
|
||||
from ..utilities import PropagatingThread
|
||||
from epics import PV
|
||||
from asyncio import Future
|
||||
|
||||
|
||||
class Acquisition:
|
||||
def __init__(
|
||||
self, parent=None, acquire=None, acquisition_kwargs={}, hold=True, stopper=None
|
||||
self,
|
||||
parent=None,
|
||||
acquire=lambda: None,
|
||||
acquisition_kwargs={},
|
||||
hold=True,
|
||||
stopper=None,
|
||||
get_result=lambda: None,
|
||||
):
|
||||
self.acquisition_kwargs = acquisition_kwargs
|
||||
self.file_names = acquisition_kwargs["file_names"]
|
||||
self._acquire = acquire
|
||||
for key, val in acquisition_kwargs.items():
|
||||
self.__dict__[key] = val
|
||||
self._stopper = stopper
|
||||
self._thread = Thread(target=self._acquire)
|
||||
self._get_result = get_result
|
||||
if acquire:
|
||||
self.set_acquire_foo(acquire, hold=hold)
|
||||
|
||||
def set_acquire_foo(self, acquire, hold=True):
|
||||
self._acquire = acquire
|
||||
self._thread = PropagatingThread(target=self._acquire)
|
||||
if not hold:
|
||||
self._thread.start()
|
||||
|
||||
def wait(self):
|
||||
self._thread.join()
|
||||
return self._get_result()
|
||||
|
||||
def start(self):
|
||||
self._thread.start()
|
||||
@@ -23,10 +38,63 @@ class Acquisition:
|
||||
if self._thread.ident is None:
|
||||
return "waiting"
|
||||
else:
|
||||
if self._thread.isAlive():
|
||||
if self._thread.is_alive():
|
||||
return "acquiring"
|
||||
else:
|
||||
return "done"
|
||||
|
||||
def stop(self):
|
||||
self._stopper()
|
||||
|
||||
|
||||
def getPVchecker(pvname, config_file=None):
|
||||
checkerPV = PV(pvname)
|
||||
|
||||
def checker_function(limits):
|
||||
cv = checkerPV.get()
|
||||
if cv > limits[0] and cv < limits[1]:
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
checker = {}
|
||||
checker["checker_call"] = checker_function
|
||||
checker["args"] = [[100, 700]]
|
||||
checker["kwargs"] = {}
|
||||
checker["wait_time"] = 3
|
||||
return checker
|
||||
|
||||
|
||||
def checker_function(limits):
|
||||
cv = checkerPV.get()
|
||||
if cv > limits[0] and cv < limits[1]:
|
||||
return True
|
||||
else:
|
||||
print(f"Gas detector intensity {cv} outside limits {limits} !")
|
||||
return False
|
||||
|
||||
|
||||
class Checker_obj:
|
||||
def __init__(self, PV):
|
||||
self.PV = PV
|
||||
self.data = []
|
||||
|
||||
def append_to_data(self, **kwargs):
|
||||
self.data.append(kwargs["value"])
|
||||
|
||||
def clear_and_start_counting(self):
|
||||
self.data = []
|
||||
self.PV.add_callback(self.append_to_data)
|
||||
|
||||
def stopcounting(self):
|
||||
self.PV.clear_callbacks()
|
||||
|
||||
def stop_and_analyze(self, limits, fraction_min):
|
||||
self.stopcounting()
|
||||
data = np.asarray(self.data)
|
||||
good = np.logical_and(data > np.min(limits), data < np.max(limits))
|
||||
fraction = good.sum() / len(good)
|
||||
print(f"Gas detector intensity was {fraction*100}% inside limits {limits},")
|
||||
print(f"given limit was {fraction_min*100}%.")
|
||||
|
||||
return fraction >= fraction_min
|
||||
|
||||
@@ -1,31 +0,0 @@
|
||||
class Adjustable:
|
||||
def __init__(self, f_get_value, f_change_value, f_set_value_to=None):
|
||||
|
||||
self._f_get_value = f_get_value
|
||||
self._f_change_value = f_change_value
|
||||
self._f_set_value_to = f_set_value_to
|
||||
|
||||
|
||||
def wrap_spec_convenience(obj):
|
||||
# spec-inspired convenience methods
|
||||
def mv(self, value):
|
||||
self._currentChange = self.changeTo(value)
|
||||
|
||||
def wm(self, *args, **kwargs):
|
||||
return self.get_current_value(*args, **kwargs)
|
||||
|
||||
def mvr(self, value, *args, **kwargs):
|
||||
|
||||
if self.get_moveDone == 1:
|
||||
startvalue = self.get_current_value(readback=True, *args, **kwargs)
|
||||
else:
|
||||
startvalue = self.get_current_value(readback=False, *args, **kwargs)
|
||||
self._currentChange = self.changeTo(value + startvalue, *args, **kwargs)
|
||||
|
||||
def wait(self):
|
||||
self._currentChange.wait()
|
||||
|
||||
obj.wm = wm
|
||||
obj.mv = mv
|
||||
obj.mvr = mvr
|
||||
obj.wait = wait
|
||||
+63
-10
@@ -1,14 +1,18 @@
|
||||
import os
|
||||
from pathlib import Path
|
||||
import json
|
||||
import logging
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class Alias:
|
||||
def __init__(self, alias, channel=None, channeltype=None):
|
||||
def __init__(self, alias, channel=None, channeltype=None, parent=None):
|
||||
self.alias = alias
|
||||
self.channel = channel
|
||||
self.channeltype = channeltype
|
||||
self.children = []
|
||||
self.parent = parent
|
||||
|
||||
def append(self, subalias):
|
||||
assert type(subalias) is Alias, "You can only append aliases to aliases!"
|
||||
@@ -16,8 +20,20 @@ class Alias:
|
||||
subalias.alias in [tc.alias for tc in self.children]
|
||||
), f"Alias {subalias.alias} exists already!"
|
||||
self.children.append(subalias)
|
||||
if subalias.parent is None:
|
||||
subalias.parent = self
|
||||
else:
|
||||
print(subalias.parent)
|
||||
logger.warning(
|
||||
f"parent of alias {subalias.alias} has been defined already {subalias.parent.alias}."
|
||||
)
|
||||
|
||||
def get_all(self,joiner='.'):
|
||||
def pop_object(self, obj):
|
||||
i = self.children.index(obj)
|
||||
o = self.children.pop(i)
|
||||
o.parent = None
|
||||
|
||||
def get_all(self, joiner=".", channeltypes=None):
|
||||
aa = []
|
||||
if self.channel:
|
||||
ta = {}
|
||||
@@ -25,14 +41,42 @@ class Alias:
|
||||
ta["channel"] = self.channel
|
||||
if self.channeltype:
|
||||
ta["channeltype"] = self.channeltype
|
||||
aa.append(ta)
|
||||
if (not channeltypes) or (ta["channeltype"] in channeltypes):
|
||||
aa.append(ta)
|
||||
if self.children:
|
||||
for tc in self.children:
|
||||
taa = tc.get_all()
|
||||
for ta in taa:
|
||||
aa.append({'alias':joiner.join([self.alias,ta['alias']]),'channel':ta['channel'], 'channeltype':ta['channeltype']})
|
||||
if (not channeltypes) or (ta["channeltype"] in channeltypes):
|
||||
aa.append(
|
||||
{
|
||||
"alias": joiner.join([self.alias, ta["alias"]]),
|
||||
"channel": ta["channel"],
|
||||
"channeltype": ta["channeltype"],
|
||||
}
|
||||
)
|
||||
return aa
|
||||
|
||||
def get_full_name(self, base=None, joiner="."):
|
||||
"""allembles full name with parent names down to base (is supplied). Joiner is the separator between the hirarchical names."""
|
||||
if (not (base is None)) and (self is base.alias):
|
||||
name = []
|
||||
return ""
|
||||
else:
|
||||
name = [self.alias]
|
||||
parent = self.parent
|
||||
while not parent == None:
|
||||
if (not (base is None) and (parent is base.alias)) or (parent is None):
|
||||
break
|
||||
name.append(parent.alias)
|
||||
parent = parent.__dict__.get("parent", None)
|
||||
|
||||
if joiner:
|
||||
return joiner.join(reversed(name))
|
||||
else:
|
||||
return name
|
||||
|
||||
|
||||
# def add_children(self, *args):
|
||||
# self.children.append(find_aliases(*args))
|
||||
|
||||
@@ -47,6 +91,14 @@ def find_aliases(*args):
|
||||
return tuple(o)
|
||||
|
||||
|
||||
def append_object_to_object(obj_target, obj_init, *args, name=None, **kwargs):
|
||||
"""append a new object to another object together with the alias.
|
||||
The new object needs to be defined with a name keyword. Theadditional
|
||||
args and kwargs are the expected input for the new opject."""
|
||||
obj_target.__dict__[name] = obj_init(*args, **kwargs, name=name)
|
||||
obj_target.alias.append(obj_target.__dict__[name].alias)
|
||||
|
||||
|
||||
class Namespace:
|
||||
def __init__(self, namespace_file=None):
|
||||
path = Path(namespace_file)
|
||||
@@ -63,19 +115,19 @@ class Namespace:
|
||||
|
||||
@property
|
||||
def aliases(self):
|
||||
if not self.data:
|
||||
if self.data is None:
|
||||
self.read_file()
|
||||
return [td["alias"] for td in self.data]
|
||||
|
||||
@property
|
||||
def channels(self):
|
||||
if not self.data:
|
||||
if self.data is None:
|
||||
self.read_file()
|
||||
return [td["channel"] for td in self.data]
|
||||
|
||||
def update(self, alias, channel, channeltype):
|
||||
assert not alias in self.aliases, "Duplicate alias {alias} found!"
|
||||
assert not channel in self.channels, "Duplicate channel {channel} found!"
|
||||
assert not alias in self.aliases, f"Duplicate alias {alias} found!"
|
||||
# assert not channel in self.channels, f"Duplicate channel {channel} found!"
|
||||
self.data.append(
|
||||
{"alias": alias, "channel": channel, "channeltype": channeltype}
|
||||
)
|
||||
@@ -89,7 +141,9 @@ class Namespace:
|
||||
|
||||
def get_info(self, alias=None, channel=None):
|
||||
assert alias or channel, "Either search alias or channel needs to be defined!"
|
||||
assert not(alias and channel), "Only either search alias or channel can be defined"
|
||||
assert not (
|
||||
alias and channel
|
||||
), "Only either search alias or channel can be defined"
|
||||
if alias:
|
||||
if alias in self.aliases:
|
||||
return self.data[self.aliases.index(alias)]
|
||||
@@ -100,7 +154,6 @@ class Namespace:
|
||||
return self.data[self.channels.index(channel)]
|
||||
else:
|
||||
return None
|
||||
|
||||
|
||||
|
||||
class NamespaceCollection:
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+3
-3
@@ -14,7 +14,7 @@ aliases = {
|
||||
"alias": "slitUnd",
|
||||
"z_und": 44,
|
||||
"desc": "Slit after Undulator",
|
||||
"eco_type": "xoptics.slits.SlitFourBlades",
|
||||
"eco_type": "xoptics.slits.SlitFourBlades_old",
|
||||
},
|
||||
"SARFE10-PBIG050": {
|
||||
"alias": "gasMon",
|
||||
@@ -76,7 +76,7 @@ aliases = {
|
||||
"alias": "slitSwitch",
|
||||
"z_und": 104,
|
||||
"desc": "Slit in Optics hutch after Photon switchyard and before Alvra mono",
|
||||
"eco_type": "xoptics.slits.SlitBlades",
|
||||
"eco_type": "xoptics.slits.SlitBlades_old",
|
||||
},
|
||||
"SAROP11-ODCM105": {
|
||||
"alias": "mono",
|
||||
@@ -157,7 +157,7 @@ aliases = {
|
||||
"alias": "slitAttExp",
|
||||
"z_und": 120,
|
||||
"desc": "Slits behind attenuator",
|
||||
"eco_type": "xoptics.slits.SlitPosWidth",
|
||||
"eco_type": "xoptics.slits.SlitPosWidth_old",
|
||||
},
|
||||
"SAROP11-OLAS120": {
|
||||
"alias": "refLaser",
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
from abc import ABC, abstractmethod
|
||||
|
||||
|
||||
class Adjustable(ABC):
|
||||
@abstractmethod
|
||||
def get_current_value(self):
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def set_target_value_to(self, value):
|
||||
pass
|
||||
|
||||
|
||||
class Changer:
|
||||
def __init__(self, target=None, parent=None, changer=None, hold=True, stopper=None):
|
||||
self.target = target
|
||||
self._changer = changer
|
||||
self._stopper = stopper
|
||||
self._thread = Thread(target=self._changer, args=(target,))
|
||||
if not hold:
|
||||
self._thread.start()
|
||||
|
||||
def wait(self):
|
||||
self._thread.join()
|
||||
|
||||
def start(self):
|
||||
self._thread.start()
|
||||
|
||||
def status(self):
|
||||
if self._thread.ident is None:
|
||||
return "waiting"
|
||||
else:
|
||||
if self._thread.is_alive():
|
||||
return "changing"
|
||||
else:
|
||||
return "done"
|
||||
|
||||
def stop(self):
|
||||
self._stopper()
|
||||
@@ -1 +1,82 @@
|
||||
from .bernina import *
|
||||
|
||||
# from ..utilities.config import initFromConfigList
|
||||
# from epics import PV
|
||||
# from .. import ecocnf
|
||||
# from ..aliases import NamespaceCollection
|
||||
# import logging
|
||||
|
||||
# from .config import components, config
|
||||
# import sys
|
||||
|
||||
|
||||
# _namespace = globals()
|
||||
|
||||
# _mod = sys.modules[__name__]
|
||||
|
||||
# _scope_name = "bernina"
|
||||
|
||||
# alias_namespaces = NamespaceCollection()
|
||||
|
||||
|
||||
# # from ..utilities.runtable import Run_Table
|
||||
# # def init(pgroup, alias_namespaces, instances):
|
||||
# # run_table = Run_Table(pgroup, alias_namespaces.bernina, instances)
|
||||
# # return run_table
|
||||
# def init(*args, lazy=None):
|
||||
# if args:
|
||||
# allnames = [tc["name"] for tc in components]
|
||||
# comp_toinit = []
|
||||
# for arg in args:
|
||||
# if not arg in allnames:
|
||||
# raise Exception(f"The component {arg} has no configuration defined!")
|
||||
# else:
|
||||
# comp_toinit.append(components[allnames.index(arg)])
|
||||
# else:
|
||||
# comp_toinit = components
|
||||
|
||||
# if lazy is None:
|
||||
# lazy = ecocnf.startup_lazy
|
||||
|
||||
# op = {}
|
||||
# for key, value in initFromConfigList(comp_toinit, components, lazy=lazy).items():
|
||||
# # _namespace[key] = value
|
||||
# _mod.__dict__[key] = value
|
||||
# op[key] = value
|
||||
# if not lazy:
|
||||
# print("made here")
|
||||
# if hasattr(value, "alias"):
|
||||
# for ta in value.alias.get_all():
|
||||
# try:
|
||||
# alias_namespaces.bernina.update(
|
||||
# ta["alias"], ta["channel"], ta["channeltype"]
|
||||
# )
|
||||
# except:
|
||||
# print(f'could not init alias {ta["alias"]}')
|
||||
# else:
|
||||
# print(f"object {key} has no alias!")
|
||||
# alias_namespaces.bernina.store()
|
||||
# # try:
|
||||
# # run_table = bernina.init(config['pgroup'], alias_namespaces,_mod)
|
||||
# # _mod.__dict__['rt'] = run_table
|
||||
# # op['rt'] = run_table
|
||||
# # except:
|
||||
# # print('Initializing of run_table failed')
|
||||
# return op
|
||||
|
||||
|
||||
# def parse_for_aliases():
|
||||
# names = [tc["name"] for tc in components]
|
||||
# for name in names:
|
||||
# to = _mod.__dict__[name]
|
||||
# if hasattr(to, "alias"):
|
||||
# for ta in to.alias.get_all():
|
||||
# try:
|
||||
# globals()["alias_namespaces"].bernina.update(
|
||||
# ta["alias"], ta["channel"], ta["channeltype"]
|
||||
# )
|
||||
# except:
|
||||
# print(f'could not init alias {ta["alias"]}')
|
||||
# else:
|
||||
# print(f"object {name} has no alias!")
|
||||
# globals()["alias_namespaces"].bernina.store()
|
||||
|
||||
+2915
-10
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,87 @@
|
||||
|
||||
# new beamline startup
|
||||
|
||||
from eco import Assembly
|
||||
from eco.xoptics.attenuator_aramis import AttenuatorAramis
|
||||
|
||||
|
||||
namespace.append_obj(
|
||||
"Att_usd",
|
||||
name="att_usd",
|
||||
module_name="eco.xoptics.att_usd",
|
||||
xp=xp,
|
||||
lazy=True,
|
||||
)
|
||||
|
||||
namespace.append_obj(
|
||||
"SlitPosWidth",
|
||||
"SAROP21-OAPU138",
|
||||
name="slit_att",
|
||||
lazy=True,
|
||||
module_name="eco.xoptics.slits",
|
||||
),
|
||||
|
||||
namespace.append_obj(
|
||||
"JJSlitUnd",
|
||||
name="slit_und",
|
||||
module_name="eco.xoptics.slits",
|
||||
lazy=True,
|
||||
)
|
||||
namespace.append_obj(
|
||||
"SlitBlades",
|
||||
"SAROP21-OAPU092",
|
||||
name="slit_switch",
|
||||
module_name="eco.xoptics.slits",
|
||||
lazy=True,
|
||||
)
|
||||
namespace.append_obj(
|
||||
"SlitBlades",
|
||||
"SAROP21-OAPU102",
|
||||
name="slit_mono",
|
||||
module_name="eco.xoptics.slits",
|
||||
lazy=True,
|
||||
)
|
||||
|
||||
|
||||
{
|
||||
"name": "pshut_und",
|
||||
"type": "eco.xoptics.shutters:PhotonShutter",
|
||||
"args": ["SARFE10-OPSH044:REQUEST"],
|
||||
"kwargs": {},
|
||||
"z_und": 44,
|
||||
"desc": "First shutter after Undulators",
|
||||
},
|
||||
|
||||
{
|
||||
"name": "xp",
|
||||
"args": [],
|
||||
"kwargs": {
|
||||
"Id": "SAROP21-OPPI113",
|
||||
"evronoff": "SGE-CPCW-72-EVR0:FrontUnivOut15-Ena-SP",
|
||||
"evrsrc": "SGE-CPCW-72-EVR0:FrontUnivOut15-Src-SP",
|
||||
},
|
||||
"z_und": 103,
|
||||
"desc": "X-ray pulse picker",
|
||||
"type": "eco.xoptics.pp:Pulsepick",
|
||||
},
|
||||
|
||||
|
||||
{
|
||||
"name": "att_fe",
|
||||
"type": "eco.xoptics.attenuator_aramis:AttenuatorAramis",
|
||||
"args": ["SARFE10-OATT053"],
|
||||
"kwargs": {"shutter": Component("pshut_und")},
|
||||
"z_und": 53,
|
||||
"desc": "Attenuator in Front End",
|
||||
},
|
||||
|
||||
|
||||
class AttenuationFELBernina(Assembly):
|
||||
def __init__(self, name=None):
|
||||
super().__init__(name=name)
|
||||
self._append(
|
||||
AttenuatorAramis, "SAROP21-OATT135", set_limits=[], name="opt", shutter=None
|
||||
)
|
||||
self._append(
|
||||
AttenuatorAramis, "SARFE10-OATT053", set_limits=[], name="fe", shutter=None
|
||||
)
|
||||
@@ -0,0 +1,313 @@
|
||||
import numpy as np
|
||||
from eco import Assembly
|
||||
from eco.devices_general.motors import MotorRecord
|
||||
from eco.devices_general.cameras_swissfel import CameraBasler
|
||||
from eco.epics.adjustable import AdjustablePv
|
||||
from eco.microscopes import MicroscopeMotorRecord
|
||||
from eco.devices_general.powersockets import MpodModule, MpodChannel
|
||||
from eco.detector import Jungfrau
|
||||
from eco.devices_general.wago import AnalogOutput
|
||||
from eco.elements.adjustable import AdjustableFS, AdjustableVirtual
|
||||
from eco.elements.detector import DetectorGet
|
||||
from eco.devices_general.pipelines_swissfel import Pipeline
|
||||
from eco.devices_general.pv_adjustable import PvRecord
|
||||
from eco.devices_general.motors import ThorlabsPiezoRecord
|
||||
|
||||
|
||||
class LiquidJetSpectroscopy(Assembly):
|
||||
def __init__(
|
||||
self, pgroup_adj=None, config_JF_adj=None, name=None, v_g=None, e2v=None
|
||||
):
|
||||
super().__init__(name=name)
|
||||
self._append(
|
||||
MpodChannel,
|
||||
pvbase="SARES21-PS7071",
|
||||
channel_number=4,
|
||||
name="illumination_inline",
|
||||
)
|
||||
# self._append(
|
||||
# MpodChannel,
|
||||
# pvbase="SARES21-PS7071",
|
||||
# channel_number=2,
|
||||
# name="illumination_side",
|
||||
# )
|
||||
|
||||
self._append(
|
||||
CameraBasler,
|
||||
# pvname_camera="SARES20-CAMS142-M3", #THC
|
||||
"SARES20-CAMS142-C2", # GIC
|
||||
name="jetcam_inline",
|
||||
)
|
||||
self._append(Pipeline, "SARES20-CAMS142-C1_fb", name="pipeline_fb")
|
||||
# this is the large camera
|
||||
|
||||
self._append(
|
||||
MicroscopeMotorRecord,
|
||||
pvname_camera="SARES20-CAMS142-C1", # GIC
|
||||
pvname_zoom="SARES20-MF1:MOT_14",
|
||||
name="jetcam_top",
|
||||
)
|
||||
# self.jetcam_top._append(Pipeline, "SARES20-CAMS142-C1_fb", name="pipeline_fb")
|
||||
|
||||
self._append(
|
||||
CameraBasler,
|
||||
# pvname_camera="SARES20-CAMS142-M3", #THC
|
||||
"SARES20-CAMS142-M1", # GIC
|
||||
name="jetcam_back",
|
||||
)
|
||||
# jetcam_top._append(Pipeline,'SARES20-CAMS142-C1_fb',name="pipeline_fb")
|
||||
|
||||
self._v_g = v_g
|
||||
self._e2v = e2v
|
||||
self._append(
|
||||
MotorRecord,
|
||||
"SARES20-MF1:MOT_5",
|
||||
name="x",
|
||||
backlash_definition=True,
|
||||
is_setting=True,
|
||||
)
|
||||
self._append(
|
||||
MotorRecord,
|
||||
"SARES20-MF1:MOT_6",
|
||||
name="y",
|
||||
backlash_definition=True,
|
||||
is_setting=True,
|
||||
)
|
||||
self._append(
|
||||
MotorRecord,
|
||||
"SARES20-MF1:MOT_7",
|
||||
name="z",
|
||||
backlash_definition=True,
|
||||
is_setting=True,
|
||||
)
|
||||
self._append(
|
||||
MpodChannel,
|
||||
pvbase="SARES21-PS7071",
|
||||
channel_number=4,
|
||||
name="light",
|
||||
)
|
||||
self._append(
|
||||
MotorRecord,
|
||||
"SARES20-MF1:MOT_2",
|
||||
name="dist_vHamos",
|
||||
backlash_definition=True,
|
||||
is_setting=True,
|
||||
)
|
||||
# self._append(
|
||||
# MotorRecord,y=True,
|
||||
# "SARES20-MF1:MOT_3",
|
||||
# name="x_analyzer",
|
||||
# backlash_definition=True,
|
||||
# is_setting=True,
|
||||
# )
|
||||
# self._append(
|
||||
# MotorRecord,
|
||||
# "SARES21-XRD:MOT_P_T",
|
||||
# name="y_vhdet",
|
||||
# is_setting=True,
|
||||
#
|
||||
self._append(
|
||||
Jungfrau,
|
||||
"JF03T01V02",
|
||||
name="det_totem",
|
||||
pgroup_adj=pgroup_adj,
|
||||
config_adj=config_JF_adj,
|
||||
)
|
||||
self._append(
|
||||
Jungfrau,
|
||||
"JF14T01V01",
|
||||
name="det_vhamos",
|
||||
pgroup_adj=pgroup_adj,
|
||||
config_adj=config_JF_adj,
|
||||
)
|
||||
self._append(
|
||||
MpodChannel,
|
||||
pvbase="SARES21-PS7071",
|
||||
module_string="HV_EHS_3",
|
||||
channel_number=1,
|
||||
name="apd",
|
||||
)
|
||||
self._append(
|
||||
AdjustableFS,
|
||||
"/sf/bernina/code/gac-bernina/eco_cnf_bernina/configuration/apd_voltage_calibration.json",
|
||||
name="apd_voltage_calibration",
|
||||
is_display=False,
|
||||
is_setting=True,
|
||||
)
|
||||
|
||||
# Convert energy - voltage using calibration
|
||||
def ene2volt(energy):
|
||||
try:
|
||||
E, V = np.asarray(self.apd_voltage_calibration()).T
|
||||
return np.interp(energy, E, V)
|
||||
except:
|
||||
return np.nan
|
||||
|
||||
# Read the APD voltage and return it as the virtual value
|
||||
def get_voltage(apd_voltage):
|
||||
return self.apd.voltage.get_current_value()
|
||||
|
||||
# compute voltage from energy and set it
|
||||
def set_voltage(target_energy):
|
||||
voltage = ene2volt(target_energy)
|
||||
self.apd.voltage.set_target_value(voltage)
|
||||
return voltage
|
||||
|
||||
# Create virtual adjustable:
|
||||
self._append(
|
||||
AdjustableVirtual,
|
||||
[self.apd.voltage],
|
||||
get_voltage,
|
||||
set_voltage,
|
||||
reset_current_value_to=False,
|
||||
name="ene2volt",
|
||||
is_display=True,
|
||||
is_setting=True,
|
||||
)
|
||||
|
||||
# Feedback adjustables
|
||||
self._append(
|
||||
AdjustablePv,
|
||||
pvsetname="SARES20-FEEDBACK-SAMPLE:TARGET",
|
||||
name="feedback_setpoint",
|
||||
)
|
||||
self._append(
|
||||
AdjustablePv,
|
||||
pvsetname="SARES20-FEEDBACK-SAMPLE:ENABLE",
|
||||
name="feedback_enabled",
|
||||
)
|
||||
|
||||
|
||||
class SaxsSpectrometer(Assembly):
|
||||
def __init__(
|
||||
self,
|
||||
pv_xgc="SARES20-MF1:MOT_4",
|
||||
pv_rana="SARES20-MF1:MOT_3",
|
||||
jf_id="JF03T01V02",
|
||||
config_jf_adj=None,
|
||||
pgroup_adj=None,
|
||||
name="xspec_gc",
|
||||
):
|
||||
super().__init__(name=name),
|
||||
self._append(
|
||||
MotorRecord,
|
||||
pv_xgc,
|
||||
name="x_gc",
|
||||
)
|
||||
self._append(
|
||||
MotorRecord,
|
||||
pv_rana,
|
||||
name="r_ana",
|
||||
)
|
||||
self._append(
|
||||
Jungfrau,
|
||||
jf_id,
|
||||
pgroup_adj=pgroup_adj,
|
||||
config_adj=config_jf_adj,
|
||||
name="detector",
|
||||
)
|
||||
|
||||
|
||||
class LinearFresnelZonePlate(Assembly):
|
||||
def __init__(
|
||||
self,
|
||||
name=None,
|
||||
):
|
||||
super().__init__(name=name)
|
||||
self._append(
|
||||
MpodChannel,
|
||||
pvbase="SARES21-PS7071",
|
||||
channel_number=4,
|
||||
name="light",
|
||||
)
|
||||
self.motor_configuration_thorlabs = {
|
||||
"hwp_mon": {
|
||||
"pvname": "SLAAR21-LMOT-ELL1",
|
||||
},
|
||||
"hwp_pump": {
|
||||
"pvname": "SLAAR21-LMOT-ELL5",
|
||||
},
|
||||
}
|
||||
|
||||
### thorlabs piezo motors ###
|
||||
for name, config in self.motor_configuration_thorlabs.items():
|
||||
self._append(
|
||||
ThorlabsPiezoRecord,
|
||||
pvname=config["pvname"],
|
||||
name=name,
|
||||
is_setting=True,
|
||||
)
|
||||
|
||||
# self._append(
|
||||
# CameraBasler,
|
||||
# # pvname_camera="SARES20-CAMS142-M3", #THC
|
||||
# "SARES20-CAMS142-C2", # GIC
|
||||
# name="cam_inline",
|
||||
# )
|
||||
|
||||
# self._append(
|
||||
# MicroscopeMotorRecord,
|
||||
# pvname_camera="SARES20-CAMS142-C1", # GIC
|
||||
# pvname_zoom="SARES20-MF1:MOT_14",
|
||||
# name="cam_top",
|
||||
# )
|
||||
|
||||
self._append(
|
||||
MotorRecord,
|
||||
"SARES20-MCS1:MOT_8",
|
||||
name="rot",
|
||||
is_setting=True,
|
||||
is_display=True,
|
||||
)
|
||||
|
||||
self._append(
|
||||
MotorRecord,
|
||||
"SARES20-MCS1:MOT_3",
|
||||
name="tilt",
|
||||
is_setting=True,
|
||||
is_display=True,
|
||||
)
|
||||
|
||||
self._append(
|
||||
MotorRecord,
|
||||
"SARES20-MCS3:MOT_6",
|
||||
name="x",
|
||||
is_setting=True,
|
||||
is_display=True,
|
||||
)
|
||||
self._append(
|
||||
MotorRecord,
|
||||
"SARES20-XPS1:MOT_2",
|
||||
name="y",
|
||||
is_setting=True,
|
||||
is_display=True,
|
||||
)
|
||||
self._append(
|
||||
MotorRecord,
|
||||
"SARES20-MCS3:MOT_4",
|
||||
name="z",
|
||||
is_setting=True,
|
||||
is_display=True,
|
||||
)
|
||||
self._append(
|
||||
MotorRecord,
|
||||
"SARES20-XPS1:MOT_1",
|
||||
name="foc",
|
||||
is_setting=True,
|
||||
is_display=True,
|
||||
)
|
||||
|
||||
self._append(
|
||||
MotorRecord,
|
||||
"SARES20-XPS1:MOT_3",
|
||||
name="beam_stop_y",
|
||||
is_setting=True,
|
||||
is_display=True,
|
||||
)
|
||||
self._append(
|
||||
MotorRecord,
|
||||
"SARES20-MCS1:MOT_1",
|
||||
name="i0_pos",
|
||||
is_setting=True,
|
||||
is_display=True,
|
||||
)
|
||||
+469
-299
@@ -11,309 +11,479 @@
|
||||
# if arg or kwarg is of type eco.utilities.Component (dummy class)
|
||||
# this indicates that an earlier initialized object is used
|
||||
# (e.g. from same configuration).
|
||||
from ..utilities.config import Component, Alias, init_device, initFromConfigList
|
||||
from ..utilities.config import (
|
||||
Component,
|
||||
init_device,
|
||||
initFromConfigList,
|
||||
Configuration,
|
||||
)
|
||||
|
||||
_eco_lazy_init = False
|
||||
|
||||
config = Configuration(
|
||||
"/sf/bernina/code/gac-bernina/eco_cnf_bernina/bernina_config_eco.json", name="bernina_config"
|
||||
)
|
||||
|
||||
components = [
|
||||
# {
|
||||
# 'name' : 'device_alias_name',
|
||||
# 'type' : 'package.module.submodule:ClassOrFactory',
|
||||
# 'args' : ['all','the','requires','args'],
|
||||
# 'kwargs': {}
|
||||
# }
|
||||
{
|
||||
"name": "elog",
|
||||
"type": "eco.utilities.elog:Elog",
|
||||
"args": ["https://elog-gfa.psi.ch/Bernina"],
|
||||
"kwargs": {
|
||||
"user": "gac-bernina",
|
||||
"screenshot_directory": "/sf/bernina/config/screenshots",
|
||||
},
|
||||
},
|
||||
{
|
||||
"name": "screenshot",
|
||||
"type": "eco.utilities.elog:Screenshot",
|
||||
"args": [],
|
||||
"kwargs": {"screenshot_directory": "/sf/bernina/config/screenshots"},
|
||||
},
|
||||
{
|
||||
"name": "slitUnd",
|
||||
"type": "eco.xoptics.slits:SlitFourBlades",
|
||||
"args": ["SARFE10-OAPU044"],
|
||||
"kwargs": {},
|
||||
"desc": "Slit after Undulator",
|
||||
},
|
||||
{
|
||||
"name": "attFE",
|
||||
"type": "eco.xoptics.attenuator_aramis:AttenuatorAramis",
|
||||
"args": ["SARFE10-OATT053"],
|
||||
"kwargs": {},
|
||||
"desc": "Attenuator in Front End",
|
||||
},
|
||||
{
|
||||
"name": "profFE",
|
||||
"args": ["SARFE10-PPRM064"],
|
||||
"kwargs": {},
|
||||
"z_und": 64,
|
||||
"desc": "Profile monitor after Front End",
|
||||
"type": "eco.xdiagnostics.profile_monitors:Pprm",
|
||||
},
|
||||
{
|
||||
"name": "profMirrAlv1",
|
||||
"args": ["SAROP11-PPRM066"],
|
||||
"kwargs": {},
|
||||
"z_und": 66,
|
||||
"desc": "Profile monitor after Alvra Mirror 1",
|
||||
"type": "eco.xdiagnostics.profile_monitors:Pprm",
|
||||
},
|
||||
{
|
||||
"name": "slitSwitch",
|
||||
"z_und": 92,
|
||||
"desc": "Slit in Optics hutch after Photon switchyard and before Bernina optics",
|
||||
"type": "eco.xoptics.slits:SlitBlades",
|
||||
"args": ["SAROP21-OAPU092"],
|
||||
"kwargs": {},
|
||||
},
|
||||
{
|
||||
"name": "profMirr1",
|
||||
"args": ["SAROP21-PPRM094"],
|
||||
"kwargs": {},
|
||||
"z_und": 94,
|
||||
"desc": "Profile monitor after Mirror 1",
|
||||
"type": "eco.xdiagnostics.profile_monitors:Pprm",
|
||||
},
|
||||
{
|
||||
"name": "mono",
|
||||
"args": ["SAROP21-ODCM098"],
|
||||
"kwargs": {},
|
||||
"z_und": 98,
|
||||
"desc": "DCM Monochromator",
|
||||
"type": "eco.xoptics.dcm:Double_Crystal_Mono",
|
||||
},
|
||||
{
|
||||
"name": "profMono",
|
||||
"args": ["SAROP21-PPRM102"],
|
||||
"kwargs": {},
|
||||
"z_und": 102,
|
||||
"desc": "Profile monitor after Monochromator",
|
||||
"type": "eco.xdiagnostics.profile_monitors:Pprm",
|
||||
},
|
||||
{
|
||||
"name": "monOpt",
|
||||
"z_und": 133,
|
||||
"desc": "Intensity/position monitor after Optics hutch",
|
||||
"type": "eco.xdiagnostics.intensity_monitors:SolidTargetDetectorPBPS",
|
||||
"args": ["SAROP21-PBPS133"],
|
||||
"kwargs": {"VME_crate": "SAROP21-CVME-PBPS1", "link": 9},
|
||||
},
|
||||
{
|
||||
"name": "profOpt",
|
||||
"args": ["SAROP21-PPRM133"],
|
||||
"kwargs": {},
|
||||
"z_und": 133,
|
||||
"desc": "Profile monitor after Optics hutch",
|
||||
"type": "eco.xdiagnostics.profile_monitors:Pprm",
|
||||
},
|
||||
{
|
||||
"name": "att",
|
||||
"args": ["SAROP21-OATT135"],
|
||||
"kwargs": {},
|
||||
"z_und": 135,
|
||||
"desc": "Attenuator Bernina",
|
||||
"type": "eco.xoptics.attenuator_aramis:AttenuatorAramis",
|
||||
},
|
||||
{
|
||||
"name": "refLaser",
|
||||
"args": ["SAROP21-OLAS136"],
|
||||
"kwargs": {},
|
||||
"z_und": 136,
|
||||
"desc": "Bernina beamline reference laser before KBs",
|
||||
"type": "eco.xoptics.reflaser:RefLaser_Aramis",
|
||||
},
|
||||
{
|
||||
"name": "slitAtt",
|
||||
"args": ["SAROP21-OAPU136"],
|
||||
"kwargs": {},
|
||||
"z_und": 136,
|
||||
"desc": "Slits behind attenuator",
|
||||
"type": "eco.xoptics.slits:SlitPosWidth",
|
||||
},
|
||||
{
|
||||
"name": "monAtt",
|
||||
"args": ["SAROP21-PBPS138"],
|
||||
"z_und": 138,
|
||||
"desc": "Intensity/Position monitor after Attenuator",
|
||||
"type": "eco.xdiagnostics.intensity_monitors:SolidTargetDetectorPBPS",
|
||||
"kwargs": {"VME_crate": "SAROP21-CVME-PBPS2", "link": 9},
|
||||
},
|
||||
{
|
||||
"name": "detDio",
|
||||
"args": ["SAROP21-PDIO138"],
|
||||
"z_und": 138,
|
||||
"desc": "Diode digitizer for exp data",
|
||||
"type": "eco.devices_general.detectors:DiodeDigitizer",
|
||||
"kwargs": {"VME_crate": "SAROP21-CVME-PBPS2", "link": 9},
|
||||
},
|
||||
{
|
||||
"name": "profAtt",
|
||||
"args": ["SAROP21-PPRM138"],
|
||||
"kwargs": {},
|
||||
"z_und": 138,
|
||||
"desc": "Profile monitor after Attenuator",
|
||||
"type": "eco.xdiagnostics.profile_monitors:Pprm",
|
||||
},
|
||||
{
|
||||
"name": "kbVer",
|
||||
"args": ["SAROP21-OKBV139"],
|
||||
"z_und": 139,
|
||||
"desc": "Vertically focusing Bernina KB mirror",
|
||||
"type": "eco.xoptics.KBver:KBver",
|
||||
"kwargs": {},
|
||||
},
|
||||
{
|
||||
"args": ["SAROP21-OKBH140"],
|
||||
"name": "kbHor",
|
||||
"z_und": 140,
|
||||
"desc": "Horizontally focusing Bernina KB mirror",
|
||||
"type": "eco.xoptics.KBhor:KBhor",
|
||||
"kwargs": {},
|
||||
},
|
||||
# {
|
||||
# 'args' : ['SARES22-GPS'],
|
||||
# 'name' : 'gps',
|
||||
# 'z_und' : 142,
|
||||
# 'desc' : 'General purpose station',
|
||||
# 'type' : 'eco.endstations.bernina_gps:GPS',
|
||||
# 'kwargs': {}
|
||||
# {
|
||||
# "type": "eco.utilities.config:append_to_path",
|
||||
# "args": config["path_exp"],
|
||||
# "name": "path_exp",
|
||||
# "kwargs": {},
|
||||
# "lazy": True,
|
||||
# },
|
||||
# {
|
||||
# "name": "screenshot",
|
||||
# "type": "eco.utilities.elog:Screenshot",
|
||||
# "args": [],
|
||||
# "kwargs": {"screenshot_directory": "/sf/bernina/config/screenshots"},
|
||||
# },
|
||||
# {
|
||||
# "name": "fel",
|
||||
# "type": "eco.fel.swissfel:SwissFel",
|
||||
# "args": [],
|
||||
# "kwargs": {},
|
||||
# "desc": "Fel related control and feedback",
|
||||
# },
|
||||
# {
|
||||
# "name": "mono",
|
||||
# "args": ["SAROP21-ODCM098"],
|
||||
# "kwargs": {},
|
||||
# "z_und": 98,
|
||||
# "desc": "DCM Monochromator",
|
||||
# "type": "eco.xoptics.dcm_new:DoubleCrystalMono",
|
||||
# },
|
||||
# {
|
||||
# "name": "slit_und",
|
||||
# "type": "eco.xoptics.slits:SlitFourBlades_old",
|
||||
# "args": ["SARFE10-OAPU044"],
|
||||
# "kwargs": {},
|
||||
# "desc": "Slit after Undulator",
|
||||
# },
|
||||
# {
|
||||
# "name": "slit_und_epics",
|
||||
# "type": "eco.xoptics.slits:SlitFourBlades_old",
|
||||
# "args": ["SARFE10-OAPU044"],
|
||||
# "kwargs": {},
|
||||
# "desc": "Slit after Undulator",
|
||||
# },
|
||||
# {
|
||||
# "name": "mon_und",
|
||||
# "args": ["SARFE10-PBPS053"],
|
||||
# "z_und": 53,
|
||||
# "desc": "Intensity/Position monitor after Undolator",
|
||||
# "type": "eco.xdiagnostics.intensity_monitors:SolidTargetDetectorPBPS",
|
||||
# "kwargs": {"VME_crate": "SAROP21-CVME-PBPS2", "link": 9},
|
||||
# },
|
||||
# {
|
||||
# "name": "mon_und",
|
||||
# "z_und": 53,
|
||||
# "desc": "Intensity/position monitor after Undulator",
|
||||
# "type": "eco.xdiagnostics.intensity_monitors:SolidTargetDetectorPBPS_new",
|
||||
# "args": ["SARFE10-PBPS053"],
|
||||
# "kwargs": {
|
||||
# "VME_crate": "SAROP21-CVME-PBPS1",
|
||||
# "link": 9,
|
||||
# "channels": {
|
||||
# "up": "SLAAR21-LSCP1-FNS:CH6:VAL_GET",
|
||||
# "down": "SLAAR21-LSCP1-FNS:CH7:VAL_GET",
|
||||
# "left": "SLAAR21-LSCP1-FNS:CH4:VAL_GET",
|
||||
# "right": "SLAAR21-LSCP1-FNS:CH5:VAL_GET",
|
||||
# },
|
||||
# "calc": {
|
||||
# "itot": "SLAAR21-LTIM01-EVR0:CALCI",
|
||||
# "xpos": "SLAAR21-LTIM01-EVR0:CALCX",
|
||||
# "ypos": "SLAAR21-LTIM01-EVR0:CALCY",
|
||||
# },
|
||||
# },
|
||||
# },
|
||||
# {
|
||||
# "name": "pshut_und",
|
||||
# "type": "eco.xoptics.shutters:PhotonShutter",
|
||||
# "args": ["SARFE10-OPSH044:REQUEST"],
|
||||
# "kwargs": {},
|
||||
# "z_und": 44,
|
||||
# "desc": "First shutter after Undulators",
|
||||
# },
|
||||
# {
|
||||
# "name": "pshut_fe",
|
||||
# "type": "eco.xoptics.shutters:PhotonShutter",
|
||||
# "args": ["SARFE10-OPSH059:REQUEST"],
|
||||
# "kwargs": {},
|
||||
# "z_und": 59,
|
||||
# "desc": "Photon shutter end of front end",
|
||||
# },
|
||||
# {
|
||||
# "name": "sshut_opt",
|
||||
# "type": "eco.xoptics.shutters:SafetyShutter",
|
||||
# "args": ["SGE01-EPKT822:BST1_oeffnen"],
|
||||
# "kwargs": {},
|
||||
# "z_und": 115,
|
||||
# "desc": "Bernina safety shutter",
|
||||
# },
|
||||
# {
|
||||
# "name": "sshut_fe",
|
||||
# "type": "eco.xoptics.shutters:SafetyShutter",
|
||||
# "args": ["SGE01-EPKT820:BST1_oeffnen"],
|
||||
# "kwargs": {},
|
||||
# "z_und": 115,
|
||||
# "desc": "Bernina safety shutter",
|
||||
# },
|
||||
# {
|
||||
# "name": "att_fe",
|
||||
# "type": "eco.xoptics.attenuator_aramis:AttenuatorAramis",
|
||||
# "args": ["SARFE10-OATT053"],
|
||||
# "kwargs": {"shutter": Component("pshut_und")},
|
||||
# "z_und": 53,
|
||||
# "desc": "Attenuator in Front End",
|
||||
# },
|
||||
# {
|
||||
# "name": "mon_und",
|
||||
# "z_und": 53,
|
||||
# "desc": "Intensity/position monitor after Optics hutch",
|
||||
# "type": "eco.xdiagnostics.intensity_monitors:SolidTargetDetectorPBPS",
|
||||
# "args": ["SARFE10-PBPS053"],
|
||||
# "kwargs": {},
|
||||
# },
|
||||
# {
|
||||
# "name": "xspect",
|
||||
# "z_und": 53,
|
||||
# "desc": "X-ray single shot spectrometer",
|
||||
# "type": "eco.xdiagnostics.xspect:Xspect",
|
||||
# "args": [],
|
||||
# "kwargs": {},
|
||||
# },
|
||||
# {
|
||||
# "name": "mono_old",
|
||||
# "args": ["SAROP21-ODCM098"],
|
||||
# "kwargs": {
|
||||
# "energy_sp": "SAROP21-ARAMIS:ENERGY_SP",
|
||||
# "energy_rb": "SAROP21-ARAMIS:ENERGY",
|
||||
# },
|
||||
# "z_und": 98,
|
||||
# "desc": "DCM Monochromator",
|
||||
# "type": "eco.xoptics.dcm:Double_Crystal_Mono",
|
||||
# },
|
||||
# {
|
||||
# "name": "xp",
|
||||
# "args": [],
|
||||
# "kwargs": {
|
||||
# "Id": "SAROP21-OPPI113",
|
||||
# "evronoff": "SGE-CPCW-72-EVR0:FrontUnivOut15-Ena-SP",
|
||||
# "evrsrc": "SGE-CPCW-72-EVR0:FrontUnivOut15-Src-SP",
|
||||
# },
|
||||
# "z_und": 103,
|
||||
# "desc": "X-ray pulse picker",
|
||||
# "type": "eco.xoptics.pp:Pulsepick",
|
||||
# },
|
||||
# {
|
||||
# "name": "mon_opt_old",
|
||||
# "z_und": 133,
|
||||
# "desc": "Intensity/position monitor after Optics hutch",
|
||||
# "type": "eco.xdiagnostics.intensity_monitors:SolidTargetDetectorPBPS",
|
||||
# "args": ["SAROP21-PBPS133"],
|
||||
# "kwargs": {"VME_crate": "SAROP21-CVME-PBPS1", "link": 9},
|
||||
# },
|
||||
# {
|
||||
# "name": "att",
|
||||
# "args": ["SAROP21-OATT135"],
|
||||
# "kwargs": {"shutter": Component("xp"), "set_limits": []},
|
||||
# "z_und": 135,
|
||||
# "desc": "Attenuator Bernina",
|
||||
# "type": "eco.xoptics.attenuator_aramis:AttenuatorAramis",
|
||||
# },
|
||||
# {
|
||||
# "name": "slit_att",
|
||||
# "args": ["SAROP21-OAPU136"],
|
||||
# "kwargs": {},
|
||||
# "z_und": 136,
|
||||
# "desc": "Slits behind attenuator",
|
||||
# "type": "eco.xoptics.slits:SlitPosWidth",
|
||||
# },
|
||||
# {
|
||||
# "name": "det_dio",
|
||||
# "args": ["SAROP21-PDIO138"],
|
||||
# "z_und": 138,
|
||||
# "desc": "Diode digitizer for exp data",
|
||||
# "type": "eco.devices_general.detectors:DiodeDigitizer",
|
||||
# "kwargs": {"VME_crate": "SAROP21-CVME-PBPS2", "link": 9},
|
||||
# },
|
||||
# {
|
||||
# "name": "spatial_tt",
|
||||
# "args": [],
|
||||
# "kwargs": {"reduction_client_address": "http://sf-daqsync-02:12003/"},
|
||||
# "z_und": 141,
|
||||
# "desc": "spatial encoding timing diagnostics before sample.",
|
||||
# "type": "eco.xdiagnostics.timetools:SpatialEncoder",
|
||||
# "lazy": True,
|
||||
# },
|
||||
# {
|
||||
# "name": "slit_kb",
|
||||
# "args": [],
|
||||
# "kwargs": {"pvname": "SARES20-MF1"},
|
||||
# "z_und": 141,
|
||||
# "desc": "Slits behind Kb",
|
||||
# "type": "eco.xoptics.slits:SlitBlades_JJ",
|
||||
# # "type": "eco.xoptics.slits:SlitBladesJJ_old",
|
||||
# },
|
||||
# {
|
||||
# "args": [],
|
||||
# "name": "gps_old",
|
||||
# "z_und": 142,
|
||||
# "desc": "General purpose station",
|
||||
# "type": "eco.endstations.bernina_diffractometers:GPS_old",
|
||||
# "kwargs": {
|
||||
# "Id": "SARES22-GPS",
|
||||
# "configuration": config["gps_config"],
|
||||
# "fina_hex_angle_offset": "/sf/bernina/code/gac-bernina/eco_cnf_bernina/reference_values/hex_pi_angle_offset.json",
|
||||
# },
|
||||
# "lazy": True,
|
||||
# },
|
||||
# {
|
||||
# "args": [],
|
||||
# "name": "xrd_old",
|
||||
# "z_und": 142,
|
||||
# "desc": "Xray diffractometer",
|
||||
# "type": "eco.endstations.bernina_diffractometers:XRD_old",
|
||||
# "kwargs": {"Id": "SARES21-XRD", "configuration": config["xrd_config"]},
|
||||
# },
|
||||
# {
|
||||
# "args": [],
|
||||
# "name": "xrd",
|
||||
# "z_und": 142,
|
||||
# "desc": "Xray diffractometer",
|
||||
# "type": "eco.endstations.bernina_diffractometers:XRD",
|
||||
# "kwargs": {
|
||||
# "Id": "SARES21-XRD",
|
||||
# "configuration": config["xrd_config"],
|
||||
# "diff_detector": {"jf_id": "JF01T03V01"},
|
||||
# },
|
||||
# },
|
||||
# {
|
||||
# "args": [],
|
||||
# "name": "gasjet",
|
||||
# "z_und": 142,
|
||||
# "desc": "ToF comm. gasjet",
|
||||
# "type": "tof:jet",
|
||||
# "kwargs": {},
|
||||
# },
|
||||
# {
|
||||
# "args": [],
|
||||
# "name": "xeye",
|
||||
# "z_und": 142,
|
||||
# "desc": "Mobile X-ray eye in Bernina hutch",
|
||||
# "type": "eco.xdiagnostics.profile_monitors:Bernina_XEYE",
|
||||
# "kwargs": {
|
||||
# "zoomstage_pv": config["xeye"]["zoomstage_pv"],
|
||||
# "camera_pv": config["xeye"]["camera_pv"],
|
||||
# "bshost": "sf-daqsync-01.psi.ch",
|
||||
# "bsport": 11151,
|
||||
# },
|
||||
# },
|
||||
# {
|
||||
# "args": ["SARES20-CAMS142-C3"],
|
||||
# "name": "cam_sample_xrd",
|
||||
# "z_und": 142,
|
||||
# "desc": "",
|
||||
# "type": "eco.devices_general.cameras_swissfel:CameraBasler",
|
||||
# "kwargs": {},
|
||||
# },
|
||||
# {
|
||||
# "args": [],
|
||||
# "name": "cams_qioptiq",
|
||||
# "z_und": 142,
|
||||
# "desc": "Qioptic sample viewer in Bernina hutch",
|
||||
# "type": "eco.endstations.bernina_cameras:Qioptiq",
|
||||
# "kwargs": {
|
||||
# "bshost": "sf-daqsync-01.psi.ch",
|
||||
# "bsport": 11149,
|
||||
# "zoomstage_pv": config["cams_qioptiq"]["zoomstage_pv"],
|
||||
# "camera_pv": config["cams_qioptiq"]["camera_pv"],
|
||||
# },
|
||||
# },
|
||||
# {
|
||||
# "args": ["SLAAR02-TSPL-EPL"],
|
||||
# "name": "phase_shifter",
|
||||
# "z_und": 142,
|
||||
# "desc": "Experiment laser phase shifter",
|
||||
# "type": "eco.devices_general.timing:PhaseShifterAramis",
|
||||
# "kwargs": {},
|
||||
# },
|
||||
# {
|
||||
# "args": ["SLAAR21-LTIM01-EVR0"],
|
||||
# "name": "laser_shutter",
|
||||
# "z_und": 142,
|
||||
# "desc": "Laser Shutter",
|
||||
# "type": "eco.loptics.laser_shutter:laser_shutter",
|
||||
# "kwargs": {},
|
||||
# },
|
||||
# {
|
||||
# "args": [],
|
||||
# "name": "daq_dia_old",
|
||||
# "desc": "server based acquisition",
|
||||
# "type": "eco.acquisition.dia:DIAClient",
|
||||
# "kwargs": {
|
||||
# "instrument": "bernina",
|
||||
# "api_address": config["daq_address"],
|
||||
# "pgroup": config["pgroup"],
|
||||
# "pedestal_directory": config["jf_pedestal_directory"],
|
||||
# "gain_path": config["jf_gain_path"],
|
||||
# "config_default": config["daq_dia_config"],
|
||||
# "jf_channels": config["jf_channels"],
|
||||
# "default_file_path": None,
|
||||
# },
|
||||
# },
|
||||
# {
|
||||
# "args": [
|
||||
# config["checker_PV"],
|
||||
# config["checker_thresholds"],
|
||||
# config["checker_fractionInThreshold"],
|
||||
# ], #'SARFE10-PBPG050:HAMP-INTENSITY-CAL',[60,700],.7],
|
||||
# "name": "checker",
|
||||
# "desc": "checker functions for data acquisition",
|
||||
# "type": "eco.acquisition.checkers:CheckerCA",
|
||||
# "kwargs": {},
|
||||
# },
|
||||
# {
|
||||
# "args": [
|
||||
# "SARES20-LSCP9-FNS:CH1:VAL_GET",
|
||||
# [-100000, 100000],
|
||||
# config["checker_fractionInThreshold"],
|
||||
# ], #'SARFE10-PBPG050:HAMP-INTENSITY-CAL',[60,700],.7],
|
||||
# "name": "checker_epics",
|
||||
# "desc": "checker functions for data acquisition",
|
||||
# "type": "eco.acquisition.checkers:CheckerCA",
|
||||
# "kwargs": {},
|
||||
# },
|
||||
# {
|
||||
# "args": [],
|
||||
# "name": "lxt",
|
||||
# "desc": "laser timing with pockels cells and phase shifter",
|
||||
# "type": "eco.timing.lasertiming:Lxt",
|
||||
# "kwargs": {},
|
||||
# },
|
||||
# {
|
||||
# "args": ["SARES20-CVME-01-EVR0"],
|
||||
# "name": "evr_bernina",
|
||||
# "desc": "Bernina event receiver",
|
||||
# "type": "eco.timing.event_timing:EventReceiver",
|
||||
# "kwargs": {},
|
||||
# },
|
||||
# {
|
||||
# "args": [],
|
||||
# "name": "default_channel_list",
|
||||
# "desc": "Bernina default channels, used in daq",
|
||||
# "type": "eco.utilities.config:ChannelList",
|
||||
# "kwargs": {
|
||||
# "file_name": "/sf/bernina/config/channel_lists/default_channel_list"
|
||||
# },
|
||||
# },
|
||||
# {
|
||||
# "args": [],
|
||||
# "name": "default_channel_list_bs",
|
||||
# "desc": "Bernina default bs channels, used by bs_daq",
|
||||
# "type": "eco.utilities.config:ChannelList",
|
||||
# "kwargs": {
|
||||
# "file_name": "/sf/bernina/config/channel_lists/default_channel_list_bs"
|
||||
# },
|
||||
# },
|
||||
# {
|
||||
# "args": [],
|
||||
# "name": "channels_spectrometer_projection",
|
||||
# "desc": "",
|
||||
# "type": "eco.utilities.config:ChannelList",
|
||||
# "kwargs": {
|
||||
# "file_name": "/sf/bernina/config/channel_lists/channel_list_PSSS_projection"
|
||||
# },
|
||||
# },
|
||||
# {
|
||||
# "args": [],
|
||||
# "name": "bs_daq",
|
||||
# "desc": "bs daq writer (locally!)",
|
||||
# "type": "eco.acquisition.bs_data:BStools",
|
||||
# "kwargs": {
|
||||
# "default_channel_list": {
|
||||
# "bernina_default_channels_bs": Component("default_channel_list_bs")
|
||||
# },
|
||||
# "default_file_path": f"/sf/bernina/data/{config['pgroup']}/res/%s",
|
||||
# },
|
||||
# },
|
||||
# {
|
||||
# "args": ["SARES23-"],
|
||||
# "name": "slit_kb",
|
||||
# "z_und": 141,
|
||||
# "desc": "Upstream diagnostics slits",
|
||||
# "type": "eco.xoptics.slit_USD:Upstream_diagnostic_slits",
|
||||
# "kwargs": {"right": "LIC4", "left": "LIC3", "up": "LIC2", "down": "LIC1"},
|
||||
# },
|
||||
# {
|
||||
# "args": ["SARES23-"],
|
||||
# "name": "slit_cleanup",
|
||||
# "z_und": 141,
|
||||
# "desc": "Upstream diagnostics slits",
|
||||
# "type": "eco.xoptics.slit_USD:Upstream_diagnostic_slits",
|
||||
# "kwargs": {"right": "LIC7", "left": "LIC8", "up": "LIC8", "down": "LIC5"},
|
||||
# },
|
||||
# {
|
||||
# "args": [
|
||||
# [
|
||||
# Component("slit_und"),
|
||||
# Component("slit_switch"),
|
||||
# Component("slit_att"),
|
||||
# Component("slit_kb"),
|
||||
# ]
|
||||
# ],
|
||||
# "name": "slits",
|
||||
# "desc": "collection of all slits",
|
||||
# "type": "eco.utilities.beamline:Slits",
|
||||
# "kwargs": {},
|
||||
# },
|
||||
# {
|
||||
# "args": [
|
||||
# [Component("slit_switch"), Component("slit_att"), Component("slit_kb"),]
|
||||
# ],
|
||||
# "name": "slits",
|
||||
# "desc": "collection of all slits",
|
||||
# "type": "eco.utilities.beamline:Slits",
|
||||
# "kwargs": {},
|
||||
# "lazy": False,
|
||||
# },
|
||||
# {
|
||||
# "args": [],
|
||||
# "name": "thc",
|
||||
# "z_und": 142,
|
||||
# "desc": "High field THz Chamber",
|
||||
# "type": "eco.endstations.bernina_sample_environments:High_field_thz_chamber",
|
||||
# "kwargs": {"Id": "SARES23", "configuration": ["ottifant"]},
|
||||
# },
|
||||
# {
|
||||
# "args": [],
|
||||
# "name": "ocb",
|
||||
# "z_und": 142,
|
||||
# "desc": "Organic Crystal Breadboard",
|
||||
# "type": "eco.endstations.bernina_sample_environments:Organic_crystal_breadboard",
|
||||
# "kwargs": {"Id": "SARES23"},
|
||||
# },
|
||||
# {
|
||||
# "args": [],
|
||||
# "name": "eos",
|
||||
# "z_und": 142,
|
||||
# "desc": "electro optic sampling stages",
|
||||
# "type": "eco.endstations.bernina_sample_environments:Electro_optic_sampling",
|
||||
# "kwargs": {
|
||||
# "Id": "SARES23",
|
||||
# "pgroup": config["pgroup"],
|
||||
# "diode_channels": {
|
||||
# "d1": "SARES20-LSCP9-FNS:CH1:VAL_GET",
|
||||
# "d2": "SARES20-LSCP9-FNS:CH2:VAL_GET",
|
||||
# "diff": "SARES20-LSCP9-FNS:CH3:VAL_GET",
|
||||
# },
|
||||
{
|
||||
"args": [],
|
||||
"name": "xrd",
|
||||
"z_und": 142,
|
||||
"desc": "Xray diffractometer",
|
||||
"type": "eco.endstations.bernina_diffractometers:XRD",
|
||||
"kwargs": {'Id':"SARES21-XRD"},
|
||||
},
|
||||
{
|
||||
"args": ["SARES20-PROF142-M1"],
|
||||
"name": "xeye",
|
||||
"z_und": 142,
|
||||
"desc": "Mobile X-ray eye in Bernina hutch",
|
||||
"type": "eco.xdiagnostics.profile_monitors:Bernina_XEYE",
|
||||
"kwargs": {"bshost": "sf-daqsync-01.psi.ch", "bsport": 11173},
|
||||
},
|
||||
{
|
||||
"args": ["SLAAR02-TSPL-EPL"],
|
||||
"name": "phaseShifter",
|
||||
"z_und": 142,
|
||||
"desc": "Experiment laser phase shifter",
|
||||
"type": "eco.devices_general.timing:PhaseShifterAramis",
|
||||
"kwargs": {},
|
||||
},
|
||||
{
|
||||
"args": ["SLAAR21-LMOT"],
|
||||
"name": "las",
|
||||
"z_und": 142,
|
||||
"desc": "Experiment laser optics",
|
||||
"type": "eco.loptics.bernina_experiment:Laser_Exp",
|
||||
"kwargs": {},
|
||||
},
|
||||
{
|
||||
"args": ["SLAAR21-LTIM01-EVR0"],
|
||||
"name": "laserShutter",
|
||||
"z_und": 142,
|
||||
"desc": "Laser Shutter",
|
||||
"type": "eco.loptics.laser_shutter:laser_shutter",
|
||||
"kwargs": {},
|
||||
},
|
||||
{
|
||||
"args": ["/sf/bernina/config/channel_lists/default_channel_list_ioxos"],
|
||||
"name": "ioxos_channel_list",
|
||||
"desc": "ioxos channel list",
|
||||
"type": "eco.utilities.config:parseChannelListFile",
|
||||
"kwargs": {},
|
||||
},
|
||||
{
|
||||
"args": [],
|
||||
"name": "ioxosdaq",
|
||||
"z_und": 142,
|
||||
"desc": "ioxos acquisition",
|
||||
"type": "eco.acquisition.ioxos_data:Ioxostools",
|
||||
"kwargs": {"channel_list": Component("ioxos_channel_list")},
|
||||
},
|
||||
# },
|
||||
# },
|
||||
]
|
||||
|
||||
|
||||
components_old = {
|
||||
"SARFE10-OPSH044": {
|
||||
"alias": "ShutUnd",
|
||||
"z_und": 44,
|
||||
"desc": "Photon shutter after Undulator",
|
||||
},
|
||||
"SARFE10-PBIG050": {
|
||||
"alias": "GasMon",
|
||||
"z_und": 50,
|
||||
"desc": "Gas Monitor Intensity",
|
||||
},
|
||||
"SARFE10-PBPS053": {
|
||||
"alias": "MonUnd",
|
||||
"z_und": 44,
|
||||
"desc": "Intensity position monitor after Undulator",
|
||||
},
|
||||
"SARFE10-SBST060": {
|
||||
"alias": "ShutFE",
|
||||
"z_und": 60,
|
||||
"desc": "Photon shutter in the end of Front End",
|
||||
},
|
||||
"SAROP11-OOMH064": {
|
||||
"alias": "MirrAlv1",
|
||||
"z_und": 64,
|
||||
"desc": "Horizontal mirror Alvra 1",
|
||||
},
|
||||
"SAROP21-OOMV092": {
|
||||
"alias": "Mirr1",
|
||||
"z_und": 92,
|
||||
"desc": "Vertical offset Mirror 1",
|
||||
},
|
||||
"SAROP21-OOMV096": {
|
||||
"alias": "Mirr2",
|
||||
"z_und": 96,
|
||||
"desc": "Vertical offset mirror 2",
|
||||
},
|
||||
"SAROP21-PSCR097": {
|
||||
"alias": "ProfMirr2",
|
||||
"z_und": 97,
|
||||
"desc": "Profile Monitor after Mirror 2",
|
||||
},
|
||||
"SAROP21-OPPI103": {"alias": "Pick", "z_und": 103, "desc": "X-ray pulse picker"},
|
||||
"SAROP21-BST114": {
|
||||
"alias": "ShutOpt",
|
||||
"z_und": 114,
|
||||
"desc": "Shutter after Optics hutch",
|
||||
},
|
||||
"SAROP21-PALM134": {
|
||||
"alias": "TimTof",
|
||||
"z_und": 134,
|
||||
"desc": "Timing diagnostics THz streaking/TOF",
|
||||
},
|
||||
"SAROP21-PSEN135": {
|
||||
"alias": "TimRef",
|
||||
"z_und": 135,
|
||||
"desc": "Timing diagnostics spectral encoding of ref. index change",
|
||||
}
|
||||
# 'SLAAR21-LMOT' : {
|
||||
# 'alias' : 'Palm',
|
||||
# 'z_und' : 142,
|
||||
# 'desc' : 'Streaking arrival time monitor',
|
||||
# 'eco_type' : 'timing.palm.Palm'},
|
||||
# 'SLAAR21-LMOT' : {
|
||||
# 'alias' : 'Psen',
|
||||
# 'z_und' : 142,
|
||||
# 'desc' : 'Streaking arrival time monitor',
|
||||
# 'eco_type' : 'timing.psen.Psen'}
|
||||
# = dict(
|
||||
# alias = ''
|
||||
# z_und =
|
||||
# desc = ''},
|
||||
}
|
||||
try:
|
||||
components.extend(config["components"])
|
||||
print("Did append additional components!")
|
||||
except:
|
||||
print("Could not append components from config.")
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
from eco import ecocnf
|
||||
|
||||
|
||||
def get_from_archive(Obj, attribute_name="pvname"):
|
||||
def get_archiver_time_range(self, start=None, end=None, plot=True, **kwargs):
|
||||
"""Try to retrieve data within timerange from archiver. A time delta from now is assumed if end time is missing."""
|
||||
channelname = self.__dict__[attribute_name]
|
||||
return ecocnf.archiver.get_data_time_range(
|
||||
channels=[channelname],
|
||||
start=start,
|
||||
end=end,
|
||||
plot=plot,
|
||||
**kwargs,
|
||||
)
|
||||
|
||||
Obj.get_archiver_time_range = get_archiver_time_range
|
||||
return Obj
|
||||
@@ -0,0 +1,224 @@
|
||||
from enum import IntEnum
|
||||
from time import time, sleep
|
||||
|
||||
import numpy as np
|
||||
from epics import PV
|
||||
|
||||
from eco.acquisition.utilities import Acquisition
|
||||
from eco.aliases import Alias
|
||||
from eco.elements.assembly import Assembly
|
||||
from eco.epics.adjustable import AdjustablePvString
|
||||
from eco.epics import get_from_archive
|
||||
|
||||
|
||||
@get_from_archive
|
||||
class DetectorBsData(Assembly):
|
||||
def __init__(self, bschannel, name=None):
|
||||
super().__init__(name=name)
|
||||
self.status_collection.append(self)
|
||||
self.bschannel = bschannel
|
||||
if epics_pv_available & epics_pv_availabe == "same":
|
||||
self._pv = PV(pvname)
|
||||
self._append(
|
||||
AdjustablePvString, self.pvname + ".EGU", name="unit", is_setting=False
|
||||
)
|
||||
self.name = name
|
||||
self.alias = Alias(self.name, channel=self.pvname, channeltype="BS")
|
||||
|
||||
def get_current_value(self):
|
||||
return self._pv.get()
|
||||
|
||||
def __call__(self):
|
||||
return self.get_current_value()
|
||||
|
||||
|
||||
@get_from_archive
|
||||
class DetectorPvEnum(Assembly):
|
||||
def __init__(self, pvname, name=None):
|
||||
super().__init__(name=name)
|
||||
self.pvname = pvname
|
||||
self._pv = PV(pvname, connection_timeout=0.05)
|
||||
self.name = name
|
||||
self.enum_strs = self._pv.enum_strs
|
||||
|
||||
self.PvEnum = IntEnum(name, {tstr: n for n, tstr in enumerate(self.enum_strs)})
|
||||
self.alias = Alias(name, channel=self.pvname, channeltype="CA")
|
||||
|
||||
def validate(self, value):
|
||||
if type(value) is str:
|
||||
return self.PvEnum.__members__[value]
|
||||
else:
|
||||
return self.PvEnum(value)
|
||||
|
||||
def get_current_value(self):
|
||||
return self.validate(self._pv.get())
|
||||
|
||||
def __repr__(self):
|
||||
if not self.name:
|
||||
name = self.Id
|
||||
else:
|
||||
name = self.name
|
||||
cv = self.get_current_value()
|
||||
s = f"{name} (enum) at value: {cv}" + "\n"
|
||||
s += "{:<5}{:<5}{:<}\n".format("Num.", "Sel.", "Name")
|
||||
# s+= '_'*40+'\n'
|
||||
for name, val in self.PvEnum.__members__.items():
|
||||
if val == cv:
|
||||
sel = "x"
|
||||
else:
|
||||
sel = " "
|
||||
s += "{:>4} {} {}\n".format(val, sel, name)
|
||||
return s
|
||||
|
||||
def __call__(self):
|
||||
return self.get_current_value()
|
||||
|
||||
|
||||
class DetectorPvString:
|
||||
def __init__(self, pvname, name=None, elog=None):
|
||||
self.name = name
|
||||
self.pvname = pvname
|
||||
self._pv = PV(pvname, connection_timeout=0.05)
|
||||
self._elog = elog
|
||||
self.alias = Alias(name, channel=self.pvname, channeltype="CA")
|
||||
|
||||
def get_current_value(self):
|
||||
return self._pv.get()
|
||||
|
||||
def set_target_value(self, value, hold=False):
|
||||
changer = lambda value: self._pv.put(bytes(value, "utf8"), wait=True)
|
||||
return Changer(
|
||||
target=value, parent=self, changer=changer, hold=hold, stopper=None
|
||||
)
|
||||
|
||||
def __repr__(self):
|
||||
return self.get_current_value()
|
||||
|
||||
def __call__(self, string=None):
|
||||
if not string is None:
|
||||
self.set_target_value(string)
|
||||
else:
|
||||
return self.get_current_value()
|
||||
|
||||
|
||||
@get_from_archive
|
||||
class DetectorPvDataStream(Assembly):
|
||||
def __init__(self, pvname, name=None):
|
||||
super().__init__(name=name)
|
||||
self.Id = pvname
|
||||
self.pvname = pvname
|
||||
self._pv = PV(pvname)
|
||||
self.alias = Alias(self.name, channel=self.pvname, channeltype="CA")
|
||||
self._append(
|
||||
AdjustablePvString, self.pvname + ".EGU", name="unit", is_setting=False
|
||||
)
|
||||
# self._append(
|
||||
# PvString, self.pvname + ".DESC", name="description", is_setting=False
|
||||
# )
|
||||
|
||||
def collect(self, seconds=None, samples=None):
|
||||
if (not seconds) and (not samples):
|
||||
raise Exception(
|
||||
"Either a time interval or number of samples need to be defined."
|
||||
)
|
||||
try:
|
||||
self._pv.callbacks.pop(self._collection["ix_cb"])
|
||||
except:
|
||||
pass
|
||||
self._collection = {"done": False}
|
||||
self.data_collected = []
|
||||
if seconds:
|
||||
self._collection["start_time"] = time()
|
||||
self._collection["seconds"] = seconds
|
||||
stopcond = (
|
||||
lambda: (time() - self._collection["start_time"])
|
||||
> self._collection["seconds"]
|
||||
)
|
||||
|
||||
def addData(**kw):
|
||||
if not stopcond():
|
||||
self.data_collected.append(kw["value"])
|
||||
else:
|
||||
self._pv.callbacks.pop(self._collection["ix_cb"])
|
||||
self._collection["done"] = True
|
||||
|
||||
elif samples:
|
||||
self._collection["samples"] = samples
|
||||
stopcond = lambda: len(self.data_collected) >= self._collection["samples"]
|
||||
|
||||
def addData(**kw):
|
||||
self.data_collected.append(kw["value"])
|
||||
if stopcond():
|
||||
self._pv.callbacks.pop(self._collection["ix_cb"])
|
||||
self._collection["done"] = True
|
||||
|
||||
self._collection["ix_cb"] = self._pv.add_callback(addData)
|
||||
time_wait_start = time()
|
||||
while not self._collection["done"]:
|
||||
sleep(0.005)
|
||||
if seconds:
|
||||
if (time() - time_wait_start) > seconds:
|
||||
if len(self.data_collected) == 0:
|
||||
print(
|
||||
f"No {self.name}({self.Id}) data update in time interval, reporting last value"
|
||||
)
|
||||
self._pv.callbacks.pop(self._collection["ix_cb"])
|
||||
self.data_collected.append(self.get_current_value())
|
||||
break
|
||||
|
||||
return self.data_collected
|
||||
|
||||
def acquire(self, hold=False, seconds=None, samples=None, **kwargs):
|
||||
return Acquisition(
|
||||
acquire=lambda: self.collect(seconds=seconds, samples=samples, **kwargs),
|
||||
hold=hold,
|
||||
stopper=None,
|
||||
get_result=lambda: self.data_collected,
|
||||
)
|
||||
|
||||
def accumulate_ring_buffer(self, n_buffer):
|
||||
if not hasattr(self, "_accumulate"):
|
||||
self._accumulate = {"n_buffer": n_buffer, "ix": 0, "n_cb": -1}
|
||||
else:
|
||||
self._accumulate["n_buffer"] = n_buffer
|
||||
self._accumulate["ix"] = 0
|
||||
self._pv.callbacks.pop(self._accumulate["n_cb"], None)
|
||||
self._data = np.squeeze(np.zeros([n_buffer * 2, self._pv.count])) * np.nan
|
||||
|
||||
def addData(**kw):
|
||||
self._accumulate["ix"] = (self._accumulate["ix"] + 1) % self._accumulate[
|
||||
"n_buffer"
|
||||
]
|
||||
self._data[self._accumulate["ix"] :: self._accumulate["n_buffer"]] = kw[
|
||||
"value"
|
||||
]
|
||||
|
||||
self._accumulate["n_cb"] = self._pv.add_callback(addData)
|
||||
|
||||
def accumulate_start(self):
|
||||
if not hasattr(self, "_accumulate_inf"):
|
||||
self._accumulate_inf = {"n_cb": -1}
|
||||
self._pv.callbacks.pop(self._accumulate_inf["n_cb"], None)
|
||||
self._data_inf = []
|
||||
|
||||
def addData(**kw):
|
||||
self._data_inf.append(kw["value"])
|
||||
|
||||
self._accumulate_inf["n_cb"] = self._pv.add_callback(addData)
|
||||
|
||||
def accumulate_stop(self):
|
||||
self._pv.callbacks.pop(self._accumulate_inf["n_cb"], None)
|
||||
return self._data_inf
|
||||
|
||||
def get_data(self):
|
||||
return self._data[
|
||||
self._accumulate["ix"]
|
||||
+ 1 : self._accumulate["ix"]
|
||||
+ 1
|
||||
+ self._accumulate["n_buffer"]
|
||||
]
|
||||
|
||||
data = property(get_data)
|
||||
|
||||
def get_current_value(self):
|
||||
return self._pv.get()
|
||||
@@ -0,0 +1,292 @@
|
||||
# from data_api import get_data, search
|
||||
from ..epics.detector import DetectorPvDataStream
|
||||
from fnmatch import translate
|
||||
import datetime, dateutil
|
||||
from numbers import Number
|
||||
from matplotlib import pyplot as plt
|
||||
import numpy as np
|
||||
from .. import ecocnf
|
||||
from ..elements.assembly import Assembly
|
||||
from datahub import DataBuffer, Table, Stdout
|
||||
|
||||
|
||||
class DataHub(Assembly):
|
||||
def __init__(self, pv_pulse_id=None, name=None, add_to_cnf=False):
|
||||
super().__init__(name=name)
|
||||
if pv_pulse_id:
|
||||
self._append(DetectorPvDataStream, pv_pulse_id, name="pulse_id")
|
||||
if add_to_cnf:
|
||||
ecocnf.archiver = self
|
||||
self._databuffer = None
|
||||
|
||||
@property
|
||||
def databuffer(self):
|
||||
if self._databuffer is None:
|
||||
self._databuffer = DataBuffer(backend="sf-databuffer")
|
||||
return self._databuffer
|
||||
|
||||
def get_data(self, channels, start, end, range_type=None):
|
||||
table = Table()
|
||||
self.databuffer.add_listener(table)
|
||||
self.databuffer.request(dict(channels=channels, start=start, end=end))
|
||||
op = table.as_dataframe()
|
||||
self.databuffer.remove_listeners()
|
||||
return op
|
||||
|
||||
def get_data_time_range(
|
||||
self,
|
||||
channels=[],
|
||||
start=None,
|
||||
end=None,
|
||||
plot=False,
|
||||
force_type=None,
|
||||
labels=None,
|
||||
convert_timezone=False,
|
||||
**kwargs,
|
||||
):
|
||||
if not end:
|
||||
end = datetime.datetime.now()
|
||||
if isinstance(start, datetime.timedelta):
|
||||
start = end + start
|
||||
elif isinstance(start, dict):
|
||||
start = datetime.timedelta(**start)
|
||||
start = end + start
|
||||
elif isinstance(start, Number):
|
||||
start = datetime.timedelta(seconds=start)
|
||||
start = end + start
|
||||
else:
|
||||
start = datetime.timedelta(**kwargs)
|
||||
start = end + start
|
||||
|
||||
if force_type:
|
||||
archive_types = ["CA", "BS"]
|
||||
if force_type in archive_types:
|
||||
if force_type == "CA":
|
||||
channels_req = [f"sf-archiverappliance/{tch}" for tch in channels]
|
||||
elif force_type == "BS":
|
||||
channels_req = [f"sf-databuffer/{tch}" for tch in channels]
|
||||
else:
|
||||
raise Exception(f"force_type must be one of {archive_types}")
|
||||
else:
|
||||
channels_req = channels
|
||||
|
||||
if type(start) is str:
|
||||
start = dateutil.parser.parse(start)
|
||||
if type(end) is str:
|
||||
end = dateutil.parser.parse(end)
|
||||
|
||||
start = datetime2str(local2utc(start))
|
||||
end = datetime2str(local2utc(end))
|
||||
|
||||
data = self.get_data(channels_req, start=start, end=end, range_type="time")
|
||||
if convert_timezone:
|
||||
data.index = data.index.tz_convert("Europe/Zurich")
|
||||
|
||||
if plot:
|
||||
ah = plt.gca()
|
||||
if not labels:
|
||||
labels = channels
|
||||
for chan, label in zip(channels, labels):
|
||||
sel = ~data[chan].isnull()
|
||||
if any(sel):
|
||||
x = data.index[sel]
|
||||
y = data[chan][sel]
|
||||
ah.step(x, y, ".-", label=label, where="post")
|
||||
plt.xticks(rotation=30)
|
||||
plt.legend()
|
||||
plt.tight_layout()
|
||||
plt.xlabel(data.index.name)
|
||||
ah.figure.tight_layout()
|
||||
return data
|
||||
|
||||
def get_data_pulse_id_range(
|
||||
self,
|
||||
channels=[],
|
||||
start=None,
|
||||
end=None,
|
||||
plot=False,
|
||||
force_type=None,
|
||||
convert_timezone=False,
|
||||
labels=None,
|
||||
):
|
||||
if not end:
|
||||
if hasattr(self, "pulse_id"):
|
||||
end = int(self.pulse_id.get_current_value())
|
||||
else:
|
||||
raise Exception("no end pulse id provided")
|
||||
start = start + end
|
||||
if force_type:
|
||||
archive_types = ["CA", "BS"]
|
||||
if force_type in archive_types:
|
||||
if force_type == "CA":
|
||||
channels_req = [f"sf-archiverappliance/{tch}" for tch in channels]
|
||||
elif force_type == "BS":
|
||||
channels_req = [f"sf-databuffer/{tch}" for tch in channels]
|
||||
else:
|
||||
raise Exception(f"force_type must be one of {archive_types}")
|
||||
else:
|
||||
channels_req = channels
|
||||
|
||||
data = self.get_data(channels_req, start=start, end=end, range_type="pulseId")
|
||||
if convert_timezone:
|
||||
data.index = data.index.tz_convert("Europe/Zurich")
|
||||
if plot:
|
||||
ah = plt.gca()
|
||||
if not labels:
|
||||
labels = channels
|
||||
for chan, label in zip(channels, labels):
|
||||
sel = ~np.isnan(data[chan])
|
||||
x = data.index[sel]
|
||||
y = data[chan][sel]
|
||||
ah.step(x, y, ".-", label=label, where="post")
|
||||
plt.xticks(rotation=30)
|
||||
plt.legend()
|
||||
plt.tight_layout()
|
||||
plt.xlabel(data.index.name)
|
||||
ah.figure.tight_layout()
|
||||
|
||||
return data
|
||||
|
||||
def search(self, searchstring):
|
||||
"""A search in database using simpler unix glob expressions (e.g. '*ARES*')"""
|
||||
return search(translate(searchstring))
|
||||
|
||||
|
||||
class DataApi(Assembly):
|
||||
def __init__(self, pv_pulse_id=None, name=None, add_to_cnf=True):
|
||||
super().__init__(name=name)
|
||||
if pv_pulse_id:
|
||||
self._append(DetectorPvDataStream, pv_pulse_id, name="pulse_id")
|
||||
if add_to_cnf:
|
||||
ecocnf.archiver = self
|
||||
|
||||
def get_data_time_range(
|
||||
self,
|
||||
channels=[],
|
||||
start=None,
|
||||
end=None,
|
||||
plot=False,
|
||||
force_type=None,
|
||||
labels=None,
|
||||
convert_timezone=True,
|
||||
**kwargs,
|
||||
):
|
||||
if not end:
|
||||
end = datetime.datetime.now()
|
||||
if isinstance(start, datetime.timedelta):
|
||||
start = end + start
|
||||
elif isinstance(start, dict):
|
||||
start = datetime.timedelta(**start)
|
||||
start = end + start
|
||||
elif isinstance(start, Number):
|
||||
start = datetime.timedelta(seconds=start)
|
||||
start = end + start
|
||||
else:
|
||||
start = datetime.timedelta(**kwargs)
|
||||
start = end + start
|
||||
|
||||
if force_type:
|
||||
archive_types = ["CA", "BS"]
|
||||
if force_type in archive_types:
|
||||
if force_type == "CA":
|
||||
channels_req = [f"sf-archiverappliance/{tch}" for tch in channels]
|
||||
elif force_type == "BS":
|
||||
channels_req = [f"sf-databuffer/{tch}" for tch in channels]
|
||||
else:
|
||||
raise Exception(f"force_type must be one of {archive_types}")
|
||||
else:
|
||||
channels_req = channels
|
||||
|
||||
if type(start) is str:
|
||||
start = dateutil.parser.parse(start)
|
||||
if type(end) is str:
|
||||
end = dateutil.parser.parse(end)
|
||||
|
||||
start = datetime2str(local2utc(start))
|
||||
end = datetime2str(local2utc(end))
|
||||
|
||||
data = get_data(channels_req, start=start, end=end, range_type="time")
|
||||
if convert_timezone:
|
||||
data.index = data.index.tz_convert("Europe/Zurich")
|
||||
|
||||
if plot:
|
||||
ah = plt.gca()
|
||||
if not labels:
|
||||
labels = channels
|
||||
for chan, label in zip(channels, labels):
|
||||
sel = ~data[chan].isnull()
|
||||
if any(sel):
|
||||
x = data.index[sel]
|
||||
y = data[chan][sel]
|
||||
ah.step(x, y, ".-", label=label, where="post")
|
||||
plt.xticks(rotation=30)
|
||||
plt.legend()
|
||||
plt.tight_layout()
|
||||
plt.xlabel(data.index.name)
|
||||
ah.figure.tight_layout()
|
||||
return data
|
||||
|
||||
def get_data_pulse_id_range(
|
||||
self,
|
||||
channels=[],
|
||||
start=None,
|
||||
end=None,
|
||||
plot=False,
|
||||
force_type=None,
|
||||
convert_timezone=True,
|
||||
labels=None,
|
||||
):
|
||||
if not end:
|
||||
if hasattr(self, "pulse_id"):
|
||||
end = int(self.pulse_id.get_current_value())
|
||||
else:
|
||||
raise Exception("no end pulse id provided")
|
||||
start = start + end
|
||||
if force_type:
|
||||
archive_types = ["CA", "BS"]
|
||||
if force_type in archive_types:
|
||||
if force_type == "CA":
|
||||
channels_req = [f"sf-archiverappliance/{tch}" for tch in channels]
|
||||
elif force_type == "BS":
|
||||
channels_req = [f"sf-databuffer/{tch}" for tch in channels]
|
||||
else:
|
||||
raise Exception(f"force_type must be one of {archive_types}")
|
||||
else:
|
||||
channels_req = channels
|
||||
|
||||
data = get_data(channels_req, start=start, end=end, range_type="pulseId")
|
||||
if convert_timezone:
|
||||
data.index = data.index.tz_convert("Europe/Zurich")
|
||||
if plot:
|
||||
ah = plt.gca()
|
||||
if not labels:
|
||||
labels = channels
|
||||
for chan, label in zip(channels, labels):
|
||||
sel = ~np.isnan(data[chan])
|
||||
x = data.index[sel]
|
||||
y = data[chan][sel]
|
||||
ah.step(x, y, ".-", label=label, where="post")
|
||||
plt.xticks(rotation=30)
|
||||
plt.legend()
|
||||
plt.tight_layout()
|
||||
plt.xlabel(data.index.name)
|
||||
ah.figure.tight_layout()
|
||||
|
||||
return data
|
||||
|
||||
def search(self, searchstring):
|
||||
"""A search in database using simpler unix glob expressions (e.g. '*ARES*')"""
|
||||
return search(translate(searchstring))
|
||||
|
||||
|
||||
def datetime2str(datetime_date):
|
||||
return datetime_date.isoformat()
|
||||
|
||||
|
||||
def local2utc(datetime_date):
|
||||
|
||||
return datetime_date.replace(
|
||||
tzinfo=None,
|
||||
).astimezone(
|
||||
tz=datetime.timezone.utc,
|
||||
)
|
||||
@@ -0,0 +1,19 @@
|
||||
import subprocess, os
|
||||
|
||||
|
||||
def get_strip_chart_function():
|
||||
return strip_chart
|
||||
|
||||
|
||||
def strip_chart(*args, **kwargs):
|
||||
"""Usage: Arguments represent channels in the strip_chart config command line argument.
|
||||
Alternatively arguments can be detectors or adjustables, from which _all_ channels are determined
|
||||
"""
|
||||
channels = list(args)
|
||||
cmd = ["strip_chart"]
|
||||
cmd += ['-config="' + str(channels) + '"']
|
||||
cmd += ["-start"]
|
||||
line = " ".join(cmd)
|
||||
print(f"Starting following commandline silently:\n" + line)
|
||||
with open(os.devnull, "w") as FNULL:
|
||||
subprocess.Popen(line, shell=True, stdout=FNULL, stderr=subprocess.STDOUT)
|
||||
@@ -0,0 +1,2 @@
|
||||
ELOG = None
|
||||
ARCHIVER=None
|
||||
@@ -0,0 +1 @@
|
||||
from .jungfrau import Jungfrau
|
||||
@@ -0,0 +1,188 @@
|
||||
from ..elements.assembly import Assembly
|
||||
from ..aliases import Alias
|
||||
from eco import ecocnf
|
||||
from epics.pv import PV
|
||||
|
||||
# try:
|
||||
# from bsread.bsavail import pollStream
|
||||
# except:
|
||||
# from bsread.unused.bsavail import pollStream
|
||||
from bsread import dispatcher, source
|
||||
from ..epics import get_from_archive
|
||||
from escape import stream
|
||||
from time import time, sleep
|
||||
from eco.acquisition.utilities import Acquisition
|
||||
from eco.acquisition.decorators import scannable
|
||||
from eco.epics.detector import CallbackEpics
|
||||
|
||||
|
||||
@get_from_archive
|
||||
@scannable
|
||||
class DetectorBsStream:
|
||||
def __init__(self, bs_channel, cachannel="same", name=None):
|
||||
self.name = name
|
||||
self.bs_channel = bs_channel
|
||||
if cachannel == "same":
|
||||
self.pvname = bs_channel
|
||||
elif (not cachannel) or cachannel == "none":
|
||||
self.pvname = None
|
||||
else:
|
||||
self.pvname = cachannel
|
||||
if self.pvname:
|
||||
self._pv = PV(self.pvname)
|
||||
self.alias = Alias(name, channel=bs_channel, channeltype="BS")
|
||||
|
||||
self.stream = stream.EscData(source=stream.EventSource(self.bs_channel, None))
|
||||
|
||||
def bs_avail(self):
|
||||
return self.bs_channel in [
|
||||
tmp["name"] for tmp in dispatcher.get_current_channels()
|
||||
]
|
||||
|
||||
def get_current_value(self, force_bsstream=False):
|
||||
if not force_bsstream:
|
||||
if not hasattr(self, "_pv"):
|
||||
return None
|
||||
return self._pv.get()
|
||||
else:
|
||||
raise NotImplementedError(
|
||||
"setup of stream for bs channel not implemented yet"
|
||||
)
|
||||
|
||||
# def get_stream_state(self, timeout=1):
|
||||
# return pollStream(self.bs_channel, timeout=1)
|
||||
|
||||
def create_stream_callback(self, foo):
|
||||
with source(channels=[self.bs_channel]) as s:
|
||||
done = False
|
||||
while not done:
|
||||
done = foo(s.receive())
|
||||
|
||||
def collect(self, seconds=None, samples=None):
|
||||
if (not seconds) and (not samples):
|
||||
raise Exception(
|
||||
"Either a time interval or number of samples need to be defined."
|
||||
)
|
||||
try:
|
||||
self._pv.callbacks.pop(self._collection["ix_cb"])
|
||||
except:
|
||||
pass
|
||||
self._collection = {"done": False}
|
||||
self.data_collected = []
|
||||
if seconds:
|
||||
self._collection["start_time"] = time()
|
||||
self._collection["seconds"] = seconds
|
||||
stopcond = (
|
||||
lambda: (time() - self._collection["start_time"])
|
||||
> self._collection["seconds"]
|
||||
)
|
||||
|
||||
def addData(**kw):
|
||||
if not stopcond():
|
||||
self.data_collected.append(kw["value"])
|
||||
else:
|
||||
try:
|
||||
self._pv.callbacks.pop(self._collection["ix_cb"])
|
||||
except:
|
||||
pass
|
||||
self._collection["done"] = True
|
||||
|
||||
elif samples:
|
||||
self._collection["samples"] = samples
|
||||
stopcond = lambda: len(self.data_collected) >= self._collection["samples"]
|
||||
|
||||
def addData(**kw):
|
||||
self.data_collected.append(kw["value"])
|
||||
if stopcond():
|
||||
try:
|
||||
self._pv.callbacks.pop(self._collection["ix_cb"])
|
||||
except:
|
||||
pass
|
||||
self._collection["done"] = True
|
||||
|
||||
self._collection["ix_cb"] = self._pv.add_callback(addData)
|
||||
time_wait_start = time()
|
||||
while not self._collection["done"]:
|
||||
sleep(0.005)
|
||||
if seconds:
|
||||
if (time() - time_wait_start) > seconds:
|
||||
if len(self.data_collected) == 0:
|
||||
print(
|
||||
f"No {self.name}({self.pvname}) data update in time interval, reporting last value"
|
||||
)
|
||||
self._pv.callbacks.pop(self._collection["ix_cb"])
|
||||
self.data_collected.append(self.get_current_value())
|
||||
break
|
||||
|
||||
return self.data_collected
|
||||
|
||||
def acquire(self, hold=False, seconds=None, samples=None, **kwargs):
|
||||
return Acquisition(
|
||||
acquire=lambda: self.collect(seconds=seconds, samples=samples, **kwargs),
|
||||
hold=hold,
|
||||
stopper=None,
|
||||
get_result=lambda: self.data_collected,
|
||||
)
|
||||
|
||||
def accumulate_ring_buffer(self, n_buffer):
|
||||
if not hasattr(self, "_accumulate"):
|
||||
self._accumulate = {"n_buffer": n_buffer, "ix": 0, "n_cb": -1}
|
||||
else:
|
||||
self._accumulate["n_buffer"] = n_buffer
|
||||
self._accumulate["ix"] = 0
|
||||
self._pv.callbacks.pop(self._accumulate["n_cb"], None)
|
||||
self._data = np.squeeze(np.zeros([n_buffer * 2, self._pv.count])) * np.nan
|
||||
|
||||
def addData(**kw):
|
||||
self._accumulate["ix"] = (self._accumulate["ix"] + 1) % self._accumulate[
|
||||
"n_buffer"
|
||||
]
|
||||
self._data[self._accumulate["ix"] :: self._accumulate["n_buffer"]] = kw[
|
||||
"value"
|
||||
]
|
||||
|
||||
self._accumulate["n_cb"] = self._pv.add_callback(addData)
|
||||
|
||||
def accumulate_start(self):
|
||||
if not hasattr(self, "_accumulate_inf"):
|
||||
self._accumulate_inf = {"n_cb": -1}
|
||||
self._pv.callbacks.pop(self._accumulate_inf["n_cb"], None)
|
||||
self._data_inf = []
|
||||
|
||||
def addData(**kw):
|
||||
self._data_inf.append(kw["value"])
|
||||
|
||||
self._accumulate_inf["n_cb"] = self._pv.add_callback(addData)
|
||||
|
||||
def accumulate_stop(self):
|
||||
self._pv.callbacks.pop(self._accumulate_inf["n_cb"], None)
|
||||
return self._data_inf
|
||||
|
||||
@property
|
||||
def data(self):
|
||||
return self._data[
|
||||
self._accumulate["ix"]
|
||||
+ 1 : self._accumulate["ix"]
|
||||
+ 1
|
||||
+ self._accumulate["n_buffer"]
|
||||
]
|
||||
|
||||
def set_current_value_callback(
|
||||
self, func="accumulate", run_once=True, print_output=False, **kwargs
|
||||
):
|
||||
if hasattr(self, "_pv"):
|
||||
return CallbackEpics(
|
||||
self._pv,
|
||||
func=func,
|
||||
run_once=run_once,
|
||||
print_output=print_output,
|
||||
**kwargs,
|
||||
)
|
||||
|
||||
|
||||
@get_from_archive
|
||||
class DetectorBsCam:
|
||||
def __init__(self, bschannel, name=None):
|
||||
self.name = name
|
||||
self.bschannel = bschannel
|
||||
self.alias = Alias(name, channel=bschannel, channeltype="BSCAM")
|
||||
@@ -0,0 +1,569 @@
|
||||
import shutil
|
||||
import time
|
||||
from tkinter import W
|
||||
|
||||
from eco.base.adjustable import Adjustable
|
||||
from eco.devices_general.therm import ChillerThermotek
|
||||
from eco.elements.adj_obj import AdjustableObject
|
||||
from eco.elements.detector import DetectorGet
|
||||
from ..elements.adjustable import AdjustableFS, AdjustableVirtual, AdjustableGetSet
|
||||
from ..epics.adjustable import AdjustablePv
|
||||
from ..elements.assembly import Assembly
|
||||
from ..aliases import Alias
|
||||
from pathlib import Path
|
||||
from ..elements import memory
|
||||
from datetime import datetime
|
||||
import requests
|
||||
|
||||
|
||||
class JungfrauChannel(Assembly):
|
||||
def __init__(
|
||||
self,
|
||||
jf_id,
|
||||
name=None,
|
||||
):
|
||||
super().__init__(name=name)
|
||||
self.alias = Alias(name, channel=jf_id, channeltype="JF")
|
||||
|
||||
|
||||
class Jungfrau(Assembly):
|
||||
def __init__(
|
||||
self,
|
||||
jf_id,
|
||||
pv_trigger="SAR-CVME-TIFALL5-EVG0:SoftEvt-EvtCode-SP",
|
||||
trigger_on=254,
|
||||
trigger_off=255,
|
||||
broker_address="http://sf-daq:10002",
|
||||
broker_address_aux="http://sf-daq:10003",
|
||||
pgroup_adj=None,
|
||||
config_adj=None,
|
||||
chiller_thermotek="SARES20-CHIL",
|
||||
name=None,
|
||||
):
|
||||
super().__init__(name=name)
|
||||
# self.alias = Alias(name, channel=jf_id, channeltype="JF")
|
||||
self.pgroup = pgroup_adj
|
||||
self.jf_id = jf_id
|
||||
self.broker_address = broker_address
|
||||
self.broker_address_aux = broker_address_aux
|
||||
self._append(
|
||||
DetectorGet, lambda: f"http://{self.get_vis_url()}", name="visulization_url"
|
||||
)
|
||||
self._append(JungfrauChannel, jf_id, name="data")
|
||||
self._append(JungfrauChannel, jf_id + "_rawdata", name="data_raw")
|
||||
self._append(
|
||||
JungfrauChannel, jf_id + "_dap_col4", name="data_online_processing"
|
||||
)
|
||||
for n in range(10):
|
||||
self._append(
|
||||
JungfrauChannel,
|
||||
jf_id + f"_dap_col{n+4}",
|
||||
name=f"data_online_processing_roi{n}",
|
||||
is_display=False,
|
||||
)
|
||||
self._append(
|
||||
JungfrauChannel, jf_id + "_dap_col3", name="ppref_online_processing"
|
||||
)
|
||||
self._append(
|
||||
AdjustablePv,
|
||||
pv_trigger,
|
||||
is_display=True,
|
||||
is_setting=False,
|
||||
name="trigger",
|
||||
)
|
||||
self._trigger_on = trigger_on
|
||||
self._trigger_off = trigger_off
|
||||
self._append(
|
||||
AdjustableVirtual,
|
||||
[self.trigger],
|
||||
lambda value: value == self._trigger_on,
|
||||
self._set_trigger_enable,
|
||||
name="trigger_enable",
|
||||
append_aliases=False,
|
||||
is_setting=True,
|
||||
)
|
||||
self._append(
|
||||
AdjustableGetSet,
|
||||
self.get_present_pedestal_filename,
|
||||
lambda value: NotImplementedError(
|
||||
"Can not set the pedestal file manually yet."
|
||||
),
|
||||
name="pedestal_file",
|
||||
is_display=True,
|
||||
)
|
||||
self._append(
|
||||
AdjustableGetSet,
|
||||
self.get_present_gain_filename,
|
||||
lambda value: NotImplementedError(
|
||||
"Can not set the pedestal file manually yet."
|
||||
),
|
||||
name="gain_file",
|
||||
is_display=True,
|
||||
)
|
||||
self._append(
|
||||
AdjustableGetSet,
|
||||
self.get_present_pedestal_filename_in_run,
|
||||
lambda value: NotImplementedError(
|
||||
"Can not set the pedestal file manually yet."
|
||||
),
|
||||
name="pedestal_file_in_run",
|
||||
is_display=True,
|
||||
)
|
||||
self._append(
|
||||
AdjustableGetSet,
|
||||
self.get_present_gain_filename_in_run,
|
||||
lambda value: NotImplementedError(
|
||||
"Can not set the pedestal file manually yet."
|
||||
),
|
||||
name="gain_file_in_run",
|
||||
is_display=True,
|
||||
)
|
||||
self._last_dap_req_time = 0
|
||||
self._append(
|
||||
AdjustableFS,
|
||||
'/sf/bernina/code/gac-bernina/eco_cnf_bernina/reference_values/dap_settings',
|
||||
name="_dap_settings_storage",
|
||||
is_display=False,
|
||||
is_setting=False,
|
||||
)
|
||||
self._append(
|
||||
AdjustableGetSet,
|
||||
self.get_dap_settings,
|
||||
self.set_dap_settings,
|
||||
name="_dap_settings",
|
||||
is_display=False,
|
||||
is_setting=False,
|
||||
)
|
||||
self._append(
|
||||
AdjustableObject,
|
||||
self._dap_settings,
|
||||
is_setting_children=True,
|
||||
name="settings_dap",
|
||||
)
|
||||
|
||||
|
||||
if config_adj:
|
||||
self._append(
|
||||
JungfrauDaqConfig,
|
||||
jf_id,
|
||||
config_adj,
|
||||
name="config_daq",
|
||||
is_setting=True,
|
||||
is_status=True,
|
||||
is_display="recursive",
|
||||
)
|
||||
if chiller_thermotek:
|
||||
self._append(
|
||||
ChillerThermotek,
|
||||
pvbase=chiller_thermotek,
|
||||
name="chiller",
|
||||
is_display="recursive",
|
||||
)
|
||||
|
||||
def set_dap_rois(self,*rois):
|
||||
tmp = self.settings_dap._base_dict()
|
||||
tmp['roi_x1']=[roi[0] for roi in rois if roi]
|
||||
tmp['roi_x2']=[roi[1] for roi in rois if roi]
|
||||
tmp['roi_y1']=[roi[2] for roi in rois if roi]
|
||||
tmp['roi_y2']=[roi[3] for roi in rois if roi]
|
||||
self.settings_dap._base_dict(tmp)
|
||||
|
||||
def _set_trigger_enable(self, value):
|
||||
if value:
|
||||
self.trigger.set_target_value(self._trigger_on).wait()
|
||||
else:
|
||||
self.trigger.set_target_value(self._trigger_off).wait()
|
||||
|
||||
def get_present_gain_filename(self):
|
||||
filepath = Path(f"/sf/jungfrau/config/gainMaps/{self.jf_id}/gains.h5")
|
||||
|
||||
if filepath.exists():
|
||||
return filepath.as_posix()
|
||||
else:
|
||||
raise Exception(f"File {filepath.as_posix()} seems not to exist!")
|
||||
|
||||
def get_present_gain_filename_in_run(self, intempdir=False):
|
||||
f = Path(self.get_present_gain_filename())
|
||||
dest = Path(
|
||||
f"/sf/bernina/data/{self.pgroup()}/res/tmp/gainmaps_{self.jf_id}.h5"
|
||||
)
|
||||
|
||||
try:
|
||||
if not dest.exists():
|
||||
dest.parent.mkdir(parents=True, exist_ok=True, mode=0o775)
|
||||
try:
|
||||
dest.parent.chmod(0o775)
|
||||
except:
|
||||
pass
|
||||
shutil.copyfile(f, dest)
|
||||
|
||||
except PermissionError:
|
||||
return "No permissions to res directory!"
|
||||
|
||||
if intempdir:
|
||||
return dest.as_posix()
|
||||
else:
|
||||
return f"aux/{dest.name}"
|
||||
|
||||
def get_present_pedestal_filename(self):
|
||||
searchpath = Path(f"/sf/jungfrau/data/pedestal/{self.jf_id}")
|
||||
filelist = list(searchpath.glob("*.h5"))
|
||||
times = [datetime.strptime(f.stem, "%Y%m%d_%H%M%S") for f in filelist]
|
||||
return filelist[times.index(max(times))].as_posix()
|
||||
|
||||
def get_present_pedestal_filename_in_run(self, intempdir=False):
|
||||
f = Path(self.get_present_pedestal_filename())
|
||||
dest = Path(
|
||||
f"/sf/bernina/data/{self.pgroup()}/res/tmp/pedestal_{self.jf_id}_{f.stem}.h5"
|
||||
)
|
||||
try:
|
||||
if not dest.exists():
|
||||
dest.parent.mkdir(parents=True, exist_ok=True, mode=0o775)
|
||||
try:
|
||||
dest.parent.chmod(0o775)
|
||||
except:
|
||||
pass
|
||||
shutil.copyfile(f, dest)
|
||||
except PermissionError:
|
||||
return "No poermissions to res directory!"
|
||||
|
||||
if intempdir:
|
||||
return dest.as_posix()
|
||||
else:
|
||||
return f"aux/{dest.name}"
|
||||
|
||||
def get_dap_settings(self, force=False):
|
||||
|
||||
if force:
|
||||
if 5 < (time.time() - self._last_dap_req_time):
|
||||
self._last_dap_message = requests.get(
|
||||
f"{self.broker_address_aux}/get_dap_settings",
|
||||
json={"detector_name": self.jf_id},
|
||||
).json()
|
||||
self._last_dap_req_time = time.time()
|
||||
|
||||
if self._last_dap_message["status"] == "ok":
|
||||
self._dap_settings_storage.set_target_value(self._last_dap_message["parameters"]).wait()
|
||||
return self._last_dap_message["parameters"]
|
||||
else:
|
||||
val = self._dap_settings_storage.get_current_value()
|
||||
if not val:
|
||||
val = self.get_dap_settings(force=True)
|
||||
return val
|
||||
|
||||
def set_dap_settings(self, dap_setting_dict):
|
||||
# print("Setting not implmented yet!")
|
||||
# return
|
||||
m = requests.post(
|
||||
f"{self.broker_address_aux}/set_dap_settings",
|
||||
json={"detector_name": self.jf_id, "parameters": dap_setting_dict},
|
||||
).json()
|
||||
if m["status"] == "ok":
|
||||
self._dap_settings_storage.set_target_value(dap_setting_dict).wait()
|
||||
return m
|
||||
|
||||
def get_detector_frequency(self):
|
||||
return self._event_master.event_codes[
|
||||
self._detectors_event_code
|
||||
].frequency.get_current_value()
|
||||
|
||||
def get_availability(self):
|
||||
is_available = (
|
||||
self.jf_id
|
||||
in requests.get(f"{self.broker_address}/get_allowed_detectors").json()[
|
||||
"detectors"
|
||||
]
|
||||
)
|
||||
return is_available
|
||||
|
||||
def get_vis_url(self):
|
||||
tmp = requests.get(f"{self.broker_address}/get_allowed_detectors").json()
|
||||
ix = tmp["detectors"].index(self.jf_id)
|
||||
return tmp["visualisation_address"][ix]
|
||||
|
||||
def get_isrunning(self):
|
||||
is_running = (
|
||||
self.jf_id
|
||||
in requests.get(f"{self.broker_address}/get_running_detectors").json()[
|
||||
"detectors"
|
||||
]
|
||||
)
|
||||
return is_running
|
||||
|
||||
def power_on(self):
|
||||
JF_channel = self.jf_id
|
||||
par = {"detector_name": JF_channel}
|
||||
return requests.post(
|
||||
f"{self.broker_address}/power_on_detector", json=par
|
||||
).json()
|
||||
|
||||
# def take_pedestal(self, JF_list=None, pgroup=None):
|
||||
# if pgroup is None:
|
||||
# pgroup = self.pgroup
|
||||
# if not JF_list:
|
||||
# JF_list = self.get_JFs_running()
|
||||
# parameters = {
|
||||
# "pgroup": pgroup,
|
||||
# "rate_multiplicator": 1,adc_to_energy
|
||||
# "detectors": {tJF: {} for tJF in JF_list},
|
||||
# }
|
||||
# return requests.post(
|
||||
# f"{self.broker_address}/take_pedestal", json=parameters
|
||||
# ).json()
|
||||
|
||||
|
||||
class JungfrauDaqConfig(Assembly):
|
||||
def __init__(self, jf_id, jf_daq_cfg: Adjustable, name=None):
|
||||
super().__init__(name=name)
|
||||
self._jf_id = jf_id
|
||||
self._jf_daq_cfg = jf_daq_cfg
|
||||
cfg = self._jf_daq_cfg.get_current_value()
|
||||
if self._jf_id not in cfg.keys():
|
||||
cfg[self._jf_id] = {}
|
||||
self._jf_daq_cfg.set_target_value(cfg).wait()
|
||||
|
||||
self._append(
|
||||
AdjustableGetSet,
|
||||
self._get_adc_to_energy,
|
||||
self._set_adc_to_energy,
|
||||
name="convert_adc_to_energy",
|
||||
is_display=True,
|
||||
is_setting=True,
|
||||
)
|
||||
self._append(
|
||||
AdjustableGetSet,
|
||||
self._get_geometry_corr,
|
||||
self._set_geometry_corr,
|
||||
name="apply_tile_geometry",
|
||||
is_display=True,
|
||||
is_setting=True,
|
||||
)
|
||||
|
||||
self._append(
|
||||
AdjustableGetSet,
|
||||
self._get_compressed_bitshuffle,
|
||||
self._set_compressed_bitshuffle,
|
||||
name="compress_bitshuffle",
|
||||
is_display=True,
|
||||
is_setting=True,
|
||||
)
|
||||
|
||||
self._append(
|
||||
AdjustableGetSet,
|
||||
self._get_rounding_factor,
|
||||
self._set_rounding_factor,
|
||||
name="rounding_factor_keV",
|
||||
is_display=True,
|
||||
is_setting=True,
|
||||
)
|
||||
|
||||
self._append(
|
||||
AdjustableGetSet,
|
||||
self._get_large_pixel_processing,
|
||||
self._set_large_pixel_processing,
|
||||
name="large_pixel_processing",
|
||||
is_display=True,
|
||||
is_setting=True,
|
||||
)
|
||||
|
||||
self._append(
|
||||
AdjustableGetSet,
|
||||
self._get_disabled_modules,
|
||||
self._set_disabled_modules,
|
||||
name="disabled_tiles",
|
||||
is_display=True,
|
||||
is_setting=True,
|
||||
)
|
||||
self._append(
|
||||
AdjustableGetSet,
|
||||
self._get_binning,
|
||||
self._set_binning,
|
||||
name="downsample",
|
||||
is_display=True,
|
||||
is_setting=True,
|
||||
)
|
||||
self._append(
|
||||
AdjustableGetSet,
|
||||
self._get_keep_raw_data,
|
||||
self._set_keep_raw_data,
|
||||
name="keep_raw_data",
|
||||
is_display=True,
|
||||
is_setting=True,
|
||||
)
|
||||
self._append(
|
||||
AdjustableGetSet,
|
||||
self._get_save_online_processing,
|
||||
self._set_save_online_processing,
|
||||
name="save_online_processing",
|
||||
is_display=True,
|
||||
is_setting=True,
|
||||
)
|
||||
|
||||
def _get_adc_to_energy(self, *args):
|
||||
try:
|
||||
return self._jf_daq_cfg.get_current_value()[self._jf_id]["adc_to_energy"]
|
||||
except KeyError:
|
||||
return False
|
||||
|
||||
def _set_adc_to_energy(self, value):
|
||||
if value:
|
||||
cfg = self._jf_daq_cfg.get_current_value()
|
||||
cfg[self._jf_id]["adc_to_energy"] = True
|
||||
self._jf_daq_cfg.set_target_value(cfg).wait()
|
||||
else:
|
||||
cfg = self._jf_daq_cfg.get_current_value()
|
||||
cfg[self._jf_id]["adc_to_energy"] = False
|
||||
self._jf_daq_cfg.set_target_value(cfg).wait()
|
||||
|
||||
def _get_geometry_corr(self, *args):
|
||||
try:
|
||||
return self._jf_daq_cfg.get_current_value()[self._jf_id]["geometry"]
|
||||
except KeyError:
|
||||
return "not sure what happens"
|
||||
|
||||
def _set_geometry_corr(self, value):
|
||||
if value:
|
||||
cfg = self._jf_daq_cfg.get_current_value()
|
||||
cfg[self._jf_id]["geometry"] = True
|
||||
self._jf_daq_cfg.set_target_value(cfg).wait()
|
||||
else:
|
||||
cfg = self._jf_daq_cfg.get_current_value()
|
||||
cfg[self._jf_id]["geometry"] = False
|
||||
self._jf_daq_cfg.set_target_value(cfg).wait()
|
||||
|
||||
def _get_compressed_bitshuffle(self, *args):
|
||||
try:
|
||||
return self._jf_daq_cfg.get_current_value()[self._jf_id]["compression"]
|
||||
except KeyError:
|
||||
return False
|
||||
|
||||
def _set_compressed_bitshuffle(self, value):
|
||||
if value:
|
||||
cfg = self._jf_daq_cfg.get_current_value()
|
||||
cfg[self._jf_id]["compression"] = True
|
||||
self._jf_daq_cfg.set_target_value(cfg).wait()
|
||||
else:
|
||||
cfg = self._jf_daq_cfg.get_current_value()
|
||||
cfg[self._jf_id]["compression"] = False
|
||||
self._jf_daq_cfg.set_target_value(cfg).wait()
|
||||
|
||||
def _get_save_online_processing(self, *args):
|
||||
try:
|
||||
return self._jf_daq_cfg.get_current_value()[self._jf_id]["save_dap_results"]
|
||||
except KeyError:
|
||||
# raise Exception("unclear what the default for keeping raw files is!")
|
||||
return None
|
||||
|
||||
def _set_save_online_processing(self, value):
|
||||
if value:
|
||||
cfg = self._jf_daq_cfg.get_current_value()
|
||||
cfg[self._jf_id]["save_dap_results"] = True
|
||||
self._jf_daq_cfg.set_target_value(cfg).wait()
|
||||
else:
|
||||
cfg = self._jf_daq_cfg.get_current_value()
|
||||
cfg[self._jf_id]["save_dap_results"] = False
|
||||
self._jf_daq_cfg.set_target_value(cfg).wait()
|
||||
|
||||
def _get_keep_raw_data(self, *args):
|
||||
try:
|
||||
return not self._jf_daq_cfg.get_current_value()[self._jf_id][
|
||||
"remove_raw_files"
|
||||
]
|
||||
except KeyError:
|
||||
# raise Exception("unclear what the default for keeping raw files is!")
|
||||
return None
|
||||
|
||||
def _set_keep_raw_data(self, value):
|
||||
if value:
|
||||
cfg = self._jf_daq_cfg.get_current_value()
|
||||
cfg[self._jf_id]["remove_raw_files"] = False
|
||||
self._jf_daq_cfg.set_target_value(cfg).wait()
|
||||
else:
|
||||
cfg = self._jf_daq_cfg.get_current_value()
|
||||
cfg[self._jf_id]["remove_raw_files"] = True
|
||||
self._jf_daq_cfg.set_target_value(cfg).wait()
|
||||
|
||||
def _get_large_pixel_processing(self, *args):
|
||||
try:
|
||||
return self._jf_daq_cfg.get_current_value()[self._jf_id][
|
||||
"double_pixels_action"
|
||||
]
|
||||
except KeyError:
|
||||
# raise Exception("unclear what the default for double pixels is!")
|
||||
return None
|
||||
|
||||
def _set_large_pixel_processing(self, value):
|
||||
cfg = self._jf_daq_cfg.get_current_value()
|
||||
cfg[self._jf_id]["double_pixels_action"] = value
|
||||
self._jf_daq_cfg.set_target_value(cfg).wait()
|
||||
|
||||
def _get_rounding_factor(self, *args):
|
||||
try:
|
||||
return self._jf_daq_cfg.get_current_value()[self._jf_id]["factor"]
|
||||
except KeyError:
|
||||
# raise Exception("unclear what the default for double pixels is!")
|
||||
return None
|
||||
|
||||
def _set_rounding_factor(self, value):
|
||||
cfg = self._jf_daq_cfg.get_current_value()
|
||||
cfg[self._jf_id]["factor"] = value
|
||||
self._jf_daq_cfg.set_target_value(cfg).wait()
|
||||
|
||||
def _get_disabled_modules(self, *args):
|
||||
try:
|
||||
return self._jf_daq_cfg.get_current_value()[self._jf_id]["disabled_modules"]
|
||||
except KeyError:
|
||||
return []
|
||||
|
||||
def _set_disabled_modules(self, value):
|
||||
cfg = self._jf_daq_cfg.get_current_value()
|
||||
if value == []:
|
||||
cfg[self._jf_id].pop("disabled_modules")
|
||||
else:
|
||||
cfg[self._jf_id]["disabled_modules"] = value
|
||||
self._jf_daq_cfg.set_target_value(cfg).wait()
|
||||
|
||||
def _get_binning(self, *args):
|
||||
try:
|
||||
return self._jf_daq_cfg.get_current_value()[self._jf_id]["downsample"]
|
||||
except KeyError:
|
||||
return [1, 1]
|
||||
|
||||
def _set_binning(self, value):
|
||||
cfg = self._jf_daq_cfg.get_current_value()
|
||||
if value == [1, 1]:
|
||||
cfg[self._jf_id].pop("downsample")
|
||||
else:
|
||||
cfg[self._jf_id]["downsample"] = value
|
||||
self._jf_daq_cfg.set_target_value(cfg).wait()
|
||||
|
||||
def _get_keepraw(self, *args):
|
||||
try:
|
||||
remove_raw = self._jf_daq_cfg.get_current_value()[self._jf_id][
|
||||
"remove_raw_files"
|
||||
]
|
||||
# if type(remove_raw) is bool:
|
||||
return remove_raw
|
||||
|
||||
except KeyError:
|
||||
return "not sure what happens"
|
||||
|
||||
def _set_keepraw(self, value):
|
||||
cfg = self._jf_daq_cfg.get_current_value()
|
||||
cfg[self._jf_id]["remove_raw_files"] = value
|
||||
self._jf_daq_cfg.set_target_value(cfg).wait()
|
||||
|
||||
|
||||
# {
|
||||
# "adc_to_energy": true,
|
||||
# "compression": true,
|
||||
# "double_pixels_actions": "interpolate",
|
||||
# "downsample": [
|
||||
# 1,
|
||||
# 1
|
||||
# ],
|
||||
# "factor": 0.25,x
|
||||
# "geometry": true,
|
||||
# "remove_raw_files": false
|
||||
# "disabled_modules": [],
|
||||
# },
|
||||
Binary file not shown.
Executable
+299
@@ -0,0 +1,299 @@
|
||||
#!/usr/bin/env python
|
||||
# *-----------------------------------------------------------------------*
|
||||
# | |
|
||||
# | Copyright (c) 2015 by Paul Scherrer Institute (http://www.psi.ch) |
|
||||
# | |
|
||||
# | Author Thierry Zamofing (thierry.zamofing@psi.ch) |
|
||||
# *-----------------------------------------------------------------------*
|
||||
"""
|
||||
PBComm:
|
||||
SSH communication class with gpascii.
|
||||
"""
|
||||
|
||||
# backup Motor[1].status
|
||||
# backup Coord[x].Status
|
||||
# backup Sys.Status
|
||||
# ? -> $00000080
|
||||
|
||||
from __future__ import print_function
|
||||
|
||||
# import wx
|
||||
import time
|
||||
import paramiko, re, os
|
||||
|
||||
# WX3=(wx.VERSION[0]==3) #old version 3.x
|
||||
|
||||
import logging
|
||||
|
||||
_log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def debug(*args):
|
||||
_log.debug(" ".join(map(str, args)))
|
||||
|
||||
|
||||
class SSHComm:
|
||||
"""Communicates with the Delta Tau gpascii programm wia SSH"""
|
||||
|
||||
gpascii_ack = "\r\n\x06\r\n"
|
||||
gpascii_inp = "Input\r\n"
|
||||
|
||||
def __init__(self):
|
||||
self.reqSet = set()
|
||||
# def __init__(self,args=None):
|
||||
# self.args=args
|
||||
self.defDictStack = [
|
||||
dict()
|
||||
] # definition dictionaties. stacked with OPEN and CLOSE commands
|
||||
|
||||
pass
|
||||
|
||||
def connect(self, host, password="deltatau", timeout=30.0):
|
||||
"start communication to gpascii by starting a SSH session (if needed and gpascii on the PowerBrick"
|
||||
p = host.rfind(":")
|
||||
if p >= 0:
|
||||
hostname = host[:p]
|
||||
port = int(host[p + 1 :])
|
||||
else:
|
||||
hostname = host
|
||||
port = 22
|
||||
p = hostname.rfind("@")
|
||||
if p >= 0:
|
||||
username = hostname[:p]
|
||||
hostname = hostname[p + 1 :]
|
||||
else:
|
||||
username = "root"
|
||||
|
||||
self.client = client = paramiko.SSHClient()
|
||||
client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
||||
# client.set_missing_host_key_policy(paramiko.WarningPolicy())
|
||||
cfg = {
|
||||
"hostname": hostname,
|
||||
"port": port,
|
||||
"username": username,
|
||||
"password": password,
|
||||
"timeout": timeout,
|
||||
}
|
||||
|
||||
try:
|
||||
with open(os.path.expanduser("~/.ssh/config")) as fh:
|
||||
ssh_config = paramiko.SSHConfig()
|
||||
ssh_config.parse(fh)
|
||||
except IOError as e:
|
||||
pass
|
||||
else:
|
||||
host_conf = ssh_config.lookup(hostname)
|
||||
for k, v in host_conf.items():
|
||||
if k == "hostname":
|
||||
cfg[k] = v
|
||||
elif k == "user":
|
||||
cfg["username"] = v
|
||||
elif k == "port":
|
||||
cfg[k] = int(v)
|
||||
elif k == "identityfile":
|
||||
cfg["key_filename"] = os.path.expanduser(v[0])
|
||||
# elif k == "stricthostkeychecking" and v == "no":
|
||||
# client.set_missing_host_key_policy(paramiko.WarningPolicy())
|
||||
elif k == "requesttty":
|
||||
self.get_pty = v in ("yes", "force")
|
||||
elif k == "gssapikeyexchange":
|
||||
cfg["gss_auth"] = v == "yes"
|
||||
elif k == "gssapiauthentication":
|
||||
cfg["gss_kex"] = v == "yes"
|
||||
elif k == "proxycommand":
|
||||
cfg["sock"] = paramiko.ProxyCommand(v)
|
||||
|
||||
client.connect(**cfg)
|
||||
self.gpascii()
|
||||
|
||||
def gpascii(self):
|
||||
self.chan = (
|
||||
chan
|
||||
) = self.client.invoke_shell() # -> returns a paramiko.channel.Channel
|
||||
chan.settimeout(1.0)
|
||||
self.read_until("root@.*?# ")
|
||||
chan.send("stty -echo\n") # dont't echo the send data
|
||||
chan.send("gpascii -2\n")
|
||||
try:
|
||||
self.read_until(".*?STDIN Open for ASCII Input")
|
||||
except TimeoutError as err:
|
||||
raise ValueError("GPASCII startup string not found")
|
||||
|
||||
# chan.send('#1..8p\n')
|
||||
# print(self.read_until_endswith(SSHComm.gpascii_ack).rstrip(SSHComm.gpascii_ack))
|
||||
# print('done')
|
||||
|
||||
def read_until(self, regex, timeout=1.0):
|
||||
"SSH wait, for regex in buffer up to `timeout` seconds,"
|
||||
chan = self.chan
|
||||
wait_re = re.compile(regex)
|
||||
t0 = time.time()
|
||||
buf = ""
|
||||
|
||||
while True:
|
||||
if chan.recv_ready():
|
||||
t0 = time.time() # reset timeout
|
||||
s = chan.recv(4096).decode()
|
||||
debug(repr(s))
|
||||
buf += s
|
||||
m = wait_re.search(buf)
|
||||
if m:
|
||||
return (buf, m.span())
|
||||
elif (time.time() - t0) > timeout:
|
||||
raise TimeoutError(repr(buf))
|
||||
else:
|
||||
time.sleep(0.001)
|
||||
|
||||
def read_until_endswith(self, txt, timeout=1.0):
|
||||
"SSH wait, up until received data ends with text up to `timeout` seconds"
|
||||
chan = self.chan
|
||||
t0 = time.time()
|
||||
buf = ""
|
||||
while True:
|
||||
if chan.recv_ready():
|
||||
t0 = time.time() # reset timeout
|
||||
s = chan.recv(4096).decode()
|
||||
debug(repr(s))
|
||||
buf += s
|
||||
if buf.endswith(txt):
|
||||
return buf
|
||||
elif (time.time() - t0) > timeout:
|
||||
raise TimeoutError(repr(buf))
|
||||
else:
|
||||
time.sleep(0.001)
|
||||
|
||||
def iawrite(self, msg):
|
||||
"""interactive write. if 1 is returned, nothing is done"""
|
||||
m = re.match("\s*(define|open|close)", msg, re.IGNORECASE)
|
||||
if m:
|
||||
kw = m.group(1).lower()
|
||||
if kw == "open":
|
||||
self.defDictStack.append(dict())
|
||||
print(self.defDictStack)
|
||||
elif kw == "close":
|
||||
if len(self.defDictStack) > 1:
|
||||
del self.defDictStack[-1]
|
||||
else:
|
||||
print("close stack error")
|
||||
print(self.defDictStack)
|
||||
elif kw == "define":
|
||||
m = re.match("\s*define\s*\(([^)]*)", msg, re.IGNORECASE)
|
||||
if m:
|
||||
args = eval("dict(" + m.group(1) + ")")
|
||||
self.defDictStack[-1].update(args)
|
||||
print(self.defDictStack)
|
||||
return 1
|
||||
for defDic in reversed(self.defDictStack):
|
||||
for k, v in defDic.items():
|
||||
msg = re.sub(
|
||||
r"(\W|^)" + k + r"(?=\W|$)", r"\g<1>" + str(v), msg
|
||||
) # define must be separated with non-alphanumeric character [^a-zA-Z0-9_], or at begin/end
|
||||
|
||||
if False:
|
||||
# self.args.dryrun:
|
||||
# print(msg)
|
||||
return 1
|
||||
|
||||
elif hasattr(self, "chan"):
|
||||
self.chan.send(msg + "\n")
|
||||
else:
|
||||
print("nothing done! no opened connection.")
|
||||
return 1
|
||||
return None
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import argparse # since python 2.7
|
||||
|
||||
def ParseArgs(required=True):
|
||||
parser = argparse.ArgumentParser(
|
||||
description=__doc__, formatter_class=argparse.RawTextHelpFormatter
|
||||
)
|
||||
parser.add_argument(
|
||||
"--host",
|
||||
default="SARES20-CPPM-EXP1",
|
||||
help="the hostname (default=%(default)s)",
|
||||
)
|
||||
return parser.parse_args()
|
||||
|
||||
# --- main code ---
|
||||
logging.basicConfig(
|
||||
level=logging.DEBUG,
|
||||
format="%(levelname).1s:%(module)s:%(lineno)d:%(funcName)s:%(message)s ",
|
||||
)
|
||||
args = ParseArgs()
|
||||
|
||||
gpascii = SSHComm()
|
||||
gpascii.connect(args.host)
|
||||
chan = gpascii.chan
|
||||
cmd = {
|
||||
b"EncTable[1].PrevEnc\n",
|
||||
b"EncTable[3].PrevEnc\n",
|
||||
b"#9;p\n",
|
||||
b"Motor[5].pos\n",
|
||||
b"EncTable[7].PrevEnc\n",
|
||||
b"Motor[4].idCmd\n",
|
||||
b"Motor[2].Ctrl\n",
|
||||
b"Motor[2].pos\n",
|
||||
b"EncTable[4].PrevEnc\n",
|
||||
b"Motor[1].Ctrl\n",
|
||||
b"#8?\n",
|
||||
b"#13;p\n",
|
||||
b"EncTable[2].PrevEnc\n",
|
||||
b"Motor[5].idCmd\n",
|
||||
b"#5?\n",
|
||||
b"EncTable[5].PrevEnc\n",
|
||||
b"Motor[2].idCmd\n",
|
||||
b"#1;p\n",
|
||||
b"#2;p\n",
|
||||
b"#7?\n",
|
||||
b"Motor[4].Ctrl\n",
|
||||
b"Motor[8].idCmd\n",
|
||||
b"#12;p\n",
|
||||
b"Motor[6].idCmd\n",
|
||||
b"#14;p\n",
|
||||
b"#5;p\n",
|
||||
b"Motor[5].Ctrl\n",
|
||||
b"#6?\n",
|
||||
b"Motor[4].pos\n",
|
||||
b"Motor[1].idCmd\n",
|
||||
b"Motor[3].pos\n",
|
||||
b"#10;p\n",
|
||||
b"#15;p\n",
|
||||
b"#4?\n",
|
||||
b"#6;p\n",
|
||||
b"Motor[3].idCmd\n",
|
||||
b"#8;p\n",
|
||||
b"Motor[7].Ctrl\n",
|
||||
b"#7;p\n",
|
||||
b"Motor[8].Ctrl\n",
|
||||
b"Motor[6].pos\n",
|
||||
b"#3;p\n",
|
||||
b"?\n",
|
||||
b"EncTable[8].PrevEnc\n",
|
||||
b"#11;p\n",
|
||||
b"#1?\n",
|
||||
b"Motor[8].pos\n",
|
||||
b"Motor[7].pos\n",
|
||||
b"#3?\n",
|
||||
b"EncTable[6].PrevEnc\n",
|
||||
b"#2?\n",
|
||||
b"#16;p\n",
|
||||
b"#4;p\n",
|
||||
b"Motor[7].idCmd\n",
|
||||
b"Motor[3].Ctrl\n",
|
||||
b"Motor[1].pos\n",
|
||||
b"Motor[6].Ctrl\n",
|
||||
}
|
||||
c = ""
|
||||
for k in cmd:
|
||||
c += k.decode()
|
||||
fn = "/tmp/PBComm.log"
|
||||
fh = open(fn, "w")
|
||||
fh.write(c + "\n\n\n***\n")
|
||||
for i in range(100):
|
||||
chan.send(c)
|
||||
res = gpascii.read_until_endswith(SSHComm.gpascii_ack)
|
||||
fh.write(res)
|
||||
print(i, len(res))
|
||||
print(fn + " generated.")
|
||||
Executable
+379
@@ -0,0 +1,379 @@
|
||||
import subprocess
|
||||
from threading import Thread
|
||||
from epics import PV, ca
|
||||
import time
|
||||
from ..eco_epics import device
|
||||
from ..eco_epics.device import Device
|
||||
from .utilities import Changer
|
||||
from ..aliases import Alias
|
||||
|
||||
|
||||
_guiTypes = ["xdm"]
|
||||
|
||||
_status_messages = {
|
||||
-13: "invalid value (cannot convert to float). Move not attempted.",
|
||||
-12: "target value outside soft limits. Move not attempted.",
|
||||
-11: "drive PV is not connected: Move not attempted.",
|
||||
-8: "move started, but timed-out.",
|
||||
-7: "move started, timed-out, but appears done.",
|
||||
-5: "move started, unexpected return value from PV.put()",
|
||||
-4: "move-with-wait finished, soft limit violation seen",
|
||||
-3: "move-with-wait finished, hard limit violation seen",
|
||||
0: "move-with-wait finish OK.",
|
||||
0: "move-without-wait executed, not cpmfirmed",
|
||||
1: "move-without-wait executed, move confirmed",
|
||||
3: "move-without-wait finished, hard limit violation seen",
|
||||
4: "move-without-wait finished, soft limit violation seen",
|
||||
}
|
||||
|
||||
|
||||
def _keywordChecker(kw_key_list_tups):
|
||||
for tkw, tkey, tlist in kw_key_list_tups:
|
||||
assert tkey in tlist, "Keyword %s should be one of %s" % (tkw, tlist)
|
||||
|
||||
|
||||
class SmarActException(Exception):
|
||||
""" raised to indicate a problem with a smartact"""
|
||||
|
||||
def __init__(self, msg, *args):
|
||||
Exception.__init__(self, *args)
|
||||
self.msg = msg
|
||||
|
||||
def __str__(self):
|
||||
return str(self.msg)
|
||||
|
||||
|
||||
class SmarAct(Device):
|
||||
_extras = {"disabled": "_able.VAL"}
|
||||
_init_list = ("VAL", "DESC", "RTYP")
|
||||
_nonpvs = ("_prefix", "_pvs", "_delim", "_init", "_init_list", "_alias", "_extras")
|
||||
|
||||
def __init__(self, name=None, timeout=3.0, record=None):
|
||||
if name is None:
|
||||
raise SmarActException("must supply SmarAct name")
|
||||
|
||||
if name.endswith(".VAL"):
|
||||
name = name[:-4]
|
||||
if name.endswith("."):
|
||||
name = name[:-1]
|
||||
|
||||
self._prefix = name
|
||||
self._record = record
|
||||
self._callbacks = {}
|
||||
|
||||
device.Device.__init__(
|
||||
self, name, delim=".", attrs=self._init_list, timeout=timeout
|
||||
)
|
||||
|
||||
|
||||
# for key, val in self._extras.items():
|
||||
# pvname = "%s%s" % (name, val)
|
||||
# self.add_pv(pvname, attr=key)
|
||||
|
||||
# self.put('disabled', 0)
|
||||
|
||||
|
||||
class SmarActRecord:
|
||||
def __init__(
|
||||
self,
|
||||
Id,
|
||||
name=None,
|
||||
elog=None,
|
||||
alias_fields={"readback": "MOTRBV", "homed": "GET_HOMED"},
|
||||
):
|
||||
|
||||
self.Id = Id
|
||||
self._drive = SmarAct(Id + ":DRIVE")
|
||||
self._rbv = SmarAct(Id + ":MOTRBV")
|
||||
self._hlm = SmarAct(Id + ":HLM")
|
||||
self._llm = SmarAct(Id + ":LLM")
|
||||
self._statusstg = SmarAct(Id + ":STATUS")
|
||||
self._status = []
|
||||
self._set_pos = SmarAct(Id + ":SET_POS")
|
||||
self._stop = SmarAct(Id + ":STOP")
|
||||
self._hold = SmarAct(Id + ":HOLD")
|
||||
self._twv = SmarAct(Id + ":TWV")
|
||||
self._elog = elog
|
||||
self.name = name
|
||||
self.units = self._drive.get("EGU")
|
||||
self.alias = Alias(name)
|
||||
for an, af in alias_fields.items():
|
||||
self.alias.append(
|
||||
Alias(an, channel=".".join([self.Id, af]), channeltype="CA")
|
||||
)
|
||||
|
||||
# Conventional methods and properties for all Adjustable objects
|
||||
def changeTo(self, value, hold=False, check=True):
|
||||
""" Adjustable convention"""
|
||||
|
||||
def changer(value):
|
||||
self._status = self.move(value, ignore_limits=(not check), wait=True)
|
||||
self._status_message = _status_messages[self._status]
|
||||
if not self._status == 0:
|
||||
#print(self._status_message)
|
||||
|
||||
# mover = lambda value: self.move(\
|
||||
# value, ignore_limits=(not check),
|
||||
# wait=True)
|
||||
return Changer(
|
||||
target=value,
|
||||
parent=self,
|
||||
changer=changer,
|
||||
hold=hold,
|
||||
stopper=self._stop.put("PROC", 1),
|
||||
)
|
||||
|
||||
def stop(self):
|
||||
""" Adjustable convention"""
|
||||
try:
|
||||
self._currentChange.stop()
|
||||
except:
|
||||
self._stop.put("VAL", 1)
|
||||
pass
|
||||
|
||||
def within_limits(self, val):
|
||||
""" returns whether a value for a motor is within drive limits"""
|
||||
return val <= self._hlm.get("VAL") and val >= self._llm.get("VAL")
|
||||
|
||||
def move(
|
||||
self,
|
||||
val,
|
||||
relative=False,
|
||||
wait=False,
|
||||
timeout=300.0,
|
||||
ignore_limits=False,
|
||||
confirm_move=False,
|
||||
):
|
||||
""" moves smaract drive to position
|
||||
|
||||
arguments:
|
||||
==========
|
||||
val value to move to (float) [Must be provided]
|
||||
relative move relative to current position (T/F) [F]
|
||||
wait whether to wait for move to complete (T/F) [F]
|
||||
ignore_limits try move without regard to limits (T/F) [F]
|
||||
confirm_move try to confirm that move has begun (T/F) [F]
|
||||
timeout max time for move to complete (in seconds) [300]
|
||||
|
||||
return values:
|
||||
-13 : invalid value (cannot convert to float). Move not attempted.
|
||||
-12 : target value outside soft limits. Move not attempted.
|
||||
-11 : drive PV is not connected: Move not attempted.
|
||||
-8 : move started, but timed-out.
|
||||
-7 : move started, timed-out, but appears done.
|
||||
-5 : move started, unexpected return value from PV.put()
|
||||
-4 : move-with-wait finished, soft limit violation seen
|
||||
-3 : move-with-wait finished, hard limit violation seen
|
||||
0 : move-with-wait finish OK.
|
||||
0 : move-without-wait executed, not cpmfirmed
|
||||
1 : move-without-wait executed, move confirmed
|
||||
3 : move-without-wait finished, hard limit violation seen
|
||||
4 : move-without-wait finished, soft limit violation seen
|
||||
|
||||
"""
|
||||
NONFLOAT, OUTSIDE_LIMITS, UNCONNECTED = -13, -12, -11
|
||||
TIMEOUT = -8
|
||||
UNKNOWN_ERROR = -5
|
||||
DONE_OK = 0
|
||||
MOVE_BEGUN, MOVE_BEGUN_CONFIRMED = 0, 1
|
||||
try:
|
||||
val = float(val)
|
||||
except TypeError:
|
||||
return NONFLOAT
|
||||
|
||||
if relative:
|
||||
val += self._drive.get("VAL")
|
||||
|
||||
# Check for limit violations
|
||||
if not ignore_limits:
|
||||
if not self.within_limits(val):
|
||||
return OUTSIDE_LIMITS
|
||||
|
||||
stat = self._drive.put("VAL", val, wait=wait, timeout=timeout)
|
||||
if stat is None:
|
||||
return UNCONNECTED
|
||||
|
||||
if wait and stat == -1:
|
||||
return TIMEOUT
|
||||
|
||||
if 1 == stat:
|
||||
s0 = self._statusstg.get("VAL")
|
||||
s1 = s0
|
||||
t0 = time.time()
|
||||
t1 = t0 + min(10.0, timeout) # should be moving by now
|
||||
thold = self._hold.get("VAL") * 0.001 + t0
|
||||
tout = t0 + timeout
|
||||
if wait or confirm_move:
|
||||
while time.time() <= thold and s1 == 3:
|
||||
ca.poll(evt=1.e-2)
|
||||
s1 = self._statusstg.get("VAL")
|
||||
while time.time() <= t1 and s1 == 0:
|
||||
ca.poll(evt=1.e-2)
|
||||
s1 = self._statusstg.get("VAL")
|
||||
if s1 == 4:
|
||||
if wait:
|
||||
while time.time() <= tout and s1 == 4:
|
||||
ca.poll(evt=1.e-2)
|
||||
s1 = self._statusstg.get("VAL")
|
||||
if s1 == 3 or s1 == 4:
|
||||
if time.time() > tout:
|
||||
return TIMEOUT
|
||||
else:
|
||||
twv = abs(self._twv.get("VAL"))
|
||||
while (
|
||||
s1 == 3
|
||||
and time.time() <= tout
|
||||
and abs(self._rbv.get("VAL") - val) >= twv
|
||||
):
|
||||
ca.poll(evt=1.e-2)
|
||||
return DONE_OK
|
||||
else:
|
||||
return MOVE_BEGUN_CONFIRMED
|
||||
elif time.time() > tout:
|
||||
return TIMEOUT
|
||||
else:
|
||||
return UNKNOWN_ERROR
|
||||
else:
|
||||
return MOVE_BEGUN
|
||||
return UNKNOWN_ERROR
|
||||
|
||||
def get_current_value(self, readback=True):
|
||||
if readback:
|
||||
return self._rbv.get("VAL")
|
||||
else:
|
||||
return self._drive.get("VAL")
|
||||
|
||||
def set_current_value(self, value):
|
||||
return self._set_pos.put("VAL", value)
|
||||
|
||||
def get_precision(self):
|
||||
""" Adjustable convention"""
|
||||
pass
|
||||
|
||||
def set_precision(self):
|
||||
""" Adjustable convention"""
|
||||
pass
|
||||
|
||||
precision = property(get_precision, set_precision)
|
||||
|
||||
def set_speed(self):
|
||||
""" Adjustable convention"""
|
||||
pass
|
||||
|
||||
def get_speed(self):
|
||||
""" Adjustable convention"""
|
||||
pass
|
||||
|
||||
def set_speedMax(self):
|
||||
""" Adjustable convention"""
|
||||
pass
|
||||
|
||||
def get_moveDone(self):
|
||||
pass
|
||||
|
||||
def set_limits(self, values, posType="user", relative_to_present=False):
|
||||
""" Adjustable convention"""
|
||||
if relative_to_present:
|
||||
v = self.get_current_value()
|
||||
values = [v - values[0], v - values[1]]
|
||||
self._llm.put("VAL", values[0])
|
||||
self._hlm.put("VAL", values[1])
|
||||
|
||||
def get_limits(self, posType="user"):
|
||||
""" Adjustable convention"""
|
||||
return self._llm.get("VAL"), self._hlm.get("VAL")
|
||||
|
||||
def gui(self, guiType="xdm"):
|
||||
""" Adjustable convention"""
|
||||
cmd = ["caqtdm", "-macro"]
|
||||
|
||||
for i in range(len(self.Id) - 1):
|
||||
if self.Id[-i - 1].isnumeric() is False:
|
||||
M = self.Id[-i:]
|
||||
P = self.Id[:-i]
|
||||
print(P, M)
|
||||
break
|
||||
|
||||
cmd.append('"P=%s,M=%s"' % (P, M))
|
||||
# #cmd.append('/sf/common/config/qt/motorx_more.ui')
|
||||
cmd.append("ESB_MX_SMARACT_mot_exp.ui")
|
||||
# #os.system(' '.join(cmd))
|
||||
return subprocess.Popen(" ".join(cmd), shell=True)
|
||||
|
||||
def mv(self, value):
|
||||
self._currentChange = self.changeTo(value)
|
||||
|
||||
def wm(self, *args, **kwargs):
|
||||
return self.get_current_value(*args, **kwargs)
|
||||
|
||||
def mvr(self, value, *args, **kwargs):
|
||||
startvalue = self.get_current_value(readback=True, *args, **kwargs)
|
||||
self._currentChange = self.changeTo(value + startvalue, *args, **kwargs)
|
||||
|
||||
def wait(self):
|
||||
self._currentChange.wait()
|
||||
|
||||
# return string with motor value as variable representation
|
||||
def __str__(self):
|
||||
return "SmarAct is at %s" % (self.wm())
|
||||
# return "SmarAct is at %s %s"%(self.wm(),self.units)
|
||||
|
||||
def __repr__(self):
|
||||
return self.__str__()
|
||||
|
||||
def __call__(self, value):
|
||||
self._currentChange = self.changeTo(value)
|
||||
|
||||
|
||||
class SmarActDevice(SmarActRecord):
|
||||
def __init__(self, Id, alias_namespace=None):
|
||||
SmarActRecord.__init__(self, Id)
|
||||
|
||||
|
||||
# self.Id = Id
|
||||
#
|
||||
# self.x = SmarActRecord(Id+':DRIVE')
|
||||
|
||||
|
||||
class SmarActStage:
|
||||
def __init__(self, axes, name):
|
||||
self._keys = axes.keys()
|
||||
for axis in self._keys:
|
||||
self.__dict__[axis] = axes[axis]
|
||||
self.name = name
|
||||
|
||||
def __str__(self):
|
||||
return "SmarAct positions\n%s" % "\n".join(
|
||||
["%s: %s" % (key, self.__dict__[key].wm()) for key in self._keys]
|
||||
)
|
||||
|
||||
def __repr__(self):
|
||||
return str({key: self.__dict__[key].wm() for key in self._keys})
|
||||
|
||||
|
||||
class Changer_old:
|
||||
def __init__(self, target=None, parent=None, mover=None, hold=True, stopper=None):
|
||||
self.target = target
|
||||
self._mover = mover
|
||||
self._stopper = stopper
|
||||
self._thread = Thread(target=self._mover, args=(target,))
|
||||
if not hold:
|
||||
self._thread.start()
|
||||
|
||||
def wait(self):
|
||||
self._thread.join()
|
||||
|
||||
def start(self):
|
||||
self._thread.start()
|
||||
|
||||
def status(self):
|
||||
if self._thread.ident is None:
|
||||
return "waiting"
|
||||
else:
|
||||
if self._isAlive:
|
||||
return "changing"
|
||||
else:
|
||||
return "done"
|
||||
|
||||
def stop(self):
|
||||
self._stopper()
|
||||
@@ -1,147 +1,10 @@
|
||||
import subprocess
|
||||
from threading import Thread
|
||||
from epics import PV
|
||||
from .utilities import Changer
|
||||
# exceptions
|
||||
|
||||
|
||||
def _keywordChecker(kw_key_list_tups):
|
||||
for tkw, tkey, tlist in kw_key_list_tups:
|
||||
assert tkey in tlist, "Keyword %s should be one of %s" % (tkw, tlist)
|
||||
# wrappers for adjustables >>>>>>>>>>>
|
||||
|
||||
|
||||
class ValueRdback:
|
||||
def __init__(self, pv_value, pv_readback, name=None, elog=None):
|
||||
self.Id = pvname
|
||||
self._PV_value = PV(pv_value)
|
||||
self._PV_readback = PV(pv_readback)
|
||||
self._elog = elog
|
||||
self.name = name
|
||||
self._currentChange = None
|
||||
|
||||
# Conventional methods and properties for all Adjustable objects
|
||||
def changeTo(self, value, hold=False, check=True):
|
||||
""" Adjustable convention"""
|
||||
|
||||
def changer(value):
|
||||
self._status = self._motor.move(value, ignore_limits=(not check), wait=True)
|
||||
self._status_message = _status_messages[self._status]
|
||||
if not self._status == 0:
|
||||
print(self._status_message)
|
||||
|
||||
# changer = lambda value: self._motor.move(\
|
||||
# value, ignore_limits=(not check),
|
||||
# wait=True)
|
||||
return Changer(
|
||||
target=value,
|
||||
parent=self,
|
||||
changer=changer,
|
||||
hold=hold,
|
||||
stopper=self._motor.stop,
|
||||
)
|
||||
|
||||
def stop(self):
|
||||
""" Adjustable convention"""
|
||||
try:
|
||||
self._currentChange.stop()
|
||||
except:
|
||||
self._motor.stop()
|
||||
pass
|
||||
|
||||
def get_current_value(self):
|
||||
""" Adjustable convention"""
|
||||
return self._PV_readback.get()
|
||||
|
||||
def set_current_value(self, value):
|
||||
""" Adjustable convention"""
|
||||
print("not implemented: depends on a defined offset")
|
||||
|
||||
def get_precision(self):
|
||||
""" Adjustable convention"""
|
||||
if isinstance(self._precision, PV):
|
||||
return self._precision.get()
|
||||
else:
|
||||
return self._precision
|
||||
|
||||
def set_precision(self, value):
|
||||
""" Adjustable convention"""
|
||||
if isinstance(self._precision, PV):
|
||||
self._precision.put(value)
|
||||
else:
|
||||
self._precision = value
|
||||
|
||||
precision = property(get_precision, set_precision)
|
||||
|
||||
def set_speed(self):
|
||||
""" Adjustable convention"""
|
||||
pass
|
||||
|
||||
def get_speed(self):
|
||||
""" Adjustable convention"""
|
||||
pass
|
||||
|
||||
def set_speedMax(self):
|
||||
""" Adjustable convention"""
|
||||
pass
|
||||
|
||||
def get_moveDone(self):
|
||||
""" Adjustable convention"""
|
||||
""" 0: moving 1: move done"""
|
||||
return PV(str(self.Id + ".DMOV")).value
|
||||
|
||||
def set_limits(self, values, posType="user", relative_to_present=False):
|
||||
""" Adjustable convention"""
|
||||
_keywordChecker([("posType", posType, _posTypes)])
|
||||
ll_name, hl_name = "LLM", "HLM"
|
||||
if posType is "dial":
|
||||
ll_name, hl_name = "DLLM", "DHLM"
|
||||
if relative_to_present:
|
||||
v = self.get_current_value(posType=posType)
|
||||
values = [v - values[0], v - values[1]]
|
||||
self._motor.put(ll_name, values[0])
|
||||
self._motor.put(hl_name, values[1])
|
||||
|
||||
def get_limits(self, posType="user"):
|
||||
""" Adjustable convention"""
|
||||
_keywordChecker([("posType", posType, _posTypes)])
|
||||
ll_name, hl_name = "LLM", "HLM"
|
||||
if posType is "dial":
|
||||
ll_name, hl_name = "DLLM", "DHLM"
|
||||
return self._motor.get(ll_name), self._motor.get(hl_name)
|
||||
|
||||
# return string with motor value as variable representation
|
||||
def __str__(self):
|
||||
return "Motor is at %s" % self.wm()
|
||||
|
||||
def __repr__(self):
|
||||
return self.__str__()
|
||||
|
||||
def __call__(self, value):
|
||||
self._currentChange = self.changeTo(value)
|
||||
# wrappers for adjustables <<<<<<<<<<<
|
||||
|
||||
|
||||
class ChangerOld:
|
||||
def __init__(self, target=None, parent=None, mover=None, hold=True, stopper=None):
|
||||
self.target = target
|
||||
self._mover = mover
|
||||
self._stopper = stopper
|
||||
self._thread = Thread(target=self._mover, args=(target,))
|
||||
if not hold:
|
||||
self._thread.start()
|
||||
|
||||
def wait(self):
|
||||
self._thread.join()
|
||||
|
||||
def start(self):
|
||||
self._thread.start()
|
||||
|
||||
def status(self):
|
||||
if self._thread.ident is None:
|
||||
return "waiting"
|
||||
else:
|
||||
if self._isAlive:
|
||||
return "changing"
|
||||
else:
|
||||
return "done"
|
||||
|
||||
def stop(self):
|
||||
self._stopper()
|
||||
# @default_representation
|
||||
|
||||
@@ -1,15 +1,12 @@
|
||||
import numpy as np
|
||||
from epics import caget
|
||||
from epics import PV
|
||||
from ..eco_epics.utilities_epics import EnumWrapper
|
||||
from eco.epics.utilities_epics import EnumWrapper
|
||||
|
||||
from cam_server import PipelineClient
|
||||
from cam_server.utils import get_host_port_from_stream_address
|
||||
from bsread import source, SUB
|
||||
import subprocess
|
||||
import h5py
|
||||
from time import sleep
|
||||
from threading import Thread
|
||||
from datetime import datetime
|
||||
|
||||
from ..acquisition.utilities import Acquisition
|
||||
@@ -63,7 +60,7 @@ class CameraCA:
|
||||
f["images"] = np.asarray(d)
|
||||
|
||||
def gui(self, guiType="xdm"):
|
||||
""" Adjustable convention"""
|
||||
"""Adjustable convention"""
|
||||
cmd = ["caqtdm", "-macro"]
|
||||
|
||||
cmd.append('"NAME=%s,CAMNAME=%s"' % (self.Id, self.Id))
|
||||
@@ -483,7 +480,9 @@ class DIAClient:
|
||||
print("ERROR: please configure the instrument parameter in DIAClient")
|
||||
self.update_config()
|
||||
|
||||
def update_config(self,):
|
||||
def update_config(
|
||||
self,
|
||||
):
|
||||
self.writer_config = {
|
||||
"output_file": "/sf/%s/data/p%d/raw/test_data.h5"
|
||||
% (self.instrument, self.pgroup),
|
||||
@@ -553,8 +552,8 @@ class DIAClient:
|
||||
"general/process": __name__,
|
||||
"general/created": str(datetime.now()),
|
||||
"general/instrument": self.instrument,
|
||||
#'Npulses':100,
|
||||
#'channels': default_channels_list
|
||||
# 'Npulses':100,
|
||||
# 'channels': default_channels_list
|
||||
}
|
||||
|
||||
# self.default_channels_list = jungfrau_utils.load_default_channel_list()
|
||||
@@ -574,7 +573,9 @@ class DIAClient:
|
||||
self.pgroup = pgroup
|
||||
self.update_config()
|
||||
|
||||
def set_bs_channels(self,):
|
||||
def set_bs_channels(
|
||||
self,
|
||||
):
|
||||
print(
|
||||
"Please update /sf/%s/config/com/channel_lists/default_channel_list and restart all services on the DAQ server"
|
||||
% self.instrument
|
||||
|
||||
@@ -58,7 +58,7 @@ class Storage(object):
|
||||
|
||||
|
||||
class Pockels_trigger(PV):
|
||||
""" this class is needed to store the offset in files and read in s """
|
||||
"""this class is needed to store the offset in files and read in s"""
|
||||
|
||||
def __init__(self, pv_basename):
|
||||
pvname = pv_basename + "-RB"
|
||||
@@ -75,7 +75,7 @@ class Pockels_trigger(PV):
|
||||
return super().get() * 1e-6
|
||||
|
||||
def get(self):
|
||||
""" convert time to sec """
|
||||
"""convert time to sec"""
|
||||
return self.get_dial() - self.offset
|
||||
|
||||
def store(self, value=None):
|
||||
@@ -98,7 +98,7 @@ class Pockels_trigger(PV):
|
||||
|
||||
|
||||
class Phase_shifter(PV):
|
||||
""" this class is needed to store the offset in files and read in ps """
|
||||
"""this class is needed to store the offset in files and read in ps"""
|
||||
|
||||
def __init__(self, pv_basename="SLAAR01-TSPL-EPL"):
|
||||
pvname = pv_basename + ":CURR_DELTA_T"
|
||||
@@ -116,7 +116,7 @@ class Phase_shifter(PV):
|
||||
return super().get() * 1e-12
|
||||
|
||||
def get(self):
|
||||
""" convert time to sec """
|
||||
"""convert time to sec"""
|
||||
return self.get_dial() - self.offset
|
||||
|
||||
def store(self, value=None):
|
||||
@@ -155,18 +155,18 @@ class PhaseShifterAramis:
|
||||
self._elog = elog
|
||||
self.name = name
|
||||
|
||||
def changeTo(self, value, hold=False, check=True):
|
||||
""" Adjustable convention"""
|
||||
def set_target_value(self, value, hold=False, check=True):
|
||||
"""Adjustable convention"""
|
||||
|
||||
mover = lambda value: self._pshifter.move(value)
|
||||
return Changer(target=value, parent=self, mover=mover, hold=hold, stopper=None)
|
||||
|
||||
def stop(self):
|
||||
""" Adjustable convention"""
|
||||
"""Adjustable convention"""
|
||||
pass
|
||||
|
||||
def get_current_value(self, posType="user", readback=True):
|
||||
""" Adjustable convention"""
|
||||
"""Adjustable convention"""
|
||||
_keywordChecker([("posType", posType, _posTypes)])
|
||||
if posType == "user":
|
||||
return self._pshifter.get()
|
||||
@@ -174,7 +174,7 @@ class PhaseShifterAramis:
|
||||
return self._pshifter.get_dial()
|
||||
|
||||
def set_current_value(self, value, posType="user"):
|
||||
""" Adjustable convention"""
|
||||
"""Adjustable convention"""
|
||||
_keywordChecker([("posType", posType, _posTypes)])
|
||||
if posType == "user":
|
||||
return self._motor.set(value)
|
||||
|
||||
@@ -0,0 +1,299 @@
|
||||
import requests
|
||||
import time
|
||||
from ..elements.assembly import Assembly
|
||||
from ..elements.adjustable import AdjustableGetSet, Tweak
|
||||
from numpy import polyval
|
||||
import numpy as np
|
||||
import urllib.request
|
||||
import io
|
||||
from PIL import Image
|
||||
import os
|
||||
from timg import Renderer, Ansi24HblockMethod
|
||||
from shutil import get_terminal_size
|
||||
from enum import IntEnum, auto
|
||||
|
||||
# function ptz_slider_onChange(group)
|
||||
# {
|
||||
# if ((group == "pan" && "abs" == "rel") ||
|
||||
# (group == "tilt" && "abs" == "rel") ||
|
||||
# (group == "zoom" && "abs" == "rel") ||
|
||||
# (group == "focus" && "no" == "rel") ||
|
||||
# (group == "brightness" && "" == "rel") ||
|
||||
# (group == "iris" && "abs" == "rel")) {
|
||||
# group = "r" + group;
|
||||
# }
|
||||
# if (theCameraNumber == "") theCameraNumber = "1";
|
||||
# var url = "/axis-cgi/com/ptz.cgi?camera="+theCameraNumber+"&"+group+"="+parseFloat(Math.round(theNewSliderValue * 10)/10);
|
||||
# }
|
||||
#
|
||||
# Looks like 'pan' is an absolute pan where 'rpan' is a relative pan.
|
||||
#
|
||||
# curl 'http://<<camera address>>/axis-cgi/com/ptz.cgi?camera=1&continuouspantiltmove=0,0&imagerotation=0×tamp=1491243098477'
|
||||
|
||||
|
||||
AUTOFOCUS = IntEnum("autofocus", {"on": 1, "off": 0})
|
||||
AUTOIRIS = IntEnum("autoiris", {"on": 1, "off": 0})
|
||||
|
||||
|
||||
class AxisPTZ(Assembly):
|
||||
def __init__(
|
||||
self,
|
||||
camera_address,
|
||||
name="dummycam",
|
||||
timeout=0.1,
|
||||
tweak_steps=[-3, -3],
|
||||
):
|
||||
super().__init__(name=name)
|
||||
self.camera_address = camera_address
|
||||
self.camera_n = 1
|
||||
self.camera_ir = 0
|
||||
self.timeout = timeout
|
||||
|
||||
try:
|
||||
self.get_position()
|
||||
except:
|
||||
raise Exception(f"Could not connect to camera {self.name}!!")
|
||||
self._append(
|
||||
AdjustableGetSet,
|
||||
lambda: polyval([0.00290058, 0.99709942], self.get_position()["zoom"]),
|
||||
lambda val: self.set_par(
|
||||
"zoom", int(polyval([344.75862069, -343.75862069], val))
|
||||
),
|
||||
precision=10 / 9999 * 30,
|
||||
check_interval=0.05,
|
||||
name="zoom",
|
||||
is_setting=True,
|
||||
)
|
||||
self._append(
|
||||
AdjustableGetSet,
|
||||
lambda: self.get_position()["tilt"],
|
||||
lambda val: self.set_par("tilt", val),
|
||||
name="tilt",
|
||||
is_setting=True,
|
||||
)
|
||||
self._append(
|
||||
AdjustableGetSet,
|
||||
lambda: self.get_position()["pan"],
|
||||
lambda val: self.set_par("pan", val),
|
||||
name="pan",
|
||||
is_setting=True,
|
||||
)
|
||||
self._append(
|
||||
AdjustableGetSet,
|
||||
lambda: (self.get_position()["iris"] - 1) / 9995,
|
||||
lambda val: self.set_par("iris", val * 9995 + 1),
|
||||
name="iris",
|
||||
is_setting=True,
|
||||
)
|
||||
self._append(
|
||||
AdjustableGetSet,
|
||||
lambda: (self.get_position()["focus"] - 750) / (9999 - 750),
|
||||
lambda val: self.set_par("focus", val * (9999 - 750) + 750),
|
||||
name="focus",
|
||||
is_setting=True,
|
||||
)
|
||||
self._append(
|
||||
AdjustableGetSet,
|
||||
lambda: AUTOFOCUS.__dict__[self.get_position()["autofocus"]],
|
||||
lambda val: self.set_par("autofocus", AUTOFOCUS(val).name),
|
||||
name="autofocus",
|
||||
is_setting=True,
|
||||
)
|
||||
self._append(
|
||||
AdjustableGetSet,
|
||||
lambda: AUTOFOCUS.__dict__[self.get_position()["autoiris"]],
|
||||
lambda val: self.set_par("autoiris", AUTOFOCUS(val).name),
|
||||
name="autoiris",
|
||||
is_setting=True,
|
||||
)
|
||||
self._tweak_steps = tweak_steps
|
||||
|
||||
def tweak(self):
|
||||
t = Tweak([self.pan, self._tweak_steps[0]], [self.tilt, self._tweak_steps[1]])
|
||||
t.xy_adjustable_tweak()
|
||||
|
||||
# camera_n = 1
|
||||
# camera_url = 'http://<<camera address>>/axis-cgi/com/ptz.cgi'
|
||||
# camera_ir = 0
|
||||
|
||||
# presets = {
|
||||
# 'Home':
|
||||
# {
|
||||
# 'pan' : -120.7629,
|
||||
# 'tilt' : -4.8568,
|
||||
# 'zoom' : 696.0,
|
||||
# 'brightness' : 3333.0,
|
||||
# #'autofocus' : 'on',
|
||||
# #'autoiris' : 'on',
|
||||
# #'focus' : 6424.0, # generates error
|
||||
# #'iris' : 2739.0,
|
||||
# },
|
||||
# 'Mt. Washington':
|
||||
# #value="tilt=154749:focus=32766.000000:pan=267468:iris=32766.000000:zoom=11111.000000"
|
||||
# {
|
||||
# 'pan' : 156.7195,
|
||||
# 'tilt' : -0.6732,
|
||||
# 'zoom' : 11111.0,
|
||||
# #'autofocus' : 'on',
|
||||
# #'autoiris' : 'on',
|
||||
# #'focus' : 7964.0,
|
||||
# #'iris' : 2583.0,
|
||||
# }
|
||||
# }
|
||||
@property
|
||||
def camera_url(self):
|
||||
return f"http://{self.camera_address}/axis-cgi/com/ptz.cgi"
|
||||
|
||||
def get_image(self, filename=None, as_array=False):
|
||||
img_url = f"http://{self.camera_address}/jpg/image.jpg"
|
||||
with urllib.request.urlopen(img_url) as url:
|
||||
f = io.BytesIO(url.read())
|
||||
img = Image.open(f)
|
||||
if as_array:
|
||||
return np.asarray(img)
|
||||
else:
|
||||
return img
|
||||
|
||||
def show(self, in_terminal=True):
|
||||
r = Renderer()
|
||||
r.load_image(self.get_image())
|
||||
r.resize(get_terminal_size()[0])
|
||||
r.render(Ansi24HblockMethod)
|
||||
|
||||
def cameraCmd(self, q_cmd):
|
||||
resp_data = {}
|
||||
base_q_args = {
|
||||
"camera": self.camera_n,
|
||||
"imagerotation": self.camera_ir,
|
||||
"html": "no",
|
||||
"timestamp": int(time.time()),
|
||||
}
|
||||
|
||||
q_args = merge_dicts(q_cmd, base_q_args)
|
||||
resp = requests.get(self.camera_url, params=q_args, timeout=self.timeout)
|
||||
if resp.text.startswith("Error"):
|
||||
print(resp.text)
|
||||
else:
|
||||
for line in resp.text.splitlines():
|
||||
(name, var) = line.split("=", 2)
|
||||
try:
|
||||
resp_data[name.strip()] = float(var)
|
||||
except ValueError:
|
||||
resp_data[name.strip()] = var
|
||||
|
||||
return resp_data
|
||||
|
||||
def get_par(self, query):
|
||||
# print("cameraGet(" + query + ")")
|
||||
return self.cameraCmd({"query": query})
|
||||
|
||||
# resp_data = {}
|
||||
# q_args = { 'query': query,
|
||||
# 'camera': camera_n, 'imagerotation': camera_ir,
|
||||
# 'html': 'no', 'timestamp': int(time.time())
|
||||
# }
|
||||
# resp = requests.get(camera_url, params=q_args)
|
||||
# for line in resp.text.splitlines():
|
||||
# (name, var) = line.split("=", 2)
|
||||
# try:
|
||||
# resp_data[name.strip()] = float(var)
|
||||
# except ValueError:
|
||||
# resp_data[name.strip()] = var
|
||||
#
|
||||
# return resp_data
|
||||
|
||||
def set_par(self, group, val):
|
||||
print(val)
|
||||
# print("cameraSet(" + group + ", " + str(val) + ")")
|
||||
return self.cameraCmd({group: val})
|
||||
|
||||
# resp_data = {}
|
||||
# q_args = { group: val,
|
||||
# 'camera': camera_n, 'imagerotation': camera_ir,
|
||||
# 'html': 'no', 'timestamp': int(time.time())
|
||||
# }
|
||||
#
|
||||
# resp = requests.get(camera_url, params=q_args)
|
||||
# for line in resp.text.splitlines():
|
||||
# (name, var) = line.split("=", 2)
|
||||
# try:
|
||||
# resp_data[name.strip()] = float(var)
|
||||
# except ValueError:
|
||||
# resp_data[name.strip()] = var
|
||||
#
|
||||
# return resp_data
|
||||
|
||||
# def cameraGoToPreset(preset_name):
|
||||
# preset = presets[preset_name]
|
||||
# if preset != None:
|
||||
# for key, value in preset.items():
|
||||
# cameraSet(key, value)
|
||||
|
||||
def home(self):
|
||||
return self.cameraCmd({"move": "home"})
|
||||
|
||||
def get_position(self):
|
||||
return self.get_par("position")
|
||||
|
||||
def get_limits(self):
|
||||
return self.get_par("limits")
|
||||
|
||||
def set_pan(self, value):
|
||||
return self.set_par("pan", value)
|
||||
|
||||
def set_tilt(self, value):
|
||||
return self.set_par("tilt", value)
|
||||
|
||||
def set_zoom(self, value):
|
||||
return self.set_par("zoom", value)
|
||||
|
||||
def set_panrelative(self, value):
|
||||
return self.set_par("rpan", value)
|
||||
|
||||
def set_tiltrelative(self, value):
|
||||
return self.set_par("rtilt", value)
|
||||
|
||||
def set_zoomrelative(self, value):
|
||||
return self.set_par("rzoom", value)
|
||||
|
||||
|
||||
# print("Move to home...")
|
||||
# print(cameraHome())
|
||||
|
||||
# #time.sleep(1)
|
||||
# print("Get PTZ and Limits...")
|
||||
# print(cameraGetPTZ())
|
||||
# print(cameraGetLimits())
|
||||
|
||||
# for i in range(5):
|
||||
# time.sleep(1)
|
||||
# print("Move left...")
|
||||
# print(cameraPanRelative(-1))
|
||||
|
||||
# for i in range(5):
|
||||
# time.sleep(1)
|
||||
# print("Move right...")
|
||||
# print(cameraPanRelative(1))
|
||||
|
||||
|
||||
# print(cameraTilt(-1))
|
||||
|
||||
# time.sleep(5)
|
||||
# print("Show Mt. Washington...")
|
||||
# print(cameraGoToPreset('Mt. Washington'))
|
||||
|
||||
# time.sleep(2)
|
||||
# print("Move back home...")
|
||||
# print(cameraHome())
|
||||
# print(cameraGoToPreset('Home'))
|
||||
|
||||
|
||||
def merge_dicts(*dict_args):
|
||||
"""
|
||||
Given any number of dicts, shallow copy and merge into a new dict,
|
||||
precedence goes to key value pairs in latter dicts.
|
||||
"""
|
||||
result = {}
|
||||
for dictionary in dict_args:
|
||||
result.update(dictionary)
|
||||
return result
|
||||
@@ -0,0 +1,751 @@
|
||||
from cam_server import CamClient, PipelineClient
|
||||
from matplotlib.backend_bases import MouseButton
|
||||
from eco.devices_general.utilities import Changer
|
||||
from eco.epics.detector import DetectorPvData, DetectorPvEnum
|
||||
|
||||
from ..aliases import Alias, append_object_to_object
|
||||
from ..elements.adjustable import AdjustableVirtual, AdjustableGetSet, value_property
|
||||
from eco.elements.detector import DetectorGet
|
||||
from ..epics.adjustable import AdjustablePv, AdjustablePvEnum
|
||||
from eco.elements.adj_obj import AdjustableObject, DetectorObject
|
||||
from .pipelines_swissfel import Pipeline
|
||||
from ..elements.assembly import Assembly
|
||||
from .motors import MotorRecord
|
||||
import sys
|
||||
from pathlib import Path
|
||||
import time
|
||||
import matplotlib.pyplot as plt
|
||||
import numpy as np
|
||||
|
||||
sys.path.append("/sf/bernina/config/src/python/sf_databuffer/")
|
||||
import bufferutils
|
||||
|
||||
CAM_CLIENT = None
|
||||
PIPELINE_CLIENT = None
|
||||
|
||||
|
||||
def get_camclient():
|
||||
global CAM_CLIENT
|
||||
if not CAM_CLIENT:
|
||||
CAM_CLIENT = CamClient()
|
||||
CAM_CLIENT.timeout = 8
|
||||
return CAM_CLIENT
|
||||
|
||||
|
||||
def get_pipelineclient():
|
||||
global PIPELINE_CLIENT
|
||||
if not PIPELINE_CLIENT:
|
||||
PIPELINE_CLIENT = PipelineClient()
|
||||
PIPELINE_CLIENT.timeout = 8
|
||||
return PIPELINE_CLIENT
|
||||
|
||||
|
||||
@value_property
|
||||
class CamserverConfig2(Assembly):
|
||||
def __init__(self, cam_id, camserver_alias=None, name=None, camserver_group=None):
|
||||
super().__init__(name=name)
|
||||
self.cam_id = cam_id
|
||||
self.camserver_alias = camserver_alias
|
||||
self.camserver_group = camserver_group
|
||||
self._cross = None
|
||||
self._append(
|
||||
AdjustableGetSet,
|
||||
self._get_config,
|
||||
self._set_config,
|
||||
cache_get_seconds=0.05,
|
||||
precision=0,
|
||||
check_interval=None,
|
||||
name="_config",
|
||||
is_setting=False,
|
||||
is_display=False,
|
||||
)
|
||||
|
||||
self._append(
|
||||
AdjustableObject,
|
||||
self._config,
|
||||
name="config",
|
||||
is_setting=True,
|
||||
is_display="recursive",
|
||||
)
|
||||
self._append(
|
||||
DetectorGet,
|
||||
self._get_info,
|
||||
cache_get_seconds=0.05,
|
||||
name="_info",
|
||||
is_setting=False,
|
||||
is_display=False,
|
||||
)
|
||||
self._append(
|
||||
DetectorObject,
|
||||
self._info,
|
||||
name="info",
|
||||
is_display="recursive",
|
||||
is_setting=False,
|
||||
)
|
||||
|
||||
@property
|
||||
def pc(self):
|
||||
return get_pipelineclient()
|
||||
|
||||
@property
|
||||
def cc(self):
|
||||
return get_camclient()
|
||||
|
||||
def _get_config(self):
|
||||
return self.cc.get_camera_config(self.cam_id)
|
||||
|
||||
def _set_config(self, value, hold=False):
|
||||
return Changer(
|
||||
target=value,
|
||||
changer=lambda v: self.cc.set_camera_config(self.cam_id, v),
|
||||
hold=hold,
|
||||
)
|
||||
|
||||
def _get_info(self):
|
||||
fields = {
|
||||
"camera_geometry": self.cc.get_camera_geometry(self.cam_id),
|
||||
"pipelines": self._get_pipelines(),
|
||||
}
|
||||
return fields
|
||||
|
||||
### convenience functions ###
|
||||
def get_camera_image(self):
|
||||
im = self.cc.get_camera_array(self.cam_id)
|
||||
return im
|
||||
|
||||
def set_alias(self, alias=None):
|
||||
"""creates an alias in the camera config on the server. If no alias is provided, it defaults to the camera name"""
|
||||
if not alias:
|
||||
alias = self.camserver_alias
|
||||
self.set_config_fields({"alias": [alias.upper()]})
|
||||
|
||||
def set_group(self, group=None):
|
||||
"""adds the camera to the given group"""
|
||||
if not group:
|
||||
group = self.camserver_group
|
||||
self.config.group(group)
|
||||
|
||||
def _get_pipelines(self):
|
||||
return [p for p in self.pc.get_pipelines() if self.cam_id in p]
|
||||
|
||||
def set_config_fields(self, fields):
|
||||
"""fields is a dictionary containing the keys and values that should be updated, e.g. fields={'group': ['Laser', 'Bernina']}"""
|
||||
config = self.cc.get_camera_config(self.cam_id)
|
||||
config.update(fields)
|
||||
self.cc.set_camera_config(self.cam_id, config)
|
||||
|
||||
def set_config_fields_multiple_cams(self, conditions, fields):
|
||||
"""
|
||||
conditions is a dictionary holding the conditions to select a subset of cameras, e.g. {"group": Bernina}
|
||||
fields is a dictionary containing the keys and values that should be updated, e.g. fields={'alias': ['huhu', 'duda']}
|
||||
"""
|
||||
cams = {
|
||||
cam: self.cc.get_camera_config(cam)
|
||||
for cam in self.cc.get_cameras()
|
||||
if not "jungfrau" in cam
|
||||
}
|
||||
cams_selected = {}
|
||||
for cam, cfg in cams.items():
|
||||
try:
|
||||
if all([value in cfg[key] for key, value in conditions.items()]):
|
||||
cfg.update(fields)
|
||||
self.cc.set_camera_config(cam, cfg)
|
||||
cams_selected[cam] = cfg
|
||||
except Exception as e:
|
||||
print(f"{type(e)} {e} in cam {cam}")
|
||||
return cams_selected
|
||||
|
||||
def clear_all_bernina_aliases(self, verbose=True):
|
||||
cams_selected = self.set_config_fields_multiple_cams(
|
||||
conditions={"group": "Bernina"}, fields={"alias": []}
|
||||
)
|
||||
if verbose:
|
||||
print(f"Reset alias of {len(cams_selected)} cameras")
|
||||
print(cams_selected.keys())
|
||||
|
||||
def _run_cmd(self, line, silent=True):
|
||||
if silent:
|
||||
print(f"Starting following commandline silently:\n" + line)
|
||||
with open(os.devnull, "w") as FNULL:
|
||||
subprocess.Popen(
|
||||
line, shell=True, stdout=FNULL, stderr=subprocess.STDOUT
|
||||
)
|
||||
else:
|
||||
subprocess.Popen(line, shell=True)
|
||||
|
||||
def gui(self):
|
||||
self._run_cmd(f"csm")
|
||||
|
||||
|
||||
@value_property
|
||||
class CamserverConfig(Assembly):
|
||||
def __init__(self, cam_id, camserver_alias=None, name=None, camserver_group=None):
|
||||
super().__init__(name=name)
|
||||
self.cam_id = cam_id
|
||||
self.camserver_alias = camserver_alias
|
||||
self.camserver_group = camserver_group
|
||||
|
||||
@property
|
||||
def cc(self):
|
||||
return get_camclient()
|
||||
|
||||
@property
|
||||
def pc(self):
|
||||
return get_pipelineclient()
|
||||
|
||||
def get_current_value(self):
|
||||
return self.cc.get_camera_config(self.cam_id)
|
||||
|
||||
def set_target_value(self, value, hold=False):
|
||||
return Changer(
|
||||
target=value,
|
||||
changer=lambda v: self.cc.set_camera_config(self.cam_id, v),
|
||||
hold=hold,
|
||||
)
|
||||
|
||||
def set_config_fields(self, fields):
|
||||
"""fields is a dictionary containing the keys and values that should be updated, e.g. fields={'group': ['Laser', 'Bernina']}"""
|
||||
config = self.get_current_value()
|
||||
config.update(fields)
|
||||
self.cc.set_camera_config(self.cam_id, config)
|
||||
|
||||
### convenience functions ###
|
||||
def set_alias(self, alias=None):
|
||||
"""creates an alias in the camera config on the server. If no alias is provided, it defaults to the camera name"""
|
||||
if not alias:
|
||||
alias = self.camserver_alias
|
||||
self.set_config_fields({"alias": [alias.upper()]})
|
||||
|
||||
def set_group(self, group=None):
|
||||
"""creates an alias in the camera config on the server. If no alias is provided, it defaults to the camera name"""
|
||||
if not group:
|
||||
group = self.camserver_group
|
||||
self.set_config_fields({"group": group})
|
||||
|
||||
def restart_pipeline(self):
|
||||
base_directory = "/sf/bernina/config/src/python/sf_databuffer/"
|
||||
label = self.cam_id
|
||||
|
||||
policies = bufferutils.read_files(base_directory / Path("policies"), "policies")
|
||||
sources = bufferutils.read_files(base_directory / Path("sources"), "sources")
|
||||
sources_new = sources.copy()
|
||||
|
||||
# Only for debugging purposes
|
||||
labeled_sources = bufferutils.get_labeled_sources(sources_new, label)
|
||||
for s in labeled_sources:
|
||||
bufferutils.logging.info(f"Restarting {s['stream']}")
|
||||
|
||||
sources_new = bufferutils.remove_labeled_source(sources_new, label)
|
||||
|
||||
# Stopping the removed source(s)
|
||||
bufferutils.update_sources_and_policies(sources_new, policies)
|
||||
|
||||
# Starting the source(s) again
|
||||
bufferutils.update_sources_and_policies(sources, policies)
|
||||
|
||||
def stop(self):
|
||||
self.cc.stop_instance(self.cam_id)
|
||||
|
||||
def set_cross(self, x, y, x_um_per_px=None, y_um_per_px=None):
|
||||
"""set x and y position of the refetence marker on a camera px/um calibration is conserved if no new value is given"""
|
||||
calib = self.get_current_value()["camera_calibration"]
|
||||
if calib:
|
||||
if not x_um_per_px:
|
||||
x_um_per_px = calib["reference_marker_width"] / abs(
|
||||
calib["reference_marker"][2] - calib["reference_marker"][0]
|
||||
)
|
||||
if not y_um_per_px:
|
||||
y_um_per_px = calib["reference_marker_height"] / abs(
|
||||
calib["reference_marker"][3] - calib["reference_marker"][1]
|
||||
)
|
||||
else:
|
||||
calib = {}
|
||||
x_um_per_px = 1
|
||||
y_um_per_px = 1
|
||||
|
||||
# calib["reference_marker"] = [x - 1, y - 1, x + 1, y + 1]
|
||||
calib["reference_marker_width"] = 2 * x_um_per_px
|
||||
calib["reference_marker_height"] = 2 * y_um_per_px
|
||||
self.set_config_fields(fields={"camera_calibration": calib})
|
||||
|
||||
def set_config_fields_multiple_cams(self, conditions, fields):
|
||||
"""
|
||||
conditions is a dictionary holding the conditions to select a subset of cameras, e.g. {"group": Bernina}
|
||||
fields is a dictionary containing the keys and values that should be updated, e.g. fields={'alias': ['huhu', 'duda']}
|
||||
"""
|
||||
cams = {
|
||||
cam: self.cc.get_camera_config(cam)
|
||||
for cam in self.cc.get_cameras()
|
||||
if not "jungfrau" in cam
|
||||
}
|
||||
cams_selected = {}
|
||||
for cam, cfg in cams.items():
|
||||
try:
|
||||
if all([value in cfg[key] for key, value in conditions.items()]):
|
||||
cfg.update(fields)
|
||||
self.cc.set_camera_config(cam, cfg)
|
||||
cams_selected[cam] = cfg
|
||||
except Exception as e:
|
||||
print(f"{type(e)} {e} in cam {cam}")
|
||||
return cams_selected
|
||||
|
||||
def clear_all_bernina_aliases(self, verbose=True):
|
||||
cams_selected = self.set_config_fields_multiple_cams(
|
||||
conditions={"group": "Bernina"}, fields={"alias": []}
|
||||
)
|
||||
if verbose:
|
||||
print(f"Reset alias of {len(cams_selected)} cameras")
|
||||
print(cams_selected.keys())
|
||||
|
||||
def __repr__(self):
|
||||
s = f"**Camera Server Config {self.cam_id} with Alias {self.name}**\n"
|
||||
for key, item in self.get_current_value().items():
|
||||
s += f"{key:20} : {item}\n"
|
||||
return s
|
||||
|
||||
|
||||
class CameraBasler(Assembly):
|
||||
def __init__(
|
||||
self,
|
||||
pvname,
|
||||
camserver_alias=None,
|
||||
name=None,
|
||||
camserver_group=None,
|
||||
connect_camserver=True,
|
||||
):
|
||||
super().__init__(name=name)
|
||||
self.pvname = pvname
|
||||
if not camserver_alias:
|
||||
camserver_alias = self.alias.get_full_name() + f" ({pvname})"
|
||||
else:
|
||||
camserver_alias = camserver_alias + f" ({pvname})"
|
||||
if connect_camserver:
|
||||
self._append(
|
||||
CamserverConfig2,
|
||||
self.pvname,
|
||||
camserver_alias=camserver_alias,
|
||||
camserver_group=camserver_group,
|
||||
name="config_cs",
|
||||
is_display=True,
|
||||
is_setting=True,
|
||||
)
|
||||
|
||||
self.config_cs.set_alias()
|
||||
if camserver_group is not None:
|
||||
self.config_cs.set_group()
|
||||
self._append(
|
||||
AdjustablePvEnum,
|
||||
self.pvname + ":INIT",
|
||||
name="initialize",
|
||||
is_setting=True,
|
||||
is_display=False,
|
||||
)
|
||||
self._append(
|
||||
DetectorPvEnum,
|
||||
self.pvname + ":BUSY_INIT",
|
||||
name="is_initializing",
|
||||
is_setting=False,
|
||||
is_display=False,
|
||||
)
|
||||
self._append(
|
||||
AdjustablePvEnum,
|
||||
self.pvname + ":CAMERASTATUS",
|
||||
name="cam_status",
|
||||
is_setting=False,
|
||||
is_display=True,
|
||||
)
|
||||
self._append(
|
||||
AdjustablePv,
|
||||
self.pvname + ":BOARD",
|
||||
name="board_no",
|
||||
is_setting=True,
|
||||
is_display=False,
|
||||
)
|
||||
self._append(
|
||||
AdjustablePv,
|
||||
self.pvname + ":SERIALNR",
|
||||
name="serial_no",
|
||||
is_setting=True,
|
||||
is_display=False,
|
||||
)
|
||||
self._append(
|
||||
AdjustablePv,
|
||||
self.pvname + ":EXPOSURE",
|
||||
name="_exposure_time",
|
||||
is_setting=True,
|
||||
is_display=False,
|
||||
)
|
||||
self._append(
|
||||
AdjustablePvEnum,
|
||||
self.pvname + ":ACQMODE",
|
||||
name="_acq_mode",
|
||||
is_setting=True,
|
||||
is_display=False,
|
||||
)
|
||||
self._append(
|
||||
AdjustablePvEnum,
|
||||
self.pvname + ":RECMODE",
|
||||
name="_req_mode",
|
||||
is_setting=True,
|
||||
is_display=False,
|
||||
)
|
||||
self._append(
|
||||
AdjustablePvEnum,
|
||||
self.pvname + ":STOREMODE",
|
||||
name="_store_mode",
|
||||
is_setting=True,
|
||||
is_display=False,
|
||||
)
|
||||
self._append(
|
||||
AdjustablePv,
|
||||
self.pvname + ":BINX",
|
||||
name="_binx",
|
||||
is_setting=True,
|
||||
is_display=False,
|
||||
)
|
||||
self._append(
|
||||
AdjustablePv,
|
||||
self.pvname + ":BINY",
|
||||
name="_biny",
|
||||
is_setting=True,
|
||||
is_display=False,
|
||||
)
|
||||
self._append(
|
||||
AdjustablePv,
|
||||
self.pvname + ":REGIONX_START",
|
||||
name="_roixmin",
|
||||
is_setting=True,
|
||||
is_display=False,
|
||||
)
|
||||
self._append(
|
||||
AdjustablePv,
|
||||
self.pvname + ":REGIONX_END",
|
||||
name="_roixmax",
|
||||
is_setting=True,
|
||||
is_display=False,
|
||||
)
|
||||
self._append(
|
||||
AdjustablePv,
|
||||
self.pvname + ":REGIONY_START",
|
||||
name="_roiymin",
|
||||
is_setting=True,
|
||||
is_display=False,
|
||||
)
|
||||
self._append(
|
||||
AdjustablePv,
|
||||
self.pvname + ":REGIONY_END",
|
||||
name="_roiymax",
|
||||
is_setting=True,
|
||||
is_display=False,
|
||||
)
|
||||
self._append(
|
||||
AdjustablePvEnum,
|
||||
self.pvname + ":SET_PARAM",
|
||||
name="_set_parameters",
|
||||
is_setting=True,
|
||||
is_display=False,
|
||||
)
|
||||
|
||||
self._append(
|
||||
DetectorPvData,
|
||||
self.pvname + ":DEVICEFREQUENCY",
|
||||
has_unit=True,
|
||||
name="frequency",
|
||||
is_setting=False,
|
||||
is_display=True,
|
||||
)
|
||||
self._append(
|
||||
AdjustablePvEnum,
|
||||
self.pvname + ":SW_PULSID_SRC",
|
||||
name="bscheck",
|
||||
is_setting=True,
|
||||
is_display=True,
|
||||
)
|
||||
self._append(
|
||||
DetectorPvData,
|
||||
self.pvname + ":ERRORCOUNTER",
|
||||
name="pulse_id_error_sum",
|
||||
is_setting=False,
|
||||
is_display=True,
|
||||
)
|
||||
self._append(
|
||||
DetectorPvData,
|
||||
self.pvname + ":FEEDBACKTIME0",
|
||||
name="response_time_bs",
|
||||
is_setting=False,
|
||||
is_display=True,
|
||||
)
|
||||
self._append(
|
||||
AdjustablePv,
|
||||
self.pvname + ":AMPGAIN",
|
||||
name="_gain",
|
||||
is_setting=True,
|
||||
is_display=False,
|
||||
)
|
||||
self._append(
|
||||
AdjustablePvEnum,
|
||||
self.pvname + ":TRIGGER",
|
||||
name="trigger_on",
|
||||
)
|
||||
self._append(
|
||||
AdjustablePvEnum,
|
||||
self.pvname + ":TRIGGERSOURCE",
|
||||
name="trigger_source",
|
||||
is_setting=True,
|
||||
is_display=False,
|
||||
)
|
||||
# append_object_to_object(self,PvEnum,self.pvname+':TRIGGEREDGE',name='trigger_edge')
|
||||
self._append(
|
||||
AdjustableGetSet,
|
||||
self._exposure_time.get_current_value,
|
||||
lambda value: self._set_params((self._exposure_time, value)),
|
||||
name="exposure_time",
|
||||
unit="ms",
|
||||
is_setting=True,
|
||||
is_display=True,
|
||||
)
|
||||
self._append(
|
||||
AdjustableGetSet,
|
||||
self._gain.get_current_value,
|
||||
lambda value: self._set_params((self._gain, value)),
|
||||
name="gain",
|
||||
is_setting=True,
|
||||
is_display=True,
|
||||
)
|
||||
|
||||
def set_roi(roi):
|
||||
self._set_params(
|
||||
[self._roixmin, roi[0]],
|
||||
[self._roixmax, roi[1]],
|
||||
[self._roiymin, roi[2]],
|
||||
[self._roiymax, roi[3]],
|
||||
)
|
||||
return (roi[0], roi[1], roi[2], roi[3])
|
||||
|
||||
self._append(
|
||||
AdjustableVirtual,
|
||||
[self._roixmin, self._roixmax, self._roiymin, self._roiymax],
|
||||
lambda x_from, x_to, y_from, y_to: [x_from, x_to, y_from, y_to],
|
||||
set_roi,
|
||||
name="roi",
|
||||
is_setting=True,
|
||||
)
|
||||
|
||||
def _set_params(self, *args):
|
||||
self.cam_status(1)
|
||||
for ob, val in args:
|
||||
ob(val)
|
||||
self._set_parameters(1)
|
||||
self.cam_status(2)
|
||||
|
||||
def re_initialize(self, wait_before_init=1, wait_for_init=3):
|
||||
self.cam_status(0)
|
||||
time.sleep(wait_before_init)
|
||||
self.cam_status(1)
|
||||
time.sleep(wait_for_init)
|
||||
self.cam_status(2)
|
||||
|
||||
def get_camera_images(self, n):
|
||||
imgs = []
|
||||
while len(np.unique(imgs, axis=0)) < n:
|
||||
imgs.append(self.config_cs.get_camera_image())
|
||||
return np.unique(imgs, axis=0)
|
||||
|
||||
def set_cross(
|
||||
self, x=None, y=None, x_um_per_px=None, y_um_per_px=None, n_images=10
|
||||
):
|
||||
"""set x and y position of the refetence marker on a camera px/um calibration is conserved if no new value is given"""
|
||||
|
||||
def prompt(x, y, x_um_per_px, y_um_per_px):
|
||||
x = int(x)
|
||||
y = int(y)
|
||||
answer = (
|
||||
input(
|
||||
f"Set the new cross position [{x}, {y}] with calibration [{x_um_per_px:.3}, {y_um_per_px:.3}] ([y]/n)?"
|
||||
)
|
||||
or "y"
|
||||
)
|
||||
if answer == "y":
|
||||
calib.reference_marker([x - 1, y - 1, x + 1, y + 1])
|
||||
calib.reference_marker_width(2 * x_um_per_px)
|
||||
calib.reference_marker_height(2 * y_um_per_px)
|
||||
print("\nNew calibration:")
|
||||
print(calib)
|
||||
else:
|
||||
print("aborted")
|
||||
|
||||
calib = self.config_cs.config.camera_calibration
|
||||
print("Current calibration:")
|
||||
print(calib)
|
||||
try:
|
||||
w = calib.reference_marker_width()
|
||||
h = calib.reference_marker_height()
|
||||
rm = calib.reference_marker()
|
||||
if not x_um_per_px:
|
||||
x_um_per_px = w / abs(rm[2] - rm[0])
|
||||
if not y_um_per_px:
|
||||
y_um_per_px = h / abs(rm[3] - rm[1])
|
||||
except:
|
||||
rm = [0, 0, 0, 0]
|
||||
x_um_per_px = 1
|
||||
y_um_per_px = 1
|
||||
if x is None or y is None:
|
||||
x = (rm[2] + rm[0]) / 2
|
||||
y = (rm[3] + rm[1]) / 2
|
||||
img = np.mean(self.get_camera_images(n_images), axis=0)
|
||||
run = True
|
||||
|
||||
def on_click(event):
|
||||
if event.button is MouseButton.LEFT:
|
||||
x = event.xdata
|
||||
y = event.ydata
|
||||
cross_plot.set_data(x, y)
|
||||
plt.draw()
|
||||
print(f"cross at x: {x:.4} and y: {y:.4}")
|
||||
self.config_cs._cross = [x, y]
|
||||
else:
|
||||
plt.disconnect(bid)
|
||||
plt.close(self.config_cs.cam_id)
|
||||
|
||||
fig = plt.figure(num=self.config_cs.cam_id)
|
||||
plt.title(f"Set cross: left mouse click, Finish: right click")
|
||||
plt.imshow(img)
|
||||
cross_plot = plt.plot(x, y, "+r", markersize=10)[0]
|
||||
bid = fig.canvas.mpl_connect("button_press_event", on_click)
|
||||
plt.show(block=True)
|
||||
x, y = self.config_cs._cross
|
||||
print(x, y)
|
||||
prompt(x, y, x_um_per_px, y_um_per_px)
|
||||
|
||||
def gui(self):
|
||||
self._run_cmd(
|
||||
f'caqtdm -macro "NAME={self.pvname},CAMNAME={self.pvname}" /sf/controls/config/qt/Camera/CameraExpert.ui'
|
||||
)
|
||||
|
||||
|
||||
# NB: please note this should be moved to microscopes which are using cameras plus zooms,
|
||||
class QioptiqMicroscope(CameraBasler):
|
||||
def __init__(self, pvname_camera, pvname_zoom=None, pvname_focus=None, name=None):
|
||||
super().__init__(pvname_camera, name=name)
|
||||
if pvname_zoom:
|
||||
self._append(MotorRecord, pvname_zoom, name="zoom", is_setting=True)
|
||||
if pvname_focus:
|
||||
self._append(MotorRecord, pvname_focus, name="focus", is_setting=True)
|
||||
|
||||
|
||||
class CameraPCO(Assembly):
|
||||
def __init__(self, pvname, camserver_alias=None, name=None):
|
||||
super().__init__(name=name)
|
||||
self.pvname = pvname
|
||||
if not camserver_alias:
|
||||
camserver_alias = self.alias.get_full_name() + f"({pvname})"
|
||||
else:
|
||||
camserver_alias = camserver_alias + f"({pvname})"
|
||||
self._append(
|
||||
CamserverConfig,
|
||||
self.pvname,
|
||||
camserver_alias=camserver_alias,
|
||||
name="config_cs",
|
||||
)
|
||||
self.config_cs.set_alias()
|
||||
self._append(AdjustablePvEnum, self.pvname + ":INIT", name="initialize")
|
||||
self._append(
|
||||
AdjustablePvEnum,
|
||||
self.pvname + ":CAMERASTATUS",
|
||||
name="camera_status",
|
||||
is_display=True,
|
||||
)
|
||||
self._append(AdjustablePv, self.pvname + ":BOARD", name="board_no")
|
||||
# self._append(AdjustablePv, self.pvname + ":SERIALNR", name="serial_no") Apparently not exisitng and timing out.
|
||||
self._append(AdjustablePv, self.pvname + ":EXPOSURE", name="_exposure_time")
|
||||
self._append(AdjustablePvEnum, self.pvname + ":ACQMODE", name="_acq_mode")
|
||||
self._append(AdjustablePvEnum, self.pvname + ":RECMODE", name="_req_mode")
|
||||
self._append(AdjustablePvEnum, self.pvname + ":STOREMODE", name="_store_mode")
|
||||
self._append(AdjustablePv, self.pvname + ":HSSPEED", name="_hs_speed")
|
||||
self._append(
|
||||
AdjustablePvEnum, self.pvname + ":SCMOSREADOUT", name="_readout_mode"
|
||||
)
|
||||
self._append(AdjustablePv, self.pvname + ":BINY", name="_binx")
|
||||
self._append(AdjustablePv, self.pvname + ":BINY", name="_biny")
|
||||
self._append(AdjustablePv, self.pvname + ":REGIONX_START", name="_roixmin")
|
||||
self._append(AdjustablePv, self.pvname + ":REGIONX_END", name="_roixmax")
|
||||
self._append(AdjustablePv, self.pvname + ":REGIONY_START", name="_roiymin")
|
||||
self._append(AdjustablePv, self.pvname + ":REGIONY_END", name="_roiymax")
|
||||
self._append(
|
||||
AdjustablePvEnum, self.pvname + ":SET_PARAM", name="_set_parameters"
|
||||
)
|
||||
self._append(AdjustablePvEnum, self.pvname + ":TRIGGER", name="trigger_on")
|
||||
# append_object_to_object(self,PvEnum,self.pvname+':TRIGGEREDGE',name='trigger_edge')
|
||||
|
||||
self._append(
|
||||
AdjustableGetSet,
|
||||
self._exposure_time.get_current_value,
|
||||
lambda value: self._set_params((self._exposure_time, value)),
|
||||
name="exposure_time",
|
||||
is_setting=True,
|
||||
)
|
||||
self._append(
|
||||
AdjustableVirtual,
|
||||
[self.camera_status],
|
||||
lambda stat: stat == 2,
|
||||
lambda running: 2 if running else 1,
|
||||
name="running",
|
||||
is_setting=True,
|
||||
)
|
||||
|
||||
def set_roi(roi):
|
||||
self._set_params(
|
||||
[self._roixmin, roi[0]],
|
||||
[self._roixmax, roi[1]],
|
||||
[self._roiymin, roi[2]],
|
||||
[self._roiymax, roi[3]],
|
||||
)
|
||||
return (roi[0], roi[1], roi[2], roi[3])
|
||||
|
||||
self._append(
|
||||
AdjustableVirtual,
|
||||
[self._roixmin, self._roixmax, self._roiymin, self._roiymax],
|
||||
lambda x_from, x_to, y_from, y_to: [x_from, x_to, y_from, y_to],
|
||||
set_roi,
|
||||
name="roi",
|
||||
is_setting=True,
|
||||
)
|
||||
|
||||
def _set_params(self, *args):
|
||||
self.running(False)
|
||||
for ob, val in args:
|
||||
ob(val)
|
||||
self._set_parameters(1)
|
||||
self.running(True)
|
||||
|
||||
def gui(self):
|
||||
self._run_cmd(
|
||||
f'caqtdm -macro "NAME={self.pvname},CAMNAME={self.pvname}" /sf/controls/config/qt/Camera/CameraExpert.ui'
|
||||
)
|
||||
|
||||
|
||||
# NB: please note this should be moved to microscopes which are using cameras plus zooms,
|
||||
class FeturaMicroscope(CameraBasler):
|
||||
def __init__(
|
||||
self, pvname_camera, pvname_base_zoom=None, name=None, camserver_alias=None
|
||||
):
|
||||
super().__init__(pvname_camera, name=name, camserver_alias=camserver_alias)
|
||||
if pvname_base_zoom:
|
||||
self._append(
|
||||
AdjustablePv,
|
||||
pvsetname=pvname_base_zoom + ":POS_SP",
|
||||
pvreadbackname=pvname_base_zoom + ":POS_RB",
|
||||
name="_zoom_motor",
|
||||
is_setting=True,
|
||||
is_display=False,
|
||||
)
|
||||
|
||||
def getv(v):
|
||||
return v / 10.0
|
||||
|
||||
def setv(v):
|
||||
return v * 10.0
|
||||
|
||||
self._append(
|
||||
AdjustableVirtual, [self._zoom_motor], getv, setv, name="zoom", unit="%"
|
||||
)
|
||||
@@ -1,5 +1,6 @@
|
||||
from ..devices_general.utilities import Changer
|
||||
from epics import PV
|
||||
from ..aliases import Alias
|
||||
|
||||
|
||||
_status_messages = {
|
||||
@@ -18,12 +19,12 @@ _status_messages = {
|
||||
4: "move-without-wait finished, soft limit violation seen",
|
||||
}
|
||||
|
||||
|
||||
class DelayStage:
|
||||
def __init__(self, stage):
|
||||
def __init__(self, stage, name=None):
|
||||
self._stage = stage
|
||||
self.delay_stage_offset = 0.0
|
||||
self.name = self._stage.name
|
||||
self.name = name
|
||||
self.alias = Alias(name)
|
||||
self.Id = self._stage.Id
|
||||
self._elog = self._stage._elog
|
||||
|
||||
@@ -32,7 +33,7 @@ class DelayStage:
|
||||
return motor_pos
|
||||
|
||||
def get_current_value(self):
|
||||
""" Adjustable convention"""
|
||||
"""Adjustable convention"""
|
||||
motor_pos = self._stage.get_current_value()
|
||||
motor_pos -= self.delay_stage_offset
|
||||
delay = motor_pos * 2.0 * 3.33333333 * 1e-12
|
||||
@@ -43,17 +44,17 @@ class DelayStage:
|
||||
self._stage.set_current_value(motor_pos)
|
||||
return (value, motor_pos)
|
||||
|
||||
def changeTo(self, value, hold=False, check=True):
|
||||
def set_target_value(self, value, hold=False, check=True):
|
||||
value = self.delay_to_motor(value) + self.delay_stage_offset
|
||||
delay = (value - self.delay_stage_offset) * 2.0 * 3.33333333 * 1e-12
|
||||
return self._stage.changeTo(value, hold, check)
|
||||
return self._stage.set_target_value(value, hold, check)
|
||||
|
||||
def gui(self, guiType="xdm"):
|
||||
return self._stage.gui()
|
||||
|
||||
# spec-inspired convenience methods
|
||||
def mv(self, value):
|
||||
self._stage._currentChange = self.changeTo(value)
|
||||
self._stage._currentChange = self.set_target_value(value)
|
||||
|
||||
def wm(self, *args, **kwargs):
|
||||
return self.get_current_value(*args, **kwargs)
|
||||
@@ -66,7 +67,7 @@ class DelayStage:
|
||||
self._stage._currentChange.wait()
|
||||
|
||||
def stop(self):
|
||||
""" Adjustable convention"""
|
||||
"""Adjustable convention"""
|
||||
try:
|
||||
self._stage._currentChange.stop()
|
||||
except:
|
||||
@@ -81,4 +82,4 @@ class DelayStage:
|
||||
return self.__str__()
|
||||
|
||||
def __call__(self, value):
|
||||
self._currentChange = self.changeTo(value)
|
||||
self._currentChange = self.set_target_value(value)
|
||||
|
||||
@@ -1,29 +1,16 @@
|
||||
import numpy as np
|
||||
from epics import caget
|
||||
from epics import PV
|
||||
from ..eco_epics.utilities_epics import EnumWrapper
|
||||
from eco.epics.utilities_epics import EnumWrapper
|
||||
|
||||
from cam_server import PipelineClient
|
||||
from cam_server.utils import get_host_port_from_stream_address
|
||||
from bsread import source, SUB
|
||||
import subprocess
|
||||
import h5py
|
||||
from time import sleep
|
||||
from threading import Thread
|
||||
from datetime import datetime
|
||||
|
||||
from ..acquisition.utilities import Acquisition
|
||||
|
||||
try:
|
||||
import sys, os
|
||||
|
||||
tpath = os.path.dirname(__file__)
|
||||
sys.path.insert(0, os.path.join(tpath, "../../detector_integration_api"))
|
||||
# ask Leo(2018.03.14):
|
||||
# sys.path.insert(0,os.path.join(tpath,'../../jungfrau_utils'))
|
||||
from detector_integration_api import DetectorIntegrationClient
|
||||
except:
|
||||
print("NB: detector integration could not be imported!")
|
||||
from ..elements.assembly import Assembly
|
||||
from ..elements.adjustable import AdjustableMemory
|
||||
from ..elements.detector import DetectorVirtual
|
||||
|
||||
|
||||
_cameraArrayTypes = ["monochrome", "rgb"]
|
||||
@@ -48,8 +35,8 @@ class CameraCA:
|
||||
return self.px_width
|
||||
|
||||
def get_data(self):
|
||||
w = self.get_px_width()
|
||||
h = self.get_px_height()
|
||||
w = int(self.get_px_width())
|
||||
h = int(self.get_px_height())
|
||||
numpix = int(caget(self.Id + ":FPICTURE.NORD"))
|
||||
i = caget(self.Id + ":FPICTURE", count=numpix)
|
||||
return i.reshape(h, w)
|
||||
@@ -63,7 +50,7 @@ class CameraCA:
|
||||
f["images"] = np.asarray(d)
|
||||
|
||||
def gui(self, guiType="xdm"):
|
||||
""" Adjustable convention"""
|
||||
"""Adjustable convention"""
|
||||
cmd = ["caqtdm", "-macro"]
|
||||
|
||||
cmd.append('"NAME=%s,CAMNAME=%s"' % (self.Id, self.Id))
|
||||
@@ -140,278 +127,3 @@ class DiodeDigitizer:
|
||||
if VME_crate:
|
||||
self.diode_0 = FeDigitizer("%s:Lnk%dCh%d" % (VME_crate, link, ch_0))
|
||||
self.diode_1 = FeDigitizer("%s:Lnk%dCh%d" % (VME_crate, link, ch_1))
|
||||
|
||||
|
||||
class DIAClient:
|
||||
def __init__(
|
||||
self,
|
||||
Id,
|
||||
instrument=None,
|
||||
api_address="http://sf-daq-2:10000",
|
||||
jf_name="JF_1.5M",
|
||||
):
|
||||
self.Id = Id
|
||||
self._api_address = api_address
|
||||
self.client = DetectorIntegrationClient(api_address)
|
||||
print("\nDetector Integration API on %s" % api_address)
|
||||
# No pgroup by default
|
||||
self.pgroup = 0
|
||||
self.n_frames = 100
|
||||
self.jf_name = jf_name
|
||||
self.pede_file = ""
|
||||
self.gain_file = ""
|
||||
self.instrument = instrument
|
||||
if instrument is None:
|
||||
print("ERROR: please configure the instrument parameter in DIAClient")
|
||||
self.update_config()
|
||||
|
||||
def update_config(self,):
|
||||
self.writer_config = {
|
||||
"output_file": "/sf/%s/data/p%d/raw/test_data.h5"
|
||||
% (self.instrument, self.pgroup),
|
||||
"user_id": self.pgroup,
|
||||
"n_frames": self.n_frames,
|
||||
"general/user": str(self.pgroup),
|
||||
"general/process": __name__,
|
||||
"general/created": str(datetime.now()),
|
||||
"general/instrument": self.instrument,
|
||||
# "general/correction": "test"
|
||||
}
|
||||
|
||||
self.backend_config = {
|
||||
"n_frames": self.n_frames,
|
||||
"bit_depth": 16,
|
||||
"gain_corrections_filename": self.gain_file, # "/sf/alvra/config/jungfrau/jungfrau_4p5_gaincorrections_v0.h5",
|
||||
# "gain_corrections_dataset": "gains",
|
||||
# "pede_corrections_filename": "/sf/alvra/data/res/p%d/pedestal_20171210_1628_res.h5" % self.pgroup,
|
||||
# "pede_corrections_dataset": "gains",
|
||||
# "pede_mask_dataset": "pixel_mask",
|
||||
# "activate_corrections_preview": True,
|
||||
# FIXME: HARDCODED!!!
|
||||
"is_HG0": False,
|
||||
}
|
||||
|
||||
if self.pede_file != "":
|
||||
self.backend_config[
|
||||
"gain_corrections_filename"
|
||||
] = (
|
||||
self.gain_file
|
||||
) # "/sf/alvra/config/jungfrau/jungfrau_4p5_gaincorrections_v0.h5",
|
||||
self.backend_config["gain_corrections_dataset"] = "gains"
|
||||
self.backend_config[
|
||||
"pede_corrections_filename"
|
||||
] = (
|
||||
self.pede_file
|
||||
) # "/sf/alvra/data/res/p%d/pedestal_20171210_1628_res.h5" % self.pgroup,
|
||||
self.backend_config["pede_corrections_dataset"] = "gains"
|
||||
self.backend_config["pede_mask_dataset"] = "pixel_mask"
|
||||
self.backend_config["activate_corrections_preview"] = True
|
||||
else:
|
||||
self.backend_config["pede_corrections_dataset"] = "gains"
|
||||
self.backend_config["pede_mask_dataset"] = "pixel_mask"
|
||||
self.backend_config["gain_corrections_filename"] = ""
|
||||
self.backend_config["pede_corrections_filename"] = ""
|
||||
self.backend_config["activate_corrections_preview"] = False
|
||||
|
||||
self.detector_config = {
|
||||
"timing": "trigger",
|
||||
# FIXME: HARDCODED
|
||||
"exptime": 0.000010,
|
||||
"cycles": self.n_frames,
|
||||
# "delay" : 0.001992,
|
||||
"frames": 1,
|
||||
"dr": 16,
|
||||
}
|
||||
|
||||
# Not needed anymore?
|
||||
# default_channels_list = parseChannelListFile(
|
||||
# '/sf/alvra/config/com/channel_lists/default_channel_list')
|
||||
|
||||
self.bsread_config = {
|
||||
"output_file": "/sf/%s/data/p%d/raw/test_bsread.h5"
|
||||
% (self.instrument, self.pgroup),
|
||||
"user_id": self.pgroup,
|
||||
"general/user": str(self.pgroup),
|
||||
"general/process": __name__,
|
||||
"general/created": str(datetime.now()),
|
||||
"general/instrument": self.instrument,
|
||||
#'Npulses':100,
|
||||
#'channels': default_channels_list
|
||||
}
|
||||
|
||||
# self.default_channels_list = jungfrau_utils.load_default_channel_list()
|
||||
|
||||
def reset(self):
|
||||
self.client.reset()
|
||||
# pass
|
||||
|
||||
def get_status(self):
|
||||
return self.client.get_status()
|
||||
|
||||
def get_config(self):
|
||||
config = self.client.get_config()
|
||||
return config
|
||||
|
||||
def set_pgroup(self, pgroup):
|
||||
self.pgroup = pgroup
|
||||
self.update_config()
|
||||
|
||||
def set_bs_channels(self,):
|
||||
print(
|
||||
"Please update /sf/%s/config/com/channel_lists/default_channel_list and restart all services on the DAQ server"
|
||||
% self.instrument
|
||||
)
|
||||
|
||||
def set_config(self):
|
||||
self.reset()
|
||||
self.client.set_config(
|
||||
{
|
||||
"writer": self.writer_config,
|
||||
"backend": self.backend_config,
|
||||
"detector": self.detector_config,
|
||||
"bsread": self.bsread_config,
|
||||
}
|
||||
)
|
||||
|
||||
def check_still_running(self, time_interval=0.5):
|
||||
cfg = self.get_config()
|
||||
running = True
|
||||
while running:
|
||||
if not self.get_status()["status"][-7:] == "RUNNING":
|
||||
running = False
|
||||
break
|
||||
# elif not self.get_status()['status'][-20:]=='BSREAD_STILL_RUNNING':
|
||||
# running = False
|
||||
# break
|
||||
else:
|
||||
sleep(time_interval)
|
||||
|
||||
def take_pedestal(
|
||||
self, n_frames, analyze=True, n_bad_modules=0, update_config=True
|
||||
):
|
||||
from jungfrau_utils.scripts.jungfrau_run_pedestals import (
|
||||
run as jungfrau_utils_run,
|
||||
)
|
||||
|
||||
directory = "/sf/%s/data/p%d/raw/JF_pedestal/" % (self.instrument, self.pgroup)
|
||||
if not os.path.exists(directory):
|
||||
print("Directory %s not existing, creating it" % directory)
|
||||
os.makedirs(directory)
|
||||
|
||||
res_dir = directory.replace("/raw/", "/res/")
|
||||
if not os.path.exists(res_dir):
|
||||
print("Directory %s not existing, creating it" % res_dir)
|
||||
os.makedirs(res_dir)
|
||||
filename = "pedestal_%s.h5" % datetime.now().strftime("%Y%m%d_%H%M")
|
||||
period = 0.04
|
||||
jungfrau_utils_run(
|
||||
self._api_address,
|
||||
filename,
|
||||
directory,
|
||||
self.pgroup,
|
||||
period,
|
||||
self.detector_config["exptime"],
|
||||
n_frames,
|
||||
1,
|
||||
analyze,
|
||||
n_bad_modules,
|
||||
self.instrument,
|
||||
self.jf_name,
|
||||
)
|
||||
|
||||
if update_config:
|
||||
self.pede_file = (
|
||||
(directory + filename).replace("raw/", "res/").replace(".h5", "_res.h5")
|
||||
)
|
||||
print("Pedestal file updated to %s" % self.pede_file)
|
||||
return self.pede_file
|
||||
|
||||
def start(self):
|
||||
self.client.start()
|
||||
print("start acquisition")
|
||||
pass
|
||||
|
||||
def stop(self):
|
||||
self.client.stop()
|
||||
print("stop acquisition")
|
||||
pass
|
||||
|
||||
def config_and_start_test(self):
|
||||
self.reset()
|
||||
self.set_config()
|
||||
self.start()
|
||||
pass
|
||||
|
||||
def wait_for_status(self, *args, **kwargs):
|
||||
return self.client.wait_for_status(*args, **kwargs)
|
||||
|
||||
def acquire(self, file_name=None, Npulses=100, JF_factor=1, bsread_padding=0):
|
||||
"""
|
||||
JF_factor?
|
||||
bsread_padding?
|
||||
"""
|
||||
file_rootdir = "/sf/%s/data/p%d/raw/" % (self.instrument, self.pgroup)
|
||||
|
||||
if file_name is None:
|
||||
# FIXME /dev/null crashes the data taking (h5py can't close /dev/null and crashes)
|
||||
print("Not saving any data, as file_name is not set")
|
||||
file_name_JF = file_rootdir + "DelMe" + "_JF1p5M.h5"
|
||||
file_name_bsread = file_rootdir + "DelMe" + ".h5"
|
||||
else:
|
||||
# FIXME hardcoded
|
||||
file_name_JF = file_rootdir + file_name + "_JF1p5M.h5"
|
||||
file_name_bsread = file_rootdir + file_name + ".h5"
|
||||
|
||||
if self.pgroup == 0:
|
||||
raise ValueError("Please use set_pgroup() to set a pgroup value.")
|
||||
|
||||
def acquire():
|
||||
self.n_frames = Npulses * JF_factor
|
||||
self.update_config()
|
||||
# self.detector_config.update({
|
||||
# 'cycles': n_frames})
|
||||
self.writer_config.update(
|
||||
{
|
||||
"output_file": file_name_JF,
|
||||
# 'n_messages': n_frames
|
||||
}
|
||||
)
|
||||
# self.backend_config.update({
|
||||
# 'n_frames': n_frames})
|
||||
self.bsread_config.update(
|
||||
{
|
||||
"output_file": file_name_bsread,
|
||||
# 'Npulses': Npulses + bsread_padding
|
||||
}
|
||||
)
|
||||
|
||||
self.reset()
|
||||
self.set_config()
|
||||
# print(self.get_config())
|
||||
self.client.start()
|
||||
done = False
|
||||
|
||||
while not done:
|
||||
stat = self.get_status()
|
||||
if stat["status"] == "IntegrationStatus.FINISHED":
|
||||
done = True
|
||||
if stat["status"] == "IntegrationStatus.BSREAD_STILL_RUNNING":
|
||||
done = True
|
||||
if stat["status"] == "IntegrationStatus.INITIALIZED":
|
||||
done = True
|
||||
if stat["status"] == "IntegrationStatus.DETECTOR_STOPPED":
|
||||
done = True
|
||||
sleep(0.1)
|
||||
|
||||
return Acquisition(
|
||||
acquire=acquire,
|
||||
acquisition_kwargs={
|
||||
"file_names": [file_name_bsread, file_name_JF],
|
||||
"Npulses": Npulses,
|
||||
},
|
||||
hold=False,
|
||||
)
|
||||
|
||||
def wait_done(self):
|
||||
self.check_running()
|
||||
self.check_still_running()
|
||||
|
||||
@@ -0,0 +1,225 @@
|
||||
from ..detector.detectors_psi import DetectorBsStream
|
||||
from eco.elements.assembly import Assembly
|
||||
from eco.epics.detector import DetectorPvDataStream
|
||||
from eco.epics.adjustable import AdjustablePv, AdjustablePvEnum
|
||||
|
||||
|
||||
class DigitizerKeysightBoxcarChannel(Assembly):
|
||||
def __init__(self, pvbase, name=None):
|
||||
super().__init__(name=name)
|
||||
self.pvbase = pvbase
|
||||
self._append(DetectorBsStream, self.pvbase + "_VAL_GET", name="value")
|
||||
self._append(
|
||||
DetectorPvDataStream,
|
||||
self.pvbase + "_BOXCAR.VALF",
|
||||
name="waveform_slow",
|
||||
is_display=False,
|
||||
is_status=True,
|
||||
)
|
||||
|
||||
# self.status_collection.append(self.waveform_slow, force=True)
|
||||
self._append(
|
||||
DetectorPvDataStream,
|
||||
self.pvbase + "_BOXCAR.VALH",
|
||||
name="background_average",
|
||||
)
|
||||
self._append(
|
||||
DetectorPvDataStream,
|
||||
self.pvbase + "_BOXCAR.VALI",
|
||||
name="signal_average",
|
||||
)
|
||||
self._append(
|
||||
DetectorPvDataStream,
|
||||
self.pvbase + "_BOXCAR.VALG",
|
||||
name="difference_average",
|
||||
)
|
||||
self._append(
|
||||
DetectorPvDataStream,
|
||||
self.pvbase + "_BOXCAR.VALP",
|
||||
name="background_integral",
|
||||
)
|
||||
self._append(
|
||||
DetectorPvDataStream, self.pvbase + "_BOXCAR.VALO", name="signal_integral"
|
||||
)
|
||||
self._append(
|
||||
DetectorPvDataStream,
|
||||
self.pvbase + "_BOXCAR.VALQ",
|
||||
name="difference_integral",
|
||||
)
|
||||
self._append(
|
||||
AdjustablePv,
|
||||
self.pvbase + "_BSTART",
|
||||
name="bgregion_start",
|
||||
is_setting="auto",
|
||||
)
|
||||
self._append(
|
||||
AdjustablePv, self.pvbase + "_BEND", name="bgregion_end", is_setting="auto"
|
||||
)
|
||||
self._append(
|
||||
AdjustablePv,
|
||||
self.pvbase + "_START",
|
||||
name="sigregion_start",
|
||||
is_setting="auto",
|
||||
)
|
||||
self._append(
|
||||
AdjustablePv, self.pvbase + "_END", name="sigregion_end", is_setting="auto"
|
||||
)
|
||||
self._append(
|
||||
AdjustablePv, self.pvbase + "_LEVEL", name="cross_level", is_setting="auto"
|
||||
)
|
||||
self._append(
|
||||
AdjustablePv,
|
||||
self.pvbase + "_CALIB",
|
||||
name="calibration_gain",
|
||||
is_setting="auto",
|
||||
)
|
||||
self._append(
|
||||
AdjustablePv,
|
||||
self.pvbase + "_CALIB_OFFS",
|
||||
name="calibration_offset",
|
||||
is_setting="auto",
|
||||
)
|
||||
self._append(
|
||||
AdjustablePvEnum,
|
||||
self.pvbase + "_WHICH_CHAN",
|
||||
name="output_mode",
|
||||
is_setting="auto",
|
||||
)
|
||||
|
||||
def get_current_value(self):
|
||||
return self.value.get_current_value()
|
||||
|
||||
|
||||
class DigitizerKeysight(Assembly):
|
||||
def __init__(self, pvbase="SARES20-LSCP9-FNS", name=None):
|
||||
super().__init__(name=name)
|
||||
self.pvbase = pvbase
|
||||
for chno in range(2):
|
||||
chno = chno + 1
|
||||
self._append(
|
||||
DigitizerKeysightBoxcarChannel,
|
||||
f"{self.pvbase}:PR1_CH{chno}",
|
||||
name=f"channel_{chno:d}",
|
||||
is_setting=True,
|
||||
)
|
||||
self._append(
|
||||
AdjustablePv,
|
||||
self.pvbase + ":A_SCANRATERB",
|
||||
name="sample_rate",
|
||||
is_setting="auto",
|
||||
)
|
||||
self._append(
|
||||
AdjustablePv,
|
||||
self.pvbase + ":A_TRIGGERDELAYNS",
|
||||
pvreadbackname=self.pvbase + ":A_TRIGGERDELAYRB",
|
||||
name="trigger_delay",
|
||||
is_setting="auto",
|
||||
)
|
||||
|
||||
|
||||
class DigitizerIoxos(Assembly):
|
||||
def __init__(self, pvbase="SARES20-LSCP9-FNS", name=None):
|
||||
super().__init__(name=name)
|
||||
self.pvbase = pvbase
|
||||
for chno in range(8):
|
||||
self._append(
|
||||
DigitizerIoxosBoxcarChannel,
|
||||
f"{self.pvbase}:CH{chno}",
|
||||
name=f"channel_{chno:d}",
|
||||
is_setting=True,
|
||||
)
|
||||
|
||||
|
||||
class DigitizerIoxosBoxcarChannel(Assembly):
|
||||
def __init__(self, pvbase, name=None):
|
||||
super().__init__(name=name)
|
||||
self.pvbase = pvbase
|
||||
self._append(DetectorBsStream, self.pvbase + ":VAL_GET", name="value")
|
||||
self._append(
|
||||
AdjustablePv, self.pvbase + ":VAL_GET.EGU", name="unit", is_setting="auto"
|
||||
)
|
||||
self._append(
|
||||
DetectorPvDataStream,
|
||||
self.pvbase + ":WFM",
|
||||
name="waveform_slow",
|
||||
is_display=False,
|
||||
is_status=True,
|
||||
)
|
||||
# self.status_collection.append(self.waveform_slow)
|
||||
self._append(
|
||||
DetectorPvDataStream,
|
||||
self.pvbase + ":BOXCAR.VALH",
|
||||
name="background",
|
||||
)
|
||||
self._append(
|
||||
DetectorPvDataStream,
|
||||
self.pvbase + ":BOXCAR.VALI",
|
||||
name="signal",
|
||||
)
|
||||
self._append(
|
||||
DetectorPvDataStream,
|
||||
self.pvbase + ":BOXCAR.VALG",
|
||||
name="difference",
|
||||
)
|
||||
self._append(
|
||||
DetectorPvDataStream, self.pvbase + ":BOXCAR.VALO", name="signal_integral"
|
||||
)
|
||||
self._append(
|
||||
DetectorPvDataStream, self.pvbase + ":BOXCAR.VALE", name="signal_average"
|
||||
)
|
||||
self._append(
|
||||
DetectorPvDataStream,
|
||||
self.pvbase + ":BOXCAR.VALP",
|
||||
name="background_integral",
|
||||
)
|
||||
self._append(
|
||||
DetectorPvDataStream,
|
||||
self.pvbase + ":BOXCAR.VALD",
|
||||
name="background_average",
|
||||
is_setting="auto",
|
||||
)
|
||||
self._append(
|
||||
AdjustablePv,
|
||||
self.pvbase + ":BSTART",
|
||||
name="bgregion_start",
|
||||
is_setting="auto",
|
||||
)
|
||||
self._append(
|
||||
AdjustablePv, self.pvbase + ":BEND", name="bgregion_end", is_setting="auto"
|
||||
)
|
||||
self._append(
|
||||
AdjustablePv,
|
||||
self.pvbase + ":START",
|
||||
name="sigregion_start",
|
||||
is_setting="auto",
|
||||
)
|
||||
self._append(
|
||||
AdjustablePv, self.pvbase + ":END", name="sigregion_end", is_setting="auto"
|
||||
)
|
||||
self._append(
|
||||
AdjustablePv,
|
||||
self.pvbase + ":CALIB",
|
||||
name="calibration_gain",
|
||||
is_setting="auto",
|
||||
)
|
||||
self._append(
|
||||
AdjustablePv,
|
||||
self.pvbase + ":CALIB_OFFS",
|
||||
name="calibration_offset",
|
||||
is_setting="auto",
|
||||
)
|
||||
self._append(
|
||||
AdjustablePvEnum,
|
||||
self.pvbase + ":BOXCAR.SCAN",
|
||||
name="scan_mode",
|
||||
is_setting="auto",
|
||||
)
|
||||
self._append(
|
||||
AdjustablePvEnum,
|
||||
self.pvbase + ":WHICH_CHAN",
|
||||
name="output_mode",
|
||||
is_setting="auto",
|
||||
)
|
||||
|
||||
def get_current_value(self):
|
||||
return self.value.get_current_value()
|
||||
@@ -0,0 +1,65 @@
|
||||
from eco import Assembly
|
||||
from eco.epics.adjustable import AdjustablePv, AdjustablePvEnum, AdjustablePvString
|
||||
from eco.epics.detector import DetectorPvData, DetectorPvEnum
|
||||
from epics import PV
|
||||
|
||||
|
||||
|
||||
|
||||
class I2cChannel(Assembly):
|
||||
def __init__(self, pvbase, channelnumber, name=None):
|
||||
super().__init__(name=name)
|
||||
self.pvbase = pvbase
|
||||
self.channel_number = channelnumber
|
||||
self._append(
|
||||
AdjustablePvString,
|
||||
f'{self.pvbase}_CH{self.channel_number}:PROCESS.DESC',
|
||||
name="description",
|
||||
is_setting=True,
|
||||
)
|
||||
self._append(DetectorPvData,f'{self.pvbase}_CH{self.channel_number}:TEMP', has_unit=True, name='temp')
|
||||
self._append(DetectorPvData,f'{self.pvbase}_CH{self.channel_number}:HUMIREL', has_unit=True, name = 'humi')
|
||||
self._append(DetectorPvData,f'{self.pvbase}_CH{self.channel_number}:PRES', has_unit=True, name='pres')
|
||||
self._append(AdjustablePv,f'{self.pvbase}_CH{self.channel_number}:ONOFF', name='enabled', is_setting=True)
|
||||
self._append(AdjustablePvEnum,f'{self.pvbase}_CH{self.channel_number}:SENSOR_TYPE', name='sensor_type', is_setting=True)
|
||||
self._pv_init = PV(self.pvbase+':INIT.PROC')
|
||||
|
||||
def get_current_value(self,*args,**kwargs):
|
||||
return f'{self.temp.get_current_value(*args,**kwargs):.2f}°C , {self.humi.get_current_value(*args,**kwargs):.2f}%relHum, {self.pres.get_current_value(*args,**kwargs):.2f} mB'
|
||||
|
||||
# def set_target_value(self,*args,**kwargs):
|
||||
# return self.enabled.set_target_value(*args,**kwargs)
|
||||
|
||||
def initialize(self):
|
||||
self._pv_init.put(1)
|
||||
|
||||
|
||||
|
||||
|
||||
class I2cModule(Assembly):
|
||||
def __init__(self,pvbase='SARES20-CI2C',N_channels=8, name=None):
|
||||
super().__init__(name=name)
|
||||
for n in range(1,N_channels+1):
|
||||
self._append(I2cChannel,pvbase,channelnumber=n,name=f'ch{n}')
|
||||
|
||||
|
||||
class BerninaEnvironment(Assembly):
|
||||
def __init__(self,pvbases=['SLAAR21-LI2C01', 'SARES20-CI2C'],channels=[[1,2,3,4,5,6,7,8],[4,5,6,7,8]], channelnames = [['las_tab1_in', 'las_tab1_cen', 'las_tab1_out', 'las_tab2_in', 'las_tab2_cen', 'las_tab2_out', 'las_tab2_below', 'tt_spec'], ['tt_opt', 'tt_kb', 'exp1', 'exp2', 'exp3']], name=None):
|
||||
super().__init__(name=name)
|
||||
for pvbase,channelnumbers,tnames in zip(pvbases,channels,channelnames):
|
||||
for n,tname in zip(channelnumbers,tnames):
|
||||
self._append(I2cChannel,pvbase,channelnumber=n,name=tname)
|
||||
self._append(DetectorPvData,"IKA-GAMA:Luft_SF", unit='%', name='he_recovery_air_content')
|
||||
|
||||
|
||||
class WagoSensor(Assembly):
|
||||
def __init__(self,pvbase='SARES20-CWAG-GPS01:TEMP-T9', name=None):
|
||||
super().__init__(name=name)
|
||||
self.pvbase = pvbase
|
||||
self._append(DetectorPvData,f'{self.pvbase}', unit='°C', name='temperature')
|
||||
self._append(DetectorPvEnum,f'{self.pvbase}-SS', name='status')
|
||||
self._append(AdjustablePv,f'{self.pvbase}-WLEN', name='cable_length', unit='m', is_setting=True)
|
||||
self.unit = self.temperature.unit
|
||||
|
||||
def get_current_value(self):
|
||||
return self.temperature.get_current_value()
|
||||
@@ -0,0 +1,295 @@
|
||||
*-------------------------------------------------------
|
||||
* itemType: Rectangle
|
||||
*-------------------------------------------------------
|
||||
pvs=0(Visibility),1(Alarm)
|
||||
controlPoints=1,2
|
||||
*-------------------------------------------------------
|
||||
* itemType: Line
|
||||
*-------------------------------------------------------
|
||||
pvs=0(Visibility),1(Alarm)
|
||||
controlPoints=1,2
|
||||
*-------------------------------------------------------
|
||||
* itemType: Triangle
|
||||
*-------------------------------------------------------
|
||||
pvs=0(Visibility),1(Alarm)
|
||||
controlPoints=1,2
|
||||
*-------------------------------------------------------
|
||||
* itemType: Circle
|
||||
*-------------------------------------------------------
|
||||
pvs=0(Visibility),1(Alarm)
|
||||
controlPoints=1,2,11,12
|
||||
contextActions(editmode)=10(ClosePath),21(Pie),20(Arc),30(90deg),31(180deg),32(270deg),33(360deg)
|
||||
*-------------------------------------------------------
|
||||
* itemType: Polygon
|
||||
*-------------------------------------------------------
|
||||
pvs=0(Visibility),1(Alarm)
|
||||
controlPoints=1,2
|
||||
*-------------------------------------------------------
|
||||
* itemType: Path
|
||||
*-------------------------------------------------------
|
||||
pvs=0(Visibility),1(Alarm)
|
||||
controlPoints=1,2
|
||||
contextActions(editmode)=10(ClosePath),11(FillSolid),20(OddEven),21(Winding)
|
||||
*-------------------------------------------------------
|
||||
* itemType: Text
|
||||
*-------------------------------------------------------
|
||||
pvs=0(Visibility),1(Alarm)
|
||||
controlPoints=1,2
|
||||
*-------------------------------------------------------
|
||||
* itemType: Image
|
||||
*-------------------------------------------------------
|
||||
pvs=0(Visibility),1(Alarm),2(Monitor)
|
||||
controlPoints=1,2
|
||||
*-------------------------------------------------------
|
||||
* itemType: Symbol
|
||||
*-------------------------------------------------------
|
||||
pvs=0(Visibility),1(Alarm)
|
||||
controlPoints=1,2
|
||||
*-------------------------------------------------------
|
||||
* itemType: AttachLabel
|
||||
*-------------------------------------------------------
|
||||
pvs=0(Visibility),1(Alarm)
|
||||
controlPoints=1,2
|
||||
*-------------------------------------------------------
|
||||
* itemType: AttachTransform
|
||||
*-------------------------------------------------------
|
||||
pvs=0(Visibility),1(Alarm)
|
||||
controlPoints=1,2
|
||||
*-------------------------------------------------------
|
||||
* itemType: Frame
|
||||
*-------------------------------------------------------
|
||||
pvs=0(Visibility),1(Alarm)
|
||||
controlPoints=1,2
|
||||
*-------------------------------------------------------
|
||||
* itemType: ScrollArea
|
||||
*-------------------------------------------------------
|
||||
pvs=0(Visibility),1(Alarm)
|
||||
controlPoints=1,2
|
||||
*-------------------------------------------------------
|
||||
* itemType: GroupBox
|
||||
*-------------------------------------------------------
|
||||
pvs=0(Visibility),1(Alarm)
|
||||
controlPoints=1,2
|
||||
*-------------------------------------------------------
|
||||
* itemType: Stacked
|
||||
*-------------------------------------------------------
|
||||
pvs=0(Visibility),1(Alarm)
|
||||
controlPoints=1,2
|
||||
contextActions(editmode)=100(Add Tab),101(Delete Tab)
|
||||
*-------------------------------------------------------
|
||||
* itemType: SubPicture
|
||||
*-------------------------------------------------------
|
||||
pvs=0(Visibility),1(Alarm)
|
||||
controlPoints=1,2
|
||||
*-------------------------------------------------------
|
||||
* itemType: MonitorText
|
||||
*-------------------------------------------------------
|
||||
pvs=0(Visibility),1(Alarm),2(Monitor)
|
||||
controlPoints=1,2,10
|
||||
contextActions(editmode)=1(Show Unit)
|
||||
*-------------------------------------------------------
|
||||
* itemType: MonitorBar
|
||||
*-------------------------------------------------------
|
||||
pvs=0(Visibility),1(Alarm),2(Monitor),3(Monitor)
|
||||
controlPoints=1,2,10,11
|
||||
contextActions(editmode)=30(No),31(Gradient),32(Solid),33(Warning&Alarm Gradient),34(Warning&Alarm Solid),35(Alarm Solid),36(Warning Solid),20(No),21(Inside),22(Outside)
|
||||
*-------------------------------------------------------
|
||||
* itemType: MonitorMeter
|
||||
*-------------------------------------------------------
|
||||
pvs=0(Visibility),1(Alarm),2(Monitor),3(Monitor)
|
||||
controlPoints=1,2,10,11
|
||||
contextActions(editmode)=30(No),31(Gradient),32(Solid),33(Warning&Alarm Gradient),34(Warning&Alarm Solid),35(Alarm Solid),36(Warning Solid),20(No),21(Inside),22(Outside),50(Two Third),51(Three Quarters),60(Upper Half),70(TopRight Quarter),71(TopLeft Quarter),72(BotRight Quarter),73(BotLeft Quarter)
|
||||
*-------------------------------------------------------
|
||||
* itemType: MonitorIndic
|
||||
*-------------------------------------------------------
|
||||
pvs=0(Visibility),1(Alarm),2(Monitor)
|
||||
controlPoints=1,2
|
||||
*-------------------------------------------------------
|
||||
* itemType: MonitorBits
|
||||
*-------------------------------------------------------
|
||||
pvs=0(Visibility),1(Alarm),2(Monitor)
|
||||
controlPoints=1,2
|
||||
contextActions(editmode)=1(Up),2(Down),3(Left),4(Right)
|
||||
*-------------------------------------------------------
|
||||
* itemType: MonitorBitnames
|
||||
*-------------------------------------------------------
|
||||
pvs=0(Visibility),1(Alarm),2(Monitor),3(Monitor)
|
||||
controlPoints=1,2
|
||||
contextActions(editmode)=1(Up),2(Down),3(Left),4(Right)
|
||||
*-------------------------------------------------------
|
||||
* itemType: StateSymbol
|
||||
*-------------------------------------------------------
|
||||
pvs=0(Visibility),1(Alarm),2(Monitor)
|
||||
controlPoints=1,2
|
||||
*-------------------------------------------------------
|
||||
* itemType: MonitorStripChart
|
||||
*-------------------------------------------------------
|
||||
pvs=0(Visibility),1(Alarm),2(Monitor),3(Monitor),4(Monitor),5(Monitor),6(Monitor),7(Monitor),8(Monitor),9(Monitor),10(Monitor)
|
||||
controlPoints=1,2
|
||||
contextActions(editmode)=30(PV),31(Received),10(None),11(TopLeft),12(Top),13(TopRight),14(Right),15(BottomRight),16(Bottom),17(BottomLeft),18(Left),40(Absolut),41(Relative),60(Right to Left),61(Left to Right),50(None),51(Horizontal),52(Vertical),53(Hor&Vert)
|
||||
contextActions(runtime)=80(Clear Curve),30(PV),31(Received),10(None),11(TopLeft),12(Top),13(TopRight),14(Right),15(BottomRight),16(Bottom),17(BottomLeft),18(Left),40(Absolut),41(Relative),60(Right to Left),61(Left to Right),50(None),51(Horizontal),52(Vertical),53(Hor&Vert)
|
||||
*-------------------------------------------------------
|
||||
* itemType: MonitorXYPlot
|
||||
*-------------------------------------------------------
|
||||
pvs=0(Visibility),1(Alarm),10(Monitor),18(Monitor),11(Monitor),19(Monitor),12(Monitor),20(Monitor),13(Monitor),21(Monitor),14(Monitor),22(Monitor),15(Monitor),23(Monitor),16(Monitor),24(Monitor),17(Monitor),25(Monitor),3(Monitor),2(Monitor),4(Monitor),50(Monitor),52(Monitor),51(Monitor),53(Monitor)
|
||||
controlPoints=1,2
|
||||
contextActions(editmode)=30(PV),31(Received),10(None),11(TopLeft),12(Top),13(TopRight),14(Right),15(BottomRight),16(Bottom),17(BottomLeft),18(Left),50(None),51(Horizontal),52(Vertical),53(Hor&Vert)
|
||||
contextActions(runtime)=80(Clear Plot),30(PV),31(Received),10(None),11(TopLeft),12(Top),13(TopRight),14(Right),15(BottomRight),16(Bottom),17(BottomLeft),18(Left),50(None),51(Horizontal),52(Vertical),53(Hor&Vert)
|
||||
*-------------------------------------------------------
|
||||
* itemType: MonitorCamera
|
||||
*-------------------------------------------------------
|
||||
pvs=0(Visibility),1(Alarm),2(Monitor),3(Monitor),4(Monitor),5(Monitor),6(Monitor)
|
||||
controlPoints=1,2,10,11
|
||||
contextActions(editmode)=21(Information),20(Histogram),22(Level Bar),11(<Disable All>),1(Default),2(Grey),3(Spectrum),4(Automatic Levels)
|
||||
contextActions(runtime)=21(Information),20(Histogram),22(Level Bar),11(<Disable All>),1(Default),2(Grey),3(Spectrum),4(Automatic Levels)
|
||||
*-------------------------------------------------------
|
||||
* itemType: MonitorTable
|
||||
*-------------------------------------------------------
|
||||
pvs=0(Visibility),1(Alarm)
|
||||
controlPoints=1,2,100,101,102
|
||||
*-------------------------------------------------------
|
||||
* itemType: MonitorWaterfallPlot
|
||||
*-------------------------------------------------------
|
||||
pvs=0(Visibility),1(Alarm),2(Monitor),3(Monitor),4(Monitor)
|
||||
controlPoints=1,2,10,11
|
||||
contextActions(editmode)=30(Scroll),31(Repeate),32(Single),40(Down),41(Up),21(Information),20(Intensity),22(Grid),11(<Disable All>),2(Grey),3(Spectrum0),4(Spectrum1)
|
||||
contextActions(runtime)=90(Clear Plot),30(Scroll),31(Repeate),32(Single),40(Down),41(Up),21(Information),20(Intensity),22(Grid),11(<Disable All>),2(Grey),3(Spectrum0),4(Spectrum1)
|
||||
*-------------------------------------------------------
|
||||
* itemType: ControlKnob
|
||||
*-------------------------------------------------------
|
||||
pvs=0(Visibility),1(Alarm),2(Control)
|
||||
controlPoints=1,2,10,11
|
||||
contextActions(editmode)=10(Unit),30(No),31(Gradient),32(Solid),33(Warning&Alarm Gradient),34(Warning&Alarm Solid),35(Alarm Solid),36(Warning Solid)
|
||||
*-------------------------------------------------------
|
||||
* itemType: ControlSlider
|
||||
*-------------------------------------------------------
|
||||
pvs=0(Visibility),1(Alarm),2(Control),3(Monitor)
|
||||
controlPoints=1,2,10,11
|
||||
contextActions(editmode)=30(No),31(Gradient),32(Solid),33(Warning&Alarm Gradient),34(Warning&Alarm Solid),35(Alarm Solid),36(Warning Solid),20(No),21(Inside),22(Outside)
|
||||
*-------------------------------------------------------
|
||||
* itemType: ControlTextEntry
|
||||
*-------------------------------------------------------
|
||||
pvs=0(Visibility),1(Alarm),2(Control)
|
||||
controlPoints=1,2,10
|
||||
contextActions(editmode)=1(Show Unit)
|
||||
*-------------------------------------------------------
|
||||
* itemType: ControlNumeric
|
||||
*-------------------------------------------------------
|
||||
pvs=0(Visibility),1(Alarm),2(Control)
|
||||
controlPoints=1,2,10
|
||||
contextActions(editmode)=1(Show Unit)
|
||||
*-------------------------------------------------------
|
||||
* itemType: ControlMessageButton
|
||||
*-------------------------------------------------------
|
||||
pvs=0(Visibility),1(Alarm),2(Control)
|
||||
controlPoints=1,2
|
||||
*-------------------------------------------------------
|
||||
* itemType: ControlChoiceButton
|
||||
*-------------------------------------------------------
|
||||
pvs=0(Visibility),1(Alarm),2(Control)
|
||||
controlPoints=1,2
|
||||
*-------------------------------------------------------
|
||||
* itemType: ControlCheckBox
|
||||
*-------------------------------------------------------
|
||||
pvs=0(Visibility),1(Alarm),2(Control)
|
||||
controlPoints=1,2
|
||||
*-------------------------------------------------------
|
||||
* itemType: ActionDisplayGroup
|
||||
*-------------------------------------------------------
|
||||
pvs=0(Visibility),1(Alarm)
|
||||
controlPoints=1,2
|
||||
*-------------------------------------------------------
|
||||
* itemType: ActionLoadPicture
|
||||
*-------------------------------------------------------
|
||||
pvs=0(Visibility),1(Alarm)
|
||||
controlPoints=1,2
|
||||
*-------------------------------------------------------
|
||||
* itemType: ActionShellCommand
|
||||
*-------------------------------------------------------
|
||||
pvs=0(Visibility),1(Alarm)
|
||||
controlPoints=1,2
|
||||
*-------------------------------------------------------
|
||||
* itemType: PropertyModifierCheckBox
|
||||
*-------------------------------------------------------
|
||||
pvs=0(Visibility),1(Alarm)
|
||||
controlPoints=1,2
|
||||
*-------------------------------------------------------
|
||||
* itemType: ScriptContext2d
|
||||
*-------------------------------------------------------
|
||||
pvs=0(Visibility),1(Alarm)
|
||||
controlPoints=1,2
|
||||
*-------------------------------------------------------
|
||||
* itemType: ScriptActionButton
|
||||
*-------------------------------------------------------
|
||||
pvs=0(Visibility),1(Alarm)
|
||||
controlPoints=1,2
|
||||
*-------------------------------------------------------
|
||||
* itemType: BackgroundImage
|
||||
*-------------------------------------------------------
|
||||
pvs=0(Visibility),1(Alarm),2(Monitor)
|
||||
*-------------------------------------------------------
|
||||
* itemType: TestEvents
|
||||
*-------------------------------------------------------
|
||||
pvs=0(Visibility),1(Alarm)
|
||||
controlPoints=1,2
|
||||
*-------------------------------------------------------
|
||||
* itemType: TestTGeneric
|
||||
*-------------------------------------------------------
|
||||
pvs=0(Visibility),1(Alarm)
|
||||
controlPoints=1,2
|
||||
*-------------------------------------------------------
|
||||
* itemType: TestArea
|
||||
*-------------------------------------------------------
|
||||
pvs=0(Visibility),1(Alarm)
|
||||
controlPoints=1,2
|
||||
*-------------------------------------------------------
|
||||
* itemType: Button
|
||||
*-------------------------------------------------------
|
||||
pvs=0(Visibility),1(Alarm)
|
||||
controlPoints=1,2
|
||||
*-------------------------------------------------------
|
||||
* itemType: SymOriginPosition
|
||||
*-------------------------------------------------------
|
||||
pvs=0(Visibility),1(Alarm)
|
||||
*-------------------------------------------------------
|
||||
* itemType: SymConnectionPoint
|
||||
*-------------------------------------------------------
|
||||
pvs=0(Visibility),1(Alarm)
|
||||
*-------------------------------------------------------
|
||||
* itemType: SymConnectionLine
|
||||
*-------------------------------------------------------
|
||||
pvs=0(Visibility),1(Alarm)
|
||||
controlPoints=100
|
||||
*-------------------------------------------------------
|
||||
* itemType: SymElmFrame
|
||||
*-------------------------------------------------------
|
||||
pvs=0(Visibility),1(Alarm)
|
||||
*-------------------------------------------------------
|
||||
* itemType: SymElmRectangle
|
||||
*-------------------------------------------------------
|
||||
pvs=0(Visibility),1(Alarm)
|
||||
*-------------------------------------------------------
|
||||
* itemType: SymElmCircle
|
||||
*-------------------------------------------------------
|
||||
pvs=0(Visibility),1(Alarm)
|
||||
controlPoints=11,12
|
||||
contextActions(editmode)=10(ClosePath),21(Pie),20(Arc),30(90deg),31(180deg),32(270deg),33(360deg)
|
||||
*-------------------------------------------------------
|
||||
* itemType: SymElmText
|
||||
*-------------------------------------------------------
|
||||
pvs=0(Visibility),1(Alarm)
|
||||
*-------------------------------------------------------
|
||||
* itemType: SymElmPath
|
||||
*-------------------------------------------------------
|
||||
pvs=0(Visibility),1(Alarm)
|
||||
contextActions(editmode)=10(ClosePath),11(FillSolid),20(OddEven),21(Winding)
|
||||
*-------------------------------------------------------
|
||||
* itemType: SymElmImage
|
||||
*-------------------------------------------------------
|
||||
pvs=0(Visibility),1(Alarm),2(Monitor)
|
||||
*-------------------------------------------------------
|
||||
* itemType: Cross
|
||||
*-------------------------------------------------------
|
||||
pvs=0(Visibility),1(Alarm)
|
||||
controlPoints=1,2
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,34 @@
|
||||
from ..devices_general.motors import MotorRecord
|
||||
from ..aliases import Alias, append_object_to_object
|
||||
|
||||
|
||||
def addMotorRecordToSelf(self, name=None, Id=None):
|
||||
try:
|
||||
self.__dict__[name] = MotorRecord(Id, name=name)
|
||||
self.alias.append(self.__dict__[name].alias)
|
||||
except:
|
||||
print(f"Warning! Could not find motor {name} (Id:{Id})")
|
||||
|
||||
self.Id = Id
|
||||
self.name = name
|
||||
self.alias = Alias(name)
|
||||
|
||||
|
||||
class stage:
|
||||
def __init__(self, name=None, vonHamos_horiz_pv=None, vonHamos_vert_pv=None):
|
||||
self.name = name
|
||||
self.alias = Alias(name)
|
||||
addMotorRecordToSelf(self, Id=vonHamos_horiz_pv, name="horiz")
|
||||
addMotorRecordToSelf(self, Id=vonHamos_vert_pv, name="vert")
|
||||
|
||||
def get_adjustable_positions_str(self):
|
||||
ostr = "***** VonHamos motor positions******\n"
|
||||
|
||||
for tkey, item in self.__dict__.items():
|
||||
if hasattr(item, "get_current_value"):
|
||||
pos = item.get_current_value()
|
||||
ostr += " " + tkey.ljust(17) + " : % 14g\n" % pos
|
||||
return ostr
|
||||
|
||||
def __repr__(self):
|
||||
return self.get_adjustable_positions_str()
|
||||
@@ -0,0 +1,66 @@
|
||||
from ..elements.assembly import Assembly
|
||||
from ..epics.adjustable import AdjustablePv
|
||||
|
||||
|
||||
class MforceChannel(Assembly):
|
||||
def __init__(self, pv_motor, pv_mcodebase, name=None):
|
||||
super().__init__(name=name)
|
||||
self.pv_motor = pv_motor # Example SARES20-MF1:
|
||||
self.pv_mcodebase = pv_mcodebase
|
||||
self._append(
|
||||
AdjustablePv,
|
||||
self.pv_mcodebase + "_RC",
|
||||
name="run_current",
|
||||
is_setting=True,
|
||||
)
|
||||
self._append(
|
||||
AdjustablePv,
|
||||
self.pv_mcodebase + "_HC",
|
||||
name="holding_current",
|
||||
is_setting=True,
|
||||
)
|
||||
self._append(
|
||||
AdjustablePv,
|
||||
self.pv_motor + ".DESC",
|
||||
name="display_name",
|
||||
is_setting=True,
|
||||
)
|
||||
self._append(
|
||||
AdjustablePv,
|
||||
self.pv_motor + ".EGU",
|
||||
name="units",
|
||||
is_setting=True,
|
||||
)
|
||||
self._append(
|
||||
AdjustablePv,
|
||||
self.pv_motor + ".MRES",
|
||||
name="motor_resolution",
|
||||
is_setting=True,
|
||||
)
|
||||
self._append(
|
||||
AdjustablePv,
|
||||
self.pv_motor + ".ERES",
|
||||
name="encoder_resolution",
|
||||
is_setting=True,
|
||||
)
|
||||
self._append(
|
||||
AdjustablePv,
|
||||
self.pv_motor + ".VELO",
|
||||
name="velocity",
|
||||
is_setting=True,
|
||||
)
|
||||
|
||||
# self._append(
|
||||
# PvRecord,
|
||||
# self.pv_base + self.port + "_set",
|
||||
# name="limit_switch_I",
|
||||
# is_setting=True,
|
||||
# ) # IS=1,2,0
|
||||
# # IS=1,3,0 set wire 1 (1,3) to high limit, active when at 0
|
||||
# self._append(
|
||||
# PvRecord,
|
||||
# self.pv_base + self.port + "_set",
|
||||
# name="limit_switch_II",
|
||||
# is_setting=True,
|
||||
# ) # IS=2,3,0
|
||||
# # IS=2,2,0 set wire 2 (2,2) to low limit, active when at 0
|
||||
+2514
-102
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,199 @@
|
||||
from cam_server import CamClient, PipelineClient
|
||||
|
||||
from eco.devices_general.utilities import Changer
|
||||
from eco.elements.adj_obj import AdjustableObject, DetectorObject
|
||||
from eco.elements.detector import DetectorGet
|
||||
from ..aliases import Alias, append_object_to_object
|
||||
from ..elements.adjustable import AdjustableVirtual, AdjustableGetSet, value_property
|
||||
from ..epics.adjustable import AdjustablePv, AdjustablePvEnum
|
||||
from ..elements.assembly import Assembly
|
||||
from .motors import MotorRecord
|
||||
import sys
|
||||
from pathlib import Path
|
||||
import time
|
||||
import cachebox
|
||||
|
||||
sys.path.append("/sf/bernina/config/src/python/sf_databuffer/")
|
||||
import bufferutils
|
||||
|
||||
CAM_CLIENT = None
|
||||
PIPELINE_CLIENT = None
|
||||
|
||||
|
||||
def get_camclient():
|
||||
global CAM_CLIENT
|
||||
if not CAM_CLIENT:
|
||||
CAM_CLIENT = CamClient()
|
||||
return CAM_CLIENT
|
||||
|
||||
|
||||
def get_pipelineclient():
|
||||
global PIPELINE_CLIENT
|
||||
if not PIPELINE_CLIENT:
|
||||
PIPELINE_CLIENT = PipelineClient()
|
||||
return PIPELINE_CLIENT
|
||||
|
||||
|
||||
@value_property
|
||||
class Pipeline(Assembly):
|
||||
def __init__(self, pipeline_name, name=None, camserver_group=None):
|
||||
super().__init__(name=name)
|
||||
self.pipeline_name = pipeline_name
|
||||
self.camserver_group = camserver_group
|
||||
self._append(
|
||||
AdjustableGetSet,
|
||||
self._get_config,
|
||||
self._set_config,
|
||||
cache_get_seconds=0.05,
|
||||
precision=0,
|
||||
check_interval=None,
|
||||
name="_config",
|
||||
is_setting=True,
|
||||
is_display=False,
|
||||
)
|
||||
|
||||
self._append(
|
||||
AdjustableObject,
|
||||
self._config,
|
||||
name="config",
|
||||
is_setting=False,
|
||||
# recursive=False,
|
||||
is_display="recursive",
|
||||
)
|
||||
self._append(
|
||||
DetectorGet,
|
||||
self._get_info,
|
||||
cache_get_seconds=0.05,
|
||||
name="_info",
|
||||
is_setting=False,
|
||||
is_display=False,
|
||||
)
|
||||
self._append(
|
||||
DetectorObject,
|
||||
self._info,
|
||||
name="info",
|
||||
is_display="recursive",
|
||||
is_setting=False,
|
||||
)
|
||||
|
||||
# @property
|
||||
# def cc(self):
|
||||
# return get_camclient()
|
||||
|
||||
@property
|
||||
def pc(self):
|
||||
return get_pipelineclient()
|
||||
|
||||
@cachebox.cached(cachebox.TTLCache(maxsize=0, ttl=1))
|
||||
def _get_config(self):
|
||||
return self.pc.get_instance_config(self.pipeline_name)
|
||||
|
||||
def _set_config(self, value, hold=False):
|
||||
return Changer(
|
||||
target=value,
|
||||
changer=lambda v: self.pc.set_instance_config(self.pipeline_name, v),
|
||||
hold=hold,
|
||||
)
|
||||
|
||||
def _get_info(self, reject_kws=["config"]):
|
||||
info = self.pc.get_instance_info(self.pipeline_name)
|
||||
for rkw in reject_kws:
|
||||
info.pop(rkw)
|
||||
return info
|
||||
|
||||
def _get_stream(self):
|
||||
return self.pc.get_instance_stream(self.pipeline_name)
|
||||
|
||||
# ### convenience functions ###
|
||||
# def set_alias(self, alias=None):
|
||||
# """creates an alias in the camera config on the server. If no alias is provided, it defaults to the camera name"""
|
||||
# if not alias:
|
||||
# alias = self.camserver_alias
|
||||
# self.set_config_fields({"alias": [alias.upper()]})
|
||||
|
||||
# def set_group(self, group=None):
|
||||
# """creates an alias in the camera config on the server. If no alias is provided, it defaults to the camera name"""
|
||||
# if not group:
|
||||
# group = self.camserver_group
|
||||
# self.set_config_fields({"group": group})
|
||||
|
||||
def restart_pipeline(self):
|
||||
base_directory = "/sf/bernina/config/src/python/sf_databuffer/"
|
||||
label = self.pipeline_name
|
||||
|
||||
policies = bufferutils.read_files(base_directory / Path("policies"), "policies")
|
||||
sources = bufferutils.read_files(base_directory / Path("sources"), "sources")
|
||||
sources_new = sources.copy()
|
||||
|
||||
# Only for debugging purposes
|
||||
labeled_sources = bufferutils.get_labeled_sources(sources_new, label)
|
||||
for s in labeled_sources:
|
||||
bufferutils.logging.info(f"Restarting {s['stream']}")
|
||||
|
||||
sources_new = bufferutils.remove_labeled_source(sources_new, label)
|
||||
|
||||
# Stopping the removed source(s)
|
||||
bufferutils.update_sources_and_policies(sources_new, policies)
|
||||
|
||||
# Starting the source(s) again
|
||||
bufferutils.update_sources_and_policies(sources, policies)
|
||||
|
||||
def stop(self):
|
||||
self.pc.stop_instance(self.pipeline_name)
|
||||
|
||||
# def set_cross(self, x, y, x_um_per_px=None, y_um_per_px=None):
|
||||
# """set x and y position of the refetence marker on a camera px/um calibration is conserved if no new value is given"""
|
||||
# calib = self.get_current_value()["camera_calibration"]
|
||||
# if calib:
|
||||
# if not x_um_per_px:
|
||||
# x_um_per_px = calib["reference_marker_width"] / abs(
|
||||
# calib["reference_marker"][2] - calib["reference_marker"][0]
|
||||
# )
|
||||
# if not y_um_per_px:
|
||||
# y_um_per_px = calib["reference_marker_height"] / abs(
|
||||
# calib["reference_marker"][3] - calib["reference_marker"][1]
|
||||
# )
|
||||
# else:
|
||||
# calib = {}
|
||||
# x_um_per_px = 1
|
||||
# y_um_per_px = 1
|
||||
|
||||
# calib["reference_marker"] = [x - 1, y - 1, x + 1, y + 1]
|
||||
# calib["reference_marker_width"] = 2 * x_um_per_px
|
||||
# calib["reference_marker_height"] = 2 * y_um_per_px
|
||||
# self.set_config_fields(fields={"camera_calibration": calib})
|
||||
|
||||
# def set_config_fields_multiple_cams(self, conditions, fields):
|
||||
# """
|
||||
# conditions is a dictionary holding the conditions to select a subset of cameras, e.g. {"group": Bernina}
|
||||
# fields is a dictionary containing the keys and values that should be updated, e.g. fields={'alias': ['huhu', 'duda']}
|
||||
# """
|
||||
# cams = {
|
||||
# cam: self.cc.get_camera_config(cam)
|
||||
# for cam in self.cc.get_cameras()
|
||||
# if not "jungfrau" in cam
|
||||
# }
|
||||
# cams_selected = {}
|
||||
# for cam, cfg in cams.items():
|
||||
# try:
|
||||
# if all([value in cfg[key] for key, value in conditions.items()]):
|
||||
# cfg.update(fields)
|
||||
# self.cc.set_camera_config(cam, cfg)
|
||||
# cams_selected[cam] = cfg
|
||||
# except Exception as e:
|
||||
# print(f"{type(e)} {e} in cam {cam}")
|
||||
# return cams_selected
|
||||
|
||||
# def clear_all_bernina_aliases(self, verbose=True):
|
||||
# cams_selected = self.set_config_fields_multiple_cams(
|
||||
# conditions={"group": "Bernina"}, fields={"alias": []}
|
||||
# )
|
||||
# if verbose:
|
||||
# print(f"Reset alias of {len(cams_selected)} cameras")
|
||||
# print(cams_selected.keys())
|
||||
|
||||
# def __repr__(self):
|
||||
# s = f"**Camera Server Config {self.pipeline_name} with Alias {self.name}**\n"
|
||||
# for key, item in self.get_current_value().items():
|
||||
# s += f"{key:20} : {item}\n"
|
||||
# return s
|
||||
@@ -0,0 +1,97 @@
|
||||
from eco import Assembly
|
||||
from eco.elements.detector import DetectorGet
|
||||
from eco.elements.adjustable import AdjustableGetSet
|
||||
from .PBComm import SSHComm
|
||||
from .powerbrick_parameters import *
|
||||
|
||||
connected_power_bricks = {}
|
||||
|
||||
|
||||
def get_power_brick_comm(hostname):
|
||||
if hostname in connected_power_bricks.keys():
|
||||
return connected_power_bricks[hostname]
|
||||
else:
|
||||
return PowerBrickComm(hostname)
|
||||
|
||||
|
||||
class PowerBrickComm:
|
||||
def __init__(self, hostname):
|
||||
self.pbsshcom = SSHComm()
|
||||
self.pbsshcom.connect(hostname)
|
||||
connected_power_bricks[hostname] = self
|
||||
|
||||
def get_parameter(self, parstring, autoconvert=True):
|
||||
if isinstance(parstring, bytes):
|
||||
parstring = parstring.decode()
|
||||
if not parstring.endswith("\n"):
|
||||
parstring += "\n"
|
||||
self.pbsshcom.chan.send(parstring)
|
||||
par = self.pbsshcom.read_until_endswith(SSHComm.gpascii_ack)
|
||||
par = par.split(SSHComm.gpascii_ack)[0]
|
||||
# print(par)
|
||||
if autoconvert:
|
||||
par = par.lower().split(parstring.lower().strip("\n"))[1]
|
||||
if par.startswith("="):
|
||||
par = par[1:]
|
||||
try:
|
||||
par = int(par)
|
||||
except ValueError:
|
||||
try:
|
||||
par = float(par)
|
||||
except ValueError:
|
||||
pass
|
||||
return par
|
||||
|
||||
def set_parameter(self, value, parstring):
|
||||
if isinstance(parstring, bytes):
|
||||
parstring = parstring.decode()
|
||||
if parstring.endswith("\n"):
|
||||
parstring.strip("\n")
|
||||
setstring = parstring + "=" + str(value)
|
||||
return self.pbsshcom.iawrite(setstring)
|
||||
|
||||
# def set_hmz
|
||||
|
||||
|
||||
class PowerBrickChannelPars(Assembly):
|
||||
def __init__(self, hostname, axis=None, type=None, pars_list=[], name=None):
|
||||
super().__init__(name=name)
|
||||
self._pbcom = get_power_brick_comm(hostname)
|
||||
self._ch = axis
|
||||
|
||||
if type == "motor":
|
||||
pars_list = pars_motors
|
||||
|
||||
else:
|
||||
pass
|
||||
|
||||
for par, mode in pars_list:
|
||||
if "r" in mode and "w" not in mode:
|
||||
self.append_par_detector(par)
|
||||
elif "r" in mode and "w" in mode:
|
||||
# print(par)
|
||||
self.append_par_adjustable(par)
|
||||
|
||||
def get_from_par(self, formstr, **kwargs):
|
||||
return self._pbcom.get_parameter(formstr.format(self._ch), **kwargs)
|
||||
|
||||
def set_from_par(self, value, formstr, **kwargs):
|
||||
return self._pbcom.set_parameter(value, formstr.format(self._ch), **kwargs)
|
||||
|
||||
def append_par_detector(self, par):
|
||||
self._append(DetectorGet, lambda: self.get_from_par(par), name=str2varname(par))
|
||||
|
||||
def append_par_adjustable(self, par):
|
||||
self._append(
|
||||
AdjustableGetSet,
|
||||
lambda: self.get_from_par(par),
|
||||
lambda value: self.set_from_par(value, par),
|
||||
name=str2varname(par),
|
||||
)
|
||||
|
||||
|
||||
def str2varname(s, delimeter_swaps=[(".", "_")]):
|
||||
for tds in delimeter_swaps:
|
||||
s = s.replace(*tds)
|
||||
s = "".join(filter(lambda c: str.isidentifier(c) or str.isdecimal(c), s))
|
||||
return s
|
||||
@@ -0,0 +1,148 @@
|
||||
pars_default = {
|
||||
("EncTable[{}].PrevEnc", "r"),
|
||||
("Motor[{}].pos", "r"),
|
||||
("Motor[{}].idCmd", "r"),
|
||||
("Motor[{}].Ctrl", "r"),
|
||||
}
|
||||
|
||||
|
||||
pars_StatusGblListCtrl = {
|
||||
("WDTFault", "r"),
|
||||
("PwrOnFault", "r"),
|
||||
("ProjectLoadErr", "r"),
|
||||
("ConfigLoadErr", "r"),
|
||||
("HWChangeErr", "r"),
|
||||
("FileConfigErr", "r"),
|
||||
("Default", "r"),
|
||||
("NoClocks", "r"),
|
||||
("AbortAll", "r"),
|
||||
("BufSizeErr", "r"),
|
||||
("FlashSizeErr", "r"),
|
||||
("CK3WConfigErr", "r"),
|
||||
("CK3WHWChange", "r"),
|
||||
}
|
||||
|
||||
|
||||
pars_StatusMotorListCtrl = {
|
||||
("AmpEna", "r"),
|
||||
("AmpWarn", "r"),
|
||||
("AmpFault", "r"),
|
||||
("ClosedLoop", "r"),
|
||||
("I2tFault", "r"),
|
||||
("FeWarn", "r"),
|
||||
("FeFatal", "r"),
|
||||
("DesVelZero", "r"),
|
||||
("InPos", "r"),
|
||||
("InPosBand", "rw"),
|
||||
("LimitStop", "r"),
|
||||
("MinusLimit", "r"),
|
||||
("PlusLimit", "r"),
|
||||
("EncLoss", "r"),
|
||||
("HomeInProgress", "r"),
|
||||
("HomeComplete", "r"),
|
||||
("HomeOffset", "r"),
|
||||
("TriggerMove", "r"),
|
||||
("DacLimit", "r"),
|
||||
("SoftLimit", "r"),
|
||||
("SoftMinusLimit", "r"),
|
||||
("SoftPlusLimit", "r"),
|
||||
("SoftLimitDir", "r"),
|
||||
("TriggerNotFound", "r"),
|
||||
("AuxFault", "r"),
|
||||
("BlockRequest", "r"),
|
||||
("PhaseFound", "r"),
|
||||
("TriggerSpeedSel", "r"),
|
||||
("GantryHomed", "r"),
|
||||
("SpindleMotor", "r"),
|
||||
("Csolve", "r"),
|
||||
("Servo.OutDbOn", "rw"),
|
||||
("Servo.OutDbOff", "rw"),
|
||||
("Servo.BreakPosErr", "rw"),
|
||||
("Servo.Kp", "rw"),
|
||||
("Servo.Ki", "rw"),
|
||||
("Servo.Kvff", "rw"),
|
||||
("Servo.Kaff", "rw"),
|
||||
("Servo.Kviff", "rw"),
|
||||
("Servo.MaxPosErr", "rw"),
|
||||
("Servo.MaxInt", "rw"),
|
||||
("FatalFeLimit", "rw"),
|
||||
("WarnFeLimit", "rw"),
|
||||
("BlDir", "r"),
|
||||
("BlDir", "r"),
|
||||
("BlDir", "r"),
|
||||
("BlDir", "r"),
|
||||
# ('TraceCount' , 8,1)))
|
||||
}
|
||||
|
||||
|
||||
pars_StatusCoordListCtrl = {
|
||||
("TriggerMove", "r"),
|
||||
("HomeInProgress", "r"),
|
||||
("MinusLimit", "r"),
|
||||
("PlusLimit", "r"),
|
||||
("FeWarn", "r"),
|
||||
("FeFatal", "r"),
|
||||
("LimitStop", "r"),
|
||||
("AmpFault", "r"),
|
||||
("SoftMinusLimit", "r"),
|
||||
("SoftPlusLimit", "r"),
|
||||
("I2tFault", "r"),
|
||||
("TriggerNotFound", "r"),
|
||||
("AmpWarn", "r"),
|
||||
("EncLoss", "r"),
|
||||
("AuxFault", "r"),
|
||||
("TimerEnabled", "r"),
|
||||
("HomeComplete", "r"),
|
||||
("DesVelZero", "r"),
|
||||
("ClosedLoop", "r"),
|
||||
("AmpEna", "r"),
|
||||
("InPos", "r"),
|
||||
("InPosBand", "rw"),
|
||||
("ErrorStatus", "r"),
|
||||
("BlockRequest", "r"),
|
||||
("TimersEnabled", "r"),
|
||||
("Csolve", "r"),
|
||||
("LinToPvtBuf", "r"),
|
||||
("FeedHold", "r"),
|
||||
("BlockActive", "r"),
|
||||
("ContMotion", "r"),
|
||||
("CCMode", "r"),
|
||||
("MoveMode", "r"),
|
||||
("SegMove", "r"),
|
||||
("SegMoveAccel", "r"),
|
||||
("SegMoveDecel", "r"),
|
||||
("SegEnabled", "r"),
|
||||
("SegStopReq", "r"),
|
||||
("LookAheadWrap", "r"),
|
||||
("LookAheadLookBack", "r"),
|
||||
("LookAheadDir", "r"),
|
||||
("LookAheadStop", "r"),
|
||||
("LookAheadChange", "r"),
|
||||
("LookAheadReCalc", "r"),
|
||||
("LookAheadFlush", "r"),
|
||||
("LookAheadActive", "r"),
|
||||
("CCAddedArc", "r"),
|
||||
("CCOffReq", "r"),
|
||||
("CCMoveType", "r"),
|
||||
("CC3Active", "r"),
|
||||
("SharpCornerStop", "r"),
|
||||
("AddedDwellDis", "r"),
|
||||
# ('ProgRunning', 'r'),
|
||||
# ('ProgActive', 'r'),
|
||||
# ('ProgProceeding', 'r'),
|
||||
# ('BufferWarn', 'r'))
|
||||
}
|
||||
|
||||
pars_default = {
|
||||
("EncTable[{}].PrevEnc", "r"),
|
||||
("EncTable[{}].EncLoss", "r"),
|
||||
("EncTable[{}].EncLossBit", "r"),
|
||||
("EncTable[{}].MaxDelta", "rw"),
|
||||
("Motor[{}].pos", "r"),
|
||||
("Motor[{}].idCmd", "r"),
|
||||
("Motor[{}].Ctrl", "r"),
|
||||
}
|
||||
|
||||
pars_motors = pars_default.union(
|
||||
{("Motor[{}]." + par, mode) for par, mode in pars_StatusMotorListCtrl}
|
||||
)
|
||||
@@ -0,0 +1,442 @@
|
||||
from ..epics.adjustable import AdjustablePvEnum, AdjustablePvString, AdjustablePv
|
||||
from ..elements.assembly import Assembly
|
||||
from ..epics.detector import DetectorPvEnum, DetectorPvData
|
||||
from .detectors import DetectorVirtual
|
||||
from functools import partial
|
||||
from eco.elements.adjustable import spec_convenience
|
||||
|
||||
|
||||
class PowerSocket(Assembly):
|
||||
def __init__(self, pvname, name=None):
|
||||
super().__init__(name=name)
|
||||
self.pvname = pvname
|
||||
self._append(
|
||||
AdjustablePvString,
|
||||
pvname + ":POWERONOFF-DESC",
|
||||
name="description",
|
||||
is_setting=True,
|
||||
)
|
||||
self._append(
|
||||
DetectorPvEnum, pvname + ":POWERONOFF-RB", name="stat", is_display=True
|
||||
)
|
||||
self._append(
|
||||
AdjustablePvEnum,
|
||||
pvname + ":POWERONOFF",
|
||||
name="on_switch",
|
||||
is_setting=True,
|
||||
is_display=False,
|
||||
)
|
||||
self._append(
|
||||
AdjustablePvString,
|
||||
pvname + ":POWERCYCLE",
|
||||
name="powercycle_for_10s",
|
||||
is_setting=False,
|
||||
is_display=False,
|
||||
)
|
||||
|
||||
def toggle(self):
|
||||
self.on_switch(int(not (self.stat() == 1)))
|
||||
|
||||
def on(self):
|
||||
self.on_switch(1)
|
||||
|
||||
def off(self):
|
||||
self.on_switch(0)
|
||||
|
||||
def __call__(self, *args):
|
||||
if not args:
|
||||
self.toggle()
|
||||
else:
|
||||
self.on_switch(args[0])
|
||||
|
||||
|
||||
class GudeStrip(Assembly):
|
||||
def __init__(self, pvbase, name=None):
|
||||
super().__init__(name=name)
|
||||
self.pvbase = pvbase
|
||||
for n in range(1, 5):
|
||||
self._append(
|
||||
PowerSocket,
|
||||
pvbase + f"-CH{n}",
|
||||
is_display="recursive",
|
||||
is_setting=True,
|
||||
name=f"ch{n}",
|
||||
)
|
||||
self._append(
|
||||
DetectorPvData, pvbase + ":CURRENT", is_display=True, name="current"
|
||||
)
|
||||
self._append(
|
||||
DetectorPvData, pvbase + ":VOLTAGE", is_display=True, name="voltage"
|
||||
)
|
||||
|
||||
|
||||
class MpodStatus(Assembly):
|
||||
def __init__(self, pvbase, channel_number, module_string="LV_OMPV_1", name=None):
|
||||
super().__init__(name=name)
|
||||
self.pvbase = pvbase
|
||||
self._module_string = module_string
|
||||
self.channel_number = channel_number
|
||||
self._append(
|
||||
DetectorPvEnum,
|
||||
self.pvbase + f":{self._module_string}_CH{self.channel_number}_ON",
|
||||
name="is_on",
|
||||
)
|
||||
self._append(
|
||||
DetectorPvEnum,
|
||||
self.pvbase + f":{self._module_string}_CH{self.channel_number}_INHIBIT",
|
||||
name="inhibited",
|
||||
)
|
||||
self._append(
|
||||
DetectorPvEnum,
|
||||
self.pvbase
|
||||
+ f":{self._module_string}_CH{self.channel_number}_FAILURE_MIN_SENS_VOLTAGE",
|
||||
name="voltage_readback_low",
|
||||
)
|
||||
self._append(
|
||||
DetectorPvEnum,
|
||||
self.pvbase
|
||||
+ f":{self._module_string}_CH{self.channel_number}_FAILURE_MAX_SENS_VOLTAGE",
|
||||
name="voltage_readback_high",
|
||||
)
|
||||
self._append(
|
||||
DetectorPvEnum,
|
||||
self.pvbase
|
||||
+ f":{self._module_string}_CH{self.channel_number}_FAILURE_MAX_TERM_VOLTAGE",
|
||||
name="terminal_voltage_readback_high",
|
||||
)
|
||||
self._append(
|
||||
DetectorPvEnum,
|
||||
self.pvbase
|
||||
+ f":{self._module_string}_CH{self.channel_number}_FAILURE_MAX_CURRENT",
|
||||
name="current_too_high",
|
||||
)
|
||||
self._append(
|
||||
DetectorPvEnum,
|
||||
self.pvbase
|
||||
+ f":{self._module_string}_CH{self.channel_number}_FAILURE_MAX_TEMP",
|
||||
name="temperature_high",
|
||||
)
|
||||
self._append(
|
||||
DetectorPvEnum,
|
||||
self.pvbase
|
||||
+ f":{self._module_string}_CH{self.channel_number}_FAILURE_MAX_POWER",
|
||||
name="output_power_high",
|
||||
)
|
||||
self._append(
|
||||
DetectorPvEnum,
|
||||
self.pvbase + f":{self._module_string}_CH{self.channel_number}_TIMEOUT",
|
||||
name="communication_timeout",
|
||||
)
|
||||
self._append(
|
||||
DetectorPvEnum,
|
||||
self.pvbase + f":{self._module_string}_CH{self.channel_number}_CURR_CTRL",
|
||||
name="constant_current_mode",
|
||||
)
|
||||
self._append(
|
||||
DetectorPvEnum,
|
||||
self.pvbase + f":{self._module_string}_CH{self.channel_number}_RMP_UP",
|
||||
name="ramping_up",
|
||||
)
|
||||
self._append(
|
||||
DetectorPvEnum,
|
||||
self.pvbase + f":{self._module_string}_CH{self.channel_number}_RMP_DOWN",
|
||||
name="ramping_down",
|
||||
)
|
||||
self._append(
|
||||
DetectorPvEnum,
|
||||
self.pvbase + f":{self._module_string}_CH{self.channel_number}_KILL",
|
||||
name="kill_enabled",
|
||||
)
|
||||
self._append(
|
||||
DetectorPvEnum,
|
||||
self.pvbase
|
||||
+ f":{self._module_string}_CH{self.channel_number}_EMERGENCY_OFF",
|
||||
name="emergency_off",
|
||||
)
|
||||
self._append(
|
||||
DetectorPvEnum,
|
||||
self.pvbase + f":{self._module_string}_CH{self.channel_number}_FINE_ADJUST",
|
||||
name="fine_adjustment",
|
||||
)
|
||||
self._append(
|
||||
DetectorPvEnum,
|
||||
self.pvbase
|
||||
+ f":{self._module_string}_CH{self.channel_number}_VOLTAGE_CTRL",
|
||||
name="constant_voltage_mode",
|
||||
)
|
||||
self._append(
|
||||
DetectorPvEnum,
|
||||
self.pvbase
|
||||
+ f":{self._module_string}_CH{self.channel_number}_LOW_CURR_MEAS",
|
||||
name="current_readback_range_low",
|
||||
)
|
||||
self._append(
|
||||
DetectorPvEnum,
|
||||
self.pvbase
|
||||
+ f":{self._module_string}_CH{self.channel_number}_OUT_CURR_OOB",
|
||||
name="current_readback_range_high",
|
||||
)
|
||||
self._append(
|
||||
DetectorPvEnum,
|
||||
self.pvbase + f":{self._module_string}_CH{self.channel_number}_OVERCURRENT",
|
||||
name="overcurrent",
|
||||
)
|
||||
|
||||
|
||||
@spec_convenience
|
||||
class MpodChannel(Assembly):
|
||||
def __init__(self, pvbase, channel_number, module_string="LV_OMPV_1", name=None):
|
||||
super().__init__(name=name)
|
||||
self.pvbase = pvbase
|
||||
self._module_string = module_string
|
||||
self.channel_number = channel_number
|
||||
self._append(
|
||||
AdjustablePvEnum,
|
||||
self.pvbase + f":{self._module_string}_CH{self.channel_number}_SWITCH_SP",
|
||||
name="on",
|
||||
is_setting=True,
|
||||
)
|
||||
self._append(
|
||||
AdjustablePv,
|
||||
self.pvbase + f":{self._module_string}_CH{self.channel_number}_OUTPUT_V_SP",
|
||||
pvreadbackname=self.pvbase
|
||||
+ f":{self._module_string}_CH{self.channel_number}_MEAS_SENS_V",
|
||||
pvlowlimname=self.pvbase
|
||||
+ f":{self._module_string}_CH{self.channel_number}_OUTPUT_V_SP.LOPR",
|
||||
pvhighlimname=self.pvbase
|
||||
+ f":{self._module_string}_CH{self.channel_number}_OUTPUT_V_SP.HOPR",
|
||||
name="voltage",
|
||||
is_setting=True,
|
||||
)
|
||||
self._append(
|
||||
AdjustablePv,
|
||||
self.pvbase + f":{self._module_string}_CH{self.channel_number}_OUTPUT_V_SP",
|
||||
pvlowlimname=self.pvbase
|
||||
+ f":{self._module_string}_CH{self.channel_number}_OUTPUT_V_SP.LOPR",
|
||||
pvhighlimname=self.pvbase
|
||||
+ f":{self._module_string}_CH{self.channel_number}_OUTPUT_V_SP.HOPR",
|
||||
name="voltage_setpoint",
|
||||
is_setting=True,
|
||||
)
|
||||
self._append(
|
||||
AdjustablePv,
|
||||
self.pvbase
|
||||
+ f":{self._module_string}_CH{self.channel_number}_RMP_UP_RATE_SP",
|
||||
pvlowlimname=self.pvbase
|
||||
+ f":{self._module_string}_CH{self.channel_number}_RMP_UP_RATE_SP.LOPR",
|
||||
pvhighlimname=self.pvbase
|
||||
+ f":{self._module_string}_CH{self.channel_number}_RMP_UP_RATE_SP.HOPR",
|
||||
name="ramp_up",
|
||||
is_setting=True,
|
||||
)
|
||||
self._append(
|
||||
AdjustablePv,
|
||||
self.pvbase
|
||||
+ f":{self._module_string}_CH{self.channel_number}_RMP_DOWN_RATE_SP",
|
||||
pvlowlimname=self.pvbase
|
||||
+ f":{self._module_string}_CH{self.channel_number}_RMP_DOWN_RATE_SP.LOPR",
|
||||
pvhighlimname=self.pvbase
|
||||
+ f":{self._module_string}_CH{self.channel_number}_RMP_DOWN_RATE_SP.HOPR",
|
||||
name="ramp_down",
|
||||
is_setting=True,
|
||||
)
|
||||
self._append(
|
||||
AdjustablePv,
|
||||
self.pvbase + f":{self._module_string}_CH{self.channel_number}_MEAS_OUT_A",
|
||||
name="current",
|
||||
is_setting=False,
|
||||
is_display=True,
|
||||
)
|
||||
self._append(
|
||||
MpodStatus,
|
||||
self.pvbase,
|
||||
self.channel_number,
|
||||
self._module_string,
|
||||
name="flags",
|
||||
)
|
||||
|
||||
def get_current_value(self, *args, **kwargs):
|
||||
return self.on.get_current_value(*args, **kwargs)
|
||||
|
||||
def set_target_value(self, *args, **kwargs):
|
||||
return self.on.set_target_value(*args, **kwargs)
|
||||
|
||||
|
||||
class MpodModule(Assembly):
|
||||
def __init__(
|
||||
self, pvbase, channelnumbers, channelnames, module_string="LV_OMPV_1", name=None
|
||||
):
|
||||
super().__init__(name=name)
|
||||
for channelnumber, channelname in zip(channelnumbers, channelnames):
|
||||
self._append(
|
||||
MpodChannel,
|
||||
pvbase,
|
||||
channel_number=channelnumber,
|
||||
module_string=module_string,
|
||||
name=channelname,
|
||||
)
|
||||
|
||||
|
||||
# for new ioc by Thierry
|
||||
|
||||
flag_names_mpod = [
|
||||
"outputOn",
|
||||
"outputInhibit",
|
||||
"outputFailureMinSenseVoltage",
|
||||
"outputFailureMaxSenseVoltage",
|
||||
"outputFailureMaxTerminalVoltage",
|
||||
"outputFailureMaxCurrent",
|
||||
"outputFailureMaxTemperature",
|
||||
"outputFailureMaxPower",
|
||||
"outputFailureTimeout",
|
||||
"outputCurrentLimited",
|
||||
"outputRampUp",
|
||||
"outputRampDown",
|
||||
"outputEnableKill",
|
||||
"outputEmergencyOff",
|
||||
"outputAdjusting",
|
||||
"outputConstantVoltage",
|
||||
"outputLowCurrentRange",
|
||||
"outputCurrentBoundsExceeded",
|
||||
"outputFailureCurrentLimit",
|
||||
"outputCurrentIncreasing",
|
||||
"outputCurrentDecreasing",
|
||||
"outputConstantPower",
|
||||
"outputVoltageRampSpeedLimited",
|
||||
"outputVoltageBottomReached",
|
||||
"outputInitCrcCheckBad",
|
||||
]
|
||||
|
||||
|
||||
class NEW_MpodFlags(Assembly):
|
||||
def __init__(self, flags, name="flags"):
|
||||
super().__init__(name=name)
|
||||
self._flags = flags
|
||||
for flag_name in flag_names_mpod:
|
||||
self._append(
|
||||
DetectorVirtual,
|
||||
[self._flags],
|
||||
partial(self._get_flag_name_value, flag_name=flag_name),
|
||||
name=flag_name,
|
||||
is_status=False,
|
||||
is_display=True,
|
||||
)
|
||||
|
||||
def _get_flag_name_value(self, value, flag_name=None):
|
||||
index = flag_names_mpod.index(flag_name)
|
||||
return int("{0:015b}".format(int(value))[-1 * (index + 1)]) == 1
|
||||
|
||||
|
||||
class NEW_MpodChannel(Assembly):
|
||||
def __init__(self, pvbase, channel_number, module_string, name=None):
|
||||
super().__init__(name=name)
|
||||
self.pvbase = pvbase
|
||||
self._module_string = module_string
|
||||
self.channel_number = channel_number
|
||||
|
||||
self._append(
|
||||
AdjustablePvEnum,
|
||||
self.pvbase + f":{self._module_string}0{self.channel_number}-SWITCH_SP",
|
||||
name="on",
|
||||
is_setting=True,
|
||||
is_display=True,
|
||||
)
|
||||
|
||||
self._append(
|
||||
AdjustablePv,
|
||||
self.pvbase + f":{self._module_string}0{self.channel_number}-V_SP",
|
||||
pvreadbackname=self.pvbase
|
||||
+ f":{self._module_string}0{self.channel_number}-V_RB",
|
||||
pvlowlimname=self.pvbase,
|
||||
name="voltage",
|
||||
is_setting=True,
|
||||
is_display=True,
|
||||
)
|
||||
|
||||
self._append(
|
||||
AdjustablePv,
|
||||
self.pvbase + f":{self._module_string}0{self.channel_number}-I_SP",
|
||||
pvreadbackname=self.pvbase
|
||||
+ f":{self._module_string}0{self.channel_number}-I_RB",
|
||||
pvlowlimname=self.pvbase,
|
||||
name="current",
|
||||
is_setting=True,
|
||||
is_display=True,
|
||||
)
|
||||
|
||||
# self._append(
|
||||
# AdjustablePv,
|
||||
# self.pvbase + f":{self._module_string}0{self.channel_number}-VRISE_SP",
|
||||
# pvreadbackname=self.pvbase
|
||||
# + f":{self._module_string}0{self.channel_number}-VRISE_RB",
|
||||
# pvlowlimname=self.pvbase,
|
||||
# name="V_rise",
|
||||
# is_setting=True,
|
||||
# is_display=True,
|
||||
# )
|
||||
|
||||
# self._append(
|
||||
# AdjustablePv,
|
||||
# self.pvbase + f":{self._module_string}0{self.channel_number}-IRISE_SP",
|
||||
# pvreadbackname=self.pvbase
|
||||
# + f":{self._module_string}0{self.channel_number}-IRISE_RB",
|
||||
# pvlowlimname=self.pvbase,
|
||||
# name="I_rise",
|
||||
# is_setting=True,
|
||||
# is_display=True,
|
||||
# )
|
||||
# self._append(
|
||||
# AdjustablePv,
|
||||
# self.pvbase + f":{self._module_string}0{self.channel_number}-VFALL_SP",
|
||||
# pvreadbackname=self.pvbase
|
||||
# + f":{self._module_string}0{self.channel_number}-VFALL_RB",
|
||||
# pvlowlimname=self.pvbase,
|
||||
# name="V_fall",
|
||||
# is_setting=True,
|
||||
# is_display=True,
|
||||
# )
|
||||
|
||||
# self._append(
|
||||
# AdjustablePv,
|
||||
# self.pvbase + f":{self._module_string}0{self.channel_number}-IFALL_SP",
|
||||
# pvreadbackname=self.pvbase
|
||||
# + f":{self._module_string}0{self.channel_number}-IFALL_RB",
|
||||
# pvlowlimname=self.pvbase,
|
||||
# name="I_fall",
|
||||
# is_setting=True,
|
||||
# is_display=True,
|
||||
# )
|
||||
|
||||
self._append(
|
||||
DetectorPvData,
|
||||
self.pvbase + f":{self._module_string}0{self.channel_number}-STAT",
|
||||
name="_flags",
|
||||
is_setting=False,
|
||||
)
|
||||
|
||||
self._append(
|
||||
NEW_MpodFlags,
|
||||
self._flags,
|
||||
name="flags",
|
||||
is_setting=False,
|
||||
is_status=True,
|
||||
)
|
||||
|
||||
def get_current_value(self, *args, **kwargs):
|
||||
return self.on.get_current_value(*args, **kwargs)
|
||||
|
||||
def set_target_value(self, *args, **kwargs):
|
||||
return self.on.set_target_value(*args, **kwargs)
|
||||
|
||||
|
||||
class NEW_MpodModule(Assembly):
|
||||
def __init__(self, pvbase, channelnumbers, channelnames, module_string, name=None):
|
||||
super().__init__(name=name)
|
||||
for channelnumber, channelname in zip(channelnumbers, channelnames):
|
||||
self._append(
|
||||
NEW_MpodChannel,
|
||||
pvbase,
|
||||
channel_number=channelnumber,
|
||||
module_string=module_string,
|
||||
name=channelname,
|
||||
)
|
||||
@@ -0,0 +1,99 @@
|
||||
from epics import PV
|
||||
import os
|
||||
import numpy as np
|
||||
import time
|
||||
from .utilities import Changer
|
||||
from ..aliases import Alias
|
||||
from time import sleep
|
||||
|
||||
|
||||
class PvRecord:
|
||||
def __init__(
|
||||
self,
|
||||
pvsetname,
|
||||
pvreadbackname=None,
|
||||
accuracy=None,
|
||||
sleeptime=0,
|
||||
name=None,
|
||||
elog=None,
|
||||
):
|
||||
|
||||
self.Id = pvsetname
|
||||
self.name = name
|
||||
self.alias = Alias(name)
|
||||
self.sleeptime = sleeptime
|
||||
|
||||
self._pv = PV(self.Id, connection_timeout=0.05, auto_monitor=True)
|
||||
self._currentChange = None
|
||||
self.accuracy = accuracy
|
||||
|
||||
if pvreadbackname is None:
|
||||
self._pvreadback = PV(self.Id, connection_timeout=0.05, auto_monitor=True)
|
||||
alias_fields = {"set": pvsetname}
|
||||
else:
|
||||
self._pvreadback = PV(
|
||||
pvreadbackname, connection_timeout=0.05, auto_monitor=True
|
||||
)
|
||||
alias_fields = {"set": pvsetname, "readback": pvreadbackname}
|
||||
|
||||
for name, ch in alias_fields.items():
|
||||
self.alias.append(Alias(name, channel=ch, channeltype="CA"))
|
||||
|
||||
def get_current_value(self, readback=True):
|
||||
if readback:
|
||||
currval = self._pvreadback.get()
|
||||
if not readback:
|
||||
currval = self._pv.get()
|
||||
return currval
|
||||
|
||||
def get_moveDone(self):
|
||||
"""Adjustable convention"""
|
||||
""" 0: moving 1: move done"""
|
||||
movedone = 1
|
||||
if self.accuracy is not None:
|
||||
if (
|
||||
np.abs(
|
||||
self.get_current_value(readback=False)
|
||||
- self.get_current_value(readback=True)
|
||||
)
|
||||
> self.accuracy
|
||||
):
|
||||
movedone = 0
|
||||
else:
|
||||
sleep(self.sleeptime)
|
||||
return movedone
|
||||
|
||||
def move(self, value):
|
||||
self._pv.put(value)
|
||||
time.sleep(0.1)
|
||||
while self.get_moveDone() == 0:
|
||||
time.sleep(0.1)
|
||||
|
||||
def set_target_value(self, value, hold=False):
|
||||
"""Adjustable convention"""
|
||||
|
||||
changer = lambda value: self.move(value)
|
||||
return Changer(
|
||||
target=value, parent=self, changer=changer, hold=hold, stopper=None
|
||||
)
|
||||
|
||||
# spec-inspired convenience methods
|
||||
def mv(self, value):
|
||||
self._currentChange = self.set_target_value(value)
|
||||
|
||||
def wm(self, *args, **kwargs):
|
||||
return self.get_current_value(*args, **kwargs)
|
||||
|
||||
def mvr(self, value, *args, **kwargs):
|
||||
|
||||
if self.get_moveDone == 1:
|
||||
startvalue = self.get_current_value(readback=True, *args, **kwargs)
|
||||
else:
|
||||
startvalue = self.get_current_value(readback=False, *args, **kwargs)
|
||||
self._currentChange = self.set_target_value(value + startvalue, *args, **kwargs)
|
||||
|
||||
def wait(self):
|
||||
self._currentChange.wait()
|
||||
|
||||
def __repr__(self):
|
||||
return "%s is at: %s" % (self.Id, self.get_current_value())
|
||||
@@ -0,0 +1,34 @@
|
||||
from eco.devices_general.wago import AnalogInput
|
||||
from eco.elements.adjustable import AdjustableVirtual
|
||||
|
||||
|
||||
class OxygenSensor(AnalogInput):
|
||||
def __init__(self, pvname, name=None):
|
||||
super().__init__(pvname, name=name)
|
||||
self.unit.set_target_value("%")
|
||||
|
||||
def set_no_oxygen(self, val_curr=None):
|
||||
if not val_curr:
|
||||
val_curr = self.raw.get_current_value()
|
||||
slo = self.linear_calibration_slope.get_current_value()
|
||||
off = self.linear_calibration_offset.get_current_value()
|
||||
|
||||
off_n = -slo * val_curr
|
||||
af = (100 - off) / slo
|
||||
# slo_new = slo*((100-val_curr)/(100-off))
|
||||
slo_n = 100 / af
|
||||
self.linear_calibration_offset(off_n)
|
||||
self.linear_calibration_slope(slo_n)
|
||||
|
||||
def set_full_oxygen(self, val_curr=None):
|
||||
if not val_curr:
|
||||
val_curr = self.raw.get_current_value()
|
||||
af = val_curr
|
||||
slo = self.linear_calibration_slope.get_current_value()
|
||||
off = self.linear_calibration_offset.get_current_value()
|
||||
|
||||
az = -off / slo
|
||||
slo_n = 100 / (af - az)
|
||||
off_n = -slo_n * az
|
||||
self.linear_calibration_offset(off_n)
|
||||
self.linear_calibration_slope(slo_n)
|
||||
@@ -4,9 +4,28 @@ from epics import PV, ca
|
||||
import time
|
||||
from ..eco_epics import device
|
||||
from ..eco_epics.device import Device
|
||||
from .utilities import Changer
|
||||
from ..aliases import Alias
|
||||
|
||||
|
||||
_guiTypes = ["xdm"]
|
||||
|
||||
_status_messages = {
|
||||
-13: "invalid value (cannot convert to float). Move not attempted.",
|
||||
-12: "target value outside soft limits. Move not attempted.",
|
||||
-11: "drive PV is not connected: Move not attempted.",
|
||||
-8: "move started, but timed-out.",
|
||||
-7: "move started, timed-out, but appears done.",
|
||||
-5: "move started, unexpected return value from PV.put()",
|
||||
-4: "move-with-wait finished, soft limit violation seen",
|
||||
-3: "move-with-wait finished, hard limit violation seen",
|
||||
0: "move-with-wait finish OK.",
|
||||
0: "move-without-wait executed, not cpmfirmed",
|
||||
1: "move-without-wait executed, move confirmed",
|
||||
3: "move-without-wait finished, hard limit violation seen",
|
||||
4: "move-without-wait finished, soft limit violation seen",
|
||||
}
|
||||
|
||||
|
||||
def _keywordChecker(kw_key_list_tups):
|
||||
for tkw, tkey, tlist in kw_key_list_tups:
|
||||
@@ -14,7 +33,7 @@ def _keywordChecker(kw_key_list_tups):
|
||||
|
||||
|
||||
class SmarActException(Exception):
|
||||
""" raised to indicate a problem with a smartact"""
|
||||
"""raised to indicate a problem with a smartact"""
|
||||
|
||||
def __init__(self, msg, *args):
|
||||
Exception.__init__(self, *args)
|
||||
@@ -55,13 +74,22 @@ class SmarAct(Device):
|
||||
|
||||
|
||||
class SmarActRecord:
|
||||
def __init__(self, Id, name=None, elog=None):
|
||||
def __init__(
|
||||
self,
|
||||
Id,
|
||||
name=None,
|
||||
elog=None,
|
||||
alias_fields={"readback": "MOTRBV", "homed": "GET_HOMED"},
|
||||
):
|
||||
|
||||
self.Id = Id
|
||||
self._device = Device(Id, delim=":")
|
||||
self._drive = SmarAct(Id + ":DRIVE")
|
||||
self._rbv = SmarAct(Id + ":MOTRBV")
|
||||
self._hlm = SmarAct(Id + ":HLM")
|
||||
self._llm = SmarAct(Id + ":LLM")
|
||||
self._status = SmarAct(Id + ":STATUS")
|
||||
self._statusstg = SmarAct(Id + ":STATUS")
|
||||
self._status = []
|
||||
self._set_pos = SmarAct(Id + ":SET_POS")
|
||||
self._stop = SmarAct(Id + ":STOP")
|
||||
self._hold = SmarAct(Id + ":HOLD")
|
||||
@@ -69,22 +97,35 @@ class SmarActRecord:
|
||||
self._elog = elog
|
||||
self.name = name
|
||||
self.units = self._drive.get("EGU")
|
||||
self.alias = Alias(name)
|
||||
for an, af in alias_fields.items():
|
||||
self.alias.append(
|
||||
Alias(an, channel=".".join([self.Id, af]), channeltype="CA")
|
||||
)
|
||||
|
||||
# Conventional methods and properties for all Adjustable objects
|
||||
def changeTo(self, value, hold=False, check=True):
|
||||
""" Adjustable convention"""
|
||||
def set_target_value(self, value, hold=False, check=True):
|
||||
"""Adjustable convention"""
|
||||
|
||||
mover = lambda value: self.move(value, ignore_limits=(not check), wait=True)
|
||||
def changer(value):
|
||||
self._status = self.move(value, ignore_limits=(not check), wait=True)
|
||||
self._status_message = _status_messages[self._status]
|
||||
# if not self._status == 0:
|
||||
# print(self._status_message)
|
||||
|
||||
# mover = lambda value: self.move(\
|
||||
# value, ignore_limits=(not check),
|
||||
# wait=True)
|
||||
return Changer(
|
||||
target=value,
|
||||
parent=self,
|
||||
mover=mover,
|
||||
changer=changer,
|
||||
hold=hold,
|
||||
stopper=self._stop.put("PROC", 1),
|
||||
)
|
||||
|
||||
def stop(self):
|
||||
""" Adjustable convention"""
|
||||
"""Adjustable convention"""
|
||||
try:
|
||||
self._currentChange.stop()
|
||||
except:
|
||||
@@ -92,7 +133,7 @@ class SmarActRecord:
|
||||
pass
|
||||
|
||||
def within_limits(self, val):
|
||||
""" returns whether a value for a motor is within drive limits"""
|
||||
"""returns whether a value for a motor is within drive limits"""
|
||||
return val <= self._hlm.get("VAL") and val >= self._llm.get("VAL")
|
||||
|
||||
def move(
|
||||
@@ -104,7 +145,7 @@ class SmarActRecord:
|
||||
ignore_limits=False,
|
||||
confirm_move=False,
|
||||
):
|
||||
""" moves smaract drive to position
|
||||
"""moves smaract drive to position
|
||||
|
||||
arguments:
|
||||
==========
|
||||
@@ -126,7 +167,7 @@ class SmarActRecord:
|
||||
-3 : move-with-wait finished, hard limit violation seen
|
||||
0 : move-with-wait finish OK.
|
||||
0 : move-without-wait executed, not cpmfirmed
|
||||
1 : move-without-wait executed, move confirmed
|
||||
1 : move-without-wait executed, move confirmed
|
||||
3 : move-without-wait finished, hard limit violation seen
|
||||
4 : move-without-wait finished, soft limit violation seen
|
||||
|
||||
@@ -157,7 +198,7 @@ class SmarActRecord:
|
||||
return TIMEOUT
|
||||
|
||||
if 1 == stat:
|
||||
s0 = self._status.get("VAL")
|
||||
s0 = self._statusstg.get("VAL")
|
||||
s1 = s0
|
||||
t0 = time.time()
|
||||
t1 = t0 + min(10.0, timeout) # should be moving by now
|
||||
@@ -166,15 +207,15 @@ class SmarActRecord:
|
||||
if wait or confirm_move:
|
||||
while time.time() <= thold and s1 == 3:
|
||||
ca.poll(evt=1.0e-2)
|
||||
s1 = self._status.get("VAL")
|
||||
s1 = self._statusstg.get("VAL")
|
||||
while time.time() <= t1 and s1 == 0:
|
||||
ca.poll(evt=1.0e-2)
|
||||
s1 = self._status.get("VAL")
|
||||
s1 = self._statusstg.get("VAL")
|
||||
if s1 == 4:
|
||||
if wait:
|
||||
while time.time() <= tout and s1 == 4:
|
||||
ca.poll(evt=1.0e-2)
|
||||
s1 = self._status.get("VAL")
|
||||
s1 = self._statusstg.get("VAL")
|
||||
if s1 == 3 or s1 == 4:
|
||||
if time.time() > tout:
|
||||
return TIMEOUT
|
||||
@@ -207,33 +248,32 @@ class SmarActRecord:
|
||||
return self._set_pos.put("VAL", value)
|
||||
|
||||
def get_precision(self):
|
||||
""" Adjustable convention"""
|
||||
"""Adjustable convention"""
|
||||
pass
|
||||
|
||||
def set_precision(self):
|
||||
""" Adjustable convention"""
|
||||
"""Adjustable convention"""
|
||||
pass
|
||||
|
||||
precision = property(get_precision, set_precision)
|
||||
|
||||
def set_speed(self):
|
||||
""" Adjustable convention"""
|
||||
"""Adjustable convention"""
|
||||
pass
|
||||
|
||||
def get_speed(self):
|
||||
""" Adjustable convention"""
|
||||
"""Adjustable convention"""
|
||||
pass
|
||||
|
||||
def set_speedMax(self):
|
||||
""" Adjustable convention"""
|
||||
"""Adjustable convention"""
|
||||
pass
|
||||
|
||||
def get_moveDone(self):
|
||||
""" Adjustable convention"""
|
||||
pass
|
||||
|
||||
def set_limits(self, values, posType="user", relative_to_present=False):
|
||||
""" Adjustable convention"""
|
||||
"""Adjustable convention"""
|
||||
if relative_to_present:
|
||||
v = self.get_current_value()
|
||||
values = [v - values[0], v - values[1]]
|
||||
@@ -241,11 +281,11 @@ class SmarActRecord:
|
||||
self._hlm.put("VAL", values[1])
|
||||
|
||||
def get_limits(self, posType="user"):
|
||||
""" Adjustable convention"""
|
||||
"""Adjustable convention"""
|
||||
return self._llm.get("VAL"), self._hlm.get("VAL")
|
||||
|
||||
def gui(self, guiType="xdm"):
|
||||
""" Adjustable convention"""
|
||||
"""Adjustable convention"""
|
||||
cmd = ["caqtdm", "-macro"]
|
||||
|
||||
for i in range(len(self.Id) - 1):
|
||||
@@ -257,23 +297,19 @@ class SmarActRecord:
|
||||
|
||||
cmd.append('"P=%s,M=%s"' % (P, M))
|
||||
# #cmd.append('/sf/common/config/qt/motorx_more.ui')
|
||||
cmd.append("ESB_MX_SMARACT_mot_exp.ui")
|
||||
cmd.append("/sf/common/config/qt/ESB_MX_SmarAct_mot_exp.ui")
|
||||
# #os.system(' '.join(cmd))
|
||||
return subprocess.Popen(" ".join(cmd), shell=True)
|
||||
|
||||
def mv(self, value):
|
||||
self._currentChange = self.changeTo(value)
|
||||
self._currentChange = self.set_target_value(value)
|
||||
|
||||
def wm(self, *args, **kwargs):
|
||||
return self.get_current_value(*args, **kwargs)
|
||||
|
||||
def mvr(self, value, *args, **kwargs):
|
||||
|
||||
if self.get_moveDone == 1:
|
||||
startvalue = self.get_current_value(readback=True, *args, **kwargs)
|
||||
else:
|
||||
startvalue = self.get_current_value(readback=False, *args, **kwargs)
|
||||
self._currentChange = self.changeTo(value + startvalue, *args, **kwargs)
|
||||
startvalue = self.get_current_value(readback=True, *args, **kwargs)
|
||||
self._currentChange = self.set_target_value(value + startvalue, *args, **kwargs)
|
||||
|
||||
def wait(self):
|
||||
self._currentChange.wait()
|
||||
@@ -287,7 +323,7 @@ class SmarActRecord:
|
||||
return self.__str__()
|
||||
|
||||
def __call__(self, value):
|
||||
self._currentChange = self.changeTo(value)
|
||||
self._currentChange = self.set_target_value(value)
|
||||
|
||||
|
||||
class SmarActDevice(SmarActRecord):
|
||||
@@ -316,7 +352,7 @@ class SmarActStage:
|
||||
return str({key: self.__dict__[key].wm() for key in self._keys})
|
||||
|
||||
|
||||
class Changer:
|
||||
class Changer_old:
|
||||
def __init__(self, target=None, parent=None, mover=None, hold=True, stopper=None):
|
||||
self.target = target
|
||||
self._mover = mover
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
|
||||
|
||||
from eco.elements.assembly import Assembly
|
||||
from eco.epics.adjustable import AdjustablePv, AdjustablePvEnum
|
||||
from eco.epics.detector import DetectorPvData
|
||||
|
||||
|
||||
class SpectrometerAndor(Assembly):
|
||||
def __init__(self, pvname, name=None):
|
||||
super().__init__(name=name)
|
||||
self.pvname=pvname
|
||||
self._append(DetectorPvData, pvname + ":SERIAL_NUM", unit='', name="sno_spectrometer")
|
||||
self._append(
|
||||
AdjustablePv, pvname + ":SEND_WL", pvreadbackname= pvname + ":WAVELENGTH", name="wavelength", is_setting=True, unit='nm'
|
||||
)
|
||||
self._append(
|
||||
AdjustablePv, pvname + ":SEND_SW", pvreadbackname= pvname + ":SLIT_WIDTH", name="slit_width", is_setting=True
|
||||
)
|
||||
|
||||
self._append(DetectorPvData, pvname + ":MIN_WL", unit='nm', name="min_wavelength")
|
||||
self._append(DetectorPvData, pvname + ":MAX_WL", unit='nm', name="max_wavelength")
|
||||
self._append(DetectorPvData, pvname + ":FOCAL_POS", unit='mm', name="focal_position")
|
||||
self._append(DetectorPvData, pvname + ":TURRET", unit='', name="turret_number")
|
||||
self._append(DetectorPvData, pvname + ":NUM_GRATINGS", unit='', name="noof_installed_gratings")
|
||||
self._append(DetectorPvData, pvname + ":GRATING_NUM", unit='', name="active_grating")
|
||||
self._append(DetectorPvData, pvname + ":LPMM", unit='lns/mm', name="line_density")
|
||||
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
|
||||
|
||||
from functools import partial
|
||||
from eco import Assembly
|
||||
from eco.elements.detector import DetectorVirtual
|
||||
from eco.epics.detector import DetectorPvData
|
||||
|
||||
|
||||
class ChillerThermotek(Assembly):
|
||||
def __init__(self,pvbase="SARES20-CHIL",name=None):
|
||||
self.pvbase = pvbase
|
||||
super().__init__(name=name)
|
||||
self._append(DetectorPvData,pvbase+":H2O_FLUSS", name='flow_rate', is_display=True)
|
||||
self._append(DetectorPvData,pvbase+":T_VORLAUF", name='temp_feed', is_display=True)
|
||||
self._append(DetectorPvData,pvbase+":bitIO",name='bitIO',is_display=False)
|
||||
self._append(ThermotekChillerFlags,self.bitIO,name='flags')
|
||||
|
||||
|
||||
|
||||
|
||||
flag_names_thermotek_chiller = [
|
||||
"operation",
|
||||
"error",
|
||||
"pressure_state",
|
||||
"temperature_state",
|
||||
"flow_state",
|
||||
"state_flow2",
|
||||
"liquid_level_state",
|
||||
"condictivity_state",
|
||||
"ambient_temperature_state",
|
||||
"interruption_clearance",
|
||||
"alarm_beep",
|
||||
"control_valve",
|
||||
"compressor",
|
||||
"heater",
|
||||
"pump",
|
||||
"LF_valve",
|
||||
"remote_start",
|
||||
"error_max_pressure",
|
||||
"error_min_pressure",
|
||||
"error_fill_level",
|
||||
"warning_fill_level",
|
||||
"flow_ping0",
|
||||
"flow_ping1",
|
||||
# "flow_switch",
|
||||
]
|
||||
|
||||
class ThermotekChillerFlags(Assembly):
|
||||
def __init__(self, flags, name="flags"):
|
||||
super().__init__(name=name)
|
||||
self._flags = flags
|
||||
for flag_name in flag_names_thermotek_chiller:
|
||||
self._append(
|
||||
DetectorVirtual,
|
||||
[self._flags],
|
||||
partial(self._get_flag_name_value, flag_name=flag_name),
|
||||
name=flag_name,
|
||||
is_status=True,
|
||||
is_display=True,
|
||||
)
|
||||
|
||||
def _get_flag_name_value(self, value, flag_name=None):
|
||||
index = flag_names_thermotek_chiller.index(flag_name)
|
||||
return int("{0:015b}".format(int(value))[-1 * (index + 1)]) == 1
|
||||
@@ -58,7 +58,7 @@ class Storage(object):
|
||||
|
||||
|
||||
class Pockels_trigger(PV):
|
||||
""" this class is needed to store the offset in files and read in s """
|
||||
"""this class is needed to store the offset in files and read in s"""
|
||||
|
||||
def __init__(self, pv_basename):
|
||||
pvname = pv_basename + "-RB"
|
||||
@@ -75,7 +75,7 @@ class Pockels_trigger(PV):
|
||||
return super().get() * 1e-6
|
||||
|
||||
def get(self):
|
||||
""" convert time to sec """
|
||||
"""convert time to sec"""
|
||||
return self.get_dial() - self.offset
|
||||
|
||||
def store(self, value=None):
|
||||
@@ -98,7 +98,7 @@ class Pockels_trigger(PV):
|
||||
|
||||
|
||||
class Phase_shifter(PV):
|
||||
""" this class is needed to store the offset in files and read in ps """
|
||||
"""this class is needed to store the offset in files and read in ps"""
|
||||
|
||||
def __init__(self, pv_basename="SLAAR01-TSPL-EPL"):
|
||||
pvname = pv_basename + ":CURR_DELTA_T"
|
||||
@@ -116,7 +116,7 @@ class Phase_shifter(PV):
|
||||
return super().get() * 1e-12
|
||||
|
||||
def get(self):
|
||||
""" convert time to sec """
|
||||
"""convert time to sec"""
|
||||
return self.get_dial() - self.offset
|
||||
|
||||
def store(self, value=None):
|
||||
@@ -155,18 +155,18 @@ class PhaseShifterAramis:
|
||||
self._elog = elog
|
||||
self.name = name
|
||||
|
||||
def changeTo(self, value, hold=False, check=True):
|
||||
""" Adjustable convention"""
|
||||
def set_target_value(self, value, hold=False, check=True):
|
||||
"""Adjustable convention"""
|
||||
|
||||
mover = lambda value: self._pshifter.move(value)
|
||||
return Changer(target=value, parent=self, mover=mover, hold=hold, stopper=None)
|
||||
|
||||
def stop(self):
|
||||
""" Adjustable convention"""
|
||||
"""Adjustable convention"""
|
||||
pass
|
||||
|
||||
def get_current_value(self, posType="user", readback=True):
|
||||
""" Adjustable convention"""
|
||||
"""Adjustable convention"""
|
||||
_keywordChecker([("posType", posType, _posTypes)])
|
||||
if posType == "user":
|
||||
return self._pshifter.get()
|
||||
@@ -174,7 +174,7 @@ class PhaseShifterAramis:
|
||||
return self._pshifter.get_dial()
|
||||
|
||||
def set_current_value(self, value, posType="user"):
|
||||
""" Adjustable convention"""
|
||||
"""Adjustable convention"""
|
||||
_keywordChecker([("posType", posType, _posTypes)])
|
||||
if posType == "user":
|
||||
return self._motor.set(value)
|
||||
|
||||
@@ -33,7 +33,7 @@ class User_to_motor:
|
||||
return motor_pos
|
||||
|
||||
def get_current_value(self):
|
||||
""" Adjustable convention"""
|
||||
"""Adjustable convention"""
|
||||
motor_pos = self._stage.get_current_value()
|
||||
motor_pos -= self.offset
|
||||
user = motor_pos * self.conv
|
||||
@@ -44,17 +44,17 @@ class User_to_motor:
|
||||
self._stage.set_current_value(motor_pos)
|
||||
return (value, motor_pos)
|
||||
|
||||
def changeTo(self, value, hold=False, check=True):
|
||||
def set_target_value(self, value, hold=False, check=True):
|
||||
value = self.user_to_motor(value) + self.offset
|
||||
user = (value - self.offset) * self.conv
|
||||
return self._stage.changeTo(value, hold, check)
|
||||
return self._stage.set_target_value(value, hold, check)
|
||||
|
||||
def gui(self, guiType="xdm"):
|
||||
return self._stage.gui()
|
||||
|
||||
# spec-inspired convenience methods
|
||||
def mv(self, value):
|
||||
self._stage._currentChange = self.changeTo(value)
|
||||
self._stage._currentChange = self.set_target_value(value)
|
||||
|
||||
def wm(self, *args, **kwargs):
|
||||
return self.get_current_value(*args, **kwargs)
|
||||
@@ -67,7 +67,7 @@ class User_to_motor:
|
||||
self._stage._currentChange.wait()
|
||||
|
||||
def stop(self):
|
||||
""" Adjustable convention"""
|
||||
"""Adjustable convention"""
|
||||
try:
|
||||
self._stage._currentChange.stop()
|
||||
except:
|
||||
@@ -82,4 +82,4 @@ class User_to_motor:
|
||||
return self.__str__()
|
||||
|
||||
def __call__(self, value):
|
||||
self._currentChange = self.changeTo(value)
|
||||
self._currentChange = self.set_target_value(value)
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
from threading import Thread
|
||||
from ..utilities import PropagatingThread
|
||||
|
||||
|
||||
class Changer:
|
||||
@@ -6,12 +7,17 @@ class Changer:
|
||||
self.target = target
|
||||
self._changer = changer
|
||||
self._stopper = stopper
|
||||
self._thread = Thread(target=self._changer, args=(target,))
|
||||
self._thread = PropagatingThread(target=self._changer, args=(target,))
|
||||
# self._thread = Thread(target=self._changer, args=(target,))
|
||||
if not hold:
|
||||
self._thread.start()
|
||||
|
||||
def wait(self):
|
||||
self._thread.join()
|
||||
def wait(self, timeout=None):
|
||||
self._thread.join(timeout=timeout)
|
||||
if self._thread.is_alive():
|
||||
raise TimeoutError(
|
||||
f"Changer did not finish within timeout period {timeout}."
|
||||
)
|
||||
|
||||
def start(self):
|
||||
self._thread.start()
|
||||
@@ -20,10 +26,22 @@ class Changer:
|
||||
if self._thread.ident is None:
|
||||
return "waiting"
|
||||
else:
|
||||
if self._thread.isAlive:
|
||||
if self._thread.is_alive():
|
||||
return "changing"
|
||||
else:
|
||||
return "done"
|
||||
|
||||
def is_alive(self):
|
||||
if self._thread.ident is None:
|
||||
return True
|
||||
else:
|
||||
if self._thread.is_alive():
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
def isAlive(self):
|
||||
return self.is_alive()
|
||||
|
||||
def stop(self):
|
||||
self._stopper()
|
||||
|
||||
@@ -0,0 +1,195 @@
|
||||
from eco.elements.assembly import Assembly
|
||||
from eco.epics.detector import DetectorPvData
|
||||
from eco.epics.adjustable import (
|
||||
AdjustablePvString,
|
||||
AdjustablePv,
|
||||
spec_convenience,
|
||||
tweak_option,
|
||||
)
|
||||
|
||||
|
||||
class AnalogInput(Assembly):
|
||||
def __init__(self, pvname, name=None):
|
||||
"""Analog input, which is defined by a PV name. There are linear calibration
|
||||
options (hidden adjustment and visible linear_calibration values):
|
||||
value = raw*linear_calibration_slope + linear_calibration_offset"""
|
||||
super().__init__(name=name)
|
||||
self.pvname = pvname
|
||||
self._append(
|
||||
DetectorPvData, self.pvname, name="value", is_setting=False, is_display=True
|
||||
)
|
||||
self._append(
|
||||
AdjustablePvString,
|
||||
self.pvname + ".DESC",
|
||||
name="description",
|
||||
is_setting=True,
|
||||
is_display=True,
|
||||
)
|
||||
self._append(
|
||||
AdjustablePvString,
|
||||
self.pvname + ".EGU",
|
||||
name="unit",
|
||||
is_setting=False,
|
||||
is_display=False,
|
||||
)
|
||||
self.value.unit = self.unit
|
||||
self._append(
|
||||
DetectorPvData,
|
||||
self.pvname + ".RVAL",
|
||||
name="raw",
|
||||
is_setting=False,
|
||||
is_display=False,
|
||||
)
|
||||
self._append(
|
||||
AdjustablePv,
|
||||
self.pvname + ".AOFF",
|
||||
name="_adj_offset",
|
||||
is_setting=True,
|
||||
is_display=False,
|
||||
)
|
||||
self._append(
|
||||
AdjustablePv,
|
||||
self.pvname + ".ASLO",
|
||||
name="_adj_slope",
|
||||
is_setting=True,
|
||||
is_display=False,
|
||||
)
|
||||
self._append(
|
||||
AdjustablePv,
|
||||
self.pvname + ".EOFF",
|
||||
name="linear_calibration_offset",
|
||||
is_setting=True,
|
||||
is_display=False,
|
||||
)
|
||||
self._append(
|
||||
AdjustablePv,
|
||||
self.pvname + ".ESLO",
|
||||
name="linear_calibration_slope",
|
||||
is_setting=True,
|
||||
is_display=False,
|
||||
)
|
||||
|
||||
def get_current_value(self):
|
||||
return self.value.get_current_value()
|
||||
|
||||
def reset_offset_current_value_to(self,value=0):
|
||||
self.linear_calibration_offset.set_target_value(
|
||||
(-1)
|
||||
* self.raw.get_current_value()
|
||||
* self.linear_calibration_slope.get_current_value()
|
||||
* self._adj_slope.get_current_value()
|
||||
+ value
|
||||
).wait()
|
||||
|
||||
def reset_slope_current_value_to(self,value=1):
|
||||
oslo = self.linear_calibration_slope.get_current_value()
|
||||
ooff = self.linear_calibration_offset.get_current_value()
|
||||
ooff_raw = ooff / oslo / self._adj_slope.get_current_value()
|
||||
# print(ooff_raw)
|
||||
|
||||
|
||||
nslo = value / (self.raw.get_current_value()+ooff_raw)
|
||||
self.linear_calibration_slope.set_target_value(nslo).wait()
|
||||
self.linear_calibration_offset.set_target_value(
|
||||
ooff_raw
|
||||
* nslo
|
||||
* self._adj_slope.get_current_value()
|
||||
).wait()
|
||||
|
||||
|
||||
class WagoAnalogInputs(Assembly):
|
||||
def __init__(self, pvbase, name=None):
|
||||
super().__init__(name=name)
|
||||
self.pvbase = pvbase
|
||||
for n in range(1, 9):
|
||||
self._append(AnalogInput, pvbase + f":ADC{n:02d}", name=f"ch{n:d}")
|
||||
|
||||
|
||||
@spec_convenience
|
||||
@tweak_option
|
||||
class AnalogOutput(Assembly):
|
||||
def __init__(self, pvname, name=None):
|
||||
super().__init__(name=name)
|
||||
self.pvname = pvname
|
||||
self._append(
|
||||
AdjustablePv,
|
||||
self.pvname,
|
||||
name="value",
|
||||
is_setting=True,
|
||||
is_display=True,
|
||||
)
|
||||
self._append(
|
||||
AdjustablePvString,
|
||||
self.pvname + ".DESC",
|
||||
name="description",
|
||||
is_setting=True,
|
||||
is_display=True,
|
||||
)
|
||||
self._append(
|
||||
AdjustablePvString,
|
||||
self.pvname + ".EGU",
|
||||
name="unit",
|
||||
is_setting=False,
|
||||
is_display=False,
|
||||
)
|
||||
self._append(
|
||||
DetectorPvData,
|
||||
self.pvname + ".RVAL",
|
||||
name="raw",
|
||||
is_setting=False,
|
||||
is_display=False,
|
||||
)
|
||||
self._append(
|
||||
AdjustablePv,
|
||||
self.pvname + ".AOFF",
|
||||
name="_adj_offset",
|
||||
is_setting=True,
|
||||
is_display=False,
|
||||
)
|
||||
self._append(
|
||||
AdjustablePv,
|
||||
self.pvname + ".ASLO",
|
||||
name="_adj_slope",
|
||||
is_setting=True,
|
||||
is_display=False,
|
||||
)
|
||||
self._append(
|
||||
AdjustablePv,
|
||||
self.pvname + ".EOFF",
|
||||
name="linear_calibration_offset",
|
||||
is_setting=True,
|
||||
is_display=False,
|
||||
)
|
||||
self._append(
|
||||
AdjustablePv,
|
||||
self.pvname + ".ESLO",
|
||||
name="linear_calibration_slope",
|
||||
is_setting=True,
|
||||
is_display=False,
|
||||
)
|
||||
|
||||
def get_current_value(self):
|
||||
return self.value.get_current_value()
|
||||
|
||||
def set_target_value(self, *args, **kwargs):
|
||||
return self.value.set_target_value(*args, **kwargs)
|
||||
|
||||
def __call__(self, *args):
|
||||
if args:
|
||||
self.value.set_target_Value(*args).wait()
|
||||
else:
|
||||
return self.value.get_current_value()
|
||||
|
||||
|
||||
class WagoAnalogOutputs(Assembly):
|
||||
def __init__(self, pvbase, name=None):
|
||||
super().__init__(name=name)
|
||||
self.pvbase = pvbase
|
||||
for n in range(1, 9):
|
||||
self._append(
|
||||
AnalogOutput,
|
||||
pvbase + f":DAC{n:02d}",
|
||||
name=f"ch{n:d}",
|
||||
is_setting=True,
|
||||
is_display=True,
|
||||
)
|
||||
@@ -5,6 +5,7 @@
|
||||
"""
|
||||
basic device object defined
|
||||
"""
|
||||
|
||||
from epics.ca import poll
|
||||
from epics.pv import get_pv
|
||||
import time
|
||||
@@ -134,12 +135,14 @@ class Device(object):
|
||||
|
||||
if attrs is not None:
|
||||
for attr in attrs:
|
||||
self.PV(attr, connect=False, connection_timeout=timeout)
|
||||
# self.PV(attr, connect=False, connection_timeout=timeout)
|
||||
self.PV(attr, connect=False, timeout=timeout)
|
||||
|
||||
if aliases:
|
||||
for attr in aliases.values():
|
||||
if attrs is None or attr not in attrs:
|
||||
self.PV(attr, connect=False, connection_timeout=timeout)
|
||||
# self.PV(attr, connect=False, connection_timeout=timeout)
|
||||
self.PV(attr, connect=False, timeout=timeout)
|
||||
|
||||
if with_poll:
|
||||
poll()
|
||||
@@ -215,7 +218,7 @@ class Device(object):
|
||||
"""write save state to external file.
|
||||
If state is not provided, the current state is used
|
||||
|
||||
Note that this only writes data for PVs with write-access, and count=1 (except CHAR """
|
||||
Note that this only writes data for PVs with write-access, and count=1 (except CHAR"""
|
||||
if state is None:
|
||||
state = self.save_state()
|
||||
out = [
|
||||
|
||||
+112
-112
@@ -50,11 +50,11 @@ import sys
|
||||
import time
|
||||
|
||||
from epics import ca
|
||||
from . import device
|
||||
from epics import device
|
||||
|
||||
|
||||
class MotorLimitException(Exception):
|
||||
""" raised to indicate a motor limit has been reached """
|
||||
"""raised to indicate a motor limit has been reached"""
|
||||
|
||||
def __init__(self, msg, *args):
|
||||
Exception.__init__(self, *args)
|
||||
@@ -65,7 +65,7 @@ class MotorLimitException(Exception):
|
||||
|
||||
|
||||
class MotorException(Exception):
|
||||
""" raised to indicate a problem with a motor"""
|
||||
"""raised to indicate a problem with a motor"""
|
||||
|
||||
def __init__(self, msg, *args):
|
||||
Exception.__init__(self, *args)
|
||||
@@ -77,59 +77,59 @@ class MotorException(Exception):
|
||||
|
||||
class Motor(device.Device):
|
||||
"""Epics Motor Class for pyepics3
|
||||
|
||||
This module provides a class library for the EPICS motor record.
|
||||
|
||||
It uses the epics.Device and epics.PV classese
|
||||
This module provides a class library for the EPICS motor record.
|
||||
|
||||
Virtual attributes:
|
||||
These attributes do not appear in the dictionary for this class, but
|
||||
are implemented with the __getattr__ and __setattr__ methods. They
|
||||
simply get or putthe appropriate motor record fields. All attributes
|
||||
can be both read and written unless otherwise noted.
|
||||
It uses the epics.Device and epics.PV classese
|
||||
|
||||
Attribute Description Field
|
||||
--------- ----------------------- -----
|
||||
drive Motor Drive Value .VAL
|
||||
readback Motor Readback Value .RBV (read-only)
|
||||
slew_speed Slew speed or velocity .VELO
|
||||
base_speed Base or starting speed .VBAS
|
||||
acceleration Acceleration time (sec) .ACCL
|
||||
description Description of motor .DESC
|
||||
resolution Resolution (units/step) .MRES
|
||||
high_limit High soft limit (user) .HLM
|
||||
low_limit Low soft limit (user) .LLM
|
||||
dial_high_limit High soft limit (dial) .DHLM
|
||||
dial_low_limit Low soft limit (dial) .DLLM
|
||||
backlash Backlash distance .BDST
|
||||
offset Offset from dial to user .OFF
|
||||
done_moving 1=Done, 0=Moving, read-only .DMOV
|
||||
|
||||
Exceptions:
|
||||
The check_limits() method raises an 'MotorLimitException' if a soft limit
|
||||
or hard limit is detected. The move() method calls
|
||||
check_limits() unless they are called with the
|
||||
ignore_limits=True keyword set.
|
||||
Virtual attributes:
|
||||
These attributes do not appear in the dictionary for this class, but
|
||||
are implemented with the __getattr__ and __setattr__ methods. They
|
||||
simply get or putthe appropriate motor record fields. All attributes
|
||||
can be both read and written unless otherwise noted.
|
||||
|
||||
Example use:
|
||||
from epics import Motor
|
||||
m = Motor('13BMD:m38')
|
||||
m.move(10) # Move to position 10 in user coordinates
|
||||
m.move(100, dial=True) # Move to position 100 in dial coordinates
|
||||
m.move(1, step=True, relative=True) # Move 1 step relative to current position
|
||||
Attribute Description Field
|
||||
--------- ----------------------- -----
|
||||
drive Motor Drive Value .VAL
|
||||
readback Motor Readback Value .RBV (read-only)
|
||||
slew_speed Slew speed or velocity .VELO
|
||||
base_speed Base or starting speed .VBAS
|
||||
acceleration Acceleration time (sec) .ACCL
|
||||
description Description of motor .DESC
|
||||
resolution Resolution (units/step) .MRES
|
||||
high_limit High soft limit (user) .HLM
|
||||
low_limit Low soft limit (user) .LLM
|
||||
dial_high_limit High soft limit (dial) .DHLM
|
||||
dial_low_limit Low soft limit (dial) .DLLM
|
||||
backlash Backlash distance .BDST
|
||||
offset Offset from dial to user .OFF
|
||||
done_moving 1=Done, 0=Moving, read-only .DMOV
|
||||
|
||||
m.stop() # Stop moving immediately
|
||||
high = m.high_limit # Get the high soft limit in user coordinates
|
||||
m.dial_high_limit = 100 # Set the high limit to 100 in dial coodinates
|
||||
speed = m.slew_speed # Get the slew speed
|
||||
m.acceleration = 0.1 # Set the acceleration to 0.1 seconds
|
||||
p=m.get_position() # Get the desired motor position in user coordinates
|
||||
p=m.get_position(dial=1) # Get the desired motor position in dial coordinates
|
||||
p=m.get_position(readback=1) # Get the actual position in user coordinates
|
||||
p=m.get_position(readback=1, step=1) Get the actual motor position in steps
|
||||
p=m.set_position(100) # Set the current position to 100 in user coordinates
|
||||
# Puts motor in Set mode, writes value, puts back in Use mode.
|
||||
p=m.set_position(10000, step=1) # Set the current position to 10000 steps
|
||||
Exceptions:
|
||||
The check_limits() method raises an 'MotorLimitException' if a soft limit
|
||||
or hard limit is detected. The move() method calls
|
||||
check_limits() unless they are called with the
|
||||
ignore_limits=True keyword set.
|
||||
|
||||
Example use:
|
||||
from epics import Motor
|
||||
m = Motor('13BMD:m38')
|
||||
m.move(10) # Move to position 10 in user coordinates
|
||||
m.move(100, dial=True) # Move to position 100 in dial coordinates
|
||||
m.move(1, step=True, relative=True) # Move 1 step relative to current position
|
||||
|
||||
m.stop() # Stop moving immediately
|
||||
high = m.high_limit # Get the high soft limit in user coordinates
|
||||
m.dial_high_limit = 100 # Set the high limit to 100 in dial coodinates
|
||||
speed = m.slew_speed # Get the slew speed
|
||||
m.acceleration = 0.1 # Set the acceleration to 0.1 seconds
|
||||
p=m.get_position() # Get the desired motor position in user coordinates
|
||||
p=m.get_position(dial=1) # Get the desired motor position in dial coordinates
|
||||
p=m.get_position(readback=1) # Get the actual position in user coordinates
|
||||
p=m.get_position(readback=1, step=1) Get the actual motor position in steps
|
||||
p=m.set_position(100) # Set the current position to 100 in user coordinates
|
||||
# Puts motor in Set mode, writes value, puts back in Use mode.
|
||||
p=m.set_position(10000, step=1) # Set the current position to 10000 steps
|
||||
|
||||
"""
|
||||
|
||||
@@ -272,7 +272,7 @@ class Motor(device.Device):
|
||||
return self.__repr__()
|
||||
|
||||
def __getattr__(self, attr):
|
||||
" internal method "
|
||||
"internal method"
|
||||
if attr in self._alias:
|
||||
attr = self._alias[attr]
|
||||
if attr in self._pvs:
|
||||
@@ -315,7 +315,7 @@ class Motor(device.Device):
|
||||
raise MotorException("EpicsMotor has no attribute %s" % attr)
|
||||
|
||||
def check_limits(self):
|
||||
""" check motor limits:
|
||||
"""check motor limits:
|
||||
returns None if no limits are violated
|
||||
raises expection if a limit is violated"""
|
||||
for field, msg in (
|
||||
@@ -328,7 +328,7 @@ class Motor(device.Device):
|
||||
return
|
||||
|
||||
def within_limits(self, val, dial=False):
|
||||
""" returns whether a value for a motor is within drive limits
|
||||
"""returns whether a value for a motor is within drive limits
|
||||
with dial=True dial limits are used (default is user limits)"""
|
||||
ll_name, hl_name = "LLM", "HLM"
|
||||
if dial:
|
||||
@@ -347,7 +347,7 @@ class Motor(device.Device):
|
||||
ignore_limits=False,
|
||||
confirm_move=False,
|
||||
):
|
||||
""" moves motor drive to position
|
||||
"""moves motor drive to position
|
||||
|
||||
arguments:
|
||||
==========
|
||||
@@ -372,7 +372,7 @@ class Motor(device.Device):
|
||||
-3 : move-with-wait finished, hard limit violation seen
|
||||
0 : move-with-wait finish OK.
|
||||
0 : move-without-wait executed, not cpmfirmed
|
||||
1 : move-without-wait executed, move confirmed
|
||||
1 : move-without-wait executed, move confirmed
|
||||
3 : move-without-wait finished, hard limit violation seen
|
||||
4 : move-without-wait finished, soft limit violation seen
|
||||
|
||||
@@ -442,33 +442,33 @@ class Motor(device.Device):
|
||||
|
||||
def get_position(self, dial=False, readback=False, step=False, raw=False):
|
||||
"""
|
||||
Returns the target or readback motor position in user, dial or step
|
||||
coordinates.
|
||||
|
||||
Keywords:
|
||||
readback:
|
||||
Set readback=True to return the readback position in the
|
||||
desired coordinate system. The default is to return the
|
||||
drive position of the motor.
|
||||
|
||||
dial:
|
||||
Set dial=True to return the position in dial coordinates.
|
||||
The default is user coordinates.
|
||||
|
||||
raw (or step):
|
||||
Set raw=True to return the raw position in steps.
|
||||
The default is user coordinates.
|
||||
Returns the target or readback motor position in user, dial or step
|
||||
coordinates.
|
||||
|
||||
Notes:
|
||||
The "raw" or "step" and "dial" keywords are mutually exclusive.
|
||||
The "readback" keyword can be used in user, dial or step
|
||||
coordinates.
|
||||
|
||||
Examples:
|
||||
m=epicsMotor('13BMD:m38')
|
||||
m.move(10) # Move to position 10 in user coordinates
|
||||
p=m.get_position(dial=True) # Read the target position in dial coordinates
|
||||
p=m.get_position(readback=True, step=True) # Read the actual position in steps
|
||||
Keywords:
|
||||
readback:
|
||||
Set readback=True to return the readback position in the
|
||||
desired coordinate system. The default is to return the
|
||||
drive position of the motor.
|
||||
|
||||
dial:
|
||||
Set dial=True to return the position in dial coordinates.
|
||||
The default is user coordinates.
|
||||
|
||||
raw (or step):
|
||||
Set raw=True to return the raw position in steps.
|
||||
The default is user coordinates.
|
||||
|
||||
Notes:
|
||||
The "raw" or "step" and "dial" keywords are mutually exclusive.
|
||||
The "readback" keyword can be used in user, dial or step
|
||||
coordinates.
|
||||
|
||||
Examples:
|
||||
m=epicsMotor('13BMD:m38')
|
||||
m.move(10) # Move to position 10 in user coordinates
|
||||
p=m.get_position(dial=True) # Read the target position in dial coordinates
|
||||
p=m.get_position(readback=True, step=True) # Read the actual position in steps
|
||||
"""
|
||||
pos, rbv = ("VAL", "RBV")
|
||||
if dial:
|
||||
@@ -480,8 +480,8 @@ class Motor(device.Device):
|
||||
return self.get(pos)
|
||||
|
||||
def tweak(self, direction="foreward", wait=False, timeout=300.0):
|
||||
""" move the motor by the tweak_val
|
||||
|
||||
"""move the motor by the tweak_val
|
||||
|
||||
takes optional args:
|
||||
direction direction of motion (forward/reverse) [forward]
|
||||
must start with 'rev' or 'back' for a reverse tweak.
|
||||
@@ -507,30 +507,30 @@ class Motor(device.Device):
|
||||
|
||||
def set_position(self, position, dial=False, step=False, raw=False):
|
||||
"""
|
||||
Sets the motor position in user, dial or step coordinates.
|
||||
|
||||
Inputs:
|
||||
position:
|
||||
The new motor position
|
||||
|
||||
Keywords:
|
||||
dial:
|
||||
Set dial=True to set the position in dial coordinates.
|
||||
The default is user coordinates.
|
||||
|
||||
raw:
|
||||
Set raw=True to set the position in raw steps.
|
||||
The default is user coordinates.
|
||||
|
||||
Notes:
|
||||
The 'raw' and 'dial' keywords are mutually exclusive.
|
||||
|
||||
Examples:
|
||||
m=epicsMotor('13BMD:m38')
|
||||
m.set_position(10, dial=True) # Set the motor position to 10 in
|
||||
# dial coordinates
|
||||
m.set_position(1000, raw=True) # Set the motor position to 1000 steps
|
||||
"""
|
||||
Sets the motor position in user, dial or step coordinates.
|
||||
|
||||
Inputs:
|
||||
position:
|
||||
The new motor position
|
||||
|
||||
Keywords:
|
||||
dial:
|
||||
Set dial=True to set the position in dial coordinates.
|
||||
The default is user coordinates.
|
||||
|
||||
raw:
|
||||
Set raw=True to set the position in raw steps.
|
||||
The default is user coordinates.
|
||||
|
||||
Notes:
|
||||
The 'raw' and 'dial' keywords are mutually exclusive.
|
||||
|
||||
Examples:
|
||||
m=epicsMotor('13BMD:m38')
|
||||
m.set_position(10, dial=True) # Set the motor position to 10 in
|
||||
# dial coordinates
|
||||
m.set_position(1000, raw=True) # Set the motor position to 1000 steps
|
||||
"""
|
||||
|
||||
# Put the motor in "SET" mode
|
||||
self.put("SET", 1)
|
||||
@@ -572,7 +572,7 @@ class Motor(device.Device):
|
||||
self._callbacks[attr] = index
|
||||
|
||||
def refresh(self):
|
||||
""" refresh all motor parameters currently in use:
|
||||
"""refresh all motor parameters currently in use:
|
||||
make sure all used attributes are up-to-date."""
|
||||
ca.poll()
|
||||
|
||||
@@ -585,7 +585,7 @@ class Motor(device.Device):
|
||||
self.STOP = 1
|
||||
|
||||
def make_step_list(self, minstep=0.0, maxstep=None, decades=10):
|
||||
""" create a reasonable list of motor steps, as for a dropdown menu
|
||||
"""create a reasonable list of motor steps, as for a dropdown menu
|
||||
The list is based on motor range Mand precision"""
|
||||
|
||||
if maxstep is None:
|
||||
@@ -617,7 +617,7 @@ class Motor(device.Device):
|
||||
return out
|
||||
|
||||
def show_info(self):
|
||||
" show basic motor settings "
|
||||
"show basic motor settings"
|
||||
ca.poll()
|
||||
out = []
|
||||
out.append(repr(self))
|
||||
@@ -630,7 +630,7 @@ class Motor(device.Device):
|
||||
ca.write("\n".join(out))
|
||||
|
||||
def show_all(self):
|
||||
""" show all motor attributes"""
|
||||
"""show all motor attributes"""
|
||||
out = []
|
||||
add = out.append
|
||||
add("# Motor %s" % (self._prefix))
|
||||
|
||||
@@ -1,64 +0,0 @@
|
||||
from epics import PV
|
||||
|
||||
|
||||
class EnumWrapper:
|
||||
def __init__(self, pvname, elog=None):
|
||||
self._elog = elog
|
||||
self._pv = PV(pvname)
|
||||
self.names = self._pv.enum_strs
|
||||
# print(self.names)
|
||||
# if self.names:
|
||||
self.setters = Positioner([(nam, lambda: self.set(nam)) for nam in self.names])
|
||||
|
||||
def set(self, target):
|
||||
if type(target) is str:
|
||||
assert target in self.names, (
|
||||
"set value need to be one of \n %s" % self.names
|
||||
)
|
||||
self._pv.put(self.names.index(target))
|
||||
elif type(target) is int:
|
||||
assert target >= 0, "set integer needs to be positive"
|
||||
assert target < len(self.names)
|
||||
self._pv.put(target)
|
||||
|
||||
def get(self):
|
||||
return self._pv.get()
|
||||
|
||||
def get_name(self):
|
||||
return self.names[self.get()]
|
||||
|
||||
def __repr__(self):
|
||||
return self.get_name()
|
||||
|
||||
|
||||
class MonitorAccumulator:
|
||||
def __init__(self, pv, attr=None, keywords=["value", "timestamp"]):
|
||||
self.pv = pv
|
||||
self.attr = attr
|
||||
self.values = []
|
||||
self.keywords = keywords
|
||||
|
||||
def _accumulate(self, **kwargs):
|
||||
self.values.append([kwargs[kw] for kw in self.keywords])
|
||||
|
||||
def accumulate(self):
|
||||
self.pv.add_callback(self._accumulate, self.attr)
|
||||
|
||||
def stop(self):
|
||||
self.pv.remove_callbacks(self.attr)
|
||||
|
||||
def cycle(self):
|
||||
self.stop()
|
||||
d = self.values.copy()
|
||||
self.values = []
|
||||
self.accumulate()
|
||||
return d
|
||||
|
||||
|
||||
class Positioner:
|
||||
def __init__(self, list_of_name_func_tuples):
|
||||
for name, func in list_of_name_func_tuples:
|
||||
tname = name.replace(" ", "_").replace(".", "p")
|
||||
if tname[0].isnumeric():
|
||||
tname = "v" + tname
|
||||
self.__dict__[tname] = func
|
||||
@@ -1,6 +1,15 @@
|
||||
import logging
|
||||
import json
|
||||
from pathlib import Path
|
||||
from .utilities.config import Configuration
|
||||
|
||||
startup_lazy = False
|
||||
|
||||
scopes = [
|
||||
{"name": "Alvra", "facility": "SwissFEL", "module": "alvra"},
|
||||
{"name": "Bernina", "facility": "SwissFEL", "module": "bernina"},
|
||||
{"name": "SwissMX", "facility": "SwissFEL", "module": "swissmx"},
|
||||
]
|
||||
|
||||
|
||||
# settings = Configuration(Path.home() / '.ecorc', name='eco_startup_settings')
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
from .assembly import Assembly
|
||||
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
|
||||
|
||||
|
||||
# single acquisition class
|
||||
class Acquisition:
|
||||
def __init__(
|
||||
self,
|
||||
parent=None,
|
||||
acquire=lambda: None,
|
||||
acquisition_kwargs={},
|
||||
hold=True,
|
||||
stopper=None,
|
||||
get_result=lambda: None,
|
||||
):
|
||||
self.acquisition_kwargs = acquisition_kwargs
|
||||
for key, val in acquisition_kwargs.items():
|
||||
self.__dict__[key] = val
|
||||
self._stopper = stopper
|
||||
self._get_result = get_result
|
||||
if acquire:
|
||||
self.set_acquire_foo(acquire, hold=hold)
|
||||
|
||||
def set_acquire_foo(self, acquire, hold=True):
|
||||
self._acquire = acquire
|
||||
self._thread = PropagatingThread(target=self._acquire)
|
||||
if not hold:
|
||||
self._thread.start()
|
||||
|
||||
def wait(self):
|
||||
self._thread.join()
|
||||
return self._get_result()
|
||||
|
||||
def start(self):
|
||||
self._thread.start()
|
||||
|
||||
def status(self):
|
||||
if self._thread.ident is None:
|
||||
return "waiting"
|
||||
else:
|
||||
if self._thread.is_alive():
|
||||
return "acquiring"
|
||||
else:
|
||||
return "done"
|
||||
|
||||
def stop(self):
|
||||
self._stopper()
|
||||
@@ -0,0 +1,94 @@
|
||||
from eco.elements.detector import DetectorGet
|
||||
from .assembly import Assembly
|
||||
|
||||
from .adjustable import AdjustableGetSet
|
||||
from functools import partial
|
||||
|
||||
|
||||
class AdjustableObject(Assembly):
|
||||
def __init__(self, adjustable_dict, is_setting_children=False, name=None):
|
||||
super().__init__(name=name)
|
||||
self._append(adjustable_dict, name="_base_dict", is_setting=False)
|
||||
self.init_object(is_setting_children=is_setting_children)
|
||||
|
||||
def set_field(self, fieldname, value):
|
||||
d = self._base_dict.get_current_value()
|
||||
if fieldname not in d.keys():
|
||||
raise Exception(f"{fieldname} is not in dictionary")
|
||||
d[fieldname] = value
|
||||
self._base_dict.set_target_value(d)
|
||||
|
||||
def get_field(self, fieldname):
|
||||
d = self._base_dict.get_current_value()
|
||||
if fieldname not in d.keys():
|
||||
raise Exception(f"{fieldname} is not in dictionary")
|
||||
return d[fieldname]
|
||||
|
||||
def update_base_dict(self, updatedict):
|
||||
tmp = self._base_dict.get_current_value()
|
||||
tmp.update(updatedict)
|
||||
self._base_dict.set_target_value(tmp)
|
||||
self.__init__(self._base_dict, name=self.name)
|
||||
|
||||
def init_object(self, is_setting_children=False):
|
||||
# super().__init__(name=self.name)
|
||||
for k, v in self._base_dict.get_current_value().items():
|
||||
tadj = AdjustableGetSet(
|
||||
partial(self.get_field, k), partial(self.set_field, k), name=k
|
||||
)
|
||||
if k in self.__dict__.keys():
|
||||
ln = f"{k}_"
|
||||
else:
|
||||
ln = f"{k}"
|
||||
if type(v) is dict:
|
||||
|
||||
self._append(
|
||||
AdjustableObject(tadj, name=k),
|
||||
call_obj=False,
|
||||
is_setting=is_setting_children,
|
||||
name=ln,
|
||||
is_display="recursive",
|
||||
)
|
||||
else:
|
||||
self._append(
|
||||
tadj,
|
||||
call_obj=False,
|
||||
is_setting=is_setting_children,
|
||||
is_display=True,
|
||||
name=ln,
|
||||
)
|
||||
|
||||
|
||||
class DetectorObject(Assembly):
|
||||
def __init__(self, detector_dict, name=None):
|
||||
super().__init__(name=name)
|
||||
self._base_dict = detector_dict
|
||||
self.init_object()
|
||||
|
||||
def get_field(self, fieldname):
|
||||
d = self._base_dict.get_current_value()
|
||||
if fieldname not in d.keys():
|
||||
raise Exception(f"{fieldname} is not in dictionary")
|
||||
return d[fieldname]
|
||||
|
||||
def init_object(self):
|
||||
# super().__init__(name=self.name)
|
||||
for k, v in self._base_dict.get_current_value().items():
|
||||
tdet = DetectorGet(partial(self.get_field, k), name=k)
|
||||
if k in self.__dict__.keys():
|
||||
ln = f"{k}_"
|
||||
else:
|
||||
ln = f"{k}"
|
||||
if type(v) is dict:
|
||||
|
||||
self._append(
|
||||
DetectorObject(tdet, name=k),
|
||||
call_obj=False,
|
||||
is_setting=False,
|
||||
name=ln,
|
||||
is_display="recursive",
|
||||
)
|
||||
else:
|
||||
self._append(
|
||||
tdet, call_obj=False, is_setting=False, is_display=True, name=ln
|
||||
)
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,638 @@
|
||||
from concurrent.futures import ThreadPoolExecutor
|
||||
import copy
|
||||
from datetime import datetime
|
||||
from inspect import isclass
|
||||
import json
|
||||
from pathlib import Path
|
||||
from tkinter import W
|
||||
import weakref
|
||||
from markdown import markdown
|
||||
|
||||
from numpy import isin
|
||||
import numpy as np
|
||||
|
||||
|
||||
from eco.acquisition.scan_data import run_status_convenience
|
||||
from eco.elements.protocols import Detector, InitialisationWaitable
|
||||
from eco.epics import get_from_archive
|
||||
|
||||
from ..aliases import Alias
|
||||
from tabulate import tabulate
|
||||
import colorama
|
||||
from . import memory
|
||||
from enum import Enum
|
||||
import os
|
||||
import subprocess
|
||||
from rich.progress import track
|
||||
from eco import Adjustable, Detector
|
||||
|
||||
import eco
|
||||
|
||||
|
||||
_initializing_assemblies = []
|
||||
|
||||
|
||||
class StatusCollection:
|
||||
def __init__(self, parent, name="status_collection"):
|
||||
self.parent = weakref.ref(parent)
|
||||
self.selections = {}
|
||||
|
||||
if name is None:
|
||||
raise Exception("A name of collection is required")
|
||||
self.name = name
|
||||
self._list = []
|
||||
|
||||
def get_list(self, selection=None, **kwargs):
|
||||
rec_list_items = kwargs.get("rec_list_items", [])
|
||||
ls = []
|
||||
for witem in self._list:
|
||||
item = witem()
|
||||
if item is None:
|
||||
continue
|
||||
|
||||
if item is self.parent:
|
||||
continue
|
||||
|
||||
if item in ls:
|
||||
continue
|
||||
|
||||
if selection is not None:
|
||||
if selection not in self.selections.keys():
|
||||
continue
|
||||
item_name = item.alias.get_full_name(base=self.parent())
|
||||
if item_name not in self.selections[selection].keys():
|
||||
continue
|
||||
recurse = self.selections[selection][item_name]["recurse"]
|
||||
else:
|
||||
recurse = True
|
||||
|
||||
ls.append(item)
|
||||
|
||||
# important to get field in case no recursion is defined.
|
||||
if item is self.parent():
|
||||
recurse = False
|
||||
|
||||
if hasattr(item, f"{self.name}") and isinstance(
|
||||
item.__dict__[self.name], self.__class__
|
||||
):
|
||||
if recurse:
|
||||
# if hasattr(item, "recursing") and item.recursing:
|
||||
# print(
|
||||
# f"recursing detected loop at {item.alias.get_full_name()}"
|
||||
# )
|
||||
# item.recursing = True
|
||||
|
||||
for titem in item.__dict__[self.name].get_list(
|
||||
selection=selection, ls=[]
|
||||
):
|
||||
|
||||
if titem not in ls:
|
||||
ls.append(titem)
|
||||
|
||||
else:
|
||||
|
||||
if item not in ls:
|
||||
ls.append(item)
|
||||
|
||||
else:
|
||||
if item not in ls:
|
||||
ls.append(item)
|
||||
|
||||
return ls
|
||||
|
||||
def get_names(self, selection=None):
|
||||
return [
|
||||
item.alias.get_full_name(base=self.parent())
|
||||
for item in self.get_list(selection=selection)
|
||||
]
|
||||
|
||||
def get_selections_names(self):
|
||||
return self.selections.keys()
|
||||
|
||||
def append(self, obj, selection=None, recursive=True):
|
||||
|
||||
if selection is not None:
|
||||
if selection not in self.selections:
|
||||
self.selections[selection] = {}
|
||||
obj_name = obj.alias.get_full_name(base=self.parent())
|
||||
self.selections[selection][obj_name] = {"recurse": recursive}
|
||||
if obj not in [tl() for tl in self._list]:
|
||||
self._list.append(weakref.ref(obj))
|
||||
|
||||
def remove(self, obj, selection=None):
|
||||
"""Remove an object from the collection. If selection is given, only remove from that selection."""
|
||||
if obj in [wobj() for wobj in self._list]:
|
||||
obj_name = obj.alias.get_full_name(base=self.parent())
|
||||
if selection is None:
|
||||
ix = [wobj() for wobj in self._list].index(obj)
|
||||
self._list.remove(self._list[ix])
|
||||
else:
|
||||
raise ValueError("Item not in list")
|
||||
if selection is not None:
|
||||
selections = [selection]
|
||||
else:
|
||||
selections = self.selections.keys()
|
||||
for selection in selections:
|
||||
if obj_name in self.selections[selection]:
|
||||
del self.selections[selection][obj_name]
|
||||
|
||||
def __call__(self):
|
||||
return self.get_list()
|
||||
|
||||
|
||||
class NumpyEncoder(json.JSONEncoder):
|
||||
"""Special json encoder for numpy types"""
|
||||
|
||||
def default(self, obj):
|
||||
if isinstance(obj, np.integer):
|
||||
return int(obj)
|
||||
elif isinstance(obj, np.floating):
|
||||
return float(obj)
|
||||
elif isinstance(obj, np.ndarray):
|
||||
return obj.tolist()
|
||||
return json.JSONEncoder.default(self, obj)
|
||||
|
||||
|
||||
@get_from_archive
|
||||
@run_status_convenience
|
||||
class Assembly:
|
||||
def __init__(self, name=None, parent=None, is_alias=True, elog=None):
|
||||
self.name = name
|
||||
self.alias = Alias(name, parent=parent)
|
||||
# self.settings = []
|
||||
# self.status_indicators = []
|
||||
self.status_collection = StatusCollection(self, name="status_collection")
|
||||
|
||||
if memory.global_memory_dir:
|
||||
self.memory = memory.Memory(self)
|
||||
if elog:
|
||||
self.__elog = elog
|
||||
# else:
|
||||
# self.__class__.__elog = property(lambda dum: ELOG)
|
||||
|
||||
# TODO: Lazy an threaded append! (for PVs, should be quite a speedup).
|
||||
def _append(
|
||||
self,
|
||||
foo_obj_init,
|
||||
*args,
|
||||
name=None,
|
||||
is_setting=False,
|
||||
is_display=True,
|
||||
is_status=True,
|
||||
# recursive=None,
|
||||
call_obj=True,
|
||||
overwrite=False,
|
||||
**kwargs,
|
||||
):
|
||||
"""This hidden method appends an object to the assembly. It can take either an object instance, or a class (in which case it will be called with the provided args and kwargs).
|
||||
Parameters
|
||||
----------
|
||||
foo_obj_init : Adjustable, Detector, Assembly, class, callable
|
||||
The object to append, or a class/callable to instantiate.
|
||||
name : str, optional
|
||||
The name of the object within the assembly. If None, the name attribute of the object will be used.
|
||||
is_setting : bool or str "recursive", optional"""
|
||||
if overwrite:
|
||||
|
||||
if name in self.__dict__:
|
||||
old = self.__dict__[name]
|
||||
self.status_collection.remove(old)
|
||||
self.alias.pop_object(old.alias)
|
||||
del old
|
||||
|
||||
if isinstance(foo_obj_init, Adjustable) and not isclass(foo_obj_init):
|
||||
# adj_copy = copy.copy(foo_obj_init)
|
||||
adj_copy = foo_obj_init
|
||||
self.__dict__[name] = adj_copy
|
||||
elif isinstance(foo_obj_init, Detector) and not isclass(foo_obj_init):
|
||||
self.__dict__[name] = foo_obj_init
|
||||
elif isinstance(foo_obj_init, Assembly) and not isclass(foo_obj_init):
|
||||
self.__dict__[name] = foo_obj_init
|
||||
elif call_obj and callable(foo_obj_init):
|
||||
self.__dict__[name] = foo_obj_init(*args, **kwargs, name=name)
|
||||
else:
|
||||
self.__dict__[name] = foo_obj_init
|
||||
self.alias.append(self.__dict__[name].alias)
|
||||
|
||||
self.status_collection.append(self.__dict__[name])
|
||||
# if is_status == "auto":
|
||||
# is_status = isinstance(self.__dict__[name], Detector)
|
||||
if is_setting:
|
||||
if isinstance(is_setting, str):
|
||||
recursive = is_setting.lower() == "recursive"
|
||||
else:
|
||||
recursive = True
|
||||
self.status_collection.append(
|
||||
self.__dict__[name], selection="settings", recursive=recursive
|
||||
)
|
||||
# self.status_collection.append(
|
||||
# self.__dict__[name], selection="settings", recursive=True
|
||||
# )
|
||||
if is_display:
|
||||
if isinstance(is_display, str):
|
||||
recursive = is_display.lower() == "recursive"
|
||||
else:
|
||||
recursive = False
|
||||
self.status_collection.append(
|
||||
self.__dict__[name], selection="display", recursive=recursive
|
||||
)
|
||||
|
||||
def get_status(
|
||||
self,
|
||||
base="self",
|
||||
verbose=False,
|
||||
print_times=False,
|
||||
channeltypes=None,
|
||||
selections=[],
|
||||
threads=False,
|
||||
max_workers=10,
|
||||
# print_name=False,
|
||||
):
|
||||
if base == "self":
|
||||
base = self
|
||||
# settings = {}
|
||||
# settings_channels = {}
|
||||
# settings_times = {}
|
||||
status = {}
|
||||
status_channels = {}
|
||||
status_times = {}
|
||||
nodet = []
|
||||
geterror = []
|
||||
|
||||
# with ThreadPoolExecutor(max_workers=max_workers) as exc:
|
||||
# list(
|
||||
# progress.track(
|
||||
# exc.map(
|
||||
# lambda name: self.init_name(
|
||||
# name, verbose=verbose, raise_errors=raise_errors
|
||||
# ),
|
||||
# self.all_names
|
||||
# - self.initialized_names
|
||||
# - set(exclude_names),
|
||||
# ),
|
||||
# description="Initializing ...",
|
||||
# total=len(
|
||||
# self.all_names - self.initialized_names - set(exclude_names)
|
||||
# ),
|
||||
# transient=True,
|
||||
# )
|
||||
# )
|
||||
|
||||
def get_stat_one_detector(ts):
|
||||
tstart = time.time()
|
||||
try:
|
||||
if (not channeltypes) or (ts.alias.channeltype in channeltypes):
|
||||
status[ts.alias.get_full_name(base=base)] = ts.get_current_value()
|
||||
try:
|
||||
status_channels[ts.alias.get_full_name(base=base)] = (
|
||||
ts.alias.channel
|
||||
)
|
||||
except:
|
||||
pass
|
||||
except:
|
||||
geterror.append(ts.alias.get_full_name(base=base))
|
||||
status_times[ts.alias.get_full_name(base=base)] = time.time() - tstart
|
||||
|
||||
ts_t = []
|
||||
for ts in track(
|
||||
self.status_collection.get_list(),
|
||||
transient=True,
|
||||
description="Reading status indicators ...",
|
||||
):
|
||||
|
||||
if isinstance(ts, Detector):
|
||||
if threads:
|
||||
ts_t.append(ts)
|
||||
else:
|
||||
tstart = time.time()
|
||||
try:
|
||||
if (not channeltypes) or (ts.alias.channeltype in channeltypes):
|
||||
status[ts.alias.get_full_name(base=base)] = (
|
||||
ts.get_current_value()
|
||||
)
|
||||
try:
|
||||
status_channels[ts.alias.get_full_name(base=base)] = (
|
||||
ts.alias.channel
|
||||
)
|
||||
except:
|
||||
pass
|
||||
except:
|
||||
geterror.append(ts.alias.get_full_name(base=base))
|
||||
status_times[ts.alias.get_full_name(base=base)] = (
|
||||
time.time() - tstart
|
||||
)
|
||||
else:
|
||||
nodet.append(ts.alias.get_full_name(base=base))
|
||||
if threads:
|
||||
|
||||
with ThreadPoolExecutor(max_workers=max_workers) as exc:
|
||||
list(
|
||||
track(
|
||||
exc.map(get_stat_one_detector, ts_t),
|
||||
description="Getting status...",
|
||||
total=len(ts_t),
|
||||
)
|
||||
)
|
||||
|
||||
if verbose:
|
||||
if nodet:
|
||||
print("Could not retrieve status from:\n " + ",\n ".join(nodet))
|
||||
if geterror:
|
||||
print(
|
||||
"Retrieved error while running get_current_value from:\n "
|
||||
+ ",\n ".join(geterror)
|
||||
)
|
||||
|
||||
if print_times:
|
||||
from ascii_graph import Pyasciigraph
|
||||
|
||||
gr = Pyasciigraph()
|
||||
for line in gr.graph(
|
||||
"Times required to get status",
|
||||
sorted(status_times.items(), key=lambda w: w[1]),
|
||||
):
|
||||
print(line)
|
||||
|
||||
sel_dict = {}
|
||||
for selection_name in selections:
|
||||
sel = self.status_collection.get_names(selection=selection_name)
|
||||
sel_dict[selection_name] = {
|
||||
tname: status[tname] for tname in sel if tname in status.keys()
|
||||
}
|
||||
|
||||
return {
|
||||
# "settings": settings,
|
||||
"status": status,
|
||||
# "settings_channels": settings_channels,
|
||||
"status_channels": status_channels,
|
||||
# "settings_times": settings_times,
|
||||
"status_times": status_times,
|
||||
"selections": sel_dict,
|
||||
}
|
||||
def get_tree(self, level=1, print_tree=True):
|
||||
"""Return nested status_collection member names as a dictionary."""
|
||||
|
||||
def build_tree(node, depth):
|
||||
result = {}
|
||||
for wref in node.status_collection._list:
|
||||
item = wref()
|
||||
if item is None or item is node:
|
||||
continue
|
||||
try:
|
||||
name = item.alias.get_full_name(base=node)
|
||||
except Exception:
|
||||
name = getattr(item, 'name', str(item))
|
||||
if hasattr(item, 'status_collection') and (depth > 1 or depth <= 0):
|
||||
next_depth = depth - 1 if depth > 0 else depth
|
||||
result[name] = build_tree(item, next_depth)
|
||||
else:
|
||||
result[name] = {}
|
||||
return result
|
||||
|
||||
def format_tree(tree, prefix=''):
|
||||
lines = []
|
||||
items = list(tree.items())
|
||||
for index, (name, subtree) in enumerate(items):
|
||||
connector = '└── ' if index == len(items) - 1 else '├── '
|
||||
lines.append(f"{prefix}{connector}{name}")
|
||||
if subtree:
|
||||
extension = ' ' if index == len(items) - 1 else '│ '
|
||||
lines.extend(format_tree(subtree, prefix + extension))
|
||||
return lines
|
||||
|
||||
tree = build_tree(self, level)
|
||||
if print_tree:
|
||||
if tree:
|
||||
for line in format_tree(tree):
|
||||
print(line)
|
||||
else:
|
||||
print('(empty)')
|
||||
return tree
|
||||
|
||||
def status(self, get_string=False):
|
||||
stat = self.get_status()
|
||||
s = tabulate([[name, value] for name, value in stat["status"].items()])
|
||||
if get_string:
|
||||
return s
|
||||
else:
|
||||
print(s)
|
||||
|
||||
def settings(self, get_string=False):
|
||||
stat = self.get_status()
|
||||
s = tabulate(
|
||||
[
|
||||
[colorama.Style.BRIGHT + name + colorama.Style.RESET_ALL, value]
|
||||
for name, value in stat["settings"].items()
|
||||
]
|
||||
)
|
||||
if get_string:
|
||||
return s
|
||||
else:
|
||||
print(s)
|
||||
|
||||
def get_status_str(self, base=None, stat_fields=["settings"]):
|
||||
stat = self.get_status(base=base)
|
||||
stat_filt = {}
|
||||
for stat_field in stat_fields:
|
||||
tstat = stat[stat_field]
|
||||
for to in self.view_toplevel_only:
|
||||
tname = to.alias.get_full_name(base=base)
|
||||
tstat = filter_names(tname, tstat)
|
||||
stat_filt[stat_field] = tstat
|
||||
s = tabulate([[name, value] for name, value in stat_filt[stat_field].items()])
|
||||
return s
|
||||
|
||||
def get_display_str(
|
||||
self,
|
||||
tablefmt="simple",
|
||||
with_base_name=False,
|
||||
maxcolwidths=[None, 50, None, None, None],
|
||||
):
|
||||
main_name = self.name
|
||||
stats = self.status_collection.get_list(selection="display")
|
||||
# stats_dict = {}
|
||||
tab = []
|
||||
for to in stats:
|
||||
name = to.alias.get_full_name(base=self)
|
||||
|
||||
is_adjustable = isinstance(to, Adjustable)
|
||||
is_detector = isinstance(to, Detector)
|
||||
typechar = ""
|
||||
if is_adjustable:
|
||||
typechar += "✏️"
|
||||
elif is_detector:
|
||||
typechar += "👁️"
|
||||
if hasattr(to, "status_collection"):
|
||||
typechar += " ↳"
|
||||
|
||||
try:
|
||||
value = to.get_current_value()
|
||||
except AttributeError:
|
||||
if hasattr(to, "status_collection"):
|
||||
value = "\x1b[3mhas lower level items\x1b[0m"
|
||||
|
||||
if isinstance(value, Enum):
|
||||
value = f"{value.value} ({value.name})"
|
||||
try:
|
||||
unit = to.unit.get_current_value()
|
||||
except:
|
||||
unit = ""
|
||||
try:
|
||||
description = to.description.get_current_value()
|
||||
except:
|
||||
description = ""
|
||||
|
||||
if value is None:
|
||||
value = ""
|
||||
if with_base_name:
|
||||
tab.append(
|
||||
[".".join([main_name, name]), value, unit, typechar, description]
|
||||
)
|
||||
else:
|
||||
tab.append([name, value, unit, typechar, description])
|
||||
if tab:
|
||||
s = tabulate(tab, tablefmt=tablefmt, maxcolwidths=maxcolwidths)
|
||||
else:
|
||||
s = ""
|
||||
|
||||
return s
|
||||
|
||||
def status_to_elog(
|
||||
self,
|
||||
text="",
|
||||
elog=None,
|
||||
files=None,
|
||||
text_encoding="markdown",
|
||||
auto_title=True,
|
||||
attach_display=True,
|
||||
attach_status_file=True,
|
||||
):
|
||||
if elog is None:
|
||||
elog = self._get_elog()
|
||||
message = ""
|
||||
|
||||
if auto_title:
|
||||
message += markdown(f"#### Status {self.alias.get_full_name()}")
|
||||
|
||||
if text:
|
||||
if text_encoding == "markdown":
|
||||
message += markdown(text)
|
||||
if attach_display:
|
||||
message += self.get_display_str(tablefmt="html")
|
||||
if files is None:
|
||||
files = []
|
||||
if attach_status_file:
|
||||
stat = self.get_status()
|
||||
tmppath = Path("/tmp")
|
||||
filepath = tmppath / Path(
|
||||
f"status_{self.alias.get_full_name}_{datetime.now().isoformat()}.json"
|
||||
)
|
||||
with open(filepath, "w") as f:
|
||||
# json.dump(stat, f, cls=NumpyEncoder, indent=4)
|
||||
json.dump(stat, f, indent=4, cls=NumpyEncoder)
|
||||
files.append(filepath)
|
||||
|
||||
if len(files) > 1:
|
||||
print(files)
|
||||
|
||||
return elog.post(
|
||||
message,
|
||||
*files,
|
||||
text_encoding="html",
|
||||
)
|
||||
# tags=[],
|
||||
|
||||
def __repr__(self):
|
||||
label = self.alias.get_full_name() + " display\n"
|
||||
return label + self.get_display_str()
|
||||
|
||||
# def _wait_for_initialisation(self, timeout=2):
|
||||
# for ton, to in self.__dict__.items():
|
||||
# try:
|
||||
# iswaitable = isinstance(to, InitialisationWaitable)
|
||||
# if iswaitable:
|
||||
# to._wait_for_initialisation()
|
||||
# except:
|
||||
# pass
|
||||
|
||||
def _wait_for_initialisation(self):
|
||||
for item in self.status_collection.get_list():
|
||||
if isinstance(item, Assembly) and (item in _initializing_assemblies):
|
||||
continue
|
||||
if isinstance(item, InitialisationWaitable):
|
||||
if isinstance(item, Assembly):
|
||||
_initializing_assemblies.append(item)
|
||||
item._wait_for_initialisation()
|
||||
|
||||
def _run_cmd(self, line, silent=True):
|
||||
if silent:
|
||||
print(f"Starting following commandline silently:\n" + line)
|
||||
with open(os.devnull, "w") as FNULL:
|
||||
subprocess.Popen(
|
||||
line, shell=True, stdout=FNULL, stderr=subprocess.STDOUT
|
||||
)
|
||||
else:
|
||||
subprocess.Popen(line, shell=True)
|
||||
|
||||
def _get_elog(self):
|
||||
if hasattr(self, "_elog") and self._elog:
|
||||
return self._elog
|
||||
elif hasattr(self, "__elog") and self.__elog:
|
||||
return self.__elog
|
||||
elif eco.defaults.ELOG:
|
||||
return eco.defaults.ELOG
|
||||
else:
|
||||
return None
|
||||
|
||||
def widget(self):
|
||||
from eco.widgets.display_widget import make_assembly_widget
|
||||
|
||||
return make_assembly_widget(self)
|
||||
|
||||
|
||||
import epics.pv
|
||||
import time
|
||||
|
||||
|
||||
class Monitor:
|
||||
def __init__(self, assembly):
|
||||
self.assembly = assembly
|
||||
self.data = {}
|
||||
self.callbacks = {}
|
||||
self.pvs = {}
|
||||
|
||||
def start_monitoring(self):
|
||||
o = self.assembly.get_status(channeltypes=["CA"])
|
||||
# self.data = {k: [v] for k, v in o["status"].items()}
|
||||
self.channelkeys = {v: k for k, v in o["status_channels"].items()}
|
||||
self.pvs = {k: epics.pv.PV(v) for k, v in o["status_channels"].items()}
|
||||
# for cik, civ in epics.pv._PVcache_.items():
|
||||
# if cik[0] in o["status_channels"].keys():
|
||||
# tname = self.channelkeys[cik[0]]
|
||||
# tpv = civ
|
||||
for tname, tpv in self.pvs.items():
|
||||
self.callbacks[tname] = tpv.add_callback(self.append)
|
||||
|
||||
def stop_monitoring(self):
|
||||
for tname in self.pvs:
|
||||
self.pvs[tname].remove_callback(index=self.callbacks[tname])
|
||||
|
||||
def append(self, pvname=None, value=None, timestamp=None, **kwargs):
|
||||
if not (self.channelkeys[pvname] in self.data):
|
||||
self.data[self.channelkeys[pvname]] = []
|
||||
ts_local = time.time()
|
||||
self.data[self.channelkeys[pvname]].append(
|
||||
{"value": value, "timestamp": timestamp, "timestamp_local": ts_local}
|
||||
)
|
||||
|
||||
|
||||
def filter_names(name, stat_dict):
|
||||
out = {}
|
||||
for key, value in stat_dict.items():
|
||||
keys = key.split(".")
|
||||
if keys[0] == name:
|
||||
if len(keys) == 1:
|
||||
out[key] = value
|
||||
else:
|
||||
out[key] = value
|
||||
return out
|
||||
@@ -0,0 +1,173 @@
|
||||
from copy import deepcopy
|
||||
from threading import Thread
|
||||
from eco.acquisition.decorators import scannable
|
||||
from eco.elements.adjustable import (
|
||||
AdjustableMemory,
|
||||
default_representation,
|
||||
spec_convenience,
|
||||
)
|
||||
from eco.elements.assembly import Assembly
|
||||
from eco.aliases import Alias
|
||||
import time
|
||||
|
||||
|
||||
def value_property(Det, value_name="_value"):
|
||||
setattr(
|
||||
Det,
|
||||
value_name,
|
||||
property(
|
||||
Det.get_current_value,
|
||||
),
|
||||
)
|
||||
return Det
|
||||
|
||||
|
||||
def call_convenience(Det, value=None):
|
||||
# spec-inspired convenience methods
|
||||
|
||||
def wm(self, *args, **kwargs):
|
||||
return self.get_current_value(*args, **kwargs)
|
||||
|
||||
Det.wm = wm
|
||||
|
||||
def call(self, value=value):
|
||||
if value is None:
|
||||
return self.wm()
|
||||
else:
|
||||
raise ValueError(f"{self.name} is just a readback, which cannot be set.")
|
||||
|
||||
Det.__call__ = call
|
||||
|
||||
return Det
|
||||
|
||||
|
||||
@call_convenience
|
||||
@value_property
|
||||
@default_representation
|
||||
class DetectorVirtual(Assembly):
|
||||
def __init__(
|
||||
self,
|
||||
detectors,
|
||||
foo_get_current_value,
|
||||
append_aliases=False,
|
||||
name=None,
|
||||
unit=None,
|
||||
):
|
||||
super().__init__(name=name)
|
||||
if append_aliases:
|
||||
for det in detectors:
|
||||
try:
|
||||
self.alias.append(det.alias)
|
||||
except Exception as e:
|
||||
print(f"could not find alias in {det}")
|
||||
print(str(e))
|
||||
self._detectors = detectors
|
||||
self._foo_get_current_value = foo_get_current_value
|
||||
if unit:
|
||||
self.unit = AdjustableMemory(unit, name="unit")
|
||||
self.status_collection.append(self)
|
||||
self.status_collection.append(self, selection="settings", recursive=False)
|
||||
self.status_collection.append(self, selection="display", recursive=False)
|
||||
|
||||
def get_current_value(self):
|
||||
return self._foo_get_current_value(
|
||||
*[det.get_current_value() for det in self._detectors]
|
||||
)
|
||||
|
||||
|
||||
@call_convenience
|
||||
@value_property
|
||||
@default_representation
|
||||
@scannable
|
||||
class DetectorGet:
|
||||
def __init__(
|
||||
self, foo_get, cache_get_seconds=None, monitor_frequency=None, name=None
|
||||
):
|
||||
""" """
|
||||
self.alias = Alias(name)
|
||||
self.name = name
|
||||
self._get = foo_get
|
||||
self._cache_get_seconds = cache_get_seconds
|
||||
self._accumulate_frequency = monitor_frequency
|
||||
if monitor_frequency:
|
||||
self.set_current_value_callback = self._set_current_value_callback
|
||||
|
||||
def get_current_value(self):
|
||||
ts = time.time()
|
||||
if self._cache_get_seconds and hasattr(self, "_get_cache"):
|
||||
if ts - self._get_cache[0] < self._cache_get_seconds:
|
||||
value = self._get_cache[1]
|
||||
else:
|
||||
value = self._get()
|
||||
else:
|
||||
value = self._get()
|
||||
if self._cache_get_seconds:
|
||||
self._get_cache = (ts, value)
|
||||
return value
|
||||
|
||||
def _set_current_value_callback(self):
|
||||
return CallbackTimedelta(
|
||||
self, frequency=self._accumulate_frequency, func="accumulate"
|
||||
)
|
||||
|
||||
|
||||
@call_convenience
|
||||
@value_property
|
||||
class DetectorMemory:
|
||||
def __init__(self, value=0, name="detector_memory", return_deep_copy=True):
|
||||
self.name = name
|
||||
self.alias = Alias(name)
|
||||
self.current_value = value
|
||||
self._return_deep_copy = return_deep_copy
|
||||
|
||||
def get_current_value(self):
|
||||
if self._return_deep_copy:
|
||||
return deepcopy(self.current_value)
|
||||
else:
|
||||
return self.current_value
|
||||
|
||||
def __repr__(self):
|
||||
name = self.name
|
||||
cv = self.get_current_value()
|
||||
s = f"{name} at value: {cv}" + "\n"
|
||||
return s
|
||||
|
||||
|
||||
class CallbackTimedelta:
|
||||
def __init__(
|
||||
self, detector, frequency=10, func="accumulate", collector=None, run_once=True
|
||||
):
|
||||
self.detector = detector
|
||||
self.frequency = frequency
|
||||
# self.data = collector
|
||||
if func == "accumulate":
|
||||
func = self.accumulate_values
|
||||
if collector is None:
|
||||
collector = {"timestamps": [], "values": []}
|
||||
self.data = (
|
||||
collector # {"timestamps": [], "values": [], "timestamps_ioc": []}
|
||||
)
|
||||
self.foo = func
|
||||
self.run_once = run_once
|
||||
self.running = False
|
||||
self.thread = None
|
||||
|
||||
def start(self, add_current_value=True):
|
||||
if add_current_value:
|
||||
ts_local = time.time()
|
||||
self.data["timestamps"].append(ts_local)
|
||||
self.data["values"].append(self.detector.get_current_value())
|
||||
self.running = True
|
||||
self.thread = Thread(target=self.accumulate_values)
|
||||
self.thread.start()
|
||||
|
||||
def accumulate_values(self, *args, **kwargs):
|
||||
while self.running:
|
||||
ts_local = time.time()
|
||||
self.data["timestamps"].append(ts_local)
|
||||
self.data["values"].append(self.detector.get_current_value())
|
||||
time.sleep(1 / self.frequency)
|
||||
|
||||
def stop(self):
|
||||
self.running = False
|
||||
self.thread.join()
|
||||
@@ -0,0 +1,558 @@
|
||||
import itertools
|
||||
from pathlib import Path
|
||||
from datetime import datetime
|
||||
import weakref
|
||||
from .adjustable import AdjustableFS
|
||||
from ..utilities.keypress import KeyPress
|
||||
from tabulate import tabulate
|
||||
import sys, colorama
|
||||
|
||||
try:
|
||||
from inspect import getargspec
|
||||
except: # for python 3.12
|
||||
from inspect import getfullargspec as getargspec
|
||||
|
||||
import eco
|
||||
from ansi2html import Ansi2HTMLConverter
|
||||
from simple_term_menu import TerminalMenu
|
||||
|
||||
conv = Ansi2HTMLConverter()
|
||||
|
||||
global_memory_dir = None
|
||||
|
||||
|
||||
def set_global_memory_dir(dirpath, mode="w"):
|
||||
globals()["global_memory_dir"] = Path(dirpath).expanduser()
|
||||
|
||||
|
||||
def get_memory(name):
|
||||
if not (global_memory_dir is None):
|
||||
return Memory(name)
|
||||
|
||||
|
||||
class Memory:
|
||||
def __init__(
|
||||
self,
|
||||
obj,
|
||||
memory_dir=global_memory_dir,
|
||||
categories={"recall": ["settings"], "track": ["display"]},
|
||||
):
|
||||
self.obj_parent = weakref.ref(obj)
|
||||
self.categories = categories
|
||||
if not memory_dir:
|
||||
memory_dir = global_memory_dir
|
||||
self.base_dir = Path(memory_dir)
|
||||
self.obj_parent().presets = Presets(self)
|
||||
|
||||
def setup_path(self):
|
||||
name = self.obj_parent().alias.get_full_name(joiner=None)
|
||||
self.dir = Path(self.base_dir) / Path("/".join(reversed(name)))
|
||||
try:
|
||||
self.dir.mkdir(exist_ok=True)
|
||||
try:
|
||||
self.dir.chmod(0o775)
|
||||
except:
|
||||
pass
|
||||
except:
|
||||
print("Could not create memory directory")
|
||||
self._memories = AdjustableFS(
|
||||
self.dir / Path("memories.json"), default_value={}
|
||||
)
|
||||
self._presets = AdjustableFS(self.dir / Path("presets.json"), default_value={})
|
||||
|
||||
def memories(self, indices=None, search_key=None):
|
||||
self.setup_path()
|
||||
mem = self._memories()
|
||||
memkeys = list(mem.keys())
|
||||
if indices is None:
|
||||
indices = range(len(mem))
|
||||
mems = []
|
||||
for index in indices:
|
||||
tkey = memkeys[index]
|
||||
tmem = mem[tkey]
|
||||
cats = list(itertools.chain.from_iterable(tmem["categories"].values()))
|
||||
tmem_all = self.get_memory(key=tkey)
|
||||
if search_key is not None:
|
||||
tmem_sel = {
|
||||
tk: {ttk: ttv for ttk, ttv in tv.items() if search_key in ttk}
|
||||
for tk, tv in tmem_all.items()
|
||||
if tk in cats
|
||||
}
|
||||
else:
|
||||
tmem_sel = tmem_all
|
||||
tmem.update(tmem_sel)
|
||||
mems.append(tmem)
|
||||
return mems
|
||||
|
||||
def plot_parameter(self, parameter_name, group_name="settings"):
|
||||
mem = self.memories(search_key=parameter_name)
|
||||
date = []
|
||||
value = []
|
||||
message = []
|
||||
for tmem in mem:
|
||||
try:
|
||||
tdate = datetime.fromisoformat(tmem["date"])
|
||||
tval = tmem[group_name][parameter_name]
|
||||
tmess = tmem["message"]
|
||||
date.append(tdate)
|
||||
value.append(tval)
|
||||
message.append(tmess)
|
||||
except:
|
||||
pass
|
||||
|
||||
return date, value, message
|
||||
|
||||
def __str__(self):
|
||||
self.setup_path()
|
||||
mem = self._memories()
|
||||
a = []
|
||||
for n, (key, content) in enumerate(mem.items()):
|
||||
row = [n]
|
||||
t = datetime.fromisoformat(key)
|
||||
row.append(t.strftime("%Y-%m-%d: %a %-H:%M"))
|
||||
row.append(content["message"])
|
||||
a.append(row)
|
||||
|
||||
return tabulate(a, headers=["Index", "Time", "Message"])
|
||||
|
||||
def __call__(self, index=None, **kwargs):
|
||||
# print(self.get_memory_difference_str(index))
|
||||
|
||||
if index is None:
|
||||
self.setup_path()
|
||||
mem = self._memories()
|
||||
a = []
|
||||
for n, (key, content) in enumerate(mem.items()):
|
||||
row = ""
|
||||
t = datetime.fromisoformat(key)
|
||||
row += t.strftime("%Y-%m-%d: %a %H:%M")
|
||||
row += " "
|
||||
row += content["message"]
|
||||
a.append(row)
|
||||
ind_cancel = len(a)
|
||||
a.append("--> do nothing")
|
||||
menu = TerminalMenu(a, cursor_index=ind_cancel)
|
||||
print("Select memory to recall")
|
||||
index = menu.show()
|
||||
if index == ind_cancel:
|
||||
return
|
||||
self.recall(memory_index=index, **kwargs)
|
||||
|
||||
def _get_elog(self):
|
||||
if hasattr(self, "_elog") and self._elog:
|
||||
return self._elog
|
||||
elif hasattr(self, "__elog") and self.__elog:
|
||||
return self.__elog
|
||||
elif eco.defaults.ELOG:
|
||||
return eco.defaults.ELOG
|
||||
else:
|
||||
return None
|
||||
|
||||
def memorize(
|
||||
self,
|
||||
message=None,
|
||||
attributes={},
|
||||
force_message=True,
|
||||
preset_varname=None,
|
||||
to_elog=True,
|
||||
):
|
||||
self.setup_path()
|
||||
cats = list(itertools.chain.from_iterable(self.categories.values()))
|
||||
stat_now = {}
|
||||
allstat = self.obj_parent().get_status(base=self.obj_parent(), selections=cats)
|
||||
stat_now["status"] = allstat["status"]
|
||||
for trec in self.categories["recall"]:
|
||||
stat_now[trec] = allstat["selections"][trec]
|
||||
for trec in self.categories["track"]:
|
||||
stat_now[trec] = allstat["selections"][trec]
|
||||
|
||||
stat_now["memorized_attributes"] = attributes
|
||||
key = datetime.now().isoformat()
|
||||
stat_now["date"] = key
|
||||
mem = self._memories()
|
||||
if force_message:
|
||||
while not message:
|
||||
message = input(
|
||||
"Please enter a message associated to this memory entry:\n>>> "
|
||||
)
|
||||
mem[key] = {
|
||||
"message": message,
|
||||
"categories": self.categories,
|
||||
"date": key,
|
||||
}
|
||||
if preset_varname:
|
||||
mem[key].update({"presetname": preset_varname})
|
||||
tmp = AdjustableFS(self.dir / Path(key + ".json"))
|
||||
tmp(stat_now)
|
||||
self._memories(mem)
|
||||
print(f"Saved memory for {self.obj_parent().alias.get_full_name()}: {message}")
|
||||
print(f"memory file: {tmp.file_path.as_posix()}")
|
||||
if to_elog:
|
||||
elog = self._get_elog()
|
||||
elog.post(
|
||||
f"Saved memory for {self.obj_parent().alias.get_full_name()}: {message}",
|
||||
tmp.file_path,
|
||||
text_encoding="markdown",
|
||||
)
|
||||
|
||||
def get_memory(self, input_obj=None, index=None, key=None, filter_existing=True):
|
||||
if not input_obj is None:
|
||||
if type(input_obj) is dict:
|
||||
mem_full = input_obj
|
||||
else:
|
||||
tmp = AdjustableFS(Path(input_obj))
|
||||
mem_full = tmp()
|
||||
else:
|
||||
self.setup_path()
|
||||
if not (index is None):
|
||||
key = list(self._memories().keys())[index]
|
||||
tmp = AdjustableFS(self.dir / Path(key + ".json"))
|
||||
mem_full = tmp()
|
||||
if filter_existing:
|
||||
mem_filt = {}
|
||||
for tkey, tval in mem_full.items():
|
||||
if tkey in ["settings", "status_indicators"]:
|
||||
mem_filt[tkey] = {}
|
||||
for ttkey, ttval in tval.items():
|
||||
try:
|
||||
name2obj(self.obj_parent(), ttkey)
|
||||
mem_filt[tkey][ttkey] = ttval
|
||||
except KeyError:
|
||||
...
|
||||
else:
|
||||
mem_filt[tkey] = tval
|
||||
|
||||
return mem_filt
|
||||
else:
|
||||
return mem_full
|
||||
|
||||
def clear_memory(self, index=None, key=None):
|
||||
if not (index is None):
|
||||
key = list(self._memories().keys())[index]
|
||||
if key is None:
|
||||
raise Exception("memory key or index to be deleted needs to be specified!")
|
||||
mem = self._memories.get_current_value()
|
||||
mem.pop(key)
|
||||
self._memories.set_target_value(mem).wait()
|
||||
|
||||
def recall(
|
||||
self,
|
||||
memory_index=None,
|
||||
input_obj=None,
|
||||
key=None,
|
||||
wait=True,
|
||||
show_changes_only=True,
|
||||
set_changes_only=True,
|
||||
check_limits=True,
|
||||
change_serially=False,
|
||||
force=False,
|
||||
):
|
||||
"""Recall a memory_index, from an index in the default meory list, from a
|
||||
dictionary containing the memory information, or from a path to a file containing the memory.
|
||||
|
||||
Args:
|
||||
memory_index (integer, optional): index in memory list. Defaults to None.
|
||||
input_obj (dictionary or string, optional): direct passing memory as dict or s filepath (string) to the memory file. Defaults to None.
|
||||
key (string, optional): key of memory in memory list (if not defined by the index). Defaults to None.
|
||||
wait (bool, optional): Wait for the memory recall changes to complete. Defaults to True.
|
||||
show_changes_only (bool, optional): in rpreview show only changes that are different to present setting. Defaults to True.
|
||||
set_changes_only (bool, optional): setting only the changes that changed. Defaults to True.
|
||||
check_limits (bool, optional): check limits before changing. Defaults to True.
|
||||
change_serially (bool, optional): change and wait each change after each other, not simultaneously. Defaults to False.
|
||||
force (bool, optional): force the change without previous preview. Defaults to False.
|
||||
|
||||
Returns:
|
||||
_type_: _description_
|
||||
"""
|
||||
# if input_obj:
|
||||
mem = self.get_memory(
|
||||
index=memory_index,
|
||||
key=key,
|
||||
input_obj=input_obj,
|
||||
)
|
||||
rec = {}
|
||||
for trec in self.categories["recall"]:
|
||||
rec.update(mem[trec])
|
||||
|
||||
if force:
|
||||
select = [True] * len(rec.items())
|
||||
else:
|
||||
select = self.select_from_memory(
|
||||
rec,
|
||||
show_changes_only=show_changes_only,
|
||||
)
|
||||
if not select:
|
||||
return
|
||||
if not input("would you really like to do the change? (y/n):") == "y":
|
||||
return
|
||||
|
||||
changes = []
|
||||
for sel, (key, val) in zip(select, rec.items()):
|
||||
if sel:
|
||||
to = name2obj(self.obj_parent(), key)
|
||||
if set_changes_only:
|
||||
if to.get_current_value() == val:
|
||||
continue
|
||||
print(f"Changing {key} from {to.get_current_value()} to {val}")
|
||||
if "check" in getargspec(to.set_target_value).args:
|
||||
changes.append(to.set_target_value(val, check=check_limits))
|
||||
else:
|
||||
changes.append(to.set_target_value(val))
|
||||
if change_serially:
|
||||
changes[-1].wait()
|
||||
if wait:
|
||||
for change in changes:
|
||||
change.wait()
|
||||
return
|
||||
else:
|
||||
return changes
|
||||
|
||||
def recall_from_runtable(self): ...
|
||||
|
||||
def get_memory_difference_str(
|
||||
self,
|
||||
recall_dict,
|
||||
select=None,
|
||||
ask_select=True,
|
||||
show_changes_only=False,
|
||||
tablefmt="plain",
|
||||
):
|
||||
|
||||
if not select:
|
||||
select = [True] * len(recall_dict)
|
||||
table = []
|
||||
for n, (tsel, (key, recall_value)) in enumerate(
|
||||
zip(select, recall_dict.items())
|
||||
):
|
||||
present_value = name2obj(self.obj_parent(), key).get_current_value()
|
||||
if tsel:
|
||||
tselstr = "x"
|
||||
else:
|
||||
tselstr = " "
|
||||
if present_value == recall_value:
|
||||
changed = False
|
||||
if tablefmt == "html":
|
||||
comp_indicator = "=="
|
||||
else:
|
||||
comp_indicator = (
|
||||
colorama.Fore.GREEN
|
||||
+ colorama.Style.BRIGHT
|
||||
+ "=="
|
||||
+ colorama.Style.RESET_ALL
|
||||
)
|
||||
else:
|
||||
changed = True
|
||||
if not tsel:
|
||||
try:
|
||||
comp_indicator = (
|
||||
f"not changed ({recall_value-present_value:+g})"
|
||||
)
|
||||
except:
|
||||
comp_indicator = f"not changed"
|
||||
else:
|
||||
try:
|
||||
tdiff = f"{recall_value - present_value:+g}"
|
||||
except TypeError:
|
||||
tdiff = "special"
|
||||
if tablefmt == "html":
|
||||
comp_indicator = f"{tdiff:s}"
|
||||
else:
|
||||
comp_indicator = (
|
||||
colorama.Fore.RED
|
||||
+ colorama.Style.BRIGHT
|
||||
+ f"{tdiff:s}"
|
||||
+ colorama.Style.RESET_ALL
|
||||
)
|
||||
if show_changes_only and (not changed):
|
||||
continue
|
||||
|
||||
table.append([n, tselstr, key, present_value, comp_indicator, recall_value])
|
||||
|
||||
if len(table) == 0:
|
||||
return "No changes compared to memory!"
|
||||
return tabulate(
|
||||
table,
|
||||
headers=[
|
||||
"",
|
||||
"",
|
||||
"name",
|
||||
"present",
|
||||
"difference",
|
||||
"memory",
|
||||
],
|
||||
colalign=("decimal", "center", "left", "decimal", "center", "decimal"),
|
||||
tablefmt=tablefmt,
|
||||
)
|
||||
|
||||
def select_from_memory(self, recall_dict, show_changes_only=True):
|
||||
# mem = self.get_memory(input_obj=input_obj, key=key, index=memory_index)
|
||||
|
||||
k = KeyPress()
|
||||
# cll = colorama.ansi.clear_line()
|
||||
|
||||
help = "Change selection pressing keys followed by numbered seelection \n"
|
||||
help += " o : Select only (enter comma-separated row numbers)\n"
|
||||
help += " a : Select additionally (enter comma-separated row numbers)\n"
|
||||
help += " e : Exclude from selection (enter comma-separated row numbers)\n"
|
||||
help += " r : recall selected memory\n"
|
||||
help += " q : quit\n"
|
||||
|
||||
class Printer:
|
||||
def __init__(self, o=self):
|
||||
self.o = o
|
||||
self.len = len(recall_dict)
|
||||
self.select = [True] * self.len
|
||||
|
||||
def print(self, **kwargs):
|
||||
print(
|
||||
self.o.get_memory_difference_str(
|
||||
recall_dict,
|
||||
select=self.select,
|
||||
show_changes_only=show_changes_only,
|
||||
)
|
||||
)
|
||||
print(help)
|
||||
|
||||
def select_only(self):
|
||||
v = self.get_array()
|
||||
self.select = [False] * self.len
|
||||
for tv in v:
|
||||
self.select[tv] = True
|
||||
|
||||
def select_additional(self):
|
||||
v = self.get_array()
|
||||
for tv in v:
|
||||
self.select[tv] = True
|
||||
|
||||
def exclude(self):
|
||||
v = self.get_array()
|
||||
for tv in v:
|
||||
self.select[tv] = False
|
||||
|
||||
def get_array(self):
|
||||
sys.stdout.flush()
|
||||
v = sys.stdin.readline()
|
||||
try:
|
||||
v = v.split(",")
|
||||
v = [int(tv) for tv in v]
|
||||
print(v)
|
||||
return v
|
||||
except:
|
||||
print(
|
||||
"value cannot be converted to listed integers, please try again!"
|
||||
)
|
||||
sys.stdout.flush()
|
||||
return self.get_array()
|
||||
|
||||
p = Printer()
|
||||
while k.isq() is False:
|
||||
p.print()
|
||||
k.waitkey()
|
||||
if k.iskey("o"):
|
||||
print("Select only: ")
|
||||
p.select_only()
|
||||
elif k.iskey("a"):
|
||||
print("Append to selection: ")
|
||||
p.select_additional()
|
||||
elif k.iskey("e"):
|
||||
print("Exclude from selection: ")
|
||||
p.exclude()
|
||||
elif k.isq():
|
||||
return
|
||||
elif k.iskey("r"):
|
||||
return p.select
|
||||
else:
|
||||
# print(help)
|
||||
pass
|
||||
|
||||
# stat_now = self.obj_parent.get_status()
|
||||
# for mem
|
||||
|
||||
def __repr__(self):
|
||||
return self.__str__()
|
||||
|
||||
|
||||
class Presets:
|
||||
def __init__(
|
||||
self,
|
||||
memory,
|
||||
):
|
||||
self._memory = memory
|
||||
self._setup_presets()
|
||||
|
||||
def __dir__(self):
|
||||
return self._setup_presets()
|
||||
|
||||
def __getattr__(self, name):
|
||||
self._setup_presets()
|
||||
if not name in self.__dict__.keys():
|
||||
raise AttributeError
|
||||
return self.__dict__[name]
|
||||
|
||||
def _setup_presets(self):
|
||||
self._memory.setup_path()
|
||||
mem = self._memory._memories()
|
||||
presets = []
|
||||
for key, dat in mem.items():
|
||||
if "presetname" in dat.keys():
|
||||
self.__dict__[dat["presetname"]] = Preset(
|
||||
self._memory, key, name=dat["presetname"]
|
||||
)
|
||||
presets.append(dat["presetname"])
|
||||
return presets
|
||||
|
||||
def __str__(self):
|
||||
self._memory.setup_path()
|
||||
mem = self._memory._memories()
|
||||
table = []
|
||||
for key, dat in mem.items():
|
||||
if "presetname" in dat.keys():
|
||||
table.append([dat["presetname"], key, dat["message"]])
|
||||
|
||||
return tabulate(
|
||||
table,
|
||||
headers=[
|
||||
"Preset",
|
||||
"Date",
|
||||
"Message",
|
||||
],
|
||||
colalign=("left", "left", "left"),
|
||||
)
|
||||
|
||||
def __repr__(self):
|
||||
return self.__str__()
|
||||
|
||||
|
||||
class Preset:
|
||||
def __init__(self, memory, key, name=None):
|
||||
self._memory = memory
|
||||
self._key = key
|
||||
self._name = name
|
||||
|
||||
def get_memory(self):
|
||||
return self._memory.get_memory(key=self._key)
|
||||
|
||||
def __call__(self, force=True):
|
||||
self._memory.recall(key=self._key, force=force)
|
||||
|
||||
def __str__(self):
|
||||
s = f"Preset {self._name} - saved values compared to the present status\n"
|
||||
tmem = self._memory.get_memory(key=self._key)
|
||||
s += self._memory.get_memory_difference_str(tmem)
|
||||
return s
|
||||
|
||||
def __repr__(self):
|
||||
return self.__str__()
|
||||
|
||||
|
||||
def name2obj(obj_parent, name, delimiter="."):
|
||||
if type(name) is str:
|
||||
name = name.split(delimiter)
|
||||
obj = obj_parent
|
||||
for tn in name:
|
||||
if not tn or tn == "self":
|
||||
obj = obj
|
||||
else:
|
||||
obj = obj.__dict__[tn]
|
||||
|
||||
return obj
|
||||
@@ -0,0 +1,66 @@
|
||||
from typing import Protocol, runtime_checkable
|
||||
|
||||
|
||||
@runtime_checkable
|
||||
class Adjustable(Protocol):
|
||||
def get_current_value(self):
|
||||
...
|
||||
|
||||
def set_target_value(self, value):
|
||||
...
|
||||
|
||||
# def set_target_value(self,value) -> Changer:...
|
||||
|
||||
|
||||
@runtime_checkable
|
||||
class Detector(Protocol):
|
||||
def get_current_value(self):
|
||||
...
|
||||
|
||||
|
||||
@runtime_checkable
|
||||
class MonitorableValueUpdate(Protocol):
|
||||
def set_current_value_callback(self):
|
||||
...
|
||||
|
||||
|
||||
@runtime_checkable
|
||||
class InitialisationWaitable(Protocol):
|
||||
def _wait_for_initialisation(self):
|
||||
...
|
||||
|
||||
@runtime_checkable
|
||||
class Counter(Protocol):
|
||||
def acquire(self):
|
||||
...
|
||||
def start(self):
|
||||
...
|
||||
def stop(self):
|
||||
...
|
||||
|
||||
def __enter__(self):
|
||||
self.start()
|
||||
|
||||
def __exit__(self, type, value, traceback):
|
||||
self.stop()
|
||||
|
||||
|
||||
# file_name=fina, Npulses=self.pulses_per_step[0], acq_pars=acq_pars):
|
||||
|
||||
|
||||
|
||||
# class Callback:
|
||||
# self.__init__(self, func=None, *args, **kwargs):
|
||||
# self.func = func
|
||||
# self.args = args
|
||||
# self.kwargs = kwargs
|
||||
|
||||
# def start(self,func=None):
|
||||
# if func is not None:
|
||||
# self.func = func
|
||||
|
||||
|
||||
|
||||
|
||||
# def
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2023 [Your Name]
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
1. The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
2. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
@@ -0,0 +1,64 @@
|
||||
# textual-status-editor
|
||||
|
||||
This project implements a textual user interface for displaying and managing the status of various items in an assembly. It provides a tabular view of current values and allows users to set target values for these items.
|
||||
|
||||
## Features
|
||||
|
||||
- Display current values of status items in a tabular format.
|
||||
- Entry fields for setting target values of status items.
|
||||
- Interactive user interface built with the Textual library.
|
||||
|
||||
## Project Structure
|
||||
|
||||
```
|
||||
textual-status-editor
|
||||
├── src
|
||||
│ └── textual_status_editor
|
||||
│ ├── __init__.py
|
||||
│ ├── app.py
|
||||
│ ├── main.py
|
||||
│ ├── config.py
|
||||
│ ├── adapters
|
||||
│ │ └── assembly_adapter.py
|
||||
│ ├── models
|
||||
│ │ └── status_item.py
|
||||
│ └── widgets
|
||||
│ ├── __init__.py
|
||||
│ └── status_table.py
|
||||
├── tests
|
||||
│ ├── test_status_table.py
|
||||
│ └── test_assembly_adapter.py
|
||||
├── pyproject.toml
|
||||
├── requirements.txt
|
||||
├── README.md
|
||||
├── .gitignore
|
||||
└── LICENSE
|
||||
```
|
||||
|
||||
## Installation
|
||||
|
||||
1. Clone the repository:
|
||||
```
|
||||
git clone https://github.com/yourusername/textual-status-editor.git
|
||||
cd textual-status-editor
|
||||
```
|
||||
|
||||
2. Install the required dependencies:
|
||||
```
|
||||
pip install -r requirements.txt
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
To run the application, execute the following command:
|
||||
```
|
||||
python -m textual_status_editor.main
|
||||
```
|
||||
|
||||
## Contributing
|
||||
|
||||
Contributions are welcome! Please feel free to submit a pull request or open an issue for any suggestions or improvements.
|
||||
|
||||
## License
|
||||
|
||||
This project is licensed under the MIT License. See the LICENSE file for more details.
|
||||
@@ -0,0 +1,23 @@
|
||||
[tool.poetry]
|
||||
name = "textual-status-editor"
|
||||
version = "0.1.0"
|
||||
description = "A textual tabular widget to display and edit status values."
|
||||
authors = ["Your Name <youremail@example.com>"]
|
||||
license = "MIT"
|
||||
readme = "README.md"
|
||||
homepage = "https://github.com/yourusername/textual-status-editor"
|
||||
repository = "https://github.com/yourusername/textual-status-editor"
|
||||
keywords = ["textual", "status", "editor", "widget"]
|
||||
|
||||
[tool.poetry.dependencies]
|
||||
python = "^3.8"
|
||||
textual = "^0.1.0" # Replace with the actual version you want to use
|
||||
rich = "^10.0.0" # For rich text formatting in the terminal
|
||||
|
||||
[tool.poetry.dev-dependencies]
|
||||
pytest = "^6.0"
|
||||
pytest-cov = "^2.10"
|
||||
|
||||
[build-system]
|
||||
requires = ["poetry-core>=1.0.0"]
|
||||
build-backend = "poetry.core.masonry.api"
|
||||
@@ -0,0 +1,5 @@
|
||||
textual
|
||||
rich
|
||||
pytest
|
||||
pytest-asyncio
|
||||
textual-widget
|
||||
@@ -0,0 +1 @@
|
||||
# This file is intentionally left blank.
|
||||
+61
@@ -0,0 +1,61 @@
|
||||
from eco.elements.protocols import Assembly
|
||||
from textual.app import App
|
||||
from textual.widgets import Table, Input
|
||||
from textual.reactive import Reactive
|
||||
from textual.containers import Container
|
||||
|
||||
class StatusItem:
|
||||
def __init__(self, name, current_value):
|
||||
self.name = name
|
||||
self.current_value = current_value
|
||||
|
||||
def set_target_value(self, value):
|
||||
# Placeholder for setting the target value
|
||||
self.current_value = value
|
||||
|
||||
class AssemblyAdapter:
|
||||
def __init__(self, assembly: Assembly):
|
||||
self.assembly = assembly
|
||||
|
||||
def get_status_items(self):
|
||||
status_items = []
|
||||
for item in self.assembly.status_collection.get_list():
|
||||
current_value = item.get_current_value() if hasattr(item, 'get_current_value') else None
|
||||
status_items.append(StatusItem(item.alias.get_full_name(), current_value))
|
||||
return status_items
|
||||
|
||||
class StatusTable(App):
|
||||
def __init__(self, assembly_adapter: AssemblyAdapter):
|
||||
super().__init__()
|
||||
self.assembly_adapter = assembly_adapter
|
||||
self.status_items = self.assembly_adapter.get_status_items()
|
||||
|
||||
async def on_mount(self):
|
||||
self.table = Table()
|
||||
self.table.add_column("Name")
|
||||
self.table.add_column("Current Value")
|
||||
self.table.add_column("Set Target Value")
|
||||
|
||||
for item in self.status_items:
|
||||
input_field = Input(placeholder="Enter value")
|
||||
input_field.on_submit(lambda value, item=item: self.set_target_value(item, value))
|
||||
self.table.add_row(item.name, str(item.current_value), input_field)
|
||||
|
||||
await self.view.dock(self.table)
|
||||
|
||||
def set_target_value(self, item: StatusItem, value: str):
|
||||
item.set_target_value(value)
|
||||
self.refresh_table()
|
||||
|
||||
def refresh_table(self):
|
||||
self.table.clear()
|
||||
for item in self.status_items:
|
||||
input_field = Input(placeholder="Enter value")
|
||||
input_field.on_submit(lambda value, item=item: self.set_target_value(item, value))
|
||||
self.table.add_row(item.name, str(item.current_value), input_field)
|
||||
|
||||
if __name__ == "__main__":
|
||||
assembly = Assembly() # Replace with actual assembly initialization
|
||||
adapter = AssemblyAdapter(assembly)
|
||||
app = StatusTable(adapter)
|
||||
app.run()
|
||||
@@ -0,0 +1,45 @@
|
||||
from textual.app import App
|
||||
from textual.widgets import Static, Input, Table
|
||||
from textual.reactive import Reactive
|
||||
from textual import events
|
||||
from .adapters.assembly_adapter import AssemblyAdapter
|
||||
|
||||
class StatusItem:
|
||||
def __init__(self, name, current_value):
|
||||
self.name = name
|
||||
self.current_value = current_value
|
||||
|
||||
def set_target_value(self, value):
|
||||
# Logic to set the target value
|
||||
pass
|
||||
|
||||
class StatusTable(Table):
|
||||
def __init__(self, items):
|
||||
super().__init__()
|
||||
self.items = items
|
||||
self.add_column("Name", min_width=20)
|
||||
self.add_column("Current Value", min_width=20)
|
||||
self.add_column("Set Target Value", min_width=20)
|
||||
|
||||
for item in self.items:
|
||||
self.add_row(item.name, str(item.current_value), Input(placeholder="Set value"))
|
||||
|
||||
async def on_input_changed(self, event: events.InputChanged):
|
||||
# Logic to handle input changes
|
||||
row_index = self.get_row_index(event.sender)
|
||||
if row_index is not None:
|
||||
item = self.items[row_index]
|
||||
item.set_target_value(event.sender.value)
|
||||
|
||||
class StatusEditorApp(App):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.adapter = AssemblyAdapter()
|
||||
self.status_items = self.adapter.get_status_items()
|
||||
|
||||
async def on_mount(self):
|
||||
self.table = StatusTable(self.status_items)
|
||||
await self.view.dock(self.table)
|
||||
|
||||
if __name__ == "__main__":
|
||||
StatusEditorApp.run()
|
||||
@@ -0,0 +1,10 @@
|
||||
# Configuration settings for the textual status editor application
|
||||
|
||||
# Layout parameters
|
||||
TABLE_WIDTH = 80
|
||||
TABLE_HEIGHT = 20
|
||||
ENTRY_WIDTH = 10
|
||||
|
||||
# Constants
|
||||
APP_TITLE = "Textual Status Editor"
|
||||
STATUS_COLLECTION_NAME = "status_collection"
|
||||
@@ -0,0 +1,52 @@
|
||||
from textual.app import App
|
||||
from textual.widgets import Static, Input, Table
|
||||
from textual.reactive import Reactive
|
||||
from textual import events
|
||||
|
||||
from .adapters.assembly_adapter import AssemblyAdapter
|
||||
|
||||
class StatusItem:
|
||||
def __init__(self, name, current_value):
|
||||
self.name = name
|
||||
self.current_value = current_value
|
||||
|
||||
def set_target_value(self, value):
|
||||
# Logic to set the target value
|
||||
self.current_value = value
|
||||
|
||||
class StatusTable(Table):
|
||||
def __init__(self, items):
|
||||
super().__init__()
|
||||
self.items = items
|
||||
self.add_column("Name", min_width=20)
|
||||
self.add_column("Current Value", min_width=20)
|
||||
self.add_column("Set Target Value", min_width=20)
|
||||
|
||||
for item in self.items:
|
||||
self.add_row(item.name, str(item.current_value), "")
|
||||
|
||||
async def on_input_changed(self, event: events.InputChanged):
|
||||
row_index = event.row_index
|
||||
column_index = event.column_index
|
||||
if column_index == 2: # Assuming the third column is for setting target values
|
||||
target_value = event.value
|
||||
self.items[row_index].set_target_value(target_value)
|
||||
self.update_row(row_index)
|
||||
|
||||
def update_row(self, row_index):
|
||||
item = self.items[row_index]
|
||||
self.update_row(row_index, item.name, str(item.current_value), "")
|
||||
|
||||
class StatusEditorApp(App):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.adapter = AssemblyAdapter()
|
||||
self.status_items = self.adapter.get_status_items()
|
||||
self.table = StatusTable(self.status_items)
|
||||
|
||||
async def on_mount(self):
|
||||
await self.view.dock(self.table)
|
||||
|
||||
if __name__ == "__main__":
|
||||
app = StatusEditorApp()
|
||||
app.run()
|
||||
+60
@@ -0,0 +1,60 @@
|
||||
from textual import events
|
||||
from textual.widget import Widget
|
||||
from textual.reactive import Reactive
|
||||
from textual.containers import Container
|
||||
from textual.widgets import Input, Table
|
||||
|
||||
|
||||
class StatusItem:
|
||||
def __init__(self, name, current_value):
|
||||
self.name = name
|
||||
self.current_value = current_value
|
||||
|
||||
def set_target_value(self, value):
|
||||
# Logic to set the target value
|
||||
self.current_value = value
|
||||
|
||||
|
||||
class StatusItemWidget(Widget):
|
||||
name: str
|
||||
current_value: Reactive[str] = Reactive("")
|
||||
|
||||
def __init__(self, status_item: StatusItem):
|
||||
super().__init__()
|
||||
self.status_item = status_item
|
||||
self.name = status_item.name
|
||||
self.current_value = str(status_item.current_value)
|
||||
|
||||
def render(self):
|
||||
return f"{self.name}: {self.current_value}"
|
||||
|
||||
async def on_input_changed(self, event: events.InputChanged):
|
||||
if event.input.value:
|
||||
self.status_item.set_target_value(event.input.value)
|
||||
self.current_value = event.input.value
|
||||
await self.refresh()
|
||||
|
||||
|
||||
class StatusItemTable(Widget):
|
||||
def __init__(self, status_items):
|
||||
super().__init__()
|
||||
self.status_items = status_items
|
||||
self.table = Table()
|
||||
|
||||
def render(self):
|
||||
self.table.clear()
|
||||
self.table.add_column("Item Name")
|
||||
self.table.add_column("Current Value")
|
||||
self.table.add_column("Set Value")
|
||||
|
||||
for item in self.status_items:
|
||||
row = [item.name, str(item.current_value), Input(placeholder="Set value")]
|
||||
self.table.add_row(*row)
|
||||
|
||||
return Container(self.table)
|
||||
|
||||
async def on_input_changed(self, event: events.InputChanged):
|
||||
for item in self.status_items:
|
||||
if event.input.value:
|
||||
item.set_target_value(event.input.value)
|
||||
await self.refresh()
|
||||
+1
@@ -0,0 +1 @@
|
||||
# This file is intentionally left blank.
|
||||
+50
@@ -0,0 +1,50 @@
|
||||
from textual.app import App
|
||||
from textual.widgets import Table, Input
|
||||
from textual.reactive import Reactive
|
||||
from textual.containers import Container
|
||||
from eco.elements.protocols import Detector # Assuming Detector is imported from the correct module
|
||||
from .assembly_adapter import AssemblyAdapter # Import the AssemblyAdapter
|
||||
|
||||
class StatusItem:
|
||||
def __init__(self, name, current_value):
|
||||
self.name = name
|
||||
self.current_value = current_value
|
||||
|
||||
def set_target_value(self, value):
|
||||
# Logic to set the target value
|
||||
pass
|
||||
|
||||
class StatusTable(App):
|
||||
status_items: Reactive[list[StatusItem]] = Reactive([])
|
||||
|
||||
def __init__(self, assembly_adapter: AssemblyAdapter):
|
||||
super().__init__()
|
||||
self.assembly_adapter = assembly_adapter
|
||||
|
||||
async def on_mount(self):
|
||||
self.status_items = await self.assembly_adapter.get_status_items()
|
||||
self.render_table()
|
||||
|
||||
def render_table(self):
|
||||
table = Table(title="Status Table")
|
||||
table.add_column("Name", justify="left")
|
||||
table.add_column("Current Value", justify="right")
|
||||
table.add_column("Set Target Value", justify="right")
|
||||
|
||||
for item in self.status_items:
|
||||
input_field = Input(placeholder="Enter value", on_submit=self.set_value(item))
|
||||
table.add_row(item.name, str(item.current_value), input_field)
|
||||
|
||||
self.set_widget(table)
|
||||
|
||||
async def set_value(self, item: StatusItem, value: str):
|
||||
item.set_target_value(value)
|
||||
await self.assembly_adapter.update_status_item(item)
|
||||
|
||||
def set_widget(self, widget):
|
||||
container = Container(widget)
|
||||
self.set_root(container)
|
||||
|
||||
if __name__ == "__main__":
|
||||
assembly_adapter = AssemblyAdapter() # Initialize your adapter here
|
||||
StatusTable(assembly_adapter).run()
|
||||
@@ -0,0 +1,56 @@
|
||||
import pytest
|
||||
from textual_status_editor.adapters.assembly_adapter import AssemblyAdapter
|
||||
from textual_status_editor.models.status_item import StatusItem
|
||||
|
||||
@pytest.fixture
|
||||
def assembly_adapter():
|
||||
return AssemblyAdapter()
|
||||
|
||||
def test_get_current_values(assembly_adapter):
|
||||
# Mock the status collection to return predefined values
|
||||
assembly_adapter.status_collection = [
|
||||
StatusItem(name="Item1", current_value=10),
|
||||
StatusItem(name="Item2", current_value=20),
|
||||
]
|
||||
|
||||
current_values = assembly_adapter.get_current_values()
|
||||
|
||||
assert current_values == {
|
||||
"Item1": 10,
|
||||
"Item2": 20,
|
||||
}
|
||||
|
||||
def test_set_target_value(assembly_adapter):
|
||||
# Mock the status item
|
||||
item = StatusItem(name="Item1", current_value=10)
|
||||
assembly_adapter.status_collection = [item]
|
||||
|
||||
assembly_adapter.set_target_value("Item1", 15)
|
||||
|
||||
assert item.target_value == 15
|
||||
|
||||
def test_set_target_value_nonexistent_item(assembly_adapter):
|
||||
# Mock the status collection
|
||||
assembly_adapter.status_collection = [
|
||||
StatusItem(name="Item1", current_value=10),
|
||||
]
|
||||
|
||||
result = assembly_adapter.set_target_value("NonexistentItem", 15)
|
||||
|
||||
assert result is False # Expecting failure when item does not exist
|
||||
|
||||
def test_update_status_item(assembly_adapter):
|
||||
item = StatusItem(name="Item1", current_value=10)
|
||||
assembly_adapter.status_collection = [item]
|
||||
|
||||
assembly_adapter.update_status_item("Item1", 20)
|
||||
|
||||
assert item.current_value == 20
|
||||
|
||||
def test_update_status_item_nonexistent(assembly_adapter):
|
||||
item = StatusItem(name="Item1", current_value=10)
|
||||
assembly_adapter.status_collection = [item]
|
||||
|
||||
result = assembly_adapter.update_status_item("NonexistentItem", 20)
|
||||
|
||||
assert result is False # Expecting failure when item does not exist
|
||||
@@ -0,0 +1,36 @@
|
||||
import pytest
|
||||
from textual_status_editor.widgets.status_table import StatusTable
|
||||
from textual_status_editor.models.status_item import StatusItem
|
||||
|
||||
@pytest.fixture
|
||||
def status_items():
|
||||
return [
|
||||
StatusItem(name="Item 1", current_value=10),
|
||||
StatusItem(name="Item 2", current_value=20),
|
||||
StatusItem(name="Item 3", current_value=30),
|
||||
]
|
||||
|
||||
def test_status_table_display(status_items):
|
||||
table = StatusTable(status_items)
|
||||
rendered = table.render()
|
||||
|
||||
assert "Item 1" in rendered
|
||||
assert "10" in rendered
|
||||
assert "Item 2" in rendered
|
||||
assert "20" in rendered
|
||||
assert "Item 3" in rendered
|
||||
assert "30" in rendered
|
||||
|
||||
def test_status_table_set_target_value(status_items):
|
||||
table = StatusTable(status_items)
|
||||
table.set_target_value("Item 1", 15)
|
||||
|
||||
assert status_items[0].current_value == 15
|
||||
|
||||
def test_status_table_invalid_target_value(status_items):
|
||||
table = StatusTable(status_items)
|
||||
table.set_target_value("Item 4", 25) # Non-existent item
|
||||
|
||||
assert status_items[0].current_value == 10 # Should remain unchanged
|
||||
assert status_items[1].current_value == 20
|
||||
assert status_items[2].current_value == 30
|
||||
Binary file not shown.
@@ -65,31 +65,25 @@ class table:
|
||||
self.status = PV(Id + ":SS_STATUS")
|
||||
|
||||
def __str__(self):
|
||||
return (
|
||||
"Prime Table position\nx: %s mm\ny: %s mm\nz: %s\npitch: %s mrad\nyaw: %s mrad\nmode SP: %s \nstatus: %s"
|
||||
% (
|
||||
self.x.wm(),
|
||||
self.y.wm(),
|
||||
self.z.wm(),
|
||||
self.pitch.wm(),
|
||||
self.yaw.wm(),
|
||||
self.modeSP.get(as_string=True),
|
||||
self.status.get(),
|
||||
)
|
||||
return "Prime Table position\nx: %s mm\ny: %s mm\nz: %s\npitch: %s mrad\nyaw: %s mrad\nmode SP: %s \nstatus: %s" % (
|
||||
self.x.wm(),
|
||||
self.y.wm(),
|
||||
self.z.wm(),
|
||||
self.pitch.wm(),
|
||||
self.yaw.wm(),
|
||||
self.modeSP.get(as_string=True),
|
||||
self.status.get(),
|
||||
)
|
||||
|
||||
def __repr__(self):
|
||||
return (
|
||||
"{'x': %s, 'y': %s,'z': %s,'pitch': %s, 'yaw': %s, 'mode set point': %s,'status': %s}"
|
||||
% (
|
||||
self.x,
|
||||
self.y,
|
||||
self.z,
|
||||
self.pitch,
|
||||
self.yaw,
|
||||
self.modeSP.get(as_string=True),
|
||||
self.status.get(),
|
||||
)
|
||||
return "{'x': %s, 'y': %s,'z': %s,'pitch': %s, 'yaw': %s, 'mode set point': %s,'status': %s}" % (
|
||||
self.x,
|
||||
self.y,
|
||||
self.z,
|
||||
self.pitch,
|
||||
self.yaw,
|
||||
self.modeSP.get(as_string=True),
|
||||
self.status.get(),
|
||||
)
|
||||
|
||||
|
||||
|
||||
Executable
+108
@@ -0,0 +1,108 @@
|
||||
from ..devices_general.motors import MotorRecord
|
||||
from ..devices_general.detectors import CameraCA, CameraBS
|
||||
from ..epics.adjustable import AdjustablePv
|
||||
from ..aliases import Alias, append_object_to_object
|
||||
|
||||
# from ..devices_general.epics_wrappers import EnumSelector
|
||||
|
||||
|
||||
def addMotorRecordToSelf(self, Id=None, name=None):
|
||||
self.__dict__[name] = MotorRecord(Id, name=name)
|
||||
self.alias.append(self.__dict__[name].alias)
|
||||
|
||||
|
||||
def addPvRecordToSelf(
|
||||
self, name=None, pvsetname=None, pvreadbackname=None, accuracy=None
|
||||
):
|
||||
self.__dict__[name] = AdjustablePv(
|
||||
name=name, pvsetname=pvsetname, pvreadbackname=pvreadbackname, accuracy=accuracy
|
||||
)
|
||||
self.alias.append(self.__dict__[name].alias)
|
||||
|
||||
|
||||
class Sigma:
|
||||
def __init__(
|
||||
self,
|
||||
camera_pv=None,
|
||||
zoomstage_pvs={},
|
||||
bshost=None,
|
||||
bsport=None,
|
||||
name=None,
|
||||
):
|
||||
self.alias = Alias(name)
|
||||
|
||||
# zoomstage_pvs={
|
||||
# "set_value": "SARES20-OPSI:MOT_SP",
|
||||
# "readback": "SEARES20-OPSI:MOT_RB",
|
||||
# }
|
||||
self.name = name
|
||||
|
||||
append_object_to_object
|
||||
if zoomstage_pvs:
|
||||
append_object_to_object(
|
||||
self,
|
||||
AdjustablePv,
|
||||
name="zoom",
|
||||
pvsetname=zoomstage_pvs["set_value"],
|
||||
pvreadbackname=zoomstage_pvs["readback"],
|
||||
)
|
||||
|
||||
try:
|
||||
self.cam = CameraCA(camera_pv)
|
||||
except:
|
||||
print("Sigma Cam not found")
|
||||
pass
|
||||
|
||||
if bshost:
|
||||
self.camBS = CameraBS(host=bshost, port=bsport)
|
||||
|
||||
def get_adjustable_positions_str(self):
|
||||
ostr = "*****Qioptic motor positions******\n"
|
||||
|
||||
for tkey, item in self.__dict__.items():
|
||||
if hasattr(item, "get_current_value"):
|
||||
pos = item.get_current_value()
|
||||
ostr += " " + tkey.ljust(17) + " : % 14g\n" % pos
|
||||
return ostr
|
||||
|
||||
def __repr__(self):
|
||||
return self.get_adjustable_positions_str()
|
||||
|
||||
|
||||
class Qioptiq:
|
||||
def __init__(
|
||||
self, camera_pv=None, zoomstage_pv=None, bshost=None, bsport=None, name=None
|
||||
):
|
||||
self.alias = Alias(name)
|
||||
|
||||
self.name = name
|
||||
|
||||
if zoomstage_pv:
|
||||
append_object_to_object(self, MotorRecord, zoomstage_pv, name="zoom")
|
||||
|
||||
try:
|
||||
addMotorRecordToSelf(self, Id="SARES20-EXP:MOT_QIOPT_F", name="focus")
|
||||
|
||||
except:
|
||||
print("Qioptic focus motor not found")
|
||||
pass
|
||||
try:
|
||||
self.cam = CameraCA(camera_pv)
|
||||
except:
|
||||
print("Qioptic Cam not found")
|
||||
pass
|
||||
|
||||
if bshost:
|
||||
self.camBS = CameraBS(host=bshost, port=bsport)
|
||||
|
||||
def get_adjustable_positions_str(self):
|
||||
ostr = "*****Qioptic motor positions******\n"
|
||||
|
||||
for tkey, item in self.__dict__.items():
|
||||
if hasattr(item, "get_current_value"):
|
||||
pos = item.get_current_value()
|
||||
ostr += " " + tkey.ljust(17) + " : % 14g\n" % pos
|
||||
return ostr
|
||||
|
||||
def __repr__(self):
|
||||
return self.get_adjustable_positions_str()
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,64 +0,0 @@
|
||||
import sys
|
||||
|
||||
sys.path.append("..")
|
||||
from ..devices_general.motors import MotorRecord
|
||||
from epics import PV
|
||||
|
||||
|
||||
class GPS:
|
||||
def __init__(self, Id, alias_namespace=None):
|
||||
self.Id = Id
|
||||
|
||||
### motors heavy load gps table ###
|
||||
self.xhl = MotorRecord(Id + ":MOT_TBL_TX")
|
||||
self.zhl = MotorRecord(Id + ":MOT_TBL_TZ")
|
||||
self.yhl = MotorRecord(Id + ":MOT_TBL_TY")
|
||||
self.th = MotorRecord(Id + ":MOT_MY_RYTH")
|
||||
try:
|
||||
self.rxhl = MotorRecord(Id + ":MOT_TBL_RX")
|
||||
except:
|
||||
print("GPS.pitch not found")
|
||||
pass
|
||||
try:
|
||||
self.ryhl = MotorRecord(Id + ":MOT_TBL_RY")
|
||||
except:
|
||||
print("GPS.roll not found")
|
||||
pass
|
||||
|
||||
### motors heavy load gonio base ###
|
||||
self.xmu = MotorRecord(Id + ":MOT_HEX_TX")
|
||||
self.mu = MotorRecord(Id + ":MOT_HEX_RX")
|
||||
self.tth = MotorRecord(Id + ":MOT_NY_RY2TH")
|
||||
self.xbase = MotorRecord(Id + ":MOT_TX")
|
||||
self.ybase = MotorRecord(Id + ":MOT_TY")
|
||||
|
||||
self.hex_x = PV("SARES20-HEX_PI:POSI-X")
|
||||
self.hex_y = PV("SARES20-HEX_PI:POSI-Y")
|
||||
self.hex_z = PV("SARES20-HEX_PI:POSI-Z")
|
||||
self.hex_u = PV("SARES20-HEX_PI:POSI-U")
|
||||
self.hex_v = PV("SARES20-HEX_PI:POSI-V")
|
||||
self.hex_w = PV("SARES20-HEX_PI:POSI-W")
|
||||
|
||||
def __repr__(self):
|
||||
s = "**Heavy Load**\n"
|
||||
motors = "xmu mu tth xbase ybase".split()
|
||||
for motor in motors:
|
||||
s += " - %s %.4f\n" % (motor, getattr(self, motor).wm())
|
||||
|
||||
s += " - HLX %.4f\n" % (self.xhl.wm())
|
||||
s += " - HLY %.4f\n" % (self.yhl.wm())
|
||||
s += " - HLZ %.4f\n" % (self.zhl.wm())
|
||||
s += " - HLTheta %.4f\n" % (self.th.wm())
|
||||
s += "\n"
|
||||
|
||||
s += "**Gonio**\n"
|
||||
motors = "xmu mu tth xbase ybase".split()
|
||||
for motor in motors:
|
||||
s += " - %s %.4f\n" % (motor, getattr(self, motor).wm())
|
||||
s += "\n"
|
||||
|
||||
s += "**Hexapod**\n"
|
||||
motors = "x y z u v w".split()
|
||||
for motor in motors:
|
||||
s += " - hex_%s %.4f\n" % (motor, getattr(self, "hex_" + motor).get())
|
||||
return s
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user