Compare commits
482 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 8b09b887f8 | |||
| 69d63c4e11 | |||
| 01dd256858 | |||
| 0e01193240 | |||
| a590027c44 | |||
| 558bbfded9 | |||
| bee3f5615a | |||
| f0d83e47d8 | |||
| 7e5b47d45f | |||
| 97e52fd27c | |||
| 904db04447 | |||
| 7a5694fbe2 | |||
| b3d243d831 | |||
| 7bfc6b3cb8 | |||
| e77c48ace0 | |||
| d5d9d70713 | |||
| 1777e4b7b1 | |||
| 1990f906a3 | |||
| c2f4df30eb | |||
| 1ded495e4a | |||
| bdf0d4f8ae | |||
| ef769773f0 | |||
| fc44f5597f | |||
| ff75c9ecbf | |||
| bfea1263ae | |||
| 2194085e8e | |||
| 7adf4bf452 | |||
| 00e662a27f | |||
| d7b45cd163 | |||
| 730aa61789 | |||
| ccd15f50e7 | |||
| 44a4d921af | |||
| 5dfe929da5 | |||
| 10acd4a188 | |||
| 388748c995 | |||
| 2fce39c381 | |||
| 365f0a2374 | |||
| 05324a8966 | |||
| 2c5d5da773 | |||
| 6f599d569c | |||
|
|
f725a0f9d4 | ||
| abed998e43 | |||
| 29004f7ac5 | |||
| 5436e34179 | |||
| f3fa2ac53a | |||
|
|
2040d7d021 | ||
| 50fc0fd038 | |||
| 0df7fe4013 | |||
| b2eb4fb663 | |||
| 0706ffa66d | |||
| 27acfa46ab | |||
| 3295be396c | |||
| 75c00f8c07 | |||
| 2d01ca60c8 | |||
|
|
bd2bbea1c8 | ||
|
|
9cdfac7450 | ||
| 0466e5b10e | |||
| 0e673a3ed0 | |||
| f7e04231b6 | |||
| 23f724e110 | |||
| 56204b2ece | |||
| 30495db0c2 | |||
| 7376b39c27 | |||
| 6d2a53acbf | |||
| 3e3c1b51af | |||
| 78dfedfe3d | |||
| 4ad77798c4 | |||
| b3cfa85478 | |||
| 53bea83f3e | |||
| e9a61be0a4 | |||
| 21b8fd6518 | |||
|
|
3bf1a838d4 | ||
|
|
a772c00d91 | ||
| 3fca02e549 | |||
|
|
a9636572bb | ||
| 7232bc7e1f | |||
|
|
623a4f4ef7 | ||
|
|
febb08dafb | ||
|
|
948411e041 | ||
|
|
9f35a7a7b9 | ||
|
|
78cc236f55 | ||
|
|
ed1d693008 | ||
|
|
fb883f64de | ||
| 7ab062a449 | |||
|
|
16d5749d9e | ||
|
|
e0cff7f81c | ||
|
|
dbb4ed6e97 | ||
| ae11d0b33f | |||
|
|
4a332532e7 | ||
|
|
2ece560ad7 | ||
|
|
512f95eafd | ||
|
|
ba59bd5498 | ||
| f5e46d82fb | |||
| ccc976984d | |||
|
|
d1930868aa | ||
|
|
51fe236a63 | ||
|
|
29ec5c16aa | ||
|
|
11ea75bedf | ||
|
|
323581075b | ||
|
|
ea1d08e669 | ||
|
|
baa0946e9d | ||
|
|
1c69abfd45 | ||
|
|
d4c6a4d6d4 | ||
|
|
79cbf87da8 | ||
|
|
e8a7825f6e | ||
|
|
fa75fe9da8 | ||
|
|
b7ededef24 | ||
|
|
2101bb727b | ||
|
|
1d609deab6 | ||
|
|
63ec5b6cb4 | ||
|
|
035472a707 | ||
| 2a99aa73e0 | |||
| cd20011437 | |||
| 28603aed5d | |||
|
|
d50718a121 | ||
|
|
d4668d904f | ||
|
|
5d74042fc9 | ||
|
|
bd4519db4c | ||
|
|
084529d5fb | ||
|
|
3bbde105f8 | ||
|
|
831d74b993 | ||
| ffe5f52790 | |||
|
|
6ac25c7504 | ||
|
|
e1458471a8 | ||
|
|
c550721e2d | ||
|
|
90d49355fa | ||
| 116ad54ac1 | |||
| 8660d83a67 | |||
|
|
235e0f5820 | ||
|
|
a7b763713b | ||
|
|
4c77b0b923 | ||
|
|
3cd63119a3 | ||
|
|
19f6e8ed43 | ||
|
|
6e56c19b77 | ||
|
|
359fb4e69b | ||
|
|
6479134e6a | ||
|
|
63bb34cf51 | ||
|
|
dacae94408 | ||
|
|
f9f0eda937 | ||
|
|
768d758efa | ||
|
|
1b40b77fd4 | ||
|
|
54782bdbad | ||
|
|
fb9e713043 | ||
|
|
bcfb4ae474 | ||
|
|
e30d2544d2 | ||
|
|
48de238dbc | ||
|
|
1d71445440 | ||
|
|
9ed1f29e68 | ||
|
|
ba1d4c3a9e | ||
|
|
c5bb0c37b1 | ||
|
|
1d49577f9a | ||
|
|
f055ae20c2 | ||
|
|
bb5459243c | ||
|
|
c4ad2ec83d | ||
|
|
abee1cc463 | ||
| e0e90c4000 | |||
| 81002c2568 | |||
| 2598e94cb7 | |||
| 67611d6d23 | |||
|
|
3c7c883538 | ||
|
|
22d016bd59 | ||
| 185b47a471 | |||
| 7dc4cf7029 | |||
| 924a9a2c7f | |||
| 36dd7598cc | |||
| 0641bc22c3 | |||
|
|
3d27a805d4 | ||
| fa007e4f1c | |||
| e74192d45e | |||
| 516e0c7a59 | |||
|
|
a53eebe2f3 | ||
|
|
91d50ae703 | ||
|
|
878f4774e4 | ||
|
|
d77a7da606 | ||
| d3f1fdce4a | |||
| d357a9695b | |||
|
|
09e7f9e532 | ||
|
|
cdab83368a | ||
|
|
216f96cc04 | ||
|
|
46bdd6dd99 | ||
| cb9b0f0c8b | |||
| 23b60703dc | |||
| d5448118e4 | |||
| 81a345f61d | |||
| 4c4b565ad0 | |||
| 5d5c1cfcf6 | |||
|
|
2962b8aef5 | ||
|
|
c1cfea4cc2 | ||
| e416878990 | |||
|
|
1375072f1e | ||
|
|
ee61224fc6 | ||
|
|
c496a42830 | ||
|
|
9aba3db8a6 | ||
| 08f889e6a3 | |||
| 93be8c9ba1 | |||
| 45871bd3ca | |||
|
|
ee5a945e11 | ||
|
|
ec58bd4d58 | ||
| 63f7a31480 | |||
| 65cc7f7d32 | |||
| 6cddb2d0c4 | |||
| 38a2151771 | |||
| 96e4d7bb75 | |||
| 7fdd0204d2 | |||
| b0518e7bd4 | |||
|
|
d2d45e8253 | ||
|
|
5bfe97a1a6 | ||
|
|
6bb6071561 | ||
|
|
72dc30b8df | ||
| a43b6ae65b | |||
| d2862c9cb2 | |||
| 376def0410 | |||
| 9c286943f8 | |||
| b4cfd7cd4f | |||
| adabf5c4b1 | |||
|
|
dbacac71f9 | ||
| c5c479f424 | |||
|
|
2de9a3ab43 | ||
|
|
96b56ca411 | ||
|
|
ca236d2064 | ||
|
|
748c31c774 | ||
|
|
acbd424ae1 | ||
| c0b8699ccf | |||
| 677a3bddae | |||
| c8ed47df48 | |||
| 39fd6e0be3 | |||
|
|
69bb896ebb | ||
|
|
757e96e7c0 | ||
| 8d26ab4fe2 | |||
|
|
a3651f71f2 | ||
|
|
48c9853b37 | ||
|
|
c68529d42d | ||
| 4b5e02c957 | |||
|
|
7d85d85963 | ||
|
|
d7ab35a461 | ||
| dd67c48c9e | |||
| 4f86fc5c3c | |||
| 4db1421b52 | |||
| 81279d3b61 | |||
| ebb076a485 | |||
| 98f9b2b8ad | |||
| e416a657fd | |||
| 19c0bcdf14 | |||
| 0d2c677de3 | |||
| c7c6d60a99 | |||
| 20b6d3e953 | |||
|
|
d946efa629 | ||
| 3b95277152 | |||
| 2e74172869 | |||
| 501b781144 | |||
| 2272aecf0f | |||
| 3569876bfc | |||
|
|
4d3d78ad3c | ||
|
|
5b95a0ccca | ||
|
|
8232b106a7 | ||
|
|
bc652f00bc | ||
|
|
e67a46cd01 | ||
|
|
a974f85a41 | ||
|
|
819e2c5381 | ||
|
|
c6796a1199 | ||
|
|
2a769bbb40 | ||
| 7ee65f47ee | |||
| 285df89d4d | |||
| 78e8b9127f | |||
| 8647814220 | |||
| 9935546efd | |||
| 830b5b3be0 | |||
| fd2d05d7b5 | |||
|
|
01348e0c7c | ||
| fdf82c3989 | |||
| 9ece4f797b | |||
| b47e5d270c | |||
|
|
cffe301c26 | ||
|
|
83d11da5ae | ||
| 4c911d58b7 | |||
|
|
d079bd15e7 | ||
|
|
0c45755170 | ||
|
|
b91905fc04 | ||
| 5727ecbc7f | |||
| 27b8deeb2e | |||
| 98c8600117 | |||
| f7129f6a00 | |||
| 2feff36077 | |||
| 04b6ad09ba | |||
| 06175da887 | |||
| 9306596cb2 | |||
| 666ebf5719 | |||
|
|
3783210b81 | ||
|
|
18f2fa05c9 | ||
|
|
b0e23466cc | ||
|
|
6802e8f9d3 | ||
|
|
ff9f7239cf | ||
|
|
15ca3c984d | ||
| 68d74347c6 | |||
|
|
a11b8cf2d1 | ||
|
|
4a80b4e45d | ||
|
|
d3c9666593 | ||
| fa4349784c | |||
|
|
815e91ae26 | ||
|
|
3b92688b84 | ||
|
|
37337ac959 | ||
|
|
af0f9b39b5 | ||
| a643093934 | |||
| d8c0a1c507 | |||
| bda0afe042 | |||
| 4b307fe92a | |||
| 04d7d0249a | |||
|
|
386e5d241e | ||
| ccb33b81b3 | |||
| 37e18a8a5b | |||
| dfe0038494 | |||
|
|
259970249a | ||
| 3a3ca0372b | |||
| bc2b860f31 | |||
|
|
e20986c65b | ||
| 0153975d32 | |||
| e4bb28aa09 | |||
| 761b7b4cf8 | |||
| ed61a2f7ce | |||
| f48a742bb5 | |||
| 6d7fbda286 | |||
|
|
5283b06cb7 | ||
|
|
f491625dd1 | ||
|
|
a49d64953c | ||
|
|
507a941459 | ||
|
|
02c20f5296 | ||
| 54c0a6062a | |||
| 140a3e9e8a | |||
| 3cea8580ae | |||
| 65ff0c0fc3 | |||
| 21427ca765 | |||
| 3929a37e93 | |||
| ea5cdbbe44 | |||
|
|
827d27ed59 | ||
| 5f4e5d22b7 | |||
|
|
97083a4db5 | ||
| 1d97a422c2 | |||
|
|
4b2500a9d7 | ||
|
|
9e7a0a8c1d | ||
|
|
805df680da | ||
|
|
0bf4b6cdf2 | ||
|
|
32b6d6ffc2 | ||
|
|
bbddb6c572 | ||
|
|
de1579c34a | ||
|
|
34183453e0 | ||
|
|
c114dbab26 | ||
|
|
1dcd1db4eb | ||
| 08071acbf9 | |||
|
|
4ad9669531 | ||
|
|
33f9f39f3c | ||
|
|
59131c7456 | ||
|
|
5245779fd9 | ||
|
|
fed071d74a | ||
|
|
d79a24221b | ||
| bf83f9a185 | |||
|
|
c1d5b77e9c | ||
|
|
9791f15807 | ||
|
|
2daa8d49bf | ||
|
|
1041fd58f3 | ||
|
|
56c4e221bc | ||
|
|
d654708060 | ||
|
|
2086259bb4 | ||
|
|
205789a51d | ||
|
|
6bb3104134 | ||
|
|
b6204d5fbc | ||
|
|
bd7ecbe167 | ||
|
|
78ab28c6d3 | ||
|
|
013d20e8af | ||
|
|
2a4b94aa3b | ||
| bf809b4b9a | |||
| 7186759435 | |||
|
|
2722a0a40f | ||
|
|
df7dc2e612 | ||
| fb3a31761c | |||
| 80297963d2 | |||
| ace85026e0 | |||
| a15cfc87bf | |||
| 670fc39821 | |||
| b80d39773b | |||
|
|
c41eefba51 | ||
|
|
4ab33fc0fc | ||
|
|
c6d588c881 | ||
| e0b2747821 | |||
| 8f9b2902d9 | |||
|
|
0ce1e7d8ca | ||
|
|
5a182acf2d | ||
| 0560668b91 | |||
|
|
20506a8393 | ||
|
|
c4de1d4db6 | ||
|
|
80a4a123b0 | ||
|
|
7be0ed5c65 | ||
|
|
e186442084 | ||
|
|
44f171aa72 | ||
| a72038e18c | |||
|
|
81f9e89402 | ||
|
|
aa43254b55 | ||
|
|
bc7975f4e5 | ||
|
|
5983a4ac9d | ||
|
|
3afd0b07ed | ||
|
|
af57d92e99 | ||
|
|
16a3b1dc35 | ||
|
|
1b38a989d8 | ||
|
|
e820e1a822 | ||
| f6ee2f8d27 | |||
| be24ea882b | |||
|
|
a8711f9706 | ||
|
|
4ca3d5ba22 | ||
|
|
8c916eb7aa | ||
| 5a23c5af37 | |||
|
|
d48ab55909 | ||
|
|
47b1268ceb | ||
|
|
e4a60863b5 | ||
|
|
a9257b2a0c | ||
|
|
9e6afffbec | ||
|
|
33f4bb98d9 | ||
|
|
d449e715c5 | ||
|
|
c0dca1e753 | ||
| 67d72245ff | |||
|
|
4f54423be5 | ||
|
|
25df2cffd1 | ||
| 4fe5ddeaa7 | |||
| 731ecbeb32 | |||
| 9796a25fe5 | |||
| 2e9ed3ddea | |||
|
|
4b42d72bc6 | ||
|
|
25618a6264 | ||
| bb82179191 | |||
|
|
578a412de1 | ||
|
|
7428634297 | ||
|
|
aa7ba84425 | ||
| a3a963721a | |||
|
|
367ecda9d6 | ||
| 549980d9cd | |||
|
|
0bbf3337b2 | ||
|
|
43b5f707e8 | ||
|
|
612695b644 | ||
|
|
cb8366105d | ||
|
|
5de9ff2901 | ||
|
|
35fce652b7 | ||
| eb46733c86 | |||
|
|
318f33961a | ||
|
|
d641784104 | ||
|
|
97a9e6624b | ||
|
|
72a28b198b | ||
|
|
86f97f0492 | ||
|
|
9a91f28413 | ||
|
|
9c17f09bdf | ||
|
|
7465824423 | ||
|
|
a36d875fae | ||
|
|
12f21996e4 | ||
|
|
879267aad1 | ||
|
|
7081976612 | ||
|
|
ea98023c28 | ||
|
|
167294aad8 | ||
|
|
716fd0df2c | ||
|
|
d168384d0c | ||
|
|
56454b9d9a | ||
| 4000371b97 | |||
| 1b18337d2a | |||
| 55077e2417 | |||
|
|
127f1712ee | ||
| c4fbd8a7bf | |||
|
|
4a7294679b | ||
|
|
5c3d09288b | ||
| 365476256e | |||
| a2cd6051f2 | |||
| 5b060d1b1c | |||
| 10a61aa760 | |||
| 60f6c2dda5 | |||
| a72c2b685d | |||
| 6b751f845f | |||
| 12ef37504a | |||
|
|
3c1f4b0bc1 | ||
| ada9e53a4d | |||
| 66d363cc07 | |||
| ddd16f4ed4 | |||
| a4ebcb9bb7 | |||
| 878bb6f892 | |||
| e571abdb18 | |||
|
|
ec16ee7e8b | ||
|
|
401447ffd6 | ||
| 37ca162ae2 |
10
.gitignore
vendored
10
.gitignore
vendored
@@ -1,13 +1,15 @@
|
||||
frappydemo.PID
|
||||
log/*
|
||||
html/*
|
||||
*.pyc
|
||||
pid/*
|
||||
|
||||
# ide
|
||||
# ide
|
||||
.idea
|
||||
|
||||
RELEASE-VERSION
|
||||
build
|
||||
*.egg-info
|
||||
|
||||
# Sphinx
|
||||
doc/_build
|
||||
@@ -21,4 +23,8 @@ doc/_build
|
||||
._*
|
||||
|
||||
# pyinstaller
|
||||
dist/
|
||||
dist/
|
||||
|
||||
*.cfg
|
||||
*.bk.tnt
|
||||
*.tps
|
||||
8
.isort.cfg
Normal file
8
.isort.cfg
Normal file
@@ -0,0 +1,8 @@
|
||||
[settings]
|
||||
multi_line_output=2
|
||||
combine_as_imports=True
|
||||
|
||||
known_qt=frappy.gui.qt
|
||||
known_core=frappy
|
||||
|
||||
sections=FUTURE,STDLIB,QT,THIRDPARTY,CORE,FIRSTPARTY,LOCALFOLDER
|
||||
10
.pylintrc
10
.pylintrc
@@ -9,7 +9,7 @@
|
||||
|
||||
# Add <file or directory> to the black list. It should be a base name, not a
|
||||
# path. You may set this option multiple times.
|
||||
ignore = .git
|
||||
ignore = .git,resources_qt5.py,resources_qt6.py
|
||||
|
||||
# Pickle collected data for later comparisons.
|
||||
persistent=yes
|
||||
@@ -40,6 +40,7 @@ disable=missing-docstring
|
||||
,locally-disabled
|
||||
,fixme
|
||||
,no-member
|
||||
,not-callable
|
||||
,wrong-import-position
|
||||
,ungrouped-imports
|
||||
,import-self
|
||||
@@ -53,6 +54,8 @@ disable=missing-docstring
|
||||
,unidiomatic-typecheck
|
||||
,undefined-loop-variable
|
||||
,consider-using-f-string
|
||||
,use-dict-literal
|
||||
|
||||
[REPORTS]
|
||||
|
||||
# Set the output format. Available formats are text, parseable, colorized, msvs
|
||||
@@ -199,7 +202,10 @@ max-branches=50
|
||||
max-statements=150
|
||||
|
||||
# Maximum number of parents for a class (see R0901).
|
||||
max-parents=15
|
||||
max-parents=20
|
||||
|
||||
# Maximum number of positional arguments
|
||||
max-positional-arguments=10
|
||||
|
||||
# Maximum number of attributes for a class (see R0902).
|
||||
max-attributes=50
|
||||
|
||||
33
README.md
Normal file
33
README.md
Normal file
@@ -0,0 +1,33 @@
|
||||
Frappy: Framework for programming secnodes in Python
|
||||
====================================================
|
||||
|
||||
Frappy is a Python-framework for writing [SECoP](https://github.com/SampleEnvironment/SECoP) servers (called SECNodes or Nodes) and Clients.
|
||||
It comes with its own Graphical client and a collection of example Nodes.
|
||||
It is able to use TCP and Serial connections.
|
||||
|
||||
To get started, look at the provided demo, the provided examples, or have a look
|
||||
at the INTRODUCTION section.
|
||||
|
||||
Main development is done
|
||||
[here](https://forge.frm2.tum.de/review/q/project:secop%252Ffrappy)
|
||||
and a readonly GitHub-mirror for easier access is available
|
||||
[here](https://github.com/SampleEnvironment/frappy).
|
||||
|
||||
Requirements
|
||||
------------
|
||||
|
||||
See `requirements.txt`.
|
||||
|
||||
Demo
|
||||
----
|
||||
|
||||
Use the following command after installing the dependencies:
|
||||
|
||||
```
|
||||
$ make demo
|
||||
```
|
||||
|
||||
Documentation
|
||||
-------------
|
||||
|
||||
[See here for documentation of Frappy](https://forge.frm2.tum.de/public/doc/frappy/html/)
|
||||
@@ -1,7 +1,7 @@
|
||||
#!/usr/bin/env python
|
||||
# pylint: disable=invalid-name
|
||||
# -*- coding: utf-8 -*-
|
||||
# *****************************************************************************
|
||||
# Copyright (c) 2015-2024 by the authors, see LICENSE
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify it under
|
||||
# the terms of the GNU General Public License as published by the Free Software
|
||||
@@ -29,6 +29,9 @@ from os import path
|
||||
# Add import path for inplace usage
|
||||
sys.path.insert(0, path.abspath(path.join(path.dirname(__file__), '..')))
|
||||
|
||||
import logging
|
||||
from mlzlog import ColoredConsoleHandler
|
||||
|
||||
from frappy.gui.qt import QApplication
|
||||
from frappy.gui.cfg_editor.mainwindow import MainWindow
|
||||
|
||||
@@ -38,9 +41,13 @@ def main(argv=None):
|
||||
parser.add_argument('-f', '--file', help='Configuration file to open.')
|
||||
args = parser.parse_args()
|
||||
app = QApplication(argv)
|
||||
window = MainWindow(args.file)
|
||||
logger = logging.getLogger('gui')
|
||||
console = ColoredConsoleHandler()
|
||||
console.setLevel(logging.INFO)
|
||||
logger.addHandler(console)
|
||||
window = MainWindow(args.file, log=logger)
|
||||
window.show()
|
||||
return app.exec_()
|
||||
return app.exec()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
66
bin/frappy-cli
Executable file
66
bin/frappy-cli
Executable file
@@ -0,0 +1,66 @@
|
||||
#!/usr/bin/env python3
|
||||
# *****************************************************************************
|
||||
# Copyright (c) 2015-2024 by the authors, see LICENSE
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify it under
|
||||
# the terms of the GNU General Public License as published by the Free Software
|
||||
# Foundation; either version 2 of the License, or (at your option) any later
|
||||
# version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
|
||||
# details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License along with
|
||||
# this program; if not, write to the Free Software Foundation, Inc.,
|
||||
# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
#
|
||||
# Module authors:
|
||||
# Alexander Lenz <alexander.lenz@frm2.tum.de>
|
||||
# Markus Zolliker <markus.zolliker@psi.ch>
|
||||
#
|
||||
# *****************************************************************************
|
||||
|
||||
import sys
|
||||
import argparse
|
||||
from pathlib import Path
|
||||
|
||||
# Add import path for inplace usage
|
||||
sys.path.insert(0, str(Path(__file__).absolute().parents[1]))
|
||||
|
||||
from frappy.client.interactive import init, run, clientenv, interact
|
||||
|
||||
|
||||
def parseArgv(argv):
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument('-i', '--include',
|
||||
help='file to execute after connecting to the clients', metavar='file',
|
||||
type=Path, action='append', default=[])
|
||||
parser.add_argument('-o', '--only-execute',
|
||||
help='Do not go into interactive mode after executing files. \
|
||||
Has no effect without --include.', action='store_true')
|
||||
parser.add_argument('node',
|
||||
help='Nodes the client should connect to.\n', metavar='host:port',
|
||||
nargs='*', type=str, default=[])
|
||||
return parser.parse_args(argv)
|
||||
|
||||
|
||||
args = parseArgv(sys.argv[1:])
|
||||
|
||||
success = init(*args.node)
|
||||
|
||||
run_error = ''
|
||||
file_success = False
|
||||
try:
|
||||
for file in args.include:
|
||||
run(file)
|
||||
file_success = True
|
||||
except Exception as e:
|
||||
run_error = f'\n{clientenv.short_traceback()}'
|
||||
|
||||
if success:
|
||||
if args.include and file_success and args.only_execute:
|
||||
print('skipping interactive mode')
|
||||
exit()
|
||||
interact(run_error)
|
||||
@@ -1,8 +1,7 @@
|
||||
#!/usr/bin/env python3
|
||||
# pylint: disable=invalid-name
|
||||
# -*- coding: utf-8 -*-
|
||||
# *****************************************************************************
|
||||
# Copyright (c) 2015-2016 by the authors, see LICENSE
|
||||
# Copyright (c) 2015-2024 by the authors, see LICENSE
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify it under
|
||||
# the terms of the GNU General Public License as published by the Free Software
|
||||
@@ -27,10 +26,10 @@ from __future__ import print_function
|
||||
|
||||
import sys
|
||||
import argparse
|
||||
from os import path
|
||||
from pathlib import Path
|
||||
|
||||
# Add import path for inplace usage
|
||||
sys.path.insert(0, path.abspath(path.join(path.dirname(__file__), '..')))
|
||||
sys.path.insert(0, str(Path(__file__).absolute().parents[1]))
|
||||
|
||||
import logging
|
||||
from mlzlog import ColoredConsoleHandler
|
||||
@@ -47,9 +46,12 @@ def parseArgv(argv):
|
||||
loggroup.add_argument('-q', '--quiet',
|
||||
help='Supress everything but errors',
|
||||
action='store_true', default=False)
|
||||
parser.add_argument('-D', '--detailed',
|
||||
help='Start in detailed mode',
|
||||
action='store_true', default=False)
|
||||
parser.add_argument('node',
|
||||
help='Nodes the Gui should connect to.\n', metavar='host[:port]',
|
||||
nargs='*', type=str, default=['localhost:10767'])
|
||||
help='Nodes the GUI should connect to.\n', metavar='host[:port]',
|
||||
nargs='*', type=str, default=[])
|
||||
return parser.parse_args(argv)
|
||||
|
||||
|
||||
@@ -62,16 +64,18 @@ def main(argv=None):
|
||||
loglevel = logging.DEBUG if args.debug else (logging.ERROR if args.quiet else logging.INFO)
|
||||
logger = logging.getLogger('gui')
|
||||
logger.setLevel(logging.DEBUG)
|
||||
console = ColoredConsoleHandler()
|
||||
console.setLevel(loglevel)
|
||||
logger.addHandler(console)
|
||||
if sys.stdout is not None:
|
||||
console = ColoredConsoleHandler()
|
||||
console.setLevel(loglevel)
|
||||
logger.addHandler(console)
|
||||
|
||||
app = QApplication(argv)
|
||||
|
||||
win = MainWindow(args.node, logger)
|
||||
win = MainWindow(args, logger)
|
||||
app.aboutToQuit.connect(win._onQuit)
|
||||
win.show()
|
||||
|
||||
return app.exec_()
|
||||
return app.exec()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
42
bin/frappy-play
Executable file
42
bin/frappy-play
Executable file
@@ -0,0 +1,42 @@
|
||||
#!/usr/bin/env python3
|
||||
# *****************************************************************************
|
||||
# Copyright (c) 2015-2024 by the authors, see LICENSE
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify it under
|
||||
# the terms of the GNU General Public License as published by the Free Software
|
||||
# Foundation; either version 2 of the License, or (at your option) any later
|
||||
# version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
|
||||
# details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License along with
|
||||
# this program; if not, write to the Free Software Foundation, Inc.,
|
||||
# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
#
|
||||
# Module authors:
|
||||
# Markus Zolliker <markus.zolliker@psi.ch>
|
||||
#
|
||||
# *****************************************************************************
|
||||
|
||||
import sys
|
||||
from pathlib import Path
|
||||
from frappy.lib import generalConfig
|
||||
from frappy.logging import logger
|
||||
|
||||
# Add import path for inplace usage
|
||||
sys.path.insert(0, str(Path(__file__).absolute().parents[1]))
|
||||
|
||||
from frappy.client.interactive import Console
|
||||
from frappy.playground import play, USAGE
|
||||
|
||||
generalConfig.init()
|
||||
logger.init()
|
||||
if len(sys.argv) > 1:
|
||||
play(sys.argv[1])
|
||||
else:
|
||||
print(USAGE)
|
||||
|
||||
Console('play', sys.modules['__main__'].__dict__)
|
||||
128
bin/frappy-scan
Executable file
128
bin/frappy-scan
Executable file
@@ -0,0 +1,128 @@
|
||||
#!/usr/bin/env python3
|
||||
# *****************************************************************************
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify it under
|
||||
# the terms of the GNU General Public License as published by the Free Software
|
||||
# Foundation; either version 2 of the License, or (at your option) any later
|
||||
# version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
|
||||
# details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License along with
|
||||
# this program; if not, write to the Free Software Foundation, Inc.,
|
||||
# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
#
|
||||
# Module authors:
|
||||
# Alexander Zaft <a.zaft@fz-juelich.de>
|
||||
#
|
||||
# *****************************************************************************
|
||||
|
||||
"""SEC node autodiscovery tool."""
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import os
|
||||
import select
|
||||
import socket
|
||||
import sys
|
||||
from collections import namedtuple
|
||||
from time import time as currenttime
|
||||
|
||||
UDP_PORT = 10767
|
||||
|
||||
Answer = namedtuple('Answer',
|
||||
'address, port, equipment_id, firmware, description')
|
||||
|
||||
|
||||
def decode(msg, addr):
|
||||
msg = msg.decode('utf-8')
|
||||
try:
|
||||
data = json.loads(msg)
|
||||
except Exception:
|
||||
return None
|
||||
if not isinstance(data, dict):
|
||||
return None
|
||||
if data.get('SECoP') != 'node':
|
||||
return None
|
||||
try:
|
||||
eq_id = data['equipment_id']
|
||||
fw = data['firmware']
|
||||
desc = data['description']
|
||||
port = data['port']
|
||||
except KeyError:
|
||||
return None
|
||||
addr, _scanport = addr
|
||||
return Answer(addr, port, eq_id, fw, desc)
|
||||
|
||||
|
||||
def print_answer(answer, *, short=False):
|
||||
if short:
|
||||
# NOTE: keep this easily parseable!
|
||||
print(f'{answer.equipment_id} {answer.address}:{answer.port}')
|
||||
return
|
||||
print(f'Found {answer.equipment_id} at {answer.address}:')
|
||||
print(f' Port: {answer.port}')
|
||||
print(f' Firmware: {answer.firmware}')
|
||||
desc = answer.description.replace('\n', '\n ')
|
||||
print(f' Node description: {desc}')
|
||||
print()
|
||||
|
||||
|
||||
def scan(max_wait=1.0):
|
||||
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||
s.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
|
||||
# send a general broadcast
|
||||
try:
|
||||
s.sendto(json.dumps(dict(SECoP='discover')).encode('utf-8'),
|
||||
('255.255.255.255', UDP_PORT))
|
||||
except OSError as e:
|
||||
print('could not send the broadcast:', e)
|
||||
# we still keep listening for self-announcements
|
||||
start = currenttime()
|
||||
seen = set()
|
||||
while currenttime() < start + max_wait:
|
||||
res = select.select([s], [], [], 0.1)
|
||||
if res[0]:
|
||||
try:
|
||||
msg, addr = s.recvfrom(1024)
|
||||
except socket.error: # pragma: no cover
|
||||
continue
|
||||
answer = decode(msg, addr)
|
||||
if answer is None:
|
||||
continue
|
||||
if (answer.address, answer.equipment_id, answer.port) in seen:
|
||||
continue
|
||||
seen.add((answer.address, answer.equipment_id, answer.port))
|
||||
yield answer
|
||||
|
||||
|
||||
def listen(*, short=False):
|
||||
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||
if os.name == 'nt':
|
||||
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
||||
else:
|
||||
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1)
|
||||
s.bind(('0.0.0.0', UDP_PORT))
|
||||
while True:
|
||||
try:
|
||||
msg, addr = s.recvfrom(1024)
|
||||
except KeyboardInterrupt:
|
||||
break
|
||||
answer = decode(msg, addr)
|
||||
if answer:
|
||||
print_answer(answer, short=short)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument('-l', '--listen', action='store_true',
|
||||
help='Print short info. '
|
||||
'Keep listening after the broadcast.')
|
||||
args = parser.parse_args(sys.argv[1:])
|
||||
for answer in scan():
|
||||
print_answer(answer, short=args.listen)
|
||||
if args.listen:
|
||||
listen(short=args.listen)
|
||||
@@ -1,6 +1,5 @@
|
||||
#!/usr/bin/env python3
|
||||
# pylint: disable=invalid-name
|
||||
# -*- coding: utf-8 -*-
|
||||
# *****************************************************************************
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify it under
|
||||
@@ -23,12 +22,12 @@
|
||||
#
|
||||
# *****************************************************************************
|
||||
|
||||
import sys
|
||||
import argparse
|
||||
from os import path
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
# Add import path for inplace usage
|
||||
sys.path.insert(0, path.abspath(path.join(path.dirname(__file__), '..')))
|
||||
sys.path.insert(0, str(Path(__file__).absolute().parents[1]))
|
||||
|
||||
from frappy.lib import generalConfig
|
||||
from frappy.logging import logger
|
||||
@@ -36,7 +35,15 @@ from frappy.server import Server
|
||||
|
||||
|
||||
def parseArgv(argv):
|
||||
parser = argparse.ArgumentParser(description="Manage a SECoP server")
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Manage a SECoP server",
|
||||
epilog="""The server needs some configuration, by default from the
|
||||
generalConfig.cfg file. the keys confdir, logdir and piddir have to
|
||||
be set.
|
||||
Alternatively, one can set the environment variables FRAPPY_CONFDIR
|
||||
FRAPPY_LOGDIR and FRAPPY_PIDDIR to set the required values.
|
||||
"""
|
||||
)
|
||||
loggroup = parser.add_mutually_exclusive_group()
|
||||
loggroup.add_argument("-v", "--verbose",
|
||||
help="Output lots of diagnostic information",
|
||||
@@ -61,8 +68,9 @@ def parseArgv(argv):
|
||||
action='store',
|
||||
help="comma separated list of cfg files,\n"
|
||||
"defaults to <name_of_the_instance>.\n"
|
||||
"cfgfiles given without '.cfg' extension are searched in the configuration directory, "
|
||||
"else they are treated as path names",
|
||||
"If a config file contains a slash, it is treated as a"
|
||||
"path, otherwise the file is searched for in the "
|
||||
"configuration directory.",
|
||||
default=None)
|
||||
parser.add_argument('-g',
|
||||
'--gencfg',
|
||||
@@ -90,20 +98,21 @@ def main(argv=None):
|
||||
args = parseArgv(argv[1:])
|
||||
|
||||
loglevel = 'debug' if args.verbose else ('error' if args.quiet else 'info')
|
||||
generalConfig.defaults = {k: args.relaxed for k in (
|
||||
'lazy_number_validation', 'disable_value_range_check', 'legacy_hasiodev', 'tolerate_poll_property')}
|
||||
generalConfig.set_default('lazy_number_validation', args.relaxed)
|
||||
generalConfig.set_default('legacy_hasiodev', args.relaxed)
|
||||
generalConfig.set_default('tolerate_poll_property', args.relaxed)
|
||||
generalConfig.init(args.gencfg)
|
||||
logger.init(loglevel)
|
||||
|
||||
srv = Server(args.name, logger.log, cfgfiles=args.cfgfiles, interface=args.port, testonly=args.test)
|
||||
cfgfiles = [s.strip() for s in args.cfgfiles.split(',')] if args.cfgfiles else None
|
||||
|
||||
srv = Server(args.name, logger.log, cfgfiles=cfgfiles,
|
||||
interface=args.port, testonly=args.test)
|
||||
|
||||
if args.daemonize:
|
||||
srv.start()
|
||||
else:
|
||||
try:
|
||||
srv.run()
|
||||
except KeyboardInterrupt:
|
||||
pass
|
||||
srv.run()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
@@ -1,64 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
# *****************************************************************************
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify it under
|
||||
# the terms of the GNU General Public License as published by the Free Software
|
||||
# Foundation; either version 2 of the License, or (at your option) any later
|
||||
# version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
|
||||
# details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License along with
|
||||
# this program; if not, write to the Free Software Foundation, Inc.,
|
||||
# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
#
|
||||
# Module authors:
|
||||
# Enrico Faulhaber <enrico.faulhaber@frm2.tum.de>
|
||||
#
|
||||
# *****************************************************************************
|
||||
|
||||
|
||||
import os
|
||||
from os import path
|
||||
|
||||
import markdown
|
||||
|
||||
BASE_PATH = path.abspath(path.join(path.dirname(__file__), '..'))
|
||||
DOC_SRC = path.join(BASE_PATH, 'doc')
|
||||
DOC_DST = path.join(BASE_PATH, 'html')
|
||||
|
||||
conv = markdown.Markdown()
|
||||
|
||||
for dirpath, dirnames, filenames in os.walk(DOC_SRC):
|
||||
# re-create the dir-structure of DOC_SRC into DOC_DST
|
||||
dst_path = path.join(DOC_DST, path.relpath(dirpath, DOC_SRC))
|
||||
try:
|
||||
os.mkdir(dst_path)
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
for fn in filenames:
|
||||
full_name = path.join(dirpath, fn)
|
||||
sub_name = path.relpath(full_name, DOC_SRC)
|
||||
final_name = path.join(DOC_DST, sub_name)
|
||||
|
||||
if not fn.endswith('md'):
|
||||
# just copy everything else
|
||||
with open(full_name, 'rb') as fi:
|
||||
with open(final_name, 'wb') as fo:
|
||||
# WARNING: possible Memory hog!
|
||||
fo.write(fi.read())
|
||||
continue
|
||||
# treat .md files special
|
||||
final_sub_name = path.splitext(sub_name)[0] + '.html'
|
||||
final_name = path.join(DOC_DST, final_sub_name)
|
||||
print("Converting %s to %s" %(sub_name, final_sub_name))
|
||||
# transform one file
|
||||
conv.reset()
|
||||
conv.convertFile(input=full_name,
|
||||
output=final_name,
|
||||
encoding="utf-8")
|
||||
197
bin/sim-server
Executable file
197
bin/sim-server
Executable file
@@ -0,0 +1,197 @@
|
||||
#!/usr/bin/env python3
|
||||
# pylint: disable=invalid-name
|
||||
# *****************************************************************************
|
||||
# This program is free software; you can redistribute it and/or modify it under
|
||||
# the terms of the GNU General Public License as published by the Free Software
|
||||
# Foundation; either version 2 of the License, or (at your option) any later
|
||||
# version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
|
||||
# details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License along with
|
||||
# this program; if not, write to the Free Software Foundation, Inc.,
|
||||
# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
#
|
||||
# Module authors:
|
||||
# Markus Zolliker <markus.zolliker@psi.ch>
|
||||
# *****************************************************************************
|
||||
"""server for a string communicator
|
||||
|
||||
Usage:
|
||||
|
||||
bin/sim-server <communicator class> -p <server port> [-o <option1>=<value> <option2>=<value>]
|
||||
|
||||
open a server on <server port> to communicate with the string based <communicator> over TCP/IP.
|
||||
|
||||
Use cases, mainly for test purposes:
|
||||
|
||||
- relay to a hardware simulation written as a communicator
|
||||
|
||||
> bin/sim-server frappy_psi.ls370sim.Ls370Sim
|
||||
|
||||
- relay to a communicator not using TCP/IP, if Frappy should run on an other host
|
||||
|
||||
> bin/sim-server frappy.io.StringIO -o uri=serial:///dev/tty...
|
||||
|
||||
- as a T, if the hardware allows only one connection, and more than one is needed:
|
||||
|
||||
> bin/sim-server frappy.io.StringIO -o uri=tcp://<host>:<port>
|
||||
|
||||
typically using communicator class frappy.io.StringIO
|
||||
"""
|
||||
|
||||
import sys
|
||||
import argparse
|
||||
from pathlib import Path
|
||||
import socket
|
||||
import time
|
||||
import os
|
||||
from ast import literal_eval
|
||||
from socketserver import BaseRequestHandler, ThreadingTCPServer
|
||||
|
||||
# Add import path for inplace usage
|
||||
sys.path.insert(0, str(Path(__file__).absolute().parents[1]))
|
||||
|
||||
from frappy.lib import get_class, formatException, mkthread
|
||||
|
||||
|
||||
class Logger:
|
||||
def debug(self, *args):
|
||||
pass
|
||||
|
||||
def log(self, level, *args):
|
||||
pass
|
||||
|
||||
def info(self, *args):
|
||||
print(*args)
|
||||
|
||||
exception = error = warn = info
|
||||
|
||||
|
||||
class TcpRequestHandler(BaseRequestHandler):
|
||||
def setup(self):
|
||||
print(f'connection opened from {self.client_address}')
|
||||
self.running = True
|
||||
self.request.settimeout(1)
|
||||
self.data = b''
|
||||
|
||||
def finish(self):
|
||||
"""called when handle() terminates, i.e. the socket closed"""
|
||||
# close socket
|
||||
try:
|
||||
self.request.shutdown(socket.SHUT_RDWR)
|
||||
except Exception:
|
||||
pass
|
||||
finally:
|
||||
print(f'connection closed from {self.client_address}')
|
||||
self.request.close()
|
||||
|
||||
def poller(self):
|
||||
while True:
|
||||
time.sleep(1.0)
|
||||
self.module.doPoll()
|
||||
|
||||
def handle(self):
|
||||
"""handle a new connection"""
|
||||
# do a copy of the options, as they are consumed
|
||||
self.module = self.server.modulecls(
|
||||
'mod', Logger(), dict(self.server.options), self.server)
|
||||
self.module.earlyInit()
|
||||
|
||||
mkthread(self.poller)
|
||||
while self.running:
|
||||
try:
|
||||
newdata = self.request.recv(1024)
|
||||
if not newdata:
|
||||
return
|
||||
except socket.timeout:
|
||||
# no new data during read, continue
|
||||
continue
|
||||
self.data += newdata
|
||||
while self.running:
|
||||
message, sep, self.data = self.data.partition(b'\n')
|
||||
if not sep:
|
||||
break
|
||||
cmd = message.decode('latin-1')
|
||||
try:
|
||||
reply = self.module.communicate(cmd.strip())
|
||||
if self.server.verbose:
|
||||
print('%-40s | %s' % (cmd, reply))
|
||||
except Exception:
|
||||
print(formatException(verbose=True))
|
||||
return
|
||||
outdata = reply.encode('latin-1') + b'\n'
|
||||
try:
|
||||
self.request.sendall(outdata)
|
||||
except Exception as e:
|
||||
print(repr(e))
|
||||
self.running = False
|
||||
|
||||
|
||||
class Server(ThreadingTCPServer):
|
||||
allow_reuse_address = os.name != 'nt' # False on Windows systems
|
||||
|
||||
class Dispatcher:
|
||||
def announce_update(self, *_):
|
||||
pass
|
||||
|
||||
def announce_update_error(self, *_):
|
||||
pass
|
||||
|
||||
def __init__(self, port, modulecls, options, verbose=False):
|
||||
super().__init__(('', port), TcpRequestHandler,
|
||||
bind_and_activate=True)
|
||||
self.secnode = None
|
||||
self.dispatcher = self.Dispatcher()
|
||||
self.verbose = verbose
|
||||
self.modulecls = get_class(modulecls)
|
||||
self.options = options
|
||||
print(f'started sim-server listening on port {port}')
|
||||
|
||||
|
||||
def parse_argv(argv):
|
||||
parser = argparse.ArgumentParser(description="Relay to a communicator (simulated HW or other)")
|
||||
parser.add_argument("-v", "--verbose",
|
||||
help="output full communication",
|
||||
action='store_true', default=False)
|
||||
parser.add_argument("cls",
|
||||
type=str,
|
||||
help="communicator class.\n",)
|
||||
parser.add_argument('-p',
|
||||
'--port',
|
||||
action='store',
|
||||
help='server port or uri',
|
||||
default=2089)
|
||||
parser.add_argument('-o',
|
||||
'--options',
|
||||
action='store',
|
||||
nargs='*',
|
||||
help='options in the form key=value',
|
||||
default=None)
|
||||
return parser.parse_args(argv)
|
||||
|
||||
|
||||
def main(argv=None):
|
||||
if argv is None:
|
||||
argv = sys.argv
|
||||
|
||||
args = parse_argv(argv[1:])
|
||||
options = {'description': ''}
|
||||
for item in args.options or ():
|
||||
key, eq, value = item.partition('=')
|
||||
if not eq:
|
||||
raise ValueError(f"missing '=' in {item}")
|
||||
try:
|
||||
value = literal_eval(value)
|
||||
except Exception:
|
||||
pass
|
||||
options[key] = value
|
||||
srv = Server(int(args.port), args.cls, options, args.verbose)
|
||||
srv.serve_forever()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
sys.exit(main(sys.argv))
|
||||
@@ -1,154 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
# pylint: disable=invalid-name
|
||||
# -*- coding: utf-8 -*-
|
||||
# *****************************************************************************
|
||||
# This program is free software; you can redistribute it and/or modify it under
|
||||
# the terms of the GNU General Public License as published by the Free Software
|
||||
# Foundation; either version 2 of the License, or (at your option) any later
|
||||
# version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
|
||||
# details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License along with
|
||||
# this program; if not, write to the Free Software Foundation, Inc.,
|
||||
# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
#
|
||||
# Module authors:
|
||||
# Markus Zolliker <markus.zolliker@psi.ch>
|
||||
# *****************************************************************************
|
||||
"""server for a string communicator
|
||||
|
||||
Usage:
|
||||
|
||||
bin/stringio-server <communciator> <server port>
|
||||
|
||||
open a server on <server port> to communicate with the string based <communicator> over TCP/IP.
|
||||
|
||||
Use cases, mainly for test purposes:
|
||||
- as a T, if the hardware allows only one connection, and more than one is needed
|
||||
- relay to a communicator not using TCP/IP, if Frappy should run on an other host
|
||||
- relay to a hardware simulation written as a communicator
|
||||
"""
|
||||
|
||||
import sys
|
||||
from os import path
|
||||
import asyncore
|
||||
import socket
|
||||
import ast
|
||||
|
||||
# Add import path for inplace usage
|
||||
sys.path.insert(0, path.abspath(path.join(path.dirname(__file__), '..')))
|
||||
|
||||
from frappy.lib import get_class, formatException
|
||||
|
||||
class LineHandler(asyncore.dispatcher_with_send):
|
||||
|
||||
def __init__(self, sock):
|
||||
self.buffer = b""
|
||||
asyncore.dispatcher_with_send.__init__(self, sock)
|
||||
self.crlf = 0
|
||||
|
||||
def handle_read(self):
|
||||
data = self.recv(8192)
|
||||
if data:
|
||||
parts = data.split(b"\n")
|
||||
if len(parts) == 1:
|
||||
self.buffer += data
|
||||
else:
|
||||
self.handle_line((self.buffer + parts[0]).decode('latin_1'))
|
||||
for part in parts[1:-1]:
|
||||
if part[-1] == b"\r":
|
||||
self.crlf = True
|
||||
part = part[:-1]
|
||||
else:
|
||||
self.crlf = False
|
||||
self.handle_line(part.decode('latin_1'))
|
||||
self.buffer = parts[-1]
|
||||
|
||||
def send_line(self, line):
|
||||
self.send((line + ("\r\n" if self.crlf else "\n")).encode('latin_1'))
|
||||
|
||||
|
||||
class LineServer(asyncore.dispatcher):
|
||||
|
||||
def __init__(self, host, port, lineHandlerClass):
|
||||
asyncore.dispatcher.__init__(self)
|
||||
self.create_socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
self.set_reuse_addr()
|
||||
self.bind((host, port))
|
||||
self.listen(5)
|
||||
self.lineHandlerClass = lineHandlerClass
|
||||
|
||||
def handle_accept(self):
|
||||
pair = self.accept()
|
||||
if pair is not None:
|
||||
sock, addr = pair
|
||||
print("Incoming connection from %s" % repr(addr))
|
||||
self.lineHandlerClass(sock)
|
||||
|
||||
def loop(self):
|
||||
asyncore.loop()
|
||||
|
||||
|
||||
class Server(LineServer):
|
||||
|
||||
class Dispatcher:
|
||||
def announce_update(self, *_):
|
||||
pass
|
||||
|
||||
def announce_update_error(self, *_):
|
||||
pass
|
||||
|
||||
def __init__(self, *args, **kwds):
|
||||
super().__init__(*args, **kwds)
|
||||
self.dispatcher = self.Dispatcher()
|
||||
|
||||
|
||||
class Handler(LineHandler):
|
||||
def handle_line(self, line):
|
||||
try:
|
||||
reply = module.do_communicate(line.strip())
|
||||
if verbose:
|
||||
print('%-40s | %s' % (line, reply))
|
||||
except Exception:
|
||||
print(formatException(verbose=True))
|
||||
self.send_line(reply)
|
||||
|
||||
|
||||
class Logger:
|
||||
def debug(self, *args):
|
||||
print(*args)
|
||||
info = exception = debug
|
||||
|
||||
|
||||
opts = {'description': 'simulator'}
|
||||
args = []
|
||||
for arg in sys.argv[1:]:
|
||||
k, sep, v = arg.partition('=')
|
||||
if not k:
|
||||
args.append(v)
|
||||
try:
|
||||
v = ast.literal_eval(v)
|
||||
except Exception:
|
||||
pass
|
||||
opts[k] = v
|
||||
verbose = opts.pop('verbose', False)
|
||||
opts['cls'] = 'frappy_psi.ls370sim.Ls370Sim'
|
||||
opts['port'] = 4567
|
||||
if len(args) > 2:
|
||||
raise ValueError('do not know about: %s' % ' '.join(args[2:]))
|
||||
if len(args) == 2:
|
||||
opts['port'] = int(args[1])
|
||||
if len(args) > 0:
|
||||
opts['cls'] = args[0]
|
||||
args.append(opts)
|
||||
|
||||
cls = opts.pop('cls')
|
||||
port = opts.pop('port')
|
||||
srv = Server('localhost', int(port), Handler)
|
||||
module = get_class(cls)(cls, Logger(), opts, srv)
|
||||
module.earlyInit()
|
||||
srv.loop()
|
||||
@@ -1,17 +0,0 @@
|
||||
[node AH2700Test.psi.ch]
|
||||
description = AH2700 capacitance bridge test
|
||||
|
||||
[interface tcp]
|
||||
type = tcp
|
||||
bindto = 0.0.0.0
|
||||
bindport = 5000
|
||||
|
||||
[module cap]
|
||||
class = frappy_psi.ah2700.Capacitance
|
||||
description = capacitance
|
||||
uri=ldmse3-ts:3015
|
||||
|
||||
#[module ahcom]
|
||||
#class = frappy_psi.ah2700.StringIO
|
||||
#uri=ldmse3-ts:3015
|
||||
#description = serial communicator to an AH2700
|
||||
10
cfg/ah2700test_cfg.py
Normal file
10
cfg/ah2700test_cfg.py
Normal file
@@ -0,0 +1,10 @@
|
||||
Node('AH2700Test.psi.ch',
|
||||
'AH2700 capacitance bridge test',
|
||||
'tcp://5000',
|
||||
)
|
||||
|
||||
Mod('cap',
|
||||
'frappy_psi.ah2700.Capacitance',
|
||||
'capacitance',
|
||||
uri='ldmse3-ts:3015',
|
||||
)
|
||||
111
cfg/amagnet.cfg
111
cfg/amagnet.cfg
@@ -1,111 +0,0 @@
|
||||
[node MLZ_amagnet(Garfield)]
|
||||
description=MLZ-Amagnet
|
||||
.
|
||||
Water cooled magnet from ANTARES@MLZ.
|
||||
.
|
||||
Use module to control the magnetic field.
|
||||
Don't forget to select symmetry first (can be moved only at zero field!).
|
||||
.
|
||||
Monitor T1..T4 (Coil temps), if they get to hot, field will ramp down!
|
||||
.
|
||||
In case of Problems, contact the ANTARES people at MLZ.
|
||||
|
||||
visibility=expert
|
||||
foo=bar
|
||||
|
||||
[interface tcp]
|
||||
type=tcp
|
||||
bindto=0.0.0.0
|
||||
bindport=10767
|
||||
|
||||
[module enable]
|
||||
class=frappy_mlz.entangle.NamedDigitalOutput
|
||||
tangodevice='tango://localhost:10000/box/plc/_enable'
|
||||
value.datatype=["enum", {'On':1,'Off':0}]
|
||||
target.datatype=["enum", {'On':1,'Off':0}]
|
||||
.description='Enables to Output of the Powersupply'
|
||||
.visibility='advanced'
|
||||
|
||||
[module polarity]
|
||||
class=frappy_mlz.entangle.NamedDigitalOutput
|
||||
tangodevice=tango://localhost:10000/box/plc/_polarity
|
||||
value.datatype=["enum", {'+1':1,'0':0,'-1':-1}]
|
||||
target.datatype=["enum", {'+1':1,'0':0,'-1':-1}]
|
||||
.description=polarity (+/-) switch
|
||||
.
|
||||
there is an interlock in the plc:
|
||||
if there is current, switching polarity is forbidden
|
||||
if polarity is short, powersupply is disabled
|
||||
.visibility=advanced
|
||||
comtries=50
|
||||
|
||||
|
||||
[module symmetry]
|
||||
class=frappy_mlz.entangle.NamedDigitalOutput
|
||||
tangodevice=tango://localhost:10000/box/plc/_symmetric
|
||||
value.datatype=["enum",{'symmetric':1,'short':0, 'asymmetric':-1}]
|
||||
target.datatype=["enum",{'symmetric':1,'short':0, 'asymmetric':-1}]
|
||||
.description=par/ser switch selecting (a)symmetric mode
|
||||
.
|
||||
symmetric is ser, asymmetric is par
|
||||
.visibility=advanced
|
||||
|
||||
[module T1]
|
||||
class=frappy_mlz.entangle.AnalogInput
|
||||
tangodevice=tango://localhost:10000/box/plc/_t1
|
||||
.description=Temperature1 of the coils system
|
||||
#warnlimits=(0, 50)
|
||||
value.unit='degC'
|
||||
|
||||
[module T2]
|
||||
class=frappy_mlz.entangle.AnalogInput
|
||||
tangodevice=tango://localhost:10000/box/plc/_t2
|
||||
.description=Temperature2 of the coils system
|
||||
#warnlimits=(0, 50)
|
||||
value.unit='degC'
|
||||
|
||||
[module T3]
|
||||
class=frappy_mlz.entangle.AnalogInput
|
||||
tangodevice=tango://localhost:10000/box/plc/_t3
|
||||
.description=Temperature3 of the coils system
|
||||
#warnlimits=(0, 50)
|
||||
value.unit='degC'
|
||||
|
||||
[module T4]
|
||||
class=frappy_mlz.entangle.AnalogInput
|
||||
tangodevice=tango://localhost:10000/box/plc/_t4
|
||||
.description=Temperature4 of the coils system
|
||||
#warnlimits=(0, 50)
|
||||
value.unit='degC'
|
||||
|
||||
[module currentsource]
|
||||
class=frappy_mlz.entangle.PowerSupply
|
||||
tangodevice=tango://localhost:10000/box/lambda/curr
|
||||
.description=Device for the magnet power supply (current mode)
|
||||
abslimits=(0,200)
|
||||
speed=1
|
||||
ramp=60
|
||||
precision=0.02
|
||||
current=0
|
||||
voltage=10
|
||||
#unit=A
|
||||
.visibility=advanced
|
||||
|
||||
[module mf]
|
||||
class=frappy_mlz.amagnet.GarfieldMagnet
|
||||
.description=magnetic field module, handling polarity switching and stuff
|
||||
subdev_currentsource=currentsource
|
||||
subdev_enable=enable
|
||||
subdev_polswitch=polarity
|
||||
subdev_symmetry=symmetry
|
||||
target.unit='T'
|
||||
value.unit='T'
|
||||
userlimits=(-0.35, 0.35)
|
||||
calibrationtable={'symmetric':[0.00186517, 0.0431937, -0.185956, 0.0599757, 0.194042],
|
||||
'short': [0.0, 0.0, 0.0, 0.0, 0.0],
|
||||
'asymmetric':[0.00136154, 0.027454, -0.120951, 0.0495289, 0.110689]}
|
||||
.meaning=The magnetic field
|
||||
.priority=100
|
||||
.visibility=user
|
||||
|
||||
abslimits.default=-0.4,0.4
|
||||
91
cfg/amagnet_cfg.py
Normal file
91
cfg/amagnet_cfg.py
Normal file
@@ -0,0 +1,91 @@
|
||||
Node('MLZ_amagnet(Garfield)',
|
||||
'MLZ-Amagnet\n'
|
||||
'\n'
|
||||
'Water cooled magnet from ANTARES@MLZ.\n'
|
||||
'\n'
|
||||
'Use module to control the magnetic field.\n'
|
||||
'Don\'t forget to select symmetry first (can be moved only at zero field!).\n'
|
||||
'\n'
|
||||
'Monitor T1..T4 (Coil temps), if they get to hot, field will ramp down!\n'
|
||||
'\n'
|
||||
'In case of Problems, contact the ANTARES people at MLZ.',
|
||||
'tcp://10767',
|
||||
visibility = 'expert',
|
||||
foo = 'bar',
|
||||
)
|
||||
|
||||
Mod('enable',
|
||||
'frappy_mlz.entangle.NamedDigitalOutput',
|
||||
'Enables to Output of the Powersupply',
|
||||
tangodevice = 'tango://localhost:10000/box/plc/_enable',
|
||||
value = Param(datatype=["enum", {'On':1,'Off':0}]),
|
||||
target = Param(datatype=["enum", {'On':1,'Off':0}]),
|
||||
visibility = 'advanced',
|
||||
)
|
||||
|
||||
Mod('polarity',
|
||||
'frappy_mlz.entangle.NamedDigitalOutput',
|
||||
'polarity (+/-) switch\n'
|
||||
'\n'
|
||||
'there is an interlock in the plc:\n'
|
||||
'if there is current, switching polarity is forbidden\n'
|
||||
'if polarity is short, powersupply is disabled',
|
||||
tangodevice = 'tango://localhost:10000/box/plc/_polarity',
|
||||
value = Param(datatype=["enum", {'+1':1,'0':0,'-1':-1}]),
|
||||
target = Param(datatype=["enum", {'+1':1,'0':0,'-1':-1}]),
|
||||
visibility = 'advanced',
|
||||
comtries = 50,
|
||||
)
|
||||
|
||||
Mod('symmetry',
|
||||
'frappy_mlz.entangle.NamedDigitalOutput',
|
||||
'par/ser switch selecting (a)symmetric mode\n'
|
||||
'\n'
|
||||
'symmetric is ser, asymmetric is par',
|
||||
tangodevice = 'tango://localhost:10000/box/plc/_symmetric',
|
||||
value = Param(datatype=["enum",{'symmetric':1,'short':0, 'asymmetric':-1}]),
|
||||
target = Param(datatype=["enum",{'symmetric':1,'short':0, 'asymmetric':-1}]),
|
||||
visibility = 'advanced',
|
||||
)
|
||||
|
||||
for i in range(1,5):
|
||||
Mod('T%d' % i,
|
||||
'frappy_mlz.entangle.AnalogInput',
|
||||
'Temperature %d of the coils system' % i,
|
||||
tangodevice = 'tango://localhost:10000/box/plc/_t%d' % i,
|
||||
#warnlimits=(0, 50),
|
||||
value = Param(unit='degC'),
|
||||
)
|
||||
|
||||
Mod('currentsource',
|
||||
'frappy_mlz.entangle.PowerSupply',
|
||||
'Device for the magnet power supply (current mode)',
|
||||
tangodevice = 'tango://localhost:10000/box/lambda/curr',
|
||||
abslimits = (0,200),
|
||||
speed = 1,
|
||||
ramp = 60,
|
||||
precision = 0.02,
|
||||
current = 0,
|
||||
voltage = 10,
|
||||
#value=Param(unit='A')
|
||||
visibility = 'advanced',
|
||||
)
|
||||
|
||||
Mod('mf',
|
||||
'frappy_mlz.amagnet.GarfieldMagnet',
|
||||
'magnetic field module, handling polarity switching and stuff',
|
||||
currentsource = 'currentsource',
|
||||
enable = 'enable',
|
||||
polswitch = 'polarity',
|
||||
symmetry = 'symmetry',
|
||||
target = Param(unit='T'),
|
||||
value = Param(unit='T'),
|
||||
userlimits = (-0.35, 0.35),
|
||||
calibrationtable = {'symmetric':[0.00186517, 0.0431937, -0.185956, 0.0599757, 0.194042],
|
||||
'short': [0.0, 0.0, 0.0, 0.0, 0.0],
|
||||
'asymmetric':[0.00136154, 0.027454, -0.120951, 0.0495289, 0.110689]},
|
||||
meaning = ['The magnetic field', 1],
|
||||
#priority=100,
|
||||
visibility = 'user',
|
||||
abslimits = (-0.4,0.4,),
|
||||
)
|
||||
150
cfg/ccr.cfg
150
cfg/ccr.cfg
@@ -1,150 +0,0 @@
|
||||
[node MLZ_ccr12]
|
||||
description = CCR box of MLZ Sample environment group
|
||||
.
|
||||
Contains a Lakeshore 336 and an PLC controlling the compressor
|
||||
and some valves.
|
||||
|
||||
|
||||
[interface tcp]
|
||||
type=tcp
|
||||
bindto=0.0.0.0
|
||||
bindport=10767
|
||||
|
||||
[module automatik]
|
||||
class=frappy_mlz.entangle.NamedDigitalOutput
|
||||
tangodevice=tango://localhost:10000/box/plc/_automatik
|
||||
mapping=dict(Off=0,p1=1,p2=2)
|
||||
description="controls the (simple) pressure regulation
|
||||
.
|
||||
selects between off, regulate on p1 or regulate on p2 sensor"
|
||||
|
||||
[module compressor]
|
||||
class=frappy_mlz.entangle.NamedDigitalOutput
|
||||
tangodevice=tango://localhost:10000/box/plc/_cooler_onoff
|
||||
mapping=dict(Off=0,On=1)
|
||||
description=control the compressor (on/off)
|
||||
|
||||
[module gas]
|
||||
class=frappy_mlz.entangle.NamedDigitalOutput
|
||||
tangodevice=tango://localhost:10000/box/plc/_gas_onoff
|
||||
mapping=dict(Off=0,On=1)
|
||||
description=control the gas inlet into the ccr (on/off)
|
||||
.
|
||||
note: this switches off automatically after 15 min.
|
||||
note: activation de-activates the vacuum inlet
|
||||
note: if the pressure regulation is active, it enslave this device
|
||||
|
||||
[module vacuum]
|
||||
class=frappy_mlz.entangle.NamedDigitalOutput
|
||||
tangodevice=tango://localhost:10000/box/plc/_vacuum_Onoff
|
||||
mapping=dict(Off=0,On=1)
|
||||
description=control the vacuum inlet into the ccr (on/off)
|
||||
.
|
||||
note: activation de-activates the gas inlet
|
||||
note: if the pressure regulation is active, it enslave this device
|
||||
|
||||
[module p1]
|
||||
class=frappy_mlz.entangle.AnalogInput
|
||||
tangodevice=tango://localhost:10000/box/plc/_p1
|
||||
value.unit='mbar'
|
||||
description=pressure sensor 1 (linear scale)
|
||||
|
||||
[module p2]
|
||||
class=frappy_mlz.entangle.AnalogInput
|
||||
tangodevice=tango://localhost:10000/box/plc/_p2
|
||||
value.unit='mbar'
|
||||
description=pressure sensor 2 (selectable curve)
|
||||
|
||||
[module curve_p2]
|
||||
class=frappy_mlz.entangle.NamedDigitalInput
|
||||
tangodevice=tango://localhost:10000/box/plc/_curve
|
||||
value.default=0
|
||||
description=calibration curve for pressure sensor 2
|
||||
mapping="{'0-10V':0, '0-1000mbar':1, '1-9V to 0-1 mbar':2,
|
||||
'DI200':3, 'DI2000':4, 'TTR100':7, 'PTR90':8,
|
||||
'PTR225/237':9, 'ITR90':10, 'ITR100-D':11,
|
||||
'ITR100-2':12, 'ITR100-3':13, 'ITR100-4':14,
|
||||
'ITR100-5':15, 'ITR100-6':16, 'ITR100-7':17,
|
||||
'ITR100-8':18, 'ITR100-9':19, 'ITR100-A':20,
|
||||
'CMR361':21, 'CMR362':22, 'CMR363':23,
|
||||
'CMR364':24, 'CMR365':25}"
|
||||
|
||||
# sensors
|
||||
[module T_sample]
|
||||
class=frappy_mlz.entangle.Sensor
|
||||
tangodevice=tango://localhost:10000/box/sample/sensora
|
||||
value.unit='K'
|
||||
description=sample temperature
|
||||
|
||||
[module T_stick]
|
||||
class=frappy_mlz.entangle.Sensor
|
||||
tangodevice=tango://localhost:10000/box/stick/sensorb
|
||||
value.unit='K'
|
||||
description=temperature at bottom of sample stick
|
||||
|
||||
[module T_coldhead]
|
||||
class=frappy_mlz.entangle.Sensor
|
||||
tangodevice=tango://localhost:10000/box/coldhead/sensorc
|
||||
value.unit='K'
|
||||
description=temperature at coldhead
|
||||
|
||||
[module T_tube]
|
||||
class=frappy_mlz.entangle.Sensor
|
||||
tangodevice=tango://localhost:10000/box/tube/sensord
|
||||
value.unit='K'
|
||||
description=temperature at thermal coupling tube <-> stick
|
||||
|
||||
|
||||
# regulations
|
||||
|
||||
[module T_stick_regulation]
|
||||
class=frappy_mlz.entangle.TemperatureController
|
||||
tangodevice=tango://localhost:10000/box/stick/control2
|
||||
heateroutput.default=0
|
||||
description=regulation of stick temperature
|
||||
ramp.default=6
|
||||
speed.default=0.1
|
||||
setpoint.default=0
|
||||
pid.default=(40,10,1)
|
||||
p.default=40
|
||||
i.default=10
|
||||
d.default=1
|
||||
abslimits=(0,500)
|
||||
value.unit='K'
|
||||
|
||||
# OMG! a NamedDigitalOutput, but with float'ints' 0..3
|
||||
[module T_stick_regulation_heaterrange]
|
||||
class=frappy_mlz.entangle.AnalogOutput
|
||||
tangodevice=tango://localhost:10000/box/stick/range2
|
||||
precision.default=1
|
||||
abslimits=(0,3)
|
||||
description=heaterrange for stick regulation
|
||||
|
||||
|
||||
[module T_tube_regulation]
|
||||
class=frappy_mlz.entangle.TemperatureController
|
||||
tangodevice=tango://localhost:10000/box/tube/control1
|
||||
description=regulation of tube temperature
|
||||
heateroutput.default=0
|
||||
ramp.default=6
|
||||
speed.default=0.1
|
||||
setpoint.default=0
|
||||
pid.default=(40,10,1)
|
||||
p.default=40
|
||||
i.default=10
|
||||
d.default=1
|
||||
abslimits=(0,500)
|
||||
value.unit='K'
|
||||
|
||||
# OMG! a NamedDigitalOutput, but with float'ints' 0..3
|
||||
#[module T_tube_regulation_heaterrange]
|
||||
#class=frappy_mlz.entangle.AnalogOutput
|
||||
#tangodevice=tango://localhost:10000/box/tube/range1
|
||||
#precision.default=1
|
||||
#abslimits=(0,3)
|
||||
|
||||
[module T_tube_regulation_heaterrange]
|
||||
class=frappy_mlz.entangle.NamedDigitalOutput
|
||||
tangodevice=tango://localhost:10000/box/tube/range1
|
||||
mapping=dict(Off=0, Low=1, Medium=2, High=3)
|
||||
description=heaterrange for tube regulation
|
||||
148
cfg/ccr_cfg.py
Normal file
148
cfg/ccr_cfg.py
Normal file
@@ -0,0 +1,148 @@
|
||||
desc = '''CCR box of MLZ Sample environment group
|
||||
|
||||
Contains a Lakeshore 336 and an PLC controlling the compressor
|
||||
and some valves.'''
|
||||
Node('MLZ_ccr',
|
||||
desc,
|
||||
'tcp://10767',
|
||||
)
|
||||
|
||||
Mod('automatik',
|
||||
'frappy_mlz.entangle.NamedDigitalOutput',
|
||||
'controls the (simple) pressure regulation\n'
|
||||
'\n'
|
||||
'selects between off, regulate on p1 or regulate on p2 sensor',
|
||||
tangodevice = 'tango://localhost:10000/box/plc/_automatik',
|
||||
mapping={'Off':0,'p1':1,'p2':2},
|
||||
)
|
||||
|
||||
Mod('compressor',
|
||||
'frappy_mlz.entangle.NamedDigitalOutput',
|
||||
'control the compressor (on/off)',
|
||||
tangodevice = 'tango://localhost:10000/box/plc/_cooler_onoff',
|
||||
mapping={'Off':0,'On':1},
|
||||
)
|
||||
|
||||
Mod('gas',
|
||||
'frappy_mlz.entangle.NamedDigitalOutput',
|
||||
'control the gas inlet into the ccr (on/off)\n'
|
||||
'\n'
|
||||
'note: this switches off automatically after 15 min.\n'
|
||||
'note: activation de-activates the vacuum inlet\n'
|
||||
'note: if the pressure regulation is active, it enslave this device',
|
||||
tangodevice = 'tango://localhost:10000/box/plc/_gas_onoff',
|
||||
mapping={'Off':0,'On':1},
|
||||
)
|
||||
|
||||
Mod('vacuum',
|
||||
'frappy_mlz.entangle.NamedDigitalOutput',
|
||||
'control the vacuum inlet into the ccr (on/off)\n'
|
||||
'\n'
|
||||
'note: activation de-activates the gas inlet\n'
|
||||
'note: if the pressure regulation is active, it enslave this device',
|
||||
tangodevice = 'tango://localhost:10000/box/plc/_vacuum_onoff',
|
||||
mapping={'Off':0,'On':1},
|
||||
)
|
||||
|
||||
Mod('p1',
|
||||
'frappy_mlz.entangle.AnalogInput',
|
||||
'pressure sensor 1 (linear scale)',
|
||||
tangodevice = 'tango://localhost:10000/box/plc/_p1',
|
||||
value = Param(unit='mbar')
|
||||
)
|
||||
|
||||
Mod('p2',
|
||||
'frappy_mlz.entangle.AnalogInput',
|
||||
'pressure sensor 2 (selectable curve)',
|
||||
tangodevice = 'tango://localhost:10000/box/plc/_p2',
|
||||
value = Param(unit='mbar'),
|
||||
)
|
||||
|
||||
Mod('curve_p2',
|
||||
'frappy_mlz.entangle.NamedDigitalInput',
|
||||
'calibration curve for pressure sensor 2',
|
||||
tangodevice = 'tango://localhost:10000/box/plc/_curve',
|
||||
value = 0,
|
||||
mapping = {'0-10V':0, '0-1000mbar':1, '1-9V to 0-1 mbar':2,
|
||||
'DI200':3, 'DI2000':4, 'TTR100':7, 'PTR90':8,
|
||||
'PTR225/237':9, 'ITR90':10, 'ITR100-D':11,
|
||||
'ITR100-2':12, 'ITR100-3':13, 'ITR100-4':14,
|
||||
'ITR100-5':15, 'ITR100-6':16, 'ITR100-7':17,
|
||||
'ITR100-8':18, 'ITR100-9':19, 'ITR100-A':20,
|
||||
'CMR361':21, 'CMR362':22, 'CMR363':23,
|
||||
'CMR364':24, 'CMR365':25},
|
||||
)
|
||||
|
||||
Mod('T_tube_regulation',
|
||||
'frappy_mlz.entangle.TemperatureController',
|
||||
'regulation of tube temperature',
|
||||
tangodevice = 'tango://localhost:10000/box/tube/control1',
|
||||
value = Param(unit = 'K'),
|
||||
heateroutput = 0,
|
||||
ramp = 6,
|
||||
speed = 0.1,
|
||||
setpoint = 0,
|
||||
pid = (40, 10, 1),
|
||||
p = 40,
|
||||
i = 10,
|
||||
d = 1,
|
||||
abslimits = (0, 500),
|
||||
)
|
||||
|
||||
Mod('T_stick_regulation',
|
||||
'frappy_mlz.entangle.TemperatureController',
|
||||
'regualtion of stick temperature',
|
||||
tangodevice = 'tango://localhost:10000/box/stick/control2',
|
||||
value = Param(unit = 'K'),
|
||||
heateroutput = 0,
|
||||
ramp = 6,
|
||||
speed = 0.1,
|
||||
setpoint = 0,
|
||||
pid = (40, 10, 1),
|
||||
p = 40,
|
||||
i = 10,
|
||||
d = 1,
|
||||
abslimits = (0, 500),
|
||||
)
|
||||
Mod('T_sample',
|
||||
'frappy_mlz.entangle.Sensor',
|
||||
'sample temperature',
|
||||
tangodevice = 'tango://localhost:10000/box/sample/sensora',
|
||||
value = Param(unit = 'K'),
|
||||
)
|
||||
|
||||
Mod('T_stick',
|
||||
'frappy_mlz.entangle.Sensor',
|
||||
'temperature at bottom of sample stick',
|
||||
tangodevice = 'tango://localhost:10000/box/stick/sensorb',
|
||||
value = Param(unit = 'K'),
|
||||
)
|
||||
|
||||
Mod('T_coldhead',
|
||||
'frappy_mlz.entangle.Sensor',
|
||||
'temperature at coldhead',
|
||||
tangodevice = 'tango://localhost:10000/box/coldhead/sensorc',
|
||||
value = Param(unit = 'K'),
|
||||
)
|
||||
|
||||
Mod('T_tube',
|
||||
'frappy_mlz.entangle.Sensor',
|
||||
'temperature at thermal coupling tube <-> stick',
|
||||
tangodevice = 'tango://localhost:10000/box/tube/sensord',
|
||||
value = Param(unit = 'K'),
|
||||
)
|
||||
|
||||
# THIS IS A HACK: due to entangle (in controller)
|
||||
Mod('T_tube_regulation_heaterrange',
|
||||
'frappy_mlz.entangle.NamedDigitalOutput',
|
||||
'heaterrange for tube regulation',
|
||||
tangodevice = 'tango://localhost:10000/box/tube/range1',
|
||||
mapping={'Off':0,'Low':1,'Medium':2, 'High':3},
|
||||
)
|
||||
|
||||
Mod('T_stick_regulation_heaterrange',
|
||||
'frappy_mlz.entangle.NamedDigitalOutput',
|
||||
'heaterrange for stick regulation',
|
||||
tangodevice = 'tango://localhost:10000/box/stick/range2',
|
||||
mapping={'Off':0,'Low':1,'Medium':2, 'High':3},
|
||||
)
|
||||
@@ -3,19 +3,17 @@
|
||||
#####################################################################
|
||||
|
||||
Node('cryo_7.frappy.demo',
|
||||
'short description' \
|
||||
'' \
|
||||
'' \
|
||||
'This is a very long description providing all the glory details in all the ' \
|
||||
'glory details about the stuff we are describing',
|
||||
'short description\n\n'
|
||||
'This is a very long description providing all the gory details '
|
||||
'about the stuff we are describing.',
|
||||
'tcp://10769',
|
||||
more="blub",
|
||||
)
|
||||
|
||||
Mod('cryo',
|
||||
'frappy_demo.cryo.Cryostat',
|
||||
'A simulated cc cryostat with heat-load, specific heat for the sample and a ' \
|
||||
'temperature dependend heat-link between sample and regulation.',
|
||||
'A simulated cc cryostat with heat-load, specific heat for the sample and a '
|
||||
'temperature dependent heat-link between sample and regulation.',
|
||||
group='very important/stuff',
|
||||
jitter=0.1,
|
||||
T_start=10.0,
|
||||
|
||||
@@ -39,7 +39,8 @@ Mod('tc2',
|
||||
|
||||
Mod('label',
|
||||
'frappy_demo.modules.Label',
|
||||
'some label indicating the state of the magnet `,f`.',
|
||||
subdev_mf = 'mf',
|
||||
subdev_ts = 'ts',
|
||||
'some label indicating the state of the magnet `mf`.',
|
||||
system = 'Cryomagnet MX15',
|
||||
mf = 'mf',
|
||||
ts = 'ts',
|
||||
)
|
||||
|
||||
@@ -1,50 +0,0 @@
|
||||
[node see_demo_equipment]
|
||||
description=Do not use, it needs to be rewritten....
|
||||
|
||||
[interface testing]
|
||||
type=tcp
|
||||
bindto=0.0.0.0
|
||||
bindport=10767
|
||||
|
||||
[module tc1]
|
||||
class=frappy_demo.modules.CoilTemp
|
||||
sensor="X34598T7"
|
||||
|
||||
[module tc2]
|
||||
class=frappy_demo.modules.CoilTemp
|
||||
sensor="X39284Q8'
|
||||
|
||||
|
||||
[module sensor1]
|
||||
class=frappy_ess.epics.EpicsReadable
|
||||
epics_version="v4"
|
||||
.group="Lakeshore336"
|
||||
value_pv="DEV:KRDG1"
|
||||
|
||||
|
||||
[module loop1]
|
||||
class=frappy_ess.epics.EpicsTempCtrl
|
||||
epics_version="v4"
|
||||
.group="Lakeshore336"
|
||||
|
||||
value_pv="DEV:KRDG1"
|
||||
target_pv="DEV:SETP_S1"
|
||||
heaterrange_pv="DEV:RANGE_S1"
|
||||
|
||||
|
||||
[module sensor2]
|
||||
class=frappy_ess.epics.EpicsReadable
|
||||
epics_version="v4"
|
||||
.group="Lakeshore336"
|
||||
value_pv="DEV:KRDG2"
|
||||
|
||||
|
||||
[module loop2]
|
||||
class=frappy_ess.epics.EpicsTempCtrl
|
||||
epics_version="v4"
|
||||
.group="Lakeshore336"
|
||||
|
||||
value_pv="DEV:KRDG2"
|
||||
target_pv="DEV:SETP_S2"
|
||||
heaterrange_pv="DEV:RANGE_S2"
|
||||
|
||||
36
cfg/epics_cfg.py
Normal file
36
cfg/epics_cfg.py
Normal file
@@ -0,0 +1,36 @@
|
||||
Node('see_demo_equipment',
|
||||
'Do not use, it needs to be rewritten....',
|
||||
'tcp://10767',
|
||||
)
|
||||
|
||||
Mod('tc1',
|
||||
'frappy_demo.modules.CoilTemp',
|
||||
'',
|
||||
sensor="X34598T7",
|
||||
)
|
||||
|
||||
Mod('tc2',
|
||||
'frappy_demo.modules.CoilTemp',
|
||||
'',
|
||||
sensor="X39284Q8",
|
||||
)
|
||||
|
||||
|
||||
for i in [1,2]:
|
||||
Mod('sensor%d' % i,
|
||||
'frappy_ess.epics.EpicsReadable',
|
||||
'',
|
||||
epics_version="v4",
|
||||
value_pv="DEV:KRDG%d" % i,
|
||||
group="Lakeshore336",
|
||||
)
|
||||
|
||||
Mod('loop%d' % i,
|
||||
'frappy_ess.epics.EpicsTempCtrl',
|
||||
'',
|
||||
epics_version="v4",
|
||||
group="Lakeshore336",
|
||||
value_pv="DEV:KRDG%d" % i,
|
||||
target_pv="DEV:SETP_S%d" % i,
|
||||
heaterrange_pv="DEV:RANGE_S%d" % i,
|
||||
)
|
||||
7
cfg/frappy_test_cfg.py
Normal file
7
cfg/frappy_test_cfg.py
Normal file
@@ -0,0 +1,7 @@
|
||||
import frappy.core as fc
|
||||
import TNMRExt.FlexibleModule as mod
|
||||
|
||||
Node('example_TNMR.psi.ch', 'The NMR system running the Scout and controlled with TNMR', interface='tcp://5000')
|
||||
|
||||
Mod('sequence0', mod.flexible_module('D:\\Users\\Garrad\\sequence_0.tps', 's0'), 'The zeroth sequence')
|
||||
Mod('sequence1', mod.flexible_module('D:\\Users\\Garrad\\sequence_1.tps', 's1'), 'The first sequence')
|
||||
23
cfg/ls336_cfg.py
Normal file
23
cfg/ls336_cfg.py
Normal file
@@ -0,0 +1,23 @@
|
||||
from os import environ
|
||||
|
||||
# either change the uri or set the environment variable 'LS_URI'
|
||||
lakeshore_uri = environ.get('LS_URI', 'tcp://<host>:7777')
|
||||
|
||||
Node('example_cryo.psi.ch', # a globally unique identification
|
||||
'this is an example cryostat for the Frappy tutorial', # describes the node
|
||||
interface='tcp://10767') # you might choose any port number > 1024
|
||||
Mod('io', # the name of the module
|
||||
'frappy_demo.lakeshore.LakeshoreIO', # the class used for communication
|
||||
'communication to main controller', # a description
|
||||
uri=lakeshore_uri, # the serial connection
|
||||
)
|
||||
Mod('T',
|
||||
'frappy_demo.lakeshore.TemperatureLoop',
|
||||
'Sample Temperature',
|
||||
io='io',
|
||||
channel='A', # the channel on the LakeShore for this module
|
||||
loop=1, # the loop to be used
|
||||
value=Param(max=470), # set the maximum expected T
|
||||
target=Param(max=420), # set the maximum allowed target T
|
||||
heater_range=3, # 5 for model 350
|
||||
)
|
||||
@@ -1,28 +0,0 @@
|
||||
[NODE]
|
||||
id = LscSIM.psi.ch
|
||||
description = Lsc Simulation at PSI
|
||||
|
||||
[INTERFACE]
|
||||
uri = tcp://5000
|
||||
|
||||
[lscom]
|
||||
class = frappy_psi.ls370sim.Ls370Sim
|
||||
description = simulated serial communicator to a LS 370
|
||||
visibility = 3
|
||||
|
||||
[sw]
|
||||
class = frappy_psi.ls370res.Switcher
|
||||
description = channel switcher for Lsc controller
|
||||
io = lscom
|
||||
|
||||
[a]
|
||||
class = frappy_psi.ls370res.ResChannel
|
||||
channel = 1
|
||||
description = resistivity
|
||||
switcher = sw
|
||||
|
||||
[b]
|
||||
class = frappy_psi.ls370res.ResChannel
|
||||
channel = 3
|
||||
description = resistivity
|
||||
switcher = sw
|
||||
30
cfg/ls370sim_cfg.py
Normal file
30
cfg/ls370sim_cfg.py
Normal file
@@ -0,0 +1,30 @@
|
||||
Node('LscSIM.psi.ch',
|
||||
'Lsc Simulation at PSI',
|
||||
'tcp://5000',
|
||||
)
|
||||
|
||||
Mod('lscom',
|
||||
'frappy_psi.ls370sim.Ls370Sim',
|
||||
'simulated serial communicator to a LS 370',
|
||||
visibility = 3
|
||||
)
|
||||
|
||||
Mod('sw',
|
||||
'frappy_psi.ls370res.Switcher',
|
||||
'channel switcher for Lsc controller',
|
||||
io = 'lscom',
|
||||
)
|
||||
|
||||
Mod('a',
|
||||
'frappy_psi.ls370res.ResChannel',
|
||||
'resistivity',
|
||||
channel = 1,
|
||||
switcher = 'sw',
|
||||
)
|
||||
|
||||
Mod('b',
|
||||
'frappy_psi.ls370res.ResChannel',
|
||||
'resistivity',
|
||||
channel = 3,
|
||||
switcher = 'sw',
|
||||
)
|
||||
@@ -1,21 +0,0 @@
|
||||
[node LscSIM.psi.ch]
|
||||
description = Lsc370 Test
|
||||
|
||||
[interface tcp]
|
||||
type = tcp
|
||||
bindto = 0.0.0.0
|
||||
bindport = 5000
|
||||
|
||||
[module lsmain]
|
||||
class = frappy_psi.ls370res.Main
|
||||
description = main control of Lsc controller
|
||||
uri = localhost:4567
|
||||
|
||||
[module res]
|
||||
class = frappy_psi.ls370res.ResChannel
|
||||
vexc = '2mV'
|
||||
channel = 3
|
||||
description = resistivity
|
||||
main = lsmain
|
||||
# the auto created iodev from lsmain:
|
||||
iodev = lsmain_iodev
|
||||
29
cfg/ls370test_cfg.py
Normal file
29
cfg/ls370test_cfg.py
Normal file
@@ -0,0 +1,29 @@
|
||||
Node('LscSIM.psi.ch',
|
||||
'Lsc370 Test',
|
||||
'tcp://5000',
|
||||
)
|
||||
|
||||
Mod('io',
|
||||
'frappy_psi.ls370res.StringIO',
|
||||
'io for Ls370',
|
||||
uri = 'localhost:2089',
|
||||
)
|
||||
Mod('sw',
|
||||
'frappy_psi.ls370res.Switcher',
|
||||
'channel switcher',
|
||||
io = 'io',
|
||||
)
|
||||
Mod('res1',
|
||||
'frappy_psi.ls370res.ResChannel',
|
||||
'resistivity chan 1',
|
||||
vexc = '2mV',
|
||||
channel = 1,
|
||||
switcher = 'sw',
|
||||
)
|
||||
Mod('res2',
|
||||
'frappy_psi.ls370res.ResChannel',
|
||||
'resistivity chn 3',
|
||||
vexc = '2mV',
|
||||
channel = 3,
|
||||
switcher = 'sw',
|
||||
)
|
||||
@@ -1,8 +0,0 @@
|
||||
[NODE]
|
||||
class = protocol.router.Router
|
||||
id = multiplexer
|
||||
description = multiplexer node
|
||||
nodes = ['localhost:5000', 'localhost:10769']
|
||||
|
||||
[INTERFACE]
|
||||
uri = tcp://5000
|
||||
6
cfg/multiplexer_cfg.py
Normal file
6
cfg/multiplexer_cfg.py
Normal file
@@ -0,0 +1,6 @@
|
||||
Node('multiplexer',
|
||||
'multiplexer node',
|
||||
'tcp://5000',
|
||||
cls = 'protocol.router.Router',
|
||||
nodes = ['localhost:10768', 'localhost:10769'],
|
||||
)
|
||||
5
cfg/network_analyser_cfg.py
Normal file
5
cfg/network_analyser_cfg.py
Normal file
@@ -0,0 +1,5 @@
|
||||
import frappy.core as fc
|
||||
|
||||
Node('example_NA.psi.ch', 'A demo system showing how to connect network analysers', interface='tcp://5000')
|
||||
|
||||
Mod('ZVLNode', 'frappy_psi.network_analysers.ZVL.ZVLNode.ZVLNode', 'ZVL Network Analyser', analyser_ip=Param('169.254.150.182')) # must be connected on the same ethernet network
|
||||
14
cfg/nmr_scout_cfg.py
Normal file
14
cfg/nmr_scout_cfg.py
Normal file
@@ -0,0 +1,14 @@
|
||||
import frappy.core as fc
|
||||
|
||||
docstring = 'NMR pulse sequence.\
|
||||
Each pulse ``block`` consists of first a pulse with parameters pulse_width (microseconds), pulse_height (amplitude; for the SCOUT, percentage of max), and phase_cycle, and then a delay with parameter delay_time (microseconds).\
|
||||
\
|
||||
Please use the custom commands, ``generate_pulse``, ``scan_sequence``, and ``scan_sequences``. There exist example scripts in your NICOS install location/nicos_sinq/tnmr/example_scripts.\
|
||||
Durations are in microseconds, excepting post_acquisition_time (ms), and frequencies are in MHz.\
|
||||
'
|
||||
|
||||
Node('example_TNMR.psi.ch', 'The NMR system running the Scout and controlled with TNMR', interface='tcp://5000')
|
||||
|
||||
Mod('daq_scout', 'frappy_psi.tnmr.TNMRModule.TNMRModule', 'Tecmag SCOUT-based NMR System')
|
||||
|
||||
print('TNMR Module loaded - please refer to https://www.psi.ch/en/lin/nmr-spectroscopy for help and documentation')
|
||||
124
cfg/ppms.cfg
124
cfg/ppms.cfg
@@ -1,124 +0,0 @@
|
||||
[NODE]
|
||||
id = PPMS.psi.ch
|
||||
description = PPMS at PSI
|
||||
|
||||
[INTERFACE]
|
||||
uri = tcp://5000
|
||||
|
||||
[tt]
|
||||
class = frappy_psi.ppms.Temp
|
||||
description = main temperature
|
||||
io = ppms
|
||||
|
||||
[mf]
|
||||
class = frappy_psi.ppms.Field
|
||||
target.min = -9
|
||||
target.max = 9
|
||||
description = magnetic field
|
||||
io = ppms
|
||||
|
||||
[pos]
|
||||
class = frappy_psi.ppms.Position
|
||||
description = sample rotator
|
||||
io = ppms
|
||||
|
||||
[lev]
|
||||
class = frappy_psi.ppms.Level
|
||||
description = helium level
|
||||
io = ppms
|
||||
|
||||
[chamber]
|
||||
class = frappy_psi.ppms.Chamber
|
||||
description = chamber state
|
||||
io = ppms
|
||||
|
||||
[r1]
|
||||
class = frappy_psi.ppms.BridgeChannel
|
||||
description = resistivity channel 1
|
||||
no = 1
|
||||
value.unit = Ohm
|
||||
io = ppms
|
||||
|
||||
[r2]
|
||||
class = frappy_psi.ppms.BridgeChannel
|
||||
description = resistivity channel 2
|
||||
no = 2
|
||||
value.unit = Ohm
|
||||
io = ppms
|
||||
|
||||
[r3]
|
||||
class = frappy_psi.ppms.BridgeChannel
|
||||
description = resistivity channel 3
|
||||
no = 3
|
||||
value.unit = Ohm
|
||||
io = ppms
|
||||
|
||||
[r4]
|
||||
class = frappy_psi.ppms.BridgeChannel
|
||||
description = resistivity channel 4
|
||||
no = 4
|
||||
value.unit = Ohm
|
||||
io = ppms
|
||||
|
||||
[i1]
|
||||
class = frappy_psi.ppms.Channel
|
||||
description = current channel 1
|
||||
no = 1
|
||||
value.unit = uA
|
||||
io = ppms
|
||||
|
||||
[i2]
|
||||
class = frappy_psi.ppms.Channel
|
||||
description = current channel 2
|
||||
no = 2
|
||||
value.unit = uA
|
||||
io = ppms
|
||||
|
||||
[i3]
|
||||
class = frappy_psi.ppms.Channel
|
||||
description = current channel 3
|
||||
no = 3
|
||||
value.unit = uA
|
||||
io = ppms
|
||||
|
||||
[i4]
|
||||
class = frappy_psi.ppms.Channel
|
||||
description = current channel 4
|
||||
no = 4
|
||||
value.unit = uA
|
||||
io = ppms
|
||||
|
||||
[v1]
|
||||
class = frappy_psi.ppms.DriverChannel
|
||||
description = voltage channel 1
|
||||
no = 1
|
||||
value.unit = V
|
||||
io = ppms
|
||||
|
||||
[v2]
|
||||
class = frappy_psi.ppms.DriverChannel
|
||||
description = voltage channel 2
|
||||
no = 2
|
||||
value.unit = V
|
||||
io = ppms
|
||||
|
||||
[tv]
|
||||
class = frappy_psi.ppms.UserChannel
|
||||
description = VTI temperature
|
||||
enabled = 1
|
||||
value.unit = K
|
||||
io = ppms
|
||||
|
||||
[ts]
|
||||
class = frappy_psi.ppms.UserChannel
|
||||
description = sample temperature
|
||||
enabled = 1
|
||||
value.unit = K
|
||||
io = ppms
|
||||
|
||||
[ppms]
|
||||
class = frappy_psi.ppms.Main
|
||||
description = the main and poller module
|
||||
class_id = QD.MULTIVU.PPMS.1
|
||||
visibility = 3
|
||||
pollinterval = 2
|
||||
93
cfg/ppms_cfg.py
Normal file
93
cfg/ppms_cfg.py
Normal file
@@ -0,0 +1,93 @@
|
||||
Node('PPMS.psi.ch',
|
||||
'PPMS at PSI',
|
||||
'tcp://5000',
|
||||
)
|
||||
|
||||
Mod('tt',
|
||||
'frappy_psi.ppms.Temp',
|
||||
'main temperature',
|
||||
io = 'ppms',
|
||||
)
|
||||
|
||||
Mod('mf',
|
||||
'frappy_psi.ppms.Field',
|
||||
'magnetic field',
|
||||
target = Param(min=-9, max=9),
|
||||
io = 'ppms',
|
||||
)
|
||||
|
||||
Mod('pos',
|
||||
'frappy_psi.ppms.Position',
|
||||
'sample rotator',
|
||||
io = 'ppms',
|
||||
)
|
||||
|
||||
Mod('lev',
|
||||
'frappy_psi.ppms.Level',
|
||||
'helium level',
|
||||
io = 'ppms',
|
||||
)
|
||||
|
||||
Mod('chamber',
|
||||
'frappy_psi.ppms.Chamber',
|
||||
'chamber state',
|
||||
io = 'ppms',
|
||||
)
|
||||
|
||||
for i in range(1,5):
|
||||
Mod('r%d' % i,
|
||||
'frappy_psi.ppms.BridgeChannel',
|
||||
'resistivity channel %d' % i,
|
||||
no = i,
|
||||
value = Param(unit = 'Ohm'),
|
||||
io = 'ppms',
|
||||
)
|
||||
|
||||
for i in range(1,5):
|
||||
Mod('i%d' % i,
|
||||
'frappy_psi.ppms.Channel',
|
||||
'current channel %d' % i,
|
||||
no = i,
|
||||
value = Param(unit = 'uA'),
|
||||
io = 'ppms',
|
||||
)
|
||||
|
||||
Mod('v1',
|
||||
'frappy_psi.ppms.DriverChannel',
|
||||
'voltage channel 1',
|
||||
no = 1,
|
||||
value = Param(unit = 'V'),
|
||||
io = 'ppms',
|
||||
)
|
||||
|
||||
Mod('v2',
|
||||
'frappy_psi.ppms.DriverChannel',
|
||||
'voltage channel 2',
|
||||
no = 2,
|
||||
value = Param(unit = 'V'),
|
||||
io = 'ppms',
|
||||
)
|
||||
|
||||
Mod('tv',
|
||||
'frappy_psi.ppms.UserChannel',
|
||||
'VTI temperature',
|
||||
enabled = 1,
|
||||
value = Param(unit = 'K'),
|
||||
io = 'ppms',
|
||||
)
|
||||
|
||||
Mod('ts',
|
||||
'frappy_psi.ppms.UserChannel',
|
||||
'sample temperature',
|
||||
enabled = 1,
|
||||
value = Param(unit = 'K'),
|
||||
io = 'ppms',
|
||||
)
|
||||
|
||||
Mod('ppms',
|
||||
'frappy_psi.ppms.Main',
|
||||
'the main and poller module',
|
||||
class_id = 'QD.MULTIVU.PPMS.1',
|
||||
visibility = 3,
|
||||
pollinterval = 2,
|
||||
)
|
||||
@@ -1,22 +0,0 @@
|
||||
[node filtered.PPMS.psi.ch]
|
||||
description = filtered PPMS at PSI
|
||||
|
||||
[interface tcp]
|
||||
type = tcp
|
||||
bindto = 0.0.0.0
|
||||
bindport = 5002
|
||||
|
||||
[module secnode]
|
||||
class = frappy.SecNode
|
||||
description = a SEC node
|
||||
uri = tcp://localhost:5000
|
||||
|
||||
[module mf]
|
||||
class = frappy.Proxy
|
||||
remote_class = frappy_psi.ppms.Field
|
||||
description = magnetic field
|
||||
iodev = secnode
|
||||
value.min = -0.1
|
||||
value.max = 0.1
|
||||
target.min = -8
|
||||
target.max = 8
|
||||
21
cfg/ppms_proxy_test_cfg.py
Normal file
21
cfg/ppms_proxy_test_cfg.py
Normal file
@@ -0,0 +1,21 @@
|
||||
Node('filtered.PPMS.psi.ch',
|
||||
'filtered PPMS at PSI',
|
||||
'tcp://5002',
|
||||
)
|
||||
|
||||
|
||||
Mod('secnode',
|
||||
'frappy.proxy.SecNode',
|
||||
'a SEC node',
|
||||
uri = 'tcp://localhost:5000',
|
||||
)
|
||||
|
||||
Mod('mf',
|
||||
'frappy.proxy.Proxy',
|
||||
'magnetic field',
|
||||
remote_class = 'frappy_psi.ppms.Field',
|
||||
io = 'secnode',
|
||||
|
||||
value = Param(),
|
||||
target = Param(min=-0.1, max=0.1),
|
||||
)
|
||||
34
cfg/psi_nmr_setup_cfg.py
Normal file
34
cfg/psi_nmr_setup_cfg.py
Normal file
@@ -0,0 +1,34 @@
|
||||
import frappy.core as fc
|
||||
|
||||
enabled_modules = [ 'TNMR', 'Razorbill', 'Capacitance', 'NetAnalyser' ] # default
|
||||
#enabled_modules = [ 'Capacitance' ]
|
||||
|
||||
Node('uniaxial_nmr.psi.ch',
|
||||
'The NMR system running the Scout and controlled with TNMR. Also contains a uniaxial cell (Razorbill RP100), a network analyser (ZVL), and a capacitance reader (for more direct measurements of the strain cell\'s status) (TSSOP16 with Arduino Nano V3)',
|
||||
interface='tcp://5000')
|
||||
|
||||
# TNMR
|
||||
if('TNMR' in enabled_modules):
|
||||
Mod('tnmr_otf_module', 'frappy_psi.tnmr.OTFModule.ProgrammedSequence', 'NMR Sequence')
|
||||
|
||||
# Razorbill RP100
|
||||
if('Razorbill' in enabled_modules):
|
||||
Mod('io_razorbill',
|
||||
'frappy_psi.uniaxial_cell.RP100.RP100IO',
|
||||
'communication',
|
||||
uri='serial://COM3?baudrate=9600+bytesize=8+parity=none+stopbits=1')
|
||||
Mod('RP100Node_CH1', 'frappy_psi.uniaxial_cell.RP100.VoltageChannel', 'Razorbill RP100 PSU (CH1)', channel=1, io='io_razorbill')
|
||||
Mod('RP100Node_CH2', 'frappy_psi.uniaxial_cell.RP100.VoltageChannel', 'Razorbill RP100 PSU (CH2)', channel=2, io='io_razorbill')
|
||||
|
||||
# Capacitance readings (TSSOP16 with Arduino Nano V3)
|
||||
if('Capacitance' in enabled_modules):
|
||||
Mod('io_capacitance',
|
||||
'frappy_psi.capacitance_readings.TSSOP16.TSSOP16_IO',
|
||||
'communication',
|
||||
uri='serial://COM5?baudrate=9600+bytesize=8+parity=none+stopbits=1')
|
||||
|
||||
Mod('TSSOP16', 'frappy_psi.capacitance_readings.TSSOP16.TSSOP16', 'Capacitance-reading Arduino (with TSSOP16)', io='io_capacitance')
|
||||
|
||||
# ZVL Network Analyser
|
||||
if('NetAnalyser' in enabled_modules):
|
||||
Mod('ZVLNode', 'frappy_psi.network_analysers.ZVL.ZVLNode.ZVLNode', 'ZVL Network Analyser', analyser_ip=Param('129.129.156.201')) # must be connected on the same ethernet network
|
||||
12
cfg/razorbill_rp100_cfg.py
Normal file
12
cfg/razorbill_rp100_cfg.py
Normal file
@@ -0,0 +1,12 @@
|
||||
import frappy.core as fc
|
||||
import serial
|
||||
|
||||
Node('example_RP100.psi.ch', 'A demo system showing how to connect the Razorbill PSU', interface='tcp://5000')
|
||||
|
||||
Mod('io1',
|
||||
'frappy_psi.uniaxial_cell.RP100.RP100IO',
|
||||
'communication',
|
||||
uri='serial://COM10?baudrate=9600+bytesize=8+parity=none+stopbits=1')
|
||||
|
||||
Mod('RP100Node_CH1', 'frappy_psi.uniaxial_cell.RP100.VoltageChannel', 'Razorbill RP100 PSU (CH1)', channel=1, io='io1')
|
||||
Mod('RP100Node_CH2', 'frappy_psi.uniaxial_cell.RP100.VoltageChannel', 'Razorbill RP100 PSU (CH2)', channel=2, io='io1')
|
||||
@@ -1,8 +0,0 @@
|
||||
[NODE]
|
||||
id = router
|
||||
class = protocol.router.Router
|
||||
description = router node
|
||||
node = localhost:5000
|
||||
|
||||
[INTERFACE]
|
||||
uri=tcp://5002
|
||||
6
cfg/router_cfg.py
Normal file
6
cfg/router_cfg.py
Normal file
@@ -0,0 +1,6 @@
|
||||
Node('router',
|
||||
'router node',
|
||||
'tcp://5002',
|
||||
cls = 'protocol.router.Router',
|
||||
node = 'localhost:5000',
|
||||
)
|
||||
43
cfg/seop_cfg.py
Normal file
43
cfg/seop_cfg.py
Normal file
@@ -0,0 +1,43 @@
|
||||
description = """
|
||||
3He system in Lab ...
|
||||
"""
|
||||
Node('mlz_seop',
|
||||
description,
|
||||
'tcp://10767',
|
||||
)
|
||||
|
||||
Mod('cell',
|
||||
'frappy_mlz.seop.Cell',
|
||||
'interface module to the driver',
|
||||
config_directory = '/home/jcns/daemon/config',
|
||||
)
|
||||
|
||||
Mod('afp',
|
||||
'frappy_mlz.seop.Afp',
|
||||
'controls the afp flip of the cell',
|
||||
cell = 'cell'
|
||||
)
|
||||
|
||||
Mod('nmr',
|
||||
'frappy_mlz.seop.Nmr',
|
||||
'controls the ',
|
||||
cell = 'cell'
|
||||
)
|
||||
|
||||
fitparams = [
|
||||
('amplitude', 'V'),
|
||||
('T1', 's'),
|
||||
('T2', 's'),
|
||||
('b', ''),
|
||||
('frequency', 'Hz'),
|
||||
('phase', 'deg'),
|
||||
]
|
||||
for param, unit in fitparams:
|
||||
Mod(f'nmr_{param.lower()}',
|
||||
'frappy_mlz.seop.FitParam',
|
||||
f'fittet parameter {param} of NMR',
|
||||
cell = 'cell',
|
||||
value = Param(unit=unit),
|
||||
sigma = Param(unit=unit),
|
||||
param = param,
|
||||
)
|
||||
23
cfg/sim.cfg
23
cfg/sim.cfg
@@ -1,23 +0,0 @@
|
||||
[node test config]
|
||||
description=description of the simulation sec-node
|
||||
.
|
||||
Testing simulation dummy setup.
|
||||
|
||||
[interface tcp]
|
||||
type=tcp
|
||||
bindto=0.0.0.0
|
||||
bindport=10767
|
||||
|
||||
|
||||
[module sim]
|
||||
class=frappy.simulation.SimDrivable
|
||||
.description=simulation stuff
|
||||
.extra_params=param3,param4,jitter,ramp
|
||||
param3.datatype={"type":"bool"}
|
||||
param3.default=True
|
||||
param3.readonly=False
|
||||
jitter.default=1
|
||||
ramp.default=60
|
||||
value.default=123
|
||||
target.default=42
|
||||
|
||||
18
cfg/sim_cfg.py
Normal file
18
cfg/sim_cfg.py
Normal file
@@ -0,0 +1,18 @@
|
||||
Node('sim.test.config',
|
||||
'description of the simulation sec-node\n'
|
||||
'\n'
|
||||
'Testing simulation dummy setup.',
|
||||
'tcp://10767',
|
||||
)
|
||||
|
||||
|
||||
Mod('sim',
|
||||
'frappy.simulation.SimDrivable',
|
||||
'simulation stuff',
|
||||
extra_params = 'param3,param4,jitter,ramp',
|
||||
param3 = Param(default=True, datatype={'type': 'bool'}, readonly=False),
|
||||
jitter = 1,
|
||||
ramp = 60,
|
||||
value = 123,
|
||||
target = 42,
|
||||
)
|
||||
@@ -1,116 +0,0 @@
|
||||
[node SIM_MLZ_amagnet(Garfield)]
|
||||
description=MLZ-Amagnet
|
||||
.
|
||||
Water cooled magnet from ANTARES@MLZ.
|
||||
.
|
||||
Use module to control the magnetic field.
|
||||
Don't forget to select symmetry first (can be moved only at zero field!).
|
||||
.
|
||||
Monitor T1..T4 (Coil temps), if they get to hot, field will ramp down!
|
||||
.
|
||||
In case of Problems, contact the ANTARES people at MLZ.
|
||||
|
||||
visibility=expert
|
||||
foo=bar
|
||||
|
||||
[interface tcp]
|
||||
type=tcp
|
||||
bindto=0.0.0.0
|
||||
bindport=10767
|
||||
|
||||
[module enable]
|
||||
class=frappy.simulation.SimWritable
|
||||
value.datatype={"type":"enum", "members":{'On':1,'Off':0}}
|
||||
target.datatype={"type":"enum", "members":{'On':1,'Off':0}}
|
||||
.description='Enables to Output of the Powersupply'
|
||||
.visibility='advanced'
|
||||
|
||||
[module polarity]
|
||||
class=frappy.simulation.SimWritable
|
||||
value.datatype={"type":"enum", "members":{'+1':1,'0':0,'-1':-1}}
|
||||
target.datatype={"type":"enum", "members":{'+1':1,'0':0,'-1':-1}}
|
||||
.description=polarity (+/-) switch
|
||||
.
|
||||
there is an interlock in the plc:
|
||||
if there is current, switching polarity is forbidden
|
||||
if polarity is short, powersupply is disabled
|
||||
.visibility=advanced
|
||||
|
||||
|
||||
[module symmetry]
|
||||
class=frappy.simulation.SimWritable
|
||||
value.datatype={"type":"enum", "members":{'symmetric':1,'short':0, 'asymmetric':-1}}
|
||||
target.datatype={"type":"enum", "members":{'symmetric':1,'short':0, 'asymmetric':-1}}
|
||||
.description=par/ser switch selecting (a)symmetric mode
|
||||
.
|
||||
note: on the front panel symmetric is ser, asymmetric is par
|
||||
.visibility=advanced
|
||||
value.default = 'symmetric'
|
||||
|
||||
[module T1]
|
||||
class=frappy.simulation.SimReadable
|
||||
.description=Temperature1 of the coils system
|
||||
value.unit='degC'
|
||||
value.default = 23.45
|
||||
|
||||
[module T2]
|
||||
class=frappy.simulation.SimReadable
|
||||
.description=Temperature2 of the coils system
|
||||
value.unit='degC'
|
||||
value.default = 23.45
|
||||
|
||||
[module T3]
|
||||
class=frappy.simulation.SimReadable
|
||||
.description=Temperature3 of the coils system
|
||||
value.unit='degC'
|
||||
value.default = 23.45
|
||||
|
||||
[module T4]
|
||||
class=frappy.simulation.SimReadable
|
||||
.description=Temperature4 of the coils system
|
||||
value.unit='degC'
|
||||
value.default = 23.45
|
||||
|
||||
[module currentsource]
|
||||
class=frappy.simulation.SimDrivable
|
||||
.description=Device for the magnet power supply (current mode)
|
||||
abslimits=(0,200)
|
||||
speed=1
|
||||
ramp=60
|
||||
precision=0.02
|
||||
current=0
|
||||
voltage=10
|
||||
.visibility=advanced
|
||||
.extra_params = abslimits, speed, ramp, precision, current, voltage, window
|
||||
abslimits.datatype = {"type":"limit", "members":{"type":"double", "min":0, "max":200, "unit":"A"}}
|
||||
abslimits.default = (0, 200)
|
||||
speed.datatype = {"type":"double", "min":0, "max":10, "unit":"A/s"}
|
||||
speed.default = 10
|
||||
ramp.datatype = {"type":"double", "min":0, "max":600, "unit":"A/min"}
|
||||
ramp.default = 600
|
||||
precision.datatype = {"type":"double", "unit":"A"}
|
||||
precision.default = 0.1
|
||||
current.datatype = {"type":"double", "min":0, "max":200, "unit":"A"}
|
||||
current.default = 0
|
||||
voltage.datatype = {"type":"double", "min":0, "max":10, "unit":"V"}
|
||||
voltage.default = 0
|
||||
window.datatype = {"type":"double", "min":0, "max":120, "unit":"s"}
|
||||
window.default = 10
|
||||
|
||||
[module mf]
|
||||
class=frappy_mlz.amagnet.GarfieldMagnet
|
||||
.description=magnetic field module, handling polarity switching and stuff
|
||||
subdev_currentsource=currentsource
|
||||
subdev_enable=enable
|
||||
subdev_polswitch=polarity
|
||||
subdev_symmetry=symmetry
|
||||
target.unit='T'
|
||||
value.unit='T'
|
||||
userlimits=(-0.35, 0.35)
|
||||
calibrationtable={'symmetric':[0.00186517, 0.0431937, -0.185956, 0.0599757, 0.194042],
|
||||
'short': [0.0, 0.0, 0.0, 0.0, 0.0],
|
||||
'asymmetric':[0.00136154, 0.027454, -0.120951, 0.0495289, 0.110689]}
|
||||
.meaning=["magneticfield", 20]
|
||||
.visibility=user
|
||||
|
||||
abslimits.default=-0.4,0.4
|
||||
116
cfg/sim_mlz_amagnet_cfg.py
Normal file
116
cfg/sim_mlz_amagnet_cfg.py
Normal file
@@ -0,0 +1,116 @@
|
||||
Node('SIM_MLZ_amagnet(Garfield)',
|
||||
'MLZ-Amagnet\n'
|
||||
'\n'
|
||||
'Water cooled magnet from ANTARES@MLZ.\n'
|
||||
'\n'
|
||||
' Use module to control the magnetic field.\n'
|
||||
'Don\'t forget to select symmetry first (can be moved only at zero field!).\n'
|
||||
'\n'
|
||||
'Monitor T1..T4 (Coil temps), if they get to hot, field will ramp down!\n'
|
||||
'\n'
|
||||
'In case of Problems, contact the ANTARES people at MLZ.',
|
||||
'tcp://10767',
|
||||
visibility = 'expert',
|
||||
foo = 'bar',
|
||||
)
|
||||
|
||||
Mod('enable',
|
||||
'frappy.simulation.SimWritable',
|
||||
'Enables to Output of the Powersupply',
|
||||
value = Param(datatype={"type":"enum", "members":{'On':1,'Off':0}}),
|
||||
target = Param(datatype={"type":"enum", "members":{'On':1,'Off':0}}),
|
||||
visibility = 'advanced',
|
||||
)
|
||||
|
||||
Mod('polarity',
|
||||
'frappy.simulation.SimWritable',
|
||||
'polarity (+/-) switch\n'
|
||||
'\n'
|
||||
'there is an interlock in the plc:\n'
|
||||
'if there is current, switching polarity is forbidden\n'
|
||||
'if polarity is short, powersupply is disabled',
|
||||
value = Param(datatype={"type":"enum", "members":{'+1':1,'0':0,'-1':-1}}),
|
||||
target = Param(datatype={"type":"enum", "members":{'+1':1,'0':0,'-1':-1}}),
|
||||
visibility = 'advanced',
|
||||
)
|
||||
|
||||
|
||||
Mod('symmetry',
|
||||
'frappy.simulation.SimWritable',
|
||||
'par/ser switch selecting (a)symmetric mode\n'
|
||||
'\n'
|
||||
'note: on the front panel symmetric is ser, asymmetric is par',
|
||||
value = Param(
|
||||
datatype={"type":"enum", "members":{'symmetric':1,'short':0, 'asymmetric':-1}},
|
||||
default = 'symmetric'
|
||||
),
|
||||
target = Param(datatype={"type":"enum", "members":{'symmetric':1,'short':0, 'asymmetric':-1}}),
|
||||
visibility = 'advanced',
|
||||
)
|
||||
|
||||
for i in range(1,5):
|
||||
Mod('T%d' % i,
|
||||
'frappy.simulation.SimReadable',
|
||||
'Temperature%d of the coils system' % i,
|
||||
value = Param(default = 23.45, unit='degC'),
|
||||
)
|
||||
|
||||
Mod('currentsource',
|
||||
'frappy.simulation.SimDrivable',
|
||||
'Device for the magnet power supply (current mode)',
|
||||
value = 0,
|
||||
#abslimits = (0,200),
|
||||
#speed = 1,
|
||||
#ramp = 60,
|
||||
#precision = 0.02,
|
||||
#current = 0,
|
||||
#voltage = 10,
|
||||
visibility = 'advanced',
|
||||
extra_params = 'abslimits, speed, ramp, precision, current, voltage, window',
|
||||
abslimits = Param(
|
||||
default = (0, 200),
|
||||
datatype = {"type":"limit", "members":{"type":"double", "min":0, "max":200, "unit":"A"}}
|
||||
),
|
||||
speed = Param(
|
||||
default = 10,
|
||||
datatype = {"type":"double", "min":0, "max":10, "unit":"A/s"}
|
||||
),
|
||||
ramp = Param(
|
||||
default = 600,
|
||||
datatype = {"type":"double", "min":0, "max":600, "unit":"A/min"}
|
||||
),
|
||||
precision = Param(
|
||||
default = 0.1,
|
||||
datatype = {"type":"double", "unit":"A"}
|
||||
),
|
||||
current = Param(
|
||||
default = 0,
|
||||
datatype = {"type":"double", "min":0, "max":200, "unit":"A"}
|
||||
),
|
||||
voltage = Param(
|
||||
default = 0,
|
||||
datatype = {"type":"double", "min":0, "max":10, "unit":"V"}
|
||||
),
|
||||
window = Param(
|
||||
default = 10,
|
||||
datatype = {"type":"double", "min":0, "max":120, "unit":"s"}
|
||||
)
|
||||
)
|
||||
|
||||
Mod('mf',
|
||||
'frappy_mlz.amagnet.GarfieldMagnet',
|
||||
'magnetic field module, handling polarity switching and stuff',
|
||||
currentsource='currentsource',
|
||||
enable='enable',
|
||||
polswitch='polarity',
|
||||
symmetry='symmetry',
|
||||
target = Param(unit='T'),
|
||||
value = Param(unit='T'),
|
||||
userlimits=(-0.35, 0.35),
|
||||
calibrationtable={'symmetric':[0.00186517, 0.0431937, -0.185956, 0.0599757, 0.194042],
|
||||
'short': [0.0, 0.0, 0.0, 0.0, 0.0],
|
||||
'asymmetric':[0.00136154, 0.027454, -0.120951, 0.0495289, 0.110689]},
|
||||
meaning=("magneticfield", 20),
|
||||
visibility='user',
|
||||
abslimits=(-0.4,0.4),
|
||||
)
|
||||
@@ -1,93 +0,0 @@
|
||||
[node cci3he1]
|
||||
description = [sim] cci3he box of MLZ Sample environment group
|
||||
.
|
||||
Controls an 3He insert with an ls370 and an PLC controlling
|
||||
the compressor and the valves of the gas handling.
|
||||
|
||||
|
||||
[interface tcp]
|
||||
type=tcp
|
||||
bindto=0.0.0.0
|
||||
bindport=10767
|
||||
|
||||
[module T_cci3he1]
|
||||
class=frappy.simulation.SimDrivable
|
||||
.description=Main temperature control node of cci3he1.
|
||||
.
|
||||
Controls the regulation loop of the ls370.
|
||||
value.datatype={"type":"double","unit":"K"}
|
||||
value.default=300
|
||||
target.datatype={"type":"double", "min":0, "max":300, "unit":"K"}
|
||||
target.default=300
|
||||
.extra_params=ramp
|
||||
ramp.datatype={"type":"double","min":0,"max":600,"unit":"K/min"}
|
||||
ramp.description=target ramping speed in K/min.
|
||||
ramp.default=60
|
||||
.meaning=["temperature_regulation",40]
|
||||
|
||||
[module T_cci3he1_A]
|
||||
class=frappy.simulation.SimReadable
|
||||
.description=3He pot temperature sensor. Also used for the regulation.
|
||||
.visibility=expert
|
||||
value.default=300
|
||||
value.datatype={"type":"double","unit":"K"}
|
||||
.meaning=["temperature",38]
|
||||
|
||||
[module T_cci3he1_B]
|
||||
class=frappy.simulation.SimReadable
|
||||
.description=(optional) sample temperature sensor close to sample.
|
||||
.visibility=user
|
||||
value.default=300
|
||||
value.datatype={"type":"double","unit":"K"}
|
||||
.meaning=["temperature",39]
|
||||
|
||||
[module cci3he1_p1]
|
||||
class=frappy.simulation.SimReadable
|
||||
.description=Pressure at turbo pump inlet.
|
||||
.visibility=expert
|
||||
value.default=2e-3
|
||||
value.datatype={"type":"double","unit":"mbar"}
|
||||
|
||||
[module cci3he1_p2]
|
||||
class=frappy.simulation.SimReadable
|
||||
.description=Pressure at turbo pump outlet.
|
||||
.visibility=expert
|
||||
value.default=9.87
|
||||
value.datatype={"type":"double","unit":"mbar"}
|
||||
|
||||
[module cci3he1_p3]
|
||||
class=frappy.simulation.SimReadable
|
||||
.description=Pressure at compressor inlet.
|
||||
.visibility=expert
|
||||
value.default=19.99
|
||||
value.datatype={"type":"double","unit":"mbar"}
|
||||
|
||||
[module cci3he1_p4]
|
||||
class=frappy.simulation.SimReadable
|
||||
.description=Pressure at compressor outlet.
|
||||
.visibility=expert
|
||||
value.default=999
|
||||
value.datatype={"type":"double","unit":"mbar"}
|
||||
|
||||
[module cci3he1_p5]
|
||||
class=frappy.simulation.SimReadable
|
||||
.description=Pressure in dump tank.
|
||||
.visibility=expert
|
||||
value.default=567
|
||||
value.datatype={"type":"double","unit":"mbar"}
|
||||
|
||||
[module cci3he1_p6]
|
||||
class=frappy.simulation.SimReadable
|
||||
.description=Pressure in the vacuum dewar (ivc).
|
||||
.visibility=expert
|
||||
value.default=1e-3
|
||||
value.datatype={"type":"double","unit":"mbar"}
|
||||
|
||||
[module cci3he1_flow]
|
||||
class=frappy.simulation.SimReadable
|
||||
.description=Gas Flow (condensing line).
|
||||
.visibility=expert
|
||||
value.default=12.34
|
||||
value.datatype={"type":"double","unit":"mln/min"}
|
||||
|
||||
# note: all valves and switches are missing: use VNC to control them
|
||||
88
cfg/sim_mlz_cci3he1_cfg.py
Normal file
88
cfg/sim_mlz_cci3he1_cfg.py
Normal file
@@ -0,0 +1,88 @@
|
||||
Node('cci3he1',
|
||||
'[sim] cci3he box of MLZ Sample environment group\n'
|
||||
'\n'
|
||||
'Controls an 3He insert with an ls370 and an PLC controlling \n'
|
||||
'the compressor and the valves of the gas handling.',
|
||||
'tcp://10767',
|
||||
)
|
||||
|
||||
Mod('T_cci3he1',
|
||||
'frappy.simulation.SimDrivable',
|
||||
'Main temperature control node of cci3he1.\n'
|
||||
'\n'
|
||||
'Controls the regulation loop of the ls370.',
|
||||
value = Param(default=300, datatype={"type":"double","unit":"K"}),
|
||||
target = Param(default=300, datatype={"type":"double", "min":0, "max":300, "unit":"K"}),
|
||||
extra_params='ramp',
|
||||
ramp = Param(default=60,
|
||||
datatype={"type":"double","min":0,"max":600,"unit":"K/min"},
|
||||
description='target ramping speed in K/min.',
|
||||
),
|
||||
meaning=["temperature_regulation",40]
|
||||
)
|
||||
|
||||
Mod('T_cci3he1_A',
|
||||
'frappy.simulation.SimReadable',
|
||||
'3He pot temperature sensor. Also used for the regulation.',
|
||||
visibility='expert',
|
||||
value = Param(default=300, datatype={"type":"double","unit":"K"}),
|
||||
meaning=["temperature",38]
|
||||
)
|
||||
|
||||
Mod('T_cci3he1_B',
|
||||
'frappy.simulation.SimReadable',
|
||||
'(optional) sample temperature sensor close to sample.',
|
||||
visibility='user',
|
||||
value = Param(default=300, datatype={"type":"double","unit":"K"}),
|
||||
meaning=["temperature",39]
|
||||
)
|
||||
|
||||
Mod('cci3he1_p1',
|
||||
'frappy.simulation.SimReadable',
|
||||
'Pressure at turbo pump inlet.',
|
||||
value = Param(default=2e-3, datatype={"type":"double","unit":"mbar"}),
|
||||
visibility='expert',
|
||||
)
|
||||
|
||||
Mod('cci3he1_p2',
|
||||
'frappy.simulation.SimReadable',
|
||||
'Pressure at turbo pump outlet.',
|
||||
value = Param(default=9.87, datatype={"type":"double","unit":"mbar"}),
|
||||
visibility='expert',
|
||||
)
|
||||
|
||||
Mod('cci3he1_p3',
|
||||
'frappy.simulation.SimReadable',
|
||||
'Pressure at compressor inlet.',
|
||||
value = Param(default=19.99, datatype={"type":"double","unit":"mbar"}),
|
||||
visibility='expert',
|
||||
)
|
||||
|
||||
Mod('cci3he1_p4',
|
||||
'frappy.simulation.SimReadable',
|
||||
'Pressure at compressor outlet.',
|
||||
value = Param(default=999, datatype={"type":"double","unit":"mbar"}),
|
||||
visibility='expert',
|
||||
)
|
||||
|
||||
Mod('cci3he1_p5',
|
||||
'frappy.simulation.SimReadable',
|
||||
'Pressure in dump tank.',
|
||||
value = Param(default=567, datatype={"type":"double","unit":"mbar"}),
|
||||
visibility='expert',
|
||||
)
|
||||
|
||||
Mod('cci3he1_p6',
|
||||
'frappy.simulation.SimReadable',
|
||||
'Pressure in the vacuum dewar (ivc).',
|
||||
value = Param(default=1e-3, datatype={"type":"double","unit":"mbar"}),
|
||||
visibility='expert',
|
||||
)
|
||||
|
||||
Mod('cci3he1_flow',
|
||||
'frappy.simulation.SimReadable',
|
||||
'Gas Flow (condensing line).',
|
||||
value = Param(default=12.34, datatype={"type":"double","unit":"mln/min"}),
|
||||
visibility='expert',
|
||||
)
|
||||
# note: all valves and switches are missing: use VNC to control them
|
||||
@@ -1,107 +0,0 @@
|
||||
[node ccidu1]
|
||||
description = [sim] ccidu box of MLZ Sample environment group
|
||||
.
|
||||
Controls an 3He/4He dilution insert with an ls372 and an PLC controlling
|
||||
the compressor and the valves of the gas handling.
|
||||
|
||||
|
||||
[interface tcp]
|
||||
type=tcp
|
||||
bindto=0.0.0.0
|
||||
bindport=10767
|
||||
|
||||
[module T_ccidu1]
|
||||
class=frappy.simulation.SimDrivable
|
||||
.description=Main temperature control node of ccidu1.
|
||||
.
|
||||
Controls the regulation loop of the ls372.
|
||||
value.unit='K'
|
||||
value.default=300
|
||||
target.datatype={"type":"double", "min":0, "max":300, "unit":"K"}
|
||||
target.default=300
|
||||
.extra_params=ramp
|
||||
ramp.datatype={"type":"double", "min":0, "max":600, "unit":"K/min"}
|
||||
ramp.description=target ramping speed in K/min.
|
||||
ramp.default=60
|
||||
.meaning=["temperature_regulation",40]
|
||||
|
||||
[module T_ccidu1_A]
|
||||
class=frappy.simulation.SimReadable
|
||||
.description=mixing chamber temperature sensor. Also used for the regulation.
|
||||
.visibility=expert
|
||||
value.default=300
|
||||
value.datatype={"type":"double", "unit":"K"}
|
||||
.meaning=["temperature",38]
|
||||
|
||||
[module T_ccidu1_B]
|
||||
class=frappy.simulation.SimReadable
|
||||
.description=(optional) sample temperature sensor close to sample.
|
||||
.visibility=user
|
||||
value.default=300
|
||||
value.datatype={"type":"double", "unit":"K"}
|
||||
.meaning=["temperature",39]
|
||||
|
||||
[module ccidu1_pstill]
|
||||
class=frappy.simulation.SimReadable
|
||||
.description=Pressure at the still/turbo pump inlet.
|
||||
.visibility=expert
|
||||
value.default=999
|
||||
value.datatype={"type":"double", "unit":"mbar"}
|
||||
|
||||
[module ccidu1_pinlet]
|
||||
class=frappy.simulation.SimReadable
|
||||
.description=Pressure at forepump inlet/turbo pump outlet.
|
||||
.visibility=expert
|
||||
value.default=999
|
||||
value.datatype={"type":"double", "unit":"mbar"}
|
||||
|
||||
[module ccidu1_poutlet]
|
||||
class=frappy.simulation.SimReadable
|
||||
.description=Pressure at forepump outlet/compressor inlet.
|
||||
.visibility=expert
|
||||
value.default=999
|
||||
value.datatype={"type":"double", "unit":"mbar"}
|
||||
|
||||
[module ccidu1_pkond]
|
||||
class=frappy.simulation.SimReadable
|
||||
.description=Pressure at condensing line/compressor outlet.
|
||||
.visibility=expert
|
||||
value.default=999
|
||||
value.datatype={"type":"double", "unit":"mbar"}
|
||||
|
||||
[module ccidu1_ptank]
|
||||
class=frappy.simulation.SimReadable
|
||||
.description=Pressure in dump tank.
|
||||
.visibility=expert
|
||||
value.default=999
|
||||
value.datatype={"type":"double", "unit":"mbar"}
|
||||
|
||||
[module ccidu1_pvac]
|
||||
class=frappy.simulation.SimReadable
|
||||
.description=Pressure in the vacuum dewar (ivc).
|
||||
.visibility=expert
|
||||
value.default=999
|
||||
value.datatype={"type":"double", "unit":"mbar"}
|
||||
|
||||
[module ccidu1_flow]
|
||||
class=frappy.simulation.SimReadable
|
||||
.description=Gas Flow (condensing line).
|
||||
.visibility=expert
|
||||
value.default=999
|
||||
value.datatype={"type":"double", "unit":"mbar"}
|
||||
|
||||
# note: all valves and switches are missing: use VNC to control them
|
||||
[module ccidu1_V6]
|
||||
class=frappy.simulation.SimDrivable
|
||||
.description=Needle valve
|
||||
.visibility=expert
|
||||
value.default=99
|
||||
value.datatype={"type":"double", "min":0, "max":100, "unit":"%%"}
|
||||
|
||||
[module ccidu1_V3]
|
||||
class=frappy.simulation.SimWritable
|
||||
.description=Dump Valve
|
||||
.visibility=expert
|
||||
value.default="OFF"
|
||||
value.datatype={"type":"enum", "members":{"on": 1, "OFF":0}}
|
||||
target.datatype={"type":"enum", "members":{"on": 1, "OFF":0}}
|
||||
107
cfg/sim_mlz_ccidu1_cfg.py
Normal file
107
cfg/sim_mlz_ccidu1_cfg.py
Normal file
@@ -0,0 +1,107 @@
|
||||
Node('ccidu1',
|
||||
'[sim] ccidu box of MLZ Sample environment group\n'
|
||||
'\n'
|
||||
'Controls an 3He/4He dilution insert with an ls372 and an PLC controlling\n'
|
||||
'the compressor and the valves of the gas handling.',
|
||||
'tcp://10767',
|
||||
)
|
||||
|
||||
Mod('T_ccidu1',
|
||||
'frappy.simulation.SimDrivable',
|
||||
'Main temperature control node of ccidu1.\n'
|
||||
'\n'
|
||||
'Controls the regulation loop of the ls372.',
|
||||
value = Param(default=300, unit='K'),
|
||||
target = Param(default=300, datatype={"type":"double", "min":0, "max":300, "unit":"K"}),
|
||||
extra_params='ramp',
|
||||
ramp = Param(default=60,
|
||||
datatype={"type":"double", "min":0, "max":600, "unit":"K/min"},
|
||||
description='target ramping speed in K/min.',
|
||||
),
|
||||
meaning=["temperature_regulation",40]
|
||||
)
|
||||
|
||||
Mod('T_ccidu1_A',
|
||||
'frappy.simulation.SimReadable',
|
||||
'mixing chamber temperature sensor. Also used for the regulation.',
|
||||
value = Param(default=300, datatype={"type":"double", "unit":"K"}),
|
||||
visibility='expert',
|
||||
meaning=["temperature",38],
|
||||
)
|
||||
|
||||
Mod('T_ccidu1_B',
|
||||
'frappy.simulation.SimReadable',
|
||||
'(optional) sample temperature sensor close to sample.',
|
||||
value = Param(default=300, datatype={"type":"double", "unit":"K"}),
|
||||
visibility='user',
|
||||
meaning=["temperature",39],
|
||||
)
|
||||
|
||||
Mod('ccidu1_pstill',
|
||||
'frappy.simulation.SimReadable',
|
||||
'Pressure at the still/turbo pump inlet.',
|
||||
value = Param(default=999, datatype={"type":"double", "unit":"mbar"}),
|
||||
visibility='expert',
|
||||
)
|
||||
|
||||
Mod('ccidu1_pinlet',
|
||||
'frappy.simulation.SimReadable',
|
||||
'Pressure at forepump inlet/turbo pump outlet.',
|
||||
value = Param(default=999, datatype={"type":"double", "unit":"mbar"}),
|
||||
visibility='expert',
|
||||
)
|
||||
|
||||
Mod('ccidu1_poutlet',
|
||||
'frappy.simulation.SimReadable',
|
||||
'Pressure at forepump outlet/compressor inlet.',
|
||||
value = Param(default=999, datatype={"type":"double", "unit":"mbar"}),
|
||||
visibility='expert',
|
||||
)
|
||||
|
||||
Mod('ccidu1_pkond',
|
||||
'frappy.simulation.SimReadable',
|
||||
'Pressure at condensing line/compressor outlet.',
|
||||
value = Param(default=999, datatype={"type":"double", "unit":"mbar"}),
|
||||
visibility='expert',
|
||||
)
|
||||
|
||||
Mod('ccidu1_ptank',
|
||||
'frappy.simulation.SimReadable',
|
||||
'Pressure in dump tank.',
|
||||
value = Param(default=999, datatype={"type":"double", "unit":"mbar"}),
|
||||
visibility='expert',
|
||||
)
|
||||
|
||||
Mod('ccidu1_pvac',
|
||||
'frappy.simulation.SimReadable',
|
||||
'Pressure in the vacuum dewar (ivc).',
|
||||
value = Param(default=999, datatype={"type":"double", "unit":"mbar"}),
|
||||
visibility='expert',
|
||||
)
|
||||
|
||||
Mod('ccidu1_flow',
|
||||
'frappy.simulation.SimReadable',
|
||||
'Gas Flow (condensing line).',
|
||||
value = Param(default=999, datatype={"type":"double", "unit":"mbar"}),
|
||||
visibility='expert',
|
||||
)
|
||||
|
||||
# note: all valves and switches are missing: use VNC to control them
|
||||
Mod('ccidu1_V6',
|
||||
'frappy.simulation.SimDrivable',
|
||||
'Needle valve',
|
||||
value = Param(default=99, datatype={"type":"double", "min":0, "max":100, "unit":"%%"}),
|
||||
target = Param(min=0, max=100),
|
||||
visibility='expert',
|
||||
)
|
||||
|
||||
Mod('ccidu1_V3',
|
||||
'frappy.simulation.SimWritable',
|
||||
'Dump Valve',
|
||||
value = Param(
|
||||
default="OFF",
|
||||
datatype={"type":"enum", "members":{"on": 1, "OFF":0}}
|
||||
),
|
||||
target = Param(datatype={"type":"enum", "members":{"on": 1, "OFF":0}}),
|
||||
visibility='expert',
|
||||
)
|
||||
@@ -1,164 +0,0 @@
|
||||
[node ccr12]
|
||||
description = [sim] CCR12 box of MLZ Sample environment group
|
||||
.
|
||||
Contains a Lakeshore 336 and an PLC controlling the compressor
|
||||
and some valves.
|
||||
.
|
||||
This is how we use it now.
|
||||
|
||||
[interface tcp]
|
||||
type=tcp
|
||||
bindto=0.0.0.0
|
||||
bindport=10767
|
||||
|
||||
[module T_ccr12]
|
||||
class=frappy.simulation.SimDrivable
|
||||
.description=Main temperature control node of CCR12.
|
||||
.
|
||||
Switches between regulation on stick and regulation on tube depending on temperature requested.
|
||||
May also pump gas for higher temperatures, if configured.
|
||||
.
|
||||
Note: in nicos this is handled by its own class which manages T_ccr12_stick and T_ccr12_tube.
|
||||
in this simulation this module is isolated.
|
||||
.extra_params=ramp
|
||||
ramp.datatype={"type":"double", "min":0,"max":60, "unit":"K/min"}
|
||||
ramp.default=60
|
||||
value.datatype={"type":"double", "min":0,"max":600, "unit":"K"}
|
||||
value.default=300
|
||||
target.datatype={"type":"double", "min":0,"max":600, "unit":"K"}
|
||||
target.default=300
|
||||
.meaning=["temperature_regulation", 20]
|
||||
|
||||
[module T_ccr12_stick]
|
||||
class=frappy.simulation.SimDrivable
|
||||
.description=Temperature regulation for the sample stick in ccr12.
|
||||
.extra_params=ramp
|
||||
ramp.datatype={"type":"double", "min":0,"max":60, "unit":"K/min"}
|
||||
ramp.default=60
|
||||
value.datatype={"type":"double", "min":0,"max":600, "unit":"K"}
|
||||
value.default=300
|
||||
target.datatype={"type":"double", "min":0,"max":600, "unit":"K"}
|
||||
target.default=300
|
||||
.meaning=["temperature_regulation", 15]
|
||||
|
||||
[module T_ccr12_tube]
|
||||
class=frappy.simulation.SimDrivable
|
||||
.description=Temperature regulation for the tube of ccr12.
|
||||
.extra_params=ramp
|
||||
ramp.datatype={"type":"double", "min":0,"max":60, "unit":"K/min"}
|
||||
ramp.default=60
|
||||
value.datatype={"type":"double", "min":0,"max":600, "unit":"K"}
|
||||
value.default=300
|
||||
target.datatype={"type":"double", "min":0,"max":600, "unit":"K"}
|
||||
target.default=300
|
||||
.meaning=["temperature_regulation", 10]
|
||||
|
||||
[module T_ccr12_A]
|
||||
class=frappy.simulation.SimReadable
|
||||
.description=(optional) Sample temperature sensor.
|
||||
value.datatype={"type":"double", "unit":"K"}
|
||||
value.default=300
|
||||
.meaning=["temperature", 9]
|
||||
|
||||
[module T_ccr12_B]
|
||||
class=frappy.simulation.SimReadable
|
||||
.description=(regulation) temperature sensor on stick.
|
||||
value.datatype={"type":"double", "unit":"K"}
|
||||
value.default=300
|
||||
.meaning=["temperature", 10]
|
||||
|
||||
[module T_ccr12_C]
|
||||
class=frappy.simulation.SimReadable
|
||||
.description=Temperature at the coldhead.
|
||||
value.datatype={"type":"double", "unit":"K"}
|
||||
value.default=70
|
||||
.meaning=["temperature", 1]
|
||||
|
||||
[module T_ccr12_D]
|
||||
class=frappy.simulation.SimReadable
|
||||
.description=(regulation) temperature at coupling to stick.
|
||||
value.datatype={"type":"double", "unit":"K"}
|
||||
value.default=80
|
||||
.meaning=["temperature", 2]
|
||||
|
||||
|
||||
|
||||
[module ccr12_pressure_regulate]
|
||||
class=frappy.simulation.SimWritable
|
||||
.description=Selects on which Sensor the pressure regulation works, or switches it off.
|
||||
.visibility=expert
|
||||
value.datatype={"type":"enum", "members":{'off':0,'p1':1,'p2':2}}
|
||||
value.default='off'
|
||||
target.datatype={"type":"enum", "members":{'off':0,'p1':1,'p2':2}}
|
||||
target.default='off'
|
||||
|
||||
[module ccr12_compressor]
|
||||
class=frappy.simulation.SimDrivable
|
||||
.description=Switches the compressor for the cooling stage on or off.
|
||||
.
|
||||
Note: This should always be on, except for fast heatup for sample change.
|
||||
value.datatype={"type":"enum", "members":{'off':0,'on':1}}
|
||||
value.default='on'
|
||||
target.datatype={"type":"enum", "members":{'off':0,'on':1}}
|
||||
target.default='on'
|
||||
|
||||
[module ccr12_gas_switch]
|
||||
class=frappy.simulation.SimWritable
|
||||
.description=Switches the gas inlet on or off.
|
||||
.
|
||||
note: in reality this switches itself off after 15min.
|
||||
note: in reality this is interlocked with ccr12_vacuum_switch, only one can be on!
|
||||
note: in this simulation this module is isolated.
|
||||
value.datatype={"type":"enum", "members":{'off':0,'on':1}}
|
||||
value.default='off'
|
||||
target.datatype={"type":"enum", "members":{'off':0,'on':1}}
|
||||
target.default='off'
|
||||
|
||||
[module ccr12_vacuum_switch]
|
||||
class=frappy.simulation.SimWritable
|
||||
.description=Switches the vacuum pumping valve on or off.
|
||||
.
|
||||
note: in reality this is interlocked with ccr12_gas_switch, only one can be on!
|
||||
note: in this simulation this module is isolated.
|
||||
value.datatype={"type":"enum", "members":{'off':0,'on':1}}
|
||||
value.default='off'
|
||||
target.datatype={"type":"enum", "members":{'off':0,'on':1}}
|
||||
target.default='off'
|
||||
|
||||
[module ccr12_p1]
|
||||
class=frappy.simulation.SimReadable
|
||||
.description=Default pressure Sensor, linear scale 0..1000mbar
|
||||
value.datatype={"type":"double", "unit":"mbar"}
|
||||
value.default=999
|
||||
|
||||
[module ccr12_p2]
|
||||
class=frappy.simulation.SimReadable
|
||||
.description=Auxillary pressure Sensor.
|
||||
value.datatype={"type":"double", "unit":"mbar"}
|
||||
value.default=1e-6
|
||||
|
||||
[module ccr12_curve_p2]
|
||||
class=frappy.simulation.SimWritable
|
||||
.description=Curve for Aux pressure Sensor p2
|
||||
.visibility=expert
|
||||
value.default='TTR100'
|
||||
value.datatype={"type":"enum", "members":{'0..10V':0,'default':1,'0..9V':2,'DI200':3,'DI2000':4,'TTR100':7,'PTR90':8,'PTR225/PTR237':9,'ITR90':10,'ITR100 curve D':11, 'ITR100 curve 2':12, 'ITR100 curve 3':13,'ITR100 curve 4':14,'ITR100 curve 5':15, 'ITR100 curve 6':16, 'ITR100 curve 7':17, 'ITR100 curve 8':18, 'ITR100 curve 9':19, 'ITR100 curve A':20,'CMR361':21, 'CMR362':22, 'CMR363':23, 'CMR364':24, 'CMR365':25}}
|
||||
target.datatype={"type":"enum", "members":{'0..10V':0,'default':1,'0..9V':2,'DI200':3,'DI2000':4,'TTR100':7,'PTR90':8,'PTR225/PTR237':9,'ITR90':10,'ITR100 curve D':11, 'ITR100 curve 2':12, 'ITR100 curve 3':13,'ITR100 curve 4':14,'ITR100 curve 5':15, 'ITR100 curve 6':16, 'ITR100 curve 7':17, 'ITR100 curve 8':18, 'ITR100 curve 9':19, 'ITR100 curve A':20,'CMR361':21, 'CMR362':22, 'CMR363':23, 'CMR364':24, 'CMR365':25}}
|
||||
|
||||
[module ccr12_p1_limits]
|
||||
class=frappy.simulation.SimWritable
|
||||
.description=Limits for pressure regulation in P1.
|
||||
.visibility=expert
|
||||
value.datatype={"type":"limit","members":{"type":"double", "min":0,"max":1000, "unit":"mbar"}}
|
||||
value.default=[0,10]
|
||||
target.datatype={"type":"limit","members":{"type":"double", "min":0,"max":1000, "unit":"mbar"}}
|
||||
target.default=[0,10]
|
||||
|
||||
[module ccr12_p2_limits]
|
||||
class=frappy.simulation.SimWritable
|
||||
.description=Limits for pressure regulation in P2.
|
||||
.visibility=expert
|
||||
value.datatype={"type":"limit","members":{"type":"double", "min":0,"max":1000, "unit":"mbar"}}
|
||||
value.default=[1e-5,1e-3]
|
||||
target.datatype={"type":"limit","members":{"type":"double", "min":0,"max":1000, "unit":"mbar"}}
|
||||
target.default=[1e-5,1e-3]
|
||||
@@ -1,155 +0,0 @@
|
||||
[node ccr12]
|
||||
description = [sim] CCR12 box of MLZ Sample environment group
|
||||
.
|
||||
Contains a Lakeshore 336 and an PLC controlling the compressor
|
||||
and some valves.
|
||||
.
|
||||
This is an improved version, how we think it should be.
|
||||
|
||||
[interface tcp]
|
||||
type=tcp
|
||||
bindto=0.0.0.0
|
||||
bindport=10767
|
||||
|
||||
[module T_ccr12]
|
||||
class=frappy.simulation.SimDrivable
|
||||
.description=Main temperature control node of CCR12.
|
||||
.
|
||||
Switches between regulation on stick and regulation on tube depending on temperature requested.
|
||||
May also pump gas for higher temperatures, if configured.
|
||||
Manual switching of the regulation node is supported via the regulationmode parameter.
|
||||
value.datatype={"type":"double", "min":0, "max":600, "unit":"K"}
|
||||
value.default=300
|
||||
target.datatype={"type":"double", "min":0, "max":600, "unit":"K"}
|
||||
target.default=300
|
||||
.extra_params=ramp,regulationmode,abslimits,userlimits
|
||||
ramp.datatype={"type":"double", "min":0, "max":60, "unit":"K/min"}
|
||||
ramp.description=target ramping speed in K/min.
|
||||
ramp.default=60
|
||||
ramp.readonly=False
|
||||
regulationmode.datatype={"type":"enum","members":{"stick":1,"tube":2,"both":3}}
|
||||
regulationmode.default='both'
|
||||
regulationmode.description=regulate only stick, tube or select based upon the target value.
|
||||
regulationmode.readonly=False
|
||||
abslimits.datatype={"type":"limit","members":{"type":"double", "min":0,"max":600, "unit":"K"}}
|
||||
abslimits.default=[0,600]
|
||||
abslimits.description=currently active absolute limits for the setpoint. depend on the regulationmode parameter (both/stick->0..600, tube->0..300K).
|
||||
userlimits.datatype={"type":"limit","members":{"type":"double", "min":0,"max":600, "unit":"K"}}
|
||||
userlimits.default=[0,300]
|
||||
userlimits.description=current user set limits for the setpoint. must be inside abslimits.
|
||||
userlimits.readonly=False
|
||||
.meaning=["temperature_regulation", 20]
|
||||
|
||||
[module T_ccr12_A]
|
||||
class=frappy.simulation.SimReadable
|
||||
.description=(optional) Sample temperature sensor.
|
||||
.visibility=expert
|
||||
value.datatype={"type":"double", "min":0, "unit":"K"}
|
||||
value.default=300
|
||||
.meaning=["temperature", 9]
|
||||
|
||||
[module T_ccr12_B]
|
||||
class=frappy.simulation.SimReadable
|
||||
.description=(regulation) temperature sensor on stick.
|
||||
.visibility=expert
|
||||
value.datatype={"type":"double", "min":0, "unit":"K"}
|
||||
value.default=300
|
||||
.meaning=["temperature", 10]
|
||||
|
||||
[module T_ccr12_C]
|
||||
class=frappy.simulation.SimReadable
|
||||
.description=Temperature at the coldhead.
|
||||
.visibility=expert
|
||||
value.datatype={"type":"double", "min":0, "unit":"K"}
|
||||
value.default=70
|
||||
.meaning=["temperature", 1]
|
||||
|
||||
[module T_ccr12_D]
|
||||
class=frappy.simulation.SimReadable
|
||||
.description=(regulation) temperature at coupling to stick.
|
||||
.visibility=expert
|
||||
value.datatype={"type":"double", "min":0, "unit":"K"}
|
||||
value.default=80
|
||||
.meaning=["temperature", 2]
|
||||
|
||||
|
||||
|
||||
[module ccr12_pressure_regulation]
|
||||
class=frappy.simulation.SimReadable
|
||||
.description=Simple two-point presssure regulation. the mode parameter selects the readout on which to regulate, or 'none' for no regulation.
|
||||
.visibility=user
|
||||
.extra_params=switchpoints, mode
|
||||
mode.datatype={"type":"enum", "members":{"none":0,"p1":1,"p2":2}}
|
||||
mode.description=Select pressure sensor to regulate on, or 'none' to disable regulation.
|
||||
mode.default='none'
|
||||
# struct is more explicit, but ugly to read....
|
||||
switchpoints.datatype={"type":"struct", "members":{"lower":{"type":"double", "unit":"mbar"},"upper":{"type":"double", "unit":"mbar"}}, "optional":["upper","lower"]}
|
||||
switchpoints.description=Switching points for regulation. If the selected pressure is below 'lower' value, the gas valve is opened, above 'upper' the value vacuum valve is openend, else both are closed. values for switchpoints are taken from the selected pressure sensors userlimits.
|
||||
switchpoints.default={'lower':1e-6,'upper':1e-3}
|
||||
switchpoints.readonly=True
|
||||
value.datatype={"type":"double", "min":0, "max":1000, "unit":"mbar"}
|
||||
value.default = 1e-5
|
||||
|
||||
[module ccr12_compressor]
|
||||
class=frappy.simulation.SimDrivable
|
||||
.description=Switches the compressor for the cooling stage on or off.
|
||||
.
|
||||
Note: This should always be on, except for fast heatup for sample change.
|
||||
.visibility=user
|
||||
value.datatype={"type":"enum", "members":{'off':0,'on':1}}
|
||||
value.default='off'
|
||||
target.datatype={"type":"enum", "members":{'off':0,'on':1}}
|
||||
|
||||
[module ccr12_gas_switch]
|
||||
class=frappy.simulation.SimWritable
|
||||
.description=Switches the gas inlet on or off.
|
||||
.
|
||||
note: in reality this switches itself off after 15min.
|
||||
note: in reality this is interlocked with ccr12_vacuum_switch, only one can be on!
|
||||
note: in this simulation this module is isolated.
|
||||
value.datatype={"type":"enum", "members":{'off':0,'on':1}}
|
||||
value.default='off'
|
||||
target.datatype={"type":"enum", "members":{'off':0,'on':1}}
|
||||
target.default='off'
|
||||
|
||||
[module ccr12_vacuum_switch]
|
||||
class=frappy.simulation.SimWritable
|
||||
.description=Switches the vacuum pumping valve on or off.
|
||||
.
|
||||
note: in reality this is interlocked with ccr12_gas_switch, only one can be on!
|
||||
note: in this simulation this module is isolated.
|
||||
value.datatype={"type":"enum", "members":{'off':0,'on':1}}
|
||||
value.default='off'
|
||||
target.datatype={"type":"enum", "members":{'off':0,'on':1}}
|
||||
target.default='off'
|
||||
|
||||
[module ccr12_p1]
|
||||
class=frappy.simulation.SimReadable
|
||||
.description=Default pressure Sensor, linear scale 0..1000 mbar
|
||||
.
|
||||
Good candidate for a 'Sensor' Interface class!
|
||||
value.default=999
|
||||
value.unit=mbar
|
||||
.extra_params=curve, userlimits
|
||||
curve.datatype={"type":"enum", "members":{'0..10V':0,'default':1,'0..9V':2,'DI200':3,'DI2000':4,'TTR100':7,'PTR90':8,'PTR225/PTR237':9,'ITR90':10,'ITR100 curve D':11, 'ITR100 curve 2':12, 'ITR100 curve 3':13,'ITR100 curve 4':14,'ITR100 curve 5':15, 'ITR100 curve 6':16, 'ITR100 curve 7':17, 'ITR100 curve 8':18, 'ITR100 curve 9':19, 'ITR100 curve A':20,'CMR361':21, 'CMR362':22, 'CMR363':23, 'CMR364':24, 'CMR365':25}}
|
||||
curve.description=Calibration curve for pressure sensor
|
||||
curve.default='TTR100'
|
||||
userlimits.datatype={"type":"limit","members":{"type":"double", "min":0,"max":1000, "unit":"mbar"}}
|
||||
userlimits.default=[1, 100]
|
||||
userlimits.description=current user set limits for the pressure regulation.
|
||||
userlimits.readonly=False
|
||||
|
||||
[module ccr12_p2]
|
||||
class=frappy.simulation.SimReadable
|
||||
.description=Auxillary pressure Sensor.
|
||||
value.default=1e-6
|
||||
value.unit=mbar
|
||||
.extra_params=curve,userlimits
|
||||
curve.datatype={"type":"enum", "members":{'0..10V':0,'default':1,'0..9V':2,'DI200':3,'DI2000':4,'TTR100':7,'PTR90':8,'PTR225/PTR237':9,'ITR90':10,'ITR100 curve D':11, 'ITR100 curve 2':12, 'ITR100 curve 3':13,'ITR100 curve 4':14,'ITR100 curve 5':15, 'ITR100 curve 6':16, 'ITR100 curve 7':17, 'ITR100 curve 8':18, 'ITR100 curve 9':19, 'ITR100 curve A':20,'CMR361':21, 'CMR362':22, 'CMR363':23, 'CMR364':24, 'CMR365':25}}
|
||||
curve.description=Calibration curve for pressure sensor
|
||||
curve.default='PTR90'
|
||||
userlimits.datatype={"type":"limit","members":{"type":"double", "min":0,"max":1000, "unit":"mbar"}}
|
||||
userlimits.default=[1e-6, 1e-3]
|
||||
userlimits.description=current user set limits for the pressure regulation.
|
||||
userlimits.readonly=False
|
||||
pollinterval.visibility='expert'
|
||||
177
cfg/sim_mlz_ccr12_v2_cfg.py
Normal file
177
cfg/sim_mlz_ccr12_v2_cfg.py
Normal file
@@ -0,0 +1,177 @@
|
||||
# pylint: skip-file
|
||||
Node('ccr12',
|
||||
'[sim] CCR12 box of MLZ Sample environment group'
|
||||
''
|
||||
'Contains a Lakeshore 336 and an PLC controlling the compressor'
|
||||
'and some valves.'
|
||||
''
|
||||
'This is an improved version, how we think it should be.',
|
||||
'tcp://10767',
|
||||
)
|
||||
|
||||
Mod('T_ccr12',
|
||||
'frappy.simulation.SimDrivable',
|
||||
'Main temperature control node of CCR12.'
|
||||
''
|
||||
'Switches between regulation on stick and regulation on tube depending on temperature requested.'
|
||||
'May also pump gas for higher temperatures, if configured.'
|
||||
'Manual switching of the regulation node is supported via the regulationmode parameter.',
|
||||
value = Param(default=300,
|
||||
datatype={"type":"double", "min":0, "max":600, "unit":"K"}),
|
||||
target = Param(default=300,
|
||||
datatype={"type":"double", "min":0, "max":600, "unit":"K"}),
|
||||
extra_params='ramp,regulationmode,abslimits,userlimits',
|
||||
ramp = Param(
|
||||
default=60,
|
||||
datatype={"type":"double", "min":0, "max":60, "unit":"K/min"},
|
||||
description='target ramping speed in K/min.',
|
||||
readonly=False,
|
||||
),
|
||||
regulationmode = Param(
|
||||
default='both',
|
||||
datatype={"type":"enum","members":{"stick":1,"tube":2,"both":3}},
|
||||
description='regulate only stick, tube or select based upon the target value.',
|
||||
readonly=False,
|
||||
),
|
||||
abslimits = Param(
|
||||
default=[0,600],
|
||||
datatype={"type":"limit","members":{"type":"double", "min":0,"max":600, "unit":"K"}},
|
||||
description='currently active absolute limits for the setpoint. depend on the regulationmode parameter (both/stick->0..600, tube->0..300K).',
|
||||
),
|
||||
userlimits = Param(
|
||||
default=[0,300],
|
||||
datatype={"type":"limit","members":{"type":"double", "min":0,"max":600, "unit":"K"}},
|
||||
description='current user set limits for the setpoint. must be inside abslimits.',
|
||||
readonly=False,
|
||||
),
|
||||
meaning=["temperature_regulation", 20],
|
||||
)
|
||||
|
||||
Mod('T_ccr12_A',
|
||||
'frappy.simulation.SimReadable',
|
||||
'(optional) Sample temperature sensor.',
|
||||
value = Param(default=300,
|
||||
datatype={"type":"double", "min":0, "unit":"K"}),
|
||||
visibility='expert',
|
||||
meaning=["temperature", 9],
|
||||
)
|
||||
|
||||
Mod('T_ccr12_B',
|
||||
'frappy.simulation.SimReadable',
|
||||
'(regulation) temperature sensor on stick.',
|
||||
value = Param(default=300, datatype={"type":"double", "min":0, "unit":"K"}),
|
||||
visibility='expert',
|
||||
meaning=["temperature", 10],
|
||||
)
|
||||
|
||||
Mod('T_ccr12_C',
|
||||
'frappy.simulation.SimReadable',
|
||||
'Temperature at the coldhead.',
|
||||
value = Param(default=70, datatype={"type":"double", "min":0, "unit":"K"}),
|
||||
visibility='expert',
|
||||
meaning=["temperature", 1],
|
||||
)
|
||||
|
||||
Mod('T_ccr12_D',
|
||||
'frappy.simulation.SimReadable',
|
||||
'(regulation) temperature at coupling to stick.',
|
||||
value = Param(default=80, datatype={"type":"double", "min":0, "unit":"K"}),
|
||||
visibility='expert',
|
||||
meaning=["temperature", 2],
|
||||
)
|
||||
|
||||
Mod('ccr12_pressure_regulation',
|
||||
'frappy.simulation.SimReadable',
|
||||
'Simple two-point presssure regulation. the mode parameter selects the readout on which to regulate, or, \'none\' for no regulation.',
|
||||
value = Param(default = 1e-5,
|
||||
datatype={"type":"double", "min":0, "max":1000, "unit":"mbar"}),
|
||||
extra_params='switchpoints, mode',
|
||||
mode = Param(
|
||||
default='none',
|
||||
datatype={"type":"enum", "members":{"none":0,"p1":1,"p2":2}},
|
||||
description='Select pressure sensor to regulate on, or \'none\' to disable regulation.',
|
||||
),
|
||||
switchpoints = Param(
|
||||
default={'lower':1e-6,'upper':1e-3},
|
||||
# struct is more explicit, but ugly to read....
|
||||
datatype={"type":"struct", "members":{"lower":{"type":"double", "unit":"mbar"},"upper":{"type":"double", "unit":"mbar"}}, "optional":["upper","lower"]},
|
||||
description='Switching points for regulation. If the selected pressure is below \'lower\' value, the gas valve is opened, above \'upper\' the value vacuum valve is openend, else both are closed. values for switchpoints are taken from the selected pressure sensors userlimits.',
|
||||
readonly=True,
|
||||
),
|
||||
visibility='user',
|
||||
)
|
||||
|
||||
Mod('ccr12_compressor',
|
||||
'frappy.simulation.SimDrivable',
|
||||
'Switches the compressor for the cooling stage on or off.\n'
|
||||
'\n'
|
||||
'Note: This should always be on, except for fast heatup for sample change.',
|
||||
value = Param(default='off',
|
||||
datatype={"type":"enum", "members":{'off':0,'on':1}}),
|
||||
target = Param(datatype={"type":"enum", "members":{'off':0,'on':1}}),
|
||||
visibility='user',
|
||||
)
|
||||
|
||||
Mod('ccr12_gas_switch',
|
||||
'frappy.simulation.SimWritable',
|
||||
'Switches the gas inlet on or off.\n'
|
||||
'\n'
|
||||
'note: in reality this switches itself off after 15min.\n'
|
||||
'note: in reality this is interlocked with ccr12_vacuum_switch, only one can be on!\n'
|
||||
'note: in this simulation this module is isolated.',
|
||||
value = Param(default='off',
|
||||
datatype={"type":"enum", "members":{'off':0,'on':1}}),
|
||||
target = Param(default='off',
|
||||
datatype={"type":"enum", "members":{'off':0,'on':1}}),
|
||||
)
|
||||
|
||||
Mod('ccr12_vacuum_switch',
|
||||
'frappy.simulation.SimWritable',
|
||||
'Switches the vacuum pumping valve on or off.\n'
|
||||
'\n'
|
||||
'note: in reality this is interlocked with ccr12_gas_switch, only one can be on!\n'
|
||||
'note: in this simulation this module is isolated.',
|
||||
value = Param(default='off',
|
||||
datatype={"type":"enum", "members":{'off':0,'on':1}}),
|
||||
target = Param(default='off',
|
||||
datatype={"type":"enum", "members":{'off':0,'on':1}}),
|
||||
)
|
||||
|
||||
Mod('ccr12_p1',
|
||||
'frappy.simulation.SimReadable',
|
||||
'Default pressure Sensor, linear scale 0..1000 mbar\n'
|
||||
'\n'
|
||||
'Good candidate for a \'Sensor\' Interface class!',
|
||||
value = Param(default=999, unit='mbar'),
|
||||
extra_params='curve, userlimits',
|
||||
curve = Param(
|
||||
default='TTR100',
|
||||
datatype={"type":"enum", "members":{'0..10V':0,'default':1,'0..9V':2,'DI200':3,'DI2000':4,'TTR100':7,'PTR90':8,'PTR225/PTR237':9,'ITR90':10,'ITR100 curve D':11, 'ITR100 curve 2':12, 'ITR100 curve 3':13,'ITR100 curve 4':14,'ITR100 curve 5':15, 'ITR100 curve 6':16, 'ITR100 curve 7':17, 'ITR100 curve 8':18, 'ITR100 curve 9':19, 'ITR100 curve A':20,'CMR361':21, 'CMR362':22, 'CMR363':23, 'CMR364':24, 'CMR365':25}},
|
||||
description='Calibration curve for pressure sensor',
|
||||
),
|
||||
userlimits = Param(
|
||||
default=[1, 100],
|
||||
datatype={"type":"limit","members":{"type":"double", "min":0,"max":1000, "unit":"mbar"}},
|
||||
description='current user set limits for the pressure regulation.',
|
||||
readonly=False,
|
||||
),
|
||||
)
|
||||
|
||||
Mod('ccr12_p2',
|
||||
'frappy.simulation.SimReadable',
|
||||
'Auxillary pressure Sensor.',
|
||||
value = Param(default=1e-6, unit='mbar'),
|
||||
extra_params='curve,userlimits',
|
||||
curve = Param(
|
||||
default='PTR90',
|
||||
datatype={"type":"enum", "members":{'0..10V':0,'default':1,'0..9V':2,'DI200':3,'DI2000':4,'TTR100':7,'PTR90':8,'PTR225/PTR237':9,'ITR90':10,'ITR100 curve D':11, 'ITR100 curve 2':12, 'ITR100 curve 3':13,'ITR100 curve 4':14,'ITR100 curve 5':15, 'ITR100 curve 6':16, 'ITR100 curve 7':17, 'ITR100 curve 8':18, 'ITR100 curve 9':19, 'ITR100 curve A':20,'CMR361':21, 'CMR362':22, 'CMR363':23, 'CMR364':24, 'CMR365':25}},
|
||||
description='Calibration curve for pressure sensor',
|
||||
),
|
||||
userlimits = Param(
|
||||
default=[1e-6, 1e-3],
|
||||
datatype={"type":"limit","members":{"type":"double", "min":0,"max":1000, "unit":"mbar"}},
|
||||
description='current user set limits for the pressure regulation.',
|
||||
readonly=False,
|
||||
),
|
||||
pollinterval = Param(visibility='expert'),
|
||||
)
|
||||
49
cfg/sim_mlz_entangle_simulation_cfg.py
Normal file
49
cfg/sim_mlz_entangle_simulation_cfg.py
Normal file
@@ -0,0 +1,49 @@
|
||||
Node('simulation',
|
||||
'Auto-generated configuration by frappy.',
|
||||
'tcp://10767',
|
||||
)
|
||||
Mod('analoginput',
|
||||
'frappy_mlz.entangle.AnalogInput',
|
||||
'from test/sim/analoginput',
|
||||
tangodevice = 'tango://localhost:10000/test/sim/analoginput',
|
||||
)
|
||||
Mod('sensor',
|
||||
'frappy_mlz.entangle.Sensor',
|
||||
'from test/sim/sensor',
|
||||
tangodevice = 'tango://localhost:10000/test/sim/sensor',
|
||||
)
|
||||
Mod('analogoutput',
|
||||
'frappy_mlz.entangle.AnalogOutput',
|
||||
'from test/sim/analogoutput',
|
||||
tangodevice = 'tango://localhost:10000/test/sim/analogoutput',
|
||||
)
|
||||
Mod('actuator',
|
||||
'frappy_mlz.entangle.Actuator',
|
||||
'from test/sim/actuator',
|
||||
tangodevice = 'tango://localhost:10000/test/sim/actuator',
|
||||
)
|
||||
Mod('motor',
|
||||
'frappy_mlz.entangle.Motor',
|
||||
'from test/sim/motor',
|
||||
tangodevice = 'tango://localhost:10000/test/sim/motor',
|
||||
)
|
||||
Mod('powersupply',
|
||||
'frappy_mlz.entangle.PowerSupply',
|
||||
'from test/sim/powersupply',
|
||||
tangodevice = 'tango://localhost:10000/test/sim/powersupply',
|
||||
)
|
||||
Mod('digitalinput',
|
||||
'frappy_mlz.entangle.DigitalInput',
|
||||
'from test/sim/digitalinput',
|
||||
tangodevice = 'tango://localhost:10000/test/sim/digitalinput',
|
||||
)
|
||||
Mod('digitaloutput',
|
||||
'frappy_mlz.entangle.DigitalOutput',
|
||||
'from test/sim/digitaloutput',
|
||||
tangodevice = 'tango://localhost:10000/test/sim/digitaloutput',
|
||||
)
|
||||
Mod('stringio',
|
||||
'frappy_mlz.entangle.StringIO',
|
||||
'from test/sim/stringio',
|
||||
tangodevice = 'tango://localhost:10000/test/sim/stringio',
|
||||
)
|
||||
@@ -1,33 +0,0 @@
|
||||
[node htf02]
|
||||
description = [sim] htf02 box of MLZ Sample environment group
|
||||
.
|
||||
Controls an High Temperature Furnace with an eurotherm controller and an PLC checking the cooing water.
|
||||
|
||||
|
||||
[interface tcp]
|
||||
type=tcp
|
||||
bindto=0.0.0.0
|
||||
bindport=10767
|
||||
|
||||
[module T_htf02]
|
||||
class=frappy.simulation.SimDrivable
|
||||
.description=Main temperature control node of htf02.
|
||||
.
|
||||
Controls the regulation loop of the Eurotherm.
|
||||
value.datatype={"type":"double", "min":0, "unit":"degC"}
|
||||
value.default=300
|
||||
target.datatype={"type":"double", "min":0, "max": 2000, "unit":"degC"}
|
||||
target.default=300
|
||||
.extra_params=ramp
|
||||
ramp.datatype={"type":"double", "min":0, "max": 600, "unit":"K/min"}
|
||||
ramp.description=target ramping speed in K/min.
|
||||
ramp.default=60
|
||||
ramp.readonly=False
|
||||
.meaning=["temperature", 10]
|
||||
|
||||
[module htf02_p]
|
||||
class=frappy.simulation.SimReadable
|
||||
.description=Pressure Sensor at sample space (ivc).
|
||||
value.datatype={"type":"double", "min":0, "unit":"mbar"}
|
||||
value.default=989
|
||||
|
||||
30
cfg/sim_mlz_htf02_cfg.py
Normal file
30
cfg/sim_mlz_htf02_cfg.py
Normal file
@@ -0,0 +1,30 @@
|
||||
Node('htf02',
|
||||
'[sim] htf02 box of MLZ Sample environment group\n'
|
||||
'\n'
|
||||
'Controls an High Temperature Furnace with an eurotherm controller and an PLC checking the cooing water.',
|
||||
'tcp://10767',
|
||||
)
|
||||
|
||||
Mod('T_htf02',
|
||||
'frappy.simulation.SimDrivable',
|
||||
'Main temperature control node of htf02.\n'
|
||||
'\n'
|
||||
'Controls the regulation loop of the Eurotherm.',
|
||||
value = Param(default=300,
|
||||
datatype={"type":"double", "min":0, "unit":"degC"}),
|
||||
target = Param(default=300,
|
||||
datatype={"type":"double", "min":0, "max": 2000, "unit":"degC"}),
|
||||
extra_params='ramp',
|
||||
ramp = Param(default=60,
|
||||
datatype={"type":"double", "min":0, "max": 600, "unit":"K/min"},
|
||||
description='target ramping speed in K/min.',
|
||||
readonly=False,
|
||||
),
|
||||
meaning=["temperature", 10],
|
||||
)
|
||||
|
||||
Mod('htf02_p',
|
||||
'frappy.simulation.SimReadable',
|
||||
'Pressure Sensor at sample space (ivc).',
|
||||
value = Param(default=989, datatype={"type":"double", "min":0, "unit":"mbar"}),
|
||||
)
|
||||
@@ -1,73 +0,0 @@
|
||||
[node stressihtf2]
|
||||
description = [sim] Stressihtf2 box of MLZ Sample environment group
|
||||
.
|
||||
Controls an High Temperature Furnace with an Eurotherm and an PLC controlling some valves and checking cooling water.
|
||||
|
||||
|
||||
[interface tcp]
|
||||
type=tcp
|
||||
bindto=0.0.0.0
|
||||
bindport=10767
|
||||
|
||||
[module T_stressihtf2]
|
||||
class=frappy.simulation.SimDrivable
|
||||
.description=Main temperature control node of Stressihtf2.
|
||||
value.datatype={"type":"double", "min":0, "unit":"degC"}
|
||||
value.default=20
|
||||
target.datatype={"type":"double", "min":0, "max":2000, "unit":"degC"}
|
||||
target.default=20
|
||||
.extra_params=ramp,regulationmode,abslimits,userlimits
|
||||
ramp.datatype={"type":"double", "min":0, "max":600, "unit":"K/min"}
|
||||
ramp.description=target ramping speed in K/min.
|
||||
ramp.default=60
|
||||
abslimits.datatype={"type":"limit", "members":{"type":"double", "min":0, "max":2000, "unit":"degC"}}
|
||||
abslimits.default=[0,2000]
|
||||
abslimits.description=currently active absolute limits for the setpoint. depend on the regulationmode parameter (both/stick->0..600, tube->0..300K).
|
||||
userlimits.datatype={"type":"limit", "members":{"type":"double", "min":0, "max":2000, "unit":"degC"}}
|
||||
userlimits.default=[0,300]
|
||||
userlimits.description=current user set limits for the setpoint. must be inside abslimits.
|
||||
userlimits.readonly=False
|
||||
.meaning=['temperature_regulation', 10]
|
||||
|
||||
[module T_stressihtf2_sample]
|
||||
class=frappy.simulation.SimReadable
|
||||
.description=(optional) Sample temperature sensor.
|
||||
.visibility=expert
|
||||
value.default=300
|
||||
value.datatype={"type":"double", "min":0, "unit":"degC"}
|
||||
.meaning=["temperature", 9]
|
||||
|
||||
[module stressihtf2_n2]
|
||||
class=frappy.simulation.SimWritable
|
||||
.description=Switches the N2 gas inlet on or off.
|
||||
.visibility=expert
|
||||
value.default='off'
|
||||
value.datatype={"type":"enum", "members":{'off':0,'on':1}}
|
||||
target.datatype={"type":"enum", "members":{'off':0,'on':1}}
|
||||
target.default='off'
|
||||
|
||||
[module stressihtf2_he]
|
||||
class=frappy.simulation.SimWritable
|
||||
.description=Switches the He gas inlet on or off.
|
||||
.visibility=expert
|
||||
value.default='off'
|
||||
value.datatype={"type":"enum", "members":{'off':0,'on':1}}
|
||||
target.datatype={"type":"enum", "members":{'off':0,'on':1}}
|
||||
target.default='off'
|
||||
|
||||
[module stressihtf2_lamps]
|
||||
class=frappy.simulation.SimWritable
|
||||
.description=Switches the heating lamps on or off.
|
||||
.visibility=expert
|
||||
value.default='on'
|
||||
value.datatype={"type":"enum", "members":{'off':0,'on':1}}
|
||||
target.datatype={"type":"enum", "members":{'off':0,'on':1}}
|
||||
target.default='on'
|
||||
|
||||
[module stressihtf2_water_ok]
|
||||
class=frappy.simulation.SimReadable
|
||||
.description=Readout of the cooling water state.
|
||||
.visibility=expert
|
||||
value.default='ok'
|
||||
value.datatype={"type":"enum", "members":{'failed':0,'ok':1}}
|
||||
|
||||
@@ -1,73 +0,0 @@
|
||||
[node stressihtf2_v2]
|
||||
description = [sim] Stressihtf2 box of MLZ Sample environment group
|
||||
.
|
||||
Controls an High Temperature Furnace with an Eurotherm and an PLC controlling some valves and checking cooling water.
|
||||
|
||||
|
||||
[interface tcp]
|
||||
type=tcp
|
||||
bindto=0.0.0.0
|
||||
bindport=10767
|
||||
|
||||
[module T]
|
||||
class=frappy.simulation.SimDrivable
|
||||
.description=Main temperature control node of Stressihtf2.
|
||||
value.datatype={"type":"double", "min":0, "unit":"degC"}
|
||||
value.default=20
|
||||
target.datatype={"type":"double", "min":0, "max":2000, "unit":"degC"}
|
||||
target.default=20
|
||||
.extra_params=ramp,regulationmode,abslimits,userlimits
|
||||
ramp.datatype={"type":"double", "min":0, "max":600, "unit":"K/min"}
|
||||
ramp.description=target ramping speed in K/min.
|
||||
ramp.default=60
|
||||
abslimits.datatype={"type":"limit", "members":{"type":"double", "min":0, "max":2000, "unit":"degC"}}
|
||||
abslimits.default=[0,2000]
|
||||
abslimits.description=currently active absolute limits for the setpoint. depend on the regulationmode parameter (both/stick->0..600, tube->0..300K).
|
||||
userlimits.datatype={"type":"limit", "members":{"type":"double", "min":0, "max":2000, "unit":"degC"}}
|
||||
userlimits.default=[0,300]
|
||||
userlimits.description=current user set limits for the setpoint. must be inside abslimits.
|
||||
userlimits.readonly=False
|
||||
.meaning=['temperature_regulation', 10]
|
||||
|
||||
[module T_sample]
|
||||
class=frappy.simulation.SimReadable
|
||||
.description=(optional) Sample temperature sensor.
|
||||
.visibility=expert
|
||||
value.default=300
|
||||
value.datatype={"type":"double", "min":0, "unit":"degC"}
|
||||
.meaning=["temperature", 9]
|
||||
|
||||
[module N2]
|
||||
class=frappy.simulation.SimWritable
|
||||
.description=Switches the N2 gas inlet on or off.
|
||||
.visibility=expert
|
||||
value.default='off'
|
||||
value.datatype={"type":"enum", "members":{'off':0,'on':1}}
|
||||
target.datatype={"type":"enum", "members":{'off':0,'on':1}}
|
||||
target.default='off'
|
||||
|
||||
[module He]
|
||||
class=frappy.simulation.SimWritable
|
||||
.description=Switches the He gas inlet on or off.
|
||||
.visibility=expert
|
||||
value.default='off'
|
||||
value.datatype={"type":"enum", "members":{'off':0,'on':1}}
|
||||
target.datatype={"type":"enum", "members":{'off':0,'on':1}}
|
||||
target.default='off'
|
||||
|
||||
[module lamps]
|
||||
class=frappy.simulation.SimWritable
|
||||
.description=Switches the heating lamps on or off.
|
||||
.visibility=expert
|
||||
value.default='on'
|
||||
value.datatype={"type":"enum", "members":{'off':0,'on':1}}
|
||||
target.datatype={"type":"enum", "members":{'off':0,'on':1}}
|
||||
target.default='on'
|
||||
|
||||
[module water_ok]
|
||||
class=frappy.simulation.SimReadable
|
||||
.description=Readout of the cooling water state.
|
||||
.visibility=expert
|
||||
value.default='ok'
|
||||
value.datatype={"type":"enum", "members":{'failed':0,'ok':1}}
|
||||
|
||||
82
cfg/sim_mlz_stressihtf2_v2_cfg.py
Normal file
82
cfg/sim_mlz_stressihtf2_v2_cfg.py
Normal file
@@ -0,0 +1,82 @@
|
||||
Node('stressihtf2_v2',
|
||||
'[sim] Stressihtf2 box of MLZ Sample environment group\n'
|
||||
'\n'
|
||||
'Controls an High Temperature Furnace with an Eurotherm and an PLC controlling some valves and checking cooling water.',
|
||||
'tcp://10767',
|
||||
)
|
||||
|
||||
|
||||
Mod('T',
|
||||
'frappy.simulation.SimDrivable',
|
||||
'Main temperature control node of Stressihtf2.',
|
||||
value = Param(default=20,
|
||||
datatype={"type":"double", "min":0, "unit":"degC"}),
|
||||
target = Param(default=20,
|
||||
datatype={"type":"double", "min":0, "max":2000, "unit":"degC"}),
|
||||
extra_params='ramp,regulationmode,abslimits,userlimits',
|
||||
ramp = Param(
|
||||
default=60,
|
||||
datatype={"type":"double", "min":0, "max":600, "unit":"K/min"},
|
||||
description='target ramping speed in K/min.',
|
||||
),
|
||||
abslimits = Param(
|
||||
default=[0,2000],
|
||||
datatype={"type":"limit", "members":{"type":"double", "min":0, "max":2000, "unit":"degC"}},
|
||||
description='currently active absolute limits for the setpoint. depend \
|
||||
on the regulationmode parameter (both/stick->0..600, tube->0..300K).',
|
||||
),
|
||||
userlimits = Param(
|
||||
default=[0,300],
|
||||
datatype={"type":"limit", "members":{"type":"double", "min":0, "max":2000, "unit":"degC"}},
|
||||
description='current user set limits for the setpoint. must be inside abslimits.',
|
||||
readonly=False,
|
||||
),
|
||||
meaning=['temperature_regulation', 10],
|
||||
)
|
||||
|
||||
Mod('T_sample',
|
||||
'frappy.simulation.SimReadable',
|
||||
'(optional) Sample temperature sensor.',
|
||||
value = Param(default=300,
|
||||
datatype={"type":"double", "min":0, "unit":"degC"}),
|
||||
visibility='expert',
|
||||
meaning=["temperature", 9],
|
||||
)
|
||||
|
||||
Mod('N2',
|
||||
'frappy.simulation.SimWritable',
|
||||
'Switches the N2 gas inlet on or off.',
|
||||
value = Param(default='off',
|
||||
datatype={"type":"enum", "members":{'off':0,'on':1}}),
|
||||
target = Param(default='off',
|
||||
datatype={"type":"enum", "members":{'off':0,'on':1}}),
|
||||
visibility='expert',
|
||||
)
|
||||
|
||||
Mod('He',
|
||||
'frappy.simulation.SimWritable',
|
||||
'Switches the He gas inlet on or off.',
|
||||
value = Param(default='off',
|
||||
datatype={"type":"enum", "members":{'off':0,'on':1}}),
|
||||
target = Param(default='off',
|
||||
datatype={"type":"enum", "members":{'off':0,'on':1}}),
|
||||
visibility='expert',
|
||||
)
|
||||
|
||||
Mod('lamps',
|
||||
'frappy.simulation.SimWritable',
|
||||
'Switches the heating lamps on or off.',
|
||||
value = Param(default='on',
|
||||
datatype={"type":"enum", "members":{'off':0,'on':1}}),
|
||||
target = Param(default='on',
|
||||
datatype={"type":"enum", "members":{'off':0,'on':1}}),
|
||||
visibility='expert',
|
||||
)
|
||||
|
||||
Mod('water_ok',
|
||||
'frappy.simulation.SimReadable',
|
||||
'Readout of the cooling water state.',
|
||||
value = Param(default='ok',
|
||||
datatype={"type":"enum", "members":{'failed':0,'ok':1}}),
|
||||
visibility='expert',
|
||||
)
|
||||
@@ -1,12 +0,0 @@
|
||||
[r3]
|
||||
class = frappy.core.Proxy
|
||||
remote_class = frappy.core.Readable
|
||||
description = temp sensor on 3He system
|
||||
uri = tcp://pc12694:5000
|
||||
export = False
|
||||
|
||||
[t3]
|
||||
class = frappy_psi.softcal.Sensor
|
||||
rawsensor = r3
|
||||
calib = X131346
|
||||
value.unit = K
|
||||
20
cfg/softcal_cfg.py
Normal file
20
cfg/softcal_cfg.py
Normal file
@@ -0,0 +1,20 @@
|
||||
Node('softcal.demo.example',
|
||||
'convert r2 from PPMS to a temperature',
|
||||
'tcp://5001',
|
||||
)
|
||||
|
||||
Mod('r2',
|
||||
'frappy.core.Proxy',
|
||||
'convert r2 from PPMS to a temperature',
|
||||
remote_class = 'frappy.core.Readable',
|
||||
uri = 'tcp://localhost:5000',
|
||||
export = False,
|
||||
)
|
||||
|
||||
Mod('T2',
|
||||
'frappy_psi.softcal.Sensor',
|
||||
'',
|
||||
value = Param(unit = 'K'),
|
||||
rawsensor = 'r2',
|
||||
calib = 'X131346',
|
||||
)
|
||||
@@ -1,83 +0,0 @@
|
||||
[node stressihtf2]
|
||||
description = Stressihtf2 box of MLZ Sample environment group
|
||||
.
|
||||
Controls an High Temperature Furnace with an Eurotherm and an PLC controlling some valves and checking cooling water.
|
||||
|
||||
meaning={'T_regulation':{'T':100}, 'T_sample':{'T_sample':100}}
|
||||
|
||||
[interface tcp]
|
||||
type=tcp
|
||||
bindto=0.0.0.0
|
||||
bindport=10767
|
||||
|
||||
[module T]
|
||||
class=frappy_mlz.entangle.TemperatureController
|
||||
tangodevice=tango://localhost:10000/box/eurotherm/ctrl
|
||||
.description=Main temperature control node of Stressihtf2.
|
||||
value.unit='degC'
|
||||
target.datatype=["double", 0, 2000]
|
||||
ramp.datatype=["double",0,9999]
|
||||
ramp.description=target ramping speed in K/min.
|
||||
ramp.default=60
|
||||
ramp.unit=K/min
|
||||
abslimits.datatype=["tuple",[["double"],["double"]]]
|
||||
abslimits.default=[0,2000]
|
||||
abslimits.description=currently active absolute limits for the setpoint. depend on the regulationmode parameter (both/stick->0..600, tube->0..300K).
|
||||
abslimits.unit='degC'
|
||||
abslimits.readonly=True
|
||||
userlimits.datatype=["tuple",[["double"],["double"]]]
|
||||
userlimits.default=[0,300]
|
||||
userlimits.description=current user set limits for the setpoint. must be inside abslimits.
|
||||
userlimits.unit='degC'
|
||||
heateroutput.datatype=["double",0,100]
|
||||
heateroutput.description=output to the heater
|
||||
heateroutput.unit='%%'
|
||||
heateroutput.default=0
|
||||
setpoint.default=0
|
||||
p.default=1
|
||||
i.default=0
|
||||
d.default=0
|
||||
pid.default=[1,0,0]
|
||||
speed.default=0
|
||||
|
||||
[module T_sample_a]
|
||||
class=frappy_mlz.entangle.Sensor
|
||||
tangodevice=tango://localhost:10000/box/eurotherm/sensora
|
||||
.description=Regulation temperature sensor.
|
||||
.visibility=user
|
||||
value.unit='degC'
|
||||
|
||||
[module T_sample_b]
|
||||
class=frappy_mlz.entangle.Sensor
|
||||
tangodevice=tango://localhost:10000/box/eurotherm/sensorb
|
||||
.description=(optional) Sample temperature sensor.
|
||||
.visibility=expert
|
||||
value.unit='degC'
|
||||
|
||||
[module N2]
|
||||
class=frappy_mlz.entangle.NamedDigitalOutput
|
||||
tangodevice=tango://localhost:10000/box/plc/_gas1
|
||||
.description=Switches the N2 gas inlet on or off.
|
||||
.visibility=expert
|
||||
mapping=dict(off=0,on=1)
|
||||
|
||||
[module He]
|
||||
class=frappy_mlz.entangle.NamedDigitalOutput
|
||||
tangodevice=tango://localhost:10000/box/plc/_gas2
|
||||
.description=Switches the He gas inlet on or off.
|
||||
.visibility=expert
|
||||
mapping=dict(off=0,on=1)
|
||||
|
||||
[module lamps]
|
||||
class=frappy_mlz.entangle.NamedDigitalOutput
|
||||
tangodevice=tango://localhost:10000/box/plc/_onoff
|
||||
.description=Switches the heating lamps on or off.
|
||||
.visibility=expert
|
||||
mapping=dict(off=0,on=1)
|
||||
|
||||
[module water_ok]
|
||||
class=frappy_mlz.entangle.NamedDigitalInput
|
||||
tangodevice=tango://localhost:10000/box/plc/_waterok
|
||||
.description=Readout of the cooling water state.
|
||||
.visibility=expert
|
||||
mapping=dict(failed=0,ok=1)
|
||||
96
cfg/stressihtf2_cfg.py
Normal file
96
cfg/stressihtf2_cfg.py
Normal file
@@ -0,0 +1,96 @@
|
||||
Node('stressihtf2',
|
||||
'Stressihtf2 box of MLZ Sample environment group\n'
|
||||
'\n'
|
||||
'Controls an High Temperature Furnace with an Eurotherm and an PLC controlling some valves and checking cooling water.',
|
||||
'localhost:10767',
|
||||
meaning={'T_regulation':{'T':100}, 'T_sample':{'T_sample':100}},
|
||||
)
|
||||
|
||||
|
||||
Mod('T',
|
||||
'frappy_mlz.entangle.TemperatureController',
|
||||
'Main temperature control node of Stressihtf2.',
|
||||
tangodevice='tango://localhost:10000/box/eurotherm/ctrl',
|
||||
value = Param(unit='degC'),
|
||||
target = Param(datatype=["double", 0, 2000]),
|
||||
ramp = Param(
|
||||
default=60,
|
||||
datatype=["double",0,9999],
|
||||
unit='K/min',
|
||||
description='target ramping speed in K/min.',
|
||||
),
|
||||
abslimits = Param(
|
||||
default=[0,2000],
|
||||
datatype=["tuple",[["double"],["double"]]],
|
||||
unit='degC',
|
||||
description='currently active absolute limits for the setpoint.\
|
||||
depend on the regulationmode parameter (both/stick->0..600, tube->0..300K).',
|
||||
readonly=True,
|
||||
),
|
||||
userlimits = Param(
|
||||
default=[0,300],
|
||||
datatype=["tuple",[["double"],["double"]]],
|
||||
unit='degC',
|
||||
description='current user set limits for the setpoint. must be inside abslimits.',
|
||||
),
|
||||
heateroutput = Param(
|
||||
default=0,
|
||||
datatype=["double",0,100],
|
||||
unit='%%',
|
||||
description='output to the heater',
|
||||
),
|
||||
setpoint = 0,
|
||||
p = 1,
|
||||
i = 0,
|
||||
d = 0,
|
||||
pid = [1,0,0],
|
||||
speed = 0,
|
||||
)
|
||||
|
||||
Mod('T_sample_a',
|
||||
'frappy_mlz.entangle.Sensor',
|
||||
'Regulation temperature sensor.',
|
||||
tangodevice='tango://localhost:10000/box/eurotherm/sensora',
|
||||
value = Param(unit='degC'),
|
||||
visibility='user',
|
||||
)
|
||||
|
||||
Mod('T_sample_b',
|
||||
'frappy_mlz.entangle.Sensor',
|
||||
'(optional) Sample temperature sensor.',
|
||||
tangodevice='tango://localhost:10000/box/eurotherm/sensorb',
|
||||
value = Param(unit='degC'),
|
||||
visibility='expert',
|
||||
)
|
||||
|
||||
Mod('N2',
|
||||
'frappy_mlz.entangle.NamedDigitalOutput',
|
||||
'Switches the N2 gas inlet on or off.',
|
||||
tangodevice='tango://localhost:10000/box/plc/_gas1',
|
||||
mapping={'off':0,'on':1},
|
||||
visibility='expert',
|
||||
)
|
||||
|
||||
Mod('He',
|
||||
'frappy_mlz.entangle.NamedDigitalOutput',
|
||||
'Switches the He gas inlet on or off.',
|
||||
tangodevice='tango://localhost:10000/box/plc/_gas2',
|
||||
mapping={'off':0,'on':1},
|
||||
visibility='expert',
|
||||
)
|
||||
|
||||
Mod('lamps',
|
||||
'frappy_mlz.entangle.NamedDigitalOutput',
|
||||
'Switches the heating lamps on or off.',
|
||||
tangodevice='tango://localhost:10000/box/plc/_onoff',
|
||||
mapping={'off':0,'on':1},
|
||||
visibility='expert',
|
||||
)
|
||||
|
||||
Mod('water_ok',
|
||||
'frappy_mlz.entangle.NamedDigitalInput',
|
||||
'Readout of the cooling water state.',
|
||||
tangodevice='tango://localhost:10000/box/plc/_waterok',
|
||||
mapping={'failed':0,'ok':1},
|
||||
visibility='expert',
|
||||
)
|
||||
@@ -1,7 +1,7 @@
|
||||
Node('test.config.frappy.demo',
|
||||
'''short description of the testing sec-node
|
||||
|
||||
This description for the Nodecan be as long as you need if you use a multiline string.
|
||||
This description for the node can be as long as you need if you use a multiline string.
|
||||
|
||||
Very long!
|
||||
The needed fields are Equipment id (1st argument), description (this)
|
||||
@@ -10,6 +10,22 @@ The needed fields are Equipment id (1st argument), description (this)
|
||||
'tcp://10768',
|
||||
)
|
||||
|
||||
Mod('attachtest',
|
||||
'frappy_demo.test.WithAtt',
|
||||
'test attached',
|
||||
att = 'LN2',
|
||||
)
|
||||
|
||||
Mod('pinata',
|
||||
'frappy_demo.test.Pin',
|
||||
'scan test',
|
||||
)
|
||||
|
||||
Mod('recursive',
|
||||
'frappy_demo.test.RecPin',
|
||||
'scan test',
|
||||
)
|
||||
|
||||
Mod('LN2',
|
||||
'frappy_demo.test.LN2',
|
||||
'random value between 0..100%',
|
||||
@@ -44,3 +60,15 @@ Mod('Lower',
|
||||
'frappy_demo.test.Lower',
|
||||
'something else',
|
||||
)
|
||||
|
||||
Mod('Decision',
|
||||
'frappy_demo.test.Mapped',
|
||||
'Random value from configured property choices. Config accepts anything ' \
|
||||
'that can be converted to a list',
|
||||
choices = ['Yes', 'Maybe', 'No'],
|
||||
)
|
||||
|
||||
Mod('c',
|
||||
'frappy_demo.test.Commands',
|
||||
'a command test',
|
||||
)
|
||||
|
||||
11
cfg/tssop16_cfg.py
Normal file
11
cfg/tssop16_cfg.py
Normal file
@@ -0,0 +1,11 @@
|
||||
import frappy.core as fc
|
||||
import serial
|
||||
|
||||
Node('example_TSSOP16.psi.ch', 'A demo system showing how to connect the TSSOP16 Arduino', interface='tcp://5000')
|
||||
|
||||
Mod('io1',
|
||||
'frappy_psi.capacitance_readings.TSSOP16.TSSOP16_IO',
|
||||
'communication',
|
||||
uri='serial://COM11?baudrate=9600+bytesize=8+parity=none+stopbits=1')
|
||||
|
||||
Mod('TSSOP16', 'frappy_psi.capacitance_readings.TSSOP16.TSSOP16', 'Capacitance-reading Arduino (with TSSOP16)', io='io1')
|
||||
@@ -9,10 +9,11 @@ RUN apt-get update && \
|
||||
openssh-client \
|
||||
make \
|
||||
locales \
|
||||
libgl1 \
|
||||
python3 \
|
||||
python3-dev \
|
||||
python3-tango \
|
||||
python3-venv python3-setuptools \
|
||||
python3-venv \
|
||||
python3-setuptools \
|
||||
virtualenv
|
||||
|
||||
|
||||
@@ -35,8 +36,10 @@ RUN virtualenv /home/jenkins/tools2 && \
|
||||
RUN virtualenv -p /usr/bin/python3 --system-site-packages /home/jenkins/secopvenv && \
|
||||
git clone https://forge.frm2.tum.de/review/secop/frappy /home/jenkins/frappy && \
|
||||
. /home/jenkins/secopvenv/bin/activate && \
|
||||
pip install -U pip wheel setuptools && \
|
||||
pip install -r /home/jenkins/frappy/requirements-dev.txt -r /home/jenkins/frappy/requirements.txt pylint pytest && \
|
||||
pip install -U pip wheel setuptools importlib_metadata && \
|
||||
pip install -r /home/jenkins/frappy/requirements-dev.txt \
|
||||
-r /home/jenkins/frappy/requirements-gui.txt \
|
||||
-r /home/jenkins/frappy/requirements.txt pylint pytest && \
|
||||
rm -rf /home/jenkins/frappy
|
||||
|
||||
|
||||
|
||||
21
ci/Jenkinsfile
vendored
21
ci/Jenkinsfile
vendored
@@ -13,11 +13,6 @@ properties([
|
||||
choice(choices: '''\
|
||||
patchset-created
|
||||
ref-updated
|
||||
change-merged''',
|
||||
description: '', name: 'GERRIT_EVENT'),
|
||||
choice(choices: '''\
|
||||
patchset-created
|
||||
ref-updated
|
||||
change-merged''',
|
||||
description: '', name: 'GERRIT_EVENT_TYPE')])
|
||||
])
|
||||
@@ -41,6 +36,7 @@ git diff HEAD~1... --name-only --diff-filter=ARCM -- \\*.py
|
||||
#!/bin/bash -x
|
||||
. /home/jenkins/secopvenv/bin/activate
|
||||
pip install -r requirements-dev.txt
|
||||
pip install -r requirements-gui.txt
|
||||
pip install -r requirements.txt
|
||||
pip install isort pylint
|
||||
pip install -e .
|
||||
@@ -50,7 +46,7 @@ echo "$changedFiles"
|
||||
if [[ -n "$changedFiles" ]]; then
|
||||
set -o pipefail
|
||||
pylint $changedFiles | tee pylint_results.txt
|
||||
isort --df $changedFiles | tee isort_results.txt
|
||||
isort --df $changedFiles || true | tee isort_results.txt
|
||||
fi
|
||||
"""
|
||||
withCredentials([string(credentialsId: 'GERRITHTTP',
|
||||
@@ -99,6 +95,7 @@ addopts = --junit-xml=pytest.xml --junit-prefix=''' + pyver
|
||||
#!/bin/bash
|
||||
. /home/jenkins/secopvenv/bin/activate
|
||||
pip install -r requirements-dev.txt
|
||||
pip install -r requirements-gui.txt
|
||||
pip install -r requirements.txt
|
||||
pip install -e .
|
||||
make test
|
||||
@@ -119,6 +116,7 @@ def run_docs() {
|
||||
sh '''
|
||||
. /home/jenkins/secopvenv/bin/activate
|
||||
pip install -r requirements-dev.txt
|
||||
pip install -r requirements-gui.txt
|
||||
pip install -r requirements.txt
|
||||
pip install -e .
|
||||
'''
|
||||
@@ -146,12 +144,23 @@ def run_docs() {
|
||||
'''
|
||||
}
|
||||
|
||||
/* does not work with too many quote levels
|
||||
* alternatively use pdf (based on rst2pdf)
|
||||
* or singlehtml converted to pdf manually from a browser (may produce nicer output)
|
||||
stage('build latexpdf') {
|
||||
sh '''
|
||||
. /home/jenkins/secopvenv/bin/activate
|
||||
make -C doc latexpdf
|
||||
'''
|
||||
}
|
||||
*/
|
||||
|
||||
stage('build pdf') {
|
||||
sh '''
|
||||
. /home/jenkins/secopvenv/bin/activate
|
||||
make -C doc pdf
|
||||
'''
|
||||
}
|
||||
|
||||
stage('build man') {
|
||||
sh '''
|
||||
|
||||
932
debian/changelog
vendored
932
debian/changelog
vendored
@@ -1,4 +1,912 @@
|
||||
frappy-core (0.15.0) focal; urgency=medium
|
||||
frappy-core (0.20.5) stable; urgency=medium
|
||||
|
||||
[ Markus Zolliker ]
|
||||
* add sim-server again based on socketserver
|
||||
* fix bug when overriding a property with bare value
|
||||
* frappy.server bug fix: server name must not be a list
|
||||
* frappy.server: use server name for SecNode name
|
||||
* frappy.server: remove comment about opts in SecNode/Dispatcher
|
||||
* follow up change for 'better order of accessibles' (34904)
|
||||
* better message when a parameter is overridden by an invalid value
|
||||
* pylint: increase max number of positional arguments
|
||||
* an error on a write must not send an error update
|
||||
* fix bug in change 35001 (better error message)
|
||||
* make UPD listener work when 'tcp://' is omitted on interface
|
||||
* config: do not override equipment_id with name
|
||||
* equipment_id for merged configs and routed nodes
|
||||
* core: alternative approach for optional accessibles
|
||||
* core: simplify test for methods names
|
||||
|
||||
[ Georg Brandl ]
|
||||
* debian: update compat
|
||||
* remove wrong <weight> from fonts on Qt6
|
||||
|
||||
[ Markus Zolliker ]
|
||||
* config: validate value and default of parameters
|
||||
* config: Mod() should return config dict
|
||||
* stop poller threads on shutdown
|
||||
* fix overriding Parameter with value
|
||||
* improve error messages on module creation
|
||||
* make sure unexported modules are initialized
|
||||
* change to new visibility spec
|
||||
* follow-up change to 35931: make Proxy a Module
|
||||
|
||||
[ Konstantin Kholostov ]
|
||||
* installer: add recipe to build macOS app bundle
|
||||
|
||||
[ Markus Zolliker ]
|
||||
* frappy.client.SecopClient: fix setParameterFromString
|
||||
* frappy_psi/ls370res: various bug fixes
|
||||
* client: add SecopClient.execCommandFromString
|
||||
* frappy.client.interactive: improve updates while driving
|
||||
|
||||
-- Markus Zolliker <jenkins@frm2.tum.de> Mon, 12 May 2025 14:03:22 +0200
|
||||
|
||||
frappy-core (0.20.4) stable; urgency=medium
|
||||
|
||||
[ Georg Brandl ]
|
||||
* remove unused file
|
||||
|
||||
[ Markus Zolliker ]
|
||||
* frappy.lib.multievent: avoid deadlock
|
||||
|
||||
[ Jens Krüger ]
|
||||
* PSI: Fix import error on ThermoFisher module
|
||||
|
||||
[ Markus Zolliker ]
|
||||
* frappy.client: catch all errors in handleError callback
|
||||
|
||||
[ Jens Krüger ]
|
||||
* Lib/config: Create a list of pathes only for confdir
|
||||
|
||||
-- Georg Brandl <jenkins@frm2.tum.de> Thu, 14 Nov 2024 14:43:54 +0100
|
||||
|
||||
frappy-core (0.20.3) stable; urgency=medium
|
||||
|
||||
[ Georg Brandl ]
|
||||
* fixup test for cfg_editor utils to run from non-checkout, and fix names, and remove example code
|
||||
|
||||
[ Alexander Zaft ]
|
||||
* add generalConfig to etc
|
||||
|
||||
-- Georg Brandl <jenkins@frm2.tum.de> Thu, 07 Nov 2024 10:57:11 +0100
|
||||
|
||||
frappy-core (0.20.2) stable; urgency=medium
|
||||
|
||||
[ Georg Brandl ]
|
||||
* pylint: do not try to infer too much
|
||||
|
||||
[ Alexander Zaft ]
|
||||
* test_server: basic description checks
|
||||
* simulation: fix extra_params default, ccidu1 cfg
|
||||
* sim: make amagnet sim cfg startable again
|
||||
* server: fix positional argument lint
|
||||
* server: show interfaces as custom property
|
||||
* core: fix Dispatcher and SECNode opts handling
|
||||
|
||||
[ Markus Zolliker ]
|
||||
* frappy_psi.sea: bugfix: revert change of updateEvent to udpateItem
|
||||
* fix playground
|
||||
|
||||
[ Alexander Zaft ]
|
||||
* config: allow using Prop(...)
|
||||
* config: fix typo
|
||||
* Revert "config: allow using Prop(...)"
|
||||
* generalconfig: streamlined config discovery
|
||||
|
||||
[ Markus Zolliker ]
|
||||
* better order of accessibles: 'value' 'status' and 'target' first
|
||||
|
||||
[ Alexander Zaft ]
|
||||
* server: fix windows ctrl-c
|
||||
|
||||
[ Georg Brandl ]
|
||||
* systemd: enable indication of reloading/stopping
|
||||
|
||||
[ Alexander Zaft ]
|
||||
* server: service discovery over UDP.
|
||||
|
||||
[ Markus Zolliker ]
|
||||
* generalConfig: fix the case when confdir is a list of paths
|
||||
|
||||
[ Georg Brandl ]
|
||||
* server: better handling of cfgfile argument
|
||||
|
||||
[ Alexander Zaft ]
|
||||
* fix frappy-server cfgfiles command
|
||||
|
||||
-- Georg Brandl <jenkins@frm2.tum.de> Wed, 06 Nov 2024 10:40:26 +0100
|
||||
|
||||
frappy-core (0.20.1) stable; urgency=medium
|
||||
|
||||
* gui: do not add a console logger when there is no sys.stdout
|
||||
* remove unused test class
|
||||
* remove old unused parse module
|
||||
* remove sim-server
|
||||
* lib: there might be no confdir in "cfg"
|
||||
|
||||
-- Georg Brandl <jenkins@frm2.tum.de> Thu, 17 Oct 2024 16:31:27 +0200
|
||||
|
||||
frappy-core (0.20.0) stable; urgency=medium
|
||||
|
||||
[ Alexander Zaft ]
|
||||
* bin: remove make_doc
|
||||
|
||||
[ Markus Zolliker ]
|
||||
* GUI: avoid space needed for closed groups
|
||||
* GUI: allow enums to be plotted
|
||||
|
||||
[ Alexander Zaft ]
|
||||
* all: start using pathlib
|
||||
* generalConfig, config: use pathlib
|
||||
* psi: change open calls in sea
|
||||
|
||||
[ Georg Brandl ]
|
||||
* fix descriptive data
|
||||
|
||||
[ Markus Zolliker ]
|
||||
* frappy_psi.sea: avoid error on import
|
||||
|
||||
[ Enrico Faulhaber ]
|
||||
* ci: also install gui requirements for additional tests
|
||||
|
||||
[ Georg Brandl ]
|
||||
* remove old "buffer" message
|
||||
* ci/Dockerfile: do no use pytango from upstream
|
||||
* new setuptools needs new importlib_metadata
|
||||
* install pytango for testing
|
||||
* install libgl1 for pyqt6
|
||||
|
||||
[ Enrico Faulhaber ]
|
||||
* add test for importing custom modules
|
||||
|
||||
[ Markus Zolliker ]
|
||||
* do not fail when generalConfig.init() is called twice
|
||||
* add Datatype.to_string as counterpart of .from_string
|
||||
* add more datatype tests
|
||||
|
||||
[ Georg Brandl ]
|
||||
* update declared version to 1.0 final
|
||||
* setup: fix classifiers
|
||||
* setup: fill long_description and url
|
||||
|
||||
-- Alexander Zaft <jenkins@frm2.tum.de> Thu, 17 Oct 2024 14:24:29 +0200
|
||||
|
||||
frappy-core (0.19.10) stable; urgency=medium
|
||||
|
||||
[ Alexander Zaft ]
|
||||
* debian: let frappy-core replace frappy-demo
|
||||
|
||||
[ Georg Brandl ]
|
||||
* remove walrus
|
||||
|
||||
-- Alexander Zaft <jenkins@frm2.tum.de> Wed, 07 Aug 2024 17:00:06 +0200
|
||||
|
||||
frappy-core (0.19.9) stable; urgency=medium
|
||||
|
||||
* debian: fix missing install dir
|
||||
|
||||
-- Georg Brandl <jenkins@frm2.tum.de> Tue, 06 Aug 2024 16:02:50 +0200
|
||||
|
||||
frappy-core (0.19.8) stable; urgency=medium
|
||||
|
||||
* debian: move demo into core
|
||||
|
||||
-- Georg Brandl <jenkins@frm2.tum.de> Tue, 06 Aug 2024 15:58:20 +0200
|
||||
|
||||
frappy-core (0.19.7) stable; urgency=medium
|
||||
|
||||
* lib: GeneralConfig fix missing keys logic
|
||||
|
||||
-- Alexander Zaft <jenkins@frm2.tum.de> Tue, 06 Aug 2024 15:04:07 +0200
|
||||
|
||||
frappy-core (0.19.6) stable; urgency=medium
|
||||
|
||||
[ Jens Krüger ]
|
||||
* SINQ/SEA: Fix import error due to None value
|
||||
|
||||
[ Georg Brandl ]
|
||||
* GUI: allow starting in detailed view by cmdline flag
|
||||
* gui: save/restore window geometry
|
||||
|
||||
[ Alexander Zaft ]
|
||||
* lib: Fix GeneralConfig defaults handling
|
||||
|
||||
-- Jens Krüger <jenkins@frm2.tum.de> Tue, 06 Aug 2024 13:56:51 +0200
|
||||
|
||||
frappy-core (0.19.5) stable; urgency=medium
|
||||
|
||||
* client: fix how to raise error on wrong ident
|
||||
* add missing requirements to setup.py
|
||||
* add RemoteLogHandler independent of MainLogger
|
||||
* pass logger parameter only for mlzlogger
|
||||
|
||||
-- Alexander Zaft <jenkins@frm2.tum.de> Mon, 05 Aug 2024 09:30:53 +0200
|
||||
|
||||
frappy-core (0.19.4) stable; urgency=medium
|
||||
|
||||
* actually exclude cfg-editor
|
||||
|
||||
-- Georg Brandl <jenkins@frm2.tum.de> Fri, 26 Jul 2024 11:46:10 +0200
|
||||
|
||||
frappy-core (0.19.3) stable; urgency=medium
|
||||
|
||||
[ Markus Zolliker ]
|
||||
* frappy_psi.extparams.StructParam: fix doc + simplify
|
||||
* better behaviour on startup in case of errors
|
||||
* frappy_psi.sea: use ReadFailedError
|
||||
* frappy_psi.sea: use raise from
|
||||
|
||||
[ Alexander Zaft ]
|
||||
* mlz: fix delayed import of he3cell
|
||||
* mlz seop: add pylint
|
||||
|
||||
[ Jens Krüger ]
|
||||
* Update copyright year
|
||||
|
||||
[ Enrico Faulhaber ]
|
||||
* remove cfg_editor for now
|
||||
|
||||
-- Markus Zolliker <jenkins@frm2.tum.de> Fri, 26 Jul 2024 08:36:43 +0200
|
||||
|
||||
frappy-core (0.19.2) stable; urgency=medium
|
||||
|
||||
[ l_samenv ]
|
||||
* fix missing update after error on parameter
|
||||
|
||||
[ Markus Zolliker ]
|
||||
* frappy.lib.asynconn: handle ConnectionResetError nicely
|
||||
* HasControlledBy: update target without switching to self control
|
||||
* bug fix in frappy_psi.convergence
|
||||
|
||||
[ Alexander Zaft ]
|
||||
* add option for delayed imports
|
||||
|
||||
[ Markus Zolliker ]
|
||||
* frappy_psi.ah2700: auto create loss module with Pinata
|
||||
* frappy_psi.picontrol: software control loop
|
||||
* frappy_psi.triton: bug fixes
|
||||
* frappy_psi.sea: various improvments
|
||||
* frappy_psi.triton: fix heater output issue
|
||||
|
||||
[ Enrico Faulhaber ]
|
||||
* frappy_mlz/entangle: fix missing status enum
|
||||
|
||||
-- l_samenv <jenkins@frm2.tum.de> Tue, 18 Jun 2024 15:21:43 +0200
|
||||
|
||||
frappy-core (0.19.1) stable; urgency=medium
|
||||
|
||||
[ Markus Zolliker ]
|
||||
* SecopClient.online must be True while activating
|
||||
* frappy.client.readParameter: handle connection errors correctly
|
||||
|
||||
[ Alexander Zaft ]
|
||||
* datatypes: add more detail to error messages
|
||||
* mlz: derive Digitaloutput from Drivable
|
||||
|
||||
-- Markus Zolliker <jenkins@frm2.tum.de> Fri, 07 Jun 2024 16:50:33 +0200
|
||||
|
||||
frappy-core (0.19.0) stable; urgency=medium
|
||||
|
||||
[ Markus Zolliker ]
|
||||
* simulation: extra_params might be a list
|
||||
* add FloatEnumParam
|
||||
* move StructParam to frappy/extparams.py
|
||||
* bugfix in automatic creation if attached io
|
||||
* fixes for proxy modules
|
||||
* simplify callbacks
|
||||
* fix docstring in frappy.error.OutOfRangeError
|
||||
|
||||
[ Alexander Zaft ]
|
||||
* core: introduce common handler class
|
||||
* core: cover errors in handler setup()
|
||||
|
||||
[ Markus Zolliker ]
|
||||
* fix command doc string handling and change default stop doc string
|
||||
* follow up fix for change 33168
|
||||
* follow up fix: handler export=True correctly
|
||||
|
||||
[ Alexander Zaft ]
|
||||
* core: add websocket interface
|
||||
|
||||
[ Georg Brandl ]
|
||||
* dispatcher: consistent handling of missing timestamps
|
||||
|
||||
[ Alexander Zaft ]
|
||||
* gui: catch invalid inputs
|
||||
* gui: sort qt imports
|
||||
|
||||
[ Markus Zolliker ]
|
||||
* frappy.client: cleanup properly after a reply timeout
|
||||
|
||||
[ Alexander Zaft ]
|
||||
* gui: more specialized input widgets
|
||||
* test: add uri attach test
|
||||
|
||||
[ Markus Zolliker ]
|
||||
* frappy.client: catch errors on callbacks
|
||||
* frappy.client: improve error handling more
|
||||
* frappy.client.interactive: improve logging and error handling
|
||||
* frappy.client.SecopClient: add the option to use no logging at all
|
||||
* SecopClient.__del__ must not call callbacks
|
||||
* frappy.client: avoid shutdown callback sent twice
|
||||
|
||||
[ Georg Brandl ]
|
||||
* add config for the Entangle simulation server
|
||||
|
||||
[ Jens Krüger ]
|
||||
* Fix abslimits reading from entangle device
|
||||
|
||||
[ Georg Brandl ]
|
||||
* fix LimitsType to be actually used and validated
|
||||
|
||||
-- Markus Zolliker <jenkins@frm2.tum.de> Thu, 16 May 2024 11:31:25 +0200
|
||||
|
||||
frappy-core (0.18.1) stable; urgency=medium
|
||||
|
||||
* mlz: Zapf fix unit handling and small errors
|
||||
* mlz: entangle fix limit check
|
||||
|
||||
-- Alexander Zaft <jenkins@frm2.tum.de> Wed, 24 Jan 2024 14:59:21 +0100
|
||||
|
||||
frappy-core (0.18.0) stable; urgency=medium
|
||||
|
||||
[ Alexander Zaft ]
|
||||
* Add shutdownModule function
|
||||
|
||||
[ Markus Zolliker ]
|
||||
* frappy_psi.convergence: bug fixes and improvements
|
||||
|
||||
[ Alexander Zaft ]
|
||||
* server: Add signal handling
|
||||
* add test cases for server and config
|
||||
|
||||
[ Markus Zolliker ]
|
||||
* fix frappy.lib.merge_status
|
||||
* frappy_psi.sea: try to reconnect on failure
|
||||
* pylint: disable use-dict-literal
|
||||
|
||||
[ Alexander Zaft ]
|
||||
* server: add option to dynamically create devices
|
||||
|
||||
[ Markus Zolliker ]
|
||||
* add StructParam
|
||||
* add frappy_psi.thermofisher
|
||||
* add frappy_psi.thermofisher to the doc
|
||||
* frappy.io: make error reporting consistent
|
||||
* frappy_psi.sea: avoid multiple connections
|
||||
* frappy_psi.sea: further bug fixes
|
||||
* frappy.client.interactive: bug fixes
|
||||
|
||||
[ Alexander Zaft ]
|
||||
* mlz: Add Zebra Barcode Reader
|
||||
* frappy_mlz: Zebra fixes after basic test
|
||||
* dispatcher: change logging calls to debug
|
||||
* core: do not call register_module on error
|
||||
* add zapf to requirements-dev.txt
|
||||
* frappy_mlz: Add Zapf PLC
|
||||
* Revert "add zapf to requirements-dev.txt"
|
||||
* add zapf to requirements-dev
|
||||
* frappy_mlz: fix one-off error in barcode reader
|
||||
|
||||
[ Markus Zolliker ]
|
||||
* improve error message on client when host/port is bad
|
||||
* frappy/protocol/interface/tcp.py: use SECoP_DEFAULT_PORT
|
||||
* frappy_psi.phytron: stop motor before restart
|
||||
* interactive client: improve keyboard interrupt
|
||||
* fix frappy/playground.py after change 31470
|
||||
|
||||
[ Alexander Zaft ]
|
||||
* frappy_mlz seop: add count to ampl and phase cmds
|
||||
|
||||
[ Markus Zolliker ]
|
||||
* frappy_psi.phytron: further improvements
|
||||
* further fixes after change 31470
|
||||
* fix missing .poll attribute in simulation
|
||||
* psi: improve sea interface
|
||||
* fix frappy_demo.lakeshore
|
||||
* change FloatRange arguments minval/maxval to min/max
|
||||
* improve client shutdown time
|
||||
* introduce FrozenParam
|
||||
* phytron.py: improve status
|
||||
* frappy_psi.sea: small fixes
|
||||
* bug in Attached (fix after change 31470)
|
||||
|
||||
[ Alexander Zaft ]
|
||||
* core: split module code
|
||||
* core: factor out accessibles from init
|
||||
|
||||
[ Markus Zolliker ]
|
||||
* proxy: fix command wrapper
|
||||
|
||||
[ Alexander Zaft ]
|
||||
* server: handle signals during startup
|
||||
* all: remove coding cookies
|
||||
* psi: fix Done import in sea
|
||||
|
||||
[ Markus Zolliker ]
|
||||
* frappy.io: change default to retry_first_idn=True
|
||||
|
||||
[ Alexander Zaft ]
|
||||
* core: move module handling out of dispatcher
|
||||
* mlz/demo: move old examples to Attached
|
||||
|
||||
[ Markus Zolliker ]
|
||||
* frappy.client: fix the case then timestamp is missing
|
||||
* doc: drop latex support, add pdf support
|
||||
* add StringIO.writeline, improve StringIO.multicomm
|
||||
* implement pfeiffer TPG vacuum reading
|
||||
|
||||
[ Alexander Zaft ]
|
||||
* core: allow multiple interfaces
|
||||
* core: formatting and update server docstring
|
||||
* mlz: handle unconfigured abslimits
|
||||
* datatypes: fix optional struct export
|
||||
* core: better command handling
|
||||
|
||||
[ Markus Zolliker ]
|
||||
* frappy_psi.sea: workaround for bug in sea
|
||||
|
||||
[ Alexander Zaft ]
|
||||
* core: better error on export of internal type
|
||||
|
||||
[ Markus Zolliker ]
|
||||
* fix missing import in change message
|
||||
* modify arguments of Dispatcher.announce_update
|
||||
* frappy.secnode: fix strange error message
|
||||
* fix playground after change 32249
|
||||
* remove py35 compatibility code
|
||||
* bug fix in frappy.io.BytesIO.checkHWIdent
|
||||
|
||||
-- Alexander Zaft <jenkins@frm2.tum.de> Wed, 17 Jan 2024 12:35:00 +0100
|
||||
|
||||
frappy-core (0.17.13) stable; urgency=medium
|
||||
|
||||
[ Alexander Zaft ]
|
||||
* add egg-info to gitignore
|
||||
|
||||
[ Markus Zolliker ]
|
||||
* GUI bugfix: use isChecked instead of checkState in BoolInput
|
||||
* frappy_psi.mercury/triton: add control_off command
|
||||
* frappy_psi.phytron: rename reset_error to clear_errors
|
||||
* frappy.mixins.HasOutputModule
|
||||
* frappy_psi.mercury: proper handling of control_active
|
||||
* add a hook for reads to be done initially
|
||||
* frappy_psi.triton: fix HeaterOutput.limit
|
||||
* frappy_psi.magfield: bug fix
|
||||
* frappy_psi.sea: bug fixes
|
||||
|
||||
[ Alexander Zaft ]
|
||||
* server: fix systemd variable scope
|
||||
|
||||
-- Alexander Zaft <jenkins@frm2.tum.de> Tue, 20 Jun 2023 14:38:00 +0200
|
||||
|
||||
frappy-core (0.17.12) stable; urgency=medium
|
||||
|
||||
[ Alexander Zaft ]
|
||||
* Warn about duplicate module definitions in a file
|
||||
* Add influences property to parameters/commands
|
||||
|
||||
[ Markus Zolliker ]
|
||||
* frappy.client: dummy logger is missing 'exception' method
|
||||
|
||||
[ Alexander Zaft ]
|
||||
* Add SEOP He3-polarization device
|
||||
* Typo in influences description
|
||||
* seop: fix fitparam command
|
||||
|
||||
[ Markus Zolliker ]
|
||||
* silently catches error in systemd.daemon.notify
|
||||
|
||||
[ Alexander Zaft ]
|
||||
* io: add option to retry first ident request
|
||||
* config: fix merge_modules
|
||||
* io: followup fix for retry-first-ident
|
||||
* entangle: fix tango guards for pytango 9.3
|
||||
|
||||
-- Alexander Zaft <jenkins@frm2.tum.de> Tue, 13 Jun 2023 06:51:27 +0200
|
||||
|
||||
frappy-core (0.17.11) stable; urgency=medium
|
||||
|
||||
[ Alexander Zaft ]
|
||||
* Add __format__ to EnumMember
|
||||
|
||||
[ Markus Zolliker ]
|
||||
* fix error from manual %-format conversion
|
||||
|
||||
[ Alexander Zaft ]
|
||||
* specify minimum pyqt5 version
|
||||
|
||||
[ Markus Zolliker ]
|
||||
* make io device visible as expert by default
|
||||
|
||||
[ Jens Krüger ]
|
||||
* MLZ/Entangle: Fix formatting issues
|
||||
|
||||
[ Markus Zolliker ]
|
||||
* improve error handling on callbacks
|
||||
* fix issues with lakeshore 370
|
||||
* frappy_psi: two small fixes in k2601b/ppmssim
|
||||
* a playground for debugging drivers
|
||||
* issues with StructOf
|
||||
* improve mercury temperature loop
|
||||
* improve interactive client
|
||||
|
||||
[ Jens Krüger ]
|
||||
* Server: add missing 'restart_hook' missing
|
||||
* MLZ/Entangle: Add unit init in AnalogOutput
|
||||
* MLZ/Entangle: Fix user limits handling
|
||||
|
||||
[ Alexander Zaft ]
|
||||
* Improve address and connection handling
|
||||
* Add Stopgap handling of cfg files in cfg-editor
|
||||
|
||||
[ Markus Zolliker ]
|
||||
* fix in frappy_psi.historywriter
|
||||
* improve mercury driver
|
||||
* driver for the triton dilution refrigerator
|
||||
* fixes on HasConvergence and HasOutputModule
|
||||
* improve and fix errors with parameter limits
|
||||
* add HasOffset feature
|
||||
|
||||
[ Alexander Zaft ]
|
||||
* Fix interface class list
|
||||
|
||||
[ Markus Zolliker ]
|
||||
* mixins should not inherit Module
|
||||
* fix interplay mercury.TemperatureLoop and HasConvergence
|
||||
|
||||
[ Alexander Zaft ]
|
||||
* Fix lower limit checking of FloatRange
|
||||
|
||||
[ Markus Zolliker ]
|
||||
* improve traceback while processing config file
|
||||
* driver for mercury IPS
|
||||
* add sea driver
|
||||
|
||||
[ Georg Brandl ]
|
||||
* debian: add new executable
|
||||
|
||||
[ Markus Zolliker ]
|
||||
* move more code from bin/frappy-cli to frappy/client/interactive.py
|
||||
* add unit=s to pollinterval
|
||||
* client.interactive: fix error when interface_classes empty
|
||||
* phytron motor driver
|
||||
* client: timestamps must never lie in the future
|
||||
|
||||
[ Alexander Zaft ]
|
||||
* Fix rstrip misuse in frappy-generator
|
||||
|
||||
-- Alexander Zaft <jenkins@frm2.tum.de> Thu, 25 May 2023 09:38:24 +0200
|
||||
|
||||
frappy-core (0.17.10) stable; urgency=medium
|
||||
|
||||
* Change leftover %-logging calls to lazy
|
||||
* Convert formatting automatically to f-strings
|
||||
* move unit into display label
|
||||
* [WIP] gui: add specific input widgets
|
||||
* Rebuild NodeWidget when the description changes
|
||||
* Manually convert most remaining format statements
|
||||
* make entangle mapping a dict
|
||||
|
||||
-- Alexander Zaft <jenkins@frm2.tum.de> Wed, 19 Apr 2023 14:32:52 +0200
|
||||
|
||||
frappy-core (0.17.9) stable; urgency=medium
|
||||
|
||||
* interactive client: avoid messing up the input line
|
||||
|
||||
-- Markus Zolliker <jenkins@frm2.tum.de> Tue, 11 Apr 2023 16:09:03 +0200
|
||||
|
||||
frappy-core (0.17.8) stable; urgency=medium
|
||||
|
||||
* Debian: Fix typo
|
||||
|
||||
-- Jens Krüger <jenkins@frm2.tum.de> Wed, 05 Apr 2023 07:20:25 +0200
|
||||
|
||||
frappy-core (0.17.7) stable; urgency=medium
|
||||
|
||||
* Debian: add pyqtgraph dependency
|
||||
|
||||
-- Jens Krüger <jenkins@frm2.tum.de> Wed, 05 Apr 2023 07:07:24 +0200
|
||||
|
||||
frappy-core (0.17.6) stable; urgency=medium
|
||||
|
||||
[ Alexander Zaft ]
|
||||
* gui: show parameter properties again
|
||||
* gui: full module description only in detailed mode
|
||||
* gui: switch group button order, change text
|
||||
* gui: add console history
|
||||
* gui: logwindow improvements
|
||||
* add copyright headers where missing
|
||||
* add optional validation to ValueType
|
||||
* cli: add argparse and inlcudes before repl
|
||||
|
||||
[ Markus Zolliker ]
|
||||
* improve error messages
|
||||
|
||||
[ Jens Krüger ]
|
||||
* Fix debian GUI package dependency
|
||||
|
||||
-- Alexander Zaft <jenkins@frm2.tum.de> Tue, 04 Apr 2023 08:42:26 +0200
|
||||
|
||||
frappy-core (0.17.5) stable; urgency=medium
|
||||
|
||||
* Fix generator
|
||||
|
||||
-- Alexander Zaft <jenkins@frm2.tum.de> Wed, 22 Mar 2023 12:32:06 +0100
|
||||
|
||||
frappy-core (0.17.4) stable; urgency=medium
|
||||
|
||||
* Fix entangle integration bugs
|
||||
|
||||
-- Alexander Zaft <jenkins@frm2.tum.de> Wed, 22 Mar 2023 11:44:34 +0100
|
||||
|
||||
frappy-core (0.17.3) stable; urgency=medium
|
||||
|
||||
* UNRELEASED
|
||||
|
||||
-- Alexander Zaft <jenkins@frm2.tum.de> Tue, 21 Mar 2023 15:55:09 +0100
|
||||
|
||||
frappy-core (0.17.2) stable; urgency=medium
|
||||
|
||||
[ Alexander Zaft ]
|
||||
* Fix Simulation and Proxy
|
||||
|
||||
[ Markus Zolliker ]
|
||||
* cfg path quick fix for hands-on session
|
||||
|
||||
[ Georg Brandl ]
|
||||
* add pyqtgraph to gui dependencies
|
||||
|
||||
[ Markus Zolliker ]
|
||||
* allow super calls on read_/write_ methods
|
||||
|
||||
[ Alexander Zaft ]
|
||||
* Add greeter tab to UI
|
||||
* Add recent SECNodes to Drop down menu
|
||||
* Remove auto-connect to 10767 on startup
|
||||
|
||||
[ Markus Zolliker ]
|
||||
* do not reuse address on Windows
|
||||
|
||||
[ Georg Brandl ]
|
||||
* version.py: sync with other projects
|
||||
|
||||
[ Alexander Zaft ]
|
||||
* Fix forge link in README
|
||||
|
||||
[ Markus Zolliker ]
|
||||
* better guess for cfg file location
|
||||
* fix ppms with proxy
|
||||
* default settings on the IO class
|
||||
* demo lakeshore with simulation
|
||||
|
||||
[ Georg Brandl ]
|
||||
* cfg editor: fixes
|
||||
* installer: add config for frappy gui exe
|
||||
|
||||
[ Markus Zolliker ]
|
||||
* make doc should not fail when version is not available
|
||||
|
||||
[ Alexander Zaft ]
|
||||
* Add reconnect Action
|
||||
|
||||
[ Georg Brandl ]
|
||||
* readme: fix make call
|
||||
* gui: add about dialog, remove "about Qt" menu entry
|
||||
* gui: move QSECNode to separate module
|
||||
* gui: isort
|
||||
|
||||
[ Alexander Zaft ]
|
||||
* Fix Node adding logic
|
||||
* Remove unneeded constants
|
||||
* Make input field more distinct
|
||||
|
||||
[ Markus Zolliker ]
|
||||
* AsynConn.uri: better handling for missing scheme
|
||||
|
||||
[ Georg Brandl ]
|
||||
* gui: remove unused ui file
|
||||
* gui: better label for param set button
|
||||
|
||||
[ Alexander Zaft ]
|
||||
* Always make a greeter tab
|
||||
|
||||
[ Markus Zolliker ]
|
||||
* improve online help of frappy-cli
|
||||
* refresh logging when reconnected while watching
|
||||
|
||||
[ Alexander Zaft ]
|
||||
* [Needs Feedback] Add PyQt6. Remove PyQt4
|
||||
* Merge resource files
|
||||
* Extend Node and Module widgets
|
||||
* Scroll Module area instead of replacing widgets
|
||||
* Remove Modulectrl Widget
|
||||
|
||||
[ Georg Brandl ]
|
||||
* gui: better display of protocol version
|
||||
* gui: cleanup code in modulewidget
|
||||
* gui: always show scrollbar in nodewidget
|
||||
|
||||
[ Alexander Zaft ]
|
||||
* Short background Color animation on scroll
|
||||
|
||||
[ Markus Zolliker ]
|
||||
* treat returning None from write_<param> properly
|
||||
|
||||
[ Alexander Zaft ]
|
||||
* Fix doubled module info
|
||||
|
||||
[ Georg Brandl ]
|
||||
* Jenkinsfile: isort does not fail the build
|
||||
* isort: add firstparty
|
||||
|
||||
[ Alexander Zaft ]
|
||||
* gui: make module details button checkable
|
||||
* gui: Logo in greeter
|
||||
|
||||
[ Enrico Faulhaber ]
|
||||
* handle connection close more gracefully
|
||||
|
||||
[ Markus Zolliker ]
|
||||
* split BadValue into WrongType and RangeError
|
||||
|
||||
[ Georg Brandl ]
|
||||
* demo: fixup Switch class
|
||||
|
||||
[ Alexander Zaft ]
|
||||
* fix importing AsynCon without serial
|
||||
|
||||
[ Markus Zolliker ]
|
||||
* gui: support proper formatting of values
|
||||
* central point for status codes
|
||||
|
||||
[ Alexander Zaft ]
|
||||
* fix README typo
|
||||
* gui: terminate connection on tab close
|
||||
* check configured names for spaces
|
||||
|
||||
[ Markus Zolliker ]
|
||||
* make return value 'Done' unneccessary
|
||||
* enhanced parameter range checks
|
||||
* simplify status type declaration
|
||||
* improve frappy.errors
|
||||
* remove UNKNOWN, UNSTABLE and DISABLED from Readable.status
|
||||
* fix generalConfig defaults
|
||||
|
||||
[ Alexander Zaft ]
|
||||
* Add SECoP link to readme
|
||||
* gui: more greeter interactions
|
||||
|
||||
[ Markus Zolliker ]
|
||||
* fix issues when closing tabs
|
||||
* format unit properly in the case of nested arrays
|
||||
|
||||
-- Alexander Zaft <jenkins@frm2.tum.de> Tue, 21 Mar 2023 15:49:06 +0100
|
||||
|
||||
frappy-core (0.17.1) stable; urgency=medium
|
||||
|
||||
[ Georg Brandl ]
|
||||
* gitignore: ignore demo PID file
|
||||
* config: demo config fixes
|
||||
* cfg: repair demo cfg after conversion
|
||||
* gui: make spacing more consistent
|
||||
|
||||
[ Markus Zolliker ]
|
||||
* fix links in doc/introduction
|
||||
|
||||
[ Georg Brandl ]
|
||||
* gui console: better formatting of input/output
|
||||
* gui: as a stopgap measure, apply %g format to floats
|
||||
* gui: clear tree selection by clicking into empty space
|
||||
* gui: make plot windows children of the node, so they close automatically
|
||||
|
||||
-- Georg Brandl <jenkins@frm2.tum.de> Tue, 21 Feb 2023 17:44:56 +0100
|
||||
|
||||
frappy-core (0.17.0) stable; urgency=medium
|
||||
|
||||
[ Alexander Zaft ]
|
||||
* Rework GUI.
|
||||
|
||||
[ Georg Brandl ]
|
||||
* ci: remove duplicate variable
|
||||
* doc: fix trailing comma in authors
|
||||
|
||||
-- Alexander Zaft <jenkins@frm2.tum.de> Tue, 21 Feb 2023 13:52:17 +0100
|
||||
|
||||
frappy-core (0.16.1) stable; urgency=medium
|
||||
|
||||
* UNRELEASED
|
||||
|
||||
-- Georg Brandl <jenkins@frm2.tum.de> Tue, 21 Feb 2023 08:44:28 +0100
|
||||
|
||||
frappy-core (0.16.4) stable; urgency=medium
|
||||
|
||||
* UNRELEASED
|
||||
|
||||
-- Georg Brandl <jenkins@frm2.tum.de> Tue, 21 Feb 2023 08:09:20 +0100
|
||||
|
||||
frappy-core (0.16.3) stable; urgency=medium
|
||||
|
||||
* UNRELEASED
|
||||
|
||||
-- Georg Brandl <jenkins@frm2.tum.de> Tue, 21 Feb 2023 08:00:15 +0100
|
||||
|
||||
frappy-core (0.16.2) stable; urgency=medium
|
||||
|
||||
* gui: move icon resources for the cfg editor to its subdirectory
|
||||
|
||||
-- Georg Brandl <jenkins@frm2.tum.de> Tue, 21 Feb 2023 07:50:13 +0100
|
||||
|
||||
frappy-core (0.16.1) stable; urgency=medium
|
||||
|
||||
* add frappy-cli to package
|
||||
|
||||
-- Enrico Faulhaber <jenkins@frm2.tum.de> Mon, 20 Feb 2023 17:17:23 +0100
|
||||
|
||||
frappy-core (0.16.0) stable; urgency=medium
|
||||
|
||||
[ Enrico Faulhaber ]
|
||||
* fix sorce package name
|
||||
|
||||
[ Markus Zolliker ]
|
||||
* interactive client: fix detection of overriding modules
|
||||
|
||||
[ Alexander Zaft ]
|
||||
* Fix error Message for too large arrays
|
||||
|
||||
[ Markus Zolliker ]
|
||||
* redesign of the state machine
|
||||
|
||||
[ Alexander Zaft ]
|
||||
* Fix identification response
|
||||
* GUI: add logging infra, switch to argparse
|
||||
|
||||
[ Bjoern Pedersen ]
|
||||
* Improve jenkinsfile
|
||||
|
||||
[ Alexander Zaft ]
|
||||
* Change config to Python
|
||||
|
||||
[ Markus Zolliker ]
|
||||
* improve He level tutorial
|
||||
* client: detect original frappy error class
|
||||
* rework datatypes (setter should not check limits)
|
||||
|
||||
[ Alexander Zaft ]
|
||||
* Bring demo up to date
|
||||
* Revert limit change in demo
|
||||
|
||||
[ Markus Zolliker ]
|
||||
* improve parameter initialisation
|
||||
* fix copy method of Attached
|
||||
|
||||
[ Alexander Zaft ]
|
||||
* Add .desktop file
|
||||
|
||||
[ Markus Zolliker ]
|
||||
* improve persistent parameters
|
||||
* do not throw ZeroDivisonError when pollinterval is 0
|
||||
* HasStates: fix status code inheritance
|
||||
* HasControlledBy and HasOutputModule mixins
|
||||
* adapt tutorial to new config file format
|
||||
* raise ProtcolError when specifier is missing
|
||||
* interactive client: improve watch function
|
||||
* add lakeshore demo for hands-on workshop
|
||||
* T controller tutorial and improve documentation
|
||||
* do proper value import on the client side
|
||||
|
||||
[ Alexander Zaft ]
|
||||
* Add initial README
|
||||
* Change Readme title
|
||||
* Convert example configs to python
|
||||
|
||||
[ Markus Zolliker ]
|
||||
* README: add link to doc on forge.frm2.tum.de
|
||||
|
||||
-- Enrico Faulhaber <jenkins@frm2.tum.de> Mon, 20 Feb 2023 16:15:10 +0100
|
||||
|
||||
frappy-core (0.15.0) stable; urgency=medium
|
||||
|
||||
[ Björn Pedersen ]
|
||||
* Remove iohandler left-overs from docs
|
||||
@@ -28,7 +936,7 @@ frappy-core (0.15.0) focal; urgency=medium
|
||||
|
||||
-- Björn Pedersen <jenkins@frm2.tum.de> Thu, 10 Nov 2022 14:46:01 +0100
|
||||
|
||||
secop-core (0.14.3) focal; urgency=medium
|
||||
secop-core (0.14.3) stable; urgency=medium
|
||||
|
||||
[ Enrico Faulhaber ]
|
||||
* change repo to secop/frappy
|
||||
@@ -44,13 +952,13 @@ secop-core (0.14.3) focal; urgency=medium
|
||||
|
||||
-- Enrico Faulhaber <jenkins@frm2.tum.de> Thu, 03 Nov 2022 13:51:52 +0100
|
||||
|
||||
secop-core (0.14.2) focal; urgency=medium
|
||||
secop-core (0.14.2) stable; urgency=medium
|
||||
|
||||
* systemd generator: adapt to changed config API
|
||||
|
||||
-- Georg Brandl <jenkins@frm2.tum.de> Thu, 20 Oct 2022 15:38:45 +0200
|
||||
|
||||
secop-core (0.14.1) focal; urgency=medium
|
||||
secop-core (0.14.1) stable; urgency=medium
|
||||
|
||||
[ Markus Zolliker ]
|
||||
* secop_psi.entangle.AnalogInput: fix main value
|
||||
@@ -62,7 +970,7 @@ secop-core (0.14.1) focal; urgency=medium
|
||||
|
||||
-- Markus Zolliker <jenkins@frm2.tum.de> Thu, 20 Oct 2022 14:04:07 +0200
|
||||
|
||||
secop-core (0.14.0) focal; urgency=medium
|
||||
secop-core (0.14.0) stable; urgency=medium
|
||||
|
||||
* add simple interactive python client
|
||||
* fix undefined status in softcal
|
||||
@@ -76,7 +984,7 @@ secop-core (0.14.0) focal; urgency=medium
|
||||
|
||||
-- Markus Zolliker <jenkins@frm2.tum.de> Wed, 19 Oct 2022 11:31:50 +0200
|
||||
|
||||
secop-core (0.13.1) focal; urgency=medium
|
||||
secop-core (0.13.1) stable; urgency=medium
|
||||
|
||||
[ Markus Zolliker ]
|
||||
* an enum with value 0 should be interpreted as False
|
||||
@@ -87,7 +995,7 @@ secop-core (0.13.1) focal; urgency=medium
|
||||
|
||||
-- Markus Zolliker <jenkins@jenkins02.admin.frm2.tum.de> Tue, 02 Aug 2022 15:31:52 +0200
|
||||
|
||||
secop-core (0.13.0) focal; urgency=medium
|
||||
secop-core (0.13.0) stable; urgency=medium
|
||||
|
||||
[ Georg Brandl ]
|
||||
* debian: fix email addresses in changelog
|
||||
@@ -150,13 +1058,13 @@ secop-core (0.13.0) focal; urgency=medium
|
||||
|
||||
-- Georg Brandl <jenkins@frm2.tum.de> Tue, 02 Aug 2022 09:47:06 +0200
|
||||
|
||||
secop-core (0.12.4) focal; urgency=medium
|
||||
secop-core (0.12.4) stable; urgency=medium
|
||||
|
||||
* fix command inheritance
|
||||
|
||||
-- Markus Zolliker <jenkins@jenkins01.admin.frm2.tum.de> Thu, 11 Nov 2021 16:21:19 +0100
|
||||
|
||||
secop-core (0.12.3) focal; urgency=medium
|
||||
secop-core (0.12.3) stable; urgency=medium
|
||||
|
||||
[ Georg Brandl ]
|
||||
* Makefile: fix docker image
|
||||
@@ -179,7 +1087,7 @@ secop-core (0.12.3) focal; urgency=medium
|
||||
|
||||
-- Georg Brandl <jenkins@jenkins01.admin.frm2.tum.de> Wed, 10 Nov 2021 16:33:19 +0100
|
||||
|
||||
secop-core (0.12.2) focal; urgency=medium
|
||||
secop-core (0.12.2) stable; urgency=medium
|
||||
|
||||
[ Markus Zolliker ]
|
||||
* fix issue with new syntax in simulation
|
||||
@@ -191,13 +1099,13 @@ secop-core (0.12.2) focal; urgency=medium
|
||||
|
||||
-- Markus Zolliker <jenkins@jenkins01.admin.frm2.tum.de> Tue, 18 May 2021 10:29:17 +0200
|
||||
|
||||
secop-core (0.12.1) focal; urgency=medium
|
||||
secop-core (0.12.1) stable; urgency=medium
|
||||
|
||||
* remove secop-console from debian *.install file
|
||||
|
||||
-- Enrico Faulhaber <jenkins@jenkins02.admin.frm2.tum.de> Tue, 04 May 2021 09:42:53 +0200
|
||||
|
||||
secop-core (0.12.0) focal; urgency=medium
|
||||
secop-core (0.12.0) stable; urgency=medium
|
||||
|
||||
[ Markus Zolliker ]
|
||||
* make datatypes immutable
|
||||
|
||||
1
debian/compat
vendored
1
debian/compat
vendored
@@ -1 +0,0 @@
|
||||
11
|
||||
24
debian/control
vendored
24
debian/control
vendored
@@ -2,7 +2,7 @@ Source: frappy-core
|
||||
Section: contrib/misc
|
||||
Priority: optional
|
||||
Maintainer: Enrico Faulhaber <enrico.faulhaber@frm2.tum.de>
|
||||
Build-Depends: debhelper (>= 11~),
|
||||
Build-Depends: debhelper-compat (= 13),
|
||||
dh-python,
|
||||
python3 (>=3.6),
|
||||
python3-all,
|
||||
@@ -20,7 +20,7 @@ Build-Depends: debhelper (>= 11~),
|
||||
git,
|
||||
markdown,
|
||||
python3-daemon
|
||||
Standards-Version: 4.1.4
|
||||
Standards-Version: 4.6.2
|
||||
X-Python3-Version: >= 3.6
|
||||
|
||||
Package: frappy-core
|
||||
@@ -35,8 +35,10 @@ Depends: python3 (>= 3.6),
|
||||
python3-mlzlog,
|
||||
markdown,
|
||||
python3-daemon
|
||||
Replaces: secop-core (<= 0.14.3)
|
||||
Breaks: secop-core (<= 0.14.3)
|
||||
Replaces: secop-core (<= 0.14.3),
|
||||
frappy-demo (<= 0.19.7)
|
||||
Breaks: secop-core (<= 0.14.3),
|
||||
frappy-demo (<= 0.19.7)
|
||||
Description: Frappy SECoP core system
|
||||
contains the core server and client libraries and the server binary
|
||||
as well as the systemd integration
|
||||
@@ -54,23 +56,13 @@ Architecture: all
|
||||
Depends: frappy-core,
|
||||
${misc:Depends},
|
||||
${python3:Depends},
|
||||
python3-pyqt (>=4)
|
||||
python3-pyqtgraph (>=0.11.0),
|
||||
python3-pyqt5 (>=5)
|
||||
Replaces: secop-gui (<= 0.14.3)
|
||||
Breaks: secop-gui (<= 0.14.3)
|
||||
Description: Frappy SECoP gui client + cfgtool
|
||||
contains the GUI client and the configurator
|
||||
|
||||
Package: frappy-demo
|
||||
Architecture: all
|
||||
Depends: frappy-core,
|
||||
${misc:Depends},
|
||||
${python3:Depends}
|
||||
Replaces: secop-demo (<= 0.14.3)
|
||||
Breaks: secop-demo (<= 0.14.3)
|
||||
Recommends: frappy-gui
|
||||
Description: SECoP demo files
|
||||
for demonstration purposes
|
||||
|
||||
Package: frappy-ess
|
||||
Architecture: all
|
||||
Depends: frappy-core,
|
||||
|
||||
4
debian/copyright
vendored
4
debian/copyright
vendored
@@ -5,11 +5,11 @@ Comment: FRAPPY is an implementation of the free SECoP protocol
|
||||
see https://www.github.com/SampleEnvironment/SECoP
|
||||
|
||||
Files: *
|
||||
Copyright: 2016-2022 by the FRAPPY-SECOP contributors (see AUTHORS)
|
||||
Copyright: 2016-2024 by the FRAPPY-SECOP contributors (see AUTHORS)
|
||||
License: GPL-2
|
||||
|
||||
Files: debian/*
|
||||
Copyright: 2015-2022 Enrico Faulhaber <enrico.faulhaber@frm2.tum.de>
|
||||
Copyright: 2015-2024 Enrico Faulhaber <enrico.faulhaber@frm2.tum.de>
|
||||
License: GPL-2
|
||||
|
||||
License: GPL-2
|
||||
|
||||
6
debian/frappy-core.install
vendored
6
debian/frappy-core.install
vendored
@@ -1,9 +1,15 @@
|
||||
usr/bin/frappy-cli
|
||||
usr/bin/frappy-server
|
||||
usr/bin/frappy-play
|
||||
usr/bin/frappy-scan
|
||||
usr/lib/python3.*/dist-packages/frappy/*.py
|
||||
usr/lib/python3.*/dist-packages/frappy/__pycache__
|
||||
usr/lib/python3.*/dist-packages/frappy/lib
|
||||
usr/lib/python3.*/dist-packages/frappy/client
|
||||
usr/lib/python3.*/dist-packages/frappy/protocol
|
||||
usr/lib/python3.*/dist-packages/frappy_core-*
|
||||
usr/lib/python3.*/dist-packages/frappy/RELEASE-VERSION
|
||||
usr/lib/python3.*/dist-packages/frappy_demo
|
||||
lib/systemd
|
||||
var/log/frappy
|
||||
etc/frappy/generalConfig.cfg
|
||||
|
||||
2
debian/frappy-demo.install
vendored
2
debian/frappy-demo.install
vendored
@@ -1,2 +0,0 @@
|
||||
usr/lib/python3.*/dist-packages/frappy_demo
|
||||
usr/lib/python3.*/dist-packages/frappy/__pycache__
|
||||
1
debian/frappy-gui.install
vendored
1
debian/frappy-gui.install
vendored
@@ -1,3 +1,2 @@
|
||||
usr/bin/frappy-gui
|
||||
usr/bin/frappy-cfg-editor
|
||||
usr/lib/python3.*/dist-packages/frappy/gui
|
||||
|
||||
1
debian/rules
vendored
1
debian/rules
vendored
@@ -11,6 +11,7 @@ override_dh_install:
|
||||
rmdir debian/tmp
|
||||
mv debian/python3-frappy debian/tmp
|
||||
|
||||
install -m644 -Dt debian/tmp/etc/frappy etc/generalConfig.cfg
|
||||
dh_install -i -O--buildsystem=pybuild
|
||||
dh_missing --fail-missing
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Frappy documentation build configuration file, created by
|
||||
# sphinx-quickstart on Mon Sep 11 10:58:28 2017.
|
||||
@@ -43,7 +42,9 @@ extensions = ['sphinx.ext.autodoc',
|
||||
'sphinx.ext.intersphinx',
|
||||
'sphinx.ext.todo',
|
||||
'sphinx.ext.mathjax',
|
||||
'sphinx.ext.viewcode']
|
||||
'sphinx.ext.viewcode',
|
||||
'rst2pdf.pdfbuilder',
|
||||
]
|
||||
|
||||
# Add any paths that contain templates here, relative to this directory.
|
||||
templates_path = ['_templates']
|
||||
@@ -58,7 +59,7 @@ master_doc = 'index'
|
||||
|
||||
# General information about the project.
|
||||
project = 'Frappy'
|
||||
copyright = '2017-2021, Enrico Faulhaber, Markus Zolliker,'
|
||||
copyright = '2017-2024, Enrico Faulhaber, Markus Zolliker'
|
||||
#copyright = '2017, SECoP Committee'
|
||||
author = 'Enrico Faulhaber, Markus Zolliker'
|
||||
|
||||
@@ -66,10 +67,13 @@ author = 'Enrico Faulhaber, Markus Zolliker'
|
||||
# |version| and |release|, also used in various other places throughout the
|
||||
# built documents.
|
||||
#
|
||||
# The full version, including alpha/beta/rc tags.
|
||||
release = get_version()
|
||||
# The short X.Y version.
|
||||
version = release.split('-')[0]
|
||||
try:
|
||||
# The full version, including alpha/beta/rc tags.
|
||||
release = get_version()
|
||||
# The short X.Y version.
|
||||
version = release.split('-')[0]
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
# The language for content autogenerated by Sphinx. Refer to documentation
|
||||
# for a list of supported languages.
|
||||
@@ -211,9 +215,86 @@ epub_exclude_files = ['search.html']
|
||||
|
||||
|
||||
# Example configuration for intersphinx: refer to the Python standard library.
|
||||
intersphinx_mapping = {'https://docs.python.org/3/': None}
|
||||
# intersphinx_mapping = {'https://docs.python.org/3/': None}
|
||||
|
||||
from frappy.lib.classdoc import class_doc_handler
|
||||
|
||||
def setup(app):
|
||||
app.connect('autodoc-process-docstring', class_doc_handler)
|
||||
|
||||
|
||||
# -- Options for PDF output --------------------------------------------------
|
||||
# Grouping the document tree into PDF files. List of tuples
|
||||
# (source start file, target name, title, author, options).
|
||||
#
|
||||
# If there is more than one author, separate them with \\.
|
||||
# For example: r'Guido van Rossum\\Fred L. Drake, Jr., editor'
|
||||
#
|
||||
# The options element is a dictionary that lets you override
|
||||
# this config per-document. For example:
|
||||
#
|
||||
# ('index', 'MyProject', 'My Project', 'Author Name', {'pdf_compressed': True})
|
||||
#
|
||||
# would mean that specific document would be compressed
|
||||
# regardless of the global 'pdf_compressed' setting.
|
||||
pdf_documents = [
|
||||
('index', project, project, author),
|
||||
]
|
||||
# A comma-separated list of custom stylesheets. Example:
|
||||
pdf_stylesheets = ['sphinx', 'a4']
|
||||
# A list of folders to search for stylesheets. Example:
|
||||
pdf_style_path = ['.', '_styles']
|
||||
# Create a compressed PDF
|
||||
# Use True/False or 1/0
|
||||
# Example: compressed=True
|
||||
# pdf_compressed = False
|
||||
# A colon-separated list of folders to search for fonts. Example:
|
||||
# pdf_font_path = ['/usr/share/fonts', '/usr/share/texmf-dist/fonts/']
|
||||
# Language to be used for hyphenation support
|
||||
# pdf_language = "en_US"
|
||||
# Mode for literal blocks wider than the frame. Can be
|
||||
# overflow, shrink or truncate
|
||||
# pdf_fit_mode = "shrink"
|
||||
# Section level that forces a break page.
|
||||
# For example: 1 means top-level sections start in a new page
|
||||
# 0 means disabled
|
||||
# pdf_break_level = 0
|
||||
# When a section starts in a new page, force it to be 'even', 'odd',
|
||||
# or just use 'any'
|
||||
# pdf_breakside = 'any'
|
||||
# Insert footnotes where they are defined instead of
|
||||
# at the end.
|
||||
# pdf_inline_footnotes = True
|
||||
# verbosity level. 0 1 or 2
|
||||
# pdf_verbosity = 0
|
||||
# If false, no index is generated.
|
||||
# pdf_use_index = True
|
||||
# If false, no modindex is generated.
|
||||
# pdf_use_modindex = True
|
||||
# If false, no coverpage is generated.
|
||||
# pdf_use_coverpage = True
|
||||
# Name of the cover page template to use
|
||||
# pdf_cover_template = 'sphinxcover.tmpl'
|
||||
# Documents to append as an appendix to all manuals.
|
||||
# pdf_appendices = []
|
||||
# Enable experimental feature to split table cells. Use it
|
||||
# if you get "DelayedTable too big" errors
|
||||
# pdf_splittables = False
|
||||
# Set the default DPI for images
|
||||
# pdf_default_dpi = 72
|
||||
# Enable rst2pdf extension modules
|
||||
# pdf_extensions = []
|
||||
# Page template name for "regular" pages
|
||||
# pdf_page_template = 'cutePage'
|
||||
# Show Table Of Contents at the beginning?
|
||||
# pdf_use_toc = True
|
||||
# How many levels deep should the table of contents be?
|
||||
pdf_toc_depth = 9999
|
||||
# Add section number to section references
|
||||
pdf_use_numbered_links = False
|
||||
# Background images fitting mode
|
||||
pdf_fit_background_mode = 'scale'
|
||||
# Repeat table header on tables that cross a page boundary?
|
||||
pdf_repeat_table_rows = True
|
||||
# Enable smart quotes (1, 2 or 3) or disable by setting to 0
|
||||
pdf_smartquotes = 0
|
||||
|
||||
58
doc/source/configuration.rst
Normal file
58
doc/source/configuration.rst
Normal file
@@ -0,0 +1,58 @@
|
||||
Configuration File
|
||||
..................
|
||||
|
||||
.. _node configuration:
|
||||
|
||||
:Node(equipment_id, description, interface, \*\*kwds):
|
||||
|
||||
Specify the SEC-node properties.
|
||||
|
||||
The arguments are SECoP node properties and additional internal node configurations
|
||||
|
||||
:Parameters:
|
||||
|
||||
- **equipment_id** - a globally unique string identifying the SEC node
|
||||
- **description** - a human readable description of the SEC node
|
||||
- **interface** - an uri style string indication the address for the server
|
||||
- **kwds** - other SEC node properties
|
||||
|
||||
.. _mod configuration:
|
||||
|
||||
:Mod(name, cls, description, \*\*kwds):
|
||||
|
||||
Create a SECoP module.
|
||||
Keyworded argument matching a parameter name are used to configure
|
||||
the initial value of a parameter. For configuring the parameter properties
|
||||
the value must be an instance of **Param**, using the keyworded arguments
|
||||
for modifying the default values of the parameter properties. In this case,
|
||||
the initial value may be given as the first positional argument.
|
||||
In case command properties are to be modified **Command** has to be used.
|
||||
|
||||
:Parameters:
|
||||
|
||||
- **name** - the module name
|
||||
- **cls** - a qualified class name or the python class of a module
|
||||
- **description** - a human readable description of the module
|
||||
- **kwds** - parameter, property or command configurations
|
||||
|
||||
.. _param configuration:
|
||||
|
||||
:Param(value=<undef>, \*\*kwds):
|
||||
|
||||
Configure a parameter
|
||||
|
||||
:Parameters:
|
||||
|
||||
- **value** - if given, the initial value of the parameter
|
||||
- **kwds** - parameter or datatype SECoP properties (see :class:`frappy.param.Parameter`
|
||||
and :class:`frappy.datatypes.Datatypes`)
|
||||
|
||||
.. _command configuration:
|
||||
|
||||
:Command(\*\*kwds):
|
||||
|
||||
Configure a command
|
||||
|
||||
:Parameters:
|
||||
|
||||
- **kwds** - command SECoP properties (see :class:`frappy.param.Commands`)
|
||||
@@ -8,3 +8,7 @@ Demo
|
||||
.. automodule:: frappy_demo.test
|
||||
:show-inheritance:
|
||||
:members:
|
||||
|
||||
.. automodule:: frappy_demo.lakeshore
|
||||
:show-inheritance:
|
||||
:members:
|
||||
|
||||
@@ -25,3 +25,10 @@ Calibrated sensors and control loop not yet supported.
|
||||
:show-inheritance:
|
||||
:members:
|
||||
|
||||
Bath Thermostat Thermofisher
|
||||
............................
|
||||
|
||||
.. automodule:: frappy_psi.thermofisher
|
||||
:show-inheritance:
|
||||
:members:
|
||||
|
||||
|
||||
@@ -5,6 +5,10 @@ Frappy Programming Guide
|
||||
:maxdepth: 2
|
||||
|
||||
introduction
|
||||
structure
|
||||
programming
|
||||
magic
|
||||
server
|
||||
tutorial
|
||||
reference
|
||||
frappy_psi
|
||||
|
||||
@@ -57,14 +57,30 @@ to provide to the user.
|
||||
Programming a Driver
|
||||
--------------------
|
||||
|
||||
Programming a driver means extending one of the base classes like :class:`frappy.modules.Readable`
|
||||
or :class:`frappy.modules.Drivable`. The parameters are defined in the dict :py:attr:`parameters`, as a
|
||||
class attribute of the extended class, using the :class:`frappy.params.Parameter` constructor, or in case
|
||||
of altering the properties of an inherited parameter, :class:`frappy.params.Override`.
|
||||
:ref:`Programming a driver <class_coding>` means:
|
||||
|
||||
- selecting a base class to be extended (e.g. :class:`frappy.modules.Readable`
|
||||
or :class:`frappy.modules.Drivable`).
|
||||
- defining the parameters
|
||||
- coding the methods to retrieve and access these parameters
|
||||
|
||||
|
||||
Support for Communication with the Hardware
|
||||
-------------------------------------------
|
||||
|
||||
Often the access to the hardware has to be done over a serial communication over LAN,
|
||||
RS232 or USB. The mixin :class:`frappy.io.HasIO` and the classes :class:`frappy.io.StringIO`
|
||||
and :class:`frappy.io.BytesIO` have all the functionality needed for this.
|
||||
|
||||
Some hardware also requires calls to libraries offered by the manufacturers, certainly this
|
||||
is also possible. In case there is no python package for this, but a C/C++ API, you might
|
||||
use one of the following:
|
||||
|
||||
- `Ctypes (A foreign function library for Python) <https://docs.python.org/3/library/ctypes.html>`_
|
||||
- `CFFI (C Foreign Function Interface for Python) <https://cffi.readthedocs.io/>`_
|
||||
- `Extending Python with C or C++ <https://docs.python.org/3/extending/extending.html>`_
|
||||
|
||||
|
||||
.. TODO: shift this to an extra section
|
||||
|
||||
Parameters usually need a method :meth:`read_<name>()`
|
||||
implementing the code to retrieve their value from the hardware. Writeable parameters
|
||||
(with the argument ``readonly=False``) usually need a method :meth:`write_<name>(<value>)`
|
||||
implementing how they are written to the hardware. Above methods may be omitted, when
|
||||
there is no interaction with the hardware involved.
|
||||
|
||||
|
||||
78
doc/source/magic.rst
Normal file
78
doc/source/magic.rst
Normal file
@@ -0,0 +1,78 @@
|
||||
Frappy Internals
|
||||
----------------
|
||||
|
||||
Frappy is a powerful framework, which does everything behind the
|
||||
scenes you need for getting a SEC node to work. This section describes
|
||||
what the framwork does for you.
|
||||
|
||||
Startup
|
||||
.......
|
||||
|
||||
On startup several methods are called. First :meth:`earlyInit` is called on all modules.
|
||||
Use this to initialize attributes independent of other modules, if you can not initialize
|
||||
as a class attribute, for example for mutable attributes.
|
||||
|
||||
Then :meth:`initModule` is called for all modules.
|
||||
Use it to initialize things related to other modules, for example registering callbacks.
|
||||
|
||||
After this, :meth:`startModule` is called with a callback function argument.
|
||||
:func:`frappy.modules.Module.startModule` starts the poller thread, calling
|
||||
:meth:`writeInitParams` for writing initial parameters to hardware, followed
|
||||
by :meth:`initialReads`. The latter is meant for reading values from hardware,
|
||||
which are not polled continuously. Then all parameters configured for poll are polled
|
||||
by calling the corresponding read_*() method. The end of this last initialisation
|
||||
step is indicated to the server by the callback function.
|
||||
After this, the poller thread starts regular polling, see next section.
|
||||
|
||||
When overriding one of above methods, do not forget to super call.
|
||||
|
||||
|
||||
.. _polling:
|
||||
|
||||
Polling
|
||||
.......
|
||||
|
||||
By default, a module inheriting from :class:`Readable <frappy.modules.Readable>` is
|
||||
polled every :attr:`pollinterval` seconds. More exactly, the :meth:`doPoll`
|
||||
method is called, which by default calls :meth:`read_value` and :meth:`read_status`.
|
||||
|
||||
The programmer might override the behaviour of :meth:`doPoll`, often it is wise
|
||||
to super call the inherited method.
|
||||
|
||||
:Note:
|
||||
|
||||
Even for modules not inheriting from :class:`Readable <frappy.modules.Readable>`,
|
||||
:meth:`doPoll` is called regularly. Its default implementation is doing nothing,
|
||||
but may be overridden to do customized polling.
|
||||
|
||||
In addition, the :meth:`read_<param>` method is called every :attr:`slowinterval`
|
||||
seconds for all parameters, in case the value was not updated since :attr:`pollinterval`
|
||||
seconds.
|
||||
|
||||
The decorator :func:`nopoll <frappy.rwhandler.nopoll>` might be used on a :meth:`read_<param>`
|
||||
method in order to indicate, that the value is not polled by the slow poll mechanism.
|
||||
|
||||
|
||||
.. _client notification:
|
||||
|
||||
Client Notification
|
||||
...................
|
||||
|
||||
Whenever a parameter is changed by assigning a value to the attribute or by
|
||||
means of the access method, an ``update`` message is sent to all activated clients.
|
||||
Frappy implements the extended version of the ``activate`` message, where single modules
|
||||
and parameters might be activated.
|
||||
|
||||
|
||||
.. _type check:
|
||||
|
||||
Type check and type conversion
|
||||
..............................
|
||||
|
||||
Assigning a parameter to a value by setting the attribute via ``self.<param> = <value>``
|
||||
or ``<module>.<param> = <value>`` involves a type check and possible a type conversion,
|
||||
but not a range check for numeric types. The range check is only done on a ``change``
|
||||
message.
|
||||
|
||||
|
||||
TODO: error handling, logging
|
||||
117
doc/source/programming.rst
Normal file
117
doc/source/programming.rst
Normal file
@@ -0,0 +1,117 @@
|
||||
Coding
|
||||
======
|
||||
|
||||
.. _class_coding:
|
||||
|
||||
Coding a Class for a SECoP Module
|
||||
---------------------------------
|
||||
|
||||
A SECoP module is represented as an instance of a python class.
|
||||
For programming such a class, typically you create a
|
||||
subclass of one of the base classes :class:`Readable <frappy.modules.Readable>`,
|
||||
:class:`Writable <frappy.modules.Writable>` or :class:`Drivable <frappy.modules.Drivable>`.
|
||||
It is also quite common to inherit from classes created for similar modules,
|
||||
and or to inherit from a mixin class like :class:`HasIO <frappy.io.HasIO>`.
|
||||
|
||||
For creating the :ref:`parameters <module structure parameters>`,
|
||||
class attributes are used, using the name of
|
||||
the parameter as the attribute name and an instantiation of :class:`frappy.params.Parameter`
|
||||
for defining the parameter. If a parameter is already given by an inherited class,
|
||||
the parameter declaration might be omitted, or just its altered properties
|
||||
have to be given.
|
||||
|
||||
In addition, you might need one or several configurable items
|
||||
(see :ref:`properties <module structure properties>`), declared in the same way, with
|
||||
``<property name> =`` :class:`frappy.params.Property` ``(...)``.
|
||||
|
||||
For each of the parameters, the behaviour has to be programmed with the
|
||||
following access methods:
|
||||
|
||||
def read\_\ *<parameter>*\ (self):
|
||||
Called on a ``read`` SECoP message and whenever the internal poll mechanism
|
||||
of Frappy tries to get a new value. The return value should be the
|
||||
retrieved value.
|
||||
This method might also be called internally, in case a fresh value of
|
||||
the parameter is needed.
|
||||
|
||||
.. admonition:: polling
|
||||
|
||||
The Frappy framework has a built in :ref:`polling <polling>` mechanism,
|
||||
which calls above method regularely. Each time ``read_<parameter>`` is
|
||||
called, the Frappy framework ensures then that the value of the parameter
|
||||
is updated and the activated clients will be notified by means of an
|
||||
``update`` message.
|
||||
|
||||
def write\_\ *<parameter>*\ (self, value):
|
||||
Called on a ``change`` SECoP message. The ``value`` argument is the value
|
||||
given by the change message, and the method should implement the change,
|
||||
typically by handing it over to the hardware. On success, the method must
|
||||
return the accepted value. If the value may be read back
|
||||
from the hardware, the readback value should be returned, which might be
|
||||
slighly altered for example by rounding. The idea is, that the returned
|
||||
value would be the same, as if it would be done by the ``read_<parameter>``
|
||||
method. Often the easiest implementation is just returning the result of
|
||||
a call to the ``read_<parameter>`` method.
|
||||
|
||||
.. admonition:: behind the scenes
|
||||
|
||||
Assigning a parameter to a value by setting the attribute via
|
||||
``self.<param> = <value>`` or ``<module>.<param> = <value>`` includes
|
||||
a :ref:`type check <type check>`, some type conversion and ensures that
|
||||
a :ref:`notification <client notification>` with an
|
||||
``update`` message is sent to all activated clients.
|
||||
|
||||
Example code:
|
||||
|
||||
.. code:: python
|
||||
|
||||
from frappy.core import HasIO, Drivable, Property, Parameter, StringType
|
||||
|
||||
class TemperatureLoop(HasIO, Drivable):
|
||||
"""a temperature sensor with loop"""
|
||||
# internal property to configure the channel
|
||||
channel = Property('the Lakeshore channel', datatype=StringType())
|
||||
# modifying a property of inherited parameters (unit is propagated to the FloatRange datatype)
|
||||
value = Parameter(unit='K')
|
||||
target = Parameter(unit='K')
|
||||
|
||||
def read_value(self):
|
||||
# using the inherited HasIO.communicate method to send a command and get the reply
|
||||
reply = self.communicate(f'KRDG?{self.channel}')
|
||||
return float(reply)
|
||||
|
||||
def read_status(self):
|
||||
... determine the status from the hardware and return it ...
|
||||
return status_code, status_text
|
||||
|
||||
def read_target(self):
|
||||
... read back the target value ...
|
||||
return target
|
||||
|
||||
def write_target(self, target):
|
||||
... write here the target to the hardware ...
|
||||
# important: make sure that the status is changed to BUSY within this method:
|
||||
self.status = BUSY, 'target changed'
|
||||
return self.read_target() # return the read back value
|
||||
|
||||
|
||||
Parameter Initialisation
|
||||
------------------------
|
||||
|
||||
Initial values of parameters might be given by several different sources:
|
||||
|
||||
1) value argument of a Parameter declaration
|
||||
2) read from HW
|
||||
3) read from persistent data file
|
||||
4) value given in config file
|
||||
|
||||
For (2) the programmer might decide for any parameter to poll it regularely from the
|
||||
hardware. In this case changes from an other input, for example a keyboard or other
|
||||
interface of the connected devices would be updated continuously in Frappy.
|
||||
If there is no such other input, or if the programmer decides that such other
|
||||
data sources are not to be considered, the hardware parameter might be read in just
|
||||
once on startup, :func:`frappy.modules.Module.initialReads` may be overriden.
|
||||
This method is called once on startup, before the regular polls start.
|
||||
|
||||
|
||||
.. TODO: io, state machine, persistent parameters, rwhandler, datatypes, features, commands, proxies
|
||||
@@ -1,13 +1,18 @@
|
||||
Reference
|
||||
---------
|
||||
|
||||
Core
|
||||
....
|
||||
|
||||
For convenience everything documented on this page may also be
|
||||
imported from the frappy.core module.
|
||||
|
||||
|
||||
Module Base Classes
|
||||
...................
|
||||
|
||||
.. autodata:: frappy.modules.Done
|
||||
|
||||
.. autoclass:: frappy.modules.Module
|
||||
:members: earlyInit, initModule, startModule
|
||||
:members: earlyInit, initModule, startModule, initialReads
|
||||
|
||||
.. autoclass:: frappy.modules.Readable
|
||||
:members: Status
|
||||
@@ -27,21 +32,61 @@ Parameters, Commands and Properties
|
||||
.. autoclass:: frappy.modules.Attached
|
||||
:show-inheritance:
|
||||
|
||||
Access method decorators
|
||||
........................
|
||||
|
||||
.. autofunction:: frappy.rwhandler.nopoll
|
||||
|
||||
|
||||
.. _datatypes:
|
||||
|
||||
Datatypes
|
||||
.........
|
||||
|
||||
.. autoclass:: frappy.datatypes.FloatRange
|
||||
.. autoclass:: frappy.datatypes.IntRange
|
||||
.. autoclass:: frappy.datatypes.BoolType
|
||||
.. autoclass:: frappy.datatypes.ScaledInteger
|
||||
.. autoclass:: frappy.datatypes.EnumType
|
||||
.. autoclass:: frappy.datatypes.StringType
|
||||
.. autoclass:: frappy.datatypes.TupleOf
|
||||
.. autoclass:: frappy.datatypes.ArrayOf
|
||||
.. autoclass:: frappy.datatypes.StructOf
|
||||
.. autoclass:: frappy.datatypes.BLOBType
|
||||
:members: __call__
|
||||
|
||||
.. autoclass:: frappy.datatypes.IntRange
|
||||
:members: __call__
|
||||
|
||||
.. autoclass:: frappy.datatypes.BoolType
|
||||
:members: __call__
|
||||
|
||||
.. autoclass:: frappy.datatypes.ScaledInteger
|
||||
:members: __call__
|
||||
|
||||
.. autoclass:: frappy.datatypes.EnumType
|
||||
:members: __call__
|
||||
|
||||
.. autoclass:: frappy.datatypes.StringType
|
||||
:members: __call__
|
||||
|
||||
.. autoclass:: frappy.datatypes.TupleOf
|
||||
:members: __call__
|
||||
|
||||
.. autoclass:: frappy.datatypes.ArrayOf
|
||||
:members: __call__
|
||||
|
||||
.. autoclass:: frappy.datatypes.StructOf
|
||||
:members: __call__
|
||||
|
||||
.. autoclass:: frappy.datatypes.BLOBType
|
||||
:members: __call__
|
||||
|
||||
.. autoclass:: frappy.datatypes.DataTypeType
|
||||
:members: __call__
|
||||
|
||||
.. autoclass:: frappy.datatypes.ValueType
|
||||
:members: __call__
|
||||
|
||||
.. autoclass:: frappy.datatypes.NoneOr
|
||||
:members: __call__
|
||||
|
||||
.. autoclass:: frappy.datatypes.OrType
|
||||
:members: __call__
|
||||
|
||||
.. autoclass:: frappy.datatypes.LimitsType
|
||||
:members: __call__
|
||||
|
||||
|
||||
Communication
|
||||
@@ -51,6 +96,10 @@ Communication
|
||||
:show-inheritance:
|
||||
:members: communicate
|
||||
|
||||
.. autoclass:: frappy.io.IOBase
|
||||
:show-inheritance:
|
||||
:members: default_settings
|
||||
|
||||
.. autoclass:: frappy.io.StringIO
|
||||
:show-inheritance:
|
||||
:members: communicate, multicomm
|
||||
@@ -62,6 +111,12 @@ Communication
|
||||
.. autoclass:: frappy.io.HasIO
|
||||
:show-inheritance:
|
||||
|
||||
.. autoclass:: frappy.lib.asynconn.AsynTcp
|
||||
:show-inheritance:
|
||||
|
||||
.. autoclass:: frappy.lib.asynconn.AsynSerial
|
||||
:show-inheritance:
|
||||
|
||||
.. autoclass:: frappy.rwhandler.ReadHandler
|
||||
:show-inheritance:
|
||||
:members:
|
||||
@@ -85,5 +140,4 @@ Exception classes
|
||||
.. automodule:: frappy.errors
|
||||
:members:
|
||||
|
||||
.. include:: server.rst
|
||||
|
||||
.. include:: configuration.rst
|
||||
@@ -1,47 +1,42 @@
|
||||
Server
|
||||
------
|
||||
|
||||
Configuration
|
||||
.............
|
||||
|
||||
The configuration consists of a **NODE** section, an **INTERFACE** section and one
|
||||
section per SECoP module.
|
||||
The configuration code consists of a :ref:`Node() <node configuration>` section, and one
|
||||
:ref:`Mod() <mod configuration>` section per SECoP module.
|
||||
|
||||
The **NODE** section contains a description of the SEC node and a globally unique ID of
|
||||
the SEC node. Example:
|
||||
The **Node** section contains a globally unique ID of the SEC node,
|
||||
a description of the SEC node and the server interface uri. Example:
|
||||
|
||||
.. code::
|
||||
.. code:: python
|
||||
|
||||
[NODE]
|
||||
description = a description of the SEC node
|
||||
id = globally.valid.identifier
|
||||
Node('globally.valid.identifier',
|
||||
'a description of the SEC node',
|
||||
interface = 'tcp://5000')
|
||||
|
||||
The **INTERFACE** section defines the server interface. Currently only tcp is supported.
|
||||
When the TCP port is given as an argument of the server start script, this section is not
|
||||
needed or ignored. The main information is the port number, in this example 5000:
|
||||
For the interface scheme currently only tcp is supported.
|
||||
When the TCP port is given as an argument of the server start script, **interface** is not
|
||||
needed or ignored. The main information is the port number, in this example 5000.
|
||||
|
||||
.. code::
|
||||
|
||||
[INTERFACE]
|
||||
uri = tcp://5000
|
||||
|
||||
|
||||
All other sections define the SECoP modules. The section name itself is the module name,
|
||||
mandatory fields are **class** and **description**. **class** is a path to the Python class
|
||||
from there the module is instantiated, separated with dots. In the following example the class
|
||||
All other :ref:`Mod() <mod configuration>` sections define the SECoP modules.
|
||||
Mandatory fields are **name**, **cls** and **description**. **cls** is a path to the Python class
|
||||
from where the module is instantiated, separated with dots. In the following example the class
|
||||
**HeLevel** used by the **helevel** module can be found in the PSI facility subdirectory
|
||||
frappy_psi in the python module file ccu4.py:
|
||||
|
||||
.. code::
|
||||
.. code:: python
|
||||
|
||||
[helevel]
|
||||
class = frappy_psi.ccu4.HeLevel
|
||||
description = this is the He level sensor of the main reservoir
|
||||
empty = 380
|
||||
empty.export = False
|
||||
full = 0
|
||||
full.export = False
|
||||
Mod('helevel',
|
||||
'frappy_psi.ccu4.HeLevel',
|
||||
'this is the He level sensor of the main reservoir',
|
||||
empty_length = Param(380, export=False),
|
||||
full = Param(0, export=False))
|
||||
|
||||
It is highly recommended to use all lower case for the module name, as SECoP names have to be
|
||||
unique despite of casing. In addition, parameters, properties and parameter properties might
|
||||
be initialized in this section. In the above example **empty** and **full** are parameters,
|
||||
be initialized in this section. In the above example **empty_length** and **full_length** are parameters,
|
||||
the resistivity of the He Level sensor at the end of the ranges. In addition, we alter the
|
||||
default property **export** of theses parameters, as we do not want to expose these parameters to
|
||||
the SECoP interface.
|
||||
@@ -54,12 +49,12 @@ The Frappy server can be started via the **bin/frappy-server** script.
|
||||
|
||||
.. parsed-literal::
|
||||
|
||||
usage: frappy-server [-h] [-v | -q] [-d] name
|
||||
usage: bin/frappy-server [-h] [-v | -q] [-d] [-t] [-p port] [-c cfgfiles] name
|
||||
|
||||
Manage a Frappy server
|
||||
|
||||
positional arguments:
|
||||
name name of the instance. Uses etc/name.cfg for configuration
|
||||
name name of the instance. Uses <config path>/name_cfg.py for configuration
|
||||
|
||||
optional arguments:
|
||||
-c, --cfgfiles config files to be used. Comma separated list.
|
||||
|
||||
83
doc/source/structure.rst
Normal file
83
doc/source/structure.rst
Normal file
@@ -0,0 +1,83 @@
|
||||
Structure
|
||||
---------
|
||||
|
||||
Node Structure
|
||||
..............
|
||||
|
||||
Before starting to write the code for drivers, you have to think about
|
||||
the node structure. What are the modules I want to create? What is to
|
||||
be represented as a SECoP module, what as a parameter? At this point
|
||||
you should not look what the hardware offers (e.g. channels A and B of
|
||||
a temperature controller), but on what you need for doing an
|
||||
experiment. Typically, each quantity you measure or control, has to
|
||||
be represented by a module. You need module parameters for influencing
|
||||
how you achieve or control the quantity. And you will need configurable
|
||||
internal properties to configure the access to the hardware.
|
||||
|
||||
|
||||
Examples:
|
||||
|
||||
- A temperature sensor, without an attached control loop, should inherit
|
||||
from :class:`Readable <frappy.modules.Readable>`
|
||||
|
||||
- A temperature sensor with a control loop should inherit from
|
||||
:class:`Drivable <frappy.modules.Drivable>`. You will need to implement a criterion for
|
||||
deciding when the temperature is reached (e.g. tolerance and time window)
|
||||
|
||||
- If the heater power is a quantity of interest, it should be its own
|
||||
module inheriting from :class:`Writable <frappy.modules.Writable>`.
|
||||
|
||||
- If it is a helium cryostat, you may want to implement a helium level
|
||||
reading module inheriting from :class:`Readable <frappy.modules.Readable>`
|
||||
|
||||
|
||||
.. _module structure parameters:
|
||||
|
||||
Module Structure: Parameters
|
||||
............................
|
||||
|
||||
The next step is to determine which parameters we need in addition to
|
||||
the standard ones given by the inherited class. As a temperature sensor
|
||||
inherits from :class:`Readable <frappy.modules.Readable>`, it has already a ``value``
|
||||
parameter representing the measured temperature. It has also a
|
||||
``status`` parameter, indicating whether the measured temperature is
|
||||
valid (``IDLE``), invalid (``ERROR``) or there might be a less
|
||||
critical issue (``WARN``). In addition you might want additional
|
||||
parameters, like an alarm threshold.
|
||||
|
||||
For the controlled temperature, in addition to above, inherited from
|
||||
:class:`Drivable <frappy.modules.Drivable>` it has a writable ``target`` parameter.
|
||||
In addition we might need control parameters or a changeable target limits.
|
||||
|
||||
For the heater you might want to have a changeable power limit or power range.
|
||||
|
||||
|
||||
.. _module structure properties:
|
||||
|
||||
Module Structure: Properties
|
||||
............................
|
||||
|
||||
For the access to the hardware, we will need internal properties for
|
||||
configuring the hardware access. This might the IP address of a
|
||||
LAN connection or the path of an internal serial device.
|
||||
In Frappy, when inheriting from the mixin :class:`HasIO <frappy.io.HasIO>`,
|
||||
either the property ``io`` referring to an explicitly configured
|
||||
communicator or the ``uri`` property, generating a communicator with
|
||||
the given uri can be used for this.
|
||||
|
||||
In addition, depending on the hardware probably you need a property to
|
||||
configure the channel number or name assigned to the module.
|
||||
|
||||
For the heater output, you might need to configure the heater resistance.
|
||||
|
||||
|
||||
Parameter Structure
|
||||
...................
|
||||
|
||||
A parameter also has properties, which have to be set when declaring
|
||||
the parameter. Even for the inherited parameters, often the properties
|
||||
have to be overriden. For example, the ``unit`` property of the ``value``
|
||||
parameter on the temperature sensor will be set to 'K', and the ``max``
|
||||
property of the ``target`` parameter should be set to the maximum possible
|
||||
value for the hardware. This value may then probably get more restricted
|
||||
by an entry in the configuration file.
|
||||
@@ -5,3 +5,4 @@ Tutorial
|
||||
:maxdepth: 2
|
||||
|
||||
tutorial_helevel
|
||||
tutorial_t_control
|
||||
|
||||
@@ -205,46 +205,50 @@ Before we continue coding, we may try out what we have coded and create a config
|
||||
The directory tree of the Frappy framework contains the code for all drivers, but the
|
||||
configuration file determines, which code will be loaded when a server is started.
|
||||
We choose the name *example_cryo* and create therefore a configuration file
|
||||
*example_cryo.cfg* in the *cfg* subdirectory:
|
||||
*example_cryo_cfg.py* in the *cfg* subdirectory:
|
||||
|
||||
``cfg/example_cryo.cfg``:
|
||||
``cfg/example_cryo_cfg.py``:
|
||||
|
||||
.. code:: ini
|
||||
.. code:: python
|
||||
|
||||
[NODE]
|
||||
description = this is an example cryostat for the Frappy tutorial
|
||||
id = example_cryo.psi.ch
|
||||
Node('example_cryo.psi.ch',
|
||||
'this is an example cryostat for the Frappy tutorial',
|
||||
interface='tcp://5000')
|
||||
Mod('helev',
|
||||
'frappy_psi.ccu4.HeLevel',
|
||||
'He level of the cryostat He reservoir',
|
||||
uri='linse-moxa-4.psi.ch:3001',
|
||||
empty_length=380,
|
||||
full_length=0)
|
||||
|
||||
[INTERFACE]
|
||||
uri = tcp://5000
|
||||
A configuration file contains a node configuration and one or several module configurations.
|
||||
|
||||
[helev]
|
||||
description = He level of the cryostat He reservoir
|
||||
class = frappy_psi.ccu4.HeLevel
|
||||
uri = linse-moxa-4.psi.ch:3001
|
||||
empty_length = 380
|
||||
full_length = 0
|
||||
*Node* describes the main properties of the SEC Node: an id and a description of the node
|
||||
which should be globally unique, and an interface defining the address of the server,
|
||||
usually the only important value here is the TCP port under which the server will be accessible.
|
||||
Currently only tcp is supported.
|
||||
|
||||
A configuration file contains several sections with a header enclosed by rectangular brackets.
|
||||
All the other sections define the SECoP modules to be used. This first arguments of *Mod(* are:
|
||||
|
||||
The *NODE* section describes the main properties of the SEC Node: a description of the node
|
||||
and an id, which should be globally unique.
|
||||
* the module name
|
||||
* the python class to be used for the creation of the module
|
||||
* a human readable description is its
|
||||
|
||||
The *INTERFACE* section defines the address of the server, usually the only important value
|
||||
here is the TCP port under which the server will be accessible. Currently only tcp is
|
||||
supported.
|
||||
|
||||
All the other sections define the SECoP modules to be used. A module section at least contains a
|
||||
human readable *description*, and the Python *class* used. Other properties or parameter values may
|
||||
follow, in this case the *uri* for the communication with the He level monitor and the values for
|
||||
configuring the He Level sensor. We might also alter parameter properties, for example we may hide
|
||||
Other properties or parameter values may follow, in this case the *uri* for the communication
|
||||
with the He level monitor and the values for configuring the He Level sensor.
|
||||
We might also alter parameter properties, for example we may hide
|
||||
the parameters *empty_length* and *full_length* from the client by defining:
|
||||
|
||||
.. code:: ini
|
||||
.. code:: python
|
||||
|
||||
empty_length.export = False
|
||||
full_length.export = False
|
||||
Mod('helev',
|
||||
'frappy_psi.ccu4.HeLevel',
|
||||
'He level of the cryostat He reservoir',
|
||||
uri='linse-moxa-4.psi.ch:3001',
|
||||
empty_length=Param(380, export=False),
|
||||
full_length=Param(0, export=False))
|
||||
|
||||
However, we do not put this here, as it is nice to try out changing parameters for a test!
|
||||
As we configure more than just an initial value, we have to call *Param* and give the
|
||||
value as the first argument, and additional properties as keyworded arguments.
|
||||
|
||||
*to be continued*
|
||||
|
||||
412
doc/source/tutorial_t_control.rst
Normal file
412
doc/source/tutorial_t_control.rst
Normal file
@@ -0,0 +1,412 @@
|
||||
A Simple Temperature Controller
|
||||
===============================
|
||||
|
||||
The Use Case
|
||||
------------
|
||||
|
||||
Let us assume we have simple cryostat or furnace with one temperature sensor
|
||||
and a heater. We want first to implement reading the temperature and then
|
||||
add the control loop. Assume also we have a LakeShore temperature controller
|
||||
to access the hardware.
|
||||
|
||||
|
||||
Coding the Sensor Module
|
||||
------------------------
|
||||
|
||||
A temperature sensor without control loop is to be implemented as a subclass
|
||||
of :class:`Readable <frappy.modules.Readable>`. You create this example to be used in your
|
||||
facility, so you add it to the subdirectory of your facility. You might need
|
||||
to create it, if it is not already there. In this example, you may
|
||||
replace *frappy_psi* by *frappy_<your facility>*. The name the python file
|
||||
is chosen from the type of temperature controller *lakeshore.py*.
|
||||
|
||||
We assume that the temperature controller is already configured with input ``A``
|
||||
being used, and the proper calibration curve assigned. In productive code
|
||||
this configuration may also be done by Frappy, but this would extend the scope
|
||||
of this tutorial too much.
|
||||
|
||||
So we define a class and define the parameter properties for the value:
|
||||
|
||||
``frappy_psi/lakeshore.py``:
|
||||
|
||||
.. code:: python
|
||||
|
||||
# the most common Frappy classes can be imported from frappy.core
|
||||
from frappy.core import Readable, Parameter, FloatRange
|
||||
|
||||
class TemperatureSensor(Readable):
|
||||
"""a temperature sensor (generic for different models)"""
|
||||
# 1500 is the maximum T allowed for most of the lakeshore models
|
||||
# this should be further restricted in the configuration (see below)
|
||||
value = Parameter(datatype=FloatRange(0, 1500, unit='K'))
|
||||
|
||||
|
||||
For the next step, we have to code how to retrieve the temperature
|
||||
from the controller. For this we add the method ``read_value``.
|
||||
In addition, we have to define a communicator class, and make
|
||||
``TemperatureSensor`` inherit from :class:`HasIO <frappy.io.HasIO>`
|
||||
in order to add the :meth:`communicate` method to the class.
|
||||
|
||||
See :ref:`lsc_manual_extract` for details of the needed commands.
|
||||
|
||||
|
||||
.. code:: python
|
||||
|
||||
from frappy.core import Readable, Parameter, FloatRange, HasIO, StringIO, Property, StringType
|
||||
|
||||
class LakeshoreIO(StringIO):
|
||||
wait_before = 0.05 # Lakeshore requires a wait time of 50 ms between commands
|
||||
# '*IDN?' is sent on connect, and the reply is checked to match the regexp 'LSCI,.*'
|
||||
identification = [('*IDN?', 'LSCI,.*')]
|
||||
|
||||
class TemperatureSensor(HasIO, Readable):
|
||||
"""a temperature sensor (generic for different models)"""
|
||||
# internal property to configure the channel
|
||||
# see below for the difference of 'Property' and 'Parameter'
|
||||
channel = Property('the Lakeshore channel', datatype=StringType())
|
||||
# 0, 1500 is the allowed range by the LakeShore controller
|
||||
# this should be further restricted in the configuration (see below)
|
||||
value = Parameter(datatype=FloatRange(0, 1500, unit='K'))
|
||||
|
||||
def read_value(self):
|
||||
# the communicate method sends a command and returns the reply
|
||||
reply = self.communicate(f'KRDG?{self.channel}')
|
||||
# convert to float
|
||||
return float(reply)
|
||||
|
||||
|
||||
This is the code to run a minimalistic SEC Node, which does just read a temperature
|
||||
and nothing else.
|
||||
|
||||
.. Note::
|
||||
|
||||
A :class:`Property <frappy.properties.Property>` is used instead of a
|
||||
:class:`Parameter <frappy.param.Parameter>`, for a configurable item not changing
|
||||
on run time. A ``Property`` is typically only internal needed and by default not
|
||||
visible by SECoP.
|
||||
|
||||
|
||||
Before we start the frappy server for the first time, we have to create a configuration file.
|
||||
The directory tree of the Frappy framework contains the code for all drivers but the
|
||||
configuration file determines, which code will be loaded when a server is started.
|
||||
We choose the name *example_cryo* and create therefore a configuration file
|
||||
*example_cryo_cfg.py* in the *cfg* subdirectory:
|
||||
|
||||
``cfg/example_cryo_cfg.py``:
|
||||
|
||||
.. code:: python
|
||||
|
||||
Node('example_cryo.psi.ch', # a globally unique identification
|
||||
'this is an example cryostat for the Frappy tutorial', # describes the node
|
||||
interface='tcp://10767') # you might choose any port number > 1024
|
||||
Mod('io', # the name of the module
|
||||
'frappy_psi.lakeshore.LakeshoreIO', # the class used for communication
|
||||
'communication to main controller', # a description
|
||||
# the serial connection, including serial settings (see frappy.io.IOBase):
|
||||
uri='serial://COM6:?baudrate=57600+parity=odd+bytesize=7',
|
||||
)
|
||||
Mod('T',
|
||||
'frappy_psi.lakeshore.TemperatureSensor',
|
||||
'Sample Temperature',
|
||||
io='io', # refers to above defined module 'io'
|
||||
channel='A', # the channel on the LakeShore for this module
|
||||
value=Param(max=470), # alter the maximum expected T
|
||||
)
|
||||
|
||||
The first section in the configuration file configures the common settings for the server.
|
||||
:ref:`Node <node configuration>` describes the main properties of the SEC Node: an identifier,
|
||||
which should be globally unique, a description of the node, and an interface defining the server address.
|
||||
Usually the only important value in the server address is the TCP port under which the
|
||||
server will be accessible. Currently only the tcp scheme is supported.
|
||||
|
||||
Then for each module a :ref:`Mod <mod configuration>` section follows.
|
||||
We have to create the ``io`` module for communication first, with
|
||||
the ``uri`` as its most important argument.
|
||||
In case of a serial connection the prefix is ``serial://``. On a Windows machine, the full
|
||||
uri is something like ``serial://COM6:?baudrate=9600`` on a linux system it might be
|
||||
``serial:///dev/ttyUSB0?baudrate=9600``. In case of a LAN connection, the uri should
|
||||
be something like ``tcp://129.129.138.78:7777`` or ``tcp://mydevice.psi.ch:7777``, where
|
||||
7777 is the tcp port the LakeShore is listening to.
|
||||
|
||||
Now, we are ready to start our first server. In the main frappy directory, we
|
||||
start it with:
|
||||
|
||||
.. code::
|
||||
|
||||
python bin/frappy-server example_cryo
|
||||
|
||||
If error messages appear, you have first to try to fix the errors.
|
||||
Else you might open an other console or terminal, in order to start
|
||||
a frappy client, for example the GUI client. The argument is
|
||||
compose by the machine running the server and the server port chosen
|
||||
in the configuration file:
|
||||
|
||||
.. code::
|
||||
|
||||
python bin/frappy-gui localhost:10767
|
||||
|
||||
|
||||
A ``Readable`` SECoP module also has a status parameter. Until now, we completely
|
||||
ignored it. As you may see, the value of status parameter is always ``(IDLE, '')``.
|
||||
However, we should implement the status parameter to give information about the
|
||||
validity of the sensor reading. The controller has a query command ``RDGST?<channel>``
|
||||
returning a code describing error states. We implement this by adding a the
|
||||
``read_status`` method to the class:
|
||||
|
||||
.. code:: python
|
||||
|
||||
from frappy.core import Readable, Parameter, FloatRange, HasIO, StringIO, Property, StringType,\
|
||||
IDLE, ERROR
|
||||
|
||||
...
|
||||
|
||||
class TemperatureSensor(HasIO, Readable):
|
||||
|
||||
...
|
||||
|
||||
def read_status(self):
|
||||
code = int(self.communicate(f'RDGST?{self.channel}'))
|
||||
if code >= 128:
|
||||
text = 'units overrange'
|
||||
elif code >= 64:
|
||||
text = 'units zero'
|
||||
elif code >= 32:
|
||||
text = 'temperature overrange'
|
||||
elif code >= 16:
|
||||
text = 'temperature underrange'
|
||||
elif code % 2:
|
||||
# ignore 'old reading', as this may happen in normal operation
|
||||
text = 'invalid reading'
|
||||
else:
|
||||
return IDLE, ''
|
||||
return ERROR, text
|
||||
|
||||
After a restart of the server and the client, the status should change to
|
||||
``ERROR, '<some error message>'`` when the sensor is unplugged.
|
||||
|
||||
|
||||
Extend the Class to a Temperature Loop
|
||||
--------------------------------------
|
||||
|
||||
As we want to implement also temperature control, we have extend the class more.
|
||||
Instead of adding just more methods to the ``TemperatureSensor`` class, we
|
||||
create a new class ``TemperatureLoop`` inheriting from Temperature sensor.
|
||||
This way, we would for example be able to create a node with a controlled
|
||||
temperature on one channel, and a sensor module without control on an other channel.
|
||||
|
||||
Temperature control is represented by a subclass of :class:`Drivable <frappy.modules.Drivable>`.
|
||||
So our new class will be based on ``TemperatureSensor`` where we have already
|
||||
implemented the readable stuff. We need to define some properties of the ``target``
|
||||
parameter and add a property ``loop`` indicating, which control loop and
|
||||
heater output we use.
|
||||
|
||||
In addition, we have to implement the method ``write_target``. Remark: we do not
|
||||
implement ``read_target`` here, because the lakeshore does not offer to read back the
|
||||
real target. The SETP command is returning the working setpoint, which may be distinct
|
||||
from target during a ramp.
|
||||
|
||||
.. code:: python
|
||||
|
||||
from frappy.core import Readable, Parameter, FloatRange, HasIO, StringIO, Property, StringType,\
|
||||
IDLE, BUSY, WARN, ERROR, Drivable, IntRange
|
||||
|
||||
...
|
||||
|
||||
class TemperatureLoop(TemperatureSensor, Drivable):
|
||||
# lakeshore loop number to be used for this module
|
||||
loop = Property('lakeshore loop', IntRange(1, 2), default=1)
|
||||
target = Parameter(datatype=FloatRange(unit='K', min=0, max=1500))
|
||||
|
||||
def write_target(self, target):
|
||||
# we always use a request / reply scheme
|
||||
self.communicate(f'SETP {self.loop},{target};*OPC?')
|
||||
return target
|
||||
|
||||
|
||||
In order to test this, we will need to change the entry module ``T`` in the
|
||||
configuration file:
|
||||
|
||||
.. code:: python
|
||||
|
||||
Mod('T',
|
||||
'frappy_psi.lakeshore.TemperatureLoop',
|
||||
'Sample Temperature',
|
||||
io='io',
|
||||
channel='A', # the channel on the LakeShore for this module
|
||||
loop=1, # the loop to be used
|
||||
value=Param(max=470), # set the maximum expected T
|
||||
target=Param(max=420), # set the maximum allowed target T
|
||||
)
|
||||
|
||||
To test that this step worked, just restart the server and the client.
|
||||
If the temperature controller is not yet configured for controlling the
|
||||
temperature on channel A with loop 1, this has to be done first.
|
||||
Especially the heater has to be switched on, setting the maximum heater
|
||||
range.
|
||||
|
||||
There are two things still missing:
|
||||
|
||||
- We want to switch on the heater automatically, when the target is changed.
|
||||
A property ``heater_range`` is added for this.
|
||||
- We want to handle the status code correctly: set to ``BUSY`` when the
|
||||
target is changed, and back to ``IDLE`` when the target temperature is reached.
|
||||
The parameter ``tolerance`` is used for this. For the tutorial we use here
|
||||
a rather simple mechanism. In reality, often over- or undershoot happens.
|
||||
A better algorithm would not switch to IDLE before the temperature was within
|
||||
tolerance for some given time.
|
||||
|
||||
|
||||
.. code:: python
|
||||
|
||||
from frappy.core import Readable, Drivable, Parameter, FloatRange, \
|
||||
HasIO, StringIO, IDLE, BUSY, WARN, ERROR
|
||||
|
||||
...
|
||||
|
||||
class TemperatureLoop(TemperatureSensor, Drivable):
|
||||
...
|
||||
heater_range = Property('heater power range', IntRange(0, 5)) # max. 3 on LakeShore 336
|
||||
tolerance = Parameter('convergence criterion', FloatRange(0), default=0.1, readonly=False)
|
||||
_driving = False
|
||||
...
|
||||
|
||||
def write_target(self, target):
|
||||
# reactivate heater in case it was switched off
|
||||
self.communicate(f'RANGE {self.loop},{self.heater_range};RANGE?{self.loop}')
|
||||
self.communicate(f'SETP {self.loop},{target};*OPC?')
|
||||
self._driving = True
|
||||
# Setting the status attribute triggers an update message for the SECoP status
|
||||
# parameter. This has to be done before returning from this method!
|
||||
self.status = BUSY, 'target changed'
|
||||
return target
|
||||
...
|
||||
|
||||
def read_status(self):
|
||||
code = int(self.communicate(f'RDGST?{self.channel}'))
|
||||
if code >= 128:
|
||||
text = 'units overrange'
|
||||
elif code >= 64:
|
||||
text = 'units zero'
|
||||
elif code >= 32:
|
||||
text = 'temperature overrange'
|
||||
elif code >= 16:
|
||||
text = 'temperature underrange'
|
||||
elif code % 2:
|
||||
# ignore 'old reading', as this may happen in normal operation
|
||||
text = 'invalid reading'
|
||||
elif abs(self.target - self.value) > self.tolerance:
|
||||
if self._driving:
|
||||
return BUSY, 'approaching setpoint'
|
||||
return WARN, 'temperature out of tolerance'
|
||||
else: # within tolerance: simple convergence criterion
|
||||
self._driving = False
|
||||
return IDLE, ''
|
||||
return ERROR, text
|
||||
|
||||
|
||||
Finally, the config file would be:
|
||||
|
||||
``cfg/example_cryo_cfg.py``:
|
||||
|
||||
.. code:: python
|
||||
|
||||
Node('example_cryo.psi.ch', # a globally unique identification
|
||||
'this is an example cryostat for the Frappy tutorial', # describes the node
|
||||
interface='tcp://10767') # you might choose any port number > 1024
|
||||
Mod('io', # the name of the module
|
||||
'frappy_psi.lakeshore.LakeshoreIO', # the class used for communication
|
||||
'communication to main controller', # a description
|
||||
uri='serial://COM6:?baudrate=57600+parity=odd+bytesize=7', # the serial connection
|
||||
)
|
||||
Mod('T',
|
||||
'frappy_psi.lakeshore.TemperatureLoop',
|
||||
'Sample Temperature',
|
||||
io='io',
|
||||
channel='A', # the channel on the LakeShore for this module
|
||||
loop=1, # the loop to be used
|
||||
value=Param(max=470), # set the maximum expected T
|
||||
target=Param(max=420), # set the maximum allowed target T
|
||||
heater_range=3, # 5 for model 350
|
||||
)
|
||||
|
||||
|
||||
Now, you should try again restarting the server and the client, if it works, you have done a good job!
|
||||
If not, you might need to fix the code first ...
|
||||
|
||||
|
||||
More Complex Configurations
|
||||
...........................
|
||||
|
||||
Without coding any more class, much more complex situations might be realized just by
|
||||
extending the configuration. Using a single LakeShore controller, you might add more
|
||||
temperature sensors or (in the case of Model 336 or 350) even a second temperature loop,
|
||||
just by adding more ``Mod(`` sections to the configuration file. In case more than 4 channels
|
||||
are needed, an other module ``io2`` has to be added for the second controller and so on.
|
||||
|
||||
|
||||
Appendix 1: The Solution
|
||||
------------------------
|
||||
|
||||
You will find the full solution code via the ``[source]`` link in the automatic
|
||||
created documentation of the class :class:`frappy_demo.lakeshore.TemperatureLoop`.
|
||||
|
||||
|
||||
|
||||
.. _lsc_manual_extract:
|
||||
|
||||
Appendix 2: Extract from the LakeShore Manual
|
||||
---------------------------------------------
|
||||
|
||||
.. table:: commands used in this tutorial
|
||||
|
||||
====================== =======================
|
||||
**Query Identification**
|
||||
----------------------------------------------
|
||||
Command \*IDN? *term*
|
||||
Reply <manufacturer>,<model>,<instrument serial>/<option serial>, <firmware version> *term*
|
||||
Example LSCI,MODEL336,1234567/1234567,1.0
|
||||
**Query Kelvin Reading for an Input**
|
||||
----------------------------------------------
|
||||
Command KRDG?<input> *term*
|
||||
Example KRDG?A
|
||||
Reply <kelvin value> *term*
|
||||
Example +273.15
|
||||
**Query Input Status**
|
||||
----------------------------------------------
|
||||
Command RDGST?<input> *term*
|
||||
Reply <status bit weighting> *term*
|
||||
Description The integer returned represents the sum of the bit weighting \
|
||||
of the input status flag bits. A “000” response indicates a valid reading is present.
|
||||
Bit / Value Status
|
||||
0 / 1 invalid reading
|
||||
1 / 2 old reading (Model 340 only)
|
||||
4 / 16 temperature underrange
|
||||
5 / 32 temperature overrange
|
||||
6 / 64 sensor units zero
|
||||
7 / 128 sensor units overrange
|
||||
**Set Control Loop Setpoint**
|
||||
----------------------------------------------
|
||||
Command SETP <loop>,<value> *term*
|
||||
Example SETP 1,273.15
|
||||
**Query Control Loop Setpoint**
|
||||
----------------------------------------------
|
||||
Command SETP?<loop> *term*
|
||||
Reply <value> *term*
|
||||
Example +273.15
|
||||
**Set Heater Range**
|
||||
----------------------------------------------
|
||||
Command (340) RANGE <range number> *term*
|
||||
Command (336/350) RANGE <loop>,<range number> *term*
|
||||
Description 0: heater off, 1-5: heater range (Model 336: 1-3)
|
||||
**Query Heater Range**
|
||||
----------------------------------------------
|
||||
Command (340) RANGE? *term*
|
||||
Command (336/350) RANGE?<loop> *term*
|
||||
Reply <range> *term*
|
||||
**Operation Complete Query**
|
||||
----------------------------------------------
|
||||
Command \*OPC?
|
||||
Reply 1
|
||||
Description in Frappy, we append this command to request in order
|
||||
to generate a reply
|
||||
====================== =======================
|
||||
@@ -1,7 +1,6 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
# *****************************************************************************
|
||||
# Copyright (c) 2015-2019 by the authors, see LICENSE
|
||||
# Copyright (c) 2015-2024 by the authors, see LICENSE
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify it under
|
||||
# the terms of the GNU General Public License as published by the Free Software
|
||||
@@ -39,8 +38,8 @@ def main():
|
||||
frappy_unit = '/lib/systemd/system/frappy@.service'
|
||||
wants_dir = normal_dir + '/frappy.target.wants'
|
||||
|
||||
all_servers = [base for (base, ext) in
|
||||
map(path.splitext, os.listdir(config_dir)) if ext == '.cfg']
|
||||
all_servers = [base[:-4] if base.endswith('_cfg') else base for (base, ext) in
|
||||
map(path.splitext, os.listdir(config_dir)) if ext == '.py']
|
||||
all_servers.sort()
|
||||
|
||||
for srv in all_servers:
|
||||
|
||||
4
etc/generalConfig.cfg
Normal file
4
etc/generalConfig.cfg
Normal file
@@ -0,0 +1,4 @@
|
||||
[FRAPPY]
|
||||
logdir = /var/log
|
||||
piddir = /var/run/frappy
|
||||
confdir = /etc/frappy
|
||||
@@ -1,6 +1,5 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# *****************************************************************************
|
||||
# Copyright (c) 2015-2016 by the authors, see LICENSE
|
||||
# Copyright (c) 2015-2024 by the authors, see LICENSE
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify it under
|
||||
# the terms of the GNU General Public License as published by the Free Software
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# *****************************************************************************
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify it under
|
||||
@@ -23,33 +22,34 @@
|
||||
# *****************************************************************************
|
||||
"""general SECoP client"""
|
||||
|
||||
import re
|
||||
import json
|
||||
import queue
|
||||
import re
|
||||
import time
|
||||
from collections import defaultdict
|
||||
from threading import Event, RLock, current_thread
|
||||
|
||||
import frappy.errors
|
||||
import frappy.params
|
||||
from frappy.datatypes import get_datatype
|
||||
from frappy.errors import HardwareError, SECoPError, WrongTypeError, \
|
||||
make_secop_error
|
||||
from frappy.lib import mkthread
|
||||
from frappy.lib.asynconn import AsynConn, ConnectionClosed
|
||||
from frappy.protocol.interface import decode_msg, encode_msg_frame
|
||||
from frappy.protocol.messages import COMMANDREQUEST, \
|
||||
DESCRIPTIONREQUEST, ENABLEEVENTSREQUEST, ERRORPREFIX, \
|
||||
EVENTREPLY, HEARTBEATREQUEST, IDENTPREFIX, IDENTREQUEST, \
|
||||
READREPLY, READREQUEST, REQUEST2REPLY, WRITEREPLY, WRITEREQUEST
|
||||
from frappy.protocol.messages import COMMANDREQUEST, DESCRIPTIONREQUEST, \
|
||||
ENABLEEVENTSREQUEST, ERRORPREFIX, EVENTREPLY, HEARTBEATREQUEST, \
|
||||
IDENTPREFIX, IDENTREQUEST, READREPLY, READREQUEST, REQUEST2REPLY, \
|
||||
WRITEREPLY, WRITEREQUEST
|
||||
|
||||
# replies to be handled for cache
|
||||
UPDATE_MESSAGES = {EVENTREPLY, READREPLY, WRITEREPLY, ERRORPREFIX + READREQUEST, ERRORPREFIX + EVENTREPLY}
|
||||
|
||||
VERSIONFMT= re.compile(r'^[^,]*?ISSE[^,]*,SECoP,')
|
||||
VERSIONFMT = re.compile(r'^[^,]*?ISSE[^,]*,SECoP,')
|
||||
|
||||
class UNREGISTER:
|
||||
"""a magic value, used a returned value in a callback
|
||||
|
||||
to indicate it has to be unregistered
|
||||
class UnregisterCallback(Exception):
|
||||
"""raise in a callback to indicate it has to be unregistered
|
||||
|
||||
used to implement one shot callbacks
|
||||
"""
|
||||
|
||||
@@ -66,7 +66,11 @@ class Logger:
|
||||
pass
|
||||
|
||||
debug = noop
|
||||
error = warning = critical = info
|
||||
error = exception = warning = critical = info
|
||||
|
||||
|
||||
class NullLogger(Logger):
|
||||
error = exception = warning = critical = info = Logger.noop
|
||||
|
||||
|
||||
class CallbackObject:
|
||||
@@ -75,15 +79,23 @@ class CallbackObject:
|
||||
this is mainly for documentation, but it might be extended
|
||||
and used as a mixin for objects registered as a callback
|
||||
"""
|
||||
def updateEvent(self, module, parameter, value, timestamp, readerror):
|
||||
def updateItem(self, module, parameter, item):
|
||||
"""called whenever a value is changed
|
||||
|
||||
or when new callbacks are registered
|
||||
:param module: the module name
|
||||
:param parameter: the parameter name
|
||||
:param item: a CacheItem object
|
||||
"""
|
||||
|
||||
def unhandledMessage(self, action, ident, data):
|
||||
"""called on an unhandled message"""
|
||||
|
||||
def handleError(self, exc):
|
||||
"""called on errors handling messages
|
||||
|
||||
:param exc: the exception raised (= sys.exception())
|
||||
"""
|
||||
|
||||
def nodeStateChange(self, online, state):
|
||||
"""called when the state of the connection changes
|
||||
|
||||
@@ -98,57 +110,157 @@ class CallbackObject:
|
||||
and on every changed module with module==<module name>
|
||||
"""
|
||||
|
||||
def updateEvent(self, module, parameter, value, timestamp, readerror):
|
||||
"""legacy method: called whenever a value is changed
|
||||
|
||||
or when new callbacks are registered
|
||||
"""
|
||||
|
||||
|
||||
class CacheItem(tuple):
|
||||
"""cache entry
|
||||
|
||||
includes formatting information
|
||||
inheriting from tuple: compatible with old previous version of cache
|
||||
"""
|
||||
|
||||
def __new__(cls, value, timestamp=None, readerror=None, datatype=None):
|
||||
obj = tuple.__new__(cls, (value, timestamp, readerror))
|
||||
if datatype:
|
||||
try:
|
||||
# override default methods
|
||||
obj.format_value = datatype.format_value
|
||||
obj.to_string = datatype.to_string
|
||||
except AttributeError:
|
||||
pass
|
||||
return obj
|
||||
|
||||
@property
|
||||
def value(self):
|
||||
return self[0]
|
||||
|
||||
@property
|
||||
def timestamp(self):
|
||||
return self[1]
|
||||
|
||||
@property
|
||||
def readerror(self):
|
||||
return self[2]
|
||||
|
||||
def __str__(self):
|
||||
"""format value without unit
|
||||
|
||||
may be used in this form for SecopClient.setParameterFromString
|
||||
"""
|
||||
if self[2]: # readerror
|
||||
return repr(self[2])
|
||||
return self.to_string(self[0])
|
||||
|
||||
def formatted(self):
|
||||
"""format value with using unit
|
||||
|
||||
nicer format for humans, hard to parse
|
||||
"""
|
||||
if self[2]: # readerror
|
||||
return repr(self[2])
|
||||
return self.format_value(self[0])
|
||||
|
||||
@staticmethod
|
||||
def format_value(value):
|
||||
"""typically overridden with datatype.format_value"""
|
||||
return str(value)
|
||||
|
||||
@staticmethod
|
||||
def to_string(value):
|
||||
"""typically overridden with datatype.to_string"""
|
||||
return str(value)
|
||||
|
||||
def __repr__(self):
|
||||
args = (self.value,)
|
||||
if self.timestamp:
|
||||
args += (self.timestamp,)
|
||||
if self.readerror:
|
||||
args += (self.readerror,)
|
||||
return f'CacheItem{args!r}'
|
||||
|
||||
|
||||
class Cache(dict):
|
||||
class Undefined(Exception):
|
||||
def __repr__(self):
|
||||
return '<undefined>'
|
||||
|
||||
undefined = CacheItem(None, None, Undefined())
|
||||
|
||||
def __missing__(self, key):
|
||||
return self.undefined
|
||||
|
||||
|
||||
class ProxyClient:
|
||||
"""common functionality for proxy clients"""
|
||||
|
||||
CALLBACK_NAMES = ('updateEvent', 'descriptiveDataChange', 'nodeStateChange', 'unhandledMessage')
|
||||
CALLBACK_NAMES = {'updateEvent', 'updateItem', 'descriptiveDataChange',
|
||||
'nodeStateChange', 'unhandledMessage', 'handleError'}
|
||||
online = False # connected or reconnecting since a short time
|
||||
validate_data = False
|
||||
state = 'disconnected' # further possible values: 'connecting', 'reconnecting', 'connected'
|
||||
log = None
|
||||
|
||||
def __init__(self):
|
||||
self.callbacks = {cbname: defaultdict(list) for cbname in self.CALLBACK_NAMES}
|
||||
# caches (module, parameter) = value, timestamp, readerror (internal names!)
|
||||
self.cache = {}
|
||||
self.cache = Cache() # dict returning Cache.undefined for missing keys
|
||||
|
||||
def register_callback(self, key, *args, **kwds):
|
||||
def register_callback(self, key, *args, callimmediately=True, **kwds):
|
||||
"""register callback functions
|
||||
|
||||
- key might be either:
|
||||
several callbacks might be registered within one call.
|
||||
ProxyClient.CALLBACK_NAMES contains all names of valid callbacks
|
||||
|
||||
:param key: might be either:
|
||||
1) None: general callback (all callbacks)
|
||||
2) <module name>: callbacks related to a module (not called for 'unhandledMessage')
|
||||
3) (<module name>, <parameter name>): callback for specified parameter (only called for 'updateEvent')
|
||||
- all the following arguments are callback functions. The callback name may be
|
||||
given by the keyword, or, for non-keyworded arguments it is taken from the
|
||||
__name__ attribute of the function
|
||||
3) (<module name>, <parameter name>): callback for specified parameter
|
||||
(only called for 'updateEvent' and 'updateItem')
|
||||
:param args: callback functions. the callback name is taken from the the __name__ attribute of the function
|
||||
:param callimmediately: True (default): call immediately for updateItem and updateEvent callbacks
|
||||
:param kwds: callback functions. the callback name is taken from the key
|
||||
"""
|
||||
for cbfunc in args:
|
||||
kwds[cbfunc.__name__] = cbfunc
|
||||
for cbname in self.CALLBACK_NAMES:
|
||||
cbfunc = kwds.pop(cbname, None)
|
||||
if not cbfunc:
|
||||
continue
|
||||
cbdict = self.callbacks[cbname]
|
||||
cbdict[key].append(cbfunc)
|
||||
for cbname, cbfunc in kwds.items():
|
||||
if cbname not in self.CALLBACK_NAMES:
|
||||
raise TypeError(f"unknown callback: {', '.join(kwds)}")
|
||||
|
||||
# immediately call for some callback types
|
||||
if cbname == 'updateEvent':
|
||||
if key is None:
|
||||
for (mname, pname), data in self.cache.items():
|
||||
cbfunc(mname, pname, *data)
|
||||
# call immediately for some callback types
|
||||
if cbname in ('updateItem', 'updateEvent') and callimmediately:
|
||||
if key is None: # case generic callback
|
||||
cbargs = [(m, p, d) for (m, p), d in self.cache.items()]
|
||||
else:
|
||||
data = self.cache.get(key, None)
|
||||
if data:
|
||||
cbfunc(*key, *data) # case single parameter
|
||||
if data: # case single parameter
|
||||
cbargs = [key + (data,)]
|
||||
else: # case key = module
|
||||
for (mname, pname), data in self.cache.items():
|
||||
if mname == key:
|
||||
cbfunc(mname, pname, *data)
|
||||
cbargs = [(m, p, d) for (m, p), d in self.cache.items() if m == key]
|
||||
|
||||
if cbname == 'updateEvent':
|
||||
# expand entry argument to (value, timestamp, readerror)
|
||||
cbargs = [a[0:2] + a[2] for a in cbargs]
|
||||
|
||||
elif cbname == 'nodeStateChange':
|
||||
cbfunc(self.online, self.state)
|
||||
if kwds:
|
||||
raise TypeError('unknown callback: %s' % (', '.join(kwds)))
|
||||
cbargs = [(self.online, self.state)]
|
||||
else:
|
||||
cbargs = []
|
||||
|
||||
do_append = True
|
||||
for args in cbargs:
|
||||
try:
|
||||
cbfunc(*args)
|
||||
except UnregisterCallback:
|
||||
do_append = False
|
||||
except Exception as e:
|
||||
if self.log:
|
||||
self.log.error('error %r calling %s%r', e, cbfunc.__name__, args)
|
||||
if do_append:
|
||||
self.callbacks[cbname][key].append(cbfunc)
|
||||
|
||||
def unregister_callback(self, key, *args, **kwds):
|
||||
"""unregister a callback
|
||||
@@ -172,20 +284,21 @@ class ProxyClient:
|
||||
key=(<module name>, <parameter name): callbacks for specified parameter
|
||||
"""
|
||||
cblist = self.callbacks[cbname].get(key, [])
|
||||
self.callbacks[cbname][key] = [cb for cb in cblist if cb(*args) is not UNREGISTER]
|
||||
for cbfunc in list(cblist):
|
||||
try:
|
||||
cbfunc(*args)
|
||||
except UnregisterCallback:
|
||||
cblist.remove(cbfunc)
|
||||
except Exception as e:
|
||||
if cbname != 'handleError':
|
||||
try:
|
||||
e.args = [f'error in callback {cbname}{args}: {e}']
|
||||
self.callback(None, 'handleError', e)
|
||||
except Exception:
|
||||
pass
|
||||
return bool(cblist)
|
||||
|
||||
def updateValue(self, module, param, value, timestamp, readerror):
|
||||
if readerror:
|
||||
assert isinstance(readerror, Exception)
|
||||
if self.validate_data:
|
||||
try:
|
||||
# try to validate, reason: make enum_members from integers
|
||||
datatype = self.modules[module]['parameters'][param]['datatype']
|
||||
value = datatype(value)
|
||||
except (KeyError, ValueError):
|
||||
pass
|
||||
self.cache[(module, param)] = (value, timestamp, readerror)
|
||||
self.callback(None, 'updateEvent', module, param, value, timestamp, readerror)
|
||||
self.callback(module, 'updateEvent', module, param, value, timestamp, readerror)
|
||||
self.callback((module, param), 'updateEvent', module, param, value, timestamp, readerror)
|
||||
@@ -195,7 +308,6 @@ class SecopClient(ProxyClient):
|
||||
"""a general SECoP client"""
|
||||
reconnect_timeout = 10
|
||||
_running = False
|
||||
_shutdown = False
|
||||
_rxthread = None
|
||||
_txthread = None
|
||||
_connthread = None
|
||||
@@ -204,8 +316,17 @@ class SecopClient(ProxyClient):
|
||||
descriptive_data = {}
|
||||
modules = {}
|
||||
_last_error = None
|
||||
_update_error_count = 0
|
||||
_max_error_count = 10
|
||||
|
||||
def __init__(self, uri, log=Logger):
|
||||
"""initialize SecopClient
|
||||
|
||||
:param uri: the uri to connect to
|
||||
:param log: a logger.
|
||||
when not given, the print command is used for messages with at least info level.
|
||||
when None, nothing is logged at all
|
||||
"""
|
||||
super().__init__()
|
||||
# maps expected replies to [request, Event, is_error, result] until a response came
|
||||
# there can only be one entry per thread calling 'request'
|
||||
@@ -213,14 +334,21 @@ class SecopClient(ProxyClient):
|
||||
self.io = None
|
||||
self.txq = queue.Queue(30) # queue for tx requests
|
||||
self.pending = queue.Queue(30) # requests with colliding action + ident
|
||||
self.log = log
|
||||
self.log = log or NullLogger
|
||||
self.uri = uri
|
||||
self.nodename = uri
|
||||
self._lock = RLock()
|
||||
self._shutdown = Event()
|
||||
self.cleanup = []
|
||||
self.register_callback(None, self.handleError)
|
||||
|
||||
def __del__(self):
|
||||
# make sure threads are stopping. this is needed in case
|
||||
# a frappy client object is lost without calling .disconnect()
|
||||
try:
|
||||
self.disconnect()
|
||||
# avoid callbacks when deleting. may cause deadlocks in NICOS
|
||||
self.callbacks.clear()
|
||||
self.disconnect(True)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
@@ -232,12 +360,17 @@ class SecopClient(ProxyClient):
|
||||
with self._lock:
|
||||
if self.io:
|
||||
return
|
||||
self._shutdown.clear()
|
||||
self.txq = queue.Queue(30)
|
||||
self.pending = queue.Queue(30)
|
||||
self.active_requests.clear()
|
||||
self.cleanup.clear()
|
||||
if self.online:
|
||||
self._set_state(True, 'reconnecting')
|
||||
else:
|
||||
self._set_state(False, 'connecting')
|
||||
deadline = time.time() + try_period
|
||||
while not self._shutdown:
|
||||
while not self._shutdown.is_set():
|
||||
try:
|
||||
self.io = AsynConn(self.uri) # timeout 1 sec
|
||||
self.io.writeline(IDENTREQUEST.encode('utf-8'))
|
||||
@@ -245,16 +378,15 @@ class SecopClient(ProxyClient):
|
||||
if reply:
|
||||
self.secop_version = reply.decode('utf-8')
|
||||
else:
|
||||
raise self.error_map('HardwareError')('no answer to %s' % IDENTREQUEST)
|
||||
raise HardwareError(f'no answer to {IDENTREQUEST}')
|
||||
|
||||
if not VERSIONFMT.match(self.secop_version):
|
||||
raise self.error_map('HardwareError')('bad answer to %s: %r' %
|
||||
(IDENTREQUEST, self.secop_version))
|
||||
raise HardwareError(f'bad answer to {IDENTREQUEST}: {self.secop_version!r}')
|
||||
# inform that the other party still uses a legacy identifier
|
||||
# see e.g. Frappy Bug #4659 (https://forge.frm2.tum.de/redmine/issues/4659)
|
||||
if not self.secop_version.startswith(IDENTPREFIX):
|
||||
self.log.warning('SEC-Node replied with legacy identify reply: %s'
|
||||
% self.secop_version)
|
||||
self.log.warning('SEC-Node replied with legacy identify reply: %s',
|
||||
self.secop_version)
|
||||
|
||||
# now its safe to do secop stuff
|
||||
self._running = True
|
||||
@@ -265,6 +397,7 @@ class SecopClient(ProxyClient):
|
||||
self._init_descriptive_data(self.request(DESCRIPTIONREQUEST)[2])
|
||||
self.nodename = self.properties.get('equipment_id', self.uri)
|
||||
if self.activate:
|
||||
self._set_state(True, 'activating')
|
||||
self.request(ENABLEEVENTSREQUEST)
|
||||
self._set_state(True, 'connected')
|
||||
break
|
||||
@@ -274,8 +407,8 @@ class SecopClient(ProxyClient):
|
||||
# stay online for now, if activated
|
||||
self._set_state(self.online and self.activate)
|
||||
raise
|
||||
time.sleep(1)
|
||||
if not self._shutdown:
|
||||
self._shutdown.wait(1)
|
||||
if not self._shutdown.is_set():
|
||||
self.log.info('%s ready', self.nodename)
|
||||
|
||||
def __txthread(self):
|
||||
@@ -302,8 +435,15 @@ class SecopClient(ProxyClient):
|
||||
|
||||
def __rxthread(self):
|
||||
noactivity = 0
|
||||
shutdown = False
|
||||
try:
|
||||
while self._running:
|
||||
while self.cleanup:
|
||||
entry = self.cleanup.pop()
|
||||
for key, prev in self.active_requests.items():
|
||||
if prev is entry:
|
||||
self.active_requests.pop(key)
|
||||
break
|
||||
# may raise ConnectionClosed
|
||||
reply = self.io.readline()
|
||||
if reply is None:
|
||||
@@ -312,34 +452,42 @@ class SecopClient(ProxyClient):
|
||||
# send ping to check if the connection is still alive
|
||||
self.queue_request(HEARTBEATREQUEST, str(noactivity))
|
||||
continue
|
||||
self.log.debug('RX: %r', reply)
|
||||
noactivity = 0
|
||||
action, ident, data = decode_msg(reply)
|
||||
if ident == '.':
|
||||
ident = None
|
||||
if action in UPDATE_MESSAGES:
|
||||
module_param = self.internal.get(ident, None)
|
||||
if module_param is None and ':' not in ident:
|
||||
# allow missing ':value'/':target'
|
||||
if action == WRITEREPLY:
|
||||
module_param = self.internal.get(ident + ':target', None)
|
||||
else:
|
||||
module_param = self.internal.get(ident + ':value', None)
|
||||
if module_param is not None:
|
||||
if action.startswith(ERRORPREFIX):
|
||||
timestamp = data[2].get('t', None)
|
||||
readerror = frappy.errors.make_secop_error(*data[0:2])
|
||||
value = None
|
||||
else:
|
||||
timestamp = data[1].get('t', None)
|
||||
value = data[0]
|
||||
readerror = None
|
||||
module, param = module_param
|
||||
try:
|
||||
try:
|
||||
action, ident, data = decode_msg(reply)
|
||||
if ident == '.':
|
||||
ident = None
|
||||
if action in UPDATE_MESSAGES:
|
||||
module_param = self.internal.get(ident, None)
|
||||
if module_param is None and ':' not in (ident or ''):
|
||||
# allow missing ':value'/':target'
|
||||
if action == WRITEREPLY:
|
||||
module_param = self.internal.get(f'{ident}:target', None)
|
||||
else:
|
||||
module_param = self.internal.get(f'{ident}:value', None)
|
||||
if module_param is not None:
|
||||
now = time.time()
|
||||
if action.startswith(ERRORPREFIX):
|
||||
timestamp = data[2].get('t', now)
|
||||
readerror = make_secop_error(*data[0:2])
|
||||
value = None
|
||||
else:
|
||||
timestamp = data[1].get('t', now)
|
||||
value = data[0]
|
||||
readerror = None
|
||||
module, param = module_param
|
||||
timestamp = min(now, timestamp) # no timestamps in the future!
|
||||
self.updateValue(module, param, value, timestamp, readerror)
|
||||
except KeyError:
|
||||
pass # ignore updates of unknown parameters
|
||||
if action in (EVENTREPLY, ERRORPREFIX + EVENTREPLY):
|
||||
continue
|
||||
if action in (EVENTREPLY, ERRORPREFIX + EVENTREPLY):
|
||||
continue
|
||||
except Exception as e:
|
||||
e.args = (f'error handling SECoP message {reply!r}: {e}',)
|
||||
try:
|
||||
self.callback(None, 'handleError', e)
|
||||
except Exception:
|
||||
pass
|
||||
continue
|
||||
try:
|
||||
key = action, ident
|
||||
entry = self.active_requests.pop(key)
|
||||
@@ -366,17 +514,19 @@ class SecopClient(ProxyClient):
|
||||
except ConnectionClosed:
|
||||
pass
|
||||
except Exception as e:
|
||||
self.log.error('rxthread ended with %r', e)
|
||||
self._rxthread = None
|
||||
self.disconnect(False)
|
||||
if self._shutdown:
|
||||
return
|
||||
if self.activate:
|
||||
self.log.info('try to reconnect to %s', self.uri)
|
||||
self._connthread = mkthread(self._reconnect)
|
||||
else:
|
||||
self.log.warning('%s disconnected', self.uri)
|
||||
self._set_state(False, 'disconnected')
|
||||
shutdown = True
|
||||
self.callback(None, 'handleError', e)
|
||||
finally:
|
||||
self._rxthread = None
|
||||
self.disconnect(shutdown)
|
||||
if self._shutdown.is_set():
|
||||
pass
|
||||
elif self.activate:
|
||||
self.log.info('try to reconnect to %s', self.uri)
|
||||
self._connthread = mkthread(self._reconnect)
|
||||
else:
|
||||
self.log.warning('%s disconnected', self.uri)
|
||||
self._set_state(False, 'disconnected')
|
||||
|
||||
def spawn_connect(self, connected_callback=None):
|
||||
"""try to connect in background
|
||||
@@ -387,7 +537,7 @@ class SecopClient(ProxyClient):
|
||||
self._connthread = mkthread(self._reconnect, connected_callback)
|
||||
|
||||
def _reconnect(self, connected_callback=None):
|
||||
while not self._shutdown:
|
||||
while not self._shutdown.is_set():
|
||||
try:
|
||||
self.connect()
|
||||
if connected_callback:
|
||||
@@ -407,15 +557,15 @@ class SecopClient(ProxyClient):
|
||||
self.log.info('continue trying to reconnect')
|
||||
# self.log.warning(formatExtendedTraceback())
|
||||
self._set_state(False)
|
||||
time.sleep(self.reconnect_timeout)
|
||||
self._shutdown.wait(self.reconnect_timeout)
|
||||
else:
|
||||
time.sleep(1)
|
||||
self._shutdown.wait(1)
|
||||
self._connthread = None
|
||||
|
||||
def disconnect(self, shutdown=True):
|
||||
self._running = False
|
||||
if shutdown:
|
||||
self._shutdown = True
|
||||
self._shutdown.set()
|
||||
self._set_state(False, 'shutdown')
|
||||
if self._connthread:
|
||||
if self._connthread == current_thread():
|
||||
@@ -429,6 +579,8 @@ class SecopClient(ProxyClient):
|
||||
self.txq.get(False)
|
||||
except Exception:
|
||||
pass
|
||||
if self.io:
|
||||
self.io.shutdown()
|
||||
if self._txthread:
|
||||
self.txq.put(None) # shutdown marker
|
||||
self._txthread.join()
|
||||
@@ -478,7 +630,7 @@ class SecopClient(ProxyClient):
|
||||
iname = self.internalize_name(aname)
|
||||
datatype = get_datatype(aentry['datainfo'], iname)
|
||||
aentry = dict(aentry, datatype=datatype)
|
||||
ident = '%s:%s' % (modname, aname)
|
||||
ident = f'{modname}:{aname}'
|
||||
self.identifier[modname, iname] = ident
|
||||
self.internal[ident] = modname, iname
|
||||
if datatype.IS_COMMAND:
|
||||
@@ -486,8 +638,8 @@ class SecopClient(ProxyClient):
|
||||
else:
|
||||
parameters[iname] = aentry
|
||||
properties = {k: v for k, v in moddescr.items() if k != 'accessibles'}
|
||||
self.modules[modname] = dict(accessibles=accessibles, parameters=parameters,
|
||||
commands=commands, properties=properties)
|
||||
self.modules[modname] = {'accessibles': accessibles, 'parameters': parameters,
|
||||
'commands': commands, 'properties': properties}
|
||||
if changed_modules is not None:
|
||||
done = done_main = self.callback(None, 'descriptiveDataChange', None, self)
|
||||
for mname in changed_modules:
|
||||
@@ -502,6 +654,13 @@ class SecopClient(ProxyClient):
|
||||
if not self.callback(None, 'unhandledMessage', action, ident, data):
|
||||
self.log.warning('unhandled message: %s %s %r', action, ident, data)
|
||||
|
||||
def handleError(self, exc):
|
||||
if self._update_error_count < self._max_error_count:
|
||||
self.log.exception('%s', exc)
|
||||
self._update_error_count += 1
|
||||
if self._update_error_count == self._max_error_count:
|
||||
self.log.error('disabled reporting of further update errors')
|
||||
|
||||
def _set_state(self, online, state=None):
|
||||
# remark: reconnecting is treated as online
|
||||
self.online = online
|
||||
@@ -522,13 +681,16 @@ class SecopClient(ProxyClient):
|
||||
def get_reply(self, entry):
|
||||
"""wait for reply and return it"""
|
||||
if not entry[1].wait(10): # event
|
||||
self.cleanup.append(entry)
|
||||
raise TimeoutError('no response within 10s')
|
||||
if not entry[2]: # reply
|
||||
if self._shutdown.is_set():
|
||||
raise ConnectionError('connection shut down')
|
||||
# no cleanup needed as self.active_requests will be cleared on connect
|
||||
raise ConnectionError('connection closed before reply')
|
||||
action, _, data = entry[2] # pylint: disable=unpacking-non-sequence
|
||||
if action.startswith(ERRORPREFIX):
|
||||
errcls = self.error_map(data[0])
|
||||
raise errcls(data[1])
|
||||
raise make_secop_error(*data[0:2])
|
||||
return entry[2] # reply
|
||||
|
||||
def request(self, action, ident=None, data=None):
|
||||
@@ -543,9 +705,14 @@ class SecopClient(ProxyClient):
|
||||
"""forced read over connection"""
|
||||
try:
|
||||
self.request(READREQUEST, self.identifier[module, parameter])
|
||||
except frappy.errors.SECoPError:
|
||||
# error reply message is already stored as readerror in cache
|
||||
pass
|
||||
except SECoPError as e:
|
||||
result = self.cache[module, parameter]
|
||||
if e == result.readerror:
|
||||
# the update was already done in the rx thread
|
||||
return result
|
||||
# e was not originating from a secop error message e.g. a connection problem
|
||||
# -> we have to do the error update
|
||||
self.updateValue(module, parameter, None, time.time(), e)
|
||||
return self.cache.get((module, parameter), None)
|
||||
|
||||
def getParameter(self, module, parameter, trycache=False):
|
||||
@@ -564,6 +731,17 @@ class SecopClient(ProxyClient):
|
||||
self.request(WRITEREQUEST, self.identifier[module, parameter], value)
|
||||
return self.cache[module, parameter]
|
||||
|
||||
def setParameterFromString(self, module, parameter, formatted):
|
||||
"""set parameter from string
|
||||
|
||||
formatted is a string in the form obtained by str(<cache item>)
|
||||
"""
|
||||
self.connect() # make sure we are connected
|
||||
datatype = self.modules[module]['parameters'][parameter]['datatype']
|
||||
value = datatype.export_value(datatype.from_string(formatted))
|
||||
self.request(WRITEREQUEST, self.identifier[module, parameter], value)
|
||||
return self.cache[module, parameter]
|
||||
|
||||
def execCommand(self, module, command, argument=None):
|
||||
self.connect() # make sure we are connected
|
||||
datatype = self.modules[module]['commands'][command]['datatype'].argument
|
||||
@@ -571,7 +749,7 @@ class SecopClient(ProxyClient):
|
||||
argument = datatype.export_value(argument)
|
||||
else:
|
||||
if argument is not None:
|
||||
raise frappy.errors.BadValueError('command has no argument')
|
||||
raise WrongTypeError('command has no argument')
|
||||
# pylint: disable=unsubscriptable-object
|
||||
data, qualifiers = self.request(COMMANDREQUEST, self.identifier[module, command], argument)[2]
|
||||
datatype = self.modules[module]['commands'][command]['datatype'].result
|
||||
@@ -579,17 +757,47 @@ class SecopClient(ProxyClient):
|
||||
data = datatype.import_value(data)
|
||||
return data, qualifiers
|
||||
|
||||
def execCommandFromString(self, module, command, formatted_argument=''):
|
||||
"""call command from string argument
|
||||
|
||||
return data as CacheItem which allows to get
|
||||
- result.value # the python value
|
||||
- result.formatted() # a string (incl. units)
|
||||
- result.timestamp
|
||||
"""
|
||||
self.connect()
|
||||
datatype = self.modules[module]['commands'][command]['datatype'].argument
|
||||
if datatype:
|
||||
argument = datatype.from_string(formatted_argument)
|
||||
else:
|
||||
if formatted_argument:
|
||||
raise WrongTypeError('command has no argument')
|
||||
argument = None
|
||||
# pylint: disable=unsubscriptable-object
|
||||
data, qualifiers = self.request(COMMANDREQUEST, self.identifier[module, command], argument)[2]
|
||||
datatype = self.modules[module]['commands'][command]['datatype'].result
|
||||
value = datatype.import_value(data) if datatype else None
|
||||
return CacheItem(value, qualifiers.get('t'), None, datatype)
|
||||
|
||||
def updateValue(self, module, param, value, timestamp, readerror):
|
||||
datatype = self.modules[module]['parameters'][param]['datatype']
|
||||
if readerror:
|
||||
assert isinstance(readerror, Exception)
|
||||
else:
|
||||
value = datatype.import_value(value)
|
||||
entry = CacheItem(value, timestamp, readerror, datatype)
|
||||
self.cache[(module, param)] = entry
|
||||
self.callback(None, 'updateItem', module, param, entry)
|
||||
self.callback(module, 'updateItem', module, param, entry)
|
||||
self.callback((module, param), 'updateItem', module, param, entry)
|
||||
# TODO: change clients to use updateItem instead of updateEvent
|
||||
super().updateValue(module, param, value, timestamp, readerror)
|
||||
|
||||
# the following attributes may be/are intended to be overwritten by a subclass
|
||||
|
||||
ERROR_MAP = frappy.errors.EXCEPTIONS
|
||||
DEFAULT_EXCEPTION = frappy.errors.SECoPError
|
||||
PREDEFINED_NAMES = set(frappy.params.PREDEFINED_ACCESSIBLES)
|
||||
activate = True
|
||||
|
||||
def error_map(self, exc):
|
||||
"""how to convert SECoP and unknown exceptions"""
|
||||
return self.ERROR_MAP.get(exc, self.DEFAULT_EXCEPTION)
|
||||
|
||||
def internalize_name(self, name):
|
||||
"""how to create internal names"""
|
||||
if name.startswith('_') and name[1:] not in self.PREDEFINED_NAMES:
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# *****************************************************************************
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify it under
|
||||
@@ -24,54 +23,77 @@
|
||||
import sys
|
||||
import time
|
||||
import re
|
||||
from queue import Queue
|
||||
from frappy.client import SecopClient
|
||||
import code
|
||||
import signal
|
||||
import os
|
||||
import traceback
|
||||
import threading
|
||||
import logging
|
||||
from os.path import expanduser
|
||||
from frappy.lib import delayed_import
|
||||
from frappy.client import SecopClient, UnregisterCallback
|
||||
from frappy.errors import SECoPError
|
||||
from frappy.datatypes import get_datatype
|
||||
from frappy.datatypes import get_datatype, StatusType
|
||||
|
||||
readline = delayed_import('readline')
|
||||
|
||||
|
||||
USAGE = """
|
||||
Usage:
|
||||
{client_assign}
|
||||
# for all SECoP modules objects are created in the main namespace
|
||||
|
||||
from frappy.client.interactive import Client
|
||||
<module> # list all parameters
|
||||
<module>.<param> = <value> # change parameter
|
||||
<module>(<target>) # set target and wait until not busy
|
||||
# 'status' and 'value' changes are shown every 1 sec
|
||||
{client_name}.mininterval = 0.2 # change minimal update interval to 0.2 s (default is 1 s)
|
||||
|
||||
client = Client('localhost:5000') # start client.
|
||||
# this connects and creates objects for all SECoP modules in the main namespace
|
||||
|
||||
<module> # list all parameters
|
||||
<module>.<param> = <value> # change parameter
|
||||
<module>(<target>) # set target and wait until not busy
|
||||
# 'status' and 'value' changes are shown every 1 sec
|
||||
client.mininterval = 0.2 # change minimal update interval to 0.2 sec (default is 1 second)
|
||||
|
||||
<module>.watch(1) # watch changes of all parameters of a module
|
||||
<module>.watch(0) # remove all watching
|
||||
<module>.watch(status=1, value=1) # add 'status' and 'value' to watched parameters
|
||||
<module>.watch(value=0) # remove 'value' from watched parameters
|
||||
"""
|
||||
|
||||
main = sys.modules['__main__']
|
||||
watch(T) # watch changes of T.status and T.value (stop with ctrl-C)
|
||||
watch(T='status target') # watch status and target parameters
|
||||
watch(io, T=True) # watch io and all parameters of T
|
||||
{tail}"""
|
||||
|
||||
|
||||
class Logger:
|
||||
def __init__(self, loglevel='info'):
|
||||
func = self.noop
|
||||
for lev in 'debug', 'info', 'warning', 'error':
|
||||
if lev == loglevel:
|
||||
func = self.emit
|
||||
setattr(self, lev, func)
|
||||
self._minute = 0
|
||||
LOG_LEVELS = {
|
||||
'debug': logging.DEBUG,
|
||||
'comlog': logging.DEBUG+1,
|
||||
'info': logging.INFO,
|
||||
'warning': logging.WARN,
|
||||
'error': logging.ERROR,
|
||||
'off': logging.ERROR+1}
|
||||
CLR = '\r\x1b[K' # code to move to the left and clear current line
|
||||
|
||||
def emit(self, fmt, *args, **kwds):
|
||||
now = time.time()
|
||||
minute = now // 60
|
||||
if minute != self._minute:
|
||||
self._minute = minute
|
||||
print(time.strftime('--- %H:%M:%S ---', time.localtime(now)))
|
||||
print('%6.3f' % (now % 60.0), str(fmt) % args)
|
||||
|
||||
@staticmethod
|
||||
def noop(fmt, *args, **kwds):
|
||||
pass
|
||||
class Handler(logging.StreamHandler):
|
||||
def emit(self, record):
|
||||
super().emit(record)
|
||||
if clientenv.sigwinch:
|
||||
# SIGWINCH: 'window size has changed' -> triggers a refresh of the input line
|
||||
os.kill(os.getpid(), signal.SIGWINCH)
|
||||
|
||||
|
||||
class Logger(logging.Logger):
|
||||
show_time = False
|
||||
_minute = None
|
||||
|
||||
def __init__(self, name, loglevel='info'):
|
||||
super().__init__(name, LOG_LEVELS.get(loglevel, logging.INFO))
|
||||
handler = Handler()
|
||||
handler.formatter = logging.Formatter('%(asctime)s%(message)s')
|
||||
handler.formatter.formatTime = self.format_time
|
||||
self.addHandler(handler)
|
||||
|
||||
def format_time(self, record, datefmt=None):
|
||||
if self.show_time:
|
||||
now = record.created
|
||||
tm = time.localtime(now)
|
||||
sec = f'{now % 60.0:6.3f}'.replace(' ', '0')
|
||||
if tm.tm_min == self._minute:
|
||||
return f'{CLR}{sec} '
|
||||
self._minute = tm.tm_min
|
||||
return f"{CLR}{time.strftime('--- %H:%M:%S ---', tm)}\n{sec} "
|
||||
return ''
|
||||
|
||||
|
||||
class PrettyFloat(float):
|
||||
@@ -82,7 +104,7 @@ class PrettyFloat(float):
|
||||
- always display a decimal point
|
||||
"""
|
||||
def __repr__(self):
|
||||
result = '%.12g' % self
|
||||
result = f'{self:.12g}'
|
||||
if '.' in result or 'e' in result:
|
||||
return result
|
||||
return result + '.'
|
||||
@@ -96,36 +118,35 @@ class Module:
|
||||
self._secnode = secnode
|
||||
self._parameters = list(secnode.modules[name]['parameters'])
|
||||
self._commands = list(secnode.modules[name]['commands'])
|
||||
self._running = None
|
||||
if 'communicate' in self._commands:
|
||||
self._watched_params = {}
|
||||
self._log_level = 'comlog'
|
||||
else:
|
||||
self._watched_params = {'value', 'status'}
|
||||
self._log_level = 'info'
|
||||
self._is_driving = False
|
||||
self._driving_event = threading.Event()
|
||||
self._status = None
|
||||
props = secnode.modules[name]['properties']
|
||||
self._title = '# %s (%s)' % (props.get('implementation', ''), props.get('interface_classes', [''])[0])
|
||||
self._title = f"# {props.get('implementation', '')} ({(props.get('interface_classes') or ['Module'])[0]})"
|
||||
|
||||
def _one_line(self, pname, minwid=0):
|
||||
"""return <module>.<param> = <value> truncated to one line"""
|
||||
param = getattr(type(self), pname)
|
||||
try:
|
||||
value = getattr(self, pname)
|
||||
r = param.format(value)
|
||||
except Exception as e:
|
||||
r = repr(e)
|
||||
result = param.formatted(self)
|
||||
pname = pname.ljust(minwid)
|
||||
vallen = 113 - len(self._name) - len(pname)
|
||||
if len(r) > vallen:
|
||||
r = r[:vallen - 4] + ' ...'
|
||||
return '%s.%s = %s' % (self._name, pname, r)
|
||||
if len(result) > vallen:
|
||||
result = result[:vallen - 4] + ' ...'
|
||||
return f'{self._name}.{pname} = {result}'
|
||||
|
||||
def _isBusy(self):
|
||||
return 300 <= self.status[0] < 400
|
||||
return self.status[0] // 100 == StatusType.BUSY // 100
|
||||
|
||||
def _status_value_update(self, m, p, status, t, e):
|
||||
if self._running:
|
||||
try:
|
||||
self._running.put(True)
|
||||
if self._running and not self._isBusy():
|
||||
self._running.put(False)
|
||||
except TypeError: # may happen when _running is removed during above lines
|
||||
pass
|
||||
def _status_update(self, m, p, status, t, e):
|
||||
if self._is_driving and not self._isBusy():
|
||||
self._is_driving = False
|
||||
self._driving_event.set()
|
||||
|
||||
def _watch_parameter(self, m, pname, *args, forced=False, mininterval=0):
|
||||
"""show parameter update"""
|
||||
@@ -139,59 +160,111 @@ class Module:
|
||||
pobj.prev = value
|
||||
pobj.prev_time = now
|
||||
|
||||
def watch(self, *args, **kwds):
|
||||
enabled = {}
|
||||
for arg in args:
|
||||
if arg == 1: # or True
|
||||
enabled.update({k: True for k in self._parameters})
|
||||
elif arg == 0: # or False
|
||||
enabled.update({k: False for k in self._parameters})
|
||||
def _set_watching(self, watch_list=None):
|
||||
"""set parameters for watching and log levels
|
||||
|
||||
:param watch_list: items to be watched
|
||||
True or 1: watch all parameters
|
||||
a string from LOG_LEVELS: change the log level
|
||||
any other string: convert space separated string to a list of strings
|
||||
a list of string: parameters to be watched or log_level to be set
|
||||
"""
|
||||
if isinstance(watch_list, str):
|
||||
if watch_list in LOG_LEVELS:
|
||||
self._log_level = watch_list
|
||||
watch_list = None
|
||||
else:
|
||||
enabled.update(arg)
|
||||
enabled.update(kwds)
|
||||
for pname, enable in enabled.items():
|
||||
# accept also space separated list instead of list of strings
|
||||
watch_list = watch_list.split()
|
||||
elif isinstance(watch_list, int): # includes also True
|
||||
watch_list = self._parameters if watch_list else ()
|
||||
if watch_list is not None:
|
||||
params = []
|
||||
for item in watch_list:
|
||||
if item in self._parameters:
|
||||
params.append(item)
|
||||
elif item in LOG_LEVELS:
|
||||
self._log_level = item
|
||||
else:
|
||||
self._secnode.log.error('can not set %r on module %s', item, self._name)
|
||||
self._watched_params = params
|
||||
print(f"--- {self._name}:\nlog: {self._log_level}, watch: {' '.join(self._watched_params)}")
|
||||
|
||||
def _start_watching(self):
|
||||
for pname in self._watched_params:
|
||||
self._watch_parameter(self, pname, forced=True)
|
||||
self._secnode.register_callback((self._name, pname), updateEvent=self._watch_parameter)
|
||||
self._secnode.request('logging', self._name, self._log_level)
|
||||
self._secnode.register_callback(None, nodeStateChange=self._set_log_level)
|
||||
|
||||
def _stop_watching(self):
|
||||
for pname in self._watched_params:
|
||||
self._secnode.unregister_callback((self._name, pname), updateEvent=self._watch_parameter)
|
||||
if enable:
|
||||
self._secnode.register_callback((self._name, pname), updateEvent=self._watch_parameter)
|
||||
self._secnode.unregister_callback(None, nodeStateChange=self._set_log_level)
|
||||
self._secnode.request('logging', self._name, 'off')
|
||||
|
||||
def _set_log_level(self, online, state):
|
||||
if online and state == 'connected':
|
||||
self._secnode.request('logging', self._name, self._log_level)
|
||||
|
||||
def read(self, pname='value'):
|
||||
value, _, error = self._secnode.readParameter(self._name, pname)
|
||||
if error:
|
||||
raise error
|
||||
clientenv.raise_with_short_traceback(error)
|
||||
return value
|
||||
|
||||
def __call__(self, target=None):
|
||||
if target is None:
|
||||
return self.read()
|
||||
self.target = target # this sets self._running
|
||||
type(self).value.prev = None # show at least one value
|
||||
show_final_value = True
|
||||
watch_params = ['value', 'status']
|
||||
for pname in watch_params:
|
||||
self._secnode.register_callback((self._name, pname),
|
||||
updateEvent=self._watch_parameter,
|
||||
callimmediately=False)
|
||||
|
||||
self.target = target # this sets self._is_driving
|
||||
|
||||
def loop():
|
||||
while self._is_driving:
|
||||
self._driving_event.wait()
|
||||
self._driving_event.clear()
|
||||
try:
|
||||
while self._running.get():
|
||||
self._watch_parameter(self._name, 'value', mininterval=self._secnode.mininterval)
|
||||
self._watch_parameter(self._name, 'status')
|
||||
except KeyboardInterrupt:
|
||||
loop()
|
||||
except KeyboardInterrupt as e:
|
||||
self._secnode.log.info('-- interrupted --')
|
||||
self._running = None
|
||||
self._watch_parameter(self._name, 'status')
|
||||
self._secnode.readParameter(self._name, 'value')
|
||||
self._watch_parameter(self._name, 'value', forced=show_final_value)
|
||||
self.stop()
|
||||
try:
|
||||
loop() # wait for stopping to be finished
|
||||
except KeyboardInterrupt:
|
||||
# interrupted again while stopping -> definitely quit
|
||||
pass
|
||||
clientenv.raise_with_short_traceback(e)
|
||||
finally:
|
||||
self._secnode.readParameter(self._name, 'value')
|
||||
for pname in watch_params:
|
||||
self._secnode.unregister_callback((self._name, pname),
|
||||
updateEvent=self._watch_parameter)
|
||||
return self.value
|
||||
|
||||
def __repr__(self):
|
||||
wid = max(len(k) for k in self._parameters)
|
||||
return '%s\n%s\nCommands: %s' % (
|
||||
return f'<module {self._name}>'
|
||||
|
||||
def showAll(self):
|
||||
wid = max((len(k) for k in self._parameters), default=0)
|
||||
return '%s\n%s%s' % (
|
||||
self._title,
|
||||
'\n'.join(self._one_line(k, wid) for k in self._parameters),
|
||||
', '.join(k + '()' for k in self._commands))
|
||||
'\nCommands: %s' % ', '.join(k + '()' for k in self._commands) if self._commands else '')
|
||||
|
||||
def logging(self, level='comlog', pattern='.*'):
|
||||
def log_filter(self, pattern='.*'):
|
||||
self._log_pattern = re.compile(pattern)
|
||||
self._secnode.request('logging', self._name, level)
|
||||
|
||||
def handle_log_message_(self, data):
|
||||
def handle_log_message_(self, loglevel, data):
|
||||
if self._log_pattern.match(data):
|
||||
self._secnode.log.info('%s: %r', self._name, data)
|
||||
if loglevel == 'comlog':
|
||||
self._secnode.log.info('%s%s', self._name, data)
|
||||
else:
|
||||
self._secnode.log.info('%s %s: %s', self._name, loglevel, data)
|
||||
|
||||
|
||||
class Param:
|
||||
@@ -206,20 +279,22 @@ class Param:
|
||||
return self
|
||||
value, _, error = obj._secnode.cache[obj._name, self.name]
|
||||
if error:
|
||||
raise error
|
||||
clientenv.raise_with_short_traceback(error)
|
||||
return value
|
||||
|
||||
def formatted(self, obj):
|
||||
return obj._secnode.cache[obj._name, self.name].formatted()
|
||||
|
||||
def __set__(self, obj, value):
|
||||
if self.name == 'target':
|
||||
obj._running = Queue()
|
||||
try:
|
||||
obj._secnode.setParameter(obj._name, self.name, value)
|
||||
if self.name == 'target':
|
||||
obj._is_driving = obj._isBusy()
|
||||
return
|
||||
except SECoPError as e:
|
||||
clientenv.raise_with_short_traceback(e)
|
||||
obj._secnode.log.error(repr(e))
|
||||
|
||||
def format(self, value):
|
||||
return self.datatype.format_value(value)
|
||||
|
||||
|
||||
class Command:
|
||||
def __init__(self, name, modname, secnode):
|
||||
@@ -232,6 +307,8 @@ class Command:
|
||||
if args:
|
||||
raise TypeError('mixed arguments forbidden')
|
||||
result, _ = self.exec(self.modname, self.name, kwds)
|
||||
elif len(args) == 1:
|
||||
result, _ = self.exec(self.modname, self.name, *args)
|
||||
else:
|
||||
result, _ = self.exec(self.modname, self.name, args or None)
|
||||
return result
|
||||
@@ -242,54 +319,228 @@ class Command:
|
||||
return self.call
|
||||
|
||||
|
||||
def show_parameter(modname, pname, *args, forced=False, mininterval=0):
|
||||
"""show parameter update"""
|
||||
mobj = clientenv.namespace[modname]
|
||||
mobj._watch_parameter(modname, pname, *args)
|
||||
|
||||
|
||||
def watch(*args, **kwds):
|
||||
modules = []
|
||||
for mobj in args:
|
||||
if isinstance(mobj, Module):
|
||||
if mobj._name not in kwds:
|
||||
modules.append(mobj)
|
||||
mobj._set_watching()
|
||||
else:
|
||||
print(f'do not know {mobj!r}')
|
||||
for key, arg in kwds.items():
|
||||
mobj = clientenv.namespace.get(key)
|
||||
if mobj is None:
|
||||
print(f'do not know {key!r}')
|
||||
else:
|
||||
modules.append(mobj)
|
||||
mobj._set_watching(arg)
|
||||
print('---')
|
||||
try:
|
||||
nodes = set()
|
||||
for mobj in modules:
|
||||
nodes.add(mobj._secnode)
|
||||
mobj._start_watching()
|
||||
|
||||
close_event = threading.Event()
|
||||
|
||||
def close_node(online, state):
|
||||
if online and state != 'shutdown':
|
||||
return None
|
||||
close_event.set()
|
||||
return UnregisterCallback
|
||||
|
||||
def handle_error(*_):
|
||||
close_event.set()
|
||||
return UnregisterCallback
|
||||
|
||||
for node in nodes:
|
||||
node.register_callback(None, nodeStateChange=close_node, handleError=handle_error)
|
||||
|
||||
close_event.wait()
|
||||
|
||||
except KeyboardInterrupt as e:
|
||||
clientenv.raise_with_short_traceback(e)
|
||||
finally:
|
||||
for mobj in modules:
|
||||
mobj._stop_watching()
|
||||
print()
|
||||
|
||||
|
||||
class Client(SecopClient):
|
||||
activate = True
|
||||
secnodes = {}
|
||||
mininterval = 1
|
||||
|
||||
def __init__(self, uri, loglevel='info'):
|
||||
def __init__(self, uri, loglevel='info', name=''):
|
||||
if clientenv.namespace is None:
|
||||
# called from a simple python interpeter
|
||||
clientenv.init(sys.modules['__main__'].__dict__)
|
||||
# remove previous client:
|
||||
prev = self.secnodes.pop(uri, None)
|
||||
log = Logger(name, loglevel)
|
||||
removed_modules = []
|
||||
if prev:
|
||||
prev.log.info('remove previous client to %s', uri)
|
||||
log.info('remove previous client to %s', uri)
|
||||
for modname in prev.modules:
|
||||
prevnode = getattr(getattr(main, modname, None), '_secnode', None)
|
||||
prevnode = getattr(clientenv.namespace.get(modname), '_secnode', None)
|
||||
if prevnode == prev:
|
||||
prev.log.info('remove previous module %s', modname)
|
||||
delattr(main, modname)
|
||||
removed_modules.append(modname)
|
||||
clientenv.namespace.pop(modname)
|
||||
prev.disconnect()
|
||||
self.secnodes[uri] = self
|
||||
super().__init__(uri, Logger(loglevel))
|
||||
if name:
|
||||
log.info('\n>>> %s = Client(%r)', name, uri)
|
||||
super().__init__(uri, log)
|
||||
self.connect()
|
||||
created_modules = []
|
||||
skipped_modules = []
|
||||
for modname, moddesc in self.modules.items():
|
||||
prev = getattr(main, modname, None)
|
||||
prev = clientenv.namespace.get(modname)
|
||||
if prev is None:
|
||||
self.log.info('create module %s', modname)
|
||||
created_modules.append(modname)
|
||||
else:
|
||||
if getattr(prev, '_secnode', None) is None:
|
||||
self.log.error('skip module %s overwriting a global variable' % modname)
|
||||
skipped_modules.append(modname)
|
||||
continue
|
||||
self.log.info('overwrite module %s', modname)
|
||||
removed_modules.append(modname)
|
||||
created_modules.append(modname)
|
||||
attrs = {}
|
||||
for pname, pinfo in moddesc['parameters'].items():
|
||||
attrs[pname] = Param(pname, pinfo['datainfo'])
|
||||
for cname in moddesc['commands']:
|
||||
attrs[cname] = Command(cname, modname, self)
|
||||
mobj = type('M_%s' % modname, (Module,), attrs)(modname, self)
|
||||
mobj = type(f'M_{modname}', (Module,), attrs)(modname, self)
|
||||
if 'status' in mobj._parameters:
|
||||
self.register_callback((modname, 'status'), updateEvent=mobj._status_value_update)
|
||||
self.register_callback((modname, 'value'), updateEvent=mobj._status_value_update)
|
||||
setattr(main, modname, mobj)
|
||||
self.register_callback((modname, 'status'), updateEvent=mobj._status_update)
|
||||
clientenv.namespace[modname] = mobj
|
||||
if removed_modules:
|
||||
self.log.info('removed modules: %s', ' '.join(removed_modules))
|
||||
if skipped_modules:
|
||||
self.log.info('skipped modules overwriting globals: %s', ' '.join(skipped_modules))
|
||||
if created_modules:
|
||||
self.log.info('created modules: %s', ' '.join(created_modules))
|
||||
self.register_callback(None, self.unhandledMessage)
|
||||
self.log.info('%s', USAGE)
|
||||
log.show_time = True
|
||||
|
||||
def unhandledMessage(self, action, ident, data):
|
||||
"""handle logging messages"""
|
||||
if action == 'log':
|
||||
modname = ident.split(':')[0]
|
||||
modobj = getattr(main, modname, None)
|
||||
modname, loglevel = ident.split(':')
|
||||
modobj = clientenv.namespace.get(modname)
|
||||
if modobj:
|
||||
modobj.handle_log_message_(data)
|
||||
modobj.handle_log_message_(loglevel, data)
|
||||
return
|
||||
self.log.info('module %s not found', modname)
|
||||
self.log.info('unhandled: %s %s %r', action, ident, data)
|
||||
|
||||
def __repr__(self):
|
||||
return f'Client({self.uri!r})'
|
||||
|
||||
|
||||
def run(filepath):
|
||||
clientenv.namespace.update({
|
||||
"__file__": filepath,
|
||||
"__name__": "__main__",
|
||||
})
|
||||
with open(filepath, 'rb') as file:
|
||||
# pylint: disable=exec-used
|
||||
exec(compile(file.read(), filepath, 'exec'), clientenv.namespace, None)
|
||||
|
||||
|
||||
class ClientEnvironment:
|
||||
namespace = None
|
||||
last_frames = 0
|
||||
sigwinch = False
|
||||
|
||||
def init(self, namespace=None):
|
||||
self.nodes = []
|
||||
self.namespace = namespace or {}
|
||||
self.namespace.update(run=run, watch=watch, Client=Client)
|
||||
|
||||
def raise_with_short_traceback(self, exc):
|
||||
# count number of lines of internal irrelevant stack (for remote errors)
|
||||
self.last_frames = len(traceback.format_exception(*sys.exc_info()))
|
||||
raise exc
|
||||
|
||||
def short_traceback(self):
|
||||
"""cleanup traceback from irrelevant lines"""
|
||||
lines = traceback.format_exception(*sys.exc_info())
|
||||
# line 0: Traceback header
|
||||
# skip line 1+2 (contains unspecific console line and exec code)
|
||||
lines[1:3] = []
|
||||
if ' exec(' in lines[1]:
|
||||
# replace additional irrelevant exec line if needed with run command
|
||||
lines[1:2] = []
|
||||
# skip lines of client code not relevant for remote errors
|
||||
lines[-self.last_frames-1:-1] = []
|
||||
self.last_frames = 0
|
||||
if len(lines) <= 2: # traceback contains only console line
|
||||
lines = lines[-1:]
|
||||
return ''.join(lines)
|
||||
|
||||
|
||||
clientenv = ClientEnvironment()
|
||||
|
||||
|
||||
class Console(code.InteractiveConsole):
|
||||
def __init__(self, name='cli', namespace=None):
|
||||
if namespace:
|
||||
clientenv.namespace = namespace
|
||||
super().__init__(clientenv.namespace)
|
||||
history = None
|
||||
if readline:
|
||||
try:
|
||||
history = expanduser(f'~/.frappy-{name}-history')
|
||||
readline.read_history_file(history)
|
||||
except FileNotFoundError:
|
||||
pass
|
||||
try:
|
||||
self.interact('', '')
|
||||
finally:
|
||||
if history:
|
||||
readline.write_history_file(history)
|
||||
|
||||
def raw_input(self, prompt=""):
|
||||
clientenv.sigwinch = bool(readline) # activate refresh signal
|
||||
line = input(prompt)
|
||||
clientenv.sigwinch = False
|
||||
if line.startswith('/'):
|
||||
line = f"run('{line[1:].strip()}')"
|
||||
module = clientenv.namespace.get(line.strip())
|
||||
if isinstance(module, Module):
|
||||
print(module.showAll())
|
||||
line = ''
|
||||
return line
|
||||
|
||||
def showtraceback(self):
|
||||
self.write(clientenv.short_traceback())
|
||||
|
||||
|
||||
def init(*nodes):
|
||||
clientenv.init()
|
||||
success = not nodes
|
||||
for idx, node in enumerate(nodes):
|
||||
client_name = '_c%d' % idx
|
||||
try:
|
||||
node = clientenv.namespace[client_name] = Client(node, name=client_name)
|
||||
clientenv.nodes.append(node)
|
||||
success = True
|
||||
except Exception as e:
|
||||
print(repr(e))
|
||||
return success
|
||||
|
||||
|
||||
def interact(usage_tail=''):
|
||||
empty = '_c0' not in clientenv.namespace
|
||||
print(USAGE.format(
|
||||
client_name='cli' if empty else '_c0',
|
||||
client_assign="\ncli = Client('localhost:5000')\n" if empty else '',
|
||||
tail=usage_tail))
|
||||
Console()
|
||||
|
||||
151
frappy/config.py
151
frappy/config.py
@@ -1,4 +1,3 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# *****************************************************************************
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify it under
|
||||
@@ -17,24 +16,29 @@
|
||||
#
|
||||
# Module authors:
|
||||
# Alexander Zaft <a.zaft@fz-juelich.de>
|
||||
# Markus Zolliker <markus.zolliker@psi.ch>
|
||||
#
|
||||
# *****************************************************************************
|
||||
|
||||
import os
|
||||
from pathlib import Path
|
||||
import re
|
||||
|
||||
from frappy.errors import ConfigError
|
||||
from frappy.lib import generalConfig
|
||||
|
||||
|
||||
class Undef:
|
||||
pass
|
||||
|
||||
|
||||
class Node(dict):
|
||||
def __init__(
|
||||
self,
|
||||
equipment_id,
|
||||
description,
|
||||
interface=None,
|
||||
cls='protocol.dispatcher.Dispatcher',
|
||||
omit_unchanged_within=1.1,
|
||||
cls='frappy.protocol.dispatcher.Dispatcher',
|
||||
**kwds
|
||||
):
|
||||
super().__init__(
|
||||
@@ -42,20 +46,22 @@ class Node(dict):
|
||||
description=description,
|
||||
interface=interface,
|
||||
cls=cls,
|
||||
omit_unchanged_within=omit_unchanged_within,
|
||||
**kwds
|
||||
)
|
||||
|
||||
|
||||
class Param(dict):
|
||||
def __init__(self, value=Undef, **kwds):
|
||||
if value is not Undef:
|
||||
kwds['value'] = value
|
||||
super().__init__(**kwds)
|
||||
|
||||
|
||||
class Group(tuple):
|
||||
def __new__(cls, *args):
|
||||
return super().__new__(cls, args)
|
||||
|
||||
|
||||
class Mod(dict):
|
||||
def __init__(self, name, cls, description, **kwds):
|
||||
super().__init__(
|
||||
@@ -63,6 +69,11 @@ class Mod(dict):
|
||||
cls=cls,
|
||||
description=description
|
||||
)
|
||||
|
||||
# matches name from spec
|
||||
if not re.match(r'^[a-zA-Z]\w{0,62}$', name, re.ASCII):
|
||||
raise ConfigError(f'Not a valid SECoP Module name: "{name}".'
|
||||
' Does it only contain letters, numbers and underscores?')
|
||||
# Make parameters out of all keywords
|
||||
groups = {}
|
||||
for key, val in kwds.items():
|
||||
@@ -77,16 +88,50 @@ class Mod(dict):
|
||||
for member in members:
|
||||
self[member]['group'] = group
|
||||
|
||||
def override(self, **kwds):
|
||||
name = self['name']
|
||||
warnings = []
|
||||
for key, ovr in kwds.items():
|
||||
if isinstance(ovr, Group):
|
||||
warnings.append(f'ignore Group when overriding module {name}')
|
||||
continue
|
||||
param = self.get(key)
|
||||
if param is None:
|
||||
self[key] = ovr if isinstance(ovr, Param) else Param(ovr)
|
||||
continue
|
||||
if isinstance(param, Param):
|
||||
if isinstance(ovr, Param):
|
||||
param.update(ovr)
|
||||
else:
|
||||
param['value'] = ovr
|
||||
else: # description or cls
|
||||
self[key] = ovr
|
||||
return warnings
|
||||
|
||||
|
||||
class Collector:
|
||||
def __init__(self, cls):
|
||||
self.list = []
|
||||
self.cls = cls
|
||||
def __init__(self):
|
||||
self.modules = {}
|
||||
self.warnings = []
|
||||
|
||||
def add(self, *args, **kwds):
|
||||
self.list.append(self.cls(*args, **kwds))
|
||||
mod = Mod(*args, **kwds)
|
||||
name = mod.pop('name')
|
||||
if name in self.modules:
|
||||
self.warnings.append(f'duplicate module {name} overrides previous')
|
||||
self.modules[name] = mod
|
||||
return mod
|
||||
|
||||
def append(self, mod):
|
||||
self.list.append(mod)
|
||||
def override(self, name, **kwds):
|
||||
"""override properties/parameters of previously configured modules
|
||||
|
||||
this is useful together with 'include'
|
||||
"""
|
||||
mod = self.modules.get(name)
|
||||
if mod is None:
|
||||
self.warnings.append(f'try to override nonexisting module {name}')
|
||||
return
|
||||
self.warnings.extend(mod.override(**kwds))
|
||||
|
||||
|
||||
class NodeCollector:
|
||||
@@ -99,57 +144,77 @@ class NodeCollector:
|
||||
else:
|
||||
raise ConfigError('Only one Node is allowed per file!')
|
||||
|
||||
def override(self, **kwds):
|
||||
if self.node is None:
|
||||
raise ConfigError('node must be defined before overriding')
|
||||
self.node.update(kwds)
|
||||
|
||||
|
||||
class Config(dict):
|
||||
def __init__(self, node, modules):
|
||||
super().__init__(
|
||||
node=node.node,
|
||||
**{mod['name']: mod for mod in modules.list}
|
||||
)
|
||||
self.module_names = {mod.pop('name') for mod in modules.list}
|
||||
super().__init__(node=node.node, **modules.modules)
|
||||
self.module_names = set(modules.modules)
|
||||
self.ambiguous = set()
|
||||
|
||||
def merge_modules(self, other):
|
||||
""" merges only the modules from 'other' into 'self'"""
|
||||
self.ambiguous |= self.module_names & other.module_names
|
||||
equipment_id = other['node']['equipment_id']
|
||||
for name, mod in other.items():
|
||||
if name == 'node':
|
||||
continue
|
||||
if name not in self.module_names:
|
||||
self.module_names.add(name)
|
||||
self.modules.append(mod)
|
||||
self[name] = mod
|
||||
mod['original_id'] = equipment_id
|
||||
|
||||
|
||||
def process_file(config_text):
|
||||
class Include:
|
||||
def __init__(self, namespace, log):
|
||||
self.namespace = namespace
|
||||
self.log = log
|
||||
|
||||
def __call__(self, cfgfile):
|
||||
filename = to_config_path(cfgfile, self.log, '')
|
||||
# pylint: disable=exec-used
|
||||
exec(compile(filename.read_bytes(), filename, 'exec'), self.namespace)
|
||||
|
||||
|
||||
def process_file(filename, log):
|
||||
config_text = filename.read_bytes()
|
||||
node = NodeCollector()
|
||||
mods = Collector(Mod)
|
||||
ns = {'Node': node.add, 'Mod': mods.add, 'Param': Param, 'Command': Param, 'Group': Group}
|
||||
|
||||
mods = Collector()
|
||||
ns = {'Node': node.add, 'Mod': mods.add, 'Param': Param, 'Command': Param, 'Group': Group,
|
||||
'override': mods.override, 'overrideNode': node.override}
|
||||
ns['include'] = Include(ns, log)
|
||||
# pylint: disable=exec-used
|
||||
exec(config_text, ns)
|
||||
exec(compile(config_text, filename, 'exec'), ns)
|
||||
|
||||
if mods.warnings:
|
||||
log.warning('warnings in %s', filename)
|
||||
for text in mods.warnings:
|
||||
log.warning(text)
|
||||
return Config(node, mods)
|
||||
|
||||
|
||||
def to_config_path(cfgfile, log):
|
||||
def to_config_path(cfgfile, log, check_end='_cfg.py'):
|
||||
candidates = [cfgfile + e for e in ['_cfg.py', '.py', '']]
|
||||
if os.sep in cfgfile: # specified as full path
|
||||
filename = cfgfile if os.path.exists(cfgfile) else None
|
||||
file = Path(cfgfile) if Path(cfgfile).exists() else None
|
||||
else:
|
||||
for filename in [os.path.join(d, candidate)
|
||||
for d in generalConfig.confdir.split(os.pathsep)
|
||||
for candidate in candidates]:
|
||||
if os.path.exists(filename):
|
||||
for file in [Path(d) / candidate
|
||||
for d in generalConfig.confdir
|
||||
for candidate in candidates]:
|
||||
if file.exists():
|
||||
break
|
||||
else:
|
||||
filename = None
|
||||
|
||||
if filename is None:
|
||||
raise ConfigError("Couldn't find cfg file %r in %s"
|
||||
% (cfgfile, generalConfig.confdir))
|
||||
if not filename.endswith('_cfg.py'):
|
||||
log.warning("Config files should end in '_cfg.py': %s", os.path.basename(filename))
|
||||
log.debug('Using config file %s for %s', filename, cfgfile)
|
||||
return filename
|
||||
file = None
|
||||
if file is None:
|
||||
raise ConfigError(f"Couldn't find cfg file {cfgfile!r} in {generalConfig.confdir}")
|
||||
if not file.name.endswith(check_end):
|
||||
log.warning("Config files should end in %r: %s", check_end, file.name)
|
||||
log.debug('Using config file %s for %s', file, cfgfile)
|
||||
return file
|
||||
|
||||
|
||||
def load_config(cfgfiles, log):
|
||||
@@ -158,8 +223,8 @@ def load_config(cfgfiles, log):
|
||||
Only the node-section of the first config file will be returned.
|
||||
The others will be discarded.
|
||||
Arguments
|
||||
- cfgfiles : str
|
||||
Comma separated list of config-files
|
||||
- cfgfiles : list
|
||||
List of config file paths
|
||||
- log : frappy.logging.Mainlogger
|
||||
Logger aquired from frappy.logging
|
||||
Returns
|
||||
@@ -167,16 +232,16 @@ def load_config(cfgfiles, log):
|
||||
merged configuration
|
||||
"""
|
||||
config = None
|
||||
for cfgfile in cfgfiles.split(','):
|
||||
filename = to_config_path(cfgfile, log)
|
||||
for cfgfile in cfgfiles:
|
||||
filename = to_config_path(str(cfgfile), log)
|
||||
log.debug('Parsing config file %s...', filename)
|
||||
with open(filename, 'rb') as f:
|
||||
config_text = f.read()
|
||||
cfg = process_file(config_text)
|
||||
cfg = process_file(filename, log)
|
||||
if config:
|
||||
config.merge_modules(cfg)
|
||||
else:
|
||||
config = cfg
|
||||
if config.get('node') is None:
|
||||
raise ConfigError(f'missing Node in {filename}')
|
||||
|
||||
if config.ambiguous:
|
||||
log.warning('ambiguous sections in %s: %r',
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# *****************************************************************************
|
||||
# Copyright (c) 2015-2016 by the authors, see LICENSE
|
||||
# Copyright (c) 2015-2024 by the authors, see LICENSE
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify it under
|
||||
# the terms of the GNU General Public License as published by the Free Software
|
||||
@@ -27,19 +26,36 @@
|
||||
|
||||
# pylint: disable=unused-import
|
||||
from frappy.datatypes import ArrayOf, BLOBType, BoolType, EnumType, \
|
||||
FloatRange, IntRange, ScaledInteger, StringType, StructOf, TupleOf
|
||||
FloatRange, IntRange, ScaledInteger, StringType, StructOf, TupleOf, StatusType
|
||||
from frappy.lib.enum import Enum
|
||||
from frappy.modulebase import Done, Module, Feature
|
||||
from frappy.modules import Attached, Communicator, \
|
||||
Done, Drivable, Feature, Module, Readable, Writable, HasAccessibles
|
||||
from frappy.params import Command, Parameter
|
||||
Drivable, Readable, Writable
|
||||
from frappy.params import Command, Parameter, Limit
|
||||
from frappy.properties import Property
|
||||
from frappy.proxy import Proxy, SecNode, proxy_class
|
||||
from frappy.io import HasIO, StringIO, BytesIO, HasIodev # TODO: remove HasIodev (legacy stuff)
|
||||
from frappy.persistent import PersistentMixin, PersistentParam
|
||||
from frappy.persistent import PersistentMixin, PersistentParam, PersistentLimit
|
||||
from frappy.rwhandler import ReadHandler, WriteHandler, CommonReadHandler, \
|
||||
CommonWriteHandler, nopoll
|
||||
|
||||
ERROR = Drivable.Status.ERROR
|
||||
WARN = Drivable.Status.WARN
|
||||
BUSY = Drivable.Status.BUSY
|
||||
IDLE = Drivable.Status.IDLE
|
||||
DISABLED = StatusType.DISABLED
|
||||
IDLE = StatusType.IDLE
|
||||
STANDBY = StatusType.STANDBY
|
||||
PREPARED = StatusType.PREPARED
|
||||
WARN = StatusType.WARN
|
||||
WARN_STANDBY = StatusType.WARN_STANDBY
|
||||
WARN_PREPARED = StatusType.WARN_PREPARED
|
||||
UNSTABLE = StatusType.UNSTABLE # no SECoP standard (yet)
|
||||
BUSY = StatusType.BUSY
|
||||
DISABLING = StatusType.DISABLING
|
||||
INITIALIZING = StatusType.INITIALIZING
|
||||
PREPARING = StatusType.PREPARING
|
||||
STARTING = StatusType.STARTING
|
||||
RAMPING = StatusType.RAMPING
|
||||
STABILIZING = StatusType.STABILIZING
|
||||
FINALIZING = StatusType.FINALIZING
|
||||
ERROR = StatusType.ERROR
|
||||
ERROR_STANDBY = StatusType.ERROR_STANDBY
|
||||
ERROR_PREPARED = StatusType.ERROR_PREPARED
|
||||
UNKNOWN = StatusType.UNKNOWN # no SECoP standard (yet)
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
41
frappy/dynamic.py
Normal file
41
frappy/dynamic.py
Normal file
@@ -0,0 +1,41 @@
|
||||
# *****************************************************************************
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify it under
|
||||
# the terms of the GNU General Public License as published by the Free Software
|
||||
# Foundation; either version 2 of the License, or (at your option) any later
|
||||
# version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
|
||||
# details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License along with
|
||||
# this program; if not, write to the Free Software Foundation, Inc.,
|
||||
# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
#
|
||||
# Module authors:
|
||||
# Alexander Zaft <a.zaft@fz-juelich.de>
|
||||
#
|
||||
# *****************************************************************************
|
||||
|
||||
from .core import Module
|
||||
|
||||
class Pinata(Module):
|
||||
"""Base class for scanning conections and adding modules accordingly.
|
||||
|
||||
Like a piñata. You poke it, and modules fall out.
|
||||
|
||||
To use it, subclass it for your connection type and override the function
|
||||
'scanModules'. For each module you want to register, you should yield the
|
||||
modules name and its config options.
|
||||
The connection will then be scanned during server startup.
|
||||
"""
|
||||
export = False
|
||||
|
||||
# POKE
|
||||
def scanModules(self):
|
||||
"""yield (modname, options) for each module the Pinata should create.
|
||||
Options has to include keys for class and the config for the module.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
235
frappy/errors.py
235
frappy/errors.py
@@ -1,4 +1,3 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# *****************************************************************************
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify it under
|
||||
@@ -20,111 +19,235 @@
|
||||
# Markus Zolliker <markus.zolliker@psi.ch>
|
||||
#
|
||||
# *****************************************************************************
|
||||
"""Define (internal) SECoP Errors"""
|
||||
"""Define (internal) SECoP Errors
|
||||
|
||||
all error classes inherited from SECoPError should be placed in this module,
|
||||
else they might not be registered and can therefore not be rebuilt on the client side
|
||||
"""
|
||||
|
||||
import re
|
||||
from ast import literal_eval
|
||||
|
||||
|
||||
class SECoPError(RuntimeError):
|
||||
silent = False # silent = True indicates that the error is already logged
|
||||
clsname2class = {} # needed to convert error reports back to classes
|
||||
name = 'InternalError'
|
||||
name2class = {}
|
||||
report_error = True
|
||||
raising_methods = None
|
||||
|
||||
def __init_subclass__(cls):
|
||||
cls.clsname2class[cls.__name__] = cls
|
||||
if 'name' in cls.__dict__:
|
||||
cls.name2class[cls.name] = cls
|
||||
|
||||
def __init__(self, *args, **kwds):
|
||||
super().__init__()
|
||||
self.args = args
|
||||
self.kwds = kwds
|
||||
for k, v in list(kwds.items()):
|
||||
setattr(self, k, v)
|
||||
self.raising_methods = []
|
||||
|
||||
def __repr__(self):
|
||||
args = ', '.join(map(repr, self.args))
|
||||
kwds = ', '.join(['%s=%r' % i for i in list(self.__dict__.items())
|
||||
if i[0] != 'silent'])
|
||||
res = []
|
||||
if args:
|
||||
res.append(args)
|
||||
if kwds:
|
||||
res.append(kwds)
|
||||
return '%s(%s)' % (self.name, ', '.join(res))
|
||||
res.extend((repr(a) for a in self.args))
|
||||
#res.extend(('%s=%r' % i for i in self.kwds.items()))
|
||||
return f"{self.name or type(self).__name__}({', '.join(res)})"
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
return self.__class__.__name__[:-len('Error')]
|
||||
def __str__(self):
|
||||
return self.format(True)
|
||||
|
||||
def format(self, stripped):
|
||||
"""format with info about raising methods
|
||||
|
||||
class SECoPServerError(SECoPError):
|
||||
name = 'InternalError'
|
||||
:param stripped: strip last method.
|
||||
:return: the formatted error message
|
||||
|
||||
Use stripped=True (or str()) for the following cases, as the last method can be derived from the context:
|
||||
|
||||
- stored in pobj.readerror: read_<pobj.name>
|
||||
- error message from a change command: write_<pname>
|
||||
- error message from a read command: read_<pname>
|
||||
|
||||
Use stripped=False for the log file, as the related parameter is not known
|
||||
"""
|
||||
mlist = self.raising_methods
|
||||
if mlist and stripped:
|
||||
mlist = mlist[:-1] # do not pop, as this would change self.raising_methods
|
||||
prefix = '' if self.name2class.get(self.name) == type(self) else type(self).__name__
|
||||
prefix += ''.join(' in ' + m for m in mlist).strip()
|
||||
if prefix:
|
||||
return f'{prefix}: {super().__str__()}'
|
||||
return super().__str__()
|
||||
|
||||
def __eq__(self, other):
|
||||
return type(self) is type(other) and self.args == other.args and self.kwds == other.kwds
|
||||
|
||||
def __ne__(self, other):
|
||||
return not self == other
|
||||
|
||||
|
||||
class InternalError(SECoPError):
|
||||
"""uncatched error"""
|
||||
name = 'InternalError'
|
||||
|
||||
|
||||
class ProgrammingError(SECoPError):
|
||||
name = 'InternalError'
|
||||
"""catchable programming error"""
|
||||
|
||||
|
||||
class ConfigError(SECoPError):
|
||||
name = 'InternalError'
|
||||
"""invalid configuration"""
|
||||
|
||||
|
||||
class ProtocolError(SECoPError):
|
||||
"""A malformed request or on unspecified message was sent
|
||||
|
||||
This includes non-understood actions and malformed specifiers.
|
||||
Also if the message exceeds an implementation defined maximum size.
|
||||
"""
|
||||
name = 'ProtocolError'
|
||||
|
||||
|
||||
class NoSuchModuleError(SECoPError):
|
||||
"""missing module
|
||||
|
||||
The action can not be performed as the specified module is non-existent"""
|
||||
name = 'NoSuchModule'
|
||||
|
||||
|
||||
# pylint: disable=redefined-builtin
|
||||
class NotImplementedError(NotImplementedError, SECoPError):
|
||||
pass
|
||||
class NotImplementedSECoPError(NotImplementedError, SECoPError):
|
||||
"""not (yet) implemented
|
||||
|
||||
A (not yet) implemented action or combination of action and specifier
|
||||
was requested. This should not be used in productive setups, but is very
|
||||
helpful during development."""
|
||||
name = 'NotImplemented'
|
||||
|
||||
|
||||
class NoSuchParameterError(SECoPError):
|
||||
pass
|
||||
"""missing parameter
|
||||
|
||||
The action can not be performed as the specified parameter is non-existent.
|
||||
Also raised when trying to use a command name in a 'read' or 'change' message.
|
||||
"""
|
||||
name = 'NoSuchParameter'
|
||||
|
||||
|
||||
class NoSuchCommandError(SECoPError):
|
||||
pass
|
||||
"""The specified command does not exist
|
||||
|
||||
Also raised when trying to use a parameter name in a 'do' message.
|
||||
"""
|
||||
name = 'NoSuchCommand'
|
||||
|
||||
|
||||
class ReadOnlyError(SECoPError):
|
||||
pass
|
||||
"""The requested write can not be performed on a readonly value"""
|
||||
name = 'ReadOnly'
|
||||
|
||||
|
||||
class BadValueError(ValueError, SECoPError):
|
||||
pass
|
||||
class BadValueError(SECoPError):
|
||||
"""do not raise, but might used for instance checks (WrongTypeError, RangeError)"""
|
||||
|
||||
|
||||
class RangeError(BadValueError, ValueError):
|
||||
"""data out of range
|
||||
|
||||
The requested parameter change or Command can not be performed as the
|
||||
argument value is not in the allowed range specified by the datainfo
|
||||
property. This also happens if an unspecified Enum variant is tried
|
||||
to be used, the size of a Blob or String does not match the limits
|
||||
given in the descriptive data, or if the number of elements in an
|
||||
array does not match the limits given in the descriptive data."""
|
||||
name = 'RangeError'
|
||||
|
||||
|
||||
class BadJSONError(SECoPError):
|
||||
"""The data part of the message can not be parsed, i.e. the JSON-data is no valid JSON.
|
||||
|
||||
not used in Frappy, but might appear on the client side from a foreign SEC Node
|
||||
"""
|
||||
# TODO: check whether this should not be removed from specs!
|
||||
name = 'BadJSON'
|
||||
|
||||
|
||||
class WrongTypeError(BadValueError, TypeError):
|
||||
"""Wrong data type
|
||||
|
||||
The requested parameter change or Command can not be performed as the
|
||||
argument has the wrong type. (i.e. a string where a number is expected.)
|
||||
It may also be used if an incomplete struct is sent, but a complete
|
||||
struct is expected."""
|
||||
name = 'WrongType'
|
||||
|
||||
|
||||
class CommandFailedError(SECoPError):
|
||||
pass
|
||||
name = 'CommandFailed'
|
||||
|
||||
|
||||
class CommandRunningError(SECoPError):
|
||||
pass
|
||||
"""The command is already executing.
|
||||
|
||||
request may be retried after the module is no longer BUSY
|
||||
(retryable)"""
|
||||
name = 'CommandRunning'
|
||||
|
||||
|
||||
class CommunicationFailedError(SECoPError):
|
||||
pass
|
||||
"""Some communication (with hardware controlled by this SEC node) failed
|
||||
(retryable)"""
|
||||
name = 'CommunicationFailed'
|
||||
|
||||
|
||||
class SilentCommunicationFailedError(CommunicationFailedError):
|
||||
silent = True
|
||||
|
||||
|
||||
class IsBusyError(SECoPError):
|
||||
pass
|
||||
"""The requested action can not be performed while the module is Busy
|
||||
or the command still running"""
|
||||
name = 'IsBusy'
|
||||
|
||||
|
||||
class IsErrorError(SECoPError):
|
||||
pass
|
||||
"""The requested action can not be performed while the module is in error state"""
|
||||
name = 'IsError'
|
||||
|
||||
|
||||
class DisabledError(SECoPError):
|
||||
pass
|
||||
"""The requested action can not be performed while the module is disabled"""
|
||||
name = 'Disabled'
|
||||
|
||||
|
||||
class ImpossibleError(SECoPError):
|
||||
"""The requested action can not be performed at the moment"""
|
||||
name = 'Impossible'
|
||||
|
||||
|
||||
class ReadFailedError(SECoPError):
|
||||
"""The requested parameter can not be read just now"""
|
||||
name = 'ReadFailed'
|
||||
|
||||
|
||||
class OutOfRangeError(SECoPError):
|
||||
"""The value read from the hardware is out of sensor or calibration range"""
|
||||
name = 'OutOfRange'
|
||||
|
||||
|
||||
class HardwareError(SECoPError):
|
||||
"""The connected hardware operates incorrect or may not operate at all
|
||||
due to errors inside or in connected components."""
|
||||
name = 'HardwareError'
|
||||
|
||||
|
||||
FRAPPY_ERROR = re.compile(r'(.*)\(.*\)$')
|
||||
class TimeoutSECoPError(TimeoutError, SECoPError):
|
||||
"""Some initiated action took longer than the maximum allowed time (retryable)"""
|
||||
name = 'TimeoutError'
|
||||
|
||||
|
||||
FRAPPY_ERROR = re.compile(r'(\w*): (.*)$')
|
||||
|
||||
|
||||
def make_secop_error(name, text):
|
||||
@@ -134,43 +257,25 @@ def make_secop_error(name, text):
|
||||
:param text: the second item of a SECoP error report
|
||||
:return: the built instance of SECoPError
|
||||
"""
|
||||
try:
|
||||
# try to interprete the error text as a repr(<instance of SECoPError>)
|
||||
# as it would be created by a Frappy server
|
||||
cls, textarg = FRAPPY_ERROR.match(text).groups()
|
||||
errcls = locals()[cls]
|
||||
if errcls.name == name:
|
||||
# convert repr(<string>) to <string>
|
||||
text = literal_eval(textarg)
|
||||
except Exception:
|
||||
# probably not a Frappy server, or running a different version
|
||||
errcls = EXCEPTIONS.get(name, InternalError)
|
||||
return errcls(text)
|
||||
match = FRAPPY_ERROR.match(text)
|
||||
if match:
|
||||
clsname, errtext = match.groups()
|
||||
errcls = SECoPError.clsname2class.get(clsname)
|
||||
if errcls:
|
||||
return errcls(errtext)
|
||||
return SECoPError.name2class.get(name, InternalError)(text)
|
||||
|
||||
|
||||
def secop_error(exception):
|
||||
if isinstance(exception, SECoPError):
|
||||
return exception
|
||||
return InternalError(repr(exception))
|
||||
def secop_error(exc):
|
||||
"""turn into InternalError, if not already a SECoPError"""
|
||||
if isinstance(exc, SECoPError):
|
||||
return exc
|
||||
return InternalError(f'{type(exc).__name__}: {exc}')
|
||||
|
||||
|
||||
EXCEPTIONS = dict(
|
||||
NoSuchModule=NoSuchModuleError,
|
||||
NoSuchParameter=NoSuchParameterError,
|
||||
NoSuchCommand=NoSuchCommandError,
|
||||
CommandFailed=CommandFailedError,
|
||||
CommandRunning=CommandRunningError,
|
||||
ReadOnly=ReadOnlyError,
|
||||
BadValue=BadValueError,
|
||||
CommunicationFailed=CommunicationFailedError,
|
||||
HardwareError=HardwareError,
|
||||
IsBusy=IsBusyError,
|
||||
IsError=IsErrorError,
|
||||
Disabled=DisabledError,
|
||||
# TODO: check if these are really needed:
|
||||
SECoPError.name2class.update(
|
||||
SyntaxError=ProtocolError,
|
||||
NotImplemented=NotImplementedError,
|
||||
ProtocolError=ProtocolError,
|
||||
InternalError=InternalError,
|
||||
# internal short versions (candidates for spec)
|
||||
Protocol=ProtocolError,
|
||||
Internal=InternalError,
|
||||
|
||||
300
frappy/extparams.py
Normal file
300
frappy/extparams.py
Normal file
@@ -0,0 +1,300 @@
|
||||
# *****************************************************************************
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify it under
|
||||
# the terms of the GNU General Public License as published by the Free Software
|
||||
# Foundation; either version 2 of the License, or (at your option) any later
|
||||
# version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
|
||||
# details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License along with
|
||||
# this program; if not, write to the Free Software Foundation, Inc.,
|
||||
# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
#
|
||||
# Module authors:
|
||||
# Markus Zolliker <markus.zolliker@psi.ch>
|
||||
#
|
||||
# *****************************************************************************
|
||||
"""extended parameters
|
||||
|
||||
special parameter classes with some automatic functionality
|
||||
"""
|
||||
|
||||
import re
|
||||
from frappy.core import Parameter, Property
|
||||
from frappy.datatypes import BoolType, DataType, DataTypeType, EnumType, \
|
||||
FloatRange, StringType, StructOf, ValueType
|
||||
from frappy.errors import ProgrammingError
|
||||
|
||||
|
||||
class StructParam(Parameter):
|
||||
"""convenience class to create a struct Parameter together with individual params
|
||||
|
||||
Usage:
|
||||
|
||||
class Controller(Drivable):
|
||||
|
||||
...
|
||||
|
||||
ctrlpars = StructParam('ctrlpars struct', {
|
||||
'p': Parameter('control parameter p', FloatRange()),
|
||||
'i': Parameter('control parameter i', FloatRange()),
|
||||
'd': Parameter('control parameter d', FloatRange()),
|
||||
}, prefix='pid_', readonly=False)
|
||||
...
|
||||
|
||||
then implement either read_ctrlpars and write_ctrlpars or
|
||||
read_pid_p, read_pid_i, read_pid_d, write_pid_p, write_pid_i and write_pid_d
|
||||
|
||||
the methods not implemented will be created automatically
|
||||
"""
|
||||
|
||||
# use properties, as simple attributes are not considered on copy()
|
||||
paramdict = Property('dict <parametername> of Parameter(...)', ValueType())
|
||||
hasStructRW = Property('has a read_<struct param> or write_<struct param> method',
|
||||
BoolType(), default=False)
|
||||
|
||||
insideRW = 0 # counter for avoiding multiple superfluous updates
|
||||
|
||||
def __init__(self, description=None, paramdict=None, prefix='', *, datatype=None, readonly=False, **kwds):
|
||||
"""create a struct parameter together with individual parameters
|
||||
|
||||
in addition to normal Parameter arguments:
|
||||
|
||||
:param paramdict: dict <member name> of Parameter(...)
|
||||
:param prefix: a prefix for the parameter name to add to the member name
|
||||
"""
|
||||
if isinstance(paramdict, DataType):
|
||||
raise ProgrammingError('second argument must be a dict of Param')
|
||||
if datatype is None and paramdict is not None: # omit the following on Parameter.copy()
|
||||
for membername, param in paramdict.items():
|
||||
param.name = prefix + membername
|
||||
datatype = StructOf(**{m: p.datatype for m, p in paramdict.items()})
|
||||
kwds['influences'] = [p.name for p in paramdict.values()]
|
||||
self.updateEnable = {}
|
||||
if paramdict:
|
||||
kwds['paramdict'] = paramdict
|
||||
super().__init__(description, datatype, readonly=readonly, **kwds)
|
||||
|
||||
def __set_name__(self, owner, name):
|
||||
# names of access methods of structed param (e.g. ctrlpars)
|
||||
struct_read_name = f'read_{name}' # e.g. 'read_ctrlpars'
|
||||
struct_write_name = f'write_{name}' # e.h. 'write_ctrlpars'
|
||||
self.hasStructRW = hasattr(owner, struct_read_name) or hasattr(owner, struct_write_name)
|
||||
|
||||
for membername, param in self.paramdict.items():
|
||||
pname = param.name
|
||||
changes = {
|
||||
'readonly': self.readonly,
|
||||
'influences': set(param.influences) | {name},
|
||||
}
|
||||
param.ownProperties.update(changes)
|
||||
param.init(changes)
|
||||
setattr(owner, pname, param)
|
||||
param.__set_name__(owner, param.name)
|
||||
|
||||
if self.hasStructRW:
|
||||
rname = f'read_{pname}'
|
||||
|
||||
if not hasattr(owner, rname):
|
||||
def rfunc(self, membername=membername, struct_read_name=struct_read_name):
|
||||
return getattr(self, struct_read_name)()[membername]
|
||||
|
||||
rfunc.poll = False # read_<struct param> is polled only
|
||||
setattr(owner, rname, rfunc)
|
||||
|
||||
if not self.readonly:
|
||||
wname = f'write_{pname}'
|
||||
if not hasattr(owner, wname):
|
||||
def wfunc(self, value, membername=membername,
|
||||
name=name, rname=rname, struct_write_name=struct_write_name):
|
||||
valuedict = dict(getattr(self, name))
|
||||
valuedict[membername] = value
|
||||
getattr(self, struct_write_name)(valuedict)
|
||||
return getattr(self, rname)()
|
||||
|
||||
setattr(owner, wname, wfunc)
|
||||
|
||||
if not self.hasStructRW:
|
||||
if not hasattr(owner, struct_read_name):
|
||||
def struct_read_func(self, name=name, flist=tuple(
|
||||
(m, f'read_{p.name}') for m, p in self.paramdict.items())):
|
||||
pobj = self.parameters[name]
|
||||
# disable updates generated from the callbacks of individual params
|
||||
pobj.insideRW += 1 # guarded by self.accessLock
|
||||
try:
|
||||
return {m: getattr(self, f)() for m, f in flist}
|
||||
finally:
|
||||
pobj.insideRW -= 1
|
||||
|
||||
setattr(owner, struct_read_name, struct_read_func)
|
||||
|
||||
if not (self.readonly or hasattr(owner, struct_write_name)):
|
||||
|
||||
def struct_write_func(self, value, name=name, funclist=tuple(
|
||||
(m, f'write_{p.name}') for m, p in self.paramdict.items())):
|
||||
pobj = self.parameters[name]
|
||||
pobj.insideRW += 1 # guarded by self.accessLock
|
||||
try:
|
||||
return {m: getattr(self, f)(value[m]) for m, f in funclist}
|
||||
finally:
|
||||
pobj.insideRW -= 1
|
||||
|
||||
setattr(owner, struct_write_name, struct_write_func)
|
||||
|
||||
super().__set_name__(owner, name)
|
||||
|
||||
def finish(self, modobj=None):
|
||||
"""register callbacks for consistency"""
|
||||
super().finish(modobj)
|
||||
if modobj:
|
||||
|
||||
if self.hasStructRW:
|
||||
def cb(value, modobj=modobj, structparam=self):
|
||||
for membername, param in structparam.paramdict.items():
|
||||
setattr(modobj, param.name, value[membername])
|
||||
|
||||
modobj.addCallback(self.name, cb)
|
||||
else:
|
||||
for membername, param in self.paramdict.items():
|
||||
def cb(value, modobj=modobj, structparam=self, membername=membername):
|
||||
if not structparam.insideRW:
|
||||
prev = dict(getattr(modobj, structparam.name))
|
||||
prev[membername] = value
|
||||
setattr(modobj, structparam.name, prev)
|
||||
|
||||
modobj.addCallback(param.name, cb)
|
||||
|
||||
|
||||
class FloatEnumParam(Parameter):
|
||||
"""combine enum and float parameter
|
||||
|
||||
Example Usage:
|
||||
|
||||
vrange = FloatEnumParam('sensor range', ['500uV', '20mV', '1V'], 'V')
|
||||
|
||||
The following will be created automatically:
|
||||
|
||||
- the parameter vrange will get a datatype FloatRange(5e-4, 1, unit='V')
|
||||
- an additional parameter `vrange_idx` will be created with an enum type
|
||||
{'500uV': 0, '20mV': 1, '1V': 2}
|
||||
- the method `write_vrange` will be created automatically
|
||||
|
||||
However, the methods `write_vrange_idx` and `read_vrange_idx`, if needed,
|
||||
have to implemented by the programmer.
|
||||
|
||||
Writing to the float parameter involves 'rounding' to the closest allowed value.
|
||||
|
||||
Customization:
|
||||
|
||||
The individual labels might be customized by defining them as a tuple
|
||||
(<index>, <label>, <float value>) where either the index or the float value
|
||||
may be omitted.
|
||||
|
||||
When the index is omitted, the element will be the previous index + 1 or
|
||||
0 when it is the first element.
|
||||
|
||||
Omitted values will be determined from the label, assuming that they use
|
||||
one of the predefined unit prefixes together with the given unit.
|
||||
|
||||
The name of the index parameter is by default '<name>_idx' but might be
|
||||
changed with the idx_name argument.
|
||||
"""
|
||||
# use properties, as simple attributes are not considered on copy()
|
||||
idx_name = Property('name of attached index parameter', StringType(), default='')
|
||||
valuedict = Property('dict <index> of <value>', ValueType(dict))
|
||||
enumtype = Property('dict <label> of <index', DataTypeType())
|
||||
|
||||
# TODO: factor out unit handling, at the latest when needed elsewhere
|
||||
PREFIXES = {'q': -30, 'r': -27, 'y': -24, 'z': -21, 'a': -18, 'f': -15,
|
||||
'p': -12, 'n': -9, 'u': -6, 'µ': -6, 'm': -3,
|
||||
'': 0, 'k': 3, 'M': 6, 'G': 9, 'T': 12,
|
||||
'P': 15, 'E': 18, 'Z': 21, 'Y': 24, 'R': 25, 'Q': 30}
|
||||
|
||||
def __init__(self, description=None, labels=None, unit='',
|
||||
*, datatype=None, readonly=False, **kwds):
|
||||
if labels is None:
|
||||
# called on Parameter.copy()
|
||||
super().__init__(description, datatype, readonly=readonly, **kwds)
|
||||
return
|
||||
if isinstance(labels, DataType):
|
||||
raise ProgrammingError('second argument must be a list of labels, not a datatype')
|
||||
nextidx = 0
|
||||
try:
|
||||
edict = {}
|
||||
vdict = {}
|
||||
for elem in labels:
|
||||
if isinstance(elem, str):
|
||||
idx, label = [nextidx, elem]
|
||||
else:
|
||||
if isinstance(elem[0], str):
|
||||
elem = [nextidx] + list(elem)
|
||||
idx, label, *tail = elem
|
||||
if tail:
|
||||
vdict[idx], = tail
|
||||
edict[label] = idx
|
||||
nextidx = idx + 1
|
||||
except (ValueError, TypeError) as e:
|
||||
raise ProgrammingError('labels must be a list of labels or tuples '
|
||||
'([index], label, [value])') from e
|
||||
pat = re.compile(rf'([+-]?\d*\.?\d*) *({"|".join(self.PREFIXES)}){unit}$')
|
||||
try:
|
||||
# determine missing values from labels
|
||||
for label, idx in edict.items():
|
||||
if idx not in vdict:
|
||||
value, prefix = pat.match(label).groups()
|
||||
vdict[idx] = float(f'{value}e{self.PREFIXES[prefix]}')
|
||||
except (AttributeError, ValueError) as e:
|
||||
raise ProgrammingError(f"{label!r} has not the form '<float><prefix>{unit}'") from e
|
||||
try:
|
||||
enumtype = EnumType(**edict)
|
||||
except TypeError as e:
|
||||
raise ProgrammingError(str(e)) from e
|
||||
datatype = FloatRange(min(vdict.values()), max(vdict.values()), unit=unit)
|
||||
super().__init__(description, datatype, enumtype=enumtype, valuedict=vdict,
|
||||
readonly=readonly, **kwds)
|
||||
|
||||
def __set_name__(self, owner, name):
|
||||
super().__set_name__(owner, name)
|
||||
if not self.idx_name:
|
||||
self.idx_name = name + '_idx'
|
||||
iname = self.idx_name
|
||||
idx_param = Parameter(f'index of {name}', self.enumtype,
|
||||
readonly=self.readonly, influences={name})
|
||||
idx_param.init({})
|
||||
setattr(owner, iname, idx_param)
|
||||
idx_param.__set_name__(owner, iname)
|
||||
|
||||
self.setProperty('influences', {iname})
|
||||
|
||||
if not hasattr(owner, f'write_{name}'):
|
||||
|
||||
# customization (like rounding up or down) might be
|
||||
# achieved by adding write_<name>. if not, the default
|
||||
# is rounding to the closest value
|
||||
|
||||
def wfunc(mobj, value, vdict=self.valuedict, fname=name, wfunc_iname=f'write_{iname}'):
|
||||
getattr(mobj, wfunc_iname)(
|
||||
min(vdict, key=lambda i: abs(vdict[i] - value)))
|
||||
return getattr(mobj, fname)
|
||||
|
||||
setattr(owner, f'write_{name}', wfunc)
|
||||
|
||||
def __get__(self, instance, owner):
|
||||
"""getter for value"""
|
||||
if instance is None:
|
||||
return self
|
||||
return self.valuedict[instance.parameters[self.idx_name].value]
|
||||
|
||||
def trigger_setter(self, modobj, _):
|
||||
# trigger update of float parameter on change of enum parameter
|
||||
modobj.announceUpdate(self.name, getattr(modobj, self.name))
|
||||
|
||||
def finish(self, modobj=None):
|
||||
"""register callbacks for consistency"""
|
||||
super().finish(modobj)
|
||||
if modobj:
|
||||
modobj.addCallback(self.idx_name, self.trigger_setter, modobj)
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user