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). # Maximum number of parents for a class (see R0901).
max-parents=20 max-parents=20
# Maximum number of positional arguments
max-positional-arguments=10
# Maximum number of attributes for a class (see R0902). # Maximum number of attributes for a class (see R0902).
max-attributes=50 max-attributes=50

View File

@ -23,6 +23,8 @@
import sys import sys
from pathlib import Path from pathlib import Path
from frappy.lib import generalConfig
from frappy.logging import logger
# Add import path for inplace usage # Add import path for inplace usage
sys.path.insert(0, str(Path(__file__).absolute().parents[1])) 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.client.interactive import Console
from frappy.playground import play, USAGE from frappy.playground import play, USAGE
generalConfig.init()
logger.init()
if len(sys.argv) > 1: if len(sys.argv) > 1:
play(sys.argv[1]) play(sys.argv[1])
else: 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): 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 = parser.add_mutually_exclusive_group()
loggroup.add_argument("-v", "--verbose", loggroup.add_argument("-v", "--verbose",
help="Output lots of diagnostic information", help="Output lots of diagnostic information",
@ -60,9 +68,9 @@ def parseArgv(argv):
action='store', action='store',
help="comma separated list of cfg files,\n" help="comma separated list of cfg files,\n"
"defaults to <name_of_the_instance>.\n" "defaults to <name_of_the_instance>.\n"
"cfgfiles given without '.cfg' extension are searched" "If a config file contains a slash, it is treated as a"
" in the configuration directory," "path, otherwise the file is searched for in the "
" else they are treated as path names", "configuration directory.",
default=None) default=None)
parser.add_argument('-g', parser.add_argument('-g',
'--gencfg', '--gencfg',
@ -96,7 +104,9 @@ def main(argv=None):
generalConfig.init(args.gencfg) generalConfig.init(args.gencfg)
logger.init(loglevel) 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) interface=args.port, testonly=args.test)
if args.daemonize: if args.daemonize:

View File

@ -22,22 +22,35 @@
Usage: 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. open a server on <server port> to communicate with the string based <communicator> over TCP/IP.
Use cases, mainly for test purposes: 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 - 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 sys
import argparse import argparse
from pathlib import Path from pathlib import Path
import asyncore
import socket import socket
import time import time
import os
from ast import literal_eval
from socketserver import BaseRequestHandler, ThreadingTCPServer
# Add import path for inplace usage # Add import path for inplace usage
sys.path.insert(0, str(Path(__file__).absolute().parents[1])) 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 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: class Logger:
def debug(self, *args): def debug(self, *args):
pass pass
@ -144,43 +71,126 @@ class Logger:
exception = error = warn = info 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): 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", parser.add_argument("-v", "--verbose",
help="output full communication", help="output full communication",
action='store_true', default=False) action='store_true', default=False)
parser.add_argument("cls", parser.add_argument("cls",
type=str, type=str,
help="simulator class.\n",) help="communicator class.\n",)
parser.add_argument('-p', parser.add_argument('-p',
'--port', '--port',
action='store', action='store',
help='server port or uri', help='server port or uri',
default=2089) default=2089)
parser.add_argument('-o',
'--options',
action='store',
nargs='*',
help='options in the form key=value',
default=None)
return parser.parse_args(argv) return parser.parse_args(argv)
def poller(pollfunc):
while True:
time.sleep(1.0)
pollfunc()
def main(argv=None): def main(argv=None):
if argv is None: if argv is None:
argv = sys.argv argv = sys.argv
args = parse_argv(argv[1:]) args = parse_argv(argv[1:])
options = {'description': ''}
opts = {'description': 'simulator'} for item in args.options or ():
key, eq, value = item.partition('=')
handler_args = {'verbose': args.verbose} if not eq:
srv = Server(int(args.port), Handler, handler_args) raise ValueError(f"missing '=' in {item}")
module = get_class(args.cls)(args.cls, Logger(), opts, srv) try:
handler_args['module'] = module value = literal_eval(value)
module.earlyInit() except Exception:
mkthread(poller, module.doPoll) pass
srv.loop() options[key] = value
srv = Server(int(args.port), args.cls, options, args.verbose)
srv.serve_forever()
if __name__ == '__main__': 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', Mod('tt',
'frappy_psi.sea.SeaDrivable', '', 'frappy_psi.sea.SeaDrivable', '',
io='sea_main', io='sea_main',
meaning=['temperature_regulation', 20], meaning=['temperature_regulation', 27],
sea_object='tt', sea_object='tt',
rel_paths=['tm', 'set', 'dblctrl'], rel_paths=['tm', '.', 'set', 'dblctrl'],
) )
Mod('cc', Mod('cc',

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -23,7 +23,7 @@ Mod('ips',
Mod('T_stat', Mod('T_stat',
'frappy_psi.mercury.TemperatureAutoFlow', 'frappy_psi.mercury.TemperatureAutoFlow',
'static heat exchanger temperature', 'static heat exchanger temperature',
meaning=['temperature_regulation', 20], meaning=['temperature_regulation', 27],
output_module='htr_stat', output_module='htr_stat',
needle_valve='p_stat', needle_valve='p_stat',
slot='DB6.T1', 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', Mod('tt',
'frappy_psi.sea.SeaDrivable', '', 'frappy_psi.sea.SeaDrivable', '',
io='sea_main', io='sea_main',
meaning=['temperature_regulation', 20], meaning=['temperature_regulation', 27],
sea_object='tt', sea_object='tt',
rel_paths=['tm', 'set', 'dblctrl'], rel_paths=['tm', '.', 'set', 'dblctrl'],
) )
Mod('cc', Mod('cc',

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -18,22 +18,22 @@
{"path": "t1", "type": "float", "readonly": false, "cmd": "run tt", "kids": 3}, {"path": "t1", "type": "float", "readonly": false, "cmd": "run tt", "kids": 3},
{"path": "t1/raw", "type": "float", "readonly": false, "cmd": "run tt"}, {"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", "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": "t1/valid", "type": "bool", "readonly": false, "cmd": "run tt"},
{"path": "t2", "type": "float", "readonly": false, "cmd": "run tt", "kids": 3}, {"path": "t2", "type": "float", "readonly": false, "cmd": "run tt", "kids": 3},
{"path": "t2/raw", "type": "float", "readonly": false, "cmd": "run tt"}, {"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", "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": "t2/valid", "type": "bool", "readonly": false, "cmd": "run tt"},
{"path": "t3", "type": "float", "readonly": false, "cmd": "run tt", "kids": 3}, {"path": "t3", "type": "float", "readonly": false, "cmd": "run tt", "kids": 3},
{"path": "t3/raw", "type": "float", "readonly": false, "cmd": "run tt"}, {"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", "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": "t3/valid", "type": "bool", "readonly": false, "cmd": "run tt"},
{"path": "t4", "type": "float", "readonly": false, "cmd": "run tt", "kids": 3}, {"path": "t4", "type": "float", "readonly": false, "cmd": "run tt", "kids": 3},
{"path": "t4/raw", "type": "float", "readonly": false, "cmd": "run tt"}, {"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", "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": "t4/valid", "type": "bool", "readonly": false, "cmd": "run tt"},
{"path": "tref", "type": "float", "readonly": false, "cmd": "run tt"}, {"path": "tref", "type": "float", "readonly": false, "cmd": "run tt"},
{"path": "tout", "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"}]}, {"path": "custompar", "type": "float", "readonly": false, "cmd": "hemot custompar"}]},
"mf": {"base": "/mf", "params": [ "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": "persmode", "type": "int", "readonly": false, "cmd": "mf persmode"},
{"path": "perswitch", "type": "int"}, {"path": "perswitch", "type": "int"},
{"path": "nowait", "type": "int", "readonly": false, "cmd": "mf nowait"}, {"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": "send", "type": "text", "readonly": false, "cmd": "tt send", "visibility": 3},
{"path": "status", "type": "text", "visibility": 3}, {"path": "status", "type": "text", "visibility": 3},
{"path": "is_running", "type": "int", "readonly": false, "cmd": "tt is_running", "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/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": "setsamp/deriv", "type": "float", "readonly": false, "cmd": "tt setsamp/deriv"},
{"path": "display", "type": "text", "readonly": false, "cmd": "tt display"}, {"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": "send", "type": "text", "readonly": false, "cmd": "cc send", "visibility": 3},
{"path": "status", "type": "text", "visibility": 3}, {"path": "status", "type": "text", "visibility": 3},
{"path": "autodevice", "type": "bool", "readonly": false, "cmd": "cc autodevice"}, {"path": "autodevice", "type": "bool", "readonly": false, "cmd": "cc autodevice"},
@ -181,7 +185,10 @@
{"path": "tm", "type": "float", "visibility": 3}, {"path": "tm", "type": "float", "visibility": 3},
{"path": "tv", "type": "float", "visibility": 3}, {"path": "tv", "type": "float", "visibility": 3},
{"path": "tq", "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": "send", "type": "text", "readonly": false, "cmd": "nv send", "visibility": 3},
{"path": "status", "type": "text", "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": "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": "autoflow/flowtarget", "type": "float"},
{"path": "calib", "type": "none", "kids": 2}, {"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/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": "send", "type": "text", "readonly": false, "cmd": "hepump send", "visibility": 3},
{"path": "status", "type": "text", "visibility": 3}, {"path": "status", "type": "text", "visibility": 3},
{"path": "running", "type": "bool", "readonly": false, "cmd": "hepump running"}, {"path": "running", "type": "bool", "readonly": false, "cmd": "hepump running"},
@ -239,7 +250,10 @@
{"path": "auto", "type": "bool", "readonly": false, "cmd": "hepump auto"}, {"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": "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": "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": "send", "type": "text", "readonly": false, "cmd": "hemot send", "visibility": 3},
{"path": "status", "type": "text", "visibility": 3}, {"path": "status", "type": "text", "visibility": 3},
{"path": "is_running", "type": "int", "readonly": false, "cmd": "hemot is_running", "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": "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": "customadr", "type": "text", "readonly": false, "cmd": "hemot customadr"},
{"path": "custompar", "type": "float", "readonly": false, "cmd": "hemot custompar"}]}, {"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": "", "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": "send", "type": "text", "readonly": false, "cmd": "ln2fill send", "visibility": 3},
{"path": "status", "type": "text", "visibility": 3}, {"path": "status", "type": "text", "visibility": 3},
@ -285,7 +300,10 @@
{"path": "maxholdhours", "type": "float", "readonly": false, "cmd": "ln2fill maxholdhours"}, {"path": "maxholdhours", "type": "float", "readonly": false, "cmd": "ln2fill maxholdhours"},
{"path": "tolerance", "type": "float", "readonly": false, "cmd": "ln2fill tolerance"}, {"path": "tolerance", "type": "float", "readonly": false, "cmd": "ln2fill tolerance"},
{"path": "badreadingminutes", "type": "float", "readonly": false, "cmd": "ln2fill badreadingminutes"}, {"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": "send", "type": "text", "readonly": false, "cmd": "hefill send", "visibility": 3},
{"path": "status", "type": "text", "visibility": 3}, {"path": "status", "type": "text", "visibility": 3},
{"path": "state", "type": "text"}, {"path": "state", "type": "text"},
@ -301,11 +319,17 @@
{"path": "badreadingminutes", "type": "float", "readonly": false, "cmd": "hefill badreadingminutes"}, {"path": "badreadingminutes", "type": "float", "readonly": false, "cmd": "hefill badreadingminutes"},
{"path": "tubecoolingminutes", "type": "float", "readonly": false, "cmd": "hefill tubecoolingminutes"}, {"path": "tubecoolingminutes", "type": "float", "readonly": false, "cmd": "hefill tubecoolingminutes"},
{"path": "vessellimit", "type": "float", "readonly": false, "cmd": "hefill vessellimit"}, {"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": "send", "type": "text", "readonly": false, "cmd": "lev send", "visibility": 3},
{"path": "status", "type": "text", "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": "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": "persmode", "type": "int", "readonly": false, "cmd": "mf persmode"},
{"path": "perswitch", "type": "int"}, {"path": "perswitch", "type": "int"},
{"path": "nowait", "type": "int", "readonly": false, "cmd": "mf nowait"}, {"path": "nowait", "type": "int", "readonly": false, "cmd": "mf nowait"},
@ -330,7 +354,10 @@
{"path": "driver", "type": "text", "visibility": 3}, {"path": "driver", "type": "text", "visibility": 3},
{"path": "creationCmd", "type": "text", "visibility": 3}, {"path": "creationCmd", "type": "text", "visibility": 3},
{"path": "targetValue", "type": "float"}, {"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": "send", "type": "text", "readonly": false, "cmd": "tcoil send", "visibility": 3},
{"path": "status", "type": "text", "visibility": 3}, {"path": "status", "type": "text", "visibility": 3},
{"path": "excitation", "type": "float", "readonly": false, "cmd": "tcoil excitation", "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/enable", "type": "bool", "readonly": false, "cmd": "tcoil td/enable"},
{"path": "td/r", "type": "float"}, {"path": "td/r", "type": "float"},
{"path": "td/curve", "type": "text", "readonly": false, "cmd": "tcoil td/curve", "kids": 3}, {"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/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"}, {"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"}, {"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", "type": "float", "visibility": 3, "kids": 3},
{"path": "tc/enable", "type": "bool", "readonly": false, "cmd": "tcoil tc/enable"}, {"path": "tc/enable", "type": "bool", "readonly": false, "cmd": "tcoil tc/enable"},
{"path": "tc/r", "type": "float"}, {"path": "tc/r", "type": "float"},
{"path": "tc/curve", "type": "text", "readonly": false, "cmd": "tcoil tc/curve", "kids": 3}, {"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/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"}, {"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"}, {"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", "type": "float", "visibility": 3, "kids": 3},
{"path": "tb/enable", "type": "bool", "readonly": false, "cmd": "tcoil tb/enable"}, {"path": "tb/enable", "type": "bool", "readonly": false, "cmd": "tcoil tb/enable"},
{"path": "tb/r", "type": "float"}, {"path": "tb/r", "type": "float"},
{"path": "tb/curve", "type": "text", "readonly": false, "cmd": "tcoil tb/curve", "kids": 3}, {"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/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"}, {"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"}, {"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", "type": "float", "visibility": 3, "kids": 3},
{"path": "ta/enable", "type": "bool", "readonly": false, "cmd": "tcoil ta/enable"}, {"path": "ta/enable", "type": "bool", "readonly": false, "cmd": "tcoil ta/enable"},
{"path": "ta/r", "type": "float"}, {"path": "ta/r", "type": "float"},
{"path": "ta/curve", "type": "text", "readonly": false, "cmd": "tcoil ta/curve", "kids": 3}, {"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/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"}, {"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"}, {"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", "type": "float", "visibility": 3, "kids": 3},
{"path": "ref/enable", "type": "bool", "readonly": false, "cmd": "tcoil ref/enable"}, {"path": "ref/enable", "type": "bool", "readonly": false, "cmd": "tcoil ref/enable"},
{"path": "ref/r", "type": "float"}, {"path": "ref/r", "type": "float"},
{"path": "ref/curve", "type": "text", "readonly": false, "cmd": "tcoil ref/curve", "kids": 3}, {"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/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"}, {"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"}, {"path": "ref/curve/cpoints", "type": "floatvarar", "readonly": false, "cmd": "tcoil ref/curve/cpoints", "visibility": 3},
{"path": "ext", "type": "float", "visibility": 3}, {"path": "ext", "type": "float", "visibility": 3},
{"path": "com", "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": "send", "type": "text", "readonly": false, "cmd": "table send", "visibility": 3},
{"path": "status", "type": "text", "visibility": 3}, {"path": "status", "type": "text", "visibility": 3},
{"path": "fix_tt_set_prop", "type": "bool", "readonly": false, "cmd": "table fix_tt_set_prop"}, {"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": "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": "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": "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": "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 ..."}]},
{"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},
"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": "send", "type": "text", "readonly": false, "cmd": "lnv send", "visibility": 3},
{"path": "status", "type": "text", "visibility": 3}, {"path": "status", "type": "text", "visibility": 3},
{"path": "set", "type": "float", "readonly": false, "cmd": "lnv set"}, {"path": "set", "type": "float", "readonly": false, "cmd": "lnv set"},
@ -427,7 +463,10 @@
{"path": "autoflow/difmax", "type": "float"}, {"path": "autoflow/difmax", "type": "float"},
{"path": "autoflow/setmin", "type": "float"}, {"path": "autoflow/setmin", "type": "float"},
{"path": "autoflow/setmax", "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": "send", "type": "text", "readonly": false, "cmd": "lpr send", "visibility": 3},
{"path": "status", "type": "text", "visibility": 3}, {"path": "status", "type": "text", "visibility": 3},
{"path": "is_running", "type": "int", "readonly": false, "cmd": "lpr is_running", "visibility": 3}, {"path": "is_running", "type": "int", "readonly": false, "cmd": "lpr is_running", "visibility": 3},
@ -455,12 +494,13 @@
{"path": "running", "type": "int"}, {"path": "running", "type": "int"},
{"path": "tolerance", "type": "float", "readonly": false, "cmd": "lpr tolerance"}, {"path": "tolerance", "type": "float", "readonly": false, "cmd": "lpr tolerance"},
{"path": "maxwait", "type": "float", "readonly": false, "cmd": "lpr maxwait"}, {"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": "send", "type": "text", "readonly": false, "cmd": "lambdawatch send", "visibility": 3},
{"path": "status", "type": "text", "visibility": 3}, {"path": "status", "type": "text", "visibility": 3},
{"path": "safefield", "type": "float", "readonly": false, "cmd": "lambdawatch safefield"}, {"path": "safefield", "type": "float", "readonly": false, "cmd": "lambdawatch safefield"},
{"path": "maxfield", "type": "float", "readonly": false, "cmd": "lambdawatch maxfield"}, {"path": "maxfield", "type": "float", "readonly": false, "cmd": "lambdawatch maxfield"},
{"path": "safetemp", "type": "float", "readonly": false, "cmd": "lambdawatch safetemp"}, {"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": "coiltemp", "type": "text", "readonly": false, "cmd": "lambdawatch coiltemp"}]}}
{"path": "send", "type": "text", "readonly": false, "cmd": "prep0 send", "visibility": 3},
{"path": "status", "type": "text", "visibility": 3}]}}

View File

@ -371,37 +371,37 @@
{"path": "td/enable", "type": "bool", "readonly": false, "cmd": "tcoil td/enable"}, {"path": "td/enable", "type": "bool", "readonly": false, "cmd": "tcoil td/enable"},
{"path": "td/r", "type": "float"}, {"path": "td/r", "type": "float"},
{"path": "td/curve", "type": "text", "readonly": false, "cmd": "tcoil td/curve", "kids": 3}, {"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/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"}, {"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"}, {"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", "type": "float", "visibility": 3, "kids": 3},
{"path": "tb/enable", "type": "bool", "readonly": false, "cmd": "tcoil tb/enable"}, {"path": "tb/enable", "type": "bool", "readonly": false, "cmd": "tcoil tb/enable"},
{"path": "tb/r", "type": "float"}, {"path": "tb/r", "type": "float"},
{"path": "tb/curve", "type": "text", "readonly": false, "cmd": "tcoil tb/curve", "kids": 3}, {"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/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"}, {"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"}, {"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", "type": "float", "visibility": 3, "kids": 3},
{"path": "tc/enable", "type": "bool", "readonly": false, "cmd": "tcoil tc/enable"}, {"path": "tc/enable", "type": "bool", "readonly": false, "cmd": "tcoil tc/enable"},
{"path": "tc/r", "type": "float"}, {"path": "tc/r", "type": "float"},
{"path": "tc/curve", "type": "text", "readonly": false, "cmd": "tcoil tc/curve", "kids": 3}, {"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/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"}, {"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"}, {"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", "type": "float", "visibility": 3, "kids": 3},
{"path": "ta/enable", "type": "bool", "readonly": false, "cmd": "tcoil ta/enable"}, {"path": "ta/enable", "type": "bool", "readonly": false, "cmd": "tcoil ta/enable"},
{"path": "ta/r", "type": "float"}, {"path": "ta/r", "type": "float"},
{"path": "ta/curve", "type": "text", "readonly": false, "cmd": "tcoil ta/curve", "kids": 3}, {"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/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"}, {"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"}, {"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", "type": "float", "visibility": 3, "kids": 3},
{"path": "ref/enable", "type": "bool", "readonly": false, "cmd": "tcoil ref/enable"}, {"path": "ref/enable", "type": "bool", "readonly": false, "cmd": "tcoil ref/enable"},
{"path": "ref/r", "type": "float"}, {"path": "ref/r", "type": "float"},
{"path": "ref/curve", "type": "text", "readonly": false, "cmd": "tcoil ref/curve", "kids": 3}, {"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/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"}, {"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"}, {"path": "ref/curve/cpoints", "type": "floatvarar", "readonly": false, "cmd": "tcoil ref/curve/cpoints", "visibility": 3},
{"path": "ext", "type": "float", "visibility": 3}, {"path": "ext", "type": "float", "visibility": 3},
{"path": "com", "type": "float", "visibility": 3}, {"path": "com", "type": "float", "visibility": 3},
{"path": "gnd", "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/enable", "type": "bool", "readonly": false, "cmd": "tcoil ta/enable"},
{"path": "ta/r", "type": "float"}, {"path": "ta/r", "type": "float"},
{"path": "ta/curve", "type": "text", "readonly": false, "cmd": "tcoil ta/curve", "kids": 3}, {"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/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"}, {"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"}, {"path": "ta/curve/cpoints", "type": "floatvarar", "readonly": false, "cmd": "tcoil ta/curve/cpoints", "visibility": 3},
{"path": "tb", "type": "float", "kids": 3}, {"path": "tb", "type": "float", "kids": 3},
{"path": "tb/enable", "type": "bool", "readonly": false, "cmd": "tcoil tb/enable"}, {"path": "tb/enable", "type": "bool", "readonly": false, "cmd": "tcoil tb/enable"},
{"path": "tb/r", "type": "float"}, {"path": "tb/r", "type": "float"},
{"path": "tb/curve", "type": "text", "readonly": false, "cmd": "tcoil tb/curve", "kids": 3}, {"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/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"}, {"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"}, {"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", "type": "float", "visibility": 3, "kids": 3},
{"path": "td/enable", "type": "bool", "readonly": false, "cmd": "tcoil td/enable"}, {"path": "td/enable", "type": "bool", "readonly": false, "cmd": "tcoil td/enable"},
{"path": "td/r", "type": "float"}, {"path": "td/r", "type": "float"},
{"path": "td/curve", "type": "text", "readonly": false, "cmd": "tcoil td/curve", "kids": 3}, {"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/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"}, {"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"}, {"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", "type": "float", "visibility": 3, "kids": 3},
{"path": "ref/enable", "type": "bool", "readonly": false, "cmd": "tcoil ref/enable"}, {"path": "ref/enable", "type": "bool", "readonly": false, "cmd": "tcoil ref/enable"},
{"path": "ref/r", "type": "float"}, {"path": "ref/r", "type": "float"},
{"path": "ref/curve", "type": "text", "readonly": false, "cmd": "tcoil ref/curve", "kids": 3}, {"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/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"}, {"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"}, {"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", "type": "float", "visibility": 3, "kids": 3},
{"path": "tc/enable", "type": "bool", "readonly": false, "cmd": "tcoil tc/enable"}, {"path": "tc/enable", "type": "bool", "readonly": false, "cmd": "tcoil tc/enable"},
{"path": "tc/r", "type": "float"}, {"path": "tc/r", "type": "float"},
{"path": "tc/curve", "type": "text", "readonly": false, "cmd": "tcoil tc/curve", "kids": 3}, {"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/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"}, {"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"}, {"path": "tc/curve/cpoints", "type": "floatvarar", "readonly": false, "cmd": "tcoil tc/curve/cpoints", "visibility": 3},
{"path": "ext", "type": "float", "visibility": 3}, {"path": "ext", "type": "float", "visibility": 3},
{"path": "com", "type": "float", "visibility": 3}, {"path": "com", "type": "float", "visibility": 3},
{"path": "gnd", "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/enable", "type": "bool", "readonly": false, "cmd": "tcoil ta/enable"},
{"path": "ta/r", "type": "float"}, {"path": "ta/r", "type": "float"},
{"path": "ta/curve", "type": "text", "readonly": false, "cmd": "tcoil ta/curve", "kids": 3}, {"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/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"}, {"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"}, {"path": "ta/curve/cpoints", "type": "floatvarar", "readonly": false, "cmd": "tcoil ta/curve/cpoints", "visibility": 3},
{"path": "tb", "type": "float", "kids": 3}, {"path": "tb", "type": "float", "kids": 3},
{"path": "tb/enable", "type": "bool", "readonly": false, "cmd": "tcoil tb/enable"}, {"path": "tb/enable", "type": "bool", "readonly": false, "cmd": "tcoil tb/enable"},
{"path": "tb/r", "type": "float"}, {"path": "tb/r", "type": "float"},
{"path": "tb/curve", "type": "text", "readonly": false, "cmd": "tcoil tb/curve", "kids": 3}, {"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/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"}, {"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"}, {"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", "type": "float", "visibility": 3, "kids": 3},
{"path": "td/enable", "type": "bool", "readonly": false, "cmd": "tcoil td/enable"}, {"path": "td/enable", "type": "bool", "readonly": false, "cmd": "tcoil td/enable"},
{"path": "td/r", "type": "float"}, {"path": "td/r", "type": "float"},
{"path": "td/curve", "type": "text", "readonly": false, "cmd": "tcoil td/curve", "kids": 3}, {"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/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"}, {"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"}, {"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", "type": "float", "visibility": 3, "kids": 3},
{"path": "ref/enable", "type": "bool", "readonly": false, "cmd": "tcoil ref/enable"}, {"path": "ref/enable", "type": "bool", "readonly": false, "cmd": "tcoil ref/enable"},
{"path": "ref/r", "type": "float"}, {"path": "ref/r", "type": "float"},
{"path": "ref/curve", "type": "text", "readonly": false, "cmd": "tcoil ref/curve", "kids": 3}, {"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/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"}, {"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"}, {"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", "type": "float", "visibility": 3, "kids": 3},
{"path": "tc/enable", "type": "bool", "readonly": false, "cmd": "tcoil tc/enable"}, {"path": "tc/enable", "type": "bool", "readonly": false, "cmd": "tcoil tc/enable"},
{"path": "tc/r", "type": "float"}, {"path": "tc/r", "type": "float"},
{"path": "tc/curve", "type": "text", "readonly": false, "cmd": "tcoil tc/curve", "kids": 3}, {"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/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"}, {"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"}, {"path": "tc/curve/cpoints", "type": "floatvarar", "readonly": false, "cmd": "tcoil tc/curve/cpoints", "visibility": 3},
{"path": "ext", "type": "float", "visibility": 3}, {"path": "ext", "type": "float", "visibility": 3},
{"path": "com", "type": "float", "visibility": 3}, {"path": "com", "type": "float", "visibility": 3},
{"path": "gnd", "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/enable", "type": "bool", "readonly": false, "cmd": "tcoil ta/enable"},
{"path": "ta/r", "type": "float"}, {"path": "ta/r", "type": "float"},
{"path": "ta/curve", "type": "text", "readonly": false, "cmd": "tcoil ta/curve", "kids": 3}, {"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/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"}, {"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"}, {"path": "ta/curve/cpoints", "type": "floatvarar", "readonly": false, "cmd": "tcoil ta/curve/cpoints", "visibility": 3},
{"path": "tb", "type": "float", "kids": 3}, {"path": "tb", "type": "float", "kids": 3},
{"path": "tb/enable", "type": "bool", "readonly": false, "cmd": "tcoil tb/enable"}, {"path": "tb/enable", "type": "bool", "readonly": false, "cmd": "tcoil tb/enable"},
{"path": "tb/r", "type": "float"}, {"path": "tb/r", "type": "float"},
{"path": "tb/curve", "type": "text", "readonly": false, "cmd": "tcoil tb/curve", "kids": 3}, {"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/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"}, {"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"}, {"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", "type": "float", "visibility": 3, "kids": 3},
{"path": "td/enable", "type": "bool", "readonly": false, "cmd": "tcoil td/enable"}, {"path": "td/enable", "type": "bool", "readonly": false, "cmd": "tcoil td/enable"},
{"path": "td/r", "type": "float"}, {"path": "td/r", "type": "float"},
{"path": "td/curve", "type": "text", "readonly": false, "cmd": "tcoil td/curve", "kids": 3}, {"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/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"}, {"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"}, {"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", "type": "float", "visibility": 3, "kids": 3},
{"path": "ref/enable", "type": "bool", "readonly": false, "cmd": "tcoil ref/enable"}, {"path": "ref/enable", "type": "bool", "readonly": false, "cmd": "tcoil ref/enable"},
{"path": "ref/r", "type": "float"}, {"path": "ref/r", "type": "float"},
{"path": "ref/curve", "type": "text", "readonly": false, "cmd": "tcoil ref/curve", "kids": 3}, {"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/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"}, {"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"}, {"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", "type": "float", "visibility": 3, "kids": 3},
{"path": "tc/enable", "type": "bool", "readonly": false, "cmd": "tcoil tc/enable"}, {"path": "tc/enable", "type": "bool", "readonly": false, "cmd": "tcoil tc/enable"},
{"path": "tc/r", "type": "float"}, {"path": "tc/r", "type": "float"},
{"path": "tc/curve", "type": "text", "readonly": false, "cmd": "tcoil tc/curve", "kids": 3}, {"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/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"}, {"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"}, {"path": "tc/curve/cpoints", "type": "floatvarar", "readonly": false, "cmd": "tcoil tc/curve/cpoints", "visibility": 3},
{"path": "ext", "type": "float", "visibility": 3}, {"path": "ext", "type": "float", "visibility": 3},
{"path": "com", "type": "float", "visibility": 3}, {"path": "com", "type": "float", "visibility": 3},
{"path": "gnd", "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/enable", "type": "bool", "readonly": false, "cmd": "tcoil ta/enable"},
{"path": "ta/r", "type": "float"}, {"path": "ta/r", "type": "float"},
{"path": "ta/curve", "type": "text", "readonly": false, "cmd": "tcoil ta/curve", "kids": 3}, {"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/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"}, {"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"}, {"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", "type": "float", "visibility": 3, "kids": 3},
{"path": "tb/enable", "type": "bool", "readonly": false, "cmd": "tcoil tb/enable"}, {"path": "tb/enable", "type": "bool", "readonly": false, "cmd": "tcoil tb/enable"},
{"path": "tb/r", "type": "float"}, {"path": "tb/r", "type": "float"},
{"path": "tb/curve", "type": "text", "readonly": false, "cmd": "tcoil tb/curve", "kids": 3}, {"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/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"}, {"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"}, {"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", "type": "float", "visibility": 3, "kids": 3},
{"path": "td/enable", "type": "bool", "readonly": false, "cmd": "tcoil td/enable"}, {"path": "td/enable", "type": "bool", "readonly": false, "cmd": "tcoil td/enable"},
{"path": "td/r", "type": "float"}, {"path": "td/r", "type": "float"},
{"path": "td/curve", "type": "text", "readonly": false, "cmd": "tcoil td/curve", "kids": 3}, {"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/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"}, {"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"}, {"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", "type": "float", "visibility": 3, "kids": 3},
{"path": "ref/enable", "type": "bool", "readonly": false, "cmd": "tcoil ref/enable"}, {"path": "ref/enable", "type": "bool", "readonly": false, "cmd": "tcoil ref/enable"},
{"path": "ref/r", "type": "float"}, {"path": "ref/r", "type": "float"},
{"path": "ref/curve", "type": "text", "readonly": false, "cmd": "tcoil ref/curve", "kids": 3}, {"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/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"}, {"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"}, {"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", "type": "float", "visibility": 3, "kids": 3},
{"path": "tc/enable", "type": "bool", "readonly": false, "cmd": "tcoil tc/enable"}, {"path": "tc/enable", "type": "bool", "readonly": false, "cmd": "tcoil tc/enable"},
{"path": "tc/r", "type": "float"}, {"path": "tc/r", "type": "float"},
{"path": "tc/curve", "type": "text", "readonly": false, "cmd": "tcoil tc/curve", "kids": 3}, {"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/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"}, {"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"}, {"path": "tc/curve/cpoints", "type": "floatvarar", "readonly": false, "cmd": "tcoil tc/curve/cpoints", "visibility": 3},
{"path": "ext", "type": "float", "visibility": 3}, {"path": "ext", "type": "float", "visibility": 3},
{"path": "com", "type": "float", "visibility": 3}, {"path": "com", "type": "float", "visibility": 3},
{"path": "gnd", "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/enable", "type": "bool", "readonly": false, "cmd": "tcoil ta/enable"},
{"path": "ta/r", "type": "float"}, {"path": "ta/r", "type": "float"},
{"path": "ta/curve", "type": "text", "readonly": false, "cmd": "tcoil ta/curve", "kids": 3}, {"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/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"}, {"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"}, {"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", "type": "float", "visibility": 3, "kids": 3},
{"path": "tb/enable", "type": "bool", "readonly": false, "cmd": "tcoil tb/enable"}, {"path": "tb/enable", "type": "bool", "readonly": false, "cmd": "tcoil tb/enable"},
{"path": "tb/r", "type": "float"}, {"path": "tb/r", "type": "float"},
{"path": "tb/curve", "type": "text", "readonly": false, "cmd": "tcoil tb/curve", "kids": 3}, {"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/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"}, {"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"}, {"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", "type": "float", "visibility": 3, "kids": 3},
{"path": "td/enable", "type": "bool", "readonly": false, "cmd": "tcoil td/enable"}, {"path": "td/enable", "type": "bool", "readonly": false, "cmd": "tcoil td/enable"},
{"path": "td/r", "type": "float"}, {"path": "td/r", "type": "float"},
{"path": "td/curve", "type": "text", "readonly": false, "cmd": "tcoil td/curve", "kids": 3}, {"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/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"}, {"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"}, {"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", "type": "float", "visibility": 3, "kids": 3},
{"path": "ref/enable", "type": "bool", "readonly": false, "cmd": "tcoil ref/enable"}, {"path": "ref/enable", "type": "bool", "readonly": false, "cmd": "tcoil ref/enable"},
{"path": "ref/r", "type": "float"}, {"path": "ref/r", "type": "float"},
{"path": "ref/curve", "type": "text", "readonly": false, "cmd": "tcoil ref/curve", "kids": 3}, {"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/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"}, {"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"}, {"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", "type": "float", "visibility": 3, "kids": 3},
{"path": "tc/enable", "type": "bool", "readonly": false, "cmd": "tcoil tc/enable"}, {"path": "tc/enable", "type": "bool", "readonly": false, "cmd": "tcoil tc/enable"},
{"path": "tc/r", "type": "float"}, {"path": "tc/r", "type": "float"},
{"path": "tc/curve", "type": "text", "readonly": false, "cmd": "tcoil tc/curve", "kids": 3}, {"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/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"}, {"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"}, {"path": "tc/curve/cpoints", "type": "floatvarar", "readonly": false, "cmd": "tcoil tc/curve/cpoints", "visibility": 3},
{"path": "ext", "type": "float", "visibility": 3}, {"path": "ext", "type": "float", "visibility": 3},
{"path": "com", "type": "float", "visibility": 3}, {"path": "com", "type": "float", "visibility": 3},
{"path": "gnd", "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', Mod('ts',
'frappy_psi.parmod.Converging', 'frappy_psi.parmod.Converging',
'virtual stick T', 'stick T (controlled)',
meaning=['temperature', 30], meaning=['temperature', 25],
unit='K', unit='K',
read='tsam.value', read='tsam.value',
write='tsam.setsamp', write='tsam.setsamp',

View File

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

View File

@ -13,8 +13,6 @@ Mod('ts',
'frappy_psi.sea.SeaReadable', '', 'frappy_psi.sea.SeaReadable', '',
meaning=['temperature', 30], meaning=['temperature', 30],
io='sea_stick', io='sea_stick',
sea_object='tt', sea_path='tt/ts',
json_file='ma7.config.json', 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 frappy-core (0.20.1) jammy; urgency=medium
* gui: do not add a console logger when there is no sys.stdout * 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-cli
usr/bin/frappy-server usr/bin/frappy-server
usr/bin/frappy-play usr/bin/frappy-play
usr/bin/frappy-scan
usr/lib/python3.*/dist-packages/frappy/*.py usr/lib/python3.*/dist-packages/frappy/*.py
usr/lib/python3.*/dist-packages/frappy/__pycache__ usr/lib/python3.*/dist-packages/frappy/__pycache__
usr/lib/python3.*/dist-packages/frappy/lib 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 usr/lib/python3.*/dist-packages/frappy_demo
lib/systemd lib/systemd
var/log/frappy var/log/frappy
etc/frappy/generalConfig.cfg

1
debian/rules vendored
View File

@ -11,6 +11,7 @@ override_dh_install:
rmdir debian/tmp rmdir debian/tmp
mv debian/python3-frappy 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_install -i -O--buildsystem=pybuild
dh_missing --fail-missing 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""" """general SECoP client"""
# pylint: disable=too-many-positional-arguments
import json import json
import queue import queue
import re import re
@ -481,7 +479,10 @@ class SecopClient(ProxyClient):
continue continue
except Exception as e: except Exception as e:
e.args = (f'error handling SECoP message {reply!r}: {e}',) e.args = (f'error handling SECoP message {reply!r}: {e}',)
try:
self.callback(None, 'handleError', e) self.callback(None, 'handleError', e)
except Exception:
pass
continue continue
try: try:
key = action, ident key = action, ident

View File

@ -56,10 +56,12 @@ class Param(dict):
kwds['value'] = value kwds['value'] = value
super().__init__(**kwds) super().__init__(**kwds)
class Group(tuple): class Group(tuple):
def __new__(cls, *args): def __new__(cls, *args):
return super().__new__(cls, args) return super().__new__(cls, args)
class Mod(dict): class Mod(dict):
def __init__(self, name, cls, description, **kwds): def __init__(self, name, cls, description, **kwds):
super().__init__( super().__init__(
@ -70,7 +72,8 @@ class Mod(dict):
# matches name from spec # matches name from spec
if not re.match(r'^[a-zA-Z]\w{0,62}$', name, re.ASCII): 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 # Make parameters out of all keywords
groups = {} groups = {}
for key, val in kwds.items(): for key, val in kwds.items():
@ -85,6 +88,7 @@ class Mod(dict):
for member in members: for member in members:
self[member]['group'] = group self[member]['group'] = group
class Collector: class Collector:
def __init__(self, cls): def __init__(self, cls):
self.list = [] self.list = []
@ -120,12 +124,14 @@ class Config(dict):
def merge_modules(self, other): def merge_modules(self, other):
""" merges only the modules from 'other' into 'self'""" """ merges only the modules from 'other' into 'self'"""
self.ambiguous |= self.module_names & other.module_names self.ambiguous |= self.module_names & other.module_names
equipment_id = other['node']['equipment_id']
for name, mod in other.items(): for name, mod in other.items():
if name == 'node': if name == 'node':
continue continue
if name not in self.module_names: if name not in self.module_names:
self.module_names.add(name) self.module_names.add(name)
self[name] = mod self[name] = mod
mod['original_id'] = equipment_id
def process_file(filename, log): 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. Only the node-section of the first config file will be returned.
The others will be discarded. The others will be discarded.
Arguments Arguments
- cfgfiles : str - cfgfiles : list
Comma separated list of config-files List of config file paths
- log : frappy.logging.Mainlogger - log : frappy.logging.Mainlogger
Logger aquired from frappy.logging Logger aquired from frappy.logging
Returns Returns
@ -181,8 +187,8 @@ def load_config(cfgfiles, log):
merged configuration merged configuration
""" """
config = None config = None
for cfgfile in cfgfiles.split(','): for cfgfile in cfgfiles:
filename = to_config_path(cfgfile, log) filename = to_config_path(str(cfgfile), log)
log.debug('Parsing config file %s...', filename) log.debug('Parsing config file %s...', filename)
cfg = process_file(filename, log) cfg = process_file(filename, log)
if config: 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: with open(file_name, 'w', encoding='utf-8') as configfile:
configfile.write('\n'.join(lines)) configfile.write('\n'.join(lines))
def read_config(file_path, log): def read_config(file_path, log):
config = load_config(file_path, log) config = load_config([file_path], log)
node = TreeWidgetItem(NODE) node = TreeWidgetItem(NODE)
ifs = TreeWidgetItem(name='Interfaces') ifs = TreeWidgetItem(name='Interfaces')
mods = TreeWidgetItem(name='Modules') mods = TreeWidgetItem(name='Modules')

View File

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

View File

@ -57,11 +57,12 @@ class GeneralConfig:
:param configfile: if present, keys and values from the [FRAPPY] section are read :param configfile: if present, keys and values from the [FRAPPY] section are read
default values for 'piddir', 'logdir' and 'confdir' are guessed from the The following locations are searched for the generalConfig.cfg file.
location of this source file and from sys.executable. - command line argument
- environment variable FRAPPY_CONFIG_FILE
if configfile is not given, the general config file is determined by - git location (../cfg)
the env. variable FRAPPY_CONFIG_FILE or <confdir>/generalConfig.cfg is used - local location (cwd)
- global location (/etc/frappy)
if a configfile is given, the values from the FRAPPY section are if a configfile is given, the values from the FRAPPY section are
overriding above defaults overriding above defaults
@ -69,37 +70,12 @@ class GeneralConfig:
finally, the env. variables FRAPPY_PIDDIR, FRAPPY_LOGDIR and FRAPPY_CONFDIR finally, the env. variables FRAPPY_PIDDIR, FRAPPY_LOGDIR and FRAPPY_CONFDIR
are overriding these values when given are overriding these values when given
""" """
configfile = self._get_file_location(configfile)
cfg = {} cfg = {}
mandatory = 'piddir', 'logdir', 'confdir' mandatory = 'piddir', 'logdir', 'confdir'
repodir = Path(__file__).parents[2].expanduser().resolve() 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: if configfile:
parser = ConfigParser() parser = ConfigParser()
parser.optionxform = str parser.optionxform = str
@ -113,28 +89,63 @@ class GeneralConfig:
cfg[key] = ':'.join(path.expanduser(v) for v in value.split(':')) cfg[key] = ':'.join(path.expanduser(v) for v in value.split(':'))
if cfg.get('confdir') is None: if cfg.get('confdir') is None:
cfg['confdir'] = configfile.parent cfg['confdir'] = configfile.parent
# environment variables will overwrite the config file
missing_keys = []
for key in mandatory: for key in mandatory:
env = environ.get(f'FRAPPY_{key.upper()}') env = environ.get(f'FRAPPY_{key.upper()}') or cfg.get(key)
if env is not None: if env is None:
if ':' in env: if self.defaults.get(key) is None:
cfg[key] = [Path(v) for v in env.split(':')] missing_keys.append(key)
else: else:
cfg[key] = Path(env) if not isinstance(env, Path):
missing_keys = [ if key == 'confdir':
key for key in mandatory env = [Path(v) for v in env.split(':')]
if cfg.get(key) is None and self.defaults.get(key) is None else:
] env = Path(env)
cfg[key] = env
if missing_keys: if missing_keys:
if configfile: if configfile:
raise KeyError(f"missing value for {' and '.join(missing_keys)} in {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): if 'confdir' in cfg and isinstance(cfg['confdir'], Path):
cfg['confdir'] = [cfg['confdir']] cfg['confdir'] = [cfg['confdir']]
# this is not customizable # this is not customizable
cfg['basedir'] = repodir cfg['basedir'] = repodir
self._config = cfg 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): def __getitem__(self, key):
"""access for keys known to exist """access for keys known to exist

View File

@ -55,7 +55,7 @@ class MultiEvent(threading.Event):
def __init__(self, default_timeout=None): def __init__(self, default_timeout=None):
self.events = set() self.events = set()
self._lock = threading.Lock() self._lock = threading.RLock()
self.default_timeout = default_timeout or None # treat 0 as None self.default_timeout = default_timeout or None # treat 0 as None
self.name = None # default event name self.name = None # default event name
self._actions = [] # actions to be executed on trigger 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, \ from frappy.errors import BadValueError, CommunicationFailedError, ConfigError, \
ProgrammingError, SECoPError, secop_error, RangeError ProgrammingError, SECoPError, secop_error, RangeError
from frappy.lib import formatException, mkthread, UniqueObject 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.properties import HasProperties, Property
from frappy.logging import RemoteLogHandler from frappy.logging import RemoteLogHandler
@ -41,6 +41,7 @@ from frappy.logging import RemoteLogHandler
# from .interfaces import SECoP_BASE_CLASSES # from .interfaces import SECoP_BASE_CLASSES
# WORKAROUND: # WORKAROUND:
SECoP_BASE_CLASSES = ['Readable', 'Writable', 'Drivable', 'Communicator'] SECoP_BASE_CLASSES = ['Readable', 'Writable', 'Drivable', 'Communicator']
PREDEF_ORDER = list(PREDEFINED_ACCESSIBLES)
Done = UniqueObject('Done') Done = UniqueObject('Done')
"""a special return value for a read_<param>/write_<param> method """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(): for key, value in base.__dict__.items():
if isinstance(value, Accessible): if isinstance(value, Accessible):
value.updateProperties(merged_properties.setdefault(key, {})) 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) new_names.append(key)
accessibles[key] = value accessibles[key] = value
override_values.pop(key, None) override_values.pop(key, None)
@ -97,16 +98,14 @@ class HasAccessibles(HasProperties):
aobj.merge(merged_properties[aname]) aobj.merge(merged_properties[aname])
accessibles[aname] = aobj accessibles[aname] = aobj
# rebuild order: (1) inherited items, (2) items from paramOrder, (3) new accessibles # rebuild order:
# move (2) to the end # (1) predefined accessibles, in a predefined order, (2) inherited custom items, (3) new custom items
paramOrder = cls.__dict__.get('paramOrder', ()) # move (1) to the beginning
for aname in paramOrder: for key in reversed(PREDEF_ORDER):
if aname in accessibles: if key in accessibles:
accessibles.move_to_end(aname) accessibles.move_to_end(key, last=False)
# ignore unknown names
# move (3) to the end # move (3) to the end
for aname in new_names: for aname in new_names:
if aname not in paramOrder:
accessibles.move_to_end(aname) accessibles.move_to_end(aname)
cls.accessibles = accessibles 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 if new_value is Done: # TODO: to be removed when all code using Done is updated
return getattr(self, pname) return getattr(self, pname)
new_value = value if new_value is None else validate(new_value) new_value = value if new_value is None else validate(new_value)
except Exception as e: except SECoPError as e:
if isinstance(e, SECoPError):
e.raising_methods.append(f'{self.name}.write_{pname}') e.raising_methods.append(f'{self.name}.write_{pname}')
self.announceUpdate(pname, err=e)
raise raise
self.announceUpdate(pname, new_value, validate=False) self.announceUpdate(pname, new_value, validate=False)
return new_value return new_value
@ -322,6 +319,8 @@ class Module(HasAccessibles):
slowinterval = Property('poll interval for other parameters', FloatRange(0.1, 120), default=15) 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', omit_unchanged_within = Property('default for minimum time between updates of unchanged values',
NoneOr(FloatRange(0)), export=False, default=None) 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 enablePoll = True
pollInfo = None pollInfo = None
@ -517,13 +516,13 @@ class Module(HasAccessibles):
with self.updateLock: with self.updateLock:
pobj = self.parameters[pname] pobj = self.parameters[pname]
timestamp = timestamp or time.time() timestamp = timestamp or time.time()
changed = False
if not err: if not err:
try: try:
if validate: if validate:
value = pobj.datatype(value) value = pobj.datatype(value)
except Exception as e: except Exception as e:
err = e err = e
changed = False
else: else:
changed = pobj.value != value or pobj.readerror changed = pobj.value != value or pobj.readerror
# store the value even in case of error # store the value even in case of error

View File

@ -259,8 +259,16 @@ class Parameter(Accessible):
merged_properties.update(self.ownProperties) merged_properties.update(self.ownProperties)
def create_from_value(self, properties, value): def create_from_value(self, properties, value):
"""return a clone with given value and inherited properties""" """return a clone with given value and inherited properties
return self.clone(properties, value=self.datatype(value))
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): def merge(self, merged_properties):
"""merge with inherited properties """merge with inherited properties
@ -462,7 +470,8 @@ class Command(Accessible):
def create_from_value(self, properties, value): def create_from_value(self, properties, value):
"""return a clone with given value and inherited properties """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): if not callable(value):
raise ProgrammingError(f'{self.name} = {value!r} is overriding a Command') raise ProgrammingError(f'{self.name} = {value!r} is overriding a Command')
return self.clone(properties)(value) return self.clone(properties)(value)
@ -564,15 +573,18 @@ class Limit(Parameter):
# list of predefined accessibles with their type # list of predefined accessibles with their type
# the order of this list affects the parameter order
PREDEFINED_ACCESSIBLES = { PREDEFINED_ACCESSIBLES = {
'value': Parameter, 'value': Parameter,
'status': Parameter, 'status': Parameter,
'target': Parameter, 'target': Parameter,
'pollinterval': Parameter, 'pollinterval': Parameter,
'ramp': Parameter, 'ramp': Parameter,
'user_ramp': Parameter, 'use_ramp': Parameter,
'setpoint': Parameter, 'setpoint': Parameter,
'time_to_target': Parameter, 'time_to_target': Parameter,
'controlled_by': Parameter,
'control_active': Parameter,
'unit': Parameter, # reserved name 'unit': Parameter, # reserved name
'loglevel': Parameter, # reserved name 'loglevel': Parameter, # reserved name
'mode': Parameter, # reserved name 'mode': Parameter, # reserved name

View File

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

View File

@ -143,6 +143,7 @@ class HasProperties(HasDescriptors):
try: try:
# try to apply bare value to Property # try to apply bare value to Property
po.value = po.datatype.validate(value) po.value = po.datatype.validate(value)
setattr(cls, pn, po) # replace bare value by updated Property
except BadValueError: except BadValueError:
if callable(value): if callable(value):
raise ProgrammingError(f'method {cls.__name__}.{pn} collides with property of {base.__name__}') from None 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): class Router(frappy.protocol.dispatcher.Dispatcher):
singlenode = None
def __init__(self, name, logger, options, srv): def __init__(self, name, logger, options, srv):
"""initialize router """initialize router
Use the option node = <uri> for a single node or Use the option node = <uri> for a single node or
nodes = ["<uri1>", "<uri2>" ...] for multiple nodes. 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. else the description property is a merge from all client node properties.
""" """
uri = options.pop('node', None) uri = options.pop('node', None)
uris = options.pop('nodes', None) uris = options.pop('nodes', None)
if uri and uris: try:
raise frappy.errors.ConfigError('can not specify node _and_ nodes') if uris is not None:
super().__init__(name, logger, options, srv) if isinstance(uris, str) or not all(isinstance(v, str) for v in uris) or uri:
if uri: raise TypeError()
self.nodes = [SecopClient(uri, logger.getChild('routed'), self)] elif isinstance(uri, str):
self.singlenode = self.nodes[0] uris = [uri]
else: 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)] self.nodes = [SecopClient(uri, logger.getChild(f'routed{i}'), self) for i, uri in enumerate(uris)]
# register callbacks # register callbacks
for node in self.nodes: 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) logger.warning('can not connect to node %r', node.nodename)
def handle_describe(self, conn, specifier, data): def handle_describe(self, conn, specifier, data):
if self.singlenode: if len(self.nodes) == 1 and not self.secnode.modules:
return DESCRIPTIONREPLY, specifier, self.singlenode.descriptive_data return DESCRIPTIONREPLY, specifier, self.nodes[0].descriptive_data
reply = super().handle_describe(conn, specifier, data) reply = super().handle_describe(conn, specifier, data)
result = reply[2] result = reply[2]
allmodules = result.get('modules', {}) allmodules = result.get('modules', {})
@ -144,6 +148,7 @@ class Router(frappy.protocol.dispatcher.Dispatcher):
self.log.info('module %r is already present', modname) self.log.info('module %r is already present', modname)
else: else:
allmodules[modname] = moddesc allmodules[modname] = moddesc
moddesc.setdefault('original_id', equipment_id)
result['modules'] = allmodules result['modules'] = allmodules
result['description'] = '\n\n'.join(node_description) result['description'] = '\n\n'.join(node_description)
return DESCRIPTIONREPLY, specifier, result return DESCRIPTIONREPLY, specifier, result

View File

@ -54,8 +54,17 @@ class SecNode:
self.name = name self.name = name
def add_secnode_property(self, prop, value): 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 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): def get_module(self, modulename):
""" Returns a fully initialized module. Or None, if something went """ Returns a fully initialized module. Or None, if something went
wrong during instatiating/initializing the module.""" wrong during instatiating/initializing the module."""

View File

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

View File

@ -69,14 +69,16 @@ class TemperatureLoop(TemperatureSensor, Drivable):
# lakeshore loop number to be used for this module # lakeshore loop number to be used for this module
loop = Property('lakeshore loop', IntRange(1, 2), default=1) loop = Property('lakeshore loop', IntRange(1, 2), default=1)
target = Parameter(datatype=FloatRange(unit='K', min=0, max=1500)) 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) tolerance = Parameter('convergence criterion', FloatRange(0), default=0.1, readonly=False)
_driving = False _driving = False
def write_heater_range(self, value):
self.communicate(f'RANGE {self.loop},{value};RANGE?{self.loop}')
def write_target(self, target): def write_target(self, target):
# reactivate heater in case it was switched off # 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.write_heater_range(self.heater_range)
self.communicate(f'RANGE {self.loop},{self.heater_range};RANGE?{self.loop}')
self.communicate(f'SETP {self.loop},{target};*OPC?') self.communicate(f'SETP {self.loop},{target};*OPC?')
self._driving = True self._driving = True
# Setting the status attribute triggers an update message for the SECoP status # Setting the status attribute triggers an update message for the SECoP status
@ -85,23 +87,21 @@ class TemperatureLoop(TemperatureSensor, Drivable):
return target return target
def read_status(self): def read_status(self):
code = int(self.communicate(f'RDGST?{self.channel}')) status = super().read_status()
if code >= 128: if status[0] == ERROR:
text = 'units overrange' return status
elif code >= 64: if abs(self.target - self.value) > self.tolerance:
text = 'units zero'
elif code >= 32:
text = 'temperature overrange'
elif code >= 16:
text = 'temperature underrange'
elif code % 2:
# ignore 'old reading', as this may happen in normal operation
text = 'invalid reading'
elif abs(self.target - self.value) > self.tolerance:
if self._driving: if self._driving:
return BUSY, 'approaching setpoint' return BUSY, 'approaching setpoint'
return WARN, 'temperature out of tolerance' return WARN, 'temperature out of tolerance'
else: # within tolerance: simple convergence criterion # within tolerance: simple convergence criterion
self._driving = False self._driving = False
return IDLE, '' 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.modules import Communicator, Drivable, Parameter, Property, Readable, Module, Attached
from frappy.params import Command from frappy.params import Command
from frappy.dynamic import Pinata from frappy.dynamic import Pinata
from frappy.errors import RangeError from frappy.errors import RangeError, HardwareError
class Pin(Pinata): class Pin(Pinata):
def scanModules(self): def scanModules(self):
@ -72,8 +72,12 @@ class Heater(Drivable):
maxheaterpower = Parameter('maximum allowed heater power', maxheaterpower = Parameter('maximum allowed heater power',
datatype=FloatRange(0, 100), unit='W', datatype=FloatRange(0, 100), unit='W',
) )
error_message = Parameter('simulated error message', StringType(),
default='', readonly=False)
def read_value(self): def read_value(self):
if self.error_message:
raise HardwareError(self.error_message)
return round(100 * random.random(), 1) return round(100 * random.random(), 1)
def write_target(self, target): def write_target(self, target):

View File

@ -1,25 +1,37 @@
""" # *****************************************************************************
Created on Tue Nov 26 15:42:43 2019 # 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
@author: tartarotti_d-adm # 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 numpy as np
import ctypes as ct import ctypes as ct
import time from scipy.signal import butter, filtfilt
from numpy import sqrt, arctan2, sin, cos
#from pylab import *
from scipy import signal # For different trigger modes
#ADQAPI = ct.cdll.LoadLibrary("ADQAPI.dll")
ADQAPI = ct.cdll.LoadLibrary("libadq.so.0")
#For different trigger modes
SW_TRIG = 1 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_2 = 7
EXT_TRIG_3 = 8 EXT_TRIG_3 = 8
LVL_TRIG = 3 LVL_TRIG = 3
@ -27,29 +39,36 @@ INT_TRIG = 4
LVL_FALLING = 0 LVL_FALLING = 0
LVL_RISING = 1 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_TRANSFER_MODE_NORMAL = 0x00
ADQ_CHANNELS_MASK = 0x3 ADQ_CHANNELS_MASK = 0x3
#f_LO = 40 GHz = 1e9
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
class Adq(object): class Adq:
sample_rate = 2 * GHz
max_number_of_channels = 2 max_number_of_channels = 2
samp_freq = 2 ndecimate = 50 # decimation ratio (2GHz / 40 MHz)
#ndecimate = 50 # decimation ratio (2GHz / 40 MHz) number_of_records = 1
ndecimate = 50 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() ADQAPI.ADQAPI_GetRevision()
# Manually set return type from some ADQAPI functions # Manually set return type from some ADQAPI functions
@ -60,74 +79,67 @@ class Adq(object):
# Create ADQControlUnit # Create ADQControlUnit
self.adq_cu = ct.c_void_p(ADQAPI.CreateADQControlUnit()) self.adq_cu = ct.c_void_p(ADQAPI.CreateADQControlUnit())
ADQAPI.ADQControlUnit_EnableErrorTrace(self.adq_cu, 3, '.') ADQAPI.ADQControlUnit_EnableErrorTrace(self.adq_cu, 3, '.')
self.adq_num = 1
# Find ADQ devices # Find ADQ devices
ADQAPI.ADQControlUnit_FindDevices(self.adq_cu) ADQAPI.ADQControlUnit_FindDevices(self.adq_cu)
n_of_ADQ = ADQAPI.ADQControlUnit_NofADQ(self.adq_cu) n_of_adq = ADQAPI.ADQControlUnit_NofADQ(self.adq_cu)
if n_of_ADQ != 1: if n_of_adq != 1:
raise ValueError('number of ADQs must be 1, not %d' % n_of_ADQ) raise RuntimeError('number of ADQs must be 1, not %d' % n_of_adq)
rev = ADQAPI.ADQ_GetRevision(self.adq_cu, self.adq_num) rev = ADQAPI.ADQ_GetRevision(self.adq_cu, self.adq_num)
revision = ct.cast(rev,ct.POINTER(ct.c_int)) revision = ct.cast(rev, ct.POINTER(ct.c_int))
print('\nConnected to ADQ #1') print('\nConnected to ADQ #1')
# Print revision information # Print revision information
print('FPGA Revision: {}'.format(revision[0])) print('FPGA Revision: {}'.format(revision[0]))
if (revision[1]): if revision[1]:
print('Local copy') print('Local copy')
else : else:
print('SVN Managed') print('SVN Managed')
if (revision[2]): if revision[2]:
print('Mixed Revision') print('Mixed Revision')
else : else:
print('SVN Updated') print('SVN Updated')
print('') print('')
ADQ_CLOCK_INT_INTREF = 0 #internal clock source ADQAPI.ADQ_SetClockSource(self.adq_cu, self.adq_num, ADQ_CLOCK_EXT_REF)
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);
########################## ##########################
# Test pattern # Test pattern
#ADQAPI.ADQ_SetTestPatternMode(self.adq_cu, self.adq_num, 4) # ADQAPI.ADQ_SetTestPatternMode(self.adq_cu, self.adq_num, 4)
########################## ##########################
# Sample skip # Sample skip
#ADQAPI.ADQ_SetSampleSkip(self.adq_cu, self.adq_num, 1) # ADQAPI.ADQ_SetSampleSkip(self.adq_cu, self.adq_num, 1)
########################## ##########################
# Set trig mode # set trigger mode
self.trigger = EXT_TRIG_1 if not ADQAPI.ADQ_SetTriggerMode(self.adq_cu, self.adq_num, self.trigger):
#trigger = LVL_TRIG raise RuntimeError('ADQ_SetTriggerMode failed.')
success = ADQAPI.ADQ_SetTriggerMode(self.adq_cu, self.adq_num, self.trigger) if self.trigger == LVL_TRIG:
if (success == 0): if not ADQAPI.ADQ_SetLvlTrigLevel(self.adq_cu, self.adq_num, -100):
print('ADQ_SetTriggerMode failed.') raise RuntimeError('ADQ_SetLvlTrigLevel failed.')
if (self.trigger == LVL_TRIG): if not ADQAPI.ADQ_SetTrigLevelResetValue(self.adq_cu, self.adq_num, 1000):
success = ADQAPI.ADQ_SetLvlTrigLevel(self.adq_cu, self.adq_num, -100) raise RuntimeError('ADQ_SetTrigLevelResetValue failed.')
if (success == 0): if not ADQAPI.ADQ_SetLvlTrigChannel(self.adq_cu, self.adq_num, 1):
print('ADQ_SetLvlTrigLevel failed.') raise RuntimeError('ADQ_SetLvlTrigChannel failed.')
success = ADQAPI.ADQ_SetTrigLevelResetValue(self.adq_cu, self.adq_num, 1000) if not ADQAPI.ADQ_SetLvlTrigEdge(self.adq_cu, self.adq_num, LVL_RISING):
if (success == 0): raise RuntimeError('ADQ_SetLvlTrigEdge failed.')
print('ADQ_SetTrigLevelResetValue failed.') elif self.trigger == EXT_TRIG_1:
success = ADQAPI.ADQ_SetLvlTrigChannel(self.adq_cu, self.adq_num, 1) if not ADQAPI.ADQ_SetExternTrigEdge(self.adq_cu, self.adq_num, 2):
if (success == 0): raise RuntimeError('ADQ_SetLvlTrigEdge failed.')
print('ADQ_SetLvlTrigChannel failed.') # if not ADQAPI.ADQ_SetTriggerThresholdVoltage(self.adq_cu, self.adq_num, trigger, ct.c_double(0.2)):
success = ADQAPI.ADQ_SetLvlTrigEdge(self.adq_cu, self.adq_num, LVL_RISING) # raise RuntimeError('SetTriggerThresholdVoltage failed.')
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.')
print("CHANNEL:"+str(ct.c_int(ADQAPI.ADQ_GetLvlTrigChannel(self.adq_cu, self.adq_num)))) 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 # Setup target buffers for data
self.target_buffers=(ct.POINTER(ct.c_int16 * self.samples_per_record * self.number_of_records) self.target_buffers = (ct.POINTER(ct.c_int16 * self.samples_per_record * self.number_of_records)
* self.max_number_of_channels)() * self.max_number_of_channels)()
for bufp in self.target_buffers: for bufp in self.target_buffers:
bufp.contents = (ct.c_int16 * self.samples_per_record * self.number_of_records)() bufp.contents = (ct.c_int16 * self.samples_per_record * self.number_of_records)()
@ -135,14 +147,11 @@ class Adq(object):
def deletecu(self): def deletecu(self):
# Only disarm trigger after data is collected # Only disarm trigger after data is collected
ADQAPI.ADQ_DisarmTrigger(self.adq_cu, self.adq_num) 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 # Delete ADQControlunit
ADQAPI.DeleteADQControlUnit(self.adq_cu) ADQAPI.DeleteADQControlUnit(self.adq_cu)
def start(self): 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 # Start acquisition
ADQAPI.ADQ_MultiRecordSetup(self.adq_cu, self.adq_num, ADQAPI.ADQ_MultiRecordSetup(self.adq_cu, self.adq_num,
self.number_of_records, self.number_of_records,
@ -150,79 +159,50 @@ class Adq(object):
ADQAPI.ADQ_DisarmTrigger(self.adq_cu, self.adq_num) ADQAPI.ADQ_DisarmTrigger(self.adq_cu, self.adq_num)
ADQAPI.ADQ_ArmTrigger(self.adq_cu, self.adq_num) ADQAPI.ADQ_ArmTrigger(self.adq_cu, self.adq_num)
self.status = self.BUSY
def getdata(self): def get_status(self):
"""wait for aquisition to be finished and get data""" """check if ADQ card is busy"""
#start = time.time() if self.status == self.BUSY:
while(ADQAPI.ADQ_GetAcquiredAll(self.adq_cu,self.adq_num) == 0): if ADQAPI.ADQ_GetAcquiredAll(self.adq_cu, self.adq_num):
time.sleep(0.001) self.status = self.READY
#if (self.trigger == SW_TRIG): else:
# ADQAPI.ADQ_SWTrig(self.adq_cu, self.adq_num) if self.trigger == SW_TRIG:
#mid = time.time() ADQAPI.ADQ_SWTrig(self.adq_cu, self.adq_num)
status = ADQAPI.ADQ_GetData(self.adq_cu, self.adq_num, self.target_buffers, 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, self.samples_per_record * self.number_of_records, 2,
0, self.number_of_records, ADQ_CHANNELS_MASK, 0, self.number_of_records, ADQ_CHANNELS_MASK,
0, self.samples_per_record, ADQ_TRANSFER_MODE_NORMAL); 0, self.samples_per_record, ADQ_TRANSFER_MODE_NORMAL):
#print(time.time()-mid,mid-start) raise RuntimeError('no success from ADQ_GetDATA')
if not status: self.data = dataclass(self, **kwds)
raise ValueError('no succesS from ADQ_GetDATA') self.status = self.IDLE
# Now this is an array with all records, but the time is artificial 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 = [] data = []
for ch in range(2): for ch in range(2):
onedim = np.frombuffer(self.target_buffers[ch].contents, dtype=np.int16) onedim = np.frombuffer(adq.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 data.append(onedim.reshape(adq.number_of_records, adq.samples_per_record) / float(2**14)) # 14 bits ADC
return data # Now this is an array with all records, but the time is artificial
self.data = data
def acquire(self): def sinW(self, sig, freq, ti, tf):
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 # sig: signal array
# freq # freq
# ti, tf: initial and end time # ti, tf: initial and end time
@ -241,30 +221,28 @@ class Adq(object):
def mix(self, sigin, sigout, freq, ti, tf): def mix(self, sigin, sigout, freq, ti, tf):
# sigin, sigout: signal array, incomping, output # sigin, sigout: signal array, incomping, output
# freq # 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) a, b = self.sinW(sigin, freq, ti, tf)
phase = arctan2(a,b) * 180 / np.pi amp = np.sqrt(a**2 + b**2)
amp = sqrt(a**2 + b**2)
a, b = a/amp, b/amp a, b = a/amp, b/amp
#si = int(ti * self.samp_freq) # si = int(ti * self.samp_freq)
t = np.arange(len(sigout)) / 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)) wave1 = sigout * (a * np.cos(2*np.pi*freq*t) + b * np.sin(2*np.pi*freq*t))
wave2 = sigout * (a * sin(2*np.pi*freq*t) - b * cos(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 return wave1, wave2
def averageiq(self, data, freq, ti, tf): 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][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 return iorq.sum(axis=0) / self.number_of_records
def filtro(self, iorq, cutoff): def filtro(self, iorq, cutoff):
b, a = butter_lowpass(cutoff, self.samp_freq*1e9) # butter lowpass
nyq = 0.5 * self.sample_rate
#ifi = np.array(signal.filtfilt(b,a,iorq[0])) normal_cutoff = cutoff / nyq
#qf = np.array(signal.filtfilt(b,a,iorq[1])) order = 5
iqf = [signal.filtfilt(b,a,iorq[i]) for i in np.arange(len(iorq))] 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 return iqf
def box(self, iorq, ti, tf): 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))] bxa = [sum(iorq[i][si:sf])/(sf-si) for i in np.arange(len(iorq))]
return bxa 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""" """return iq values of rois and prepare plottable curves for iq"""
times = [] self.ndecimate = int(round(self.sample_rate / freq))
times.append(('aviq', time.time())) # times = []
iq = self.averageiq(data,freq*1e-9,*pulse) # times.append(('aviq', time.time()))
times.append(('filtro', time.time())) iq = self.averageiq(self.data, freq / GHz, *pulse)
iqf = self.filtro(iq,self.bw_cutoff) # times.append(('filtro', time.time()))
iqf = self.filtro(iq, bw_cutoff)
m = len(iqf[0]) // self.ndecimate 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) iqd = np.average(np.resize(iqf, (2, m, self.ndecimate)), axis=2)
t_axis = np.arange(m) * self.ndecimate / self.samp_freq t_axis = np.arange(m) * self.ndecimate / self.samp_freq
pulsig = np.abs(data[0][0]) pulsig = np.abs(self.data[0][0])
times.append(('pulsig', time.time())) # times.append(('pulsig', time.time()))
pulsig = np.average(np.resize(pulsig, (m, self.ndecimate)), axis=1) pulsig = np.average(np.resize(pulsig, (m, self.ndecimate)), axis=1)
self.curves = (t_axis, iqd[0], iqd[1], pulsig) self.curves = (t_axis, iqd[0], iqd[1], pulsig)
#print(times) # print(times)
return [self.box(iqf,*r) for r in roi] 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> # Markus Zolliker <markus.zolliker@psi.ch>
# Jael Celia Lorenzana <jael-celia.lorenzana@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 from frappy.core import StringIO, Readable, Parameter, FloatRange, Writable, HasIO, BoolType
# define the IO class
class IO(StringIO): class IO(StringIO):
end_of_line = ('OK\r', '\r') end_of_line = ('OK\r', '\r')
default_settings = {'baudrate': 9600} default_settings = {'baudrate': 9600}

View File

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

View File

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

View File

@ -16,84 +16,38 @@
# Module authors: # Module authors:
# Markus Zolliker <markus.zolliker@psi.ch> # 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 from frappy.modules import Communicator
class Ls370Sim(Communicator): class _Ls37xSim(Communicator):
CHANNEL_COMMANDS = [ # commands containing %d for the channel number
('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):
CHANNEL_COMMANDS = [ CHANNEL_COMMANDS = [
('RDGR?%d', '1.0'), ('RDGR?%d', '1.0'),
('RDGK?%d', '1.5'), ('RDGK?%d', '1.5'),
('RDGST?%d', '0'), ('RDGST?%d', '0'),
('RDGRNG?%d', '0,5,5,0,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'), ('FILTER?%d', '1,5,80'),
] ]
# commands not related to a channel
OTHER_COMMANDS = [ OTHER_COMMANDS = [
('*IDN?', 'LSCI,MODEL372,372184,05302003'),
('SCAN?', '3,1'), ('SCAN?', '3,1'),
('PID?1', '10,10,0'),
('*OPC?', '1'), ('*OPC?', '1'),
] ]
def earlyInit(self): def earlyInit(self):
super().earlyInit() super().earlyInit()
self._res = {}
self._start = time.time()
self._data = dict(self.OTHER_COMMANDS) self._data = dict(self.OTHER_COMMANDS)
for fmt, v in self.CHANNEL_COMMANDS: for fmt, v in self.CHANNEL_COMMANDS:
for chan in range(1,17): for chan in range(1, 17):
self._data[fmt % chan] = v self._data[fmt % chan] = v
def communicate(self, command): def communicate(self, command):
@ -105,6 +59,10 @@ class Ls372Sim(Communicator):
self._data['RDGST?%d' % channel] = '6' self._data['RDGST?%d' % channel] = '6'
else: else:
self._data['RDGST?%d' % channel] = '0' 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(';') chunks = command.split(';')
reply = [] reply = []
@ -112,7 +70,7 @@ class Ls372Sim(Communicator):
if '?' in chunk: if '?' in chunk:
reply.append(self._data[chunk]) reply.append(self._data[chunk])
else: else:
for nqarg in (1,0): for nqarg in (1, 0):
if nqarg == 0: if nqarg == 0:
qcmd, arg = chunk.split(' ', 1) qcmd, arg = chunk.split(' ', 1)
qcmd += '?' qcmd += '?'
@ -125,3 +83,16 @@ class Ls372Sim(Communicator):
reply = ';'.join(reply) reply = ';'.join(reply)
self.comLog('< %s' % reply) self.comLog('< %s' % reply)
return 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) self.setFastPoll(True, 1.0)
return self.start_ramp_to_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(BUSY, 'ramping field') @status_code(BUSY, 'ramping field')
def ramp_to_target(self, sm): def ramp_to_target(self, sm):
if sm.init: if sm.init:
@ -324,15 +333,6 @@ class Magfield(SimpleMagfield):
self._last_target = sm.target self._last_target = sm.target
return self.start_ramp_to_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) @status_code(Status.RAMPING)
def ramp_to_target(self, sm): def ramp_to_target(self, sm):
dif = abs(self.value - sm.target) 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): def write_target(self, value):
self.read_alive_time() self.read_alive_time()
if self._blocking_error: 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]) raise HardwareError(self.status[1])
self.saveParameters() self.saveParameters()
self.start_machine(self.starting, target=value) self.start_machine(self.starting, target=value)

View File

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

View File

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

View File

@ -19,17 +19,19 @@
"""frappy support for ultrasound""" """frappy support for ultrasound"""
import math import math
#import serial
import os import os
import time import time
import numpy as np import numpy as np
import frappy_psi.iqplot as iqplot from frappy_psi.adq_mr import Adq, PEdata, RUSdata
from frappy_psi.adq_mr import Adq
from frappy.core import Attached, BoolType, Done, FloatRange, HasIO, \ 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.properties import Property
#from frappy.modules import Collector
Collector = Readable
def fname_from_time(t, extension): def fname_from_time(t, extension):
@ -52,10 +54,9 @@ class Roi(Readable):
time = Parameter('start time', FloatRange(unit='nsec'), readonly=False) time = Parameter('start time', FloatRange(unit='nsec'), readonly=False)
size = Parameter('interval (symmetric around 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) enable = Parameter('calculate this roi', BoolType(), readonly=False, default=True)
#status = Parameter(export=False)
pollinterval = Parameter(export=False) pollinterval = Parameter(export=False)
interval = (0,0) interval = (0, 0)
def initModule(self): def initModule(self):
super().initModule() super().initModule()
@ -65,6 +66,9 @@ class Roi(Readable):
def calc_interval(self): def calc_interval(self):
self.interval = (self.time - 0.5 * self.size, self.time + 0.5 * self.size) 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): def write_time(self, value):
self.time = value self.time = value
self.calc_interval() self.calc_interval()
@ -82,81 +86,29 @@ class Pars(Module):
timestamp = Parameter('unix timestamp', StringType(), default='0', readonly=False) timestamp = Parameter('unix timestamp', StringType(), default='0', readonly=False)
temperature = Parameter('T', FloatRange(unit='K'), default=0, readonly=False) temperature = Parameter('T', FloatRange(unit='K'), default=0, readonly=False)
mf = Parameter('field', FloatRange(unit='T'), 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): class FreqStringIO(StringIO):
end_of_line = '\r' end_of_line = '\r'
class Frequency(HasIO, Readable): class Frequency(HasIO, Writable):
pars = Attached() value = Parameter('frequency', unit='Hz')
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)
amp = Parameter('amplitude', FloatRange(unit='dBm'), readonly=False) 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 ioClass = FreqStringIO
dif = None
lastfreq = None def register_dif(self, dif):
old = None self.dif = dif
starttime = None
interval = (0,0)
def earlyInit(self): def write_target(self, value):
super().earlyInit() self.communicate('FREQ %.15g;FREQ?' % value)
self.adq = Adq(self.nr, self.sr, self.bw) self.last_change = time.time()
self.roilist = [] if self.dif:
self.write_nr(self.nr) self.dif.read_value()
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_amp(self, amp): def write_amp(self, amp):
reply = self.communicate('AMPR %g;AMPR?' % amp) reply = self.communicate('AMPR %g;AMPR?' % amp)
@ -166,94 +118,196 @@ class Frequency(HasIO, Readable):
reply = self.communicate('AMPR?') reply = self.communicate('AMPR?')
return float(reply) return float(reply)
def write_freq(self, value):
self.skipctrl = 2 # suppress control for the 2 next steps
return value
def doPoll(self): class FrequencyDif(Readable):
"""main poll loop body""" freq = Attached(Frequency)
if self.lastfreq is None: base = Parameter('base frequency', FloatRange(unit='Hz'), default=0)
self.lastfreq = self.set_freq() value = Parameter('difference to base frequency', FloatRange(unit='Hz'), default=0)
self.adq.start()
if self.starttime is None: 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() self.starttime = time.time()
times = [] self.adq.start()
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]
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]) [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): for i, roi in enumerate(roilist):
roi.i = a = gates[i][0] roi.i = a = gates[i][0]
roi.q = b = gates[i][1] roi.q = b = gates[i][1]
roi.value = math.sqrt(a ** 2 + b ** 2) roi.value = math.sqrt(a ** 2 + b ** 2)
roi.phase = math.atan2(a,b) * 180 / math.pi roi.phase = math.atan2(a, b) * 180 / math.pi
inphase = self.roilist[0].i return self.adq.curves
if self.control:
newfreq = freq + inphase * self.slope - self.basefreq # TODO: CONTROL
# step = sorted((-self.maxstep, inphase * self.slope, self.maxstep))[1] # inphase = self.roilist[0].i
if self.old: # if self.control:
fdif = freq - self.old[0] # newfreq = freq + inphase * self.slope - self.basefreq
idif = inphase - self.old[1] # # step = sorted((-self.maxstep, inphase * self.slope, self.maxstep))[1]
if abs(fdif) >= self.minstep: # if self.old:
self.slope = - fdif / idif # fdif = freq - self.old[0]
else: # idif = inphase - self.old[1]
fdif = 0 # if abs(fdif) >= self.minstep:
idif = 0 # self.slope = - fdif / idif
newfreq = freq + self.minstep # else:
self.old = (freq, inphase) # fdif = 0
if self.skipctrl > 0: # do no control for some time after changing frequency # idif = 0
self.skipctrl -= 1 # newfreq = freq + self.minstep
elif self.control: # self.old = (freq, inphase)
self.freq = sorted((self.freq - self.maxstep, newfreq, self.freq + self.maxstep))[1] # if self.skipctrl > 0: # do no control for some time after changing frequency
#print(times) # self.skipctrl -= 1
return Done # 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,12 +96,14 @@ def print_commit(line):
print(' '.join(output), title) print(' '.join(output), title)
cnt[0] += 1 cnt[0] += 1
if cnt[0] % 50 == 0: 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()) (br0, log0), (br1, log1) = list(log_no.items())
no1 = 0 no1 = 0
for no0, line0 in enumerate(log0): try:
for no0, line0 in enumerate(log0):
if line0[1]: # line not yet printed if line0[1]: # line not yet printed
infodict = commits[line0[1]] infodict = commits[line0[1]]
if len(infodict) > 1: # found a match if len(infodict) > 1: # found a match
@ -114,3 +116,5 @@ for no0, line0 in enumerate(log0):
print_commit(line1) print_commit(line1)
no1 = no1end no1 = no1end
print_commit(line0) print_commit(line0)
except StopIteration:
pass

View File

@ -1,2 +1,3 @@
2024-01-29 wip develop 2024-01-29 wip develop
2024-01-29 wip mlz 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): def test_full(direc, log):
ret = load_config('pyfile_cfg.py', log) ret = load_config(['pyfile_cfg.py'], log)
do_asserts(ret) 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 import pytest
from frappy.datatypes import BoolType, FloatRange, StringType, IntRange, ScaledInteger 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.modules import Communicator, Drivable, Readable, Module, Writable
from frappy.params import Command, Parameter, Limit from frappy.params import Command, Parameter, Limit
from frappy.rwhandler import ReadHandler, WriteHandler, nopoll from frappy.rwhandler import ReadHandler, WriteHandler, nopoll
@ -141,12 +141,10 @@ def test_ModuleMagic():
# first inherited accessibles # first inherited accessibles
sortcheck1 = ['value', 'status', 'pollinterval', 'target', 'stop', sortcheck1 = ['value', 'status', 'target', 'pollinterval', 'stop',
'param1', 'param2', 'cmd', 'a1', 'a2', 'cmd2'] 'param1', 'param2', 'cmd', 'a1', 'a2', 'cmd2']
class Newclass2(Newclass1): class Newclass2(Newclass1):
paramOrder = 'param1', 'param2', 'cmd', 'value'
@Command(description='another stuff') @Command(description='another stuff')
def cmd2(self, arg): def cmd2(self, arg):
return arg return arg
@ -171,9 +169,9 @@ def test_ModuleMagic():
def read_value(self): def read_value(self):
return 0 return 0
# first inherited items not mentioned, then the ones mentioned in paramOrder, then the other new ones # first predefined parameters, then in the order of inheritance
sortcheck2 = ['status', 'pollinterval', 'target', 'stop', sortcheck2 = ['value', 'status', 'target', 'pollinterval', 'stop',
'a1', 'a2', 'cmd2', 'param1', 'param2', 'cmd', 'value', 'b2'] 'param1', 'param2', 'cmd', 'a1', 'a2', 'cmd2', 'b2']
updates = {} updates = {}
srv = ServerStub(updates) srv = ServerStub(updates)
@ -245,7 +243,7 @@ def test_ModuleMagic():
'export', 'group', 'description', 'features', 'export', 'group', 'description', 'features',
'meaning', 'visibility', 'implementation', 'interface_classes', 'target', 'stop', 'meaning', 'visibility', 'implementation', 'interface_classes', 'target', 'stop',
'status', 'param1', 'param2', 'cmd', 'a2', 'pollinterval', 'slowinterval', 'b2', '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()) == { assert set(cfg['value'].keys()) == {
'group', 'export', 'relative_resolution', 'group', 'export', 'relative_resolution',
'visibility', 'unit', 'default', 'value', 'datatype', 'fmtstr', 'visibility', 'unit', 'default', 'value', 'datatype', 'fmtstr',
@ -943,3 +941,36 @@ def test_stop_doc(modcls):
if (modcls.stop.description == Drivable.stop.description if (modcls.stop.description == Drivable.stop.description
and modcls.stop.func != Drivable.stop.func): and modcls.stop.func != Drivable.stop.func):
assert modcls.stop.func.__doc__ # stop method needs a doc string 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() o2 = co()
assert o1.a == 1 assert o1.a == 1
assert o2.a == 3 assert o2.a == 3
o2.setProperty('a', 4)
assert o2.a == 4
with pytest.raises(ProgrammingError) as e: with pytest.raises(ProgrammingError) as e:
class cx(c): # pylint: disable=unused-variable class cx(c): # pylint: disable=unused-variable

View File

@ -57,13 +57,13 @@ def test_name_only(direc, log):
def test_file(direc, log): def test_file(direc, log):
"""only see that this does not throw. get config from cfgfiles.""" """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() s._processCfg()
def test_basic_description(direc, log): def test_basic_description(direc, log):
"""only see that this does not throw. get config from cfgfiles.""" """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() s._processCfg()
desc = s.secnode.get_descriptive_data('') desc = s.secnode.get_descriptive_data('')
# secnode properties correctly exported # 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()