Compare commits

...

57 Commits
wip ... sinq

Author SHA1 Message Date
6d63c4e0df frappy_psi.phytron: improve clear_errors message 2025-03-20 12:53:15 +01:00
98fa19ce3b WIP new version of ultrasound
Change-Id: Iadb83396a64e277f6f0a37f7a96d92105648c4fe
2025-02-13 09:39:49 +01:00
7f83f76d38 frappy_demo.test: add parameter for testing error messages
Change-Id: Ifbf9d6829be373417d3bf1ff398d2aee283d8c9a
2025-02-13 09:39:49 +01:00
0ab849d0cf config: do not override equipment_id with name
In the previous code, the equipment_id was overridden by the
server name when the interface argument was given over
the commandline. This was leftover from the previous config
file format, where the config files not neccessarly needed
an equipment_id.

Change-Id: I2fc248372a7d2f61cc0690804268d6d066a0a9fa
Reviewed-on: https://forge.frm2.tum.de/review/c/secop/frappy/+/35391
Tested-by: Jenkins Automated Tests <pedersen+jenkins@frm2.tum.de>
Reviewed-by: Markus Zolliker <markus.zolliker@psi.ch>
2025-02-13 09:39:49 +01:00
8ee49caba5 equipment_id for merged configs and routed nodes
Add a new custom module property 'original_id' indicating
the equipment_id the modules originally belongs to.
This property is only given, when distinct from the equipment_id
of the SEC node.
It happens when multiple config files are given, for all modules
but the ones given in the first file, and for routed modules,
when  multiple nodes are routed or own modules are given.

+ fix an issue in router: additional modules were ignore in case
of a single node.

+ small cosmetic changes in config.py reducing IDE complains

Change-Id: If846c47a06158629cef807d22b91f69e4f416563
Reviewed-on: https://forge.frm2.tum.de/review/c/secop/frappy/+/35396
Tested-by: Jenkins Automated Tests <pedersen+jenkins@frm2.tum.de>
Reviewed-by: Markus Zolliker <markus.zolliker@psi.ch>
2025-02-13 09:39:49 +01:00
b1de9218bd take over changes from ultrasound PC
Change-Id: I1eae717a5963e618d87ddf52db991d428a046d24
2025-02-13 09:39:49 +01:00
8eaad86b66 WIP: old oxford devices (ILM, IPS, IGH...)
Change-Id: I4ca0dc6149d257818d300db4d886a1e33e8210be
2025-02-13 09:39:49 +01:00
85400a2777 move start_ramp_to_target to SimpleMagfiield
Change-Id: Iab3fe8738c560bf5ac2f11a4a34428a8ffd6a7c2
2025-02-13 09:39:49 +01:00
dda4afbe5b frappy_psi.ccu4: some smaller updates
Change-Id: I128ac57aad951fd8ad3bdf663c69c85644063645
2025-02-13 09:39:49 +01:00
9b079ddf4b make UPD listener work when 'tcp://' is omitted on interface
'tcp://' may be omitted on interfaces
add missing 'tcp://' earlier in code, so we do not need to check
for missing 'tcp://' again.

Change-Id: Ie9b4dbd168aebdb6edfe71dbd2cfc25e9229fe67
Reviewed-on: https://forge.frm2.tum.de/review/c/secop/frappy/+/35321
Reviewed-by: Georg Brandl <g.brandl@fz-juelich.de>
Reviewed-by: Markus Zolliker <markus.zolliker@psi.ch>
Tested-by: Jenkins Automated Tests <pedersen+jenkins@frm2.tum.de>
2025-02-13 09:39:49 +01:00
898da75b89 fix bug in change 35001 (better error message)
fix bug in error message

Change-Id: I8151d20f840818fc26d42348f73e740cdb20e03d
Reviewed-on: https://forge.frm2.tum.de/review/c/secop/frappy/+/35287
Tested-by: Jenkins Automated Tests <pedersen+jenkins@frm2.tum.de>
Reviewed-by: Markus Zolliker <markus.zolliker@psi.ch>
2025-02-13 09:39:49 +01:00
a7a846dfba frappy_psi.sea: fix case when bool is implemented as text
introduce SeaBool for this

Change-Id: I9c6b6ee7d33f11b173d612efc044fce8a563f626
2025-02-13 09:39:49 +01:00
6da671df62 an error on a write must not send an error update
Change-Id: I07a991bcf26e87121160a2e604f8842eba23ebaf
Reviewed-on: https://forge.frm2.tum.de/review/c/secop/frappy/+/35281
Tested-by: Jenkins Automated Tests <pedersen+jenkins@frm2.tum.de>
Reviewed-by: Markus Zolliker <markus.zolliker@psi.ch>
2025-02-13 09:39:49 +01:00
bdb14af4af pylint: increase max number of positional arguments
Change-Id: Id88270b3c3c1efb56f47def733c1e9c745f1ab18
Reviewed-on: https://forge.frm2.tum.de/review/c/secop/frappy/+/35282
Reviewed-by: Markus Zolliker <markus.zolliker@psi.ch>
Tested-by: Jenkins Automated Tests <pedersen+jenkins@frm2.tum.de>
Reviewed-by: Georg Brandl <g.brandl@fz-juelich.de>
2025-02-13 09:39:11 +01:00
e57ad9826e better message when a parameter is overridden by an invalid value
happens e.g. then writing status = StatusType(...) instead of
status = Parameter(datatype=StatusType(...)) on the class level

+ improve doc strings

Change-Id: I05a0b0b0da4438a40b525da40018bb5b09fd5303
Reviewed-on: https://forge.frm2.tum.de/review/c/secop/frappy/+/35001
Tested-by: Jenkins Automated Tests <pedersen+jenkins@frm2.tum.de>
Reviewed-by: Markus Zolliker <markus.zolliker@psi.ch>
2025-02-13 09:39:11 +01:00
8775103bf8 frappy_psi.pulse: fix fatal errors
even when a module is work in progress, it should not raise
an error on import

Change-Id: I2f91301ba2b0c574ea344c36a74da0f893aa326d
2025-02-13 09:39:11 +01:00
l_samenv
5636a76152 adapt temperature and temperature_regulation importance
- temperature_regulation on VTI should have higher importance (27)
  than temperature on sample stick, when Drivable (25)
2025-02-13 09:39:11 +01:00
l_samenv
745cc69e9e sea cfg: set visibility of calibration points to expert 2025-02-13 09:39:11 +01:00
l_samenv
b4c0a827f0 ma7: use new config type with sea_path and frappy.sea.LscDrivable 2025-02-13 09:39:11 +01:00
d57416a73e frappy_psi.sea: more improvements
- add sea_path property
- add LscDrivable (config of these modules is easier to understand)

Change-Id: I616dc94de6e784f6d8cfcf080d9a8408cbf73d93
2025-02-13 09:39:11 +01:00
l_samenv
8dcf6ca658 sea: fix parameter name mapping
- rel_path = ['tm', '.', 'set'] should mean:

'tm': tm parameters first, with /<obj?/tm as main value
'.': then all parameters directly at top level, except 'set'
'set': all parameters  below 'set'
driving happens at object level

- better name mangling (the 1st appearance of the same shortname
  is kept short)
2025-02-13 09:39:11 +01:00
bc66a314c4 logdif.py: leave on every input except bare return
Change-Id: I3d53c7b45fb9ef09a61be5af13a2cdc4d32d5c7d
2025-02-13 09:38:20 +01:00
6fac63d769 frappy.server: remove comment about opts in SecNode/Dispatcher
The options given in the node configuration may be used
for both SecNode (equipment_id) and Dispatcher (when the
frappy.protocol.router.Router is used as dispatcher).
It is correct that both remove the options known to them.

Change-Id: I2a34073e4e5490dcf8db577d9cb74788c0cb657b
Reviewed-on: https://forge.frm2.tum.de/review/c/secop/frappy/+/34989
Tested-by: Jenkins Automated Tests <pedersen+jenkins@frm2.tum.de>
Reviewed-by: Markus Zolliker <markus.zolliker@psi.ch>
2024-11-28 18:04:26 +01:00
e41692bf2c frappy.server: use server name for SecNode name
no need to configure the name of SecNode and Dispatcher

Change-Id: I5199bbd77c74e4fe56b527a5a565a8285b0d831e
Reviewed-on: https://forge.frm2.tum.de/review/c/secop/frappy/+/34988
Reviewed-by: Markus Zolliker <markus.zolliker@psi.ch>
Tested-by: Jenkins Automated Tests <pedersen+jenkins@frm2.tum.de>
2024-11-28 18:04:26 +01:00
dff3bd2f24 frappy.server bug fix: server name must not be a list
followup error from change 34893
this bug appears in HasComlog, only when comlog is switched on

Change-Id: Ic0db5ae0b0af9981b0c91ebacf2eb6cd704aaa58
Reviewed-on: https://forge.frm2.tum.de/review/c/secop/frappy/+/34987
Tested-by: Jenkins Automated Tests <pedersen+jenkins@frm2.tum.de>
Reviewed-by: Markus Zolliker <markus.zolliker@psi.ch>
2024-11-28 18:04:26 +01:00
b67e5a9260 updated sync_branches for sinq branch
Change-Id: Ic3330c4049b527dc98704fbbd94180dcd4930cb1
2024-11-28 18:04:26 +01:00
4815f4e6b4 follow up change for 'better order of accessibles' (34904)
slight change to make it compatible with py 3.6/3.7, where
reversed(<dict>) was not allowed.

Change-Id: Id440870b5523a866b3afb470ba5db9cd6a9bb0ec
Reviewed-on: https://forge.frm2.tum.de/review/c/secop/frappy/+/35002
Tested-by: Jenkins Automated Tests <pedersen+jenkins@frm2.tum.de>
Reviewed-by: Markus Zolliker <markus.zolliker@psi.ch>
2024-11-28 18:04:26 +01:00
e8ec9b415a improve lakeshore demo
use super call for read_status

TODO: update tutorial!
Change-Id: I2dd5631908dc370c6e6286587099e25a0e5ee867
2024-11-28 18:03:37 +01:00
5b9e36180e frappy_psi.bkpower: improve doc
Change-Id: I0736d1d8a40b0140bfdbf5aca189b8ddc5b22973
2024-11-28 18:03:37 +01:00
f1b59e4150 fix bug when overriding a property with bare value
the bare value must be converted to a updated property.
add also a test for this

Change-Id: I261daaaa8e12d7f739d8b2e8389c1b871b26c5b3
Reviewed-on: https://forge.frm2.tum.de/review/c/secop/frappy/+/34985
Reviewed-by: Markus Zolliker <markus.zolliker@psi.ch>
Tested-by: Jenkins Automated Tests <pedersen+jenkins@frm2.tum.de>
2024-11-28 18:03:37 +01:00
17070ca732 add sim-server again based on socketserver
- fix ls370test config file
+ fix issues with frappy_psi.ls370res
+ add frappy_psi.ls370sim

Change-Id: Ie61e3ea01c4b9c7c1286426504e50acf9413a8ba
Reviewed-on: https://forge.frm2.tum.de/review/c/secop/frappy/+/34957
Reviewed-by: Markus Zolliker <markus.zolliker@psi.ch>
Tested-by: Jenkins Automated Tests <pedersen+jenkins@frm2.tum.de>
2024-11-28 18:03:37 +01:00
Jenkins system
d618fafe4b [deb] Release v0.20.4 2024-11-28 18:03:37 +01:00
Jens Krüger
dd1dfb3094 Lib/config: Create a list of pathes only for confdir
Under some condition (no general config file) it's possible that the
piddir and logdir as well are lists of pathes which creates some errors
during the server start

This problems occurs at least in NICOS test suite where no general
config file is defined.

Change-Id: I94c5db927923834c1546dbc34e2490b07b0bf111
Reviewed-on: https://forge.frm2.tum.de/review/c/secop/frappy/+/34952
Reviewed-by: Markus Zolliker <markus.zolliker@psi.ch>
Tested-by: Jenkins Automated Tests <pedersen+jenkins@frm2.tum.de>
Reviewed-by: Jens Krueger <jens.krueger@tum.de>
2024-11-28 18:03:37 +01:00
8d6617e288 frappy.client: catch all errors in handleError callback
put try/execpt around handleError callback

Change-Id: I3d97f085556665189da848e52a7148248f55eb0e
Reviewed-on: https://forge.frm2.tum.de/review/c/secop/frappy/+/34955
Tested-by: Jenkins Automated Tests <pedersen+jenkins@frm2.tum.de>
Reviewed-by: Markus Zolliker <markus.zolliker@psi.ch>
2024-11-28 18:03:37 +01:00
Jens Krüger
fdec531c99 PSI: Fix import error on ThermoFisher module
Change-Id: I691d8f5057fdb19ba14c109399417a7ee9962637
Reviewed-on: https://forge.frm2.tum.de/review/c/secop/frappy/+/34954
Tested-by: Jenkins Automated Tests <pedersen+jenkins@frm2.tum.de>
Reviewed-by: Markus Zolliker <markus.zolliker@psi.ch>
2024-11-28 18:03:37 +01:00
a246584c4a frappy.lib.multievent: avoid deadlock
use RLock instead of Lock, as queued actions might call
the set/clear methods recursively

Change-Id: Id43aa8669955e6be9f61379d039a4f65eb7b2dc4
Reviewed-on: https://forge.frm2.tum.de/review/c/secop/frappy/+/34950
Reviewed-by: Markus Zolliker <markus.zolliker@psi.ch>
Tested-by: Jenkins Automated Tests <pedersen+jenkins@frm2.tum.de>
2024-11-28 18:03:37 +01:00
Georg Brandl
00ef174292 remove unused file
Change-Id: I969bfb22f2196227abe8c5ecef628a15e6eb75f1
Reviewed-on: https://forge.frm2.tum.de/review/c/secop/frappy/+/34939
Reviewed-by: Georg Brandl <g.brandl@fz-juelich.de>
Tested-by: Jenkins Automated Tests <pedersen+jenkins@frm2.tum.de>
2024-11-28 18:03:37 +01:00
Jenkins system
ada66f4851 [deb] Release v0.20.3 2024-11-28 18:03:37 +01:00
Alexander Zaft
a9be6475b1 add generalConfig to etc
Change-Id: I768b136c803d5e197e3653d1b84e147b62a97676
Reviewed-on: https://forge.frm2.tum.de/review/c/secop/frappy/+/34924
Tested-by: Jenkins Automated Tests <pedersen+jenkins@frm2.tum.de>
Reviewed-by: Alexander Zaft <a.zaft@fz-juelich.de>
2024-11-28 18:03:37 +01:00
Georg Brandl
f380289a84 fixup test for cfg_editor utils to run from non-checkout, and fix names, and remove example code
Change-Id: I6224244392e2a2d0928065ba24abcbe822096084
Reviewed-on: https://forge.frm2.tum.de/review/c/secop/frappy/+/34934
Tested-by: Jenkins Automated Tests <pedersen+jenkins@frm2.tum.de>
Reviewed-by: Alexander Zaft <a.zaft@fz-juelich.de>
2024-11-28 18:03:37 +01:00
Jenkins system
528d80652c [deb] Release v0.20.2 2024-11-28 18:03:37 +01:00
Alexander Zaft
7c6df58906 fix frappy-server cfgfiles command
frappy-server <name> errors after 34893

Change-Id: Ifba758fbabc3aef32e20b683f1c1edbfea711a75
Reviewed-on: https://forge.frm2.tum.de/review/c/secop/frappy/+/34913
Tested-by: Jenkins Automated Tests <pedersen+jenkins@frm2.tum.de>
Reviewed-by: Georg Brandl <g.brandl@fz-juelich.de>
2024-11-28 18:03:37 +01:00
Georg Brandl
1851c0ac43 server: better handling of cfgfile argument
No reason to keep stringly-typed data on that level

Change-Id: Iba8d88301bf36ef6051031d1916d1bac84ede546
Reviewed-on: https://forge.frm2.tum.de/review/c/secop/frappy/+/34893
Reviewed-by: Georg Brandl <g.brandl@fz-juelich.de>
Tested-by: Jenkins Automated Tests <pedersen+jenkins@frm2.tum.de>
Reviewed-by: Alexander Zaft <a.zaft@fz-juelich.de>
Reviewed-by: Enrico Faulhaber <enrico.faulhaber@frm2.tum.de>
2024-11-28 18:03:37 +01:00
880d472a4a generalConfig: fix the case when confdir is a list of paths
convert all env variable values containing ':' into a list of paths
+ fix one case where an env variable is not converted to a Path
+ remove unused _gcfg_help

Change-Id: Ibc51ab4606ca51e0e87d0fedfac1aca4952f3270
Reviewed-on: https://forge.frm2.tum.de/review/c/secop/frappy/+/34872
Tested-by: Jenkins Automated Tests <pedersen+jenkins@frm2.tum.de>
Reviewed-by: Enrico Faulhaber <enrico.faulhaber@frm2.tum.de>
Reviewed-by: Markus Zolliker <markus.zolliker@psi.ch>
2024-11-28 18:03:37 +01:00
Alexander Zaft
25ff96873b server: service discovery over UDP.
implement RFC-005
- server broadcasts once on startup and answers to broadcasts
- small tool for listening on the port and sending broadcasts

Change-Id: I02d1184d6be62bef6f964eb9d238220aef062e94
Reviewed-on: https://forge.frm2.tum.de/review/c/secop/frappy/+/34851
Tested-by: Jenkins Automated Tests <pedersen+jenkins@frm2.tum.de>
Reviewed-by: Georg Brandl <g.brandl@fz-juelich.de>
2024-11-28 18:03:37 +01:00
Georg Brandl
82881049c4 systemd: enable indication of reloading/stopping
Change-Id: I6dd1b3a50234fb0304fb1a5318f2f22d35d464ec
Reviewed-on: https://forge.frm2.tum.de/review/c/secop/frappy/+/34896
Tested-by: Jenkins Automated Tests <pedersen+jenkins@frm2.tum.de>
Reviewed-by: Enrico Faulhaber <enrico.faulhaber@frm2.tum.de>
Reviewed-by: Georg Brandl <g.brandl@fz-juelich.de>
2024-11-28 18:03:37 +01:00
Alexander Zaft
60c9737cfe server: fix windows ctrl-c
thread.join() blocks indefinetely, not allowing python to handle the
interrupt. Same is true for sleep on windows, but when we only sleep a
second, this is fine. Instead of joining the threads, keep track of them
manually.

Change-Id: I559fe06d9ce005a15388c881e4f076d996aea9dc
Reviewed-on: https://forge.frm2.tum.de/review/c/secop/frappy/+/34894
Reviewed-by: Markus Zolliker <markus.zolliker@psi.ch>
Reviewed-by: Alexander Zaft <a.zaft@fz-juelich.de>
Reviewed-by: Georg Brandl <g.brandl@fz-juelich.de>
Reviewed-by: Enrico Faulhaber <enrico.faulhaber@frm2.tum.de>
Tested-by: Jenkins Automated Tests <pedersen+jenkins@frm2.tum.de>
2024-11-28 18:03:37 +01:00
Alexander Zaft
632db924eb generalconfig: streamlined config discovery
determine generalconfig file location in order:
  - command line argument
  - environment variable
  - git location (../cfg)
  - local location (cwd)
  - global location (/etc/frappy)

Change-Id: Ie34bcbd5188837075ee7bb7d5029d676ae72378e
Reviewed-on: https://forge.frm2.tum.de/review/c/secop/frappy/+/34839
Reviewed-by: Bjoern Pedersen <bjoern.pedersen@frm2.tum.de>
Reviewed-by: Alexander Zaft <a.zaft@fz-juelich.de>
Tested-by: Jenkins Automated Tests <pedersen+jenkins@frm2.tum.de>
2024-11-28 18:03:37 +01:00
Alexander Zaft
261121297b Revert "config: allow using Prop(...)"
This reverts commit ba59bd549860797f5bdf15cadfea539754d833cd.

Reason for revert: unnecessary

Change-Id: I4bf46a1de2e699049572f376e84fa39db5dae76c
Reviewed-on: https://forge.frm2.tum.de/review/c/secop/frappy/+/34888
Reviewed-by: Alexander Zaft <a.zaft@fz-juelich.de>
Tested-by: Jenkins Automated Tests <pedersen+jenkins@frm2.tum.de>
2024-11-28 18:03:37 +01:00
Alexander Zaft
1bd243f3d2 config: fix typo
Change-Id: Ie90993d9b2d387780fa3faa28fd8d4523f7fc866
2024-11-28 18:03:37 +01:00
Alexander Zaft
7c3f9f7196 config: allow using Prop(...)
Still maps to the same logic, but it might be a bit confusing to
configure properties with prop = Param(...)

Change-Id: I6bde6a0b015095a8b765d98cb2780f0d42de7e6e
Reviewed-on: https://forge.frm2.tum.de/review/c/secop/frappy/+/34886
Tested-by: Jenkins Automated Tests <pedersen+jenkins@frm2.tum.de>
Reviewed-by: Alexander Zaft <a.zaft@fz-juelich.de>
2024-11-28 18:03:37 +01:00
9074dfda9d fix playground
- fix initialization
- add description

Change-Id: Ic210c26edfec709bafa902e32eae04350d571acd
Reviewed-on: https://forge.frm2.tum.de/review/c/secop/frappy/+/34874
Reviewed-by: Alexander Zaft <a.zaft@fz-juelich.de>
Reviewed-by: Georg Brandl <g.brandl@fz-juelich.de>
Reviewed-by: Markus Zolliker <markus.zolliker@psi.ch>
Tested-by: Jenkins Automated Tests <pedersen+jenkins@frm2.tum.de>
2024-11-28 18:03:37 +01:00
de32eb09e6 better order of accessibles: 'value' 'status' and 'target' first
- predefined parameters/commands appear first, in the order
  defined in frappy.params.PREDEFINED_ACCESSIBLES
- other (custom) parameters by inheritance order
- remove paramOrder attribute (not used currently)

Change-Id: If4c43189e4837dba057dc0a430ac6c3d1ae10829
Reviewed-on: https://forge.frm2.tum.de/review/c/secop/frappy/+/34904
Reviewed-by: Enrico Faulhaber <enrico.faulhaber@frm2.tum.de>
Reviewed-by: Markus Zolliker <markus.zolliker@psi.ch>
Tested-by: Jenkins Automated Tests <pedersen+jenkins@frm2.tum.de>
2024-11-28 18:03:37 +01:00
2e97f0f0ce frappy_psi.sea: bugfix: revert change of updateEvent to udpateItem
revert some of change 34813
SeaClient is based on ProxyClient, not SecopClient
-> updateItem is not defined there

Change-Id: Ib3049038481917ec7a11b9fb2d285cedff5febbb
Reviewed-on: https://forge.frm2.tum.de/review/c/secop/frappy/+/34873
Reviewed-by: Markus Zolliker <markus.zolliker@psi.ch>
Tested-by: Jenkins Automated Tests <pedersen+jenkins@frm2.tum.de>
2024-11-28 18:03:37 +01:00
0b06acf304 add ori2 2024-11-26 15:16:53 +01:00
dmc
cc90291358 varioxb: fix config, om not yet available 2024-11-26 13:59:05 +01:00
03b4604643 fix description of ts in ma11stick 2024-11-15 09:14:54 +01:00
91 changed files with 2653 additions and 1170 deletions

View File

@ -204,6 +204,9 @@ max-statements=150
# Maximum number of parents for a class (see R0901).
max-parents=20
# Maximum number of positional arguments
max-positional-arguments=10
# Maximum number of attributes for a class (see R0902).
max-attributes=50

View File

@ -23,6 +23,8 @@
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]))
@ -30,6 +32,8 @@ 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:

128
bin/frappy-scan Executable file
View 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)

View File

@ -35,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",
@ -60,9 +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',
@ -96,7 +104,9 @@ def main(argv=None):
generalConfig.init(args.gencfg)
logger.init(loglevel)
srv = Server(args.name, logger.log, cfgfiles=args.cfgfiles,
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:

View File

@ -22,22 +22,35 @@
Usage:
bin/stringio-server <communciator> <server port>
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:
- 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
> 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 asyncore
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]))
@ -45,92 +58,6 @@ sys.path.insert(0, str(Path(__file__).absolute().parents[1]))
from frappy.lib import get_class, formatException, mkthread
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_line(self, line):
raise NotImplementedError
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, port, line_handler_cls, handler_args):
asyncore.dispatcher.__init__(self)
self.create_socket(socket.AF_INET, socket.SOCK_STREAM)
self.set_reuse_addr()
self.bind(('0.0.0.0', port))
self.listen(5)
print('accept connections at port', port)
self.line_handler_cls = line_handler_cls
self.handler_args = handler_args
def handle_accept(self):
pair = self.accept()
if pair is not None:
sock, addr = pair
print("Incoming connection from %s" % repr(addr))
self.line_handler_cls(sock, self.handler_args)
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.secnode = None
self.dispatcher = self.Dispatcher()
class Handler(LineHandler):
def __init__(self, sock, handler_args):
super().__init__(sock)
self.module = handler_args['module']
self.verbose = handler_args['verbose']
def handle_line(self, line):
try:
reply = self.module.communicate(line.strip())
if self.verbose:
print('%-40s | %s' % (line, reply))
except Exception:
print(formatException(verbose=True))
return
self.send_line(reply)
class Logger:
def debug(self, *args):
pass
@ -144,43 +71,126 @@ class Logger:
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="Simulate HW with a serial interface")
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="simulator class.\n",)
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 poller(pollfunc):
while True:
time.sleep(1.0)
pollfunc()
def main(argv=None):
if argv is None:
argv = sys.argv
args = parse_argv(argv[1:])
opts = {'description': 'simulator'}
handler_args = {'verbose': args.verbose}
srv = Server(int(args.port), Handler, handler_args)
module = get_class(args.cls)(args.cls, Logger(), opts, srv)
handler_args['module'] = module
module.earlyInit()
mkthread(poller, module.doPoll)
srv.loop()
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__':

67
cfg/PEUS.py Normal file
View File

@ -0,0 +1,67 @@
Node(equipment_id = 'pe_ultrasound.psi.ch',
description = 'pulse echo ultra sound setup',
interface = 'tcp://5000',
)
Mod('f',
cls = 'frappy_psi.ultrasound.Frequency',
description = 'ultrasound frequency and acquisition loop',
uri = 'serial:///dev/ttyS1',
pars = 'pars',
pollinterval = 0.1,
time = 900, # start time
size = 5000,
freq = 1.17568e+06,
basefreq = 4.14902e+07,
control = False,
rusmode = False,
amp = 5.0,
nr = 1000, #500 #300 #100 #50 #30 #10 #5 #3 #1 #1000 #500 #300 #100 #50 #30 #10 #5 #3 #1 #500
sr = 32768, #16384
plot = True,
maxstep = 100000,
bw = 10E6, #butter worth filter bandwidth
maxy = 0.7, # y scale for plot
curves = 'curves', # module to transmit curves:
)
Mod('curves',
cls = 'frappy_psi.ultrasound.Curves',
description = 't, I, Q and pulse arrays for plot',
)
Mod('delay',
cls = 'frappy__psi.dg645.Delay',
description = 'delay line with 2 channels',
uri = 'serial:///dev/ttyS2',
on1 = 1e-9,
on2 = 1E-9,
off1 = 400e-9,
off2 = 600e-9,
)
Mod('pars',
cls = 'frappy_psi.ultrasound.Pars',
description = 'SEA parameters',
)
def roi(nr, time=None, size=300):
Mod(f'roi{nr}',
cls = 'frappy_psi.ultrasound.Roi',
description = f'I/Q of region {nr}',
main = 'f',
time=time or 4000,
size=size,
enable=time is not None,
)
roi(0, 2450) # you may add size as argument if not default
roi(1, 5950)
roi(2, 9475)
roi(3, 12900)
roi(4, 16100)
roi(5) # disabled
roi(6)
roi(7)
roi(8)
roi(9)

62
cfg/RUS.py Normal file
View File

@ -0,0 +1,62 @@
Node(equipment_id = 'r_ultrasound.psi.ch',
description = 'resonant ultra sound setup',
interface = 'tcp://5000',
)
Mod('f',
cls = 'frappy_psi.ultrasound.Frequency',
description = 'ultrasound frequency and acquisition loop',
uri = 'serial:///dev/ttyS1',
pars = 'pars',
pollinterval = 0.1,
time = 900, # start time
size = 5000,
freq = 1.e+03,
basefreq = 1.E+3,
control = False,
rusmode = False,
amp = 2.5,
nr = 1, #500 #300 #100 #50 #30 #10 #5 #3 #1 #1000 #500 #300 #100 #50 #30 #10 #5 #3 #1 #500
sr = 1E8, #16384
plot = True,
maxstep = 100000,
bw = 10E6, #butter worth filter bandwidth
maxy = 0.7, # y scale for plot
curves = 'curves', # module to transmit curves:
)
Mod('curves',
cls = 'frappy_psi.ultrasound.Curves',
description = 't, I, Q and pulse arrays for plot',
)
Mod('roi0',
cls = 'frappy_psi.ultrasound.Roi',
description = 'I/Q of region in the control loop',
time = 300, # this is the center of roi:
size = 5000,
main = f,
)
Mod('roi1',
cls = 'frappy_psi.ultrasound.Roi',
description = 'I/Q of region 1',
time = 100, # this is the center of roi:
size = 300,
main = f,
)
Mod('delay',
cls = 'frappy__psi.dg645.Delay',
description = 'delay line with 2 channels',
uri = 'serial:///dev/ttyS2',
on1 = 1e-9,
on2 = 1E-9,
off1 = 400e-9,
off2 = 600e-9,
)
Mod('pars',
cls = 'frappy_psi.ultrasound.Pars',
description = 'SEA parameters',
)

View File

@ -12,9 +12,9 @@ Mod('sea_main',
Mod('tt',
'frappy_psi.sea.SeaDrivable', '',
io='sea_main',
meaning=['temperature_regulation', 20],
meaning=['temperature_regulation', 27],
sea_object='tt',
rel_paths=['tm', 'set', 'dblctrl'],
rel_paths=['tm', '.', 'set', 'dblctrl'],
)
Mod('cc',

View File

@ -12,9 +12,9 @@ Mod('sea_main',
Mod('tt',
'frappy_psi.sea.SeaDrivable', '',
io='sea_main',
meaning=['temperature_regulation', 20],
meaning=['temperature_regulation', 27],
sea_object='tt',
rel_paths=['tm', 'set', 'dblctrl'],
rel_paths=['tm', '.', 'set', 'dblctrl'],
)
Mod('cc',

View File

@ -12,9 +12,9 @@ Mod('sea_main',
Mod('tt',
'frappy_psi.sea.SeaDrivable', '',
io='sea_main',
meaning=['temperature_regulation', 20],
meaning=['temperature_regulation', 27],
sea_object='tt',
rel_paths=['tm', 'set', 'dblctrl'],
rel_paths=['tm', '.', 'set', 'dblctrl'],
)
Mod('cc',

View File

@ -12,9 +12,9 @@ Mod('sea_main',
Mod('tt',
'frappy_psi.sea.SeaDrivable', '',
io='sea_main',
meaning=['temperature_regulation', 20],
meaning=['temperature_regulation', 27],
sea_object='tt',
rel_paths=['tm', 'set', 'dblctrl'],
rel_paths=['tm', '.', 'set', 'dblctrl'],
)
Mod('cc',

View File

@ -12,9 +12,9 @@ Mod('sea_main',
Mod('tt',
'frappy_psi.sea.SeaDrivable', '',
io='sea_main',
meaning=['temperature_regulation', 20],
meaning=['temperature_regulation', 27],
sea_object='tt',
rel_paths=['tm', 'set', 'dblctrl'],
rel_paths=['tm', '.', 'set', 'dblctrl'],
)
Mod('cc',

View File

@ -12,9 +12,9 @@ Mod('sea_main',
Mod('tt',
'frappy_psi.sea.SeaDrivable', '',
io='sea_main',
meaning=['temperature_regulation', 20],
meaning=['temperature_regulation', 27],
sea_object='tt',
rel_paths=['tm', 'set', 'dblctrl'],
rel_paths=['tm', '.', 'set', 'dblctrl'],
)
Mod('pauto',

View File

@ -12,7 +12,7 @@ Mod('sea_main',
Mod('tt',
'frappy_psi.sea.SeaDrivable', '',
io='sea_main',
meaning=['temperature_regulation', 20],
meaning=['temperature_regulation', 27],
sea_object='tt',
rel_paths=['tt', 'set'],
)

View File

@ -12,9 +12,9 @@ Mod('sea_main',
Mod('tt',
'frappy_psi.sea.SeaDrivable', '',
io='sea_main',
meaning=['temperature_regulation', 20],
meaning=['temperature_regulation', 27],
sea_object='tt',
rel_paths=['tm', 'set', 'dblctrl'],
rel_paths=['tm', '.', 'set', 'dblctrl'],
)
Mod('cc',

View File

@ -12,9 +12,9 @@ Mod('sea_main',
Mod('tt',
'frappy_psi.sea.SeaDrivable', '',
io='sea_main',
meaning=['temperature_regulation', 20],
meaning=['temperature_regulation', 27],
sea_object='tt',
rel_paths=['tm', 'set', 'dblctrl'],
rel_paths=['tm', '.', 'set', 'dblctrl'],
)
Mod('cc',

View File

@ -12,7 +12,7 @@ Mod('sea_main',
Mod('ts',
'frappy_psi.sea.SeaDrivable', '',
io='sea_main',
meaning=['temperature_regulation', 20],
meaning=['temperature_regulation', 27],
sea_object='tt',
rel_paths=['ts', 'set']
)

View File

@ -13,7 +13,7 @@ Mod('th',
'frappy_psi.sea.SeaReadable',
'sample heater temperature',
io='sea_main',
meaning=['temperature_regulation', 20],
meaning=['temperature_regulation', 27],
sea_object='tt',
rel_paths=['ts', 'setsamp']
)
@ -22,7 +22,7 @@ Mod('tm',
'frappy_psi.sea.SeaDrivable', '',
io='sea_main',
sea_object='tt',
rel_paths=['tm', 'set']
rel_paths=['tm', '.', 'set']
)
Mod('ts',

View File

@ -12,9 +12,9 @@ Mod('sea_main',
Mod('tt',
'frappy_psi.sea.SeaDrivable', '',
io='sea_main',
meaning=['temperature_regulation', 20],
meaning=['temperature_regulation', 27],
sea_object='tt',
rel_paths=['tm', 'set', 'dblctrl'],
rel_paths=['tm', '.', 'set', 'dblctrl'],
)
Mod('cc',

View File

@ -10,9 +10,9 @@ Mod('sea_main',
Mod('tt',
'frappy_psi.sea.SeaDrivable', '',
io = 'sea_main',
meaning=['temperature_regulation', 20],
meaning=['temperature_regulation', 27],
sea_object = 'tt',
rel_paths=['tm', 'set', 'dblctrl'],
rel_paths=['tm', '.', 'set', 'dblctrl'],
)
Mod('cc',
'frappy_psi.sea.SeaReadable', '',

View File

@ -12,9 +12,9 @@ Mod('sea_main',
Mod('tt',
'frappy_psi.sea.SeaDrivable', '',
io='sea_main',
meaning=['temperature_regulation', 20],
meaning=['temperature_regulation', 27],
sea_object='tt',
rel_paths=['tm', 'set', 'dblctrl'],
rel_paths=['tm', '.', 'set', 'dblctrl'],
)
Mod('cc',

View File

@ -12,9 +12,9 @@ Mod('sea_main',
Mod('tt',
'frappy_psi.sea.SeaDrivable', '',
io='sea_main',
meaning=['temperature_regulation', 20],
meaning=['temperature_regulation', 27],
sea_object='tt',
rel_paths=['tm', 'set', 'dblctrl'],
rel_paths=['tm', '.', 'set', 'dblctrl'],
)
Mod('th',

View File

@ -10,11 +10,12 @@ Mod('sea_main',
)
Mod('tt',
'frappy_psi.sea.SeaDrivable', '',
'frappy_psi.sea.LscDrivable', '',
io='sea_main',
meaning=['temperature_regulation', 20],
meaning=['temperature_regulation', 27],
sea_object='tt',
rel_paths=['tm', 'set', 'dblctrl'],
sensor_path='tm',
set_path='set',
)
Mod('cc',
@ -69,15 +70,13 @@ Mod('lev',
Mod('tcoil1',
'frappy_psi.sea.SeaReadable', '',
io='sea_main',
sea_object='tcoil',
rel_paths=['ta'],
sea_path='tcoil/ta',
)
Mod('tcoil2',
'frappy_psi.sea.SeaReadable', '',
io='sea_main',
sea_object='tcoil',
rel_paths=['tb'],
sea_path='tcoil/tb',
)
Mod('table',

View File

@ -12,9 +12,9 @@ Mod('sea_main',
Mod('tt',
'frappy_psi.sea.SeaDrivable', '',
io='sea_main',
meaning=['temperature_regulation', 20],
meaning=['temperature_regulation', 27],
sea_object='tt',
rel_paths=['tm', 'set', 'dblctrl', 'voltage'],
rel_paths=['tm', '.', 'set', 'dblctrl', 'voltage'],
extra_modules=['manualpower'],
)

View File

@ -12,9 +12,9 @@ Mod('sea_main',
Mod('tt',
'frappy_psi.sea.SeaDrivable', '',
io='sea_main',
meaning=['temperature_regulation', 20],
meaning=['temperature_regulation', 27],
sea_object='tt',
rel_paths=['tm', 'set', 'dblctrl'],
rel_paths=['tm', '.', 'set', 'dblctrl'],
)
Mod('th',

View File

@ -16,10 +16,10 @@ Mod('sea_main',
Mod('tt',
'frappy_psi.sea.SeaDrivable', '',
meaning=['temperature_regulation', 20],
meaning=['temperature_regulation', 27],
io='sea_main',
sea_object='tt',
rel_paths=['tm', 'set', 'dblctrl'],
rel_paths=['tm', '.', 'set', 'dblctrl'],
)
Mod('th',

View File

@ -12,9 +12,9 @@ Mod('sea_main',
Mod('tt',
'frappy_psi.sea.SeaDrivable', '',
io='sea_main',
meaning=['temperature_regulation', 20],
meaning=['temperature_regulation', 27],
sea_object='tt',
rel_paths=['tm', 'set', 'dblctrl'],
rel_paths=['tm', '.', 'set', 'dblctrl'],
)
Mod('cc',

View File

@ -25,7 +25,7 @@ Mod('ips',
Mod('T_stat',
'frappy_psi.mercury.TemperatureAutoFlow',
'static heat exchanger temperature',
meaning=['temperature_regulation', 20],
meaning=['temperature_regulation', 27],
output_module='htr_stat',
needle_valve='p_stat',
slot='DB6.T1',

View File

@ -23,7 +23,7 @@ Mod('ips',
Mod('T_stat',
'frappy_psi.mercury.TemperatureAutoFlow',
'static heat exchanger temperature',
meaning=['temperature_regulation', 20],
meaning=['temperature_regulation', 27],
output_module='htr_stat',
needle_valve='p_stat',
slot='DB6.T1',

73
cfg/main/ori2_cfg.py Normal file
View File

@ -0,0 +1,73 @@
Node('ori3.config.sea.psi.ch',
'orange cryostat with 50 mm sample space',
)
Mod('sea_main',
'frappy_psi.sea.SeaClient',
'main sea connection for ori2.config',
config='ori2.config',
service='main',
)
Mod('tt',
'frappy_psi.sea.SeaDrivable', '',
io='sea_main',
meaning=['temperature_regulation', 27],
sea_object='tt',
rel_paths=['tm', '.', 'set', 'dblctrl'],
)
Mod('cc',
'frappy_psi.sea.SeaReadable', '',
io='sea_main',
sea_object='cc',
extra_modules=['h'],
)
Mod('lev',
'frappy_psi.sea.SeaReadable', '',
io='sea_main',
single_module='cc.h',
)
Mod('nv',
'frappy_psi.sea.SeaWritable', '',
io='sea_main',
sea_object='nv',
)
Mod('ln2fill',
'frappy_psi.sea.SeaWritable', '',
io='sea_main',
sea_object='ln2fill',
)
Mod('hefill',
'frappy_psi.sea.SeaWritable', '',
io='sea_main',
sea_object='hefill',
)
Mod('hepump',
'frappy_psi.sea.SeaWritable', '',
io='sea_main',
sea_object='hepump',
)
Mod('hemot',
'frappy_psi.sea.SeaDrivable', '',
io='sea_main',
sea_object='hemot',
)
Mod('nvflow',
'frappy_psi.sea.SeaReadable', '',
io='sea_main',
sea_object='nvflow',
)
Mod('table',
'frappy_psi.sea.SeaReadable', '',
io='sea_main',
sea_object='table',
)

View File

@ -12,9 +12,9 @@ Mod('sea_main',
Mod('tt',
'frappy_psi.sea.SeaDrivable', '',
io='sea_main',
meaning=['temperature_regulation', 20],
meaning=['temperature_regulation', 27],
sea_object='tt',
rel_paths=['tm', 'set', 'dblctrl'],
rel_paths=['tm', '.', 'set', 'dblctrl'],
)
Mod('cc',

View File

@ -12,9 +12,9 @@ Mod('sea_main',
Mod('tt',
'frappy_psi.sea.SeaDrivable', '',
io='sea_main',
meaning=['temperature_regulation', 20],
meaning=['temperature_regulation', 27],
sea_object='tt',
rel_paths=['tm', 'set', 'dblctrl'],
rel_paths=['tm', '.', 'set', 'dblctrl'],
)
Mod('cc',

View File

@ -12,9 +12,9 @@ Mod('sea_main',
Mod('tt',
'frappy_psi.sea.SeaDrivable', '',
io='sea_main',
meaning=['temperature_regulation', 20],
meaning=['temperature_regulation', 27],
sea_object='tt',
rel_paths=['tm', 'set', 'dblctrl'],
rel_paths=['tm', '.', 'set', 'dblctrl'],
)
Mod('cc',

View File

@ -12,9 +12,9 @@ Mod('sea_main',
Mod('tt',
'frappy_psi.sea.SeaDrivable', '',
io = 'sea_main',
meaning=['temperature_regulation', 20],
meaning=['temperature_regulation', 27],
sea_object = 'tt',
rel_paths=['tm', 'set', 'dblctrl'],
rel_paths=['tm', '.', 'set', 'dblctrl'],
)
Mod('cc',

View File

@ -12,9 +12,9 @@ Mod('sea_main',
Mod('tt',
'frappy_psi.sea.SeaDrivable', '',
io='sea_main',
meaning=['temperature_regulation', 20],
meaning=['temperature_regulation', 27],
sea_object='tt',
rel_paths=['tm', 'set', 'dblctrl'],
rel_paths=['tm', '.', 'set', 'dblctrl'],
)
Mod('cc',

View File

@ -18,7 +18,7 @@ Mod('itc2',
Mod('T_stat',
'frappy_psi.mercury.TemperatureAutoFlow',
'static heat exchanger temperature',
meaning=['temperature_regulation', 20],
meaning=['temperature_regulation', 27],
output_module='htr_stat',
needle_valve='p_stat',
slot='DB6.T1',
@ -168,6 +168,8 @@ Mod('htr_nvd',
io='itc2',
)
# Motor controller is not yet available!
#
#Mod('om_io',
# 'frappy_psi.phytron.PhytronIO',
# 'dom motor IO',

View File

@ -18,22 +18,22 @@
{"path": "t1", "type": "float", "readonly": false, "cmd": "run tt", "kids": 3},
{"path": "t1/raw", "type": "float", "readonly": false, "cmd": "run tt"},
{"path": "t1/curve", "type": "text", "readonly": false, "cmd": "tt t1/curve", "kids": 1},
{"path": "t1/curve/points", "type": "floatvarar", "readonly": false, "cmd": "tt t1/curve/points"},
{"path": "t1/curve/points", "type": "floatvarar", "readonly": false, "cmd": "tt t1/curve/points", "visibility": 3},
{"path": "t1/valid", "type": "bool", "readonly": false, "cmd": "run tt"},
{"path": "t2", "type": "float", "readonly": false, "cmd": "run tt", "kids": 3},
{"path": "t2/raw", "type": "float", "readonly": false, "cmd": "run tt"},
{"path": "t2/curve", "type": "text", "readonly": false, "cmd": "tt t2/curve", "kids": 1},
{"path": "t2/curve/points", "type": "floatvarar", "readonly": false, "cmd": "tt t2/curve/points"},
{"path": "t2/curve/points", "type": "floatvarar", "readonly": false, "cmd": "tt t2/curve/points", "visibility": 3},
{"path": "t2/valid", "type": "bool", "readonly": false, "cmd": "run tt"},
{"path": "t3", "type": "float", "readonly": false, "cmd": "run tt", "kids": 3},
{"path": "t3/raw", "type": "float", "readonly": false, "cmd": "run tt"},
{"path": "t3/curve", "type": "text", "readonly": false, "cmd": "tt t3/curve", "kids": 1},
{"path": "t3/curve/points", "type": "floatvarar", "readonly": false, "cmd": "tt t3/curve/points"},
{"path": "t3/curve/points", "type": "floatvarar", "readonly": false, "cmd": "tt t3/curve/points", "visibility": 3},
{"path": "t3/valid", "type": "bool", "readonly": false, "cmd": "run tt"},
{"path": "t4", "type": "float", "readonly": false, "cmd": "run tt", "kids": 3},
{"path": "t4/raw", "type": "float", "readonly": false, "cmd": "run tt"},
{"path": "t4/curve", "type": "text", "readonly": false, "cmd": "tt t4/curve", "kids": 1},
{"path": "t4/curve/points", "type": "floatvarar", "readonly": false, "cmd": "tt t4/curve/points"},
{"path": "t4/curve/points", "type": "floatvarar", "readonly": false, "cmd": "tt t4/curve/points", "visibility": 3},
{"path": "t4/valid", "type": "bool", "readonly": false, "cmd": "run tt"},
{"path": "tref", "type": "float", "readonly": false, "cmd": "run tt"},
{"path": "tout", "type": "float", "readonly": false, "cmd": "run tt"},

View File

@ -269,7 +269,7 @@
{"path": "custompar", "type": "float", "readonly": false, "cmd": "hemot custompar"}]},
"mf": {"base": "/mf", "params": [
{"path": "", "type": "float", "kids": 26},
{"path": "", "type": "float", "cmd": "run mf", "kids": 26},
{"path": "persmode", "type": "int", "readonly": false, "cmd": "mf persmode"},
{"path": "perswitch", "type": "int"},
{"path": "nowait", "type": "int", "readonly": false, "cmd": "mf nowait"},

View File

@ -1,4 +1,5 @@
{"tt": {"base": "/tt", "params": [{"path": "", "type": "float", "readonly": false, "cmd": "run tt", "description": "tt", "kids": 18},
{"tt": {"base": "/tt", "params": [
{"path": "", "type": "float", "readonly": false, "cmd": "run tt", "description": "tt", "kids": 18},
{"path": "send", "type": "text", "readonly": false, "cmd": "tt send", "visibility": 3},
{"path": "status", "type": "text", "visibility": 3},
{"path": "is_running", "type": "int", "readonly": false, "cmd": "tt is_running", "visibility": 3},
@ -85,7 +86,10 @@
{"path": "setsamp/integ", "type": "float", "readonly": false, "cmd": "tt setsamp/integ", "description": "bigger means faster"},
{"path": "setsamp/deriv", "type": "float", "readonly": false, "cmd": "tt setsamp/deriv"},
{"path": "display", "type": "text", "readonly": false, "cmd": "tt display"},
{"path": "remote", "type": "bool"}]}, "cc": {"base": "/cc", "params": [{"path": "", "type": "bool", "kids": 96},
{"path": "remote", "type": "bool"}]},
"cc": {"base": "/cc", "params": [
{"path": "", "type": "bool", "kids": 96},
{"path": "send", "type": "text", "readonly": false, "cmd": "cc send", "visibility": 3},
{"path": "status", "type": "text", "visibility": 3},
{"path": "autodevice", "type": "bool", "readonly": false, "cmd": "cc autodevice"},
@ -181,7 +185,10 @@
{"path": "tm", "type": "float", "visibility": 3},
{"path": "tv", "type": "float", "visibility": 3},
{"path": "tq", "type": "float", "visibility": 3},
{"path": "bdl", "type": "float", "readonly": false, "cmd": "cc bdl"}]}, "nv": {"base": "/nv", "params": [{"path": "", "type": "enum", "enum": {"fixed": 0, "controlled": 1, "automatic": 2, "close": 3, "open": 4}, "readonly": false, "cmd": "nv", "kids": 11},
{"path": "bdl", "type": "float", "readonly": false, "cmd": "cc bdl"}]},
"nv": {"base": "/nv", "params": [
{"path": "", "type": "enum", "enum": {"fixed": 0, "controlled": 1, "automatic": 2, "close": 3, "open": 4}, "readonly": false, "cmd": "nv", "kids": 11},
{"path": "send", "type": "text", "readonly": false, "cmd": "nv send", "visibility": 3},
{"path": "status", "type": "text", "visibility": 3},
{"path": "motstat", "type": "enum", "enum": {"idle": 0, "opening": 1, "closing": 2, "opened": 3, "closed": 4, "no_motor": 5}},
@ -231,7 +238,11 @@
{"path": "autoflow/flowtarget", "type": "float"},
{"path": "calib", "type": "none", "kids": 2},
{"path": "calib/ln_per_min_per_mbar", "type": "float", "readonly": false, "cmd": "nv calib/ln_per_min_per_mbar"},
{"path": "calib/mbar_offset", "type": "float", "readonly": false, "cmd": "nv calib/mbar_offset"}]}, "hepump": {"base": "/hepump", "params": [{"path": "", "type": "enum", "enum": {"neodry": 8, "xds35_auto": 0, "xds35_manual": 1, "sv65": 2, "other": 3, "no": -1}, "readonly": false, "cmd": "hepump", "description": "xds35: scroll pump, sv65: leybold", "kids": 9},
{"path": "calib/mbar_offset", "type": "float", "readonly": false, "cmd": "nv calib/mbar_offset"}]},
"hepump": {"base": "/hepump", "params": [
{"path": "", "type": "enum", "enum": {"neodry": 8, "xds35_auto": 0, "xds35_manual": 1, "sv65": 2, "other": 3, "no": -1}, "readonly": false, "cmd": "hepump", "description": "xds35: scroll pump, sv65: leybold", "kids": 9},
{"path": "send", "type": "text", "readonly": false, "cmd": "hepump send", "visibility": 3},
{"path": "status", "type": "text", "visibility": 3},
{"path": "running", "type": "bool", "readonly": false, "cmd": "hepump running"},
@ -239,7 +250,10 @@
{"path": "auto", "type": "bool", "readonly": false, "cmd": "hepump auto"},
{"path": "valve", "type": "enum", "enum": {"closed": 0, "closing": 1, "opening": 2, "opened": 3, "undefined": 4}, "readonly": false, "cmd": "hepump valve"},
{"path": "eco_t_lim", "type": "float", "readonly": false, "cmd": "hepump eco_t_lim", "description": "switch off eco mode when T_set < eco_t_lim and T < eco_t_lim * 2"},
{"path": "calib", "type": "float", "readonly": false, "cmd": "hepump calib", "visibility": 3}]}, "hemot": {"base": "/hepump/hemot", "params": [{"path": "", "type": "float", "readonly": false, "cmd": "run hemot", "kids": 30},
{"path": "calib", "type": "float", "readonly": false, "cmd": "hepump calib", "visibility": 3}]},
"hemot": {"base": "/hepump/hemot", "params": [
{"path": "", "type": "float", "readonly": false, "cmd": "run hemot", "kids": 30},
{"path": "send", "type": "text", "readonly": false, "cmd": "hemot send", "visibility": 3},
{"path": "status", "type": "text", "visibility": 3},
{"path": "is_running", "type": "int", "readonly": false, "cmd": "hemot is_running", "visibility": 3},
@ -270,7 +284,8 @@
{"path": "eeprom", "type": "enum", "enum": {"ok": 0, "dirty": 1, "save": 2, "load": 3}, "readonly": false, "cmd": "hemot eeprom"},
{"path": "customadr", "type": "text", "readonly": false, "cmd": "hemot customadr"},
{"path": "custompar", "type": "float", "readonly": false, "cmd": "hemot custompar"}]},
"ln2fill": {"base": "/ln2fill", "params": [
"
ln2fill": {"base": "/ln2fill", "params": [
{"path": "", "type": "enum", "enum": {"watching": 0, "fill": 1, "inactive": 2}, "readonly": false, "cmd": "ln2fill", "kids": 14},
{"path": "send", "type": "text", "readonly": false, "cmd": "ln2fill send", "visibility": 3},
{"path": "status", "type": "text", "visibility": 3},
@ -285,7 +300,10 @@
{"path": "maxholdhours", "type": "float", "readonly": false, "cmd": "ln2fill maxholdhours"},
{"path": "tolerance", "type": "float", "readonly": false, "cmd": "ln2fill tolerance"},
{"path": "badreadingminutes", "type": "float", "readonly": false, "cmd": "ln2fill badreadingminutes"},
{"path": "tubecoolingminutes", "type": "float", "readonly": false, "cmd": "ln2fill tubecoolingminutes"}]}, "hefill": {"base": "/hefill", "params": [{"path": "", "type": "enum", "enum": {"watching": 0, "fill": 1, "inactive": 2}, "readonly": false, "cmd": "hefill", "kids": 16},
{"path": "tubecoolingminutes", "type": "float", "readonly": false, "cmd": "ln2fill tubecoolingminutes"}]},
"hefill": {"base": "/hefill", "params": [
{"path": "", "type": "enum", "enum": {"watching": 0, "fill": 1, "inactive": 2}, "readonly": false, "cmd": "hefill", "kids": 16},
{"path": "send", "type": "text", "readonly": false, "cmd": "hefill send", "visibility": 3},
{"path": "status", "type": "text", "visibility": 3},
{"path": "state", "type": "text"},
@ -301,11 +319,17 @@
{"path": "badreadingminutes", "type": "float", "readonly": false, "cmd": "hefill badreadingminutes"},
{"path": "tubecoolingminutes", "type": "float", "readonly": false, "cmd": "hefill tubecoolingminutes"},
{"path": "vessellimit", "type": "float", "readonly": false, "cmd": "hefill vessellimit"},
{"path": "vext", "type": "float"}]}, "lev": {"base": "/lev", "params": [{"path": "", "type": "float", "kids": 4},
{"path": "vext", "type": "float"}]},
"lev": {"base": "/lev", "params": [
{"path": "", "type": "float", "kids": 4},
{"path": "send", "type": "text", "readonly": false, "cmd": "lev send", "visibility": 3},
{"path": "status", "type": "text", "visibility": 3},
{"path": "mode", "type": "enum", "enum": {"slow": 0, "fast (switches to slow automatically after filling)": 1}, "readonly": false, "cmd": "lev mode"},
{"path": "n2", "type": "float"}]}, "mf": {"base": "/mf", "params": [{"path": "", "type": "float", "kids": 26},
{"path": "n2", "type": "float"}]},
"mf": {"base": "/mf", "params": [
{"path": "", "type": "float", "cmd": "run mf", "kids": 26},
{"path": "persmode", "type": "int", "readonly": false, "cmd": "mf persmode"},
{"path": "perswitch", "type": "int"},
{"path": "nowait", "type": "int", "readonly": false, "cmd": "mf nowait"},
@ -330,7 +354,10 @@
{"path": "driver", "type": "text", "visibility": 3},
{"path": "creationCmd", "type": "text", "visibility": 3},
{"path": "targetValue", "type": "float"},
{"path": "status", "type": "text", "readonly": false, "cmd": "mf status", "visibility": 3}]}, "tcoil": {"base": "/tcoil", "params": [{"path": "", "type": "float", "kids": 11},
{"path": "status", "type": "text", "readonly": false, "cmd": "mf status", "visibility": 3}]},
"tcoil": {"base": "/tcoil", "params": [
{"path": "", "type": "float", "kids": 11},
{"path": "send", "type": "text", "readonly": false, "cmd": "tcoil send", "visibility": 3},
{"path": "status", "type": "text", "visibility": 3},
{"path": "excitation", "type": "float", "readonly": false, "cmd": "tcoil excitation", "visibility": 3},
@ -338,40 +365,43 @@
{"path": "td/enable", "type": "bool", "readonly": false, "cmd": "tcoil td/enable"},
{"path": "td/r", "type": "float"},
{"path": "td/curve", "type": "text", "readonly": false, "cmd": "tcoil td/curve", "kids": 3},
{"path": "td/curve/adjust", "type": "text", "readonly": false, "cmd": "tcoil td/curve/adjust"},
{"path": "td/curve/points", "type": "floatvarar", "readonly": false, "cmd": "tcoil td/curve/points"},
{"path": "td/curve/cpoints", "type": "floatvarar", "readonly": false, "cmd": "tcoil td/curve/cpoints"},
{"path": "td/curve/adjust", "type": "text", "readonly": false, "cmd": "tcoil td/curve/adjust", "visibility": 3},
{"path": "td/curve/points", "type": "floatvarar", "readonly": false, "cmd": "tcoil td/curve/points", "visibility": 3},
{"path": "td/curve/cpoints", "type": "floatvarar", "readonly": false, "cmd": "tcoil td/curve/cpoints", "visibility": 3},
{"path": "tc", "type": "float", "visibility": 3, "kids": 3},
{"path": "tc/enable", "type": "bool", "readonly": false, "cmd": "tcoil tc/enable"},
{"path": "tc/r", "type": "float"},
{"path": "tc/curve", "type": "text", "readonly": false, "cmd": "tcoil tc/curve", "kids": 3},
{"path": "tc/curve/adjust", "type": "text", "readonly": false, "cmd": "tcoil tc/curve/adjust"},
{"path": "tc/curve/points", "type": "floatvarar", "readonly": false, "cmd": "tcoil tc/curve/points"},
{"path": "tc/curve/cpoints", "type": "floatvarar", "readonly": false, "cmd": "tcoil tc/curve/cpoints"},
{"path": "tc/curve/adjust", "type": "text", "readonly": false, "cmd": "tcoil tc/curve/adjust", "visibility": 3},
{"path": "tc/curve/points", "type": "floatvarar", "readonly": false, "cmd": "tcoil tc/curve/points", "visibility": 3},
{"path": "tc/curve/cpoints", "type": "floatvarar", "readonly": false, "cmd": "tcoil tc/curve/cpoints", "visibility": 3},
{"path": "tb", "type": "float", "visibility": 3, "kids": 3},
{"path": "tb/enable", "type": "bool", "readonly": false, "cmd": "tcoil tb/enable"},
{"path": "tb/r", "type": "float"},
{"path": "tb/curve", "type": "text", "readonly": false, "cmd": "tcoil tb/curve", "kids": 3},
{"path": "tb/curve/adjust", "type": "text", "readonly": false, "cmd": "tcoil tb/curve/adjust"},
{"path": "tb/curve/points", "type": "floatvarar", "readonly": false, "cmd": "tcoil tb/curve/points"},
{"path": "tb/curve/cpoints", "type": "floatvarar", "readonly": false, "cmd": "tcoil tb/curve/cpoints"},
{"path": "tb/curve/adjust", "type": "text", "readonly": false, "cmd": "tcoil tb/curve/adjust", "visibility": 3},
{"path": "tb/curve/points", "type": "floatvarar", "readonly": false, "cmd": "tcoil tb/curve/points", "visibility": 3},
{"path": "tb/curve/cpoints", "type": "floatvarar", "readonly": false, "cmd": "tcoil tb/curve/cpoints", "visibility": 3},
{"path": "ta", "type": "float", "visibility": 3, "kids": 3},
{"path": "ta/enable", "type": "bool", "readonly": false, "cmd": "tcoil ta/enable"},
{"path": "ta/r", "type": "float"},
{"path": "ta/curve", "type": "text", "readonly": false, "cmd": "tcoil ta/curve", "kids": 3},
{"path": "ta/curve/adjust", "type": "text", "readonly": false, "cmd": "tcoil ta/curve/adjust"},
{"path": "ta/curve/points", "type": "floatvarar", "readonly": false, "cmd": "tcoil ta/curve/points"},
{"path": "ta/curve/cpoints", "type": "floatvarar", "readonly": false, "cmd": "tcoil ta/curve/cpoints"},
{"path": "ta/curve/adjust", "type": "text", "readonly": false, "cmd": "tcoil ta/curve/adjust", "visibility": 3},
{"path": "ta/curve/points", "type": "floatvarar", "readonly": false, "cmd": "tcoil ta/curve/points", "visibility": 3},
{"path": "ta/curve/cpoints", "type": "floatvarar", "readonly": false, "cmd": "tcoil ta/curve/cpoints", "visibility": 3},
{"path": "ref", "type": "float", "visibility": 3, "kids": 3},
{"path": "ref/enable", "type": "bool", "readonly": false, "cmd": "tcoil ref/enable"},
{"path": "ref/r", "type": "float"},
{"path": "ref/curve", "type": "text", "readonly": false, "cmd": "tcoil ref/curve", "kids": 3},
{"path": "ref/curve/adjust", "type": "text", "readonly": false, "cmd": "tcoil ref/curve/adjust"},
{"path": "ref/curve/points", "type": "floatvarar", "readonly": false, "cmd": "tcoil ref/curve/points"},
{"path": "ref/curve/cpoints", "type": "floatvarar", "readonly": false, "cmd": "tcoil ref/curve/cpoints"},
{"path": "ref/curve/adjust", "type": "text", "readonly": false, "cmd": "tcoil ref/curve/adjust", "visibility": 3},
{"path": "ref/curve/points", "type": "floatvarar", "readonly": false, "cmd": "tcoil ref/curve/points", "visibility": 3},
{"path": "ref/curve/cpoints", "type": "floatvarar", "readonly": false, "cmd": "tcoil ref/curve/cpoints", "visibility": 3},
{"path": "ext", "type": "float", "visibility": 3},
{"path": "com", "type": "float", "visibility": 3},
{"path": "gnd", "type": "float", "visibility": 3}]}, "table": {"base": "/table", "params": [{"path": "", "type": "none", "kids": 17},
{"path": "gnd", "type": "float", "visibility": 3}]},
"table": {"base": "/table", "params": [
{"path": "", "type": "none", "kids": 17},
{"path": "send", "type": "text", "readonly": false, "cmd": "table send", "visibility": 3},
{"path": "status", "type": "text", "visibility": 3},
{"path": "fix_tt_set_prop", "type": "bool", "readonly": false, "cmd": "table fix_tt_set_prop"},
@ -388,8 +418,14 @@
{"path": "tbl_tt_dblctrl_prop_up", "type": "text", "readonly": false, "cmd": "table tbl_tt_dblctrl_prop_up", "description": "enter value pair separated with colon T1:par1 T2:par2 ..."},
{"path": "fix_tt_dblctrl_prop_lo", "type": "bool", "readonly": false, "cmd": "table fix_tt_dblctrl_prop_lo"},
{"path": "val_tt_dblctrl_prop_lo", "type": "float"},
{"path": "tbl_tt_dblctrl_prop_lo", "type": "text", "readonly": false, "cmd": "table tbl_tt_dblctrl_prop_lo", "description": "enter value pair separated with colon T1:par1 T2:par2 ..."}]}, "ccu2": {"base": "/sics/ccu2", "params": [{"path": "", "type": "text", "readonly": false, "cmd": "ccu2", "kids": 23},
{"path": "tasks", "type": "none", "visibility": 3}]}, "lnv": {"base": "/lnv", "params": [{"path": "", "type": "enum", "enum": {"off": 5, "fixed": 0, "controlling": 1, "close": 3, "open": 4}, "readonly": false, "cmd": "lnv", "kids": 12},
{"path": "tbl_tt_dblctrl_prop_lo", "type": "text", "readonly": false, "cmd": "table tbl_tt_dblctrl_prop_lo", "description": "enter value pair separated with colon T1:par1 T2:par2 ..."}]},
"ccu2": {"base": "/sics/ccu2", "params": [
{"path": "", "type": "text", "readonly": false, "cmd": "ccu2", "kids": 23},
{"path": "tasks", "type": "none", "visibility": 3}]},
"lnv": {"base": "/lnv", "params": [
{"path": "", "type": "enum", "enum": {"off": 5, "fixed": 0, "controlling": 1, "close": 3, "open": 4}, "readonly": false, "cmd": "lnv", "kids": 12},
{"path": "send", "type": "text", "readonly": false, "cmd": "lnv send", "visibility": 3},
{"path": "status", "type": "text", "visibility": 3},
{"path": "set", "type": "float", "readonly": false, "cmd": "lnv set"},
@ -427,7 +463,10 @@
{"path": "autoflow/difmax", "type": "float"},
{"path": "autoflow/setmin", "type": "float"},
{"path": "autoflow/setmax", "type": "float"},
{"path": "autoflow/flowtarget", "type": "float"}]}, "lpr": {"base": "/lpr", "params": [{"path": "", "type": "float", "readonly": false, "cmd": "run lpr", "description": "lpr", "kids": 28},
{"path": "autoflow/flowtarget", "type": "float"}]},
"lpr": {"base": "/lpr", "params": [
{"path": "", "type": "float", "readonly": false, "cmd": "run lpr", "description": "lpr", "kids": 28},
{"path": "send", "type": "text", "readonly": false, "cmd": "lpr send", "visibility": 3},
{"path": "status", "type": "text", "visibility": 3},
{"path": "is_running", "type": "int", "readonly": false, "cmd": "lpr is_running", "visibility": 3},
@ -455,12 +494,13 @@
{"path": "running", "type": "int"},
{"path": "tolerance", "type": "float", "readonly": false, "cmd": "lpr tolerance"},
{"path": "maxwait", "type": "float", "readonly": false, "cmd": "lpr maxwait"},
{"path": "settle", "type": "float", "readonly": false, "cmd": "lpr settle"}]}, "lambdawatch": {"base": "/lambdawatch", "params": [{"path": "", "type": "float", "kids": 6},
{"path": "settle", "type": "float", "readonly": false, "cmd": "lpr settle"}]},
"lambdawatch": {"base": "/lambdawatch", "params": [
{"path": "", "type": "float", "kids": 6},
{"path": "send", "type": "text", "readonly": false, "cmd": "lambdawatch send", "visibility": 3},
{"path": "status", "type": "text", "visibility": 3},
{"path": "safefield", "type": "float", "readonly": false, "cmd": "lambdawatch safefield"},
{"path": "maxfield", "type": "float", "readonly": false, "cmd": "lambdawatch maxfield"},
{"path": "safetemp", "type": "float", "readonly": false, "cmd": "lambdawatch safetemp"},
{"path": "coiltemp", "type": "text", "readonly": false, "cmd": "lambdawatch coiltemp"}]}, "prep0": {"base": "/prep0", "params": [{"path": "", "type": "text", "readonly": false, "cmd": "prep0", "kids": 2},
{"path": "send", "type": "text", "readonly": false, "cmd": "prep0 send", "visibility": 3},
{"path": "status", "type": "text", "visibility": 3}]}}
{"path": "coiltemp", "type": "text", "readonly": false, "cmd": "lambdawatch coiltemp"}]}}

View File

@ -371,37 +371,37 @@
{"path": "td/enable", "type": "bool", "readonly": false, "cmd": "tcoil td/enable"},
{"path": "td/r", "type": "float"},
{"path": "td/curve", "type": "text", "readonly": false, "cmd": "tcoil td/curve", "kids": 3},
{"path": "td/curve/adjust", "type": "text", "readonly": false, "cmd": "tcoil td/curve/adjust"},
{"path": "td/curve/points", "type": "floatvarar", "readonly": false, "cmd": "tcoil td/curve/points"},
{"path": "td/curve/cpoints", "type": "floatvarar", "readonly": false, "cmd": "tcoil td/curve/cpoints"},
{"path": "td/curve/adjust", "type": "text", "readonly": false, "cmd": "tcoil td/curve/adjust", "visibility": 3},
{"path": "td/curve/points", "type": "floatvarar", "readonly": false, "cmd": "tcoil td/curve/points", "visibility": 3},
{"path": "td/curve/cpoints", "type": "floatvarar", "readonly": false, "cmd": "tcoil td/curve/cpoints", "visibility": 3},
{"path": "tb", "type": "float", "visibility": 3, "kids": 3},
{"path": "tb/enable", "type": "bool", "readonly": false, "cmd": "tcoil tb/enable"},
{"path": "tb/r", "type": "float"},
{"path": "tb/curve", "type": "text", "readonly": false, "cmd": "tcoil tb/curve", "kids": 3},
{"path": "tb/curve/adjust", "type": "text", "readonly": false, "cmd": "tcoil tb/curve/adjust"},
{"path": "tb/curve/points", "type": "floatvarar", "readonly": false, "cmd": "tcoil tb/curve/points"},
{"path": "tb/curve/cpoints", "type": "floatvarar", "readonly": false, "cmd": "tcoil tb/curve/cpoints"},
{"path": "tb/curve/adjust", "type": "text", "readonly": false, "cmd": "tcoil tb/curve/adjust", "visibility": 3},
{"path": "tb/curve/points", "type": "floatvarar", "readonly": false, "cmd": "tcoil tb/curve/points", "visibility": 3},
{"path": "tb/curve/cpoints", "type": "floatvarar", "readonly": false, "cmd": "tcoil tb/curve/cpoints", "visibility": 3},
{"path": "tc", "type": "float", "visibility": 3, "kids": 3},
{"path": "tc/enable", "type": "bool", "readonly": false, "cmd": "tcoil tc/enable"},
{"path": "tc/r", "type": "float"},
{"path": "tc/curve", "type": "text", "readonly": false, "cmd": "tcoil tc/curve", "kids": 3},
{"path": "tc/curve/adjust", "type": "text", "readonly": false, "cmd": "tcoil tc/curve/adjust"},
{"path": "tc/curve/points", "type": "floatvarar", "readonly": false, "cmd": "tcoil tc/curve/points"},
{"path": "tc/curve/cpoints", "type": "floatvarar", "readonly": false, "cmd": "tcoil tc/curve/cpoints"},
{"path": "tc/curve/adjust", "type": "text", "readonly": false, "cmd": "tcoil tc/curve/adjust", "visibility": 3},
{"path": "tc/curve/points", "type": "floatvarar", "readonly": false, "cmd": "tcoil tc/curve/points", "visibility": 3},
{"path": "tc/curve/cpoints", "type": "floatvarar", "readonly": false, "cmd": "tcoil tc/curve/cpoints", "visibility": 3},
{"path": "ta", "type": "float", "visibility": 3, "kids": 3},
{"path": "ta/enable", "type": "bool", "readonly": false, "cmd": "tcoil ta/enable"},
{"path": "ta/r", "type": "float"},
{"path": "ta/curve", "type": "text", "readonly": false, "cmd": "tcoil ta/curve", "kids": 3},
{"path": "ta/curve/adjust", "type": "text", "readonly": false, "cmd": "tcoil ta/curve/adjust"},
{"path": "ta/curve/points", "type": "floatvarar", "readonly": false, "cmd": "tcoil ta/curve/points"},
{"path": "ta/curve/cpoints", "type": "floatvarar", "readonly": false, "cmd": "tcoil ta/curve/cpoints"},
{"path": "ta/curve/adjust", "type": "text", "readonly": false, "cmd": "tcoil ta/curve/adjust", "visibility": 3},
{"path": "ta/curve/points", "type": "floatvarar", "readonly": false, "cmd": "tcoil ta/curve/points", "visibility": 3},
{"path": "ta/curve/cpoints", "type": "floatvarar", "readonly": false, "cmd": "tcoil ta/curve/cpoints", "visibility": 3},
{"path": "ref", "type": "float", "visibility": 3, "kids": 3},
{"path": "ref/enable", "type": "bool", "readonly": false, "cmd": "tcoil ref/enable"},
{"path": "ref/r", "type": "float"},
{"path": "ref/curve", "type": "text", "readonly": false, "cmd": "tcoil ref/curve", "kids": 3},
{"path": "ref/curve/adjust", "type": "text", "readonly": false, "cmd": "tcoil ref/curve/adjust"},
{"path": "ref/curve/points", "type": "floatvarar", "readonly": false, "cmd": "tcoil ref/curve/points"},
{"path": "ref/curve/cpoints", "type": "floatvarar", "readonly": false, "cmd": "tcoil ref/curve/cpoints"},
{"path": "ref/curve/adjust", "type": "text", "readonly": false, "cmd": "tcoil ref/curve/adjust", "visibility": 3},
{"path": "ref/curve/points", "type": "floatvarar", "readonly": false, "cmd": "tcoil ref/curve/points", "visibility": 3},
{"path": "ref/curve/cpoints", "type": "floatvarar", "readonly": false, "cmd": "tcoil ref/curve/cpoints", "visibility": 3},
{"path": "ext", "type": "float", "visibility": 3},
{"path": "com", "type": "float", "visibility": 3},
{"path": "gnd", "type": "float", "visibility": 3}]},

View File

@ -371,37 +371,37 @@
{"path": "ta/enable", "type": "bool", "readonly": false, "cmd": "tcoil ta/enable"},
{"path": "ta/r", "type": "float"},
{"path": "ta/curve", "type": "text", "readonly": false, "cmd": "tcoil ta/curve", "kids": 3},
{"path": "ta/curve/adjust", "type": "text", "readonly": false, "cmd": "tcoil ta/curve/adjust"},
{"path": "ta/curve/points", "type": "floatvarar", "readonly": false, "cmd": "tcoil ta/curve/points"},
{"path": "ta/curve/cpoints", "type": "floatvarar", "readonly": false, "cmd": "tcoil ta/curve/cpoints"},
{"path": "ta/curve/adjust", "type": "text", "readonly": false, "cmd": "tcoil ta/curve/adjust", "visibility": 3},
{"path": "ta/curve/points", "type": "floatvarar", "readonly": false, "cmd": "tcoil ta/curve/points", "visibility": 3},
{"path": "ta/curve/cpoints", "type": "floatvarar", "readonly": false, "cmd": "tcoil ta/curve/cpoints", "visibility": 3},
{"path": "tb", "type": "float", "kids": 3},
{"path": "tb/enable", "type": "bool", "readonly": false, "cmd": "tcoil tb/enable"},
{"path": "tb/r", "type": "float"},
{"path": "tb/curve", "type": "text", "readonly": false, "cmd": "tcoil tb/curve", "kids": 3},
{"path": "tb/curve/adjust", "type": "text", "readonly": false, "cmd": "tcoil tb/curve/adjust"},
{"path": "tb/curve/points", "type": "floatvarar", "readonly": false, "cmd": "tcoil tb/curve/points"},
{"path": "tb/curve/cpoints", "type": "floatvarar", "readonly": false, "cmd": "tcoil tb/curve/cpoints"},
{"path": "tb/curve/adjust", "type": "text", "readonly": false, "cmd": "tcoil tb/curve/adjust", "visibility": 3},
{"path": "tb/curve/points", "type": "floatvarar", "readonly": false, "cmd": "tcoil tb/curve/points", "visibility": 3},
{"path": "tb/curve/cpoints", "type": "floatvarar", "readonly": false, "cmd": "tcoil tb/curve/cpoints", "visibility": 3},
{"path": "td", "type": "float", "visibility": 3, "kids": 3},
{"path": "td/enable", "type": "bool", "readonly": false, "cmd": "tcoil td/enable"},
{"path": "td/r", "type": "float"},
{"path": "td/curve", "type": "text", "readonly": false, "cmd": "tcoil td/curve", "kids": 3},
{"path": "td/curve/adjust", "type": "text", "readonly": false, "cmd": "tcoil td/curve/adjust"},
{"path": "td/curve/points", "type": "floatvarar", "readonly": false, "cmd": "tcoil td/curve/points"},
{"path": "td/curve/cpoints", "type": "floatvarar", "readonly": false, "cmd": "tcoil td/curve/cpoints"},
{"path": "td/curve/adjust", "type": "text", "readonly": false, "cmd": "tcoil td/curve/adjust", "visibility": 3},
{"path": "td/curve/points", "type": "floatvarar", "readonly": false, "cmd": "tcoil td/curve/points", "visibility": 3},
{"path": "td/curve/cpoints", "type": "floatvarar", "readonly": false, "cmd": "tcoil td/curve/cpoints", "visibility": 3},
{"path": "ref", "type": "float", "visibility": 3, "kids": 3},
{"path": "ref/enable", "type": "bool", "readonly": false, "cmd": "tcoil ref/enable"},
{"path": "ref/r", "type": "float"},
{"path": "ref/curve", "type": "text", "readonly": false, "cmd": "tcoil ref/curve", "kids": 3},
{"path": "ref/curve/adjust", "type": "text", "readonly": false, "cmd": "tcoil ref/curve/adjust"},
{"path": "ref/curve/points", "type": "floatvarar", "readonly": false, "cmd": "tcoil ref/curve/points"},
{"path": "ref/curve/cpoints", "type": "floatvarar", "readonly": false, "cmd": "tcoil ref/curve/cpoints"},
{"path": "ref/curve/adjust", "type": "text", "readonly": false, "cmd": "tcoil ref/curve/adjust", "visibility": 3},
{"path": "ref/curve/points", "type": "floatvarar", "readonly": false, "cmd": "tcoil ref/curve/points", "visibility": 3},
{"path": "ref/curve/cpoints", "type": "floatvarar", "readonly": false, "cmd": "tcoil ref/curve/cpoints", "visibility": 3},
{"path": "tc", "type": "float", "visibility": 3, "kids": 3},
{"path": "tc/enable", "type": "bool", "readonly": false, "cmd": "tcoil tc/enable"},
{"path": "tc/r", "type": "float"},
{"path": "tc/curve", "type": "text", "readonly": false, "cmd": "tcoil tc/curve", "kids": 3},
{"path": "tc/curve/adjust", "type": "text", "readonly": false, "cmd": "tcoil tc/curve/adjust"},
{"path": "tc/curve/points", "type": "floatvarar", "readonly": false, "cmd": "tcoil tc/curve/points"},
{"path": "tc/curve/cpoints", "type": "floatvarar", "readonly": false, "cmd": "tcoil tc/curve/cpoints"},
{"path": "tc/curve/adjust", "type": "text", "readonly": false, "cmd": "tcoil tc/curve/adjust", "visibility": 3},
{"path": "tc/curve/points", "type": "floatvarar", "readonly": false, "cmd": "tcoil tc/curve/points", "visibility": 3},
{"path": "tc/curve/cpoints", "type": "floatvarar", "readonly": false, "cmd": "tcoil tc/curve/cpoints", "visibility": 3},
{"path": "ext", "type": "float", "visibility": 3},
{"path": "com", "type": "float", "visibility": 3},
{"path": "gnd", "type": "float", "visibility": 3}]},

View File

@ -370,37 +370,37 @@
{"path": "ta/enable", "type": "bool", "readonly": false, "cmd": "tcoil ta/enable"},
{"path": "ta/r", "type": "float"},
{"path": "ta/curve", "type": "text", "readonly": false, "cmd": "tcoil ta/curve", "kids": 3},
{"path": "ta/curve/adjust", "type": "text", "readonly": false, "cmd": "tcoil ta/curve/adjust"},
{"path": "ta/curve/points", "type": "floatvarar", "readonly": false, "cmd": "tcoil ta/curve/points"},
{"path": "ta/curve/cpoints", "type": "floatvarar", "readonly": false, "cmd": "tcoil ta/curve/cpoints"},
{"path": "ta/curve/adjust", "type": "text", "readonly": false, "cmd": "tcoil ta/curve/adjust", "visibility": 3},
{"path": "ta/curve/points", "type": "floatvarar", "readonly": false, "cmd": "tcoil ta/curve/points", "visibility": 3},
{"path": "ta/curve/cpoints", "type": "floatvarar", "readonly": false, "cmd": "tcoil ta/curve/cpoints", "visibility": 3},
{"path": "tb", "type": "float", "kids": 3},
{"path": "tb/enable", "type": "bool", "readonly": false, "cmd": "tcoil tb/enable"},
{"path": "tb/r", "type": "float"},
{"path": "tb/curve", "type": "text", "readonly": false, "cmd": "tcoil tb/curve", "kids": 3},
{"path": "tb/curve/adjust", "type": "text", "readonly": false, "cmd": "tcoil tb/curve/adjust"},
{"path": "tb/curve/points", "type": "floatvarar", "readonly": false, "cmd": "tcoil tb/curve/points"},
{"path": "tb/curve/cpoints", "type": "floatvarar", "readonly": false, "cmd": "tcoil tb/curve/cpoints"},
{"path": "tb/curve/adjust", "type": "text", "readonly": false, "cmd": "tcoil tb/curve/adjust", "visibility": 3},
{"path": "tb/curve/points", "type": "floatvarar", "readonly": false, "cmd": "tcoil tb/curve/points", "visibility": 3},
{"path": "tb/curve/cpoints", "type": "floatvarar", "readonly": false, "cmd": "tcoil tb/curve/cpoints", "visibility": 3},
{"path": "td", "type": "float", "visibility": 3, "kids": 3},
{"path": "td/enable", "type": "bool", "readonly": false, "cmd": "tcoil td/enable"},
{"path": "td/r", "type": "float"},
{"path": "td/curve", "type": "text", "readonly": false, "cmd": "tcoil td/curve", "kids": 3},
{"path": "td/curve/adjust", "type": "text", "readonly": false, "cmd": "tcoil td/curve/adjust"},
{"path": "td/curve/points", "type": "floatvarar", "readonly": false, "cmd": "tcoil td/curve/points"},
{"path": "td/curve/cpoints", "type": "floatvarar", "readonly": false, "cmd": "tcoil td/curve/cpoints"},
{"path": "td/curve/adjust", "type": "text", "readonly": false, "cmd": "tcoil td/curve/adjust", "visibility": 3},
{"path": "td/curve/points", "type": "floatvarar", "readonly": false, "cmd": "tcoil td/curve/points", "visibility": 3},
{"path": "td/curve/cpoints", "type": "floatvarar", "readonly": false, "cmd": "tcoil td/curve/cpoints", "visibility": 3},
{"path": "ref", "type": "float", "visibility": 3, "kids": 3},
{"path": "ref/enable", "type": "bool", "readonly": false, "cmd": "tcoil ref/enable"},
{"path": "ref/r", "type": "float"},
{"path": "ref/curve", "type": "text", "readonly": false, "cmd": "tcoil ref/curve", "kids": 3},
{"path": "ref/curve/adjust", "type": "text", "readonly": false, "cmd": "tcoil ref/curve/adjust"},
{"path": "ref/curve/points", "type": "floatvarar", "readonly": false, "cmd": "tcoil ref/curve/points"},
{"path": "ref/curve/cpoints", "type": "floatvarar", "readonly": false, "cmd": "tcoil ref/curve/cpoints"},
{"path": "ref/curve/adjust", "type": "text", "readonly": false, "cmd": "tcoil ref/curve/adjust", "visibility": 3},
{"path": "ref/curve/points", "type": "floatvarar", "readonly": false, "cmd": "tcoil ref/curve/points", "visibility": 3},
{"path": "ref/curve/cpoints", "type": "floatvarar", "readonly": false, "cmd": "tcoil ref/curve/cpoints", "visibility": 3},
{"path": "tc", "type": "float", "visibility": 3, "kids": 3},
{"path": "tc/enable", "type": "bool", "readonly": false, "cmd": "tcoil tc/enable"},
{"path": "tc/r", "type": "float"},
{"path": "tc/curve", "type": "text", "readonly": false, "cmd": "tcoil tc/curve", "kids": 3},
{"path": "tc/curve/adjust", "type": "text", "readonly": false, "cmd": "tcoil tc/curve/adjust"},
{"path": "tc/curve/points", "type": "floatvarar", "readonly": false, "cmd": "tcoil tc/curve/points"},
{"path": "tc/curve/cpoints", "type": "floatvarar", "readonly": false, "cmd": "tcoil tc/curve/cpoints"},
{"path": "tc/curve/adjust", "type": "text", "readonly": false, "cmd": "tcoil tc/curve/adjust", "visibility": 3},
{"path": "tc/curve/points", "type": "floatvarar", "readonly": false, "cmd": "tcoil tc/curve/points", "visibility": 3},
{"path": "tc/curve/cpoints", "type": "floatvarar", "readonly": false, "cmd": "tcoil tc/curve/cpoints", "visibility": 3},
{"path": "ext", "type": "float", "visibility": 3},
{"path": "com", "type": "float", "visibility": 3},
{"path": "gnd", "type": "float", "visibility": 3}]},

View File

@ -371,37 +371,37 @@
{"path": "ta/enable", "type": "bool", "readonly": false, "cmd": "tcoil ta/enable"},
{"path": "ta/r", "type": "float"},
{"path": "ta/curve", "type": "text", "readonly": false, "cmd": "tcoil ta/curve", "kids": 3},
{"path": "ta/curve/adjust", "type": "text", "readonly": false, "cmd": "tcoil ta/curve/adjust"},
{"path": "ta/curve/points", "type": "floatvarar", "readonly": false, "cmd": "tcoil ta/curve/points"},
{"path": "ta/curve/cpoints", "type": "floatvarar", "readonly": false, "cmd": "tcoil ta/curve/cpoints"},
{"path": "ta/curve/adjust", "type": "text", "readonly": false, "cmd": "tcoil ta/curve/adjust", "visibility": 3},
{"path": "ta/curve/points", "type": "floatvarar", "readonly": false, "cmd": "tcoil ta/curve/points", "visibility": 3},
{"path": "ta/curve/cpoints", "type": "floatvarar", "readonly": false, "cmd": "tcoil ta/curve/cpoints", "visibility": 3},
{"path": "tb", "type": "float", "kids": 3},
{"path": "tb/enable", "type": "bool", "readonly": false, "cmd": "tcoil tb/enable"},
{"path": "tb/r", "type": "float"},
{"path": "tb/curve", "type": "text", "readonly": false, "cmd": "tcoil tb/curve", "kids": 3},
{"path": "tb/curve/adjust", "type": "text", "readonly": false, "cmd": "tcoil tb/curve/adjust"},
{"path": "tb/curve/points", "type": "floatvarar", "readonly": false, "cmd": "tcoil tb/curve/points"},
{"path": "tb/curve/cpoints", "type": "floatvarar", "readonly": false, "cmd": "tcoil tb/curve/cpoints"},
{"path": "tb/curve/adjust", "type": "text", "readonly": false, "cmd": "tcoil tb/curve/adjust", "visibility": 3},
{"path": "tb/curve/points", "type": "floatvarar", "readonly": false, "cmd": "tcoil tb/curve/points", "visibility": 3},
{"path": "tb/curve/cpoints", "type": "floatvarar", "readonly": false, "cmd": "tcoil tb/curve/cpoints", "visibility": 3},
{"path": "td", "type": "float", "visibility": 3, "kids": 3},
{"path": "td/enable", "type": "bool", "readonly": false, "cmd": "tcoil td/enable"},
{"path": "td/r", "type": "float"},
{"path": "td/curve", "type": "text", "readonly": false, "cmd": "tcoil td/curve", "kids": 3},
{"path": "td/curve/adjust", "type": "text", "readonly": false, "cmd": "tcoil td/curve/adjust"},
{"path": "td/curve/points", "type": "floatvarar", "readonly": false, "cmd": "tcoil td/curve/points"},
{"path": "td/curve/cpoints", "type": "floatvarar", "readonly": false, "cmd": "tcoil td/curve/cpoints"},
{"path": "td/curve/adjust", "type": "text", "readonly": false, "cmd": "tcoil td/curve/adjust", "visibility": 3},
{"path": "td/curve/points", "type": "floatvarar", "readonly": false, "cmd": "tcoil td/curve/points", "visibility": 3},
{"path": "td/curve/cpoints", "type": "floatvarar", "readonly": false, "cmd": "tcoil td/curve/cpoints", "visibility": 3},
{"path": "ref", "type": "float", "visibility": 3, "kids": 3},
{"path": "ref/enable", "type": "bool", "readonly": false, "cmd": "tcoil ref/enable"},
{"path": "ref/r", "type": "float"},
{"path": "ref/curve", "type": "text", "readonly": false, "cmd": "tcoil ref/curve", "kids": 3},
{"path": "ref/curve/adjust", "type": "text", "readonly": false, "cmd": "tcoil ref/curve/adjust"},
{"path": "ref/curve/points", "type": "floatvarar", "readonly": false, "cmd": "tcoil ref/curve/points"},
{"path": "ref/curve/cpoints", "type": "floatvarar", "readonly": false, "cmd": "tcoil ref/curve/cpoints"},
{"path": "ref/curve/adjust", "type": "text", "readonly": false, "cmd": "tcoil ref/curve/adjust", "visibility": 3},
{"path": "ref/curve/points", "type": "floatvarar", "readonly": false, "cmd": "tcoil ref/curve/points", "visibility": 3},
{"path": "ref/curve/cpoints", "type": "floatvarar", "readonly": false, "cmd": "tcoil ref/curve/cpoints", "visibility": 3},
{"path": "tc", "type": "float", "visibility": 3, "kids": 3},
{"path": "tc/enable", "type": "bool", "readonly": false, "cmd": "tcoil tc/enable"},
{"path": "tc/r", "type": "float"},
{"path": "tc/curve", "type": "text", "readonly": false, "cmd": "tcoil tc/curve", "kids": 3},
{"path": "tc/curve/adjust", "type": "text", "readonly": false, "cmd": "tcoil tc/curve/adjust"},
{"path": "tc/curve/points", "type": "floatvarar", "readonly": false, "cmd": "tcoil tc/curve/points"},
{"path": "tc/curve/cpoints", "type": "floatvarar", "readonly": false, "cmd": "tcoil tc/curve/cpoints"},
{"path": "tc/curve/adjust", "type": "text", "readonly": false, "cmd": "tcoil tc/curve/adjust", "visibility": 3},
{"path": "tc/curve/points", "type": "floatvarar", "readonly": false, "cmd": "tcoil tc/curve/points", "visibility": 3},
{"path": "tc/curve/cpoints", "type": "floatvarar", "readonly": false, "cmd": "tcoil tc/curve/cpoints", "visibility": 3},
{"path": "ext", "type": "float", "visibility": 3},
{"path": "com", "type": "float", "visibility": 3},
{"path": "gnd", "type": "float", "visibility": 3}]},

View File

@ -365,37 +365,37 @@
{"path": "ta/enable", "type": "bool", "readonly": false, "cmd": "tcoil ta/enable"},
{"path": "ta/r", "type": "float"},
{"path": "ta/curve", "type": "text", "readonly": false, "cmd": "tcoil ta/curve", "kids": 3},
{"path": "ta/curve/adjust", "type": "text", "readonly": false, "cmd": "tcoil ta/curve/adjust"},
{"path": "ta/curve/points", "type": "floatvarar", "readonly": false, "cmd": "tcoil ta/curve/points"},
{"path": "ta/curve/cpoints", "type": "floatvarar", "readonly": false, "cmd": "tcoil ta/curve/cpoints"},
{"path": "ta/curve/adjust", "type": "text", "readonly": false, "cmd": "tcoil ta/curve/adjust", "visibility": 3},
{"path": "ta/curve/points", "type": "floatvarar", "readonly": false, "cmd": "tcoil ta/curve/points", "visibility": 3},
{"path": "ta/curve/cpoints", "type": "floatvarar", "readonly": false, "cmd": "tcoil ta/curve/cpoints", "visibility": 3},
{"path": "tb", "type": "float", "visibility": 3, "kids": 3},
{"path": "tb/enable", "type": "bool", "readonly": false, "cmd": "tcoil tb/enable"},
{"path": "tb/r", "type": "float"},
{"path": "tb/curve", "type": "text", "readonly": false, "cmd": "tcoil tb/curve", "kids": 3},
{"path": "tb/curve/adjust", "type": "text", "readonly": false, "cmd": "tcoil tb/curve/adjust"},
{"path": "tb/curve/points", "type": "floatvarar", "readonly": false, "cmd": "tcoil tb/curve/points"},
{"path": "tb/curve/cpoints", "type": "floatvarar", "readonly": false, "cmd": "tcoil tb/curve/cpoints"},
{"path": "tb/curve/adjust", "type": "text", "readonly": false, "cmd": "tcoil tb/curve/adjust", "visibility": 3},
{"path": "tb/curve/points", "type": "floatvarar", "readonly": false, "cmd": "tcoil tb/curve/points", "visibility": 3},
{"path": "tb/curve/cpoints", "type": "floatvarar", "readonly": false, "cmd": "tcoil tb/curve/cpoints", "visibility": 3},
{"path": "td", "type": "float", "visibility": 3, "kids": 3},
{"path": "td/enable", "type": "bool", "readonly": false, "cmd": "tcoil td/enable"},
{"path": "td/r", "type": "float"},
{"path": "td/curve", "type": "text", "readonly": false, "cmd": "tcoil td/curve", "kids": 3},
{"path": "td/curve/adjust", "type": "text", "readonly": false, "cmd": "tcoil td/curve/adjust"},
{"path": "td/curve/points", "type": "floatvarar", "readonly": false, "cmd": "tcoil td/curve/points"},
{"path": "td/curve/cpoints", "type": "floatvarar", "readonly": false, "cmd": "tcoil td/curve/cpoints"},
{"path": "td/curve/adjust", "type": "text", "readonly": false, "cmd": "tcoil td/curve/adjust", "visibility": 3},
{"path": "td/curve/points", "type": "floatvarar", "readonly": false, "cmd": "tcoil td/curve/points", "visibility": 3},
{"path": "td/curve/cpoints", "type": "floatvarar", "readonly": false, "cmd": "tcoil td/curve/cpoints", "visibility": 3},
{"path": "ref", "type": "float", "visibility": 3, "kids": 3},
{"path": "ref/enable", "type": "bool", "readonly": false, "cmd": "tcoil ref/enable"},
{"path": "ref/r", "type": "float"},
{"path": "ref/curve", "type": "text", "readonly": false, "cmd": "tcoil ref/curve", "kids": 3},
{"path": "ref/curve/adjust", "type": "text", "readonly": false, "cmd": "tcoil ref/curve/adjust"},
{"path": "ref/curve/points", "type": "floatvarar", "readonly": false, "cmd": "tcoil ref/curve/points"},
{"path": "ref/curve/cpoints", "type": "floatvarar", "readonly": false, "cmd": "tcoil ref/curve/cpoints"},
{"path": "ref/curve/adjust", "type": "text", "readonly": false, "cmd": "tcoil ref/curve/adjust", "visibility": 3},
{"path": "ref/curve/points", "type": "floatvarar", "readonly": false, "cmd": "tcoil ref/curve/points", "visibility": 3},
{"path": "ref/curve/cpoints", "type": "floatvarar", "readonly": false, "cmd": "tcoil ref/curve/cpoints", "visibility": 3},
{"path": "tc", "type": "float", "visibility": 3, "kids": 3},
{"path": "tc/enable", "type": "bool", "readonly": false, "cmd": "tcoil tc/enable"},
{"path": "tc/r", "type": "float"},
{"path": "tc/curve", "type": "text", "readonly": false, "cmd": "tcoil tc/curve", "kids": 3},
{"path": "tc/curve/adjust", "type": "text", "readonly": false, "cmd": "tcoil tc/curve/adjust"},
{"path": "tc/curve/points", "type": "floatvarar", "readonly": false, "cmd": "tcoil tc/curve/points"},
{"path": "tc/curve/cpoints", "type": "floatvarar", "readonly": false, "cmd": "tcoil tc/curve/cpoints"},
{"path": "tc/curve/adjust", "type": "text", "readonly": false, "cmd": "tcoil tc/curve/adjust", "visibility": 3},
{"path": "tc/curve/points", "type": "floatvarar", "readonly": false, "cmd": "tcoil tc/curve/points", "visibility": 3},
{"path": "tc/curve/cpoints", "type": "floatvarar", "readonly": false, "cmd": "tcoil tc/curve/cpoints", "visibility": 3},
{"path": "ext", "type": "float", "visibility": 3},
{"path": "com", "type": "float", "visibility": 3},
{"path": "gnd", "type": "float", "visibility": 3}]},

View File

@ -331,37 +331,37 @@
{"path": "ta/enable", "type": "bool", "readonly": false, "cmd": "tcoil ta/enable"},
{"path": "ta/r", "type": "float"},
{"path": "ta/curve", "type": "text", "readonly": false, "cmd": "tcoil ta/curve", "kids": 3},
{"path": "ta/curve/adjust", "type": "text", "readonly": false, "cmd": "tcoil ta/curve/adjust"},
{"path": "ta/curve/points", "type": "floatvarar", "readonly": false, "cmd": "tcoil ta/curve/points"},
{"path": "ta/curve/cpoints", "type": "floatvarar", "readonly": false, "cmd": "tcoil ta/curve/cpoints"},
{"path": "ta/curve/adjust", "type": "text", "readonly": false, "cmd": "tcoil ta/curve/adjust", "visibility": 3},
{"path": "ta/curve/points", "type": "floatvarar", "readonly": false, "cmd": "tcoil ta/curve/points", "visibility": 3},
{"path": "ta/curve/cpoints", "type": "floatvarar", "readonly": false, "cmd": "tcoil ta/curve/cpoints", "visibility": 3},
{"path": "tb", "type": "float", "visibility": 3, "kids": 3},
{"path": "tb/enable", "type": "bool", "readonly": false, "cmd": "tcoil tb/enable"},
{"path": "tb/r", "type": "float"},
{"path": "tb/curve", "type": "text", "readonly": false, "cmd": "tcoil tb/curve", "kids": 3},
{"path": "tb/curve/adjust", "type": "text", "readonly": false, "cmd": "tcoil tb/curve/adjust"},
{"path": "tb/curve/points", "type": "floatvarar", "readonly": false, "cmd": "tcoil tb/curve/points"},
{"path": "tb/curve/cpoints", "type": "floatvarar", "readonly": false, "cmd": "tcoil tb/curve/cpoints"},
{"path": "tb/curve/adjust", "type": "text", "readonly": false, "cmd": "tcoil tb/curve/adjust", "visibility": 3},
{"path": "tb/curve/points", "type": "floatvarar", "readonly": false, "cmd": "tcoil tb/curve/points", "visibility": 3},
{"path": "tb/curve/cpoints", "type": "floatvarar", "readonly": false, "cmd": "tcoil tb/curve/cpoints", "visibility": 3},
{"path": "td", "type": "float", "visibility": 3, "kids": 3},
{"path": "td/enable", "type": "bool", "readonly": false, "cmd": "tcoil td/enable"},
{"path": "td/r", "type": "float"},
{"path": "td/curve", "type": "text", "readonly": false, "cmd": "tcoil td/curve", "kids": 3},
{"path": "td/curve/adjust", "type": "text", "readonly": false, "cmd": "tcoil td/curve/adjust"},
{"path": "td/curve/points", "type": "floatvarar", "readonly": false, "cmd": "tcoil td/curve/points"},
{"path": "td/curve/cpoints", "type": "floatvarar", "readonly": false, "cmd": "tcoil td/curve/cpoints"},
{"path": "td/curve/adjust", "type": "text", "readonly": false, "cmd": "tcoil td/curve/adjust", "visibility": 3},
{"path": "td/curve/points", "type": "floatvarar", "readonly": false, "cmd": "tcoil td/curve/points", "visibility": 3},
{"path": "td/curve/cpoints", "type": "floatvarar", "readonly": false, "cmd": "tcoil td/curve/cpoints", "visibility": 3},
{"path": "ref", "type": "float", "visibility": 3, "kids": 3},
{"path": "ref/enable", "type": "bool", "readonly": false, "cmd": "tcoil ref/enable"},
{"path": "ref/r", "type": "float"},
{"path": "ref/curve", "type": "text", "readonly": false, "cmd": "tcoil ref/curve", "kids": 3},
{"path": "ref/curve/adjust", "type": "text", "readonly": false, "cmd": "tcoil ref/curve/adjust"},
{"path": "ref/curve/points", "type": "floatvarar", "readonly": false, "cmd": "tcoil ref/curve/points"},
{"path": "ref/curve/cpoints", "type": "floatvarar", "readonly": false, "cmd": "tcoil ref/curve/cpoints"},
{"path": "ref/curve/adjust", "type": "text", "readonly": false, "cmd": "tcoil ref/curve/adjust", "visibility": 3},
{"path": "ref/curve/points", "type": "floatvarar", "readonly": false, "cmd": "tcoil ref/curve/points", "visibility": 3},
{"path": "ref/curve/cpoints", "type": "floatvarar", "readonly": false, "cmd": "tcoil ref/curve/cpoints", "visibility": 3},
{"path": "tc", "type": "float", "visibility": 3, "kids": 3},
{"path": "tc/enable", "type": "bool", "readonly": false, "cmd": "tcoil tc/enable"},
{"path": "tc/r", "type": "float"},
{"path": "tc/curve", "type": "text", "readonly": false, "cmd": "tcoil tc/curve", "kids": 3},
{"path": "tc/curve/adjust", "type": "text", "readonly": false, "cmd": "tcoil tc/curve/adjust"},
{"path": "tc/curve/points", "type": "floatvarar", "readonly": false, "cmd": "tcoil tc/curve/points"},
{"path": "tc/curve/cpoints", "type": "floatvarar", "readonly": false, "cmd": "tcoil tc/curve/cpoints"},
{"path": "tc/curve/adjust", "type": "text", "readonly": false, "cmd": "tcoil tc/curve/adjust", "visibility": 3},
{"path": "tc/curve/points", "type": "floatvarar", "readonly": false, "cmd": "tcoil tc/curve/points", "visibility": 3},
{"path": "tc/curve/cpoints", "type": "floatvarar", "readonly": false, "cmd": "tcoil tc/curve/cpoints", "visibility": 3},
{"path": "ext", "type": "float", "visibility": 3},
{"path": "com", "type": "float", "visibility": 3},
{"path": "gnd", "type": "float", "visibility": 3}]},

307
cfg/sea/ori2.config.json Normal file
View File

@ -0,0 +1,307 @@
{"tt": {"base": "/tt", "params": [
{"path": "", "type": "float", "readonly": false, "cmd": "run tt", "description": "tt", "kids": 19},
{"path": "send", "type": "text", "readonly": false, "cmd": "tt send", "visibility": 3},
{"path": "status", "type": "text", "visibility": 3},
{"path": "is_running", "type": "int", "readonly": false, "cmd": "tt is_running", "visibility": 3},
{"path": "mainloop", "type": "text", "readonly": false, "cmd": "tt mainloop", "visibility": 3},
{"path": "target", "type": "float"},
{"path": "running", "type": "int"},
{"path": "tolerance", "type": "float", "readonly": false, "cmd": "tt tolerance"},
{"path": "maxwait", "type": "float", "readonly": false, "cmd": "tt maxwait"},
{"path": "settle", "type": "float", "readonly": false, "cmd": "tt settle"},
{"path": "log", "type": "text", "readonly": false, "cmd": "tt log", "visibility": 3, "kids": 4},
{"path": "log/mean", "type": "float", "visibility": 3},
{"path": "log/m2", "type": "float", "visibility": 3},
{"path": "log/stddev", "type": "float", "visibility": 3},
{"path": "log/n", "type": "float", "visibility": 3},
{"path": "dblctrl", "type": "bool", "readonly": false, "cmd": "tt dblctrl", "kids": 9},
{"path": "dblctrl/tshift", "type": "float", "readonly": false, "cmd": "tt dblctrl/tshift"},
{"path": "dblctrl/mode", "type": "enum", "enum": {"disabled": -1, "inactive": 0, "stable": 1, "up": 2, "down": 3}, "readonly": false, "cmd": "tt dblctrl/mode"},
{"path": "dblctrl/shift_up", "type": "float"},
{"path": "dblctrl/shift_lo", "type": "float"},
{"path": "dblctrl/t_min", "type": "float"},
{"path": "dblctrl/t_max", "type": "float"},
{"path": "dblctrl/int2", "type": "float", "readonly": false, "cmd": "tt dblctrl/int2"},
{"path": "dblctrl/prop_up", "type": "float", "readonly": false, "cmd": "tt dblctrl/prop_up"},
{"path": "dblctrl/prop_lo", "type": "float", "readonly": false, "cmd": "tt dblctrl/prop_lo"},
{"path": "tm", "type": "float", "kids": 4},
{"path": "tm/curve", "type": "text", "readonly": false, "cmd": "tt tm/curve", "kids": 1},
{"path": "tm/curve/points", "type": "floatvarar", "readonly": false, "cmd": "tt tm/curve/points", "visibility": 3},
{"path": "tm/alarm", "type": "float", "readonly": false, "cmd": "tt tm/alarm"},
{"path": "tm/stddev", "type": "float"},
{"path": "tm/raw", "type": "float"},
{"path": "ts", "type": "float", "kids": 4},
{"path": "ts/curve", "type": "text", "readonly": false, "cmd": "tt ts/curve", "kids": 1},
{"path": "ts/curve/points", "type": "floatvarar", "readonly": false, "cmd": "tt ts/curve/points", "visibility": 3},
{"path": "ts/alarm", "type": "float", "readonly": false, "cmd": "tt ts/alarm"},
{"path": "ts/stddev", "type": "float"},
{"path": "ts/raw", "type": "float"},
{"path": "ts_2", "type": "float", "visibility": 3, "kids": 4},
{"path": "ts_2/curve", "type": "text", "readonly": false, "cmd": "tt ts_2/curve", "visibility": 3, "kids": 1},
{"path": "ts_2/curve/points", "type": "floatvarar", "readonly": false, "cmd": "tt ts_2/curve/points", "visibility": 3},
{"path": "ts_2/alarm", "type": "float", "readonly": false, "cmd": "tt ts_2/alarm", "visibility": 3},
{"path": "ts_2/stddev", "type": "float", "visibility": 3},
{"path": "ts_2/raw", "type": "float", "visibility": 3},
{"path": "set", "type": "float", "readonly": false, "cmd": "tt set", "kids": 18},
{"path": "set/mode", "type": "enum", "enum": {"disabled": -1, "off": 0, "controlling": 1, "manual": 2}, "readonly": false, "cmd": "tt set/mode"},
{"path": "set/reg", "type": "float"},
{"path": "set/ramp", "type": "float", "readonly": false, "cmd": "tt set/ramp", "description": "maximum ramp in K/min (0: ramp off)"},
{"path": "set/wramp", "type": "float", "readonly": false, "cmd": "tt set/wramp"},
{"path": "set/smooth", "type": "float", "readonly": false, "cmd": "tt set/smooth", "description": "smooth time (minutes)"},
{"path": "set/channel", "type": "text", "readonly": false, "cmd": "tt set/channel"},
{"path": "set/limit", "type": "float", "readonly": false, "cmd": "tt set/limit"},
{"path": "set/resist", "type": "float", "readonly": false, "cmd": "tt set/resist"},
{"path": "set/maxheater", "type": "text", "readonly": false, "cmd": "tt set/maxheater", "description": "maximum heater limit, units should be given without space: W, mW, A, mA"},
{"path": "set/linearpower", "type": "float", "readonly": false, "cmd": "tt set/linearpower", "description": "when not 0, it is the maximum effective power, and the power is linear to the heater output"},
{"path": "set/maxpowerlim", "type": "float", "description": "the maximum power limit (before any booster or converter)"},
{"path": "set/maxpower", "type": "float", "readonly": false, "cmd": "tt set/maxpower", "description": "maximum power [W]"},
{"path": "set/maxcurrent", "type": "float", "description": "the maximum current before any booster or converter"},
{"path": "set/manualpower", "type": "float", "readonly": false, "cmd": "tt set/manualpower"},
{"path": "set/power", "type": "float"},
{"path": "set/prop", "type": "float", "readonly": false, "cmd": "tt set/prop", "description": "bigger means more gain"},
{"path": "set/integ", "type": "float", "readonly": false, "cmd": "tt set/integ", "description": "bigger means faster"},
{"path": "set/deriv", "type": "float", "readonly": false, "cmd": "tt set/deriv"},
{"path": "display", "type": "text", "readonly": false, "cmd": "tt display"},
{"path": "remote", "type": "bool"}]},
"cc": {"base": "/cc", "params": [
{"path": "", "type": "bool", "kids": 96},
{"path": "send", "type": "text", "readonly": false, "cmd": "cc send", "visibility": 3},
{"path": "status", "type": "text", "visibility": 3},
{"path": "autodevice", "type": "bool", "readonly": false, "cmd": "cc autodevice"},
{"path": "fav", "type": "bool", "readonly": false, "cmd": "cc fav"},
{"path": "f", "type": "float"},
{"path": "fs", "type": "enum", "enum": {"ok": 0, "no_sens": 1}, "readonly": false, "cmd": "cc fs"},
{"path": "mav", "type": "bool", "readonly": false, "cmd": "cc mav"},
{"path": "fm", "type": "enum", "enum": {"idle": 0, "opening": 1, "closing": 2, "opened": 3, "closed": 4, "no_motor": 5}},
{"path": "fa", "type": "enum", "enum": {"fixed": 0, "controlled": 1, "automatic": 2, "offline": 3}, "readonly": false, "cmd": "cc fa"},
{"path": "mp", "type": "float", "readonly": false, "cmd": "cc mp"},
{"path": "msp", "type": "float"},
{"path": "mmp", "type": "float"},
{"path": "mc", "type": "float", "readonly": false, "cmd": "cc mc"},
{"path": "mfc", "type": "float", "readonly": false, "cmd": "cc mfc"},
{"path": "moc", "type": "float", "readonly": false, "cmd": "cc moc"},
{"path": "mtc", "type": "float", "readonly": false, "cmd": "cc mtc"},
{"path": "mtl", "type": "float"},
{"path": "mft", "type": "float", "readonly": false, "cmd": "cc mft"},
{"path": "mt", "type": "float"},
{"path": "mo", "type": "float"},
{"path": "mcr", "type": "float"},
{"path": "mot", "type": "float"},
{"path": "mw", "type": "float", "readonly": false, "cmd": "cc mw", "description": "correction pulse after automatic open"},
{"path": "hav", "type": "enum", "type": "enum", "enum": {"none": 0, "int": 1, "ext": 2}, "readonly": false, "cmd": "cc hav"},
{"path": "h", "type": "float"},
{"path": "hr", "type": "float"},
{"path": "hc", "type": "float"},
{"path": "hu", "type": "float"},
{"path": "hh", "type": "float", "readonly": false, "cmd": "cc hh"},
{"path": "hl", "type": "float", "readonly": false, "cmd": "cc hl"},
{"path": "htf", "type": "float", "readonly": false, "cmd": "cc htf", "description": "meas. period in fast mode"},
{"path": "hts", "type": "float", "readonly": false, "cmd": "cc hts", "description": "meas. period in slow mode"},
{"path": "hd", "type": "float", "readonly": false, "cmd": "cc hd"},
{"path": "hwr", "type": "float", "readonly": false, "cmd": "cc hwr"},
{"path": "hem", "type": "float", "readonly": false, "cmd": "cc hem", "description": "sensor length in mm from top to empty pos."},
{"path": "hfu", "type": "float", "readonly": false, "cmd": "cc hfu", "description": "sensor length in mm from top to full pos."},
{"path": "hcd", "type": "enum", "enum": {"stop": 0, "fill": 1, "off": 2, "auto": 3, "manual": 7}, "readonly": false, "cmd": "cc hcd"},
{"path": "hv", "type": "enum", "enum": {"fill_valve_off": 0, "filling": 1, "no_fill_valve": 2, "timeout": 3, "timeout1": 4}},
{"path": "hsf", "type": "enum", "enum": {"sens_ok": 0, "sens_warm": 1, "no_sens": 2, "timeout": 3, "not_yet_read": 4, "disabled": 5}},
{"path": "ha", "type": "bool", "readonly": false, "cmd": "cc ha"},
{"path": "hm", "type": "bool"},
{"path": "hf", "type": "enum", "enum": {"slow": 0, "fast": 1}, "readonly": false, "cmd": "cc hf"},
{"path": "hbe", "type": "bool", "readonly": false, "cmd": "cc hbe"},
{"path": "hmf", "type": "float"},
{"path": "hms", "type": "float"},
{"path": "hit", "type": "float", "readonly": false, "cmd": "cc hit"},
{"path": "hft", "type": "int", "readonly": false, "cmd": "cc hft"},
{"path": "hea", "type": "enum", "enum": {"0": 0, "1": 1, "6": 2}, "readonly": false, "cmd": "cc hea"},
{"path": "hch", "type": "int", "readonly": false, "cmd": "cc hch", "visibility": 3},
{"path": "hwr0", "type": "float", "readonly": false, "cmd": "cc hwr0", "visibility": 3},
{"path": "hem0", "type": "float", "readonly": false, "cmd": "cc hem0", "description": "sensor length in mm from top to empty pos.", "visibility": 3},
{"path": "hfu0", "type": "float", "readonly": false, "cmd": "cc hfu0", "description": "sensor length in mm from top to full pos.", "visibility": 3},
{"path": "hd0", "type": "float", "readonly": false, "cmd": "cc hd0", "description": "external sensor drive current (mA)", "visibility": 3},
{"path": "h0", "type": "float", "visibility": 3},
{"path": "hs0", "type": "enum", "enum": {"sens_ok": 0, "sens_warm": 1, "no_sens": 2, "timeout": 3, "not_yet_read": 4, "disabled": 5}, "visibility": 3},
{"path": "h1", "type": "float", "visibility": 3},
{"path": "hs1", "type": "enum", "enum": {"sens_ok": 0, "sens_warm": 1, "no_sens": 2, "timeout": 3, "not_yet_read": 4, "disabled": 5}, "visibility": 3},
{"path": "h2", "type": "float", "visibility": 3},
{"path": "hs2", "type": "enum", "enum": {"sens_ok": 0, "sens_warm": 1, "no_sens": 2, "timeout": 3, "not_yet_read": 4, "disabled": 5}, "visibility": 3},
{"path": "h3", "type": "float", "visibility": 3},
{"path": "hs3", "type": "enum", "enum": {"sens_ok": 0, "sens_warm": 1, "no_sens": 2, "timeout": 3, "not_yet_read": 4, "disabled": 5}, "visibility": 3},
{"path": "h4", "type": "float", "visibility": 3},
{"path": "hs4", "type": "enum", "enum": {"sens_ok": 0, "sens_warm": 1, "no_sens": 2, "timeout": 3, "not_yet_read": 4, "disabled": 5}, "visibility": 3},
{"path": "h5", "type": "float", "visibility": 3},
{"path": "hs5", "type": "enum", "enum": {"sens_ok": 0, "sens_warm": 1, "no_sens": 2, "timeout": 3, "not_yet_read": 4, "disabled": 5}, "visibility": 3},
{"path": "hfb", "type": "float"},
{"path": "nav", "type": "enum", "type": "enum", "enum": {"none": 0, "int": 1, "ext": 2}, "readonly": false, "cmd": "cc nav"},
{"path": "nu", "type": "float"},
{"path": "nl", "type": "float"},
{"path": "nth", "type": "float", "readonly": false, "cmd": "cc nth"},
{"path": "ntc", "type": "float", "readonly": false, "cmd": "cc ntc"},
{"path": "ntm", "type": "float", "readonly": false, "cmd": "cc ntm"},
{"path": "ns", "type": "enum", "enum": {"sens_ok": 0, "no_sens": 1, "short_circuit": 2, "upside_down": 3, "sens_warm": 4, "empty": 5}},
{"path": "na", "type": "bool", "readonly": false, "cmd": "cc na"},
{"path": "nv", "type": "enum", "enum": {"fill_valve_off": 0, "filling": 1, "no_fill_valve": 2, "timeout": 3, "timeout1": 4, "boost": 5}},
{"path": "nc", "type": "enum", "enum": {"stop": 0, "fill": 1, "off": 2, "auto": 3}, "readonly": false, "cmd": "cc nc"},
{"path": "nfb", "type": "float"},
{"path": "cda", "type": "float"},
{"path": "cdb", "type": "float"},
{"path": "cba", "type": "float"},
{"path": "cbb", "type": "float"},
{"path": "cvs", "type": "int"},
{"path": "csp", "type": "int"},
{"path": "cdv", "type": "text", "readonly": false, "cmd": "cc cdv"},
{"path": "cic", "type": "text", "readonly": false, "cmd": "cc cic"},
{"path": "cin", "type": "text"},
{"path": "cds", "type": "enum", "enum": {"local": 0, "remote": 1, "loading": 2, "by_code": 3, "by_touch": 4}, "readonly": false, "cmd": "cc cds"},
{"path": "timing", "type": "bool", "readonly": false, "cmd": "cc timing"},
{"path": "tc", "type": "float", "visibility": 3},
{"path": "tn", "type": "float", "visibility": 3},
{"path": "th", "type": "float", "visibility": 3},
{"path": "tf", "type": "float", "visibility": 3},
{"path": "tm", "type": "float", "visibility": 3},
{"path": "tv", "type": "float", "visibility": 3},
{"path": "tq", "type": "float", "visibility": 3},
{"path": "bdl", "type": "float", "readonly": false, "cmd": "cc bdl"}]},
"nv": {"base": "/nv", "params": [
{"path": "", "type": "enum", "enum": {"fixed": 0, "controlled": 1, "automatic": 2, "close": 3, "open": 4}, "readonly": false, "cmd": "nv", "kids": 11},
{"path": "send", "type": "text", "readonly": false, "cmd": "nv send", "visibility": 3},
{"path": "status", "type": "text", "visibility": 3},
{"path": "motstat", "type": "enum", "enum": {"idle": 0, "opening": 1, "closing": 2, "opened": 3, "closed": 4, "no_motor": 5}},
{"path": "flow", "type": "float"},
{"path": "set", "type": "float", "readonly": false, "cmd": "nv set"},
{"path": "flowmax", "type": "float", "readonly": false, "cmd": "nv flowmax"},
{"path": "flowp", "type": "float"},
{"path": "span", "type": "float"},
{"path": "ctrl", "type": "none", "kids": 13},
{"path": "ctrl/regtext", "type": "text"},
{"path": "ctrl/prop_o", "type": "float", "readonly": false, "cmd": "nv ctrl/prop_o", "description": "prop [sec/mbar] when opening. above 4 mbar a 10 times lower value is used"},
{"path": "ctrl/prop_c", "type": "float", "readonly": false, "cmd": "nv ctrl/prop_c", "description": "prop [sec/mbar] when closing. above 4 mbar a 10 times lower value is used"},
{"path": "ctrl/deriv_o", "type": "float", "readonly": false, "cmd": "nv ctrl/deriv_o", "description": "convergence target time [sec] when opening"},
{"path": "ctrl/deriv_c", "type": "float", "readonly": false, "cmd": "nv ctrl/deriv_c", "description": "convergence target time [sec] when closing"},
{"path": "ctrl/minpulse_o", "type": "float", "readonly": false, "cmd": "nv ctrl/minpulse_o", "description": "minimum close pulse [sec]"},
{"path": "ctrl/minpulse_c", "type": "float", "readonly": false, "cmd": "nv ctrl/minpulse_c", "description": "standard close pulse [sec]"},
{"path": "ctrl/hystpulse_o", "type": "float", "readonly": false, "cmd": "nv ctrl/hystpulse_o", "description": "motor pulse to overcome hysteresis when opening"},
{"path": "ctrl/hystpulse_c", "type": "float", "readonly": false, "cmd": "nv ctrl/hystpulse_c", "description": "motor pulse to overcome hysteresis when closing"},
{"path": "ctrl/tol", "type": "float", "readonly": false, "cmd": "nv ctrl/tol", "description": "valid below 3 mbar"},
{"path": "ctrl/tolhigh", "type": "float", "readonly": false, "cmd": "nv ctrl/tolhigh", "description": "valid above 4 mbar"},
{"path": "ctrl/openpulse", "type": "float", "readonly": false, "cmd": "nv ctrl/openpulse", "description": "time to open from completely closed to a significant opening"},
{"path": "ctrl/adjust_minpulse", "type": "bool", "readonly": false, "cmd": "nv ctrl/adjust_minpulse", "description": "adjust minpulse automatically"},
{"path": "autoflow", "type": "none", "kids": 24},
{"path": "autoflow/suspended", "type": "bool", "readonly": false, "cmd": "nv autoflow/suspended"},
{"path": "autoflow/prop", "type": "float", "readonly": false, "cmd": "nv autoflow/prop"},
{"path": "autoflow/flowstd", "type": "float", "readonly": false, "cmd": "nv autoflow/flowstd"},
{"path": "autoflow/flowlim", "type": "float", "readonly": false, "cmd": "nv autoflow/flowlim"},
{"path": "autoflow/smooth", "type": "float", "readonly": false, "cmd": "nv autoflow/smooth"},
{"path": "autoflow/difSize", "type": "float", "readonly": false, "cmd": "nv autoflow/difSize"},
{"path": "autoflow/difRange", "type": "float", "readonly": false, "cmd": "nv autoflow/difRange"},
{"path": "autoflow/flowSize", "type": "float", "readonly": false, "cmd": "nv autoflow/flowSize"},
{"path": "autoflow/convTime", "type": "float", "readonly": false, "cmd": "nv autoflow/convTime"},
{"path": "autoflow/Tmin", "type": "float", "readonly": false, "cmd": "nv autoflow/Tmin"},
{"path": "autoflow/script", "type": "text", "readonly": false, "cmd": "nv autoflow/script"},
{"path": "autoflow/getTemp", "type": "text", "readonly": false, "cmd": "nv autoflow/getTemp"},
{"path": "autoflow/getTset", "type": "text", "readonly": false, "cmd": "nv autoflow/getTset"},
{"path": "autoflow/getFlow", "type": "text", "readonly": false, "cmd": "nv autoflow/getFlow"},
{"path": "autoflow/difBuf", "type": "text"},
{"path": "autoflow/flowBuf", "type": "text"},
{"path": "autoflow/flowset", "type": "float"},
{"path": "autoflow/flowmin", "type": "float"},
{"path": "autoflow/flowmax", "type": "float"},
{"path": "autoflow/difmin", "type": "float"},
{"path": "autoflow/difmax", "type": "float"},
{"path": "autoflow/setmin", "type": "float"},
{"path": "autoflow/setmax", "type": "float"},
{"path": "autoflow/flowtarget", "type": "float"},
{"path": "calib", "type": "none", "kids": 2},
{"path": "calib/ln_per_min_per_mbar", "type": "float", "readonly": false, "cmd": "nv calib/ln_per_min_per_mbar"},
{"path": "calib/mbar_offset", "type": "float", "readonly": false, "cmd": "nv calib/mbar_offset"}]},
"ln2fill": {"base": "/ln2fill", "params": [
{"path": "", "type": "enum", "enum": {"watching": 0, "fill": 1, "inactive": 2}, "readonly": false, "cmd": "ln2fill", "kids": 3},
{"path": "send", "type": "text", "readonly": false, "cmd": "ln2fill send", "visibility": 3},
{"path": "status", "type": "text", "visibility": 3},
{"path": "state", "type": "text"}]},
"hefill": {"base": "/hefill", "params": [
{"path": "", "type": "enum", "enum": {"watching": 0, "fill": 1, "inactive": 2, "manualfill": 3}, "readonly": false, "cmd": "hefill", "kids": 6},
{"path": "send", "type": "text", "readonly": false, "cmd": "hefill send", "visibility": 3},
{"path": "status", "type": "text", "visibility": 3},
{"path": "fast", "type": "enum", "enum": {"slow": 0, "fast": 1}, "readonly": false, "cmd": "cc hf"},
{"path": "state", "type": "text"},
{"path": "hefull", "type": "float", "readonly": false, "cmd": "cc hh"},
{"path": "helow", "type": "float", "readonly": false, "cmd": "cc hl"}]},
"hepump": {"base": "/hepump", "params": [
{"path": "", "type": "enum", "enum": {"neodry": 8, "xds35_auto": 0, "xds35_manual": 1, "sv65": 2, "other": 3, "no": -1}, "readonly": false, "cmd": "hepump", "description": "xds35: scroll pump, sv65: leybold", "kids": 10},
{"path": "send", "type": "text", "readonly": false, "cmd": "hepump send", "visibility": 3},
{"path": "status", "type": "text", "visibility": 3},
{"path": "running", "type": "bool", "readonly": false, "cmd": "hepump running"},
{"path": "eco", "type": "bool", "readonly": false, "cmd": "hepump eco", "visibility": 3},
{"path": "auto", "type": "bool", "readonly": false, "cmd": "hepump auto", "visibility": 3},
{"path": "valve", "type": "enum", "enum": {"closed": 0, "closing": 1, "opening": 2, "opened": 3, "undefined": 4}, "readonly": false, "cmd": "hepump valve"},
{"path": "eco_t_lim", "type": "float", "readonly": false, "cmd": "hepump eco_t_lim", "description": "switch off eco mode when T_set < eco_t_lim and T < eco_t_lim * 2", "visibility": 3},
{"path": "calib", "type": "float", "readonly": false, "cmd": "hepump calib", "visibility": 3},
{"path": "health", "type": "float"}]},
"hemot": {"base": "/hepump/hemot", "params": [
{"path": "", "type": "float", "readonly": false, "cmd": "run hemot", "visibility": 3, "kids": 30},
{"path": "send", "type": "text", "readonly": false, "cmd": "hemot send", "visibility": 3},
{"path": "status", "type": "text", "visibility": 3},
{"path": "is_running", "type": "int", "readonly": false, "cmd": "hemot is_running", "visibility": 3},
{"path": "pos", "type": "float"},
{"path": "encoder", "type": "float"},
{"path": "zero", "type": "float", "readonly": false, "cmd": "hemot zero"},
{"path": "lowerlimit", "type": "float", "readonly": false, "cmd": "hemot lowerlimit"},
{"path": "upperlimit", "type": "float", "readonly": false, "cmd": "hemot upperlimit"},
{"path": "disablelimits", "type": "bool", "readonly": false, "cmd": "hemot disablelimits"},
{"path": "verbose", "type": "bool", "readonly": false, "cmd": "hemot verbose"},
{"path": "target", "type": "float"},
{"path": "runstate", "type": "enum", "enum": {"idle": 0, "running": 1, "finished": 2, "error": 3}},
{"path": "precision", "type": "float", "readonly": false, "cmd": "hemot precision"},
{"path": "maxencdif", "type": "float", "readonly": false, "cmd": "hemot maxencdif"},
{"path": "id", "type": "float", "readonly": false, "cmd": "hemot id"},
{"path": "pump_number", "type": "float", "readonly": false, "cmd": "hemot pump_number"},
{"path": "init", "type": "float", "readonly": false, "cmd": "hemot init"},
{"path": "maxspeed", "type": "float", "readonly": false, "cmd": "hemot maxspeed"},
{"path": "acceleration", "type": "float", "readonly": false, "cmd": "hemot acceleration"},
{"path": "maxcurrent", "type": "float", "readonly": false, "cmd": "hemot maxcurrent"},
{"path": "standbycurrent", "type": "float", "readonly": false, "cmd": "hemot standbycurrent"},
{"path": "freewheeling", "type": "bool", "readonly": false, "cmd": "hemot freewheeling"},
{"path": "output0", "type": "bool", "readonly": false, "cmd": "hemot output0"},
{"path": "output1", "type": "bool", "readonly": false, "cmd": "hemot output1"},
{"path": "input3", "type": "bool"},
{"path": "pullup", "type": "float", "readonly": false, "cmd": "hemot pullup"},
{"path": "nopumpfeedback", "type": "bool", "readonly": false, "cmd": "hemot nopumpfeedback"},
{"path": "eeprom", "type": "enum", "enum": {"ok": 0, "dirty": 1, "save": 2, "load": 3}, "readonly": false, "cmd": "hemot eeprom"},
{"path": "customadr", "type": "text", "readonly": false, "cmd": "hemot customadr"},
{"path": "custompar", "type": "float", "readonly": false, "cmd": "hemot custompar"}]},
"nvflow": {"base": "/nvflow", "params": [
{"path": "", "type": "float", "kids": 7},
{"path": "send", "type": "text", "readonly": false, "cmd": "nvflow send", "visibility": 3},
{"path": "status", "type": "text", "visibility": 3},
{"path": "stddev", "type": "float"},
{"path": "nsamples", "type": "int", "readonly": false, "cmd": "nvflow nsamples"},
{"path": "offset", "type": "float", "readonly": false, "cmd": "nvflow offset"},
{"path": "scale", "type": "float", "readonly": false, "cmd": "nvflow scale"},
{"path": "save", "type": "bool", "readonly": false, "cmd": "nvflow save", "description": "unchecked: current calib is not saved. set checked: save calib"}]},
"table": {"base": "/table", "params": [
{"path": "", "type": "none", "kids": 17},
{"path": "send", "type": "text", "readonly": false, "cmd": "table send", "visibility": 3},
{"path": "status", "type": "text", "visibility": 3},
{"path": "fix_tt_set_prop", "type": "bool", "readonly": false, "cmd": "table fix_tt_set_prop"},
{"path": "val_tt_set_prop", "type": "float"},
{"path": "tbl_tt_set_prop", "type": "text", "readonly": false, "cmd": "table tbl_tt_set_prop", "description": "enter value pair separated with colon T1:par1 T2:par2 ..."},
{"path": "fix_tt_set_integ", "type": "bool", "readonly": false, "cmd": "table fix_tt_set_integ"},
{"path": "val_tt_set_integ", "type": "float"},
{"path": "tbl_tt_set_integ", "type": "text", "readonly": false, "cmd": "table tbl_tt_set_integ", "description": "enter value pair separated with colon T1:par1 T2:par2 ..."},
{"path": "fix_tt_dblctrl_int2", "type": "bool", "readonly": false, "cmd": "table fix_tt_dblctrl_int2"},
{"path": "val_tt_dblctrl_int2", "type": "float"},
{"path": "tbl_tt_dblctrl_int2", "type": "text", "readonly": false, "cmd": "table tbl_tt_dblctrl_int2", "description": "enter value pair separated with colon T1:par1 T2:par2 ..."},
{"path": "fix_tt_dblctrl_prop_up", "type": "bool", "readonly": false, "cmd": "table fix_tt_dblctrl_prop_up"},
{"path": "val_tt_dblctrl_prop_up", "type": "float"},
{"path": "tbl_tt_dblctrl_prop_up", "type": "text", "readonly": false, "cmd": "table tbl_tt_dblctrl_prop_up", "description": "enter value pair separated with colon T1:par1 T2:par2 ..."},
{"path": "fix_tt_dblctrl_prop_lo", "type": "bool", "readonly": false, "cmd": "table fix_tt_dblctrl_prop_lo"},
{"path": "val_tt_dblctrl_prop_lo", "type": "float"},
{"path": "tbl_tt_dblctrl_prop_lo", "type": "text", "readonly": false, "cmd": "table tbl_tt_dblctrl_prop_lo", "description": "enter value pair separated with colon T1:par1 T2:par2 ..."}]}}

View File

@ -20,8 +20,8 @@ Mod('tsam',
Mod('ts',
'frappy_psi.parmod.Converging',
'virtual stick T',
meaning=['temperature', 30],
'stick T (controlled)',
meaning=['temperature', 25],
unit='K',
read='tsam.value',
write='tsam.setsamp',

View File

@ -16,7 +16,7 @@ Mod('ts',
json_file='ma6.config.json',
sea_object='tt',
rel_paths=['ts', 'setsamp'],
meaning=['temperature', 20],
meaning=['temperature', 30],
)
@ -24,11 +24,10 @@ Mod('ts',
Mod('ts',
'frappy_psi.parmod.Converging',
'drivable stick T using setsamp',
meaning=['temperature', 30],
meaning=['temperature', 25],
unit='K',
read='tsam.value',
write='tsam.setsamp',
meaning=['temperature', 20],
settling_time=20,
tolerance=1,
)

View File

@ -13,8 +13,6 @@ Mod('ts',
'frappy_psi.sea.SeaReadable', '',
meaning=['temperature', 30],
io='sea_stick',
sea_object='tt',
sea_path='tt/ts',
json_file='ma7.config.json',
rel_paths=['ts'],
)

75
debian/changelog vendored
View File

@ -1,3 +1,78 @@
frappy-core (0.20.4) jammy; 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) jammy; 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) jammy; 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) jammy; urgency=medium
* gui: do not add a console logger when there is no sys.stdout

View File

@ -1,6 +1,7 @@
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
@ -11,3 +12,4 @@ usr/lib/python3.*/dist-packages/frappy/RELEASE-VERSION
usr/lib/python3.*/dist-packages/frappy_demo
lib/systemd
var/log/frappy
etc/frappy/generalConfig.cfg

1
debian/rules vendored
View File

@ -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

4
etc/generalConfig.cfg Normal file
View File

@ -0,0 +1,4 @@
[FRAPPY]
logdir = /var/log
piddir = /var/run/frappy
confdir = /etc/frappy

View File

@ -22,8 +22,6 @@
# *****************************************************************************
"""general SECoP client"""
# pylint: disable=too-many-positional-arguments
import json
import queue
import re
@ -481,7 +479,10 @@ class SecopClient(ProxyClient):
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

View File

@ -56,10 +56,12 @@ class Param(dict):
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__(
@ -70,7 +72,8 @@ class Mod(dict):
# 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?')
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():
@ -85,6 +88,7 @@ class Mod(dict):
for member in members:
self[member]['group'] = group
class Collector:
def __init__(self, cls):
self.list = []
@ -120,12 +124,14 @@ class Config(dict):
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[name] = mod
mod['original_id'] = equipment_id
def process_file(filename, log):
@ -172,8 +178,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
@ -181,8 +187,8 @@ 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)
cfg = process_file(filename, log)
if config:

View File

@ -218,8 +218,9 @@ def write_config(file_name, tree_widget):
with open(file_name, 'w', encoding='utf-8') as configfile:
configfile.write('\n'.join(lines))
def read_config(file_path, log):
config = load_config(file_path, log)
config = load_config([file_path], log)
node = TreeWidgetItem(NODE)
ifs = TreeWidgetItem(name='Interfaces')
mods = TreeWidgetItem(name='Modules')

View File

@ -106,6 +106,7 @@ def get_file_paths(widget, open_file=True):
def get_modules():
modules = {}
if not generalConfig.initialized:
generalConfig.init()
base_path = generalConfig.basedir
# pylint: disable=too-many-nested-blocks
@ -157,6 +158,7 @@ def get_interface_class_from_name(name):
def get_interfaces():
# TODO class must be found out like for modules
interfaces = []
if not generalConfig.initialized:
generalConfig.init()
interface_path = path.join(generalConfig.basedir, 'frappy',
'protocol', 'interface')

View File

@ -57,11 +57,12 @@ class GeneralConfig:
:param configfile: if present, keys and values from the [FRAPPY] section are read
default values for 'piddir', 'logdir' and 'confdir' are guessed from the
location of this source file and from sys.executable.
if configfile is not given, the general config file is determined by
the env. variable FRAPPY_CONFIG_FILE or <confdir>/generalConfig.cfg is used
The following locations are searched for the generalConfig.cfg file.
- command line argument
- environment variable FRAPPY_CONFIG_FILE
- git location (../cfg)
- local location (cwd)
- global location (/etc/frappy)
if a configfile is given, the values from the FRAPPY section are
overriding above defaults
@ -69,37 +70,12 @@ class GeneralConfig:
finally, the env. variables FRAPPY_PIDDIR, FRAPPY_LOGDIR and FRAPPY_CONFDIR
are overriding these values when given
"""
configfile = self._get_file_location(configfile)
cfg = {}
mandatory = 'piddir', 'logdir', 'confdir'
repodir = Path(__file__).parents[2].expanduser().resolve()
# create default paths
if (Path(sys.executable).suffix == ".exe"
and not Path(sys.executable).name.startswith('python')):
# special MS windows environment
confdir = Path('./')
self.update_defaults(piddir=Path('./'), logdir=Path('./log'))
elif path.exists(path.join(repodir, 'cfg')):
# running from git repo
confdir = repodir / 'cfg'
# take logdir and piddir from <repodir>/cfg/generalConfig.cfg
else:
# running on installed system (typically with systemd)
self.update_defaults(
piddir=Path('/var/run/frappy'),
logdir=Path('/var/log'),
)
confdir = Path('/etc/frappy')
self.set_default('confdir', confdir)
if configfile is None:
configfile = environ.get('FRAPPY_CONFIG_FILE')
if configfile:
configfile = Path(configfile).expanduser()
if not configfile.exists():
raise FileNotFoundError(configfile)
else:
configfile = confdir / 'generalConfig.cfg'
if not configfile.exists():
configfile = None
if configfile:
parser = ConfigParser()
parser.optionxform = str
@ -113,28 +89,63 @@ class GeneralConfig:
cfg[key] = ':'.join(path.expanduser(v) for v in value.split(':'))
if cfg.get('confdir') is None:
cfg['confdir'] = configfile.parent
# environment variables will overwrite the config file
missing_keys = []
for key in mandatory:
env = environ.get(f'FRAPPY_{key.upper()}')
if env is not None:
if ':' in env:
cfg[key] = [Path(v) for v in env.split(':')]
env = environ.get(f'FRAPPY_{key.upper()}') or cfg.get(key)
if env is None:
if self.defaults.get(key) is None:
missing_keys.append(key)
else:
cfg[key] = Path(env)
missing_keys = [
key for key in mandatory
if cfg.get(key) is None and self.defaults.get(key) is None
]
if not isinstance(env, Path):
if key == 'confdir':
env = [Path(v) for v in env.split(':')]
else:
env = Path(env)
cfg[key] = env
if missing_keys:
if configfile:
raise KeyError(f"missing value for {' and '.join(missing_keys)} in {configfile}")
raise KeyError('missing %s'
% ' and '.join('FRAPPY_%s' % k.upper() for k in missing_keys))
if len(missing_keys) < 3:
# user specified at least one env variable already
missing = ' (missing %s)' % ', '.join('FRAPPY_%s' % k.upper() for k in missing_keys)
else:
missing = ''
raise FileNotFoundError(
'Could not determine config file location for the general frappy config. '
f'Provide a config file or all required environment variables{missing}. '
'For more information, see frappy-server --help.'
)
if 'confdir' in cfg and isinstance(cfg['confdir'], Path):
cfg['confdir'] = [cfg['confdir']]
# this is not customizable
cfg['basedir'] = repodir
self._config = cfg
def _get_file_location(self, configfile):
"""Determining the defaultConfig.cfg location as documented in init()"""
# given as command line arg
if configfile and Path(configfile).exists():
return configfile
# if not given as argument, check different sources
# env variable
fromenv = environ.get('FRAPPY_CONFIG_FILE')
if fromenv and Path(fromenv).exists():
return fromenv
# from ../cfg (there if running from checkout)
repodir = Path(__file__).parents[2].expanduser().resolve()
if (repodir / 'cfg' / 'generalConfig.cfg').exists():
return repodir / 'cfg' / 'generalConfig.cfg'
localfile = Path.cwd() / 'generalConfig.cfg'
if localfile.exists():
return localfile
# TODO: leave this hardcoded?
globalfile = Path('/etc/frappy/generalConfig.cfg')
if globalfile.exists():
return globalfile
return None
def __getitem__(self, key):
"""access for keys known to exist

View File

@ -55,7 +55,7 @@ class MultiEvent(threading.Event):
def __init__(self, default_timeout=None):
self.events = set()
self._lock = threading.Lock()
self._lock = threading.RLock()
self.default_timeout = default_timeout or None # treat 0 as None
self.name = None # default event name
self._actions = [] # actions to be executed on trigger

View File

@ -33,7 +33,7 @@ from frappy.datatypes import ArrayOf, BoolType, EnumType, FloatRange, \
from frappy.errors import BadValueError, CommunicationFailedError, ConfigError, \
ProgrammingError, SECoPError, secop_error, RangeError
from frappy.lib import formatException, mkthread, UniqueObject
from frappy.params import Accessible, Command, Parameter, Limit
from frappy.params import Accessible, Command, Parameter, Limit, PREDEFINED_ACCESSIBLES
from frappy.properties import HasProperties, Property
from frappy.logging import RemoteLogHandler
@ -41,6 +41,7 @@ from frappy.logging import RemoteLogHandler
# from .interfaces import SECoP_BASE_CLASSES
# WORKAROUND:
SECoP_BASE_CLASSES = ['Readable', 'Writable', 'Drivable', 'Communicator']
PREDEF_ORDER = list(PREDEFINED_ACCESSIBLES)
Done = UniqueObject('Done')
"""a special return value for a read_<param>/write_<param> method
@ -77,7 +78,7 @@ class HasAccessibles(HasProperties):
for key, value in base.__dict__.items():
if isinstance(value, Accessible):
value.updateProperties(merged_properties.setdefault(key, {}))
if base == cls and key not in accessibles:
if base == cls and key not in accessibles and key not in PREDEFINED_ACCESSIBLES:
new_names.append(key)
accessibles[key] = value
override_values.pop(key, None)
@ -97,16 +98,14 @@ class HasAccessibles(HasProperties):
aobj.merge(merged_properties[aname])
accessibles[aname] = aobj
# rebuild order: (1) inherited items, (2) items from paramOrder, (3) new accessibles
# move (2) to the end
paramOrder = cls.__dict__.get('paramOrder', ())
for aname in paramOrder:
if aname in accessibles:
accessibles.move_to_end(aname)
# ignore unknown names
# rebuild order:
# (1) predefined accessibles, in a predefined order, (2) inherited custom items, (3) new custom items
# move (1) to the beginning
for key in reversed(PREDEF_ORDER):
if key in accessibles:
accessibles.move_to_end(key, last=False)
# move (3) to the end
for aname in new_names:
if aname not in paramOrder:
accessibles.move_to_end(aname)
cls.accessibles = accessibles
@ -189,10 +188,8 @@ class HasAccessibles(HasProperties):
if new_value is Done: # TODO: to be removed when all code using Done is updated
return getattr(self, pname)
new_value = value if new_value is None else validate(new_value)
except Exception as e:
if isinstance(e, SECoPError):
except SECoPError as e:
e.raising_methods.append(f'{self.name}.write_{pname}')
self.announceUpdate(pname, err=e)
raise
self.announceUpdate(pname, new_value, validate=False)
return new_value
@ -322,6 +319,8 @@ class Module(HasAccessibles):
slowinterval = Property('poll interval for other parameters', FloatRange(0.1, 120), default=15)
omit_unchanged_within = Property('default for minimum time between updates of unchanged values',
NoneOr(FloatRange(0)), export=False, default=None)
original_id = Property('original equipment_id\n\ngiven only if different from equipment_id of node',
NoneOr(StringType()), default=None, export=True) # exported as custom property _original_id
enablePoll = True
pollInfo = None
@ -517,13 +516,13 @@ class Module(HasAccessibles):
with self.updateLock:
pobj = self.parameters[pname]
timestamp = timestamp or time.time()
changed = False
if not err:
try:
if validate:
value = pobj.datatype(value)
except Exception as e:
err = e
changed = False
else:
changed = pobj.value != value or pobj.readerror
# store the value even in case of error

View File

@ -259,8 +259,16 @@ class Parameter(Accessible):
merged_properties.update(self.ownProperties)
def create_from_value(self, properties, value):
"""return a clone with given value and inherited properties"""
return self.clone(properties, value=self.datatype(value))
"""return a clone with given value and inherited properties
called when a Parameter is overridden with a bare value
"""
try:
value = self.datatype(value)
except Exception as e:
raise ProgrammingError(f'{self.name} must be assigned to a Parameter '
f'or a value compatible with {type(self.datatype).__name__}') from e
return self.clone(properties, value=value)
def merge(self, merged_properties):
"""merge with inherited properties
@ -462,7 +470,8 @@ class Command(Accessible):
def create_from_value(self, properties, value):
"""return a clone with given value and inherited properties
this is needed when the @Command is missing on a method overriding a command"""
called when the @Command is missing on a method overriding a command
"""
if not callable(value):
raise ProgrammingError(f'{self.name} = {value!r} is overriding a Command')
return self.clone(properties)(value)
@ -564,15 +573,18 @@ class Limit(Parameter):
# list of predefined accessibles with their type
# the order of this list affects the parameter order
PREDEFINED_ACCESSIBLES = {
'value': Parameter,
'status': Parameter,
'target': Parameter,
'pollinterval': Parameter,
'ramp': Parameter,
'user_ramp': Parameter,
'use_ramp': Parameter,
'setpoint': Parameter,
'time_to_target': Parameter,
'controlled_by': Parameter,
'control_active': Parameter,
'unit': Parameter, # reserved name
'loglevel': Parameter, # reserved name
'mode': Parameter, # reserved name

View File

@ -97,7 +97,9 @@ class Playground(Server):
for modname, cfg in kwds.items():
cfg.setdefault('description', modname)
self.log = logger.log
self.node_cfg = {'cls': 'frappy.playground.Dispatcher', 'name': 'playground'}
self.node_cfg = {'cls': 'frappy.playground.Dispatcher',
'name': 'playground',
'description': 'playground'}
self._testonly = True # stops before calling startModule
self._cfgfiles = 'main'
self.module_cfg = {}
@ -106,6 +108,7 @@ class Playground(Server):
if cfgfiles:
if not generalConfig.initialized:
generalConfig.init()
cfgfiles = [s.strip() for s in cfgfiles.split(',')]
merged_cfg = load_config(cfgfiles, self.log)
merged_cfg.pop('node', None)
self.module_cfg = merged_cfg

View File

@ -143,6 +143,7 @@ class HasProperties(HasDescriptors):
try:
# try to apply bare value to Property
po.value = po.datatype.validate(value)
setattr(cls, pn, po) # replace bare value by updated Property
except BadValueError:
if callable(value):
raise ProgrammingError(f'method {cls.__name__}.{pn} collides with property of {base.__name__}') from None

View File

@ -0,0 +1,108 @@
# *****************************************************************************
#
# 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>
#
# *****************************************************************************
"""Discovery via UDP broadcasts."""
import os
import json
import socket
from frappy.lib import closeSocket
from frappy.protocol.interface.tcp import format_address
from frappy.version import get_version
UDP_PORT = 10767
MAX_MESSAGE_LEN = 508
class UDPListener:
def __init__(self, equipment_id, description, ifaces, logger, *,
startup_broadcast=True):
self.equipment_id = equipment_id
self.log = logger
self.description = description or ''
self.firmware = 'FRAPPY ' + get_version()
self.ports = [int(iface.split('://')[1])
for iface in ifaces if iface.startswith('tcp')]
self.running = False
self.is_enabled = True
self.startup_broadcast = startup_broadcast
self.sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
if os.name == 'nt':
self.sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
else:
self.sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1)
if startup_broadcast:
self.sock.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
self.sock.bind(('0.0.0.0', UDP_PORT))
available = MAX_MESSAGE_LEN - len(self._getMessage(2**16-1))
if available < 0:
desc_length = len(self.description.encode('utf-8'))
if available + desc_length < 0:
self.log.warn('Equipment id and firmware name exceed 430 byte '
'limit, not answering to udp discovery')
self.is_enabled = False
else:
self.log.debug('truncating description for udp discovery')
# with errors='ignore', cutting insite a utf-8 glyph will not
# report an error but remove the rest of the glyph from the
# output.
self.description = self.description \
.encode('utf-8')[:available] \
.decode('utf-8', errors='ignore')
def _getMessage(self, port):
return json.dumps({
'SECoP': 'node',
'port': port,
'equipment_id': self.equipment_id,
'firmware': self.firmware,
'description': self.description,
}, ensure_ascii=False, separators=(',', ':')).encode('utf-8')
def run(self):
if self.startup_broadcast:
self.log.debug('Sending startup UDP broadcast.')
for port in self.ports:
self.sock.sendto(self._getMessage(port),
('255.255.255.255', UDP_PORT))
self.running = True
while self.running and self.is_enabled:
try:
msg, addr = self.sock.recvfrom(1024)
except socket.error:
return
try:
request = json.loads(msg.decode('utf-8'))
except json.JSONDecodeError:
continue
if 'SECoP' not in request or request['SECoP'] != 'discover':
continue
self.log.debug('Answering UDP broadcast from: %s',
format_address(addr))
for port in self.ports:
self.sock.sendto(self._getMessage(port), addr)
def shutdown(self):
self.log.debug('shut down of discovery listener')
self.running = False
closeSocket(self.sock)

View File

@ -77,25 +77,29 @@ class SecopClient(frappy.client.SecopClient):
class Router(frappy.protocol.dispatcher.Dispatcher):
singlenode = None
def __init__(self, name, logger, options, srv):
"""initialize router
Use the option node = <uri> for a single node or
nodes = ["<uri1>", "<uri2>" ...] for multiple nodes.
If a single node is given, the node properties are forwarded transparently,
If a single node is given, and no more additional modules are given,
the node properties are forwarded transparently,
else the description property is a merge from all client node properties.
"""
uri = options.pop('node', None)
uris = options.pop('nodes', None)
if uri and uris:
raise frappy.errors.ConfigError('can not specify node _and_ nodes')
super().__init__(name, logger, options, srv)
if uri:
self.nodes = [SecopClient(uri, logger.getChild('routed'), self)]
self.singlenode = self.nodes[0]
try:
if uris is not None:
if isinstance(uris, str) or not all(isinstance(v, str) for v in uris) or uri:
raise TypeError()
elif isinstance(uri, str):
uris = [uri]
else:
raise TypeError()
except Exception as e:
raise frappy.errors.ConfigError("a router needs either 'node' as a string'"
"' or 'nodes' as a list of strings") from e
super().__init__(name, logger, options, srv)
self.nodes = [SecopClient(uri, logger.getChild(f'routed{i}'), self) for i, uri in enumerate(uris)]
# register callbacks
for node in self.nodes:
@ -127,8 +131,8 @@ class Router(frappy.protocol.dispatcher.Dispatcher):
logger.warning('can not connect to node %r', node.nodename)
def handle_describe(self, conn, specifier, data):
if self.singlenode:
return DESCRIPTIONREPLY, specifier, self.singlenode.descriptive_data
if len(self.nodes) == 1 and not self.secnode.modules:
return DESCRIPTIONREPLY, specifier, self.nodes[0].descriptive_data
reply = super().handle_describe(conn, specifier, data)
result = reply[2]
allmodules = result.get('modules', {})
@ -144,6 +148,7 @@ class Router(frappy.protocol.dispatcher.Dispatcher):
self.log.info('module %r is already present', modname)
else:
allmodules[modname] = moddesc
moddesc.setdefault('original_id', equipment_id)
result['modules'] = allmodules
result['description'] = '\n\n'.join(node_description)
return DESCRIPTIONREPLY, specifier, result

View File

@ -54,8 +54,17 @@ class SecNode:
self.name = name
def add_secnode_property(self, prop, value):
"""Add SECNode property. If starting with an underscore, it is exported
in the description."""
self.nodeprops[prop] = value
def get_secnode_property(self, prop):
"""Get SECNode property.
Returns None if not present.
"""
return self.nodeprops.get(prop)
def get_module(self, modulename):
""" Returns a fully initialized module. Or None, if something went
wrong during instatiating/initializing the module."""

View File

@ -26,6 +26,7 @@ import os
import signal
import sys
import threading
import time
import mlzlog
@ -36,6 +37,7 @@ from frappy.lib.multievent import MultiEvent
from frappy.logging import init_remote_logging
from frappy.params import PREDEFINED_ACCESSIBLES
from frappy.secnode import SecNode
from frappy.protocol.discovery import UDPListener
try:
from daemon import DaemonContext
@ -67,9 +69,9 @@ class Server:
- name: the node name
- parent_logger: the logger to inherit from. a handler is installed by
the server to provide remote logging
- cfgfiles: if not given, defaults to name
may be a comma separated list of cfg files
items ending with .cfg are taken as paths, else .cfg is appended and
- cfgfiles: if not given, defaults to [name]
may be a list of cfg files
items ending with .py are taken as paths, else _cfg.py is appended and
files are looked up in the config path retrieved from the general config
- interface: an uri of the from tcp://<port> or a bare port number for tcp
if not given, the interface is taken from the config file. In case of
@ -93,9 +95,9 @@ class Server:
self._testonly = testonly
if not cfgfiles:
cfgfiles = name
cfgfiles = [name]
# sanitize name (in case it is a cfgfile)
name = os.path.splitext(os.path.basename(name))[0]
self.name = name = os.path.splitext(os.path.basename(name))[0]
if isinstance(parent_logger, mlzlog.MLZLogger):
self.log = parent_logger.getChild(name, True)
else:
@ -106,7 +108,6 @@ class Server:
self.node_cfg = merged_cfg.pop('node')
self.module_cfg = merged_cfg
if interface:
self.node_cfg['equipment_id'] = name
self.node_cfg['interface'] = str(interface)
elif not self.node_cfg.get('interface'):
raise ConfigError('No interface specified in configuration or arguments!')
@ -116,6 +117,8 @@ class Server:
signal.signal(signal.SIGINT, self.signal_handler)
signal.signal(signal.SIGTERM, self.signal_handler)
self.discovery = None
def signal_handler(self, num, frame):
if hasattr(self, 'interfaces') and self.interfaces:
self.shutdown()
@ -164,6 +167,7 @@ class Server:
print(formatException(verbose=True))
raise
# client interfaces
self.interfaces = {}
iface_threads = []
# default_timeout 12 sec: TCPServer might need up to 10 sec to wait for Address no longer in use
@ -171,7 +175,9 @@ class Server:
lock = threading.Lock()
failed = {}
interfaces = [self.node_cfg['interface']] + self.node_cfg.get('secondary', [])
# TODO: check if only one interface of each type is open?
# allow missing "tcp://"
interfaces = [iface if '://' in iface else f'tcp://{iface}' for iface in interfaces]
with lock:
for interface in interfaces:
opts = {'uri': interface}
t = mkthread(
@ -179,6 +185,7 @@ class Server:
opts,
lock,
failed,
interfaces,
interfaces_started.get_trigger(),
)
iface_threads.append(t)
@ -192,13 +199,31 @@ class Server:
if not self.interfaces:
self.log.error('no interface started')
return
self.secnode.add_secnode_property('_interfaces', list(self.interfaces.keys()))
self.log.info('startup done with interface(s) %s' % ', '.join(self.interfaces))
self.secnode.add_secnode_property('_interfaces', list(self.interfaces))
self.log.info('startup done with interface(s) %s',
', '.join(self.interfaces))
# start discovery interface when we know where we listen
self.discovery = UDPListener(
self.secnode.equipment_id,
self.secnode.get_secnode_property('description'),
list(self.interfaces),
self.log.getChild('discovery')
)
mkthread(self.discovery.run)
if systemd:
systemd.daemon.notify("READY=1\nSTATUS=accepting requests")
# we wait here on the thread finishing, which means we got a
# signal to shut down or an exception was raised
if os.name == 'nt':
# workaround: thread.join() on Windows blocks and is not
# interruptible by the signal handler, so loop and check
# periodically whether the interfaces are still running.
while True:
time.sleep(1)
if not interfaces:
break
else:
for t in iface_threads:
t.join()
@ -208,11 +233,11 @@ class Server:
self.log.info('stopped listening, cleaning up %d modules',
len(self.secnode.modules))
# if systemd:
# if self._restart:
# systemd.daemon.notify('RELOADING=1')
# else:
# systemd.daemon.notify('STOPPING=1')
if systemd:
if self._restart:
systemd.daemon.notify('RELOADING=1')
else:
systemd.daemon.notify('STOPPING=1')
self.secnode.shutdown_modules()
if self._restart:
self.restart_hook()
@ -227,13 +252,14 @@ class Server:
def shutdown(self):
self._restart = False
if self.discovery:
self.discovery.shutdown()
for iface in self.interfaces.values():
iface.shutdown()
def _interfaceThread(self, opts, lock, failed, start_cb):
def _interfaceThread(self, opts, lock, failed, interfaces, start_cb):
iface = opts['uri']
scheme, _, _ = iface.rpartition('://')
scheme = scheme or 'tcp'
scheme = iface.split('://')[0]
cls = get_class(self.INTERFACES[scheme])
try:
with cls(scheme, self.log.getChild(scheme), opts, self) as interface:
@ -247,8 +273,11 @@ class Server:
except Exception as e:
with lock:
failed[iface] = e
start_cb()
return
interfaces.remove(iface)
start_cb() # callback should also be called on failure
else:
with lock:
interfaces.remove(iface)
self.log.info(f'stopped {iface}')
def _processCfg(self):
@ -263,10 +292,8 @@ class Server:
errors = []
opts = dict(self.node_cfg)
cls = get_class(opts.pop('cls'))
name = opts.pop('name', self._cfgfiles)
# TODO: opts not in both
self.secnode = SecNode(name, self.log.getChild('secnode'), opts, self)
self.dispatcher = cls(name, self.log.getChild('dispatcher'), opts, self)
self.secnode = SecNode(self.name, self.log.getChild('secnode'), opts, self)
self.dispatcher = cls(self.name, self.log.getChild('dispatcher'), opts, self)
# add other options as SECNode properties, those with '_' prefixed will
# get exported

View File

@ -69,14 +69,16 @@ 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))
heater_range = Property('heater power range', IntRange(0, 5)) # max. 3 on LakeShore 336
heater_range = Property('heater power range', IntRange(0, 3), readonly=False)
tolerance = Parameter('convergence criterion', FloatRange(0), default=0.1, readonly=False)
_driving = False
def write_heater_range(self, value):
self.communicate(f'RANGE {self.loop},{value};RANGE?{self.loop}')
def write_target(self, target):
# reactivate heater in case it was switched off
# the command has to be changed in case of model 340 to f'RANGE {self.heater_range};RANGE?'
self.communicate(f'RANGE {self.loop},{self.heater_range};RANGE?{self.loop}')
self.write_heater_range(self.heater_range)
self.communicate(f'SETP {self.loop},{target};*OPC?')
self._driving = True
# Setting the status attribute triggers an update message for the SECoP status
@ -85,23 +87,21 @@ class TemperatureLoop(TemperatureSensor, Drivable):
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:
status = super().read_status()
if status[0] == ERROR:
return status
if 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
# within tolerance: simple convergence criterion
self._driving = False
return IDLE, ''
return ERROR, text
class TemperatureLoop340(TemperatureLoop):
# slightly different behaviour for model 340
heater_range = Property('heater power range', IntRange(0, 5))
def write_heater_range(self, value):
self.communicate(f'RANGE {value};RANGE?')

View File

@ -26,7 +26,7 @@ from frappy.datatypes import FloatRange, StringType, ValueType, TupleOf, StructO
from frappy.modules import Communicator, Drivable, Parameter, Property, Readable, Module, Attached
from frappy.params import Command
from frappy.dynamic import Pinata
from frappy.errors import RangeError
from frappy.errors import RangeError, HardwareError
class Pin(Pinata):
def scanModules(self):
@ -72,8 +72,12 @@ class Heater(Drivable):
maxheaterpower = Parameter('maximum allowed heater power',
datatype=FloatRange(0, 100), unit='W',
)
error_message = Parameter('simulated error message', StringType(),
default='', readonly=False)
def read_value(self):
if self.error_message:
raise HardwareError(self.error_message)
return round(100 * random.random(), 1)
def write_target(self, target):

View File

@ -1,25 +1,37 @@
"""
Created on Tue Nov 26 15:42:43 2019
@author: tartarotti_d-adm
"""
# *****************************************************************************
# 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:
# Damaris Tartarotti Maimone
# Markus Zolliker <markus.zolliker@psi.ch>
# *****************************************************************************
"""Wrapper for the ADQ data acquisition card for ultrasound"""
import sys
import atexit
import signal
import time
import numpy as np
import ctypes as ct
import time
from numpy import sqrt, arctan2, sin, cos
from scipy.signal import butter, filtfilt
#from pylab import *
from scipy import signal
#ADQAPI = ct.cdll.LoadLibrary("ADQAPI.dll")
ADQAPI = ct.cdll.LoadLibrary("libadq.so.0")
# For different trigger modes
SW_TRIG = 1
EXT_TRIG_1 = 2 #This external trigger does not work if the level of the trigger is very close to 0.5V. Now we have it close to 3V, and it works
# The following external trigger does not work if the level of the trigger is very close to 0.5V.
# Now we have it close to 3V, and it works
EXT_TRIG_1 = 2
EXT_TRIG_2 = 7
EXT_TRIG_3 = 8
LVL_TRIG = 3
@ -27,29 +39,36 @@ INT_TRIG = 4
LVL_FALLING = 0
LVL_RISING = 1
#samples_per_record=16384
ADQ_CLOCK_INT_INTREF = 0 # internal clock source
ADQ_CLOCK_EXT_REF = 1 # internal clock source, external reference
ADQ_CLOCK_EXT_CLOCK = 2 # External clock source
ADQ_TRANSFER_MODE_NORMAL = 0x00
ADQ_CHANNELS_MASK = 0x3
#f_LO = 40
def butter_lowpass(cutoff, sr, order=5):
nyq = 0.5 * sr
normal_cutoff = cutoff / nyq
b, a = signal.butter(order, normal_cutoff, btype = 'low', analog = False)
return b, a
GHz = 1e9
class Adq(object):
class Adq:
sample_rate = 2 * GHz
max_number_of_channels = 2
samp_freq = 2
#ndecimate = 50 # decimation ratio (2GHz / 40 MHz)
ndecimate = 50
ndecimate = 50 # decimation ratio (2GHz / 40 MHz)
number_of_records = 1
samples_per_record = 16384
bw_cutoff = 10E6
trigger = EXT_TRIG_1
adq_num = 1
UNDEFINED = -1
IDLE = 0
BUSY = 1
READY = 2
status = UNDEFINED
data = None
def __init__(self):
global ADQAPI
ADQAPI = ct.cdll.LoadLibrary("libadq.so.0")
def __init__(self, number_of_records, samples_per_record, bw_cutoff):
self.number_of_records = number_of_records
self.samples_per_record = samples_per_record
self.bw_cutoff = bw_cutoff
ADQAPI.ADQAPI_GetRevision()
# Manually set return type from some ADQAPI functions
@ -60,33 +79,29 @@ class Adq(object):
# Create ADQControlUnit
self.adq_cu = ct.c_void_p(ADQAPI.CreateADQControlUnit())
ADQAPI.ADQControlUnit_EnableErrorTrace(self.adq_cu, 3, '.')
self.adq_num = 1
# Find ADQ devices
ADQAPI.ADQControlUnit_FindDevices(self.adq_cu)
n_of_ADQ = ADQAPI.ADQControlUnit_NofADQ(self.adq_cu)
if n_of_ADQ != 1:
raise ValueError('number of ADQs must be 1, not %d' % n_of_ADQ)
n_of_adq = ADQAPI.ADQControlUnit_NofADQ(self.adq_cu)
if n_of_adq != 1:
raise RuntimeError('number of ADQs must be 1, not %d' % n_of_adq)
rev = ADQAPI.ADQ_GetRevision(self.adq_cu, self.adq_num)
revision = ct.cast(rev, ct.POINTER(ct.c_int))
print('\nConnected to ADQ #1')
# Print revision information
print('FPGA Revision: {}'.format(revision[0]))
if (revision[1]):
if revision[1]:
print('Local copy')
else:
print('SVN Managed')
if (revision[2]):
if revision[2]:
print('Mixed Revision')
else:
print('SVN Updated')
print('')
ADQ_CLOCK_INT_INTREF = 0 #internal clock source
ADQ_CLOCK_EXT_REF = 1 #internal clock source, external reference
ADQ_CLOCK_EXT_CLOCK = 2 #External clock source
ADQAPI.ADQ_SetClockSource(self.adq_cu, self.adq_num, ADQ_CLOCK_EXT_REF);
ADQAPI.ADQ_SetClockSource(self.adq_cu, self.adq_num, ADQ_CLOCK_EXT_REF)
##########################
# Test pattern
@ -96,36 +111,33 @@ class Adq(object):
# ADQAPI.ADQ_SetSampleSkip(self.adq_cu, self.adq_num, 1)
##########################
# Set trig mode
self.trigger = EXT_TRIG_1
#trigger = LVL_TRIG
success = ADQAPI.ADQ_SetTriggerMode(self.adq_cu, self.adq_num, self.trigger)
if (success == 0):
print('ADQ_SetTriggerMode failed.')
if (self.trigger == LVL_TRIG):
success = ADQAPI.ADQ_SetLvlTrigLevel(self.adq_cu, self.adq_num, -100)
if (success == 0):
print('ADQ_SetLvlTrigLevel failed.')
success = ADQAPI.ADQ_SetTrigLevelResetValue(self.adq_cu, self.adq_num, 1000)
if (success == 0):
print('ADQ_SetTrigLevelResetValue failed.')
success = ADQAPI.ADQ_SetLvlTrigChannel(self.adq_cu, self.adq_num, 1)
if (success == 0):
print('ADQ_SetLvlTrigChannel failed.')
success = ADQAPI.ADQ_SetLvlTrigEdge(self.adq_cu, self.adq_num, LVL_RISING)
if (success == 0):
print('ADQ_SetLvlTrigEdge failed.')
elif (self.trigger == EXT_TRIG_1) :
success = ADQAPI.ADQ_SetExternTrigEdge(self.adq_cu, self.adq_num,2)
if (success == 0):
print('ADQ_SetLvlTrigEdge failed.')
# success = ADQAPI.ADQ_SetTriggerThresholdVoltage(self.adq_cu, self.adq_num, trigger, ct.c_double(0.2))
# if (success == 0):
# print('SetTriggerThresholdVoltage failed.')
# set trigger mode
if not ADQAPI.ADQ_SetTriggerMode(self.adq_cu, self.adq_num, self.trigger):
raise RuntimeError('ADQ_SetTriggerMode failed.')
if self.trigger == LVL_TRIG:
if not ADQAPI.ADQ_SetLvlTrigLevel(self.adq_cu, self.adq_num, -100):
raise RuntimeError('ADQ_SetLvlTrigLevel failed.')
if not ADQAPI.ADQ_SetTrigLevelResetValue(self.adq_cu, self.adq_num, 1000):
raise RuntimeError('ADQ_SetTrigLevelResetValue failed.')
if not ADQAPI.ADQ_SetLvlTrigChannel(self.adq_cu, self.adq_num, 1):
raise RuntimeError('ADQ_SetLvlTrigChannel failed.')
if not ADQAPI.ADQ_SetLvlTrigEdge(self.adq_cu, self.adq_num, LVL_RISING):
raise RuntimeError('ADQ_SetLvlTrigEdge failed.')
elif self.trigger == EXT_TRIG_1:
if not ADQAPI.ADQ_SetExternTrigEdge(self.adq_cu, self.adq_num, 2):
raise RuntimeError('ADQ_SetLvlTrigEdge failed.')
# if not ADQAPI.ADQ_SetTriggerThresholdVoltage(self.adq_cu, self.adq_num, trigger, ct.c_double(0.2)):
# raise RuntimeError('SetTriggerThresholdVoltage failed.')
print("CHANNEL:"+str(ct.c_int(ADQAPI.ADQ_GetLvlTrigChannel(self.adq_cu, self.adq_num))))
self.setup_target_buffers()
atexit.register(self.deletecu)
signal.signal(signal.SIGTERM, lambda *_: sys.exit(0))
def setup_target_buffers(self):
def init(self, samples_per_record=None, number_of_records=None):
"""initialize dimensions"""
if samples_per_record:
self.samples_per_record = samples_per_record
if number_of_records:
self.number_of_records = number_of_records
# Setup target buffers for data
self.target_buffers = (ct.POINTER(ct.c_int16 * self.samples_per_record * self.number_of_records)
* self.max_number_of_channels)()
@ -135,14 +147,11 @@ class Adq(object):
def deletecu(self):
# Only disarm trigger after data is collected
ADQAPI.ADQ_DisarmTrigger(self.adq_cu, self.adq_num)
ADQAPI.ADQ_MultiRecordClose(self.adq_cu, self.adq_num);
ADQAPI.ADQ_MultiRecordClose(self.adq_cu, self.adq_num)
# Delete ADQControlunit
ADQAPI.DeleteADQControlUnit(self.adq_cu)
def start(self):
"""start datat acquisition"""
# samples_per_records = samples_per_record/number_of_records
# Change number of pulses to be acquired acording to how many records are taken
# Start acquisition
ADQAPI.ADQ_MultiRecordSetup(self.adq_cu, self.adq_num,
self.number_of_records,
@ -150,78 +159,49 @@ class Adq(object):
ADQAPI.ADQ_DisarmTrigger(self.adq_cu, self.adq_num)
ADQAPI.ADQ_ArmTrigger(self.adq_cu, self.adq_num)
self.status = self.BUSY
def getdata(self):
"""wait for aquisition to be finished and get data"""
#start = time.time()
while(ADQAPI.ADQ_GetAcquiredAll(self.adq_cu,self.adq_num) == 0):
time.sleep(0.001)
#if (self.trigger == SW_TRIG):
# ADQAPI.ADQ_SWTrig(self.adq_cu, self.adq_num)
#mid = time.time()
status = ADQAPI.ADQ_GetData(self.adq_cu, self.adq_num, self.target_buffers,
def get_status(self):
"""check if ADQ card is busy"""
if self.status == self.BUSY:
if ADQAPI.ADQ_GetAcquiredAll(self.adq_cu, self.adq_num):
self.status = self.READY
else:
if self.trigger == SW_TRIG:
ADQAPI.ADQ_SWTrig(self.adq_cu, self.adq_num)
return self.status
def get_data(self, dataclass, **kwds):
"""when ready, get raw data from card, else return cached data
return
"""
if self.get_status() == self.READY:
# Get data from ADQ
if not ADQAPI.ADQ_GetData(self.adq_cu, self.adq_num, self.target_buffers,
self.samples_per_record * self.number_of_records, 2,
0, self.number_of_records, ADQ_CHANNELS_MASK,
0, self.samples_per_record, ADQ_TRANSFER_MODE_NORMAL);
#print(time.time()-mid,mid-start)
if not status:
raise ValueError('no succesS from ADQ_GetDATA')
# Now this is an array with all records, but the time is artificial
0, self.samples_per_record, ADQ_TRANSFER_MODE_NORMAL):
raise RuntimeError('no success from ADQ_GetDATA')
self.data = dataclass(self, **kwds)
self.status = self.IDLE
if self.status == self.UNDEFINED:
raise RuntimeError('no data available yet')
return self.data
class PEdata:
def __init__(self, adq):
self.sample_rate = adq.sample_rate
self.samp_freq = self.sample_rate / GHz
self.number_of_records = adq.number_of_records
data = []
for ch in range(2):
onedim = np.frombuffer(self.target_buffers[ch].contents, dtype=np.int16)
data.append(onedim.reshape(self.number_of_records, self.samples_per_record) / float(2**14)) # 14 bits ADC
return data
onedim = np.frombuffer(adq.target_buffers[ch].contents, dtype=np.int16)
data.append(onedim.reshape(adq.number_of_records, adq.samples_per_record) / float(2**14)) # 14 bits ADC
# Now this is an array with all records, but the time is artificial
self.data = data
def acquire(self):
self.start()
return self.getdata()
'''
def average(self, data):
#Average over records
return [data[ch].sum(axis=0) / self.number_of_records for ch in range(2)]
def iq(self, channel, f_LO):
newx = np.linspace(0, self.samples_per_record /2, self.samples_per_record)
s0 = channel /((2**16)/2)*0.5*np.exp(1j*2*np.pi*f_LO/(1e3)*newx)
I0 = s0.real
Q0 = s0.imag
return I0, Q0
def fitting(self, data, f_LO, ti, tf):
# As long as data[0] is the pulse
si = 2*ti #Those are for fitting the pulse
sf = 2*tf
phase = np.zeros(self.number_of_records)
amplitude = np.zeros(self.number_of_records)
offset = np.zeros(self.number_of_records)
for i in range(self.number_of_records):
phase[i], amplitude[i] = sineW(data[0][i][si:sf],f_LO*1e-9,ti,tf)
offset[i] = np.average(data[0][i][si:sf])
return phase, amplitude, offset
def waveIQ(self, channel,ti,f_LO):
#channel is not the sample data
t = np.linspace(0, self.samples_per_record /2, self.samples_per_record + 1)[:-1]
si = 2*ti # Again that is where the wave pulse starts
cwi = np.zeros((self.number_of_records,self.samples_per_record))
cwq = np.zeros((self.number_of_records,self.samples_per_record))
iq = np.zeros((self.number_of_records,self.samples_per_record))
q = np.zeros((self.number_of_records,self.samples_per_record))
for i in range(self.number_of_records):
cwi[i] = np.zeros(self.samples_per_record)
cwq[i] = np.zeros(self.samples_per_record)
cwi[i] = amplitude[i]*sin(t[si:]*f_LO*1e-9*2*np.pi+phase[i]*np.pi/180)+bias[i]
cwq[i] = amplitude[i]*sin(t[si:]*f_LO*1e-9*(2*np.pi+(phase[i]+90)*np.pi/180))+bias[i]
iq[i] = channel[i]*cwi[i]
q[i] = channel[i]*cwq[i]
return iq,q
'''
def sinW(self, sig, freq, ti, tf):
# sig: signal array
# freq
@ -241,30 +221,28 @@ class Adq(object):
def mix(self, sigin, sigout, freq, ti, tf):
# sigin, sigout: signal array, incomping, output
# freq
# ti, tf: initial and end time if sigin
# ti, tf: initial and end time of sigin
a, b = self.sinW(sigin, freq, ti, tf)
phase = arctan2(a,b) * 180 / np.pi
amp = sqrt(a**2 + b**2)
amp = np.sqrt(a**2 + b**2)
a, b = a/amp, b/amp
# si = int(ti * self.samp_freq)
t = np.arange(len(sigout)) / self.samp_freq
wave1 = sigout * (a * cos(2*np.pi*freq*t) + b * sin(2*np.pi*freq*t))
wave2 = sigout * (a * sin(2*np.pi*freq*t) - b * cos(2*np.pi*freq*t))
wave1 = sigout * (a * np.cos(2*np.pi*freq*t) + b * np.sin(2*np.pi*freq*t))
wave2 = sigout * (a * np.sin(2*np.pi*freq*t) - b * np.cos(2*np.pi*freq*t))
return wave1, wave2
def averageiq(self, data, freq, ti, tf):
'''Average over records'''
"""Average over records"""
iorq = np.array([self.mix(data[0][i], data[1][i], freq, ti, tf) for i in range(self.number_of_records)])
# iorq = np.array([self.mix(data[0][:], data[1][:], freq, ti, tf)])
return iorq.sum(axis=0) / self.number_of_records
def filtro(self, iorq, cutoff):
b, a = butter_lowpass(cutoff, self.samp_freq*1e9)
#ifi = np.array(signal.filtfilt(b,a,iorq[0]))
#qf = np.array(signal.filtfilt(b,a,iorq[1]))
iqf = [signal.filtfilt(b,a,iorq[i]) for i in np.arange(len(iorq))]
# butter lowpass
nyq = 0.5 * self.sample_rate
normal_cutoff = cutoff / nyq
order = 5
b, a = butter(order, normal_cutoff, btype='low', analog=False)
iqf = [filtfilt(b, a, iorq[i]) for i in np.arange(len(iorq))]
return iqf
def box(self, iorq, ti, tf):
@ -273,21 +251,63 @@ class Adq(object):
bxa = [sum(iorq[i][si:sf])/(sf-si) for i in np.arange(len(iorq))]
return bxa
def gates_and_curves(self, data, freq, pulse, roi):
def gates_and_curves(self, freq, pulse, roi, bw_cutoff):
"""return iq values of rois and prepare plottable curves for iq"""
times = []
times.append(('aviq', time.time()))
iq = self.averageiq(data,freq*1e-9,*pulse)
times.append(('filtro', time.time()))
iqf = self.filtro(iq,self.bw_cutoff)
self.ndecimate = int(round(self.sample_rate / freq))
# times = []
# times.append(('aviq', time.time()))
iq = self.averageiq(self.data, freq / GHz, *pulse)
# times.append(('filtro', time.time()))
iqf = self.filtro(iq, bw_cutoff)
m = len(iqf[0]) // self.ndecimate
times.append(('iqdec', time.time()))
ll = m * self.ndecimate
iqf = [iqfx[0:ll] for iqfx in iqf]
# times.append(('iqdec', time.time()))
iqd = np.average(np.resize(iqf, (2, m, self.ndecimate)), axis=2)
t_axis = np.arange(m) * self.ndecimate / self.samp_freq
pulsig = np.abs(data[0][0])
times.append(('pulsig', time.time()))
pulsig = np.abs(self.data[0][0])
# times.append(('pulsig', time.time()))
pulsig = np.average(np.resize(pulsig, (m, self.ndecimate)), axis=1)
self.curves = (t_axis, iqd[0], iqd[1], pulsig)
# print(times)
return [self.box(iqf, *r) for r in roi]
class RUSdata:
def __init__(self, adq, freq, periods):
self.sample_rate = adq.sample_rate
self.freq = freq
self.periods = periods
self.samples_per_record = adq.samples_per_record
input_signal = np.frombuffer(adq.target_buffers[0].contents, dtype=np.int16)
output_signal = np.frombuffer(adq.target_buffers[1].contents, dtype=np.int16)
complex_sinusoid = np.exp(1j * 2 * np.pi * self.freq / self.sample_rate * np.arange(len(input_signal)))
self.input_mixed = input_signal * complex_sinusoid
self.output_mixed = output_signal * complex_sinusoid
self.input_mean = self.input_mixed.mean()
self.output_mean = self.output_mixed.mean()
self.iq = self.output_mean / self.input_mean
def get_reduced(self, mixed):
"""get reduced array and normalize"""
nper = self.samples_per_record // self.periods
mean = mixed.mean()
return mixed[:self.period * nper].reshape((-1, nper)).mean(axis=0) / mean
def calc_quality(self):
"""get signal quality info
quality info (small values indicate good quality):
- input_std and output_std:
the imaginary part indicates deviations in phase
the real part indicates deviations in amplitude
- input_slope and output_slope:
the imaginary part indicates a turning phase (rad/sec)
the real part indicates changes in amplitude (0.01 ~= 1%/sec)
"""
reduced = self.get_reduced(self.input_mixed)
self.input_stdev = reduced.std()
reduced = self.get_reduced(self.output_mixed)
timeaxis = np.arange(len(reduced)) * self.sample_rate / self.freq
self.output_slope = np.polyfit(timeaxis, reduced, 1)[0]

View File

@ -17,10 +17,39 @@
# Markus Zolliker <markus.zolliker@psi.ch>
# Jael Celia Lorenzana <jael-celia.lorenzana@psi.ch>
# *****************************************************************************
"""Powersupply B&K Precision BK168xB"""
"""Powersupply B&K Precision BK168xB
The following lines are part of a config file for the frappy-server process
The frappy server creates the following modules and refreshes the values with a refresh rate of 1 sec.
The communication with the powersupply is established via serial over USB.
The manual can be found here https://www.vectortechnologies.gr/images/products/2022/02/168xB_programming_manual.pdf
Mod('htr_io',
'frappy_psi.bkpower.IO',
'powersupply communicator',
uri = 'serial:///dev/ttyUSBupper',
)
Mod('htr',
'frappy_psi.bkpower.Power',
'heater power',
io= 'htr_io',
)
Mod('out',
'frappy_psi.bkpower.Output',
'heater output',
io = 'htr_io',
maxvolt = 50,
maxcurrent = 2,
)
"""
from frappy.core import StringIO, Readable, Parameter, FloatRange, Writable, HasIO, BoolType
# define the IO class
class IO(StringIO):
end_of_line = ('OK\r', '\r')
default_settings = {'baudrate': 9600}

View File

@ -22,6 +22,7 @@
"""drivers for CCU4, the cryostat control unit at SINQ"""
import time
import math
from frappy.lib.enum import Enum
# the most common Frappy classes can be imported from frappy.core
from frappy.core import HasIO, Parameter, Command, Readable, Writable, Drivable, \
Property, StringIO, BUSY, IDLE, WARN, ERROR, DISABLED, Attached
@ -32,6 +33,10 @@ from frappy.errors import CommunicationFailedError
from frappy.states import HasStates, status_code, Retry
M = Enum(idle=0, opening=1, closing=2, opened=3, closed=5, no_motor=6)
A = Enum(disabled=0, manual=1, auto=2)
class CCU4IO(StringIO):
"""communication with CCU4"""
# for completeness: (not needed, as it is the default)
@ -43,25 +48,34 @@ class CCU4IO(StringIO):
class CCU4Base(HasIO):
ioClass = CCU4IO
def command(self, *args, **kwds):
def command(self, **kwds):
"""send a command and get the response
:param args: the name of the parameters to query
:param kwds: <parameter>=<value> for changing a parameter
:param kwds: <parameter>=<value> for changing a parameter <parameter>=<type> for querying a parameter
:returns: the (new) values of the parameters
"""
cmds = [f'{k}={v:g}' for k, v in kwds.items()] + list(args)
types = {}
cmds = []
for key, value in kwds.items():
if isinstance(value, type):
types[key] = value
cmds.append(key)
elif isinstance(value, str):
types[key] = str
cmds.append(f'{key}={value}')
else:
types[key] = float
cmds.append(f'{key}={value:g}')
reply = self.io.communicate(' '.join(cmds)).split()
names = list(args) + list(kwds)
if len(reply) != len(names):
if len(reply) != len(types):
raise CommunicationFailedError('number of reply items does not match')
result = []
for given, item in zip(names, reply):
name, txtvalue = item.split('=')
for (given, typ), res in zip(types.items(), reply):
name, txtvalue = res.split('=')
if given != name:
raise CommunicationFailedError('result keys do not match given keys')
result.append(float(txtvalue))
if len(result) == 1:
result.append(typ(txtvalue))
if len(kwds) == 1:
return result[0]
return result
@ -89,13 +103,13 @@ class HeLevel(CCU4Base, Readable):
}
def read_value(self):
return self.command('h')
return self.command(h=float)
def read_status(self):
return self.STATUS_MAP[int(self.command('hsf'))]
return self.STATUS_MAP[int(self.command(hsf=int))]
def read_sample_rate(self):
value, self.empty_length, self.full_length = self.command('hf', 'hem', 'hfu')
value, self.empty_length, self.full_length = self.command(hf=int, hem=float, hfu=float)
return value
def write_sample_rate(self, value):
@ -138,13 +152,13 @@ class Valve(CCU4Base, Writable):
class HeFillValve(Valve):
_open_command = {'hcd': 1, 'hf': 1}
_close_command = {'hcd': 0, 'hf': 0}
_query_state = 'hv'
_query_state = {'hv': int}
class N2FillValve(Valve):
_open_command = {'nc': 1}
_close_command = {'nc': 0}
_query_state = 'nv'
_query_state = {'nv': int}
class AuxValve(Valve):
@ -153,7 +167,7 @@ class AuxValve(Valve):
def initModule(self):
self._open_command = {f'vc{self.channel}': 1}
self._close_command = {f'vc{self.channel}': 0}
self._query_state = f'v{self.channel}'
self._query_state = {f'v{self.channel}': int}
class N2TempSensor(Readable):
@ -167,7 +181,7 @@ class N2Level(CCU4Base, Pinata, Readable):
value = Parameter('vessel state', EnumType(empty=0, ok=1, full=2))
status = Parameter(datatype=StatusType(Readable, 'BUSY'))
mode = Parameter('auto mode', EnumType(disabled=0, manual=1, auto=2), readonly=False)
mode = Parameter('auto mode', EnumType(A), readonly=False)
threshold = Parameter('threshold triggering start/stop filling',
FloatRange(unit='K'), readonly=False)
@ -206,17 +220,17 @@ class N2Level(CCU4Base, Pinata, Readable):
super().initialReads()
def read_status(self):
auto, nstate = self.command('na', 'ns')
auto, nstate = self.command(na=int, ns=int)
if not self.valve or not auto:
if self.mode == 'auto':
if self.mode == A.auto:
# no valve assigned
self.mode = 'manual'
if self.mode == 'disabled':
self.mode = A.manual
if self.mode == A.disabled:
return DISABLED, ''
status = self.STATUS_MAP[nstate]
if status[0] // 100 != IDLE // 100:
return status
if self.mode == 'manual':
if self.mode == A.manual:
return IDLE, ''
vstatus = self.valve.status
if vstatus[0] // 100 == WARN // 100:
@ -229,7 +243,7 @@ class N2Level(CCU4Base, Pinata, Readable):
def read_value(self):
# read sensors
lower, upper = self.command('nl', 'nu')
lower, upper = self.command(nl=float, nu=float)
if self.lower:
self.lower.value = lower
if self.upper:
@ -241,7 +255,7 @@ class N2Level(CCU4Base, Pinata, Readable):
return 'empty'
def write_mode(self, mode):
if mode == 'auto':
if mode == A.auto:
if self.isBusy():
return mode
# set to watching
@ -252,7 +266,7 @@ class N2Level(CCU4Base, Pinata, Readable):
return mode
def read_threshold(self):
value, self.cool_delay, self.fill_timeout = self.command('nth', 'ntc', 'ntm')
value, self.cool_delay, self.fill_timeout = self.command(nth=float, ntc=float, ntm=float)
return value
def write_threshold(self, value):
@ -266,12 +280,12 @@ class N2Level(CCU4Base, Pinata, Readable):
@Command()
def fill(self):
self.mode = 'auto'
self.mode = A.auto
self.io.write(nc=1)
@Command()
def stop(self):
if self.mode == 'auto':
if self.mode == A.auto:
# set to watching
self.command(nc=3)
else:
@ -285,7 +299,7 @@ class FlowPressure(CCU4Base, Readable):
pollinterval = Parameter(default=0.25)
def read_value(self):
return self.filter(self.command('f')) - self.mbar_offset
return self.filter(self.command(f=float)) - self.mbar_offset
class NeedleValve(HasStates, CCU4Base, Drivable):
@ -298,9 +312,7 @@ class NeedleValve(HasStates, CCU4Base, Drivable):
lnm_per_mbar = Parameter(unit='ln/min/mbar', default=0.6, readonly=False)
use_pressure = Parameter('use flow from pressure', BoolType(),
default=False, readonly=False)
motor_state = Parameter('motor_state',
EnumType(idle=0, opening=1, closing=2,
opened=3, closed=5, no_motor=6))
motor_state = Parameter('motor_state', EnumType(M))
tolerance = Parameter('tolerance', FloatRange(0), value=0.25, readonly=False)
tolerance2 = Parameter('tolerance limit above 2 lnm', FloatRange(0), value=0.5, readonly=False)
prop = Parameter('proportional term', FloatRange(unit='s/lnm'), readonly=False)
@ -341,12 +353,12 @@ class NeedleValve(HasStates, CCU4Base, Drivable):
def update_flow(self, value):
if not self.use_pressure:
self.value = value
self.doPoll()
self.cycle_machine()
def update_flow_pressure(self, value):
if self.use_pressure:
self.value = value * self.lnm_per_mbar
self.doPoll()
self.cycle_machine()
def write_target(self, value):
self.start_machine(self.controlling, in_tol_time=0,
@ -354,7 +366,7 @@ class NeedleValve(HasStates, CCU4Base, Drivable):
@status_code(BUSY)
def unblock_from_open(self, state):
self.motor_state = self.command('fm')
self.motor_state = self.command(fm=int)
if self.motor_state == 'opened':
self.command(mp=-60)
return Retry
@ -372,7 +384,7 @@ class NeedleValve(HasStates, CCU4Base, Drivable):
@status_code(BUSY)
def unblock_open(self, state):
self.motor_state = self.command('fm')
self.motor_state = self.command(fm=int)
if self.value < state.flow_before:
state.flow_before_open = self.value
elif self.value > state.flow_before + 1:
@ -393,7 +405,7 @@ class NeedleValve(HasStates, CCU4Base, Drivable):
@status_code(BUSY)
def unblock_close(self, state):
self.motor_state = self.command('fm')
self.motor_state = self.command(fm=int)
if self.value > state.flow_before:
state.flow_before_open = self.value
elif self.value < state.flow_before - 1:
@ -435,7 +447,7 @@ class NeedleValve(HasStates, CCU4Base, Drivable):
dif = self.target - self.value
difdif = dif - state.prev_dif
state.prev_dif = dif
self.motor_state = self.command('fm')
self.motor_state = self.command(fm=int)
if self.motor_state == 'closed':
if dif < 0 or difdif < 0:
return Retry
@ -467,5 +479,3 @@ class NeedleValve(HasStates, CCU4Base, Drivable):
return Retry
self.command(mp=state.step)
return Retry

View File

@ -63,7 +63,12 @@ def parse_result(reply):
class LakeShoreIO(HasIO):
def set_param(self, cmd, *args):
head = ','.join([cmd] + [f'{a:g}' for a in args])
args = [f'{a:g}' for a in args]
if ' ' in cmd.strip():
args.insert(0, cmd)
else:
args[0] = cmd + args[0]
head = ','.join(args)
tail = cmd.replace(' ', '?')
reply = self.io.communicate(f'{head};{tail}')
return parse_result(reply)
@ -99,7 +104,7 @@ class Switcher(LakeShoreIO, ChannelSwitcher):
if channelno is None:
self.status = 'ERROR', 'no enabled channel'
return
self.set_param(f'SCAN {channelno},0')
self.set_param('SCAN ', channelno, 0)
def doPoll(self):
"""poll buttons
@ -160,7 +165,7 @@ class Switcher(LakeShoreIO, ChannelSwitcher):
self.measure_delay = chan.dwell
def set_active_channel(self, chan):
self.set_param(f'SCAN {chan.channel},0')
self.set_param('SCAN ', chan.channel, 0)
chan._last_range_change = time.monotonic()
self.set_delays(chan)
@ -227,7 +232,7 @@ class ResChannel(LakeShoreIO, Channel):
now = time.monotonic()
if now + 0.5 < max(self._last_range_change, self.switcher._start_switch) + self.pause:
return None
result = self.get_param(f'RDGR{self.channel}')
result = self.get_param(f'RDGR?{self.channel}')
if self.autorange:
self.fix_autorange()
if now + 0.5 > self._last_range_change + self.pause:
@ -251,7 +256,7 @@ class ResChannel(LakeShoreIO, Channel):
def read_value(self):
if self.channel == self.switcher.value == self.switcher.target:
value = self._read_value()
value = self.get_value()
if value is not None:
return value
return self.value # return previous value
@ -264,7 +269,7 @@ class ResChannel(LakeShoreIO, Channel):
@CommonReadHandler(rdgrng_params)
def read_rdgrng(self):
iscur, exc, rng, autorange, excoff = self.get_param(f'RDGRNG{self.channel}')
iscur, exc, rng, autorange, excoff = self.get_param(f'RDGRNG?{self.channel}')
self._prev_rdgrng = iscur, exc
if autorange: # pressed autorange button
if not self._toggle_autorange:
@ -293,8 +298,7 @@ class ResChannel(LakeShoreIO, Channel):
excoff = 1
rng = change['range']
if self.autorange:
if rng < self.minrange:
rng = self.minrange
rng = max(rng, self.minrange)
self.set_param(f'RDGRNG {self.channel}', iscur, exc, rng, 0, excoff)
self.read_range()

View File

@ -16,81 +16,35 @@
# Module authors:
# Markus Zolliker <markus.zolliker@psi.ch>
# *****************************************************************************
"""a very simple simulator for a LakeShore Model 370"""
"""a very simple simulator for LakeShore Models 370 and 372
reduced to the functionality actually used in e.g. frappy_psi.ls370res
"""
import time
from frappy.modules import Communicator
class Ls370Sim(Communicator):
CHANNEL_COMMANDS = [
('RDGR?%d', '1.0'),
('RDGST?%d', '0'),
('RDGRNG?%d', '0,5,5,0,0'),
('INSET?%d', '1,5,5,0,0'),
('FILTER?%d', '1,5,80'),
]
OTHER_COMMANDS = [
('*IDN?', 'LSCI,MODEL370,370184,05302003'),
('SCAN?', '3,1'),
('*OPC?', '1'),
]
def earlyInit(self):
super().earlyInit()
self._data = dict(self.OTHER_COMMANDS)
for fmt, v in self.CHANNEL_COMMANDS:
for chan in range(1,17):
self._data[fmt % chan] = v
def communicate(self, command):
self.comLog('> %s' % command)
# simulation part, time independent
for channel in range(1,17):
_, _, _, _, excoff = self._data['RDGRNG?%d' % channel].split(',')
if excoff == '1':
self._data['RDGST?%d' % channel] = '6'
else:
self._data['RDGST?%d' % channel] = '0'
chunks = command.split(';')
reply = []
for chunk in chunks:
if '?' in chunk:
reply.append(self._data[chunk])
else:
for nqarg in (1,0):
if nqarg == 0:
qcmd, arg = chunk.split(' ', 1)
qcmd += '?'
else:
qcmd, arg = chunk.split(',', nqarg)
qcmd = qcmd.replace(' ', '?', 1)
if qcmd in self._data:
self._data[qcmd] = arg
break
reply = ';'.join(reply)
self.comLog('< %s' % reply)
return reply
class Ls372Sim(Communicator):
class _Ls37xSim(Communicator):
# commands containing %d for the channel number
CHANNEL_COMMANDS = [
('RDGR?%d', '1.0'),
('RDGK?%d', '1.5'),
('RDGST?%d', '0'),
('RDGRNG?%d', '0,5,5,0,0'),
('INSET?%d', '1,5,5,0,0'),
('INSET?%d', '1,3,3,0,0'),
('FILTER?%d', '1,5,80'),
]
# commands not related to a channel
OTHER_COMMANDS = [
('*IDN?', 'LSCI,MODEL372,372184,05302003'),
('SCAN?', '3,1'),
('PID?1', '10,10,0'),
('*OPC?', '1'),
]
def earlyInit(self):
super().earlyInit()
self._res = {}
self._start = time.time()
self._data = dict(self.OTHER_COMMANDS)
for fmt, v in self.CHANNEL_COMMANDS:
for chan in range(1, 17):
@ -105,6 +59,10 @@ class Ls372Sim(Communicator):
self._data['RDGST?%d' % channel] = '6'
else:
self._data['RDGST?%d' % channel] = '0'
channel = int(self._data['SCAN?'].split(',', 1)[0])
self._res[channel] = channel + (time.time() - self._start) / 3600
strvalue = f'{self._res[channel]:g}'
self._data['RDGR?%d' % channel] = self._data['RDGK?%d' % channel] = strvalue
chunks = command.split(';')
reply = []
@ -125,3 +83,16 @@ class Ls372Sim(Communicator):
reply = ';'.join(reply)
self.comLog('< %s' % reply)
return reply
class Ls370Sim(_Ls37xSim):
OTHER_COMMANDS = _Ls37xSim.OTHER_COMMANDS + [
('*IDN?', 'LSCI,MODEL370,370184,05302003'),
]
class Ls372Sim(_Ls37xSim):
OTHER_COMMANDS = _Ls37xSim.OTHER_COMMANDS + [
('*IDN?', 'LSCI,MODEL372,372184,05302003'),
('PID?1', '10,10,0'),
]

View File

@ -140,6 +140,15 @@ class SimpleMagfield(HasStates, Drivable):
self.setFastPoll(True, 1.0)
return self.start_ramp_to_target
@status_code(Status.RAMPING)
def start_ramp_to_target(self, sm):
"""start ramping current to target field
initiate ramp to target
the implementation should return ramp_to_target
"""
raise NotImplementedError
@status_code(BUSY, 'ramping field')
def ramp_to_target(self, sm):
if sm.init:
@ -324,15 +333,6 @@ class Magfield(SimpleMagfield):
self._last_target = sm.target
return self.start_ramp_to_target
@status_code(Status.RAMPING)
def start_ramp_to_target(self, sm):
"""start ramping current to target field
initiate ramp to target
the implementation should return ramp_to_target
"""
raise NotImplementedError
@status_code(Status.RAMPING)
def ramp_to_target(self, sm):
dif = abs(self.value - sm.target)

276
frappy_psi/oxinst.py Normal file
View File

@ -0,0 +1,276 @@
# *****************************************************************************
# 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>
# *****************************************************************************
"""oxford instruments old devices (ILM, IGH, IPS)"""
import re
import time
from frappy.core import StringIO, HasIO, Readable, Parameter, ERROR, IDLE, PREPARING
from frappy.datatypes import FloatRange, BoolType, EnumType
from frappy.errors import HardwareError, RangeError
from frappy.lib import formatStatusBits, clamp
from frappy.lib.enum import Enum
from frappy_psi.magfield import Magfield
from frappy.states import Retry
class IlmIO(StringIO):
end_of_line = '\r'
identification = [('V', r'ILM200.*')]
timeout = 5
class OxiBase(HasIO):
def query(self, cmd, dig=0):
reply = self.communicate(cmd)
if reply[0] == cmd[0]:
if '.' not in reply and dig > 0:
# add decimal point if not already there (for older systems)
reply = f'{reply[1:-dig]}.{reply[-dig:]}'
try:
value = float(reply)
return value
except Exception:
pass
raise HardwareError(f'bad reply {reply!r} to {cmd!r}')
def command(self, *cmds):
try:
self.communicate('C3')
for cmd in cmds:
self.communicate(cmd)
finally:
self.communicate('C0')
def change(self, cmd, query, value):
try:
self.communicate('C3')
self.communicate(f'{cmd}{value:g}')
return self.query(query)
finally:
self.communicate('C0')
class Level(OxiBase, Readable):
ioClass = IlmIO
value = Parameter(datatype=FloatRange(unit='%'))
CHANNEL = None
XPAT = re.compile(r'X(\d)(\d)(\d)S([0-9A-F]{2}){3}R\d\d$')
FLUID = None
_statusbits = None
def read_value(self):
return self.query(f'R{self.CHANNEL}', 1)
def write_fast(self, fast):
self.command(f'T{self.CHANNEL}' if fast else f'S{self.CHANNEL}')
def get_status(self):
reply = self.communicate('X')
match = self.XPAT.match(reply)
if match:
statuslist = match.groups()
if statuslist[self.CHANNEL] == '9':
return ERROR, f'error on {self.FLUID} level channel (not connected?)'
if statuslist[self.CHANNEL] != '2':
return ERROR, f'{self.FLUID} level channel not configured properly'
self._statusbits = int(statuslist[self.CHANNEL + 3], 16)
return None
return ERROR, f'bad status message {reply}'
class HeLevel:
CHANNEL = 1
FLUID = 'He'
fast = Parameter('measuring mode: True is fast', BoolType())
def read_status(self):
status = self.get_status()
if status is not None:
return status
return IDLE, formatStatusBits(self._statusbits, ['meas', 'fast', 'slow'])
class N2Level:
CHANNEL = 2
MEDIUM = 'N2'
def read_status(self):
status = self.get_status()
if status is not None:
return status
return IDLE, ''
A = Enum(hold=0, run_to_set=1, run_to_zero=2, clamped=4)
class Field(OxiBase, Magfield):
action = Parameter('action', EnumType(A), readonly=False)
setpoint = Parameter('field setpoint', FloatRange(unit='T'), default=0)
voltage = Parameter('leads voltage', FloatRange(unit='V'), default=0)
atob = Parameter('field to amp', FloatRange(0, unit='A/T'), default=0)
working_ramp = Parameter('effective ramp', FloatRange(0, unit='T/min'), default=0)
persistent_field = Parameter(
'persistent field at last switch off', FloatRange(unit='$'), readonly=False)
wait_switch_on = Parameter(default=60)
wait_switch_off = Parameter(default=60)
forced_persistent_field = Parameter(
'manual indication that persistent field is bad', BoolType(), readonly=False, default=False)
XPAT = re.compile(r'X(\d)(\d)A(\d)C\dH(\d)M(\d\d)P\d\d$')
def startModule(self, start_events):
# on restart, assume switch is changed long time ago, if not, the mercury
# will complain and this will be handled in start_ramp_to_field
self.switch_on_time = 0
self.switch_off_time = 0
super().startModule(start_events)
def read_value(self):
current = self.query('R7')
if self.switch_heater == self.switch_heater.on:
self.__persistent_field = current
self.forced_persistent_field = False
self._field_mismatch = False
return current
pf = self.query('R18')
if self.__persistent_field is None:
self.__persistent_field = pf
self._field_mismatch = False
else:
self._field_mismatch = abs(self.__persistent_field - pf) > self.tolerance * 10
self.persistent_field = self.__persistent_field
return self.__persistent_field
def read_current(self):
current = self.query('R2')
return current / self.atob
def write_persistent_field(self, value):
if self.forced_persistent_field or abs(self.__persistent_field - value) <= self.tolerance * 10:
self._field_mismatch = False
self.__persistent_field = value
return value
raise RangeError('changing persistent field needs forced_persistent_field=True')
def write_target(self, target):
if self._field_mismatch:
self.forced_persistent_field = True
raise RangeError('persistent field does not match - set persistent field to guessed value first')
return super().write_target(target)
def read_status(self):
status = super().read_status() # from HasStates
reply = self.communicate('X')
match = self.XPAT.match(reply)
statuslist = match.group()
if statuslist[0] != '0':
return ERROR, formatStatusBits(int(statuslist[0]),
['quenched', 'overheated', 'warming up', 'fault'])
# TODO: statuslist[1]: voltage / current limit status
self.action = int(statuslist[2])
if statuslist[3] >= '4':
return ERROR, 'auto run-down'
self.switch_heater = statuslist[3] == '1'
if statuslist[3] == '5':
return ERROR, 'switch heater failure'
# TODO: sweep mode (fast, slow), sweep limits
return status
def read_ramp(self):
return self.query('R9')
def write_ramp(self, value):
return self.change('T', 'R9', value)
def write_action(self, value):
return self.change(f'A{int(value)}')
def read_voltage(self):
return self.query('R1')
def read_working_ramp(self):
return self.query('R6')
def read_setpoint(self):
return self.query('R8')
def write_setpoint(self, value):
return self.change('J', 'R8', value)
def write_switch_heater(self, value):
self.read_status()
if value == self.switch_heater:
self.log.info('switch heater already %r', value)
# we do not want to restart the timer
return value
self.command('H1')
# inherit Magfield.start_field_change
def start_ramp_to_field(self, sm):
if abs(self.current - self.__persistent_field) <= self.tolerance:
self.log.info('leads %g are already at %g', self.current, self.__persistent_field)
return self.ramp_to_field
self.read_value()
self.write_setpoint(self.__persistent_field)
self.write_action(A.run_to_set)
return self.ramp_to_field
# inherit from Magfield: ramp_to_field, stabilize_current, start_switch_on
def wait_for_switch_on(self, sm):
self.read_status()
if self.switch_heater == self.switch_heater.off:
if sm.init: # avoid too many states chained
return Retry
self.log.warning('switch turned off manually?')
return self.start_switch_on
return super().wait_for_switch_on(sm) # will eventually return start_ramp_to_target
def start_ramp_to_target(self, sm):
self.write_action(A.run_to_set)
return self.ramp_to_target
def ramp_to_target(self, sm):
step = self.ramp / 4 # step to be done in 15 seconds
# change the setpoint only gradually, ramping stoppes soon after connection is lost
self.write_setpoint(clamp(self.target, self.setpoint + step, self.setpoint - step))
return super().ramp_to_target() # will eventually return stabilize_field
# inherit from Magfield: stabilize_field, check_switch_off, start_switch_off
def wait_for_switch_off(self, sm):
self.read_status()
if self.switch_heater == self.switch_heater.on:
if sm.init: # avoid too many states chained
return Retry
self.log.warning('switch turned on manually?')
return self.start_switch_off
return super().wait_for_switch_off(sm) # will eventually return start_ramp_to_zero
def start_ramp_to_zero(self, sm):
self.write_action(A.run_to_zero)
return self.ramp_to_zero
# inherit from Magfield: ramp_to_zero
def final_status(self, *args, **kwds):
self.write_action(A.hold)
return super().final_status(*args, **kwds)

View File

@ -166,7 +166,7 @@ class Motor(HasOffset, HasStates, PersistentMixin, HasIO, Drivable):
def write_target(self, value):
self.read_alive_time()
if self._blocking_error:
self.status = ERROR, 'clear_errors needed after ' + self._blocking_error
self.status = ERROR, '<motor>.clear_errors() needed after ' + self._blocking_error
raise HardwareError(self.status[1])
self.saveParameters()
self.start_machine(self.starting, target=value)

View File

@ -48,7 +48,7 @@ class Base(HasIO):
return self.communicate(f'smua.measure.{cmd} = {val}')
class Create_Pulse(Base, Readable, Writable):
class Create_Pulse(Base, Writable):
target = Parameter('source target', FloatRange, unit='A', readonly=False)
width = Parameter('pulse width', FloatRange, unit="s", readonly=False)
resistance = Parameter('resistance', FloatRange)
@ -72,3 +72,4 @@ class Create_Pulse(Base, Readable, Writable):
class Script(Create_Pulse):
pass

View File

@ -36,11 +36,12 @@ import time
import os
from pathlib import Path
from frappy.client import ProxyClient
from frappy.client import ProxyClient, CacheItem
from frappy.datatypes import ArrayOf, BoolType, \
EnumType, FloatRange, IntRange, StringType, StatusType
from frappy.core import IDLE, BUSY, WARN, ERROR, DISABLED
from frappy.errors import ConfigError, HardwareError, ReadFailedError, CommunicationFailedError
from frappy.errors import ConfigError, HardwareError, ReadFailedError, \
CommunicationFailedError, ProgrammingError
from frappy.lib import generalConfig, mkthread, lazy_property
from frappy.lib.asynconn import AsynConn, ConnectionClosed
from frappy.modulebase import Done
@ -332,9 +333,10 @@ class SeaClient(ProxyClient, Module):
self.secNode.srv.shutdown()
else:
for module, param in mplist:
oldv, oldt, oldr = self.cache.get((module, param), [None, None, None])
oldv, oldt, oldr = self.cache[module, param]
if value is None:
value = oldv
self.cache[module, param] = CacheItem(value, now, readerror)
if value != oldv or str(readerror) != str(oldr) or abs(now - oldt) > 60:
# do not update unchanged values within 60 sec
self.updateValue(module, param, value, now, readerror)
@ -417,11 +419,24 @@ class SeaConfigCreator(SeaClient):
return reply
class SeaBool(BoolType):
"""some sea enum nodes used as boolean have text type -> accept '<integer>' also"""
def copy(self):
return SeaBool()
def __call__(self, value):
try:
value = int(value)
return super().__call__(value)
except Exception as e:
raise ReadFailedError(e) from e
SEA_TO_SECOPTYPE = {
'float': FloatRange(),
'text': StringType(),
'int': IntRange(-1 << 63, 1 << 63 - 1),
'bool': BoolType(),
'bool': SeaBool(),
'none': None,
'floatvarar': ArrayOf(FloatRange(), 0, 400), # 400 is the current limit for proper notify events in SEA
}
@ -451,13 +466,19 @@ def get_datatype(paramdesc):
raise ValueError('unknown SEA type %r' % typ)
def get_cfg(cfgdict, *args):
result = cfgdict.get(*args)
return result['value'] if isinstance(result, dict) else result
def pop_cfg(cfgdict, *args):
result = cfgdict.pop(*args)
return result['value'] if isinstance(result, dict) else result
class SeaModule(Module):
io = Attached()
path2param = None
sea_object = None
hdbpath = None # hdbpath for main writable
# pylint: disable=too-many-statements,arguments-differ,too-many-branches
def __new__(cls, name, logger, cfgdict, srv):
if hasattr(srv, 'extra_sea_modules'):
@ -465,90 +486,100 @@ class SeaModule(Module):
else:
extra_modules = {}
srv.extra_sea_modules = extra_modules
for k, v in cfgdict.items():
try:
cfgdict[k] = v['value']
except (KeyError, TypeError):
pass
json_file = cfgdict.pop('json_file', None) or SeaClient.default_json_file[cfgdict['io']]
visibility_level = cfgdict.pop('visibility_level', 2)
single_module = cfgdict.pop('single_module', None)
json_file = pop_cfg(cfgdict, 'json_file', None) or SeaClient.default_json_file[get_cfg(cfgdict, 'io')]
visibility_level = pop_cfg(cfgdict, 'visibility_level', 2)
single_module = pop_cfg(cfgdict, 'single_module', None)
if single_module:
sea_object, base, paramdesc = extra_modules[single_module]
params = [paramdesc]
paramdesc['key'] = 'value'
if issubclass(cls, SeaWritable):
if issubclass(cls, SeaWritable): # and not SeaDrivable!
if paramdesc.get('readonly', True):
raise ConfigError(f"{sea_object}/{paramdesc['path']} is not writable")
params.insert(0, paramdesc.copy()) # copy value
paramdesc['key'] = 'target'
paramdesc['readonly'] = False
extra_module_set = ()
if 'description' not in cfgdict:
if not get_cfg(cfgdict, 'description'):
cfgdict['description'] = f'{single_module}@{json_file}'
else:
sea_object = cfgdict.pop('sea_object')
rel_paths = cfgdict.pop('rel_paths', '.')
if 'description' not in cfgdict:
sea_object = pop_cfg(cfgdict, 'sea_object', None)
sea_path = pop_cfg(cfgdict, 'sea_path', None)
if sea_object:
if sea_path:
raise ConfigError(f'module {name}: superfluous sea_object property (sea_path is given)')
sea_path = sea_object
rel_paths = get_cfg(cfgdict, 'rel_paths', None)
if rel_paths is None:
sea_object, *rel_paths = sea_path.split('/', 1)
if not rel_paths:
rel_paths = None
else:
if '/' in sea_path:
raise ConfigError(f'module {name}: superfluous rel_paths property (sea_path is given)')
sea_object = sea_path
# rel_paths:
# a list of sub nodes to look for parameters
# '.' denotes the main path
# Readable: the main value is taken from the first subpath
# Writable:
# - read the target value: <sicsobj> target
# - write target value: command from first subpath
# Drivable:
# - write target value: run <sea_object> <target>
if not get_cfg(cfgdict, 'description'):
cfgdict['description'] = '%s@%s%s' % (
name, json_file, '' if rel_paths == '.' else f' (rel_paths={rel_paths})')
name, json_file, '' if rel_paths is None else f' (rel_paths={rel_paths})')
with (seaconfig.dir / json_file).open(encoding='utf-8') as fp:
content = json.load(fp)
descr = content[sea_object]
if rel_paths == '*' or not rel_paths:
# take all
main = descr['params'][0]
if issubclass(cls, Readable):
# assert main['path'] == '' # TODO: check cases where this fails
main['key'] = 'value'
else:
descr['params'].pop(0)
else:
# filter by relative paths
result = []
if rel_paths:
result = {k: [] for k in rel_paths}
else:
result = {True: []}
is_running = None
for rpath in rel_paths:
include = True
for paramdesc in descr['params']:
path = paramdesc['path']
if path.endswith('is_running') and issubclass(cls, Drivable):
pathlist = path.split('/')
if pathlist[-1] == 'is_running' and issubclass(cls, Drivable):
# take this independent of visibility
is_running = paramdesc
continue
if paramdesc.get('visibility', 1) > visibility_level:
continue
sub = path.split('/', 1)
if rpath == '.': # take all except subpaths with readonly node at top
if len(sub) == 1:
include = paramdesc.get('kids', 0) == 0 or not paramdesc.get('readonly', True)
if include or path == '':
result.append(paramdesc)
elif sub[0] == rpath:
result.append(paramdesc)
if rel_paths is None:
result[True].append(paramdesc)
else:
cls.paramFilter(result, paramdesc)
cfgdict.pop('rel_paths', None)
params = sum(result.values(), [])
if is_running: # take this at end
result.append(is_running)
descr['params'] = result
rel0 = '' if rel_paths[0] == '.' else rel_paths[0]
if result[0]['path'] == rel0:
params.append(is_running)
main_value = params[0]
if issubclass(cls, Readable):
result[0]['key'] = 'value'
if 'key' in main_value:
raise ProgrammingError(f'main_value {main_value!r}')
main_value['key'] = 'value'
else:
result.pop(0)
else:
logger.error('%s: no value found', name)
params.pop(0)
base = descr['base']
params = descr['params']
if issubclass(cls, SeaWritable):
if issubclass(cls, SeaWritable): # and not SeaDrivable!
paramdesc = params[0]
assert paramdesc['key'] == 'value'
if paramdesc.get('key') != 'value':
raise ProgrammingError(f"key of first parameter of {name} must be 'value'")
params.append(paramdesc.copy()) # copy value?
if paramdesc.get('readonly', True):
raise ConfigError(f"{sea_object}/{paramdesc['path']} is not writable")
paramdesc['key'] = 'target'
paramdesc['readonly'] = False
extra_module_set = set(cfgdict.pop('extra_modules', ()))
extra_module_set = set(pop_cfg(cfgdict, 'extra_modules', ()))
path2param = {}
attributes = {'sea_object': sea_object, 'path2param': path2param}
@ -559,14 +590,14 @@ class SeaModule(Module):
attributes['visibility'] = 2
# check for ambiguous names. candidates are either the last item
# of the path or the full path (underscore separated)
simple_names = {k: 1 for k in cls.accessibles}
duplicates = {k: [k] for k in cls.accessibles}
for paramdesc in params:
path = paramdesc['path']
if path:
pathlist = path.split('/')
if 'key' not in paramdesc:
pname = pathlist[-1]
simple_names[pname] = simple_names.get(pname, 0) + 1
duplicates.setdefault(pname, pathlist)
for paramdesc in params:
path = paramdesc['path']
readonly = paramdesc.get('readonly', True)
@ -583,11 +614,11 @@ class SeaModule(Module):
if len(pathlist) > 0:
if len(pathlist) == 1:
if issubclass(cls, Readable):
kwds['group'] = 'more'
kwds['group'] = 'more_'
else:
kwds['group'] = pathlist[-2]
kwds['group'] = pathlist[-2] + '_'
# take short name if unique
if simple_names[pathlist[-1]] == 1:
if duplicates[pathlist[-1]] == pathlist:
key = pathlist[-1]
else:
key = '_'.join(pathlist)
@ -595,29 +626,39 @@ class SeaModule(Module):
kwds['export'] = False
if key == 'target' and kwds.get('group') == 'more':
kwds.pop('group')
prev = cls.accessibles.get(key)
if key in cls.accessibles:
if key == 'target':
kwds['readonly'] = False
prev = cls.accessibles[key]
if key == 'status':
# special case: status from sea is a string, not the status tuple
pobj = prev.copy()
else:
pobj = Parameter(**kwds)
merged_properties = prev.propertyValues.copy()
pobj.updateProperties(merged_properties)
pobj.merge(merged_properties)
else:
pobj = Parameter(**kwds)
datatype = pobj.datatype
if issubclass(cls, SeaWritable) and key == 'target':
kwds['readonly'] = False
attributes['value'] = Parameter(**kwds)
attributes['target'] = pobj = Parameter(**kwds)
if prev:
merged_properties = prev.propertyValues.copy()
pobj.updateProperties(merged_properties)
pobj.merge(merged_properties)
if key in ('value', 'target'):
unit = get_cfg(cfgdict, 'unit')
if unit is not None:
pcfg = cfgdict.get(key, None)
if not isinstance(pcfg, dict):
cfgdict[key] = pcfg = {} if pcfg is None else {'value': pcfg}
pcfg['unit'] = unit
hdbpath = '/'.join([base] + pathlist)
if key in extra_module_set:
extra_modules[name + '.' + key] = sea_object, base, paramdesc
continue # skip this parameter
if key is not None:
path2param.setdefault(hdbpath, []).append((name, key))
attributes[key] = pobj
@ -631,7 +672,7 @@ class SeaModule(Module):
return reply
rfunc.poll = False
if key != 'status':
if key != 'status' and key is not None:
attributes['read_' + key] = rfunc
if not readonly:
@ -645,7 +686,7 @@ class SeaModule(Module):
self.io.query(cmd)
return Done
attributes['write_' + key] = wfunc
attributes['write_' + (key or 'target')] = wfunc
# create standard parameters like value and status, if not yet there
for pname, pobj in cls.accessibles.items():
@ -659,10 +700,23 @@ class SeaModule(Module):
pobj.__set_name__(cls, pname)
classname = f'{cls.__name__}_{name}'
try:
newcls = type(classname, (cls,), attributes)
except Exception as e:
raise
# newcls = type(classname, (cls,), attributes)
result = Module.__new__(newcls)
return result
@classmethod
def paramFilter(cls, result, paramdesc):
sub = paramdesc['path'].split('/', 1)
sublist = result.get(sub[0])
if sublist is None:
return False
sublist.append(paramdesc)
return True
def updateEvent(self, module, parameter, value, timestamp, readerror):
upd = getattr(self, 'update_' + parameter, None)
if upd:
@ -679,6 +733,7 @@ class SeaReadable(SeaModule, Readable):
_readerror = None
_status = IDLE, ''
unit = Property('physical unit', StringType(isUTF8=True), default='')
status = Parameter(datatype=StatusType(Readable, 'DISABLED'))
def update_value(self, value, timestamp, readerror):
@ -778,11 +833,6 @@ class SeaDrivable(SeaReadable, Drivable):
return IDLE, f'started, but not running'
return IDLE, ''
def update_target(self, module, parameter, value, timestamp, readerror):
# TODO: check if this is needed
if value is not None:
self.target = value
@Command()
def stop(self):
"""propagate to SEA
@ -791,3 +841,20 @@ class SeaDrivable(SeaReadable, Drivable):
- on EaseDriv this will set the stopped state
"""
self.io.query(f'{self.sea_object} is_running 0')
class LscDrivable(SeaDrivable):
def __new__(cls, name, logger, cfgdict, srv):
cfgdict['rel_paths'] = [pop_cfg(cfgdict, 'sensor_path', 'tm'), '.',
pop_cfg(cfgdict, 'set_path', 'set'), 'dblctrl']
return super().__new__(cls, name, logger, cfgdict, srv)
@classmethod
def paramFilter(cls, result, paramdesc):
if super().paramFilter(result, paramdesc):
return True
pathlist = paramdesc['path'].split('/')
if len(pathlist) == 1 and paramdesc.get('kids', 0) == 0:
result['.'].append(paramdesc)
return True
return False

View File

@ -21,7 +21,7 @@
from frappy.core import Command, StringIO, Parameter, HasIO, \
Drivable, FloatRange, IDLE, BUSY, ERROR, WARN, BoolType
from frappy.structparam import StructParam
from frappy.extparams import StructParam
from frappy_psi.convergence import HasConvergence

View File

@ -19,17 +19,19 @@
"""frappy support for ultrasound"""
import math
#import serial
import os
import time
import numpy as np
import frappy_psi.iqplot as iqplot
from frappy_psi.adq_mr import Adq
from frappy_psi.adq_mr import Adq, PEdata, RUSdata
from frappy.core import Attached, BoolType, Done, FloatRange, HasIO, \
IntRange, Module, Parameter, Readable, StringIO, StringType
IntRange, Module, Parameter, Readable, Writable, Drivable, StringIO, StringType, \
IDLE, BUSY, DISABLED, ERROR, TupleOf, ArrayOf, Command
from frappy.properties import Property
#from frappy.modules import Collector
Collector = Readable
def fname_from_time(t, extension):
@ -52,7 +54,6 @@ class Roi(Readable):
time = Parameter('start time', FloatRange(unit='nsec'), readonly=False)
size = Parameter('interval (symmetric around time)', FloatRange(unit='nsec'), readonly=False)
enable = Parameter('calculate this roi', BoolType(), readonly=False, default=True)
#status = Parameter(export=False)
pollinterval = Parameter(export=False)
interval = (0, 0)
@ -65,6 +66,9 @@ class Roi(Readable):
def calc_interval(self):
self.interval = (self.time - 0.5 * self.size, self.time + 0.5 * self.size)
def read_status(self):
return (IDLE, '') if self.enable else (DISABLED, 'disabled')
def write_time(self, value):
self.time = value
self.calc_interval()
@ -82,81 +86,29 @@ class Pars(Module):
timestamp = Parameter('unix timestamp', StringType(), default='0', readonly=False)
temperature = Parameter('T', FloatRange(unit='K'), default=0, readonly=False)
mf = Parameter('field', FloatRange(unit='T'), default=0, readonly=False)
sr = Parameter('rotaion angle', FloatRange(unit='deg'), default=0, readonly=False)
sr = Parameter('rotation angle', FloatRange(unit='deg'), default=0, readonly=False)
class FreqStringIO(StringIO):
end_of_line = '\r'
class Frequency(HasIO, Readable):
pars = Attached()
sr = Property('samples per record', datatype=IntRange(), default=16384)
maxy = Property('plot y scale', datatype=FloatRange(), default=0.5)
value = Parameter('frequency@I,q', datatype=FloatRange(unit='Hz'), default=0)
basefreq = Parameter('base frequency', FloatRange(unit='Hz'), readonly=False)
nr = Parameter('number of records', datatype=IntRange(1,10000), default=500)
freq = Parameter('target frequency', FloatRange(unit='Hz'), readonly=False)
bw = Parameter('bandwidth lowpassfilter', datatype=FloatRange(unit='Hz'),default=10E6)
class Frequency(HasIO, Writable):
value = Parameter('frequency', unit='Hz')
amp = Parameter('amplitude', FloatRange(unit='dBm'), readonly=False)
control = Parameter('control loop on?', BoolType(), readonly=False, default=True)
time = Parameter('pulse start time', FloatRange(unit='nsec'),
readonly=False)
size = Parameter('pulse length (starting from time)', FloatRange(unit='nsec'),
readonly=False)
pulselen = Parameter('adjusted pulse length (integer number of periods)', FloatRange(unit='nsec'), default=1)
maxstep = Parameter('max frequency step', FloatRange(unit='Hz'), readonly=False,
default=10000)
minstep = Parameter('min frequency step for slope calculation', FloatRange(unit='Hz'),
readonly=False, default=4000)
slope = Parameter('inphase/frequency slope', FloatRange(), readonly=False,
default=1e6)
plot = Parameter('create plot images', BoolType(), readonly=False, default=True)
save = Parameter('save data', BoolType(), readonly=False, default=True)
pollinterval = Parameter(datatype=FloatRange(0,120))
last_change = 0
ioClass = FreqStringIO
dif = None
lastfreq = None
old = None
starttime = None
interval = (0,0)
def register_dif(self, dif):
self.dif = dif
def earlyInit(self):
super().earlyInit()
self.adq = Adq(self.nr, self.sr, self.bw)
self.roilist = []
self.write_nr(self.nr)
self.skipctrl = 0
self.plotter = iqplot.Plot(self.maxy)
self.calc_interval()
def calc_interval(self):
self.interval = (self.time, self.time + self.size)
def write_time(self, value):
self.time = value
self.calc_interval()
return Done
def write_size(self, value):
self.size = value
self.calc_interval()
return Done
def write_nr(self, value):
# self.pollinterval = value * 0.0001
return value
def register_roi(self, roi):
self.roilist.append(roi)
def set_freq(self):
freq = self.freq + self.basefreq
self.communicate('FREQ %.15g;FREQ?' % freq)
#self._iodev.readline().decode('ascii')
return freq
def write_target(self, value):
self.communicate('FREQ %.15g;FREQ?' % value)
self.last_change = time.time()
if self.dif:
self.dif.read_value()
def write_amp(self, amp):
reply = self.communicate('AMPR %g;AMPR?' % amp)
@ -166,94 +118,196 @@ class Frequency(HasIO, Readable):
reply = self.communicate('AMPR?')
return float(reply)
def write_freq(self, value):
self.skipctrl = 2 # suppress control for the 2 next steps
return value
def doPoll(self):
"""main poll loop body"""
if self.lastfreq is None:
self.lastfreq = self.set_freq()
self.adq.start()
if self.starttime is None:
class FrequencyDif(Readable):
freq = Attached(Frequency)
base = Parameter('base frequency', FloatRange(unit='Hz'), default=0)
value = Parameter('difference to base frequency', FloatRange(unit='Hz'), default=0)
def initModule(self):
super().initModule()
self.freq.register_dif(self)
def read_value(self):
return self.freq - self.base
class Base(Collector):
freq = Attached()
adq = Attached(Adq)
sr = Parameter('samples per record', datatype=IntRange(1, 1E9), default=16384)
pollinterval = Parameter(datatype=FloatRange(0, 120)) # allow pollinterval = 0
_data = None
_data_args = None
def read_status(self):
adqstate = self.adq.get_status()
if adqstate == Adq.BUSY:
return BUSY, 'acquiring'
if adqstate == Adq.UNDEFINED:
return ERROR, 'no data yet'
if adqstate == Adq.READY:
return IDLE, 'new data available'
return IDLE, ''
def get_data(self):
data = self.adq.get_data(*self._data_args)
if id(data) != id(self._data):
self._data = data
return data
return None
class PulseEcho(Base):
value = Parameter("t, i, q, pulse curves",
TupleOf(*[ArrayOf(FloatRange(), 0, 16283) for _ in range(4)]), default=[[]] * 4)
nr = Parameter('number of records', datatype=IntRange(1, 9999), default=500)
bw = Parameter('bandwidth lowpassfilter', datatype=FloatRange(unit='Hz'), default=10E6)
control = Parameter('control loop on?', BoolType(), readonly=False, default=True)
time = Parameter('pulse start time', FloatRange(unit='nsec'),
readonly=False)
size = Parameter('pulse length (starting from time)', FloatRange(unit='nsec'),
readonly=False)
pulselen = Parameter('adjusted pulse length (integer number of periods)', FloatRange(unit='nsec'), default=1)
starttime = None
def initModule(self):
super().initModule()
self.adq = Adq()
self.adq.init(self.sr, self.nr)
self.roilist = []
def write_nr(self, value):
self.adq.init(self.sr, value)
def write_sr(self, value):
self.adq.init(value, self.nr)
def write_bw(self, value):
self.adq.bw_cutoff = value
def register_roi(self, roi):
self.roilist.append(roi)
def go(self):
self.starttime = time.time()
times = []
times.append(('init', time.time()))
seadata = {p: float(getattr(self.pars, p)) for p in self.pars.parameters}
data = self.adq.getdata() # this will wait, if not yet finished
#save sample
#np.save('sample.dat',data)
times.append(('wait',time.time()))
if self.control:
freq = self.lastfreq # data was acquired at this freq
else:
freq = self.set_freq()
seadata['frequency'] = freq
if self.control:
self.lastfreq = self.set_freq()
times.append(('setf',time.time()))
self.adq.start() # start next acq
times.append(('start',time.time()))
roilist = [r for r in self.roilist if r.enable]
self.adq.start()
gates = self.adq.gates_and_curves(data, freq, self.interval,
def read_value(self):
if self.get_rawdata(): # new data available
roilist = [r for r in self.roilist if r.enable]
freq = self.freq.value
gates = self.adq.gates_and_curves(self._data, freq,
(self.time, self.time + self.size),
[r.interval for r in roilist])
if self.save:
times.append(('save',time.time()))
tdata, idata, qdata, pdata = self.adq.curves
seadata['timestep'] = tdata[1] - tdata[0]
iqdata = np.array((idata, qdata, pdata), dtype='f4')
ts = seadata['timestamp']
if ts:
filename = fname_from_time(ts, '.npz')
seanp = np.array(list(seadata.items()), dtype=[('name', 'U16'), ('value', 'f8')])
np.savez(filename, seadata=seanp, iqdata=iqdata)
# can be load back via
# content = np.load(filename)
# content['seadata'], content['iqdata']
self.pulselen = self.adq.pulselen
times.append(('ana',time.time()))
if self.plot:
# get reduced interval from adq.sinW
pulseint = (self.interval[0], self.interval[0] + self.pulselen)
try:
self.plotter.plot(
self.adq.curves,
rois=[pulseint] + [r.interval for r in roilist],
average=([r.time for r in roilist],
[r.i for r in roilist],
[r.q for r in roilist]))
except Exception as e:
self.log.warning('can not plot %r' % e)
else:
self.plotter.close()
now = time.time()
times.append(('plot',now))
# print(' '.join('%s %5.3f' % (label, t - self.starttime) for label, t in times))
self.starttime = now
self.value = freq - self.basefreq
for i, roi in enumerate(roilist):
roi.i = a = gates[i][0]
roi.q = b = gates[i][1]
roi.value = math.sqrt(a ** 2 + b ** 2)
roi.phase = math.atan2(a, b) * 180 / math.pi
inphase = self.roilist[0].i
if self.control:
newfreq = freq + inphase * self.slope - self.basefreq
# step = sorted((-self.maxstep, inphase * self.slope, self.maxstep))[1]
if self.old:
fdif = freq - self.old[0]
idif = inphase - self.old[1]
if abs(fdif) >= self.minstep:
self.slope = - fdif / idif
else:
fdif = 0
idif = 0
newfreq = freq + self.minstep
self.old = (freq, inphase)
if self.skipctrl > 0: # do no control for some time after changing frequency
self.skipctrl -= 1
elif self.control:
self.freq = sorted((self.freq - self.maxstep, newfreq, self.freq + self.maxstep))[1]
#print(times)
return Done
return self.adq.curves
# TODO: CONTROL
# inphase = self.roilist[0].i
# if self.control:
# newfreq = freq + inphase * self.slope - self.basefreq
# # step = sorted((-self.maxstep, inphase * self.slope, self.maxstep))[1]
# if self.old:
# fdif = freq - self.old[0]
# idif = inphase - self.old[1]
# if abs(fdif) >= self.minstep:
# self.slope = - fdif / idif
# else:
# fdif = 0
# idif = 0
# newfreq = freq + self.minstep
# self.old = (freq, inphase)
# if self.skipctrl > 0: # do no control for some time after changing frequency
# self.skipctrl -= 1
# elif self.control:
# self.freq = sorted((self.freq - self.maxstep, newfreq, self.freq + self.maxstep))[1]
class RUS(Base):
value = Parameter('averaged (I, Q) tuple', TupleOf(FloatRange(), FloatRange()))
periods = Parameter('number of periods', IntRange(1, 9999), default=12)
scale = Parameter('scale,taking into account input attenuation', FloatRange(), default=0.1)
input_phase_stddev = Parameter('input signal quality', FloatRange(unit='rad'))
output_phase_slope = Parameter('output signal phase slope', FloatRange(unit='rad/sec'))
output_amp_slope = Parameter('output signal amplitude change', FloatRange(unit='1/sec'))
phase = Parameter('phase', FloatRange(unit='deg'))
amp = Parameter('amplitude', FloatRange())
starttime = None
_data_args = None
def initModule(self):
super().initModule()
self.adq = Adq()
# self.write_periods(self.periods)
def read_value(self):
if self._data_args is None:
return self.value # or may we raise as no value is defined yet?
data = self.get_data(RUSdata, *self._data_args)
if data:
# data available
data.calc_quality()
self.input_phase_stddev = data.input_stddev.imag
self.output_phase_slope = data.output_slope.imag
self.output_amp_slope = data.output_slope.real
iq = data.iq * self.scale
self.phase = np.arctan2(iq.imag, iq.real) * 180 / np.pi
self.amp = np.abs(iq.imag, iq.real)
return iq.real, iq.imag
return self.value
def go(self):
self.starttime = time.time()
freq = self.freq.value
self._data_args = (RUSdata, freq, self.periods)
self.sr = round(self.periods * self.adq.sample_rate / freq)
self.adq.init(self.sr, 1)
self.adq.start()
self.read_status()
class ControlLoop:
maxstep = Parameter('max frequency step', FloatRange(unit='Hz'), readonly=False,
default=10000)
minstep = Parameter('min frequency step for slope calculation', FloatRange(unit='Hz'),
readonly=False, default=4000)
slope = Parameter('inphase/frequency slope', FloatRange(), readonly=False,
default=1e6)
# class Frequency(HasIO, Readable):
# pars = Attached()
# curves = Attached(mandatory=False)
# maxy = Property('plot y scale', datatype=FloatRange(), default=0.5)
#
# value = Parameter('frequency@I,q', datatype=FloatRange(unit='Hz'), default=0)
# basefreq = Parameter('base frequency', FloatRange(unit='Hz'), readonly=False)
# nr = Parameter('number of records', datatype=IntRange(1,10000), default=500)
# sr = Parameter('samples per record', datatype=IntRange(1,1E9), default=16384)
# freq = Parameter('target frequency', FloatRange(unit='Hz'), readonly=False)
# bw = Parameter('bandwidth lowpassfilter', datatype=FloatRange(unit='Hz'),default=10E6)
# amp = Parameter('amplitude', FloatRange(unit='dBm'), readonly=False)
# control = Parameter('control loop on?', BoolType(), readonly=False, default=True)
# rusmode = Parameter('RUS mode on?', BoolType(), readonly=False, default=False)
# time = Parameter('pulse start time', FloatRange(unit='nsec'),
# readonly=False)
# size = Parameter('pulse length (starting from time)', FloatRange(unit='nsec'),
# readonly=False)
# pulselen = Parameter('adjusted pulse length (integer number of periods)', FloatRange(unit='nsec'), default=1)
# maxstep = Parameter('max frequency step', FloatRange(unit='Hz'), readonly=False,
# default=10000)
# minstep = Parameter('min frequency step for slope calculation', FloatRange(unit='Hz'),
# readonly=False, default=4000)
# slope = Parameter('inphase/frequency slope', FloatRange(), readonly=False,
# default=1e6)
# plot = Parameter('create plot images', BoolType(), readonly=False, default=True)
# save = Parameter('save data', BoolType(), readonly=False, default=True)
# pollinterval = Parameter(datatype=FloatRange(0,120))

View File

@ -96,11 +96,13 @@ def print_commit(line):
print(' '.join(output), title)
cnt[0] += 1
if cnt[0] % 50 == 0:
input(f' {br0:11s} {br1:11s}')
if input(f' {br0:11s} {br1:11s}'):
raise StopIteration()
(br0, log0), (br1, log1) = list(log_no.items())
no1 = 0
try:
for no0, line0 in enumerate(log0):
if line0[1]: # line not yet printed
infodict = commits[line0[1]]
@ -114,3 +116,5 @@ for no0, line0 in enumerate(log0):
print_commit(line1)
no1 = no1end
print_commit(line0)
except StopIteration:
pass

View File

@ -1,2 +1,3 @@
2024-01-29 wip develop
2024-01-29 wip mlz
2024-10-01 wip sinq

View File

@ -1,21 +0,0 @@
# content of conftest.py
import pytest
@pytest.fixture(scope="module")
def constants():
# setup
class Constants:
ONE = 1
TWO = 2
c = Constants()
yield c
# teardown
del c
# pylint: disable=redefined-builtin
@pytest.fixture(scope="session")
def globals():
return {}

13
test/test_cfg_editor.py Normal file
View File

@ -0,0 +1,13 @@
from pathlib import Path
from frappy.gui.cfg_editor.utils import get_modules, get_interfaces
from frappy.lib import generalConfig
basedir = Path(__file__).parent.parent.absolute()
def test_imports():
generalConfig.testinit(basedir=basedir)
get_modules()
get_interfaces()

View File

@ -137,5 +137,5 @@ def test_process_file(direc, log):
def test_full(direc, log):
ret = load_config('pyfile_cfg.py', log)
ret = load_config(['pyfile_cfg.py'], log)
do_asserts(ret)

62
test/test_discovery.py Normal file
View File

@ -0,0 +1,62 @@
# *****************************************************************************
#
# 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>
#
# *****************************************************************************
"""Test discovery messages"""
from test.test_modules import LoggerStub
from frappy.protocol.discovery import UDPListener, MAX_MESSAGE_LEN
from frappy.version import get_version
def test_empty():
logger = LoggerStub()
udp = UDPListener('', '', ['tcp://0'], logger)
udp.firmware = ''
# 78 is the maximum overhead
assert 78 == len(udp._getMessage(2**16-1))
def test_basic():
logger = LoggerStub()
udp = UDPListener('eq', 'desc', ['tcp://1234'], logger)
assert udp.description == 'desc'
assert udp.equipment_id == 'eq'
assert udp.ports == [1234]
assert udp.firmware == 'FRAPPY ' + get_version()
def test_ascii_truncation():
logger = LoggerStub()
desc = 'a' * MAX_MESSAGE_LEN
udp = UDPListener('eq', desc, ['tcp://1234'], logger)
assert MAX_MESSAGE_LEN == len(udp._getMessage(65535))
fw = len(('FRAPPY ' + get_version()).encode('utf-8'))
expected_length = 430 - fw - 2
assert expected_length == len(udp.description)
def test_unicode_truncation():
logger = LoggerStub()
desc = '\U0001f604' * 400
udp = UDPListener('eq', desc, ['tcp://1234'], logger)
fw = len(('FRAPPY ' + get_version()).encode('utf-8'))
# 4 bytes per symbol, rounded down for the potential cut
expected_length = (430 - fw - 2) // 4
assert expected_length == len(udp.description)

View File

@ -28,7 +28,7 @@ from glob import glob
import pytest
from frappy.datatypes import BoolType, FloatRange, StringType, IntRange, ScaledInteger
from frappy.errors import ProgrammingError, ConfigError, RangeError
from frappy.errors import ProgrammingError, ConfigError, RangeError, HardwareError
from frappy.modules import Communicator, Drivable, Readable, Module, Writable
from frappy.params import Command, Parameter, Limit
from frappy.rwhandler import ReadHandler, WriteHandler, nopoll
@ -141,12 +141,10 @@ def test_ModuleMagic():
# first inherited accessibles
sortcheck1 = ['value', 'status', 'pollinterval', 'target', 'stop',
sortcheck1 = ['value', 'status', 'target', 'pollinterval', 'stop',
'param1', 'param2', 'cmd', 'a1', 'a2', 'cmd2']
class Newclass2(Newclass1):
paramOrder = 'param1', 'param2', 'cmd', 'value'
@Command(description='another stuff')
def cmd2(self, arg):
return arg
@ -171,9 +169,9 @@ def test_ModuleMagic():
def read_value(self):
return 0
# first inherited items not mentioned, then the ones mentioned in paramOrder, then the other new ones
sortcheck2 = ['status', 'pollinterval', 'target', 'stop',
'a1', 'a2', 'cmd2', 'param1', 'param2', 'cmd', 'value', 'b2']
# first predefined parameters, then in the order of inheritance
sortcheck2 = ['value', 'status', 'target', 'pollinterval', 'stop',
'param1', 'param2', 'cmd', 'a1', 'a2', 'cmd2', 'b2']
updates = {}
srv = ServerStub(updates)
@ -245,7 +243,7 @@ def test_ModuleMagic():
'export', 'group', 'description', 'features',
'meaning', 'visibility', 'implementation', 'interface_classes', 'target', 'stop',
'status', 'param1', 'param2', 'cmd', 'a2', 'pollinterval', 'slowinterval', 'b2',
'cmd2', 'value', 'a1', 'omit_unchanged_within'}
'cmd2', 'value', 'a1', 'omit_unchanged_within', 'original_id'}
assert set(cfg['value'].keys()) == {
'group', 'export', 'relative_resolution',
'visibility', 'unit', 'default', 'value', 'datatype', 'fmtstr',
@ -943,3 +941,36 @@ def test_stop_doc(modcls):
if (modcls.stop.description == Drivable.stop.description
and modcls.stop.func != Drivable.stop.func):
assert modcls.stop.func.__doc__ # stop method needs a doc string
def test_write_error():
updates = {}
srv = ServerStub(updates)
class Mod(Module):
par = Parameter('', FloatRange(), readonly=False, default=0)
def read_par(self):
raise HardwareError('failure')
def write_par(self, value):
if value < 0:
raise RangeError('outside range')
a = Mod('a', LoggerStub(), {'description': 'test'}, srv)
# behaviour on read errors:
with pytest.raises(HardwareError):
a.read_par()
assert updates == {'a': {('error', 'par'): 'failure'}}
updates.clear()
# behaviour on normal write
a.write_par(1)
assert updates['a']['par'] == 1
updates.clear()
# behaviour when write failed
with pytest.raises(RangeError):
a.write_par(-1)
assert not updates # no error update!

View File

@ -148,6 +148,8 @@ def test_Property_override():
o2 = co()
assert o1.a == 1
assert o2.a == 3
o2.setProperty('a', 4)
assert o2.a == 4
with pytest.raises(ProgrammingError) as e:
class cx(c): # pylint: disable=unused-variable

View File

@ -57,13 +57,13 @@ def test_name_only(direc, log):
def test_file(direc, log):
"""only see that this does not throw. get config from cfgfiles."""
s = Server('foo', log, cfgfiles='pyfile_cfg.py')
s = Server('foo', log, cfgfiles=['pyfile_cfg.py'])
s._processCfg()
def test_basic_description(direc, log):
"""only see that this does not throw. get config from cfgfiles."""
s = Server('foo', log, cfgfiles='pyfile_cfg.py')
s = Server('foo', log, cfgfiles=['pyfile_cfg.py'])
s._processCfg()
desc = s.secnode.get_descriptive_data('')
# secnode properties correctly exported

View File

@ -1,14 +0,0 @@
from frappy.gui.cfg_editor.utils import get_modules
def test_assert():
assert 1
def test_constants(constants):
assert constants.ONE == 1
assert constants.TWO == 2
def test_imports():
get_modules()