Compare commits

...

84 Commits
sinq ... wip

Author SHA1 Message Date
421eb67b93 Merge branch 'glab_merge_request' into 'wip'
frappy_psi.sensirion: fix a typo

See merge request samenv/frappy!1
2025-03-28 16:47:39 +01:00
3048b8cb7d frappy_psi.sensirion: fix a typo
Change-Id: I259151b7a1b908c8289ecb88d2d3d4e6d9e45c12
2025-03-28 16:30:10 +01:00
0ef484e082 frappy_psi/adq_mr (ultrasound): exit on reboot error message
otherwise the error message is confusing
+ remove CR from line endings in adq_mr.py

Change-Id: Ia465a26803a92677383969ff620ef35e58f1a5ec
2025-03-28 14:27:06 +01:00
8560384529 ls370res: do not raise in read_rdgrng error when channel is disabled
Change-Id: I565e5cd74cf7f12bfd5eea9e8867117154461017
2025-03-28 14:27:06 +01:00
l_samenv
16d419c0f3 ah2700: make loss its own module 2025-03-28 13:15:24 +01:00
Ultrasound PC
8c548da2e0 bin/us-plot: fix usage message 2025-03-26 17:02:35 +01:00
Ultrasound PC
d9f340dce6 ultrasound: change control roi0 to a Readable (2)
+ remove cfg/PEUS.py
+ fix equipment_id of PEUS
+ add header to frappy_psi.iqplot
2025-03-26 16:45:53 +01:00
Ultrasound PC
1325c8924d ultrasound: change control roi0 to a Readable
+ remove cfg/PEUS.py
+ fix equipment_id of PEUS
2025-03-26 16:37:15 +01:00
Ultrasound PC
f8e3bd9ad2 improve ultrasound plot clients
- make plot window not to raise to the front on replot
- chmod +x
2025-03-26 16:18:54 +01:00
6f547f0781 ultrasound: reworked after tests
- new classes in frappy_psi/ultrasound.py and frappy_psi/adq.mr.py
- add signal plottter
- move clients to bin/ directory

Change-Id: I8db8e5ebc082c346278f09e0e54504e070655f14
2025-03-26 15:31:46 +01:00
l_samenv
322cd39e0a gas10k / mercury.HeaterUpdate: switch off loop on startup
the class frappy_psi.mercury.HeaterUpdate is used for the output
of a soft pid loop. set target to 0 to switch off the loop
on startup.
2025-03-26 10:51:16 +01:00
l_samenv
41b51b35fd further work on needle valve, pump and lakeshore 2025-03-19 16:38:21 +01:00
19571ab83d change again how to exit logdif.py
Change-Id: I442ca8c2ee7ca25ff98a0e84df2688a55a0dcec9
2025-03-19 16:34:59 +01:00
b35c97f311 stop poller threads on shutdown: cosmetics
cosmetics after gerrit

Change-Id: I4d982f83e3fe5a8c8c821ac718e51b9a58de2a62
2025-03-19 15:33:25 +01:00
5d175b89ca frappy_psi.ultrasound: add input_delay and other improvments
Change-Id: I6cb5690d82d96d6775fcb649fc633c4039932463
2025-03-19 15:29:17 +01:00
f8c52af3ac frappy_psi.ultrasound: after rework (still wip)
Change-Id: I200cbeca2dd0f030a01a78ba4d38c342c3c8c8e3
2025-03-17 09:37:13 +01:00
bf9c946b1d frappy-scan: resolve ip numbers to names
Change-Id: I07bf7c274aeb52f2aaa58e8aa2f3bcb2788556ee
2025-03-17 09:36:50 +01:00
09e596f847 stop poller threads on shutdown
make sure module methods are not called after shutdownModule

+ fix: when mod.enablePoll is False, pollInfo is None
  therefore we have to check before access

Change-Id: I83b28607b25996376939175be8abf0c5b27bcac1
2025-03-17 09:35:57 +01:00
l_samenv
7e2ccd214e frappy_psi.drums: changes after test
when trying with Marcel, we needed these fixes
2025-03-14 09:05:09 +01:00
907a52ccdb config: Mod() should return config dict
this helps for coded configuration

Change-Id: I07bdf72f77082f31ee86192faec63df706dcbf56
Reviewed-on: https://forge.frm2.tum.de/review/c/secop/frappy/+/35803
Tested-by: Jenkins Automated Tests <pedersen+jenkins@frm2.tum.de>
Reviewed-by: Georg Brandl <g.brandl@fz-juelich.de>
Reviewed-by: Markus Zolliker <markus.zolliker@psi.ch>
2025-03-07 10:19:46 +01:00
51dba895a5 config: validate value and default of parameters
The Parameter Properties 'value', 'default' and 'constant'
have ValueType, so they are not checked in the setProperty call.
We have to do this explicitly in Module._add_accessible.

Change-Id: I1e35adf2fe539411b4aebacd813adb07497de95b
Reviewed-on: https://forge.frm2.tum.de/review/c/secop/frappy/+/35797
Tested-by: Jenkins Automated Tests <pedersen+jenkins@frm2.tum.de>
Reviewed-by: Alexander Zaft <a.zaft@fz-juelich.de>
Reviewed-by: Markus Zolliker <markus.zolliker@psi.ch>
2025-03-07 10:19:01 +01:00
Georg Brandl
d86718b81e remove wrong <weight> from fonts on Qt6
Change-Id: Ib94b2ed74598b9f54c2361e61bfa940e60bd7c62
2025-03-07 10:18:51 +01:00
Georg Brandl
42a6bfb5d2 debian: update compat
Change-Id: I172dff4e0239ce90fe7b1c19fc800ba98f116270
2025-03-07 10:18:42 +01:00
895f66f713 core: simplify test for methods names
The test for method names 'read_<param>' and 'write_<param>'
without a defined parameter is simplified. We do not check
anymore method names from base classes. Base classes
inheriting from HasAccessible are checked anyway at the
place they are defined.

+ add a test for it
+ move some tests to a new file test_all_modules.py, as
  test_modules.py is getting too long
+ fix missing doc string (frappy.simulation.SimDrivable.stop)

Change-Id: Id8a9afe5c977ae3b1371bd40c6da52be2fc79eb9
Reviewed-on: https://forge.frm2.tum.de/review/c/secop/frappy/+/35503
Tested-by: Jenkins Automated Tests <pedersen+jenkins@frm2.tum.de>
Reviewed-by: Markus Zolliker <markus.zolliker@psi.ch>
2025-03-07 10:18:35 +01:00
3663c62b46 core: alternative approach for optional accessibles
This is meant to replace change 33375.
Optional commands and parameters may be declared with the argument
optional=True. In principle, optional commands are not really needed
to be declared, but doing so is nice for documentation reasons
and for inherited accessible properties.

Optional parameters and commands can not be used and are not
exported als long as they are not overridden in subclasses.

- add a test for this
+ fix an issue with checking for methods like read_<param> without
  <param> being a parameter

Change-Id: Ide5021127a02778e7f2f7162555ec8826f1471cb
Reviewed-on: https://forge.frm2.tum.de/review/c/secop/frappy/+/35495
Tested-by: Jenkins Automated Tests <pedersen+jenkins@frm2.tum.de>
Reviewed-by: Alexander Zaft <a.zaft@fz-juelich.de>
Reviewed-by: Markus Zolliker <markus.zolliker@psi.ch>
Reviewed-by: Enrico Faulhaber <enrico.faulhaber@frm2.tum.de>
2025-03-07 10:18:27 +01:00
l_samenv
8c2588a5ed merged changes for lakeshore and ccu4 2025-03-07 07:37:11 +01:00
l_samenv
95dc8b186e improve error handling 2025-03-06 17:22:21 +01:00
l_samenv
265dbb1a57 gui: add org- and app-name to QtApplication
for a better path name of stored configuration
2025-03-06 16:57:55 +01:00
73e9c8915b logdif.py: leave on every input except bare return
Change-Id: I3d53c7b45fb9ef09a61be5af13a2cdc4d32d5c7d
2025-02-13 09:40:27 +01:00
2e99e45aea WIP new version of ultrasound
Change-Id: Iadb83396a64e277f6f0a37f7a96d92105648c4fe
2025-01-28 09:40:36 +01:00
b7bc81710d frappy_demo.test: add parameter for testing error messages
Change-Id: Ifbf9d6829be373417d3bf1ff398d2aee283d8c9a
2025-01-17 15:01:11 +01:00
eee63ee3df 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-01-17 15:00:20 +01:00
fd43687465 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-01-17 15:00:20 +01:00
a25a368491 take over changes from ultrasound PC
Change-Id: I1eae717a5963e618d87ddf52db991d428a046d24
2025-01-09 11:23:12 +01:00
4397d8db1a WIP: old oxford devices (ILM, IPS, IGH...)
Change-Id: I4ca0dc6149d257818d300db4d886a1e33e8210be
2025-01-09 10:09:33 +01:00
e60ac5e655 move start_ramp_to_target to SimpleMagfiield
Change-Id: Iab3fe8738c560bf5ac2f11a4a34428a8ffd6a7c2
2024-12-20 15:49:33 +01:00
0b5b40cfba frappy_psi.ccu4: some smaller updates
Change-Id: I128ac57aad951fd8ad3bdf663c69c85644063645
2024-12-18 15:40:05 +01:00
2a617fbaf0 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>
2024-12-18 09:29:58 +01:00
72d09ea73a 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>
2024-12-10 16:30:19 +01:00
1ae19d03b3 frappy_psi.sea: fix case when bool is implemented as text
introduce SeaBool for this

Change-Id: I9c6b6ee7d33f11b173d612efc044fce8a563f626
2024-12-10 16:29:07 +01:00
41cb107f50 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>
2024-12-10 10:28:25 +01:00
8b0c4c78a9 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>
2024-12-10 10:28:25 +01:00
7ac10d2260 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>
2024-12-10 10:28:25 +01:00
6cbb3a094b 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
2024-12-06 11:58:45 +01:00
l_samenv
405d316568 adapt temperature and temperature_regulation importance
- temperature_regulation on VTI should have higher importance (27)
  than temperature on sample stick, when Drivable (25)
2024-12-03 15:32:57 +01:00
l_samenv
ac92a6ca3d sea cfg: set visibility of calibration points to expert 2024-12-03 15:23:01 +01:00
l_samenv
a9e3489325 ma7: use new config type with sea_path and frappy.sea.LscDrivable 2024-12-03 15:19:45 +01:00
654a472a7e frappy_psi.sea: more improvements
- add sea_path property
- add LscDrivable (config of these modules is easier to understand)

Change-Id: I616dc94de6e784f6d8cfcf080d9a8408cbf73d93
2024-12-03 15:12:02 +01:00
l_samenv
ddc72d0ea7 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)
2024-11-28 18:06:14 +01:00
ede07e266c add ori2 2024-11-28 18:05:02 +01:00
dmc
4b543d02a0 varioxb: fix config, om not yet available 2024-11-28 18:05:02 +01:00
a4d5d8d3b7 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-27 17:38:14 +01:00
b37e625df3 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-27 17:38:14 +01:00
1dbd7c145a 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-27 17:38:14 +01:00
2aa27f1ea5 updated sync_branches for sinq branch
Change-Id: Ic3330c4049b527dc98704fbbd94180dcd4930cb1
2024-11-27 17:38:14 +01:00
b28cdefe8a 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-27 16:30:06 +01:00
e0e442814f fix description of ts in ma11stick 2024-11-26 13:48:37 +01:00
66895f4f82 improve lakeshore demo
use super call for read_status

TODO: update tutorial!
Change-Id: I2dd5631908dc370c6e6286587099e25a0e5ee867
2024-11-26 13:40:13 +01:00
49bf0d21a9 frappy_psi.bkpower: improve doc
Change-Id: I0736d1d8a40b0140bfdbf5aca189b8ddc5b22973
2024-11-26 13:39:34 +01:00
e8cd193d0d 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-19 14:03:35 +01:00
142add9109 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-19 14:03:28 +01:00
Jenkins system
c2673952f4 [deb] Release v0.20.4 2024-11-19 14:01:20 +01:00
Jens Krüger
9fc2aa65d5 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-19 14:01:20 +01:00
09fbaedb16 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-15 10:39:47 +01:00
Jens Krüger
5deaf4cfd9 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-15 10:39:47 +01:00
81f7426739 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-15 10:39:47 +01:00
Georg Brandl
c69e516873 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-15 10:39:47 +01:00
Jenkins system
64732eb0c8 [deb] Release v0.20.3 2024-11-15 10:39:47 +01:00
Alexander Zaft
1535448090 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-15 10:39:47 +01:00
Georg Brandl
554996ffd3 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-15 10:39:47 +01:00
Jenkins system
2d824978a9 [deb] Release v0.20.2 2024-11-15 10:39:47 +01:00
Alexander Zaft
35dd166fee 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-15 10:39:47 +01:00
Georg Brandl
aee99df2d0 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-15 10:39:47 +01:00
8e05090795 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-15 10:39:47 +01:00
Alexander Zaft
eac58982d9 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-15 10:39:47 +01:00
Georg Brandl
0f34418435 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-15 10:39:47 +01:00
Alexander Zaft
1423800ff4 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-15 10:39:47 +01:00
Alexander Zaft
e333763105 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-15 10:39:47 +01:00
Alexander Zaft
c09e02a01e 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-15 10:39:47 +01:00
Alexander Zaft
337be1b2bc config: fix typo
Change-Id: Ie90993d9b2d387780fa3faa28fd8d4523f7fc866
2024-11-15 10:39:47 +01:00
Alexander Zaft
752942483f 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-15 10:39:47 +01:00
0204bdfe2f 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-15 10:39:47 +01:00
facaca94eb 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-15 10:39:47 +01:00
0f0a177254 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-15 10:39:47 +01:00
119 changed files with 5778 additions and 1697 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

@ -69,7 +69,7 @@ def main(argv=None):
console.setLevel(loglevel) console.setLevel(loglevel)
logger.addHandler(console) logger.addHandler(console)
app = QApplication(argv) app = QApplication(argv, organizationName='frappy', applicationName='frappy_gui')
win = MainWindow(args, logger) win = MainWindow(args, logger)
app.aboutToQuit.connect(win._onQuit) app.aboutToQuit.connect(win._onQuit)

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:

139
bin/frappy-scan Executable file
View File

@ -0,0 +1,139 @@
#!/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):
try:
hostname = socket.gethostbyaddr(answer.address)[0]
address = hostname
numeric = f' ({answer.address})'
except Exception:
address = answer.address
numeric = ''
if short:
# NOTE: keep this easily parseable!
print(f'{answer.equipment_id} {address}:{answer.port}')
return
print(f'Found {answer.equipment_id} at {address}{numeric}:')
print(f' Port: {answer.port}')
print(f' Firmware: {answer.firmware}')
desc = answer.description.replace('\n', '\n ')
print(f' Node description: {desc}')
print('-' * 80)
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='Keep listening after the broadcast.')
parser.add_argument('-s', '--short', action='store_true',
help='Print short info (always on when listen).')
args = parser.parse_args(sys.argv[1:])
short = args.listen or args.short
if not short:
print('-' * 80)
for answer in scan():
print_answer(answer, short=short)
if args.listen:
listen(short=short)

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:

50
bin/peus-plot Executable file
View File

@ -0,0 +1,50 @@
#!/usr/bin/env python3
import sys
from pathlib import Path
# Add import path for inplace usage
sys.path.insert(0, str(Path(__file__).absolute().parents[1]))
from frappy.client.interactive import Client
from frappy_psi.iqplot import Plot
import numpy as np
import matplotlib.pyplot as plt
if len(sys.argv) < 2:
print('Usage: peus-plot <maxY>')
def get_modules(name):
return list(filter(None, (globals().get(name % i) for i in range(10))))
secnode = Client('pc13252:5000')
time_size = {'time', 'size'}
int_mods = [u] + get_modules('roi%d')
t_rois = get_modules('roi%d')
i_rois = get_modules('roi%di')
q_rois = get_modules('roi%dq')
if len(sys.argv) > 1:
maxy = float(sys.argv[1])
else:
maxy = 0.02
iqplot = Plot(maxy)
for i in range(99):
pass
try:
while True:
curves = np.array(u.get_curves())
iqplot.plot(curves,
rois=[(r.time - r.size * 0.5, r.time + r.size * 0.5) for r in int_mods],
average=([r.time for r in t_rois],
[r.value for r in i_rois],
[r.value for r in q_rois]))
if not iqplot.pause(0.5):
break
except KeyboardInterrupt:
iqplot.close()

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__':

65
bin/us-plot Executable file
View File

@ -0,0 +1,65 @@
#!/usr/bin/env python3
import sys
from pathlib import Path
# Add import path for inplace usage
sys.path.insert(0, str(Path(__file__).absolute().parents[1]))
from frappy.client.interactive import Client
import numpy as np
import matplotlib.pyplot as plt
from frappy_psi.iqplot import Pause
if len(sys.argv) < 2:
print("""
Usage:
us-plot <end> [<start> [<npoints>]]
end: end of window [ns]
start: start of window [n2], default: 0
npoints: number fo points (default 1000)
""")
sys.exit(0)
Client('pc13252:5000')
def plot(array, ax, style, xs):
xaxis = np.arange(len(array)) * xs
return ax.plot(xaxis, array, style)[0]
def update(array, line, xs):
xaxis = np.arange(len(array)) * xs
line.set_data(np.array([xaxis, array]))
def on_close(event):
sys.exit(0)
start = 0
end = float(sys.argv[1])
npoints = 1000
if len(sys.argv) > 2:
start = float(sys.argv[2])
if len(sys.argv) > 3:
npoints = float(sys.argv[3])
fig, ax = plt.subplots(figsize=(15,3))
pause = Pause(fig)
try:
get_signal = iq.get_signal
print('plotting RUS signal')
except NameError:
get_signal = u.get_signal
print('plotting PE signal')
xs, signal = get_signal(start, end, npoints)
lines = [plot(s, ax, '-', xs) for s in signal]
while pause(0.5):
plt.draw()
xs, signal = get_signal(start, end, npoints)
for line, sig in zip(lines, signal):
update(sig, line, xs)

87
cfg/PEUS_cfg.py Normal file
View File

@ -0,0 +1,87 @@
Node('PEUS.psi.ch',
'ultrasound, pulse_echo configuration',
interface='5000',
)
Mod('u',
'frappy_psi.ultrasound.PulseEcho',
'ultrasound acquisition loop',
freq='f',
# pollinterval=0.1,
time=900.0,
size=5000.0,
nr=500,
sr=32768,
bw=1e7,
)
Mod('fio',
'frappy_psi.ultrasound.FreqStringIO', '',
uri='serial:///dev/ttyS1?baudrate=57600',
)
Mod('f',
'frappy_psi.ultrasound.Frequency',
'writable for frequency',
output='R', # L for LF (bnc), R for RF (type N)
io='fio',
amp=0.5, # VPP
)
Mod('fdif',
'frappy_psi.ultrasound.FrequencyDif',
'writable for frequency minus base frequency',
freq='f',
base=41490200.0,
)
# Mod('curves',
# 'frappy_psi.ultrasound.Curves',
# 't, I, Q and pulse arrays for plot',
# )
def roi(name, time, size, components='iqpa', enable=True, control=False, freq=None, **kwds):
description = 'I/Q of region {name}'
if freq:
kwds.update(cls='frappy_psi.ultrasound.ControlRoi',
description=f'{description} as control loop',
freq=freq, **kwds)
else:
kwds.update(cls='frappy_psi.ultrasound.Roi',
description=description, **kwds)
kwds.update({c: name + c for c in components})
Mod(name,
main='u',
time=time,
size=size,
enable=enable,
**kwds,
)
for c in components:
Mod(name + c,
'frappy.modules.Readable',
f'{name}{c} component',
)
# control loop
roi('roi0', 2450, 300, freq='f', maxstep=100000, minstep=4000)
# other rois
roi('roi1', 5950, 300)
roi('roi2', 9475, 300)
roi('roi3', 12900, 300)
#roi('roi4', 400, 30, False)
#roi('roi5', 400, 30, False)
#roi('roi6', 400, 30, False)
#roi('roi7', 400, 30, False)
#roi('roi8', 400, 30, False)
#roi('roi9', 400, 30, False)
Mod('delay',
'frappy_psi.dg645.Delay',
'delay line with 2 channels',
uri='serial:///dev/ttyS2',
on1=1e-09,
on2=1e-09,
off1=4e-07,
off2=6e-07,
)

39
cfg/RUS_cfg.py Normal file
View File

@ -0,0 +1,39 @@
Node(equipment_id = 'r_ultrasound.psi.ch',
description = 'resonant ultra sound setup',
interface = 'tcp://5000',
)
Mod('iq',
cls = 'frappy_psi.ultrasound.RUS',
description = 'ultrasound iq mesurement',
imod = 'i',
qmod = 'q',
freq='f',
input_range=10, # VPP
input_delay = 0,
periods = 163,
)
Mod('freqio',
'frappy_psi.ultrasound.FreqStringIO',
' ',
uri = 'serial:///dev/ttyS1?baudrate=57600',
)
Mod('f',
cls = 'frappy_psi.ultrasound.Frequency',
description = 'ultrasound frequency',
io='freqio',
output='L', # L for LF (bnc), R for RF (type N)
target=10000,
)
Mod('i',
cls='frappy.modules.Readable',
description='I component',
)
Mod('q',
cls='frappy.modules.Readable',
description='Q component',
)

15
cfg/addons/ah2700_cfg.py Normal file → Executable file
View File

@ -2,8 +2,21 @@ Node('ah2700.frappy.psi.ch',
'Andeen Hagerlin 2700 Capacitance Bridge', 'Andeen Hagerlin 2700 Capacitance Bridge',
) )
Mod('cap_io',
'frappy_psi.ah2700.Ah2700IO',
'',
uri='linse-976d-ts:3006',
)
Mod('cap', Mod('cap',
'frappy_psi.ah2700.Capacitance', 'frappy_psi.ah2700.Capacitance',
'capacitance', 'capacitance',
uri='dil4-ts.psi.ch:3008', io = 'cap_io',
)
Mod('loss',
'frappy_psi.parmod.Par',
'loss parameter',
read='cap.loss',
unit='deg',
) )

View File

@ -4,33 +4,22 @@ Node('ls340test.psi.ch',
) )
Mod('io', Mod('io',
'frappy_psi.lakeshore.Ls340IO', 'frappy_psi.lakeshore.IO340',
'communication to ls340', 'communication to ls340',
uri='tcp://ldmprep56-ts:3002' uri='tcp://localhost:7777'
) )
Mod('dev',
'frappy_psi.lakeshore.Device340',
'device for calcurve',
io='io',
curve_handling=True,
)
Mod('T', Mod('T',
'frappy_psi.lakeshore.TemperatureLoop340',
'sample temperature',
output_module='Heater',
target=Param(max=470),
io='io',
channel='B'
)
Mod('T_cold_finger',
'frappy_psi.lakeshore.Sensor340', 'frappy_psi.lakeshore.Sensor340',
'cold finger temperature', 'sample temperature',
io='io', # output_module='Heater',
channel='A' device='dev',
) channel='A',
calcurve='x29746',
Mod('Heater',
'frappy_psi.lakeshore.HeaterOutput',
'heater output',
channel='B',
io='io',
resistance=25,
max_power=50,
current=1
) )

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',

17
cfg/main/ori7test_cfg.py Normal file
View File

@ -0,0 +1,17 @@
from frappy_psi.ccracks import Rack
Node('ori7test.psi.ch',
'ORI7 test',
'tcp://5000'
)
rack = Rack(Mod)
rack.lakeshore()
rack.sensor('Ts', channel='C', calcurve='x186350')
rack.loop('T', channel='B', calcurve='x174786', output_module='htr', target=10)
rack.heater('htr', 1, '100W', 25)
rack.ccu(he=True, n2=True)
rack.hepump()

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'],
) )

169
debian/changelog vendored
View File

@ -1,4 +1,79 @@
frappy-core (0.20.1) jammy; urgency=medium frappy-core (0.20.4) stable; urgency=medium
[ Georg Brandl ]
* remove unused file
[ Markus Zolliker ]
* frappy.lib.multievent: avoid deadlock
[ Jens Krüger ]
* PSI: Fix import error on ThermoFisher module
[ Markus Zolliker ]
* frappy.client: catch all errors in handleError callback
[ Jens Krüger ]
* Lib/config: Create a list of pathes only for confdir
-- Georg Brandl <jenkins@frm2.tum.de> Thu, 14 Nov 2024 14:43:54 +0100
frappy-core (0.20.3) stable; urgency=medium
[ Georg Brandl ]
* fixup test for cfg_editor utils to run from non-checkout, and fix names, and remove example code
[ Alexander Zaft ]
* add generalConfig to etc
-- Georg Brandl <jenkins@frm2.tum.de> Thu, 07 Nov 2024 10:57:11 +0100
frappy-core (0.20.2) stable; urgency=medium
[ Georg Brandl ]
* pylint: do not try to infer too much
[ Alexander Zaft ]
* test_server: basic description checks
* simulation: fix extra_params default, ccidu1 cfg
* sim: make amagnet sim cfg startable again
* server: fix positional argument lint
* server: show interfaces as custom property
* core: fix Dispatcher and SECNode opts handling
[ Markus Zolliker ]
* frappy_psi.sea: bugfix: revert change of updateEvent to udpateItem
* fix playground
[ Alexander Zaft ]
* config: allow using Prop(...)
* config: fix typo
* Revert "config: allow using Prop(...)"
* generalconfig: streamlined config discovery
[ Markus Zolliker ]
* better order of accessibles: 'value' 'status' and 'target' first
[ Alexander Zaft ]
* server: fix windows ctrl-c
[ Georg Brandl ]
* systemd: enable indication of reloading/stopping
[ Alexander Zaft ]
* server: service discovery over UDP.
[ Markus Zolliker ]
* generalConfig: fix the case when confdir is a list of paths
[ Georg Brandl ]
* server: better handling of cfgfile argument
[ Alexander Zaft ]
* fix frappy-server cfgfiles command
-- Georg Brandl <jenkins@frm2.tum.de> Wed, 06 Nov 2024 10:40:26 +0100
frappy-core (0.20.1) stable; urgency=medium
* gui: do not add a console logger when there is no sys.stdout * gui: do not add a console logger when there is no sys.stdout
* remove unused test class * remove unused test class
@ -8,7 +83,7 @@ frappy-core (0.20.1) jammy; urgency=medium
-- Georg Brandl <jenkins@frm2.tum.de> Thu, 17 Oct 2024 16:31:27 +0200 -- Georg Brandl <jenkins@frm2.tum.de> Thu, 17 Oct 2024 16:31:27 +0200
frappy-core (0.20.0) jammy; urgency=medium frappy-core (0.20.0) stable; urgency=medium
[ Alexander Zaft ] [ Alexander Zaft ]
* bin: remove make_doc * bin: remove make_doc
@ -53,7 +128,7 @@ frappy-core (0.20.0) jammy; urgency=medium
-- Alexander Zaft <jenkins@frm2.tum.de> Thu, 17 Oct 2024 14:24:29 +0200 -- Alexander Zaft <jenkins@frm2.tum.de> Thu, 17 Oct 2024 14:24:29 +0200
frappy-core (0.19.10) jammy; urgency=medium frappy-core (0.19.10) stable; urgency=medium
[ Alexander Zaft ] [ Alexander Zaft ]
* debian: let frappy-core replace frappy-demo * debian: let frappy-core replace frappy-demo
@ -63,25 +138,25 @@ frappy-core (0.19.10) jammy; urgency=medium
-- Alexander Zaft <jenkins@frm2.tum.de> Wed, 07 Aug 2024 17:00:06 +0200 -- Alexander Zaft <jenkins@frm2.tum.de> Wed, 07 Aug 2024 17:00:06 +0200
frappy-core (0.19.9) jammy; urgency=medium frappy-core (0.19.9) stable; urgency=medium
* debian: fix missing install dir * debian: fix missing install dir
-- Georg Brandl <jenkins@frm2.tum.de> Tue, 06 Aug 2024 16:02:50 +0200 -- Georg Brandl <jenkins@frm2.tum.de> Tue, 06 Aug 2024 16:02:50 +0200
frappy-core (0.19.8) jammy; urgency=medium frappy-core (0.19.8) stable; urgency=medium
* debian: move demo into core * debian: move demo into core
-- Georg Brandl <jenkins@frm2.tum.de> Tue, 06 Aug 2024 15:58:20 +0200 -- Georg Brandl <jenkins@frm2.tum.de> Tue, 06 Aug 2024 15:58:20 +0200
frappy-core (0.19.7) jammy; urgency=medium frappy-core (0.19.7) stable; urgency=medium
* lib: GeneralConfig fix missing keys logic * lib: GeneralConfig fix missing keys logic
-- Alexander Zaft <jenkins@frm2.tum.de> Tue, 06 Aug 2024 15:04:07 +0200 -- Alexander Zaft <jenkins@frm2.tum.de> Tue, 06 Aug 2024 15:04:07 +0200
frappy-core (0.19.6) jammy; urgency=medium frappy-core (0.19.6) stable; urgency=medium
[ Jens Krüger ] [ Jens Krüger ]
* SINQ/SEA: Fix import error due to None value * SINQ/SEA: Fix import error due to None value
@ -95,7 +170,7 @@ frappy-core (0.19.6) jammy; urgency=medium
-- Jens Krüger <jenkins@frm2.tum.de> Tue, 06 Aug 2024 13:56:51 +0200 -- Jens Krüger <jenkins@frm2.tum.de> Tue, 06 Aug 2024 13:56:51 +0200
frappy-core (0.19.5) jammy; urgency=medium frappy-core (0.19.5) stable; urgency=medium
* client: fix how to raise error on wrong ident * client: fix how to raise error on wrong ident
* add missing requirements to setup.py * add missing requirements to setup.py
@ -104,13 +179,13 @@ frappy-core (0.19.5) jammy; urgency=medium
-- Alexander Zaft <jenkins@frm2.tum.de> Mon, 05 Aug 2024 09:30:53 +0200 -- Alexander Zaft <jenkins@frm2.tum.de> Mon, 05 Aug 2024 09:30:53 +0200
frappy-core (0.19.4) jammy; urgency=medium frappy-core (0.19.4) stable; urgency=medium
* actually exclude cfg-editor * actually exclude cfg-editor
-- Georg Brandl <jenkins@frm2.tum.de> Fri, 26 Jul 2024 11:46:10 +0200 -- Georg Brandl <jenkins@frm2.tum.de> Fri, 26 Jul 2024 11:46:10 +0200
frappy-core (0.19.3) jammy; urgency=medium frappy-core (0.19.3) stable; urgency=medium
[ Markus Zolliker ] [ Markus Zolliker ]
* frappy_psi.extparams.StructParam: fix doc + simplify * frappy_psi.extparams.StructParam: fix doc + simplify
@ -130,7 +205,7 @@ frappy-core (0.19.3) jammy; urgency=medium
-- Markus Zolliker <jenkins@frm2.tum.de> Fri, 26 Jul 2024 08:36:43 +0200 -- Markus Zolliker <jenkins@frm2.tum.de> Fri, 26 Jul 2024 08:36:43 +0200
frappy-core (0.19.2) jammy; urgency=medium frappy-core (0.19.2) stable; urgency=medium
[ l_samenv ] [ l_samenv ]
* fix missing update after error on parameter * fix missing update after error on parameter
@ -155,7 +230,7 @@ frappy-core (0.19.2) jammy; urgency=medium
-- l_samenv <jenkins@frm2.tum.de> Tue, 18 Jun 2024 15:21:43 +0200 -- l_samenv <jenkins@frm2.tum.de> Tue, 18 Jun 2024 15:21:43 +0200
frappy-core (0.19.1) jammy; urgency=medium frappy-core (0.19.1) stable; urgency=medium
[ Markus Zolliker ] [ Markus Zolliker ]
* SecopClient.online must be True while activating * SecopClient.online must be True while activating
@ -167,7 +242,7 @@ frappy-core (0.19.1) jammy; urgency=medium
-- Markus Zolliker <jenkins@frm2.tum.de> Fri, 07 Jun 2024 16:50:33 +0200 -- Markus Zolliker <jenkins@frm2.tum.de> Fri, 07 Jun 2024 16:50:33 +0200
frappy-core (0.19.0) jammy; urgency=medium frappy-core (0.19.0) stable; urgency=medium
[ Markus Zolliker ] [ Markus Zolliker ]
* simulation: extra_params might be a list * simulation: extra_params might be a list
@ -223,14 +298,14 @@ frappy-core (0.19.0) jammy; urgency=medium
-- Markus Zolliker <jenkins@frm2.tum.de> Thu, 16 May 2024 11:31:25 +0200 -- Markus Zolliker <jenkins@frm2.tum.de> Thu, 16 May 2024 11:31:25 +0200
frappy-core (0.18.1) focal; urgency=medium frappy-core (0.18.1) stable; urgency=medium
* mlz: Zapf fix unit handling and small errors * mlz: Zapf fix unit handling and small errors
* mlz: entangle fix limit check * mlz: entangle fix limit check
-- Alexander Zaft <jenkins@frm2.tum.de> Wed, 24 Jan 2024 14:59:21 +0100 -- Alexander Zaft <jenkins@frm2.tum.de> Wed, 24 Jan 2024 14:59:21 +0100
frappy-core (0.18.0) focal; urgency=medium frappy-core (0.18.0) stable; urgency=medium
[ Alexander Zaft ] [ Alexander Zaft ]
* Add shutdownModule function * Add shutdownModule function
@ -341,7 +416,7 @@ frappy-core (0.18.0) focal; urgency=medium
-- Alexander Zaft <jenkins@frm2.tum.de> Wed, 17 Jan 2024 12:35:00 +0100 -- Alexander Zaft <jenkins@frm2.tum.de> Wed, 17 Jan 2024 12:35:00 +0100
frappy-core (0.17.13) focal; urgency=medium frappy-core (0.17.13) stable; urgency=medium
[ Alexander Zaft ] [ Alexander Zaft ]
* add egg-info to gitignore * add egg-info to gitignore
@ -362,7 +437,7 @@ frappy-core (0.17.13) focal; urgency=medium
-- Alexander Zaft <jenkins@frm2.tum.de> Tue, 20 Jun 2023 14:38:00 +0200 -- Alexander Zaft <jenkins@frm2.tum.de> Tue, 20 Jun 2023 14:38:00 +0200
frappy-core (0.17.12) focal; urgency=medium frappy-core (0.17.12) stable; urgency=medium
[ Alexander Zaft ] [ Alexander Zaft ]
* Warn about duplicate module definitions in a file * Warn about duplicate module definitions in a file
@ -387,7 +462,7 @@ frappy-core (0.17.12) focal; urgency=medium
-- Alexander Zaft <jenkins@frm2.tum.de> Tue, 13 Jun 2023 06:51:27 +0200 -- Alexander Zaft <jenkins@frm2.tum.de> Tue, 13 Jun 2023 06:51:27 +0200
frappy-core (0.17.11) focal; urgency=medium frappy-core (0.17.11) stable; urgency=medium
[ Alexander Zaft ] [ Alexander Zaft ]
* Add __format__ to EnumMember * Add __format__ to EnumMember
@ -460,7 +535,7 @@ frappy-core (0.17.11) focal; urgency=medium
-- Alexander Zaft <jenkins@frm2.tum.de> Thu, 25 May 2023 09:38:24 +0200 -- Alexander Zaft <jenkins@frm2.tum.de> Thu, 25 May 2023 09:38:24 +0200
frappy-core (0.17.10) focal; urgency=medium frappy-core (0.17.10) stable; urgency=medium
* Change leftover %-logging calls to lazy * Change leftover %-logging calls to lazy
* Convert formatting automatically to f-strings * Convert formatting automatically to f-strings
@ -472,25 +547,25 @@ frappy-core (0.17.10) focal; urgency=medium
-- Alexander Zaft <jenkins@frm2.tum.de> Wed, 19 Apr 2023 14:32:52 +0200 -- Alexander Zaft <jenkins@frm2.tum.de> Wed, 19 Apr 2023 14:32:52 +0200
frappy-core (0.17.9) focal; urgency=medium frappy-core (0.17.9) stable; urgency=medium
* interactive client: avoid messing up the input line * interactive client: avoid messing up the input line
-- Markus Zolliker <jenkins@frm2.tum.de> Tue, 11 Apr 2023 16:09:03 +0200 -- Markus Zolliker <jenkins@frm2.tum.de> Tue, 11 Apr 2023 16:09:03 +0200
frappy-core (0.17.8) focal; urgency=medium frappy-core (0.17.8) stable; urgency=medium
* Debian: Fix typo * Debian: Fix typo
-- Jens Krüger <jenkins@frm2.tum.de> Wed, 05 Apr 2023 07:20:25 +0200 -- Jens Krüger <jenkins@frm2.tum.de> Wed, 05 Apr 2023 07:20:25 +0200
frappy-core (0.17.7) focal; urgency=medium frappy-core (0.17.7) stable; urgency=medium
* Debian: add pyqtgraph dependency * Debian: add pyqtgraph dependency
-- Jens Krüger <jenkins@frm2.tum.de> Wed, 05 Apr 2023 07:07:24 +0200 -- Jens Krüger <jenkins@frm2.tum.de> Wed, 05 Apr 2023 07:07:24 +0200
frappy-core (0.17.6) focal; urgency=medium frappy-core (0.17.6) stable; urgency=medium
[ Alexander Zaft ] [ Alexander Zaft ]
* gui: show parameter properties again * gui: show parameter properties again
@ -510,25 +585,25 @@ frappy-core (0.17.6) focal; urgency=medium
-- Alexander Zaft <jenkins@frm2.tum.de> Tue, 04 Apr 2023 08:42:26 +0200 -- Alexander Zaft <jenkins@frm2.tum.de> Tue, 04 Apr 2023 08:42:26 +0200
frappy-core (0.17.5) focal; urgency=medium frappy-core (0.17.5) stable; urgency=medium
* Fix generator * Fix generator
-- Alexander Zaft <jenkins@frm2.tum.de> Wed, 22 Mar 2023 12:32:06 +0100 -- Alexander Zaft <jenkins@frm2.tum.de> Wed, 22 Mar 2023 12:32:06 +0100
frappy-core (0.17.4) focal; urgency=medium frappy-core (0.17.4) stable; urgency=medium
* Fix entangle integration bugs * Fix entangle integration bugs
-- Alexander Zaft <jenkins@frm2.tum.de> Wed, 22 Mar 2023 11:44:34 +0100 -- Alexander Zaft <jenkins@frm2.tum.de> Wed, 22 Mar 2023 11:44:34 +0100
frappy-core (0.17.3) focal; urgency=medium frappy-core (0.17.3) stable; urgency=medium
* UNRELEASED * UNRELEASED
-- Alexander Zaft <jenkins@frm2.tum.de> Tue, 21 Mar 2023 15:55:09 +0100 -- Alexander Zaft <jenkins@frm2.tum.de> Tue, 21 Mar 2023 15:55:09 +0100
frappy-core (0.17.2) focal; urgency=medium frappy-core (0.17.2) stable; urgency=medium
[ Alexander Zaft ] [ Alexander Zaft ]
* Fix Simulation and Proxy * Fix Simulation and Proxy
@ -665,7 +740,7 @@ frappy-core (0.17.2) focal; urgency=medium
-- Alexander Zaft <jenkins@frm2.tum.de> Tue, 21 Mar 2023 15:49:06 +0100 -- Alexander Zaft <jenkins@frm2.tum.de> Tue, 21 Mar 2023 15:49:06 +0100
frappy-core (0.17.1) focal; urgency=medium frappy-core (0.17.1) stable; urgency=medium
[ Georg Brandl ] [ Georg Brandl ]
* gitignore: ignore demo PID file * gitignore: ignore demo PID file
@ -684,7 +759,7 @@ frappy-core (0.17.1) focal; urgency=medium
-- Georg Brandl <jenkins@frm2.tum.de> Tue, 21 Feb 2023 17:44:56 +0100 -- Georg Brandl <jenkins@frm2.tum.de> Tue, 21 Feb 2023 17:44:56 +0100
frappy-core (0.17.0) focal; urgency=medium frappy-core (0.17.0) stable; urgency=medium
[ Alexander Zaft ] [ Alexander Zaft ]
* Rework GUI. * Rework GUI.
@ -695,37 +770,37 @@ frappy-core (0.17.0) focal; urgency=medium
-- Alexander Zaft <jenkins@frm2.tum.de> Tue, 21 Feb 2023 13:52:17 +0100 -- Alexander Zaft <jenkins@frm2.tum.de> Tue, 21 Feb 2023 13:52:17 +0100
frappy-core (0.16.1) focal; urgency=medium frappy-core (0.16.1) stable; urgency=medium
* UNRELEASED * UNRELEASED
-- Georg Brandl <jenkins@frm2.tum.de> Tue, 21 Feb 2023 08:44:28 +0100 -- Georg Brandl <jenkins@frm2.tum.de> Tue, 21 Feb 2023 08:44:28 +0100
frappy-core (0.16.4) focal; urgency=medium frappy-core (0.16.4) stable; urgency=medium
* UNRELEASED * UNRELEASED
-- Georg Brandl <jenkins@frm2.tum.de> Tue, 21 Feb 2023 08:09:20 +0100 -- Georg Brandl <jenkins@frm2.tum.de> Tue, 21 Feb 2023 08:09:20 +0100
frappy-core (0.16.3) focal; urgency=medium frappy-core (0.16.3) stable; urgency=medium
* UNRELEASED * UNRELEASED
-- Georg Brandl <jenkins@frm2.tum.de> Tue, 21 Feb 2023 08:00:15 +0100 -- Georg Brandl <jenkins@frm2.tum.de> Tue, 21 Feb 2023 08:00:15 +0100
frappy-core (0.16.2) focal; urgency=medium frappy-core (0.16.2) stable; urgency=medium
* gui: move icon resources for the cfg editor to its subdirectory * gui: move icon resources for the cfg editor to its subdirectory
-- Georg Brandl <jenkins@frm2.tum.de> Tue, 21 Feb 2023 07:50:13 +0100 -- Georg Brandl <jenkins@frm2.tum.de> Tue, 21 Feb 2023 07:50:13 +0100
frappy-core (0.16.1) focal; urgency=medium frappy-core (0.16.1) stable; urgency=medium
* add frappy-cli to package * add frappy-cli to package
-- Enrico Faulhaber <jenkins@frm2.tum.de> Mon, 20 Feb 2023 17:17:23 +0100 -- Enrico Faulhaber <jenkins@frm2.tum.de> Mon, 20 Feb 2023 17:17:23 +0100
frappy-core (0.16.0) focal; urgency=medium frappy-core (0.16.0) stable; urgency=medium
[ Enrico Faulhaber ] [ Enrico Faulhaber ]
* fix sorce package name * fix sorce package name
@ -787,7 +862,7 @@ frappy-core (0.16.0) focal; urgency=medium
-- Enrico Faulhaber <jenkins@frm2.tum.de> Mon, 20 Feb 2023 16:15:10 +0100 -- Enrico Faulhaber <jenkins@frm2.tum.de> Mon, 20 Feb 2023 16:15:10 +0100
frappy-core (0.15.0) focal; urgency=medium frappy-core (0.15.0) stable; urgency=medium
[ Björn Pedersen ] [ Björn Pedersen ]
* Remove iohandler left-overs from docs * Remove iohandler left-overs from docs
@ -817,7 +892,7 @@ frappy-core (0.15.0) focal; urgency=medium
-- Björn Pedersen <jenkins@frm2.tum.de> Thu, 10 Nov 2022 14:46:01 +0100 -- Björn Pedersen <jenkins@frm2.tum.de> Thu, 10 Nov 2022 14:46:01 +0100
secop-core (0.14.3) focal; urgency=medium secop-core (0.14.3) stable; urgency=medium
[ Enrico Faulhaber ] [ Enrico Faulhaber ]
* change repo to secop/frappy * change repo to secop/frappy
@ -833,13 +908,13 @@ secop-core (0.14.3) focal; urgency=medium
-- Enrico Faulhaber <jenkins@frm2.tum.de> Thu, 03 Nov 2022 13:51:52 +0100 -- Enrico Faulhaber <jenkins@frm2.tum.de> Thu, 03 Nov 2022 13:51:52 +0100
secop-core (0.14.2) focal; urgency=medium secop-core (0.14.2) stable; urgency=medium
* systemd generator: adapt to changed config API * systemd generator: adapt to changed config API
-- Georg Brandl <jenkins@frm2.tum.de> Thu, 20 Oct 2022 15:38:45 +0200 -- Georg Brandl <jenkins@frm2.tum.de> Thu, 20 Oct 2022 15:38:45 +0200
secop-core (0.14.1) focal; urgency=medium secop-core (0.14.1) stable; urgency=medium
[ Markus Zolliker ] [ Markus Zolliker ]
* secop_psi.entangle.AnalogInput: fix main value * secop_psi.entangle.AnalogInput: fix main value
@ -851,7 +926,7 @@ secop-core (0.14.1) focal; urgency=medium
-- Markus Zolliker <jenkins@frm2.tum.de> Thu, 20 Oct 2022 14:04:07 +0200 -- Markus Zolliker <jenkins@frm2.tum.de> Thu, 20 Oct 2022 14:04:07 +0200
secop-core (0.14.0) focal; urgency=medium secop-core (0.14.0) stable; urgency=medium
* add simple interactive python client * add simple interactive python client
* fix undefined status in softcal * fix undefined status in softcal
@ -865,7 +940,7 @@ secop-core (0.14.0) focal; urgency=medium
-- Markus Zolliker <jenkins@frm2.tum.de> Wed, 19 Oct 2022 11:31:50 +0200 -- Markus Zolliker <jenkins@frm2.tum.de> Wed, 19 Oct 2022 11:31:50 +0200
secop-core (0.13.1) focal; urgency=medium secop-core (0.13.1) stable; urgency=medium
[ Markus Zolliker ] [ Markus Zolliker ]
* an enum with value 0 should be interpreted as False * an enum with value 0 should be interpreted as False
@ -876,7 +951,7 @@ secop-core (0.13.1) focal; urgency=medium
-- Markus Zolliker <jenkins@jenkins02.admin.frm2.tum.de> Tue, 02 Aug 2022 15:31:52 +0200 -- Markus Zolliker <jenkins@jenkins02.admin.frm2.tum.de> Tue, 02 Aug 2022 15:31:52 +0200
secop-core (0.13.0) focal; urgency=medium secop-core (0.13.0) stable; urgency=medium
[ Georg Brandl ] [ Georg Brandl ]
* debian: fix email addresses in changelog * debian: fix email addresses in changelog
@ -939,13 +1014,13 @@ secop-core (0.13.0) focal; urgency=medium
-- Georg Brandl <jenkins@frm2.tum.de> Tue, 02 Aug 2022 09:47:06 +0200 -- Georg Brandl <jenkins@frm2.tum.de> Tue, 02 Aug 2022 09:47:06 +0200
secop-core (0.12.4) focal; urgency=medium secop-core (0.12.4) stable; urgency=medium
* fix command inheritance * fix command inheritance
-- Markus Zolliker <jenkins@jenkins01.admin.frm2.tum.de> Thu, 11 Nov 2021 16:21:19 +0100 -- Markus Zolliker <jenkins@jenkins01.admin.frm2.tum.de> Thu, 11 Nov 2021 16:21:19 +0100
secop-core (0.12.3) focal; urgency=medium secop-core (0.12.3) stable; urgency=medium
[ Georg Brandl ] [ Georg Brandl ]
* Makefile: fix docker image * Makefile: fix docker image
@ -968,7 +1043,7 @@ secop-core (0.12.3) focal; urgency=medium
-- Georg Brandl <jenkins@jenkins01.admin.frm2.tum.de> Wed, 10 Nov 2021 16:33:19 +0100 -- Georg Brandl <jenkins@jenkins01.admin.frm2.tum.de> Wed, 10 Nov 2021 16:33:19 +0100
secop-core (0.12.2) focal; urgency=medium secop-core (0.12.2) stable; urgency=medium
[ Markus Zolliker ] [ Markus Zolliker ]
* fix issue with new syntax in simulation * fix issue with new syntax in simulation
@ -980,13 +1055,13 @@ secop-core (0.12.2) focal; urgency=medium
-- Markus Zolliker <jenkins@jenkins01.admin.frm2.tum.de> Tue, 18 May 2021 10:29:17 +0200 -- Markus Zolliker <jenkins@jenkins01.admin.frm2.tum.de> Tue, 18 May 2021 10:29:17 +0200
secop-core (0.12.1) focal; urgency=medium secop-core (0.12.1) stable; urgency=medium
* remove secop-console from debian *.install file * remove secop-console from debian *.install file
-- Enrico Faulhaber <jenkins@jenkins02.admin.frm2.tum.de> Tue, 04 May 2021 09:42:53 +0200 -- Enrico Faulhaber <jenkins@jenkins02.admin.frm2.tum.de> Tue, 04 May 2021 09:42:53 +0200
secop-core (0.12.0) focal; urgency=medium secop-core (0.12.0) stable; urgency=medium
[ Markus Zolliker ] [ Markus Zolliker ]
* make datatypes immutable * make datatypes immutable

1
debian/compat vendored
View File

@ -1 +0,0 @@
11

4
debian/control vendored
View File

@ -2,7 +2,7 @@ Source: frappy-core
Section: contrib/misc Section: contrib/misc
Priority: optional Priority: optional
Maintainer: Enrico Faulhaber <enrico.faulhaber@frm2.tum.de> Maintainer: Enrico Faulhaber <enrico.faulhaber@frm2.tum.de>
Build-Depends: debhelper (>= 11~), Build-Depends: debhelper-compat (= 13),
dh-python, dh-python,
python3 (>=3.6), python3 (>=3.6),
python3-all, python3-all,
@ -20,7 +20,7 @@ Build-Depends: debhelper (>= 11~),
git, git,
markdown, markdown,
python3-daemon python3-daemon
Standards-Version: 4.1.4 Standards-Version: 4.6.2
X-Python3-Version: >= 3.6 X-Python3-Version: >= 3.6
Package: frappy-core Package: frappy-core

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}',)
self.callback(None, 'handleError', e) try:
self.callback(None, 'handleError', e)
except Exception:
pass
continue continue
try: try:
key = action, ident key = action, ident

View File

@ -498,7 +498,7 @@ class Console(code.InteractiveConsole):
history = None history = None
if readline: if readline:
try: try:
history = expanduser(f'~/.frappy-{name}-history') history = expanduser(f'~/.config/frappy/{name}-history')
readline.read_history_file(history) readline.read_history_file(history)
except FileNotFoundError: except FileNotFoundError:
pass pass
@ -538,10 +538,10 @@ def init(*nodes):
return success return success
def interact(usage_tail=''): def interact(usage_tail='', appname=None):
empty = '_c0' not in clientenv.namespace empty = '_c0' not in clientenv.namespace
print(USAGE.format( print(USAGE.format(
client_name='cli' if empty else '_c0', client_name='cli' if empty else '_c0',
client_assign="\ncli = Client('localhost:5000')\n" if empty else '', client_assign="\ncli = Client('localhost:5000')\n" if empty else '',
tail=usage_tail)) tail=usage_tail))
Console() Console(name=f'cli-{appname}' if appname else 'cli')

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,13 +88,16 @@ 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 = []
self.cls = cls self.cls = cls
def add(self, *args, **kwds): def add(self, *args, **kwds):
self.list.append(self.cls(*args, **kwds)) result = self.cls(*args, **kwds)
self.list.append(result)
return result
def append(self, mod): def append(self, mod):
self.list.append(mod) self.list.append(mod)
@ -120,12 +126,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 +180,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 +189,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,7 +106,8 @@ def get_file_paths(widget, open_file=True):
def get_modules(): def get_modules():
modules = {} modules = {}
generalConfig.init() if not generalConfig.initialized:
generalConfig.init()
base_path = generalConfig.basedir base_path = generalConfig.basedir
# pylint: disable=too-many-nested-blocks # pylint: disable=too-many-nested-blocks
for dirname in listdir(base_path): for dirname in listdir(base_path):
@ -157,7 +158,8 @@ 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 = []
generalConfig.init() if not generalConfig.initialized:
generalConfig.init()
interface_path = path.join(generalConfig.basedir, 'frappy', interface_path = path.join(generalConfig.basedir, 'frappy',
'protocol', 'interface') 'protocol', 'interface')
for filename in listdir(interface_path): for filename in listdir(interface_path):

View File

@ -27,7 +27,6 @@
<property name="font"> <property name="font">
<font> <font>
<pointsize>18</pointsize> <pointsize>18</pointsize>
<weight>75</weight>
<bold>true</bold> <bold>true</bold>
</font> </font>
</property> </property>

View File

@ -21,7 +21,6 @@
<property name="font"> <property name="font">
<font> <font>
<pointsize>12</pointsize> <pointsize>12</pointsize>
<weight>75</weight>
<bold>true</bold> <bold>true</bold>
<underline>true</underline> <underline>true</underline>
</font> </font>

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

50
frappy/lib/units.py Normal file
View File

@ -0,0 +1,50 @@
# *****************************************************************************
#
# 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>
#
# *****************************************************************************
"""handling of prefixes of physical units"""
import re
import prefixed
prefixed.SI_MAGNITUDE['u'] = 1e-6 # accept 'u' as replacement for 'µ'
class NumberWithUnit:
def __init__(self, *units):
pfx = "|".join(prefixed.SI_MAGNITUDE)
unt = "|".join(units)
self.units = units
self.pattern = re.compile(rf'\s*([+-]?\d*\.?\d*(?:[eE][+-]?\d+)?\s*(?:{pfx})?)({unt})\s*$')
def parse(self, value):
"""parse and return number and value"""
match = self.pattern.match(value)
if not match:
raise ValueError(f'{value!r} can not be interpreted as a number with unit {",".join(self.units)}')
number, unit = match.groups()
return prefixed.Float(number), unit
def getnum(self, value):
"""parse and return value only"""
return self.parse(value)[0]
def format_with_unit(value, unit='', digits=3):
return f'{prefixed.Float(value):.{digits}H}{unit}'

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
@ -59,7 +60,6 @@ class HasAccessibles(HasProperties):
(so the dispatcher will get notified of changed values) (so the dispatcher will get notified of changed values)
""" """
isWrapped = False isWrapped = False
checkedMethods = set()
@classmethod @classmethod
def __init_subclass__(cls): # pylint: disable=too-many-branches def __init_subclass__(cls): # pylint: disable=too-many-branches
@ -77,7 +77,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,17 +97,15 @@ 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
cls.wrappedAttributes = {'isWrapped': True} cls.wrappedAttributes = {'isWrapped': True}
@ -115,8 +113,8 @@ class HasAccessibles(HasProperties):
wrapped_name = '_' + cls.__name__ wrapped_name = '_' + cls.__name__
for pname, pobj in accessibles.items(): for pname, pobj in accessibles.items():
# wrap of reading/writing funcs # wrap of reading/writing funcs
if not isinstance(pobj, Parameter): if not isinstance(pobj, Parameter) or pobj.optional:
# nothing to do for Commands # nothing to do for Commands and optional parameters
continue continue
rname = 'read_' + pname rname = 'read_' + pname
@ -189,10 +187,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
@ -202,16 +198,15 @@ class HasAccessibles(HasProperties):
new_wfunc.__module__ = cls.__module__ new_wfunc.__module__ = cls.__module__
cls.wrappedAttributes[wname] = new_wfunc cls.wrappedAttributes[wname] = new_wfunc
cls.checkedMethods.update(cls.wrappedAttributes)
# check for programming errors # check for programming errors
for attrname in dir(cls): for attrname, func in cls.__dict__.items():
prefix, _, pname = attrname.partition('_') prefix, _, pname = attrname.partition('_')
if not pname: if not pname:
continue continue
if prefix == 'do': if prefix == 'do':
raise ProgrammingError(f'{cls.__name__!r}: old style command {attrname!r} not supported anymore') raise ProgrammingError(f'{cls.__name__!r}: old style command {attrname!r} not supported anymore')
if prefix in ('read', 'write') and attrname not in cls.checkedMethods: if (prefix in ('read', 'write') and attrname not in cls.wrappedAttributes
and not hasattr(func, 'poll')): # may be a handler, which always has a poll attribute
raise ProgrammingError(f'{cls.__name__}.{attrname} defined, but {pname!r} is no parameter') raise ProgrammingError(f'{cls.__name__}.{attrname} defined, but {pname!r} is no parameter')
try: try:
@ -322,10 +317,13 @@ 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
triggerPoll = None # trigger event for polls. used on io modules and modules without io triggerPoll = None # trigger event for polls. used on io modules and modules without io
__poller = None # the poller thread, if used
def __init__(self, name, logger, cfgdict, srv): def __init__(self, name, logger, cfgdict, srv):
# remember the secnode for interacting with other modules and the # remember the secnode for interacting with other modules and the
@ -391,6 +389,8 @@ class Module(HasAccessibles):
accessibles = self.accessibles accessibles = self.accessibles
self.accessibles = {} self.accessibles = {}
for aname, aobj in accessibles.items(): for aname, aobj in accessibles.items():
if aobj.optional:
continue
# make a copy of the Parameter/Command object # make a copy of the Parameter/Command object
aobj = aobj.copy() aobj = aobj.copy()
acfg = cfgdict.pop(aname, None) acfg = cfgdict.pop(aname, None)
@ -451,9 +451,12 @@ class Module(HasAccessibles):
self.parameters[name] = accessible self.parameters[name] = accessible
if isinstance(accessible, Command): if isinstance(accessible, Command):
self.commands[name] = accessible self.commands[name] = accessible
if cfg: if cfg is not None:
try: try:
for propname, propvalue in cfg.items(): for propname, propvalue in cfg.items():
if propname in {'value', 'default', 'constant'}:
# these properties have ValueType(), but should be checked for datatype
accessible.datatype(cfg[propname])
accessible.setProperty(propname, propvalue) accessible.setProperty(propname, propvalue)
except KeyError: except KeyError:
self.errors.append(f"'{name}' has no property '{propname}'") self.errors.append(f"'{name}' has no property '{propname}'")
@ -517,13 +520,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
@ -610,7 +613,7 @@ class Module(HasAccessibles):
# we do not need self.errors any longer. should we delete it? # we do not need self.errors any longer. should we delete it?
# del self.errors # del self.errors
if self.polledModules: if self.polledModules:
mkthread(self.__pollThread, self.polledModules, start_events.get_trigger()) self.__poller = mkthread(self.__pollThread, self.polledModules, start_events.get_trigger())
self.startModuleDone = True self.startModuleDone = True
def initialReads(self): def initialReads(self):
@ -623,8 +626,28 @@ class Module(HasAccessibles):
all parameters are polled once all parameters are polled once
""" """
def stopPollThread(self):
"""trigger the poll thread to stop
this is called on shutdown
"""
if self.__poller:
self.polledModules.clear()
self.triggerPoll.set()
def joinPollThread(self, timeout):
"""wait for poll thread to finish
if the wait time exceeds <timeout> seconds, return and log a warning
"""
if self.__poller:
self.stopPollThread()
self.__poller.join(timeout)
if self.__poller.is_alive():
self.log.warning('can not stop poller')
def shutdownModule(self): def shutdownModule(self):
"""called when the sever shuts down """called when the server shuts down
any cleanup-work should be performed here, like closing threads and any cleanup-work should be performed here, like closing threads and
saving data. saving data.
@ -727,13 +750,14 @@ class Module(HasAccessibles):
if not polled_modules: # no polls needed - exit thread if not polled_modules: # no polls needed - exit thread
return return
to_poll = () to_poll = ()
while True: while modules: # modules will be cleared on shutdown
now = time.time() now = time.time()
wait_time = 999 wait_time = 999
for mobj in modules: for mobj in modules:
pinfo = mobj.pollInfo pinfo = mobj.pollInfo
wait_time = min(pinfo.last_main + pinfo.interval - now, wait_time, if pinfo:
pinfo.last_slow + mobj.slowinterval - now) wait_time = min(pinfo.last_main + pinfo.interval - now, wait_time,
pinfo.last_slow + mobj.slowinterval - now)
if wait_time > 0 and not to_poll: if wait_time > 0 and not to_poll:
# nothing to do # nothing to do
self.triggerPoll.wait(wait_time) self.triggerPoll.wait(wait_time)
@ -742,7 +766,7 @@ class Module(HasAccessibles):
# call doPoll of all modules where due # call doPoll of all modules where due
for mobj in modules: for mobj in modules:
pinfo = mobj.pollInfo pinfo = mobj.pollInfo
if now > pinfo.last_main + pinfo.interval: if pinfo and now > pinfo.last_main + pinfo.interval:
try: try:
pinfo.last_main = (now // pinfo.interval) * pinfo.interval pinfo.last_main = (now // pinfo.interval) * pinfo.interval
except ZeroDivisionError: except ZeroDivisionError:
@ -762,7 +786,7 @@ class Module(HasAccessibles):
# collect due slow polls # collect due slow polls
for mobj in modules: for mobj in modules:
pinfo = mobj.pollInfo pinfo = mobj.pollInfo
if now > pinfo.last_slow + mobj.slowinterval: if pinfo and now > pinfo.last_slow + mobj.slowinterval:
to_poll.extend(pinfo.polled_parameters) to_poll.extend(pinfo.polled_parameters)
pinfo.last_slow = (now // mobj.slowinterval) * mobj.slowinterval pinfo.last_slow = (now // mobj.slowinterval) * mobj.slowinterval
if to_poll: if to_poll:

View File

@ -47,6 +47,7 @@ class Accessible(HasProperties):
""" """
ownProperties = None ownProperties = None
optional = False
def init(self, kwds): def init(self, kwds):
# do not use self.propertyValues.update here, as no invalid values should be # do not use self.propertyValues.update here, as no invalid values should be
@ -96,6 +97,8 @@ class Accessible(HasProperties):
props = [] props = []
for k, v in sorted(self.propertyValues.items()): for k, v in sorted(self.propertyValues.items()):
props.append(f'{k}={v!r}') props.append(f'{k}={v!r}')
if self.optional:
props.append('optional=True')
return f"{self.__class__.__name__}({', '.join(props)})" return f"{self.__class__.__name__}({', '.join(props)})"
def fixExport(self): def fixExport(self):
@ -191,8 +194,9 @@ class Parameter(Accessible):
readerror = None readerror = None
omit_unchanged_within = 0 omit_unchanged_within = 0
def __init__(self, description=None, datatype=None, inherit=True, **kwds): def __init__(self, description=None, datatype=None, inherit=True, optional=False, **kwds):
super().__init__() super().__init__()
self.optional = optional
if 'poll' in kwds and generalConfig.tolerate_poll_property: if 'poll' in kwds and generalConfig.tolerate_poll_property:
kwds.pop('poll') kwds.pop('poll')
if datatype is None: if datatype is None:
@ -226,10 +230,16 @@ class Parameter(Accessible):
def __get__(self, instance, owner): def __get__(self, instance, owner):
if instance is None: if instance is None:
return self return self
return instance.parameters[self.name].value try:
return instance.parameters[self.name].value
except KeyError:
raise ProgrammingError(f'optional parameter {self.name} it is not implemented') from None
def __set__(self, obj, value): def __set__(self, obj, value):
obj.announceUpdate(self.name, value) try:
obj.announceUpdate(self.name, value)
except KeyError:
raise ProgrammingError(f'optional parameter {self.name} it is not implemented') from None
def __set_name__(self, owner, name): def __set_name__(self, owner, name):
self.name = name self.name = name
@ -259,8 +269,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
@ -358,9 +376,6 @@ class Command(Accessible):
* True: exported, name automatic. * True: exported, name automatic.
* a string: exported with custom name''', OrType(BoolType(), StringType()), * a string: exported with custom name''', OrType(BoolType(), StringType()),
export=False, default=True) export=False, default=True)
# optional = Property(
# '[internal] is the command optional to implement? (vs. mandatory)', BoolType(),
# export=False, default=False, settable=False)
datatype = Property( datatype = Property(
"datatype of the command, auto generated from 'argument' and 'result'", "datatype of the command, auto generated from 'argument' and 'result'",
DataTypeType(), extname='datainfo', export='always') DataTypeType(), extname='datainfo', export='always')
@ -376,8 +391,9 @@ class Command(Accessible):
func = None func = None
def __init__(self, argument=False, *, result=None, inherit=True, **kwds): def __init__(self, argument=False, *, result=None, inherit=True, optional=False, **kwds):
super().__init__() super().__init__()
self.optional = optional
if 'datatype' in kwds: if 'datatype' in kwds:
# self.init will complain about invalid keywords except 'datatype', as this is a property # self.init will complain about invalid keywords except 'datatype', as this is a property
raise ProgrammingError("Command() got an invalid keyword 'datatype'") raise ProgrammingError("Command() got an invalid keyword 'datatype'")
@ -403,8 +419,8 @@ class Command(Accessible):
def __set_name__(self, owner, name): def __set_name__(self, owner, name):
self.name = name self.name = name
if self.func is None: if self.func is None and not self.optional:
raise ProgrammingError(f'Command {owner.__name__}.{name} must be used as a method decorator') raise ProgrammingError(f'Command {owner.__name__}.{name} must be optional or used as a method decorator')
self.fixExport() self.fixExport()
self.datatype = CommandType(self.argument, self.result) self.datatype = CommandType(self.argument, self.result)
@ -462,7 +478,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 +581,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,26 +77,30 @@ 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:
if isinstance(uris, str) or not all(isinstance(v, str) for v in uris) or uri:
raise TypeError()
elif isinstance(uri, str):
uris = [uri]
else:
raise TypeError()
except Exception as e:
raise frappy.errors.ConfigError("a router needs either 'node' as a string'"
"' or 'nodes' as a list of strings") from e
super().__init__(name, logger, options, srv) super().__init__(name, logger, options, srv)
if uri: self.nodes = [SecopClient(uri, logger.getChild(f'routed{i}'), self) for i, uri in enumerate(uris)]
self.nodes = [SecopClient(uri, logger.getChild('routed'), self)]
self.singlenode = self.nodes[0]
else:
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:
node.register_callback(None, node.updateEvent, node.descriptiveDataChange, node.nodeStateChange) node.register_callback(None, node.updateEvent, node.descriptiveDataChange, node.nodeStateChange)
@ -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

@ -102,7 +102,6 @@ class Handler:
"""create the wrapped read_* or write_* methods""" """create the wrapped read_* or write_* methods"""
# at this point, this 'method_names' entry is no longer used -> delete # at this point, this 'method_names' entry is no longer used -> delete
self.method_names.discard((self.func.__module__, self.func.__qualname__)) self.method_names.discard((self.func.__module__, self.func.__qualname__))
owner.checkedMethods.add(name)
for key in self.keys: for key in self.keys:
wrapped = self.wrap(key) wrapped = self.wrap(key)
method_name = self.prefix + key method_name = self.prefix + key

View File

@ -19,6 +19,7 @@
# #
# ***************************************************************************** # *****************************************************************************
import time
import traceback import traceback
from collections import OrderedDict from collections import OrderedDict
@ -54,8 +55,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."""
@ -246,6 +256,15 @@ class SecNode:
def shutdown_modules(self): def shutdown_modules(self):
"""Call 'shutdownModule' for all modules.""" """Call 'shutdownModule' for all modules."""
# stop pollers
for mod in self.modules.values():
mod.stopPollThread()
# do not yet join here, as we want to wait in parallel
now = time.time()
deadline = now + 0.5 # should be long enough for most read functions to finish
for mod in self.modules.values():
mod.joinPollThread(max(0, deadline - now))
now = time.time()
for name in self._getSortedModules(): for name in self._getSortedModules():
self.modules[name].shutdownModule() self.modules[name].shutdownModule()

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,17 +175,20 @@ 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://"
for interface in interfaces: interfaces = [iface if '://' in iface else f'tcp://{iface}' for iface in interfaces]
opts = {'uri': interface} with lock:
t = mkthread( for interface in interfaces:
self._interfaceThread, opts = {'uri': interface}
opts, t = mkthread(
lock, self._interfaceThread,
failed, opts,
interfaces_started.get_trigger(), lock,
) failed,
iface_threads.append(t) interfaces,
interfaces_started.get_trigger(),
)
iface_threads.append(t)
if not interfaces_started.wait(): if not interfaces_started.wait():
for iface in interfaces: for iface in interfaces:
if iface not in failed and iface not in self.interfaces: if iface not in failed and iface not in self.interfaces:
@ -192,15 +199,33 @@ 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
for t in iface_threads: # interruptible by the signal handler, so loop and check
t.join() # periodically whether the interfaces are still running.
while True:
time.sleep(1)
if not interfaces:
break
else:
for t in iface_threads:
t.join()
while failed: while failed:
iface, err = failed.popitem() iface, err = failed.popitem()
@ -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,9 +273,12 @@ 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
self.log.info(f'stopped {iface}') else:
with lock:
interfaces.remove(iface)
self.log.info(f'stopped {iface}')
def _processCfg(self): def _processCfg(self):
"""Processes the module configuration. """Processes the module configuration.
@ -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

@ -142,4 +142,5 @@ class SimDrivable(SimReadable, Drivable):
@Command @Command
def stop(self): def stop(self):
"""set target to value"""
self.target = self.value self.target = self.value

View File

@ -215,7 +215,10 @@ class HasStates:
self.read_status() self.read_status()
if fast_poll: if fast_poll:
sm.reset_fast_poll = True sm.reset_fast_poll = True
self.setFastPoll(True) if fast_poll is True:
self.setFastPoll(True)
else:
self.setFastPoll(True, fast_poll)
self.pollInfo.trigger(True) # trigger poller self.pollInfo.trigger(True) # trigger poller
def stop_machine(self, stopped_status=(IDLE, 'stopped')): def stop_machine(self, stopped_status=(IDLE, 'stopped')):

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,54 @@ 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
RMS_TO_VPP = 2 * np.sqrt(2)
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 Timer:
def __init__(self):
self.data = [(time.time(), 'start')]
def __call__(self, text=''):
now = time.time()
prev = self.data[-1][0]
self.data.append((now, text))
return now - prev
def summary(self):
return ' '.join(f'{txt} {tim:.3f}' for tim, txt in self.data[1:])
def show(self):
first = prev = self.data[0][0]
print('---', first)
for tim, txt in self.data[1:]:
print(f'{(tim - first) * 1000:9.3f} {(tim - prev) * 1000:9.3f} ms {txt}')
prev = tim
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
data = None
busy = False
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,89 +97,84 @@ 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) print('number of ADQs must be 1, not %d' % n_of_adq)
print('it seems the ADQ was not properly closed')
print('please try again or reboot')
sys.exit(0)
atexit.register(self.deletecu)
signal.signal(signal.SIGTERM, lambda *_: sys.exit(0))
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') out = [f'Connected to ADQ #1, FPGA Revision: {revision[0]}']
# Print revision information if revision[1]:
print('FPGA Revision: {}'.format(revision[0])) out.append('Local copy')
if (revision[1]): else:
print('Local copy') if revision[2]:
else : out.append('SVN Managed - Mixed Revision')
print('SVN Managed') else:
if (revision[2]): out.append('SVN Updated')
print('Mixed Revision') print(', '.join(out))
else : ADQAPI.ADQ_SetClockSource(self.adq_cu, self.adq_num, ADQ_CLOCK_EXT_REF)
print('SVN Updated')
print('')
ADQ_CLOCK_INT_INTREF = 0 #internal clock source
ADQ_CLOCK_EXT_REF = 1 #internal clock source, external reference
ADQ_CLOCK_EXT_CLOCK = 2 #External clock source
ADQAPI.ADQ_SetClockSource(self.adq_cu, self.adq_num, ADQ_CLOCK_EXT_REF);
########################## ##########################
# 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): # proabably the folloiwng is wrong.
print('ADQ_SetLvlTrigEdge failed.') # print("CHANNEL:" + str(ct.c_int(ADQAPI.ADQ_GetLvlTrigChannel(self.adq_cu, self.adq_num))))
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))))
self.setup_target_buffers()
def setup_target_buffers(self): def init(self, samples_per_record=None, number_of_records=None):
"""initialize dimensions and store result object"""
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)()
def deletecu(self): def deletecu(self):
# Only disarm trigger after data is collected cu = self.__dict__.pop('adq_cu', None)
ADQAPI.ADQ_DisarmTrigger(self.adq_cu, self.adq_num) if cu is None:
ADQAPI.ADQ_MultiRecordClose(self.adq_cu, self.adq_num); return
print('shut down ADQ')
# Only disarm trigger after data is collected
ADQAPI.ADQ_DisarmTrigger(cu, self.adq_num)
ADQAPI.ADQ_MultiRecordClose(cu, self.adq_num)
# Delete ADQControlunit # Delete ADQControlunit
ADQAPI.DeleteADQControlUnit(self.adq_cu) ADQAPI.DeleteADQControlUnit(cu)
print('ADQ closed')
def start(self): def start(self, data):
"""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 +182,59 @@ 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.data = data
def getdata(self): def get_data(self):
"""wait for aquisition to be finished and get data""" """get new data if available"""
#start = time.time() ready = False
while(ADQAPI.ADQ_GetAcquiredAll(self.adq_cu,self.adq_num) == 0): data = self.data
time.sleep(0.001) if not data:
#if (self.trigger == SW_TRIG): self.busy = False
# ADQAPI.ADQ_SWTrig(self.adq_cu, self.adq_num) return None # no new data
#mid = time.time()
status = ADQAPI.ADQ_GetData(self.adq_cu, self.adq_num, self.target_buffers, if ADQAPI.ADQ_GetAcquiredAll(self.adq_cu, self.adq_num):
self.samples_per_record * self.number_of_records, 2, ready = True
0, self.number_of_records, ADQ_CHANNELS_MASK, data.timer('ready')
0, self.samples_per_record, ADQ_TRANSFER_MODE_NORMAL); else:
#print(time.time()-mid,mid-start) if self.trigger == SW_TRIG:
if not status: ADQAPI.ADQ_SWTrig(self.adq_cu, self.adq_num)
raise ValueError('no succesS from ADQ_GetDATA') if not ready:
# Now this is an array with all records, but the time is artificial self.busy = True
data = [] return None
for ch in range(2): self.data = None
onedim = np.frombuffer(self.target_buffers[ch].contents, dtype=np.int16) t = time.time()
data.append(onedim.reshape(self.number_of_records, self.samples_per_record) / float(2**14)) # 14 bits ADC # Get data from ADQ
if not ADQAPI.ADQ_GetData(
self.adq_cu, self.adq_num, self.target_buffers,
self.samples_per_record * self.number_of_records, 2,
0, self.number_of_records, ADQ_CHANNELS_MASK,
0, self.samples_per_record, ADQ_TRANSFER_MODE_NORMAL):
raise RuntimeError('no success from ADQ_GetDATA')
data.retrieve(self)
return data return data
def acquire(self):
self.start()
return self.getdata()
'''
def average(self, data):
#Average over records
return [data[ch].sum(axis=0) / self.number_of_records for ch in range(2)]
def iq(self, channel, f_LO): class PEdata:
newx = np.linspace(0, self.samples_per_record /2, self.samples_per_record) def __init__(self, adq):
s0 = channel /((2**16)/2)*0.5*np.exp(1j*2*np.pi*f_LO/(1e3)*newx) self.sample_rate = adq.sample_rate
I0 = s0.real self.samp_freq = self.sample_rate / GHz
Q0 = s0.imag self.number_of_records = adq.number_of_records
return I0, Q0 self.timer = Timer()
def retrieve(self, adq):
data = []
rawsignal = []
for ch in range(2):
onedim = np.frombuffer(adq.target_buffers[ch].contents, dtype=np.int16)
rawsignal.append(onedim[:adq.samples_per_record])
# convert 16 bit int to a value in the range -1 .. 1
data.append(onedim.reshape(adq.number_of_records, adq.samples_per_record) / float(2 ** 15))
# Now this is an array with all records, but the time is artificial
self.data = data
self.rawsignal = rawsignal
self.timer('retrieved')
def fitting(self, data, f_LO, ti, tf): def sinW(self, sig, freq, 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 +253,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 +283,115 @@ 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.timer('gates')
times.append(('aviq', time.time())) try:
iq = self.averageiq(data,freq*1e-9,*pulse) self.ndecimate = int(round(self.sample_rate / freq))
times.append(('filtro', time.time())) except TypeError as e:
iqf = self.filtro(iq,self.bw_cutoff) raise TypeError(f'{self.sample_rate}/{freq} {e}')
m = len(iqf[0]) // self.ndecimate iq = self.averageiq(self.data, freq / GHz, *pulse)
times.append(('iqdec', time.time())) self.timer('aviq')
iqf = self.filtro(iq, bw_cutoff)
self.timer('filtro')
m = max(1, len(iqf[0]) // self.ndecimate)
ll = m * self.ndecimate
iqf = [iqfx[0:ll] for iqfx in iqf]
self.timer('iqf')
iqd = np.average(np.resize(iqf, (2, m, self.ndecimate)), axis=2) iqd = np.average(np.resize(iqf, (2, m, self.ndecimate)), axis=2)
self.timer('avg')
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())) self.timer('pulsig')
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) result = ([self.box(iqf, *r) for r in roi], # gates
#print(times) (t_axis, iqd[0], iqd[1], pulsig)) # curves
return [self.box(iqf,*r) for r in roi] self.timer('result')
# self.timer.show()
# ns = len(self.rawsignal[0]) * self.number_of_records
# print(f'{ns} {ns / 2e6} ms')
return result
class Namespace:
"""holds channel or other data"""
def __init__(self, **kwds):
self.__dict__.update(**kwds)
class RUSdata:
def __init__(self, adq, freq, periods, delay_samples):
self.sample_rate = adq.sample_rate
self.freq = freq
self.periods = periods
self.delay_samples = delay_samples
self.samples_per_record = adq.samples_per_record
self.inp = Namespace(idx=0, name='input')
self.out = Namespace(idx=1, name='output')
self.channels = (self.inp, self.out)
self.timer = Timer()
def retrieve(self, adq):
self.timer('start retrieve')
npts = self.samples_per_record - self.delay_samples
nbin = max(1, npts // (self.periods * 60)) # for performance reasons, do the binning first
nreduced = npts // nbin
ft = 2 * np.pi * self.freq * nbin / self.sample_rate * np.arange(nreduced)
self.timer('create time axis')
# complex_sinusoid = np.exp(1j * ft) # do not use this, below is 33 % faster
complex_sinusoid = 1j * np.sin(ft) + np.cos(ft)
self.timer('sinusoid')
rawsignal = [] # for raw plot
for chan in self.channels: # looping over input and output
# although the ADC is only 14 bit it is represented as unsigend 16 bit numbers,
# and due to some calculations (calibration) the last 2 bits are not zero
beg = self.delay_samples
isignal = np.frombuffer(adq.target_buffers[chan.idx].contents, dtype=np.int16)[beg:beg+nreduced * nbin]
self.timer('isignal')
reduced = isignal.reshape((-1, nbin)).mean(axis=1) # this converts also int16 to float
self.timer('reduce')
rawsignal.append(reduced)
chan.signal = signal = reduced * 2 ** -16 # in V -> peak to peak 1 V ~ +- 0.5 V
self.timer('divide')
# calculate RMS * sqrt(2) -> peak sinus amplitude.
# may be higher than the input range by a factor 1.4 when heavily clipped
chan.amplitude = np.sqrt((signal ** 2).mean()) * RMS_TO_VPP
self.timer('amp')
chan.mixed = signal * complex_sinusoid
self.timer('mix')
chan.mean = chan.mixed.mean()
self.timer('mean')
self.rawsignal = rawsignal
if self.inp.mean:
self.iq = self.out.mean / self.inp.mean
else:
self.iq = 0
def get_quality(self):
"""get signal quality info
quality info (small values indicate good quality):
- input_stddev:
the imaginary part indicates deviations in phase
the real part indicates deviations in amplitude
- output_slope:
the imaginary part indicates a turning phase (rad/sec)
the real part indicates changes in amplitude (0.01 ~= 1%/sec)
"""
self.timer('get_quality')
npts = len(self.channels[0].signal)
nper = npts // self.periods
for chan in self.channels:
mean = chan.mixed.mean()
chan.reduced = chan.mixed[:self.periods * nper].reshape((-1, nper)).mean(axis=1) / mean
timeaxis = np.arange(len(self.out.reduced)) * self.sample_rate / self.freq
result = Namespace(
input_stddev=self.inp.reduced.std(),
output_slope=np.polyfit(timeaxis, self.out.reduced, 1)[0])
self.timer('got_quality')
self.timer.show()
ns = len(self.rawsignal[0])
print(f'{ns} {ns / 2e6} ms')
return result

131
frappy_psi/autofill.py Normal file
View File

@ -0,0 +1,131 @@
# *****************************************************************************
#
# This program is free software; you can redistribute it and/or modify it under
# the terms of the GNU General Public License as published by the Free Software
# Foundation; either version 2 of the License, or (at your option) any later
# version.
#
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
# details.
#
# You should have received a copy of the GNU General Public License along with
# this program; if not, write to the Free Software Foundation, Inc.,
# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
# Module authors:
# Markus Zolliker <markus.zolliker@psi.ch>
#
# *****************************************************************************
import time
from frappy.core import Attached, Readable, Writable, Parameter, Command, \
IDLE, BUSY, DISABLED, ERROR
from frappy.datatypes import FloatRange, StatusType, TupleOf, EnumType
from frappy.states import HasStates, Retry, status_code
class AutoFill(HasStates, Readable):
level = Attached(Readable)
valve = Attached(Writable)
status = Parameter(datatype=StatusType(Readable, 'BUSY'))
mode = Parameter('auto mode', EnumType(disabled=0, auto=30), readonly=False)
fill_level = Parameter('low threshold triggering start filling',
FloatRange(unit='%'), readonly=False)
full_level = Parameter('high threshold triggering stop filling',
FloatRange(unit='%'), readonly=False)
fill_minutes_range = Parameter('range of possible fill rate',
TupleOf(FloatRange(unit='min'), FloatRange(unit='min')),
readonly=False)
hold_hours_range = Parameter('range of possible consumption rate',
TupleOf(FloatRange(unit='h'), FloatRange(unit='h')),
readonly=False)
fill_delay = Parameter('delay for cooling the transfer line',
FloatRange(unit='min'), readonly=False)
def read_status(self):
if self.mode == 'DISABLED':
return DISABLED, ''
vstatus = self.valve.status
if vstatus[0] // 100 != IDLE // 100:
self.stop_machine(vstatus)
return vstatus
status = self.level.read_status(self)
if status[0] // 100 == IDLE // 100:
return HasStates.read_status(self)
self.stop_machine(status)
return status
def write_mode(self, mode):
if mode == 'DISABLED':
self.stop_machine((DISABLED, ''))
elif mode == 'AUTO':
self.start_machine(self.watching)
return mode
@status_code(BUSY)
def watching(self, state):
if state.init:
self.valve.write_target(0)
delta = state.delta(10)
raw = self.level.value
if raw > self.value:
self.value -= delta / (3600 * self.hold_hours_range[1])
elif raw < self.value:
self.value -= delta / (3600 * self.hold_hours_range[0])
else:
self.value = raw
if self.value < self.fill_level:
return self.precooling
return Retry
@status_code(BUSY)
def precooling(self, state):
if state.init:
state.fillstart = state.now
self.valve.write_target(1)
delta = state.delta(1)
raw = self.level.value
if raw > self.value:
self.value += delta / (60 * self.fill_minutes_range[0])
elif raw < self.value:
self.value -= delta / (60 * self.fill_minutes_range[0])
else:
self.value = raw
if self.value > self.full_level:
return self.watching
if state.now > state.fillstart + self.fill_delay * 60:
return self.filling
return Retry
@status_code(BUSY)
def filling(self, state):
delta = state.delta(1)
raw = self.level.value
if raw > self.value:
self.value += delta / (60 * self.fill_minutes_range[0])
elif raw < self.value:
self.value += delta / (60 * self.fill_minutes_range[1])
else:
self.value = raw
if self.value > self.full_level:
return self.watching
return Retry
def on_cleanup(self, state):
try:
self.valve.write_target(0)
except Exception:
pass
super().on_cleanup()
@Command()
def fill(self):
self.mode = 'AUTO'
self.start_machine(self.precooling, fillstart=time.time())
@Command()
def stop(self):
self.start_machine(self.watching)

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

@ -0,0 +1,128 @@
# *****************************************************************************
#
# 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:
# M. Zolliker <markus.zolliker@psi.ch>
#
# *****************************************************************************
from frappy.core import Attached, Command, EnumType, FloatRange, \
Drivable, Parameter, BUSY, IDLE, ERROR
class Valve(Drivable):
motor = Attached(Drivable) # refers to motor module
value = Parameter('valve state',
EnumType(closed=0, open=1, undefined=2),
default=2)
status = Parameter() # inherit properties from Drivable
target = Parameter('valve target',
EnumType(closed=0, open=1),
readonly=False)
# TODO: convert to properties after tests
open_pos = Parameter('target position for open state', FloatRange(), readonly=False, default=80)
mid_pos = Parameter('position for changing speed', FloatRange(), readonly=False, default=5)
fast_speed = Parameter('normal speed', FloatRange(), readonly=False, default=40)
slow_speed = Parameter('reduced speed', FloatRange(), readonly=False, default=10)
__motor_target = None
__status = IDLE, ''
__value = 'undefined'
__drivestate = 0 # 2 when driving to intermediate target or on retry, 1 when driving to final target, 0 when idle
def doPoll(self):
mot = self.motor
motpos = mot.read_value()
scode, stext = mot.read_status()
drivestate = self.__drivestate
if scode >= ERROR:
if self.__drivestate and self.__remaining_tries > 0:
drivestate = 2
self.__remaining_tries -= 1
mot.reset()
mot.write_speed(self.slow_speed)
self.__status = BUSY, f'retry {self._action}'
else:
self.__status = ERROR, f'valve motor: {stext}'
elif scode < BUSY:
if self.__motor_target is not None and mot.target != self.__motor_target:
self.__status = ERROR, 'motor was driven directly'
elif drivestate == 2:
self.goto(self.target)
drivestate = 1
else:
if -3 < motpos < 3:
self.__value = 'closed'
self.__status = IDLE, ''
elif self.open_pos * 0.5 < motpos < self.open_pos * 1.5:
self.__value = 'open'
self.__status = IDLE, ''
else:
self.__status = ERROR, 'undefined'
if self.__drivestate and not self.isBusy(self.__status):
drivestate = 0
self.__motor_target = None
self.setFastPoll(False)
self.__drivestate = drivestate
self.read_status()
self.read_value()
def read_status(self):
return self.__status
def read_value(self):
if self.read_status()[0] >= BUSY:
return 'undefined'
return self.__value
def goto(self, target):
"""go to open, closed or intermediate position
the intermediate position is targeted when a speed change is needed
return 2 when a retry is needed, 1 else
"""
mot = self.motor
if target: # 'open'
self._action = 'opening'
if True or mot.value > self.mid_pos:
mot.write_speed(self.fast_speed)
self.__motor_target = mot.write_target(self.open_pos)
return 1
mot.write_speed(self.slow_speed)
self.__motor_target = mot.write_target(self.mid_pos)
return 2
self._action = 'closing'
if mot.value > self.mid_pos * 2:
mot.write_speed(self.fast_speed)
self.__motor_target = mot.write_target(self.mid_pos)
return 2
mot.write_speed(self.slow_speed)
self.__motor_target = mot.write_target(0)
return 1
def write_target(self, target):
self.__remaining_tries = 5
self.__drivestate = self.goto(target)
self.__status = BUSY, self._action
self.read_status()
self.read_value()
self.setFastPoll(True)
@Command() # python decorator to mark it as a command
def stop(self):
"""stop the motor -> value might get undefined"""
self.__drivestate = 0
self.motor.stop()

View File

@ -22,6 +22,7 @@
import os import os
import re import re
from pathlib import Path
from os.path import basename, dirname, exists, join from os.path import basename, dirname, exists, join
import numpy as np import numpy as np
@ -31,13 +32,22 @@ from scipy.interpolate import PchipInterpolator, CubicSpline, PPoly # pylint: d
from frappy.errors import ProgrammingError, RangeError from frappy.errors import ProgrammingError, RangeError
from frappy.lib import clamp from frappy.lib import clamp
def identity(x):
return x
def exp10(x):
return 10 ** np.array(x)
to_scale = { to_scale = {
'lin': lambda x: x, 'lin': identity,
'log': lambda x: np.log10(x), 'log': np.log10,
} }
from_scale = { from_scale = {
'lin': lambda x: x, 'lin': identity,
'log': lambda x: 10 ** np.array(x), 'log': exp10,
} }
TYPES = [ # lakeshore type, inp-type, loglog TYPES = [ # lakeshore type, inp-type, loglog
('DT', 'si', False), # Si diode ('DT', 'si', False), # Si diode
@ -55,7 +65,7 @@ TYPES = [ # lakeshore type, inp-type, loglog
OPTION_TYPE = { OPTION_TYPE = {
'loglog': 0, # boolean 'loglog': 0, # boolean
'extrange': 2, # tuple(min T, max T for extrapolation 'extrange': 2, # tuple(min T, max T) for extrapolation
'calibrange': 2, # tuple(min T, max T) 'calibrange': 2, # tuple(min T, max T)
} }
@ -222,14 +232,6 @@ PARSERS = {
} }
def check(x, y, islog):
# check interpolation error
yi = y[:-2] + (x[1:-1] - x[:-2]) * (y[2:] - y[:-2]) / (x[2:] - x[:-2])
if islog:
return sum((yi - y[1:-1]) ** 2)
return sum((np.log10(yi) - np.log10(y[1:-1])) ** 2)
def get_curve(newscale, curves): def get_curve(newscale, curves):
"""get curve from curve cache (converts not existing ones) """get curve from curve cache (converts not existing ones)
@ -247,6 +249,7 @@ def get_curve(newscale, curves):
class CalCurve(HasOptions): class CalCurve(HasOptions):
EXTRAPOLATION_AMOUNT = 0.1 EXTRAPOLATION_AMOUNT = 0.1
MAX_EXTRAPOLATION_FACTOR = 2 MAX_EXTRAPOLATION_FACTOR = 2
filename = None # calibration file
def __init__(self, calibspec=None, *, x=None, y=None, cubic_spline=True, **options): def __init__(self, calibspec=None, *, x=None, y=None, cubic_spline=True, **options):
"""calibration curve """calibration curve
@ -257,7 +260,7 @@ class CalCurve(HasOptions):
[<full path> | <name>][,<key>=<value> ...] [<full path> | <name>][,<key>=<value> ...]
for <key>/<value> as in parser arguments for <key>/<value> as in parser arguments
:param x, y: x and y arrays (given instead of calibspec) :param x, y: x and y arrays (given instead of calibspec)
:param cubic_split: set to False for always using Pchip interpolation :param cubic_spline: set to False for always using Pchip interpolation
:param options: options for parsers :param options: options for parsers
""" """
self.options = options self.options = options
@ -265,26 +268,31 @@ class CalCurve(HasOptions):
parser = StdParser() parser = StdParser()
parser.xdata = x parser.xdata = x
parser.ydata = y parser.ydata = y
self.calibname = 'custom'
else: else:
if x or y: if x or y:
raise ProgrammingError('can not give both calibspec and x,y ') raise ProgrammingError('can not give both calibspec and x,y ')
sensopt = calibspec.split(',') sensopt = calibspec.split(',')
calibname = sensopt.pop(0) calibname = sensopt.pop(0)
_, dot, ext = basename(calibname).rpartition('.') self.calibname = basename(calibname)
head, dot, ext = self.calibname.rpartition('.')
if dot:
self.calibname = head
kind = None kind = None
pathlist = os.environ.get('FRAPPY_CALIB_PATH', '').split(':') pathlist = [Path(p.strip()) for p in os.environ.get('FRAPPY_CALIB_PATH', '').split(':')]
pathlist.append(join(dirname(__file__), 'calcurves')) pathlist.append(Path(dirname(__file__)) / 'calcurves')
for path in pathlist: for path in pathlist:
# first try without adding kind # first try without adding kind
filename = join(path.strip(), calibname) filename = path / calibname
if exists(filename): if filename.exists():
kind = ext if dot else None kind = ext if dot else None
break break
# then try adding all kinds as extension # then try adding all kinds as extension
for nam in calibname, calibname.upper(), calibname.lower(): for nam in calibname, calibname.upper(), calibname.lower():
for kind in PARSERS: for kind in PARSERS:
filename = join(path.strip(), '%s.%s' % (nam, kind)) filename = path / f'{nam}.{kind}'
if exists(filename): if exists(filename):
self.filename = filename
break break
else: else:
continue continue
@ -328,6 +336,7 @@ class CalCurve(HasOptions):
not_incr_idx = np.argwhere(x[1:] <= x[:-1]) not_incr_idx = np.argwhere(x[1:] <= x[:-1])
if len(not_incr_idx): if len(not_incr_idx):
raise RangeError('x not monotonic at x=%.4g' % x[not_incr_idx[0]]) raise RangeError('x not monotonic at x=%.4g' % x[not_incr_idx[0]])
self.ptc = y[-1] > y[0]
self.x = {parser.xscale: x} self.x = {parser.xscale: x}
self.y = {parser.yscale: y} self.y = {parser.yscale: y}
@ -344,8 +353,7 @@ class CalCurve(HasOptions):
self.convert_x = to_scale[newscale] self.convert_x = to_scale[newscale]
self.convert_y = from_scale[newscale] self.convert_y = from_scale[newscale]
self.calibrange = self.options.get('calibrange') self.calibrange = self.options.get('calibrange')
dirty = set() self.extra_points = (0, 0)
self.extra_points = False
self.cutted = False self.cutted = False
if self.calibrange: if self.calibrange:
self.calibrange = sorted(self.calibrange) self.calibrange = sorted(self.calibrange)
@ -371,7 +379,6 @@ class CalCurve(HasOptions):
self.y = {newscale: y} self.y = {newscale: y}
ibeg = 0 ibeg = 0
iend = len(x) iend = len(x)
dirty.add('xy')
else: else:
self.extra_points = ibeg, len(x) - iend self.extra_points = ibeg, len(x) - iend
else: else:
@ -493,13 +500,48 @@ class CalCurve(HasOptions):
except IndexError: except IndexError:
return defaultx return defaultx
def export(self, logformat=False, nmax=199, yrange=None, extrapolate=True, xlimits=None): def interpolation_error(self, x0, x1, y0, y1, funx, funy, relerror, return_tuple=False):
"""calcualte interpoaltion error
:param x0: start of interval
:param x1: end of interval
:param y0: y at start of interval
:param y1: y at end of interval
:param funx: function to convert x from exported scale to internal scale
:param funy: function to convert y from internal scale to exported scale
:param relerror: True when the exported y scale is linear
:param return_tuple: True: return interpolation error as a tuple with two values
(without and with 3 additional points)
False: return one value without additional points
:return: relative deviation
"""
xspace = np.linspace(x0, x1, 9)
x = funx(xspace)
yr = self.spline(x)
yspline = funy(yr)
yinterp = y0 + np.linspace(0.0, y1 - y0, 9)
# difference between spline (at m points) and liner interpolation
diff = np.abs(yspline - yinterp)
# estimate of interpolation error with 4 sections:
# difference between spline (at m points) and linear interpolation between neighboring points
if relerror:
fact = 2 / (np.abs(y0) + np.abs(y1)) # division by zero can not happen, as y0 and y1 can not both be zero
else:
fact = 2.3 # difference is in log10 -> multiply by 1 / log10(e)
result = np.max(diff, axis=0) * fact
if return_tuple:
diff2 = np.abs(0.5 * (yspline[:-2:2] + yspline[2::2]) - funy(yr[1:-1:2]))
return result, np.max(diff2, axis=0) * fact
return result
def export(self, logformat=False, nmax=199, yrange=None, extrapolate=True, xlimits=None, nmin=199):
"""export curve for downloading to hardware """export curve for downloading to hardware
:param nmax: max number of points. if the number of given points is bigger, :param nmax: max number of points. if the number of given points is bigger,
the points with the lowest interpolation error are omitted the points with the lowest interpolation error are omitted
:param logformat: a list with two elements of None, True or False :param logformat: a list with two elements of None, True or False for x and y
True: use log, False: use line, None: use log if self.loglog True: use log, False: use lin, None: use log if self.loglog
values None are replaced with the effectively used format values None are replaced with the effectively used format
False / True are replaced by [False, False] / [True, True] False / True are replaced by [False, False] / [True, True]
default is False default is False
@ -507,25 +549,26 @@ class CalCurve(HasOptions):
:param extrapolate: a flag indicating whether the curves should be extrapolated :param extrapolate: a flag indicating whether the curves should be extrapolated
to the preset extrapolation range to the preset extrapolation range
:param xlimits: max x range :param xlimits: max x range
:param nmin: minimum number of points
:return: numpy array with 2 dimensions returning the curve :return: numpy array with 2 dimensions returning the curve
""" """
if logformat in (True, False): if logformat in (True, False):
logformat = [logformat, logformat] logformat = (logformat, logformat)
self.logformat = list(logformat)
try: try:
scales = [] scales = []
for idx, logfmt in enumerate(logformat): for idx, logfmt in enumerate(logformat):
if logfmt and self.lin_forced[idx]: if logfmt and self.lin_forced[idx]:
raise ValueError('%s must contain positive values only' % 'xy'[idx]) raise ValueError('%s must contain positive values only' % 'xy'[idx])
logformat[idx] = linlog = self.loglog if logfmt is None else logfmt self.logformat[idx] = linlog = self.loglog if logfmt is None else logfmt
scales.append('log' if linlog else 'lin') scales.append('log' if linlog else 'lin')
xscale, yscale = scales xscale, yscale = scales
except (TypeError, AssertionError): except (TypeError, AssertionError):
raise ValueError('logformat must be a 2 element list or a boolean') raise ValueError('logformat must be a 2 element sequence or a boolean')
x = self.spline.x[1:-1] # raw units, excluding extrapolated points xr = self.spline.x[1:-1] # raw units, excluding extrapolated points
x1, x2 = xmin, xmax = x[0], x[-1] x1, x2 = xmin, xmax = xr[0], xr[-1]
y1, y2 = sorted(self.spline([x1, x2]))
if extrapolate and not yrange: if extrapolate and not yrange:
yrange = self.exty yrange = self.exty
@ -535,42 +578,100 @@ class CalCurve(HasOptions):
lim = to_scale[self.scale](xlimits) lim = to_scale[self.scale](xlimits)
xmin = clamp(xmin, *lim) xmin = clamp(xmin, *lim)
xmax = clamp(xmax, *lim) xmax = clamp(xmax, *lim)
# start and end index of calibrated range
ibeg, iend = self.extra_points[0], len(xr) - self.extra_points[1]
if xmin != x1 or xmax != x2: if xmin != x1 or xmax != x2:
ibeg, iend = np.searchsorted(x, (xmin, xmax)) i, j = np.searchsorted(xr, (xmin, xmax))
if abs(x[ibeg] - xmin) < 0.1 * (x[ibeg + 1] - x[ibeg]): if abs(xr[i] - xmin) < 0.1 * (xr[i + 1] - xr[i]):
# remove first point, if close # remove first point, if close
ibeg += 1 i += 1
if abs(x[iend - 1] - xmax) < 0.1 * (x[iend - 1] - x[iend - 2]): if abs(xr[j - 1] - xmax) < 0.1 * (xr[j - 1] - xr[j - 2]):
# remove last point, if close # remove last point, if close
iend -= 1 j -= 1
x = np.concatenate(([xmin], x[ibeg:iend], [xmax])) offset = i - 1
y = self.spline(x) xr = np.concatenate(([xmin], xr[i:j], [xmax]))
ibeg = max(0, ibeg - offset)
iend = min(len(xr), iend - offset)
yr = self.spline(xr)
# convert to exported scale # convert to exported scale
if xscale != self.scale: if xscale == self.scale:
x = to_scale[xscale](from_scale[self.scale](x)) xbwd = identity
if yscale != self.scale: x = xr
y = to_scale[yscale](from_scale[self.scale](y)) else:
if self.scale == 'log':
# reduce number of points, if needed xfwd, xbwd = from_scale[self.scale], to_scale[self.scale]
n = len(x)
i, j = 1, n - 1 # index range for calculating interpolation deviation
deviation = np.zeros(n)
while True:
# calculate interpolation error when a single point is omitted
ym = y[i-1:j-1] + (x[i:j] - x[i-1:j-1]) * (y[i+1:j+1] - y[i-1:j-1]) / (x[i+1:j+1] - x[i-1:j-1])
if yscale == 'log':
deviation[i:j] = np.abs(ym - y[i:j])
else: else:
deviation[i:j] = np.abs(ym - y[i:j]) / (np.abs(ym + y[i:j]) + 1e-10) xfwd, xbwd = to_scale[xscale], from_scale[xscale]
if n <= nmax: x = xfwd(xr)
break if yscale == self.scale:
idx = np.argmin(deviation[1:-1]) + 1 # find index of the smallest error yfwd = identity
y = np.delete(y, idx) y = yr
x = np.delete(x, idx) else:
deviation = np.delete(deviation, idx) if self.scale == 'log':
n -= 1 yfwd = from_scale[self.scale]
# index range to recalculate else:
i, j = max(1, idx - 1), min(n - 1, idx + 1) yfwd = to_scale[yscale]
self.deviation = deviation # for debugging purposes y = yfwd(yr)
self.deviation = None
nmin = min(nmin, nmax)
n = len(x)
relerror = yscale == 'lin'
if len(x) > nmax:
# reduce number of points, if needed
i, j = 1, n - 1 # index range for calculating interpolation deviation
deviation = np.zeros(n)
while True:
deviation[i:j] = self.interpolation_error(
x[i-1:j-1], x[i+1:j+1], y[i-1:j-1], y[i+1:j+1],
xbwd, yfwd, relerror)
# calculate interpolation error when a single point is omitted
if n <= nmax:
break
idx = np.argmin(deviation[1:-1]) + 1 # find index of the smallest error
y = np.delete(y, idx)
x = np.delete(x, idx)
deviation = np.delete(deviation, idx)
n = len(x)
# index range to recalculate
i, j = max(1, idx - 1), min(n - 1, idx + 1)
self.deviation = deviation # for debugging purposes
elif n < nmin:
if ibeg + 1 < iend:
diff1, diff4 = self.interpolation_error(
x[ibeg:iend - 1], x[ibeg + 1:iend], y[ibeg:iend - 1], y[ibeg + 1:iend],
xbwd, yfwd, relerror, return_tuple=True)
dif_target = 1e-4
sq4 = np.sqrt(diff4) * 4
sq1 = np.sqrt(diff1)
offset = 0.49
n_mid = nmax - len(x) + iend - ibeg - 1
# iteration to find a dif target resulting in no more than nmax points
while True:
scale = 1 / np.sqrt(dif_target)
# estimate number of intermediate points (float!) needed to reach dif_target
# number of points estimated from the result of the interpolation error with 4 sections
n4 = np.maximum(1, sq4 * scale)
# number of points estimated from the result of the interpolation error with 1 section
n1 = np.maximum(1, sq1 * scale)
# use n4 where n4 > 4, n1, where n1 < 1 and a weighted average in between
nn = np.select([n4 > 4, n1 > 1],
[n4, (n4 * (n1 - 1) + n1 * (4 - n4)) / (3 + n1 - n4)], n1)
n_tot = np.sum(np.rint(nn + offset))
extra = n_tot - n_mid
if extra <= 0:
break
dif_target *= (n_tot / n_mid) ** 2
xnew = [x[:ibeg]]
for x0, x1, ni in zip(x[ibeg:iend-1], x[ibeg+1:iend], np.rint(nn + offset)):
xnew.append(np.linspace(x0, x1, int(ni) + 1)[:-1])
xnew.append(x[iend-1:])
x = np.concatenate(xnew)
y = yfwd(self.spline(xbwd(x)))
# for debugging purposes:
self.deviation = self.interpolation_error(x[:-1], x[1:], y[:-1], y[1:], xbwd, yfwd, relerror)
return np.stack([x, y], axis=1) return np.stack([x, y], axis=1)

139
frappy_psi/ccracks.py Normal file
View File

@ -0,0 +1,139 @@
import os
from glob import glob
from pathlib import Path
from configparser import ConfigParser
from frappy.errors import ConfigError
class Rack:
configbase = Path('/home/l_samenv/.config/frappy_instruments')
def __init__(self, modfactory, **kwds):
self.modfactory = modfactory
instpath = self.configbase / os.environ['Instrument']
sections = {}
self.config = {}
files = glob(str(instpath / '*.ini'))
for filename in files:
parser = ConfigParser()
parser.optionxform = str
parser.read([filename])
for section in parser.sections():
prev = sections.get(section)
if prev:
raise ConfigError(f'duplicate {section} section in {filename} and {prev}')
sections[section] = filename
self.config.update(parser.items(section))
if 'rack' not in sections:
raise ConfigError(f'no rack found in {instpath}')
self.props = {} # dict (<property>, <method>) of value
self.mods = {} # dict (<property>, <method>) of list of <cfg>
def set_props(self, mod, **kwds):
for prop, method in kwds.items():
value = self.props.get((prop, method))
if value is None:
# add mod to the list of cfgs to be fixed
self.mods.setdefault((prop, method), []).append(mod)
else:
# set prop in current module
if not mod.get(prop): # do not override given and not empty property
mod[prop] = value
def fix_props(self, method, **kwds):
for prop, value in kwds.items():
if (prop, method) in self.props:
raise ConfigError(f'duplicate call to {method}()')
self.props[prop, method] = value
# set property in modules to be fixed
for mod in self.mods.get((prop, method), ()):
mod[prop] = value
def lakeshore(self, ls_uri=None, io='ls_io', dev='ls', model='336', **kwds):
Mod = self.modfactory
self.fix_props('lakeshore', io=io, device=dev)
self.ls_model = model
self.ls_dev = dev
ls_uri = ls_uri or self.config.get('ls_uri')
Mod(io, cls=f'frappy_psi.lakeshore.IO{self.ls_model}',
description='comm. to lakeshore in cc rack', uri=ls_uri)
self.dev = Mod(dev, cls=f'frappy_psi.lakeshore.Device{self.ls_model}',
description='lakeshore in cc rack', io=io, curve_handling=True)
def sensor(self, name, channel, calcurve, **kwds):
Mod = self.modfactory
kwds.setdefault('cls', f'frappy_psi.lakeshore.Sensor{self.ls_model}')
kwds.setdefault('description', f'T sensor {name}')
mod = Mod(name, channel=channel, calcurve=calcurve,
device=self.ls_dev, **kwds)
self.set_props(mod, io='lakeshore', dev='lakeshore')
def loop(self, name, channel, calcurve, output_module, **kwds):
Mod = self.modfactory
kwds.setdefault('cls', f'frappy_psi.lakeshore.Loop{self.ls_model}')
kwds.setdefault('description', f'T loop {name}')
Mod(name, channel=channel, calcurve=calcurve, output_module=output_module,
device=self.ls_dev, **kwds)
self.fix_props(f'heater({output_module})', description=f'heater for {name}')
def heater(self, name, output_no, max_heater, resistance, **kwds):
Mod = self.modfactory
if output_no == 1:
kwds.setdefault('cls', f'frappy_psi.lakeshore.MainOutput{self.ls_model}')
elif output_no == 2:
kwds.setdefault('cls', f'frappy_psi.lakeshore.SecondaryOutput{self.ls_model}')
else:
return
kwds.setdefault('description', '')
mod = Mod(name, max_heater=max_heater, resistance=resistance, **kwds)
self.set_props(mod, io='lakeshore', device='lakeshore', description=f'heater({name})')
def ccu(self, ccu_uri=None, ccu_io='ccu_io', he=None, n2=None, **kwds):
Mod = self.modfactory
self.ccu_io = ccu_io
ccu_uri = ccu_uri or self.config.get('ccu_uri')
# self.devname = ccu_devname
Mod(ccu_io, 'frappy_psi.ccu4.IO',
'comm. to CCU4', uri=ccu_uri)
if he:
if not isinstance(he, str): # e.g. True
he = 'He_lev'
Mod(he, cls='frappy_psi.ccu4.HeLevel',
description='the He Level', io=self.ccu_io)
if n2:
if isinstance(n2, str):
n2 = n2.split(',')
else: # e.g. True
n2 = []
n2, valve, upper, lower = n2 + ['N2_lev', 'N2_valve', 'N2_upper', 'N2_lower'][len(n2):]
print(n2, valve, upper, lower)
Mod(n2, cls='frappy_psi.ccu4.N2Level',
description='the N2 Level', io=ccu_io,
valve=valve, upper=upper, lower=lower)
Mod(valve, cls='frappy_psi.ccu4.N2FillValve',
description='LN2 fill valve', io=ccu_io)
Mod(upper, cls='frappy_psi.ccu4.N2TempSensor',
description='upper LN2 sensor')
Mod(lower, cls='frappy_psi.ccu4.N2TempSensor',
description='lower LN2 sensor')
def hepump(self, hepump_uri=None, hepump_type=None, hepump_io='hepump_io',
hepump='hepump', hepump_mot='hepump_mot', hepump_valve='hepump_valve',
flow_sensor='flow_sensor', pump_pressure='pump_pressure', nv='nv',
ccu_io='ccu_io', **kwds):
Mod = self.modfactory
hepump_type = hepump_type or self.config.get('hepump_type', 'no')
Mod(nv, 'frappy_psi.ccu4.NeedleValveFlow', 'flow from flow sensor or pump pressure',
flow_sensor=flow_sensor, pressure=pump_pressure, io=ccu_io)
Mod(pump_pressure, 'frappy_psi.ccu4.Pressure', 'He pump pressure', io=ccu_io)
if hepump_type == 'no':
print('no pump, no flow meter - using flow from pressure alone')
return
hepump_uri = hepump_uri or self.config['hepump_uri']
Mod(hepump_io, 'frappy.io.BytesIO', 'He pump connection', uri=hepump_uri)
Mod(hepump, 'frappy_psi.hepump.HePump', 'He pump', pump_type=hepump_type,
valvemotor=hepump_mot, valve=hepump_valve, flow=nv)
Mod(hepump_mot, 'frappy_psi.hepump.Motor', 'He pump valve motor', io=hepump_io, maxcurrent=2.8)
Mod(hepump_valve, 'frappy_psi.butterflyvalve.Valve', 'He pump valve', motor=hepump_mot)
Mod(flow_sensor, 'frappy_psi.sensirion.FlowSensor', 'Flow Sensor', io=hepump_io, nsamples=160)

View File

@ -22,51 +22,65 @@
"""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
from frappy.lib import clamp
# 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, nopoll
from frappy.datatypes import BoolType, EnumType, FloatRange, StructOf, \ from frappy.datatypes import BoolType, EnumType, FloatRange, StructOf, \
StatusType, IntRange, StringType, TupleOf StatusType, IntRange, StringType, TupleOf
from frappy.dynamic import Pinata
from frappy.errors import CommunicationFailedError from frappy.errors import CommunicationFailedError
from frappy.states import HasStates, status_code, Retry from frappy.states import HasStates, status_code, Retry
class CCU4IO(StringIO): M = Enum(idle=0, opening=1, closing=2, opened=3, closed=4, no_motor=5)
A = Enum(disabled=0, manual=1, auto=2)
class IO(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)
end_of_line = '\n' end_of_line = '\n'
# on connect, we send 'cid' and expect a reply starting with 'CCU4' # on connect, we send 'cid' and expect a reply starting with 'CCU4'
identification = [('cid', r'CCU4.*')] identification = [('cid', r'cid=CCU4.*')]
class CCU4Base(HasIO): class Base(HasIO):
ioClass = CCU4IO ioClass = IO
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
class HeLevel(CCU4Base, Readable): class HeLevel(Base, Readable):
"""He Level channel of CCU4""" """He Level channel of CCU4"""
value = Parameter(unit='%') value = Parameter(unit='%')
@ -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):
@ -108,10 +122,10 @@ class HeLevel(CCU4Base, Readable):
return self.command(hfu=value) return self.command(hfu=value)
class Valve(CCU4Base, Writable): class Valve(Base, Writable):
value = Parameter('relay state', BoolType()) value = Parameter('relay state', BoolType())
target = Parameter('relay target', BoolType()) target = Parameter('relay target', BoolType())
ioClass = CCU4IO ioClass = IO
STATE_MAP = {0: (0, (IDLE, 'off')), STATE_MAP = {0: (0, (IDLE, 'off')),
1: (1, (IDLE, 'on')), 1: (1, (IDLE, 'on')),
2: (0, (ERROR, 'no valve')), 2: (0, (ERROR, 'no valve')),
@ -130,7 +144,7 @@ class Valve(CCU4Base, Writable):
self.command(**self._close_command) self.command(**self._close_command)
def read_status(self): def read_status(self):
state = self.command(self._query_state) state = int(self.command(**self._query_state))
self.value, status = self.STATE_MAP[state] self.value, status = self.STATE_MAP[state]
return status return status
@ -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,21 +167,21 @@ 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):
value = Parameter('LN2 T sensor', FloatRange(unit='K'), default=0) value = Parameter('LN2 T sensor', FloatRange(unit='K'), default=0)
class N2Level(CCU4Base, Pinata, Readable): class N2Level(Base, Readable):
valve = Attached(Writable, mandatory=False) valve = Attached(Writable, mandatory=False)
lower = Attached(Readable, mandatory=False) lower = Attached(Readable, mandatory=False)
upper = Attached(Readable, mandatory=False) upper = Attached(Readable, mandatory=False)
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, 'DISABLED', 'BUSY'))
mode = Parameter('auto mode', EnumType(disabled=0, manual=1, auto=2), readonly=False) mode = Parameter('auto mode', EnumType(A), readonly=False, default=A.manual)
threshold = Parameter('threshold triggering start/stop filling', threshold = Parameter('threshold triggering start/stop filling',
FloatRange(unit='K'), readonly=False) FloatRange(unit='K'), readonly=False)
@ -192,31 +206,22 @@ class N2Level(CCU4Base, Pinata, Readable):
5: (WARN, 'empty'), 5: (WARN, 'empty'),
} }
def scanModules(self):
for modname, name in self.names.items():
if name:
sensor_name = name.replace('$', self.name)
self.setProperty(modname, sensor_name)
yield sensor_name, {
'cls': N2FillValve if modname == 'valve' else N2TempSensor,
'description': f'LN2 {modname} T sensor'}
def initialReads(self): def initialReads(self):
self.command(nav=1) # tell CCU4 to activate LN2 sensor readings self.command(nav=1) # tell CCU4 to activate LN2 sensor readings
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 +234,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 +246,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 +257,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,153 +271,335 @@ class N2Level(CCU4Base, Pinata, Readable):
@Command() @Command()
def fill(self): def fill(self):
self.mode = 'auto' """start filling"""
self.io.write(nc=1) self.mode = A.auto
self.command(nc=1)
@Command() @Command()
def stop(self): def stop(self):
if self.mode == 'auto': """stop filling"""
if self.mode == A.auto:
# set to watching # set to watching
self.command(nc=3) self.command(nc=3)
else: else:
# set to off # set to off
self.io.write(nc=0) self.command(nc=0)
class FlowPressure(CCU4Base, Readable): class HasFilter:
__value1 = None
__value = None
__last = None
def filter(self, filter_time, value):
now = time.time()
if self.__value is None:
self.__last = now
self.__value1 = value
self.__value = value
weight = (now - self.__last) / filter_time
self.__value1 += weight * (value - self.__value)
self.__value += weight * (self.__value1 - self.__value)
self.__last = now
return self.__value
class Pressure(HasFilter, Base, Readable):
value = Parameter(unit='mbar') value = Parameter(unit='mbar')
mbar_offset = Parameter(unit='mbar', default=0.8, readonly=False) mbar_offset = Parameter('offset in mbar', FloatRange(unit='mbar'), default=0.8, readonly=False)
filter_time = Parameter('filter time', FloatRange(unit='sec'), readonly=False, default=3)
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.filter_time, self.command(f=float)) - self.mbar_offset
class NeedleValve(HasStates, CCU4Base, Drivable): class NeedleValveFlow(HasStates, Base, Drivable):
flow = Attached(Readable, mandatory=False) flow_sensor = Attached(Readable, mandatory=False)
flow_pressure = Attached(Readable, mandatory=False) pressure = Attached(Pressure, mandatory=False)
use_pressure = Parameter('flag (use pressure instead of flow meter)', BoolType(),
readonly=False, default=False)
lnm_per_mbar = Parameter('scale factor', FloatRange(unit='lnm/mbar'), readonly=False, default=0.6)
value = Parameter(unit='ln/min') value = Parameter(unit='ln/min')
target = Parameter(unit='ln/min') target = Parameter(unit='ln/min')
lnm_per_mbar = Parameter(unit='ln/min/mbar', default=0.6, readonly=False) motor_state = Parameter('motor_state', EnumType(M), default=0)
use_pressure = Parameter('use flow from pressure', BoolType(),
default=False, readonly=False)
motor_state = Parameter('motor_state',
EnumType(idle=0, opening=1, closing=2,
opened=3, closed=5, no_motor=6))
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, default=0.001)
deriv = Parameter('min progress time constant', FloatRange(unit='s'), deriv = Parameter('min progress time constant', FloatRange(unit='s'),
default=30, readonly=False) default=30, readonly=False)
settle = Parameter('time within tolerance before getting quiet', FloatRange(unit='s'), settle = Parameter('time within tolerance before getting quiet', FloatRange(unit='s'),
default=30, readonly=False) default=30, readonly=False)
step_factor = Parameter('factor (no progress time) / (min step size)', FloatRange(), default=300) step_factor = Parameter('factor (no progress time) / (min step size)', FloatRange(), default=300, readonly=False)
control_active = Parameter('control active flag', BoolType(), readonly=False) control_active = Parameter('control active flag', BoolType(), readonly=False, default=1)
pollinterval = Parameter(default=1) min_open_step = Parameter('minimal open step', FloatRange(unit='s'), readonly=False, default=0.06)
min_close_step = Parameter('minimal close step', FloatRange(unit='s'), readonly=False, default=0.05)
raw_open_step = Parameter('step after direction change', FloatRange(unit='s'), readonly=False, default=0.12)
raw_close_step = Parameter('step after direction change', FloatRange(unit='s'), readonly=False, default=0.04)
pollinterval = Parameter(default=5)
_ref_time = 0 _ref_time = 0
_ref_dif = 0 _ref_dif = 0
_last_cycle = 0 _dir = 0
_last_progress = 0 _rawdir = 0
_step = 0 _step = 0
def initModule(self): def initModule(self):
if self.pressure:
self.pressure.addCallback('value', self.update_from_pressure)
if self.flow_sensor:
self.flow_sensor.addCallback('value', self.update_from_flow)
super().initModule() super().initModule()
if self.flow_pressure:
self.flow_pressure.addCallback('value', self.update_flow_pressure) def update_from_flow(self, value):
if self.flow: if not self.use_pressure:
self.flow.addCallback('value', self.update_flow) self.value = value
self.write_tolerance(self.tolerance) # self.cycle_machine()
def update_from_pressure(self, value):
if self.use_pressure:
self.value = value * self.lnm_per_mbar
# self.cycle_machine()
# def doPoll(self):
# """only the updates should trigger the machine"""
def read_value(self):
p = self.pressure.read_value() * self.lnm_per_mbar
f = self.flow_sensor.read_value()
# self.log.info('p %g f %g +- %.2g', p, f, self.flow_sensor.stddev)
self.read_motor_state()
return p if self.use_pressure else f
def write_tolerance(self, tolerance): def write_tolerance(self, tolerance):
if hasattr(self.flow_pressure, 'tolerance'): if hasattr(self.pressure, 'tolerance'):
self.flow_pressure.tolerance = tolerance / self.lnm_per_mbar self.pressure.tolerance = tolerance / self.lnm_per_mbar
if hasattr(self.flow, 'tolerance'): if hasattr(self.flow_sensor, 'tolerance'):
self.flow.tolerance = tolerance self.flow_sensor.tolerance = tolerance
def read_use_pressure(self): def read_use_pressure(self):
if self.flow_pressure: if self.pressure:
if self.flow: if self.flow_sensor:
return self.use_pressure return self.use_pressure
return True return True
return False return False
def update_flow(self, value):
if not self.use_pressure:
self.value = value
self.doPoll()
def update_flow_pressure(self, value):
if self.use_pressure:
self.value = value * self.lnm_per_mbar
self.doPoll()
def write_target(self, value): def write_target(self, value):
self.start_machine(self.controlling, in_tol_time=0, self.start_machine(self.change_target, fast_poll=1)
ref_time=0, ref_dif=0, prev_dif=0)
@status_code(BUSY) @status_code(BUSY)
def unblock_from_open(self, state): def change_target(self, state):
self.motor_state = self.command('fm') state.in_tol_time = 0
if self.motor_state == 'opened': state.last_minstep = {}
self.command(mp=-60) state.last_progress = state.now
return Retry state.ref_time = 0
if self.motor_state == 'closing': state.ref_dif = 0
return Retry state.prev_dif = 0 # used?
if self.motor_state == 'closed': state.last_close_time = 0
if self.value > max(1, self.target): state.last_pulse_time = 0
return Retry state.raw_fact = 1
state.flow_before = self.value state.raw_step = 0
state.wiggle = 1 if abs(self.target - self.value) < self._tolerance():
state.start_wiggle = state.now return self.at_target
self.command(mp=60) return self.raw_control
return self.unblock_open
return self.approaching def start_direction(self, state):
if self.target > self.value:
self._dir = 1
state.minstep = self.min_open_step
else:
self._dir = -1
state.minstep = self.min_close_step
state.prev = []
def perform_pulse(self, state):
tol = self._tolerance()
dif = self.target - self.value
difdir = dif * self._dir
state.last_pulse_time = state.now
if difdir > tol:
step = state.minstep + (difdir - tol) * self.prop
elif difdir > 0:
step = state.minstep * difdir / tol
else:
return
self.log.info('MP %g dif=%g tol=%g', step * self._dir, dif, tol)
self.command(mp=clamp(-1, step * self._dir, 1))
@status_code(BUSY) @status_code(BUSY)
def unblock_open(self, state): def raw_control(self, state):
self.motor_state = self.command('fm') tol = self._tolerance()
if self.value < state.flow_before: if state.init:
state.flow_before_open = self.value self.start_direction(state)
elif self.value > state.flow_before + 1: state.raw_step = self.raw_open_step if self._dir > 0 else -self.raw_close_step
state.wiggle = -state.wiggle / 2 state.raw_fact = 1
self.command(mp=state.wiggle) # if self.read_motor_state() == M.closed:
state.start_wiggle = state.now # # TODO: also check for flow near lower limit ? but only once after change_target
return self.unblock_close # self.log.info('start with fast opening')
if self.motor_state == 'opening': # state.raw_step = 1
# self._dir = 1
difdir = (self.target - self.value) * self._dir
state.prev.append(difdir)
state.diflim = max(0, difdir - tol * 1)
state.success = 0
self.command(mp=state.raw_step)
self.log.info('first rawstep %g', state.raw_step)
state.last_pulse_time = state.now
state.raw_pulse_cnt = 0
state.cycle_cnt = 0
return Retry return Retry
if self.motor_state == 'idle': difdir = (self.target - self.value) * self._dir
self.command(mp=state.wiggle) state.cycle_cnt += 1
state.prev.append(difdir)
del state.prev[:-5]
if state.prev[-1] > max(state.prev[:-1]):
# TODO: use the amount of overshoot to reduce the raw_step
state.cycle_cnt = 0
self.log.info('difference is increasing %s', ' '.join(f'{v:g}' for v in state.prev))
return Retry return Retry
if self.motor_state == 'opened': if state.cycle_cnt >= 5:
if state.now < state.start_wiggle + 20: state.cycle_cnt = 0
return Retry state.diflim = max(tol, min(state.prev) - tol * 0.5)
return self.final_status(ERROR, 'can not open') state.raw_pulse_cnt += 1
self.command(mp=state.raw_step * state.raw_fact)
self.log.info('rawstep %g', state.raw_step)
if state.raw_pulse_cnt % 5 == 0 and state.raw_pulse_cnt > 5:
state.raw_fact *= 1.25
return Retry
if difdir >= state.diflim:
state.success = max(0, state.success - 1)
return Retry
state.success += 1
if state.success <= 3:
return Retry
if state.raw_pulse_cnt < 3:
state.raw_fact = 1 - (3 - state.raw_pulse_cnt) ** 2 * 0.05
if state.raw_fact != 1:
if self._dir > 0:
self.raw_open_step *= state.raw_fact
self.log.info('raw_open_step %g f=%g', self.raw_open_step, state.raw_fact)
self.min_open_pulse = min(self.min_open_pulse, self.raw_open_step)
else:
self.raw_close_step *= state.raw_fact
self.log.info('raw_close_step %g f=%g', self.raw_close_step, state.raw_fact)
self.min_close_pulse = min(self.min_close_pulse, self.raw_close_step)
return self.controlling return self.controlling
# @status_code(BUSY)
# def raw_control(self, state):
# tol = self._tolerance()
# if state.init:
# self.start_direction(state)
# if self._dir != self._rawdir:
# self._rawdir = self._dir
# state.first_step = self.first_open_step if self._dir > 0 else -self.first_close_step
# else:
# state.first_step = 0
# state.first_fact = 1
# # if self.read_motor_state() == M.closed:
# # # TODO: also check for flow near lower limit ? but only once after change_target
# # self.log.info('start with fast opening')
# # state.first_step = 1
# # self._dir = 1
# difdir = (self.target - self.value) * self._dir
# state.prev = [difdir]
# state.diflim = max(0, difdir - tol * 0.5)
# state.success = 0
# if state.first_step:
# self.command(mp=state.first_step)
# else:
# self.perform_pulse(state)
# self.log.info('firststep %g', state.first_step)
# state.last_pulse_time = state.now
# state.raw_pulse_cnt = 0
# return Retry
# difdir = (self.target - self.value) * self._dir
# if state.delta(5):
# state.diflim = max(0, min(state.prev) - tol * 0.1)
# state.prev = [difdir]
# state.raw_pulse_cnt += 1
# if state.first_step and state.raw_pulse_cnt % 10 == 0:
# self.command(mp=state.first_step * state.first_fact)
# self.log.info('repeat firststep %g', state.first_step * state.first_fact)
# state.first_fact *= 1.25
# else:
# self.perform_pulse(state)
# return Retry
# state.prev.append(difdir)
# if difdir >= state.diflim:
# state.success = max(0, state.success - 1)
# return Retry
# state.success += 1
# if state.success <= 5:
# return Retry
# if state.first_step:
# if state.raw_pulse_cnt < 3:
# state.first_fact = 1 - (3 - state.raw_pulse_cnt) ** 2 * 0.04
# if state.first_fact != 1:
# if self._dir > 0:
# self.first_open_step *= state.first_fact
# self.log.info('first_open_step %g f=%g', self.first_open_step, state.first_fact)
# else:
# self.first_close_step *= state.first_fact
# self.log.info('first_close_step %g f=%g', self.first_close_step, state.first_fact)
# return self.controlling
@status_code(BUSY) @status_code(BUSY)
def unblock_close(self, state): def controlling(self, state):
self.motor_state = self.command('fm') dif = self.target - self.value
if self.value > state.flow_before: if state.init:
state.flow_before_open = self.value self.start_direction(state)
elif self.value < state.flow_before - 1: state.ref_dif = abs(dif)
if state.wiggle < self.prop * 2: state.ref_time = state.now
return self.final_status(IDLE, '') state.in_tol_time = 0
state.wiggle = -state.wiggle / 2 difdir = dif * self._dir # negative when overshoot happend
self.command(mp=state.wiggle) # difdif = dif - state.prev_dif
state.start_wiggle = state.now # state.prev_dif = dif
return self.unblock_open expected_dif = state.ref_dif * math.exp((state.ref_time - state.now) / self.deriv)
if self.motor_state == 'closing':
tol = self._tolerance()
if difdir < tol:
# prev_minstep = state.last_minstep.pop(self._dir, None)
# attr = 'min_open_step' if self._dir > 0 else 'min_close_step'
# if prev_minstep is not None:
# # increase minstep
# minstep = getattr(self, attr)
# setattr(self, attr, minstep * 1.1)
# self.log.info('increase %s to %g', attr, minstep)
if difdir > -tol: # within tolerance
delta = state.delta()
state.in_tol_time += delta
if state.in_tol_time > self.settle:
# state.last_minstep.pop(self._dir, None)
self.log.info('at target %g %g', dif, tol)
return self.at_target
if difdir < 0:
return Retry
# self.log.info('minstep=0 dif=%g', dif)
else: # overshoot
self.log.info('overshoot %g', dif)
return self.raw_control
# # overshoot
# prev_minstep = state.last_minstep.pop(self._dir, None)
# if prev_minstep is None:
# minstep = getattr(self, attr) * 0.9
# self.log.info('decrease %s to %g', attr, minstep)
# setattr(self, attr, minstep)
# self.start_step(state, self.target)
# still approaching
if difdir <= expected_dif:
if difdir < expected_dif / 1.25 - tol:
state.ref_time = state.now
state.ref_dif = (difdir + tol) * 1.25
# self.log.info('new ref %g', state.ref_dif)
state.last_progress = state.now
return Retry # progressing: no pulse needed
if state.now < state.last_pulse_time + 2.5:
return Retry return Retry
if self.motor_state == 'idle': # TODO: check motor state for closed / opened ?
self.command(mp=state.wiggle) self.perform_pulse(state)
return Retry return Retry
if self.motor_state == 'closed':
if state.now < state.start_wiggle + 20:
return Retry
return self.final_status(ERROR, 'can not close')
return self.final_status(WARN, 'unblock interrupted')
def _tolerance(self): def _tolerance(self):
return min(self.tolerance * min(1, self.value / 2), self.tolerance2) return min(self.tolerance * min(1, self.value / 2), self.tolerance2)
@ -422,50 +609,67 @@ class NeedleValve(HasStates, CCU4Base, Drivable):
dif = self.target - self.value dif = self.target - self.value
if abs(dif) > self._tolerance(): if abs(dif) > self._tolerance():
state.in_tol_time = 0 state.in_tol_time = 0
self.log.info('unstable %g', dif)
return self.unstable return self.unstable
return Retry return Retry
@status_code(IDLE, 'unstable') @status_code(IDLE, 'unstable')
def unstable(self, state): def unstable(self, state):
difdir = (self.target - self.value) * self._dir
if difdir < 0 or self._dir == 0:
return self.raw_control
return self.controlling(state) return self.controlling(state)
def read_motor_state(self):
return self.command(fm=int)
@Command
def close(self):
"""close valve fully"""
self.command(mp=-60)
self.motor_state = self.command(fm=int)
self.start_machine(self.closing)
@status_code(BUSY) @status_code(BUSY)
def controlling(self, state): def closing(self, state):
delta = state.delta(0) if state.init:
dif = self.target - self.value state.start_time = state.now
difdif = dif - state.prev_dif self.read_motor_state()
state.prev_dif = dif if self.motor_state == M.closing:
self.motor_state = self.command('fm')
if self.motor_state == 'closed':
if dif < 0 or difdif < 0:
return Retry
return self.unblock_from_open
elif self.motor_state == 'opened': # trigger also when flow too high?
if dif > 0 or difdif > 0:
return Retry
self.command(mp=-60)
return self.unblock_from_open
tolerance = self._tolerance()
if abs(dif) < tolerance:
state.in_tol_time += delta
if state.in_tol_time > self.settle:
return self.at_target
return Retry return Retry
expected_dif = state.ref_dif * math.exp((state.now - state.ref_time) / self.deriv) if self.motor_state == M.closed:
if abs(dif) < expected_dif: return self.final_status(IDLE, 'closed')
if abs(dif) < expected_dif / 1.25: if state.now < state.start_time + 1:
state.ref_time = state.now
state.ref_dif = abs(dif) * 1.25
state.last_progress = state.now
return Retry # progress is fast enough
state.ref_time = state.now
state.ref_dif = abs(dif)
state.step += dif * delta * self.prop
if abs(state.step) < (state.now - state.last_progress) / self.step_factor:
# wait until step size is big enough
return Retry return Retry
self.command(mp=state.step) return self.final_status(IDLE, 'fixed')
return Retry
@Command
def open(self):
"""open valve fully"""
self.command(mp=60)
self.read_motor_state()
self.start_machine(self.opening)
@status_code(BUSY)
def opening(self, state):
if state.init:
state.start_time = state.now
self.read_motor_state()
if self.motor_state == M.opening:
return Retry
if self.motor_state == M.opened:
return self.final_status(IDLE, 'opened')
if state.now < state.start_time + 1:
return Retry
return self.final_status(IDLE, 'fixed')
@Command(FloatRange())
def pulse(self, value):
"""perform a motor pulse"""
self.log.info('pulse %g', value)
self.command(mp=value)
if value > 0:
self.motor_state = M.opening
return self.opening
self.motor_state = M.closing
return self.closing

View File

@ -56,7 +56,7 @@ class Drums(Writable):
self._pos = 0 self._pos = 0
for i, action in enumerate(self.pattern[self._pos:]): for i, action in enumerate(self.pattern[self._pos:]):
upper = action.upper() upper = action.upper()
relais = self.actions.get(action.upper()) relais = self.actions.get(upper)
if relais: if relais:
relais.write_target(upper == action) # True when capital letter relais.write_target(upper == action) # True when capital letter
else: else:

71
frappy_psi/hepump.py Normal file
View File

@ -0,0 +1,71 @@
# *****************************************************************************
#
# 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>
#
# *****************************************************************************
from frappy.core import BoolType, FloatRange, Parameter, Readable, Writable, Attached, EnumType, nopoll
from frappy_psi.trinamic import Motor
from frappy_psi.ccu4 import Pressure, NeedleValveFlow
class ValveMotor(Motor):
has_inputs = True
class HePump(Writable):
valvemotor = Attached(Motor)
flow = Attached(NeedleValveFlow)
valve = Attached(Writable)
value = Parameter(datatype=BoolType())
target = Parameter(datatype=BoolType())
pump_type = Parameter('pump type', EnumType(no=0, neodry=1, xds35=2, sv65=3), readonly=False, default=0)
eco_mode = Parameter('eco mode', BoolType(), readonly=False)
has_feedback = Parameter('feedback works', BoolType(), readonly=False, default=True)
FLOW_SCALE = {'no': 0, 'neodry': 0.55, 'xds35': 0.6, 'sv65': 0.9}
def write_target(self, value):
self.valvemotor.write_output0(value)
def read_target(self):
return self.valvemotor.read_output0()
def read_value(self):
if self.has_feedback:
return not self.valvemotor.read_input3()
return self.target
def write_pump_type(self, value):
self.flow.pressure_scale = self.FLOW_SCALE[value.name]
def read_eco_mode(self):
if self.pump_type == 'xds35':
return self.valvemotor.read_output1()
return False
def write_eco_mode(self, value):
if self.pump_type == 'xds35':
return self.valvemotor.write_output1(value)
# else silently ignore

View File

@ -18,16 +18,39 @@
# Jael Celia Lorenzana <jael-celia.lorenzana@psi.ch> # Jael Celia Lorenzana <jael-celia.lorenzana@psi.ch>
# ***************************************************************************** # *****************************************************************************
import os
from glob import glob
from frappy.core import Readable, Writable, Parameter, BoolType, StringType,\ from frappy.core import Readable, Writable, Parameter, BoolType, StringType,\
FloatRange, Property, TupleOf, ERROR, IDLE FloatRange, Property, TupleOf, ERROR, IDLE
from frappy.errors import ConfigError
from math import log from math import log
basepaths = '/sys/class/ionopimax', '/sys/class/ionopi'
class Base: class Base:
addr = Property('address', StringType()) addr = Property('address', StringType())
_devpath = None
devclass = None
def initModule(self):
super().initModule()
# candidates = glob(f'/sys/class/iono*/*/{self.addr}')
# if not candidates:
# raise ConfigError(f'can not find path for {self.addr}')
for basepath in basepaths:
for devclass in ([self.devclass] if isinstance(self.devclass, str) else self.devclass):
devpath = f'{basepath}/{devclass}'
if os.path.exists(devpath):
self._devpath = devpath
return
else:
self.log.info('%s does not exist', devpath)
else:
raise ConfigError(f'device path for {self.devclass} not found {devpath}')
def read(self, addr, scale=None): def read(self, addr, scale=None):
with open(f'/sys/class/ionopimax/{self.devclass}/{addr}') as f: with open(f'{self._devpath}/{addr}') as f:
result = f.read() result = f.read()
if scale: if scale:
return float(result) / scale return float(result) / scale
@ -35,7 +58,7 @@ class Base:
def write(self, addr, value, scale=None): def write(self, addr, value, scale=None):
value = str(round(value * scale)) if scale else str(value) value = str(round(value * scale)) if scale else str(value)
with open(f'/sys/class/ionopimax/{self.devclass}/{addr}', 'w') as f: with open(f'{self._devpath}/{addr}', 'w') as f:
f.write(value) f.write(value)
@ -49,7 +72,7 @@ class DigitalInput(Base, Readable):
class DigitalOutput(DigitalInput, Writable): class DigitalOutput(DigitalInput, Writable):
target = Parameter('output state', BoolType(), readonly=False) target = Parameter('output state', BoolType(), readonly=False)
devclass = 'digital_out' devclass = 'digital_out', 'relay'
def write_target(self, value): def write_target(self, value):
self.write(self.addr, value, 1) self.write(self.addr, value, 1)

View File

@ -1,16 +1,65 @@
""" # *****************************************************************************
Created on Tue Feb 4 11:07:56 2020 #
# This program is free software; you can redistribute it and/or modify it under
@author: tartarotti_d-adm # the terms of the GNU General Public License as published by the Free Software
""" # Foundation; either version 2 of the License, or (at your option) any later
# version.
#
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
# details.
#
# You should have received a copy of the GNU General Public License along with
# this program; if not, write to the Free Software Foundation, Inc.,
# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
# Module authors:
# Damaris Tartarotti Maimone
# Markus Zolliker <markus.zolliker@psi.ch>
#
# *****************************************************************************
"""support for ultrasound plot clients"""
import numpy as np import numpy as np
import matplotlib.pyplot as plt import matplotlib.pyplot as plt
# disable the behaviour of raising the window to the front each time it is updated
plt.rcParams["figure.raise_window"] = False
NAN = float('nan')
class Pause:
"""allows to leave the plot loop when the window is closed
Usage:
pause = Pause(fig)
# do initial plots
plt.show()
while pause(0.5):
# do plot updates
plt.draw()
"""
def __init__(self, fig):
fig.canvas.mpl_connect('close_event', self.on_close)
self.running = True
def on_close(self, event):
self.running = False
def __call__(self, interval):
try:
plt.pause(interval)
except Exception:
pass
return self.running
def rect(x1, x2, y1, y2): def rect(x1, x2, y1, y2):
return np.array([[x1,x2,x2,x1,x1],[y1,y1,y2,y2,y1]]) return np.array([[x1,x2,x2,x1,x1],[y1,y1,y2,y2,y1]])
NAN = float('nan')
def rects(intervals, y12): def rects(intervals, y12):
result = [rect(*intervals[0], *y12)] result = [rect(*intervals[0], *y12)]
@ -19,6 +68,7 @@ def rects(intervals, y12):
result.append(rect(*x12, *y12)) result.append(rect(*x12, *y12))
return np.concatenate(result, axis=1) return np.concatenate(result, axis=1)
class Plot: class Plot:
def __init__(self, maxy): def __init__(self, maxy):
self.lines = {} self.lines = {}
@ -26,6 +76,10 @@ class Plot:
self.first = True self.first = True
self.fig = None self.fig = None
def pause(self, interval):
"""will be overridden when figure is created"""
return False
def set_line(self, iax, name, data, fmt, **kwds): def set_line(self, iax, name, data, fmt, **kwds):
""" """
plot or update a line plot or update a line
@ -68,6 +122,7 @@ class Plot:
if self.first: if self.first:
plt.ion() plt.ion()
self.fig, axleft = plt.subplots(figsize=(15,7)) self.fig, axleft = plt.subplots(figsize=(15,7))
self.pause = Pause(self.fig)
plt.title("I/Q", fontsize=14) plt.title("I/Q", fontsize=14)
axleft.set_xlim(0, curves[0][-1]) axleft.set_xlim(0, curves[0][-1])
self.ax = [axleft, axleft.twinx()] self.ax = [axleft, axleft.twinx()]
@ -97,5 +152,6 @@ class Plot:
self.first = False self.first = False
plt.draw() plt.draw()
# TODO: do not know why this is needed:
self.fig.canvas.draw() self.fig.canvas.draw()
self.fig.canvas.flush_events() self.fig.canvas.flush_events()

File diff suppressed because it is too large Load Diff

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:
@ -273,7 +278,12 @@ class ResChannel(LakeShoreIO, Channel):
vexc = 0 if excoff or iscur else exc vexc = 0 if excoff or iscur else exc
if (rng, iexc, vexc) != (self.range, self.iexc, self.vexc): if (rng, iexc, vexc) != (self.range, self.iexc, self.vexc):
self._last_range_change = time.monotonic() self._last_range_change = time.monotonic()
self.range, self.iexc, self.vexc = rng, iexc, vexc try:
self.range, self.iexc, self.vexc = rng, iexc, vexc
except Exception:
# avoid raising errors on disabled channel
if self.enabled:
raise
@CommonWriteHandler(rdgrng_params) @CommonWriteHandler(rdgrng_params)
def write_rdgrng(self, change): def write_rdgrng(self, change):
@ -293,8 +303,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)

View File

@ -375,6 +375,7 @@ class HeaterOutput(HasInput, Writable):
class HeaterUpdate(HeaterOutput): class HeaterUpdate(HeaterOutput):
kind = 'HTR,TEMP' kind = 'HTR,TEMP'
target = 0 # switch off loop on startup
def update_target(self, module, value): def update_target(self, module, value):
self.change(f'DEV::TEMP:LOOP:ENAB', False, off_on) self.change(f'DEV::TEMP:LOOP:ENAB', False, off_on)

Some files were not shown because too many files have changed in this diff Show More