Compare commits

..

164 Commits
sinqtmp ... wip

Author SHA1 Message Date
eecbe9ca6b MA6: fix bad sea config 2025-06-06 12:17:05 +02:00
385480a8cc SEA: again fixing paramFilter 2025-06-06 12:15:48 +02:00
c92cb22d97 dil5: updated cfg to match class 2025-06-05 17:49:02 +02:00
d85d80ba36 dil5: working alfa version
Change-Id: Ib6bf2234633cc760fc771a3f5c0beb4cb63a0f6d
2025-06-05 17:48:15 +02:00
2b7ee0a72c addressed parameters
- generic method to access parameters with just an address
  changed to avoid boilerplate code
- it would really be nice to include the generic methods
  into Module

Change-Id: I898e5eeb282f03d3177a324fa88813976fb15f3c
2025-06-05 17:48:15 +02:00
97140aa3b4 SEA: do not override write_target method
else it will not wait for is_running

+ fix in playground
2025-06-05 11:52:50 +02:00
1ad699fb66 jtccr cfg: missing tolerance and settle (need '.' in rel_paths) 2025-06-05 11:52:50 +02:00
52f90fe5be frappy_psi.manual_valves: make persistent and clean up
Change-Id: Ifbdc886f556064745a7aaffbfdc1bad2ad74632e
2025-06-05 10:22:36 +02:00
404b38d91a frappy.params: fix typo
Change-Id: I724615de8d8d6b44bc5aceded85bf40d2a5e96f7
2025-06-05 10:22:07 +02:00
3203c6c9b3 [WIP] clean dil5 configuration 2025-06-05 10:20:54 +02:00
472ae3f04d [WIP] dil5 improvements
Change-Id: I2b439bf5898601e10448511479bc67afa3edb4d3
2025-06-05 10:16:47 +02:00
04f7f6ece5 bin/frappy-cli: fix hosts variable 2025-06-05 10:16:42 +02:00
54c9fb9db9 SEA: again a fix - commits before damaged 'mf' of MA10 2025-06-03 11:43:43 +02:00
c0f6569f1b frappy-cli: connect to servers on localhost by default
--scan option: specify where to scan if not on localhost

Change-Id: I51a694eb3cb045e7d18c19a332db8e6ba063009b
2025-06-02 15:24:04 +02:00
745e15c709 SEA: another fix: make sure the value parameter comes first 2025-06-02 11:01:12 +02:00
62adec4874 jtccr: the main subpath in sea is names 'main', not 'tt' 2025-06-02 09:17:48 +02:00
9131bdab51 SEA: fix lost parameter on top level 2025-06-02 09:01:13 +02:00
a0629db53d SEA: fix missing parameters at top level 2025-05-30 10:45:56 +02:00
415acb42cf frappy.client.SecopClient: fix setParameterFromString
add missing datatype.export_value

Change-Id: I824d922fdda1ab548e4625982e1485ec4b758cb9
Reviewed-on: https://forge.frm2.tum.de/review/c/secop/frappy/+/36172
Reviewed-by: Enrico Faulhaber <enrico.faulhaber@frm2.tum.de>
Tested-by: Jenkins Automated Tests <pedersen+jenkins@frm2.tum.de>
Reviewed-by: Markus Zolliker <markus.zolliker@psi.ch>
2025-05-27 15:54:27 +02:00
ee67a135e9 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>
2025-05-27 15:54:27 +02:00
ef826297fd 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>
2025-05-27 15:54:27 +02:00
50118e4ead haake: fix haake2 and add pumprunning 2025-05-27 15:51:54 +02:00
f9880f1473 frappy-psi.logo: fix IO.communicate reply 2025-05-27 15:41:33 +02:00
0dd13d7ef9 dil5/logo: fix valve addresses 2025-05-23 14:54:54 +02:00
6aec38d03e frappy_psi.logo: add comLog
+ fix bug in frappy_psi/dilution_statemachine.py
2025-05-23 10:31:51 +02:00
2238c20a37 DIL5: improve states in the state machine 2025-05-22 15:11:21 +02:00
018ed920b9 use longer, unique uris for USB serial connections 2025-05-22 15:10:38 +02:00
14f61b01db frappy_psi.pfeiffer_new: calculate crc in PfeifferProtocol 2025-05-22 15:08:41 +02:00
6fed5a3651 increase omit_unchanged_within to 60 s
+ fix a bug reading this from general config file
2025-05-22 08:38:51 +02:00
f3c7cdc7c4 frappy.client.SecopClient: fix setParameterFromString
add missing datatype.export_value

Change-Id: I824d922fdda1ab548e4625982e1485ec4b758cb9
Reviewed-on: https://forge.frm2.tum.de/review/c/secop/frappy/+/36172
Reviewed-by: Enrico Faulhaber <enrico.faulhaber@frm2.tum.de>
Tested-by: Jenkins Automated Tests <pedersen+jenkins@frm2.tum.de>
Reviewed-by: Markus Zolliker <markus.zolliker@psi.ch>
2025-05-22 08:38:51 +02:00
4f3ebf0aaf 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>
2025-05-22 08:38:51 +02:00
85f14ace40 add units to jtccr 2025-05-22 08:38:51 +02:00
bc1ba4abb9 haake: make 2 versions depending on extra T sensor 2025-05-22 08:38:51 +02:00
cf50a372b3 varioxb: uncomment motor setup with triple quote 2025-05-22 08:38:51 +02:00
67ea5340ee ori4: revert removing '.' from rel_path
not sure if this is correct
2025-05-22 08:38:51 +02:00
7d6ac4d742 dil2/3/4: make He3 pump visible 2025-05-22 08:38:51 +02:00
a76e529a82 frappy_psi.phytron: improve clear_errors message 2025-05-22 08:38:51 +02:00
7dfe2925aa 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>
2025-05-22 08:38:51 +02:00
a6c86a5fe7 [WIP] further work on CCU4
Change-Id: Icdd6e253f276a5ade44fb4103306160fc348556b
2025-05-16 16:20:59 +02:00
d3280474c3 frappy.client.interactive: no pathlib needed here
allow the argument of the run function to be a simple string

Change-Id: I1d5de3802b6adc22a01a262d4cb017bf7dd084c5
Reviewed-on: https://forge.frm2.tum.de/review/c/secop/frappy/+/36343
Reviewed-by: Markus Zolliker <markus.zolliker@psi.ch>
Tested-by: Jenkins Automated Tests <pedersen+jenkins@frm2.tum.de>
2025-05-16 15:43:00 +02:00
7cf32c4e7c config: add 'include' and 'override'
including a config file and overriding some properties is
helpful when we do not want to modify the original config
but run it with sligthly different properties.

this involves some redesign a.o.:
- modules are collected in a dict instead of a list in
  order for 'override' to find the related module
- checking for duplicates happens in the Collector

Do not warn when included file does not end with '_cfg.py',
as this may be intentional, in case a file is only used
via 'include' and not as cfg file alone.

+ remove unused method Collector.append
+ complain with specific error message when Node is not given

Change-Id: Id568f04d6d84622ef2547412eb6f288fcebf986f
Reviewed-on: https://forge.frm2.tum.de/review/c/secop/frappy/+/36357
Tested-by: Jenkins Automated Tests <pedersen+jenkins@frm2.tum.de>
Reviewed-by: Markus Zolliker <markus.zolliker@psi.ch>
2025-05-16 15:43:00 +02:00
Jenkins system
48b79af96a [deb] Release v0.20.5 2025-05-16 15:43:00 +02:00
b1c920819e frappy.client.interactive: improve updates while driving
- instead to show first current 'value' and 'status', and then
  the changes, show changes only - this way updates appear
  in the expected order
- for this SecopClient.register_callback needs a 'callimmediately'
  argument

Change-Id: I3e91c2c15bca7fee2eba3b1bf1dd27313da3ae29
Reviewed-on: https://forge.frm2.tum.de/review/c/secop/frappy/+/36291
Reviewed-by: Markus Zolliker <markus.zolliker@psi.ch>
Tested-by: Jenkins Automated Tests <pedersen+jenkins@frm2.tum.de>
2025-05-12 10:48:14 +02:00
8f2973c39d client: add SecopClient.execCommandFromString
analogous to setParameterFromString
to be used in simple clients where values of structured datatypes
are just python literals

Change-Id: I4936a3c5c0cac45bd7e875c938ce2700b4f4a979
Reviewed-on: https://forge.frm2.tum.de/review/c/secop/frappy/+/36262
Reviewed-by: Markus Zolliker <markus.zolliker@psi.ch>
Tested-by: Jenkins Automated Tests <pedersen+jenkins@frm2.tum.de>
2025-05-12 10:46:39 +02:00
6343edc3cb frappy_psi/ls370res: various bug fixes
- avoid some error messages on disabled channels
- update value of channel switcher properly
- fix bug in set_param method

Change-Id: I16f5cf2d7abce0c0fae17266f0c8e949e8416b7a
Reviewed-on: https://forge.frm2.tum.de/review/c/secop/frappy/+/36263
Reviewed-by: Markus Zolliker <markus.zolliker@psi.ch>
Tested-by: Jenkins Automated Tests <pedersen+jenkins@frm2.tum.de>
2025-05-12 10:45:04 +02:00
bd00758460 frappy.client.SecopClient: fix setParameterFromString
add missing datatype.export_value

Change-Id: I824d922fdda1ab548e4625982e1485ec4b758cb9
Reviewed-on: https://forge.frm2.tum.de/review/c/secop/frappy/+/36172
Reviewed-by: Enrico Faulhaber <enrico.faulhaber@frm2.tum.de>
Tested-by: Jenkins Automated Tests <pedersen+jenkins@frm2.tum.de>
Reviewed-by: Markus Zolliker <markus.zolliker@psi.ch>
2025-05-12 10:44:47 +02:00
Konstantin Kholostov
d2cce8f21c installer: add recipe to build macOS app bundle
Change-Id: Ief4401626db293fecc37f1d1ec72cf295b55fccf
Reviewed-on: https://forge.frm2.tum.de/review/c/secop/frappy/+/36060
Reviewed-by: Markus Zolliker <markus.zolliker@psi.ch>
Tested-by: Jenkins Automated Tests <pedersen+jenkins@frm2.tum.de>
Reviewed-by: Alexander Zaft <a.zaft@fz-juelich.de>
2025-05-12 10:41:59 +02:00
f6a5ef8f4d Add DIL5 Statemachine and LOGO 2025-05-09 10:35:11 +02:00
dad9536eb5 [WIP] fi furnace improvements
- still under development

Change-Id: I5fc22f041fb136b549016f510f06ea703122bee5
2025-05-08 08:29:45 +02:00
ccc66468d4 change prot for dummy to 5000
Change-Id: If35bbe6783fe133c0c6c87f402ba70aec00fa964
2025-05-01 11:43:49 +02:00
52215f9ec1 dummy_cfg.py: add test case with big enum
+ fix undefined value in frappy_psi.ls370res.Switcher

Change-Id: I59f2814b945533c487999f9af638e0fb2040e862
2025-05-01 08:52:30 +02:00
58549065fb more demo test cases
- added them to cfg/dummy_cfg.py
+ treat enum correctly in SecopClient.setParameterFromString

Change-Id: Ia5b2d8d3a21c3215cb93d90975086eb9995b1543
2025-04-25 15:38:16 +02:00
0230641b1d logdif.py: use single key to stop or continue
Change-Id: I53b3254074eda7491dd16bbc39168960b0980e39
2025-04-24 11:24:46 +02:00
b264455ad3 follow-up change to 35931: make Proxy a Module
Proxy must be a class, because of the new check for configured
'cls' inheriting from Module. Use Proxy.__new__ to implement
this.

Change-Id: I4bb036afc2ce92187a9049dff0a6f22b20c3a260
Reviewed-on: https://forge.frm2.tum.de/review/c/secop/frappy/+/36104
Reviewed-by: Markus Zolliker <markus.zolliker@psi.ch>
Tested-by: Jenkins Automated Tests <pedersen+jenkins@frm2.tum.de>
Reviewed-by: Enrico Faulhaber <enrico.faulhaber@frm2.tum.de>
2025-04-24 11:14:35 +02:00
07c5b32c5f revert 'change to new visibility spec'
nicos is not yet ready for this

Change-Id: Ibfbb7e32e06a6e0616ded8342bc5844fd531944f
2025-04-24 11:09:26 +02:00
PREVENT_DEFAULT
80cb3f08d7 add sr830 addons cfg 2025-04-24 10:50:09 +02:00
fb4755502b frappy_psi.parmod: extract a tuple element as own moudle
Change-Id: I8d904ed21f8a5c16ae71daf30c9a1ea42876b451
2025-04-24 10:48:35 +02:00
3580cb9dc0 frappy_psi.ionopimax.AnalogInput: set value range on datainfo
Change-Id: I1e3da956e829f69a0af416b7beadb81bd6bc0cb1
2025-04-23 08:37:55 +02:00
d681507f94 frappy_psi.furnace: special classes PTXgauge and PRtransmitter
move some initialization from cfg file to source code
+ make 'out of calibrated range' and 'sensor break' more generic

Change-Id: I3e92100fdb9c983f82665de9d8e063609cd7af5a
2025-04-23 08:28:08 +02:00
e0bd84cc3b change to new visibility spec
+ visibiliy is no longer an EnumType, as this would break
  the specs

Change-Id: I1197c82f31c33c210fdcda0b49a0c38027880d77
Reviewed-on: https://forge.frm2.tum.de/review/c/secop/frappy/+/36088
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>
2025-04-23 08:20:54 +02:00
9545cb4188 make sure unexported modules are initialized
take the opportunity for a small redesign:

- create a new method build_descriptive_data which
  calls secnode.get_modules also on unexported modules.

+ cache descriptive data

Change-Id: I4a0b8ac96108463dc0c800bb11a404206c26b092
Reviewed-on: https://forge.frm2.tum.de/review/c/secop/frappy/+/36089
Tested-by: Jenkins Automated Tests <pedersen+jenkins@frm2.tum.de>
Reviewed-by: Markus Zolliker <markus.zolliker@psi.ch>
Reviewed-by: Enrico Faulhaber <enrico.faulhaber@frm2.tum.de>
2025-04-23 08:20:54 +02:00
1fead8b2c6 better dummy server for seaweb tests
- new config file dummy
- frappy_demo.test.Temp now creates WARN and ERROR status
  and may be disabled

Change-Id: Ibc7bb565f18c2c12cdc2a77bea1ee1bf1cc8bd41
2025-04-22 18:06:23 +02:00
809eda314b ionopimax: bug fixes 2025-04-15 10:57:02 +02:00
ca6fd1dd5e frappy_psi.ionopimax redesign
Change-Id: I46b62522c24ad9f0352ba7a784d39ffd1cb79ef3
2025-04-15 09:00:30 +02:00
d0c063c60b [WIP] further fixes for linse-fi 2025-04-14 17:26:26 +02:00
7a59cf4956 frappy-play: fix import order 2025-04-14 17:20:12 +02:00
7254d7f95c [WIP] fixes for linse-fi
Change-Id: Iac28e9654a764331cd903896879834cd6127a919
2025-04-14 11:46:02 +02:00
c368292873 fixes on picontrol and tdkpower
Change-Id: Ia891e7df23d8408b857dac795ed0ad9973ccf993
2025-04-08 17:15:17 +02:00
6a2aece383 fixes on small furnace 2025-04-08 17:12:44 +02:00
ad76a5d752 add fi_cfg.py (ILL furnace)
Change-Id: I8720dbeb3f29b07eaeae59558c58b2fa87096dc9
2025-04-08 15:22:35 +02:00
42e40db14b WIP frappy_psi/tdkpower
Change-Id: I80d1beb0fae2a1cdd2aa5fabc5d31c651c2cb3e7
2025-04-08 08:32:26 +02:00
343ce90321 peus-plot: optionally give x-range as 2nd argument
Change-Id: I445ef00487fc34343f5a0333643f61753a1c4948
2025-04-02 14:56:47 +02:00
75783b211a ultrasound.ControlRoi: fix control mechanism
Change-Id: I9d01de260d3bdc63eb1004ba4f714d38d1c0508c
2025-04-02 14:56:47 +02:00
l_samenv
36f2919ec2 cfg/ls370test update 2025-04-01 09:56:39 +02:00
7cca3192df improve error messages on module creation
- add name when target and value datatype are incompatible
- check that module class inherits from Module

Change-Id: I4edbdff1c250b64b74b1adf7287f9659dff69b26
Reviewed-on: https://forge.frm2.tum.de/review/c/secop/frappy/+/35931
Reviewed-by: Markus Zolliker <markus.zolliker@psi.ch>
Tested-by: Jenkins Automated Tests <pedersen+jenkins@frm2.tum.de>
2025-04-01 09:55:44 +02:00
a632c53405 fix overriding Parameter with value
a property declared in a base class may be overriden
with a parameter in a subclass. this is already allowed.
if then, in the subsubclass it is overridden by a bare value,
it fails.

Patchset 1: add a test for this
Patchset 4: add the fix

Change-Id: Ia5a26076a9ee98439932643a03878342d56f8396
Reviewed-on: https://forge.frm2.tum.de/review/c/secop/frappy/+/35932
Reviewed-by: Markus Zolliker <markus.zolliker@psi.ch>
Tested-by: Jenkins Automated Tests <pedersen+jenkins@frm2.tum.de>
2025-04-01 09:55:44 +02:00
a76425cb2e ultrasound.PE: fix control loop
Change-Id: I2b2bdf4ad48887ae256a68119f53e6a38048ce1e
2025-04-01 09:54:18 +02:00
d231e9ce06 [WIP] ccracks / ccu4: split ccu() into nv(), he() and flow()
Change-Id: I346330a5f350bf03eefe86c8e890b59afaaaa231
2025-03-31 17:30:29 +02:00
44750572d9 Merge branch 'wip' of gitlab.psi.ch:samenv/frappy into wip 2025-03-31 17:26:02 +02:00
e0ef6047e2 ultrasound.PulseEcho: fix issue with roi intervals
now it should be time +- 0.5 * size
2025-03-31 17:25:53 +02:00
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
16 changed files with 1157 additions and 810 deletions

View File

@ -24,12 +24,14 @@
import sys
import argparse
import socket
from pathlib import Path
# Add import path for inplace usage
sys.path.insert(0, str(Path(__file__).absolute().parents[1]))
from frappy.client.interactive import init, run, clientenv, interact
from frappy.protocol.discovery import scan
def parseArgv(argv):
@ -37,6 +39,9 @@ def parseArgv(argv):
parser.add_argument('-i', '--include',
help='file to execute after connecting to the clients', metavar='file',
type=Path, action='append', default=[])
parser.add_argument('-s', '--scan',
help='hosts to scan for (-s subnet for all nodes in subnet)',
action='append', default=[])
parser.add_argument('-o', '--only-execute',
help='Do not go into interactive mode after executing files. \
Has no effect without --include.', action='store_true')
@ -46,9 +51,38 @@ def parseArgv(argv):
return parser.parse_args(argv)
def own_ip():
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s.settimeout(0)
try:
# doesn't even have to be reachable
s.connect(('10.254.254.254', 1))
return s.getsockname()[0]
except Exception:
return '127.0.0.1'
finally:
s.close()
args = parseArgv(sys.argv[1:])
success = init(*args.node)
nodes = args.node
hosts = args.scan
if not nodes and not hosts:
hosts = ['localhost']
if hosts:
answers = []
for host in hosts:
ans = scan()
if host == 'subnet': # all in subnet
answers.extend(ans)
else: # filter by ip
ip = socket.gethostbyname(host)
if ip == '127.0.0.1':
ip = own_ip()
answers.extend(a for a in ans if a.address == ip)
nodes.extend(f'{h.hostname}:{h.port}' for h in answers)
success = init(*nodes)
run_error = ''
file_success = False

View File

@ -23,54 +23,17 @@
"""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)
from frappy.protocol.discovery import scan, listen
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}')
print(f'{answer.equipment_id} {answer.hostname}:{answer.port}')
return
print(f'Found {answer.equipment_id} at {address}{numeric}:')
numeric = f' ({answer.address})' if answer.address == answer.hostname else ''
print(f'Found {answer.equipment_id} at {answer.hostname}{numeric}:')
print(f' Port: {answer.port}')
print(f' Firmware: {answer.firmware}')
desc = answer.description.replace('\n', '\n ')
@ -78,51 +41,6 @@ def print_answer(answer, *, short=False):
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',
@ -136,4 +54,5 @@ if __name__ == '__main__':
for answer in scan():
print_answer(answer, short=short)
if args.listen:
listen(short=short)
for answer in listen():
print_answer(short=short)

View File

@ -11,13 +11,13 @@ lsc_uri = '192.168.1.2:7777'
#lsc_uri='serial:///dev/ttyACM1?baudrate=57600+parity=odd+bytesize=7+stopbits=1',
Node('dil5.psi.ch',
'dil5 with state machine for condensing and removing',
Node('dil5_logo.psi.ch',
'dil5 logo test',
interface='tcp://5000',
secondary = ['ws://8010']
)
Mod('io',
Mod('logo',
'frappy_psi.logo.IO',
'',
ip_address = "192.168.0.3",
@ -26,155 +26,101 @@ Mod('io',
)
Mod('V1',
'frappy_psi.logo.Valve',
'frappy_psi.logo.DigitalActuator',
'Valves',
io = 'io',
vm_address_input ="V1025.0",
vm_address_output ="V1064.3"
io = 'logo',
feedback_addr ="V1025.0",
output_addr ="V1064.3"
)
Mod('V2',
'frappy_psi.logo.Valve',
'Valves',
io = 'io',
vm_address_input ="V1024.2",
vm_address_output ="V1064.0",
'frappy_psi.logo.DigitalActuator',
'dil bypass',
io = 'logo',
feedback_addr ="V1024.2",
output_addr ="V1064.0",
)
Mod('V4',
'frappy_psi.logo.Valve',
'Valves',
io = 'io',
vm_address_input ="V1024.5",
vm_address_output ="V1064.7",
'frappy_psi.logo.DigitalActuator',
'compressor to dump',
io = 'logo',
# feedback_addr ="V1024.5", # not verified
output_addr ="V1064.7",
target_addr ="V404.1",
)
Mod('V5',
'frappy_psi.logo.Valve',
'Valves',
io = 'io',
vm_address_input ="V1024.4",
vm_address_output ="V1064.2"
'frappy_psi.logo.DigitalActuator',
'compressor input',
io = 'logo',
feedback_addr ="V1024.4",
output_addr ="V1064.2",
)
Mod('V9',
'frappy_psi.logo.Valve',
'Valves',
io = 'io',
vm_address_input ="V1024.3",
vm_address_output ="V404.1",
'frappy_psi.logo.DelayedActuator',
'dump output',
io = 'logo',
delay_addr = 'VW24',
feedback_addr ="V1024.3",
output_addr ="V1064.5",
target_addr ="V404.3",
)
Mod('pump',
'frappy_psi.logo.FluidMachines',
'Pump',
io = 'io',
vm_address_output ="V414.1"
Mod('forepump',
'frappy_psi.logo.DigitalActuator',
'forepump',
io = 'logo',
output_addr ="V1064.6",
target_addr ="V404.4",
)
Mod('compressor',
'frappy_psi.logo.FluidMachines',
'Compressor',
io = 'io',
vm_address_output ="V400.1"
'frappy_psi.logo.DigitalActuator',
'',
io = 'logo',
output_addr ="V1064.4",
target_addr ="V404.2",
)
Mod('p2',
'frappy_psi.logo.Pressure',
'Pressure in mBar',
io = 'io',
vm_address ="VW0",
'frappy_psi.logo.Value',
'pressure after compressor',
io = 'logo',
addr ="VW0",
value = Param(unit='mbar'),
)
Mod('p1',
'frappy_psi.logo.Pressure',
'Pressure in mBar',
io = 'io',
vm_address ="VW2",
'frappy_psi.logo.Value',
'dump pressure',
io = 'logo',
addr ="VW28",
value = Param(unit='mbar'),
)
Mod('p5',
'frappy_psi.logo.Pressure',
'Pressure in mBar',
io = 'io',
vm_address ="VW4",
'frappy_psi.logo.Value',
'pressure after forepump',
io = 'logo',
addr ="VW4",
value = Param(unit='mbar'),
)
Mod('Druckluft',
'frappy_psi.logo.Airpressure',
Mod('airpressure',
'frappy_psi.logo.DigitalValue',
'Airpressure state',
io = 'io',
vm_address ="VW6",
io = 'logo',
addr ="V1024.7",
)
Mod('SF1',
'frappy_psi.logo.safetyfeatureState',
'Safety Feature',
io = 'io',
vm_address ="V410.1",
)
Mod('SF2',
'frappy_psi.logo.safetyfeatureState',
'Safety Feature',
io = 'io',
vm_address ="V406.1",
)
Mod('SF3',
'frappy_psi.logo.safetyfeatureState',
'Safety Feature',
io = 'io',
vm_address ="V408.1",
)
Mod('SF4',
'frappy_psi.logo.safetyfeatureState',
'Safety Feature',
io = 'io',
vm_address ="V412.1",
)
Mod('p2max',
'frappy_psi.logo.safetyfeatureParam',
'Safety Feature Param',
io = 'io',
target = 2000,
vm_address ="VW8",
)
Mod('pcond',
'frappy_psi.logo.safetyfeatureParam',
'Safety Feature Param',
io = 'io',
target = 1800,
vm_address ="VW10",
)
Mod('p5min',
'frappy_psi.logo.safetyfeatureParam',
'Safety Feature Param',
io = 'io',
target = 0,
vm_address ="VW12",
)
Mod('p5max',
'frappy_psi.logo.safetyfeatureParam',
'Safety Feature Param',
io = 'io',
target = 900,
vm_address ="VW14",
)
Mod('io_ls273',
'frappy_psi.ls372.StringIO',
'io for Ls372',
uri=lsc_uri,
)
Mod('sw',
'frappy_psi.ls372.Switcher',
'channel switcher',
@ -361,41 +307,31 @@ Mod('T_cond',
io='itc',
)
Mod('stateMachine',
'frappy_psi.dilution_statemachine.DIL5',
'Statemachine',
Mod('safety',
'frappy_psi.dilution.Interlock',
'interlock mechanism',
io='logo',
dil='dil',
)
Mod('dil',
'frappy_psi.dilution.DIL5',
'dilution state machine and parameters',
condenseline_pressure = "p2",
condense_valve = "V9",
dump_valve = "V4",
circulate_pump = "pump",
forepump = "forepump",
compressor = "compressor",
turbopump = "turbopump",
condenseline_valve = "V1",
circuitshort_valve = "V2",
still_pressure = "p3",
still_pressure = "p4",
still_pressure_turbo = "p3",
#ls372 = "res1",
V5 = "V5",
p1 = "p1",
MV10 = 'MV10',
MV13 ='MV13',
MV8 = 'MV8',
MVB = 'MVB',
MV2 = 'MV2',
MV1 = 'MV1',
MV3a = 'MV3a',
MV3b = 'MV3b',
GV1 = 'GV1',
MV14 = 'MV14',
MV12 = 'MV12',
MV11 = 'MV11',
MV9 = 'MV9',
GV2 = 'GV2',
condensing_p_low = 150,
condensing_p_high = 250
dump_pressure = "p1",
condensing_p_low = 1200,
condensing_p_high = 1500,
)

View File

@ -14,7 +14,7 @@ Mod('tt',
io='sea_main',
meaning=['temperature_regulation', 27],
sea_object='tt',
rel_paths=['tt', 'set'],
rel_paths=['main', '.', 'set'],
value=Param(unit='K'),
)

View File

@ -9,12 +9,21 @@ Mod('sea_main',
service='main',
)
#Mod('tt',
# 'frappy_psi.sea.SeaDrivable', '',
# io='sea_main',
# meaning=['temperature_regulation', 27],
# sea_object='tt',
# rel_paths=['tm', '.', 'set', 'dblctrl'],
#)
Mod('tt',
'frappy_psi.sea.SeaDrivable', '',
'frappy_psi.sea.LscDrivable', '',
io='sea_main',
meaning=['temperature_regulation', 27],
sea_object='tt',
rel_paths=['.', 'tm', 'set', 'dblctrl'],
sensor_path='tm',
set_path='set',
)
Mod('cc',

View File

@ -1,50 +1,50 @@
{"tt": {"base": "/tt", "params": [
{"path": "", "type": "float", "readonly": false, "cmd": "run tt", "description": "tt", "kids": 18},
{"path": "send", "type": "text", "readonly": false, "cmd": "tt send", "visibility": 3},
{"path": "status", "type": "text", "readonly": false, "cmd": "run tt", "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", "readonly": false, "cmd": "run tt"},
{"path": "running", "type": "int", "readonly": false, "cmd": "run tt"},
{"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", "readonly": false, "cmd": "run tt", "visibility": 3},
{"path": "log/m2", "type": "float", "readonly": false, "cmd": "run tt", "visibility": 3},
{"path": "log/stddev", "type": "float", "readonly": false, "cmd": "run tt", "visibility": 3},
{"path": "log/n", "type": "float", "readonly": false, "cmd": "run tt", "visibility": 3},
{"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", "readonly": false, "cmd": "run tt"},
{"path": "dblctrl/shift_lo", "type": "float", "readonly": false, "cmd": "run tt"},
{"path": "dblctrl/t_min", "type": "float", "readonly": false, "cmd": "run tt"},
{"path": "dblctrl/t_max", "type": "float", "readonly": false, "cmd": "run tt"},
{"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", "readonly": false, "cmd": "run tt", "kids": 4},
{"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", "readonly": false, "cmd": "run tt"},
{"path": "tm/raw", "type": "float", "readonly": false, "cmd": "run tt"},
{"path": "ts", "type": "float", "readonly": false, "cmd": "run tt", "kids": 4},
{"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", "readonly": false, "cmd": "run tt"},
{"path": "ts/raw", "type": "float", "readonly": false, "cmd": "run tt"},
{"path": "ts_2", "type": "float", "readonly": false, "cmd": "run tt", "kids": 4},
{"path": "ts_2/curve", "type": "text", "readonly": false, "cmd": "tt ts_2/curve", "kids": 1},
{"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"},
{"path": "ts_2/stddev", "type": "float", "readonly": false, "cmd": "run tt"},
{"path": "ts_2/raw", "type": "float", "readonly": false, "cmd": "run tt"},
{"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", "readonly": false, "cmd": "run tt"},
{"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)"},
@ -53,17 +53,17 @@
{"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", "readonly": false, "cmd": "run tt", "description": "the maximum power limit (before any booster or converter)"},
{"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", "readonly": false, "cmd": "run tt", "description": "the maximum current before any booster or converter"},
{"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", "readonly": false, "cmd": "run tt"},
{"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": "setsamp", "type": "float", "readonly": false, "cmd": "tt setsamp", "kids": 18},
{"path": "setsamp/mode", "type": "enum", "enum": {"disabled": -1, "off": 0, "controlling": 1, "manual": 2}, "readonly": false, "cmd": "tt setsamp/mode"},
{"path": "setsamp/reg", "type": "float", "readonly": false, "cmd": "run tt"},
{"path": "setsamp/reg", "type": "float"},
{"path": "setsamp/ramp", "type": "float", "readonly": false, "cmd": "tt setsamp/ramp", "description": "maximum ramp in K/min (0: ramp off)"},
{"path": "setsamp/wramp", "type": "float", "readonly": false, "cmd": "tt setsamp/wramp"},
{"path": "setsamp/smooth", "type": "float", "readonly": false, "cmd": "tt setsamp/smooth", "description": "smooth time (minutes)"},
@ -72,16 +72,16 @@
{"path": "setsamp/resist", "type": "float", "readonly": false, "cmd": "tt setsamp/resist"},
{"path": "setsamp/maxheater", "type": "text", "readonly": false, "cmd": "tt setsamp/maxheater", "description": "maximum heater limit, units should be given without space: W, mW, A, mA"},
{"path": "setsamp/linearpower", "type": "float", "readonly": false, "cmd": "tt setsamp/linearpower", "description": "when not 0, it is the maximum effective power, and the power is linear to the heater output"},
{"path": "setsamp/maxpowerlim", "type": "float", "readonly": false, "cmd": "run tt", "description": "the maximum power limit (before any booster or converter)"},
{"path": "setsamp/maxpowerlim", "type": "float", "description": "the maximum power limit (before any booster or converter)"},
{"path": "setsamp/maxpower", "type": "float", "readonly": false, "cmd": "tt setsamp/maxpower", "description": "maximum power [W]"},
{"path": "setsamp/maxcurrent", "type": "float", "readonly": false, "cmd": "run tt", "description": "the maximum current before any booster or converter"},
{"path": "setsamp/maxcurrent", "type": "float", "description": "the maximum current before any booster or converter"},
{"path": "setsamp/manualpower", "type": "float", "readonly": false, "cmd": "tt setsamp/manualpower"},
{"path": "setsamp/power", "type": "float", "readonly": false, "cmd": "run tt"},
{"path": "setsamp/power", "type": "float"},
{"path": "setsamp/prop", "type": "float", "readonly": false, "cmd": "tt setsamp/prop", "description": "bigger means more gain"},
{"path": "setsamp/integ", "type": "float", "readonly": false, "cmd": "tt setsamp/integ", "description": "bigger means faster"},
{"path": "setsamp/deriv", "type": "float", "readonly": false, "cmd": "tt setsamp/deriv"},
{"path": "display", "type": "text", "readonly": false, "cmd": "tt display"},
{"path": "remote", "type": "bool", "readonly": false, "cmd": "run tt"}]},
{"path": "remote", "type": "bool"}]},
"cc": {"base": "/cc", "params": [
{"path": "", "type": "bool", "kids": 96},
@ -108,7 +108,7 @@
{"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": "hav", "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"},
@ -132,26 +132,26 @@
{"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": "hea", "type": "enum", "enum": {"0": 0, "1": 1, "6": 6}, "readonly": false, "cmd": "cc hea"},
{"path": "hch", "type": "int", "readonly": false, "cmd": "cc hch"},
{"path": "hwr0", "type": "float", "readonly": false, "cmd": "cc hwr0"},
{"path": "hem0", "type": "float", "readonly": false, "cmd": "cc hem0", "description": "sensor length in mm from top to empty pos."},
{"path": "hfu0", "type": "float", "readonly": false, "cmd": "cc hfu0", "description": "sensor length in mm from top to full pos."},
{"path": "hd0", "type": "float", "readonly": false, "cmd": "cc hd0", "description": "external sensor drive current (mA)"},
{"path": "h0", "type": "float"},
{"path": "hs0", "type": "enum", "enum": {"sens_ok": 0, "sens_warm": 1, "no_sens": 2, "timeout": 3, "not_yet_read": 4, "disabled": 5}},
{"path": "h1", "type": "float"},
{"path": "hs1", "type": "enum", "enum": {"sens_ok": 0, "sens_warm": 1, "no_sens": 2, "timeout": 3, "not_yet_read": 4, "disabled": 5}},
{"path": "h2", "type": "float"},
{"path": "hs2", "type": "enum", "enum": {"sens_ok": 0, "sens_warm": 1, "no_sens": 2, "timeout": 3, "not_yet_read": 4, "disabled": 5}},
{"path": "h3", "type": "float"},
{"path": "hs3", "type": "enum", "enum": {"sens_ok": 0, "sens_warm": 1, "no_sens": 2, "timeout": 3, "not_yet_read": 4, "disabled": 5}},
{"path": "h4", "type": "float"},
{"path": "hs4", "type": "enum", "enum": {"sens_ok": 0, "sens_warm": 1, "no_sens": 2, "timeout": 3, "not_yet_read": 4, "disabled": 5}},
{"path": "h5", "type": "float"},
{"path": "hs5", "type": "enum", "enum": {"sens_ok": 0, "sens_warm": 1, "no_sens": 2, "timeout": 3, "not_yet_read": 4, "disabled": 5}},
{"path": "hfb", "type": "float"},
{"path": "nav", "type": "enum", "type": "enum", "enum": {"none": 0, "int": 1, "ext": 2}, "readonly": false, "cmd": "cc nav"},
{"path": "nav", "type": "bool", "readonly": false, "cmd": "cc nav"},
{"path": "nu", "type": "float"},
{"path": "nl", "type": "float"},
{"path": "nth", "type": "float", "readonly": false, "cmd": "cc nth"},
@ -183,15 +183,16 @@
{"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": "", "type": "enum", "enum": {"fixed": 0, "controlled": 1, "automatic": 2, "close": 3, "open": 4}, "readonly": false, "cmd": "nv", "kids": 12},
{"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": "flowp", "type": "float", "description": "flow calculated from pressure before pump"},
{"path": "span", "type": "float"},
{"path": "use_pressure", "type": "bool", "readonly": false, "cmd": "nv use_pressure", "description": "use pressure instead of flow meter for control"},
{"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"},
@ -235,15 +236,34 @@
{"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"}]},
"hefill": {"base": "/hefill", "params": [
{"path": "", "type": "enum", "enum": {"watching": 0, "filling": 1, "inactive": 2, "manualfill": 3}, "readonly": false, "cmd": "hefill", "kids": 16},
{"path": "send", "type": "text", "readonly": false, "cmd": "hefill send", "visibility": 3},
{"path": "status", "type": "text", "visibility": 3},
{"path": "state", "type": "text"},
{"path": "readpath", "type": "text", "readonly": false, "cmd": "hefill readpath", "visibility": 3},
{"path": "lowlevel", "type": "float", "readonly": false, "cmd": "hefill lowlevel"},
{"path": "highlevel", "type": "float", "readonly": false, "cmd": "hefill highlevel"},
{"path": "smooth", "type": "float"},
{"path": "minfillminutes", "type": "float", "readonly": false, "cmd": "hefill minfillminutes"},
{"path": "maxfillminutes", "type": "float", "readonly": false, "cmd": "hefill maxfillminutes"},
{"path": "minholdhours", "type": "float", "readonly": false, "cmd": "hefill minholdhours"},
{"path": "maxholdhours", "type": "float", "readonly": false, "cmd": "hefill maxholdhours"},
{"path": "tolerance", "type": "float", "readonly": false, "cmd": "hefill tolerance"},
{"path": "badreadingminutes", "type": "float", "readonly": false, "cmd": "hefill badreadingminutes"},
{"path": "tubecoolingminutes", "type": "float", "readonly": false, "cmd": "hefill tubecoolingminutes"},
{"path": "vessellimit", "type": "float", "readonly": false, "cmd": "hefill vessellimit"},
{"path": "vext", "type": "float"}]},
"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"},
{"path": "auto", "type": "bool", "readonly": false, "cmd": "hepump auto"},
{"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"},
{"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"}]},
@ -291,11 +311,11 @@
{"path": "save", "type": "bool", "readonly": false, "cmd": "nvflow save", "description": "unchecked: current calib is not saved. set checked: save calib"}]},
"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, "filling": 1, "inactive": 2, "manualfill": 3}, "readonly": false, "cmd": "ln2fill", "kids": 14},
{"path": "send", "type": "text", "readonly": false, "cmd": "ln2fill send", "visibility": 3},
{"path": "status", "type": "text", "visibility": 3},
{"path": "state", "type": "text"},
{"path": "readlevel", "type": "text", "readonly": false, "cmd": "ln2fill readlevel", "visibility": 3},
{"path": "readpath", "type": "text", "readonly": false, "cmd": "ln2fill readpath", "visibility": 3},
{"path": "lowlevel", "type": "float", "readonly": false, "cmd": "ln2fill lowlevel"},
{"path": "highlevel", "type": "float", "readonly": false, "cmd": "ln2fill highlevel"},
{"path": "smooth", "type": "float"},
@ -307,52 +327,33 @@
{"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": "send", "type": "text", "readonly": false, "cmd": "hefill send", "visibility": 3},
{"path": "status", "type": "text", "visibility": 3},
{"path": "state", "type": "text"},
{"path": "readlevel", "type": "text", "readonly": false, "cmd": "hefill readlevel", "visibility": 3},
{"path": "lowlevel", "type": "float", "readonly": false, "cmd": "hefill lowlevel"},
{"path": "highlevel", "type": "float", "readonly": false, "cmd": "hefill highlevel"},
{"path": "smooth", "type": "float"},
{"path": "minfillminutes", "type": "float", "readonly": false, "cmd": "hefill minfillminutes"},
{"path": "maxfillminutes", "type": "float", "readonly": false, "cmd": "hefill maxfillminutes"},
{"path": "minholdhours", "type": "float", "readonly": false, "cmd": "hefill minholdhours"},
{"path": "maxholdhours", "type": "float", "readonly": false, "cmd": "hefill maxholdhours"},
{"path": "tolerance", "type": "float", "readonly": false, "cmd": "hefill tolerance"},
{"path": "badreadingminutes", "type": "float", "readonly": false, "cmd": "hefill badreadingminutes"},
{"path": "tubecoolingminutes", "type": "float", "readonly": false, "cmd": "hefill tubecoolingminutes"},
{"path": "vessellimit", "type": "float", "readonly": false, "cmd": "hefill vessellimit"},
{"path": "vext", "type": "float"}]},
"mf": {"base": "/mf", "params": [
{"path": "", "type": "float", "readonly": false, "cmd": "run mf", "kids": 26},
{"path": "persmode", "type": "int", "readonly": false, "cmd": "mf persmode"},
{"path": "perswitch", "type": "int", "readonly": false, "cmd": "run mf"},
{"path": "perswitch", "type": "int"},
{"path": "nowait", "type": "int", "readonly": false, "cmd": "mf nowait"},
{"path": "maxlimit", "type": "float", "readonly": false, "cmd": "run mf", "visibility": 3},
{"path": "maxlimit", "type": "float", "visibility": 3},
{"path": "limit", "type": "float", "readonly": false, "cmd": "mf limit"},
{"path": "ramp", "type": "float", "readonly": false, "cmd": "mf ramp"},
{"path": "perscurrent", "type": "float", "readonly": false, "cmd": "mf perscurrent"},
{"path": "perslimit", "type": "float", "readonly": false, "cmd": "mf perslimit"},
{"path": "perswait", "type": "int", "readonly": false, "cmd": "mf perswait"},
{"path": "persdelay", "type": "int", "readonly": false, "cmd": "mf persdelay"},
{"path": "current", "type": "float", "readonly": false, "cmd": "run mf"},
{"path": "measured", "type": "float", "readonly": false, "cmd": "run mf"},
{"path": "voltage", "type": "float", "readonly": false, "cmd": "run mf"},
{"path": "lastfield", "type": "float", "readonly": false, "cmd": "run mf", "visibility": 3},
{"path": "ampRamp", "type": "float", "readonly": false, "cmd": "run mf", "visibility": 3},
{"path": "inductance", "type": "float", "readonly": false, "cmd": "run mf", "visibility": 3},
{"path": "current", "type": "float"},
{"path": "measured", "type": "float"},
{"path": "voltage", "type": "float"},
{"path": "lastfield", "type": "float", "visibility": 3},
{"path": "ampRamp", "type": "float", "visibility": 3},
{"path": "inductance", "type": "float", "visibility": 3},
{"path": "trainedTo", "type": "float", "readonly": false, "cmd": "mf trainedTo"},
{"path": "trainMode", "type": "int", "readonly": false, "cmd": "run mf"},
{"path": "trainMode", "type": "int"},
{"path": "external", "type": "int", "readonly": false, "cmd": "mf external"},
{"path": "startScript", "type": "text", "readonly": false, "cmd": "mf startScript", "visibility": 3},
{"path": "is_running", "type": "int", "readonly": false, "cmd": "run mf", "visibility": 3},
{"path": "is_running", "type": "int", "readonly": false, "cmd": "mf is_running", "visibility": 3},
{"path": "verbose", "type": "int", "readonly": false, "cmd": "mf verbose", "visibility": 3},
{"path": "driver", "type": "text", "readonly": false, "cmd": "run mf", "visibility": 3},
{"path": "creationCmd", "type": "text", "readonly": false, "cmd": "run mf", "visibility": 3},
{"path": "targetValue", "type": "float", "readonly": false, "cmd": "run mf"},
{"path": "driver", "type": "text", "visibility": 3},
{"path": "creationCmd", "type": "text", "visibility": 3},
{"path": "targetValue", "type": "float"},
{"path": "status", "type": "text", "readonly": false, "cmd": "mf status", "visibility": 3}]},
"lev": {"base": "/lev", "params": [
@ -362,7 +363,22 @@
{"path": "mode", "type": "enum", "enum": {"slow": 0, "fast (switches to slow automatically after filling)": 1}, "readonly": false, "cmd": "lev mode"},
{"path": "n2", "type": "float"}]},
"prep0": {"base": "/prep0", "params": [
{"path": "", "type": "text", "readonly": false, "cmd": "prep0", "kids": 2},
{"path": "send", "type": "text", "readonly": false, "cmd": "prep0 send", "visibility": 3},
{"path": "status", "type": "text", "visibility": 3}]}}
"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 ..."}]}}

75
frappy/addrparam.py Normal file
View File

@ -0,0 +1,75 @@
# *****************************************************************************
#
# 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 Parameter, Property
from frappy.datatypes import ValueType
class AddrParam(Parameter):
"""parameter with an address field
instead of implementing read_<param> and write_<param>, just implement
addressed_read and addressed_write.
"""
addr = Property('address', ValueType())
class AddrMixin:
"""mixin for addressed parameters
in case a read_<param> and/or write_<param> are not implemented,
they are created with a call to addressed_read and/or addressed_write
"""
def __init_subclass__(cls):
for aname, aobj in list(cls.__dict__.items()):
if isinstance(aobj, AddrParam):
methodname = f'read_{aname}'
if not hasattr(cls, methodname):
def rfunc(self, pname=aname):
return self.addressed_read(self.accessibles[pname])
setattr(cls, methodname, rfunc)
if not aobj.readonly:
methodname = f'write_{aname}'
if not hasattr(cls, methodname):
def wfunc(self, value, pname=aname):
return self.addressed_write(self.accessibles[pname], value)
setattr(cls, methodname, wfunc)
super().__init_subclass__()
def addressed_read(self, pobj):
"""addressed read
:param pobj: the AddrParam
:return: the value read
"""
return getattr(self, pobj.name)
def addressed_write(self, pobj, value):
"""addressed write
:param pobj: the AddrParam
:param value: the value to be written
:return: the value written or None
"""

View File

@ -233,13 +233,13 @@ class Parameter(Accessible):
try:
return instance.parameters[self.name].value
except KeyError:
raise ProgrammingError(f'optional parameter {self.name} it is not implemented') from None
raise ProgrammingError(f'optional parameter {self.name} is not implemented') from None
def __set__(self, obj, value):
try:
obj.announceUpdate(self.name, value)
except KeyError:
raise ProgrammingError(f'optional parameter {self.name} it is not implemented') from None
raise ProgrammingError(f'optional parameter {self.name} is not implemented') from None
def __set_name__(self, owner, name):
self.name = name

View File

@ -94,6 +94,7 @@ logger = MainLogger()
class Playground(Server):
def __init__(self, **kwds): # pylint: disable=super-init-not-called
self.name = 'playground'
for modname, cfg in kwds.items():
cfg.setdefault('description', modname)
self.log = logger.log

View File

@ -23,6 +23,9 @@
import os
import json
import socket
import select
from time import monotonic
from collections import namedtuple
from frappy.lib import closeSocket
from frappy.protocol.interface.tcp import format_address
@ -32,6 +35,79 @@ UDP_PORT = 10767
MAX_MESSAGE_LEN = 508
Answer = namedtuple('Answer',
'address, hostname, 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
try:
hostname = socket.gethostbyaddr(addr[0])[0]
except Exception:
hostname = addr[0]
return Answer(addr[0], hostname, port, eq_id, fw, desc)
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
seen = set()
start = monotonic()
while monotonic() < 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():
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:
yield answer
class UDPListener:
def __init__(self, equipment_id, description, ifaces, logger, *,
startup_broadcast=True):

451
frappy_psi/dilution.py Normal file
View File

@ -0,0 +1,451 @@
# *****************************************************************************
#
# 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:
# Andrea Plank <andrea.plank@psi.ch>
#
# *****************************************************************************
import time
from frappy.core import Readable, Drivable, Parameter, Attached, FloatRange, \
Command, IDLE, BUSY, WARN, ERROR, Property
from frappy.datatypes import EnumType, IntRange, BoolType, StructOf, StringType
from frappy.states import Retry, Finish, status_code, HasStates
from frappy.lib.enum import Enum
from frappy.errors import ImpossibleError, HardwareError
from frappy.addrparam import AddrParam, AddrMixin
from frappy.lib import formatStatusBits
from frappy.persistent import PersistentMixin, PersistentParam
from frappy_psi.logo import LogoMixin, DigitalActuator
T = Enum( # target states
off = 0,
sorbpumped = 2,
condense = 5,
remove = 7,
remove_and_sorbpump = 9,
remove_and_condense = 10,
manual = 11,
test = 12,
)
V = Enum(T, # value status inherits from target status
sorbpumping=1,
condensing=4,
circulating=6,
removing=8,
)
class Dilution(HasStates, Drivable):
condenseline_pressure = Attached()
condense_valve = Attached()
dump_valve = Attached()
forepump = Attached()
compressor = Attached(mandatory=False)
turbopump = Attached(mandatory=False)
condenseline_valve = Attached()
circuitshort_valve = Attached()
still_pressure = Attached()
still_pressure_turbo = Attached(mandatory=False)
value = Parameter('current state', EnumType(T), default=0)
target = Parameter('target state', EnumType(T), default=0)
sorbpumped = Parameter('sorb pump done', BoolType(), default=False)
dump_pressure = Attached()
#ls372 = Attached()
condensing_p_low = Parameter('Lower limit for condenseline pressure',
FloatRange(unit='mbar'), readonly=False, default=1200)
condensing_p_high = Parameter('Higher limit for condenseline pressure',
FloatRange(unit='mbar'), readonly=False, default=1500)
dump_target = Parameter('low dump pressure limit indicating end of condensation phase',
FloatRange(unit='mbar * min'), readonly=False, default=100)
pulse_factor = Parameter('factor for calculating V9 pulse length',
FloatRange(unit='mbar'), readonly=False, default=20)
end_condense_pressure = Parameter('low condense pressure indicating end of condensation phase',
FloatRange(unit='mbar'), readonly=False, default=500)
turbo_condense_pressure = Parameter('low condense pressure before turbo start',
FloatRange(unit='mbar'), readonly=False, default=900)
safe_turbo_pressure = Parameter('low still pressure before turbo start',
FloatRange(unit='mbar'), readonly=False, default=10)
turbo_off_speed = Parameter('speed to wait for after switching turbo off',
FloatRange(unit='Hz'), readonly=False, default=200)
end_remove_turbo_pressure = Parameter('pressure reached before end of remove (before turbo)',
FloatRange(unit='mbar'), readonly=False, default=1e-4)
end_remove_pressure = Parameter('pressure reached before end of remove (before fore pump)',
FloatRange(unit='mbar'), readonly=False, default=0.02)
st = StringType()
valve_set = StructOf(close=st, open=st, check_open=st, check_closed=st)
condense_valves = Parameter('valve to act when condensing', valve_set)
valves_after_remove = Parameter('valve to act after remove', valve_set)
check_after_remove = Parameter('check for manual valves after remove', valve_set)
_start_time = 0
init = True
_warn_manual_work = None
def write_target(self, target):
"""
if (target == Targetstates.SORBPUMP):
if self.value == target:
return self.target
self.start_machine(self.sorbpump)
self.value = Targetstates.SORBPUMP
return self.value
"""
self.log.info('start %s', target.name)
if self.value == target:
return target # not sure if this is correct. may be a step wants to be repeated?
try:
self.start_machine(getattr(self, target.name, None))
except Exception as e:
self.log.exception('error')
self.log.info('started %s', target.name)
return target
"""
@status_code(BUSY, 'sorbpump state')
def sorbpump(self, state):
#Heizt Tsorb auf und wartet ab.
if self.init:
self.ls372.write_target(40) #Setze Tsorb auf 40K
self.start_time = self.now
self.init = false
return Retry
if self.now - self.start_time < 2400: # 40 Minuten warten
return Retry
self.ls372.write_target(0)
if self.ls372.read_value() > 10: # Warten bis Tsorb unter 10K
return Retry
return self.condense
"""
@status_code(BUSY, 'start test')
def test(self, state):
"""Nur zum testen, ob UI funktioniert"""
self.init = False
if state.init:
state._start = state.now
return self.wait_test
@status_code(BUSY)
def wait_test(self, state):
if state.now < state.start + 20:
return Retry
return self.final_status(IDLE, 'end test')
@status_code(BUSY)
def condense(self, state):
"""Führt das Kondensationsverfahren durch."""
if state.init:
# self.value = V.condensing
pumpstate = self.forepump.read_value()
if self.turbopump:
self.turbopump.write_target(0)
self.handle_valves(**self.condense_valves)
self._start_time = state.now
if not pumpstate: # wait longer for starting fore pump
self._start_time += 10
return Retry
if self.wait_valves():
return Retry
self.check_valve_result()
return self.condensing
@status_code(BUSY)
def condensing(self, state):
pdump = self.dump_pressure.value # or self.dump_pressure.read_value() ?
pcond = self.condenseline_pressure.read_value()
v9 = self.condense_valve.read_value()
if v9:
if pcond > self.condensing_p_high:
self.log.info('shut V9')
self.condense_valve.write_target(0)
elif pcond < self.condensing_p_low and state.now > self._start_time + 5:
pulse_time = 60 * self.pulse_factor / pdump
if pulse_time > 59:
self.log.info('open V9')
self.condense_value.write_target(1)
else:
self.log.info('V9 pulse %r', pulse_time)
self._start_time = state.now
self.condense_valve.pulse(pulse_time)
if pdump > self.dump_target:
return Retry
self.condense_valve.write_target(1)
if self.turbopump is not None:
return self.condense_wait_before_turbo_start
return self.wait_for_condense_line_pressure
@status_code(BUSY, 'condense (wait before starting turbo)')
def condense_wait_before_turbo_start(self, state):
if (self.condenseline_pressure.read_value() > self.turbo_condense_pressure
and self.still_pressure.read_value() > self.safe_turbo_pressure):
return Retry
self.turbopump.write_target(1)
return self.wait_for_condense_line_pressure
@status_code(BUSY)
def wait_for_condense_line_pressure(self, state):
if self.condenseline_pressure.read_value() > self.end_condense_pressure:
return Retry
self.condense_valve.write_target(0)
return self.circulate
@status_code(BUSY)
def circulate(self, state):
"""Zirkuliert die Mischung."""
if state.init:
self.handle_valves(**self.condense_valves)
if self.wait_valves():
return Retry
self.check_valve_result()
self.value = V.circulating
return Finish
@status_code(BUSY, 'remove (wait for turbo shut down)')
def remove(self, state):
"""Entfernt die Mischung."""
if state.init:
self.handle_valves(**self.remove_valves)
if self.turbopump is not None:
self._start_time = state.now
self.turbopump.write_target(0)
return Retry
if self.turbopump is not None:
# if (state.now - self._start_time < self.turbo_off_delay or
if self.turbopump.read_speed() > self.turbo_off_speed:
return Retry
self.circuitshort_valve.write_target(1)
if self.turbopump is not None:
return self.remove_wait_for_still_pressure
return self.remove_endsequence
@status_code(BUSY, 'remove (wait for still pressure low)')
def remove_wait_for_still_pressure(self, state):
if self.still_pressure.read_value() > self.safe_turbo_pressure:
return Retry
self.turbopump.write_target(1)
return self.remove_endsequence
@status_code(BUSY)
def remove_endsequence(self, state):
if (self.still_pressure_turbo and
self.still_pressure_turbo.read_value() > self.end_remove_turbo_pressure):
return Retry
if self.still_pressure.read_value() > self.end_remove_pressure:
return Retry
self.circuitshort_valve.write_target(0)
self.dump_valve.write_target(0)
if self.compressor is not None:
self.compressor.write_target(0)
return self.close_valves_after_remove
@status_code(BUSY)
def close_valves_after_remove(self, state):
if state.init:
self.handle_valves(**self.valves_after_remove)
self.turbopump.write_target(0)
if self.wait_valves():
return Retry
self.check_valve_result()
self._warn_manual_work = True
return self.final_status(WARN, 'please check manual valves')
def read_status(self):
status = super().read_status()
if status[0] < ERROR and self._warn_manual_work:
try:
self.handle_valves(**self.check_after_remove)
self._warn_manual_work = False
except ImpossibleError:
return WARN, f'please close manual valves {",".join(self._valves_failed[False])}'
return status
def handle_valves(self, check_closed=(), check_open=(), close=(), open=()):
"""check ot set given valves
raises ImpossibleError, when checks fails
"""
self._valves_to_wait_for = {}
self._valves_failed = {True: [], False: []}
for flag, valves in enumerate([check_closed, check_open]):
for vname in valves.split():
if self.secNode.modules[vname].read_value() != flag:
self._valves_failed[flag].append(vname)
for flag, valves in enumerate([close, open]):
for vname in valves.split():
valve = self.secNode.modules[vname]
valve.write_target(flag)
if valve.isBusy():
self._valves_to_wait_for[vname] = (valve, flag)
elif valve.read_value() != flag:
self._valves_failed[flag].append(vname)
def wait_valves(self):
busy = False
for vname, (valve, flag) in dict(self._valves_to_wait_for.items()):
statuscode = valve.read_status()[0]
if statuscode == BUSY:
busy = True
continue
if valve.read_value() == flag and statuscode == IDLE:
self._valves_to_wait_for.pop(vname)
else:
self._valves_failed[flag].append(vname)
return busy
def check_valve_result(self):
result = []
for flag, valves in self._valves_failed.items():
if valves:
result.append(f"{','.join(valves)} not {'open' if flag else 'closed'}")
if result:
raise ImpossibleError(f"failed: {', '.join(result)}")
class DIL5(Dilution):
condense_valves = {
'close': 'V2 V4 V9',
'check_closed': 'MV10 MV13 MV8 MVB MV2',
'check_open': 'MV1 MV3a MV3b GV1 MV9 MV11 MV12 MV14',
'open': 'V1 V5 compressor forepump',
}
remove_valves = {
'close': 'V1 V2 V9',
'check_closed': 'MV10 MV13 MV8 MVB MV2',
'check_open': 'MV1 MV3a MV3b GV1 MV9 MV11 MV12 MV14',
'open': 'V4 V5 compressor forepump',
}
valves_after_remove = {
'close': 'V1 V2 V4 V5 V9',
'check_closed': 'MV10 MV13 MV8 MVB MV2',
'open': '',
'check_open': '',
}
check_after_remove = {
'close': '',
'check_closed': 'MV1 MV9 MV10 MV11 MV12',
'open': '',
'check_open': '',
}
class Interlock(LogoMixin, AddrMixin, Readable):
dil = Attached()
value = AddrParam('interlock state (bitmap)',
IntRange(0, 31), addr='V414', readonly=False)
p5lim = AddrParam('safety limit on p5 to protect forepump',
FloatRange(), value=1300, addr='VW16 VW18', readonly=False)
p2lim = AddrParam('safety limit on p2 to protect compressor',
FloatRange(), value=4000, addr='VW8 VW10', readonly=False)
p1lim = AddrParam('safety limit to protect dump',
FloatRange(), value=1300, addr='VW12 VW14', readonly=False)
p2max = AddrParam('limit pn p2 for mechanism to put mix to dump',
FloatRange(), value=3000, addr='VW20 VW22', readonly=False)
conditions = { # starting with bit 1
'off (p5>p5lim)': {'forepump': False},
'off (p2>p2lim)': {'compressor': False},
'off (p1>p2lim)': {'forepump': False, 'compressor': False},
'open (p2>p2max)': {'V4': True}}
reset_param = Property('addr for reset', StringType(), default='V418.1')
_mismatch = None
_prefix = ''
_actuators = None
def initModule(self):
super().initModule()
self._actuators = {}
for actions in self.conditions.values():
for modname in actions:
if modname not in self._actuators:
self._actuators[modname] = self.secNode.modules[modname]
def doPoll(self):
self.read_status() # this includes read_value
def initialReads(self):
super().initialReads()
self.reset()
@Command
def reset(self):
"""reset the interlock"""
self._prefix = ''
self.set_vm_value(self.reset_param, 1)
for actuator in self._actuators.values():
actuator.reset_fault()
if self.read_value() != 0:
raise HardwareError('can not clear status byte')
self.set_vm_value(self.reset_param, 0)
self.read_status() # update status (this may trigger ERROR again)
def read_status(self):
if self._mismatch is None: # init
self._mismatch = set()
bits = self.read_value()
if bits:
self.dil.stop()
keys = formatStatusBits(bits, self.conditions, 1)
statustext = []
for key in keys:
actions = self.conditions[key]
statustext.append(f"{' and '.join(actions)} {key}")
for module, value in actions.items():
modobj = self._actuators[module]
if modobj.target != value:
self._prefix = 'switched '
modobj.set_fault(value, f'switched {key}')
return ERROR, f"{self._prefix}{', '.join(statustext)}"
if self._mismatch:
return ERROR, f"mismatch on values for {', '.join(self._mismatch)}"
return IDLE, ''
def addressed_read(self, pobj):
values = [self.get_vm_value(a) for a in pobj.addr.split()]
if any(v != values[0] for v in values):
self._mismatch.add(pobj.name)
self.read_status()
else:
self._mismatch.discard(pobj.name)
return values[0]
def addressed_write(self, pobj, value):
for addr in pobj.addr.split():
self.set_vm_value(addr, value)
self.read_status()

View File

@ -1,320 +0,0 @@
# *****************************************************************************
#
# 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:
# Andrea Plank <andrea.plank@psi.ch>
#
# *****************************************************************************
from frappy.core import Drivable, Parameter, EnumType, Attached, FloatRange, \
Command, IDLE, BUSY, WARN, ERROR, Property
from frappy.datatypes import StatusType, EnumType, ArrayOf, BoolType, IntRange
from frappy.states import StateMachine, Retry, Finish, status_code, HasStates
from frappy.lib.enum import Enum
from frappy.errors import ImpossibleError
import time
Targetstates = Enum(
SORBPUMP = 0,
CONDENSE = 1,
CIRCULATE = 2,
REMOVE = 3,
MANUAL = 4,
TEST = 5,
STOP = 6,
)
class Dilution(HasStates, Drivable):
condenseline_pressure = Attached()
condense_valve = Attached()
dump_valve = Attached()
circulate_pump = Attached()
compressor = Attached(mandatory=False)
turbopump = Attached(mandatory=False)
condenseline_valve = Attached()
circuitshort_valve = Attached()
still_pressure = Attached()
#ls372 = Attached()
V5 = Attached() #Name noch ändern!!!
p1 = Attached() #Name noch ändern!!!
condensing_p_low = Property('Lower limit for condenseline pressure', IntRange())
condensing_p_high = Property('Higher limit for condenseline pressure', IntRange())
target = Parameter('target state', EnumType(Targetstates))
value = Parameter('current state', EnumType(Targetstates))
init = True
def write_target(self, target):
"""
if (target == Targetstates.SORBPUMP):
if self.value == target:
return self.target
self.start_machine(self.sorbpump)
self.value = Targetstates.SORBPUMP
return self.value
"""
if (target == Targetstates.TEST):
self.value = Targetstates.TEST
self.init = True
self.start_machine(self.test)
if (target == Targetstates.REMOVE):
if self.value == target:
return target
if self.value != Teststates.CIRCULATE:
self.final_status(WARN, "state before is not circulate")
return self.value
self.value = Targetstates.REMOVE
self.init = True
self.start_machine(self.remove)
elif (target == Targetstates.CIRCULATE):
if self.value == target:
return target
self.value = Targetstates.CIRCULATE
self.init = True
self.start_machine(self.circulate)
elif (target == Targetstates.CONDENSE):
if self.value == target:
return target
self.value = Targetstates.CONDENSE
self.init = True
self.start_machine(self.condense)
elif(target == Targetstates.MANUAL):
self.value = Targetstates.MANUAL
self.stop_machine()
elif (target == Targetstates.STOP):
self.value = Targetstates.STOP
self.stop_machine()
return self.value
"""
@status_code(BUSY, 'sorbpump state')
def sorbpump(self, state):
#Heizt Tsorb auf und wartet ab.
if self.init:
self.ls372.write_target(40) #Setze Tsorb auf 40K
self.start_time = self.now
self.init = false
return Retry
if self.now - self.start_time < 2400: # 40 Minuten warten
return Retry
self.ls372.write_target(0)
if self.ls372.read_value() > 10: # Warten bis Tsorb unter 10K
return Retry
return self.condense
"""
@status_code(BUSY, 'test mode')
def test(self, state):
"Nur zum testen, ob UI funktioniert"
self.init = False
self.condense_valve.write_target(1)
time.sleep(1)
self.condense_valve.write_target(0)
self.dump_valve.write_target(1)
time.sleep(1)
self.dump_valve.write_target(0)
self.compressor.write_target(1)
return True
@status_code(BUSY)
def wait_for_condense_line_pressure(self, state):
if (self.condenseline_pressure.read_value > 500):
return Retry
self.condense_valve.write_target(0)
return self.circulate
def initialize_condense_valves(self, state):
raise NotImplementedError
@status_code(BUSY)
def condense(self, state):
"""Führt das Kondensationsverfahren durch."""
if state.init:
self.initialize_condense_valves()
self.circuitshort_valve.write_target(0)
self.dump_valve.write_target(0)
self.condense_valve.write_target(0)
self.condenseline_valve.write_target(1)
self.V5.write_target(1)
if (self.compressor is not None):
self.compressor.write_target(1)
self.circulate_pump.write_target(1)
return Retry
if self.condenseline_pressure.read_value() < self.condensing_p_low:
self.condense_valve.write_target(1)
elif (self.condenseline_pressure.read_value() > self.condensing_p_high):
self.condense_valve.write_target(0)
if (self.p1.read_value() > 20):
return Retry
self.condense_valve.write_target(1)
if (self.turbopump is not None):
return self.condense_wait_before_turbo_start
return self.wait_for_condense_line_pressure
@status_code(BUSY, 'condense (wait before starting turbo)')
def condense_wait_before_turbo_start(self, state):
if (self.condenseline_pressure.read_value() > 900 and self.still_pressure.read_value() > 10):
return Retry
else:
self.turbopump.write_target(1)
return self.wait_for_condense_line_pressure
def initialize_circulation_valves(self, state):
raise NotImplementedError
@status_code(BUSY)
def circulate(self, state):
"""Zirkuliert die Mischung."""
if state.init:
self.initialize_circulation_valves()
return Retry
@status_code(BUSY, 'remove (wait for turbo shut down)')
def remove(self, state):
"""Entfernt die Mischung."""
if state.init:
self.condenseline_valve.write_target(0)
self.dump_valve.write_target(1)
self.start_time = self.now
return Retry
if self.turbopump is not None:
self.turbopump.write_target(0)
if (self.now - self.start_time < 300 or self.turbopump.read_speed() > 60):
return Retry
self.circuitshort_valve.write_target(1)
if self.turbopump is not None:
return self.remove_wait_for_still_pressure
return remove_endsequence
@status_code(BUSY, 'remove (wait for still pressure low)')
def remove_wait_for_still_pressure(self, state):
if self.still_pressure.read_value() > 20:
return Retry
self.turbopump.write_target(1)
return self.remove_endsequence
@status_code(BUSY)
def remove_endsequence(self, state):
if self.still_pressure.read_value() > 1e-4:
return Retry
self.circuitshort_valve.write_target(0)
self.dump_valve.write_target(0)
if self.compressor is not None:
self.compressor.write_target(0)
self.remove_check_manual_valves()
self.remove_close_valves()
self.circulate_pump.write_target(0)
return Finish
def remove_check_manual_valves(self):
raise NotImplementedError
def remove_close_valves(self):
raise NotImplementedError
class DIL5(Dilution):
MV10 = Attached()
MV13 = Attached()
MV8 = Attached()
MVB = Attached()
MV2 = Attached()
MV1 = Attached()
MV3a = Attached()
MV3b = Attached()
GV1 = Attached()
MV14 = Attached()
MV12 = Attached()
MV11 = Attached()
MV9 = Attached()
GV2 = Attached()
def earlyInit(self):
self.circulate_closed_valves = [self.condense_valve, self.dump_valve, self.circuitshort_valve, self.MV10, self.MV13, self.MV8, self.MVB, self.MV2]
self.circulate_open_valves = [self.MV11, self.circulate_pump, self.GV2, self.V5, self.compressor, self.condenseline_valve, self.MV1, self.MV3a, self.MV3b, self.GV1, self.MV9, self.MV14]
self.condense_closed_valves = [self.MV10, self.MV13, self.MV8, self.MVB, self.MV2]
self.condense_open_valves = [self.MV1, self.MV3a, self.MV3b, self.GV1, self.MV9, self.MV14, self.MV12, self.MV11]
self.remove_check_closed_valves = [self.MV11, self.MV9, self.MV12, self.MV1]
self.remove_closed_valves = [self.condenseline_valve, self.circuitshort_valve, self.V5, self.condense_valve, self.dump_valve]
super().earlyInit()
def initialize_condense_valves(self):
#Anfangszustand der Ventile überprüfen
for valve in self.condense_open_valves:
if valve.read_value() == 0:
self.stop_machine()
raise ImpossibleError(f'valve {valve.name} must be open')
for valve in self.condense_closed_valves:
if valve.read_value == 1:
self.stop_machine()
return ImpossibleError(f'valve {valve.name} must be closed')
def initialize_circulation_valves(self):
#Anfangszustand der Ventile überprüfen
self.value = Targetstates.CIRCULATE
for valve in self.circulate_closed_valves:
if (valve.read_value() == 1):
self.stop_machine()
raise ImpossibleError(f'valve {valve.name} must be open')
for valve in self.circulate_open_valves:
if (valve.read_value() == 0):
valve.write_target(1)
self.stop_machine()
raise ImpossibleError(f'valve {valve.name} must be open')
def remove_check_manual_valves(self):
for valve in self.remove_check_closed_valves:
if (valve.read_value() == 1):
self.final_status(WARN, "manual valve {valve.name} must be closed")
def remove_close_valves(self):
for valve in self.remove_closed_valves:
valve.write_target(0)

View File

@ -17,17 +17,17 @@
#
#
# *****************************************************************************
import sys
from time import monotonic
from ast import literal_eval
import snap7
from frappy.core import Readable, Parameter, FloatRange, HasIO, StringIO, Property, StringType,IDLE, BUSY, WARN, ERROR,Writable, Drivable, BoolType, IntRange, Communicator
from frappy.errors import CommunicationFailedError
from frappy.core import Attached, Command, Readable, Parameter, FloatRange, HasIO, Property, StringType, \
IDLE, BUSY, WARN, ERROR, Writable, Drivable, BoolType, IntRange, Communicator, StatusType
from frappy.errors import CommunicationFailedError, ConfigError
from threading import RLock
import sys
import time
class IO(Communicator):
tcap_client = Property('tcap_client', IntRange())
tsap_server = Property('tcap_server', IntRange())
ip_address = Property('numeric ip address', StringType())
@ -37,30 +37,28 @@ class IO(Communicator):
def initModule(self):
self._lock = RLock()
super().initModule()
def _init(self):
if not self._plc:
if time.time() < self._last_try + 10:
raise CommunicationFailedError('logo PLC not reachable')
self._plc = snap7.logo.Logo()
prev_stderr = sys.stdout
sys.stderr = open('/dev/null', 'w') # suppress output of snap7
try:
self._plc.connect(self.ip_address, self.tcap_client, self.tsap_server)
if self._plc.get_connected():
return
except Exception:
pass
finally:
sys.stderr = prev_stderr
self._plc = None
self._last_try = time.time()
raise CommunicationFailedError('logo PLC not reachable')
if monotonic() < self._last_try + 10:
raise CommunicationFailedError('logo PLC not reachable')
self._plc = snap7.logo.Logo()
sys.stderr = open('/dev/null', 'w') # suppress output of snap7
try:
self._plc.connect(self.ip_address, self.tcap_client, self.tsap_server)
if self._plc.get_connected():
return
except Exception:
pass
finally:
sys.stderr = sys.stdout
self._plc = None
self._last_try = monotonic()
raise CommunicationFailedError('logo PLC not reachable')
def communicate(self, cmd):
with self._lock:
self._init()
if not self._plc:
self._init()
cmd = cmd.split(maxsplit=1)
if len(cmd) == 2:
self.comLog('> %s %s', cmd[0], cmd[1])
@ -76,59 +74,207 @@ class IO(Communicator):
self.comLog('? %r', e)
self.log.exception('error in plc read')
self._plc = None
raise
class Snap7Mixin(HasIO):
raise
class LogoMixin(HasIO):
ioclass = IO
def get_vm_value(self, vm_address):
return literal_eval(self.io.communicate(vm_address))
def set_vm_value(self, vm_address, value):
return literal_eval(self.io.communicate(f'{vm_address} {value}'))
return literal_eval(self.io.communicate(f'{vm_address} {round(value)}'))
class DigitalActuator(LogoMixin, Writable):
"""output with or without feedback"""
output_addr = Property('VM address output', datatype=StringType(), default='')
target_addr = Property('VM address target', datatype=StringType(), default='')
feedback_addr = Property('VM address feedback', datatype=StringType(), default='')
target = Parameter('target', datatype=BoolType())
value = Parameter('feedback or output', datatype=BoolType())
_input = 'output'
_value_addr = None
_target_addr = None
_fault = None
def doPoll(self):
self.read_status() # this calls also read_value
def checkProperties(self):
super().checkProperties()
if self.feedback_addr:
self._input = 'feedback'
self._value_addr = self.feedback_addr
else:
self._input = 'output'
self._value_addr = self.output_addr
self._target_addr = self.target_addr or self.output_addr
if self._target_addr and self._value_addr:
self._check_feedback = self.feedback_addr and self.output_addr
return
raise ConfigError('need either output_addr or both feedback_addr and target_addr')
def initialReads(self):
super().initialReads()
self.target = self.read_value()
def set_fault(self, value, statustext):
"""on a fault condition, set target to value
and status to statustext
"""
self.write_target(value)
self._fault = statustext
self.read_status()
def reset_fault(self):
"""reset fault condition"""
self._fault = None
self.read_status()
class Pressure(Snap7Mixin, Readable):
vm_address = Property('VM address', datatype= StringType())
value = Parameter('pressure', datatype = FloatRange(unit = 'mbar'))
#pollinterval = 0.5
def read_value(self):
return self.get_vm_value(self.vm_address)
return self.get_vm_value(self._value_addr)
def write_target(self, target):
self._fault = None
self.set_vm_value(self._target_addr, target)
value = self.read_value()
if value != target and self.feedback_addr:
# retry only if we have a feedback and the feedback did not change yet
for i in range(20):
if self.read_value() == target:
self.log.debug('tried %d times', i)
break
self.set_vm_value(self._target_addr, target)
def read_status(self):
if self._fault:
return ERROR, self._fault
value = self.read_value()
if value != self.target:
return ERROR, 'value and target do not match'
if self._check_feedback:
if value != self.get_vm_value(self._check_feedback):
return ERROR, f'feedback does not match output'
if self.feedback_addr:
return IDLE, 'feedback confirmed'
return IDLE, ''
class DelayedActuator(DigitalActuator, Drivable):
delay_addr = Property('address of delay value', StringType())
_pulse_start = 0
_pulse_end = 0
_fault = None
def read_status(self):
if self._fault:
return ERROR, self._fault
value = self.read_value()
fberror = None
if self._pulse_start:
now = monotonic()
if now < self._pulse_start + 1:
value = 1
elif now < self._pulse_end - 1:
if not value:
self._pulse_start = 0
return WARN, f'{self._input} is not on during pulse - due to interlock?'
if value:
if now < self._pulse_end + 1:
return BUSY, 'pulsing'
self.log.warn('pulse timeout')
self.set_vm_value(self._target_addr, 0)
self._pulse_start = 0
self.set_vm_value(self.delay_addr, 0)
elif self._check_feedback and value != self.get_vm_value(self._check_feedback):
fberror = ERROR, f'feedback does not match output'
if value != self.target:
return ERROR, 'value does not match target'
self.setFastPoll(False)
if fberror:
return fberror
if self.feedback_addr:
return IDLE, 'feedback confirmed'
return IDLE, ''
def write_target(self, value):
self._pulse_start = 0
if not value:
self.set_vm_value(self.delay_addr, 0)
return super().write_target(value)
@Command(argument=FloatRange(0))
def pulse(self, delay):
"""open for delay seconds"""
self.set_vm_value(self.delay_addr, delay)
self.set_vm_value(self._target_addr, 1)
self.set_vm_value(self._target_addr, 0)
self.setFastPoll(True, 0.5)
self.status = BUSY, 'pulsing'
now = monotonic()
self._pulse_start = now
self._pulse_end = now + delay
class Value(LogoMixin, Readable):
addr = Property('VM address', datatype=StringType())
def read_value(self):
return self.get_vm_value(self.addr)
def read_status(self):
return IDLE, ''
class Airpressure(Snap7Mixin, Readable):
vm_address = Property('VM address', datatype= StringType())
value = Parameter('airpressure state', datatype = BoolType())
#pollinterval = 0.5
class DigitalValue(Value):
value = Parameter('airpressure state', datatype=BoolType())
# TODO: the following classes are too specific, they have to be moved
class Pressure(LogoMixin, Drivable):
vm_address = Property('VM address', datatype=StringType())
value = Parameter('pressure', datatype=FloatRange(unit='mbar'))
# pollinterval = 0.5
def read_value(self):
return self.get_vm_value(self.vm_address)
def read_status(self):
return IDLE, ''
class Airpressure(LogoMixin, Readable):
vm_address = Property('VM address', datatype=StringType())
value = Parameter('airpressure state', datatype=BoolType())
# pollinterval = 0.5
def read_value(self):
if (self.get_vm_value(self.vm_address) > 500):
return 1
else:
return 0
def read_status(self):
return IDLE, ''
class Valve(Snap7Mixin, Drivable):
vm_address_input = Property('VM address input', datatype= StringType())
vm_address_output = Property('VM address output', datatype= StringType())
target = Parameter('Valve target', datatype = BoolType())
value = Parameter('Value state', datatype = BoolType())
def read_status(self):
return IDLE, ''
class Valve(LogoMixin, Drivable):
vm_address_input = Property('VM address input', datatype=StringType())
vm_address_output = Property('VM address output', datatype=StringType())
target = Parameter('Valve target', datatype=BoolType())
value = Parameter('Value state', datatype=BoolType())
_remaining_tries = None
def read_value(self):
return self.get_vm_value(self.vm_address_input)
def write_target(self, target):
self.set_vm_value(self.vm_address_output, target)
self._remaining_tries = 5
@ -142,127 +288,126 @@ class Valve(Snap7Mixin, Drivable):
if value != self.target:
if self._remaining_tries is None:
self.target = self.read_value()
return IDLE,''
return IDLE, ''
self._remaining_tries -= 1
if self._remaining_tries < 0:
self.setFastPoll(False)
return ERROR, 'too many tries to switch'
self.set_vm_value(self.vm_address_output, self.target)
self.set_vm_value(self.vm_address_output, self.target)
return BUSY, 'switching (try again)'
self.setFastPoll(False)
return IDLE, ''
class FluidMachines(Snap7Mixin, Drivable):
vm_address_output = Property('VM address output', datatype= StringType())
target = Parameter('Valve target', datatype = BoolType())
value = Parameter('Value state', datatype = BoolType())
class FluidMachines(LogoMixin, Drivable):
vm_address_output = Property('VM address output', datatype=StringType())
target = Parameter('Valve target', datatype=BoolType())
value = Parameter('Valve state', datatype=BoolType())
def read_value(self):
return self.get_vm_value(self.vm_address_output)
def write_target(self, target):
return self.set_vm_value(self.vm_address_output, target)
def read_status(self):
return IDLE, ''
class TempSensor(Snap7Mixin, Readable):
vm_address = Property('VM address', datatype= StringType())
value = Parameter('resistance', datatype = FloatRange(unit = 'Ohm'))
def read_value(self):
return self.get_vm_value(self.vm_address)
def read_status(self):
return IDLE, ''
class HeaterParam(Snap7Mixin, Writable):
vm_address = Property('VM address output', datatype= StringType())
target = Parameter('Heater target', datatype = IntRange())
value = Parameter('Heater Param', datatype = IntRange())
class TempSensor(LogoMixin, Readable):
vm_address = Property('VM address', datatype=StringType())
value = Parameter('resistance', datatype=FloatRange(unit='Ohm'))
def read_value(self):
return self.get_vm_value(self.vm_address)
def read_status(self):
return IDLE, ''
class HeaterParam(LogoMixin, Writable):
vm_address = Property('VM address output', datatype=StringType())
target = Parameter('Heater target', datatype=IntRange())
value = Parameter('Heater Param', datatype=IntRange())
def read_value(self):
return self.get_vm_value(self.vm_address)
def write_target(self, target):
return self.set_vm_value(self.vm_address, target)
def read_status(self):
return IDLE, ''
return IDLE, ''
class controlHeater(Snap7Mixin, Writable):
vm_address = Property('VM address on switch', datatype= StringType())
target = Parameter('Heater state', datatype = BoolType())
value = Parameter('Heater state', datatype = BoolType())
class controlHeater(LogoMixin, Writable):
vm_address = Property('VM address on switch', datatype=StringType())
target = Parameter('Heater state', datatype=BoolType())
value = Parameter('Heater state', datatype=BoolType())
def read_value(self):
return self.get_vm_value(self.vm_address_on)
def write_target(self, target):
if (target):
return self.set_vm_value(self.vm_address, True)
else:
return self.set_vm_value(self.vm_address, False)
def read_status(self):
return IDLE, ''
class safetyfeatureState(Snap7Mixin, Readable):
vm_address = Property('VM address state', datatype= StringType())
value = Parameter('safety Feature state', datatype = BoolType())
def read_value(self):
return self.get_vm_value(self.vm_address)
def read_status(self):
return IDLE, ''
return IDLE, ''
class safetyfeatureParam(Snap7Mixin, Writable):
vm_address = Property('VM address output', datatype= StringType())
target = Parameter('safety Feature target', datatype = IntRange())
value = Parameter('safety Feature Param', datatype = IntRange())
class safetyfeatureState(LogoMixin, Readable):
vm_address = Property('VM address state', datatype=StringType())
value = Parameter('safety Feature state', datatype=BoolType())
def read_value(self):
return self.get_vm_value(self.vm_address)
def read_status(self):
return IDLE, ''
class safetyfeatureParam(LogoMixin, Writable):
vm_address = Property('VM address output', datatype=StringType())
target = Parameter('safety Feature target', datatype=IntRange())
value = Parameter('safety Feature Param', datatype=IntRange())
def read_value(self):
return self.get_vm_value(self.vm_address)
def write_target(self, target):
return self.set_vm_value(self.vm_address, target)
def read_status(self):
return IDLE, ''
return IDLE, ''
class comparatorgekoppeltParam(Snap7Mixin, Writable):
vm_address_1 = Property('VM address output', datatype= StringType())
vm_address_2 = Property('VM address output', datatype= StringType())
target = Parameter('safety Feature target', datatype = IntRange())
value = Parameter('safety Feature Param', datatype = IntRange())
class comparatorgekoppeltParam(LogoMixin, Writable):
vm_address_1 = Property('VM address output', datatype=StringType())
vm_address_2 = Property('VM address output', datatype=StringType())
target = Parameter('safety Feature target', datatype=IntRange())
value = Parameter('safety Feature Param', datatype=IntRange())
def read_value(self):
return self.get_vm_value(self.vm_address_1)
def write_target(self, target):
self.set_vm_value(self.vm_address_1, target)
return self.set_vm_value(self.vm_address_2, target)
def read_status(self):
return IDLE, ''
def read_status(self):
return IDLE, ''

View File

@ -19,20 +19,14 @@
#
# *****************************************************************************
from frappy.core import Readable, Parameter, FloatRange, BoolType, StringIO, HasIO, \
Property, StringType, Writable, IntRange, IDLE, BUSY, ERROR
from frappy.errors import CommunicationFailedError
from frappy.core import Parameter, BoolType, Writable
from frappy.persistent import PersistentMixin, PersistentParam
class ManualValve(Writable):
target = Parameter('Valve target', datatype = BoolType())
value = Parameter('Valve state', datatype = BoolType())
def read_value(self):
return self.value
class ManualValve(PersistentMixin, Writable):
target = PersistentParam('Valve target', datatype=BoolType(), persistent='auto')
value = Parameter('Valve state', datatype=BoolType())
def write_target(self, target):
self.value = target
return self.value
def read_status(self):
return IDLE, ''

View File

@ -129,7 +129,7 @@ class PfeifferMixin(HasIO):
class RPT200(PfeifferMixin, Readable):
value = Parameter('Pressure', FloatRange(unit='hPa'))
value = Parameter('Pressure', FloatRange(unit='mbar'))
def read_value(self):
return self.data_request_u_expo_new(740)
@ -143,11 +143,11 @@ class RPT200(PfeifferMixin, Readable):
class TCP400(PfeifferMixin, Drivable, Readable):
speed= Parameter('Rotational speed', FloatRange(unit = 'Hz'), readonly = False)
speed= Parameter('Rotational speed', FloatRange(unit='Hz'), readonly = False)
target= Parameter('Pumping station', BoolType())
current= Parameter('Current consumption', FloatRange(unit = '%'))
current= Parameter('Current consumption', FloatRange(unit='%'))
value = Parameter('Turbopump state', BoolType())
temp = Parameter('temp', FloatRange(unit = 'C'))
temp = Parameter('temp', FloatRange(unit='C'))
def read_temp (self):
return self.data_request_u_int(326)

View File

@ -213,7 +213,10 @@ class SeaClient(ProxyClient, Module):
if not self._connected.is_set():
if self._connect_thread is None:
# let doPoll do the reconnect
self.pollInfo.trigger(True)
if self.pollInfo: # is None in playground
self.pollInfo.trigger(True)
else:
self.log.info('sea_main.doPoll() will connect')
raise ConnectionClosed('disconnected - reconnect is tried later')
return self.raw_request(command, quiet)
@ -557,10 +560,12 @@ class SeaModule(Module):
else:
cls.paramFilter(result, paramdesc)
cfgdict.pop('rel_paths', None)
params = sum(result.values(), [])
params = []
for key, plist in result.items():
params.extend(plist)
if is_running: # take this at end
params.append(is_running)
# this needs some care in the configuration: the main value must be the first!
main_value = params[0]
if issubclass(cls, Readable):
if 'key' in main_value:
@ -675,7 +680,7 @@ class SeaModule(Module):
if key != 'status' and key is not None:
attributes['read_' + key] = rfunc
if not readonly:
if not readonly and key:
def wfunc(self, value, datatype=datatype, command=paramdesc['cmd']):
value = datatype.export_value(value)
@ -711,9 +716,15 @@ class SeaModule(Module):
@classmethod
def paramFilter(cls, result, paramdesc):
sub = paramdesc['path'].split('/', 1)
sublist = result.get(sub[0])
sublist = result.get(sub[0] or '.')
if sublist is None:
return False
sublist = result.get('.')
if sublist is None:
return False
if len(sub) > 1 or 'kids' in paramdesc:
# avoid params from subtrees
return False
# this is a top paramerter without kids
sublist.append(paramdesc)
return True